From 38be0d13830efd2d98281c645c3a60afe05ffece Mon Sep 17 00:00:00 2001 From: Qt by Nokia Date: Wed, 27 Apr 2011 12:05:43 +0200 Subject: Initial import from the monolithic Qt. This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12 --- src/gui/QtGui.dynlist | 8 + src/gui/accessible/accessible.pri | 25 + src/gui/accessible/qaccessible.cpp | 1094 ++ src/gui/accessible/qaccessible.h | 428 + src/gui/accessible/qaccessible2.cpp | 205 + src/gui/accessible/qaccessible2.h | 246 + src/gui/accessible/qaccessible_mac.mm | 2469 ++++ src/gui/accessible/qaccessible_mac_carbon.cpp | 119 + src/gui/accessible/qaccessible_mac_cocoa.mm | 239 + src/gui/accessible/qaccessible_mac_p.h | 479 + src/gui/accessible/qaccessible_unix.cpp | 134 + src/gui/accessible/qaccessible_win.cpp | 1217 ++ src/gui/accessible/qaccessiblebridge.cpp | 158 + src/gui/accessible/qaccessiblebridge.h | 92 + src/gui/accessible/qaccessibleobject.cpp | 410 + src/gui/accessible/qaccessibleobject.h | 140 + src/gui/accessible/qaccessibleplugin.cpp | 107 + src/gui/accessible/qaccessibleplugin.h | 87 + src/gui/accessible/qaccessiblewidget.cpp | 1031 ++ src/gui/accessible/qaccessiblewidget.h | 141 + src/gui/animation/animation.pri | 3 + src/gui/animation/qguivariantanimation.cpp | 98 + src/gui/dialogs/dialogs.pri | 122 + src/gui/dialogs/images/fit-page-24.png | Bin 0 -> 985 bytes src/gui/dialogs/images/fit-page-32.png | Bin 0 -> 1330 bytes src/gui/dialogs/images/fit-width-24.png | Bin 0 -> 706 bytes src/gui/dialogs/images/fit-width-32.png | Bin 0 -> 1004 bytes src/gui/dialogs/images/go-first-24.png | Bin 0 -> 796 bytes src/gui/dialogs/images/go-first-32.png | Bin 0 -> 985 bytes src/gui/dialogs/images/go-last-24.png | Bin 0 -> 792 bytes src/gui/dialogs/images/go-last-32.png | Bin 0 -> 984 bytes src/gui/dialogs/images/go-next-24.png | Bin 0 -> 782 bytes src/gui/dialogs/images/go-next-32.png | Bin 0 -> 948 bytes src/gui/dialogs/images/go-previous-24.png | Bin 0 -> 797 bytes src/gui/dialogs/images/go-previous-32.png | Bin 0 -> 945 bytes src/gui/dialogs/images/layout-landscape-24.png | Bin 0 -> 820 bytes src/gui/dialogs/images/layout-landscape-32.png | Bin 0 -> 1353 bytes src/gui/dialogs/images/layout-portrait-24.png | Bin 0 -> 817 bytes src/gui/dialogs/images/layout-portrait-32.png | Bin 0 -> 1330 bytes src/gui/dialogs/images/page-setup-24.png | Bin 0 -> 620 bytes src/gui/dialogs/images/page-setup-32.png | Bin 0 -> 1154 bytes src/gui/dialogs/images/print-24.png | Bin 0 -> 914 bytes src/gui/dialogs/images/print-32.png | Bin 0 -> 1202 bytes src/gui/dialogs/images/qtlogo-64.png | Bin 0 -> 2991 bytes src/gui/dialogs/images/status-color.png | Bin 0 -> 1475 bytes src/gui/dialogs/images/status-gray-scale.png | Bin 0 -> 1254 bytes src/gui/dialogs/images/view-page-multi-24.png | Bin 0 -> 390 bytes src/gui/dialogs/images/view-page-multi-32.png | Bin 0 -> 556 bytes src/gui/dialogs/images/view-page-one-24.png | Bin 0 -> 662 bytes src/gui/dialogs/images/view-page-one-32.png | Bin 0 -> 810 bytes src/gui/dialogs/images/view-page-sided-24.png | Bin 0 -> 700 bytes src/gui/dialogs/images/view-page-sided-32.png | Bin 0 -> 908 bytes src/gui/dialogs/images/zoom-in-24.png | Bin 0 -> 1302 bytes src/gui/dialogs/images/zoom-in-32.png | Bin 0 -> 1873 bytes src/gui/dialogs/images/zoom-out-24.png | Bin 0 -> 1247 bytes src/gui/dialogs/images/zoom-out-32.png | Bin 0 -> 1749 bytes src/gui/dialogs/qabstractpagesetupdialog.cpp | 139 + src/gui/dialogs/qabstractpagesetupdialog.h | 82 + src/gui/dialogs/qabstractpagesetupdialog_p.h | 88 + src/gui/dialogs/qabstractprintdialog.cpp | 497 + src/gui/dialogs/qabstractprintdialog.h | 129 + src/gui/dialogs/qabstractprintdialog_p.h | 95 + src/gui/dialogs/qcolordialog.cpp | 2114 ++++ src/gui/dialogs/qcolordialog.h | 150 + src/gui/dialogs/qcolordialog_mac.mm | 500 + src/gui/dialogs/qcolordialog_p.h | 142 + src/gui/dialogs/qcolordialog_symbian.cpp | 107 + src/gui/dialogs/qdialog.cpp | 1272 ++ src/gui/dialogs/qdialog.h | 140 + src/gui/dialogs/qdialog_p.h | 113 + src/gui/dialogs/qdialogsbinarycompat_win.cpp | 137 + src/gui/dialogs/qerrormessage.cpp | 429 + src/gui/dialogs/qerrormessage.h | 88 + src/gui/dialogs/qfiledialog.cpp | 3486 +++++ src/gui/dialogs/qfiledialog.h | 331 + src/gui/dialogs/qfiledialog.ui | 356 + src/gui/dialogs/qfiledialog_embedded.ui | 340 + src/gui/dialogs/qfiledialog_mac.mm | 1157 ++ src/gui/dialogs/qfiledialog_p.h | 427 + src/gui/dialogs/qfiledialog_symbian.cpp | 199 + src/gui/dialogs/qfiledialog_win.cpp | 825 ++ src/gui/dialogs/qfiledialog_win_p.h | 243 + src/gui/dialogs/qfileinfogatherer.cpp | 355 + src/gui/dialogs/qfileinfogatherer_p.h | 207 + src/gui/dialogs/qfilesystemmodel.cpp | 2027 +++ src/gui/dialogs/qfilesystemmodel.h | 180 + src/gui/dialogs/qfilesystemmodel_p.h | 337 + src/gui/dialogs/qfontdialog.cpp | 1077 ++ src/gui/dialogs/qfontdialog.h | 147 + src/gui/dialogs/qfontdialog_mac.mm | 699 + src/gui/dialogs/qfontdialog_p.h | 166 + src/gui/dialogs/qfscompleter_p.h | 82 + src/gui/dialogs/qinputdialog.cpp | 1489 +++ src/gui/dialogs/qinputdialog.h | 256 + src/gui/dialogs/qmessagebox.cpp | 2751 ++++ src/gui/dialogs/qmessagebox.h | 365 + src/gui/dialogs/qmessagebox.qrc | 5 + src/gui/dialogs/qnspanelproxy_mac.mm | 228 + src/gui/dialogs/qpagesetupdialog.cpp | 240 + src/gui/dialogs/qpagesetupdialog.h | 112 + src/gui/dialogs/qpagesetupdialog_mac.mm | 315 + src/gui/dialogs/qpagesetupdialog_unix.cpp | 620 + src/gui/dialogs/qpagesetupdialog_unix_p.h | 105 + src/gui/dialogs/qpagesetupdialog_win.cpp | 168 + src/gui/dialogs/qpagesetupwidget.ui | 353 + src/gui/dialogs/qprintdialog.h | 174 + src/gui/dialogs/qprintdialog.qdoc | 58 + src/gui/dialogs/qprintdialog.qrc | 38 + src/gui/dialogs/qprintdialog_mac.mm | 429 + src/gui/dialogs/qprintdialog_qws.cpp | 567 + src/gui/dialogs/qprintdialog_unix.cpp | 1309 ++ src/gui/dialogs/qprintdialog_win.cpp | 316 + src/gui/dialogs/qprintpreviewdialog.cpp | 802 ++ src/gui/dialogs/qprintpreviewdialog.h | 107 + src/gui/dialogs/qprintpropertieswidget.ui | 70 + src/gui/dialogs/qprintsettingsoutput.ui | 363 + src/gui/dialogs/qprintwidget.ui | 116 + src/gui/dialogs/qprogressdialog.cpp | 907 ++ src/gui/dialogs/qprogressdialog.h | 145 + src/gui/dialogs/qsidebar.cpp | 509 + src/gui/dialogs/qsidebar_p.h | 158 + src/gui/dialogs/qwizard.cpp | 3928 ++++++ src/gui/dialogs/qwizard.h | 268 + src/gui/dialogs/qwizard_win.cpp | 759 ++ src/gui/dialogs/qwizard_win_p.h | 158 + src/gui/effects/effects.pri | 4 + src/gui/effects/qgraphicseffect.cpp | 1235 ++ src/gui/effects/qgraphicseffect.h | 289 + src/gui/effects/qgraphicseffect_p.h | 231 + src/gui/egl/egl.pri | 46 + src/gui/egl/qegl.cpp | 753 ++ src/gui/egl/qegl_p.h | 242 + src/gui/egl/qegl_qpa.cpp | 111 + src/gui/egl/qegl_qws.cpp | 114 + src/gui/egl/qegl_stub.cpp | 301 + src/gui/egl/qegl_symbian.cpp | 88 + src/gui/egl/qegl_wince.cpp | 85 + src/gui/egl/qegl_x11.cpp | 471 + src/gui/egl/qeglcontext_p.h | 117 + src/gui/egl/qeglproperties.cpp | 563 + src/gui/egl/qeglproperties_p.h | 99 + src/gui/egl/qeglproperties_stub.cpp | 148 + src/gui/embedded/directfb.pri | 40 + src/gui/embedded/embedded.pri | 226 + src/gui/embedded/qcopchannel_qws.cpp | 608 + src/gui/embedded/qcopchannel_qws.h | 108 + src/gui/embedded/qdecoration_qws.cpp | 404 + src/gui/embedded/qdecoration_qws.h | 124 + src/gui/embedded/qdecorationdefault_qws.cpp | 803 ++ src/gui/embedded/qdecorationdefault_qws.h | 101 + src/gui/embedded/qdecorationfactory_qws.cpp | 156 + src/gui/embedded/qdecorationfactory_qws.h | 66 + src/gui/embedded/qdecorationplugin_qws.cpp | 116 + src/gui/embedded/qdecorationplugin_qws.h | 80 + src/gui/embedded/qdecorationstyled_qws.cpp | 313 + src/gui/embedded/qdecorationstyled_qws.h | 73 + src/gui/embedded/qdecorationwindows_qws.cpp | 407 + src/gui/embedded/qdecorationwindows_qws.h | 77 + src/gui/embedded/qdirectpainter_qws.cpp | 682 + src/gui/embedded/qdirectpainter_qws.h | 112 + src/gui/embedded/qkbd_defaultmap_qws_p.h | 806 ++ src/gui/embedded/qkbd_qws.cpp | 693 + src/gui/embedded/qkbd_qws.h | 103 + src/gui/embedded/qkbd_qws_p.h | 134 + src/gui/embedded/qkbddriverfactory_qws.cpp | 187 + src/gui/embedded/qkbddriverfactory_qws.h | 70 + src/gui/embedded/qkbddriverplugin_qws.cpp | 124 + src/gui/embedded/qkbddriverplugin_qws.h | 84 + src/gui/embedded/qkbdintegrity_qws.cpp | 197 + src/gui/embedded/qkbdintegrity_qws.h | 81 + src/gui/embedded/qkbdlinuxinput_qws.cpp | 245 + src/gui/embedded/qkbdlinuxinput_qws.h | 79 + src/gui/embedded/qkbdqnx_qws.cpp | 236 + src/gui/embedded/qkbdqnx_qws.h | 76 + src/gui/embedded/qkbdtty_qws.cpp | 353 + src/gui/embedded/qkbdtty_qws.h | 79 + src/gui/embedded/qkbdum_qws.cpp | 144 + src/gui/embedded/qkbdum_qws.h | 77 + src/gui/embedded/qkbdvfb_qws.cpp | 124 + src/gui/embedded/qkbdvfb_qws.h | 86 + src/gui/embedded/qlock.cpp | 325 + src/gui/embedded/qlock_p.h | 100 + src/gui/embedded/qmouse_qws.cpp | 653 + src/gui/embedded/qmouse_qws.h | 123 + src/gui/embedded/qmousedriverfactory_qws.cpp | 197 + src/gui/embedded/qmousedriverfactory_qws.h | 67 + src/gui/embedded/qmousedriverplugin_qws.cpp | 124 + src/gui/embedded/qmousedriverplugin_qws.h | 84 + src/gui/embedded/qmouseintegrity_qws.cpp | 271 + src/gui/embedded/qmouseintegrity_qws.h | 82 + src/gui/embedded/qmouselinuxinput_qws.cpp | 205 + src/gui/embedded/qmouselinuxinput_qws.h | 78 + src/gui/embedded/qmouselinuxtp_qws.cpp | 335 + src/gui/embedded/qmouselinuxtp_qws.h | 77 + src/gui/embedded/qmousepc_qws.cpp | 794 ++ src/gui/embedded/qmousepc_qws.h | 76 + src/gui/embedded/qmouseqnx_qws.cpp | 190 + src/gui/embedded/qmouseqnx_qws.h | 79 + src/gui/embedded/qmousetslib_qws.cpp | 371 + src/gui/embedded/qmousetslib_qws.h | 80 + src/gui/embedded/qmousevfb_qws.cpp | 133 + src/gui/embedded/qmousevfb_qws.h | 83 + src/gui/embedded/qscreen_qws.cpp | 3347 +++++ src/gui/embedded/qscreen_qws.h | 391 + src/gui/embedded/qscreendriverfactory_qws.cpp | 204 + src/gui/embedded/qscreendriverfactory_qws.h | 67 + src/gui/embedded/qscreendriverplugin_qws.cpp | 123 + src/gui/embedded/qscreendriverplugin_qws.h | 84 + src/gui/embedded/qscreenintegrityfb_qws.cpp | 405 + src/gui/embedded/qscreenintegrityfb_qws.h | 83 + src/gui/embedded/qscreenlinuxfb_qws.cpp | 1386 ++ src/gui/embedded/qscreenlinuxfb_qws.h | 135 + src/gui/embedded/qscreenmulti_qws.cpp | 486 + src/gui/embedded/qscreenmulti_qws_p.h | 114 + src/gui/embedded/qscreenproxy_qws.cpp | 635 + src/gui/embedded/qscreenproxy_qws.h | 153 + src/gui/embedded/qscreenqnx_qws.cpp | 450 + src/gui/embedded/qscreenqnx_qws.h | 82 + src/gui/embedded/qscreentransformed_qws.cpp | 748 ++ src/gui/embedded/qscreentransformed_qws.h | 103 + src/gui/embedded/qscreenvfb_qws.cpp | 445 + src/gui/embedded/qscreenvfb_qws.h | 86 + src/gui/embedded/qsoundqss_qws.cpp | 1530 +++ src/gui/embedded/qsoundqss_qws.h | 177 + src/gui/embedded/qtransportauth_qws.cpp | 1563 +++ src/gui/embedded/qtransportauth_qws.h | 281 + src/gui/embedded/qtransportauth_qws_p.h | 189 + src/gui/embedded/qtransportauthdefs_qws.h | 174 + src/gui/embedded/qunixsocket.cpp | 1800 +++ src/gui/embedded/qunixsocket_p.h | 202 + src/gui/embedded/qunixsocketserver.cpp | 376 + src/gui/embedded/qunixsocketserver_p.h | 98 + src/gui/embedded/qvfbhdr.h | 119 + src/gui/embedded/qwindowsystem_p.h | 315 + src/gui/embedded/qwindowsystem_qws.cpp | 4960 ++++++++ src/gui/embedded/qwindowsystem_qws.h | 508 + src/gui/embedded/qwscommand_qws.cpp | 609 + src/gui/embedded/qwscommand_qws_p.h | 851 ++ src/gui/embedded/qwscursor_qws.cpp | 654 + src/gui/embedded/qwscursor_qws.h | 83 + src/gui/embedded/qwsdisplay_qws.h | 185 + src/gui/embedded/qwsdisplay_qws_p.h | 161 + src/gui/embedded/qwsembedwidget.cpp | 227 + src/gui/embedded/qwsembedwidget.h | 82 + src/gui/embedded/qwsevent_qws.cpp | 216 + src/gui/embedded/qwsevent_qws.h | 459 + src/gui/embedded/qwslock.cpp | 236 + src/gui/embedded/qwslock_p.h | 85 + src/gui/embedded/qwsmanager_p.h | 122 + src/gui/embedded/qwsmanager_qws.cpp | 537 + src/gui/embedded/qwsmanager_qws.h | 122 + src/gui/embedded/qwsproperty_qws.cpp | 145 + src/gui/embedded/qwsproperty_qws.h | 96 + src/gui/embedded/qwsprotocolitem_qws.h | 100 + src/gui/embedded/qwssharedmemory.cpp | 185 + src/gui/embedded/qwssharedmemory_p.h | 105 + src/gui/embedded/qwssignalhandler.cpp | 128 + src/gui/embedded/qwssignalhandler_p.h | 99 + src/gui/embedded/qwssocket_qws.cpp | 280 + src/gui/embedded/qwssocket_qws.h | 120 + src/gui/embedded/qwsutils_qws.h | 98 + src/gui/graphicsview/graphicsview.pri | 52 + src/gui/graphicsview/qgraph_p.h | 286 + src/gui/graphicsview/qgraphicsanchorlayout.cpp | 533 + src/gui/graphicsview/qgraphicsanchorlayout.h | 128 + src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 3015 +++++ src/gui/graphicsview/qgraphicsanchorlayout_p.h | 594 + src/gui/graphicsview/qgraphicsgridlayout.cpp | 690 + src/gui/graphicsview/qgraphicsgridlayout.h | 144 + src/gui/graphicsview/qgraphicsitem.cpp | 11597 +++++++++++++++++ src/gui/graphicsview/qgraphicsitem.h | 1172 ++ src/gui/graphicsview/qgraphicsitem_p.h | 891 ++ src/gui/graphicsview/qgraphicsitemanimation.cpp | 599 + src/gui/graphicsview/qgraphicsitemanimation.h | 120 + src/gui/graphicsview/qgraphicslayout.cpp | 451 + src/gui/graphicsview/qgraphicslayout.h | 98 + src/gui/graphicsview/qgraphicslayout_p.cpp | 198 + src/gui/graphicsview/qgraphicslayout_p.h | 154 + src/gui/graphicsview/qgraphicslayoutitem.cpp | 935 ++ src/gui/graphicsview/qgraphicslayoutitem.h | 155 + src/gui/graphicsview/qgraphicslayoutitem_p.h | 103 + src/gui/graphicsview/qgraphicslinearlayout.cpp | 568 + src/gui/graphicsview/qgraphicslinearlayout.h | 119 + src/gui/graphicsview/qgraphicsproxywidget.cpp | 1570 +++ src/gui/graphicsview/qgraphicsproxywidget.h | 147 + src/gui/graphicsview/qgraphicsproxywidget_p.h | 130 + src/gui/graphicsview/qgraphicsscene.cpp | 6491 ++++++++++ src/gui/graphicsview/qgraphicsscene.h | 329 + src/gui/graphicsview/qgraphicsscene_bsp.cpp | 296 + src/gui/graphicsview/qgraphicsscene_bsp_p.h | 132 + src/gui/graphicsview/qgraphicsscene_p.h | 359 + .../graphicsview/qgraphicsscenebsptreeindex.cpp | 719 ++ .../graphicsview/qgraphicsscenebsptreeindex_p.h | 206 + src/gui/graphicsview/qgraphicssceneevent.cpp | 1674 +++ src/gui/graphicsview/qgraphicssceneevent.h | 326 + src/gui/graphicsview/qgraphicssceneindex.cpp | 648 + src/gui/graphicsview/qgraphicssceneindex_p.h | 182 + src/gui/graphicsview/qgraphicsscenelinearindex.cpp | 95 + src/gui/graphicsview/qgraphicsscenelinearindex_p.h | 109 + src/gui/graphicsview/qgraphicstransform.cpp | 594 + src/gui/graphicsview/qgraphicstransform.h | 159 + src/gui/graphicsview/qgraphicstransform_p.h | 78 + src/gui/graphicsview/qgraphicsview.cpp | 3880 ++++++ src/gui/graphicsview/qgraphicsview.h | 316 + src/gui/graphicsview/qgraphicsview_p.h | 233 + src/gui/graphicsview/qgraphicswidget.cpp | 2388 ++++ src/gui/graphicsview/qgraphicswidget.h | 257 + src/gui/graphicsview/qgraphicswidget_p.cpp | 910 ++ src/gui/graphicsview/qgraphicswidget_p.h | 226 + src/gui/graphicsview/qgridlayoutengine.cpp | 1742 +++ src/gui/graphicsview/qgridlayoutengine_p.h | 458 + src/gui/graphicsview/qsimplex_p.cpp | 673 + src/gui/graphicsview/qsimplex_p.h | 208 + src/gui/gui.pro | 223 + src/gui/image/image.pri | 115 + src/gui/image/qbitmap.cpp | 411 + src/gui/image/qbitmap.h | 109 + src/gui/image/qbmphandler.cpp | 837 ++ src/gui/image/qbmphandler_p.h | 117 + src/gui/image/qgifhandler.cpp | 1214 ++ src/gui/image/qgifhandler.pri | 4 + src/gui/image/qgifhandler_p.h | 96 + src/gui/image/qicon.cpp | 1258 ++ src/gui/image/qicon.h | 162 + src/gui/image/qicon_p.h | 139 + src/gui/image/qiconengine.cpp | 324 + src/gui/image/qiconengine.h | 104 + src/gui/image/qiconengineplugin.cpp | 171 + src/gui/image/qiconengineplugin.h | 104 + src/gui/image/qiconloader.cpp | 570 + src/gui/image/qiconloader_p.h | 192 + src/gui/image/qimage.cpp | 6717 ++++++++++ src/gui/image/qimage.h | 374 + src/gui/image/qimage_neon.cpp | 114 + src/gui/image/qimage_p.h | 154 + src/gui/image/qimage_sse2.cpp | 109 + src/gui/image/qimage_ssse3.cpp | 149 + src/gui/image/qimageiohandler.cpp | 570 + src/gui/image/qimageiohandler.h | 152 + src/gui/image/qimagepixmapcleanuphooks.cpp | 163 + src/gui/image/qimagepixmapcleanuphooks_p.h | 103 + src/gui/image/qimagereader.cpp | 1515 +++ src/gui/image/qimagereader.h | 147 + src/gui/image/qimagewriter.cpp | 734 ++ src/gui/image/qimagewriter.h | 116 + src/gui/image/qjpeghandler.cpp | 915 ++ src/gui/image/qjpeghandler.pri | 10 + src/gui/image/qjpeghandler_p.h | 76 + src/gui/image/qmnghandler.cpp | 497 + src/gui/image/qmnghandler.pri | 10 + src/gui/image/qmnghandler_p.h | 83 + src/gui/image/qmovie.cpp | 1089 ++ src/gui/image/qmovie.h | 177 + src/gui/image/qnativeimage.cpp | 315 + src/gui/image/qnativeimage_p.h | 109 + src/gui/image/qnativeimagehandleprovider_p.h | 69 + src/gui/image/qpaintengine_pic.cpp | 539 + src/gui/image/qpaintengine_pic_p.h | 122 + src/gui/image/qpicture.cpp | 1999 +++ src/gui/image/qpicture.h | 204 + src/gui/image/qpicture_p.h | 169 + src/gui/image/qpictureformatplugin.cpp | 139 + src/gui/image/qpictureformatplugin.h | 94 + src/gui/image/qpixmap.cpp | 2297 ++++ src/gui/image/qpixmap.h | 335 + src/gui/image/qpixmap_blitter.cpp | 310 + src/gui/image/qpixmap_blitter_p.h | 166 + src/gui/image/qpixmap_mac.cpp | 1195 ++ src/gui/image/qpixmap_mac_p.h | 134 + src/gui/image/qpixmap_qpa.cpp | 49 + src/gui/image/qpixmap_qws.cpp | 160 + src/gui/image/qpixmap_raster.cpp | 492 + src/gui/image/qpixmap_raster_p.h | 107 + src/gui/image/qpixmap_s60.cpp | 1040 ++ src/gui/image/qpixmap_s60_p.h | 141 + src/gui/image/qpixmap_win.cpp | 477 + src/gui/image/qpixmap_x11.cpp | 2419 ++++ src/gui/image/qpixmap_x11_p.h | 156 + src/gui/image/qpixmapcache.cpp | 680 + src/gui/image/qpixmapcache.h | 102 + src/gui/image/qpixmapcache_p.h | 103 + src/gui/image/qpixmapdata.cpp | 290 + src/gui/image/qpixmapdata_p.h | 180 + src/gui/image/qpixmapdatafactory.cpp | 115 + src/gui/image/qpixmapdatafactory_p.h | 81 + src/gui/image/qpixmapfilter.cpp | 1382 ++ src/gui/image/qpixmapfilter_p.h | 196 + src/gui/image/qpnghandler.cpp | 991 ++ src/gui/image/qpnghandler.pri | 10 + src/gui/image/qpnghandler_p.h | 88 + src/gui/image/qppmhandler.cpp | 533 + src/gui/image/qppmhandler_p.h | 98 + src/gui/image/qtiffhandler.cpp | 665 + src/gui/image/qtiffhandler.pri | 10 + src/gui/image/qtiffhandler_p.h | 78 + src/gui/image/qvolatileimage.cpp | 318 + src/gui/image/qvolatileimage_p.h | 101 + src/gui/image/qvolatileimagedata.cpp | 114 + src/gui/image/qvolatileimagedata_p.h | 97 + src/gui/image/qvolatileimagedata_symbian.cpp | 474 + src/gui/image/qxbmhandler.cpp | 361 + src/gui/image/qxbmhandler_p.h | 95 + src/gui/image/qxpmhandler.cpp | 1295 ++ src/gui/image/qxpmhandler_p.h | 100 + src/gui/inputmethod/inputmethod.pri | 31 + src/gui/inputmethod/qcoefepinputcontext_p.h | 176 + src/gui/inputmethod/qcoefepinputcontext_s60.cpp | 1200 ++ src/gui/inputmethod/qinputcontext.cpp | 500 + src/gui/inputmethod/qinputcontext.h | 139 + src/gui/inputmethod/qinputcontext_p.h | 94 + src/gui/inputmethod/qinputcontextfactory.cpp | 354 + src/gui/inputmethod/qinputcontextfactory.h | 88 + src/gui/inputmethod/qinputcontextplugin.cpp | 178 + src/gui/inputmethod/qinputcontextplugin.h | 106 + src/gui/inputmethod/qmacinputcontext_mac.cpp | 378 + src/gui/inputmethod/qmacinputcontext_p.h | 97 + src/gui/inputmethod/qwininputcontext_p.h | 111 + src/gui/inputmethod/qwininputcontext_win.cpp | 847 ++ src/gui/inputmethod/qwsinputcontext_p.h | 97 + src/gui/inputmethod/qwsinputcontext_qws.cpp | 246 + src/gui/inputmethod/qximinputcontext_p.h | 142 + src/gui/inputmethod/qximinputcontext_x11.cpp | 885 ++ src/gui/itemviews/itemviews.pri | 70 + src/gui/itemviews/qabstractitemdelegate.cpp | 393 + src/gui/itemviews/qabstractitemdelegate.h | 134 + src/gui/itemviews/qabstractitemview.cpp | 4241 +++++++ src/gui/itemviews/qabstractitemview.h | 380 + src/gui/itemviews/qabstractitemview_p.h | 457 + src/gui/itemviews/qabstractproxymodel.cpp | 409 + src/gui/itemviews/qabstractproxymodel.h | 116 + src/gui/itemviews/qabstractproxymodel_p.h | 76 + src/gui/itemviews/qbsptree.cpp | 145 + src/gui/itemviews/qbsptree_p.h | 119 + src/gui/itemviews/qcolumnview.cpp | 1168 ++ src/gui/itemviews/qcolumnview.h | 123 + src/gui/itemviews/qcolumnview_p.h | 189 + src/gui/itemviews/qcolumnviewgrip.cpp | 194 + src/gui/itemviews/qcolumnviewgrip_p.h | 104 + src/gui/itemviews/qdatawidgetmapper.cpp | 849 ++ src/gui/itemviews/qdatawidgetmapper.h | 128 + src/gui/itemviews/qdirmodel.cpp | 1406 ++ src/gui/itemviews/qdirmodel.h | 160 + src/gui/itemviews/qfileiconprovider.cpp | 509 + src/gui/itemviews/qfileiconprovider.h | 82 + src/gui/itemviews/qheaderview.cpp | 3615 ++++++ src/gui/itemviews/qheaderview.h | 250 + src/gui/itemviews/qheaderview_p.h | 366 + src/gui/itemviews/qitemdelegate.cpp | 1341 ++ src/gui/itemviews/qitemdelegate.h | 141 + src/gui/itemviews/qitemeditorfactory.cpp | 578 + src/gui/itemviews/qitemeditorfactory.h | 124 + src/gui/itemviews/qitemeditorfactory_p.h | 99 + src/gui/itemviews/qitemselectionmodel.cpp | 1625 +++ src/gui/itemviews/qitemselectionmodel.h | 255 + src/gui/itemviews/qitemselectionmodel_p.h | 113 + src/gui/itemviews/qlistview.cpp | 3212 +++++ src/gui/itemviews/qlistview.h | 203 + src/gui/itemviews/qlistview_p.h | 492 + src/gui/itemviews/qlistwidget.cpp | 1914 +++ src/gui/itemviews/qlistwidget.h | 335 + src/gui/itemviews/qlistwidget_p.h | 175 + src/gui/itemviews/qproxymodel.cpp | 547 + src/gui/itemviews/qproxymodel.h | 143 + src/gui/itemviews/qproxymodel_p.h | 100 + src/gui/itemviews/qsortfilterproxymodel.cpp | 2542 ++++ src/gui/itemviews/qsortfilterproxymodel.h | 201 + src/gui/itemviews/qstandarditemmodel.cpp | 3115 +++++ src/gui/itemviews/qstandarditemmodel.h | 456 + src/gui/itemviews/qstandarditemmodel_p.h | 192 + src/gui/itemviews/qstringlistmodel.cpp | 308 + src/gui/itemviews/qstringlistmodel.h | 91 + src/gui/itemviews/qstyleditemdelegate.cpp | 765 ++ src/gui/itemviews/qstyleditemdelegate.h | 116 + src/gui/itemviews/qtableview.cpp | 3198 +++++ src/gui/itemviews/qtableview.h | 197 + src/gui/itemviews/qtableview_p.h | 257 + src/gui/itemviews/qtablewidget.cpp | 2685 ++++ src/gui/itemviews/qtablewidget.h | 377 + src/gui/itemviews/qtablewidget_p.h | 222 + src/gui/itemviews/qtreeview.cpp | 3755 ++++++ src/gui/itemviews/qtreeview.h | 239 + src/gui/itemviews/qtreeview_p.h | 251 + src/gui/itemviews/qtreewidget.cpp | 3460 +++++ src/gui/itemviews/qtreewidget.h | 432 + src/gui/itemviews/qtreewidget_p.h | 248 + src/gui/itemviews/qtreewidgetitemiterator.cpp | 458 + src/gui/itemviews/qtreewidgetitemiterator.h | 159 + src/gui/itemviews/qtreewidgetitemiterator_p.h | 110 + src/gui/itemviews/qwidgetitemdata_p.h | 88 + src/gui/kernel/kernel.pri | 329 + src/gui/kernel/mac.pri | 4 + src/gui/kernel/qaction.cpp | 1520 +++ src/gui/kernel/qaction.h | 264 + src/gui/kernel/qaction_p.h | 144 + src/gui/kernel/qactiongroup.cpp | 422 + src/gui/kernel/qactiongroup.h | 112 + src/gui/kernel/qapplication.cpp | 6139 +++++++++ src/gui/kernel/qapplication.h | 430 + src/gui/kernel/qapplication_mac.mm | 3134 +++++ src/gui/kernel/qapplication_p.h | 683 + src/gui/kernel/qapplication_qpa.cpp | 965 ++ src/gui/kernel/qapplication_qws.cpp | 3797 ++++++ src/gui/kernel/qapplication_s60.cpp | 2712 ++++ src/gui/kernel/qapplication_win.cpp | 4232 +++++++ src/gui/kernel/qapplication_x11.cpp | 6239 +++++++++ src/gui/kernel/qboxlayout.cpp | 1550 +++ src/gui/kernel/qboxlayout.h | 173 + src/gui/kernel/qclipboard.cpp | 667 + src/gui/kernel/qclipboard.h | 130 + src/gui/kernel/qclipboard_mac.cpp | 634 + src/gui/kernel/qclipboard_p.h | 131 + src/gui/kernel/qclipboard_qpa.cpp | 105 + src/gui/kernel/qclipboard_qws.cpp | 304 + src/gui/kernel/qclipboard_s60.cpp | 331 + src/gui/kernel/qclipboard_win.cpp | 398 + src/gui/kernel/qclipboard_x11.cpp | 1539 +++ src/gui/kernel/qcocoaapplication_mac.mm | 222 + src/gui/kernel/qcocoaapplication_mac_p.h | 117 + src/gui/kernel/qcocoaapplicationdelegate_mac.mm | 354 + src/gui/kernel/qcocoaapplicationdelegate_mac_p.h | 128 + src/gui/kernel/qcocoaintrospection_mac.mm | 125 + src/gui/kernel/qcocoaintrospection_p.h | 84 + src/gui/kernel/qcocoamenuloader_mac.mm | 264 + src/gui/kernel/qcocoamenuloader_mac_p.h | 95 + src/gui/kernel/qcocoapanel_mac.mm | 70 + src/gui/kernel/qcocoapanel_mac_p.h | 83 + src/gui/kernel/qcocoasharedwindowmethods_mac_p.h | 610 + src/gui/kernel/qcocoaview_mac.mm | 1389 ++ src/gui/kernel/qcocoaview_mac_p.h | 87 + src/gui/kernel/qcocoawindow_mac.mm | 90 + src/gui/kernel/qcocoawindow_mac_p.h | 97 + src/gui/kernel/qcocoawindowcustomthemeframe_mac.mm | 62 + .../kernel/qcocoawindowcustomthemeframe_mac_p.h | 61 + src/gui/kernel/qcocoawindowdelegate_mac.mm | 439 + src/gui/kernel/qcocoawindowdelegate_mac_p.h | 110 + src/gui/kernel/qcursor.cpp | 573 + src/gui/kernel/qcursor.h | 180 + src/gui/kernel/qcursor_mac.mm | 689 + src/gui/kernel/qcursor_p.h | 134 + src/gui/kernel/qcursor_qpa.cpp | 127 + src/gui/kernel/qcursor_qws.cpp | 138 + src/gui/kernel/qcursor_s60.cpp | 533 + src/gui/kernel/qcursor_win.cpp | 492 + src/gui/kernel/qcursor_x11.cpp | 637 + src/gui/kernel/qdesktopwidget.cpp | 76 + src/gui/kernel/qdesktopwidget.h | 110 + src/gui/kernel/qdesktopwidget.qdoc | 269 + src/gui/kernel/qdesktopwidget_mac.mm | 257 + src/gui/kernel/qdesktopwidget_mac_p.h | 75 + src/gui/kernel/qdesktopwidget_qpa.cpp | 180 + src/gui/kernel/qdesktopwidget_qpa_p.h | 82 + src/gui/kernel/qdesktopwidget_qws.cpp | 159 + src/gui/kernel/qdesktopwidget_s60.cpp | 316 + src/gui/kernel/qdesktopwidget_win.cpp | 387 + src/gui/kernel/qdesktopwidget_x11.cpp | 406 + src/gui/kernel/qdnd.cpp | 491 + src/gui/kernel/qdnd_mac.mm | 753 ++ src/gui/kernel/qdnd_p.h | 336 + src/gui/kernel/qdnd_qws.cpp | 426 + src/gui/kernel/qdnd_s60.cpp | 359 + src/gui/kernel/qdnd_win.cpp | 1027 ++ src/gui/kernel/qdnd_x11.cpp | 2076 +++ src/gui/kernel/qdrag.cpp | 359 + src/gui/kernel/qdrag.h | 105 + src/gui/kernel/qevent.cpp | 4851 +++++++ src/gui/kernel/qevent.h | 942 ++ src/gui/kernel/qevent_p.h | 219 + src/gui/kernel/qeventdispatcher_glib_qpa.cpp | 147 + src/gui/kernel/qeventdispatcher_glib_qpa_p.h | 88 + src/gui/kernel/qeventdispatcher_glib_qws.cpp | 195 + src/gui/kernel/qeventdispatcher_glib_qws_p.h | 78 + src/gui/kernel/qeventdispatcher_mac.mm | 1200 ++ src/gui/kernel/qeventdispatcher_mac_p.h | 224 + src/gui/kernel/qeventdispatcher_qpa.cpp | 334 + src/gui/kernel/qeventdispatcher_qpa_p.h | 86 + src/gui/kernel/qeventdispatcher_qws.cpp | 168 + src/gui/kernel/qeventdispatcher_qws_p.h | 86 + src/gui/kernel/qeventdispatcher_s60.cpp | 196 + src/gui/kernel/qeventdispatcher_s60_p.h | 127 + src/gui/kernel/qeventdispatcher_x11.cpp | 191 + src/gui/kernel/qeventdispatcher_x11_p.h | 86 + src/gui/kernel/qformlayout.cpp | 2079 +++ src/gui/kernel/qformlayout.h | 163 + src/gui/kernel/qgenericplugin_qpa.cpp | 112 + src/gui/kernel/qgenericplugin_qpa.h | 84 + src/gui/kernel/qgenericpluginfactory_qpa.cpp | 115 + src/gui/kernel/qgenericpluginfactory_qpa.h | 67 + src/gui/kernel/qgesture.cpp | 807 ++ src/gui/kernel/qgesture.h | 275 + src/gui/kernel/qgesture_p.h | 197 + src/gui/kernel/qgesturemanager.cpp | 721 ++ src/gui/kernel/qgesturemanager_p.h | 151 + src/gui/kernel/qgesturerecognizer.cpp | 240 + src/gui/kernel/qgesturerecognizer.h | 102 + src/gui/kernel/qgridlayout.cpp | 1889 +++ src/gui/kernel/qgridlayout.h | 176 + src/gui/kernel/qguieventdispatcher_glib.cpp | 224 + src/gui/kernel/qguieventdispatcher_glib_p.h | 79 + src/gui/kernel/qguifunctions_wince.cpp | 408 + src/gui/kernel/qguifunctions_wince.h | 151 + src/gui/kernel/qguiplatformplugin.cpp | 298 + src/gui/kernel/qguiplatformplugin_p.h | 126 + src/gui/kernel/qguivariant.cpp | 828 ++ src/gui/kernel/qkde.cpp | 176 + src/gui/kernel/qkde_p.h | 81 + src/gui/kernel/qkeymapper.cpp | 118 + src/gui/kernel/qkeymapper_mac.cpp | 1023 ++ src/gui/kernel/qkeymapper_p.h | 224 + src/gui/kernel/qkeymapper_qws.cpp | 77 + src/gui/kernel/qkeymapper_s60.cpp | 258 + src/gui/kernel/qkeymapper_win.cpp | 1207 ++ src/gui/kernel/qkeymapper_x11.cpp | 1869 +++ src/gui/kernel/qkeymapper_x11_p.cpp | 489 + src/gui/kernel/qkeysequence.cpp | 1726 +++ src/gui/kernel/qkeysequence.h | 240 + src/gui/kernel/qkeysequence_p.h | 98 + src/gui/kernel/qlayout.cpp | 1632 +++ src/gui/kernel/qlayout.h | 245 + src/gui/kernel/qlayout_p.h | 101 + src/gui/kernel/qlayoutengine.cpp | 436 + src/gui/kernel/qlayoutengine_p.h | 140 + src/gui/kernel/qlayoutitem.cpp | 834 ++ src/gui/kernel/qlayoutitem.h | 182 + src/gui/kernel/qmacdefines_mac.h | 180 + src/gui/kernel/qmacgesturerecognizer_mac.mm | 272 + src/gui/kernel/qmacgesturerecognizer_mac_p.h | 106 + src/gui/kernel/qmime.cpp | 97 + src/gui/kernel/qmime.h | 176 + src/gui/kernel/qmime_mac.cpp | 1310 ++ src/gui/kernel/qmime_win.cpp | 1556 +++ src/gui/kernel/qmotifdnd_x11.cpp | 1031 ++ src/gui/kernel/qmultitouch_mac.mm | 218 + src/gui/kernel/qmultitouch_mac_p.h | 102 + src/gui/kernel/qnsframeview_mac_p.h | 154 + src/gui/kernel/qnsthemeframe_mac_p.h | 246 + src/gui/kernel/qnstitledframe_mac_p.h | 205 + src/gui/kernel/qole_win.cpp | 255 + src/gui/kernel/qpalette.cpp | 1406 ++ src/gui/kernel/qpalette.h | 270 + src/gui/kernel/qplatformclipboard_qpa.cpp | 105 + src/gui/kernel/qplatformclipboard_qpa.h | 73 + src/gui/kernel/qplatformcursor_qpa.cpp | 660 + src/gui/kernel/qplatformcursor_qpa.h | 103 + .../kernel/qplatformeventloopintegration_qpa.cpp | 86 + src/gui/kernel/qplatformeventloopintegration_qpa.h | 82 + src/gui/kernel/qplatformglcontext_qpa.cpp | 209 + src/gui/kernel/qplatformglcontext_qpa.h | 91 + src/gui/kernel/qplatformintegration_qpa.cpp | 224 + src/gui/kernel/qplatformintegration_qpa.h | 107 + src/gui/kernel/qplatformintegrationfactory_qpa.cpp | 109 + src/gui/kernel/qplatformintegrationfactory_qpa_p.h | 78 + src/gui/kernel/qplatformintegrationplugin_qpa.cpp | 55 + src/gui/kernel/qplatformintegrationplugin_qpa.h | 92 + src/gui/kernel/qplatformnativeinterface_qpa.cpp | 53 + src/gui/kernel/qplatformnativeinterface_qpa.h | 65 + src/gui/kernel/qplatformscreen_qpa.cpp | 129 + src/gui/kernel/qplatformscreen_qpa.h | 86 + src/gui/kernel/qplatformwindow_qpa.cpp | 229 + src/gui/kernel/qplatformwindow_qpa.h | 95 + src/gui/kernel/qplatformwindowformat_qpa.cpp | 1008 ++ src/gui/kernel/qplatformwindowformat_qpa.h | 234 + src/gui/kernel/qsessionmanager.h | 111 + src/gui/kernel/qsessionmanager_qpa.cpp | 172 + src/gui/kernel/qsessionmanager_qws.cpp | 171 + src/gui/kernel/qshortcut.cpp | 407 + src/gui/kernel/qshortcut.h | 107 + src/gui/kernel/qshortcutmap.cpp | 897 ++ src/gui/kernel/qshortcutmap_p.h | 123 + src/gui/kernel/qsizepolicy.h | 244 + src/gui/kernel/qsizepolicy.qdoc | 529 + src/gui/kernel/qsoftkeymanager.cpp | 319 + src/gui/kernel/qsoftkeymanager_common_p.h | 86 + src/gui/kernel/qsoftkeymanager_p.h | 115 + src/gui/kernel/qsoftkeymanager_s60.cpp | 440 + src/gui/kernel/qsoftkeymanager_s60_p.h | 113 + src/gui/kernel/qsound.cpp | 390 + src/gui/kernel/qsound.h | 95 + src/gui/kernel/qsound_mac.mm | 190 + src/gui/kernel/qsound_p.h | 100 + src/gui/kernel/qsound_qws.cpp | 350 + src/gui/kernel/qsound_s60.cpp | 224 + src/gui/kernel/qsound_win.cpp | 205 + src/gui/kernel/qsound_x11.cpp | 296 + src/gui/kernel/qstackedlayout.cpp | 543 + src/gui/kernel/qstackedlayout.h | 115 + src/gui/kernel/qstandardgestures.cpp | 595 + src/gui/kernel/qstandardgestures_p.h | 117 + src/gui/kernel/qt_cocoa_helpers_mac.mm | 1824 +++ src/gui/kernel/qt_cocoa_helpers_mac_p.h | 340 + src/gui/kernel/qt_gui_pch.h | 85 + src/gui/kernel/qt_mac.cpp | 174 + src/gui/kernel/qt_mac_p.h | 286 + src/gui/kernel/qt_s60_p.h | 625 + src/gui/kernel/qt_x11_p.h | 757 ++ src/gui/kernel/qtooltip.cpp | 623 + src/gui/kernel/qtooltip.h | 84 + src/gui/kernel/qwhatsthis.cpp | 777 ++ src/gui/kernel/qwhatsthis.h | 88 + src/gui/kernel/qwidget.cpp | 12679 +++++++++++++++++++ src/gui/kernel/qwidget.h | 1090 ++ src/gui/kernel/qwidget_mac.mm | 5420 ++++++++ src/gui/kernel/qwidget_p.h | 1035 ++ src/gui/kernel/qwidget_qpa.cpp | 875 ++ src/gui/kernel/qwidget_qws.cpp | 1221 ++ src/gui/kernel/qwidget_s60.cpp | 1450 +++ src/gui/kernel/qwidget_win.cpp | 2139 ++++ src/gui/kernel/qwidget_wince.cpp | 675 + src/gui/kernel/qwidget_x11.cpp | 3146 +++++ src/gui/kernel/qwidgetaction.cpp | 287 + src/gui/kernel/qwidgetaction.h | 91 + src/gui/kernel/qwidgetaction_p.h | 77 + src/gui/kernel/qwidgetcreate_x11.cpp | 79 + src/gui/kernel/qwindowdefs.h | 163 + src/gui/kernel/qwindowdefs_win.h | 132 + src/gui/kernel/qwindowsysteminterface_qpa.cpp | 291 + src/gui/kernel/qwindowsysteminterface_qpa.h | 107 + src/gui/kernel/qwindowsysteminterface_qpa_p.h | 208 + .../kernel/qwinnativepangesturerecognizer_win.cpp | 133 + .../kernel/qwinnativepangesturerecognizer_win_p.h | 80 + src/gui/kernel/qx11embed_x11.cpp | 1808 +++ src/gui/kernel/qx11embed_x11.h | 132 + src/gui/kernel/qx11info_x11.cpp | 543 + src/gui/kernel/qx11info_x11.h | 123 + src/gui/kernel/symbian.pri | 7 + src/gui/kernel/win.pri | 4 + src/gui/kernel/x11.pri | 4 + src/gui/mac/images/copyarrowcursor.png | Bin 0 -> 1976 bytes src/gui/mac/images/forbiddencursor.png | Bin 0 -> 1745 bytes src/gui/mac/images/leopard-unified-toolbar-on.png | Bin 0 -> 356 bytes src/gui/mac/images/pluscursor.png | Bin 0 -> 688 bytes src/gui/mac/images/spincursor.png | Bin 0 -> 748 bytes src/gui/mac/images/waitcursor.png | Bin 0 -> 724 bytes src/gui/mac/macresources.qrc | 12 + src/gui/mac/qt_menu.nib/classes.nib | 59 + src/gui/mac/qt_menu.nib/info.nib | 18 + src/gui/mac/qt_menu.nib/keyedobjects.nib | Bin 0 -> 5560 bytes src/gui/math3d/math3d.pri | 15 + src/gui/math3d/qgenericmatrix.cpp | 342 + src/gui/math3d/qgenericmatrix.h | 407 + src/gui/math3d/qmatrix4x4.cpp | 1915 +++ src/gui/math3d/qmatrix4x4.h | 1022 ++ src/gui/math3d/qquaternion.cpp | 636 + src/gui/math3d/qquaternion.h | 330 + src/gui/math3d/qvector2d.cpp | 475 + src/gui/math3d/qvector2d.h | 262 + src/gui/math3d/qvector3d.cpp | 629 + src/gui/math3d/qvector3d.h | 286 + src/gui/math3d/qvector4d.cpp | 584 + src/gui/math3d/qvector4d.h | 291 + src/gui/painting/makepsheader.pl | 195 + src/gui/painting/painting.pri | 269 + src/gui/painting/qbackingstore.cpp | 1668 +++ src/gui/painting/qbackingstore_p.h | 278 + src/gui/painting/qbezier.cpp | 702 + src/gui/painting/qbezier_p.h | 274 + src/gui/painting/qblendfunctions.cpp | 1644 +++ src/gui/painting/qblendfunctions_p.h | 497 + src/gui/painting/qblittable.cpp | 105 + src/gui/painting/qblittable_p.h | 91 + src/gui/painting/qbrush.cpp | 2196 ++++ src/gui/painting/qbrush.h | 338 + src/gui/painting/qcolor.cpp | 2720 ++++ src/gui/painting/qcolor.h | 308 + src/gui/painting/qcolor_p.cpp | 370 + src/gui/painting/qcolor_p.h | 71 + src/gui/painting/qcolormap.h | 97 + src/gui/painting/qcolormap.qdoc | 148 + src/gui/painting/qcolormap_mac.cpp | 111 + src/gui/painting/qcolormap_qpa.cpp | 231 + src/gui/painting/qcolormap_qws.cpp | 185 + src/gui/painting/qcolormap_s60.cpp | 107 + src/gui/painting/qcolormap_win.cpp | 201 + src/gui/painting/qcolormap_x11.cpp | 670 + src/gui/painting/qcssutil.cpp | 408 + src/gui/painting/qcssutil_p.h | 84 + src/gui/painting/qcups.cpp | 401 + src/gui/painting/qcups_p.h | 121 + src/gui/painting/qdatabuffer_p.h | 145 + src/gui/painting/qdrawhelper.cpp | 8078 ++++++++++++ src/gui/painting/qdrawhelper_arm_simd.cpp | 332 + src/gui/painting/qdrawhelper_arm_simd_p.h | 76 + src/gui/painting/qdrawhelper_iwmmxt.cpp | 148 + src/gui/painting/qdrawhelper_mmx.cpp | 155 + src/gui/painting/qdrawhelper_mmx3dnow.cpp | 128 + src/gui/painting/qdrawhelper_mmx_p.h | 892 ++ src/gui/painting/qdrawhelper_neon.cpp | 961 ++ src/gui/painting/qdrawhelper_neon_asm.S | 297 + src/gui/painting/qdrawhelper_neon_p.h | 146 + src/gui/painting/qdrawhelper_p.h | 2033 +++ src/gui/painting/qdrawhelper_sse.cpp | 168 + src/gui/painting/qdrawhelper_sse2.cpp | 496 + src/gui/painting/qdrawhelper_sse3dnow.cpp | 143 + src/gui/painting/qdrawhelper_sse_p.h | 182 + src/gui/painting/qdrawhelper_ssse3.cpp | 183 + src/gui/painting/qdrawhelper_x86_p.h | 141 + src/gui/painting/qdrawingprimitive_sse2_p.h | 241 + src/gui/painting/qdrawutil.cpp | 1360 ++ src/gui/painting/qdrawutil.h | 195 + src/gui/painting/qemulationpaintengine.cpp | 289 + src/gui/painting/qemulationpaintengine_p.h | 113 + src/gui/painting/qfixed_p.h | 221 + src/gui/painting/qgraphicssystem.cpp | 97 + src/gui/painting/qgraphicssystem_mac.cpp | 59 + src/gui/painting/qgraphicssystem_mac_p.h | 69 + src/gui/painting/qgraphicssystem_p.h | 85 + src/gui/painting/qgraphicssystem_qws.cpp | 62 + src/gui/painting/qgraphicssystem_qws_p.h | 79 + src/gui/painting/qgraphicssystem_raster.cpp | 72 + src/gui/painting/qgraphicssystem_raster_p.h | 69 + src/gui/painting/qgraphicssystem_runtime.cpp | 426 + src/gui/painting/qgraphicssystem_runtime_p.h | 187 + src/gui/painting/qgraphicssystemfactory.cpp | 123 + src/gui/painting/qgraphicssystemfactory_p.h | 78 + src/gui/painting/qgraphicssystemplugin.cpp | 56 + src/gui/painting/qgraphicssystemplugin_p.h | 92 + src/gui/painting/qgrayraster.c | 1942 +++ src/gui/painting/qgrayraster_p.h | 105 + src/gui/painting/qimagescale.cpp | 1031 ++ src/gui/painting/qimagescale_p.h | 66 + src/gui/painting/qmath_p.h | 72 + src/gui/painting/qmatrix.cpp | 1229 ++ src/gui/painting/qmatrix.h | 206 + src/gui/painting/qmemrotate.cpp | 648 + src/gui/painting/qmemrotate_p.h | 115 + src/gui/painting/qoutlinemapper.cpp | 393 + src/gui/painting/qoutlinemapper_p.h | 241 + src/gui/painting/qpaintbuffer.cpp | 2287 ++++ src/gui/painting/qpaintbuffer_p.h | 461 + src/gui/painting/qpaintdevice.cpp | 75 + src/gui/painting/qpaintdevice.h | 177 + src/gui/painting/qpaintdevice.qdoc | 286 + src/gui/painting/qpaintdevice_mac.cpp | 152 + src/gui/painting/qpaintdevice_qpa.cpp | 68 + src/gui/painting/qpaintdevice_qws.cpp | 56 + src/gui/painting/qpaintdevice_win.cpp | 62 + src/gui/painting/qpaintdevice_x11.cpp | 198 + src/gui/painting/qpaintengine.cpp | 1030 ++ src/gui/painting/qpaintengine.h | 368 + src/gui/painting/qpaintengine_alpha.cpp | 516 + src/gui/painting/qpaintengine_alpha_p.h | 135 + src/gui/painting/qpaintengine_blitter.cpp | 663 + src/gui/painting/qpaintengine_blitter_p.h | 113 + src/gui/painting/qpaintengine_mac.cpp | 1749 +++ src/gui/painting/qpaintengine_mac_p.h | 254 + src/gui/painting/qpaintengine_p.h | 125 + src/gui/painting/qpaintengine_preview.cpp | 223 + src/gui/painting/qpaintengine_preview_p.h | 106 + src/gui/painting/qpaintengine_raster.cpp | 6325 +++++++++ src/gui/painting/qpaintengine_raster_p.h | 567 + src/gui/painting/qpaintengine_s60.cpp | 145 + src/gui/painting/qpaintengine_s60_p.h | 84 + src/gui/painting/qpaintengine_x11.cpp | 2507 ++++ src/gui/painting/qpaintengine_x11_p.h | 246 + src/gui/painting/qpaintengineex.cpp | 1015 ++ src/gui/painting/qpaintengineex_p.h | 268 + src/gui/painting/qpainter.cpp | 9572 ++++++++++++++ src/gui/painting/qpainter.h | 1008 ++ src/gui/painting/qpainter_p.h | 273 + src/gui/painting/qpainterpath.cpp | 3420 +++++ src/gui/painting/qpainterpath.h | 435 + src/gui/painting/qpainterpath_p.h | 278 + src/gui/painting/qpathclipper.cpp | 2149 ++++ src/gui/painting/qpathclipper_p.h | 494 + src/gui/painting/qpdf.cpp | 2106 +++ src/gui/painting/qpdf_p.h | 299 + src/gui/painting/qpen.cpp | 1032 ++ src/gui/painting/qpen.h | 145 + src/gui/painting/qpen_p.h | 78 + src/gui/painting/qpolygon.cpp | 1003 ++ src/gui/painting/qpolygon.h | 189 + src/gui/painting/qpolygonclipper_p.h | 317 + src/gui/painting/qprintengine.h | 119 + src/gui/painting/qprintengine_mac.mm | 911 ++ src/gui/painting/qprintengine_mac_p.h | 166 + src/gui/painting/qprintengine_pdf.cpp | 1241 ++ src/gui/painting/qprintengine_pdf_p.h | 195 + src/gui/painting/qprintengine_ps.cpp | 972 ++ src/gui/painting/qprintengine_ps_p.h | 137 + src/gui/painting/qprintengine_qws.cpp | 886 ++ src/gui/painting/qprintengine_qws_p.h | 213 + src/gui/painting/qprintengine_win.cpp | 1776 +++ src/gui/painting/qprintengine_win_p.h | 261 + src/gui/painting/qprinter.cpp | 2453 ++++ src/gui/painting/qprinter.h | 337 + src/gui/painting/qprinter_p.h | 140 + src/gui/painting/qprinterinfo.cpp | 173 + src/gui/painting/qprinterinfo.h | 90 + src/gui/painting/qprinterinfo_mac.cpp | 120 + src/gui/painting/qprinterinfo_p.h | 107 + src/gui/painting/qprinterinfo_unix.cpp | 927 ++ src/gui/painting/qprinterinfo_unix_p.h | 125 + src/gui/painting/qprinterinfo_win.cpp | 122 + src/gui/painting/qpsprinter.agl | 452 + src/gui/painting/qpsprinter.ps | 449 + src/gui/painting/qrasterdefs_p.h | 1279 ++ src/gui/painting/qrasterizer.cpp | 1270 ++ src/gui/painting/qrasterizer_p.h | 95 + src/gui/painting/qregion.cpp | 4365 +++++++ src/gui/painting/qregion.h | 239 + src/gui/painting/qregion_mac.cpp | 286 + src/gui/painting/qregion_qws.cpp | 3183 +++++ src/gui/painting/qregion_s60.cpp | 52 + src/gui/painting/qregion_win.cpp | 149 + src/gui/painting/qregion_x11.cpp | 92 + src/gui/painting/qrgb.h | 88 + src/gui/painting/qstroker.cpp | 1263 ++ src/gui/painting/qstroker_p.h | 394 + src/gui/painting/qstylepainter.cpp | 176 + src/gui/painting/qstylepainter.h | 112 + src/gui/painting/qtessellator.cpp | 1498 +++ src/gui/painting/qtessellator_p.h | 102 + src/gui/painting/qtextureglyphcache.cpp | 468 + src/gui/painting/qtextureglyphcache_p.h | 189 + src/gui/painting/qtransform.cpp | 2301 ++++ src/gui/painting/qtransform.h | 395 + src/gui/painting/qunifiedtoolbarsurface_mac.cpp | 264 + src/gui/painting/qunifiedtoolbarsurface_mac_p.h | 107 + src/gui/painting/qvectorpath_p.h | 184 + src/gui/painting/qwindowsurface.cpp | 383 + src/gui/painting/qwindowsurface_mac.cpp | 139 + src/gui/painting/qwindowsurface_mac_p.h | 84 + src/gui/painting/qwindowsurface_p.h | 138 + src/gui/painting/qwindowsurface_qws.cpp | 1433 +++ src/gui/painting/qwindowsurface_qws_p.h | 355 + src/gui/painting/qwindowsurface_raster.cpp | 487 + src/gui/painting/qwindowsurface_raster_p.h | 132 + src/gui/painting/qwindowsurface_s60.cpp | 254 + src/gui/painting/qwindowsurface_s60_p.h | 93 + src/gui/painting/qwindowsurface_x11.cpp | 265 + src/gui/painting/qwindowsurface_x11_p.h | 92 + src/gui/painting/qwmatrix.h | 61 + src/gui/s60framework/qs60mainapplication.cpp | 165 + src/gui/s60framework/qs60mainapplication.h | 93 + src/gui/s60framework/qs60mainapplication_p.h | 66 + src/gui/s60framework/qs60mainappui.cpp | 430 + src/gui/s60framework/qs60mainappui.h | 152 + src/gui/s60framework/qs60maindocument.cpp | 138 + src/gui/s60framework/qs60maindocument.h | 92 + src/gui/s60framework/s60framework.pri | 10 + src/gui/s60framework/s60main.rss | 85 + src/gui/statemachine/qbasickeyeventtransition.cpp | 208 + src/gui/statemachine/qbasickeyeventtransition_p.h | 98 + .../statemachine/qbasicmouseeventtransition.cpp | 213 + .../statemachine/qbasicmouseeventtransition_p.h | 101 + src/gui/statemachine/qguistatemachine.cpp | 523 + src/gui/statemachine/qkeyeventtransition.cpp | 178 + src/gui/statemachine/qkeyeventtransition.h | 88 + src/gui/statemachine/qmouseeventtransition.cpp | 206 + src/gui/statemachine/qmouseeventtransition.h | 92 + src/gui/statemachine/statemachine.pri | 13 + src/gui/styles/images/cdr-128.png | Bin 0 -> 16418 bytes src/gui/styles/images/cdr-16.png | Bin 0 -> 845 bytes src/gui/styles/images/cdr-32.png | Bin 0 -> 2016 bytes src/gui/styles/images/closedock-16.png | Bin 0 -> 516 bytes src/gui/styles/images/closedock-down-16.png | Bin 0 -> 578 bytes src/gui/styles/images/computer-16.png | Bin 0 -> 782 bytes src/gui/styles/images/computer-32.png | Bin 0 -> 1807 bytes src/gui/styles/images/defaults60theme.blob | Bin 0 -> 74127 bytes src/gui/styles/images/desktop-16.png | Bin 0 -> 773 bytes src/gui/styles/images/desktop-32.png | Bin 0 -> 1103 bytes src/gui/styles/images/dirclosed-128.png | Bin 0 -> 1386 bytes src/gui/styles/images/dirclosed-16.png | Bin 0 -> 231 bytes src/gui/styles/images/dirclosed-32.png | Bin 0 -> 474 bytes src/gui/styles/images/dirlink-128.png | Bin 0 -> 5155 bytes src/gui/styles/images/dirlink-16.png | Bin 0 -> 416 bytes src/gui/styles/images/dirlink-32.png | Bin 0 -> 1046 bytes src/gui/styles/images/diropen-128.png | Bin 0 -> 2075 bytes src/gui/styles/images/diropen-16.png | Bin 0 -> 248 bytes src/gui/styles/images/diropen-32.png | Bin 0 -> 633 bytes src/gui/styles/images/dockdock-16.png | Bin 0 -> 438 bytes src/gui/styles/images/dockdock-down-16.png | Bin 0 -> 406 bytes src/gui/styles/images/down-128.png | Bin 0 -> 9550 bytes src/gui/styles/images/down-16.png | Bin 0 -> 817 bytes src/gui/styles/images/down-32.png | Bin 0 -> 1820 bytes src/gui/styles/images/dvd-128.png | Bin 0 -> 14941 bytes src/gui/styles/images/dvd-16.png | Bin 0 -> 892 bytes src/gui/styles/images/dvd-32.png | Bin 0 -> 2205 bytes src/gui/styles/images/file-128.png | Bin 0 -> 3997 bytes src/gui/styles/images/file-16.png | Bin 0 -> 423 bytes src/gui/styles/images/file-32.png | Bin 0 -> 713 bytes src/gui/styles/images/filecontents-128.png | Bin 0 -> 8109 bytes src/gui/styles/images/filecontents-16.png | Bin 0 -> 766 bytes src/gui/styles/images/filecontents-32.png | Bin 0 -> 1712 bytes src/gui/styles/images/fileinfo-128.png | Bin 0 -> 12002 bytes src/gui/styles/images/fileinfo-16.png | Bin 0 -> 849 bytes src/gui/styles/images/fileinfo-32.png | Bin 0 -> 2010 bytes src/gui/styles/images/filelink-128.png | Bin 0 -> 5601 bytes src/gui/styles/images/filelink-16.png | Bin 0 -> 566 bytes src/gui/styles/images/filelink-32.png | Bin 0 -> 1192 bytes src/gui/styles/images/floppy-128.png | Bin 0 -> 5074 bytes src/gui/styles/images/floppy-16.png | Bin 0 -> 602 bytes src/gui/styles/images/floppy-32.png | Bin 0 -> 1019 bytes src/gui/styles/images/fontbitmap-16.png | Bin 0 -> 537 bytes src/gui/styles/images/fonttruetype-16.png | Bin 0 -> 442 bytes src/gui/styles/images/harddrive-128.png | Bin 0 -> 11250 bytes src/gui/styles/images/harddrive-16.png | Bin 0 -> 802 bytes src/gui/styles/images/harddrive-32.png | Bin 0 -> 1751 bytes src/gui/styles/images/left-128.png | Bin 0 -> 9432 bytes src/gui/styles/images/left-16.png | Bin 0 -> 826 bytes src/gui/styles/images/left-32.png | Bin 0 -> 1799 bytes src/gui/styles/images/media-pause-16.png | Bin 0 -> 229 bytes src/gui/styles/images/media-pause-32.png | Bin 0 -> 185 bytes src/gui/styles/images/media-play-16.png | Bin 0 -> 262 bytes src/gui/styles/images/media-play-32.png | Bin 0 -> 413 bytes src/gui/styles/images/media-seek-backward-16.png | Bin 0 -> 384 bytes src/gui/styles/images/media-seek-backward-32.png | Bin 0 -> 548 bytes src/gui/styles/images/media-seek-forward-16.png | Bin 0 -> 370 bytes src/gui/styles/images/media-seek-forward-32.png | Bin 0 -> 524 bytes src/gui/styles/images/media-skip-backward-16.png | Bin 0 -> 396 bytes src/gui/styles/images/media-skip-backward-32.png | Bin 0 -> 570 bytes src/gui/styles/images/media-skip-forward-16.png | Bin 0 -> 384 bytes src/gui/styles/images/media-skip-forward-32.png | Bin 0 -> 549 bytes src/gui/styles/images/media-stop-16.png | Bin 0 -> 166 bytes src/gui/styles/images/media-stop-32.png | Bin 0 -> 176 bytes src/gui/styles/images/media-volume-16.png | Bin 0 -> 799 bytes src/gui/styles/images/media-volume-muted-16.png | Bin 0 -> 668 bytes src/gui/styles/images/networkdrive-128.png | Bin 0 -> 18075 bytes src/gui/styles/images/networkdrive-16.png | Bin 0 -> 885 bytes src/gui/styles/images/networkdrive-32.png | Bin 0 -> 2245 bytes src/gui/styles/images/newdirectory-128.png | Bin 0 -> 7503 bytes src/gui/styles/images/newdirectory-16.png | Bin 0 -> 870 bytes src/gui/styles/images/newdirectory-32.png | Bin 0 -> 1590 bytes src/gui/styles/images/parentdir-128.png | Bin 0 -> 8093 bytes src/gui/styles/images/parentdir-16.png | Bin 0 -> 938 bytes src/gui/styles/images/parentdir-32.png | Bin 0 -> 1603 bytes src/gui/styles/images/refresh-24.png | Bin 0 -> 1654 bytes src/gui/styles/images/refresh-32.png | Bin 0 -> 2431 bytes src/gui/styles/images/right-128.png | Bin 0 -> 9367 bytes src/gui/styles/images/right-16.png | Bin 0 -> 811 bytes src/gui/styles/images/right-32.png | Bin 0 -> 1804 bytes src/gui/styles/images/standardbutton-apply-128.png | Bin 0 -> 5395 bytes src/gui/styles/images/standardbutton-apply-16.png | Bin 0 -> 611 bytes src/gui/styles/images/standardbutton-apply-32.png | Bin 0 -> 1279 bytes .../styles/images/standardbutton-cancel-128.png | Bin 0 -> 7039 bytes src/gui/styles/images/standardbutton-cancel-16.png | Bin 0 -> 689 bytes src/gui/styles/images/standardbutton-cancel-32.png | Bin 0 -> 1573 bytes src/gui/styles/images/standardbutton-clear-128.png | Bin 0 -> 3094 bytes src/gui/styles/images/standardbutton-clear-16.png | Bin 0 -> 456 bytes src/gui/styles/images/standardbutton-clear-32.png | Bin 0 -> 866 bytes src/gui/styles/images/standardbutton-close-128.png | Bin 0 -> 4512 bytes src/gui/styles/images/standardbutton-close-16.png | Bin 0 -> 366 bytes src/gui/styles/images/standardbutton-close-32.png | Bin 0 -> 780 bytes .../styles/images/standardbutton-closetab-16.png | Bin 0 -> 406 bytes .../images/standardbutton-closetab-down-16.png | Bin 0 -> 481 bytes .../images/standardbutton-closetab-hover-16.png | Bin 0 -> 570 bytes .../styles/images/standardbutton-delete-128.png | Bin 0 -> 5414 bytes src/gui/styles/images/standardbutton-delete-16.png | Bin 0 -> 722 bytes src/gui/styles/images/standardbutton-delete-32.png | Bin 0 -> 1541 bytes src/gui/styles/images/standardbutton-help-128.png | Bin 0 -> 10765 bytes src/gui/styles/images/standardbutton-help-16.png | Bin 0 -> 840 bytes src/gui/styles/images/standardbutton-help-32.png | Bin 0 -> 2066 bytes src/gui/styles/images/standardbutton-no-128.png | Bin 0 -> 6520 bytes src/gui/styles/images/standardbutton-no-16.png | Bin 0 -> 701 bytes src/gui/styles/images/standardbutton-no-32.png | Bin 0 -> 1445 bytes src/gui/styles/images/standardbutton-ok-128.png | Bin 0 -> 4232 bytes src/gui/styles/images/standardbutton-ok-16.png | Bin 0 -> 584 bytes src/gui/styles/images/standardbutton-ok-32.png | Bin 0 -> 1246 bytes src/gui/styles/images/standardbutton-open-128.png | Bin 0 -> 5415 bytes src/gui/styles/images/standardbutton-open-16.png | Bin 0 -> 629 bytes src/gui/styles/images/standardbutton-open-32.png | Bin 0 -> 1154 bytes src/gui/styles/images/standardbutton-save-128.png | Bin 0 -> 4398 bytes src/gui/styles/images/standardbutton-save-16.png | Bin 0 -> 583 bytes src/gui/styles/images/standardbutton-save-32.png | Bin 0 -> 1092 bytes src/gui/styles/images/standardbutton-yes-128.png | Bin 0 -> 6554 bytes src/gui/styles/images/standardbutton-yes-16.png | Bin 0 -> 687 bytes src/gui/styles/images/standardbutton-yes-32.png | Bin 0 -> 1504 bytes src/gui/styles/images/stop-24.png | Bin 0 -> 1267 bytes src/gui/styles/images/stop-32.png | Bin 0 -> 1878 bytes src/gui/styles/images/trash-128.png | Bin 0 -> 3296 bytes src/gui/styles/images/trash-16.png | Bin 0 -> 419 bytes src/gui/styles/images/trash-32.png | Bin 0 -> 883 bytes src/gui/styles/images/up-128.png | Bin 0 -> 9363 bytes src/gui/styles/images/up-16.png | Bin 0 -> 814 bytes src/gui/styles/images/up-32.png | Bin 0 -> 1798 bytes src/gui/styles/images/viewdetailed-128.png | Bin 0 -> 4743 bytes src/gui/styles/images/viewdetailed-16.png | Bin 0 -> 499 bytes src/gui/styles/images/viewdetailed-32.png | Bin 0 -> 1092 bytes src/gui/styles/images/viewlist-128.png | Bin 0 -> 4069 bytes src/gui/styles/images/viewlist-16.png | Bin 0 -> 490 bytes src/gui/styles/images/viewlist-32.png | Bin 0 -> 1006 bytes src/gui/styles/qcdestyle.cpp | 305 + src/gui/styles/qcdestyle.h | 82 + src/gui/styles/qcleanlooksstyle.cpp | 4466 +++++++ src/gui/styles/qcleanlooksstyle.h | 114 + src/gui/styles/qcleanlooksstyle_p.h | 80 + src/gui/styles/qcommonstyle.cpp | 6085 +++++++++ src/gui/styles/qcommonstyle.h | 109 + src/gui/styles/qcommonstyle_p.h | 113 + src/gui/styles/qcommonstylepixmaps_p.h | 186 + src/gui/styles/qgtkpainter.cpp | 721 ++ src/gui/styles/qgtkpainter_p.h | 129 + src/gui/styles/qgtkstyle.cpp | 3563 ++++++ src/gui/styles/qgtkstyle.h | 128 + src/gui/styles/qgtkstyle_p.cpp | 1146 ++ src/gui/styles/qgtkstyle_p.h | 531 + src/gui/styles/qmacstyle.qdoc | 247 + src/gui/styles/qmacstyle_mac.h | 148 + src/gui/styles/qmacstyle_mac.mm | 6042 +++++++++ src/gui/styles/qmacstyle_mac_p.h | 241 + src/gui/styles/qmacstylepixmaps_mac_p.h | 72 + src/gui/styles/qmotifstyle.cpp | 2721 ++++ src/gui/styles/qmotifstyle.h | 128 + src/gui/styles/qmotifstyle_p.h | 82 + src/gui/styles/qplastiquestyle.cpp | 6011 +++++++++ src/gui/styles/qplastiquestyle.h | 119 + src/gui/styles/qproxystyle.cpp | 420 + src/gui/styles/qproxystyle.h | 114 + src/gui/styles/qproxystyle_p.h | 79 + src/gui/styles/qs60style.cpp | 3618 ++++++ src/gui/styles/qs60style.h | 118 + src/gui/styles/qs60style_p.h | 638 + src/gui/styles/qs60style_s60.cpp | 1591 +++ src/gui/styles/qs60style_simulated.cpp | 457 + src/gui/styles/qs60style_stub.cpp | 131 + src/gui/styles/qstyle.cpp | 2493 ++++ src/gui/styles/qstyle.h | 889 ++ src/gui/styles/qstyle.qrc | 135 + src/gui/styles/qstyle_p.h | 108 + src/gui/styles/qstyle_s60.qrc | 137 + src/gui/styles/qstyle_s60_simulated.qrc | 6 + src/gui/styles/qstyle_wince.qrc | 97 + src/gui/styles/qstylefactory.cpp | 271 + src/gui/styles/qstylefactory.h | 66 + src/gui/styles/qstylehelper.cpp | 378 + src/gui/styles/qstylehelper_p.h | 118 + src/gui/styles/qstyleoption.cpp | 5508 ++++++++ src/gui/styles/qstyleoption.h | 970 ++ src/gui/styles/qstyleplugin.cpp | 115 + src/gui/styles/qstyleplugin.h | 81 + src/gui/styles/qstylesheetstyle.cpp | 5898 +++++++++ src/gui/styles/qstylesheetstyle_default.cpp | 512 + src/gui/styles/qstylesheetstyle_p.h | 204 + src/gui/styles/qwindowscestyle.cpp | 2429 ++++ src/gui/styles/qwindowscestyle.h | 103 + src/gui/styles/qwindowscestyle_p.h | 118 + src/gui/styles/qwindowsmobilestyle.cpp | 7283 +++++++++++ src/gui/styles/qwindowsmobilestyle.h | 116 + src/gui/styles/qwindowsmobilestyle_p.h | 136 + src/gui/styles/qwindowsstyle.cpp | 3392 +++++ src/gui/styles/qwindowsstyle.h | 111 + src/gui/styles/qwindowsstyle_p.h | 106 + src/gui/styles/qwindowsvistastyle.cpp | 2670 ++++ src/gui/styles/qwindowsvistastyle.h | 108 + src/gui/styles/qwindowsvistastyle_p.h | 220 + src/gui/styles/qwindowsxpstyle.cpp | 4271 +++++++ src/gui/styles/qwindowsxpstyle.h | 107 + src/gui/styles/qwindowsxpstyle_p.h | 356 + src/gui/styles/styles.pri | 190 + src/gui/symbian/images/blank.png | Bin 0 -> 91 bytes src/gui/symbian/images/busy12.png | Bin 0 -> 253 bytes src/gui/symbian/images/busy3.png | Bin 0 -> 251 bytes src/gui/symbian/images/busy6.png | Bin 0 -> 253 bytes src/gui/symbian/images/busy9.png | Bin 0 -> 255 bytes src/gui/symbian/images/closehand.png | Bin 0 -> 190 bytes src/gui/symbian/images/cross.png | Bin 0 -> 145 bytes src/gui/symbian/images/forbidden.png | Bin 0 -> 256 bytes src/gui/symbian/images/handpoint.png | Bin 0 -> 230 bytes src/gui/symbian/images/ibeam.png | Bin 0 -> 176 bytes src/gui/symbian/images/openhand.png | Bin 0 -> 201 bytes src/gui/symbian/images/pointer.png | Bin 0 -> 222 bytes src/gui/symbian/images/sizeall.png | Bin 0 -> 188 bytes src/gui/symbian/images/sizebdiag.png | Bin 0 -> 192 bytes src/gui/symbian/images/sizefdiag.png | Bin 0 -> 197 bytes src/gui/symbian/images/sizehor.png | Bin 0 -> 175 bytes src/gui/symbian/images/sizever.png | Bin 0 -> 171 bytes src/gui/symbian/images/splith.png | Bin 0 -> 206 bytes src/gui/symbian/images/splitv.png | Bin 0 -> 205 bytes src/gui/symbian/images/uparrow.png | Bin 0 -> 157 bytes src/gui/symbian/images/wait1.png | Bin 0 -> 219 bytes src/gui/symbian/images/wait10.png | Bin 0 -> 220 bytes src/gui/symbian/images/wait11.png | Bin 0 -> 220 bytes src/gui/symbian/images/wait12.png | Bin 0 -> 213 bytes src/gui/symbian/images/wait2.png | Bin 0 -> 219 bytes src/gui/symbian/images/wait3.png | Bin 0 -> 210 bytes src/gui/symbian/images/wait4.png | Bin 0 -> 215 bytes src/gui/symbian/images/wait5.png | Bin 0 -> 217 bytes src/gui/symbian/images/wait6.png | Bin 0 -> 213 bytes src/gui/symbian/images/wait7.png | Bin 0 -> 215 bytes src/gui/symbian/images/wait8.png | Bin 0 -> 217 bytes src/gui/symbian/images/wait9.png | Bin 0 -> 209 bytes src/gui/symbian/images/whatsthis.png | Bin 0 -> 254 bytes src/gui/symbian/qsymbianevent.cpp | 176 + src/gui/symbian/qsymbianevent.h | 108 + src/gui/symbian/symbianresources.qrc | 37 + src/gui/text/qabstractfontengine_p.h | 110 + src/gui/text/qabstractfontengine_qws.cpp | 776 ++ src/gui/text/qabstractfontengine_qws.h | 221 + src/gui/text/qabstracttextdocumentlayout.cpp | 623 + src/gui/text/qabstracttextdocumentlayout.h | 150 + src/gui/text/qabstracttextdocumentlayout_p.h | 100 + src/gui/text/qcssparser.cpp | 2768 ++++ src/gui/text/qcssparser_p.h | 847 ++ src/gui/text/qcssscanner.cpp | 1146 ++ src/gui/text/qfont.cpp | 3188 +++++ src/gui/text/qfont.h | 378 + src/gui/text/qfont_mac.cpp | 165 + src/gui/text/qfont_p.h | 290 + src/gui/text/qfont_qpa.cpp | 114 + src/gui/text/qfont_qws.cpp | 135 + src/gui/text/qfont_s60.cpp | 136 + src/gui/text/qfont_win.cpp | 166 + src/gui/text/qfont_x11.cpp | 368 + src/gui/text/qfontdatabase.cpp | 2715 ++++ src/gui/text/qfontdatabase.h | 180 + src/gui/text/qfontdatabase_mac.cpp | 466 + src/gui/text/qfontdatabase_qpa.cpp | 394 + src/gui/text/qfontdatabase_qws.cpp | 975 ++ src/gui/text/qfontdatabase_s60.cpp | 1091 ++ src/gui/text/qfontdatabase_win.cpp | 1348 ++ src/gui/text/qfontdatabase_x11.cpp | 2146 ++++ src/gui/text/qfontengine.cpp | 1684 +++ src/gui/text/qfontengine_coretext.mm | 843 ++ src/gui/text/qfontengine_coretext_p.h | 141 + src/gui/text/qfontengine_ft.cpp | 2074 +++ src/gui/text/qfontengine_ft_p.h | 380 + src/gui/text/qfontengine_mac.mm | 1236 ++ src/gui/text/qfontengine_mac_p.h | 165 + src/gui/text/qfontengine_p.h | 452 + src/gui/text/qfontengine_qpa.cpp | 691 + src/gui/text/qfontengine_qpa_p.h | 262 + src/gui/text/qfontengine_qpf.cpp | 1220 ++ src/gui/text/qfontengine_qpf_p.h | 299 + src/gui/text/qfontengine_qws.cpp | 665 + src/gui/text/qfontengine_s60.cpp | 569 + src/gui/text/qfontengine_s60_p.h | 167 + src/gui/text/qfontengine_win.cpp | 1322 ++ src/gui/text/qfontengine_win_p.h | 161 + src/gui/text/qfontengine_x11.cpp | 1201 ++ src/gui/text/qfontengine_x11_p.h | 178 + src/gui/text/qfontenginedirectwrite.cpp | 635 + src/gui/text/qfontenginedirectwrite_p.h | 130 + src/gui/text/qfontengineglyphcache_p.h | 98 + src/gui/text/qfontinfo.h | 88 + src/gui/text/qfontmetrics.cpp | 1819 +++ src/gui/text/qfontmetrics.h | 208 + src/gui/text/qfontsubset.cpp | 1743 +++ src/gui/text/qfontsubset_p.h | 99 + src/gui/text/qfragmentmap.cpp | 46 + src/gui/text/qfragmentmap_p.h | 888 ++ src/gui/text/qglyphs.cpp | 323 + src/gui/text/qglyphs.h | 107 + src/gui/text/qglyphs_p.h | 103 + src/gui/text/qpfutil.cpp | 66 + src/gui/text/qplatformfontdatabase_qpa.cpp | 291 + src/gui/text/qplatformfontdatabase_qpa.h | 108 + src/gui/text/qrawfont.cpp | 612 + src/gui/text/qrawfont.h | 140 + src/gui/text/qrawfont_ft.cpp | 189 + src/gui/text/qrawfont_mac.cpp | 105 + src/gui/text/qrawfont_p.h | 132 + src/gui/text/qrawfont_win.cpp | 750 ++ src/gui/text/qstatictext.cpp | 733 ++ src/gui/text/qstatictext.h | 109 + src/gui/text/qstatictext_p.h | 203 + src/gui/text/qsyntaxhighlighter.cpp | 665 + src/gui/text/qsyntaxhighlighter.h | 112 + src/gui/text/qtextcontrol.cpp | 3148 +++++ src/gui/text/qtextcontrol_p.h | 312 + src/gui/text/qtextcontrol_p_p.h | 238 + src/gui/text/qtextcursor.cpp | 2561 ++++ src/gui/text/qtextcursor.h | 240 + src/gui/text/qtextcursor_p.h | 122 + src/gui/text/qtextdocument.cpp | 3044 +++++ src/gui/text/qtextdocument.h | 309 + src/gui/text/qtextdocument_p.cpp | 1724 +++ src/gui/text/qtextdocument_p.h | 408 + src/gui/text/qtextdocumentfragment.cpp | 1224 ++ src/gui/text/qtextdocumentfragment.h | 92 + src/gui/text/qtextdocumentfragment_p.h | 236 + src/gui/text/qtextdocumentlayout.cpp | 3263 +++++ src/gui/text/qtextdocumentlayout_p.h | 119 + src/gui/text/qtextdocumentwriter.cpp | 372 + src/gui/text/qtextdocumentwriter.h | 93 + src/gui/text/qtextengine.cpp | 2845 +++++ src/gui/text/qtextengine_mac.cpp | 656 + src/gui/text/qtextengine_p.h | 643 + src/gui/text/qtextformat.cpp | 3297 +++++ src/gui/text/qtextformat.h | 976 ++ src/gui/text/qtextformat_p.h | 111 + src/gui/text/qtexthtmlparser.cpp | 1905 +++ src/gui/text/qtexthtmlparser_p.h | 344 + src/gui/text/qtextimagehandler.cpp | 232 + src/gui/text/qtextimagehandler_p.h | 80 + src/gui/text/qtextlayout.cpp | 2929 +++++ src/gui/text/qtextlayout.h | 259 + src/gui/text/qtextlist.cpp | 321 + src/gui/text/qtextlist.h | 94 + src/gui/text/qtextobject.cpp | 1785 +++ src/gui/text/qtextobject.h | 335 + src/gui/text/qtextobject_p.h | 114 + src/gui/text/qtextodfwriter.cpp | 837 ++ src/gui/text/qtextodfwriter_p.h | 115 + src/gui/text/qtextoption.cpp | 434 + src/gui/text/qtextoption.h | 163 + src/gui/text/qtexttable.cpp | 1323 ++ src/gui/text/qtexttable.h | 145 + src/gui/text/qtexttable_p.h | 89 + src/gui/text/qzip.cpp | 1263 ++ src/gui/text/qzipreader_p.h | 124 + src/gui/text/qzipwriter_p.h | 116 + src/gui/text/text.pri | 245 + src/gui/util/qcompleter.cpp | 1833 +++ src/gui/util/qcompleter.h | 171 + src/gui/util/qcompleter_p.h | 264 + src/gui/util/qdesktopservices.cpp | 309 + src/gui/util/qdesktopservices.h | 91 + src/gui/util/qdesktopservices_mac.cpp | 188 + src/gui/util/qdesktopservices_qws.cpp | 93 + src/gui/util/qdesktopservices_s60.cpp | 461 + src/gui/util/qdesktopservices_win.cpp | 267 + src/gui/util/qdesktopservices_x11.cpp | 242 + src/gui/util/qflickgesture.cpp | 715 ++ src/gui/util/qflickgesture_p.h | 113 + src/gui/util/qscroller.cpp | 2059 +++ src/gui/util/qscroller.h | 155 + src/gui/util/qscroller_mac.mm | 71 + src/gui/util/qscroller_p.h | 209 + src/gui/util/qscrollerproperties.cpp | 393 + src/gui/util/qscrollerproperties.h | 140 + src/gui/util/qscrollerproperties_p.h | 94 + src/gui/util/qsystemtrayicon.cpp | 674 + src/gui/util/qsystemtrayicon.h | 132 + src/gui/util/qsystemtrayicon_mac.mm | 578 + src/gui/util/qsystemtrayicon_p.h | 186 + src/gui/util/qsystemtrayicon_qws.cpp | 96 + src/gui/util/qsystemtrayicon_win.cpp | 524 + src/gui/util/qsystemtrayicon_wince.cpp | 293 + src/gui/util/qsystemtrayicon_x11.cpp | 401 + src/gui/util/qundogroup.cpp | 499 + src/gui/util/qundogroup.h | 110 + src/gui/util/qundostack.cpp | 1127 ++ src/gui/util/qundostack.h | 158 + src/gui/util/qundostack_p.h | 111 + src/gui/util/qundoview.cpp | 476 + src/gui/util/qundoview.h | 102 + src/gui/util/util.pri | 71 + src/gui/widgets/qabstractbutton.cpp | 1470 +++ src/gui/widgets/qabstractbutton.h | 180 + src/gui/widgets/qabstractbutton_p.h | 110 + src/gui/widgets/qabstractscrollarea.cpp | 1506 +++ src/gui/widgets/qabstractscrollarea.h | 144 + src/gui/widgets/qabstractscrollarea_p.h | 146 + src/gui/widgets/qabstractslider.cpp | 1001 ++ src/gui/widgets/qabstractslider.h | 184 + src/gui/widgets/qabstractslider_p.h | 147 + src/gui/widgets/qabstractspinbox.cpp | 2122 ++++ src/gui/widgets/qabstractspinbox.h | 181 + src/gui/widgets/qabstractspinbox_p.h | 170 + src/gui/widgets/qbuttongroup.cpp | 269 + src/gui/widgets/qbuttongroup.h | 112 + src/gui/widgets/qcalendartextnavigator_p.h | 112 + src/gui/widgets/qcalendarwidget.cpp | 3104 +++++ src/gui/widgets/qcalendarwidget.h | 204 + src/gui/widgets/qcheckbox.cpp | 447 + src/gui/widgets/qcheckbox.h | 114 + src/gui/widgets/qcocoamenu_mac.mm | 244 + src/gui/widgets/qcocoamenu_mac_p.h | 82 + src/gui/widgets/qcocoatoolbardelegate_mac.mm | 153 + src/gui/widgets/qcocoatoolbardelegate_mac_p.h | 71 + src/gui/widgets/qcombobox.cpp | 3365 +++++ src/gui/widgets/qcombobox.h | 339 + src/gui/widgets/qcombobox_p.h | 421 + src/gui/widgets/qcommandlinkbutton.cpp | 410 + src/gui/widgets/qcommandlinkbutton.h | 85 + src/gui/widgets/qdatetimeedit.cpp | 2642 ++++ src/gui/widgets/qdatetimeedit.h | 230 + src/gui/widgets/qdatetimeedit_p.h | 183 + src/gui/widgets/qdial.cpp | 546 + src/gui/widgets/qdial.h | 122 + src/gui/widgets/qdialogbuttonbox.cpp | 1285 ++ src/gui/widgets/qdialogbuttonbox.h | 168 + src/gui/widgets/qdockarealayout.cpp | 3334 +++++ src/gui/widgets/qdockarealayout_p.h | 308 + src/gui/widgets/qdockwidget.cpp | 1606 +++ src/gui/widgets/qdockwidget.h | 146 + src/gui/widgets/qdockwidget_p.h | 207 + src/gui/widgets/qeffects.cpp | 613 + src/gui/widgets/qeffects_p.h | 84 + src/gui/widgets/qfocusframe.cpp | 339 + src/gui/widgets/qfocusframe.h | 82 + src/gui/widgets/qfontcombobox.cpp | 474 + src/gui/widgets/qfontcombobox.h | 112 + src/gui/widgets/qframe.cpp | 564 + src/gui/widgets/qframe.h | 148 + src/gui/widgets/qframe_p.h | 84 + src/gui/widgets/qgroupbox.cpp | 781 ++ src/gui/widgets/qgroupbox.h | 122 + src/gui/widgets/qlabel.cpp | 1718 +++ src/gui/widgets/qlabel.h | 182 + src/gui/widgets/qlabel_p.h | 153 + src/gui/widgets/qlcdnumber.cpp | 1312 ++ src/gui/widgets/qlcdnumber.h | 144 + src/gui/widgets/qlinecontrol.cpp | 1832 +++ src/gui/widgets/qlinecontrol_p.h | 452 + src/gui/widgets/qlineedit.cpp | 2332 ++++ src/gui/widgets/qlineedit.h | 295 + src/gui/widgets/qlineedit_p.cpp | 291 + src/gui/widgets/qlineedit_p.h | 156 + src/gui/widgets/qmaccocoaviewcontainer_mac.h | 73 + src/gui/widgets/qmaccocoaviewcontainer_mac.mm | 187 + src/gui/widgets/qmacnativewidget_mac.h | 74 + src/gui/widgets/qmacnativewidget_mac.mm | 133 + src/gui/widgets/qmainwindow.cpp | 1686 +++ src/gui/widgets/qmainwindow.h | 219 + src/gui/widgets/qmainwindowlayout.cpp | 2017 +++ src/gui/widgets/qmainwindowlayout_mac.mm | 607 + src/gui/widgets/qmainwindowlayout_p.h | 357 + src/gui/widgets/qmdiarea.cpp | 2678 ++++ src/gui/widgets/qmdiarea.h | 179 + src/gui/widgets/qmdiarea_p.h | 285 + src/gui/widgets/qmdisubwindow.cpp | 3547 ++++++ src/gui/widgets/qmdisubwindow.h | 159 + src/gui/widgets/qmdisubwindow_p.h | 348 + src/gui/widgets/qmenu.cpp | 3525 ++++++ src/gui/widgets/qmenu.h | 434 + src/gui/widgets/qmenu_mac.mm | 2217 ++++ src/gui/widgets/qmenu_p.h | 393 + src/gui/widgets/qmenu_symbian.cpp | 464 + src/gui/widgets/qmenu_wince.cpp | 668 + src/gui/widgets/qmenu_wince.rc | 231 + src/gui/widgets/qmenu_wince_resource_p.h | 94 + src/gui/widgets/qmenubar.cpp | 2508 ++++ src/gui/widgets/qmenubar.h | 367 + src/gui/widgets/qmenubar_p.h | 283 + src/gui/widgets/qmenudata.cpp | 96 + src/gui/widgets/qmenudata.h | 80 + src/gui/widgets/qplaintextedit.cpp | 2992 +++++ src/gui/widgets/qplaintextedit.h | 329 + src/gui/widgets/qplaintextedit_p.h | 187 + src/gui/widgets/qprintpreviewwidget.cpp | 844 ++ src/gui/widgets/qprintpreviewwidget.h | 127 + src/gui/widgets/qprogressbar.cpp | 595 + src/gui/widgets/qprogressbar.h | 132 + src/gui/widgets/qpushbutton.cpp | 784 ++ src/gui/widgets/qpushbutton.h | 127 + src/gui/widgets/qpushbutton_p.h | 90 + src/gui/widgets/qradiobutton.cpp | 296 + src/gui/widgets/qradiobutton.h | 89 + src/gui/widgets/qrubberband.cpp | 336 + src/gui/widgets/qrubberband.h | 104 + src/gui/widgets/qscrollarea.cpp | 522 + src/gui/widgets/qscrollarea.h | 101 + src/gui/widgets/qscrollarea_p.h | 81 + src/gui/widgets/qscrollbar.cpp | 764 ++ src/gui/widgets/qscrollbar.h | 104 + src/gui/widgets/qsizegrip.cpp | 570 + src/gui/widgets/qsizegrip.h | 95 + src/gui/widgets/qslider.cpp | 666 + src/gui/widgets/qslider.h | 134 + src/gui/widgets/qspinbox.cpp | 1327 ++ src/gui/widgets/qspinbox.h | 188 + src/gui/widgets/qsplashscreen.cpp | 330 + src/gui/widgets/qsplashscreen.h | 99 + src/gui/widgets/qsplitter.cpp | 1877 +++ src/gui/widgets/qsplitter.h | 192 + src/gui/widgets/qsplitter_p.h | 149 + src/gui/widgets/qstackedwidget.cpp | 338 + src/gui/widgets/qstackedwidget.h | 100 + src/gui/widgets/qstatusbar.cpp | 847 ++ src/gui/widgets/qstatusbar.h | 116 + src/gui/widgets/qtabbar.cpp | 2376 ++++ src/gui/widgets/qtabbar.h | 226 + src/gui/widgets/qtabbar_p.h | 263 + src/gui/widgets/qtabwidget.cpp | 1516 +++ src/gui/widgets/qtabwidget.h | 253 + src/gui/widgets/qtextbrowser.cpp | 1275 ++ src/gui/widgets/qtextbrowser.h | 140 + src/gui/widgets/qtextedit.cpp | 2811 ++++ src/gui/widgets/qtextedit.h | 430 + src/gui/widgets/qtextedit_p.h | 141 + src/gui/widgets/qtoolbar.cpp | 1349 ++ src/gui/widgets/qtoolbar.h | 188 + src/gui/widgets/qtoolbar_p.h | 135 + src/gui/widgets/qtoolbararealayout.cpp | 1391 ++ src/gui/widgets/qtoolbararealayout_p.h | 248 + src/gui/widgets/qtoolbarextension.cpp | 92 + src/gui/widgets/qtoolbarextension_p.h | 80 + src/gui/widgets/qtoolbarlayout.cpp | 742 ++ src/gui/widgets/qtoolbarlayout_p.h | 134 + src/gui/widgets/qtoolbarseparator.cpp | 91 + src/gui/widgets/qtoolbarseparator_p.h | 88 + src/gui/widgets/qtoolbox.cpp | 822 ++ src/gui/widgets/qtoolbox.h | 148 + src/gui/widgets/qtoolbutton.cpp | 1260 ++ src/gui/widgets/qtoolbutton.h | 199 + src/gui/widgets/qvalidator.cpp | 925 ++ src/gui/widgets/qvalidator.h | 217 + src/gui/widgets/qwidgetanimator.cpp | 117 + src/gui/widgets/qwidgetanimator_p.h | 89 + src/gui/widgets/qwidgetresizehandler.cpp | 547 + src/gui/widgets/qwidgetresizehandler_p.h | 141 + src/gui/widgets/qworkspace.cpp | 3377 +++++ src/gui/widgets/qworkspace.h | 137 + src/gui/widgets/widgets.pri | 169 + 1494 files changed, 787399 insertions(+) create mode 100644 src/gui/QtGui.dynlist create mode 100644 src/gui/accessible/accessible.pri create mode 100644 src/gui/accessible/qaccessible.cpp create mode 100644 src/gui/accessible/qaccessible.h create mode 100644 src/gui/accessible/qaccessible2.cpp create mode 100644 src/gui/accessible/qaccessible2.h create mode 100644 src/gui/accessible/qaccessible_mac.mm create mode 100644 src/gui/accessible/qaccessible_mac_carbon.cpp create mode 100644 src/gui/accessible/qaccessible_mac_cocoa.mm create mode 100644 src/gui/accessible/qaccessible_mac_p.h create mode 100644 src/gui/accessible/qaccessible_unix.cpp create mode 100644 src/gui/accessible/qaccessible_win.cpp create mode 100644 src/gui/accessible/qaccessiblebridge.cpp create mode 100644 src/gui/accessible/qaccessiblebridge.h create mode 100644 src/gui/accessible/qaccessibleobject.cpp create mode 100644 src/gui/accessible/qaccessibleobject.h create mode 100644 src/gui/accessible/qaccessibleplugin.cpp create mode 100644 src/gui/accessible/qaccessibleplugin.h create mode 100644 src/gui/accessible/qaccessiblewidget.cpp create mode 100644 src/gui/accessible/qaccessiblewidget.h create mode 100644 src/gui/animation/animation.pri create mode 100644 src/gui/animation/qguivariantanimation.cpp create mode 100644 src/gui/dialogs/dialogs.pri create mode 100644 src/gui/dialogs/images/fit-page-24.png create mode 100644 src/gui/dialogs/images/fit-page-32.png create mode 100644 src/gui/dialogs/images/fit-width-24.png create mode 100644 src/gui/dialogs/images/fit-width-32.png create mode 100644 src/gui/dialogs/images/go-first-24.png create mode 100644 src/gui/dialogs/images/go-first-32.png create mode 100644 src/gui/dialogs/images/go-last-24.png create mode 100644 src/gui/dialogs/images/go-last-32.png create mode 100644 src/gui/dialogs/images/go-next-24.png create mode 100644 src/gui/dialogs/images/go-next-32.png create mode 100644 src/gui/dialogs/images/go-previous-24.png create mode 100644 src/gui/dialogs/images/go-previous-32.png create mode 100644 src/gui/dialogs/images/layout-landscape-24.png create mode 100644 src/gui/dialogs/images/layout-landscape-32.png create mode 100644 src/gui/dialogs/images/layout-portrait-24.png create mode 100644 src/gui/dialogs/images/layout-portrait-32.png create mode 100644 src/gui/dialogs/images/page-setup-24.png create mode 100644 src/gui/dialogs/images/page-setup-32.png create mode 100644 src/gui/dialogs/images/print-24.png create mode 100644 src/gui/dialogs/images/print-32.png create mode 100644 src/gui/dialogs/images/qtlogo-64.png create mode 100644 src/gui/dialogs/images/status-color.png create mode 100644 src/gui/dialogs/images/status-gray-scale.png create mode 100644 src/gui/dialogs/images/view-page-multi-24.png create mode 100644 src/gui/dialogs/images/view-page-multi-32.png create mode 100644 src/gui/dialogs/images/view-page-one-24.png create mode 100644 src/gui/dialogs/images/view-page-one-32.png create mode 100644 src/gui/dialogs/images/view-page-sided-24.png create mode 100644 src/gui/dialogs/images/view-page-sided-32.png create mode 100644 src/gui/dialogs/images/zoom-in-24.png create mode 100644 src/gui/dialogs/images/zoom-in-32.png create mode 100644 src/gui/dialogs/images/zoom-out-24.png create mode 100644 src/gui/dialogs/images/zoom-out-32.png create mode 100644 src/gui/dialogs/qabstractpagesetupdialog.cpp create mode 100644 src/gui/dialogs/qabstractpagesetupdialog.h create mode 100644 src/gui/dialogs/qabstractpagesetupdialog_p.h create mode 100644 src/gui/dialogs/qabstractprintdialog.cpp create mode 100644 src/gui/dialogs/qabstractprintdialog.h create mode 100644 src/gui/dialogs/qabstractprintdialog_p.h create mode 100644 src/gui/dialogs/qcolordialog.cpp create mode 100644 src/gui/dialogs/qcolordialog.h create mode 100644 src/gui/dialogs/qcolordialog_mac.mm create mode 100644 src/gui/dialogs/qcolordialog_p.h create mode 100644 src/gui/dialogs/qcolordialog_symbian.cpp create mode 100644 src/gui/dialogs/qdialog.cpp create mode 100644 src/gui/dialogs/qdialog.h create mode 100644 src/gui/dialogs/qdialog_p.h create mode 100644 src/gui/dialogs/qdialogsbinarycompat_win.cpp create mode 100644 src/gui/dialogs/qerrormessage.cpp create mode 100644 src/gui/dialogs/qerrormessage.h create mode 100644 src/gui/dialogs/qfiledialog.cpp create mode 100644 src/gui/dialogs/qfiledialog.h create mode 100644 src/gui/dialogs/qfiledialog.ui create mode 100644 src/gui/dialogs/qfiledialog_embedded.ui create mode 100644 src/gui/dialogs/qfiledialog_mac.mm create mode 100644 src/gui/dialogs/qfiledialog_p.h create mode 100644 src/gui/dialogs/qfiledialog_symbian.cpp create mode 100644 src/gui/dialogs/qfiledialog_win.cpp create mode 100644 src/gui/dialogs/qfiledialog_win_p.h create mode 100644 src/gui/dialogs/qfileinfogatherer.cpp create mode 100644 src/gui/dialogs/qfileinfogatherer_p.h create mode 100644 src/gui/dialogs/qfilesystemmodel.cpp create mode 100644 src/gui/dialogs/qfilesystemmodel.h create mode 100644 src/gui/dialogs/qfilesystemmodel_p.h create mode 100644 src/gui/dialogs/qfontdialog.cpp create mode 100644 src/gui/dialogs/qfontdialog.h create mode 100644 src/gui/dialogs/qfontdialog_mac.mm create mode 100644 src/gui/dialogs/qfontdialog_p.h create mode 100644 src/gui/dialogs/qfscompleter_p.h create mode 100644 src/gui/dialogs/qinputdialog.cpp create mode 100644 src/gui/dialogs/qinputdialog.h create mode 100644 src/gui/dialogs/qmessagebox.cpp create mode 100644 src/gui/dialogs/qmessagebox.h create mode 100644 src/gui/dialogs/qmessagebox.qrc create mode 100644 src/gui/dialogs/qnspanelproxy_mac.mm create mode 100644 src/gui/dialogs/qpagesetupdialog.cpp create mode 100644 src/gui/dialogs/qpagesetupdialog.h create mode 100644 src/gui/dialogs/qpagesetupdialog_mac.mm create mode 100644 src/gui/dialogs/qpagesetupdialog_unix.cpp create mode 100644 src/gui/dialogs/qpagesetupdialog_unix_p.h create mode 100644 src/gui/dialogs/qpagesetupdialog_win.cpp create mode 100644 src/gui/dialogs/qpagesetupwidget.ui create mode 100644 src/gui/dialogs/qprintdialog.h create mode 100644 src/gui/dialogs/qprintdialog.qdoc create mode 100644 src/gui/dialogs/qprintdialog.qrc create mode 100644 src/gui/dialogs/qprintdialog_mac.mm create mode 100644 src/gui/dialogs/qprintdialog_qws.cpp create mode 100644 src/gui/dialogs/qprintdialog_unix.cpp create mode 100644 src/gui/dialogs/qprintdialog_win.cpp create mode 100644 src/gui/dialogs/qprintpreviewdialog.cpp create mode 100644 src/gui/dialogs/qprintpreviewdialog.h create mode 100644 src/gui/dialogs/qprintpropertieswidget.ui create mode 100644 src/gui/dialogs/qprintsettingsoutput.ui create mode 100644 src/gui/dialogs/qprintwidget.ui create mode 100644 src/gui/dialogs/qprogressdialog.cpp create mode 100644 src/gui/dialogs/qprogressdialog.h create mode 100644 src/gui/dialogs/qsidebar.cpp create mode 100644 src/gui/dialogs/qsidebar_p.h create mode 100644 src/gui/dialogs/qwizard.cpp create mode 100644 src/gui/dialogs/qwizard.h create mode 100644 src/gui/dialogs/qwizard_win.cpp create mode 100644 src/gui/dialogs/qwizard_win_p.h create mode 100644 src/gui/effects/effects.pri create mode 100644 src/gui/effects/qgraphicseffect.cpp create mode 100644 src/gui/effects/qgraphicseffect.h create mode 100644 src/gui/effects/qgraphicseffect_p.h create mode 100644 src/gui/egl/egl.pri create mode 100644 src/gui/egl/qegl.cpp create mode 100644 src/gui/egl/qegl_p.h create mode 100644 src/gui/egl/qegl_qpa.cpp create mode 100644 src/gui/egl/qegl_qws.cpp create mode 100644 src/gui/egl/qegl_stub.cpp create mode 100644 src/gui/egl/qegl_symbian.cpp create mode 100644 src/gui/egl/qegl_wince.cpp create mode 100644 src/gui/egl/qegl_x11.cpp create mode 100644 src/gui/egl/qeglcontext_p.h create mode 100644 src/gui/egl/qeglproperties.cpp create mode 100644 src/gui/egl/qeglproperties_p.h create mode 100644 src/gui/egl/qeglproperties_stub.cpp create mode 100644 src/gui/embedded/directfb.pri create mode 100644 src/gui/embedded/embedded.pri create mode 100644 src/gui/embedded/qcopchannel_qws.cpp create mode 100644 src/gui/embedded/qcopchannel_qws.h create mode 100644 src/gui/embedded/qdecoration_qws.cpp create mode 100644 src/gui/embedded/qdecoration_qws.h create mode 100644 src/gui/embedded/qdecorationdefault_qws.cpp create mode 100644 src/gui/embedded/qdecorationdefault_qws.h create mode 100644 src/gui/embedded/qdecorationfactory_qws.cpp create mode 100644 src/gui/embedded/qdecorationfactory_qws.h create mode 100644 src/gui/embedded/qdecorationplugin_qws.cpp create mode 100644 src/gui/embedded/qdecorationplugin_qws.h create mode 100644 src/gui/embedded/qdecorationstyled_qws.cpp create mode 100644 src/gui/embedded/qdecorationstyled_qws.h create mode 100644 src/gui/embedded/qdecorationwindows_qws.cpp create mode 100644 src/gui/embedded/qdecorationwindows_qws.h create mode 100644 src/gui/embedded/qdirectpainter_qws.cpp create mode 100644 src/gui/embedded/qdirectpainter_qws.h create mode 100644 src/gui/embedded/qkbd_defaultmap_qws_p.h create mode 100644 src/gui/embedded/qkbd_qws.cpp create mode 100644 src/gui/embedded/qkbd_qws.h create mode 100644 src/gui/embedded/qkbd_qws_p.h create mode 100644 src/gui/embedded/qkbddriverfactory_qws.cpp create mode 100644 src/gui/embedded/qkbddriverfactory_qws.h create mode 100644 src/gui/embedded/qkbddriverplugin_qws.cpp create mode 100644 src/gui/embedded/qkbddriverplugin_qws.h create mode 100644 src/gui/embedded/qkbdintegrity_qws.cpp create mode 100644 src/gui/embedded/qkbdintegrity_qws.h create mode 100644 src/gui/embedded/qkbdlinuxinput_qws.cpp create mode 100644 src/gui/embedded/qkbdlinuxinput_qws.h create mode 100644 src/gui/embedded/qkbdqnx_qws.cpp create mode 100644 src/gui/embedded/qkbdqnx_qws.h create mode 100644 src/gui/embedded/qkbdtty_qws.cpp create mode 100644 src/gui/embedded/qkbdtty_qws.h create mode 100644 src/gui/embedded/qkbdum_qws.cpp create mode 100644 src/gui/embedded/qkbdum_qws.h create mode 100644 src/gui/embedded/qkbdvfb_qws.cpp create mode 100644 src/gui/embedded/qkbdvfb_qws.h create mode 100644 src/gui/embedded/qlock.cpp create mode 100644 src/gui/embedded/qlock_p.h create mode 100644 src/gui/embedded/qmouse_qws.cpp create mode 100644 src/gui/embedded/qmouse_qws.h create mode 100644 src/gui/embedded/qmousedriverfactory_qws.cpp create mode 100644 src/gui/embedded/qmousedriverfactory_qws.h create mode 100644 src/gui/embedded/qmousedriverplugin_qws.cpp create mode 100644 src/gui/embedded/qmousedriverplugin_qws.h create mode 100644 src/gui/embedded/qmouseintegrity_qws.cpp create mode 100644 src/gui/embedded/qmouseintegrity_qws.h create mode 100644 src/gui/embedded/qmouselinuxinput_qws.cpp create mode 100644 src/gui/embedded/qmouselinuxinput_qws.h create mode 100644 src/gui/embedded/qmouselinuxtp_qws.cpp create mode 100644 src/gui/embedded/qmouselinuxtp_qws.h create mode 100644 src/gui/embedded/qmousepc_qws.cpp create mode 100644 src/gui/embedded/qmousepc_qws.h create mode 100644 src/gui/embedded/qmouseqnx_qws.cpp create mode 100644 src/gui/embedded/qmouseqnx_qws.h create mode 100644 src/gui/embedded/qmousetslib_qws.cpp create mode 100644 src/gui/embedded/qmousetslib_qws.h create mode 100644 src/gui/embedded/qmousevfb_qws.cpp create mode 100644 src/gui/embedded/qmousevfb_qws.h create mode 100644 src/gui/embedded/qscreen_qws.cpp create mode 100644 src/gui/embedded/qscreen_qws.h create mode 100644 src/gui/embedded/qscreendriverfactory_qws.cpp create mode 100644 src/gui/embedded/qscreendriverfactory_qws.h create mode 100644 src/gui/embedded/qscreendriverplugin_qws.cpp create mode 100644 src/gui/embedded/qscreendriverplugin_qws.h create mode 100644 src/gui/embedded/qscreenintegrityfb_qws.cpp create mode 100644 src/gui/embedded/qscreenintegrityfb_qws.h create mode 100644 src/gui/embedded/qscreenlinuxfb_qws.cpp create mode 100644 src/gui/embedded/qscreenlinuxfb_qws.h create mode 100644 src/gui/embedded/qscreenmulti_qws.cpp create mode 100644 src/gui/embedded/qscreenmulti_qws_p.h create mode 100644 src/gui/embedded/qscreenproxy_qws.cpp create mode 100644 src/gui/embedded/qscreenproxy_qws.h create mode 100644 src/gui/embedded/qscreenqnx_qws.cpp create mode 100644 src/gui/embedded/qscreenqnx_qws.h create mode 100644 src/gui/embedded/qscreentransformed_qws.cpp create mode 100644 src/gui/embedded/qscreentransformed_qws.h create mode 100644 src/gui/embedded/qscreenvfb_qws.cpp create mode 100644 src/gui/embedded/qscreenvfb_qws.h create mode 100644 src/gui/embedded/qsoundqss_qws.cpp create mode 100644 src/gui/embedded/qsoundqss_qws.h create mode 100644 src/gui/embedded/qtransportauth_qws.cpp create mode 100644 src/gui/embedded/qtransportauth_qws.h create mode 100644 src/gui/embedded/qtransportauth_qws_p.h create mode 100644 src/gui/embedded/qtransportauthdefs_qws.h create mode 100644 src/gui/embedded/qunixsocket.cpp create mode 100644 src/gui/embedded/qunixsocket_p.h create mode 100644 src/gui/embedded/qunixsocketserver.cpp create mode 100644 src/gui/embedded/qunixsocketserver_p.h create mode 100644 src/gui/embedded/qvfbhdr.h create mode 100644 src/gui/embedded/qwindowsystem_p.h create mode 100644 src/gui/embedded/qwindowsystem_qws.cpp create mode 100644 src/gui/embedded/qwindowsystem_qws.h create mode 100644 src/gui/embedded/qwscommand_qws.cpp create mode 100644 src/gui/embedded/qwscommand_qws_p.h create mode 100644 src/gui/embedded/qwscursor_qws.cpp create mode 100644 src/gui/embedded/qwscursor_qws.h create mode 100644 src/gui/embedded/qwsdisplay_qws.h create mode 100644 src/gui/embedded/qwsdisplay_qws_p.h create mode 100644 src/gui/embedded/qwsembedwidget.cpp create mode 100644 src/gui/embedded/qwsembedwidget.h create mode 100644 src/gui/embedded/qwsevent_qws.cpp create mode 100644 src/gui/embedded/qwsevent_qws.h create mode 100644 src/gui/embedded/qwslock.cpp create mode 100644 src/gui/embedded/qwslock_p.h create mode 100644 src/gui/embedded/qwsmanager_p.h create mode 100644 src/gui/embedded/qwsmanager_qws.cpp create mode 100644 src/gui/embedded/qwsmanager_qws.h create mode 100644 src/gui/embedded/qwsproperty_qws.cpp create mode 100644 src/gui/embedded/qwsproperty_qws.h create mode 100644 src/gui/embedded/qwsprotocolitem_qws.h create mode 100644 src/gui/embedded/qwssharedmemory.cpp create mode 100644 src/gui/embedded/qwssharedmemory_p.h create mode 100644 src/gui/embedded/qwssignalhandler.cpp create mode 100644 src/gui/embedded/qwssignalhandler_p.h create mode 100644 src/gui/embedded/qwssocket_qws.cpp create mode 100644 src/gui/embedded/qwssocket_qws.h create mode 100644 src/gui/embedded/qwsutils_qws.h create mode 100644 src/gui/graphicsview/graphicsview.pri create mode 100644 src/gui/graphicsview/qgraph_p.h create mode 100644 src/gui/graphicsview/qgraphicsanchorlayout.cpp create mode 100644 src/gui/graphicsview/qgraphicsanchorlayout.h create mode 100644 src/gui/graphicsview/qgraphicsanchorlayout_p.cpp create mode 100644 src/gui/graphicsview/qgraphicsanchorlayout_p.h create mode 100644 src/gui/graphicsview/qgraphicsgridlayout.cpp create mode 100644 src/gui/graphicsview/qgraphicsgridlayout.h create mode 100644 src/gui/graphicsview/qgraphicsitem.cpp create mode 100644 src/gui/graphicsview/qgraphicsitem.h create mode 100644 src/gui/graphicsview/qgraphicsitem_p.h create mode 100644 src/gui/graphicsview/qgraphicsitemanimation.cpp create mode 100644 src/gui/graphicsview/qgraphicsitemanimation.h create mode 100644 src/gui/graphicsview/qgraphicslayout.cpp create mode 100644 src/gui/graphicsview/qgraphicslayout.h create mode 100644 src/gui/graphicsview/qgraphicslayout_p.cpp create mode 100644 src/gui/graphicsview/qgraphicslayout_p.h create mode 100644 src/gui/graphicsview/qgraphicslayoutitem.cpp create mode 100644 src/gui/graphicsview/qgraphicslayoutitem.h create mode 100644 src/gui/graphicsview/qgraphicslayoutitem_p.h create mode 100644 src/gui/graphicsview/qgraphicslinearlayout.cpp create mode 100644 src/gui/graphicsview/qgraphicslinearlayout.h create mode 100644 src/gui/graphicsview/qgraphicsproxywidget.cpp create mode 100644 src/gui/graphicsview/qgraphicsproxywidget.h create mode 100644 src/gui/graphicsview/qgraphicsproxywidget_p.h create mode 100644 src/gui/graphicsview/qgraphicsscene.cpp create mode 100644 src/gui/graphicsview/qgraphicsscene.h create mode 100644 src/gui/graphicsview/qgraphicsscene_bsp.cpp create mode 100644 src/gui/graphicsview/qgraphicsscene_bsp_p.h create mode 100644 src/gui/graphicsview/qgraphicsscene_p.h create mode 100644 src/gui/graphicsview/qgraphicsscenebsptreeindex.cpp create mode 100644 src/gui/graphicsview/qgraphicsscenebsptreeindex_p.h create mode 100644 src/gui/graphicsview/qgraphicssceneevent.cpp create mode 100644 src/gui/graphicsview/qgraphicssceneevent.h create mode 100644 src/gui/graphicsview/qgraphicssceneindex.cpp create mode 100644 src/gui/graphicsview/qgraphicssceneindex_p.h create mode 100644 src/gui/graphicsview/qgraphicsscenelinearindex.cpp create mode 100644 src/gui/graphicsview/qgraphicsscenelinearindex_p.h create mode 100644 src/gui/graphicsview/qgraphicstransform.cpp create mode 100644 src/gui/graphicsview/qgraphicstransform.h create mode 100644 src/gui/graphicsview/qgraphicstransform_p.h create mode 100644 src/gui/graphicsview/qgraphicsview.cpp create mode 100644 src/gui/graphicsview/qgraphicsview.h create mode 100644 src/gui/graphicsview/qgraphicsview_p.h create mode 100644 src/gui/graphicsview/qgraphicswidget.cpp create mode 100644 src/gui/graphicsview/qgraphicswidget.h create mode 100644 src/gui/graphicsview/qgraphicswidget_p.cpp create mode 100644 src/gui/graphicsview/qgraphicswidget_p.h create mode 100644 src/gui/graphicsview/qgridlayoutengine.cpp create mode 100644 src/gui/graphicsview/qgridlayoutengine_p.h create mode 100644 src/gui/graphicsview/qsimplex_p.cpp create mode 100644 src/gui/graphicsview/qsimplex_p.h create mode 100644 src/gui/gui.pro create mode 100644 src/gui/image/image.pri create mode 100644 src/gui/image/qbitmap.cpp create mode 100644 src/gui/image/qbitmap.h create mode 100644 src/gui/image/qbmphandler.cpp create mode 100644 src/gui/image/qbmphandler_p.h create mode 100644 src/gui/image/qgifhandler.cpp create mode 100644 src/gui/image/qgifhandler.pri create mode 100644 src/gui/image/qgifhandler_p.h create mode 100644 src/gui/image/qicon.cpp create mode 100644 src/gui/image/qicon.h create mode 100644 src/gui/image/qicon_p.h create mode 100644 src/gui/image/qiconengine.cpp create mode 100644 src/gui/image/qiconengine.h create mode 100644 src/gui/image/qiconengineplugin.cpp create mode 100644 src/gui/image/qiconengineplugin.h create mode 100644 src/gui/image/qiconloader.cpp create mode 100644 src/gui/image/qiconloader_p.h create mode 100644 src/gui/image/qimage.cpp create mode 100644 src/gui/image/qimage.h create mode 100644 src/gui/image/qimage_neon.cpp create mode 100644 src/gui/image/qimage_p.h create mode 100644 src/gui/image/qimage_sse2.cpp create mode 100644 src/gui/image/qimage_ssse3.cpp create mode 100644 src/gui/image/qimageiohandler.cpp create mode 100644 src/gui/image/qimageiohandler.h create mode 100644 src/gui/image/qimagepixmapcleanuphooks.cpp create mode 100644 src/gui/image/qimagepixmapcleanuphooks_p.h create mode 100644 src/gui/image/qimagereader.cpp create mode 100644 src/gui/image/qimagereader.h create mode 100644 src/gui/image/qimagewriter.cpp create mode 100644 src/gui/image/qimagewriter.h create mode 100644 src/gui/image/qjpeghandler.cpp create mode 100644 src/gui/image/qjpeghandler.pri create mode 100644 src/gui/image/qjpeghandler_p.h create mode 100644 src/gui/image/qmnghandler.cpp create mode 100644 src/gui/image/qmnghandler.pri create mode 100644 src/gui/image/qmnghandler_p.h create mode 100644 src/gui/image/qmovie.cpp create mode 100644 src/gui/image/qmovie.h create mode 100644 src/gui/image/qnativeimage.cpp create mode 100644 src/gui/image/qnativeimage_p.h create mode 100644 src/gui/image/qnativeimagehandleprovider_p.h create mode 100644 src/gui/image/qpaintengine_pic.cpp create mode 100644 src/gui/image/qpaintengine_pic_p.h create mode 100644 src/gui/image/qpicture.cpp create mode 100644 src/gui/image/qpicture.h create mode 100644 src/gui/image/qpicture_p.h create mode 100644 src/gui/image/qpictureformatplugin.cpp create mode 100644 src/gui/image/qpictureformatplugin.h create mode 100644 src/gui/image/qpixmap.cpp create mode 100644 src/gui/image/qpixmap.h create mode 100644 src/gui/image/qpixmap_blitter.cpp create mode 100644 src/gui/image/qpixmap_blitter_p.h create mode 100644 src/gui/image/qpixmap_mac.cpp create mode 100644 src/gui/image/qpixmap_mac_p.h create mode 100644 src/gui/image/qpixmap_qpa.cpp create mode 100644 src/gui/image/qpixmap_qws.cpp create mode 100644 src/gui/image/qpixmap_raster.cpp create mode 100644 src/gui/image/qpixmap_raster_p.h create mode 100644 src/gui/image/qpixmap_s60.cpp create mode 100644 src/gui/image/qpixmap_s60_p.h create mode 100644 src/gui/image/qpixmap_win.cpp create mode 100644 src/gui/image/qpixmap_x11.cpp create mode 100644 src/gui/image/qpixmap_x11_p.h create mode 100644 src/gui/image/qpixmapcache.cpp create mode 100644 src/gui/image/qpixmapcache.h create mode 100644 src/gui/image/qpixmapcache_p.h create mode 100644 src/gui/image/qpixmapdata.cpp create mode 100644 src/gui/image/qpixmapdata_p.h create mode 100644 src/gui/image/qpixmapdatafactory.cpp create mode 100644 src/gui/image/qpixmapdatafactory_p.h create mode 100644 src/gui/image/qpixmapfilter.cpp create mode 100644 src/gui/image/qpixmapfilter_p.h create mode 100644 src/gui/image/qpnghandler.cpp create mode 100644 src/gui/image/qpnghandler.pri create mode 100644 src/gui/image/qpnghandler_p.h create mode 100644 src/gui/image/qppmhandler.cpp create mode 100644 src/gui/image/qppmhandler_p.h create mode 100644 src/gui/image/qtiffhandler.cpp create mode 100644 src/gui/image/qtiffhandler.pri create mode 100644 src/gui/image/qtiffhandler_p.h create mode 100644 src/gui/image/qvolatileimage.cpp create mode 100644 src/gui/image/qvolatileimage_p.h create mode 100644 src/gui/image/qvolatileimagedata.cpp create mode 100644 src/gui/image/qvolatileimagedata_p.h create mode 100644 src/gui/image/qvolatileimagedata_symbian.cpp create mode 100644 src/gui/image/qxbmhandler.cpp create mode 100644 src/gui/image/qxbmhandler_p.h create mode 100644 src/gui/image/qxpmhandler.cpp create mode 100644 src/gui/image/qxpmhandler_p.h create mode 100644 src/gui/inputmethod/inputmethod.pri create mode 100644 src/gui/inputmethod/qcoefepinputcontext_p.h create mode 100644 src/gui/inputmethod/qcoefepinputcontext_s60.cpp create mode 100644 src/gui/inputmethod/qinputcontext.cpp create mode 100644 src/gui/inputmethod/qinputcontext.h create mode 100644 src/gui/inputmethod/qinputcontext_p.h create mode 100644 src/gui/inputmethod/qinputcontextfactory.cpp create mode 100644 src/gui/inputmethod/qinputcontextfactory.h create mode 100644 src/gui/inputmethod/qinputcontextplugin.cpp create mode 100644 src/gui/inputmethod/qinputcontextplugin.h create mode 100644 src/gui/inputmethod/qmacinputcontext_mac.cpp create mode 100644 src/gui/inputmethod/qmacinputcontext_p.h create mode 100644 src/gui/inputmethod/qwininputcontext_p.h create mode 100644 src/gui/inputmethod/qwininputcontext_win.cpp create mode 100644 src/gui/inputmethod/qwsinputcontext_p.h create mode 100644 src/gui/inputmethod/qwsinputcontext_qws.cpp create mode 100644 src/gui/inputmethod/qximinputcontext_p.h create mode 100644 src/gui/inputmethod/qximinputcontext_x11.cpp create mode 100644 src/gui/itemviews/itemviews.pri create mode 100644 src/gui/itemviews/qabstractitemdelegate.cpp create mode 100644 src/gui/itemviews/qabstractitemdelegate.h create mode 100644 src/gui/itemviews/qabstractitemview.cpp create mode 100644 src/gui/itemviews/qabstractitemview.h create mode 100644 src/gui/itemviews/qabstractitemview_p.h create mode 100644 src/gui/itemviews/qabstractproxymodel.cpp create mode 100644 src/gui/itemviews/qabstractproxymodel.h create mode 100644 src/gui/itemviews/qabstractproxymodel_p.h create mode 100644 src/gui/itemviews/qbsptree.cpp create mode 100644 src/gui/itemviews/qbsptree_p.h create mode 100644 src/gui/itemviews/qcolumnview.cpp create mode 100644 src/gui/itemviews/qcolumnview.h create mode 100644 src/gui/itemviews/qcolumnview_p.h create mode 100644 src/gui/itemviews/qcolumnviewgrip.cpp create mode 100644 src/gui/itemviews/qcolumnviewgrip_p.h create mode 100644 src/gui/itemviews/qdatawidgetmapper.cpp create mode 100644 src/gui/itemviews/qdatawidgetmapper.h create mode 100644 src/gui/itemviews/qdirmodel.cpp create mode 100644 src/gui/itemviews/qdirmodel.h create mode 100644 src/gui/itemviews/qfileiconprovider.cpp create mode 100644 src/gui/itemviews/qfileiconprovider.h create mode 100644 src/gui/itemviews/qheaderview.cpp create mode 100644 src/gui/itemviews/qheaderview.h create mode 100644 src/gui/itemviews/qheaderview_p.h create mode 100644 src/gui/itemviews/qitemdelegate.cpp create mode 100644 src/gui/itemviews/qitemdelegate.h create mode 100644 src/gui/itemviews/qitemeditorfactory.cpp create mode 100644 src/gui/itemviews/qitemeditorfactory.h create mode 100644 src/gui/itemviews/qitemeditorfactory_p.h create mode 100644 src/gui/itemviews/qitemselectionmodel.cpp create mode 100644 src/gui/itemviews/qitemselectionmodel.h create mode 100644 src/gui/itemviews/qitemselectionmodel_p.h create mode 100644 src/gui/itemviews/qlistview.cpp create mode 100644 src/gui/itemviews/qlistview.h create mode 100644 src/gui/itemviews/qlistview_p.h create mode 100644 src/gui/itemviews/qlistwidget.cpp create mode 100644 src/gui/itemviews/qlistwidget.h create mode 100644 src/gui/itemviews/qlistwidget_p.h create mode 100644 src/gui/itemviews/qproxymodel.cpp create mode 100644 src/gui/itemviews/qproxymodel.h create mode 100644 src/gui/itemviews/qproxymodel_p.h create mode 100644 src/gui/itemviews/qsortfilterproxymodel.cpp create mode 100644 src/gui/itemviews/qsortfilterproxymodel.h create mode 100644 src/gui/itemviews/qstandarditemmodel.cpp create mode 100644 src/gui/itemviews/qstandarditemmodel.h create mode 100644 src/gui/itemviews/qstandarditemmodel_p.h create mode 100644 src/gui/itemviews/qstringlistmodel.cpp create mode 100644 src/gui/itemviews/qstringlistmodel.h create mode 100644 src/gui/itemviews/qstyleditemdelegate.cpp create mode 100644 src/gui/itemviews/qstyleditemdelegate.h create mode 100644 src/gui/itemviews/qtableview.cpp create mode 100644 src/gui/itemviews/qtableview.h create mode 100644 src/gui/itemviews/qtableview_p.h create mode 100644 src/gui/itemviews/qtablewidget.cpp create mode 100644 src/gui/itemviews/qtablewidget.h create mode 100644 src/gui/itemviews/qtablewidget_p.h create mode 100644 src/gui/itemviews/qtreeview.cpp create mode 100644 src/gui/itemviews/qtreeview.h create mode 100644 src/gui/itemviews/qtreeview_p.h create mode 100644 src/gui/itemviews/qtreewidget.cpp create mode 100644 src/gui/itemviews/qtreewidget.h create mode 100644 src/gui/itemviews/qtreewidget_p.h create mode 100644 src/gui/itemviews/qtreewidgetitemiterator.cpp create mode 100644 src/gui/itemviews/qtreewidgetitemiterator.h create mode 100644 src/gui/itemviews/qtreewidgetitemiterator_p.h create mode 100644 src/gui/itemviews/qwidgetitemdata_p.h create mode 100644 src/gui/kernel/kernel.pri create mode 100644 src/gui/kernel/mac.pri create mode 100644 src/gui/kernel/qaction.cpp create mode 100644 src/gui/kernel/qaction.h create mode 100644 src/gui/kernel/qaction_p.h create mode 100644 src/gui/kernel/qactiongroup.cpp create mode 100644 src/gui/kernel/qactiongroup.h create mode 100644 src/gui/kernel/qapplication.cpp create mode 100644 src/gui/kernel/qapplication.h create mode 100644 src/gui/kernel/qapplication_mac.mm create mode 100644 src/gui/kernel/qapplication_p.h create mode 100644 src/gui/kernel/qapplication_qpa.cpp create mode 100644 src/gui/kernel/qapplication_qws.cpp create mode 100644 src/gui/kernel/qapplication_s60.cpp create mode 100644 src/gui/kernel/qapplication_win.cpp create mode 100644 src/gui/kernel/qapplication_x11.cpp create mode 100644 src/gui/kernel/qboxlayout.cpp create mode 100644 src/gui/kernel/qboxlayout.h create mode 100644 src/gui/kernel/qclipboard.cpp create mode 100644 src/gui/kernel/qclipboard.h create mode 100644 src/gui/kernel/qclipboard_mac.cpp create mode 100644 src/gui/kernel/qclipboard_p.h create mode 100644 src/gui/kernel/qclipboard_qpa.cpp create mode 100644 src/gui/kernel/qclipboard_qws.cpp create mode 100644 src/gui/kernel/qclipboard_s60.cpp create mode 100644 src/gui/kernel/qclipboard_win.cpp create mode 100644 src/gui/kernel/qclipboard_x11.cpp create mode 100644 src/gui/kernel/qcocoaapplication_mac.mm create mode 100644 src/gui/kernel/qcocoaapplication_mac_p.h create mode 100644 src/gui/kernel/qcocoaapplicationdelegate_mac.mm create mode 100644 src/gui/kernel/qcocoaapplicationdelegate_mac_p.h create mode 100644 src/gui/kernel/qcocoaintrospection_mac.mm create mode 100644 src/gui/kernel/qcocoaintrospection_p.h create mode 100644 src/gui/kernel/qcocoamenuloader_mac.mm create mode 100644 src/gui/kernel/qcocoamenuloader_mac_p.h create mode 100644 src/gui/kernel/qcocoapanel_mac.mm create mode 100644 src/gui/kernel/qcocoapanel_mac_p.h create mode 100644 src/gui/kernel/qcocoasharedwindowmethods_mac_p.h create mode 100644 src/gui/kernel/qcocoaview_mac.mm create mode 100644 src/gui/kernel/qcocoaview_mac_p.h create mode 100644 src/gui/kernel/qcocoawindow_mac.mm create mode 100644 src/gui/kernel/qcocoawindow_mac_p.h create mode 100644 src/gui/kernel/qcocoawindowcustomthemeframe_mac.mm create mode 100644 src/gui/kernel/qcocoawindowcustomthemeframe_mac_p.h create mode 100644 src/gui/kernel/qcocoawindowdelegate_mac.mm create mode 100644 src/gui/kernel/qcocoawindowdelegate_mac_p.h create mode 100644 src/gui/kernel/qcursor.cpp create mode 100644 src/gui/kernel/qcursor.h create mode 100644 src/gui/kernel/qcursor_mac.mm create mode 100644 src/gui/kernel/qcursor_p.h create mode 100644 src/gui/kernel/qcursor_qpa.cpp create mode 100644 src/gui/kernel/qcursor_qws.cpp create mode 100644 src/gui/kernel/qcursor_s60.cpp create mode 100644 src/gui/kernel/qcursor_win.cpp create mode 100644 src/gui/kernel/qcursor_x11.cpp create mode 100644 src/gui/kernel/qdesktopwidget.cpp create mode 100644 src/gui/kernel/qdesktopwidget.h create mode 100644 src/gui/kernel/qdesktopwidget.qdoc create mode 100644 src/gui/kernel/qdesktopwidget_mac.mm create mode 100644 src/gui/kernel/qdesktopwidget_mac_p.h create mode 100644 src/gui/kernel/qdesktopwidget_qpa.cpp create mode 100644 src/gui/kernel/qdesktopwidget_qpa_p.h create mode 100644 src/gui/kernel/qdesktopwidget_qws.cpp create mode 100644 src/gui/kernel/qdesktopwidget_s60.cpp create mode 100644 src/gui/kernel/qdesktopwidget_win.cpp create mode 100644 src/gui/kernel/qdesktopwidget_x11.cpp create mode 100644 src/gui/kernel/qdnd.cpp create mode 100644 src/gui/kernel/qdnd_mac.mm create mode 100644 src/gui/kernel/qdnd_p.h create mode 100644 src/gui/kernel/qdnd_qws.cpp create mode 100644 src/gui/kernel/qdnd_s60.cpp create mode 100644 src/gui/kernel/qdnd_win.cpp create mode 100644 src/gui/kernel/qdnd_x11.cpp create mode 100644 src/gui/kernel/qdrag.cpp create mode 100644 src/gui/kernel/qdrag.h create mode 100644 src/gui/kernel/qevent.cpp create mode 100644 src/gui/kernel/qevent.h create mode 100644 src/gui/kernel/qevent_p.h create mode 100644 src/gui/kernel/qeventdispatcher_glib_qpa.cpp create mode 100644 src/gui/kernel/qeventdispatcher_glib_qpa_p.h create mode 100644 src/gui/kernel/qeventdispatcher_glib_qws.cpp create mode 100644 src/gui/kernel/qeventdispatcher_glib_qws_p.h create mode 100644 src/gui/kernel/qeventdispatcher_mac.mm create mode 100644 src/gui/kernel/qeventdispatcher_mac_p.h create mode 100644 src/gui/kernel/qeventdispatcher_qpa.cpp create mode 100644 src/gui/kernel/qeventdispatcher_qpa_p.h create mode 100644 src/gui/kernel/qeventdispatcher_qws.cpp create mode 100644 src/gui/kernel/qeventdispatcher_qws_p.h create mode 100644 src/gui/kernel/qeventdispatcher_s60.cpp create mode 100644 src/gui/kernel/qeventdispatcher_s60_p.h create mode 100644 src/gui/kernel/qeventdispatcher_x11.cpp create mode 100644 src/gui/kernel/qeventdispatcher_x11_p.h create mode 100644 src/gui/kernel/qformlayout.cpp create mode 100644 src/gui/kernel/qformlayout.h create mode 100644 src/gui/kernel/qgenericplugin_qpa.cpp create mode 100644 src/gui/kernel/qgenericplugin_qpa.h create mode 100644 src/gui/kernel/qgenericpluginfactory_qpa.cpp create mode 100644 src/gui/kernel/qgenericpluginfactory_qpa.h create mode 100644 src/gui/kernel/qgesture.cpp create mode 100644 src/gui/kernel/qgesture.h create mode 100644 src/gui/kernel/qgesture_p.h create mode 100644 src/gui/kernel/qgesturemanager.cpp create mode 100644 src/gui/kernel/qgesturemanager_p.h create mode 100644 src/gui/kernel/qgesturerecognizer.cpp create mode 100644 src/gui/kernel/qgesturerecognizer.h create mode 100644 src/gui/kernel/qgridlayout.cpp create mode 100644 src/gui/kernel/qgridlayout.h create mode 100644 src/gui/kernel/qguieventdispatcher_glib.cpp create mode 100644 src/gui/kernel/qguieventdispatcher_glib_p.h create mode 100644 src/gui/kernel/qguifunctions_wince.cpp create mode 100644 src/gui/kernel/qguifunctions_wince.h create mode 100644 src/gui/kernel/qguiplatformplugin.cpp create mode 100644 src/gui/kernel/qguiplatformplugin_p.h create mode 100644 src/gui/kernel/qguivariant.cpp create mode 100644 src/gui/kernel/qkde.cpp create mode 100644 src/gui/kernel/qkde_p.h create mode 100644 src/gui/kernel/qkeymapper.cpp create mode 100644 src/gui/kernel/qkeymapper_mac.cpp create mode 100644 src/gui/kernel/qkeymapper_p.h create mode 100644 src/gui/kernel/qkeymapper_qws.cpp create mode 100644 src/gui/kernel/qkeymapper_s60.cpp create mode 100644 src/gui/kernel/qkeymapper_win.cpp create mode 100644 src/gui/kernel/qkeymapper_x11.cpp create mode 100644 src/gui/kernel/qkeymapper_x11_p.cpp create mode 100644 src/gui/kernel/qkeysequence.cpp create mode 100644 src/gui/kernel/qkeysequence.h create mode 100644 src/gui/kernel/qkeysequence_p.h create mode 100644 src/gui/kernel/qlayout.cpp create mode 100644 src/gui/kernel/qlayout.h create mode 100644 src/gui/kernel/qlayout_p.h create mode 100644 src/gui/kernel/qlayoutengine.cpp create mode 100644 src/gui/kernel/qlayoutengine_p.h create mode 100644 src/gui/kernel/qlayoutitem.cpp create mode 100644 src/gui/kernel/qlayoutitem.h create mode 100644 src/gui/kernel/qmacdefines_mac.h create mode 100644 src/gui/kernel/qmacgesturerecognizer_mac.mm create mode 100644 src/gui/kernel/qmacgesturerecognizer_mac_p.h create mode 100644 src/gui/kernel/qmime.cpp create mode 100644 src/gui/kernel/qmime.h create mode 100644 src/gui/kernel/qmime_mac.cpp create mode 100644 src/gui/kernel/qmime_win.cpp create mode 100644 src/gui/kernel/qmotifdnd_x11.cpp create mode 100644 src/gui/kernel/qmultitouch_mac.mm create mode 100644 src/gui/kernel/qmultitouch_mac_p.h create mode 100644 src/gui/kernel/qnsframeview_mac_p.h create mode 100644 src/gui/kernel/qnsthemeframe_mac_p.h create mode 100644 src/gui/kernel/qnstitledframe_mac_p.h create mode 100644 src/gui/kernel/qole_win.cpp create mode 100644 src/gui/kernel/qpalette.cpp create mode 100644 src/gui/kernel/qpalette.h create mode 100644 src/gui/kernel/qplatformclipboard_qpa.cpp create mode 100644 src/gui/kernel/qplatformclipboard_qpa.h create mode 100644 src/gui/kernel/qplatformcursor_qpa.cpp create mode 100644 src/gui/kernel/qplatformcursor_qpa.h create mode 100644 src/gui/kernel/qplatformeventloopintegration_qpa.cpp create mode 100644 src/gui/kernel/qplatformeventloopintegration_qpa.h create mode 100644 src/gui/kernel/qplatformglcontext_qpa.cpp create mode 100644 src/gui/kernel/qplatformglcontext_qpa.h create mode 100644 src/gui/kernel/qplatformintegration_qpa.cpp create mode 100644 src/gui/kernel/qplatformintegration_qpa.h create mode 100644 src/gui/kernel/qplatformintegrationfactory_qpa.cpp create mode 100644 src/gui/kernel/qplatformintegrationfactory_qpa_p.h create mode 100644 src/gui/kernel/qplatformintegrationplugin_qpa.cpp create mode 100644 src/gui/kernel/qplatformintegrationplugin_qpa.h create mode 100644 src/gui/kernel/qplatformnativeinterface_qpa.cpp create mode 100644 src/gui/kernel/qplatformnativeinterface_qpa.h create mode 100644 src/gui/kernel/qplatformscreen_qpa.cpp create mode 100644 src/gui/kernel/qplatformscreen_qpa.h create mode 100644 src/gui/kernel/qplatformwindow_qpa.cpp create mode 100644 src/gui/kernel/qplatformwindow_qpa.h create mode 100644 src/gui/kernel/qplatformwindowformat_qpa.cpp create mode 100644 src/gui/kernel/qplatformwindowformat_qpa.h create mode 100644 src/gui/kernel/qsessionmanager.h create mode 100644 src/gui/kernel/qsessionmanager_qpa.cpp create mode 100644 src/gui/kernel/qsessionmanager_qws.cpp create mode 100644 src/gui/kernel/qshortcut.cpp create mode 100644 src/gui/kernel/qshortcut.h create mode 100644 src/gui/kernel/qshortcutmap.cpp create mode 100644 src/gui/kernel/qshortcutmap_p.h create mode 100644 src/gui/kernel/qsizepolicy.h create mode 100644 src/gui/kernel/qsizepolicy.qdoc create mode 100644 src/gui/kernel/qsoftkeymanager.cpp create mode 100644 src/gui/kernel/qsoftkeymanager_common_p.h create mode 100644 src/gui/kernel/qsoftkeymanager_p.h create mode 100644 src/gui/kernel/qsoftkeymanager_s60.cpp create mode 100644 src/gui/kernel/qsoftkeymanager_s60_p.h create mode 100644 src/gui/kernel/qsound.cpp create mode 100644 src/gui/kernel/qsound.h create mode 100644 src/gui/kernel/qsound_mac.mm create mode 100644 src/gui/kernel/qsound_p.h create mode 100644 src/gui/kernel/qsound_qws.cpp create mode 100644 src/gui/kernel/qsound_s60.cpp create mode 100644 src/gui/kernel/qsound_win.cpp create mode 100644 src/gui/kernel/qsound_x11.cpp create mode 100644 src/gui/kernel/qstackedlayout.cpp create mode 100644 src/gui/kernel/qstackedlayout.h create mode 100644 src/gui/kernel/qstandardgestures.cpp create mode 100644 src/gui/kernel/qstandardgestures_p.h create mode 100644 src/gui/kernel/qt_cocoa_helpers_mac.mm create mode 100644 src/gui/kernel/qt_cocoa_helpers_mac_p.h create mode 100644 src/gui/kernel/qt_gui_pch.h create mode 100644 src/gui/kernel/qt_mac.cpp create mode 100644 src/gui/kernel/qt_mac_p.h create mode 100644 src/gui/kernel/qt_s60_p.h create mode 100644 src/gui/kernel/qt_x11_p.h create mode 100644 src/gui/kernel/qtooltip.cpp create mode 100644 src/gui/kernel/qtooltip.h create mode 100644 src/gui/kernel/qwhatsthis.cpp create mode 100644 src/gui/kernel/qwhatsthis.h create mode 100644 src/gui/kernel/qwidget.cpp create mode 100644 src/gui/kernel/qwidget.h create mode 100644 src/gui/kernel/qwidget_mac.mm create mode 100644 src/gui/kernel/qwidget_p.h create mode 100644 src/gui/kernel/qwidget_qpa.cpp create mode 100644 src/gui/kernel/qwidget_qws.cpp create mode 100644 src/gui/kernel/qwidget_s60.cpp create mode 100644 src/gui/kernel/qwidget_win.cpp create mode 100644 src/gui/kernel/qwidget_wince.cpp create mode 100644 src/gui/kernel/qwidget_x11.cpp create mode 100644 src/gui/kernel/qwidgetaction.cpp create mode 100644 src/gui/kernel/qwidgetaction.h create mode 100644 src/gui/kernel/qwidgetaction_p.h create mode 100644 src/gui/kernel/qwidgetcreate_x11.cpp create mode 100644 src/gui/kernel/qwindowdefs.h create mode 100644 src/gui/kernel/qwindowdefs_win.h create mode 100644 src/gui/kernel/qwindowsysteminterface_qpa.cpp create mode 100644 src/gui/kernel/qwindowsysteminterface_qpa.h create mode 100644 src/gui/kernel/qwindowsysteminterface_qpa_p.h create mode 100644 src/gui/kernel/qwinnativepangesturerecognizer_win.cpp create mode 100644 src/gui/kernel/qwinnativepangesturerecognizer_win_p.h create mode 100644 src/gui/kernel/qx11embed_x11.cpp create mode 100644 src/gui/kernel/qx11embed_x11.h create mode 100644 src/gui/kernel/qx11info_x11.cpp create mode 100644 src/gui/kernel/qx11info_x11.h create mode 100644 src/gui/kernel/symbian.pri create mode 100644 src/gui/kernel/win.pri create mode 100644 src/gui/kernel/x11.pri create mode 100644 src/gui/mac/images/copyarrowcursor.png create mode 100644 src/gui/mac/images/forbiddencursor.png create mode 100644 src/gui/mac/images/leopard-unified-toolbar-on.png create mode 100644 src/gui/mac/images/pluscursor.png create mode 100644 src/gui/mac/images/spincursor.png create mode 100644 src/gui/mac/images/waitcursor.png create mode 100644 src/gui/mac/macresources.qrc create mode 100644 src/gui/mac/qt_menu.nib/classes.nib create mode 100644 src/gui/mac/qt_menu.nib/info.nib create mode 100644 src/gui/mac/qt_menu.nib/keyedobjects.nib create mode 100644 src/gui/math3d/math3d.pri create mode 100644 src/gui/math3d/qgenericmatrix.cpp create mode 100644 src/gui/math3d/qgenericmatrix.h create mode 100644 src/gui/math3d/qmatrix4x4.cpp create mode 100644 src/gui/math3d/qmatrix4x4.h create mode 100644 src/gui/math3d/qquaternion.cpp create mode 100644 src/gui/math3d/qquaternion.h create mode 100644 src/gui/math3d/qvector2d.cpp create mode 100644 src/gui/math3d/qvector2d.h create mode 100644 src/gui/math3d/qvector3d.cpp create mode 100644 src/gui/math3d/qvector3d.h create mode 100644 src/gui/math3d/qvector4d.cpp create mode 100644 src/gui/math3d/qvector4d.h create mode 100755 src/gui/painting/makepsheader.pl create mode 100644 src/gui/painting/painting.pri create mode 100644 src/gui/painting/qbackingstore.cpp create mode 100644 src/gui/painting/qbackingstore_p.h create mode 100644 src/gui/painting/qbezier.cpp create mode 100644 src/gui/painting/qbezier_p.h create mode 100644 src/gui/painting/qblendfunctions.cpp create mode 100644 src/gui/painting/qblendfunctions_p.h create mode 100644 src/gui/painting/qblittable.cpp create mode 100644 src/gui/painting/qblittable_p.h create mode 100644 src/gui/painting/qbrush.cpp create mode 100644 src/gui/painting/qbrush.h create mode 100644 src/gui/painting/qcolor.cpp create mode 100644 src/gui/painting/qcolor.h create mode 100644 src/gui/painting/qcolor_p.cpp create mode 100644 src/gui/painting/qcolor_p.h create mode 100644 src/gui/painting/qcolormap.h create mode 100644 src/gui/painting/qcolormap.qdoc create mode 100644 src/gui/painting/qcolormap_mac.cpp create mode 100644 src/gui/painting/qcolormap_qpa.cpp create mode 100644 src/gui/painting/qcolormap_qws.cpp create mode 100644 src/gui/painting/qcolormap_s60.cpp create mode 100644 src/gui/painting/qcolormap_win.cpp create mode 100644 src/gui/painting/qcolormap_x11.cpp create mode 100644 src/gui/painting/qcssutil.cpp create mode 100644 src/gui/painting/qcssutil_p.h create mode 100644 src/gui/painting/qcups.cpp create mode 100644 src/gui/painting/qcups_p.h create mode 100644 src/gui/painting/qdatabuffer_p.h create mode 100644 src/gui/painting/qdrawhelper.cpp create mode 100644 src/gui/painting/qdrawhelper_arm_simd.cpp create mode 100644 src/gui/painting/qdrawhelper_arm_simd_p.h create mode 100644 src/gui/painting/qdrawhelper_iwmmxt.cpp create mode 100644 src/gui/painting/qdrawhelper_mmx.cpp create mode 100644 src/gui/painting/qdrawhelper_mmx3dnow.cpp create mode 100644 src/gui/painting/qdrawhelper_mmx_p.h create mode 100644 src/gui/painting/qdrawhelper_neon.cpp create mode 100644 src/gui/painting/qdrawhelper_neon_asm.S create mode 100644 src/gui/painting/qdrawhelper_neon_p.h create mode 100644 src/gui/painting/qdrawhelper_p.h create mode 100644 src/gui/painting/qdrawhelper_sse.cpp create mode 100644 src/gui/painting/qdrawhelper_sse2.cpp create mode 100644 src/gui/painting/qdrawhelper_sse3dnow.cpp create mode 100644 src/gui/painting/qdrawhelper_sse_p.h create mode 100644 src/gui/painting/qdrawhelper_ssse3.cpp create mode 100644 src/gui/painting/qdrawhelper_x86_p.h create mode 100644 src/gui/painting/qdrawingprimitive_sse2_p.h create mode 100644 src/gui/painting/qdrawutil.cpp create mode 100644 src/gui/painting/qdrawutil.h create mode 100644 src/gui/painting/qemulationpaintengine.cpp create mode 100644 src/gui/painting/qemulationpaintengine_p.h create mode 100644 src/gui/painting/qfixed_p.h create mode 100644 src/gui/painting/qgraphicssystem.cpp create mode 100644 src/gui/painting/qgraphicssystem_mac.cpp create mode 100644 src/gui/painting/qgraphicssystem_mac_p.h create mode 100644 src/gui/painting/qgraphicssystem_p.h create mode 100644 src/gui/painting/qgraphicssystem_qws.cpp create mode 100644 src/gui/painting/qgraphicssystem_qws_p.h create mode 100644 src/gui/painting/qgraphicssystem_raster.cpp create mode 100644 src/gui/painting/qgraphicssystem_raster_p.h create mode 100644 src/gui/painting/qgraphicssystem_runtime.cpp create mode 100644 src/gui/painting/qgraphicssystem_runtime_p.h create mode 100644 src/gui/painting/qgraphicssystemfactory.cpp create mode 100644 src/gui/painting/qgraphicssystemfactory_p.h create mode 100644 src/gui/painting/qgraphicssystemplugin.cpp create mode 100644 src/gui/painting/qgraphicssystemplugin_p.h create mode 100644 src/gui/painting/qgrayraster.c create mode 100644 src/gui/painting/qgrayraster_p.h create mode 100644 src/gui/painting/qimagescale.cpp create mode 100644 src/gui/painting/qimagescale_p.h create mode 100644 src/gui/painting/qmath_p.h create mode 100644 src/gui/painting/qmatrix.cpp create mode 100644 src/gui/painting/qmatrix.h create mode 100644 src/gui/painting/qmemrotate.cpp create mode 100644 src/gui/painting/qmemrotate_p.h create mode 100644 src/gui/painting/qoutlinemapper.cpp create mode 100644 src/gui/painting/qoutlinemapper_p.h create mode 100644 src/gui/painting/qpaintbuffer.cpp create mode 100644 src/gui/painting/qpaintbuffer_p.h create mode 100644 src/gui/painting/qpaintdevice.cpp create mode 100644 src/gui/painting/qpaintdevice.h create mode 100644 src/gui/painting/qpaintdevice.qdoc create mode 100644 src/gui/painting/qpaintdevice_mac.cpp create mode 100644 src/gui/painting/qpaintdevice_qpa.cpp create mode 100644 src/gui/painting/qpaintdevice_qws.cpp create mode 100644 src/gui/painting/qpaintdevice_win.cpp create mode 100644 src/gui/painting/qpaintdevice_x11.cpp create mode 100644 src/gui/painting/qpaintengine.cpp create mode 100644 src/gui/painting/qpaintengine.h create mode 100644 src/gui/painting/qpaintengine_alpha.cpp create mode 100644 src/gui/painting/qpaintengine_alpha_p.h create mode 100644 src/gui/painting/qpaintengine_blitter.cpp create mode 100644 src/gui/painting/qpaintengine_blitter_p.h create mode 100644 src/gui/painting/qpaintengine_mac.cpp create mode 100644 src/gui/painting/qpaintengine_mac_p.h create mode 100644 src/gui/painting/qpaintengine_p.h create mode 100644 src/gui/painting/qpaintengine_preview.cpp create mode 100644 src/gui/painting/qpaintengine_preview_p.h create mode 100644 src/gui/painting/qpaintengine_raster.cpp create mode 100644 src/gui/painting/qpaintengine_raster_p.h create mode 100644 src/gui/painting/qpaintengine_s60.cpp create mode 100644 src/gui/painting/qpaintengine_s60_p.h create mode 100644 src/gui/painting/qpaintengine_x11.cpp create mode 100644 src/gui/painting/qpaintengine_x11_p.h create mode 100644 src/gui/painting/qpaintengineex.cpp create mode 100644 src/gui/painting/qpaintengineex_p.h create mode 100644 src/gui/painting/qpainter.cpp create mode 100644 src/gui/painting/qpainter.h create mode 100644 src/gui/painting/qpainter_p.h create mode 100644 src/gui/painting/qpainterpath.cpp create mode 100644 src/gui/painting/qpainterpath.h create mode 100644 src/gui/painting/qpainterpath_p.h create mode 100644 src/gui/painting/qpathclipper.cpp create mode 100644 src/gui/painting/qpathclipper_p.h create mode 100644 src/gui/painting/qpdf.cpp create mode 100644 src/gui/painting/qpdf_p.h create mode 100644 src/gui/painting/qpen.cpp create mode 100644 src/gui/painting/qpen.h create mode 100644 src/gui/painting/qpen_p.h create mode 100644 src/gui/painting/qpolygon.cpp create mode 100644 src/gui/painting/qpolygon.h create mode 100644 src/gui/painting/qpolygonclipper_p.h create mode 100644 src/gui/painting/qprintengine.h create mode 100644 src/gui/painting/qprintengine_mac.mm create mode 100644 src/gui/painting/qprintengine_mac_p.h create mode 100644 src/gui/painting/qprintengine_pdf.cpp create mode 100644 src/gui/painting/qprintengine_pdf_p.h create mode 100644 src/gui/painting/qprintengine_ps.cpp create mode 100644 src/gui/painting/qprintengine_ps_p.h create mode 100644 src/gui/painting/qprintengine_qws.cpp create mode 100644 src/gui/painting/qprintengine_qws_p.h create mode 100644 src/gui/painting/qprintengine_win.cpp create mode 100644 src/gui/painting/qprintengine_win_p.h create mode 100644 src/gui/painting/qprinter.cpp create mode 100644 src/gui/painting/qprinter.h create mode 100644 src/gui/painting/qprinter_p.h create mode 100644 src/gui/painting/qprinterinfo.cpp create mode 100644 src/gui/painting/qprinterinfo.h create mode 100644 src/gui/painting/qprinterinfo_mac.cpp create mode 100644 src/gui/painting/qprinterinfo_p.h create mode 100644 src/gui/painting/qprinterinfo_unix.cpp create mode 100644 src/gui/painting/qprinterinfo_unix_p.h create mode 100644 src/gui/painting/qprinterinfo_win.cpp create mode 100644 src/gui/painting/qpsprinter.agl create mode 100644 src/gui/painting/qpsprinter.ps create mode 100644 src/gui/painting/qrasterdefs_p.h create mode 100644 src/gui/painting/qrasterizer.cpp create mode 100644 src/gui/painting/qrasterizer_p.h create mode 100644 src/gui/painting/qregion.cpp create mode 100644 src/gui/painting/qregion.h create mode 100644 src/gui/painting/qregion_mac.cpp create mode 100644 src/gui/painting/qregion_qws.cpp create mode 100644 src/gui/painting/qregion_s60.cpp create mode 100644 src/gui/painting/qregion_win.cpp create mode 100644 src/gui/painting/qregion_x11.cpp create mode 100644 src/gui/painting/qrgb.h create mode 100644 src/gui/painting/qstroker.cpp create mode 100644 src/gui/painting/qstroker_p.h create mode 100644 src/gui/painting/qstylepainter.cpp create mode 100644 src/gui/painting/qstylepainter.h create mode 100644 src/gui/painting/qtessellator.cpp create mode 100644 src/gui/painting/qtessellator_p.h create mode 100644 src/gui/painting/qtextureglyphcache.cpp create mode 100644 src/gui/painting/qtextureglyphcache_p.h create mode 100644 src/gui/painting/qtransform.cpp create mode 100644 src/gui/painting/qtransform.h create mode 100644 src/gui/painting/qunifiedtoolbarsurface_mac.cpp create mode 100644 src/gui/painting/qunifiedtoolbarsurface_mac_p.h create mode 100644 src/gui/painting/qvectorpath_p.h create mode 100644 src/gui/painting/qwindowsurface.cpp create mode 100644 src/gui/painting/qwindowsurface_mac.cpp create mode 100644 src/gui/painting/qwindowsurface_mac_p.h create mode 100644 src/gui/painting/qwindowsurface_p.h create mode 100644 src/gui/painting/qwindowsurface_qws.cpp create mode 100644 src/gui/painting/qwindowsurface_qws_p.h create mode 100644 src/gui/painting/qwindowsurface_raster.cpp create mode 100644 src/gui/painting/qwindowsurface_raster_p.h create mode 100644 src/gui/painting/qwindowsurface_s60.cpp create mode 100644 src/gui/painting/qwindowsurface_s60_p.h create mode 100644 src/gui/painting/qwindowsurface_x11.cpp create mode 100644 src/gui/painting/qwindowsurface_x11_p.h create mode 100644 src/gui/painting/qwmatrix.h create mode 100644 src/gui/s60framework/qs60mainapplication.cpp create mode 100644 src/gui/s60framework/qs60mainapplication.h create mode 100644 src/gui/s60framework/qs60mainapplication_p.h create mode 100644 src/gui/s60framework/qs60mainappui.cpp create mode 100644 src/gui/s60framework/qs60mainappui.h create mode 100644 src/gui/s60framework/qs60maindocument.cpp create mode 100644 src/gui/s60framework/qs60maindocument.h create mode 100644 src/gui/s60framework/s60framework.pri create mode 100644 src/gui/s60framework/s60main.rss create mode 100644 src/gui/statemachine/qbasickeyeventtransition.cpp create mode 100644 src/gui/statemachine/qbasickeyeventtransition_p.h create mode 100644 src/gui/statemachine/qbasicmouseeventtransition.cpp create mode 100644 src/gui/statemachine/qbasicmouseeventtransition_p.h create mode 100644 src/gui/statemachine/qguistatemachine.cpp create mode 100644 src/gui/statemachine/qkeyeventtransition.cpp create mode 100644 src/gui/statemachine/qkeyeventtransition.h create mode 100644 src/gui/statemachine/qmouseeventtransition.cpp create mode 100644 src/gui/statemachine/qmouseeventtransition.h create mode 100644 src/gui/statemachine/statemachine.pri create mode 100644 src/gui/styles/images/cdr-128.png create mode 100644 src/gui/styles/images/cdr-16.png create mode 100644 src/gui/styles/images/cdr-32.png create mode 100644 src/gui/styles/images/closedock-16.png create mode 100644 src/gui/styles/images/closedock-down-16.png create mode 100644 src/gui/styles/images/computer-16.png create mode 100644 src/gui/styles/images/computer-32.png create mode 100644 src/gui/styles/images/defaults60theme.blob create mode 100644 src/gui/styles/images/desktop-16.png create mode 100644 src/gui/styles/images/desktop-32.png create mode 100644 src/gui/styles/images/dirclosed-128.png create mode 100644 src/gui/styles/images/dirclosed-16.png create mode 100644 src/gui/styles/images/dirclosed-32.png create mode 100644 src/gui/styles/images/dirlink-128.png create mode 100644 src/gui/styles/images/dirlink-16.png create mode 100644 src/gui/styles/images/dirlink-32.png create mode 100644 src/gui/styles/images/diropen-128.png create mode 100644 src/gui/styles/images/diropen-16.png create mode 100644 src/gui/styles/images/diropen-32.png create mode 100644 src/gui/styles/images/dockdock-16.png create mode 100644 src/gui/styles/images/dockdock-down-16.png create mode 100644 src/gui/styles/images/down-128.png create mode 100644 src/gui/styles/images/down-16.png create mode 100644 src/gui/styles/images/down-32.png create mode 100644 src/gui/styles/images/dvd-128.png create mode 100644 src/gui/styles/images/dvd-16.png create mode 100644 src/gui/styles/images/dvd-32.png create mode 100644 src/gui/styles/images/file-128.png create mode 100644 src/gui/styles/images/file-16.png create mode 100644 src/gui/styles/images/file-32.png create mode 100644 src/gui/styles/images/filecontents-128.png create mode 100644 src/gui/styles/images/filecontents-16.png create mode 100644 src/gui/styles/images/filecontents-32.png create mode 100644 src/gui/styles/images/fileinfo-128.png create mode 100644 src/gui/styles/images/fileinfo-16.png create mode 100644 src/gui/styles/images/fileinfo-32.png create mode 100644 src/gui/styles/images/filelink-128.png create mode 100644 src/gui/styles/images/filelink-16.png create mode 100644 src/gui/styles/images/filelink-32.png create mode 100644 src/gui/styles/images/floppy-128.png create mode 100644 src/gui/styles/images/floppy-16.png create mode 100644 src/gui/styles/images/floppy-32.png create mode 100644 src/gui/styles/images/fontbitmap-16.png create mode 100644 src/gui/styles/images/fonttruetype-16.png create mode 100644 src/gui/styles/images/harddrive-128.png create mode 100644 src/gui/styles/images/harddrive-16.png create mode 100644 src/gui/styles/images/harddrive-32.png create mode 100644 src/gui/styles/images/left-128.png create mode 100644 src/gui/styles/images/left-16.png create mode 100644 src/gui/styles/images/left-32.png create mode 100644 src/gui/styles/images/media-pause-16.png create mode 100644 src/gui/styles/images/media-pause-32.png create mode 100644 src/gui/styles/images/media-play-16.png create mode 100644 src/gui/styles/images/media-play-32.png create mode 100644 src/gui/styles/images/media-seek-backward-16.png create mode 100644 src/gui/styles/images/media-seek-backward-32.png create mode 100644 src/gui/styles/images/media-seek-forward-16.png create mode 100644 src/gui/styles/images/media-seek-forward-32.png create mode 100644 src/gui/styles/images/media-skip-backward-16.png create mode 100644 src/gui/styles/images/media-skip-backward-32.png create mode 100644 src/gui/styles/images/media-skip-forward-16.png create mode 100644 src/gui/styles/images/media-skip-forward-32.png create mode 100644 src/gui/styles/images/media-stop-16.png create mode 100644 src/gui/styles/images/media-stop-32.png create mode 100644 src/gui/styles/images/media-volume-16.png create mode 100644 src/gui/styles/images/media-volume-muted-16.png create mode 100644 src/gui/styles/images/networkdrive-128.png create mode 100644 src/gui/styles/images/networkdrive-16.png create mode 100644 src/gui/styles/images/networkdrive-32.png create mode 100644 src/gui/styles/images/newdirectory-128.png create mode 100644 src/gui/styles/images/newdirectory-16.png create mode 100644 src/gui/styles/images/newdirectory-32.png create mode 100644 src/gui/styles/images/parentdir-128.png create mode 100644 src/gui/styles/images/parentdir-16.png create mode 100644 src/gui/styles/images/parentdir-32.png create mode 100644 src/gui/styles/images/refresh-24.png create mode 100644 src/gui/styles/images/refresh-32.png create mode 100644 src/gui/styles/images/right-128.png create mode 100644 src/gui/styles/images/right-16.png create mode 100644 src/gui/styles/images/right-32.png create mode 100644 src/gui/styles/images/standardbutton-apply-128.png create mode 100644 src/gui/styles/images/standardbutton-apply-16.png create mode 100644 src/gui/styles/images/standardbutton-apply-32.png create mode 100644 src/gui/styles/images/standardbutton-cancel-128.png create mode 100644 src/gui/styles/images/standardbutton-cancel-16.png create mode 100644 src/gui/styles/images/standardbutton-cancel-32.png create mode 100644 src/gui/styles/images/standardbutton-clear-128.png create mode 100644 src/gui/styles/images/standardbutton-clear-16.png create mode 100644 src/gui/styles/images/standardbutton-clear-32.png create mode 100644 src/gui/styles/images/standardbutton-close-128.png create mode 100644 src/gui/styles/images/standardbutton-close-16.png create mode 100644 src/gui/styles/images/standardbutton-close-32.png create mode 100644 src/gui/styles/images/standardbutton-closetab-16.png create mode 100644 src/gui/styles/images/standardbutton-closetab-down-16.png create mode 100644 src/gui/styles/images/standardbutton-closetab-hover-16.png create mode 100644 src/gui/styles/images/standardbutton-delete-128.png create mode 100644 src/gui/styles/images/standardbutton-delete-16.png create mode 100644 src/gui/styles/images/standardbutton-delete-32.png create mode 100644 src/gui/styles/images/standardbutton-help-128.png create mode 100644 src/gui/styles/images/standardbutton-help-16.png create mode 100644 src/gui/styles/images/standardbutton-help-32.png create mode 100644 src/gui/styles/images/standardbutton-no-128.png create mode 100644 src/gui/styles/images/standardbutton-no-16.png create mode 100644 src/gui/styles/images/standardbutton-no-32.png create mode 100644 src/gui/styles/images/standardbutton-ok-128.png create mode 100644 src/gui/styles/images/standardbutton-ok-16.png create mode 100644 src/gui/styles/images/standardbutton-ok-32.png create mode 100644 src/gui/styles/images/standardbutton-open-128.png create mode 100644 src/gui/styles/images/standardbutton-open-16.png create mode 100644 src/gui/styles/images/standardbutton-open-32.png create mode 100644 src/gui/styles/images/standardbutton-save-128.png create mode 100644 src/gui/styles/images/standardbutton-save-16.png create mode 100644 src/gui/styles/images/standardbutton-save-32.png create mode 100644 src/gui/styles/images/standardbutton-yes-128.png create mode 100644 src/gui/styles/images/standardbutton-yes-16.png create mode 100644 src/gui/styles/images/standardbutton-yes-32.png create mode 100644 src/gui/styles/images/stop-24.png create mode 100644 src/gui/styles/images/stop-32.png create mode 100644 src/gui/styles/images/trash-128.png create mode 100644 src/gui/styles/images/trash-16.png create mode 100644 src/gui/styles/images/trash-32.png create mode 100644 src/gui/styles/images/up-128.png create mode 100644 src/gui/styles/images/up-16.png create mode 100644 src/gui/styles/images/up-32.png create mode 100644 src/gui/styles/images/viewdetailed-128.png create mode 100644 src/gui/styles/images/viewdetailed-16.png create mode 100644 src/gui/styles/images/viewdetailed-32.png create mode 100644 src/gui/styles/images/viewlist-128.png create mode 100644 src/gui/styles/images/viewlist-16.png create mode 100644 src/gui/styles/images/viewlist-32.png create mode 100644 src/gui/styles/qcdestyle.cpp create mode 100644 src/gui/styles/qcdestyle.h create mode 100644 src/gui/styles/qcleanlooksstyle.cpp create mode 100644 src/gui/styles/qcleanlooksstyle.h create mode 100644 src/gui/styles/qcleanlooksstyle_p.h create mode 100644 src/gui/styles/qcommonstyle.cpp create mode 100644 src/gui/styles/qcommonstyle.h create mode 100644 src/gui/styles/qcommonstyle_p.h create mode 100644 src/gui/styles/qcommonstylepixmaps_p.h create mode 100644 src/gui/styles/qgtkpainter.cpp create mode 100644 src/gui/styles/qgtkpainter_p.h create mode 100644 src/gui/styles/qgtkstyle.cpp create mode 100644 src/gui/styles/qgtkstyle.h create mode 100644 src/gui/styles/qgtkstyle_p.cpp create mode 100644 src/gui/styles/qgtkstyle_p.h create mode 100644 src/gui/styles/qmacstyle.qdoc create mode 100644 src/gui/styles/qmacstyle_mac.h create mode 100644 src/gui/styles/qmacstyle_mac.mm create mode 100644 src/gui/styles/qmacstyle_mac_p.h create mode 100644 src/gui/styles/qmacstylepixmaps_mac_p.h create mode 100644 src/gui/styles/qmotifstyle.cpp create mode 100644 src/gui/styles/qmotifstyle.h create mode 100644 src/gui/styles/qmotifstyle_p.h create mode 100644 src/gui/styles/qplastiquestyle.cpp create mode 100644 src/gui/styles/qplastiquestyle.h create mode 100644 src/gui/styles/qproxystyle.cpp create mode 100644 src/gui/styles/qproxystyle.h create mode 100644 src/gui/styles/qproxystyle_p.h create mode 100644 src/gui/styles/qs60style.cpp create mode 100644 src/gui/styles/qs60style.h create mode 100644 src/gui/styles/qs60style_p.h create mode 100644 src/gui/styles/qs60style_s60.cpp create mode 100644 src/gui/styles/qs60style_simulated.cpp create mode 100644 src/gui/styles/qs60style_stub.cpp create mode 100644 src/gui/styles/qstyle.cpp create mode 100644 src/gui/styles/qstyle.h create mode 100644 src/gui/styles/qstyle.qrc create mode 100644 src/gui/styles/qstyle_p.h create mode 100644 src/gui/styles/qstyle_s60.qrc create mode 100644 src/gui/styles/qstyle_s60_simulated.qrc create mode 100644 src/gui/styles/qstyle_wince.qrc create mode 100644 src/gui/styles/qstylefactory.cpp create mode 100644 src/gui/styles/qstylefactory.h create mode 100644 src/gui/styles/qstylehelper.cpp create mode 100644 src/gui/styles/qstylehelper_p.h create mode 100644 src/gui/styles/qstyleoption.cpp create mode 100644 src/gui/styles/qstyleoption.h create mode 100644 src/gui/styles/qstyleplugin.cpp create mode 100644 src/gui/styles/qstyleplugin.h create mode 100644 src/gui/styles/qstylesheetstyle.cpp create mode 100644 src/gui/styles/qstylesheetstyle_default.cpp create mode 100644 src/gui/styles/qstylesheetstyle_p.h create mode 100644 src/gui/styles/qwindowscestyle.cpp create mode 100644 src/gui/styles/qwindowscestyle.h create mode 100644 src/gui/styles/qwindowscestyle_p.h create mode 100644 src/gui/styles/qwindowsmobilestyle.cpp create mode 100644 src/gui/styles/qwindowsmobilestyle.h create mode 100644 src/gui/styles/qwindowsmobilestyle_p.h create mode 100644 src/gui/styles/qwindowsstyle.cpp create mode 100644 src/gui/styles/qwindowsstyle.h create mode 100644 src/gui/styles/qwindowsstyle_p.h create mode 100644 src/gui/styles/qwindowsvistastyle.cpp create mode 100644 src/gui/styles/qwindowsvistastyle.h create mode 100644 src/gui/styles/qwindowsvistastyle_p.h create mode 100644 src/gui/styles/qwindowsxpstyle.cpp create mode 100644 src/gui/styles/qwindowsxpstyle.h create mode 100644 src/gui/styles/qwindowsxpstyle_p.h create mode 100644 src/gui/styles/styles.pri create mode 100644 src/gui/symbian/images/blank.png create mode 100644 src/gui/symbian/images/busy12.png create mode 100644 src/gui/symbian/images/busy3.png create mode 100644 src/gui/symbian/images/busy6.png create mode 100644 src/gui/symbian/images/busy9.png create mode 100644 src/gui/symbian/images/closehand.png create mode 100644 src/gui/symbian/images/cross.png create mode 100644 src/gui/symbian/images/forbidden.png create mode 100644 src/gui/symbian/images/handpoint.png create mode 100644 src/gui/symbian/images/ibeam.png create mode 100644 src/gui/symbian/images/openhand.png create mode 100644 src/gui/symbian/images/pointer.png create mode 100644 src/gui/symbian/images/sizeall.png create mode 100644 src/gui/symbian/images/sizebdiag.png create mode 100644 src/gui/symbian/images/sizefdiag.png create mode 100644 src/gui/symbian/images/sizehor.png create mode 100644 src/gui/symbian/images/sizever.png create mode 100644 src/gui/symbian/images/splith.png create mode 100644 src/gui/symbian/images/splitv.png create mode 100644 src/gui/symbian/images/uparrow.png create mode 100644 src/gui/symbian/images/wait1.png create mode 100644 src/gui/symbian/images/wait10.png create mode 100644 src/gui/symbian/images/wait11.png create mode 100644 src/gui/symbian/images/wait12.png create mode 100644 src/gui/symbian/images/wait2.png create mode 100644 src/gui/symbian/images/wait3.png create mode 100644 src/gui/symbian/images/wait4.png create mode 100644 src/gui/symbian/images/wait5.png create mode 100644 src/gui/symbian/images/wait6.png create mode 100644 src/gui/symbian/images/wait7.png create mode 100644 src/gui/symbian/images/wait8.png create mode 100644 src/gui/symbian/images/wait9.png create mode 100644 src/gui/symbian/images/whatsthis.png create mode 100644 src/gui/symbian/qsymbianevent.cpp create mode 100644 src/gui/symbian/qsymbianevent.h create mode 100644 src/gui/symbian/symbianresources.qrc create mode 100644 src/gui/text/qabstractfontengine_p.h create mode 100644 src/gui/text/qabstractfontengine_qws.cpp create mode 100644 src/gui/text/qabstractfontengine_qws.h create mode 100644 src/gui/text/qabstracttextdocumentlayout.cpp create mode 100644 src/gui/text/qabstracttextdocumentlayout.h create mode 100644 src/gui/text/qabstracttextdocumentlayout_p.h create mode 100644 src/gui/text/qcssparser.cpp create mode 100644 src/gui/text/qcssparser_p.h create mode 100644 src/gui/text/qcssscanner.cpp create mode 100644 src/gui/text/qfont.cpp create mode 100644 src/gui/text/qfont.h create mode 100644 src/gui/text/qfont_mac.cpp create mode 100644 src/gui/text/qfont_p.h create mode 100644 src/gui/text/qfont_qpa.cpp create mode 100644 src/gui/text/qfont_qws.cpp create mode 100644 src/gui/text/qfont_s60.cpp create mode 100644 src/gui/text/qfont_win.cpp create mode 100644 src/gui/text/qfont_x11.cpp create mode 100644 src/gui/text/qfontdatabase.cpp create mode 100644 src/gui/text/qfontdatabase.h create mode 100644 src/gui/text/qfontdatabase_mac.cpp create mode 100644 src/gui/text/qfontdatabase_qpa.cpp create mode 100644 src/gui/text/qfontdatabase_qws.cpp create mode 100644 src/gui/text/qfontdatabase_s60.cpp create mode 100644 src/gui/text/qfontdatabase_win.cpp create mode 100644 src/gui/text/qfontdatabase_x11.cpp create mode 100644 src/gui/text/qfontengine.cpp create mode 100644 src/gui/text/qfontengine_coretext.mm create mode 100644 src/gui/text/qfontengine_coretext_p.h create mode 100644 src/gui/text/qfontengine_ft.cpp create mode 100644 src/gui/text/qfontengine_ft_p.h create mode 100644 src/gui/text/qfontengine_mac.mm create mode 100644 src/gui/text/qfontengine_mac_p.h create mode 100644 src/gui/text/qfontengine_p.h create mode 100644 src/gui/text/qfontengine_qpa.cpp create mode 100644 src/gui/text/qfontengine_qpa_p.h create mode 100644 src/gui/text/qfontengine_qpf.cpp create mode 100644 src/gui/text/qfontengine_qpf_p.h create mode 100644 src/gui/text/qfontengine_qws.cpp create mode 100644 src/gui/text/qfontengine_s60.cpp create mode 100644 src/gui/text/qfontengine_s60_p.h create mode 100644 src/gui/text/qfontengine_win.cpp create mode 100644 src/gui/text/qfontengine_win_p.h create mode 100644 src/gui/text/qfontengine_x11.cpp create mode 100644 src/gui/text/qfontengine_x11_p.h create mode 100644 src/gui/text/qfontenginedirectwrite.cpp create mode 100644 src/gui/text/qfontenginedirectwrite_p.h create mode 100644 src/gui/text/qfontengineglyphcache_p.h create mode 100644 src/gui/text/qfontinfo.h create mode 100644 src/gui/text/qfontmetrics.cpp create mode 100644 src/gui/text/qfontmetrics.h create mode 100644 src/gui/text/qfontsubset.cpp create mode 100644 src/gui/text/qfontsubset_p.h create mode 100644 src/gui/text/qfragmentmap.cpp create mode 100644 src/gui/text/qfragmentmap_p.h create mode 100644 src/gui/text/qglyphs.cpp create mode 100644 src/gui/text/qglyphs.h create mode 100644 src/gui/text/qglyphs_p.h create mode 100644 src/gui/text/qpfutil.cpp create mode 100644 src/gui/text/qplatformfontdatabase_qpa.cpp create mode 100644 src/gui/text/qplatformfontdatabase_qpa.h create mode 100644 src/gui/text/qrawfont.cpp create mode 100644 src/gui/text/qrawfont.h create mode 100644 src/gui/text/qrawfont_ft.cpp create mode 100644 src/gui/text/qrawfont_mac.cpp create mode 100644 src/gui/text/qrawfont_p.h create mode 100644 src/gui/text/qrawfont_win.cpp create mode 100644 src/gui/text/qstatictext.cpp create mode 100644 src/gui/text/qstatictext.h create mode 100644 src/gui/text/qstatictext_p.h create mode 100644 src/gui/text/qsyntaxhighlighter.cpp create mode 100644 src/gui/text/qsyntaxhighlighter.h create mode 100644 src/gui/text/qtextcontrol.cpp create mode 100644 src/gui/text/qtextcontrol_p.h create mode 100644 src/gui/text/qtextcontrol_p_p.h create mode 100644 src/gui/text/qtextcursor.cpp create mode 100644 src/gui/text/qtextcursor.h create mode 100644 src/gui/text/qtextcursor_p.h create mode 100644 src/gui/text/qtextdocument.cpp create mode 100644 src/gui/text/qtextdocument.h create mode 100644 src/gui/text/qtextdocument_p.cpp create mode 100644 src/gui/text/qtextdocument_p.h create mode 100644 src/gui/text/qtextdocumentfragment.cpp create mode 100644 src/gui/text/qtextdocumentfragment.h create mode 100644 src/gui/text/qtextdocumentfragment_p.h create mode 100644 src/gui/text/qtextdocumentlayout.cpp create mode 100644 src/gui/text/qtextdocumentlayout_p.h create mode 100644 src/gui/text/qtextdocumentwriter.cpp create mode 100644 src/gui/text/qtextdocumentwriter.h create mode 100644 src/gui/text/qtextengine.cpp create mode 100644 src/gui/text/qtextengine_mac.cpp create mode 100644 src/gui/text/qtextengine_p.h create mode 100644 src/gui/text/qtextformat.cpp create mode 100644 src/gui/text/qtextformat.h create mode 100644 src/gui/text/qtextformat_p.h create mode 100644 src/gui/text/qtexthtmlparser.cpp create mode 100644 src/gui/text/qtexthtmlparser_p.h create mode 100644 src/gui/text/qtextimagehandler.cpp create mode 100644 src/gui/text/qtextimagehandler_p.h create mode 100644 src/gui/text/qtextlayout.cpp create mode 100644 src/gui/text/qtextlayout.h create mode 100644 src/gui/text/qtextlist.cpp create mode 100644 src/gui/text/qtextlist.h create mode 100644 src/gui/text/qtextobject.cpp create mode 100644 src/gui/text/qtextobject.h create mode 100644 src/gui/text/qtextobject_p.h create mode 100644 src/gui/text/qtextodfwriter.cpp create mode 100644 src/gui/text/qtextodfwriter_p.h create mode 100644 src/gui/text/qtextoption.cpp create mode 100644 src/gui/text/qtextoption.h create mode 100644 src/gui/text/qtexttable.cpp create mode 100644 src/gui/text/qtexttable.h create mode 100644 src/gui/text/qtexttable_p.h create mode 100644 src/gui/text/qzip.cpp create mode 100644 src/gui/text/qzipreader_p.h create mode 100644 src/gui/text/qzipwriter_p.h create mode 100644 src/gui/text/text.pri create mode 100644 src/gui/util/qcompleter.cpp create mode 100644 src/gui/util/qcompleter.h create mode 100644 src/gui/util/qcompleter_p.h create mode 100644 src/gui/util/qdesktopservices.cpp create mode 100644 src/gui/util/qdesktopservices.h create mode 100644 src/gui/util/qdesktopservices_mac.cpp create mode 100644 src/gui/util/qdesktopservices_qws.cpp create mode 100644 src/gui/util/qdesktopservices_s60.cpp create mode 100644 src/gui/util/qdesktopservices_win.cpp create mode 100644 src/gui/util/qdesktopservices_x11.cpp create mode 100644 src/gui/util/qflickgesture.cpp create mode 100644 src/gui/util/qflickgesture_p.h create mode 100644 src/gui/util/qscroller.cpp create mode 100644 src/gui/util/qscroller.h create mode 100644 src/gui/util/qscroller_mac.mm create mode 100644 src/gui/util/qscroller_p.h create mode 100644 src/gui/util/qscrollerproperties.cpp create mode 100644 src/gui/util/qscrollerproperties.h create mode 100644 src/gui/util/qscrollerproperties_p.h create mode 100644 src/gui/util/qsystemtrayicon.cpp create mode 100644 src/gui/util/qsystemtrayicon.h create mode 100644 src/gui/util/qsystemtrayicon_mac.mm create mode 100644 src/gui/util/qsystemtrayicon_p.h create mode 100644 src/gui/util/qsystemtrayicon_qws.cpp create mode 100644 src/gui/util/qsystemtrayicon_win.cpp create mode 100644 src/gui/util/qsystemtrayicon_wince.cpp create mode 100644 src/gui/util/qsystemtrayicon_x11.cpp create mode 100644 src/gui/util/qundogroup.cpp create mode 100644 src/gui/util/qundogroup.h create mode 100644 src/gui/util/qundostack.cpp create mode 100644 src/gui/util/qundostack.h create mode 100644 src/gui/util/qundostack_p.h create mode 100644 src/gui/util/qundoview.cpp create mode 100644 src/gui/util/qundoview.h create mode 100644 src/gui/util/util.pri create mode 100644 src/gui/widgets/qabstractbutton.cpp create mode 100644 src/gui/widgets/qabstractbutton.h create mode 100644 src/gui/widgets/qabstractbutton_p.h create mode 100644 src/gui/widgets/qabstractscrollarea.cpp create mode 100644 src/gui/widgets/qabstractscrollarea.h create mode 100644 src/gui/widgets/qabstractscrollarea_p.h create mode 100644 src/gui/widgets/qabstractslider.cpp create mode 100644 src/gui/widgets/qabstractslider.h create mode 100644 src/gui/widgets/qabstractslider_p.h create mode 100644 src/gui/widgets/qabstractspinbox.cpp create mode 100644 src/gui/widgets/qabstractspinbox.h create mode 100644 src/gui/widgets/qabstractspinbox_p.h create mode 100644 src/gui/widgets/qbuttongroup.cpp create mode 100644 src/gui/widgets/qbuttongroup.h create mode 100644 src/gui/widgets/qcalendartextnavigator_p.h create mode 100644 src/gui/widgets/qcalendarwidget.cpp create mode 100644 src/gui/widgets/qcalendarwidget.h create mode 100644 src/gui/widgets/qcheckbox.cpp create mode 100644 src/gui/widgets/qcheckbox.h create mode 100644 src/gui/widgets/qcocoamenu_mac.mm create mode 100644 src/gui/widgets/qcocoamenu_mac_p.h create mode 100644 src/gui/widgets/qcocoatoolbardelegate_mac.mm create mode 100644 src/gui/widgets/qcocoatoolbardelegate_mac_p.h create mode 100644 src/gui/widgets/qcombobox.cpp create mode 100644 src/gui/widgets/qcombobox.h create mode 100644 src/gui/widgets/qcombobox_p.h create mode 100644 src/gui/widgets/qcommandlinkbutton.cpp create mode 100644 src/gui/widgets/qcommandlinkbutton.h create mode 100644 src/gui/widgets/qdatetimeedit.cpp create mode 100644 src/gui/widgets/qdatetimeedit.h create mode 100644 src/gui/widgets/qdatetimeedit_p.h create mode 100644 src/gui/widgets/qdial.cpp create mode 100644 src/gui/widgets/qdial.h create mode 100644 src/gui/widgets/qdialogbuttonbox.cpp create mode 100644 src/gui/widgets/qdialogbuttonbox.h create mode 100644 src/gui/widgets/qdockarealayout.cpp create mode 100644 src/gui/widgets/qdockarealayout_p.h create mode 100644 src/gui/widgets/qdockwidget.cpp create mode 100644 src/gui/widgets/qdockwidget.h create mode 100644 src/gui/widgets/qdockwidget_p.h create mode 100644 src/gui/widgets/qeffects.cpp create mode 100644 src/gui/widgets/qeffects_p.h create mode 100644 src/gui/widgets/qfocusframe.cpp create mode 100644 src/gui/widgets/qfocusframe.h create mode 100644 src/gui/widgets/qfontcombobox.cpp create mode 100644 src/gui/widgets/qfontcombobox.h create mode 100644 src/gui/widgets/qframe.cpp create mode 100644 src/gui/widgets/qframe.h create mode 100644 src/gui/widgets/qframe_p.h create mode 100644 src/gui/widgets/qgroupbox.cpp create mode 100644 src/gui/widgets/qgroupbox.h create mode 100644 src/gui/widgets/qlabel.cpp create mode 100644 src/gui/widgets/qlabel.h create mode 100644 src/gui/widgets/qlabel_p.h create mode 100644 src/gui/widgets/qlcdnumber.cpp create mode 100644 src/gui/widgets/qlcdnumber.h create mode 100644 src/gui/widgets/qlinecontrol.cpp create mode 100644 src/gui/widgets/qlinecontrol_p.h create mode 100644 src/gui/widgets/qlineedit.cpp create mode 100644 src/gui/widgets/qlineedit.h create mode 100644 src/gui/widgets/qlineedit_p.cpp create mode 100644 src/gui/widgets/qlineedit_p.h create mode 100644 src/gui/widgets/qmaccocoaviewcontainer_mac.h create mode 100644 src/gui/widgets/qmaccocoaviewcontainer_mac.mm create mode 100644 src/gui/widgets/qmacnativewidget_mac.h create mode 100644 src/gui/widgets/qmacnativewidget_mac.mm create mode 100644 src/gui/widgets/qmainwindow.cpp create mode 100644 src/gui/widgets/qmainwindow.h create mode 100644 src/gui/widgets/qmainwindowlayout.cpp create mode 100644 src/gui/widgets/qmainwindowlayout_mac.mm create mode 100644 src/gui/widgets/qmainwindowlayout_p.h create mode 100644 src/gui/widgets/qmdiarea.cpp create mode 100644 src/gui/widgets/qmdiarea.h create mode 100644 src/gui/widgets/qmdiarea_p.h create mode 100644 src/gui/widgets/qmdisubwindow.cpp create mode 100644 src/gui/widgets/qmdisubwindow.h create mode 100644 src/gui/widgets/qmdisubwindow_p.h create mode 100644 src/gui/widgets/qmenu.cpp create mode 100644 src/gui/widgets/qmenu.h create mode 100644 src/gui/widgets/qmenu_mac.mm create mode 100644 src/gui/widgets/qmenu_p.h create mode 100644 src/gui/widgets/qmenu_symbian.cpp create mode 100644 src/gui/widgets/qmenu_wince.cpp create mode 100644 src/gui/widgets/qmenu_wince.rc create mode 100644 src/gui/widgets/qmenu_wince_resource_p.h create mode 100644 src/gui/widgets/qmenubar.cpp create mode 100644 src/gui/widgets/qmenubar.h create mode 100644 src/gui/widgets/qmenubar_p.h create mode 100644 src/gui/widgets/qmenudata.cpp create mode 100644 src/gui/widgets/qmenudata.h create mode 100644 src/gui/widgets/qplaintextedit.cpp create mode 100644 src/gui/widgets/qplaintextedit.h create mode 100644 src/gui/widgets/qplaintextedit_p.h create mode 100644 src/gui/widgets/qprintpreviewwidget.cpp create mode 100644 src/gui/widgets/qprintpreviewwidget.h create mode 100644 src/gui/widgets/qprogressbar.cpp create mode 100644 src/gui/widgets/qprogressbar.h create mode 100644 src/gui/widgets/qpushbutton.cpp create mode 100644 src/gui/widgets/qpushbutton.h create mode 100644 src/gui/widgets/qpushbutton_p.h create mode 100644 src/gui/widgets/qradiobutton.cpp create mode 100644 src/gui/widgets/qradiobutton.h create mode 100644 src/gui/widgets/qrubberband.cpp create mode 100644 src/gui/widgets/qrubberband.h create mode 100644 src/gui/widgets/qscrollarea.cpp create mode 100644 src/gui/widgets/qscrollarea.h create mode 100644 src/gui/widgets/qscrollarea_p.h create mode 100644 src/gui/widgets/qscrollbar.cpp create mode 100644 src/gui/widgets/qscrollbar.h create mode 100644 src/gui/widgets/qsizegrip.cpp create mode 100644 src/gui/widgets/qsizegrip.h create mode 100644 src/gui/widgets/qslider.cpp create mode 100644 src/gui/widgets/qslider.h create mode 100644 src/gui/widgets/qspinbox.cpp create mode 100644 src/gui/widgets/qspinbox.h create mode 100644 src/gui/widgets/qsplashscreen.cpp create mode 100644 src/gui/widgets/qsplashscreen.h create mode 100644 src/gui/widgets/qsplitter.cpp create mode 100644 src/gui/widgets/qsplitter.h create mode 100644 src/gui/widgets/qsplitter_p.h create mode 100644 src/gui/widgets/qstackedwidget.cpp create mode 100644 src/gui/widgets/qstackedwidget.h create mode 100644 src/gui/widgets/qstatusbar.cpp create mode 100644 src/gui/widgets/qstatusbar.h create mode 100644 src/gui/widgets/qtabbar.cpp create mode 100644 src/gui/widgets/qtabbar.h create mode 100644 src/gui/widgets/qtabbar_p.h create mode 100644 src/gui/widgets/qtabwidget.cpp create mode 100644 src/gui/widgets/qtabwidget.h create mode 100644 src/gui/widgets/qtextbrowser.cpp create mode 100644 src/gui/widgets/qtextbrowser.h create mode 100644 src/gui/widgets/qtextedit.cpp create mode 100644 src/gui/widgets/qtextedit.h create mode 100644 src/gui/widgets/qtextedit_p.h create mode 100644 src/gui/widgets/qtoolbar.cpp create mode 100644 src/gui/widgets/qtoolbar.h create mode 100644 src/gui/widgets/qtoolbar_p.h create mode 100644 src/gui/widgets/qtoolbararealayout.cpp create mode 100644 src/gui/widgets/qtoolbararealayout_p.h create mode 100644 src/gui/widgets/qtoolbarextension.cpp create mode 100644 src/gui/widgets/qtoolbarextension_p.h create mode 100644 src/gui/widgets/qtoolbarlayout.cpp create mode 100644 src/gui/widgets/qtoolbarlayout_p.h create mode 100644 src/gui/widgets/qtoolbarseparator.cpp create mode 100644 src/gui/widgets/qtoolbarseparator_p.h create mode 100644 src/gui/widgets/qtoolbox.cpp create mode 100644 src/gui/widgets/qtoolbox.h create mode 100644 src/gui/widgets/qtoolbutton.cpp create mode 100644 src/gui/widgets/qtoolbutton.h create mode 100644 src/gui/widgets/qvalidator.cpp create mode 100644 src/gui/widgets/qvalidator.h create mode 100644 src/gui/widgets/qwidgetanimator.cpp create mode 100644 src/gui/widgets/qwidgetanimator_p.h create mode 100644 src/gui/widgets/qwidgetresizehandler.cpp create mode 100644 src/gui/widgets/qwidgetresizehandler_p.h create mode 100644 src/gui/widgets/qworkspace.cpp create mode 100644 src/gui/widgets/qworkspace.h create mode 100644 src/gui/widgets/widgets.pri (limited to 'src/gui') diff --git a/src/gui/QtGui.dynlist b/src/gui/QtGui.dynlist new file mode 100644 index 0000000000..ea47f598a7 --- /dev/null +++ b/src/gui/QtGui.dynlist @@ -0,0 +1,8 @@ +{ + extern "C++" { + "qt_x11ft_convert_pattern(_FcPattern*, QByteArray*, int*, bool*)"; + "QApplication::x11ClientMessage(QWidget*, _XEvent*, bool)"; + "QApplication::notify(QObject*, QEvent*)"; + "QApplication::qwsEventFilter(QWSEvent*)"; + }; +}; diff --git a/src/gui/accessible/accessible.pri b/src/gui/accessible/accessible.pri new file mode 100644 index 0000000000..31362ffded --- /dev/null +++ b/src/gui/accessible/accessible.pri @@ -0,0 +1,25 @@ +# Qt accessibility module + +contains(QT_CONFIG, accessibility) { + HEADERS += accessible/qaccessible.h \ + accessible/qaccessible2.h \ + accessible/qaccessibleobject.h \ + accessible/qaccessiblewidget.h \ + accessible/qaccessibleplugin.h + SOURCES += accessible/qaccessible.cpp \ + accessible/qaccessible2.cpp \ + accessible/qaccessibleobject.cpp \ + accessible/qaccessiblewidget.cpp \ + accessible/qaccessibleplugin.cpp + + mac:!embedded:!qpa { + HEADERS += accessible/qaccessible_mac_p.h + OBJECTIVE_SOURCES += accessible/qaccessible_mac.mm \ + accessible/qaccessible_mac_cocoa.mm + } else:win32 { + SOURCES += accessible/qaccessible_win.cpp + } else { + HEADERS += accessible/qaccessiblebridge.h + SOURCES += accessible/qaccessible_unix.cpp accessible/qaccessiblebridge.cpp + } +} diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp new file mode 100644 index 0000000000..abe68f8108 --- /dev/null +++ b/src/gui/accessible/qaccessible.cpp @@ -0,0 +1,1094 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessible.h" + +#ifndef QT_NO_ACCESSIBILITY + +#include "qaccessibleplugin.h" +#include "qaccessiblewidget.h" +#include "qapplication.h" +#include "qhash.h" +#include "qmetaobject.h" +#include "qmutex.h" +#include + +#include "qwidget.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAccessible + \brief The QAccessible class provides enums and static functions + relating to accessibility. + + \ingroup accessibility + + + Accessible applications can be used by people who are not able to + use applications by conventional means. + + The functions in this class are used for communication between + accessible applications (also called AT Servers) and + accessibility tools (AT Clients), such as screen readers and + braille displays. Clients and servers communicate in the following way: + + \list + \o \e{AT Servers} notify the clients about events through calls to the + updateAccessibility() function. + + \o \e{AT Clients} request information about the objects in the server. + The QAccessibleInterface class is the core interface, and encapsulates + this information in a pure virtual API. Implementations of the interface + are provided by Qt through the queryAccessibleInterface() API. + \endlist + + The communication between servers and clients is initialized by + the setRootObject() function. Function pointers can be installed + to replace or extend the default behavior of the static functions + in QAccessible. + + Qt supports Microsoft Active Accessibility (MSAA), Mac OS X + Accessibility, and the Unix/X11 AT-SPI standard. Other backends + can be supported using QAccessibleBridge. + + In addition to QAccessible's static functions, Qt offers one + generic interface, QAccessibleInterface, that can be used to wrap + all widgets and objects (e.g., QPushButton). This single + interface provides all the metadata necessary for the assistive + technologies. Qt provides implementations of this interface for + its built-in widgets as plugins. + + When you develop custom widgets, you can create custom subclasses + of QAccessibleInterface and distribute them as plugins (using + QAccessiblePlugin) or compile them into the application. + Likewise, Qt's predefined accessibility support can be built as + plugin (the default) or directly into the Qt library. The main + advantage of using plugins is that the accessibility classes are + only loaded into memory if they are actually used; they don't + slow down the common case where no assistive technology is being + used. + + Qt also includes two convenience classes, QAccessibleObject and + QAccessibleWidget, that inherit from QAccessibleInterface and + provide the lowest common denominator of metadata (e.g., widget + geometry, window title, basic help text). You can use them as + base classes when wrapping your custom QObject or QWidget + subclasses. + + \sa QAccessibleInterface +*/ + +/*! + \enum QAccessible::Action + + This enum describes the possible types of action that can occur. + + \value DefaultAction + \value Press + \value SetFocus + \value Increase + \value Decrease + \value Accept + \value Cancel + \value Select + \value ClearSelection + \value RemoveSelection + \value ExtendSelection + \value AddToSelection + + \value FirstStandardAction + \value LastStandardAction +*/ + +/*! + \enum QAccessible::Method + + This enum describes the possible types of methods that can be + invoked on an accessible object. + + \value ListSupportedMethods + \value SetCursorPosition + \value GetCursorPosition + + \omitvalue ForegroundColor + \omitvalue BackgroundColor + + \sa QAccessibleInterface::invokeMethod() +*/ + +/*! + \fn QSet QAccessibleInterface::supportedMethods() + \since 4.3 + + Returns a QSet of \l{QAccessible::}{Method}s that are supported by this + accessible interface. + + \sa QAccessible::Method invokeMethod() +*/ + +/*! + \enum QAccessible::StateFlag + + This enum type defines bit flags that can be combined to indicate + the state of an accessible object. The values are: + + \value Animated The object's appearance changes frequently. + \value Busy The object cannot accept input at the moment. + \value Checked The object's check box is checked. + \value Collapsed The object is collapsed, e.g. a closed listview item, or an iconified window. + \value DefaultButton The object represents the default button in a dialog. + \value Expanded The object is expandable, and currently the children are visible. + \value ExtSelectable The object supports extended selection. + \value Focusable The object can receive focus. Only objects in the active window can receive focus. + \value Focused The object has keyboard focus. + \value HasPopup The object opens a popup. + \value HotTracked The object's appearance is sensitive to the mouse cursor position. + \value Invisible The object is not visible to the user. + \value Linked The object is linked to another object, e.g. a hyperlink. + \value Marqueed The object displays scrolling contents, e.g. a log view. + \value Mixed The state of the object is not determined, e.g. a tri-state check box that is neither checked nor unchecked. + \value Modal The object blocks input from other objects. + \value Movable The object can be moved. + \value MultiSelectable The object supports multiple selected items. + \value Normal The normal state. + \value Offscreen The object is clipped by the visible area. Objects that are off screen are also invisible. + \value Pressed The object is pressed. + \value Protected The object is password protected, e.g. a line edit for entering a Password. + \value ReadOnly The object can usually be edited, but is explicitly set to read-only. + \value Selectable The object is selectable. + \value Selected The object is selected. + \value SelfVoicing The object describes itself through speech or sound. + \value Sizeable The object can be resized, e.g. top-level windows. + \value Traversed The object is linked and has been visited. + \value Unavailable The object is unavailable to the user, e.g. a disabled widget. + \omitvalue Moveable + \omitvalue HasInvokeExtension + + Implementations of QAccessibleInterface::state() return a combination + of these flags. +*/ + +/*! + \enum QAccessible::Event + + This enum type defines accessible event types. + + \value AcceleratorChanged + \value Alert A system alert (e.g., a message from a QMessageBox) + \value ContextHelpEnd Context help (QWhatsThis) for an object is finished. + \value ContextHelpStart Context help (QWhatsThis) for an object is initiated. + \value DefaultActionChanged The default QAccessible::Action for the accessible object changed + \value DescriptionChanged The objects QAccessible::Description changed. + \value DialogEnd A dialog (QDialog) is been hidden + \value DialogStart A dialog (QDialog) has been set visible. + \value DragDropEnd A Drag & Drop operation is about to finished. + \value DragDropStart A Drag & Drop operation is about to be initiated. + \value Focus An object has gained keyboard focus. + \value ForegroundChanged A window has been activated (i.e., a new window has gained focus on the desktop) + \value HelpChanged The QAccessible::Help text property of an object has changed + \value LocationChanged An objects location on the screen changed + \value MenuCommand A menu item is triggered. + \value MenuEnd A menu has been closed (Qt uses PopupMenuEnd for all menus) + \value MenuStart A menu has been opened on the menubar (Qt uses PopupMenuStart for all menus) + \value NameChanged The QAccessible::Name property of an object has changed + \value ObjectCreated A new object is created. + \value ObjectDestroyed An object is deleted. + \value ObjectHide An object is hidden (i.e., with QWidget::hide()). Any children the object that is hidden has do not send this event. + It is not send when an object is hidden as it is being obcured by others. + \value ObjectReorder A layout or item view has added, removed, or moved an object (Qt does not use this event). + \value ObjectShow An object is displayed (i.e., with QWidget::show()). + \value ParentChanged An objects parent object changed. + \value PopupMenuEnd A popup menu has closed. + \value PopupMenuStart A popupmenu has opened. + \value ScrollingEnd A scrollbar scroll operation has ended (the mouse has released the slider handle) + \value ScrollingStart A scrollbar scroll operation is about to start (i.e., the mouse has pressed on the slider handle) + \value Selection The selection has changed in a menu or item view. + \value SelectionAdd An item has been added to the selection in an item view. + \value SelectionRemove An item has been removed from an item view selection. + \value SelectionWithin Several changes to a selection has occurred in an item view. + \value SoundPlayed A sound has been played by an object + \value StateChanged The QAccessible::State of an object has changed. + \value ValueChanged The QAccessible::Value of an object has changed. +*/ + +/*! + \enum QAccessible::Role + + This enum defines the role of an accessible object. The roles are: + + \value AlertMessage An object that is used to alert the user. + \value Animation An object that displays an animation. + \value Application The application's main window. + \value Assistant An object that provids interactive help. + \value Border An object that represents a border. + \value ButtonDropDown A button that drops down a list of items. + \value ButtonDropGrid A button that drops down a grid. + \value ButtonMenu A button that drops down a menu. + \value Canvas An object that displays graphics that the user can interact with. + \value Caret An object that represents the system caret (text cursor). + \value Cell A cell in a table. + \value Chart An object that displays a graphical representation of data. + \value CheckBox An object that represents an option that can be checked or unchecked. Some options provide a "mixed" state, e.g. neither checked nor unchecked. + \value Client The client area in a window. + \value Clock A clock displaying time. + \value Column A column of cells, usually within a table. + \value ColumnHeader A header for a column of data. + \value ComboBox A list of choices that the user can select from. + \value Cursor An object that represents the mouse cursor. + \value Dial An object that represents a dial or knob. + \value Dialog A dialog box. + \value Document A document window, usually in an MDI environment. + \value EditableText Editable text + \value Equation An object that represents a mathematical equation. + \value Graphic A graphic or picture, e.g. an icon. + \value Grip A grip that the user can drag to change the size of widgets. + \value Grouping An object that represents a logical grouping of other objects. + \value HelpBalloon An object that displays help in a separate, short lived window. + \value HotkeyField A hotkey field that allows the user to enter a key sequence. + \value Indicator An indicator that represents a current value or item. + \value LayeredPane An object that can contain layered children, e.g. in a stack. + \value Link A link to something else. + \value List A list of items, from which the user can select one or more items. + \value ListItem An item in a list of items. + \value MenuBar A menu bar from which menus are opened by the user. + \value MenuItem An item in a menu or menu bar. + \value NoRole The object has no role. This usually indicates an invalid object. + \value PageTab A page tab that the user can select to switch to a different page in a dialog. + \value PageTabList A list of page tabs. + \value Pane A generic container. + \value PopupMenu A menu which lists options that the user can select to perform an action. + \value ProgressBar The object displays the progress of an operation in progress. + \value PropertyPage A property page where the user can change options and settings. + \value PushButton A button. + \value RadioButton An object that represents an option that is mutually exclusive with other options. + \value Row A row of cells, usually within a table. + \value RowHeader A header for a row of data. + \value ScrollBar A scroll bar, which allows the user to scroll the visible area. + \value Separator A separator that divides space into logical areas. + \value Slider A slider that allows the user to select a value within a given range. + \value Sound An object that represents a sound. + \value SpinBox A spin box widget that allows the user to enter a value within a given range. + \value Splitter A splitter distributing available space between its child widgets. + \value StaticText Static text, such as labels for other widgets. + \value StatusBar A status bar. + \value Table A table representing data in a grid of rows and columns. + \value TitleBar The title bar caption of a window. + \value ToolBar A tool bar, which groups widgets that the user accesses frequently. + \value ToolTip A tool tip which provides information about other objects. + \value Tree A list of items in a tree structure. + \value TreeItem An item in a tree structure. + \value UserRole The first value to be used for user defined roles. + \value Whitespace Blank space between other objects. + \value Window A top level window. +*/ + +/*! + \enum QAccessible::RelationFlag + + This enum type defines bit flags that can be combined to indicate + the relationship between two accessible objects. + + \value Unrelated The objects are unrelated. + \value Self The objects are the same. + \value Ancestor The first object is a parent of the second object. + \value Child The first object is a direct child of the second object. + \value Descendent The first object is an indirect child of the second object. + \value Sibling The objects are siblings. + + \value Up The first object is above the second object. + \value Down The first object is below the second object. + \value Left The first object is left of the second object. + \value Right The first object is right of the second object. + \value Covers The first object covers the second object. + \value Covered The first object is covered by the second object. + + \value FocusChild The first object is the second object's focus child. + \value Label The first object is the label of the second object. + \value Labelled The first object is labelled by the second object. + \value Controller The first object controls the second object. + \value Controlled The first object is controlled by the second object. + + \omitvalue HierarchyMask + \omitvalue GeometryMask + \omitvalue LogicalMask + + Implementations of relationTo() return a combination of these flags. + Some values are mutually exclusive. + + Implementations of navigate() can accept only one distinct value. +*/ + +/*! + \enum QAccessible::Text + + This enum specifies string information that an accessible object + returns. + + \value Name The name of the object. This can be used both + as an identifier or a short description by + accessible clients. + \value Description A short text describing the object. + \value Value The value of the object. + \value Help A longer text giving information about how to use the object. + \value Accelerator The keyboard shortcut that executes the object's default action. + \value UserText The first value to be used for user defined text. +*/ + +/*! + \fn QAccessibleInterface::~QAccessibleInterface() + + Destroys the object. +*/ + +/*! + \fn void QAccessible::initialize() + \internal +*/ + +/*! + \fn void QAccessible::cleanup() + \internal +*/ + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QAccessibleFactoryInterface_iid, QLatin1String("/accessible"))) +#endif + +Q_GLOBAL_STATIC(QList, qAccessibleFactories) + +QAccessible::UpdateHandler QAccessible::updateHandler = 0; +QAccessible::RootObjectHandler QAccessible::rootObjectHandler = 0; + +static bool accessibility_active = false; +static bool cleanupAdded = false; +static void qAccessibleCleanup() +{ + qAccessibleFactories()->clear(); +} + +/*! + \typedef QAccessible::InterfaceFactory + + This is a typedef for a pointer to a function with the following + signature: + + \snippet doc/src/snippets/code/src_gui_accessible_qaccessible.cpp 1 + + The function receives a QString and a QObject pointer, where the + QString is the key identifying the interface. The QObject is used + to pass on to the QAccessibleInterface so that it can hold a reference + to it. + + If the key and the QObject does not have a corresponding + QAccessibleInterface, a null-pointer will be returned. + + Installed factories are called by queryAccessibilityInterface() until + one provides an interface. +*/ + +/*! + \typedef QAccessible::UpdateHandler + + \internal + + A function pointer type. Use a function with this prototype to install + your own update function. + + The function is called by updateAccessibility(). +*/ + +/*! + \typedef QAccessible::RootObjectHandler + + \internal + + A function pointer type. Use a function with this prototype to install + your own root object handler. + + The function is called by setRootObject(). +*/ + +/*! + Installs the InterfaceFactory \a factory. The last factory added + is the first one used by queryAccessibleInterface(). +*/ +void QAccessible::installFactory(InterfaceFactory factory) +{ + if (!factory) + return; + + if (!cleanupAdded) { + qAddPostRoutine(qAccessibleCleanup); + cleanupAdded = true; + } + if (qAccessibleFactories()->contains(factory)) + return; + qAccessibleFactories()->append(factory); +} + +/*! + Removes \a factory from the list of installed InterfaceFactories. +*/ +void QAccessible::removeFactory(InterfaceFactory factory) +{ + qAccessibleFactories()->removeAll(factory); +} + +/*! + \internal + + Installs the given \a handler as the function to be used by + updateAccessibility(), and returns the previously installed + handler. +*/ +QAccessible::UpdateHandler QAccessible::installUpdateHandler(UpdateHandler handler) +{ + UpdateHandler old = updateHandler; + updateHandler = handler; + return old; +} + +/*! + Installs the given \a handler as the function to be used by setRootObject(), + and returns the previously installed handler. +*/ +QAccessible::RootObjectHandler QAccessible::installRootObjectHandler(RootObjectHandler handler) +{ + RootObjectHandler old = rootObjectHandler; + rootObjectHandler = handler; + return old; +} + +/*! + If a QAccessibleInterface implementation exists for the given \a object, + this function returns a pointer to the implementation; otherwise it + returns 0. + + The function calls all installed factory functions (from most + recently installed to least recently installed) until one is found + that provides an interface for the class of \a object. If no + factory can provide an accessibility implementation for the class + the function loads installed accessibility plugins, and tests if + any of the plugins can provide the implementation. + + If no implementation for the object's class is available, the + function tries to find an implementation for the object's parent + class, using the above strategy. + + \warning The caller is responsible for deleting the returned + interface after use. +*/ +QAccessibleInterface *QAccessible::queryAccessibleInterface(QObject *object) +{ + accessibility_active = true; + QAccessibleInterface *iface = 0; + if (!object) + return 0; + + QEvent e(QEvent::AccessibilityPrepare); + QApplication::sendEvent(object, &e); + + const QMetaObject *mo = object->metaObject(); + while (mo) { + const QLatin1String cn(mo->className()); + for (int i = qAccessibleFactories()->count(); i > 0; --i) { + InterfaceFactory factory = qAccessibleFactories()->at(i - 1); + iface = factory(cn, object); + if (iface) + return iface; + } +#ifndef QT_NO_LIBRARY + QAccessibleFactoryInterface *factory = qobject_cast(loader()->instance(cn)); + if (factory) { + iface = factory->create(cn, object); + if (iface) + return iface; + } +#endif + mo = mo->superClass(); + } + + QWidget *widget = qobject_cast(object); + if (widget) + return new QAccessibleWidget(widget); + else if (object == qApp) + return new QAccessibleApplication(); + + return 0; +} + +/*! + Returns true if an accessibility implementation has been requested + during the runtime of the application; otherwise returns false. + + Use this function to prevent potentially expensive notifications via + updateAccessibility(). +*/ +bool QAccessible::isActive() +{ + return accessibility_active; +} + +/*! + \fn void QAccessible::setRootObject(QObject *object) + + Sets the root accessible object of this application to \a object. + All other accessible objects in the application can be reached by the + client using object navigation. + + You should never need to call this function. Qt sets the QApplication + object as the root object immediately before the event loop is entered + in QApplication::exec(). + + Use QAccessible::installRootObjectHandler() to redirect the function + call to a customized handler function. + + \sa queryAccessibleInterface() +*/ + +/*! + \fn void QAccessible::updateAccessibility(QObject *object, int child, Event reason) + + Notifies accessibility clients about a change in \a object's + accessibility information. + + \a reason specifies the cause of the change, for example, + \c ValueChange when the position of a slider has been changed. \a + child is the (1-based) index of the child element that has changed. + When \a child is 0, the object itself has changed. + + Call this function whenever the state of your accessible object or + one of its sub-elements has been changed either programmatically + (e.g. by calling QLabel::setText()) or by user interaction. + + If there are no accessibility tools listening to this event, the + performance penalty for calling this function is small, but if determining + the parameters of the call is expensive you can test isActive() to + avoid unnecessary computations. +*/ + + +/*! + \class QAccessibleInterface + \brief The QAccessibleInterface class defines an interface that exposes information + about accessible objects. + + \ingroup accessibility + + Accessibility tools (also called AT Clients), such as screen readers + or braille displays, require high-level information about + accessible objects in an application. Accessible objects provide + specialized input and output methods, making it possible for users + to use accessibility tools with enabled applications (AT Servers). + + Every element that the user needs to interact with or react to is + an accessible object, and should provide this information. These + are mainly visual objects, such as widgets and widget elements, but + can also be content, such as sounds. + + The AT client uses three basic concepts to acquire information + about any accessible object in an application: + \list + \i \e Properties The client can read information about + accessible objects. In some cases the client can also modify these + properties; such as text in a line edit. + \i \e Actions The client can invoke actions like pressing a button + or . + \i \e{Relationships and Navigation} The client can traverse from one + accessible object to another, using the relationships between objects. + \endlist + + The QAccessibleInterface defines the API for these three concepts. + + \section1 Relationships and Navigation + + The functions childCount() and indexOfChild() return the number of + children of an accessible object and the index a child object has + in its parent. The childAt() function returns the index of a child + at a given position. + + The relationTo() function provides information about how two + different objects relate to each other, and navigate() allows + traversing from one object to another object with a given + relationship. + + \section1 Properties + + The central property of an accessible objects is what role() it + has. Different objects can have the same role, e.g. both the "Add + line" element in a scroll bar and the \c OK button in a dialog have + the same role, "button". The role implies what kind of + interaction the user can perform with the user interface element. + + An object's state() property is a combination of different state + flags and can describe both how the object's state differs from a + "normal" state, e.g. it might be unavailable, and also how it + behaves, e.g. it might be selectable. + + The text() property provides textual information about the object. + An object usually has a name, but can provide extended information + such as a description, help text, or information about any + keyboard accelerators it provides. Some objects allow changing the + text() property through the setText() function, but this + information is in most cases read-only. + + The rect() property provides information about the geometry of an + accessible object. This information is usually only available for + visual objects. + + \section1 Actions and Selection + + To enable the user to interact with an accessible object the + object must expose information about the actions that it can + perform. userActionCount() returns the number of actions supported by + an accessible object, and actionText() returns textual information + about those actions. doAction() invokes an action. + + Objects that support selections can define actions to change the selection. + + \section2 Objects and children + + A QAccessibleInterface provides information about the accessible + object, and can also provide information for the children of that + object if those children don't provide a QAccessibleInterface + implementation themselves. This is practical if the object has + many similar children (e.g. items in a list view), or if the + children are an integral part of the object itself, for example, the + different sections in a scroll bar. + + If an accessible object provides information about its children + through one QAccessibleInterface, the children are referenced + using indexes. The index is 1-based for the children, i.e. 0 + refers to the object itself, 1 to the first child, 2 to the second + child, and so on. + + All functions in QAccessibleInterface that take a child index + relate to the object itself if the index is 0, or to the child + specified. If a child provides its own interface implementation + (which can be retrieved through navigation) asking the parent for + information about that child will usually not succeed. + + \sa QAccessible +*/ + +/*! + \fn bool QAccessibleInterface::isValid() const + + Returns true if all the data necessary to use this interface + implementation is valid (e.g. all pointers are non-null); + otherwise returns false. + + \sa object() +*/ + +/*! + \fn QObject *QAccessibleInterface::object() const + + Returns a pointer to the QObject this interface implementation provides + information for. + + \sa isValid() +*/ + +/*! + \fn int QAccessibleInterface::childCount() const + + Returns the number of children that belong to this object. A child + can provide accessibility information on its own (e.g. a child + widget), or be a sub-element of this accessible object. + + All objects provide this information. + + \sa indexOfChild() +*/ + +/*! + \fn int QAccessibleInterface::indexOfChild(const QAccessibleInterface *child) const + + Returns the 1-based index of the object \a child in this object's + children list, or -1 if \a child is not a child of this object. 0 + is not a possible return value. + + All objects provide this information about their children. + + \sa childCount() +*/ + +/*! + \fn QAccessible::Relation QAccessibleInterface::relationTo(int child, +const QAccessibleInterface *other, int otherChild) const + + Returns the relationship between this object's \a child and the \a + other object's \a otherChild. If \a child is 0 the object's own relation + is returned. + + The returned value indicates the relation of the called object to + the \a other object, e.g. if this object is a child of \a other + the return value will be \c Child. + + The return value is a combination of the bit flags in the + QAccessible::Relation enumeration. + + All objects provide this information. + + \sa indexOfChild(), navigate() +*/ + +/*! + \fn int QAccessibleInterface::childAt(int x, int y) const + + Returns the 1-based index of the child that contains the screen + coordinates (\a x, \a y). This function returns 0 if the point is + positioned on the object itself. If the tested point is outside + the boundaries of the object this function returns -1. + + This function is only relyable for visible objects (invisible + object might not be laid out correctly). + + All visual objects provide this information. + + \sa rect() +*/ + +/*! + \fn int QAccessibleInterface::navigate(RelationFlag relation, int entry, QAccessibleInterface +**target) const + + Navigates from this object to an object that has a relationship + \a relation to this object, and returns the respective object in + \a target. It is the caller's responsibility to delete *\a target + after use. + + If an object is found, \a target is set to point to the object, and + the index of the child of \a target is returned. The return value + is 0 if \a target itself is the requested object. \a target is set + to null if this object is the target object (i.e. the requested + object is a handled by this object). + + If no object is found \a target is set to null, and the return + value is -1. + + The \a entry parameter has two different meanings: + \list + \i \e{Hierarchical and Logical relationships} -- if multiple objects with + the requested relationship exist \a entry specifies which one to + return. \a entry is 1-based, e.g. use 1 to get the first (and + possibly only) object with the requested relationship. + + The following code demonstrates how to use this function to + navigate to the first child of an object: + + \snippet doc/src/snippets/code/src_gui_accessible_qaccessible.cpp 0 + + \i \e{Geometric relationships} -- the index of the child from + which to start navigating in the specified direction. \a entry + can be 0 to navigate to a sibling of this object, or non-null to + navigate within contained children that don't provide their own + accessible information. + \endlist + + Note that the \c Descendent value for \a relation is not supported. + + All objects support navigation. + + \sa relationTo(), childCount() +*/ + +/*! + \fn QString QAccessibleInterface::text(Text t, int child) const + + Returns the value of the text property \a t of the object, or of + the object's child if \a child is not 0. + + The \l Name is a string used by clients to identify, find, or + announce an accessible object for the user. All objects must have + a name that is unique within their container. The name can be + used differently by clients, so the name should both give a + short description of the object and be unique. + + An accessible object's \l Description provides textual information + about an object's visual appearance. The description is primarily + used to provide greater context for vision-impaired users, but is + also used for context searching or other applications. Not all + objects have a description. An "OK" button would not need a + description, but a tool button that shows a picture of a smiley + would. + + The \l Value of an accessible object represents visual information + contained by the object, e.g. the text in a line edit. Usually, + the value can be modified by the user. Not all objects have a + value, e.g. static text labels don't, and some objects have a + state that already is the value, e.g. toggle buttons. + + The \l Help text provides information about the function and + usage of an accessible object. Not all objects provide this + information. + + The \l Accelerator is a keyboard shortcut that activates the + object's default action. A keyboard shortcut is the underlined + character in the text of a menu, menu item or widget, and is + either the character itself, or a combination of this character + and a modifier key like Alt, Ctrl or Shift. Command controls like + tool buttons also have shortcut keys and usually display them in + their tooltip. + + All objects provide a string for \l Name. + + \sa role(), state() +*/ + +/*! + \fn void QAccessibleInterface::setText(Text t, int child, const QString &text) + + Sets the text property \a t of the object, or of the object's + child if \a child is not 0, to \a text. + + Note that the text properties of most objects are read-only. + + \sa text() +*/ + +/*! + \fn QRect QAccessibleInterface::rect(int child) const + + Returns the geometry of the object, or of the object's child if \a child + is not 0. The geometry is in screen coordinates. + + This function is only reliable for visible objects (invisible + objects might not be laid out correctly). + + All visual objects provide this information. + + \sa childAt() +*/ + +/*! + \fn QAccessible::Role QAccessibleInterface::role(int child) const + + Returns the role of the object, or of the object's child if \a child + is not 0. The role of an object is usually static. + + All accessible objects have a role. + + \sa text(), state() +*/ + +/*! + \fn QAccessible::State QAccessibleInterface::state(int child) const + + Returns the current state of the object, or of the object's child if + \a child is not 0. The returned value is a combination of the flags in + the QAccessible::StateFlag enumeration. + + All accessible objects have a state. + + \sa text(), role() +*/ + +/*! + \fn int QAccessibleInterface::userActionCount(int child) const + + Returns the number of custom actions of the object, or of the + object's child if \a child is not 0. + + The \c Action type enumerates predefined actions: these + are not included in the returned value. + + \sa actionText(), doAction() +*/ + +/*! + \fn QString QAccessibleInterface::actionText(int action, Text t, int child) const + + Returns the text property \a t of the action \a action supported by + the object, or of the object's child if \a child is not 0. + + \sa text(), userActionCount() +*/ + +/*! + \fn bool QAccessibleInterface::doAction(int action, int child, const QVariantList ¶ms) + + Asks the object, or the object's \a child if \a child is not 0, to + execute \a action using the parameters, \a params. Returns true if + the action could be executed; otherwise returns false. + + \a action can be a predefined or a custom action. + + \sa userActionCount(), actionText() +*/ + +/*! + \fn QColor QAccessibleInterface::backgroundColor() + \internal +*/ + +/*! + \fn QAccessibleEditableTextInterface *QAccessibleInterface::editableTextInterface() + \internal +*/ + +/*! + \fn QColor QAccessibleInterface::foregroundColor() + \internal +*/ + +/*! + \fn QAccessibleTextInterface *QAccessibleInterface::textInterface() + \internal +*/ + +/*! + \fn QAccessibleValueInterface *QAccessibleInterface::valueInterface() + \internal +*/ + +/*! + \fn QAccessibleTableInterface *QAccessibleInterface::tableInterface() + \internal +*/ + +/*! + \fn QAccessibleActionInterface *QAccessibleInterface::actionInterface() + \internal +*/ + +/*! + \fn QAccessibleImageInterface *QAccessibleInterface::imageInterface() + \internal +*/ + +/*! + \class QAccessibleEvent + \brief The QAccessibleEvent class is used to query addition + accessibility information about complex widgets. + + The event can be of type QEvent::AccessibilityDescription or + QEvent::AccessibilityHelp. + + Some QAccessibleInterface implementations send QAccessibleEvents + to the widget they wrap to obtain the description or help text of + a widget or of its children. The widget can answer by calling + setValue() with the requested information. + + The default QWidget::event() implementation simply sets the text + to be the widget's \l{QWidget::toolTip}{tooltip} (for \l + AccessibilityDescription event) or its + \l{QWidget::whatsThis}{"What's This?" text} (for \l + AccessibilityHelp event). + + \ingroup accessibility + \ingroup events +*/ + +/*! + \fn QAccessibleEvent::QAccessibleEvent(Type type, int child) + + Constructs an accessibility event of the given \a type, which + must be QEvent::AccessibilityDescription or + QEvent::AccessibilityHelp. + + \a child is the (1-based) index of the child to which the request + applies. If \a child is 0, the request is for the widget itself. + + \sa child() +*/ + +/*! + \fn int QAccessibleEvent::child() const + + Returns the (1-based) index of the child to which the request + applies. If the child is 0, the request is for the widget itself. +*/ + +/*! + \fn QString QAccessibleEvent::value() const + + Returns the text set using setValue(). + + \sa setValue() +*/ + +/*! + \fn void QAccessibleEvent::setValue(const QString &text) + + Set the description or help text for the given child() to \a + text, thereby answering the request. + + \sa value() +*/ + +/*! + \since 4.2 + + Invokes a \a method on \a child with the given parameters \a params + and returns the result of the operation as QVariant. + + Note that the type of the returned QVariant depends on the action. + + Returns an invalid QVariant if the object doesn't support the action. +*/ +QVariant QAccessibleInterface::invokeMethod(Method method, int child, const QVariantList ¶ms) +{ + if (!(state(0) & HasInvokeExtension)) + return QVariant(); + + return static_cast(this)->invokeMethodEx(method, child, params); +} + +QVariant QAccessibleInterfaceEx::virtual_hook(const QVariant &) +{ + return QVariant(); +} + +/*! \internal */ +QAccessible2Interface *QAccessibleInterface::cast_helper(QAccessible2::InterfaceType t) +{ + if (state(0) & HasInvokeExtension) + return static_cast(this)->interface_cast(t); + return 0; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/accessible/qaccessible.h b/src/gui/accessible/qaccessible.h new file mode 100644 index 0000000000..c71417352b --- /dev/null +++ b/src/gui/accessible/qaccessible.h @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLE_H +#define QACCESSIBLE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +class QAccessibleInterface; + +class Q_GUI_EXPORT QAccessible +{ +public: + enum Event { + SoundPlayed = 0x0001, + Alert = 0x0002, + ForegroundChanged = 0x0003, + MenuStart = 0x0004, + MenuEnd = 0x0005, + PopupMenuStart = 0x0006, + PopupMenuEnd = 0x0007, + ContextHelpStart = 0x000C, + ContextHelpEnd = 0x000D, + DragDropStart = 0x000E, + DragDropEnd = 0x000F, + DialogStart = 0x0010, + DialogEnd = 0x0011, + ScrollingStart = 0x0012, + ScrollingEnd = 0x0013, + + MenuCommand = 0x0018, + + ObjectCreated = 0x8000, + ObjectDestroyed = 0x8001, + ObjectShow = 0x8002, + ObjectHide = 0x8003, + ObjectReorder = 0x8004, + Focus = 0x8005, + Selection = 0x8006, + SelectionAdd = 0x8007, + SelectionRemove = 0x8008, + SelectionWithin = 0x8009, + StateChanged = 0x800A, + LocationChanged = 0x800B, + NameChanged = 0x800C, + DescriptionChanged = 0x800D, + ValueChanged = 0x800E, + ParentChanged = 0x800F, + HelpChanged = 0x80A0, + DefaultActionChanged = 0x80B0, + AcceleratorChanged = 0x80C0 + }; + + enum StateFlag { + Normal = 0x00000000, + Unavailable = 0x00000001, + Selected = 0x00000002, + Focused = 0x00000004, + Pressed = 0x00000008, + Checked = 0x00000010, + Mixed = 0x00000020, + ReadOnly = 0x00000040, + HotTracked = 0x00000080, + DefaultButton = 0x00000100, + Expanded = 0x00000200, + Collapsed = 0x00000400, + Busy = 0x00000800, + // Floating = 0x00001000, + Marqueed = 0x00002000, + Animated = 0x00004000, + Invisible = 0x00008000, + Offscreen = 0x00010000, + Sizeable = 0x00020000, + Movable = 0x00040000, +#ifdef QT3_SUPPORT + Moveable = Movable, +#endif + SelfVoicing = 0x00080000, + Focusable = 0x00100000, + Selectable = 0x00200000, + Linked = 0x00400000, + Traversed = 0x00800000, + MultiSelectable = 0x01000000, + ExtSelectable = 0x02000000, + //AlertLow = 0x04000000, + //AlertMedium = 0x08000000, + //AlertHigh = 0x10000000, /* reused for HasInvokeExtension */ + Protected = 0x20000000, + HasPopup = 0x40000000, + Modal = 0x80000000, + + HasInvokeExtension = 0x10000000 // internal + }; + Q_DECLARE_FLAGS(State, StateFlag) + + enum Role { + NoRole = 0x00000000, + TitleBar = 0x00000001, + MenuBar = 0x00000002, + ScrollBar = 0x00000003, + Grip = 0x00000004, + Sound = 0x00000005, + Cursor = 0x00000006, + Caret = 0x00000007, + AlertMessage = 0x00000008, + Window = 0x00000009, + Client = 0x0000000A, + PopupMenu = 0x0000000B, + MenuItem = 0x0000000C, + ToolTip = 0x0000000D, + Application = 0x0000000E, + Document = 0x0000000F, + Pane = 0x00000010, + Chart = 0x00000011, + Dialog = 0x00000012, + Border = 0x00000013, + Grouping = 0x00000014, + Separator = 0x00000015, + ToolBar = 0x00000016, + StatusBar = 0x00000017, + Table = 0x00000018, + ColumnHeader = 0x00000019, + RowHeader = 0x0000001A, + Column = 0x0000001B, + Row = 0x0000001C, + Cell = 0x0000001D, + Link = 0x0000001E, + HelpBalloon = 0x0000001F, + Assistant = 0x00000020, + List = 0x00000021, + ListItem = 0x00000022, + Tree = 0x00000023, + TreeItem = 0x00000024, + PageTab = 0x00000025, + PropertyPage = 0x00000026, + Indicator = 0x00000027, + Graphic = 0x00000028, + StaticText = 0x00000029, + EditableText = 0x0000002A, // Editable, selectable, etc. + PushButton = 0x0000002B, + CheckBox = 0x0000002C, + RadioButton = 0x0000002D, + ComboBox = 0x0000002E, + // DropList = 0x0000002F, + ProgressBar = 0x00000030, + Dial = 0x00000031, + HotkeyField = 0x00000032, + Slider = 0x00000033, + SpinBox = 0x00000034, + Canvas = 0x00000035, + Animation = 0x00000036, + Equation = 0x00000037, + ButtonDropDown = 0x00000038, + ButtonMenu = 0x00000039, + ButtonDropGrid = 0x0000003A, + Whitespace = 0x0000003B, + PageTabList = 0x0000003C, + Clock = 0x0000003D, + Splitter = 0x0000003E, + // Additional Qt roles where enum value does not map directly to MSAA: + LayeredPane = 0x0000003F, + UserRole = 0x0000ffff + }; + + enum Text { + Name = 0, + Description, + Value, + Help, + Accelerator, + UserText = 0x0000ffff + }; + + enum RelationFlag { + Unrelated = 0x00000000, + Self = 0x00000001, + Ancestor = 0x00000002, + Child = 0x00000004, + Descendent = 0x00000008, + Sibling = 0x00000010, + HierarchyMask = 0x000000ff, + + Up = 0x00000100, + Down = 0x00000200, + Left = 0x00000400, + Right = 0x00000800, + Covers = 0x00001000, + Covered = 0x00002000, + GeometryMask = 0x0000ff00, + + FocusChild = 0x00010000, + Label = 0x00020000, + Labelled = 0x00040000, + Controller = 0x00080000, + Controlled = 0x00100000, + LogicalMask = 0x00ff0000 + }; + Q_DECLARE_FLAGS(Relation, RelationFlag) + + enum Action { + DefaultAction = 0, + Press = -1, + FirstStandardAction = Press, + SetFocus = -2, + Increase = -3, + Decrease = -4, + Accept = -5, + Cancel = -6, + Select = -7, + ClearSelection = -8, + RemoveSelection = -9, + ExtendSelection = -10, + AddToSelection = -11, + LastStandardAction = AddToSelection + }; + + enum Method { + ListSupportedMethods = 0, + SetCursorPosition = 1, + GetCursorPosition = 2, + ForegroundColor = 3, + BackgroundColor = 4 + }; + + typedef QAccessibleInterface*(*InterfaceFactory)(const QString &key, QObject*); + typedef void(*UpdateHandler)(QObject*, int who, Event reason); + typedef void(*RootObjectHandler)(QObject*); + + static void installFactory(InterfaceFactory); + static void removeFactory(InterfaceFactory); + static UpdateHandler installUpdateHandler(UpdateHandler); + static RootObjectHandler installRootObjectHandler(RootObjectHandler); + + static QAccessibleInterface *queryAccessibleInterface(QObject *); + static void updateAccessibility(QObject *, int who, Event reason); + static bool isActive(); + static void setRootObject(QObject*); + + static void initialize(); + static void cleanup(); + +private: + static UpdateHandler updateHandler; + static RootObjectHandler rootObjectHandler; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAccessible::State) +Q_DECLARE_OPERATORS_FOR_FLAGS(QAccessible::Relation) +QT_END_NAMESPACE +Q_DECLARE_METATYPE(QSet) +QT_BEGIN_NAMESPACE + +namespace QAccessible2 +{ + enum InterfaceType + { + TextInterface, + EditableTextInterface, + ValueInterface, + TableInterface, + ActionInterface, + ImageInterface + }; +} + +class QAccessible2Interface; +class QAccessibleTextInterface; +class QAccessibleEditableTextInterface; +class QAccessibleValueInterface; +class QAccessibleTableInterface; +class QAccessibleActionInterface; +class QAccessibleImageInterface; + +class Q_GUI_EXPORT QAccessibleInterface : public QAccessible +{ +public: + virtual ~QAccessibleInterface() {} + // check for valid pointers + virtual bool isValid() const = 0; + virtual QObject *object() const = 0; + + // hierarchy + virtual int childCount() const = 0; + virtual int indexOfChild(const QAccessibleInterface *) const = 0; + + // relations + virtual Relation relationTo(int child, const QAccessibleInterface *other, + int otherChild) const = 0; + virtual int childAt(int x, int y) const = 0; + + // navigation + virtual int navigate(RelationFlag relation, int index, QAccessibleInterface **iface) const = 0; + + // properties and state + virtual QString text(Text t, int child) const = 0; + virtual void setText(Text t, int child, const QString &text) = 0; + virtual QRect rect(int child) const = 0; + virtual Role role(int child) const = 0; + virtual State state(int child) const = 0; + + // action + virtual int userActionCount(int child) const = 0; + virtual QString actionText(int action, Text t, int child) const = 0; + virtual bool doAction(int action, int child, const QVariantList ¶ms = QVariantList()) = 0; + + QVariant invokeMethod(Method method, int child = 0, + const QVariantList ¶ms = QVariantList()); + + inline QSet supportedMethods() + { return qvariant_cast >(invokeMethod(ListSupportedMethods)); } + + inline QColor foregroundColor() + { return qvariant_cast(invokeMethod(ForegroundColor)); } + + inline QColor backgroundColor() + { return qvariant_cast(invokeMethod(BackgroundColor)); } + + inline QAccessibleTextInterface *textInterface() + { return reinterpret_cast(cast_helper(QAccessible2::TextInterface)); } + + inline QAccessibleEditableTextInterface *editableTextInterface() + { return reinterpret_cast(cast_helper(QAccessible2::EditableTextInterface)); } + + inline QAccessibleValueInterface *valueInterface() + { return reinterpret_cast(cast_helper(QAccessible2::ValueInterface)); } + + inline QAccessibleTableInterface *tableInterface() + { return reinterpret_cast(cast_helper(QAccessible2::TableInterface)); } + + inline QAccessibleActionInterface *actionInterface() + { return reinterpret_cast(cast_helper(QAccessible2::ActionInterface)); } + + inline QAccessibleImageInterface *imageInterface() + { return reinterpret_cast(cast_helper(QAccessible2::ImageInterface)); } + +private: + QAccessible2Interface *cast_helper(QAccessible2::InterfaceType); +}; + +class Q_GUI_EXPORT QAccessibleInterfaceEx: public QAccessibleInterface +{ +public: + virtual QVariant invokeMethodEx(Method method, int child, const QVariantList ¶ms) = 0; + virtual QVariant virtual_hook(const QVariant &data); + virtual QAccessible2Interface *interface_cast(QAccessible2::InterfaceType) + { return 0; } +}; + + +class Q_GUI_EXPORT QAccessibleEvent : public QEvent +{ +public: + inline QAccessibleEvent(Type type, int child); + inline int child() const { return c; } + inline QString value() const { return val; } + inline void setValue(const QString &aText) { val = aText; } + +private: + int c; + QString val; +}; + +inline QAccessibleEvent::QAccessibleEvent(Type atype, int achild) + : QEvent(atype), c(achild) {} + +#define QAccessibleInterface_iid "com.trolltech.Qt.QAccessibleInterface" +Q_DECLARE_INTERFACE(QAccessibleInterface, QAccessibleInterface_iid) + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACCESSIBLE_H diff --git a/src/gui/accessible/qaccessible2.cpp b/src/gui/accessible/qaccessible2.cpp new file mode 100644 index 0000000000..36187f5315 --- /dev/null +++ b/src/gui/accessible/qaccessible2.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessible2.h" +#include "qapplication.h" +#include "qclipboard.h" + +#ifndef QT_NO_ACCESSIBILITY + +QT_BEGIN_NAMESPACE + +/*! + \namespace QAccessible2 + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessible2 namespace defines constants relating to + IAccessible2-based interfaces + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleTextInterface + + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleTextInterface class implements support for + the IAccessibleText interface. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleEditableTextInterface + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleEditableTextInterface class implements support for + the IAccessibleEditableText interface. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleSimpleEditableTextInterface + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleSimpleEditableTextInterface class is a convenience class for + text-based widgets. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleValueInterface + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleValueInterface class implements support for + the IAccessibleValue interface. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleActionInterface + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleActionInterface class implements support for + the IAccessibleAction interface. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +/*! + \class QAccessibleImageInterface + \ingroup accessibility + \internal + \preliminary + + \brief The QAccessibleImageInterface class implements support for + the IAccessibleImage interface. + + \link http://www.linux-foundation.org/en/Accessibility/IAccessible2 IAccessible2 Specification \endlink +*/ + +QAccessibleSimpleEditableTextInterface::QAccessibleSimpleEditableTextInterface( + QAccessibleInterface *accessibleInterface) + : iface(accessibleInterface) +{ + Q_ASSERT(iface); +} + +#ifndef QT_NO_CLIPBOARD +static QString textForRange(QAccessibleInterface *iface, int startOffset, int endOffset) +{ + return iface->text(QAccessible::Value, 0).mid(startOffset, endOffset - startOffset); +} +#endif + +void QAccessibleSimpleEditableTextInterface::copyText(int startOffset, int endOffset) +{ +#ifdef QT_NO_CLIPBOARD + Q_UNUSED(startOffset); + Q_UNUSED(endOffset); +#else + QApplication::clipboard()->setText(textForRange(iface, startOffset, endOffset)); +#endif +} + +void QAccessibleSimpleEditableTextInterface::deleteText(int startOffset, int endOffset) +{ + QString txt = iface->text(QAccessible::Value, 0); + txt.remove(startOffset, endOffset - startOffset); + iface->setText(QAccessible::Value, 0, txt); +} + +void QAccessibleSimpleEditableTextInterface::insertText(int offset, const QString &text) +{ + QString txt = iface->text(QAccessible::Value, 0); + txt.insert(offset, text); + iface->setText(QAccessible::Value, 0, txt); +} + +void QAccessibleSimpleEditableTextInterface::cutText(int startOffset, int endOffset) +{ +#ifdef QT_NO_CLIPBOARD + Q_UNUSED(startOffset); + Q_UNUSED(endOffset); +#else + QString sub = textForRange(iface, startOffset, endOffset); + deleteText(startOffset, endOffset); + QApplication::clipboard()->setText(sub); +#endif +} + +void QAccessibleSimpleEditableTextInterface::pasteText(int offset) +{ +#ifdef QT_NO_CLIPBOARD + Q_UNUSED(offset); +#else + QString txt = iface->text(QAccessible::Value, 0); + txt.insert(offset, QApplication::clipboard()->text()); + iface->setText(QAccessible::Value, 0, txt); +#endif +} + +void QAccessibleSimpleEditableTextInterface::replaceText(int startOffset, int endOffset, const QString &text) +{ + QString txt = iface->text(QAccessible::Value, 0); + txt.replace(startOffset, endOffset - startOffset, text); + iface->setText(QAccessible::Value, 0, txt); +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessible2.h b/src/gui/accessible/qaccessible2.h new file mode 100644 index 0000000000..65ab7166c6 --- /dev/null +++ b/src/gui/accessible/qaccessible2.h @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLE2_H +#define QACCESSIBLE2_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +namespace QAccessible2 +{ + enum CoordinateType + { + RelativeToScreen = 0, + RelativeToParent = 1 + }; + + enum BoundaryType { + CharBoundary, + WordBoundary, + SentenceBoundary, + ParagraphBoundary, + LineBoundary, + NoBoundary + }; +} + +class Q_GUI_EXPORT QAccessible2Interface +{ +public: + virtual ~QAccessible2Interface() {} +}; + +// catch-all functions. If an accessible class doesn't implement interface T, return 0 +inline QAccessible2Interface *qAccessibleValueCastHelper() { return 0; } +inline QAccessible2Interface *qAccessibleTextCastHelper() { return 0; } +inline QAccessible2Interface *qAccessibleEditableTextCastHelper() { return 0; } +inline QAccessible2Interface *qAccessibleTableCastHelper() { return 0; } +inline QAccessible2Interface *qAccessibleActionCastHelper() { return 0; } +inline QAccessible2Interface *qAccessibleImageCastHelper() { return 0; } + +#define Q_ACCESSIBLE_OBJECT \ + public: \ + QAccessible2Interface *interface_cast(QAccessible2::InterfaceType t) \ + { \ + switch (t) { \ + case QAccessible2::TextInterface: \ + return qAccessibleTextCastHelper(); \ + case QAccessible2::EditableTextInterface: \ + return qAccessibleEditableTextCastHelper(); \ + case QAccessible2::ValueInterface: \ + return qAccessibleValueCastHelper(); \ + case QAccessible2::TableInterface: \ + return qAccessibleTableCastHelper(); \ + case QAccessible2::ActionInterface: \ + return qAccessibleActionCastHelper(); \ + case QAccessible2::ImageInterface: \ + return qAccessibleImageCastHelper(); \ + } \ + return 0; \ + } \ + private: + +class Q_GUI_EXPORT QAccessibleTextInterface: public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleTextCastHelper() { return this; } + + virtual ~QAccessibleTextInterface() {} + + virtual void addSelection(int startOffset, int endOffset) = 0; + virtual QString attributes(int offset, int *startOffset, int *endOffset) = 0; + virtual int cursorPosition() = 0; + virtual QRect characterRect(int offset, QAccessible2::CoordinateType coordType) = 0; + virtual int selectionCount() = 0; + virtual int offsetAtPoint(const QPoint &point, QAccessible2::CoordinateType coordType) = 0; + virtual void selection(int selectionIndex, int *startOffset, int *endOffset) = 0; + virtual QString text(int startOffset, int endOffset) = 0; + virtual QString textBeforeOffset (int offset, QAccessible2::BoundaryType boundaryType, + int *startOffset, int *endOffset) = 0; + virtual QString textAfterOffset(int offset, QAccessible2::BoundaryType boundaryType, + int *startOffset, int *endOffset) = 0; + virtual QString textAtOffset(int offset, QAccessible2::BoundaryType boundaryType, + int *startOffset, int *endOffset) = 0; + virtual void removeSelection(int selectionIndex) = 0; + virtual void setCursorPosition(int position) = 0; + virtual void setSelection(int selectionIndex, int startOffset, int endOffset) = 0; + virtual int characterCount() = 0; + virtual void scrollToSubstring(int startIndex, int endIndex) = 0; +}; + +class Q_GUI_EXPORT QAccessibleEditableTextInterface: public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleEditableTextCastHelper() { return this; } + + virtual ~QAccessibleEditableTextInterface() {} + + virtual void copyText(int startOffset, int endOffset) = 0; + virtual void deleteText(int startOffset, int endOffset) = 0; + virtual void insertText(int offset, const QString &text) = 0; + virtual void cutText(int startOffset, int endOffset) = 0; + virtual void pasteText(int offset) = 0; + virtual void replaceText(int startOffset, int endOffset, const QString &text) = 0; + virtual void setAttributes(int startOffset, int endOffset, const QString &attributes) = 0; +}; + +class Q_GUI_EXPORT QAccessibleSimpleEditableTextInterface: public QAccessibleEditableTextInterface +{ +public: + QAccessibleSimpleEditableTextInterface(QAccessibleInterface *accessibleInterface); + + void copyText(int startOffset, int endOffset); + void deleteText(int startOffset, int endOffset); + void insertText(int offset, const QString &text); + void cutText(int startOffset, int endOffset); + void pasteText(int offset); + void replaceText(int startOffset, int endOffset, const QString &text); + inline void setAttributes(int, int, const QString &) {} + +private: + QAccessibleInterface *iface; +}; + +class Q_GUI_EXPORT QAccessibleValueInterface: public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleValueCastHelper() { return this; } + + virtual ~QAccessibleValueInterface() {} + + virtual QVariant currentValue() = 0; + virtual void setCurrentValue(const QVariant &value) = 0; + virtual QVariant maximumValue() = 0; + virtual QVariant minimumValue() = 0; +}; + +class Q_GUI_EXPORT QAccessibleTableInterface: public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleTableCastHelper() { return this; } + + virtual QAccessibleInterface *accessibleAt(int row, int column) = 0; + virtual QAccessibleInterface *caption() = 0; + virtual int childIndex(int rowIndex, int columnIndex) = 0; + virtual QString columnDescription(int column) = 0; + virtual int columnSpan(int row, int column) = 0; + virtual QAccessibleInterface *columnHeader() = 0; + virtual int columnIndex(int childIndex) = 0; + virtual int columnCount() = 0; + virtual int rowCount() = 0; + virtual int selectedColumnCount() = 0; + virtual int selectedRowCount() = 0; + virtual QString rowDescription(int row) = 0; + virtual int rowSpan(int row, int column) = 0; + virtual QAccessibleInterface *rowHeader() = 0; + virtual int rowIndex(int childIndex) = 0; + virtual int selectedRows(int maxRows, QList *rows) = 0; + virtual int selectedColumns(int maxColumns, QList *columns) = 0; + virtual QAccessibleInterface *summary() = 0; + virtual bool isColumnSelected(int column) = 0; + virtual bool isRowSelected(int row) = 0; + virtual bool isSelected(int row, int column) = 0; + virtual void selectRow(int row) = 0; + virtual void selectColumn(int column) = 0; + virtual void unselectRow(int row) = 0; + virtual void unselectColumn(int column) = 0; + virtual void cellAtIndex(int index, int *row, int *column, int *rowSpan, + int *columnSpan, bool *isSelected) = 0; +}; + +class Q_GUI_EXPORT QAccessibleActionInterface : public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleActionCastHelper() { return this; } + + virtual int actionCount() = 0; + virtual void doAction(int actionIndex) = 0; + virtual QString description(int actionIndex) = 0; + virtual QString name(int actionIndex) = 0; + virtual QString localizedName(int actionIndex) = 0; + virtual QStringList keyBindings(int actionIndex) = 0; +}; + +class Q_GUI_EXPORT QAccessibleImageInterface : public QAccessible2Interface +{ +public: + inline QAccessible2Interface *qAccessibleImageCastHelper() { return this; } + + virtual QString imageDescription() = 0; + virtual QSize imageSize() = 0; + virtual QRect imagePosition(QAccessible2::CoordinateType coordType) = 0; +}; + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/accessible/qaccessible_mac.mm b/src/gui/accessible/qaccessible_mac.mm new file mode 100644 index 0000000000..d01c1c9733 --- /dev/null +++ b/src/gui/accessible/qaccessible_mac.mm @@ -0,0 +1,2469 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessible.h" + +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible_mac_p.h" +#include "qhash.h" +#include "qset.h" +#include "qpointer.h" +#include "qapplication.h" +#include "qmainwindow.h" +#include "qtextdocument.h" +#include "qdebug.h" +#include "qabstractslider.h" +#include "qsplitter.h" +#include "qtabwidget.h" +#include "qlistview.h" +#include "qtableview.h" +#include "qdockwidget.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + Set up platform defines. There is a one-to-one correspondence between the + Carbon and Cocoa roles and attributes, but the prefix and type changes. +*/ +#ifdef QT_MAC_USE_COCOA +typedef NSString * const QAXRoleType; +#define QAXApplicationRole NSAccessibilityApplicationRole +#define QAXButtonRole NSAccessibilityButtonRole +#define QAXCancelAction NSAccessibilityCancelAction +#define QAXCheckBoxRole NSAccessibilityCheckBoxRole +#define QAXChildrenAttribute NSAccessibilityChildrenAttribute +#define QAXCloseButtonAttribute NSAccessibilityCloseButtonAttribute +#define QAXCloseButtonAttribute NSAccessibilityCloseButtonAttribute +#define QAXColumnRole NSAccessibilityColumnRole +#define QAXConfirmAction NSAccessibilityConfirmAction +#define QAXContentsAttribute NSAccessibilityContentsAttribute +#define QAXDecrementAction NSAccessibilityDecrementAction +#define QAXDecrementArrowSubrole NSAccessibilityDecrementArrowSubrole +#define QAXDecrementPageSubrole NSAccessibilityDecrementPageSubrole +#define QAXDescriptionAttribute NSAccessibilityDescriptionAttribute +#define QAXEnabledAttribute NSAccessibilityEnabledAttribute +#define QAXExpandedAttribute NSAccessibilityExpandedAttribute +#define QAXFocusedAttribute NSAccessibilityFocusedAttribute +#define QAXFocusedUIElementChangedNotification NSAccessibilityFocusedUIElementChangedNotification +#define QAXFocusedWindowChangedNotification NSAccessibilityFocusedWindowChangedNotification +#define QAXGroupRole NSAccessibilityGroupRole +#define QAXGrowAreaAttribute NSAccessibilityGrowAreaAttribute +#define QAXGrowAreaRole NSAccessibilityGrowAreaRole +#define QAXHelpAttribute NSAccessibilityHelpAttribute +#define QAXHorizontalOrientationValue NSAccessibilityHorizontalOrientationValue +#define QAXHorizontalScrollBarAttribute NSAccessibilityHorizontalScrollBarAttribute +#define QAXIncrementAction NSAccessibilityIncrementAction +#define QAXIncrementArrowSubrole NSAccessibilityIncrementArrowSubrole +#define QAXIncrementPageSubrole NSAccessibilityIncrementPageSubrole +#define QAXIncrementorRole NSAccessibilityIncrementorRole +#define QAXLinkedUIElementsAttribute NSAccessibilityLinkedUIElementsAttribute +#define QAXListRole NSAccessibilityListRole +#define QAXMainAttribute NSAccessibilityMainAttribute +#define QAXMaxValueAttribute NSAccessibilityMaxValueAttribute +#define QAXMenuBarRole NSAccessibilityMenuBarRole +#define QAXMenuButtonRole NSAccessibilityMenuButtonRole +#define QAXMenuClosedNotification NSAccessibilityMenuClosedNotification +#define QAXMenuItemRole NSAccessibilityMenuItemRole +#define QAXMenuOpenedNotification NSAccessibilityMenuOpenedNotification +#define QAXMenuRole NSAccessibilityMenuRole +#define QAXMinValueAttribute NSAccessibilityMinValueAttribute +#define QAXMinimizeButtonAttribute NSAccessibilityMinimizeButtonAttribute +#define QAXMinimizedAttribute NSAccessibilityMinimizedAttribute +#define QAXNextContentsAttribute NSAccessibilityNextContentsAttribute +#define QAXOrientationAttribute NSAccessibilityOrientationAttribute +#define QAXParentAttribute NSAccessibilityParentAttribute +#define QAXPickAction NSAccessibilityPickAction +#define QAXPopUpButtonRole NSAccessibilityPopUpButtonRole +#define QAXPositionAttribute NSAccessibilityPositionAttribute +#define QAXPressAction NSAccessibilityPressAction +#define QAXPreviousContentsAttribute NSAccessibilityPreviousContentsAttribute +#define QAXProgressIndicatorRole NSAccessibilityProgressIndicatorRole +#define QAXRadioButtonRole NSAccessibilityRadioButtonRole +#define QAXRoleAttribute NSAccessibilityRoleAttribute +#define QAXRoleDescriptionAttribute NSAccessibilityRoleDescriptionAttribute +#define QAXRowRole NSAccessibilityRowRole +#define QAXRowsAttribute NSAccessibilityRowsAttribute +#define QAXScrollAreaRole NSAccessibilityScrollAreaRole +#define QAXScrollBarRole NSAccessibilityScrollBarRole +#define QAXSelectedAttribute NSAccessibilitySelectedAttribute +#define QAXSelectedChildrenAttribute NSAccessibilitySelectedChildrenAttribute +#define QAXSelectedRowsAttribute NSAccessibilitySelectedRowsAttribute +#define QAXSizeAttribute NSAccessibilitySizeAttribute +#define QAXSliderRole NSAccessibilitySliderRole +#define QAXSplitGroupRole NSAccessibilitySplitGroupRole +#define QAXSplitterRole NSAccessibilitySplitterRole +#define QAXSplittersAttribute NSAccessibilitySplittersAttribute +#define QAXStaticTextRole NSAccessibilityStaticTextRole +#define QAXSubroleAttribute NSAccessibilitySubroleAttribute +#define QAXSubroleAttribute NSAccessibilitySubroleAttribute +#define QAXTabGroupRole NSAccessibilityTabGroupRole +#define QAXTableRole NSAccessibilityTableRole +#define QAXTabsAttribute NSAccessibilityTabsAttribute +#define QAXTextFieldRole NSAccessibilityTextFieldRole +#define QAXTitleAttribute NSAccessibilityTitleAttribute +#define QAXTitleUIElementAttribute NSAccessibilityTitleUIElementAttribute +#define QAXToolbarButtonAttribute NSAccessibilityToolbarButtonAttribute +#define QAXToolbarRole NSAccessibilityToolbarRole +#define QAXTopLevelUIElementAttribute NSAccessibilityTopLevelUIElementAttribute +#define QAXUnknownRole NSAccessibilityUnknownRole +#define QAXValueAttribute NSAccessibilityValueAttribute +#define QAXValueChangedNotification NSAccessibilityValueChangedNotification +#define QAXValueIndicatorRole NSAccessibilityValueIndicatorRole +#define QAXVerticalOrientationValue NSAccessibilityVerticalOrientationValue +#define QAXVerticalScrollBarAttribute NSAccessibilityVerticalScrollBarAttribute +#define QAXVisibleRowsAttribute NSAccessibilityVisibleRowsAttribute +#define QAXWindowAttribute NSAccessibilityWindowAttribute +#define QAXWindowCreatedNotification NSAccessibilityWindowCreatedNotification +#define QAXWindowMovedNotification NSAccessibilityWindowMovedNotification +#define QAXWindowRole NSAccessibilityWindowRole +#define QAXZoomButtonAttribute NSAccessibilityZoomButtonAttribute +#else +typedef CFStringRef const QAXRoleType; +#define QAXApplicationRole kAXApplicationRole +#define QAXButtonRole kAXButtonRole +#define QAXCancelAction kAXCancelAction +#define QAXCheckBoxRole kAXCheckBoxRole +#define QAXChildrenAttribute kAXChildrenAttribute +#define QAXCloseButtonAttribute kAXCloseButtonAttribute +#define QAXColumnRole kAXColumnRole +#define QAXConfirmAction kAXConfirmAction +#define QAXContentsAttribute kAXContentsAttribute +#define QAXDecrementAction kAXDecrementAction +#define QAXDecrementArrowSubrole kAXDecrementArrowSubrole +#define QAXDecrementPageSubrole kAXDecrementPageSubrole +#define QAXDescriptionAttribute kAXDescriptionAttribute +#define QAXEnabledAttribute kAXEnabledAttribute +#define QAXExpandedAttribute kAXExpandedAttribute +#define QAXFocusedAttribute kAXFocusedAttribute +#define QAXFocusedUIElementChangedNotification kAXFocusedUIElementChangedNotification +#define QAXFocusedWindowChangedNotification kAXFocusedWindowChangedNotification +#define QAXGroupRole kAXGroupRole +#define QAXGrowAreaAttribute kAXGrowAreaAttribute +#define QAXGrowAreaRole kAXGrowAreaRole +#define QAXHelpAttribute kAXHelpAttribute +#define QAXHorizontalOrientationValue kAXHorizontalOrientationValue +#define QAXHorizontalScrollBarAttribute kAXHorizontalScrollBarAttribute +#define QAXIncrementAction kAXIncrementAction +#define QAXIncrementArrowSubrole kAXIncrementArrowSubrole +#define QAXIncrementPageSubrole kAXIncrementPageSubrole +#define QAXIncrementorRole kAXIncrementorRole +#define QAXLinkedUIElementsAttribute kAXLinkedUIElementsAttribute +#define QAXListRole kAXListRole +#define QAXMainAttribute kAXMainAttribute +#define QAXMaxValueAttribute kAXMaxValueAttribute +#define QAXMenuBarRole kAXMenuBarRole +#define QAXMenuButtonRole kAXMenuButtonRole +#define QAXMenuClosedNotification kAXMenuClosedNotification +#define QAXMenuItemRole kAXMenuItemRole +#define QAXMenuOpenedNotification kAXMenuOpenedNotification +#define QAXMenuRole kAXMenuRole +#define QAXMinValueAttribute kAXMinValueAttribute +#define QAXMinimizeButtonAttribute kAXMinimizeButtonAttribute +#define QAXMinimizedAttribute kAXMinimizedAttribute +#define QAXNextContentsAttribute kAXNextContentsAttribute +#define QAXOrientationAttribute kAXOrientationAttribute +#define QAXParentAttribute kAXParentAttribute +#define QAXPickAction kAXPickAction +#define QAXPopUpButtonRole kAXPopUpButtonRole +#define QAXPositionAttribute kAXPositionAttribute +#define QAXPressAction kAXPressAction +#define QAXPreviousContentsAttribute kAXPreviousContentsAttribute +#define QAXProgressIndicatorRole kAXProgressIndicatorRole +#define QAXRadioButtonRole kAXRadioButtonRole +#define QAXRoleAttribute kAXRoleAttribute +#define QAXRoleDescriptionAttribute kAXRoleDescriptionAttribute +#define QAXRowRole kAXRowRole +#define QAXRowsAttribute kAXRowsAttribute +#define QAXScrollAreaRole kAXScrollAreaRole +#define QAXScrollBarRole kAXScrollBarRole +#define QAXSelectedAttribute kAXSelectedAttribute +#define QAXSelectedChildrenAttribute kAXSelectedChildrenAttribute +#define QAXSelectedRowsAttribute kAXSelectedRowsAttribute +#define QAXSizeAttribute kAXSizeAttribute +#define QAXSliderRole kAXSliderRole +#define QAXSplitGroupRole kAXSplitGroupRole +#define QAXSplitterRole kAXSplitterRole +#define QAXSplittersAttribute kAXSplittersAttribute +#define QAXStaticTextRole kAXStaticTextRole +#define QAXSubroleAttribute kAXSubroleAttribute +#define QAXTabGroupRole kAXTabGroupRole +#define QAXTableRole kAXTableRole +#define QAXTabsAttribute kAXTabsAttribute +#define QAXTextFieldRole kAXTextFieldRole +#define QAXTitleAttribute kAXTitleAttribute +#define QAXTitleUIElementAttribute kAXTitleUIElementAttribute +#define QAXToolbarButtonAttribute kAXToolbarButtonAttribute +#define QAXToolbarRole kAXToolbarRole +#define QAXTopLevelUIElementAttribute kAXTopLevelUIElementAttribute +#define QAXUnknownRole kAXUnknownRole +#define QAXValueAttribute kAXValueAttribute +#define QAXValueChangedNotification kAXValueChangedNotification +#define QAXValueIndicatorRole kAXValueIndicatorRole +#define QAXVerticalOrientationValue kAXVerticalOrientationValue +#define QAXVerticalScrollBarAttribute kAXVerticalScrollBarAttribute +#define QAXVisibleRowsAttribute kAXVisibleRowsAttribute +#define QAXWindowAttribute kAXWindowAttribute +#define QAXWindowCreatedNotification kAXWindowCreatedNotification +#define QAXWindowMovedNotification kAXWindowMovedNotification +#define QAXWindowRole kAXWindowRole +#define QAXZoomButtonAttribute kAXZoomButtonAttribute +#endif + + +/***************************************************************************** + Externals + *****************************************************************************/ +extern bool qt_mac_is_macsheet(const QWidget *w); //qwidget_mac.cpp +extern bool qt_mac_is_macdrawer(const QWidget *w); //qwidget_mac.cpp + +/***************************************************************************** + QAccessible Bindings + *****************************************************************************/ +//hardcoded bindings between control info and (known) QWidgets +struct QAccessibleTextBinding { + int qt; + QAXRoleType mac; + bool settable; +} text_bindings[][10] = { + { { QAccessible::MenuItem, QAXMenuItemRole, false }, + { -1, 0, false } + }, + { { QAccessible::MenuBar, QAXMenuBarRole, false }, + { -1, 0, false } + }, + { { QAccessible::ScrollBar, QAXScrollBarRole, false }, + { -1, 0, false } + }, + { { QAccessible::Grip, QAXGrowAreaRole, false }, + { -1, 0, false } + }, + { { QAccessible::Window, QAXWindowRole, false }, + { -1, 0, false } + }, + { { QAccessible::Dialog, QAXWindowRole, false }, + { -1, 0, false } + }, + { { QAccessible::AlertMessage, QAXWindowRole, false }, + { -1, 0, false } + }, + { { QAccessible::ToolTip, QAXWindowRole, false }, + { -1, 0, false } + }, + { { QAccessible::HelpBalloon, QAXWindowRole, false }, + { -1, 0, false } + }, + { { QAccessible::PopupMenu, QAXMenuRole, false }, + { -1, 0, false } + }, + { { QAccessible::Application, QAXApplicationRole, false }, + { -1, 0, false } + }, + { { QAccessible::Pane, QAXGroupRole, false }, + { -1, 0, false } + }, + { { QAccessible::Grouping, QAXGroupRole, false }, + { -1, 0, false } + }, + { { QAccessible::Separator, QAXSplitterRole, false }, + { -1, 0, false } + }, + { { QAccessible::ToolBar, QAXToolbarRole, false }, + { -1, 0, false } + }, + { { QAccessible::PageTab, QAXRadioButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::ButtonMenu, QAXMenuButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::ButtonDropDown, QAXPopUpButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::SpinBox, QAXIncrementorRole, false }, + { -1, 0, false } + }, + { { QAccessible::Slider, QAXSliderRole, false }, + { -1, 0, false } + }, + { { QAccessible::ProgressBar, QAXProgressIndicatorRole, false }, + { -1, 0, false } + }, + { { QAccessible::ComboBox, QAXPopUpButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::RadioButton, QAXRadioButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::CheckBox, QAXCheckBoxRole, false }, + { -1, 0, false } + }, + { { QAccessible::StaticText, QAXStaticTextRole, false }, + { QAccessible::Name, QAXValueAttribute, false }, + { -1, 0, false } + }, + { { QAccessible::Table, QAXTableRole, false }, + { -1, 0, false } + }, + { { QAccessible::StatusBar, QAXStaticTextRole, false }, + { -1, 0, false } + }, + { { QAccessible::Column, QAXColumnRole, false }, + { -1, 0, false } + }, + { { QAccessible::ColumnHeader, QAXColumnRole, false }, + { -1, 0, false } + }, + { { QAccessible::Row, QAXRowRole, false }, + { -1, 0, false } + }, + { { QAccessible::RowHeader, QAXRowRole, false }, + { -1, 0, false } + }, + { { QAccessible::Cell, QAXTextFieldRole, false }, + { -1, 0, false } + }, + { { QAccessible::PushButton, QAXButtonRole, false }, + { -1, 0, false } + }, + { { QAccessible::EditableText, QAXTextFieldRole, true }, + { -1, 0, false } + }, + { { QAccessible::Link, QAXTextFieldRole, false }, + { -1, 0, false } + }, + { { QAccessible::Indicator, QAXValueIndicatorRole, false }, + { -1, 0, false } + }, + { { QAccessible::Splitter, QAXSplitGroupRole, false }, + { -1, 0, false } + }, + { { QAccessible::List, QAXListRole, false }, + { -1, 0, false } + }, + { { QAccessible::ListItem, QAXStaticTextRole, false }, + { -1, 0, false } + }, + { { QAccessible::Cell, QAXStaticTextRole, false }, + { -1, 0, false } + }, + { { -1, 0, false } } +}; + +class QAInterface; +static CFStringRef macRole(const QAInterface &interface); + +QDebug operator<<(QDebug debug, const QAInterface &interface) +{ + if (interface.isValid() == false) + debug << "invalid interface"; + else + debug << interface.object() << "id" << interface.id() << "role" << hex << interface.role(); + return debug; +} + +// The root of the Qt accessible hiearchy. +static QObject *rootObject = 0; + + +bool QAInterface::operator==(const QAInterface &other) const +{ + if (isValid() == false || other.isValid() == false) + return (isValid() && other.isValid()); + + // walk up the parent chain, comparing child indexes, until we reach + // an interface that has a QObject. + QAInterface currentThis = *this; + QAInterface currentOther = other; + + while (currentThis.object() == 0) { + if (currentOther.object() != 0) + return false; + + // fail if the child indexes in the two hirearchies don't match. + if (currentThis.parent().indexOfChild(currentThis) != + currentOther.parent().indexOfChild(currentOther)) + return false; + + currentThis = currentThis.parent(); + currentOther = currentOther.parent(); + } + + return (currentThis.object() == currentOther.object() && currentThis.id() == currentOther.id()); +} + +bool QAInterface::operator!=(const QAInterface &other) const +{ + return !operator==(other); +} + +uint qHash(const QAInterface &item) +{ + if (item.isValid()) + return qHash(item.object()) + qHash(item.id()); + else + return qHash(item.cachedObject()) + qHash(item.id()); +} + +QAInterface QAInterface::navigate(RelationFlag relation, int entry) const +{ + if (!checkValid()) + return QAInterface(); + + // On a QAccessibleInterface that handles its own children we can short-circut + // the navigation if this QAInterface refers to one of the children: + if (child != 0) { + // The Ancestor interface will always be the same QAccessibleInterface with + // a child value of 0. + if (relation == QAccessible::Ancestor) + return QAInterface(*this, 0); + + // The child hiearchy is only one level deep, so navigating to a child + // of a child is not possible. + if (relation == QAccessible::Child) { + return QAInterface(); + } + } + QAccessibleInterface *child_iface = 0; + + const int status = base.interface->navigate(relation, entry, &child_iface); + + if (status == -1) + return QAInterface(); // not found; + + // Check if target is a child of this interface. + if (!child_iface) { + return QAInterface(*this, status); + } else { + // Target is child_iface or a child of that (status decides). + return QAInterface(child_iface, status); + } +} + +QAElement::QAElement() +:elementRef(0) +{} + +QAElement::QAElement(AXUIElementRef elementRef) +:elementRef(elementRef) +{ + if (elementRef != 0) { + CFRetain(elementRef); + CFRetain(object()); + } +} + +QAElement::QAElement(const QAElement &element) +:elementRef(element.elementRef) +{ + if (elementRef != 0) { + CFRetain(elementRef); + CFRetain(object()); + } +} + +QAElement::QAElement(HIObjectRef object, int child) +{ +#ifndef QT_MAC_USE_COCOA + if (object == 0) { + elementRef = 0; // Create invalid QAElement. + } else { + elementRef = AXUIElementCreateWithHIObjectAndIdentifier(object, child); + CFRetain(object); + } +#else + Q_UNUSED(object); + Q_UNUSED(child); +#endif +} + +QAElement::~QAElement() +{ + if (elementRef != 0) { + CFRelease(object()); + CFRelease(elementRef); + } +} + +void QAElement::operator=(const QAElement &other) +{ + if (*this == other) + return; + + if (elementRef != 0) { + CFRelease(object()); + CFRelease(elementRef); + } + + elementRef = other.elementRef; + + if (elementRef != 0) { + CFRetain(elementRef); + CFRetain(object()); + } +} + +bool QAElement::operator==(const QAElement &other) const +{ + if (elementRef == 0 || other.elementRef == 0) + return (elementRef == other.elementRef); + + return CFEqual(elementRef, other.elementRef); +} + +uint qHash(QAElement element) +{ + return qHash(element.object()) + qHash(element.id()); +} + +#ifndef QT_MAC_USE_COCOA +static QInterfaceFactory *createFactory(const QAInterface &interface); +#endif +Q_GLOBAL_STATIC(QAccessibleHierarchyManager, accessibleHierarchyManager); + +/* + Reomves all accessibility info accosiated with the sender object. +*/ +void QAccessibleHierarchyManager::objectDestroyed(QObject *object) +{ + HIObjectRef hiObject = qobjectHiobjectHash.value(object); + delete qobjectElementHash.value(object); + qobjectElementHash.remove(object); + hiobjectInterfaceHash.remove(hiObject); +} + +/* + Removes all stored items. +*/ +void QAccessibleHierarchyManager::reset() +{ + qDeleteAll(qobjectElementHash); + qobjectElementHash.clear(); + hiobjectInterfaceHash.clear(); + qobjectHiobjectHash.clear(); +} + +QAccessibleHierarchyManager *QAccessibleHierarchyManager::instance() +{ + return accessibleHierarchyManager(); +} + +#ifndef QT_MAC_USE_COCOA +static bool isItemView(const QAInterface &interface) +{ + QObject *object = interface.object(); + return (interface.role() == QAccessible::List || interface.role() == QAccessible::Table + || (object && qobject_cast(interface.object())) + || (object && object->objectName() == QLatin1String("qt_scrollarea_viewport") + && qobject_cast(object->parent()))); +} +#endif + +static bool isTabWidget(const QAInterface &interface) +{ + if (QObject *object = interface.object()) + return (object->inherits("QTabWidget") && interface.id() == 0); + return false; +} + +static bool isStandaloneTabBar(const QAInterface &interface) +{ + QObject *object = interface.object(); + if (interface.role() == QAccessible::PageTabList && object) + return (qobject_cast(object->parent()) == 0); + + return false; +} + +static bool isEmbeddedTabBar(const QAInterface &interface) +{ + QObject *object = interface.object(); + if (interface.role() == QAccessible::PageTabList && object) + return (qobject_cast(object->parent())); + + return false; +} + +/* + Decides if a QAInterface is interesting from an accessibility users point of view. +*/ +bool isItInteresting(const QAInterface &interface) +{ + // Mac accessibility does not have an attribute that corresponds to the Invisible/Offscreen + // state, so we disable the interface here. + const QAccessible::State state = interface.state(); + if (state & QAccessible::Invisible || + state & QAccessible::Offscreen ) + return false; + + const QAccessible::Role role = interface.role(); + + if (QObject * const object = interface.object()) { + const QString className = QLatin1String(object->metaObject()->className()); + + // VoiceOver focusing on tool tips can be confusing. The contents of the + // tool tip is avalible through the description attribute anyway, so + // we disable accessibility for tool tips. + if (className == QLatin1String("QTipLabel")) + return false; + + // Hide TabBars that has a QTabWidget parent (the tab widget handles the accessibility) + if (isEmbeddedTabBar(interface)) + return false; + + // Hide docked dockwidgets. ### causes infinitie loop in the apple accessibility code. + /* if (QDockWidget *dockWidget = qobject_cast(object)) { + if (dockWidget->isFloating() == false) + return false; + } + */ + } + + // Client is a generic role returned by plain QWidgets or other + // widgets that does not have separate QAccessible interface, such + // as the TabWidget. Return false unless macRole gives the interface + // a special role. + if (role == QAccessible::Client && macRole(interface) == CFStringRef(QAXUnknownRole)) + return false; + + // Some roles are not interesting: + if (role == QAccessible::Border || // QFrame + role == QAccessible::Application || // We use the system-provided application element. + role == QAccessible::MenuItem) // The system also provides the menu items. + return false; + + // It is probably better to access the toolbar buttons directly than having + // to navigate through the toolbar. + if (role == QAccessible::ToolBar) + return false; + + return true; +} + +QAElement QAccessibleHierarchyManager::registerInterface(QObject *object, int child) +{ +#ifndef QT_MAC_USE_COCOA + return registerInterface(QAInterface(QAccessible::queryAccessibleInterface(object), child)); +#else + Q_UNUSED(object); + Q_UNUSED(child); + return QAElement(); +#endif +} + +/* + Creates a QAXUIelement that corresponds to the given QAInterface. +*/ +QAElement QAccessibleHierarchyManager::registerInterface(const QAInterface &interface) +{ +#ifndef QT_MAC_USE_COCOA + if (interface.isValid() == false) + return QAElement(); + QAInterface objectInterface = interface.objectInterface(); + + QObject * qobject = objectInterface.object(); + HIObjectRef hiobject = objectInterface.hiObject(); + if (qobject == 0 || hiobject == 0) + return QAElement(); + + if (qobjectElementHash.contains(qobject) == false) { + registerInterface(qobject, hiobject, createFactory(interface)); + HIObjectSetAccessibilityIgnored(hiobject, !isItInteresting(interface)); + } + + return QAElement(hiobject, interface.id()); +#else + Q_UNUSED(interface); + return QAElement(); +#endif +} + +#ifndef QT_MAC_USE_COCOA +#include "qaccessible_mac_carbon.cpp" +#endif + +void QAccessibleHierarchyManager::registerInterface(QObject * qobject, HIObjectRef hiobject, QInterfaceFactory *interfaceFactory) +{ +#ifndef QT_MAC_USE_COCOA + if (qobjectElementHash.contains(qobject) == false) { + qobjectElementHash.insert(qobject, interfaceFactory); + qobjectHiobjectHash.insert(qobject, hiobject); + connect(qobject, SIGNAL(destroyed(QObject *)), SLOT(objectDestroyed(QObject *))); + } + + if (hiobjectInterfaceHash.contains(hiobject) == false) { + hiobjectInterfaceHash.insert(hiobject, interfaceFactory); + installAcessibilityEventHandler(hiobject); + } +#else + Q_UNUSED(qobject); + Q_UNUSED(hiobject); + Q_UNUSED(interfaceFactory); +#endif +} + +void QAccessibleHierarchyManager::registerChildren(const QAInterface &interface) +{ + QObject * const object = interface.object(); + if (object == 0) + return; + + QInterfaceFactory *interfaceFactory = qobjectElementHash.value(object); + + if (interfaceFactory == 0) + return; + + interfaceFactory->registerChildren(); +} + +QAInterface QAccessibleHierarchyManager::lookup(const AXUIElementRef &element) +{ + if (element == 0) + return QAInterface(); +#ifndef QT_MAC_USE_COCOA + HIObjectRef hiObject = AXUIElementGetHIObject(element); + + QInterfaceFactory *factory = hiobjectInterfaceHash.value(hiObject); + if (factory == 0) { + return QAInterface(); + } + + UInt64 id; + AXUIElementGetIdentifier(element, &id); + return factory->interface(id); +#else + return QAInterface(); +#endif +} + +QAInterface QAccessibleHierarchyManager::lookup(const QAElement &element) +{ + return lookup(element.element()); +} + +QAElement QAccessibleHierarchyManager::lookup(const QAInterface &interface) +{ + if (interface.isValid() == false) + return QAElement(); + + QInterfaceFactory *factory = qobjectElementHash.value(interface.objectInterface().object()); + if (factory == 0) + return QAElement(); + + return factory->element(interface); +} + +QAElement QAccessibleHierarchyManager::lookup(QObject * const object, int id) +{ + QInterfaceFactory *factory = qobjectElementHash.value(object); + if (factory == 0) + return QAElement(); + + return factory->element(id); +} + +/* + Standard interface mapping, return the stored interface + or HIObjectRef, and there is an one-to-one mapping between + the identifier and child. +*/ +class QStandardInterfaceFactory : public QInterfaceFactory +{ +public: + QStandardInterfaceFactory(const QAInterface &interface) + : m_interface(interface), object(interface.hiObject()) + { + CFRetain(object); + } + + ~QStandardInterfaceFactory() + { + CFRelease(object); + } + + + QAInterface interface(UInt64 identifier) + { + const int child = identifier; + return QAInterface(m_interface, child); + } + + QAElement element(int id) + { + return QAElement(object, id); + } + + QAElement element(const QAInterface &interface) + { + if (interface.object() == 0) + return QAElement(); + return QAElement(object, interface.id()); + } + + void registerChildren() + { + const int childCount = m_interface.childCount(); + for (int i = 1; i <= childCount; ++i) { + accessibleHierarchyManager()->registerInterface(m_interface.navigate(QAccessible::Child, i)); + } + } + +private: + QAInterface m_interface; + HIObjectRef object; +}; + +/* + Interface mapping where that creates one HIObject for each interface child. +*/ +class QMultipleHIObjectFactory : public QInterfaceFactory +{ +public: + QMultipleHIObjectFactory(const QAInterface &interface) + : m_interface(interface) + { } + + ~QMultipleHIObjectFactory() + { + foreach (HIObjectRef object, objects) { + CFRelease(object); + } + } + + QAInterface interface(UInt64 identifier) + { + const int child = identifier; + return QAInterface(m_interface, child); + } + + QAElement element(int child) + { + if (child == 0) + return QAElement(m_interface.hiObject(), 0); + + if (child > objects.count()) + return QAElement(); + + return QAElement(objects.at(child - 1), child); + } + + void registerChildren() + { +#ifndef QT_MAC_USE_COCOA + const int childCount = m_interface.childCount(); + for (int i = 1; i <= childCount; ++i) { + HIObjectRef hiobject; + HIObjectCreate(kObjectQtAccessibility, 0, &hiobject); + objects.append(hiobject); + accessibleHierarchyManager()->registerInterface(m_interface.object(), hiobject, this); + HIObjectSetAccessibilityIgnored(hiobject, !isItInteresting(m_interface.navigate(QAccessible::Child, i))); + } +#endif + } + +private: + QAInterface m_interface; + QList objects; +}; + +class QItemViewInterfaceFactory : public QInterfaceFactory +{ +public: + QItemViewInterfaceFactory(const QAInterface &interface) + : m_interface(interface), object(interface.hiObject()) + { + CFRetain(object); + columnCount = 0; + if (QTableView * tableView = qobject_cast(interface.parent().object())) { + if (tableView->model()) + columnCount = tableView->model()->columnCount(); + if (tableView->verticalHeader()) + ++columnCount; + } + } + + ~QItemViewInterfaceFactory() + { + CFRelease(object); + } + + QAInterface interface(UInt64 identifier) + { + if (identifier == 0) + return m_interface; + + if (m_interface.role() == QAccessible::List) + return m_interface.childAt(identifier); + + if (m_interface.role() == QAccessible::Table) { + const int index = identifier; + if (index == 0) + return m_interface; // return the item view interface. + + const int rowIndex = (index - 1) / (columnCount + 1); + const int cellIndex = (index - 1) % (columnCount + 1); +/* + qDebug() << "index" << index; + qDebug() << "rowIndex" << rowIndex; + qDebug() << "cellIndex" << cellIndex; +*/ + const QAInterface rowInterface = m_interface.childAt(rowIndex + 1); + + if ((cellIndex) == 0) // Is it a row? + return rowInterface; + else { + return rowInterface.childAt(cellIndex); + } + } + + return QAInterface(); + } + + QAElement element(int id) + { + if (id != 0) { + return QAElement(); + } + return QAElement(object, 0); + } + + QAElement element(const QAInterface &interface) + { + if (interface.object() && interface.object() == m_interface.object()) { + return QAElement(object, 0); + } else if (m_interface.role() == QAccessible::List) { + if (interface.parent().object() && interface.parent().object() == m_interface.object()) + return QAElement(object, m_interface.indexOfChild(interface)); + } else if (m_interface.role() == QAccessible::Table) { + QAInterface currentInterface = interface; + int index = 0; + + while (currentInterface.isValid() && currentInterface.object() == 0) { + const QAInterface parentInterface = currentInterface.parent(); +/* + qDebug() << "current index" << index; + qDebug() << "current interface" << interface; + + qDebug() << "parent interface" << parentInterface; + qDebug() << "grandparent interface" << parentInterface.parent(); + qDebug() << "childCount" << interface.childCount(); + qDebug() << "index of child" << parentInterface.indexOfChild(currentInterface); +*/ + index += ((parentInterface.indexOfChild(currentInterface) - 1) * (currentInterface.childCount() + 1)) + 1; + currentInterface = parentInterface; +// qDebug() << "new current interface" << currentInterface; + } + if (currentInterface.object() == m_interface.object()) + return QAElement(object, index); + + + } + return QAElement(); + } + + void registerChildren() + { + // Item view child interfraces don't have their own qobjects, so there is nothing to register here. + } + +private: + QAInterface m_interface; + HIObjectRef object; + int columnCount; // for table views; +}; + +#ifndef QT_MAC_USE_COCOA +static bool managesChildren(const QAInterface &interface) +{ + return (interface.childCount() > 0 && interface.childAt(1).id() > 0); +} + +static QInterfaceFactory *createFactory(const QAInterface &interface) +{ + if (isItemView(interface)) { + return new QItemViewInterfaceFactory(interface); + } if (managesChildren(interface)) { + return new QMultipleHIObjectFactory(interface); + } + + return new QStandardInterfaceFactory(interface); +} +#endif + +QList lookup(const QList &interfaces) +{ + QList elements; + foreach (const QAInterface &interface, interfaces) + if (interface.isValid()) { + const QAElement element = accessibleHierarchyManager()->lookup(interface); + if (element.isValid()) + elements.append(element); + } + return elements; +} + +// Debug output helpers: +/* +static QString nameForEventKind(UInt32 kind) +{ + switch(kind) { + case kEventAccessibleGetChildAtPoint: return QString("GetChildAtPoint"); break; + case kEventAccessibleGetAllAttributeNames: return QString("GetAllAttributeNames"); break; + case kEventAccessibleGetNamedAttribute: return QString("GetNamedAttribute"); break; + case kEventAccessibleSetNamedAttribute: return QString("SetNamedAttribute"); break; + case kEventAccessibleGetAllActionNames: return QString("GetAllActionNames"); break; + case kEventAccessibleGetFocusedChild: return QString("GetFocusedChild"); break; + default: + return QString("Unknown accessibility event type: %1").arg(kind); + break; + }; +} +*/ +#ifndef QT_MAC_USE_COCOA +static bool qt_mac_append_cf_uniq(CFMutableArrayRef array, CFTypeRef value) +{ + if (value == 0) + return false; + + CFRange range; + range.location = 0; + range.length = CFArrayGetCount(array); + if(!CFArrayContainsValue(array, range, value)) { + CFArrayAppendValue(array, value); + return true; + } + return false; +} + +static OSStatus setAttributeValue(EventRef event, const QList &elements) +{ + CFMutableArrayRef array = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); + foreach (const QAElement &element, elements) { + if (element.isValid()) + CFArrayAppendValue(array, element.element()); + } + + const OSStatus err = SetEventParameter(event, kEventParamAccessibleAttributeValue, + typeCFTypeRef, sizeof(array), &array); + CFRelease(array); + return err; +} +#endif //QT_MAC_USE_COCOA + +/* + Gets the AccessibleObject parameter from an event. +*/ +static inline AXUIElementRef getAccessibleObjectParameter(EventRef event) +{ + AXUIElementRef element; + GetEventParameter(event, kEventParamAccessibleObject, typeCFTypeRef, 0, + sizeof(element), 0, &element); + return element; +} + +/* + The application event handler makes sure that all top-level qt windows are registered + before any accessibility events are handeled. +*/ +#ifndef QT_MAC_USE_COCOA +static OSStatus applicationEventHandler(EventHandlerCallRef next_ref, EventRef event, void *) +{ + QAInterface rootInterface(QAccessible::queryAccessibleInterface(rootObject ? rootObject : qApp), 0); + accessibleHierarchyManager()->registerChildren(rootInterface); + + return CallNextEventHandler(next_ref, event); +} + +/* + Returns the value for element by combining the QAccessibility::Checked and + QAccessibility::Mixed flags into an int value that the Mac accessibilty + system understands. This works for check boxes, radio buttons, and the like. + The return values are: + 0: unchecked + 1: checked + 2: undecided +*/ +static int buttonValue(QAInterface element) +{ + const QAccessible::State state = element.state(); + if (state & QAccessible::Mixed) + return 2; + else if(state & QAccessible::Checked) + return 1; + else + return 0; +} + +static QString getValue(const QAInterface &interface) +{ + const QAccessible::Role role = interface.role(); + if (role == QAccessible::RadioButton || role == QAccessible::CheckBox) + return QString::number(buttonValue(interface)); + else + return interface.text(QAccessible::Value); +} +#endif //QT_MAC_USE_COCOA + +/* + Translates a QAccessible::Role into a mac accessibility role. +*/ +static CFStringRef macRole(const QAInterface &interface) +{ + const QAccessible::Role qtRole = interface.role(); + +// qDebug() << "role for" << interface.object() << "interface role" << hex << qtRole; + + // Qt accessibility: QAccessible::Splitter contains QAccessible::Grip. + // Mac accessibility: AXSplitGroup contains AXSplitter. + if (qtRole == QAccessible::Grip) { + const QAInterface parent = interface.parent(); + if (parent.isValid() && parent.role() == QAccessible::Splitter) + return CFStringRef(QAXSplitterRole); + } + + // Tab widgets and standalone tab bars get the kAXTabGroupRole. Accessibility + // for tab bars emebedded in a tab widget is handled by the tab widget. + if (isTabWidget(interface) || isStandaloneTabBar(interface)) + return kAXTabGroupRole; + + if (QObject *object = interface.object()) { + // ### The interface for an abstract scroll area returns the generic "Client" + // role, so we have to to an extra detect on the QObject here. + if (object->inherits("QAbstractScrollArea") && interface.id() == 0) + return CFStringRef(QAXScrollAreaRole); + + if (object->inherits("QDockWidget")) + return CFStringRef(QAXUnknownRole); + } + + int i = 0; + int testRole = text_bindings[i][0].qt; + while (testRole != -1) { + if (testRole == qtRole) + return CFStringRef(text_bindings[i][0].mac); + ++i; + testRole = text_bindings[i][0].qt; + } + +// qDebug() << "got unknown role!" << interface << interface.parent(); + + return CFStringRef(QAXUnknownRole); +} + +/* + Translates a QAccessible::Role and an attribute name into a QAccessible::Text, taking into + account execptions listed in text_bindings. +*/ +#ifndef QT_MAC_USE_COCOA +static int textForRoleAndAttribute(QAccessible::Role role, CFStringRef attribute) +{ + // Search for exception, return it if found. + int testRole = text_bindings[0][0].qt; + int i = 0; + while (testRole != -1) { + if (testRole == role) { + int j = 1; + int qtRole = text_bindings[i][j].qt; + CFStringRef testAttribute = CFStringRef(text_bindings[i][j].mac); + while (qtRole != -1) { + if (CFStringCompare(attribute, testAttribute, 0) == kCFCompareEqualTo) { + return (QAccessible::Text)qtRole; + } + ++j; + testAttribute = CFStringRef(text_bindings[i][j].mac); /// ### custom compare + qtRole = text_bindings[i][j].qt; /// ### custom compare + } + break; + } + ++i; + testRole = text_bindings[i][0].qt; + } + + // Return default mappping + if (CFStringCompare(attribute, CFStringRef(QAXTitleAttribute), 0) == kCFCompareEqualTo) + return QAccessible::Name; + else if (CFStringCompare(attribute, CFStringRef(QAXValueAttribute), 0) == kCFCompareEqualTo) + return QAccessible::Value; + else if (CFStringCompare(attribute, CFStringRef(QAXHelpAttribute), 0) == kCFCompareEqualTo) + return QAccessible::Help; + else if (CFStringCompare(attribute, CFStringRef(QAXDescriptionAttribute), 0) == kCFCompareEqualTo) + return QAccessible::Description; + else + return -1; +} + +/* + Returns the subrole string constant for the interface if it has one, + else returns an empty string. +*/ +static QCFString subrole(const QAInterface &interface) +{ + const QAInterface parent = interface.parent(); + if (parent.isValid() == false) + return QCFString(); + + if (parent.role() == QAccessible::ScrollBar) { + QCFString subrole; + switch(interface.id()) { + case 1: subrole = CFStringRef(QAXDecrementArrowSubrole); break; + case 2: subrole = CFStringRef(QAXDecrementPageSubrole); break; + case 4: subrole = CFStringRef(QAXIncrementPageSubrole); break; + case 5: subrole = CFStringRef(QAXIncrementArrowSubrole); break; + default: + break; + } + return subrole; + } + return QCFString(); +} + +// Gets the scroll bar orientation by asking the QAbstractSlider object directly. +static Qt::Orientation scrollBarOrientation(const QAInterface &scrollBar) +{ + QObject *const object = scrollBar.object(); + if (QAbstractSlider * const sliderObject = qobject_cast(object)) + return sliderObject->orientation(); + + return Qt::Vertical; // D'oh! The interface wasn't a scroll bar. +} + +static QAInterface scrollAreaGetScrollBarInterface(const QAInterface &scrollArea, Qt::Orientation orientation) +{ + if (macRole(scrollArea) != CFStringRef(CFStringRef(QAXScrollAreaRole))) + return QAInterface(); + + // Child 1 is the contents widget, 2 and 3 are the scroll bar containers wich contains possible scroll bars. + for (int i = 2; i <= 3; ++i) { + QAInterface scrollBarContainer = scrollArea.childAt(i); + for (int i = 1; i <= scrollBarContainer.childCount(); ++i) { + QAInterface scrollBar = scrollBarContainer.childAt(i); + if (scrollBar.isValid() && + scrollBar.role() == QAccessible::ScrollBar && + scrollBarOrientation(scrollBar) == orientation) + return scrollBar; + } + } + + return QAInterface(); +} + +static bool scrollAreaHasScrollBar(const QAInterface &scrollArea, Qt::Orientation orientation) +{ + return scrollAreaGetScrollBarInterface(scrollArea, orientation).isValid(); +} + +static QAElement scrollAreaGetScrollBar(const QAInterface &scrollArea, Qt::Orientation orientation) +{ + return accessibleHierarchyManager()->lookup(scrollAreaGetScrollBarInterface(scrollArea, orientation)); +} + +static QAElement scrollAreaGetContents(const QAInterface &scrollArea) +{ + // Child 1 is the contents widget, + return accessibleHierarchyManager()->lookup(scrollArea.navigate(QAccessible::Child, 1)); +} + +static QAElement tabWidgetGetContents(const QAInterface &interface) +{ + // A kAXTabGroup has a kAXContents attribute, which consists of the + // ui elements for the current tab page. Get the current tab page + // from the QStackedWidget, where the current visible page can + // be found at index 1. + QAInterface stackedWidget = interface.childAt(1); + accessibleHierarchyManager()->registerChildren(stackedWidget); + QAInterface tabPageInterface = stackedWidget.childAt(1); + return accessibleHierarchyManager()->lookup(tabPageInterface); +} + +static QList tabBarGetTabs(const QAInterface &interface) +{ + // Get the tabs by searching for children with the "PageTab" role. + // This filters out the left/right navigation buttons. + accessibleHierarchyManager()->registerChildren(interface); + QList tabs; + const int numChildren = interface.childCount(); + for (int i = 1; i < numChildren + 1; ++i) { + QAInterface child = interface.navigate(QAccessible::Child, i); + if (child.isValid() && child.role() == QAccessible::PageTab) { + tabs.append(accessibleHierarchyManager()->lookup(child)); + } + } + return tabs; +} + +static QList tabWidgetGetTabs(const QAInterface &interface) +{ + // Each QTabWidget has two children, a QStackedWidget and a QTabBar. + // Get the tabs from the QTabBar. + return tabBarGetTabs(interface.childAt(2)); +} + +static QList tabWidgetGetChildren(const QAInterface &interface) +{ + // The children for a kAXTabGroup should consist of the tabs and the + // contents of the current open tab page. + QList children = tabWidgetGetTabs(interface); + children += tabWidgetGetContents(interface); + return children; +} +#endif //QT_MAC_USE_COCOA + +/* + Returns the label (buddy) interface for interface, or 0 if it has none. +*/ +/* +static QAInterface findLabel(const QAInterface &interface) +{ + return interface.navigate(QAccessible::Label, 1); +} +*/ +/* + Returns a list of interfaces this interface labels, or an empty list if it doesn't label any. +*/ +/* +static QList findLabelled(const QAInterface &interface) +{ + QList interfaceList; + + int count = 1; + const QAInterface labelled = interface.navigate(QAccessible::Labelled, count); + while (labelled.isValid()) { + interfaceList.append(labelled); + ++count; + } + return interfaceList; +} +*/ +/* + Tests if the given QAInterface has data for a mac attribute. +*/ +#ifndef QT_MAC_USE_COCOA +static bool supportsAttribute(CFStringRef attribute, const QAInterface &interface) +{ + const int text = textForRoleAndAttribute(interface.role(), attribute); + + // Special case: Static texts don't have a title. + if (interface.role() == QAccessible::StaticText && attribute == CFStringRef(QAXTitleAttribute)) + return false; + + // Return true if we the attribute matched a QAccessible::Role and we get text for that role from the interface. + if (text != -1) { + if (text == QAccessible::Value) // Special case for Value, see getValue() + return !getValue(interface).isEmpty(); + else + return !interface.text((QAccessible::Text)text).isEmpty(); + } + + if (CFStringCompare(attribute, CFStringRef(QAXChildrenAttribute), 0) == kCFCompareEqualTo) { + if (interface.childCount() > 0) + return true; + } + + if (CFStringCompare(attribute, CFStringRef(QAXSubroleAttribute), 0) == kCFCompareEqualTo) { + return (subrole(interface) != QCFString()); + } + + return false; +} + +static void appendIfSupported(CFMutableArrayRef array, CFStringRef attribute, const QAInterface &interface) +{ + if (supportsAttribute(attribute, interface)) + qt_mac_append_cf_uniq(array, attribute); +} + +/* + Returns the names of the attributes the give QAInterface supports. +*/ +static OSStatus getAllAttributeNames(EventRef event, const QAInterface &interface, EventHandlerCallRef next_ref) +{ + // Call system event handler. + OSStatus err = CallNextEventHandler(next_ref, event); + if(err != noErr && err != eventNotHandledErr) + return err; + CFMutableArrayRef attrs = 0; + GetEventParameter(event, kEventParamAccessibleAttributeNames, typeCFMutableArrayRef, 0, + sizeof(attrs), 0, &attrs); + + if (!attrs) + return eventNotHandledErr; + + // Append attribute names that are always supported. + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXPositionAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXSizeAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXRoleAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXEnabledAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXWindowAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXTopLevelUIElementAttribute)); + + // Append these names if the QInterafceItem returns any data for them. + appendIfSupported(attrs, CFStringRef(QAXTitleAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXValueAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXDescriptionAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXLinkedUIElementsAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXHelpAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXTitleUIElementAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXChildrenAttribute), interface); + appendIfSupported(attrs, CFStringRef(QAXSubroleAttribute), interface); + + // Append attribute names based on the interaface role. + switch (interface.role()) { + case QAccessible::Window: + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXMainAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXMinimizedAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXCloseButtonAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXZoomButtonAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXMinimizeButtonAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXToolbarButtonAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXGrowAreaAttribute)); + break; + case QAccessible::RadioButton: + case QAccessible::CheckBox: + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXMinValueAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXMaxValueAttribute)); + break; + case QAccessible::ScrollBar: + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXOrientationAttribute)); + break; + case QAccessible::Splitter: + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXSplittersAttribute)); + break; + case QAccessible::Table: + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXRowsAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXVisibleRowsAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXSelectedRowsAttribute)); + break; + default: + break; + } + + // Append attribute names based on the mac accessibility role. + const QCFString mac_role = macRole(interface); + if (mac_role == CFStringRef(QAXSplitterRole)) { + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXPreviousContentsAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXNextContentsAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXOrientationAttribute)); + } else if (mac_role == CFStringRef(QAXScrollAreaRole)) { + if (scrollAreaHasScrollBar(interface, Qt::Horizontal)) + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXHorizontalScrollBarAttribute)); + if (scrollAreaHasScrollBar(interface, Qt::Vertical)) + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXVerticalScrollBarAttribute)); + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXContentsAttribute)); + } else if (mac_role == CFStringRef(QAXTabGroupRole)) { + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXTabsAttribute)); + // Only tab widgets can have the contents attribute, there is no way of getting + // the contents from a QTabBar. + if (isTabWidget(interface)) + qt_mac_append_cf_uniq(attrs, CFStringRef(QAXContentsAttribute)); + } + + return noErr; +} + +static void handleStringAttribute(EventRef event, QAccessible::Text text, const QAInterface &interface) +{ + QString str = interface.text(text); + if (str.isEmpty()) + return; + + // Remove any html markup from the text string, or VoiceOver will read the html tags. + static QTextDocument document; + document.setHtml(str); + str = document.toPlainText(); + + CFStringRef cfstr = QCFString::toCFStringRef(str); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFStringRef, sizeof(cfstr), &cfstr); +} + +/* + Handles the parent attribute for a interface. + There are basically three cases here: + 1. interface is a HIView and has only HIView children. + 2. interface is a HIView but has children that is not a HIView + 3. interface is not a HIView. +*/ +static OSStatus handleChildrenAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + // Add the children for this interface to the global QAccessibelHierachyManager. + accessibleHierarchyManager()->registerChildren(interface); + + if (isTabWidget(interface)) { + QList children = tabWidgetGetChildren(interface); + const int childCount = children.count(); + + CFMutableArrayRef array = 0; + array = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); + for (int i = 0; i < childCount; ++i) { + qt_mac_append_cf_uniq(array, children.at(i).element()); + } + + OSStatus err; + err = SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFArrayRef, sizeof(array), &array); + if (err != noErr) + qWarning("Qt:Internal error (%s:%d)", __FILE__, __LINE__); + + return noErr; + } + + const QList children = lookup(interface.children()); + const int childCount = children.count(); + + OSStatus err = eventNotHandledErr; + if (interface.isHIView()) + err = CallNextEventHandler(next_ref, event); + + CFMutableArrayRef array = 0; + int arraySize = 0; + if (err == noErr) { + CFTypeRef obj = 0; + err = GetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, NULL , sizeof(obj), NULL, &obj); + if (err == noErr && obj != 0) { + array = (CFMutableArrayRef)obj; + arraySize = CFArrayGetCount(array); + } + } + + if (array) { + CFArrayRemoveAllValues(array); + for (int i = 0; i < childCount; ++i) { + qt_mac_append_cf_uniq(array, children.at(i).element()); + } + } else { + array = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); + for (int i = 0; i < childCount; ++i) { + qt_mac_append_cf_uniq(array, children.at(i).element()); + } + + err = SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFArrayRef, sizeof(array), &array); + if (err != noErr) + qWarning("Qt:Internal error (%s:%d)", __FILE__, __LINE__); + } + + return noErr; +} + +/* + +*/ +static OSStatus handleParentAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + OSStatus err = eventNotHandledErr; + if (interface.isHIView()) { + err = CallNextEventHandler(next_ref, event); + } + if (err == noErr) + return err; + + const QAInterface parentInterface = interface.navigate(QAccessible::Ancestor, 1); + const QAElement parentElement = accessibleHierarchyManager()->lookup(parentInterface); + + if (parentElement.isValid() == false) + return eventNotHandledErr; + + AXUIElementRef elementRef = parentElement.element(); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof(elementRef), &elementRef); + return noErr; +} +#endif + +struct IsWindowTest +{ + static inline bool test(const QAInterface &interface) + { + return (interface.role() == QAccessible::Window); + } +}; + +struct IsWindowAndNotDrawerOrSheetTest +{ + static inline bool test(const QAInterface &interface) + { + QWidget * const widget = qobject_cast(interface.object()); + return (interface.role() == QAccessible::Window && + widget && widget->isWindow() && + !qt_mac_is_macdrawer(widget) && + !qt_mac_is_macsheet(widget)); + } +}; + +/* + Navigates up the iterfaces ancestor hierachy until a QAccessibleInterface that + passes the Test is found. If we reach a interface that is a HIView we stop the + search and call AXUIElementCopyAttributeValue. +*/ +template +OSStatus navigateAncestors(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface, CFStringRef attribute) +{ + if (interface.isHIView()) + return CallNextEventHandler(next_ref, event); + + QAInterface current = interface; + QAElement element; + while (current.isValid()) { + if (TestType::test(interface)) { + element = accessibleHierarchyManager()->lookup(current); + break; + } + + // If we reach an InterfaceItem that is a HiView we can hand of the search to + // the system event handler. This is the common case. + if (current.isHIView()) { + CFTypeRef value = 0; + const QAElement currentElement = accessibleHierarchyManager()->lookup(current); + AXError err = AXUIElementCopyAttributeValue(currentElement.element(), attribute, &value); + AXUIElementRef newElement = (AXUIElementRef)value; + + if (err == noErr) + element = QAElement(newElement); + + if (newElement != 0) + CFRelease(newElement); + break; + } + + QAInterface next = current.parent(); + if (next.isValid() == false) + break; + if (next == current) + break; + current = next; + } + + if (element.isValid() == false) + return eventNotHandledErr; + + + AXUIElementRef elementRef = element.element(); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, + sizeof(elementRef), &elementRef); + return noErr; +} + +/* + Returns the top-level window for an interface, which is the closest ancestor interface that + has the Window role, but is not a sheet or a drawer. +*/ +#ifndef QT_MAC_USE_COCOA +static OSStatus handleWindowAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + return navigateAncestors(next_ref, event, interface, CFStringRef(QAXWindowAttribute)); +} + +/* + Returns the top-level window for an interface, which is the closest ancestor interface that + has the Window role. (Can also be a sheet or a drawer) +*/ +static OSStatus handleTopLevelUIElementAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + return navigateAncestors(next_ref, event, interface, CFStringRef(QAXTopLevelUIElementAttribute)); +} + +/* + Returns the tab buttons for an interface. +*/ +static OSStatus handleTabsAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + Q_UNUSED(next_ref); + if (isTabWidget(interface)) + return setAttributeValue(event, tabWidgetGetTabs(interface)); + else + return setAttributeValue(event, tabBarGetTabs(interface)); +} + +static OSStatus handlePositionAttribute(EventHandlerCallRef, EventRef event, const QAInterface &interface) +{ + QPoint qpoint(interface.rect().topLeft()); + HIPoint point; + point.x = qpoint.x(); + point.y = qpoint.y(); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeHIPoint, sizeof(point), &point); + return noErr; +} + +static OSStatus handleSizeAttribute(EventHandlerCallRef, EventRef event, const QAInterface &interface) +{ + QSize qSize(interface.rect().size()); + HISize size; + size.width = qSize.width(); + size.height = qSize.height(); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeHISize, sizeof(size), &size); + return noErr; +} + +static OSStatus handleSubroleAttribute(EventHandlerCallRef, EventRef event, const QAInterface &interface) +{ + const QCFString role = subrole(interface); + CFStringRef rolestr = (CFStringRef)role; + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof(rolestr), &rolestr); + return noErr; +} + +static OSStatus handleOrientationAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + QObject *const object = interface.object(); + Qt::Orientation orientation; + if (interface.role() == QAccessible::ScrollBar) { + orientation = scrollBarOrientation(interface); + } else if (QSplitterHandle * const splitter = qobject_cast(object)) { + // Qt reports the layout orientation, but we want the splitter handle orientation. + orientation = (splitter->orientation() == Qt::Horizontal) ? Qt::Vertical : Qt::Horizontal; + } else { + return CallNextEventHandler(next_ref, event); + } + const CFStringRef orientationString = (orientation == Qt::Vertical) + ? CFStringRef(QAXVerticalOrientationValue) : CFStringRef(QAXHorizontalOrientationValue); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFStringRef, sizeof(orientationString), &orientationString); + return noErr; +} + +/* + Figures out the next or previous contents for a splitter. +*/ +static OSStatus handleSplitterContentsAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface, QCFString nextOrPrev) +{ + if (interface.isValid() == false || interface.role() != QAccessible::Grip) + return eventNotHandledErr; + + const QAInterface parent = interface.parent(); + if (parent.isValid() == false) + return CallNextEventHandler(next_ref, event); + + if (parent.role() != QAccessible::Splitter) + return CallNextEventHandler(next_ref, event); + + const QSplitter * const splitter = qobject_cast(parent.object()); + if (splitter == 0) + return CallNextEventHandler(next_ref, event); + + QWidget * const splitterHandle = qobject_cast(interface.object()); + const int splitterHandleIndex = splitter->indexOf(splitterHandle); + const int widgetIndex = (nextOrPrev == QCFString(CFStringRef(QAXPreviousContentsAttribute))) ? splitterHandleIndex - 1 : splitterHandleIndex; + const QAElement contentsElement = accessibleHierarchyManager()->lookup(splitter->widget(widgetIndex), 0); + return setAttributeValue(event, QList() << contentsElement); +} + +/* + Creates a list of all splitter handles the splitter contains. +*/ +static OSStatus handleSplittersAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + const QSplitter * const splitter = qobject_cast(interface.object()); + if (splitter == 0) + return CallNextEventHandler(next_ref, event); + + accessibleHierarchyManager()->registerChildren(interface); + + QList handles; + const int visibleSplitterCount = splitter->count() -1; // skip first handle, it's always invisible. + for (int i = 0; i < visibleSplitterCount; ++i) + handles.append(accessibleHierarchyManager()->lookup(splitter->handle(i + 1), 0)); + + return setAttributeValue(event, handles); +} + +// This handler gets the scroll bars for a scroll area +static OSStatus handleScrollBarAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &scrollArea, Qt::Orientation orientation) +{ + QAElement scrollBar = scrollAreaGetScrollBar(scrollArea, orientation); + if (scrollBar.isValid() == false) + return CallNextEventHandler(next_ref, event); + + AXUIElementRef elementRef = scrollBar.element(); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof(elementRef), &elementRef); + return noErr; +} + +// This handler gets the contents for a scroll area or tab widget. +static OSStatus handleContentsAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + const QCFString mac_role = macRole(interface); + + QAElement contents; + + if (mac_role == kAXTabGroupRole) { + contents = tabWidgetGetContents(interface); + } else { + contents = scrollAreaGetContents(interface); + if (contents.isValid() == false) + return CallNextEventHandler(next_ref, event); + } + + return setAttributeValue(event, QList() << contents); +} + +static OSStatus handleRowsAttribute(EventHandlerCallRef, EventRef event, QAInterface &tableView) +{ + QList rows = lookup(tableView.children()); + + // kill the first row which is the horizontal header. + rows.removeAt(0); + + return setAttributeValue(event, rows); +} + +static OSStatus handleVisibleRowsAttribute(EventHandlerCallRef, EventRef event, QAInterface &tableView) +{ + QList visibleRows; + + QList rows = tableView.children(); + // kill the first row which is the horizontal header. + rows.removeAt(0); + + foreach (const QAInterface &interface, rows) + if ((interface.state() & QAccessible::Invisible) == false) + visibleRows.append(accessibleHierarchyManager()->lookup(interface)); + + return setAttributeValue(event, visibleRows); +} + +static OSStatus handleSelectedRowsAttribute(EventHandlerCallRef, EventRef event, QAInterface &tableView) +{ + QList selectedRows; + foreach (const QAInterface &interface, tableView.children()) + if ((interface.state() & QAccessible::Selected)) + selectedRows.append(accessibleHierarchyManager()->lookup(interface)); + + return setAttributeValue(event, selectedRows); +} + +static OSStatus getNamedAttribute(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + CFStringRef var; + GetEventParameter(event, kEventParamAccessibleAttributeName, typeCFStringRef, 0, + sizeof(var), 0, &var); + + if (CFStringCompare(var, CFStringRef(QAXChildrenAttribute), 0) == kCFCompareEqualTo) { + return handleChildrenAttribute(next_ref, event, interface); + } else if(CFStringCompare(var, CFStringRef(QAXTopLevelUIElementAttribute), 0) == kCFCompareEqualTo) { + return handleTopLevelUIElementAttribute(next_ref, event, interface); + } else if(CFStringCompare(var, CFStringRef(QAXWindowAttribute), 0) == kCFCompareEqualTo) { + return handleWindowAttribute(next_ref, event, interface); + } else if(CFStringCompare(var, CFStringRef(QAXParentAttribute), 0) == kCFCompareEqualTo) { + return handleParentAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXPositionAttribute), 0) == kCFCompareEqualTo) { + return handlePositionAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXSizeAttribute), 0) == kCFCompareEqualTo) { + return handleSizeAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXRoleAttribute), 0) == kCFCompareEqualTo) { + CFStringRef role = macRole(interface); +// ### +// QWidget * const widget = qobject_cast(interface.object()); +// if (role == CFStringRef(QAXUnknownRole) && widget && widget->isWindow()) +// role = CFStringRef(QAXWindowRole); + + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFStringRef, + sizeof(role), &role); + + } else if (CFStringCompare(var, CFStringRef(QAXEnabledAttribute), 0) == kCFCompareEqualTo) { + Boolean val = !((interface.state() & QAccessible::Unavailable)) + && !((interface.state() & QAccessible::Invisible)); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } else if (CFStringCompare(var, CFStringRef(QAXExpandedAttribute), 0) == kCFCompareEqualTo) { + Boolean val = (interface.state() & QAccessible::Expanded); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } else if (CFStringCompare(var, CFStringRef(QAXSelectedAttribute), 0) == kCFCompareEqualTo) { + Boolean val = (interface.state() & QAccessible::Selection); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } else if (CFStringCompare(var, CFStringRef(QAXFocusedAttribute), 0) == kCFCompareEqualTo) { + Boolean val = (interface.state() & QAccessible::Focus); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } else if (CFStringCompare(var, CFStringRef(QAXSelectedChildrenAttribute), 0) == kCFCompareEqualTo) { + const int cc = interface.childCount(); + QList selected; + for (int i = 1; i <= cc; ++i) { + const QAInterface child_iface = interface.navigate(QAccessible::Child, i); + if (child_iface.isValid() && child_iface.state() & QAccessible::Selected) + selected.append(accessibleHierarchyManager()->lookup(child_iface)); + } + + return setAttributeValue(event, selected); + + } else if (CFStringCompare(var, CFStringRef(QAXCloseButtonAttribute), 0) == kCFCompareEqualTo) { + if(interface.object() && interface.object()->isWidgetType()) { + Boolean val = true; //do we want to add a WState for this? + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXZoomButtonAttribute), 0) == kCFCompareEqualTo) { + if(interface.object() && interface.object()->isWidgetType()) { + QWidget *widget = (QWidget*)interface.object(); + Boolean val = (widget->windowFlags() & Qt::WindowMaximizeButtonHint); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXMinimizeButtonAttribute), 0) == kCFCompareEqualTo) { + if(interface.object() && interface.object()->isWidgetType()) { + QWidget *widget = (QWidget*)interface.object(); + Boolean val = (widget->windowFlags() & Qt::WindowMinimizeButtonHint); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXToolbarButtonAttribute), 0) == kCFCompareEqualTo) { + if(interface.object() && interface.object()->isWidgetType()) { + QWidget *widget = (QWidget*)interface.object(); + Boolean val = qobject_cast(widget) != 0; + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXGrowAreaAttribute), 0) == kCFCompareEqualTo) { + if(interface.object() && interface.object()->isWidgetType()) { + Boolean val = true; //do we want to add a WState for this? + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXMinimizedAttribute), 0) == kCFCompareEqualTo) { + if (interface.object() && interface.object()->isWidgetType()) { + QWidget *widget = (QWidget*)interface.object(); + Boolean val = (widget->windowState() & Qt::WindowMinimized); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeBoolean, + sizeof(val), &val); + } + } else if (CFStringCompare(var, CFStringRef(QAXSubroleAttribute), 0) == kCFCompareEqualTo) { + return handleSubroleAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXRoleDescriptionAttribute), 0) == kCFCompareEqualTo) { +#if !defined(QT_MAC_USE_COCOA) + if (HICopyAccessibilityRoleDescription) { + const CFStringRef roleDescription = HICopyAccessibilityRoleDescription(macRole(interface), 0); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFStringRef, + sizeof(roleDescription), &roleDescription); + } else +#endif + { + // Just use Qt::Description on 10.3 + handleStringAttribute(event, QAccessible::Description, interface); + } + } else if (CFStringCompare(var, CFStringRef(QAXTitleAttribute), 0) == kCFCompareEqualTo) { + const QAccessible::Role role = interface.role(); + const QAccessible::Text text = (QAccessible::Text)textForRoleAndAttribute(role, var); + handleStringAttribute(event, text, interface); + } else if (CFStringCompare(var, CFStringRef(QAXValueAttribute), 0) == kCFCompareEqualTo) { + const QAccessible::Role role = interface.role(); + const QAccessible::Text text = (QAccessible::Text)textForRoleAndAttribute(role, var); + if (role == QAccessible::CheckBox || role == QAccessible::RadioButton) { + int value = buttonValue(interface); + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeUInt32, sizeof(value), &value); + } else { + handleStringAttribute(event, text, interface); + } + } else if (CFStringCompare(var, CFStringRef(QAXDescriptionAttribute), 0) == kCFCompareEqualTo) { + const QAccessible::Role role = interface.role(); + const QAccessible::Text text = (QAccessible::Text)textForRoleAndAttribute(role, var); + handleStringAttribute(event, text, interface); + } else if (CFStringCompare(var, CFStringRef(QAXLinkedUIElementsAttribute), 0) == kCFCompareEqualTo) { + return CallNextEventHandler(next_ref, event); + } else if (CFStringCompare(var, CFStringRef(QAXHelpAttribute), 0) == kCFCompareEqualTo) { + const QAccessible::Role role = interface.role(); + const QAccessible::Text text = (QAccessible::Text)textForRoleAndAttribute(role, var); + handleStringAttribute(event, text, interface); + } else if (CFStringCompare(var, kAXTitleUIElementAttribute, 0) == kCFCompareEqualTo) { + return CallNextEventHandler(next_ref, event); + } else if (CFStringCompare(var, CFStringRef(QAXTabsAttribute), 0) == kCFCompareEqualTo) { + return handleTabsAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXMinValueAttribute), 0) == kCFCompareEqualTo) { + // tabs we first go to the tab bar which is child #2. + QAInterface tabBarInterface = interface.childAt(2); + return handleTabsAttribute(next_ref, event, tabBarInterface); + } else if (CFStringCompare(var, CFStringRef(QAXMinValueAttribute), 0) == kCFCompareEqualTo) { + if (interface.role() == QAccessible::RadioButton || interface.role() == QAccessible::CheckBox) { + uint value = 0; + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeUInt32, sizeof(value), &value); + } else { + return CallNextEventHandler(next_ref, event); + } + } else if (CFStringCompare(var, CFStringRef(QAXMaxValueAttribute), 0) == kCFCompareEqualTo) { + if (interface.role() == QAccessible::RadioButton || interface.role() == QAccessible::CheckBox) { + uint value = 2; + SetEventParameter(event, kEventParamAccessibleAttributeValue, typeUInt32, sizeof(value), &value); + } else { + return CallNextEventHandler(next_ref, event); + } + } else if (CFStringCompare(var, CFStringRef(QAXOrientationAttribute), 0) == kCFCompareEqualTo) { + return handleOrientationAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXPreviousContentsAttribute), 0) == kCFCompareEqualTo) { + return handleSplitterContentsAttribute(next_ref, event, interface, CFStringRef(QAXPreviousContentsAttribute)); + } else if (CFStringCompare(var, CFStringRef(QAXNextContentsAttribute), 0) == kCFCompareEqualTo) { + return handleSplitterContentsAttribute(next_ref, event, interface, CFStringRef(QAXNextContentsAttribute)); + } else if (CFStringCompare(var, CFStringRef(QAXSplittersAttribute), 0) == kCFCompareEqualTo) { + return handleSplittersAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXHorizontalScrollBarAttribute), 0) == kCFCompareEqualTo) { + return handleScrollBarAttribute(next_ref, event, interface, Qt::Horizontal); + } else if (CFStringCompare(var, CFStringRef(QAXVerticalScrollBarAttribute), 0) == kCFCompareEqualTo) { + return handleScrollBarAttribute(next_ref, event, interface, Qt::Vertical); + } else if (CFStringCompare(var, CFStringRef(QAXContentsAttribute), 0) == kCFCompareEqualTo) { + return handleContentsAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXRowsAttribute), 0) == kCFCompareEqualTo) { + return handleRowsAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXVisibleRowsAttribute), 0) == kCFCompareEqualTo) { + return handleVisibleRowsAttribute(next_ref, event, interface); + } else if (CFStringCompare(var, CFStringRef(QAXSelectedRowsAttribute), 0) == kCFCompareEqualTo) { + return handleSelectedRowsAttribute(next_ref, event, interface); + } else { + return CallNextEventHandler(next_ref, event); + } + return noErr; +} + +static OSStatus isNamedAttributeSettable(EventRef event, const QAInterface &interface) +{ + CFStringRef var; + GetEventParameter(event, kEventParamAccessibleAttributeName, typeCFStringRef, 0, + sizeof(var), 0, &var); + Boolean settable = false; + if (CFStringCompare(var, CFStringRef(QAXFocusedAttribute), 0) == kCFCompareEqualTo) { + settable = true; + } else { + for (int r = 0; text_bindings[r][0].qt != -1; r++) { + if(interface.role() == (QAccessible::Role)text_bindings[r][0].qt) { + for (int a = 1; text_bindings[r][a].qt != -1; a++) { + if (CFStringCompare(var, CFStringRef(text_bindings[r][a].mac), 0) == kCFCompareEqualTo) { + settable = text_bindings[r][a].settable; + break; + } + } + } + } + } + SetEventParameter(event, kEventParamAccessibleAttributeSettable, typeBoolean, + sizeof(settable), &settable); + return noErr; +} + +static OSStatus getChildAtPoint(EventHandlerCallRef next_ref, EventRef event, QAInterface &interface) +{ + Q_UNUSED(next_ref); + if (interface.isValid() == false) + return eventNotHandledErr; + + // Add the children for this interface to the global QAccessibelHierachyManager. + accessibleHierarchyManager()->registerChildren(interface); + + Point where; + GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, 0, sizeof(where), 0, &where); + const QAInterface childInterface = interface.childAt(where.h, where.v); + + if (childInterface.isValid() == false || childInterface == interface) + return eventNotHandledErr; + + const QAElement element = accessibleHierarchyManager()->lookup(childInterface); + if (element.isValid() == false) + return eventNotHandledErr; + + AXUIElementRef elementRef = element.element(); + CFRetain(elementRef); + SetEventParameter(event, kEventParamAccessibleChild, typeCFTypeRef, + sizeof(elementRef), &elementRef); + + return noErr; +} + +/* + Returns a list of actions the given interface supports. + Currently implemented by getting the interface role and deciding based on that. +*/ +static QList supportedPredefinedActions(const QAInterface &interface) +{ + QList actions; + switch (interface.role()) { + default: + // Most things can be pressed. + actions.append(QAccessible::Press); + break; + } + + return actions; +} + +/* + Translates a predefined QAccessible::Action to a Mac action constant. + Returns an empty string if the Qt Action has no mac equivalent. +*/ +static QCFString translateAction(const QAccessible::Action action) +{ + switch (action) { + case QAccessible::Press: + return CFStringRef(QAXPressAction); + break; + case QAccessible::Increase: + return CFStringRef(QAXIncrementAction); + break; + case QAccessible::Decrease: + return CFStringRef(QAXDecrementAction); + break; + case QAccessible::Accept: + return CFStringRef(QAXConfirmAction); + break; + case QAccessible::Select: + return CFStringRef(QAXPickAction); + break; + case QAccessible::Cancel: + return CFStringRef(QAXCancelAction); + break; + default: + return QCFString(); + break; + } +} + +/* + Translates between a Mac action constant and a QAccessible::Action. + Returns QAccessible::Default action if there is no Qt predefined equivalent. +*/ +static QAccessible::Action translateAction(const CFStringRef actionName) +{ + if(CFStringCompare(actionName, CFStringRef(QAXPressAction), 0) == kCFCompareEqualTo) { + return QAccessible::Press; + } else if(CFStringCompare(actionName, CFStringRef(QAXIncrementAction), 0) == kCFCompareEqualTo) { + return QAccessible::Increase; + } else if(CFStringCompare(actionName, CFStringRef(QAXDecrementAction), 0) == kCFCompareEqualTo) { + return QAccessible::Decrease; + } else if(CFStringCompare(actionName, CFStringRef(QAXConfirmAction), 0) == kCFCompareEqualTo) { + return QAccessible::Accept; + } else if(CFStringCompare(actionName, CFStringRef(QAXPickAction), 0) == kCFCompareEqualTo) { + return QAccessible::Select; + } else if(CFStringCompare(actionName, CFStringRef(QAXCancelAction), 0) == kCFCompareEqualTo) { + return QAccessible::Cancel; + } else { + return QAccessible::DefaultAction; + } +} +#endif // QT_MAC_USE_COCOA + +/* + Copies the translated names all supported actions for an interface into the kEventParamAccessibleActionNames + event parameter. +*/ +#ifndef QT_MAC_USE_COCOA +static OSStatus getAllActionNames(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + Q_UNUSED(next_ref); + + CFMutableArrayRef actions = 0; + GetEventParameter(event, kEventParamAccessibleActionNames, typeCFMutableArrayRef, 0, + sizeof(actions), 0, &actions); + + // Add supported predefined actions. + const QList predefinedActions = supportedPredefinedActions(interface); + for (int i = 0; i < predefinedActions.count(); ++i) { + const QCFString action = translateAction(predefinedActions.at(i)); + if (action != QCFString()) + qt_mac_append_cf_uniq(actions, action); + } + + // Add user actions + const int actionCount = interface.userActionCount(); + for (int i = 0; i < actionCount; ++i) { + const QString actionName = interface.actionText(i, QAccessible::Name); + qt_mac_append_cf_uniq(actions, QCFString::toCFStringRef(actionName)); + } + + return noErr; +} +#endif + +/* + Handles the perforNamedAction event. +*/ +#ifndef QT_MAC_USE_COCOA +static OSStatus performNamedAction(EventHandlerCallRef next_ref, EventRef event, const QAInterface& interface) +{ + Q_UNUSED(next_ref); + + CFStringRef act; + GetEventParameter(event, kEventParamAccessibleActionName, typeCFStringRef, 0, + sizeof(act), 0, &act); + + const QAccessible::Action action = translateAction(act); + + // Perform built-in action + if (action != QAccessible::DefaultAction) { + interface.doAction(action, QVariantList()); + return noErr; + } + + // Search for user-defined actions and perform it if found. + const int actCount = interface.userActionCount(); + const QString qAct = QCFString::toQString(act); + for(int i = 0; i < actCount; i++) { + if(interface.actionText(i, QAccessible::Name) == qAct) { + interface.doAction(i, QVariantList()); + break; + } + } + return noErr; +} + +static OSStatus setNamedAttribute(EventHandlerCallRef next_ref, EventRef event, const QAInterface &interface) +{ + Q_UNUSED(next_ref); + Q_UNUSED(event); + + CFStringRef var; + GetEventParameter(event, kEventParamAccessibleAttributeName, typeCFStringRef, 0, + sizeof(var), 0, &var); + if(CFStringCompare(var, CFStringRef(QAXFocusedAttribute), 0) == kCFCompareEqualTo) { + CFTypeRef val; + if(GetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, 0, + sizeof(val), 0, &val) == noErr) { + if(CFGetTypeID(val) == CFBooleanGetTypeID() && + CFEqual(static_cast(val), kCFBooleanTrue)) { + interface.doAction(QAccessible::SetFocus); + } + } + } else { + bool found = false; + for(int r = 0; text_bindings[r][0].qt != -1; r++) { + if(interface.role() == (QAccessible::Role)text_bindings[r][0].qt) { + for(int a = 1; text_bindings[r][a].qt != -1; a++) { + if(CFStringCompare(var, CFStringRef(text_bindings[r][a].mac), 0) == kCFCompareEqualTo) { + if(!text_bindings[r][a].settable) { + } else { + CFTypeRef val; + if(GetEventParameter(event, kEventParamAccessibleAttributeValue, typeCFTypeRef, 0, + sizeof(val), 0, &val) == noErr) { + if(CFGetTypeID(val) == CFStringGetTypeID()) + interface.setText((QAccessible::Text)text_bindings[r][a].qt, + QCFString::toQString(static_cast(val))); + + } + } + found = true; + break; + } + } + break; + } + } + } + return noErr; +} + +/* + This is the main accessibility event handler. +*/ +static OSStatus accessibilityEventHandler(EventHandlerCallRef next_ref, EventRef event, void *data) +{ + Q_UNUSED(data) + + // Return if this event is not a AccessibleGetNamedAttribute event. + const UInt32 eclass = GetEventClass(event); + if (eclass != kEventClassAccessibility) + return eventNotHandledErr; + + // Get the AXUIElementRef and QAInterface pointer + AXUIElementRef element = 0; + GetEventParameter(event, kEventParamAccessibleObject, typeCFTypeRef, 0, sizeof(element), 0, &element); + QAInterface interface = accessibleHierarchyManager()->lookup(element); + if (interface.isValid() == false) + return eventNotHandledErr; + + const UInt32 ekind = GetEventKind(event); + OSStatus status = noErr; + switch (ekind) { + case kEventAccessibleGetAllAttributeNames: + status = getAllAttributeNames(event, interface, next_ref); + break; + case kEventAccessibleGetNamedAttribute: + status = getNamedAttribute(next_ref, event, interface); + break; + case kEventAccessibleIsNamedAttributeSettable: + status = isNamedAttributeSettable(event, interface); + break; + case kEventAccessibleGetChildAtPoint: + status = getChildAtPoint(next_ref, event, interface); + break; + case kEventAccessibleGetAllActionNames: + status = getAllActionNames(next_ref, event, interface); + break; + case kEventAccessibleGetFocusedChild: + status = CallNextEventHandler(next_ref, event); + break; + case kEventAccessibleSetNamedAttribute: + status = setNamedAttribute(next_ref, event, interface); + break; + case kEventAccessiblePerformNamedAction: + status = performNamedAction(next_ref, event, interface); + break; + default: + status = CallNextEventHandler(next_ref, event); + break; + }; + return status; +} +#endif + +void QAccessible::initialize() +{ +#ifndef QT_MAC_USE_COCOA + registerQtAccessibilityHIObjectSubclass(); + installApplicationEventhandler(); +#endif +} + +// Sets thre root object for the application +void QAccessible::setRootObject(QObject *object) +{ + // Call installed root object handler if we have one + if (rootObjectHandler) { + rootObjectHandler(object); + return; + } + + rootObject = object; +} + +void QAccessible::cleanup() +{ + accessibleHierarchyManager()->reset(); +#ifndef QT_MAC_USE_COCOA + removeEventhandler(applicationEventHandlerUPP); + removeEventhandler(objectCreateEventHandlerUPP); + removeEventhandler(accessibilityEventHandlerUPP); +#endif +} + +void QAccessible::updateAccessibility(QObject *object, int child, Event reason) +{ + // Call installed update handler if we have one. + if (updateHandler) { + updateHandler(object, child, reason); + return; + } + +#ifndef QT_MAC_USE_COCOA + // Return if the mac accessibility is not enabled. + if(!AXAPIEnabled()) + return; + + // Work around crash, disable accessiblity for focus frames. + if (qstrcmp(object->metaObject()->className(), "QFocusFrame") == 0) + return; + +// qDebug() << "updateAccessibility" << object << child << hex << reason; + + if (reason == ObjectShow) { + QAInterface interface = QAInterface(QAccessible::queryAccessibleInterface(object), child); + accessibleHierarchyManager()->registerInterface(interface); + } + + const QAElement element = accessibleHierarchyManager()->lookup(object, child); + if (element.isValid() == false) + return; + + + CFStringRef notification = 0; + if(object && object->isWidgetType() && reason == ObjectCreated) { + notification = CFStringRef(QAXWindowCreatedNotification); + } else if(reason == ValueChanged) { + notification = CFStringRef(QAXValueChangedNotification); + } else if(reason == MenuStart) { + notification = CFStringRef(QAXMenuOpenedNotification); + } else if(reason == MenuEnd) { + notification = CFStringRef(QAXMenuClosedNotification); + } else if(reason == LocationChanged) { + notification = CFStringRef(QAXWindowMovedNotification); + } else if(reason == ObjectShow || reason == ObjectHide ) { + // When a widget is deleted we get a ObjectHide before the destroyed(QObject *) + // signal is emitted (which makes sense). However, at this point we are in the + // middle of the QWidget destructor which means that we have to be careful when + // using the widget pointer. Since we can't control what the accessibilty interfaces + // does when navigate() is called below we ignore the hide update in this case. + // (the widget will be deleted soon anyway.) + extern QWidgetPrivate * qt_widget_private(QWidget *); + if (QWidget *widget = qobject_cast(object)) { + if (qt_widget_private(widget)->data.in_destructor) + return; + + // Check widget parent as well, special case for preventing crash + // when the viewport() of an abstract scroll area is hidden when + // the QWidget destructor hides all its children. + QWidget *parentWidget = widget->parentWidget(); + if (parentWidget && qt_widget_private(parentWidget)->data.in_destructor) + return; + } + + // There is no equivalent Mac notification for ObjectShow/Hide, so we call HIObjectSetAccessibilityIgnored + // and isItIntersting which will mark the HIObject accociated with the element as ignored if the + // QAccessible::Invisible state bit is set. + QAInterface interface = accessibleHierarchyManager()->lookup(element); + if (interface.isValid()) { + HIObjectSetAccessibilityIgnored(element.object(), !isItInteresting(interface)); + } + + // If the interface manages its own children, also check if we should ignore those. + if (isItemView(interface) == false && managesChildren(interface)) { + for (int i = 1; i <= interface.childCount(); ++i) { + QAInterface childInterface = interface.navigate(QAccessible::Child, i); + if (childInterface.isValid() && childInterface.isHIView() == false) { + const QAElement element = accessibleHierarchyManager()->lookup(childInterface); + if (element.isValid()) { + HIObjectSetAccessibilityIgnored(element.object(), !isItInteresting(childInterface)); + } + } + } + } + + } else if(reason == Focus) { + if(object && object->isWidgetType()) { + QWidget *w = static_cast(object); + if(w->isWindow()) + notification = CFStringRef(QAXFocusedWindowChangedNotification); + else + notification = CFStringRef(QAXFocusedUIElementChangedNotification); + } + } + + if (!notification) + return; + + AXNotificationHIObjectNotify(notification, element.object(), element.id()); +#endif +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessible_mac_carbon.cpp b/src/gui/accessible/qaccessible_mac_carbon.cpp new file mode 100644 index 0000000000..2e8a7628e8 --- /dev/null +++ b/src/gui/accessible/qaccessible_mac_carbon.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +static OSStatus applicationEventHandler(EventHandlerCallRef next_ref, EventRef event, void *data); +static EventHandlerUPP applicationEventHandlerUPP = 0; +static EventTypeSpec application_events[] = { + { kEventClassAccessibility, kEventAccessibleGetChildAtPoint }, + { kEventClassAccessibility, kEventAccessibleGetNamedAttribute } +}; + +static CFStringRef kObjectQtAccessibility = CFSTR("com.trolltech.qt.accessibility"); +static EventHandlerUPP objectCreateEventHandlerUPP = 0; +static EventTypeSpec objectCreateEvents[] = { + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectInitialize }, + { kEventClassHIObject, kEventHIObjectDestruct }, + { kEventClassHIObject, kEventHIObjectPrintDebugInfo } +}; + +static OSStatus accessibilityEventHandler(EventHandlerCallRef next_ref, EventRef event, void *data); +static EventHandlerUPP accessibilityEventHandlerUPP = 0; +static EventTypeSpec accessibilityEvents[] = { + { kEventClassAccessibility, kEventAccessibleGetChildAtPoint }, + { kEventClassAccessibility, kEventAccessibleGetFocusedChild }, + { kEventClassAccessibility, kEventAccessibleGetAllAttributeNames }, + { kEventClassAccessibility, kEventAccessibleGetNamedAttribute }, + { kEventClassAccessibility, kEventAccessibleSetNamedAttribute }, + { kEventClassAccessibility, kEventAccessibleIsNamedAttributeSettable }, + { kEventClassAccessibility, kEventAccessibleGetAllActionNames }, + { kEventClassAccessibility, kEventAccessiblePerformNamedAction }, + { kEventClassAccessibility, kEventAccessibleGetNamedActionDescription } +}; + +static void installAcessibilityEventHandler(HIObjectRef hiObject) +{ + if (!accessibilityEventHandlerUPP) + accessibilityEventHandlerUPP = NewEventHandlerUPP(accessibilityEventHandler); + + InstallHIObjectEventHandler(hiObject, accessibilityEventHandlerUPP, + GetEventTypeCount(accessibilityEvents), + accessibilityEvents, 0, 0); +} + +static OSStatus objectCreateEventHandler(EventHandlerCallRef next_ref, EventRef event, void *data) +{ + Q_UNUSED(data) + Q_UNUSED(event) + Q_UNUSED(next_ref) + return noErr; +} + +static void registerQtAccessibilityHIObjectSubclass() +{ + if (!objectCreateEventHandlerUPP) + objectCreateEventHandlerUPP = NewEventHandlerUPP(objectCreateEventHandler); + OSStatus err = HIObjectRegisterSubclass(kObjectQtAccessibility, 0, 0, objectCreateEventHandlerUPP, + GetEventTypeCount(objectCreateEvents), objectCreateEvents, 0, 0); + if (err && err != hiObjectClassExistsErr) + qWarning("qaccessible_mac internal error: Could not register accessibility HIObject subclass"); +} + +static void installApplicationEventhandler() +{ + if (!applicationEventHandlerUPP) + applicationEventHandlerUPP = NewEventHandlerUPP(applicationEventHandler); + + OSStatus err = InstallApplicationEventHandler(applicationEventHandlerUPP, + GetEventTypeCount(application_events), application_events, + 0, 0); + + if (err && err != eventHandlerAlreadyInstalledErr) + qWarning("qaccessible_mac internal error: Could not install application accessibility event handler"); +} + +static void removeEventhandler(EventHandlerUPP eventHandler) +{ + if (eventHandler) { + DisposeEventHandlerUPP(eventHandler); + eventHandler = 0; + } +} diff --git a/src/gui/accessible/qaccessible_mac_cocoa.mm b/src/gui/accessible/qaccessible_mac_cocoa.mm new file mode 100644 index 0000000000..60b967ab39 --- /dev/null +++ b/src/gui/accessible/qaccessible_mac_cocoa.mm @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qaccessible.h" +#include "qaccessible_mac_p.h" +#include "qdebug.h" +#include "qtabwidget.h" + +#include +#include +#include + + +#ifndef QT_NO_ACCESSIBILITY + +#ifdef QT_MAC_USE_COCOA + +QT_BEGIN_NAMESPACE + +//#define MAC_ACCESSIBILTY_DEVELOPER_MODE + +#ifndef QT_NO_DEBUG_STREAM +#ifdef MAC_ACCESSIBILTY_DEVELOPER_MODE +#define MAC_ACCESSIBILTY_DEBUG QT_PREPEND_NAMESPACE(qDebug) +#else +#define MAC_ACCESSIBILTY_DEBUG if (0) QT_PREPEND_NAMESPACE(qDebug) +#endif +#else +#define MAC_ACCESSIBILTY_DEBUG if (0) QT_PREPEND_NAMESPACE(QNoDebug) +#endif + +typedef QMap QMacAccessibiltyRoleMap; +Q_GLOBAL_STATIC(QMacAccessibiltyRoleMap, qMacAccessibiltyRoleMap); + +static QAInterface interfaceForView(QT_MANGLE_NAMESPACE(QCocoaView) *view) +{ + return QAInterface(QAccessible::queryAccessibleInterface([view qt_qwidget])); +} + +/* + Set up mappings from Qt accessibilty roles to Mac accessibilty roles. +*/ +static void populateRoleMap() +{ + QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap(); + roleMap[QAccessible::MenuItem] = NSAccessibilityMenuItemRole; + roleMap[QAccessible::MenuBar] = NSAccessibilityMenuBarRole; + roleMap[QAccessible::ScrollBar] = NSAccessibilityScrollBarRole; + roleMap[QAccessible::Grip] = NSAccessibilityGrowAreaRole; + roleMap[QAccessible::Window] = NSAccessibilityWindowRole; + roleMap[QAccessible::Dialog] = NSAccessibilityWindowRole; + roleMap[QAccessible::AlertMessage] = NSAccessibilityWindowRole; + roleMap[QAccessible::ToolTip] = NSAccessibilityWindowRole; + roleMap[QAccessible::HelpBalloon] = NSAccessibilityWindowRole; + roleMap[QAccessible::PopupMenu] = NSAccessibilityMenuRole; + roleMap[QAccessible::Application] = NSAccessibilityApplicationRole; + roleMap[QAccessible::Pane] = NSAccessibilityGroupRole; + roleMap[QAccessible::Grouping] = NSAccessibilityGroupRole; + roleMap[QAccessible::Separator] = NSAccessibilitySplitterRole; + roleMap[QAccessible::ToolBar] = NSAccessibilityToolbarRole; + roleMap[QAccessible::PageTab] = NSAccessibilityRadioButtonRole; + roleMap[QAccessible::ButtonMenu] = NSAccessibilityMenuButtonRole; + roleMap[QAccessible::ButtonDropDown] = NSAccessibilityPopUpButtonRole; + roleMap[QAccessible::SpinBox] = NSAccessibilityIncrementorRole; + roleMap[QAccessible::Slider] = NSAccessibilitySliderRole; + roleMap[QAccessible::ProgressBar] = NSAccessibilityProgressIndicatorRole; + roleMap[QAccessible::ComboBox] = NSAccessibilityPopUpButtonRole; + roleMap[QAccessible::RadioButton] = NSAccessibilityRadioButtonRole; + roleMap[QAccessible::CheckBox] = NSAccessibilityCheckBoxRole; + roleMap[QAccessible::StaticText] = NSAccessibilityStaticTextRole; + roleMap[QAccessible::Table] = NSAccessibilityTableRole; + roleMap[QAccessible::StatusBar] = NSAccessibilityStaticTextRole; + roleMap[QAccessible::Column] = NSAccessibilityColumnRole; + roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole; + roleMap[QAccessible::Row] = NSAccessibilityRowRole; + roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole; + roleMap[QAccessible::Cell] = NSAccessibilityTextFieldRole; + roleMap[QAccessible::PushButton] = NSAccessibilityButtonRole; + roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole; + roleMap[QAccessible::Link] = NSAccessibilityTextFieldRole; + roleMap[QAccessible::Indicator] = NSAccessibilityValueIndicatorRole; + roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole; + roleMap[QAccessible::List] = NSAccessibilityListRole; + roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole; + roleMap[QAccessible::Cell] = NSAccessibilityStaticTextRole; +} + +/* + Returns a Mac accessibility role for the given interface, or + NSAccessibilityUnknownRole if no role mapping is found. +*/ +static NSString *macRoleForInterface(QAInterface interface) +{ + const QAccessible::Role qtRole = interface.role(); + QMacAccessibiltyRoleMap &roleMap = *qMacAccessibiltyRoleMap(); + + if (roleMap.isEmpty()) + populateRoleMap(); + + MAC_ACCESSIBILTY_DEBUG() << "role for" << interface.object() << "interface role" << hex << qtRole; + + if (roleMap.contains(qtRole)) { + MAC_ACCESSIBILTY_DEBUG() << "return" << roleMap[qtRole]; + return roleMap[qtRole]; + } + + MAC_ACCESSIBILTY_DEBUG() << "return NSAccessibilityUnknownRole"; + return NSAccessibilityUnknownRole; +} + +/* + Is the interface a QTabBar embedded in a QTabWidget? + (as opposed to a stand-alone tab bar) +*/ +static bool isEmbeddedTabBar(const QAInterface &interface) +{ + QObject *object = interface.object(); + if (interface.role() == QAccessible::PageTabList && object) + return (qobject_cast(object->parent())); + + return false; +} + +static bool isInterfaceIgnored(QAInterface interface) +{ + // Mac accessibility does not have an attribute that corresponds to the + // Invisible/Offscreen state. Use the ignore facility to disable them. + const QAccessible::State state = interface.state(); + if (state & QAccessible::Invisible || + state & QAccessible::Offscreen ) + return false; + + // Hide QTabBars that has a QTabWidget parent (the QTabWidget handles the accessibility) + if (isEmbeddedTabBar(interface)) + return false; + + if (QObject * const object = interface.object()) { + const QString className = QLatin1String(object->metaObject()->className()); + + // Prevent VoiceOver from focusing on tool tips by ignoring those + // interfaces. Shifting VoiceOver focus to the tool tip is confusing + // and the contents of the tool tip is avalible through the description + // attribute anyway. + if (className == QLatin1String("QTipLabel")) + return false; + } + + // Hide interfaces with an unknown role. When developing it's often useful to disable + // this check to see all interfaces in the hierarchy. +#ifndef MAC_ACCESSIBILTY_DEVELOPER_MODE + return [macRoleForInterface(interface) isEqualToString: NSAccessibilityUnknownRole]; +#else + return NO; +#endif +} + +QT_END_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QCocoaView) (Accessibility) + +- (BOOL)accessibilityIsIgnored +{ + QT_PREPEND_NAMESPACE(QAInterface) interface = QT_PREPEND_NAMESPACE(interfaceForView)(self); + return isInterfaceIgnored(interface); +} + +- (NSArray *)accessibilityAttributeNames +{ + QT_PREPEND_NAMESPACE(QAInterface) interface = QT_PREPEND_NAMESPACE(interfaceForView)(self); + + static NSArray *attributes = nil; + if (attributes == nil) { + attributes = [super accessibilityAttributeNames]; + + } + return attributes; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + MAC_ACCESSIBILTY_DEBUG() << "accessibilityAttributeValue" << self << + QT_PREPEND_NAMESPACE(QCFString)::toQString(reinterpret_cast(attribute)); + + QT_PREPEND_NAMESPACE(QAInterface) interface = QT_PREPEND_NAMESPACE(interfaceForView)(self); + + // Switch on the attribute name and call the appropriate handler function. + // Pass the call on to the NSView class for attributes we don't handle. + if ([attribute isEqualToString:@"AXRole"]) { + return macRoleForInterface(interface); + } else { + return [super accessibilityAttributeValue:attribute]; + } +} + +@end + +#endif // QT_MAC_USE_COCOA + +#endif // QT_NO_ACCESSIBILITY + diff --git a/src/gui/accessible/qaccessible_mac_p.h b/src/gui/accessible/qaccessible_mac_p.h new file mode 100644 index 0000000000..9acb5d3aef --- /dev/null +++ b/src/gui/accessible/qaccessible_mac_p.h @@ -0,0 +1,479 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLE_MAC_P_H +#define QACCESSIBLE_MAC_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 +#include +#include +#include +#include + +//#define Q_ACCESSIBLE_MAC_DEBUG + +QT_BEGIN_NAMESPACE + +/* + QAccessibleInterfaceWrapper wraps QAccessibleInterface and adds + a ref count. QAccessibleInterfaceWrapper is a "by-value" class. +*/ +class Q_AUTOTEST_EXPORT QAccessibleInterfaceWrapper +{ +public: + QAccessibleInterfaceWrapper() + : interface(0), childrenIsRegistered(new bool(false)), refCount(new int(1)) { } + + QAccessibleInterfaceWrapper(QAccessibleInterface *interface) + :interface(interface), childrenIsRegistered(new bool(false)), refCount(new int(1)) { } + + ~QAccessibleInterfaceWrapper() + { + if (--(*refCount) == 0) { + delete interface; + delete refCount; + delete childrenIsRegistered; + } + } + + QAccessibleInterfaceWrapper(const QAccessibleInterfaceWrapper &other) + :interface(other.interface), childrenIsRegistered(other.childrenIsRegistered), refCount(other.refCount) + { + ++(*refCount); + } + + void operator=(const QAccessibleInterfaceWrapper &other) + { + if (other.interface == interface) + return; + + if (--(*refCount) == 0) { + delete interface; + delete refCount; + delete childrenIsRegistered; + } + + interface = other.interface; + childrenIsRegistered = other.childrenIsRegistered; + refCount = other.refCount; + ++(*refCount); + } + + QAccessibleInterface *interface; + bool *childrenIsRegistered; +private: + int *refCount; +}; + +/* + QAInterface represents one accessiblity item. It hides the fact that + one QAccessibleInterface may represent more than one item, and it also + automates the memory management for QAccessibleInterfaces using the + QAccessibleInterfaceWrapper wrapper class. + + It has the same API as QAccessibleInterface, minus the child parameter + in the functions. +*/ +class Q_AUTOTEST_EXPORT QAInterface : public QAccessible +{ +public: + QAInterface() + : base(QAccessibleInterfaceWrapper()) + { } + + QAInterface(QAccessibleInterface *interface, int child = 0) + { + if (interface == 0 || child > interface->childCount()) { + base = QAccessibleInterfaceWrapper(); + } else { + base = QAccessibleInterfaceWrapper(interface); + m_cachedObject = interface->object(); + this->child = child; + } + } + + QAInterface(QAccessibleInterfaceWrapper wrapper, int child = 0) + :base(wrapper), m_cachedObject(wrapper.interface->object()), child(child) + { } + + QAInterface(const QAInterface &other, int child) + { + if (other.isValid() == false || child > other.childCount()) { + base = QAccessibleInterfaceWrapper(); + } else { + base = other.base; + m_cachedObject = other.m_cachedObject; + this->child = child; + } + } + + bool operator==(const QAInterface &other) const; + bool operator!=(const QAInterface &other) const; + + inline QString actionText (int action, Text text) const + { return base.interface->actionText(action, text, child); } + + QAInterface childAt(int x, int y) const + { + if (!checkValid()) + return QAInterface(); + + const int foundChild = base.interface->childAt(x, y); + + if (foundChild == -1) + return QAInterface(); + + if (child == 0) + return navigate(QAccessible::Child, foundChild); + + if (foundChild == child) + return *this; + return QAInterface(); + } + + int indexOfChild(const QAInterface &child) const + { + if (!checkValid()) + return -1; + + if (*this != child.parent()) + return -1; + + if (object() == child.object()) + return child.id(); + + return base.interface->indexOfChild(child.base.interface); + } + + inline int childCount() const + { + if (!checkValid()) + return 0; + + if (child != 0) + return 0; + return base.interface->childCount(); + } + + QList children() const + { + if (!checkValid()) + return QList(); + + QList children; + for (int i = 1; i <= childCount(); ++i) { + children.append(navigate(QAccessible::Child, i)); + } + return children; + } + + QAInterface childAt(int index) const + { + return navigate(QAccessible::Child, index); + } + + inline void doAction(int action, const QVariantList ¶ms = QVariantList()) const + { + if (!checkValid()) + return; + + base.interface->doAction(action, child, params); + } + + QAInterface navigate(RelationFlag relation, int entry) const; + + inline QObject * object() const + { + if (!checkValid()) + return 0; + + return base.interface->object(); + } + + QAInterface objectInterface() const + { + if (!checkValid()) + return QAInterface(); + + QObject *obj = object(); + QAInterface current = *this; + while (obj == 0) + { + QAInterface parent = current.parent(); + if (parent.isValid() == false) + break; + obj = parent.object(); + current = parent; + } + return current; + } + + inline HIObjectRef hiObject() const + { + if (!checkValid()) + return 0; + QWidget * const widget = qobject_cast(object()); + if (widget) + return (HIObjectRef)widget->winId(); + else + return 0; + } + + inline QObject * cachedObject() const + { + if (!checkValid()) + return 0; + return m_cachedObject; + } + + inline QRect rect() const + { + if (!checkValid()) + return QRect(); + return base.interface->rect(child); + } + + inline Role role() const + { + if (!checkValid()) + return QAccessible::NoRole; + return base.interface->role(child); + } + + inline void setText(Text t, const QString &text) const + { + if (!checkValid()) + return; + base.interface->setText(t, child, text); + } + + inline State state() const + { + if (!checkValid()) + return 0; + return base.interface->state(child); + } + + inline QString text (Text text) const + { + if (!checkValid()) + return QString(); + return base.interface->text(text, child); + } + + inline QString value() const + { return text(QAccessible::Value); } + + inline QString name() const + { return text(QAccessible::Name); } + + inline int userActionCount() const + { + if (!checkValid()) + return 0; + return base.interface->userActionCount(child); + } + + inline QString className() const + { + if (!checkValid()) + return QString(); + return QLatin1String(base.interface->object()->metaObject()->className()); + } + + inline bool isHIView() const + { return (child == 0 && object() != 0); } + + inline int id() const + { return child; } + + inline bool isValid() const + { + return (base.interface != 0 && base.interface->isValid()); + } + + QAInterface parent() const + { return navigate(QAccessible::Ancestor, 1); } + + QAccessibleInterfaceWrapper interfaceWrapper() const + { return base; } + +protected: + bool checkValid() const + { + const bool valid = isValid(); +#ifdef Q_ACCESSIBLE_MAC_DEBUG + if (!valid) + qFatal("QAccessible_mac: tried to use invalid interface."); +#endif + return valid; + } + + QAccessibleInterfaceWrapper base; + QObject *m_cachedObject; + int child; +}; + +Q_AUTOTEST_EXPORT QDebug operator<<(QDebug debug, const QAInterface &interface); + +/* + QAElement is a thin wrapper around an AXUIElementRef that automates + the ref-counting. +*/ +class Q_AUTOTEST_EXPORT QAElement +{ +public: + QAElement(); + explicit QAElement(AXUIElementRef elementRef); + QAElement(const QAElement &element); + QAElement(HIObjectRef, int child); + ~QAElement(); + + inline HIObjectRef object() const + { +#ifndef Q_WS_MAC64 + return AXUIElementGetHIObject(elementRef); +#else + return 0; +#endif + } + + inline int id() const + { + UInt64 theId; +#ifndef QT_MAC_USE_COCOA + AXUIElementGetIdentifier(elementRef, &theId); +#else + theId = 0; +#endif + return theId; + } + + inline AXUIElementRef element() const + { + return elementRef; + } + + inline bool isValid() const + { + return (elementRef != 0); + } + + void operator=(const QAElement &other); + bool operator==(const QAElement &other) const; +private: + AXUIElementRef elementRef; +}; + + +class QInterfaceFactory +{ +public: + virtual QAInterface interface(UInt64 identifier) = 0; + virtual QAElement element(int id) = 0; + virtual QAElement element(const QAInterface &interface) + { + return element(interface.id()); + } + virtual void registerChildren() = 0; + virtual ~QInterfaceFactory() {} +}; + +/* + QAccessibleHierarchyManager bridges the Mac and Qt accessibility hierarchies. + There is a one-to-one relationship between QAElements on the Mac side + and QAInterfaces on the Qt side, and this class provides lookup functions + that translates between these to items. + + The identity of a QAInterface is determined by its QAccessibleInterface and + child identifier, and the identity of a QAElement is determined by its + HIObjectRef and identifier. + + QAccessibleHierarchyManager receives QObject::destroyed() signals and deletes + the accessibility objects for destroyed objects. +*/ +class Q_AUTOTEST_EXPORT QAccessibleHierarchyManager : public QObject +{ +Q_OBJECT +public: + ~QAccessibleHierarchyManager() { reset(); } + static QAccessibleHierarchyManager *instance(); + void reset(); + + QAElement registerInterface(QObject *object, int child); + QAElement registerInterface(const QAInterface &interface); + void registerInterface(QObject *object, HIObjectRef hiobject, QInterfaceFactory *interfaceFactory); + + void registerChildren(const QAInterface &interface); + + QAInterface lookup(const AXUIElementRef &element); + QAInterface lookup(const QAElement &element); + QAElement lookup(const QAInterface &interface); + QAElement lookup(QObject * const object, int id); +private slots: + void objectDestroyed(QObject *); +private: + typedef QHash QObjectElementHash; + typedef QHash HIObjectInterfaceHash; + typedef QHash QObjectHIObjectHash; + + QObjectElementHash qobjectElementHash; + HIObjectInterfaceHash hiobjectInterfaceHash; + QObjectHIObjectHash qobjectHiobjectHash; +}; + +Q_AUTOTEST_EXPORT bool isItInteresting(const QAInterface &interface); + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/accessible/qaccessible_unix.cpp b/src/gui/accessible/qaccessible_unix.cpp new file mode 100644 index 0000000000..f04d075543 --- /dev/null +++ b/src/gui/accessible/qaccessible_unix.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessible.h" +#include "qaccessiblebridge.h" + +#ifndef QT_NO_ACCESSIBILITY + +#include "qcoreapplication.h" +#include "qmutex.h" +#include "qvector.h" +#include "private/qfactoryloader_p.h" + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QAccessibleBridgeFactoryInterface_iid, QLatin1String("/accessiblebridge"))) +#endif +Q_GLOBAL_STATIC(QVector, bridges) +static bool isInit = false; + +void QAccessible::initialize() +{ + if (isInit) + return; + isInit = true; + + if (qgetenv("QT_ACCESSIBILITY") != "1") + return; +#ifndef QT_NO_LIBRARY + const QStringList l = loader()->keys(); + for (int i = 0; i < l.count(); ++i) { + if (QAccessibleBridgeFactoryInterface *factory = + qobject_cast(loader()->instance(l.at(i)))) { + QAccessibleBridge * bridge = factory->create(l.at(i)); + if (bridge) + bridges()->append(bridge); + } + } +#endif +} + +void QAccessible::cleanup() +{ + qDeleteAll(*bridges()); +} + +void QAccessible::updateAccessibility(QObject *o, int who, Event reason) +{ + Q_ASSERT(o); + + if (updateHandler) { + updateHandler(o, who, reason); + return; + } + + initialize(); + if (bridges()->isEmpty()) + return; + + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(o); + if (!iface) + return; + + for (int i = 0; i < bridges()->count(); ++i) + bridges()->at(i)->notifyAccessibilityUpdate(reason, iface, who); + delete iface; +} + +void QAccessible::setRootObject(QObject *o) +{ + if (rootObjectHandler) { + rootObjectHandler(o); + return; + } + + initialize(); + if (bridges()->isEmpty()) + return; + + if (!o) + return; + + for (int i = 0; i < bridges()->count(); ++i) { + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(o); + bridges()->at(i)->setRootObject(iface); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY + diff --git a/src/gui/accessible/qaccessible_win.cpp b/src/gui/accessible/qaccessible_win.cpp new file mode 100644 index 0000000000..caa21043d8 --- /dev/null +++ b/src/gui/accessible/qaccessible_win.cpp @@ -0,0 +1,1217 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qaccessible.h" +#ifndef QT_NO_ACCESSIBILITY + +#include "qapplication.h" +#include +#include "qmessagebox.h" // ### dependency +#include "qt_windows.h" +#include "qwidget.h" +#include "qsettings.h" + +#include +#if !defined(WINABLEAPI) +# if defined(Q_WS_WINCE) +# include +# endif +# include +#endif + +#include +#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) +#include +#endif + +#ifdef Q_WS_WINCE +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +//#define DEBUG_SHOW_ATCLIENT_COMMANDS +#ifdef DEBUG_SHOW_ATCLIENT_COMMANDS +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +static const char *roleString(QAccessible::Role role) +{ + static const char *roles[] = { + "NoRole" /* = 0x00000000 */, + "TitleBar" /* = 0x00000001 */, + "MenuBar" /* = 0x00000002 */, + "ScrollBar" /* = 0x00000003 */, + "Grip" /* = 0x00000004 */, + "Sound" /* = 0x00000005 */, + "Cursor" /* = 0x00000006 */, + "Caret" /* = 0x00000007 */, + "AlertMessage" /* = 0x00000008 */, + "Window" /* = 0x00000009 */, + "Client" /* = 0x0000000A */, + "PopupMenu" /* = 0x0000000B */, + "MenuItem" /* = 0x0000000C */, + "ToolTip" /* = 0x0000000D */, + "Application" /* = 0x0000000E */, + "Document" /* = 0x0000000F */, + "Pane" /* = 0x00000010 */, + "Chart" /* = 0x00000011 */, + "Dialog" /* = 0x00000012 */, + "Border" /* = 0x00000013 */, + "Grouping" /* = 0x00000014 */, + "Separator" /* = 0x00000015 */, + "ToolBar" /* = 0x00000016 */, + "StatusBar" /* = 0x00000017 */, + "Table" /* = 0x00000018 */, + "ColumnHeader" /* = 0x00000019 */, + "RowHeader" /* = 0x0000001A */, + "Column" /* = 0x0000001B */, + "Row" /* = 0x0000001C */, + "Cell" /* = 0x0000001D */, + "Link" /* = 0x0000001E */, + "HelpBalloon" /* = 0x0000001F */, + "Assistant" /* = 0x00000020 */, + "List" /* = 0x00000021 */, + "ListItem" /* = 0x00000022 */, + "Tree" /* = 0x00000023 */, + "TreeItem" /* = 0x00000024 */, + "PageTab" /* = 0x00000025 */, + "PropertyPage" /* = 0x00000026 */, + "Indicator" /* = 0x00000027 */, + "Graphic" /* = 0x00000028 */, + "StaticText" /* = 0x00000029 */, + "EditableText" /* = 0x0000002A */, // Editable, selectable, etc. + "PushButton" /* = 0x0000002B */, + "CheckBox" /* = 0x0000002C */, + "RadioButton" /* = 0x0000002D */, + "ComboBox" /* = 0x0000002E */, + "DropList" /* = 0x0000002F */, // commented out + "ProgressBar" /* = 0x00000030 */, + "Dial" /* = 0x00000031 */, + "HotkeyField" /* = 0x00000032 */, + "Slider" /* = 0x00000033 */, + "SpinBox" /* = 0x00000034 */, + "Canvas" /* = 0x00000035 */, + "Animation" /* = 0x00000036 */, + "Equation" /* = 0x00000037 */, + "ButtonDropDown" /* = 0x00000038 */, + "ButtonMenu" /* = 0x00000039 */, + "ButtonDropGrid" /* = 0x0000003A */, + "Whitespace" /* = 0x0000003B */, + "PageTabList" /* = 0x0000003C */, + "Clock" /* = 0x0000003D */, + "Splitter" /* = 0x0000003E */, + "LayeredPane" /* = 0x0000003F */, + "UserRole" /* = 0x0000ffff*/ + }; + + if (role >=0x40) + role = QAccessible::UserRole; + return roles[int(role)]; +} + +void showDebug(const char* funcName, const QAccessibleInterface *iface) +{ + qDebug() << "Role:" << roleString(iface->role(0)) + << "Name:" << iface->text(QAccessible::Name, 0) + << "State:" << QString::number(int(iface->state(0)), 16) + << QLatin1String(funcName); +} +#else +# define showDebug(f, iface) +#endif + +void QAccessible::initialize() +{ + +} +void QAccessible::cleanup() +{ + +} + +void QAccessible::updateAccessibility(QObject *o, int who, Event reason) +{ + Q_ASSERT(o); + + if (updateHandler) { + updateHandler(o, who, reason); + return; + } + + QString soundName; + switch (reason) { + case PopupMenuStart: + soundName = QLatin1String("MenuPopup"); + break; + + case MenuCommand: + soundName = QLatin1String("MenuCommand"); + break; + + case Alert: + { +#ifndef QT_NO_MESSAGEBOX + QMessageBox *mb = qobject_cast(o); + if (mb) { + switch (mb->icon()) { + case QMessageBox::Warning: + soundName = QLatin1String("SystemExclamation"); + break; + case QMessageBox::Critical: + soundName = QLatin1String("SystemHand"); + break; + case QMessageBox::Information: + soundName = QLatin1String("SystemAsterisk"); + break; + default: + break; + } + } else +#endif // QT_NO_MESSAGEBOX + { + soundName = QLatin1String("SystemAsterisk"); + } + + } + break; + default: + break; + } + + if (soundName.size()) { +#ifndef QT_NO_SETTINGS + QSettings settings(QLatin1String("HKEY_CURRENT_USER\\AppEvents\\Schemes\\Apps\\.Default\\") + soundName, + QSettings::NativeFormat); + QString file = settings.value(QLatin1String(".Current/.")).toString(); +#else + QString file; +#endif + if (!file.isEmpty()) { + PlaySound(reinterpret_cast(soundName.utf16()), 0, SND_ALIAS | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT); + } + } + + if (!isActive()) + return; + + typedef void (WINAPI *PtrNotifyWinEvent)(DWORD, HWND, LONG, LONG); + +#if defined(Q_WS_WINCE) // ### TODO: check for NotifyWinEvent in CE 6.0 + // There is no user32.lib nor NotifyWinEvent for CE + return; +#else + static PtrNotifyWinEvent ptrNotifyWinEvent = 0; + static bool resolvedNWE = false; + if (!resolvedNWE) { + resolvedNWE = true; + ptrNotifyWinEvent = (PtrNotifyWinEvent)QSystemLibrary::resolve(QLatin1String("user32"), "NotifyWinEvent"); + } + if (!ptrNotifyWinEvent) + return; + + // An event has to be associated with a window, + // so find the first parent that is a widget. + QWidget *w = 0; + if (o->isWidgetType()) { + w = (QWidget*)o; + } else { + QObject *p = o; + while ((p = p->parent()) != 0) { + if (p->isWidgetType()) { + w = (QWidget*)p; + break; + } + } + } + + if (!w) { + if (reason != QAccessible::ContextHelpStart && + reason != QAccessible::ContextHelpEnd) + w = QApplication::focusWidget(); + if (!w) { + w = QApplication::activeWindow(); + + if (!w) + return; + +// ### Fixme +// if (!w) { +// w = qApp->mainWidget(); +// if (!w) +// return; +// } + } + } + + if (reason != MenuCommand) { // MenuCommand is faked + ptrNotifyWinEvent(reason, w->winId(), OBJID_CLIENT, who); + } +#endif // Q_WS_WINCE +} + +void QAccessible::setRootObject(QObject *o) +{ + if (rootObjectHandler) { + rootObjectHandler(o); + } +} + +class QWindowsEnumerate : public IEnumVARIANT +{ +public: + QWindowsEnumerate(const QVector &a) + : ref(0), current(0),array(a) + { + } + + virtual ~QWindowsEnumerate() {} + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE Clone(IEnumVARIANT **ppEnum); + HRESULT STDMETHODCALLTYPE Next(unsigned long celt, VARIANT FAR* rgVar, unsigned long FAR* pCeltFetched); + HRESULT STDMETHODCALLTYPE Reset(); + HRESULT STDMETHODCALLTYPE Skip(unsigned long celt); + +private: + ULONG ref; + ULONG current; + QVector array; +}; + +HRESULT STDMETHODCALLTYPE QWindowsEnumerate::QueryInterface(REFIID id, LPVOID *iface) +{ + *iface = 0; + if (id == IID_IUnknown) + *iface = (IUnknown*)this; + else if (id == IID_IEnumVARIANT) + *iface = (IEnumVARIANT*)this; + + if (*iface) { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE QWindowsEnumerate::AddRef() +{ + return ++ref; +} + +ULONG STDMETHODCALLTYPE QWindowsEnumerate::Release() +{ + if (!--ref) { + delete this; + return 0; + } + return ref; +} + +HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Clone(IEnumVARIANT **ppEnum) +{ + QWindowsEnumerate *penum = 0; + *ppEnum = 0; + + penum = new QWindowsEnumerate(array); + if (!penum) + return E_OUTOFMEMORY; + penum->current = current; + penum->array = array; + penum->AddRef(); + *ppEnum = penum; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Next(unsigned long celt, VARIANT FAR* rgVar, unsigned long FAR* pCeltFetched) +{ + if (pCeltFetched) + *pCeltFetched = 0; + + ULONG l; + for (l = 0; l < celt; l++) { + VariantInit(&rgVar[l]); + if ((current+1) > (ULONG)array.size()) { + *pCeltFetched = l; + return S_FALSE; + } + + rgVar[l].vt = VT_I4; + rgVar[l].lVal = array[(int)current]; + ++current; + } + *pCeltFetched = l; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Reset() +{ + current = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsEnumerate::Skip(unsigned long celt) +{ + current += celt; + if (current > (ULONG)array.size()) { + current = array.size(); + return S_FALSE; + } + return S_OK; +} + +/* +*/ +class QWindowsAccessible : public IAccessible, IOleWindow, QAccessible +{ +public: + QWindowsAccessible(QAccessibleInterface *a) + : ref(0), accessible(a) + { + } + + virtual ~QWindowsAccessible() + { + delete accessible; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int *); + HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int, unsigned long, ITypeInfo **); + HRESULT STDMETHODCALLTYPE GetIDsOfNames(const _GUID &, wchar_t **, unsigned int, unsigned long, long *); + HRESULT STDMETHODCALLTYPE Invoke(long, const _GUID &, unsigned long, unsigned short, tagDISPPARAMS *, tagVARIANT *, tagEXCEPINFO *, unsigned int *); + + HRESULT STDMETHODCALLTYPE accHitTest(long xLeft, long yTop, VARIANT *pvarID); + HRESULT STDMETHODCALLTYPE accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varID); + HRESULT STDMETHODCALLTYPE accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEnd); + HRESULT STDMETHODCALLTYPE get_accChild(VARIANT varChildID, IDispatch** ppdispChild); + HRESULT STDMETHODCALLTYPE get_accChildCount(long* pcountChildren); + HRESULT STDMETHODCALLTYPE get_accParent(IDispatch** ppdispParent); + + HRESULT STDMETHODCALLTYPE accDoDefaultAction(VARIANT varID); + HRESULT STDMETHODCALLTYPE get_accDefaultAction(VARIANT varID, BSTR* pszDefaultAction); + HRESULT STDMETHODCALLTYPE get_accDescription(VARIANT varID, BSTR* pszDescription); + HRESULT STDMETHODCALLTYPE get_accHelp(VARIANT varID, BSTR *pszHelp); + HRESULT STDMETHODCALLTYPE get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic); + HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut(VARIANT varID, BSTR *pszKeyboardShortcut); + HRESULT STDMETHODCALLTYPE get_accName(VARIANT varID, BSTR* pszName); + HRESULT STDMETHODCALLTYPE put_accName(VARIANT varChild, BSTR szName); + HRESULT STDMETHODCALLTYPE get_accRole(VARIANT varID, VARIANT *pvarRole); + HRESULT STDMETHODCALLTYPE get_accState(VARIANT varID, VARIANT *pvarState); + HRESULT STDMETHODCALLTYPE get_accValue(VARIANT varID, BSTR* pszValue); + HRESULT STDMETHODCALLTYPE put_accValue(VARIANT varChild, BSTR szValue); + + HRESULT STDMETHODCALLTYPE accSelect(long flagsSelect, VARIANT varID); + HRESULT STDMETHODCALLTYPE get_accFocus(VARIANT *pvarID); + HRESULT STDMETHODCALLTYPE get_accSelection(VARIANT *pvarChildren); + + HRESULT STDMETHODCALLTYPE GetWindow(HWND *phwnd); + HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL fEnterMode); + +private: + ULONG ref; + QAccessibleInterface *accessible; +}; + +static inline BSTR QStringToBSTR(const QString &str) +{ + BSTR bstrVal; + + int wlen = str.length()+1; + bstrVal = SysAllocStringByteLen(0, wlen*2); + memcpy(bstrVal, str.unicode(), sizeof(QChar)*(wlen)); + bstrVal[wlen] = 0; + + return bstrVal; +} + +/* +*/ +IAccessible *qt_createWindowsAccessible(QAccessibleInterface *access) +{ + QWindowsAccessible *acc = new QWindowsAccessible(access); + IAccessible *iface; + acc->QueryInterface(IID_IAccessible, (void**)&iface); + + return iface; +} + +/* + IUnknown +*/ +HRESULT STDMETHODCALLTYPE QWindowsAccessible::QueryInterface(REFIID id, LPVOID *iface) +{ + *iface = 0; + if (id == IID_IUnknown) + *iface = (IUnknown*)(IDispatch*)this; + else if (id == IID_IDispatch) + *iface = (IDispatch*)this; + else if (id == IID_IAccessible) + *iface = (IAccessible*)this; + else if (id == IID_IOleWindow) + *iface = (IOleWindow*)this; + else + return E_NOINTERFACE; + + AddRef(); + return S_OK; +} + +ULONG STDMETHODCALLTYPE QWindowsAccessible::AddRef() +{ + return ++ref; +} + +ULONG STDMETHODCALLTYPE QWindowsAccessible::Release() +{ + if (!--ref) { + delete this; + return 0; + } + return ref; +} + +/* + IDispatch +*/ + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::GetTypeInfoCount(unsigned int * pctinfo) +{ + // We don't use a type library + *pctinfo = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::GetTypeInfo(unsigned int, unsigned long, ITypeInfo **pptinfo) +{ + // We don't use a type library + *pptinfo = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::GetIDsOfNames(const _GUID &, wchar_t **rgszNames, unsigned int, unsigned long, long *rgdispid) +{ +#if !defined(Q_CC_BOR) && !defined(Q_CC_GNU) + // PROPERTIES: Hierarchical + if (_bstr_t(rgszNames[0]) == _bstr_t(L"accParent")) + rgdispid[0] = DISPID_ACC_PARENT; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accChildCount")) + rgdispid[0] = DISPID_ACC_CHILDCOUNT; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accChild")) + rgdispid[0] = DISPID_ACC_CHILD; + + // PROPERTIES: Descriptional + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accName(")) + rgdispid[0] = DISPID_ACC_NAME; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accValue")) + rgdispid[0] = DISPID_ACC_VALUE; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accDescription")) + rgdispid[0] = DISPID_ACC_DESCRIPTION; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accRole")) + rgdispid[0] = DISPID_ACC_ROLE; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accState")) + rgdispid[0] = DISPID_ACC_STATE; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accHelp")) + rgdispid[0] = DISPID_ACC_HELP; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accHelpTopic")) + rgdispid[0] = DISPID_ACC_HELPTOPIC; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accKeyboardShortcut")) + rgdispid[0] = DISPID_ACC_KEYBOARDSHORTCUT; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accFocus")) + rgdispid[0] = DISPID_ACC_FOCUS; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accSelection")) + rgdispid[0] = DISPID_ACC_SELECTION; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accDefaultAction")) + rgdispid[0] = DISPID_ACC_DEFAULTACTION; + + // METHODS + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accSelect")) + rgdispid[0] = DISPID_ACC_SELECT; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accLocation")) + rgdispid[0] = DISPID_ACC_LOCATION; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accNavigate")) + rgdispid[0] = DISPID_ACC_NAVIGATE; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accHitTest")) + rgdispid[0] = DISPID_ACC_HITTEST; + else if(_bstr_t(rgszNames[0]) == _bstr_t(L"accDoDefaultAction")) + rgdispid[0] = DISPID_ACC_DODEFAULTACTION; + else + return DISP_E_UNKNOWNINTERFACE; + + return S_OK; +#else + Q_UNUSED(rgszNames); + Q_UNUSED(rgdispid); + + return DISP_E_MEMBERNOTFOUND; +#endif +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::Invoke(long dispIdMember, const _GUID &, unsigned long, unsigned short wFlags, tagDISPPARAMS *pDispParams, tagVARIANT *pVarResult, tagEXCEPINFO *, unsigned int *) +{ + HRESULT hr = DISP_E_MEMBERNOTFOUND; + + switch(dispIdMember) + { + case DISPID_ACC_PARENT: + if (wFlags == DISPATCH_PROPERTYGET) { + if (!pVarResult) + return E_INVALIDARG; + hr = get_accParent(&pVarResult->pdispVal); + } else { + hr = DISP_E_MEMBERNOTFOUND; + } + break; + + case DISPID_ACC_CHILDCOUNT: + if (wFlags == DISPATCH_PROPERTYGET) { + if (!pVarResult) + return E_INVALIDARG; + hr = get_accChildCount(&pVarResult->lVal); + } else { + hr = DISP_E_MEMBERNOTFOUND; + } + break; + + case DISPID_ACC_CHILD: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accChild(pDispParams->rgvarg[0], &pVarResult->pdispVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_NAME: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accName(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else if (wFlags == DISPATCH_PROPERTYPUT) + hr = put_accName(pDispParams->rgvarg[0], pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_VALUE: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accValue(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else if (wFlags == DISPATCH_PROPERTYPUT) + hr = put_accValue(pDispParams->rgvarg[0], pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_DESCRIPTION: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accDescription(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_ROLE: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accRole(pDispParams->rgvarg[0], pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_STATE: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accState(pDispParams->rgvarg[0], pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_HELP: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accHelp(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_HELPTOPIC: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accHelpTopic(&pDispParams->rgvarg[2].bstrVal, pDispParams->rgvarg[1], &pDispParams->rgvarg[0].lVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_KEYBOARDSHORTCUT: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accKeyboardShortcut(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_FOCUS: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accFocus(pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_SELECTION: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accSelection(pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_DEFAULTACTION: + if (wFlags == DISPATCH_PROPERTYGET) + hr = get_accDefaultAction(pDispParams->rgvarg[0], &pVarResult->bstrVal); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_SELECT: + if (wFlags == DISPATCH_METHOD) + hr = accSelect(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0]); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_LOCATION: + if (wFlags == DISPATCH_METHOD) + hr = accLocation(&pDispParams->rgvarg[4].lVal, &pDispParams->rgvarg[3].lVal, &pDispParams->rgvarg[2].lVal, &pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0]); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_NAVIGATE: + if (wFlags == DISPATCH_METHOD) + hr = accNavigate(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0], pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_HITTEST: + if (wFlags == DISPATCH_METHOD) + hr = accHitTest(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal, pVarResult); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + case DISPID_ACC_DODEFAULTACTION: + if (wFlags == DISPATCH_METHOD) + hr = accDoDefaultAction(pDispParams->rgvarg[0]); + else + hr = DISP_E_MEMBERNOTFOUND; + break; + + default: + hr = DISP_E_MEMBERNOTFOUND; + break; + } + + if (!SUCCEEDED(hr)) { + return hr; + } + return hr; +} + +/* + IAccessible +*/ +HRESULT STDMETHODCALLTYPE QWindowsAccessible::accHitTest(long xLeft, long yTop, VARIANT *pvarID) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + int control = accessible->childAt(xLeft, yTop); + if (control == -1) { + (*pvarID).vt = VT_EMPTY; + return S_FALSE; + } + QAccessibleInterface *acc = 0; + if (control) + accessible->navigate(Child, control, &acc); + if (!acc) { + (*pvarID).vt = VT_I4; + (*pvarID).lVal = control; + return S_OK; + } + + QWindowsAccessible* wacc = new QWindowsAccessible(acc); + IDispatch *iface = 0; + wacc->QueryInterface(IID_IDispatch, (void**)&iface); + if (iface) { + (*pvarID).vt = VT_DISPATCH; + (*pvarID).pdispVal = iface; + return S_OK; + } else { + delete wacc; + } + + (*pvarID).vt = VT_EMPTY; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varID) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QRect rect = accessible->rect(varID.lVal); + if (rect.isValid()) { + *pxLeft = rect.x(); + *pyTop = rect.y(); + *pcxWidth = rect.width(); + *pcyHeight = rect.height(); + } else { + *pxLeft = 0; + *pyTop = 0; + *pcxWidth = 0; + *pcyHeight = 0; + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEnd) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QAccessibleInterface *acc = 0; + int control = -1; + switch(navDir) { + case NAVDIR_FIRSTCHILD: + control = accessible->navigate(Child, 1, &acc); + break; + case NAVDIR_LASTCHILD: + control = accessible->navigate(Child, accessible->childCount(), &acc); + break; + case NAVDIR_NEXT: + case NAVDIR_PREVIOUS: + if (!varStart.lVal){ + QAccessibleInterface *parent = 0; + accessible->navigate(Ancestor, 1, &parent); + if (parent) { + int index = parent->indexOfChild(accessible); + index += (navDir == NAVDIR_NEXT) ? 1 : -1; + if (index > 0 && index <= parent->childCount()) + control = parent->navigate(Child, index, &acc); + delete parent; + } + } else { + int index = varStart.lVal; + index += (navDir == NAVDIR_NEXT) ? 1 : -1; + if (index > 0 && index <= accessible->childCount()) + control = accessible->navigate(Child, index, &acc); + } + break; + case NAVDIR_UP: + control = accessible->navigate(Up, varStart.lVal, &acc); + break; + case NAVDIR_DOWN: + control = accessible->navigate(Down, varStart.lVal, &acc); + break; + case NAVDIR_LEFT: + control = accessible->navigate(Left, varStart.lVal, &acc); + break; + case NAVDIR_RIGHT: + control = accessible->navigate(Right, varStart.lVal, &acc); + break; + default: + break; + } + if (control == -1) { + (*pvarEnd).vt = VT_EMPTY; + return S_FALSE; + } + if (!acc) { + (*pvarEnd).vt = VT_I4; + (*pvarEnd).lVal = control; + return S_OK; + } + + QWindowsAccessible* wacc = new QWindowsAccessible(acc); + + IDispatch *iface = 0; + wacc->QueryInterface(IID_IDispatch, (void**)&iface); + if (iface) { + (*pvarEnd).vt = VT_DISPATCH; + (*pvarEnd).pdispVal = iface; + return S_OK; + } else { + delete wacc; + } + + (*pvarEnd).vt = VT_EMPTY; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accChild(VARIANT varChildID, IDispatch** ppdispChild) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + if (varChildID.vt == VT_EMPTY) + return E_INVALIDARG; + + QAccessibleInterface *acc = 0; + RelationFlag rel = varChildID.lVal ? Child : Self; + accessible->navigate(rel, varChildID.lVal, &acc); + + if (acc) { + QWindowsAccessible* wacc = new QWindowsAccessible(acc); + wacc->QueryInterface(IID_IDispatch, (void**)ppdispChild); + return S_OK; + } + + *ppdispChild = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accChildCount(long* pcountChildren) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + *pcountChildren = accessible->childCount(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accParent(IDispatch** ppdispParent) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QAccessibleInterface *acc = 0; + accessible->navigate(Ancestor, 1, &acc); + if (acc) { + QWindowsAccessible* wacc = new QWindowsAccessible(acc); + wacc->QueryInterface(IID_IDispatch, (void**)ppdispParent); + + if (*ppdispParent) + return S_OK; + } + + *ppdispParent = 0; + return S_FALSE; +} + +/* + Properties and methods +*/ +HRESULT STDMETHODCALLTYPE QWindowsAccessible::accDoDefaultAction(VARIANT varID) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + return accessible->doAction(DefaultAction, varID.lVal, QVariantList()) ? S_OK : S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accDefaultAction(VARIANT varID, BSTR* pszDefaultAction) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString def = accessible->actionText(DefaultAction, Name, varID.lVal); + if (def.isEmpty()) { + *pszDefaultAction = 0; + return S_FALSE; + } + + *pszDefaultAction = QStringToBSTR(def); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accDescription(VARIANT varID, BSTR* pszDescription) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString descr = accessible->text(Description, varID.lVal); + if (descr.size()) { + *pszDescription = QStringToBSTR(descr); + return S_OK; + } + + *pszDescription = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accHelp(VARIANT varID, BSTR *pszHelp) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString help = accessible->text(Help, varID.lVal); + if (help.size()) { + *pszHelp = QStringToBSTR(help); + return S_OK; + } + + *pszHelp = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accHelpTopic(BSTR *, VARIANT, long *) +{ + return DISP_E_MEMBERNOTFOUND; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accKeyboardShortcut(VARIANT varID, BSTR *pszKeyboardShortcut) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString sc = accessible->text(Accelerator, varID.lVal); + if (sc.size()) { + *pszKeyboardShortcut = QStringToBSTR(sc); + return S_OK; + } + + *pszKeyboardShortcut = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accName(VARIANT varID, BSTR* pszName) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString n = accessible->text(Name, varID.lVal); + if (n.size()) { + *pszName = QStringToBSTR(n); + return S_OK; + } + + *pszName = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::put_accName(VARIANT, BSTR) +{ + showDebug(__FUNCTION__, accessible); + return DISP_E_MEMBERNOTFOUND; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accRole(VARIANT varID, VARIANT *pvarRole) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + Role role = accessible->role(varID.lVal); + if (role != NoRole) { + if (role == LayeredPane) + role = QAccessible::Pane; + (*pvarRole).vt = VT_I4; + (*pvarRole).lVal = role; + } else { + (*pvarRole).vt = VT_EMPTY; + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accState(VARIANT varID, VARIANT *pvarState) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + (*pvarState).vt = VT_I4; + (*pvarState).lVal = accessible->state(varID.lVal); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accValue(VARIANT varID, BSTR* pszValue) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QString value = accessible->text(Value, varID.lVal); + if (!value.isNull()) { + *pszValue = QStringToBSTR(value); + return S_OK; + } + + *pszValue = 0; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::put_accValue(VARIANT, BSTR) +{ + showDebug(__FUNCTION__, accessible); + return DISP_E_MEMBERNOTFOUND; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::accSelect(long flagsSelect, VARIANT varID) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + bool res = false; + + if (flagsSelect & SELFLAG_TAKEFOCUS) + res = accessible->doAction(SetFocus, varID.lVal, QVariantList()); + if (flagsSelect & SELFLAG_TAKESELECTION) { + accessible->doAction(ClearSelection, 0, QVariantList()); + res = accessible->doAction(AddToSelection, varID.lVal, QVariantList()); + } + if (flagsSelect & SELFLAG_EXTENDSELECTION) + res = accessible->doAction(ExtendSelection, varID.lVal, QVariantList()); + if (flagsSelect & SELFLAG_ADDSELECTION) + res = accessible->doAction(AddToSelection, varID.lVal, QVariantList()); + if (flagsSelect & SELFLAG_REMOVESELECTION) + res = accessible->doAction(RemoveSelection, varID.lVal, QVariantList()); + + return res ? S_OK : S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accFocus(VARIANT *pvarID) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + QAccessibleInterface *acc = 0; + int control = accessible->navigate(FocusChild, 1, &acc); + if (control == -1) { + (*pvarID).vt = VT_EMPTY; + return S_FALSE; + } + if (!acc || control == 0) { + (*pvarID).vt = VT_I4; + (*pvarID).lVal = control ? control : CHILDID_SELF; + return S_OK; + } + + QWindowsAccessible* wacc = new QWindowsAccessible(acc); + IDispatch *iface = 0; + wacc->QueryInterface(IID_IDispatch, (void**)&iface); + if (iface) { + (*pvarID).vt = VT_DISPATCH; + (*pvarID).pdispVal = iface; + return S_OK; + } else { + delete wacc; + } + + (*pvarID).vt = VT_EMPTY; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::get_accSelection(VARIANT *pvarChildren) +{ + showDebug(__FUNCTION__, accessible); + if (!accessible->isValid()) + return E_FAIL; + + int cc = accessible->childCount(); + QVector sel(cc); + int selIndex = 0; + for (int i = 1; i <= cc; ++i) { + QAccessibleInterface *child = 0; + int i2 = accessible->navigate(Child, i, &child); + bool isSelected = false; + if (child) { + isSelected = child->state(0) & Selected; + delete child; + child = 0; + } else { + isSelected = accessible->state(i2) & Selected; + } + if (isSelected) + sel[selIndex++] = i; + } + sel.resize(selIndex); + if (sel.isEmpty()) { + (*pvarChildren).vt = VT_EMPTY; + return S_FALSE; + } + if (sel.size() == 1) { + (*pvarChildren).vt = VT_I4; + (*pvarChildren).lVal = sel[0]; + return S_OK; + } + IEnumVARIANT *iface = new QWindowsEnumerate(sel); + IUnknown *uiface; + iface->QueryInterface(IID_IUnknown, (void**)&uiface); + (*pvarChildren).vt = VT_UNKNOWN; + (*pvarChildren).punkVal = uiface; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::GetWindow(HWND *phwnd) +{ + *phwnd = 0; + if (!accessible->isValid()) + return E_UNEXPECTED; + + QObject *o = accessible->object(); + if (!o || !o->isWidgetType()) + return E_FAIL; + + *phwnd = static_cast(o)->winId(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QWindowsAccessible::ContextSensitiveHelp(BOOL) +{ + return S_OK; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessiblebridge.cpp b/src/gui/accessible/qaccessiblebridge.cpp new file mode 100644 index 0000000000..e151bb6752 --- /dev/null +++ b/src/gui/accessible/qaccessiblebridge.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblebridge.h" + +#ifndef QT_NO_ACCESSIBILITY + +QT_BEGIN_NAMESPACE + +/*! + \class QAccessibleBridge + \brief The QAccessibleBridge class is the base class for + accessibility back-ends. + + \ingroup accessibility + + Qt supports Microsoft Active Accessibility (MSAA), Mac OS X + Accessibility, and the Unix/X11 AT-SPI standard. By subclassing + QAccessibleBridge, you can support other backends than the + predefined ones. + + Currently, custom bridges are only supported on Unix. We might + add support for them on other platforms as well if there is + enough demand. + + \sa QAccessible, QAccessibleBridgePlugin +*/ + +/*! + \fn QAccessibleBridge::~QAccessibleBridge() + + Destroys the accessibility bridge object. +*/ + +/*! + \fn void QAccessibleBridge::setRootObject(QAccessibleInterface *object) + + This function is called by Qt at application startup to set the + root accessible object of the application to \a object. All other + accessible objects in the application can be reached by the + client using object navigation. +*/ + +/*! + \fn void QAccessibleBridge::notifyAccessibilityUpdate(int reason, QAccessibleInterface *interface, int child) + + This function is called by Qt to notify the bridge about a change + in the accessibility information for object wrapped by the given + \a interface. + + \a reason specifies the cause of the change. It can take values + of type QAccessible::Event. + + \a child is the (1-based) index of the child element that has + changed. When \a child is 0, the object itself has changed. + + \sa QAccessible::updateAccessibility() +*/ + +/*! + \class QAccessibleBridgePlugin + \brief The QAccessibleBridgePlugin class provides an abstract + base for accessibility bridge plugins. + + \ingroup plugins + \ingroup accessibility + + Writing an accessibility bridge plugin is achieved by subclassing + this base class, reimplementing the pure virtual functions keys() + and create(), and exporting the class with the + Q_EXPORT_PLUGIN2() macro. + + \sa QAccessibleBridge, QAccessiblePlugin, {How to Create Qt Plugins} +*/ + +/*! + Constructs an accessibility bridge plugin with the given \a + parent. This is invoked automatically by the Q_EXPORT_PLUGIN2() + macro. +*/ +QAccessibleBridgePlugin::QAccessibleBridgePlugin(QObject *parent) + : QObject(parent) +{ + +} + +/*! + Destroys the accessibility bridge plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QAccessibleBridgePlugin::~QAccessibleBridgePlugin() +{ + +} + +/*! + \fn QStringList QAccessibleBridgePlugin::keys() const + + Returns the list of keys this plugins supports. + + These keys must be the names of the bridges that this + plugin provides. + + \sa create() +*/ + +/*! + \fn QAccessibleBridge *QAccessibleBridgePlugin::create(const QString &key) + + Creates and returns the QAccessibleBridge object corresponding to + the given \a key. Keys are case sensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessiblebridge.h b/src/gui/accessible/qaccessiblebridge.h new file mode 100644 index 0000000000..d5e35df33f --- /dev/null +++ b/src/gui/accessible/qaccessiblebridge.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEBRIDGE_H +#define QACCESSIBLEBRIDGE_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +class QAccessibleInterface; + +class QAccessibleBridge +{ +public: + virtual ~QAccessibleBridge() {} + virtual void setRootObject(QAccessibleInterface *) = 0; + virtual void notifyAccessibilityUpdate(int, QAccessibleInterface*, int) = 0; +}; + +struct Q_GUI_EXPORT QAccessibleBridgeFactoryInterface : public QFactoryInterface +{ + virtual QAccessibleBridge *create(const QString& name) = 0; +}; + +#define QAccessibleBridgeFactoryInterface_iid "com.trolltech.Qt.QAccessibleBridgeFactoryInterface" +Q_DECLARE_INTERFACE(QAccessibleBridgeFactoryInterface, QAccessibleBridgeFactoryInterface_iid) + +class Q_GUI_EXPORT QAccessibleBridgePlugin : public QObject, public QAccessibleBridgeFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QAccessibleBridgeFactoryInterface:QFactoryInterface) +public: + explicit QAccessibleBridgePlugin(QObject *parent = 0); + ~QAccessibleBridgePlugin(); + + virtual QStringList keys() const = 0; + virtual QAccessibleBridge *create(const QString &key) = 0; +}; + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACCESSIBLEBRIDGE_H diff --git a/src/gui/accessible/qaccessibleobject.cpp b/src/gui/accessible/qaccessibleobject.cpp new file mode 100644 index 0000000000..1d2d1da806 --- /dev/null +++ b/src/gui/accessible/qaccessibleobject.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessibleobject.h" + +#ifndef QT_NO_ACCESSIBILITY + +#include "qapplication.h" +#include "qwidget.h" +#include "qpointer.h" +#include "qmetaobject.h" +#include "qvarlengtharray.h" + +QT_BEGIN_NAMESPACE + +class QAccessibleObjectPrivate +{ +public: + QPointer object; + + QList actionList() const; +}; + +QList QAccessibleObjectPrivate::actionList() const +{ + QList actionList; + + if (!object) + return actionList; + + const QMetaObject *mo = object->metaObject(); + Q_ASSERT(mo); + + QByteArray defaultAction = QMetaObject::normalizedSignature( + mo->classInfo(mo->indexOfClassInfo("DefaultSlot")).value()); + + for (int i = 0; i < mo->methodCount(); ++i) { + const QMetaMethod member = mo->method(i); + if (member.methodType() != QMetaMethod::Slot && member.access() != QMetaMethod::Public) + continue; + + if (!qstrcmp(member.tag(), "QACCESSIBLE_SLOT")) { + if (member.signature() == defaultAction) + actionList.prepend(defaultAction); + else + actionList << member.signature(); + } + } + + return actionList; +} + +/*! + \class QAccessibleObject + \brief The QAccessibleObject class implements parts of the + QAccessibleInterface for QObjects. + + \ingroup accessibility + + This class is mainly provided for convenience. All subclasses of + the QAccessibleInterface that provide implementations of non-widget objects + should use this class as their base class. + + \sa QAccessible, QAccessibleWidget +*/ + +/*! + Creates a QAccessibleObject for \a object. +*/ +QAccessibleObject::QAccessibleObject(QObject *object) +{ + d = new QAccessibleObjectPrivate; + d->object = object; +} + +/*! + Destroys the QAccessibleObject. + + This only happens when a call to release() decrements the internal + reference counter to zero. +*/ +QAccessibleObject::~QAccessibleObject() +{ + delete d; +} + +/*! + \reimp +*/ +QObject *QAccessibleObject::object() const +{ +#ifndef QT_NO_DEBUG + if (!d->object) + qWarning("QAccessibleInterface is invalid. Crash pending..."); +#endif + return d->object; +} + +/*! + \reimp +*/ +bool QAccessibleObject::isValid() const +{ + return !d->object.isNull(); +} + +/*! \reimp */ +QRect QAccessibleObject::rect(int) const +{ + return QRect(); +} + +/*! \reimp */ +void QAccessibleObject::setText(Text, int, const QString &) +{ +} + +/*! \reimp */ +int QAccessibleObject::userActionCount(int) const +{ + return 0; +} + +/*! \reimp */ +bool QAccessibleObject::doAction(int, int, const QVariantList &) +{ + return false; +} + +static const char * const action_text[][5] = +{ + // Name, Description, Value, Help, Accelerator + { "Press", "", "", "", "Space" }, + { "SetFocus", "Passes focus to this widget", "", "", "" }, + { "Increase", "", "", "", "" }, + { "Decrease", "", "", "", "" }, + { "Accept", "", "", "", "" }, + { "Cancel", "", "", "", "" }, + { "Select", "", "", "", "" }, + { "ClearSelection", "", "", "", "" }, + { "RemoveSelection", "", "", "", "" }, + { "ExtendSelection", "", "", "", "" }, + { "AddToSelection", "", "", "", "" } +}; + +/*! \reimp */ +QString QAccessibleObject::actionText(int action, Text t, int child) const +{ + if (child || action > FirstStandardAction || action < LastStandardAction || t > Accelerator) + return QString(); + + return QString::fromLatin1(action_text[-(action - FirstStandardAction)][t]); +} + + +/*! + \class QAccessibleApplication + \brief The QAccessibleApplication class implements the QAccessibleInterface for QApplication. + + \internal + + \ingroup accessibility +*/ + +/*! + Creates a QAccessibleApplication for the QApplication object referenced by qApp. +*/ +QAccessibleApplication::QAccessibleApplication() +: QAccessibleObject(qApp) +{ +} + +// all toplevel widgets except popups and the desktop +static QWidgetList topLevelWidgets() +{ + QWidgetList list; + const QWidgetList tlw(QApplication::topLevelWidgets()); + for (int i = 0; i < tlw.count(); ++i) { + QWidget *w = tlw.at(i); + if (!(w->windowType() == Qt::Popup) && !(w->windowType() == Qt::Desktop)) + list.append(w); + } + + return list; +} + +/*! \reimp */ +int QAccessibleApplication::childCount() const +{ + return topLevelWidgets().count(); +} + +/*! \reimp */ +int QAccessibleApplication::indexOfChild(const QAccessibleInterface *child) const +{ + if (!child->object()->isWidgetType()) + return -1; + + const QWidgetList tlw(topLevelWidgets()); + int index = tlw.indexOf(static_cast(child->object())); + if (index != -1) + ++index; + return index; +} + +/*! \reimp */ +int QAccessibleApplication::childAt(int x, int y) const +{ + const QWidgetList tlw(topLevelWidgets()); + for (int i = 0; i < tlw.count(); ++i) { + QWidget *w = tlw.at(i); + if (w->frameGeometry().contains(x,y)) + return i+1; + } + return -1; +} + +/*! \reimp */ +QAccessible::Relation QAccessibleApplication::relationTo(int child, const + QAccessibleInterface *other, int otherChild) const +{ + QObject *o = other ? other->object() : 0; + if (!o) + return Unrelated; + + if(o == object()) { + if (child && !otherChild) + return Child; + if (!child && otherChild) + return Ancestor; + if (!child && !otherChild) + return Self; + } + + QWidgetList tlw(topLevelWidgets()); + if (tlw.contains(qobject_cast(o))) + return Ancestor; + + for (int i = 0; i < tlw.count(); ++i) { + QWidget *w = tlw.at(i); + QObjectList cl = w->findChildren(QString()); + if (cl.contains(o)) + return Ancestor; + } + + return Unrelated; +} + +/*! \reimp */ +int QAccessibleApplication::navigate(RelationFlag relation, int entry, + QAccessibleInterface **target) const +{ + if (!target) + return -1; + + *target = 0; + QObject *targetObject = 0; + + switch (relation) { + case Self: + targetObject = object(); + break; + case Child: + if (entry > 0 && entry <= childCount()) { + const QWidgetList tlw(topLevelWidgets()); + if (tlw.count() >= entry) + targetObject = tlw.at(entry-1); + } else { + return -1; + } + break; + case FocusChild: + targetObject = QApplication::activeWindow(); + break; + default: + break; + } + *target = QAccessible::queryAccessibleInterface(targetObject); + return *target ? 0 : -1; +} + +/*! \reimp */ +QString QAccessibleApplication::text(Text t, int) const +{ + switch (t) { + case Name: + if (QApplication::activeWindow()) + return QApplication::activeWindow()->windowTitle(); + break; + case Description: + return QApplication::applicationFilePath(); + default: + break; + } + return QString(); +} + +/*! \reimp */ +QAccessible::Role QAccessibleApplication::role(int) const +{ + return Application; +} + +/*! \reimp */ +QAccessible::State QAccessibleApplication::state(int) const +{ + return QApplication::activeWindow() ? Focused : Normal; +} + +/*! \reimp */ +int QAccessibleApplication::userActionCount(int) const +{ + return 1; +} + +/*! \reimp */ +bool QAccessibleApplication::doAction(int action, int child, const QVariantList ¶m) +{ + if (action == 0 || action == 1) { + QWidget *w = 0; + w = QApplication::activeWindow(); + if (!w) + w = topLevelWidgets().at(0); + if (!w) + return false; + w->activateWindow(); + return true; + } + return QAccessibleObject::doAction(action, child, param); +} + +/*! \reimp */ +QString QAccessibleApplication::actionText(int action, Text text, int child) const +{ + QString str; + if ((action == 0 || action == 1) && !child) switch (text) { + case Name: + return QApplication::tr("Activate"); + case Description: + return QApplication::tr("Activates the program's main window"); + default: + break; + } + return QAccessibleObject::actionText(action, text, child); +} + +// ### Qt 5: remove me - binary compatibility hack +QAccessibleObjectEx::QAccessibleObjectEx(QObject *object) +{ + d = new QAccessibleObjectPrivate; + d->object = object; +} +bool QAccessibleObjectEx::isValid() const +{ return reinterpret_cast(this)->QAccessibleObject::isValid(); } +QObject *QAccessibleObjectEx::object() const +{ return reinterpret_cast(this)->QAccessibleObject::object(); } +QRect QAccessibleObjectEx::rect(int child) const +{ return reinterpret_cast(this)->QAccessibleObject::rect(child); } +void QAccessibleObjectEx::setText(Text t, int child, const QString &text) +{ reinterpret_cast(this)->QAccessibleObject::setText(t, child, text); } +int QAccessibleObjectEx::userActionCount(int child) const +{ return reinterpret_cast(this)->QAccessibleObject::userActionCount(child); } +bool QAccessibleObjectEx::doAction(int action, int child, const QVariantList ¶ms) +{ return reinterpret_cast(this)->QAccessibleObject::doAction(action, child, params); } +QString QAccessibleObjectEx::actionText(int action, Text t, int child) const +{ return reinterpret_cast(this)->QAccessibleObject::actionText(action, t, child); } +QAccessibleObjectEx::~QAccessibleObjectEx() +{ delete d; } + +QT_END_NAMESPACE + +#endif //QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessibleobject.h b/src/gui/accessible/qaccessibleobject.h new file mode 100644 index 0000000000..0eb9399028 --- /dev/null +++ b/src/gui/accessible/qaccessibleobject.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEOBJECT_H +#define QACCESSIBLEOBJECT_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +class QAccessibleObjectPrivate; +class QObject; + +class Q_GUI_EXPORT QAccessibleObject : public QAccessibleInterface +{ +public: + explicit QAccessibleObject(QObject *object); + + bool isValid() const; + QObject *object() const; + + // properties + QRect rect(int child) const; + void setText(Text t, int child, const QString &text); + + // actions + int userActionCount(int child) const; + bool doAction(int action, int child, const QVariantList ¶ms); + QString actionText(int action, Text t, int child) const; + +protected: + virtual ~QAccessibleObject(); + +private: + friend class QAccessibleObjectEx; + QAccessibleObjectPrivate *d; + Q_DISABLE_COPY(QAccessibleObject) +}; + +class Q_GUI_EXPORT QAccessibleObjectEx : public QAccessibleInterfaceEx +{ +public: + explicit QAccessibleObjectEx(QObject *object); + + bool isValid() const; + QObject *object() const; + + // properties + QRect rect(int child) const; + void setText(Text t, int child, const QString &text); + + // actions + int userActionCount(int child) const; + bool doAction(int action, int child, const QVariantList ¶ms); + QString actionText(int action, Text t, int child) const; + +protected: + virtual ~QAccessibleObjectEx(); + +private: + QAccessibleObjectPrivate *d; + Q_DISABLE_COPY(QAccessibleObjectEx) +}; + +class Q_GUI_EXPORT QAccessibleApplication : public QAccessibleObject +{ +public: + QAccessibleApplication(); + + // relations + int childCount() const; + int indexOfChild(const QAccessibleInterface*) const; + Relation relationTo(int, const QAccessibleInterface *, int) const; + + // navigation + int childAt(int x, int y) const; + int navigate(RelationFlag, int, QAccessibleInterface **) const; + + // properties and state + QString text(Text t, int child) const; + Role role(int child) const; + State state(int child) const; + + // actions + int userActionCount(int child) const; + bool doAction(int action, int child, const QVariantList ¶ms); + QString actionText(int action, Text t, int child) const; +}; + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACCESSIBLEOBJECT_H diff --git a/src/gui/accessible/qaccessibleplugin.cpp b/src/gui/accessible/qaccessibleplugin.cpp new file mode 100644 index 0000000000..f52d4b1807 --- /dev/null +++ b/src/gui/accessible/qaccessibleplugin.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessibleplugin.h" + +#ifndef QT_NO_ACCESSIBILITY + +#include "qaccessible.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAccessiblePlugin + \brief The QAccessiblePlugin class provides an abstract base for + accessibility plugins. + + \ingroup plugins + \ingroup accessibility + + Writing an accessibility plugin is achieved by subclassing this + base class, reimplementing the pure virtual functions keys() and + create(), and exporting the class with the Q_EXPORT_PLUGIN2() + macro. + + \sa QAccessibleBridgePlugin, {How to Create Qt Plugins} +*/ + +/*! + Constructs an accessibility plugin with the given \a parent. This + is invoked automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QAccessiblePlugin::QAccessiblePlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the accessibility plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QAccessiblePlugin::~QAccessiblePlugin() +{ +} + +/*! + \fn QStringList QAccessiblePlugin::keys() const + + Returns the list of keys this plugin supports. + + These keys must be the class names that this plugin provides + an accessibility implementation for. + + \sa create() +*/ + +/*! + \fn QAccessibleInterface *QAccessiblePlugin::create(const QString &key, QObject *object) + + Creates and returns a QAccessibleInterface implementation for the + class \a key and the object \a object. Keys are case sensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessibleplugin.h b/src/gui/accessible/qaccessibleplugin.h new file mode 100644 index 0000000000..dcf794e752 --- /dev/null +++ b/src/gui/accessible/qaccessibleplugin.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEPLUGIN_H +#define QACCESSIBLEPLUGIN_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +class QStringList; +class QAccessibleInterface; + +struct Q_GUI_EXPORT QAccessibleFactoryInterface : public QAccessible, public QFactoryInterface +{ + virtual QAccessibleInterface* create(const QString &key, QObject *object) = 0; +}; + +#define QAccessibleFactoryInterface_iid "com.trolltech.Qt.QAccessibleFactoryInterface" +Q_DECLARE_INTERFACE(QAccessibleFactoryInterface, QAccessibleFactoryInterface_iid) + +class QAccessiblePluginPrivate; + +class Q_GUI_EXPORT QAccessiblePlugin : public QObject, public QAccessibleFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QAccessibleFactoryInterface:QFactoryInterface) +public: + explicit QAccessiblePlugin(QObject *parent = 0); + ~QAccessiblePlugin(); + + virtual QStringList keys() const = 0; + virtual QAccessibleInterface *create(const QString &key, QObject *object) = 0; +}; + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACCESSIBLEPLUGIN_H diff --git a/src/gui/accessible/qaccessiblewidget.cpp b/src/gui/accessible/qaccessiblewidget.cpp new file mode 100644 index 0000000000..2b2cec0f02 --- /dev/null +++ b/src/gui/accessible/qaccessiblewidget.cpp @@ -0,0 +1,1031 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaccessiblewidget.h" + +#ifndef QT_NO_ACCESSIBILITY + +#include "qaction.h" +#include "qapplication.h" +#include "qgroupbox.h" +#include "qlabel.h" +#include "qtooltip.h" +#include "qwhatsthis.h" +#include "qwidget.h" +#include "qdebug.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QList childWidgets(const QWidget *widget) +{ + QList list = widget->children(); + QList widgets; + for (int i = 0; i < list.size(); ++i) { + QWidget *w = qobject_cast(list.at(i)); + if (w && !w->isWindow() + && !qobject_cast(w) +#if !defined(QT_NO_MENU) + && !qobject_cast(w) +#endif + && w->objectName() != QLatin1String("qt_rubberband")) + widgets.append(w); + } + return widgets; +} + +static QString buddyString(const QWidget *widget) +{ + if (!widget) + return QString(); + QWidget *parent = widget->parentWidget(); + if (!parent) + return QString(); +#ifndef QT_NO_SHORTCUT + QObjectList ol = parent->children(); + for (int i = 0; i < ol.size(); ++i) { + QLabel *label = qobject_cast(ol.at(i)); + if (label && label->buddy() == widget) + return label->text(); + } +#endif + +#ifndef QT_NO_GROUPBOX + QGroupBox *groupbox = qobject_cast(parent); + if (groupbox) + return groupbox->title(); +#endif + + return QString(); +} + +QString Q_GUI_EXPORT qt_accStripAmp(const QString &text) +{ + return QString(text).remove(QLatin1Char('&')); +} + +QString Q_GUI_EXPORT qt_accHotKey(const QString &text) +{ +#ifndef QT_NO_SHORTCUT + if (text.isEmpty()) + return text; + + int fa = 0; + QChar ac; + while ((fa = text.indexOf(QLatin1Char('&'), fa)) != -1) { + ++fa; + if (fa < text.length()) { + // ignore "&&" + if (text.at(fa) == QLatin1Char('&')) { + ++fa; + continue; + } else { + ac = text.at(fa); + break; + } + } + } + if (ac.isNull()) + return QString(); + return (QString)QKeySequence(Qt::ALT) + ac.toUpper(); +#else + Q_UNUSED(text); + return QString(); +#endif +} + +class QAccessibleWidgetPrivate : public QAccessible +{ +public: + QAccessibleWidgetPrivate() + :role(Client) + {} + + Role role; + QString name; + QString description; + QString value; + QString help; + QString accelerator; + QStringList primarySignals; + const QAccessibleInterface *asking; +}; + +/*! + \class QAccessibleWidget + \brief The QAccessibleWidget class implements the QAccessibleInterface for QWidgets. + + \ingroup accessibility + + This class is convenient to use as a base class for custom + implementations of QAccessibleInterfaces that provide information + about widget objects. + + The class provides functions to retrieve the parentObject() (the + widget's parent widget), and the associated widget(). Controlling + signals can be added with addControllingSignal(), and setters are + provided for various aspects of the interface implementation, for + example setValue(), setDescription(), setAccelerator(), and + setHelp(). + + \sa QAccessible, QAccessibleObject +*/ + +/*! + Creates a QAccessibleWidget object for widget \a w. + \a role and \a name are optional parameters that set the object's + role and name properties. +*/ +QAccessibleWidget::QAccessibleWidget(QWidget *w, Role role, const QString &name) +: QAccessibleObject(w) +{ + Q_ASSERT(widget()); + d = new QAccessibleWidgetPrivate(); + d->role = role; + d->name = name; + d->asking = 0; +} + +/*! + Destroys this object. +*/ +QAccessibleWidget::~QAccessibleWidget() +{ + delete d; +} + +/*! + Returns the associated widget. +*/ +QWidget *QAccessibleWidget::widget() const +{ + return qobject_cast(object()); +} + +/*! + Returns the associated widget's parent object, which is either the + parent widget, or qApp for top-level widgets. +*/ +QObject *QAccessibleWidget::parentObject() const +{ + QObject *parent = object()->parent(); + if (!parent) + parent = qApp; + return parent; +} + +/*! \reimp */ +int QAccessibleWidget::childAt(int x, int y) const +{ + QWidget *w = widget(); + if (!w->isVisible()) + return -1; + QPoint gp = w->mapToGlobal(QPoint(0, 0)); + if (!QRect(gp.x(), gp.y(), w->width(), w->height()).contains(x, y)) + return -1; + + QWidgetList list = childWidgets(w); + int ccount = childCount(); + + // a complex child + if (list.size() < ccount) { + for (int i = 1; i <= ccount; ++i) { + if (rect(i).contains(x, y)) + return i; + } + return 0; + } + + QPoint rp = w->mapFromGlobal(QPoint(x, y)); + for (int i = 0; iisWindow() && !child->isHidden() && child->geometry().contains(rp)) { + return i + 1; + } + } + return 0; +} + +/*! \reimp */ +QRect QAccessibleWidget::rect(int child) const +{ + if (child) { + qWarning("QAccessibleWidget::rect: This implementation does not support subelements! " + "(ID %d unknown for %s)", child, widget()->metaObject()->className()); + } + + QWidget *w = widget(); + if (!w->isVisible()) + return QRect(); + QPoint wpos = w->mapToGlobal(QPoint(0, 0)); + + return QRect(wpos.x(), wpos.y(), w->width(), w->height()); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +class QACConnectionObject : public QObject +{ + Q_DECLARE_PRIVATE(QObject) +public: + inline bool isSender(const QObject *receiver, const char *signal) const + { return d_func()->isSender(receiver, signal); } + inline QObjectList receiverList(const char *signal) const + { return d_func()->receiverList(signal); } + inline QObjectList senderList() const + { return d_func()->senderList(); } +}; + +/*! + Registers \a signal as a controlling signal. + + An object is a Controller to any other object connected to a + controlling signal. +*/ +void QAccessibleWidget::addControllingSignal(const QString &signal) +{ + QByteArray s = QMetaObject::normalizedSignature(signal.toAscii()); + if (object()->metaObject()->indexOfSignal(s) < 0) + qWarning("Signal %s unknown in %s", s.constData(), object()->metaObject()->className()); + d->primarySignals << QLatin1String(s); +} + +/*! + Sets the value of this interface implementation to \a value. + + The default implementation of text() returns the set value for + the Value text. + + Note that the object wrapped by this interface is not modified. +*/ +void QAccessibleWidget::setValue(const QString &value) +{ + d->value = value; +} + +/*! + Sets the description of this interface implementation to \a desc. + + The default implementation of text() returns the set value for + the Description text. + + Note that the object wrapped by this interface is not modified. +*/ +void QAccessibleWidget::setDescription(const QString &desc) +{ + d->description = desc; +} + +/*! + Sets the help of this interface implementation to \a help. + + The default implementation of text() returns the set value for + the Help text. + + Note that the object wrapped by this interface is not modified. +*/ +void QAccessibleWidget::setHelp(const QString &help) +{ + d->help = help; +} + +/*! + Sets the accelerator of this interface implementation to \a accel. + + The default implementation of text() returns the set value for + the Accelerator text. + + Note that the object wrapped by this interface is not modified. +*/ +void QAccessibleWidget::setAccelerator(const QString &accel) +{ + d->accelerator = accel; +} + +static inline bool isAncestor(const QObject *obj, const QObject *child) +{ + while (child) { + if (child == obj) + return true; + child = child->parent(); + } + return false; +} + + +/*! \reimp */ +QAccessible::Relation QAccessibleWidget::relationTo(int child, + const QAccessibleInterface *other, int otherChild) const +{ + Relation relation = Unrelated; + if (d->asking == this) // recursive call + return relation; + + QObject *o = other ? other->object() : 0; + if (!o) + return relation; + + QWidget *focus = widget()->focusWidget(); + if (object() == focus && isAncestor(o, focus)) + relation |= FocusChild; + + QACConnectionObject *connectionObject = (QACConnectionObject*)object(); + for (int sig = 0; sig < d->primarySignals.count(); ++sig) { + if (connectionObject->isSender(o, d->primarySignals.at(sig).toAscii())) { + relation |= Controller; + break; + } + } + // test for passive relationships. + // d->asking protects from endless recursion. + d->asking = this; + int inverse = other->relationTo(otherChild, this, child); + d->asking = 0; + + if (inverse & Controller) + relation |= Controlled; + if (inverse & Label) + relation |= Labelled; + + if(o == object()) { + if (child && !otherChild) + return relation | Child; + if (!child && otherChild) + return relation | Ancestor; + if (!child && !otherChild) + return relation | Self; + } + + QObject *parent = object()->parent(); + if (o == parent) + return relation | Child; + + if (o->parent() == parent) { + relation |= Sibling; + QAccessibleInterface *sibIface = QAccessible::queryAccessibleInterface(o); + Q_ASSERT(sibIface); + QRect wg = rect(0); + QRect sg = sibIface->rect(0); + if (wg.intersects(sg)) { + QAccessibleInterface *pIface = 0; + sibIface->navigate(Ancestor, 1, &pIface); + if (pIface && !((sibIface->state(0) | state(0)) & Invisible)) { + int wi = pIface->indexOfChild(this); + int si = pIface->indexOfChild(sibIface); + + if (wi > si) + relation |= QAccessible::Covers; + else + relation |= QAccessible::Covered; + } + delete pIface; + } else { + QPoint wc = wg.center(); + QPoint sc = sg.center(); + if (wc.x() < sc.x()) + relation |= QAccessible::Left; + else if(wc.x() > sc.x()) + relation |= QAccessible::Right; + if (wc.y() < sc.y()) + relation |= QAccessible::Up; + else if (wc.y() > sc.y()) + relation |= QAccessible::Down; + } + delete sibIface; + + return relation; + } + + if (isAncestor(o, object())) + return relation | Descendent; + if (isAncestor(object(), o)) + return relation | Ancestor; + + return relation; +} + +/*! \reimp */ +int QAccessibleWidget::navigate(RelationFlag relation, int entry, + QAccessibleInterface **target) const +{ + if (!target) + return -1; + + *target = 0; + QObject *targetObject = 0; + + QWidgetList childList = childWidgets(widget()); + bool complexWidget = childList.size() < childCount(); + + switch (relation) { + // Hierarchical + case Self: + targetObject = object(); + break; + case Child: + if (complexWidget) { + if (entry > 0 && entry <= childCount()) + return entry; + return -1; + }else { + if (entry > 0 && childList.size() >= entry) + targetObject = childList.at(entry - 1); + } + break; + case Ancestor: + { + if (entry <= 0) + return -1; + targetObject = widget()->parentWidget(); + int i; + for (i = entry; i > 1 && targetObject; --i) + targetObject = targetObject->parent(); + if (!targetObject && i == 1) + targetObject = qApp; + } + break; + case Sibling: + { + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(parentObject()); + if (!iface) + return -1; + + iface->navigate(Child, entry, target); + delete iface; + if (*target) + return 0; + } + break; + + // Geometrical + case QAccessible::Left: + if (complexWidget && entry) { + if (entry < 2 || widget()->height() > widget()->width() + 20) // looks vertical + return -1; + return entry - 1; + } + // fall through + case QAccessible::Right: + if (complexWidget && entry) { + if (entry >= childCount() || widget()->height() > widget()->width() + 20) // looks vertical + return -1; + return entry + 1; + } + // fall through + case QAccessible::Up: + if (complexWidget && entry) { + if (entry < 2 || widget()->width() > widget()->height() + 20) // looks horizontal + return - 1; + return entry - 1; + } + // fall through + case QAccessible::Down: + if (complexWidget && entry) { + if (entry >= childCount() || widget()->width() > widget()->height() + 20) // looks horizontal + return - 1; + return entry + 1; + } else { + QAccessibleInterface *pIface = QAccessible::queryAccessibleInterface(parentObject()); + if (!pIface) + return -1; + + QRect startg = rect(0); + QPoint startc = startg.center(); + QAccessibleInterface *candidate = 0; + int mindist = 100000; + int sibCount = pIface->childCount(); + for (int i = 0; i < sibCount; ++i) { + QAccessibleInterface *sibling = 0; + pIface->navigate(Child, i+1, &sibling); + Q_ASSERT(sibling); + if ((relationTo(0, sibling, 0) & Self) || (sibling->state(0) & QAccessible::Invisible)) { + //ignore ourself and invisible siblings + delete sibling; + continue; + } + + QRect sibg = sibling->rect(0); + QPoint sibc = sibg.center(); + QPoint sibp; + QPoint startp; + QPoint distp; + switch (relation) { + case QAccessible::Left: + startp = QPoint(startg.left(), startg.top() + startg.height() / 2); + sibp = QPoint(sibg.right(), sibg.top() + sibg.height() / 2); + if (QPoint(sibc - startc).x() >= 0) { + delete sibling; + continue; + } + distp = sibp - startp; + break; + case QAccessible::Right: + startp = QPoint(startg.right(), startg.top() + startg.height() / 2); + sibp = QPoint(sibg.left(), sibg.top() + sibg.height() / 2); + if (QPoint(sibc - startc).x() <= 0) { + delete sibling; + continue; + } + distp = sibp - startp; + break; + case QAccessible::Up: + startp = QPoint(startg.left() + startg.width() / 2, startg.top()); + sibp = QPoint(sibg.left() + sibg.width() / 2, sibg.bottom()); + if (QPoint(sibc - startc).y() >= 0) { + delete sibling; + continue; + } + distp = sibp - startp; + break; + case QAccessible::Down: + startp = QPoint(startg.left() + startg.width() / 2, startg.bottom()); + sibp = QPoint(sibg.left() + sibg.width() / 2, sibg.top()); + if (QPoint(sibc - startc).y() <= 0) { + delete sibling; + continue; + } + distp = sibp - startp; + break; + default: + break; + } + + int dist = (int)qSqrt((qreal)distp.x() * distp.x() + distp.y() * distp.y()); + if (dist < mindist) { + delete candidate; + candidate = sibling; + mindist = dist; + } else { + delete sibling; + } + } + delete pIface; + *target = candidate; + if (*target) + return 0; + } + break; + case Covers: + if (entry > 0) { + QAccessibleInterface *pIface = QAccessible::queryAccessibleInterface(parentObject()); + if (!pIface) + return -1; + + QRect r = rect(0); + int sibCount = pIface->childCount(); + QAccessibleInterface *sibling = 0; + for (int i = pIface->indexOfChild(this) + 1; i <= sibCount && entry; ++i) { + pIface->navigate(Child, i, &sibling); + if (!sibling || (sibling->state(0) & Invisible)) { + delete sibling; + sibling = 0; + continue; + } + if (sibling->rect(0).intersects(r)) + --entry; + if (!entry) + break; + delete sibling; + sibling = 0; + } + delete pIface; + *target = sibling; + if (*target) + return 0; + } + break; + case Covered: + if (entry > 0) { + QAccessibleInterface *pIface = QAccessible::queryAccessibleInterface(parentObject()); + if (!pIface) + return -1; + + QRect r = rect(0); + int index = pIface->indexOfChild(this); + QAccessibleInterface *sibling = 0; + for (int i = 1; i < index && entry; ++i) { + pIface->navigate(Child, i, &sibling); + Q_ASSERT(sibling); + if (!sibling || (sibling->state(0) & Invisible)) { + delete sibling; + sibling = 0; + continue; + } + if (sibling->rect(0).intersects(r)) + --entry; + if (!entry) + break; + delete sibling; + sibling = 0; + } + delete pIface; + *target = sibling; + if (*target) + return 0; + } + break; + + // Logical + case FocusChild: + { + if (widget()->hasFocus()) { + targetObject = object(); + break; + } + + QWidget *fw = widget()->focusWidget(); + if (!fw) + return -1; + + if (isAncestor(widget(), fw) || fw == widget()) + targetObject = fw; + /* ### + QWidget *parent = fw; + while (parent && !targetObject) { + parent = parent->parentWidget(); + if (parent == widget()) + targetObject = fw; + } + */ + } + break; + case Label: + if (entry > 0) { + QAccessibleInterface *pIface = QAccessible::queryAccessibleInterface(parentObject()); + if (!pIface) + return -1; + + // first check for all siblings that are labels to us + // ideally we would go through all objects and check, but that + // will be too expensive + int sibCount = pIface->childCount(); + QAccessibleInterface *candidate = 0; + for (int i = 0; i < sibCount && entry; ++i) { + pIface->navigate(Child, i+1, &candidate); + Q_ASSERT(candidate); + if (candidate->relationTo(0, this, 0) & Label) + --entry; + if (!entry) + break; + delete candidate; + candidate = 0; + } + if (!candidate) { + if (pIface->relationTo(0, this, 0) & Label) + --entry; + if (!entry) + candidate = pIface; + } + if (pIface != candidate) + delete pIface; + + *target = candidate; + if (*target) + return 0; + } + break; + case Labelled: // only implemented in subclasses + break; + case Controller: + if (entry > 0) { + // check all senders we are connected to, + // and figure out which one are controllers to us + QACConnectionObject *connectionObject = (QACConnectionObject*)object(); + QObjectList allSenders = connectionObject->senderList(); + QObjectList senders; + for (int s = 0; s < allSenders.size(); ++s) { + QObject *sender = allSenders.at(s); + QAccessibleInterface *candidate = QAccessible::queryAccessibleInterface(sender); + if (!candidate) + continue; + if (candidate->relationTo(0, this, 0)&Controller) + senders << sender; + delete candidate; + } + if (entry <= senders.size()) + targetObject = senders.at(entry-1); + } + break; + case Controlled: + if (entry > 0) { + QObjectList allReceivers; + QACConnectionObject *connectionObject = (QACConnectionObject*)object(); + for (int sig = 0; sig < d->primarySignals.count(); ++sig) { + QObjectList receivers = connectionObject->receiverList(d->primarySignals.at(sig).toAscii()); + allReceivers += receivers; + } + if (entry <= allReceivers.size()) + targetObject = allReceivers.at(entry-1); + } + break; + default: + break; + } + + *target = QAccessible::queryAccessibleInterface(targetObject); + return *target ? 0 : -1; +} + +/*! \reimp */ +int QAccessibleWidget::childCount() const +{ + QWidgetList cl = childWidgets(widget()); + return cl.size(); +} + +/*! \reimp */ +int QAccessibleWidget::indexOfChild(const QAccessibleInterface *child) const +{ + QWidgetList cl = childWidgets(widget()); + int index = cl.indexOf(qobject_cast(child->object())); + if (index != -1) + ++index; + return index; +} + +// from qwidget.cpp +extern QString qt_setWindowTitle_helperHelper(const QString &, const QWidget*); + +/*! \reimp */ +QString QAccessibleWidget::text(Text t, int child) const +{ + QString str; + + switch (t) { + case Name: + if (!d->name.isEmpty()) { + str = d->name; + } else if (!widget()->accessibleName().isEmpty()) { + str = widget()->accessibleName(); + } else if (!child && widget()->isWindow()) { + if (widget()->isMinimized()) + str = qt_setWindowTitle_helperHelper(widget()->windowIconText(), widget()); + else + str = qt_setWindowTitle_helperHelper(widget()->windowTitle(), widget()); + } else { + str = qt_accStripAmp(buddyString(widget())); + } + break; + case Description: + if (!d->description.isEmpty()) + str = d->description; + else if (!widget()->accessibleDescription().isEmpty()) + str = widget()->accessibleDescription(); +#ifndef QT_NO_TOOLTIP + else + str = widget()->toolTip(); +#endif + break; + case Help: + if (!d->help.isEmpty()) + str = d->help; +#ifndef QT_NO_WHATSTHIS + else + str = widget()->whatsThis(); +#endif + break; + case Accelerator: + if (!d->accelerator.isEmpty()) + str = d->accelerator; + else + str = qt_accHotKey(buddyString(widget())); + break; + case Value: + str = d->value; + break; + default: + break; + } + return str; +} + +#ifndef QT_NO_ACTION + +/*! \reimp */ +int QAccessibleWidget::userActionCount(int child) const +{ + if (child) + return 0; + return widget()->actions().count(); +} + +/*! \reimp */ +QString QAccessibleWidget::actionText(int action, Text t, int child) const +{ + if (action == DefaultAction) + action = SetFocus; + + if (action > 0 && !child) { + QAction *act = widget()->actions().value(action - 1); + if (act) { + switch (t) { + case Name: + return act->text(); + case Description: + return act->toolTip(); +#ifndef QT_NO_SHORTCUT + case Accelerator: + return act->shortcut().toString(); +#endif + default: + break; + } + } + } + + return QAccessibleObject::actionText(action, t, child); +} + +/*! \reimp */ +bool QAccessibleWidget::doAction(int action, int child, const QVariantList ¶ms) +{ + if (action == SetFocus || action == DefaultAction) { + if (child || !widget()->isEnabled()) + return false; + if (widget()->focusPolicy() != Qt::NoFocus) + widget()->setFocus(); + else if (widget()->isWindow()) + widget()->activateWindow(); + else + return false; + return true; + } else if (action > 0) { + if (QAction *act = widget()->actions().value(action - 1)) { + act->trigger(); + return true; + } + } + return QAccessibleObject::doAction(action, child, params); +} + +#endif // QT_NO_ACTION + +/*! \reimp */ +QAccessible::Role QAccessibleWidget::role(int child) const +{ + if (!child) + return d->role; + + QWidgetList childList = childWidgets(widget()); + if (childList.count() > 0 && child <= childList.count()) { + QWidget *targetWidget = childList.at(child - 1); + QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(targetWidget); + if (iface) { + QAccessible::Role role = iface->role(0); + delete iface; + return role; + } + } + + return NoRole; +} + +/*! \reimp */ +QAccessible::State QAccessibleWidget::state(int child) const +{ + if (child) + return Normal; + + QAccessible::State state = Normal; + + QWidget *w = widget(); + if (w->testAttribute(Qt::WA_WState_Visible) == false) + state |= Invisible; + if (w->focusPolicy() != Qt::NoFocus && w->isActiveWindow()) + state |= Focusable; + if (w->hasFocus()) + state |= Focused; + if (!w->isEnabled()) + state |= Unavailable; + if (w->isWindow()) { + if (w->windowFlags() & Qt::WindowSystemMenuHint) + state |= Movable; + if (w->minimumSize() != w->maximumSize()) + state |= Sizeable; + } + + return state; +} + +// ### Qt 5: remove me - binary compatibility hack +QAccessibleWidgetEx::QAccessibleWidgetEx(QWidget *o, Role role, const QString& name) + : QAccessibleObjectEx(o) +{ + Q_ASSERT(widget()); + d = new QAccessibleWidgetPrivate(); + d->role = role; + d->name = name; + d->asking = 0; +} + +int QAccessibleWidgetEx::childCount() const +{ return reinterpret_cast(this)->QAccessibleWidget::childCount(); } +int QAccessibleWidgetEx::indexOfChild(const QAccessibleInterface *child) const +{ return reinterpret_cast(this)->QAccessibleWidget::indexOfChild(child); } +QAccessible::Relation QAccessibleWidgetEx::relationTo(int child, const QAccessibleInterface *other, int otherChild) const +{ return reinterpret_cast(this)->QAccessibleWidget::relationTo(child, other, otherChild); } + +int QAccessibleWidgetEx::childAt(int x, int y) const +{ return reinterpret_cast(this)->QAccessibleWidget::childAt(x, y); } +QRect QAccessibleWidgetEx::rect(int child) const +{ return reinterpret_cast(this)->QAccessibleWidget::rect(child); } +int QAccessibleWidgetEx::navigate(RelationFlag rel, int entry, QAccessibleInterface **target) const +{ return reinterpret_cast(this)->QAccessibleWidget::navigate(rel, entry, target); } + +QString QAccessibleWidgetEx::text(Text t, int child) const +{ return reinterpret_cast(this)->QAccessibleWidget::text(t, child); } +QAccessible::Role QAccessibleWidgetEx::role(int child) const +{ return reinterpret_cast(this)->QAccessibleWidget::role(child); } +QAccessible::State QAccessibleWidgetEx::state(int child) const +{ return (reinterpret_cast(this)->QAccessibleWidget::state(child)) + | HasInvokeExtension; } + +QString QAccessibleWidgetEx::actionText(int action, Text t, int child) const +{ return reinterpret_cast(this)->QAccessibleWidget::actionText(action, t, child); } +bool QAccessibleWidgetEx::doAction(int action, int child, const QVariantList ¶ms) +{ return reinterpret_cast(this)->QAccessibleWidget::doAction(action, child, params); } + +QAccessibleWidgetEx::~QAccessibleWidgetEx() +{ delete d; } +QWidget *QAccessibleWidgetEx::widget() const +{ return reinterpret_cast(this)->QAccessibleWidget::widget(); } +QObject *QAccessibleWidgetEx::parentObject() const +{ return reinterpret_cast(this)->QAccessibleWidget::parentObject(); } + +void QAccessibleWidgetEx::addControllingSignal(const QString &signal) +{ reinterpret_cast(this)->QAccessibleWidget::addControllingSignal(signal); } +void QAccessibleWidgetEx::setValue(const QString &value) +{ reinterpret_cast(this)->QAccessibleWidget::setValue(value); } +void QAccessibleWidgetEx::setDescription(const QString &desc) +{ reinterpret_cast(this)->QAccessibleWidget::setDescription(desc); } +void QAccessibleWidgetEx::setHelp(const QString &help) +{ reinterpret_cast(this)->QAccessibleWidget::setHelp(help); } +void QAccessibleWidgetEx::setAccelerator(const QString &accel) +{ reinterpret_cast(this)->QAccessibleWidget::setAccelerator(accel); } + +QVariant QAccessibleWidgetEx::invokeMethodEx(Method method, int child, const QVariantList & /*params*/) +{ + if (child) + return QVariant(); + + switch (method) { + case ListSupportedMethods: { + QSet set; + set << ListSupportedMethods << ForegroundColor << BackgroundColor; + return QVariant::fromValue(set); + } + case ForegroundColor: + return widget()->palette().color(widget()->foregroundRole()); + case BackgroundColor: + return widget()->palette().color(widget()->backgroundRole()); + default: + return QVariant(); + } +} + +QT_END_NAMESPACE + +#endif //QT_NO_ACCESSIBILITY diff --git a/src/gui/accessible/qaccessiblewidget.h b/src/gui/accessible/qaccessiblewidget.h new file mode 100644 index 0000000000..ef22e05c60 --- /dev/null +++ b/src/gui/accessible/qaccessiblewidget.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACCESSIBLEWIDGET_H +#define QACCESSIBLEWIDGET_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACCESSIBILITY + +class QAccessibleWidgetPrivate; + +class Q_GUI_EXPORT QAccessibleWidget : public QAccessibleObject +{ +public: + explicit QAccessibleWidget(QWidget *o, Role r = Client, const QString& name = QString()); + + int childCount() const; + int indexOfChild(const QAccessibleInterface *child) const; + Relation relationTo(int child, const QAccessibleInterface *other, int otherChild) const; + + int childAt(int x, int y) const; + QRect rect(int child) const; + int navigate(RelationFlag rel, int entry, QAccessibleInterface **target) const; + + QString text(Text t, int child) const; + Role role(int child) const; + State state(int child) const; + +#ifndef QT_NO_ACTION + int userActionCount(int child) const; + QString actionText(int action, Text t, int child) const; + bool doAction(int action, int child, const QVariantList ¶ms); +#endif + +protected: + ~QAccessibleWidget(); + QWidget *widget() const; + QObject *parentObject() const; + + void addControllingSignal(const QString &signal); + void setValue(const QString &value); + void setDescription(const QString &desc); + void setHelp(const QString &help); + void setAccelerator(const QString &accel); + +private: + friend class QAccessibleWidgetEx; + QAccessibleWidgetPrivate *d; + Q_DISABLE_COPY(QAccessibleWidget) +}; + +class Q_GUI_EXPORT QAccessibleWidgetEx : public QAccessibleObjectEx +{ +public: + explicit QAccessibleWidgetEx(QWidget *o, Role r = Client, const QString& name = QString()); + + int childCount() const; + int indexOfChild(const QAccessibleInterface *child) const; + Relation relationTo(int child, const QAccessibleInterface *other, int otherChild) const; + + int childAt(int x, int y) const; + QRect rect(int child) const; + int navigate(RelationFlag rel, int entry, QAccessibleInterface **target) const; + + QString text(Text t, int child) const; + Role role(int child) const; + State state(int child) const; + + QString actionText(int action, Text t, int child) const; + bool doAction(int action, int child, const QVariantList ¶ms); + + QVariant invokeMethodEx(Method method, int child, const QVariantList ¶ms); + +protected: + ~QAccessibleWidgetEx(); + QWidget *widget() const; + QObject *parentObject() const; + + void addControllingSignal(const QString &signal); + void setValue(const QString &value); + void setDescription(const QString &desc); + void setHelp(const QString &help); + void setAccelerator(const QString &accel); + +private: + QAccessibleWidgetPrivate *d; + Q_DISABLE_COPY(QAccessibleWidgetEx) +}; + +#endif // QT_NO_ACCESSIBILITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACCESSIBLEWIDGET_H diff --git a/src/gui/animation/animation.pri b/src/gui/animation/animation.pri new file mode 100644 index 0000000000..27763ca003 --- /dev/null +++ b/src/gui/animation/animation.pri @@ -0,0 +1,3 @@ +# Qt gui animation module + +SOURCES += animation/qguivariantanimation.cpp diff --git a/src/gui/animation/qguivariantanimation.cpp b/src/gui/animation/qguivariantanimation.cpp new file mode 100644 index 0000000000..ccd27d5d91 --- /dev/null +++ b/src/gui/animation/qguivariantanimation.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include + +#ifndef QT_NO_ANIMATION + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +template<> Q_INLINE_TEMPLATE QColor _q_interpolate(const QColor &f,const QColor &t, qreal progress) +{ + return QColor(qBound(0,_q_interpolate(f.red(), t.red(), progress),255), + qBound(0,_q_interpolate(f.green(), t.green(), progress),255), + qBound(0,_q_interpolate(f.blue(), t.blue(), progress),255), + qBound(0,_q_interpolate(f.alpha(), t.alpha(), progress),255)); +} + +template<> Q_INLINE_TEMPLATE QQuaternion _q_interpolate(const QQuaternion &f,const QQuaternion &t, qreal progress) +{ + return QQuaternion::slerp(f, t, progress); +} + +static int qRegisterGuiGetInterpolator() +{ + qRegisterAnimationInterpolator(_q_interpolateVariant); + qRegisterAnimationInterpolator(_q_interpolateVariant); + qRegisterAnimationInterpolator(_q_interpolateVariant); + qRegisterAnimationInterpolator(_q_interpolateVariant); + qRegisterAnimationInterpolator(_q_interpolateVariant); + return 1; +} +Q_CONSTRUCTOR_FUNCTION(qRegisterGuiGetInterpolator) + +static int qUnregisterGuiGetInterpolator() +{ + // casts required by Sun CC 5.5 + qRegisterAnimationInterpolator( + (QVariant (*)(const QColor &, const QColor &, qreal))0); + qRegisterAnimationInterpolator( + (QVariant (*)(const QVector2D &, const QVector2D &, qreal))0); + qRegisterAnimationInterpolator( + (QVariant (*)(const QVector3D &, const QVector3D &, qreal))0); + qRegisterAnimationInterpolator( + (QVariant (*)(const QVector4D &, const QVector4D &, qreal))0); + qRegisterAnimationInterpolator( + (QVariant (*)(const QQuaternion &, const QQuaternion &, qreal))0); + + return 1; +} +Q_DESTRUCTOR_FUNCTION(qUnregisterGuiGetInterpolator) + +QT_END_NAMESPACE + +#endif //QT_NO_ANIMATION diff --git a/src/gui/dialogs/dialogs.pri b/src/gui/dialogs/dialogs.pri new file mode 100644 index 0000000000..6ba707c9d3 --- /dev/null +++ b/src/gui/dialogs/dialogs.pri @@ -0,0 +1,122 @@ +# Qt dialogs module + +HEADERS += \ + dialogs/qabstractprintdialog.h \ + dialogs/qabstractprintdialog_p.h \ + dialogs/qabstractpagesetupdialog.h \ + dialogs/qabstractpagesetupdialog_p.h \ + dialogs/qcolordialog.h \ + dialogs/qcolordialog_p.h \ + dialogs/qfscompleter_p.h \ + dialogs/qdialog.h \ + dialogs/qdialog_p.h \ + dialogs/qerrormessage.h \ + dialogs/qfiledialog.h \ + dialogs/qfiledialog_p.h \ + dialogs/qfontdialog.h \ + dialogs/qfontdialog_p.h \ + dialogs/qinputdialog.h \ + dialogs/qmessagebox.h \ + dialogs/qpagesetupdialog.h \ + dialogs/qprintdialog.h \ + dialogs/qprogressdialog.h \ + dialogs/qsidebar_p.h \ + dialogs/qfilesystemmodel.h \ + dialogs/qfilesystemmodel_p.h \ + dialogs/qfileinfogatherer_p.h \ + dialogs/qwizard.h \ + dialogs/qprintpreviewdialog.h + +!embedded:!qpa:mac { + OBJECTIVE_SOURCES += dialogs/qfiledialog_mac.mm \ + dialogs/qfontdialog_mac.mm \ + dialogs/qnspanelproxy_mac.mm \ + dialogs/qpagesetupdialog_mac.mm \ + dialogs/qprintdialog_mac.mm + +# Compile qcolordialog_mac.mm with exception support, disregarding the -no-exceptions +# configure option. (qcolordialog_mac needs to catch exceptions thrown by cocoa) + EXCEPTION_SOURCES = dialogs/qcolordialog_mac.mm + exceptions_compiler.commands = $$QMAKE_CXX -c + exceptions_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + exceptions_compiler.commands += -fexceptions + exceptions_compiler.dependency_type = TYPE_C + exceptions_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + exceptions_compiler.input = EXCEPTION_SOURCES + exceptions_compiler.variable_out = OBJECTS + exceptions_compiler.name = compiling[exceptopns] ${QMAKE_FILE_IN} + silent:exceptions_compiler.commands = @echo compiling[exceptopns] ${QMAKE_FILE_IN} && $$exceptions_compiler.commands + QMAKE_EXTRA_COMPILERS += exceptions_compiler +} + +win32 { + HEADERS += dialogs/qwizard_win_p.h \ + dialogs/qfiledialog_win_p.h + SOURCES += dialogs/qdialogsbinarycompat_win.cpp \ + dialogs/qfiledialog_win.cpp \ + dialogs/qpagesetupdialog_win.cpp \ + dialogs/qprintdialog_win.cpp \ + dialogs/qwizard_win.cpp + + !win32-borland:!wince*: LIBS += -lshell32 # the filedialog needs this library +} + +!mac:!embedded:!symbian:unix|qpa { + HEADERS += dialogs/qpagesetupdialog_unix_p.h + SOURCES += dialogs/qprintdialog_unix.cpp \ + dialogs/qpagesetupdialog_unix.cpp + FORMS += dialogs/qprintsettingsoutput.ui \ + dialogs/qprintwidget.ui \ + dialogs/qprintpropertieswidget.ui +} + +embedded { + contains(QT_CONFIG,qtopia) { + HEADERS += dialogs/qpagesetupdialog_unix_p.h + DEFINES += QTOPIA_PRINTDIALOG + SOURCES += dialogs/qprintdialog_qws.cpp \ + dialogs/qpagesetupdialog_unix.cpp + } else { + HEADERS += dialogs/qpagesetupdialog_unix_p.h + SOURCES += dialogs/qprintdialog_unix.cpp \ + dialogs/qpagesetupdialog_unix.cpp + FORMS += dialogs/qprintsettingsoutput.ui \ + dialogs/qprintwidget.ui \ + dialogs/qprintpropertieswidget.ui + } +} + +wince*|symbian: FORMS += dialogs/qfiledialog_embedded.ui +else: FORMS += dialogs/qfiledialog.ui + +INCLUDEPATH += $$PWD +SOURCES += \ + dialogs/qabstractprintdialog.cpp \ + dialogs/qabstractpagesetupdialog.cpp \ + dialogs/qcolordialog.cpp \ + dialogs/qdialog.cpp \ + dialogs/qerrormessage.cpp \ + dialogs/qfiledialog.cpp \ + dialogs/qfontdialog.cpp \ + dialogs/qinputdialog.cpp \ + dialogs/qmessagebox.cpp \ + dialogs/qprogressdialog.cpp \ + dialogs/qsidebar.cpp \ + dialogs/qfilesystemmodel.cpp \ + dialogs/qfileinfogatherer.cpp \ + dialogs/qpagesetupdialog.cpp \ + dialogs/qwizard.cpp \ + dialogs/qprintpreviewdialog.cpp + +symbian:contains(QT_CONFIG, s60) { + LIBS += -lCommonDialogs + SOURCES += dialogs/qfiledialog_symbian.cpp \ + dialogs/qcolordialog_symbian.cpp +} + +FORMS += dialogs/qpagesetupwidget.ui +RESOURCES += dialogs/qprintdialog.qrc +RESOURCES += dialogs/qmessagebox.qrc + +# Compensate for lack of platform defines in Symbian3 +symbian: DEFINES += SYMBIAN_VERSION_$$upper($$replace(SYMBIAN_VERSION,\\.,_)) diff --git a/src/gui/dialogs/images/fit-page-24.png b/src/gui/dialogs/images/fit-page-24.png new file mode 100644 index 0000000000..c7b39d8853 Binary files /dev/null and b/src/gui/dialogs/images/fit-page-24.png differ diff --git a/src/gui/dialogs/images/fit-page-32.png b/src/gui/dialogs/images/fit-page-32.png new file mode 100644 index 0000000000..98bc12d3ed Binary files /dev/null and b/src/gui/dialogs/images/fit-page-32.png differ diff --git a/src/gui/dialogs/images/fit-width-24.png b/src/gui/dialogs/images/fit-width-24.png new file mode 100644 index 0000000000..a729ffda54 Binary files /dev/null and b/src/gui/dialogs/images/fit-width-24.png differ diff --git a/src/gui/dialogs/images/fit-width-32.png b/src/gui/dialogs/images/fit-width-32.png new file mode 100644 index 0000000000..470a8b45d0 Binary files /dev/null and b/src/gui/dialogs/images/fit-width-32.png differ diff --git a/src/gui/dialogs/images/go-first-24.png b/src/gui/dialogs/images/go-first-24.png new file mode 100644 index 0000000000..55315ffa38 Binary files /dev/null and b/src/gui/dialogs/images/go-first-24.png differ diff --git a/src/gui/dialogs/images/go-first-32.png b/src/gui/dialogs/images/go-first-32.png new file mode 100644 index 0000000000..0fe6f94b77 Binary files /dev/null and b/src/gui/dialogs/images/go-first-32.png differ diff --git a/src/gui/dialogs/images/go-last-24.png b/src/gui/dialogs/images/go-last-24.png new file mode 100644 index 0000000000..81061b80f2 Binary files /dev/null and b/src/gui/dialogs/images/go-last-24.png differ diff --git a/src/gui/dialogs/images/go-last-32.png b/src/gui/dialogs/images/go-last-32.png new file mode 100644 index 0000000000..887506107e Binary files /dev/null and b/src/gui/dialogs/images/go-last-32.png differ diff --git a/src/gui/dialogs/images/go-next-24.png b/src/gui/dialogs/images/go-next-24.png new file mode 100644 index 0000000000..9a55ef3d86 Binary files /dev/null and b/src/gui/dialogs/images/go-next-24.png differ diff --git a/src/gui/dialogs/images/go-next-32.png b/src/gui/dialogs/images/go-next-32.png new file mode 100644 index 0000000000..6d98f50f4f Binary files /dev/null and b/src/gui/dialogs/images/go-next-32.png differ diff --git a/src/gui/dialogs/images/go-previous-24.png b/src/gui/dialogs/images/go-previous-24.png new file mode 100644 index 0000000000..2ea769eb8d Binary files /dev/null and b/src/gui/dialogs/images/go-previous-24.png differ diff --git a/src/gui/dialogs/images/go-previous-32.png b/src/gui/dialogs/images/go-previous-32.png new file mode 100644 index 0000000000..37ba0c4e8d Binary files /dev/null and b/src/gui/dialogs/images/go-previous-32.png differ diff --git a/src/gui/dialogs/images/layout-landscape-24.png b/src/gui/dialogs/images/layout-landscape-24.png new file mode 100644 index 0000000000..6f89a31cb6 Binary files /dev/null and b/src/gui/dialogs/images/layout-landscape-24.png differ diff --git a/src/gui/dialogs/images/layout-landscape-32.png b/src/gui/dialogs/images/layout-landscape-32.png new file mode 100644 index 0000000000..6a94946c36 Binary files /dev/null and b/src/gui/dialogs/images/layout-landscape-32.png differ diff --git a/src/gui/dialogs/images/layout-portrait-24.png b/src/gui/dialogs/images/layout-portrait-24.png new file mode 100644 index 0000000000..e0dbabc83b Binary files /dev/null and b/src/gui/dialogs/images/layout-portrait-24.png differ diff --git a/src/gui/dialogs/images/layout-portrait-32.png b/src/gui/dialogs/images/layout-portrait-32.png new file mode 100644 index 0000000000..d17468c0a4 Binary files /dev/null and b/src/gui/dialogs/images/layout-portrait-32.png differ diff --git a/src/gui/dialogs/images/page-setup-24.png b/src/gui/dialogs/images/page-setup-24.png new file mode 100644 index 0000000000..4bfafdace0 Binary files /dev/null and b/src/gui/dialogs/images/page-setup-24.png differ diff --git a/src/gui/dialogs/images/page-setup-32.png b/src/gui/dialogs/images/page-setup-32.png new file mode 100644 index 0000000000..2313b8fe3b Binary files /dev/null and b/src/gui/dialogs/images/page-setup-32.png differ diff --git a/src/gui/dialogs/images/print-24.png b/src/gui/dialogs/images/print-24.png new file mode 100644 index 0000000000..c6bf3e8672 Binary files /dev/null and b/src/gui/dialogs/images/print-24.png differ diff --git a/src/gui/dialogs/images/print-32.png b/src/gui/dialogs/images/print-32.png new file mode 100644 index 0000000000..5830888653 Binary files /dev/null and b/src/gui/dialogs/images/print-32.png differ diff --git a/src/gui/dialogs/images/qtlogo-64.png b/src/gui/dialogs/images/qtlogo-64.png new file mode 100644 index 0000000000..4f68e162de Binary files /dev/null and b/src/gui/dialogs/images/qtlogo-64.png differ diff --git a/src/gui/dialogs/images/status-color.png b/src/gui/dialogs/images/status-color.png new file mode 100644 index 0000000000..af3cbfa31c Binary files /dev/null and b/src/gui/dialogs/images/status-color.png differ diff --git a/src/gui/dialogs/images/status-gray-scale.png b/src/gui/dialogs/images/status-gray-scale.png new file mode 100644 index 0000000000..4462588809 Binary files /dev/null and b/src/gui/dialogs/images/status-gray-scale.png differ diff --git a/src/gui/dialogs/images/view-page-multi-24.png b/src/gui/dialogs/images/view-page-multi-24.png new file mode 100644 index 0000000000..87241472ae Binary files /dev/null and b/src/gui/dialogs/images/view-page-multi-24.png differ diff --git a/src/gui/dialogs/images/view-page-multi-32.png b/src/gui/dialogs/images/view-page-multi-32.png new file mode 100644 index 0000000000..130885a041 Binary files /dev/null and b/src/gui/dialogs/images/view-page-multi-32.png differ diff --git a/src/gui/dialogs/images/view-page-one-24.png b/src/gui/dialogs/images/view-page-one-24.png new file mode 100644 index 0000000000..4c6457b892 Binary files /dev/null and b/src/gui/dialogs/images/view-page-one-24.png differ diff --git a/src/gui/dialogs/images/view-page-one-32.png b/src/gui/dialogs/images/view-page-one-32.png new file mode 100644 index 0000000000..537193984e Binary files /dev/null and b/src/gui/dialogs/images/view-page-one-32.png differ diff --git a/src/gui/dialogs/images/view-page-sided-24.png b/src/gui/dialogs/images/view-page-sided-24.png new file mode 100644 index 0000000000..2131305c41 Binary files /dev/null and b/src/gui/dialogs/images/view-page-sided-24.png differ diff --git a/src/gui/dialogs/images/view-page-sided-32.png b/src/gui/dialogs/images/view-page-sided-32.png new file mode 100644 index 0000000000..e4d63f9992 Binary files /dev/null and b/src/gui/dialogs/images/view-page-sided-32.png differ diff --git a/src/gui/dialogs/images/zoom-in-24.png b/src/gui/dialogs/images/zoom-in-24.png new file mode 100644 index 0000000000..d29b142b6c Binary files /dev/null and b/src/gui/dialogs/images/zoom-in-24.png differ diff --git a/src/gui/dialogs/images/zoom-in-32.png b/src/gui/dialogs/images/zoom-in-32.png new file mode 100644 index 0000000000..34d70af37b Binary files /dev/null and b/src/gui/dialogs/images/zoom-in-32.png differ diff --git a/src/gui/dialogs/images/zoom-out-24.png b/src/gui/dialogs/images/zoom-out-24.png new file mode 100644 index 0000000000..19703474f8 Binary files /dev/null and b/src/gui/dialogs/images/zoom-out-24.png differ diff --git a/src/gui/dialogs/images/zoom-out-32.png b/src/gui/dialogs/images/zoom-out-32.png new file mode 100644 index 0000000000..b832206612 Binary files /dev/null and b/src/gui/dialogs/images/zoom-out-32.png differ diff --git a/src/gui/dialogs/qabstractpagesetupdialog.cpp b/src/gui/dialogs/qabstractpagesetupdialog.cpp new file mode 100644 index 0000000000..080ddeaa85 --- /dev/null +++ b/src/gui/dialogs/qabstractpagesetupdialog.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractpagesetupdialog.h" +#include "qabstractpagesetupdialog_p.h" + +#ifndef QT_NO_PRINTDIALOG + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QAbstractPageSetupDialog + + \brief The QAbstractPageSetupDialog class provides a base for + implementations of page setup dialogs. +*/ + +/*! + Constructs the page setup dialog for the printer \a printer with + \a parent as parent widget. +*/ +QAbstractPageSetupDialog::QAbstractPageSetupDialog(QPrinter *printer, QWidget *parent) + : QDialog(*(new QAbstractPageSetupDialogPrivate), parent) +{ + Q_D(QAbstractPageSetupDialog); + setWindowTitle(QCoreApplication::translate("QPrintPreviewDialog", "Page Setup")); + d->setPrinter(printer); +} + +/*! + \internal +*/ +QAbstractPageSetupDialog::QAbstractPageSetupDialog(QAbstractPageSetupDialogPrivate &ptr, + QPrinter *printer, QWidget *parent) + : QDialog(ptr, parent) +{ + Q_D(QAbstractPageSetupDialog); + setWindowTitle(QCoreApplication::translate("QPrintPreviewDialog", "Page Setup")); + d->setPrinter(printer); +} + +QAbstractPageSetupDialog::~QAbstractPageSetupDialog() +{ + Q_D(QAbstractPageSetupDialog); + if (d->opts & QPageSetupDialog::OwnsPrinter) + delete d->printer; +} + +/*! + Returns the printer that this page setup dialog is operating on. +*/ +QPrinter *QAbstractPageSetupDialog::printer() +{ + Q_D(QAbstractPageSetupDialog); + return d->printer; +} + +void QAbstractPageSetupDialogPrivate::setPrinter(QPrinter *newPrinter) +{ + if (newPrinter) { + printer = newPrinter; + } else { + printer = new QPrinter; + opts |= QPageSetupDialog::OwnsPrinter; + } +#ifndef Q_WS_X11 + if (printer->outputFormat() != QPrinter::NativeFormat) + qWarning("QPageSetupDialog: Cannot be used on non-native printers"); +#endif +} + +/*! + \fn int QAbstractPageSetupDialog::exec() + + This virtual function is called to pop up the dialog. It must be + reimplemented in subclasses. +*/ + +/*! + \reimp +*/ +void QAbstractPageSetupDialog::done(int result) +{ + Q_D(QAbstractPageSetupDialog); + QDialog::done(result); + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(accepted()), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); + +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qabstractpagesetupdialog.h b/src/gui/dialogs/qabstractpagesetupdialog.h new file mode 100644 index 0000000000..ac3ffa5451 --- /dev/null +++ b/src/gui/dialogs/qabstractpagesetupdialog.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPAGESETUPDIALOG_H +#define QABSTRACTPAGESETUPDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTDIALOG + +class QAbstractPageSetupDialogPrivate; +class QPrinter; + +// ### Qt 5: Remove this class +class Q_GUI_EXPORT QAbstractPageSetupDialog : public QDialog +{ + Q_DECLARE_PRIVATE(QAbstractPageSetupDialog) + Q_OBJECT + +public: + explicit QAbstractPageSetupDialog(QPrinter *printer, QWidget *parent = 0); + QAbstractPageSetupDialog(QAbstractPageSetupDialogPrivate &ptr, + QPrinter *printer, QWidget *parent = 0); + ~QAbstractPageSetupDialog(); + + virtual int exec() = 0; + void done(int result); + + QPrinter *printer(); +}; + +#endif // QT_NO_PRINTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTPAGESETUPDIALOG_H diff --git a/src/gui/dialogs/qabstractpagesetupdialog_p.h b/src/gui/dialogs/qabstractpagesetupdialog_p.h new file mode 100644 index 0000000000..3dbfdd5e26 --- /dev/null +++ b/src/gui/dialogs/qabstractpagesetupdialog_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPAGESETUPDIALOG_P_H +#define QABSTRACTPAGESETUPDIALOG_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 +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qdialog_p.h" + +#ifndef QT_NO_PRINTDIALOG + +#include "qbytearray.h" +#include "qpagesetupdialog.h" +#include "qpointer.h" + +QT_BEGIN_NAMESPACE + +class QPrinter; + +class QAbstractPageSetupDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QAbstractPageSetupDialog) + +public: + QAbstractPageSetupDialogPrivate() : printer(0) {} + + void setPrinter(QPrinter *newPrinter); + + QPrinter *printer; + QPageSetupDialog::PageSetupDialogOptions opts; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTDIALOG + +#endif // QABSTRACTPAGESETUPDIALOG_P_H diff --git a/src/gui/dialogs/qabstractprintdialog.cpp b/src/gui/dialogs/qabstractprintdialog.cpp new file mode 100644 index 0000000000..ef46697c2c --- /dev/null +++ b/src/gui/dialogs/qabstractprintdialog.cpp @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractprintdialog_p.h" +#include "qcoreapplication.h" +#include "qprintdialog.h" +#include "qprinter.h" +#include "private/qprinter_p.h" + +#ifndef QT_NO_PRINTDIALOG + +QT_BEGIN_NAMESPACE + +// hack +class QPrintDialogPrivate : public QAbstractPrintDialogPrivate +{ +}; + +/*! + \class QAbstractPrintDialog + \brief The QAbstractPrintDialog class provides a base implementation for + print dialogs used to configure printers. + + \ingroup printing + + This class implements getter and setter functions that are used to + customize settings shown in print dialogs, but it is not used directly. + Use QPrintDialog to display a print dialog in your application. + + In Symbian, there is no support for printing. Hence, this dialog should not + be used in Symbian. + + \sa QPrintDialog, QPrinter, {Printing with Qt} +*/ + +/*! + \enum QAbstractPrintDialog::PrintRange + + Used to specify the print range selection option. + + \value AllPages All pages should be printed. + \value Selection Only the selection should be printed. + \value PageRange The specified page range should be printed. + \value CurrentPage Only the currently visible page should be printed. + + \sa QPrinter::PrintRange +*/ + +/*! + \enum QAbstractPrintDialog::PrintDialogOption + + Used to specify which parts of the print dialog should be visible. + + \value None None of the options are enabled. + \value PrintToFile The print to file option is enabled. + \value PrintSelection The print selection option is enabled. + \value PrintPageRange The page range selection option is enabled. + \value PrintShowPageSize Show the page size + margins page only if this is enabled. + \value PrintCollateCopies The collate copies option is enabled + \value PrintCurrentPage The print current page option is enabled + + This value is obsolete and does nothing since Qt 4.5: + + \value DontUseSheet In previous versions of Qt, exec() the print dialog + would create a sheet by default the dialog was given a parent. + This is no longer supported in Qt 4.5. If you want to use sheets, use + QPrintDialog::open() instead. +*/ + +/*! + Constructs an abstract print dialog for \a printer with \a parent + as parent widget. +*/ +QAbstractPrintDialog::QAbstractPrintDialog(QPrinter *printer, QWidget *parent) + : QDialog(*(new QAbstractPrintDialogPrivate), parent) +{ + Q_D(QAbstractPrintDialog); + setWindowTitle(QCoreApplication::translate("QPrintDialog", "Print")); + d->setPrinter(printer); +} + +/*! + \internal +*/ +QAbstractPrintDialog::QAbstractPrintDialog(QAbstractPrintDialogPrivate &ptr, + QPrinter *printer, + QWidget *parent) + : QDialog(ptr, parent) +{ + Q_D(QAbstractPrintDialog); + setWindowTitle(QCoreApplication::translate("QPrintDialog", "Print")); + d->setPrinter(printer); +} + +/*! + \internal +*/ +QAbstractPrintDialog::~QAbstractPrintDialog() +{ + Q_D(QAbstractPrintDialog); + if (d->ownsPrinter) + delete d->printer; +} + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QPrintDialog::setOption(PrintDialogOption option, bool on) +{ + Q_D(QPrintDialog); + if (!(d->pd->options & option) != !on) + setOptions(d->pd->options ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QPrintDialog::testOption(PrintDialogOption option) const +{ + Q_D(const QPrintDialog); + return (d->pd->options & option) != 0; +} + +/*! + \property QPrintDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QPrintDialog::setOptions(PrintDialogOptions options) +{ + Q_D(QPrintDialog); + + PrintDialogOptions changed = (options ^ d->pd->options); + if (!changed) + return; + + d->pd->options = options; +} + +QPrintDialog::PrintDialogOptions QPrintDialog::options() const +{ + Q_D(const QPrintDialog); + return d->pd->options; +} + +/*! + \obsolete + + Use QPrintDialog::setOptions() instead. +*/ +void QAbstractPrintDialog::setEnabledOptions(PrintDialogOptions options) +{ + Q_D(QAbstractPrintDialog); + d->pd->options = options; +} + +/*! + \obsolete + + Use QPrintDialog::setOption(\a option, true) instead. +*/ +void QAbstractPrintDialog::addEnabledOption(PrintDialogOption option) +{ + Q_D(QAbstractPrintDialog); + d->pd->options |= option; +} + +/*! + \obsolete + + Use QPrintDialog::options() instead. +*/ +QAbstractPrintDialog::PrintDialogOptions QAbstractPrintDialog::enabledOptions() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->options; +} + +/*! + \obsolete + + Use QPrintDialog::testOption(\a option) instead. +*/ +bool QAbstractPrintDialog::isOptionEnabled(PrintDialogOption option) const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->options & option; +} + +/*! + Sets the print range option in to be \a range. + */ +void QAbstractPrintDialog::setPrintRange(PrintRange range) +{ + Q_D(QAbstractPrintDialog); + d->pd->printRange = range; +} + +/*! + Returns the print range. +*/ +QAbstractPrintDialog::PrintRange QAbstractPrintDialog::printRange() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->printRange; +} + +/*! + Sets the page range in this dialog to be from \a min to \a max. This also + enables the PrintPageRange option. +*/ +void QAbstractPrintDialog::setMinMax(int min, int max) +{ + Q_D(QAbstractPrintDialog); + Q_ASSERT_X(min <= max, "QAbstractPrintDialog::setMinMax", + "'min' must be less than or equal to 'max'"); + d->pd->minPage = min; + d->pd->maxPage = max; + d->pd->options |= PrintPageRange; +} + +/*! + Returns the minimum page in the page range. + By default, this value is set to 1. +*/ +int QAbstractPrintDialog::minPage() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->minPage; +} + +/*! + Returns the maximum page in the page range. As of Qt 4.4, this + function returns INT_MAX by default. Previous versions returned 1 + by default. +*/ +int QAbstractPrintDialog::maxPage() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->maxPage; +} + +/*! + Sets the range in the print dialog to be from \a from to \a to. +*/ +void QAbstractPrintDialog::setFromTo(int from, int to) +{ + Q_D(QAbstractPrintDialog); + Q_ASSERT_X(from <= to, "QAbstractPrintDialog::setFromTo", + "'from' must be less than or equal to 'to'"); + d->pd->fromPage = from; + d->pd->toPage = to; + + if (d->pd->minPage == 0 && d->pd->maxPage == 0) + setMinMax(1, to); +} + +/*! + Returns the first page to be printed + By default, this value is set to 0. +*/ +int QAbstractPrintDialog::fromPage() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->fromPage; +} + +/*! + Returns the last page to be printed. + By default, this value is set to 0. +*/ +int QAbstractPrintDialog::toPage() const +{ + Q_D(const QAbstractPrintDialog); + return d->pd->toPage; +} + + +/*! + Returns the printer that this printer dialog operates + on. +*/ +QPrinter *QAbstractPrintDialog::printer() const +{ + Q_D(const QAbstractPrintDialog); + return d->printer; +} + +void QAbstractPrintDialogPrivate::setPrinter(QPrinter *newPrinter) +{ + if (newPrinter) { + printer = newPrinter; + ownsPrinter = false; + } else { + printer = new QPrinter; + ownsPrinter = true; + } + pd = printer->d_func(); +} + +/*! + \fn int QAbstractPrintDialog::exec() + + This virtual function is called to pop up the dialog. It must be + reimplemented in subclasses. +*/ + +/*! + \class QPrintDialog + + \brief The QPrintDialog class provides a dialog for specifying + the printer's configuration. + + \ingroup standard-dialogs + \ingroup printing + + The dialog allows users to change document-related settings, such + as the paper size and orientation, type of print (color or + grayscale), range of pages, and number of copies to print. + + Controls are also provided to enable users to choose from the + printers available, including any configured network printers. + + Typically, QPrintDialog objects are constructed with a QPrinter + object, and executed using the exec() function. + + \snippet doc/src/snippets/code/src_gui_dialogs_qabstractprintdialog.cpp 0 + + If the dialog is accepted by the user, the QPrinter object is + correctly configured for printing. + + \table + \row + \o \inlineimage plastique-printdialog.png + \o \inlineimage plastique-printdialog-properties.png + \endtable + + The printer dialog (shown above in Plastique style) enables access to common + printing properties. On X11 platforms that use the CUPS printing system, the + settings for each available printer can be modified via the dialog's + \gui{Properties} push button. + + On Windows and Mac OS X, the native print dialog is used, which means that + some QWidget and QDialog properties set on the dialog won't be respected. + The native print dialog on Mac OS X does not support setting printer options, + i.e. setOptions() and setOption() have no effect. + + In Qt 4.4, it was possible to use the static functions to show a sheet on + Mac OS X. This is no longer supported in Qt 4.5. If you want this + functionality, use QPrintDialog::open(). + + \sa QPageSetupDialog, QPrinter, {Pixelator Example}, {Order Form Example}, + {Image Viewer Example}, {Scribble Example} +*/ + +/*! + \fn QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent) + + Constructs a new modal printer dialog for the given \a printer + with the given \a parent. +*/ + +/*! + \fn QPrintDialog::~QPrintDialog() + + Destroys the print dialog. +*/ + +/*! + \fn int QPrintDialog::exec() + \reimp +*/ + +/*! + \since 4.4 + + Set a list of widgets as \a tabs to be shown on the print dialog, if supported. + + Currently this option is only supported on X11. + + Setting the option tabs will transfer their ownership to the print dialog. +*/ +void QAbstractPrintDialog::setOptionTabs(const QList &tabs) +{ + Q_D(QAbstractPrintDialog); + d->setTabs(tabs); +} + +/*! + + \fn void QPrintDialog::accepted(QPrinter *printer) + + This signal is emitted when the user accepts the values set in the print dialog. + The \a printer parameter includes the printer that the settings were applied to. +*/ + +/*! + \fn QPrinter *QPrintDialog::printer() + + Returns the printer that this printer dialog operates + on. This can be useful when using the QPrintDialog::open() method. +*/ + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QPrintDialog::done(int result) +{ + Q_D(QPrintDialog); + QDialog::done(result); + if (result == Accepted) + emit accepted(printer()); + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(accepted(QPrinter*)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +/*! + \since 4.5 + \overload + + Opens the dialog and connects its accepted() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QPrintDialog::open(QObject *receiver, const char *member) +{ + Q_D(QPrintDialog); + connect(this, SIGNAL(accepted(QPrinter*)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qabstractprintdialog.h b/src/gui/dialogs/qabstractprintdialog.h new file mode 100644 index 0000000000..02e7b0ea32 --- /dev/null +++ b/src/gui/dialogs/qabstractprintdialog.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPRINTDIALOG_H +#define QABSTRACTPRINTDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTER + +class QAbstractPrintDialogPrivate; +class QPrinter; + +// ### Qt 5: remove this class +class Q_GUI_EXPORT QAbstractPrintDialog : public QDialog +{ + Q_DECLARE_PRIVATE(QAbstractPrintDialog) + Q_OBJECT + +public: + enum PrintRange { + AllPages, + Selection, + PageRange, + CurrentPage + }; + + enum PrintDialogOption { + None = 0x0000, // obsolete + PrintToFile = 0x0001, + PrintSelection = 0x0002, + PrintPageRange = 0x0004, + PrintShowPageSize = 0x0008, + PrintCollateCopies = 0x0010, + DontUseSheet = 0x0020, + PrintCurrentPage = 0x0040 + }; + + Q_DECLARE_FLAGS(PrintDialogOptions, PrintDialogOption) + +#ifndef QT_NO_PRINTDIALOG + explicit QAbstractPrintDialog(QPrinter *printer, QWidget *parent = 0); + ~QAbstractPrintDialog(); + + virtual int exec() = 0; + + // obsolete + void addEnabledOption(PrintDialogOption option); + void setEnabledOptions(PrintDialogOptions options); + PrintDialogOptions enabledOptions() const; + bool isOptionEnabled(PrintDialogOption option) const; + + void setOptionTabs(const QList &tabs); + + void setPrintRange(PrintRange range); + PrintRange printRange() const; + + void setMinMax(int min, int max); + int minPage() const; + int maxPage() const; + + void setFromTo(int fromPage, int toPage); + int fromPage() const; + int toPage() const; + + QPrinter *printer() const; + +protected: + QAbstractPrintDialog(QAbstractPrintDialogPrivate &ptr, QPrinter *printer, QWidget *parent = 0); + +private: + Q_DISABLE_COPY(QAbstractPrintDialog) + +#endif // QT_NO_PRINTDIALOG +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractPrintDialog::PrintDialogOptions) + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTPRINTDIALOG_H diff --git a/src/gui/dialogs/qabstractprintdialog_p.h b/src/gui/dialogs/qabstractprintdialog_p.h new file mode 100644 index 0000000000..4de34a2ec3 --- /dev/null +++ b/src/gui/dialogs/qabstractprintdialog_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPRINTDIALOG_P_H +#define QABSTRACTPRINTDIALOG_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/qdialog_p.h" + +#ifndef QT_NO_PRINTDIALOG + +#include "QtGui/qabstractprintdialog.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +class QPrinter; +class QPrinterPrivate; + +class QAbstractPrintDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QAbstractPrintDialog) + +public: + QAbstractPrintDialogPrivate() + : printer(0), pd(0), ownsPrinter(false) + { + } + + QPrinter *printer; + QPrinterPrivate *pd; + bool ownsPrinter; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + + virtual void setTabs(const QList &) {} + void setPrinter(QPrinter *newPrinter); +}; + +#endif //QT_NO_PRINTER + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTDIALOG + +#endif // QABSTRACTPRINTDIALOG_P_H diff --git a/src/gui/dialogs/qcolordialog.cpp b/src/gui/dialogs/qcolordialog.cpp new file mode 100644 index 0000000000..94a54fe26c --- /dev/null +++ b/src/gui/dialogs/qcolordialog.cpp @@ -0,0 +1,2114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" + +#ifndef QT_NO_COLORDIALOG + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qdrawutil.h" +#include "qevent.h" +#include "qimage.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qlineedit.h" +#include "qmenu.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qpushbutton.h" +#include "qsettings.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qvalidator.h" +#include "qmime.h" +#include "qspinbox.h" +#include "qdialogbuttonbox.h" +#include "private/qguiplatformplugin_p.h" + +#ifdef Q_WS_S60 +#include "private/qt_s60_p.h" +#endif + +#if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5) +# define QT_SMALL_COLORDIALOG +#endif + +QT_BEGIN_NAMESPACE + +//////////// QWellArray BEGIN + +struct QWellArrayData; + +class QWellArray : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int selectedColumn READ selectedColumn) + Q_PROPERTY(int selectedRow READ selectedRow) + +public: + QWellArray(int rows, int cols, QWidget* parent=0); + ~QWellArray() {} + QString cellContent(int row, int col) const; + + int selectedColumn() const { return selCol; } + int selectedRow() const { return selRow; } + + virtual void setCurrent(int row, int col); + virtual void setSelected(int row, int col); + + QSize sizeHint() const; + + virtual void setCellBrush(int row, int col, const QBrush &); + QBrush cellBrush(int row, int col); + + inline int cellWidth() const + { return cellw; } + + inline int cellHeight() const + { return cellh; } + + inline int rowAt(int y) const + { return y / cellh; } + + inline int columnAt(int x) const + { if (isRightToLeft()) return ncols - (x / cellw) - 1; return x / cellw; } + + inline int rowY(int row) const + { return cellh * row; } + + inline int columnX(int column) const + { if (isRightToLeft()) return cellw * (ncols - column - 1); return cellw * column; } + + inline int numRows() const + { return nrows; } + + inline int numCols() const + {return ncols; } + + inline QRect cellRect() const + { return QRect(0, 0, cellw, cellh); } + + inline QSize gridSize() const + { return QSize(ncols * cellw, nrows * cellh); } + + QRect cellGeometry(int row, int column) + { + QRect r; + if (row >= 0 && row < nrows && column >= 0 && column < ncols) + r.setRect(columnX(column), rowY(row), cellw, cellh); + return r; + } + + inline void updateCell(int row, int column) { update(cellGeometry(row, column)); } + +signals: + void selected(int row, int col); + +protected: + virtual void paintCell(QPainter *, int row, int col, const QRect&); + virtual void paintCellContents(QPainter *, int row, int col, const QRect&); + + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void keyPressEvent(QKeyEvent*); + void focusInEvent(QFocusEvent*); + void focusOutEvent(QFocusEvent*); + void paintEvent(QPaintEvent *); + +private: + Q_DISABLE_COPY(QWellArray) + + int nrows; + int ncols; + int cellw; + int cellh; + int curRow; + int curCol; + int selRow; + int selCol; + QWellArrayData *d; +}; + +void QWellArray::paintEvent(QPaintEvent *e) +{ + QRect r = e->rect(); + int cx = r.x(); + int cy = r.y(); + int ch = r.height(); + int cw = r.width(); + int colfirst = columnAt(cx); + int collast = columnAt(cx + cw); + int rowfirst = rowAt(cy); + int rowlast = rowAt(cy + ch); + + if (isRightToLeft()) { + int t = colfirst; + colfirst = collast; + collast = t; + } + + QPainter painter(this); + QPainter *p = &painter; + QRect rect(0, 0, cellWidth(), cellHeight()); + + + if (collast < 0 || collast >= ncols) + collast = ncols-1; + if (rowlast < 0 || rowlast >= nrows) + rowlast = nrows-1; + + // Go through the rows + for (int r = rowfirst; r <= rowlast; ++r) { + // get row position and height + int rowp = rowY(r); + + // Go through the columns in the row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + for (int c = colfirst; c <= collast; ++c) { + // get position and width of column c + int colp = columnX(c); + // Translate painter and draw the cell + rect.translate(colp, rowp); + paintCell(p, r, c, rect); + rect.translate(-colp, -rowp); + } + } +} + +struct QWellArrayData { + QBrush *brush; +}; + +QWellArray::QWellArray(int rows, int cols, QWidget *parent) + : QWidget(parent) + ,nrows(rows), ncols(cols) +{ + d = 0; + setFocusPolicy(Qt::StrongFocus); + cellw = 28; + cellh = 24; + curCol = 0; + curRow = 0; + selCol = -1; + selRow = -1; +} + +QSize QWellArray::sizeHint() const +{ + ensurePolished(); + return gridSize().boundedTo(QSize(640, 480)); +} + + +void QWellArray::paintCell(QPainter* p, int row, int col, const QRect &rect) +{ + int b = 3; //margin + + const QPalette & g = palette(); + QStyleOptionFrame opt; + int dfw = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + opt.lineWidth = dfw; + opt.midLineWidth = 1; + opt.rect = rect.adjusted(b, b, -b, -b); + opt.palette = g; + opt.state = QStyle::State_Enabled | QStyle::State_Sunken; + style()->drawPrimitive(QStyle::PE_Frame, &opt, p, this); + b += dfw; + + if ((row == curRow) && (col == curCol)) { + if (hasFocus()) { + QStyleOptionFocusRect opt; + opt.palette = g; + opt.rect = rect; + opt.state = QStyle::State_None | QStyle::State_KeyboardFocusChange; + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, p, this); + } + } + paintCellContents(p, row, col, opt.rect.adjusted(dfw, dfw, -dfw, -dfw)); +} + +/*! + Reimplement this function to change the contents of the well array. + */ +void QWellArray::paintCellContents(QPainter *p, int row, int col, const QRect &r) +{ + if (d) { + p->fillRect(r, d->brush[row*numCols()+col]); + } else { + p->fillRect(r, Qt::white); + p->setPen(Qt::black); + p->drawLine(r.topLeft(), r.bottomRight()); + p->drawLine(r.topRight(), r.bottomLeft()); + } +} + +void QWellArray::mousePressEvent(QMouseEvent *e) +{ + // The current cell marker is set to the cell the mouse is pressed in + QPoint pos = e->pos(); + setCurrent(rowAt(pos.y()), columnAt(pos.x())); +} + +void QWellArray::mouseReleaseEvent(QMouseEvent * /* event */) +{ + // The current cell marker is set to the cell the mouse is clicked in + setSelected(curRow, curCol); +} + + +/* + Sets the cell currently having the focus. This is not necessarily + the same as the currently selected cell. +*/ + +void QWellArray::setCurrent(int row, int col) +{ + if ((curRow == row) && (curCol == col)) + return; + + if (row < 0 || col < 0) + row = col = -1; + + int oldRow = curRow; + int oldCol = curCol; + + curRow = row; + curCol = col; + + updateCell(oldRow, oldCol); + updateCell(curRow, curCol); +} + +/* + Sets the currently selected cell to \a row, \a column. If \a row or + \a column are less than zero, the current cell is unselected. + + Does not set the position of the focus indicator. +*/ +void QWellArray::setSelected(int row, int col) +{ + int oldRow = selRow; + int oldCol = selCol; + + if (row < 0 || col < 0) + row = col = -1; + + selCol = col; + selRow = row; + + updateCell(oldRow, oldCol); + updateCell(selRow, selCol); + if (row >= 0) + emit selected(row, col); + +#ifndef QT_NO_MENU + if (isVisible() && qobject_cast(parentWidget())) + parentWidget()->close(); +#endif +} + +void QWellArray::focusInEvent(QFocusEvent*) +{ + updateCell(curRow, curCol); +} + +void QWellArray::setCellBrush(int row, int col, const QBrush &b) +{ + if (!d) { + d = new QWellArrayData; + int i = numRows()*numCols(); + d->brush = new QBrush[i]; + } + if (row >= 0 && row < numRows() && col >= 0 && col < numCols()) + d->brush[row*numCols()+col] = b; +} + +/* + Returns the brush set for the cell at \a row, \a column. If no brush is + set, Qt::NoBrush is returned. +*/ + +QBrush QWellArray::cellBrush(int row, int col) +{ + if (d && row >= 0 && row < numRows() && col >= 0 && col < numCols()) + return d->brush[row*numCols()+col]; + return Qt::NoBrush; +} + + + +/*!\reimp +*/ + +void QWellArray::focusOutEvent(QFocusEvent*) +{ + updateCell(curRow, curCol); +} + +/*\reimp +*/ +void QWellArray::keyPressEvent(QKeyEvent* e) +{ + switch(e->key()) { // Look at the key code + case Qt::Key_Left: // If 'left arrow'-key, + if(curCol > 0) // and cr't not in leftmost col + setCurrent(curRow, curCol - 1); // set cr't to next left column + break; + case Qt::Key_Right: // Correspondingly... + if(curCol < numCols()-1) + setCurrent(curRow, curCol + 1); + break; + case Qt::Key_Up: + if(curRow > 0) + setCurrent(curRow - 1, curCol); + break; + case Qt::Key_Down: + if(curRow < numRows()-1) + setCurrent(curRow + 1, curCol); + break; +#if 0 + // bad idea that shouldn't have been implemented; very counterintuitive + case Qt::Key_Return: + case Qt::Key_Enter: + /* + ignore the key, so that the dialog get it, but still select + the current row/col + */ + e->ignore(); + // fallthrough intended +#endif + case Qt::Key_Space: + setSelected(curRow, curCol); + break; + default: // If not an interesting key, + e->ignore(); // we don't accept the event + return; + } + +} + +//////////// QWellArray END + +static bool initrgb = false; +static QRgb stdrgb[6*8]; +static QRgb cusrgb[2*8]; +static bool customSet = false; + + +static void initRGB() +{ + if (initrgb) + return; + initrgb = true; + int i = 0; + for (int g = 0; g < 4; g++) + for (int r = 0; r < 4; r++) + for (int b = 0; b < 3; b++) + stdrgb[i++] = qRgb(r * 255 / 3, g * 255 / 3, b * 255 / 2); + + for (i = 0; i < 2*8; i++) + cusrgb[i] = 0xffffffff; +} + +/*! + Returns the number of custom colors supported by QColorDialog. All + color dialogs share the same custom colors. +*/ +int QColorDialog::customCount() +{ + return 2 * 8; +} + +/*! + \since 4.5 + + Returns the custom color at the given \a index as a QRgb value. +*/ +QRgb QColorDialog::customColor(int index) +{ + if (uint(index) >= uint(customCount())) + return qRgb(255, 255, 255); + initRGB(); + return cusrgb[index]; +} + +/*! + Sets the custom color at \a index to the QRgb \a color value. + + \note This function does not apply to the Native Color Dialog on the Mac + OS X platform. If you still require this function, use the + QColorDialog::DontUseNativeDialog option. +*/ +void QColorDialog::setCustomColor(int index, QRgb color) +{ + if (uint(index) >= uint(customCount())) + return; + initRGB(); + customSet = true; + cusrgb[index] = color; +} + +/*! + Sets the standard color at \a index to the QRgb \a color value. + + \note This function does not apply to the Native Color Dialog on the Mac + OS X platform. If you still require this function, use the + QColorDialog::DontUseNativeDialog option. +*/ + +void QColorDialog::setStandardColor(int index, QRgb color) +{ + if (uint(index) >= uint(6 * 8)) + return; + initRGB(); + stdrgb[index] = color; +} + +static inline void rgb2hsv(QRgb rgb, int &h, int &s, int &v) +{ + QColor c; + c.setRgb(rgb); + c.getHsv(&h, &s, &v); +} + +class QColorWell : public QWellArray +{ +public: + QColorWell(QWidget *parent, int r, int c, QRgb *vals) + :QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1) + { setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); } + +protected: + void paintCellContents(QPainter *, int row, int col, const QRect&); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dragMoveEvent(QDragMoveEvent *e); + void dropEvent(QDropEvent *e); +#endif + +private: + QRgb *values; + bool mousePressed; + QPoint pressPos; + QPoint oldCurrent; + +}; + +void QColorWell::paintCellContents(QPainter *p, int row, int col, const QRect &r) +{ + int i = row + col*numRows(); + p->fillRect(r, QColor(values[i])); +} + +void QColorWell::mousePressEvent(QMouseEvent *e) +{ + oldCurrent = QPoint(selectedRow(), selectedColumn()); + QWellArray::mousePressEvent(e); + mousePressed = true; + pressPos = e->pos(); +} + +void QColorWell::mouseMoveEvent(QMouseEvent *e) +{ + QWellArray::mouseMoveEvent(e); +#ifndef QT_NO_DRAGANDDROP + if (!mousePressed) + return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { + setCurrent(oldCurrent.x(), oldCurrent.y()); + int i = rowAt(pressPos.y()) + columnAt(pressPos.x()) * numRows(); + QColor col(values[i]); + QMimeData *mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(cellWidth(), cellHeight()); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->start(); + } +#endif +} + +#ifndef QT_NO_DRAGANDDROP +void QColorWell::dragEnterEvent(QDragEnterEvent *e) +{ + if (qvariant_cast(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void QColorWell::dragLeaveEvent(QDragLeaveEvent *) +{ + if (hasFocus()) + parentWidget()->setFocus(); +} + +void QColorWell::dragMoveEvent(QDragMoveEvent *e) +{ + if (qvariant_cast(e->mimeData()->colorData()).isValid()) { + setCurrent(rowAt(e->pos().y()), columnAt(e->pos().x())); + e->accept(); + } else { + e->ignore(); + } +} + +void QColorWell::dropEvent(QDropEvent *e) +{ + QColor col = qvariant_cast(e->mimeData()->colorData()); + if (col.isValid()) { + int i = rowAt(e->pos().y()) + columnAt(e->pos().x()) * numRows(); + values[i] = col.rgb(); + update(); + e->accept(); + } else { + e->ignore(); + } +} + +#endif // QT_NO_DRAGANDDROP + +void QColorWell::mouseReleaseEvent(QMouseEvent *e) +{ + if (!mousePressed) + return; + QWellArray::mouseReleaseEvent(e); + mousePressed = false; +} + +class QColorPicker : public QFrame +{ + Q_OBJECT +public: + QColorPicker(QWidget* parent); + ~QColorPicker(); + +public slots: + void setCol(int h, int s); + +signals: + void newCol(int h, int s); + +protected: + QSize sizeHint() const; + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void resizeEvent(QResizeEvent *); + +private: + int hue; + int sat; + + QPoint colPt(); + int huePt(const QPoint &pt); + int satPt(const QPoint &pt); + void setCol(const QPoint &pt); + + QPixmap pix; +}; + +static int pWidth = 220; +static int pHeight = 200; + +class QColorLuminancePicker : public QWidget +{ + Q_OBJECT +public: + QColorLuminancePicker(QWidget* parent=0); + ~QColorLuminancePicker(); + +public slots: + void setCol(int h, int s, int v); + void setCol(int h, int s); + +signals: + void newHsv(int h, int s, int v); + +protected: + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + +private: + enum { foff = 3, coff = 4 }; //frame and contents offset + int val; + int hue; + int sat; + + int y2val(int y); + int val2y(int val); + void setVal(int v); + + QPixmap *pix; +}; + + +int QColorLuminancePicker::y2val(int y) +{ + int d = height() - 2*coff - 1; + return 255 - (y - coff)*255/d; +} + +int QColorLuminancePicker::val2y(int v) +{ + int d = height() - 2*coff - 1; + return coff + (255-v)*d/255; +} + +QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) + :QWidget(parent) +{ + hue = 100; val = 100; sat = 100; + pix = 0; + // setAttribute(WA_NoErase, true); +} + +QColorLuminancePicker::~QColorLuminancePicker() +{ + delete pix; +} + +void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) +{ + setVal(y2val(m->y())); +} +void QColorLuminancePicker::mousePressEvent(QMouseEvent *m) +{ + setVal(y2val(m->y())); +} + +void QColorLuminancePicker::setVal(int v) +{ + if (val == v) + return; + val = qMax(0, qMin(v,255)); + delete pix; pix=0; + repaint(); + emit newHsv(hue, sat, val); +} + +//receives from a hue,sat chooser and relays. +void QColorLuminancePicker::setCol(int h, int s) +{ + setCol(h, s, val); + emit newHsv(h, s, val); +} + +void QColorLuminancePicker::paintEvent(QPaintEvent *) +{ + int w = width() - 5; + + QRect r(0, foff, w, height() - 2*foff); + int wi = r.width() - 2; + int hi = r.height() - 2; + if (!pix || pix->height() != hi || pix->width() != wi) { + delete pix; + QImage img(wi, hi, QImage::Format_RGB32); + int y; + uint *pixel = (uint *) img.scanLine(0); + for (y = 0; y < hi; y++) { + const uint *end = pixel + wi; + while (pixel < end) { + QColor c; + c.setHsv(hue, sat, y2val(y+coff)); + *pixel = c.rgb(); + ++pixel; + } + } + pix = new QPixmap(QPixmap::fromImage(img)); + } + QPainter p(this); + p.drawPixmap(1, coff, *pix); + const QPalette &g = palette(); + qDrawShadePanel(&p, r, g, true); + p.setPen(g.foreground().color()); + p.setBrush(g.foreground()); + QPolygon a; + int y = val2y(val); + a.setPoints(3, w, y, w+5, y+5, w+5, y-5); + p.eraseRect(w, 0, 5, height()); + p.drawPolygon(a); +} + +void QColorLuminancePicker::setCol(int h, int s , int v) +{ + val = v; + hue = h; + sat = s; + delete pix; pix=0; + repaint(); +} + +QPoint QColorPicker::colPt() +{ + QRect r = contentsRect(); + return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255); +} + +int QColorPicker::huePt(const QPoint &pt) +{ + QRect r = contentsRect(); + return 360 - pt.x() * 360 / (r.width() - 1); +} + +int QColorPicker::satPt(const QPoint &pt) +{ + QRect r = contentsRect(); + return 255 - pt.y() * 255 / (r.height() - 1); +} + +void QColorPicker::setCol(const QPoint &pt) +{ + setCol(huePt(pt), satPt(pt)); +} + +QColorPicker::QColorPicker(QWidget* parent) + : QFrame(parent) +{ + hue = 0; sat = 0; + setCol(150, 255); + + setAttribute(Qt::WA_NoSystemBackground); + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) ); +} + +QColorPicker::~QColorPicker() +{ +} + +QSize QColorPicker::sizeHint() const +{ + return QSize(pWidth + 2*frameWidth(), pHeight + 2*frameWidth()); +} + +void QColorPicker::setCol(int h, int s) +{ + int nhue = qMin(qMax(0,h), 359); + int nsat = qMin(qMax(0,s), 255); + if (nhue == hue && nsat == sat) + return; + + QRect r(colPt(), QSize(20,20)); + hue = nhue; sat = nsat; + r = r.united(QRect(colPt(), QSize(20,20))); + r.translate(contentsRect().x()-9, contentsRect().y()-9); + // update(r); + repaint(r); +} + +void QColorPicker::mouseMoveEvent(QMouseEvent *m) +{ + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); +} + +void QColorPicker::mousePressEvent(QMouseEvent *m) +{ + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); +} + +void QColorPicker::paintEvent(QPaintEvent* ) +{ + QPainter p(this); + drawFrame(&p); + QRect r = contentsRect(); + + p.drawPixmap(r.topLeft(), pix); + QPoint pt = colPt() + r.topLeft(); + p.setPen(Qt::black); + + p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black); + p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black); + +} + +void QColorPicker::resizeEvent(QResizeEvent *ev) +{ + QFrame::resizeEvent(ev); + + int w = width() - frameWidth() * 2; + int h = height() - frameWidth() * 2; + QImage img(w, h, QImage::Format_RGB32); + int x, y; + uint *pixel = (uint *) img.scanLine(0); + for (y = 0; y < h; y++) { + const uint *end = pixel + w; + x = 0; + while (pixel < end) { + QPoint p(x, y); + QColor c; + c.setHsv(huePt(p), satPt(p), 200); + *pixel = c.rgb(); + ++pixel; + ++x; + } + } + pix = QPixmap::fromImage(img); +} + + +class QColSpinBox : public QSpinBox +{ +public: + QColSpinBox(QWidget *parent) + : QSpinBox(parent) { setRange(0, 255); } + void setValue(int i) { + bool block = signalsBlocked(); + blockSignals(true); + QSpinBox::setValue(i); + blockSignals(block); + } +}; + +class QColorShowLabel; + +class QColorShower : public QWidget +{ + Q_OBJECT +public: + QColorShower(QColorDialog *parent); + + //things that don't emit signals + void setHsv(int h, int s, int v); + + int currentAlpha() const + { return (colorDialog->options() & QColorDialog::ShowAlphaChannel) ? alphaEd->value() : 255; } + void setCurrentAlpha(int a) { alphaEd->setValue(a); rgbEd(); } + void showAlpha(bool b); + bool isAlphaVisible() const; + + QRgb currentColor() const { return curCol; } + QColor currentQColor() const { return curQColor; } + void retranslateStrings(); + void updateQColor(); + +public slots: + void setRgb(QRgb rgb); + +signals: + void newCol(QRgb rgb); + void currentColorChanged(const QColor &color); + +private slots: + void rgbEd(); + void hsvEd(); +private: + void showCurrentColor(); + int hue, sat, val; + QRgb curCol; + QColor curQColor; + QLabel *lblHue; + QLabel *lblSat; + QLabel *lblVal; + QLabel *lblRed; + QLabel *lblGreen; + QLabel *lblBlue; + QColSpinBox *hEd; + QColSpinBox *sEd; + QColSpinBox *vEd; + QColSpinBox *rEd; + QColSpinBox *gEd; + QColSpinBox *bEd; + QColSpinBox *alphaEd; + QLabel *alphaLab; + QColorShowLabel *lab; + bool rgbOriginal; + QColorDialog *colorDialog; + + friend class QColorDialog; + friend class QColorDialogPrivate; +}; + +class QColorShowLabel : public QFrame +{ + Q_OBJECT + +public: + QColorShowLabel(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::Panel|QFrame::Sunken); + setAcceptDrops(true); + mousePressed = false; + } + void setColor(QColor c) { col = c; } + +signals: + void colorDropped(QRgb); + +protected: + void paintEvent(QPaintEvent *); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *e); +#endif + +private: + QColor col; + bool mousePressed; + QPoint pressPos; +}; + +void QColorShowLabel::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + drawFrame(&p); + p.fillRect(contentsRect()&e->rect(), col); +} + +void QColorShower::showAlpha(bool b) +{ + alphaLab->setVisible(b); + alphaEd->setVisible(b); +} + +inline bool QColorShower::isAlphaVisible() const +{ + return alphaLab->isVisible(); +} + +void QColorShowLabel::mousePressEvent(QMouseEvent *e) +{ + mousePressed = true; + pressPos = e->pos(); +} + +void QColorShowLabel::mouseMoveEvent(QMouseEvent *e) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(e); +#else + if (!mousePressed) + return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) { + QMimeData *mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(30, 20); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->start(); + } +#endif +} + +#ifndef QT_NO_DRAGANDDROP +void QColorShowLabel::dragEnterEvent(QDragEnterEvent *e) +{ + if (qvariant_cast(e->mimeData()->colorData()).isValid()) + e->accept(); + else + e->ignore(); +} + +void QColorShowLabel::dragLeaveEvent(QDragLeaveEvent *) +{ +} + +void QColorShowLabel::dropEvent(QDropEvent *e) +{ + QColor color = qvariant_cast(e->mimeData()->colorData()); + if (color.isValid()) { + col = color; + repaint(); + emit colorDropped(col.rgb()); + e->accept(); + } else { + e->ignore(); + } +} +#endif // QT_NO_DRAGANDDROP + +void QColorShowLabel::mouseReleaseEvent(QMouseEvent *) +{ + if (!mousePressed) + return; + mousePressed = false; +} + +QColorShower::QColorShower(QColorDialog *parent) + : QWidget(parent) +{ + colorDialog = parent; + + curCol = qRgb(255, 255, 255); + curQColor = Qt::white; + + QGridLayout *gl = new QGridLayout(this); + gl->setMargin(gl->spacing()); + lab = new QColorShowLabel(this); + +#ifdef QT_SMALL_COLORDIALOG +# ifdef Q_WS_S60 + const bool nonTouchUI = !S60->hasTouchscreen; +# elif defined Q_WS_MAEMO_5 + const bool nonTouchUI = false; +# endif +#endif + +#ifndef Q_WS_WINCE +#ifdef QT_SMALL_COLORDIALOG + lab->setMinimumHeight(60); +#endif + lab->setMinimumWidth(60); +#else + lab->setMinimumWidth(20); +#endif + +// In S60, due to small screen and different screen layouts need to re-arrange the widgets. +// For QVGA screens only the comboboxes and color label are visible. +// For nHD screens only color and luminence pickers and color label are visible. +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lab, 0, 0, -1, 1); +#else + if (nonTouchUI) + gl->addWidget(lab, 0, 0, 1, -1); + else + gl->addWidget(lab, 0, 0, -1, 1); +#endif + connect(lab, SIGNAL(colorDropped(QRgb)), this, SIGNAL(newCol(QRgb))); + connect(lab, SIGNAL(colorDropped(QRgb)), this, SLOT(setRgb(QRgb))); + + hEd = new QColSpinBox(this); + hEd->setRange(0, 359); + lblHue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblHue->setBuddy(hEd); +#endif + lblHue->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblHue, 0, 1); + gl->addWidget(hEd, 0, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblHue, 1, 0); + gl->addWidget(hEd, 2, 0); + } else { + lblHue->hide(); + hEd->hide(); + } +#endif + + sEd = new QColSpinBox(this); + lblSat = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblSat->setBuddy(sEd); +#endif + lblSat->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 1, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 2, 1); + } else { + lblSat->hide(); + sEd->hide(); + } +#endif + + vEd = new QColSpinBox(this); + lblVal = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblVal->setBuddy(vEd); +#endif + lblVal->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblVal, 2, 1); + gl->addWidget(vEd, 2, 2); +#else + if (nonTouchUI) { + gl->addWidget(lblVal, 1, 2); + gl->addWidget(vEd, 2, 2); + } else { + lblVal->hide(); + vEd->hide(); + } +#endif + + rEd = new QColSpinBox(this); + lblRed = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblRed->setBuddy(rEd); +#endif + lblRed->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblRed, 0, 3); + gl->addWidget(rEd, 0, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblRed, 3, 0); + gl->addWidget(rEd, 4, 0); + } else { + lblRed->hide(); + rEd->hide(); + } +#endif + + gEd = new QColSpinBox(this); + lblGreen = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblGreen->setBuddy(gEd); +#endif + lblGreen->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblGreen, 1, 3); + gl->addWidget(gEd, 1, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblGreen, 3, 1); + gl->addWidget(gEd, 4, 1); + } else { + lblGreen->hide(); + gEd->hide(); + } +#endif + + bEd = new QColSpinBox(this); + lblBlue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblBlue->setBuddy(bEd); +#endif + lblBlue->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblBlue, 2, 3); + gl->addWidget(bEd, 2, 4); +#else + if (nonTouchUI) { + gl->addWidget(lblBlue, 3, 2); + gl->addWidget(bEd, 4, 2); + } else { + lblBlue->hide(); + bEd->hide(); + } +#endif + + alphaEd = new QColSpinBox(this); + alphaLab = new QLabel(this); +#ifndef QT_NO_SHORTCUT + alphaLab->setBuddy(alphaEd); +#endif + alphaLab->setAlignment(Qt::AlignRight|Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(alphaLab, 3, 1, 1, 3); + gl->addWidget(alphaEd, 3, 4); +#else + if (nonTouchUI) { + gl->addWidget(alphaLab, 1, 3, 3, 1); + gl->addWidget(alphaEd, 4, 3); + } else { + alphaLab->hide(); + alphaEd->hide(); + } +#endif + alphaEd->hide(); + alphaLab->hide(); + + connect(hEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(sEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(vEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + + connect(rEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(gEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(bEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(alphaEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + + retranslateStrings(); +} + +inline QRgb QColorDialogPrivate::currentColor() const { return cs->currentColor(); } +inline int QColorDialogPrivate::currentAlpha() const { return cs->currentAlpha(); } +inline void QColorDialogPrivate::setCurrentAlpha(int a) { cs->setCurrentAlpha(a); } +inline void QColorDialogPrivate::showAlpha(bool b) { cs->showAlpha(b); } +inline bool QColorDialogPrivate::isAlphaVisible() const { return cs->isAlphaVisible(); } + +QColor QColorDialogPrivate::currentQColor() const +{ + return cs->currentQColor(); +} + +void QColorShower::showCurrentColor() +{ + lab->setColor(currentColor()); + lab->repaint(); +} + +void QColorShower::rgbEd() +{ + rgbOriginal = true; + curCol = qRgba(rEd->value(), gEd->value(), bEd->value(), currentAlpha()); + + rgb2hsv(currentColor(), hue, sat, val); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); +} + +void QColorShower::hsvEd() +{ + rgbOriginal = false; + hue = hEd->value(); + sat = sEd->value(); + val = vEd->value(); + + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); +} + +void QColorShower::setRgb(QRgb rgb) +{ + rgbOriginal = true; + curCol = rgb; + + rgb2hsv(currentColor(), hue, sat, val); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + updateQColor(); +} + +void QColorShower::setHsv(int h, int s, int v) +{ + if (h < -1 || (uint)s > 255 || (uint)v > 255) + return; + + rgbOriginal = false; + hue = h; val = v; sat = s; + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + + showCurrentColor(); + updateQColor(); +} + +void QColorShower::retranslateStrings() +{ + lblHue->setText(QColorDialog::tr("Hu&e:")); + lblSat->setText(QColorDialog::tr("&Sat:")); + lblVal->setText(QColorDialog::tr("&Val:")); + lblRed->setText(QColorDialog::tr("&Red:")); + lblGreen->setText(QColorDialog::tr("&Green:")); + lblBlue->setText(QColorDialog::tr("Bl&ue:")); + alphaLab->setText(QColorDialog::tr("A&lpha channel:")); +} + +void QColorShower::updateQColor() +{ + QColor oldQColor(curQColor); + curQColor.setRgba(qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), currentAlpha())); + if (curQColor != oldQColor) + emit currentColorChanged(curQColor); +} + +//sets all widgets to display h,s,v +void QColorDialogPrivate::_q_newHsv(int h, int s, int v) +{ + cs->setHsv(h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); +} + +//sets all widgets to display rgb +void QColorDialogPrivate::setCurrentColor(QRgb rgb) +{ + cs->setRgb(rgb); + _q_newColorTypedIn(rgb); +} + +// hack; doesn't keep curCol in sync, so use with care +void QColorDialogPrivate::setCurrentQColor(const QColor &color) +{ + Q_Q(QColorDialog); + if (cs->curQColor != color) { + cs->curQColor = color; + emit q->currentColorChanged(color); + } +} + +bool QColorDialogPrivate::selectColor(const QColor &col) +{ + QRgb color = col.rgb(); + int i = 0, j = 0; + // Check standard colors + if (standard) { + for (i = 0; i < 6; i++) { + for (j = 0; j < 8; j++) { + if (color == stdrgb[i + j*6]) { + _q_newStandard(i, j); + standard->setCurrent(i, j); + standard->setSelected(i, j); + standard->setFocus(); + return true; + } + } + } + } + // Check custom colors + if (custom) { + for (i = 0; i < 2; i++) { + for (j = 0; j < 8; j++) { + if (color == cusrgb[i + j*2]) { + _q_newCustom(i, j); + custom->setCurrent(i, j); + custom->setSelected(i, j); + custom->setFocus(); + return true; + } + } + } + } + return false; +} + +//sets all widgets except cs to display rgb +void QColorDialogPrivate::_q_newColorTypedIn(QRgb rgb) +{ + int h, s, v; + rgb2hsv(rgb, h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); +} + +void QColorDialogPrivate::_q_newCustom(int r, int c) +{ + int i = r+2*c; + setCurrentColor(cusrgb[i]); + nextCust = i; + if (standard) + standard->setSelected(-1,-1); +} + +void QColorDialogPrivate::_q_newStandard(int r, int c) +{ + setCurrentColor(stdrgb[r+c*6]); + if (custom) + custom->setSelected(-1,-1); +} + +void QColorDialogPrivate::init(const QColor &initial) +{ + Q_Q(QColorDialog); + + q->setSizeGripEnabled(false); + q->setWindowTitle(QColorDialog::tr("Select Color")); + + nativeDialogInUse = false; + + nextCust = 0; + QVBoxLayout *mainLay = new QVBoxLayout(q); + // there's nothing in this dialog that benefits from sizing up + mainLay->setSizeConstraint(QLayout::SetFixedSize); + + QHBoxLayout *topLay = new QHBoxLayout(); + mainLay->addLayout(topLay); + + leftLay = 0; + +#if defined(Q_WS_WINCE) || defined(QT_SMALL_COLORDIALOG) + smallDisplay = true; + const int lumSpace = 20; +#else + // small displays (e.g. PDAs) cannot fit the full color dialog, + // so just use the color picker. + smallDisplay = (QApplication::desktop()->width() < 480 || QApplication::desktop()->height() < 350); + const int lumSpace = topLay->spacing() / 2; +#endif + + if (!smallDisplay) { + leftLay = new QVBoxLayout; + topLay->addLayout(leftLay); + } + + initRGB(); + +#ifndef QT_NO_SETTINGS + if (!customSet) { + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + for (int i = 0; i < 2*8; ++i) { + QVariant v = settings.value(QLatin1String("Qt/customColors/") + QString::number(i)); + if (v.isValid()) { + QRgb rgb = v.toUInt(); + cusrgb[i] = rgb; + } + } + } +#endif + +#if defined(QT_SMALL_COLORDIALOG) +# if defined(Q_WS_S60) + const bool nonTouchUI = !S60->hasTouchscreen; +# elif defined(Q_WS_MAEMO_5) + const bool nonTouchUI = false; +# endif +#endif + + if (!smallDisplay) { + standard = new QColorWell(q, 6, 8, stdrgb); + lblBasicColors = new QLabel(q); +#ifndef QT_NO_SHORTCUT + lblBasicColors->setBuddy(standard); +#endif + q->connect(standard, SIGNAL(selected(int,int)), SLOT(_q_newStandard(int,int))); + leftLay->addWidget(lblBasicColors); + leftLay->addWidget(standard); + +#if !defined(Q_WS_WINCE) + leftLay->addStretch(); +#endif + + custom = new QColorWell(q, 2, 8, cusrgb); + custom->setAcceptDrops(true); + + q->connect(custom, SIGNAL(selected(int,int)), SLOT(_q_newCustom(int,int))); + lblCustomColors = new QLabel(q); +#ifndef QT_NO_SHORTCUT + lblCustomColors->setBuddy(custom); +#endif + leftLay->addWidget(lblCustomColors); + leftLay->addWidget(custom); + + addCusBt = new QPushButton(q); + QObject::connect(addCusBt, SIGNAL(clicked()), q, SLOT(_q_addCustom())); + leftLay->addWidget(addCusBt); + } else { + // better color picker size for small displays +#if defined(QT_SMALL_COLORDIALOG) + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); + pWidth = pHeight = qMin(screenSize.width(), screenSize.height()); + pHeight -= 20; + if(screenSize.height() > screenSize.width()) + pWidth -= 20; +#else + pWidth = 150; + pHeight = 100; +#endif + custom = 0; + standard = 0; + } + + QVBoxLayout *rightLay = new QVBoxLayout; + topLay->addLayout(rightLay); + + QHBoxLayout *pickLay = new QHBoxLayout; + rightLay->addLayout(pickLay); + + QVBoxLayout *cLay = new QVBoxLayout; + pickLay->addLayout(cLay); + cp = new QColorPicker(q); + + cp->setFrameStyle(QFrame::Panel + QFrame::Sunken); + +#if defined(QT_SMALL_COLORDIALOG) + if (!nonTouchUI) { + pickLay->addWidget(cp); + cLay->addSpacing(lumSpace); + } else { + cp->hide(); + } +#else + cLay->addSpacing(lumSpace); + cLay->addWidget(cp); +#endif + cLay->addSpacing(lumSpace); + + lp = new QColorLuminancePicker(q); +#if defined(QT_SMALL_COLORDIALOG) + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); + const int minDimension = qMin(screenSize.height(), screenSize.width()); + //set picker to be finger-usable + int pickerWidth = !nonTouchUI ? minDimension/9 : minDimension/12; + lp->setFixedWidth(pickerWidth); + if (!nonTouchUI) + pickLay->addWidget(lp); + else + lp->hide(); +#else + lp->setFixedWidth(20); + pickLay->addWidget(lp); +#endif + + QObject::connect(cp, SIGNAL(newCol(int,int)), lp, SLOT(setCol(int,int))); + QObject::connect(lp, SIGNAL(newHsv(int,int,int)), q, SLOT(_q_newHsv(int,int,int))); + + rightLay->addStretch(); + + cs = new QColorShower(q); + QObject::connect(cs, SIGNAL(newCol(QRgb)), q, SLOT(_q_newColorTypedIn(QRgb))); + QObject::connect(cs, SIGNAL(currentColorChanged(QColor)), + q, SIGNAL(currentColorChanged(QColor))); +#if defined(QT_SMALL_COLORDIALOG) + if (!nonTouchUI) + pWidth -= cp->size().width(); + topLay->addWidget(cs); +#else + rightLay->addWidget(cs); +#endif + + buttons = new QDialogButtonBox(q); + mainLay->addWidget(buttons); + + ok = buttons->addButton(QDialogButtonBox::Ok); + QObject::connect(ok, SIGNAL(clicked()), q, SLOT(accept())); + ok->setDefault(true); + cancel = buttons->addButton(QDialogButtonBox::Cancel); + QObject::connect(cancel, SIGNAL(clicked()), q, SLOT(reject())); + + retranslateStrings(); + +#ifdef Q_WS_MAC + delegate = 0; +#endif + + q->setCurrentColor(initial); +} + +void QColorDialogPrivate::_q_addCustom() +{ + cusrgb[nextCust] = cs->currentColor(); + if (custom) + custom->update(); + nextCust = (nextCust+1) % 16; +} + +void QColorDialogPrivate::retranslateStrings() +{ + if (!smallDisplay) { + lblBasicColors->setText(QColorDialog::tr("&Basic colors")); + lblCustomColors->setText(QColorDialog::tr("&Custom colors")); + addCusBt->setText(QColorDialog::tr("&Add to Custom Colors")); + } + + cs->retranslateStrings(); +} + +static const Qt::WindowFlags DefaultWindowFlags = + Qt::Dialog | Qt::WindowTitleHint | Qt::MSWindowsFixedSizeDialogHint + | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + +/*! + \class QColorDialog + \brief The QColorDialog class provides a dialog widget for specifying colors. + + \ingroup standard-dialogs + + The color dialog's function is to allow users to choose colors. + For example, you might use this in a drawing program to allow the + user to set the brush color. + + The static functions provide modal color dialogs. + \omit + If you require a modeless dialog, use the QColorDialog constructor. + \endomit + + The static getColor() function shows the dialog, and allows the user to + specify a color. This function can also be used to let users choose a + color with a level of transparency: pass the ShowAlphaChannel option as + an additional argument. + + The user can store customCount() different custom colors. The + custom colors are shared by all color dialogs, and remembered + during the execution of the program. Use setCustomColor() to set + the custom colors, and use customColor() to get them. + + Additional widgets that allow users to pick colors are available + as \l{Qt Solutions}. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QColorDialog as well as other built-in Qt dialogs. + + \image plastique-colordialog.png A color dialog in the Plastique widget style. + + \sa QColor, QFileDialog, QPrintDialog, QFontDialog, {Standard Dialogs Example} +*/ + +/*! + \since 4.5 + + Constructs a color dialog with the given \a parent. +*/ +QColorDialog::QColorDialog(QWidget *parent) + : QDialog(*new QColorDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QColorDialog); + d->init(Qt::white); +} + +/*! + \since 4.5 + + Constructs a color dialog with the given \a parent and specified + \a initial color. +*/ +QColorDialog::QColorDialog(const QColor &initial, QWidget *parent) + : QDialog(*new QColorDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QColorDialog); + d->init(initial); +} + +/*! + \property QColorDialog::currentColor + \brief the currently selected color in the dialog +*/ + +void QColorDialog::setCurrentColor(const QColor &color) +{ + Q_D(QColorDialog); + d->setCurrentColor(color.rgb()); + d->selectColor(color); + d->setCurrentAlpha(color.alpha()); + +#ifdef Q_WS_MAC + d->setCurrentQColor(color); + d->setCocoaPanelColor(color); +#endif + if (d->nativeDialogInUse) + qt_guiPlatformPlugin()->colorDialogSetCurrentColor(this, color); +} + +QColor QColorDialog::currentColor() const +{ + Q_D(const QColorDialog); + return d->currentQColor(); +} + + +/*! + Returns the color that the user selected by clicking the \gui{OK} + or equivalent button. + + \note This color is not always the same as the color held by the + \l currentColor property since the user can choose different colors + before finally selecting the one to use. +*/ +QColor QColorDialog::selectedColor() const +{ + Q_D(const QColorDialog); + return d->selectedQColor; +} + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QColorDialog::setOption(ColorDialogOption option, bool on) +{ + Q_D(QColorDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + \since 4.5 + + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QColorDialog::testOption(ColorDialogOption option) const +{ + Q_D(const QColorDialog); + return (d->opts & option) != 0; +} + +/*! + \property QColorDialog::options + \brief the various options that affect the look and feel of the dialog + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QColorDialog::setOptions(ColorDialogOptions options) +{ + Q_D(QColorDialog); + + ColorDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->buttons->setVisible(!(options & NoButtons)); + d->showAlpha(options & ShowAlphaChannel); +} + +QColorDialog::ColorDialogOptions QColorDialog::options() const +{ + Q_D(const QColorDialog); + return d->opts; +} + +/*! + \enum QColorDialog::ColorDialogOption + + \since 4.5 + + This enum specifies various options that affect the look and feel + of a color dialog. + + \value ShowAlphaChannel Allow the user to select the alpha component of a color. + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value DontUseNativeDialog Use Qt's standard color dialog on the Mac instead of Apple's + native color panel. + + \sa options, setOption(), testOption(), windowModality() +*/ + +/*! + \fn void QColorDialog::currentColorChanged(const QColor &color) + + This signal is emitted whenever the current color changes in the dialog. + The current color is specified by \a color. + + \sa color, colorSelected() +*/ + +#ifdef Q_WS_MAC +// can only have one Cocoa color panel active +bool QColorDialogPrivate::sharedColorPanelAvailable = true; +#endif + +/*! + \fn void QColorDialog::colorSelected(const QColor &color); + + This signal is emitted just after the user has clicked \gui{OK} to + select a color to use. The chosen color is specified by \a color. + + \sa color, currentColorChanged() +*/ + +/*! + Changes the visibility of the dialog. If \a visible is true, the dialog + is shown; otherwise, it is hidden. +*/ +void QColorDialog::setVisible(bool visible) +{ + Q_D(QColorDialog); + + if (visible){ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + + if (visible) + d->selectedQColor = QColor(); + +#if defined(Q_WS_MAC) + if (visible) { + if (d->delegate || (QColorDialogPrivate::sharedColorPanelAvailable && + !(testAttribute(Qt::WA_DontShowOnScreen) || (d->opts & DontUseNativeDialog)))){ + d->openCocoaColorPanel(currentColor(), parentWidget(), windowTitle(), options()); + QColorDialogPrivate::sharedColorPanelAvailable = false; + setAttribute(Qt::WA_DontShowOnScreen); + } + setWindowFlags(windowModality() == Qt::WindowModal ? Qt::Sheet : DefaultWindowFlags); + } else { + if (d->delegate) { + d->closeCocoaColorPanel(); + setAttribute(Qt::WA_DontShowOnScreen, false); + } + } +#else + + if (!(d->opts & DontUseNativeDialog) && qt_guiPlatformPlugin()->colorDialogSetVisible(this, visible)) { + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); + } +#endif + + QDialog::setVisible(visible); +} + +/*! + \overload + \since 4.5 + + Opens the dialog and connects its colorSelected() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QColorDialog::open(QObject *receiver, const char *member) +{ + Q_D(QColorDialog); + connect(this, SIGNAL(colorSelected(QColor)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \fn QColorDialog::open() + + \since 4.5 + Shows the dialog as a \l{QDialog#Modal Dialogs}{window modal dialog}, + returning immediately. + + \sa QDialog::open() +*/ + +/* + For Symbian color dialogs +*/ +#ifdef Q_WS_S60 +extern QColor qtSymbianGetColor(const QColor &initial); +#endif +/*! + \since 4.5 + + Pops up a modal color dialog with the given window \a title (or "Select Color" if none is + specified), lets the user choose a color, and returns that color. The color is initially set + to \a initial. The dialog is a child of \a parent. It returns an invalid (see + QColor::isValid()) color if the user cancels the dialog. + + The \a options argument allows you to customize the dialog. + + On Symbian, this static function will use the native color dialog and not a QColorDialog. + On Symbian the parameters \a title and \a parent has no relevance and the + \a options parameter is only used to define if the native color dialog is + used or not. +*/ +QColor QColorDialog::getColor(const QColor &initial, QWidget *parent, const QString &title, + ColorDialogOptions options) +{ +#ifdef Q_WS_S60 + if (!(options & DontUseNativeDialog)) + return qtSymbianGetColor(initial); +#endif + QColorDialog dlg(parent); + if (!title.isEmpty()) + dlg.setWindowTitle(title); + dlg.setOptions(options); + dlg.setCurrentColor(initial); + dlg.exec(); + return dlg.selectedColor(); +} + +/*! + Pops up a modal color dialog, lets the user choose a color, and + returns that color. The color is initially set to \a initial. The + dialog is a child of \a parent. It returns an invalid (see + QColor::isValid()) color if the user cancels the dialog. + + On Symbian, this static function will use the native + color dialog and not a QColorDialog. +*/ + +QColor QColorDialog::getColor(const QColor &initial, QWidget *parent) +{ +#ifdef Q_WS_S60 + return qtSymbianGetColor(initial); +#endif + return getColor(initial, parent, QString(), ColorDialogOptions(0)); +} + + +/*! + \obsolete + + Pops up a modal color dialog to allow the user to choose a color + and an alpha channel (transparency) value. The color+alpha is + initially set to \a initial. The dialog is a child of \a parent. + + If \a ok is non-null, \e *\a ok is set to true if the user clicked + \gui{OK}, and to false if the user clicked Cancel. + + If the user clicks Cancel, the \a initial value is returned. + + Use QColorDialog::getColor() instead, passing the + QColorDialog::ShowAlphaChannel option. +*/ + +QRgb QColorDialog::getRgba(QRgb initial, bool *ok, QWidget *parent) +{ + QColor color(getColor(QColor(initial), parent, QString(), ShowAlphaChannel)); + QRgb result = color.isValid() ? color.rgba() : initial; + if (ok) + *ok = color.isValid(); + return result; +} + +/*! + Destroys the color dialog. +*/ + +QColorDialog::~QColorDialog() +{ + Q_D(QColorDialog); +#if defined(Q_WS_MAC) + if (d->delegate) { + d->releaseCocoaColorPanelDelegate(); + QColorDialogPrivate::sharedColorPanelAvailable = true; + } +#endif + +#ifndef QT_NO_SETTINGS + if (!customSet) { + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + for (int i = 0; i < 2*8; ++i) + settings.setValue(QLatin1String("Qt/customColors/") + QString::number(i), cusrgb[i]); + } +#endif + if (d->nativeDialogInUse) + qt_guiPlatformPlugin()->colorDialogDelete(this); + +} + + +/*! + \reimp +*/ +void QColorDialog::changeEvent(QEvent *e) +{ + Q_D(QColorDialog); + if (e->type() == QEvent::LanguageChange) + d->retranslateStrings(); + QDialog::changeEvent(e); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QColorDialog::done(int result) +{ + Q_D(QColorDialog); + QDialog::done(result); + if (result == Accepted) { + d->selectedQColor = d->currentQColor(); + emit colorSelected(d->selectedQColor); + } else { + d->selectedQColor = QColor(); + } + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(colorSelected(QColor)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +QT_END_NAMESPACE + +#include "qcolordialog.moc" +#include "moc_qcolordialog.cpp" + +#endif // QT_NO_COLORDIALOG + +/*! + \fn QColor QColorDialog::getColor(const QColor &init, QWidget *parent, const char *name) + \compat +*/ + +/*! + \fn QRgb QColorDialog::getRgba(QRgb rgba, bool *ok, QWidget *parent, const char *name) + \compat +*/ diff --git a/src/gui/dialogs/qcolordialog.h b/src/gui/dialogs/qcolordialog.h new file mode 100644 index 0000000000..80a893d568 --- /dev/null +++ b/src/gui/dialogs/qcolordialog.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORDIALOG_H +#define QCOLORDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COLORDIALOG + +class QColorDialogPrivate; + +class Q_GUI_EXPORT QColorDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QColorDialog) + Q_ENUMS(ColorDialogOption) + Q_PROPERTY(QColor currentColor READ currentColor WRITE setCurrentColor + NOTIFY currentColorChanged) + Q_PROPERTY(ColorDialogOptions options READ options WRITE setOptions) + +public: + enum ColorDialogOption { + ShowAlphaChannel = 0x00000001, + NoButtons = 0x00000002, + DontUseNativeDialog = 0x00000004 + }; + + Q_DECLARE_FLAGS(ColorDialogOptions, ColorDialogOption) + + explicit QColorDialog(QWidget *parent = 0); + explicit QColorDialog(const QColor &initial, QWidget *parent = 0); + ~QColorDialog(); + + void setCurrentColor(const QColor &color); + QColor currentColor() const; + + QColor selectedColor() const; + + void setOption(ColorDialogOption option, bool on = true); + bool testOption(ColorDialogOption option) const; + void setOptions(ColorDialogOptions options); + ColorDialogOptions options() const; + +#ifdef Q_NO_USING_KEYWORD + void open() { QDialog::open(); } +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + void setVisible(bool visible); + + // ### Qt 5: merge overloads with title = QString() + static QColor getColor(const QColor &initial, QWidget *parent, const QString &title, + ColorDialogOptions options = 0); + static QColor getColor(const QColor &initial = Qt::white, QWidget *parent = 0); + + // obsolete + static QRgb getRgba(QRgb rgba = 0xffffffff, bool *ok = 0, QWidget *parent = 0); + + // ### Qt 5: use QColor in signatures + static int customCount(); + static QRgb customColor(int index); + static void setCustomColor(int index, QRgb color); + static void setStandardColor(int index, QRgb color); + +#ifdef QT3_SUPPORT + static QColor getColor(const QColor &init, QWidget *parent, const char *name) + { Q_UNUSED(name); return getColor(init, parent); } + static QRgb getRgba(QRgb rgba, bool *ok, QWidget *parent, const char *name) + { Q_UNUSED(name); return getRgba(rgba, ok, parent); } +#endif + +Q_SIGNALS: + void currentColorChanged(const QColor &color); + void colorSelected(const QColor &color); + +protected: + void changeEvent(QEvent *event); + void done(int result); + +private: + Q_DISABLE_COPY(QColorDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_addCustom()) + Q_PRIVATE_SLOT(d_func(), void _q_newHsv(int h, int s, int v)) + Q_PRIVATE_SLOT(d_func(), void _q_newColorTypedIn(QRgb rgb)) + Q_PRIVATE_SLOT(d_func(), void _q_newCustom(int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_newStandard(int, int)) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif + + friend class QColorShower; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QColorDialog::ColorDialogOptions) + +#endif // QT_NO_COLORDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLORDIALOG_H diff --git a/src/gui/dialogs/qcolordialog_mac.mm b/src/gui/dialogs/qcolordialog_mac.mm new file mode 100644 index 0000000000..ee9b19ad99 --- /dev/null +++ b/src/gui/dialogs/qcolordialog_mac.mm @@ -0,0 +1,500 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" +#if !defined(QT_NO_COLORDIALOG) && defined(Q_WS_MAC) +#include +#include +#include +#include +#include +#include +#include +#import +#import + +#if !CGFLOAT_DEFINED +typedef float CGFloat; // Should only not be defined on 32-bit platforms +#endif + + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSWindowDelegate +- (void)windowDidResize:(NSNotification *)notification; +- (BOOL)windowShouldClose:(id)window; +@end +#endif + +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate); + +@interface QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) : NSObject { + NSColorPanel *mColorPanel; + NSView *mStolenContentView; + NSButton *mOkButton; + NSButton *mCancelButton; + QColorDialogPrivate *mPriv; + QColor *mQtColor; + CGFloat mMinWidth; // currently unused + CGFloat mExtraHeight; // currently unused + BOOL mHackedPanel; + NSInteger mResultCode; + BOOL mDialogIsExecuting; + BOOL mResultSet; +} +- (id)initWithColorPanel:(NSColorPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QColorDialogPrivate *)priv; +- (void)colorChanged:(NSNotification *)notification; +- (void)relayout; +- (void)onOkClicked; +- (void)onCancelClicked; +- (void)updateQtColor; +- (NSColorPanel *)colorPanel; +- (QColor)qtColor; +- (void)finishOffWithCode:(NSInteger)result; +- (void)showColorPanel; +- (void)exec; +- (void)setResultSet:(BOOL)result; +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) +- (id)initWithColorPanel:(NSColorPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QColorDialogPrivate *)priv +{ + self = [super init]; + + mColorPanel = panel; + mStolenContentView = stolenContentView; + mOkButton = okButton; + mCancelButton = cancelButton; + mPriv = priv; + mMinWidth = 0.0; + mExtraHeight = 0.0; + mHackedPanel = (okButton != 0); + mResultCode = NSCancelButton; + mDialogIsExecuting = false; + mResultSet = false; + + if (mHackedPanel) { + [self relayout]; + + [okButton setAction:@selector(onOkClicked)]; + [okButton setTarget:self]; + + [cancelButton setAction:@selector(onCancelClicked)]; + [cancelButton setTarget:self]; + } + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(colorChanged:) + name:NSColorPanelColorDidChangeNotification + object:mColorPanel]; + + mQtColor = new QColor(); + return self; +} + +- (void)dealloc +{ + QMacCocoaAutoReleasePool pool; + if (mHackedPanel) { + NSView *ourContentView = [mColorPanel contentView]; + + // return stolen stuff to its rightful owner + [mStolenContentView removeFromSuperview]; + [mColorPanel setContentView:mStolenContentView]; + + [mOkButton release]; + [mCancelButton release]; + [ourContentView release]; + } + [mColorPanel setDelegate:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + delete mQtColor; + [super dealloc]; +} + +- (void)setResultSet:(BOOL)result +{ + mResultSet = result; +} + +- (BOOL)windowShouldClose:(id)window +{ + Q_UNUSED(window); + if (!mHackedPanel) + [self updateQtColor]; + if (mDialogIsExecuting) { + [self finishOffWithCode:NSCancelButton]; + } else { + mResultSet = true; + mPriv->colorDialog()->reject(); + } + return true; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (mHackedPanel) + [self relayout]; +} + +- (void)colorChanged:(NSNotification *)notification +{ + Q_UNUSED(notification); + [self updateQtColor]; +} + +- (void)relayout +{ + Q_ASSERT(mHackedPanel); + + NSRect rect = [[mStolenContentView superview] frame]; + + // should a priori be kept in sync with qfontdialog_mac.mm + const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon + const CGFloat ButtonMinHeight = 32.0; + const CGFloat ButtonSpacing = 0.0; + const CGFloat ButtonTopMargin = 0.0; + const CGFloat ButtonBottomMargin = 7.0; + const CGFloat ButtonSideMargin = 9.0; + + [mOkButton sizeToFit]; + NSSize okSizeHint = [mOkButton frame].size; + + [mCancelButton sizeToFit]; + NSSize cancelSizeHint = [mCancelButton frame].size; + + const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth, + qMax(okSizeHint.width, cancelSizeHint.width)), + CGFloat((rect.size.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5)); + const CGFloat ButtonHeight = qMax(ButtonMinHeight, + qMax(okSizeHint.height, cancelSizeHint.height)); + + NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mOkButton setFrame:okRect]; + [mOkButton setNeedsDisplay:YES]; + + NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mCancelButton setFrame:cancelRect]; + [mCancelButton setNeedsDisplay:YES]; + + const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin; + NSRect stolenCVRect = { { 0.0, Y }, + { rect.size.width, rect.size.height - Y } }; + [mStolenContentView setFrame:stolenCVRect]; + [mStolenContentView setNeedsDisplay:YES]; + + [[mStolenContentView superview] setNeedsDisplay:YES]; + mMinWidth = 2 * ButtonSideMargin + ButtonSpacing + 2 * ButtonWidth; + mExtraHeight = Y; +} + +- (void)onOkClicked +{ + Q_ASSERT(mHackedPanel); + [[mStolenContentView window] close]; + [self updateQtColor]; + [self finishOffWithCode:NSOKButton]; +} + +- (void)onCancelClicked +{ + if (mHackedPanel) { + [[mStolenContentView window] close]; + delete mQtColor; + mQtColor = new QColor(); + [self finishOffWithCode:NSCancelButton]; + } +} + +- (void)updateQtColor +{ + delete mQtColor; + mQtColor = new QColor(); + NSColor *color = [mColorPanel color]; + NSString *colorSpaceName = [color colorSpaceName]; + if (colorSpaceName == NSDeviceCMYKColorSpace) { + CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0; + [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha]; + mQtColor->setCmykF(cyan, magenta, yellow, black, alpha); + } else if (colorSpaceName == NSCalibratedRGBColorSpace || colorSpaceName == NSDeviceRGBColorSpace) { + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } else if (colorSpaceName == NSNamedColorSpace) { + NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } else { + NSColorSpace *colorSpace = [color colorSpace]; + if ([colorSpace colorSpaceModel] == NSCMYKColorSpaceModel && [color numberOfComponents] == 5){ + CGFloat components[5]; + [color getComponents:components]; + mQtColor->setCmykF(components[0], components[1], components[2], components[3], components[4]); + } else { + NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + mQtColor->setRgbF(red, green, blue, alpha); + } + } + + mPriv->setCurrentQColor(*mQtColor); +} + +- (NSColorPanel *)colorPanel +{ + return mColorPanel; +} + +- (QColor)qtColor +{ + return *mQtColor; +} + +- (void)finishOffWithCode:(NSInteger)code +{ + mResultCode = code; + if (mDialogIsExecuting) { + // We stop the current modal event loop. The control + // will then return inside -(void)exec below. + // It's important that the modal event loop is stopped before + // we accept/reject QColorDialog, since QColorDialog has its + // own event loop that needs to be stopped last. + [NSApp stopModalWithCode:code]; + } else { + // Since we are not in a modal event loop, we can safely close + // down QColorDialog + // Calling accept() or reject() can in turn call closeCocoaColorPanel. + // This check will prevent any such recursion. + if (!mResultSet) { + mResultSet = true; + if (mResultCode == NSCancelButton) { + mPriv->colorDialog()->reject(); + } else { + mPriv->colorDialog()->accept(); + } + } + } +} + +- (void)showColorPanel +{ + mDialogIsExecuting = false; + [mColorPanel makeKeyAndOrderFront:mColorPanel]; +} + +- (void)exec +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); + QMacCocoaAutoReleasePool pool; + mDialogIsExecuting = true; + bool modalEnded = false; + while (!modalEnded) { + @try { + [NSApp runModalForWindow:mColorPanel]; + modalEnded = true; + } @catch (NSException *) { + // For some reason, NSColorPanel throws an exception when + // clicking on 'SelectedMenuItemColor' from the 'Developer' + // palette (tab three). + } + } + + QAbstractEventDispatcher::instance()->interrupt(); + if (mResultCode == NSCancelButton) + mPriv->colorDialog()->reject(); + else + mPriv->colorDialog()->accept(); +} + +@end + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptNSPanelCtor(); +extern void macStopInterceptNSPanelCtor(); +extern NSButton *macCreateButton(const char *text, NSView *superview); + +void QColorDialogPrivate::openCocoaColorPanel(const QColor &initial, + QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options) +{ + Q_UNUSED(parent); // we would use the parent if only NSColorPanel could be a sheet + QMacCocoaAutoReleasePool pool; + + if (!delegate) { + /* + The standard Cocoa color panel has no OK or Cancel button and + is created as a utility window, whereas we want something like + the Carbon color panel. We need to take the following steps: + + 1. Intercept the color panel constructor to turn off the + NSUtilityWindowMask flag. This is done by temporarily + replacing initWithContentRect:styleMask:backing:defer: + in NSPanel by our own method. + + 2. Modify the color panel so that its content view is part + of a new content view that contains it as well as two + buttons (OK and Cancel). + + 3. Lay out the original content view and the buttons when + the color panel is shown and whenever it is resized. + + 4. Clean up after ourselves. + */ + + bool hackColorPanel = !(options & QColorDialog::NoButtons); + + if (hackColorPanel) + macStartInterceptNSPanelCtor(); + NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel]; + if (hackColorPanel) + macStopInterceptNSPanelCtor(); + + [colorPanel setHidesOnDeactivate:false]; + + // set up the Cocoa color panel + [colorPanel setShowsAlpha:options & QColorDialog::ShowAlphaChannel]; + [colorPanel setTitle:(NSString*)(CFStringRef)QCFString(title)]; + + NSView *stolenContentView = 0; + NSButton *okButton = 0; + NSButton *cancelButton = 0; + + if (hackColorPanel) { + // steal the color panel's contents view + stolenContentView = [colorPanel contentView]; + [stolenContentView retain]; + [colorPanel setContentView:0]; + + // create a new content view and add the stolen one as a subview + NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect]; + [ourContentView addSubview:stolenContentView]; + + // create OK and Cancel buttons and add these as subviews + okButton = macCreateButton("&OK", ourContentView); + cancelButton = macCreateButton("Cancel", ourContentView); + + [colorPanel setContentView:ourContentView]; + [colorPanel setDefaultButtonCell:[okButton cell]]; + } + + delegate = [[QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) alloc] initWithColorPanel:colorPanel + stolenContentView:stolenContentView + okButton:okButton + cancelButton:cancelButton + priv:this]; + [colorPanel setDelegate:static_cast(delegate)]; + } + [static_cast(delegate) setResultSet:NO]; + setCocoaPanelColor(initial); + [static_cast(delegate) showColorPanel]; +} + +void QColorDialogPrivate::closeCocoaColorPanel() +{ + [static_cast(delegate) onCancelClicked]; +} + +void QColorDialogPrivate::releaseCocoaColorPanelDelegate() +{ + [static_cast(delegate) release]; +} + +void QColorDialogPrivate::mac_nativeDialogModalHelp() +{ + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most 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 (delegate){ + Q_Q(QColorDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +void QColorDialogPrivate::_q_macRunNativeAppModalPanel() +{ + [static_cast(delegate) exec]; +} + +void QColorDialogPrivate::setCocoaPanelColor(const QColor &color) +{ + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *theDelegate = static_cast(delegate); + NSColor *nsColor; + const QColor::Spec spec = color.spec(); + if (spec == QColor::Cmyk) { + nsColor = [NSColor colorWithDeviceCyan:color.cyanF() + magenta:color.magentaF() + yellow:color.yellowF() + black:color.blackF() + alpha:color.alphaF()]; + } else { + nsColor = [NSColor colorWithCalibratedRed:color.redF() + green:color.greenF() + blue:color.blueF() + alpha:color.alphaF()]; + } + [[theDelegate colorPanel] setColor:nsColor]; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qcolordialog_p.h b/src/gui/dialogs/qcolordialog_p.h new file mode 100644 index 0000000000..243e7277c5 --- /dev/null +++ b/src/gui/dialogs/qcolordialog_p.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORDIALOG_P_H +#define QCOLORDIALOG_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 +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qdialog_p.h" +#include "qcolordialog.h" + +#ifndef QT_NO_COLORDIALOG + +QT_BEGIN_NAMESPACE + +class QColorLuminancePicker; +class QColorPicker; +class QColorShower; +class QDialogButtonBox; +class QLabel; +class QVBoxLayout; +class QPushButton; +class QWellArray; + +class QColorDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QColorDialog) + +public: + void init(const QColor &initial); + QRgb currentColor() const; + QColor currentQColor() const; + void setCurrentColor(QRgb rgb); + void setCurrentQColor(const QColor &color); + bool selectColor(const QColor &color); + + int currentAlpha() const; + void setCurrentAlpha(int a); + void showAlpha(bool b); + bool isAlphaVisible() const; + void retranslateStrings(); + + void _q_addCustom(); + + void _q_newHsv(int h, int s, int v); + void _q_newColorTypedIn(QRgb rgb); + void _q_newCustom(int, int); + void _q_newStandard(int, int); + + QWellArray *custom; + QWellArray *standard; + + QDialogButtonBox *buttons; + QVBoxLayout *leftLay; + QColorPicker *cp; + QColorLuminancePicker *lp; + QColorShower *cs; + QLabel *lblBasicColors; + QLabel *lblCustomColors; + QPushButton *ok; + QPushButton *cancel; + QPushButton *addCusBt; + QColor selectedQColor; + int nextCust; + bool smallDisplay; + QColorDialog::ColorDialogOptions opts; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + bool nativeDialogInUse; + +#ifdef Q_WS_MAC + void openCocoaColorPanel(const QColor &initial, + QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options); + void closeCocoaColorPanel(); + void releaseCocoaColorPanelDelegate(); + void setCocoaPanelColor(const QColor &color); + + inline void done(int result) { q_func()->done(result); } + inline QColorDialog *colorDialog() { return q_func(); } + + void *delegate; + + static bool sharedColorPanelAvailable; + + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); +#endif +}; + +#endif // QT_NO_COLORDIALOG + +QT_END_NAMESPACE + +#endif // QCOLORDIALOG_P_H diff --git a/src/gui/dialogs/qcolordialog_symbian.cpp b/src/gui/dialogs/qcolordialog_symbian.cpp new file mode 100644 index 0000000000..3dbb5c11a9 --- /dev/null +++ b/src/gui/dialogs/qcolordialog_symbian.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolordialog_p.h" + +#ifndef QT_NO_COLORDIALOG + + +#include "qcolor.h" +#include "private/qguiplatformplugin_p.h" + +#ifdef Q_WS_S60 +#include +#endif + +#include "private/qt_s60_p.h" + +QT_BEGIN_NAMESPACE + +QColor launchSymbianColorDialog(QColor initial) +{ + QColor currentColor = QColor::Invalid; +#ifdef Q_WS_S60 + QT_TRAP_THROWING( + CArrayFixFlat* array = new( ELeave ) CArrayFixFlat(17); + CleanupStack::PushL(array); + array->AppendL(KRgbBlack); + array->AppendL(KRgbDarkGray); + array->AppendL(KRgbDarkRed); + array->AppendL(KRgbDarkGreen); + array->AppendL(KRgbDarkYellow); + array->AppendL(KRgbDarkBlue); + array->AppendL(KRgbDarkMagenta); + array->AppendL(KRgbDarkCyan); + array->AppendL(KRgbRed); + array->AppendL(KRgbGreen); + array->AppendL(KRgbYellow); + array->AppendL(KRgbBlue); + array->AppendL(KRgbMagenta); + array->AppendL(KRgbCyan); + array->AppendL(KRgbGray); + array->AppendL(KRgbWhite); + + TRgb initialColour(initial.red(), initial.green(), initial.blue(), initial.alpha()); + + TBool noneChosen = EFalse; // If true shows the default colour button + CAknColourSelectionGrid* colourSelectionGrid = + CAknColourSelectionGrid::NewL(array, EFalse, noneChosen, initialColour); + CleanupStack::PushL(colourSelectionGrid); + + if (colourSelectionGrid->ExecuteLD()) { + currentColor.setRgb(initialColour.Red(), initialColour.Green(), + initialColour.Blue(), initialColour.Alpha()); + } + CleanupStack::Pop(colourSelectionGrid); + CleanupStack::PopAndDestroy(array); + ); +#endif + return currentColor; +} + +QColor qtSymbianGetColor(const QColor &initial) +{ + return launchSymbianColorDialog(initial); +} + +QT_END_NAMESPACE + +#endif // QT_NO_COLORDIALOG diff --git a/src/gui/dialogs/qdialog.cpp b/src/gui/dialogs/qdialog.cpp new file mode 100644 index 0000000000..6838d927a8 --- /dev/null +++ b/src/gui/dialogs/qdialog.cpp @@ -0,0 +1,1272 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdialog.h" + + +#include "qevent.h" +#include "qdesktopwidget.h" +#include "qpushbutton.h" +#include "qapplication.h" +#include "qlayout.h" +#include "qsizegrip.h" +#include "qwhatsthis.h" +#include "qmenu.h" +#include "qcursor.h" +#include "private/qdialog_p.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#if defined(Q_WS_WINCE) +#include "qt_windows.h" +#include "qmenubar.h" +#include "qpointer.h" +#include "qguifunctions_wince.h" +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp +extern bool qt_wince_is_smartphone(); //is defined in qguifunctions_wce.cpp +#elif defined(Q_WS_X11) +# include "../kernel/qt_x11_p.h" +#elif defined(Q_OS_SYMBIAN) +# include "qfiledialog.h" +# include "qfontdialog.h" +# include "qwizard.h" +# include "private/qt_s60_p.h" +#endif + +#if defined(Q_WS_S60) +#include // AknLayoutUtils +#endif + +#ifndef SPI_GETSNAPTODEFBUTTON +# define SPI_GETSNAPTODEFBUTTON 95 +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QDialog + \brief The QDialog class is the base class of dialog windows. + + \ingroup dialog-classes + \ingroup abstractwidgets + + + A dialog window is a top-level window mostly used for short-term + tasks and brief communications with the user. QDialogs may be + modal or modeless. QDialogs can + provide a \link #return return + value\endlink, and they can have \link #default default + buttons\endlink. QDialogs can also have a QSizeGrip in their + lower-right corner, using setSizeGripEnabled(). + + Note that QDialog (an any other widget that has type Qt::Dialog) uses + the parent widget slightly differently from other classes in Qt. A + dialog is always a top-level widget, but if it has a parent, its + default location is centered on top of the parent's top-level widget + (if it is not top-level itself). It will also share the parent's + taskbar entry. + + Use the overload of the QWidget::setParent() function to change + the ownership of a QDialog widget. This function allows you to + explicitly set the window flags of the reparented widget; using + the overloaded function will clear the window flags specifying the + window-system properties for the widget (in particular it will + reset the Qt::Dialog flag). + + \section1 Modal Dialogs + + A \bold{modal} dialog is a dialog that blocks input to other + visible windows in the same application. Dialogs that are used to + request a file name from the user or that are used to set + application preferences are usually modal. Dialogs can be + \l{Qt::ApplicationModal}{application modal} (the default) or + \l{Qt::WindowModal}{window modal}. + + When an application modal dialog is opened, the user must finish + interacting with the dialog and close it before they can access + any other window in the application. Window modal dialogs only + block access to the window associated with the dialog, allowing + the user to continue to use other windows in an application. + + The most common way to display a modal dialog is to call its + exec() function. When the user closes the dialog, exec() will + provide a useful \link #return return value\endlink. Typically, + to get the dialog to close and return the appropriate value, we + connect a default button, e.g. \gui OK, to the accept() slot and a + \gui Cancel button to the reject() slot. + Alternatively you can call the done() slot with \c Accepted or + \c Rejected. + + An alternative is to call setModal(true) or setWindowModality(), + then show(). Unlike exec(), show() returns control to the caller + immediately. Calling setModal(true) is especially useful for + progress dialogs, where the user must have the ability to interact + with the dialog, e.g. to cancel a long running operation. If you + use show() and setModal(true) together to perform a long operation, + you must call QApplication::processEvents() periodically during + processing to enable the user to interact with the dialog. (See + QProgressDialog.) + + \section1 Modeless Dialogs + + A \bold{modeless} dialog is a dialog that operates + independently of other windows in the same application. Find and + replace dialogs in word-processors are often modeless to allow the + user to interact with both the application's main window and with + the dialog. + + Modeless dialogs are displayed using show(), which returns control + to the caller immediately. + + If you invoke the \l{QWidget::show()}{show()} function after hiding + a dialog, the dialog will be displayed in its original position. This is + because the window manager decides the position for windows that + have not been explicitly placed by the programmer. To preserve the + position of a dialog that has been moved by the user, save its position + in your \l{QWidget::closeEvent()}{closeEvent()} handler and then + move the dialog to that position, before showing it again. + + \target default + \section1 Default Button + + A dialog's \e default button is the button that's pressed when the + user presses Enter (Return). This button is used to signify that + the user accepts the dialog's settings and wants to close the + dialog. Use QPushButton::setDefault(), QPushButton::isDefault() + and QPushButton::autoDefault() to set and control the dialog's + default button. + + \target escapekey + \section1 Escape Key + + If the user presses the Esc key in a dialog, QDialog::reject() + will be called. This will cause the window to close: The \link + QCloseEvent close event \endlink cannot be \link + QCloseEvent::ignore() ignored \endlink. + + \section1 Extensibility + + Extensibility is the ability to show the dialog in two ways: a + partial dialog that shows the most commonly used options, and a + full dialog that shows all the options. Typically an extensible + dialog will initially appear as a partial dialog, but with a + \gui More toggle button. If the user presses the \gui More button down, + the dialog is expanded. The \l{Extension Example} shows how to achieve + extensible dialogs using Qt. + + \target return + \section1 Return Value (Modal Dialogs) + + Modal dialogs are often used in situations where a return value is + required, e.g. to indicate whether the user pressed \gui OK or + \gui Cancel. A dialog can be closed by calling the accept() or the + reject() slots, and exec() will return \c Accepted or \c Rejected + as appropriate. The exec() call returns the result of the dialog. + The result is also available from result() if the dialog has not + been destroyed. + + In order to modify your dialog's close behavior, you can reimplement + the functions accept(), reject() or done(). The + \l{QWidget::closeEvent()}{closeEvent()} function should only be + reimplemented to preserve the dialog's position or to override the + standard close or reject behavior. + + \target examples + \section1 Code Examples + + A modal dialog: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 1 + + A modeless dialog: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 0 + + \sa QDialogButtonBox, QTabWidget, QWidget, QProgressDialog, + {fowler}{GUI Design Handbook: Dialogs, Standard}, {Extension Example}, + {Standard Dialogs Example} +*/ + +/*! \enum QDialog::DialogCode + + The value returned by a modal dialog. + + \value Accepted + \value Rejected +*/ + +/*! + \property QDialog::sizeGripEnabled + \brief whether the size grip is enabled + + A QSizeGrip is placed in the bottom-right corner of the dialog when this + property is enabled. By default, the size grip is disabled. +*/ + + +/*! + Constructs a dialog with parent \a parent. + + A dialog is always a top-level widget, but if it has a parent, its + default location is centered on top of the parent. It will also + share the parent's taskbar entry. + + The widget flags \a f are passed on to the QWidget constructor. + If, for example, you don't want a What's This button in the title bar + of the dialog, pass Qt::WindowTitleHint | Qt::WindowSystemMenuHint in \a f. + + \sa QWidget::setWindowFlags() +*/ + +QDialog::QDialog(QWidget *parent, Qt::WindowFlags f) + : QWidget(*new QDialogPrivate, parent, + f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0))) +{ +#ifdef Q_WS_WINCE + if (!qt_wince_is_smartphone()) + setWindowFlags(windowFlags() | Qt::WindowOkButtonHint | QFlag(qt_wince_is_mobile() ? 0 : Qt::WindowCancelButtonHint)); +#endif + +#ifdef Q_WS_S60 + if (S60->avkonComponentsSupportTransparency) { + bool noSystemBackground = testAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); // also sets WA_NoSystemBackground + setAttribute(Qt::WA_NoSystemBackground, noSystemBackground); // restore system background attribute + } +#endif +} + +#ifdef QT3_SUPPORT +/*! + \overload + \obsolete +*/ +QDialog::QDialog(QWidget *parent, const char *name, bool modal, Qt::WindowFlags f) + : QWidget(*new QDialogPrivate, parent, + f + | QFlag(modal ? Qt::WShowModal : Qt::WindowType(0)) + | QFlag((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0)) + ) +{ + setObjectName(QString::fromAscii(name)); +} +#endif + +/*! + \overload + \internal +*/ +QDialog::QDialog(QDialogPrivate &dd, QWidget *parent, Qt::WindowFlags f) + : QWidget(dd, parent, f | ((f & Qt::WindowType_Mask) == 0 ? Qt::Dialog : Qt::WindowType(0))) +{ +#ifdef Q_WS_WINCE + if (!qt_wince_is_smartphone()) + setWindowFlags(windowFlags() | Qt::WindowOkButtonHint | QFlag(qt_wince_is_mobile() ? 0 : Qt::WindowCancelButtonHint)); +#endif + +#ifdef Q_WS_S60 + if (S60->avkonComponentsSupportTransparency) { + bool noSystemBackground = testAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); // also sets WA_NoSystemBackground + setAttribute(Qt::WA_NoSystemBackground, noSystemBackground); // restore system background attribute + } +#endif +} + +/*! + Destroys the QDialog, deleting all its children. +*/ + +QDialog::~QDialog() +{ + QT_TRY { + // Need to hide() here, as our (to-be) overridden hide() + // will not be called in ~QWidget. + hide(); + } QT_CATCH(...) { + // we're in the destructor - just swallow the exception + } +} + +/*! + \internal + This function is called by the push button \a pushButton when it + becomes the default button. If \a pushButton is 0, the dialogs + default default button becomes the default button. This is what a + push button calls when it loses focus. +*/ +void QDialogPrivate::setDefault(QPushButton *pushButton) +{ + Q_Q(QDialog); + bool hasMain = false; + QList list = q->findChildren(); + for (int i=0; iwindow() == q) { + if (pb == mainDef) + hasMain = true; + if (pb != pushButton) + pb->setDefault(false); + } + } + if (!pushButton && hasMain) + mainDef->setDefault(true); + if (!hasMain) + mainDef = pushButton; +} + +/*! + \internal + This function sets the default default push button to \a pushButton. + This function is called by QPushButton::setDefault(). +*/ +void QDialogPrivate::setMainDefault(QPushButton *pushButton) +{ + mainDef = 0; + setDefault(pushButton); +} + +/*! + \internal + Hides the default button indicator. Called when non auto-default + push button get focus. + */ +void QDialogPrivate::hideDefault() +{ + Q_Q(QDialog); + QList list = q->findChildren(); + for (int i=0; isetDefault(false); + } +} + +void QDialogPrivate::resetModalitySetByOpen() +{ + Q_Q(QDialog); + if (resetModalityTo != -1 && !q->testAttribute(Qt::WA_SetWindowModality)) { + // open() changed the window modality and the user didn't touch it afterwards; restore it + q->setWindowModality(Qt::WindowModality(resetModalityTo)); + q->setAttribute(Qt::WA_SetWindowModality, wasModalitySet); +#ifdef Q_WS_MAC + Q_ASSERT(resetModalityTo != Qt::WindowModal); + q->setParent(q->parentWidget(), Qt::Dialog); +#endif + } + resetModalityTo = -1; +} + +#if defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) +#ifdef Q_WS_WINCE_WM +void QDialogPrivate::_q_doneAction() +{ + //Done... + QApplication::postEvent(q_func(), new QEvent(QEvent::OkRequest)); +} +#endif + +/*! + \reimp +*/ +bool QDialog::event(QEvent *e) +{ + bool result = QWidget::event(e); +#ifdef Q_WS_WINCE + if (e->type() == QEvent::OkRequest) { + accept(); + result = true; + } +#elif defined(Q_WS_S60) + if ((e->type() == QEvent::StyleChange) || (e->type() == QEvent::Resize )) { + if (!testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } + } + // TODO is Symbian, non-S60 behaviour required? +#endif + return result; +} +#endif + +/*! + Returns the modal dialog's result code, \c Accepted or \c Rejected. + + Do not call this function if the dialog was constructed with the + Qt::WA_DeleteOnClose attribute. +*/ +int QDialog::result() const +{ + Q_D(const QDialog); + return d->rescode; +} + +/*! + \fn void QDialog::setResult(int i) + + Sets the modal dialog's result code to \a i. + + \note We recommend that you use one of the values defined by + QDialog::DialogCode. +*/ +void QDialog::setResult(int r) +{ + Q_D(QDialog); + d->rescode = r; +} + +/*! + \since 4.5 + + Shows the dialog as a \l{QDialog#Modal Dialogs}{window modal dialog}, + returning immediately. + + \sa exec(), show(), result(), setWindowModality() +*/ +void QDialog::open() +{ + Q_D(QDialog); + + Qt::WindowModality modality = windowModality(); + if (modality != Qt::WindowModal) { + d->resetModalityTo = modality; + d->wasModalitySet = testAttribute(Qt::WA_SetWindowModality); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_SetWindowModality, false); +#ifdef Q_WS_MAC + setParent(parentWidget(), Qt::Sheet); +#endif + } + + setResult(0); + show(); +} + +/*! + Shows the dialog as a \l{QDialog#Modal Dialogs}{modal dialog}, + blocking until the user closes it. The function returns a \l + DialogCode result. + + If the dialog is \l{Qt::ApplicationModal}{application modal}, users cannot + interact with any other window in the same application until they close + the dialog. If the dialog is \l{Qt::ApplicationModal}{window modal}, only + interaction with the parent window is blocked while the dialog is open. + By default, the dialog is application modal. + + \sa open(), show(), result(), setWindowModality() +*/ + +int QDialog::exec() +{ + Q_D(QDialog); + + if (d->eventLoop) { + qWarning("QDialog::exec: Recursive call detected"); + return -1; + } + + bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, false); + + d->resetModalitySetByOpen(); + + bool wasShowModal = testAttribute(Qt::WA_ShowModal); + setAttribute(Qt::WA_ShowModal, true); + setResult(0); + +//On Windows Mobile we create an empty menu to hide the current menu +#ifdef Q_WS_WINCE_WM +#ifndef QT_NO_MENUBAR + QMenuBar *menuBar = 0; + if (!findChild()) + menuBar = new QMenuBar(this); + if (qt_wince_is_smartphone()) { + QAction *doneAction = new QAction(tr("Done"), this); + menuBar->setDefaultAction(doneAction); + connect(doneAction, SIGNAL(triggered()), this, SLOT(_q_doneAction())); + } +#endif //QT_NO_MENUBAR +#endif //Q_WS_WINCE_WM + + bool showSystemDialogFullScreen = false; +#ifdef Q_OS_SYMBIAN + if (qobject_cast(this) || qobject_cast(this) || + qobject_cast(this)) { + showSystemDialogFullScreen = true; + } +#endif // Q_OS_SYMBIAN + + if (showSystemDialogFullScreen) { + setWindowFlags(windowFlags() | Qt::WindowSoftkeysVisibleHint); + setWindowState(Qt::WindowFullScreen); + } + show(); + +#ifdef Q_WS_MAC + d->mac_nativeDialogModalHelp(); +#endif + + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + QPointer guard = this; + (void) eventLoop.exec(QEventLoop::DialogExec); + if (guard.isNull()) + return QDialog::Rejected; + d->eventLoop = 0; + + setAttribute(Qt::WA_ShowModal, wasShowModal); + + int res = result(); + if (deleteOnClose) + delete this; +#ifdef Q_WS_WINCE_WM +#ifndef QT_NO_MENUBAR + else if (menuBar) + delete menuBar; +#endif //QT_NO_MENUBAR +#endif //Q_WS_WINCE_WM + return res; +} + + +/*! + Closes the dialog and sets its result code to \a r. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a r. + + As with QWidget::close(), done() deletes the dialog if the + Qt::WA_DeleteOnClose flag is set. If the dialog is the application's + main widget, the application terminates. If the dialog is the + last window closed, the QApplication::lastWindowClosed() signal is + emitted. + + \sa accept(), reject(), QApplication::activeWindow(), QApplication::quit() +*/ + +void QDialog::done(int r) +{ + Q_D(QDialog); + hide(); + setResult(r); + + d->close_helper(QWidgetPrivate::CloseNoEvent); + d->resetModalitySetByOpen(); + + emit finished(r); + if (r == Accepted) + emit accepted(); + else if (r == Rejected) + emit rejected(); +} + +/*! + Hides the modal dialog and sets the result code to \c Accepted. + + \sa reject() done() +*/ + +void QDialog::accept() +{ + done(Accepted); +} + +/*! + Hides the modal dialog and sets the result code to \c Rejected. + + \sa accept() done() +*/ + +void QDialog::reject() +{ + done(Rejected); +} + +/*! \reimp */ +bool QDialog::eventFilter(QObject *o, QEvent *e) +{ + return QWidget::eventFilter(o, e); +} + +/***************************************************************************** + Event handlers + *****************************************************************************/ + +#ifndef QT_NO_CONTEXTMENU +/*! \reimp */ +void QDialog::contextMenuEvent(QContextMenuEvent *e) +{ +#if defined(QT_NO_WHATSTHIS) || defined(QT_NO_MENU) + Q_UNUSED(e); +#else + QWidget *w = childAt(e->pos()); + if (!w) { + w = rect().contains(e->pos()) ? this : 0; + if (!w) + return; + } + while (w && w->whatsThis().size() == 0 && !w->testAttribute(Qt::WA_CustomWhatsThis)) + w = w->isWindow() ? 0 : w->parentWidget(); + if (w) { + QWeakPointer p = new QMenu(this); + QAction *wt = p.data()->addAction(tr("What's This?")); + if (p.data()->exec(e->globalPos()) == wt) { + QHelpEvent e(QEvent::WhatsThis, w->rect().center(), + w->mapToGlobal(w->rect().center())); + QApplication::sendEvent(w, &e); + } + delete p.data(); + } +#endif +} +#endif // QT_NO_CONTEXTMENU + +/*! \reimp */ +void QDialog::keyPressEvent(QKeyEvent *e) +{ + // Calls reject() if Escape is pressed. Simulates a button + // click for the default button if Enter is pressed. Move focus + // for the arrow keys. Ignore the rest. +#ifdef Q_WS_MAC + if(e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) { + reject(); + } else +#endif + if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { + switch (e->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: { + QList list = findChildren(); + for (int i=0; iisDefault() && pb->isVisible()) { + if (pb->isEnabled()) + pb->click(); + return; + } + } + } + break; + case Qt::Key_Escape: + reject(); + break; + default: + e->ignore(); + return; + } + } else { + e->ignore(); + } +} + +/*! \reimp */ +void QDialog::closeEvent(QCloseEvent *e) +{ +#ifndef QT_NO_WHATSTHIS + if (isModal() && QWhatsThis::inWhatsThisMode()) + QWhatsThis::leaveWhatsThisMode(); +#endif + if (isVisible()) { + QPointer that = this; + reject(); + if (that && isVisible()) + e->ignore(); + } else { + e->accept(); + } +} + +/***************************************************************************** + Geometry management. + *****************************************************************************/ + +/*! \reimp +*/ + +void QDialog::setVisible(bool visible) +{ + Q_D(QDialog); + if (visible) { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + + if (!testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } + QWidget::setVisible(visible); + showExtension(d->doShowExtension); + QWidget *fw = window()->focusWidget(); + if (!fw) + fw = this; + + /* + The following block is to handle a special case, and does not + really follow propper logic in concern of autoDefault and TAB + order. However, it's here to ease usage for the users. If a + dialog has a default QPushButton, and first widget in the TAB + order also is a QPushButton, then we give focus to the main + default QPushButton. This simplifies code for the developers, + and actually catches most cases... If not, then they simply + have to use [widget*]->setFocus() themselves... + */ + if (d->mainDef && fw->focusPolicy() == Qt::NoFocus) { + QWidget *first = fw; + while ((first = first->nextInFocusChain()) != fw && first->focusPolicy() == Qt::NoFocus) + ; + if (first != d->mainDef && qobject_cast(first)) + d->mainDef->setFocus(); + } + if (!d->mainDef && isWindow()) { + QWidget *w = fw; + while ((w = w->nextInFocusChain()) != fw) { + QPushButton *pb = qobject_cast(w); + if (pb && pb->autoDefault() && pb->focusPolicy() != Qt::NoFocus) { + pb->setDefault(true); + break; + } + } + } + if (fw && !fw->hasFocus()) { + QFocusEvent e(QEvent::FocusIn, Qt::TabFocusReason); + QApplication::sendEvent(fw, &e); + } + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DialogStart); +#endif + + } else { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + +#ifndef QT_NO_ACCESSIBILITY + if (isVisible()) + QAccessible::updateAccessibility(this, 0, QAccessible::DialogEnd); +#endif + + // Reimplemented to exit a modal event loop when the dialog is hidden. + QWidget::setVisible(visible); + if (d->eventLoop) + d->eventLoop->exit(); + } +#ifdef Q_WS_WIN + if (d->mainDef && isActiveWindow()) { + BOOL snapToDefault = false; + if (SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapToDefault, 0)) { + if (snapToDefault) + QCursor::setPos(d->mainDef->mapToGlobal(d->mainDef->rect().center())); + } + } +#endif +} + +/*!\reimp */ +void QDialog::showEvent(QShowEvent *event) +{ + if (!event->spontaneous() && !testAttribute(Qt::WA_Moved)) { + Qt::WindowStates state = windowState(); + adjustPosition(parentWidget()); + setAttribute(Qt::WA_Moved, false); // not really an explicit position + if (state != windowState()) + setWindowState(state); + } +} + +/*! \internal */ +void QDialog::adjustPosition(QWidget* w) +{ +#ifdef Q_WS_X11 + // if the WM advertises that it will place the windows properly for us, let it do it :) + if (X11->isSupportedByWM(ATOM(_NET_WM_FULL_PLACEMENT))) + return; +#endif + +#ifdef Q_OS_SYMBIAN + if (symbianAdjustedPosition()) + //dialog has already been positioned + return; +#endif + + QPoint p(0, 0); + int extraw = 0, extrah = 0, scrn = 0; + if (w) + w = w->window(); + QRect desk; + if (w) { + scrn = QApplication::desktop()->screenNumber(w); + } else if (QApplication::desktop()->isVirtualDesktop()) { + scrn = QApplication::desktop()->screenNumber(QCursor::pos()); + } else { + scrn = QApplication::desktop()->screenNumber(this); + } + desk = QApplication::desktop()->availableGeometry(scrn); + + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; (extraw == 0 || extrah == 0) && i < list.size(); ++i) { + QWidget * current = list.at(i); + if (current->isVisible()) { + int framew = current->geometry().x() - current->x(); + int frameh = current->geometry().y() - current->y(); + + extraw = qMax(extraw, framew); + extrah = qMax(extrah, frameh); + } + } + + // sanity check for decoration frames. With embedding, we + // might get extraordinary values + if (extraw == 0 || extrah == 0 || extraw >= 10 || extrah >= 40) { + extrah = 40; + extraw = 10; + } + + + if (w) { + // Use mapToGlobal rather than geometry() in case w might + // be embedded in another application + QPoint pp = w->mapToGlobal(QPoint(0,0)); + p = QPoint(pp.x() + w->width()/2, + pp.y() + w->height()/ 2); + } else { + // p = middle of the desktop + p = QPoint(desk.x() + desk.width()/2, desk.y() + desk.height()/2); + } + + // p = origin of this + p = QPoint(p.x()-width()/2 - extraw, + p.y()-height()/2 - extrah); + + + if (p.x() + extraw + width() > desk.x() + desk.width()) + p.setX(desk.x() + desk.width() - width() - extraw); + if (p.x() < desk.x()) + p.setX(desk.x()); + + if (p.y() + extrah + height() > desk.y() + desk.height()) + p.setY(desk.y() + desk.height() - height() - extrah); + if (p.y() < desk.y()) + p.setY(desk.y()); + + move(p); +} + +#if defined(Q_OS_SYMBIAN) +/*! \internal */ +bool QDialog::symbianAdjustedPosition() +{ +#if defined(Q_WS_S60) + QPoint p; + QPoint oldPos = pos(); + if (isFullScreen()) { + p.setX(0); + p.setY(0); + } else if (isMaximized()) { + TRect statusPaneRect = TRect(); + if (S60->screenHeightInPixels > S60->screenWidthInPixels) { + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStatusPane, statusPaneRect); + } else { + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStaconTop, statusPaneRect); + } + + p.setX(0); + p.setY(statusPaneRect.Height()); + } else { + // naive way to deduce screen orientation + if (S60->screenHeightInPixels > S60->screenWidthInPixels) { + int cbaHeight; + TRect rect; + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EControlPane, rect); + cbaHeight = rect.Height(); + p.setY(S60->screenHeightInPixels - height() - cbaHeight); + p.setX(0); + } else { + const int scrollbarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + TRect staConTopRect = TRect(); + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EStaconTop, staConTopRect); + if (staConTopRect.IsEmpty()) { + TRect cbaRect = TRect(); + AknLayoutUtils::LayoutMetricsRect(AknLayoutUtils::EControlPane, cbaRect); + AknLayoutUtils::TAknCbaLocation cbaLocation = AknLayoutUtils::CbaLocation(); + switch (cbaLocation) { + case AknLayoutUtils::EAknCbaLocationBottom: + p.setY(S60->screenHeightInPixels - height() - cbaRect.Height()); + p.setX((S60->screenWidthInPixels - width()) >> 1); + break; + case AknLayoutUtils::EAknCbaLocationRight: + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,S60->screenWidthInPixels - width() - scrollbarWidth - cbaRect.Width())); + break; + case AknLayoutUtils::EAknCbaLocationLeft: + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,scrollbarWidth + cbaRect.Width())); + break; + } + } else { + p.setY((S60->screenHeightInPixels - height()) >> 1); + p.setX(qMax(0,S60->screenWidthInPixels - width())); + } + } + } + if (oldPos != p || p.y() < 0) + move(p); + return true; +#else + // TODO - check positioning requirement for Symbian, non-s60 + return false; +#endif +} +#endif + +/*! + \obsolete + + If \a orientation is Qt::Horizontal, the extension will be displayed + to the right of the dialog's main area. If \a orientation is + Qt::Vertical, the extension will be displayed below the dialog's main + area. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa setExtension() +*/ +void QDialog::setOrientation(Qt::Orientation orientation) +{ + Q_D(QDialog); + d->orientation = orientation; +} + +/*! + \obsolete + + Returns the dialog's extension orientation. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa extension() +*/ +Qt::Orientation QDialog::orientation() const +{ + Q_D(const QDialog); + return d->orientation; +} + +/*! + \obsolete + + Sets the widget, \a extension, to be the dialog's extension, + deleting any previous extension. The dialog takes ownership of the + extension. Note that if 0 is passed any existing extension will be + deleted. This function must only be called while the dialog is hidden. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa showExtension(), setOrientation() +*/ +void QDialog::setExtension(QWidget* extension) +{ + Q_D(QDialog); + delete d->extension; + d->extension = extension; + + if (!extension) + return; + + if (extension->parentWidget() != this) + extension->setParent(this); + extension->hide(); +} + +/*! + \obsolete + + Returns the dialog's extension or 0 if no extension has been + defined. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa showExtension(), setOrientation() +*/ +QWidget* QDialog::extension() const +{ + Q_D(const QDialog); + return d->extension; +} + + +/*! + \obsolete + + If \a showIt is true, the dialog's extension is shown; otherwise the + extension is hidden. + + Instead of using this functionality, we recommend that you simply call + show() or hide() on the part of the dialog that you want to use as an + extension. See the \l{Extension Example} for details. + + \sa show(), setExtension(), setOrientation() +*/ +void QDialog::showExtension(bool showIt) +{ + Q_D(QDialog); + d->doShowExtension = showIt; + if (!d->extension) + return; + if (!testAttribute(Qt::WA_WState_Visible)) + return; + if (d->extension->isVisible() == showIt) + return; + + if (showIt) { + d->size = size(); + d->min = minimumSize(); + d->max = maximumSize(); + if (layout()) + layout()->setEnabled(false); + QSize s(d->extension->sizeHint() + .expandedTo(d->extension->minimumSize()) + .boundedTo(d->extension->maximumSize())); + if (d->orientation == Qt::Horizontal) { + int h = qMax(height(), s.height()); + d->extension->setGeometry(width(), 0, s.width(), h); + setFixedSize(width() + s.width(), h); + } else { + int w = qMax(width(), s.width()); + d->extension->setGeometry(0, height(), w, s.height()); + setFixedSize(w, height() + s.height()); + } + d->extension->show(); +#ifndef QT_NO_SIZEGRIP + const bool sizeGripEnabled = isSizeGripEnabled(); + setSizeGripEnabled(false); + d->sizeGripEnabled = sizeGripEnabled; +#endif + } else { + d->extension->hide(); + // workaround for CDE window manager that won't shrink with (-1,-1) + setMinimumSize(d->min.expandedTo(QSize(1, 1))); + setMaximumSize(d->max); + resize(d->size); + if (layout()) + layout()->setEnabled(true); +#ifndef QT_NO_SIZEGRIP + setSizeGripEnabled(d->sizeGripEnabled); +#endif + } +} + + +/*! \reimp */ +QSize QDialog::sizeHint() const +{ + Q_D(const QDialog); + if (d->extension) { + if (d->orientation == Qt::Horizontal) + return QSize(QWidget::sizeHint().width(), + qMax(QWidget::sizeHint().height(),d->extension->sizeHint().height())); + else + return QSize(qMax(QWidget::sizeHint().width(), d->extension->sizeHint().width()), + QWidget::sizeHint().height()); + } +#if defined(Q_WS_S60) + // if size is not fixed, try to adjust it according to S60 layoutting + if (minimumSize() != maximumSize()) { + // In S60, dialogs are always the width of screen (in portrait, regardless of current layout) + return QSize(qMin(S60->screenHeightInPixels, S60->screenWidthInPixels), QWidget::sizeHint().height()); + } else { + return QWidget::sizeHint(); + } +#else + return QWidget::sizeHint(); +#endif //Q_WS_S60 +} + + +/*! \reimp */ +QSize QDialog::minimumSizeHint() const +{ + Q_D(const QDialog); + if (d->extension) { + if (d->orientation == Qt::Horizontal) + return QSize(QWidget::minimumSizeHint().width(), + qMax(QWidget::minimumSizeHint().height(), d->extension->minimumSizeHint().height())); + else + return QSize(qMax(QWidget::minimumSizeHint().width(), d->extension->minimumSizeHint().width()), + QWidget::minimumSizeHint().height()); + } + + return QWidget::minimumSizeHint(); +} + +/*! + \property QDialog::modal + \brief whether show() should pop up the dialog as modal or modeless + + By default, this property is false and show() pops up the dialog + as modeless. Setting his property to true is equivalent to setting + QWidget::windowModality to Qt::ApplicationModal. + + exec() ignores the value of this property and always pops up the + dialog as modal. + + \sa QWidget::windowModality, show(), exec() +*/ + +void QDialog::setModal(bool modal) +{ + setAttribute(Qt::WA_ShowModal, modal); +} + + +bool QDialog::isSizeGripEnabled() const +{ +#ifndef QT_NO_SIZEGRIP + Q_D(const QDialog); + return !!d->resizer; +#else + return false; +#endif +} + + +void QDialog::setSizeGripEnabled(bool enabled) +{ +#ifdef QT_NO_SIZEGRIP + Q_UNUSED(enabled); +#else + Q_D(QDialog); +#ifndef QT_NO_SIZEGRIP + d->sizeGripEnabled = enabled; + if (enabled && d->doShowExtension) + return; +#endif + if (!enabled != !d->resizer) { + if (enabled) { + d->resizer = new QSizeGrip(this); + // adjustSize() processes all events, which is suboptimal + d->resizer->resize(d->resizer->sizeHint()); + if (isRightToLeft()) + d->resizer->move(rect().bottomLeft() -d->resizer->rect().bottomLeft()); + else + d->resizer->move(rect().bottomRight() -d->resizer->rect().bottomRight()); + d->resizer->raise(); + d->resizer->show(); + } else { + delete d->resizer; + d->resizer = 0; + } + } +#endif //QT_NO_SIZEGRIP +} + + + +/*! \reimp */ +void QDialog::resizeEvent(QResizeEvent *) +{ +#ifndef QT_NO_SIZEGRIP + Q_D(QDialog); + if (d->resizer) { + if (isRightToLeft()) + d->resizer->move(rect().bottomLeft() -d->resizer->rect().bottomLeft()); + else + d->resizer->move(rect().bottomRight() -d->resizer->rect().bottomRight()); + d->resizer->raise(); + } +#endif +} + +/*! \fn void QDialog::finished(int result) + \since 4.1 + + This signal is emitted when the dialog's \a result code has been + set, either by the user or by calling done(), accept(), or + reject(). + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa accepted(), rejected() +*/ + +/*! \fn void QDialog::accepted() + \since 4.1 + + This signal is emitted when the dialog has been accepted either by + the user or by calling accept() or done() with the + QDialog::Accepted argument. + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa finished(), rejected() +*/ + +/*! \fn void QDialog::rejected() + \since 4.1 + + This signal is emitted when the dialog has been rejected either by + the user or by calling reject() or done() with the + QDialog::Rejected argument. + + Note that this signal is \e not emitted when hiding the dialog + with hide() or setVisible(false). This includes deleting the + dialog while it is visible. + + \sa finished(), accepted() +*/ + +QT_END_NAMESPACE +#include "moc_qdialog.cpp" diff --git a/src/gui/dialogs/qdialog.h b/src/gui/dialogs/qdialog.h new file mode 100644 index 0000000000..ee23b7bf87 --- /dev/null +++ b/src/gui/dialogs/qdialog.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIALOG_H +#define QDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPushButton; +class QDialogPrivate; + +class Q_GUI_EXPORT QDialog : public QWidget +{ + Q_OBJECT + friend class QPushButton; + + Q_PROPERTY(bool sizeGripEnabled READ isSizeGripEnabled WRITE setSizeGripEnabled) + Q_PROPERTY(bool modal READ isModal WRITE setModal) + +public: + explicit QDialog(QWidget *parent = 0, Qt::WindowFlags f = 0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QDialog(QWidget *parent, const char *name, bool modal = false, + Qt::WindowFlags f = 0); +#endif + ~QDialog(); + + enum DialogCode { Rejected, Accepted }; + + int result() const; + + void setVisible(bool visible); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void setExtension(QWidget* extension); + QWidget* extension() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void setSizeGripEnabled(bool); + bool isSizeGripEnabled() const; + + void setModal(bool modal); + void setResult(int r); + +Q_SIGNALS: + void finished(int result); + void accepted(); + void rejected(); + +public Q_SLOTS: + void open(); + int exec(); + virtual void done(int); + virtual void accept(); + virtual void reject(); + + void showExtension(bool); + +protected: + QDialog(QDialogPrivate &, QWidget *parent, Qt::WindowFlags f = 0); + +#if defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) + bool event(QEvent *e); +#endif + void keyPressEvent(QKeyEvent *); + void closeEvent(QCloseEvent *); + void showEvent(QShowEvent *); + void resizeEvent(QResizeEvent *); +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *); +#endif + bool eventFilter(QObject *, QEvent *); + void adjustPosition(QWidget*); +private: + Q_DECLARE_PRIVATE(QDialog) + Q_DISABLE_COPY(QDialog) + +#if defined(Q_OS_SYMBIAN) + bool symbianAdjustedPosition(); +#endif + + +#ifdef Q_WS_WINCE_WM + Q_PRIVATE_SLOT(d_func(), void _q_doneAction()) +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIALOG_H diff --git a/src/gui/dialogs/qdialog_p.h b/src/gui/dialogs/qdialog_p.h new file mode 100644 index 0000000000..a2a948e951 --- /dev/null +++ b/src/gui/dialogs/qdialog_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIALOG_P_H +#define QDIALOG_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/qwidget_p.h" +#include "QtCore/qeventloop.h" +#include "QtCore/qpointer.h" +#include "QtGui/qdialog.h" +#include "QtGui/qpushbutton.h" + +QT_BEGIN_NAMESPACE + +class QSizeGrip; + +class QDialogPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QDialog) +public: + + QDialogPrivate() + : mainDef(0), orientation(Qt::Horizontal),extension(0), doShowExtension(false), +#ifndef QT_NO_SIZEGRIP + resizer(0), + sizeGripEnabled(false), +#endif + rescode(0), resetModalityTo(-1), wasModalitySet(true), eventLoop(0) + {} + + QPointer mainDef; + Qt::Orientation orientation; + QWidget *extension; + bool doShowExtension; + QSize size, min, max; +#ifndef QT_NO_SIZEGRIP + QSizeGrip *resizer; + bool sizeGripEnabled; +#endif + QPoint lastRMBPress; + + void setDefault(QPushButton *); + void setMainDefault(QPushButton *); + void hideDefault(); + void resetModalitySetByOpen(); + +#ifdef Q_WS_WINCE_WM + void _q_doneAction(); +#endif + +#ifdef Q_WS_MAC + virtual void mac_nativeDialogModalHelp() {} +#endif + + int rescode; + int resetModalityTo; + bool wasModalitySet; + + QPointer eventLoop; +}; + +QT_END_NAMESPACE + +#endif // QDIALOG_P_H diff --git a/src/gui/dialogs/qdialogsbinarycompat_win.cpp b/src/gui/dialogs/qdialogsbinarycompat_win.cpp new file mode 100644 index 0000000000..beec2cd29c --- /dev/null +++ b/src/gui/dialogs/qdialogsbinarycompat_win.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +// ### Qt 5: eliminate this file + +/* + This is evil. MSVC doesn't let us remove private symbols, nor change their + visibility; yet there are some symbols we really needed to make public, e.g., + ~QColorDialog(), and then there were some totally needless symbols in our + header files, e.g., setSelectedAlpha(). So we define a new version of + QColorDialog & Co. with only the private symbols that we removed from the + public header files. The friends are there only to prevent potential compiler + warnings. + + It would have been nicer to export the missing symbols as mangled "C" symbols + instead but unfortunately MSVC uses out-of-reach characters like @ and . in + their mangled C++ symbols. +*/ + +#if QT_VERSION < 0x050000 && defined(Q_CC_MSVC) + +QT_BEGIN_NAMESPACE + +#include +#include + +class QColorDialogPrivate; +class QFontDialogPrivate; +class QInputDialogPrivate; +class QWidget; + +class Q_GUI_EXPORT QColorDialog +{ +private: + explicit QColorDialog(QWidget *, bool); + ~QColorDialog(); + + void setColor(const QColor &); + QColor color() const; + bool selectColor(const QColor &); + void setSelectedAlpha(int); + int selectedAlpha() const; + + friend class QColorDialogPrivate; +}; + +QColorDialog::QColorDialog(QWidget *, bool) {} +QColorDialog::~QColorDialog() {} +void QColorDialog::setColor(const QColor &) {} +QColor QColorDialog::color() const { return QColor(); } +bool QColorDialog::selectColor(const QColor &) { return false; } +void QColorDialog::setSelectedAlpha(int) {} +int QColorDialog::selectedAlpha() const { return 0; } + +class Q_GUI_EXPORT QFontDialog +{ +private: + explicit QFontDialog(QWidget *, bool, Qt::WindowFlags); + ~QFontDialog(); + + QFont font() const; + void setFont(const QFont &); + void updateFamilies(); + void updateStyles(); + void updateSizes(); + + static QFont getFont(bool *, const QFont *, QWidget *); + + friend class QFontDialogPrivate; +}; + +QFontDialog::QFontDialog(QWidget *, bool, Qt::WindowFlags) {} +QFontDialog::~QFontDialog() {} +QFont QFontDialog::font() const { return QFont(); } +void QFontDialog::setFont(const QFont &) { } +void QFontDialog::updateFamilies() {} +void QFontDialog::updateStyles() {} +void QFontDialog::updateSizes() {} +QFont QFontDialog::getFont(bool *, const QFont *, QWidget *) { return QFont(); } + +class Q_GUI_EXPORT QInputDialog +{ +private: + enum Type { LineEdit, SpinBox, DoubleSpinBox, ComboBox, EditableComboBox }; + + QInputDialog(const QString &, QWidget *, Type, Qt::WindowFlags); + QInputDialog(const QString &, const QString &, QWidget *, QWidget *, Qt::WindowFlags); + ~QInputDialog(); +}; + +QInputDialog::QInputDialog(const QString &, QWidget *, Type, Qt::WindowFlags) {} +QInputDialog::QInputDialog(const QString &, const QString &, QWidget *, QWidget *, Qt::WindowFlags) {} +QInputDialog::~QInputDialog() {} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qerrormessage.cpp b/src/gui/dialogs/qerrormessage.cpp new file mode 100644 index 0000000000..6d474cc4f9 --- /dev/null +++ b/src/gui/dialogs/qerrormessage.cpp @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qerrormessage.h" + +#ifndef QT_NO_ERRORMESSAGE + +#include "qapplication.h" +#include "qcheckbox.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qmessagebox.h" +#include "qpushbutton.h" +#include "qstringlist.h" +#include "qtextedit.h" +#include "qdialog_p.h" +#include "qpixmap.h" +#include "qmetaobject.h" +#include "qthread.h" +#include "qqueue.h" +#include "qset.h" + +#include +#include + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_high_dpi(); //defined in qguifunctions_wince.cpp + +#include "qguifunctions_wince.h" +#endif + +#if defined(QT_SOFTKEYS_ENABLED) +#include +#endif +#ifdef Q_WS_S60 +#include "private/qt_s60_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QErrorMessagePrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QErrorMessage) +public: + QPushButton * ok; + QCheckBox * again; + QTextEdit * errors; + QLabel * icon; +#ifdef QT_SOFTKEYS_ENABLED + QAction *okAction; +#endif + QQueue > pending; + QSet doNotShow; + QSet doNotShowType; + QString currentMessage; + QString currentType; + + bool nextPending(); + void retranslateStrings(); +}; + +class QErrorMessageTextView : public QTextEdit +{ +public: + QErrorMessageTextView(QWidget *parent) + : QTextEdit(parent) { setReadOnly(true); } + + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; +}; + +QSize QErrorMessageTextView::minimumSizeHint() const +{ +#ifdef Q_WS_WINCE + if (qt_wince_is_mobile()) + if (qt_wince_is_high_dpi()) + return QSize(200, 200); + else + return QSize(100, 100); + else + return QSize(70, 70); +#else + return QSize(50, 50); +#endif +} + +QSize QErrorMessageTextView::sizeHint() const +{ +#ifdef Q_WS_WINCE + if (qt_wince_is_mobile()) + if (qt_wince_is_high_dpi()) + return QSize(400, 200); + else + return QSize(320, 120); + else + return QSize(300, 100); +#else + +#ifdef Q_WS_S60 + const int smallerDimension = qMin(S60->screenHeightInPixels, S60->screenWidthInPixels); + // In S60 layout data, error messages seem to be one third of the screen height (in portrait) minus two. + return QSize(smallerDimension, smallerDimension/3-2); +#else + return QSize(250, 75); +#endif //Q_WS_S60 +#endif //Q_WS_WINCE +} + +/*! + \class QErrorMessage + + \brief The QErrorMessage class provides an error message display dialog. + + \ingroup standard-dialog + + An error message widget consists of a text label and a checkbox. The + checkbox lets the user control whether the same error message will be + displayed again in the future, typically displaying the text, + "Show this message again" translated into the appropriate local + language. + + For production applications, the class can be used to display messages which + the user only needs to see once. To use QErrorMessage like this, you create + the dialog in the usual way, and show it by calling the showMessage() slot or + connecting signals to it. + + The static qtHandler() function installs a message handler + using qInstallMsgHandler() and creates a QErrorMessage that displays + qDebug(), qWarning() and qFatal() messages. This is most useful in + environments where no console is available to display warnings and + error messages. + + In both cases QErrorMessage will queue pending messages and display + them in order, with each new message being shown as soon as the user + has accepted the previous message. Once the user has specified that a + message is not to be shown again it is automatically skipped, and the + dialog will show the next appropriate message in the queue. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QErrorMessage as well as other built-in Qt dialogs. + + \img qerrormessage.png + + \sa QMessageBox, QStatusBar::showMessage(), {Standard Dialogs Example} +*/ + +static QErrorMessage * qtMessageHandler = 0; + +static void deleteStaticcQErrorMessage() // post-routine +{ + if (qtMessageHandler) { + delete qtMessageHandler; + qtMessageHandler = 0; + } +} + +static bool metFatal = false; + +static void jump(QtMsgType t, const char * m) +{ + if (!qtMessageHandler) + return; + + QString rich; + + switch (t) { + case QtDebugMsg: + default: + rich = QErrorMessage::tr("Debug Message:"); + break; + case QtWarningMsg: + rich = QErrorMessage::tr("Warning:"); + break; + case QtFatalMsg: + rich = QErrorMessage::tr("Fatal Error:"); + } + rich = QString::fromLatin1("

%1

").arg(rich); + rich += Qt::convertFromPlainText(QLatin1String(m), Qt::WhiteSpaceNormal); + + // ### work around text engine quirk + if (rich.endsWith(QLatin1String("

"))) + rich.chop(4); + + if (!metFatal) { + if (QThread::currentThread() == qApp->thread()) { + qtMessageHandler->showMessage(rich); + } else { + QMetaObject::invokeMethod(qtMessageHandler, + "showMessage", + Qt::QueuedConnection, + Q_ARG(QString, rich)); + } + metFatal = (t == QtFatalMsg); + } +} + + +/*! + Constructs and installs an error handler window with the given \a + parent. +*/ + +QErrorMessage::QErrorMessage(QWidget * parent) + : QDialog(*new QErrorMessagePrivate, parent) +{ + Q_D(QErrorMessage); + QGridLayout * grid = new QGridLayout(this); + d->icon = new QLabel(this); +#ifndef QT_NO_MESSAGEBOX + d->icon->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); + d->icon->setAlignment(Qt::AlignHCenter | Qt::AlignTop); +#endif + grid->addWidget(d->icon, 0, 0, Qt::AlignTop); + d->errors = new QErrorMessageTextView(this); + grid->addWidget(d->errors, 0, 1); + d->again = new QCheckBox(this); + d->again->setChecked(true); + grid->addWidget(d->again, 1, 1, Qt::AlignTop); + d->ok = new QPushButton(this); +#ifdef QT_SOFTKEYS_ENABLED + d->okAction = new QAction(d->ok); + d->okAction->setSoftKeyRole(QAction::PositiveSoftKey); + connect(d->okAction, SIGNAL(triggered()), this, SLOT(accept())); + addAction(d->okAction); +#endif + + +#if defined(Q_WS_WINCE) || defined(Q_WS_S60) + d->ok->setFixedSize(0,0); +#endif + connect(d->ok, SIGNAL(clicked()), this, SLOT(accept())); + d->ok->setFocus(); + grid->addWidget(d->ok, 2, 0, 1, 2, Qt::AlignCenter); + grid->setColumnStretch(1, 42); + grid->setRowStretch(0, 42); + d->retranslateStrings(); +} + + +/*! + Destroys the error message dialog. +*/ + +QErrorMessage::~QErrorMessage() +{ + if (this == qtMessageHandler) { + qtMessageHandler = 0; + QtMsgHandler tmp = qInstallMsgHandler(0); + // in case someone else has later stuck in another... + if (tmp != jump) + qInstallMsgHandler(tmp); + } +} + + +/*! \reimp */ + +void QErrorMessage::done(int a) +{ + Q_D(QErrorMessage); + if (!d->again->isChecked() && !d->currentMessage.isEmpty() && d->currentType.isEmpty()) { + d->doNotShow.insert(d->currentMessage); + } + if (!d->again->isChecked() && !d->currentType.isEmpty()) { + d->doNotShowType.insert(d->currentType); + } + d->currentMessage.clear(); + d->currentType.clear(); + if (!d->nextPending()) { + QDialog::done(a); + if (this == qtMessageHandler && metFatal) + exit(1); + } +} + + +/*! + Returns a pointer to a QErrorMessage object that outputs the + default Qt messages. This function creates such an object, if there + isn't one already. +*/ + +QErrorMessage * QErrorMessage::qtHandler() +{ + if (!qtMessageHandler) { + qtMessageHandler = new QErrorMessage(0); + qAddPostRoutine(deleteStaticcQErrorMessage); // clean up + qtMessageHandler->setWindowTitle(QApplication::applicationName()); + qInstallMsgHandler(jump); + } + return qtMessageHandler; +} + + +/*! \internal */ + +bool QErrorMessagePrivate::nextPending() +{ + while (!pending.isEmpty()) { + QPair pendingMessage = pending.dequeue(); + QString message = pendingMessage.first; + QString type = pendingMessage.second; + if (!message.isEmpty() && ((type.isEmpty() && !doNotShow.contains(message)) || (!type.isEmpty() && !doNotShowType.contains(type)))) { +#ifndef QT_NO_TEXTHTMLPARSER + errors->setHtml(message); +#else + errors->setPlainText(message); +#endif + currentMessage = message; + currentType = type; + return true; + } + } + return false; +} + + +/*! + Shows the given message, \a message, and returns immediately. If the user + has requested for the message not to be shown again, this function does + nothing. + + Normally, the message is displayed immediately. However, if there are + pending messages, it will be queued to be displayed later. +*/ + +void QErrorMessage::showMessage(const QString &message) +{ + Q_D(QErrorMessage); + if (d->doNotShow.contains(message)) + return; + d->pending.enqueue(qMakePair(message,QString())); + if (!isVisible() && d->nextPending()) + show(); +} + +/*! + \since 4.5 + \overload + + Shows the given message, \a message, and returns immediately. If the user + has requested for messages of type, \a type, not to be shown again, this + function does nothing. + + Normally, the message is displayed immediately. However, if there are + pending messages, it will be queued to be displayed later. + + \sa showMessage() +*/ + +void QErrorMessage::showMessage(const QString &message, const QString &type) +{ + Q_D(QErrorMessage); + if (d->doNotShow.contains(message) && d->doNotShowType.contains(type)) + return; + d->pending.push_back(qMakePair(message,type)); + if (!isVisible() && d->nextPending()) + show(); +} + +/*! + \reimp +*/ +void QErrorMessage::changeEvent(QEvent *e) +{ + Q_D(QErrorMessage); + if (e->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +void QErrorMessagePrivate::retranslateStrings() +{ + again->setText(QErrorMessage::tr("&Show this message again")); + ok->setText(QErrorMessage::tr("&OK")); +#ifdef QT_SOFTKEYS_ENABLED + okAction->setText(ok->text()); +#endif +} + +/*! + \fn void QErrorMessage::message(const QString & message) + + Use showMessage(\a message) instead. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_ERRORMESSAGE diff --git a/src/gui/dialogs/qerrormessage.h b/src/gui/dialogs/qerrormessage.h new file mode 100644 index 0000000000..500482c722 --- /dev/null +++ b/src/gui/dialogs/qerrormessage.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QERRORMESSAGE_H +#define QERRORMESSAGE_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ERRORMESSAGE + +class QErrorMessagePrivate; + +class Q_GUI_EXPORT QErrorMessage: public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QErrorMessage) +public: + explicit QErrorMessage(QWidget* parent = 0); + ~QErrorMessage(); + + static QErrorMessage * qtHandler(); + +public Q_SLOTS: + void showMessage(const QString &message); + void showMessage(const QString &message, const QString &type); +#ifdef QT3_SUPPORT + inline QT_MOC_COMPAT void message(const QString &text) { showMessage(text); } +#endif + +protected: + void done(int); + void changeEvent(QEvent *e); + +private: + Q_DISABLE_COPY(QErrorMessage) +}; + +#endif // QT_NO_ERRORMESSAGE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QERRORMESSAGE_H diff --git a/src/gui/dialogs/qfiledialog.cpp b/src/gui/dialogs/qfiledialog.cpp new file mode 100644 index 0000000000..897a9164a2 --- /dev/null +++ b/src/gui/dialogs/qfiledialog.cpp @@ -0,0 +1,3486 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG +#include "qfiledialog_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined(Q_WS_WINCE) && !defined(Q_OS_SYMBIAN) +#include "ui_qfiledialog.h" +#else +#define Q_EMBEDDED_SMALLSCREEN +#include "ui_qfiledialog_embedded.h" +#if defined(Q_OS_WINCE) +extern bool qt_priv_ptr_valid; +#endif +#endif + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QString, lastVisitedDir) + +/* + \internal + + Exported hooks that can be used to customize the static functions. + */ +typedef QString (*_qt_filedialog_existing_directory_hook)(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options); +Q_GUI_EXPORT _qt_filedialog_existing_directory_hook qt_filedialog_existing_directory_hook = 0; + +typedef QString (*_qt_filedialog_open_filename_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_GUI_EXPORT _qt_filedialog_open_filename_hook qt_filedialog_open_filename_hook = 0; + +typedef QStringList (*_qt_filedialog_open_filenames_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_GUI_EXPORT _qt_filedialog_open_filenames_hook qt_filedialog_open_filenames_hook = 0; + +typedef QString (*_qt_filedialog_save_filename_hook)(QWidget * parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options); +Q_GUI_EXPORT _qt_filedialog_save_filename_hook qt_filedialog_save_filename_hook = 0; + +/*! + \class QFileDialog + \brief The QFileDialog class provides a dialog that allow users to select files or directories. + \ingroup standard-dialogs + + + The QFileDialog class enables a user to traverse the file system in + order to select one or many files or a directory. + + The easiest way to create a QFileDialog is to use the static + functions. On Windows, Mac OS X, KDE and GNOME, these static functions will + call the native file dialog when possible. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 0 + + In the above example, a modal QFileDialog is created using a static + function. The dialog initially displays the contents of the "/home/jana" + directory, and displays files matching the patterns given in the + string "Image Files (*.png *.jpg *.bmp)". The parent of the file dialog + is set to \e this, and the window title is set to "Open Image". + + If you want to use multiple filters, separate each one with + \e two semicolons. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 1 + + You can create your own QFileDialog without using the static + functions. By calling setFileMode(), you can specify what the user must + select in the dialog: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 2 + + In the above example, the mode of the file dialog is set to + AnyFile, meaning that the user can select any file, or even specify a + file that doesn't exist. This mode is useful for creating a + "Save As" file dialog. Use ExistingFile if the user must select an + existing file, or \l Directory if only a directory may be selected. + See the \l QFileDialog::FileMode enum for the complete list of modes. + + The fileMode property contains the mode of operation for the dialog; + this indicates what types of objects the user is expected to select. + Use setNameFilter() to set the dialog's file filter. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 3 + + In the above example, the filter is set to \c{"Images (*.png *.xpm *.jpg)"}, + this means that only files with the extension \c png, \c xpm, + or \c jpg will be shown in the QFileDialog. You can apply + several filters by using setNameFilters(). Use selectNameFilter() to select + one of the filters you've given as the file dialog's default filter. + + The file dialog has two view modes: \l{QFileDialog::}{List} and + \l{QFileDialog::}{Detail}. + \l{QFileDialog::}{List} presents the contents of the current directory + as a list of file and directory names. \l{QFileDialog::}{Detail} also + displays a list of file and directory names, but provides additional + information alongside each name, such as the file size and modification + date. Set the mode with setViewMode(): + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 4 + + The last important function you will need to use when creating your + own file dialog is selectedFiles(). + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 5 + + In the above example, a modal file dialog is created and shown. If + the user clicked OK, the file they selected is put in \c fileName. + + The dialog's working directory can be set with setDirectory(). + Each file in the current directory can be selected using + the selectFile() function. + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QFileDialog as well as other built-in Qt dialogs. + + \sa QDir, QFileInfo, QFile, QPrintDialog, QColorDialog, QFontDialog, {Standard Dialogs Example}, + {Application Example} +*/ + +/*! + \enum QFileDialog::AcceptMode + + \value AcceptOpen + \value AcceptSave +*/ + +/*! + \enum QFileDialog::ViewMode + + This enum describes the view mode of the file dialog; i.e. what + information about each file will be displayed. + + \value Detail Displays an icon, a name, and details for each item in + the directory. + \value List Displays only an icon and a name for each item in the + directory. + + \sa setViewMode() +*/ + +/*! + \enum QFileDialog::FileMode + + This enum is used to indicate what the user may select in the file + dialog; i.e. what the dialog will return if the user clicks OK. + + \value AnyFile The name of a file, whether it exists or not. + \value ExistingFile The name of a single existing file. + \value Directory The name of a directory. Both files and + directories are displayed. + \value ExistingFiles The names of zero or more existing files. + + This value is obsolete since Qt 4.5: + + \value DirectoryOnly Use \c Directory and setOption(ShowDirsOnly, true) instead. + + \sa setFileMode() +*/ + +/*! + \enum QFileDialog::Option + + \value ShowDirsOnly Only show directories in the file dialog. By + default both files and directories are shown. (Valid only in the + \l Directory file mode.) + + \value DontResolveSymlinks Don't resolve symlinks in the file + dialog. By default symlinks are resolved. + + \value DontConfirmOverwrite Don't ask for confirmation if an + existing file is selected. By default confirmation is requested. + + \value DontUseNativeDialog Don't use the native file dialog. By + default, the native file dialog is used unless you use a subclass + of QFileDialog that contains the Q_OBJECT macro. + + \value ReadOnly Indicates that the model is readonly. + + \value HideNameFilterDetails Indicates if the file name filter details are + hidden or not. + + \value DontUseSheet In previous versions of Qt, the static + functions would create a sheet by default if the static function + was given a parent. This is no longer supported and does nothing in Qt 4.5, The + static functions will always be an application modal dialog. If + you want to use sheets, use QFileDialog::open() instead. + +*/ + +/*! + \enum QFileDialog::DialogLabel + + \value LookIn + \value FileName + \value FileType + \value Accept + \value Reject +*/ + +/*! + \fn void QFileDialog::filesSelected(const QStringList &selected) + + When the selection changes and the dialog is accepted, this signal is + emitted with the (possibly empty) list of \a selected files. + + \sa currentChanged(), QDialog::Accepted +*/ + + +/*! + \fn void QFileDialog::fileSelected(const QString &file) + + When the selection changes and the dialog is accepted, this signal is + emitted with the (possibly empty) selected \a file. + + \sa currentChanged(), QDialog::Accepted +*/ + + +/*! + \fn void QFileDialog::currentChanged(const QString &path) + + When the current file changes, this signal is emitted with the + new file name as the \a path parameter. + + \sa filesSelected() +*/ + +/*! + \fn void QFileDialog::directoryEntered(const QString &directory) + \since 4.3 + + This signal is emitted when the user enters a \a directory. +*/ + +/*! + \fn void QFileDialog::filterSelected(const QString &filter) + \since 4.3 + + This signal is emitted when the user selects a \a filter. +*/ + +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) +bool Q_GUI_EXPORT qt_use_native_dialogs = true; // for the benefit of testing tools, until we have a proper API +#endif + +QT_BEGIN_INCLUDE_NAMESPACE +#ifdef Q_WS_WIN +#include +#endif +#include +#ifdef Q_WS_MAC +#include +#endif +QT_END_INCLUDE_NAMESPACE + +/*! + \fn QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags flags) + + Constructs a file dialog with the given \a parent and widget \a flags. +*/ +QFileDialog::QFileDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(*new QFileDialogPrivate, parent, f) +{ + Q_D(QFileDialog); + d->init(); + d->lineEdit()->selectAll(); +} + +/*! + Constructs a file dialog with the given \a parent and \a caption that + initially displays the contents of the specified \a directory. + The contents of the directory are filtered before being shown in the + dialog, using a semicolon-separated list of filters specified by + \a filter. +*/ +QFileDialog::QFileDialog(QWidget *parent, + const QString &caption, + const QString &directory, + const QString &filter) + : QDialog(*new QFileDialogPrivate, parent, 0) +{ + Q_D(QFileDialog); + d->init(directory, filter, caption); + d->lineEdit()->selectAll(); +} + +/*! + \internal +*/ +QFileDialog::QFileDialog(const QFileDialogArgs &args) + : QDialog(*new QFileDialogPrivate, args.parent, 0) +{ + Q_D(QFileDialog); + d->init(args.directory, args.filter, args.caption); + setFileMode(args.mode); + setOptions(args.options); + selectFile(args.selection); + d->lineEdit()->selectAll(); +} + +/*! + Destroys the file dialog. +*/ +QFileDialog::~QFileDialog() +{ + Q_D(QFileDialog); +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + settings.setValue(QLatin1String("filedialog"), saveState()); +#endif + d->deleteNativeDialog_sys(); +} + +/*! + \since 4.3 + Sets the \a urls that are located in the sidebar. + + For instance: + + \snippet doc/src/snippets/filedialogurls.cpp 0 + + The file dialog will then look like this: + + \image filedialogurls.png + + \sa sidebarUrls() +*/ +void QFileDialog::setSidebarUrls(const QList &urls) +{ + Q_D(QFileDialog); + d->qFileDialogUi->sidebar->setUrls(urls); +} + +/*! + \since 4.3 + Returns a list of urls that are currently in the sidebar +*/ +QList QFileDialog::sidebarUrls() const +{ + Q_D(const QFileDialog); + return d->qFileDialogUi->sidebar->urls(); +} + +static const qint32 QFileDialogMagic = 0xbe; + +const char *qt_file_dialog_filter_reg_exp = +"^(.*)\\(([a-zA-Z0-9_.*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; + +/*! + \since 4.3 + Saves the state of the dialog's layout, history and current directory. + + Typically this is used in conjunction with QSettings to remember the size + for a future session. A version number is stored as part of the data. +*/ +QByteArray QFileDialog::saveState() const +{ + Q_D(const QFileDialog); + int version = 3; + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + + stream << qint32(QFileDialogMagic); + stream << qint32(version); + stream << d->qFileDialogUi->splitter->saveState(); + stream << d->qFileDialogUi->sidebar->urls(); + stream << history(); + stream << *lastVisitedDir(); + stream << d->qFileDialogUi->treeView->header()->saveState(); + stream << qint32(viewMode()); + return data; +} + +/*! + \since 4.3 + Restores the dialogs's layout, history and current directory to the \a state specified. + + Typically this is used in conjunction with QSettings to restore the size + from a past session. + + Returns false if there are errors +*/ +bool QFileDialog::restoreState(const QByteArray &state) +{ + Q_D(QFileDialog); + int version = 3; + QByteArray sd = state; + QDataStream stream(&sd, QIODevice::ReadOnly); + if (stream.atEnd()) + return false; + QByteArray splitterState; + QByteArray headerData; + QList bookmarks; + QStringList history; + QString currentDirectory; + qint32 marker; + qint32 v; + qint32 viewMode; + stream >> marker; + stream >> v; + if (marker != QFileDialogMagic || v != version) + return false; + + stream >> splitterState + >> bookmarks + >> history + >> currentDirectory + >> headerData + >> viewMode; + + if (!d->qFileDialogUi->splitter->restoreState(splitterState)) + return false; + QList list = d->qFileDialogUi->splitter->sizes(); + if (list.count() >= 2 && list.at(0) == 0 && list.at(1) == 0) { + for (int i = 0; i < list.count(); ++i) + list[i] = d->qFileDialogUi->splitter->widget(i)->sizeHint().width(); + d->qFileDialogUi->splitter->setSizes(list); + } + + d->qFileDialogUi->sidebar->setUrls(bookmarks); + while (history.count() > 5) + history.pop_front(); + setHistory(history); + setDirectory(lastVisitedDir()->isEmpty() ? currentDirectory : *lastVisitedDir()); + if (!d->qFileDialogUi->treeView->header()->restoreState(headerData)) + return false; + + setViewMode(ViewMode(viewMode)); + return true; +} + +/*! + \reimp +*/ +void QFileDialog::changeEvent(QEvent *e) +{ + Q_D(QFileDialog); + if (e->type() == QEvent::LanguageChange) { + d->retranslateWindowTitle(); + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +QFileDialogPrivate::QFileDialogPrivate() + : +#ifndef QT_NO_PROXYMODEL + proxyModel(0), +#endif + model(0), + fileMode(QFileDialog::AnyFile), + acceptMode(QFileDialog::AcceptOpen), + currentHistoryLocation(-1), + renameAction(0), + deleteAction(0), + showHiddenAction(0), + useDefaultCaption(true), + defaultFileTypes(true), + fileNameLabelExplicitlySat(false), + nativeDialogInUse(false), +#ifdef Q_WS_MAC + mDelegate(0), +#ifndef QT_MAC_USE_COCOA + mDialog(0), + mDialogStarted(false), + mDialogClosed(true), +#endif +#endif + qFileDialogUi(0) +{ +} + +QFileDialogPrivate::~QFileDialogPrivate() +{ +} + +void QFileDialogPrivate::retranslateWindowTitle() +{ + Q_Q(QFileDialog); + if (!useDefaultCaption || setWindowTitle != q->windowTitle()) + return; + if (acceptMode == QFileDialog::AcceptOpen) { + if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) + q->setWindowTitle(QFileDialog::tr("Find Directory")); + else + q->setWindowTitle(QFileDialog::tr("Open")); + } else + q->setWindowTitle(QFileDialog::tr("Save As")); + + setWindowTitle = q->windowTitle(); +} + +void QFileDialogPrivate::setLastVisitedDirectory(const QString &dir) +{ + *lastVisitedDir() = dir; +} + +void QFileDialogPrivate::retranslateStrings() +{ + Q_Q(QFileDialog); + /* WIDGETS */ + if (defaultFileTypes) + q->setNameFilter(QFileDialog::tr("All Files (*)")); + + QList actions = qFileDialogUi->treeView->header()->actions(); + QAbstractItemModel *abstractModel = model; +#ifndef QT_NO_PROXYMODEL + if (proxyModel) + abstractModel = proxyModel; +#endif + int total = qMin(abstractModel->columnCount(QModelIndex()), actions.count() + 1); + for (int i = 1; i < total; ++i) { + actions.at(i - 1)->setText(QFileDialog::tr("Show ") + abstractModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()); + } + + /* MENU ACTIONS */ + renameAction->setText(QFileDialog::tr("&Rename")); + deleteAction->setText(QFileDialog::tr("&Delete")); + showHiddenAction->setText(QFileDialog::tr("Show &hidden files")); + newFolderAction->setText(QFileDialog::tr("&New Folder")); + qFileDialogUi->retranslateUi(q); + + if (!fileNameLabelExplicitlySat){ + if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) { + q->setLabelText(QFileDialog::FileName, QFileDialog::tr("Directory:")); + } else { + q->setLabelText(QFileDialog::FileName, QFileDialog::tr("File &name:")); + } + fileNameLabelExplicitlySat = false; + } +} + +void QFileDialogPrivate::emitFilesSelected(const QStringList &files) +{ + Q_Q(QFileDialog); + emit q->filesSelected(files); + if (files.count() == 1) + emit q->fileSelected(files.first()); +} + +bool QFileDialogPrivate::canBeNativeDialog() +{ + Q_Q(QFileDialog); + if (nativeDialogInUse) + return true; + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return false; + if (opts & QFileDialog::DontUseNativeDialog) + return false; + + QLatin1String staticName(QFileDialog::staticMetaObject.className()); + QLatin1String dynamicName(q->metaObject()->className()); + return (staticName == dynamicName); +} + +/*! + \since 4.5 + Sets the given \a option to be enabled if \a on is true; otherwise, + clears the given \a option. + + \sa options, testOption() +*/ +void QFileDialog::setOption(Option option, bool on) +{ + Q_D(QFileDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + \since 4.5 + + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QFileDialog::testOption(Option option) const +{ + Q_D(const QFileDialog); + return (d->opts & option) != 0; +} + +/*! + \property QFileDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QFileDialog::setOptions(Options options) +{ + Q_D(QFileDialog); + + Options changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + if (changed & DontResolveSymlinks) + d->model->setResolveSymlinks(!(options & DontResolveSymlinks)); + if (changed & ReadOnly) { + bool ro = (options & ReadOnly); + d->model->setReadOnly(ro); + d->qFileDialogUi->newFolderButton->setEnabled(!ro); + d->renameAction->setEnabled(!ro); + d->deleteAction->setEnabled(!ro); + } + if (changed & HideNameFilterDetails) + setNameFilters(d->nameFilters); + + if (changed & ShowDirsOnly) + setFilter((options & ShowDirsOnly) ? filter() & ~QDir::Files : filter() | QDir::Files); +} + +QFileDialog::Options QFileDialog::options() const +{ + Q_D(const QFileDialog); + return d->opts; +} + +/*! + \overload + + \since 4.5 + + This function connects one of its signals to the slot specified by \a receiver + and \a member. The specific signal depends is filesSelected() if fileMode is + ExistingFiles and fileSelected() if fileMode is anything else. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QFileDialog::open(QObject *receiver, const char *member) +{ + Q_D(QFileDialog); + const char *signal = (fileMode() == ExistingFiles) ? SIGNAL(filesSelected(QStringList)) + : SIGNAL(fileSelected(QString)); + connect(this, signal, receiver, member); + d->signalToDisconnectOnClose = signal; + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + + QDialog::open(); +} + + +/*! + \reimp +*/ +void QFileDialog::setVisible(bool visible) +{ + Q_D(QFileDialog); + if (visible){ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; + + if (d->canBeNativeDialog()){ + if (d->setVisible_sys(visible)){ + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); +#ifndef QT_NO_FSCOMPLETER + //So the completer don't try to complete and therefore to show a popup + d->completer->setModel(0); +#endif + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); +#ifndef QT_NO_FSCOMPLETER + if (d->proxyModel != 0) + d->completer->setModel(d->proxyModel); + else + d->completer->setModel(d->model); +#endif + } + } + + if (!d->nativeDialogInUse) + d->qFileDialogUi->fileNameEdit->setFocus(); + + QDialog::setVisible(visible); +} + +/*! + \internal + set the directory to url +*/ +void QFileDialogPrivate::_q_goToUrl(const QUrl &url) +{ + //The shortcut in the side bar may have a parent that is not fetched yet (e.g. an hidden file) + //so we force the fetching + QFileSystemModelPrivate::QFileSystemNode *node = model->d_func()->node(url.toLocalFile(), true); + QModelIndex idx = model->d_func()->index(node); + _q_enterDirectory(idx); +} + +/*! + \fn void QFileDialog::setDirectory(const QDir &directory) + + \overload +*/ + +/*! + Sets the file dialog's current \a directory. +*/ +void QFileDialog::setDirectory(const QString &directory) +{ + Q_D(QFileDialog); + QString newDirectory = directory; + QFileInfo info(directory); + //we remove .. and . from the given path if exist + if (!directory.isEmpty()) + newDirectory = QDir::cleanPath(directory); + + if (!directory.isEmpty() && newDirectory.isEmpty()) + return; + + d->setLastVisitedDirectory(newDirectory); + + if (d->nativeDialogInUse){ + d->setDirectory_sys(newDirectory); + return; + } + if (d->rootPath() == newDirectory) + return; + QModelIndex root = d->model->setRootPath(newDirectory); + d->qFileDialogUi->newFolderButton->setEnabled(d->model->flags(root) & Qt::ItemIsDropEnabled); + if (root != d->rootIndex()) { +#ifndef QT_NO_FSCOMPLETER + if (directory.endsWith(QLatin1Char('/'))) + d->completer->setCompletionPrefix(newDirectory); + else + d->completer->setCompletionPrefix(newDirectory + QLatin1Char('/')); +#endif + d->setRootIndex(root); + } + d->qFileDialogUi->listView->selectionModel()->clear(); +} + +/*! + Returns the directory currently being displayed in the dialog. +*/ +QDir QFileDialog::directory() const +{ + Q_D(const QFileDialog); + return QDir(d->nativeDialogInUse ? d->directory_sys() : d->rootPath()); +} + +/*! + Selects the given \a filename in the file dialog. + + \sa selectedFiles() +*/ +void QFileDialog::selectFile(const QString &filename) +{ + Q_D(QFileDialog); + if (filename.isEmpty()) + return; + + if (d->nativeDialogInUse){ + d->selectFile_sys(filename); + return; + } + + if (!QDir::isRelativePath(filename)) { + QFileInfo info(filename); + QString filenamePath = info.absoluteDir().path(); + + if (d->model->rootPath() != filenamePath) + setDirectory(filenamePath); + } + + QModelIndex index = d->model->index(filename); + QString file; + if (!index.isValid()) { + // save as dialog where we want to input a default value + QString text = filename; + if (QFileInfo(filename).isAbsolute()) { + QString current = d->rootPath(); + text.remove(current); + if (text.at(0) == QDir::separator() +#ifdef Q_OS_WIN + //On Windows both cases can happen + || text.at(0) == QLatin1Char('/') +#endif + ) + text = text.remove(0,1); + } + file = text; + } else { + file = index.data().toString(); + } + d->qFileDialogUi->listView->selectionModel()->clear(); + if (!isVisible() || !d->lineEdit()->hasFocus()) + d->lineEdit()->setText(file); +} + +/** + Returns the text in the line edit which can be one or more file names + */ +QStringList QFileDialogPrivate::typedFiles() const +{ + QStringList files; + QString editText = lineEdit()->text(); + if (!editText.contains(QLatin1Char('"'))) + files << editText; + else { + // " is used to separate files like so: "file1" "file2" "file3" ... + // ### need escape character for filenames with quotes (") + QStringList tokens = editText.split(QLatin1Char('\"')); + for (int i=0; inativeDialogInUse) + return d->addDefaultSuffixToFiles(d->selectedFiles_sys()); + + QModelIndexList indexes = d->qFileDialogUi->listView->selectionModel()->selectedRows(); + QStringList files; + for (int i = 0; i < indexes.count(); ++i) + files.append(indexes.at(i).data(QFileSystemModel::FilePathRole).toString()); + + if (files.isEmpty() && !d->lineEdit()->text().isEmpty()) + files = d->typedFiles(); + + if (files.isEmpty() && !(d->fileMode == ExistingFile || d->fileMode == ExistingFiles)) + files.append(d->rootIndex().data(QFileSystemModel::FilePathRole).toString()); + return files; +} + +/* + Makes a list of filters from ;;-separated text. + Used by the mac and windows implementations +*/ +QStringList qt_make_filter_list(const QString &filter) +{ + QString f(filter); + + if (f.isEmpty()) + return QStringList(); + + QString sep(QLatin1String(";;")); + int i = f.indexOf(sep, 0); + if (i == -1) { + if (f.indexOf(QLatin1Char('\n'), 0) != -1) { + sep = QLatin1Char('\n'); + i = f.indexOf(sep, 0); + } + } + + return f.split(sep); +} + +/*! + \since 4.4 + + Sets the filter used in the file dialog to the given \a filter. + + If \a filter contains a pair of parentheses containing one or more + of \bold{anything*something}, separated by spaces, then only the + text contained in the parentheses is used as the filter. This means + that these calls are all equivalent: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 6 + + \sa setNameFilters() +*/ +void QFileDialog::setNameFilter(const QString &filter) +{ + setNameFilters(qt_make_filter_list(filter)); +} + +/*! + \obsolete + + Use setNameFilter() instead. +*/ +void QFileDialog::setFilter(const QString &filter) +{ + setNameFilter(filter); +} + +/*! + \property QFileDialog::nameFilterDetailsVisible + \obsolete + \brief This property holds whether the filter details is shown or not. + \since 4.4 + + When this property is true (the default), the filter details are shown + in the combo box. When the property is set to false, these are hidden. + + Use setOption(HideNameFilterDetails, !\e enabled) or + !testOption(HideNameFilterDetails). +*/ +void QFileDialog::setNameFilterDetailsVisible(bool enabled) +{ + setOption(HideNameFilterDetails, !enabled); +} + +bool QFileDialog::isNameFilterDetailsVisible() const +{ + return !testOption(HideNameFilterDetails); +} + + +/* + Strip the filters by removing the details, e.g. (*.*). +*/ +QStringList qt_strip_filters(const QStringList &filters) +{ + QStringList strippedFilters; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + for (int i = 0; i < filters.count(); ++i) { + QString filterName; + int index = r.indexIn(filters[i]); + if (index >= 0) + filterName = r.cap(1); + strippedFilters.append(filterName.simplified()); + } + return strippedFilters; +} + + +/*! + \since 4.4 + + Sets the \a filters used in the file dialog. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 7 +*/ +void QFileDialog::setNameFilters(const QStringList &filters) +{ + Q_D(QFileDialog); + d->defaultFileTypes = (filters == QStringList(QFileDialog::tr("All Files (*)"))); + QStringList cleanedFilters; + for (int i = 0; i < filters.count(); ++i) { + cleanedFilters << filters[i].simplified(); + } + d->nameFilters = cleanedFilters; + + if (d->nativeDialogInUse){ + d->setNameFilters_sys(cleanedFilters); + return; + } + + d->qFileDialogUi->fileTypeCombo->clear(); + if (cleanedFilters.isEmpty()) + return; + + if (testOption(HideNameFilterDetails)) + d->qFileDialogUi->fileTypeCombo->addItems(qt_strip_filters(cleanedFilters)); + else + d->qFileDialogUi->fileTypeCombo->addItems(cleanedFilters); + + d->_q_useNameFilter(0); +} + +/*! + \obsolete + + Use setNameFilters() instead. +*/ +void QFileDialog::setFilters(const QStringList &filters) +{ + setNameFilters(filters); +} + +/*! + \since 4.4 + + Returns the file type filters that are in operation on this file + dialog. +*/ +QStringList QFileDialog::nameFilters() const +{ + return d_func()->nameFilters; +} + +/*! + \obsolete + + Use nameFilters() instead. +*/ + +QStringList QFileDialog::filters() const +{ + return nameFilters(); +} + +/*! + \since 4.4 + + Sets the current file type \a filter. Multiple filters can be + passed in \a filter by separating them with semicolons or spaces. + + \sa setNameFilter(), setNameFilters(), selectedNameFilter() +*/ +void QFileDialog::selectNameFilter(const QString &filter) +{ + Q_D(QFileDialog); + if (d->nativeDialogInUse) { + d->selectNameFilter_sys(filter); + return; + } + int i; + if (testOption(HideNameFilterDetails)) { + i = d->qFileDialogUi->fileTypeCombo->findText(qt_strip_filters(qt_make_filter_list(filter)).first()); + } else { + i = d->qFileDialogUi->fileTypeCombo->findText(filter); + } + if (i >= 0) { + d->qFileDialogUi->fileTypeCombo->setCurrentIndex(i); + d->_q_useNameFilter(d->qFileDialogUi->fileTypeCombo->currentIndex()); + } +} + +/*! + \obsolete + + Use selectNameFilter() instead. +*/ + +void QFileDialog::selectFilter(const QString &filter) +{ + selectNameFilter(filter); +} + +/*! + \since 4.4 + + Returns the filter that the user selected in the file dialog. + + \sa selectedFiles() +*/ +QString QFileDialog::selectedNameFilter() const +{ + Q_D(const QFileDialog); + if (d->nativeDialogInUse) + return d->selectedNameFilter_sys(); + + return d->qFileDialogUi->fileTypeCombo->currentText(); +} + +/*! + \obsolete + + Use selectedNameFilter() instead. +*/ +QString QFileDialog::selectedFilter() const +{ + return selectedNameFilter(); +} + +/*! + \since 4.4 + + Returns the filter that is used when displaying files. + + \sa setFilter() +*/ +QDir::Filters QFileDialog::filter() const +{ + Q_D(const QFileDialog); + return d->model->filter(); +} + +/*! + \since 4.4 + + Sets the filter used by the model to \a filters. The filter is used + to specify the kind of files that should be shown. + + \sa filter() +*/ + +void QFileDialog::setFilter(QDir::Filters filters) +{ + Q_D(QFileDialog); + d->model->setFilter(filters); + if (d->nativeDialogInUse){ + d->setFilter_sys(); + return; + } + + d->showHiddenAction->setChecked((filters & QDir::Hidden)); +} + +/*! + \property QFileDialog::viewMode + \brief the way files and directories are displayed in the dialog + + By default, the \c Detail mode is used to display information about + files and directories. + + \sa ViewMode +*/ +void QFileDialog::setViewMode(QFileDialog::ViewMode mode) +{ + Q_D(QFileDialog); + if (mode == Detail) + d->_q_showDetailsView(); + else + d->_q_showListView(); +} + +QFileDialog::ViewMode QFileDialog::viewMode() const +{ + Q_D(const QFileDialog); + return (d->qFileDialogUi->stackedWidget->currentWidget() == d->qFileDialogUi->listView->parent() ? QFileDialog::List : QFileDialog::Detail); +} + +/*! + \property QFileDialog::fileMode + \brief the file mode of the dialog + + The file mode defines the number and type of items that the user is + expected to select in the dialog. + + By default, this property is set to AnyFile. + + This function will set the labels for the FileName and + \l{QFileDialog::}{Accept} \l{DialogLabel}s. It is possible to set + custom text after the call to setFileMode(). + + \sa FileMode +*/ +void QFileDialog::setFileMode(QFileDialog::FileMode mode) +{ + Q_D(QFileDialog); + d->fileMode = mode; + d->retranslateWindowTitle(); + + // keep ShowDirsOnly option in sync with fileMode (BTW, DirectoryOnly is obsolete) + setOption(ShowDirsOnly, mode == DirectoryOnly); + + // set selection mode and behavior + QAbstractItemView::SelectionMode selectionMode; + if (mode == QFileDialog::ExistingFiles) + selectionMode = QAbstractItemView::ExtendedSelection; + else + selectionMode = QAbstractItemView::SingleSelection; + d->qFileDialogUi->listView->setSelectionMode(selectionMode); + d->qFileDialogUi->treeView->setSelectionMode(selectionMode); + // set filter + d->model->setFilter(d->filterForMode(filter())); + // setup file type for directory + QString buttonText = (d->acceptMode == AcceptOpen ? tr("&Open") : tr("&Save")); + if (mode == DirectoryOnly || mode == Directory) { + d->qFileDialogUi->fileTypeCombo->clear(); + d->qFileDialogUi->fileTypeCombo->addItem(tr("Directories")); + d->qFileDialogUi->fileTypeCombo->setEnabled(false); + + if (!d->fileNameLabelExplicitlySat){ + setLabelText(FileName, tr("Directory:")); + d->fileNameLabelExplicitlySat = false; + } + buttonText = tr("&Choose"); + } else { + if (!d->fileNameLabelExplicitlySat){ + setLabelText(FileName, tr("File &name:")); + d->fileNameLabelExplicitlySat = false; + } + } + setLabelText(Accept, buttonText); + if (d->nativeDialogInUse){ + d->setFilter_sys(); + return; + } + + d->qFileDialogUi->fileTypeCombo->setEnabled(!testOption(ShowDirsOnly)); + d->_q_updateOkButton(); +} + +QFileDialog::FileMode QFileDialog::fileMode() const +{ + Q_D(const QFileDialog); + return d->fileMode; +} + +/*! + \property QFileDialog::acceptMode + \brief the accept mode of the dialog + + The action mode defines whether the dialog is for opening or saving files. + + By default, this property is set to \l{AcceptOpen}. + + \sa AcceptMode +*/ +void QFileDialog::setAcceptMode(QFileDialog::AcceptMode mode) +{ + Q_D(QFileDialog); + d->acceptMode = mode; + bool directoryMode = (d->fileMode == Directory || d->fileMode == DirectoryOnly); + QDialogButtonBox::StandardButton button = (mode == AcceptOpen ? QDialogButtonBox::Open : QDialogButtonBox::Save); + d->qFileDialogUi->buttonBox->setStandardButtons(button | QDialogButtonBox::Cancel); + d->qFileDialogUi->buttonBox->button(button)->setEnabled(false); + d->_q_updateOkButton(); + if (mode == AcceptOpen && directoryMode) + setLabelText(Accept, tr("&Choose")); + else + setLabelText(Accept, (mode == AcceptOpen ? tr("&Open") : tr("&Save"))); + if (mode == AcceptSave) { + d->qFileDialogUi->lookInCombo->setEditable(false); + } + d->retranslateWindowTitle(); +#if defined(Q_WS_MAC) + d->deleteNativeDialog_sys(); + setAttribute(Qt::WA_DontShowOnScreen, false); +#endif +} + +/* + Returns the file system model index that is the root index in the + views +*/ +QModelIndex QFileDialogPrivate::rootIndex() const { + return mapToSource(qFileDialogUi->listView->rootIndex()); +} + +QAbstractItemView *QFileDialogPrivate::currentView() const { + if (!qFileDialogUi->stackedWidget) + return 0; + if (qFileDialogUi->stackedWidget->currentWidget() == qFileDialogUi->listView->parent()) + return qFileDialogUi->listView; + return qFileDialogUi->treeView; +} + +QLineEdit *QFileDialogPrivate::lineEdit() const { + return (QLineEdit*)qFileDialogUi->fileNameEdit; +} + +/* + Sets the view root index to be the file system model index +*/ +void QFileDialogPrivate::setRootIndex(const QModelIndex &index) const { + Q_ASSERT(index.isValid() ? index.model() == model : true); + QModelIndex idx = mapFromSource(index); + qFileDialogUi->treeView->setRootIndex(idx); + qFileDialogUi->listView->setRootIndex(idx); +} +/* + Select a file system model index + returns the index that was selected (or not depending upon sortfilterproxymodel) +*/ +QModelIndex QFileDialogPrivate::select(const QModelIndex &index) const { + Q_ASSERT(index.isValid() ? index.model() == model : true); + + QModelIndex idx = mapFromSource(index); + if (idx.isValid() && !qFileDialogUi->listView->selectionModel()->isSelected(idx)) + qFileDialogUi->listView->selectionModel()->select(idx, + QItemSelectionModel::Select | QItemSelectionModel::Rows); + return idx; +} + +QFileDialog::AcceptMode QFileDialog::acceptMode() const +{ + Q_D(const QFileDialog); + return d->acceptMode; +} + +/*! + \property QFileDialog::readOnly + \obsolete + \brief Whether the filedialog is read-only + + If this property is set to false, the file dialog will allow renaming, + and deleting of files and directories and creating directories. + + Use setOption(ReadOnly, \e enabled) or testOption(ReadOnly) instead. +*/ +void QFileDialog::setReadOnly(bool enabled) +{ + setOption(ReadOnly, enabled); +} + +bool QFileDialog::isReadOnly() const +{ + return testOption(ReadOnly); +} + +/*! + \property QFileDialog::resolveSymlinks + \obsolete + \brief whether the filedialog should resolve shortcuts + + If this property is set to true, the file dialog will resolve + shortcuts or symbolic links. + + Use setOption(DontResolveSymlinks, !\a enabled) or + !testOption(DontResolveSymlinks). +*/ +void QFileDialog::setResolveSymlinks(bool enabled) +{ + setOption(DontResolveSymlinks, !enabled); +} + +bool QFileDialog::resolveSymlinks() const +{ + return !testOption(DontResolveSymlinks); +} + +/*! + \property QFileDialog::confirmOverwrite + \obsolete + \brief whether the filedialog should ask before accepting a selected file, + when the accept mode is AcceptSave + + Use setOption(DontConfirmOverwrite, !\e enabled) or + !testOption(DontConfirmOverwrite) instead. +*/ +void QFileDialog::setConfirmOverwrite(bool enabled) +{ + setOption(DontConfirmOverwrite, !enabled); +} + +bool QFileDialog::confirmOverwrite() const +{ + return !testOption(DontConfirmOverwrite); +} + +/*! + \property QFileDialog::defaultSuffix + \brief suffix added to the filename if no other suffix was specified + + This property specifies a string that will be added to the + filename if it has no suffix already. The suffix is typically + used to indicate the file type (e.g. "txt" indicates a text + file). +*/ +void QFileDialog::setDefaultSuffix(const QString &suffix) +{ + Q_D(QFileDialog); + d->defaultSuffix = suffix; +} + +QString QFileDialog::defaultSuffix() const +{ + Q_D(const QFileDialog); + return d->defaultSuffix; +} + +/*! + Sets the browsing history of the filedialog to contain the given + \a paths. +*/ +void QFileDialog::setHistory(const QStringList &paths) +{ + Q_D(QFileDialog); + d->qFileDialogUi->lookInCombo->setHistory(paths); +} + +void QFileDialogComboBox::setHistory(const QStringList &paths) +{ + m_history = paths; + // Only populate the first item, showPopup will populate the rest if needed + QList list; + QModelIndex idx = d_ptr->model->index(d_ptr->rootPath()); + //On windows the popup display the "C:\", convert to nativeSeparators + QUrl url = QUrl::fromLocalFile(QDir::toNativeSeparators(idx.data(QFileSystemModel::FilePathRole).toString())); + if (url.isValid()) + list.append(url); + urlModel->setUrls(list); +} + +/*! + Returns the browsing history of the filedialog as a list of paths. +*/ +QStringList QFileDialog::history() const +{ + Q_D(const QFileDialog); + QStringList currentHistory = d->qFileDialogUi->lookInCombo->history(); + //On windows the popup display the "C:\", convert to nativeSeparators + QString newHistory = QDir::toNativeSeparators(d->rootIndex().data(QFileSystemModel::FilePathRole).toString()); + if (!currentHistory.contains(newHistory)) + currentHistory << newHistory; + return currentHistory; +} + +/*! + Sets the item delegate used to render items in the views in the + file dialog to the given \a delegate. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + Note that the model used is QFileSystemModel. It has custom item data roles, which is + described by the \l{QFileSystemModel::}{Roles} enum. You can use a QFileIconProvider if + you only want custom icons. + + \sa itemDelegate(), setIconProvider(), QFileSystemModel +*/ +void QFileDialog::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QFileDialog); + d->qFileDialogUi->listView->setItemDelegate(delegate); + d->qFileDialogUi->treeView->setItemDelegate(delegate); +} + +/*! + Returns the item delegate used to render the items in the views in the filedialog. +*/ +QAbstractItemDelegate *QFileDialog::itemDelegate() const +{ + Q_D(const QFileDialog); + return d->qFileDialogUi->listView->itemDelegate(); +} + +/*! + Sets the icon provider used by the filedialog to the specified \a provider. +*/ +void QFileDialog::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QFileDialog); + d->model->setIconProvider(provider); + //It forces the refresh of all entries in the side bar, then we can get new icons + d->qFileDialogUi->sidebar->setUrls(d->qFileDialogUi->sidebar->urls()); +} + +/*! + Returns the icon provider used by the filedialog. +*/ +QFileIconProvider *QFileDialog::iconProvider() const +{ + Q_D(const QFileDialog); + return d->model->iconProvider(); +} + +/*! + Sets the \a text shown in the filedialog in the specified \a label. +*/ +void QFileDialog::setLabelText(DialogLabel label, const QString &text) +{ + Q_D(QFileDialog); + QPushButton *button; + switch (label) { + case LookIn: + d->qFileDialogUi->lookInLabel->setText(text); + break; + case FileName: + d->qFileDialogUi->fileNameLabel->setText(text); + d->fileNameLabelExplicitlySat = true; + break; + case FileType: + d->qFileDialogUi->fileTypeLabel->setText(text); + break; + case Accept: + d->acceptLabel = text; + if (acceptMode() == AcceptOpen) + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Open); + else + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Save); + if (button) + button->setText(text); + break; + case Reject: + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel); + if (button) + button->setText(text); + break; + } +} + +/*! + Returns the text shown in the filedialog in the specified \a label. +*/ +QString QFileDialog::labelText(DialogLabel label) const +{ + QPushButton *button; + Q_D(const QFileDialog); + switch (label) { + case LookIn: + return d->qFileDialogUi->lookInLabel->text(); + case FileName: + return d->qFileDialogUi->fileNameLabel->text(); + case FileType: + return d->qFileDialogUi->fileTypeLabel->text(); + case Accept: + if (acceptMode() == AcceptOpen) + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Open); + else + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Save); + if (button) + return button->text(); + case Reject: + button = d->qFileDialogUi->buttonBox->button(QDialogButtonBox::Cancel); + if (button) + return button->text(); + } + return QString(); +} + +/* + For the native file dialogs +*/ + +#if defined(Q_WS_WIN) +extern QString qt_win_get_open_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QString qt_win_get_save_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QStringList qt_win_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter); + +extern QString qt_win_get_existing_directory(const QFileDialogArgs &args); +#endif + +/* + For Symbian file dialogs +*/ +#if defined(Q_WS_S60) +extern QString qtSymbianGetOpenFileName(const QString &caption, + const QString &dir, + const QString &filter); + +extern QStringList qtSymbianGetOpenFileNames(const QString &caption, + const QString &dir, + const QString &filter); + +extern QString qtSymbianGetSaveFileName(const QString &caption, + const QString &dir); + +extern QString qtSymbianGetExistingDirectory(const QString &caption, + const QString &dir); +#endif + +/*! + This is a convenience static function that returns an existing file + selected by the user. If the user presses Cancel, it returns a null string. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 8 + + The function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. Only files that match the + given \a filter are shown. The filter selected is set to \a selectedFilter. + The parameters \a dir, \a selectedFilter, and \a filter may be empty + strings. If you want multiple filters, separate them with ';;', for + example: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + The dialog's caption is set to \a caption. If \a caption is not specified + then a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks, the file dialog will treat + symlinks as regular directories. + + On Symbian^3 the parameter \a selectedFilter has no meaning and the + \a options parameter is only used to define if the native file dialog is + used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileNames(), getSaveFileName(), getExistingDirectory() +*/ +QString QFileDialog::getOpenFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_open_filename_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_open_filename_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetOpenFileName(caption, dir, filter); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = ExistingFile; + args.options = options; +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_open_file_name(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles().value(0); + } + return QString(); +} + +/*! + This is a convenience static function that will return one or more existing + files selected by the user. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 9 + + This function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. The filter is set to + \a filter so that only those files which match the filter are shown. The + filter selected is set to \a selectedFilter. The parameters \a dir, + \a selectedFilter and \a filter may be empty strings. If you need multiple + filters, separate them with ';;', for instance: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The dialog's caption is set to \a caption. If \a caption is not specified + then a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + \note If you want to iterate over the list of files, you should iterate + over a copy. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 10 + + On Symbian^3 the parameter \a selectedFilter has no meaning and the + \a options parameter is only used to define if the native file dialog is + used. On Symbian^3, this function can only return a single filename. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getSaveFileName(), getExistingDirectory() +*/ +QStringList QFileDialog::getOpenFileNames(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_open_filenames_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_open_filenames_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetOpenFileNames(caption, dir, filter); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = ExistingFiles; + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_open_file_names(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles(); + } + return QStringList(); +} + +/*! + This is a convenience static function that will return a file name selected + by the user. The file does not have to exist. + + It creates a modal file dialog with the given \a parent widget. If + \a parent is not 0, the dialog will be shown centered over the parent + widget. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 11 + + The file dialog's working directory will be set to \a dir. If \a dir + includes a file name, the file will be selected. Only files that match the + \a filter are shown. The filter selected is set to \a selectedFilter. The + parameters \a dir, \a selectedFilter, and \a filter may be empty strings. + Multiple filters are separated with ';;'. For instance: + + \code + "Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)" + \endcode + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. + + The default filter can be chosen by setting \a selectedFilter to the + desired value. + + The dialog's caption is set to \a caption. If \a caption is not specified, + a default caption will be used. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. On Mac OS X, with its native file + dialog, the filter argument is ignored. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks the file dialog will treat symlinks + as regular directories. + + On Symbian^3 the parameters \a filter and \a selectedFilter have no + meaning. The \a options parameter is only used to define if the native file + dialog is used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getOpenFileNames(), getExistingDirectory() +*/ +QString QFileDialog::getSaveFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ + if (qt_filedialog_save_filename_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_save_filename_hook(parent, caption, dir, filter, selectedFilter, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetSaveFileName(caption, dir); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.selection = QFileDialogPrivate::initialSelection(dir); + args.filter = filter; + args.mode = AnyFile; + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog)) { + return qt_win_get_save_file_name(args, &(args.directory), selectedFilter); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + dialog.setAcceptMode(AcceptSave); + if (selectedFilter) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedFilter(); + return dialog.selectedFiles().value(0); + } + + return QString(); +} + +/*! + This is a convenience static function that will return an existing + directory selected by the user. + + \snippet doc/src/snippets/code/src_gui_dialogs_qfiledialog.cpp 12 + + This function creates a modal file dialog with the given \a parent widget. + If \a parent is not 0, the dialog will be shown centered over the parent + widget. + + The dialog's working directory is set to \a dir, and the caption is set to + \a caption. Either of these may be an empty string in which case the + current directory and a default caption will be used respectively. + + The \a options argument holds various options about how to run the dialog, + see the QFileDialog::Option enum for more information on the flags you can + pass. To ensure a native file dialog, \l{QFileDialog::}{ShowDirsOnly} must + be set. + + On Windows, Mac OS X and Symbian^3, this static function will use the + native file dialog and not a QFileDialog. On Windows CE, if the device has + no native file dialog, a QFileDialog will be used. + + On Unix/X11, the normal behavior of the file dialog is to resolve and + follow symlinks. For example, if \c{/usr/tmp} is a symlink to \c{/var/tmp}, + the file dialog will change to \c{/var/tmp} after entering \c{/usr/tmp}. If + \a options includes DontResolveSymlinks, the file dialog will treat + symlinks as regular directories. + + On Windows the dialog will spin a blocking modal event loop that will not + dispatch any QTimers, and if \a parent is not 0 then it will position the + dialog just below the parent's title bar. + + On Symbian^3 the \a options parameter is only used to define if the native + file dialog is used. + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QFileDialog constructors. + + \sa getOpenFileName(), getOpenFileNames(), getSaveFileName() +*/ +QString QFileDialog::getExistingDirectory(QWidget *parent, + const QString &caption, + const QString &dir, + Options options) +{ + if (qt_filedialog_existing_directory_hook && !(options & DontUseNativeDialog)) + return qt_filedialog_existing_directory_hook(parent, caption, dir, options); +#if defined(Q_WS_S60) + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 && !(options & DontUseNativeDialog)) + return qtSymbianGetExistingDirectory(caption, dir); +#endif + QFileDialogArgs args; + args.parent = parent; + args.caption = caption; + args.directory = QFileDialogPrivate::workingDirectory(dir); + args.mode = (options & ShowDirsOnly ? DirectoryOnly : Directory); + args.options = options; + +#if defined(Q_WS_WIN) + if (qt_use_native_dialogs && !(args.options & DontUseNativeDialog) && (options & ShowDirsOnly) +#if defined(Q_WS_WINCE) + && qt_priv_ptr_valid +#endif + ) { + return qt_win_get_existing_directory(args); + } +#endif + + // create a qt dialog + QFileDialog dialog(args); + if (dialog.exec() == QDialog::Accepted) { + return dialog.selectedFiles().value(0); + } + return QString(); +} + +inline static QString _qt_get_directory(const QString &path) +{ + QFileInfo info = QFileInfo(QDir::current(), path); + if (info.exists() && info.isDir()) + return QDir::cleanPath(info.absoluteFilePath()); + info.setFile(info.absolutePath()); + if (info.exists() && info.isDir()) + return info.absoluteFilePath(); + return QString(); +} +/* + Get the initial directory path + + \sa initialSelection() + */ +QString QFileDialogPrivate::workingDirectory(const QString &path) +{ + if (!path.isEmpty()) { + QString directory = _qt_get_directory(path); + if (!directory.isEmpty()) + return directory; + } + QString directory = _qt_get_directory(*lastVisitedDir()); + if (!directory.isEmpty()) + return directory; + return QDir::currentPath(); +} + +/* + Get the initial selection given a path. The initial directory + can contain both the initial directory and initial selection + /home/user/foo.txt + + \sa workingDirectory() + */ +QString QFileDialogPrivate::initialSelection(const QString &path) +{ + if (!path.isEmpty()) { + QFileInfo info(path); + if (!info.isDir()) + return info.fileName(); + } + return QString(); +} + +/*! + \reimp +*/ +void QFileDialog::done(int result) +{ + Q_D(QFileDialog); + + QDialog::done(result); + + if (d->receiverToDisconnectOnClose) { + disconnect(this, d->signalToDisconnectOnClose, + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); + d->signalToDisconnectOnClose.clear(); +} + +/*! + \reimp +*/ +void QFileDialog::accept() +{ + Q_D(QFileDialog); + QStringList files = selectedFiles(); + if (files.isEmpty()) + return; + if (d->nativeDialogInUse){ + d->emitFilesSelected(files); + QDialog::accept(); + return; + } + + QString lineEditText = d->lineEdit()->text(); + // "hidden feature" type .. and then enter, and it will move up a dir + // special case for ".." + if (lineEditText == QLatin1String("..")) { + d->_q_navigateToParent(); + bool block = d->qFileDialogUi->fileNameEdit->blockSignals(true); + d->lineEdit()->selectAll(); + d->qFileDialogUi->fileNameEdit->blockSignals(block); + return; + } + + switch (d->fileMode) { + case DirectoryOnly: + case Directory: { + QString fn = files.first(); + QFileInfo info(fn); + if (!info.exists()) + info = QFileInfo(d->getEnvironmentVariable(fn)); + if (!info.exists()) { +#ifndef QT_NO_MESSAGEBOX + QString message = tr("%1\nDirectory not found.\nPlease verify the " + "correct directory name was given."); + QMessageBox::warning(this, windowTitle(), message.arg(info.fileName())); +#endif // QT_NO_MESSAGEBOX + return; + } + if (info.isDir()) { + d->emitFilesSelected(files); + QDialog::accept(); + } + return; + } + + case AnyFile: { + QString fn = files.first(); + QFileInfo info(fn); + if (info.isDir()) { + setDirectory(info.absoluteFilePath()); + return; + } + + if (!info.exists()) { + int maxNameLength = d->maxNameLength(info.path()); + if (maxNameLength >= 0 && info.fileName().length() > maxNameLength) + return; + } + + // check if we have to ask for permission to overwrite the file + if (!info.exists() || !confirmOverwrite() || acceptMode() == AcceptOpen) { + d->emitFilesSelected(QStringList(fn)); + QDialog::accept(); +#ifndef QT_NO_MESSAGEBOX + } else { + if (QMessageBox::warning(this, windowTitle(), + tr("%1 already exists.\nDo you want to replace it?") + .arg(info.fileName()), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + == QMessageBox::Yes) { + d->emitFilesSelected(QStringList(fn)); + QDialog::accept(); + } +#endif + } + return; + } + + case ExistingFile: + case ExistingFiles: + for (int i = 0; i < files.count(); ++i) { + QFileInfo info(files.at(i)); + if (!info.exists()) + info = QFileInfo(d->getEnvironmentVariable(files.at(i))); + if (!info.exists()) { +#ifndef QT_NO_MESSAGEBOX + QString message = tr("%1\nFile not found.\nPlease verify the " + "correct file name was given."); + QMessageBox::warning(this, windowTitle(), message.arg(info.fileName())); +#endif // QT_NO_MESSAGEBOX + return; + } + if (info.isDir()) { + setDirectory(info.absoluteFilePath()); + d->lineEdit()->clear(); + return; + } + } + d->emitFilesSelected(files); + QDialog::accept(); + return; + } +} + +/*! + \internal + + Create widgets, layout and set default values +*/ +void QFileDialogPrivate::init(const QString &directory, const QString &nameFilter, + const QString &caption) +{ + Q_Q(QFileDialog); + if (!caption.isEmpty()) { + useDefaultCaption = false; + setWindowTitle = caption; + q->setWindowTitle(caption); + } + + createWidgets(); + createMenuActions(); + retranslateStrings(); + q->setFileMode(fileMode); + +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + if (!directory.isEmpty()) + setLastVisitedDirectory(workingDirectory(directory)); + q->restoreState(settings.value(QLatin1String("filedialog")).toByteArray()); +#endif + +#if defined(Q_EMBEDDED_SMALLSCREEN) + qFileDialogUi->lookInLabel->setVisible(false); + qFileDialogUi->fileNameLabel->setVisible(false); + qFileDialogUi->fileTypeLabel->setVisible(false); + qFileDialogUi->sidebar->hide(); +#endif + // Default case + if (!nameFilter.isEmpty()) + q->setNameFilter(nameFilter); + q->setAcceptMode(QFileDialog::AcceptOpen); + q->setDirectory(workingDirectory(directory)); + q->selectFile(initialSelection(directory)); + + _q_updateOkButton(); + q->resize(q->sizeHint()); +} + +/*! + \internal + + Create the widgets, set properties and connections +*/ +void QFileDialogPrivate::createWidgets() +{ + Q_Q(QFileDialog); + model = new QFileSystemModel(q); + model->setObjectName(QLatin1String("qt_filesystem_model")); +#ifdef Q_WS_MAC + model->setNameFilterDisables(true); +#else + model->setNameFilterDisables(false); +#endif + model->d_func()->disableRecursiveSort = true; + QFileDialog::connect(model, SIGNAL(fileRenamed(QString,QString,QString)), q, SLOT(_q_fileRenamed(QString,QString,QString))); + QFileDialog::connect(model, SIGNAL(rootPathChanged(QString)), + q, SLOT(_q_pathChanged(QString))); + QFileDialog::connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + q, SLOT(_q_rowsInserted(QModelIndex))); + model->setReadOnly(false); + + qFileDialogUi.reset(new Ui_QFileDialog()); + qFileDialogUi->setupUi(q); + + QList initialBookmarks; + initialBookmarks << QUrl::fromLocalFile(QLatin1String("")) + << QUrl::fromLocalFile(QDir::homePath()); + qFileDialogUi->sidebar->init(model, initialBookmarks); + QFileDialog::connect(qFileDialogUi->sidebar, SIGNAL(goToUrl(QUrl)), + q, SLOT(_q_goToUrl(QUrl))); + + QObject::connect(qFileDialogUi->buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + QObject::connect(qFileDialogUi->buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + + + qFileDialogUi->lookInCombo->init(this); + QObject::connect(qFileDialogUi->lookInCombo, SIGNAL(activated(QString)), q, SLOT(_q_goToDirectory(QString))); + + qFileDialogUi->lookInCombo->setInsertPolicy(QComboBox::NoInsert); + qFileDialogUi->lookInCombo->setDuplicatesEnabled(false); + + // filename + qFileDialogUi->fileNameEdit->init(this); +#ifndef QT_NO_SHORTCUT + qFileDialogUi->fileNameLabel->setBuddy(qFileDialogUi->fileNameEdit); +#endif +#ifndef QT_NO_FSCOMPLETER + completer = new QFSCompleter(model, q); + qFileDialogUi->fileNameEdit->setCompleter(completer); +#endif // QT_NO_FSCOMPLETER + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_autoCompleteFileName(QString))); + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_updateOkButton())); + + QObject::connect(qFileDialogUi->fileNameEdit, SIGNAL(returnPressed()), q, SLOT(accept())); + + // filetype + qFileDialogUi->fileTypeCombo->setDuplicatesEnabled(false); + qFileDialogUi->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + qFileDialogUi->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + QObject::connect(qFileDialogUi->fileTypeCombo, SIGNAL(activated(int)), + q, SLOT(_q_useNameFilter(int))); + QObject::connect(qFileDialogUi->fileTypeCombo, SIGNAL(activated(QString)), + q, SIGNAL(filterSelected(QString))); + + qFileDialogUi->listView->init(this); + qFileDialogUi->listView->setModel(model); + QObject::connect(qFileDialogUi->listView, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_enterDirectory(QModelIndex))); + QObject::connect(qFileDialogUi->listView, SIGNAL(customContextMenuRequested(QPoint)), + q, SLOT(_q_showContextMenu(QPoint))); +#ifndef QT_NO_SHORTCUT + QShortcut *shortcut = new QShortcut(qFileDialogUi->listView); + shortcut->setKey(QKeySequence(QLatin1String("Delete"))); + QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent())); +#endif + + qFileDialogUi->treeView->init(this); + qFileDialogUi->treeView->setModel(model); + QHeaderView *treeHeader = qFileDialogUi->treeView->header(); + QFontMetrics fm(q->font()); + treeHeader->resizeSection(0, fm.width(QLatin1String("wwwwwwwwwwwwwwwwwwwwwwwwww"))); + treeHeader->resizeSection(1, fm.width(QLatin1String("128.88 GB"))); + treeHeader->resizeSection(2, fm.width(QLatin1String("mp3Folder"))); + treeHeader->resizeSection(3, fm.width(QLatin1String("10/29/81 02:02PM"))); + treeHeader->setContextMenuPolicy(Qt::ActionsContextMenu); + + QActionGroup *showActionGroup = new QActionGroup(q); + showActionGroup->setExclusive(false); + QObject::connect(showActionGroup, SIGNAL(triggered(QAction*)), + q, SLOT(_q_showHeader(QAction*)));; + + QAbstractItemModel *abstractModel = model; +#ifndef QT_NO_PROXYMODEL + if (proxyModel) + abstractModel = proxyModel; +#endif + for (int i = 1; i < abstractModel->columnCount(QModelIndex()); ++i) { + QAction *showHeader = new QAction(showActionGroup); + showHeader->setCheckable(true); + showHeader->setChecked(true); + treeHeader->addAction(showHeader); + } + + QScopedPointer selModel(qFileDialogUi->treeView->selectionModel()); + qFileDialogUi->treeView->setSelectionModel(qFileDialogUi->listView->selectionModel()); + + QObject::connect(qFileDialogUi->treeView, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_enterDirectory(QModelIndex))); + QObject::connect(qFileDialogUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), + q, SLOT(_q_showContextMenu(QPoint))); +#ifndef QT_NO_SHORTCUT + shortcut = new QShortcut(qFileDialogUi->treeView); + shortcut->setKey(QKeySequence(QLatin1String("Delete"))); + QObject::connect(shortcut, SIGNAL(activated()), q, SLOT(_q_deleteCurrent())); +#endif + + // Selections + QItemSelectionModel *selections = qFileDialogUi->listView->selectionModel(); + QObject::connect(selections, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SLOT(_q_selectionChanged())); + QObject::connect(selections, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_currentChanged(QModelIndex))); + qFileDialogUi->splitter->setStretchFactor(qFileDialogUi->splitter->indexOf(qFileDialogUi->splitter->widget(1)), QSizePolicy::Expanding); + + createToolButtons(); +} + +void QFileDialogPrivate::_q_showHeader(QAction *action) +{ + Q_Q(QFileDialog); + QActionGroup *actionGroup = qobject_cast(q->sender()); + qFileDialogUi->treeView->header()->setSectionHidden(actionGroup->actions().indexOf(action) + 1, !action->isChecked()); +} + +#ifndef QT_NO_PROXYMODEL +/*! + \since 4.3 + + Sets the model for the views to the given \a proxyModel. This is useful if you + want to modify the underlying model; for example, to add columns, filter + data or add drives. + + Any existing proxy model will be removed, but not deleted. The file dialog + will take ownership of the \a proxyModel. + + \sa proxyModel() +*/ +void QFileDialog::setProxyModel(QAbstractProxyModel *proxyModel) +{ + Q_D(QFileDialog); + if ((!proxyModel && !d->proxyModel) + || (proxyModel == d->proxyModel)) + return; + + QModelIndex idx = d->rootIndex(); + if (d->proxyModel) { + disconnect(d->proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } else { + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } + + if (proxyModel != 0) { + proxyModel->setParent(this); + d->proxyModel = proxyModel; + proxyModel->setSourceModel(d->model); + d->qFileDialogUi->listView->setModel(d->proxyModel); + d->qFileDialogUi->treeView->setModel(d->proxyModel); +#ifndef QT_NO_FSCOMPLETER + d->completer->setModel(d->proxyModel); + d->completer->proxyModel = d->proxyModel; +#endif + connect(d->proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } else { + d->proxyModel = 0; + d->qFileDialogUi->listView->setModel(d->model); + d->qFileDialogUi->treeView->setModel(d->model); +#ifndef QT_NO_FSCOMPLETER + d->completer->setModel(d->model); + d->completer->sourceModel = d->model; + d->completer->proxyModel = 0; +#endif + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex))); + } + QScopedPointer selModel(d->qFileDialogUi->treeView->selectionModel()); + d->qFileDialogUi->treeView->setSelectionModel(d->qFileDialogUi->listView->selectionModel()); + + d->setRootIndex(idx); + + // reconnect selection + QItemSelectionModel *selections = d->qFileDialogUi->listView->selectionModel(); + QObject::connect(selections, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(_q_selectionChanged())); + QObject::connect(selections, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_currentChanged(QModelIndex))); +} + +/*! + Returns the proxy model used by the file dialog. By default no proxy is set. + + \sa setProxyModel() +*/ +QAbstractProxyModel *QFileDialog::proxyModel() const +{ + Q_D(const QFileDialog); + return d->proxyModel; +} +#endif // QT_NO_PROXYMODEL + +/*! + \internal + + Create tool buttons, set properties and connections +*/ +void QFileDialogPrivate::createToolButtons() +{ + Q_Q(QFileDialog); + qFileDialogUi->backButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowBack, 0, q)); + qFileDialogUi->backButton->setAutoRaise(true); + qFileDialogUi->backButton->setEnabled(false); + QObject::connect(qFileDialogUi->backButton, SIGNAL(clicked()), q, SLOT(_q_navigateBackward())); + + qFileDialogUi->forwardButton->setIcon(q->style()->standardIcon(QStyle::SP_ArrowForward, 0, q)); + qFileDialogUi->forwardButton->setAutoRaise(true); + qFileDialogUi->forwardButton->setEnabled(false); + QObject::connect(qFileDialogUi->forwardButton, SIGNAL(clicked()), q, SLOT(_q_navigateForward())); + + qFileDialogUi->toParentButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogToParent, 0, q)); + qFileDialogUi->toParentButton->setAutoRaise(true); + qFileDialogUi->toParentButton->setEnabled(false); + QObject::connect(qFileDialogUi->toParentButton, SIGNAL(clicked()), q, SLOT(_q_navigateToParent())); + + qFileDialogUi->listModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogListView, 0, q)); + qFileDialogUi->listModeButton->setAutoRaise(true); + qFileDialogUi->listModeButton->setDown(true); + QObject::connect(qFileDialogUi->listModeButton, SIGNAL(clicked()), q, SLOT(_q_showListView())); + + qFileDialogUi->detailModeButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogDetailedView, 0, q)); + qFileDialogUi->detailModeButton->setAutoRaise(true); + QObject::connect(qFileDialogUi->detailModeButton, SIGNAL(clicked()), q, SLOT(_q_showDetailsView())); + + QSize toolSize(qFileDialogUi->fileNameEdit->sizeHint().height(), qFileDialogUi->fileNameEdit->sizeHint().height()); + qFileDialogUi->backButton->setFixedSize(toolSize); + qFileDialogUi->listModeButton->setFixedSize(toolSize); + qFileDialogUi->detailModeButton->setFixedSize(toolSize); + qFileDialogUi->forwardButton->setFixedSize(toolSize); + qFileDialogUi->toParentButton->setFixedSize(toolSize); + + qFileDialogUi->newFolderButton->setIcon(q->style()->standardIcon(QStyle::SP_FileDialogNewFolder, 0, q)); + qFileDialogUi->newFolderButton->setFixedSize(toolSize); + qFileDialogUi->newFolderButton->setAutoRaise(true); + qFileDialogUi->newFolderButton->setEnabled(false); + QObject::connect(qFileDialogUi->newFolderButton, SIGNAL(clicked()), q, SLOT(_q_createDirectory())); +} + +/*! + \internal + + Create actions which will be used in the right click. +*/ +void QFileDialogPrivate::createMenuActions() +{ + Q_Q(QFileDialog); + + QAction *goHomeAction = new QAction(q); +#ifndef QT_NO_SHORTCUT + goHomeAction->setShortcut(Qt::CTRL + Qt::Key_H + Qt::SHIFT); +#endif + QObject::connect(goHomeAction, SIGNAL(triggered()), q, SLOT(_q_goHome())); + q->addAction(goHomeAction); + + // ### TODO add Desktop & Computer actions + + QAction *goToParent = new QAction(q); + goToParent->setObjectName(QLatin1String("qt_goto_parent_action")); +#ifndef QT_NO_SHORTCUT + goToParent->setShortcut(Qt::CTRL + Qt::UpArrow); +#endif + QObject::connect(goToParent, SIGNAL(triggered()), q, SLOT(_q_navigateToParent())); + q->addAction(goToParent); + + renameAction = new QAction(q); + renameAction->setEnabled(false); + renameAction->setObjectName(QLatin1String("qt_rename_action")); + QObject::connect(renameAction, SIGNAL(triggered()), q, SLOT(_q_renameCurrent())); + + deleteAction = new QAction(q); + deleteAction->setEnabled(false); + deleteAction->setObjectName(QLatin1String("qt_delete_action")); + QObject::connect(deleteAction, SIGNAL(triggered()), q, SLOT(_q_deleteCurrent())); + + showHiddenAction = new QAction(q); + showHiddenAction->setObjectName(QLatin1String("qt_show_hidden_action")); + showHiddenAction->setCheckable(true); + QObject::connect(showHiddenAction, SIGNAL(triggered()), q, SLOT(_q_showHidden())); + + newFolderAction = new QAction(q); + newFolderAction->setObjectName(QLatin1String("qt_new_folder_action")); + QObject::connect(newFolderAction, SIGNAL(triggered()), q, SLOT(_q_createDirectory())); +} + +void QFileDialogPrivate::_q_goHome() +{ + Q_Q(QFileDialog); + q->setDirectory(QDir::homePath()); +} + +/*! + \internal + + Update history with new path, buttons, and combo +*/ +void QFileDialogPrivate::_q_pathChanged(const QString &newPath) +{ + Q_Q(QFileDialog); + QDir dir(model->rootDirectory()); + qFileDialogUi->toParentButton->setEnabled(dir.exists()); + qFileDialogUi->sidebar->selectUrl(QUrl::fromLocalFile(newPath)); + q->setHistory(qFileDialogUi->lookInCombo->history()); + + if (currentHistoryLocation < 0 || currentHistory.value(currentHistoryLocation) != QDir::toNativeSeparators(newPath)) { + while (currentHistoryLocation >= 0 && currentHistoryLocation + 1 < currentHistory.count()) { + currentHistory.removeLast(); + } + currentHistory.append(QDir::toNativeSeparators(newPath)); + ++currentHistoryLocation; + } + qFileDialogUi->forwardButton->setEnabled(currentHistory.size() - currentHistoryLocation > 1); + qFileDialogUi->backButton->setEnabled(currentHistoryLocation > 0); +} + +/*! + \internal + + Navigates to the last directory viewed in the dialog. +*/ +void QFileDialogPrivate::_q_navigateBackward() +{ + Q_Q(QFileDialog); + if (!currentHistory.isEmpty() && currentHistoryLocation > 0) { + --currentHistoryLocation; + QString previousHistory = currentHistory.at(currentHistoryLocation); + q->setDirectory(previousHistory); + } +} + +/*! + \internal + + Navigates to the last directory viewed in the dialog. +*/ +void QFileDialogPrivate::_q_navigateForward() +{ + Q_Q(QFileDialog); + if (!currentHistory.isEmpty() && currentHistoryLocation < currentHistory.size() - 1) { + ++currentHistoryLocation; + QString nextHistory = currentHistory.at(currentHistoryLocation); + q->setDirectory(nextHistory); + } +} + +/*! + \internal + + Navigates to the parent directory of the currently displayed directory + in the dialog. +*/ +void QFileDialogPrivate::_q_navigateToParent() +{ + Q_Q(QFileDialog); + QDir dir(model->rootDirectory()); + QString newDirectory; + if (dir.isRoot()) { + newDirectory = model->myComputer().toString(); + } else { + dir.cdUp(); + newDirectory = dir.absolutePath(); + } + q->setDirectory(newDirectory); + emit q->directoryEntered(newDirectory); +} + +/*! + \internal + + Creates a new directory, first asking the user for a suitable name. +*/ +void QFileDialogPrivate::_q_createDirectory() +{ + Q_Q(QFileDialog); + qFileDialogUi->listView->clearSelection(); + + QString newFolderString = QFileDialog::tr("New Folder"); + QString folderName = newFolderString; + QString prefix = q->directory().absolutePath() + QDir::separator(); + if (QFile::exists(prefix + folderName)) { + qlonglong suffix = 2; + while (QFile::exists(prefix + folderName)) { + folderName = newFolderString + QString::number(suffix++); + } + } + + QModelIndex parent = rootIndex(); + QModelIndex index = model->mkdir(parent, folderName); + if (!index.isValid()) + return; + + index = select(index); + if (index.isValid()) { + qFileDialogUi->treeView->setCurrentIndex(index); + currentView()->edit(index); + } +} + +void QFileDialogPrivate::_q_showListView() +{ + qFileDialogUi->listModeButton->setDown(true); + qFileDialogUi->detailModeButton->setDown(false); + qFileDialogUi->treeView->hide(); + qFileDialogUi->listView->show(); + qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->listView->parentWidget()); + qFileDialogUi->listView->doItemsLayout(); +} + +void QFileDialogPrivate::_q_showDetailsView() +{ + qFileDialogUi->listModeButton->setDown(false); + qFileDialogUi->detailModeButton->setDown(true); + qFileDialogUi->listView->hide(); + qFileDialogUi->treeView->show(); + qFileDialogUi->stackedWidget->setCurrentWidget(qFileDialogUi->treeView->parentWidget()); + qFileDialogUi->treeView->doItemsLayout(); +} + +/*! + \internal + + Show the context menu for the file/dir under position +*/ +void QFileDialogPrivate::_q_showContextMenu(const QPoint &position) +{ +#ifdef QT_NO_MENU + Q_UNUSED(position); +#else + Q_Q(QFileDialog); + QAbstractItemView *view = 0; + if (q->viewMode() == QFileDialog::Detail) + view = qFileDialogUi->treeView; + else + view = qFileDialogUi->listView; + QModelIndex index = view->indexAt(position); + index = mapToSource(index.sibling(index.row(), 0)); + + QMenu menu(view); + if (index.isValid()) { + // file context menu + QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt()); + renameAction->setEnabled(p & QFile::WriteUser); + menu.addAction(renameAction); + deleteAction->setEnabled(p & QFile::WriteUser); + menu.addAction(deleteAction); + menu.addSeparator(); + } + menu.addAction(showHiddenAction); + if (qFileDialogUi->newFolderButton->isVisible()) { + newFolderAction->setEnabled(qFileDialogUi->newFolderButton->isEnabled()); + menu.addAction(newFolderAction); + } + menu.exec(view->viewport()->mapToGlobal(position)); +#endif // QT_NO_MENU +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_renameCurrent() +{ + Q_Q(QFileDialog); + QModelIndex index = qFileDialogUi->listView->currentIndex(); + index = index.sibling(index.row(), 0); + if (q->viewMode() == QFileDialog::List) + qFileDialogUi->listView->edit(index); + else + qFileDialogUi->treeView->edit(index); +} + +bool QFileDialogPrivate::removeDirectory(const QString &path) +{ + QModelIndex modelIndex = model->index(path); + return model->remove(modelIndex); +} + +/*! + \internal + + Deletes the currently selected item in the dialog. +*/ +void QFileDialogPrivate::_q_deleteCurrent() +{ + if (model->isReadOnly()) + return; + + QModelIndexList list = qFileDialogUi->listView->selectionModel()->selectedRows(); + for (int i = list.count() - 1; i >= 0; --i) { + QModelIndex index = list.at(i); + if (index == qFileDialogUi->listView->rootIndex()) + continue; + + index = mapToSource(index.sibling(index.row(), 0)); + if (!index.isValid()) + continue; + + QString fileName = index.data(QFileSystemModel::FileNameRole).toString(); + QString filePath = index.data(QFileSystemModel::FilePathRole).toString(); + bool isDir = model->isDir(index); + + QFile::Permissions p(index.parent().data(QFileSystemModel::FilePermissions).toInt()); +#ifndef QT_NO_MESSAGEBOX + Q_Q(QFileDialog); + if (!(p & QFile::WriteUser) && (QMessageBox::warning(q_func(), q_func()->windowTitle(), + QFileDialog::tr("'%1' is write protected.\nDo you want to delete it anyway?") + .arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No)) + return; + else if (QMessageBox::warning(q_func(), q_func()->windowTitle(), + QFileDialog::tr("Are sure you want to delete '%1'?") + .arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) + return; + +#else + if (!(p & QFile::WriteUser)) + return; +#endif // QT_NO_MESSAGEBOX + + // the event loop has run, we can NOT reuse index because the model might have removed it. + if (isDir) { + if (!removeDirectory(filePath)) { +#ifndef QT_NO_MESSAGEBOX + QMessageBox::warning(q, q->windowTitle(), + QFileDialog::tr("Could not delete directory.")); +#endif + } + } else { + model->remove(index); + } + } +} + +void QFileDialogPrivate::_q_autoCompleteFileName(const QString &text) +{ + if (text.startsWith(QLatin1String("//")) || text.startsWith(QLatin1Char('\\'))) { + qFileDialogUi->listView->selectionModel()->clearSelection(); + return; + } + + QStringList multipleFiles = typedFiles(); + if (multipleFiles.count() > 0) { + QModelIndexList oldFiles = qFileDialogUi->listView->selectionModel()->selectedRows(); + QModelIndexList newFiles; + for (int i = 0; i < multipleFiles.count(); ++i) { + QModelIndex idx = model->index(multipleFiles.at(i)); + if (oldFiles.contains(idx)) + oldFiles.removeAll(idx); + else + newFiles.append(idx); + } + for (int i = 0; i < newFiles.count(); ++i) + select(newFiles.at(i)); + if (lineEdit()->hasFocus()) + for (int i = 0; i < oldFiles.count(); ++i) + qFileDialogUi->listView->selectionModel()->select(oldFiles.at(i), + QItemSelectionModel::Toggle | QItemSelectionModel::Rows); + } +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_updateOkButton() +{ + Q_Q(QFileDialog); + QPushButton *button = qFileDialogUi->buttonBox->button((acceptMode == QFileDialog::AcceptOpen) + ? QDialogButtonBox::Open : QDialogButtonBox::Save); + if (!button) + return; + + bool enableButton = true; + bool isOpenDirectory = false; + + QStringList files = q->selectedFiles(); + QString lineEditText = lineEdit()->text(); + + if (lineEditText.startsWith(QLatin1String("//")) || lineEditText.startsWith(QLatin1Char('\\'))) { + button->setEnabled(true); + if (acceptMode == QFileDialog::AcceptSave) + button->setText(acceptLabel); + return; + } + + if (files.isEmpty()) { + enableButton = false; + } else if (lineEditText == QLatin1String("..")) { + isOpenDirectory = true; + } else { + switch (fileMode) { + case QFileDialog::DirectoryOnly: + case QFileDialog::Directory: { + QString fn = files.first(); + QModelIndex idx = model->index(fn); + if (!idx.isValid()) + idx = model->index(getEnvironmentVariable(fn)); + if (!idx.isValid() || !model->isDir(idx)) + enableButton = false; + break; + } + case QFileDialog::AnyFile: { + QString fn = files.first(); + QFileInfo info(fn); + QModelIndex idx = model->index(fn); + QString fileDir; + QString fileName; + if (info.isDir()) { + fileDir = info.canonicalFilePath(); + } else { + fileDir = fn.mid(0, fn.lastIndexOf(QLatin1Char('/'))); + fileName = fn.mid(fileDir.length() + 1); + } + if (lineEditText.contains(QLatin1String(".."))) { + fileDir = info.canonicalFilePath(); + fileName = info.fileName(); + } + + if (fileDir == q->directory().canonicalPath() && fileName.isEmpty()) { + enableButton = false; + break; + } + if (idx.isValid() && model->isDir(idx)) { + isOpenDirectory = true; + enableButton = true; + break; + } + if (!idx.isValid()) { + int maxLength = maxNameLength(fileDir); + enableButton = maxLength < 0 || fileName.length() <= maxLength; + } + break; + } + case QFileDialog::ExistingFile: + case QFileDialog::ExistingFiles: + for (int i = 0; i < files.count(); ++i) { + QModelIndex idx = model->index(files.at(i)); + if (!idx.isValid()) + idx = model->index(getEnvironmentVariable(files.at(i))); + if (!idx.isValid()) { + enableButton = false; + break; + } + if (idx.isValid() && model->isDir(idx)) { + isOpenDirectory = true; + break; + } + } + break; + default: + break; + } + } + + button->setEnabled(enableButton); + if (acceptMode == QFileDialog::AcceptSave) + button->setText(isOpenDirectory ? QFileDialog::tr("&Open") : acceptLabel); +} + +/*! + \internal +*/ +void QFileDialogPrivate::_q_currentChanged(const QModelIndex &index) +{ + _q_updateOkButton(); + emit q_func()->currentChanged(index.data(QFileSystemModel::FilePathRole).toString()); +} + +/*! + \internal + + This is called when the user double clicks on a file with the corresponding + model item \a index. +*/ +void QFileDialogPrivate::_q_enterDirectory(const QModelIndex &index) +{ + Q_Q(QFileDialog); + // My Computer or a directory + QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index; + QString path = sourceIndex.data(QFileSystemModel::FilePathRole).toString(); + if (path.isEmpty() || model->isDir(sourceIndex)) { + q->setDirectory(path); + emit q->directoryEntered(path); + if (fileMode == QFileDialog::Directory + || fileMode == QFileDialog::DirectoryOnly) { + // ### find out why you have to do both of these. + lineEdit()->setText(QString()); + lineEdit()->clear(); + } + } else { + q->accept(); + } +} + +/*! + \internal + + Changes the file dialog's current directory to the one specified + by \a path. +*/ +void QFileDialogPrivate::_q_goToDirectory(const QString &path) +{ + #ifndef QT_NO_MESSAGEBOX + Q_Q(QFileDialog); +#endif + QModelIndex index = qFileDialogUi->lookInCombo->model()->index(qFileDialogUi->lookInCombo->currentIndex(), + qFileDialogUi->lookInCombo->modelColumn(), + qFileDialogUi->lookInCombo->rootModelIndex()); + QString path2 = path; + if (!index.isValid()) + index = mapFromSource(model->index(getEnvironmentVariable(path))); + else { + path2 = index.data(UrlRole).toUrl().toLocalFile(); + index = mapFromSource(model->index(path2)); + } + QDir dir(path2); + if (!dir.exists()) + dir = getEnvironmentVariable(path2); + + if (dir.exists() || path2.isEmpty() || path2 == model->myComputer().toString()) { + _q_enterDirectory(index); +#ifndef QT_NO_MESSAGEBOX + } else { + QString message = QFileDialog::tr("%1\nDirectory not found.\nPlease verify the " + "correct directory name was given."); + QMessageBox::warning(q, q->windowTitle(), message.arg(path2)); +#endif // QT_NO_MESSAGEBOX + } +} + +// Makes a list of filters from a normal filter string "Image Files (*.png *.jpg)" +QStringList qt_clean_filter_list(const QString &filter) +{ + QRegExp regexp(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + QString f = filter; + int i = regexp.indexIn(f); + if (i >= 0) + f = regexp.cap(2); + return f.split(QLatin1Char(' '), QString::SkipEmptyParts); +} + +/*! + \internal + + Sets the current name filter to be nameFilter and + update the qFileDialogUi->fileNameEdit when in AcceptSave mode with the new extension. +*/ +void QFileDialogPrivate::_q_useNameFilter(int index) +{ + if (index == nameFilters.size()) { + QAbstractItemModel *comboModel = qFileDialogUi->fileTypeCombo->model(); + nameFilters.append(comboModel->index(comboModel->rowCount() - 1, 0).data().toString()); + } + + QString nameFilter = nameFilters.at(index); + QStringList newNameFilters = qt_clean_filter_list(nameFilter); + if (acceptMode == QFileDialog::AcceptSave) { + QString newNameFilterExtension; + if (newNameFilters.count() > 0) + newNameFilterExtension = QFileInfo(newNameFilters.at(0)).suffix(); + + QString fileName = lineEdit()->text(); + const QString fileNameExtension = QFileInfo(fileName).suffix(); + if (!fileNameExtension.isEmpty() && !newNameFilterExtension.isEmpty()) { + const int fileNameExtensionLength = fileNameExtension.count(); + fileName.replace(fileName.count() - fileNameExtensionLength, + fileNameExtensionLength, newNameFilterExtension); + qFileDialogUi->listView->clearSelection(); + lineEdit()->setText(fileName); + } + } + + model->setNameFilters(newNameFilters); +} + +/*! + \internal + + This is called when the model index corresponding to the current file is changed + from \a index to \a current. +*/ +void QFileDialogPrivate::_q_selectionChanged() +{ + QModelIndexList indexes = qFileDialogUi->listView->selectionModel()->selectedRows(); + bool stripDirs = (fileMode != QFileDialog::DirectoryOnly && fileMode != QFileDialog::Directory); + + QStringList allFiles; + for (int i = 0; i < indexes.count(); ++i) { + if (stripDirs && model->isDir(mapToSource(indexes.at(i)))) + continue; + allFiles.append(indexes.at(i).data().toString()); + } + if (allFiles.count() > 1) + for (int i = 0; i < allFiles.count(); ++i) { + allFiles.replace(i, QString(QLatin1Char('"') + allFiles.at(i) + QLatin1Char('"'))); + } + + QString finalFiles = allFiles.join(QLatin1String(" ")); + if (!finalFiles.isEmpty() && !lineEdit()->hasFocus() && lineEdit()->isVisible()) + lineEdit()->setText(finalFiles); + else + _q_updateOkButton(); +} + +/*! + \internal + + Includes hidden files and directories in the items displayed in the dialog. +*/ +void QFileDialogPrivate::_q_showHidden() +{ + Q_Q(QFileDialog); + QDir::Filters dirFilters = q->filter(); + if (showHiddenAction->isChecked()) + dirFilters |= QDir::Hidden; + else + dirFilters &= ~QDir::Hidden; + q->setFilter(dirFilters); +} + +/*! + \internal + + When parent is root and rows have been inserted when none was there before + then select the first one. +*/ +void QFileDialogPrivate::_q_rowsInserted(const QModelIndex &parent) +{ + if (!qFileDialogUi->treeView + || parent != qFileDialogUi->treeView->rootIndex() + || !qFileDialogUi->treeView->selectionModel() + || qFileDialogUi->treeView->selectionModel()->hasSelection() + || qFileDialogUi->treeView->model()->rowCount(parent) == 0) + return; +} + +void QFileDialogPrivate::_q_fileRenamed(const QString &path, const QString oldName, const QString newName) +{ + if (fileMode == QFileDialog::Directory || fileMode == QFileDialog::DirectoryOnly) { + if (path == rootPath() && lineEdit()->text() == oldName) + lineEdit()->setText(newName); + } +} + +/*! + \internal + + For the list and tree view watch keys to goto parent and back in the history + + returns true if handled +*/ +bool QFileDialogPrivate::itemViewKeyboardEvent(QKeyEvent *event) { + + Q_Q(QFileDialog); + switch (event->key()) { + case Qt::Key_Backspace: + _q_navigateToParent(); + return true; + case Qt::Key_Back: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) + return false; +#endif + case Qt::Key_Left: + if (event->key() == Qt::Key_Back || event->modifiers() == Qt::AltModifier) { + _q_navigateBackward(); + return true; + } + break; + case Qt::Key_Escape: + q->hide(); + return true; + default: + break; + } + return false; +} + +QString QFileDialogPrivate::getEnvironmentVariable(const QString &string) +{ +#ifdef Q_OS_UNIX + if (string.size() > 1 && string.startsWith(QLatin1Char('$'))) { + return QString::fromLocal8Bit(getenv(string.mid(1).toLatin1().constData())); + } +#else + if (string.size() > 2 && string.startsWith(QLatin1Char('%')) && string.endsWith(QLatin1Char('%'))) { + return QString::fromLocal8Bit(qgetenv(string.mid(1, string.size() - 2).toLatin1().constData())); + } +#endif + return string; +} + +void QFileDialogComboBox::init(QFileDialogPrivate *d_pointer) { + d_ptr = d_pointer; + urlModel = new QUrlModel(this); + urlModel->showFullPath = true; + urlModel->setFileSystemModel(d_ptr->model); + setModel(urlModel); +} + +void QFileDialogComboBox::showPopup() +{ + if (model()->rowCount() > 1) + QComboBox::showPopup(); + + urlModel->setUrls(QList()); + QList list; + QModelIndex idx = d_ptr->model->index(d_ptr->rootPath()); + while (idx.isValid()) { + QUrl url = QUrl::fromLocalFile(idx.data(QFileSystemModel::FilePathRole).toString()); + if (url.isValid()) + list.append(url); + idx = idx.parent(); + } + // add "my computer" + list.append(QUrl::fromLocalFile(QLatin1String(""))); + urlModel->addUrls(list, 0); + idx = model()->index(model()->rowCount() - 1, 0); + + // append history + QList urls; + for (int i = 0; i < m_history.count(); ++i) { + QUrl path = QUrl::fromLocalFile(m_history.at(i)); + if (!urls.contains(path)) + urls.prepend(path); + } + if (urls.count() > 0) { + model()->insertRow(model()->rowCount()); + idx = model()->index(model()->rowCount()-1, 0); + // ### TODO maybe add a horizontal line before this + model()->setData(idx, QFileDialog::tr("Recent Places")); + QStandardItemModel *m = qobject_cast(model()); + if (m) { + Qt::ItemFlags flags = m->flags(idx); + flags &= ~Qt::ItemIsEnabled; + m->item(idx.row(), idx.column())->setFlags(flags); + } + urlModel->addUrls(urls, -1, false); + } + setCurrentIndex(0); + + QComboBox::showPopup(); +} + +// Exact same as QComboBox::paintEvent(), except we elide the text. +void QFileDialogComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + + QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, this); + int size = editRect.width() - opt.iconSize.width() - 4; + opt.currentText = opt.fontMetrics.elidedText(opt.currentText, Qt::ElideMiddle, size); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +QFileDialogListView::QFileDialogListView(QWidget *parent) : QListView(parent) +{ +} + +void QFileDialogListView::init(QFileDialogPrivate *d_pointer) +{ + d_ptr = d_pointer; + setSelectionBehavior(QAbstractItemView::SelectRows); + setWrapping(true); + setResizeMode(QListView::Adjust); + setEditTriggers(QAbstractItemView::EditKeyPressed); + setContextMenuPolicy(Qt::CustomContextMenu); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::InternalMove); +#endif +} + +QSize QFileDialogListView::sizeHint() const +{ + int height = qMax(10, sizeHintForRow(0)); + return QSize(QListView::sizeHint().width() * 2, height * 30); +} + +void QFileDialogListView::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QListView::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + if (!d_ptr->itemViewKeyboardEvent(e)) + QListView::keyPressEvent(e); + e->accept(); +} + +QFileDialogTreeView::QFileDialogTreeView(QWidget *parent) : QTreeView(parent) +{ +} + +void QFileDialogTreeView::init(QFileDialogPrivate *d_pointer) +{ + d_ptr = d_pointer; + setSelectionBehavior(QAbstractItemView::SelectRows); + setRootIsDecorated(false); + setItemsExpandable(false); + setSortingEnabled(true); + header()->setSortIndicator(0, Qt::AscendingOrder); + header()->setStretchLastSection(false); + setTextElideMode(Qt::ElideMiddle); + setEditTriggers(QAbstractItemView::EditKeyPressed); + setContextMenuPolicy(Qt::CustomContextMenu); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::InternalMove); +#endif +} + +void QFileDialogTreeView::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QTreeView::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + if (!d_ptr->itemViewKeyboardEvent(e)) + QTreeView::keyPressEvent(e); + e->accept(); +} + +QSize QFileDialogTreeView::sizeHint() const +{ + int height = qMax(10, sizeHintForRow(0)); + QSize sizeHint = header()->sizeHint(); + return QSize(sizeHint.width() * 4, height * 30); +} + +/*! + // FIXME: this is a hack to avoid propagating key press events + // to the dialog and from there to the "Ok" button +*/ +void QFileDialogLineEdit::keyPressEvent(QKeyEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + QLineEdit::keyPressEvent(e); + return; + } +#endif // QT_KEYPAD_NAVIGATION + + int key = e->key(); + QLineEdit::keyPressEvent(e); + if (key != Qt::Key_Escape) + e->accept(); + if (hideOnEsc && (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter)) { + e->accept(); + hide(); + d_ptr->currentView()->setFocus(Qt::ShortcutFocusReason); + } +} + +#ifndef QT_NO_FSCOMPLETER + +QString QFSCompleter::pathFromIndex(const QModelIndex &index) const +{ + const QFileSystemModel *dirModel; + if (proxyModel) + dirModel = qobject_cast(proxyModel->sourceModel()); + else + dirModel = sourceModel; + QString currentLocation = dirModel->rootPath(); + QString path = index.data(QFileSystemModel::FilePathRole).toString(); + if (!currentLocation.isEmpty() && path.startsWith(currentLocation)) { +#if defined(Q_OS_UNIX) || defined(Q_OS_WINCE) + if (currentLocation == QDir::separator()) + return path.mid(currentLocation.length()); +#endif + if (currentLocation.endsWith(QLatin1Char('/'))) + return path.mid(currentLocation.length()); + else + return path.mid(currentLocation.length()+1); + } + return index.data(QFileSystemModel::FilePathRole).toString(); +} + +QStringList QFSCompleter::splitPath(const QString &path) const +{ + if (path.isEmpty()) + return QStringList(completionPrefix()); + + QString pathCopy = QDir::toNativeSeparators(path); + QString sep = QDir::separator(); +#if defined(Q_OS_SYMBIAN) + if (pathCopy == QLatin1String("\\")) + return QStringList(pathCopy); +#elif defined(Q_OS_WIN) + if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\")) + return QStringList(pathCopy); + QString doubleSlash(QLatin1String("\\\\")); + if (pathCopy.startsWith(doubleSlash)) + pathCopy = pathCopy.mid(2); + else + doubleSlash.clear(); +#endif + + QRegExp re(QLatin1Char('[') + QRegExp::escape(sep) + QLatin1Char(']')); + +#if defined(Q_OS_SYMBIAN) + QStringList parts = pathCopy.split(re, QString::SkipEmptyParts); + if (pathCopy.endsWith(sep)) + parts.append(QString()); +#elif defined(Q_OS_WIN) + QStringList parts = pathCopy.split(re, QString::SkipEmptyParts); + if (!doubleSlash.isEmpty() && !parts.isEmpty()) + parts[0].prepend(doubleSlash); + if (pathCopy.endsWith(sep)) + parts.append(QString()); +#else + QStringList parts = pathCopy.split(re); + if (path[0] == sep[0]) // read the "/" at the beginning as the split removed it + parts[0] = sep[0]; +#endif + +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + bool startsFromRoot = !parts.isEmpty() && parts[0].endsWith(QLatin1Char(':')); +#else + bool startsFromRoot = path[0] == sep[0]; +#endif + if (parts.count() == 1 || (parts.count() > 1 && !startsFromRoot)) { + const QFileSystemModel *dirModel; + if (proxyModel) + dirModel = qobject_cast(proxyModel->sourceModel()); + else + dirModel = sourceModel; + QString currentLocation = QDir::toNativeSeparators(dirModel->rootPath()); +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (currentLocation.endsWith(QLatin1Char(':'))) + currentLocation.append(sep); +#endif + if (currentLocation.contains(sep) && path != currentLocation) { + QStringList currentLocationList = splitPath(currentLocation); + while (!currentLocationList.isEmpty() + && parts.count() > 0 + && parts.at(0) == QLatin1String("..")) { + parts.removeFirst(); + currentLocationList.removeLast(); + } + if (!currentLocationList.isEmpty() && currentLocationList.last().isEmpty()) + currentLocationList.removeLast(); + return currentLocationList + parts; + } + } + return parts; +} + +#endif // QT_NO_COMPLETER + +#ifdef QT3_SUPPORT +/*! + Use selectedFiles() instead. + + \oldcode + QString selected = dialog->selectedFile(); + \newcode + QStringList files = dialog->selectedFiles(); + QString selected; + if (!files.isEmpty()) + selected = files[0]; + \endcode +*/ +QString QFileDialog::selectedFile() const +{ + QStringList files = selectedFiles(); + return files.size() ? files.at(0) : QString(); +} + +/*! + \typedef QFileDialog::Mode + + Use QFileDialog::FileMode instead. +*/ + +/*! + \fn void QFileDialog::setMode(FileMode m) + + Use setFileMode() instead. +*/ + +/*! + \fn FileMode QFileDialog::mode() const + + Use fileMode() instead. +*/ + +/*! + \fn void QFileDialog::setDir(const QString &directory) + + Use setDirectory() instead. +*/ + +/*! + \fn void QFileDialog::setDir( const QDir &directory ) + + Use setDirectory() instead. +*/ + +/*! + \fn QStringList QFileDialog::getOpenFileNames(const QString &filter, + const QString &dir, QWidget *parent, const char* name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getOpenFileNames() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getOpenFileName(const QString &dir, + const QString &filter, QWidget *parent = 0, const char *name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getOpenFileName() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getSaveFileName(const QString &dir, + const QString &filter, QWidget *parent, const char *name, + const QString &caption, QString *selectedFilter, bool resolveSymlinks) + + Use the getSaveFileName() overload that takes \a parent as the first + argument instead. +*/ + +/*! + \fn QString QFileDialog::getExistingDirectory(const QString &dir, + QWidget *parent, const char *name, const QString &caption, + bool dirOnly, bool resolveSymlinks) + + Use the getExistingDirectory() overload that takes \a parent as + the first argument instead. +*/ + +#endif // QT3_SUPPORT + +QT_END_NAMESPACE + +#include "moc_qfiledialog.cpp" + +#endif // QT_NO_FILEDIALOG diff --git a/src/gui/dialogs/qfiledialog.h b/src/gui/dialogs/qfiledialog.h new file mode 100644 index 0000000000..fabb575a5a --- /dev/null +++ b/src/gui/dialogs/qfiledialog.h @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEDIALOG_H +#define QFILEDIALOG_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILEDIALOG + +class QModelIndex; +class QItemSelection; +struct QFileDialogArgs; +class QFileIconProvider; +class QFileDialogPrivate; +class QAbstractItemDelegate; +class QAbstractProxyModel; +class QUrl; + +class Q_GUI_EXPORT QFileDialog : public QDialog +{ + Q_OBJECT + Q_ENUMS(ViewMode FileMode AcceptMode Option) + Q_FLAGS(Options) + Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode) + Q_PROPERTY(FileMode fileMode READ fileMode WRITE setFileMode) + Q_PROPERTY(AcceptMode acceptMode READ acceptMode WRITE setAcceptMode) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly DESIGNABLE false) + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks DESIGNABLE false) + Q_PROPERTY(bool confirmOverwrite READ confirmOverwrite WRITE setConfirmOverwrite DESIGNABLE false) + Q_PROPERTY(QString defaultSuffix READ defaultSuffix WRITE setDefaultSuffix) + Q_PROPERTY(bool nameFilterDetailsVisible READ isNameFilterDetailsVisible + WRITE setNameFilterDetailsVisible DESIGNABLE false) + Q_PROPERTY(Options options READ options WRITE setOptions) + +public: + enum ViewMode { Detail, List }; + enum FileMode { AnyFile, ExistingFile, Directory, ExistingFiles, DirectoryOnly }; + enum AcceptMode { AcceptOpen, AcceptSave }; + enum DialogLabel { LookIn, FileName, FileType, Accept, Reject }; + + // ### Rename to FileDialogOption and FileDialogOptions for Qt 5.0 + enum Option + { + ShowDirsOnly = 0x00000001, + DontResolveSymlinks = 0x00000002, + DontConfirmOverwrite = 0x00000004, + DontUseSheet = 0x00000008, + DontUseNativeDialog = 0x00000010, + ReadOnly = 0x00000020, + HideNameFilterDetails = 0x00000040 + }; + Q_DECLARE_FLAGS(Options, Option) + + QFileDialog(QWidget *parent, Qt::WindowFlags f); + explicit QFileDialog(QWidget *parent = 0, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &filter = QString()); + ~QFileDialog(); + + void setDirectory(const QString &directory); + inline void setDirectory(const QDir &directory); + QDir directory() const; + + void selectFile(const QString &filename); + QStringList selectedFiles() const; + +#ifdef QT_DEPRECATED + QT_DEPRECATED void setFilter(const QString &filter); + QT_DEPRECATED void setFilters(const QStringList &filters); + QT_DEPRECATED QStringList filters() const; + QT_DEPRECATED void selectFilter(const QString &filter); + QT_DEPRECATED QString selectedFilter() const; +#endif + void setNameFilterDetailsVisible(bool enabled); + bool isNameFilterDetailsVisible() const; + + void setNameFilter(const QString &filter); + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + void selectNameFilter(const QString &filter); + QString selectedNameFilter() const; + + QDir::Filters filter() const; + void setFilter(QDir::Filters filters); + + void setViewMode(ViewMode mode); + ViewMode viewMode() const; + + void setFileMode(FileMode mode); + FileMode fileMode() const; + + void setAcceptMode(AcceptMode mode); + AcceptMode acceptMode() const; + + void setReadOnly(bool enabled); + bool isReadOnly() const; + + void setResolveSymlinks(bool enabled); + bool resolveSymlinks() const; + + void setSidebarUrls(const QList &urls); + QList sidebarUrls() const; + + QByteArray saveState() const; + bool restoreState(const QByteArray &state); + + void setConfirmOverwrite(bool enabled); + bool confirmOverwrite() const; + + void setDefaultSuffix(const QString &suffix); + QString defaultSuffix() const; + + void setHistory(const QStringList &paths); + QStringList history() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setLabelText(DialogLabel label, const QString &text); + QString labelText(DialogLabel label) const; + +#ifndef QT_NO_PROXYMODEL + void setProxyModel(QAbstractProxyModel *model); + QAbstractProxyModel *proxyModel() const; +#endif + + void setOption(Option option, bool on = true); + bool testOption(Option option) const; + void setOptions(Options options); + Options options() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + void setVisible(bool visible); + +Q_SIGNALS: + void fileSelected(const QString &file); + void filesSelected(const QStringList &files); + void currentChanged(const QString &path); + void directoryEntered(const QString &directory); + void filterSelected(const QString &filter); + +public: +#ifdef QT3_SUPPORT + typedef FileMode Mode; + inline QT3_SUPPORT void setMode(FileMode m) { setFileMode(m); } + inline QT3_SUPPORT FileMode mode() const { return fileMode(); } + inline QT3_SUPPORT void setDir(const QString &directory) { setDirectory(directory); } + inline QT3_SUPPORT void setDir( const QDir &directory ) { setDirectory(directory); } + QT3_SUPPORT QString selectedFile() const; +#endif + + static QString getOpenFileName(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + + static QString getSaveFileName(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + + static QString getExistingDirectory(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + Options options = ShowDirsOnly); + + static QStringList getOpenFileNames(QWidget *parent = 0, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = 0, + Options options = 0); + +#ifdef QT3_SUPPORT + inline static QString QT3_SUPPORT getOpenFileName(const QString &dir, + const QString &filter = QString(), + QWidget *parent = 0, const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getOpenFileName(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } + + inline static QString QT3_SUPPORT getSaveFileName(const QString &dir, + const QString &filter = QString(), + QWidget *parent = 0, const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getSaveFileName(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } + + inline static QString QT3_SUPPORT getExistingDirectory(const QString &dir, + QWidget *parent = 0, + const char* name = 0, + const QString &caption = QString(), + bool dirOnly = true, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getExistingDirectory(parent, caption, dir, + Options((resolveSymlinks ? Option(0) : DontResolveSymlinks) + | (dirOnly ? ShowDirsOnly : Option(0)))); } + + inline static QStringList QT3_SUPPORT getOpenFileNames(const QString &filter, + const QString &dir = QString(), + QWidget *parent = 0, + const char* name = 0, + const QString &caption = QString(), + QString *selectedFilter = 0, + bool resolveSymlinks = true) + { Q_UNUSED(name); + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, + resolveSymlinks ? Option(0) : DontResolveSymlinks); } +#endif // QT3_SUPPORT + +protected: + QFileDialog(const QFileDialogArgs &args); + void done(int result); + void accept(); + void changeEvent(QEvent *e); + +private: + Q_DECLARE_PRIVATE(QFileDialog) + Q_DISABLE_COPY(QFileDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_pathChanged(const QString &)) + + Q_PRIVATE_SLOT(d_func(), void _q_navigateBackward()) + Q_PRIVATE_SLOT(d_func(), void _q_navigateForward()) + Q_PRIVATE_SLOT(d_func(), void _q_navigateToParent()) + Q_PRIVATE_SLOT(d_func(), void _q_createDirectory()) + Q_PRIVATE_SLOT(d_func(), void _q_showListView()) + Q_PRIVATE_SLOT(d_func(), void _q_showDetailsView()) + Q_PRIVATE_SLOT(d_func(), void _q_showContextMenu(const QPoint &)) + Q_PRIVATE_SLOT(d_func(), void _q_renameCurrent()) + Q_PRIVATE_SLOT(d_func(), void _q_deleteCurrent()) + Q_PRIVATE_SLOT(d_func(), void _q_showHidden()) + Q_PRIVATE_SLOT(d_func(), void _q_updateOkButton()) + Q_PRIVATE_SLOT(d_func(), void _q_currentChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_enterDirectory(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_goToDirectory(const QString &path)) + Q_PRIVATE_SLOT(d_func(), void _q_useNameFilter(int index)) + Q_PRIVATE_SLOT(d_func(), void _q_selectionChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_goToUrl(const QUrl &url)) + Q_PRIVATE_SLOT(d_func(), void _q_goHome()) + Q_PRIVATE_SLOT(d_func(), void _q_showHeader(QAction *)) + Q_PRIVATE_SLOT(d_func(), void _q_autoCompleteFileName(const QString &text)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsInserted(const QModelIndex & parent)) + Q_PRIVATE_SLOT(d_func(), void _q_fileRenamed(const QString &path, + const QString oldName, const QString newName)) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif +}; + +inline void QFileDialog::setDirectory(const QDir &adirectory) +{ setDirectory(adirectory.absolutePath()); } + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFileDialog::Options) + +#endif // QT_NO_FILEDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILEDIALOG_H diff --git a/src/gui/dialogs/qfiledialog.ui b/src/gui/dialogs/qfiledialog.ui new file mode 100644 index 0000000000..dcb08114e2 --- /dev/null +++ b/src/gui/dialogs/qfiledialog.ui @@ -0,0 +1,356 @@ + + ********************************************************************* +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +********************************************************************* + QFileDialog + + + + 0 + 0 + 521 + 316 + + + + true + + + + + + Look in: + + + + + + + + + + 1 + 0 + + + + + 50 + 0 + + + + + + + + Back + + + Back + + + Go back + + + + + + + Forward + + + Forward + + + Go forward + + + + + + + Parent Directory + + + Parent Directory + + + Go to the parent directory + + + + + + + Create New Folder + + + Create New Folder + + + Create a New Folder + + + + + + + List View + + + List View + + + Change to list view mode + + + + + + + Detail View + + + Detail View + + + Change to detail view mode + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + 1 + 0 + + + + + + + + Qt::Vertical + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + Files of type: + + + + + + + + 0 + 0 + + + + + + + + + QFileDialogTreeView + QTreeView +
qfiledialog_p.h
+
+ + QFileDialogListView + QListView +
qfiledialog_p.h
+
+ + QSidebar + QListWidget +
qsidebar_p.h
+
+ + QFileDialogLineEdit + QLineEdit +
qfiledialog_p.h
+
+ + QFileDialogComboBox + QComboBox +
qfiledialog_p.h
+
+
+ + lookInCombo + backButton + forwardButton + toParentButton + newFolderButton + listModeButton + detailModeButton + sidebar + listView + fileNameEdit + fileTypeCombo + buttonBox + treeView + + + +
diff --git a/src/gui/dialogs/qfiledialog_embedded.ui b/src/gui/dialogs/qfiledialog_embedded.ui new file mode 100644 index 0000000000..e8de400cab --- /dev/null +++ b/src/gui/dialogs/qfiledialog_embedded.ui @@ -0,0 +1,340 @@ + + ********************************************************************* +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +********************************************************************* + QFileDialog + + + + 0 + 0 + 240 + 320 + + + + true + + + + + + + 1 + 0 + + + + + + + + + + Back + + + + + + + Forward + + + + + + + Parent Directory + + + + + + + Create New Folder + + + + + + + List View + + + + + + + Detail View + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 0 + 108 + 164 + + + + + 0 + + + 0 + + + + + + + + + + 0 + 0 + 100 + 30 + + + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + Qt::Vertical + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + Files of type: + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + Look in: + + + + + + + + QFileDialogTreeView + QTreeView +
qfiledialog_p.h
+
+ + QFileDialogListView + QListView +
qfiledialog_p.h
+
+ + QSidebar + QListWidget +
qsidebar_p.h
+
+ + QFileDialogLineEdit + QLineEdit +
qfiledialog_p.h
+
+ + QFileDialogComboBox + QComboBox +
qfiledialog_p.h
+
+
+ + lookInCombo + backButton + forwardButton + toParentButton + newFolderButton + listModeButton + detailModeButton + sidebar + listView + fileNameEdit + fileTypeCombo + buttonBox + treeView + + + +
diff --git a/src/gui/dialogs/qfiledialog_mac.mm b/src/gui/dialogs/qfiledialog_mac.mm new file mode 100644 index 0000000000..832f9bfaf8 --- /dev/null +++ b/src/gui/dialogs/qfiledialog_mac.mm @@ -0,0 +1,1157 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +/***************************************************************************** + QFileDialog debug facilities + *****************************************************************************/ +//#define DEBUG_FILEDIALOG_FILTERS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#import +#include "ui_qfiledialog.h" + +QT_BEGIN_NAMESPACE + +extern QStringList qt_make_filter_list(const QString &filter); // qfiledialog.cpp +extern QStringList qt_clean_filter_list(const QString &filter); // qfiledialog.cpp +extern const char *qt_file_dialog_filter_reg_exp; // qfiledialog.cpp +extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm + +QT_END_NAMESPACE + +QT_FORWARD_DECLARE_CLASS(QFileDialogPrivate) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QStringList) +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QFileInfo) +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate); + +@interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + : NSObject +#else + : NSObject +#endif +{ + @public + NSOpenPanel *mOpenPanel; + NSSavePanel *mSavePanel; + NSView *mAccessoryView; + NSPopUpButton *mPopUpButton; + NSTextField *mTextField; + QFileDialogPrivate *mPriv; + NSString *mCurrentDir; + bool mConfirmOverwrite; + int mReturnCode; + + QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode) mAcceptMode; + QT_PREPEND_NAMESPACE(QDir::Filters) *mQDirFilter; + QT_PREPEND_NAMESPACE(QFileDialog::FileMode) mFileMode; + QT_PREPEND_NAMESPACE(QFileDialog::Options) *mFileOptions; + + QString *mLastFilterCheckPath; + QString *mCurrentSelection; + QStringList *mQDirFilterEntryList; + QStringList *mNameFilterDropDownList; + QStringList *mSelectedNameFilter; +} + +- (NSString *)strip:(const QString &)label; +- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename; +- (void)filterChanged:(id)sender; +- (void)showModelessPanel; +- (BOOL)runApplicationModalPanel; +- (void)showWindowModalSheet:(QWidget *)docWidget; +- (void)updateProperties; +- (QStringList)acceptableExtensionsForSave; +- (QString)removeExtensions:(const QString &)filter; +- (void)createTextField; +- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails; +- (QStringList)findStrippedFilterWithVisualFilterName:(QString)name; +- (void)createAccessory; + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) + +- (id)initWithAcceptMode:(QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode))acceptMode + title:(const QString &)title + hideNameFilterDetails:(bool)hideNameFilterDetails + qDirFilter:(QT_PREPEND_NAMESPACE(QDir::Filters))qDirFilter + fileOptions:(QT_PREPEND_NAMESPACE(QFileDialog::Options))fileOptions + fileMode:(QT_PREPEND_NAMESPACE(QFileDialog::FileMode))fileMode + selectFile:(const QString &)selectFile + confirmOverwrite:(bool)confirm + priv:(QFileDialogPrivate *)priv +{ + self = [super init]; + + mAcceptMode = acceptMode; + if (mAcceptMode == QT_PREPEND_NAMESPACE(QFileDialog::AcceptOpen)){ + mOpenPanel = [NSOpenPanel openPanel]; + mSavePanel = mOpenPanel; + } else { + mSavePanel = [NSSavePanel savePanel]; + mOpenPanel = 0; + } + + [mSavePanel setLevel:NSModalPanelWindowLevel]; + [mSavePanel setDelegate:self]; + mQDirFilter = new QT_PREPEND_NAMESPACE(QDir::Filters)(qDirFilter); + mFileOptions = new QT_PREPEND_NAMESPACE(QFileDialog::Options)(fileOptions); + mFileMode = fileMode; + mConfirmOverwrite = confirm; + mReturnCode = -1; + mPriv = priv; + mLastFilterCheckPath = new QString; + mQDirFilterEntryList = new QStringList; + mNameFilterDropDownList = new QStringList(priv->nameFilters); + QString selectedVisualNameFilter = priv->qFileDialogUi->fileTypeCombo->currentText(); + mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]); + + QFileInfo sel(selectFile); + if (sel.isDir()){ + mCurrentDir = [qt_mac_QStringToNSString(sel.absoluteFilePath()) retain]; + mCurrentSelection = new QString; + } else { + mCurrentDir = [qt_mac_QStringToNSString(sel.absolutePath()) retain]; + mCurrentSelection = new QString(sel.absoluteFilePath()); + } + + [mSavePanel setTitle:qt_mac_QStringToNSString(title)]; + [self createPopUpButton:selectedVisualNameFilter hideDetails:hideNameFilterDetails]; + [self createTextField]; + [self createAccessory]; + [mSavePanel setAccessoryView:mNameFilterDropDownList->size() > 1 ? mAccessoryView : nil]; + + if (mPriv){ + [mSavePanel setPrompt:[self strip:mPriv->acceptLabel]]; + if (mPriv->fileNameLabelExplicitlySat) + [mSavePanel setNameFieldLabel:[self strip:mPriv->qFileDialogUi->fileNameLabel->text()]]; + } + + [self updateProperties]; + [mSavePanel retain]; + return self; +} + +- (void)dealloc +{ + delete mQDirFilter; + delete mFileOptions; + delete mLastFilterCheckPath; + delete mQDirFilterEntryList; + delete mNameFilterDropDownList; + delete mSelectedNameFilter; + delete mCurrentSelection; + + [mSavePanel orderOut:mSavePanel]; + [mSavePanel setAccessoryView:nil]; + [mPopUpButton release]; + [mTextField release]; + [mAccessoryView release]; + [mSavePanel setDelegate:nil]; + [mSavePanel release]; + [mCurrentDir release]; + [super dealloc]; +} + +- (NSString *)strip:(const QString &)label +{ + QAction a(label, 0); + return qt_mac_QStringToNSString(a.iconText()); +} + +- (void)closePanel +{ + *mCurrentSelection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]); + [mSavePanel close]; +} + +- (void)showModelessPanel +{ + if (mOpenPanel){ + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + [mOpenPanel + beginForDirectory:mCurrentDir + file:selectable ? filename : nil + types:nil + modelessDelegate:self + didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) + contextInfo:nil]; + } +} + +- (BOOL)runApplicationModalPanel +{ + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + mReturnCode = [mSavePanel + runModalForDirectory:mCurrentDir + file:selectable ? filename : @"untitled"]; + + QAbstractEventDispatcher::instance()->interrupt(); + return (mReturnCode == NSOKButton); +} + +- (QT_PREPEND_NAMESPACE(QDialog::DialogCode))dialogResultCode +{ + return (mReturnCode == NSOKButton) ? QT_PREPEND_NAMESPACE(QDialog::Accepted) : QT_PREPEND_NAMESPACE(QDialog::Rejected); +} + +- (void)showWindowModalSheet:(QWidget *)docWidget +{ + Q_UNUSED(docWidget); + QFileInfo info(*mCurrentSelection); + NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName()); + NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath()); + bool selectable = (mAcceptMode == QFileDialog::AcceptSave) + || [self panel:nil shouldShowFilename:filepath]; + [mSavePanel + beginSheetForDirectory:mCurrentDir + file:selectable ? filename : nil +#ifdef QT_MAC_USE_COCOA + modalForWindow:QT_PREPEND_NAMESPACE(qt_mac_window_for)(docWidget) +#else + modalForWindow:nil +#endif + modalDelegate:self + didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) + contextInfo:nil]; +} + +- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename +{ + Q_UNUSED(sender); + + if ([filename length] == 0) + return NO; + + // Always accept directories regardless of their names (unless it is a bundle): + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDir] && isDir) { + if ([mSavePanel treatsFilePackagesAsDirectories] == NO) { + if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO) + return YES; + } + } + + QString qtFileName = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)(filename); + QFileInfo info(qtFileName.normalized(QT_PREPEND_NAMESPACE(QString::NormalizationForm_C))); + QString path = info.absolutePath(); + if (path != *mLastFilterCheckPath){ + *mLastFilterCheckPath = path; + *mQDirFilterEntryList = info.dir().entryList(*mQDirFilter); + } + // Check if the QDir filter accepts the file: + if (!mQDirFilterEntryList->contains(info.fileName())) + return NO; + + // No filter means accept everything + if (mSelectedNameFilter->isEmpty()) + return YES; + // Check if the current file name filter accepts the file: + for (int i=0; isize(); ++i) { + if (QDir::match(mSelectedNameFilter->at(i), qtFileName)) + return YES; + } + return NO; +} + +- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag +{ + Q_UNUSED(sender); + if (!okFlag) + return filename; + if (mConfirmOverwrite) + return filename; + + // User has clicked save, and no overwrite confirmation should occur. + // To get the latter, we need to change the name we return (hence the prefix): + return [@"___qt_very_unlikely_prefix_" stringByAppendingString:filename]; +} + +- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails +{ + [mPopUpButton removeAllItems]; + *mNameFilterDropDownList = filters; + if (filters.size() > 0){ + for (int i=0; ivalue([mPopUpButton indexOfSelectedItem]); + *mSelectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection]; + [mSavePanel validateVisibleColumns]; + [self updateProperties]; + if (mPriv) + mPriv->QNSOpenSavePanelDelegate_filterSelected([mPopUpButton indexOfSelectedItem]); +} + +- (QString)currentNameFilter +{ + return mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]); +} + +- (QStringList)selectedFiles +{ + if (mOpenPanel) + return QT_PREPEND_NAMESPACE(qt_mac_NSArrayToQStringList)([mOpenPanel filenames]); + else{ + QStringList result; + QString filename = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]); + result << filename.remove(QLatin1String("___qt_very_unlikely_prefix_")); + return result; + } +} + +- (void)updateProperties +{ + // Call this functions if mFileMode, mFileOptions, + // mNameFilterDropDownList or mQDirFilter changes. + // The savepanel does not contain the neccessary functions for this. + bool chooseFilesOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFile) + || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles); + bool chooseDirsOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::Directory) + || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::DirectoryOnly) + || *mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ShowDirsOnly); + + [mOpenPanel setCanChooseFiles:!chooseDirsOnly]; + [mOpenPanel setCanChooseDirectories:!chooseFilesOnly]; + [mSavePanel setCanCreateDirectories:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ReadOnly))]; + [mOpenPanel setAllowsMultipleSelection:(mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles))]; + [mOpenPanel setResolvesAliases:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::DontResolveSymlinks))]; + + QStringList ext = [self acceptableExtensionsForSave]; + if (mPriv && !ext.isEmpty() && !mPriv->defaultSuffix.isEmpty()) + ext.prepend(mPriv->defaultSuffix); + [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : QT_PREPEND_NAMESPACE(qt_mac_QStringListToNSMutableArray(ext))]; + + if ([mSavePanel isVisible]) + [mOpenPanel validateVisibleColumns]; +} + +- (void)panelSelectionDidChange:(id)sender +{ + Q_UNUSED(sender); + if (mPriv) { + QString selection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString([mSavePanel filename])); + if (selection != mCurrentSelection) { + *mCurrentSelection = selection; + mPriv->QNSOpenSavePanelDelegate_selectionChanged(selection); + } + } +} + +- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + Q_UNUSED(panel); + Q_UNUSED(contextInfo); + mReturnCode = returnCode; + if (mPriv) + mPriv->QNSOpenSavePanelDelegate_panelClosed(returnCode == NSOKButton); +} + +- (void)panel:(id)sender directoryDidChange:(NSString *)path +{ + Q_UNUSED(sender); + if (!mPriv) + return; + if ([path isEqualToString:mCurrentDir]) + return; + + [mCurrentDir release]; + mCurrentDir = [path retain]; + mPriv->QNSOpenSavePanelDelegate_directoryEntered(QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString(mCurrentDir))); +} + +/* + Returns a list of extensions (e.g. "png", "jpg", "gif") + for the current name filter. If a filter do not conform + to the format *.xyz or * or *.*, an empty list + is returned meaning accept everything. +*/ +- (QStringList)acceptableExtensionsForSave +{ + QStringList result; + for (int i=0; icount(); ++i) { + const QString &filter = mSelectedNameFilter->at(i); + if (filter.startsWith(QLatin1String("*.")) + && !filter.contains(QLatin1Char('?')) + && filter.count(QLatin1Char('*')) == 1) { + result += filter.mid(2); + } else { + return QStringList(); // Accept everything + } + } + return result; +} + +- (QString)removeExtensions:(const QString &)filter +{ + QRegExp regExp(QT_PREPEND_NAMESPACE(QString::fromLatin1)(QT_PREPEND_NAMESPACE(qt_file_dialog_filter_reg_exp))); + if (regExp.indexIn(filter) != -1) + return regExp.cap(1).trimmed(); + return filter; +} + +- (void)createTextField +{ + NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } }; + mTextField = [[NSTextField alloc] initWithFrame:textRect]; + [[mTextField cell] setFont:[NSFont systemFontOfSize: + [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + [mTextField setAlignment:NSRightTextAlignment]; + [mTextField setEditable:false]; + [mTextField setSelectable:false]; + [mTextField setBordered:false]; + [mTextField setDrawsBackground:false]; + if (mPriv){ + [mTextField setStringValue:[self strip:mPriv->qFileDialogUi->fileTypeLabel->text()]]; + } else + [mTextField setStringValue:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(QT_PREPEND_NAMESPACE(QFileDialog::tr)("Files of type:"))]; +} + +- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails +{ + NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } }; + mPopUpButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO]; + [mPopUpButton setTarget:self]; + [mPopUpButton setAction:@selector(filterChanged:)]; + + QStringList *filters = mNameFilterDropDownList; + if (filters->size() > 0){ + for (int i=0; isize(); ++i) { + QString filter = hideDetails ? [self removeExtensions:filters->at(i)] : filters->at(i); + [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)]; + if (filters->at(i).startsWith(selectedFilter)) + [mPopUpButton selectItemAtIndex:i]; + } + } +} + +- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name +{ + for (int i=0; isize(); ++i) { + if (mNameFilterDropDownList->at(i).startsWith(name)) + return qt_clean_filter_list(mNameFilterDropDownList->at(i)); + } + return QStringList(); +} + +- (void)createAccessory +{ + NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } }; + mAccessoryView = [[NSView alloc] initWithFrame:accessoryRect]; + [mAccessoryView addSubview:mTextField]; + [mAccessoryView addSubview:mPopUpButton]; +} + +@end + +QT_BEGIN_NAMESPACE + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath) +{ + emit q_func()->currentChanged(newPath); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_panelClosed(bool accepted) +{ + if (accepted) + q_func()->accept(); + else + q_func()->reject(); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir) +{ + setLastVisitedDirectory(newDir); + emit q_func()->directoryEntered(newDir); +} + +void QFileDialogPrivate::QNSOpenSavePanelDelegate_filterSelected(int menuIndex) +{ + emit q_func()->filterSelected(nameFilters.at(menuIndex)); +} + +extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp +extern void qt_mac_to_pascal_string(QString s, Str255 str, TextEncoding encoding=0, int len=-1); // qglobal.cpp + +void QFileDialogPrivate::setDirectory_sys(const QString &directory) +{ +#ifndef QT_MAC_USE_COCOA + if (directory == mCurrentLocation) + return; + mCurrentLocation = directory; + emit q_func()->directoryEntered(mCurrentLocation); + + FSRef fsRef; + if (qt_mac_create_fsref(directory, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc); + } +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + [delegate->mSavePanel setDirectory:qt_mac_QStringToNSString(directory)]; +#endif +} + +QString QFileDialogPrivate::directory_sys() const +{ +#ifndef QT_MAC_USE_COCOA + return mCurrentLocation; +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + return qt_mac_NSStringToQString([delegate->mSavePanel directory]); +#endif +} + +void QFileDialogPrivate::selectFile_sys(const QString &filename) +{ + QString filePath = filename; + if (QDir::isRelativePath(filePath)) + filePath = QFileInfo(directory_sys(), filePath).filePath(); + +#ifndef QT_MAC_USE_COCOA + // Update the selection list immidiatly, so + // subsequent calls to selectedFiles() gets correct: + mCurrentSelectionList.clear(); + mCurrentSelectionList << filename; + if (mCurrentSelection != filename){ + mCurrentSelection = filename; + emit q_func()->currentChanged(mCurrentSelection); + } + + AEDescList descList; + if (AECreateList(0, 0, false, &descList) != noErr) + return; + + FSRef fsRef; + if (qt_mac_create_fsref(filePath, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr){ + if (AEPutDesc(&descList, 0, &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetSelection, (void*)&descList); + } + } + + // Type the file name into the save dialog's text field: + UInt8 *strBuffer = (UInt8 *)malloc(1024); + qt_mac_to_pascal_string(QFileInfo(filename).fileName(), strBuffer); + NavCustomControl(mDialog, kNavCtlSetEditFileName, strBuffer); + free(strBuffer); +#else + // There seems to no way to select a file once the dialog is running. + // So do the next best thing, set the file's directory: + setDirectory_sys(QFileInfo(filePath).absolutePath()); +#endif +} + +QStringList QFileDialogPrivate::selectedFiles_sys() const +{ +#ifndef QT_MAC_USE_COCOA + if (q_func()->acceptMode() == QFileDialog::AcceptOpen){ + return mCurrentSelectionList; + } else { + return QStringList() << mCurrentLocation + QLatin1Char('/') + + QCFString::toQString(NavDialogGetSaveFileName(mDialog)); + } +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + return [delegate selectedFiles]; +#endif +} + +void QFileDialogPrivate::setNameFilters_sys(const QStringList &filters) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(filters); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + bool hideDetails = q_func()->testOption(QFileDialog::HideNameFilterDetails); + [delegate setNameFilters:filters hideDetails:hideDetails]; +#endif +} + +void QFileDialogPrivate::setFilter_sys() +{ +#ifndef QT_MAC_USE_COCOA +#else + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + *(delegate->mQDirFilter) = model->filter(); + delegate->mFileMode = fileMode; + [delegate->mSavePanel setTitle:qt_mac_QStringToNSString(q->windowTitle())]; + [delegate->mSavePanel setPrompt:[delegate strip:acceptLabel]]; + if (fileNameLabelExplicitlySat) + [delegate->mSavePanel setNameFieldLabel:[delegate strip:qFileDialogUi->fileNameLabel->text()]]; + + [delegate updateProperties]; +#endif +} + +void QFileDialogPrivate::selectNameFilter_sys(const QString &filter) +{ + int index = nameFilters.indexOf(filter); + if (index != -1) { +#ifndef QT_MAC_USE_COCOA + NavMenuItemSpec navSpec; + bzero(&navSpec, sizeof(NavMenuItemSpec)); + navSpec.menuType = index; + NavCustomControl(mDialog, kNavCtlSelectCustomType, &navSpec); +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + [delegate->mPopUpButton selectItemAtIndex:index]; + [delegate filterChanged:nil]; +#endif + } +} + +QString QFileDialogPrivate::selectedNameFilter_sys() const +{ +#ifndef QT_MAC_USE_COCOA + int index = filterInfo.currentSelection; +#else + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + int index = [delegate->mPopUpButton indexOfSelectedItem]; +#endif + return index != -1 ? nameFilters.at(index) : QString(); +} + +void QFileDialogPrivate::deleteNativeDialog_sys() +{ +#ifndef QT_MAC_USE_COCOA + if (mDialog) + NavDialogDispose(mDialog); + mDialog = 0; + mDialogStarted = false; +#else + QMacCocoaAutoReleasePool pool; + [reinterpret_cast(mDelegate) release]; + mDelegate = 0; +#endif + nativeDialogInUse = false; +} + +bool QFileDialogPrivate::setVisible_sys(bool visible) +{ + Q_Q(QFileDialog); + if (!visible == q->isHidden()) + return false; + + if (q->windowFlags() & Qt::WindowStaysOnTopHint) { + // The native file dialog tries all it can to stay + // on the NSModalPanel level. And it might also show + // its own "create directory" dialog that we cannot control. + // So we need to use the non-native version in this case... + return false; + } + +#ifndef QT_MAC_USE_COCOA + return visible ? showCarbonNavServicesDialog() : hideCarbonNavServicesDialog(); +#else + return visible ? showCocoaFilePanel() : hideCocoaFilePanel(); +#endif +} + +#ifndef QT_MAC_USE_COCOA +Boolean QFileDialogPrivate::qt_mac_filedialog_filter_proc(AEDesc *theItem, void *info, + void *data, NavFilterModes) +{ + QFileDialogPrivate *fileDialogPrivate = static_cast(data); + + if (!fileDialogPrivate || fileDialogPrivate->filterInfo.filters.isEmpty() + || (fileDialogPrivate->filterInfo.currentSelection < 0 + && fileDialogPrivate->filterInfo.currentSelection + >= fileDialogPrivate->filterInfo.filters.size())) + return true; + + NavFileOrFolderInfo *theInfo = static_cast(info); + QString file; + QString path; + const QtMacFilterName &fn + = fileDialogPrivate->filterInfo.filters.at(fileDialogPrivate->filterInfo.currentSelection); + if (theItem->descriptorType == typeFSRef) { + FSRef ref; + AEGetDescData(theItem, &ref, sizeof(ref)); + UInt8 str_buffer[1024]; + FSRefMakePath(&ref, str_buffer, 1024); + path = QString::fromUtf8(reinterpret_cast(str_buffer)); + int slsh = path.lastIndexOf(QLatin1Char('/')); + if (slsh != -1) + file = path.right(path.length() - slsh - 1); + else + file = path; + } + QStringList reg = fn.regexp.split(QLatin1String(";")); + for (QStringList::const_iterator it = reg.constBegin(); it != reg.constEnd(); ++it) { + QRegExp rg(*it, Qt::CaseInsensitive, QRegExp::Wildcard); +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialogPrivate::qt_mac_filedialog_filter_proc:%d, asked to filter.. %s (%s)", __LINE__, + qPrintable(file), qPrintable(*it)); +#endif + if (rg.exactMatch(file)) + return true; + } + + if (theInfo->isFolder) { + if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:qt_mac_QStringToNSString(path)]) + return false; + return true; + } + return false; +} + +void QFileDialogPrivate::qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg, + NavCBRecPtr p, NavCallBackUserData data) +{ + QFileDialogPrivate *fileDialogPrivate = static_cast(data); + + switch(msg) { + case kNavCBPopupMenuSelect: { + NavMenuItemSpec *s = static_cast(p->eventData.eventDataParms.param); + if (int(s->menuType) != fileDialogPrivate->filterInfo.currentSelection) { + fileDialogPrivate->filterInfo.currentSelection = s->menuType; + emit fileDialogPrivate->q_func()->filterSelected(fileDialogPrivate->nameFilters.at(s->menuType)); + } + if (fileDialogPrivate->acceptMode == QFileDialog::AcceptSave) { + QString base = QCFString::toQString(NavDialogGetSaveFileName(p->context)); + QFileInfo fi(base); + base = fi.completeBaseName(); + const QtMacFilterName &fn = fileDialogPrivate->filterInfo.filters.at( + fileDialogPrivate->filterInfo.currentSelection); + QStringList reg = fn.regexp.split(QLatin1String(";"), QString::SkipEmptyParts); + if (reg.count()) { + QString r = reg.first(); + r = r.right(r.length()-1); // Strip the * + base += r; //"." + QString::number(s->menuType); + } + NavDialogSetSaveFileName(p->context, QCFString::toCFStringRef(base)); + } +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialogPrivate::qt_mac_filedialog_event_proc:%d - Selected a filter: %ld", __LINE__, s->menuType); +#endif + break; } + case kNavCBStart:{ + fileDialogPrivate->mDialogStarted = true; + // Set selected file: + QModelIndexList indexes = fileDialogPrivate->qFileDialogUi->listView->selectionModel()->selectedRows(); + QString selected; + if (!indexes.isEmpty()) + selected = indexes.at(0).data(QFileSystemModel::FilePathRole).toString(); + else + selected = fileDialogPrivate->typedFiles().value(0); + fileDialogPrivate->selectFile_sys(selected); + fileDialogPrivate->selectNameFilter_sys(fileDialogPrivate->qFileDialogUi->fileTypeCombo->currentText()); + break; } + case kNavCBSelectEntry:{ + // Event: Current selection has changed. + QStringList prevSelectionList = fileDialogPrivate->mCurrentSelectionList; + fileDialogPrivate->mCurrentSelectionList.clear(); + QString fileNameToEmit; + + AEDescList *descList = (AEDescList *)p->eventData.eventDataParms.param; + // Get the number of files selected: + UInt8 strBuffer[1024]; + long count; + OSErr err = AECountItems(descList, &count); + if (err != noErr || !count) + break; + + for (long index=1; index<=count; ++index) { + FSRef ref; + err = AEGetNthPtr(descList, index, typeFSRef, 0, 0, &ref, sizeof(ref), 0); + if (err != noErr) + break; + FSRefMakePath(&ref, strBuffer, 1024); + QString selected = QString::fromUtf8((const char *)strBuffer); + fileDialogPrivate->mCurrentSelectionList << selected; + if (!prevSelectionList.contains(selected)) + fileNameToEmit = selected; + } + + if (!fileNameToEmit.isEmpty() && fileNameToEmit != fileDialogPrivate->mCurrentSelection) + emit fileDialogPrivate->q_func()->currentChanged(fileNameToEmit); + fileDialogPrivate->mCurrentSelection = fileNameToEmit; + break; } + case kNavCBShowDesktop: + case kNavCBNewLocation:{ + // Event: Current directory has changed. + AEDesc *desc = (AEDesc *)p->eventData.eventDataParms.param; + FSRef ref; + AEGetDescData(desc, &ref, sizeof(ref)); + UInt8 *strBuffer = (UInt8 *)malloc(1024); + FSRefMakePath(&ref, strBuffer, 1024); + QString newLocation = QString::fromUtf8((const char *)strBuffer); + free(strBuffer); + if (fileDialogPrivate->mCurrentLocation != newLocation){ + fileDialogPrivate->mCurrentLocation = newLocation; + QFileDialog::FileMode mode = fileDialogPrivate->fileMode; + if (mode == QFileDialog::AnyFile || mode == QFileDialog::ExistingFile + || mode == QFileDialog::ExistingFiles){ + // When changing directory, the current selection is cleared if + // we are supposed to be selecting files only: + if (!fileDialogPrivate->mCurrentSelection.isEmpty()){ + fileDialogPrivate->mCurrentSelectionList.clear(); + fileDialogPrivate->mCurrentSelection.clear(); + emit fileDialogPrivate->q_func()->currentChanged(fileDialogPrivate->mCurrentSelection); + } + } + fileDialogPrivate->setLastVisitedDirectory(newLocation); + emit fileDialogPrivate->q_func()->directoryEntered(newLocation); + } + break; } + case kNavCBAccept: + fileDialogPrivate->mDialogClosed = true; + fileDialogPrivate->q_func()->accept(); + break; + case kNavCBCancel: + fileDialogPrivate->mDialogClosed = true; + fileDialogPrivate->q_func()->reject(); + break; + } +} + +static QFileDialogPrivate::QtMacFilterName qt_mac_extract_filter(const QString &rawFilter, bool showDetails) +{ + QFileDialogPrivate::QtMacFilterName ret; + ret.filter = rawFilter; + QString result = rawFilter; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + int index = r.indexIn(result); + if (index >= 0) + result = r.cap(2); + + if (showDetails) { + ret.description = rawFilter; + } else { + if (index >= 0) + ret.description = r.cap(1).trimmed(); + if (ret.description.isEmpty()) + ret.description = result; + } + ret.regexp = result.replace(QLatin1Char(' '), QLatin1Char(';')); + return ret; +} + +static QList qt_mac_make_filters_list(const QString &filter, bool showDetails) +{ +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialog:%d - Got filter (%s)", __LINE__, filter.latin1()); +#endif + + QList ret; + QString f(filter); + if (f.isEmpty()) + f = QFileDialog::tr("All Files (*)"); + if (f.isEmpty()) + return ret; + QStringList filts = qt_make_filter_list(f); + for (QStringList::const_iterator it = filts.constBegin(); it != filts.constEnd(); ++it) { + QFileDialogPrivate::QtMacFilterName filter = qt_mac_extract_filter(*it, showDetails); +#ifdef DEBUG_FILEDIALOG_FILTERS + qDebug("QFileDialog:%d Split out filter (%d) '%s' '%s' [%s]", __LINE__, ret.count(), + filter->regxp.latin1(), filter->description.latin1(), (*it).latin1()); +#endif + ret.append(filter); + } + return ret; +} + +void QFileDialogPrivate::createNavServicesDialog() +{ + Q_Q(QFileDialog); + if (mDialog) + deleteNativeDialog_sys(); + + NavDialogCreationOptions navOptions; + NavGetDefaultDialogCreationOptions(&navOptions); + + // Translate QFileDialog settings into NavDialog options: + if (qt_mac_is_macsheet(q)) { + navOptions.modality = kWindowModalityWindowModal; + navOptions.parentWindow = qt_mac_window_for(q->parentWidget()); + } else if (q->windowModality() == Qt::ApplicationModal) + navOptions.modality = kWindowModalityAppModal; + else + navOptions.modality = kWindowModalityNone; + navOptions.optionFlags |= kNavSupportPackages; + if (q->testOption(QFileDialog::DontConfirmOverwrite)) + navOptions.optionFlags |= kNavDontConfirmReplacement; + if (fileMode != QFileDialog::ExistingFiles) + navOptions.optionFlags &= ~kNavAllowMultipleFiles; + + navOptions.windowTitle = QCFString::toCFStringRef(q->windowTitle()); + + navOptions.location.h = -1; + navOptions.location.v = -1; + + QWidget *parent = q->parentWidget(); + if (parent && parent->isVisible()) { + WindowClass wclass; + GetWindowClass(qt_mac_window_for(parent), &wclass); + parent = parent->window(); + QString s = parent->windowTitle(); + navOptions.clientName = QCFString::toCFStringRef(s); + } + + filterInfo.currentSelection = 0; + filterInfo.filters = qt_mac_make_filters_list(nameFilters.join(QLatin1String(";;")), q->isNameFilterDetailsVisible()); + QCFType filterArray; + if (filterInfo.filters.size() > 1) { + int i = 0; + CFStringRef *cfstringArray = static_cast(malloc(sizeof(CFStringRef) + * filterInfo.filters.size())); + for (i = 0; i < filterInfo.filters.size(); ++i) { + cfstringArray[i] = QCFString::toCFStringRef(filterInfo.filters.at(i).description); + } + filterArray = CFArrayCreate(kCFAllocatorDefault, + reinterpret_cast(cfstringArray), filterInfo.filters.size(), + &kCFTypeArrayCallBacks); + navOptions.popupExtension = filterArray; + free(cfstringArray); + } + + if (q->acceptMode() == QFileDialog::AcceptSave) { + if (NavCreatePutFileDialog(&navOptions, 'cute', kNavGenericSignature, + QFileDialogPrivate::qt_mac_filedialog_event_proc, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } else if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) { + if (NavCreateChooseFolderDialog(&navOptions, + QFileDialogPrivate::qt_mac_filedialog_event_proc, 0, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } else { + if (NavCreateGetFileDialog(&navOptions, 0, + QFileDialogPrivate::qt_mac_filedialog_event_proc, 0, + QFileDialogPrivate::qt_mac_filedialog_filter_proc, this, &mDialog)) { + qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__); + return; + } + } + + // Set start-up directory: + if (mCurrentLocation.isEmpty()) + mCurrentLocation = rootPath(); + FSRef fsRef; + if (qt_mac_create_fsref(mCurrentLocation, &fsRef) == noErr) { + AEDesc desc; + if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr) + NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc); + } +} + +bool QFileDialogPrivate::showCarbonNavServicesDialog() +{ + Q_Q(QFileDialog); + if (q->acceptMode() == QFileDialog::AcceptSave && q->windowModality() == Qt::NonModal) + return false; // cannot do native no-modal save dialogs. + createNavServicesDialog(); + mDialogClosed = false; + if (q->windowModality() != Qt::ApplicationModal) + NavDialogRun(mDialog); + return true; +} + +bool QFileDialogPrivate::hideCarbonNavServicesDialog() +{ + if (!mDialogClosed){ + mDialogClosed = true; + NavCustomControl(mDialog, kNavCtlCancel, 0); + } + return true; +} + +#else // Cocoa + +void QFileDialogPrivate::createNSOpenSavePanelDelegate() +{ + Q_Q(QFileDialog); + if (mDelegate) + return; + + bool selectDir = q->selectedFiles().isEmpty(); + QString selection(selectDir ? q->directory().absolutePath() : q->selectedFiles().value(0)); + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) alloc] + initWithAcceptMode:acceptMode + title:q->windowTitle() + hideNameFilterDetails:q->testOption(QFileDialog::HideNameFilterDetails) + qDirFilter:model->filter() + fileOptions:opts + fileMode:fileMode + selectFile:selection + confirmOverwrite:!q->testOption(QFileDialog::DontConfirmOverwrite) + priv:this]; + + mDelegate = delegate; +} + +bool QFileDialogPrivate::showCocoaFilePanel() +{ + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + createNSOpenSavePanelDelegate(); + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + if (qt_mac_is_macsheet(q)) + [delegate showWindowModalSheet:q->parentWidget()]; + else + [delegate showModelessPanel]; + return true; +} + +bool QFileDialogPrivate::hideCocoaFilePanel() +{ + if (!mDelegate){ + // Nothing to do. We return false to leave the question + // open regarding whether or not to go native: + return false; + } else { + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + [delegate closePanel]; + // Even when we hide it, we are still using a + // native dialog, so return true: + return true; + } +} + +#endif + +void QFileDialogPrivate::mac_nativeDialogModalHelp() +{ + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most 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 (nativeDialogInUse){ + Q_Q(QFileDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +void QFileDialogPrivate::_q_macRunNativeAppModalPanel() +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); +#ifndef QT_MAC_USE_COCOA + NavDialogRun(mDialog); +#else + Q_Q(QFileDialog); + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + [delegate runApplicationModalPanel]; + dialogResultCode_sys() == QDialog::Accepted ? q->accept() : q->reject(); +#endif +} + +QDialog::DialogCode QFileDialogPrivate::dialogResultCode_sys() +{ +#ifndef QT_MAC_USE_COCOA + NavUserAction result = NavDialogGetUserAction(mDialog); + if (result == kNavUserActionCancel || result == kNavUserActionNone) + return QDialog::Rejected; + else + return QDialog::Accepted; +#else + QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast(mDelegate); + return [delegate dialogResultCode]; +#endif +} + + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + diff --git a/src/gui/dialogs/qfiledialog_p.h b/src/gui/dialogs/qfiledialog_p.h new file mode 100644 index 0000000000..882acdd758 --- /dev/null +++ b/src/gui/dialogs/qfiledialog_p.h @@ -0,0 +1,427 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEDIALOG_P_H +#define QFILEDIALOG_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. +// + +#ifndef QT_NO_FILEDIALOG + +#include "qfiledialog.h" +#include "private/qdialog_p.h" +#include "qplatformdefs.h" + +#include "qfilesystemmodel_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qsidebar_p.h" +#include "qfscompleter_p.h" +#include "private/qguiplatformplugin_p.h" + + +#if defined (Q_OS_UNIX) +#include +#endif + +QT_BEGIN_NAMESPACE + +class QFileDialogListView; +class QFileDialogTreeView; +class QFileDialogLineEdit; +class QGridLayout; +class QCompleter; +class QHBoxLayout; +class Ui_QFileDialog; + + +struct QFileDialogArgs +{ + QFileDialogArgs() : parent(0), mode(QFileDialog::AnyFile) {} + + QWidget *parent; + QString caption; + QString directory; + QString selection; + QString filter; + QFileDialog::FileMode mode; + QFileDialog::Options options; +}; + +#define UrlRole (Qt::UserRole + 1) + +class Q_AUTOTEST_EXPORT QFileDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QFileDialog) + +public: + QFileDialogPrivate(); + + void createToolButtons(); + void createMenuActions(); + void createWidgets(); + + void init(const QString &directory = QString(), const QString &nameFilter = QString(), + const QString &caption = QString()); + bool itemViewKeyboardEvent(QKeyEvent *event); + QString getEnvironmentVariable(const QString &string); + static QString workingDirectory(const QString &path); + static QString initialSelection(const QString &path); + QStringList typedFiles() const; + QStringList addDefaultSuffixToFiles(const QStringList filesToFix) const; + bool removeDirectory(const QString &path); + + inline QModelIndex mapToSource(const QModelIndex &index) const; + inline QModelIndex mapFromSource(const QModelIndex &index) const; + inline QModelIndex rootIndex() const; + inline void setRootIndex(const QModelIndex &index) const; + inline QModelIndex select(const QModelIndex &index) const; + inline QString rootPath() const; + + QLineEdit *lineEdit() const; + + int maxNameLength(const QString &path) { +#if defined(Q_OS_UNIX) + return ::pathconf(QFile::encodeName(path).data(), _PC_NAME_MAX); +#elif defined(Q_OS_WIN) +#ifndef Q_OS_WINCE + DWORD maxLength; + QString drive = path.left(3); + if (::GetVolumeInformation(reinterpret_cast(drive.utf16()), NULL, 0, NULL, &maxLength, NULL, NULL, 0) == FALSE) + return -1; + return maxLength; +#else + Q_UNUSED(path); + return MAX_PATH; +#endif //Q_OS_WINCE +#else + Q_UNUSED(path); +#endif + return -1; + } + + QString basename(const QString &path) const + { + int separator = QDir::toNativeSeparators(path).lastIndexOf(QDir::separator()); + if (separator != -1) + return path.mid(separator + 1); + return path; + } + + QDir::Filters filterForMode(QDir::Filters filters) const + { + if (fileMode == QFileDialog::DirectoryOnly) { + filters |= QDir::Drives | QDir::AllDirs | QDir::Dirs; + filters &= ~QDir::Files; + } else { + filters |= QDir::Drives | QDir::AllDirs | QDir::Files | QDir::Dirs; + } + return filters; + } + + QAbstractItemView *currentView() const; + + static inline QString toInternal(const QString &path) + { +#if defined(Q_FS_FAT) || defined(Q_OS_OS2EMX) || defined(Q_OS_SYMBIAN) + QString n(path); + for (int i = 0; i < (int)n.length(); ++i) + if (n[i] == QLatin1Char('\\')) n[i] = QLatin1Char('/'); +#if defined(Q_OS_WINCE) + if ((n.size() > 1) && (n.startsWith(QLatin1String("//")))) + n = n.mid(1); +#endif + return n; +#else // the compile should optimize away this + return path; +#endif + } + + void setLastVisitedDirectory(const QString &dir); + void retranslateWindowTitle(); + void retranslateStrings(); + void emitFilesSelected(const QStringList &files); + + void _q_goHome(); + void _q_pathChanged(const QString &); + void _q_navigateBackward(); + void _q_navigateForward(); + void _q_navigateToParent(); + void _q_createDirectory(); + void _q_showListView(); + void _q_showDetailsView(); + void _q_showContextMenu(const QPoint &position); + void _q_renameCurrent(); + void _q_deleteCurrent(); + void _q_showHidden(); + void _q_showHeader(QAction *); + void _q_updateOkButton(); + void _q_currentChanged(const QModelIndex &index); + void _q_enterDirectory(const QModelIndex &index); + void _q_goToDirectory(const QString &); + void _q_useNameFilter(int index); + void _q_selectionChanged(); + void _q_goToUrl(const QUrl &url); + void _q_autoCompleteFileName(const QString &); + void _q_rowsInserted(const QModelIndex & parent); + void _q_fileRenamed(const QString &path, const QString oldName, const QString newName); + + // layout +#ifndef QT_NO_PROXYMODEL + QAbstractProxyModel *proxyModel; +#endif + + // data + QStringList watching; + QFileSystemModel *model; + +#ifndef QT_NO_FSCOMPLETER + QFSCompleter *completer; +#endif //QT_NO_FSCOMPLETER + + QFileDialog::FileMode fileMode; + QFileDialog::AcceptMode acceptMode; + bool confirmOverwrite; + QString defaultSuffix; + QString setWindowTitle; + + QStringList currentHistory; + int currentHistoryLocation; + + QAction *renameAction; + QAction *deleteAction; + QAction *showHiddenAction; + QAction *newFolderAction; + + bool useDefaultCaption; + bool defaultFileTypes; + bool fileNameLabelExplicitlySat; + QStringList nameFilters; + + // Members for using native dialogs: + bool nativeDialogInUse; + // setVisible_sys returns true if it ends up showing a native + // dialog. Returning false means that a non-native dialog must be + // used instead. + bool canBeNativeDialog(); + bool setVisible_sys(bool visible); + void deleteNativeDialog_sys(); + QDialog::DialogCode dialogResultCode_sys(); + + void setDirectory_sys(const QString &directory); + QString directory_sys() const; + void selectFile_sys(const QString &filename); + QStringList selectedFiles_sys() const; + void setFilter_sys(); + void setNameFilters_sys(const QStringList &filters); + void selectNameFilter_sys(const QString &filter); + QString selectedNameFilter_sys() const; + ////////////////////////////////////////////// + +#if defined(Q_WS_MAC) + void *mDelegate; +#ifndef QT_MAC_USE_COCOA + NavDialogRef mDialog; + bool mDialogStarted; + bool mDialogClosed; + QString mCurrentLocation; + QString mCurrentSelection; + QStringList mCurrentSelectionList; + + struct QtMacFilterName { + QString description; + QString regexp; + QString filter; + }; + struct QtMacNavFilterInfo { + QtMacNavFilterInfo() : currentSelection(-1) {} + int currentSelection; + QList filters; + } filterInfo; + + static void qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg, NavCBRecPtr p, + NavCallBackUserData data); + static Boolean qt_mac_filedialog_filter_proc(AEDesc *theItem, void *info, void *data, + NavFilterModes); + bool showCarbonNavServicesDialog(); + bool hideCarbonNavServicesDialog(); + void createNavServicesDialog(); +#else + bool showCocoaFilePanel(); + bool hideCocoaFilePanel(); +#endif + void createNSOpenSavePanelDelegate(); + void QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath); + void QNSOpenSavePanelDelegate_panelClosed(bool accepted); + void QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir); + void QNSOpenSavePanelDelegate_filterSelected(int menuIndex); + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); +#endif + + QScopedPointer qFileDialogUi; + + QString acceptLabel; + + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + QByteArray signalToDisconnectOnClose; + + QFileDialog::Options opts; + + ~QFileDialogPrivate(); + +private: + Q_DISABLE_COPY(QFileDialogPrivate) +}; + +class QFileDialogLineEdit : public QLineEdit +{ +public: + QFileDialogLineEdit(QWidget *parent = 0) : QLineEdit(parent), hideOnEsc(false), d_ptr(0){} + void init(QFileDialogPrivate *d_pointer) {d_ptr = d_pointer; } + void keyPressEvent(QKeyEvent *e); + bool hideOnEsc; +private: + QFileDialogPrivate *d_ptr; +}; + +class QFileDialogComboBox : public QComboBox +{ +public: + QFileDialogComboBox(QWidget *parent = 0) : QComboBox(parent), urlModel(0) {} + void init(QFileDialogPrivate *d_pointer); + void showPopup(); + void setHistory(const QStringList &paths); + QStringList history() const { return m_history; } + void paintEvent(QPaintEvent *); + +private: + QUrlModel *urlModel; + QFileDialogPrivate *d_ptr; + QStringList m_history; +}; + +class QFileDialogListView : public QListView +{ +public: + QFileDialogListView(QWidget *parent = 0); + void init(QFileDialogPrivate *d_pointer); + QSize sizeHint() const; +protected: + void keyPressEvent(QKeyEvent *e); +private: + QFileDialogPrivate *d_ptr; +}; + +class QFileDialogTreeView : public QTreeView +{ +public: + QFileDialogTreeView(QWidget *parent); + void init(QFileDialogPrivate *d_pointer); + QSize sizeHint() const; + +protected: + void keyPressEvent(QKeyEvent *e); +private: + QFileDialogPrivate *d_ptr; +}; + +inline QModelIndex QFileDialogPrivate::mapToSource(const QModelIndex &index) const { +#ifdef QT_NO_PROXYMODEL + return index; +#else + return proxyModel ? proxyModel->mapToSource(index) : index; +#endif +} +inline QModelIndex QFileDialogPrivate::mapFromSource(const QModelIndex &index) const { +#ifdef QT_NO_PROXYMODEL + return index; +#else + return proxyModel ? proxyModel->mapFromSource(index) : index; +#endif +} + +inline QString QFileDialogPrivate::rootPath() const { + return model->rootPath(); +} + +#ifndef Q_WS_MAC + // Dummies for platforms that don't use native dialogs: + inline void QFileDialogPrivate::deleteNativeDialog_sys() { qt_guiPlatformPlugin()->fileDialogDelete(q_func()); } + inline bool QFileDialogPrivate::setVisible_sys(bool visible) { return qt_guiPlatformPlugin()->fileDialogSetVisible(q_func(), visible); } + inline QDialog::DialogCode QFileDialogPrivate::dialogResultCode_sys(){ return qt_guiPlatformPlugin()->fileDialogResultCode(q_func()); } + inline void QFileDialogPrivate::setDirectory_sys(const QString &directory) { qt_guiPlatformPlugin()->fileDialogSetDirectory(q_func(), directory); } + inline QString QFileDialogPrivate::directory_sys() const { return qt_guiPlatformPlugin()->fileDialogDirectory(q_func()); } + inline void QFileDialogPrivate::selectFile_sys(const QString &filename) { qt_guiPlatformPlugin()->fileDialogSelectFile(q_func(), filename); } + inline QStringList QFileDialogPrivate::selectedFiles_sys() const { return qt_guiPlatformPlugin()->fileDialogSelectedFiles(q_func()); } + inline void QFileDialogPrivate::setFilter_sys() { qt_guiPlatformPlugin()->fileDialogSetFilter(q_func()); } + inline void QFileDialogPrivate::setNameFilters_sys(const QStringList &filters) { qt_guiPlatformPlugin()->fileDialogSetNameFilters(q_func(), filters); } + inline void QFileDialogPrivate::selectNameFilter_sys(const QString &filter) { qt_guiPlatformPlugin()->fileDialogSelectNameFilter(q_func(), filter); } + inline QString QFileDialogPrivate::selectedNameFilter_sys() const { return qt_guiPlatformPlugin()->fileDialogSelectedNameFilter(q_func()); } +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + +#endif // QFILEDIALOG_P_H diff --git a/src/gui/dialogs/qfiledialog_symbian.cpp b/src/gui/dialogs/qfiledialog_symbian.cpp new file mode 100644 index 0000000000..ed9895019f --- /dev/null +++ b/src/gui/dialogs/qfiledialog_symbian.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +#include +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) +#include +#include +#include +#include +#endif +#include "private/qcore_symbian_p.h" + +QT_BEGIN_NAMESPACE + +extern QStringList qt_make_filter_list(const QString &filter); // defined in qfiledialog.cpp +extern QStringList qt_clean_filter_list(const QString &filter); // defined in qfiledialog.cpp + +enum DialogMode { DialogOpen, DialogSave, DialogFolder }; +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) +class CExtensionFilter : public MAknFileFilter +{ +public: + void setFilter(const QString filter) + { + QStringList unparsedFiltersList = qt_make_filter_list(filter); + QStringList filterList; + filterRxList.clear(); + + foreach (QString unparsedFilter, unparsedFiltersList) { + filterList << qt_clean_filter_list(unparsedFilter); + } + foreach (QString currentFilter, filterList) { + QRegExp filterRx(currentFilter, Qt::CaseInsensitive, QRegExp::Wildcard); + filterRxList << filterRx; + } + } + + TBool Accept(const TDesC &/*aDriveAndPath*/, const TEntry &aEntry) const + { + //If no filter for files, all can be accepted + if (filterRxList.isEmpty()) + return ETrue; + + if (aEntry.IsDir()) + return ETrue; + + foreach (QRegExp rx, filterRxList) { + QString fileName = qt_TDesC2QString(aEntry.iName); + if (rx.exactMatch(fileName)) + return ETrue; + } + + return EFalse; + } + +private: + QList filterRxList; +}; +#endif + +static QString launchSymbianDialog(const QString dialogCaption, const QString startDirectory, + const QString filter, DialogMode dialogMode) +{ + QString selection; +#if defined(Q_WS_S60) && defined(SYMBIAN_VERSION_SYMBIAN3) + TFileName startFolder; + if (!startDirectory.isEmpty()) { + QString dir = QDir::toNativeSeparators(QFileDialogPrivate::workingDirectory(startDirectory)); + startFolder = qt_QString2TPtrC(dir); + } + TInt types = AknCommonDialogsDynMem::EMemoryTypeMMCExternal| + AknCommonDialogsDynMem::EMemoryTypeInternalMassStorage| + AknCommonDialogsDynMem::EMemoryTypePhone; + + TPtrC titlePtr(qt_QString2TPtrC(dialogCaption)); + TFileName target; + bool select = false; + int tryCount = 2; + while (tryCount--) { + TInt err(KErrNone); + TRAP(err, + if (dialogMode == DialogOpen) { + CExtensionFilter* extensionFilter = new (ELeave) CExtensionFilter; + CleanupStack::PushL(extensionFilter); + extensionFilter->setFilter(filter); + select = AknCommonDialogsDynMem::RunSelectDlgLD(types, target, + startFolder, 0, 0, titlePtr, extensionFilter); + CleanupStack::Pop(extensionFilter); + } else if (dialogMode == DialogSave) { + QString defaultFileName = QFileDialogPrivate::initialSelection(startDirectory); + target = qt_QString2TPtrC(defaultFileName); + select = AknCommonDialogsDynMem::RunSaveDlgLD(types, target, + startFolder, 0, 0, titlePtr); + } else if (dialogMode == DialogFolder) { + select = AknCommonDialogsDynMem::RunFolderSelectDlgLD(types, target, startFolder, + 0, 0, titlePtr, NULL, NULL); + } + ); + + if (err == KErrNone) { + tryCount = 0; + } else { + // Symbian native file dialog doesn't allow accessing files outside C:/Data + // It will always leave in that case, so default into QDir::rootPath() in error cases. + QString dir = QDir::toNativeSeparators(QDir::rootPath()); + startFolder = qt_QString2TPtrC(dir); + } + } + if (select) { + QFileInfo fi(qt_TDesC2QString(target)); + selection = fi.absoluteFilePath(); + } +#endif + return selection; +} + +QString qtSymbianGetOpenFileName(const QString &caption, + const QString &dir, + const QString &filter) +{ + return launchSymbianDialog(caption, dir, filter, DialogOpen); +} + +QStringList qtSymbianGetOpenFileNames(const QString &caption, + const QString &dir, + const QString &filter) +{ + QString fileName; + fileName.append(launchSymbianDialog(caption, dir, filter, DialogOpen)); + QStringList fileList; + fileList << fileName; + + return fileList; +} + +QString qtSymbianGetSaveFileName(const QString &caption, + const QString &dir) +{ + return launchSymbianDialog(caption, dir, QString(), DialogSave); +} + +QString qtSymbianGetExistingDirectory(const QString &caption, + const QString &dir) +{ + QString folderCaption; + if (!caption.isEmpty()) { + folderCaption.append(caption); + } else { + // Title for folder selection dialog is mandatory + folderCaption.append(QFileDialog::tr("Find Directory")); + } + return launchSymbianDialog(folderCaption, dir, QString(), DialogFolder); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qfiledialog_win.cpp b/src/gui/dialogs/qfiledialog_win.cpp new file mode 100644 index 0000000000..114f4bedbc --- /dev/null +++ b/src/gui/dialogs/qfiledialog_win.cpp @@ -0,0 +1,825 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfiledialog.h" + +#ifndef QT_NO_FILEDIALOG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qfiledialog_win_p.h" + +#ifndef QT_NO_THREAD +# include +#endif + +#ifdef Q_WS_WINCE +#include +bool qt_priv_ptr_valid = false; +#else +//we have to declare them here because they're not present for all SDK/compilers +static const IID QT_IID_IFileOpenDialog = {0xd57c7288, 0xd4ad, 0x4768, {0xbe, 0x02, 0x9d, 0x96, 0x95, 0x32, 0xd9, 0x60} }; +static const IID QT_IID_IShellItem = {0x43826d1e, 0xe718, 0x42ee, {0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe} }; +static const CLSID QT_CLSID_FileOpenDialog = {0xdc1c5a9c, 0xe88a, 0x4dde, {0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7} }; +#endif + + +typedef qt_LPITEMIDLIST (WINAPI *PtrSHBrowseForFolder)(qt_BROWSEINFO*); +static PtrSHBrowseForFolder ptrSHBrowseForFolder = 0; +typedef BOOL (WINAPI *PtrSHGetPathFromIDList)(qt_LPITEMIDLIST, LPWSTR); +static PtrSHGetPathFromIDList ptrSHGetPathFromIDList = 0; +typedef HRESULT (WINAPI *PtrSHGetMalloc)(LPMALLOC *); +static PtrSHGetMalloc ptrSHGetMalloc = 0; + + +QT_BEGIN_NAMESPACE + +static void qt_win_resolve_libs() +{ + static bool triedResolve = false; + + if (!triedResolve) { +#ifndef QT_NO_THREAD + // protect initialization + QMutexLocker locker(QMutexPool::globalInstanceGet(&triedResolve)); + // check triedResolve again, since another thread may have already + // done the initialization + if (triedResolve) { + // another thread did initialize the security function pointers, + // so we shouldn't do it again. + return; + } +#endif + + triedResolve = true; +#if !defined(Q_WS_WINCE) + QSystemLibrary lib(L"shell32"); + ptrSHBrowseForFolder = (PtrSHBrowseForFolder)lib.resolve("SHBrowseForFolderW"); + ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)lib.resolve("SHGetPathFromIDListW"); + ptrSHGetMalloc = (PtrSHGetMalloc)lib.resolve("SHGetMalloc"); +#else + // CE stores them in a different lib and does not use unicode version + HINSTANCE handle = LoadLibrary(L"Ceshell"); + ptrSHBrowseForFolder = (PtrSHBrowseForFolder)GetProcAddress(handle, L"SHBrowseForFolder"); + ptrSHGetPathFromIDList = (PtrSHGetPathFromIDList)GetProcAddress(handle, L"SHGetPathFromIDList"); + ptrSHGetMalloc = (PtrSHGetMalloc)GetProcAddress(handle, L"SHGetMalloc"); + if (ptrSHBrowseForFolder && ptrSHGetPathFromIDList && ptrSHGetMalloc) + qt_priv_ptr_valid = true; +#endif + } +} + +extern const char* qt_file_dialog_filter_reg_exp; // defined in qfiledialog.cpp +extern QStringList qt_make_filter_list(const QString &filter); + +const int maxNameLen = 1023; +const int maxMultiLen = 65535; + +// Returns the wildcard part of a filter. +static QString qt_win_extract_filter(const QString &rawFilter) +{ + QString result = rawFilter; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + int index = r.indexIn(result); + if (index >= 0) + result = r.cap(2); + QStringList list = result.split(QLatin1Char(' ')); + for(QStringList::iterator it = list.begin(); it < list.end(); ++it) { + if (*it == QLatin1String("*")) { + *it = QLatin1String("*.*"); + break; + } + } + return list.join(QLatin1String(";")); +} + +static QStringList qt_win_make_filters_list(const QString &filter) +{ + QString f(filter); + + if (f.isEmpty()) + f = QFileDialog::tr("All Files (*.*)"); + + return qt_make_filter_list(f); +} + +// Makes a NUL-oriented Windows filter from a Qt filter. +static QString qt_win_filter(const QString &filter, bool hideFiltersDetails) +{ + QStringList filterLst = qt_win_make_filters_list(filter); + QStringList::Iterator it = filterLst.begin(); + QString winfilters; + QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp)); + for (; it != filterLst.end(); ++it) { + QString subfilter = *it; + if (!subfilter.isEmpty()) { + if (hideFiltersDetails) { + int index = r.indexIn(subfilter); + if (index >= 0) + winfilters += r.cap(1); + } else { + winfilters += subfilter; + } + winfilters += QChar(); + winfilters += qt_win_extract_filter(subfilter); + winfilters += QChar(); + } + } + winfilters += QChar(); + return winfilters; +} + +static QString qt_win_selected_filter(const QString &filter, DWORD idx) +{ + return qt_win_make_filters_list(filter).at((int)idx - 1); +} + +static QString tFilters, tTitle, tInitDir; + +static OPENFILENAME* qt_win_make_OFN(QWidget *parent, + const QString& initialSelection, + const QString& initialDirectory, + const QString& title, + const QString& filters, + QFileDialog::FileMode mode, + QFileDialog::Options options) +{ + if (parent) + parent = parent->window(); + else + parent = QApplication::activeWindow(); + + tInitDir = QDir::toNativeSeparators(initialDirectory); + tFilters = filters; + tTitle = title; + QString initSel = QDir::toNativeSeparators(initialSelection); + if (!initSel.isEmpty()) { + initSel.remove(QLatin1Char('<')); + initSel.remove(QLatin1Char('>')); + initSel.remove(QLatin1Char('\"')); + initSel.remove(QLatin1Char('|')); + } + + int maxLen = mode == QFileDialog::ExistingFiles ? maxMultiLen : maxNameLen; + wchar_t *tInitSel = new wchar_t[maxLen + 1]; + if (initSel.length() > 0 && initSel.length() <= maxLen) + memcpy(tInitSel, initSel.utf16(), (initSel.length()+1)*sizeof(QChar)); + else + tInitSel[0] = 0; + + OPENFILENAME* ofn = new OPENFILENAME; + memset(ofn, 0, sizeof(OPENFILENAME)); + + ofn->lStructSize = sizeof(OPENFILENAME); + ofn->hwndOwner = parent ? parent->winId() : 0; + ofn->lpstrFilter = (wchar_t*)tFilters.utf16(); + ofn->lpstrFile = tInitSel; + ofn->nMaxFile = maxLen; + ofn->lpstrInitialDir = (wchar_t*)tInitDir.utf16(); + ofn->lpstrTitle = (wchar_t*)tTitle.utf16(); + ofn->Flags = (OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_PATHMUSTEXIST); + if (mode == QFileDialog::ExistingFile || + mode == QFileDialog::ExistingFiles) + ofn->Flags |= (OFN_FILEMUSTEXIST); + if (mode == QFileDialog::ExistingFiles) + ofn->Flags |= (OFN_ALLOWMULTISELECT); + if (!(options & QFileDialog::DontConfirmOverwrite)) + ofn->Flags |= OFN_OVERWRITEPROMPT; + + return ofn; +} + +static void qt_win_clean_up_OFN(OPENFILENAME **ofn) +{ + delete [] (*ofn)->lpstrFile; + delete *ofn; + *ofn = 0; +} + +extern void qt_win_eatMouseMove(); + +QString qt_win_get_open_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QString result; + + QString isel = args.selection; + + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + QFileInfo fi(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + if (isel.isEmpty()) + isel = fi.fileName(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + int idx = 0; + if (selectedFilter) { + QStringList filterLst = qt_win_make_filters_list(args.filter); + idx = filterLst.indexOf(*selectedFilter); + } + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::ExistingFile, + args.options); + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetOpenFileName(ofn)) { + result = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + } + qt_win_clean_up_OFN(&ofn); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (result.isEmpty()) + return result; + + fi = result; + *initialDirectory = fi.path(); + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + return fi.absoluteFilePath(); +} + +QString qt_win_get_save_file_name(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QString result; + + QString isel = args.selection; + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + QFileInfo fi(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + if (isel.isEmpty()) + isel = fi.fileName(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + int idx = 0; + if (selectedFilter) { + QStringList filterLst = qt_win_make_filters_list(args.filter); + idx = filterLst.indexOf(*selectedFilter); + } + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + // This block is used below for the lpstrDefExt member. + // Note that the current MSDN docs document this member wrong. + // It should rather be documented as "the default extension if no extension was given and if the + // current filter does not have a extension (e.g (*)). If the current filter have an extension, use + // the extension of the current filter" + QString defaultSaveExt; + if (selectedFilter && !selectedFilter->isEmpty()) { + defaultSaveExt = qt_win_extract_filter(*selectedFilter); + // make sure we only have the extension + int firstDot = defaultSaveExt.indexOf(QLatin1Char('.')); + if (firstDot != -1) { + defaultSaveExt.remove(0, firstDot + 1); + } else { + defaultSaveExt.clear(); + } + } + + OPENFILENAME *ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::AnyFile, + args.options); + + ofn->lpstrDefExt = (wchar_t*)defaultSaveExt.utf16(); + + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetSaveFileName(ofn)) { + result = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + } + qt_win_clean_up_OFN(&ofn); + +#if defined(Q_WS_WINCE) + int semIndex = result.indexOf(QLatin1Char(';')); + if (semIndex >= 0) + result = result.left(semIndex); +#endif + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (result.isEmpty()) + return result; + + fi = result; + *initialDirectory = fi.path(); + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + return fi.absoluteFilePath(); +} + + +#ifndef Q_WS_WINCE + +typedef HRESULT (WINAPI *PtrSHCreateItemFromParsingName)(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv); +static PtrSHCreateItemFromParsingName pSHCreateItemFromParsingName = 0; + +static bool qt_win_set_IFileDialogOptions(IFileDialog *pfd, + const QString& initialSelection, + const QString& initialDirectory, + const QString& title, + const QStringList& filterLst, + QFileDialog::FileMode mode, + QFileDialog::Options options) +{ + if (!pSHCreateItemFromParsingName) { + // This function is available only in Vista & above. + QSystemLibrary shellLib(QLatin1String("Shell32")); + pSHCreateItemFromParsingName = (PtrSHCreateItemFromParsingName) + shellLib.resolve("SHCreateItemFromParsingName"); + if (!pSHCreateItemFromParsingName) + return false; + } + HRESULT hr; + QString winfilters; + int numFilters = 0; + quint32 currentOffset = 0; + QList offsets; + QStringList::ConstIterator it = filterLst.begin(); + // Create the native filter string and save offset to each entry. + for (; it != filterLst.end(); ++it) { + QString subfilter = *it; + if (!subfilter.isEmpty()) { + offsets<SetFileTypes(numFilters, filterSpec); + delete []filterSpec; + } + // Set the starting folder. + tInitDir = QDir::toNativeSeparators(initialDirectory); + if (!tInitDir.isEmpty()) { + IShellItem *psiDefaultFolder; + hr = pSHCreateItemFromParsingName((wchar_t*)tInitDir.utf16(), NULL, QT_IID_IShellItem, + reinterpret_cast(&psiDefaultFolder)); + + if (SUCCEEDED(hr)) { + hr = pfd->SetFolder(psiDefaultFolder); + psiDefaultFolder->Release(); + } + } + // Set the currently selected file. + QString initSel = QDir::toNativeSeparators(initialSelection); + if (!initSel.isEmpty()) { + initSel.remove(QLatin1Char('<')); + initSel.remove(QLatin1Char('>')); + initSel.remove(QLatin1Char('\"')); + initSel.remove(QLatin1Char('|')); + } + if (!initSel.isEmpty()) { + hr = pfd->SetFileName((wchar_t*)initSel.utf16()); + } + // Set the title for the file dialog. + if (!title.isEmpty()) { + hr = pfd->SetTitle((wchar_t*)title.utf16()); + } + // Set other flags for the dialog. + DWORD newOptions; + hr = pfd->GetOptions(&newOptions); + if (SUCCEEDED(hr)) { + newOptions |= FOS_NOCHANGEDIR; + if (mode == QFileDialog::ExistingFile || + mode == QFileDialog::ExistingFiles) + newOptions |= (FOS_FILEMUSTEXIST | FOS_PATHMUSTEXIST); + if (mode == QFileDialog::ExistingFiles) + newOptions |= FOS_ALLOWMULTISELECT; + if (!(options & QFileDialog::DontConfirmOverwrite)) + newOptions |= FOS_OVERWRITEPROMPT; + hr = pfd->SetOptions(newOptions); + } + return SUCCEEDED(hr); +} + +static QStringList qt_win_CID_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + const QStringList &filterList, + QString *selectedFilter, + int selectedFilterIndex) +{ + QStringList result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + // Multiple selection is allowed only in IFileOpenDialog. + IFileOpenDialog *pfd = 0; + HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, QT_IID_IFileOpenDialog, + reinterpret_cast(&pfd)); + + if (SUCCEEDED(hr)) { + qt_win_set_IFileDialogOptions(pfd, args.selection, + args.directory, args.caption, + filterList, QFileDialog::ExistingFiles, + args.options); + // Set the currently selected filter (one-based index). + hr = pfd->SetFileTypeIndex(selectedFilterIndex+1); + QWidget *parentWindow = args.parent; + if (parentWindow) + parentWindow = parentWindow->window(); + else + parentWindow = QApplication::activeWindow(); + // Show the file dialog. + hr = pfd->Show(parentWindow ? parentWindow->winId() : 0); + if (SUCCEEDED(hr)) { + // Retrieve the results. + IShellItemArray *psiaResults; + hr = pfd->GetResults(&psiaResults); + if (SUCCEEDED(hr)) { + DWORD numItems = 0; + psiaResults->GetCount(&numItems); + for (DWORD i = 0; iGetItemAt(i, &psi); + if (SUCCEEDED(hr)) { + // Retrieve the file name from shell item. + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + QString fileName = QString::fromWCharArray(pszPath); + result.append(fileName); + CoTaskMemFree(pszPath); + } + psi->Release(); // Free the current item. + } + } + psiaResults->Release(); // Free the array of items. + } + } + } + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) { + // Retrieve the current folder name. + IShellItem *psi = 0; + hr = pfd->GetFolder(&psi); + if (SUCCEEDED(hr)) { + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + *initialDirectory = QString::fromWCharArray(pszPath); + CoTaskMemFree(pszPath); + } + psi->Release(); + } + // Retrieve the currently selected filter. + if (selectedFilter) { + quint32 filetype = 0; + hr = pfd->GetFileTypeIndex(&filetype); + if (SUCCEEDED(hr) && filetype && filetype <= (quint32)filterList.length()) { + // This is a one-based index, not zero-based. + *selectedFilter = filterList[filetype-1]; + } + } + } + if (pfd) + pfd->Release(); + return result; +} + +QString qt_win_CID_get_existing_directory(const QFileDialogArgs &args) +{ + QString result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + IFileOpenDialog *pfd = 0; + HRESULT hr = CoCreateInstance(QT_CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, + QT_IID_IFileOpenDialog, reinterpret_cast(&pfd)); + + if (SUCCEEDED(hr)) { + qt_win_set_IFileDialogOptions(pfd, args.selection, + args.directory, args.caption, + QStringList(), QFileDialog::ExistingFiles, + args.options); + + // Set the FOS_PICKFOLDERS flag + DWORD newOptions; + hr = pfd->GetOptions(&newOptions); + newOptions |= (FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + if (SUCCEEDED(hr) && SUCCEEDED((hr = pfd->SetOptions(newOptions)))) { + QWidget *parentWindow = args.parent; + if (parentWindow) + parentWindow = parentWindow->window(); + else + parentWindow = QApplication::activeWindow(); + + // Show the file dialog. + hr = pfd->Show(parentWindow ? parentWindow->winId() : 0); + if (SUCCEEDED(hr)) { + // Retrieve the result + IShellItem *psi = 0; + hr = pfd->GetResult(&psi); + if (SUCCEEDED(hr)) { + // Retrieve the file name from shell item. + wchar_t *pszPath; + hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + if (SUCCEEDED(hr)) { + result = QString::fromWCharArray(pszPath); + CoTaskMemFree(pszPath); + } + psi->Release(); // Free the current item. + } + } + } + } + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (pfd) + pfd->Release(); + return result; +} + +#endif + +QStringList qt_win_get_open_file_names(const QFileDialogArgs &args, + QString *initialDirectory, + QString *selectedFilter) +{ + QFileInfo fi; + QDir dir; + + if (initialDirectory && initialDirectory->left(5) == QLatin1String("file:")) + initialDirectory->remove(0, 5); + fi = QFileInfo(*initialDirectory); + + if (initialDirectory && !fi.isDir()) { + *initialDirectory = fi.absolutePath(); + } + + if (!fi.exists()) + *initialDirectory = QDir::homePath(); + + DWORD selFilIdx = 0; + + QStringList filterLst = qt_win_make_filters_list(args.filter); + int idx = 0; + if (selectedFilter) { + idx = filterLst.indexOf(*selectedFilter); + } + // Windows Vista (& above) allows users to search from file dialogs. If user selects + // multiple files belonging to different folders from these search results, the + // GetOpenFileName() will return only one folder name for all the files. To retrieve + // the correct path for all selected files, we have to use Common Item Dialog interfaces. +#ifndef Q_WS_WINCE + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + return qt_win_CID_get_open_file_names(args, initialDirectory, filterLst, selectedFilter, idx); +#endif + + QStringList result; + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(args.parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + bool hideFiltersDetails = args.options & QFileDialog::HideNameFilterDetails; + OPENFILENAME* ofn = qt_win_make_OFN(args.parent, args.selection, + args.directory, args.caption, + qt_win_filter(args.filter, hideFiltersDetails), + QFileDialog::ExistingFiles, + args.options); + if (idx) + ofn->nFilterIndex = idx + 1; + if (GetOpenFileName(ofn)) { + QString fileOrDir = QString::fromWCharArray(ofn->lpstrFile); + selFilIdx = ofn->nFilterIndex; + int offset = fileOrDir.length() + 1; + if (ofn->lpstrFile[offset] == 0) { + // Only one file selected; has full path + fi.setFile(fileOrDir); + QString res = fi.absoluteFilePath(); + if (!res.isEmpty()) + result.append(res); + } + else { + // Several files selected; first string is path + dir.setPath(fileOrDir); + QString f; + while(!(f = QString::fromWCharArray(ofn->lpstrFile + offset)).isEmpty()) { + fi.setFile(dir, f); + QString res = fi.absoluteFilePath(); + if (!res.isEmpty()) + result.append(res); + offset += f.length() + 1; + } + } + } + qt_win_clean_up_OFN(&ofn); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) { + *initialDirectory = fi.path(); // only save the path if there is a result + if (selectedFilter) + *selectedFilter = qt_win_selected_filter(args.filter, selFilIdx); + } + return result; +} + +// MFC Directory Dialog. Contrib: Steve Williams (minor parts from Scott Powers) + +static int __stdcall winGetExistDirCallbackProc(HWND hwnd, + UINT uMsg, + LPARAM lParam, + LPARAM lpData) +{ + if (uMsg == BFFM_INITIALIZED && lpData != 0) { + QString *initDir = (QString *)(lpData); + if (!initDir->isEmpty()) { + SendMessage(hwnd, BFFM_SETSELECTION, TRUE, LPARAM(initDir->utf16())); + } + } else if (uMsg == BFFM_SELCHANGED) { + qt_win_resolve_libs(); + if (ptrSHGetPathFromIDList) { + wchar_t path[MAX_PATH]; + ptrSHGetPathFromIDList(qt_LPITEMIDLIST(lParam), path); + QString tmpStr = QString::fromWCharArray(path); + if (!tmpStr.isEmpty()) + SendMessage(hwnd, BFFM_ENABLEOK, 1, 1); + else + SendMessage(hwnd, BFFM_ENABLEOK, 0, 0); + SendMessage(hwnd, BFFM_SETSTATUSTEXT, 1, LPARAM(path)); + } + } + return 0; +} + +QString qt_win_get_existing_directory(const QFileDialogArgs &args) +{ +#ifndef Q_WS_WINCE + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + return qt_win_CID_get_existing_directory(args); +#endif + + QString currentDir = QDir::currentPath(); + QString result; + QWidget *parent = args.parent; + if (parent) + parent = parent->window(); + else + parent = QApplication::activeWindow(); + if (parent) + parent->createWinId(); + + QDialog modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + QString initDir = QDir::toNativeSeparators(args.directory); + wchar_t path[MAX_PATH]; + wchar_t initPath[MAX_PATH]; + initPath[0] = 0; + path[0] = 0; + tTitle = args.caption; + + qt_BROWSEINFO bi; + + Q_ASSERT(!parent ||parent->testAttribute(Qt::WA_WState_Created)); + bi.hwndOwner = (parent ? parent->winId() : 0); + bi.pidlRoot = NULL; + //### This does not seem to be respected? - the dialog always displays "Browse for folder" + bi.lpszTitle = (wchar_t*)tTitle.utf16(); + bi.pszDisplayName = initPath; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_NEWDIALOGSTYLE; + bi.lpfn = winGetExistDirCallbackProc; + bi.lParam = LPARAM(&initDir); + + qt_win_resolve_libs(); + if (ptrSHBrowseForFolder) { + qt_LPITEMIDLIST pItemIDList = ptrSHBrowseForFolder(&bi); + if (pItemIDList) { + ptrSHGetPathFromIDList(pItemIDList, path); + IMalloc *pMalloc; + if (ptrSHGetMalloc(&pMalloc) == NOERROR) { + pMalloc->Free(pItemIDList); + pMalloc->Release(); + result = QString::fromWCharArray(path); + } + } + } + tTitle = QString(); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + if (!result.isEmpty()) + result.replace(QLatin1Char('\\'), QLatin1Char('/')); + return result; +} + + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qfiledialog_win_p.h b/src/gui/dialogs/qfiledialog_win_p.h new file mode 100644 index 0000000000..7580f0ad6b --- /dev/null +++ b/src/gui/dialogs/qfiledialog_win_p.h @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#ifndef QFILEDIAG_WIN_P_H +#define QFILEDIAG_WIN_P_H + +//these are the interface declarations needed for the file dialog on Vista and up + +//At some point we can hope that all compilers/sdk will support that interface +//and we won't have to declare it ourselves + +//declarations +#define FOS_OVERWRITEPROMPT 0x2 +#define FOS_STRICTFILETYPES 0x4 +#define FOS_NOCHANGEDIR 0x8 +#define FOS_PICKFOLDERS 0x20 +#define FOS_FORCEFILESYSTEM 0x40 +#define FOS_ALLNONSTORAGEITEMS 0x80 +#define FOS_NOVALIDATE 0x100 +#define FOS_ALLOWMULTISELECT 0x200 +#define FOS_PATHMUSTEXIST 0x800 +#define FOS_FILEMUSTEXIST 0x1000 +#define FOS_CREATEPROMPT 0x2000 +#define FOS_SHAREAWARE 0x4000 +#define FOS_NOREADONLYRETURN 0x8000 +#define FOS_NOTESTFILECREATE 0x10000 +#define FOS_HIDEMRUPLACES 0x20000 +#define FOS_HIDEPINNEDPLACES 0x40000 +#define FOS_NODEREFERENCELINKS 0x100000 +#define FOS_DONTADDTORECENT 0x2000000 +#define FOS_FORCESHOWHIDDEN 0x10000000 +#define FOS_DEFAULTNOMINIMODE 0x20000000 +#define FOS_FORCEPREVIEWPANEON 0x40000000 + +typedef int GETPROPERTYSTOREFLAGS; +#define GPS_DEFAULT 0x00000000 +#define GPS_HANDLERPROPERTIESONLY 0x00000001 +#define GPS_READWRITE 0x00000002 +#define GPS_TEMPORARY 0x00000004 +#define GPS_FASTPROPERTIESONLY 0x00000008 +#define GPS_OPENSLOWITEM 0x00000010 +#define GPS_DELAYCREATION 0x00000020 +#define GPS_BESTEFFORT 0x00000040 +#define GPS_MASK_VALID 0x0000007F + +typedef int (QT_WIN_CALLBACK* BFFCALLBACK)(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData); +// message from browser +#define BFFM_INITIALIZED 1 +#define BFFM_SELCHANGED 2 +#define BFFM_ENABLEOK (WM_USER + 101) +#define BFFM_SETSELECTION (WM_USER + 103) +#define BFFM_SETSTATUSTEXT (WM_USER + 104) + +// Browsing for directory. +#define BIF_RETURNONLYFSDIRS 0x0001 +#define BIF_DONTGOBELOWDOMAIN 0x0002 +#define BIF_STATUSTEXT 0x0004 +#define BIF_RETURNFSANCESTORS 0x0008 +#define BIF_EDITBOX 0x0010 +#define BIF_VALIDATE 0x0020 +#define BIF_NEWDIALOGSTYLE 0x0040 +#define BIF_BROWSEINCLUDEURLS 0x0080 +#define BIF_UAHINT 0x0100 +#define BIF_NONEWFOLDERBUTTON 0x0200 +#define BIF_NOTRANSLATETARGETS 0x0400 +#define BIF_BROWSEFORCOMPUTER 0x1000 +#define BIF_BROWSEFORPRINTER 0x2000 +#define BIF_BROWSEINCLUDEFILES 0x4000 +#define BIF_SHAREABLE 0x8000 + +//the enums +typedef enum { + SIATTRIBFLAGS_AND = 0x1, + SIATTRIBFLAGS_OR = 0x2, + SIATTRIBFLAGS_APPCOMPAT = 0x3, + SIATTRIBFLAGS_MASK = 0x3 +} SIATTRIBFLAGS; +typedef enum { + SIGDN_NORMALDISPLAY = 0x00000000, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001c001, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_URL = 0x80068000 +} SIGDN; +typedef enum { + FDAP_BOTTOM = 0x00000000, + FDAP_TOP = 0x00000001 +} FDAP; +typedef enum { + FDESVR_DEFAULT = 0x00000000, + FDESVR_ACCEPT = 0x00000001, + FDESVR_REFUSE = 0x00000002 +} FDE_SHAREVIOLATION_RESPONSE; +typedef FDE_SHAREVIOLATION_RESPONSE FDE_OVERWRITE_RESPONSE; + +//the structs +typedef struct { + LPCWSTR pszName; + LPCWSTR pszSpec; +} qt_COMDLG_FILTERSPEC; +typedef struct { + GUID fmtid; + DWORD pid; +} qt_PROPERTYKEY; + +typedef struct { + USHORT cb; + BYTE abID[1]; +} qt_SHITEMID, *qt_LPSHITEMID; +typedef struct { + qt_SHITEMID mkid; +} qt_ITEMIDLIST, *qt_LPITEMIDLIST; +typedef const qt_ITEMIDLIST *qt_LPCITEMIDLIST; +typedef struct { + HWND hwndOwner; + qt_LPCITEMIDLIST pidlRoot; + LPWSTR pszDisplayName; + LPCWSTR lpszTitle; + UINT ulFlags; + BFFCALLBACK lpfn; + LPARAM lParam; + int iImage; +} qt_BROWSEINFO; + +DECLARE_INTERFACE(IFileDialogEvents); +DECLARE_INTERFACE_(IShellItem, IUnknown) +{ + STDMETHOD(BindToHandler)(THIS_ IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppv) PURE; + STDMETHOD(GetParent)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(GetDisplayName)(THIS_ SIGDN sigdnName, LPWSTR *ppszName) PURE; + STDMETHOD(GetAttributes)(THIS_ ULONG sfgaoMask, ULONG *psfgaoAttribs) PURE; + STDMETHOD(Compare)(THIS_ IShellItem *psi, DWORD hint, int *piOrder) PURE; +}; +DECLARE_INTERFACE_(IShellItemFilter, IUnknown) +{ + STDMETHOD(IncludeItem)(THIS_ IShellItem *psi) PURE; + STDMETHOD(GetEnumFlagsForItem)(THIS_ IShellItem *psi, DWORD *pgrfFlags) PURE; +}; +DECLARE_INTERFACE_(IEnumShellItems, IUnknown) +{ + STDMETHOD(Next)(THIS_ ULONG celt, IShellItem **rgelt, ULONG *pceltFetched) PURE; + STDMETHOD(Skip)(THIS_ ULONG celt) PURE; + STDMETHOD(Reset)(THIS_) PURE; + STDMETHOD(Clone)(THIS_ IEnumShellItems **ppenum) PURE; +}; +DECLARE_INTERFACE_(IShellItemArray, IUnknown) +{ + STDMETHOD(BindToHandler)(THIS_ IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppvOut) PURE; + STDMETHOD(GetPropertyStore)(THIS_ GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv) PURE; + STDMETHOD(GetPropertyDescriptionList)(THIS_ const qt_PROPERTYKEY *keyType, REFIID riid, void **ppv) PURE; + STDMETHOD(GetAttributes)(THIS_ SIATTRIBFLAGS dwAttribFlags, ULONG sfgaoMask, ULONG *psfgaoAttribs) PURE; + STDMETHOD(GetCount)(THIS_ DWORD *pdwNumItems) PURE; + STDMETHOD(GetItemAt)(THIS_ DWORD dwIndex, IShellItem **ppsi) PURE; + STDMETHOD(EnumItems)(THIS_ IEnumShellItems **ppenumShellItems) PURE; +}; +DECLARE_INTERFACE_(IModalWindow, IUnknown) +{ + STDMETHOD(Show)(THIS_ HWND hwndParent) PURE; +}; +DECLARE_INTERFACE_(IFileDialog, IModalWindow) +{ + STDMETHOD(SetFileTypes)(THIS_ UINT cFileTypes, const qt_COMDLG_FILTERSPEC *rgFilterSpec) PURE; + STDMETHOD(SetFileTypeIndex)(THIS_ UINT iFileType) PURE; + STDMETHOD(GetFileTypeIndex)(THIS_ UINT *piFileType) PURE; + STDMETHOD(Advise)(THIS_ IFileDialogEvents *pfde, DWORD *pdwCookie) PURE; + STDMETHOD(Unadvise)(THIS_ DWORD dwCookie) PURE; + STDMETHOD(SetOptions)(THIS_ DWORD fos) PURE; + STDMETHOD(GetOptions)(THIS_ DWORD *pfos) PURE; + STDMETHOD(SetDefaultFolder)(THIS_ IShellItem *psi) PURE; + STDMETHOD(SetFolder)(THIS_ IShellItem *psi) PURE; + STDMETHOD(GetFolder)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(GetCurrentSelection)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(SetFileName)(THIS_ LPCWSTR pszName) PURE; + STDMETHOD(GetFileName)(THIS_ LPWSTR *pszName) PURE; + STDMETHOD(SetTitle)(THIS_ LPCWSTR pszTitle) PURE; + STDMETHOD(SetOkButtonLabel)(THIS_ LPCWSTR pszText) PURE; + STDMETHOD(SetFileNameLabel)(THIS_ LPCWSTR pszLabel) PURE; + STDMETHOD(GetResult)(THIS_ IShellItem **ppsi) PURE; + STDMETHOD(AddPlace)(THIS_ IShellItem *psi, FDAP fdap) PURE; + STDMETHOD(SetDefaultExtension)(THIS_ LPCWSTR pszDefaultExtension) PURE; + STDMETHOD(Close)(THIS_ HRESULT hr) PURE; + STDMETHOD(SetClientGuid)(THIS_ REFGUID guid) PURE; + STDMETHOD(ClearClientData)(THIS_) PURE; + STDMETHOD(SetFilter)(THIS_ IShellItemFilter *pFilter) PURE; +}; +DECLARE_INTERFACE_(IFileDialogEvents, IUnknown) +{ + STDMETHOD(OnFileOk)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnFolderChanging)(THIS_ IFileDialog *pfd, IShellItem *psiFolder) PURE; + STDMETHOD(OnFolderChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnSelectionChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnShareViolation)(THIS_ IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse) PURE; + STDMETHOD(OnTypeChange)(THIS_ IFileDialog *pfd) PURE; + STDMETHOD(OnOverwrite)(THIS_ IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse) PURE; +}; +DECLARE_INTERFACE_(IFileOpenDialog, IFileDialog) +{ + STDMETHOD(GetResults)(THIS_ IShellItemArray **ppenum) PURE; + STDMETHOD(GetSelectedItems)(THIS_ IShellItemArray **ppsai) PURE; +}; +#endif \ No newline at end of file diff --git a/src/gui/dialogs/qfileinfogatherer.cpp b/src/gui/dialogs/qfileinfogatherer.cpp new file mode 100644 index 0000000000..b36b21e63d --- /dev/null +++ b/src/gui/dialogs/qfileinfogatherer.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfileinfogatherer_p.h" +#include +#include +#include +#ifndef Q_OS_WIN +# include +# include +#endif +#if defined(Q_OS_VXWORKS) +# include "qplatformdefs.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_FILESYSTEMMODEL + +#ifdef QT_BUILD_INTERNAL +static bool fetchedRoot = false; +Q_AUTOTEST_EXPORT void qt_test_resetFetchedRoot() +{ + fetchedRoot = false; +} + +Q_AUTOTEST_EXPORT bool qt_test_isFetchedRoot() +{ + return fetchedRoot; +} +#endif + +/*! + Creates thread +*/ +QFileInfoGatherer::QFileInfoGatherer(QObject *parent) + : QThread(parent), abort(false), +#ifndef QT_NO_FILESYSTEMWATCHER + watcher(0), +#endif + m_resolveSymlinks(false), m_iconProvider(&defaultProvider) +{ +#ifdef Q_OS_WIN + m_resolveSymlinks = true; +#elif !defined(Q_OS_INTEGRITY) + userId = getuid(); + groupId = getgid(); +#endif +#ifndef QT_NO_FILESYSTEMWATCHER + watcher = new QFileSystemWatcher(this); + connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(list(QString))); + connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(updateFile(QString))); +#endif + start(LowPriority); +} + +/*! + Destroys thread +*/ +QFileInfoGatherer::~QFileInfoGatherer() +{ + QMutexLocker locker(&mutex); + abort = true; + condition.wakeOne(); + locker.unlock(); + wait(); +} + +void QFileInfoGatherer::setResolveSymlinks(bool enable) +{ + Q_UNUSED(enable); +#ifdef Q_OS_WIN + QMutexLocker locker(&mutex); + m_resolveSymlinks = enable; +#endif +} + +bool QFileInfoGatherer::resolveSymlinks() const +{ + return m_resolveSymlinks; +} + +void QFileInfoGatherer::setIconProvider(QFileIconProvider *provider) +{ + QMutexLocker locker(&mutex); + m_iconProvider = provider; +} + +QFileIconProvider *QFileInfoGatherer::iconProvider() const +{ + return m_iconProvider; +} + +/*! + Fetch extended information for all \a files in \a path + + \sa updateFile(), update(), resolvedName() +*/ +void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStringList &files) +{ + QMutexLocker locker(&mutex); + // See if we already have this dir/file in our que + int loc = this->path.lastIndexOf(path); + while (loc > 0) { + if (this->files.at(loc) == files) { + return; + } + loc = this->path.lastIndexOf(path, loc - 1); + } + this->path.push(path); + this->files.push(files); + condition.wakeAll(); +} + +/*! + Fetch extended information for all \a filePath + + \sa fetchExtendedInformation() +*/ +void QFileInfoGatherer::updateFile(const QString &filePath) +{ + QString dir = filePath.mid(0, filePath.lastIndexOf(QDir::separator())); + QString fileName = filePath.mid(dir.length() + 1); + fetchExtendedInformation(dir, QStringList(fileName)); +} + +/* + List all files in \a directoryPath + + \sa listed() +*/ +void QFileInfoGatherer::clear() +{ +#ifndef QT_NO_FILESYSTEMWATCHER + QMutexLocker locker(&mutex); + watcher->removePaths(watcher->files()); + watcher->removePaths(watcher->directories()); +#endif +} + +/* + Remove a \a path from the watcher + + \sa listed() +*/ +void QFileInfoGatherer::removePath(const QString &path) +{ +#ifndef QT_NO_FILESYSTEMWATCHER + QMutexLocker locker(&mutex); + watcher->removePath(path); +#endif +} + +/* + List all files in \a directoryPath + + \sa listed() +*/ +void QFileInfoGatherer::list(const QString &directoryPath) +{ + fetchExtendedInformation(directoryPath, QStringList()); +} + +/* + Until aborted wait to fetch a directory or files +*/ +void QFileInfoGatherer::run() +{ + forever { + bool updateFiles = false; + QMutexLocker locker(&mutex); + if (abort) { + return; + } + if (this->path.isEmpty()) + condition.wait(&mutex); + QString path; + QStringList list; + if (!this->path.isEmpty()) { + path = this->path.first(); + list = this->files.first(); + this->path.pop_front(); + this->files.pop_front(); + updateFiles = true; + } + locker.unlock(); + if (updateFiles) + getFileInfos(path, list); + } +} + +QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const +{ + QExtendedInformation info(fileInfo); + info.icon = m_iconProvider->icon(fileInfo); + info.displayType = m_iconProvider->type(fileInfo); +#ifndef QT_NO_FILESYSTEMWATCHER + // ### Not ready to listen all modifications + #if 0 + // Enable the next two commented out lines to get updates when the file sizes change... + if (!fileInfo.exists() && !fileInfo.isSymLink()) { + info.size = -1; + //watcher->removePath(fileInfo.absoluteFilePath()); + } else { + if (!fileInfo.absoluteFilePath().isEmpty() && fileInfo.exists() && fileInfo.isReadable() + && !watcher->files().contains(fileInfo.absoluteFilePath())) { + //watcher->addPath(fileInfo.absoluteFilePath()); + } + } + #endif +#endif + + if (fileInfo.isSymLink() && m_resolveSymlinks) { + QFileInfo resolvedInfo(fileInfo.symLinkTarget()); + resolvedInfo = resolvedInfo.canonicalFilePath(); + if (resolvedInfo.exists()) { + emit nameResolved(fileInfo.filePath(), resolvedInfo.fileName()); + } + } + return info; +} + +QString QFileInfoGatherer::translateDriveName(const QFileInfo &drive) const +{ + QString driveName = drive.absoluteFilePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (driveName.startsWith(QLatin1Char('/'))) // UNC host + return drive.fileName(); +#endif +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + if (driveName.endsWith(QLatin1Char('/'))) + driveName.chop(1); +#endif + return driveName; +} + +/* + Get specific file info's, batch the files so update when we have 100 + items and every 200ms after that + */ +void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &files) +{ +#ifndef QT_NO_FILESYSTEMWATCHER + if (files.isEmpty() + && !watcher->directories().contains(path) + && !path.isEmpty() + && !path.startsWith(QLatin1String("//")) /*don't watch UNC path*/) { + watcher->addPath(path); + } +#endif + + // List drives + if (path.isEmpty()) { +#ifdef QT_BUILD_INTERNAL + fetchedRoot = true; +#endif + QFileInfoList infoList; + if (files.isEmpty()) { + infoList = QDir::drives(); + } else { + for (int i = 0; i < files.count(); ++i) + infoList << QFileInfo(files.at(i)); + } + for (int i = infoList.count() - 1; i >= 0; --i) { + QString driveName = translateDriveName(infoList.at(i)); + QList > updatedFiles; + updatedFiles.append(QPair(driveName, infoList.at(i))); + emit updates(path, updatedFiles); + } + return; + } + + QElapsedTimer base; + base.start(); + QFileInfo fileInfo; + bool firstTime = true; + QList > updatedFiles; + QStringList filesToCheck = files; + + QString itPath = QDir::fromNativeSeparators(files.isEmpty() ? path : QLatin1String("")); + QDirIterator dirIt(itPath, QDir::AllEntries | QDir::System | QDir::Hidden); + QStringList allFiles; + while(!abort && dirIt.hasNext()) { + dirIt.next(); + fileInfo = dirIt.fileInfo(); + allFiles.append(fileInfo.fileName()); + fetch(fileInfo, base, firstTime, updatedFiles, path); + } + if (!allFiles.isEmpty()) + emit newListOfFiles(path, allFiles); + + QStringList::const_iterator filesIt = filesToCheck.constBegin(); + while(!abort && filesIt != filesToCheck.constEnd()) { + fileInfo.setFile(path + QDir::separator() + *filesIt); + ++filesIt; + fetch(fileInfo, base, firstTime, updatedFiles, path); + } + if (!updatedFiles.isEmpty()) + emit updates(path, updatedFiles); + emit directoryLoaded(path); +} + +void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime, QList > &updatedFiles, const QString &path) { + updatedFiles.append(QPair(fileInfo.fileName(), fileInfo)); + QElapsedTimer current; + current.start(); + if ((firstTime && updatedFiles.count() > 100) || base.msecsTo(current) > 1000) { + emit updates(path, updatedFiles); + updatedFiles.clear(); + base = current; + firstTime = false; + } +} + +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE diff --git a/src/gui/dialogs/qfileinfogatherer_p.h b/src/gui/dialogs/qfileinfogatherer_p.h new file mode 100644 index 0000000000..db308ef57e --- /dev/null +++ b/src/gui/dialogs/qfileinfogatherer_p.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEINFOGATHERER_H +#define QFILEINFOGATHERER_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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QExtendedInformation { +public: + enum Type { Dir, File, System }; + + QExtendedInformation() {} + QExtendedInformation(const QFileInfo &info) : mFileInfo(info) {} + + inline bool isDir() { return type() == Dir; } + inline bool isFile() { return type() == File; } + inline bool isSystem() { return type() == System; } + + bool operator ==(const QExtendedInformation &fileInfo) const { + return mFileInfo == fileInfo.mFileInfo + && displayType == fileInfo.displayType + && permissions() == fileInfo.permissions(); + } + +#ifndef QT_NO_FSFILEENGINE + bool isCaseSensitive() const { + QFSFileEngine fe(mFileInfo.absoluteFilePath()); + return fe.caseSensitive(); + } +#endif + + QFile::Permissions permissions() const { + return mFileInfo.permissions(); + } + + Type type() const { + if (mFileInfo.isDir()) { + return QExtendedInformation::Dir; + } + if (mFileInfo.isFile()) { + return QExtendedInformation::File; + } + if (!mFileInfo.exists() && mFileInfo.isSymLink()) { + return QExtendedInformation::System; + } + return QExtendedInformation::System; + } + + bool isSymLink() const { + return mFileInfo.isSymLink(); + } + + bool isHidden() const { + return mFileInfo.isHidden(); + } + + QFileInfo fileInfo() const { + return mFileInfo; + } + + QDateTime lastModified() const { + return mFileInfo.lastModified(); + } + + qint64 size() const { + qint64 size = -1; + if (type() == QExtendedInformation::Dir) + size = 0; + if (type() == QExtendedInformation::File) + size = mFileInfo.size(); + if (!mFileInfo.exists() && !mFileInfo.isSymLink()) + size = -1; + return size; + } + + QString displayType; + QIcon icon; + +private : + QFileInfo mFileInfo; +}; + +class QFileIconProvider; + +#ifndef QT_NO_FILESYSTEMMODEL + +class Q_AUTOTEST_EXPORT QFileInfoGatherer : public QThread +{ +Q_OBJECT + +Q_SIGNALS: + void updates(const QString &directory, const QList > &updates); + void newListOfFiles(const QString &directory, const QStringList &listOfFiles) const; + void nameResolved(const QString &fileName, const QString &resolvedName) const; + void directoryLoaded(const QString &path); + +public: + QFileInfoGatherer(QObject *parent = 0); + ~QFileInfoGatherer(); + + void clear(); + void removePath(const QString &path); + QExtendedInformation getInfo(const QFileInfo &info) const; + +public Q_SLOTS: + void list(const QString &directoryPath); + void fetchExtendedInformation(const QString &path, const QStringList &files); + void updateFile(const QString &path); + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + +protected: + void run(); + void getFileInfos(const QString &path, const QStringList &files); + +private: + void fetch(const QFileInfo &info, QElapsedTimer &base, bool &firstTime, QList > &updatedFiles, const QString &path); + QString translateDriveName(const QFileInfo &drive) const; + + QMutex mutex; + QWaitCondition condition; + volatile bool abort; + + QStack path; + QStack files; + +#ifndef QT_NO_FILESYSTEMWATCHER + QFileSystemWatcher *watcher; +#endif + bool m_resolveSymlinks; + QFileIconProvider *m_iconProvider; + QFileIconProvider defaultProvider; +#ifndef Q_OS_WIN + uint userId; + uint groupId; +#endif +}; +#endif // QT_NO_FILESYSTEMMODEL + + +QT_END_NAMESPACE +#endif // QFILEINFOGATHERER_H + diff --git a/src/gui/dialogs/qfilesystemmodel.cpp b/src/gui/dialogs/qfilesystemmodel.cpp new file mode 100644 index 0000000000..cb8eb6ad66 --- /dev/null +++ b/src/gui/dialogs/qfilesystemmodel.cpp @@ -0,0 +1,2027 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfilesystemmodel_p.h" +#include "qfilesystemmodel.h" +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif +#ifdef Q_OS_WIN32 +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_FILESYSTEMMODEL + +/*! + \enum QFileSystemModel::Roles + \value FileIconRole + \value FilePathRole + \value FileNameRole + \value FilePermissions +*/ + +/*! + \class QFileSystemModel + \since 4.4 + + \brief The QFileSystemModel class provides a data model for the local filesystem. + + \ingroup model-view + + This class provides access to the local filesystem, providing functions + for renaming and removing files and directories, and for creating new + directories. In the simplest case, it can be used with a suitable display + widget as part of a browser or filter. + + QFileSystemModel can be accessed using the standard interface provided by + QAbstractItemModel, but it also provides some convenience functions that are + specific to a directory model. + The fileInfo(), isDir(), name(), and path() functions provide information + about the underlying files and directories related to items in the model. + Directories can be created and removed using mkdir(), rmdir(). + + \note QFileSystemModel requires an instance of a GUI application. + + \section1 Example Usage + + A directory model that displays the contents of a default directory + is usually constructed with a parent object: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 2 + + A tree view can be used to display the contents of the model + + \snippet doc/src/snippets/shareddirmodel/main.cpp 4 + + and the contents of a particular directory can be displayed by + setting the tree view's root index: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 7 + + The view's root index can be used to control how much of a + hierarchical model is displayed. QDirModel provides a convenience + function that returns a suitable model index for a path to a + directory within the model. + + \section1 Caching and Performance + + QFileSystemModel will not fetch any files or directories until setRootPath() + is called. This will prevent any unnecessary querying on the file system + until that point such as listing the drives on Windows. + + Unlike QDirModel, QFileSystemModel uses a separate thread to populate + itself so it will not cause the main thread to hang as the file system + is being queried. Calls to rowCount() will return 0 until the model + populates a directory. + + QFileSystemModel keeps a cache with file information. The cache is + automatically kept up to date using the QFileSystemWatcher. + + \sa {Model Classes} +*/ + +/*! + \fn bool QFileSystemModel::rmdir(const QModelIndex &index) const + + Removes the directory corresponding to the model item \a index in the + file system model and \bold{deletes the corresponding directory from the + file system}, returning true if successful. If the directory cannot be + removed, false is returned. + + \warning This function deletes directories from the file system; it does + \bold{not} move them to a location where they can be recovered. + + \sa remove() +*/ + +/*! + \fn QIcon QFileSystemModel::fileName(const QModelIndex &index) const + + Returns the file name for the item stored in the model under the given + \a index. +*/ + +/*! + \fn QIcon QFileSystemModel::fileIcon(const QModelIndex &index) const + + Returns the icon for the item stored in the model under the given + \a index. +*/ + +/*! + \fn QFileInfo QFileSystemModel::fileInfo(const QModelIndex &index) const + + Returns the QFileInfo for the item stored in the model under the given + \a index. +*/ + +/*! + \fn void QFileSystemModel::rootPathChanged(const QString &newPath); + + This signal is emitted whenever the root path has been changed to a \a newPath. +*/ + +/*! + \fn void QFileSystemModel::fileRenamed(const QString &path, const QString &oldName, const QString &newName) + + This signal is emitted whenever a file with the \a oldName is successfully + renamed to \a newName. The file is located in in the directory \a path. +*/ + +/*! + \since 4.7 + \fn void QFileSystemModel::directoryLoaded(const QString &path) + + This signal is emitted when the gatherer thread has finished to load the \a path. + +*/ + +/*! + \fn bool QFileSystemModel::remove(const QModelIndex &index) const + + Removes the model item \a index from the file system model and \bold{deletes the + corresponding file from the file system}, returning true if successful. If the + item cannot be removed, false is returned. + + \warning This function deletes files from the file system; it does \bold{not} + move them to a location where they can be recovered. + + \sa rmdir() +*/ + +bool QFileSystemModel::remove(const QModelIndex &aindex) const +{ + //### TODO optim + QString path = filePath(aindex); + QFileSystemModelPrivate * d = const_cast(d_func()); + d->fileInfoGatherer.removePath(path); + QDirIterator it(path, + QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + QStringList children; + while (it.hasNext()) + children.prepend(it.next()); + children.append(path); + + bool error = false; + for (int i = 0; i < children.count(); ++i) { + QFileInfo info(children.at(i)); + QModelIndex modelIndex = index(children.at(i)); + if (info.isDir()) { + QDir dir; + if (children.at(i) != path) + error |= remove(modelIndex); + error |= rmdir(modelIndex); + } else { + error |= QFile::remove(filePath(modelIndex)); + } + } + return error; +} + +/*! + Constructs a file system model with the given \a parent. +*/ +QFileSystemModel::QFileSystemModel(QObject *parent) + : QAbstractItemModel(*new QFileSystemModelPrivate, parent) +{ + Q_D(QFileSystemModel); + d->init(); +} + +/*! + \internal +*/ +QFileSystemModel::QFileSystemModel(QFileSystemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QFileSystemModel); + d->init(); +} + +/*! + Destroys this file system model. +*/ +QFileSystemModel::~QFileSystemModel() +{ +} + +/*! + \reimp +*/ +QModelIndex QFileSystemModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent)) + return QModelIndex(); + + // get the parent node + QFileSystemModelPrivate::QFileSystemNode *parentNode = (d->indexValid(parent) ? d->node(parent) : + const_cast(&d->root)); + Q_ASSERT(parentNode); + + // now get the internal pointer for the index + QString childName = parentNode->visibleChildren[d->translateVisibleLocation(parentNode, row)]; + const QFileSystemModelPrivate::QFileSystemNode *indexNode = parentNode->children.value(childName); + Q_ASSERT(indexNode); + + return createIndex(row, column, const_cast(indexNode)); +} + +/*! + \overload + + Returns the model item index for the given \a path and \a column. +*/ +QModelIndex QFileSystemModel::index(const QString &path, int column) const +{ + Q_D(const QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *node = d->node(path, false); + QModelIndex idx = d->index(node); + if (idx.column() != column) + idx = idx.sibling(idx.row(), column); + return idx; +} + +/*! + \internal + + Return the QFileSystemNode that goes to index. + */ +QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QModelIndex &index) const +{ + if (!index.isValid()) + return const_cast(&root); + QFileSystemModelPrivate::QFileSystemNode *indexNode = static_cast(index.internalPointer()); + Q_ASSERT(indexNode); + return indexNode; +} + +#ifdef Q_OS_WIN32 +static QString qt_GetLongPathName(const QString &strShortPath) +{ + if (strShortPath.isEmpty() + || strShortPath == QLatin1String(".") || strShortPath == QLatin1String("..")) + return strShortPath; + if (strShortPath.length() == 2 && strShortPath.endsWith(QLatin1Char(':'))) + return strShortPath.toUpper(); + const QString absPath = QDir(strShortPath).absolutePath(); + if (absPath.startsWith(QLatin1String("//")) + || absPath.startsWith(QLatin1String("\\\\"))) // unc + return QDir::fromNativeSeparators(absPath); + if (absPath.startsWith(QLatin1Char('/'))) + return QString(); + const QString inputString = QLatin1String("\\\\?\\") + QDir::toNativeSeparators(absPath); + QVarLengthArray buffer(MAX_PATH); + DWORD result = ::GetLongPathName((wchar_t*)inputString.utf16(), + buffer.data(), + buffer.size()); + if (result > DWORD(buffer.size())) { + buffer.resize(result); + result = ::GetLongPathName((wchar_t*)inputString.utf16(), + buffer.data(), + buffer.size()); + } + if (result > 4) { + QString longPath = QString::fromWCharArray(buffer.data() + 4); // ignoring prefix + longPath[0] = longPath.at(0).toUpper(); // capital drive letters + return QDir::fromNativeSeparators(longPath); + } else { + return QDir::fromNativeSeparators(strShortPath); + } +} +#endif + +/*! + \internal + + Given a path return the matching QFileSystemNode or &root if invalid +*/ +QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QString &path, bool fetch) const +{ + Q_Q(const QFileSystemModel); + Q_UNUSED(q); + if (path.isEmpty() || path == myComputer() || path.startsWith(QLatin1Char(':'))) + return const_cast(&root); + + // Construct the nodes up to the new root path if they need to be built + QString absolutePath; +#ifdef Q_OS_WIN32 + QString longPath = qt_GetLongPathName(path); +#else + QString longPath = path; +#endif + if (longPath == rootDir.path()) + absolutePath = rootDir.absolutePath(); + else + absolutePath = QDir(longPath).absolutePath(); + + // ### TODO can we use bool QAbstractFileEngine::caseSensitive() const? + QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if ((pathElements.isEmpty()) +#if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN) + && QDir::fromNativeSeparators(longPath) != QLatin1String("/") +#endif + ) + return const_cast(&root); + QModelIndex index = QModelIndex(); // start with "My Computer" +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path + QString host = QLatin1String("\\\\") + pathElements.first(); + if (absolutePath == QDir::fromNativeSeparators(host)) + absolutePath.append(QLatin1Char('/')); + if (longPath.endsWith(QLatin1Char('/')) && !absolutePath.endsWith(QLatin1Char('/'))) + absolutePath.append(QLatin1Char('/')); + int r = 0; + QFileSystemModelPrivate::QFileSystemNode *rootNode = const_cast(&root); + if (!root.children.contains(host.toLower())) { + if (pathElements.count() == 1 && !absolutePath.endsWith(QLatin1Char('/'))) + return rootNode; + QFileInfo info(host); + if (!info.exists()) + return rootNode; + QFileSystemModelPrivate *p = const_cast(this); + p->addNode(rootNode, host,info); + p->addVisibleFiles(rootNode, QStringList(host)); + } + r = rootNode->visibleLocation(host); + r = translateVisibleLocation(rootNode, r); + index = q->index(r, 0, QModelIndex()); + pathElements.pop_front(); + } else +#endif + +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + { + if (!pathElements.at(0).contains(QLatin1String(":"))) { + // The reason we express it like this instead of with anonymous, temporary + // variables, is to workaround a compiler crash with Q_CC_NOKIAX86. + QString rootPath = QDir(longPath).rootPath(); + pathElements.prepend(rootPath); + } + if (pathElements.at(0).endsWith(QLatin1Char('/'))) + pathElements[0].chop(1); + } +#else + // add the "/" item, since it is a valid path element on Unix + if (absolutePath[0] == QLatin1Char('/')) + pathElements.prepend(QLatin1String("/")); +#endif + + QFileSystemModelPrivate::QFileSystemNode *parent = node(index); + + for (int i = 0; i < pathElements.count(); ++i) { + QString element = pathElements.at(i); +#ifdef Q_OS_WIN + // On Windows, "filename......." and "filename" are equivalent Task #133928 + while (element.endsWith(QLatin1Char('.'))) + element.chop(1); +#endif + bool alreadyExisted = parent->children.contains(element); + + // we couldn't find the path element, we create a new node since we + // _know_ that the path is valid + if (alreadyExisted) { + if ((parent->children.count() == 0) + || (parent->caseSensitive() + && parent->children.value(element)->fileName != element) + || (!parent->caseSensitive() + && parent->children.value(element)->fileName.toLower() != element.toLower())) + alreadyExisted = false; + } + + QFileSystemModelPrivate::QFileSystemNode *node; + if (!alreadyExisted) { + // Someone might call ::index("file://cookie/monster/doesn't/like/veggies"), + // a path that doesn't exists, I.E. don't blindly create directories. + QFileInfo info(absolutePath); + if (!info.exists()) + return const_cast(&root); + QFileSystemModelPrivate *p = const_cast(this); + node = p->addNode(parent, element,info); +#ifndef QT_NO_FILESYSTEMWATCHER + node->populate(fileInfoGatherer.getInfo(info)); +#endif + } else { + node = parent->children.value(element); + } + + Q_ASSERT(node); + if (!node->isVisible) { + // It has been filtered out + if (alreadyExisted && node->hasInformation() && !fetch) + return const_cast(&root); + + QFileSystemModelPrivate *p = const_cast(this); + p->addVisibleFiles(parent, QStringList(element)); + if (!p->bypassFilters.contains(node)) + p->bypassFilters[node] = 1; + QString dir = q->filePath(this->index(parent)); + if (!node->hasInformation() && fetch) { + Fetching f; + f.dir = dir; + f.file = element; + f.node = node; + p->toFetch.append(f); + p->fetchingTimer.start(0, const_cast(q)); + } + } + parent = node; + } + + return parent; +} + +/*! + \reimp +*/ +void QFileSystemModel::timerEvent(QTimerEvent *event) +{ + Q_D(QFileSystemModel); + if (event->timerId() == d->fetchingTimer.timerId()) { + d->fetchingTimer.stop(); +#ifndef QT_NO_FILESYSTEMWATCHER + for (int i = 0; i < d->toFetch.count(); ++i) { + const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node; + if (!node->hasInformation()) { + d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir, + QStringList(d->toFetch.at(i).file)); + } else { + // qDebug() << "yah!, you saved a little gerbil soul"; + } + } +#endif + d->toFetch.clear(); + } +} + +/*! + Returns true if the model item \a index represents a directory; + otherwise returns false. +*/ +bool QFileSystemModel::isDir(const QModelIndex &index) const +{ + // This function is for public usage only because it could create a file info + Q_D(const QFileSystemModel); + if (!index.isValid()) + return true; + QFileSystemModelPrivate::QFileSystemNode *n = d->node(index); + if (n->hasInformation()) + return n->isDir(); + return fileInfo(index).isDir(); +} + +/*! + Returns the size in bytes of \a index. If the file does not exist, 0 is returned. + */ +qint64 QFileSystemModel::size(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return 0; + return d->node(index)->size(); +} + +/*! + Returns the type of file \a index such as "Directory" or "JPEG file". + */ +QString QFileSystemModel::type(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return QString(); + return d->node(index)->type(); +} + +/*! + Returns the date and time when \a index was last modified. + */ +QDateTime QFileSystemModel::lastModified(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid()) + return QDateTime(); + return d->node(index)->lastModified(); +} + +/*! + \reimp +*/ +QModelIndex QFileSystemModel::parent(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + if (!d->indexValid(index)) + return QModelIndex(); + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index); + Q_ASSERT(indexNode != 0); + QFileSystemModelPrivate::QFileSystemNode *parentNode = (indexNode ? indexNode->parent : 0); + if (parentNode == 0 || parentNode == &d->root) + return QModelIndex(); + + // get the parent's row + QFileSystemModelPrivate::QFileSystemNode *grandParentNode = parentNode->parent; + Q_ASSERT(grandParentNode->children.contains(parentNode->fileName)); + int visualRow = d->translateVisibleLocation(grandParentNode, grandParentNode->visibleLocation(grandParentNode->children.value(parentNode->fileName)->fileName)); + if (visualRow == -1) + return QModelIndex(); + return createIndex(visualRow, 0, parentNode); +} + +/* + \internal + + return the index for node +*/ +QModelIndex QFileSystemModelPrivate::index(const QFileSystemModelPrivate::QFileSystemNode *node) const +{ + Q_Q(const QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *parentNode = (node ? node->parent : 0); + if (node == &root || !parentNode) + return QModelIndex(); + + // get the parent's row + Q_ASSERT(node); + if (!node->isVisible) + return QModelIndex(); + + int visualRow = translateVisibleLocation(parentNode, parentNode->visibleLocation(node->fileName)); + return q->createIndex(visualRow, 0, const_cast(node)); +} + +/*! + \reimp +*/ +bool QFileSystemModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (parent.column() > 0) + return false; + + if (!parent.isValid()) // drives + return true; + + const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + Q_ASSERT(indexNode); + return (indexNode->isDir()); +} + +/*! + \reimp + */ +bool QFileSystemModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + const QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + return (!indexNode->populatedChildren); +} + +/*! + \reimp + */ +void QFileSystemModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QFileSystemModel); + if (!d->setRootPath) + return; + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(parent); + if (indexNode->populatedChildren) + return; + indexNode->populatedChildren = true; + d->fileInfoGatherer.list(filePath(parent)); +} + +/*! + \reimp +*/ +int QFileSystemModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QFileSystemModel); + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + return d->root.visibleChildren.count(); + + const QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent); + return parentNode->visibleChildren.count(); +} + +/*! + \reimp +*/ +int QFileSystemModel::columnCount(const QModelIndex &parent) const +{ + return (parent.column() > 0) ? 0 : 4; +} + +/*! + Returns the data stored under the given \a role for the item "My Computer". + + \sa Qt::ItemDataRole + */ +QVariant QFileSystemModel::myComputer(int role) const +{ + Q_D(const QFileSystemModel); + switch (role) { + case Qt::DisplayRole: + return d->myComputer(); + case Qt::DecorationRole: + return d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Computer); + } + return QVariant(); +} + +/*! + \reimp +*/ +QVariant QFileSystemModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QFileSystemModel); + if (!index.isValid() || index.model() != this) + return QVariant(); + + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + switch (index.column()) { + case 0: return d->displayName(index); + case 1: return d->size(index); + case 2: return d->type(index); + case 3: return d->time(index); + default: + qWarning("data: invalid display value column %d", index.column()); + break; + } + break; + case FilePathRole: + return filePath(index); + case FileNameRole: + return d->name(index); + case Qt::DecorationRole: + if (index.column() == 0) { + QIcon icon = d->icon(index); + if (icon.isNull()) { + if (d->node(index)->isDir()) + icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Folder); + else + icon = d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::File); + } + return icon; + } + break; + case Qt::TextAlignmentRole: + if (index.column() == 1) + return Qt::AlignRight; + break; + case FilePermissions: + int p = permissions(index); + return p; + } + + return QVariant(); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::size(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + const QFileSystemNode *n = node(index); + if (n->isDir()) { +#ifdef Q_OS_MAC + return QLatin1String("--"); +#else + return QLatin1String(""); +#endif + // Windows - "" + // OS X - "--" + // Konqueror - "4 KB" + // Nautilus - "9 items" (the number of children) + } + return size(n->size()); +} + +QString QFileSystemModelPrivate::size(qint64 bytes) +{ + // According to the Si standard KB is 1000 bytes, KiB is 1024 + // but on windows sizes are calculated by dividing by 1024 so we do what they do. + const qint64 kb = 1024; + const qint64 mb = 1024 * kb; + const qint64 gb = 1024 * mb; + const qint64 tb = 1024 * gb; + if (bytes >= tb) + return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); + if (bytes >= gb) + return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); + if (bytes >= mb) + return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); + if (bytes >= kb) + return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); + return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes)); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::time(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); +#ifndef QT_NO_DATESTRING + return node(index)->lastModified().toString(Qt::SystemLocaleDate); +#else + Q_UNUSED(index); + return QString(); +#endif +} + +/* + \internal +*/ +QString QFileSystemModelPrivate::type(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + return node(index)->type(); +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::name(const QModelIndex &index) const +{ + if (!index.isValid()) + return QString(); + QFileSystemNode *dirNode = node(index); + if (dirNode->isSymLink() && fileInfoGatherer.resolveSymlinks()) { + QString fullPath = QDir::fromNativeSeparators(filePath(index)); + if (resolvedSymLinks.contains(fullPath)) + return resolvedSymLinks[fullPath]; + } + return dirNode->fileName; +} + +/*! + \internal +*/ +QString QFileSystemModelPrivate::displayName(const QModelIndex &index) const +{ +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QFileSystemNode *dirNode = node(index); + if (!dirNode->volumeName.isNull()) + return dirNode->volumeName + QLatin1String(" (") + name(index) + QLatin1Char(')'); +#endif + return name(index); +} + +/*! + \internal +*/ +QIcon QFileSystemModelPrivate::icon(const QModelIndex &index) const +{ + if (!index.isValid()) + return QIcon(); + return node(index)->icon(); +} + +/*! + \reimp +*/ +bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + Q_D(QFileSystemModel); + if (!idx.isValid() + || idx.column() != 0 + || role != Qt::EditRole + || (flags(idx) & Qt::ItemIsEditable) == 0) { + return false; + } + + QString newName = value.toString(); + QString oldName = idx.data().toString(); + if (newName == idx.data().toString()) + return true; + + if (newName.isEmpty() + || newName.contains(QDir::separator()) + || !QDir(filePath(parent(idx))).rename(oldName, newName)) { +#ifndef QT_NO_MESSAGEBOX + QMessageBox::information(0, QFileSystemModel::tr("Invalid filename"), + QFileSystemModel::tr("The name \"%1\" can not be used.

Try using another name, with fewer characters or no punctuations marks.") + .arg(newName), + QMessageBox::Ok); +#endif // QT_NO_MESSAGEBOX + return false; + } else { + /* + *After re-naming something we don't want the selection to change* + - can't remove rows and later insert + - can't quickly remove and insert + - index pointer can't change because treeview doesn't use persistant index's + + - if this get any more complicated think of changing it to just + use layoutChanged + */ + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(idx); + QFileSystemModelPrivate::QFileSystemNode *parentNode = indexNode->parent; + int visibleLocation = parentNode->visibleLocation(parentNode->children.value(indexNode->fileName)->fileName); + + d->addNode(parentNode, newName,indexNode->info->fileInfo()); + parentNode->visibleChildren.removeAt(visibleLocation); + QFileSystemModelPrivate::QFileSystemNode * oldValue = parentNode->children.value(oldName); + parentNode->children[newName] = oldValue; + QFileInfo info(d->rootDir, newName); + oldValue->fileName = newName; + oldValue->parent = parentNode; + oldValue->populate(d->fileInfoGatherer.getInfo(info)); + oldValue->isVisible = true; + + parentNode->children.remove(oldName); + parentNode->visibleChildren.insert(visibleLocation, newName); + + d->delayedSort(); + emit fileRenamed(filePath(idx.parent()), oldName, newName); + } + return true; +} + +/*! + \reimp +*/ +QVariant QFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DecorationRole: + if (section == 0) { + // ### TODO oh man this is ugly and doesn't even work all the way! + // it is still 2 pixels off + QImage pixmap(16, 1, QImage::Format_Mono); + pixmap.fill(0); + pixmap.setAlphaChannel(pixmap.createAlphaMask()); + return pixmap; + } + break; + case Qt::TextAlignmentRole: + return Qt::AlignLeft; + } + + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QAbstractItemModel::headerData(section, orientation, role); + + QString returnValue; + switch (section) { + case 0: returnValue = tr("Name"); + break; + case 1: returnValue = tr("Size"); + break; + case 2: returnValue = +#ifdef Q_OS_MAC + tr("Kind", "Match OS X Finder"); +#else + tr("Type", "All other platforms"); +#endif + break; + // Windows - Type + // OS X - Kind + // Konqueror - File Type + // Nautilus - Type + case 3: returnValue = tr("Date Modified"); + break; + default: return QVariant(); + } + return returnValue; +} + +/*! + \reimp +*/ +Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (!index.isValid()) + return flags; + + QFileSystemModelPrivate::QFileSystemNode *indexNode = d->node(index); + if (d->nameFilterDisables && !d->passNameFilters(indexNode)) { + flags &= ~Qt::ItemIsEnabled; + // ### TODO you shouldn't be able to set this as the current item, task 119433 + return flags; + } + + flags |= Qt::ItemIsDragEnabled; + if (d->readOnly) + return flags; + if ((index.column() == 0) && indexNode->permissions() & QFile::WriteUser) { + flags |= Qt::ItemIsEditable; + if (indexNode->isDir()) + flags |= Qt::ItemIsDropEnabled; + } + return flags; +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::_q_performDelayedSort() +{ + Q_Q(QFileSystemModel); + q->sort(sortColumn, sortOrder); +} + +static inline QChar getNextChar(const QString &s, int location) +{ + return (location < s.length()) ? s.at(location) : QChar(); +} + +/*! + Natural number sort, skips spaces. + + Examples: + 1, 2, 10, 55, 100 + 01.jpg, 2.jpg, 10.jpg + + Note on the algorithm: + Only as many characters as necessary are looked at and at most they all + are looked at once. + + Slower then QString::compare() (of course) + */ +int QFileSystemModelPrivate::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) +{ + for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) { + // skip spaces, tabs and 0's + QChar c1 = getNextChar(s1, l1); + while (c1.isSpace()) + c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); + while (c2.isSpace()) + c2 = getNextChar(s2, ++l2); + + if (c1.isDigit() && c2.isDigit()) { + while (c1.digitValue() == 0) + c1 = getNextChar(s1, ++l1); + while (c2.digitValue() == 0) + c2 = getNextChar(s2, ++l2); + + int lookAheadLocation1 = l1; + int lookAheadLocation2 = l2; + int currentReturnValue = 0; + // find the last digit, setting currentReturnValue as we go if it isn't equal + for ( + QChar lookAhead1 = c1, lookAhead2 = c2; + (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), + lookAhead2 = getNextChar(s2, ++lookAheadLocation2) + ) { + bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); + bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); + if (!is1ADigit && !is2ADigit) + break; + if (!is1ADigit) + return -1; + if (!is2ADigit) + return 1; + if (currentReturnValue == 0) { + if (lookAhead1 < lookAhead2) { + currentReturnValue = -1; + } else if (lookAhead1 > lookAhead2) { + currentReturnValue = 1; + } + } + } + if (currentReturnValue != 0) + return currentReturnValue; + } + + if (cs == Qt::CaseInsensitive) { + if (!c1.isLower()) c1 = c1.toLower(); + if (!c2.isLower()) c2 = c2.toLower(); + } + int r = QString::localeAwareCompare(c1, c2); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + // The two strings are the same (02 == 2) so fall back to the normal sort + return QString::compare(s1, s2, cs); +} + +/* + \internal + Helper functor used by sort() +*/ +class QFileSystemModelSorter +{ +public: + inline QFileSystemModelSorter(int column) : sortColumn(column) {} + + bool compareNodes(const QFileSystemModelPrivate::QFileSystemNode *l, + const QFileSystemModelPrivate::QFileSystemNode *r) const + { + switch (sortColumn) { + case 0: { +#ifndef Q_OS_MAC + // place directories before files + bool left = l->isDir(); + bool right = r->isDir(); + if (left ^ right) + return left; +#endif + return QFileSystemModelPrivate::naturalCompare(l->fileName, + r->fileName, Qt::CaseInsensitive) < 0; + } + case 1: + // Directories go first + if (l->isDir() && !r->isDir()) + return true; + return l->size() < r->size(); + case 2: + return l->type() < r->type(); + case 3: + return l->lastModified() < r->lastModified(); + } + Q_ASSERT(false); + return false; + } + + bool operator()(const QPair &l, + const QPair &r) const + { + return compareNodes(l.first, r.first); + } + + +private: + int sortColumn; +}; + +/* + \internal + + Sort all of the children of parent +*/ +void QFileSystemModelPrivate::sortChildren(int column, const QModelIndex &parent) +{ + Q_Q(QFileSystemModel); + QFileSystemModelPrivate::QFileSystemNode *indexNode = node(parent); + if (indexNode->children.count() == 0) + return; + + QList > values; + QHash::const_iterator iterator; + int i = 0; + for(iterator = indexNode->children.begin() ; iterator != indexNode->children.end() ; ++iterator) { + if (filtersAcceptsNode(iterator.value())) { + values.append(QPair((iterator.value()), i)); + } else { + iterator.value()->isVisible = false; + } + i++; + } + QFileSystemModelSorter ms(column); + qStableSort(values.begin(), values.end(), ms); + // First update the new visible list + indexNode->visibleChildren.clear(); + //No more dirty item we reset our internal dirty index + indexNode->dirtyChildrenIndex = -1; + for (int i = 0; i < values.count(); ++i) { + indexNode->visibleChildren.append(values.at(i).first->fileName); + values.at(i).first->isVisible = true; + } + + if (!disableRecursiveSort) { + for (int i = 0; i < q->rowCount(parent); ++i) { + const QModelIndex childIndex = q->index(i, 0, parent); + QFileSystemModelPrivate::QFileSystemNode *indexNode = node(childIndex); + //Only do a recursive sort on visible nodes + if (indexNode->isVisible) + sortChildren(column, childIndex); + } + } +} + +/*! + \reimp +*/ +void QFileSystemModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QFileSystemModel); + if (d->sortOrder == order && d->sortColumn == column && !d->forceSort) + return; + + emit layoutAboutToBeChanged(); + QModelIndexList oldList = persistentIndexList(); + QList > oldNodes; + for (int i = 0; i < oldList.count(); ++i) { + QPair pair(d->node(oldList.at(i)), oldList.at(i).column()); + oldNodes.append(pair); + } + + if (!(d->sortColumn == column && d->sortOrder != order && !d->forceSort)) { + //we sort only from where we are, don't need to sort all the model + d->sortChildren(column, index(rootPath())); + d->sortColumn = column; + d->forceSort = false; + } + d->sortOrder = order; + + QModelIndexList newList; + for (int i = 0; i < oldNodes.count(); ++i) { + QModelIndex idx = d->index(oldNodes.at(i).first); + idx = idx.sibling(idx.row(), oldNodes.at(i).second); + newList.append(idx); + } + changePersistentIndexList(oldList, newList); + emit layoutChanged(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of items + in the model. +*/ +QStringList QFileSystemModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + Returns an object that contains a serialized description of the specified + \a indexes. The format used to describe the items corresponding to the + indexes is obtained from the mimeTypes() function. + + If the list of indexes is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QFileSystemModel::mimeData(const QModelIndexList &indexes) const +{ + QList urls; + QList::const_iterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + if ((*it).column() == 0) + urls << QUrl::fromLocalFile(filePath(*it)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action over the row in the model specified by the \a row and + \a column and by the \a parent index. + + \sa supportedDropActions() +*/ +bool QFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_UNUSED(row); + Q_UNUSED(column); + if (!parent.isValid() || isReadOnly()) + return false; + + bool success = true; + QString to = filePath(parent) + QDir::separator(); + + QList urls = data->urls(); + QList::const_iterator it = urls.constBegin(); + + switch (action) { + case Qt::CopyAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::copy(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::LinkAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::link(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::MoveAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::rename(path, to + QFileInfo(path).fileName()) && success; + } + break; + default: + return false; + } + + return success; +} + +/*! + \reimp +*/ +Qt::DropActions QFileSystemModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; +} + +/*! + Returns the path of the item stored in the model under the + \a index given. +*/ +QString QFileSystemModel::filePath(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + QString fullPath = d->filePath(index); + QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index); + if (dirNode->isSymLink() && d->fileInfoGatherer.resolveSymlinks() + && d->resolvedSymLinks.contains(fullPath) + && dirNode->isDir()) { + QFileInfo resolvedInfo(fullPath); + resolvedInfo = resolvedInfo.canonicalFilePath(); + if (resolvedInfo.exists()) + return resolvedInfo.filePath(); + } + return fullPath; +} + +QString QFileSystemModelPrivate::filePath(const QModelIndex &index) const +{ + Q_Q(const QFileSystemModel); + Q_UNUSED(q); + if (!index.isValid()) + return QString(); + Q_ASSERT(index.model() == q); + + QStringList path; + QModelIndex idx = index; + while (idx.isValid()) { + QFileSystemModelPrivate::QFileSystemNode *dirNode = node(idx); + if (dirNode) + path.prepend(dirNode->fileName); + idx = idx.parent(); + } + QString fullPath = QDir::fromNativeSeparators(path.join(QDir::separator())); +#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) + if ((fullPath.length() > 2) && fullPath[0] == QLatin1Char('/') && fullPath[1] == QLatin1Char('/')) + fullPath = fullPath.mid(1); +#endif +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + if (fullPath.length() == 2 && fullPath.endsWith(QLatin1Char(':'))) + fullPath.append(QLatin1Char('/')); +#endif + return fullPath; +} + +/*! + Create a directory with the \a name in the \a parent model index. +*/ +QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &name) +{ + Q_D(QFileSystemModel); + if (!parent.isValid()) + return parent; + + QDir dir(filePath(parent)); + if (!dir.mkdir(name)) + return QModelIndex(); + QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(parent); + d->addNode(parentNode, name, QFileInfo()); + Q_ASSERT(parentNode->children.contains(name)); + QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name]; + node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); + d->addVisibleFiles(parentNode, QStringList(name)); + return d->index(node); +} + +/*! + Returns the complete OR-ed together combination of QFile::Permission for the \a index. + */ +QFile::Permissions QFileSystemModel::permissions(const QModelIndex &index) const +{ + Q_D(const QFileSystemModel); + QFile::Permissions p = d->node(index)->permissions(); + if (d->readOnly) { + p ^= (QFile::WriteOwner | QFile::WriteUser + | QFile::WriteGroup | QFile::WriteOther); + } + return p; +} + +/*! + Sets the directory that is being watched by the model to \a newPath by + installing a \l{QFileSystemWatcher}{file system watcher} on it. Any + changes to files and directories within this directory will be + reflected in the model. + + If the path is changed, the rootPathChanged() signal will be emitted. + + \note This function does not change the structure of the model or + modify the data available to views. In other words, the "root" of + the model is \e not changed to include only files and directories + within the directory specified by \a newPath in the file system. + */ +QModelIndex QFileSystemModel::setRootPath(const QString &newPath) +{ + Q_D(QFileSystemModel); +#ifdef Q_OS_WIN +#ifdef Q_OS_WIN32 + QString longNewPath = qt_GetLongPathName(newPath); +#else + QString longNewPath = QDir::fromNativeSeparators(newPath); +#endif +#else + QString longNewPath = newPath; +#endif + QDir newPathDir(longNewPath); + //we remove .. and . from the given path if exist + if (!newPath.isEmpty()) { + longNewPath = QDir::cleanPath(longNewPath); + newPathDir.setPath(longNewPath); + } + + d->setRootPath = true; + + //user don't ask for the root path ("") but the conversion failed + if (!newPath.isEmpty() && longNewPath.isEmpty()) + return d->index(rootPath()); + + if (d->rootDir.path() == longNewPath) + return d->index(rootPath()); + + bool showDrives = (longNewPath.isEmpty() || longNewPath == d->myComputer()); + if (!showDrives && !newPathDir.exists()) + return d->index(rootPath()); + + //We remove the watcher on the previous path + if (!rootPath().isEmpty() && rootPath() != QLatin1String(".")) { + //This remove the watcher for the old rootPath + d->fileInfoGatherer.removePath(rootPath()); + //This line "marks" the node as dirty, so the next fetchMore + //call on the path will ask the gatherer to install a watcher again + //But it doesn't re-fetch everything + d->node(rootPath())->populatedChildren = false; + } + + // We have a new valid root path + d->rootDir = newPathDir; + QModelIndex newRootIndex; + if (showDrives) { + // otherwise dir will become '.' + d->rootDir.setPath(QLatin1String("")); + } else { + newRootIndex = d->index(newPathDir.path()); + } + fetchMore(newRootIndex); + emit rootPathChanged(longNewPath); + d->forceSort = true; + d->delayedSort(); + return newRootIndex; +} + +/*! + The currently set root path + + \sa rootDirectory() +*/ +QString QFileSystemModel::rootPath() const +{ + Q_D(const QFileSystemModel); + return d->rootDir.path(); +} + +/*! + The currently set directory + + \sa rootPath() +*/ +QDir QFileSystemModel::rootDirectory() const +{ + Q_D(const QFileSystemModel); + QDir dir(d->rootDir); + dir.setNameFilters(nameFilters()); + dir.setFilter(filter()); + return dir; +} + +/*! + Sets the \a provider of file icons for the directory model. +*/ +void QFileSystemModel::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QFileSystemModel); + d->fileInfoGatherer.setIconProvider(provider); + d->root.updateIcon(provider, QString()); +} + +/*! + Returns the file icon provider for this directory model. +*/ +QFileIconProvider *QFileSystemModel::iconProvider() const +{ + Q_D(const QFileSystemModel); + return d->fileInfoGatherer.iconProvider(); +} + +/*! + Sets the directory model's filter to that specified by \a filters. + + Note that the filter you set should always include the QDir::AllDirs enum value, + otherwise QFileSystemModel won't be able to read the directory structure. + + \sa QDir::Filters +*/ +void QFileSystemModel::setFilter(QDir::Filters filters) +{ + Q_D(QFileSystemModel); + if (d->filters == filters) + return; + d->filters = filters; + // CaseSensitivity might have changed + setNameFilters(nameFilters()); + d->forceSort = true; + d->delayedSort(); +} + +/*! + Returns the filter specified for the directory model. + + If a filter has not been set, the default filter is QDir::AllEntries | + QDir::NoDotAndDotDot | QDir::AllDirs. + + \sa QDir::Filters +*/ +QDir::Filters QFileSystemModel::filter() const +{ + Q_D(const QFileSystemModel); + return d->filters; +} + +/*! + \property QFileSystemModel::resolveSymlinks + \brief Whether the directory model should resolve symbolic links + + This is only relevant on operating systems that support symbolic links. + + By default, this property is false. +*/ +void QFileSystemModel::setResolveSymlinks(bool enable) +{ + Q_D(QFileSystemModel); + d->fileInfoGatherer.setResolveSymlinks(enable); +} + +bool QFileSystemModel::resolveSymlinks() const +{ + Q_D(const QFileSystemModel); + return d->fileInfoGatherer.resolveSymlinks(); +} + +/*! + \property QFileSystemModel::readOnly + \brief Whether the directory model allows writing to the file system + + If this property is set to false, the directory model will allow renaming, copying + and deleting of files and directories. + + This property is true by default +*/ +void QFileSystemModel::setReadOnly(bool enable) +{ + Q_D(QFileSystemModel); + d->readOnly = enable; +} + +bool QFileSystemModel::isReadOnly() const +{ + Q_D(const QFileSystemModel); + return d->readOnly; +} + +/*! + \property QFileSystemModel::nameFilterDisables + \brief Whether files that don't pass the name filter are hidden or disabled + + This property is true by default +*/ +void QFileSystemModel::setNameFilterDisables(bool enable) +{ + Q_D(QFileSystemModel); + if (d->nameFilterDisables == enable) + return; + d->nameFilterDisables = enable; + d->forceSort = true; + d->delayedSort(); +} + +bool QFileSystemModel::nameFilterDisables() const +{ + Q_D(const QFileSystemModel); + return d->nameFilterDisables; +} + +/*! + Sets the name \a filters to apply against the existing files. +*/ +void QFileSystemModel::setNameFilters(const QStringList &filters) +{ + // Prep the regexp's ahead of time +#ifndef QT_NO_REGEXP + Q_D(QFileSystemModel); + + if (!d->bypassFilters.isEmpty()) { + // update the bypass filter to only bypass the stuff that must be kept around + d->bypassFilters.clear(); + // We guarantee that rootPath will stick around + QPersistentModelIndex root(index(rootPath())); + QModelIndexList persistantList = persistentIndexList(); + for (int i = 0; i < persistantList.count(); ++i) { + QFileSystemModelPrivate::QFileSystemNode *node; + node = d->node(persistantList.at(i)); + while (node) { + if (d->bypassFilters.contains(node)) + break; + if (node->isDir()) + d->bypassFilters[node] = true; + node = node->parent; + } + } + } + + d->nameFilters.clear(); + const Qt::CaseSensitivity caseSensitive = + (filter() & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive; + for (int i = 0; i < filters.size(); ++i) { + d->nameFilters << QRegExp(filters.at(i), caseSensitive, QRegExp::Wildcard); + } + d->forceSort = true; + d->delayedSort(); +#endif +} + +/*! + Returns a list of filters applied to the names in the model. +*/ +QStringList QFileSystemModel::nameFilters() const +{ + Q_D(const QFileSystemModel); + QStringList filters; +#ifndef QT_NO_REGEXP + for (int i = 0; i < d->nameFilters.size(); ++i) { + filters << d->nameFilters.at(i).pattern(); + } +#endif + return filters; +} + +/*! + \reimp +*/ +bool QFileSystemModel::event(QEvent *event) +{ + Q_D(QFileSystemModel); + if (event->type() == QEvent::LanguageChange) { + d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString()); + return true; + } + return QAbstractItemModel::event(event); +} + +bool QFileSystemModel::rmdir(const QModelIndex &aindex) const +{ + QString path = filePath(aindex); + QFileSystemModelPrivate * d = const_cast(d_func()); + d->fileInfoGatherer.removePath(path); + return QDir().rmdir(path); +} + +/*! + \internal + + Performed quick listing and see if any files have been added or removed, + then fetch more information on visible files. + */ +void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files) +{ + QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false); + if (parentNode->children.count() == 0) + return; + QStringList toRemove; +#if defined(Q_OS_SYMBIAN) + // Filename case must be exact in qBinaryFind below, so create a list of all lowercase names. + QStringList newFiles; + for(int i = 0; i < files.size(); i++) { + newFiles << files.at(i).toLower(); + } +#else + QStringList newFiles = files; +#endif + qSort(newFiles.begin(), newFiles.end()); + QHash::const_iterator i = parentNode->children.constBegin(); + while (i != parentNode->children.constEnd()) { + QStringList::iterator iterator; + iterator = qBinaryFind(newFiles.begin(), newFiles.end(), +#if defined(Q_OS_SYMBIAN) + i.value()->fileName.toLower()); +#else + i.value()->fileName); +#endif + if (iterator == newFiles.end()) { + toRemove.append(i.value()->fileName); + } + ++i; + } + for (int i = 0 ; i < toRemove.count() ; ++i ) + removeNode(parentNode, toRemove[i]); +} + +/*! + \internal + + Adds a new file to the children of parentNode + + *WARNING* this will change the count of children +*/ +QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info) +{ + // In the common case, itemLocation == count() so check there first + QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode); +#ifndef QT_NO_FILESYSTEMWATCHER + node->populate(info); +#endif +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + //The parentNode is "" so we are listing the drives + if (parentNode->fileName.isEmpty()) { + wchar_t name[MAX_PATH + 1]; + //GetVolumeInformation requires to add trailing backslash + const QString nodeName = fileName + QLatin1String("\\"); + BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()), + name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0); + if (success && name[0]) + node->volumeName = QString::fromWCharArray(name); + } +#endif + parentNode->children.insert(fileName, node); + return node; +} + +/*! + \internal + + File at parentNode->children(itemLocation) has been removed, remove from the lists + and emit signals if necessary + + *WARNING* this will change the count of children and could change visibleChildren + */ +void QFileSystemModelPrivate::removeNode(QFileSystemModelPrivate::QFileSystemNode *parentNode, const QString& name) +{ + Q_Q(QFileSystemModel); + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + + int vLocation = parentNode->visibleLocation(name); + if (vLocation >= 0 && !indexHidden) + q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation), + translateVisibleLocation(parentNode, vLocation)); + QFileSystemNode * node = parentNode->children.take(name); + delete node; + // cleanup sort files after removing rather then re-sorting which is O(n) + if (vLocation >= 0) + parentNode->visibleChildren.removeAt(vLocation); + if (vLocation >= 0 && !indexHidden) + q->endRemoveRows(); +} + +/* + \internal + Helper functor used by addVisibleFiles() +*/ +class QFileSystemModelVisibleFinder +{ +public: + inline QFileSystemModelVisibleFinder(QFileSystemModelPrivate::QFileSystemNode *node, QFileSystemModelSorter *sorter) : parentNode(node), sorter(sorter) {} + + bool operator()(const QString &, QString r) const + { + return sorter->compareNodes(parentNode->children.value(name), parentNode->children.value(r)); + } + + QString name; +private: + QFileSystemModelPrivate::QFileSystemNode *parentNode; + QFileSystemModelSorter *sorter; +}; + +/*! + \internal + + File at parentNode->children(itemLocation) was not visible before, but now should be + and emit signals if necessary. + + *WARNING* this will change the visible count + */ +void QFileSystemModelPrivate::addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles) +{ + Q_Q(QFileSystemModel); + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + if (!indexHidden) { + q->beginInsertRows(parent, parentNode->visibleChildren.count() , parentNode->visibleChildren.count() + newFiles.count() - 1); + } + + if (parentNode->dirtyChildrenIndex == -1) + parentNode->dirtyChildrenIndex = parentNode->visibleChildren.count(); + + for (int i = 0; i < newFiles.count(); ++i) { + parentNode->visibleChildren.append(newFiles.at(i)); + parentNode->children[newFiles.at(i)]->isVisible = true; + } + if (!indexHidden) + q->endInsertRows(); +} + +/*! + \internal + + File was visible before, but now should NOT be + + *WARNING* this will change the visible count + */ +void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int vLocation) +{ + Q_Q(QFileSystemModel); + if (vLocation == -1) + return; + QModelIndex parent = index(parentNode); + bool indexHidden = isHiddenByFilter(parentNode, parent); + if (!indexHidden) + q->beginRemoveRows(parent, translateVisibleLocation(parentNode, vLocation), + translateVisibleLocation(parentNode, vLocation)); + parentNode->children[parentNode->visibleChildren.at(vLocation)]->isVisible = false; + parentNode->visibleChildren.removeAt(vLocation); + if (!indexHidden) + q->endRemoveRows(); +} + +/*! + \internal + + The thread has received new information about files, + update and emit dataChanged if it has actually changed. + */ +void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, const QList > &updates) +{ + Q_Q(QFileSystemModel); + QVector rowsToUpdate; + QStringList newFiles; + QFileSystemModelPrivate::QFileSystemNode *parentNode = node(path, false); + QModelIndex parentIndex = index(parentNode); + for (int i = 0; i < updates.count(); ++i) { + QString fileName = updates.at(i).first; + Q_ASSERT(!fileName.isEmpty()); + QExtendedInformation info = fileInfoGatherer.getInfo(updates.at(i).second); + bool previouslyHere = parentNode->children.contains(fileName); + if (!previouslyHere) { + addNode(parentNode, fileName, info.fileInfo()); + } + QFileSystemModelPrivate::QFileSystemNode * node = parentNode->children.value(fileName); + bool isCaseSensitive = parentNode->caseSensitive(); + if (isCaseSensitive) { + if (node->fileName != fileName) + continue; + } else { + if (QString::compare(node->fileName,fileName,Qt::CaseInsensitive) != 0) + continue; + } + if (isCaseSensitive) { + Q_ASSERT(node->fileName == fileName); + } else { + node->fileName = fileName; + } + + if (info.size() == -1 && !info.isSymLink()) { + removeNode(parentNode, fileName); + continue; + } + if (*node != info ) { + node->populate(info); + bypassFilters.remove(node); + // brand new information. + if (filtersAcceptsNode(node)) { + if (!node->isVisible) { + newFiles.append(fileName); + } else { + rowsToUpdate.append(fileName); + } + } else { + if (node->isVisible) { + int visibleLocation = parentNode->visibleLocation(fileName); + removeVisibleFile(parentNode, visibleLocation); + } else { + // The file is not visible, don't do anything + } + } + } + } + + // bundle up all of the changed signals into as few as possible. + qSort(rowsToUpdate.begin(), rowsToUpdate.end()); + QString min; + QString max; + for (int i = 0; i < rowsToUpdate.count(); ++i) { + QString value = rowsToUpdate.at(i); + //##TODO is there a way to bundle signals with QString as the content of the list? + /*if (min.isEmpty()) { + min = value; + if (i != rowsToUpdate.count() - 1) + continue; + } + if (i != rowsToUpdate.count() - 1) { + if ((value == min + 1 && max.isEmpty()) || value == max + 1) { + max = value; + continue; + } + }*/ + max = value; + min = value; + int visibleMin = parentNode->visibleLocation(min); + int visibleMax = parentNode->visibleLocation(max); + if (visibleMin >= 0 + && visibleMin < parentNode->visibleChildren.count() + && parentNode->visibleChildren.at(visibleMin) == min + && visibleMax >= 0) { + QModelIndex bottom = q->index(translateVisibleLocation(parentNode, visibleMin), 0, parentIndex); + QModelIndex top = q->index(translateVisibleLocation(parentNode, visibleMax), 3, parentIndex); + emit q->dataChanged(bottom, top); + } + + /*min = QString(); + max = QString();*/ + } + + if (newFiles.count() > 0) { + addVisibleFiles(parentNode, newFiles); + } + + if (newFiles.count() > 0 || (sortColumn != 0 && rowsToUpdate.count() > 0)) { + forceSort = true; + delayedSort(); + } +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName) +{ + resolvedSymLinks[fileName] = resolvedName; +} + +/*! + \internal +*/ +void QFileSystemModelPrivate::init() +{ + Q_Q(QFileSystemModel); + qRegisterMetaType > >("QList >"); + q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), + q, SLOT(_q_directoryChanged(QString,QStringList))); + q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList >)), + q, SLOT(_q_fileSystemChanged(QString,QList >))); + q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), + q, SLOT(_q_resolvedName(QString,QString))); + q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), + q, SIGNAL(directoryLoaded(QString))); + q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection); + + QHash roles = q->roleNames(); + roles.insertMulti(QFileSystemModel::FileIconRole, "fileIcon"); // == Qt::decoration + roles.insert(QFileSystemModel::FilePathRole, "filePath"); + roles.insert(QFileSystemModel::FileNameRole, "fileName"); + roles.insert(QFileSystemModel::FilePermissions, "filePermissions"); + q->setRoleNames(roles); +} + +/*! + \internal + + Returns false if node doesn't pass the filters otherwise true + + QDir::Modified is not supported + QDir::Drives is not supported +*/ +bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const +{ + // always accept drives + if (node->parent == &root || bypassFilters.contains(node)) + return true; + + // If we don't know anything yet don't accept it + if (!node->hasInformation()) + return false; + + const bool filterPermissions = ((filters & QDir::PermissionMask) + && (filters & QDir::PermissionMask) != QDir::PermissionMask); + const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); + const bool hideFiles = !(filters & QDir::Files); + const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable)); + const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable)); + const bool hideExecutable = !(!filterPermissions || (filters & QDir::Executable)); + const bool hideHidden = !(filters & QDir::Hidden); + const bool hideSystem = !(filters & QDir::System); + const bool hideSymlinks = (filters & QDir::NoSymLinks); + const bool hideDotAndDotDot = (filters & QDir::NoDotAndDotDot); + + // Note that we match the behavior of entryList and not QFileInfo on this and this + // incompatibility won't be fixed until Qt 5 at least + bool isDotOrDot = ( (node->fileName == QLatin1String(".") + || node->fileName == QLatin1String(".."))); + if ( (hideHidden && (!isDotOrDot && node->isHidden())) + || (hideSystem && node->isSystem()) + || (hideDirs && node->isDir()) + || (hideFiles && node->isFile()) + || (hideSymlinks && node->isSymLink()) + || (hideReadable && node->isReadable()) + || (hideWritable && node->isWritable()) + || (hideExecutable && node->isExecutable()) + || (hideDotAndDotDot && isDotOrDot)) + return false; + + return nameFilterDisables || passNameFilters(node); +} + +/* + \internal + + Returns true if node passes the name filters and should be visible. + */ +bool QFileSystemModelPrivate::passNameFilters(const QFileSystemNode *node) const +{ +#ifndef QT_NO_REGEXP + if (nameFilters.isEmpty()) + return true; + + // Check the name regularexpression filters + if (!(node->isDir() && (filters & QDir::AllDirs))) { + for (int i = 0; i < nameFilters.size(); ++i) { + if (nameFilters.at(i).exactMatch(node->fileName)) + return true; + } + return false; + } +#endif + return true; +} + +QT_END_NAMESPACE + +#include "moc_qfilesystemmodel.cpp" + +#endif // QT_NO_FILESYSTEMMODEL diff --git a/src/gui/dialogs/qfilesystemmodel.h b/src/gui/dialogs/qfilesystemmodel.h new file mode 100644 index 0000000000..8aa9875d13 --- /dev/null +++ b/src/gui/dialogs/qfilesystemmodel.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMMODEL_H +#define QFILESYSTEMMODEL_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILESYSTEMMODEL + +class ExtendedInformation; +class QFileSystemModelPrivate; +class QFileIconProvider; + +class Q_GUI_EXPORT QFileSystemModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool nameFilterDisables READ nameFilterDisables WRITE setNameFilterDisables) + +Q_SIGNALS: + void rootPathChanged(const QString &newPath); + void fileRenamed(const QString &path, const QString &oldName, const QString &newName); + void directoryLoaded(const QString &path); + +public: + enum Roles { + FileIconRole = Qt::DecorationRole, + FilePathRole = Qt::UserRole + 1, + FileNameRole = Qt::UserRole + 2, + FilePermissions = Qt::UserRole + 3 + }; + + explicit QFileSystemModel(QObject *parent = 0); + ~QFileSystemModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex index(const QString &path, int column = 0) const; + QModelIndex parent(const QModelIndex &child) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant myComputer(int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + // QFileSystemModel specific API + QModelIndex setRootPath(const QString &path); + QString rootPath() const; + QDir rootDirectory() const; + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setFilter(QDir::Filters filters); + QDir::Filters filter() const; + + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + + void setReadOnly(bool enable); + bool isReadOnly() const; + + void setNameFilterDisables(bool enable); + bool nameFilterDisables() const; + + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + + QString filePath(const QModelIndex &index) const; + bool isDir(const QModelIndex &index) const; + qint64 size(const QModelIndex &index) const; + QString type(const QModelIndex &index) const; + QDateTime lastModified(const QModelIndex &index) const; + + QModelIndex mkdir(const QModelIndex &parent, const QString &name); + bool rmdir(const QModelIndex &index) const; // ### Qt5: should not be const + inline QString fileName(const QModelIndex &index) const; + inline QIcon fileIcon(const QModelIndex &index) const; + QFile::Permissions permissions(const QModelIndex &index) const; + inline QFileInfo fileInfo(const QModelIndex &index) const; + bool remove(const QModelIndex &index) const; + +protected: + QFileSystemModel(QFileSystemModelPrivate &, QObject *parent = 0); + void timerEvent(QTimerEvent *event); + bool event(QEvent *event); + +private: + Q_DECLARE_PRIVATE(QFileSystemModel) + Q_DISABLE_COPY(QFileSystemModel) + + Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &directory, const QStringList &list)) + Q_PRIVATE_SLOT(d_func(), void _q_performDelayedSort()) + Q_PRIVATE_SLOT(d_func(), void _q_fileSystemChanged(const QString &path, const QList > &)) + Q_PRIVATE_SLOT(d_func(), void _q_resolvedName(const QString &fileName, const QString &resolvedName)) + + friend class QFileDialogPrivate; +}; + +inline QString QFileSystemModel::fileName(const QModelIndex &aindex) const +{ return aindex.data(Qt::DisplayRole).toString(); } +inline QIcon QFileSystemModel::fileIcon(const QModelIndex &aindex) const +{ return qvariant_cast(aindex.data(Qt::DecorationRole)); } +inline QFileInfo QFileSystemModel::fileInfo(const QModelIndex &aindex) const +{ return QFileInfo(filePath(aindex)); } + +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILESYSTEMMODEL_H + diff --git a/src/gui/dialogs/qfilesystemmodel_p.h b/src/gui/dialogs/qfilesystemmodel_p.h new file mode 100644 index 0000000000..e83bbd11d3 --- /dev/null +++ b/src/gui/dialogs/qfilesystemmodel_p.h @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILESYSTEMMODEL_P_H +#define QFILESYSTEMMODEL_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 "qfilesystemmodel.h" + +#ifndef QT_NO_FILESYSTEMMODEL + +#include +#include +#include "qfileinfogatherer_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class ExtendedInformation; +class QFileSystemModelPrivate; +class QFileIconProvider; + +class Q_AUTOTEST_EXPORT QFileSystemModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QFileSystemModel) + +public: + class QFileSystemNode + { + public: + QFileSystemNode(const QString &filename = QString(), QFileSystemNode *p = 0) + : fileName(filename), populatedChildren(false), isVisible(false), dirtyChildrenIndex(-1), parent(p), info(0) {} + ~QFileSystemNode() { + QHash::const_iterator i = children.constBegin(); + while (i != children.constEnd()) { + delete i.value(); + ++i; + } + delete info; + info = 0; + parent = 0; + } + + QString fileName; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QString volumeName; +#endif + + inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; } + inline QString type() const { if (info) return info->displayType; return QLatin1String(""); } + inline QDateTime lastModified() const { if (info) return info->lastModified(); return QDateTime(); } + inline QFile::Permissions permissions() const { if (info) return info->permissions(); return 0; } + inline bool isReadable() const { return ((permissions() & QFile::ReadUser) != 0); } + inline bool isWritable() const { return ((permissions() & QFile::WriteUser) != 0); } + inline bool isExecutable() const { return ((permissions() & QFile::ExeUser) != 0); } + inline bool isDir() const { + if (info) + return info->isDir(); + if (children.count() > 0) + return true; + return false; + } + inline bool isFile() const { if (info) return info->isFile(); return true; } + inline bool isSystem() const { if (info) return info->isSystem(); return true; } + inline bool isHidden() const { if (info) return info->isHidden(); return false; } + inline bool isSymLink() const { if (info) return info->isSymLink(); return false; } + inline bool caseSensitive() const { if (info) return info->isCaseSensitive(); return false; } + inline QIcon icon() const { if (info) return info->icon; return QIcon(); } + + inline bool operator <(const QFileSystemNode &node) const { + if (caseSensitive() || node.caseSensitive()) + return fileName < node.fileName; + return QString::compare(fileName, node.fileName, Qt::CaseInsensitive) < 0; + } + inline bool operator >(const QString &name) const { + if (caseSensitive()) + return fileName > name; + return QString::compare(fileName, name, Qt::CaseInsensitive) > 0; + } + inline bool operator <(const QString &name) const { + if (caseSensitive()) + return fileName < name; + return QString::compare(fileName, name, Qt::CaseInsensitive) < 0; + } + inline bool operator !=(const QExtendedInformation &fileInfo) const { + return !operator==(fileInfo); + } + bool operator ==(const QString &name) const { + if (caseSensitive()) + return fileName == name; + return QString::compare(fileName, name, Qt::CaseInsensitive) == 0; + } + bool operator ==(const QExtendedInformation &fileInfo) const { + return info && (*info == fileInfo); + } + + inline bool hasInformation() const { return info != 0; } + + void populate(const QExtendedInformation &fileInfo) { + if (!info) + info = new QExtendedInformation(fileInfo.fileInfo()); + (*info) = fileInfo; + } + + // children shouldn't normally be accessed directly, use node() + inline int visibleLocation(QString childName) { + return visibleChildren.indexOf(childName); + } + void updateIcon(QFileIconProvider *iconProvider, const QString &path) { + if (info) + info->icon = iconProvider->icon(QFileInfo(path)); + QHash::const_iterator iterator; + for(iterator = children.constBegin() ; iterator != children.constEnd() ; ++iterator) { + //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/) + if (!path.isEmpty()) { + if (path.endsWith(QLatin1Char('/'))) + iterator.value()->updateIcon(iconProvider, path + iterator.value()->fileName); + else + iterator.value()->updateIcon(iconProvider, path + QLatin1Char('/') + iterator.value()->fileName); + } else + iterator.value()->updateIcon(iconProvider, iterator.value()->fileName); + } + } + + void retranslateStrings(QFileIconProvider *iconProvider, const QString &path) { + if (info) + info->displayType = iconProvider->type(QFileInfo(path)); + QHash::const_iterator iterator; + for(iterator = children.constBegin() ; iterator != children.constEnd() ; ++iterator) { + //On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/) + if (!path.isEmpty()) { + if (path.endsWith(QLatin1Char('/'))) + iterator.value()->retranslateStrings(iconProvider, path + iterator.value()->fileName); + else + iterator.value()->retranslateStrings(iconProvider, path + QLatin1Char('/') + iterator.value()->fileName); + } else + iterator.value()->retranslateStrings(iconProvider, iterator.value()->fileName); + } + } + + bool populatedChildren; + bool isVisible; + QHash children; + QList visibleChildren; + int dirtyChildrenIndex; + QFileSystemNode *parent; + + + QExtendedInformation *info; + + }; + + QFileSystemModelPrivate() : + forceSort(true), + sortColumn(0), + sortOrder(Qt::AscendingOrder), + readOnly(true), + setRootPath(false), + filters(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs), + nameFilterDisables(true), // false on windows, true on mac and unix + disableRecursiveSort(false) + { + delayedSortTimer.setSingleShot(true); + } + + void init(); + /* + \internal + + Return true if index which is owned by node is hidden by the filter. + */ + inline bool isHiddenByFilter(QFileSystemNode *indexNode, const QModelIndex &index) const + { + return (indexNode != &root && !index.isValid()); + } + QFileSystemNode *node(const QModelIndex &index) const; + QFileSystemNode *node(const QString &path, bool fetch = true) const; + inline QModelIndex index(const QString &path) { return index(node(path)); } + QModelIndex index(const QFileSystemNode *node) const; + bool filtersAcceptsNode(const QFileSystemNode *node) const; + bool passNameFilters(const QFileSystemNode *node) const; + void removeNode(QFileSystemNode *parentNode, const QString &name); + QFileSystemNode* addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo &info); + void addVisibleFiles(QFileSystemNode *parentNode, const QStringList &newFiles); + void removeVisibleFile(QFileSystemNode *parentNode, int visibleLocation); + void sortChildren(int column, const QModelIndex &parent); + + inline int translateVisibleLocation(QFileSystemNode *parent, int row) const { + if (sortOrder != Qt::AscendingOrder) { + if (parent->dirtyChildrenIndex == -1) + return parent->visibleChildren.count() - row - 1; + + if (row < parent->dirtyChildrenIndex) + return parent->dirtyChildrenIndex - row - 1; + } + + return row; + } + + inline static QString myComputer() { + // ### TODO We should query the system to find out what the string should be + // XP == "My Computer", + // Vista == "Computer", + // OS X == "Computer" (sometime user generated) "Benjamin's PowerBook G4" +#ifdef Q_OS_WIN + return QFileSystemModel::tr("My Computer"); +#else + return QFileSystemModel::tr("Computer"); +#endif + } + + inline void delayedSort() { + if (!delayedSortTimer.isActive()) + delayedSortTimer.start(0); + } + + static bool caseInsensitiveLessThan(const QString &s1, const QString &s2) + { + return QString::compare(s1, s2, Qt::CaseInsensitive) < 0; + } + + static bool nodeCaseInsensitiveLessThan(const QFileSystemModelPrivate::QFileSystemNode &s1, const QFileSystemModelPrivate::QFileSystemNode &s2) + { + return QString::compare(s1.fileName, s2.fileName, Qt::CaseInsensitive) < 0; + } + + QIcon icon(const QModelIndex &index) const; + QString name(const QModelIndex &index) const; + QString displayName(const QModelIndex &index) const; + QString filePath(const QModelIndex &index) const; + QString size(const QModelIndex &index) const; + static QString size(qint64 bytes); + QString type(const QModelIndex &index) const; + QString time(const QModelIndex &index) const; + + void _q_directoryChanged(const QString &directory, const QStringList &list); + void _q_performDelayedSort(); + void _q_fileSystemChanged(const QString &path, const QList > &); + void _q_resolvedName(const QString &fileName, const QString &resolvedName); + + static int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); + + QDir rootDir; +#ifndef QT_NO_FILESYSTEMWATCHER + QFileInfoGatherer fileInfoGatherer; +#endif + QTimer delayedSortTimer; + bool forceSort; + int sortColumn; + Qt::SortOrder sortOrder; + bool readOnly; + bool setRootPath; + QDir::Filters filters; + QHash bypassFilters; + bool nameFilterDisables; + //This flag is an optimization for the QFileDialog + //It enable a sort which is not recursive, it means + //we sort only what we see. + bool disableRecursiveSort; +#ifndef QT_NO_REGEXP + QList nameFilters; +#endif + // ### Qt 5: resolvedSymLinks goes away + QHash resolvedSymLinks; + + QFileSystemNode root; + + QBasicTimer fetchingTimer; + struct Fetching { + QString dir; + QString file; + const QFileSystemNode *node; + }; + QList toFetch; + +}; +#endif // QT_NO_FILESYSTEMMODEL + +QT_END_NAMESPACE + +#endif + diff --git a/src/gui/dialogs/qfontdialog.cpp b/src/gui/dialogs/qfontdialog.cpp new file mode 100644 index 0000000000..b58021d877 --- /dev/null +++ b/src/gui/dialogs/qfontdialog.cpp @@ -0,0 +1,1077 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowdefs.h" + +#ifndef QT_NO_FONTDIALOG + +#include "qfontdialog.h" +#include "qfontdialog_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(Q_WS_S60) +#include +#endif + +QT_BEGIN_NAMESPACE + +class QFontListView : public QListView +{ + Q_OBJECT +public: + QFontListView(QWidget *parent); + inline QStringListModel *model() const { + return static_cast(QListView::model()); + } + inline void setCurrentItem(int item) { + QListView::setCurrentIndex(static_cast(model())->index(item)); + } + inline int currentItem() const { + return QListView::currentIndex().row(); + } + inline int count() const { + return model()->rowCount(); + } + inline QString currentText() const { + int row = QListView::currentIndex().row(); + return row < 0 ? QString() : model()->stringList().at(row); + } + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QListView::currentChanged(current, previous); + if (current.isValid()) + emit highlighted(current.row()); + } + QString text(int i) const { + return model()->stringList().at(i); + } +signals: + void highlighted(int); +}; + +QFontListView::QFontListView(QWidget *parent) + : QListView(parent) +{ + setModel(new QStringListModel(parent)); + setEditTriggers(NoEditTriggers); +} + +static const Qt::WindowFlags DefaultWindowFlags = + Qt::Dialog | Qt::WindowSystemMenuHint; + +/*! + \class QFontDialog + \ingroup standard-dialogs + + \brief The QFontDialog class provides a dialog widget for selecting a font. + + A font dialog is created through one of the static getFont() + functions. + + Examples: + + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 0 + + The dialog can also be used to set a widget's font directly: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 1 + If the user clicks OK the font they chose will be used for myWidget, + and if they click Cancel the original font is used. + + \image plastique-fontdialog.png A font dialog in the Plastique widget style. + + \sa QFont, QFontInfo, QFontMetrics, QColorDialog, QFileDialog, QPrintDialog, + {Standard Dialogs Example} +*/ + +/*! + \since 4.5 + + Constructs a standard font dialog. + + Use setCurrentFont() to set the initial font attributes. + + The \a parent parameter is passed to the QDialog constructor. + + \sa getFont() +*/ +QFontDialog::QFontDialog(QWidget *parent) + : QDialog(*new QFontDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QFontDialog); + d->init(); +} + +/*! + \since 4.5 + + Constructs a standard font dialog with the given \a parent and specified + \a initial color. +*/ +QFontDialog::QFontDialog(const QFont &initial, QWidget *parent) + : QDialog(*new QFontDialogPrivate, parent, DefaultWindowFlags) +{ + Q_D(QFontDialog); + d->init(); + setCurrentFont(initial); +} + +void QFontDialogPrivate::init() +{ + Q_Q(QFontDialog); + +#ifdef Q_WS_MAC + nativeDialogInUse = false; + delegate = 0; +#endif + + q->setSizeGripEnabled(true); + q->setWindowTitle(QFontDialog::tr("Select Font")); + + // grid + familyEdit = new QLineEdit(q); + familyEdit->setReadOnly(true); + familyList = new QFontListView(q); + familyEdit->setFocusProxy(familyList); + + familyAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + familyAccel->setBuddy(familyList); +#endif + familyAccel->setIndent(2); + + styleEdit = new QLineEdit(q); + styleEdit->setReadOnly(true); + styleList = new QFontListView(q); + styleEdit->setFocusProxy(styleList); + + styleAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + styleAccel->setBuddy(styleList); +#endif + styleAccel->setIndent(2); + + sizeEdit = new QLineEdit(q); + sizeEdit->setFocusPolicy(Qt::ClickFocus); + QIntValidator *validator = new QIntValidator(1, 512, q); + sizeEdit->setValidator(validator); + sizeList = new QFontListView(q); + + sizeAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + sizeAccel->setBuddy(sizeEdit); +#endif + sizeAccel->setIndent(2); + + // effects box + effects = new QGroupBox(q); + QVBoxLayout *vbox = new QVBoxLayout(effects); + strikeout = new QCheckBox(effects); + vbox->addWidget(strikeout); + underline = new QCheckBox(effects); + vbox->addWidget(underline); + + sample = new QGroupBox(q); + QHBoxLayout *hbox = new QHBoxLayout(sample); + sampleEdit = new QLineEdit(sample); + sampleEdit->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); + sampleEdit->setAlignment(Qt::AlignCenter); + // Note that the sample text is *not* translated with tr(), as the + // characters used depend on the charset encoding. + sampleEdit->setText(QLatin1String("AaBbYyZz")); + hbox->addWidget(sampleEdit); + + writingSystemCombo = new QComboBox(q); + + writingSystemAccel = new QLabel(q); +#ifndef QT_NO_SHORTCUT + writingSystemAccel->setBuddy(writingSystemCombo); +#endif + writingSystemAccel->setIndent(2); + + size = 0; + smoothScalable = false; + + QObject::connect(writingSystemCombo, SIGNAL(activated(int)), q, SLOT(_q_writingSystemHighlighted(int))); + QObject::connect(familyList, SIGNAL(highlighted(int)), q, SLOT(_q_familyHighlighted(int))); + QObject::connect(styleList, SIGNAL(highlighted(int)), q, SLOT(_q_styleHighlighted(int))); + QObject::connect(sizeList, SIGNAL(highlighted(int)), q, SLOT(_q_sizeHighlighted(int))); + QObject::connect(sizeEdit, SIGNAL(textChanged(QString)), q, SLOT(_q_sizeChanged(QString))); + + QObject::connect(strikeout, SIGNAL(clicked()), q, SLOT(_q_updateSample())); + QObject::connect(underline, SIGNAL(clicked()), q, SLOT(_q_updateSample())); + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + QFontDatabase::WritingSystem ws = QFontDatabase::WritingSystem(i); + QString writingSystemName = QFontDatabase::writingSystemName(ws); + if (writingSystemName.isEmpty()) + break; + writingSystemCombo->addItem(writingSystemName); + } + + updateFamilies(); + if (familyList->count() != 0) + familyList->setCurrentItem(0); + + // grid layout + QGridLayout *mainGrid = new QGridLayout(q); + + int spacing = mainGrid->spacing(); + if (spacing >= 0) { // uniform spacing + mainGrid->setSpacing(0); + + mainGrid->setColumnMinimumWidth(1, spacing); + mainGrid->setColumnMinimumWidth(3, spacing); + + int margin = 0; + mainGrid->getContentsMargins(0, 0, 0, &margin); + + mainGrid->setRowMinimumHeight(3, margin); + mainGrid->setRowMinimumHeight(6, 2); + mainGrid->setRowMinimumHeight(8, margin); + } + + mainGrid->addWidget(familyAccel, 0, 0); + mainGrid->addWidget(familyEdit, 1, 0); + mainGrid->addWidget(familyList, 2, 0); + + mainGrid->addWidget(styleAccel, 0, 2); + mainGrid->addWidget(styleEdit, 1, 2); + mainGrid->addWidget(styleList, 2, 2); + + mainGrid->addWidget(sizeAccel, 0, 4); + mainGrid->addWidget(sizeEdit, 1, 4); + mainGrid->addWidget(sizeList, 2, 4); + + mainGrid->setColumnStretch(0, 38); + mainGrid->setColumnStretch(2, 24); + mainGrid->setColumnStretch(4, 10); + + mainGrid->addWidget(effects, 4, 0); + + mainGrid->addWidget(sample, 4, 2, 4, 3); + + mainGrid->addWidget(writingSystemAccel, 5, 0); + mainGrid->addWidget(writingSystemCombo, 7, 0); + + buttonBox = new QDialogButtonBox(q); + mainGrid->addWidget(buttonBox, 9, 0, 1, 5); + + QPushButton *button + = static_cast(buttonBox->addButton(QDialogButtonBox::Ok)); + QObject::connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + button->setDefault(true); + + buttonBox->addButton(QDialogButtonBox::Cancel); + QObject::connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + +#if defined(Q_WS_WINCE) + q->resize(180, 120); +#elif defined(Q_WS_S60) + q->resize(QApplication::desktop()->availableGeometry(QCursor::pos()).size()); +#else + q->resize(500, 360); +#endif // Q_WS_WINCE + + sizeEdit->installEventFilter(q); + familyList->installEventFilter(q); + styleList->installEventFilter(q); + sizeList->installEventFilter(q); + + familyList->setFocus(); + retranslateStrings(); +} + +/*! + \internal + Destroys the font dialog and frees up its storage. +*/ + +QFontDialog::~QFontDialog() +{ +#ifdef Q_WS_MAC + Q_D(QFontDialog); + if (d->delegate) { + d->closeCocoaFontPanel(); + return; + } +#endif +} + +/*! + Executes a modal font dialog and returns a font. + + If the user clicks \gui OK, the selected font is returned. If the user + clicks \gui Cancel, the \a initial font is returned. + + The dialog is constructed with the given \a parent and the options specified + in \a options. \a title is shown as the window title of the dialog and \a + initial is the initially selected font. If the \a ok parameter is not-null, + the value it refers to is set to true if the user clicks \gui OK, and set to + false if the user clicks \gui Cancel. + + Examples: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 2 + + The dialog can also be used to set a widget's font directly: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 3 + In this example, if the user clicks OK the font they chose will be + used, and if they click Cancel the original font is used. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title, + FontDialogOptions options) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, title, options); +} + +/*! + \overload + \since 4.5 +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, title, 0); +} + +/*! + \overload +*/ +QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget *parent) +{ + return QFontDialogPrivate::getFont(ok, initial, parent, QString(), 0); +} + +/*! + \overload + + Executes a modal font dialog and returns a font. + + If the user clicks \gui OK, the selected font is returned. If the user + clicks \gui Cancel, the Qt default font is returned. + + The dialog is constructed with the given \a parent. + If the \a ok parameter is not-null, the value it refers to is set + to true if the user clicks \gui OK, and false if the user clicks + \gui Cancel. + + Example: + \snippet doc/src/snippets/code/src_gui_dialogs_qfontdialog.cpp 4 + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. +*/ +QFont QFontDialog::getFont(bool *ok, QWidget *parent) +{ + QFont initial; + return QFontDialogPrivate::getFont(ok, initial, parent, QString(), 0); +} + +QFont QFontDialogPrivate::getFont(bool *ok, const QFont &initial, QWidget *parent, + const QString &title, QFontDialog::FontDialogOptions options) +{ + QFontDialog dlg(parent); + dlg.setOptions(options); + dlg.setCurrentFont(initial); + if (!title.isEmpty()) + dlg.setWindowTitle(title); + + int ret = (dlg.exec() || (options & QFontDialog::NoButtons)); + if (ok) + *ok = !!ret; + if (ret) { + return dlg.selectedFont(); + } else { + return initial; + } +} + +/*! + \internal + An event filter to make the Up, Down, PageUp and PageDown keys work + correctly in the line edits. The source of the event is the object + \a o and the event is \a e. +*/ + +bool QFontDialog::eventFilter(QObject *o , QEvent *e) +{ + Q_D(QFontDialog); + if (e->type() == QEvent::KeyPress) { + QKeyEvent *k = (QKeyEvent *)e; + if (o == d->sizeEdit && + (k->key() == Qt::Key_Up || + k->key() == Qt::Key_Down || + k->key() == Qt::Key_PageUp || + k->key() == Qt::Key_PageDown)) { + + int ci = d->sizeList->currentItem(); + (void)QApplication::sendEvent(d->sizeList, k); + + if (ci != d->sizeList->currentItem() + && style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, this)) + d->sizeEdit->selectAll(); + return true; + } else if ((o == d->familyList || o == d->styleList) && + (k->key() == Qt::Key_Return || k->key() == Qt::Key_Enter)) { + k->accept(); + accept(); + return true; + } + } else if (e->type() == QEvent::FocusIn + && style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, this)) { + if (o == d->familyList) + d->familyEdit->selectAll(); + else if (o == d->styleList) + d->styleEdit->selectAll(); + else if (o == d->sizeList) + d->sizeEdit->selectAll(); + } else if (e->type() == QEvent::MouseButtonPress && o == d->sizeList) { + d->sizeEdit->setFocus(); + } + return QDialog::eventFilter(o, e); +} + +/* + Updates the contents of the "font family" list box. This + function can be reimplemented if you have special requirements. +*/ + +void QFontDialogPrivate::updateFamilies() +{ + Q_Q(QFontDialog); + + enum match_t { MATCH_NONE = 0, MATCH_LAST_RESORT = 1, MATCH_APP = 2, MATCH_FAMILY = 3 }; + + QStringList familyNames = fdb.families(writingSystem); + + familyList->model()->setStringList(familyNames); + + QString foundryName1, familyName1, foundryName2, familyName2; + int bestFamilyMatch = -1; + match_t bestFamilyType = MATCH_NONE; + + QFont f; + + // ##### do the right thing for a list of family names in the font. + QFontDatabase::parseFontName(family, foundryName1, familyName1); + + QStringList::const_iterator it = familyNames.constBegin(); + int i = 0; + for(; it != familyNames.constEnd(); ++it, ++i) { + QFontDatabase::parseFontName(*it, foundryName2, familyName2); + + //try to match... + if (familyName1 == familyName2) { + bestFamilyType = MATCH_FAMILY; + if (foundryName1 == foundryName2) { + bestFamilyMatch = i; + break; + } + if (bestFamilyMatch < MATCH_FAMILY) + bestFamilyMatch = i; + } + + //and try some fall backs + match_t type = MATCH_NONE; + if (bestFamilyType <= MATCH_NONE && familyName2 == f.lastResortFamily()) + type = MATCH_LAST_RESORT; + if (bestFamilyType <= MATCH_LAST_RESORT && familyName2 == f.family()) + type = MATCH_APP; + // ### add fallback for writingSystem + if (type != MATCH_NONE) { + bestFamilyType = type; + bestFamilyMatch = i; + } + } + + if (i != -1 && bestFamilyType != MATCH_NONE) + familyList->setCurrentItem(bestFamilyMatch); + else + familyList->setCurrentItem(0); + familyEdit->setText(familyList->currentText()); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && familyList->hasFocus()) + familyEdit->selectAll(); + + updateStyles(); +} + +/* + Updates the contents of the "font style" list box. This + function can be reimplemented if you have special requirements. +*/ +void QFontDialogPrivate::updateStyles() +{ + Q_Q(QFontDialog); + QStringList styles = fdb.styles(familyList->currentText()); + styleList->model()->setStringList(styles); + + if (styles.isEmpty()) { + styleEdit->clear(); + smoothScalable = false; + } else { + if (!style.isEmpty()) { + bool found = false; + bool first = true; + QString cstyle = style; + + redo: + for (int i = 0; i < (int)styleList->count(); i++) { + if (cstyle == styleList->text(i)) { + styleList->setCurrentItem(i); + found = true; + break; + } + } + if (!found && first) { + if (cstyle.contains(QLatin1String("Italic"))) { + cstyle.replace(QLatin1String("Italic"), QLatin1String("Oblique")); + first = false; + goto redo; + } else if (cstyle.contains(QLatin1String("Oblique"))) { + cstyle.replace(QLatin1String("Oblique"), QLatin1String("Italic")); + first = false; + goto redo; + } + } + if (!found) + styleList->setCurrentItem(0); + } else { + styleList->setCurrentItem(0); + } + + styleEdit->setText(styleList->currentText()); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && styleList->hasFocus()) + styleEdit->selectAll(); + + smoothScalable = fdb.isSmoothlyScalable(familyList->currentText(), styleList->currentText()); + } + + updateSizes(); +} + +/*! + \internal + Updates the contents of the "font size" list box. This + function can be reimplemented if you have special requirements. +*/ + +void QFontDialogPrivate::updateSizes() +{ + Q_Q(QFontDialog); + + if (!familyList->currentText().isEmpty()) { + QList sizes = fdb.pointSizes(familyList->currentText(), styleList->currentText()); + + int i = 0; + int current = -1; + QStringList str_sizes; + for(QList::const_iterator it = sizes.constBegin(); it != sizes.constEnd(); ++it) { + str_sizes.append(QString::number(*it)); + if (current == -1 && *it >= size) + current = i; + ++i; + } + sizeList->model()->setStringList(str_sizes); + if (current == -1) { + // we request a size bigger than the ones in the list, select the biggest one + current = sizeList->count() - 1; + } + sizeList->setCurrentItem(current); + + sizeEdit->blockSignals(true); + sizeEdit->setText((smoothScalable ? QString::number(size) : sizeList->currentText())); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && sizeList->hasFocus()) + sizeEdit->selectAll(); + sizeEdit->blockSignals(false); + } else { + sizeEdit->clear(); + } + + _q_updateSample(); +} + +void QFontDialogPrivate::_q_updateSample() +{ + // compute new font + int pSize = sizeEdit->text().toInt(); + QFont newFont(fdb.font(familyList->currentText(), style, pSize)); + newFont.setStrikeOut(strikeout->isChecked()); + newFont.setUnderline(underline->isChecked()); + + if (familyList->currentText().isEmpty()) + sampleEdit->clear(); + + updateSampleFont(newFont); +} + +void QFontDialogPrivate::updateSampleFont(const QFont &newFont) +{ + Q_Q(QFontDialog); + if (newFont != sampleEdit->font()) { + sampleEdit->setFont(newFont); + emit q->currentFontChanged(newFont); + } +} + +/*! + \internal +*/ +void QFontDialogPrivate::_q_writingSystemHighlighted(int index) +{ + writingSystem = QFontDatabase::WritingSystem(index); + sampleEdit->setText(fdb.writingSystemSample(writingSystem)); + updateFamilies(); +} + +/*! + \internal +*/ +void QFontDialogPrivate::_q_familyHighlighted(int i) +{ + Q_Q(QFontDialog); + family = familyList->text(i); + familyEdit->setText(family); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && familyList->hasFocus()) + familyEdit->selectAll(); + + updateStyles(); +} + + +/*! + \internal +*/ + +void QFontDialogPrivate::_q_styleHighlighted(int index) +{ + Q_Q(QFontDialog); + QString s = styleList->text(index); + styleEdit->setText(s); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && styleList->hasFocus()) + styleEdit->selectAll(); + + style = s; + + updateSizes(); +} + + +/*! + \internal +*/ + +void QFontDialogPrivate::_q_sizeHighlighted(int index) +{ + Q_Q(QFontDialog); + QString s = sizeList->text(index); + sizeEdit->setText(s); + if (q->style()->styleHint(QStyle::SH_FontDialog_SelectAssociatedText, 0, q) + && sizeEdit->hasFocus()) + sizeEdit->selectAll(); + + size = s.toInt(); + _q_updateSample(); +} + +/*! + \internal + This slot is called if the user changes the font size. + The size is passed in the \a s argument as a \e string. +*/ + +void QFontDialogPrivate::_q_sizeChanged(const QString &s) +{ + // no need to check if the conversion is valid, since we have an QIntValidator in the size edit + int size = s.toInt(); + if (this->size == size) + return; + + this->size = size; + if (sizeList->count() != 0) { + int i; + for (i = 0; i < sizeList->count() - 1; i++) { + if (sizeList->text(i).toInt() >= this->size) + break; + } + sizeList->blockSignals(true); + sizeList->setCurrentItem(i); + sizeList->blockSignals(false); + } + _q_updateSample(); +} + +void QFontDialogPrivate::retranslateStrings() +{ + familyAccel->setText(QFontDialog::tr("&Font")); + styleAccel->setText(QFontDialog::tr("Font st&yle")); + sizeAccel->setText(QFontDialog::tr("&Size")); +#ifndef Q_WS_S60 + // Removed the title due to lack of screen estate in small S60 screen. + // The effects are descriptive without a title (strikeout, underline). + effects->setTitle(QFontDialog::tr("Effects")); +#endif + strikeout->setText(QFontDialog::tr("Stri&keout")); + underline->setText(QFontDialog::tr("&Underline")); + sample->setTitle(QFontDialog::tr("Sample")); + writingSystemAccel->setText(QFontDialog::tr("Wr&iting System")); +} + +/*! + \reimp +*/ +void QFontDialog::changeEvent(QEvent *e) +{ + Q_D(QFontDialog); + if (e->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(e); +} + +/*! + \since 4.5 + + \property QFontDialog::currentFont + \brief the current font of the dialog. +*/ + +/*! + \since 4.5 + + Sets the font highlighted in the QFontDialog to the given \a font. + + \sa selectedFont() +*/ +void QFontDialog::setCurrentFont(const QFont &font) +{ + Q_D(QFontDialog); + d->family = font.family(); + d->style = d->fdb.styleString(font); + d->size = font.pointSize(); + if (d->size == -1) { + QFontInfo fi(font); + d->size = fi.pointSize(); + } + d->strikeout->setChecked(font.strikeOut()); + d->underline->setChecked(font.underline()); + d->updateFamilies(); + +#ifdef Q_WS_MAC + if (d->delegate) + QFontDialogPrivate::setFont(d->delegate, font); +#endif +} + +/*! + \since 4.5 + + Returns the current font. + + \sa selectedFont() +*/ +QFont QFontDialog::currentFont() const +{ + Q_D(const QFontDialog); + return d->sampleEdit->font(); +} + +/*! + Returns the font that the user selected by clicking the \gui{OK} + or equivalent button. + + \note This font is not always the same as the font held by the + \l currentFont property since the user can choose different fonts + before finally selecting the one to use. +*/ +QFont QFontDialog::selectedFont() const +{ + Q_D(const QFontDialog); + return d->selectedFont; +} + +/*! + \enum QFontDialog::FontDialogOption + \since 4.5 + + This enum specifies various options that affect the look and feel + of a font dialog. + + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value DontUseNativeDialog Use Qt's standard font dialog on the Mac instead of Apple's + native font panel. (Currently, the native dialog is never used, + but this is likely to change in future Qt releases.) + + \sa options, setOption(), testOption() +*/ + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QFontDialog::setOption(FontDialogOption option, bool on) +{ + Q_D(QFontDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QFontDialog::testOption(FontDialogOption option) const +{ + Q_D(const QFontDialog); + return (d->opts & option) != 0; +} + +/*! + \property QFontDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QFontDialog::setOptions(FontDialogOptions options) +{ + Q_D(QFontDialog); + + FontDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->buttonBox->setVisible(!(options & NoButtons)); +} + +QFontDialog::FontDialogOptions QFontDialog::options() const +{ + Q_D(const QFontDialog); + return d->opts; +} + +#ifdef Q_WS_MAC +// can only have one Cocoa font panel active +bool QFontDialogPrivate::sharedFontPanelAvailable = true; +#endif + +/*! + \since 4.5 + \overload + + Opens the dialog and connects its fontSelected() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QFontDialog::open(QObject *receiver, const char *member) +{ + Q_D(QFontDialog); + connect(this, SIGNAL(fontSelected(QFont)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \since 4.5 + + \fn void QFontDialog::currentFontChanged(const QFont &font) + + This signal is emitted when the current font is changed. The new font is + specified in \a font. + + The signal is emitted while a user is selecting a font. Ultimately, the + chosen font may differ from the font currently selected. + + \sa currentFont, fontSelected(), selectedFont() +*/ + +/*! + \since 4.5 + + \fn void QFontDialog::fontSelected(const QFont &font) + + This signal is emitted when a font has been selected. The selected font is + specified in \a font. + + The signal is only emitted when a user has chosen the final font to be + used. It is not emitted while the user is changing the current font in the + font dialog. + + \sa selectedFont(), currentFontChanged(), currentFont +*/ + +/*! + \reimp +*/ +void QFontDialog::setVisible(bool visible) +{ + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) != visible) + return; +#ifdef Q_WS_MAC + Q_D(QFontDialog); + if (d->canBeNativeDialog()){ + if (d->setVisible_sys(visible)){ + d->nativeDialogInUse = true; + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen, true); + } else { + d->nativeDialogInUse = false; + setAttribute(Qt::WA_DontShowOnScreen, false); + } + } +#endif // Q_WS_MAC + QDialog::setVisible(visible); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QFontDialog::done(int result) +{ + Q_D(QFontDialog); + QDialog::done(result); + if (result == Accepted) { + // We check if this is the same font we had before, if so we emit currentFontChanged + QFont selectedFont = currentFont(); + if(selectedFont != d->selectedFont) + emit(currentFontChanged(selectedFont)); + d->selectedFont = selectedFont; + emit fontSelected(d->selectedFont); + } else + d->selectedFont = QFont(); + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(fontSelected(QFont)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +#ifdef Q_WS_MAC +bool QFontDialogPrivate::canBeNativeDialog() +{ + Q_Q(QFontDialog); + if (nativeDialogInUse) + return true; + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return false; + if (opts & QFontDialog::DontUseNativeDialog) + return false; + + QLatin1String staticName(QFontDialog::staticMetaObject.className()); + QLatin1String dynamicName(q->metaObject()->className()); + return (staticName == dynamicName); +} +#endif // Q_WS_MAC + +/*! + \fn QFont QFontDialog::getFont(bool *ok, const QFont &initial, QWidget* parent, const char* name) + \since 4.5 + + Call getFont(\a ok, \a initial, \a parent) instead. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. + + The \a name parameter is ignored. +*/ + +/*! + \fn QFont QFontDialog::getFont(bool *ok, QWidget* parent, const char* name) + + Call getFont(\a ok, \a parent) instead. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QFontDialog constructors. + + The \a name parameter is ignored. +*/ + +QT_END_NAMESPACE + +#include "qfontdialog.moc" +#include "moc_qfontdialog.cpp" + +#endif // QT_NO_FONTDIALOG diff --git a/src/gui/dialogs/qfontdialog.h b/src/gui/dialogs/qfontdialog.h new file mode 100644 index 0000000000..a09f16d0a0 --- /dev/null +++ b/src/gui/dialogs/qfontdialog.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDIALOG_H +#define QFONTDIALOG_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FONTDIALOG + +class QFontDialogPrivate; + +class Q_GUI_EXPORT QFontDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QFontDialog) + Q_ENUMS(FontDialogOption) + Q_PROPERTY(QFont currentFont READ currentFont WRITE setCurrentFont NOTIFY currentFontChanged) + Q_PROPERTY(FontDialogOptions options READ options WRITE setOptions) + +public: + enum FontDialogOption { + NoButtons = 0x00000001, + DontUseNativeDialog = 0x00000002 + }; + + Q_DECLARE_FLAGS(FontDialogOptions, FontDialogOption) + + explicit QFontDialog(QWidget *parent = 0); + explicit QFontDialog(const QFont &initial, QWidget *parent = 0); + ~QFontDialog(); + + void setCurrentFont(const QFont &font); + QFont currentFont() const; + + QFont selectedFont() const; + + void setOption(FontDialogOption option, bool on = true); + bool testOption(FontDialogOption option) const; + void setOptions(FontDialogOptions options); + FontDialogOptions options() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + void setVisible(bool visible); + + // ### Qt 5: merge overloads + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title, + FontDialogOptions options); + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const QString &title); + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent = 0); + static QFont getFont(bool *ok, QWidget *parent = 0); + +#ifdef QT3_SUPPORT + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, const char *name) + { Q_UNUSED(name); return getFont(ok, initial, parent); } + static QFont getFont(bool *ok, QWidget *parent, const char *name) + { Q_UNUSED(name); return getFont(ok, parent); } +#endif + +Q_SIGNALS: + void currentFontChanged(const QFont &font); + void fontSelected(const QFont &font); + +protected: + void changeEvent(QEvent *event); + void done(int result); + +private: + // ### Qt 5: make protected + bool eventFilter(QObject *object, QEvent *event); + + Q_DISABLE_COPY(QFontDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_sizeChanged(const QString &)) + Q_PRIVATE_SLOT(d_func(), void _q_familyHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_writingSystemHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_styleHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_sizeHighlighted(int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSample()) +#if defined(Q_WS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_macRunNativeAppModalPanel()) +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QFontDialog::FontDialogOptions) + +#endif // QT_NO_FONTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTDIALOG_H diff --git a/src/gui/dialogs/qfontdialog_mac.mm b/src/gui/dialogs/qfontdialog_mac.mm new file mode 100644 index 0000000000..1552ad675f --- /dev/null +++ b/src/gui/dialogs/qfontdialog_mac.mm @@ -0,0 +1,699 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontdialog_p.h" +#if !defined(QT_NO_FONTDIALOG) && defined(Q_WS_MAC) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#import +#import + +#if !CGFLOAT_DEFINED +typedef float CGFloat; // Should only not be defined on 32-bit platforms +#endif + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptNSPanelCtor(); +extern void macStopInterceptNSPanelCtor(); +extern NSButton *macCreateButton(const char *text, NSView *superview); +extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm + +QT_END_NAMESPACE +QT_USE_NAMESPACE + +// should a priori be kept in sync with qcolordialog_mac.mm +const CGFloat ButtonMinWidth = 78.0; +const CGFloat ButtonMinHeight = 32.0; +const CGFloat ButtonSpacing = 0.0; +const CGFloat ButtonTopMargin = 0.0; +const CGFloat ButtonBottomMargin = 7.0; +const CGFloat ButtonSideMargin = 9.0; + +// looks better with some margins +const CGFloat DialogTopMargin = 7.0; +const CGFloat DialogSideMargin = 9.0; + +const int StyleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask; + +@class QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate); + + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + +@protocol NSWindowDelegate +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +@end + +#endif + +@interface QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) : NSObject { + NSFontPanel *mFontPanel; + NSView *mStolenContentView; + NSButton *mOkButton; + NSButton *mCancelButton; + QFontDialogPrivate *mPriv; + QFont *mQtFont; + BOOL mPanelHackedWithButtons; + CGFloat mDialogExtraWidth; + CGFloat mDialogExtraHeight; + int mReturnCode; + BOOL mAppModal; +} +- (id)initWithFontPanel:(NSFontPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QFontDialogPrivate *)priv + extraWidth:(CGFloat)extraWidth + extraHeight:(CGFloat)extraHeight; +- (void)showModelessPanel; +- (void)showWindowModalSheet:(QWidget *)docWidget; +- (void)runApplicationModalPanel; +- (BOOL)isAppModal; +- (void)changeFont:(id)sender; +- (void)changeAttributes:(id)sender; +- (BOOL)windowShouldClose:(id)window; +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +- (void)relayout; +- (void)relayoutToContentSize:(NSSize)frameSize; +- (void)onOkClicked; +- (void)onCancelClicked; +- (NSFontPanel *)fontPanel; +- (NSWindow *)actualPanel; +- (NSSize)dialogExtraSize; +- (void)setQtFont:(const QFont &)newFont; +- (QFont)qtFont; +- (void)finishOffWithCode:(NSInteger)result; +- (void)cleanUpAfterMyself; +- (void)setSubwindowStacking; +@end + +static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont) +{ + QFont newFont; + if (cocoaFont) { + int pSize = qRound([cocoaFont pointSize]); + QString family(qt_mac_NSStringToQString([cocoaFont familyName])); + QString typeface(qt_mac_NSStringToQString([cocoaFont fontName])); + + int hyphenPos = typeface.indexOf(QLatin1Char('-')); + if (hyphenPos != -1) { + typeface.remove(0, hyphenPos + 1); + } else { + typeface = QLatin1String("Normal"); + } + + newFont = QFontDatabase().font(family, typeface, pSize); + newFont.setUnderline(resolveFont.underline()); + newFont.setStrikeOut(resolveFont.strikeOut()); + + } + return newFont; +} + +@implementation QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) +- (id)initWithFontPanel:(NSFontPanel *)panel + stolenContentView:(NSView *)stolenContentView + okButton:(NSButton *)okButton + cancelButton:(NSButton *)cancelButton + priv:(QFontDialogPrivate *)priv + extraWidth:(CGFloat)extraWidth + extraHeight:(CGFloat)extraHeight +{ + self = [super init]; + mFontPanel = panel; + mStolenContentView = stolenContentView; + mOkButton = okButton; + mCancelButton = cancelButton; + mPriv = priv; + mPanelHackedWithButtons = (okButton != 0); + mDialogExtraWidth = extraWidth; + mDialogExtraHeight = extraHeight; + mReturnCode = -1; + mAppModal = false; + + if (mPanelHackedWithButtons) { + [self relayout]; + + [okButton setAction:@selector(onOkClicked)]; + [okButton setTarget:self]; + + [cancelButton setAction:@selector(onCancelClicked)]; + [cancelButton setTarget:self]; + } + + mQtFont = new QFont(); + return self; +} + +- (void)setSubwindowStacking +{ +#ifdef QT_MAC_USE_COCOA + // Stack the native dialog in front of its parent, if any: + QFontDialog *q = mPriv->fontDialog(); + if (!qt_mac_is_macsheet(q)) { + if (QWidget *parent = q->parentWidget()) { + if (parent->isWindow()) { + [qt_mac_window_for(parent) + addChildWindow:[mStolenContentView window] ordered:NSWindowAbove]; + } + } + } +#endif +} + +- (void)dealloc +{ + delete mQtFont; + [super dealloc]; +} + +- (void)showModelessPanel +{ + mAppModal = false; + NSWindow *ourPanel = [mStolenContentView window]; + [ourPanel makeKeyAndOrderFront:self]; +} + +- (void)runApplicationModalPanel +{ + QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); + mAppModal = true; + NSWindow *ourPanel = [mStolenContentView window]; + [ourPanel setReleasedWhenClosed:NO]; + [NSApp runModalForWindow:ourPanel]; + QAbstractEventDispatcher::instance()->interrupt(); + + if (mReturnCode == NSOKButton) + mPriv->fontDialog()->accept(); + else + mPriv->fontDialog()->reject(); +} + +- (BOOL)isAppModal +{ + return mAppModal; +} + +- (void)showWindowModalSheet:(QWidget *)docWidget +{ +#ifdef QT_MAC_USE_COCOA + NSWindow *window = qt_mac_window_for(docWidget); +#else + WindowRef hiwindowRef = qt_mac_window_for(docWidget); + NSWindow *window = [[NSWindow alloc] initWithWindowRef:hiwindowRef]; + CFRetain(hiwindowRef); +#endif + + mAppModal = false; + NSWindow *ourPanel = [mStolenContentView window]; + [NSApp beginSheet:ourPanel + modalForWindow:window + modalDelegate:0 + didEndSelector:0 + contextInfo:0 ]; + +#ifndef QT_MAC_USE_COCOA + CFRelease(hiwindowRef); +#endif +} + +- (void)changeFont:(id)sender +{ + NSFont *dummyFont = [NSFont userFontOfSize:12.0]; + [self setQtFont:qfontForCocoaFont([sender convertFont:dummyFont], *mQtFont)]; + if (mPriv) + mPriv->updateSampleFont(*mQtFont); +} + +- (void)changeAttributes:(id)sender +{ + NSDictionary *dummyAttribs = [NSDictionary dictionary]; + NSDictionary *attribs = [sender convertAttributes:dummyAttribs]; + +#ifdef QT_MAC_USE_COCOA + for (id key in attribs) { +#else + NSEnumerator *enumerator = [attribs keyEnumerator]; + id key; + while((key = [enumerator nextObject])) { +#endif + NSNumber *number = static_cast([attribs objectForKey:key]); + if ([key isEqual:NSUnderlineStyleAttributeName]) { + mQtFont->setUnderline([number intValue] != NSUnderlineStyleNone); + } else if ([key isEqual:NSStrikethroughStyleAttributeName]) { + mQtFont->setStrikeOut([number intValue] != NSUnderlineStyleNone); + } + } + + if (mPriv) + mPriv->updateSampleFont(*mQtFont); +} + +- (BOOL)windowShouldClose:(id)window +{ + Q_UNUSED(window); + if (mPanelHackedWithButtons) { + [self onCancelClicked]; + } else { + [self finishOffWithCode:NSCancelButton]; + } + return true; +} + +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize +{ + if (mFontPanel == window) { + proposedFrameSize = [static_cast >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize]; + } else { + /* + Ugly hack: NSFontPanel rearranges the layout of its main + component in windowWillResize:toSize:. So we temporarily + restore the stolen content view to its rightful owner, + call windowWillResize:toSize:, and steal the content view + again. + */ + [mStolenContentView removeFromSuperview]; + [mFontPanel setContentView:mStolenContentView]; + NSSize extraSize = [self dialogExtraSize]; + proposedFrameSize.width -= extraSize.width; + proposedFrameSize.height -= extraSize.height; + proposedFrameSize = [static_cast >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize]; + NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize }; + [mFontPanel setFrame:frameRect display:NO]; + [mFontPanel setContentView:0]; + [[window contentView] addSubview:mStolenContentView]; + proposedFrameSize.width += extraSize.width; + proposedFrameSize.height += extraSize.height; + } + if (mPanelHackedWithButtons) { + NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize }; + NSRect contentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[window styleMask]]; + [self relayoutToContentSize:contentRect.size]; + } + return proposedFrameSize; +} + +- (void)relayout +{ + [self relayoutToContentSize:[[mStolenContentView superview] frame].size]; +} + +- (void)relayoutToContentSize:(NSSize)frameSize +{ + Q_ASSERT(mPanelHackedWithButtons); + + [mOkButton sizeToFit]; + NSSize okSizeHint = [mOkButton frame].size; + + [mCancelButton sizeToFit]; + NSSize cancelSizeHint = [mCancelButton frame].size; + + const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth, + qMax(okSizeHint.width, cancelSizeHint.width)), + CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5)); + const CGFloat ButtonHeight = qMax(ButtonMinHeight, + qMax(okSizeHint.height, cancelSizeHint.height)); + + const CGFloat X = DialogSideMargin; + const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin; + + NSRect okRect = { { frameSize.width - ButtonSideMargin - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mOkButton setFrame:okRect]; + [mOkButton setNeedsDisplay:YES]; + + NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth, + ButtonBottomMargin }, + { ButtonWidth, ButtonHeight } }; + [mCancelButton setFrame:cancelRect]; + [mCancelButton setNeedsDisplay:YES]; + + NSRect stolenCVRect = { { X, Y }, + { frameSize.width - X - X, frameSize.height - Y - DialogTopMargin } }; + [mStolenContentView setFrame:stolenCVRect]; + [mStolenContentView setNeedsDisplay:YES]; + + [[mStolenContentView superview] setNeedsDisplay:YES]; +} + +- (void)onOkClicked +{ + Q_ASSERT(mPanelHackedWithButtons); + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + [self setQtFont:qfontForCocoaFont([fontManager convertFont:[fontManager selectedFont]], + *mQtFont)]; + [self finishOffWithCode:NSOKButton]; +} + +- (void)onCancelClicked +{ + Q_ASSERT(mPanelHackedWithButtons); + [self finishOffWithCode:NSCancelButton]; +} + +- (NSFontPanel *)fontPanel +{ + return mFontPanel; +} + +- (NSWindow *)actualPanel +{ + return [mStolenContentView window]; +} + +- (NSSize)dialogExtraSize +{ + // this must be recomputed each time, because sometimes the + // NSFontPanel has the NSDocModalWindowMask flag set, and sometimes + // not -- which affects the frame rect vs. content rect measurements + + // take the different frame rectangles into account for dialogExtra{Width,Height} + NSRect someRect = { { 0.0, 0.0 }, { 100000.0, 100000.0 } }; + NSRect sharedFontPanelContentRect = [mFontPanel contentRectForFrameRect:someRect]; + NSRect ourPanelContentRect = [NSWindow contentRectForFrameRect:someRect styleMask:StyleMask]; + + NSSize result = { mDialogExtraWidth, mDialogExtraHeight }; + result.width -= ourPanelContentRect.size.width - sharedFontPanelContentRect.size.width; + result.height -= ourPanelContentRect.size.height - sharedFontPanelContentRect.size.height; + return result; +} + +- (void)setQtFont:(const QFont &)newFont +{ + delete mQtFont; + mQtFont = new QFont(newFont); +} + +- (QFont)qtFont +{ + return *mQtFont; +} + +- (void)finishOffWithCode:(NSInteger)code +{ +#ifdef QT_MAC_USE_COCOA + QFontDialog *q = mPriv->fontDialog(); + if (QWidget *parent = q->parentWidget()) { + if (parent->isWindow()) { + [qt_mac_window_for(parent) removeChildWindow:[mStolenContentView window]]; + } + } +#endif + + if(code == NSOKButton) + mPriv->sampleEdit->setFont([self qtFont]); + + if (mAppModal) { + mReturnCode = code; + [NSApp stopModalWithCode:code]; + } else { + if (code == NSOKButton) + mPriv->fontDialog()->accept(); + else + mPriv->fontDialog()->reject(); + } +} + +- (void)cleanUpAfterMyself +{ + if (mPanelHackedWithButtons) { + NSView *ourContentView = [mFontPanel contentView]; + + // return stolen stuff to its rightful owner + [mStolenContentView removeFromSuperview]; + [mFontPanel setContentView:mStolenContentView]; + + [mOkButton release]; + [mCancelButton release]; + [ourContentView release]; + } + [mFontPanel setDelegate:nil]; + [[NSFontManager sharedFontManager] setDelegate:nil]; +#ifdef QT_MAC_USE_COCOA + [[NSFontManager sharedFontManager] setTarget:nil]; +#endif +} +@end + +QT_BEGIN_NAMESPACE + +void QFontDialogPrivate::closeCocoaFontPanel() +{ + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *theDelegate = static_cast(delegate); + NSWindow *ourPanel = [theDelegate actualPanel]; + [ourPanel close]; + if ([theDelegate isAppModal]) + [ourPanel release]; + [theDelegate cleanUpAfterMyself]; + [theDelegate release]; + this->delegate = 0; + sharedFontPanelAvailable = true; +} + +void QFontDialogPrivate::setFont(void *delegate, const QFont &font) +{ + QMacCocoaAutoReleasePool pool; + QFontEngine *fe = font.d->engineForScript(QUnicodeTables::Common); + NSFontManager *mgr = [NSFontManager sharedFontManager]; + const NSFont *nsFont = 0; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (qstrcmp(fe->name(), "CoreText") == 0) { + nsFont = reinterpret_cast(static_cast(fe)->ctfont); + } else +#endif + { + int weight = 5; + NSFontTraitMask mask = 0; + if (font.style() == QFont::StyleItalic) { + mask |= NSItalicFontMask; + } + if (font.weight() == QFont::Bold) { + weight = 9; + mask |= NSBoldFontMask; + } + + NSFontManager *mgr = [NSFontManager sharedFontManager]; + QFontInfo fontInfo(font); + nsFont = [mgr fontWithFamily:qt_mac_QStringToNSString(fontInfo.family()) + traits:mask + weight:weight + size:fontInfo.pointSize()]; + } + + [mgr setSelectedFont:const_cast(nsFont) isMultiple:NO]; + [static_cast(delegate) setQtFont:font]; +} + +void QFontDialogPrivate::createNSFontPanelDelegate() +{ + if (delegate) + return; + + sharedFontPanelAvailable = false; + QMacCocoaAutoReleasePool pool; + bool sharedFontPanelExisted = [NSFontPanel sharedFontPanelExists]; + NSFontPanel *sharedFontPanel = [NSFontPanel sharedFontPanel]; + [sharedFontPanel setHidesOnDeactivate:false]; + + // hack to ensure that QCocoaApplication's validModesForFontPanel: + // implementation is honored + if (!sharedFontPanelExisted) { + [sharedFontPanel makeKeyAndOrderFront:sharedFontPanel]; + [sharedFontPanel close]; + } + + NSPanel *ourPanel = 0; + NSView *stolenContentView = 0; + NSButton *okButton = 0; + NSButton *cancelButton = 0; + + CGFloat dialogExtraWidth = 0.0; + CGFloat dialogExtraHeight = 0.0; + + // compute dialogExtra{Width,Height} + dialogExtraWidth = 2.0 * DialogSideMargin; + dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight + ButtonBottomMargin; + + // compute initial contents rectangle + NSRect contentRect = [sharedFontPanel contentRectForFrameRect:[sharedFontPanel frame]]; + contentRect.size.width += dialogExtraWidth; + contentRect.size.height += dialogExtraHeight; + + // create the new panel + ourPanel = [[NSPanel alloc] initWithContentRect:contentRect + styleMask:StyleMask + backing:NSBackingStoreBuffered + defer:YES]; + [ourPanel setReleasedWhenClosed:YES]; + stolenContentView = [sharedFontPanel contentView]; + + // steal the font panel's contents view + [stolenContentView retain]; + [sharedFontPanel setContentView:0]; + + { + // create a new content view and add the stolen one as a subview + NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect]; + [ourContentView addSubview:stolenContentView]; + + // create OK and Cancel buttons and add these as subviews + okButton = macCreateButton("&OK", ourContentView); + cancelButton = macCreateButton("Cancel", ourContentView); + + [ourPanel setContentView:ourContentView]; + [ourPanel setDefaultButtonCell:[okButton cell]]; + } + + // create the delegate and set it + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = [[QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) alloc] initWithFontPanel:sharedFontPanel + stolenContentView:stolenContentView + okButton:okButton + cancelButton:cancelButton + priv:this + extraWidth:dialogExtraWidth + extraHeight:dialogExtraHeight]; + delegate = del; + [ourPanel setDelegate:del]; + + [[NSFontManager sharedFontManager] setDelegate:del]; +#ifdef QT_MAC_USE_COCOA + [[NSFontManager sharedFontManager] setTarget:del]; +#endif + setFont(del, q_func()->currentFont()); + + { + // hack to get correct initial layout + NSRect frameRect = [ourPanel frame]; + frameRect.size.width += 1.0; + [ourPanel setFrame:frameRect display:NO]; + frameRect.size.width -= 1.0; + frameRect.size = [del windowWillResize:ourPanel toSize:frameRect.size]; + [ourPanel setFrame:frameRect display:NO]; + [ourPanel center]; + } + [del setSubwindowStacking]; + NSString *title = @"Select font"; + [ourPanel setTitle:title]; +} + +void QFontDialogPrivate::mac_nativeDialogModalHelp() +{ + // Copied from QFileDialogPrivate + // Do a queued meta-call to open the native modal dialog so it opens after the new + // event loop has started to execute (in QDialog::exec). Using a timer rather than + // a queued meta call is intentional to ensure that the call is only delivered when + // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not + // running (which is the case if e.g a top-most 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 (nativeDialogInUse) { + Q_Q(QFontDialog); + QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel())); + } +} + +// The problem with the native font dialog is that OS X does not +// offer a proper dialog, but a panel (i.e. without Ok and Cancel buttons). +// This means we need to "construct" a native dialog by taking the panel +// and "adding" the buttons. +void QFontDialogPrivate::_q_macRunNativeAppModalPanel() +{ + createNSFontPanelDelegate(); + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast(delegate); + [del runApplicationModalPanel]; +} + +bool QFontDialogPrivate::showCocoaFontPanel() +{ + if (!sharedFontPanelAvailable) + return false; + + Q_Q(QFontDialog); + QMacCocoaAutoReleasePool pool; + createNSFontPanelDelegate(); + QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast(delegate); + if (qt_mac_is_macsheet(q)) + [del showWindowModalSheet:q->parentWidget()]; + else + [del showModelessPanel]; + return true; +} + +bool QFontDialogPrivate::hideCocoaFontPanel() +{ + if (!delegate){ + // Nothing to do. We return false to leave the question + // open regarding whether or not to go native: + return false; + } else { + closeCocoaFontPanel(); + // Even when we hide it, we are still using a + // native dialog, so return true: + return true; + } +} +bool QFontDialogPrivate::setVisible_sys(bool visible) +{ + Q_Q(QFontDialog); + if (!visible == q->isHidden()) + return false; + + return visible ? showCocoaFontPanel() : hideCocoaFontPanel(); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qfontdialog_p.h b/src/gui/dialogs/qfontdialog_p.h new file mode 100644 index 0000000000..3179b89af7 --- /dev/null +++ b/src/gui/dialogs/qfontdialog_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDIALOG_P_H +#define QFONTDIALOG_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/qdialog_p.h" +#include "qfontdatabase.h" +#include "qfontdialog.h" + +#ifndef QT_NO_FONTDIALOG + +QT_BEGIN_NAMESPACE + +class QBoxLayout; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QFontListView; +class QGroupBox; +class QLabel; +class QLineEdit; + +class QFontDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QFontDialog) + +public: + inline QFontDialogPrivate() + : writingSystem(QFontDatabase::Any) + { } + + void updateFamilies(); + void updateStyles(); + void updateSizes(); + + static QFont getFont(bool *ok, const QFont &initial, QWidget *parent, + const QString &title, QFontDialog::FontDialogOptions options); + + void init(); + void _q_sizeChanged(const QString &); + void _q_familyHighlighted(int); + void _q_writingSystemHighlighted(int); + void _q_styleHighlighted(int); + void _q_sizeHighlighted(int); + void _q_updateSample(); + void updateSampleFont(const QFont &newFont); + void retranslateStrings(); + + QLabel *familyAccel; + QLineEdit *familyEdit; + QFontListView *familyList; + + QLabel *styleAccel; + QLineEdit *styleEdit; + QFontListView *styleList; + + QLabel *sizeAccel; + QLineEdit *sizeEdit; + QFontListView *sizeList; + + QGroupBox *effects; + QCheckBox *strikeout; + QCheckBox *underline; + QComboBox *color; + + QGroupBox *sample; + QLineEdit *sampleEdit; + + QLabel *writingSystemAccel; + QComboBox *writingSystemCombo; + + QBoxLayout *buttonLayout; + QBoxLayout *effectsLayout; + QBoxLayout *sampleLayout; + QBoxLayout *sampleEditLayout; + + QDialogButtonBox *buttonBox; + + QFontDatabase fdb; + QString family; + QFontDatabase::WritingSystem writingSystem; + QString style; + int size; + bool smoothScalable; + QFont selectedFont; + QFontDialog::FontDialogOptions opts; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + +#ifdef Q_WS_MAC + static void setFont(void *delegate, const QFont &font); + + inline void done(int result) { q_func()->done(result); } + inline QFontDialog *fontDialog() { return q_func(); } + + void *delegate; + void closeCocoaFontPanel(); + bool nativeDialogInUse; + bool canBeNativeDialog(); + bool setVisible_sys(bool visible); + void createNSFontPanelDelegate(); + void _q_macRunNativeAppModalPanel(); + void mac_nativeDialogModalHelp(); + bool showCocoaFontPanel(); + bool hideCocoaFontPanel(); + + static bool sharedFontPanelAvailable; +#endif +}; + +#endif // QT_NO_FONTDIALOG + +QT_END_NAMESPACE + +#endif // QFONTDIALOG_P_H diff --git a/src/gui/dialogs/qfscompleter_p.h b/src/gui/dialogs/qfscompleter_p.h new file mode 100644 index 0000000000..26a6698a6c --- /dev/null +++ b/src/gui/dialogs/qfscompleter_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFSCOMPLETOR_P_H +#define QFSCOMPLETOR_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 "qcompleter.h" +#include +QT_BEGIN_NAMESPACE +#ifndef QT_NO_FSCOMPLETER + +/*! + QCompleter that can deal with QFileSystemModel + */ +class QFSCompleter : public QCompleter { +public: + QFSCompleter(QFileSystemModel *model, QObject *parent = 0) + : QCompleter(model, parent), proxyModel(0), sourceModel(model) + { +#if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN) + setCaseSensitivity(Qt::CaseInsensitive); +#endif + } + QString pathFromIndex(const QModelIndex &index) const; + QStringList splitPath(const QString& path) const; + + QAbstractProxyModel *proxyModel; + QFileSystemModel *sourceModel; +}; +#endif // QT_NO_FSCOMPLETER +QT_END_NAMESPACE +#endif // QFSCOMPLETOR_P_H + diff --git a/src/gui/dialogs/qinputdialog.cpp b/src/gui/dialogs/qinputdialog.cpp new file mode 100644 index 0000000000..f13b8f55c3 --- /dev/null +++ b/src/gui/dialogs/qinputdialog.cpp @@ -0,0 +1,1489 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qinputdialog.h" + +#ifndef QT_NO_INPUTDIALOG + +#include "qapplication.h" +#include "qcombobox.h" +#include "qdialogbuttonbox.h" +#include "qlabel.h" +#include "qlayout.h" +#include "qlineedit.h" +#include "qlistwidget.h" +#include "qpushbutton.h" +#include "qspinbox.h" +#include "qstackedlayout.h" +#include "qvalidator.h" +#include "qevent.h" +#include "qdialog_p.h" + +QT_USE_NAMESPACE + +static const char *signalForMember(const char *member) +{ + static const int NumCandidates = 4; + static const char * const candidateSignals[NumCandidates] = { + SIGNAL(textValueSelected(QString)), + SIGNAL(intValueSelected(int)), + SIGNAL(doubleValueSelected(double)), + SIGNAL(accepted()) + }; + + QByteArray normalizedMember(QMetaObject::normalizedSignature(member)); + + int i = 0; + while (i < NumCandidates - 1) { // sic + if (QMetaObject::checkConnectArgs(candidateSignals[i], normalizedMember)) + break; + ++i; + } + return candidateSignals[i]; +} + +QT_BEGIN_NAMESPACE + +/* + These internal classes add extra validation to QSpinBox and QDoubleSpinBox by emitting + textChanged(bool) after events that may potentially change the visible text. Return or + Enter key presses are not propagated if the visible text is invalid. Instead, the visible + text is modified to the last valid value. +*/ +class QInputDialogSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + QInputDialogSpinBox(QWidget *parent) + : QSpinBox(parent) { + connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(notifyTextChanged())); + connect(this, SIGNAL(editingFinished()), this, SLOT(notifyTextChanged())); + } + +signals: + void textChanged(bool); + +private slots: + void notifyTextChanged() { emit textChanged(hasAcceptableInput()); } + +private: + void keyPressEvent(QKeyEvent *event) { + if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && !hasAcceptableInput()) { +#ifndef QT_NO_PROPERTIES + setProperty("value", property("value")); +#endif + } else { + QSpinBox::keyPressEvent(event); + } + notifyTextChanged(); + } + + void mousePressEvent(QMouseEvent *event) { + QSpinBox::mousePressEvent(event); + notifyTextChanged(); + } +}; + +class QInputDialogDoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + QInputDialogDoubleSpinBox(QWidget *parent = 0) + : QDoubleSpinBox(parent) { + connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(notifyTextChanged())); + connect(this, SIGNAL(editingFinished()), this, SLOT(notifyTextChanged())); + } + +signals: + void textChanged(bool); + +private slots: + void notifyTextChanged() { emit textChanged(hasAcceptableInput()); } + +private: + void keyPressEvent(QKeyEvent *event) { + if ((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && !hasAcceptableInput()) { +#ifndef QT_NO_PROPERTIES + setProperty("value", property("value")); +#endif + } else { + QDoubleSpinBox::keyPressEvent(event); + } + notifyTextChanged(); + } + + void mousePressEvent(QMouseEvent *event) { + QDoubleSpinBox::mousePressEvent(event); + notifyTextChanged(); + } +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qinputdialog.moc" +QT_END_INCLUDE_NAMESPACE + +class QInputDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QInputDialog) + +public: + QInputDialogPrivate(); + + void ensureLayout(); + void ensureLineEdit(); + void ensureComboBox(); + void ensureListView(); + void ensureIntSpinBox(); + void ensureDoubleSpinBox(); + void ensureEnabledConnection(QAbstractSpinBox *spinBox); + void setInputWidget(QWidget *widget); + void chooseRightTextInputWidget(); + void setComboBoxText(const QString &text); + void setListViewText(const QString &text); + QString listViewText() const; + void ensureLayout() const { const_cast(this)->ensureLayout(); } + bool useComboBoxOrListView() const { return comboBox && comboBox->count() > 0; } + void _q_textChanged(const QString &text); + void _q_currentRowChanged(const QModelIndex &newIndex, const QModelIndex &oldIndex); + + mutable QLabel *label; + mutable QDialogButtonBox *buttonBox; + mutable QLineEdit *lineEdit; + mutable QSpinBox *intSpinBox; + mutable QDoubleSpinBox *doubleSpinBox; + mutable QComboBox *comboBox; + mutable QListView *listView; + mutable QWidget *inputWidget; + mutable QVBoxLayout *mainLayout; + QInputDialog::InputDialogOptions opts; + QString textValue; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +QInputDialogPrivate::QInputDialogPrivate() + : label(0), buttonBox(0), lineEdit(0), intSpinBox(0), doubleSpinBox(0), comboBox(0), listView(0), + inputWidget(0), mainLayout(0) +{ +} + +void QInputDialogPrivate::ensureLayout() +{ + Q_Q(QInputDialog); + + if (mainLayout) + return; + + if (!inputWidget) { + ensureLineEdit(); + inputWidget = lineEdit; + } + + if (!label) + label = new QLabel(QInputDialog::tr("Enter a value:"), q); +#ifndef QT_NO_SHORTCUT + label->setBuddy(inputWidget); +#endif + label->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q); + QObject::connect(buttonBox, SIGNAL(accepted()), q, SLOT(accept())); + QObject::connect(buttonBox, SIGNAL(rejected()), q, SLOT(reject())); + + mainLayout = new QVBoxLayout(q); + //we want to let the input dialog grow to available size on Symbian. +#ifndef Q_OS_SYMBIAN + mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); +#else + label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +#endif + mainLayout->addWidget(label); + mainLayout->addWidget(inputWidget); + mainLayout->addWidget(buttonBox); + ensureEnabledConnection(qobject_cast(inputWidget)); + inputWidget->show(); +} + +void QInputDialogPrivate::ensureLineEdit() +{ + Q_Q(QInputDialog); + if (!lineEdit) { + lineEdit = new QLineEdit(q); +#ifndef QT_NO_IM + qt_widget_private(lineEdit)->inheritsInputMethodHints = 1; +#endif + lineEdit->hide(); + QObject::connect(lineEdit, SIGNAL(textChanged(QString)), + q, SLOT(_q_textChanged(QString))); + } +} + +void QInputDialogPrivate::ensureComboBox() +{ + Q_Q(QInputDialog); + if (!comboBox) { + comboBox = new QComboBox(q); +#ifndef QT_NO_IM + qt_widget_private(comboBox)->inheritsInputMethodHints = 1; +#endif + comboBox->hide(); + QObject::connect(comboBox, SIGNAL(editTextChanged(QString)), + q, SLOT(_q_textChanged(QString))); + QObject::connect(comboBox, SIGNAL(currentIndexChanged(QString)), + q, SLOT(_q_textChanged(QString))); + } +} + +void QInputDialogPrivate::ensureListView() +{ + Q_Q(QInputDialog); + if (!listView) { + ensureComboBox(); + + listView = new QListView(q); + listView->hide(); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + listView->setSelectionMode(QAbstractItemView::SingleSelection); + listView->setModel(comboBox->model()); + listView->setCurrentIndex(QModelIndex()); // ### + QObject::connect(listView->selectionModel(), + SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_currentRowChanged(QModelIndex,QModelIndex))); + } +} + +void QInputDialogPrivate::ensureIntSpinBox() +{ + Q_Q(QInputDialog); + if (!intSpinBox) { + intSpinBox = new QInputDialogSpinBox(q); + intSpinBox->hide(); + QObject::connect(intSpinBox, SIGNAL(valueChanged(int)), + q, SIGNAL(intValueChanged(int))); + } +} + +void QInputDialogPrivate::ensureDoubleSpinBox() +{ + Q_Q(QInputDialog); + if (!doubleSpinBox) { + doubleSpinBox = new QInputDialogDoubleSpinBox(q); + doubleSpinBox->hide(); + QObject::connect(doubleSpinBox, SIGNAL(valueChanged(double)), + q, SIGNAL(doubleValueChanged(double))); + } +} + +void QInputDialogPrivate::ensureEnabledConnection(QAbstractSpinBox *spinBox) +{ + if (spinBox) { + QAbstractButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + QObject::connect(spinBox, SIGNAL(textChanged(bool)), okButton, SLOT(setEnabled(bool)), Qt::UniqueConnection); + } +} + +void QInputDialogPrivate::setInputWidget(QWidget *widget) +{ + Q_ASSERT(widget); + if (inputWidget == widget) + return; + + if (mainLayout) { + Q_ASSERT(inputWidget); + mainLayout->removeWidget(inputWidget); + inputWidget->hide(); + mainLayout->insertWidget(1, widget); + widget->show(); + + // disconnect old input widget + QAbstractButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + if (QAbstractSpinBox *spinBox = qobject_cast(inputWidget)) + QObject::disconnect(spinBox, SIGNAL(textChanged(bool)), okButton, SLOT(setEnabled(bool))); + + // connect new input widget and update enabled state of OK button + QAbstractSpinBox *spinBox = qobject_cast(widget); + ensureEnabledConnection(spinBox); + okButton->setEnabled(!spinBox || spinBox->hasAcceptableInput()); + } + + inputWidget = widget; + + // synchronize the text shown in the new text editor with the current + // textValue + if (widget == lineEdit) { + lineEdit->setText(textValue); + } else if (widget == comboBox) { + setComboBoxText(textValue); + } else if (widget == listView) { + setListViewText(textValue); + ensureLayout(); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(listView->selectionModel()->hasSelection()); + } +} + +void QInputDialogPrivate::chooseRightTextInputWidget() +{ + QWidget *widget; + + if (useComboBoxOrListView()) { + if ((opts & QInputDialog::UseListViewForComboBoxItems) && !comboBox->isEditable()) { + ensureListView(); + widget = listView; + } else { + widget = comboBox; + } + } else { + ensureLineEdit(); + widget = lineEdit; + } + + setInputWidget(widget); + + if (inputWidget == comboBox) { + _q_textChanged(comboBox->currentText()); + } else if (inputWidget == listView) { + _q_textChanged(listViewText()); + } +} + +void QInputDialogPrivate::setComboBoxText(const QString &text) +{ + int index = comboBox->findText(text); + if (index != -1) { + comboBox->setCurrentIndex(index); + } else if (comboBox->isEditable()) { + comboBox->setEditText(text); + } +} + +void QInputDialogPrivate::setListViewText(const QString &text) +{ + int row = comboBox->findText(text); + if (row != -1) { + QModelIndex index(comboBox->model()->index(row, 0)); + listView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Clear + | QItemSelectionModel::SelectCurrent); + } +} + +QString QInputDialogPrivate::listViewText() const +{ + if (listView->selectionModel()->hasSelection()) { + int row = listView->selectionModel()->selectedRows().value(0).row(); + return comboBox->itemText(row); + } else { + return QString(); + } +} + +void QInputDialogPrivate::_q_textChanged(const QString &text) +{ + Q_Q(QInputDialog); + if (textValue != text) { + textValue = text; + emit q->textValueChanged(text); + } +} + +void QInputDialogPrivate::_q_currentRowChanged(const QModelIndex &newIndex, + const QModelIndex & /* oldIndex */) +{ + _q_textChanged(comboBox->model()->data(newIndex).toString()); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} + +/*! + \class QInputDialog + \brief The QInputDialog class provides a simple convenience dialog to get a + single value from the user. + \ingroup standard-dialogs + + + The input value can be a string, a number or an item from a list. A label + must be set to tell the user what they should enter. + + Four static convenience functions are provided: getText(), getInt(), + getDouble(), and getItem(). All the functions can be used in a similar way, + for example: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 3 + + The \c ok variable is set to true if the user clicks \gui OK; otherwise it + is set to false. + + \img inputdialogs.png Input Dialogs + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows how to use + QInputDialog as well as other built-in Qt dialogs. + + \sa QMessageBox, {Standard Dialogs Example} +*/ + +/*! + \enum QInputDialog::InputMode + \since 4.5 + + This enum describes the different modes of input that can be selected for + the dialog. + + \value TextInput Used to input text strings. + \value IntInput Used to input integers. + \value DoubleInput Used to input floating point numbers with double + precision accuracy. + + \sa inputMode +*/ + +/*! + \since 4.5 + + Constructs a new input dialog with the given \a parent and window \a flags. +*/ +QInputDialog::QInputDialog(QWidget *parent, Qt::WindowFlags flags) + : QDialog(*new QInputDialogPrivate, parent, flags) +{ +} + +/*! + \since 4.5 + + Destroys the input dialog. +*/ +QInputDialog::~QInputDialog() +{ +} + +/*! + \since 4.5 + + \property QInputDialog::inputMode + + \brief the mode used for input + + This property help determines which widget is used for entering input into + the dialog. +*/ +void QInputDialog::setInputMode(InputMode mode) +{ + Q_D(QInputDialog); + + QWidget *widget; + + /* + Warning: Some functions in QInputDialog rely on implementation details + of the code below. Look for the comments that accompany the calls to + setInputMode() throughout this file before you change the code below. + */ + + switch (mode) { + case IntInput: + d->ensureIntSpinBox(); + widget = d->intSpinBox; + break; + case DoubleInput: + d->ensureDoubleSpinBox(); + widget = d->doubleSpinBox; + break; + default: + Q_ASSERT(mode == TextInput); + d->chooseRightTextInputWidget(); + return; + } + + d->setInputWidget(widget); +} + +QInputDialog::InputMode QInputDialog::inputMode() const +{ + Q_D(const QInputDialog); + + if (d->inputWidget) { + if (d->inputWidget == d->intSpinBox) { + return IntInput; + } else if (d->inputWidget == d->doubleSpinBox) { + return DoubleInput; + } + } + + return TextInput; +} + +/*! + \since 4.5 + + \property QInputDialog::labelText + + \brief the text to for the label to describe what needs to be input +*/ +void QInputDialog::setLabelText(const QString &text) +{ + Q_D(QInputDialog); + if (!d->label) { + d->label = new QLabel(text, this); + } else { + d->label->setText(text); + } +#ifdef Q_OS_SYMBIAN + d->label->setWordWrap(true); +#endif +} + +QString QInputDialog::labelText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->label->text(); +} + +/*! + \enum QInputDialog::InputDialogOption + + \since 4.5 + + This enum specifies various options that affect the look and feel + of an input dialog. + + \value NoButtons Don't display \gui{OK} and \gui{Cancel} buttons. (Useful for "live dialogs".) + \value UseListViewForComboBoxItems Use a QListView rather than a non-editable QComboBox for + displaying the items set with setComboBoxItems(). + + \sa options, setOption(), testOption() +*/ + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QInputDialog::setOption(InputDialogOption option, bool on) +{ + Q_D(QInputDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QInputDialog::testOption(InputDialogOption option) const +{ + Q_D(const QInputDialog); + return (d->opts & option) != 0; +} + +/*! + \property QInputDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + \sa setOption(), testOption() +*/ +void QInputDialog::setOptions(InputDialogOptions options) +{ + Q_D(QInputDialog); + + InputDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; + d->ensureLayout(); + + if (changed & NoButtons) + d->buttonBox->setVisible(!(options & NoButtons)); + if ((changed & UseListViewForComboBoxItems) && inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +QInputDialog::InputDialogOptions QInputDialog::options() const +{ + Q_D(const QInputDialog); + return d->opts; +} + +/*! + \since 4.5 + + \property QInputDialog::textValue + + \brief the text value for the input dialog + + This property is only relevant when the input dialog is used in + TextInput mode. +*/ +void QInputDialog::setTextValue(const QString &text) +{ + Q_D(QInputDialog); + + setInputMode(TextInput); + if (d->inputWidget == d->lineEdit) { + d->lineEdit->setText(text); + } else if (d->inputWidget == d->comboBox) { + d->setComboBoxText(text); + } else { + d->setListViewText(text); + } +} + +QString QInputDialog::textValue() const +{ + Q_D(const QInputDialog); + return d->textValue; +} + +/*! + \since 4.5 + + \property QInputDialog::textEchoMode + + \brief the echo mode for the text value + + This property is only relevant when the input dialog is used in + TextInput mode. +*/ +void QInputDialog::setTextEchoMode(QLineEdit::EchoMode mode) +{ + Q_D(QInputDialog); + d->ensureLineEdit(); + d->lineEdit->setEchoMode(mode); +} + +QLineEdit::EchoMode QInputDialog::textEchoMode() const +{ + Q_D(const QInputDialog); + if (d->lineEdit) { + return d->lineEdit->echoMode(); + } else { + return QLineEdit::Normal; + } +} + +/*! + \since 4.5 + + \property QInputDialog::comboBoxEditable + + \brief whether or not the combo box is used in the input dialog is editable +*/ +void QInputDialog::setComboBoxEditable(bool editable) +{ + Q_D(QInputDialog); + d->ensureComboBox(); + d->comboBox->setEditable(editable); + if (inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +bool QInputDialog::isComboBoxEditable() const +{ + Q_D(const QInputDialog); + if (d->comboBox) { + return d->comboBox->isEditable(); + } else { + return false; + } +} + +/*! + \since 4.5 + + \property QInputDialog::comboBoxItems + + \brief the items used in the combobox for the input dialog +*/ +void QInputDialog::setComboBoxItems(const QStringList &items) +{ + Q_D(QInputDialog); + + d->ensureComboBox(); + d->comboBox->blockSignals(true); + d->comboBox->clear(); + d->comboBox->addItems(items); + d->comboBox->blockSignals(false); + + if (inputMode() == TextInput) + d->chooseRightTextInputWidget(); +} + +QStringList QInputDialog::comboBoxItems() const +{ + Q_D(const QInputDialog); + QStringList result; + if (d->comboBox) { + const int count = d->comboBox->count(); + for (int i = 0; i < count; ++i) + result.append(d->comboBox->itemText(i)); + } + return result; +} + +/*! + \property QInputDialog::intValue + \since 4.5 + \brief the current integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntValue(int value) +{ + Q_D(QInputDialog); + setInputMode(IntInput); + d->intSpinBox->setValue(value); +} + +int QInputDialog::intValue() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->value(); + } else { + return 0; + } +} + +/*! + \property QInputDialog::intMinimum + \since 4.5 + \brief the minimum integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntMinimum(int min) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setMinimum(min); +} + +int QInputDialog::intMinimum() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->minimum(); + } else { + return 0; + } +} + +/*! + \property QInputDialog::intMaximum + \since 4.5 + \brief the maximum integer value accepted as input + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntMaximum(int max) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setMaximum(max); +} + +int QInputDialog::intMaximum() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->maximum(); + } else { + return 99; + } +} + +/*! + Sets the range of integer values accepted by the dialog when used in + IntInput mode, with minimum and maximum values specified by \a min and + \a max respectively. +*/ +void QInputDialog::setIntRange(int min, int max) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setRange(min, max); +} + +/*! + \property QInputDialog::intStep + \since 4.5 + \brief the step by which the integer value is increased and decreased + + This property is only relevant when the input dialog is used in + IntInput mode. +*/ +void QInputDialog::setIntStep(int step) +{ + Q_D(QInputDialog); + d->ensureIntSpinBox(); + d->intSpinBox->setSingleStep(step); +} + +int QInputDialog::intStep() const +{ + Q_D(const QInputDialog); + if (d->intSpinBox) { + return d->intSpinBox->singleStep(); + } else { + return 1; + } +} + +/*! + \property QInputDialog::doubleValue + \since 4.5 + \brief the current double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleValue(double value) +{ + Q_D(QInputDialog); + setInputMode(DoubleInput); + d->doubleSpinBox->setValue(value); +} + +double QInputDialog::doubleValue() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->value(); + } else { + return 0.0; + } +} + +/*! + \property QInputDialog::doubleMinimum + \since 4.5 + \brief the minimum double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleMinimum(double min) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setMinimum(min); +} + +double QInputDialog::doubleMinimum() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->minimum(); + } else { + return 0.0; + } +} + +/*! + \property QInputDialog::doubleMaximum + \since 4.5 + \brief the maximum double precision floating point value accepted as input + + This property is only relevant when the input dialog is used in + DoubleInput mode. +*/ +void QInputDialog::setDoubleMaximum(double max) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setMaximum(max); +} + +double QInputDialog::doubleMaximum() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->maximum(); + } else { + return 99.99; + } +} + +/*! + Sets the range of double precision floating point values accepted by the + dialog when used in DoubleInput mode, with minimum and maximum values + specified by \a min and \a max respectively. +*/ +void QInputDialog::setDoubleRange(double min, double max) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setRange(min, max); +} + +/*! + \since 4.5 + + \property QInputDialog::doubleDecimals + + \brief sets the percision of the double spinbox in decimals + + \sa QDoubleSpinBox::setDecimals() +*/ +void QInputDialog::setDoubleDecimals(int decimals) +{ + Q_D(QInputDialog); + d->ensureDoubleSpinBox(); + d->doubleSpinBox->setDecimals(decimals); +} + +int QInputDialog::doubleDecimals() const +{ + Q_D(const QInputDialog); + if (d->doubleSpinBox) { + return d->doubleSpinBox->decimals(); + } else { + return 2; + } +} + +/*! + \since 4.5 + + \property QInputDialog::okButtonText + + \brief the text for the button used to accept the entry in the dialog +*/ +void QInputDialog::setOkButtonText(const QString &text) +{ + Q_D(const QInputDialog); + d->ensureLayout(); + d->buttonBox->button(QDialogButtonBox::Ok)->setText(text); +} + +QString QInputDialog::okButtonText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->buttonBox->button(QDialogButtonBox::Ok)->text(); +} + +/*! + \since 4.5 + + \property QInputDialog::cancelButtonText + \brief the text for the button used to cancel the dialog +*/ +void QInputDialog::setCancelButtonText(const QString &text) +{ + Q_D(const QInputDialog); + d->ensureLayout(); + d->buttonBox->button(QDialogButtonBox::Cancel)->setText(text); +} + +QString QInputDialog::cancelButtonText() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return d->buttonBox->button(QDialogButtonBox::Cancel)->text(); +} + +/*! + \since 4.5 + \overload + + This function connects one of its signals to the slot specified by \a receiver + and \a member. The specific signal depends on the arguments that are specified + in \a member. These are: + + \list + \o textValueSelected() if \a member has a QString for its first argument. + \o intValueSelected() if \a member has an int for its first argument. + \o doubleValueSelected() if \a member has a double for its first argument. + \o accepted() if \a member has NO arguments. + \endlist + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QInputDialog::open(QObject *receiver, const char *member) +{ + Q_D(QInputDialog); + connect(this, signalForMember(member), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \reimp +*/ +QSize QInputDialog::minimumSizeHint() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return QDialog::minimumSizeHint(); +} + +/*! + \reimp +*/ +QSize QInputDialog::sizeHint() const +{ + Q_D(const QInputDialog); + d->ensureLayout(); + return QDialog::sizeHint(); +} + +/*! + \reimp +*/ +void QInputDialog::setVisible(bool visible) +{ + Q_D(const QInputDialog); + if (visible) { + d->ensureLayout(); + d->inputWidget->setFocus(); + if (d->inputWidget == d->lineEdit) { + d->lineEdit->selectAll(); + } else if (d->inputWidget == d->intSpinBox) { + d->intSpinBox->selectAll(); + } else if (d->inputWidget == d->doubleSpinBox) { + d->doubleSpinBox->selectAll(); + } + } + QDialog::setVisible(visible); +} + +/*! + Closes the dialog and sets its result code to \a result. If this dialog + is shown with exec(), done() causes the local event loop to finish, + and exec() to return \a result. + + \sa QDialog::done() +*/ +void QInputDialog::done(int result) +{ + Q_D(QInputDialog); + QDialog::done(result); + if (result) { + InputMode mode = inputMode(); + switch (mode) { + case DoubleInput: + emit doubleValueSelected(doubleValue()); + break; + case IntInput: + emit intValueSelected(intValue()); + break; + default: + Q_ASSERT(mode == TextInput); + emit textValueSelected(textValue()); + } + } + if (d->receiverToDisconnectOnClose) { + disconnect(this, signalForMember(d->memberToDisconnectOnClose), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +/*! + Static convenience function to get a string from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a text is the default text which is placed in the line edit. + \a mode is the echo mode the line edit will use. + \a inputMethodHints is the input method hints that will be used in the + edit widget if an input method is active. + + If \a ok is nonnull \e *\a ok will be set to true if the user pressed + \gui OK and to false if the user pressed \gui Cancel. The dialog's parent + is \a parent. The dialog will be modal and uses the specified widget + \a flags. + + If the dialog is accepted, this function returns the text in the dialog's + line edit. If the dialog is rejected, a null QString is returned. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 3 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getInt(), getDouble(), getItem() +*/ + +QString QInputDialog::getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode mode, const QString &text, bool *ok, + Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setTextValue(text); + dialog.setTextEchoMode(mode); + dialog.setInputMethodHints(inputMethodHints); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.textValue(); + } else { + return QString(); + } +} + +/*! + \internal +*/ +// ### Qt 5: Use only the version above. +QString QInputDialog::getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode mode, const QString &text, bool *ok, + Qt::WindowFlags flags) +{ + return getText(parent, title, label, mode, text, ok, flags, Qt::ImhNone); +} + +/*! + \since 4.5 + + Static convenience function to get an integer input from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a value is the default integer which the spinbox will be set to. + \a min and \a max are the minimum and maximum values the user may choose. + \a step is the amount by which the values change as the user presses the + arrow buttons to increment or decrement the value. + + If \a ok is nonnull *\a ok will be set to true if the user pressed \gui OK + and to false if the user pressed \gui Cancel. The dialog's parent is + \a parent. The dialog will be modal and uses the widget \a flags. + + On success, this function returns the integer which has been entered by the + user; on failure, it returns the initial \a value. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 0 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getDouble(), getItem() +*/ + +int QInputDialog::getInt(QWidget *parent, const QString &title, const QString &label, int value, + int min, int max, int step, bool *ok, Qt::WindowFlags flags) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setIntRange(min, max); + dialog.setIntValue(value); + dialog.setIntStep(step); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.intValue(); + } else { + return value; + } +} + +/*! + Static convenience function to get a floating point number from the user. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a value is the default floating point number that the line edit will be + set to. + \a min and \a max are the minimum and maximum values the user may choose. + \a decimals is the maximum number of decimal places the number may have. + + If \a ok is nonnull, *\a ok will be set to true if the user pressed \gui OK + and to false if the user pressed \gui Cancel. The dialog's parent is + \a parent. The dialog will be modal and uses the widget \a flags. + + This function returns the floating point number which has been entered by + the user. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 1 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getInt(), getItem() +*/ + +double QInputDialog::getDouble(QWidget *parent, const QString &title, const QString &label, + double value, double min, double max, int decimals, bool *ok, + Qt::WindowFlags flags) +{ + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setDoubleDecimals(decimals); + dialog.setDoubleRange(min, max); + dialog.setDoubleValue(value); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.doubleValue(); + } else { + return value; + } +} + +/*! + Static convenience function to let the user select an item from a string + list. + + \a title is the text which is displayed in the title bar of the dialog. + \a label is the text which is shown to the user (it should say what should + be entered). + \a items is the string list which is inserted into the combobox. + \a current is the number of the item which should be the current item. + \a inputMethodHints is the input method hints that will be used if the + combobox is editable and an input method is active. + + If \a editable is true the user can enter their own text; otherwise the + user may only select one of the existing items. + + If \a ok is nonnull \e *\a ok will be set to true if the user pressed + \gui OK and to false if the user pressed \gui Cancel. The dialog's parent + is \a parent. The dialog will be modal and uses the widget \a flags. + + This function returns the text of the current item, or if \a editable is + true, the current text of the combobox. + + Use this static function like this: + + \snippet examples/dialogs/standarddialogs/dialog.cpp 2 + + \warning Do not delete \a parent during the execution of the dialog. If you + want to do this, you should create the dialog yourself using one of the + QInputDialog constructors. + + \sa getText(), getInt(), getDouble() +*/ + +QString QInputDialog::getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, bool *ok, + Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints) +{ + QString text(items.value(current)); + + QInputDialog dialog(parent, flags); + dialog.setWindowTitle(title); + dialog.setLabelText(label); + dialog.setComboBoxItems(items); + dialog.setTextValue(text); + dialog.setComboBoxEditable(editable); + dialog.setInputMethodHints(inputMethodHints); + + int ret = dialog.exec(); + if (ok) + *ok = !!ret; + if (ret) { + return dialog.textValue(); + } else { + return text; + } +} + +/*! + \internal +*/ +// ### Qt 5: Use only the version above. +QString QInputDialog::getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, bool *ok, + Qt::WindowFlags flags) +{ + return getItem(parent, title, label, items, current, editable, ok, flags, Qt::ImhNone); +} + +/*! + \obsolete + + Use getInt() instead. +*/ +int QInputDialog::getInteger(QWidget *parent, const QString &title, const QString &label, + int value, int min, int max, int step, bool *ok, + Qt::WindowFlags flags) +{ + return getInt(parent, title, label, value, min, max, step, ok, flags); +} + +/*! + \fn QString QInputDialog::getText(const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getText(\a parent, \a title, \a label, \a echo, \a text, \a + ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn int QInputDialog::getInteger(const QString &title, const QString &label, int value = 0, + int min = -2147483647, int max = 2147483647, + int step = 1, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + + Call getInteger(\a parent, \a title, \a label, \a value, \a + min, \a max, \a step, \a ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn double QInputDialog::getDouble(const QString &title, const QString &label, double value = 0, + double min = -2147483647, double max = 2147483647, + int decimals = 1, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getDouble(\a parent, \a title, \a label, \a value, \a + min, \a max, \a decimals, \a ok, \a flags). + + The \a name parameter is ignored. +*/ + +/*! + \fn QString QInputDialog::getItem(const QString &title, const QString &label, const QStringList &list, + int current = 0, bool editable = true, bool *ok = 0, + QWidget *parent = 0, const char *name = 0, Qt::WindowFlags flags = 0) + + Call getItem(\a parent, \a title, \a label, \a list, \a current, + \a editable, \a ok, \a flags) instead. + + The \a name parameter is ignored. +*/ + +/*! + \fn void QInputDialog::doubleValueChanged(double value) + + This signal is emitted whenever the double value changes in the dialog. + The current value is specified by \a value. + + This signal is only relevant when the input dialog is used in + DoubleInput mode. +*/ + +/*! + \fn void QInputDialog::doubleValueSelected(double value) + + This signal is emitted whenever the user selects a double value by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected value is specified by \a value. + + This signal is only relevant when the input dialog is used in + DoubleInput mode. +*/ + +/*! + \fn void QInputDialog::intValueChanged(int value) + + This signal is emitted whenever the integer value changes in the dialog. + The current value is specified by \a value. + + This signal is only relevant when the input dialog is used in + IntInput mode. +*/ + +/*! + \fn void QInputDialog::intValueSelected(int value) + + This signal is emitted whenever the user selects a integer value by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected value is specified by \a value. + + This signal is only relevant when the input dialog is used in + IntInput mode. +*/ + +/*! + \fn void QInputDialog::textValueChanged(const QString &text) + + This signal is emitted whenever the text string changes in the dialog. + The current string is specified by \a text. + + This signal is only relevant when the input dialog is used in + TextInput mode. +*/ + +/*! + \fn void QInputDialog::textValueSelected(const QString &text) + + This signal is emitted whenever the user selects a text string by + accepting the dialog; for example, by clicking the \gui{OK} button. + The selected string is specified by \a text. + + This signal is only relevant when the input dialog is used in + TextInput mode. +*/ + +QT_END_NAMESPACE + +#include "moc_qinputdialog.cpp" + +#endif // QT_NO_INPUTDIALOG diff --git a/src/gui/dialogs/qinputdialog.h b/src/gui/dialogs/qinputdialog.h new file mode 100644 index 0000000000..b0e6fbb216 --- /dev/null +++ b/src/gui/dialogs/qinputdialog.h @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QINPUTDIALOG_H +#define QINPUTDIALOG_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_INPUTDIALOG + +class QInputDialogPrivate; + +class Q_GUI_EXPORT QInputDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QInputDialog) +// Q_ENUMS(InputMode InputDialogOption) + QDOC_PROPERTY(InputMode inputMode READ inputMode WRITE setInputMode) + QDOC_PROPERTY(QString labelText READ labelText WRITE setLabelText) + QDOC_PROPERTY(InputDialogOptions options READ options WRITE setOptions) + QDOC_PROPERTY(QString textValue READ textValue WRITE setTextValue NOTIFY textValueChanged) + QDOC_PROPERTY(int intValue READ intValue WRITE setIntValue NOTIFY intValueChanged) + QDOC_PROPERTY(int doubleValue READ doubleValue WRITE setDoubleValue NOTIFY doubleValueChanged) + QDOC_PROPERTY(QLineEdit::EchoMode textEchoMode READ textEchoMode WRITE setTextEchoMode) + QDOC_PROPERTY(bool comboBoxEditable READ isComboBoxEditable WRITE setComboBoxEditable) + QDOC_PROPERTY(QStringList comboBoxItems READ comboBoxItems WRITE setComboBoxItems) + QDOC_PROPERTY(int intMinimum READ intMinimum WRITE setIntMinimum) + QDOC_PROPERTY(int intMaximum READ intMaximum WRITE setIntMaximum) + QDOC_PROPERTY(int intStep READ intStep WRITE setIntStep) + QDOC_PROPERTY(double doubleMinimum READ doubleMinimum WRITE setDoubleMinimum) + QDOC_PROPERTY(double doubleMaximum READ doubleMaximum WRITE setDoubleMaximum) + QDOC_PROPERTY(int doubleDecimals READ doubleDecimals WRITE setDoubleDecimals) + QDOC_PROPERTY(QString okButtonText READ okButtonText WRITE setOkButtonText) + QDOC_PROPERTY(QString cancelButtonText READ cancelButtonText WRITE setCancelButtonText) + +public: + enum InputDialogOption { + NoButtons = 0x00000001, + UseListViewForComboBoxItems = 0x00000002 + }; + + Q_DECLARE_FLAGS(InputDialogOptions, InputDialogOption) + + enum InputMode { + TextInput, + IntInput, + DoubleInput + }; + + QInputDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QInputDialog(); + + void setInputMode(InputMode mode); + InputMode inputMode() const; + + void setLabelText(const QString &text); + QString labelText() const; + + void setOption(InputDialogOption option, bool on = true); + bool testOption(InputDialogOption option) const; + void setOptions(InputDialogOptions options); + InputDialogOptions options() const; + + void setTextValue(const QString &text); + QString textValue() const; + + void setTextEchoMode(QLineEdit::EchoMode mode); + QLineEdit::EchoMode textEchoMode() const; + + void setComboBoxEditable(bool editable); + bool isComboBoxEditable() const; + + void setComboBoxItems(const QStringList &items); + QStringList comboBoxItems() const; + + void setIntValue(int value); + int intValue() const; + + void setIntMinimum(int min); + int intMinimum() const; + + void setIntMaximum(int max); + int intMaximum() const; + + void setIntRange(int min, int max); + + void setIntStep(int step); + int intStep() const; + + void setDoubleValue(double value); + double doubleValue() const; + + void setDoubleMinimum(double min); + double doubleMinimum() const; + + void setDoubleMaximum(double max); + double doubleMaximum() const; + + void setDoubleRange(double min, double max); + + void setDoubleDecimals(int decimals); + int doubleDecimals() const; + + void setOkButtonText(const QString &text); + QString okButtonText() const; + + void setCancelButtonText(const QString &text); + QString cancelButtonText() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + QSize minimumSizeHint() const; + QSize sizeHint() const; + + void setVisible(bool visible); + +#ifdef Q_QDOC + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, Qt::WindowFlags flags = 0, + Qt::InputMethodHints inputMethodHints = Qt::ImhNone); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current = 0, bool editable = true, + bool *ok = 0, Qt::WindowFlags flags = 0, + Qt::InputMethodHints inputMethodHints = Qt::ImhNone); +#else + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, Qt::WindowFlags flags = 0); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current = 0, bool editable = true, + bool *ok = 0, Qt::WindowFlags flags = 0); + static QString getText(QWidget *parent, const QString &title, const QString &label, + QLineEdit::EchoMode echo, + const QString &text, bool *ok, Qt::WindowFlags flags, + Qt::InputMethodHints inputMethodHints); + static QString getItem(QWidget *parent, const QString &title, const QString &label, + const QStringList &items, int current, bool editable, + bool *ok, Qt::WindowFlags flags, + Qt::InputMethodHints inputMethodHints); +#endif + static int getInt(QWidget *parent, const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + static double getDouble(QWidget *parent, const QString &title, const QString &label, double value = 0, + double minValue = -2147483647, double maxValue = 2147483647, + int decimals = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + + // obsolete + static int getInteger(QWidget *parent, const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, Qt::WindowFlags flags = 0); + +#ifdef QT3_SUPPORT + inline static QT3_SUPPORT QString getText(const QString &title, const QString &label, + QLineEdit::EchoMode echo = QLineEdit::Normal, + const QString &text = QString(), bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getText(parent, title, label, echo, text, ok, flags); } + inline static QT3_SUPPORT int getInteger(const QString &title, const QString &label, int value = 0, + int minValue = -2147483647, int maxValue = 2147483647, + int step = 1, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getInteger(parent, title, label, value, minValue, maxValue, step, ok, flags); } + inline static QT3_SUPPORT double getDouble(const QString &title, const QString &label, double value = 0, + double minValue = -2147483647, double maxValue = 2147483647, + int decimals = 1, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getDouble(parent, title, label, value, minValue, maxValue, decimals, ok, flags); } + inline static QT3_SUPPORT QString getItem(const QString &title, const QString &label, const QStringList &list, + int current = 0, bool editable = true, bool *ok = 0, + QWidget *parent = 0, const char * = 0, Qt::WindowFlags flags = 0) + { return getItem(parent, title, label, list, current, editable, ok, flags); } +#endif + +Q_SIGNALS: + // ### emit signals! + void textValueChanged(const QString &text); + void textValueSelected(const QString &text); + void intValueChanged(int value); + void intValueSelected(int value); + void doubleValueChanged(double value); + void doubleValueSelected(double value); + + +public: + void done(int result); // ### Qt 5: Make protected. + +private: + Q_DISABLE_COPY(QInputDialog) + Q_PRIVATE_SLOT(d_func(), void _q_textChanged(const QString&)) + Q_PRIVATE_SLOT(d_func(), void _q_currentRowChanged(const QModelIndex&, const QModelIndex&)) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QInputDialog::InputDialogOptions) + +#endif // QT_NO_INPUTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QINPUTDIALOG_H diff --git a/src/gui/dialogs/qmessagebox.cpp b/src/gui/dialogs/qmessagebox.cpp new file mode 100644 index 0000000000..d6dbcf6e78 --- /dev/null +++ b/src/gui/dialogs/qmessagebox.cpp @@ -0,0 +1,2751 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QT_NO_MESSAGEBOX + +#include +#include "private/qlabel_p.h" +#include "private/qapplication_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qdialog_p.h" +#include +#include +#include + +#ifndef QT_NO_STYLE_S60 +#include +#endif + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_smartphone();//defined in qguifunctions_wince.cpp +extern bool qt_wince_is_pocket_pc(); //defined in qguifunctions_wince.cpp + +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +enum Button { Old_Ok = 1, Old_Cancel = 2, Old_Yes = 3, Old_No = 4, Old_Abort = 5, Old_Retry = 6, + Old_Ignore = 7, Old_YesAll = 8, Old_NoAll = 9, Old_ButtonMask = 0xFF, + NewButtonMask = 0xFFFFFC00 }; + +enum DetailButtonLabel { ShowLabel = 0, HideLabel = 1 }; +#ifndef QT_NO_TEXTEDIT +class QMessageBoxDetailsText : public QWidget +{ +public: + class TextEdit : public QTextEdit + { + public: + TextEdit(QWidget *parent=0) : QTextEdit(parent) { } + void contextMenuEvent(QContextMenuEvent * e) + { +#ifndef QT_NO_CONTEXTMENU + QMenu *menu = createStandardContextMenu(); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(e->globalPos()); +#else + Q_UNUSED(e); +#endif + } + }; + + QMessageBoxDetailsText(QWidget *parent=0) + : QWidget(parent) + { + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(0); + QFrame *line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + layout->addWidget(line); + textEdit = new TextEdit(); + textEdit->setFixedHeight(100); + textEdit->setFocusPolicy(Qt::NoFocus); + textEdit->setReadOnly(true); + layout->addWidget(textEdit); + setLayout(layout); + } + void setText(const QString &text) { textEdit->setPlainText(text); } + QString text() const { return textEdit->toPlainText(); } +private: + TextEdit *textEdit; +}; +#endif // QT_NO_TEXTEDIT + +class DetailButton : public QPushButton +{ +public: + DetailButton(QWidget *parent) : QPushButton(label(ShowLabel), parent) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + QString label(DetailButtonLabel label) const + { return label == ShowLabel ? QMessageBox::tr("Show Details...") : QMessageBox::tr("Hide Details..."); } + + void setLabel(DetailButtonLabel lbl) + { setText(label(lbl)); } + + QSize sizeHint() const + { + ensurePolished(); + QStyleOptionButton opt; + initStyleOption(&opt); + const QFontMetrics fm = fontMetrics(); + opt.text = label(ShowLabel); + QSize sz = fm.size(Qt::TextShowMnemonic, opt.text); + QSize ret = style()->sizeFromContents(QStyle::CT_PushButton, &opt, sz, this). + expandedTo(QApplication::globalStrut()); + opt.text = label(HideLabel); + sz = fm.size(Qt::TextShowMnemonic, opt.text); + ret.expandedTo(style()->sizeFromContents(QStyle::CT_PushButton, &opt, sz, this). + expandedTo(QApplication::globalStrut())); + return ret; + } +}; + + +class QMessageBoxPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QMessageBox) + +public: + QMessageBoxPrivate() : escapeButton(0), defaultButton(0), clickedButton(0), detailsButton(0), +#ifndef QT_NO_TEXTEDIT + detailsText(0), +#endif + compatMode(false), autoAddOkButton(true), + detectedEscapeButton(0), informativeLabel(0) { } + + void init(const QString &title = QString(), const QString &text = QString()); + void _q_buttonClicked(QAbstractButton *); + + QAbstractButton *findButton(int button0, int button1, int button2, int flags); + void addOldButtons(int button0, int button1, int button2); + + QAbstractButton *abstractButtonForId(int id) const; + int execReturnCode(QAbstractButton *button); + + void detectEscapeButton(); + void updateSize(); + int layoutMinimumWidth(); + void retranslateStrings(); + +#ifdef Q_WS_WINCE + void hideSpecial(); +#endif + + static int showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + int button0, int button1, int button2); + static int showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + const QString &button0Text, + const QString &button1Text, + const QString &button2Text, + int defaultButtonNumber, + int escapeButtonNumber); + + static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, + QMessageBox::Icon icon, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); + + static QPixmap standardIcon(QMessageBox::Icon icon, QMessageBox *mb); + + QLabel *label; + QMessageBox::Icon icon; + QLabel *iconLabel; + QDialogButtonBox *buttonBox; + QList customButtonList; + QAbstractButton *escapeButton; + QPushButton *defaultButton; + QAbstractButton *clickedButton; + DetailButton *detailsButton; +#ifndef QT_NO_TEXTEDIT + QMessageBoxDetailsText *detailsText; +#endif + bool compatMode; + bool autoAddOkButton; + QAbstractButton *detectedEscapeButton; + QLabel *informativeLabel; +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + QTextBrowser *textBrowser; +#endif + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; + QByteArray signalToDisconnectOnClose; +}; + +void QMessageBoxPrivate::init(const QString &title, const QString &text) +{ + Q_Q(QMessageBox); + + label = new QLabel; + label->setObjectName(QLatin1String("qt_msgbox_label")); + label->setTextInteractionFlags(Qt::TextInteractionFlags(q->style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, q))); + label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + label->setOpenExternalLinks(true); +#if defined(Q_WS_MAC) + label->setContentsMargins(16, 0, 0, 0); +#elif !defined(Q_WS_QWS) + label->setContentsMargins(2, 0, 0, 0); + label->setIndent(9); +#endif + icon = QMessageBox::NoIcon; + iconLabel = new QLabel; + iconLabel->setObjectName(QLatin1String("qt_msgboxex_icon_label")); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + buttonBox = new QDialogButtonBox; + buttonBox->setObjectName(QLatin1String("qt_msgbox_buttonbox")); + buttonBox->setCenterButtons(q->style()->styleHint(QStyle::SH_MessageBox_CenterButtons, 0, q)); + QObject::connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), + q, SLOT(_q_buttonClicked(QAbstractButton*))); + + QGridLayout *grid = new QGridLayout; +#ifndef Q_WS_MAC + grid->addWidget(iconLabel, 0, 0, 2, 1, Qt::AlignTop); + grid->addWidget(label, 0, 1, 1, 1); + // -- leave space for information label -- + grid->addWidget(buttonBox, 2, 0, 1, 2); +#else + grid->setMargin(0); + grid->setVerticalSpacing(8); + grid->setHorizontalSpacing(0); + q->setContentsMargins(24, 15, 24, 20); + grid->addWidget(iconLabel, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignLeft); + grid->addWidget(label, 0, 1, 1, 1); + // -- leave space for information label -- + grid->setRowStretch(1, 100); + grid->setRowMinimumHeight(2, 6); + grid->addWidget(buttonBox, 3, 1, 1, 1); +#endif + + grid->setSizeConstraint(QLayout::SetNoConstraint); + q->setLayout(grid); + + if (!title.isEmpty() || !text.isEmpty()) { + q->setWindowTitle(title); + q->setText(text); + } + q->setModal(true); + +#ifdef Q_WS_MAC + QFont f = q->font(); + f.setBold(true); + label->setFont(f); +#endif + retranslateStrings(); +} + +int QMessageBoxPrivate::layoutMinimumWidth() +{ + layout->activate(); + return layout->totalMinimumSize().width(); +} + +void QMessageBoxPrivate::updateSize() +{ + Q_Q(QMessageBox); + + if (!q->isVisible()) + return; + + QSize screenSize = QApplication::desktop()->availableGeometry(QCursor::pos()).size(); +#if defined(Q_WS_QWS) || defined(Q_WS_WINCE) || defined(Q_OS_SYMBIAN) + // the width of the screen, less the window border. + int hardLimit = screenSize.width() - (q->frameGeometry().width() - q->geometry().width()); +#else + int hardLimit = qMin(screenSize.width() - 480, 1000); // can never get bigger than this + // on small screens allows the messagebox be the same size as the screen + if (screenSize.width() <= 1024) + hardLimit = screenSize.width(); +#endif +#ifdef Q_WS_MAC + int softLimit = qMin(screenSize.width()/2, 420); +#elif defined(Q_WS_QWS) + int softLimit = qMin(hardLimit, 500); +#else + // note: ideally on windows, hard and soft limits but it breaks compat +#ifndef Q_WS_WINCE + int softLimit = qMin(screenSize.width()/2, 500); +#else + int softLimit = qMin(screenSize.width() * 3 / 4, 500); +#endif //Q_WS_WINCE +#endif + + if (informativeLabel) + informativeLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + + label->setWordWrap(false); // makes the label return min size + int width = layoutMinimumWidth(); + + if (width > softLimit) { + label->setWordWrap(true); + width = qMax(softLimit, layoutMinimumWidth()); + + if (width > hardLimit) { + label->d_func()->ensureTextControl(); + if (QTextControl *control = label->d_func()->control) { + QTextOption opt = control->document()->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAnywhere); + control->document()->setDefaultTextOption(opt); + } + width = hardLimit; + } + } +#ifdef Q_WS_S60 + // in S60 portait messageBoxes should always occupy maximum width + if (QApplication::desktop()->size().height() > QApplication::desktop()->size().width()){ + width = hardLimit; + } else { + // in landscape the messageBoxes should be of same width as in portrait + width = qMin(QApplication::desktop()->size().height(), hardLimit); + } +#endif + + if (informativeLabel) { + label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + QSizePolicy policy(QSizePolicy::Minimum, QSizePolicy::Preferred); + policy.setHeightForWidth(true); + informativeLabel->setSizePolicy(policy); + width = qMax(width, layoutMinimumWidth()); + if (width > hardLimit) { // longest word is really big, so wrap anywhere + informativeLabel->d_func()->ensureTextControl(); + if (QTextControl *control = informativeLabel->d_func()->control) { + QTextOption opt = control->document()->defaultTextOption(); + opt.setWrapMode(QTextOption::WrapAnywhere); + control->document()->setDefaultTextOption(opt); + } + width = hardLimit; + } + policy.setHeightForWidth(label->wordWrap()); + label->setSizePolicy(policy); + } + + QFontMetrics fm(QApplication::font("QWorkspaceTitleBar")); + int windowTitleWidth = qMin(fm.width(q->windowTitle()) + 50, hardLimit); + if (windowTitleWidth > width) + width = windowTitleWidth; + + layout->activate(); + int height = (layout->hasHeightForWidth()) + ? layout->totalHeightForWidth(width) + : layout->totalMinimumSize().height(); + +#ifndef QT_NO_STYLE_S60 + QS60Style *s60Style = 0; + s60Style = qobject_cast(QApplication::style()); + + //use custom pixel metric to deduce the minimum height of the messagebox + if (s60Style) + height = qMax(height, s60Style->pixelMetric((QStyle::PixelMetric)PM_MessageBoxHeight)); +#endif + + q->setFixedSize(width, height); + QCoreApplication::removePostedEvents(q, QEvent::LayoutRequest); +} + + +#ifdef Q_WS_WINCE +/*! + \internal + Hides special buttons which are rather shown in the title bar + on WinCE, to conserve screen space. +*/ + +void QMessageBoxPrivate::hideSpecial() +{ + Q_Q(QMessageBox); + QList list = q->findChildren(); + for (int i=0; itext(); + text.remove(QChar::fromLatin1('&')); + if (text == QApplication::translate("QMessageBox", "OK" )) + pb->setFixedSize(0,0); + } +} +#endif + +static int oldButton(int button) +{ + switch (button & QMessageBox::ButtonMask) { + case QMessageBox::Ok: + return Old_Ok; + case QMessageBox::Cancel: + return Old_Cancel; + case QMessageBox::Yes: + return Old_Yes; + case QMessageBox::No: + return Old_No; + case QMessageBox::Abort: + return Old_Abort; + case QMessageBox::Retry: + return Old_Retry; + case QMessageBox::Ignore: + return Old_Ignore; + case QMessageBox::YesToAll: + return Old_YesAll; + case QMessageBox::NoToAll: + return Old_NoAll; + default: + return 0; + } +} + +int QMessageBoxPrivate::execReturnCode(QAbstractButton *button) +{ + int ret = buttonBox->standardButton(button); + if (ret == QMessageBox::NoButton) { + ret = customButtonList.indexOf(button); // if button == 0, correctly sets ret = -1 + } else if (compatMode) { + ret = oldButton(ret); + } + return ret; +} + +void QMessageBoxPrivate::_q_buttonClicked(QAbstractButton *button) +{ + Q_Q(QMessageBox); +#ifndef QT_NO_TEXTEDIT + if (detailsButton && detailsText && button == detailsButton) { + detailsButton->setLabel(detailsText->isHidden() ? HideLabel : ShowLabel); + detailsText->setHidden(!detailsText->isHidden()); + updateSize(); + } else +#endif + { + clickedButton = button; + q->done(execReturnCode(button)); // does not trigger closeEvent + emit q->buttonClicked(button); + + if (receiverToDisconnectOnClose) { + QObject::disconnect(q, signalToDisconnectOnClose, receiverToDisconnectOnClose, + memberToDisconnectOnClose); + receiverToDisconnectOnClose = 0; + } + signalToDisconnectOnClose.clear(); + memberToDisconnectOnClose.clear(); + } +} + +/*! + \class QMessageBox + + \brief The QMessageBox class provides a modal dialog for informing + the user or for asking the user a question and receiving an answer. + + \ingroup standard-dialogs + + + A message box displays a primary \l{QMessageBox::text}{text} to + alert the user to a situation, an \l{QMessageBox::informativeText} + {informative text} to further explain the alert or to ask the user + a question, and an optional \l{QMessageBox::detailedText} + {detailed text} to provide even more data if the user requests + it. A message box can also display an \l{QMessageBox::icon} {icon} + and \l{QMessageBox::standardButtons} {standard buttons} for + accepting a user response. + + Two APIs for using QMessageBox are provided, the property-based + API, and the static functions. Calling one of the static functions + is the simpler approach, but it is less flexible than using the + property-based API, and the result is less informative. Using the + property-based API is recommended. + + \section1 The Property-based API + + To use the property-based API, construct an instance of + QMessageBox, set the desired properties, and call exec() to show + the message. The simplest configuration is to set only the + \l{QMessageBox::text} {message text} property. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 5 + + The user must click the \gui{OK} button to dismiss the message + box. The rest of the GUI is blocked until the message box is + dismissed. + + \image msgbox1.png + + A better approach than just alerting the user to an event is to + also ask the user what to do about it. Store the question in the + \l{QMessageBox::informativeText} {informative text} property, and + set the \l{QMessageBox::standardButtons} {standard buttons} + property to the set of buttons you want as the set of user + responses. The buttons are specified by combining values from + StandardButtons using the bitwise OR operator. The display order + for the buttons is platform-dependent. For example, on Windows, + \gui{Save} is displayed to the left of \gui{Cancel}, whereas on + Mac OS, the order is reversed. + + Mark one of your standard buttons to be your + \l{QMessageBox::defaultButton()} {default button}. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 6 + + This is the approach recommended in the + \l{http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGWindows/chapter_18_section_7.html} + {Mac OS X Guidlines}. Similar guidlines apply for the other + platforms, but note the different ways the + \l{QMessageBox::informativeText} {informative text} is handled for + different platforms. + + \image msgbox2.png + + The exec() slot returns the StandardButtons value of the button + that was clicked. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 7 + + To give the user more information to help him answer the question, + set the \l{QMessageBox::detailedText} {detailed text} property. If + the \l{QMessageBox::detailedText} {detailed text} property is set, + the \gui{Show Details...} button will be shown. + + \image msgbox3.png + + Clicking the \gui{Show Details...} button displays the detailed text. + + \image msgbox4.png + + \section2 Rich Text and the Text Format Property + + The \l{QMessageBox::detailedText} {detailed text} property is + always interpreted as plain text. The \l{QMessageBox::text} {main + text} and \l{QMessageBox::informativeText} {informative text} + properties can be either plain text or rich text. These strings + are interpreted according to the setting of the + \l{QMessageBox::textFormat} {text format} property. The default + setting is \l{Qt::AutoText} {auto-text}. + + Note that for some plain text strings containing XML + meta-characters, the auto-text \l{Qt::mightBeRichText()} {rich + text detection test} may fail causing your plain text string to be + interpreted incorrectly as rich text. In these rare cases, use + Qt::convertFromPlainText() to convert your plain text string to a + visually equivalent rich text string, or set the + \l{QMessageBox::textFormat} {text format} property explicitly with + setTextFormat(). + + \section2 Severity Levels and the Icon and Pixmap Properties + + QMessageBox supports four predefined message severity levels, or message + types, which really only differ in the predefined icon they each show. + Specify one of the four predefined message types by setting the + \l{QMessageBox::icon}{icon} property to one of the + \l{QMessageBox::Icon}{predefined icons}. The following rules are + guidelines: + + \table + \row + \o \img qmessagebox-quest.png + \o \l Question + \o For asking a question during normal operations. + \row + \o \img qmessagebox-info.png + \o \l Information + \o For reporting information about normal operations. + \row + \o \img qmessagebox-warn.png + \o \l Warning + \o For reporting non-critical errors. + \row + \o \img qmessagebox-crit.png + \o \l Critical + \o For reporting critical errors. + \endtable + + \l{QMessageBox::Icon}{Predefined icons} are not defined by QMessageBox, but + provided by the style. The default value is \l{QMessageBox::NoIcon} + {No Icon}. The message boxes are otherwise the same for all cases. When + using a standard icon, use the one recommended in the table, or use the + one recommended by the style guidelines for your platform. If none of the + standard icons is right for your message box, you can use a custom icon by + setting the \l{QMessageBox::iconPixmap}{icon pixmap} property instead of + setting the \l{QMessageBox::icon}{icon} property. + + In summary, to set an icon, use \e{either} setIcon() for one of the + standard icons, \e{or} setIconPixmap() for a custom icon. + + \section1 The Static Functions API + + Building message boxes with the static functions API, although + convenient, is less flexible than using the property-based API, + because the static function signatures lack parameters for setting + the \l{QMessageBox::informativeText} {informative text} and + \l{QMessageBox::detailedText} {detailed text} properties. One + work-around for this has been to use the \c{title} parameter as + the message box main text and the \c{text} parameter as the + message box informative text. Because this has the obvious + drawback of making a less readable message box, platform + guidelines do not recommend it. The \e{Microsoft Windows User + Interface Guidelines} recommend using the + \l{QCoreApplication::applicationName} {application name} as the + \l{QMessageBox::setWindowTitle()} {window's title}, which means + that if you have an informative text in addition to your main + text, you must concatenate it to the \c{text} parameter. + + Note that the static function signatures have changed with respect + to their button parameters, which are now used to set the + \l{QMessageBox::standardButtons} {standard buttons} and the + \l{QMessageBox::defaultButton()} {default button}. + + Static functions are available for creating information(), + question(), warning(), and critical() message boxes. + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 0 + + The \l{dialogs/standarddialogs}{Standard Dialogs} example shows + how to use QMessageBox and the other built-in Qt dialogs. + + \section1 Advanced Usage + + If the \l{QMessageBox::StandardButtons} {standard buttons} are not + flexible enough for your message box, you can use the addButton() + overload that takes a text and a ButtonRoleto to add custom + buttons. The ButtonRole is used by QMessageBox to determine the + ordering of the buttons on screen (which varies according to the + platform). You can test the value of clickedButton() after calling + exec(). For example, + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 2 + + \section1 Default and Escape Keys + + The default button (i.e., the button activated when \key Enter is + pressed) can be specified using setDefaultButton(). If a default + button is not specified, QMessageBox tries to find one based on + the \l{ButtonRole} {button roles} of the buttons used in the + message box. + + The escape button (the button activated when \key Esc is pressed) + can be specified using setEscapeButton(). If an escape button is + not specified, QMessageBox tries to find one using these rules: + + \list 1 + + \o If there is only one button, it is the button activated when + \key Esc is pressed. + + \o If there is a \l Cancel button, it is the button activated when + \key Esc is pressed. + + \o If there is exactly one button having either + \l{QMessageBox::RejectRole} {the Reject role} or the + \l{QMessageBox::NoRole} {the No role}, it is the button + activated when \key Esc is pressed. + + \endlist + + When an escape button can't be determined using these rules, + pressing \key Esc has no effect. + + \sa QDialogButtonBox, {fowler}{GUI Design Handbook: Message Box}, {Standard Dialogs Example}, {Application Example} +*/ + +/*! + \enum QMessageBox::StandardButton + \since 4.2 + + These enums describe flags for standard buttons. Each button has a + defined \l ButtonRole. + + \value Ok An "OK" button defined with the \l AcceptRole. + \value Open A "Open" button defined with the \l AcceptRole. + \value Save A "Save" button defined with the \l AcceptRole. + \value Cancel A "Cancel" button defined with the \l RejectRole. + \value Close A "Close" button defined with the \l RejectRole. + \value Discard A "Discard" or "Don't Save" button, depending on the platform, + defined with the \l DestructiveRole. + \value Apply An "Apply" button defined with the \l ApplyRole. + \value Reset A "Reset" button defined with the \l ResetRole. + \value RestoreDefaults A "Restore Defaults" button defined with the \l ResetRole. + \value Help A "Help" button defined with the \l HelpRole. + \value SaveAll A "Save All" button defined with the \l AcceptRole. + \value Yes A "Yes" button defined with the \l YesRole. + \value YesToAll A "Yes to All" button defined with the \l YesRole. + \value No A "No" button defined with the \l NoRole. + \value NoToAll A "No to All" button defined with the \l NoRole. + \value Abort An "Abort" button defined with the \l RejectRole. + \value Retry A "Retry" button defined with the \l AcceptRole. + \value Ignore An "Ignore" button defined with the \l AcceptRole. + + \value NoButton An invalid button. + + \omitvalue FirstButton + \omitvalue LastButton + + The following values are obsolete: + + \value YesAll Use YesToAll instead. + \value NoAll Use NoToAll instead. + \value Default Use the \c defaultButton argument of + information(), warning(), etc. instead, or call + setDefaultButton(). + \value Escape Call setEscapeButton() instead. + \value FlagMask + \value ButtonMask + + \sa ButtonRole, standardButtons +*/ + +/*! + \fn void QMessageBox::buttonClicked(QAbstractButton *button) + + This signal is emitted whenever a button is clicked inside the QMessageBox. + The button that was clicked in returned in \a button. +*/ + +/*! + Constructs a message box with no text and no buttons. \a parent is + passed to the QDialog constructor. + + On Mac OS X, if you want your message box to appear + as a Qt::Sheet of its \a parent, set the message box's + \l{setWindowModality()} {window modality} to Qt::WindowModal or use open(). + Otherwise, the message box will be a standard dialog. + +*/ +QMessageBox::QMessageBox(QWidget *parent) + : QDialog(*new QMessageBoxPrivate, parent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(); +} + +/*! + Constructs a message box with the given \a icon, \a title, \a + text, and standard \a buttons. Standard or custom buttons can be + added at any time using addButton(). The \a parent and \a f + arguments are passed to the QDialog constructor. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + On Mac OS X, if \a parent is not 0 and you want your message box + to appear as a Qt::Sheet of that parent, set the message box's + \l{setWindowModality()} {window modality} to Qt::WindowModal + (default). Otherwise, the message box will be a standard dialog. + + \sa setWindowTitle(), setText(), setIcon(), setStandardButtons() +*/ +QMessageBox::QMessageBox(Icon icon, const QString &title, const QString &text, + StandardButtons buttons, QWidget *parent, + Qt::WindowFlags f) +: QDialog(*new QMessageBoxPrivate, parent, f | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(title, text); + setIcon(icon); + if (buttons != NoButton) + setStandardButtons(buttons); +} + +/*! + Destroys the message box. +*/ +QMessageBox::~QMessageBox() +{ +} + +/*! + \since 4.2 + + Adds the given \a button to the message box with the specified \a + role. + + \sa removeButton(), button(), setStandardButtons() +*/ +void QMessageBox::addButton(QAbstractButton *button, ButtonRole role) +{ + Q_D(QMessageBox); + if (!button) + return; + removeButton(button); + d->buttonBox->addButton(button, (QDialogButtonBox::ButtonRole)role); + d->customButtonList.append(button); + d->autoAddOkButton = false; +} + +/*! + \since 4.2 + \overload + + Creates a button with the given \a text, adds it to the message box for the + specified \a role, and returns it. +*/ +QPushButton *QMessageBox::addButton(const QString& text, ButtonRole role) +{ + Q_D(QMessageBox); + QPushButton *pushButton = new QPushButton(text); + addButton(pushButton, role); + d->updateSize(); + return pushButton; +} + +/*! + \since 4.2 + \overload + + Adds a standard \a button to the message box if it is valid to do so, and + returns the push button. + + \sa setStandardButtons() +*/ +QPushButton *QMessageBox::addButton(StandardButton button) +{ + Q_D(QMessageBox); + QPushButton *pushButton = d->buttonBox->addButton((QDialogButtonBox::StandardButton)button); + if (pushButton) + d->autoAddOkButton = false; + return pushButton; +} + +/*! + \since 4.2 + + Removes \a button from the button box without deleting it. + + \sa addButton(), setStandardButtons() +*/ +void QMessageBox::removeButton(QAbstractButton *button) +{ + Q_D(QMessageBox); + d->customButtonList.removeAll(button); + if (d->escapeButton == button) + d->escapeButton = 0; + if (d->defaultButton == button) + d->defaultButton = 0; + d->buttonBox->removeButton(button); + d->updateSize(); +} + +/*! + \property QMessageBox::standardButtons + \brief collection of standard buttons in the message box + \since 4.2 + + This property controls which standard buttons are used by the message box. + + By default, this property contains no standard buttons. + + \sa addButton() +*/ +void QMessageBox::setStandardButtons(StandardButtons buttons) +{ + Q_D(QMessageBox); + d->buttonBox->setStandardButtons(QDialogButtonBox::StandardButtons(int(buttons))); + + QList buttonList = d->buttonBox->buttons(); + if (!buttonList.contains(d->escapeButton)) + d->escapeButton = 0; + if (!buttonList.contains(d->defaultButton)) + d->defaultButton = 0; + d->autoAddOkButton = false; + d->updateSize(); +} + +QMessageBox::StandardButtons QMessageBox::standardButtons() const +{ + Q_D(const QMessageBox); + return QMessageBox::StandardButtons(int(d->buttonBox->standardButtons())); +} + +/*! + \since 4.2 + + Returns the standard button enum value corresponding to the given \a button, + or NoButton if the given \a button isn't a standard button. + + \sa button(), standardButtons() +*/ +QMessageBox::StandardButton QMessageBox::standardButton(QAbstractButton *button) const +{ + Q_D(const QMessageBox); + return (QMessageBox::StandardButton)d->buttonBox->standardButton(button); +} + +/*! + \since 4.2 + + Returns a pointer corresponding to the standard button \a which, + or 0 if the standard button doesn't exist in this message box. + + \sa standardButtons, standardButton() +*/ +QAbstractButton *QMessageBox::button(StandardButton which) const +{ + Q_D(const QMessageBox); + return d->buttonBox->button(QDialogButtonBox::StandardButton(which)); +} + +/*! + \since 4.2 + + Returns the button that is activated when escape is pressed. + + By default, QMessageBox attempts to automatically detect an + escape button as follows: + + \list 1 + \o If there is only one button, it is made the escape button. + \o If there is a \l Cancel button, it is made the escape button. + \o On Mac OS X only, if there is exactly one button with the role + QMessageBox::RejectRole, it is made the escape button. + \endlist + + When an escape button could not be automatically detected, pressing + \key Esc has no effect. + + \sa addButton() +*/ +QAbstractButton *QMessageBox::escapeButton() const +{ + Q_D(const QMessageBox); + return d->escapeButton; +} + +/*! + \since 4.2 + + Sets the button that gets activated when the \key Escape key is + pressed to \a button. + + \sa addButton(), clickedButton() +*/ +void QMessageBox::setEscapeButton(QAbstractButton *button) +{ + Q_D(QMessageBox); + if (d->buttonBox->buttons().contains(button)) + d->escapeButton = button; +} + +/*! + \since 4.3 + + Sets the buttons that gets activated when the \key Escape key is + pressed to \a button. + + \sa addButton(), clickedButton() +*/ +void QMessageBox::setEscapeButton(QMessageBox::StandardButton button) +{ + Q_D(QMessageBox); + setEscapeButton(d->buttonBox->button(QDialogButtonBox::StandardButton(button))); +} + +void QMessageBoxPrivate::detectEscapeButton() +{ + if (escapeButton) { // escape button explicitly set + detectedEscapeButton = escapeButton; + return; + } + + // Cancel button automatically becomes escape button + detectedEscapeButton = buttonBox->button(QDialogButtonBox::Cancel); + if (detectedEscapeButton) + return; + + // If there is only one button, make it the escape button + const QList buttons = buttonBox->buttons(); + if (buttons.count() == 1) { + detectedEscapeButton = buttons.first(); + return; + } + + // if the message box has one RejectRole button, make it the escape button + for (int i = 0; i < buttons.count(); i++) { + if (buttonBox->buttonRole(buttons.at(i)) == QDialogButtonBox::RejectRole) { + if (detectedEscapeButton) { // already detected! + detectedEscapeButton = 0; + break; + } + detectedEscapeButton = buttons.at(i); + } + } + if (detectedEscapeButton) + return; + + // if the message box has one NoRole button, make it the escape button + for (int i = 0; i < buttons.count(); i++) { + if (buttonBox->buttonRole(buttons.at(i)) == QDialogButtonBox::NoRole) { + if (detectedEscapeButton) { // already detected! + detectedEscapeButton = 0; + break; + } + detectedEscapeButton = buttons.at(i); + } + } +} + +/*! + \since 4.2 + + Returns the button that was clicked by the user, + or 0 if the user hit the \key Esc key and + no \l{setEscapeButton()}{escape button} was set. + + If exec() hasn't been called yet, returns 0. + + Example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qmessagebox.cpp 3 + + \sa standardButton(), button() +*/ +QAbstractButton *QMessageBox::clickedButton() const +{ + Q_D(const QMessageBox); + return d->clickedButton; +} + +/*! + \since 4.2 + + Returns the button that should be the message box's + \l{QPushButton::setDefault()}{default button}. Returns 0 + if no default button was set. + + \sa addButton(), QPushButton::setDefault() +*/ +QPushButton *QMessageBox::defaultButton() const +{ + Q_D(const QMessageBox); + return d->defaultButton; +} + +/*! + \since 4.2 + + Sets the message box's \l{QPushButton::setDefault()}{default button} + to \a button. + + \sa addButton(), QPushButton::setDefault() +*/ +void QMessageBox::setDefaultButton(QPushButton *button) +{ + Q_D(QMessageBox); + if (!d->buttonBox->buttons().contains(button)) + return; + d->defaultButton = button; + button->setDefault(true); + button->setFocus(); +} + +/*! + \since 4.3 + + Sets the message box's \l{QPushButton::setDefault()}{default button} + to \a button. + + \sa addButton(), QPushButton::setDefault() +*/ +void QMessageBox::setDefaultButton(QMessageBox::StandardButton button) +{ + Q_D(QMessageBox); + setDefaultButton(d->buttonBox->button(QDialogButtonBox::StandardButton(button))); +} + +/*! + \property QMessageBox::text + \brief the message box text to be displayed. + + The text will be interpreted either as a plain text or as rich text, + depending on the text format setting (\l QMessageBox::textFormat). + The default setting is Qt::AutoText, i.e., the message box will try + to auto-detect the format of the text. + + The default value of this property is an empty string. + + \sa textFormat, QMessageBox::informativeText, QMessageBox::detailedText +*/ +QString QMessageBox::text() const +{ + Q_D(const QMessageBox); + return d->label->text(); +} + +void QMessageBox::setText(const QString &text) +{ + Q_D(QMessageBox); + d->label->setText(text); + d->label->setWordWrap(d->label->textFormat() == Qt::RichText + || (d->label->textFormat() == Qt::AutoText && Qt::mightBeRichText(text))); + d->updateSize(); +} + +/*! + \enum QMessageBox::Icon + + This enum has the following values: + + \value NoIcon the message box does not have any icon. + + \value Question an icon indicating that + the message is asking a question. + + \value Information an icon indicating that + the message is nothing out of the ordinary. + + \value Warning an icon indicating that the + message is a warning, but can be dealt with. + + \value Critical an icon indicating that + the message represents a critical problem. + +*/ + +/*! + \property QMessageBox::icon + \brief the message box's icon + + The icon of the message box can be specified with one of the + values: + + \list + \o QMessageBox::NoIcon + \o QMessageBox::Question + \o QMessageBox::Information + \o QMessageBox::Warning + \o QMessageBox::Critical + \endlist + + The default is QMessageBox::NoIcon. + + The pixmap used to display the actual icon depends on the current + \l{QWidget::style()} {GUI style}. You can also set a custom pixmap + for the icon by setting the \l{QMessageBox::iconPixmap} {icon + pixmap} property. + + \sa iconPixmap +*/ +QMessageBox::Icon QMessageBox::icon() const +{ + Q_D(const QMessageBox); + return d->icon; +} + +void QMessageBox::setIcon(Icon icon) +{ + Q_D(QMessageBox); + setIconPixmap(QMessageBoxPrivate::standardIcon((QMessageBox::Icon)icon, + this)); + d->icon = icon; +} + +/*! + \property QMessageBox::iconPixmap + \brief the current icon + + The icon currently used by the message box. Note that it's often + hard to draw one pixmap that looks appropriate in all GUI styles; + you may want to supply a different pixmap for each platform. + + By default, this property is undefined. + + \sa icon +*/ +QPixmap QMessageBox::iconPixmap() const +{ + Q_D(const QMessageBox); + if (d->iconLabel && d->iconLabel->pixmap()) + return *d->iconLabel->pixmap(); + return QPixmap(); +} + +void QMessageBox::setIconPixmap(const QPixmap &pixmap) +{ + Q_D(QMessageBox); + d->iconLabel->setPixmap(pixmap); + d->updateSize(); + d->icon = NoIcon; +} + +/*! + \property QMessageBox::textFormat + \brief the format of the text displayed by the message box + + The current text format used by the message box. See the \l + Qt::TextFormat enum for an explanation of the possible options. + + The default format is Qt::AutoText. + + \sa setText() +*/ +Qt::TextFormat QMessageBox::textFormat() const +{ + Q_D(const QMessageBox); + return d->label->textFormat(); +} + +void QMessageBox::setTextFormat(Qt::TextFormat format) +{ + Q_D(QMessageBox); + d->label->setTextFormat(format); + d->label->setWordWrap(format == Qt::RichText + || (format == Qt::AutoText && Qt::mightBeRichText(d->label->text()))); + d->updateSize(); +} + +/*! + \reimp +*/ +bool QMessageBox::event(QEvent *e) +{ + bool result =QDialog::event(e); + switch (e->type()) { + case QEvent::LayoutRequest: + d_func()->updateSize(); + break; + case QEvent::LanguageChange: + d_func()->retranslateStrings(); + break; +#ifdef Q_WS_WINCE + case QEvent::OkRequest: + case QEvent::HelpRequest: { + QString bName = + (e->type() == QEvent::OkRequest) + ? QApplication::translate("QMessageBox", "OK") + : QApplication::translate("QMessageBox", "Help"); + QList list = findChildren(); + for (int i=0; itext() == bName) { + if (pb->isEnabled()) + pb->click(); + return pb->isEnabled(); + } + } + } +#endif + default: + break; + } + return result; +} + +/*! + \reimp +*/ +void QMessageBox::resizeEvent(QResizeEvent *event) +{ + QDialog::resizeEvent(event); +} + +/*! + \reimp +*/ +void QMessageBox::closeEvent(QCloseEvent *e) +{ + Q_D(QMessageBox); + if (!d->detectedEscapeButton) { + e->ignore(); + return; + } + QDialog::closeEvent(e); + d->clickedButton = d->detectedEscapeButton; + setResult(d->execReturnCode(d->detectedEscapeButton)); +} + +/*! + \reimp +*/ +void QMessageBox::changeEvent(QEvent *ev) +{ + Q_D(QMessageBox); + switch (ev->type()) { + case QEvent::StyleChange: + { + if (d->icon != NoIcon) + setIcon(d->icon); + Qt::TextInteractionFlags flags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this)); + d->label->setTextInteractionFlags(flags); + d->buttonBox->setCenterButtons(style()->styleHint(QStyle::SH_MessageBox_CenterButtons, 0, this)); + if (d->informativeLabel) + d->informativeLabel->setTextInteractionFlags(flags); + // intentional fall through + } + case QEvent::FontChange: + case QEvent::ApplicationFontChange: +#ifdef Q_WS_MAC + { + QFont f = font(); + f.setBold(true); + d->label->setFont(f); + } +#endif + default: + break; + } + QDialog::changeEvent(ev); +} + +/*! + \reimp +*/ +void QMessageBox::keyPressEvent(QKeyEvent *e) +{ + Q_D(QMessageBox); + if (e->key() == Qt::Key_Escape +#ifdef Q_WS_MAC + || (e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) +#endif + ) { + if (d->detectedEscapeButton) { +#ifdef Q_WS_MAC + d->detectedEscapeButton->animateClick(); +#else + d->detectedEscapeButton->click(); +#endif + } + return; + } + +#if defined (Q_OS_WIN) && !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) + if (e == QKeySequence::Copy) { + QString separator = QString::fromLatin1("---------------------------\n"); + QString textToCopy = separator; + separator.prepend(QLatin1Char('\n')); + textToCopy += windowTitle() + separator; // title + textToCopy += d->label->text() + separator; // text + + if (d->informativeLabel) + textToCopy += d->informativeLabel->text() + separator; + + QString buttonTexts; + QList buttons = d->buttonBox->buttons(); + for (int i = 0; i < buttons.count(); i++) { + buttonTexts += buttons[i]->text() + QLatin1String(" "); + } + textToCopy += buttonTexts + separator; + + QApplication::clipboard()->setText(textToCopy); + return; + } +#endif //QT_NO_SHORTCUT QT_NO_CLIPBOARD Q_OS_WIN + +#ifndef QT_NO_SHORTCUT + if (!(e->modifiers() & Qt::AltModifier)) { + int key = e->key() & ~((int)Qt::MODIFIER_MASK|(int)Qt::UNICODE_ACCEL); + if (key) { + const QList buttons = d->buttonBox->buttons(); + for (int i = 0; i < buttons.count(); ++i) { + QAbstractButton *pb = buttons.at(i); + int acc = pb->shortcut() & ~((int)Qt::MODIFIER_MASK|(int)Qt::UNICODE_ACCEL); + if (acc == key) { + pb->animateClick(); + return; + } + } + } + } +#endif + QDialog::keyPressEvent(e); +} + +#ifdef Q_WS_WINCE +/*! + \reimp +*/ +void QMessageBox::setVisible(bool visible) +{ + Q_D(QMessageBox); + if (visible) + d->hideSpecial(); + QDialog::setVisible(visible); +} +#endif + + +/*! + \overload + + Opens the dialog and connects its finished() or buttonClicked() signal to + the slot specified by \a receiver and \a member. If the slot in \a member + has a pointer for its first parameter the connection is to buttonClicked(), + otherwise the connection is to finished(). + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QMessageBox::open(QObject *receiver, const char *member) +{ + Q_D(QMessageBox); + const char *signal = member && strchr(member, '*') ? SIGNAL(buttonClicked(QAbstractButton*)) + : SIGNAL(finished(int)); + connect(this, signal, receiver, member); + d->signalToDisconnectOnClose = signal; + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + \since 4.5 + + Returns a list of all the buttons that have been added to the message box. + + \sa buttonRole(), addButton(), removeButton() +*/ +QList QMessageBox::buttons() const +{ + Q_D(const QMessageBox); + return d->buttonBox->buttons(); +} + +/*! + \since 4.5 + + Returns the button role for the specified \a button. This function returns + \l InvalidRole if \a button is 0 or has not been added to the message box. + + \sa buttons(), addButton() +*/ +QMessageBox::ButtonRole QMessageBox::buttonRole(QAbstractButton *button) const +{ + Q_D(const QMessageBox); + return QMessageBox::ButtonRole(d->buttonBox->buttonRole(button)); +} + +/*! + \reimp +*/ +void QMessageBox::showEvent(QShowEvent *e) +{ + Q_D(QMessageBox); + if (d->autoAddOkButton) { + addButton(Ok); +#if defined(Q_WS_WINCE) + d->hideSpecial(); +#endif + } + if (d->detailsButton) + addButton(d->detailsButton, QMessageBox::ActionRole); + d->detectEscapeButton(); + d->updateSize(); + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::Alert); +#endif +#ifdef Q_WS_WIN + HMENU systemMenu = GetSystemMenu((HWND)winId(), FALSE); + if (!d->detectedEscapeButton) { + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED); + } + else { + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED); + } +#endif + QDialog::showEvent(e); +} + + +static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, + QMessageBox::Icon icon, + const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) +{ + // necessary for source compatibility with Qt 4.0 and 4.1 + // handles (Yes, No) and (Yes|Default, No) + if (defaultButton && !(buttons & defaultButton)) + return (QMessageBox::StandardButton) + QMessageBoxPrivate::showOldMessageBox(parent, icon, title, + text, int(buttons), + int(defaultButton), 0); + + QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent); + QDialogButtonBox *buttonBox = msgBox.findChild(); + Q_ASSERT(buttonBox != 0); + + uint mask = QMessageBox::FirstButton; + while (mask <= QMessageBox::LastButton) { + uint sb = buttons & mask; + mask <<= 1; + if (!sb) + continue; + QPushButton *button = msgBox.addButton((QMessageBox::StandardButton)sb); + // Choose the first accept role as the default + if (msgBox.defaultButton()) + continue; + if ((defaultButton == QMessageBox::NoButton && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton))) + msgBox.setDefaultButton(button); + } + if (msgBox.exec() == -1) + return QMessageBox::Cancel; + return msgBox.standardButton(msgBox.clickedButton()); +} + +/*! + \since 4.2 + + Opens an information message box with the given \a title and + \a text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. + \a defaultButton specifies the button used when \key Enter is pressed. + \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal}{application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ +QMessageBox::StandardButton QMessageBox::information(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Information, title, text, buttons, + defaultButton); +} + + +/*! + \since 4.2 + + Opens a question message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +QMessageBox::StandardButton QMessageBox::question(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Question, title, text, buttons, defaultButton); +} + +/*! + \since 4.2 + + Opens a warning message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), information(), critical() +*/ +QMessageBox::StandardButton QMessageBox::warning(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Warning, title, text, buttons, defaultButton); +} + +/*! + \since 4.2 + + Opens a critical message box with the given \a title and \a + text in front of the specified \a parent widget. + + The standard \a buttons are added to the message box. \a + defaultButton specifies the button used when \key Enter is + pressed. \a defaultButton must refer to a button that was given in \a buttons. + If \a defaultButton is QMessageBox::NoButton, QMessageBox + chooses a suitable default automatically. + + Returns the identity of the standard button that was clicked. If + \key Esc was pressed instead, the \l{Default and Escape Keys} + {escape button} is returned. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), information() +*/ +QMessageBox::StandardButton QMessageBox::critical(QWidget *parent, const QString &title, + const QString& text, StandardButtons buttons, + StandardButton defaultButton) +{ + return showNewMessageBox(parent, Critical, title, text, buttons, defaultButton); +} + +/*! + Displays a simple about box with title \a title and text \a + text. The about box's parent is \a parent. + + about() looks for a suitable icon in four locations: + + \list 1 + \o It prefers \link QWidget::windowIcon() parent->icon() \endlink + if that exists. + \o If not, it tries the top-level widget containing \a parent. + \o If that fails, it tries the \link + QApplication::activeWindow() active window. \endlink + \o As a last resort it uses the Information icon. + \endlist + + The about box has a single button labelled "OK". On Mac OS X, the + about box is popped up as a modeless window; on other platforms, + it is currently application modal. + + \sa QWidget::windowIcon(), QApplication::activeWindow() +*/ +void QMessageBox::about(QWidget *parent, const QString &title, const QString &text) +{ +#ifdef Q_WS_MAC + static QPointer oldMsgBox; + + if (oldMsgBox && oldMsgBox->text() == text) { + oldMsgBox->show(); + oldMsgBox->raise(); + oldMsgBox->activateWindow(); + return; + } +#endif + + QMessageBox *msgBox = new QMessageBox(title, text, Information, 0, 0, 0, parent +#ifdef Q_WS_MAC + , Qt::WindowTitleHint | Qt::WindowSystemMenuHint +#endif + ); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + QIcon icon = msgBox->windowIcon(); + QSize size = icon.actualSize(QSize(64, 64)); + msgBox->setIconPixmap(icon.pixmap(size)); + + // should perhaps be a style hint +#ifdef Q_WS_MAC + oldMsgBox = msgBox; +#if 0 + // ### doesn't work until close button is enabled in title bar + msgBox->d_func()->autoAddOkButton = false; +#else + msgBox->d_func()->buttonBox->setCenterButtons(true); +#endif + msgBox->show(); +#else + msgBox->exec(); +#endif +} + +/*! + Displays a simple message box about Qt, with the given \a title + and centered over \a parent (if \a parent is not 0). The message + includes the version number of Qt being used by the application. + + This is useful for inclusion in the \gui Help menu of an application, + as shown in the \l{mainwindows/menus}{Menus} example. + + QApplication provides this functionality as a slot. + + On Mac OS X, the about box is popped up as a modeless window; on + other platforms, it is currently application modal. + + \sa QApplication::aboutQt() +*/ +void QMessageBox::aboutQt(QWidget *parent, const QString &title) +{ +#ifdef Q_WS_MAC + static QPointer oldMsgBox; + + if (oldMsgBox) { + oldMsgBox->show(); + oldMsgBox->raise(); + oldMsgBox->activateWindow(); + return; + } +#endif + + QString translatedTextAboutQtCaption; + translatedTextAboutQtCaption = QMessageBox::tr( + "

About Qt

" + "

This program uses Qt version %1.

" + ).arg(QLatin1String(QT_VERSION_STR)); + QString translatedTextAboutQtText; + translatedTextAboutQtText = QMessageBox::tr( + "

Qt is a C++ toolkit for cross-platform application " + "development.

" + "

Qt provides single-source portability across MS Windows, " + "Mac OS X, Linux, and all major commercial Unix variants. " + "Qt is also available for embedded devices as Qt for Embedded Linux " + "and Qt for Windows CE.

" + "

Qt is available under three different licensing options designed " + "to accommodate the needs of our various users.

" + "

Qt licensed under our commercial license agreement is appropriate " + "for development of proprietary/commercial software where you do not " + "want to share any source code with third parties or otherwise cannot " + "comply with the terms of the GNU LGPL version 2.1 or GNU GPL version " + "3.0.

" + "

Qt licensed under the GNU LGPL version 2.1 is appropriate for the " + "development of Qt applications (proprietary or open source) provided " + "you can comply with the terms and conditions of the GNU LGPL version " + "2.1.

" + "

Qt licensed under the GNU General Public License version 3.0 is " + "appropriate for the development of Qt applications where you wish to " + "use such applications in combination with software subject to the " + "terms of the GNU GPL version 3.0 or where you are otherwise willing " + "to comply with the terms of the GNU GPL version 3.0.

" + "

Please see qt.nokia.com/products/licensing " + "for an overview of Qt licensing.

" + "

Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).

" + "

Qt is a Nokia product. See qt.nokia.com " + "for more information.

" + ); + QMessageBox *msgBox = new QMessageBox(parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setWindowTitle(title.isEmpty() ? tr("About Qt") : title); + msgBox->setText(translatedTextAboutQtCaption); + msgBox->setInformativeText(translatedTextAboutQtText); + + QPixmap pm(QLatin1String(":/trolltech/qmessagebox/images/qtlogo-64.png")); + if (!pm.isNull()) + msgBox->setIconPixmap(pm); +#if defined(Q_WS_WINCE) + msgBox->setDefaultButton(msgBox->addButton(QMessageBox::Ok)); +#endif + + // should perhaps be a style hint +#ifdef Q_WS_MAC + oldMsgBox = msgBox; +#if 0 + // ### doesn't work until close button is enabled in title bar + msgBox->d_func()->autoAddOkButton = false; +#else + msgBox->d_func()->buttonBox->setCenterButtons(true); +#endif + msgBox->show(); +#else + msgBox->exec(); +#endif +} + +/*! + \internal +*/ +QSize QMessageBox::sizeHint() const +{ + // ### Qt 5: remove + return QDialog::sizeHint(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Source and binary compatibility routines for 4.0 and 4.1 + +static QMessageBox::StandardButton newButton(int button) +{ + // this is needed for source compatibility with Qt 4.0 and 4.1 + if (button == QMessageBox::NoButton || (button & NewButtonMask)) + return QMessageBox::StandardButton(button & QMessageBox::ButtonMask); + +#if QT_VERSION < 0x050000 + // this is needed for binary compatibility with Qt 4.0 and 4.1 + switch (button & Old_ButtonMask) { + case Old_Ok: + return QMessageBox::Ok; + case Old_Cancel: + return QMessageBox::Cancel; + case Old_Yes: + return QMessageBox::Yes; + case Old_No: + return QMessageBox::No; + case Old_Abort: + return QMessageBox::Abort; + case Old_Retry: + return QMessageBox::Retry; + case Old_Ignore: + return QMessageBox::Ignore; + case Old_YesAll: + return QMessageBox::YesToAll; + case Old_NoAll: + return QMessageBox::NoToAll; + default: + return QMessageBox::NoButton; + } +#endif +} + +static bool detectedCompat(int button0, int button1, int button2) +{ + if (button0 != 0 && !(button0 & NewButtonMask)) + return true; + if (button1 != 0 && !(button1 & NewButtonMask)) + return true; + if (button2 != 0 && !(button2 & NewButtonMask)) + return true; + return false; +} + +QAbstractButton *QMessageBoxPrivate::findButton(int button0, int button1, int button2, int flags) +{ + Q_Q(QMessageBox); + int button = 0; + + if (button0 & flags) { + button = button0; + } else if (button1 & flags) { + button = button1; + } else if (button2 & flags) { + button = button2; + } + return q->button(newButton(button)); +} + +void QMessageBoxPrivate::addOldButtons(int button0, int button1, int button2) +{ + Q_Q(QMessageBox); + q->addButton(newButton(button0)); + q->addButton(newButton(button1)); + q->addButton(newButton(button2)); + q->setDefaultButton( + static_cast(findButton(button0, button1, button2, QMessageBox::Default))); + q->setEscapeButton(findButton(button0, button1, button2, QMessageBox::Escape)); + compatMode = detectedCompat(button0, button1, button2); +} + +QAbstractButton *QMessageBoxPrivate::abstractButtonForId(int id) const +{ + Q_Q(const QMessageBox); + QAbstractButton *result = customButtonList.value(id); + if (result) + return result; + if (id & QMessageBox::FlagMask) // for compatibility with Qt 4.0/4.1 (even if it is silly) + return 0; + return q->button(newButton(id)); +} + +int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + int button0, int button1, int button2) +{ + QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent); + messageBox.d_func()->addOldButtons(button0, button1, button2); + return messageBox.exec(); +} + +int QMessageBoxPrivate::showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, + const QString &button0Text, + const QString &button1Text, + const QString &button2Text, + int defaultButtonNumber, + int escapeButtonNumber) +{ + QMessageBox messageBox(icon, title, text, QMessageBox::NoButton, parent); + QString myButton0Text = button0Text; + if (myButton0Text.isEmpty()) + myButton0Text = QDialogButtonBox::tr("OK"); + messageBox.addButton(myButton0Text, QMessageBox::ActionRole); + if (!button1Text.isEmpty()) + messageBox.addButton(button1Text, QMessageBox::ActionRole); + if (!button2Text.isEmpty()) + messageBox.addButton(button2Text, QMessageBox::ActionRole); + + const QList &buttonList = messageBox.d_func()->customButtonList; + messageBox.setDefaultButton(static_cast(buttonList.value(defaultButtonNumber))); + messageBox.setEscapeButton(buttonList.value(escapeButtonNumber)); + + return messageBox.exec(); +} + +void QMessageBoxPrivate::retranslateStrings() +{ +#ifndef QT_NO_TEXTEDIT + if (detailsButton) + detailsButton->setLabel(detailsText->isHidden() ? ShowLabel : HideLabel); +#endif +} + +/*! + \obsolete + + Constructs a message box with a \a title, a \a text, an \a icon, + and up to three buttons. + + The \a icon must be one of the following: + \list + \o QMessageBox::NoIcon + \o QMessageBox::Question + \o QMessageBox::Information + \o QMessageBox::Warning + \o QMessageBox::Critical + \endlist + + Each button, \a button0, \a button1 and \a button2, can have one + of the following values: + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + Use QMessageBox::NoButton for the later parameters to have fewer + than three buttons in your message box. If you don't specify any + buttons at all, QMessageBox will provide an Ok button. + + One of the buttons can be OR-ed with the QMessageBox::Default + flag to make it the default button (clicked when Enter is + pressed). + + One of the buttons can be OR-ed with the QMessageBox::Escape flag + to make it the cancel or close button (clicked when \key Esc is + pressed). + + \snippet doc/src/snippets/dialogs/dialogs.cpp 2 + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + The \a parent and \a f arguments are passed to + the QDialog constructor. + + \sa setWindowTitle(), setText(), setIcon() +*/ +QMessageBox::QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, QWidget *parent, + Qt::WindowFlags f) + : QDialog(*new QMessageBoxPrivate, parent, + f /*| Qt::MSWindowsFixedSizeDialogHint #### */| Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + d->init(title, text); + setIcon(icon); + d->addOldButtons(button0, button1, button2); +} + +/*! + \obsolete + + Opens an information message box with the given \a title and the + \a text. The dialog may have up to three buttons. Each of the + buttons, \a button0, \a button1 and \a button2 may be set to one + of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ +int QMessageBox::information(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Information, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays an information message box with the given \a title and + \a text, as well as one, two or three buttons. Returns the index + of the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be + used. \a button1Text is the text of the second button, and is + optional. \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the escape button; pressing + \key Esc is the same as clicking this button. It defaults to -1; + supply 0, 1 or 2 to make pressing \key Esc equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa question(), warning(), critical() +*/ + +int QMessageBox::information(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Information, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + +/*! + \obsolete + + Opens a question message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the buttons, \a + button0, \a button1 and \a button2 may be set to one of the + following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Yes, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +int QMessageBox::question(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Question, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a question message box with the given \a title and \a + text, as well as one, two or three buttons. Returns the index of + the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional. + \a button2Text is the text of the third button, and is optional. + \a defaultButtonNumber (0, 1 or 2) is the index of the default + button; pressing Return or Enter is the same as clicking the + default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1 or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), warning(), critical() +*/ +int QMessageBox::question(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Question, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + + +/*! + \obsolete + + Opens a warning message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the button + parameters, \a button0, \a button1 and \a button2 may be set to + one of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok or QMessageBox::No or ...) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), critical() +*/ +int QMessageBox::warning(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Warning, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a warning message box with the given \a title and \a + text, as well as one, two, or three buttons. Returns the number + of the button that was clicked (0, 1, or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional, + and \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1, or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), critical() +*/ +int QMessageBox::warning(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Warning, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + +/*! + \obsolete + + Opens a critical message box with the given \a title and \a text. + The dialog may have up to three buttons. Each of the button + parameters, \a button0, \a button1 and \a button2 may be set to + one of the following values: + + \list + \o QMessageBox::NoButton + \o QMessageBox::Ok + \o QMessageBox::Cancel + \o QMessageBox::Yes + \o QMessageBox::No + \o QMessageBox::Abort + \o QMessageBox::Retry + \o QMessageBox::Ignore + \o QMessageBox::YesAll + \o QMessageBox::NoAll + \endlist + + If you don't want all three buttons, set the last button, or last + two buttons to QMessageBox::NoButton. + + One button can be OR-ed with QMessageBox::Default, and one + button can be OR-ed with QMessageBox::Escape. + + Returns the identity (QMessageBox::Ok, or QMessageBox::No, etc.) + of the button that was clicked. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), warning() +*/ + +int QMessageBox::critical(QWidget *parent, const QString &title, const QString& text, + int button0, int button1, int button2) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Critical, title, text, + button0, button1, button2); +} + +/*! + \obsolete + \overload + + Displays a critical error message box with the given \a title and + \a text, as well as one, two, or three buttons. Returns the + number of the button that was clicked (0, 1 or 2). + + \a button0Text is the text of the first button, and is optional. + If \a button0Text is not supplied, "OK" (translated) will be used. + \a button1Text is the text of the second button, and is optional, + and \a button2Text is the text of the third button, and is + optional. \a defaultButtonNumber (0, 1 or 2) is the index of the + default button; pressing Return or Enter is the same as clicking + the default button. It defaults to 0 (the first button). \a + escapeButtonNumber is the index of the Escape button; pressing + Escape is the same as clicking this button. It defaults to -1; + supply 0, 1, or 2 to make pressing Escape equivalent to clicking + the relevant button. + + The message box is an \l{Qt::ApplicationModal} {application modal} + dialog box. + + \warning Do not delete \a parent during the execution of the dialog. + If you want to do this, you should create the dialog + yourself using one of the QMessageBox constructors. + + \sa information(), question(), warning() +*/ +int QMessageBox::critical(QWidget *parent, const QString &title, const QString& text, + const QString& button0Text, const QString& button1Text, + const QString& button2Text, int defaultButtonNumber, + int escapeButtonNumber) +{ + return QMessageBoxPrivate::showOldMessageBox(parent, Critical, title, text, + button0Text, button1Text, button2Text, + defaultButtonNumber, escapeButtonNumber); +} + + +/*! + \obsolete + + Returns the text of the message box button \a button, or + an empty string if the message box does not contain the button. + + Use button() and QPushButton::text() instead. +*/ +QString QMessageBox::buttonText(int button) const +{ + Q_D(const QMessageBox); + + if (QAbstractButton *abstractButton = d->abstractButtonForId(button)) { + return abstractButton->text(); + } else if (d->buttonBox->buttons().isEmpty() && (button == Ok || button == Old_Ok)) { + // for compatibility with Qt 4.0/4.1 + return QDialogButtonBox::tr("OK"); + } + return QString(); +} + +/*! + \obsolete + + Sets the text of the message box button \a button to \a text. + Setting the text of a button that is not in the message box is + silently ignored. + + Use addButton() instead. +*/ +void QMessageBox::setButtonText(int button, const QString &text) +{ + Q_D(QMessageBox); + if (QAbstractButton *abstractButton = d->abstractButtonForId(button)) { + abstractButton->setText(text); + } else if (d->buttonBox->buttons().isEmpty() && (button == Ok || button == Old_Ok)) { + // for compatibility with Qt 4.0/4.1 + addButton(QMessageBox::Ok)->setText(text); + } +} + +#ifndef QT_NO_TEXTEDIT +/*! + \property QMessageBox::detailedText + \brief the text to be displayed in the details area. + \since 4.2 + + The text will be interpreted as a plain text. + + By default, this property contains an empty string. + + \sa QMessageBox::text, QMessageBox::informativeText +*/ +QString QMessageBox::detailedText() const +{ + Q_D(const QMessageBox); + return d->detailsText ? d->detailsText->text() : QString(); +} + +void QMessageBox::setDetailedText(const QString &text) +{ + Q_D(QMessageBox); + if (text.isEmpty()) { + delete d->detailsText; + d->detailsText = 0; + removeButton(d->detailsButton); + delete d->detailsButton; + d->detailsButton = 0; + return; + } + + if (!d->detailsText) { + d->detailsText = new QMessageBoxDetailsText(this); + QGridLayout* grid = qobject_cast(layout()); + if (grid) + grid->addWidget(d->detailsText, grid->rowCount(), 0, 1, grid->columnCount()); + d->detailsText->hide(); + } + if (!d->detailsButton) + d->detailsButton = new DetailButton(this); + d->detailsText->setText(text); +} +#endif // QT_NO_TEXTEDIT + +/*! + \property QMessageBox::informativeText + + \brief the informative text that provides a fuller description for + the message + + \since 4.2 + + Infromative text can be used to expand upon the text() to give more + information to the user. On the Mac, this text appears in small + system font below the text(). On other platforms, it is simply + appended to the existing text. + + By default, this property contains an empty string. + + \sa QMessageBox::text, QMessageBox::detailedText +*/ +QString QMessageBox::informativeText() const +{ + Q_D(const QMessageBox); + return d->informativeLabel ? d->informativeLabel->text() : QString(); +} + +void QMessageBox::setInformativeText(const QString &text) +{ + Q_D(QMessageBox); + if (text.isEmpty()) { + layout()->removeWidget(d->informativeLabel); + delete d->informativeLabel; + d->informativeLabel = 0; +#ifndef Q_WS_MAC + d->label->setContentsMargins(2, 0, 0, 0); +#endif + d->updateSize(); + return; + } + + if (!d->informativeLabel) { + QLabel *label = new QLabel; + label->setObjectName(QLatin1String("qt_msgbox_informativelabel")); + label->setTextInteractionFlags(Qt::TextInteractionFlags(style()->styleHint(QStyle::SH_MessageBox_TextInteractionFlags, 0, this))); + label->setAlignment(Qt::AlignTop | Qt::AlignLeft); + label->setOpenExternalLinks(true); + label->setWordWrap(true); +#ifndef Q_WS_MAC + d->label->setContentsMargins(2, 0, 0, 0); + label->setContentsMargins(2, 0, 0, 6); + label->setIndent(9); +#else + label->setContentsMargins(16, 0, 0, 0); + // apply a smaller font the information label on the mac + label->setFont(qt_app_fonts_hash()->value("QTipLabel")); +#endif + label->setWordWrap(true); + QGridLayout *grid = static_cast(layout()); +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + label->hide(); + QTextBrowser *textBrowser = new QTextBrowser(this); + textBrowser->setOpenExternalLinks(true); + grid->addWidget(textBrowser, 1, 1, 1, 1); + d->textBrowser = textBrowser; +#else + grid->addWidget(label, 1, 1, 1, 1); +#endif + d->informativeLabel = label; + } + d->informativeLabel->setText(text); + +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + //We need to put the informative label inside textBrowser to enable scrolling of long texts. + d->textBrowser->setText(d->informativeLabel->text()); +#endif + + d->updateSize(); +} + +/*! + \since 4.2 + + This function shadows QWidget::setWindowTitle(). + + Sets the title of the message box to \a title. On Mac OS X, + the window title is ignored (as required by the Mac OS X + Guidelines). +*/ +void QMessageBox::setWindowTitle(const QString &title) +{ + // Message boxes on the mac do not have a title +#ifndef Q_WS_MAC + QDialog::setWindowTitle(title); +#else + Q_UNUSED(title); +#endif +} + + +/*! + \since 4.2 + + This function shadows QWidget::setWindowModality(). + + Sets the modality of the message box to \a windowModality. + + On Mac OS X, if the modality is set to Qt::WindowModal and the message box + has a parent, then the message box will be a Qt::Sheet, otherwise the + message box will be a standard dialog. +*/ +void QMessageBox::setWindowModality(Qt::WindowModality windowModality) +{ + QDialog::setWindowModality(windowModality); + + if (parentWidget() && windowModality == Qt::WindowModal) + setParent(parentWidget(), Qt::Sheet); + else + setParent(parentWidget(), Qt::Dialog); + setDefaultButton(d_func()->defaultButton); +} + +#ifdef QT3_SUPPORT +/*! + \compat + + Constructs a message box with the given \a parent, \a name, and + window flags, \a f. + The window title is specified by \a title, and the message box + displays message text and an icon specified by \a text and \a icon. + + The buttons that the user can access to respond to the message are + defined by \a button0, \a button1, and \a button2. +*/ +QMessageBox::QMessageBox(const QString& title, + const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent, const char *name, + bool modal, Qt::WindowFlags f) + : QDialog(*new QMessageBoxPrivate, parent, + f | Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + setObjectName(QString::fromAscii(name)); + d->init(title, text); + d->addOldButtons(button0, button1, button2); + setModal(modal); + setIcon(icon); +} + +/*! + \compat + Constructs a message box with the given \a parent and \a name. +*/ +QMessageBox::QMessageBox(QWidget *parent, const char *name) + : QDialog(*new QMessageBoxPrivate, parent, + Qt::WStyle_Customize | Qt::WStyle_DialogBorder | Qt::WStyle_Title | Qt::WStyle_SysMenu | Qt::WindowCloseButtonHint) +{ + Q_D(QMessageBox); + setObjectName(QString::fromAscii(name)); + d->init(); +} + +/*! + Returns the pixmap used for a standard icon. This + allows the pixmaps to be used in more complex message boxes. + \a icon specifies the required icon, e.g. QMessageBox::Information, + QMessageBox::Warning or QMessageBox::Critical. + + \a style is unused. +*/ + +QPixmap QMessageBox::standardIcon(Icon icon, Qt::GUIStyle style) +{ + Q_UNUSED(style); + return QMessageBox::standardIcon(icon); +} + +/*! + \fn int QMessageBox::message(const QString &title, const QString &text, + const QString &buttonText, QWidget *parent = 0, + const char *name = 0) + + Opens a modal message box with the given \a title and showing the + given \a text. The message box has a single button which has the + given \a buttonText (or tr("OK")). The message box is centred over + its \a parent and is called \a name. + + Use information(), warning(), question(), or critical() instead. + + \oldcode + QMessageBox::message(tr("My App"), tr("All occurrences replaced."), + tr("Close"), this); + \newcode + QMessageBox::information(this, tr("My App"), + tr("All occurrences replaced."), + QMessageBox::Close); + \endcode +*/ + +/*! + \fn bool QMessageBox::query(const QString &caption, + const QString& text, + const QString& yesButtonText, + const QString& noButtonText, + QWidget *parent, const char *name) + + \obsolete + + Queries the user using a modal message box with up to two buttons. + The message box has the given \a caption (although some window + managers don't show it), and shows the given \a text. The left + button has the \a yesButtonText (or tr("OK")), and the right button + has the \a noButtonText (or isn't shown). The message box is centred + over its \a parent and is called \a name. + + Use information(), question(), warning(), or critical() instead. +*/ + +#endif + +QPixmap QMessageBoxPrivate::standardIcon(QMessageBox::Icon icon, QMessageBox *mb) +{ + QStyle *style = mb ? mb->style() : QApplication::style(); + int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, mb); + QIcon tmpIcon; + switch (icon) { + case QMessageBox::Information: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, 0, mb); + break; + case QMessageBox::Warning: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, 0, mb); + break; + case QMessageBox::Critical: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, 0, mb); + break; + case QMessageBox::Question: + tmpIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mb); + default: + break; + } + if (!tmpIcon.isNull()) + return tmpIcon.pixmap(iconSize, iconSize); + return QPixmap(); +} + +/*! + \obsolete + + Returns the pixmap used for a standard icon. This allows the + pixmaps to be used in more complex message boxes. \a icon + specifies the required icon, e.g. QMessageBox::Question, + QMessageBox::Information, QMessageBox::Warning or + QMessageBox::Critical. + + Call QStyle::standardIcon() with QStyle::SP_MessageBoxInformation etc. + instead. +*/ + +QPixmap QMessageBox::standardIcon(Icon icon) +{ + return QMessageBoxPrivate::standardIcon(icon, 0); +} + +/*! + \typedef QMessageBox::Button + \obsolete + + Use QMessageBox::StandardButton instead. +*/ + +/*! + \fn int QMessageBox::information(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::warning(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::critical(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \fn int QMessageBox::question(QWidget *parent, const QString &title, + const QString& text, StandardButton button0, + StandardButton button1) + \internal + + ### Needed for Qt 4 source compatibility +*/ + +/*! + \fn int QMessageBox::exec() + + Shows the message box as a \l{QDialog#Modal Dialogs}{modal dialog}, + blocking until the user closes it. + + When using a QMessageBox with standard buttons, this functions returns a + \l StandardButton value indicating the standard button that was clicked. + When using QMessageBox with custom buttons, this function returns an + opaque value; use clickedButton() to determine which button was clicked. + + Users cannot interact with any other window in the same + application until they close the dialog, either by clicking a + button or by using a mechanism provided by the window system. + + \sa show(), result() +*/ + +QT_END_NAMESPACE + +#include "moc_qmessagebox.cpp" + +#endif // QT_NO_MESSAGEBOX diff --git a/src/gui/dialogs/qmessagebox.h b/src/gui/dialogs/qmessagebox.h new file mode 100644 index 0000000000..37ddb38e2d --- /dev/null +++ b/src/gui/dialogs/qmessagebox.h @@ -0,0 +1,365 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMESSAGEBOX_H +#define QMESSAGEBOX_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MESSAGEBOX + +class QLabel; +class QMessageBoxPrivate; +class QAbstractButton; + +class Q_GUI_EXPORT QMessageBox : public QDialog +{ + Q_OBJECT + Q_ENUMS(Icon) + Q_FLAGS(StandardButtons) + Q_PROPERTY(QString text READ text WRITE setText) + // ### Qt 5: Rename 'icon' 'standardIcon' and 'iconPixmap' 'icon' (and use QIcon?) + Q_PROPERTY(Icon icon READ icon WRITE setIcon) + Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) + Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) + Q_PROPERTY(StandardButtons standardButtons READ standardButtons WRITE setStandardButtons) +#ifndef QT_NO_TEXTEDIT + Q_PROPERTY(QString detailedText READ detailedText WRITE setDetailedText) +#endif + Q_PROPERTY(QString informativeText READ informativeText WRITE setInformativeText) + +public: + enum Icon { + NoIcon = 0, + Information = 1, + Warning = 2, + Critical = 3, + Question = 4 + }; + + enum ButtonRole { + // keep this in sync with QDialogButtonBox::ButtonRole + InvalidRole = -1, + AcceptRole, + RejectRole, + DestructiveRole, + ActionRole, + HelpRole, + YesRole, + NoRole, + ResetRole, + ApplyRole, + + NRoles + }; + + enum StandardButton { + // keep this in sync with QDialogButtonBox::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, + + FirstButton = Ok, // internal + LastButton = RestoreDefaults, // internal + + YesAll = YesToAll, // obsolete + NoAll = NoToAll, // obsolete + + Default = 0x00000100, // obsolete + Escape = 0x00000200, // obsolete + FlagMask = 0x00000300, // obsolete + ButtonMask = ~FlagMask // obsolete + }; + typedef StandardButton Button; // obsolete + + Q_DECLARE_FLAGS(StandardButtons, StandardButton) + + explicit QMessageBox(QWidget *parent = 0); + QMessageBox(Icon icon, const QString &title, const QString &text, + StandardButtons buttons = NoButton, QWidget *parent = 0, + Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + ~QMessageBox(); + + void addButton(QAbstractButton *button, ButtonRole role); + QPushButton *addButton(const QString &text, ButtonRole role); + QPushButton *addButton(StandardButton button); + void removeButton(QAbstractButton *button); + +#ifdef Q_WS_WINCE + void setVisible(bool visible); +#endif + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + QList buttons() const; + ButtonRole buttonRole(QAbstractButton *button) const; + + void setStandardButtons(StandardButtons buttons); + StandardButtons standardButtons() const; + StandardButton standardButton(QAbstractButton *button) const; + QAbstractButton *button(StandardButton which) const; + + QPushButton *defaultButton() const; + void setDefaultButton(QPushButton *button); + void setDefaultButton(StandardButton button); + + QAbstractButton *escapeButton() const; + void setEscapeButton(QAbstractButton *button); + void setEscapeButton(StandardButton button); + + QAbstractButton *clickedButton() const; + + QString text() const; + void setText(const QString &text); + + Icon icon() const; + void setIcon(Icon); + + QPixmap iconPixmap() const; + void setIconPixmap(const QPixmap &pixmap); + + Qt::TextFormat textFormat() const; + void setTextFormat(Qt::TextFormat format); + + static StandardButton information(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + // ### Qt 5: Replace Ok with Yes|No in question() function. + // Also consider if Ok == Yes and Cancel == No. + static StandardButton question(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static StandardButton warning(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static StandardButton critical(QWidget *parent, const QString &title, + const QString &text, StandardButtons buttons = Ok, + StandardButton defaultButton = NoButton); + static void about(QWidget *parent, const QString &title, const QString &text); + static void aboutQt(QWidget *parent, const QString &title = QString()); + + QSize sizeHint() const; + + // the following functions are obsolete: + + QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent = 0, + Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + + static int information(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1 = 0, int button2 = 0); + static int information(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static StandardButton information(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1 = NoButton) + { return information(parent, title, text, StandardButtons(button0), button1); } + + static int question(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1 = 0, int button2 = 0); + static int question(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int question(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return question(parent, title, text, StandardButtons(button0), button1); } + + static int warning(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1, int button2 = 0); + static int warning(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int warning(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return warning(parent, title, text, StandardButtons(button0), button1); } + + static int critical(QWidget *parent, const QString &title, + const QString& text, + int button0, int button1, int button2 = 0); + static int critical(QWidget *parent, const QString &title, + const QString& text, + const QString& button0Text, + const QString& button1Text = QString(), + const QString& button2Text = QString(), + int defaultButtonNumber = 0, + int escapeButtonNumber = -1); + inline static int critical(QWidget *parent, const QString &title, + const QString& text, + StandardButton button0, StandardButton button1) + { return critical(parent, title, text, StandardButtons(button0), button1); } + + QString buttonText(int button) const; + void setButtonText(int button, const QString &text); + + QString informativeText() const; + void setInformativeText(const QString &text); + +#ifndef QT_NO_TEXTEDIT + QString detailedText() const; + void setDetailedText(const QString &text); +#endif + + void setWindowTitle(const QString &title); + void setWindowModality(Qt::WindowModality windowModality); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QMessageBox(const QString &title, const QString &text, Icon icon, + int button0, int button1, int button2, + QWidget *parent, const char *name, bool modal, + Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + QT3_SUPPORT_CONSTRUCTOR QMessageBox(QWidget *parent, const char *name); + + static QT3_SUPPORT QPixmap standardIcon(Icon icon, Qt::GUIStyle); + static QT3_SUPPORT int message(const QString &title, + const QString& text, + const QString& buttonText=QString(), + QWidget *parent = 0, const char * = 0) { + return QMessageBox::information(parent, title, text, + buttonText.isEmpty() ? tr("OK") : buttonText) == 0; + } + static QT3_SUPPORT bool query(const QString &title, + const QString& text, + const QString& yesButtonText = QString(), + const QString& noButtonText = QString(), + QWidget *parent = 0, const char * = 0) { + return QMessageBox::information(parent, title, text, + yesButtonText.isEmpty() ? tr("OK") : yesButtonText, + noButtonText) == 0; + } +#endif + + static QPixmap standardIcon(Icon icon); + +Q_SIGNALS: + void buttonClicked(QAbstractButton *button); + +#ifdef qdoc +public Q_SLOTS: + int exec(); +#endif + +protected: + bool event(QEvent *e); + void resizeEvent(QResizeEvent *event); + void showEvent(QShowEvent *event); + void closeEvent(QCloseEvent *event); + void keyPressEvent(QKeyEvent *event); + void changeEvent(QEvent *event); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_buttonClicked(QAbstractButton *)) + + Q_DISABLE_COPY(QMessageBox) + Q_DECLARE_PRIVATE(QMessageBox) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMessageBox::StandardButtons) + +#define QT_REQUIRE_VERSION(argc, argv, str) { QString s = QString::fromLatin1(str);\ +QString sq = QString::fromLatin1(qVersion()); \ +if ((sq.section(QChar::fromLatin1('.'),0,0).toInt()<<16)+\ +(sq.section(QChar::fromLatin1('.'),1,1).toInt()<<8)+\ +sq.section(QChar::fromLatin1('.'),2,2).toInt()<(s.section(QChar::fromLatin1('.'),0,0).toInt()<<16)+\ +(s.section(QChar::fromLatin1('.'),1,1).toInt()<<8)+\ +s.section(QChar::fromLatin1('.'),2,2).toInt()) { \ +if (!qApp){ \ + new QApplication(argc,argv); \ +} \ +QString s = QApplication::tr("Executable '%1' requires Qt "\ + "%2, found Qt %3.").arg(qAppName()).arg(QString::fromLatin1(\ +str)).arg(QString::fromLatin1(qVersion())); QMessageBox::critical(0, QApplication::tr(\ +"Incompatible Qt Library Error"), s, QMessageBox::Abort, 0); qFatal("%s", s.toLatin1().data()); }} + +#endif // QT_NO_MESSAGEBOX + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMESSAGEBOX_H diff --git a/src/gui/dialogs/qmessagebox.qrc b/src/gui/dialogs/qmessagebox.qrc new file mode 100644 index 0000000000..8e6d7af671 --- /dev/null +++ b/src/gui/dialogs/qmessagebox.qrc @@ -0,0 +1,5 @@ + + + images/qtlogo-64.png + + diff --git a/src/gui/dialogs/qnspanelproxy_mac.mm b/src/gui/dialogs/qnspanelproxy_mac.mm new file mode 100644 index 0000000000..5c8ef24688 --- /dev/null +++ b/src/gui/dialogs/qnspanelproxy_mac.mm @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#if defined(Q_WS_MAC) +#include +#include +#import +#import +#import + +QT_BEGIN_NAMESPACE +static QWidget *currentWindow = 0; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QNSPanelProxy); + +@interface QT_MANGLE_NAMESPACE(QNSPanelProxy) : NSWindow { +} +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen; +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation; +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSPanelProxy) +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation +{ + // remove evil flag + windowStyle &= ~NSUtilityWindowMask; + self = [self qt_fakeInitWithContentRect:contentRect styleMask:windowStyle + backing:bufferingType defer:deferCreation]; + return self; +} + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen +{ + // remove evil flag + windowStyle &= ~NSUtilityWindowMask; + return [self qt_fakeInitWithContentRect:contentRect styleMask:windowStyle + backing:bufferingType defer:deferCreation screen:screen]; +} + +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation +{ + Q_UNUSED(contentRect); + Q_UNUSED(windowStyle); + Q_UNUSED(bufferingType); + Q_UNUSED(deferCreation); + return nil; +} + +- (id)qt_fakeInitWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation screen:(NSScreen *)screen +{ + Q_UNUSED(contentRect); + Q_UNUSED(windowStyle); + Q_UNUSED(bufferingType); + Q_UNUSED(deferCreation); + Q_UNUSED(screen); + return nil; +} +@end + +@class QT_MANGLE_NAMESPACE(QNSWindowProxy); + +@interface QT_MANGLE_NAMESPACE(QNSWindowProxy) : NSWindow { +} +- (void)setTitle:(NSString *)title; +- (void)qt_fakeSetTitle:(NSString *)title; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSWindowProxy) +- (void)setTitle:(NSString *)title +{ + QCFString cftitle(currentWindow->windowTitle()); + + // evil reverse engineering + if ([title isEqualToString:@"Print"] + || [title isEqualToString:@"Page Setup"] + || [[self className] isEqualToString:@"PMPrintingWindow"]) + title = (NSString *)(static_cast(cftitle)); + return [self qt_fakeSetTitle:title]; +} + +- (void)qt_fakeSetTitle:(NSString *)title +{ + Q_UNUSED(title); +} +@end + +QT_BEGIN_NAMESPACE + +/* + Intercept the NSColorPanel constructor if the shared + color panel doesn't exist yet. What's going on here is + quite wacky, because we want to override the NSPanel + constructor and at the same time call the old NSPanel + constructor. So what we do is we effectively rename the + old NSPanel constructor qt_fakeInitWithContentRect:... + and have the new one call the old one. +*/ +void macStartInterceptNSPanelCtor() +{ + qt_cocoa_change_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:), + [QT_MANGLE_NAMESPACE(QNSPanelProxy) class], + @selector(initWithContentRect:styleMask:backing:defer:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:)); + qt_cocoa_change_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + [QT_MANGLE_NAMESPACE(QNSPanelProxy) class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:screen:)); +} + +/* + Restore things as they were. +*/ +void macStopInterceptNSPanelCtor() +{ + qt_cocoa_change_back_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:screen:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:screen:)); + qt_cocoa_change_back_implementation( + [NSPanel class], + @selector(initWithContentRect:styleMask:backing:defer:), + @selector(qt_fakeInitWithContentRect:styleMask:backing:defer:)); +} + +/* + Intercept the NSPrintPanel and NSPageLayout setTitle: calls. The + hack is similar as for NSColorPanel above. +*/ +void macStartInterceptWindowTitle(QWidget *window) +{ + currentWindow = window; + qt_cocoa_change_implementation( + [NSWindow class], + @selector(setTitle:), + [QT_MANGLE_NAMESPACE(QNSWindowProxy) class], + @selector(setTitle:), + @selector(qt_fakeSetTitle:)); +} + +/* + Restore things as they were. +*/ +void macStopInterceptWindowTitle() +{ + currentWindow = 0; + qt_cocoa_change_back_implementation( + [NSWindow class], + @selector(setTitle:), + @selector(qt_fakeSetTitle:)); +} + +/* + Doesn't really belong in here. +*/ +NSButton *macCreateButton(const char *text, NSView *superview) +{ + static const NSRect buttonFrameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } }; + + NSButton *button = [[NSButton alloc] initWithFrame:buttonFrameRect]; + [button setButtonType:NSMomentaryLightButton]; + [button setBezelStyle:NSRoundedBezelStyle]; + [button setTitle:(NSString*)(CFStringRef)QCFString(QDialogButtonBox::tr(text) + .remove(QLatin1Char('&')))]; + [[button cell] setFont:[NSFont systemFontOfSize: + [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; + [superview addSubview:button]; + return button; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qpagesetupdialog.cpp b/src/gui/dialogs/qpagesetupdialog.cpp new file mode 100644 index 0000000000..4037e1cff8 --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QT_NO_PRINTDIALOG + +QT_BEGIN_NAMESPACE + +/*! + \class QPageSetupDialog + + \brief The QPageSetupDialog class provides a configuration dialog + for the page-related options on a printer. + + \ingroup standard-dialogs + \ingroup printing + + On Windows and Mac OS X the page setup dialog is implemented using + the native page setup dialogs. + + Note that on Windows and Mac OS X custom paper sizes won't be + reflected in the native page setup dialogs. Additionally, custom + page margins set on a QPrinter won't show in the native Mac OS X + page setup dialog. + + In Symbian, there is no support for printing. Hence, this dialog should not + be used in Symbian. + + \sa QPrinter, QPrintDialog +*/ + + +/*! + \fn QPageSetupDialog::QPageSetupDialog(QPrinter *printer, QWidget *parent) + + Constructs a page setup dialog that configures \a printer with \a + parent as the parent widget. +*/ + +/*! + \since 4.5 + + \fn QPageSetupDialog::QPageSetupDialog(QWidget *parent) + + Constructs a page setup dialog that configures a default-constructed + QPrinter with \a parent as the parent widget. + + \sa printer() +*/ + +/*! + \fn QPrinter *QPageSetupDialog::printer() + + Returns the printer that was passed to the QPageSetupDialog + constructor. +*/ + +// hack +class QPageSetupDialogPrivate : public QAbstractPageSetupDialogPrivate +{ +}; + +/*! + \enum QPageSetupDialog::PageSetupDialogOption + \since 4.4 + + Used to specify options to the page setup dialog + + This value is obsolete and does nothing since Qt 4.5: + + \value DontUseSheet In previous versions of QDialog::exec() the + page setup dialog would create a sheet by default if the dialog + was given a parent. This is no longer supported from Qt 4.5. If + you want to use sheets, use QPageSetupDialog::open() instead. + + \omitvalue None + \omitvalue OwnsPrinter +*/ + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption() +*/ +void QPageSetupDialog::setOption(PageSetupDialogOption option, bool on) +{ + Q_D(QPageSetupDialog); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption() +*/ +bool QPageSetupDialog::testOption(PageSetupDialogOption option) const +{ + Q_D(const QPageSetupDialog); + return (d->opts & option) != 0; +} + +/*! + \property QPageSetupDialog::options + \brief the various options that affect the look and feel of the dialog + \since 4.5 + + By default, all options are disabled. + + Options should be set before showing the dialog. Setting them while the + dialog is visible is not guaranteed to have an immediate effect on the + dialog (depending on the option and on the platform). + + \sa setOption(), testOption() +*/ +void QPageSetupDialog::setOptions(PageSetupDialogOptions options) +{ + Q_D(QPageSetupDialog); + + PageSetupDialogOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->opts = options; +} + +QPageSetupDialog::PageSetupDialogOptions QPageSetupDialog::options() const +{ + Q_D(const QPageSetupDialog); + return d->opts; +} + +/*! + \obsolete + + Use setOption(\a option, true) instead. +*/ +void QPageSetupDialog::addEnabledOption(PageSetupDialogOption option) +{ + setOption(option, true); +} + +/*! + \obsolete + + Use setOptions(\a options) instead. +*/ +void QPageSetupDialog::setEnabledOptions(PageSetupDialogOptions options) +{ + setOptions(options); +} + +/*! + \obsolete + + Use options() instead. +*/ +QPageSetupDialog::PageSetupDialogOptions QPageSetupDialog::enabledOptions() const +{ + return options(); +} + +/*! + \obsolete + + Use testOption(\a option) instead. +*/ +bool QPageSetupDialog::isOptionEnabled(PageSetupDialogOption option) const +{ + return testOption(option); +} + +/*! + \overload + \since 4.5 + + Opens the dialog and connects its accepted() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QPageSetupDialog::open(QObject *receiver, const char *member) +{ + Q_D(QPageSetupDialog); + connect(this, SIGNAL(accepted()), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +#if defined(Q_WS_MAC) || defined(Q_OS_WIN) +/*! \fn void QPageSetupDialog::setVisible(bool visible) + \reimp +*/ +#endif + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qpagesetupdialog.h b/src/gui/dialogs/qpagesetupdialog.h new file mode 100644 index 0000000000..184af251da --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAGESETUPDIALOG_H +#define QPAGESETUPDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTDIALOG + +class QPageSetupDialogPrivate; + +class Q_GUI_EXPORT QPageSetupDialog : public QAbstractPageSetupDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPageSetupDialog) + Q_ENUMS(PageSetupDialogOption) + Q_PROPERTY(PageSetupDialogOptions options READ options WRITE setOptions) + +public: + enum PageSetupDialogOption { + None = 0x00000000, // internal + DontUseSheet = 0x00000001, + OwnsPrinter = 0x80000000 // internal + }; + + Q_DECLARE_FLAGS(PageSetupDialogOptions, PageSetupDialogOption) + + explicit QPageSetupDialog(QPrinter *printer, QWidget *parent = 0); + explicit QPageSetupDialog(QWidget *parent = 0); + + // obsolete + void addEnabledOption(PageSetupDialogOption option); + void setEnabledOptions(PageSetupDialogOptions options); + PageSetupDialogOptions enabledOptions() const; + bool isOptionEnabled(PageSetupDialogOption option) const; + + void setOption(PageSetupDialogOption option, bool on = true); + bool testOption(PageSetupDialogOption option) const; + void setOptions(PageSetupDialogOptions options); + PageSetupDialogOptions options() const; + +#if defined(Q_WS_MAC) || defined(Q_OS_WIN) + virtual void setVisible(bool visible); +#endif + virtual int exec(); + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + +#ifdef qdoc + QPrinter *printer(); +#endif +}; + +#endif // QT_NO_PRINTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAGESETUPDIALOG_H diff --git a/src/gui/dialogs/qpagesetupdialog_mac.mm b/src/gui/dialogs/qpagesetupdialog_mac.mm new file mode 100644 index 0000000000..fa3f47378f --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog_mac.mm @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpagesetupdialog.h" + +#include +#include +#include +#include + +#ifndef QT_NO_PRINTDIALOG + +QT_USE_NAMESPACE + +@class QT_MANGLE_NAMESPACE(QCocoaPageLayoutDelegate); + +@interface QT_MANGLE_NAMESPACE(QCocoaPageLayoutDelegate) : NSObject { + QMacPrintEnginePrivate *pe; +} +- (id)initWithMacPrintEngine:(QMacPrintEnginePrivate *)printEngine; +- (void)pageLayoutDidEnd:(NSPageLayout *)pageLayout + returnCode:(int)returnCode contextInfo:(void *)contextInfo; +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaPageLayoutDelegate) +- (id)initWithMacPrintEngine:(QMacPrintEnginePrivate *)printEngine +{ + self = [super init]; + if (self) { + pe = printEngine; + } + return self; + +} +- (void)pageLayoutDidEnd:(NSPageLayout *)pageLayout + returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + Q_UNUSED(pageLayout); + QPageSetupDialog *dialog = static_cast(contextInfo); + if (returnCode == NSOKButton) { + PMRect paperRect; + PMGetUnadjustedPaperRect(pe->format, &paperRect); + pe->customSize = QSizeF(paperRect.right - paperRect.left, + paperRect.bottom - paperRect.top); + } + dialog->done((returnCode == NSOKButton) ? QDialog::Accepted : QDialog::Rejected); +} +@end + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptWindowTitle(QWidget *window); +extern void macStopInterceptWindowTitle(); + +class QPageSetupDialogPrivate : public QAbstractPageSetupDialogPrivate +{ + Q_DECLARE_PUBLIC(QPageSetupDialog) + +public: + QPageSetupDialogPrivate() : ep(0) +#ifndef QT_MAC_USE_COCOA + ,upp(0) +#else + ,pageLayout(0) +#endif + {} + + ~QPageSetupDialogPrivate() { +#ifndef QT_MAC_USE_COCOA + if (upp) { + DisposePMSheetDoneUPP(upp); + upp = 0; + } + QHash::iterator it = sheetCallbackMap.begin(); + while (it != sheetCallbackMap.end()) { + if (it.value() == this) { + it = sheetCallbackMap.erase(it); + } else { + ++it; + } + } +#endif + } + +#ifndef QT_MAC_USE_COCOA + void openCarbonPageLayout(Qt::WindowModality modality); + void closeCarbonPageLayout(); + static void pageSetupDialogSheetDoneCallback(PMPrintSession printSession, WindowRef /*documentWindow*/, Boolean accepted) { + QPageSetupDialogPrivate *priv = sheetCallbackMap.value(printSession); + if (!priv) { + qWarning("%s:%d: QPageSetupDialog::exec: Could not retrieve data structure, " + "you most likely now have an infinite modal loop", __FILE__, __LINE__); + return; + } + priv->q_func()->done(accepted ? QDialog::Accepted : QDialog::Rejected); + } +#else + void openCocoaPageLayout(Qt::WindowModality modality); + void closeCocoaPageLayout(); +#endif + + QMacPrintEnginePrivate *ep; +#ifndef QT_MAC_USE_COCOA + PMSheetDoneUPP upp; + static QHash sheetCallbackMap; +#else + NSPageLayout *pageLayout; +#endif +}; + +#ifndef QT_MAC_USE_COCOA +QHash QPageSetupDialogPrivate::sheetCallbackMap; +void QPageSetupDialogPrivate::openCarbonPageLayout(Qt::WindowModality modality) +{ + Q_Q(QPageSetupDialog); + // If someone is reusing a QPrinter object, the end released all our old + // information. In this case, we must reinitialize. + if (ep->session == 0) + ep->initialize(); + + sheetCallbackMap.insert(ep->session, this); + if (modality == Qt::ApplicationModal) { + QWidget modal_widg(0, Qt::Window); + modal_widg.setObjectName(QLatin1String(__FILE__ "__modal_dlg")); + modal_widg.createWinId(); + QApplicationPrivate::enterModal(&modal_widg); + QApplicationPrivate::native_modal_dialog_active = true; + Boolean accepted; + PMSessionPageSetupDialog(ep->session, ep->format, &accepted); + QApplicationPrivate::leaveModal(&modal_widg); + QApplicationPrivate::native_modal_dialog_active = false; + pageSetupDialogSheetDoneCallback(ep->session, 0, accepted); + } else { + // Window Modal means that we use a sheet at the moment, there's no other way to do it correctly. + if (!upp) + upp = NewPMSheetDoneUPP(QPageSetupDialogPrivate::pageSetupDialogSheetDoneCallback); + PMSessionUseSheets(ep->session, qt_mac_window_for(q->parentWidget()), upp); + Boolean unused; + PMSessionPageSetupDialog(ep->session, ep->format, &unused); + } +} + +void QPageSetupDialogPrivate::closeCarbonPageLayout() +{ + // if the margins have changed, we have to use the margins from the new + // PMFormat object + if (q_func()->result() == QDialog::Accepted) { + PMPaper paper; + PMPaperMargins margins; + PMGetPageFormatPaper(ep->format, &paper); + PMPaperGetMargins(paper, &margins); + ep->leftMargin = margins.left; + ep->topMargin = margins.top; + ep->rightMargin = margins.right; + ep->bottomMargin = margins.bottom; + + PMRect paperRect; + PMGetUnadjustedPaperRect(ep->format, &paperRect); + ep->customSize = QSizeF(paperRect.right - paperRect.left, + paperRect.bottom - paperRect.top); + } + sheetCallbackMap.remove(ep->session); +} +#else +void QPageSetupDialogPrivate::openCocoaPageLayout(Qt::WindowModality modality) +{ + Q_Q(QPageSetupDialog); + + // If someone is reusing a QPrinter object, the end released all our old + // information. In this case, we must reinitialize. + if (ep->session == 0) + ep->initialize(); + + macStartInterceptWindowTitle(q); + pageLayout = [NSPageLayout pageLayout]; + // Keep a copy to this since we plan on using it for a bit. + [pageLayout retain]; + QT_MANGLE_NAMESPACE(QCocoaPageLayoutDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QCocoaPageLayoutDelegate) alloc] initWithMacPrintEngine:ep]; + + if (modality == Qt::ApplicationModal) { + int rval = [pageLayout runModalWithPrintInfo:ep->printInfo]; + [delegate pageLayoutDidEnd:pageLayout returnCode:rval contextInfo:q]; + } else { + Q_ASSERT(q->parentWidget()); + [pageLayout beginSheetWithPrintInfo:ep->printInfo + modalForWindow:qt_mac_window_for(q->parentWidget()) + delegate:delegate + didEndSelector:@selector(pageLayoutDidEnd:returnCode:contextInfo:) + contextInfo:q]; + } + + macStopInterceptWindowTitle(); +} + +void QPageSetupDialogPrivate::closeCocoaPageLayout() +{ + // NSPageLayout can change the session behind our back and then our + // d->ep->session object will become a dangling pointer. Update it + // based on the "current" session + ep->session = static_cast([ep->printInfo PMPrintSession]); + + [pageLayout release]; + pageLayout = 0; +} +#endif + +QPageSetupDialog::QPageSetupDialog(QPrinter *printer, QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), printer, parent) +{ + Q_D(QPageSetupDialog); + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +QPageSetupDialog::QPageSetupDialog(QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), 0, parent) +{ + Q_D(QPageSetupDialog); + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +void QPageSetupDialog::setVisible(bool visible) +{ + Q_D(QPageSetupDialog); + + if (d->printer->outputFormat() != QPrinter::NativeFormat) + return; + +#ifndef QT_MAC_USE_COCOA + bool isCurrentlyVisible = d->sheetCallbackMap.contains(d->ep->session); +#else + bool isCurrentlyVisible = (d->pageLayout != 0); +#endif + if (!visible == !isCurrentlyVisible) + return; + + if (visible) { +#ifndef QT_MAC_USE_COCOA + d->openCarbonPageLayout(parentWidget() ? Qt::WindowModal + : Qt::ApplicationModal); +#else + d->openCocoaPageLayout(parentWidget() ? Qt::WindowModal + : Qt::ApplicationModal); +#endif + return; + } else { +#ifndef QT_MAC_USE_COCOA + d->closeCarbonPageLayout(); +#else + if (d->pageLayout) { + d->closeCocoaPageLayout(); + return; + } +#endif + } +} + +int QPageSetupDialog::exec() +{ + Q_D(QPageSetupDialog); + + if (d->printer->outputFormat() != QPrinter::NativeFormat) + return Rejected; + +#ifndef QT_MAC_USE_COCOA + d->openCarbonPageLayout(Qt::ApplicationModal); + d->closeCarbonPageLayout(); +#else + QMacCocoaAutoReleasePool pool; + d->openCocoaPageLayout(Qt::ApplicationModal); + d->closeCocoaPageLayout(); +#endif + return result(); +} + +QT_END_NAMESPACE + +#endif /* QT_NO_PRINTDIALOG */ diff --git a/src/gui/dialogs/qpagesetupdialog_unix.cpp b/src/gui/dialogs/qpagesetupdialog_unix.cpp new file mode 100644 index 0000000000..a4e0927e8c --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog_unix.cpp @@ -0,0 +1,620 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpagesetupdialog.h" + +#ifndef QT_NO_PRINTDIALOG +#include "qpagesetupdialog_unix_p.h" + +#include "qpainter.h" +#include "qprintdialog.h" +#include "qdialogbuttonbox.h" +#include + +#include +#include +#include + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +# include +# include +# include +#endif + + +QT_BEGIN_NAMESPACE + +QSizeF qt_printerPaperSize(QPrinter::Orientation, QPrinter::PaperSize, QPrinter::Unit, int); + +// Disabled until we have support for papersources on unix +// #define PSD_ENABLE_PAPERSOURCE + +static void populatePaperSizes(QComboBox* cb) +{ + cb->addItem(QPrintDialog::tr("A0"), QPrinter::A0); + cb->addItem(QPrintDialog::tr("A1"), QPrinter::A1); + cb->addItem(QPrintDialog::tr("A2"), QPrinter::A2); + cb->addItem(QPrintDialog::tr("A3"), QPrinter::A3); + cb->addItem(QPrintDialog::tr("A4"), QPrinter::A4); + cb->addItem(QPrintDialog::tr("A5"), QPrinter::A5); + cb->addItem(QPrintDialog::tr("A6"), QPrinter::A6); + cb->addItem(QPrintDialog::tr("A7"), QPrinter::A7); + cb->addItem(QPrintDialog::tr("A8"), QPrinter::A8); + cb->addItem(QPrintDialog::tr("A9"), QPrinter::A9); + cb->addItem(QPrintDialog::tr("B0"), QPrinter::B0); + cb->addItem(QPrintDialog::tr("B1"), QPrinter::B1); + cb->addItem(QPrintDialog::tr("B2"), QPrinter::B2); + cb->addItem(QPrintDialog::tr("B3"), QPrinter::B3); + cb->addItem(QPrintDialog::tr("B4"), QPrinter::B4); + cb->addItem(QPrintDialog::tr("B5"), QPrinter::B5); + cb->addItem(QPrintDialog::tr("B6"), QPrinter::B6); + cb->addItem(QPrintDialog::tr("B7"), QPrinter::B7); + cb->addItem(QPrintDialog::tr("B8"), QPrinter::B8); + cb->addItem(QPrintDialog::tr("B9"), QPrinter::B9); + cb->addItem(QPrintDialog::tr("B10"), QPrinter::B10); + cb->addItem(QPrintDialog::tr("C5E"), QPrinter::C5E); + cb->addItem(QPrintDialog::tr("DLE"), QPrinter::DLE); + cb->addItem(QPrintDialog::tr("Executive"), QPrinter::Executive); + cb->addItem(QPrintDialog::tr("Folio"), QPrinter::Folio); + cb->addItem(QPrintDialog::tr("Ledger"), QPrinter::Ledger); + cb->addItem(QPrintDialog::tr("Legal"), QPrinter::Legal); + cb->addItem(QPrintDialog::tr("Letter"), QPrinter::Letter); + cb->addItem(QPrintDialog::tr("Tabloid"), QPrinter::Tabloid); + cb->addItem(QPrintDialog::tr("US Common #10 Envelope"), QPrinter::Comm10E); + cb->addItem(QPrintDialog::tr("Custom"), QPrinter::Custom); +} + + +static QSizeF sizeForOrientation(QPrinter::Orientation orientation, const QSizeF &size) +{ + return (orientation == QPrinter::Portrait) ? size : QSizeF(size.height(), size.width()); +} + +#ifdef PSD_ENABLE_PAPERSOURCE +static const char *paperSourceNames[] = { + "Only One", + "Lower", + "Middle", + "Manual", + "Envelope", + "Envelope manual", + "Auto", + "Tractor", + "Small format", + "Large format", + "Large capacity", + "Cassette", + "Form source", + 0 +}; + +struct PaperSourceNames +{ + PaperSourceNames(const char *nam, QPrinter::PaperSource ps) + : paperSource(ps), name(nam) {} + QPrinter::PaperSource paperSource; + const char *name; +}; +#endif + + +class QPagePreview : public QWidget +{ +public: + QPagePreview(QWidget *parent) : QWidget(parent) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setMinimumSize(50, 50); + } + + void setPaperSize(const QSizeF& size) + { + m_size = size; + update(); + } + + void setMargins(qreal left, qreal top, qreal right, qreal bottom) + { + m_left = left; + m_top = top; + m_right = right; + m_bottom = bottom; + update(); + } + +protected: + void paintEvent(QPaintEvent *) + { + QRect pageRect; + QSizeF adjustedSize(m_size); + adjustedSize.scale(width()-10, height()-10, Qt::KeepAspectRatio); + pageRect = QRect(QPoint(0,0), adjustedSize.toSize()); + pageRect.moveCenter(rect().center()); + + qreal width_factor = pageRect.width() / m_size.width(); + qreal height_factor = pageRect.height() / m_size.height(); + int leftSize = qRound(m_left*width_factor); + int topSize = qRound(m_top*height_factor); + int rightSize = qRound(m_right*width_factor); + int bottomSize = qRound(m_bottom * height_factor); + QRect marginRect(pageRect.x()+leftSize, + pageRect.y()+topSize, + pageRect.width() - (leftSize+rightSize+1), + pageRect.height() - (topSize+bottomSize+1)); + + QPainter p(this); + QColor shadow(palette().mid().color()); + for (int i=1; i<6; ++i) { + shadow.setAlpha(180-i*30); + QRect offset(pageRect.adjusted(i, i, i, i)); + p.setPen(shadow); + p.drawLine(offset.left(), offset.bottom(), offset.right(), offset.bottom()); + p.drawLine(offset.right(), offset.top(), offset.right(), offset.bottom()-1); + } + p.fillRect(pageRect, palette().light()); + + if (marginRect.isValid()) { + p.setPen(QPen(palette().color(QPalette::Dark), 0, Qt::DotLine)); + p.drawRect(marginRect); + + marginRect.adjust(2, 2, -1, -1); + p.setClipRect(marginRect); + QFont font; + font.setPointSizeF(font.pointSizeF()*0.25); + p.setFont(font); + p.setPen(palette().color(QPalette::Dark)); + QString text(QLatin1String("Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.")); + for (int i=0; i<3; ++i) + text += text; + p.drawText(marginRect, Qt::TextWordWrap|Qt::AlignVCenter, text); + } + } + +private: + // all these are in points + qreal m_left, m_top, m_right, m_bottom; + QSizeF m_size; +}; + + +class QPageSetupDialogPrivate : public QAbstractPageSetupDialogPrivate +{ + Q_DECLARE_PUBLIC(QPageSetupDialog) + +public: + ~QPageSetupDialogPrivate(); + void init(); + + QPageSetupWidget *widget; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + QCUPSSupport *cups; +#endif +}; + +QPageSetupDialogPrivate::~QPageSetupDialogPrivate() +{ +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + delete cups; +#endif +} + +void QPageSetupDialogPrivate::init() +{ + Q_Q(QPageSetupDialog); + + widget = new QPageSetupWidget(q); + widget->setPrinter(printer); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (printer->outputFormat() == QPrinter::NativeFormat && QCUPSSupport::isAvailable()) { + cups = new QCUPSSupport; + widget->selectPrinter(cups); + } else { + cups = 0; + } +#endif + + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok + | QDialogButtonBox::Cancel, + Qt::Horizontal, q); + QObject::connect(buttons, SIGNAL(accepted()), q, SLOT(accept())); + QObject::connect(buttons, SIGNAL(rejected()), q, SLOT(reject())); + + QVBoxLayout *lay = new QVBoxLayout(q); + lay->addWidget(widget); + lay->addWidget(buttons); +} + +QPageSetupWidget::QPageSetupWidget(QWidget *parent) + : QWidget(parent), + m_printer(0), + m_blockSignals(false), + m_cups(0) +{ + widget.setupUi(this); + + QString suffix = (QLocale::system().measurementSystem() == QLocale::ImperialSystem) + ? QString::fromLatin1(" in") + : QString::fromLatin1(" mm"); + widget.topMargin->setSuffix(suffix); + widget.bottomMargin->setSuffix(suffix); + widget.leftMargin->setSuffix(suffix); + widget.rightMargin->setSuffix(suffix); + widget.paperWidth->setSuffix(suffix); + widget.paperHeight->setSuffix(suffix); + + QVBoxLayout *lay = new QVBoxLayout(widget.preview); + widget.preview->setLayout(lay); + m_pagePreview = new QPagePreview(widget.preview); + lay->addWidget(m_pagePreview); + + setAttribute(Qt::WA_WState_Polished, false); + +#ifdef PSD_ENABLE_PAPERSOURCE + for (int i=0; paperSourceNames[i]; ++i) + widget.paperSource->insertItem(paperSourceNames[i]); +#else + widget.paperSourceLabel->setVisible(false); + widget.paperSource->setVisible(false); +#endif + + widget.reverseLandscape->setVisible(false); + widget.reversePortrait->setVisible(false); + + populatePaperSizes(widget.paperSize); + + QStringList units; + units << tr("Centimeters (cm)") << tr("Millimeters (mm)") << tr("Inches (in)") << tr("Points (pt)"); + widget.unit->addItems(units); + connect(widget.unit, SIGNAL(activated(int)), this, SLOT(unitChanged(int))); + widget.unit->setCurrentIndex((QLocale::system().measurementSystem() == QLocale::ImperialSystem) ? 2 : 1); + + connect(widget.paperSize, SIGNAL(currentIndexChanged(int)), this, SLOT(_q_paperSizeChanged())); + connect(widget.paperWidth, SIGNAL(valueChanged(double)), this, SLOT(_q_paperSizeChanged())); + connect(widget.paperHeight, SIGNAL(valueChanged(double)), this, SLOT(_q_paperSizeChanged())); + + connect(widget.leftMargin, SIGNAL(valueChanged(double)), this, SLOT(setLeftMargin(double))); + connect(widget.topMargin, SIGNAL(valueChanged(double)), this, SLOT(setTopMargin(double))); + connect(widget.rightMargin, SIGNAL(valueChanged(double)), this, SLOT(setRightMargin(double))); + connect(widget.bottomMargin, SIGNAL(valueChanged(double)), this, SLOT(setBottomMargin(double))); + + connect(widget.portrait, SIGNAL(clicked()), this, SLOT(_q_pageOrientationChanged())); + connect(widget.landscape, SIGNAL(clicked()), this, SLOT(_q_pageOrientationChanged())); +} + +void QPageSetupWidget::setPrinter(QPrinter *printer) +{ + m_printer = printer; + m_blockSignals = true; + selectPdfPsPrinter(printer); + printer->getPageMargins(&m_leftMargin, &m_topMargin, &m_rightMargin, &m_bottomMargin, QPrinter::Point); + unitChanged(widget.unit->currentIndex()); + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); + m_paperSize = printer->paperSize(QPrinter::Point); + widget.paperWidth->setValue(m_paperSize.width() / m_currentMultiplier); + widget.paperHeight->setValue(m_paperSize.height() / m_currentMultiplier); + + widget.landscape->setChecked(printer->orientation() == QPrinter::Landscape); + +#ifdef PSD_ENABLE_PAPERSOURCE + widget.paperSource->setCurrentItem(printer->paperSource()); +#endif + Q_ASSERT(m_blockSignals); + m_blockSignals = false; + _q_paperSizeChanged(); +} + +// set gui data on printer +void QPageSetupWidget::setupPrinter() const +{ + QPrinter::Orientation orientation = widget.portrait->isChecked() + ? QPrinter::Portrait + : QPrinter::Landscape; + m_printer->setOrientation(orientation); + // paper format + QVariant val = widget.paperSize->itemData(widget.paperSize->currentIndex()); + int ps = m_printer->pageSize(); + if (val.type() == QVariant::Int) { + ps = val.toInt(); + } +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + else if (m_cups && QCUPSSupport::isAvailable() && m_cups->currentPPD()) { + QByteArray cupsPageSize = val.toByteArray(); + QPrintEngine *engine = m_printer->printEngine(); + engine->setProperty(PPK_CupsStringPageSize, QString::fromLatin1(cupsPageSize)); + engine->setProperty(PPK_CupsOptions, m_cups->options()); + + QRect pageRect = m_cups->pageRect(cupsPageSize); + engine->setProperty(PPK_CupsPageRect, pageRect); + + QRect paperRect = m_cups->paperRect(cupsPageSize); + engine->setProperty(PPK_CupsPaperRect, paperRect); + + for(ps = 0; ps < QPrinter::NPaperSize; ++ps) { + QPdf::PaperSize size = QPdf::paperSize(QPrinter::PaperSize(ps)); + if (size.width == paperRect.width() && size.height == paperRect.height()) + break; + } + } +#endif + if (ps == QPrinter::Custom) + m_printer->setPaperSize(sizeForOrientation(orientation, m_paperSize), QPrinter::Point); + else + m_printer->setPaperSize(static_cast(ps)); + +#ifdef PSD_ENABLE_PAPERSOURCE + m_printer->setPaperSource((QPrinter::PaperSource)widget.paperSource->currentIndex()); +#endif + m_printer->setPageMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin, QPrinter::Point); + +} + +void QPageSetupWidget::selectPrinter(QCUPSSupport *cups) +{ + m_cups = cups; + widget.paperSize->clear(); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (m_cups && QCUPSSupport::isAvailable()) { + const ppd_option_t* pageSizes = m_cups->pageSizes(); + const int numChoices = pageSizes ? pageSizes->num_choices : 0; + + int cupsDefaultSize = 0; + QSize qtPreferredSize = m_printer->paperSize(QPrinter::Point).toSize(); + bool preferredSizeMatched = false; + for (int i = 0; i < numChoices; ++i) { + widget.paperSize->addItem(QString::fromLocal8Bit(pageSizes->choices[i].text), QByteArray(pageSizes->choices[i].choice)); + if (static_cast(pageSizes->choices[i].marked) == 1) + cupsDefaultSize = i; + if (m_printer->d_func()->hasUserSetPageSize) { + QRect cupsPaperSize = m_cups->paperRect(pageSizes->choices[i].choice); + QSize diff = cupsPaperSize.size() - qtPreferredSize; + if (qAbs(diff.width()) < 5 && qAbs(diff.height()) < 5) { + widget.paperSize->setCurrentIndex(i); + preferredSizeMatched = true; + } + } + } + if (!preferredSizeMatched) + widget.paperSize->setCurrentIndex(cupsDefaultSize); + if (m_printer->d_func()->hasCustomPageMargins) { + m_printer->getPageMargins(&m_leftMargin, &m_topMargin, &m_rightMargin, &m_bottomMargin, QPrinter::Point); + } else { + QByteArray cupsPaperSizeChoice = widget.paperSize->itemData(widget.paperSize->currentIndex()).toByteArray(); + QRect paper = m_cups->paperRect(cupsPaperSizeChoice); + QRect content = m_cups->pageRect(cupsPaperSizeChoice); + + m_leftMargin = content.x() - paper.x(); + m_topMargin = content.y() - paper.y(); + m_rightMargin = paper.right() - content.right(); + m_bottomMargin = paper.bottom() - content.bottom(); + } + } +#endif + if (widget.paperSize->count() == 0) { + populatePaperSizes(widget.paperSize); + widget.paperSize->setCurrentIndex(widget.paperSize->findData( + QLocale::system().measurementSystem() == QLocale::ImperialSystem ? QPrinter::Letter : QPrinter::A4)); + } + + unitChanged(widget.unit->currentIndex()); + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + +void QPageSetupWidget::selectPdfPsPrinter(const QPrinter *p) +{ + m_cups = 0; + widget.paperSize->clear(); + populatePaperSizes(widget.paperSize); + widget.paperSize->setCurrentIndex(widget.paperSize->findData(p->paperSize())); + + m_leftMargin = 90; + m_topMargin = 72; + m_bottomMargin = 72; + m_rightMargin = 90; + unitChanged(widget.unit->currentIndex()); + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + +// Updates size/preview after the combobox has been changed. +void QPageSetupWidget::_q_paperSizeChanged() +{ + QVariant val = widget.paperSize->itemData(widget.paperSize->currentIndex()); + int index = m_printer->pageSize(); + if (val.type() == QVariant::Int) { + index = val.toInt(); + } + + if (m_blockSignals) return; + m_blockSignals = true; + + QPrinter::PaperSize size = QPrinter::PaperSize(index); + QPrinter::Orientation orientation = widget.portrait->isChecked() + ? QPrinter::Portrait + : QPrinter::Landscape; + + bool custom = size == QPrinter::Custom; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + custom = custom ? !m_cups : custom; +#endif + + widget.paperWidth->setEnabled(custom); + widget.paperHeight->setEnabled(custom); + widget.widthLabel->setEnabled(custom); + widget.heightLabel->setEnabled(custom); + if (custom) { + m_paperSize.setWidth( widget.paperWidth->value() * m_currentMultiplier); + m_paperSize.setHeight( widget.paperHeight->value() * m_currentMultiplier); + m_pagePreview->setPaperSize(m_paperSize); + } else { + Q_ASSERT(m_printer); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (m_cups) { // combobox is filled with cups based data + QByteArray cupsPageSize = widget.paperSize->itemData(widget.paperSize->currentIndex()).toByteArray(); + m_paperSize = m_cups->paperRect(cupsPageSize).size(); + if (orientation == QPrinter::Landscape) + m_paperSize = QSizeF(m_paperSize.height(), m_paperSize.width()); // swap + } + else +#endif + m_paperSize = qt_printerPaperSize(orientation, size, QPrinter::Point, 1); + + m_pagePreview->setPaperSize(m_paperSize); + widget.paperWidth->setValue(m_paperSize.width() / m_currentMultiplier); + widget.paperHeight->setValue(m_paperSize.height() / m_currentMultiplier); + } + m_blockSignals = false; +} + +void QPageSetupWidget::_q_pageOrientationChanged() +{ + if (QPrinter::PaperSize(widget.paperSize->currentIndex()) == QPrinter::Custom) { + double tmp = widget.paperWidth->value(); + widget.paperWidth->setValue(widget.paperHeight->value()); + widget.paperHeight->setValue(tmp); + } + _q_paperSizeChanged(); +} + +extern double qt_multiplierForUnit(QPrinter::Unit unit, int resolution); + +void QPageSetupWidget::unitChanged(int item) +{ + QString suffix; + switch(item) { + case 0: + m_currentMultiplier = 10 * qt_multiplierForUnit(QPrinter::Millimeter, 1); + suffix = QString::fromLatin1(" cm"); + break; + case 2: + m_currentMultiplier = qt_multiplierForUnit(QPrinter::Inch, 1); + suffix = QString::fromLatin1(" in"); + break; + case 3: + m_currentMultiplier = qt_multiplierForUnit(QPrinter::Point, 1); + suffix = QString::fromLatin1(" pt"); + break; + case 1: + default: + m_currentMultiplier = qt_multiplierForUnit(QPrinter::Millimeter, 1); + suffix = QString::fromLatin1(" mm"); + break; + } + const bool old = m_blockSignals; + m_blockSignals = true; + widget.topMargin->setSuffix(suffix); + widget.leftMargin->setSuffix(suffix); + widget.rightMargin->setSuffix(suffix); + widget.bottomMargin->setSuffix(suffix); + widget.paperWidth->setSuffix(suffix); + widget.paperHeight->setSuffix(suffix); + widget.topMargin->setValue(m_topMargin / m_currentMultiplier); + widget.leftMargin->setValue(m_leftMargin / m_currentMultiplier); + widget.rightMargin->setValue(m_rightMargin / m_currentMultiplier); + widget.bottomMargin->setValue(m_bottomMargin / m_currentMultiplier); + widget.paperWidth->setValue(m_paperSize.width() / m_currentMultiplier); + widget.paperHeight->setValue(m_paperSize.height() / m_currentMultiplier); + m_blockSignals = old; +} + +void QPageSetupWidget::setTopMargin(double newValue) +{ + if (m_blockSignals) return; + m_topMargin = newValue * m_currentMultiplier; + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + +void QPageSetupWidget::setBottomMargin(double newValue) +{ + if (m_blockSignals) return; + m_bottomMargin = newValue * m_currentMultiplier; + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + +void QPageSetupWidget::setLeftMargin(double newValue) +{ + if (m_blockSignals) return; + m_leftMargin = newValue * m_currentMultiplier; + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + +void QPageSetupWidget::setRightMargin(double newValue) +{ + if (m_blockSignals) return; + m_rightMargin = newValue * m_currentMultiplier; + m_pagePreview->setMargins(m_leftMargin, m_topMargin, m_rightMargin, m_bottomMargin); +} + + + +QPageSetupDialog::QPageSetupDialog(QPrinter *printer, QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), printer, parent) +{ + Q_D(QPageSetupDialog); + d->init(); +} + + +QPageSetupDialog::QPageSetupDialog(QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), 0, parent) +{ + Q_D(QPageSetupDialog); + d->init(); +} + +/*! + \internal +*/ +int QPageSetupDialog::exec() +{ + Q_D(QPageSetupDialog); + + int ret = QDialog::exec(); + if (ret == Accepted) + d->widget->setupPrinter(); + return ret; +} + + +QT_END_NAMESPACE + +#include "moc_qpagesetupdialog.cpp" + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qpagesetupdialog_unix_p.h b/src/gui/dialogs/qpagesetupdialog_unix_p.h new file mode 100644 index 0000000000..fea70b6bfe --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog_unix_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPAGESETUPWIDGET_H +#define QPAGESETUPWIDGET_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 +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "qglobal.h" + +#ifndef QT_NO_PRINTDIALOG + +#include + +QT_BEGIN_NAMESPACE + +class QPrinter; +class QPagePreview; +class QCUPSSupport; + +class QPageSetupWidget : public QWidget { + Q_OBJECT +public: + QPageSetupWidget(QWidget *parent = 0); + QPageSetupWidget(QPrinter *printer, QWidget *parent = 0); + void setPrinter(QPrinter *printer); + /// copy information from the widget and apply that to the printer. + void setupPrinter() const; + void selectPrinter(QCUPSSupport *m_cups); + void selectPdfPsPrinter(const QPrinter *p); + +private slots: + void _q_pageOrientationChanged(); + void _q_paperSizeChanged(); + void unitChanged(int item); + void setTopMargin(double newValue); + void setBottomMargin(double newValue); + void setLeftMargin(double newValue); + void setRightMargin(double newValue); + +private: + Ui::QPageSetupWidget widget; + QPagePreview *m_pagePreview; + QPrinter *m_printer; + qreal m_leftMargin; + qreal m_topMargin; + qreal m_rightMargin; + qreal m_bottomMargin; + QSizeF m_paperSize; + qreal m_currentMultiplier; + bool m_blockSignals; + QCUPSSupport *m_cups; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTDIALOG +#endif diff --git a/src/gui/dialogs/qpagesetupdialog_win.cpp b/src/gui/dialogs/qpagesetupdialog_win.cpp new file mode 100644 index 0000000000..d74da7c2fc --- /dev/null +++ b/src/gui/dialogs/qpagesetupdialog_win.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpagesetupdialog.h" + +#ifndef QT_NO_PRINTDIALOG +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPageSetupDialogPrivate : public QAbstractPageSetupDialogPrivate +{ +}; + +QPageSetupDialog::QPageSetupDialog(QPrinter *printer, QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), printer, parent) +{ +} + +QPageSetupDialog::QPageSetupDialog(QWidget *parent) + : QAbstractPageSetupDialog(*(new QPageSetupDialogPrivate), 0, parent) +{ +} + +int QPageSetupDialog::exec() +{ + Q_D(QPageSetupDialog); + + if (d->printer->outputFormat() != QPrinter::NativeFormat) + return Rejected; + + QWin32PrintEngine *engine = static_cast(d->printer->paintEngine()); + QWin32PrintEnginePrivate *ep = static_cast(engine->d_ptr.data()); + + PAGESETUPDLG psd; + memset(&psd, 0, sizeof(PAGESETUPDLG)); + psd.lStructSize = sizeof(PAGESETUPDLG); + + // we need a temp DEVMODE struct if we don't have a global DEVMODE + HGLOBAL hDevMode = 0; + int devModeSize = 0; + if (!ep->globalDevMode) { + devModeSize = sizeof(DEVMODE) + ep->devMode->dmDriverExtra; + hDevMode = GlobalAlloc(GHND, devModeSize); + if (hDevMode) { + void *dest = GlobalLock(hDevMode); + memcpy(dest, ep->devMode, devModeSize); + GlobalUnlock(hDevMode); + } + psd.hDevMode = hDevMode; + } else { + psd.hDevMode = ep->devMode; + } + + HGLOBAL *tempDevNames = ep->createDevNames(); + psd.hDevNames = tempDevNames; + + QWidget *parent = parentWidget(); + parent = parent ? parent->window() : QApplication::activeWindow(); + Q_ASSERT(!parent ||parent->testAttribute(Qt::WA_WState_Created)); + psd.hwndOwner = parent ? parent->winId() : 0; + + QRect paperRect = d->printer->paperRect(); + QRect pageRect = d->printer->pageRect(); + + psd.Flags = PSD_MARGINS; + double multiplier = 1; + switch (QLocale::system().measurementSystem()) { + case QLocale::MetricSystem: + psd.Flags |= PSD_INHUNDREDTHSOFMILLIMETERS; + multiplier = 1; + break; + case QLocale::ImperialSystem: + psd.Flags |= PSD_INTHOUSANDTHSOFINCHES; + multiplier = 25.4/10; + break; + } + + QRect marginRect = ep->getPageMargins(); + psd.rtMargin.left = marginRect.left() / multiplier; + psd.rtMargin.top = marginRect.top() / multiplier; + psd.rtMargin.right = marginRect.width() / multiplier;; + psd.rtMargin.bottom = marginRect.height() / multiplier;; + + bool result = PageSetupDlg(&psd); + if (result) { + ep->readDevnames(psd.hDevNames); + ep->readDevmode(psd.hDevMode); + + QRect theseMargins = QRect(psd.rtMargin.left * multiplier, + psd.rtMargin.top * multiplier, + psd.rtMargin.right * multiplier, + psd.rtMargin.bottom * multiplier); + + if (theseMargins != marginRect) { + ep->setPageMargins(psd.rtMargin.left * multiplier, + psd.rtMargin.top * multiplier, + psd.rtMargin.right * multiplier, + psd.rtMargin.bottom * multiplier); + } + + ep->updateCustomPaperSize(); + + // copy from our temp DEVMODE struct + if (!ep->globalDevMode && hDevMode) { + void *src = GlobalLock(hDevMode); + memcpy(ep->devMode, src, devModeSize); + GlobalUnlock(hDevMode); + } + } + + if (!ep->globalDevMode && hDevMode) + GlobalFree(hDevMode); + GlobalFree(tempDevNames); + done(result); + return result; +} + +void QPageSetupDialog::setVisible(bool visible) +{ + if (!visible) + return; + exec(); +} + +QT_END_NAMESPACE +#endif diff --git a/src/gui/dialogs/qpagesetupwidget.ui b/src/gui/dialogs/qpagesetupwidget.ui new file mode 100644 index 0000000000..ace2ab8f44 --- /dev/null +++ b/src/gui/dialogs/qpagesetupwidget.ui @@ -0,0 +1,353 @@ + + QPageSetupWidget + + + + 0 + 0 + 416 + 488 + + + + Form + + + + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Paper + + + + + + Page size: + + + paperSize + + + + + + + + + + Width: + + + paperWidth + + + + + + + + + 9999.989999999999782 + + + + + + + Height: + + + paperHeight + + + + + + + 9999.989999999999782 + + + + + + + + + Paper source: + + + paperSource + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Orientation + + + + + + Portrait + + + true + + + + + + + Landscape + + + + + + + Reverse landscape + + + + + + + Reverse portrait + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + + + Margins + + + + + + + + top margin + + + top margin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999.990000000000009 + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + left margin + + + left margin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999.990000000000009 + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + + + right margin + + + right margin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999.990000000000009 + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + bottom margin + + + bottom margin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 999.990000000000009 + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 20 + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + diff --git a/src/gui/dialogs/qprintdialog.h b/src/gui/dialogs/qprintdialog.h new file mode 100644 index 0000000000..05431608c4 --- /dev/null +++ b/src/gui/dialogs/qprintdialog.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTDIALOG_H +#define QPRINTDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTDIALOG + +class QPrintDialogPrivate; +class QPushButton; +class QPrinter; + +#if defined (Q_OS_UNIX) && !defined(QTOPIA_PRINTDIALOG) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) +class QUnixPrintWidgetPrivate; + +class Q_GUI_EXPORT QUnixPrintWidget : public QWidget +{ + Q_OBJECT + +public: + QUnixPrintWidget(QPrinter *printer, QWidget *parent = 0); + ~QUnixPrintWidget(); + void updatePrinter(); + +private: + friend class QPrintDialogPrivate; + friend class QUnixPrintWidgetPrivate; + QUnixPrintWidgetPrivate *d; + Q_PRIVATE_SLOT(d, void _q_printerChanged(int)) + Q_PRIVATE_SLOT(d, void _q_btnBrowseClicked()) + Q_PRIVATE_SLOT(d, void _q_btnPropertiesClicked()) +}; +#endif + +class Q_GUI_EXPORT QPrintDialog : public QAbstractPrintDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPrintDialog) + Q_ENUMS(PrintDialogOption) + Q_PROPERTY(PrintDialogOptions options READ options WRITE setOptions) + +public: + explicit QPrintDialog(QPrinter *printer, QWidget *parent = 0); + explicit QPrintDialog(QWidget *parent = 0); + ~QPrintDialog(); + + int exec(); +#if defined (Q_OS_UNIX) && !defined(QTOPIA_PRINTDIALOG) && !defined(Q_WS_MAC) + virtual void accept(); +#endif + void done(int result); + +#if defined (Q_OS_UNIX) && defined (QT3_SUPPORT) + QT3_SUPPORT void setPrinter(QPrinter *, bool = false); + QT3_SUPPORT QPrinter *printer() const; + QT3_SUPPORT void addButton(QPushButton *button); +#endif + + void setOption(PrintDialogOption option, bool on = true); + bool testOption(PrintDialogOption option) const; + void setOptions(PrintDialogOptions options); + PrintDialogOptions options() const; + +#if defined(Q_OS_UNIX) || defined(Q_WS_MAC) || defined(Q_OS_WIN) + void setVisible(bool visible); +#endif + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + +#ifdef qdoc + QPrinter *printer(); +#endif + +#ifdef QTOPIA_PRINTDIALOG +public: + bool eventFilter(QObject *, QEvent *); +#endif + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void accepted() { QDialog::accepted(); } +#endif +#else + using QDialog::accepted; +#endif + +Q_SIGNALS: + void accepted(QPrinter *printer); + +private: +#ifndef QTOPIA_PRINTDIALOG + Q_PRIVATE_SLOT(d_func(), void _q_chbPrintLastFirstToggled(bool)) +#if defined (Q_OS_UNIX) && !defined (Q_OS_MAC) + Q_PRIVATE_SLOT(d_func(), void _q_collapseOrExpandDialog()) +#endif +# if defined(Q_OS_UNIX) && !defined (Q_OS_MAC) && !defined(QT_NO_MESSAGEBOX) + Q_PRIVATE_SLOT(d_func(), void _q_checkFields()) +# endif +#else // QTOPIA_PRINTDIALOG + Q_PRIVATE_SLOT(d_func(), void _q_okClicked()) + Q_PRIVATE_SLOT(d_func(),void _q_printerOrFileSelected(QAbstractButton *b)) + Q_PRIVATE_SLOT(d_func(),void _q_paperSizeSelected(int)) + Q_PRIVATE_SLOT(d_func(), void _q_orientSelected(int)) + Q_PRIVATE_SLOT(d_func(), void _q_pageOrderSelected(int)) + Q_PRIVATE_SLOT(d_func(), void _q_colorModeSelected(QAbstractButton *)) + Q_PRIVATE_SLOT(d_func(), void _q_setNumCopies(int)) + Q_PRIVATE_SLOT(d_func(), void _q_printRangeSelected(int)) + Q_PRIVATE_SLOT(d_func(), void _q_setFirstPage(int)) + Q_PRIVATE_SLOT(d_func(), void _q_setLastPage(int)) + Q_PRIVATE_SLOT(d_func(), void _q_fileNameEditChanged(const QString &text)) +#endif // QTOPIA_PRINTDIALOG + friend class QUnixPrintWidget; +}; + +#endif // QT_NO_PRINTDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPRINTDIALOG_H diff --git a/src/gui/dialogs/qprintdialog.qdoc b/src/gui/dialogs/qprintdialog.qdoc new file mode 100644 index 0000000000..0fff9dd450 --- /dev/null +++ b/src/gui/dialogs/qprintdialog.qdoc @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef QT3_SUPPORT +/*! + \fn QPrinter *QPrintDialog::printer() const + + Returns a pointer to the printer this dialog configures, or 0 if + this dialog does not operate on any printer. + + This function is available for Unix platforms only. +*/ + +/*! + \fn void QPrintDialog::setPrinter(QPrinter *printer, bool pickupSettings) + + Sets this dialog to configure printer \a printer, or no printer if \a printer + is null. If \a pickupSettings is true, the dialog reads most of + its settings from \a printer. If \a pickupSettings is false (the + default) the dialog keeps its old settings. + + This function is available for Unix platforms only. +*/ + +/*! + \fn void QPrintDialog::addButton(QPushButton *button) + + Adds the \a button to the layout of the print dialog. The added + buttons are arranged from the left to the right below the + last groupbox of the printdialog. + + This function is available for Unix platforms only. +*/ +#endif diff --git a/src/gui/dialogs/qprintdialog.qrc b/src/gui/dialogs/qprintdialog.qrc new file mode 100644 index 0000000000..f54eb6b5ee --- /dev/null +++ b/src/gui/dialogs/qprintdialog.qrc @@ -0,0 +1,38 @@ + + +images/fit-page-24.png +images/fit-page-32.png +images/fit-width-24.png +images/fit-width-32.png +images/go-first-24.png +images/go-first-32.png +images/go-last-24.png +images/go-last-32.png +images/go-next-24.png +images/go-next-32.png +images/go-previous-24.png +images/go-previous-32.png +images/layout-landscape-24.png +images/layout-landscape-32.png +images/layout-portrait-24.png +images/layout-portrait-32.png +images/page-setup-24.png +images/page-setup-32.png +images/print-24.png +images/print-32.png +images/view-page-multi-24.png +images/view-page-multi-32.png +images/view-page-one-24.png +images/view-page-one-32.png +images/view-page-sided-24.png +images/view-page-sided-32.png +images/zoom-in-24.png +images/zoom-in-32.png +images/zoom-out-24.png +images/zoom-out-32.png + + +images/status-color.png +images/status-gray-scale.png + + diff --git a/src/gui/dialogs/qprintdialog_mac.mm b/src/gui/dialogs/qprintdialog_mac.mm new file mode 100644 index 0000000000..ed60b1016f --- /dev/null +++ b/src/gui/dialogs/qprintdialog_mac.mm @@ -0,0 +1,429 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_PRINTDIALOG + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPrintDialogPrivate : public QAbstractPrintDialogPrivate +{ + Q_DECLARE_PUBLIC(QPrintDialog) + +public: + QPrintDialogPrivate() : ep(0), printPanel(0) +#ifndef QT_MAC_USE_COCOA + ,upp(0) +#endif + {} +#ifndef QT_MAC_USE_COCOA + ~QPrintDialogPrivate() { + if (upp) { + DisposePMSheetDoneUPP(upp); + upp = 0; + } + QHash::iterator it = sheetCallbackMap.begin(); + while (it != sheetCallbackMap.end()) { + if (it.value() == this) { + it = sheetCallbackMap.erase(it); + } else { + ++it; + } + } + } +#endif + +#ifndef QT_MAC_USE_COCOA + void openCarbonPrintPanel(Qt::WindowModality modality); + void closeCarbonPrintPanel(); + static void printDialogSheetDoneCallback(PMPrintSession printSession, WindowRef /*documentWindow*/, Boolean accepted) { + QPrintDialogPrivate *priv = sheetCallbackMap.value(printSession); + if (!priv) { + qWarning("%s:%d: QPrintDialog::exec: Could not retrieve data structure, " + "you most likely now have an infinite loop", __FILE__, __LINE__); + return; + } + priv->q_func()->done(accepted ? QDialog::Accepted : QDialog::Rejected); + priv->closeCarbonPrintPanel(); + } +#else + void openCocoaPrintPanel(Qt::WindowModality modality); + void closeCocoaPrintPanel(); +#endif + void initBeforeRun(); + + inline QPrintDialog *printDialog() { return q_func(); } + + inline void _q_printToFileChanged(int) {} + inline void _q_rbPrintRangeToggled(bool) {} + inline void _q_printerChanged(int) {} +#ifndef QT_NO_MESSAGEBOX + inline void _q_checkFields() {} +#endif + inline void _q_chbPrintLastFirstToggled(bool) {} + inline void _q_paperSizeChanged(int) {} + inline void _q_btnBrowseClicked() {} + inline void _q_btnPropertiesClicked() {} + + QMacPrintEnginePrivate *ep; + NSPrintPanel *printPanel; +#ifndef QT_MAC_USE_COCOA + PMSheetDoneUPP upp; + static QHash sheetCallbackMap; +#endif +}; + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +@class QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate); + +@interface QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) : NSObject { +} +- (void)printPanelDidEnd:(NSPrintPanel *)printPanel + returnCode:(int)returnCode contextInfo:(void *)contextInfo; +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) +- (void)printPanelDidEnd:(NSPrintPanel *)printPanel + returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + Q_UNUSED(printPanel); + + QPrintDialogPrivate *d = static_cast(contextInfo); + QPrintDialog *dialog = d->printDialog(); + + if (returnCode == NSOKButton) { + UInt32 frompage, topage; + PMGetFirstPage(d->ep->settings, &frompage); + PMGetLastPage(d->ep->settings, &topage); + topage = qMin(UInt32(INT_MAX), topage); + dialog->setFromTo(frompage, topage); + + // OK, I need to map these values back let's see + // If from is 1 and to is INT_MAX, then print it all + // (Apologies to the folks with more than INT_MAX pages) + if (dialog->fromPage() == 1 && dialog->toPage() == INT_MAX) { + dialog->setPrintRange(QPrintDialog::AllPages); + dialog->setFromTo(0, 0); + } else { + dialog->setPrintRange(QPrintDialog::PageRange); // In a way a lie, but it shouldn't hurt. + // Carbon hands us back a very large number here even for ALL, set it to max + // in that case to follow the behavior of the other print dialogs. + if (dialog->maxPage() < dialog->toPage()) + dialog->setFromTo(dialog->fromPage(), dialog->maxPage()); + } + // Keep us in sync with file output + PMDestinationType dest; + + // If the user selected print to file, the session has been + // changed behind our back and our d->ep->session object is a + // dangling pointer. Update it based on the "current" session + d->ep->session = static_cast([d->ep->printInfo PMPrintSession]); + + PMSessionGetDestinationType(d->ep->session, d->ep->settings, &dest); + if (dest == kPMDestinationFile) { + QCFType file; + PMSessionCopyDestinationLocation(d->ep->session, d->ep->settings, &file); + UInt8 localFile[2048]; // Assuming there's a POSIX file system here. + CFURLGetFileSystemRepresentation(file, true, localFile, sizeof(localFile)); + d->ep->outputFilename + = QString::fromUtf8(reinterpret_cast(localFile)); + } else { + // Keep output format. + QPrinter::OutputFormat format; + format = d->printer->outputFormat(); + d->printer->setOutputFileName(QString()); + d->printer->setOutputFormat(format); + } + } + + dialog->done((returnCode == NSOKButton) ? QDialog::Accepted : QDialog::Rejected); +} +@end + +#endif + +QT_BEGIN_NAMESPACE + +extern void macStartInterceptWindowTitle(QWidget *window); +extern void macStopInterceptWindowTitle(); + + +void QPrintDialogPrivate::initBeforeRun() +{ + Q_Q(QPrintDialog); + // If someone is reusing a QPrinter object, the end released all our old + // information. In this case, we must reinitialize. + if (ep->session == 0) + ep->initialize(); + + + // It seems the only way that PM lets you use all is if the minimum + // for the page range is 1. This _kind of_ makes sense if you think about + // it. However, calling PMSetFirstPage() or PMSetLastPage() always enforces + // the range. + PMSetPageRange(ep->settings, q->minPage(), q->maxPage()); + if (q->printRange() == QAbstractPrintDialog::PageRange) { + PMSetFirstPage(ep->settings, q->fromPage(), false); + PMSetLastPage(ep->settings, q->toPage(), false); + } +} + +#ifndef QT_MAC_USE_COCOA +QHash QPrintDialogPrivate::sheetCallbackMap; +void QPrintDialogPrivate::openCarbonPrintPanel(Qt::WindowModality modality) +{ + Q_Q(QPrintDialog); + initBeforeRun(); + sheetCallbackMap.insert(ep->session, this); + if (modality == Qt::ApplicationModal) { + QWidget modal_widg(0, Qt::Window); + modal_widg.setObjectName(QLatin1String(__FILE__ "__modal_dlg")); + modal_widg.createWinId(); + QApplicationPrivate::enterModal(&modal_widg); + QApplicationPrivate::native_modal_dialog_active = true; + Boolean acceptStatus; + PMSessionPrintDialog(ep->session, ep->settings, ep->format, &acceptStatus); + QApplicationPrivate::leaveModal(&modal_widg); + QApplicationPrivate::native_modal_dialog_active = false; + printDialogSheetDoneCallback(ep->session, 0, acceptStatus); + } else { + // Window Modal means that we use a sheet at the moment, there's no other way to do it correctly. + if (!upp) + upp = NewPMSheetDoneUPP(QPrintDialogPrivate::printDialogSheetDoneCallback); + PMSessionUseSheets(ep->session, qt_mac_window_for(q->parentWidget()), upp); + QApplicationPrivate::native_modal_dialog_active = true; + Boolean unused; + PMSessionPrintDialog(ep->session, ep->settings, ep->format, &unused); + } +} + +void QPrintDialogPrivate::closeCarbonPrintPanel() +{ + Q_Q(QPrintDialog); + QApplicationPrivate::native_modal_dialog_active = false; + if (q->result() == QDialog::Accepted) { + UInt32 frompage, topage; + PMGetFirstPage(ep->settings, &frompage); + PMGetLastPage(ep->settings, &topage); + topage = qMin(UInt32(INT_MAX), topage); + q->setFromTo(frompage, topage); + + // OK, I need to map these values back let's see + // If from is 1 and to is INT_MAX, then print it all + // (Apologies to the folks with more than INT_MAX pages) + // ...that's a joke. + if (q->fromPage() == 1 && q->toPage() == INT_MAX) { + q->setPrintRange(QAbstractPrintDialog::AllPages); + q->setFromTo(0,0); + } else { + q->setPrintRange(QAbstractPrintDialog::PageRange); // In a way a lie, but it shouldn't hurt. + // Carbon hands us back a very large number here even for ALL, set it to max + // in that case to follow the behavior of the other print dialogs. + if (q->maxPage() < q->toPage()) + q->setFromTo(q->fromPage(), q->maxPage()); + } + // Keep us in sync with file output + PMDestinationType dest; + PMSessionGetDestinationType(ep->session, ep->settings, &dest); + if (dest == kPMDestinationFile) { + QCFType file; + PMSessionCopyDestinationLocation(ep->session, ep->settings, &file); + UInt8 localFile[2048]; // Assuming there's a POSIX file system here. + CFURLGetFileSystemRepresentation(file, true, localFile, sizeof(localFile)); + ep->outputFilename = QString::fromUtf8(reinterpret_cast(localFile)); + } else { + ep->outputFilename = QString(); + } + } + sheetCallbackMap.remove(ep->session); +} +#else +void QPrintDialogPrivate::openCocoaPrintPanel(Qt::WindowModality modality) +{ + Q_Q(QPrintDialog); + + initBeforeRun(); + + QPrintDialog::PrintDialogOptions qtOptions = q->options(); + NSPrintPanelOptions macOptions = NSPrintPanelShowsCopies; + if (qtOptions & QPrintDialog::PrintPageRange) + macOptions |= NSPrintPanelShowsPageRange; + if (qtOptions & QPrintDialog::PrintShowPageSize) + macOptions |= NSPrintPanelShowsPaperSize | NSPrintPanelShowsPageSetupAccessory + | NSPrintPanelShowsOrientation; + + macStartInterceptWindowTitle(q); + printPanel = [NSPrintPanel printPanel]; + QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) alloc] init]; + [printPanel setOptions:macOptions]; + + if (modality == Qt::ApplicationModal) { + int rval = [printPanel runModalWithPrintInfo:ep->printInfo]; + [delegate printPanelDidEnd:printPanel returnCode:rval contextInfo:this]; + } else { + Q_ASSERT(q->parentWidget()); + NSWindow *windowRef = qt_mac_window_for(q->parentWidget()); + [printPanel beginSheetWithPrintInfo:ep->printInfo + modalForWindow:windowRef + delegate:delegate + didEndSelector:@selector(printPanelDidEnd:returnCode:contextInfo:) + contextInfo:this]; + } + + macStopInterceptWindowTitle(); +} + +void QPrintDialogPrivate::closeCocoaPrintPanel() +{ + // ### +} +#endif + +static bool warnIfNotNative(QPrinter *printer) +{ + if (printer->outputFormat() != QPrinter::NativeFormat) { + qWarning("QPrintDialog: Cannot be used on non-native printers"); + return false; + } + return true; +} + + +QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), printer, parent) +{ + Q_D(QPrintDialog); + if (!warnIfNotNative(d->printer)) + return; + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +QPrintDialog::QPrintDialog(QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), 0, parent) +{ + Q_D(QPrintDialog); + if (!warnIfNotNative(d->printer)) + return; + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +QPrintDialog::~QPrintDialog() +{ +} + +int QPrintDialog::exec() +{ + Q_D(QPrintDialog); + if (!warnIfNotNative(d->printer)) + return QDialog::Rejected; + +#ifndef QT_MAC_USE_COCOA + d->openCarbonPrintPanel(Qt::ApplicationModal); +#else + QMacCocoaAutoReleasePool pool; + + d->openCocoaPrintPanel(Qt::ApplicationModal); + d->closeCocoaPrintPanel(); +#endif + return result(); +} + +#ifdef QT3_SUPPORT +QPrinter *QPrintDialog::printer() const +{ + Q_D(const QPrintDialog); + return d->printer; +} +#endif + +/*! + \reimp +*/ +void QPrintDialog::setVisible(bool visible) +{ + Q_D(QPrintDialog); + + bool isCurrentlyVisible = (d->printPanel != 0); + + if (!visible == !isCurrentlyVisible) + return; + + if (d->printer->outputFormat() != QPrinter::NativeFormat) + return; + + if (visible) { +#ifndef QT_MAC_USE_COCOA + d->openCarbonPrintPanel(parentWidget() ? Qt::WindowModal + : Qt::ApplicationModal); +#else + d->openCocoaPrintPanel(parentWidget() ? Qt::WindowModal + : Qt::ApplicationModal); +#endif + return; + } else { + if (d->printPanel) { +#ifndef QT_MAC_USE_COCOA + d->closeCarbonPrintPanel(); +#else + d->closeCocoaPrintPanel(); +#endif + return; + } + } +} + +QT_END_NAMESPACE + +#include "moc_qprintdialog.cpp" + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qprintdialog_qws.cpp b/src/gui/dialogs/qprintdialog_qws.cpp new file mode 100644 index 0000000000..373b986f0b --- /dev/null +++ b/src/gui/dialogs/qprintdialog_qws.cpp @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include +#include "qprintdialog.h" + +#ifndef QT_NO_PRINTDIALOG + +#include "qapplication.h" +#include "qbuttongroup.h" +#include "qradiobutton.h" +#include "qcombobox.h" +#include "qspinbox.h" +#include "qprinter.h" +#include "qlineedit.h" +#include "qdir.h" +#include "qmessagebox.h" +#include "qinputdialog.h" +#include "qlayout.h" +#include "qlabel.h" + +#include "qlibrary.h" + +#ifndef QT_NO_NIS + +#ifndef BOOL_DEFINED +#define BOOL_DEFINED +#endif + +#include +#include + +#endif //QT_NO_NIS + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +typedef void (*QPrintDialogCreator)(QPrintDialog *parent); +Q_GUI_EXPORT QPrintDialogCreator _qt_print_dialog_creator; + +class QPrintDialogPrivate : public QAbstractPrintDialogPrivate +{ + Q_DECLARE_PUBLIC(QPrintDialog) +public: + QButtonGroup *printerOrFile; + bool outputToFile; + QRadioButton *printToPrinterButton; + QRadioButton *printToFileButton; + QLineEdit *fileName; + + QButtonGroup *colorMode; + QRadioButton *printColor; + QRadioButton *printGray; + QPrinter::ColorMode colorMode2; + + QComboBox *orientationCombo, *sizeCombo; + QPrinter::PaperSize pageSize; + QPrinter::Orientation orientation; + + QSpinBox *copies; + int numCopies; + QPrinter::PaperSize indexToPaperSize[QPrinter::NPaperSize]; + + QComboBox *rangeCombo; + QSpinBox *firstPage; + QSpinBox *lastPage; + + QComboBox *pageOrderCombo; + QPrinter::PageOrder pageOrder2; + + QString faxNum; + + void init(); + + void _q_okClicked(); + void _q_printerOrFileSelected(QAbstractButton *b); + void _q_paperSizeSelected(int); + void _q_orientSelected(int); + void _q_pageOrderSelected(int); + void _q_colorModeSelected(QAbstractButton *); + void _q_setNumCopies(int); + void _q_printRangeSelected(int); + void _q_setFirstPage(int); + void _q_setLastPage(int); + void _q_fileNameEditChanged(const QString &text); + + void setupDestination(); + void setupPrinterSettings(); + void setupPaper(); + void setupOptions(); + + void setPrinter(QPrinter *p, bool pickUpSettings); +}; + +static void isc(QPrintDialogPrivate *d, const QString & text, + QPrinter::PaperSize ps); + +void QPrintDialogPrivate::_q_okClicked() +{ + Q_Q(QPrintDialog); +#ifndef QT_NO_MESSAGEBOX + if (outputToFile && fileName->isModified() && QFileInfo(fileName->text()).exists()) { + int confirm = QMessageBox::warning( + q, QPrintDialog::tr("File exists"), + QPrintDialog::tr("Do you want to overwrite it?"), + QMessageBox::Yes, QMessageBox::No); + if (confirm == QMessageBox::No) + return; + } +#endif // QT_NO_MESSAGEBOX + + lastPage->interpretText(); + firstPage->interpretText(); + copies->interpretText(); + if (outputToFile) { + printer->setOutputFileName(fileName->text()); + } + printer->setOrientation(orientation); + printer->setPaperSize(pageSize); + printer->setPageOrder(pageOrder2); + printer->setColorMode(colorMode2); + printer->setCopyCount(numCopies); + + switch ((rangeCombo->itemData(rangeCombo->currentIndex())).toInt()){ + case (int)QPrintDialog::AllPages: + q->setPrintRange(QPrintDialog::AllPages); + q->setFromTo(0, 0); + break; + case (int)QPrintDialog::Selection: + q->setPrintRange(QPrintDialog::Selection); + q->setFromTo(0, 0); + break; + case (int)QPrintDialog::PageRange: + q->setPrintRange(QPrintDialog::PageRange); + q->setFromTo(firstPage->value(), lastPage->value()); + break; + case (int)QPrintDialog::CurrentPage: + q->setPrintRange(QPrintDialog::CurrentPage); + q->setFromTo(0, 0); + break; + } + q->accept(); +} + +void QPrintDialogPrivate::_q_printerOrFileSelected(QAbstractButton *b) +{ + outputToFile = (b == printToFileButton); + if (outputToFile) { + _q_fileNameEditChanged(fileName->text()); + if (!fileName->isModified() && fileName->text().isEmpty()) { + QString file = "print.tiff"; + fileName->setText(file); + fileName->setCursorPosition(file.length()); + fileName->selectAll(); + fileName->setModified(true); // confirm overwrite when OK clicked + + } + fileName->setEnabled(true); + fileName->setFocus(); + } else { + fileName->setText(QString()); + if (fileName->isEnabled()) + fileName->setEnabled(false); + } +} + +void QPrintDialogPrivate::_q_paperSizeSelected(int id) +{ + if (id < QPrinter::NPaperSize) + pageSize = QPrinter::PaperSize(indexToPaperSize[id]); +} + +void QPrintDialogPrivate::_q_orientSelected(int id) +{ + orientation = (QPrinter::Orientation)id; +} + +void QPrintDialogPrivate::_q_pageOrderSelected(int id) +{ + pageOrder2 = (QPrinter::PageOrder)id; +} + +void QPrintDialogPrivate::_q_colorModeSelected(QAbstractButton *b) +{ + colorMode2 = (b == printColor) ? QPrinter::Color : QPrinter::GrayScale; +} + +void QPrintDialogPrivate::_q_setNumCopies(int copies) +{ + numCopies = copies; +} + +void QPrintDialogPrivate::_q_printRangeSelected(int id) +{ + bool enable = (rangeCombo->itemData(id).toInt() == (int)QPrintDialog::PageRange); + firstPage->setEnabled(enable); + lastPage->setEnabled(enable); +} + +void QPrintDialogPrivate::_q_setFirstPage(int fp) +{ + Q_Q(QPrintDialog); + if (printer) { + lastPage->setMinimum(fp); + lastPage->setMaximum(qMax(fp, q->maxPage())); + } +} + +void QPrintDialogPrivate::_q_setLastPage(int lp) +{ + Q_Q(QPrintDialog); + if (printer) { + firstPage->setMinimum(qMin(lp, q->minPage())); + firstPage->setMaximum(lp); + } +} + +void QPrintDialogPrivate::_q_fileNameEditChanged(const QString &text) +{ + Q_UNUSED(text); +} + +void QPrintDialogPrivate::setupDestination() +{ + Q_Q(QPrintDialog); + + // print destinations + printerOrFile = new QButtonGroup(q); + QObject::connect(printerOrFile, SIGNAL(buttonClicked(QAbstractButton*)), + q, SLOT(_q_printerOrFileSelected(QAbstractButton*))); + + printToPrinterButton = q->findChild("printToPrinterButton"); + printerOrFile->addButton(printToPrinterButton); + printToFileButton = q->findChild("printToFileButton"); + printerOrFile->addButton(printToFileButton); + + // file name + fileName = q->findChild("fileName"); + QObject::connect(fileName, SIGNAL(textChanged(QString)), + q, SLOT(_q_fileNameEditChanged(QString))); + + outputToFile = false; +} + +void QPrintDialogPrivate::setupPrinterSettings() +{ + Q_Q(QPrintDialog); + + // color mode + colorMode = new QButtonGroup(q); + QObject::connect(colorMode, SIGNAL(buttonClicked(QAbstractButton*)), + q, SLOT(_q_colorModeSelected(QAbstractButton*))); + + printColor = q->findChild("printColor"); + colorMode->addButton(printColor); + printGray = q->findChild("printGray"); + colorMode->addButton(printGray); +} + +void isc(QPrintDialogPrivate *ptr, const QString & text, QPrinter::PaperSize ps) +{ + if (ptr && !text.isEmpty() && ps < QPrinter::NPaperSize) { + ptr->sizeCombo->addItem(text); + int index = ptr->sizeCombo->count()-1; + if (index >= 0 && index < QPrinter::NPaperSize) + ptr->indexToPaperSize[index] = ps; + } +} + +void QPrintDialogPrivate::setupPaper() +{ + Q_Q(QPrintDialog); + + pageSize = QPrinter::A4; + + // paper orientation + orientationCombo = q->findChild("orientationCombo"); + orientation = QPrinter::Portrait; + QObject::connect(orientationCombo, SIGNAL(activated(int)), + q, SLOT(_q_orientSelected(int))); + + // paper size + sizeCombo = q->findChild("sizeCombo"); + + int n; + for(n=0; nfindChild("copies"); + QObject::connect(copies, SIGNAL(valueChanged(int)), + q, SLOT(_q_setNumCopies(int))); + + // print range + rangeCombo = q->findChild("rangeCombo"); + rangeCombo->addItem(QPrintDialog::tr("Print all"), QPrintDialog::AllPages); + rangeCombo->addItem(QPrintDialog::tr("Print selection"), QPrintDialog::Selection); + rangeCombo->addItem(QPrintDialog::tr("Print range"), QPrintDialog::PageRange); + rangeCombo->addItem(QPrintDialog::tr("Print current page"), QPrintDialog::CurrentPage); + QObject::connect(rangeCombo, SIGNAL(activated(int)), + q, SLOT(_q_printRangeSelected(int))); + + // page range + firstPage = q->findChild("firstPage"); + firstPage->setRange(1, 9999); + firstPage->setValue(1); + QObject::connect(firstPage, SIGNAL(valueChanged(int)), + q, SLOT(_q_setFirstPage(int))); + + lastPage = q->findChild("lastPage"); + lastPage->setRange(1, 9999); + lastPage->setValue(1); + QObject::connect(lastPage, SIGNAL(valueChanged(int)), + q, SLOT(_q_setLastPage(int))); + + // print order + pageOrderCombo = q->findChild("pageOrderCombo"); + QObject::connect(pageOrderCombo, SIGNAL(activated(int)), + q, SLOT(_q_pageOrderSelected(int))); +} + +bool QPrintDialog::eventFilter(QObject *o, QEvent *e) +{ + Q_UNUSED(o); + + Q_D(QPrintDialog); + switch (e->type()){ + case QEvent::KeyPress: + switch (static_cast(e)->key()) { + case Qt::Key_Back: + d->_q_okClicked(); + return true; + } + break; + default: + break; + } + return false; +} + +QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), printer, parent) +{ + d_func()->init(); +} + +QPrintDialog::QPrintDialog(QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), 0, parent) +{ + d_func()->init(); +} + +QPrintDialog::~QPrintDialog() +{ +} + +void QPrintDialogPrivate::setPrinter(QPrinter *p, bool pickUpSettings) +{ + Q_Q(QPrintDialog); + printer = p; + + if (p && pickUpSettings) { + // top to bottom in the old dialog. + // printer or file + outputToFile = !p->outputFileName().isEmpty() && q->isOptionEnabled(QPrintDialog::PrintToFile); + if (outputToFile) + printToFileButton->setChecked(true); + else + printToPrinterButton->setChecked(true); + fileName->setEnabled(outputToFile); + + // file name + if (q->isOptionEnabled(QPrintDialog::PrintToFile)) { + fileName->setText(p->outputFileName()); + fileName->setModified(!fileName->text().isEmpty()); + } else { + printToFileButton->setEnabled(false); + } + + // orientation + orientationCombo->setCurrentIndex((int)p->orientation()); + _q_orientSelected(p->orientation()); + + // page size + int n = 0; + while (n < QPrinter::NPaperSize && + indexToPaperSize[n] != p->pageSize()) + n++; + sizeCombo->setCurrentIndex(n); + _q_paperSizeSelected(n); + + // page order + pageOrder2 = p->pageOrder(); + pageOrderCombo->setCurrentIndex((int)pageOrder2); + + // color mode + colorMode2 = p->colorMode(); + if (colorMode2 == QPrinter::Color) + printColor->setChecked(true); + else + printGray->setChecked(true); + + // number of copies + copies->setValue(p->copyCount()); + _q_setNumCopies(p->copyCount()); + } + + if (p) { + if (!q->isOptionEnabled(QPrintDialog::PrintSelection) + && rangeCombo->findData(QPrintDialog::Selection) > 0) + rangeCombo->removeItem(rangeCombo->findData(QPrintDialog::Selection)); + if (!q->isOptionEnabled(QPrintDialog::PrintPageRange) + && rangeCombo->findData(QPrintDialog::PageRange) > 0) + rangeCombo->removeItem(rangeCombo->findData(QPrintDialog::PageRange)); + if (!q->isOptionEnabled(QPrintDialog::PrintCurrentPage) + && rangeCombo->findData(QPrintDialog::CurrentPage) > 0) + rangeCombo->removeItem(rangeCombo->findData(QPrintDialog::CurrentPage)); + + switch (q->printRange()) { + case QPrintDialog::AllPages: + rangeCombo->setCurrentIndex((int)(QPrintDialog::AllPages)); + break; + case QPrintDialog::Selection: + rangeCombo->setCurrentIndex((int)(QPrintDialog::Selection)); + break; + case QPrintDialog::PageRange: + rangeCombo->setCurrentIndex((int)(QPrintDialog::PageRange)); + break; + case QPrintDialog::CurrentPage: + rangeCombo->setCurrentIndex((int)(QPrintDialog::CurrentPage)); + break; + } + } + + if (p && q->maxPage()) { + int from = q->minPage(); + int to = q->maxPage(); + if (q->printRange() == QPrintDialog::PageRange) { + from = q->fromPage(); + to = q->toPage(); + } + firstPage->setRange(q->minPage(), to); + lastPage->setRange(from, q->maxPage()); + firstPage->setValue(from); + lastPage->setValue(to); + } +} + +int QPrintDialog::exec() +{ + Q_D(QPrintDialog); + d->setPrinter(d->printer, true); + return QDialog::exec(); +} + +void QPrintDialogPrivate::init() +{ + Q_Q(QPrintDialog); + numCopies = 1; + + if (_qt_print_dialog_creator) + (*_qt_print_dialog_creator)(q); + + setupDestination(); + setupPrinterSettings(); + setupPaper(); + setupOptions(); + + setPrinter(printer, true); + + q->installEventFilter(q); +} + +void QPrintDialog::setVisible(bool visible) +{ + QAbstractPrintDialog::setVisible(visible); +} + +QT_END_NAMESPACE + +#include "moc_qprintdialog.cpp" +#include "qrc_qprintdialog.cpp" + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qprintdialog_unix.cpp b/src/gui/dialogs/qprintdialog_unix.cpp new file mode 100644 index 0000000000..5b5c49d158 --- /dev/null +++ b/src/gui/dialogs/qprintdialog_unix.cpp @@ -0,0 +1,1309 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#ifndef QT_NO_PRINTDIALOG + +#include "private/qabstractprintdialog_p.h" +#include +#include "qprintdialog.h" +#include "qfiledialog.h" +#include +#include +#include +#include +#include + +#include + +#include "qfscompleter_p.h" +#include "ui_qprintpropertieswidget.h" +#include "ui_qprintsettingsoutput.h" +#include "ui_qprintwidget.h" + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +# include +# include +# include +#else +# include +#endif + +#include + +QT_BEGIN_NAMESPACE + +class QOptionTreeItem; +class QPPDOptionsModel; + +class QPrintPropertiesDialog : public QDialog +{ + Q_OBJECT +public: + QPrintPropertiesDialog(QAbstractPrintDialog *parent = 0); + ~QPrintPropertiesDialog(); + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + void setCups(QCUPSSupport *cups) { m_cups = cups; } + void addItemToOptions(QOptionTreeItem *parent, QList& options, QList& markedOptions) const; +#endif + + void selectPrinter(); + void selectPdfPsPrinter(const QPrinter *p); + + /// copy printer properties to the widget + void applyPrinterProperties(QPrinter *p); + void setupPrinter() const; + +protected: + void showEvent(QShowEvent* event); + +private: + Ui::QPrintPropertiesWidget widget; + QDialogButtonBox *m_buttons; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + QCUPSSupport *m_cups; + QPPDOptionsModel *m_cupsOptionsModel; +#endif +}; + +class QPrintDialogPrivate : public QAbstractPrintDialogPrivate +{ + Q_DECLARE_PUBLIC(QPrintDialog) + Q_DECLARE_TR_FUNCTIONS(QPrintDialog) +public: + QPrintDialogPrivate(); + ~QPrintDialogPrivate(); + + void init(); + /// copy printer properties to the widget + void applyPrinterProperties(QPrinter *p); + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + void selectPrinter(QCUPSSupport *cups); +#endif + + void _q_chbPrintLastFirstToggled(bool); +#ifndef QT_NO_MESSAGEBOX + void _q_checkFields(); +#endif + void _q_collapseOrExpandDialog(); + + void setupPrinter(); + void updateWidgets(); + + virtual void setTabs(const QList &tabs); + + Ui::QPrintSettingsOutput options; + QUnixPrintWidget *top; + QWidget *bottom; + QDialogButtonBox *buttons; + QPushButton *collapseButton; +}; + +#if defined (Q_OS_UNIX) +class QUnixPrintWidgetPrivate +{ +public: + QUnixPrintWidgetPrivate(QUnixPrintWidget *q); + ~QUnixPrintWidgetPrivate(); + + /// copy printer properties to the widget + void applyPrinterProperties(QPrinter *p); + bool checkFields(); + void setupPrinter(); + void setOptionsPane(QPrintDialogPrivate *pane); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + void setCupsProperties(); +#endif + +// slots + void _q_printerChanged(int index); + void _q_btnPropertiesClicked(); + void _q_btnBrowseClicked(); + + QUnixPrintWidget * const parent; + QPrintPropertiesDialog *propertiesDialog; + Ui::QPrintWidget widget; + QAbstractPrintDialog * q; + QPrinter *printer; + QList lprPrinters; + void updateWidget(); + +private: + QPrintDialogPrivate *optionsPane; + bool filePrintersAdded; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + QCUPSSupport* cups; + int cupsPrinterCount; + const cups_dest_t* cupsPrinters; + const ppd_file_t* cupsPPD; +#endif +}; +#endif + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +class QOptionTreeItem +{ +public: + enum ItemType { Root, Group, Option, Choice }; + + QOptionTreeItem(ItemType t, int i, const void* p, const char* desc, QOptionTreeItem* pi) + : type(t), + index(i), + ptr(p), + description(desc), + selected(-1), + selDescription(0), + parentItem(pi) {} + + ~QOptionTreeItem() { + while (!childItems.isEmpty()) + delete childItems.takeFirst(); + } + + ItemType type; + int index; + const void* ptr; + const char* description; + int selected; + const char* selDescription; + QOptionTreeItem* parentItem; + QList childItems; +}; + +class QPPDOptionsModel : public QAbstractItemModel +{ + friend class QPPDOptionsEditor; +public: + QPPDOptionsModel(QCUPSSupport *cups, QObject *parent = 0); + ~QPPDOptionsModel(); + + int columnCount(const QModelIndex& parent = QModelIndex()) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + Qt::ItemFlags flags(const QModelIndex& index) const; + QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + + QOptionTreeItem* rootItem; + QCUPSSupport *cups; + const ppd_file_t* ppd; + void parseItems(); + void parseGroups(QOptionTreeItem* parent); + void parseOptions(QOptionTreeItem* parent); + void parseChoices(QOptionTreeItem* parent); +}; + +class QPPDOptionsEditor : public QStyledItemDelegate +{ + Q_OBJECT +public: + QPPDOptionsEditor(QObject* parent = 0) : QStyledItemDelegate(parent) {} + ~QPPDOptionsEditor() {} + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void setEditorData(QWidget* editor, const QModelIndex& index) const; + void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; + +private slots: + void cbChanged(int index); + +}; + +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +QPrintPropertiesDialog::QPrintPropertiesDialog(QAbstractPrintDialog *parent) + : QDialog(parent) +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + , m_cups(0), m_cupsOptionsModel(0) +#endif +{ + QVBoxLayout *lay = new QVBoxLayout(this); + this->setLayout(lay); + QWidget *content = new QWidget(this); + widget.setupUi(content); + m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); + lay->addWidget(content); + lay->addWidget(m_buttons); + + connect(m_buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept())); + connect(m_buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); +} + +QPrintPropertiesDialog::~QPrintPropertiesDialog() +{ +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + delete m_cupsOptionsModel; +#else + delete widget.cupsPropertiesPage; +#endif +} + +void QPrintPropertiesDialog::applyPrinterProperties(QPrinter *p) +{ + widget.pageSetup->setPrinter(p); +} + +void QPrintPropertiesDialog::setupPrinter() const +{ + widget.pageSetup->setupPrinter(); + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + QPPDOptionsModel* model = static_cast(widget.treeView->model()); + if (model) { + QOptionTreeItem* rootItem = model->rootItem; + QList options; + QList markedOptions; + + addItemToOptions(rootItem, options, markedOptions); + model->cups->saveOptions(options, markedOptions); + } +#endif +} + +void QPrintPropertiesDialog::selectPrinter() +{ +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + widget.pageSetup->selectPrinter(m_cups); + widget.treeView->setModel(0); + if (m_cups && QCUPSSupport::isAvailable()) { + + if (m_cupsOptionsModel == 0) { + m_cupsOptionsModel = new QPPDOptionsModel(m_cups); + + widget.treeView->setItemDelegate(new QPPDOptionsEditor(this)); + } else { + // update the model + m_cupsOptionsModel->parseItems(); + } + + if (m_cupsOptionsModel->rowCount() > 0) { + widget.treeView->setModel(m_cupsOptionsModel); + + for (int i = 0; i < m_cupsOptionsModel->rowCount(); ++i) + widget.treeView->expand(m_cupsOptionsModel->index(i,0)); + + widget.tabs->setTabEnabled(1, true); // enable the advanced tab + } else { + widget.tabs->setTabEnabled(1, false); + } + + } else +#endif + { + widget.cupsPropertiesPage->setEnabled(false); + widget.pageSetup->selectPrinter(0); + } +} + +void QPrintPropertiesDialog::selectPdfPsPrinter(const QPrinter *p) +{ + widget.treeView->setModel(0); + widget.pageSetup->selectPdfPsPrinter(p); + widget.tabs->setTabEnabled(1, false); // disable the advanced tab +} + +void QPrintPropertiesDialog::showEvent(QShowEvent* event) +{ + widget.treeView->resizeColumnToContents(0); + event->accept(); +} + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +void QPrintPropertiesDialog::addItemToOptions(QOptionTreeItem *parent, QList& options, QList& markedOptions) const +{ + for (int i = 0; i < parent->childItems.count(); ++i) { + QOptionTreeItem *itm = parent->childItems.at(i); + if (itm->type == QOptionTreeItem::Option) { + const ppd_option_t* opt = reinterpret_cast(itm->ptr); + options << opt; + if (qstrcmp(opt->defchoice, opt->choices[itm->selected].choice) != 0) { + markedOptions << opt->keyword << opt->choices[itm->selected].choice; + } + } else { + addItemToOptions(itm, options, markedOptions); + } + } +} +#endif + +QPrintDialogPrivate::QPrintDialogPrivate() + : top(0), bottom(0), buttons(0), collapseButton(0) +{ +} + +QPrintDialogPrivate::~QPrintDialogPrivate() +{ +} + +void QPrintDialogPrivate::init() +{ + Q_Q(QPrintDialog); + + top = new QUnixPrintWidget(0, q); + bottom = new QWidget(q); + options.setupUi(bottom); + options.color->setIconSize(QSize(32, 32)); + options.color->setIcon(QIcon(QLatin1String(":/trolltech/dialogs/qprintdialog/images/status-color.png"))); + options.grayscale->setIconSize(QSize(32, 32)); + options.grayscale->setIcon(QIcon(QLatin1String(":/trolltech/dialogs/qprintdialog/images/status-gray-scale.png"))); + top->d->setOptionsPane(this); + + buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q); + collapseButton = new QPushButton(QPrintDialog::tr("&Options >>"), buttons); + buttons->addButton(collapseButton, QDialogButtonBox::ResetRole); + bottom->setVisible(false); + + QPushButton *printButton = buttons->button(QDialogButtonBox::Ok); + printButton->setText(QPrintDialog::tr("&Print")); + printButton->setDefault(true); + + QVBoxLayout *lay = new QVBoxLayout(q); + q->setLayout(lay); + lay->addWidget(top); + lay->addWidget(bottom); + lay->addWidget(buttons); + + QPrinter* p = q->printer(); + + applyPrinterProperties(p); + +#ifdef QT_NO_MESSAGEBOX + QObject::connect(buttons, SIGNAL(accepted()), q, SLOT(accept())); +#else + QObject::connect(buttons, SIGNAL(accepted()), q, SLOT(_q_checkFields())); +#endif + QObject::connect(buttons, SIGNAL(rejected()), q, SLOT(reject())); + + QObject::connect(options.reverse, SIGNAL(toggled(bool)), + q, SLOT(_q_chbPrintLastFirstToggled(bool))); + + QObject::connect(collapseButton, SIGNAL(released()), q, SLOT(_q_collapseOrExpandDialog())); +} + +void QPrintDialogPrivate::applyPrinterProperties(QPrinter *p) +{ + if (p->colorMode() == QPrinter::Color) + options.color->setChecked(true); + else + options.grayscale->setChecked(true); + + switch(p->duplex()) { + case QPrinter::DuplexNone: + options.noDuplex->setChecked(true); break; + case QPrinter::DuplexLongSide: + case QPrinter::DuplexAuto: + options.duplexLong->setChecked(true); break; + case QPrinter::DuplexShortSide: + options.duplexShort->setChecked(true); break; + } + options.copies->setValue(p->copyCount()); + options.collate->setChecked(p->collateCopies()); + options.reverse->setChecked(p->pageOrder() == QPrinter::LastPageFirst); + top->d->applyPrinterProperties(p); +} + +void QPrintDialogPrivate::_q_chbPrintLastFirstToggled(bool checked) +{ + Q_Q(QPrintDialog); + if (checked) + q->printer()->setPageOrder(QPrinter::LastPageFirst); + else + q->printer()->setPageOrder(QPrinter::FirstPageFirst); +} + +void QPrintDialogPrivate::_q_collapseOrExpandDialog() +{ + int collapseHeight = 0; + Q_Q(QPrintDialog); + QWidget *widgetToHide = bottom; + if (widgetToHide->isVisible()) { + collapseButton->setText(QPrintDialog::tr("&Options >>")); + collapseHeight = widgetToHide->y() + widgetToHide->height() - (top->y() + top->height()); + } + else + collapseButton->setText(QPrintDialog::tr("&Options <<")); + widgetToHide->setVisible(! widgetToHide->isVisible()); + if (! widgetToHide->isVisible()) { // make it shrink + q->layout()->activate(); + q->resize( QSize(q->width(), q->height() - collapseHeight) ); + } +} + +#ifndef QT_NO_MESSAGEBOX +void QPrintDialogPrivate::_q_checkFields() +{ + Q_Q(QPrintDialog); + if (top->d->checkFields()) + q->accept(); +} +#endif // QT_NO_MESSAGEBOX + +void QPrintDialogPrivate::setupPrinter() +{ + Q_Q(QPrintDialog); + QPrinter* p = q->printer(); + + if (options.duplex->isEnabled()) { + if (options.noDuplex->isChecked()) + p->setDuplex(QPrinter::DuplexNone); + else if (options.duplexLong->isChecked()) + p->setDuplex(QPrinter::DuplexLongSide); + else + p->setDuplex(QPrinter::DuplexShortSide); + } + + p->setColorMode( options.color->isChecked() ? QPrinter::Color : QPrinter::GrayScale ); + + // print range + if (options.printAll->isChecked()) { + p->setPrintRange(QPrinter::AllPages); + p->setFromTo(0,0); + } else if (options.printSelection->isChecked()) { + p->setPrintRange(QPrinter::Selection); + p->setFromTo(0,0); + } else if (options.printCurrentPage->isChecked()) { + p->setPrintRange(QPrinter::CurrentPage); + p->setFromTo(0,0); + } else if (options.printRange->isChecked()) { + p->setPrintRange(QPrinter::PageRange); + p->setFromTo(options.from->value(), qMax(options.from->value(), options.to->value())); + } + + // copies + p->setCopyCount(options.copies->value()); + p->setCollateCopies(options.collate->isChecked()); + + top->d->setupPrinter(); +} + +void QPrintDialogPrivate::updateWidgets() +{ + Q_Q(QPrintDialog); + options.gbPrintRange->setVisible(q->isOptionEnabled(QPrintDialog::PrintPageRange) || + q->isOptionEnabled(QPrintDialog::PrintSelection) || + q->isOptionEnabled(QPrintDialog::PrintCurrentPage)); + + options.printRange->setEnabled(q->isOptionEnabled(QPrintDialog::PrintPageRange)); + options.printSelection->setVisible(q->isOptionEnabled(QPrintDialog::PrintSelection)); + options.printCurrentPage->setVisible(q->isOptionEnabled(QPrintDialog::PrintCurrentPage)); + options.collate->setVisible(q->isOptionEnabled(QPrintDialog::PrintCollateCopies)); + + switch (q->printRange()) { + case QPrintDialog::AllPages: + options.printAll->setChecked(true); + break; + case QPrintDialog::Selection: + options.printSelection->setChecked(true); + break; + case QPrintDialog::PageRange: + options.printRange->setChecked(true); + break; + case QPrintDialog::CurrentPage: + if (q->isOptionEnabled(QPrintDialog::PrintCurrentPage)) + options.printCurrentPage->setChecked(true); + break; + default: + break; + } + const int minPage = qMax(1, qMin(q->minPage() , q->maxPage())); + const int maxPage = qMax(1, q->maxPage() == INT_MAX ? 9999 : q->maxPage()); + + options.from->setMinimum(minPage); + options.to->setMinimum(minPage); + options.from->setMaximum(maxPage); + options.to->setMaximum(maxPage); + + options.from->setValue(q->fromPage()); + options.to->setValue(q->toPage()); + top->d->updateWidget(); +} + +void QPrintDialogPrivate::setTabs(const QList &tabWidgets) +{ + while(options.tabs->count() > 2) + delete options.tabs->widget(2); + + QList::ConstIterator iter = tabWidgets.begin(); + while(iter != tabWidgets.constEnd()) { + QWidget *tab = *iter; + options.tabs->addTab(tab, tab->windowTitle()); + ++iter; + } +} + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +void QPrintDialogPrivate::selectPrinter(QCUPSSupport *cups) +{ + options.duplex->setEnabled(cups && cups->ppdOption("Duplex")); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// + +QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), printer, parent) +{ + Q_D(QPrintDialog); + d->init(); +} + +/*! + Constructs a print dialog with the given \a parent. +*/ +QPrintDialog::QPrintDialog(QWidget *parent) + : QAbstractPrintDialog(*(new QPrintDialogPrivate), 0, parent) +{ + Q_D(QPrintDialog); + d->init(); +} + +QPrintDialog::~QPrintDialog() +{ +} + +void QPrintDialog::setVisible(bool visible) +{ + Q_D(QPrintDialog); + + if (visible) + d->updateWidgets(); + + QAbstractPrintDialog::setVisible(visible); +} + +int QPrintDialog::exec() +{ + return QDialog::exec(); +} + +void QPrintDialog::accept() +{ + Q_D(QPrintDialog); + d->setupPrinter(); + QDialog::accept(); +} + +#ifdef QT3_SUPPORT +QPrinter *QPrintDialog::printer() const +{ + Q_D(const QPrintDialog); + return d->printer; +} + +void QPrintDialog::setPrinter(QPrinter *printer, bool pickupSettings) +{ + if (!printer) + return; + + Q_D(QPrintDialog); + d->printer = printer; + + if (pickupSettings) + d->applyPrinterProperties(printer); +} + +void QPrintDialog::addButton(QPushButton *button) +{ + Q_D(QPrintDialog); + d->buttons->addButton(button, QDialogButtonBox::HelpRole); +} +#endif // QT3_SUPPORT + +#if defined (Q_OS_UNIX) + +/*! \internal +*/ +QUnixPrintWidgetPrivate::QUnixPrintWidgetPrivate(QUnixPrintWidget *p) + : parent(p), propertiesDialog(0), printer(0), optionsPane(0), filePrintersAdded(false) +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + , cups(0), cupsPrinterCount(0), cupsPrinters(0), cupsPPD(0) +#endif +{ + q = 0; + if (parent) + q = qobject_cast (parent->parent()); + + widget.setupUi(parent); + + int currentPrinterIndex = 0; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + cups = new QCUPSSupport; + if (QCUPSSupport::isAvailable()) { + cupsPPD = cups->currentPPD(); + cupsPrinterCount = cups->availablePrintersCount(); + cupsPrinters = cups->availablePrinters(); + + for (int i = 0; i < cupsPrinterCount; ++i) { + QString printerName(QString::fromLocal8Bit(cupsPrinters[i].name)); + if (cupsPrinters[i].instance) + printerName += QLatin1Char('/') + QString::fromLocal8Bit(cupsPrinters[i].instance); + + widget.printers->addItem(printerName); + if (cupsPrinters[i].is_default) + widget.printers->setCurrentIndex(i); + } + // the model depends on valid ppd. so before enabling the + // properties button we make sure the ppd is in fact valid. + if (cupsPrinterCount && cups->currentPPD()) { + widget.properties->setEnabled(true); + } + currentPrinterIndex = cups->currentPrinterIndex(); + } else { +#endif + currentPrinterIndex = qt_getLprPrinters(lprPrinters); + // populating printer combo + QList::const_iterator i = lprPrinters.constBegin(); + for(; i != lprPrinters.constEnd(); ++i) + widget.printers->addItem((*i).name); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + } +#endif + +#if !defined(QT_NO_FILESYSTEMMODEL) && !defined(QT_NO_COMPLETER) + QFileSystemModel *fsm = new QFileSystemModel(widget.filename); + fsm->setRootPath(QDir::homePath()); + widget.filename->setCompleter(new QCompleter(fsm, widget.filename)); +#endif + _q_printerChanged(currentPrinterIndex); + + QObject::connect(widget.printers, SIGNAL(currentIndexChanged(int)), + parent, SLOT(_q_printerChanged(int))); + QObject::connect(widget.fileBrowser, SIGNAL(clicked()), parent, SLOT(_q_btnBrowseClicked())); + QObject::connect(widget.properties, SIGNAL(clicked()), parent, SLOT(_q_btnPropertiesClicked())); + + // disable features that QPrinter does not yet support. + widget.preview->setVisible(false); +} + +void QUnixPrintWidgetPrivate::updateWidget() +{ + const bool printToFile = q == 0 || q->isOptionEnabled(QPrintDialog::PrintToFile); + if (printToFile && !filePrintersAdded) { + if (widget.printers->count()) + widget.printers->insertSeparator(widget.printers->count()); + widget.printers->addItem(QPrintDialog::tr("Print to File (PDF)")); + widget.printers->addItem(QPrintDialog::tr("Print to File (Postscript)")); + filePrintersAdded = true; + } + if (!printToFile && filePrintersAdded) { + widget.printers->removeItem(widget.printers->count()-1); + widget.printers->removeItem(widget.printers->count()-1); + if (widget.printers->count()) + widget.printers->removeItem(widget.printers->count()-1); // remove separator + filePrintersAdded = false; + } + if (printer && filePrintersAdded && (printer->outputFormat() != QPrinter::NativeFormat + || printer->printerName().isEmpty())) + { + if (printer->outputFormat() == QPrinter::PdfFormat) + widget.printers->setCurrentIndex(widget.printers->count() - 2); + else if (printer->outputFormat() == QPrinter::PostScriptFormat) + widget.printers->setCurrentIndex(widget.printers->count() - 1); + widget.filename->setEnabled(true); + widget.lOutput->setEnabled(true); + } + + widget.filename->setVisible(printToFile); + widget.lOutput->setVisible(printToFile); + widget.fileBrowser->setVisible(printToFile); + + widget.properties->setVisible(q->isOptionEnabled(QAbstractPrintDialog::PrintShowPageSize)); +} + +QUnixPrintWidgetPrivate::~QUnixPrintWidgetPrivate() +{ +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + delete cups; +#endif +} + +void QUnixPrintWidgetPrivate::_q_printerChanged(int index) +{ + if (index < 0) + return; + const int printerCount = widget.printers->count(); + widget.filename->setEnabled(false); + widget.lOutput->setEnabled(false); + + if (filePrintersAdded) { + Q_ASSERT(index != printerCount - 3); // separator + if (index > printerCount - 3) { // PDF or postscript + bool pdfPrinter = (index == printerCount - 2); + widget.location->setText(QPrintDialog::tr("Local file")); + widget.type->setText(QPrintDialog::tr("Write %1 file").arg(pdfPrinter ? QString::fromLatin1("PDF") + : QString::fromLatin1("PostScript"))); + widget.properties->setEnabled(true); + widget.filename->setEnabled(true); + QString filename = widget.filename->text(); + QString suffix = QFileInfo(filename).suffix(); + if (pdfPrinter && suffix == QLatin1String("ps")) + filename = filename.replace(QLatin1String(".ps"), QLatin1String(".pdf")); + if (!pdfPrinter && suffix == QLatin1String("pdf")) + filename = filename.replace(QLatin1String(".pdf"), QLatin1String(".ps")); + widget.filename->setText(filename); + widget.lOutput->setEnabled(true); + if (propertiesDialog) + propertiesDialog->selectPdfPsPrinter(printer); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (optionsPane) + optionsPane->selectPrinter(0); +#endif + return; + } + } + + widget.location->setText(QString()); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) { + cups->setCurrentPrinter(index); + + const cups_option_t *opt = cups->printerOption(QString::fromLatin1("printer-location")); + QString location; + if (opt) + location = QString::fromLocal8Bit(opt->value); + widget.location->setText(location); + + cupsPPD = cups->currentPPD(); + // set printer type line + QString type; + if (cupsPPD) + type = QString::fromLocal8Bit(cupsPPD->manufacturer) + QLatin1String(" - ") + QString::fromLocal8Bit(cupsPPD->modelname); + widget.type->setText(type); + if (propertiesDialog) + propertiesDialog->selectPrinter(); + if (optionsPane) + optionsPane->selectPrinter(cups); + } else { + if (optionsPane) + optionsPane->selectPrinter(0); +#endif + if (lprPrinters.count() > 0) { + QString type = lprPrinters.at(index).name + QLatin1Char('@') + lprPrinters.at(index).host; + if (!lprPrinters.at(index).comment.isEmpty()) + type += QLatin1String(", ") + lprPrinters.at(index).comment; + widget.type->setText(type); + if (propertiesDialog) + propertiesDialog->selectPrinter(); + } +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + } +#endif +} + +void QUnixPrintWidgetPrivate::setOptionsPane(QPrintDialogPrivate *pane) +{ + optionsPane = pane; + if (optionsPane) + _q_printerChanged(widget.printers->currentIndex()); +} + +void QUnixPrintWidgetPrivate::_q_btnBrowseClicked() +{ + QString filename = widget.filename->text(); +#ifndef QT_NO_FILEDIALOG + filename = QFileDialog::getSaveFileName(parent, QPrintDialog::tr("Print To File ..."), filename, + QString(), 0, QFileDialog::DontConfirmOverwrite); +#else + filename.clear(); +#endif + if (!filename.isEmpty()) { + widget.filename->setText(filename); + if (filename.endsWith(QString::fromLatin1(".ps"), Qt::CaseInsensitive)) + widget.printers->setCurrentIndex(widget.printers->count() - 1); // the postscript one + else if (filename.endsWith(QString::fromLatin1(".pdf"), Qt::CaseInsensitive)) + widget.printers->setCurrentIndex(widget.printers->count() - 2); // the pdf one + else if (widget.printers->currentIndex() != widget.printers->count() - 1) // if ps is not selected, pdf is default + widget.printers->setCurrentIndex(widget.printers->count() - 2); // the pdf one + } +} + +void QUnixPrintWidgetPrivate::applyPrinterProperties(QPrinter *p) +{ + if (p == 0) + return; + printer = p; + if (p->outputFileName().isEmpty()) { + QString home = QString::fromLocal8Bit(qgetenv("HOME").constData()); + QString cur = QDir::currentPath(); + if (home.at(home.length()-1) != QLatin1Char('/')) + home += QLatin1Char('/'); + if (cur.at(cur.length()-1) != QLatin1Char('/')) + cur += QLatin1Char('/'); + if (cur.left(home.length()) != home) + cur = home; +#ifdef Q_WS_X11 + if (p->docName().isEmpty()) { + if (p->outputFormat() == QPrinter::PostScriptFormat) + cur += QLatin1String("print.ps"); + else + cur += QLatin1String("print.pdf"); + } else { + QRegExp re(QString::fromLatin1("(.*)\\.\\S+")); + if (re.exactMatch(p->docName())) + cur += re.cap(1); + else + cur += p->docName(); + if (p->outputFormat() == QPrinter::PostScriptFormat) + cur += QLatin1String(".ps"); + else + cur += QLatin1String(".pdf"); + } +#endif + widget.filename->setText(cur); + } + else + widget.filename->setText( p->outputFileName() ); + QString printer = p->printerName(); + if (!printer.isEmpty()) { + for (int i = 0; i < widget.printers->count(); ++i) { + if (widget.printers->itemText(i) == printer) { + widget.printers->setCurrentIndex(i); + break; + } + } + } + // PDF and PS printers are not added to the dialog yet, we'll handle those cases in QUnixPrintWidgetPrivate::updateWidget + + if (propertiesDialog) + propertiesDialog->applyPrinterProperties(p); +} + +#ifndef QT_NO_MESSAGEBOX +bool QUnixPrintWidgetPrivate::checkFields() +{ + if (widget.filename->isEnabled()) { + QString file = widget.filename->text(); + QFile f(file); + QFileInfo fi(f); + bool exists = fi.exists(); + bool opened = false; + if (exists && fi.isDir()) { + QMessageBox::warning(q, q->windowTitle(), + QPrintDialog::tr("%1 is a directory.\nPlease choose a different file name.").arg(file)); + return false; + } else if ((exists && !fi.isWritable()) || !(opened = f.open(QFile::Append))) { + QMessageBox::warning(q, q->windowTitle(), + QPrintDialog::tr("File %1 is not writable.\nPlease choose a different file name.").arg(file)); + return false; + } else if (exists) { + int ret = QMessageBox::question(q, q->windowTitle(), + QPrintDialog::tr("%1 already exists.\nDo you want to overwrite it?").arg(file), + QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + if (ret == QMessageBox::No) + return false; + } + if (opened) { + f.close(); + if (!exists) + f.remove(); + } + } + + // Every test passed. Accept the dialog. + return true; +} +#endif // QT_NO_MESSAGEBOX + +void QUnixPrintWidgetPrivate::_q_btnPropertiesClicked() +{ + if (!propertiesDialog) { + propertiesDialog = new QPrintPropertiesDialog(q); + propertiesDialog->setResult(QDialog::Rejected); + } + + if (propertiesDialog->result() == QDialog::Rejected) { +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + propertiesDialog->setCups(cups); +#endif + propertiesDialog->applyPrinterProperties(q->printer()); + + if (q->isOptionEnabled(QPrintDialog::PrintToFile) + && (widget.printers->currentIndex() > widget.printers->count() - 3)) // PDF or postscript + propertiesDialog->selectPdfPsPrinter(q->printer()); + else + propertiesDialog->selectPrinter(); + } + propertiesDialog->exec(); +} + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +void QUnixPrintWidgetPrivate::setCupsProperties() +{ + if (cups && QCUPSSupport::isAvailable() && cups->pageSizes()) { + QPrintEngine *engine = printer->printEngine(); + const ppd_option_t* pageSizes = cups->pageSizes(); + QByteArray cupsPageSize; + for (int i = 0; i < pageSizes->num_choices; ++i) { + if (static_cast(pageSizes->choices[i].marked) == 1) + cupsPageSize = pageSizes->choices[i].choice; + } + engine->setProperty(PPK_CupsStringPageSize, QString::fromLatin1(cupsPageSize)); + engine->setProperty(PPK_CupsOptions, cups->options()); + + QRect pageRect = cups->pageRect(cupsPageSize); + engine->setProperty(PPK_CupsPageRect, pageRect); + + QRect paperRect = cups->paperRect(cupsPageSize); + engine->setProperty(PPK_CupsPaperRect, paperRect); + + for (int ps = 0; ps < QPrinter::NPaperSize; ++ps) { + QPdf::PaperSize size = QPdf::paperSize(QPrinter::PaperSize(ps)); + if (size.width == paperRect.width() && size.height == paperRect.height()) + printer->setPaperSize(static_cast(ps)); + } + } +} +#endif + +void QUnixPrintWidgetPrivate::setupPrinter() +{ + const int printerCount = widget.printers->count(); + const int index = widget.printers->currentIndex(); + + if (filePrintersAdded && index > printerCount - 3) { // PDF or postscript + printer->setPrinterName(QString()); + Q_ASSERT(index != printerCount - 3); // separator + if (index == printerCount - 2) + printer->setOutputFormat(QPrinter::PdfFormat); + else + printer->setOutputFormat(QPrinter::PostScriptFormat); + QString path = widget.filename->text(); + if (QDir::isRelativePath(path)) + path = QDir::homePath() + QDir::separator() + path; + printer->setOutputFileName(path); + } + else { + printer->setPrinterName(widget.printers->currentText()); + printer->setOutputFileName(QString()); + } + + if (propertiesDialog && propertiesDialog->result() == QDialog::Accepted) + propertiesDialog->setupPrinter(); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (!propertiesDialog) + setCupsProperties(); +#endif +} + + +/*! \internal +*/ +QUnixPrintWidget::QUnixPrintWidget(QPrinter *printer, QWidget *parent) + : QWidget(parent), d(new QUnixPrintWidgetPrivate(this)) +{ + d->applyPrinterProperties(printer); +} + +/*! \internal +*/ +QUnixPrintWidget::~QUnixPrintWidget() +{ + delete d; +} + +/*! \internal + + Updates the printer with the states held in the QUnixPrintWidget. +*/ +void QUnixPrintWidget::updatePrinter() +{ + d->setupPrinter(); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + +QPPDOptionsModel::QPPDOptionsModel(QCUPSSupport *c, QObject *parent) + : QAbstractItemModel(parent), rootItem(0), cups(c), ppd(c->currentPPD()) +{ + parseItems(); +} + +QPPDOptionsModel::~QPPDOptionsModel() +{ +} + +int QPPDOptionsModel::columnCount(const QModelIndex&) const +{ + return 2; +} + +int QPPDOptionsModel::rowCount(const QModelIndex& parent) const +{ + QOptionTreeItem* itm; + if (!parent.isValid()) + itm = rootItem; + else + itm = reinterpret_cast(parent.internalPointer()); + + if (itm->type == QOptionTreeItem::Option) + return 0; + + return itm->childItems.count(); +} + +QVariant QPPDOptionsModel::data(const QModelIndex& index, int role) const +{ + switch(role) { + case Qt::FontRole: { + QOptionTreeItem* itm = reinterpret_cast(index.internalPointer()); + if (itm && itm->type == QOptionTreeItem::Group){ + QFont font = QApplication::font(); + font.setBold(true); + return QVariant(font); + } + return QVariant(); + } + break; + + case Qt::DisplayRole: { + QOptionTreeItem* itm; + if (!index.isValid()) + itm = rootItem; + else + itm = reinterpret_cast(index.internalPointer()); + + if (index.column() == 0) + return cups->unicodeString(itm->description); + else if (itm->type == QOptionTreeItem::Option && itm->selected > -1) + return cups->unicodeString(itm->selDescription); + else + return QVariant(); + } + break; + + default: + return QVariant(); + } + if (role != Qt::DisplayRole) + return QVariant(); +} + +QModelIndex QPPDOptionsModel::index(int row, int column, const QModelIndex& parent) const +{ + QOptionTreeItem* itm; + if (!parent.isValid()) + itm = rootItem; + else + itm = reinterpret_cast(parent.internalPointer()); + + return createIndex(row, column, itm->childItems.at(row)); +} + + +QModelIndex QPPDOptionsModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + QOptionTreeItem* itm = reinterpret_cast(index.internalPointer()); + + if (itm->parentItem && itm->parentItem != rootItem) + return createIndex(itm->parentItem->index, 0, itm->parentItem); + else + return QModelIndex(); +} + +Qt::ItemFlags QPPDOptionsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid() || reinterpret_cast(index.internalPointer())->type == QOptionTreeItem::Group) + return Qt::ItemIsEnabled; + + if (index.column() == 1) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +void QPPDOptionsModel::parseItems() +{ + emit layoutAboutToBeChanged(); + ppd = cups->currentPPD(); + delete rootItem; + rootItem = new QOptionTreeItem(QOptionTreeItem::Root, 0, ppd, "Root Item", 0); + parseGroups(rootItem); + emit layoutChanged(); +} + +void QPPDOptionsModel::parseGroups(QOptionTreeItem* parent) +{ + if (parent->type == QOptionTreeItem::Root) { + + const ppd_file_t* ppdFile = reinterpret_cast(parent->ptr); + + if (ppdFile) { + for (int i = 0; i < ppdFile->num_groups; ++i) { + QOptionTreeItem* group = new QOptionTreeItem(QOptionTreeItem::Group, i, &ppdFile->groups[i], ppdFile->groups[i].text, parent); + parent->childItems.append(group); + parseGroups(group); // parse possible subgroups + parseOptions(group); // parse options + } + } + } else if (parent->type == QOptionTreeItem::Group) { + + const ppd_group_t* group = reinterpret_cast(parent->ptr); + + if (group) { + for (int i = 0; i < group->num_subgroups; ++i) { + QOptionTreeItem* subgroup = new QOptionTreeItem(QOptionTreeItem::Group, i, &group->subgroups[i], group->subgroups[i].text, parent); + parent->childItems.append(subgroup); + parseGroups(subgroup); // parse possible subgroups + parseOptions(subgroup); // parse options + } + } + } +} + +void QPPDOptionsModel::parseOptions(QOptionTreeItem* parent) +{ + const ppd_group_t* group = reinterpret_cast(parent->ptr); + for (int i = 0; i < group->num_options; ++i) { + QOptionTreeItem* opt = new QOptionTreeItem(QOptionTreeItem::Option, i, &group->options[i], group->options[i].text, parent); + parent->childItems.append(opt); + parseChoices(opt); + } +} + +void QPPDOptionsModel::parseChoices(QOptionTreeItem* parent) +{ + const ppd_option_t* option = reinterpret_cast(parent->ptr); + bool marked = false; + for (int i = 0; i < option->num_choices; ++i) { + QOptionTreeItem* choice = new QOptionTreeItem(QOptionTreeItem::Choice, i, &option->choices[i], option->choices[i].text, parent); + if (static_cast(option->choices[i].marked) == 1) { + parent->selected = i; + parent->selDescription = option->choices[i].text; + marked = true; + } else if (!marked && qstrcmp(option->choices[i].choice, option->defchoice) == 0) { + parent->selected = i; + parent->selDescription = option->choices[i].text; + } + parent->childItems.append(choice); + } +} + +QVariant QPPDOptionsModel::headerData(int section, Qt::Orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch(section){ + case 0: + return QVariant(QApplication::translate("QPPDOptionsModel", "Name")); + case 1: + return QVariant(QApplication::translate("QPPDOptionsModel", "Value")); + default: + return QVariant(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +QWidget* QPPDOptionsEditor::createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex& index) const +{ + if (index.column() == 1 && reinterpret_cast(index.internalPointer())->type == QOptionTreeItem::Option) + return new QComboBox(parent); + else + return 0; +} + +void QPPDOptionsEditor::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + if (index.column() != 1) + return; + + QComboBox* cb = static_cast(editor); + QOptionTreeItem* itm = reinterpret_cast(index.internalPointer()); + + if (itm->selected == -1) + cb->addItem(QString()); + + for (int i = 0; i < itm->childItems.count(); ++i) + cb->addItem(QString::fromLocal8Bit(itm->childItems.at(i)->description)); + + if (itm->selected > -1) + cb->setCurrentIndex(itm->selected); + + connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(cbChanged(int))); +} + +void QPPDOptionsEditor::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + QComboBox* cb = static_cast(editor); + QOptionTreeItem* itm = reinterpret_cast(index.internalPointer()); + + if (itm->selected == cb->currentIndex()) + return; + + const ppd_option_t* opt = reinterpret_cast(itm->ptr); + QPPDOptionsModel* m = static_cast(model); + + if (m->cups->markOption(opt->keyword, opt->choices[cb->currentIndex()].choice) == 0) { + itm->selected = cb->currentIndex(); + itm->selDescription = reinterpret_cast(itm->ptr)->choices[itm->selected].text; + } +} + +void QPPDOptionsEditor::cbChanged(int) +{ +/* + emit commitData(static_cast(sender())); +*/ +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qprintdialog.cpp" +#include "qprintdialog_unix.moc" +#include "qrc_qprintdialog.cpp" + +#endif // QT_NO_PRINTDIALOG + diff --git a/src/gui/dialogs/qprintdialog_win.cpp b/src/gui/dialogs/qprintdialog_win.cpp new file mode 100644 index 0000000000..4f6866b2df --- /dev/null +++ b/src/gui/dialogs/qprintdialog_win.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_PRINTDIALOG + +#include "qprintdialog.h" + +#include +#include +#include +#include + +#include +#include +#include + +#if !defined(PD_NOCURRENTPAGE) +#define PD_NOCURRENTPAGE 0x00800000 +#define PD_RESULT_PRINT 1 +#define PD_RESULT_APPLY 2 +#define START_PAGE_GENERAL 0XFFFFFFFF +#endif + +QT_BEGIN_NAMESPACE + +extern void qt_win_eatMouseMove(); + +class QPrintDialogPrivate : public QAbstractPrintDialogPrivate +{ + Q_DECLARE_PUBLIC(QPrintDialog) +public: + QPrintDialogPrivate() + : ep(0) + { + } + + inline void _q_printToFileChanged(int) {} + inline void _q_rbPrintRangeToggled(bool) {} + inline void _q_printerChanged(int) {} + inline void _q_chbPrintLastFirstToggled(bool) {} + inline void _q_paperSizeChanged(int) {} + inline void _q_btnBrowseClicked() {} + inline void _q_btnPropertiesClicked() {} + int openWindowsPrintDialogModally(); + + QWin32PrintEnginePrivate *ep; +}; + +static void qt_win_setup_PRINTDLGEX(PRINTDLGEX *pd, QWidget *parent, + QPrintDialog *pdlg, + QPrintDialogPrivate *d, HGLOBAL *tempDevNames) +{ + DEVMODE *devMode = d->ep->devMode; + + if (devMode) { + int size = sizeof(DEVMODE) + devMode->dmDriverExtra; + pd->hDevMode = GlobalAlloc(GHND, size); + { + void *dest = GlobalLock(pd->hDevMode); + memcpy(dest, devMode, size); + GlobalUnlock(pd->hDevMode); + } + } else { + pd->hDevMode = NULL; + } + pd->hDevNames = tempDevNames; + + pd->Flags = PD_RETURNDC; + pd->Flags |= PD_USEDEVMODECOPIESANDCOLLATE; + + if (!pdlg->isOptionEnabled(QPrintDialog::PrintSelection)) + pd->Flags |= PD_NOSELECTION; + if (pdlg->isOptionEnabled(QPrintDialog::PrintPageRange)) { + pd->nMinPage = pdlg->minPage(); + pd->nMaxPage = pdlg->maxPage(); + } + + if(!pdlg->isOptionEnabled(QPrintDialog::PrintToFile)) + pd->Flags |= PD_DISABLEPRINTTOFILE; + + if (pdlg->printRange() == QPrintDialog::Selection) + pd->Flags |= PD_SELECTION; + else if (pdlg->printRange() == QPrintDialog::PageRange) + pd->Flags |= PD_PAGENUMS; + else + pd->Flags |= PD_ALLPAGES; + + // As stated by MSDN, to enable collate option when minpage==maxpage==0 + // set the PD_NOPAGENUMS flag + if (pd->nMinPage==0 && pd->nMaxPage==0) + pd->Flags |= PD_NOPAGENUMS; + + // Disable Current Page option if not required as default is Enabled + if (!pdlg->isOptionEnabled(QPrintDialog::PrintCurrentPage)) + pd->Flags |= PD_NOCURRENTPAGE; + + // Default to showing the General tab first + pd->nStartPage = START_PAGE_GENERAL; + + // We don't support more than one page range in the QPrinter API yet. + pd->nPageRanges = 1; + pd->nMaxPageRanges = 1; + + if (d->ep->printToFile) + pd->Flags |= PD_PRINTTOFILE; + Q_ASSERT(parent); + pd->hwndOwner = parent->window()->winId(); + pd->lpPageRanges[0].nFromPage = qMax(pdlg->fromPage(), pdlg->minPage()); + pd->lpPageRanges[0].nToPage = (pdlg->toPage() > 0) ? qMin(pdlg->toPage(), pdlg->maxPage()) : 1; + pd->nCopies = d->ep->num_copies; +} + +static void qt_win_read_back_PRINTDLGEX(PRINTDLGEX *pd, QPrintDialog *pdlg, QPrintDialogPrivate *d) +{ + if (pd->Flags & PD_SELECTION) { + pdlg->setPrintRange(QPrintDialog::Selection); + pdlg->setFromTo(0, 0); + } else if (pd->Flags & PD_PAGENUMS) { + pdlg->setPrintRange(QPrintDialog::PageRange); + pdlg->setFromTo(pd->lpPageRanges[0].nFromPage, pd->lpPageRanges[0].nToPage); + } else if (pd->Flags & PD_CURRENTPAGE) { + pdlg->setPrintRange(QPrintDialog::CurrentPage); + pdlg->setFromTo(0, 0); + } else { // PD_ALLPAGES + pdlg->setPrintRange(QPrintDialog::AllPages); + pdlg->setFromTo(0, 0); + } + + d->ep->printToFile = (pd->Flags & PD_PRINTTOFILE) != 0; + + d->ep->readDevnames(pd->hDevNames); + d->ep->readDevmode(pd->hDevMode); + d->ep->updateCustomPaperSize(); + + if (d->ep->printToFile && d->ep->fileName.isEmpty()) + d->ep->fileName = d->ep->port; + else if (!d->ep->printToFile && d->ep->fileName == QLatin1String("FILE:")) + d->ep->fileName.clear(); +} + +static bool warnIfNotNative(QPrinter *printer) +{ + if (printer->outputFormat() != QPrinter::NativeFormat) { + qWarning("QPrintDialog: Cannot be used on non-native printers"); + return false; + } + return true; +} + +QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent) + : QAbstractPrintDialog( *(new QPrintDialogPrivate), printer, parent) +{ + Q_D(QPrintDialog); + if (!warnIfNotNative(d->printer)) + return; + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +QPrintDialog::QPrintDialog(QWidget *parent) + : QAbstractPrintDialog( *(new QPrintDialogPrivate), 0, parent) +{ + Q_D(QPrintDialog); + if (!warnIfNotNative(d->printer)) + return; + d->ep = static_cast(d->printer->paintEngine())->d_func(); +} + +QPrintDialog::~QPrintDialog() +{ +} + +int QPrintDialog::exec() +{ + if (!warnIfNotNative(printer())) + return 0; + + Q_D(QPrintDialog); + return d->openWindowsPrintDialogModally(); +} + +int QPrintDialogPrivate::openWindowsPrintDialogModally() +{ + Q_Q(QPrintDialog); + QWidget *parent = q->parentWidget(); + if (parent) + parent = parent->window(); + else + parent = QApplication::activeWindow(); + + // If there is no window, fall back to the print dialog itself + if (parent == 0) + parent = q; + + QWidget modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + HGLOBAL *tempDevNames = ep->createDevNames(); + + bool done; + bool result; + bool doPrinting; + + PRINTPAGERANGE pageRange; + PRINTDLGEX pd; + memset(&pd, 0, sizeof(PRINTDLGEX)); + pd.lStructSize = sizeof(PRINTDLGEX); + pd.lpPageRanges = &pageRange; + qt_win_setup_PRINTDLGEX(&pd, parent, q, this, tempDevNames); + + do { + done = true; + doPrinting = false; + result = (PrintDlgEx(&pd) == S_OK); + if (result && (pd.dwResultAction == PD_RESULT_PRINT + || pd.dwResultAction == PD_RESULT_APPLY)) + { + doPrinting = (pd.dwResultAction == PD_RESULT_PRINT); + if ((pd.Flags & PD_PAGENUMS) + && (pd.lpPageRanges[0].nFromPage > pd.lpPageRanges[0].nToPage)) + { + pd.lpPageRanges[0].nFromPage = 1; + pd.lpPageRanges[0].nToPage = 1; + done = false; + } + if (pd.hDC == 0) + result = false; + } + + if (!done) { + QMessageBox::warning(0, QPrintDialog::tr("Print"), + QPrintDialog::tr("The 'From' value cannot be greater than the 'To' value."), + QPrintDialog::tr("OK")); + } + } while (!done); + + QApplicationPrivate::leaveModal(&modal_widget); + + qt_win_eatMouseMove(); + + // write values back... + if (result && (pd.dwResultAction == PD_RESULT_PRINT + || pd.dwResultAction == PD_RESULT_APPLY)) + { + qt_win_read_back_PRINTDLGEX(&pd, q, this); + // update printer validity + printer->d_func()->validPrinter = !ep->name.isEmpty(); + } + + // Cleanup... + GlobalFree(tempDevNames); + + q->done(result && doPrinting); + + return result && doPrinting; +} + +void QPrintDialog::setVisible(bool visible) +{ + Q_D(QPrintDialog); + + // its always modal, so we cannot hide a native print dialog + if (!visible) + return; + + if (!warnIfNotNative(d->printer)) + return; + + (void)d->openWindowsPrintDialogModally(); + return; +} + +QT_END_NAMESPACE + +#include "moc_qprintdialog.cpp" + +#endif // QT_NO_PRINTDIALOG diff --git a/src/gui/dialogs/qprintpreviewdialog.cpp b/src/gui/dialogs/qprintpreviewdialog.cpp new file mode 100644 index 0000000000..eb597fb384 --- /dev/null +++ b/src/gui/dialogs/qprintpreviewdialog.cpp @@ -0,0 +1,802 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprintpreviewdialog.h" +#include "qprintpreviewwidget.h" +#include +#include "private/qdialog_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef QT_NO_PRINTPREVIEWDIALOG + +QT_BEGIN_NAMESPACE + +namespace { +class QPrintPreviewMainWindow : public QMainWindow +{ +public: + QPrintPreviewMainWindow(QWidget *parent) : QMainWindow(parent) {} + QMenu *createPopupMenu() { return 0; } +}; + +class ZoomFactorValidator : public QDoubleValidator +{ +public: + ZoomFactorValidator(QObject* parent) + : QDoubleValidator(parent) {} + ZoomFactorValidator(qreal bottom, qreal top, int decimals, QObject *parent) + : QDoubleValidator(bottom, top, decimals, parent) {} + + State validate(QString &input, int &pos) const + { + bool replacePercent = false; + if (input.endsWith(QLatin1Char('%'))) { + input = input.left(input.length() - 1); + replacePercent = true; + } + State state = QDoubleValidator::validate(input, pos); + if (replacePercent) + input += QLatin1Char('%'); + const int num_size = 4; + if (state == Intermediate) { + int i = input.indexOf(QLocale::system().decimalPoint()); + if ((i == -1 && input.size() > num_size) + || (i != -1 && i > num_size)) + return Invalid; + } + return state; + } +}; + +class LineEdit : public QLineEdit +{ + Q_OBJECT +public: + LineEdit(QWidget* parent = 0) + : QLineEdit(parent) + { + setContextMenuPolicy(Qt::NoContextMenu); + connect(this, SIGNAL(returnPressed()), SLOT(handleReturnPressed())); + } + +protected: + void focusInEvent(QFocusEvent *e) + { + origText = text(); + QLineEdit::focusInEvent(e); + } + + void focusOutEvent(QFocusEvent *e) + { + if (isModified() && !hasAcceptableInput()) + setText(origText); + QLineEdit::focusOutEvent(e); + } + +private slots: + void handleReturnPressed() + { + origText = text(); + } + +private: + QString origText; +}; +} // anonymous namespace + +class QPrintPreviewDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QPrintPreviewDialog) +public: + QPrintPreviewDialogPrivate() + : printDialog(0), ownPrinter(false), + initialized(false) {} + + // private slots + void _q_fit(QAction *action); + void _q_zoomIn(); + void _q_zoomOut(); + void _q_navigate(QAction *action); + void _q_setMode(QAction *action); + void _q_pageNumEdited(); + void _q_print(); + void _q_pageSetup(); + void _q_previewChanged(); + void _q_zoomFactorChanged(); + + void init(QPrinter *printer = 0); + void populateScene(); + void layoutPages(); + void setupActions(); + void updateNavActions(); + void setFitting(bool on); + bool isFitting(); + void updatePageNumLabel(); + void updateZoomFactor(); + + QPrintDialog *printDialog; + QPrintPreviewWidget *preview; + QPrinter *printer; + bool ownPrinter; + bool initialized; + + // widgets: + QLineEdit *pageNumEdit; + QLabel *pageNumLabel; + QComboBox *zoomFactor; + + // actions: + QActionGroup* navGroup; + QAction *nextPageAction; + QAction *prevPageAction; + QAction *firstPageAction; + QAction *lastPageAction; + + QActionGroup* fitGroup; + QAction *fitWidthAction; + QAction *fitPageAction; + + QActionGroup* zoomGroup; + QAction *zoomInAction; + QAction *zoomOutAction; + + QActionGroup* orientationGroup; + QAction *portraitAction; + QAction *landscapeAction; + + QActionGroup* modeGroup; + QAction *singleModeAction; + QAction *facingModeAction; + QAction *overviewModeAction; + + QActionGroup *printerGroup; + QAction *printAction; + QAction *pageSetupAction; +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + QAction *closeAction; +#endif + + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +void QPrintPreviewDialogPrivate::init(QPrinter *_printer) +{ + Q_Q(QPrintPreviewDialog); + + if (_printer) { + preview = new QPrintPreviewWidget(_printer, q); + printer = _printer; + } else { + ownPrinter = true; + printer = new QPrinter; + preview = new QPrintPreviewWidget(printer, q); + } + QObject::connect(preview, SIGNAL(paintRequested(QPrinter*)), q, SIGNAL(paintRequested(QPrinter*))); + QObject::connect(preview, SIGNAL(previewChanged()), q, SLOT(_q_previewChanged())); + setupActions(); + + pageNumEdit = new LineEdit; + pageNumEdit->setAlignment(Qt::AlignRight); + pageNumEdit->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + pageNumLabel = new QLabel; + QObject::connect(pageNumEdit, SIGNAL(editingFinished()), q, SLOT(_q_pageNumEdited())); + + zoomFactor = new QComboBox; + zoomFactor->setEditable(true); + zoomFactor->setMinimumContentsLength(7); + zoomFactor->setInsertPolicy(QComboBox::NoInsert); + LineEdit *zoomEditor = new LineEdit; + zoomEditor->setValidator(new ZoomFactorValidator(1, 1000, 1, zoomEditor)); + zoomFactor->setLineEdit(zoomEditor); + static const short factorsX2[] = { 25, 50, 100, 200, 250, 300, 400, 800, 1600 }; + for (int i = 0; i < int(sizeof(factorsX2) / sizeof(factorsX2[0])); ++i) + zoomFactor->addItem(QPrintPreviewDialog::tr("%1%").arg(factorsX2[i] / 2.0)); + QObject::connect(zoomFactor->lineEdit(), SIGNAL(editingFinished()), + q, SLOT(_q_zoomFactorChanged())); + QObject::connect(zoomFactor, SIGNAL(currentIndexChanged(int)), + q, SLOT(_q_zoomFactorChanged())); + + QPrintPreviewMainWindow *mw = new QPrintPreviewMainWindow(q); + QToolBar *toolbar = new QToolBar(mw); + toolbar->addAction(fitWidthAction); + toolbar->addAction(fitPageAction); + toolbar->addSeparator(); + toolbar->addWidget(zoomFactor); + toolbar->addAction(zoomOutAction); + toolbar->addAction(zoomInAction); + toolbar->addSeparator(); + toolbar->addAction(portraitAction); + toolbar->addAction(landscapeAction); + toolbar->addSeparator(); + toolbar->addAction(firstPageAction); + toolbar->addAction(prevPageAction); + + // this is to ensure the label text and the editor text are + // aligned in all styles - the extra QVBoxLayout is a workaround + // for bug in QFormLayout + QWidget *pageEdit = new QWidget(toolbar); + QVBoxLayout *vboxLayout = new QVBoxLayout; + vboxLayout->setContentsMargins(0, 0, 0, 0); +#ifdef Q_WS_MAC + // We query the widgets about their size and then we fix the size. + // This should do the trick for the laying out part... + QSize pageNumEditSize, pageNumLabelSize; + pageNumEditSize = pageNumEdit->minimumSizeHint(); + pageNumLabelSize = pageNumLabel->minimumSizeHint(); + pageNumEdit->resize(pageNumEditSize); + pageNumLabel->resize(pageNumLabelSize); +#endif + QFormLayout *formLayout = new QFormLayout; +#ifdef Q_WS_MAC + // We have to change the growth policy in Mac. + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); +#endif + formLayout->setWidget(0, QFormLayout::LabelRole, pageNumEdit); + formLayout->setWidget(0, QFormLayout::FieldRole, pageNumLabel); + vboxLayout->addLayout(formLayout); + vboxLayout->setAlignment(Qt::AlignVCenter); + pageEdit->setLayout(vboxLayout); + toolbar->addWidget(pageEdit); + + toolbar->addAction(nextPageAction); + toolbar->addAction(lastPageAction); + toolbar->addSeparator(); + toolbar->addAction(singleModeAction); + toolbar->addAction(facingModeAction); + toolbar->addAction(overviewModeAction); + toolbar->addSeparator(); + toolbar->addAction(pageSetupAction); + toolbar->addAction(printAction); +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + toolbar->addAction(closeAction); +#endif + + // Cannot use the actions' triggered signal here, since it doesn't autorepeat + QToolButton *zoomInButton = static_cast(toolbar->widgetForAction(zoomInAction)); + QToolButton *zoomOutButton = static_cast(toolbar->widgetForAction(zoomOutAction)); + zoomInButton->setAutoRepeat(true); + zoomInButton->setAutoRepeatInterval(200); + zoomInButton->setAutoRepeatDelay(200); + zoomOutButton->setAutoRepeat(true); + zoomOutButton->setAutoRepeatInterval(200); + zoomOutButton->setAutoRepeatDelay(200); + QObject::connect(zoomInButton, SIGNAL(clicked()), q, SLOT(_q_zoomIn())); + QObject::connect(zoomOutButton, SIGNAL(clicked()), q, SLOT(_q_zoomOut())); + + mw->addToolBar(toolbar); + mw->setCentralWidget(preview); + // QMainWindows are always created as top levels, force it to be a + // plain widget + mw->setParent(q, Qt::Widget); + + QVBoxLayout *topLayout = new QVBoxLayout; + topLayout->addWidget(mw); + topLayout->setMargin(0); + q->setLayout(topLayout); + + QString caption = QCoreApplication::translate("QPrintPreviewDialog", "Print Preview"); + if (!printer->docName().isEmpty()) + caption += QString::fromLatin1(": ") + printer->docName(); + q->setWindowTitle(caption); + + if (!printer->isValid() +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) + || printer->outputFormat() != QPrinter::NativeFormat +#endif + ) + pageSetupAction->setEnabled(false); + preview->setFocus(); +} + +static inline void qt_setupActionIcon(QAction *action, const QLatin1String &name) +{ + QLatin1String imagePrefix(":/trolltech/dialogs/qprintpreviewdialog/images/"); + QIcon icon; + icon.addFile(imagePrefix + name + QLatin1String("-24.png"), QSize(24, 24)); + icon.addFile(imagePrefix + name + QLatin1String("-32.png"), QSize(32, 32)); + action->setIcon(icon); +} + +void QPrintPreviewDialogPrivate::setupActions() +{ + Q_Q(QPrintPreviewDialog); + + // Navigation + navGroup = new QActionGroup(q); + navGroup->setExclusive(false); + nextPageAction = navGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Next page")); + prevPageAction = navGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Previous page")); + firstPageAction = navGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "First page")); + lastPageAction = navGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Last page")); + qt_setupActionIcon(nextPageAction, QLatin1String("go-next")); + qt_setupActionIcon(prevPageAction, QLatin1String("go-previous")); + qt_setupActionIcon(firstPageAction, QLatin1String("go-first")); + qt_setupActionIcon(lastPageAction, QLatin1String("go-last")); + QObject::connect(navGroup, SIGNAL(triggered(QAction*)), q, SLOT(_q_navigate(QAction*))); + + + fitGroup = new QActionGroup(q); + fitWidthAction = fitGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Fit width")); + fitPageAction = fitGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Fit page")); + fitWidthAction->setObjectName(QLatin1String("fitWidthAction")); + fitPageAction->setObjectName(QLatin1String("fitPageAction")); + fitWidthAction->setCheckable(true); + fitPageAction->setCheckable(true); + qt_setupActionIcon(fitWidthAction, QLatin1String("fit-width")); + qt_setupActionIcon(fitPageAction, QLatin1String("fit-page")); + QObject::connect(fitGroup, SIGNAL(triggered(QAction*)), q, SLOT(_q_fit(QAction*))); + + // Zoom + zoomGroup = new QActionGroup(q); + zoomInAction = zoomGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Zoom in")); + zoomOutAction = zoomGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Zoom out")); + qt_setupActionIcon(zoomInAction, QLatin1String("zoom-in")); + qt_setupActionIcon(zoomOutAction, QLatin1String("zoom-out")); + + // Portrait/Landscape + orientationGroup = new QActionGroup(q); + portraitAction = orientationGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Portrait")); + landscapeAction = orientationGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Landscape")); + portraitAction->setCheckable(true); + landscapeAction->setCheckable(true); + qt_setupActionIcon(portraitAction, QLatin1String("layout-portrait")); + qt_setupActionIcon(landscapeAction, QLatin1String("layout-landscape")); + QObject::connect(portraitAction, SIGNAL(triggered(bool)), preview, SLOT(setPortraitOrientation())); + QObject::connect(landscapeAction, SIGNAL(triggered(bool)), preview, SLOT(setLandscapeOrientation())); + + // Display mode + modeGroup = new QActionGroup(q); + singleModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show single page")); + facingModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show facing pages")); + overviewModeAction = modeGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Show overview of all pages")); + qt_setupActionIcon(singleModeAction, QLatin1String("view-page-one")); + qt_setupActionIcon(facingModeAction, QLatin1String("view-page-sided")); + qt_setupActionIcon(overviewModeAction, QLatin1String("view-page-multi")); + singleModeAction->setObjectName(QLatin1String("singleModeAction")); + facingModeAction->setObjectName(QLatin1String("facingModeAction")); + overviewModeAction->setObjectName(QLatin1String("overviewModeAction")); + + singleModeAction->setCheckable(true); + facingModeAction->setCheckable(true); + overviewModeAction->setCheckable(true); + QObject::connect(modeGroup, SIGNAL(triggered(QAction*)), q, SLOT(_q_setMode(QAction*))); + + // Print + printerGroup = new QActionGroup(q); + printAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Print")); + pageSetupAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Page setup")); + qt_setupActionIcon(printAction, QLatin1String("print")); + qt_setupActionIcon(pageSetupAction, QLatin1String("page-setup")); + QObject::connect(printAction, SIGNAL(triggered(bool)), q, SLOT(_q_print())); + QObject::connect(pageSetupAction, SIGNAL(triggered(bool)), q, SLOT(_q_pageSetup())); +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + closeAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Close")); + QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(reject())); +#endif + + // Initial state: + fitPageAction->setChecked(true); + singleModeAction->setChecked(true); + if (preview->orientation() == QPrinter::Portrait) + portraitAction->setChecked(true); + else + landscapeAction->setChecked(true); +} + + +bool QPrintPreviewDialogPrivate::isFitting() +{ + return (fitGroup->isExclusive() + && (fitWidthAction->isChecked() || fitPageAction->isChecked())); +} + + +void QPrintPreviewDialogPrivate::setFitting(bool on) +{ + if (isFitting() == on) + return; + fitGroup->setExclusive(on); + if (on) { + QAction* action = fitWidthAction->isChecked() ? fitWidthAction : fitPageAction; + action->setChecked(true); + if (fitGroup->checkedAction() != action) { + // work around exclusitivity problem + fitGroup->removeAction(action); + fitGroup->addAction(action); + } + } else { + fitWidthAction->setChecked(false); + fitPageAction->setChecked(false); + } +} + +void QPrintPreviewDialogPrivate::updateNavActions() +{ + int curPage = preview->currentPage(); + int numPages = preview->pageCount(); + nextPageAction->setEnabled(curPage < numPages); + prevPageAction->setEnabled(curPage > 1); + firstPageAction->setEnabled(curPage > 1); + lastPageAction->setEnabled(curPage < numPages); + pageNumEdit->setText(QString::number(curPage)); +} + +void QPrintPreviewDialogPrivate::updatePageNumLabel() +{ + Q_Q(QPrintPreviewDialog); + + int numPages = preview->pageCount(); + int maxChars = QString::number(numPages).length(); + pageNumLabel->setText(QString::fromLatin1("/ %1").arg(numPages)); + int cyphersWidth = q->fontMetrics().width(QString().fill(QLatin1Char('8'), maxChars)); + int maxWidth = pageNumEdit->minimumSizeHint().width() + cyphersWidth; + pageNumEdit->setMinimumWidth(maxWidth); + pageNumEdit->setMaximumWidth(maxWidth); + pageNumEdit->setValidator(new QIntValidator(1, numPages, pageNumEdit)); + // any old one will be deleted later along with its parent pageNumEdit +} + +void QPrintPreviewDialogPrivate::updateZoomFactor() +{ + zoomFactor->lineEdit()->setText(QString().sprintf("%.1f%%", preview->zoomFactor()*100)); +} + +void QPrintPreviewDialogPrivate::_q_fit(QAction* action) +{ + setFitting(true); + if (action == fitPageAction) + preview->fitInView(); + else + preview->fitToWidth(); +} + +void QPrintPreviewDialogPrivate::_q_zoomIn() +{ + setFitting(false); + preview->zoomIn(); + updateZoomFactor(); +} + +void QPrintPreviewDialogPrivate::_q_zoomOut() +{ + setFitting(false); + preview->zoomOut(); + updateZoomFactor(); +} + +void QPrintPreviewDialogPrivate::_q_pageNumEdited() +{ + bool ok = false; + int res = pageNumEdit->text().toInt(&ok); + if (ok) + preview->setCurrentPage(res); +} + +void QPrintPreviewDialogPrivate::_q_navigate(QAction* action) +{ + int curPage = preview->currentPage(); + if (action == prevPageAction) + preview->setCurrentPage(curPage - 1); + else if (action == nextPageAction) + preview->setCurrentPage(curPage + 1); + else if (action == firstPageAction) + preview->setCurrentPage(1); + else if (action == lastPageAction) + preview->setCurrentPage(preview->pageCount()); + updateNavActions(); +} + +void QPrintPreviewDialogPrivate::_q_setMode(QAction* action) +{ + if (action == overviewModeAction) { + preview->setViewMode(QPrintPreviewWidget::AllPagesView); + setFitting(false); + fitGroup->setEnabled(false); + navGroup->setEnabled(false); + pageNumEdit->setEnabled(false); + pageNumLabel->setEnabled(false); + } else if (action == facingModeAction) { + preview->setViewMode(QPrintPreviewWidget::FacingPagesView); + } else { + preview->setViewMode(QPrintPreviewWidget::SinglePageView); + } + if (action == facingModeAction || action == singleModeAction) { + fitGroup->setEnabled(true); + navGroup->setEnabled(true); + pageNumEdit->setEnabled(true); + pageNumLabel->setEnabled(true); + setFitting(true); + } +} + +void QPrintPreviewDialogPrivate::_q_print() +{ + Q_Q(QPrintPreviewDialog); + +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) + if (printer->outputFormat() != QPrinter::NativeFormat) { + QString title; + QString suffix; + if (printer->outputFormat() == QPrinter::PdfFormat) { + title = QCoreApplication::translate("QPrintPreviewDialog", "Export to PDF"); + suffix = QLatin1String(".pdf"); + } else { + title = QCoreApplication::translate("QPrintPreviewDialog", "Export to PostScript"); + suffix = QLatin1String(".ps"); + } + QString fileName = QFileDialog::getSaveFileName(q, title, printer->outputFileName(), + QLatin1Char('*') + suffix); + if (!fileName.isEmpty()) { + if (QFileInfo(fileName).suffix().isEmpty()) + fileName.append(suffix); + printer->setOutputFileName(fileName); + } + if (!printer->outputFileName().isEmpty()) + preview->print(); + q->accept(); + return; + } +#endif + + if (!printDialog) + printDialog = new QPrintDialog(printer, q); + if (printDialog->exec() == QDialog::Accepted) { + preview->print(); + q->accept(); + } +} + +void QPrintPreviewDialogPrivate::_q_pageSetup() +{ + Q_Q(QPrintPreviewDialog); + + QPageSetupDialog pageSetup(printer, q); + if (pageSetup.exec() == QDialog::Accepted) { + // update possible orientation changes + if (preview->orientation() == QPrinter::Portrait) { + portraitAction->setChecked(true); + preview->setPortraitOrientation(); + }else { + landscapeAction->setChecked(true); + preview->setLandscapeOrientation(); + } + } +} + +void QPrintPreviewDialogPrivate::_q_previewChanged() +{ + updateNavActions(); + updatePageNumLabel(); + updateZoomFactor(); +} + +void QPrintPreviewDialogPrivate::_q_zoomFactorChanged() +{ + QString text = zoomFactor->lineEdit()->text(); + bool ok; + qreal factor = text.remove(QLatin1Char('%')).toFloat(&ok); + factor = qMax(qreal(1.0), qMin(qreal(1000.0), factor)); + if (ok) { + preview->setZoomFactor(factor/100.0); + zoomFactor->setEditText(QString::fromLatin1("%1%").arg(factor)); + setFitting(false); + } +} + +/////////////////////////////////////////////////////////////////////////// + +/*! + \class QPrintPreviewDialog + \since 4.4 + + \brief The QPrintPreviewDialog class provides a dialog for + previewing and configuring page layouts for printer output. + + \ingroup standard-dialogs + \ingroup printing + + Using QPrintPreviewDialog in your existing application is + straightforward: + + \list 1 + \o Create the QPrintPreviewDialog. + + You can construct a QPrintPreviewDialog with an existing QPrinter + object, or you can have QPrintPreviewDialog create one for you, + which will be the system default printer. + + \o Connect the paintRequested() signal to a slot. + + When the dialog needs to generate a set of preview pages, the + paintRequested() signal will be emitted. You can use the exact + same code for the actual printing as for having the preview + generated, including calling QPrinter::newPage() to start a new + page in the preview. Connect a slot to the paintRequested() + signal, where you draw onto the QPrinter object that is passed + into the slot. + + \o Call exec(). + + Call QPrintPreviewDialog::exec() to show the preview dialog. + \endlist + + In Symbian, there is no support for printing. Hence, this dialog should not + be used in Symbian. + + \sa QPrinter, QPrintDialog, QPageSetupDialog, QPrintPreviewWidget +*/ + +/*! + Constructs a QPrintPreviewDialog based on \a printer and with \a + parent as the parent widget. The widget flags \a flags are passed on + to the QWidget constructor. + + \sa QWidget::setWindowFlags() +*/ +QPrintPreviewDialog::QPrintPreviewDialog(QPrinter* printer, QWidget *parent, Qt::WindowFlags flags) + : QDialog(*new QPrintPreviewDialogPrivate, parent, flags) +{ + Q_D(QPrintPreviewDialog); + d->init(printer); +} + +/*! + \overload + \fn QPrintPreviewDialog::QPrintPreviewDialog(QWidget *parent, Qt::WindowFlags flags) + + This will create an internal QPrinter object, which will use the + system default printer. +*/ +QPrintPreviewDialog::QPrintPreviewDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(*new QPrintPreviewDialogPrivate, parent, f) +{ + Q_D(QPrintPreviewDialog); + d->init(); +} + +/*! + Destroys the QPrintPreviewDialog. +*/ +QPrintPreviewDialog::~QPrintPreviewDialog() +{ + Q_D(QPrintPreviewDialog); + if (d->ownPrinter) + delete d->printer; + delete d->printDialog; +} + +/*! + \reimp +*/ +void QPrintPreviewDialog::setVisible(bool visible) +{ + Q_D(QPrintPreviewDialog); + // this will make the dialog get a decent default size + if (visible && !d->initialized) { + d->preview->updatePreview(); + d->initialized = true; + } + QDialog::setVisible(visible); +} + +/*! + \reimp +*/ +void QPrintPreviewDialog::done(int result) +{ + Q_D(QPrintPreviewDialog); + QDialog::done(result); + if (d->receiverToDisconnectOnClose) { + disconnect(this, SIGNAL(finished(int)), + d->receiverToDisconnectOnClose, d->memberToDisconnectOnClose); + d->receiverToDisconnectOnClose = 0; + } + d->memberToDisconnectOnClose.clear(); +} + +/*! + \overload + \since 4.5 + + Opens the dialog and connects its finished(int) signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QPrintPreviewDialog::open(QObject *receiver, const char *member) +{ + Q_D(QPrintPreviewDialog); + // the int parameter isn't very useful here; we could just as well connect + // to reject(), but this feels less robust somehow + connect(this, SIGNAL(finished(int)), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +/*! + Returns a pointer to the QPrinter object this dialog is currently + operating on. +*/ +QPrinter *QPrintPreviewDialog::printer() +{ + Q_D(QPrintPreviewDialog); + return d->printer; +} + +/*! + \fn void QPrintPreviewDialog::paintRequested(QPrinter *printer) + + This signal is emitted when the QPrintPreviewDialog needs to generate + a set of preview pages. + + The \a printer instance supplied is the paint device onto which you should + paint the contents of each page, using the QPrinter instance in the same way + as you would when printing directly. +*/ + + +QT_END_NAMESPACE + +#include "moc_qprintpreviewdialog.cpp" +#include "qprintpreviewdialog.moc" + +#endif // QT_NO_PRINTPREVIEWDIALOG + + diff --git a/src/gui/dialogs/qprintpreviewdialog.h b/src/gui/dialogs/qprintpreviewdialog.h new file mode 100644 index 0000000000..a49455b520 --- /dev/null +++ b/src/gui/dialogs/qprintpreviewdialog.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTPREVIEWDIALOG_H +#define QPRINTPREVIEWDIALOG_H + +#include + +#ifndef QT_NO_PRINTPREVIEWDIALOG + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsView; +class QPrintPreviewDialogPrivate; + +class Q_GUI_EXPORT QPrintPreviewDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPrintPreviewDialog) + +public: + explicit QPrintPreviewDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + explicit QPrintPreviewDialog(QPrinter *printer, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QPrintPreviewDialog(); + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + + QPrinter *printer(); + + void setVisible(bool visible); + void done(int result); + +Q_SIGNALS: + void paintRequested(QPrinter *printer); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_fit(QAction *action)) + Q_PRIVATE_SLOT(d_func(), void _q_zoomIn()) + Q_PRIVATE_SLOT(d_func(), void _q_zoomOut()) + Q_PRIVATE_SLOT(d_func(), void _q_navigate(QAction *action)) + Q_PRIVATE_SLOT(d_func(), void _q_setMode(QAction *action)) + Q_PRIVATE_SLOT(d_func(), void _q_pageNumEdited()) + Q_PRIVATE_SLOT(d_func(), void _q_print()) + Q_PRIVATE_SLOT(d_func(), void _q_pageSetup()) + Q_PRIVATE_SLOT(d_func(), void _q_previewChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_zoomFactorChanged()) + + void *dummy; // ### Qt 5 - remove me +}; + + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_PRINTPREVIEWDIALOG + +#endif // QPRINTPREVIEWDIALOG_H diff --git a/src/gui/dialogs/qprintpropertieswidget.ui b/src/gui/dialogs/qprintpropertieswidget.ui new file mode 100644 index 0000000000..26fa09599e --- /dev/null +++ b/src/gui/dialogs/qprintpropertieswidget.ui @@ -0,0 +1,70 @@ + + QPrintPropertiesWidget + + + + 0 + 0 + 396 + 288 + + + + Form + + + + 0 + + + + + 0 + + + + + 0 + 0 + 392 + 261 + + + + Page + + + + + + + + + + Advanced + + + + + + true + + + + + + + + + + + + QPageSetupWidget + QWidget +
qpagesetupdialog_unix_p.h
+ 1 +
+
+ + +
diff --git a/src/gui/dialogs/qprintsettingsoutput.ui b/src/gui/dialogs/qprintsettingsoutput.ui new file mode 100644 index 0000000000..be916790fb --- /dev/null +++ b/src/gui/dialogs/qprintsettingsoutput.ui @@ -0,0 +1,363 @@ + + + QPrintSettingsOutput + + + + 0 + 0 + 426 + 171 + + + + Form + + + + 0 + + + + + 0 + + + + Copies + + + + + + + 0 + 0 + + + + Print range + + + + 4 + + + 6 + + + + + Print all + + + true + + + + + + + 6 + + + 0 + + + + + Pages from + + + + + + + false + + + 1 + + + 999 + + + + + + + to + + + + + + + false + + + 1 + + + 999 + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + Current Page + + + + + + + Selection + + + + + + + Qt::Vertical + + + + 1 + 1 + + + + + + + + + + + Output Settings + + + + + + Copies: + + + copies + + + + + + + 1 + + + 999 + + + + + + + Qt::Horizontal + + + + 91 + 20 + + + + + + + + Collate + + + + + + + + 0 + 0 + + + + + + + + Reverse + + + + + + + Qt::Vertical + + + + 0 + 1 + + + + + + + + + + + + Options + + + + + + Color Mode + + + + + + Qt::Vertical + + + + 1 + 0 + + + + + + + + Color + + + + + + + + + + Grayscale + + + + + + + + + + Duplex Printing + + + + + + None + + + true + + + + + + + Long side + + + + + + + Short side + + + + + + + Qt::Vertical + + + + 1 + 0 + + + + + + + + + + + + + + + + + printRange + toggled(bool) + from + setEnabled(bool) + + + 76 + 59 + + + 122 + 57 + + + + + printRange + toggled(bool) + to + setEnabled(bool) + + + 69 + 67 + + + 215 + 67 + + + + + diff --git a/src/gui/dialogs/qprintwidget.ui b/src/gui/dialogs/qprintwidget.ui new file mode 100644 index 0000000000..8a4f3bde0a --- /dev/null +++ b/src/gui/dialogs/qprintwidget.ui @@ -0,0 +1,116 @@ + + QPrintWidget + + + + 0 + 0 + 443 + 175 + + + + Form + + + + 0 + + + + + Printer + + + + + + &Name: + + + printers + + + + + + + + 3 + 0 + + + + + + + + + 1 + 0 + + + + P&roperties + + + + + + + Location: + + + + + + + + + + Preview + + + + + + + Type: + + + + + + + + + + Output &file: + + + filename + + + + + + + + + + + + ... + + + + + + + + + + + + + diff --git a/src/gui/dialogs/qprogressdialog.cpp b/src/gui/dialogs/qprogressdialog.cpp new file mode 100644 index 0000000000..a3fff9b617 --- /dev/null +++ b/src/gui/dialogs/qprogressdialog.cpp @@ -0,0 +1,907 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprogressdialog.h" + +#ifndef QT_NO_PROGRESSDIALOG + +#include "qshortcut.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qlabel.h" +#include "qprogressbar.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qpushbutton.h" +#include "qcursor.h" +#include "qtimer.h" +#include "qelapsedtimer.h" +#include +#include + +#if defined(QT_SOFTKEYS_ENABLED) +#include +#endif +#ifdef Q_WS_S60 +#include +#endif + + +QT_BEGIN_NAMESPACE + +// If the operation is expected to take this long (as predicted by +// progress time), show the progress dialog. +static const int defaultShowTime = 4000; +// Wait at least this long before attempting to make a prediction. +static const int minWaitTime = 50; + +class QProgressDialogPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QProgressDialog) + +public: + QProgressDialogPrivate() : label(0), cancel(0), bar(0), + shown_once(false), + cancellation_flag(false), + showTime(defaultShowTime), +#ifndef QT_NO_SHORTCUT + escapeShortcut(0), +#endif +#ifdef QT_SOFTKEYS_ENABLED + cancelAction(0), +#endif + useDefaultCancelText(false) + { + } + + void init(const QString &labelText, const QString &cancelText, int min, int max); + void layout(); + void retranslateStrings(); + void _q_disconnectOnClose(); + + QLabel *label; + QPushButton *cancel; + QProgressBar *bar; + QTimer *forceTimer; + bool shown_once; + bool cancellation_flag; + QElapsedTimer starttime; +#ifndef QT_NO_CURSOR + QCursor parentCursor; +#endif + int showTime; + bool autoClose; + bool autoReset; + bool forceHide; +#ifndef QT_NO_SHORTCUT + QShortcut *escapeShortcut; +#endif +#ifdef QT_SOFTKEYS_ENABLED + QAction *cancelAction; +#endif + bool useDefaultCancelText; + QPointer receiverToDisconnectOnClose; + QByteArray memberToDisconnectOnClose; +}; + +void QProgressDialogPrivate::init(const QString &labelText, const QString &cancelText, + int min, int max) +{ + Q_Q(QProgressDialog); + label = new QLabel(labelText, q); + int align = q->style()->styleHint(QStyle::SH_ProgressDialog_TextLabelAlignment, 0, q); + label->setAlignment(Qt::Alignment(align)); + bar = new QProgressBar(q); + bar->setRange(min, max); + autoClose = true; + autoReset = true; + forceHide = false; + QObject::connect(q, SIGNAL(canceled()), q, SLOT(cancel())); + forceTimer = new QTimer(q); + QObject::connect(forceTimer, SIGNAL(timeout()), q, SLOT(forceShow())); + if (useDefaultCancelText) { + retranslateStrings(); + } else { + q->setCancelButtonText(cancelText); + } +} + +void QProgressDialogPrivate::layout() +{ + Q_Q(QProgressDialog); + int sp = q->style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); + int mtb = q->style()->pixelMetric(QStyle::PM_DefaultTopLevelMargin); + int mlr = qMin(q->width() / 10, mtb); + const bool centered = + bool(q->style()->styleHint(QStyle::SH_ProgressDialog_CenterCancelButton, 0, q)); + + int additionalSpacing = 0; +#ifdef Q_OS_SYMBIAN + //In Symbian, we need to have wider margins for dialog borders, as the actual border is some pixels + //inside the dialog area (to enable transparent borders) + additionalSpacing = mlr; +#endif + + QSize cs = cancel ? cancel->sizeHint() : QSize(0,0); + QSize bh = bar->sizeHint(); + int cspc; + int lh = 0; + + // Find spacing and sizes that fit. It is important that a progress + // dialog can be made very small if the user demands it so. + for (int attempt=5; attempt--;) { + cspc = cancel ? cs.height() + sp : 0; + lh = qMax(0, q->height() - mtb - bh.height() - sp - cspc); + + if (lh < q->height()/4) { + // Getting cramped + sp /= 2; + mtb /= 2; + if (cancel) { + cs.setHeight(qMax(4,cs.height()-sp-2)); + } + bh.setHeight(qMax(4,bh.height()-sp-1)); + } else { + break; + } + } + + if (cancel) { + cancel->setGeometry( + centered ? q->width()/2 - cs.width()/2 : q->width() - mlr - cs.width(), + q->height() - mtb - cs.height(), + cs.width(), cs.height()); + } + + if (label) + label->setGeometry(mlr, additionalSpacing, q->width() - mlr * 2, lh); + bar->setGeometry(mlr, lh + sp + additionalSpacing, q->width() - mlr * 2, bh.height()); +} + +void QProgressDialogPrivate::retranslateStrings() +{ + Q_Q(QProgressDialog); + if (useDefaultCancelText) + q->setCancelButtonText(QProgressDialog::tr("Cancel")); +} + +void QProgressDialogPrivate::_q_disconnectOnClose() +{ + Q_Q(QProgressDialog); + if (receiverToDisconnectOnClose) { + QObject::disconnect(q, SIGNAL(canceled()), receiverToDisconnectOnClose, + memberToDisconnectOnClose); + receiverToDisconnectOnClose = 0; + } + memberToDisconnectOnClose.clear(); +} + +/*! + \class QProgressDialog + \brief The QProgressDialog class provides feedback on the progress of a slow operation. + \ingroup standard-dialogs + + + A progress dialog is used to give the user an indication of how long + an operation is going to take, and to demonstrate that the + application has not frozen. It can also give the user an opportunity + to abort the operation. + + A common problem with progress dialogs is that it is difficult to know + when to use them; operations take different amounts of time on different + hardware. QProgressDialog offers a solution to this problem: + it estimates the time the operation will take (based on time for + steps), and only shows itself if that estimate is beyond minimumDuration() + (4 seconds by default). + + Use setMinimum() and setMaximum() or the constructor to set the number of + "steps" in the operation and call setValue() as the operation + progresses. The number of steps can be chosen arbitrarily. It can be the + number of files copied, the number of bytes received, the number of + iterations through the main loop of your algorithm, or some other + suitable unit. Progress starts at the value set by setMinimum(), + and the progress dialog shows that the operation has finished when + you call setValue() with the value set by setMaximum() as its argument. + + The dialog automatically resets and hides itself at the end of the + operation. Use setAutoReset() and setAutoClose() to change this + behavior. Note that if you set a new maximum (using setMaximum() or + setRange()) that equals your current value(), the dialog will not + close regardless. + + There are two ways of using QProgressDialog: modal and modeless. + + Compared to a modeless QProgressDialog, a modal QProgressDialog is simpler + to use for the programmer. Do the operation in a loop, call \l setValue() at + intervals, and check for cancellation with wasCanceled(). For example: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 3 + + A modeless progress dialog is suitable for operations that take + place in the background, where the user is able to interact with the + application. Such operations are typically based on QTimer (or + QObject::timerEvent()), QSocketNotifier, or QUrlOperator; or performed + in a separate thread. A QProgressBar in the status bar of your main window + is often an alternative to a modeless progress dialog. + + You need to have an event loop to be running, connect the + canceled() signal to a slot that stops the operation, and call \l + setValue() at intervals. For example: + + \snippet doc/src/snippets/dialogs/dialogs.cpp 4 + \codeline + \snippet doc/src/snippets/dialogs/dialogs.cpp 5 + \codeline + \snippet doc/src/snippets/dialogs/dialogs.cpp 6 + + In both modes the progress dialog may be customized by + replacing the child widgets with custom widgets by using setLabel(), + setBar(), and setCancelButton(). + The functions setLabelText() and setCancelButtonText() + set the texts shown. + + \image plastique-progressdialog.png A progress dialog shown in the Plastique widget style. + + \sa QDialog, QProgressBar, {fowler}{GUI Design Handbook: Progress Indicator}, + {Find Files Example}, {Pixelator Example} +*/ + + +/*! + Constructs a progress dialog. + + Default settings: + \list + \i The label text is empty. + \i The cancel button text is (translated) "Cancel". + \i minimum is 0; + \i maximum is 100 + \endlist + + The \a parent argument is dialog's parent widget. The widget flags, \a f, are + passed to the QDialog::QDialog() constructor. + + \sa setLabelText(), setCancelButtonText(), setCancelButton(), + setMinimum(), setMaximum() +*/ + +QProgressDialog::QProgressDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(*(new QProgressDialogPrivate), parent, f) +{ + Q_D(QProgressDialog); + d->useDefaultCancelText = true; + d->init(QString::fromLatin1(""), QString(), 0, 100); +} + +/*! + Constructs a progress dialog. + + The \a labelText is the text used to remind the user what is progressing. + + The \a cancelButtonText is the text to display on the cancel button. If + QString() is passed then no cancel button is shown. + + The \a minimum and \a maximum is the number of steps in the operation for + which this progress dialog shows progress. For example, if the + operation is to examine 50 files, this value minimum value would be 0, + and the maximum would be 50. Before examining the first file, call + setValue(0). As each file is processed call setValue(1), setValue(2), + etc., finally calling setValue(50) after examining the last file. + + The \a parent argument is the dialog's parent widget. The parent, \a parent, and + widget flags, \a f, are passed to the QDialog::QDialog() constructor. + + \sa setLabelText(), setLabel(), setCancelButtonText(), setCancelButton(), + setMinimum(), setMaximum() +*/ + +QProgressDialog::QProgressDialog(const QString &labelText, + const QString &cancelButtonText, + int minimum, int maximum, + QWidget *parent, Qt::WindowFlags f) + : QDialog(*(new QProgressDialogPrivate), parent, f) +{ + Q_D(QProgressDialog); + d->init(labelText, cancelButtonText, minimum, maximum); +} + + +/*! + Destroys the progress dialog. +*/ + +QProgressDialog::~QProgressDialog() +{ +} + +/*! + \fn void QProgressDialog::canceled() + + This signal is emitted when the cancel button is clicked. + It is connected to the cancel() slot by default. + + \sa wasCanceled() +*/ + + +/*! + Sets the label to \a label. The progress dialog resizes to fit. The + label becomes owned by the progress dialog and will be deleted when + necessary, so do not pass the address of an object on the stack. + + \sa setLabelText() +*/ + +void QProgressDialog::setLabel(QLabel *label) +{ + Q_D(QProgressDialog); + delete d->label; + d->label = label; + if (label) { + if (label->parentWidget() == this) { + label->hide(); // until we resize + } else { + label->setParent(this, 0); + } + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + if (label) + label->show(); +} + + +/*! + \property QProgressDialog::labelText + \brief the label's text + + The default text is an empty string. +*/ + +QString QProgressDialog::labelText() const +{ + Q_D(const QProgressDialog); + if (d->label) + return d->label->text(); + return QString(); +} + +void QProgressDialog::setLabelText(const QString &text) +{ + Q_D(QProgressDialog); + if (d->label) { + d->label->setText(text); + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + } +} + + +/*! + Sets the cancel button to the push button, \a cancelButton. The + progress dialog takes ownership of this button which will be deleted + when necessary, so do not pass the address of an object that is on + the stack, i.e. use new() to create the button. If 0 is passed then + no cancel button will be shown. + + \sa setCancelButtonText() +*/ + +void QProgressDialog::setCancelButton(QPushButton *cancelButton) +{ + Q_D(QProgressDialog); + delete d->cancel; + d->cancel = cancelButton; + if (cancelButton) { + if (cancelButton->parentWidget() == this) { + cancelButton->hide(); // until we resize + } else { + cancelButton->setParent(this, 0); + } + connect(d->cancel, SIGNAL(clicked()), this, SIGNAL(canceled())); +#ifndef QT_NO_SHORTCUT + d->escapeShortcut = new QShortcut(Qt::Key_Escape, this, SIGNAL(canceled())); +#endif + } else { +#ifndef QT_NO_SHORTCUT + delete d->escapeShortcut; + d->escapeShortcut = 0; +#endif + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + if (cancelButton) +#if !defined(QT_SOFTKEYS_ENABLED) + cancelButton->show(); +#else + { + d->cancelAction = new QAction(cancelButton->text(), cancelButton); + d->cancelAction->setSoftKeyRole(QAction::NegativeSoftKey); + connect(d->cancelAction, SIGNAL(triggered()), this, SIGNAL(canceled())); + addAction(d->cancelAction); + } +#endif +} + +/*! + Sets the cancel button's text to \a cancelButtonText. If the text + is set to QString() then it will cause the cancel button to be + hidden and deleted. + + \sa setCancelButton() +*/ + +void QProgressDialog::setCancelButtonText(const QString &cancelButtonText) +{ + Q_D(QProgressDialog); + d->useDefaultCancelText = false; + + if (!cancelButtonText.isNull()) { + if (d->cancel) { + d->cancel->setText(cancelButtonText); +#ifdef QT_SOFTKEYS_ENABLED + d->cancelAction->setText(cancelButtonText); +#endif + } else { + setCancelButton(new QPushButton(cancelButtonText, this)); + } + } else { + setCancelButton(0); + } + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); +} + + +/*! + Sets the progress bar widget to \a bar. The progress dialog resizes to + fit. The progress dialog takes ownership of the progress \a bar which + will be deleted when necessary, so do not use a progress bar + allocated on the stack. +*/ + +void QProgressDialog::setBar(QProgressBar *bar) +{ + Q_D(QProgressDialog); + if (!bar) { + qWarning("QProgressDialog::setBar: Cannot set a null progress bar"); + return; + } +#ifndef QT_NO_DEBUG + if (value() > 0) + qWarning("QProgressDialog::setBar: Cannot set a new progress bar " + "while the old one is active"); +#endif + delete d->bar; + d->bar = bar; + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); +} + + +/*! + \property QProgressDialog::wasCanceled + \brief whether the dialog was canceled +*/ + +bool QProgressDialog::wasCanceled() const +{ + Q_D(const QProgressDialog); + return d->cancellation_flag; +} + + +/*! + \property QProgressDialog::maximum + \brief the highest value represented by the progress bar + + The default is 0. + + \sa minimum, setRange() +*/ + +int QProgressDialog::maximum() const +{ + Q_D(const QProgressDialog); + return d->bar->maximum(); +} + +void QProgressDialog::setMaximum(int maximum) +{ + Q_D(QProgressDialog); + d->bar->setMaximum(maximum); +} + +/*! + \property QProgressDialog::minimum + \brief the lowest value represented by the progress bar + + The default is 0. + + \sa maximum, setRange() +*/ + +int QProgressDialog::minimum() const +{ + Q_D(const QProgressDialog); + return d->bar->minimum(); +} + +void QProgressDialog::setMinimum(int minimum) +{ + Q_D(QProgressDialog); + d->bar->setMinimum(minimum); +} + +/*! + Sets the progress dialog's minimum and maximum values + to \a minimum and \a maximum, respectively. + + If \a maximum is smaller than \a minimum, \a minimum becomes the only + legal value. + + If the current value falls outside the new range, the progress + dialog is reset with reset(). + + \sa minimum, maximum +*/ +void QProgressDialog::setRange(int minimum, int maximum) +{ + Q_D(QProgressDialog); + d->bar->setRange(minimum, maximum); +} + + +/*! + Resets the progress dialog. + The progress dialog becomes hidden if autoClose() is true. + + \sa setAutoClose(), setAutoReset() +*/ + +void QProgressDialog::reset() +{ + Q_D(QProgressDialog); +#ifndef QT_NO_CURSOR + if (value() >= 0) { + if (parentWidget()) + parentWidget()->setCursor(d->parentCursor); + } +#endif + if (d->autoClose || d->forceHide) + hide(); + d->bar->reset(); + d->cancellation_flag = false; + d->shown_once = false; + d->forceTimer->stop(); + + /* + I wish we could disconnect the user slot provided to open() here but + unfortunately reset() is usually called before the slot has been invoked. + (reset() is itself invoked when canceled() is emitted.) + */ + if (d->receiverToDisconnectOnClose) + QMetaObject::invokeMethod(this, "_q_disconnectOnClose", Qt::QueuedConnection); +} + +/*! + Resets the progress dialog. wasCanceled() becomes true until + the progress dialog is reset. + The progress dialog becomes hidden. +*/ + +void QProgressDialog::cancel() +{ + Q_D(QProgressDialog); + d->forceHide = true; + reset(); + d->forceHide = false; + d->cancellation_flag = true; +} + + +int QProgressDialog::value() const +{ + Q_D(const QProgressDialog); + return d->bar->value(); +} + +/*! + \property QProgressDialog::value + \brief the current amount of progress made. + + For the progress dialog to work as expected, you should initially set + this property to 0 and finally set it to + QProgressDialog::maximum(); you can call setValue() any number of times + in-between. + + \warning If the progress dialog is modal + (see QProgressDialog::QProgressDialog()), + setValue() calls QApplication::processEvents(), so take care that + this does not cause undesirable re-entrancy in your code. For example, + don't use a QProgressDialog inside a paintEvent()! + + \sa minimum, maximum +*/ +void QProgressDialog::setValue(int progress) +{ + Q_D(QProgressDialog); + if (progress == d->bar->value() + || (d->bar->value() == -1 && progress == d->bar->maximum())) + return; + + d->bar->setValue(progress); + + if (d->shown_once) { + if (isModal()) + QApplication::processEvents(); + } else { + if (progress == 0) { + d->starttime.start(); + d->forceTimer->start(d->showTime); + return; + } else { + bool need_show; + int elapsed = d->starttime.elapsed(); + if (elapsed >= d->showTime) { + need_show = true; + } else { + if (elapsed > minWaitTime) { + int estimate; + int totalSteps = maximum() - minimum(); + int myprogress = progress - minimum(); + if ((totalSteps - myprogress) >= INT_MAX / elapsed) + estimate = (totalSteps - myprogress) / myprogress * elapsed; + else + estimate = elapsed * (totalSteps - myprogress) / myprogress; + need_show = estimate >= d->showTime; + } else { + need_show = false; + } + } + if (need_show) { + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + show(); + d->shown_once = true; + } + } +#ifdef Q_WS_MAC + QApplication::flush(); +#endif + } + + if (progress == d->bar->maximum() && d->autoReset) + reset(); +} + +/*! + Returns a size that fits the contents of the progress dialog. + The progress dialog resizes itself as required, so you should not + need to call this yourself. +*/ + +QSize QProgressDialog::sizeHint() const +{ + Q_D(const QProgressDialog); + QSize sh = d->label ? d->label->sizeHint() : QSize(0, 0); + QSize bh = d->bar->sizeHint(); + int margin = style()->pixelMetric(QStyle::PM_DefaultTopLevelMargin); + int spacing = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); + int h = margin * 2 + bh.height() + sh.height() + spacing; + if (d->cancel) + h += d->cancel->sizeHint().height() + spacing; +#ifdef Q_WS_S60 + if (QApplication::desktop()->size().height() > QApplication::desktop()->size().width()) + return QSize(qMax(QApplication::desktop()->size().width(), sh.width() + 2 * margin), h); + else + return QSize(qMax(QApplication::desktop()->size().height(), sh.width() + 2 * margin), h); +#else + return QSize(qMax(200, sh.width() + 2 * margin), h); +#endif +} + +/*!\reimp +*/ +void QProgressDialog::resizeEvent(QResizeEvent *) +{ + Q_D(QProgressDialog); + d->layout(); +} + +/*! + \reimp +*/ +void QProgressDialog::changeEvent(QEvent *ev) +{ + Q_D(QProgressDialog); + if (ev->type() == QEvent::StyleChange) { + d->layout(); + } else if (ev->type() == QEvent::LanguageChange) { + d->retranslateStrings(); + } + QDialog::changeEvent(ev); +} + +/*! + \property QProgressDialog::minimumDuration + \brief the time that must pass before the dialog appears + + If the expected duration of the task is less than the + minimumDuration, the dialog will not appear at all. This prevents + the dialog popping up for tasks that are quickly over. For tasks + that are expected to exceed the minimumDuration, the dialog will + pop up after the minimumDuration time or as soon as any progress + is set. + + If set to 0, the dialog is always shown as soon as any progress is + set. The default is 4000 milliseconds. +*/ +void QProgressDialog::setMinimumDuration(int ms) +{ + Q_D(QProgressDialog); + d->showTime = ms; + if (d->bar->value() == 0) { + d->forceTimer->stop(); + d->forceTimer->start(ms); + } +} + +int QProgressDialog::minimumDuration() const +{ + Q_D(const QProgressDialog); + return d->showTime; +} + + +/*! + \reimp +*/ + +void QProgressDialog::closeEvent(QCloseEvent *e) +{ + emit canceled(); + QDialog::closeEvent(e); +} + +/*! + \property QProgressDialog::autoReset + \brief whether the progress dialog calls reset() as soon as value() equals maximum() + + The default is true. + + \sa setAutoClose() +*/ + +void QProgressDialog::setAutoReset(bool b) +{ + Q_D(QProgressDialog); + d->autoReset = b; +} + +bool QProgressDialog::autoReset() const +{ + Q_D(const QProgressDialog); + return d->autoReset; +} + +/*! + \property QProgressDialog::autoClose + \brief whether the dialog gets hidden by reset() + + The default is true. + + \sa setAutoReset() +*/ + +void QProgressDialog::setAutoClose(bool close) +{ + Q_D(QProgressDialog); + d->autoClose = close; +} + +bool QProgressDialog::autoClose() const +{ + Q_D(const QProgressDialog); + return d->autoClose; +} + +/*! + \reimp +*/ + +void QProgressDialog::showEvent(QShowEvent *e) +{ + Q_D(QProgressDialog); + QDialog::showEvent(e); + int w = qMax(isVisible() ? width() : 0, sizeHint().width()); + int h = qMax(isVisible() ? height() : 0, sizeHint().height()); + resize(w, h); + d->forceTimer->stop(); +} + +/*! + Shows the dialog if it is still hidden after the algorithm has been started + and minimumDuration milliseconds have passed. + + \sa setMinimumDuration() +*/ + +void QProgressDialog::forceShow() +{ + Q_D(QProgressDialog); + d->forceTimer->stop(); + if (d->shown_once || d->cancellation_flag) + return; + + show(); + d->shown_once = true; +} + +/*! + \since 4.5 + \overload + + Opens the dialog and connects its accepted() signal to the slot specified + by \a receiver and \a member. + + The signal will be disconnected from the slot when the dialog is closed. +*/ +void QProgressDialog::open(QObject *receiver, const char *member) +{ + Q_D(QProgressDialog); + connect(this, SIGNAL(canceled()), receiver, member); + d->receiverToDisconnectOnClose = receiver; + d->memberToDisconnectOnClose = member; + QDialog::open(); +} + +QT_END_NAMESPACE + +#include "moc_qprogressdialog.cpp" + +#endif // QT_NO_PROGRESSDIALOG diff --git a/src/gui/dialogs/qprogressdialog.h b/src/gui/dialogs/qprogressdialog.h new file mode 100644 index 0000000000..21a40c152f --- /dev/null +++ b/src/gui/dialogs/qprogressdialog.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROGRESSDIALOG_H +#define QPROGRESSDIALOG_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROGRESSDIALOG + +class QPushButton; +class QLabel; +class QProgressBar; +class QTimer; +class QProgressDialogPrivate; + +class Q_GUI_EXPORT QProgressDialog : public QDialog +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QProgressDialog) + Q_PROPERTY(bool wasCanceled READ wasCanceled) + Q_PROPERTY(int minimum READ minimum WRITE setMinimum) + Q_PROPERTY(int maximum READ maximum WRITE setMaximum) + Q_PROPERTY(int value READ value WRITE setValue) + Q_PROPERTY(bool autoReset READ autoReset WRITE setAutoReset) + Q_PROPERTY(bool autoClose READ autoClose WRITE setAutoClose) + Q_PROPERTY(int minimumDuration READ minimumDuration WRITE setMinimumDuration) + Q_PROPERTY(QString labelText READ labelText WRITE setLabelText) + +public: + explicit QProgressDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + QProgressDialog(const QString &labelText, const QString &cancelButtonText, + int minimum, int maximum, QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QProgressDialog(); + + void setLabel(QLabel *label); + void setCancelButton(QPushButton *button); + void setBar(QProgressBar *bar); + + bool wasCanceled() const; + + int minimum() const; + int maximum() const; + + int value() const; + + QSize sizeHint() const; + + QString labelText() const; + int minimumDuration() const; + + void setAutoReset(bool reset); + bool autoReset() const; + void setAutoClose(bool close); + bool autoClose() const; + +#ifdef Q_NO_USING_KEYWORD +#ifndef Q_QDOC + void open() { QDialog::open(); } +#endif +#else + using QDialog::open; +#endif + void open(QObject *receiver, const char *member); + +public Q_SLOTS: + void cancel(); + void reset(); + void setMaximum(int maximum); + void setMinimum(int minimum); + void setRange(int minimum, int maximum); + void setValue(int progress); + void setLabelText(const QString &text); + void setCancelButtonText(const QString &text); + void setMinimumDuration(int ms); + +Q_SIGNALS: + void canceled(); + +protected: + void resizeEvent(QResizeEvent *event); + void closeEvent(QCloseEvent *event); + void changeEvent(QEvent *event); + void showEvent(QShowEvent *event); + +protected Q_SLOTS: + void forceShow(); + +private: + Q_DISABLE_COPY(QProgressDialog) + + Q_PRIVATE_SLOT(d_func(), void _q_disconnectOnClose()) +}; + +#endif // QT_NO_PROGRESSDIALOG + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROGRESSDIALOG_H diff --git a/src/gui/dialogs/qsidebar.cpp b/src/gui/dialogs/qsidebar.cpp new file mode 100644 index 0000000000..2869e1262b --- /dev/null +++ b/src/gui/dialogs/qsidebar.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsidebar_p.h" +#include "qfilesystemmodel.h" + +#ifndef QT_NO_FILEDIALOG + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +void QSideBarDelegate::initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const +{ + QStyledItemDelegate::initStyleOption(option,index); + QVariant value = index.data(QUrlModel::EnabledRole); + if (value.isValid()) { + //If the bookmark/entry is not enabled then we paint it in gray + if (!qvariant_cast(value)) + option->state &= ~QStyle::State_Enabled; + } +} + +/*! + QUrlModel lets you have indexes from a QFileSystemModel to a list. When QFileSystemModel + changes them QUrlModel will automatically update. + + Example usage: File dialog sidebar and combo box + */ +QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(0) +{ +} + +/*! + \reimp +*/ +QStringList QUrlModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + \reimp +*/ +Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags = QStandardItemModel::flags(index); + if (index.isValid()) { + flags &= ~Qt::ItemIsEditable; + // ### some future version could support "moving" urls onto a folder + flags &= ~Qt::ItemIsDropEnabled; + } + + if (index.data(Qt::DecorationRole).isNull()) + flags &= ~Qt::ItemIsEnabled; + + return flags; +} + +/*! + \reimp +*/ +QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const +{ + QList list; + for (int i = 0; i < indexes.count(); ++i) { + if (indexes.at(i).column() == 0) + list.append(indexes.at(i).data(UrlRole).toUrl()); + } + QMimeData *data = new QMimeData(); + data->setUrls(list); + return data; +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + Decide based upon the data if it should be accepted or not + + We only accept dirs and not files +*/ +bool QUrlModel::canDrop(QDragEnterEvent *event) +{ + if (!event->mimeData()->formats().contains(mimeTypes().first())) + return false; + + const QList list = event->mimeData()->urls(); + for (int i = 0; i < list.count(); ++i) { + QModelIndex idx = fileSystemModel->index(list.at(0).toLocalFile()); + if (!fileSystemModel->isDir(idx)) + return false; + } + return true; +} + +/*! + \reimp +*/ +bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (!data->formats().contains(mimeTypes().first())) + return false; + Q_UNUSED(action); + Q_UNUSED(column); + Q_UNUSED(parent); + addUrls(data->urls(), row); + return true; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \reimp + + If the role is the UrlRole then handle otherwise just pass to QStandardItemModel +*/ +bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (value.type() == QVariant::Url) { + QUrl url = value.toUrl(); + QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile()); + //On windows the popup display the "C:\", convert to nativeSeparators + if (showFullPath) + QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString())); + else { + QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole); + QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString()); + } + QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole), + Qt::DecorationRole); + QStandardItemModel::setData(index, url, UrlRole); + return true; + } + return QStandardItemModel::setData(index, value, role); +} + +void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex) +{ + setData(index, url, UrlRole); + if (url.path().isEmpty()) { + setData(index, fileSystemModel->myComputer()); + setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole); + } else { + QString newName; + if (showFullPath) { + //On windows the popup display the "C:\", convert to nativeSeparators + newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString()); + } else { + newName = dirIndex.data().toString(); + } + + QIcon newIcon = qvariant_cast(dirIndex.data(Qt::DecorationRole)); + if (!dirIndex.isValid()) { + newIcon = fileSystemModel->iconProvider()->icon(QFileIconProvider::Folder); + newName = QFileInfo(url.toLocalFile()).fileName(); + if (!invalidUrls.contains(url)) + invalidUrls.append(url); + //The bookmark is invalid then we set to false the EnabledRole + setData(index, false, EnabledRole); + } else { + //The bookmark is valid then we set to true the EnabledRole + setData(index, true, EnabledRole); + } + + // Make sure that we have at least 32x32 images + const QSize size = newIcon.actualSize(QSize(32,32)); + if (size.width() < 32) { + QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32)); + newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation)); + } + + if (index.data().toString() != newName) + setData(index, newName); + QIcon oldIcon = qvariant_cast(index.data(Qt::DecorationRole)); + if (oldIcon.cacheKey() != newIcon.cacheKey()) + setData(index, newIcon, Qt::DecorationRole); + } +} + +void QUrlModel::setUrls(const QList &list) +{ + removeRows(0, rowCount()); + invalidUrls.clear(); + watching.clear(); + addUrls(list, 0); +} + +/*! + Add urls \a list into the list at \a row. If move then movie + existing ones to row. + + \sa dropMimeData() +*/ +void QUrlModel::addUrls(const QList &list, int row, bool move) +{ + if (row == -1) + row = rowCount(); + row = qMin(row, rowCount()); + for (int i = list.count() - 1; i >= 0; --i) { + QUrl url = list.at(i); + if (!url.isValid() || url.scheme() != QLatin1String("file")) + continue; + //this makes sure the url is clean + const QString cleanUrl = QDir::cleanPath(url.toLocalFile()); + url = QUrl::fromLocalFile(cleanUrl); + + for (int j = 0; move && j < rowCount(); ++j) { + QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile(); +#if defined(Q_OS_WIN) + if (index(j, 0).data(UrlRole).toUrl().toLocalFile().toLower() == cleanUrl.toLower()) { +#else + if (index(j, 0).data(UrlRole).toUrl().toLocalFile() == cleanUrl) { +#endif + removeRow(j); + if (j <= row) + row--; + break; + } + } + row = qMax(row, 0); + QModelIndex idx = fileSystemModel->index(cleanUrl); + if (!fileSystemModel->isDir(idx)) + continue; + insertRows(row, 1); + setUrl(index(row, 0), url, idx); + watching.append(qMakePair(idx, cleanUrl)); + } +} + +/*! + Return the complete list of urls in a QList. +*/ +QList QUrlModel::urls() const +{ + QList list; + for (int i = 0; i < rowCount(); ++i) + list.append(data(index(i, 0), UrlRole).toUrl()); + return list; +} + +/*! + QFileSystemModel to get index's from, clears existing rows +*/ +void QUrlModel::setFileSystemModel(QFileSystemModel *model) +{ + if (model == fileSystemModel) + return; + if (fileSystemModel != 0) { + disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + disconnect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(layoutChanged())); + } + fileSystemModel = model; + if (fileSystemModel != 0) { + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(layoutChanged())); + } + clear(); + insertColumns(0, 1); +} + +/* + If one of the index's we are watching has changed update our internal data +*/ +void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + QModelIndex parent = topLeft.parent(); + for (int i = 0; i < watching.count(); ++i) { + QModelIndex index = watching.at(i).first; + if (index.model() && topLeft.model()) { + Q_ASSERT(index.model() == topLeft.model()); + } + if ( index.row() >= topLeft.row() + && index.row() <= bottomRight.row() + && index.column() >= topLeft.column() + && index.column() <= bottomRight.column() + && index.parent() == parent) { + changed(watching.at(i).second); + } + } +} + +/*! + Re-get all of our data, anything could have changed! + */ +void QUrlModel::layoutChanged() +{ + QStringList paths; + for (int i = 0; i < watching.count(); ++i) + paths.append(watching.at(i).second); + watching.clear(); + for (int i = 0; i < paths.count(); ++i) { + QString path = paths.at(i); + QModelIndex newIndex = fileSystemModel->index(path); + watching.append(QPair(newIndex, path)); + if (newIndex.isValid()) + changed(path); + } +} + +/*! + The following path changed data update our copy of that data + + \sa layoutChanged() dataChanged() +*/ +void QUrlModel::changed(const QString &path) +{ + for (int i = 0; i < rowCount(); ++i) { + QModelIndex idx = index(i, 0); + if (idx.data(UrlRole).toUrl().toLocalFile() == path) { + setData(idx, idx.data(UrlRole).toUrl()); + } + } +} + +QSidebar::QSidebar(QWidget *parent) : QListView(parent) +{ +} + +void QSidebar::init(QFileSystemModel *model, const QList &newUrls) +{ + // ### TODO make icon size dynamic + setIconSize(QSize(24,24)); + setUniformItemSizes(true); + urlModel = new QUrlModel(this); + urlModel->setFileSystemModel(model); + setModel(urlModel); + setItemDelegate(new QSideBarDelegate(this)); + + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); +#ifndef QT_NO_DRAGANDDROP + setDragDropMode(QAbstractItemView::DragDrop); +#endif + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(showContextMenu(QPoint))); + urlModel->setUrls(newUrls); + setCurrentIndex(this->model()->index(0,0)); +} + +QSidebar::~QSidebar() +{ +} + +#ifndef QT_NO_DRAGANDDROP +void QSidebar::dragEnterEvent(QDragEnterEvent *event) +{ + if (urlModel->canDrop(event)) + QListView::dragEnterEvent(event); +} +#endif // QT_NO_DRAGANDDROP + +QSize QSidebar::sizeHint() const +{ + if (model()) + return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth()); + return QListView::sizeHint(); +} + +void QSidebar::selectUrl(const QUrl &url) +{ + disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); + + selectionModel()->clear(); + for (int i = 0; i < model()->rowCount(); ++i) { + if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) { + selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select); + break; + } + } + + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(clicked(QModelIndex))); +} + +#ifndef QT_NO_MENU +/*! + \internal + + \sa removeEntry() +*/ +void QSidebar::showContextMenu(const QPoint &position) +{ + QList actions; + if (indexAt(position).isValid()) { + QAction *action = new QAction(QFileDialog::tr("Remove"), this); + if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty()) + action->setEnabled(false); + connect(action, SIGNAL(triggered()), this, SLOT(removeEntry())); + actions.append(action); + } + if (actions.count() > 0) + QMenu::exec(actions, mapToGlobal(position)); +} +#endif // QT_NO_MENU + +/*! + \internal + + \sa showContextMenu() +*/ +void QSidebar::removeEntry() +{ + QList idxs = selectionModel()->selectedIndexes(); + QList indexes; + for (int i = 0; i < idxs.count(); i++) + indexes.append(idxs.at(i)); + + for (int i = 0; i < indexes.count(); ++i) + if (!indexes.at(i).data(QUrlModel::UrlRole).toUrl().path().isEmpty()) + model()->removeRow(indexes.at(i).row()); +} + +/*! + \internal + + \sa goToUrl() +*/ +void QSidebar::clicked(const QModelIndex &index) +{ + QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl(); + emit goToUrl(url); + selectUrl(url); +} + +/*! + \reimp + Don't automatically select something + */ +void QSidebar::focusInEvent(QFocusEvent *event) +{ + QAbstractScrollArea::focusInEvent(event); + viewport()->update(); +} + +/*! + \reimp + */ +bool QSidebar::event(QEvent * event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent* ke = (QKeyEvent*) event; + if (ke->key() == Qt::Key_Delete) { + removeEntry(); + return true; + } + } + return QListView::event(event); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/dialogs/qsidebar_p.h b/src/gui/dialogs/qsidebar_p.h new file mode 100644 index 0000000000..188f4d7f0f --- /dev/null +++ b/src/gui/dialogs/qsidebar_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIDEBAR_H +#define QSIDEBAR_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 +#include +#include +#include + +#ifndef QT_NO_FILEDIALOG + +QT_BEGIN_NAMESPACE + +class QFileSystemModel; + +class QSideBarDelegate : public QStyledItemDelegate +{ + public: + QSideBarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {} + void initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const; +}; + +class Q_AUTOTEST_EXPORT QUrlModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Roles { + UrlRole = Qt::UserRole + 1, + EnabledRole = Qt::UserRole + 2 + }; + + QUrlModel(QObject *parent = 0); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; +#ifndef QT_NO_DRAGANDDROP + bool canDrop(QDragEnterEvent *event); + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); +#endif + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole); + + void setUrls(const QList &list); + void addUrls(const QList &urls, int row = -1, bool move = true); + QList urls() const; + void setFileSystemModel(QFileSystemModel *model); + bool showFullPath; + +private Q_SLOTS: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void layoutChanged(); + +private: + void setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex); + void changed(const QString &path); + void addIndexToWatch(const QString &path, const QModelIndex &index); + QFileSystemModel *fileSystemModel; + QList > watching; + QList invalidUrls; +}; + +class Q_AUTOTEST_EXPORT QSidebar : public QListView +{ + Q_OBJECT + +Q_SIGNALS: + void goToUrl(const QUrl &url); + +public: + QSidebar(QWidget *parent = 0); + void init(QFileSystemModel *model, const QList &newUrls); + ~QSidebar(); + + QSize sizeHint() const; + + void setUrls(const QList &list) { urlModel->setUrls(list); } + void addUrls(const QList &list, int row) { urlModel->addUrls(list, row); } + QList urls() const { return urlModel->urls(); } + + void selectUrl(const QUrl &url); + +protected: + bool event(QEvent * e); + void focusInEvent(QFocusEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event); +#endif + +private Q_SLOTS: + void clicked(const QModelIndex &index); +#ifndef QT_NO_MENU + void showContextMenu(const QPoint &position); +#endif + void removeEntry(); + +private: + QUrlModel *urlModel; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_FILEDIALOG + +#endif // QSIDEBAR_H + diff --git a/src/gui/dialogs/qwizard.cpp b/src/gui/dialogs/qwizard.cpp new file mode 100644 index 0000000000..ce5118f91d --- /dev/null +++ b/src/gui/dialogs/qwizard.cpp @@ -0,0 +1,3928 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwizard.h" + +#ifndef QT_NO_WIZARD + +#include "qabstractspinbox.h" +#include "qalgorithms.h" +#include "qapplication.h" +#include "qboxlayout.h" +#include "qlayoutitem.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qframe.h" +#include "qlabel.h" +#include "qlineedit.h" +#include "qpainter.h" +#include "qpushbutton.h" +#include "qset.h" +#include "qstyle.h" +#include "qvarlengtharray.h" +#if defined(Q_WS_MAC) +#include "private/qt_mac_p.h" +#include "qlibrary.h" +#elif !defined(QT_NO_STYLE_WINDOWSVISTA) +#include "qwizard_win_p.h" +#include "qtimer.h" +#endif + +#include "private/qdialog_p.h" +#include + +#ifdef Q_WS_WINCE +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wce.cpp +#endif + +#include // for memset() + +#ifdef QT_SOFTKEYS_ENABLED +#include "qaction.h" +#endif + +QT_BEGIN_NAMESPACE + +// These fudge terms were needed a few places to obtain pixel-perfect results +const int GapBetweenLogoAndRightEdge = 5; +const int ModernHeaderTopMargin = 2; +const int ClassicHMargin = 4; +const int MacButtonTopMargin = 13; +const int MacLayoutLeftMargin = 20; +//const int MacLayoutTopMargin = 14; // Unused. Save some space and avoid warning. +const int MacLayoutRightMargin = 20; +const int MacLayoutBottomMargin = 17; + +static void changeSpacerSize(QLayout *layout, int index, int width, int height) +{ + QSpacerItem *spacer = layout->itemAt(index)->spacerItem(); + if (!spacer) + return; + spacer->changeSize(width, height); +} + +static QWidget *iWantTheFocus(QWidget *ancestor) +{ + const int MaxIterations = 100; + + QWidget *candidate = ancestor; + for (int i = 0; i < MaxIterations; ++i) { + candidate = candidate->nextInFocusChain(); + if (!candidate) + break; + + if (candidate->focusPolicy() & Qt::TabFocus) { + if (candidate != ancestor && ancestor->isAncestorOf(candidate)) + return candidate; + } + } + return 0; +} + +static bool objectInheritsXAndXIsCloserThanY(const QObject *object, const QByteArray &classX, + const QByteArray &classY) +{ + const QMetaObject *metaObject = object->metaObject(); + while (metaObject) { + if (metaObject->className() == classX) + return true; + if (metaObject->className() == classY) + return false; + metaObject = metaObject->superClass(); + } + return false; +} + +const int NFallbackDefaultProperties = 7; + +const struct { + const char *className; + const char *property; + const char *changedSignal; +} fallbackProperties[NFallbackDefaultProperties] = { + // If you modify this list, make sure to update the documentation (and the auto test) + { "QAbstractButton", "checked", SIGNAL(toggled(bool)) }, + { "QAbstractSlider", "value", SIGNAL(valueChanged(int)) }, + { "QComboBox", "currentIndex", SIGNAL(currentIndexChanged(int)) }, + { "QDateTimeEdit", "dateTime", SIGNAL(dateTimeChanged(QDateTime)) }, + { "QLineEdit", "text", SIGNAL(textChanged(QString)) }, + { "QListWidget", "currentRow", SIGNAL(currentRowChanged(int)) }, + { "QSpinBox", "value", SIGNAL(valueChanged(int)) } +}; + +class QWizardDefaultProperty +{ +public: + QByteArray className; + QByteArray property; + QByteArray changedSignal; + + inline QWizardDefaultProperty() {} + inline QWizardDefaultProperty(const char *className, const char *property, + const char *changedSignal) + : className(className), property(property), changedSignal(changedSignal) {} +}; + +class QWizardField +{ +public: + inline QWizardField() {} + QWizardField(QWizardPage *page, const QString &spec, QObject *object, const char *property, + const char *changedSignal); + + void resolve(const QVector &defaultPropertyTable); + void findProperty(const QWizardDefaultProperty *properties, int propertyCount); + + QWizardPage *page; + QString name; + bool mandatory; + QObject *object; + QByteArray property; + QByteArray changedSignal; + QVariant initialValue; +}; + +QWizardField::QWizardField(QWizardPage *page, const QString &spec, QObject *object, + const char *property, const char *changedSignal) + : page(page), name(spec), mandatory(false), object(object), property(property), + changedSignal(changedSignal) +{ + if (name.endsWith(QLatin1Char('*'))) { + name.chop(1); + mandatory = true; + } +} + +void QWizardField::resolve(const QVector &defaultPropertyTable) +{ + if (property.isEmpty()) + findProperty(defaultPropertyTable.constData(), defaultPropertyTable.count()); + initialValue = object->property(property); +} + +void QWizardField::findProperty(const QWizardDefaultProperty *properties, int propertyCount) +{ + QByteArray className; + + for (int i = 0; i < propertyCount; ++i) { + if (objectInheritsXAndXIsCloserThanY(object, properties[i].className, className)) { + className = properties[i].className; + property = properties[i].property; + changedSignal = properties[i].changedSignal; + } + } +} + +class QWizardLayoutInfo +{ +public: + inline QWizardLayoutInfo() + : topLevelMarginLeft(-1), topLevelMarginRight(-1), topLevelMarginTop(-1), + topLevelMarginBottom(-1), childMarginLeft(-1), childMarginRight(-1), + childMarginTop(-1), childMarginBottom(-1), hspacing(-1), vspacing(-1), + wizStyle(QWizard::ClassicStyle), header(false), watermark(false), title(false), + subTitle(false), extension(false), sideWidget(false) {} + + int topLevelMarginLeft; + int topLevelMarginRight; + int topLevelMarginTop; + int topLevelMarginBottom; + int childMarginLeft; + int childMarginRight; + int childMarginTop; + int childMarginBottom; + int hspacing; + int vspacing; + int buttonSpacing; + QWizard::WizardStyle wizStyle; + bool header; + bool watermark; + bool title; + bool subTitle; + bool extension; + bool sideWidget; + + bool operator==(const QWizardLayoutInfo &other); + inline bool operator!=(const QWizardLayoutInfo &other) { return !operator==(other); } +}; + +bool QWizardLayoutInfo::operator==(const QWizardLayoutInfo &other) +{ + return topLevelMarginLeft == other.topLevelMarginLeft + && topLevelMarginRight == other.topLevelMarginRight + && topLevelMarginTop == other.topLevelMarginTop + && topLevelMarginBottom == other.topLevelMarginBottom + && childMarginLeft == other.childMarginLeft + && childMarginRight == other.childMarginRight + && childMarginTop == other.childMarginTop + && childMarginBottom == other.childMarginBottom + && hspacing == other.hspacing + && vspacing == other.vspacing + && buttonSpacing == other.buttonSpacing + && wizStyle == other.wizStyle + && header == other.header + && watermark == other.watermark + && title == other.title + && subTitle == other.subTitle + && extension == other.extension + && sideWidget == other.sideWidget; +} + +class QWizardHeader : public QWidget +{ +public: + enum RulerType { Ruler }; + + inline QWizardHeader(RulerType /* ruler */, QWidget *parent = 0) + : QWidget(parent) { setFixedHeight(2); } + QWizardHeader(QWidget *parent = 0); + + void setup(const QWizardLayoutInfo &info, const QString &title, + const QString &subTitle, const QPixmap &logo, const QPixmap &banner, + Qt::TextFormat titleFormat, Qt::TextFormat subTitleFormat); + +protected: + void paintEvent(QPaintEvent *event); +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +private: + bool vistaDisabled() const; +#endif +private: + QLabel *titleLabel; + QLabel *subTitleLabel; + QLabel *logoLabel; + QGridLayout *layout; + QPixmap bannerPixmap; +}; + +QWizardHeader::QWizardHeader(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setBackgroundRole(QPalette::Base); + + titleLabel = new QLabel(this); + titleLabel->setBackgroundRole(QPalette::Base); + + subTitleLabel = new QLabel(this); + subTitleLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + subTitleLabel->setWordWrap(true); + + logoLabel = new QLabel(this); + + QFont font = titleLabel->font(); + font.setBold(true); + titleLabel->setFont(font); + + layout = new QGridLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + layout->setRowMinimumHeight(3, 1); + layout->setRowStretch(4, 1); + + layout->setColumnStretch(2, 1); + layout->setColumnMinimumWidth(4, 2 * GapBetweenLogoAndRightEdge); + layout->setColumnMinimumWidth(6, GapBetweenLogoAndRightEdge); + + layout->addWidget(titleLabel, 2, 1, 1, 2); + layout->addWidget(subTitleLabel, 4, 2); + layout->addWidget(logoLabel, 1, 5, 5, 1); +} + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +bool QWizardHeader::vistaDisabled() const +{ + bool styleDisabled = false; + QWizard *wiz = parentWidget() ? qobject_cast (parentWidget()->parentWidget()) : 0; + if (wiz) { + // Designer dosen't support the Vista style for Wizards. This property is used to turn + // off the Vista style. + const QVariant v = wiz->property("_q_wizard_vista_off"); + styleDisabled = v.isValid() && v.toBool(); + } + return styleDisabled; +} +#endif + +void QWizardHeader::setup(const QWizardLayoutInfo &info, const QString &title, + const QString &subTitle, const QPixmap &logo, const QPixmap &banner, + Qt::TextFormat titleFormat, Qt::TextFormat subTitleFormat) +{ + bool modern = ((info.wizStyle == QWizard::ModernStyle) +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + || ((info.wizStyle == QWizard::AeroStyle + && QVistaHelper::vistaState() == QVistaHelper::Classic) || vistaDisabled()) +#endif + ); + + layout->setRowMinimumHeight(0, modern ? ModernHeaderTopMargin : 0); + layout->setRowMinimumHeight(1, modern ? info.topLevelMarginTop - ModernHeaderTopMargin - 1 : 0); + layout->setRowMinimumHeight(6, (modern ? 3 : GapBetweenLogoAndRightEdge) + 2); + + int minColumnWidth0 = modern ? info.topLevelMarginLeft + info.topLevelMarginRight : 0; + int minColumnWidth1 = modern ? info.topLevelMarginLeft + info.topLevelMarginRight + 1 + : info.topLevelMarginLeft + ClassicHMargin; + layout->setColumnMinimumWidth(0, minColumnWidth0); + layout->setColumnMinimumWidth(1, minColumnWidth1); + + titleLabel->setTextFormat(titleFormat); + titleLabel->setText(title); + logoLabel->setPixmap(logo); + + subTitleLabel->setTextFormat(subTitleFormat); + subTitleLabel->setText(QLatin1String("Pq\nPq")); + int desiredSubTitleHeight = subTitleLabel->sizeHint().height(); + subTitleLabel->setText(subTitle); + + if (modern) { + bannerPixmap = banner; + } else { + bannerPixmap = QPixmap(); + } + + if (bannerPixmap.isNull()) { + /* + There is no widthForHeight() function, so we simulate it with a loop. + */ + int candidateSubTitleWidth = qMin(512, 2 * QApplication::desktop()->width() / 3); + int delta = candidateSubTitleWidth >> 1; + while (delta > 0) { + if (subTitleLabel->heightForWidth(candidateSubTitleWidth - delta) + <= desiredSubTitleHeight) + candidateSubTitleWidth -= delta; + delta >>= 1; + } + + subTitleLabel->setMinimumSize(candidateSubTitleWidth, desiredSubTitleHeight); + + QSize size = layout->totalMinimumSize(); + setMinimumSize(size); + setMaximumSize(QWIDGETSIZE_MAX, size.height()); + } else { + subTitleLabel->setMinimumSize(0, 0); + setFixedSize(banner.size() + QSize(0, 2)); + } + updateGeometry(); +} + +void QWizardHeader::paintEvent(QPaintEvent * /* event */) +{ + QPainter painter(this); + painter.drawPixmap(0, 0, bannerPixmap); + + int x = width() - 2; + int y = height() - 2; + const QPalette &pal = palette(); + painter.setPen(pal.mid().color()); + painter.drawLine(0, y, x, y); + painter.setPen(pal.base().color()); + painter.drawPoint(x + 1, y); + painter.drawLine(0, y + 1, x + 1, y + 1); +} + +// We save one vtable by basing QWizardRuler on QWizardHeader +class QWizardRuler : public QWizardHeader +{ +public: + inline QWizardRuler(QWidget *parent = 0) + : QWizardHeader(Ruler, parent) {} +}; + +class QWatermarkLabel : public QLabel +{ +public: + QWatermarkLabel(QWidget *parent, QWidget *sideWidget) : QLabel(parent), m_sideWidget(sideWidget) { + m_layout = new QVBoxLayout(this); + if (m_sideWidget) + m_layout->addWidget(m_sideWidget); + } + + QSize minimumSizeHint() const { + if (!pixmap() && !pixmap()->isNull()) + return pixmap()->size(); + return QFrame::minimumSizeHint(); + } + + void setSideWidget(QWidget *widget) { + if (m_sideWidget == widget) + return; + if (m_sideWidget) { + m_layout->removeWidget(m_sideWidget); + m_sideWidget->hide(); + } + m_sideWidget = widget; + if (m_sideWidget) + m_layout->addWidget(m_sideWidget); + } + QWidget *sideWidget() const { + return m_sideWidget; + } +private: + QVBoxLayout *m_layout; + QWidget *m_sideWidget; +}; + +class QWizardPagePrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QWizardPage) + +public: + enum TriState { Tri_Unknown = -1, Tri_False, Tri_True }; + + inline QWizardPagePrivate() + : wizard(0), completeState(Tri_Unknown), explicitlyFinal(false), commit(false) {} + + bool cachedIsComplete() const; + void _q_maybeEmitCompleteChanged(); + void _q_updateCachedCompleteState(); + + QWizard *wizard; + QString title; + QString subTitle; + QPixmap pixmaps[QWizard::NPixmaps]; + QVector pendingFields; + mutable TriState completeState; + bool explicitlyFinal; + bool commit; + QMap buttonCustomTexts; +}; + +bool QWizardPagePrivate::cachedIsComplete() const +{ + Q_Q(const QWizardPage); + if (completeState == Tri_Unknown) + completeState = q->isComplete() ? Tri_True : Tri_False; + return completeState == Tri_True; +} + +void QWizardPagePrivate::_q_maybeEmitCompleteChanged() +{ + Q_Q(QWizardPage); + TriState newState = q->isComplete() ? Tri_True : Tri_False; + if (newState != completeState) + emit q->completeChanged(); +} + +void QWizardPagePrivate::_q_updateCachedCompleteState() +{ + Q_Q(QWizardPage); + completeState = q->isComplete() ? Tri_True : Tri_False; +} + +class QWizardAntiFlickerWidget : public QWidget +{ + QWizard *wizard; + QWizardPrivate *wizardPrivate; +public: + QWizardAntiFlickerWidget(QWizard *wizard, QWizardPrivate *wizardPrivate) + : QWidget(wizard) + , wizard(wizard) + , wizardPrivate(wizardPrivate) {} +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +protected: + void paintEvent(QPaintEvent *); +#endif +}; + +class QWizardPrivate : public QDialogPrivate +{ + Q_DECLARE_PUBLIC(QWizard) + +public: + typedef QMap PageMap; + + enum Direction { + Backward, + Forward + }; + + inline QWizardPrivate() + : start(-1) + , startSetByUser(false) + , current(-1) + , canContinue(false) + , canFinish(false) + , disableUpdatesCount(0) + , opts(0) + , buttonsHaveCustomLayout(false) + , titleFmt(Qt::AutoText) + , subTitleFmt(Qt::AutoText) + , placeholderWidget1(0) + , placeholderWidget2(0) + , headerWidget(0) + , watermarkLabel(0) + , sideWidget(0) + , titleLabel(0) + , subTitleLabel(0) + , bottomRuler(0) +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + , vistaInitPending(false) + , vistaState(QVistaHelper::Dirty) + , vistaStateChanged(false) + , inHandleAeroStyleChange(false) +#endif + , minimumWidth(0) + , minimumHeight(0) + , maximumWidth(QWIDGETSIZE_MAX) + , maximumHeight(QWIDGETSIZE_MAX) + { + for (int i = 0; i < QWizard::NButtons; ++i) { + btns[i] = 0; +#ifdef QT_SOFTKEYS_ENABLED + softKeys[i] = 0; +#endif + } +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + vistaInitPending = true; +#endif + } + + void init(); + void reset(); + void cleanupPagesNotInHistory(); + void addField(const QWizardField &field); + void removeFieldAt(int index); + void switchToPage(int newId, Direction direction); + QWizardLayoutInfo layoutInfoForCurrentPage(); + void recreateLayout(const QWizardLayoutInfo &info); + void updateLayout(); + void updateMinMaxSizes(const QWizardLayoutInfo &info); + void updateCurrentPage(); + bool ensureButton(QWizard::WizardButton which) const; + void connectButton(QWizard::WizardButton which) const; + void updateButtonTexts(); + void updateButtonLayout(); + void setButtonLayout(const QWizard::WizardButton *array, int size); + bool buttonLayoutContains(QWizard::WizardButton which); + void updatePixmap(QWizard::WizardPixmap which); +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + bool vistaDisabled() const; + bool isVistaThemeEnabled(QVistaHelper::VistaState state) const; + void handleAeroStyleChange(); +#endif + bool isVistaThemeEnabled() const; + void disableUpdates(); + void enableUpdates(); + void _q_emitCustomButtonClicked(); + void _q_updateButtonStates(); + void _q_handleFieldObjectDestroyed(QObject *); + void setStyle(QStyle *style); +#ifdef Q_WS_MAC + static QPixmap findDefaultBackgroundPixmap(); +#endif + + PageMap pageMap; + QVector fields; + QMap fieldIndexMap; + QVector defaultPropertyTable; + QList history; + QSet initialized; // ### remove and move bit to QWizardPage? + int start; + bool startSetByUser; + int current; + bool canContinue; + bool canFinish; + QWizardLayoutInfo layoutInfo; + int disableUpdatesCount; + + QWizard::WizardStyle wizStyle; + QWizard::WizardOptions opts; + QMap buttonCustomTexts; + bool buttonsHaveCustomLayout; + QList buttonsCustomLayout; + Qt::TextFormat titleFmt; + Qt::TextFormat subTitleFmt; + mutable QPixmap defaultPixmaps[QWizard::NPixmaps]; + + union { + // keep in sync with QWizard::WizardButton + mutable struct { + QAbstractButton *back; + QAbstractButton *next; + QAbstractButton *commit; + QAbstractButton *finish; + QAbstractButton *cancel; + QAbstractButton *help; + } btn; + mutable QAbstractButton *btns[QWizard::NButtons]; + }; + QWizardAntiFlickerWidget *antiFlickerWidget; + QWidget *placeholderWidget1; + QWidget *placeholderWidget2; + QWizardHeader *headerWidget; + QWatermarkLabel *watermarkLabel; + QWidget *sideWidget; + QFrame *pageFrame; + QLabel *titleLabel; + QLabel *subTitleLabel; + QWizardRuler *bottomRuler; +#ifdef QT_SOFTKEYS_ENABLED + mutable QAction *softKeys[QWizard::NButtons]; +#endif + + QVBoxLayout *pageVBoxLayout; + QHBoxLayout *buttonLayout; + QGridLayout *mainLayout; + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + QVistaHelper *vistaHelper; + bool vistaInitPending; + QVistaHelper::VistaState vistaState; + bool vistaStateChanged; + bool inHandleAeroStyleChange; +#endif + int minimumWidth; + int minimumHeight; + int maximumWidth; + int maximumHeight; +}; + +static QString buttonDefaultText(int wstyle, int which, const QWizardPrivate *wizardPrivate) +{ +#if defined(QT_NO_STYLE_WINDOWSVISTA) + Q_UNUSED(wizardPrivate); +#endif + const bool macStyle = (wstyle == QWizard::MacStyle); + switch (which) { + case QWizard::BackButton: + return macStyle ? QWizard::tr("Go Back") : QWizard::tr("< &Back"); + case QWizard::NextButton: + if (macStyle) + return QWizard::tr("Continue"); + else + return wizardPrivate->isVistaThemeEnabled() + ? QWizard::tr("&Next") : QWizard::tr("&Next >"); + case QWizard::CommitButton: + return QWizard::tr("Commit"); + case QWizard::FinishButton: + return macStyle ? QWizard::tr("Done") : QWizard::tr("&Finish"); + case QWizard::CancelButton: + return QWizard::tr("Cancel"); + case QWizard::HelpButton: + return macStyle ? QWizard::tr("Help") : QWizard::tr("&Help"); + default: + return QString(); + } +} + +void QWizardPrivate::init() +{ + Q_Q(QWizard); + + antiFlickerWidget = new QWizardAntiFlickerWidget(q, this); + wizStyle = QWizard::WizardStyle(q->style()->styleHint(QStyle::SH_WizardStyle, 0, q)); + if (wizStyle == QWizard::MacStyle) { + opts = (QWizard::NoDefaultButton | QWizard::NoCancelButton); + } else if (wizStyle == QWizard::ModernStyle) { + opts = QWizard::HelpButtonOnRight; + } + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + vistaHelper = new QVistaHelper(q); +#endif + + // create these buttons right away; create the other buttons as necessary + ensureButton(QWizard::BackButton); + ensureButton(QWizard::NextButton); + ensureButton(QWizard::CommitButton); + ensureButton(QWizard::FinishButton); + + pageFrame = new QFrame(antiFlickerWidget); + pageFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + pageVBoxLayout = new QVBoxLayout(pageFrame); + pageVBoxLayout->setSpacing(0); + pageVBoxLayout->addSpacing(0); + QSpacerItem *spacerItem = new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); + pageVBoxLayout->addItem(spacerItem); + + buttonLayout = new QHBoxLayout; + mainLayout = new QGridLayout(antiFlickerWidget); + mainLayout->setSizeConstraint(QLayout::SetNoConstraint); + + updateButtonLayout(); + + for (int i = 0; i < NFallbackDefaultProperties; ++i) + defaultPropertyTable.append(QWizardDefaultProperty(fallbackProperties[i].className, + fallbackProperties[i].property, + fallbackProperties[i].changedSignal)); +} + +void QWizardPrivate::reset() +{ + Q_Q(QWizard); + if (current != -1) { + q->currentPage()->hide(); + cleanupPagesNotInHistory(); + for (int i = history.count() - 1; i >= 0; --i) + q->cleanupPage(history.at(i)); + history.clear(); + initialized.clear(); + + current = -1; + emit q->currentIdChanged(-1); + } +} + +void QWizardPrivate::cleanupPagesNotInHistory() +{ + Q_Q(QWizard); + + const QSet original = initialized; + QSet::const_iterator i = original.constBegin(); + QSet::const_iterator end = original.constEnd(); + + for (; i != end; ++i) { + if (!history.contains(*i)) { + q->cleanupPage(*i); + initialized.remove(*i); + } + } +} + +void QWizardPrivate::addField(const QWizardField &field) +{ + Q_Q(QWizard); + + QWizardField myField = field; + myField.resolve(defaultPropertyTable); + + if (fieldIndexMap.contains(myField.name)) { + qWarning("QWizardPage::addField: Duplicate field '%s'", qPrintable(myField.name)); + return; + } + + fieldIndexMap.insert(myField.name, fields.count()); + fields += myField; + if (myField.mandatory && !myField.changedSignal.isEmpty()) + QObject::connect(myField.object, myField.changedSignal, + myField.page, SLOT(_q_maybeEmitCompleteChanged())); + QObject::connect( + myField.object, SIGNAL(destroyed(QObject*)), q, + SLOT(_q_handleFieldObjectDestroyed(QObject*))); +} + +void QWizardPrivate::removeFieldAt(int index) +{ + Q_Q(QWizard); + + const QWizardField &field = fields.at(index); + fieldIndexMap.remove(field.name); + if (field.mandatory && !field.changedSignal.isEmpty()) + QObject::disconnect(field.object, field.changedSignal, + field.page, SLOT(_q_maybeEmitCompleteChanged())); + QObject::disconnect( + field.object, SIGNAL(destroyed(QObject*)), q, + SLOT(_q_handleFieldObjectDestroyed(QObject*))); + fields.remove(index); +} + +void QWizardPrivate::switchToPage(int newId, Direction direction) +{ + Q_Q(QWizard); + + disableUpdates(); + + int oldId = current; + if (QWizardPage *oldPage = q->currentPage()) { + oldPage->hide(); + + if (direction == Backward) { + if (!(opts & QWizard::IndependentPages)) { + q->cleanupPage(oldId); + initialized.remove(oldId); + } + Q_ASSERT(history.last() == oldId); + history.removeLast(); + Q_ASSERT(history.last() == newId); + } + } + + current = newId; + + QWizardPage *newPage = q->currentPage(); + if (newPage) { + if (direction == Forward) { + if (!initialized.contains(current)) { + initialized.insert(current); + q->initializePage(current); + } + history.append(current); + } + newPage->show(); + } + + canContinue = (q->nextId() != -1); + canFinish = (newPage && newPage->isFinalPage()); + + _q_updateButtonStates(); + updateButtonTexts(); + + const QWizard::WizardButton nextOrCommit = + newPage && newPage->isCommitPage() ? QWizard::CommitButton : QWizard::NextButton; + QAbstractButton *nextOrFinishButton = + btns[canContinue ? nextOrCommit : QWizard::FinishButton]; + QWidget *candidate = 0; + + /* + If there is no default button and the Next or Finish button + is enabled, give focus directly to it as a convenience to the + user. This is the normal case on Mac OS X. + + Otherwise, give the focus to the new page's first child that + can handle it. If there is no such child, give the focus to + Next or Finish. + */ + if ((opts & QWizard::NoDefaultButton) && nextOrFinishButton->isEnabled()) { + candidate = nextOrFinishButton; + } else if (newPage) { + candidate = iWantTheFocus(newPage); + } + if (!candidate) + candidate = nextOrFinishButton; + candidate->setFocus(); + + if (wizStyle == QWizard::MacStyle) + q->updateGeometry(); + + enableUpdates(); + updateLayout(); + + emit q->currentIdChanged(current); +} + +// keep in sync with QWizard::WizardButton +static const char * const buttonSlots[QWizard::NStandardButtons] = { + SLOT(back()), SLOT(next()), SLOT(next()), SLOT(accept()), SLOT(reject()), + SIGNAL(helpRequested()) +}; + +QWizardLayoutInfo QWizardPrivate::layoutInfoForCurrentPage() +{ + Q_Q(QWizard); + QStyle *style = q->style(); + + QWizardLayoutInfo info; + + const int layoutHorizontalSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + info.topLevelMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, q); + info.topLevelMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, q); + info.topLevelMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, q); + info.topLevelMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, q); + info.childMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, titleLabel); + info.childMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, titleLabel); + info.childMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, titleLabel); + info.childMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, titleLabel); + info.hspacing = (layoutHorizontalSpacing == -1) + ? style->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal) + : layoutHorizontalSpacing; + info.vspacing = style->pixelMetric(QStyle::PM_LayoutVerticalSpacing); + info.buttonSpacing = (layoutHorizontalSpacing == -1) + ? style->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal) + : layoutHorizontalSpacing; + + if (wizStyle == QWizard::MacStyle) + info.buttonSpacing = 12; + + info.wizStyle = wizStyle; + if ((info.wizStyle == QWizard::AeroStyle) +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + && (QVistaHelper::vistaState() == QVistaHelper::Classic || vistaDisabled()) +#endif + ) + info.wizStyle = QWizard::ModernStyle; + + QString titleText; + QString subTitleText; + QPixmap backgroundPixmap; + QPixmap watermarkPixmap; + + if (QWizardPage *page = q->currentPage()) { + titleText = page->title(); + subTitleText = page->subTitle(); + backgroundPixmap = page->pixmap(QWizard::BackgroundPixmap); + watermarkPixmap = page->pixmap(QWizard::WatermarkPixmap); + } + + info.header = (info.wizStyle == QWizard::ClassicStyle || info.wizStyle == QWizard::ModernStyle) + && !(opts & QWizard::IgnoreSubTitles) && !subTitleText.isEmpty(); + info.sideWidget = sideWidget; + info.watermark = (info.wizStyle != QWizard::MacStyle) && (info.wizStyle != QWizard::AeroStyle) + && !watermarkPixmap.isNull(); + info.title = !info.header && !titleText.isEmpty(); + info.subTitle = !(opts & QWizard::IgnoreSubTitles) && !info.header && !subTitleText.isEmpty(); + info.extension = (info.watermark || info.sideWidget) && (opts & QWizard::ExtendedWatermarkPixmap); + + return info; +} + +void QWizardPrivate::recreateLayout(const QWizardLayoutInfo &info) +{ + Q_Q(QWizard); + + /* + Start by undoing the main layout. + */ + for (int i = mainLayout->count() - 1; i >= 0; --i) { + QLayoutItem *item = mainLayout->takeAt(i); + if (item->layout()) { + item->layout()->setParent(0); + } else { + delete item; + } + } + for (int i = mainLayout->columnCount() - 1; i >= 0; --i) + mainLayout->setColumnMinimumWidth(i, 0); + for (int i = mainLayout->rowCount() - 1; i >= 0; --i) + mainLayout->setRowMinimumHeight(i, 0); + + /* + Now, recreate it. + */ + + bool mac = (info.wizStyle == QWizard::MacStyle); + bool classic = (info.wizStyle == QWizard::ClassicStyle); + bool modern = (info.wizStyle == QWizard::ModernStyle); + bool aero = (info.wizStyle == QWizard::AeroStyle); + int deltaMarginLeft = info.topLevelMarginLeft - info.childMarginLeft; + int deltaMarginRight = info.topLevelMarginRight - info.childMarginRight; + int deltaMarginTop = info.topLevelMarginTop - info.childMarginTop; + int deltaMarginBottom = info.topLevelMarginBottom - info.childMarginBottom; + int deltaVSpacing = info.topLevelMarginBottom - info.vspacing; + + int row = 0; + int numColumns; + if (mac) { + numColumns = 3; + } else if (info.watermark || info.sideWidget) { + numColumns = 2; + } else { + numColumns = 1; + } + int pageColumn = qMin(1, numColumns - 1); + + if (mac) { + mainLayout->setMargin(0); + mainLayout->setSpacing(0); + buttonLayout->setContentsMargins(MacLayoutLeftMargin, MacButtonTopMargin, MacLayoutRightMargin, MacLayoutBottomMargin); + pageVBoxLayout->setMargin(7); + } else { + if (modern) { + mainLayout->setMargin(0); + mainLayout->setSpacing(0); + pageVBoxLayout->setContentsMargins(deltaMarginLeft, deltaMarginTop, + deltaMarginRight, deltaMarginBottom); + buttonLayout->setContentsMargins(info.topLevelMarginLeft, info.topLevelMarginTop, + info.topLevelMarginRight, info.topLevelMarginBottom); + } else { + mainLayout->setContentsMargins(info.topLevelMarginLeft, info.topLevelMarginTop, + info.topLevelMarginRight, info.topLevelMarginBottom); + mainLayout->setHorizontalSpacing(info.hspacing); + mainLayout->setVerticalSpacing(info.vspacing); + pageVBoxLayout->setContentsMargins(0, 0, 0, 0); + buttonLayout->setContentsMargins(0, 0, 0, 0); + } + } + buttonLayout->setSpacing(info.buttonSpacing); + + if (info.header) { + if (!headerWidget) + headerWidget = new QWizardHeader(antiFlickerWidget); + headerWidget->setAutoFillBackground(modern); + mainLayout->addWidget(headerWidget, row++, 0, 1, numColumns); + } + if (headerWidget) + headerWidget->setVisible(info.header); + + int watermarkStartRow = row; + + if (mac) + mainLayout->setRowMinimumHeight(row++, 10); + + if (info.title) { + if (!titleLabel) { + titleLabel = new QLabel(antiFlickerWidget); + titleLabel->setBackgroundRole(QPalette::Base); + titleLabel->setWordWrap(true); + } + + QFont titleFont = q->font(); + titleFont.setPointSize(titleFont.pointSize() + (mac ? 3 : 4)); + titleFont.setBold(true); + titleLabel->setPalette(QPalette()); + + if (aero) { + // ### hardcoded for now: + titleFont = QFont(QLatin1String("Segoe UI"), 12); + QPalette pal(titleLabel->palette()); + pal.setColor(QPalette::Text, "#003399"); + titleLabel->setPalette(pal); + } + + titleLabel->setFont(titleFont); + const int aeroTitleIndent = 25; // ### hardcoded for now - should be calculated somehow + if (aero) + titleLabel->setIndent(aeroTitleIndent); + else if (mac) + titleLabel->setIndent(2); + else if (classic) + titleLabel->setIndent(info.childMarginLeft); + else + titleLabel->setIndent(info.topLevelMarginLeft); + if (modern) { + if (!placeholderWidget1) { + placeholderWidget1 = new QWidget(antiFlickerWidget); + placeholderWidget1->setBackgroundRole(QPalette::Base); + } + placeholderWidget1->setFixedHeight(info.topLevelMarginLeft + 2); + mainLayout->addWidget(placeholderWidget1, row++, pageColumn); + } + mainLayout->addWidget(titleLabel, row++, pageColumn); + if (modern) { + if (!placeholderWidget2) { + placeholderWidget2 = new QWidget(antiFlickerWidget); + placeholderWidget2->setBackgroundRole(QPalette::Base); + } + placeholderWidget2->setFixedHeight(5); + mainLayout->addWidget(placeholderWidget2, row++, pageColumn); + } + if (mac) + mainLayout->setRowMinimumHeight(row++, 7); + } + if (placeholderWidget1) + placeholderWidget1->setVisible(info.title && modern); + if (placeholderWidget2) + placeholderWidget2->setVisible(info.title && modern); + + if (info.subTitle) { + if (!subTitleLabel) { + subTitleLabel = new QLabel(pageFrame); + subTitleLabel->setWordWrap(true); + + subTitleLabel->setContentsMargins(info.childMarginLeft , 0, + info.childMarginRight , 0); + + pageVBoxLayout->insertWidget(1, subTitleLabel); + } + } + + // ### try to replace with margin. + changeSpacerSize(pageVBoxLayout, 0, 0, info.subTitle ? info.childMarginLeft : 0); + + int hMargin = mac ? 1 : 0; + int vMargin = hMargin; + + pageFrame->setFrameStyle(mac ? (QFrame::Box | QFrame::Raised) : QFrame::NoFrame); + pageFrame->setLineWidth(0); + pageFrame->setMidLineWidth(hMargin); + + if (info.header) { + if (modern) { + hMargin = info.topLevelMarginLeft; + vMargin = deltaMarginBottom; + } else if (classic) { + hMargin = deltaMarginLeft + ClassicHMargin; + vMargin = 0; + } + } + + if (aero) { + int leftMargin = 18; // ### hardcoded for now - should be calculated somehow + int topMargin = vMargin; + int rightMargin = hMargin; // ### for now + int bottomMargin = vMargin; + pageFrame->setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin); + } else { + pageFrame->setContentsMargins(hMargin, vMargin, hMargin, vMargin); + } + + if ((info.watermark || info.sideWidget) && !watermarkLabel) { + watermarkLabel = new QWatermarkLabel(antiFlickerWidget, sideWidget); + watermarkLabel->setBackgroundRole(QPalette::Base); + watermarkLabel->setMinimumHeight(1); + watermarkLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + watermarkLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); + } + + //bool wasSemiTransparent = pageFrame->testAttribute(Qt::WA_SetPalette); + const bool wasSemiTransparent = + pageFrame->palette().brush(QPalette::Window).color().alpha() < 255 + || pageFrame->palette().brush(QPalette::Base).color().alpha() < 255; + if (mac) { + if (!wasSemiTransparent) { + QPalette pal = pageFrame->palette(); + pal.setBrush(QPalette::Window, QColor(255, 255, 255, 153)); + // ### The next line is required to ensure visual semitransparency when + // ### switching from ModernStyle to MacStyle. See TAG1 below. + pal.setBrush(QPalette::Base, QColor(255, 255, 255, 153)); + pageFrame->setPalette(pal); + pageFrame->setAutoFillBackground(true); + antiFlickerWidget->setAutoFillBackground(false); + } + } else { + if (wasSemiTransparent) + pageFrame->setPalette(QPalette()); + + bool baseBackground = (modern && !info.header); // ### TAG1 + pageFrame->setBackgroundRole(baseBackground ? QPalette::Base : QPalette::Window); + + if (titleLabel) + titleLabel->setAutoFillBackground(baseBackground); + pageFrame->setAutoFillBackground(baseBackground); + if (watermarkLabel) + watermarkLabel->setAutoFillBackground(baseBackground); + if (placeholderWidget1) + placeholderWidget1->setAutoFillBackground(baseBackground); + if (placeholderWidget2) + placeholderWidget2->setAutoFillBackground(baseBackground); + + if (aero) { + QPalette pal = pageFrame->palette(); + pal.setBrush(QPalette::Window, QColor(255, 255, 255)); + pageFrame->setPalette(pal); + pageFrame->setAutoFillBackground(true); + pal = antiFlickerWidget->palette(); + pal.setBrush(QPalette::Window, QColor(255, 255, 255)); + antiFlickerWidget->setPalette(pal); + antiFlickerWidget->setAutoFillBackground(true); + } + } + + mainLayout->addWidget(pageFrame, row++, pageColumn); + + int watermarkEndRow = row; + if (classic) + mainLayout->setRowMinimumHeight(row++, deltaVSpacing); + + if (aero) { + buttonLayout->setContentsMargins(9, 9, 9, 9); + mainLayout->setContentsMargins(0, 11, 0, 0); + } + + int buttonStartColumn = info.extension ? 1 : 0; + int buttonNumColumns = info.extension ? 1 : numColumns; + + if (classic || modern) { + if (!bottomRuler) + bottomRuler = new QWizardRuler(antiFlickerWidget); + mainLayout->addWidget(bottomRuler, row++, buttonStartColumn, 1, buttonNumColumns); + } + + if (classic) + mainLayout->setRowMinimumHeight(row++, deltaVSpacing); + + mainLayout->addLayout(buttonLayout, row++, buttonStartColumn, 1, buttonNumColumns); + + if (info.watermark || info.sideWidget) { + if (info.extension) + watermarkEndRow = row; + mainLayout->addWidget(watermarkLabel, watermarkStartRow, 0, + watermarkEndRow - watermarkStartRow, 1); + } + + mainLayout->setColumnMinimumWidth(0, mac && !info.watermark ? 181 : 0); + if (mac) + mainLayout->setColumnMinimumWidth(2, 21); + + if (headerWidget) + headerWidget->setVisible(info.header); + if (titleLabel) + titleLabel->setVisible(info.title); + if (subTitleLabel) + subTitleLabel->setVisible(info.subTitle); + if (bottomRuler) + bottomRuler->setVisible(classic || modern); + if (watermarkLabel) + watermarkLabel->setVisible(info.watermark || info.sideWidget); + + layoutInfo = info; +} + +void QWizardPrivate::updateLayout() +{ + Q_Q(QWizard); + + disableUpdates(); + + QWizardLayoutInfo info = layoutInfoForCurrentPage(); + if (layoutInfo != info) + recreateLayout(info); + QWizardPage *page = q->currentPage(); + + // If the page can expand vertically, let it stretch "infinitely" more + // than the QSpacerItem at the bottom. Otherwise, let the QSpacerItem + // stretch "infinitely" more than the page. Change the bottom item's + // policy accordingly. The case that the page has no layout is basically + // for Designer, only. + if (page) { + bool expandPage = !page->layout(); + if (!expandPage) { + const QLayoutItem *pageItem = pageVBoxLayout->itemAt(pageVBoxLayout->indexOf(page)); + expandPage = pageItem->expandingDirections() & Qt::Vertical; + } + QSpacerItem *bottomSpacer = pageVBoxLayout->itemAt(pageVBoxLayout->count() - 1)->spacerItem(); + Q_ASSERT(bottomSpacer); + bottomSpacer->changeSize(0, 0, QSizePolicy::Ignored, expandPage ? QSizePolicy::Ignored : QSizePolicy::MinimumExpanding); + pageVBoxLayout->invalidate(); + } + + if (info.header) { + Q_ASSERT(page); + headerWidget->setup(info, page->title(), page->subTitle(), + page->pixmap(QWizard::LogoPixmap), page->pixmap(QWizard::BannerPixmap), + titleFmt, subTitleFmt); + } + + if (info.watermark || info.sideWidget) { + QPixmap pix; + if (info.watermark) { + if (page) + pix = page->pixmap(QWizard::WatermarkPixmap); + else + pix = q->pixmap(QWizard::WatermarkPixmap); + } + watermarkLabel->setPixmap(pix); // in case there is no watermark and we show the side widget we need to clear the watermark + } + + if (info.title) { + Q_ASSERT(page); + titleLabel->setTextFormat(titleFmt); + titleLabel->setText(page->title()); + } + if (info.subTitle) { + Q_ASSERT(page); + subTitleLabel->setTextFormat(subTitleFmt); + subTitleLabel->setText(page->subTitle()); + } + + enableUpdates(); + updateMinMaxSizes(info); +} + +void QWizardPrivate::updateMinMaxSizes(const QWizardLayoutInfo &info) +{ + Q_Q(QWizard); + + int extraHeight = 0; +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (isVistaThemeEnabled()) + extraHeight = vistaHelper->titleBarSize() + vistaHelper->topOffset(); +#endif + QSize minimumSize = mainLayout->totalMinimumSize() + QSize(0, extraHeight); + QSize maximumSize = mainLayout->totalMaximumSize(); + if (info.header && headerWidget->maximumWidth() != QWIDGETSIZE_MAX) { + minimumSize.setWidth(headerWidget->maximumWidth()); + maximumSize.setWidth(headerWidget->maximumWidth()); + } + if (info.watermark && !info.sideWidget) { + minimumSize.setHeight(mainLayout->totalSizeHint().height()); + maximumSize.setHeight(mainLayout->totalSizeHint().height()); + } + if (q->minimumWidth() == minimumWidth) { + minimumWidth = minimumSize.width(); + q->setMinimumWidth(minimumWidth); + } + if (q->minimumHeight() == minimumHeight) { + minimumHeight = minimumSize.height(); + q->setMinimumHeight(minimumHeight); + } + if (q->maximumWidth() == maximumWidth) { + maximumWidth = maximumSize.width(); + q->setMaximumWidth(maximumWidth); + } + if (q->maximumHeight() == maximumHeight) { + maximumHeight = maximumSize.height(); + q->setMaximumHeight(maximumHeight); + } +} + +void QWizardPrivate::updateCurrentPage() +{ + Q_Q(QWizard); + if (q->currentPage()) { + canContinue = (q->nextId() != -1); + canFinish = q->currentPage()->isFinalPage(); + } else { + canContinue = false; + canFinish = false; + } + _q_updateButtonStates(); + updateButtonTexts(); +} + +bool QWizardPrivate::ensureButton(QWizard::WizardButton which) const +{ + Q_Q(const QWizard); + if (uint(which) >= QWizard::NButtons) + return false; + + if (!btns[which]) { + QPushButton *pushButton = new QPushButton(antiFlickerWidget); + QStyle *style = q->style(); + if (style != QApplication::style()) // Propagate style + pushButton->setStyle(style); + // Make navigation buttons detectable as passive interactor in designer + switch (which) { + case QWizard::CommitButton: + case QWizard::FinishButton: + case QWizard::CancelButton: + break; + default: { + QString objectName = QLatin1String("__qt__passive_wizardbutton"); + objectName += QString::number(which); + pushButton->setObjectName(objectName); + } + break; + } +#ifdef Q_WS_MAC + pushButton->setAutoDefault(false); +#endif + pushButton->hide(); +#ifdef Q_CC_HPACC + const_cast(this)->btns[which] = pushButton; +#else + btns[which] = pushButton; +#endif + if (which < QWizard::NStandardButtons) + pushButton->setText(buttonDefaultText(wizStyle, which, this)); + +#ifdef QT_SOFTKEYS_ENABLED + QAction *softKey = new QAction(pushButton->text(), pushButton); + QAction::SoftKeyRole softKeyRole; + switch(which) { + case QWizard::NextButton: + case QWizard::FinishButton: + case QWizard::CancelButton: + softKeyRole = QAction::NegativeSoftKey; + break; + case QWizard::BackButton: + case QWizard::CommitButton: + case QWizard::HelpButton: + case QWizard::CustomButton1: + case QWizard::CustomButton2: + case QWizard::CustomButton3: + default: + softKeyRole = QAction::PositiveSoftKey; + break; + } + softKey->setSoftKeyRole(softKeyRole); + softKeys[which] = softKey; +#endif + connectButton(which); + } + return true; +} + +void QWizardPrivate::connectButton(QWizard::WizardButton which) const +{ + Q_Q(const QWizard); + if (which < QWizard::NStandardButtons) { + QObject::connect(btns[which], SIGNAL(clicked()), q, buttonSlots[which]); + } else { + QObject::connect(btns[which], SIGNAL(clicked()), q, SLOT(_q_emitCustomButtonClicked())); + } + +#ifdef QT_SOFTKEYS_ENABLED + QObject::connect(softKeys[which], SIGNAL(triggered()), btns[which], SIGNAL(clicked())); +#endif +} + +void QWizardPrivate::updateButtonTexts() +{ + Q_Q(QWizard); + for (int i = 0; i < QWizard::NButtons; ++i) { + if (btns[i]) { + if (q->currentPage() && (q->currentPage()->d_func()->buttonCustomTexts.contains(i))) + btns[i]->setText(q->currentPage()->d_func()->buttonCustomTexts.value(i)); + else if (buttonCustomTexts.contains(i)) + btns[i]->setText(buttonCustomTexts.value(i)); + else if (i < QWizard::NStandardButtons) + btns[i]->setText(buttonDefaultText(wizStyle, i, this)); +#ifdef QT_SOFTKEYS_ENABLED + softKeys[i]->setText(btns[i]->text()); +#endif + } + } +} + +void QWizardPrivate::updateButtonLayout() +{ + if (buttonsHaveCustomLayout) { + QVarLengthArray array(buttonsCustomLayout.count()); + for (int i = 0; i < buttonsCustomLayout.count(); ++i) + array[i] = buttonsCustomLayout.at(i); + setButtonLayout(array.constData(), array.count()); + } else { + // Positions: + // Help Stretch Custom1 Custom2 Custom3 Cancel Back Next Commit Finish Cancel Help + + const int ArraySize = 12; + QWizard::WizardButton array[ArraySize]; + memset(array, -1, sizeof(array)); + Q_ASSERT(array[0] == QWizard::NoButton); + + if (opts & QWizard::HaveHelpButton) { + int i = (opts & QWizard::HelpButtonOnRight) ? 11 : 0; + array[i] = QWizard::HelpButton; + } + array[1] = QWizard::Stretch; + if (opts & QWizard::HaveCustomButton1) + array[2] = QWizard::CustomButton1; + if (opts & QWizard::HaveCustomButton2) + array[3] = QWizard::CustomButton2; + if (opts & QWizard::HaveCustomButton3) + array[4] = QWizard::CustomButton3; + + if (!(opts & QWizard::NoCancelButton)) { + int i = (opts & QWizard::CancelButtonOnLeft) ? 5 : 10; + array[i] = QWizard::CancelButton; + } + array[6] = QWizard::BackButton; + array[7] = QWizard::NextButton; + array[8] = QWizard::CommitButton; + array[9] = QWizard::FinishButton; + + setButtonLayout(array, ArraySize); + } +} + +void QWizardPrivate::setButtonLayout(const QWizard::WizardButton *array, int size) +{ + QWidget *prev = pageFrame; + + for (int i = buttonLayout->count() - 1; i >= 0; --i) { + QLayoutItem *item = buttonLayout->takeAt(i); + if (QWidget *widget = item->widget()) + widget->hide(); + delete item; + } + + for (int i = 0; i < size; ++i) { + QWizard::WizardButton which = array[i]; + if (which == QWizard::Stretch) { + buttonLayout->addStretch(1); + } else if (which != QWizard::NoButton) { + ensureButton(which); + buttonLayout->addWidget(btns[which]); + + // Back, Next, Commit, and Finish are handled in _q_updateButtonStates() + if (which != QWizard::BackButton && which != QWizard::NextButton + && which != QWizard::CommitButton && which != QWizard::FinishButton) + btns[which]->show(); + + if (prev) + QWidget::setTabOrder(prev, btns[which]); + prev = btns[which]; + } + } + + _q_updateButtonStates(); +} + +bool QWizardPrivate::buttonLayoutContains(QWizard::WizardButton which) +{ + return !buttonsHaveCustomLayout || buttonsCustomLayout.contains(which); +} + +void QWizardPrivate::updatePixmap(QWizard::WizardPixmap which) +{ + Q_Q(QWizard); + if (which == QWizard::BackgroundPixmap) { + if (wizStyle == QWizard::MacStyle) { + q->update(); + q->updateGeometry(); + } + } else { + updateLayout(); + } +} + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +bool QWizardPrivate::vistaDisabled() const +{ + Q_Q(const QWizard); + const QVariant v = q->property("_q_wizard_vista_off"); + return v.isValid() && v.toBool(); +} + +bool QWizardPrivate::isVistaThemeEnabled(QVistaHelper::VistaState state) const +{ + return wizStyle == QWizard::AeroStyle + && QVistaHelper::vistaState() == state + && !vistaDisabled(); +} + +void QWizardPrivate::handleAeroStyleChange() +{ + Q_Q(QWizard); + + if (inHandleAeroStyleChange) + return; // prevent recursion + inHandleAeroStyleChange = true; + + vistaHelper->disconnectBackButton(); + q->removeEventFilter(vistaHelper); + + if (isVistaThemeEnabled()) { + if (isVistaThemeEnabled(QVistaHelper::VistaAero)) { + vistaHelper->setDWMTitleBar(QVistaHelper::ExtendedTitleBar); + q->installEventFilter(vistaHelper); + q->setMouseTracking(true); + antiFlickerWidget->move(0, vistaHelper->titleBarSize() + vistaHelper->topOffset()); + vistaHelper->backButton()->move( + 0, vistaHelper->topOffset() // ### should ideally work without the '+ 1' + - qMin(vistaHelper->topOffset(), vistaHelper->topPadding() + 1)); + } else { + vistaHelper->setDWMTitleBar(QVistaHelper::NormalTitleBar); + q->setMouseTracking(true); + antiFlickerWidget->move(0, vistaHelper->topOffset()); + vistaHelper->backButton()->move(0, -1); // ### should ideally work with (0, 0) + } + vistaHelper->setTitleBarIconAndCaptionVisible(false); + QObject::connect( + vistaHelper->backButton(), SIGNAL(clicked()), q, buttonSlots[QWizard::BackButton]); + vistaHelper->backButton()->show(); + } else { + q->setMouseTracking(true); // ### original value possibly different +#ifndef QT_NO_CURSOR + q->unsetCursor(); // ### ditto +#endif + antiFlickerWidget->move(0, 0); + vistaHelper->hideBackButton(); + vistaHelper->setTitleBarIconAndCaptionVisible(true); + } + + _q_updateButtonStates(); + + if (q->isVisible()) + vistaHelper->setWindowPosHack(); + + inHandleAeroStyleChange = false; +} +#endif + +bool QWizardPrivate::isVistaThemeEnabled() const +{ +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + return isVistaThemeEnabled(QVistaHelper::VistaAero) + || isVistaThemeEnabled(QVistaHelper::VistaBasic); +#else + return false; +#endif +} + +void QWizardPrivate::disableUpdates() +{ + Q_Q(QWizard); + if (disableUpdatesCount++ == 0) { + q->setUpdatesEnabled(false); + antiFlickerWidget->hide(); + } +} + +void QWizardPrivate::enableUpdates() +{ + Q_Q(QWizard); + if (--disableUpdatesCount == 0) { + antiFlickerWidget->show(); + q->setUpdatesEnabled(true); + } +} + +void QWizardPrivate::_q_emitCustomButtonClicked() +{ + Q_Q(QWizard); + QObject *button = q->sender(); + for (int i = QWizard::NStandardButtons; i < QWizard::NButtons; ++i) { + if (btns[i] == button) { + emit q->customButtonClicked(QWizard::WizardButton(i)); + break; + } + } +} + +void QWizardPrivate::_q_updateButtonStates() +{ + Q_Q(QWizard); + + disableUpdates(); + + const QWizardPage *page = q->currentPage(); + bool complete = page && page->isComplete(); + + btn.back->setEnabled(history.count() > 1 + && !q->page(history.at(history.count() - 2))->isCommitPage() + && (!canFinish || !(opts & QWizard::DisabledBackButtonOnLastPage))); + btn.next->setEnabled(canContinue && complete); + btn.commit->setEnabled(canContinue && complete); + btn.finish->setEnabled(canFinish && complete); + + const bool backButtonVisible = buttonLayoutContains(QWizard::BackButton) + && (history.count() > 1 || !(opts & QWizard::NoBackButtonOnStartPage)) + && (canContinue || !(opts & QWizard::NoBackButtonOnLastPage)); + bool commitPage = page && page->isCommitPage(); + btn.back->setVisible(backButtonVisible); + btn.next->setVisible(buttonLayoutContains(QWizard::NextButton) && !commitPage + && (canContinue || (opts & QWizard::HaveNextButtonOnLastPage))); + btn.commit->setVisible(buttonLayoutContains(QWizard::CommitButton) && commitPage + && canContinue); + btn.finish->setVisible(buttonLayoutContains(QWizard::FinishButton) + && (canFinish || (opts & QWizard::HaveFinishButtonOnEarlyPages))); + + bool useDefault = !(opts & QWizard::NoDefaultButton); + if (QPushButton *nextPush = qobject_cast(btn.next)) + nextPush->setDefault(canContinue && useDefault && !commitPage); + if (QPushButton *commitPush = qobject_cast(btn.commit)) + commitPush->setDefault(canContinue && useDefault && commitPage); + if (QPushButton *finishPush = qobject_cast(btn.finish)) + finishPush->setDefault(!canContinue && useDefault); + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (isVistaThemeEnabled()) { + vistaHelper->backButton()->setEnabled(btn.back->isEnabled()); + vistaHelper->backButton()->setVisible(backButtonVisible); + btn.back->setVisible(false); + } +#endif + +#ifdef QT_SOFTKEYS_ENABLED + QAbstractButton *wizardButton; + for (int i = 0; i < QWizard::NButtons; ++i) { + wizardButton = btns[i]; + if (wizardButton && !wizardButton->testAttribute(Qt::WA_WState_Hidden)) { + wizardButton->hide(); + q->addAction(softKeys[i]); + } else { + q->removeAction(softKeys[i]); + } + } +#endif + + enableUpdates(); +} + +void QWizardPrivate::_q_handleFieldObjectDestroyed(QObject *object) +{ + QVector::iterator it = fields.begin(); + while (it != fields.end()) { + const QWizardField &field = *it; + if (field.object == object) { + fieldIndexMap.remove(field.name); + it = fields.erase(it); + } else { + ++it; + } + } +} + +void QWizardPrivate::setStyle(QStyle *style) +{ + for (int i = 0; i < QWizard::NButtons; i++) + if (btns[i]) + btns[i]->setStyle(style); + const PageMap::const_iterator pcend = pageMap.constEnd(); + for (PageMap::const_iterator it = pageMap.constBegin(); it != pcend; ++it) + it.value()->setStyle(style); +} + +#ifdef Q_WS_MAC + +QPixmap QWizardPrivate::findDefaultBackgroundPixmap() +{ + QCFType url; + const int ExpectedImageWidth = 242; + const int ExpectedImageHeight = 414; + if (LSFindApplicationForInfo(kLSUnknownCreator, CFSTR("com.apple.KeyboardSetupAssistant"), + 0, 0, &url) == noErr) { + QCFType bundle = CFBundleCreate(kCFAllocatorDefault, url); + if (bundle) { + url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("tif"), 0); + if (url) { + QCFType imageSource = CGImageSourceCreateWithURL(url, 0); + QCFType image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + if (image) { + int width = CGImageGetWidth(image); + int height = CGImageGetHeight(image); + if (width == ExpectedImageWidth && height == ExpectedImageHeight) + return QPixmap::fromMacCGImageRef(image); + } + } + } + } + return QPixmap(); + +} + +#endif + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +void QWizardAntiFlickerWidget::paintEvent(QPaintEvent *) +{ + if (wizardPrivate->isVistaThemeEnabled()) { + int leftMargin, topMargin, rightMargin, bottomMargin; + wizardPrivate->buttonLayout->getContentsMargins( + &leftMargin, &topMargin, &rightMargin, &bottomMargin); + const int buttonLayoutTop = wizardPrivate->buttonLayout->contentsRect().top() - topMargin; + QPainter painter(this); + const QBrush brush(QColor(240, 240, 240)); // ### hardcoded for now + painter.fillRect(0, buttonLayoutTop, width(), height() - buttonLayoutTop, brush); + painter.setPen(QPen(QBrush(QColor(223, 223, 223)), 0)); // ### hardcoded for now + painter.drawLine(0, buttonLayoutTop, width(), buttonLayoutTop); + if (wizardPrivate->isVistaThemeEnabled(QVistaHelper::VistaBasic)) { + if (window()->isActiveWindow()) + painter.setPen(QPen(QBrush(QColor(169, 191, 214)), 0)); // ### hardcoded for now + else + painter.setPen(QPen(QBrush(QColor(182, 193, 204)), 0)); // ### hardcoded for now + painter.drawLine(0, 0, width(), 0); + } + } +} +#endif + +/*! + \class QWizard + \since 4.3 + \brief The QWizard class provides a framework for wizards. + + A wizard (also called an assistant on Mac OS X) is a special type + of input dialog that consists of a sequence of pages. A wizard's + purpose is to guide the user through a process step by step. + Wizards are useful for complex or infrequent tasks that users may + find difficult to learn. + + QWizard inherits QDialog and represents a wizard. Each page is a + QWizardPage (a QWidget subclass). To create your own wizards, you + can use these classes directly, or you can subclass them for more + control. + + Topics: + + \tableofcontents + + \section1 A Trivial Example + + The following example illustrates how to create wizard pages and + add them to a wizard. For more advanced examples, see + \l{dialogs/classwizard}{Class Wizard} and \l{dialogs/licensewizard}{License + Wizard}. + + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 1 + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 3 + \dots + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 4 + \codeline + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 5 + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 7 + \dots + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 8 + \codeline + \snippet examples/dialogs/trivialwizard/trivialwizard.cpp 10 + + \section1 Wizard Look and Feel + + QWizard supports four wizard looks: + + \list + \o ClassicStyle + \o ModernStyle + \o MacStyle + \o AeroStyle + \endlist + + You can explicitly set the look to use using setWizardStyle() + (e.g., if you want the same look on all platforms). + + \table + \header \o ClassicStyle + \o ModernStyle + \o MacStyle + \o AeroStyle + \row \o \inlineimage qtwizard-classic1.png + \o \inlineimage qtwizard-modern1.png + \o \inlineimage qtwizard-mac1.png + \o \inlineimage qtwizard-aero1.png + \row \o \inlineimage qtwizard-classic2.png + \o \inlineimage qtwizard-modern2.png + \o \inlineimage qtwizard-mac2.png + \o \inlineimage qtwizard-aero2.png + \endtable + + Note: AeroStyle has effect only on a Windows Vista system with alpha compositing enabled. + ModernStyle is used as a fallback when this condition is not met. + + In addition to the wizard style, there are several options that + control the look and feel of the wizard. These can be set using + setOption() or setOptions(). For example, HaveHelpButton makes + QWizard show a \gui Help button along with the other wizard + buttons. + + You can even change the order of the wizard buttons to any + arbitrary order using setButtonLayout(), and you can add up to + three custom buttons (e.g., a \gui Print button) to the button + row. This is achieved by calling setButton() or setButtonText() + with CustomButton1, CustomButton2, or CustomButton3 to set up the + button, and by enabling the HaveCustomButton1, HaveCustomButton2, + or HaveCustomButton3 options. Whenever the user clicks a custom + button, customButtonClicked() is emitted. For example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 29 + + \section1 Elements of a Wizard Page + + Wizards consist of a sequence of \l{QWizardPage}s. At any time, + only one page is shown. A page has the following attributes: + + \list + \o A \l{QWizardPage::}{title}. + \o A \l{QWizardPage::}{subTitle}. + \o A set of pixmaps, which may or may not be honored, depending + on the wizard's style: + \list + \o WatermarkPixmap (used by ClassicStyle and ModernStyle) + \o BannerPixmap (used by ModernStyle) + \o LogoPixmap (used by ClassicStyle and ModernStyle) + \o BackgroundPixmap (used by MacStyle) + \endlist + \endlist + + The diagram belows shows how QWizard renders these attributes, + assuming they are all present and ModernStyle is used: + + \image qtwizard-nonmacpage.png + + When a \l{QWizardPage::}{subTitle} is set, QWizard displays it + in a header, in which case it also uses the BannerPixmap and the + LogoPixmap to decorate the header. The WatermarkPixmap is + displayed on the left side, below the header. At the bottom, + there is a row of buttons allowing the user to navigate through + the pages. + + The page itself (the \l{QWizardPage} widget) occupies the area + between the header, the watermark, and the button row. Typically, + the page is a QWizardPage on which a QGridLayout is installed, + with standard child widgets (\l{QLabel}s, \l{QLineEdit}s, etc.). + + If the wizard's style is MacStyle, the page looks radically + different: + + \image qtwizard-macpage.png + + The watermark, banner, and logo pixmaps are ignored by the + MacStyle. If the BackgroundPixmap is set, it is used as the + background for the wizard; otherwise, a default "assistant" image + is used. + + The title and subtitle are set by calling + QWizardPage::setTitle() and QWizardPage::setSubTitle() on the + individual pages. They may be plain text or HTML (see titleFormat + and subTitleFormat). The pixmaps can be set globally for the + entire wizard using setPixmap(), or on a per-page basis using + QWizardPage::setPixmap(). + + \target field mechanism + \section1 Registering and Using Fields + + In many wizards, the contents of a page may affect the default + values of the fields of a later page. To make it easy to + communicate between pages, QWizard supports a "field" mechanism + that allows you to register a field (e.g., a QLineEdit) on a page + and to access its value from any page. It is also possible to + specify mandatory fields (i.e., fields that must be filled before + the user can advance to the next page). + + To register a field, call QWizardPage::registerField() field. + For example: + + \snippet examples/dialogs/classwizard/classwizard.cpp 8 + \dots + \snippet examples/dialogs/classwizard/classwizard.cpp 10 + \snippet examples/dialogs/classwizard/classwizard.cpp 11 + \dots + \snippet examples/dialogs/classwizard/classwizard.cpp 13 + + The above code registers three fields, \c className, \c + baseClass, and \c qobjectMacro, which are associated with three + child widgets. The asterisk (\c *) next to \c className denotes a + mandatory field. + + \target initialize page + The fields of any page are accessible from any other page. For + example: + + \snippet examples/dialogs/classwizard/classwizard.cpp 17 + + Here, we call QWizardPage::field() to access the contents of the + \c className field (which was defined in the \c ClassInfoPage) + and use it to initialize the \c OuputFilePage. The field's + contents is returned as a QVariant. + + When we create a field using QWizardPage::registerField(), we + pass a unique field name and a widget. We can also provide a Qt + property name and a "changed" signal (a signal that is emitted + when the property changes) as third and fourth arguments; + however, this is not necessary for the most common Qt widgets, + such as QLineEdit, QCheckBox, and QComboBox, because QWizard + knows which properties to look for. + + \target mandatory fields + + If an asterisk (\c *) is appended to the name when the property + is registered, the field is a \e{mandatory field}. When a page has + mandatory fields, the \gui Next and/or \gui Finish buttons are + enabled only when all mandatory fields are filled. + + To consider a field "filled", QWizard simply checks that the + field's current value doesn't equal the original value (the value + it had when initializePage() was called). For QLineEdit and + QAbstractSpinBox subclasses, QWizard also checks that + \l{QLineEdit::hasAcceptableInput()}{hasAcceptableInput()} returns + true, to honor any validator or mask. + + QWizard's mandatory field mechanism is provided for convenience. + A more powerful (but also more cumbersome) alternative is to + reimplement QWizardPage::isComplete() and to emit the + QWizardPage::completeChanged() signal whenever the page becomes + complete or incomplete. + + The enabled/disabled state of the \gui Next and/or \gui Finish + buttons is one way to perform validation on the user input. + Another way is to reimplement validateCurrentPage() (or + QWizardPage::validatePage()) to perform some last-minute + validation (and show an error message if the user has entered + incomplete or invalid information). If the function returns true, + the next page is shown (or the wizard finishes); otherwise, the + current page stays up. + + \section1 Creating Linear Wizards + + Most wizards have a linear structure, with page 1 followed by + page 2 and so on until the last page. The \l{dialogs/classwizard}{Class + Wizard} example is such a wizard. With QWizard, linear wizards + are created by instantiating the \l{QWizardPage}s and inserting + them using addPage(). By default, the pages are shown in the + order in which they were added. For example: + + \snippet examples/dialogs/classwizard/classwizard.cpp 0 + \dots + \snippet examples/dialogs/classwizard/classwizard.cpp 2 + + When a page is about to be shown, QWizard calls initializePage() + (which in turn calls QWizardPage::initializePage()) to fill the + page with default values. By default, this function does nothing, + but it can be reimplemented to initialize the page's contents + based on other pages' fields (see the \l{initialize page}{example + above}). + + If the user presses \gui Back, cleanupPage() is called (which in + turn calls QWizardPage::cleanupPage()). The default + implementation resets the page's fields to their original values + (the values they had before initializePage() was called). If you + want the \gui Back button to be non-destructive and keep the + values entered by the user, simply enable the IndependentPages + option. + + \section1 Creating Non-Linear Wizards + + Some wizards are more complex in that they allow different + traversal paths based on the information provided by the user. + The \l{dialogs/licensewizard}{License Wizard} example illustrates this. + It provides five wizard pages; depending on which options are + selected, the user can reach different pages. + + \image licensewizard-flow.png + + In complex wizards, pages are identified by IDs. These IDs are + typically defined using an enum. For example: + + \snippet examples/dialogs/licensewizard/licensewizard.h 0 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.h 2 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.h 3 + + The pages are inserted using setPage(), which takes an ID and an + instance of QWizardPage (or of a subclass): + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 1 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 8 + + By default, the pages are shown in increasing ID order. To + provide a dynamic order that depends on the options chosen by the + user, we must reimplement QWizardPage::nextId(). For example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 18 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 23 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 24 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 25 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 26 + + It would also be possible to put all the logic in one place, in a + QWizard::nextId() reimplementation. For example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qwizard.cpp 0 + + To start at another page than the page with the lowest ID, call + setStartId(). + + To test whether a page has been visited or not, call + hasVisitedPage(). For example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 27 + + \sa QWizardPage, {Class Wizard Example}, {License Wizard Example} +*/ + +/*! + \enum QWizard::WizardButton + + This enum specifies the buttons in a wizard. + + \value BackButton The \gui Back button (\gui {Go Back} on Mac OS X) + \value NextButton The \gui Next button (\gui Continue on Mac OS X) + \value CommitButton The \gui Commit button + \value FinishButton The \gui Finish button (\gui Done on Mac OS X) + \value CancelButton The \gui Cancel button (see also NoCancelButton) + \value HelpButton The \gui Help button (see also HaveHelpButton) + \value CustomButton1 The first user-defined button (see also HaveCustomButton1) + \value CustomButton2 The second user-defined button (see also HaveCustomButton2) + \value CustomButton3 The third user-defined button (see also HaveCustomButton3) + + The following value is only useful when calling setButtonLayout(): + + \value Stretch A horizontal stretch in the button layout + + \omitvalue NoButton + \omitvalue NStandardButtons + \omitvalue NButtons + + \sa setButton(), setButtonText(), setButtonLayout(), customButtonClicked() +*/ + +/*! + \enum QWizard::WizardPixmap + + This enum specifies the pixmaps that can be associated with a page. + + \value WatermarkPixmap The tall pixmap on the left side of a ClassicStyle or ModernStyle page + \value LogoPixmap The small pixmap on the right side of a ClassicStyle or ModernStyle page header + \value BannerPixmap The pixmap that occupies the background of a ModernStyle page header + \value BackgroundPixmap The pixmap that occupies the background of a MacStyle wizard + + \omitvalue NPixmaps + + \sa setPixmap(), QWizardPage::setPixmap(), {Elements of a Wizard Page} +*/ + +/*! + \enum QWizard::WizardStyle + + This enum specifies the different looks supported by QWizard. + + \value ClassicStyle Classic Windows look + \value ModernStyle Modern Windows look + \value MacStyle Mac OS X look + \value AeroStyle Windows Aero look + + \omitvalue NStyles + + \sa setWizardStyle(), WizardOption, {Wizard Look and Feel} +*/ + +/*! + \enum QWizard::WizardOption + + This enum specifies various options that affect the look and feel + of a wizard. + + \value IndependentPages The pages are independent of each other + (i.e., they don't derive values from each + other). + \value IgnoreSubTitles Don't show any subtitles, even if they are set. + \value ExtendedWatermarkPixmap Extend any WatermarkPixmap all the + way down to the window's edge. + \value NoDefaultButton Don't make the \gui Next or \gui Finish button the + dialog's \l{QPushButton::setDefault()}{default button}. + \value NoBackButtonOnStartPage Don't show the \gui Back button on the start page. + \value NoBackButtonOnLastPage Don't show the \gui Back button on the last page. + \value DisabledBackButtonOnLastPage Disable the \gui Back button on the last page. + \value HaveNextButtonOnLastPage Show the (disabled) \gui Next button on the last page. + \value HaveFinishButtonOnEarlyPages Show the (disabled) \gui Finish button on non-final pages. + \value NoCancelButton Don't show the \gui Cancel button. + \value CancelButtonOnLeft Put the \gui Cancel button on the left of \gui Back (rather than on + the right of \gui Finish or \gui Next). + \value HaveHelpButton Show the \gui Help button. + \value HelpButtonOnRight Put the \gui Help button on the far right of the button layout + (rather than on the far left). + \value HaveCustomButton1 Show the first user-defined button (CustomButton1). + \value HaveCustomButton2 Show the second user-defined button (CustomButton2). + \value HaveCustomButton3 Show the third user-defined button (CustomButton3). + + \sa setOptions(), setOption(), testOption() +*/ + +/*! + Constructs a wizard with the given \a parent and window \a flags. + + \sa parent(), windowFlags() +*/ +QWizard::QWizard(QWidget *parent, Qt::WindowFlags flags) + : QDialog(*new QWizardPrivate, parent, flags) +{ + Q_D(QWizard); + d->init(); +#ifdef Q_WS_WINCE + if (!qt_wince_is_mobile()) + setWindowFlags(windowFlags() & ~Qt::WindowOkButtonHint); +#endif +} + +/*! + Destroys the wizard and its pages, releasing any allocated resources. +*/ +QWizard::~QWizard() +{ + Q_D(QWizard); + delete d->buttonLayout; +} + +/*! + Adds the given \a page to the wizard, and returns the page's ID. + + The ID is guaranteed to be larger than any other ID in the + QWizard so far. + + \sa setPage(), page(), pageAdded() +*/ +int QWizard::addPage(QWizardPage *page) +{ + Q_D(QWizard); + int theid = 0; + if (!d->pageMap.isEmpty()) + theid = (d->pageMap.constEnd() - 1).key() + 1; + setPage(theid, page); + return theid; +} + +/*! + \fn void QWizard::setPage(int id, QWizardPage *page) + + Adds the given \a page to the wizard with the given \a id. + + \note Adding a page may influence the value of the startId property + in case it was not set explicitly. + + \sa addPage(), page(), pageAdded() +*/ +void QWizard::setPage(int theid, QWizardPage *page) +{ + Q_D(QWizard); + + if (!page) { + qWarning("QWizard::setPage: Cannot insert null page"); + return; + } + + if (theid == -1) { + qWarning("QWizard::setPage: Cannot insert page with ID -1"); + return; + } + + if (d->pageMap.contains(theid)) { + qWarning("QWizard::setPage: Page with duplicate ID %d ignored", theid); + return; + } + + page->setParent(d->pageFrame); + + QVector &pendingFields = page->d_func()->pendingFields; + for (int i = 0; i < pendingFields.count(); ++i) + d->addField(pendingFields.at(i)); + pendingFields.clear(); + + connect(page, SIGNAL(completeChanged()), this, SLOT(_q_updateButtonStates())); + + d->pageMap.insert(theid, page); + page->d_func()->wizard = this; + + int n = d->pageVBoxLayout->count(); + + // disable layout to prevent layout updates while adding + bool pageVBoxLayoutEnabled = d->pageVBoxLayout->isEnabled(); + d->pageVBoxLayout->setEnabled(false); + + d->pageVBoxLayout->insertWidget(n - 1, page); + + // hide new page and reset layout to old status + page->hide(); + d->pageVBoxLayout->setEnabled(pageVBoxLayoutEnabled); + + if (!d->startSetByUser && d->pageMap.constBegin().key() == theid) + d->start = theid; + emit pageAdded(theid); +} + +/*! + Removes the page with the given \a id. cleanupPage() will be called if necessary. + + \note Removing a page may influence the value of the startId property. + + \since 4.5 + \sa addPage(), setPage(), pageRemoved(), startId() +*/ +void QWizard::removePage(int id) +{ + Q_D(QWizard); + + QWizardPage *removedPage = 0; + + // update startItem accordingly + if (d->pageMap.count() > 0) { // only if we have any pages + if (d->start == id) { + const int firstId = d->pageMap.constBegin().key(); + if (firstId == id) { + if (d->pageMap.count() > 1) + d->start = (++d->pageMap.constBegin()).key(); // secondId + else + d->start = -1; // removing the last page + } else { // startSetByUser has to be "true" here + d->start = firstId; + } + d->startSetByUser = false; + } + } + + if (d->pageMap.contains(id)) + emit pageRemoved(id); + + if (!d->history.contains(id)) { + // Case 1: removing a page not in the history + removedPage = d->pageMap.take(id); + d->updateCurrentPage(); + } else if (id != d->current) { + // Case 2: removing a page in the history before the current page + removedPage = d->pageMap.take(id); + d->history.removeOne(id); + d->_q_updateButtonStates(); + } else if (d->history.count() == 1) { + // Case 3: removing the current page which is the first (and only) one in the history + d->reset(); + removedPage = d->pageMap.take(id); + if (d->pageMap.isEmpty()) + d->updateCurrentPage(); + else + restart(); + } else { + // Case 4: removing the current page which is not the first one in the history + back(); + removedPage = d->pageMap.take(id); + d->updateCurrentPage(); + } + + if (removedPage) { + if (d->initialized.contains(id)) { + cleanupPage(id); + d->initialized.remove(id); + } + + d->pageVBoxLayout->removeWidget(removedPage); + + for (int i = d->fields.count() - 1; i >= 0; --i) { + if (d->fields.at(i).page == removedPage) { + removedPage->d_func()->pendingFields += d->fields.at(i); + d->removeFieldAt(i); + } + } + } +} + +/*! + \fn QWizardPage *QWizard::page(int id) const + + Returns the page with the given \a id, or 0 if there is no such + page. + + \sa addPage(), setPage() +*/ +QWizardPage *QWizard::page(int theid) const +{ + Q_D(const QWizard); + return d->pageMap.value(theid); +} + +/*! + \fn bool QWizard::hasVisitedPage(int id) const + + Returns true if the page history contains page \a id; otherwise, + returns false. + + Pressing \gui Back marks the current page as "unvisited" again. + + \sa visitedPages() +*/ +bool QWizard::hasVisitedPage(int theid) const +{ + Q_D(const QWizard); + return d->history.contains(theid); +} + +/*! + Returns the list of IDs of visited pages, in the order in which the pages + were visited. + + Pressing \gui Back marks the current page as "unvisited" again. + + \sa hasVisitedPage() +*/ +QList QWizard::visitedPages() const +{ + Q_D(const QWizard); + return d->history; +} + +/*! + Returns the list of page IDs. + \since 4.5 +*/ +QList QWizard::pageIds() const +{ + Q_D(const QWizard); + return d->pageMap.keys(); +} + +/*! + \property QWizard::startId + \brief the ID of the first page + + If this property isn't explicitly set, this property defaults to + the lowest page ID in this wizard, or -1 if no page has been + inserted yet. + + \sa restart(), nextId() +*/ +void QWizard::setStartId(int theid) +{ + Q_D(QWizard); + int newStart = theid; + if (theid == -1) + newStart = d->pageMap.count() ? d->pageMap.constBegin().key() : -1; + + if (d->start == newStart) { + d->startSetByUser = theid != -1; + return; + } + + if (!d->pageMap.contains(newStart)) { + qWarning("QWizard::setStartId: Invalid page ID %d", newStart); + return; + } + d->start = newStart; + d->startSetByUser = theid != -1; +} + +int QWizard::startId() const +{ + Q_D(const QWizard); + return d->start; +} + +/*! + Returns a pointer to the current page, or 0 if there is no current + page (e.g., before the wizard is shown). + + This is equivalent to calling page(currentId()). + + \sa page(), currentId(), restart() +*/ +QWizardPage *QWizard::currentPage() const +{ + Q_D(const QWizard); + return page(d->current); +} + +/*! + \property QWizard::currentId + \brief the ID of the current page + + This property cannot be set directly. To change the current page, + call next(), back(), or restart(). + + By default, this property has a value of -1, indicating that no page is + currently shown. + + \sa currentIdChanged(), currentPage() +*/ +int QWizard::currentId() const +{ + Q_D(const QWizard); + return d->current; +} + +/*! + Sets the value of the field called \a name to \a value. + + This function can be used to set fields on any page of the wizard. + + \sa QWizardPage::registerField(), QWizardPage::setField(), field() +*/ +void QWizard::setField(const QString &name, const QVariant &value) +{ + Q_D(QWizard); + + int index = d->fieldIndexMap.value(name, -1); + if (index != -1) { + const QWizardField &field = d->fields.at(index); + if (!field.object->setProperty(field.property, value)) + qWarning("QWizard::setField: Couldn't write to property '%s'", + field.property.constData()); + return; + } + + qWarning("QWizard::setField: No such field '%s'", qPrintable(name)); +} + +/*! + Returns the value of the field called \a name. + + This function can be used to access fields on any page of the wizard. + + \sa QWizardPage::registerField(), QWizardPage::field(), setField() +*/ +QVariant QWizard::field(const QString &name) const +{ + Q_D(const QWizard); + + int index = d->fieldIndexMap.value(name, -1); + if (index != -1) { + const QWizardField &field = d->fields.at(index); + return field.object->property(field.property); + } + + qWarning("QWizard::field: No such field '%s'", qPrintable(name)); + return QVariant(); +} + +/*! + \property QWizard::wizardStyle + \brief the look and feel of the wizard + + By default, QWizard uses the AeroStyle on a Windows Vista system with alpha compositing + enabled, regardless of the current widget style. If this is not the case, the default + wizard style depends on the current widget style as follows: MacStyle is the default if + the current widget style is QMacStyle, ModernStyle is the default if the current widget + style is QWindowsStyle, and ClassicStyle is the default in all other cases. + + \sa {Wizard Look and Feel}, options +*/ +void QWizard::setWizardStyle(WizardStyle style) +{ + Q_D(QWizard); + + const bool styleChange = style != d->wizStyle; + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + const bool aeroStyleChange = + d->vistaInitPending || d->vistaStateChanged || (styleChange && (style == AeroStyle || d->wizStyle == AeroStyle)); + d->vistaStateChanged = false; + d->vistaInitPending = false; +#endif + + if (styleChange +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + || aeroStyleChange +#endif + ) { + d->disableUpdates(); + d->wizStyle = style; + d->updateButtonTexts(); + d->updateLayout(); + updateGeometry(); + d->enableUpdates(); +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (aeroStyleChange) + d->handleAeroStyleChange(); +#endif + } +} + +QWizard::WizardStyle QWizard::wizardStyle() const +{ + Q_D(const QWizard); + return d->wizStyle; +} + +/*! + Sets the given \a option to be enabled if \a on is true; + otherwise, clears the given \a option. + + \sa options, testOption(), setWizardStyle() +*/ +void QWizard::setOption(WizardOption option, bool on) +{ + Q_D(QWizard); + if (!(d->opts & option) != !on) + setOptions(d->opts ^ option); +} + +/*! + Returns true if the given \a option is enabled; otherwise, returns + false. + + \sa options, setOption(), setWizardStyle() +*/ +bool QWizard::testOption(WizardOption option) const +{ + Q_D(const QWizard); + return (d->opts & option) != 0; +} + +/*! + \property QWizard::options + \brief the various options that affect the look and feel of the wizard + + By default, the following options are set (depending on the platform): + + \list + \o Windows: HelpButtonOnRight. + \o Mac OS X: NoDefaultButton and NoCancelButton. + \o X11 and QWS (Qt for Embedded Linux): none. + \endlist + + \sa wizardStyle +*/ +void QWizard::setOptions(WizardOptions options) +{ + Q_D(QWizard); + + WizardOptions changed = (options ^ d->opts); + if (!changed) + return; + + d->disableUpdates(); + + d->opts = options; + if ((changed & IndependentPages) && !(d->opts & IndependentPages)) + d->cleanupPagesNotInHistory(); + + if (changed & (NoDefaultButton | HaveHelpButton | HelpButtonOnRight | NoCancelButton + | CancelButtonOnLeft | HaveCustomButton1 | HaveCustomButton2 + | HaveCustomButton3)) { + d->updateButtonLayout(); + } else if (changed & (NoBackButtonOnStartPage | NoBackButtonOnLastPage + | HaveNextButtonOnLastPage | HaveFinishButtonOnEarlyPages + | DisabledBackButtonOnLastPage)) { + d->_q_updateButtonStates(); + } + + d->enableUpdates(); + d->updateLayout(); +} + +QWizard::WizardOptions QWizard::options() const +{ + Q_D(const QWizard); + return d->opts; +} + +/*! + Sets the text on button \a which to be \a text. + + By default, the text on buttons depends on the wizardStyle. For + example, on Mac OS X, the \gui Next button is called \gui + Continue. + + To add extra buttons to the wizard (e.g., a \gui Print button), + one way is to call setButtonText() with CustomButton1, + CustomButton2, or CustomButton3 to set their text, and make the + buttons visible using the HaveCustomButton1, HaveCustomButton2, + and/or HaveCustomButton3 options. + + Button texts may also be set on a per-page basis using QWizardPage::setButtonText(). + + \sa setButton(), button(), setButtonLayout(), setOptions(), QWizardPage::setButtonText() +*/ +void QWizard::setButtonText(WizardButton which, const QString &text) +{ + Q_D(QWizard); + + if (!d->ensureButton(which)) + return; + + d->buttonCustomTexts.insert(which, text); + + if (!currentPage() || !currentPage()->d_func()->buttonCustomTexts.contains(which)) + d->btns[which]->setText(text); +} + +/*! + Returns the text on button \a which. + + If a text has ben set using setButtonText(), this text is returned. + + By default, the text on buttons depends on the wizardStyle. For + example, on Mac OS X, the \gui Next button is called \gui + Continue. + + \sa button(), setButton(), setButtonText(), QWizardPage::buttonText(), + QWizardPage::setButtonText() +*/ +QString QWizard::buttonText(WizardButton which) const +{ + Q_D(const QWizard); + + if (!d->ensureButton(which)) + return QString(); + + if (d->buttonCustomTexts.contains(which)) + return d->buttonCustomTexts.value(which); + + const QString defText = buttonDefaultText(d->wizStyle, which, d); + if(!defText.isNull()) + return defText; + + return d->btns[which]->text(); +} + +/*! + Sets the order in which buttons are displayed to \a layout, where + \a layout is a list of \l{WizardButton}s. + + The default layout depends on the options (e.g., whether + HelpButtonOnRight) that are set. You can call this function if + you need more control over the buttons' layout than what \l + options already provides. + + You can specify horizontal stretches in the layout using \l + Stretch. + + Example: + + \snippet doc/src/snippets/code/src_gui_dialogs_qwizard.cpp 1 + + \sa setButton(), setButtonText(), setOptions() +*/ +void QWizard::setButtonLayout(const QList &layout) +{ + Q_D(QWizard); + + for (int i = 0; i < layout.count(); ++i) { + WizardButton button1 = layout.at(i); + + if (button1 == NoButton || button1 == Stretch) + continue; + if (!d->ensureButton(button1)) + return; + + // O(n^2), but n is very small + for (int j = 0; j < i; ++j) { + WizardButton button2 = layout.at(j); + if (button2 == button1) { + qWarning("QWizard::setButtonLayout: Duplicate button in layout"); + return; + } + } + } + + d->buttonsHaveCustomLayout = true; + d->buttonsCustomLayout = layout; + d->updateButtonLayout(); +} + +/*! + Sets the button corresponding to role \a which to \a button. + + To add extra buttons to the wizard (e.g., a \gui Print button), + one way is to call setButton() with CustomButton1 to + CustomButton3, and make the buttons visible using the + HaveCustomButton1 to HaveCustomButton3 options. + + \sa setButtonText(), setButtonLayout(), options +*/ +void QWizard::setButton(WizardButton which, QAbstractButton *button) +{ + Q_D(QWizard); + + if (uint(which) >= NButtons || d->btns[which] == button) + return; + + if (QAbstractButton *oldButton = d->btns[which]) { + d->buttonLayout->removeWidget(oldButton); + delete oldButton; + } + + d->btns[which] = button; + if (button) { + button->setParent(d->antiFlickerWidget); + d->buttonCustomTexts.insert(which, button->text()); + d->connectButton(which); + } else { + d->buttonCustomTexts.remove(which); // ### what about page-specific texts set for 'which' + d->ensureButton(which); // (QWizardPage::setButtonText())? Clear them as well? + } + + d->updateButtonLayout(); +} + +/*! + Returns the button corresponding to role \a which. + + \sa setButton(), setButtonText() +*/ +QAbstractButton *QWizard::button(WizardButton which) const +{ + Q_D(const QWizard); +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (d->wizStyle == AeroStyle && which == BackButton) + return d->vistaHelper->backButton(); +#endif + if (!d->ensureButton(which)) + return 0; + return d->btns[which]; +} + +/*! + \property QWizard::titleFormat + \brief the text format used by page titles + + The default format is Qt::AutoText. + + \sa QWizardPage::title, subTitleFormat +*/ +void QWizard::setTitleFormat(Qt::TextFormat format) +{ + Q_D(QWizard); + d->titleFmt = format; + d->updateLayout(); +} + +Qt::TextFormat QWizard::titleFormat() const +{ + Q_D(const QWizard); + return d->titleFmt; +} + +/*! + \property QWizard::subTitleFormat + \brief the text format used by page subtitles + + The default format is Qt::AutoText. + + \sa QWizardPage::title, titleFormat +*/ +void QWizard::setSubTitleFormat(Qt::TextFormat format) +{ + Q_D(QWizard); + d->subTitleFmt = format; + d->updateLayout(); +} + +Qt::TextFormat QWizard::subTitleFormat() const +{ + Q_D(const QWizard); + return d->subTitleFmt; +} + +/*! + Sets the pixmap for role \a which to \a pixmap. + + The pixmaps are used by QWizard when displaying a page. Which + pixmaps are actually used depend on the \l{Wizard Look and + Feel}{wizard style}. + + Pixmaps can also be set for a specific page using + QWizardPage::setPixmap(). + + \sa QWizardPage::setPixmap(), {Elements of a Wizard Page} +*/ +void QWizard::setPixmap(WizardPixmap which, const QPixmap &pixmap) +{ + Q_D(QWizard); + Q_ASSERT(uint(which) < NPixmaps); + d->defaultPixmaps[which] = pixmap; + d->updatePixmap(which); +} + +/*! + Returns the pixmap set for role \a which. + + By default, the only pixmap that is set is the BackgroundPixmap on + Mac OS X. + + \sa QWizardPage::pixmap(), {Elements of a Wizard Page} +*/ +QPixmap QWizard::pixmap(WizardPixmap which) const +{ + Q_D(const QWizard); + Q_ASSERT(uint(which) < NPixmaps); +#ifdef Q_WS_MAC + if (which == BackgroundPixmap && d->defaultPixmaps[BackgroundPixmap].isNull()) + d->defaultPixmaps[BackgroundPixmap] = d->findDefaultBackgroundPixmap(); +#endif + return d->defaultPixmaps[which]; +} + +/*! + Sets the default property for \a className to be \a property, + and the associated change signal to be \a changedSignal. + + The default property is used when an instance of \a className (or + of one of its subclasses) is passed to + QWizardPage::registerField() and no property is specified. + + QWizard knows the most common Qt widgets. For these (or their + subclasses), you don't need to specify a \a property or a \a + changedSignal. The table below lists these widgets: + + \table + \header \o Widget \o Property \o Change Notification Signal + \row \o QAbstractButton \o bool \l{QAbstractButton::}{checked} \o \l{QAbstractButton::}{toggled()} + \row \o QAbstractSlider \o int \l{QAbstractSlider::}{value} \o \l{QAbstractSlider::}{valueChanged()} + \row \o QComboBox \o int \l{QComboBox::}{currentIndex} \o \l{QComboBox::}{currentIndexChanged()} + \row \o QDateTimeEdit \o QDateTime \l{QDateTimeEdit::}{dateTime} \o \l{QDateTimeEdit::}{dateTimeChanged()} + \row \o QLineEdit \o QString \l{QLineEdit::}{text} \o \l{QLineEdit::}{textChanged()} + \row \o QListWidget \o int \l{QListWidget::}{currentRow} \o \l{QListWidget::}{currentRowChanged()} + \row \o QSpinBox \o int \l{QSpinBox::}{value} \o \l{QSpinBox::}{valueChanged()} + \endtable + + \sa QWizardPage::registerField() +*/ +void QWizard::setDefaultProperty(const char *className, const char *property, + const char *changedSignal) +{ + Q_D(QWizard); + for (int i = d->defaultPropertyTable.count() - 1; i >= 0; --i) { + if (qstrcmp(d->defaultPropertyTable.at(i).className, className) == 0) { + d->defaultPropertyTable.remove(i); + break; + } + } + d->defaultPropertyTable.append(QWizardDefaultProperty(className, property, changedSignal)); +} + +/*! + \since 4.7 + + Sets the given \a widget to be shown on the left side of the wizard. + For styles which use the WatermarkPixmap (ClassicStyle and ModernStyle) + the side widget is displayed on top of the watermark, for other styles + or when the watermark is not provided the side widget is displayed + on the left side of the wizard. + + Passing 0 shows no side widget. + + When the \a widget is not 0 the wizard reparents it. + + Any previous side widget is hidden. + + You may call setSideWidget() with the same widget at different + times. + + All widgets set here will be deleted by the wizard when it is + destroyed unless you separately reparent the widget after setting + some other side widget (or 0). + + By default, no side widget is present. +*/ +void QWizard::setSideWidget(QWidget *widget) +{ + Q_D(QWizard); + + d->sideWidget = widget; + if (d->watermarkLabel) { + d->watermarkLabel->setSideWidget(widget); + d->updateLayout(); + } +} + +/*! + \since 4.7 + + Returns the widget on the left side of the wizard or 0. + + By default, no side widget is present. +*/ +QWidget *QWizard::sideWidget() const +{ + Q_D(const QWizard); + + return d->sideWidget; +} + +/*! + \reimp +*/ +void QWizard::setVisible(bool visible) +{ + Q_D(QWizard); + if (visible) { + if (d->current == -1) + restart(); + } + QDialog::setVisible(visible); +} + +/*! + \reimp +*/ +QSize QWizard::sizeHint() const +{ + Q_D(const QWizard); + QSize result = d->mainLayout->totalSizeHint(); +#ifdef Q_WS_S60 + QSize extra(QApplication::desktop()->availableGeometry(QCursor::pos()).size()); +#else + QSize extra(500, 360); +#endif + if (d->wizStyle == MacStyle && d->current != -1) { + QSize pixmap(currentPage()->pixmap(BackgroundPixmap).size()); + extra.setWidth(616); + if (!pixmap.isNull()) { + extra.setHeight(pixmap.height()); + + /* + The width isn't always reliable as a size hint, as + some wizard backgrounds just cover the leftmost area. + Use a rule of thumb to determine if the width is + reliable or not. + */ + if (pixmap.width() >= pixmap.height()) + extra.setWidth(pixmap.width()); + } + } + return result.expandedTo(extra); +} + +/*! + \fn void QWizard::currentIdChanged(int id) + + This signal is emitted when the current page changes, with the new + current \a id. + + \sa currentId(), currentPage() +*/ + +/*! + \fn void QWizard::pageAdded(int id) + + \since 4.7 + + This signal is emitted whenever a page is added to the + wizard. The page's \a id is passed as parameter. + + \sa addPage(), setPage(), startId() +*/ + +/*! + \fn void QWizard::pageRemoved(int id) + + \since 4.7 + + This signal is emitted whenever a page is removed from the + wizard. The page's \a id is passed as parameter. + + \sa removePage(), startId() +*/ + +/*! + \fn void QWizard::helpRequested() + + This signal is emitted when the user clicks the \gui Help button. + + By default, no \gui Help button is shown. Call + setOption(HaveHelpButton, true) to have one. + + Example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 0 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 5 + \snippet examples/dialogs/licensewizard/licensewizard.cpp 7 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 8 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 10 + \dots + \snippet examples/dialogs/licensewizard/licensewizard.cpp 12 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 14 + \codeline + \snippet examples/dialogs/licensewizard/licensewizard.cpp 15 + + \sa customButtonClicked() +*/ + +/*! + \fn void QWizard::customButtonClicked(int which) + + This signal is emitted when the user clicks a custom button. \a + which can be CustomButton1, CustomButton2, or CustomButton3. + + By default, no custom button is shown. Call setOption() with + HaveCustomButton1, HaveCustomButton2, or HaveCustomButton3 to have + one, and use setButtonText() or setButton() to configure it. + + \sa helpRequested() +*/ + +/*! + Goes back to the previous page. + + This is equivalent to pressing the \gui Back button. + + \sa next(), accept(), reject(), restart() +*/ +void QWizard::back() +{ + Q_D(QWizard); + int n = d->history.count() - 2; + if (n < 0) + return; + d->switchToPage(d->history.at(n), QWizardPrivate::Backward); +} + +/*! + Advances to the next page. + + This is equivalent to pressing the \gui Next or \gui Commit button. + + \sa nextId(), back(), accept(), reject(), restart() +*/ +void QWizard::next() +{ + Q_D(QWizard); + + if (d->current == -1) + return; + + if (validateCurrentPage()) { + int next = nextId(); + if (next != -1) { + if (d->history.contains(next)) { + qWarning("QWizard::next: Page %d already met", next); + return; + } + if (!d->pageMap.contains(next)) { + qWarning("QWizard::next: No such page %d", next); + return; + } + d->switchToPage(next, QWizardPrivate::Forward); + } + } +} + +/*! + Restarts the wizard at the start page. This function is called automatically when the + wizard is shown. + + \sa startId() +*/ +void QWizard::restart() +{ + Q_D(QWizard); + d->disableUpdates(); + d->reset(); + d->switchToPage(startId(), QWizardPrivate::Forward); + d->enableUpdates(); +} + +/*! + \reimp +*/ +bool QWizard::event(QEvent *event) +{ + Q_D(QWizard); + if (event->type() == QEvent::StyleChange) { // Propagate style + d->setStyle(style()); + d->updateLayout(); + } +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + else if (event->type() == QEvent::Show && d->vistaInitPending) { + d->vistaInitPending = false; + d->wizStyle = AeroStyle; + d->handleAeroStyleChange(); + } + else if (d->isVistaThemeEnabled()) { + d->vistaHelper->mouseEvent(event); + } +#endif + return QDialog::event(event); +} + +/*! + \reimp +*/ +void QWizard::resizeEvent(QResizeEvent *event) +{ + Q_D(QWizard); + int heightOffset = 0; +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (d->isVistaThemeEnabled()) { + heightOffset = d->vistaHelper->topOffset(); + if (d->isVistaThemeEnabled(QVistaHelper::VistaAero)) + heightOffset += d->vistaHelper->titleBarSize(); + } +#endif + d->antiFlickerWidget->resize(event->size().width(), event->size().height() - heightOffset); +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + if (d->isVistaThemeEnabled()) + d->vistaHelper->resizeEvent(event); +#endif + QDialog::resizeEvent(event); +} + +/*! + \reimp +*/ +void QWizard::paintEvent(QPaintEvent * event) +{ + Q_D(QWizard); + if (d->wizStyle == MacStyle && currentPage()) { + QPixmap backgroundPixmap = currentPage()->pixmap(BackgroundPixmap); + if (backgroundPixmap.isNull()) + return; + + QPainter painter(this); + painter.drawPixmap(0, (height() - backgroundPixmap.height()) / 2, backgroundPixmap); + } +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + else if (d->isVistaThemeEnabled()) { + if (d->isVistaThemeEnabled(QVistaHelper::VistaBasic)) { + QPainter painter(this); + QColor color = d->vistaHelper->basicWindowFrameColor(); + painter.fillRect(0, 0, width(), QVistaHelper::topOffset(), color); + } + d->vistaHelper->paintEvent(event); + } +#else + Q_UNUSED(event); +#endif +} + +#if defined(Q_WS_WIN) +/*! + \reimp +*/ +bool QWizard::winEvent(MSG *message, long *result) +{ +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + Q_D(QWizard); + if (d->isVistaThemeEnabled()) { + const bool winEventResult = d->vistaHelper->handleWinEvent(message, result); + if (QVistaHelper::vistaState() != d->vistaState) { + d->vistaState = QVistaHelper::vistaState(); + d->vistaStateChanged = true; + setWizardStyle(AeroStyle); + } + return winEventResult; + } else { + return QDialog::winEvent(message, result); + } +#else + return QDialog::winEvent(message, result); +#endif +} +#endif + +/*! + \reimp +*/ +void QWizard::done(int result) +{ + Q_D(QWizard); + // canceling leaves the wizard in a known state + if (result == Rejected) { + d->reset(); + } else { + if (!validateCurrentPage()) + return; + } + QDialog::done(result); +} + +/*! + \fn void QWizard::initializePage(int id) + + This virtual function is called by QWizard to prepare page \a id + just before it is shown either as a result of QWizard::restart() + being called, or as a result of the user clicking \gui Next. (However, if the \l + QWizard::IndependentPages option is set, this function is only + called the first time the page is shown.) + + By reimplementing this function, you can ensure that the page's + fields are properly initialized based on fields from previous + pages. + + The default implementation calls QWizardPage::initializePage() on + page(\a id). + + \sa QWizardPage::initializePage(), cleanupPage() +*/ +void QWizard::initializePage(int theid) +{ + QWizardPage *page = this->page(theid); + if (page) + page->initializePage(); +} + +/*! + \fn void QWizard::cleanupPage(int id) + + This virtual function is called by QWizard to clean up page \a id just before the + user leaves it by clicking \gui Back (unless the \l QWizard::IndependentPages option is set). + + The default implementation calls QWizardPage::cleanupPage() on + page(\a id). + + \sa QWizardPage::cleanupPage(), initializePage() +*/ +void QWizard::cleanupPage(int theid) +{ + QWizardPage *page = this->page(theid); + if (page) + page->cleanupPage(); +} + +/*! + This virtual function is called by QWizard when the user clicks + \gui Next or \gui Finish to perform some last-minute validation. + If it returns true, the next page is shown (or the wizard + finishes); otherwise, the current page stays up. + + The default implementation calls QWizardPage::validatePage() on + the currentPage(). + + When possible, it is usually better style to disable the \gui + Next or \gui Finish button (by specifying \l{mandatory fields} or + by reimplementing QWizardPage::isComplete()) than to reimplement + validateCurrentPage(). + + \sa QWizardPage::validatePage(), currentPage() +*/ +bool QWizard::validateCurrentPage() +{ + QWizardPage *page = currentPage(); + if (!page) + return true; + + return page->validatePage(); +} + +/*! + This virtual function is called by QWizard to find out which page + to show when the user clicks the \gui Next button. + + The return value is the ID of the next page, or -1 if no page follows. + + The default implementation calls QWizardPage::nextId() on the + currentPage(). + + By reimplementing this function, you can specify a dynamic page + order. + + \sa QWizardPage::nextId(), currentPage() +*/ +int QWizard::nextId() const +{ + const QWizardPage *page = currentPage(); + if (!page) + return -1; + + return page->nextId(); +} + +/*! + \class QWizardPage + \since 4.3 + \brief The QWizardPage class is the base class for wizard pages. + + QWizard represents a wizard. Each page is a QWizardPage. When + you create your own wizards, you can use QWizardPage directly, + or you can subclass it for more control. + + A page has the following attributes, which are rendered by + QWizard: a \l title, a \l subTitle, and a \l{setPixmap()}{set of + pixmaps}. See \l{Elements of a Wizard Page} for details. Once a + page is added to the wizard (using QWizard::addPage() or + QWizard::setPage()), wizard() returns a pointer to the + associated QWizard object. + + Page provides five virtual functions that can be reimplemented to + provide custom behavior: + + \list + \o initializePage() is called to initialize the page's contents + when the user clicks the wizard's \gui Next button. If you + want to derive the page's default from what the user entered + on previous pages, this is the function to reimplement. + \o cleanupPage() is called to reset the page's contents when the + user clicks the wizard's \gui Back button. + \o validatePage() validates the page when the user clicks \gui + Next or \gui Finish. It is often used to show an error message + if the user has entered incomplete or invalid information. + \o nextId() returns the ID of the next page. It is useful when + \l{creating non-linear wizards}, which allow different + traversal paths based on the information provided by the user. + \o isComplete() is called to determine whether the \gui Next + and/or \gui Finish button should be enabled or disabled. If + you reimplement isComplete(), also make sure that + completeChanged() is emitted whenever the complete state + changes. + \endlist + + Normally, the \gui Next button and the \gui Finish button of a + wizard are mutually exclusive. If isFinalPage() returns true, \gui + Finish is available; otherwise, \gui Next is available. By + default, isFinalPage() is true only when nextId() returns -1. If + you want to show \gui Next and \gui Final simultaneously for a + page (letting the user perform an "early finish"), call + setFinalPage(true) on that page. For wizards that support early + finishes, you might also want to set the + \l{QWizard::}{HaveNextButtonOnLastPage} and + \l{QWizard::}{HaveFinishButtonOnEarlyPages} options on the + wizard. + + In many wizards, the contents of a page may affect the default + values of the fields of a later page. To make it easy to + communicate between pages, QWizard supports a \l{Registering and + Using Fields}{"field" mechanism} that allows you to register a + field (e.g., a QLineEdit) on a page and to access its value from + any page. Fields are global to the entire wizard and make it easy + for any single page to access information stored by another page, + without having to put all the logic in QWizard or having the + pages know explicitly about each other. Fields are registered + using registerField() and can be accessed at any time using + field() and setField(). + + \sa QWizard, {Class Wizard Example}, {License Wizard Example} +*/ + +/*! + Constructs a wizard page with the given \a parent. + + When the page is inserted into a wizard using QWizard::addPage() + or QWizard::setPage(), the parent is automatically set to be the + wizard. + + \sa wizard() +*/ +QWizardPage::QWizardPage(QWidget *parent) + : QWidget(*new QWizardPagePrivate, parent, 0) +{ + connect(this, SIGNAL(completeChanged()), this, SLOT(_q_updateCachedCompleteState())); +} + +/*! + \property QWizardPage::title + \brief the title of the page + + The title is shown by the QWizard, above the actual page. All + pages should have a title. + + The title may be plain text or HTML, depending on the value of the + \l{QWizard::titleFormat} property. + + By default, this property contains an empty string. + + \sa subTitle, {Elements of a Wizard Page} +*/ +void QWizardPage::setTitle(const QString &title) +{ + Q_D(QWizardPage); + d->title = title; + if (d->wizard && d->wizard->currentPage() == this) + d->wizard->d_func()->updateLayout(); +} + +QString QWizardPage::title() const +{ + Q_D(const QWizardPage); + return d->title; +} + +/*! + \property QWizardPage::subTitle + \brief the subtitle of the page + + The subtitle is shown by the QWizard, between the title and the + actual page. Subtitles are optional. In + \l{QWizard::ClassicStyle}{ClassicStyle} and + \l{QWizard::ModernStyle}{ModernStyle}, using subtitles is + necessary to make the header appear. In + \l{QWizard::MacStyle}{MacStyle}, the subtitle is shown as a text + label just above the actual page. + + The subtitle may be plain text or HTML, depending on the value of + the \l{QWizard::subTitleFormat} property. + + By default, this property contains an empty string. + + \sa title, QWizard::IgnoreSubTitles, {Elements of a Wizard Page} +*/ +void QWizardPage::setSubTitle(const QString &subTitle) +{ + Q_D(QWizardPage); + d->subTitle = subTitle; + if (d->wizard && d->wizard->currentPage() == this) + d->wizard->d_func()->updateLayout(); +} + +QString QWizardPage::subTitle() const +{ + Q_D(const QWizardPage); + return d->subTitle; +} + +/*! + Sets the pixmap for role \a which to \a pixmap. + + The pixmaps are used by QWizard when displaying a page. Which + pixmaps are actually used depend on the \l{Wizard Look and + Feel}{wizard style}. + + Pixmaps can also be set for the entire wizard using + QWizard::setPixmap(), in which case they apply for all pages that + don't specify a pixmap. + + \sa QWizard::setPixmap(), {Elements of a Wizard Page} +*/ +void QWizardPage::setPixmap(QWizard::WizardPixmap which, const QPixmap &pixmap) +{ + Q_D(QWizardPage); + Q_ASSERT(uint(which) < QWizard::NPixmaps); + d->pixmaps[which] = pixmap; + if (d->wizard && d->wizard->currentPage() == this) + d->wizard->d_func()->updatePixmap(which); +} + +/*! + Returns the pixmap set for role \a which. + + Pixmaps can also be set for the entire wizard using + QWizard::setPixmap(), in which case they apply for all pages that + don't specify a pixmap. + + \sa QWizard::pixmap(), {Elements of a Wizard Page} +*/ +QPixmap QWizardPage::pixmap(QWizard::WizardPixmap which) const +{ + Q_D(const QWizardPage); + Q_ASSERT(uint(which) < QWizard::NPixmaps); + + const QPixmap &pixmap = d->pixmaps[which]; + if (!pixmap.isNull()) + return pixmap; + + if (wizard()) + return wizard()->pixmap(which); + + return pixmap; +} + +/*! + This virtual function is called by QWizard::initializePage() to + prepare the page just before it is shown either as a result of QWizard::restart() + being called, or as a result of the user clicking \gui Next. + (However, if the \l QWizard::IndependentPages option is set, this function is only + called the first time the page is shown.) + + By reimplementing this function, you can ensure that the page's + fields are properly initialized based on fields from previous + pages. For example: + + \snippet examples/dialogs/classwizard/classwizard.cpp 17 + + The default implementation does nothing. + + \sa QWizard::initializePage(), cleanupPage(), QWizard::IndependentPages +*/ +void QWizardPage::initializePage() +{ +} + +/*! + This virtual function is called by QWizard::cleanupPage() when + the user leaves the page by clicking \gui Back (unless the \l QWizard::IndependentPages + option is set). + + The default implementation resets the page's fields to their + original values (the values they had before initializePage() was + called). + + \sa QWizard::cleanupPage(), initializePage(), QWizard::IndependentPages +*/ +void QWizardPage::cleanupPage() +{ + Q_D(QWizardPage); + if (d->wizard) { + QVector &fields = d->wizard->d_func()->fields; + for (int i = 0; i < fields.count(); ++i) { + const QWizardField &field = fields.at(i); + if (field.page == this) + field.object->setProperty(field.property, field.initialValue); + } + } +} + +/*! + This virtual function is called by QWizard::validateCurrentPage() + when the user clicks \gui Next or \gui Finish to perform some + last-minute validation. If it returns true, the next page is shown + (or the wizard finishes); otherwise, the current page stays up. + + The default implementation returns true. + + When possible, it is usually better style to disable the \gui + Next or \gui Finish button (by specifying \l{mandatory fields} or + reimplementing isComplete()) than to reimplement validatePage(). + + \sa QWizard::validateCurrentPage(), isComplete() +*/ +bool QWizardPage::validatePage() +{ + return true; +} + +/*! + This virtual function is called by QWizard to determine whether + the \gui Next or \gui Finish button should be enabled or + disabled. + + The default implementation returns true if all \l{mandatory + fields} are filled; otherwise, it returns false. + + If you reimplement this function, make sure to emit completeChanged(), + from the rest of your implementation, whenever the value of isComplete() + changes. This ensures that QWizard updates the enabled or disabled state of + its buttons. An example of the reimplementation is + available \l{http://qt.nokia.com/doc/qq/qq22-qwizard.html#validatebeforeitstoolate} + {here}. + + \sa completeChanged(), isFinalPage() +*/ +bool QWizardPage::isComplete() const +{ + Q_D(const QWizardPage); + + if (!d->wizard) + return true; + + const QVector &wizardFields = d->wizard->d_func()->fields; + for (int i = wizardFields.count() - 1; i >= 0; --i) { + const QWizardField &field = wizardFields.at(i); + if (field.page == this && field.mandatory) { + QVariant value = field.object->property(field.property); + if (value == field.initialValue) + return false; + +#ifndef QT_NO_LINEEDIT + if (QLineEdit *lineEdit = qobject_cast(field.object)) { + if (!lineEdit->hasAcceptableInput()) + return false; + } +#endif +#ifndef QT_NO_SPINBOX + if (QAbstractSpinBox *spinBox = qobject_cast(field.object)) { + if (!spinBox->hasAcceptableInput()) + return false; + } +#endif + } + } + return true; +} + +/*! + Explicitly sets this page to be final if \a finalPage is true. + + After calling setFinalPage(true), isFinalPage() returns true and the \gui + Finish button is visible (and enabled if isComplete() returns + true). + + After calling setFinalPage(false), isFinalPage() returns true if + nextId() returns -1; otherwise, it returns false. + + \sa isComplete(), QWizard::HaveFinishButtonOnEarlyPages +*/ +void QWizardPage::setFinalPage(bool finalPage) +{ + Q_D(QWizardPage); + d->explicitlyFinal = finalPage; + QWizard *wizard = this->wizard(); + if (wizard && wizard->currentPage() == this) + wizard->d_func()->updateCurrentPage(); +} + +/*! + This function is called by QWizard to determine whether the \gui + Finish button should be shown for this page or not. + + By default, it returns true if there is no next page + (i.e., nextId() returns -1); otherwise, it returns false. + + By explicitly calling setFinalPage(true), you can let the user perform an + "early finish". + + \sa isComplete(), QWizard::HaveFinishButtonOnEarlyPages +*/ +bool QWizardPage::isFinalPage() const +{ + Q_D(const QWizardPage); + if (d->explicitlyFinal) + return true; + + QWizard *wizard = this->wizard(); + if (wizard && wizard->currentPage() == this) { + // try to use the QWizard implementation if possible + return wizard->nextId() == -1; + } else { + return nextId() == -1; + } +} + +/*! + Sets this page to be a commit page if \a commitPage is true; otherwise, + sets it to be a normal page. + + A commit page is a page that represents an action which cannot be undone + by clicking \gui Back or \gui Cancel. + + A \gui Commit button replaces the \gui Next button on a commit page. Clicking this + button simply calls QWizard::next() just like clicking \gui Next does. + + A page entered directly from a commit page has its \gui Back button disabled. + + \sa isCommitPage() +*/ +void QWizardPage::setCommitPage(bool commitPage) +{ + Q_D(QWizardPage); + d->commit = commitPage; + QWizard *wizard = this->wizard(); + if (wizard && wizard->currentPage() == this) + wizard->d_func()->updateCurrentPage(); +} + +/*! + Returns true if this page is a commit page; otherwise returns false. + + \sa setCommitPage() +*/ +bool QWizardPage::isCommitPage() const +{ + Q_D(const QWizardPage); + return d->commit; +} + +/*! + Sets the text on button \a which to be \a text on this page. + + By default, the text on buttons depends on the QWizard::wizardStyle, + but may be redefined for the wizard as a whole using QWizard::setButtonText(). + + \sa buttonText(), QWizard::setButtonText(), QWizard::buttonText() +*/ +void QWizardPage::setButtonText(QWizard::WizardButton which, const QString &text) +{ + Q_D(QWizardPage); + d->buttonCustomTexts.insert(which, text); + if (wizard() && wizard()->currentPage() == this && wizard()->d_func()->btns[which]) + wizard()->d_func()->btns[which]->setText(text); +} + +/*! + Returns the text on button \a which on this page. + + If a text has ben set using setButtonText(), this text is returned. + Otherwise, if a text has been set using QWizard::setButtonText(), + this text is returned. + + By default, the text on buttons depends on the QWizard::wizardStyle. + For example, on Mac OS X, the \gui Next button is called \gui + Continue. + + \sa setButtonText(), QWizard::buttonText(), QWizard::setButtonText() +*/ +QString QWizardPage::buttonText(QWizard::WizardButton which) const +{ + Q_D(const QWizardPage); + + if (d->buttonCustomTexts.contains(which)) + return d->buttonCustomTexts.value(which); + + if (wizard()) + return wizard()->buttonText(which); + + return QString(); +} + +/*! + This virtual function is called by QWizard::nextId() to find + out which page to show when the user clicks the \gui Next button. + + The return value is the ID of the next page, or -1 if no page follows. + + By default, this function returns the lowest ID greater than the ID + of the current page, or -1 if there is no such ID. + + By reimplementing this function, you can specify a dynamic page + order. For example: + + \snippet examples/dialogs/licensewizard/licensewizard.cpp 18 + + \sa QWizard::nextId() +*/ +int QWizardPage::nextId() const +{ + Q_D(const QWizardPage); + + if (!d->wizard) + return -1; + + bool foundCurrentPage = false; + + const QWizardPrivate::PageMap &pageMap = d->wizard->d_func()->pageMap; + QWizardPrivate::PageMap::const_iterator i = pageMap.constBegin(); + QWizardPrivate::PageMap::const_iterator end = pageMap.constEnd(); + + for (; i != end; ++i) { + if (i.value() == this) { + foundCurrentPage = true; + } else if (foundCurrentPage) { + return i.key(); + } + } + return -1; +} + +/*! + \fn void QWizardPage::completeChanged() + + This signal is emitted whenever the complete state of the page + (i.e., the value of isComplete()) changes. + + If you reimplement isComplete(), make sure to emit + completeChanged() whenever the value of isComplete() changes, to + ensure that QWizard updates the enabled or disabled state of its + buttons. + + \sa isComplete() +*/ + +/*! + Sets the value of the field called \a name to \a value. + + This function can be used to set fields on any page of the wizard. + It is equivalent to calling + wizard()->\l{QWizard::setField()}{setField(\a name, \a value)}. + + \sa QWizard::setField(), field(), registerField() +*/ +void QWizardPage::setField(const QString &name, const QVariant &value) +{ + Q_D(QWizardPage); + if (!d->wizard) + return; + d->wizard->setField(name, value); +} + +/*! + Returns the value of the field called \a name. + + This function can be used to access fields on any page of the + wizard. It is equivalent to calling + wizard()->\l{QWizard::field()}{field(\a name)}. + + Example: + + \snippet examples/dialogs/classwizard/classwizard.cpp 17 + + \sa QWizard::field(), setField(), registerField() +*/ +QVariant QWizardPage::field(const QString &name) const +{ + Q_D(const QWizardPage); + if (!d->wizard) + return QVariant(); + return d->wizard->field(name); +} + +/*! + Creates a field called \a name associated with the given \a + property of the given \a widget. From then on, that property + becomes accessible using field() and setField(). + + Fields are global to the entire wizard and make it easy for any + single page to access information stored by another page, without + having to put all the logic in QWizard or having the pages know + explicitly about each other. + + If \a name ends with an asterisk (\c *), the field is a mandatory + field. When a page has mandatory fields, the \gui Next and/or + \gui Finish buttons are enabled only when all mandatory fields + are filled. This requires a \a changedSignal to be specified, to + tell QWizard to recheck the value stored by the mandatory field. + + QWizard knows the most common Qt widgets. For these (or their + subclasses), you don't need to specify a \a property or a \a + changedSignal. The table below lists these widgets: + + \table + \header \o Widget \o Property \o Change Notification Signal + \row \o QAbstractButton \o bool \l{QAbstractButton::}{checked} \o \l{QAbstractButton::}{toggled()} + \row \o QAbstractSlider \o int \l{QAbstractSlider::}{value} \o \l{QAbstractSlider::}{valueChanged()} + \row \o QComboBox \o int \l{QComboBox::}{currentIndex} \o \l{QComboBox::}{currentIndexChanged()} + \row \o QDateTimeEdit \o QDateTime \l{QDateTimeEdit::}{dateTime} \o \l{QDateTimeEdit::}{dateTimeChanged()} + \row \o QLineEdit \o QString \l{QLineEdit::}{text} \o \l{QLineEdit::}{textChanged()} + \row \o QListWidget \o int \l{QListWidget::}{currentRow} \o \l{QListWidget::}{currentRowChanged()} + \row \o QSpinBox \o int \l{QSpinBox::}{value} \o \l{QSpinBox::}{valueChanged()} + \endtable + + You can use QWizard::setDefaultProperty() to add entries to this + table or to override existing entries. + + To consider a field "filled", QWizard simply checks that their + current value doesn't equal their original value (the value they + had before initializePage() was called). For QLineEdit, it also + checks that + \l{QLineEdit::hasAcceptableInput()}{hasAcceptableInput()} returns + true, to honor any validator or mask. + + QWizard's mandatory field mechanism is provided for convenience. + It can be bypassed by reimplementing QWizardPage::isComplete(). + + \sa field(), setField(), QWizard::setDefaultProperty() +*/ +void QWizardPage::registerField(const QString &name, QWidget *widget, const char *property, + const char *changedSignal) +{ + Q_D(QWizardPage); + QWizardField field(this, name, widget, property, changedSignal); + if (d->wizard) { + d->wizard->d_func()->addField(field); + } else { + d->pendingFields += field; + } +} + +/*! + Returns the wizard associated with this page, or 0 if this page + hasn't been inserted into a QWizard yet. + + \sa QWizard::addPage(), QWizard::setPage() +*/ +QWizard *QWizardPage::wizard() const +{ + Q_D(const QWizardPage); + return d->wizard; +} + +QT_END_NAMESPACE + +#include "moc_qwizard.cpp" + +#endif // QT_NO_WIZARD diff --git a/src/gui/dialogs/qwizard.h b/src/gui/dialogs/qwizard.h new file mode 100644 index 0000000000..148a5924b9 --- /dev/null +++ b/src/gui/dialogs/qwizard.h @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIZARD_H +#define QWIZARD_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_WIZARD + +class QAbstractButton; +class QWizardPage; +class QWizardPrivate; + +class Q_GUI_EXPORT QWizard : public QDialog +{ + Q_OBJECT + Q_ENUMS(WizardStyle WizardOption) + Q_FLAGS(WizardOptions) + Q_PROPERTY(WizardStyle wizardStyle READ wizardStyle WRITE setWizardStyle) + Q_PROPERTY(WizardOptions options READ options WRITE setOptions) + Q_PROPERTY(Qt::TextFormat titleFormat READ titleFormat WRITE setTitleFormat) + Q_PROPERTY(Qt::TextFormat subTitleFormat READ subTitleFormat WRITE setSubTitleFormat) + Q_PROPERTY(int startId READ startId WRITE setStartId) + Q_PROPERTY(int currentId READ currentId NOTIFY currentIdChanged) + +public: + enum WizardButton { + BackButton, + NextButton, + CommitButton, + FinishButton, + CancelButton, + HelpButton, + CustomButton1, + CustomButton2, + CustomButton3, + Stretch, + + NoButton = -1, + NStandardButtons = 6, + NButtons = 9 + }; + + enum WizardPixmap { + WatermarkPixmap, + LogoPixmap, + BannerPixmap, + BackgroundPixmap, + NPixmaps + }; + + enum WizardStyle { + ClassicStyle, + ModernStyle, + MacStyle, + AeroStyle, + NStyles + }; + + enum WizardOption { + IndependentPages = 0x00000001, + IgnoreSubTitles = 0x00000002, + ExtendedWatermarkPixmap = 0x00000004, + NoDefaultButton = 0x00000008, + NoBackButtonOnStartPage = 0x00000010, + NoBackButtonOnLastPage = 0x00000020, + DisabledBackButtonOnLastPage = 0x00000040, + HaveNextButtonOnLastPage = 0x00000080, + HaveFinishButtonOnEarlyPages = 0x00000100, + NoCancelButton = 0x00000200, + CancelButtonOnLeft = 0x00000400, + HaveHelpButton = 0x00000800, + HelpButtonOnRight = 0x00001000, + HaveCustomButton1 = 0x00002000, + HaveCustomButton2 = 0x00004000, + HaveCustomButton3 = 0x00008000 + }; + + Q_DECLARE_FLAGS(WizardOptions, WizardOption) + + explicit QWizard(QWidget *parent = 0, Qt::WindowFlags flags = 0); + ~QWizard(); + + int addPage(QWizardPage *page); + void setPage(int id, QWizardPage *page); + void removePage(int id); + QWizardPage *page(int id) const; + bool hasVisitedPage(int id) const; + QList visitedPages() const; // ### visitedIds()? + QList pageIds() const; + void setStartId(int id); + int startId() const; + QWizardPage *currentPage() const; + int currentId() const; + + virtual bool validateCurrentPage(); + virtual int nextId() const; + + void setField(const QString &name, const QVariant &value); + QVariant field(const QString &name) const; + + void setWizardStyle(WizardStyle style); + WizardStyle wizardStyle() const; + + void setOption(WizardOption option, bool on = true); + bool testOption(WizardOption option) const; + void setOptions(WizardOptions options); + WizardOptions options() const; + + void setButtonText(WizardButton which, const QString &text); + QString buttonText(WizardButton which) const; + void setButtonLayout(const QList &layout); + void setButton(WizardButton which, QAbstractButton *button); + QAbstractButton *button(WizardButton which) const; + + void setTitleFormat(Qt::TextFormat format); + Qt::TextFormat titleFormat() const; + void setSubTitleFormat(Qt::TextFormat format); + Qt::TextFormat subTitleFormat() const; + void setPixmap(WizardPixmap which, const QPixmap &pixmap); + QPixmap pixmap(WizardPixmap which) const; + + void setSideWidget(QWidget *widget); + QWidget *sideWidget() const; + + void setDefaultProperty(const char *className, const char *property, + const char *changedSignal); + + void setVisible(bool visible); + QSize sizeHint() const; + +Q_SIGNALS: + void currentIdChanged(int id); + void helpRequested(); + void customButtonClicked(int which); + void pageAdded(int id); + void pageRemoved(int id); + +public Q_SLOTS: + void back(); + void next(); + void restart(); + +protected: + bool event(QEvent *event); + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); +#if defined(Q_WS_WIN) + bool winEvent(MSG * message, long * result); +#endif + void done(int result); + virtual void initializePage(int id); + virtual void cleanupPage(int id); + +private: + Q_DISABLE_COPY(QWizard) + Q_DECLARE_PRIVATE(QWizard) + Q_PRIVATE_SLOT(d_func(), void _q_emitCustomButtonClicked()) + Q_PRIVATE_SLOT(d_func(), void _q_updateButtonStates()) + Q_PRIVATE_SLOT(d_func(), void _q_handleFieldObjectDestroyed(QObject *)) + + friend class QWizardPage; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWizard::WizardOptions) + +class QWizardPagePrivate; + +class Q_GUI_EXPORT QWizardPage : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(QString subTitle READ subTitle WRITE setSubTitle) + +public: + QWizardPage(QWidget *parent = 0); + + void setTitle(const QString &title); + QString title() const; + void setSubTitle(const QString &subTitle); + QString subTitle() const; + void setPixmap(QWizard::WizardPixmap which, const QPixmap &pixmap); + QPixmap pixmap(QWizard::WizardPixmap which) const; + void setFinalPage(bool finalPage); + bool isFinalPage() const; + void setCommitPage(bool commitPage); + bool isCommitPage() const; + void setButtonText(QWizard::WizardButton which, const QString &text); + QString buttonText(QWizard::WizardButton which) const; + + virtual void initializePage(); + virtual void cleanupPage(); + virtual bool validatePage(); + virtual bool isComplete() const; + virtual int nextId() const; + +Q_SIGNALS: + void completeChanged(); + +protected: + void setField(const QString &name, const QVariant &value); + QVariant field(const QString &name) const; + void registerField(const QString &name, QWidget *widget, const char *property = 0, + const char *changedSignal = 0); + QWizard *wizard() const; + +private: + Q_DISABLE_COPY(QWizardPage) + Q_DECLARE_PRIVATE(QWizardPage) + Q_PRIVATE_SLOT(d_func(), void _q_maybeEmitCompleteChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_updateCachedCompleteState()) + + friend class QWizard; + friend class QWizardPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_WIZARD + +#endif // QWIZARD_H diff --git a/src/gui/dialogs/qwizard_win.cpp b/src/gui/dialogs/qwizard_win.cpp new file mode 100644 index 0000000000..3480c6853c --- /dev/null +++ b/src/gui/dialogs/qwizard_win.cpp @@ -0,0 +1,759 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_WIZARD +#ifndef QT_NO_STYLE_WINDOWSVISTA + +#include "qwizard_win_p.h" +#include +#include "qwizard.h" +#include "qpaintengine.h" +#include "qapplication.h" +#include +#include + +// Note, these tests are duplicates in qwindowsxpstyle_p.h. +#ifdef Q_CC_GNU +# include +# if (__W32API_MAJOR_VERSION >= 3 || (__W32API_MAJOR_VERSION == 2 && __W32API_MINOR_VERSION >= 5)) +# ifdef _WIN32_WINNT +# undef _WIN32_WINNT +# endif +# define _WIN32_WINNT 0x0501 +# include +# endif +#endif + +#include + +QT_BEGIN_NAMESPACE + +//DWM related +typedef struct { //MARGINS + int cxLeftWidth; // width of left border that retains its size + int cxRightWidth; // width of right border that retains its size + int cyTopHeight; // height of top border that retains its size + int cyBottomHeight; // height of bottom border that retains its size +} WIZ_MARGINS; +typedef struct { //DTTOPTS + DWORD dwSize; + DWORD dwFlags; + COLORREF crText; + COLORREF crBorder; + COLORREF crShadow; + int eTextShadowType; + POINT ptShadowOffset; + int iBorderSize; + int iFontPropId; + int iColorPropId; + int iStateId; + BOOL fApplyOverlay; + int iGlowSize; +} WIZ_DTTOPTS; + +typedef struct { + DWORD dwFlags; + DWORD dwMask; +} WIZ_WTA_OPTIONS; + +#define WIZ_WM_THEMECHANGED 0x031A +#define WIZ_WM_DWMCOMPOSITIONCHANGED 0x031E + +enum WIZ_WINDOWTHEMEATTRIBUTETYPE { + WIZ_WTA_NONCLIENT = 1 +}; + +#define WIZ_WTNCA_NODRAWCAPTION 0x00000001 +#define WIZ_WTNCA_NODRAWICON 0x00000002 + +#define WIZ_DT_CENTER 0x00000001 //DT_CENTER +#define WIZ_DT_VCENTER 0x00000004 +#define WIZ_DT_SINGLELINE 0x00000020 +#define WIZ_DT_NOPREFIX 0x00000800 + +enum WIZ_NAVIGATIONPARTS { //NAVIGATIONPARTS + WIZ_NAV_BACKBUTTON = 1, + WIZ_NAV_FORWARDBUTTON = 2, + WIZ_NAV_MENUBUTTON = 3, +}; + +enum WIZ_NAV_BACKBUTTONSTATES { //NAV_BACKBUTTONSTATES + WIZ_NAV_BB_NORMAL = 1, + WIZ_NAV_BB_HOT = 2, + WIZ_NAV_BB_PRESSED = 3, + WIZ_NAV_BB_DISABLED = 4, +}; + +#define WIZ_TMT_CAPTIONFONT (801) //TMT_CAPTIONFONT +#define WIZ_DTT_COMPOSITED (1UL << 13) //DTT_COMPOSITED +#define WIZ_DTT_GLOWSIZE (1UL << 11) //DTT_GLOWSIZE + +#define WIZ_WM_NCMOUSELEAVE 674 //WM_NCMOUSELEAVE + +#define WIZ_WP_CAPTION 1 //WP_CAPTION +#define WIZ_CS_ACTIVE 1 //CS_ACTIVE +#define WIZ_TMT_FILLCOLORHINT 3821 //TMT_FILLCOLORHINT +#define WIZ_TMT_BORDERCOLORHINT 3822 //TMT_BORDERCOLORHINT + +typedef BOOL (WINAPI *PtrDwmDefWindowProc)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult); +typedef HRESULT (WINAPI *PtrDwmIsCompositionEnabled)(BOOL* pfEnabled); +typedef HRESULT (WINAPI *PtrDwmExtendFrameIntoClientArea)(HWND hWnd, const WIZ_MARGINS* pMarInset); +typedef HRESULT (WINAPI *PtrSetWindowThemeAttribute)(HWND hwnd, enum WIZ_WINDOWTHEMEATTRIBUTETYPE eAttribute, PVOID pvAttribute, DWORD cbAttribute); + +static PtrDwmDefWindowProc pDwmDefWindowProc = 0; +static PtrDwmIsCompositionEnabled pDwmIsCompositionEnabled = 0; +static PtrDwmExtendFrameIntoClientArea pDwmExtendFrameIntoClientArea = 0; +static PtrSetWindowThemeAttribute pSetWindowThemeAttribute = 0; + +//Theme related +typedef bool (WINAPI *PtrIsAppThemed)(); +typedef bool (WINAPI *PtrIsThemeActive)(); +typedef HANDLE (WINAPI *PtrOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI *PtrCloseThemeData)(HANDLE hTheme); +typedef HRESULT (WINAPI *PtrGetThemeSysFont)(HANDLE hTheme, int iFontId, LOGFONTW *plf); +typedef HRESULT (WINAPI *PtrDrawThemeTextEx)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int cchText, DWORD dwTextFlags, LPRECT pRect, const WIZ_DTTOPTS *pOptions); +typedef HRESULT (WINAPI *PtrDrawThemeBackground)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect); +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HANDLE hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HRESULT (WINAPI *PtrGetThemeColor)(HANDLE hTheme, int iPartId, int iStateId, int iPropId, OUT COLORREF *pColor); + +static PtrIsAppThemed pIsAppThemed = 0; +static PtrIsThemeActive pIsThemeActive = 0; +static PtrOpenThemeData pOpenThemeData = 0; +static PtrCloseThemeData pCloseThemeData = 0; +static PtrGetThemeSysFont pGetThemeSysFont = 0; +static PtrDrawThemeTextEx pDrawThemeTextEx = 0; +static PtrDrawThemeBackground pDrawThemeBackground = 0; +static PtrGetThemePartSize pGetThemePartSize = 0; +static PtrGetThemeColor pGetThemeColor = 0; + +bool QVistaHelper::is_vista = false; +QVistaHelper::VistaState QVistaHelper::cachedVistaState = QVistaHelper::Dirty; + +/****************************************************************************** +** QVistaBackButton +*/ + +QVistaBackButton::QVistaBackButton(QWidget *widget) + : QAbstractButton(widget) +{ + setFocusPolicy(Qt::NoFocus); +} + +QSize QVistaBackButton::sizeHint() const +{ + ensurePolished(); + int size = int(QStyleHelper::dpiScaled(32)); + int width = size, height = size; +/* + HANDLE theme = pOpenThemeData(0, L"Navigation"); + SIZE size; + if (pGetThemePartSize(theme, 0, WIZ_NAV_BACKBUTTON, WIZ_NAV_BB_NORMAL, 0, TS_TRUE, &size) == S_OK) { + width = size.cx; + height = size.cy; + } +*/ + return QSize(width, height); +} + +void QVistaBackButton::enterEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::enterEvent(event); +} + +void QVistaBackButton::leaveEvent(QEvent *event) +{ + if (isEnabled()) + update(); + QAbstractButton::leaveEvent(event); +} + +void QVistaBackButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + QRect r = rect(); + HANDLE theme = pOpenThemeData(0, L"Navigation"); + //RECT rect; + RECT clipRect; + int xoffset = QWidget::mapToParent(r.topLeft()).x() - 1; + int yoffset = QWidget::mapToParent(r.topLeft()).y() - 1; + + clipRect.top = r.top() + yoffset; + clipRect.bottom = r.bottom() + yoffset; + clipRect.left = r.left() + xoffset; + clipRect.right = r.right() + xoffset; + + int state = WIZ_NAV_BB_NORMAL; + if (!isEnabled()) + state = WIZ_NAV_BB_DISABLED; + else if (isDown()) + state = WIZ_NAV_BB_PRESSED; + else if (underMouse()) + state = WIZ_NAV_BB_HOT; + + pDrawThemeBackground(theme, p.paintEngine()->getDC(), WIZ_NAV_BACKBUTTON, state, &clipRect, &clipRect); +} + +/****************************************************************************** +** QVistaHelper +*/ + +QVistaHelper::QVistaHelper(QWizard *wizard) + : QObject(wizard) + , pressed(false) + , wizard(wizard) + , backButton_(0) +{ + is_vista = resolveSymbols(); + if (is_vista) + backButton_ = new QVistaBackButton(wizard); + + // Handle diff between Windows 7 and Vista + iconSpacing = QStyleHelper::dpiScaled(7); + textSpacing = QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? + iconSpacing : QStyleHelper::dpiScaled(20); +} + +QVistaHelper::~QVistaHelper() +{ +} + +bool QVistaHelper::isCompositionEnabled() +{ + bool value = is_vista; + if (is_vista) { + HRESULT hr; + BOOL bEnabled; + + hr = pDwmIsCompositionEnabled(&bEnabled); + value = (SUCCEEDED(hr) && bEnabled); + } + return value; +} + +bool QVistaHelper::isThemeActive() +{ + return is_vista && pIsThemeActive(); +} + +QVistaHelper::VistaState QVistaHelper::vistaState() +{ + if (cachedVistaState == Dirty) + cachedVistaState = + isCompositionEnabled() ? VistaAero : isThemeActive() ? VistaBasic : Classic; + return cachedVistaState; +} + +QColor QVistaHelper::basicWindowFrameColor() +{ + DWORD rgb; + HANDLE hTheme = pOpenThemeData(QApplication::desktop()->winId(), L"WINDOW"); + pGetThemeColor( + hTheme, WIZ_WP_CAPTION, WIZ_CS_ACTIVE, + wizard->isActiveWindow() ? WIZ_TMT_FILLCOLORHINT : WIZ_TMT_BORDERCOLORHINT, + &rgb); + BYTE r = GetRValue(rgb); + BYTE g = GetGValue(rgb); + BYTE b = GetBValue(rgb); + return QColor(r, g, b); +} + +bool QVistaHelper::setDWMTitleBar(TitleBarChangeType type) +{ + bool value = false; + if (vistaState() == VistaAero) { + WIZ_MARGINS mar = {0}; + if (type == NormalTitleBar) + mar.cyTopHeight = 0; + else + mar.cyTopHeight = titleBarSize() + topOffset(); + HRESULT hr = pDwmExtendFrameIntoClientArea(wizard->winId(), &mar); + value = SUCCEEDED(hr); + } + return value; +} + +void QVistaHelper::drawTitleBar(QPainter *painter) +{ + HDC hdc = painter->paintEngine()->getDC(); + + if (vistaState() == VistaAero) + drawBlackRect(QRect(0, 0, wizard->width(), + titleBarSize() + topOffset()), hdc); + Q_ASSERT(backButton_); + const int btnTop = backButton_->mapToParent(QPoint()).y(); + const int btnHeight = backButton_->size().height(); + const int verticalCenter = (btnTop + btnHeight / 2) - 1; + + const QString text = wizard->window()->windowTitle(); + const QFont font = QApplication::font("QWorkspaceTitleBar"); + const QFontMetrics fontMetrics(font); + const QRect brect = fontMetrics.boundingRect(text); + int textHeight = brect.height(); + int textWidth = brect.width(); + int glowOffset = 0; + + if (vistaState() == VistaAero) { + textHeight += 2 * glowSize(); + textWidth += 2 * glowSize(); + glowOffset = glowSize(); + } + + drawTitleText( + painter, text, + QRect(titleOffset() - glowOffset, verticalCenter - textHeight / 2, textWidth, textHeight), + hdc); + + if (!wizard->windowIcon().isNull()) { + QRect rect(leftMargin(), verticalCenter - iconSize() / 2, iconSize(), iconSize()); + HICON hIcon = wizard->windowIcon().pixmap(iconSize()).toWinHICON(); + DrawIconEx(hdc, rect.left(), rect.top(), hIcon, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT); + DestroyIcon(hIcon); + } +} + +void QVistaHelper::setTitleBarIconAndCaptionVisible(bool visible) +{ + if (is_vista) { + WIZ_WTA_OPTIONS opt; + opt.dwFlags = WIZ_WTNCA_NODRAWICON | WIZ_WTNCA_NODRAWCAPTION; + if (visible) + opt.dwMask = 0; + else + opt.dwMask = WIZ_WTNCA_NODRAWICON | WIZ_WTNCA_NODRAWCAPTION; + pSetWindowThemeAttribute(wizard->winId(), WIZ_WTA_NONCLIENT, &opt, sizeof(WIZ_WTA_OPTIONS)); + } +} + +bool QVistaHelper::winEvent(MSG* msg, long* result) +{ + bool retval = true; + + switch (msg->message) { + case WM_NCHITTEST: { + LRESULT lResult; + pDwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lResult); + if (lResult == HTCLOSE || lResult == HTMAXBUTTON || lResult == HTMINBUTTON || lResult == HTHELP) + *result = lResult; + else + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + break; + } + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WIZ_WM_NCMOUSELEAVE: { + LRESULT lResult; + pDwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lResult); + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + break; + } + case WM_NCCALCSIZE: { + NCCALCSIZE_PARAMS* lpncsp = (NCCALCSIZE_PARAMS*)msg->lParam; + *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + lpncsp->rgrc[0].top -= (vistaState() == VistaAero ? titleBarSize() : 0); + break; + } + default: + retval = false; + } + + return retval; +} + +void QVistaHelper::setMouseCursor(QPoint pos) +{ +#ifndef QT_NO_CURSOR + if (rtTop.contains(pos)) + wizard->setCursor(Qt::SizeVerCursor); + else + wizard->setCursor(Qt::ArrowCursor); +#endif +} + +void QVistaHelper::mouseEvent(QEvent *event) +{ + switch (event->type()) { + case QEvent::MouseMove: + mouseMoveEvent(static_cast(event)); + break; + case QEvent::MouseButtonPress: + mousePressEvent(static_cast(event)); + break; + case QEvent::MouseButtonRelease: + mouseReleaseEvent(static_cast(event)); + break; + default: + break; + } +} + +// The following hack ensures that the titlebar is updated correctly +// when the wizard style changes to and from AeroStyle. Specifically, +// this function causes a Windows message of type WM_NCCALCSIZE to +// be triggered. +void QVistaHelper::setWindowPosHack() +{ + const int x = wizard->geometry().x(); // ignored by SWP_NOMOVE + const int y = wizard->geometry().y(); // ignored by SWP_NOMOVE + const int w = wizard->width(); + const int h = wizard->height(); + SetWindowPos(wizard->winId(), 0, x, y, w, h, SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); +} + +// The following hack allows any QWidget subclass to access +// QWidgetPrivate::topData() without being declared as a +// friend by QWidget. +class QHackWidget : public QWidget +{ +public: + Q_DECLARE_PRIVATE(QWidget) + QTLWExtra* topData() { return d_func()->topData(); } +}; + +void QVistaHelper::collapseTopFrameStrut() +{ + QTLWExtra *top = ((QHackWidget *)wizard)->d_func()->topData(); + int x1, y1, x2, y2; + top->frameStrut.getCoords(&x1, &y1, &x2, &y2); + top->frameStrut.setCoords(x1, 0, x2, y2); +} + +bool QVistaHelper::handleWinEvent(MSG *message, long *result) +{ + if (message->message == WIZ_WM_THEMECHANGED || message->message == WIZ_WM_DWMCOMPOSITIONCHANGED) + cachedVistaState = Dirty; + + bool status = false; + if (wizard->wizardStyle() == QWizard::AeroStyle && vistaState() == VistaAero) { + status = winEvent(message, result); + if (message->message == WM_NCCALCSIZE) { + if (status) + collapseTopFrameStrut(); + } else if (message->message == WM_NCPAINT) { + wizard->update(); + } + } + return status; +} + +void QVistaHelper::resizeEvent(QResizeEvent * event) +{ + Q_UNUSED(event); + rtTop = QRect (0, 0, wizard->width(), frameSize()); + int height = captionSize() + topOffset(); + if (vistaState() == VistaBasic) + height -= titleBarSize(); + rtTitle = QRect (0, frameSize(), wizard->width(), height); +} + +void QVistaHelper::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter painter(wizard); + drawTitleBar(&painter); +} + +void QVistaHelper::mouseMoveEvent(QMouseEvent *event) +{ + if (wizard->windowState() & Qt::WindowMaximized) { + event->ignore(); + return; + } + + QRect rect = wizard->geometry(); + if (pressed) { + switch (change) { + case resizeTop: + { + const int dy = event->pos().y() - pressedPos.y(); + if ((dy > 0 && rect.height() > wizard->minimumHeight()) + || (dy < 0 && rect.height() < wizard->maximumHeight())) + rect.setTop(rect.top() + dy); + } + break; + case movePosition: { + QPoint newPos = event->pos() - pressedPos; + rect.moveLeft(rect.left() + newPos.x()); + rect.moveTop(rect.top() + newPos.y()); + break; } + default: + break; + } + wizard->setGeometry(rect); + + } else if (vistaState() == VistaAero) { + setMouseCursor(event->pos()); + } + event->ignore(); +} + +void QVistaHelper::mousePressEvent(QMouseEvent *event) +{ + change = noChange; + + if (wizard->windowState() & Qt::WindowMaximized) { + event->ignore(); + return; + } + + if (rtTitle.contains(event->pos())) { + change = movePosition; + } else if (rtTop.contains(event->pos())) + change = (vistaState() == VistaAero) ? resizeTop : movePosition; + + if (change != noChange) { + if (vistaState() == VistaAero) + setMouseCursor(event->pos()); + pressed = true; + pressedPos = event->pos(); + } else { + event->ignore(); + } +} + +void QVistaHelper::mouseReleaseEvent(QMouseEvent *event) +{ + change = noChange; + if (pressed) { + pressed = false; + wizard->releaseMouse(); + if (vistaState() == VistaAero) + setMouseCursor(event->pos()); + } + event->ignore(); +} + +bool QVistaHelper::eventFilter(QObject *obj, QEvent *event) +{ + if (obj != wizard) + return QObject::eventFilter(obj, event); + + if (event->type() == QEvent::MouseMove) { + QMouseEvent *mouseEvent = static_cast(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + msg.hwnd = wizard->winId(); + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCMOUSEMOVE; + winEvent(&msg, &result); + } else if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + msg.hwnd = wizard->winId(); + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCLBUTTONDOWN; + winEvent(&msg, &result); + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + long result; + MSG msg; + msg.message = WM_NCHITTEST; + msg.wParam = 0; + msg.lParam = MAKELPARAM(mouseEvent->globalX(), mouseEvent->globalY()); + msg.hwnd = wizard->winId(); + winEvent(&msg, &result); + msg.wParam = result; + msg.message = WM_NCLBUTTONUP; + winEvent(&msg, &result); + } + + return false; +} + +HFONT QVistaHelper::getCaptionFont(HANDLE hTheme) +{ + LOGFONT lf = {0}; + + if (!hTheme) + pGetThemeSysFont(hTheme, WIZ_TMT_CAPTIONFONT, &lf); + else + { + NONCLIENTMETRICS ncm = {sizeof(NONCLIENTMETRICS)}; + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, false); + lf = ncm.lfMessageFont; + } + return CreateFontIndirect(&lf); +} + +bool QVistaHelper::drawTitleText(QPainter *painter, const QString &text, const QRect &rect, HDC hdc) +{ + bool value = false; + if (vistaState() == VistaAero) { + HANDLE hTheme = pOpenThemeData(QApplication::desktop()->winId(), L"WINDOW"); + if (!hTheme) return false; + // Set up a memory DC and bitmap that we'll draw into + HDC dcMem; + HBITMAP bmp; + BITMAPINFO dib = {{0}}; + dcMem = CreateCompatibleDC(hdc); + + dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib.bmiHeader.biWidth = rect.width(); + dib.bmiHeader.biHeight = -rect.height(); + dib.bmiHeader.biPlanes = 1; + dib.bmiHeader.biBitCount = 32; + dib.bmiHeader.biCompression = BI_RGB; + + bmp = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); + + // Set up the DC + HFONT hCaptionFont = getCaptionFont(hTheme); + HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); + HFONT hOldFont = (HFONT)SelectObject(dcMem, (HGDIOBJ) hCaptionFont); + + // Draw the text! + WIZ_DTTOPTS dto = { sizeof(WIZ_DTTOPTS) }; + const UINT uFormat = WIZ_DT_SINGLELINE|WIZ_DT_CENTER|WIZ_DT_VCENTER|WIZ_DT_NOPREFIX; + RECT rctext ={0,0, rect.width(), rect.height()}; + + dto.dwFlags = WIZ_DTT_COMPOSITED|WIZ_DTT_GLOWSIZE; + dto.iGlowSize = glowSize(); + + pDrawThemeTextEx(hTheme, dcMem, 0, 0, (LPCWSTR)text.utf16(), -1, uFormat, &rctext, &dto ); + BitBlt(hdc, rect.left(), rect.top(), rect.width(), rect.height(), dcMem, 0, 0, SRCCOPY); + SelectObject(dcMem, (HGDIOBJ) hOldBmp); + SelectObject(dcMem, (HGDIOBJ) hOldFont); + DeleteObject(bmp); + DeleteObject(hCaptionFont); + DeleteDC(dcMem); + //ReleaseDC(hwnd, hdc); + } else if (vistaState() == VistaBasic) { + painter->drawText(rect, text); + } + return value; +} + +bool QVistaHelper::drawBlackRect(const QRect &rect, HDC hdc) +{ + bool value = false; + if (vistaState() == VistaAero) { + // Set up a memory DC and bitmap that we'll draw into + HDC dcMem; + HBITMAP bmp; + BITMAPINFO dib = {{0}}; + dcMem = CreateCompatibleDC(hdc); + + dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dib.bmiHeader.biWidth = rect.width(); + dib.bmiHeader.biHeight = -rect.height(); + dib.bmiHeader.biPlanes = 1; + dib.bmiHeader.biBitCount = 32; + dib.bmiHeader.biCompression = BI_RGB; + + bmp = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0); + HBITMAP hOldBmp = (HBITMAP)SelectObject(dcMem, (HGDIOBJ) bmp); + + BitBlt(hdc, rect.left(), rect.top(), rect.width(), rect.height(), dcMem, 0, 0, SRCCOPY); + SelectObject(dcMem, (HGDIOBJ) hOldBmp); + + DeleteObject(bmp); + DeleteDC(dcMem); + } + return value; +} + +bool QVistaHelper::resolveSymbols() +{ + static bool tried = false; + if (!tried) { + tried = true; + QSystemLibrary dwmLib(L"dwmapi"); + pDwmIsCompositionEnabled = + (PtrDwmIsCompositionEnabled)dwmLib.resolve("DwmIsCompositionEnabled"); + if (pDwmIsCompositionEnabled) { + pDwmDefWindowProc = (PtrDwmDefWindowProc)dwmLib.resolve("DwmDefWindowProc"); + pDwmExtendFrameIntoClientArea = + (PtrDwmExtendFrameIntoClientArea)dwmLib.resolve("DwmExtendFrameIntoClientArea"); + } + QSystemLibrary themeLib(L"uxtheme"); + pIsAppThemed = (PtrIsAppThemed)themeLib.resolve("IsAppThemed"); + if (pIsAppThemed) { + pDrawThemeBackground = (PtrDrawThemeBackground)themeLib.resolve("DrawThemeBackground"); + pGetThemePartSize = (PtrGetThemePartSize)themeLib.resolve("GetThemePartSize"); + pGetThemeColor = (PtrGetThemeColor)themeLib.resolve("GetThemeColor"); + pIsThemeActive = (PtrIsThemeActive)themeLib.resolve("IsThemeActive"); + pOpenThemeData = (PtrOpenThemeData)themeLib.resolve("OpenThemeData"); + pCloseThemeData = (PtrCloseThemeData)themeLib.resolve("CloseThemeData"); + pGetThemeSysFont = (PtrGetThemeSysFont)themeLib.resolve("GetThemeSysFont"); + pDrawThemeTextEx = (PtrDrawThemeTextEx)themeLib.resolve("DrawThemeTextEx"); + pSetWindowThemeAttribute = (PtrSetWindowThemeAttribute)themeLib.resolve("SetWindowThemeAttribute"); + } + } + + return ( + pDwmIsCompositionEnabled != 0 + && pDwmDefWindowProc != 0 + && pDwmExtendFrameIntoClientArea != 0 + && pIsAppThemed != 0 + && pDrawThemeBackground != 0 + && pGetThemePartSize != 0 + && pGetThemeColor != 0 + && pIsThemeActive != 0 + && pOpenThemeData != 0 + && pCloseThemeData != 0 + && pGetThemeSysFont != 0 + && pDrawThemeTextEx != 0 + && pSetWindowThemeAttribute != 0 + ); +} + +int QVistaHelper::titleOffset() +{ + int iconOffset = wizard ->windowIcon().isNull() ? 0 : iconSize() + textSpacing; + return leftMargin() + iconOffset; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSVISTA + +#endif // QT_NO_WIZARD diff --git a/src/gui/dialogs/qwizard_win_p.h b/src/gui/dialogs/qwizard_win_p.h new file mode 100644 index 0000000000..b76f08264d --- /dev/null +++ b/src/gui/dialogs/qwizard_win_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIZARD_WIN_P_H +#define QWIZARD_WIN_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. +// + +#ifndef QT_NO_WIZARD +#ifndef QT_NO_STYLE_WINDOWSVISTA + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QVistaBackButton : public QAbstractButton +{ +public: + QVistaBackButton(QWidget *widget); + + QSize sizeHint() const; + inline QSize minimumSizeHint() const + { return sizeHint(); } + + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); +}; + +class QWizard; + +class QVistaHelper : public QObject +{ +public: + QVistaHelper(QWizard *wizard); + ~QVistaHelper(); + enum TitleBarChangeType { NormalTitleBar, ExtendedTitleBar }; + bool setDWMTitleBar(TitleBarChangeType type); + void setTitleBarIconAndCaptionVisible(bool visible); + void mouseEvent(QEvent *event); + bool handleWinEvent(MSG *message, long *result); + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); + QVistaBackButton *backButton() const { return backButton_; } + void disconnectBackButton() { if (backButton_) backButton_->disconnect(); } + void hideBackButton() { if (backButton_) backButton_->hide(); } + void setWindowPosHack(); + QColor basicWindowFrameColor(); + enum VistaState { VistaAero, VistaBasic, Classic, Dirty }; + static VistaState vistaState(); + static int titleBarSize() { return frameSize() + captionSize(); } + static int topPadding() { // padding under text + return int(QStyleHelper::dpiScaled( + QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? 4 : 6)); + } + static int topOffset() { + static int aeroOffset = QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS7 ? + QStyleHelper::dpiScaled(4) : QStyleHelper::dpiScaled(13); + return (titleBarSize() + (vistaState() == VistaAero ? aeroOffset : 3)); } +private: + static HFONT getCaptionFont(HANDLE hTheme); + bool drawTitleText(QPainter *painter, const QString &text, const QRect &rect, HDC hdc); + static bool drawBlackRect(const QRect &rect, HDC hdc); + + static int frameSize() { return GetSystemMetrics(SM_CYSIZEFRAME); } + static int captionSize() { return GetSystemMetrics(SM_CYCAPTION); } + + static int backButtonSize() { return int(QStyleHelper::dpiScaled(30)); } + static int iconSize() { return 16; } // Standard Aero + static int glowSize() { return 10; } + int leftMargin() { return backButton_->isVisible() ? backButtonSize() + iconSpacing : 0; } + + int titleOffset(); + bool resolveSymbols(); + void drawTitleBar(QPainter *painter); + void setMouseCursor(QPoint pos); + void collapseTopFrameStrut(); + bool winEvent(MSG *message, long *result); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + + static bool is_vista; + static VistaState cachedVistaState; + static bool isCompositionEnabled(); + static bool isThemeActive(); + enum Changes { resizeTop, movePosition, noChange } change; + QPoint pressedPos; + bool pressed; + QRect rtTop; + QRect rtTitle; + QWizard *wizard; + QVistaBackButton *backButton_; + + int titleBarOffset; // Extra spacing above the text + int iconSpacing; // Space between button and icon + int textSpacing; // Space between icon and text +}; + + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSVISTA +#endif // QT_NO_WIZARD +#endif // QWIZARD_WIN_P_H diff --git a/src/gui/effects/effects.pri b/src/gui/effects/effects.pri new file mode 100644 index 0000000000..0ebf96fccb --- /dev/null +++ b/src/gui/effects/effects.pri @@ -0,0 +1,4 @@ +HEADERS += effects/qgraphicseffect.h \ + effects/qgraphicseffect_p.h + +SOURCES += effects/qgraphicseffect.cpp diff --git a/src/gui/effects/qgraphicseffect.cpp b/src/gui/effects/qgraphicseffect.cpp new file mode 100644 index 0000000000..7ba38f6d3a --- /dev/null +++ b/src/gui/effects/qgraphicseffect.cpp @@ -0,0 +1,1235 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsEffect + \brief The QGraphicsEffect class is the base class for all graphics + effects. + \since 4.6 + \ingroup multimedia + \ingroup graphicsview-api + + Effects alter the appearance of elements by hooking into the rendering + pipeline and operating between the source (e.g., a QGraphicsPixmapItem) + and the destination device (e.g., QGraphicsView's viewport). Effects can be + disabled by calling setEnabled(false). If effects are disabled, the source + is rendered directly. + + To add a visual effect to a QGraphicsItem, for example, you can use one of + the standard effects, or alternately, create your own effect by creating a + subclass of QGraphicsEffect. The effect can then be installed on the item + using QGraphicsItem::setGraphicsEffect(). + + Qt provides the following standard effects: + + \list + \o QGraphicsBlurEffect - blurs the item by a given radius + \o QGraphicsDropShadowEffect - renders a dropshadow behind the item + \o QGraphicsColorizeEffect - renders the item in shades of any given color + \o QGraphicsOpacityEffect - renders the item with an opacity + \endlist + + \table + \row + \o{2,1} \img graphicseffect-plain.png + \row + \o \img graphicseffect-blur.png + \o \img graphicseffect-colorize.png + \row + \o \img graphicseffect-opacity.png + \o \img graphicseffect-drop-shadow.png + \endtable + + \img graphicseffect-widget.png + + For more information on how to use each effect, refer to the specific + effect's documentation. + + To create your own custom effect, create a subclass of QGraphicsEffect (or + any other existing effects) and reimplement the virtual function draw(). + This function is called whenever the effect needs to redraw. The draw() + function takes the painter with which to draw as an argument. For more + information, refer to the documenation for draw(). In the draw() function + you can call sourcePixmap() to get a pixmap of the graphics effect source + which you can then process. + + If your effect changes, use update() to request for a redraw. If your + custom effect changes the bounding rectangle of the source, e.g., a radial + glow effect may need to apply an extra margin, you can reimplement the + virtual boundingRectFor() function, and call updateBoundingRect() + to notify the framework whenever this rectangle changes. The virtual + sourceChanged() function is called to notify the effects that + the source has changed in some way - e.g., if the source is a + QGraphicsRectItem and its rectangle parameters have changed. + + \sa QGraphicsItem::setGraphicsEffect(), QWidget::setGraphicsEffect() +*/ + +#include "qgraphicseffect_p.h" +#include "private/qgraphicsitem_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_GRAPHICSEFFECT +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QGraphicsEffectSource + \brief The QGraphicsEffectSource class represents the source on which a + QGraphicsEffect is installed on. + + When a QGraphicsEffect is installed on a QGraphicsItem, for example, this + class will act as a wrapper around QGraphicsItem. Then, calling update() is + effectively the same as calling QGraphicsItem::update(). + + QGraphicsEffectSource also provides a pixmap() function which creates a + pixmap with the source painted into it. + + \sa QGraphicsItem::setGraphicsEffect(), QWidget::setGraphicsEffect(). +*/ + +/*! + \internal +*/ +QGraphicsEffectSource::QGraphicsEffectSource(QGraphicsEffectSourcePrivate &dd, QObject *parent) + : QObject(dd, parent) +{} + +/*! + Destroys the effect source. +*/ +QGraphicsEffectSource::~QGraphicsEffectSource() +{} + +/*! + Returns the bounding rectangle of the source mapped to the given \a system. + + \sa draw() +*/ +QRectF QGraphicsEffectSource::boundingRect(Qt::CoordinateSystem system) const +{ + return d_func()->boundingRect(system); +} + +/*! + Returns the bounding rectangle of the source mapped to the given \a system. + + Calling this function with Qt::DeviceCoordinates outside of + QGraphicsEffect::draw() will give undefined results, as there is no device + context available. + + \sa draw() +*/ +QRectF QGraphicsEffect::sourceBoundingRect(Qt::CoordinateSystem system) const +{ + Q_D(const QGraphicsEffect); + if (d->source) + return d->source->boundingRect(system); + return QRectF(); +} + +/*! + Returns a pointer to the item if this source is a QGraphicsItem; otherwise + returns 0. + + \sa widget() +*/ +const QGraphicsItem *QGraphicsEffectSource::graphicsItem() const +{ + return d_func()->graphicsItem(); +} + +/*! + Returns a pointer to the widget if this source is a QWidget; otherwise + returns 0. + + \sa graphicsItem() +*/ +const QWidget *QGraphicsEffectSource::widget() const +{ + return d_func()->widget(); +} + +/*! + Returns a pointer to the style options (used when drawing the source) if + available; otherwise returns 0. + + \sa graphicsItem(), widget() +*/ +const QStyleOption *QGraphicsEffectSource::styleOption() const +{ + return d_func()->styleOption(); +} + +/*! + Draws the source using the given \a painter. + + This function should only be called from QGraphicsEffect::draw(). + + \sa QGraphicsEffect::draw() +*/ +void QGraphicsEffectSource::draw(QPainter *painter) +{ + Q_D(const QGraphicsEffectSource); + + QPixmap pm; + if (QPixmapCache::find(d->m_cacheKey, &pm)) { + QTransform restoreTransform; + if (d->m_cachedSystem == Qt::DeviceCoordinates) { + restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + } + + painter->drawPixmap(d->m_cachedOffset, pm); + + if (d->m_cachedSystem == Qt::DeviceCoordinates) + painter->setWorldTransform(restoreTransform); + } else { + d_func()->draw(painter); + } +} + +/*! + Draws the source directly using the given \a painter. + + This function should only be called from QGraphicsEffect::draw(). + + For example: + + \snippet doc/src/snippets/code/src_gui_effects_qgraphicseffect.cpp 0 + + \sa QGraphicsEffect::draw() +*/ +void QGraphicsEffect::drawSource(QPainter *painter) +{ + Q_D(const QGraphicsEffect); + if (d->source) + d->source->draw(painter); +} + +/*! + Schedules a redraw of the source. Call this function whenever the source + needs to be redrawn. + + \sa QGraphicsEffect::updateBoundingRect(), QWidget::update(), + QGraphicsItem::update(), +*/ +void QGraphicsEffectSource::update() +{ + d_func()->update(); +} + +/*! + Returns true if the source effectively is a pixmap, e.g., a + QGraphicsPixmapItem. + + This function is useful for optimization purposes. For instance, there's no + point in drawing the source in device coordinates to avoid pixmap scaling + if this function returns true - the source pixmap will be scaled anyways. +*/ +bool QGraphicsEffectSource::isPixmap() const +{ + return d_func()->isPixmap(); +} + +/*! + Returns true if the source effectively is a pixmap, e.g., a + QGraphicsPixmapItem. + + This function is useful for optimization purposes. For instance, there's no + point in drawing the source in device coordinates to avoid pixmap scaling + if this function returns true - the source pixmap will be scaled anyways. +*/ +bool QGraphicsEffect::sourceIsPixmap() const +{ + return source() ? source()->isPixmap() : false; +} + +/*! + Returns a pixmap with the source painted into it. + + The \a system specifies which coordinate system to be used for the source. + The optional \a offset parameter returns the offset where the pixmap should + be painted at using the current painter. + + The \a mode determines how much of the effect the pixmap will contain. + By default, the pixmap will contain the whole effect. + + The returned pixmap is bound to the current painter's device rectangle when + \a system is Qt::DeviceCoordinates. + + \sa QGraphicsEffect::draw(), boundingRect() +*/ +QPixmap QGraphicsEffectSource::pixmap(Qt::CoordinateSystem system, QPoint *offset, QGraphicsEffect::PixmapPadMode mode) const +{ + Q_D(const QGraphicsEffectSource); + + // Shortcut, no cache for childless pixmap items... + const QGraphicsItem *item = graphicsItem(); + if (system == Qt::LogicalCoordinates && mode == QGraphicsEffect::NoPad && item && isPixmap()) { + const QGraphicsPixmapItem *pixmapItem = static_cast(item); + if (offset) + *offset = pixmapItem->offset().toPoint(); + return pixmapItem->pixmap(); + } + + if (system == Qt::DeviceCoordinates && item + && !static_cast(d_func())->info) { + qWarning("QGraphicsEffectSource::pixmap: Not yet implemented, lacking device context"); + return QPixmap(); + } + + QPixmap pm; + if (item && d->m_cachedSystem == system && d->m_cachedMode == mode) + QPixmapCache::find(d->m_cacheKey, &pm); + + if (pm.isNull()) { + pm = d->pixmap(system, &d->m_cachedOffset, mode); + d->m_cachedSystem = system; + d->m_cachedMode = mode; + + d->invalidateCache(); + d->m_cacheKey = QPixmapCache::insert(pm); + } + + if (offset) + *offset = d->m_cachedOffset; + + return pm; +} + +/*! + Returns a pixmap with the source painted into it. + + The \a system specifies which coordinate system to be used for the source. + The optional \a offset parameter returns the offset where the pixmap should + be painted at using the current painter. For control on how the pixmap is + padded use the \a mode parameter. + + The returned pixmap is clipped to the current painter's device rectangle when + \a system is Qt::DeviceCoordinates. + + Calling this function with Qt::DeviceCoordinates outside of + QGraphicsEffect::draw() will give undefined results, as there is no device + context available. + + \sa draw(), boundingRect() +*/ +QPixmap QGraphicsEffect::sourcePixmap(Qt::CoordinateSystem system, QPoint *offset, QGraphicsEffect::PixmapPadMode mode) const +{ + Q_D(const QGraphicsEffect); + if (d->source) + return d->source->pixmap(system, offset, mode); + return QPixmap(); +} + +QGraphicsEffectSourcePrivate::~QGraphicsEffectSourcePrivate() +{ + invalidateCache(); +} + +void QGraphicsEffectSourcePrivate::setCachedOffset(const QPoint &offset) +{ + m_cachedOffset = offset; +} + +void QGraphicsEffectSourcePrivate::invalidateCache(InvalidateReason reason) const +{ + if (m_cachedMode != QGraphicsEffect::PadToEffectiveBoundingRect + && (reason == EffectRectChanged + || (reason == TransformChanged && m_cachedSystem == Qt::LogicalCoordinates))) { + return; + } + + QPixmapCache::remove(m_cacheKey); +} + +/*! + Constructs a new QGraphicsEffect instance having the + specified \a parent. +*/ +QGraphicsEffect::QGraphicsEffect(QObject *parent) + : QObject(*new QGraphicsEffectPrivate, parent) +{ +} + +/*! + \internal +*/ +QGraphicsEffect::QGraphicsEffect(QGraphicsEffectPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} + +/*! + Removes the effect from the source, and destroys the graphics effect. +*/ +QGraphicsEffect::~QGraphicsEffect() +{ + Q_D(QGraphicsEffect); + d->setGraphicsEffectSource(0); +} + +/*! + Returns the effective bounding rectangle for this effect, i.e., the + bounding rectangle of the source in device coordinates, adjusted by + any margins applied by the effect itself. + + \sa boundingRectFor(), updateBoundingRect() +*/ +QRectF QGraphicsEffect::boundingRect() const +{ + Q_D(const QGraphicsEffect); + if (d->source) + return boundingRectFor(d->source->boundingRect()); + return QRectF(); +} + +/*! + Returns the effective bounding rectangle for this effect, given the + provided \a rect in the device coordinates. When writing + you own custom effect, you must call updateBoundingRect() whenever any + parameters are changed that may cause this this function to return a + different value. + + \sa sourceBoundingRect() +*/ +QRectF QGraphicsEffect::boundingRectFor(const QRectF &rect) const +{ + return rect; +} + +/*! + \property QGraphicsEffect::enabled + \brief whether the effect is enabled or not. + + If an effect is disabled, the source will be rendered with as normal, with + no interference from the effect. If the effect is enabled, the source will + be rendered with the effect applied. + + This property is enabled by default. + + Using this property, you can disable certain effects on slow platforms, in + order to ensure that the user interface is responsive. +*/ +bool QGraphicsEffect::isEnabled() const +{ + Q_D(const QGraphicsEffect); + return d->isEnabled; +} + +void QGraphicsEffect::setEnabled(bool enable) +{ + Q_D(QGraphicsEffect); + if (d->isEnabled == enable) + return; + + d->isEnabled = enable; + if (d->source) { + d->source->d_func()->effectBoundingRectChanged(); + d->source->d_func()->invalidateCache(); + } + emit enabledChanged(enable); +} + +/*! + \fn void QGraphicsEffect::enabledChanged(bool enabled) + + This signal is emitted whenever the effect is enabled or disabled. + The \a enabled parameter holds the effects's new enabled state. + + \sa isEnabled() +*/ + +/*! + Schedules a redraw of the effect. Call this function whenever the effect + needs to be redrawn. This function does not trigger a redraw of the source. + + \sa updateBoundingRect() +*/ +void QGraphicsEffect::update() +{ + Q_D(QGraphicsEffect); + if (d->source) + d->source->update(); +} + +/*! + \internal + + Returns a pointer to the source, which provides extra context information + that can be useful for the effect. + + \sa draw() +*/ +QGraphicsEffectSource *QGraphicsEffect::source() const +{ + Q_D(const QGraphicsEffect); + return d->source; +} + +/*! + This function notifies the effect framework when the effect's bounding + rectangle has changed. As a custom effect author, you must call this + function whenever you change any parameters that will cause the virtual + boundingRectFor() function to return a different value. + + This function will call update() if this is necessary. + + \sa boundingRectFor(), boundingRect(), sourceBoundingRect() +*/ +void QGraphicsEffect::updateBoundingRect() +{ + Q_D(QGraphicsEffect); + if (d->source) { + d->source->d_func()->effectBoundingRectChanged(); + d->source->d_func()->invalidateCache(QGraphicsEffectSourcePrivate::EffectRectChanged); + } +} + +/*! + \fn virtual void QGraphicsEffect::draw(QPainter *painter) = 0 + + This pure virtual function draws the effect and is called whenever the + source needs to be drawn. + + Reimplement this function in a QGraphicsEffect subclass to provide the + effect's drawing implementation, using \a painter. + + For example: + + \snippet doc/src/snippets/code/src_gui_effects_qgraphicseffect.cpp 1 + + This function should not be called explicitly by the user, since it is + meant for reimplementation purposes only. +*/ + +/*! + \enum QGraphicsEffect::ChangeFlag + + This enum describes what has changed in QGraphicsEffectSource. + + \value SourceAttached The effect is installed on a source. + \value SourceDetached The effect is uninstalled on a source. + \value SourceBoundingRectChanged The bounding rect of the source has + changed. + \value SourceInvalidated The visual appearance of the source has changed. +*/ + +/*! + \enum QGraphicsEffect::PixmapPadMode + + This enum describes how the pixmap returned from sourcePixmap should be + padded. + + \value NoPad The pixmap should not receive any additional + padding. + \value PadToTransparentBorder The pixmap should be padded + to ensure it has a completely transparent border. + \value PadToEffectiveBoundingRect The pixmap should be padded to + match the effective bounding rectangle of the effect. +*/ + +/*! + This virtual function is called by QGraphicsEffect to notify the effect + that the source has changed. If the effect applies any cache, then this + cache must be purged in order to reflect the new appearance of the source. + + The \a flags describes what has changed. +*/ +void QGraphicsEffect::sourceChanged(ChangeFlags flags) +{ + Q_UNUSED(flags); +} + +/*! + \class QGraphicsColorizeEffect + \brief The QGraphicsColorizeEffect class provides a colorize effect. + \since 4.6 + + A colorize effect renders the source with a tint of its color(). The color + can be modified using the setColor() function. + + By default, the color is light blue (QColor(0, 0, 192)). + + \img graphicseffect-colorize.png + + \sa QGraphicsDropShadowEffect, QGraphicsBlurEffect, QGraphicsOpacityEffect +*/ + +/*! + Constructs a new QGraphicsColorizeEffect instance. + The \a parent parameter is passed to QGraphicsEffect's constructor. +*/ +QGraphicsColorizeEffect::QGraphicsColorizeEffect(QObject *parent) + : QGraphicsEffect(*new QGraphicsColorizeEffectPrivate, parent) +{ +} + +/*! + Destroys the effect. +*/ +QGraphicsColorizeEffect::~QGraphicsColorizeEffect() +{ +} + +/*! + \property QGraphicsColorizeEffect::color + \brief the color of the effect. + + By default, the color is light blue (QColor(0, 0, 192)). +*/ +QColor QGraphicsColorizeEffect::color() const +{ + Q_D(const QGraphicsColorizeEffect); + return d->filter->color(); +} + +void QGraphicsColorizeEffect::setColor(const QColor &color) +{ + Q_D(QGraphicsColorizeEffect); + if (d->filter->color() == color) + return; + + d->filter->setColor(color); + update(); + emit colorChanged(color); +} + +/*! + \property QGraphicsColorizeEffect::strength + \brief the strength of the effect. + + By default, the strength is 1.0. + A strength 0.0 equals to no effect, while 1.0 means full colorization. +*/ +qreal QGraphicsColorizeEffect::strength() const +{ + Q_D(const QGraphicsColorizeEffect); + return d->filter->strength(); +} + +void QGraphicsColorizeEffect::setStrength(qreal strength) +{ + Q_D(QGraphicsColorizeEffect); + if (qFuzzyCompare(d->filter->strength(), strength)) + return; + + d->filter->setStrength(strength); + d->opaque = !qFuzzyIsNull(strength); + update(); + emit strengthChanged(strength); +} + +/*! \fn void QGraphicsColorizeEffect::strengthChanged(qreal strength) + This signal is emitted whenever setStrength() changes the colorize + strength property. \a strength contains the new strength value of + the colorize effect. + */ + +/*! + \fn void QGraphicsColorizeEffect::colorChanged(const QColor &color) + + This signal is emitted whenever the effect's color changes. + The \a color parameter holds the effect's new color. +*/ + +/*! + \reimp +*/ +void QGraphicsColorizeEffect::draw(QPainter *painter) +{ + Q_D(QGraphicsColorizeEffect); + + if (!d->opaque) { + drawSource(painter); + return; + } + + QPoint offset; + if (sourceIsPixmap()) { + // No point in drawing in device coordinates (pixmap will be scaled anyways). + const QPixmap pixmap = sourcePixmap(Qt::LogicalCoordinates, &offset, NoPad); + if (!pixmap.isNull()) + d->filter->draw(painter, offset, pixmap); + + return; + } + + // Draw pixmap in deviceCoordinates to avoid pixmap scaling. + const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset); + if (pixmap.isNull()) + return; + + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + d->filter->draw(painter, offset, pixmap); + painter->setWorldTransform(restoreTransform); +} + +/*! + \class QGraphicsBlurEffect + \brief The QGraphicsBlurEffect class provides a blur effect. + \since 4.6 + + A blur effect blurs the source. This effect is useful for reducing details, + such as when the source loses focus and you want to draw attention to other + elements. The level of detail can be modified using the setBlurRadius() + function. Use setBlurHints() to choose the blur hints. + + By default, the blur radius is 5 pixels. The blur radius is specified in + device coordinates. + + \img graphicseffect-blur.png + + \sa QGraphicsDropShadowEffect, QGraphicsColorizeEffect, QGraphicsOpacityEffect +*/ + +/*! + \enum QGraphicsBlurEffect::BlurHint + \since 4.6 + + This enum describes the possible hints that can be used to control how + blur effects are applied. The hints might not have an effect in all the + paint engines. + + \value PerformanceHint Indicates that rendering performance is the most important factor, + at the potential cost of lower quality. + + \value QualityHint Indicates that rendering quality is the most important factor, + at the potential cost of lower performance. + + \value AnimationHint Indicates that the blur radius is going to be animated, hinting + that the implementation can keep a cache of blurred verisons of the source. + Do not use this hint if the source is going to be dynamically changing. + + \sa blurHints(), setBlurHints() +*/ + + +/*! + Constructs a new QGraphicsBlurEffect instance. + The \a parent parameter is passed to QGraphicsEffect's constructor. +*/ +QGraphicsBlurEffect::QGraphicsBlurEffect(QObject *parent) + : QGraphicsEffect(*new QGraphicsBlurEffectPrivate, parent) +{ + Q_D(QGraphicsBlurEffect); + d->filter->setBlurHints(QGraphicsBlurEffect::PerformanceHint); +} + +/*! + Destroys the effect. +*/ +QGraphicsBlurEffect::~QGraphicsBlurEffect() +{ +} + +/*! + \property QGraphicsBlurEffect::blurRadius + \brief the blur radius of the effect. + + Using a smaller radius results in a sharper appearance, whereas a bigger + radius results in a more blurred appearance. + + By default, the blur radius is 5 pixels. + + The radius is given in device coordinates, meaning it is + unaffected by scale. +*/ +qreal QGraphicsBlurEffect::blurRadius() const +{ + Q_D(const QGraphicsBlurEffect); + return d->filter->radius(); +} + +void QGraphicsBlurEffect::setBlurRadius(qreal radius) +{ + Q_D(QGraphicsBlurEffect); + if (qFuzzyCompare(d->filter->radius(), radius)) + return; + + d->filter->setRadius(radius); + updateBoundingRect(); + emit blurRadiusChanged(radius); +} + +/*! + \fn void QGraphicsBlurEffect::blurRadiusChanged(qreal radius) + + This signal is emitted whenever the effect's blur radius changes. + The \a radius parameter holds the effect's new blur radius. +*/ + +/*! + \property QGraphicsBlurEffect::blurHints + \brief the blur hint of the effect. + + Use the PerformanceHint hint to say that you want a faster blur, + the QualityHint hint to say that you prefer a higher quality blur, + or the AnimationHint when you want to animate the blur radius. + + By default, the blur hint is PerformanceHint. +*/ +QGraphicsBlurEffect::BlurHints QGraphicsBlurEffect::blurHints() const +{ + Q_D(const QGraphicsBlurEffect); + return d->filter->blurHints(); +} + +void QGraphicsBlurEffect::setBlurHints(QGraphicsBlurEffect::BlurHints hints) +{ + Q_D(QGraphicsBlurEffect); + if (d->filter->blurHints() == hints) + return; + + d->filter->setBlurHints(hints); + emit blurHintsChanged(hints); +} + +/*! + \fn void QGraphicsBlurEffect::blurHintsChanged(QGraphicsBlurEffect::BlurHints hints) + + This signal is emitted whenever the effect's blur hints changes. + The \a hints parameter holds the effect's new blur hints. +*/ + +/*! + \reimp +*/ +QRectF QGraphicsBlurEffect::boundingRectFor(const QRectF &rect) const +{ + Q_D(const QGraphicsBlurEffect); + return d->filter->boundingRectFor(rect); +} + +/*! + \reimp +*/ +void QGraphicsBlurEffect::draw(QPainter *painter) +{ + Q_D(QGraphicsBlurEffect); + if (d->filter->radius() < 1) { + drawSource(painter); + return; + } + + PixmapPadMode mode = PadToEffectiveBoundingRect; + if (painter->paintEngine()->type() == QPaintEngine::OpenGL2) + mode = NoPad; + + QPoint offset; + QPixmap pixmap = sourcePixmap(Qt::LogicalCoordinates, &offset, mode); + if (pixmap.isNull()) + return; + + d->filter->draw(painter, offset, pixmap); +} + +/*! + \class QGraphicsDropShadowEffect + \brief The QGraphicsDropShadowEffect class provides a drop shadow effect. + \since 4.6 + + A drop shadow effect renders the source with a drop shadow. The color of + the drop shadow can be modified using the setColor() function. The drop + shadow offset can be modified using the setOffset() function and the blur + radius of the drop shadow can be changed with the setBlurRadius() + function. + + By default, the drop shadow is a semi-transparent dark gray + (QColor(63, 63, 63, 180)) shadow, blurred with a radius of 1 at an offset + of 8 pixels towards the lower right. The drop shadow offset is specified + in device coordinates. + + \img graphicseffect-drop-shadow.png + + \sa QGraphicsBlurEffect, QGraphicsColorizeEffect, QGraphicsOpacityEffect +*/ + +/*! + Constructs a new QGraphicsDropShadowEffect instance. + The \a parent parameter is passed to QGraphicsEffect's constructor. +*/ +QGraphicsDropShadowEffect::QGraphicsDropShadowEffect(QObject *parent) + : QGraphicsEffect(*new QGraphicsDropShadowEffectPrivate, parent) +{ +} + +/*! + Destroys the effect. +*/ +QGraphicsDropShadowEffect::~QGraphicsDropShadowEffect() +{ +} + +/*! + \property QGraphicsDropShadowEffect::offset + \brief the shadow offset in pixels. + + By default, the offset is 8 pixels towards the lower right. + + The offset is given in device coordinates, which means it is + unaffected by scale. + + \sa xOffset(), yOffset(), blurRadius(), color() +*/ +QPointF QGraphicsDropShadowEffect::offset() const +{ + Q_D(const QGraphicsDropShadowEffect); + return d->filter->offset(); +} + +void QGraphicsDropShadowEffect::setOffset(const QPointF &offset) +{ + Q_D(QGraphicsDropShadowEffect); + if (d->filter->offset() == offset) + return; + + d->filter->setOffset(offset); + updateBoundingRect(); + emit offsetChanged(offset); +} + +/*! + \property QGraphicsDropShadowEffect::xOffset + \brief the horizontal shadow offset in pixels. + + By default, the horizontal shadow offset is 8 pixels. + + + + \sa yOffset(), offset() +*/ + +/*! + \property QGraphicsDropShadowEffect::yOffset + \brief the vertical shadow offset in pixels. + + By default, the vertical shadow offset is 8 pixels. + + \sa xOffset(), offset() +*/ + +/*! + \fn void QGraphicsDropShadowEffect::offsetChanged(const QPointF &offset) + + This signal is emitted whenever the effect's shadow offset changes. + The \a offset parameter holds the effect's new shadow offset. +*/ + +/*! + \property QGraphicsDropShadowEffect::blurRadius + \brief the blur radius in pixels of the drop shadow. + + Using a smaller radius results in a sharper shadow, whereas using a bigger + radius results in a more blurred shadow. + + By default, the blur radius is 1 pixel. + + \sa color(), offset(). +*/ +qreal QGraphicsDropShadowEffect::blurRadius() const +{ + Q_D(const QGraphicsDropShadowEffect); + return d->filter->blurRadius(); +} + +void QGraphicsDropShadowEffect::setBlurRadius(qreal blurRadius) +{ + Q_D(QGraphicsDropShadowEffect); + if (qFuzzyCompare(d->filter->blurRadius(), blurRadius)) + return; + + d->filter->setBlurRadius(blurRadius); + updateBoundingRect(); + emit blurRadiusChanged(blurRadius); +} + +/*! + \fn void QGraphicsDropShadowEffect::blurRadiusChanged(qreal blurRadius) + + This signal is emitted whenever the effect's blur radius changes. + The \a blurRadius parameter holds the effect's new blur radius. +*/ + +/*! + \property QGraphicsDropShadowEffect::color + \brief the color of the drop shadow. + + By default, the drop color is a semi-transparent dark gray + (QColor(63, 63, 63, 180)). + + \sa offset(), blurRadius() +*/ +QColor QGraphicsDropShadowEffect::color() const +{ + Q_D(const QGraphicsDropShadowEffect); + return d->filter->color(); +} + +void QGraphicsDropShadowEffect::setColor(const QColor &color) +{ + Q_D(QGraphicsDropShadowEffect); + if (d->filter->color() == color) + return; + + d->filter->setColor(color); + update(); + emit colorChanged(color); +} + +/*! + \fn void QGraphicsDropShadowEffect::colorChanged(const QColor &color) + + This signal is emitted whenever the effect's color changes. + The \a color parameter holds the effect's new color. +*/ + +/*! + \reimp +*/ +QRectF QGraphicsDropShadowEffect::boundingRectFor(const QRectF &rect) const +{ + Q_D(const QGraphicsDropShadowEffect); + return d->filter->boundingRectFor(rect); +} + +/*! + \reimp +*/ +void QGraphicsDropShadowEffect::draw(QPainter *painter) +{ + Q_D(QGraphicsDropShadowEffect); + if (d->filter->blurRadius() <= 0 && d->filter->offset().isNull()) { + drawSource(painter); + return; + } + + PixmapPadMode mode = PadToEffectiveBoundingRect; + if (painter->paintEngine()->type() == QPaintEngine::OpenGL2) + mode = NoPad; + + // Draw pixmap in device coordinates to avoid pixmap scaling. + QPoint offset; + const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode); + if (pixmap.isNull()) + return; + + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + d->filter->draw(painter, offset, pixmap); + painter->setWorldTransform(restoreTransform); +} + +/*! + \class QGraphicsOpacityEffect + \brief The QGraphicsOpacityEffect class provides an opacity effect. + \since 4.6 + + An opacity effect renders the source with an opacity. This effect is useful + for making the source semi-transparent, similar to a fade-in/fade-out + sequence. The opacity can be modified using the setOpacity() function. + + By default, the opacity is 0.7. + + \img graphicseffect-opacity.png + + \sa QGraphicsDropShadowEffect, QGraphicsBlurEffect, QGraphicsColorizeEffect +*/ + +/*! + Constructs a new QGraphicsOpacityEffect instance. + The \a parent parameter is passed to QGraphicsEffect's constructor. +*/ +QGraphicsOpacityEffect::QGraphicsOpacityEffect(QObject *parent) + : QGraphicsEffect(*new QGraphicsOpacityEffectPrivate, parent) +{ +} + +/*! + Destroys the effect. +*/ +QGraphicsOpacityEffect::~QGraphicsOpacityEffect() +{ +} + +/*! + \property QGraphicsOpacityEffect::opacity + \brief the opacity of the effect. + + The value should be in the range of 0.0 to 1.0, where 0.0 is + fully transparent and 1.0 is fully opaque. + + By default, the opacity is 0.7. + + \sa setOpacityMask() +*/ +qreal QGraphicsOpacityEffect::opacity() const +{ + Q_D(const QGraphicsOpacityEffect); + return d->opacity; +} + +void QGraphicsOpacityEffect::setOpacity(qreal opacity) +{ + Q_D(QGraphicsOpacityEffect); + opacity = qBound(qreal(0.0), opacity, qreal(1.0)); + + if (qFuzzyCompare(d->opacity, opacity)) + return; + + d->opacity = opacity; + if ((d->isFullyTransparent = qFuzzyIsNull(d->opacity))) + d->isFullyOpaque = 0; + else + d->isFullyOpaque = qFuzzyIsNull(d->opacity - 1); + update(); + emit opacityChanged(opacity); +} + +/*! + \fn void QGraphicsOpacityEffect::opacityChanged(qreal opacity) + + This signal is emitted whenever the effect's opacity changes. + The \a opacity parameter holds the effect's new opacity. +*/ + +/*! + \property QGraphicsOpacityEffect::opacityMask + \brief the opacity mask of the effect. + + An opacity mask allows you apply opacity to portions of an element. + + For example: + + \snippet doc/src/snippets/code/src_gui_effects_qgraphicseffect.cpp 2 + + There is no opacity mask by default. + + \sa setOpacity() +*/ +QBrush QGraphicsOpacityEffect::opacityMask() const +{ + Q_D(const QGraphicsOpacityEffect); + return d->opacityMask; +} + +void QGraphicsOpacityEffect::setOpacityMask(const QBrush &mask) +{ + Q_D(QGraphicsOpacityEffect); + if (d->opacityMask == mask) + return; + + d->opacityMask = mask; + d->hasOpacityMask = (mask.style() != Qt::NoBrush); + update(); + + emit opacityMaskChanged(mask); +} + +/*! + \fn void QGraphicsOpacityEffect::opacityMaskChanged(const QBrush &mask) + + This signal is emitted whenever the effect's opacity mask changes. + The \a mask parameter holds the effect's new opacity mask. +*/ + +/*! + \reimp +*/ +void QGraphicsOpacityEffect::draw(QPainter *painter) +{ + Q_D(QGraphicsOpacityEffect); + + // Transparent; nothing to draw. + if (d->isFullyTransparent) + return; + + // Opaque; draw directly without going through a pixmap. + if (d->isFullyOpaque && !d->hasOpacityMask) { + drawSource(painter); + return; + } + + QPoint offset; + Qt::CoordinateSystem system = sourceIsPixmap() ? Qt::LogicalCoordinates : Qt::DeviceCoordinates; + QPixmap pixmap = sourcePixmap(system, &offset, QGraphicsEffect::NoPad); + if (pixmap.isNull()) + return; + + painter->save(); + painter->setOpacity(d->opacity); + + if (d->hasOpacityMask) { + QPainter pixmapPainter(&pixmap); + pixmapPainter.setRenderHints(painter->renderHints()); + pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn); + if (system == Qt::DeviceCoordinates) { + QTransform worldTransform = painter->worldTransform(); + worldTransform *= QTransform::fromTranslate(-offset.x(), -offset.y()); + pixmapPainter.setWorldTransform(worldTransform); + pixmapPainter.fillRect(sourceBoundingRect(), d->opacityMask); + } else { + pixmapPainter.translate(-offset); + pixmapPainter.fillRect(pixmap.rect(), d->opacityMask); + } + } + + if (system == Qt::DeviceCoordinates) + painter->setWorldTransform(QTransform()); + + painter->drawPixmap(offset, pixmap); + painter->restore(); +} + + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSEFFECT diff --git a/src/gui/effects/qgraphicseffect.h b/src/gui/effects/qgraphicseffect.h new file mode 100644 index 0000000000..9f5e94db89 --- /dev/null +++ b/src/gui/effects/qgraphicseffect.h @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSEFFECT_H +#define QGRAPHICSEFFECT_H + +#include +#include +#include +#include +#include + +#ifndef QT_NO_GRAPHICSEFFECT +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsItem; +class QStyleOption; +class QPainter; +class QPixmap; + +class QGraphicsEffectSource; + +class QGraphicsEffectPrivate; +class Q_GUI_EXPORT QGraphicsEffect : public QObject +{ + Q_OBJECT + Q_FLAGS(ChangeFlags) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) +public: + enum ChangeFlag { + SourceAttached = 0x1, + SourceDetached = 0x2, + SourceBoundingRectChanged = 0x4, + SourceInvalidated = 0x8 + }; + Q_DECLARE_FLAGS(ChangeFlags, ChangeFlag) + + enum PixmapPadMode { + NoPad, + PadToTransparentBorder, + PadToEffectiveBoundingRect + }; + + QGraphicsEffect(QObject *parent = 0); + virtual ~QGraphicsEffect(); + + virtual QRectF boundingRectFor(const QRectF &sourceRect) const; + QRectF boundingRect() const; + + bool isEnabled() const; + +public Q_SLOTS: + void setEnabled(bool enable); + void update(); + +Q_SIGNALS: + void enabledChanged(bool enabled); + +protected: + QGraphicsEffect(QGraphicsEffectPrivate &d, QObject *parent = 0); + virtual void draw(QPainter *painter) = 0; + virtual void sourceChanged(ChangeFlags flags); + void updateBoundingRect(); + + bool sourceIsPixmap() const; + QRectF sourceBoundingRect(Qt::CoordinateSystem system = Qt::LogicalCoordinates) const; + void drawSource(QPainter *painter); + QPixmap sourcePixmap(Qt::CoordinateSystem system = Qt::LogicalCoordinates, + QPoint *offset = 0, + PixmapPadMode mode = PadToEffectiveBoundingRect) const; + +private: + Q_DECLARE_PRIVATE(QGraphicsEffect) + Q_DISABLE_COPY(QGraphicsEffect) + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsScenePrivate; + friend class QWidget; + friend class QWidgetPrivate; + +public: + QGraphicsEffectSource *source() const; // internal + +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsEffect::ChangeFlags) + +class QGraphicsColorizeEffectPrivate; +class Q_GUI_EXPORT QGraphicsColorizeEffect: public QGraphicsEffect +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(qreal strength READ strength WRITE setStrength NOTIFY strengthChanged) +public: + QGraphicsColorizeEffect(QObject *parent = 0); + ~QGraphicsColorizeEffect(); + + QColor color() const; + qreal strength() const; + +public Q_SLOTS: + void setColor(const QColor &c); + void setStrength(qreal strength); + +Q_SIGNALS: + void colorChanged(const QColor &color); + void strengthChanged(qreal strength); + +protected: + void draw(QPainter *painter); + +private: + Q_DECLARE_PRIVATE(QGraphicsColorizeEffect) + Q_DISABLE_COPY(QGraphicsColorizeEffect) +}; + +class QGraphicsBlurEffectPrivate; +class Q_GUI_EXPORT QGraphicsBlurEffect: public QGraphicsEffect +{ + Q_OBJECT + Q_FLAGS(BlurHint BlurHints) + Q_PROPERTY(qreal blurRadius READ blurRadius WRITE setBlurRadius NOTIFY blurRadiusChanged) + Q_PROPERTY(BlurHints blurHints READ blurHints WRITE setBlurHints NOTIFY blurHintsChanged) +public: + enum BlurHint { + PerformanceHint = 0x00, + QualityHint = 0x01, + AnimationHint = 0x02 + }; + Q_DECLARE_FLAGS(BlurHints, BlurHint) + + QGraphicsBlurEffect(QObject *parent = 0); + ~QGraphicsBlurEffect(); + + QRectF boundingRectFor(const QRectF &rect) const; + qreal blurRadius() const; + BlurHints blurHints() const; + +public Q_SLOTS: + void setBlurRadius(qreal blurRadius); + void setBlurHints(BlurHints hints); + +Q_SIGNALS: + void blurRadiusChanged(qreal blurRadius); + void blurHintsChanged(BlurHints hints); + +protected: + void draw(QPainter *painter); + +private: + Q_DECLARE_PRIVATE(QGraphicsBlurEffect) + Q_DISABLE_COPY(QGraphicsBlurEffect) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsBlurEffect::BlurHints) + +class QGraphicsDropShadowEffectPrivate; +class Q_GUI_EXPORT QGraphicsDropShadowEffect: public QGraphicsEffect +{ + Q_OBJECT + Q_PROPERTY(QPointF offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(qreal xOffset READ xOffset WRITE setXOffset NOTIFY offsetChanged) + Q_PROPERTY(qreal yOffset READ yOffset WRITE setYOffset NOTIFY offsetChanged) + Q_PROPERTY(qreal blurRadius READ blurRadius WRITE setBlurRadius NOTIFY blurRadiusChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) +public: + QGraphicsDropShadowEffect(QObject *parent = 0); + ~QGraphicsDropShadowEffect(); + + QRectF boundingRectFor(const QRectF &rect) const; + QPointF offset() const; + + inline qreal xOffset() const + { return offset().x(); } + + inline qreal yOffset() const + { return offset().y(); } + + qreal blurRadius() const; + QColor color() const; + +public Q_SLOTS: + void setOffset(const QPointF &ofs); + + inline void setOffset(qreal dx, qreal dy) + { setOffset(QPointF(dx, dy)); } + + inline void setOffset(qreal d) + { setOffset(QPointF(d, d)); } + + inline void setXOffset(qreal dx) + { setOffset(QPointF(dx, yOffset())); } + + inline void setYOffset(qreal dy) + { setOffset(QPointF(xOffset(), dy)); } + + void setBlurRadius(qreal blurRadius); + void setColor(const QColor &color); + +Q_SIGNALS: + void offsetChanged(const QPointF &offset); + void blurRadiusChanged(qreal blurRadius); + void colorChanged(const QColor &color); + +protected: + void draw(QPainter *painter); + +private: + Q_DECLARE_PRIVATE(QGraphicsDropShadowEffect) + Q_DISABLE_COPY(QGraphicsDropShadowEffect) +}; + +class QGraphicsOpacityEffectPrivate; +class Q_GUI_EXPORT QGraphicsOpacityEffect: public QGraphicsEffect +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) + Q_PROPERTY(QBrush opacityMask READ opacityMask WRITE setOpacityMask NOTIFY opacityMaskChanged) +public: + QGraphicsOpacityEffect(QObject *parent = 0); + ~QGraphicsOpacityEffect(); + + qreal opacity() const; + QBrush opacityMask() const; + +public Q_SLOTS: + void setOpacity(qreal opacity); + void setOpacityMask(const QBrush &mask); + +Q_SIGNALS: + void opacityChanged(qreal opacity); + void opacityMaskChanged(const QBrush &mask); + +protected: + void draw(QPainter *painter); + +private: + Q_DECLARE_PRIVATE(QGraphicsOpacityEffect) + Q_DISABLE_COPY(QGraphicsOpacityEffect) +}; + +QT_END_NAMESPACE + +QT_END_HEADER +#endif //QT_NO_GRAPHICSEFFECT + +#endif // QGRAPHICSEFFECT_H + diff --git a/src/gui/effects/qgraphicseffect_p.h b/src/gui/effects/qgraphicseffect_p.h new file mode 100644 index 0000000000..cd1ae38ff8 --- /dev/null +++ b/src/gui/effects/qgraphicseffect_p.h @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSEFFECT_P_H +#define QGRAPHICSEFFECT_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 "qgraphicseffect.h" + +#include + +#include +#include + +#ifndef QT_NO_GRAPHICSEFFECT +QT_BEGIN_NAMESPACE + +class QGraphicsEffectSourcePrivate; +class Q_GUI_EXPORT QGraphicsEffectSource : public QObject +{ + Q_OBJECT +public: + ~QGraphicsEffectSource(); + const QGraphicsItem *graphicsItem() const; + const QWidget *widget() const; + const QStyleOption *styleOption() const; + + bool isPixmap() const; + void draw(QPainter *painter); + void update(); + + QRectF boundingRect(Qt::CoordinateSystem coordinateSystem = Qt::LogicalCoordinates) const; + QRect deviceRect() const; + QPixmap pixmap(Qt::CoordinateSystem system = Qt::LogicalCoordinates, + QPoint *offset = 0, + QGraphicsEffect::PixmapPadMode mode = QGraphicsEffect::PadToEffectiveBoundingRect) const; + +protected: + QGraphicsEffectSource(QGraphicsEffectSourcePrivate &dd, QObject *parent = 0); + +private: + Q_DECLARE_PRIVATE(QGraphicsEffectSource) + Q_DISABLE_COPY(QGraphicsEffectSource) + friend class QGraphicsEffect; + friend class QGraphicsEffectPrivate; + friend class QGraphicsScenePrivate; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QWidget; + friend class QWidgetPrivate; +}; + +class QGraphicsEffectSourcePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsEffectSource) +public: + QGraphicsEffectSourcePrivate() + : QObjectPrivate() + , m_cachedSystem(Qt::DeviceCoordinates) + , m_cachedMode(QGraphicsEffect::PadToTransparentBorder) + {} + + enum InvalidateReason + { + TransformChanged, + EffectRectChanged, + SourceChanged + }; + + virtual ~QGraphicsEffectSourcePrivate(); + virtual void detach() = 0; + virtual QRectF boundingRect(Qt::CoordinateSystem system) const = 0; + virtual QRect deviceRect() const = 0; + virtual const QGraphicsItem *graphicsItem() const = 0; + virtual const QWidget *widget() const = 0; + virtual const QStyleOption *styleOption() const = 0; + virtual void draw(QPainter *p) = 0; + virtual void update() = 0; + virtual bool isPixmap() const = 0; + virtual QPixmap pixmap(Qt::CoordinateSystem system, QPoint *offset = 0, + QGraphicsEffect::PixmapPadMode mode = QGraphicsEffect::PadToTransparentBorder) const = 0; + virtual void effectBoundingRectChanged() = 0; + + void setCachedOffset(const QPoint &offset); + void invalidateCache(InvalidateReason reason = SourceChanged) const; + Qt::CoordinateSystem currentCachedSystem() const { return m_cachedSystem; } + QGraphicsEffect::PixmapPadMode currentCachedMode() const { return m_cachedMode; } + + friend class QGraphicsScenePrivate; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + +private: + mutable Qt::CoordinateSystem m_cachedSystem; + mutable QGraphicsEffect::PixmapPadMode m_cachedMode; + mutable QPoint m_cachedOffset; + mutable QPixmapCache::Key m_cacheKey; +}; + +class Q_GUI_EXPORT QGraphicsEffectPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsEffect) +public: + QGraphicsEffectPrivate() : source(0), isEnabled(1) {} + + inline void setGraphicsEffectSource(QGraphicsEffectSource *newSource) + { + QGraphicsEffect::ChangeFlags flags; + if (source) { + flags |= QGraphicsEffect::SourceDetached; + source->d_func()->invalidateCache(); + source->d_func()->detach(); + delete source; + } + source = newSource; + if (newSource) + flags |= QGraphicsEffect::SourceAttached; + q_func()->sourceChanged(flags); + } + + QGraphicsEffectSource *source; + QRectF boundingRect; + quint32 isEnabled : 1; + quint32 padding : 31; // feel free to use +}; + + +class QGraphicsColorizeEffectPrivate : public QGraphicsEffectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsColorizeEffect) +public: + QGraphicsColorizeEffectPrivate() + : opaque(true) + { + filter = new QPixmapColorizeFilter; + } + ~QGraphicsColorizeEffectPrivate() { delete filter; } + + QPixmapColorizeFilter *filter; + quint32 opaque : 1; + quint32 padding : 31; +}; + +class QGraphicsBlurEffectPrivate : public QGraphicsEffectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsBlurEffect) +public: + QGraphicsBlurEffectPrivate() : filter(new QPixmapBlurFilter) {} + ~QGraphicsBlurEffectPrivate() { delete filter; } + + QPixmapBlurFilter *filter; +}; + +class QGraphicsDropShadowEffectPrivate : public QGraphicsEffectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsDropShadowEffect) +public: + QGraphicsDropShadowEffectPrivate() : filter(new QPixmapDropShadowFilter) {} + ~QGraphicsDropShadowEffectPrivate() { delete filter; } + + QPixmapDropShadowFilter *filter; +}; + +class QGraphicsOpacityEffectPrivate : public QGraphicsEffectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsOpacityEffect) +public: + QGraphicsOpacityEffectPrivate() + : opacity(qreal(0.7)), isFullyTransparent(0), isFullyOpaque(0), hasOpacityMask(0) {} + ~QGraphicsOpacityEffectPrivate() {} + + qreal opacity; + QBrush opacityMask; + uint isFullyTransparent : 1; + uint isFullyOpaque : 1; + uint hasOpacityMask : 1; +}; + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSEFFECT +#endif // QGRAPHICSEFFECT_P_H + diff --git a/src/gui/egl/egl.pri b/src/gui/egl/egl.pri new file mode 100644 index 0000000000..8e8664c679 --- /dev/null +++ b/src/gui/egl/egl.pri @@ -0,0 +1,46 @@ +contains(QT_CONFIG, egl): { + CONFIG += egl + + HEADERS += \ + egl/qegl_p.h \ + egl/qeglcontext_p.h \ + egl/qeglproperties_p.h + + SOURCES += \ + egl/qegl.cpp \ + egl/qeglproperties.cpp + unix { + !isEmpty(QMAKE_INCDIR_EGL){ + INCLUDEPATH += $$QMAKE_INCDIR_EGL + } + !isEmpty(QMAKE_LIBDIR_EGL){ + for(p, QMAKE_LIBDIR_EGL) { + exists($$p):LIBS += -L$$p + } + } + + !isEmpty(QMAKE_LIBS_EGL): LIBS += $$QMAKE_LIBS_EGL + } + + wince*: SOURCES += egl/qegl_wince.cpp + + unix { + embedded { + SOURCES += egl/qegl_qws.cpp + } else { + qpa { + SOURCES += egl/qegl_qpa.cpp + } else { + symbian { + SOURCES += egl/qegl_symbian.cpp + } else { + SOURCES += egl/qegl_x11.cpp + } + } + } + } +} else:symbian { + DEFINES += QT_NO_EGL + SOURCES += egl/qegl_stub.cpp + SOURCES += egl/qeglproperties_stub.cpp +} diff --git a/src/gui/egl/qegl.cpp b/src/gui/egl/qegl.cpp new file mode 100644 index 0000000000..fdb6a1e029 --- /dev/null +++ b/src/gui/egl/qegl.cpp @@ -0,0 +1,753 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "qegl_p.h" +#include "qeglcontext_p.h" + + +QT_BEGIN_NAMESPACE + + +/* + QEglContextTracker is used to track the EGL contexts that we + create internally in Qt, so that we can call eglTerminate() to + free additional EGL resources when the last context is destroyed. +*/ + +class QEglContextTracker +{ +public: + static void ref() { contexts.ref(); } + static void deref() { + if (!contexts.deref()) { + eglTerminate(QEgl::display()); + displayOpen = 0; + } + } + static void setDisplayOpened() { displayOpen = 1; } + static bool displayOpened() { return displayOpen; } + +private: + static QBasicAtomicInt contexts; + static QBasicAtomicInt displayOpen; +}; + +QBasicAtomicInt QEglContextTracker::contexts = Q_BASIC_ATOMIC_INITIALIZER(0); +QBasicAtomicInt QEglContextTracker::displayOpen = Q_BASIC_ATOMIC_INITIALIZER(0); + +// Current GL and VG contexts. These are used to determine if +// we can avoid an eglMakeCurrent() after a call to lazyDoneCurrent(). +// If a background thread modifies the value, the worst that will +// happen is a redundant eglMakeCurrent() in the foreground thread. +static QEglContext * volatile currentGLContext = 0; +static QEglContext * volatile currentVGContext = 0; + +QEglContext::QEglContext() + : apiType(QEgl::OpenGL) + , ctx(EGL_NO_CONTEXT) + , cfg(QEGL_NO_CONFIG) + , currentSurface(EGL_NO_SURFACE) + , current(false) + , ownsContext(true) + , sharing(false) +{ + QEglContextTracker::ref(); +} + +QEglContext::~QEglContext() +{ + destroyContext(); + + if (currentGLContext == this) + currentGLContext = 0; + if (currentVGContext == this) + currentVGContext = 0; + QEglContextTracker::deref(); +} + +bool QEglContext::isValid() const +{ + return (ctx != EGL_NO_CONTEXT); +} + +bool QEglContext::isCurrent() const +{ + return current; +} + +EGLConfig QEgl::defaultConfig(int devType, API api, ConfigOptions options) +{ + if ( (devType != QInternal::Pixmap) && ((options & Renderable) == 0)) + qWarning("QEgl::defaultConfig() - Only configs for pixmaps make sense to be read-only!"); + + EGLConfig* targetConfig = 0; + + static EGLConfig defaultVGConfigs[] = { + QEGL_NO_CONFIG, // 0 Window Renderable Translucent + QEGL_NO_CONFIG, // 1 Window Renderable Opaque + QEGL_NO_CONFIG, // 2 Pixmap Renderable Translucent + QEGL_NO_CONFIG, // 3 Pixmap Renderable Opaque + QEGL_NO_CONFIG, // 4 Pixmap ReadOnly Translucent + QEGL_NO_CONFIG // 5 Pixmap ReadOnly Opaque + }; + if (api == OpenVG) { + if (devType == QInternal::Widget) { + if (options & Translucent) + targetConfig = &(defaultVGConfigs[0]); + else + targetConfig = &(defaultVGConfigs[1]); + } else if (devType == QInternal::Pixmap) { + if (options & Renderable) { + if (options & Translucent) + targetConfig = &(defaultVGConfigs[2]); + else // Opaque + targetConfig = &(defaultVGConfigs[3]); + } else { // Read-only + if (options & Translucent) + targetConfig = &(defaultVGConfigs[4]); + else // Opaque + targetConfig = &(defaultVGConfigs[5]); + } + } + } + + + static EGLConfig defaultGLConfigs[] = { + QEGL_NO_CONFIG, // 0 Window Renderable Translucent + QEGL_NO_CONFIG, // 1 Window Renderable Opaque + QEGL_NO_CONFIG, // 2 PBuffer Renderable Translucent + QEGL_NO_CONFIG, // 3 PBuffer Renderable Opaque + QEGL_NO_CONFIG, // 4 Pixmap Renderable Translucent + QEGL_NO_CONFIG, // 5 Pixmap Renderable Opaque + QEGL_NO_CONFIG, // 6 Pixmap ReadOnly Translucent + QEGL_NO_CONFIG // 7 Pixmap ReadOnly Opaque + }; + if (api == OpenGL) { + if (devType == QInternal::Widget) { + if (options & Translucent) + targetConfig = &(defaultGLConfigs[0]); + else // Opaque + targetConfig = &(defaultGLConfigs[1]); + } else if (devType == QInternal::Pbuffer) { + if (options & Translucent) + targetConfig = &(defaultGLConfigs[2]); + else // Opaque + targetConfig = &(defaultGLConfigs[3]); + } else if (devType == QInternal::Pixmap) { + if (options & Renderable) { + if (options & Translucent) + targetConfig = &(defaultGLConfigs[4]); + else // Opaque + targetConfig = &(defaultGLConfigs[5]); + } else { // ReadOnly + if (options & Translucent) + targetConfig = &(defaultGLConfigs[6]); + else // Opaque + targetConfig = &(defaultGLConfigs[7]); + } + } + } + + if (!targetConfig) { + qWarning("QEgl::defaultConfig() - No default config for device/api/options combo"); + return QEGL_NO_CONFIG; + } + if (*targetConfig != QEGL_NO_CONFIG) + return *targetConfig; + + + // We haven't found an EGL config for the target config yet, so do it now: + + + // Allow overriding from an environment variable: + QByteArray configId; + if (api == OpenVG) + configId = qgetenv("QT_VG_EGL_CONFIG"); + else + configId = qgetenv("QT_GL_EGL_CONFIG"); + if (!configId.isEmpty()) { + // Overridden, so get the EGLConfig for the specified config ID: + EGLint properties[] = { + EGL_CONFIG_ID, (EGLint)configId.toInt(), + EGL_NONE + }; + EGLint configCount = 0; + eglChooseConfig(display(), properties, targetConfig, 1, &configCount); + if (configCount > 0) + return *targetConfig; + qWarning() << "QEgl::defaultConfig() -" << configId << "appears to be invalid"; + } + + QEglProperties configAttribs; + configAttribs.setRenderableType(api); + + EGLint surfaceType; + switch (devType) { + case QInternal::Widget: + surfaceType = EGL_WINDOW_BIT; + break; + case QInternal::Pixmap: + surfaceType = EGL_PIXMAP_BIT; + break; + case QInternal::Pbuffer: + surfaceType = EGL_PBUFFER_BIT; + break; + default: + qWarning("QEgl::defaultConfig() - Can't create EGL surface for %d device type", devType); + return QEGL_NO_CONFIG; + }; +#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT + // For OpenVG, we try to create a surface using a pre-multiplied format if + // the surface needs to have an alpha channel: + if (api == OpenVG && (options & Translucent)) + surfaceType |= EGL_VG_ALPHA_FORMAT_PRE_BIT; +#endif + configAttribs.setValue(EGL_SURFACE_TYPE, surfaceType); + +#ifdef EGL_BIND_TO_TEXTURE_RGBA + if (devType == QInternal::Pixmap || devType == QInternal::Pbuffer) { + if (options & Translucent) + configAttribs.setValue(EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE); + else + configAttribs.setValue(EGL_BIND_TO_TEXTURE_RGB, EGL_TRUE); + } +#endif + + // Add paint engine requirements + if (api == OpenVG) { +#if !defined(QVG_SCISSOR_CLIP) && defined(EGL_ALPHA_MASK_SIZE) + configAttribs.setValue(EGL_ALPHA_MASK_SIZE, 1); +#endif + } else { + // Both OpenGL paint engines need to have stencil and sample buffers + configAttribs.setValue(EGL_STENCIL_SIZE, 1); + configAttribs.setValue(EGL_SAMPLE_BUFFERS, 1); +#ifndef QT_OPENGL_ES_2 + // Additionally, the GL1 engine likes to have a depth buffer for clipping + configAttribs.setValue(EGL_DEPTH_SIZE, 1); +#endif + } + + if (options & Translucent) + configAttribs.setValue(EGL_ALPHA_SIZE, 1); + + *targetConfig = chooseConfig(&configAttribs, QEgl::BestPixelFormat); + return *targetConfig; +} + + +// Choose a configuration that matches "properties". +EGLConfig QEgl::chooseConfig(const QEglProperties* properties, QEgl::PixelFormatMatch match) +{ + QEglProperties props(*properties); + EGLConfig cfg = QEGL_NO_CONFIG; + do { + // Get the number of matching configurations for this set of properties. + EGLint matching = 0; + EGLDisplay dpy = QEgl::display(); + if (!eglChooseConfig(dpy, props.properties(), 0, 0, &matching) || !matching) + continue; + + // If we want the best pixel format, then return the first + // matching configuration. + if (match == QEgl::BestPixelFormat) { + eglChooseConfig(display(), props.properties(), &cfg, 1, &matching); + if (matching < 1) + continue; + return cfg; + } + + // Fetch all of the matching configurations and find the + // first that matches the pixel format we wanted. + EGLint size = matching; + EGLConfig *configs = new EGLConfig [size]; + eglChooseConfig(display(), props.properties(), configs, size, &matching); + for (EGLint index = 0; index < size; ++index) { + EGLint red, green, blue, alpha; + eglGetConfigAttrib(display(), configs[index], EGL_RED_SIZE, &red); + eglGetConfigAttrib(display(), configs[index], EGL_GREEN_SIZE, &green); + eglGetConfigAttrib(display(), configs[index], EGL_BLUE_SIZE, &blue); + eglGetConfigAttrib(display(), configs[index], EGL_ALPHA_SIZE, &alpha); + if (red == props.value(EGL_RED_SIZE) && + green == props.value(EGL_GREEN_SIZE) && + blue == props.value(EGL_BLUE_SIZE) && + (props.value(EGL_ALPHA_SIZE) == 0 || + alpha == props.value(EGL_ALPHA_SIZE))) { + cfg = configs[index]; + delete [] configs; + return cfg; + } + } + delete [] configs; + } while (props.reduceConfiguration()); + +#ifdef EGL_BIND_TO_TEXTURE_RGBA + // Don't report an error just yet if we failed to get a pbuffer + // configuration with texture rendering. Only report failure if + // we cannot get any pbuffer configurations at all. + if (props.value(EGL_BIND_TO_TEXTURE_RGBA) == EGL_DONT_CARE && + props.value(EGL_BIND_TO_TEXTURE_RGB) == EGL_DONT_CARE) +#endif + { + qWarning() << "QEglContext::chooseConfig(): Could not find a suitable EGL configuration"; + qWarning() << "Requested:" << props.toString(); + qWarning() << "Available:"; + QEgl::dumpAllConfigs(); + } + return QEGL_NO_CONFIG; +} + +bool QEglContext::chooseConfig(const QEglProperties& properties, QEgl::PixelFormatMatch match) +{ + cfg = QEgl::chooseConfig(&properties, match); + return cfg != QEGL_NO_CONFIG; +} + +EGLSurface QEglContext::createSurface(QPaintDevice* device, const QEglProperties *properties) +{ + return QEgl::createSurface(device, cfg, properties); +} + + +// Create the EGLContext. +bool QEglContext::createContext(QEglContext *shareContext, const QEglProperties *properties) +{ + // We need to select the correct API before calling eglCreateContext(). +#ifdef QT_OPENGL_ES +#ifdef EGL_OPENGL_ES_API + if (apiType == QEgl::OpenGL) + eglBindAPI(EGL_OPENGL_ES_API); +#endif +#else +#ifdef EGL_OPENGL_API + if (apiType == QEgl::OpenGL) + eglBindAPI(EGL_OPENGL_API); +#endif +#endif //defined(QT_OPENGL_ES) +#ifdef EGL_OPENVG_API + if (apiType == QEgl::OpenVG) + eglBindAPI(EGL_OPENVG_API); +#endif + + // Create a new context for the configuration. + QEglProperties contextProps; + if (properties) + contextProps = *properties; +#ifdef QT_OPENGL_ES_2 + if (apiType == QEgl::OpenGL) + contextProps.setValue(EGL_CONTEXT_CLIENT_VERSION, 2); +#endif + sharing = false; + if (shareContext && shareContext->ctx == EGL_NO_CONTEXT) + shareContext = 0; + if (shareContext) { + ctx = eglCreateContext(QEgl::display(), cfg, shareContext->ctx, contextProps.properties()); + if (ctx == EGL_NO_CONTEXT) { + qWarning() << "QEglContext::createContext(): Could not share context:" << QEgl::errorString(); + shareContext = 0; + } else { + sharing = true; + } + } + if (ctx == EGL_NO_CONTEXT) { + ctx = eglCreateContext(display(), cfg, EGL_NO_CONTEXT, contextProps.properties()); + if (ctx == EGL_NO_CONTEXT) { + qWarning() << "QEglContext::createContext(): Unable to create EGL context:" << QEgl::errorString(); + return false; + } + } + return true; +} + +// Destroy an EGL surface object. If it was current on this context +// then call doneCurrent() for it first. +void QEglContext::destroySurface(EGLSurface surface) +{ + if (surface != EGL_NO_SURFACE) { + if (surface == currentSurface) + doneCurrent(); + eglDestroySurface(display(), surface); + } +} + +// Destroy the context. Note: this does not destroy the surface. +void QEglContext::destroyContext() +{ + if (ctx != EGL_NO_CONTEXT && ownsContext) + eglDestroyContext(display(), ctx); + ctx = EGL_NO_CONTEXT; + cfg = 0; +} + +bool QEglContext::makeCurrent(EGLSurface surface) +{ + if (ctx == EGL_NO_CONTEXT) { + qWarning() << "QEglContext::makeCurrent(): Cannot make invalid context current"; + return false; + } + + if (surface == EGL_NO_SURFACE) { + qWarning() << "QEglContext::makeCurrent(): Cannot make invalid surface current"; + return false; + } + + // If lazyDoneCurrent() was called on the surface, then we may be able + // to assume that it is still current within the thread. + if (surface == currentSurface && currentContext(apiType) == this) { + current = true; + return true; + } + + current = true; + currentSurface = surface; + setCurrentContext(apiType, this); + + // Force the right API to be bound before making the context current. + // The EGL implementation should be able to figure this out from ctx, + // but some systems require the API to be explicitly set anyway. +#ifdef EGL_OPENGL_ES_API + if (apiType == QEgl::OpenGL) + eglBindAPI(EGL_OPENGL_ES_API); +#endif +#ifdef EGL_OPENVG_API + if (apiType == QEgl::OpenVG) + eglBindAPI(EGL_OPENVG_API); +#endif + + bool ok = eglMakeCurrent(QEgl::display(), surface, surface, ctx); + if (!ok) + qWarning() << "QEglContext::makeCurrent(" << surface << "):" << QEgl::errorString(); + return ok; +} + +bool QEglContext::doneCurrent() +{ + // If the context is invalid, we assume that an error was reported + // when makeCurrent() was called. + if (ctx == EGL_NO_CONTEXT) + return false; + + current = false; + currentSurface = EGL_NO_SURFACE; + setCurrentContext(apiType, 0); + + // We need to select the correct API before calling eglMakeCurrent() + // with EGL_NO_CONTEXT because threads can have both OpenGL and OpenVG + // contexts active at the same time. +#ifdef EGL_OPENGL_ES_API + if (apiType == QEgl::OpenGL) + eglBindAPI(EGL_OPENGL_ES_API); +#endif +#ifdef EGL_OPENVG_API + if (apiType == QEgl::OpenVG) + eglBindAPI(EGL_OPENVG_API); +#endif + + bool ok = eglMakeCurrent(QEgl::display(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (!ok) + qWarning() << "QEglContext::doneCurrent():" << QEgl::errorString(); + return ok; +} + +// Act as though doneCurrent() was called, but keep the context +// and the surface active for the moment. This allows makeCurrent() +// to skip a call to eglMakeCurrent() if we are using the same +// surface as the last set of painting operations. We leave the +// currentContext() pointer as-is for now. +bool QEglContext::lazyDoneCurrent() +{ + current = false; + return true; +} + +bool QEglContext::swapBuffers(EGLSurface surface) +{ + if(ctx == EGL_NO_CONTEXT) + return false; + + bool ok = eglSwapBuffers(QEgl::display(), surface); + if (!ok) + qWarning() << "QEglContext::swapBuffers():" << QEgl::errorString(); + return ok; +} + +bool QEglContext::swapBuffersRegion2NOK(EGLSurface surface, const QRegion *region) { + QVector qrects = region->rects(); + EGLint *gl_rects; + uint count; + uint i; + + count = qrects.size(); + QVarLengthArray arr(4 * count); + gl_rects = arr.data(); + for (i = 0; i < count; i++) { + QRect qrect = qrects[i]; + + gl_rects[4 * i + 0] = qrect.x(); + gl_rects[4 * i + 1] = qrect.y(); + gl_rects[4 * i + 2] = qrect.width(); + gl_rects[4 * i + 3] = qrect.height(); + } + + bool ok = QEgl::eglSwapBuffersRegion2NOK(QEgl::display(), surface, count, gl_rects); + + if (!ok) + qWarning() << "QEglContext::swapBuffersRegion2NOK():" << QEgl::errorString(); + return ok; +} + +int QEglContext::configAttrib(int name) const +{ + EGLint value; + EGLBoolean success = eglGetConfigAttrib(QEgl::display(), cfg, name, &value); + if (success) + return value; + else + return EGL_DONT_CARE; +} + +typedef EGLImageKHR (EGLAPIENTRY *_eglCreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint*); +typedef EGLBoolean (EGLAPIENTRY *_eglDestroyImageKHR)(EGLDisplay, EGLImageKHR); + +// Defined in qegl.cpp: +static _eglCreateImageKHR qt_eglCreateImageKHR = 0; +static _eglDestroyImageKHR qt_eglDestroyImageKHR = 0; + +typedef EGLBoolean (EGLAPIENTRY *_eglSwapBuffersRegion2NOK)(EGLDisplay, EGLSurface, EGLint, const EGLint*); + +static _eglSwapBuffersRegion2NOK qt_eglSwapBuffersRegion2NOK = 0; + +EGLDisplay QEgl::display() +{ + static EGLDisplay dpy = EGL_NO_DISPLAY; + if (!QEglContextTracker::displayOpened()) { + dpy = eglGetDisplay(nativeDisplay()); + QEglContextTracker::setDisplayOpened(); + if (dpy == EGL_NO_DISPLAY) { + qWarning("QEgl::display(): Falling back to EGL_DEFAULT_DISPLAY"); + dpy = eglGetDisplay(EGLNativeDisplayType(EGL_DEFAULT_DISPLAY)); + } + if (dpy == EGL_NO_DISPLAY) { + qWarning("QEgl::display(): Can't even open the default display"); + return EGL_NO_DISPLAY; + } + + if (!eglInitialize(dpy, NULL, NULL)) { + qWarning() << "QEgl::display(): Cannot initialize EGL display:" << QEgl::errorString(); + return EGL_NO_DISPLAY; + } + + // Resolve the egl extension function pointers: +#if (defined(EGL_KHR_image) || defined(EGL_KHR_image_base)) && !defined(EGL_EGLEXT_PROTOTYPES) + if (QEgl::hasExtension("EGL_KHR_image") || QEgl::hasExtension("EGL_KHR_image_base")) { + qt_eglCreateImageKHR = (_eglCreateImageKHR) eglGetProcAddress("eglCreateImageKHR"); + qt_eglDestroyImageKHR = (_eglDestroyImageKHR) eglGetProcAddress("eglDestroyImageKHR"); + } +#endif + + if (QEgl::hasExtension("EGL_NOK_swap_region2")) { + qt_eglSwapBuffersRegion2NOK = (_eglSwapBuffersRegion2NOK) eglGetProcAddress("eglSwapBuffersRegion2NOK"); + } + } + + return dpy; +} + +EGLImageKHR QEgl::eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) +{ + if (qt_eglCreateImageKHR) + return qt_eglCreateImageKHR(dpy, ctx, target, buffer, attrib_list); + + QEgl::display(); // Initialises function pointers + if (qt_eglCreateImageKHR) + return qt_eglCreateImageKHR(dpy, ctx, target, buffer, attrib_list); + + qWarning("QEgl::eglCreateImageKHR() called but EGL_KHR_image(_base) extension not present"); + return 0; +} + +EGLBoolean QEgl::eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img) +{ + if (qt_eglDestroyImageKHR) + return qt_eglDestroyImageKHR(dpy, img); + + QEgl::display(); // Initialises function pointers + if (qt_eglDestroyImageKHR) + return qt_eglDestroyImageKHR(dpy, img); + + qWarning("QEgl::eglDestroyImageKHR() called but EGL_KHR_image(_base) extension not present"); + return 0; +} + +EGLBoolean QEgl::eglSwapBuffersRegion2NOK(EGLDisplay dpy, EGLSurface surface, EGLint count, const EGLint *rects) +{ + if (qt_eglSwapBuffersRegion2NOK) + return qt_eglSwapBuffersRegion2NOK(dpy, surface, count, rects); + + QEgl::display(); // Initialises function pointers + if (qt_eglSwapBuffersRegion2NOK) + return qt_eglSwapBuffersRegion2NOK(dpy, surface, count, rects); + + qWarning("QEgl::eglSwapBuffersRegion2NOK() called but EGL_NOK_swap_region2 extension not present"); + return 0; +} + +#ifndef Q_WS_X11 +EGLSurface QEgl::createSurface(QPaintDevice *device, EGLConfig cfg, const QEglProperties *properties) +{ + // Create the native drawable for the paint device. + int devType = device->devType(); + EGLNativePixmapType pixmapDrawable = 0; + EGLNativeWindowType windowDrawable = 0; + bool ok; + if (devType == QInternal::Pixmap) { + pixmapDrawable = nativePixmap(static_cast(device)); + ok = (pixmapDrawable != 0); + } else if (devType == QInternal::Widget) { + windowDrawable = nativeWindow(static_cast(device)); + ok = (windowDrawable != 0); + } else { + ok = false; + } + if (!ok) { + qWarning("QEglContext::createSurface(): Cannot create the native EGL drawable"); + return EGL_NO_SURFACE; + } + + // Create the EGL surface to draw into, based on the native drawable. + const int *props; + if (properties) + props = properties->properties(); + else + props = 0; + EGLSurface surf; + if (devType == QInternal::Widget) + surf = eglCreateWindowSurface(QEgl::display(), cfg, windowDrawable, props); + else + surf = eglCreatePixmapSurface(QEgl::display(), cfg, pixmapDrawable, props); + if (surf == EGL_NO_SURFACE) { + qWarning("QEglContext::createSurface(): Unable to create EGL surface, error = 0x%x", eglGetError()); + } + return surf; +} +#endif + + +// Return the error string associated with a specific code. +QString QEgl::errorString(EGLint code) +{ + static const char * const errors[] = { + "Success (0x3000)", // No tr + "Not initialized (0x3001)", // No tr + "Bad access (0x3002)", // No tr + "Bad alloc (0x3003)", // No tr + "Bad attribute (0x3004)", // No tr + "Bad config (0x3005)", // No tr + "Bad context (0x3006)", // No tr + "Bad current surface (0x3007)", // No tr + "Bad display (0x3008)", // No tr + "Bad match (0x3009)", // No tr + "Bad native pixmap (0x300A)", // No tr + "Bad native window (0x300B)", // No tr + "Bad parameter (0x300C)", // No tr + "Bad surface (0x300D)", // No tr + "Context lost (0x300E)" // No tr + }; + if (code >= 0x3000 && code <= 0x300E) { + return QString::fromLatin1(errors[code - 0x3000]); + } else { + return QLatin1String("0x") + QString::number(int(code), 16); + } +} + +// Dump all of the EGL configurations supported by the system. +void QEgl::dumpAllConfigs() +{ + QEglProperties props; + EGLint count = 0; + if (!eglGetConfigs(display(), 0, 0, &count) || count < 1) + return; + EGLConfig *configs = new EGLConfig [count]; + eglGetConfigs(display(), configs, count, &count); + for (EGLint index = 0; index < count; ++index) { + props = QEglProperties(configs[index]); + qWarning() << props.toString(); + } + delete [] configs; +} + +QString QEgl::extensions() +{ + const char* exts = eglQueryString(QEgl::display(), EGL_EXTENSIONS); + return QString(QLatin1String(exts)); +} + +bool QEgl::hasExtension(const char* extensionName) +{ + QList extensions = + QByteArray(reinterpret_cast + (eglQueryString(QEgl::display(), EGL_EXTENSIONS))).split(' '); + return extensions.contains(extensionName); +} + +QEglContext *QEglContext::currentContext(QEgl::API api) +{ + if (api == QEgl::OpenGL) + return currentGLContext; + else + return currentVGContext; +} + +void QEglContext::setCurrentContext(QEgl::API api, QEglContext *context) +{ + if (api == QEgl::OpenGL) + currentGLContext = context; + else + currentVGContext = context; +} + +QT_END_NAMESPACE diff --git a/src/gui/egl/qegl_p.h b/src/gui/egl/qegl_p.h new file mode 100644 index 0000000000..37f4c2ae46 --- /dev/null +++ b/src/gui/egl/qegl_p.h @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEGL_P_H +#define QEGL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience of +// the QtOpenGL and QtOpenVG modules. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_INCLUDE_NAMESPACE + +#ifndef QT_NO_EGL +#if defined(QT_OPENGL_ES_2) +# include +#endif + +#if defined(QT_GLES_EGL) +# include +#else +# include +#endif +#if !defined(EGL_VERSION_1_2) +typedef unsigned int EGLenum; +typedef void *EGLClientBuffer; +#endif +#else + +//types from egltypes.h for compiling stub without EGL headers +typedef int EGLBoolean; +typedef int EGLint; +typedef int EGLenum; +typedef int NativeDisplayType; +typedef void* NativeWindowType; +typedef void* NativePixmapType; +typedef int EGLDisplay; +typedef int EGLConfig; +typedef int EGLSurface; +typedef int EGLContext; +typedef int EGLClientBuffer; +#define EGL_NONE 0x3038 /* Attrib list terminator */ + +#endif + +#if defined(Q_WS_X11) +// If included , then the global namespace +// may have been polluted with X #define's. The following makes sure +// the X11 headers were included properly and then cleans things up. +#include +#include +#undef Bool +#undef Status +#undef None +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +#undef Type +#undef FontChange +#undef CursorShape +#undef Unsorted +#undef GrayScale +#endif + +// Internally we use the EGL-prefixed native types which are used in EGL >= 1.3. +// For older versions of EGL, we have to define these types ourselves here: +#if !defined(EGL_VERSION_1_3) && !defined(QEGL_NATIVE_TYPES_DEFINED) +#undef EGLNativeWindowType +#undef EGLNativePixmapType +#undef EGLNativeDisplayType +typedef NativeWindowType EGLNativeWindowType; +typedef NativePixmapType EGLNativePixmapType; +typedef NativeDisplayType EGLNativeDisplayType; +#define QEGL_NATIVE_TYPES_DEFINED 1 +#endif + +QT_END_INCLUDE_NAMESPACE + +#include +#include + +QT_BEGIN_NAMESPACE + +#define QEGL_NO_CONFIG ((EGLConfig)-1) + +#ifndef EGLAPIENTRY +#define EGLAPIENTRY +#endif + +// Try to get some info to debug the symbian build failues: +#ifdef Q_OS_SYMBIAN + +#ifdef EGL_KHR_image +#warning "EGL_KHR_image is defined" +#else +#warning "EGL_KHR_image is NOT defined" +#endif + +#ifdef EGL_KHR_image_base +#warning "EGL_KHR_image_base is defined" +#else +#warning "EGL_KHR_image_base is NOT defined" +#endif + +#ifdef EGL_EGLEXT_PROTOTYPES +#warning "EGL_EGLEXT_PROTOTYPES is defined" +#else +#warning "EGL_EGLEXT_PROTOTYPES NOT not defined" +#endif + +#endif + + +// Declare/define the bits of EGL_KHR_image_base we need: +#if !defined(EGL_KHR_image) && !defined(EGL_KHR_image_base) +#ifdef Q_OS_SYMBIAN +//symbian version of eglext.h differs from the khronos reference +typedef int EGLImageKHR; +#else +typedef void *EGLImageKHR; +#endif + +#define EGL_NO_IMAGE_KHR ((EGLImageKHR)0) +#define EGL_IMAGE_PRESERVED_KHR 0x30D2 +#define EGL_KHR_image_base +#endif + +#if !defined(EGL_KHR_image) && !defined(EGL_KHR_image_pixmap) +#define EGL_NATIVE_PIXMAP_KHR 0x30B0 +#define EGL_KHR_image_pixmap +#endif + + +class QEglProperties; + +namespace QEgl { + enum API + { + OpenGL, + OpenVG + }; + + enum PixelFormatMatch + { + ExactPixelFormat, + BestPixelFormat + }; + + enum ConfigOption + { + NoOptions = 0, + Translucent = 0x01, + Renderable = 0x02 // Config will be compatable with the paint engines (VG or GL) + }; + Q_DECLARE_FLAGS(ConfigOptions, ConfigOption) + + // Most of the time we use the same config for things like widgets & pixmaps, so rather than + // go through the eglChooseConfig loop every time, we use defaultConfig, which will return + // the config for a particular device/api/option combo. This function assumes that once a + // config is chosen for a particular combo, it's safe to always use that combo. + Q_GUI_EXPORT EGLConfig defaultConfig(int devType, API api, ConfigOptions options); + + Q_GUI_EXPORT EGLConfig chooseConfig(const QEglProperties* configAttribs, QEgl::PixelFormatMatch match = QEgl::ExactPixelFormat); + Q_GUI_EXPORT EGLSurface createSurface(QPaintDevice *device, EGLConfig cfg, const QEglProperties *surfaceAttribs = 0); + + Q_GUI_EXPORT void dumpAllConfigs(); + +#ifdef QT_NO_EGL + Q_GUI_EXPORT QString errorString(EGLint code = 0); +#else + Q_GUI_EXPORT QString errorString(EGLint code = eglGetError()); +#endif + + Q_GUI_EXPORT QString extensions(); + Q_GUI_EXPORT bool hasExtension(const char* extensionName); + + Q_GUI_EXPORT EGLDisplay display(); + + Q_GUI_EXPORT EGLNativeDisplayType nativeDisplay(); + Q_GUI_EXPORT EGLNativeWindowType nativeWindow(QWidget*); + Q_GUI_EXPORT EGLNativePixmapType nativePixmap(QPixmap*); + + // Extension functions + Q_GUI_EXPORT EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list); + Q_GUI_EXPORT EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img); + Q_GUI_EXPORT EGLBoolean eglSwapBuffersRegion2NOK(EGLDisplay dpy, EGLSurface surface, EGLint count, const EGLint *rects); + +#ifdef Q_WS_X11 + Q_GUI_EXPORT VisualID getCompatibleVisualId(EGLConfig config); +#endif +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QEgl::ConfigOptions) + +QT_END_NAMESPACE + +#endif //QEGL_P_H diff --git a/src/gui/egl/qegl_qpa.cpp b/src/gui/egl/qegl_qpa.cpp new file mode 100644 index 0000000000..10caf46d56 --- /dev/null +++ b/src/gui/egl/qegl_qpa.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include "qeglcontext_p.h" + +#if !defined(QT_NO_EGL) + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + return EGLNativeDisplayType(EGL_DEFAULT_DISPLAY); +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + return (EGLNativeWindowType)(widget->winId()); +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap* pixmap) +{ + Q_UNUSED(pixmap); + return 0; +} + +//EGLDisplay QEglContext::display() +//{ +// return eglGetDisplay(EGLNativeDisplayType(EGL_DEFAULT_DISPLAY)); +//} + +static QPlatformScreen *screenForDevice(QPaintDevice *device) +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + + QList screens = pi->screens(); + + int screenNumber; + if (device && device->devType() == QInternal::Widget) + screenNumber = qApp->desktop()->screenNumber(static_cast(device)); + else + screenNumber = 0; + if (screenNumber < 0 || screenNumber >= screens.size()) + return 0; + return screens[screenNumber]; +} + +// Set pixel format and other properties based on a paint device. +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + if (!dev) + return; + + // Find the QGLScreen for this paint device. + QPlatformScreen *screen = screenForDevice(dev); + if (!screen) + return; + int devType = dev->devType(); + if (devType == QInternal::Image) + setPixelFormat(static_cast(dev)->format()); + else + setPixelFormat(screen->format()); +} + +QT_END_NAMESPACE + +#endif // !QT_NO_EGL diff --git a/src/gui/egl/qegl_qws.cpp b/src/gui/egl/qegl_qws.cpp new file mode 100644 index 0000000000..181a9f2c63 --- /dev/null +++ b/src/gui/egl/qegl_qws.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "qegl_p.h" +#include "qeglcontext_p.h" + +#if !defined(QT_NO_EGL) + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static QScreen *screenForDevice(QPaintDevice *device) +{ + QScreen *screen = qt_screen; + if (!screen) + return 0; + if (screen->classId() == QScreen::MultiClass) { + int screenNumber; + if (device && device->devType() == QInternal::Widget) + screenNumber = qApp->desktop()->screenNumber(static_cast(device)); + else + screenNumber = 0; + screen = screen->subScreens()[screenNumber]; + } + while (screen->classId() == QScreen::ProxyClass || + screen->classId() == QScreen::TransformedClass) { + screen = static_cast(screen)->screen(); + } + return screen; +} + +// Set pixel format and other properties based on a paint device. +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + if (!dev) + return; + + // Find the QGLScreen for this paint device. + QScreen *screen = screenForDevice(dev); + if (!screen) + return; + int devType = dev->devType(); + if (devType == QInternal::Image) + setPixelFormat(static_cast(dev)->format()); + else + setPixelFormat(screen->pixelFormat()); +} + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + return EGLNativeDisplayType(EGL_DEFAULT_DISPLAY); +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + return (EGLNativeWindowType)(widget->winId()); // Might work +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap*) +{ + qWarning("QEgl: EGL pixmap surfaces not supported on QWS"); + return (EGLNativePixmapType)0; +} + + +QT_END_NAMESPACE + +#endif // !QT_NO_EGL diff --git a/src/gui/egl/qegl_stub.cpp b/src/gui/egl/qegl_stub.cpp new file mode 100644 index 0000000000..6629844015 --- /dev/null +++ b/src/gui/egl/qegl_stub.cpp @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qegl_p.h" +#include "qeglcontext_p.h" + + +QT_BEGIN_NAMESPACE + +static void noegl(const char *fn) +{ + qWarning() << fn << " called, but Qt configured without EGL" << endl; +} + +#define NOEGL noegl(__FUNCTION__); + +QEglContext::QEglContext() + : apiType(QEgl::OpenGL) + , ctx(0) + , cfg(QEGL_NO_CONFIG) + , currentSurface(0) + , current(false) + , ownsContext(true) + , sharing(false) +{ + NOEGL +} + +QEglContext::~QEglContext() +{ + NOEGL +} + +bool QEglContext::isValid() const +{ + NOEGL + return false; +} + +bool QEglContext::isCurrent() const +{ + NOEGL + return false; +} + +EGLConfig QEgl::defaultConfig(int devType, API api, ConfigOptions options) +{ + Q_UNUSED(devType) + Q_UNUSED(api) + Q_UNUSED(options) + NOEGL + return QEGL_NO_CONFIG; +} + + +// Choose a configuration that matches "properties". +EGLConfig QEgl::chooseConfig(const QEglProperties* properties, QEgl::PixelFormatMatch match) +{ + Q_UNUSED(properties) + Q_UNUSED(match) + NOEGL + return QEGL_NO_CONFIG; +} + +bool QEglContext::chooseConfig(const QEglProperties& properties, QEgl::PixelFormatMatch match) +{ + Q_UNUSED(properties) + Q_UNUSED(match) + NOEGL + return false; +} + +EGLSurface QEglContext::createSurface(QPaintDevice* device, const QEglProperties *properties) +{ + Q_UNUSED(device) + Q_UNUSED(properties) + NOEGL + return 0; +} + + +// Create the EGLContext. +bool QEglContext::createContext(QEglContext *shareContext, const QEglProperties *properties) +{ + Q_UNUSED(shareContext) + Q_UNUSED(properties) + NOEGL + return false; +} + +// Destroy an EGL surface object. If it was current on this context +// then call doneCurrent() for it first. +void QEglContext::destroySurface(EGLSurface surface) +{ + Q_UNUSED(surface) + NOEGL +} + +// Destroy the context. Note: this does not destroy the surface. +void QEglContext::destroyContext() +{ + NOEGL +} + +bool QEglContext::makeCurrent(EGLSurface surface) +{ + Q_UNUSED(surface) + NOEGL + return false; +} + +bool QEglContext::doneCurrent() +{ + NOEGL + return false; +} + +// Act as though doneCurrent() was called, but keep the context +// and the surface active for the moment. This allows makeCurrent() +// to skip a call to eglMakeCurrent() if we are using the same +// surface as the last set of painting operations. We leave the +// currentContext() pointer as-is for now. +bool QEglContext::lazyDoneCurrent() +{ + NOEGL + return false; +} + +bool QEglContext::swapBuffers(EGLSurface surface) +{ + Q_UNUSED(surface) + NOEGL + return false; +} + +bool QEglContext::swapBuffersRegion2NOK(EGLSurface surface, const QRegion *region) +{ + Q_UNUSED(surface) + Q_UNUSED(region) + NOEGL + return false; +} + +int QEglContext::configAttrib(int name) const +{ + Q_UNUSED(name) + NOEGL + return 0; +} + +EGLDisplay QEgl::display() +{ + NOEGL + return 0; +} + +EGLImageKHR QEgl::eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) +{ + Q_UNUSED(dpy) + Q_UNUSED(ctx) + Q_UNUSED(target) + Q_UNUSED(buffer) + Q_UNUSED(attrib_list) + NOEGL + return 0; +} + +EGLBoolean QEgl::eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR img) +{ + Q_UNUSED(dpy) + Q_UNUSED(img) + NOEGL + return 0; +} + +EGLBoolean QEgl::eglSwapBuffersRegion2NOK(EGLDisplay dpy, EGLSurface surface, EGLint count, const EGLint *rects) +{ + Q_UNUSED(dpy); + Q_UNUSED(surface); + Q_UNUSED(count); + Q_UNUSED(rects); + NOEGL + return 0; +} + +#ifndef Q_WS_X11 +EGLSurface QEgl::createSurface(QPaintDevice *device, EGLConfig cfg, const QEglProperties *properties) +{ + Q_UNUSED(device) + Q_UNUSED(cfg) + Q_UNUSED(properties) + NOEGL + return 0; +} +#endif + + +// Return the error string associated with a specific code. +QString QEgl::errorString(EGLint code) +{ + Q_UNUSED(code) + NOEGL + return QString(); +} + +// Dump all of the EGL configurations supported by the system. +void QEgl::dumpAllConfigs() +{ + NOEGL +} + +QString QEgl::extensions() +{ + NOEGL + return QString(); +} + +bool QEgl::hasExtension(const char* extensionName) +{ + Q_UNUSED(extensionName) + NOEGL + return false; +} + +QEglContext *QEglContext::currentContext(QEgl::API api) +{ + Q_UNUSED(api) + NOEGL + return false; +} + +void QEglContext::setCurrentContext(QEgl::API api, QEglContext *context) +{ + Q_UNUSED(api) + Q_UNUSED(context) + NOEGL +} + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + NOEGL + return 0; +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + Q_UNUSED(widget) + NOEGL + return (EGLNativeWindowType)0; +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap*) +{ + NOEGL + return (EGLNativePixmapType)0; +} + +QT_END_NAMESPACE diff --git a/src/gui/egl/qegl_symbian.cpp b/src/gui/egl/qegl_symbian.cpp new file mode 100644 index 0000000000..fabf9d16ee --- /dev/null +++ b/src/gui/egl/qegl_symbian.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qegl_p.h" +#include "qeglcontext_p.h" + +#include + +QT_BEGIN_NAMESPACE + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + return (EGLNativeWindowType)(widget->winId()->DrawableWindow()); +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap*) +{ + qWarning("QEgl: EGL pixmap surfaces not implemented yet on Symbian"); + return (EGLNativePixmapType)0; +} + +// Set pixel format and other properties based on a paint device. +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + if(!dev) + return; + + int devType = dev->devType(); + if (devType == QInternal::Image) { + setPixelFormat(static_cast(dev)->format()); + } else { + QImage::Format format = QImage::Format_RGB32; + if (QApplicationPrivate::instance() && QApplicationPrivate::instance()->useTranslucentEGLSurfaces) + format = QImage::Format_ARGB32_Premultiplied; + setPixelFormat(format); + } +} + + +QT_END_NAMESPACE diff --git a/src/gui/egl/qegl_wince.cpp b/src/gui/egl/qegl_wince.cpp new file mode 100644 index 0000000000..1d16cadd08 --- /dev/null +++ b/src/gui/egl/qegl_wince.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "qegl_p.h" +#include "qeglcontext_p.h" + +#include + + +QT_BEGIN_NAMESPACE + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + HDC myDc = GetDC(0); + if (!myDc) { + qWarning("QEglContext::nativeDisplay(): WinCE display is not open"); + return EGL_DEFAULT_DISPLAY; + } + return EGLNativeDisplayType(myDc); +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + return (EGLNativeWindowType)(widget->winId()); +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap*) +{ + qWarning("QEgl: EGL pixmap surfaces not supported on WinCE"); + return (EGLNativePixmapType)0; +} + +// Set pixel format and other properties based on a paint device. +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + int devType = dev->devType(); + if (devType == QInternal::Image) + setPixelFormat(static_cast(dev)->format()); + else + setPixelFormat(QImage::Format_RGB16); // XXX +} + +QT_END_NAMESPACE diff --git a/src/gui/egl/qegl_x11.cpp b/src/gui/egl/qegl_x11.cpp new file mode 100644 index 0000000000..bf79ec79e7 --- /dev/null +++ b/src/gui/egl/qegl_x11.cpp @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "QtGui/private/qegl_p.h" +#include "QtGui/private/qeglcontext_p.h" + +QT_BEGIN_NAMESPACE + + +EGLNativeDisplayType QEgl::nativeDisplay() +{ + Display *xdpy = QX11Info::display(); + if (!xdpy) { + qWarning("QEglContext::getDisplay(): X11 display is not open"); + return EGLNativeDisplayType(EGL_DEFAULT_DISPLAY); + } + return EGLNativeDisplayType(xdpy); +} + +EGLNativeWindowType QEgl::nativeWindow(QWidget* widget) +{ + return (EGLNativeWindowType)(widget->winId()); +} + +EGLNativePixmapType QEgl::nativePixmap(QPixmap* pixmap) +{ + return (EGLNativePixmapType)(pixmap->handle()); +} + +static int countBits(unsigned long mask) +{ + int count = 0; + while (mask != 0) { + if (mask & 1) + ++count; + mask >>= 1; + } + return count; +} + +// Set the pixel format parameters from the visual in "xinfo". +void QEglProperties::setVisualFormat(const QX11Info *xinfo) +{ + if (!xinfo) + return; + Visual *visual = (Visual*)xinfo->visual(); + if (!visual) + return; + if (visual->c_class != TrueColor && visual->c_class != DirectColor) + return; + setValue(EGL_RED_SIZE, countBits(visual->red_mask)); + setValue(EGL_GREEN_SIZE, countBits(visual->green_mask)); + setValue(EGL_BLUE_SIZE, countBits(visual->blue_mask)); + + EGLint alphaBits = 0; +#if !defined(QT_NO_XRENDER) + XRenderPictFormat *format; + format = XRenderFindVisualFormat(xinfo->display(), visual); + if (format && (format->type == PictTypeDirect) && format->direct.alphaMask) { + alphaBits = countBits(format->direct.alphaMask); + qDebug("QEglProperties::setVisualFormat() - visual's alphaMask is %d", alphaBits); + } +#endif + setValue(EGL_ALPHA_SIZE, alphaBits); +} + +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); + +// Set pixel format and other properties based on a paint device. +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + if (!dev) + return; + if (dev->devType() == QInternal::Image) + setPixelFormat(static_cast(dev)->format()); + else + setVisualFormat(qt_x11Info(dev)); +} + +//#define QT_DEBUG_X11_VISUAL_SELECTION 1 + +VisualID QEgl::getCompatibleVisualId(EGLConfig config) +{ + VisualID visualId = 0; + EGLint eglValue = 0; + + EGLint configRedSize = 0; + eglGetConfigAttrib(display(), config, EGL_RED_SIZE, &configRedSize); + + EGLint configGreenSize = 0; + eglGetConfigAttrib(display(), config, EGL_GREEN_SIZE, &configGreenSize); + + EGLint configBlueSize = 0; + eglGetConfigAttrib(display(), config, EGL_BLUE_SIZE, &configBlueSize); + + EGLint configAlphaSize = 0; + eglGetConfigAttrib(display(), config, EGL_ALPHA_SIZE, &configAlphaSize); + + eglGetConfigAttrib(display(), config, EGL_CONFIG_ID, &eglValue); + int configId = eglValue; + + // See if EGL provided a valid VisualID: + eglGetConfigAttrib(display(), config, EGL_NATIVE_VISUAL_ID, &eglValue); + visualId = (VisualID)eglValue; + if (visualId) { + // EGL has suggested a visual id, so get the rest of the visual info for that id: + XVisualInfo visualInfoTemplate; + memset(&visualInfoTemplate, 0, sizeof(XVisualInfo)); + visualInfoTemplate.visualid = visualId; + + XVisualInfo *chosenVisualInfo; + int matchingCount = 0; + chosenVisualInfo = XGetVisualInfo(X11->display, VisualIDMask, &visualInfoTemplate, &matchingCount); + if (chosenVisualInfo) { + // Skip size checks if implementation supports non-matching visual + // and config (http://bugreports.qt.nokia.com/browse/QTBUG-9444). + if (QEgl::hasExtension("EGL_NV_post_convert_rounding")) { + XFree(chosenVisualInfo); + return visualId; + } + + int visualRedSize = countBits(chosenVisualInfo->red_mask); + int visualGreenSize = countBits(chosenVisualInfo->green_mask); + int visualBlueSize = countBits(chosenVisualInfo->blue_mask); + int visualAlphaSize = -1; // Need XRender to tell us the alpha channel size + +#if !defined(QT_NO_XRENDER) + if (X11->use_xrender) { + // If we have XRender, actually check the visual supplied by EGL is ARGB + XRenderPictFormat *format; + format = XRenderFindVisualFormat(X11->display, chosenVisualInfo->visual); + if (format && (format->type == PictTypeDirect)) + visualAlphaSize = countBits(format->direct.alphaMask); + } +#endif + + bool visualMatchesConfig = false; + if ( visualRedSize == configRedSize && + visualGreenSize == configGreenSize && + visualBlueSize == configBlueSize ) + { + // We need XRender to check the alpha channel size of the visual. If we don't have + // the alpha size, we don't check it against the EGL config's alpha size. + if (visualAlphaSize >= 0) + visualMatchesConfig = visualAlphaSize == configAlphaSize; + else + visualMatchesConfig = true; + } + + if (!visualMatchesConfig) { + if (visualAlphaSize >= 0) { + qWarning("Warning: EGL suggested using X Visual ID %d (ARGB%d%d%d%d) for EGL config %d (ARGB%d%d%d%d), but this is incompatable", + (int)visualId, visualAlphaSize, visualRedSize, visualGreenSize, visualBlueSize, + configId, configAlphaSize, configRedSize, configGreenSize, configBlueSize); + } else { + qWarning("Warning: EGL suggested using X Visual ID %d (RGB%d%d%d) for EGL config %d (RGB%d%d%d), but this is incompatable", + (int)visualId, visualRedSize, visualGreenSize, visualBlueSize, + configId, configRedSize, configGreenSize, configBlueSize); + } + visualId = 0; + } + } else { + qWarning("Warning: EGL suggested using X Visual ID %d for EGL config %d, but that isn't a valid ID", + (int)visualId, configId); + visualId = 0; + } + XFree(chosenVisualInfo); + } +#ifdef QT_DEBUG_X11_VISUAL_SELECTION + else + qDebug("EGL did not suggest a VisualID (EGL_NATIVE_VISUAL_ID was zero) for EGLConfig %d", configId); +#endif + + if (visualId) { +#ifdef QT_DEBUG_X11_VISUAL_SELECTION + if (configAlphaSize > 0) + qDebug("Using ARGB Visual ID %d provided by EGL for config %d", (int)visualId, configId); + else + qDebug("Using Opaque Visual ID %d provided by EGL for config %d", (int)visualId, configId); +#endif + return visualId; + } + + + // If EGL didn't give us a valid visual ID, try XRender +#if !defined(QT_NO_XRENDER) + if (!visualId && X11->use_xrender) { + XVisualInfo visualInfoTemplate; + memset(&visualInfoTemplate, 0, sizeof(XVisualInfo)); + + visualInfoTemplate.c_class = TrueColor; + + XVisualInfo *matchingVisuals; + int matchingCount = 0; + matchingVisuals = XGetVisualInfo(X11->display, + VisualClassMask, + &visualInfoTemplate, + &matchingCount); + + for (int i = 0; i < matchingCount; ++i) { + XRenderPictFormat *format; + format = XRenderFindVisualFormat(X11->display, matchingVisuals[i].visual); + + // Check the format for the visual matches the EGL config + if ( (countBits(format->direct.redMask) == configRedSize) && + (countBits(format->direct.greenMask) == configGreenSize) && + (countBits(format->direct.blueMask) == configBlueSize) && + (countBits(format->direct.alphaMask) == configAlphaSize) ) + { + visualId = matchingVisuals[i].visualid; + break; + } + } + if (matchingVisuals) + XFree(matchingVisuals); + + } + if (visualId) { +# ifdef QT_DEBUG_X11_VISUAL_SELECTION + if (configAlphaSize > 0) + qDebug("Using ARGB Visual ID %d provided by XRender for EGL config %d", (int)visualId, configId); + else + qDebug("Using Opaque Visual ID %d provided by XRender for EGL config %d", (int)visualId, configId); +# endif // QT_DEBUG_X11_VISUAL_SELECTION + return visualId; + } +# ifdef QT_DEBUG_X11_VISUAL_SELECTION + else + qDebug("Failed to find an XVisual which matches EGL config %d using XRender", configId); +# endif // QT_DEBUG_X11_VISUAL_SELECTION + +#endif //!defined(QT_NO_XRENDER) + + + // Finally, if XRender also failed to find a visual (or isn't present), try to + // use XGetVisualInfo and only use the bit depths to match on: + if (!visualId) { + XVisualInfo visualInfoTemplate; + memset(&visualInfoTemplate, 0, sizeof(XVisualInfo)); + XVisualInfo *matchingVisuals; + int matchingCount = 0; + + visualInfoTemplate.depth = configRedSize + configGreenSize + configBlueSize + configAlphaSize; + matchingVisuals = XGetVisualInfo(X11->display, + VisualDepthMask, + &visualInfoTemplate, + &matchingCount); + if (!matchingVisuals) { + // Try again without taking the alpha channel into account: + visualInfoTemplate.depth = configRedSize + configGreenSize + configBlueSize; + matchingVisuals = XGetVisualInfo(X11->display, + VisualDepthMask, + &visualInfoTemplate, + &matchingCount); + } + + if (matchingVisuals) { + visualId = matchingVisuals[0].visualid; + XFree(matchingVisuals); + } + } + + if (visualId) { +#ifdef QT_DEBUG_X11_VISUAL_SELECTION + qDebug("Using Visual ID %d provided by XGetVisualInfo for EGL config %d", (int)visualId, configId); +#endif + return visualId; + } + + qWarning("Unable to find an X11 visual which matches EGL config %d", configId); + return (VisualID)0; +} + +void qt_set_winid_on_widget(QWidget* w, Qt::HANDLE id) +{ + w->create(id); +} + + +// NOTE: The X11 version of createSurface will re-create the native drawable if it's visual doesn't +// match the one for the passed in EGLConfig +EGLSurface QEgl::createSurface(QPaintDevice *device, EGLConfig config, const QEglProperties *properties) +{ + int devType = device->devType(); + + if (devType == QInternal::Pbuffer) { + // TODO + return EGL_NO_SURFACE; + } + + QX11PixmapData *x11PixmapData = 0; + if (devType == QInternal::Pixmap) { + QPixmapData *pmd = static_cast(device)->data_ptr().data(); + if (pmd->classId() == QPixmapData::X11Class) + x11PixmapData = static_cast(pmd); + else { + // TODO: Replace the pixmap's data with a new QX11PixmapData + qWarning("WARNING: Creating an EGL surface on a QPixmap is only supported for QX11PixmapData"); + return EGL_NO_SURFACE; + } + } else if ((devType != QInternal::Widget) && (devType != QInternal::Pbuffer)) { + qWarning("WARNING: Creating an EGLSurface for device type %d isn't supported", devType); + return EGL_NO_SURFACE; + } + + VisualID visualId = QEgl::getCompatibleVisualId(config); + EGLint alphaSize; + eglGetConfigAttrib(QEgl::display(), config, EGL_ALPHA_SIZE, &alphaSize); + + if (devType == QInternal::Widget) { + QWidget *widget = static_cast(device); + + VisualID currentVisualId = 0; + if (widget->testAttribute(Qt::WA_WState_Created)) + currentVisualId = XVisualIDFromVisual((Visual*)widget->x11Info().visual()); + + if (currentVisualId != visualId) { + // The window is either not created or has the wrong visual. Either way, we need + // to create a window with the correct visual and call create() on the widget: + + bool visible = widget->isVisible(); + if (visible) + widget->hide(); + + XVisualInfo visualInfo; + visualInfo.visualid = visualId; + { + XVisualInfo *visualInfoPtr; + int matchingCount = 0; + visualInfoPtr = XGetVisualInfo(widget->x11Info().display(), VisualIDMask, + &visualInfo, &matchingCount); + Q_ASSERT(visualInfoPtr); // visualId really should be valid! + visualInfo = *visualInfoPtr; + XFree(visualInfoPtr); + } + + Window parentWindow = RootWindow(widget->x11Info().display(), widget->x11Info().screen()); + if (widget->parentWidget()) + parentWindow = widget->parentWidget()->winId(); + + XSetWindowAttributes windowAttribs; + QColormap colmap = QColormap::instance(widget->x11Info().screen()); + windowAttribs.background_pixel = colmap.pixel(widget->palette().color(widget->backgroundRole())); + windowAttribs.border_pixel = colmap.pixel(Qt::black); + + unsigned int valueMask = CWBackPixel|CWBorderPixel; + if (alphaSize > 0) { + windowAttribs.colormap = XCreateColormap(widget->x11Info().display(), parentWindow, + visualInfo.visual, AllocNone); + valueMask |= CWColormap; + } + + Window window = XCreateWindow(widget->x11Info().display(), parentWindow, + widget->x(), widget->y(), widget->width(), widget->height(), + 0, visualInfo.depth, InputOutput, visualInfo.visual, + valueMask, &windowAttribs); + + // This is a nasty hack to get round the fact that we can't be a friend of QWidget: + qt_set_winid_on_widget(widget, window); + + if (visible) + widget->show(); + } + + // At this point, the widget's window should be created and have the correct visual. Now we + // just need to create the EGL surface for it: + const int *props; + if (properties) + props = properties->properties(); + else + props = 0; + EGLSurface surf = eglCreateWindowSurface(QEgl::display(), config, (EGLNativeWindowType)widget->winId(), props); + if (surf == EGL_NO_SURFACE) + qWarning("QEglContext::createSurface(): Unable to create EGL surface, error = 0x%x", eglGetError()); + return surf; + } + + if (x11PixmapData) { + // X11 Pixmaps are only created with a depth, so that's all we need to check + EGLint configDepth; + eglGetConfigAttrib(QEgl::display(), config, EGL_BUFFER_SIZE , &configDepth); + if (x11PixmapData->depth() != configDepth) { + // The bit depths are wrong which means the EGLConfig isn't compatable with + // this pixmap. So we need to replace the pixmap's existing data with a new + // one which is created with the correct depth: + +#ifndef QT_NO_XRENDER + if (configDepth == 32) { + qWarning("Warning: EGLConfig's depth (32) != pixmap's depth (%d), converting to ARGB32", + x11PixmapData->depth()); + x11PixmapData->convertToARGB32(true); + } else +#endif + { + qWarning("Warning: EGLConfig's depth (%d) != pixmap's depth (%d)", + configDepth, x11PixmapData->depth()); + } + } + + QEglProperties surfaceAttribs; + + // If the pixmap can't be bound to a texture, it's pretty useless + surfaceAttribs.setValue(EGL_TEXTURE_TARGET, EGL_TEXTURE_2D); + if (alphaSize > 0) + surfaceAttribs.setValue(EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA); + else + surfaceAttribs.setValue(EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGB); + + EGLSurface surf = eglCreatePixmapSurface(QEgl::display(), config, + (EGLNativePixmapType) x11PixmapData->handle(), + surfaceAttribs.properties()); + x11PixmapData->gl_surface = (void*)surf; + QImagePixmapCleanupHooks::enableCleanupHooks(x11PixmapData); + return surf; + } + + return EGL_NO_SURFACE; +} + +QT_END_NAMESPACE diff --git a/src/gui/egl/qeglcontext_p.h b/src/gui/egl/qeglcontext_p.h new file mode 100644 index 0000000000..6f1207dd45 --- /dev/null +++ b/src/gui/egl/qeglcontext_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEGLCONTEXT_P_H +#define QEGLCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience of +// the QtOpenGL and QtOpenVG modules. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QEglContext +{ +public: + QEglContext(); + ~QEglContext(); + + bool isValid() const; + bool isCurrent() const; + bool isSharing() const { return sharing; } + + QEgl::API api() const { return apiType; } + void setApi(QEgl::API api) { apiType = api; } + + bool chooseConfig(const QEglProperties& properties, QEgl::PixelFormatMatch match = QEgl::ExactPixelFormat); + bool createContext(QEglContext *shareContext = 0, const QEglProperties *properties = 0); + void destroyContext(); + EGLSurface createSurface(QPaintDevice *device, const QEglProperties *properties = 0); + void destroySurface(EGLSurface surface); + + bool makeCurrent(EGLSurface surface); + bool doneCurrent(); + bool lazyDoneCurrent(); + bool swapBuffers(EGLSurface surface); + bool swapBuffersRegion2NOK(EGLSurface surface, const QRegion *region); + + int configAttrib(int name) const; + + EGLContext context() const { return ctx; } + void setContext(EGLContext context) { ctx = context; ownsContext = false;} + + EGLDisplay display() {return QEgl::display();} + + EGLConfig config() const { return cfg; } + void setConfig(EGLConfig config) { cfg = config; } + +private: + QEgl::API apiType; + EGLContext ctx; + EGLConfig cfg; + EGLSurface currentSurface; + bool current; + bool ownsContext; + bool sharing; + + static QEglContext *currentContext(QEgl::API api); + static void setCurrentContext(QEgl::API api, QEglContext *context); + + friend class QMeeGoGraphicsSystem; + friend class QMeeGoPixmapData; +}; + +QT_END_NAMESPACE + +#endif // QEGLCONTEXT_P_H diff --git a/src/gui/egl/qeglproperties.cpp b/src/gui/egl/qeglproperties.cpp new file mode 100644 index 0000000000..71cceb7704 --- /dev/null +++ b/src/gui/egl/qeglproperties.cpp @@ -0,0 +1,563 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qeglproperties_p.h" +#include "qeglcontext_p.h" + +QT_BEGIN_NAMESPACE + +// Initialize a property block. +QEglProperties::QEglProperties() +{ + props.append(EGL_NONE); +} + +QEglProperties::QEglProperties(EGLConfig cfg) +{ + props.append(EGL_NONE); + for (int name = 0x3020; name <= 0x304F; ++name) { + EGLint value; + if (name != EGL_NONE && eglGetConfigAttrib(QEgl::display(), cfg, name, &value)) + setValue(name, value); + } + eglGetError(); // Clear the error state. +} + +// Fetch the current value associated with a property. +int QEglProperties::value(int name) const +{ + for (int index = 0; index < (props.size() - 1); index += 2) { + if (props[index] == name) + return props[index + 1]; + } + + // If the attribute has not been explicitly set, return the EGL default + // The following defaults were taken from the EGL 1.4 spec: + switch(name) { + case EGL_BUFFER_SIZE: return 0; + case EGL_RED_SIZE: return 0; + case EGL_GREEN_SIZE: return 0; + case EGL_BLUE_SIZE: return 0; + case EGL_ALPHA_SIZE: return 0; +#ifdef EGL_LUMINANCE_SIZE + case EGL_LUMINANCE_SIZE: return 0; +#endif +#ifdef EGL_ALPHA_MASK_SIZE + case EGL_ALPHA_MASK_SIZE: return 0; +#endif +#ifdef EGL_BIND_TO_TEXTURE_RGB + case EGL_BIND_TO_TEXTURE_RGB: return EGL_DONT_CARE; +#endif +#ifdef EGL_BIND_TO_TEXTURE_RGBA + case EGL_BIND_TO_TEXTURE_RGBA: return EGL_DONT_CARE; +#endif +#ifdef EGL_COLOR_BUFFER_TYPE + case EGL_COLOR_BUFFER_TYPE: return EGL_RGB_BUFFER; +#endif + case EGL_CONFIG_CAVEAT: return EGL_DONT_CARE; + case EGL_CONFIG_ID: return EGL_DONT_CARE; + case EGL_DEPTH_SIZE: return 0; + case EGL_LEVEL: return 0; + case EGL_NATIVE_RENDERABLE: return EGL_DONT_CARE; + case EGL_NATIVE_VISUAL_TYPE: return EGL_DONT_CARE; + case EGL_MAX_SWAP_INTERVAL: return EGL_DONT_CARE; + case EGL_MIN_SWAP_INTERVAL: return EGL_DONT_CARE; +#ifdef EGL_RENDERABLE_TYPE + case EGL_RENDERABLE_TYPE: return EGL_OPENGL_ES_BIT; +#endif + case EGL_SAMPLE_BUFFERS: return 0; + case EGL_SAMPLES: return 0; + case EGL_STENCIL_SIZE: return 0; + case EGL_SURFACE_TYPE: return EGL_WINDOW_BIT; + case EGL_TRANSPARENT_TYPE: return EGL_NONE; + case EGL_TRANSPARENT_RED_VALUE: return EGL_DONT_CARE; + case EGL_TRANSPARENT_GREEN_VALUE: return EGL_DONT_CARE; + case EGL_TRANSPARENT_BLUE_VALUE: return EGL_DONT_CARE; + +#ifdef EGL_VERSION_1_3 + case EGL_CONFORMANT: return 0; + case EGL_MATCH_NATIVE_PIXMAP: return EGL_NONE; +#endif + + case EGL_MAX_PBUFFER_HEIGHT: + case EGL_MAX_PBUFFER_WIDTH: + case EGL_MAX_PBUFFER_PIXELS: + case EGL_NATIVE_VISUAL_ID: + case EGL_NONE: + // Attribute does not affect config selection. + return EGL_DONT_CARE; + default: + // Attribute is unknown in EGL <= 1.4. + return EGL_DONT_CARE; + } +} + +// Set the value associated with a property, replacing an existing +// value if there is one. +void QEglProperties::setValue(int name, int value) +{ + for (int index = 0; index < (props.size() - 1); index += 2) { + if (props[index] == name) { + props[index + 1] = value; + return; + } + } + props[props.size() - 1] = name; + props.append(value); + props.append(EGL_NONE); +} + +// Remove a property value. Returns false if the property is not present. +bool QEglProperties::removeValue(int name) +{ + for (int index = 0; index < (props.size() - 1); index += 2) { + if (props[index] == name) { + while ((index + 2) < props.size()) { + props[index] = props[index + 2]; + ++index; + } + props.resize(props.size() - 2); + return true; + } + } + return false; +} + +void QEglProperties::setDeviceType(int devType) +{ + if (devType == QInternal::Pixmap || devType == QInternal::Image) + setValue(EGL_SURFACE_TYPE, EGL_PIXMAP_BIT); + else if (devType == QInternal::Pbuffer) + setValue(EGL_SURFACE_TYPE, EGL_PBUFFER_BIT); + else + setValue(EGL_SURFACE_TYPE, EGL_WINDOW_BIT); +} + + +// Sets the red, green, blue, and alpha sizes based on a pixel format. +// Normally used to match a configuration request to the screen format. +void QEglProperties::setPixelFormat(QImage::Format pixelFormat) +{ + int red, green, blue, alpha; + switch (pixelFormat) { + case QImage::Format_RGB32: + case QImage::Format_RGB888: + red = green = blue = 8; alpha = 0; break; + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + red = green = blue = alpha = 8; break; + case QImage::Format_RGB16: + red = 5; green = 6; blue = 5; alpha = 0; break; + case QImage::Format_ARGB8565_Premultiplied: + red = 5; green = 6; blue = 5; alpha = 8; break; + case QImage::Format_RGB666: + red = green = blue = 6; alpha = 0; break; + case QImage::Format_ARGB6666_Premultiplied: + red = green = blue = alpha = 6; break; + case QImage::Format_RGB555: + red = green = blue = 5; alpha = 0; break; + case QImage::Format_ARGB8555_Premultiplied: + red = green = blue = 5; alpha = 8; break; + case QImage::Format_RGB444: + red = green = blue = 4; alpha = 0; break; + case QImage::Format_ARGB4444_Premultiplied: + red = green = blue = alpha = 4; break; + default: + qWarning() << "QEglProperties::setPixelFormat(): Unsupported pixel format"; + red = green = blue = alpha = 1; break; + } + setValue(EGL_RED_SIZE, red); + setValue(EGL_GREEN_SIZE, green); + setValue(EGL_BLUE_SIZE, blue); + setValue(EGL_ALPHA_SIZE, alpha); +} + +void QEglProperties::setRenderableType(QEgl::API api) +{ +#ifdef EGL_RENDERABLE_TYPE +#if defined(QT_OPENGL_ES_2) + if (api == QEgl::OpenGL) + setValue(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT); +#elif defined(QT_OPENGL_ES) + if (api == QEgl::OpenGL) + setValue(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT); +#elif defined(EGL_OPENGL_BIT) + if (api == QEgl::OpenGL) + setValue(EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT); +#endif +#ifdef EGL_OPENVG_BIT + if (api == QEgl::OpenVG) + setValue(EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT); +#endif +#else + Q_UNUSED(api); +#endif +} + +// Reduce the complexity of a configuration request to ask for less +// because the previous request did not result in success. Returns +// true if the complexity was reduced, or false if no further +// reductions in complexity are possible. +bool QEglProperties::reduceConfiguration() +{ +#ifdef EGL_SWAP_BEHAVIOR + if (value(EGL_SWAP_BEHAVIOR) != EGL_DONT_CARE) + removeValue(EGL_SWAP_BEHAVIOR); +#endif + +#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT + // For OpenVG, we sometimes try to create a surface using a pre-multiplied format. If we can't + // find a config which supports pre-multiplied formats, remove the flag on the surface type: + EGLint surfaceType = value(EGL_SURFACE_TYPE); + if (surfaceType & EGL_VG_ALPHA_FORMAT_PRE_BIT) { + surfaceType ^= EGL_VG_ALPHA_FORMAT_PRE_BIT; + setValue(EGL_SURFACE_TYPE, surfaceType); + return true; + } +#endif + // EGL chooses configs with the highest color depth over + // those with smaller (but faster) lower color depths. One + // way around this is to set EGL_BUFFER_SIZE to 16, which + // trumps the others. Of course, there may not be a 16-bit + // config available, so it's the first restraint we remove. + if (value(EGL_BUFFER_SIZE) == 16) { + removeValue(EGL_BUFFER_SIZE); + return true; + } + if (removeValue(EGL_SAMPLE_BUFFERS)) { + removeValue(EGL_SAMPLES); + return true; + } + if (removeValue(EGL_ALPHA_SIZE)) { +#if defined(EGL_BIND_TO_TEXTURE_RGBA) && defined(EGL_BIND_TO_TEXTURE_RGB) + if (removeValue(EGL_BIND_TO_TEXTURE_RGBA)) + setValue(EGL_BIND_TO_TEXTURE_RGB, TRUE); +#endif + return true; + } + if (removeValue(EGL_STENCIL_SIZE)) + return true; + if (removeValue(EGL_DEPTH_SIZE)) + return true; +#ifdef EGL_BIND_TO_TEXTURE_RGB + if (removeValue(EGL_BIND_TO_TEXTURE_RGB)) + return true; +#endif + return false; +} + +static void addTag(QString& str, const QString& tag) +{ + int lastnl = str.lastIndexOf(QLatin1String("\n")); + if (lastnl == -1) + lastnl = 0; + if ((str.length() - lastnl) >= 50) + str += QLatin1String("\n "); + str += tag; +} + +// Convert a property list to a string suitable for debug output. +QString QEglProperties::toString() const +{ + QString str; + int val; + + val = value(EGL_CONFIG_ID); + if (val != EGL_DONT_CARE) { + str += QLatin1String("id="); + str += QString::number(val); + str += QLatin1Char(' '); + } + +#ifdef EGL_RENDERABLE_TYPE + val = value(EGL_RENDERABLE_TYPE); + if (val != EGL_DONT_CARE) { + str += QLatin1String("type="); + QStringList types; + if ((val & EGL_OPENGL_ES_BIT) != 0) + types += QLatin1String("es1"); +#ifdef EGL_OPENGL_ES2_BIT + if ((val & EGL_OPENGL_ES2_BIT) != 0) + types += QLatin1String("es2"); +#endif +#ifdef EGL_OPENGL_BIT + if ((val & EGL_OPENGL_BIT) != 0) + types += QLatin1String("gl"); +#endif + if ((val & EGL_OPENVG_BIT) != 0) + types += QLatin1String("vg"); + if ((val & ~7) != 0) + types += QString::number(val); + str += types.join(QLatin1String(",")); + } else { + str += QLatin1String("type=any"); + } +#else + str += QLatin1String("type=es1"); +#endif + + int red = value(EGL_RED_SIZE); + int green = value(EGL_GREEN_SIZE); + int blue = value(EGL_BLUE_SIZE); + int alpha = value(EGL_ALPHA_SIZE); + int bufferSize = value(EGL_BUFFER_SIZE); + if (bufferSize == (red + green + blue + alpha)) + bufferSize = 0; + str += QLatin1String(" rgba="); + str += QString::number(red); + str += QLatin1Char(','); + str += QString::number(green); + str += QLatin1Char(','); + str += QString::number(blue); + str += QLatin1Char(','); + str += QString::number(alpha); + if (bufferSize != 0) { + // Only report buffer size if different than r+g+b+a. + str += QLatin1String(" buffer-size="); + str += QString::number(bufferSize); + } + +#ifdef EGL_COLOR_BUFFER_TYPE + val = value(EGL_COLOR_BUFFER_TYPE); + if (val == EGL_LUMINANCE_BUFFER) { + addTag(str, QLatin1String(" color-buffer-type=luminance")); + } else if (val != EGL_DONT_CARE && val != EGL_RGB_BUFFER) { + addTag(str, QLatin1String(" color-buffer-type=")); + str += QString::number(val, 16); + } +#endif + + val = value(EGL_DEPTH_SIZE); + if (val != 0) { + addTag(str, QLatin1String(" depth=")); + str += QString::number(val); + } + + val = value(EGL_STENCIL_SIZE); + if (val != 0) { + addTag(str, QLatin1String(" stencil=")); + str += QString::number(val); + } + + val = value(EGL_SURFACE_TYPE); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" surface-type=")); + QStringList types; + if ((val & EGL_WINDOW_BIT) != 0) + types += QLatin1String("window"); + if ((val & EGL_PIXMAP_BIT) != 0) + types += QLatin1String("pixmap"); + if ((val & EGL_PBUFFER_BIT) != 0) + types += QLatin1String("pbuffer"); +#ifdef EGL_VG_COLORSPACE_LINEAR_BIT + if ((val & EGL_VG_COLORSPACE_LINEAR_BIT) != 0) + types += QLatin1String("vg-colorspace-linear"); +#endif +#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT + if ((val & EGL_VG_ALPHA_FORMAT_PRE_BIT) != 0) + types += QLatin1String("vg-alpha-format-pre"); +#endif + if ((val & ~(EGL_WINDOW_BIT | EGL_PIXMAP_BIT | EGL_PBUFFER_BIT +#ifdef EGL_VG_COLORSPACE_LINEAR_BIT + | EGL_VG_COLORSPACE_LINEAR_BIT +#endif +#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT + | EGL_VG_ALPHA_FORMAT_PRE_BIT +#endif + )) != 0) { + types += QString::number(val); + } + str += types.join(QLatin1String(",")); + } + + val = value(EGL_CONFIG_CAVEAT); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" caveat=")); + if (val == EGL_NONE) + str += QLatin1String("none"); + else if (val == EGL_SLOW_CONFIG) + str += QLatin1String("slow"); + else if (val == EGL_NON_CONFORMANT_CONFIG) + str += QLatin1String("non-conformant"); + else + str += QString::number(val, 16); + } + + val = value(EGL_LEVEL); + if (val != 0) { + addTag(str, QLatin1String(" level=")); + str += QString::number(val); + } + + int width, height, pixels; + width = value(EGL_MAX_PBUFFER_WIDTH); + height = value(EGL_MAX_PBUFFER_HEIGHT); + pixels = value(EGL_MAX_PBUFFER_PIXELS); + if (height != EGL_DONT_CARE || width != EGL_DONT_CARE) { + addTag(str, QLatin1String(" max-pbuffer-size=")); + str += QString::number(width); + str += QLatin1Char('x'); + str += QString::number(height); + if (pixels != (width * height)) { + addTag(str, QLatin1String(" max-pbuffer-pixels=")); + str += QString::number(pixels); + } + } + + val = value(EGL_NATIVE_RENDERABLE); + if (val != EGL_DONT_CARE) { + if (val) + addTag(str, QLatin1String(" native-renderable=true")); + else + addTag(str, QLatin1String(" native-renderable=false")); + } + + val = value(EGL_NATIVE_VISUAL_ID); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" visual-id=")); + str += QString::number(val); + } + + val = value(EGL_NATIVE_VISUAL_TYPE); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" visual-type=")); + str += QString::number(val); + } + +#ifdef EGL_PRESERVED_RESOURCES + val = value(EGL_PRESERVED_RESOURCES); + if (val != EGL_DONT_CARE) { + if (val) + addTag(str, QLatin1String(" preserved-resources=true")); + else + addTag(str, QLatin1String(" preserved-resources=false")); + } +#endif + + val = value(EGL_SAMPLES); + if (val != 0) { + addTag(str, QLatin1String(" samples=")); + str += QString::number(val); + } + + val = value(EGL_SAMPLE_BUFFERS); + if (val != 0) { + addTag(str, QLatin1String(" sample-buffers=")); + str += QString::number(val); + } + + val = value(EGL_TRANSPARENT_TYPE); + if (val == EGL_TRANSPARENT_RGB) { + addTag(str, QLatin1String(" transparent-rgb=")); + str += QString::number(value(EGL_TRANSPARENT_RED_VALUE)); + str += QLatin1Char(','); + str += QString::number(value(EGL_TRANSPARENT_GREEN_VALUE)); + str += QLatin1Char(','); + str += QString::number(value(EGL_TRANSPARENT_BLUE_VALUE)); + } + +#if defined(EGL_BIND_TO_TEXTURE_RGB) && defined(EGL_BIND_TO_TEXTURE_RGBA) + val = value(EGL_BIND_TO_TEXTURE_RGB); + int val2 = value(EGL_BIND_TO_TEXTURE_RGBA); + if (val != EGL_DONT_CARE || val2 != EGL_DONT_CARE) { + addTag(str, QLatin1String(" bind-texture=")); + if (val == EGL_TRUE) + str += QLatin1String("rgb"); + else + str += QLatin1String("no-rgb"); + if (val2 == EGL_TRUE) + str += QLatin1String(",rgba"); + else + str += QLatin1String(",no-rgba"); + } +#endif + +#ifdef EGL_MIN_SWAP_INTERVAL + val = value(EGL_MIN_SWAP_INTERVAL); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" min-swap-interval=")); + str += QString::number(val); + } +#endif + +#ifdef EGL_MIN_SWAP_INTERVAL + val = value(EGL_MAX_SWAP_INTERVAL); + if (val != EGL_DONT_CARE) { + addTag(str, QLatin1String(" max-swap-interval=")); + str += QString::number(val); + } +#endif + +#ifdef EGL_LUMINANCE_SIZE + val = value(EGL_LUMINANCE_SIZE); + if (val != 0) { + addTag(str, QLatin1String(" luminance=")); + str += QString::number(val); + } +#endif + +#ifdef EGL_ALPHA_MASK_SIZE + val = value(EGL_ALPHA_MASK_SIZE); + if (val != 0) { + addTag(str, QLatin1String(" alpha-mask=")); + str += QString::number(val); + } +#endif + +#ifdef EGL_CONFORMANT + val = value(EGL_CONFORMANT); + if (val != 0) { + if (val) + addTag(str, QLatin1String(" conformant=true")); + else + addTag(str, QLatin1String(" conformant=false")); + } +#endif + + return str; +} + +QT_END_NAMESPACE + + diff --git a/src/gui/egl/qeglproperties_p.h b/src/gui/egl/qeglproperties_p.h new file mode 100644 index 0000000000..360eb78041 --- /dev/null +++ b/src/gui/egl/qeglproperties_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEGLPROPERTIES_P_H +#define QEGLPROPERTIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QtOpenGL and QtOpenVG modules. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QX11Info; +class QPaintDevice; + +class Q_GUI_EXPORT QEglProperties +{ +public: + QEglProperties(); + QEglProperties(EGLConfig); + QEglProperties(const QEglProperties& other) : props(other.props) {} + ~QEglProperties() {} + + int value(int name) const; + void setValue(int name, int value); + bool removeValue(int name); + bool isEmpty() const { return props[0] == EGL_NONE; } + + const int *properties() const { return props.constData(); } + + void setPixelFormat(QImage::Format pixelFormat); +#ifdef Q_WS_X11 + void setVisualFormat(const QX11Info *xinfo); +#endif + void setDeviceType(int devType); + void setPaintDeviceFormat(QPaintDevice *dev); + void setRenderableType(QEgl::API api); + + bool reduceConfiguration(); + + QString toString() const; + +private: + QVarLengthArray props; +}; + +QT_END_NAMESPACE + +#endif // QEGLPROPERTIES_P_H diff --git a/src/gui/egl/qeglproperties_stub.cpp b/src/gui/egl/qeglproperties_stub.cpp new file mode 100644 index 0000000000..7c17a1561f --- /dev/null +++ b/src/gui/egl/qeglproperties_stub.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qeglproperties_p.h" +#include "qeglcontext_p.h" + +QT_BEGIN_NAMESPACE + +static void noegl(const char *fn) +{ + qWarning() << fn << " called, but Qt configured without EGL" << endl; +} + +#define NOEGL noegl(__FUNCTION__); + +// Initialize a property block. +QEglProperties::QEglProperties() +{ + NOEGL +} + +QEglProperties::QEglProperties(EGLConfig cfg) +{ + Q_UNUSED(cfg) + NOEGL +} + +// Fetch the current value associated with a property. +int QEglProperties::value(int name) const +{ + Q_UNUSED(name) + NOEGL + return 0; +} + +// Set the value associated with a property, replacing an existing +// value if there is one. +void QEglProperties::setValue(int name, int value) +{ + Q_UNUSED(name) + Q_UNUSED(value) + NOEGL +} + +// Remove a property value. Returns false if the property is not present. +bool QEglProperties::removeValue(int name) +{ + Q_UNUSED(name) + NOEGL + return false; +} + +void QEglProperties::setDeviceType(int devType) +{ + Q_UNUSED(devType) + NOEGL +} + + +// Sets the red, green, blue, and alpha sizes based on a pixel format. +// Normally used to match a configuration request to the screen format. +void QEglProperties::setPixelFormat(QImage::Format pixelFormat) +{ + Q_UNUSED(pixelFormat) + NOEGL + +} + +void QEglProperties::setRenderableType(QEgl::API api) +{ + Q_UNUSED(api); + NOEGL +} + +// Reduce the complexity of a configuration request to ask for less +// because the previous request did not result in success. Returns +// true if the complexity was reduced, or false if no further +// reductions in complexity are possible. +bool QEglProperties::reduceConfiguration() +{ + NOEGL + return false; +} + +static void addTag(QString& str, const QString& tag) +{ + Q_UNUSED(str) + Q_UNUSED(tag) + NOEGL +} + +// Convert a property list to a string suitable for debug output. +QString QEglProperties::toString() const +{ + NOEGL + return QString(); +} + +void QEglProperties::setPaintDeviceFormat(QPaintDevice *dev) +{ + Q_UNUSED(dev) + NOEGL +} + +QT_END_NAMESPACE + + diff --git a/src/gui/embedded/directfb.pri b/src/gui/embedded/directfb.pri new file mode 100644 index 0000000000..75d693e0b4 --- /dev/null +++ b/src/gui/embedded/directfb.pri @@ -0,0 +1,40 @@ +# These defines might be necessary if your DirectFB driver doesn't +# support all of the DirectFB API. +# +#DEFINES += QT_DIRECTFB_SUBSURFACE +#DEFINES += QT_DIRECTFB_WINDOW_AS_CURSOR +#DEFINES += QT_NO_DIRECTFB_IMAGEPROVIDER +#DEFINES += QT_DIRECTFB_IMAGEPROVIDER_KEEPALIVE +#DEFINES += QT_DIRECTFB_IMAGECACHE +#DEFINES += QT_NO_DIRECTFB_WM +#DEFINES += QT_NO_DIRECTFB_LAYER +#DEFINES += QT_DIRECTFB_PALETTE +#DEFINES += QT_NO_DIRECTFB_PREALLOCATED +#DEFINES += QT_NO_DIRECTFB_MOUSE +#DEFINES += QT_NO_DIRECTFB_KEYBOARD +#DEFINES += QT_DIRECTFB_TIMING +#DEFINES += QT_NO_DIRECTFB_OPAQUE_DETECTION +#DEFINES += QT_NO_DIRECTFB_STRETCHBLIT +DIRECTFB_DRAWINGOPERATIONS=DRAW_RECTS|DRAW_LINES|DRAW_IMAGE|DRAW_PIXMAP|DRAW_TILED_PIXMAP|STROKE_PATH|DRAW_PATH|DRAW_POINTS|DRAW_ELLIPSE|DRAW_POLYGON|DRAW_TEXT|FILL_PATH|FILL_RECT|DRAW_COLORSPANS|DRAW_ROUNDED_RECT|DRAW_STATICTEXT +#DEFINES += \"QT_DIRECTFB_WARN_ON_RASTERFALLBACKS=$$DIRECTFB_DRAWINGOPERATIONS\" +#DEFINES += \"QT_DIRECTFB_DISABLE_RASTERFALLBACKS=$$DIRECTFB_DRAWINGOPERATIONS\" + +HEADERS += $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbscreen.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbwindowsurface.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpaintengine.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpaintdevice.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpixmap.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbkeyboard.h \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbmouse.h + +SOURCES += $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbscreen.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbwindowsurface.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpaintengine.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpaintdevice.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbpixmap.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbkeyboard.cpp \ + $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb/qdirectfbmouse.cpp + + +QMAKE_CXXFLAGS += $$QT_CFLAGS_DIRECTFB +LIBS += $$QT_LIBS_DIRECTFB diff --git a/src/gui/embedded/embedded.pri b/src/gui/embedded/embedded.pri new file mode 100644 index 0000000000..31f0bc6d50 --- /dev/null +++ b/src/gui/embedded/embedded.pri @@ -0,0 +1,226 @@ +# Qt for Embedded Linux + +embedded { + CONFIG -= opengl x11 + LIBS -= -dl + KERNEL_P = kernel + + !mac:HEADERS += embedded/qsoundqss_qws.h + HEADERS += \ + embedded/qcopchannel_qws.h \ + embedded/qdecoration_qws.h \ + embedded/qdecorationfactory_qws.h \ + embedded/qdecorationplugin_qws.h \ + embedded/qdirectpainter_qws.h \ + embedded/qlock_p.h \ + embedded/qscreen_qws.h \ + embedded/qscreenmulti_qws_p.h \ + embedded/qscreenproxy_qws.h \ + embedded/qwindowsystem_qws.h \ + embedded/qwindowsystem_p.h \ + embedded/qwscommand_qws_p.h \ + embedded/qwscursor_qws.h \ + embedded/qwsdisplay_qws.h \ + embedded/qwsdisplay_qws_p.h \ + embedded/qwsevent_qws.h \ + embedded/qwsmanager_qws.h \ + embedded/qwsmanager_p.h \ + embedded/qwsproperty_qws.h \ + embedded/qwsprotocolitem_qws.h \ + embedded/qtransportauth_qws.h \ + embedded/qtransportauth_qws_p.h \ + embedded/qtransportauthdefs_qws.h \ + embedded/qwssocket_qws.h \ + embedded/qwslock_p.h \ + embedded/qwsutils_qws.h \ + embedded/qwssharedmemory_p.h \ + embedded/qwssignalhandler_p.h \ + embedded/qwsembedwidget.h + + !mac:SOURCES += embedded/qsoundqss_qws.cpp + SOURCES += \ + embedded/qcopchannel_qws.cpp \ + embedded/qdecoration_qws.cpp \ + embedded/qdecorationfactory_qws.cpp \ + embedded/qdecorationplugin_qws.cpp \ + embedded/qdirectpainter_qws.cpp \ + embedded/qlock.cpp \ + embedded/qscreen_qws.cpp \ + embedded/qscreenmulti_qws.cpp \ + embedded/qscreenproxy_qws.cpp \ + embedded/qwindowsystem_qws.cpp \ + embedded/qwscommand_qws.cpp \ + embedded/qwscursor_qws.cpp \ + embedded/qwsevent_qws.cpp \ + embedded/qwsmanager_qws.cpp \ + embedded/qwsproperty_qws.cpp \ + embedded/qtransportauth_qws.cpp \ + embedded/qwslock.cpp \ + embedded/qwssharedmemory.cpp \ + embedded/qwssocket_qws.cpp \ + embedded/qwssignalhandler.cpp \ + embedded/qwsembedwidget.cpp + + contains(QT_CONFIG,sxe)|contains(QT_CONFIG,qtopia) { + SOURCES += embedded/qunixsocket.cpp embedded/qunixsocketserver.cpp + HEADERS += embedded/qunixsocket_p.h embedded/qunixsocketserver_p.h + } + +# +# Decorations +# + contains( decorations, default ) { + HEADERS += embedded/qdecorationdefault_qws.h + SOURCES += embedded/qdecorationdefault_qws.cpp + } + contains( decorations, styled ) { + HEADERS += embedded/qdecorationstyled_qws.h + SOURCES += embedded/qdecorationstyled_qws.cpp + } + + contains( decorations, windows ) { + HEADERS += embedded/qdecorationwindows_qws.h + SOURCES += embedded/qdecorationwindows_qws.cpp + } + +# +# Qt for Embedded Linux Drivers +# + HEADERS += embedded/qscreendriverplugin_qws.h \ + embedded/qscreendriverfactory_qws.h \ + embedded/qkbd_qws.h \ + embedded/qkbd_qws_p.h \ + embedded/qkbd_defaultmap_qws_p.h \ + embedded/qkbddriverplugin_qws.h \ + embedded/qkbddriverfactory_qws.h \ + embedded/qmouse_qws.h \ + embedded/qmousedriverplugin_qws.h \ + embedded/qmousedriverfactory_qws.h + + SOURCES += embedded/qscreendriverplugin_qws.cpp \ + embedded/qscreendriverfactory_qws.cpp \ + embedded/qkbd_qws.cpp \ + embedded/qkbddriverplugin_qws.cpp \ + embedded/qkbddriverfactory_qws.cpp \ + embedded/qmouse_qws.cpp \ + embedded/qmousedriverplugin_qws.cpp \ + embedded/qmousedriverfactory_qws.cpp + +# +# Graphics drivers +# + contains( gfx-drivers, linuxfb ) { + HEADERS += embedded/qscreenlinuxfb_qws.h + SOURCES += embedded/qscreenlinuxfb_qws.cpp + } + + contains( gfx-drivers, qnx ) { + HEADERS += embedded/qscreenqnx_qws.h + SOURCES += embedded/qscreenqnx_qws.cpp + LIBS += -lgf + } + + contains( gfx-drivers, integrityfb ) { + HEADERS += embedded/qscreenintegrityfb_qws.h + SOURCES += embedded/qscreenintegrityfb_qws.cpp + LIBS += -lfbdev + } + + contains( gfx-drivers, qvfb ) { + HEADERS += embedded/qscreenvfb_qws.h + SOURCES += embedded/qscreenvfb_qws.cpp + } + + + contains( gfx-drivers, vnc ) { + VNCDIR = $$QT_SOURCE_TREE/src/plugins/gfxdrivers/vnc + INCLUDEPATH += $$VNCDIR + HEADERS += $$VNCDIR/qscreenvnc_qws.h \ + $$VNCDIR/qscreenvnc_p.h + SOURCES += $$VNCDIR/qscreenvnc_qws.cpp + } + + contains( gfx-drivers, transformed ) { + HEADERS += embedded/qscreentransformed_qws.h + SOURCES += embedded/qscreentransformed_qws.cpp + } + + contains( gfx-drivers, directfb ) { + INCLUDEPATH += $$QT_SOURCE_TREE/src/plugins/gfxdrivers/directfb + include($$PWD/directfb.pri) + } +# +# Keyboard drivers +# + contains( kbd-drivers, qvfb ) { + HEADERS +=embedded/qkbdvfb_qws.h + SOURCES +=embedded/qkbdvfb_qws.cpp + !contains( kbd-drivers, qvfb ) { + kbd-drivers += qvfb + } + } + + contains( kbd-drivers, tty ) { + HEADERS +=embedded/qkbdtty_qws.h + SOURCES +=embedded/qkbdtty_qws.cpp + } + + contains( kbd-drivers, linuxinput ) { + HEADERS +=embedded/qkbdlinuxinput_qws.h + SOURCES +=embedded/qkbdlinuxinput_qws.cpp + } + + contains( kbd-drivers, um ) { + HEADERS +=embedded/qkbdum_qws.h + SOURCES +=embedded/qkbdum_qws.cpp + } + + contains( kbd-drivers, qnx ) { + HEADERS += embedded/qkbdqnx_qws.h + SOURCES += embedded/qkbdqnx_qws.cpp + } + + contains( kbd-drivers, integrity ) { + HEADERS += embedded/qkbdintegrity_qws.h + SOURCES += embedded/qkbdintegrity_qws.cpp + } + +# +# Mouse drivers +# + contains( mouse-drivers, qvfb ) { + HEADERS +=embedded/qmousevfb_qws.h + SOURCES +=embedded/qmousevfb_qws.cpp + } + + contains( mouse-drivers, pc ) { + HEADERS +=embedded/qmousepc_qws.h + SOURCES +=embedded/qmousepc_qws.cpp + } + + contains( mouse-drivers, linuxtp ) { + HEADERS +=embedded/qmouselinuxtp_qws.h + SOURCES +=embedded/qmouselinuxtp_qws.cpp + } + + contains( mouse-drivers, tslib ) { + LIBS_PRIVATE += -lts + HEADERS +=embedded/qmousetslib_qws.h + SOURCES +=embedded/qmousetslib_qws.cpp + } + + contains( mouse-drivers, linuxinput ) { + HEADERS +=embedded/qmouselinuxinput_qws.h + SOURCES +=embedded/qmouselinuxinput_qws.cpp + } + + contains( mouse-drivers, qnx ) { + HEADERS += embedded/qmouseqnx_qws.h + SOURCES += embedded/qmouseqnx_qws.cpp + } + + contains( mouse-drivers, integrity ) { + HEADERS += embedded/qmouseintegrity_qws.h + SOURCES += embedded/qmouseintegrity_qws.cpp + } +} diff --git a/src/gui/embedded/qcopchannel_qws.cpp b/src/gui/embedded/qcopchannel_qws.cpp new file mode 100644 index 0000000000..703debc69c --- /dev/null +++ b/src/gui/embedded/qcopchannel_qws.cpp @@ -0,0 +1,608 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcopchannel_qws.h" + +#ifndef QT_NO_COP + +#include "qwsdisplay_qws.h" +#include "qwscommand_qws_p.h" +#include "qwindowsystem_qws.h" +#include "qwindowsystem_p.h" +#include "qlist.h" +#include "qmap.h" +#include "qdatastream.h" +#include "qpointer.h" +#include "qmutex.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +typedef QMap > QCopServerMap; +static QCopServerMap *qcopServerMap = 0; + +class QCopServerRegexp +{ +public: + QCopServerRegexp( const QString& channel, QWSClient *client ); + QCopServerRegexp( const QCopServerRegexp& other ); + + QString channel; + QWSClient *client; + QRegExp regexp; +}; + +QCopServerRegexp::QCopServerRegexp( const QString& channel, QWSClient *client ) +{ + this->channel = channel; + this->client = client; + this->regexp = QRegExp( channel, Qt::CaseSensitive, QRegExp::Wildcard ); +} + +QCopServerRegexp::QCopServerRegexp( const QCopServerRegexp& other ) +{ + channel = other.channel; + client = other.client; + regexp = other.regexp; +} + +typedef QList QCopServerRegexpList; +static QCopServerRegexpList *qcopServerRegexpList = 0; + +typedef QMap > > QCopClientMap; +static QCopClientMap *qcopClientMap = 0; + +Q_GLOBAL_STATIC(QMutex, qcopClientMapMutex) + +// Determine if a channel name contains wildcard characters. +static bool containsWildcards( const QString& channel ) +{ + return channel.contains(QLatin1Char('*')); +} + +class QCopChannelPrivate +{ +public: + QString channel; +}; + +/*! + \class QCopChannel + \ingroup qws + + \brief The QCopChannel class provides communication capabilities + between clients in \l{Qt for Embedded Linux}. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + The Qt COmmunication Protocol (QCOP) is a many-to-many protocol + for transferring messages across registered channels. A channel is + registered by name, and anyone who wants to can listen to the + channel as well as send messages through it. The QCOP protocol + allows clients to communicate both within the same address space + and between different processes. + + To send messages to a given channel, QCopChannel provides the + static send() function. Using this function alone, the messages + are queued until Qt re-enters the event loop. To immediately flush + all queued messages to the registered listeners, call the static + flush() function. + + To listen to the traffic on a given channel, you typically + instantiate a QCopChannel object for the given channel and connect + to its received() signal that is emitted whenever there is + incoming data. Use the static isRegistered() function to query + the server for the existence of a given channel. QCopChannel + provides the channel() function returning the name of this + QCopChannel object's channel. + + In additon, QCopChannel provides the virtual receive() function + that can be reimplemented to filter the incoming messages and + data. The default implementation simply emits the received() + signal. + + \sa QWSServer, QWSClient, {Qt for Embedded Linux Architecture} +*/ + +/*! + Constructs a QCopChannel object for the specified \a channel, with + the given \a parent. Once created, the channel is registered by + the server. + + \sa isRegistered(), channel() +*/ + +QCopChannel::QCopChannel(const QString& channel, QObject *parent) : + QObject(parent) +{ + init(channel); +} + +#ifdef QT3_SUPPORT +/*! + Use the two argument overload instead, and call the + QObject::setObjectName() function to \a name the instance. +*/ +QCopChannel::QCopChannel(const QString& channel, QObject *parent, const char *name) : + QObject(parent) +{ + setObjectName(QString::fromAscii(name)); + init(channel); +} +#endif + +void QCopChannel::init(const QString& channel) +{ + d = new QCopChannelPrivate; + d->channel = channel; + + if (!qt_fbdpy) { + qFatal("QCopChannel: Must construct a QApplication " + "before QCopChannel"); + return; + } + + { + QMutexLocker locker(qcopClientMapMutex()); + + if (!qcopClientMap) + qcopClientMap = new QCopClientMap; + + // do we need a new channel list ? + QCopClientMap::Iterator it = qcopClientMap->find(channel); + if (it != qcopClientMap->end()) { + it.value().append(this); + return; + } + + it = qcopClientMap->insert(channel, QList< QPointer >()); + it.value().append(QPointer(this)); + } + + // inform server about this channel + qt_fbdpy->registerChannel(channel); +} + +/*! + \internal + + Resend all channel registrations + */ +void QCopChannel::reregisterAll() +{ + if(qcopClientMap) + for(QCopClientMap::Iterator iter = qcopClientMap->begin(); + iter != qcopClientMap->end(); + ++iter) + qt_fbdpy->registerChannel(iter.key()); +} + +/*! + Destroys this QCopChannel object. + + The server is notified that this particular listener has closed + its connection. The server will keep the channel open until the + last registered listener detaches. + + \sa isRegistered(), channel() +*/ + +QCopChannel::~QCopChannel() +{ + QMutexLocker locker(qcopClientMapMutex()); + QCopClientMap::Iterator it = qcopClientMap->find(d->channel); + Q_ASSERT(it != qcopClientMap->end()); + it.value().removeAll(this); + // still any clients connected locally ? + if (it.value().isEmpty()) { + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s << d->channel; + if (qt_fbdpy) + send(QLatin1String(""), QLatin1String("detach()"), data); + qcopClientMap->remove(d->channel); + } + + delete d; +} + +/*! + Returns the name of this object's channel. + + \sa isRegistered() +*/ + +QString QCopChannel::channel() const +{ + return d->channel; +} + +/*! + \fn void QCopChannel::receive(const QString& message, const QByteArray &data) + + Processes the incoming \a message and \a data. + + This function is called by the server when this object's channel + receives new messages. Note that the default implementation simply + emits the received() signal; reimplement this function to process + the incoming \a message and \a data. + + Note that the format of the given \a data has to be well defined + in order to extract the information it contains. In addition, it + is recommended to use the DCOP convention. This is not a + requirement, but you must ensure that the sender and receiver + agree on the argument types. For example: + + \snippet doc/src/snippets/code/src_gui_embedded_qcopchannel_qws.cpp 0 + + The above code assumes that the \c message is a DCOP-style + function signature and the \c data contains the function's + arguments. + + \sa send(), channel(), received() + */ +void QCopChannel::receive(const QString& msg, const QByteArray &data) +{ + emit received(msg, data); +} + +/*! + \fn void QCopChannel::received(const QString& message, const QByteArray &data) + + This signal is emitted whenever this object's channel receives new + messages (i.e., it is emitted by the receive() function), passing + the incoming \a message and \a data as parameters. + + \sa receive(), channel() +*/ + +/*! + Queries the server for the existence of the given \a channel. Returns true + if the channel is registered; otherwise returns false. + + \sa channel(), send() +*/ + +bool QCopChannel::isRegistered(const QString& channel) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s << channel; + if (!send(QLatin1String(""), QLatin1String("isRegistered()"), data)) + return false; + + QWSQCopMessageEvent *e = qt_fbdpy->waitForQCopResponse(); + bool known = e->message == "known"; + delete e; + return known; +} + +/*! + \fn bool QCopChannel::send(const QString& channel, const QString& message) + \overload +*/ + +bool QCopChannel::send(const QString& channel, const QString& msg) +{ + QByteArray data; + return send(channel, msg, data); +} + +/*! + \fn bool QCopChannel::send(const QString& channel, const QString& message, + const QByteArray &data) + + Sends the given \a message on the specified \a channel with the + given \a data. The message will be distributed to all clients + subscribed to the channel. Returns true if the message is sent + successfully; otherwise returns false. + + It is recommended to use the DCOP convention. This is not a + requirement, but you must ensure that the sender and receiver + agree on the argument types. + + Note that QDataStream provides a convenient way to fill the byte + array with auxiliary data. For example: + + \snippet doc/src/snippets/code/src_gui_embedded_qcopchannel_qws.cpp 1 + + In the code above the channel is \c "System/Shell". The \c message + is an arbitrary string, but in the example we've used the DCOP + convention of passing a function signature. Such a signature is + formatted as \c "functionname(types)" where \c types is a list of + zero or more comma-separated type names, with no whitespace, no + consts and no pointer or reference marks, i.e. no "*" or "&". + + \sa receive(), isRegistered() +*/ + +bool QCopChannel::send(const QString& channel, const QString& msg, + const QByteArray &data) +{ + if (!qt_fbdpy) { + qFatal("QCopChannel::send: Must construct a QApplication " + "before using QCopChannel"); + return false; + } + + qt_fbdpy->sendMessage(channel, msg, data); + + return true; +} + +/*! + \since 4.2 + + Flushes all queued messages to the registered listeners. + + Note that this function returns false if no QApplication has been + constructed, otherwise it returns true. + + \sa send() + +*/ +bool QCopChannel::flush() +{ + if (!qt_fbdpy) { + qFatal("QCopChannel::flush: Must construct a QApplication " + "before using QCopChannel"); + return false; + } + + qt_fbdpy->flushCommands(); + + return true; +} + +class QWSServerSignalBridge : public QObject { + Q_OBJECT + +public: + void emitNewChannel(const QString& channel); + void emitRemovedChannel(const QString& channel); + + signals: + void newChannel(const QString& channel); + void removedChannel(const QString& channel); +}; + +void QWSServerSignalBridge::emitNewChannel(const QString& channel){ + emit newChannel(channel); +} + +void QWSServerSignalBridge::emitRemovedChannel(const QString& channel) { + emit removedChannel(channel); +} + +/*! + \internal + Server side: subscribe client \a cl on channel \a ch. +*/ + +void QCopChannel::registerChannel(const QString& ch, QWSClient *cl) +{ + if (!qcopServerMap) + qcopServerMap = new QCopServerMap; + + // do we need a new channel list ? + QCopServerMap::Iterator it = qcopServerMap->find(ch); + if (it == qcopServerMap->end()) + it = qcopServerMap->insert(ch, QList()); + + // If the channel name contains wildcard characters, then we also + // register it on the server regexp matching list. + if (containsWildcards( ch )) { + QCopServerRegexp item(ch, cl); + if (!qcopServerRegexpList) + qcopServerRegexpList = new QCopServerRegexpList; + qcopServerRegexpList->append( item ); + } + + // If this is the first client in the channel, announce the channel as being created. + if (it.value().count() == 0) { + QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge(); + connect(qwsBridge, SIGNAL(newChannel(QString)), qwsServer, SIGNAL(newChannel(QString))); + qwsBridge->emitNewChannel(ch); + delete qwsBridge; + } + + it.value().append(cl); +} + +/*! + \internal + Server side: unsubscribe \a cl from all channels. +*/ + +void QCopChannel::detach(QWSClient *cl) +{ + if (!qcopServerMap) + return; + + QCopServerMap::Iterator it = qcopServerMap->begin(); + for (; it != qcopServerMap->end(); ++it) { + if (it.value().contains(cl)) { + it.value().removeAll(cl); + // If this was the last client in the channel, announce the channel as dead. + if (it.value().count() == 0) { + QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge(); + connect(qwsBridge, SIGNAL(removedChannel(QString)), qwsServer, SIGNAL(removedChannel(QString))); + qwsBridge->emitRemovedChannel(it.key()); + delete qwsBridge; + } + } + } + + if (!qcopServerRegexpList) + return; + + QCopServerRegexpList::Iterator it2 = qcopServerRegexpList->begin(); + while(it2 != qcopServerRegexpList->end()) { + if ((*it2).client == cl) + it2 = qcopServerRegexpList->erase(it2); + else + ++it2; + } +} + +/*! + \internal + Server side: transmit the message to all clients registered to the + specified channel. +*/ + +void QCopChannel::answer(QWSClient *cl, const QString& ch, + const QString& msg, const QByteArray &data) +{ + // internal commands + if (ch.isEmpty()) { + if (msg == QLatin1String("isRegistered()")) { + QString c; + QDataStream s(data); + s >> c; + bool known = qcopServerMap && qcopServerMap->contains(c) + && !((*qcopServerMap)[c]).isEmpty(); + // Yes, it's a typo, it's not user-visible, and we choose not to fix it for compatibility + QLatin1String ans = QLatin1String(known ? "known" : "unknown"); + QWSServerPrivate::sendQCopEvent(cl, QLatin1String(""), + ans, data, true); + return; + } else if (msg == QLatin1String("detach()")) { + QString c; + QDataStream s(data); + s >> c; + Q_ASSERT(qcopServerMap); + QCopServerMap::Iterator it = qcopServerMap->find(c); + if (it != qcopServerMap->end()) { + //Q_ASSERT(it.value().contains(cl)); + it.value().removeAll(cl); + if (it.value().isEmpty()) { + // If this was the last client in the channel, announce the channel as dead + QWSServerSignalBridge* qwsBridge = new QWSServerSignalBridge(); + connect(qwsBridge, SIGNAL(removedChannel(QString)), qwsServer, SIGNAL(removedChannel(QString))); + qwsBridge->emitRemovedChannel(it.key()); + delete qwsBridge; + qcopServerMap->erase(it); + } + } + if (qcopServerRegexpList && containsWildcards(c)) { + // Remove references to a wildcarded channel. + QCopServerRegexpList::Iterator it + = qcopServerRegexpList->begin(); + while(it != qcopServerRegexpList->end()) { + if ((*it).client == cl && (*it).channel == c) + it = qcopServerRegexpList->erase(it); + else + ++it; + } + } + return; + } + qWarning("QCopChannel: unknown internal command %s", qPrintable(msg)); + QWSServerPrivate::sendQCopEvent(cl, QLatin1String(""), + QLatin1String("bad"), data); + return; + } + + if (qcopServerMap) { + QList clist = qcopServerMap->value(ch); + for (int i=0; i < clist.size(); ++i) { + QWSClient *c = clist.at(i); + QWSServerPrivate::sendQCopEvent(c, ch, msg, data); + } + } + + if(qcopServerRegexpList && !containsWildcards(ch)) { + // Search for wildcard matches and forward the message on. + QCopServerRegexpList::ConstIterator it = qcopServerRegexpList->constBegin(); + for (; it != qcopServerRegexpList->constEnd(); ++it) { + if ((*it).regexp.exactMatch(ch)) { + QByteArray newData; + { + QDataStream stream + (&newData, QIODevice::WriteOnly | QIODevice::Append); + stream << ch; + stream << msg; + stream << data; + // Stream is flushed and closed at this point. + } + QWSServerPrivate::sendQCopEvent + ((*it).client, (*it).channel, + QLatin1String("forwardedMessage(QString,QString,QByteArray)"), + newData); + } + } + } +} + +/*! + \internal + Client side: distribute received event to the QCop instance managing the + channel. +*/ +void QCopChannel::sendLocally(const QString& ch, const QString& msg, + const QByteArray &data) +{ + Q_ASSERT(qcopClientMap); + + // filter out internal events + if (ch.isEmpty()) + return; + + // feed local clients with received data + QList< QPointer > clients; + { + QMutexLocker locker(qcopClientMapMutex()); + clients = (*qcopClientMap)[ch]; + } + for (int i = 0; i < clients.size(); ++i) { + QCopChannel *channel = (QCopChannel *)clients.at(i); + if ( channel ) + channel->receive(msg, data); + } +} + +QT_END_NAMESPACE + +#include "qcopchannel_qws.moc" + +#endif diff --git a/src/gui/embedded/qcopchannel_qws.h b/src/gui/embedded/qcopchannel_qws.h new file mode 100644 index 0000000000..5f018662c7 --- /dev/null +++ b/src/gui/embedded/qcopchannel_qws.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOPCHANNEL_QWS_H +#define QCOPCHANNEL_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COP + +class QWSClient; +class QCopChannelPrivate; + +class Q_GUI_EXPORT QCopChannel : public QObject +{ + Q_OBJECT +public: + explicit QCopChannel(const QString& channel, QObject *parent=0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QCopChannel(const QString& channel, QObject *parent, const char *name); +#endif + virtual ~QCopChannel(); + + QString channel() const; + + static bool isRegistered(const QString& channel); + static bool send(const QString& channel, const QString& msg); + static bool send(const QString& channel, const QString& msg, + const QByteArray &data); + + static bool flush(); + + static void sendLocally( const QString& ch, const QString& msg, + const QByteArray &data); + static void reregisterAll(); + + virtual void receive(const QString& msg, const QByteArray &data); + +Q_SIGNALS: + void received(const QString& msg, const QByteArray &data); + +private: + void init(const QString& channel); + + // server side + static void registerChannel(const QString& ch, QWSClient *cl); + static void detach(QWSClient *cl); + static void answer(QWSClient *cl, const QString& ch, + const QString& msg, const QByteArray &data); + // client side + QCopChannelPrivate* d; + + friend class QWSServer; + friend class QWSServerPrivate; + friend class QApplication; +}; + +#endif // QT_NO_COP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOPCHANNEL_QWS_H diff --git a/src/gui/embedded/qdecoration_qws.cpp b/src/gui/embedded/qdecoration_qws.cpp new file mode 100644 index 0000000000..7e93407c08 --- /dev/null +++ b/src/gui/embedded/qdecoration_qws.cpp @@ -0,0 +1,404 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdecoration_qws.h" + +#include "qapplication.h" +#include "qdrawutil.h" +#include "qpainter.h" +#include "qregion.h" +#include "qwhatsthis.h" + +#include "qmenu.h" +#include "private/qwidget_p.h" +#include "qwsmanager_qws.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDecoration + \ingroup qws + + \brief The QDecoration class is a base class for window + decorations in Qt for Embedded Linux + + Note that this class is non-portable and only available in + \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides window management of top level windows + and several ready made decorations (i.e., \c Default, \c Styled + and \c Windows). Custom decorations can be implemented by + subclassing the QDecoration class and creating a decoration plugin + (derived from QDecorationPlugin). The default + implementation of the QDecorationFactory class will automatically + detect the plugin, and load the decoration into the application at + run-time using Qt's \l {How to Create Qt Plugins}{plugin + system}. To actually apply a decoration, use the + QApplication::qwsSetDecoration() function. + + When creating a custom decoration, implement the paint() function + to paint the border and title decoration, and the region() + function to return the regions the decoration + occupies. Reimplement the regionClicked() and + regionDoubleClicked() functions to respond to mouse clicks (the + default implementations responds to (single) clicks on items in a + widget's system menu and double clicks on a widget's title). + + QDecoration provides the DecorationRegion enum that describes the + various regions of the window decoration, and the regionAt() + function to determine the region containing a given point. The + QDecoration class also provides the DecorationState enum + describing the state of a given region, e.g. whether it is active + or not. + + In addition, it is possible to build the system menu for a given + top level widget using the buildSysMenu() function; whenever an + action in this menu is triggered, the menuTriggered() function is + called automatically. + + Finally, the QDecoration class provides a couple of static + functions, startMove() and startResize(), which start a move or + resize action by making the appropriate decoration region active + and grabbing the mouse input. + + \sa QDecorationFactory, QDecorationPlugin, {Qt for Embedded Linux + Architecture} +*/ + +/*! + \fn QDecoration::QDecoration() + + Constructs a decoration object. +*/ + +/*! + \fn QDecoration::~QDecoration() + + Destroys this decoration object. +*/ + +/*! + \enum QDecoration::DecorationRegion + + This enum describes the various regions of the window decoration. + + \value All The entire region used by the window decoration. + + \value Top The top border used to vertically resize the window. + \value Bottom The bottom border used to vertically resize the window. + \value Left The left border used to horizontally resize the window. + \value Right The right border used to horizontally resize the window. + \value TopLeft The top-left corner of the window used to resize the + window both horizontally and vertically. + \value TopRight The top-right corner of the window used to resize the + window both horizontally and vertically. + \value BottomLeft The bottom-left corner of the window used to resize the + window both horizontally and vertically. + \value BottomRight The bottom-right corner of the window used to resize the + window both horizontally and vertically. + \value Borders All the regions used to describe the window's borders. + + \value Title The region containing the window title, used + to move the window by dragging with the mouse cursor. + \value Close The region occupied by the close button. Clicking in this + region closes the window. + \value Minimize The region occupied by the minimize button. Clicking in + this region minimizes the window. + \value Maximize The region occupied by the maximize button. Clicking in + this region maximizes the window. + \value Normalize The region occupied by a button used to restore a window's + normal size. Clicking in this region restores a maximized + window to its previous size. The region used for this + button is often also the Maximize region. + \value Menu The region occupied by the window's menu button. Clicking + in this region opens the window operations (system) menu. + \value Help The region occupied by the window's help button. Clicking + in this region causes the context-sensitive help function + to be enabled. + \value Resize The region used to resize the window. + \value Move The region used to move the window. + \value None No region. + + \sa region(), regionAt(), DecorationState +*/ + +/*! + \enum QDecoration::DecorationState + + This enum describes the various states of a decoration region. + + \value Normal The region is active + \value Disabled The region is inactive. + \value Hover The cursor is hovering over the region. + \value Pressed The region is pressed. + + \sa paint(), DecorationRegion +*/ + +/*! + \fn QRegion QDecoration::region(const QWidget *widget, const QRect & rectangle, int decorationRegion) + + Implement this function to return the region specified by \a + decorationRegion for the given top level \a widget. + + The \a rectangle parameter specifies the rectangle the decoration + is wrapped around. The \a decorationRegion is a bitmask of the + values described by the DecorationRegion enum. + + \sa regionAt(), paint() +*/ + +/*! + \fn QRegion QDecoration::region(const QWidget *widget, int decorationRegion) + \overload +*/ + +/*! + \fn bool QDecoration::paint(QPainter *painter, const QWidget *widget, int decorationRegion, + DecorationState state) + + Implement this function to paint the border and title decoration + for the specified top level \a widget using the given \a painter + and decoration \a state. The specified \a decorationRegion is a + bitmask of the values described by the DecorationRegion enum. + + Note that \l{Qt for Embedded Linux} expects this function to return true if + any of the widget's decorations are repainted; otherwise it should + return false. + + \sa region() +*/ + +/*! + \fn int QDecoration::regionAt(const QWidget *widget, const QPoint &point) + + Returns the type of the first region of the specified top level \a + widget containing the given \a point. + + The return value is one of the DecorationRegion enum's values. Use + the region() function to retrieve the actual region. If none of + the widget's regions contain the point, this function returns \l + None. + + \sa region() +*/ +int QDecoration::regionAt(const QWidget *w, const QPoint &point) +{ + int regions[] = { + TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight, // Borders first + Menu, Title, Help, Minimize, Normalize, Maximize, Close, // then buttons + None + }; + +// char *regions_str[] = { +// "TopLeft", "Top", "TopRight", "Left", "Right", "BottomLeft", "Bottom", "BottomRight", +// "Menu", "Title", "Help", "Minimize", "Normalize", "Maximize", "Close", +// "None" +// }; + + // First check to see if within all regions at all + QRegion reg = region(w, w->geometry(), All); + if (!reg.contains(point)) { + return None; + } + + int i = 0; + while (regions[i]) { + reg = region(w, w->geometry(), regions[i]); + if (reg.contains(point)) { +// qDebug("In region %s", regions_str[i]); + return regions[i]; + } + ++i; + } + return None; +} + +#ifndef QT_NO_MENU +/*! + Builds the system menu for the given top level \a widget, adding + \gui Restore, \gui Move, \gui Size, \gui Minimize, \gui Maximize + and \gui Close actions to the given \a menu. + + \sa menuTriggered() +*/ +void QDecoration::buildSysMenu(QWidget *widget, QMenu *menu) +{ + QDecorationAction *act = new QDecorationAction(QLatin1String("Restore"), + menu, Maximize); + act->setEnabled(widget->windowState() & Qt::WindowMaximized); + menu->addAction(act); + act = new QDecorationAction(QLatin1String("Move"), menu, Move); + act->setEnabled(!(widget->windowState() & Qt::WindowMaximized)); + menu->addAction(act); + menu->addAction(new QDecorationAction(QLatin1String("Size"), menu, Resize)); + act = new QDecorationAction(QLatin1String("Minimize"), menu, Minimize); + menu->addAction(act); + act = new QDecorationAction(QLatin1String("Maximize"), menu, Maximize); + act->setDisabled(widget->windowState() & Qt::WindowMaximized); + menu->addAction(act); + menu->addSeparator(); + menu->addAction(new QDecorationAction(QLatin1String("Close"), menu, Close)); +} + +/*! + This function is called whenever an action in a top level widget's + menu is triggered, and simply calls the regionClicked() function + passing the \a widget and \a action parameters as arguments. + + \sa buildSysMenu() +*/ +void QDecoration::menuTriggered(QWidget *widget, QAction *action) +{ + QDecorationAction *decAction = static_cast(action); + regionClicked(widget, decAction->reg); +} +#endif // QT_NO_MENU + +/*! + \fn void QDecoration::regionClicked(QWidget *widget, int region) + + Handles the event that the specified \a region in the given top + level \a widget is activated by a single click (the \a region + parameter is described using the DecorationRegion enum). + + This function is called whenever a region in a top level widget is + clicked; the default implementation responds to clicks on items in + the system menu, performing the requested actions. + + \sa regionDoubleClicked(), region() +*/ +void QDecoration::regionClicked(QWidget *widget, int reg) +{ + switch(reg) { + case Move: + startMove(widget); + break; + case Resize: + startResize(widget); + break; + case Help: +#ifndef QT_NO_WHATSTHIS + if (QWhatsThis::inWhatsThisMode()) + QWhatsThis::leaveWhatsThisMode(); + else + QWhatsThis::enterWhatsThisMode(); +#endif + break; + case Close: + widget->close(); + break; + case Normalize: + widget->showNormal(); + break; + case Maximize: + if (widget->windowState() & Qt::WindowMaximized) + widget->showNormal(); + else + widget->showMaximized(); + break; + } +} + +/*! + \fn void QDecoration::regionDoubleClicked(QWidget *widget, int region) + + Handles the event that the specified \a region in the given top + level \a widget is activated by a double click (the region + parameter is described using the DecorationRegion enum). + + This function is called whenever a region in a top level widget is + double clicked; the default implementation responds to a double + click on the widget's title, toggling its size between the maximum + and its normal size. + + \sa regionClicked(), region() +*/ +void QDecoration::regionDoubleClicked(QWidget *widget, int reg) +{ + switch(reg) + { + case Title: { + if (widget->windowState() & Qt::WindowMaximized) + widget->showNormal(); + else + widget->showMaximized(); + break; + } + } +} + +/*! + Starts to move the given top level \a widget by making its \l + Title region active and grabbing the mouse input. + + \sa startResize() +*/ +void QDecoration::startMove(QWidget *widget) +{ +#ifdef QT_NO_QWS_MANAGER + Q_UNUSED(widget); +#else + QWSManager *manager = widget->d_func()->topData()->qwsManager; + if (manager) + manager->startMove(); +#endif +} + +/*! + Starts to resize the given top level \a widget by making its \l + BottomRight region active and grabbing the mouse input. + + \sa startMove() +*/ +void QDecoration::startResize(QWidget *widget) +{ +#ifdef QT_NO_QWS_MANAGER + Q_UNUSED(widget); +#else + QWSManager *manager = widget->d_func()->topData()->qwsManager; + if (manager) + manager->startResize(); +#endif +} + + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecoration_qws.h b/src/gui/embedded/qdecoration_qws.h new file mode 100644 index 0000000000..979e15f07d --- /dev/null +++ b/src/gui/embedded/qdecoration_qws.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATION_QWS_H +#define QDECORATION_QWS_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPopupMenu; +class QMenu; + +#ifndef QT_NO_ACTION +class QDecorationAction : public QAction +{ +public: + QDecorationAction(const QString &text, QObject* parent, int region) + : QAction(text, parent), reg(region) {} + int reg; +}; +#endif // QT_NO_ACTION + +/* + Implements decoration styles +*/ +class Q_GUI_EXPORT QDecoration +{ +public: + QDecoration() {} + virtual ~QDecoration() {} + + /* AABBBBBBBBBBCC Items in DecorationRegion: + AijjjjjjjklmnC + A C A = TopLeft B = Top C = TopRight + D E D = Left E = Right + D E F = BottomLeft H = Bottom G = BottomRight + F G i = Menu j = Title k = Help + FFHHHHHHHHHHGG l = Minimize m = Maximize n = Close + + */ + + enum DecorationRegion { + None = 0x0000000000, All = 0x7fffffff, + TopLeft = 0x0000000001, Top = 0x0000000002, TopRight = 0x0000000004, + Left = 0x0000000008, Right = 0x0000000010, + BottomLeft = 0x0000000020, Bottom = 0x0000000040, BottomRight = 0x0000000080, + Borders = 0x00000000ff, + Menu = 0x0000000100, Title = 0x0000000200, Help = 0x0000000400, + Minimize = 0x0000000800, Maximize = 0x0000001000, Normalize = 0x0000002000, + Close = 0x0000004000, Move = 0x0000008000, Resize = 0x0000010000 + }; + + enum DecorationState { Normal = 0x04, Disabled = 0x08, Hover = 0x01, Pressed = 0x02 }; + + virtual QRegion region(const QWidget *w, const QRect &rect, int decorationRegion = All ) = 0; + QRegion region(const QWidget *w, int decorationRegion = All ) + { return region(w, w->rect(), decorationRegion); } + virtual int regionAt(const QWidget *w, const QPoint &point); + + virtual void regionClicked(QWidget *widget, int region); + virtual void regionDoubleClicked(QWidget *widget, int region); +#ifndef QT_NO_MENU + virtual void buildSysMenu(QWidget *widget, QMenu *menu); + void menuTriggered(QWidget *widget, QAction *action); +#endif + + static void startMove(QWidget *widget); + static void startResize(QWidget *widget); + + virtual bool paint(QPainter *p, const QWidget *w, int decorationRegion = All, + DecorationState state = Normal) = 0; + +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDECORATION_QWS_H diff --git a/src/gui/embedded/qdecorationdefault_qws.cpp b/src/gui/embedded/qdecorationdefault_qws.cpp new file mode 100644 index 0000000000..b87ac3223d --- /dev/null +++ b/src/gui/embedded/qdecorationdefault_qws.cpp @@ -0,0 +1,803 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include "qdecorationdefault_qws.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_QWS_DECORATION_DEFAULT) || defined(QT_PLUGIN) + +QPixmap *QDecorationDefault::staticHelpPixmap = 0; +QPixmap *QDecorationDefault::staticMenuPixmap = 0; +QPixmap *QDecorationDefault::staticClosePixmap = 0; +QPixmap *QDecorationDefault::staticMinimizePixmap = 0; +QPixmap *QDecorationDefault::staticMaximizePixmap = 0; +QPixmap *QDecorationDefault::staticNormalizePixmap = 0; + +#ifndef QT_NO_IMAGEFORMAT_XPM + +/* XPM */ +static const char * const default_menu_xpm[] = { +/* width height ncolors chars_per_pixel */ +"16 16 11 1", +/* colors */ +" c #000000", +". c #336600", +"X c #666600", +"o c #99CC00", +"O c #999933", +"+ c #333300", +"@ c #669900", +"# c #999900", +"$ c #336633", +"% c #666633", +"& c #99CC33", +/* pixels */ +"oooooooooooooooo", +"oooooooooooooooo", +"ooooo#.++X#ooooo", +"ooooX Xoooo", +"oooX XO#% X&oo", +"oo# Ooo&@O Ooo", +"oo. Xoo#+ @X Xoo", +"oo+ OoO+ +O# +oo", +"oo+ #O+ +## +oo", +"oo. %@ ++ +. Xoo", +"oo# O@OO+ #oo", +"oooX X##$ Ooo", +"ooooX Xoo", +"oooo&OX++X#OXooo", +"oooooooooooooooo", +"oooooooooooooooo" +}; + +static const char * const default_help_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #707070", +" ", +" ", +" ...... ", +" ..XXXXXX ", +" .XX .XX ", +" .XX .XX ", +" ..XX ", +" ..XX ", +" ..XX ", +" .XX ", +" .XX ", +" .. ", +" .XX ", +" .XX ", +" ", +" "}; + +static const char * const default_close_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #707070", +" ", +" ", +" .X .X ", +" .XX .XX ", +" .XX .XX ", +" .XX .XX ", +" .XX.XX ", +" .XXX ", +" .XXX ", +" .XX.XX ", +" .XX .XX ", +" .XX .XX ", +" .XX .XX ", +" .X .X ", +" ", +" "}; + +static const char * const default_maximize_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #707070", +" ", +" ", +" ........... ", +" .XXXXXXXXXX ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X........X ", +" .XXXXXXXXXX ", +" ", +" ", +" "}; + +static const char * const default_minimize_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #707070", +" ", +" ", +" ", +" ", +" ", +" ", +" ... ", +" . X ", +" .XX ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; + +static const char * const default_normalize_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #707070", +" ", +" ", +" ........ ", +" .XXXXXXXX ", +" .X .X ", +" .X .X ", +" ....X... .X ", +" .XXXXXXXX .X ", +" .X .XXXX ", +" .X .X ", +" .X .X ", +" .X......X ", +" .XXXXXXXX ", +" ", +" ", +" "}; + +#endif // QT_NO_IMAGEFORMAT_XPM + +/*! + \class QDecorationDefault + \since 4.4 + \ingroup qws + \brief The QDecorationDefault class is a base class providing default window decorations. + + See the documentation for class QDecoration for a detailed + description. This subclass of QDecoration provides standard + icons for the decoration regions. + + Note that this class is non-portable and only available in + \l{Qt for Embedded Linux}. + */ + +/*! + Default constructor. + */ +QDecorationDefault::QDecorationDefault() + : QDecoration() +{ + menu_width = 20; + help_width = 20; + close_width = 20; + minimize_width = 20; + maximize_width = 20; + normalize_width = 20; +} + +/*! + The constructor deletes the static pixmaps. + */ +QDecorationDefault::~QDecorationDefault() +{ + delete staticMenuPixmap; + delete staticClosePixmap; + delete staticMinimizePixmap; + delete staticMaximizePixmap; + delete staticNormalizePixmap; + + // This makes it safe to delete and then create a QDecorationDefault + staticMenuPixmap = 0; + staticClosePixmap = 0; + staticMinimizePixmap = 0; + staticMaximizePixmap = 0; + staticNormalizePixmap = 0; +} + +/*! + \fn const char **QDecorationDefault::xpmForRegion(int region) + + Returns a pointer to the X pixmap for the icon specified by + \a region. An X pixmap is an ASCII-text-based image. The value + of \a region must be one of a subset of the values of enum + DecorationRegion. The supported values are \e Help, \e Menu, + \e Close, \e Minimize, \e Maximize, and \e Normalize. Other + values of \a region cause zero to be returned. + + \sa QDecoration::DecorationRegion + */ +const char **QDecorationDefault::xpmForRegion(int reg) +{ +#ifdef QT_NO_IMAGEFORMAT_XPM + Q_UNUSED(reg); +#else + switch(reg) + { + case Help: + return (const char **)default_help_xpm; + case Menu: + return (const char **)default_menu_xpm; + case Close: + return (const char **)default_close_xpm; + case Minimize: + return (const char **)default_minimize_xpm; + case Maximize: + return (const char **)default_maximize_xpm; + case Normalize: + return (const char **)default_normalize_xpm; + } +#endif + return 0; +} + +/*! + \fn QPixmap QDecorationDefault::pixmapFor(const QWidget *widget, + int decorationRegion, int &xoff, int &yoff) + + Returns a pointer to the QPixmap for the widget specified by \a widget and + \a decorationRegion. The returned QPixmap is constructed from the default + X pixmap obtained from xpmForRegion(). + + \a xoff and \a yoff specify the offset for the pixmap. + + The value of \a decorationRegion must be one of a subset of the values + of enum DecorationRegion. The supported values are \e Help, + \e Menu, \e Close, \e Minimize, \e Maximize, and \e Normalize. + Other values of \a decorationRegion return 0. + + \sa QDecoration::DecorationRegion +*/ +QPixmap QDecorationDefault::pixmapFor(const QWidget *widget, + int decorationRegion, + int &xoff, + int &/*yoff*/) +{ +#ifdef QT_NO_IMAGEFORMAT_XPM + Q_UNUSED(widget); + Q_UNUSED(decorationRegion); + Q_UNUSED(xoff); + return QPixmap(); +#else + static const char **staticHelpPixmapXPM = 0; + static const char **staticMenuPixmapXPM = 0; + static const char **staticClosePixmapXPM = 0; + static const char **staticMinimizePixmapXPM = 0; + static const char **staticMaximizePixmapXPM = 0; + static const char **staticNormalizePixmapXPM = 0; + const char **xpm; + + // Why don't we just use/extend the enum type... + + if (staticHelpPixmapXPM != (xpm = xpmForRegion(Help)) || !staticHelpPixmap) { + staticHelpPixmapXPM = xpm; + staticHelpPixmap = new QPixmap(xpm); + } + if (staticMenuPixmapXPM != (xpm = xpmForRegion(Menu)) || !staticMenuPixmap) { + staticMenuPixmapXPM = xpm; + staticMenuPixmap = new QPixmap(xpm); + } + if (staticClosePixmapXPM != (xpm = xpmForRegion(Close)) || !staticClosePixmap) { + staticClosePixmapXPM = xpm; + staticClosePixmap = new QPixmap(xpm); + } + if (staticMinimizePixmapXPM != (xpm = xpmForRegion(Minimize)) || !staticMinimizePixmap) { + staticMinimizePixmapXPM = xpm; + staticMinimizePixmap = new QPixmap(xpm); + } + if (staticMaximizePixmapXPM != (xpm = xpmForRegion(Maximize)) || !staticMaximizePixmap) { + staticMaximizePixmapXPM = xpm; + staticMaximizePixmap = new QPixmap(xpm); + } + if (staticNormalizePixmapXPM != (xpm = xpmForRegion(Normalize)) || !staticNormalizePixmap) { + staticNormalizePixmapXPM = xpm; + staticNormalizePixmap = new QPixmap(xpm); + } + + const QPixmap *pm = 0; + + switch (decorationRegion) { + case Help: + pm = staticHelpPixmap; + break; + case Menu: + if (!widget->windowIcon().isNull()) + return widget->windowIcon().pixmap(16,16); //##### QIcon::pixmap() needs a size !!!!!!" + if (!pm) { + xoff = 1; + pm = staticMenuPixmap; + } + break; + case Close: + pm = staticClosePixmap; + break; + case Maximize: + pm = staticMaximizePixmap; + break; + case Normalize: + pm = staticNormalizePixmap; + break; + case Minimize: + pm = staticMinimizePixmap; + break; + default: + break; + } + return *pm; +#endif +} + +/*! + \fn int QDecorationDefault::titleBarHeight(const QWidget *widget) + + Returns the title bar height in pixels for the given \a widget. It is the + greater of 20, or the sum of the application font's line spacing value + plus a border width fudge factor. +*/ +int QDecorationDefault::titleBarHeight(const QWidget *) +{ + return qMax(20, QApplication::fontMetrics().height() + BORDER_WIDTH); +} + +/*! + Returns the region specified by \a decorationRegion for the + top-level \a widget. \a rect specifies the rectangle the decoration + wraps. The value of \a decorationRegion is a combination of the + bitmask values of enum DecorationRegion. + */ +QRegion QDecorationDefault::region(const QWidget *widget, + const QRect &rect, + int decorationRegion) +{ + Qt::WindowFlags flags = widget->windowFlags(); + bool hasBorder = !widget->isMaximized(); + bool hasTitle = flags & Qt::WindowTitleHint; + bool hasSysMenu = flags & Qt::WindowSystemMenuHint; + bool hasContextHelp = flags & Qt::WindowContextHelpButtonHint; + bool hasMinimize = flags & Qt::WindowMinimizeButtonHint; + bool hasMaximize = flags & Qt::WindowMaximizeButtonHint; + int state = widget->windowState(); + bool isMinimized = state & Qt::WindowMinimized; + bool isMaximized = state & Qt::WindowMaximized; + + int titleHeight = hasTitle ? titleBarHeight(widget) : 0; + int bw = hasBorder ? BORDER_WIDTH : 0; + int bbw = hasBorder ? BOTTOM_BORDER_WIDTH : 0; + + QRegion region; + switch (decorationRegion) { + case All: { + QRect r(rect.left() - bw, + rect.top() - titleHeight - bw, + rect.width() + 2 * bw, + rect.height() + titleHeight + bw + bbw); + region = r; + region -= rect; + } + break; + + case Title: { + QRect r(rect.left() + + (hasSysMenu ? menu_width : 0), + rect.top() - titleHeight, + rect.width() + - (hasSysMenu ? menu_width : 0) + - close_width + - (hasMaximize ? maximize_width : 0) + - (hasMinimize ? minimize_width : 0) + - (hasContextHelp ? help_width : 0), + + titleHeight); + if (r.width() > 0) + region = r; + } + break; + + case Top: { + QRect r(rect.left() + CORNER_GRAB, + rect.top() - titleHeight - bw, + rect.width() - 2 * CORNER_GRAB, + bw); + region = r; + } + break; + + case Left: { + QRect r(rect.left() - bw, + rect.top() - titleHeight + CORNER_GRAB, + bw, + rect.height() + titleHeight - 2 * CORNER_GRAB); + region = r; + } + break; + + case Right: { + QRect r(rect.right() + 1, + rect.top() - titleHeight + CORNER_GRAB, + bw, + rect.height() + titleHeight - 2 * CORNER_GRAB); + region = r; + } + break; + + case Bottom: { + QRect r(rect.left() + CORNER_GRAB, + rect.bottom() + 1, + rect.width() - 2 * CORNER_GRAB, + bw); + region = r; + } + break; + + case TopLeft: { + QRect r1(rect.left() - bw, + rect.top() - bw - titleHeight, + CORNER_GRAB + bw, + bw); + + QRect r2(rect.left() - bw, + rect.top() - bw - titleHeight, + bw, + CORNER_GRAB + bw); + + region = QRegion(r1) + r2; + } + break; + + case TopRight: { + QRect r1(rect.right() - CORNER_GRAB, + rect.top() - bw - titleHeight, + CORNER_GRAB + bw, + bw); + + QRect r2(rect.right() + 1, + rect.top() - bw - titleHeight, + bw, + CORNER_GRAB + bw); + + region = QRegion(r1) + r2; + } + break; + + case BottomLeft: { + QRect r1(rect.left() - bw, + rect.bottom() + 1, + CORNER_GRAB + bw, + bw); + + QRect r2(rect.left() - bw, + rect.bottom() - CORNER_GRAB, + bw, + CORNER_GRAB + bw); + region = QRegion(r1) + r2; + } + break; + + case BottomRight: { + QRect r1(rect.right() - CORNER_GRAB, + rect.bottom() + 1, + CORNER_GRAB + bw, + bw); + + QRect r2(rect.right() + 1, + rect.bottom() - CORNER_GRAB, + bw, + CORNER_GRAB + bw); + region = QRegion(r1) + r2; + } + break; + + case Menu: { + if (hasSysMenu) { + region = QRect(rect.left(), rect.top() - titleHeight, + menu_width, titleHeight); + } + } + break; + + case Help: { + if (hasContextHelp) { + QRect r(rect.right() + - close_width + - (hasMaximize ? maximize_width : 0) + - (hasMinimize ? minimize_width : 0) + - help_width + 1, rect.top() - titleHeight, + help_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + + case Minimize: { + if (hasMinimize && !isMinimized) { + QRect r(rect.right() - close_width + - (hasMaximize ? maximize_width : 0) + - minimize_width + 1, rect.top() - titleHeight, + minimize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Maximize: { + if (hasMaximize && !isMaximized) { + QRect r(rect.right() - close_width - maximize_width + 1, + rect.top() - titleHeight, maximize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Normalize: { + if (hasMinimize && isMinimized) { + QRect r(rect.right() - close_width + - (hasMaximize ? maximize_width : 0) + - minimize_width + 1, rect.top() - titleHeight, + minimize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } else if (hasMaximize && isMaximized) { + QRect r(rect.right() - close_width - maximize_width + 1, + rect.top() - titleHeight, maximize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Close: { + QRect r(rect.right() - close_width + 1, rect.top() - titleHeight, + close_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + break; + + default: { + int i = 1; + while (i) { + if (i & decorationRegion) + region += this->region(widget, rect, i); + i <<= 1; + } + } + break; + } + + return region; +} + +/*! + Paints the border and title decoration for the top-level \a widget + using the \a painter provided and the decoration \a state. The value + of \a decorationRegion is a combination of the bitmask values of + enum DecorationRegion. + + Note that Qt for Embedded Linux expects this function to return true if any of + the widget's decorations are repainted; otherwise it returns false. + */ +bool QDecorationDefault::paint(QPainter *painter, + const QWidget *widget, + int decorationRegion, + DecorationState state) +{ + if (decorationRegion == None) + return false; + + const QRect titleRect = QDecoration::region(widget, Title).boundingRect(); + const QPalette pal = QApplication::palette(); + int titleHeight = titleRect.height(); + int titleWidth = titleRect.width(); + QRegion oldClipRegion = painter->clipRegion(); + + + Qt::WindowFlags flags = widget->windowFlags(); + bool hasBorder = !widget->isMaximized(); + bool hasTitle = flags & Qt::WindowTitleHint; + bool hasSysMenu = flags & Qt::WindowSystemMenuHint; + bool hasContextHelp = flags & Qt::WindowContextHelpButtonHint; + bool hasMinimize = flags & Qt::WindowMinimizeButtonHint; + bool hasMaximize = flags & Qt::WindowMaximizeButtonHint; + + bool paintAll = (decorationRegion == int(All)); + bool handled = false; + + bool porterDuff = painter->paintEngine()->hasFeature(QPaintEngine::PorterDuff); + + if ((paintAll || decorationRegion & Borders) && state == Normal && hasBorder) { + if (hasTitle) { // reduce flicker + QRect rect(widget->rect()); + QRect r(rect.left(), rect.top() - titleHeight, + rect.width(), titleHeight); + painter->setClipRegion(oldClipRegion - r); + } + QRect br = QDecoration::region(widget).boundingRect(); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_Source); + qDrawWinPanel(painter, br.x(), br.y(), br.width(), + br.height(), pal, false, + &pal.brush(QPalette::Window)); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + handled |= true; + } + + if ((paintAll || decorationRegion & Title && titleWidth > 0) && state == Normal && hasTitle) { + painter->setClipRegion(oldClipRegion); + QBrush titleBrush; + QPen titlePen; + + if (widget == qApp->activeWindow()) { + titleBrush = pal.brush(QPalette::Highlight); + titlePen = pal.color(QPalette::HighlightedText); + } else { + titleBrush = pal.brush(QPalette::Window); + titlePen = pal.color(QPalette::Text); + } + + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_Source); + qDrawShadePanel(painter, + titleRect.x(), titleRect.y(), titleRect.width(), titleRect.height(), + pal, true, 1, &titleBrush); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + + painter->setPen(titlePen); + painter->drawText(titleRect.x() + 4, titleRect.y(), + titleRect.width() - 8, titleRect.height(), + Qt::AlignVCenter, windowTitleFor(widget)); + handled |= true; + } + + if (state != Hover) { + painter->setClipRegion(oldClipRegion); + if ((paintAll || decorationRegion & Menu) && hasSysMenu) { + paintButton(painter, widget, Menu, state, pal); + handled |= true; + } + + if ((paintAll || decorationRegion & Help) && hasContextHelp) { + paintButton(painter, widget, Help, state, pal); + handled |= true; + } + + if ((paintAll || decorationRegion & Minimize) && hasMinimize) { + paintButton(painter, widget, Minimize, state, pal); + handled |= true; + } + + if ((paintAll || decorationRegion & Maximize) && hasMaximize) { + paintButton(painter, widget, + ((widget->windowState() & Qt::WindowMaximized)? Normalize : Maximize), + state, pal); + handled |= true; + } + + if (paintAll || decorationRegion & Close) { + paintButton(painter, widget, Close, state, pal); + handled |= true; + } + } + return handled; +} + +/*! + \fn void QDecorationDefault::paintButton(QPainter *painter, const + QWidget *widget, int buttonRegion, DecorationState state, + const QPalette &palette) + + Paints a region of the top-level \a widget. The region is + painted in the specified decoration \a state using the + \a painter and \a palette provided. The region to be painted is specified + by \a buttonRegion, which is a combination of the bitmask values of + DecorationRegion. If the value of \a buttonRegion is one of \e Help, + \e Menu, \e Close, \e Minimize, \e Maximize, and \e Normalize, the + button pixmap for that region is painted. + + \sa pixmapFor() + */ +void QDecorationDefault::paintButton(QPainter *painter, + const QWidget *widget, + int buttonRegion, + DecorationState state, + const QPalette &pal) +{ + int xoff = 2; + int yoff = 2; + + const QPixmap pm = pixmapFor(widget, buttonRegion, xoff, yoff); + QRect brect(QDecoration::region(widget, buttonRegion).boundingRect()); + bool porterDuff = painter->paintEngine()->hasFeature(QPaintEngine::PorterDuff); + + if (state & QDecoration::Pressed) { + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_Source); + qDrawWinPanel(painter, brect, pal, true, &pal.brush(QPalette::Window)); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + ++xoff; + ++yoff; + } else { + painter->fillRect(brect, pal.brush(QPalette::Window)); + } + + if (!pm.isNull()) + painter->drawPixmap(brect.x() + xoff, brect.y() + yoff, pm); +} + +extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); + +/*! + \internal + */ +QString QDecorationDefault::windowTitleFor(const QWidget *widget) const +{ + return qt_setWindowTitle_helperHelper(widget->windowTitle(), widget); +} + +#endif // QT_NO_QWS_DECORATION_DEFAULT + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecorationdefault_qws.h b/src/gui/embedded/qdecorationdefault_qws.h new file mode 100644 index 0000000000..a166220913 --- /dev/null +++ b/src/gui/embedded/qdecorationdefault_qws.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATIONDEFAULT_QWS_H +#define QDECORATIONDEFAULT_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_QWS_DECORATION_DEFAULT) || defined(QT_PLUGIN) + +#define CORNER_GRAB 16 +#define BORDER_WIDTH 4 +#define BOTTOM_BORDER_WIDTH BORDER_WIDTH + +class Q_GUI_EXPORT QDecorationDefault : public QDecoration +{ +public: + QDecorationDefault(); + virtual ~QDecorationDefault(); + + virtual QRegion region(const QWidget *widget, const QRect &rect, int decorationRegion = All); + virtual bool paint(QPainter *painter, const QWidget *widget, int decorationRegion = All, + DecorationState state = Normal); + +protected: + virtual int titleBarHeight(const QWidget *widget); + + virtual void paintButton(QPainter *painter, const QWidget *widget, int buttonRegion, + DecorationState state, const QPalette &pal); + virtual QPixmap pixmapFor(const QWidget *widget, int decorationRegion, int &xoff, int &yoff); + virtual const char **xpmForRegion(int region); + + QString windowTitleFor(const QWidget *widget) const; + + int menu_width; + int help_width; + int close_width; + int minimize_width; + int maximize_width; + int normalize_width; + +private: + static QPixmap *staticHelpPixmap; + static QPixmap *staticMenuPixmap; + static QPixmap *staticClosePixmap; + static QPixmap *staticMinimizePixmap; + static QPixmap *staticMaximizePixmap; + static QPixmap *staticNormalizePixmap; + +}; + + +QT_END_NAMESPACE +#endif // QT_NO_QWS_DECORATION_DEFAULT +QT_END_HEADER + +#endif // QDECORATIONDEFAULT_QWS_H diff --git a/src/gui/embedded/qdecorationfactory_qws.cpp b/src/gui/embedded/qdecorationfactory_qws.cpp new file mode 100644 index 0000000000..2cae140ac0 --- /dev/null +++ b/src/gui/embedded/qdecorationfactory_qws.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdecorationfactory_qws.h" +#include "qdecorationplugin_qws.h" +#include "private/qfactoryloader_p.h" +#include "qmutex.h" + +#include "qapplication.h" +#include "qdecorationdefault_qws.h" +#include "qdecorationwindows_qws.h" +#include "qdecorationstyled_qws.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QDecorationFactoryInterface_iid, + QLatin1String("/decorations"), Qt::CaseInsensitive)) +#endif + + + +/*! + \class QDecorationFactory + \ingroup qws + \ingroup appearance + + \brief The QDecorationFactory class creates window decorations in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QDecorationFactory is used to detect and instantiate the available + decorations, allowing \l{Qt for Embedded Linux} to load the preferred + decoration into the application at runtime. The create() function + returns a QDecoration object representing the decoration + identified by a given key. The valid keys (i.e. the supported + decorations) can be retrieved using the keys() function. + + \l{Qt for Embedded Linux} provides three built-in decorations: \c Default, + \c Styled and \c Windows. In addition, custom decorations can be + added using Qt's \l {How to Create Qt Plugins}{plugin mechanism}, + i.e. by subclassing the QDecoration class and creating a mouse + driver plugin (QDecorationPlugin). + + \sa QDecoration, QDecorationPlugin +*/ + +/*! + Creates the decoration specified by the given \a key. Note that + the keys are case-insensitive. + + \sa keys() +*/ + +QDecoration *QDecorationFactory::create(const QString& key) +{ + QDecoration *ret = 0; + QString decoration = key.toLower(); +#ifndef QT_NO_QWS_DECORATION_DEFAULT + if (decoration == QLatin1String("default")) + ret = new QDecorationDefault; + else +#endif +#ifndef QT_NO_QWS_DECORATION_WINDOWS + if (decoration == QLatin1String("windows")) + ret = new QDecorationWindows; + else +#endif +#ifndef QT_NO_QWS_DECORATION_STYLED + if (decoration == QLatin1String("styled")) + ret = new QDecorationStyled; + else +#endif + { } // Keep these here - they make the #ifdefery above work +#ifndef QT_NO_LIBRARY + if (!ret) { + if (QDecorationFactoryInterface *factory = qobject_cast(loader()->instance(decoration))) { + ret = factory->create(decoration); + } + } +#endif + return ret; +} + +/*! + Returns the list of valid keys, i.e., the available decorations. + + \sa create() +*/ +QStringList QDecorationFactory::keys() +{ + QStringList list; +#ifndef QT_NO_QWS_DECORATION_STYLED + list << QLatin1String("Styled"); +#endif +#ifndef QT_NO_QWS_DECORATION_DEFAULT + list << QLatin1String("Default"); +#endif +#ifndef QT_NO_QWS_DECORATION_WINDOWS + list << QLatin1String("Windows"); +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + QStringList plugins = loader()->keys(); + for (int i = 0; i < plugins.size(); ++i) { + if (!list.contains(plugins.at(i))) + list += plugins.at(i); + } +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + + return list; +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecorationfactory_qws.h b/src/gui/embedded/qdecorationfactory_qws.h new file mode 100644 index 0000000000..c5995eeb02 --- /dev/null +++ b/src/gui/embedded/qdecorationfactory_qws.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATIONFACTORY_QWS_H +#define QDECORATIONFACTORY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QDecoration; + +class Q_GUI_EXPORT QDecorationFactory +{ +public: + static QStringList keys(); + static QDecoration *create(const QString&); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDECORATIONFACTORY_QWS_H diff --git a/src/gui/embedded/qdecorationplugin_qws.cpp b/src/gui/embedded/qdecorationplugin_qws.cpp new file mode 100644 index 0000000000..869243151f --- /dev/null +++ b/src/gui/embedded/qdecorationplugin_qws.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdecorationplugin_qws.h" +#include "qdecoration_qws.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDecorationPlugin + \ingroup qws + \ingroup plugins + + \brief The QDecorationPlugin class is an abstract base class for + window decoration plugins in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides three ready-made decoration styles: \c + Default, \c Styled and \c Windows. Custom decorations can be + implemented by subclassing the QDecoration class and creating a + decoration plugin. + + A decoration plugin can be created by subclassing + QDecorationPlugin and implementing the pure virtual keys() and + create() functions. By exporting the derived class using the + Q_EXPORT_PLUGIN2() macro, the default implementation of the + QDecorationFactory class will automatically detect the plugin and + load the driver into the application at run-time. See \l{How to + Create Qt Plugins} for details. + + To actually apply a decoration, use the + QApplication::qwsSetDecoration() function. + + \sa QDecoration, QDecorationFactory +*/ + +/*! + \fn QStringList QDecorationPlugin::keys() const + + Returns the list of valid keys, i.e., the decorations supported by + this plugin. + + \sa create() +*/ + +/*! + \fn QDecoration *QDecorationPlugin::create(const QString &key) + + Creates a decoration matching the given \a key. Note that keys are + case-insensitive. + + \sa keys() +*/ + +/*! + Constructs a decoration plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QDecorationPlugin::QDecorationPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the decoration plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QDecorationPlugin::~QDecorationPlugin() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecorationplugin_qws.h b/src/gui/embedded/qdecorationplugin_qws.h new file mode 100644 index 0000000000..4fa97f36f1 --- /dev/null +++ b/src/gui/embedded/qdecorationplugin_qws.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATIONPLUGIN_QWS_H +#define QDECORATIONPLUGIN_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QDecoration; + +struct Q_GUI_EXPORT QDecorationFactoryInterface : public QFactoryInterface +{ + virtual QDecoration *create(const QString &key) = 0; +}; + +#define QDecorationFactoryInterface_iid "com.trolltech.Qt.QDecorationFactoryInterface" +Q_DECLARE_INTERFACE(QDecorationFactoryInterface, QDecorationFactoryInterface_iid) + +class Q_GUI_EXPORT QDecorationPlugin : public QObject, public QDecorationFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QDecorationFactoryInterface:QFactoryInterface) + public: + explicit QDecorationPlugin(QObject *parent = 0); + ~QDecorationPlugin(); + + virtual QStringList keys() const = 0; + virtual QDecoration *create(const QString &key) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDECORATIONPLUGIN_QWS_H diff --git a/src/gui/embedded/qdecorationstyled_qws.cpp b/src/gui/embedded/qdecorationstyled_qws.cpp new file mode 100644 index 0000000000..40da4c2583 --- /dev/null +++ b/src/gui/embedded/qdecorationstyled_qws.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include "qdecorationstyled_qws.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "qpaintengine.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_QWS_DECORATION_STYLED) || defined(QT_PLUGIN) + +QDecorationStyled::QDecorationStyled() + : QDecorationDefault() +{ +} + +QDecorationStyled::~QDecorationStyled() +{ +} + +int QDecorationStyled::titleBarHeight(const QWidget *widget) +{ + QStyleOptionTitleBar opt; + opt.subControls = QStyle::SC_TitleBarLabel + | QStyle::SC_TitleBarSysMenu + | QStyle::SC_TitleBarNormalButton + | QStyle::SC_TitleBarContextHelpButton + | QStyle::SC_TitleBarMinButton + | QStyle::SC_TitleBarMaxButton + | QStyle::SC_TitleBarCloseButton; + opt.titleBarFlags = widget->windowFlags(); + opt.direction = QApplication::layoutDirection(); + opt.text = windowTitleFor(widget); + opt.icon = widget->windowIcon(); + opt.rect = widget->rect(); + + QStyle *style = QApplication::style(); + if (!style) + return 18; + + return style->pixelMetric(QStyle::PM_TitleBarHeight, &opt, 0); +} + +bool QDecorationStyled::paint(QPainter *painter, const QWidget *widget, int decorationRegion, + DecorationState state) +{ + if (decorationRegion == None) + return false; + + bool isActive = (widget == qApp->activeWindow()); + QPalette pal = qApp->palette(); + //ideally, the difference between Active and Inactive should be enough, so we shouldn't need to test this + if (!isActive) { + //pal.setCurrentColorGroup(QPalette::Disabled); //Can't do this either, because of palette limitations + //copied from Q3TitleBar: + pal.setColor(QPalette::Inactive, QPalette::Highlight, + pal.color(QPalette::Inactive, QPalette::Dark)); + pal.setColor(QPalette::Inactive, QPalette::Base, + pal.color(QPalette::Inactive, QPalette::Dark)); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, + pal.color(QPalette::Inactive, QPalette::Window)); + } + + Qt::WindowFlags flags = widget->windowFlags(); + bool hasBorder = !widget->isMaximized(); + bool hasTitle = flags & Qt::WindowTitleHint; + bool hasSysMenu = flags & Qt::WindowSystemMenuHint; + bool hasContextHelp = flags & Qt::WindowContextHelpButtonHint; + bool hasMinimize = flags & Qt::WindowMinimizeButtonHint; + bool hasMaximize = flags & Qt::WindowMaximizeButtonHint; + + bool paintAll = (DecorationRegion(decorationRegion) == All); + bool handled = false; + + QStyle *style = QApplication::style(); + + // In the case of a borderless title bar, the title bar must be expanded one + // borderWidth to the left, right and up. + bool noTitleBorder = style->styleHint(QStyle::SH_TitleBar_NoBorder, 0, widget); + int borderWidth = style->pixelMetric(QStyle::PM_MDIFrameWidth, 0, 0); + int titleHeight = titleBarHeight(widget) + (noTitleBorder ? borderWidth : 0); + int titleExtra = noTitleBorder ? borderWidth : 0; + + if ((paintAll || decorationRegion & Borders) && state == Normal && hasBorder) { + QRegion newClip = painter->clipRegion(); + if (hasTitle) { // reduce flicker + QRect rect(widget->rect()); + QRect r(rect.left() - titleExtra, rect.top() - titleHeight, + rect.width() + 2 * titleExtra, titleHeight); + newClip -= r; + } + if (!newClip.isEmpty()) { + QRect br = QDecoration::region(widget).boundingRect(); + painter->save(); + painter->setClipRegion(newClip); + + QStyleOptionFrame opt; + opt.palette = pal; + opt.rect = br; + opt.lineWidth = borderWidth; + + if (isActive) + opt.state |= QStyle::State_Active; + bool porterDuff = painter->paintEngine()->hasFeature(QPaintEngine::PorterDuff); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_Source); + painter->fillRect(br, pal.window()); + if (porterDuff) + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + style->drawPrimitive(QStyle::PE_FrameWindow, &opt, painter, 0); + painter->restore(); + + decorationRegion &= (~Borders); + handled |= true; + } + } + + if (hasTitle) { + painter->save(); + + QStyleOptionTitleBar opt; + opt.subControls = (decorationRegion & Title + ? QStyle::SC_TitleBarLabel : QStyle::SubControl(0)) + | (decorationRegion & Menu + ? QStyle::SC_TitleBarSysMenu : QStyle::SubControl(0)) + | (decorationRegion & Help + ? QStyle::SC_TitleBarContextHelpButton : QStyle::SubControl(0)) + | (decorationRegion & Minimize + ? QStyle::SC_TitleBarMinButton : QStyle::SubControl(0)) + | (decorationRegion & Maximize + ? QStyle::SC_TitleBarMaxButton : QStyle::SubControl(0)) + | (decorationRegion & (Minimize | Maximize) + ? QStyle::SC_TitleBarNormalButton : QStyle::SubControl(0)) + | (decorationRegion & Close + ? QStyle::SC_TitleBarCloseButton : QStyle::SubControl(0)); + opt.titleBarFlags = widget->windowFlags(); + opt.titleBarState = widget->windowState(); + if (isActive) + opt.titleBarState |= QStyle::State_Active; + opt.text = windowTitleFor(widget); + opt.icon = widget->windowIcon(); + opt.palette = pal; + opt.rect = QRect(widget->rect().x() - titleExtra, -titleHeight, + widget->rect().width() + 2 * titleExtra, titleHeight); + + if (paintAll) { + painter->setClipRegion(opt.rect); + } else { + const QRect widgetRect = widget->rect(); + QRegion newClip = opt.rect; + if (!(decorationRegion & Menu) && hasSysMenu) + newClip -= region(widget, widgetRect, Menu); + if (!(decorationRegion & Title) && hasTitle) + newClip -= region(widget, widgetRect, Title); + if (!(decorationRegion & Help) && hasContextHelp) + newClip -= region(widget, widgetRect, Help); + if (!(decorationRegion & Minimize) && hasMinimize) + newClip -= region(widget, widgetRect, Minimize); + if (!(decorationRegion & Maximize) && hasMaximize) + newClip -= region(widget, widgetRect, Maximize); + if (!(decorationRegion & (Minimize | Maximize)) && (hasMaximize | hasMinimize)) + newClip -= region(widget, widgetRect, Normal); + if (!(decorationRegion & Close)) + newClip -= region(widget, widgetRect, Close); + painter->setClipRegion(newClip); + } + + if (state == Pressed) + opt.activeSubControls = opt.subControls; + + style->drawComplexControl(QStyle::CC_TitleBar, &opt, painter, 0); + painter->restore(); + + decorationRegion &= ~(Title | Menu | Help | Normalize | Minimize | Maximize | Close); + handled |= true; + } + + return handled; +} + +QRegion QDecorationStyled::region(const QWidget *widget, const QRect &rect, int decorationRegion) +{ + QStyle *style = QApplication::style(); + + // In the case of a borderless title bar, the title bar must be expanded one + // borderWidth to the left, right and up. + bool noTitleBorder = style->styleHint(QStyle::SH_TitleBar_NoBorder, 0, widget); + int borderWidth = style->pixelMetric(QStyle::PM_MDIFrameWidth, 0, 0); + int titleHeight = titleBarHeight(widget) + (noTitleBorder ? borderWidth : 0); + int titleExtra = noTitleBorder ? borderWidth : 0; + + QRect inside = QRect(rect.x() - titleExtra, rect.top() - titleHeight, + rect.width() + 2 * titleExtra, titleHeight); + + Qt::WindowFlags flags = widget->windowFlags(); + bool hasSysMenu = flags & Qt::WindowSystemMenuHint; + bool hasContextHelp = flags & Qt::WindowContextHelpButtonHint; + bool hasMinimize = flags & Qt::WindowMinimizeButtonHint; + bool hasMaximize = flags & Qt::WindowMaximizeButtonHint; + + QStyleOptionTitleBar opt; + opt.subControls = QStyle::SC_TitleBarLabel + | QStyle::SC_TitleBarSysMenu + | QStyle::SC_TitleBarNormalButton + | QStyle::SC_TitleBarMinButton + | QStyle::SC_TitleBarMaxButton + | QStyle::SC_TitleBarCloseButton; + opt.titleBarFlags = widget->windowFlags(); + opt.direction = QApplication::layoutDirection(); + opt.text = windowTitleFor(widget); + opt.icon = widget->windowIcon(); + opt.rect = inside; + + QRegion region; + switch (decorationRegion) { + case Title: + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarLabel, 0); + break; + case Menu: + if (hasSysMenu) + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarSysMenu, 0); + break; + case Help: + if (hasContextHelp) + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarContextHelpButton, + 0); + break; + case Normalize: + if (hasMaximize | hasMinimize) + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarNormalButton, + 0); + break; + case Minimize: + if (hasMinimize) + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarMinButton, + 0); + break; + case Maximize: + if (hasMaximize) + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarMaxButton, + 0); + break; + case Close: + region = style->subControlRect(QStyle::CC_TitleBar, &opt, + QStyle::SC_TitleBarCloseButton, 0); + break; + + default: + region = QDecorationDefault::region(widget, rect, decorationRegion); + } + + opt.rect = QRect(rect.x() - titleExtra, rect.top() - titleHeight, + rect.width() + 2 * titleExtra, + rect.height() + titleHeight + titleExtra); + + QStyleHintReturnMask mask; + style->styleHint(QStyle::SH_WindowFrame_Mask, &opt, 0, &mask); + + return (mask.region.isEmpty() ? region : (region & mask.region)); +} + +#endif // QT_NO_QWS_DECORATION_STYLED + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecorationstyled_qws.h b/src/gui/embedded/qdecorationstyled_qws.h new file mode 100644 index 0000000000..ed697c0cd2 --- /dev/null +++ b/src/gui/embedded/qdecorationstyled_qws.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATIONSTYLED_QWS_H +#define QDECORATIONSTYLED_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_QWS_DECORATION_STYLED) || defined(QT_PLUGIN) + +class Q_GUI_EXPORT QDecorationStyled : public QDecorationDefault +{ +public: + QDecorationStyled(); + virtual ~QDecorationStyled(); + + QRegion region(const QWidget *widget, const QRect &rect, int decorationRegion = All); + bool paint(QPainter *painter, const QWidget *widget, int decorationRegion = All, + DecorationState state = Normal); + int titleBarHeight(const QWidget *widget); +}; + +#endif // QT_NO_QWS_DECORATION_STYLED + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDECORATIONSTYLED_QWS_H diff --git a/src/gui/embedded/qdecorationwindows_qws.cpp b/src/gui/embedded/qdecorationwindows_qws.cpp new file mode 100644 index 0000000000..51d57d7203 --- /dev/null +++ b/src/gui/embedded/qdecorationwindows_qws.cpp @@ -0,0 +1,407 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include "qdecorationwindows_qws.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_QWS_DECORATION_WINDOWS) || defined(QT_PLUGIN) + +#ifndef QT_NO_IMAGEFORMAT_XPM + +/* XPM */ +static const char * const win_close_xpm[] = { +"16 16 4 1", +" s None c None", +". c #000000", +"X c #FFFFFF", +"Y c #707070", +" ", +" ", +" ", +" Y. .Y ", +" .. .. ", +" .. .. ", +" .YY. ", +" Y..Y ", +" .YY. ", +" .. .. ", +" .. .. ", +" Y. .Y ", +" ", +" ", +" ", +" "}; + +static const char * const win_help_xpm[] = { +"16 16 3 1", +" s None c None", +". c #ffffff", +"X c #000000", +" ", +" ", +" ", +" XXXXXX ", +" XX XX ", +" XX XX ", +" XX ", +" XX ", +" XX ", +" XX ", +" ", +" XX ", +" XX ", +" ", +" ", +" "}; + +static const char * const win_maximize_xpm[] = { +"16 16 4 1", +" s None c None", +". c #000000", +"X c #FFFFFF", +"Y c #707070", +" ", +" ", +" ", +" .......... ", +" .......... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .......... ", +" ", +" ", +" ", +" "}; + +static const char * const win_minimize_xpm[] = { +"16 16 4 1", +" s None c None", +". c #000000", +"X c #FFFFFF", +"Y c #707070", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ........ ", +" ........ ", +" ", +" ", +" ", +" "}; + +static const char * const win_normalize_xpm[] = { +"16 16 4 1", +" s None c None", +". c #000000", +"X c #FFFFFF", +"Y c #707070", +" ", +" ", +" ......... ", +" ......... ", +" . . ", +" . . ", +" ......... . ", +" ......... . ", +" . . . ", +" . .... ", +" . . ", +" . . ", +" ......... ", +" ", +" ", +" "}; + +#endif // QT_NO_IMAGEFORMAT_XPM + + +QDecorationWindows::QDecorationWindows() + : QDecorationDefault() +{ + menu_width = 16; + help_width = 18; + minimize_width = 18; + maximize_width = 18; + close_width = 18; +} + +QDecorationWindows::~QDecorationWindows() +{ +} + +const char **QDecorationWindows::xpmForRegion(int reg) +{ +#ifdef QT_NO_IMAGEFORMAT_XPM + Q_UNUSED(reg); +#else + switch(reg) + { + case Close: + return (const char **)win_close_xpm; + case Help: + return (const char **)win_help_xpm; + case Minimize: + return (const char **)win_minimize_xpm; + case Maximize: + return (const char **)win_maximize_xpm; + case Normalize: + return (const char **)win_normalize_xpm; + default: + return QDecorationDefault::xpmForRegion(reg); + } +#endif + return 0; +} + +QRegion QDecorationWindows::region(const QWidget *widget, const QRect &rect, int type) +{ + Qt::WindowFlags flags = widget->windowFlags(); + bool hasTitle = flags & Qt::WindowTitleHint; + bool hasSysMenu = flags & Qt::WindowSystemMenuHint; + bool hasContextHelp = flags & Qt::WindowContextHelpButtonHint; + bool hasMinimize = flags & Qt::WindowMinimizeButtonHint; + bool hasMaximize = flags & Qt::WindowMaximizeButtonHint; + const QFontMetrics fontMetrics = QApplication::fontMetrics(); + int titleHeight = hasTitle ? qMax(20, fontMetrics.height()) : 0; + int state = widget->windowState(); + bool isMinimized = state & Qt::WindowMinimized; + bool isMaximized = state & Qt::WindowMaximized; + + QRegion region; + switch (type) { + case Menu: { + if (hasSysMenu) { + region = QRect(rect.left() + 2, rect.top() - titleHeight, + menu_width, titleHeight); + } + } + break; + + case Title: { + QRect r(rect.left() + + (hasSysMenu ? menu_width + 4: 0), + rect.top() - titleHeight, + rect.width() + - (hasSysMenu ? menu_width : 0) + - close_width + - (hasMaximize ? maximize_width : 0) + - (hasMinimize ? minimize_width : 0) + - (hasContextHelp ? help_width : 0) + - 3, + titleHeight); + if (r.width() > 0) + region = r; + } + break; + case Help: { + if (hasContextHelp) { + QRect r(rect.right() + - close_width + - (hasMaximize ? maximize_width : 0) + - (hasMinimize ? minimize_width : 0) + - help_width - 3, rect.top() - titleHeight, + help_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Minimize: { + if (hasMinimize && !isMinimized) { + QRect r(rect.right() - close_width + - (hasMaximize ? maximize_width : 0) + - minimize_width - 3, rect.top() - titleHeight, + minimize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Maximize: { + if (hasMaximize && !isMaximized) { + QRect r(rect.right() - close_width - maximize_width - 3, + rect.top() - titleHeight, maximize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Normalize: { + if (hasMinimize && isMinimized) { + QRect r(rect.right() - close_width + - (hasMaximize ? maximize_width : 0) + - minimize_width - 3, rect.top() - titleHeight, + minimize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } else if (hasMaximize && isMaximized) { + QRect r(rect.right() - close_width - maximize_width - 3, + rect.top() - titleHeight, maximize_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + } + break; + + case Close: { + QRect r(rect.right() - close_width - 1, rect.top() - titleHeight, + close_width, titleHeight); + if (r.left() > rect.left() + titleHeight) + region = r; + } + break; + + default: + region = QDecorationDefault::region(widget, rect, type); + break; + } + + return region; +} + +bool QDecorationWindows::paint(QPainter *painter, const QWidget *widget, int decorationRegion, + DecorationState state) +{ + if (decorationRegion == None) + return false; + + const QRect titleRect = QDecoration::region(widget, Title).boundingRect(); + const QPalette pal = QApplication::palette(); + QRegion oldClipRegion = painter->clipRegion(); + + bool paintAll = (decorationRegion == int(All)); + if ((paintAll || decorationRegion & Title && titleRect.width() > 0) && state == Normal + && (widget->windowFlags() & Qt::WindowTitleHint) ) { + painter->setClipRegion(oldClipRegion); + QColor fromBrush, toBrush; + QPen titlePen; + + if (widget == qApp->activeWindow() || qApp->activeWindow() == qApp->activePopupWidget()) { + fromBrush = pal.color(QPalette::Highlight); + titlePen = pal.color(QPalette::HighlightedText); + } else { + fromBrush = pal.color(QPalette::Window); + titlePen = pal.color(QPalette::Text); + } + toBrush = fromBrush.lighter(300); + + painter->setPen(Qt::NoPen); + QPoint p1(titleRect.x(), titleRect.y() + titleRect.height()/2); + QPoint p2(titleRect.right(), titleRect.y() + titleRect.height()/2); + QLinearGradient lg(p1, p2); + lg.setColorAt(0, fromBrush); + lg.setColorAt(1, toBrush); + painter->fillRect(titleRect, lg); + + painter->setPen(titlePen); + painter->drawText(titleRect, Qt::AlignVCenter, windowTitleFor(widget)); + decorationRegion ^= Title; + } + + return QDecorationDefault::paint(painter, widget, decorationRegion, state); +} + +void QDecorationWindows::paintButton(QPainter *painter, const QWidget *widget, int buttonRegion, + DecorationState state, const QPalette &pal) +{ + QBrush fromBrush, toBrush; + QPen titlePen; + + if (widget == qApp->activeWindow() || qApp->activeWindow() == qApp->activePopupWidget()) { + fromBrush = pal.brush(QPalette::Highlight); + titlePen = pal.color(QPalette::HighlightedText); + } else { + fromBrush = pal.brush(QPalette::Window); + titlePen = pal.color(QPalette::Text); + } + toBrush = fromBrush.color().lighter(300); + + QRect brect(QDecoration::region(widget, buttonRegion).boundingRect()); + if (buttonRegion != Close && buttonRegion != Menu) + painter->fillRect(brect, toBrush); + else + painter->fillRect(brect.x() - 2, brect.y(), brect.width() + 4, brect.height(), + buttonRegion == Menu ? fromBrush : toBrush); + + int xoff = 1; + int yoff = 2; + const QPixmap pm = pixmapFor(widget, buttonRegion, xoff, yoff); + if (buttonRegion != Menu) { + if (state & Normal) { + qDrawWinPanel(painter, brect.x(), brect.y() + 2, brect.width(), + brect.height() - 4, pal, false, &pal.brush(QPalette::Window)); + } else if (state & Pressed) { + qDrawWinPanel(painter, brect.x(), brect.y() + 2, brect.width(), + brect.height() - 4, pal, true, &pal.brush(QPalette::Window)); + ++xoff; + ++yoff; + } + } else { + xoff = 0; + yoff = 2; + } + + if (!pm.isNull()) + painter->drawPixmap(brect.x() + xoff, brect.y() + yoff, pm); +} + +#endif // QT_NO_QWS_DECORATION_WINDOWS || QT_PLUGIN + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdecorationwindows_qws.h b/src/gui/embedded/qdecorationwindows_qws.h new file mode 100644 index 0000000000..44213e6680 --- /dev/null +++ b/src/gui/embedded/qdecorationwindows_qws.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECORATIONWINDOWS_QWS_H +#define QDECORATIONWINDOWS_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_QWS_DECORATION_WINDOWS) || defined(QT_PLUGIN) + +class Q_GUI_EXPORT QDecorationWindows : public QDecorationDefault +{ +public: + QDecorationWindows(); + virtual ~QDecorationWindows(); + + QRegion region(const QWidget *widget, const QRect &rect, int decorationRegion = All); + bool paint(QPainter *painter, const QWidget *widget, int decorationRegion = All, + DecorationState state = Normal); + +protected: + void paintButton(QPainter *painter, const QWidget *widget, int buttonRegion, + DecorationState state, const QPalette &pal); + const char **xpmForRegion(int reg); +}; + +#endif // QT_NO_QWS_DECORATION_WINDOWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDECORATIONWINDOWS_QWS_H diff --git a/src/gui/embedded/qdirectpainter_qws.cpp b/src/gui/embedded/qdirectpainter_qws.cpp new file mode 100644 index 0000000000..47369947da --- /dev/null +++ b/src/gui/embedded/qdirectpainter_qws.cpp @@ -0,0 +1,682 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdirectpainter_qws.h" + +#include "qscreen_qws.h" +#include "private/qobject_p.h" +#include "private/qapplication_p.h" +#include "qwsdisplay_qws.h" +#include "qwidget.h" +#include "qimage.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_QWS +#ifndef QT_NO_DIRECTPAINTER + +/*! + \class QDirectPainter + \ingroup painting + \ingroup qws + + \brief The QDirectPainter class provides direct access to the + underlying hardware in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QDirectPainter allows a client application to reserve a region of + the framebuffer and render directly onto the screen. There are two + ways of using the QDirectPainter class: You can either reserve a + region using the provided static functions, or you can instantiate + an object and make use of its more dynamic API. + + \tableofcontents + + \section1 Dynamic Allocation + + By instantiating a QDirectPainter object using the default + QDirectPainter::NonReserved surface flag, the client application + only gets some control over the reserved region, i.e., it can + still render directly onto the screen but the allocated region may + change (for example, if a window with a higher focus requests + parts of the same region). The currently allocated region can be + retrieved using the allocatedRegion() function, while the + requestedRegion() function returns the originally reserved + region. + + + \section1 Static Allocation + + + Using the static approach, the client application gets complete + control over the reserved region, i.e., the affected region will + never be modified by the screen driver. + + To create a static region, pass the QDirectPainter::Reserved + surface flag to the constructor. After the reserved region is + reported through regionChanged(), the allocated region will not + change, unless setRegion() is called. + + If QDirectPainter::ReservedSynchronous is passed to the + constructor, calls to setRegion() will block until the region is + reserved, meaning that allocatedRegion() will be available immediately. + Note that in the current version setRegion() will cause the application + event loop to be entered, potentially causing reentrancy issues. + + \section1 Rendering + + To draw on a given region, the application must first get hold of + a pointer to the framebuffer. In most cases, this pointer can be + retrieved using the QDirectPainter::frameBuffer() function. But + note that if the current screen has subscreens, you must query the + screen driver instead to identify the correct subscreen. A pointer + to the current screen driver can always be retrieved using the + static QScreen::instance() function. Then use QScreen's \l + {QScreen::}{subScreenIndexAt()} and \l {QScreen::}{subScreens()} + functions to access the correct subscreen, and the subscreen's \l + {QScreen::}{base()} function to retrieve a pointer to the + framebuffer. + + Depending on the hardware, it might be necessary to lock the + framebuffer for exclusive use while writing to it. This is + possible using the lock() and unlock() functions. Note that + calling lock() will prevent all other applications from working + until unlock() is called. + + In addition, QDirectPainter provides several functions returning + information about the framebuffer: the linestep() function returns + the length (in bytes) of each scanline of the framebuffer while + the screenDepth(), screenWidth() and screenHeight() function + return the screen metrics. + + \sa QScreen, QWSEmbedWidget, {Qt for Embedded Linux Architecture} +*/ + +/*! + \enum QDirectPainter::SurfaceFlag + + This enum describes the behavior of the region reserved by this + QDirectPainter object. + + \value NonReserved The allocated region may change, e.g., if a + window with a higher focus requests parts of the same region. See + also \l {Dynamic Allocation}. + + \value Reserved The allocated region will never change. See also + \l {Static Allocation}. + + \value ReservedSynchronous The allocated region will never change and + each function that changes the allocated region will be blocking. + + \sa allocatedRegion() +*/ + +/*! + \fn QRegion QDirectPainter::region() + \obsolete + + Use QDirectPainter::allocatedRegion() instead. +*/ + +static inline QScreen *getPrimaryScreen() +{ + QScreen *screen = QScreen::instance(); + if (!screen->base()) { + QList subScreens = screen->subScreens(); + if (subScreens.size() < 1) + return 0; + screen = subScreens.at(0); + } + return screen; +} + +static inline QSize screenS() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return QSize(); + return QSize(screen->width(), screen->height()); +} + +static inline QSize devS() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return QSize(); + return QSize(screen->deviceWidth(), screen->deviceHeight()); +} + + +class QDirectPainterPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QDirectPainter); +public: + + QDirectPainterPrivate() : surface(0), seenRegion(false) {} + + ~QDirectPainterPrivate() { + if (QPaintDevice::qwsDisplay()) { // make sure not in QApplication destructor + qApp->d_func()->directPainters->remove(surface->windowId()); + surface->setGeometry(QRect()); + } + delete surface; + } + + QWSDirectPainterSurface *surface; + QRegion requested_region; + + static QDirectPainter *staticPainter; + bool seenRegion; +}; + +QDirectPainter *QDirectPainterPrivate::staticPainter = 0; + +void qt_directpainter_region(QDirectPainter *dp, const QRegion &alloc, int type) +{ + QDirectPainterPrivate *d = dp->d_func(); + + QRegion r = alloc; + QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize screenSize(screen->width(), screen->height()); + r = screen->mapToDevice(r, screenSize); + } + if (type == QWSRegionEvent::Allocation) { + d->surface->setClipRegion(alloc); + d->seenRegion = true; + if (dp != QDirectPainterPrivate::staticPainter) { + if (!d->surface->flushingRegionEvents) // recursion guard + dp->regionChanged(r); + } + } +} + +#ifndef QT_NO_QWSEMBEDWIDGET +void qt_directpainter_embedevent(QDirectPainter *dp, const QWSEmbedEvent *event) +{ + if (event->type | QWSEmbedEvent::Region) { + QScreen *screen = dp->d_func()->surface->screen(); + QRegion r = event->region; + if (screen->isTransformed()) { + const QSize screenSize(screen->width(), screen->height()); + r = screen->mapToDevice(r, screenSize); + } + dp->setRegion(r); + } +} +#endif + +/*! + Constructs a QDirectPainter object with the given \a parent and + surface \a flag. +*/ +QDirectPainter::QDirectPainter(QObject *parent, SurfaceFlag flag) + :QObject(*new QDirectPainterPrivate, parent) +{ + Q_D(QDirectPainter); + d->surface = new QWSDirectPainterSurface(true, flag); + + if (flag != NonReserved) + d->surface->setReserved(); + + QApplicationPrivate *ad = qApp->d_func(); + if (!ad->directPainters) + ad->directPainters = new QMap; + ad->directPainters->insert(d->surface->windowId(), this); +} + +/*! + Destroys this QDirectPainter object, releasing the reserved region. + + \sa allocatedRegion() +*/ +QDirectPainter::~QDirectPainter() +{ + /* should not be necessary + if (this == QDirectPainterPrivate::staticPainter) + QDirectPainterPrivate::staticPainter = 0; + */ +} + +/*! + \fn void QDirectPainter::setGeometry(const QRect &rectangle) + \since 4.2 + + Request to reserve the given \a rectangle of the framebuffer. + + Note that the actually allocated region might differ from the + requested one, e.g., if the given region overlaps with the + region of another QDirectPainter object. + + \sa geometry(), allocatedRegion(), setRegion() +*/ +void QDirectPainter::setGeometry(const QRect &rect) +{ + setRegion(rect); +} + +/*! + \since 4.2 + + Returns the bounding rectangle of the requested region. + + \sa setGeometry(), requestedRegion() +*/ +QRect QDirectPainter::geometry() const +{ + Q_D(const QDirectPainter); + return d->requested_region.boundingRect(); +} + +/*! + \since 4.2 + + Requests to reserve the given \a region of the framebuffer. + + Note that the actually allocated region might differ from the + requested one, e.g., if the given region overlaps with the region + of another QDirectPainter object. + + \sa requestedRegion(), allocatedRegion(), {Dynamic Allocation} +*/ +void QDirectPainter::setRegion(const QRegion ®ion) +{ + Q_D(QDirectPainter); + d->requested_region = region; + + const QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize devSize(screen->deviceWidth(), screen->deviceHeight()); + const QRegion r = screen->mapFromDevice(region, devSize); + d->surface->setRegion(r); + } else { + d->surface->setRegion(region); + } +} + +/*! + \since 4.2 + + Returns the region requested by this QDirectPainter. + + Note that if the QDirectPainter::Reserved flag is set, the region + returned by this function will always be equivalent to the region + returned by the allocatedRegion() function. Otherwise they might + differ (see \l {Dynamic Allocation} for details). + + \sa geometry(), setRegion(), allocatedRegion() +*/ +QRegion QDirectPainter::requestedRegion() const +{ + Q_D(const QDirectPainter); + return d->requested_region; +} + +/*! + \since 4.2 + + Returns the currently reserved region. + + Note that if the QDirectPainter::Reserved flag is set, the region + returned by this function will always be equivalent to the region + returned by the requestedRegion() function. Otherwise they might + differ (see \l {Dynamic Allocation} for details). + + \sa requestedRegion(), geometry() +*/ +QRegion QDirectPainter::allocatedRegion() const +{ + Q_D(const QDirectPainter); + const QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize screenSize(screen->width(), screen->height()); + return screen->mapToDevice(d->surface->region(), screenSize); + } else { + return d->surface->region(); + } +} + +/*! + \since 4.2 + + Returns the window system identifier of the widget. +*/ +WId QDirectPainter::winId() const +{ + Q_D(const QDirectPainter); + return d->surface->windowId(); +} + +/*! + \fn void QDirectPainter::regionChanged(const QRegion &newRegion) + \since 4.2 + + This function is called when the allocated region changes. + + This function is not called for region changes that happen while the + startPainting() function is executing. + + Note that the given region, \a newRegion, is not guaranteed to be correct at the + time you access the display. To prevent reentrancy problems you should + always call startPainting() before updating the display and then use + allocatedRegion() to retrieve the correct region. + + \sa allocatedRegion(), startPainting(), {Dynamic Allocation} +*/ +void QDirectPainter::regionChanged(const QRegion ®ion) +{ + Q_UNUSED(region); +} + +/*! + \since 4.2 + + Call this function before you start updating the pixels in the + allocated region. The hardware will be notified, if necessary, + that you are about to start painting operations. + + Set \a lockDisplay if you want startPainting() and endPainting() + to lock() and unlock() the display automatically. + + Note that for a NonReserved direct painter, you must call + allocatedRegion() after calling this function, since the allocated + region is only guaranteed to be correct after this function has + returned. + + The regionChanged() function will not be called between startPainting() + and endPainting(). + + \sa endPainting(), flush() +*/ +void QDirectPainter::startPainting(bool lockDisplay) +{ + Q_D(QDirectPainter); + d->surface->setLocking(lockDisplay); + + const QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize devSize(screen->deviceWidth(), screen->deviceHeight()); + const QRegion r = screen->mapFromDevice(d->surface->region(), devSize); + d->surface->beginPaint(r); + } else { + d->surface->beginPaint(d->surface->region()); + } +} + +/*! + \since 4.2 + + Call this function when you are done updating the screen. It will + notify the hardware, if necessary, that your painting operations + have ended. +*/ +void QDirectPainter::endPainting() +{ + Q_D(QDirectPainter); + + const QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize devSize(screen->deviceWidth(), screen->deviceHeight()); + const QRegion r = screen->mapFromDevice(d->surface->region(), devSize); + d->surface->endPaint(r); + } else { + d->surface->endPaint(d->surface->region()); + } +} + +/*! + \since 4.3 + \overload + + This function will automatically call flush() to flush the + \a region to the display before notifying the hardware, if + necessary, that painting operations have ended. +*/ +void QDirectPainter::endPainting(const QRegion ®ion) +{ + endPainting(); + flush(region); +} + +/*! + \since 4.3 + + Flushes the \a region onto the screen. +*/ +void QDirectPainter::flush(const QRegion ®ion) +{ + Q_D(QDirectPainter); + + const QScreen *screen = d->surface->screen(); + if (screen->isTransformed()) { + const QSize devSize(screen->deviceWidth(), screen->deviceHeight()); + const QRegion r = screen->mapFromDevice(region, devSize); + d->surface->flush(0, r, QPoint()); + } else { + d->surface->flush(0, region, QPoint()); + } +} + +/*! + \since 4.2 + + Raises the reserved region to the top of the widget stack. + + After this call the reserved region will be visually in front of + any overlapping widgets. + + \sa lower(), requestedRegion() +*/ +void QDirectPainter::raise() +{ + QWidget::qwsDisplay()->setAltitude(winId(),QWSChangeAltitudeCommand::Raise); +} + +/*! + \since 4.2 + + Lowers the reserved region to the bottom of the widget stack. + + After this call the reserved region will be visually behind (and + therefore obscured by) any overlapping widgets. + + \sa raise(), requestedRegion() +*/ +void QDirectPainter::lower() +{ + QWidget::qwsDisplay()->setAltitude(winId(),QWSChangeAltitudeCommand::Lower); +} + + +/*! + \fn QRegion QDirectPainter::reserveRegion(const QRegion ®ion) + + Attempts to reserve the \a region and returns the region that is + actually reserved. + + This function also releases the previously reserved region if + any. If not released explicitly, the region will be released on + application exit. + + \sa allocatedRegion(), {Static Allocation} + + \obsolete + + Construct a QDirectPainter using QDirectPainter::ReservedSynchronous instead. +*/ +QRegion QDirectPainter::reserveRegion(const QRegion ®) +{ + if (!QDirectPainterPrivate::staticPainter) + QDirectPainterPrivate::staticPainter = new QDirectPainter(qApp, ReservedSynchronous); + + QDirectPainter *dp = QDirectPainterPrivate::staticPainter; + dp->setRegion(reg); + + return dp->allocatedRegion(); +} + +/*! + Returns a pointer to the beginning of the display memory. + + Note that it is the application's responsibility to limit itself + to modifying only the reserved region. + + Do not use this pointer if the current screen has subscreens, + query the screen driver instead: A pointer to the current screen + driver can always be retrieved using the static + QScreen::instance() function. Then use QScreen's \l + {QScreen::}{subScreenIndexAt()} and \l {QScreen::}{subScreens()} + functions to access the correct subscreen, and the subscreen's \l + {QScreen::}{base()} function to retrieve a pointer to the + framebuffer. + + \sa requestedRegion(), allocatedRegion(), linestep() +*/ +uchar* QDirectPainter::frameBuffer() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return 0; + return screen->base(); +} + +/*! + \since 4.2 + + Returns the reserved region. + + \sa reserveRegion(), frameBuffer() + + \obsolete + + Use allocatedRegion() instead. +*/ +QRegion QDirectPainter::reservedRegion() +{ + return QDirectPainterPrivate::staticPainter + ? QDirectPainterPrivate::staticPainter->allocatedRegion() : QRegion(); +} + +/*! + Returns the bit depth of the display. + + \sa screenHeight(), screenWidth() +*/ +int QDirectPainter::screenDepth() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return 0; + return screen->depth(); +} + +/*! + Returns the width of the display in pixels. + + \sa screenHeight(), screenDepth() +*/ +int QDirectPainter::screenWidth() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return 0; + return screen->deviceWidth(); +} + +/*! + Returns the height of the display in pixels. + + \sa screenWidth(), screenDepth() +*/ +int QDirectPainter::screenHeight() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return 0; + return screen->deviceHeight(); +} + +/*! + Returns the length (in bytes) of each scanline of the framebuffer. + + \sa frameBuffer() +*/ +int QDirectPainter::linestep() +{ + QScreen *screen = getPrimaryScreen(); + if (!screen) + return 0; + return screen->linestep(); +} + + +/*! + Locks access to the framebuffer. + + Note that calling this function will prevent all other + applications from updating the display until unlock() is called. + + \sa unlock() +*/ +void QDirectPainter::lock() +{ + QWSDisplay::grab(true); +} +/*! + Unlocks the lock on the framebuffer (set using the lock() + function), allowing other applications to access the screen. + + \sa lock() + */ +void QDirectPainter::unlock() +{ + QWSDisplay::ungrab(); +} + +#endif //QT_NO_DIRECTPAINTER + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qdirectpainter_qws.h b/src/gui/embedded/qdirectpainter_qws.h new file mode 100644 index 0000000000..47cc721d49 --- /dev/null +++ b/src/gui/embedded/qdirectpainter_qws.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIRECTPAINTER_QWS_H +#define QDIRECTPAINTER_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DIRECTPAINTER +class QDirectPainterPrivate; +class QWSEmbedEvent; + +class Q_GUI_EXPORT QDirectPainter : public QObject { + Q_OBJECT + Q_DECLARE_PRIVATE(QDirectPainter) +public: + + enum SurfaceFlag { NonReserved = 0, + Reserved = 1, + ReservedSynchronous = 3 }; + + explicit QDirectPainter(QObject *parentObject = 0, SurfaceFlag flag = NonReserved); + ~QDirectPainter(); + + void setRegion(const QRegion&); + QRegion requestedRegion() const; + QRegion allocatedRegion() const; + + void setGeometry(const QRect&); + QRect geometry() const; + + WId winId() const; + virtual void regionChanged(const QRegion &exposedRegion); + + void startPainting(bool lockDisplay = true); + void endPainting(); + void endPainting(const QRegion ®ion); + void flush(const QRegion ®ion); + + void raise(); + void lower(); + + + static QRegion reserveRegion(const QRegion&); + static QRegion reservedRegion(); + static QRegion region() { return reservedRegion(); } + + static uchar* frameBuffer(); + static int screenDepth(); + static int screenWidth(); + static int screenHeight(); + static int linestep(); + + static void lock(); + static void unlock(); +private: + friend void qt_directpainter_region(QDirectPainter *dp, const QRegion &alloc, int type); + friend void qt_directpainter_embedevent(QDirectPainter*, const QWSEmbedEvent*); +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIRECTPAINTER_QWS_H diff --git a/src/gui/embedded/qkbd_defaultmap_qws_p.h b/src/gui/embedded/qkbd_defaultmap_qws_p.h new file mode 100644 index 0000000000..017f56deb6 --- /dev/null +++ b/src/gui/embedded/qkbd_defaultmap_qws_p.h @@ -0,0 +1,806 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSKEYBOARDHANDLER_DEFAULTMAP_H +#define QWSKEYBOARDHANDLER_DEFAULTMAP_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. +// + +// no QT_BEGIN_NAMESPACE, since we include it internally... + +const QWSKeyboard::Mapping QWSKbPrivate::s_keymap_default[] = { + { 1, 0xffff, 0x01000000, 0x00, 0x00, 0x0000 }, + { 2, 0x0031, 0x00000031, 0x00, 0x00, 0x0000 }, + { 2, 0x0021, 0x00000021, 0x01, 0x00, 0x0000 }, + { 3, 0x0032, 0x00000032, 0x00, 0x00, 0x0000 }, + { 3, 0x0040, 0x00000040, 0x01, 0x00, 0x0000 }, + { 3, 0x0040, 0x00000040, 0x02, 0x00, 0x0000 }, + { 4, 0x0033, 0x00000033, 0x00, 0x00, 0x0000 }, + { 4, 0x0023, 0x00000023, 0x01, 0x00, 0x0000 }, + { 4, 0xffff, 0x01000000, 0x04, 0x00, 0x0000 }, + { 5, 0x0034, 0x00000034, 0x00, 0x00, 0x0000 }, + { 5, 0x0024, 0x00000024, 0x01, 0x00, 0x0000 }, + { 5, 0x0024, 0x00000024, 0x02, 0x00, 0x0000 }, + { 5, 0x005c, 0x0400005c, 0x04, 0x00, 0x0000 }, + { 6, 0x0035, 0x00000035, 0x00, 0x00, 0x0000 }, + { 6, 0x0025, 0x00000025, 0x01, 0x00, 0x0000 }, + { 6, 0x005d, 0x0400005d, 0x04, 0x00, 0x0000 }, + { 7, 0x0036, 0x00000036, 0x00, 0x00, 0x0000 }, + { 7, 0x005e, 0x0000005e, 0x01, 0x00, 0x0000 }, + { 7, 0x005e, 0x01001252, 0x02, 0x01, 0x0000 }, + { 7, 0x005e, 0x0400005e, 0x04, 0x00, 0x0000 }, + { 8, 0x0037, 0x00000037, 0x00, 0x00, 0x0000 }, + { 8, 0x0026, 0x00000026, 0x01, 0x00, 0x0000 }, + { 8, 0x007b, 0x0000007b, 0x02, 0x00, 0x0000 }, + { 8, 0x005f, 0x0400005f, 0x04, 0x00, 0x0000 }, + { 9, 0x0038, 0x00000038, 0x00, 0x00, 0x0000 }, + { 9, 0x002a, 0x0000002a, 0x01, 0x00, 0x0000 }, + { 9, 0x005b, 0x0000005b, 0x02, 0x00, 0x0000 }, + { 9, 0xffff, 0x01000003, 0x04, 0x00, 0x0000 }, + { 10, 0x0039, 0x00000039, 0x00, 0x00, 0x0000 }, + { 10, 0x0028, 0x00000028, 0x01, 0x00, 0x0000 }, + { 10, 0x005d, 0x0000005d, 0x02, 0x00, 0x0000 }, + { 11, 0x0030, 0x00000030, 0x00, 0x00, 0x0000 }, + { 11, 0x0029, 0x00000029, 0x01, 0x00, 0x0000 }, + { 11, 0x007d, 0x0000007d, 0x02, 0x00, 0x0000 }, + { 12, 0x002d, 0x0000002d, 0x00, 0x00, 0x0000 }, + { 12, 0x005f, 0x0000005f, 0x01, 0x00, 0x0000 }, + { 12, 0x005c, 0x0000005c, 0x02, 0x00, 0x0000 }, + { 12, 0x005f, 0x0400005f, 0x04, 0x00, 0x0000 }, + { 12, 0x005f, 0x0400005f, 0x05, 0x00, 0x0000 }, + { 13, 0x003d, 0x0000003d, 0x00, 0x00, 0x0000 }, + { 13, 0x002b, 0x0000002b, 0x01, 0x00, 0x0000 }, + { 14, 0xffff, 0x01000003, 0x00, 0x00, 0x0000 }, + { 14, 0xffff, 0x01000000, 0x0c, 0x08, 0x0300 }, + { 15, 0xffff, 0x01000001, 0x00, 0x00, 0x0000 }, + { 16, 0x0071, 0x00000051, 0x00, 0x00, 0x0000 }, + { 16, 0x0051, 0x00000051, 0x01, 0x00, 0x0000 }, + { 16, 0x0071, 0x00000051, 0x02, 0x00, 0x0000 }, + { 16, 0x0051, 0x00000051, 0x03, 0x00, 0x0000 }, + { 16, 0x0071, 0x04000051, 0x04, 0x00, 0x0000 }, + { 16, 0x0071, 0x04000051, 0x05, 0x00, 0x0000 }, + { 16, 0x0071, 0x04000051, 0x06, 0x00, 0x0000 }, + { 16, 0x0071, 0x04000051, 0x07, 0x00, 0x0000 }, + { 16, 0x0071, 0x08000051, 0x08, 0x00, 0x0000 }, + { 16, 0x0071, 0x08000051, 0x09, 0x00, 0x0000 }, + { 16, 0x0071, 0x08000051, 0x0a, 0x00, 0x0000 }, + { 16, 0x0071, 0x08000051, 0x0b, 0x00, 0x0000 }, + { 16, 0x0071, 0x0c000051, 0x0c, 0x00, 0x0000 }, + { 16, 0x0071, 0x0c000051, 0x0d, 0x00, 0x0000 }, + { 16, 0x0071, 0x0c000051, 0x0e, 0x00, 0x0000 }, + { 16, 0x0071, 0x0c000051, 0x0f, 0x00, 0x0000 }, + { 17, 0x0077, 0x00000057, 0x00, 0x00, 0x0000 }, + { 17, 0x0057, 0x00000057, 0x01, 0x00, 0x0000 }, + { 17, 0x0077, 0x00000057, 0x02, 0x00, 0x0000 }, + { 17, 0x0057, 0x00000057, 0x03, 0x00, 0x0000 }, + { 17, 0x0077, 0x04000057, 0x04, 0x00, 0x0000 }, + { 17, 0x0077, 0x04000057, 0x05, 0x00, 0x0000 }, + { 17, 0x0077, 0x04000057, 0x06, 0x00, 0x0000 }, + { 17, 0x0077, 0x04000057, 0x07, 0x00, 0x0000 }, + { 17, 0x0077, 0x08000057, 0x08, 0x00, 0x0000 }, + { 17, 0x0077, 0x08000057, 0x09, 0x00, 0x0000 }, + { 17, 0x0077, 0x08000057, 0x0a, 0x00, 0x0000 }, + { 17, 0x0077, 0x08000057, 0x0b, 0x00, 0x0000 }, + { 17, 0x0077, 0x0c000057, 0x0c, 0x00, 0x0000 }, + { 17, 0x0077, 0x0c000057, 0x0d, 0x00, 0x0000 }, + { 17, 0x0077, 0x0c000057, 0x0e, 0x00, 0x0000 }, + { 17, 0x0077, 0x0c000057, 0x0f, 0x00, 0x0000 }, + { 18, 0x0065, 0x00000045, 0x00, 0x00, 0x0000 }, + { 18, 0x0045, 0x00000045, 0x01, 0x00, 0x0000 }, + { 18, 0x0065, 0x00000045, 0x02, 0x00, 0x0000 }, + { 18, 0x0045, 0x00000045, 0x03, 0x00, 0x0000 }, + { 18, 0x0065, 0x04000045, 0x04, 0x00, 0x0000 }, + { 18, 0x0065, 0x04000045, 0x05, 0x00, 0x0000 }, + { 18, 0x0065, 0x04000045, 0x06, 0x00, 0x0000 }, + { 18, 0x0065, 0x04000045, 0x07, 0x00, 0x0000 }, + { 18, 0x0065, 0x08000045, 0x08, 0x00, 0x0000 }, + { 18, 0x0065, 0x08000045, 0x09, 0x00, 0x0000 }, + { 18, 0x0065, 0x08000045, 0x0a, 0x00, 0x0000 }, + { 18, 0x0065, 0x08000045, 0x0b, 0x00, 0x0000 }, + { 18, 0x0065, 0x0c000045, 0x0c, 0x00, 0x0000 }, + { 18, 0x0065, 0x0c000045, 0x0d, 0x00, 0x0000 }, + { 18, 0x0065, 0x0c000045, 0x0e, 0x00, 0x0000 }, + { 18, 0x0065, 0x0c000045, 0x0f, 0x00, 0x0000 }, + { 19, 0x0072, 0x00000052, 0x00, 0x00, 0x0000 }, + { 19, 0x0052, 0x00000052, 0x01, 0x00, 0x0000 }, + { 19, 0x0072, 0x00000052, 0x02, 0x00, 0x0000 }, + { 19, 0x0052, 0x00000052, 0x03, 0x00, 0x0000 }, + { 19, 0x0072, 0x04000052, 0x04, 0x00, 0x0000 }, + { 19, 0x0072, 0x04000052, 0x05, 0x00, 0x0000 }, + { 19, 0x0072, 0x04000052, 0x06, 0x00, 0x0000 }, + { 19, 0x0072, 0x04000052, 0x07, 0x00, 0x0000 }, + { 19, 0x0072, 0x08000052, 0x08, 0x00, 0x0000 }, + { 19, 0x0072, 0x08000052, 0x09, 0x00, 0x0000 }, + { 19, 0x0072, 0x08000052, 0x0a, 0x00, 0x0000 }, + { 19, 0x0072, 0x08000052, 0x0b, 0x00, 0x0000 }, + { 19, 0x0072, 0x0c000052, 0x0c, 0x00, 0x0000 }, + { 19, 0x0072, 0x0c000052, 0x0d, 0x00, 0x0000 }, + { 19, 0x0072, 0x0c000052, 0x0e, 0x00, 0x0000 }, + { 19, 0x0072, 0x0c000052, 0x0f, 0x00, 0x0000 }, + { 20, 0x0074, 0x00000054, 0x00, 0x00, 0x0000 }, + { 20, 0x0054, 0x00000054, 0x01, 0x00, 0x0000 }, + { 20, 0x0074, 0x00000054, 0x02, 0x00, 0x0000 }, + { 20, 0x0054, 0x00000054, 0x03, 0x00, 0x0000 }, + { 20, 0x0074, 0x04000054, 0x04, 0x00, 0x0000 }, + { 20, 0x0074, 0x04000054, 0x05, 0x00, 0x0000 }, + { 20, 0x0074, 0x04000054, 0x06, 0x00, 0x0000 }, + { 20, 0x0074, 0x04000054, 0x07, 0x00, 0x0000 }, + { 20, 0x0074, 0x08000054, 0x08, 0x00, 0x0000 }, + { 20, 0x0074, 0x08000054, 0x09, 0x00, 0x0000 }, + { 20, 0x0074, 0x08000054, 0x0a, 0x00, 0x0000 }, + { 20, 0x0074, 0x08000054, 0x0b, 0x00, 0x0000 }, + { 20, 0x0074, 0x0c000054, 0x0c, 0x00, 0x0000 }, + { 20, 0x0074, 0x0c000054, 0x0d, 0x00, 0x0000 }, + { 20, 0x0074, 0x0c000054, 0x0e, 0x00, 0x0000 }, + { 20, 0x0074, 0x0c000054, 0x0f, 0x00, 0x0000 }, + { 21, 0x0079, 0x00000059, 0x00, 0x00, 0x0000 }, + { 21, 0x0059, 0x00000059, 0x01, 0x00, 0x0000 }, + { 21, 0x0079, 0x00000059, 0x02, 0x00, 0x0000 }, + { 21, 0x0059, 0x00000059, 0x03, 0x00, 0x0000 }, + { 21, 0x0079, 0x04000059, 0x04, 0x00, 0x0000 }, + { 21, 0x0079, 0x04000059, 0x05, 0x00, 0x0000 }, + { 21, 0x0079, 0x04000059, 0x06, 0x00, 0x0000 }, + { 21, 0x0079, 0x04000059, 0x07, 0x00, 0x0000 }, + { 21, 0x0079, 0x08000059, 0x08, 0x00, 0x0000 }, + { 21, 0x0079, 0x08000059, 0x09, 0x00, 0x0000 }, + { 21, 0x0079, 0x08000059, 0x0a, 0x00, 0x0000 }, + { 21, 0x0079, 0x08000059, 0x0b, 0x00, 0x0000 }, + { 21, 0x0079, 0x0c000059, 0x0c, 0x00, 0x0000 }, + { 21, 0x0079, 0x0c000059, 0x0d, 0x00, 0x0000 }, + { 21, 0x0079, 0x0c000059, 0x0e, 0x00, 0x0000 }, + { 21, 0x0079, 0x0c000059, 0x0f, 0x00, 0x0000 }, + { 22, 0x0075, 0x00000055, 0x00, 0x00, 0x0000 }, + { 22, 0x0055, 0x00000055, 0x01, 0x00, 0x0000 }, + { 22, 0x0075, 0x00000055, 0x02, 0x00, 0x0000 }, + { 22, 0x0055, 0x00000055, 0x03, 0x00, 0x0000 }, + { 22, 0x0075, 0x04000055, 0x04, 0x00, 0x0000 }, + { 22, 0x0075, 0x04000055, 0x05, 0x00, 0x0000 }, + { 22, 0x0075, 0x04000055, 0x06, 0x00, 0x0000 }, + { 22, 0x0075, 0x04000055, 0x07, 0x00, 0x0000 }, + { 22, 0x0075, 0x08000055, 0x08, 0x00, 0x0000 }, + { 22, 0x0075, 0x08000055, 0x09, 0x00, 0x0000 }, + { 22, 0x0075, 0x08000055, 0x0a, 0x00, 0x0000 }, + { 22, 0x0075, 0x08000055, 0x0b, 0x00, 0x0000 }, + { 22, 0x0075, 0x0c000055, 0x0c, 0x00, 0x0000 }, + { 22, 0x0075, 0x0c000055, 0x0d, 0x00, 0x0000 }, + { 22, 0x0075, 0x0c000055, 0x0e, 0x00, 0x0000 }, + { 22, 0x0075, 0x0c000055, 0x0f, 0x00, 0x0000 }, + { 23, 0x0069, 0x00000049, 0x00, 0x00, 0x0000 }, + { 23, 0x0049, 0x00000049, 0x01, 0x00, 0x0000 }, + { 23, 0x0069, 0x00000049, 0x02, 0x00, 0x0000 }, + { 23, 0x0049, 0x00000049, 0x03, 0x00, 0x0000 }, + { 23, 0x0069, 0x04000049, 0x04, 0x00, 0x0000 }, + { 23, 0x0069, 0x04000049, 0x05, 0x00, 0x0000 }, + { 23, 0x0069, 0x04000049, 0x06, 0x00, 0x0000 }, + { 23, 0x0069, 0x04000049, 0x07, 0x00, 0x0000 }, + { 23, 0x0069, 0x08000049, 0x08, 0x00, 0x0000 }, + { 23, 0x0069, 0x08000049, 0x09, 0x00, 0x0000 }, + { 23, 0x0069, 0x08000049, 0x0a, 0x00, 0x0000 }, + { 23, 0x0069, 0x08000049, 0x0b, 0x00, 0x0000 }, + { 23, 0x0069, 0x0c000049, 0x0c, 0x00, 0x0000 }, + { 23, 0x0069, 0x0c000049, 0x0d, 0x00, 0x0000 }, + { 23, 0x0069, 0x0c000049, 0x0e, 0x00, 0x0000 }, + { 23, 0x0069, 0x0c000049, 0x0f, 0x00, 0x0000 }, + { 24, 0x006f, 0x0000004f, 0x00, 0x00, 0x0000 }, + { 24, 0x004f, 0x0000004f, 0x01, 0x00, 0x0000 }, + { 24, 0x006f, 0x0000004f, 0x02, 0x00, 0x0000 }, + { 24, 0x004f, 0x0000004f, 0x03, 0x00, 0x0000 }, + { 24, 0x006f, 0x0400004f, 0x04, 0x00, 0x0000 }, + { 24, 0x006f, 0x0400004f, 0x05, 0x00, 0x0000 }, + { 24, 0x006f, 0x0400004f, 0x06, 0x00, 0x0000 }, + { 24, 0x006f, 0x0400004f, 0x07, 0x00, 0x0000 }, + { 24, 0x006f, 0x0800004f, 0x08, 0x00, 0x0000 }, + { 24, 0x006f, 0x0800004f, 0x09, 0x00, 0x0000 }, + { 24, 0x006f, 0x0800004f, 0x0a, 0x00, 0x0000 }, + { 24, 0x006f, 0x0800004f, 0x0b, 0x00, 0x0000 }, + { 24, 0x006f, 0x0c00004f, 0x0c, 0x00, 0x0000 }, + { 24, 0x006f, 0x0c00004f, 0x0d, 0x00, 0x0000 }, + { 24, 0x006f, 0x0c00004f, 0x0e, 0x00, 0x0000 }, + { 24, 0x006f, 0x0c00004f, 0x0f, 0x00, 0x0000 }, + { 25, 0x0070, 0x00000050, 0x00, 0x00, 0x0000 }, + { 25, 0x0050, 0x00000050, 0x01, 0x00, 0x0000 }, + { 25, 0x0070, 0x00000050, 0x02, 0x00, 0x0000 }, + { 25, 0x0050, 0x00000050, 0x03, 0x00, 0x0000 }, + { 25, 0x0070, 0x04000050, 0x04, 0x00, 0x0000 }, + { 25, 0x0070, 0x04000050, 0x05, 0x00, 0x0000 }, + { 25, 0x0070, 0x04000050, 0x06, 0x00, 0x0000 }, + { 25, 0x0070, 0x04000050, 0x07, 0x00, 0x0000 }, + { 25, 0x0070, 0x08000050, 0x08, 0x00, 0x0000 }, + { 25, 0x0070, 0x08000050, 0x09, 0x00, 0x0000 }, + { 25, 0x0070, 0x08000050, 0x0a, 0x00, 0x0000 }, + { 25, 0x0070, 0x08000050, 0x0b, 0x00, 0x0000 }, + { 25, 0x0070, 0x0c000050, 0x0c, 0x00, 0x0000 }, + { 25, 0x0070, 0x0c000050, 0x0d, 0x00, 0x0000 }, + { 25, 0x0070, 0x0c000050, 0x0e, 0x00, 0x0000 }, + { 25, 0x0070, 0x0c000050, 0x0f, 0x00, 0x0000 }, + { 26, 0x005b, 0x0000005b, 0x00, 0x00, 0x0000 }, + { 26, 0x007b, 0x0000007b, 0x01, 0x00, 0x0000 }, + { 26, 0xffff, 0x01000000, 0x04, 0x00, 0x0000 }, + { 27, 0x005d, 0x0000005d, 0x00, 0x00, 0x0000 }, + { 27, 0x007d, 0x0000007d, 0x01, 0x00, 0x0000 }, + { 27, 0x007e, 0x0000007e, 0x02, 0x00, 0x0000 }, + { 27, 0x005d, 0x0400005d, 0x04, 0x00, 0x0000 }, + { 28, 0xffff, 0x01000004, 0x00, 0x00, 0x0000 }, + { 28, 0x006d, 0x0c00004d, 0x08, 0x00, 0x0000 }, + { 29, 0xffff, 0x01000021, 0x00, 0x04, 0x0004 }, + { 30, 0x0061, 0x00000041, 0x00, 0x00, 0x0000 }, + { 30, 0x0041, 0x00000041, 0x01, 0x00, 0x0000 }, + { 30, 0x0061, 0x00000041, 0x02, 0x00, 0x0000 }, + { 30, 0x0041, 0x00000041, 0x03, 0x00, 0x0000 }, + { 30, 0x0061, 0x04000041, 0x04, 0x00, 0x0000 }, + { 30, 0x0061, 0x04000041, 0x05, 0x00, 0x0000 }, + { 30, 0x0061, 0x04000041, 0x06, 0x00, 0x0000 }, + { 30, 0x0061, 0x04000041, 0x07, 0x00, 0x0000 }, + { 30, 0x0061, 0x08000041, 0x08, 0x00, 0x0000 }, + { 30, 0x0061, 0x08000041, 0x09, 0x00, 0x0000 }, + { 30, 0x0061, 0x08000041, 0x0a, 0x00, 0x0000 }, + { 30, 0x0061, 0x08000041, 0x0b, 0x00, 0x0000 }, + { 30, 0x0061, 0x0c000041, 0x0c, 0x00, 0x0000 }, + { 30, 0x0061, 0x0c000041, 0x0d, 0x00, 0x0000 }, + { 30, 0x0061, 0x0c000041, 0x0e, 0x00, 0x0000 }, + { 30, 0x0061, 0x0c000041, 0x0f, 0x00, 0x0000 }, + { 31, 0x0073, 0x00000053, 0x00, 0x00, 0x0000 }, + { 31, 0x0053, 0x00000053, 0x01, 0x00, 0x0000 }, + { 31, 0x0073, 0x00000053, 0x02, 0x00, 0x0000 }, + { 31, 0x0053, 0x00000053, 0x03, 0x00, 0x0000 }, + { 31, 0x0073, 0x04000053, 0x04, 0x00, 0x0000 }, + { 31, 0x0073, 0x04000053, 0x05, 0x00, 0x0000 }, + { 31, 0x0073, 0x04000053, 0x06, 0x00, 0x0000 }, + { 31, 0x0073, 0x04000053, 0x07, 0x00, 0x0000 }, + { 31, 0x0073, 0x08000053, 0x08, 0x00, 0x0000 }, + { 31, 0x0073, 0x08000053, 0x09, 0x00, 0x0000 }, + { 31, 0x0073, 0x08000053, 0x0a, 0x00, 0x0000 }, + { 31, 0x0073, 0x08000053, 0x0b, 0x00, 0x0000 }, + { 31, 0x0073, 0x0c000053, 0x0c, 0x00, 0x0000 }, + { 31, 0x0073, 0x0c000053, 0x0d, 0x00, 0x0000 }, + { 31, 0x0073, 0x0c000053, 0x0e, 0x00, 0x0000 }, + { 31, 0x0073, 0x0c000053, 0x0f, 0x00, 0x0000 }, + { 32, 0x0064, 0x00000044, 0x00, 0x00, 0x0000 }, + { 32, 0x0044, 0x00000044, 0x01, 0x00, 0x0000 }, + { 32, 0x0064, 0x00000044, 0x02, 0x00, 0x0000 }, + { 32, 0x0044, 0x00000044, 0x03, 0x00, 0x0000 }, + { 32, 0x0064, 0x04000044, 0x04, 0x00, 0x0000 }, + { 32, 0x0064, 0x04000044, 0x05, 0x00, 0x0000 }, + { 32, 0x0064, 0x04000044, 0x06, 0x00, 0x0000 }, + { 32, 0x0064, 0x04000044, 0x07, 0x00, 0x0000 }, + { 32, 0x0064, 0x08000044, 0x08, 0x00, 0x0000 }, + { 32, 0x0064, 0x08000044, 0x09, 0x00, 0x0000 }, + { 32, 0x0064, 0x08000044, 0x0a, 0x00, 0x0000 }, + { 32, 0x0064, 0x08000044, 0x0b, 0x00, 0x0000 }, + { 32, 0x0064, 0x0c000044, 0x0c, 0x00, 0x0000 }, + { 32, 0x0064, 0x0c000044, 0x0d, 0x00, 0x0000 }, + { 32, 0x0064, 0x0c000044, 0x0e, 0x00, 0x0000 }, + { 32, 0x0064, 0x0c000044, 0x0f, 0x00, 0x0000 }, + { 33, 0x0066, 0x00000046, 0x00, 0x00, 0x0000 }, + { 33, 0x0046, 0x00000046, 0x01, 0x00, 0x0000 }, + { 33, 0x0066, 0x00000046, 0x02, 0x00, 0x0000 }, + { 33, 0x0046, 0x00000046, 0x03, 0x00, 0x0000 }, + { 33, 0x0066, 0x04000046, 0x04, 0x00, 0x0000 }, + { 33, 0x0066, 0x04000046, 0x05, 0x00, 0x0000 }, + { 33, 0x0066, 0x04000046, 0x06, 0x00, 0x0000 }, + { 33, 0x0066, 0x04000046, 0x07, 0x00, 0x0000 }, + { 33, 0x0066, 0x08000046, 0x08, 0x00, 0x0000 }, + { 33, 0x0066, 0x08000046, 0x09, 0x00, 0x0000 }, + { 33, 0x0066, 0x08000046, 0x0a, 0x00, 0x0000 }, + { 33, 0x0066, 0x08000046, 0x0b, 0x00, 0x0000 }, + { 33, 0x0066, 0x0c000046, 0x0c, 0x00, 0x0000 }, + { 33, 0x0066, 0x0c000046, 0x0d, 0x00, 0x0000 }, + { 33, 0x0066, 0x0c000046, 0x0e, 0x00, 0x0000 }, + { 33, 0x0066, 0x0c000046, 0x0f, 0x00, 0x0000 }, + { 34, 0x0067, 0x00000047, 0x00, 0x00, 0x0000 }, + { 34, 0x0047, 0x00000047, 0x01, 0x00, 0x0000 }, + { 34, 0x0067, 0x00000047, 0x02, 0x00, 0x0000 }, + { 34, 0x0047, 0x00000047, 0x03, 0x00, 0x0000 }, + { 34, 0x0067, 0x04000047, 0x04, 0x00, 0x0000 }, + { 34, 0x0067, 0x04000047, 0x05, 0x00, 0x0000 }, + { 34, 0x0067, 0x04000047, 0x06, 0x00, 0x0000 }, + { 34, 0x0067, 0x04000047, 0x07, 0x00, 0x0000 }, + { 34, 0x0067, 0x08000047, 0x08, 0x00, 0x0000 }, + { 34, 0x0067, 0x08000047, 0x09, 0x00, 0x0000 }, + { 34, 0x0067, 0x08000047, 0x0a, 0x00, 0x0000 }, + { 34, 0x0067, 0x08000047, 0x0b, 0x00, 0x0000 }, + { 34, 0x0067, 0x0c000047, 0x0c, 0x00, 0x0000 }, + { 34, 0x0067, 0x0c000047, 0x0d, 0x00, 0x0000 }, + { 34, 0x0067, 0x0c000047, 0x0e, 0x00, 0x0000 }, + { 34, 0x0067, 0x0c000047, 0x0f, 0x00, 0x0000 }, + { 35, 0x0068, 0x00000048, 0x00, 0x00, 0x0000 }, + { 35, 0x0048, 0x00000048, 0x01, 0x00, 0x0000 }, + { 35, 0x0068, 0x00000048, 0x02, 0x00, 0x0000 }, + { 35, 0x0048, 0x00000048, 0x03, 0x00, 0x0000 }, + { 35, 0x0068, 0x04000048, 0x04, 0x00, 0x0000 }, + { 35, 0x0068, 0x04000048, 0x05, 0x00, 0x0000 }, + { 35, 0x0068, 0x04000048, 0x06, 0x00, 0x0000 }, + { 35, 0x0068, 0x04000048, 0x07, 0x00, 0x0000 }, + { 35, 0x0068, 0x08000048, 0x08, 0x00, 0x0000 }, + { 35, 0x0068, 0x08000048, 0x09, 0x00, 0x0000 }, + { 35, 0x0068, 0x08000048, 0x0a, 0x00, 0x0000 }, + { 35, 0x0068, 0x08000048, 0x0b, 0x00, 0x0000 }, + { 35, 0x0068, 0x0c000048, 0x0c, 0x00, 0x0000 }, + { 35, 0x0068, 0x0c000048, 0x0d, 0x00, 0x0000 }, + { 35, 0x0068, 0x0c000048, 0x0e, 0x00, 0x0000 }, + { 35, 0x0068, 0x0c000048, 0x0f, 0x00, 0x0000 }, + { 36, 0x006a, 0x0000004a, 0x00, 0x00, 0x0000 }, + { 36, 0x004a, 0x0000004a, 0x01, 0x00, 0x0000 }, + { 36, 0x006a, 0x0000004a, 0x02, 0x00, 0x0000 }, + { 36, 0x004a, 0x0000004a, 0x03, 0x00, 0x0000 }, + { 36, 0x006a, 0x0400004a, 0x04, 0x00, 0x0000 }, + { 36, 0x006a, 0x0400004a, 0x05, 0x00, 0x0000 }, + { 36, 0x006a, 0x0400004a, 0x06, 0x00, 0x0000 }, + { 36, 0x006a, 0x0400004a, 0x07, 0x00, 0x0000 }, + { 36, 0x006a, 0x0800004a, 0x08, 0x00, 0x0000 }, + { 36, 0x006a, 0x0800004a, 0x09, 0x00, 0x0000 }, + { 36, 0x006a, 0x0800004a, 0x0a, 0x00, 0x0000 }, + { 36, 0x006a, 0x0800004a, 0x0b, 0x00, 0x0000 }, + { 36, 0x006a, 0x0c00004a, 0x0c, 0x00, 0x0000 }, + { 36, 0x006a, 0x0c00004a, 0x0d, 0x00, 0x0000 }, + { 36, 0x006a, 0x0c00004a, 0x0e, 0x00, 0x0000 }, + { 36, 0x006a, 0x0c00004a, 0x0f, 0x00, 0x0000 }, + { 37, 0x006b, 0x0000004b, 0x00, 0x00, 0x0000 }, + { 37, 0x004b, 0x0000004b, 0x01, 0x00, 0x0000 }, + { 37, 0x006b, 0x0000004b, 0x02, 0x00, 0x0000 }, + { 37, 0x004b, 0x0000004b, 0x03, 0x00, 0x0000 }, + { 37, 0x006b, 0x0400004b, 0x04, 0x00, 0x0000 }, + { 37, 0x006b, 0x0400004b, 0x05, 0x00, 0x0000 }, + { 37, 0x006b, 0x0400004b, 0x06, 0x00, 0x0000 }, + { 37, 0x006b, 0x0400004b, 0x07, 0x00, 0x0000 }, + { 37, 0x006b, 0x0800004b, 0x08, 0x00, 0x0000 }, + { 37, 0x006b, 0x0800004b, 0x09, 0x00, 0x0000 }, + { 37, 0x006b, 0x0800004b, 0x0a, 0x00, 0x0000 }, + { 37, 0x006b, 0x0800004b, 0x0b, 0x00, 0x0000 }, + { 37, 0x006b, 0x0c00004b, 0x0c, 0x00, 0x0000 }, + { 37, 0x006b, 0x0c00004b, 0x0d, 0x00, 0x0000 }, + { 37, 0x006b, 0x0c00004b, 0x0e, 0x00, 0x0000 }, + { 37, 0x006b, 0x0c00004b, 0x0f, 0x00, 0x0000 }, + { 38, 0x006c, 0x0000004c, 0x00, 0x00, 0x0000 }, + { 38, 0x004c, 0x0000004c, 0x01, 0x00, 0x0000 }, + { 38, 0x006c, 0x0000004c, 0x02, 0x00, 0x0000 }, + { 38, 0x004c, 0x0000004c, 0x03, 0x00, 0x0000 }, + { 38, 0x006c, 0x0400004c, 0x04, 0x00, 0x0000 }, + { 38, 0x006c, 0x0400004c, 0x05, 0x00, 0x0000 }, + { 38, 0x006c, 0x0400004c, 0x06, 0x00, 0x0000 }, + { 38, 0x006c, 0x0400004c, 0x07, 0x00, 0x0000 }, + { 38, 0x006c, 0x0800004c, 0x08, 0x00, 0x0000 }, + { 38, 0x006c, 0x0800004c, 0x09, 0x00, 0x0000 }, + { 38, 0x006c, 0x0800004c, 0x0a, 0x00, 0x0000 }, + { 38, 0x006c, 0x0800004c, 0x0b, 0x00, 0x0000 }, + { 38, 0x006c, 0x0c00004c, 0x0c, 0x00, 0x0000 }, + { 38, 0x006c, 0x0c00004c, 0x0d, 0x00, 0x0000 }, + { 38, 0x006c, 0x0c00004c, 0x0e, 0x00, 0x0000 }, + { 38, 0x006c, 0x0c00004c, 0x0f, 0x00, 0x0000 }, + { 39, 0x003b, 0x0000003b, 0x00, 0x00, 0x0000 }, + { 39, 0x003a, 0x0000003a, 0x01, 0x00, 0x0000 }, + { 40, 0x0027, 0x00000027, 0x00, 0x00, 0x0000 }, + { 40, 0x0022, 0x00000022, 0x01, 0x00, 0x0000 }, + { 40, 0x0027, 0x01001251, 0x02, 0x01, 0x0000 }, + { 40, 0x0022, 0x01001257, 0x03, 0x01, 0x0000 }, + { 40, 0x0067, 0x04000047, 0x04, 0x00, 0x0000 }, + { 41, 0x0060, 0x00000060, 0x00, 0x00, 0x0000 }, + { 41, 0x007e, 0x0000007e, 0x01, 0x00, 0x0000 }, + { 41, 0x0060, 0x01001250, 0x02, 0x01, 0x0000 }, + { 41, 0x007e, 0x01001253, 0x03, 0x01, 0x0000 }, + { 42, 0xffff, 0x01000020, 0x00, 0x04, 0x0001 }, + { 43, 0x005c, 0x0000005c, 0x00, 0x00, 0x0000 }, + { 43, 0x007c, 0x0000007c, 0x01, 0x00, 0x0000 }, + { 43, 0x005c, 0x0400005c, 0x04, 0x00, 0x0000 }, + { 44, 0x007a, 0x0000005a, 0x00, 0x00, 0x0000 }, + { 44, 0x005a, 0x0000005a, 0x01, 0x00, 0x0000 }, + { 44, 0x007a, 0x0000005a, 0x02, 0x00, 0x0000 }, + { 44, 0x005a, 0x0000005a, 0x03, 0x00, 0x0000 }, + { 44, 0x007a, 0x0400005a, 0x04, 0x00, 0x0000 }, + { 44, 0x007a, 0x0400005a, 0x05, 0x00, 0x0000 }, + { 44, 0x007a, 0x0400005a, 0x06, 0x00, 0x0000 }, + { 44, 0x007a, 0x0400005a, 0x07, 0x00, 0x0000 }, + { 44, 0x007a, 0x0800005a, 0x08, 0x00, 0x0000 }, + { 44, 0x007a, 0x0800005a, 0x09, 0x00, 0x0000 }, + { 44, 0x007a, 0x0800005a, 0x0a, 0x00, 0x0000 }, + { 44, 0x007a, 0x0800005a, 0x0b, 0x00, 0x0000 }, + { 44, 0x007a, 0x0c00005a, 0x0c, 0x00, 0x0000 }, + { 44, 0x007a, 0x0c00005a, 0x0d, 0x00, 0x0000 }, + { 44, 0x007a, 0x0c00005a, 0x0e, 0x00, 0x0000 }, + { 44, 0x007a, 0x0c00005a, 0x0f, 0x00, 0x0000 }, + { 45, 0x0078, 0x00000058, 0x00, 0x00, 0x0000 }, + { 45, 0x0058, 0x00000058, 0x01, 0x00, 0x0000 }, + { 45, 0x0078, 0x00000058, 0x02, 0x00, 0x0000 }, + { 45, 0x0058, 0x00000058, 0x03, 0x00, 0x0000 }, + { 45, 0x0078, 0x04000058, 0x04, 0x00, 0x0000 }, + { 45, 0x0078, 0x04000058, 0x05, 0x00, 0x0000 }, + { 45, 0x0078, 0x04000058, 0x06, 0x00, 0x0000 }, + { 45, 0x0078, 0x04000058, 0x07, 0x00, 0x0000 }, + { 45, 0x0078, 0x08000058, 0x08, 0x00, 0x0000 }, + { 45, 0x0078, 0x08000058, 0x09, 0x00, 0x0000 }, + { 45, 0x0078, 0x08000058, 0x0a, 0x00, 0x0000 }, + { 45, 0x0078, 0x08000058, 0x0b, 0x00, 0x0000 }, + { 45, 0x0078, 0x0c000058, 0x0c, 0x00, 0x0000 }, + { 45, 0x0078, 0x0c000058, 0x0d, 0x00, 0x0000 }, + { 45, 0x0078, 0x0c000058, 0x0e, 0x00, 0x0000 }, + { 45, 0x0078, 0x0c000058, 0x0f, 0x00, 0x0000 }, + { 46, 0x0063, 0x00000043, 0x00, 0x00, 0x0000 }, + { 46, 0x0043, 0x00000043, 0x01, 0x00, 0x0000 }, + { 46, 0x0063, 0x00000043, 0x02, 0x00, 0x0000 }, + { 46, 0x0043, 0x00000043, 0x03, 0x00, 0x0000 }, + { 46, 0x0063, 0x04000043, 0x04, 0x00, 0x0000 }, + { 46, 0x0063, 0x04000043, 0x05, 0x00, 0x0000 }, + { 46, 0x0063, 0x04000043, 0x06, 0x00, 0x0000 }, + { 46, 0x0063, 0x04000043, 0x07, 0x00, 0x0000 }, + { 46, 0x0063, 0x08000043, 0x08, 0x00, 0x0000 }, + { 46, 0x0063, 0x08000043, 0x09, 0x00, 0x0000 }, + { 46, 0x0063, 0x08000043, 0x0a, 0x00, 0x0000 }, + { 46, 0x0063, 0x08000043, 0x0b, 0x00, 0x0000 }, + { 46, 0x0063, 0x0c000043, 0x0c, 0x00, 0x0000 }, + { 46, 0x0063, 0x0c000043, 0x0d, 0x00, 0x0000 }, + { 46, 0x0063, 0x0c000043, 0x0e, 0x00, 0x0000 }, + { 46, 0x0063, 0x0c000043, 0x0f, 0x00, 0x0000 }, + { 47, 0x0076, 0x00000056, 0x00, 0x00, 0x0000 }, + { 47, 0x0056, 0x00000056, 0x01, 0x00, 0x0000 }, + { 47, 0x0076, 0x00000056, 0x02, 0x00, 0x0000 }, + { 47, 0x0056, 0x00000056, 0x03, 0x00, 0x0000 }, + { 47, 0x0076, 0x04000056, 0x04, 0x00, 0x0000 }, + { 47, 0x0076, 0x04000056, 0x05, 0x00, 0x0000 }, + { 47, 0x0076, 0x04000056, 0x06, 0x00, 0x0000 }, + { 47, 0x0076, 0x04000056, 0x07, 0x00, 0x0000 }, + { 47, 0x0076, 0x08000056, 0x08, 0x00, 0x0000 }, + { 47, 0x0076, 0x08000056, 0x09, 0x00, 0x0000 }, + { 47, 0x0076, 0x08000056, 0x0a, 0x00, 0x0000 }, + { 47, 0x0076, 0x08000056, 0x0b, 0x00, 0x0000 }, + { 47, 0x0076, 0x0c000056, 0x0c, 0x00, 0x0000 }, + { 47, 0x0076, 0x0c000056, 0x0d, 0x00, 0x0000 }, + { 47, 0x0076, 0x0c000056, 0x0e, 0x00, 0x0000 }, + { 47, 0x0076, 0x0c000056, 0x0f, 0x00, 0x0000 }, + { 48, 0x0062, 0x00000042, 0x00, 0x00, 0x0000 }, + { 48, 0x0042, 0x00000042, 0x01, 0x00, 0x0000 }, + { 48, 0x0062, 0x00000042, 0x02, 0x00, 0x0000 }, + { 48, 0x0042, 0x00000042, 0x03, 0x00, 0x0000 }, + { 48, 0x0062, 0x04000042, 0x04, 0x00, 0x0000 }, + { 48, 0x0062, 0x04000042, 0x05, 0x00, 0x0000 }, + { 48, 0x0062, 0x04000042, 0x06, 0x00, 0x0000 }, + { 48, 0x0062, 0x04000042, 0x07, 0x00, 0x0000 }, + { 48, 0x0062, 0x08000042, 0x08, 0x00, 0x0000 }, + { 48, 0x0062, 0x08000042, 0x09, 0x00, 0x0000 }, + { 48, 0x0062, 0x08000042, 0x0a, 0x00, 0x0000 }, + { 48, 0x0062, 0x08000042, 0x0b, 0x00, 0x0000 }, + { 48, 0x0062, 0x0c000042, 0x0c, 0x00, 0x0000 }, + { 48, 0x0062, 0x0c000042, 0x0d, 0x00, 0x0000 }, + { 48, 0x0062, 0x0c000042, 0x0e, 0x00, 0x0000 }, + { 48, 0x0062, 0x0c000042, 0x0f, 0x00, 0x0000 }, + { 49, 0x006e, 0x0000004e, 0x00, 0x00, 0x0000 }, + { 49, 0x004e, 0x0000004e, 0x01, 0x00, 0x0000 }, + { 49, 0x006e, 0x0000004e, 0x02, 0x00, 0x0000 }, + { 49, 0x004e, 0x0000004e, 0x03, 0x00, 0x0000 }, + { 49, 0x006e, 0x0400004e, 0x04, 0x00, 0x0000 }, + { 49, 0x006e, 0x0400004e, 0x05, 0x00, 0x0000 }, + { 49, 0x006e, 0x0400004e, 0x06, 0x00, 0x0000 }, + { 49, 0x006e, 0x0400004e, 0x07, 0x00, 0x0000 }, + { 49, 0x006e, 0x0800004e, 0x08, 0x00, 0x0000 }, + { 49, 0x006e, 0x0800004e, 0x09, 0x00, 0x0000 }, + { 49, 0x006e, 0x0800004e, 0x0a, 0x00, 0x0000 }, + { 49, 0x006e, 0x0800004e, 0x0b, 0x00, 0x0000 }, + { 49, 0x006e, 0x0c00004e, 0x0c, 0x00, 0x0000 }, + { 49, 0x006e, 0x0c00004e, 0x0d, 0x00, 0x0000 }, + { 49, 0x006e, 0x0c00004e, 0x0e, 0x00, 0x0000 }, + { 49, 0x006e, 0x0c00004e, 0x0f, 0x00, 0x0000 }, + { 50, 0x006d, 0x0000004d, 0x00, 0x00, 0x0000 }, + { 50, 0x004d, 0x0000004d, 0x01, 0x00, 0x0000 }, + { 50, 0x006d, 0x0000004d, 0x02, 0x00, 0x0000 }, + { 50, 0x004d, 0x0000004d, 0x03, 0x00, 0x0000 }, + { 50, 0x006d, 0x0400004d, 0x04, 0x00, 0x0000 }, + { 50, 0x006d, 0x0400004d, 0x05, 0x00, 0x0000 }, + { 50, 0x006d, 0x0400004d, 0x06, 0x00, 0x0000 }, + { 50, 0x006d, 0x0400004d, 0x07, 0x00, 0x0000 }, + { 50, 0x006d, 0x0800004d, 0x08, 0x00, 0x0000 }, + { 50, 0x006d, 0x0800004d, 0x09, 0x00, 0x0000 }, + { 50, 0x006d, 0x0800004d, 0x0a, 0x00, 0x0000 }, + { 50, 0x006d, 0x0800004d, 0x0b, 0x00, 0x0000 }, + { 50, 0x006d, 0x0c00004d, 0x0c, 0x00, 0x0000 }, + { 50, 0x006d, 0x0c00004d, 0x0d, 0x00, 0x0000 }, + { 50, 0x006d, 0x0c00004d, 0x0e, 0x00, 0x0000 }, + { 50, 0x006d, 0x0c00004d, 0x0f, 0x00, 0x0000 }, + { 51, 0x002c, 0x0000002c, 0x00, 0x00, 0x0000 }, + { 51, 0x003c, 0x0000003c, 0x01, 0x00, 0x0000 }, + { 51, 0x002c, 0x0100125b, 0x02, 0x01, 0x0000 }, + { 52, 0x002e, 0x0000002e, 0x00, 0x00, 0x0000 }, + { 52, 0x003e, 0x0000003e, 0x01, 0x00, 0x0000 }, + { 52, 0xffff, 0x01001120, 0x02, 0x00, 0x0000 }, + { 53, 0x002f, 0x0000002f, 0x00, 0x00, 0x0000 }, + { 53, 0x003f, 0x0000003f, 0x01, 0x00, 0x0000 }, + { 53, 0xffff, 0x01000003, 0x04, 0x00, 0x0000 }, + { 54, 0xffff, 0x01000020, 0x00, 0x04, 0x0001 }, + { 55, 0x002a, 0x2000002a, 0x00, 0x00, 0x0000 }, + { 56, 0xffff, 0x01000023, 0x00, 0x04, 0x0008 }, + { 57, 0x0020, 0x00000020, 0x00, 0x00, 0x0000 }, + { 58, 0xffff, 0x01000024, 0x00, 0x00, 0x0000 }, + { 59, 0xffff, 0x01000030, 0x00, 0x00, 0x0000 }, + { 59, 0xffff, 0x0100003c, 0x01, 0x00, 0x0000 }, + { 59, 0xffff, 0x01000048, 0x04, 0x00, 0x0000 }, + { 59, 0xffff, 0x01000000, 0x0c, 0x08, 0x0100 }, + { 60, 0xffff, 0x01000031, 0x00, 0x00, 0x0000 }, + { 60, 0xffff, 0x0100003d, 0x01, 0x00, 0x0000 }, + { 60, 0xffff, 0x01000049, 0x04, 0x00, 0x0000 }, + { 60, 0xffff, 0x01000000, 0x0c, 0x08, 0x0101 }, + { 61, 0xffff, 0x01000032, 0x00, 0x00, 0x0000 }, + { 61, 0xffff, 0x0100003e, 0x01, 0x00, 0x0000 }, + { 61, 0xffff, 0x0100004a, 0x04, 0x00, 0x0000 }, + { 61, 0xffff, 0x01000000, 0x0c, 0x08, 0x0102 }, + { 62, 0xffff, 0x01000033, 0x00, 0x00, 0x0000 }, + { 62, 0xffff, 0x0100003f, 0x01, 0x00, 0x0000 }, + { 62, 0xffff, 0x0100004b, 0x04, 0x00, 0x0000 }, + { 62, 0xffff, 0x01000000, 0x0c, 0x08, 0x0103 }, + { 63, 0xffff, 0x01000034, 0x00, 0x00, 0x0000 }, + { 63, 0xffff, 0x01000040, 0x01, 0x00, 0x0000 }, + { 63, 0xffff, 0x0100004c, 0x04, 0x00, 0x0000 }, + { 63, 0xffff, 0x01000000, 0x0c, 0x08, 0x0104 }, + { 64, 0xffff, 0x01000035, 0x00, 0x00, 0x0000 }, + { 64, 0xffff, 0x01000041, 0x01, 0x00, 0x0000 }, + { 64, 0xffff, 0x0100004d, 0x04, 0x00, 0x0000 }, + { 64, 0xffff, 0x01000000, 0x0c, 0x08, 0x0105 }, + { 65, 0xffff, 0x01000036, 0x00, 0x00, 0x0000 }, + { 65, 0xffff, 0x01000042, 0x01, 0x00, 0x0000 }, + { 65, 0xffff, 0x0100004e, 0x04, 0x00, 0x0000 }, + { 65, 0xffff, 0x01000000, 0x0c, 0x08, 0x0106 }, + { 66, 0xffff, 0x01000037, 0x00, 0x00, 0x0000 }, + { 66, 0xffff, 0x01000043, 0x01, 0x00, 0x0000 }, + { 66, 0xffff, 0x0100004f, 0x04, 0x00, 0x0000 }, + { 66, 0xffff, 0x01000000, 0x0c, 0x08, 0x0107 }, + { 67, 0xffff, 0x01000038, 0x00, 0x00, 0x0000 }, + { 67, 0xffff, 0x01000044, 0x01, 0x00, 0x0000 }, + { 67, 0xffff, 0x01000050, 0x04, 0x00, 0x0000 }, + { 67, 0xffff, 0x01000000, 0x0c, 0x08, 0x0108 }, + { 68, 0xffff, 0x01000039, 0x00, 0x00, 0x0000 }, + { 68, 0xffff, 0x01000045, 0x01, 0x00, 0x0000 }, + { 68, 0xffff, 0x01000051, 0x04, 0x00, 0x0000 }, + { 68, 0xffff, 0x01000000, 0x0c, 0x08, 0x0109 }, + { 69, 0xffff, 0x01000025, 0x00, 0x00, 0x0000 }, + { 70, 0xffff, 0x01000026, 0x00, 0x00, 0x0000 }, + { 70, 0xffff, 0x01000026, 0x08, 0x00, 0x0000 }, + { 71, 0x0037, 0x20000037, 0x00, 0x00, 0x0000 }, + { 72, 0x0038, 0x20000038, 0x00, 0x00, 0x0000 }, + { 73, 0x0039, 0x20000039, 0x00, 0x00, 0x0000 }, + { 74, 0x002d, 0x2000002d, 0x00, 0x00, 0x0000 }, + { 75, 0x0034, 0x20000034, 0x00, 0x00, 0x0000 }, + { 76, 0x0035, 0x20000035, 0x00, 0x00, 0x0000 }, + { 77, 0x0036, 0x20000036, 0x00, 0x00, 0x0000 }, + { 78, 0x002b, 0x2000002b, 0x00, 0x00, 0x0000 }, + { 79, 0x0031, 0x20000031, 0x00, 0x00, 0x0000 }, + { 80, 0x0032, 0x20000032, 0x00, 0x00, 0x0000 }, + { 81, 0x0033, 0x20000033, 0x00, 0x00, 0x0000 }, + { 82, 0x0030, 0x20000030, 0x00, 0x00, 0x0000 }, + { 83, 0x002e, 0x2000002e, 0x00, 0x00, 0x0000 }, + { 83, 0xffff, 0x01000000, 0x06, 0x08, 0x0200 }, + { 83, 0xffff, 0x01000000, 0x0c, 0x08, 0x0200 }, + { 86, 0x003c, 0x0000003c, 0x00, 0x00, 0x0000 }, + { 86, 0x003e, 0x0000003e, 0x01, 0x00, 0x0000 }, + { 86, 0x007c, 0x0000007c, 0x02, 0x00, 0x0000 }, + { 87, 0xffff, 0x0100003a, 0x00, 0x00, 0x0000 }, + { 87, 0xffff, 0x01000046, 0x01, 0x00, 0x0000 }, + { 87, 0xffff, 0x01000052, 0x04, 0x00, 0x0000 }, + { 87, 0xffff, 0x01000000, 0x0c, 0x08, 0x010a }, + { 88, 0xffff, 0x0100003b, 0x00, 0x00, 0x0000 }, + { 88, 0xffff, 0x01000047, 0x01, 0x00, 0x0000 }, + { 88, 0xffff, 0x01000000, 0x0c, 0x08, 0x010b }, + { 96, 0xffff, 0x21000005, 0x00, 0x00, 0x0000 }, + { 97, 0xffff, 0x01000021, 0x00, 0x04, 0x0004 }, + { 98, 0x002f, 0x2000002f, 0x00, 0x00, 0x0000 }, + { 99, 0x005c, 0x0400005c, 0x00, 0x00, 0x0000 }, + { 100, 0xffff, 0x01001103, 0x00, 0x04, 0x0002 }, + { 102, 0xffff, 0x01000010, 0x00, 0x00, 0x0000 }, + { 103, 0xffff, 0x01000013, 0x00, 0x00, 0x0000 }, + { 104, 0xffff, 0x01000016, 0x00, 0x00, 0x0000 }, + { 105, 0xffff, 0x01000012, 0x00, 0x00, 0x0000 }, + { 105, 0xffff, 0x01000000, 0x0c, 0x08, 0x0180 }, + { 106, 0xffff, 0x01000014, 0x00, 0x00, 0x0000 }, + { 106, 0xffff, 0x01000000, 0x0c, 0x08, 0x0181 }, + { 107, 0xffff, 0x01000011, 0x00, 0x00, 0x0000 }, + { 108, 0xffff, 0x01000015, 0x00, 0x00, 0x0000 }, + { 109, 0xffff, 0x01000017, 0x00, 0x00, 0x0000 }, + { 110, 0xffff, 0x01000006, 0x00, 0x00, 0x0000 }, + { 111, 0xffff, 0x01000007, 0x00, 0x00, 0x0000 }, + { 111, 0xffff, 0x01000000, 0x06, 0x08, 0x0200 }, + { 111, 0xffff, 0x01000000, 0x0c, 0x08, 0x0200 }, + { 113, 0xffff, 0x01000071, 0x00, 0x00, 0x0000 }, + { 114, 0xffff, 0x01000070, 0x00, 0x00, 0x0000 }, + { 115, 0xffff, 0x01000072, 0x00, 0x00, 0x0000 }, + { 116, 0xffff, 0x0100010b, 0x00, 0x00, 0x0000 }, + { 119, 0xffff, 0x01000008, 0x00, 0x00, 0x0000 }, + { 138, 0xffff, 0x01000058, 0x00, 0x00, 0x0000 }, + { 139, 0xffff, 0x01000055, 0x00, 0x00, 0x0000 }, + { 152, 0xffff, 0x010000ba, 0x00, 0x00, 0x0000 }, + +}; + +const QWSKeyboard::Composing QWSKbPrivate::s_keycompose_default[] = { + { 0x0060, 0x0041, 0x00c0 }, + { 0x0060, 0x0061, 0x00e0 }, + { 0x0027, 0x0041, 0x00c1 }, + { 0x0027, 0x0061, 0x00e1 }, + { 0x005e, 0x0041, 0x00c2 }, + { 0x005e, 0x0061, 0x00e2 }, + { 0x007e, 0x0041, 0x00c3 }, + { 0x007e, 0x0061, 0x00e3 }, + { 0x0022, 0x0041, 0x00c4 }, + { 0x0022, 0x0061, 0x00e4 }, + { 0x002d, 0x0061, 0x00aa }, + { 0x002d, 0x0041, 0x00aa }, + { 0x004f, 0x0041, 0x00c5 }, + { 0x006f, 0x0061, 0x00e5 }, + { 0x0030, 0x0041, 0x00c5 }, + { 0x0030, 0x0061, 0x00e5 }, + { 0x0041, 0x0041, 0x00c5 }, + { 0x0061, 0x0061, 0x00e5 }, + { 0x00b0, 0x0041, 0x00c5 }, + { 0x00b0, 0x0061, 0x00e5 }, + { 0x0041, 0x0045, 0x00c6 }, + { 0x0061, 0x0065, 0x00e6 }, + { 0x002c, 0x0043, 0x00c7 }, + { 0x002c, 0x0063, 0x00e7 }, + { 0x005e, 0x0043, 0x00c7 }, + { 0x005e, 0x0063, 0x00e7 }, + { 0x0060, 0x0045, 0x00c8 }, + { 0x0060, 0x0065, 0x00e8 }, + { 0x0027, 0x0045, 0x00c9 }, + { 0x0027, 0x0065, 0x00e9 }, + { 0x005e, 0x0045, 0x00ca }, + { 0x005e, 0x0065, 0x00ea }, + { 0x0022, 0x0045, 0x00cb }, + { 0x0022, 0x0065, 0x00eb }, + { 0x0060, 0x0049, 0x00cc }, + { 0x0060, 0x0069, 0x00ec }, + { 0x0027, 0x0049, 0x00cd }, + { 0x0027, 0x0069, 0x00ed }, + { 0x005e, 0x0049, 0x00ce }, + { 0x005e, 0x0069, 0x00ee }, + { 0x0022, 0x0049, 0x00cf }, + { 0x0022, 0x0069, 0x00ef }, + { 0x002d, 0x0044, 0x00d0 }, + { 0x002d, 0x0064, 0x00f0 }, + { 0x005e, 0x0044, 0x00d0 }, + { 0x005e, 0x0064, 0x00f0 }, + { 0x007e, 0x004e, 0x00d1 }, + { 0x007e, 0x006e, 0x00f1 }, + { 0x005e, 0x004e, 0x00d1 }, + { 0x005e, 0x006e, 0x00f1 }, + { 0x0060, 0x004f, 0x00d2 }, + { 0x0060, 0x006f, 0x00f2 }, + { 0x0027, 0x004f, 0x00d3 }, + { 0x0027, 0x006f, 0x00f3 }, + { 0x005e, 0x004f, 0x00d4 }, + { 0x005e, 0x006f, 0x00f4 }, + { 0x007e, 0x004f, 0x00d5 }, + { 0x007e, 0x006f, 0x00f5 }, + { 0x0022, 0x004f, 0x00d6 }, + { 0x0022, 0x006f, 0x00f6 }, + { 0x002f, 0x004f, 0x00d8 }, + { 0x002f, 0x006f, 0x00f8 }, + { 0x002d, 0x006f, 0x00ba }, + { 0x002d, 0x004f, 0x00ba }, + { 0x0060, 0x0055, 0x00d9 }, + { 0x0060, 0x0075, 0x00f9 }, + { 0x0027, 0x0055, 0x00da }, + { 0x0027, 0x0075, 0x00fa }, + { 0x005e, 0x0055, 0x00db }, + { 0x005e, 0x0075, 0x00fb }, + { 0x0022, 0x0055, 0x00dc }, + { 0x0022, 0x0075, 0x00fc }, + { 0x0027, 0x0059, 0x00dd }, + { 0x0027, 0x0079, 0x00fd }, + { 0x0054, 0x0048, 0x00de }, + { 0x0074, 0x0068, 0x00fe }, + { 0x0073, 0x0073, 0x00df }, + { 0x0022, 0x0079, 0x00ff }, + { 0x0073, 0x007a, 0x00df }, + { 0x006e, 0x006e, 0x00f1 }, + { 0x006e, 0x0068, 0x00f1 }, + { 0x004e, 0x0059, 0x00d1 }, + { 0x004e, 0x004e, 0x00d1 }, + { 0x004e, 0x0048, 0x00d1 }, + { 0x004e, 0x0079, 0x00d1 }, + { 0x004e, 0x006e, 0x00d1 }, + { 0x004e, 0x0068, 0x00d1 }, + { 0x002d, 0x004c, 0x00a3 }, + { 0x003c, 0x003c, 0x00ab }, + { 0x003e, 0x003e, 0x00bb }, + { 0x003f, 0x003f, 0x00bf }, + { 0x005e, 0x003f, 0x00bf }, + { 0x0021, 0x0021, 0x00a1 }, + { 0x005e, 0x0021, 0x00a1 }, + { 0x005e, 0x0031, 0x00b9 }, + { 0x005e, 0x0032, 0x00b2 }, + { 0x005e, 0x0033, 0x00b3 }, + { 0x002b, 0x002d, 0x00b1 }, + { 0x0063, 0x003d, 0x00a2 }, + { 0x0063, 0x002f, 0x00a2 }, + { 0x002f, 0x0063, 0x00a2 }, + { 0x002d, 0x0063, 0x00a2 }, + { 0x002d, 0x0043, 0x00a2 }, + { 0x004c, 0x003d, 0x00a3 }, + { 0x002d, 0x004c, 0x00a3 }, + { 0x002d, 0x006c, 0x00a3 }, + { 0x005e, 0x002a, 0x00d7 }, + { 0x005e, 0x0078, 0x00d7 }, + { 0x0078, 0x0078, 0x00d7 }, + { 0x005e, 0x002e, 0x00b7 }, + { 0x002e, 0x002e, 0x00b7 }, + { 0x005e, 0x002f, 0x00f7 }, + { 0x005e, 0x003a, 0x00f7 }, + { 0x002d, 0x003a, 0x00f7 }, + { 0x003a, 0x002d, 0x00f7 }, + { 0x0059, 0x003d, 0x00a5 }, + { 0x002d, 0x0059, 0x00a5 }, + { 0x002d, 0x006c, 0x00a5 }, + { 0x0028, 0x0063, 0x00a9 }, + { 0x0022, 0x0063, 0x00a9 }, + { 0x002d, 0x0061, 0x00aa }, + { 0x002d, 0x0041, 0x00aa }, + { 0x002d, 0x006f, 0x00ba }, + { 0x002d, 0x004f, 0x00ba }, + { 0x0028, 0x0072, 0x00ae }, + { 0x0022, 0x0072, 0x00ae }, + { 0x006d, 0x0075, 0x00b5 }, + { 0x0031, 0x0034, 0x0152 }, + { 0x0031, 0x0032, 0x0153 }, + { 0x0033, 0x0034, 0x0178 }, + { 0x0065, 0x003d, 0x20ac }, + { 0x002d, 0x0065, 0x20ac }, + { 0x002d, 0x0045, 0x20ac }, + { 0x0076, 0x0053, 0x0160 }, + { 0x005e, 0x0053, 0x0160 }, + { 0x0076, 0x0073, 0x0161 }, + { 0x005e, 0x0073, 0x0161 }, + { 0x0076, 0x005a, 0x017d }, + { 0x005e, 0x005a, 0x017d }, + { 0x0076, 0x007a, 0x017e }, + { 0x005e, 0x007a, 0x017e }, + { 0x004f, 0x0045, 0x0152 }, + { 0x004f, 0x0065, 0x0152 }, + { 0x006f, 0x0065, 0x0153 }, + { 0x0022, 0x0059, 0x0178 }, + { 0x0069, 0x006a, 0x00ff }, + { 0x0049, 0x004a, 0x0178 }, +}; + +#endif diff --git a/src/gui/embedded/qkbd_qws.cpp b/src/gui/embedded/qkbd_qws.cpp new file mode 100644 index 0000000000..9d2c6e86d8 --- /dev/null +++ b/src/gui/embedded/qkbd_qws.cpp @@ -0,0 +1,693 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbd_qws.h" +#include "qkbd_qws_p.h" + +#ifndef QT_NO_QWS_KEYBOARD + +#include +#include +#include + +#ifdef Q_WS_QWS +#include "qwindowsystem_qws.h" +#include "qscreen_qws.h" +#endif + +#ifdef Q_WS_QPA +#include +#include +#endif + +#include "qtimer.h" +#include + +//#define QT_DEBUG_KEYMAP + + +QT_BEGIN_NAMESPACE + +class QWSKbPrivate : public QObject +{ + Q_OBJECT +public: + QWSKbPrivate(QWSKeyboardHandler *h, const QString &device) + : m_handler(h), m_modifiers(0), m_composing(0), m_dead_unicode(0xffff), + m_no_zap(false), m_do_compose(false), + m_keymap(0), m_keymap_size(0), m_keycompose(0), m_keycompose_size(0) + { + m_ar_timer = new QTimer(this); + m_ar_timer->setSingleShot(true); + connect(m_ar_timer, SIGNAL(timeout()), SLOT(autoRepeat())); + m_ar_delay = 400; + m_ar_period = 80; + + memset(m_locks, 0, sizeof(m_locks)); + + QString keymap; + QStringList args = device.split(QLatin1Char(':')); + foreach (const QString &arg, args) { + if (arg.startsWith(QLatin1String("keymap="))) + keymap = arg.mid(7); + else if (arg == QLatin1String("disable-zap")) + m_no_zap = true; + else if (arg == QLatin1String("enable-compose")) + m_do_compose = true; + else if (arg.startsWith(QLatin1String("repeat-delay="))) + m_ar_delay = arg.mid(13).toInt(); + else if (arg.startsWith(QLatin1String("repeat-rate="))) + m_ar_period = arg.mid(12).toInt(); + } + + if (keymap.isEmpty() || !loadKeymap(keymap)) + unloadKeymap(); + } + + ~QWSKbPrivate() + { + unloadKeymap(); + } + + void beginAutoRepeat(int uni, int code, Qt::KeyboardModifiers mod) + { + m_ar_unicode = uni; + m_ar_keycode = code; + m_ar_modifier = mod; + m_ar_timer->start(m_ar_delay); + } + + void endAutoRepeat() + { + m_ar_timer->stop(); + } + + static Qt::KeyboardModifiers toQtModifiers(quint8 mod) + { + Qt::KeyboardModifiers qtmod = Qt::NoModifier; + + if (mod & (QWSKeyboard::ModShift | QWSKeyboard::ModShiftL | QWSKeyboard::ModShiftR)) + qtmod |= Qt::ShiftModifier; + if (mod & (QWSKeyboard::ModControl | QWSKeyboard::ModCtrlL | QWSKeyboard::ModCtrlR)) + qtmod |= Qt::ControlModifier; + if (mod & QWSKeyboard::ModAlt) + qtmod |= Qt::AltModifier; + + return qtmod; + } + + void unloadKeymap(); + bool loadKeymap(const QString &file); + +private slots: + void autoRepeat() + { + m_handler->processKeyEvent(m_ar_unicode, m_ar_keycode, m_ar_modifier, false, true); + m_handler->processKeyEvent(m_ar_unicode, m_ar_keycode, m_ar_modifier, true, true); + m_ar_timer->start(m_ar_period); + } + +private: + QWSKeyboardHandler *m_handler; + + // auto repeat simulation + int m_ar_unicode; + int m_ar_keycode; + Qt::KeyboardModifiers m_ar_modifier; + int m_ar_delay; + int m_ar_period; + QTimer *m_ar_timer; + + // keymap handling + quint8 m_modifiers; + quint8 m_locks[3]; + int m_composing; + quint16 m_dead_unicode; + + bool m_no_zap; + bool m_do_compose; + + const QWSKeyboard::Mapping *m_keymap; + int m_keymap_size; + const QWSKeyboard::Composing *m_keycompose; + int m_keycompose_size; + + static const QWSKeyboard::Mapping s_keymap_default[]; + static const QWSKeyboard::Composing s_keycompose_default[]; + + friend class QWSKeyboardHandler; +}; + +// simple builtin US keymap +#include "qkbd_defaultmap_qws_p.h" + +// the unloadKeymap() function needs to be AFTER the defaultmap include, +// since the sizeof(s_keymap_default) wouldn't work otherwise. + +void QWSKbPrivate::unloadKeymap() +{ + if (m_keymap && m_keymap != s_keymap_default) + delete [] m_keymap; + if (m_keycompose && m_keycompose != s_keycompose_default) + delete [] m_keycompose; + + m_keymap = s_keymap_default; + m_keymap_size = sizeof(s_keymap_default) / sizeof(s_keymap_default[0]); + m_keycompose = s_keycompose_default; + m_keycompose_size = sizeof(s_keycompose_default) / sizeof(s_keycompose_default[0]); + + // reset state, so we could switch keymaps at runtime + m_modifiers = 0; + memset(m_locks, 0, sizeof(m_locks)); + m_composing = 0; + m_dead_unicode = 0xffff; +} + +bool QWSKbPrivate::loadKeymap(const QString &file) +{ + QFile f(file); + + if (!f.open(QIODevice::ReadOnly)) { + qWarning("Could not open keymap file '%s'", qPrintable(file)); + return false; + } + + // .qmap files have a very simple structure: + // quint32 magic (QWSKeyboard::FileMagic) + // quint32 version (1) + // quint32 keymap_size (# of struct QWSKeyboard::Mappings) + // quint32 keycompose_size (# of struct QWSKeyboard::Composings) + // all QWSKeyboard::Mappings via QDataStream::operator(<<|>>) + // all QWSKeyboard::Composings via QDataStream::operator(<<|>>) + + quint32 qmap_magic, qmap_version, qmap_keymap_size, qmap_keycompose_size; + + QDataStream ds(&f); + + ds >> qmap_magic >> qmap_version >> qmap_keymap_size >> qmap_keycompose_size; + + if (ds.status() != QDataStream::Ok || qmap_magic != QWSKeyboard::FileMagic || qmap_version != 1 || qmap_keymap_size == 0) { + qWarning("'%s' is ot a valid.qmap keymap file.", qPrintable(file)); + return false; + } + + QWSKeyboard::Mapping *qmap_keymap = new QWSKeyboard::Mapping[qmap_keymap_size]; + QWSKeyboard::Composing *qmap_keycompose = qmap_keycompose_size ? new QWSKeyboard::Composing[qmap_keycompose_size] : 0; + + for (quint32 i = 0; i < qmap_keymap_size; ++i) + ds >> qmap_keymap[i]; + for (quint32 i = 0; i < qmap_keycompose_size; ++i) + ds >> qmap_keycompose[i]; + + if (ds.status() != QDataStream::Ok) { + delete [] qmap_keymap; + delete [] qmap_keycompose; + + qWarning("Keymap file '%s' can not be loaded.", qPrintable(file)); + return false; + } + + // unload currently active and clear state + unloadKeymap(); + + m_keymap = qmap_keymap; + m_keymap_size = qmap_keymap_size; + m_keycompose = qmap_keycompose; + m_keycompose_size = qmap_keycompose_size; + + m_do_compose = true; + + return true; +} + + +/*! + \class QWSKeyboardHandler + \ingroup qws + + \brief The QWSKeyboardHandler class is a base class for keyboard + drivers in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several keyboard + protocols, see the \l{Qt for Embedded Linux Character Input}{character + input} documentation for details. Custom keyboard drivers can be + implemented by subclassing the QWSKeyboardHandler class and + creating a keyboard driver plugin (derived from + QKbdDriverPlugin). The default implementation of the + QKbdDriverFactory class will automatically detect the plugin, and + load the driver into the server application at run-time using Qt's + \l{How to Create Qt Plugins}{plugin system}. + + The keyboard driver receives keyboard events from the system + device and encapsulates each event with an instance of the + QWSEvent class which it then passes to the server application (the + server is responsible for propagating the event to the appropriate + client). To receive keyboard events, a QWSKeyboardHandler object + will usually create a QSocketNotifier object for the given + device. The QSocketNotifier class provides support for monitoring + activity on a file descriptor. When the socket notifier receives + data, it will call the keyboard driver's processKeyEvent() + function to send the event to the \l{Qt for Embedded Linux} server + application for relaying to clients. + + + QWSKeyboardHandler also provides functions to control + auto-repetion of key sequences, beginAutoRepeat() and + endAutoRepeat(), and the transformDirKey() function enabling + transformation of arrow keys according to the display orientation. + + \sa QKbdDriverPlugin, QKbdDriverFactory, {Qt for Embedded Linux Character Input} +*/ + + +/*! + Constructs a keyboard driver. The \a device argument is passed by the + QWS_KEYBOARD environment variable. + + Call the QWSServer::setKeyboardHandler() function to make the + newly created keyboard driver, the primary driver. Note that the + primary driver is controlled by the system, i.e., the system will + delete it upon exit. +*/ +QWSKeyboardHandler::QWSKeyboardHandler(const QString &device) +{ + d = new QWSKbPrivate(this, device); +} + +/*! + \overload +*/ +QWSKeyboardHandler::QWSKeyboardHandler() +{ + d = new QWSKbPrivate(this, QString()); +} + + + +/*! + Destroys this keyboard driver. + + Do not call this function if this driver is the primary keyboard + handler, i.e., if QWSServer::setKeyboardHandler() function has + been called passing this driver as argument. The primary keyboard + driver is deleted by the system. +*/ +QWSKeyboardHandler::~QWSKeyboardHandler() +{ + delete d; +} + + +/*! + Sends a key event to the \l{Qt for Embedded Linux} server application. + + The key event is identified by its \a unicode value and the \a + keycode, \a modifiers, \a isPress and \a autoRepeat parameters. + + The \a keycode parameter is the Qt keycode value as defined by the + Qt::Key enum. The \a modifiers is an OR combination of + Qt::KeyboardModifier values, indicating whether \gui + Shift/Alt/Ctrl keys are pressed. The \a isPress parameter is true + if the event is a key press event and \a autoRepeat is true if the + event is caused by an auto-repeat mechanism and not an actual key + press. + + Note that this function does not handle key mapping. Please use + processKeycode() if you need that functionality. + + \sa processKeycode(), beginAutoRepeat(), endAutoRepeat(), transformDirKey() +*/ +void QWSKeyboardHandler::processKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat) +{ +#if defined(Q_WS_QWS) + qwsServer->processKeyEvent(unicode, keycode, modifiers, isPress, autoRepeat); +#elif defined(Q_WS_QPA) + QEvent::Type type = isPress ? QEvent::KeyPress : QEvent::KeyRelease; + QString str; + if (unicode != 0xffff) + str = QString(unicode); + QWindowSystemInterface::handleKeyEvent(0, type, keycode, modifiers, str); +#endif +} + +/*! + \fn int QWSKeyboardHandler::transformDirKey(int keycode) + + Transforms the arrow key specified by the given \a keycode, to the + orientation of the display and returns the transformed keycode. + + The \a keycode is a Qt::Key value. The values identifying arrow + keys are: + + \list + \o Qt::Key_Left + \o Qt::Key_Up + \o Qt::Key_Right + \o Qt::Key_Down + \endlist + + \sa processKeyEvent() + */ +int QWSKeyboardHandler::transformDirKey(int key) +{ +#ifdef Q_WS_QWS + static int dir_keyrot = -1; + if (dir_keyrot < 0) { + // get the rotation + switch (qgetenv("QWS_CURSOR_ROTATION").toInt()) { + case 90: dir_keyrot = 1; break; + case 180: dir_keyrot = 2; break; + case 270: dir_keyrot = 3; break; + default: dir_keyrot = 0; break; + } + } + int xf = qt_screen->transformOrientation() + dir_keyrot; + return (key-Qt::Key_Left+xf)%4+Qt::Key_Left; +#else + return 0; +#endif +} + +/*! + \fn void QWSKeyboardHandler::beginAutoRepeat(int unicode, int keycode, Qt::KeyboardModifiers modifier) + + Begins auto-repeating the specified key press; after a short delay + the key press is sent periodically until the endAutoRepeat() + function is called. + + The key press is specified by its \a unicode, \a keycode and \a + modifier state. + + \sa endAutoRepeat(), processKeyEvent() +*/ +void QWSKeyboardHandler::beginAutoRepeat(int uni, int code, Qt::KeyboardModifiers mod) +{ + d->beginAutoRepeat(uni, code, mod); +} + +/*! + Stops auto-repeating a key press. + + \sa beginAutoRepeat(), processKeyEvent() +*/ +void QWSKeyboardHandler::endAutoRepeat() +{ + d->endAutoRepeat(); +} + +/*! + \enum QWSKeyboardHandler::KeycodeAction + + This enum describes the various special actions that actual + QWSKeyboardHandler implementations have to take care of. + + \value None No further action required. + + \value CapsLockOn Set the state of the Caps lock LED to on. + \value CapsLockOff Set the state of the Caps lock LED to off. + \value NumLockOn Set the state of the Num lock LED to on. + \value NumLockOff Set the state of the Num lock LED to off. + \value ScrollLockOn Set the state of the Scroll lock LED to on. + \value ScrollLockOff Set the state of the Scroll lock LED to off. + + \value PreviousConsole Switch to the previous virtual console (by + default Ctrl+Alt+Left on Linux). + \value NextConsole Switch to the next virtual console (by default + Ctrl+Alt+Right on Linux). + \value SwitchConsoleFirst Switch to the first virtual console (0). + \value SwitchConsoleLast Switch to the last virtual console (255). + \value SwitchConsoleMask If the KeyAction value is between SwitchConsoleFirst + and SwitchConsoleLast, you can use this mask to get + the specific virtual console number to switch to. + + \value Reboot Reboot the machine - this is ignored in both the TTY and + LinuxInput handlers though (by default Ctrl+Alt+Del on Linux). + + \sa processKeycode() +*/ + +/*! + \fn QWSKeyboardHandler::KeycodeAction QWSKeyboardHandler::processKeycode(quint16 keycode, bool isPress, bool autoRepeat) + + \since 4.6 + + Maps \a keycode according to a keymap and sends that key event to the + \l{Qt for Embedded Linux} server application. + + Please see the \l{Qt for Embedded Linux Character Input} and the \l + {kmap2qmap} documentations for a description on how to create and use + keymap files. + + The key event is identified by its \a keycode value and the \a isPress + and \a autoRepeat parameters. + + The \a keycode parameter is \bold NOT the Qt keycode value as defined by + the Qt::Key enum. This functions expects a standard Linux 16 bit kernel + keycode as it is used in the Linux Input Event sub-system. This + \a keycode is transformed to a Qt::Key code by using either a + compiled-in US keyboard layout or by dynamically loading a keymap at + startup which can be specified via the QWS_KEYBOARD environment + variable. + + The \a isPress parameter is true if the event is a key press event and + \a autoRepeat is true if the event is caused by an auto-repeat mechanism + and not an actual key press. + + The return value indicates if the actual QWSKeyboardHandler + implementation needs to take care of a special action, like console + switching or LED handling. + + If standard Linux console keymaps are used, \a keycode must be one of the + standardized values defined in \c /usr/include/linux/input.h + + \sa processKeyEvent(), KeycodeAction +*/ + +QWSKeyboardHandler::KeycodeAction QWSKeyboardHandler::processKeycode(quint16 keycode, bool pressed, bool autorepeat) +{ + KeycodeAction result = None; + bool first_press = pressed && !autorepeat; + + const QWSKeyboard::Mapping *map_plain = 0; + const QWSKeyboard::Mapping *map_withmod = 0; + + // get a specific and plain mapping for the keycode and the current modifiers + for (int i = 0; i < d->m_keymap_size && !(map_plain && map_withmod); ++i) { + const QWSKeyboard::Mapping *m = d->m_keymap + i; + if (m->keycode == keycode) { + if (m->modifiers == 0) + map_plain = m; + + quint8 testmods = d->m_modifiers; + if (d->m_locks[0] /*CapsLock*/ && (m->flags & QWSKeyboard::IsLetter)) + testmods ^= QWSKeyboard::ModShift; + if (m->modifiers == testmods) + map_withmod = m; + } + } + +#ifdef QT_DEBUG_KEYMAP + qWarning("Processing key event: keycode=%3d, modifiers=%02x pressed=%d, autorepeat=%d | plain=%d, withmod=%d, size=%d", \ + keycode, d->m_modifiers, pressed ? 1 : 0, autorepeat ? 1 : 0, \ + map_plain ? map_plain - d->m_keymap : -1, \ + map_withmod ? map_withmod - d->m_keymap : -1, \ + d->m_keymap_size); +#endif + + const QWSKeyboard::Mapping *it = map_withmod ? map_withmod : map_plain; + + if (!it) { +#ifdef QT_DEBUG_KEYMAP + // we couldn't even find a plain mapping + qWarning("Could not find a suitable mapping for keycode: %3d, modifiers: %02x", keycode, d->m_modifiers); +#endif + return result; + } + + bool skip = false; + quint16 unicode = it->unicode; + quint32 qtcode = it->qtcode; + + if ((it->flags & QWSKeyboard::IsModifier) && it->special) { + // this is a modifier, i.e. Shift, Alt, ... + if (pressed) + d->m_modifiers |= quint8(it->special); + else + d->m_modifiers &= ~quint8(it->special); + } else if (qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock) { + // (Caps|Num|Scroll)Lock + if (first_press) { + quint8 &lock = d->m_locks[qtcode - Qt::Key_CapsLock]; + lock ^= 1; + + switch (qtcode) { + case Qt::Key_CapsLock : result = lock ? CapsLockOn : CapsLockOff; break; + case Qt::Key_NumLock : result = lock ? NumLockOn : NumLockOff; break; + case Qt::Key_ScrollLock: result = lock ? ScrollLockOn : ScrollLockOff; break; + default : break; + } + } + } else if ((it->flags & QWSKeyboard::IsSystem) && it->special && first_press) { + switch (it->special) { + case QWSKeyboard::SystemReboot: + result = Reboot; + break; + + case QWSKeyboard::SystemZap: + if (!d->m_no_zap) + qApp->quit(); + break; + + case QWSKeyboard::SystemConsolePrevious: + result = PreviousConsole; + break; + + case QWSKeyboard::SystemConsoleNext: + result = NextConsole; + break; + + default: + if (it->special >= QWSKeyboard::SystemConsoleFirst && + it->special <= QWSKeyboard::SystemConsoleLast) { + result = KeycodeAction(SwitchConsoleFirst + ((it->special & QWSKeyboard::SystemConsoleMask) & SwitchConsoleMask)); + } + break; + } + + skip = true; // no need to tell QWS about it + } else if ((qtcode == Qt::Key_Multi_key) && d->m_do_compose) { + // the Compose key was pressed + if (first_press) + d->m_composing = 2; + skip = true; + } else if ((it->flags & QWSKeyboard::IsDead) && d->m_do_compose) { + // a Dead key was pressed + if (first_press && d->m_composing == 1 && d->m_dead_unicode == unicode) { // twice + d->m_composing = 0; + qtcode = Qt::Key_unknown; // otherwise it would be Qt::Key_Dead... + } else if (first_press && unicode != 0xffff) { + d->m_dead_unicode = unicode; + d->m_composing = 1; + skip = true; + } else { + skip = true; + } + } + + if (!skip) { + // a normal key was pressed + const int modmask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier; + + // we couldn't find a specific mapping for the current modifiers, + // or that mapping didn't have special modifiers: + // so just report the plain mapping with additional modifiers. + if ((it == map_plain && it != map_withmod) || + (map_withmod && !(map_withmod->qtcode & modmask))) { + qtcode |= QWSKbPrivate::toQtModifiers(d->m_modifiers); + } + + if (d->m_composing == 2 && first_press && !(it->flags & QWSKeyboard::IsModifier)) { + // the last key press was the Compose key + if (unicode != 0xffff) { + int idx = 0; + // check if this code is in the compose table at all + for ( ; idx < d->m_keycompose_size; ++idx) { + if (d->m_keycompose[idx].first == unicode) + break; + } + if (idx < d->m_keycompose_size) { + // found it -> simulate a Dead key press + d->m_dead_unicode = unicode; + unicode = 0xffff; + d->m_composing = 1; + skip = true; + } else { + d->m_composing = 0; + } + } else { + d->m_composing = 0; + } + } else if (d->m_composing == 1 && first_press && !(it->flags & QWSKeyboard::IsModifier)) { + // the last key press was a Dead key + bool valid = false; + if (unicode != 0xffff) { + int idx = 0; + // check if this code is in the compose table at all + for ( ; idx < d->m_keycompose_size; ++idx) { + if (d->m_keycompose[idx].first == d->m_dead_unicode && d->m_keycompose[idx].second == unicode) + break; + } + if (idx < d->m_keycompose_size) { + quint16 composed = d->m_keycompose[idx].result; + if (composed != 0xffff) { + unicode = composed; + qtcode = Qt::Key_unknown; + valid = true; + } + } + } + if (!valid) { + unicode = d->m_dead_unicode; + qtcode = Qt::Key_unknown; + } + d->m_composing = 0; + } + + if (!skip) { +#ifdef QT_DEBUG_KEYMAP + qWarning("Processing: uni=%04x, qt=%08x, qtmod=%08x", unicode, qtcode & ~modmask, (qtcode & modmask)); +#endif + + // send the result to the QWS server + processKeyEvent(unicode, qtcode & ~modmask, Qt::KeyboardModifiers(qtcode & modmask), pressed, autorepeat); + } + } + return result; +} + +QT_END_NAMESPACE + +#include "qkbd_qws.moc" + +#endif // QT_NO_QWS_KEYBOARD diff --git a/src/gui/embedded/qkbd_qws.h b/src/gui/embedded/qkbd_qws.h new file mode 100644 index 0000000000..712b9007c7 --- /dev/null +++ b/src/gui/embedded/qkbd_qws.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBD_QWS_H +#define QKBD_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +class QWSKbPrivate; + +class Q_GUI_EXPORT QWSKeyboardHandler +{ +public: + QWSKeyboardHandler(); + QWSKeyboardHandler(const QString &device); + virtual ~QWSKeyboardHandler(); + + virtual void processKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat); + + enum KeycodeAction { + None = 0, + + CapsLockOff = 0x01000000, + CapsLockOn = 0x01000001, + NumLockOff = 0x02000000, + NumLockOn = 0x02000001, + ScrollLockOff = 0x03000000, + ScrollLockOn = 0x03000001, + + Reboot = 0x04000000, + + PreviousConsole = 0x05000000, + NextConsole = 0x05000001, + SwitchConsoleFirst = 0x06000000, + SwitchConsoleLast = 0x0600007f, + SwitchConsoleMask = 0x0000007f, + }; + + KeycodeAction processKeycode(quint16 keycode, bool pressed, bool autorepeat); + +protected: + int transformDirKey(int key); + void beginAutoRepeat(int uni, int code, Qt::KeyboardModifiers mod); + void endAutoRepeat(); + +private: + QWSKbPrivate *d; +}; + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBD_QWS_H diff --git a/src/gui/embedded/qkbd_qws_p.h b/src/gui/embedded/qkbd_qws_p.h new file mode 100644 index 0000000000..be260e3273 --- /dev/null +++ b/src/gui/embedded/qkbd_qws_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSKEYBOARD_P_H +#define QWSKEYBOARD_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 + +QT_BEGIN_NAMESPACE + +namespace QWSKeyboard { + const quint32 FileMagic = 0x514d4150; // 'QMAP' + + struct Mapping { + quint16 keycode; + quint16 unicode; + quint32 qtcode; + quint8 modifiers; + quint8 flags; + quint16 special; + + }; + + enum Flags { + IsDead = 0x01, + IsLetter = 0x02, + IsModifier = 0x04, + IsSystem = 0x08, + }; + + enum System { + SystemConsoleFirst = 0x0100, + SystemConsoleMask = 0x007f, + SystemConsoleLast = 0x017f, + SystemConsolePrevious = 0x0180, + SystemConsoleNext = 0x0181, + SystemReboot = 0x0200, + SystemZap = 0x0300, + }; + + struct Composing { + quint16 first; + quint16 second; + quint16 result; + }; + + enum Modifiers { + ModPlain = 0x00, + ModShift = 0x01, + ModAltGr = 0x02, + ModControl = 0x04, + ModAlt = 0x08, + ModShiftL = 0x10, + ModShiftR = 0x20, + ModCtrlL = 0x40, + ModCtrlR = 0x80, + // ModCapsShift = 0x100, // not supported! + }; +}; + +#ifndef QT_NO_DATASTREAM +inline QDataStream &operator>>(QDataStream &ds, QWSKeyboard::Mapping &m) +{ + return ds >> m.keycode >> m.unicode >> m.qtcode >> m.modifiers >> m.flags >> m.special; +} + +inline QDataStream &operator<<(QDataStream &ds, const QWSKeyboard::Mapping &m) +{ + return ds << m.keycode << m.unicode << m.qtcode << m.modifiers << m.flags << m.special; +} + +inline QDataStream &operator>>(QDataStream &ds, QWSKeyboard::Composing &c) +{ + return ds >> c.first >> c.second >> c.result; +} + +inline QDataStream &operator<<(QDataStream &ds, const QWSKeyboard::Composing &c) +{ + return ds << c.first << c.second << c.result; +} +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +#endif // QWSKEYBOARD_H diff --git a/src/gui/embedded/qkbddriverfactory_qws.cpp b/src/gui/embedded/qkbddriverfactory_qws.cpp new file mode 100644 index 0000000000..45825e52ea --- /dev/null +++ b/src/gui/embedded/qkbddriverfactory_qws.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbddriverfactory_qws.h" + +#ifndef QT_NO_QWS_KEYBOARD + +#include "qapplication.h" +#include "qkbdtty_qws.h" +#include "qkbdlinuxinput_qws.h" +#include "qkbdum_qws.h" +#include "qkbdvfb_qws.h" +#include "qkbdqnx_qws.h" +#include "qkbdintegrity_qws.h" +#include +#include "private/qfactoryloader_p.h" +#include "qkbddriverplugin_qws.h" + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QWSKeyboardHandlerFactoryInterface_iid, + QLatin1String("/kbddrivers"), Qt::CaseInsensitive)) + +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + +/*! + \class QKbdDriverFactory + \ingroup qws + + \brief The QKbdDriverFactory class creates keyboard drivers in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QKbdDriverFactory is used to detect and instantiate the available + keyboard drivers, allowing \l{Qt for Embedded Linux} to load the preferred + driver into the server application at runtime. The create() + function returns a QWSKeyboardHandler object representing the + keyboard driver identified by a given key. The valid keys + (i.e. the supported drivers) can be retrieved using the keys() + function. + + \l{Qt for Embedded Linux} provides several built-in keyboard drivers. In + addition, custom keyboard drivers can be added using Qt's plugin + mechanism, i.e. by subclassing the QWSKeyboardHandler class and + creating a keyboard driver plugin (QKbdDriverPlugin). See the + \l{Qt for Embedded Linux Character Input}{character input} documentation + for details. + + \sa QWSKeyboardHandler, QKbdDriverPlugin +*/ + +/*! + Creates the keyboard driver specified by the given \a key, using + the display specified by the given \a device. + + Note that the keys are case-insensitive. + + \sa keys() +*/ +QWSKeyboardHandler *QKbdDriverFactory::create(const QString& key, const QString& device) +{ + QString driver = key.toLower(); +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_KBD_QNX) + if (driver == QLatin1String("qnx") || driver.isEmpty()) + return new QWSQnxKeyboardHandler(device); +#endif +#if defined(Q_OS_INTEGRITY) + if (driver == QLatin1String("integrity") || driver.isEmpty()) + return new QWSIntKeyboardHandler(device); +#endif +#ifndef QT_NO_QWS_KEYBOARD +# ifndef QT_NO_QWS_KBD_TTY + if (driver == QLatin1String("tty") || driver.isEmpty()) + return new QWSTtyKeyboardHandler(device); +# endif +# ifndef QT_NO_QWS_KBD_LINUXINPUT + if (driver == QLatin1String("linuxinput") || \ + driver == QLatin1String("usb") || \ + driver == QLatin1String("linuxis")) + return new QWSLinuxInputKeyboardHandler(device); +# endif +# ifndef QT_NO_QWS_KBD_UM + if (driver == QLatin1String("um") || driver == QLatin1String("qvfbkeyboard")) + return new QWSUmKeyboardHandler(device); +# endif +# ifndef QT_NO_QWS_KBD_QVFB + if (driver == QLatin1String("qvfbkbd") + || driver == QLatin1String("qvfbkeyboard") + || driver == QLatin1String("qvfb")) + return new QVFbKeyboardHandler(device); +# endif +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + if (QWSKeyboardHandlerFactoryInterface *factory = qobject_cast(loader()->instance(driver))) + return factory->create(driver, device); +#endif +#endif + return 0; +} + +/*! + Returns the list of valid keys, i.e. the available keyboard + drivers. + + \sa create() +*/ +QStringList QKbdDriverFactory::keys() +{ + QStringList list; + +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_KBD_QNX) + list << QLatin1String("QNX"); +#endif +#if defined(Q_OS_INTEGRITY) && !defined(QT_NO_QWS_KBD_INTEGRITY) + list << QLatin1String("INTEGRITY"); +#endif +#ifndef QT_NO_QWS_KBD_TTY + list << QLatin1String("TTY"); +#endif +#ifndef QT_NO_QWS_KBD_LINUXINPUT + list << QLatin1String("LinuxInput"); +#endif +#ifndef QT_NO_QWS_KBD_UM + list << QLatin1String("UM"); +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + QStringList plugins = loader()->keys(); + for (int i = 0; i < plugins.size(); ++i) { + if (!list.contains(plugins.at(i))) + list += plugins.at(i); + } +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + + return list; +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_KEYBOARD diff --git a/src/gui/embedded/qkbddriverfactory_qws.h b/src/gui/embedded/qkbddriverfactory_qws.h new file mode 100644 index 0000000000..ffb768b01f --- /dev/null +++ b/src/gui/embedded/qkbddriverfactory_qws.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDDRIVERFACTORY_QWS_H +#define QKBDDRIVERFACTORY_QWS_H + +#include + +#ifndef QT_NO_QWS_KEYBOARD + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QWSKeyboardHandler; + +class Q_GUI_EXPORT QKbdDriverFactory +{ +public: + static QStringList keys(); + static QWSKeyboardHandler *create(const QString&, const QString&); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_QWS_KEYBOARD +#endif // QKBDDRIVERFACTORY_QWS_H diff --git a/src/gui/embedded/qkbddriverplugin_qws.cpp b/src/gui/embedded/qkbddriverplugin_qws.cpp new file mode 100644 index 0000000000..569cb425c9 --- /dev/null +++ b/src/gui/embedded/qkbddriverplugin_qws.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbddriverplugin_qws.h" + +#ifndef QT_NO_LIBRARY + +#include "qkbd_qws.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QKbdDriverPlugin + \ingroup plugins + \ingroup qws + + \brief The QKbdDriverPlugin class is an abstract base class for + keyboard driver plugins in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several keyboard + protocols, see the \l{Qt for Embedded Linux Character Input}{character + input} documentation for details. Custom keyboard drivers can be + implemented by subclassing the QWSKeyboardHandler class and + creating a keyboard driver plugin. + + A keyboard driver plugin can be created by subclassing + QKbdDriverPlugin and reimplementing the pure virtual keys() and + create() functions. By exporting the derived class using the + Q_EXPORT_PLUGIN2() macro, the default implementation of the + QKbdDriverFactory class will automatically detect the plugin and + load the driver into the server application at run-time. See + \l{How to Create Qt Plugins} for details. + + \sa QKbdDriverFactory, QWSKeyboardHandler +*/ + +/*! + \fn QStringList QKbdDriverPlugin::keys() const + + Implement this function to return the list of valid keys, i.e. the + keyboard drivers supported by this plugin. + + \l{Qt for Embedded Linux} provides ready-made drivers for several keyboard + protocols, see the \l{Qt for Embedded Linux Character Input}{character + input} documentation for details. + + \sa create() +*/ + +/*! + Constructs a keyboard driver plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QKbdDriverPlugin::QKbdDriverPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the keyboard driver plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QKbdDriverPlugin::~QKbdDriverPlugin() +{ +} + +/*! + \fn QScreen *QKbdDriverPlugin::create(const QString &key, const QString &device) + + Implement this function to create a driver matching the type + specified by the given \a key and \a device parameters. Note that + keys are case-insensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_LIBRARY diff --git a/src/gui/embedded/qkbddriverplugin_qws.h b/src/gui/embedded/qkbddriverplugin_qws.h new file mode 100644 index 0000000000..2bb111cbea --- /dev/null +++ b/src/gui/embedded/qkbddriverplugin_qws.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDDRIVERPLUGIN_QWS_H +#define QKBDDRIVERPLUGIN_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LIBRARY + +class QWSKeyboardHandler; + +struct Q_GUI_EXPORT QWSKeyboardHandlerFactoryInterface : public QFactoryInterface +{ + virtual QWSKeyboardHandler* create(const QString &name, const QString &device) = 0; +}; + +#define QWSKeyboardHandlerFactoryInterface_iid "com.trolltech.Qt.QWSKeyboardHandlerFactoryInterface" +Q_DECLARE_INTERFACE(QWSKeyboardHandlerFactoryInterface, QWSKeyboardHandlerFactoryInterface_iid) + +class Q_GUI_EXPORT QKbdDriverPlugin : public QObject, public QWSKeyboardHandlerFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QWSKeyboardHandlerFactoryInterface:QFactoryInterface) +public: + explicit QKbdDriverPlugin(QObject *parent = 0); + ~QKbdDriverPlugin(); + + virtual QStringList keys() const = 0; + virtual QWSKeyboardHandler* create(const QString& driver, const QString &device) = 0; +}; + +#endif // QT_NO_LIBRARY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDDRIVERPLUGIN_QWS_H diff --git a/src/gui/embedded/qkbdintegrity_qws.cpp b/src/gui/embedded/qkbdintegrity_qws.cpp new file mode 100644 index 0000000000..b2df4226a8 --- /dev/null +++ b/src/gui/embedded/qkbdintegrity_qws.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#if !defined(QT_NO_QWS_KEYBOARD) && !defined(QT_NO_QWS_KBD_INTEGRITY) + +#include "qkbdintegrity_qws.h" +#include +#include +#include +#include + +#include + + +//=========================================================================== + +QT_BEGIN_NAMESPACE + +// +// INTEGRITY keyboard +// + +class QIntKeyboardListenThread; + +class QWSIntKbPrivate : public QObject +{ + Q_OBJECT + friend class QIntKeyboardListenThread; +public: + QWSIntKbPrivate(QWSKeyboardHandler *, const QString &device); + ~QWSIntKbPrivate(); + void dataReady(int amount) { emit kbdDataAvailable(amount); } + uint8_t scancodebuf[32 /* USB_SCANCODE_BUF_LEN */ ]; + uint8_t rxpost; + uint8_t rxack; + +Q_SIGNALS: + void kbdDataAvailable(int amount); + +private Q_SLOTS: + void readKeyboardData(int amount); + +private: + QWSKeyboardHandler *handler; + QIntKeyboardListenThread *kbdthread; +}; +class QIntKeyboardListenThread : public QThread +{ +protected: + QWSIntKbPrivate *imp; + bool loop; +public: + QIntKeyboardListenThread(QWSIntKbPrivate *im) : QThread(), imp(im) {}; + ~QIntKeyboardListenThread() {}; + void run(); + void stoploop() { loop = false; }; +}; + + +QWSIntKeyboardHandler::QWSIntKeyboardHandler(const QString &device) + : QWSKeyboardHandler(device) +{ + d = new QWSIntKbPrivate(this, device); +} + +QWSIntKeyboardHandler::~QWSIntKeyboardHandler() +{ + delete d; +} + +//void QWSIntKeyboardHandler::processKeyEvent(int keycode, bool isPress, +// bool autoRepeat) +//{ +// QWSKeyboardHandler::processKeyEvent(keycode, isPress, autoRepeat); +//} + +void QIntKeyboardListenThread::run(void) +{ + Error E; + Buffer b; + Connection kbdc; + bool waitforresource = true; + do { + E = RequestResource((Object*)&kbdc, + "USBKeyboardClient", "!systempassword"); + if (E == Success) { + loop = false; + } else { + E = RequestResource((Object*)&kbdc, + "KeyboardClient", "!systempassword"); + if (E == Success) { + waitforresource = false; + } + } + if (waitforresource) + ::sleep(1); + } while (loop && waitforresource); + if (!loop) + return; + b.BufferType = DataBuffer | LastBuffer; + b.Length = sizeof(imp->scancodebuf); + b.TheAddress = (Address)imp->scancodebuf; + do { + b.Transferred = 0; + b.TheAddress = (Address)imp->scancodebuf + imp->rxpost; + CheckSuccess(SynchronousReceive(kbdc, &b)); + imp->rxpost += b.Transferred; + if (imp->rxpost >= 32 /* USB_SCANCODE_BUF_LEN */) + imp->rxpost = 0; + if (imp->rxpost == (imp->rxack + b.Transferred) % 32 /* USB_SCANCODE_BUF_LEN */) { + imp->kbdDataAvailable(b.Transferred); + } + } while (loop); +} + +void QWSIntKbPrivate::readKeyboardData(int amount) +{ + uint16_t keycode; + do { + if (scancodebuf[rxack] == 0xe0) { + keycode = scancodebuf[rxack] << 8; + rxack++; + if (rxack >= 32 /* USB_SCANCODE_BUF_LEN */) + rxack = 0; + } else { + keycode = 0; + } + + handler->processKeycode(keycode + (scancodebuf[rxack] & 0x7f), + (scancodebuf[rxack] & 0x80) == 0, + scancodebuf[rxack] == 2); + rxack++; + if (rxack >= 32 /* USB_SCANCODE_BUF_LEN */) + rxack = 0; + } while (rxack != rxpost); +} + +QWSIntKbPrivate::QWSIntKbPrivate(QWSKeyboardHandler *h, const QString &device) : handler(h) +{ + connect(this, SIGNAL(kbdDataAvailable(int)), this, SLOT(readKeyboardData(int))); + this->handler = handler; + rxack = rxpost = 0; + kbdthread = new QIntKeyboardListenThread(this); + kbdthread->start(); +} + +QWSIntKbPrivate::~QWSIntKbPrivate() +{ + kbdthread->stoploop(); + kbdthread->wait(); + delete kbdthread; +} + + +QT_END_NAMESPACE + +#include "qkbdintegrity_qws.moc" + +#endif // QT_NO_QWS_KEYBOARD || QT_NO_QWS_KBD_TTY diff --git a/src/gui/embedded/qkbdintegrity_qws.h b/src/gui/embedded/qkbdintegrity_qws.h new file mode 100644 index 0000000000..f9ae4e3250 --- /dev/null +++ b/src/gui/embedded/qkbdintegrity_qws.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDINTEGRITY_QWS_H +#define QKBDINTEGRITY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +#ifndef QT_NO_QWS_KBD_INTEGRITY + +class QSocketNotifier; +class QWSIntKbPrivate; + +class QWSIntKeyboardHandler : public QWSKeyboardHandler +{ +public: + explicit QWSIntKeyboardHandler(const QString&); + virtual ~QWSIntKeyboardHandler(); + +//protected: +// virtual void processKeyEvent(int keycode, bool isPress, bool autoRepeat); + +private: + QWSIntKbPrivate *d; +}; + +#endif // QT_NO_QWS_KBD_INTEGRITY + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDINTEGRITY_QWS_H diff --git a/src/gui/embedded/qkbdlinuxinput_qws.cpp b/src/gui/embedded/qkbdlinuxinput_qws.cpp new file mode 100644 index 0000000000..71910b41e7 --- /dev/null +++ b/src/gui/embedded/qkbdlinuxinput_qws.cpp @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbdlinuxinput_qws.h" + +#ifndef QT_NO_QWS_KEYBOARD + +#include +#include + +#include +#include // overrides QT_OPEN + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + + +class QWSLinuxInputKbPrivate : public QObject +{ + Q_OBJECT +public: + QWSLinuxInputKbPrivate(QWSLinuxInputKeyboardHandler *, const QString &); + ~QWSLinuxInputKbPrivate(); + +private: + void switchLed(int, bool); + +private Q_SLOTS: + void readKeycode(); + +private: + QWSLinuxInputKeyboardHandler *m_handler; + int m_fd; + int m_tty_fd; + struct termios m_tty_attr; + int m_orig_kbmode; +}; + +QWSLinuxInputKeyboardHandler::QWSLinuxInputKeyboardHandler(const QString &device) + : QWSKeyboardHandler(device) +{ + d = new QWSLinuxInputKbPrivate(this, device); +} + +QWSLinuxInputKeyboardHandler::~QWSLinuxInputKeyboardHandler() +{ + delete d; +} + +bool QWSLinuxInputKeyboardHandler::filterInputEvent(quint16 &, qint32 &) +{ + return false; +} + +QWSLinuxInputKbPrivate::QWSLinuxInputKbPrivate(QWSLinuxInputKeyboardHandler *h, const QString &device) + : m_handler(h), m_fd(-1), m_tty_fd(-1), m_orig_kbmode(K_XLATE) +{ + setObjectName(QLatin1String("LinuxInputSubsystem Keyboard Handler")); + + QString dev = QLatin1String("/dev/input/event1"); + int repeat_delay = -1; + int repeat_rate = -1; + + QStringList args = device.split(QLatin1Char(':')); + foreach (const QString &arg, args) { + if (arg.startsWith(QLatin1String("repeat-delay="))) + repeat_delay = arg.mid(13).toInt(); + else if (arg.startsWith(QLatin1String("repeat-rate="))) + repeat_rate = arg.mid(12).toInt(); + else if (arg.startsWith(QLatin1String("/dev/"))) + dev = arg; + } + + m_fd = QT_OPEN(dev.toLocal8Bit().constData(), O_RDWR, 0); + if (m_fd >= 0) { + if (repeat_delay > 0 && repeat_rate > 0) { + int kbdrep[2] = { repeat_delay, repeat_rate }; + ::ioctl(m_fd, EVIOCSREP, kbdrep); + } + + QSocketNotifier *notifier; + notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(notifier, SIGNAL(activated(int)), this, SLOT(readKeycode())); + + // play nice in case we are started from a shell (e.g. for debugging) + m_tty_fd = isatty(0) ? 0 : -1; + + if (m_tty_fd >= 0) { + // save tty config for restore. + tcgetattr(m_tty_fd, &m_tty_attr); + + struct ::termios termdata; + tcgetattr(m_tty_fd, &termdata); + + // record the original mode so we can restore it again in the destructor. + ::ioctl(m_tty_fd, KDGKBMODE, &m_orig_kbmode); + + // setting this translation mode is even needed in INPUT mode to prevent + // the shell from also interpreting codes, if the process has a tty + // attached: e.g. Ctrl+C wouldn't copy, but kill the application. + ::ioctl(m_tty_fd, KDSKBMODE, K_MEDIUMRAW); + + // set the tty layer to pass-through + termdata.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP); + termdata.c_oflag = 0; + termdata.c_cflag = CREAD | CS8; + termdata.c_lflag = 0; + termdata.c_cc[VTIME]=0; + termdata.c_cc[VMIN]=1; + cfsetispeed(&termdata, 9600); + cfsetospeed(&termdata, 9600); + tcsetattr(m_tty_fd, TCSANOW, &termdata); + } + } else { + qWarning("Cannot open keyboard input device '%s': %s", qPrintable(dev), strerror(errno)); + return; + } +} + +QWSLinuxInputKbPrivate::~QWSLinuxInputKbPrivate() +{ + if (m_tty_fd >= 0) { + ::ioctl(m_tty_fd, KDSKBMODE, m_orig_kbmode); + tcsetattr(m_tty_fd, TCSANOW, &m_tty_attr); + } + if (m_fd >= 0) + QT_CLOSE(m_fd); +} + +void QWSLinuxInputKbPrivate::switchLed(int led, bool state) +{ + struct ::input_event led_ie; + ::gettimeofday(&led_ie.time, 0); + led_ie.type = EV_LED; + led_ie.code = led; + led_ie.value = state; + + QT_WRITE(m_fd, &led_ie, sizeof(led_ie)); +} + +void QWSLinuxInputKbPrivate::readKeycode() +{ + struct ::input_event buffer[32]; + int n = 0; + + forever { + n = QT_READ(m_fd, reinterpret_cast(buffer) + n, sizeof(buffer) - n); + + if (n == 0) { + qWarning("Got EOF from the input device."); + return; + } else if (n < 0 && (errno != EINTR && errno != EAGAIN)) { + qWarning("Could not read from input device: %s", strerror(errno)); + return; + } else if (n % sizeof(buffer[0]) == 0) { + break; + } + } + + n /= sizeof(buffer[0]); + + for (int i = 0; i < n; ++i) { + if (buffer[i].type != EV_KEY) + continue; + + quint16 code = buffer[i].code; + qint32 value = buffer[i].value; + + if (m_handler->filterInputEvent(code, value)) + continue; + + QWSKeyboardHandler::KeycodeAction ka; + ka = m_handler->processKeycode(code, value != 0, value == 2); + + switch (ka) { + case QWSKeyboardHandler::CapsLockOn: + case QWSKeyboardHandler::CapsLockOff: + switchLed(LED_CAPSL, ka == QWSKeyboardHandler::CapsLockOn); + break; + + case QWSKeyboardHandler::NumLockOn: + case QWSKeyboardHandler::NumLockOff: + switchLed(LED_NUML, ka == QWSKeyboardHandler::NumLockOn); + break; + + case QWSKeyboardHandler::ScrollLockOn: + case QWSKeyboardHandler::ScrollLockOff: + switchLed(LED_SCROLLL, ka == QWSKeyboardHandler::ScrollLockOn); + break; + + default: + // ignore console switching and reboot + break; + } + } +} + +QT_END_NAMESPACE + +#include "qkbdlinuxinput_qws.moc" + +#endif // QT_NO_QWS_KEYBOARD diff --git a/src/gui/embedded/qkbdlinuxinput_qws.h b/src/gui/embedded/qkbdlinuxinput_qws.h new file mode 100644 index 0000000000..4cd45de854 --- /dev/null +++ b/src/gui/embedded/qkbdlinuxinput_qws.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDLINUXINPUT_QWS_H +#define QKBDLINUXINPUT_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +#ifndef QT_NO_QWS_KBD_LINUXINPUT + +class QWSLinuxInputKbPrivate; + +class QWSLinuxInputKeyboardHandler : public QWSKeyboardHandler +{ +public: + QWSLinuxInputKeyboardHandler(const QString&); + virtual ~QWSLinuxInputKeyboardHandler(); + + virtual bool filterInputEvent(quint16 &input_code, qint32 &input_value); + +private: + QWSLinuxInputKbPrivate *d; +}; + +#endif // QT_NO_QWS_KBD_LINUXINPUT + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDLINUXINPUT_QWS_H diff --git a/src/gui/embedded/qkbdqnx_qws.cpp b/src/gui/embedded/qkbdqnx_qws.cpp new file mode 100644 index 0000000000..87e658d35a --- /dev/null +++ b/src/gui/embedded/qkbdqnx_qws.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbdqnx_qws.h" +#include "QtCore/qsocketnotifier.h" +#include "QtCore/qdebug.h" + +#include +#include + +#include "qplatformdefs.h" +#include + + +QT_BEGIN_NAMESPACE + +/*! + \class QWSQnxKeyboardHandler + \preliminary + \ingroup qws + \since 4.6 + \internal + + \brief The QWSQnxKeyboardHandler class implements a keyboard driver + for the QNX \c{devi-hid} input manager. + + To be able to compile this mouse handler, \l{Qt for Embedded Linux} + must be configured with the \c -qt-kbd-qnx option, see the + \l{Qt for Embedded Linux Character Input} documentation for details. + + In order to use this keyboard handler, the \c{devi-hid} input manager + must be set up and run with the resource manager interface (option \c{-r}). + Also, Photon must not be running. + + Example invocation from command line: \c{/usr/photon/bin/devi-hid -Pr kbd mouse} + Note that after running \c{devi-hid}, you will not be able to use the local + shell anymore. It is suggested to run the command in a shell scrip, that launches + a Qt application after invocation of \c{devi-hid}. + + To make \l{Qt for Embedded Linux} explicitly choose the qnx keyboard + handler, set the QWS_KEYBOARD environment variable to \c{qnx}. By default, + the first keyboard device (\c{/dev/devi/keyboard0}) is used. To override, pass a device + name as the first and only parameter, for example + \c{QWS_KEYBOARD=qnx:/dev/devi/keyboard1; export QWS_KEYBOARD}. + + \sa {Qt for Embedded Linux Character Input}, {Qt for Embedded Linux} +*/ + +/*! + Constructs a keyboard handler for the specified \a device, defaulting to + \c{/dev/devi/keyboard0}. + + Note that you should never instanciate this class, instead let QKbdDriverFactory + handle the keyboard handlers. + + \sa QKbdDriverFactory + */ +QWSQnxKeyboardHandler::QWSQnxKeyboardHandler(const QString &device) +{ + // open the keyboard device + keyboardFD = QT_OPEN(device.isEmpty() ? "/dev/devi/keyboard0" : device.toLatin1().constData(), + QT_OPEN_RDONLY); + if (keyboardFD == -1) { + qErrnoWarning(errno, "QWSQnxKeyboardHandler: Unable to open device"); + return; + } + + // create a socket notifier so we'll wake up whenever keyboard input is detected. + QSocketNotifier *notifier = new QSocketNotifier(keyboardFD, QSocketNotifier::Read, this); + connect(notifier, SIGNAL(activated(int)), SLOT(socketActivated())); + + qDebug() << "QWSQnxKeyboardHandler: connected."; + +} + +/*! + Destroys this keyboard handler and closes the connection to the keyboard device. + */ +QWSQnxKeyboardHandler::~QWSQnxKeyboardHandler() +{ + QT_CLOSE(keyboardFD); +} + +/*! \internal + Translates the QNX keyboard events to Qt keyboard events + */ +void QWSQnxKeyboardHandler::socketActivated() +{ + _keyboard_packet packet; + + // read one keyboard event + int bytesRead = QT_READ(keyboardFD, &packet, sizeof(_keyboard_packet)); + if (bytesRead == -1) { + qErrnoWarning(errno, "QWSQnxKeyboardHandler::socketActivated(): Unable to read data."); + return; + } + + // the bytes read must be the size of a keyboard packet + Q_ASSERT(bytesRead == sizeof(_keyboard_packet)); + +#if 0 + qDebug() << "keyboard got scancode" + << hex << packet.data.modifiers + << packet.data.flags + << packet.data.key_cap + << packet.data.key_sym + << packet.data.key_scan; +#endif + + // QNX is nice enough to translate the raw keyboard data into a QNX data structure + // Now we just have to translate it into a format Qt understands. + + // figure out whether it's a press + bool isPress = packet.data.key_cap & KEY_DOWN; + // figure out whether the key is still pressed and the key event is repeated + bool isRepeat = packet.data.key_cap & KEY_REPEAT; + + Qt::Key key = Qt::Key_unknown; + int unicode = 0xffff; + + // TODO - this switch is not complete! + switch (packet.data.key_scan) { + case KEYCODE_SPACE: key = Qt::Key_Space; unicode = 0x20; break; + case KEYCODE_F1: key = Qt::Key_F1; break; + case KEYCODE_F2: key = Qt::Key_F2; break; + case KEYCODE_F3: key = Qt::Key_F3; break; + case KEYCODE_F4: key = Qt::Key_F4; break; + case KEYCODE_F5: key = Qt::Key_F5; break; + case KEYCODE_F6: key = Qt::Key_F6; break; + case KEYCODE_F7: key = Qt::Key_F7; break; + case KEYCODE_F8: key = Qt::Key_F8; break; + case KEYCODE_F9: key = Qt::Key_F9; break; + case KEYCODE_F10: key = Qt::Key_F10; break; + case KEYCODE_F11: key = Qt::Key_F11; break; + case KEYCODE_F12: key = Qt::Key_F12; break; + case KEYCODE_BACKSPACE: key = Qt::Key_Backspace; break; + case KEYCODE_TAB: key = Qt::Key_Tab; break; + case KEYCODE_RETURN: key = Qt::Key_Return; break; + case KEYCODE_KP_ENTER: key = Qt::Key_Enter; break; + case KEYCODE_UP: + case KEYCODE_KP_UP: + key = Qt::Key_Up; break; + case KEYCODE_DOWN: + case KEYCODE_KP_DOWN: + key = Qt::Key_Down; break; + case KEYCODE_LEFT: + case KEYCODE_KP_LEFT: + key = Qt::Key_Left; break; + case KEYCODE_RIGHT: + case KEYCODE_KP_RIGHT: + key = Qt::Key_Right; break; + case KEYCODE_HOME: + case KEYCODE_KP_HOME: + key = Qt::Key_Home; break; + case KEYCODE_END: + case KEYCODE_KP_END: + key = Qt::Key_End; break; + case KEYCODE_PG_UP: + case KEYCODE_KP_PG_UP: + key = Qt::Key_PageUp; break; + case KEYCODE_PG_DOWN: + case KEYCODE_KP_PG_DOWN: + key = Qt::Key_PageDown; break; + case KEYCODE_INSERT: + case KEYCODE_KP_INSERT: + key = Qt::Key_Insert; break; + case KEYCODE_DELETE: + case KEYCODE_KP_DELETE: + key = Qt::Key_Delete; break; + case KEYCODE_ESCAPE: + key = Qt::Key_Escape; break; + default: // none of the above, try the key_scan directly + unicode = packet.data.key_scan; + break; + } + + // figure out the modifiers that are currently pressed + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + if (packet.data.flags & KEYMOD_SHIFT) + modifiers |= Qt::ShiftModifier; + if (packet.data.flags & KEYMOD_CTRL) + modifiers |= Qt::ControlModifier; + if (packet.data.flags & KEYMOD_ALT) + modifiers |= Qt::AltModifier; + + // if the unicode value is not ascii, we ignore it. + // TODO - do a complete mapping between all QNX scan codes and Qt codes + if (unicode != 0xffff && !isascii(unicode)) + return; // unprintable character + + // call processKeyEvent. This is where all the magic happens to insert a + // key event into Qt's event loop. + // Note that for repeated key events, isPress must be true + // (on QNX, isPress is not set when the key event is repeated). + processKeyEvent(unicode, key, modifiers, isPress || isRepeat, isRepeat); +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qkbdqnx_qws.h b/src/gui/embedded/qkbdqnx_qws.h new file mode 100644 index 0000000000..1615a42fd7 --- /dev/null +++ b/src/gui/embedded/qkbdqnx_qws.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDQNX_QWS_H +#define QKBDQNX_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_QWS_KEYBOARD) && !defined(QT_NO_QWS_KBD_QNX) + +class Q_GUI_EXPORT QWSQnxKeyboardHandler : public QObject, public QWSKeyboardHandler +{ + Q_OBJECT +public: + QWSQnxKeyboardHandler(const QString &device); + ~QWSQnxKeyboardHandler(); + +private Q_SLOTS: + void socketActivated(); + +private: + int keyboardFD; +}; + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDQNX_QWS_H diff --git a/src/gui/embedded/qkbdtty_qws.cpp b/src/gui/embedded/qkbdtty_qws.cpp new file mode 100644 index 0000000000..c4b7df2d9b --- /dev/null +++ b/src/gui/embedded/qkbdtty_qws.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbdtty_qws.h" + +#if !defined(QT_NO_QWS_KEYBOARD) && !defined(QT_NO_QWS_KBD_TTY) + +#include +#include + +#include +#include // overrides QT_OPEN + +#include +#include + +#if defined Q_OS_LINUX +# include +# include //TODO: move vt handling somewhere else (QLinuxFbScreen?) + +# include "qscreen_qws.h" +# include "qwindowsystem_qws.h" +# include "qapplication.h" +# include "private/qwindowsurface_qws_p.h" +# include "private/qwssignalhandler_p.h" + +# define VTACQSIG SIGUSR1 +# define VTRELSIG SIGUSR2 +#endif + + +QT_BEGIN_NAMESPACE + +class QWSTtyKbPrivate : public QObject +{ + Q_OBJECT +public: + QWSTtyKbPrivate(QWSTtyKeyboardHandler *handler, const QString &device); + ~QWSTtyKbPrivate(); + +private: + void switchLed(char, bool); + void switchConsole(int vt); + +private Q_SLOTS: + void readKeycode(); + void handleConsoleSwitch(int sig); + +private: + QWSTtyKeyboardHandler *m_handler; + int m_tty_fd; + struct termios m_tty_attr; + char m_last_keycode; + int m_vt_qws; + int m_orig_kbmode; +}; + + +QWSTtyKeyboardHandler::QWSTtyKeyboardHandler(const QString &device) + : QWSKeyboardHandler(device) +{ + d = new QWSTtyKbPrivate(this, device); +} + +QWSTtyKeyboardHandler::~QWSTtyKeyboardHandler() +{ + delete d; +} + +bool QWSTtyKeyboardHandler::filterKeycode(char &) +{ + return false; +} + +QWSTtyKbPrivate::QWSTtyKbPrivate(QWSTtyKeyboardHandler *h, const QString &device) + : m_handler(h), m_tty_fd(-1), m_last_keycode(0), m_vt_qws(0), m_orig_kbmode(K_XLATE) +{ + setObjectName(QLatin1String("TTY Keyboard Handler")); +#ifndef QT_NO_QWS_SIGNALHANDLER + QWSSignalHandler::instance()->addObject(this); +#endif + + QString dev = QLatin1String("/dev/tty0"); + int repeat_delay = -1; + int repeat_rate = -1; + + QStringList args = device.split(QLatin1Char(':')); + foreach (const QString &arg, args) { + if (arg.startsWith(QLatin1String("repeat-delay="))) + repeat_delay = arg.mid(13).toInt(); + else if (arg.startsWith(QLatin1String("repeat-rate="))) + repeat_rate = arg.mid(12).toInt(); + else if (arg.startsWith(QLatin1String("/dev/"))) + dev = arg; + } + + m_tty_fd = QT_OPEN(dev.toLocal8Bit().constData(), O_RDWR, 0); + if (m_tty_fd >= 0) { + if (repeat_delay > 0 && repeat_rate > 0) { +#if defined(Q_OS_LINUX) + struct ::kbd_repeat kbdrep = { repeat_delay, repeat_rate }; + ::ioctl(m_tty_fd, KDKBDREP, &kbdrep); +#endif + } + + QSocketNotifier *notifier; + notifier = new QSocketNotifier(m_tty_fd, QSocketNotifier::Read, this); + connect(notifier, SIGNAL(activated(int)), this, SLOT(readKeycode())); + + // save tty config for restore. + tcgetattr(m_tty_fd, &m_tty_attr); + + struct ::termios termdata; + tcgetattr(m_tty_fd, &termdata); + +#if defined(Q_OS_LINUX) + // record the original mode so we can restore it again in the destructor. + ::ioctl(m_tty_fd, KDGKBMODE, &m_orig_kbmode); + + // PLEASE NOTE: + // the tty keycode interface can only report keycodes 0x01 .. 0x7f + // KEY_MAX is however defined to 0x1ff. In practice this is sufficient + // for a PC style keyboard though. + // we don't support K_RAW anymore - if you need that, you have to add + // a scan- to keycode converter yourself. + ::ioctl(m_tty_fd, KDSKBMODE, K_MEDIUMRAW); +#endif + + // set the tty layer to pass-through + termdata.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP); + termdata.c_oflag = 0; + termdata.c_cflag = CREAD | CS8; + termdata.c_lflag = 0; + termdata.c_cc[VTIME]=0; + termdata.c_cc[VMIN]=1; + cfsetispeed(&termdata, 9600); + cfsetospeed(&termdata, 9600); + tcsetattr(m_tty_fd, TCSANOW, &termdata); + +#if defined(Q_OS_LINUX) + // VT switching is handled via unix signals + connect(QApplication::instance(), SIGNAL(unixSignal(int)), this, SLOT(handleConsoleSwitch(int))); + QApplication::instance()->watchUnixSignal(VTACQSIG, true); + QApplication::instance()->watchUnixSignal(VTRELSIG, true); + + struct ::vt_mode vtMode; + if (::ioctl(m_tty_fd, VT_GETMODE, &vtMode) == 0) { + vtMode.mode = VT_PROCESS; + vtMode.relsig = VTRELSIG; + vtMode.acqsig = VTACQSIG; + + if (::ioctl(m_tty_fd, VT_SETMODE, &vtMode) == 0) { + struct ::vt_stat vtStat; + ::memset(&vtStat, 0, sizeof(vtStat)); + + if (::ioctl(m_tty_fd, VT_GETSTATE, &vtStat) == 0 ) { + m_vt_qws = vtStat.v_active; + } + } + } + + if (!m_vt_qws) + qWarning("Could not initialize virtual console switching"); +#endif + } else { + qWarning("Cannot open input device '%s': %s", qPrintable(dev), strerror(errno)); + return; + } + +} + +QWSTtyKbPrivate::~QWSTtyKbPrivate() +{ + if (m_tty_fd >= 0) { +#if defined(Q_OS_LINUX) + ::ioctl(m_tty_fd, KDSKBMODE, m_orig_kbmode); +#endif + tcsetattr(m_tty_fd, TCSANOW, &m_tty_attr); + QT_CLOSE(m_tty_fd); + } +} + + + +void QWSTtyKbPrivate::switchLed(char led, bool state) +{ +#if defined(Q_OS_LINUX) + char ledstate; + + ::ioctl(m_tty_fd, KDGETLED, &ledstate); + if (state) + ledstate |= led; + else + ledstate &= ~led; + ::ioctl(m_tty_fd, KDSETLED, ledstate); +#endif +} + +void QWSTtyKbPrivate::readKeycode() +{ + char buffer[32]; + int n = 0; + + forever { + n = QT_READ(m_tty_fd, buffer + n, 32 - n); + + if (n == 0) { + qWarning("Got EOF from the input device."); + return; + } else if (n < 0 && (errno != EINTR && errno != EAGAIN)) { + qWarning("Could not read from input device: %s", strerror(errno)); + return; + } else { + break; + } + } + + for (int i = 0; i < n; ++i) { + if (m_handler->filterKeycode(buffer[i])) + continue; + + QWSKeyboardHandler::KeycodeAction ka; + ka = m_handler->processKeycode(buffer[i] & 0x7f, (buffer[i] & 0x80) == 0x00, buffer[i] == m_last_keycode); + m_last_keycode = buffer[i]; + + switch (ka) { + case QWSKeyboardHandler::CapsLockOn: + case QWSKeyboardHandler::CapsLockOff: + switchLed(LED_CAP, ka == QWSKeyboardHandler::CapsLockOn); + break; + + case QWSKeyboardHandler::NumLockOn: + case QWSKeyboardHandler::NumLockOff: + switchLed(LED_NUM, ka == QWSKeyboardHandler::NumLockOn); + break; + + case QWSKeyboardHandler::ScrollLockOn: + case QWSKeyboardHandler::ScrollLockOff: + switchLed(LED_SCR, ka == QWSKeyboardHandler::ScrollLockOn); + break; + + case QWSKeyboardHandler::PreviousConsole: + switchConsole(qBound(1, m_vt_qws - 1, 10)); + break; + + case QWSKeyboardHandler::NextConsole: + switchConsole(qBound(1, m_vt_qws + 1, 10)); + break; + + default: + if (ka >= QWSKeyboardHandler::SwitchConsoleFirst && + ka <= QWSKeyboardHandler::SwitchConsoleLast) { + switchConsole(1 + (ka & QWSKeyboardHandler::SwitchConsoleMask)); + } + //ignore reboot + break; + } + } +} + + +void QWSTtyKbPrivate::switchConsole(int vt) +{ +#if defined(Q_OS_LINUX) + if (m_vt_qws && vt && (m_tty_fd >= 0 )) + ::ioctl(m_tty_fd, VT_ACTIVATE, vt); +#endif +} + +void QWSTtyKbPrivate::handleConsoleSwitch(int sig) +{ +#if defined(Q_OS_LINUX) + // received a notification from the kernel that the current VT is + // changing: either enable or disable QWS painting accordingly. + + if (sig == VTACQSIG) { + if (::ioctl(m_tty_fd, VT_RELDISP, VT_ACKACQ) == 0) { + qwsServer->enablePainting(true); + qt_screen->restore(); + qwsServer->resumeMouse(); + qwsServer->refresh(); + } + } else if (sig == VTRELSIG) { + qwsServer->enablePainting(false); + + // Check for reserved surfaces which might still do painting + bool allWindowsHidden = true; + const QList windows = QWSServer::instance()->clientWindows(); + for (int i = 0; i < windows.size(); ++i) { + const QWSWindow *w = windows.at(i); + QWSWindowSurface *s = w->windowSurface(); + if (s && s->isRegionReserved() && !w->allocatedRegion().isEmpty()) { + allWindowsHidden = false; + break; + } + } + + if (!allWindowsHidden) { + ::ioctl(m_tty_fd, VT_RELDISP, 0); // abort console switch + qwsServer->enablePainting(true); + } else if (::ioctl(m_tty_fd, VT_RELDISP, 1) == 0) { + qt_screen->save(); + qwsServer->suspendMouse(); + } else { + qwsServer->enablePainting(true); + } + } +#endif +} + +QT_END_NAMESPACE + +#include "qkbdtty_qws.moc" + +#endif // QT_NO_QWS_KEYBOARD || QT_NO_QWS_KBD_TTY diff --git a/src/gui/embedded/qkbdtty_qws.h b/src/gui/embedded/qkbdtty_qws.h new file mode 100644 index 0000000000..373342b87b --- /dev/null +++ b/src/gui/embedded/qkbdtty_qws.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDTTY_QWS_H +#define QKBDTTY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +#ifndef QT_NO_QWS_KBD_TTY + +class QWSTtyKbPrivate; + +class QWSTtyKeyboardHandler : public QWSKeyboardHandler +{ +public: + explicit QWSTtyKeyboardHandler(const QString&); + virtual ~QWSTtyKeyboardHandler(); + + virtual bool filterKeycode(char &code); + +private: + QWSTtyKbPrivate *d; +}; + +#endif // QT_NO_QWS_KBD_TTY + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDTTY_QWS_H diff --git a/src/gui/embedded/qkbdum_qws.cpp b/src/gui/embedded/qkbdum_qws.cpp new file mode 100644 index 0000000000..cfdaa2eed1 --- /dev/null +++ b/src/gui/embedded/qkbdum_qws.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkbdum_qws.h" +#include "qvfbhdr.h" + +#if !defined(QT_NO_QWS_KEYBOARD) && !defined(QT_NO_QWS_KBD_UM) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "qplatformdefs.h" + +QT_BEGIN_NAMESPACE + +class QWSUmKeyboardHandlerPrivate : public QObject +{ + Q_OBJECT + +public: + QWSUmKeyboardHandlerPrivate(const QString&); + ~QWSUmKeyboardHandlerPrivate(); + +private slots: + void readKeyboardData(); + +private: + int kbdFD; + int kbdIdx; + const int kbdBufferLen; + unsigned char *kbdBuffer; + QSocketNotifier *notifier; +}; + +QWSUmKeyboardHandlerPrivate::QWSUmKeyboardHandlerPrivate(const QString &device) + : kbdFD(-1), kbdIdx(0), kbdBufferLen(sizeof(QVFbKeyData)*5) +{ + kbdBuffer = new unsigned char [kbdBufferLen]; + + if ((kbdFD = QT_OPEN((const char *)device.toLocal8Bit(), O_RDONLY | O_NDELAY, 0)) < 0) { + qDebug("Cannot open %s (%s)", (const char *)device.toLocal8Bit(), + strerror(errno)); + } else { + // Clear pending input + char buf[2]; + while (QT_READ(kbdFD, buf, 1) > 0) { } + + notifier = new QSocketNotifier(kbdFD, QSocketNotifier::Read, this); + connect(notifier, SIGNAL(activated(int)),this, SLOT(readKeyboardData())); + } +} + +QWSUmKeyboardHandlerPrivate::~QWSUmKeyboardHandlerPrivate() +{ + if (kbdFD >= 0) + QT_CLOSE(kbdFD); + delete [] kbdBuffer; +} + + +void QWSUmKeyboardHandlerPrivate::readKeyboardData() +{ + int n; + do { + n = QT_READ(kbdFD, kbdBuffer+kbdIdx, kbdBufferLen - kbdIdx); + if (n > 0) + kbdIdx += n; + } while (n > 0); + + int idx = 0; + while (kbdIdx - idx >= (int)sizeof(QVFbKeyData)) { + QVFbKeyData *kd = (QVFbKeyData *)(kbdBuffer + idx); + // Qtopia Key filters must still work. + QWSServer::processKeyEvent(kd->unicode, kd->keycode, kd->modifiers, kd->press, kd->repeat); + idx += sizeof(QVFbKeyData); + } + + int surplus = kbdIdx - idx; + for (int i = 0; i < surplus; i++) + kbdBuffer[i] = kbdBuffer[idx+i]; + kbdIdx = surplus; +} + +QWSUmKeyboardHandler::QWSUmKeyboardHandler(const QString &device) + : QWSKeyboardHandler() +{ + d = new QWSUmKeyboardHandlerPrivate(device); +} + +QWSUmKeyboardHandler::~QWSUmKeyboardHandler() +{ + delete d; +} + +QT_END_NAMESPACE + +#include "qkbdum_qws.moc" + +#endif // QT_NO_QWS_KEYBOARD && QT_NO_QWS_KBD_UM diff --git a/src/gui/embedded/qkbdum_qws.h b/src/gui/embedded/qkbdum_qws.h new file mode 100644 index 0000000000..fd21c4875a --- /dev/null +++ b/src/gui/embedded/qkbdum_qws.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDUM_QWS_H +#define QKBDUM_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +#ifndef QT_NO_QWS_KBD_UM + +class QWSUmKeyboardHandlerPrivate; + +class QWSUmKeyboardHandler : public QWSKeyboardHandler +{ +public: + QWSUmKeyboardHandler(const QString &); + virtual ~QWSUmKeyboardHandler(); + +private: + + QWSUmKeyboardHandlerPrivate *d; +}; +#endif // QT_NO_QWS_KBD_UM + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDUM_QWS_H diff --git a/src/gui/embedded/qkbdvfb_qws.cpp b/src/gui/embedded/qkbdvfb_qws.cpp new file mode 100644 index 0000000000..a626ef5e13 --- /dev/null +++ b/src/gui/embedded/qkbdvfb_qws.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef QT_NO_QWS_KEYBOARD +#ifndef QT_NO_QWS_KBD_QVFB + +#include +#include +#include +#include // overrides QT_OPEN + +QT_BEGIN_NAMESPACE + +QVFbKeyboardHandler::QVFbKeyboardHandler(const QString &device) + : QObject() +{ + terminalName = device; + if (terminalName.isEmpty()) + terminalName = QLatin1String("/dev/vkdb"); + kbdFD = -1; + kbdIdx = 0; + kbdBufferLen = sizeof(QVFbKeyData) * 5; + kbdBuffer = new unsigned char [kbdBufferLen]; + + if ((kbdFD = QT_OPEN(terminalName.toLatin1().constData(), O_RDONLY | O_NDELAY)) < 0) { + qWarning("Cannot open %s (%s)", terminalName.toLatin1().constData(), + strerror(errno)); + } else { + // Clear pending input + char buf[2]; + while (QT_READ(kbdFD, buf, 1) > 0) { } + + notifier = new QSocketNotifier(kbdFD, QSocketNotifier::Read, this); + connect(notifier, SIGNAL(activated(int)),this, SLOT(readKeyboardData())); + } +} + +QVFbKeyboardHandler::~QVFbKeyboardHandler() +{ + if (kbdFD >= 0) + QT_CLOSE(kbdFD); + delete [] kbdBuffer; +} + + +void QVFbKeyboardHandler::readKeyboardData() +{ + int n; + do { + n = QT_READ(kbdFD, kbdBuffer+kbdIdx, kbdBufferLen - kbdIdx); + if (n > 0) + kbdIdx += n; + } while (n > 0); + + int idx = 0; + while (kbdIdx - idx >= (int)sizeof(QVFbKeyData)) { + QVFbKeyData *kd = (QVFbKeyData *)(kbdBuffer + idx); + if (kd->unicode == 0 && kd->keycode == 0 && kd->modifiers == 0 && kd->press) { + // magic exit key + qWarning("Instructed to quit by Virtual Keyboard"); + qApp->quit(); + } + QWSServer::processKeyEvent(kd->unicode ? kd->unicode : 0xffff, kd->keycode, kd->modifiers, kd->press, kd->repeat); + idx += sizeof(QVFbKeyData); + } + + int surplus = kbdIdx - idx; + for (int i = 0; i < surplus; i++) + kbdBuffer[i] = kbdBuffer[idx+i]; + kbdIdx = surplus; +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_KBD_QVFB +#endif // QT_NO_QWS_KEYBOARD diff --git a/src/gui/embedded/qkbdvfb_qws.h b/src/gui/embedded/qkbdvfb_qws.h new file mode 100644 index 0000000000..c99a6df006 --- /dev/null +++ b/src/gui/embedded/qkbdvfb_qws.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKBDVFB_QWS_H +#define QKBDVFB_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_KEYBOARD + +#ifndef QT_NO_QWS_KBD_QVFB + +class QSocketNotifier; + +class QVFbKeyboardHandler : public QObject, public QWSKeyboardHandler +{ + Q_OBJECT +public: + QVFbKeyboardHandler(const QString &device); + virtual ~QVFbKeyboardHandler(); + +private Q_SLOTS: + void readKeyboardData(); + +private: + QString terminalName; + int kbdFD; + int kbdIdx; + int kbdBufferLen; + unsigned char *kbdBuffer; + QSocketNotifier *notifier; +}; + +#endif // QT_NO_QWS_KBD_QVFB + +#endif // QT_NO_QWS_KEYBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKBDVFB_QWS_H diff --git a/src/gui/embedded/qlock.cpp b/src/gui/embedded/qlock.cpp new file mode 100644 index 0000000000..d429b93f26 --- /dev/null +++ b/src/gui/embedded/qlock.cpp @@ -0,0 +1,325 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlock_p.h" + + +#ifdef QT_NO_QWS_MULTIPROCESS + +QT_BEGIN_NAMESPACE + +/* no multiprocess - use a dummy */ + +QLock::QLock(const QString & /*filename*/, char /*id*/, bool /*create*/) + : type(Read), data(0) +{ +} + +QLock::~QLock() +{ +} + +bool QLock::isValid() const +{ + return true; +} + +void QLock::lock(Type t) +{ + data = (QLockData *)-1; + type = t; +} + +void QLock::unlock() +{ + data = 0; +} + +bool QLock::locked() const +{ + return data; +} + +QT_END_NAMESPACE + +#else // QT_NO_QWS_MULTIPROCESS + +#if defined(Q_OS_DARWIN) +# define Q_NO_SEMAPHORE +#endif + +#include "qwssignalhandler_p.h" + +#include +#include +#include +#if defined(Q_NO_SEMAPHORE) +# include +# include +#else +# include +#endif +#include +#include +#include + +#include // overrides QT_OPEN + +QT_BEGIN_NAMESPACE + +#define MAX_LOCKS 200 // maximum simultaneous read locks + +class QLockData +{ +public: +#ifdef Q_NO_SEMAPHORE + QByteArray file; +#endif // Q_NO_SEMAPHORE + int id; + int count; + bool owned; +}; + +/*! + \class QLock + \brief The QLock class is a wrapper for a System V shared semaphore. + + \ingroup qws + + \internal + + It is used by \l{Qt for Embedded Linux} for synchronizing access to the graphics + card and shared memory region between processes. +*/ + +/*! + \enum QLock::Type + + \value Read + \value Write +*/ + +/*! + \fn QLock::QLock(const QString &filename, char id, bool create) + + Creates a lock. \a filename is the file path of the Unix-domain + socket the \l{Qt for Embedded Linux} client is using. \a id is the name of the + particular lock to be created on that socket. If \a create is true + the lock is to be created (as the Qt for Embedded Linux server does); if \a + create is false the lock should exist already (as the Qt for Embedded Linux + client expects). +*/ + +QLock::QLock(const QString &filename, char id, bool create) +{ + data = new QLockData; + data->count = 0; +#ifdef Q_NO_SEMAPHORE + data->file = QString(filename+id).toLocal8Bit().constData(); + for(int x = 0; x < 2; x++) { + data->id = QT_OPEN(data->file, O_RDWR | (x ? O_CREAT : 0), S_IRWXU); + if(data->id != -1 || !create) { + data->owned = x; + break; + } + } +#else + key_t semkey = ftok(filename.toLocal8Bit().constData(), id); + data->id = semget(semkey,0,0); + data->owned = create; + if (create) { + qt_semun arg; arg.val = 0; + if (data->id != -1) + semctl(data->id,0,IPC_RMID,arg); + data->id = semget(semkey,1,IPC_CREAT|0600); + arg.val = MAX_LOCKS; + semctl(data->id,0,SETVAL,arg); + + QWSSignalHandler::instance()->addSemaphore(data->id); + } +#endif + if (data->id == -1) { + int eno = errno; + qWarning("Cannot %s semaphore %s '%c'", (create ? "create" : "get"), + qPrintable(filename), id); + qDebug() << "Error" << eno << strerror(eno); + } +} + +/*! + \fn QLock::~QLock() + + Destroys a lock +*/ + +QLock::~QLock() +{ + if (locked()) + unlock(); +#ifdef Q_NO_SEMAPHORE + if(isValid()) { + QT_CLOSE(data->id); + if(data->owned) + unlink(data->file); + } +#else + if(data->owned) + QWSSignalHandler::instance()->removeSemaphore(data->id); +#endif + delete data; +} + +/*! + \fn bool QLock::isValid() const + + Returns true if the lock constructor was successful; returns false if + the lock could not be created or was not available to connect to. +*/ + +bool QLock::isValid() const +{ + return (data->id != -1); +} + +/*! + Locks the semaphore with a lock of type \a t. Locks can either be + \c Read or \c Write. If a lock is \c Read, attempts by other + processes to obtain \c Read locks will succeed, and \c Write + attempts will block until the lock is unlocked. If locked as \c + Write, all attempts to lock by other processes will block until + the lock is unlocked. Locks are stacked: i.e. a given QLock can be + locked multiple times by the same process without blocking, and + will only be unlocked after a corresponding number of unlock() + calls. +*/ + +void QLock::lock(Type t) +{ + if (!data->count) { +#ifdef Q_NO_SEMAPHORE + int op = LOCK_SH; + if(t == Write) + op = LOCK_EX; + for(int rv=1; rv;) { + rv = flock(data->id, op); + if (rv == -1 && errno != EINTR) + qDebug("Semop lock failure %s",strerror(errno)); + } +#else + sembuf sops; + sops.sem_num = 0; + sops.sem_flg = SEM_UNDO; + + if (t == Write) { + sops.sem_op = -MAX_LOCKS; + type = Write; + } else { + sops.sem_op = -1; + type = Read; + } + + int rv; + do { + rv = semop(data->id,&sops,1); + if (rv == -1 && errno != EINTR) + qDebug("Semop lock failure %s",strerror(errno)); + } while (rv == -1 && errno == EINTR); +#endif + } + data->count++; +} + +/*! + \fn void QLock::unlock() + + Unlocks the semaphore. If other processes were blocking waiting to + lock() the semaphore, one of them will wake up and succeed in + lock()ing. +*/ + +void QLock::unlock() +{ + if(data->count) { + data->count--; + if(!data->count) { +#ifdef Q_NO_SEMAPHORE + for(int rv=1; rv;) { + rv = flock(data->id, LOCK_UN); + if (rv == -1 && errno != EINTR) + qDebug("Semop lock failure %s",strerror(errno)); + } +#else + sembuf sops; + sops.sem_num = 0; + sops.sem_op = 1; + sops.sem_flg = SEM_UNDO; + if (type == Write) + sops.sem_op = MAX_LOCKS; + + int rv; + do { + rv = semop(data->id,&sops,1); + if (rv == -1 && errno != EINTR) + qDebug("Semop unlock failure %s",strerror(errno)); + } while (rv == -1 && errno == EINTR); +#endif + } + } else { + qDebug("Unlock without corresponding lock"); + } +} + +/*! + \fn bool QLock::locked() const + + Returns true if the lock is currently held by the current process; + otherwise returns false. +*/ + +bool QLock::locked() const +{ + return (data->count > 0); +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_MULTIPROCESS + diff --git a/src/gui/embedded/qlock_p.h b/src/gui/embedded/qlock_p.h new file mode 100644 index 0000000000..34560a012e --- /dev/null +++ b/src/gui/embedded/qlock_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCK_P_H +#define QLOCK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// + +#include "QtCore/qstring.h" + +QT_BEGIN_NAMESPACE + +class QLockData; + +class Q_GUI_EXPORT QLock +{ +public: + QLock(const QString &filename, char id, bool create = false); + ~QLock(); + + enum Type { Read, Write }; + + bool isValid() const; + void lock(Type type); + void unlock(); + bool locked() const; + +private: + Type type; + QLockData *data; +}; + + +// Nice class for ensuring the lock is released. +// Just create one on the stack and the lock is automatically released +// when QLockHandle is destructed. +class Q_GUI_EXPORT QLockHandle +{ +public: + QLockHandle(QLock *l, QLock::Type type) : qlock(l) { qlock->lock(type); } + ~QLockHandle() { if (locked()) qlock->unlock(); } + + void lock(QLock::Type type) { qlock->lock(type); } + void unlock() { qlock->unlock(); } + bool locked() const { return qlock->locked(); } + +private: + QLock *qlock; +}; + +QT_END_NAMESPACE + +#endif // QLOCK_P_H diff --git a/src/gui/embedded/qmouse_qws.cpp b/src/gui/embedded/qmouse_qws.cpp new file mode 100644 index 0000000000..c373b916ca --- /dev/null +++ b/src/gui/embedded/qmouse_qws.cpp @@ -0,0 +1,653 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmouse_qws.h" +#include "qwindowsystem_qws.h" +#include "qscreen_qws.h" +#include "qapplication.h" +#include "qtextstream.h" +#include "qfile.h" +#include "qdebug.h" +#include "qscreen_qws.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QWSPointerCalibrationData + \ingroup qws + + \brief The QWSPointerCalibrationData class is a container for + mouse calibration data in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QWSPointerCalibrationData stores device and screen coordinates in + the devPoints and screenPoints variables, respectively. + + A calibration program should create a QWSPointerCalibrationData + object, fill the devPoints and screenPoints variables with its + device and screen coordinates, and pass the object to the mouse + driver using the QWSMouseHandler::calibrate() function. + + \sa QWSCalibratedMouseHandler, {Mouse Calibration Example} +*/ + +/*! + \variable QWSPointerCalibrationData::devPoints + \brief the raw device coordinates for each value of the Location enum. +*/ + +/*! + \variable QWSPointerCalibrationData::screenPoints + \brief the logical screen coordinates for each value of the Location enum. +*/ + +/*! + \enum QWSPointerCalibrationData::Location + + This enum describes the various logical positions that can be + specified by the devPoints and screenPoints variables. + + \value TopLeft Index of the top left corner of the screen. + \value BottomLeft Index of the bottom left corner of the screen. + \value BottomRight Index of the bottom right corner of the screen. + \value TopRight Index of the top right corner of the screen. + \value Center Index of the center of the screen. + \value LastLocation Last index in the pointer arrays. +*/ + +class QWSMouseHandlerPrivate +{ +public: + QWSMouseHandlerPrivate() : screen(qt_screen) {} + + const QScreen *screen; +}; + +/*! + \class QWSMouseHandler + \ingroup qws + + \brief The QWSMouseHandler class is a base class for mouse drivers in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several mouse + protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer + handling} documentation for details. Custom mouse drivers can be + implemented by subclassing the QWSMouseHandler class and creating + a mouse driver plugin (derived from QMouseDriverPlugin). + The default implementation of the QMouseDriverFactory class + will automatically detect the plugin, and load the driver into the + server application at run-time using Qt's \l {How to Create Qt + Plugins}{plugin system}. + + The mouse driver receives mouse events from the system device and + encapsulates each event with an instance of the QWSEvent class + which it then passes to the server application (the server is + responsible for propagating the event to the appropriate + client). To receive mouse events, a QWSMouseHandler object will + usually create a QSocketNotifier object for the given device. The + QSocketNotifier class provides support for monitoring activity on + a file descriptor. When the socket notifier receives data, it will + call the mouse driver's mouseChanged() function to send the event + to the \l{Qt for Embedded Linux} server application for relaying to + clients. + + If you are creating a driver for a device that needs calibration + or noise reduction, such as a touchscreen, use the + QWSCalibratedMouseHandler subclass instead to take advantage of + the calibrate() and clearCalibration() functions. The \l + {qws/mousecalibration}{Mouse Calibration} + demonstrates how to write a simple program using the mechanisms + provided by the QWSMouseHandler class to calibrate a mouse driver. + + Note that when deriving from the QWSMouseHandler class, the + resume() and suspend() functions must be reimplemented to control + the flow of mouse input, i.e., the default implementation does + nothing. Reimplementations of these functions typically call the + QSocketNotifier::setEnabled() function to enable or disable the + socket notifier, respectively. + + In addition, QWSMouseHandler provides the setScreen() function + that allows you to specify a screen for your mouse driver and the + limitToScreen() function that ensures that a given position is + within this screen's boundaries (changing the position if + necessary). Finally, QWSMouseHandler provides the pos() function + returning the current mouse position. + + \sa QMouseDriverPlugin, QMouseDriverFactory, {Qt for Embedded Linux Pointer + Handling} +*/ + + +/*! + \fn void QWSMouseHandler::suspend() + + Implement this function to suspend reading and handling of mouse + events, e.g., call the QSocketNotifier::setEnabled() function to + disable the socket notifier. + + \sa resume() +*/ + +/*! + \fn void QWSMouseHandler::resume() + + Implement this function to resume reading and handling mouse + events, e.g., call the QSocketNotifier::setEnabled() function to + enable the socket notifier. + + \sa suspend() +*/ + +/*! + \fn const QPoint &QWSMouseHandler::pos() const + + Returns the current mouse position. + + \sa mouseChanged(), limitToScreen() +*/ + +/*! + Constructs a mouse driver. The \a driver and \a device arguments + are passed by the QWS_MOUSE_PROTO environment variable. + + Call the QWSServer::setMouseHandler() function to make the newly + created mouse driver, the primary driver. Note that the primary + driver is controlled by the system, i.e., the system will delete + it upon exit. +*/ +QWSMouseHandler::QWSMouseHandler(const QString &, const QString &) + : mousePos(QWSServer::mousePosition), d_ptr(new QWSMouseHandlerPrivate) +{ +} + +/*! + Destroys this mouse driver. + + Do not call this function if this driver is the primary mouse + driver, i.e., if QWSServer::setMouseHandler() function has been + called passing this driver as argument. The primary mouse + driver is deleted by the system. +*/ +QWSMouseHandler::~QWSMouseHandler() +{ + delete d_ptr; +} + +/*! + Ensures that the given \a position is within the screen's + boundaries, changing the \a position if necessary. + + \sa pos(), setScreen() +*/ + +void QWSMouseHandler::limitToScreen(QPoint &position) +{ + position.setX(qMin(d_ptr->screen->deviceWidth() - 1, qMax(0, position.x()))); + position.setY(qMin(d_ptr->screen->deviceHeight() - 1, qMax(0, position.y()))); +} + +/*! + \since 4.2 + + Sets the screen for this mouse driver to be the given \a screen. + + \sa limitToScreen() +*/ +void QWSMouseHandler::setScreen(const QScreen *screen) +{ + d_ptr->screen = (screen ? screen : qt_screen); +} + +/*! + Notifies the system of a new mouse event. + + This function updates the current mouse position and sends the + event to the \l{Qt for Embedded Linux} server application for + delivery to the correct widget. Note that a custom mouse driver must call + this function whenever it wants to deliver a new mouse event. + + The given \a position is the global position of the mouse cursor. + The \a state parameter is a bitmask of the Qt::MouseButton enum's + values, indicating which mouse buttons are pressed. The \a wheel + parameter is the delta value of the mouse wheel as returned by + QWheelEvent::delta(). + + \sa pos() +*/ +void QWSMouseHandler::mouseChanged(const QPoint &position, int state, int wheel) +{ + mousePos = position + d_ptr->screen->offset(); + QWSServer::sendMouseEvent(mousePos, state, wheel); +} + +/*! + \fn QWSMouseHandler::clearCalibration() + + This virtual function allows subclasses of QWSMouseHandler to + clear the calibration information. Note that the default + implementation does nothing. + + \sa QWSCalibratedMouseHandler::clearCalibration(), calibrate() +*/ + +/*! + \fn QWSMouseHandler::calibrate(const QWSPointerCalibrationData *data) + + This virtual function allows subclasses of QWSMouseHandler to set + the calibration information passed in the given \a data. Note that + the default implementation does nothing. + + \sa QWSCalibratedMouseHandler::calibrate(), clearCalibration() +*/ + +/*! \fn QWSMouseHandler::getCalibration(QWSPointerCalibrationData *data) const + This virtual function allows subclasses of QWSMouseHandler + to fill in the device coordinates in \a data with values + that correspond to screen coordinates that are already in + \a data. Note that the default implementation does nothing. + */ + +/*! + \class QWSCalibratedMouseHandler + \ingroup qws + + \brief The QWSCalibratedMouseHandler class provides mouse + calibration and noise reduction in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several mouse + protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer + handling} documentation for details. In general, custom mouse + drivers can be implemented by subclassing the QWSMouseHandler + class. But when the system device does not have a fixed mapping + between device and screen coordinates and/or produces noisy events + (e.g., a touchscreen), you should derive from the + QWSCalibratedMouseHandler class instead to take advantage of its + calibration functionality. As always, you must also create a mouse + driver plugin (derived from QMouseDriverPlugin); + the implementation of the QMouseDriverFactory class will then + automatically detect the plugin, and load the driver into the + server application at run-time using Qt's + \l{How to Create Qt Plugins}{plugin system}. + + QWSCalibratedMouseHandler provides an implementation of the + calibrate() function to update the calibration parameters based on + coordinate mapping of the given calibration data. The calibration + data is represented by an QWSPointerCalibrationData object. The + linear transformation between device coordinates and screen + coordinates is performed by calling the transform() function + explicitly on the points passed to the + QWSMouseHandler::mouseChanged() function. Use the + clearCalibration() function to make the mouse driver return mouse + events in raw device coordinates and not in screen coordinates. + + The calibration parameters are recalculated whenever calibrate() + is called, and they can be stored using the writeCalibration() + function. Previously written parameters can be retrieved at any + time using the readCalibration() function (calibration parameters + are always read when the class is instantiated). Note that the + calibration parameters is written to and read from the file + currently specified by the POINTERCAL_FILE environment variable; + the default file is \c /etc/pointercal. + + To achieve noise reduction, QWSCalibratedMouseHandler provides the + sendFiltered() function. Use this function instead of + mouseChanged() whenever a mouse event occurs. The filter's size + can be manipulated using the setFilterSize() function. + + \sa QWSMouseHandler, QWSPointerCalibrationData, + {Mouse Calibration Example} +*/ + + +/*! + \internal + */ + +QWSCalibratedMouseHandler::QWSCalibratedMouseHandler(const QString &, const QString &) + : samples(5), currSample(0), numSamples(0) +{ + clearCalibration(); + readCalibration(); +} + +/*! + Fills \a cd with the device coordinates corresponding to the given + screen coordinates. + + \internal +*/ +void QWSCalibratedMouseHandler::getCalibration(QWSPointerCalibrationData *cd) const +{ + const qint64 scale = qint64(a) * qint64(e) - qint64(b) * qint64(d); + const qint64 xOff = qint64(b) * qint64(f) - qint64(c) * qint64(e); + const qint64 yOff = qint64(c) * qint64(d) - qint64(a) * qint64(f); + for (int i = 0; i <= QWSPointerCalibrationData::LastLocation; ++i) { + const qint64 sX = cd->screenPoints[i].x(); + const qint64 sY = cd->screenPoints[i].y(); + const qint64 dX = (s*(e*sX - b*sY) + xOff) / scale; + const qint64 dY = (s*(a*sY - d*sX) + yOff) / scale; + cd->devPoints[i] = QPoint(dX, dY); + } +} + +/*! + Clears the current calibration, i.e., makes the mouse + driver return mouse events in raw device coordinates instead of + screen coordinates. + + \sa calibrate() +*/ +void QWSCalibratedMouseHandler::clearCalibration() +{ + a = 1; + b = 0; + c = 0; + d = 0; + e = 1; + f = 0; + s = 1; +} + + +/*! + Saves the current calibration parameters in \c /etc/pointercal + (separated by whitespace and in alphabetical order). + + You can override the default \c /etc/pointercal by specifying + another file using the POINTERCAL_FILE environment variable. + + \sa readCalibration() +*/ +void QWSCalibratedMouseHandler::writeCalibration() +{ + QString calFile; + calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE")); + if (calFile.isEmpty()) + calFile = QLatin1String("/etc/pointercal"); + +#ifndef QT_NO_TEXTSTREAM + QFile file(calFile); + if (file.open(QIODevice::WriteOnly)) { + QTextStream t(&file); + t << a << ' ' << b << ' ' << c << ' '; + t << d << ' ' << e << ' ' << f << ' ' << s << endl; + } else +#endif + { + qCritical("QWSCalibratedMouseHandler::writeCalibration: " + "Could not save calibration into %s", qPrintable(calFile)); + } +} + +/*! + Reads previously written calibration parameters which are stored + in \c /etc/pointercal (separated by whitespace and in alphabetical + order). + + You can override the default \c /etc/pointercal by specifying + another file using the POINTERCAL_FILE environment variable. + + + \sa writeCalibration() +*/ +void QWSCalibratedMouseHandler::readCalibration() +{ + QString calFile = QString::fromLocal8Bit(qgetenv("POINTERCAL_FILE")); + if (calFile.isEmpty()) + calFile = QLatin1String("/etc/pointercal"); + +#ifndef QT_NO_TEXTSTREAM + QFile file(calFile); + if (file.open(QIODevice::ReadOnly)) { + QTextStream t(&file); + t >> a >> b >> c >> d >> e >> f >> s; + if (s == 0 || t.status() != QTextStream::Ok) { + qCritical("Corrupt calibration data"); + clearCalibration(); + } + } else +#endif + { + qDebug() << "Could not read calibration:" <>= 16; + result += 16; + } + if (n & 0xff00) { + n >>= 8; + result += 8;} + if (n & 0xf0) { + n >>= 4; + result += 4; + } + if (n & 0xc) { + n >>= 2; + result += 2; + } + if (n & 0x2) + result += 1; + + return result; +} + +/*! + Updates the calibration parameters based on coordinate mapping of + the given \a data. + + Create an instance of the QWSPointerCalibrationData class, fill in + the device and screen coordinates and pass that object to the mouse + driver using this function. + + \sa clearCalibration(), transform() +*/ +void QWSCalibratedMouseHandler::calibrate(const QWSPointerCalibrationData *data) +{ + // Algorithm derived from + // "How To Calibrate Touch Screens" by Carlos E. Vidales, + // printed in Embedded Systems Programming, Vol. 15 no 6, June 2002 + // URL: http://www.embedded.com/showArticle.jhtml?articleID=9900629 + + const QPoint pd0 = data->devPoints[QWSPointerCalibrationData::TopLeft]; + const QPoint pd1 = data->devPoints[QWSPointerCalibrationData::TopRight]; + const QPoint pd2 = data->devPoints[QWSPointerCalibrationData::BottomRight]; + const QPoint p0 = data->screenPoints[QWSPointerCalibrationData::TopLeft]; + const QPoint p1 = data->screenPoints[QWSPointerCalibrationData::TopRight]; + const QPoint p2 = data->screenPoints[QWSPointerCalibrationData::BottomRight]; + + const qint64 xd0 = pd0.x(); + const qint64 xd1 = pd1.x(); + const qint64 xd2 = pd2.x(); + const qint64 yd0 = pd0.y(); + const qint64 yd1 = pd1.y(); + const qint64 yd2 = pd2.y(); + const qint64 x0 = p0.x(); + const qint64 x1 = p1.x(); + const qint64 x2 = p2.x(); + const qint64 y0 = p0.y(); + const qint64 y1 = p1.y(); + const qint64 y2 = p2.y(); + + qint64 scale = ((xd0 - xd2)*(yd1 - yd2) - (xd1 - xd2)*(yd0 - yd2)); + int shift = 0; + qint64 absScale = qAbs(scale); + // use maximum 16 bit precision to reduce risk of integer overflow + if (absScale > (1 << 16)) { + shift = ilog2(absScale >> 16) + 1; + scale >>= shift; + } + + s = scale; + a = ((x0 - x2)*(yd1 - yd2) - (x1 - x2)*(yd0 - yd2)) >> shift; + b = ((xd0 - xd2)*(x1 - x2) - (x0 - x2)*(xd1 - xd2)) >> shift; + c = (yd0*(xd2*x1 - xd1*x2) + yd1*(xd0*x2 - xd2*x0) + yd2*(xd1*x0 - xd0*x1)) >> shift; + d = ((y0 - y2)*(yd1 - yd2) - (y1 - y2)*(yd0 - yd2)) >> shift; + e = ((xd0 - xd2)*(y1 - y2) - (y0 - y2)*(xd1 - xd2)) >> shift; + f = (yd0*(xd2*y1 - xd1*y2) + yd1*(xd0*y2 - xd2*y0) + yd2*(xd1*y0 - xd0*y1)) >> shift; + + writeCalibration(); +} + +/*! + Transforms the given \a position from device coordinates to screen + coordinates, and returns the transformed position. + + This function is typically called explicitly on the points passed + to the QWSMouseHandler::mouseChanged() function. + + This implementation is a linear transformation using 7 parameters + (\c a, \c b, \c c, \c d, \c e, \c f and \c s) to transform the + device coordinates (\c Xd, \c Yd) into screen coordinates (\c Xs, + \c Ys) using the following equations: + + \snippet doc/src/snippets/code/src_gui_embedded_qmouse_qws.cpp 0 + + \sa mouseChanged() +*/ +QPoint QWSCalibratedMouseHandler::transform(const QPoint &position) +{ + QPoint tp; + + tp.setX((a * position.x() + b * position.y() + c) / s); + tp.setY((d * position.x() + e * position.y() + f) / s); + + return tp; +} + +/*! + Sets the size of the filter used in noise reduction to the given + \a size. + + The sendFiltered() function reduces noice by calculating an + average position from a collection of mouse event positions. The + filter size determines the number of positions that forms the + basis for these calculations. + + \sa sendFiltered() +*/ +void QWSCalibratedMouseHandler::setFilterSize(int size) +{ + samples.resize(qMax(1, size)); + numSamples = 0; + currSample = 0; +} + +/*! + \fn bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int state) + + Notifies the system of a new mouse event \e after applying a noise + reduction filter. Returns true if the filtering process is + successful; otherwise returns false. Note that if the filtering + process failes, the system is not notified about the event. + + The given \a position is the global position of the mouse. The \a + state parameter is a bitmask of the Qt::MouseButton enum's values + indicating which mouse buttons are pressed. + + The noice is reduced by calculating an average position from a + collection of mouse event positions and then calling the + mouseChanged() function with the new position. The number of + positions that is used is determined by the filter size. + + \sa mouseChanged(), setFilterSize() +*/ +bool QWSCalibratedMouseHandler::sendFiltered(const QPoint &position, int button) +{ + if (!button) { + if (numSamples >= samples.count()) + mouseChanged(transform(position), 0); + currSample = 0; + numSamples = 0; + return true; + } + + bool sent = false; + samples[currSample] = position; + numSamples++; + if (numSamples >= samples.count()) { + + int ignore = -1; + if (samples.count() > 2) { // throw away the "worst" sample + int maxd = 0; + for (int i = 0; i < samples.count(); i++) { + int d = (mousePos - samples[i]).manhattanLength(); + if (d > maxd) { + maxd = d; + ignore = i; + } + } + } + + // average the rest + QPoint pos(0, 0); + int numAveraged = 0; + for (int i = 0; i < samples.count(); i++) { + if (ignore == i) + continue; + pos += samples[i]; + ++numAveraged; + } + if (numAveraged) + pos /= numAveraged; + + mouseChanged(transform(pos), button); + sent = true; + } + currSample++; + if (currSample >= samples.count()) + currSample = 0; + + return sent; +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qmouse_qws.h b/src/gui/embedded/qmouse_qws.h new file mode 100644 index 0000000000..8f2cbe5fc2 --- /dev/null +++ b/src/gui/embedded/qmouse_qws.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSE_QWS_H +#define QMOUSE_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWSMouseHandlerPrivate; +class QScreen; + +class Q_GUI_EXPORT QWSPointerCalibrationData +{ +public: + enum Location { TopLeft = 0, BottomLeft = 1, BottomRight = 2, TopRight = 3, + Center = 4, LastLocation = Center }; + QPoint devPoints[5]; + QPoint screenPoints[5]; +}; + +class Q_GUI_EXPORT QWSMouseHandler +{ +public: + explicit QWSMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + virtual ~QWSMouseHandler(); + + virtual void clearCalibration() {} + virtual void calibrate(const QWSPointerCalibrationData *) {} + virtual void getCalibration(QWSPointerCalibrationData *) const {} + + virtual void resume() = 0; + virtual void suspend() = 0; + + void limitToScreen(QPoint &pt); + void mouseChanged(const QPoint& pos, int bstate, int wheel = 0); + const QPoint &pos() const { return mousePos; } + + void setScreen(const QScreen *screen); + +protected: + QPoint &mousePos; + QWSMouseHandlerPrivate *d_ptr; +}; + + +class Q_GUI_EXPORT QWSCalibratedMouseHandler : public QWSMouseHandler +{ +public: + explicit QWSCalibratedMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + + virtual void clearCalibration(); + virtual void calibrate(const QWSPointerCalibrationData *); + virtual void getCalibration(QWSPointerCalibrationData *) const; + +protected: + bool sendFiltered(const QPoint &, int button); + QPoint transform(const QPoint &); + + void readCalibration(); + void writeCalibration(); + void setFilterSize(int); + +private: + int a, b, c; + int d, e, f; + int s; + QPolygon samples; + int currSample; + int numSamples; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSE_QWS_H diff --git a/src/gui/embedded/qmousedriverfactory_qws.cpp b/src/gui/embedded/qmousedriverfactory_qws.cpp new file mode 100644 index 0000000000..f67284d6d5 --- /dev/null +++ b/src/gui/embedded/qmousedriverfactory_qws.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmousedriverfactory_qws.h" + +#include "qapplication.h" +#include "qmousepc_qws.h" +#include "qmouselinuxtp_qws.h" +#include "qmouselinuxinput_qws.h" +#include "qmousevfb_qws.h" +#include "qmousetslib_qws.h" +#include "qmouseqnx_qws.h" +#include "qmouseintegrity_qws.h" +#include +#include "private/qfactoryloader_p.h" +#include "qmousedriverplugin_qws.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QWSMouseHandlerFactoryInterface_iid, + QLatin1String("/mousedrivers"), Qt::CaseInsensitive)) + +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + +/*! + \class QMouseDriverFactory + \ingroup qws + + \brief The QMouseDriverFactory class creates mouse drivers in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QMouseDriverFactory is used to detect and instantiate the + available mouse drivers, allowing \l{Qt for Embedded Linux} to load the + preferred driver into the server application at runtime. The + create() function returns a QWSMouseHandler object representing + the mouse driver identified by a given key. The valid keys + (i.e. the supported drivers) can be retrieved using the keys() + function. + + \l{Qt for Embedded Linux} provides several built-in mouse drivers. In + addition, custom mouse drivers can be added using Qt's plugin + mechanism, i.e. by subclassing the QWSMouseHandler class and + creating a mouse driver plugin (QMouseDriverPlugin). See the + \l{Qt for Embedded Linux Pointer Handling}{pointer handling} + documentation for details. + + \sa QWSMouseHandler, QMouseDriverPlugin +*/ + +/*! + Creates the mouse driver specified by the given \a key, using the + display specified by the given \a device. + + Note that the keys are case-insensitive. + + \sa keys() +*/ +QWSMouseHandler *QMouseDriverFactory::create(const QString& key, const QString &device) +{ + QString driver = key.toLower(); +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_MOUSE_QNX) + if (driver == QLatin1String("qnx") || driver.isEmpty()) + return new QQnxMouseHandler(key, device); +#endif +#if defined(Q_OS_INTEGRITY) && !defined(QT_NO_MOUSE_INTEGRITY) + if (driver == QLatin1String("integrity") || driver.isEmpty()) + return new QIntMouseHandler(key, device); +#endif +#ifndef QT_NO_QWS_MOUSE_LINUXTP + if (driver == QLatin1String("linuxtp") || driver.isEmpty()) + return new QWSLinuxTPMouseHandler(key, device); +#endif +#ifndef QT_NO_QWS_MOUSE_PC + if (driver == QLatin1String("auto") + || driver == QLatin1String("intellimouse") + || driver == QLatin1String("microsoft") + || driver == QLatin1String("mousesystems") + || driver == QLatin1String("mouseman") + || driver.isEmpty()) { + return new QWSPcMouseHandler(key, device); + } +#endif +#ifndef QT_NO_QWS_MOUSE_TSLIB + if (driver == QLatin1String("tslib") || driver.isEmpty()) + return new QWSTslibMouseHandler(key, device); +#endif +# ifndef QT_NO_QWS_MOUSE_LINUXINPUT + if (driver == QLatin1String("linuxinput") || \ + driver == QLatin1String("usb") || \ + driver == QLatin1String("linuxis")) + return new QWSLinuxInputMouseHandler(device); +# endif +#ifndef QT_NO_QWS_MOUSE_QVFB + if (driver == QLatin1String("qvfbmouse") || driver == QLatin1String("qvfb")) + return new QVFbMouseHandler(key, device); +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + if (QWSMouseHandlerFactoryInterface *factory = qobject_cast(loader()->instance(driver))) + return factory->create(driver, device); +#endif +#endif + return 0; +} + +/*! + Returns the list of valid keys, i.e. the available mouse drivers. + + \sa create() +*/ +QStringList QMouseDriverFactory::keys() +{ + QStringList list; + +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_MOUSE_QNX) + list << QLatin1String("QNX"); +#endif +#if defined(Q_OS_INTEGRITY) && !defined(QT_NO_QWS_MOUSE_INTEGRITY) + list << QLatin1String("INTEGRITY"); +#endif +#ifndef QT_NO_QWS_MOUSE_LINUXTP + list << QLatin1String("LinuxTP"); +#endif +#ifndef QT_NO_QWS_MOUSE_PC + list << QLatin1String("Auto") + << QLatin1String("IntelliMouse") + << QLatin1String("Microsoft") + << QLatin1String("MouseSystems") + << QLatin1String("MouseMan"); +#endif +#ifndef QT_NO_QWS_MOUSE_TSLIB + list << QLatin1String("Tslib"); +#endif +#ifndef QT_NO_QWS_MOUSE_LINUXINPUT + list << QLatin1String("LinuxInput"); +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + QStringList plugins = loader()->keys(); + for (int i = 0; i < plugins.size(); ++i) { + if (!list.contains(plugins.at(i))) + list += plugins.at(i); + } +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + return list; +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qmousedriverfactory_qws.h b/src/gui/embedded/qmousedriverfactory_qws.h new file mode 100644 index 0000000000..0eac39a758 --- /dev/null +++ b/src/gui/embedded/qmousedriverfactory_qws.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEDRIVERFACTORY_QWS_H +#define QMOUSEDRIVERFACTORY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QWSMouseHandler; + +class Q_GUI_EXPORT QMouseDriverFactory +{ +public: + static QStringList keys(); + static QWSMouseHandler *create(const QString&, const QString &); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSEDRIVERFACTORY_QWS_H diff --git a/src/gui/embedded/qmousedriverplugin_qws.cpp b/src/gui/embedded/qmousedriverplugin_qws.cpp new file mode 100644 index 0000000000..4fae54b6b7 --- /dev/null +++ b/src/gui/embedded/qmousedriverplugin_qws.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmousedriverplugin_qws.h" + +#ifndef QT_NO_LIBRARY + +#include "qmouse_qws.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QMouseDriverPlugin + \ingroup plugins + \ingroup qws + + \brief The QMouseDriverPlugin class is an abstract base class for + mouse driver plugins in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several mouse + protocols, see the \l{Qt for Embedded Linux Pointer Handling}{pointer + handling} documentation for details. Custom mouse drivers can be + implemented by subclassing the QWSMouseHandler class and creating + a mouse driver plugin. + + A mouse driver plugin can be created by subclassing + QMouseDriverPlugin and reimplementing the pure virtual keys() and + create() functions. By exporting the derived class using the + Q_EXPORT_PLUGIN2() macro, The default implementation of the + QMouseDriverFactory class will automatically detect the plugin and + load the driver into the server application at run-time. See \l + {How to Create Qt Plugins} for details. + + \sa QWSMouseHandler, QMouseDriverFactory +*/ + +/*! + \fn QStringList QMouseDriverPlugin::keys() const + + Implement this function to return the list of valid keys, i.e. the + mouse drivers supported by this plugin. + + \l{Qt for Embedded Linux} provides ready-made drivers for several mouse + protocols, see the \l {Qt for Embedded Linux Pointer Handling}{pointer + handling} documentation for details. + + \sa create() +*/ + +/*! + Constructs a mouse driver plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QMouseDriverPlugin::QMouseDriverPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the mouse driver plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QMouseDriverPlugin::~QMouseDriverPlugin() +{ +} + +/*! + \fn QScreen* QMouseDriverPlugin::create(const QString &key, const QString& device) + + Implement this function to create a driver matching the type + specified by the given \a key and \a device parameters. Note that + keys are case-insensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_LIBRARY diff --git a/src/gui/embedded/qmousedriverplugin_qws.h b/src/gui/embedded/qmousedriverplugin_qws.h new file mode 100644 index 0000000000..275bbd1514 --- /dev/null +++ b/src/gui/embedded/qmousedriverplugin_qws.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEDRIVERPLUGIN_QWS_H +#define QMOUSEDRIVERPLUGIN_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LIBRARY + +class QWSMouseHandler; + +struct Q_GUI_EXPORT QWSMouseHandlerFactoryInterface : public QFactoryInterface +{ + virtual QWSMouseHandler* create(const QString &name, const QString &device) = 0; +}; + +#define QWSMouseHandlerFactoryInterface_iid "com.trolltech.Qt.QWSMouseHandlerFactoryInterface" +Q_DECLARE_INTERFACE(QWSMouseHandlerFactoryInterface, QWSMouseHandlerFactoryInterface_iid) + +class Q_GUI_EXPORT QMouseDriverPlugin : public QObject, public QWSMouseHandlerFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QWSMouseHandlerFactoryInterface:QFactoryInterface) +public: + explicit QMouseDriverPlugin(QObject *parent = 0); + ~QMouseDriverPlugin(); + + virtual QStringList keys() const = 0; + virtual QWSMouseHandler* create(const QString& driver, const QString &device) = 0; +}; + +#endif // QT_NO_LIBRARY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSEDRIVERPLUGIN_QWS_H diff --git a/src/gui/embedded/qmouseintegrity_qws.cpp b/src/gui/embedded/qmouseintegrity_qws.cpp new file mode 100644 index 0000000000..78a8c1b509 --- /dev/null +++ b/src/gui/embedded/qmouseintegrity_qws.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QWS_MOUSE_INTEGRITY + +#include "qmouseintegrity_qws.h" +#include +#include +#include +#include + +#include + + +typedef Address MOUSEHandler; +typedef struct MOUSEMessageStruct +{ + Value x; + Value y; + Value z; + Value buttons; +} MOUSEMessage; + +static Error MOUSE_Init(MOUSEHandler *handler, Boolean *isabsolute); +static Error MOUSE_SynchronousGetPosition(MOUSEHandler handler, MOUSEMessage *msg, + Boolean absolute); +static Error MOUSE_ShouldFilter(MOUSEHandler handler, Boolean *filter); + +QT_BEGIN_NAMESPACE + +class QIntMouseListenThread; + +class QIntMousePrivate : public QObject +{ + Q_OBJECT + friend class QIntMouseListenTaskThread; +Q_SIGNALS: + void mouseDataAvailable(int x, int y, int buttons); +public: + QIntMousePrivate(QIntMouseHandler *handler); + ~QIntMousePrivate(); + void dataReady(int x, int y, int buttons) { emit mouseDataAvailable(x, y, buttons); } + bool calibrated; + bool waitforread; + bool suspended; + QIntMouseListenThread *mousethread; + +private: + QIntMouseHandler *handler; +}; + +class QIntMouseListenThread : public QThread +{ +protected: + QIntMousePrivate *imp; + bool loop; +public: + QIntMouseListenThread(QIntMousePrivate *im) : QThread(), imp(im) {}; + ~QIntMouseListenThread() {}; + void run(); + void stoploop() { loop = false; }; +}; + + +QIntMouseHandler::QIntMouseHandler(const QString &driver, const QString &device) + : QObject(), QWSCalibratedMouseHandler(driver, device) +{ + QPoint test(1,1); + d = new QIntMousePrivate(this); + connect(d, SIGNAL(mouseDataAvailable(int, int, int)), this, SLOT(readMouseData(int, int, int))); + + d->calibrated = (test != transform(test)); + + d->mousethread->start(); +} + +QIntMouseHandler::~QIntMouseHandler() +{ + disconnect(d, SIGNAL(mouseDataAvailable(int, int, int)), this, SLOT(readMouseData(int, int, int))); + delete d; +} + +void QIntMouseHandler::resume() +{ + d->suspended = true; +} + +void QIntMouseHandler::suspend() +{ + d->suspended = false; +} + +void QIntMouseHandler::readMouseData(int x, int y, int buttons) +{ + d->waitforread = false; + if (d->suspended) + return; + if (d->calibrated) { + sendFiltered(QPoint(x, y), buttons); + } else { + QPoint pos; + pos = transform(QPoint(x, y)); + limitToScreen(pos); + mouseChanged(pos, buttons, 0); + } +} + +void QIntMouseHandler::clearCalibration() +{ + QWSCalibratedMouseHandler::clearCalibration(); +} + +void QIntMouseHandler::calibrate(const QWSPointerCalibrationData *data) +{ + QWSCalibratedMouseHandler::calibrate(data); +} + +void QIntMouseListenThread::run(void) +{ + MOUSEHandler handler; + MOUSEMessage msg; + Boolean filter; + Boolean isabsolute; + loop = true; + CheckSuccess(MOUSE_Init(&handler, &isabsolute)); + CheckSuccess(MOUSE_ShouldFilter(handler, &filter)); + if (!filter) + imp->calibrated = false; + imp->waitforread = false; + do { + MOUSE_SynchronousGetPosition(handler, &msg, isabsolute); + imp->dataReady(msg.x, msg.y, msg.buttons); + } while (loop); + QThread::exit(0); +} + +QIntMousePrivate::QIntMousePrivate(QIntMouseHandler *handler) + : QObject() +{ + this->handler = handler; + suspended = false; + mousethread = new QIntMouseListenThread(this); +} + +QIntMousePrivate::~QIntMousePrivate() +{ + mousethread->stoploop(); + mousethread->wait(); + delete mousethread; +} + +QT_END_NAMESPACE + +#include "qmouseintegrity_qws.moc" + +typedef struct USBMouseStruct +{ + Connection mouseconn; + Buffer mousemsg[2]; + Value x; + Value y; +} USBMouse; + +USBMouse mousedev; + +Error MOUSE_Init(MOUSEHandler *handler, Boolean *isabsolute) +{ + Error E; + bool loop = true; + memset((void*)&mousedev, 0, sizeof(USBMouse)); + mousedev.mousemsg[0].BufferType = DataImmediate; + mousedev.mousemsg[1].BufferType = DataImmediate | LastBuffer; + do { + E = RequestResource((Object*)&mousedev.mouseconn, + "MouseClient", "!systempassword"); + if (E == Success) { + *isabsolute = true; + loop = false; + } else { + E = RequestResource((Object*)&mousedev.mouseconn, + "USBMouseClient", "!systempassword"); + if (E == Success) { + *isabsolute = false; + loop = false; + } + } + if (loop) + sleep(1); + } while (loop); + *handler = (MOUSEHandler)&mousedev; + return Success; +} + +Error MOUSE_SynchronousGetPosition(MOUSEHandler handler, MOUSEMessage *msg, + Boolean isabsolute) +{ + signed long x; + signed long y; + USBMouse *mdev = (USBMouse *)handler; + mdev->mousemsg[0].Transferred = 0; + mdev->mousemsg[1].Transferred = 0; + SynchronousReceive(mdev->mouseconn, mdev->mousemsg); + if (isabsolute) { + x = (signed long)mdev->mousemsg[0].Length; + y = (signed long)mdev->mousemsg[1].TheAddress; + } else { + x = mdev->x + (signed long)mdev->mousemsg[0].Length; + y = mdev->y + (signed long)mdev->mousemsg[1].TheAddress; + } + if (x < 0) + mdev->x = 0; + else + mdev->x = x; + if (y < 0) + mdev->y = 0; + else + mdev->y = y; + msg->x = mdev->x; + msg->y = mdev->y; + msg->buttons = mdev->mousemsg[0].TheAddress; + return Success; +} + +Error MOUSE_ShouldFilter(MOUSEHandler handler, Boolean *filter) +{ + if (filter == NULL) + return Failure; + *filter = false; + return Success; +} + +#endif // QT_NO_QWS_MOUSE_INTEGRITY + diff --git a/src/gui/embedded/qmouseintegrity_qws.h b/src/gui/embedded/qmouseintegrity_qws.h new file mode 100644 index 0000000000..46d27a3d2e --- /dev/null +++ b/src/gui/embedded/qmouseintegrity_qws.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEINTEGRITY_QWS_H +#define QMOUSEINTEGRITY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MOUSE_INTEGRITY + +class QSocketNotifier; +class QIntMousePrivate; + +class QIntMouseHandler : public QObject, public QWSCalibratedMouseHandler { + Q_OBJECT +public: + QIntMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + ~QIntMouseHandler(); + + void resume(); + void suspend(); + + void calibrate(const QWSPointerCalibrationData *data); + void clearCalibration(); + +private: + QIntMousePrivate *d; +private Q_SLOTS: + void readMouseData(int x, int y, int buttons); +}; +#endif // QT_NO_QWS_MOUSE_INTEGRITY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSEINTEGRITY_QWS_H diff --git a/src/gui/embedded/qmouselinuxinput_qws.cpp b/src/gui/embedded/qmouselinuxinput_qws.cpp new file mode 100644 index 0000000000..534e9f16f7 --- /dev/null +++ b/src/gui/embedded/qmouselinuxinput_qws.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmouselinuxinput_qws.h" + +#include +#include + +#include +#include // overrides QT_OPEN + +#include + +#include + +QT_BEGIN_NAMESPACE + + +class QWSLinuxInputMousePrivate : public QObject +{ + Q_OBJECT +public: + QWSLinuxInputMousePrivate(QWSLinuxInputMouseHandler *, const QString &); + ~QWSLinuxInputMousePrivate(); + + void enable(bool on); + +private Q_SLOTS: + void readMouseData(); + +private: + QWSLinuxInputMouseHandler *m_handler; + QSocketNotifier * m_notify; + int m_fd; + int m_x, m_y; + int m_buttons; +}; + +QWSLinuxInputMouseHandler::QWSLinuxInputMouseHandler(const QString &device) + : QWSCalibratedMouseHandler(device) +{ + d = new QWSLinuxInputMousePrivate(this, device); +} + +QWSLinuxInputMouseHandler::~QWSLinuxInputMouseHandler() +{ + delete d; +} + +void QWSLinuxInputMouseHandler::suspend() +{ + d->enable(false); +} + +void QWSLinuxInputMouseHandler::resume() +{ + d->enable(true); +} + +QWSLinuxInputMousePrivate::QWSLinuxInputMousePrivate(QWSLinuxInputMouseHandler *h, const QString &device) + : m_handler(h), m_notify(0), m_x(0), m_y(0), m_buttons(0) +{ + setObjectName(QLatin1String("LinuxInputSubsystem Mouse Handler")); + + QString dev = QLatin1String("/dev/input/event0"); + if (device.startsWith(QLatin1String("/dev/"))) + dev = device; + + m_fd = QT_OPEN(dev.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0); + if (m_fd >= 0) { + m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(m_notify, SIGNAL(activated(int)), this, SLOT(readMouseData())); + } else { + qWarning("Cannot open mouse input device '%s': %s", qPrintable(dev), strerror(errno)); + return; + } +} + +QWSLinuxInputMousePrivate::~QWSLinuxInputMousePrivate() +{ + if (m_fd >= 0) + QT_CLOSE(m_fd); +} + +void QWSLinuxInputMousePrivate::enable(bool on) +{ + if (m_notify) + m_notify->setEnabled(on); +} + +void QWSLinuxInputMousePrivate::readMouseData() +{ + if (!qt_screen) + return; + + struct ::input_event buffer[32]; + int n = 0; + + forever { + n = QT_READ(m_fd, reinterpret_cast(buffer) + n, sizeof(buffer) - n); + + if (n == 0) { + qWarning("Got EOF from the input device."); + return; + } else if (n < 0 && (errno != EINTR && errno != EAGAIN)) { + qWarning("Could not read from input device: %s", strerror(errno)); + return; + } else if (n % sizeof(buffer[0]) == 0) { + break; + } + } + + n /= sizeof(buffer[0]); + + for (int i = 0; i < n; ++i) { + struct ::input_event *data = &buffer[i]; + + bool unknown = false; + if (data->type == EV_ABS) { + if (data->code == ABS_X) { + m_x = data->value; + } else if (data->code == ABS_Y) { + m_y = data->value; + } else { + unknown = true; + } + } else if (data->type == EV_REL) { + if (data->code == REL_X) { + m_x += data->value; + } else if (data->code == REL_Y) { + m_y += data->value; + } else { + unknown = true; + } + } else if (data->type == EV_KEY && data->code == BTN_TOUCH) { + m_buttons = data->value ? Qt::LeftButton : 0; + } else if (data->type == EV_KEY) { + int button = 0; + switch (data->code) { + case BTN_LEFT: button = Qt::LeftButton; break; + case BTN_MIDDLE: button = Qt::MidButton; break; + case BTN_RIGHT: button = Qt::RightButton; break; + } + if (data->value) + m_buttons |= button; + else + m_buttons &= ~button; + } else if (data->type == EV_SYN && data->code == SYN_REPORT) { + QPoint pos(m_x, m_y); + pos = m_handler->transform(pos); + m_handler->limitToScreen(pos); + m_handler->mouseChanged(pos, m_buttons); + } else if (data->type == EV_MSC && data->code == MSC_SCAN) { + // kernel encountered an unmapped key - just ignore it + continue; + } else { + unknown = true; + } + if (unknown) { + qWarning("unknown mouse event type=%x, code=%x, value=%x", data->type, data->code, data->value); + } + } +} + +QT_END_NAMESPACE + +#include "qmouselinuxinput_qws.moc" diff --git a/src/gui/embedded/qmouselinuxinput_qws.h b/src/gui/embedded/qmouselinuxinput_qws.h new file mode 100644 index 0000000000..2a9ddb3826 --- /dev/null +++ b/src/gui/embedded/qmouselinuxinput_qws.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSELINUXINPUT_QWS_H +#define QMOUSELINUXINPUT_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MOUSE_LINUXINPUT + +class QWSLinuxInputMousePrivate; + +class QWSLinuxInputMouseHandler : public QWSCalibratedMouseHandler +{ +public: + QWSLinuxInputMouseHandler(const QString &); + ~QWSLinuxInputMouseHandler(); + + void suspend(); + void resume(); + +private: + QWSLinuxInputMousePrivate *d; + + friend class QWSLinuxInputMousePrivate; +}; + +#endif // QT_NO_QWS_MOUSE_LINUXINPUT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSELINUXINPUT_QWS_H diff --git a/src/gui/embedded/qmouselinuxtp_qws.cpp b/src/gui/embedded/qmouselinuxtp_qws.cpp new file mode 100644 index 0000000000..21fff97418 --- /dev/null +++ b/src/gui/embedded/qmouselinuxtp_qws.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmouselinuxtp_qws.h" + +#ifndef QT_NO_QWS_MOUSE_LINUXTP +#include "qwindowsystem_qws.h" +#include "qsocketnotifier.h" +#include "qtimer.h" +#include "qapplication.h" +#include "qscreen_qws.h" +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#if defined(QT_QWS_IPAQ) + #define QT_QWS_IPAQ_RAW + #define QT_QWS_SCREEN_COORDINATES + typedef struct { + unsigned short pressure; + unsigned short x; + unsigned short y; + unsigned short pad; + } TS_EVENT; +#elif defined(QT_QWS_EBX) + #define QT_QWS_EBX_RAW + #define QT_QWS_SCREEN_COORDINATES +#ifndef QT_QWS_SHARP + typedef struct { + unsigned short pressure; + unsigned short x; + unsigned short y; + unsigned short pad; + } TS_EVENT; + #else + typedef struct { + long y; + long x; + long pressure; + long long millisecs; + } TS_EVENT; + #define QT_QWS_TP_SAMPLE_SIZE 10 + #define QT_QWS_TP_MINIMUM_SAMPLES 4 + #define QT_QWS_TP_PRESSURE_THRESHOLD 500 + #define QT_QWS_TP_MOVE_LIMIT 50 + #define QT_QWS_TP_JITTER_LIMIT 2 + #endif +#else // not IPAQ, not SHARP + typedef struct { + unsigned short pressure; + unsigned short x; + unsigned short y; + unsigned short pad; + } TS_EVENT; +#endif + +#ifndef QT_QWS_TP_SAMPLE_SIZE +#define QT_QWS_TP_SAMPLE_SIZE 5 +#endif + +#ifndef QT_QWS_TP_MINIMUM_SAMPLES +#define QT_QWS_TP_MINIMUM_SAMPLES 5 +#endif + +#ifndef QT_QWS_TP_PRESSURE_THRESHOLD +#define QT_QWS_TP_PRESSURE_THRESHOLD 1 +#endif + +#ifndef QT_QWS_TP_MOVE_LIMIT +#define QT_QWS_TP_MOVE_LIMIT 100 +#endif + +#ifndef QT_QWS_TP_JITTER_LIMIT +#define QT_QWS_TP_JITTER_LIMIT 2 +#endif + +class QWSLinuxTPMouseHandlerPrivate : public QObject +{ + Q_OBJECT +public: + QWSLinuxTPMouseHandlerPrivate(QWSLinuxTPMouseHandler *h, const QString &); + ~QWSLinuxTPMouseHandlerPrivate(); + + void suspend(); + void resume(); +private: + static const int mouseBufSize = 2048; + int mouseFD; + QPoint oldmouse; + QPoint oldTotalMousePos; + bool waspressed; + QPolygon samples; + int currSample; + int lastSample; + int numSamples; + int skipCount; + int mouseIdx; + uchar mouseBuf[mouseBufSize]; + QWSLinuxTPMouseHandler *handler; + QSocketNotifier *mouseNotifier; + +private slots: + void readMouseData(); +}; + +QWSLinuxTPMouseHandler::QWSLinuxTPMouseHandler(const QString &driver, const QString &device) + : QWSCalibratedMouseHandler(driver, device) +{ + d = new QWSLinuxTPMouseHandlerPrivate(this, device); +} + +QWSLinuxTPMouseHandler::~QWSLinuxTPMouseHandler() +{ + delete d; +} + +void QWSLinuxTPMouseHandler::suspend() +{ + d->suspend(); +} + +void QWSLinuxTPMouseHandler::resume() +{ + d->resume(); +} + +QWSLinuxTPMouseHandlerPrivate::QWSLinuxTPMouseHandlerPrivate(QWSLinuxTPMouseHandler *h, + const QString &device) + : samples(QT_QWS_TP_SAMPLE_SIZE), currSample(0), lastSample(0), + numSamples(0), skipCount(0), handler(h) +{ + QString mousedev; + if (device.isEmpty()) { +#if defined(QT_QWS_IPAQ) +# ifdef QT_QWS_IPAQ_RAW + mousedev = QLatin1String("/dev/h3600_tsraw"); +# else + mousedev = QLatin1String("/dev/h3600_ts"); +# endif +#else + mousedev = QLatin1String("/dev/ts"); +#endif + } else { + mousedev = device; + } + if ((mouseFD = QT_OPEN(mousedev.toLatin1().constData(), O_RDONLY | O_NDELAY)) < 0) { + qWarning("Cannot open %s (%s)", qPrintable(mousedev), strerror(errno)); + return; + } + + mouseNotifier = new QSocketNotifier(mouseFD, QSocketNotifier::Read, + this); + connect(mouseNotifier, SIGNAL(activated(int)),this, SLOT(readMouseData())); + waspressed=false; + mouseIdx = 0; +} + +QWSLinuxTPMouseHandlerPrivate::~QWSLinuxTPMouseHandlerPrivate() +{ + if (mouseFD >= 0) + QT_CLOSE(mouseFD); +} + +void QWSLinuxTPMouseHandlerPrivate::suspend() +{ + if (mouseNotifier) + mouseNotifier->setEnabled(false); +} + +void QWSLinuxTPMouseHandlerPrivate::resume() +{ + mouseIdx=0; + currSample=0; + lastSample=0; + numSamples=0; + skipCount=0; + if (mouseNotifier) + mouseNotifier->setEnabled(true); +} + + +void QWSLinuxTPMouseHandlerPrivate::readMouseData() +{ + if(!qt_screen) + return; + + int n; + do { + n = QT_READ(mouseFD, mouseBuf+mouseIdx, mouseBufSize-mouseIdx); + if (n > 0) + mouseIdx += n; + } while (n > 0 && mouseIdx < mouseBufSize); + + //qDebug("readMouseData()"); + + TS_EVENT *data; + int idx = 0; + + // perhaps we shouldn't be reading EVERY SAMPLE. + while (mouseIdx-idx >= (int)sizeof(TS_EVENT)) { + uchar *mb = mouseBuf+idx; + data = (TS_EVENT *) mb; + + if(data->pressure >= QT_QWS_TP_PRESSURE_THRESHOLD) { +#ifdef QT_QWS_SHARP + samples[currSample] = QPoint(1000 - data->x, data->y); +#else + samples[currSample] = QPoint(data->x, data->y); +#endif + numSamples++; + if (numSamples >= QT_QWS_TP_MINIMUM_SAMPLES) { + int sampleCount = qMin(numSamples + 1,samples.count()); + + // average the rest + QPoint mousePos = QPoint(0, 0); + QPoint totalMousePos = oldTotalMousePos; + totalMousePos += samples[currSample]; + if(numSamples >= samples.count()) + totalMousePos -= samples[lastSample]; + + mousePos = totalMousePos / (sampleCount - 1); +#if defined(QT_QWS_SCREEN_COORDINATES) + mousePos = handler->transform(mousePos); +#endif + if(!waspressed) + oldmouse = mousePos; + QPoint dp = mousePos - oldmouse; + int dxSqr = dp.x() * dp.x(); + int dySqr = dp.y() * dp.y(); + if (dxSqr + dySqr < (QT_QWS_TP_MOVE_LIMIT * QT_QWS_TP_MOVE_LIMIT)) { + if (waspressed) { + if ((dxSqr + dySqr > (QT_QWS_TP_JITTER_LIMIT * QT_QWS_TP_JITTER_LIMIT)) || skipCount > 2) { + handler->mouseChanged(mousePos,Qt::LeftButton); + oldmouse = mousePos; + skipCount = 0; + } else { + skipCount++; + } + } else { + handler->mouseChanged(mousePos,Qt::LeftButton); + oldmouse=mousePos; + waspressed=true; + } + + // save recuring information + currSample++; + if (numSamples >= samples.count()) + lastSample++; + oldTotalMousePos = totalMousePos; + } else { + numSamples--; // don't use this sample, it was bad. + } + } else { + // build up the average + oldTotalMousePos += samples[currSample]; + currSample++; + } + if (currSample >= samples.count()) + currSample = 0; + if (lastSample >= samples.count()) + lastSample = 0; + } else { + currSample = 0; + lastSample = 0; + numSamples = 0; + skipCount = 0; + oldTotalMousePos = QPoint(0,0); + if (waspressed) { + handler->mouseChanged(oldmouse,0); + oldmouse = QPoint(-100, -100); + waspressed=false; + } + } + idx += sizeof(TS_EVENT); + } + + int surplus = mouseIdx - idx; + for (int i = 0; i < surplus; i++) + mouseBuf[i] = mouseBuf[idx+i]; + mouseIdx = surplus; +} + +QT_END_NAMESPACE + +#include "qmouselinuxtp_qws.moc" + +#endif //QT_NO_QWS_MOUSE_LINUXTP diff --git a/src/gui/embedded/qmouselinuxtp_qws.h b/src/gui/embedded/qmouselinuxtp_qws.h new file mode 100644 index 0000000000..1974ad0e89 --- /dev/null +++ b/src/gui/embedded/qmouselinuxtp_qws.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSELINUXTP_QWS_H +#define QMOUSELINUXTP_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MOUSE_LINUXTP + +class QWSLinuxTPMouseHandlerPrivate; + +class QWSLinuxTPMouseHandler : public QWSCalibratedMouseHandler +{ + friend class QWSLinuxTPMouseHandlerPrivate; +public: + explicit QWSLinuxTPMouseHandler(const QString & = QString(), + const QString & = QString()); + ~QWSLinuxTPMouseHandler(); + + void suspend(); + void resume(); +protected: + QWSLinuxTPMouseHandlerPrivate *d; +}; + +#endif // QT_NO_QWS_MOUSE_LINUXTP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSELINUXTP_QWS_H diff --git a/src/gui/embedded/qmousepc_qws.cpp b/src/gui/embedded/qmousepc_qws.cpp new file mode 100644 index 0000000000..e3f85cbc77 --- /dev/null +++ b/src/gui/embedded/qmousepc_qws.cpp @@ -0,0 +1,794 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmousepc_qws.h" + +#ifndef QT_NO_QWS_MOUSE_PC + +#include "qwindowsystem_qws.h" +#include "qsocketnotifier.h" +#include "qwsevent_qws.h" +#include "qwscommand_qws_p.h" +#include "qwsutils_qws.h" + +#include "qapplication.h" +#include "qpolygon.h" +#include "qtimer.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qstringlist.h" +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +//#define QWS_MOUSE_DEBUG + +/* + * Automatic-detection mouse driver + */ + +class QWSPcMouseSubHandler { +protected: + enum { max_buf=32 }; + + int fd; + + uchar buffer[max_buf]; + int nbuf; + + QPoint motion; + int bstate; + int wheel; + + int goodness; + int badness; + + virtual int tryData()=0; + +public: + QWSPcMouseSubHandler(int f) : fd(f) + { + initState(); + } + virtual ~QWSPcMouseSubHandler() {} + + int file() const { return fd; } + + void closeIfNot(int& f) + { + if (fd != f) { + f = fd; + QT_CLOSE(fd); + } + } + + void initState() { nbuf = bstate = goodness = badness = 0; } + + void worse(int by=1) { badness+=by; } + bool reliable() const { return goodness >= 5 && badness < 50; } + int buttonState() const { return bstate; } + bool motionPending() const { return motion!=QPoint(0,0); } + QPoint takeMotion() { QPoint r=motion; motion=QPoint(0,0); return r; } + int takeWheel() { int result = wheel; wheel = 0; return result; } + + void appendData(uchar* data, int length) + { + memcpy(buffer+nbuf, data, length); + nbuf += length; + } + + enum UsageResult { Insufficient, Motion, Button }; + + UsageResult useData() + { + int pbstate = bstate; + int n = tryData(); +#ifdef QWS_MOUSE_DEBUG + if (n) { + fprintf(stderr, "QWSPcMouseSubHandler tryData read %d bytes:", n); + for (int i=0; i 0) { + if (n 0) { + goodness = 10; + switch (reply[n-1]) { + case 3: + case 4: + packetsize = 4; + break; + default: + packetsize = 3; + } + } else { + badness = 100; + } + } + + int tryData() + { + if (nbuf >= packetsize) { + //int overflow = (buffer[0]>>6)& 0x03; + + if (/*overflow ||*/ !(buffer[0] & 8)) { +#ifdef QWS_MOUSE_DEBUG + qDebug("Intellimouse: skipping (overflow)"); +#endif + badness++; + return 1; + } else { + QPoint delta((buffer[0] & 0x10) ? buffer[1]-256 : buffer[1], + (buffer[0] & 0x20) ? 256-buffer[2] : -buffer[2]); + motion += delta; + int nbstate = buffer[0] & 0x7; +#ifdef QWS_MOUSE_DEBUG + int debugwheel = +#endif + wheel = packetsize > 3 ? -(signed char)buffer[3] : 0; + if (wheel < -2 || wheel > 2) + wheel = 0; + wheel *= 120; // WHEEL_DELTA? +#ifdef QWS_MOUSE_DEBUG + qDebug("Intellimouse: motion %d,%d, state %d, raw wheel %d, wheel %d", motion.x(), motion.y(), nbstate, debugwheel, wheel); +#endif + if (motion.x() || motion.y() || bstate != nbstate || wheel) { + bstate = nbstate; + goodness++; + } else { + badness++; + return 1; + } + } + return packetsize; + } + return 0; + } +}; + +class QWSPcMouseSubHandler_mouseman : public QWSPcMouseSubHandler { + int packetsize; +public: + QWSPcMouseSubHandler_mouseman(int f) : QWSPcMouseSubHandler(f) + { + init(); + } + + void init() + { + if (tcflush(fd,TCIOFLUSH) == -1) { +#ifdef QWS_MOUSE_DEBUG + perror("QWSPcMouseSubHandler_mouseman: initial tcflush"); +#endif + } + QT_WRITE(fd,"",1); + usleep(50000); + QT_WRITE(fd,"@EeI!",5); + usleep(10000); + static const char ibuf[] = { 246, 244 }; + QT_WRITE(fd,ibuf,1); + QT_WRITE(fd,ibuf+1,1); + if (tcflush(fd,TCIOFLUSH) == -1) { +#ifdef QWS_MOUSE_DEBUG + perror("QWSPcMouseSubHandler_mouseman: tcflush"); +#endif + } + usleep(10000); + + char buf[100]; + while (QT_READ(fd, buf, 100) > 0) { } // eat unwanted replies + } + + int tryData() + { + if (nbuf >= 3) { + int nbstate = 0; + if (buffer[0] & 0x01) + nbstate |= Qt::LeftButton; + if (buffer[0] & 0x02) + nbstate |= Qt::RightButton; + if (buffer[0] & 0x04) + nbstate |= Qt::MidButton; + + int overflow = (buffer[0]>>6)& 0x03; + if (overflow) { + //### wheel events signalled with overflow bit, ignore for now + badness++; + return 1; + } else { + bool xs = buffer[0] & 0x10; + bool ys = buffer[0] & 0x20; + int dx = xs ? buffer[1]-256 : buffer[1]; + int dy = ys ? buffer[2]-256 : buffer[2]; + + motion += QPoint(dx, -dy); + if (motion.x() || motion.y() || bstate != nbstate) { + bstate = nbstate; + goodness++; + } else { + badness++; + return 1; + } + } + return 3; + } + return 0; + } +}; + +class QWSPcMouseSubHandler_serial : public QWSPcMouseSubHandler { +public: + QWSPcMouseSubHandler_serial(int f) : QWSPcMouseSubHandler(f) + { + initSerial(); + } + +protected: + void setflags(int f) + { + termios tty; + if (tcgetattr(fd, &tty) == -1) { +#ifdef QWS_MOUSE_DEBUG + perror("QWSPcMouseSubHandler_serial: tcgetattr"); +#endif + } + tty.c_iflag = IGNBRK | IGNPAR; + tty.c_oflag = 0; + tty.c_lflag = 0; + tty.c_cflag = f | CREAD | CLOCAL | HUPCL; +#ifdef Q_OS_LINUX + tty.c_line = 0; +#endif + tty.c_cc[VTIME] = 0; + tty.c_cc[VMIN] = 1; + if (tcsetattr(fd, TCSANOW, &tty) == -1) { +#ifdef QWS_MOUSE_DEBUG + perror("QWSPcMouseSubHandler_serial: tcgetattr"); +#endif + } + } + +private: + void initSerial() + { + int speed[4] = { B9600, B4800, B2400, B1200 }; + + for (int n = 0; n < 4; n++) { + setflags(CSTOPB | speed[n]); + QT_WRITE(fd, "*q", 2); + usleep(10000); + } + } +}; + +class QWSPcMouseSubHandler_mousesystems : public QWSPcMouseSubHandler_serial { +public: + // ##### This driver has not been tested + + QWSPcMouseSubHandler_mousesystems(int f) : QWSPcMouseSubHandler_serial(f) + { + init(); + } + + void init() + { + setflags(B1200|CS8|CSTOPB); + // 60Hz + if (QT_WRITE(fd, "R", 1)!=1) { + badness = 100; + return; + } + if (tcflush(fd,TCIOFLUSH) == -1) { +#ifdef QT_QWS_VNC_DEBUG + perror("QWSPcMouseSubHandler_mousesystems: tcflush"); +#endif + } + } + + int tryData() + { + if (nbuf >= 5) { + if ((buffer[0] & 0xf8) != 0x80) { + badness++; + return 1; + } + motion += + QPoint((signed char)buffer[1] + (signed char)buffer[3], + -(signed char)buffer[2] + (signed char)buffer[4]); + int t = ~buffer[0]; + int nbstate = ((t&3) << 1) | ((t&4) >> 2); + if (motion.x() || motion.y() || bstate != nbstate) { + bstate = nbstate; + goodness++; + } else { + badness++; + return 1; + } + return 5; + } + return 0; + } +}; + +class QWSPcMouseSubHandler_ms : public QWSPcMouseSubHandler_serial { + int mman; +public: + QWSPcMouseSubHandler_ms(int f) : QWSPcMouseSubHandler_serial(f) + { + mman=0; + init(); + } + + void init() + { + setflags(B1200|CS7); + // 60Hz + if (QT_WRITE(fd, "R", 1)!=1) { + badness = 100; + return; + } + if (tcflush(fd,TCIOFLUSH) == -1) { +#ifdef QWS_MOUSE_DEBUG + perror("QWSPcMouseSubHandler_ms: tcflush"); +#endif + } + } + + int tryData() + { + if (!(buffer[0] & 0x40)) { + if (buffer[0] == 0x20 && (bstate & Qt::MidButton)) { + mman=1; // mouseman extension + } + return 1; + } + int extra = mman&&(bstate & Qt::MidButton); + if (nbuf >= 3+extra) { + int nbstate = 0; + if (buffer[0] == 0x40 && !bstate && !buffer[1] && !buffer[2]) { + nbstate = Qt::MidButton; + } else { + nbstate = ((buffer[0] & 0x20) >> 5) + | ((buffer[0] & 0x10) >> 3); + if (extra && buffer[3] == 0x20) + nbstate = Qt::MidButton; + } + + if (buffer[1] & 0x40) { + badness++; + return 1; + } else { + motion += + QPoint((signed char)((buffer[0]&0x3)<<6) + |(signed char)(buffer[1]&0x3f), + (signed char)((buffer[0]&0xc)<<4) + |(signed char)(buffer[2]&0x3f)); + if (motion.x() || motion.y() || bstate != nbstate) { + bstate = nbstate; + goodness++; + } else { + badness++; + return 1; + } + return 3+extra; + } + } + return 0; + } +}; + +//=========================================================================== + +class QWSPcMouseHandlerPrivate : public QObject +{ + Q_OBJECT +public: + QWSPcMouseHandlerPrivate(QWSPcMouseHandler *h, const QString &, const QString &); + ~QWSPcMouseHandlerPrivate(); + + void suspend(); + void resume(); + +private: + enum { max_dev=32 }; + QWSPcMouseSubHandler *sub[max_dev]; + QList notifiers; + int nsub; + int retries; + +private slots: + void readMouseData(int); + +private: + void openDevices(); + void closeDevices(); + void notify(int fd); + bool sendEvent(QWSPcMouseSubHandler& h); + +private: + QWSPcMouseHandler *handler; + QString driver; + QString device; + qreal accel; + int accel_limit; +}; + +QWSPcMouseHandler::QWSPcMouseHandler(const QString &driver, const QString &device) + : QWSMouseHandler(driver, device) +{ + d = new QWSPcMouseHandlerPrivate(this, driver, device); +} + +QWSPcMouseHandler::~QWSPcMouseHandler() +{ + delete d; +} + +void QWSPcMouseHandler::suspend() +{ + d->suspend(); +} + +void QWSPcMouseHandler::resume() +{ + d->resume(); +} + + +QWSPcMouseHandlerPrivate::QWSPcMouseHandlerPrivate(QWSPcMouseHandler *h, + const QString &drv, const QString &arg) + : handler(h), driver(drv) +{ + QStringList args = arg.split(QLatin1Char(':'), QString::SkipEmptyParts); + + int index; + + accel = qreal(2.0); + QRegExp accelRegex(QLatin1String("^accel=(\\d+\\.?\\d*)$")); + index = args.indexOf(accelRegex); + if (index >= 0) { + accel = qreal(accelRegex.cap(1).toDouble()); + args.removeAt(index); + } + + accel_limit = 5; + QRegExp accelLimitRegex(QLatin1String("^accel_limit=(\\d+)$")); + index = args.indexOf(accelLimitRegex); + if (index >= 0) { + accel_limit = accelLimitRegex.cap(1).toInt(); + args.removeAt(index); + } + + device = args.join(QString()); + + retries = 0; + openDevices(); +} + +QWSPcMouseHandlerPrivate::~QWSPcMouseHandlerPrivate() +{ + closeDevices(); +} + +/* +QWSPcMouseHandler::UsageResult QWSPcMouseHandler::useDev(Dev& d) +{ + if (d.nbuf >= mouseData[d.protocol].bytesPerPacket) { + uchar *mb = d.buf; + int bstate = 0; + int dx = 0; + int dy = 0; + + switch (mouseProtocol) { + case MouseMan: + case IntelliMouse: + { + bstate = mb[0] & 0x7; // assuming Qt::*Button order + + int overflow = (mb[0]>>6)& 0x03; + if (mouseProtocol == MouseMan && overflow) { + //### wheel events signalled with overflow bit, ignore for now + } + else { + bool xs = mb[0] & 0x10; + bool ys = mb[0] & 0x20; + dx = xs ? mb[1]-256 : mb[1]; + dy = ys ? mb[2]-256 : mb[2]; + } + break; + } + case Microsoft: + if (((mb[0] & 0x20) >> 3)) { + bstate |= Qt::LeftButton; + } + if (((mb[0] & 0x10) >> 4)) { + bstate |= Qt::RightButton; + } + + dx=(signed char)(((mb[0] & 0x03) << 6) | (mb[1] & 0x3f)); + dy=-(signed char)(((mb[0] & 0x0c) << 4) | (mb[2] & 0x3f)); + + break; + } + } + } +*/ + + +bool QWSPcMouseHandlerPrivate::sendEvent(QWSPcMouseSubHandler& h) +{ + if (h.reliable()) { + QPoint motion = h.takeMotion(); + if (qAbs(motion.x()) > accel_limit || qAbs(motion.y()) > accel_limit) + motion *= accel; + QPoint newPos = handler->pos() + motion; + if (qt_screen->isTransformed()) { + QSize s = QSize(qt_screen->width(), qt_screen->height()); + newPos = qt_screen->mapToDevice(newPos, s); + } + handler->limitToScreen(newPos); + + handler->mouseChanged(newPos, h.buttonState(), h.takeWheel()); + return true; + } else { + h.takeMotion(); + if (h.buttonState() & (Qt::RightButton|Qt::MidButton)) { + // Strange for the user to press right or middle without + // a moving mouse! + h.worse(); + } + return false; + } +} + +void QWSPcMouseHandlerPrivate::openDevices() +{ + nsub=0; + int fd = -1; + + QString drv = driver.toLower(); + if (!drv.isEmpty() && drv != QLatin1String("auto")) { + // Manually specified mouse + QByteArray dev = device.toLatin1(); + if (drv == QLatin1String("intellimouse")) { + if (dev.isEmpty()) + dev = "/dev/psaux"; + fd = QT_OPEN(dev, O_RDWR | O_NDELAY); + if (fd >= 0) + sub[nsub++] = new QWSPcMouseSubHandler_intellimouse(fd); + } else if (drv == QLatin1String("microsoft")) { + if (dev.isEmpty()) + dev = "/dev/ttyS0"; + fd = QT_OPEN(dev, O_RDWR | O_NDELAY); + if (fd >= 0) + sub[nsub++] = new QWSPcMouseSubHandler_ms(fd); + } else if (drv == QLatin1String("mousesystems")) { + if (dev.isEmpty()) + dev = "/dev/ttyS0"; + fd = QT_OPEN(dev, O_RDWR | O_NDELAY); + if (fd >= 0) + sub[nsub++] = new QWSPcMouseSubHandler_mousesystems(fd); + } else if (drv == QLatin1String("mouseman")) { + if (dev.isEmpty()) + dev = "/dev/psaux"; + fd = QT_OPEN(dev, O_RDWR | O_NDELAY); + if (fd >= 0) + sub[nsub++] = new QWSPcMouseSubHandler_mouseman(fd); + } + if (fd >= 0) + notify(fd); + else + qCritical("Error opening mouse device '%s': %s", + dev.constData(), strerror(errno)); + } else { + // Try automatically + fd = QT_OPEN("/dev/psaux", O_RDWR | O_NDELAY); + if (fd >= 0) { + sub[nsub++] = new QWSPcMouseSubHandler_intellimouse(fd); + notify(fd); + } + fd = QT_OPEN("/dev/input/mice", O_RDWR | O_NDELAY); + if (fd >= 0) { + sub[nsub++] = new QWSPcMouseSubHandler_intellimouse(fd); + notify(fd); + //qDebug("/dev/input/mice fd %d #%d", fd, nsub-1); + } + +// include the code below to auto-detect serial mice, and to mess up +// any sort of serial communication +#if 0 + const char fn[4][11] = { "/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3" }; + for (int ch = 0; ch < 4; ++ch) { + fd = QT_OPEN(fn[ch], O_RDWR | O_NDELAY); + if (fd >= 0) { + //sub[nsub++] = new QWSPcMouseSubHandler_intellimouse(fd); + sub[nsub++] = new QWSPcMouseSubHandler_mousesystems(fd); + sub[nsub++] = new QWSPcMouseSubHandler_ms(fd); + notify(fd); + } + } +#endif + } +} + +void QWSPcMouseHandlerPrivate::closeDevices() +{ + int pfd=-1; + for (int i=0; icloseIfNot(pfd); + delete sub[i]; + } + qDeleteAll(notifiers); + notifiers.clear(); +} + +void QWSPcMouseHandlerPrivate::suspend() +{ + for (int i=0; isetEnabled(false); +} + +void QWSPcMouseHandlerPrivate::resume() +{ + for (int i=0; iinitState(); + + for (int i=0; isetEnabled(true); +} + + + +void QWSPcMouseHandlerPrivate::notify(int fd) +{ + QSocketNotifier *mouseNotifier + = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(mouseNotifier, SIGNAL(activated(int)),this, SLOT(readMouseData(int))); + notifiers.append(mouseNotifier); +} + +void QWSPcMouseHandlerPrivate::readMouseData(int fd) +{ + for (;;) { + uchar buf[8]; + int n = read(fd, buf, 8); + if (n<=0) + break; + for (int i=0; i + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MOUSE_PC + +class QWSPcMouseHandlerPrivate; + +class QWSPcMouseHandler : public QWSMouseHandler +{ +public: + explicit QWSPcMouseHandler(const QString & = QString(), + const QString & = QString()); + ~QWSPcMouseHandler(); + + void suspend(); + void resume(); +protected: + QWSPcMouseHandlerPrivate *d; +}; + +#endif // QT_NO_QWS_MOUSE_PC + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSEPC_QWS_H diff --git a/src/gui/embedded/qmouseqnx_qws.cpp b/src/gui/embedded/qmouseqnx_qws.cpp new file mode 100644 index 0000000000..cea3673d70 --- /dev/null +++ b/src/gui/embedded/qmouseqnx_qws.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qmouseqnx_qws.h" + +#include "qsocketnotifier.h" +#include "qdebug.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QQnxMouseHandler + \preliminary + \ingroup qws + \internal + \since 4.6 + + \brief The QQnxMouseHandler class implements a mouse driver + for the QNX \c{devi-hid} input manager. + + To be able to compile this mouse handler, \l{Qt for Embedded Linux} + must be configured with the \c -qt-mouse-qnx option, see the + \l{Qt for Embedded Linux Pointer Handling}{Pointer Handling} documentation for details. + + In order to use this mouse handler, the \c{devi-hid} input manager + must be set up and run with the resource manager interface (option \c{-r}). + Also, Photon must not be running. + + Example invocation from command line: \c{/usr/photon/bin/devi-hid -Pr kbd mouse} + Note that after running \c{devi-hid}, you will not be able to use the local + shell anymore. It is suggested to run the command in a shell scrip, that launches + a Qt application after invocation of \c{devi-hid}. + + To make \l{Qt for Embedded Linux} explicitly choose the qnx mouse + handler, set the QWS_MOUSE_PROTO environment variable to \c{qnx}. By default, + the first mouse device (\c{/dev/devi/mouse0}) is used. To override, pass a device + name as the first and only parameter, for example + \c{QWS_MOUSE_PROTO=qnx:/dev/devi/mouse1; export QWS_MOUSE_PROTO}. + + \sa {Qt for Embedded Linux Pointer Handling}{Pointer Handling}, {Qt for Embedded Linux} +*/ + +/*! + Constructs a mouse handler for the specified \a device, defaulting to \c{/dev/devi/mouse0}. + The \a driver parameter must be \c{"qnx"}. + + Note that you should never instanciate this class, instead let QMouseDriverFactory + handle the mouse handlers. + + \sa QMouseDriverFactory + */ +QQnxMouseHandler::QQnxMouseHandler(const QString & /*driver*/, const QString &device) +{ + // open the mouse device with O_NONBLOCK so reading won't block when there's no data + mouseFD = QT_OPEN(device.isEmpty() ? "/dev/devi/mouse0" : device.toLatin1().constData(), + QT_OPEN_RDONLY | O_NONBLOCK); + if (mouseFD == -1) { + qErrnoWarning(errno, "QQnxMouseHandler: Unable to open mouse device"); + return; + } + + // register a socket notifier on the file descriptor so we'll wake up whenever + // there's a mouse move waiting for us. + mouseNotifier = new QSocketNotifier(mouseFD, QSocketNotifier::Read, this); + connect(mouseNotifier, SIGNAL(activated(int)), SLOT(socketActivated())); + + qDebug() << "QQnxMouseHandler: connected."; +} + +/*! + Destroys this mouse handler and closes the connection to the mouse device. + */ +QQnxMouseHandler::~QQnxMouseHandler() +{ + QT_CLOSE(mouseFD); +} + +/*! \reimp */ +void QQnxMouseHandler::resume() +{ + if (mouseNotifier) + mouseNotifier->setEnabled(true); +} + +/*! \reimp */ +void QQnxMouseHandler::suspend() +{ + if (mouseNotifier) + mouseNotifier->setEnabled(false); +} + +/*! \internal + + This function is called whenever there is activity on the mouse device. + By default, it reads up to 10 mouse move packets and calls mouseChanged() + for each of them. +*/ +void QQnxMouseHandler::socketActivated() +{ + // _mouse_packet is a QNX structure. devi-hid is nice enough to translate + // the raw byte data from mouse devices into generic format for us. + _mouse_packet packet; + + int iteration = 0; + + // read mouse events in batches of 10. Since we're getting quite a lot + // of mouse events, it's better to do them in batches than to return to the + // event loop every time. + do { + int bytesRead = QT_READ(mouseFD, &packet, sizeof(packet)); + if (bytesRead == -1) { + // EAGAIN means that there are no more mouse events to read + if (errno != EAGAIN) + qErrnoWarning(errno, "QQnxMouseHandler: Unable to read from socket"); + return; + } + + // bytes read should always be equal to the size of a packet. + Q_ASSERT(bytesRead == sizeof(packet)); + + // translate the coordinates from the QNX data structure to Qt coordinates + // note the swapped y axis + QPoint pos = mousePos; + pos += QPoint(packet.dx, -packet.dy); + + // QNX only tells us relative mouse movements, not absolute ones, so limit the + // cursor position manually to the screen + limitToScreen(pos); + + // translate the QNX mouse button bitmask to Qt buttons + int buttons = Qt::NoButton; + + if (packet.hdr.buttons & _POINTER_BUTTON_LEFT) + buttons |= Qt::LeftButton; + if (packet.hdr.buttons & _POINTER_BUTTON_MIDDLE) + buttons |= Qt::MidButton; + if (packet.hdr.buttons & _POINTER_BUTTON_RIGHT) + buttons |= Qt::RightButton; + + // call mouseChanged() - this does all the magic to actually move the on-screen + // mouse cursor. + mouseChanged(pos, buttons, 0); + } while (++iteration < 11); +} + +QT_END_NAMESPACE + diff --git a/src/gui/embedded/qmouseqnx_qws.h b/src/gui/embedded/qmouseqnx_qws.h new file mode 100644 index 0000000000..0779367600 --- /dev/null +++ b/src/gui/embedded/qmouseqnx_qws.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSE_QNX_H +#define QMOUSE_QNX_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QSocketNotifier; + +class Q_GUI_EXPORT QQnxMouseHandler : public QObject, public QWSMouseHandler +{ + Q_OBJECT +public: + explicit QQnxMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + ~QQnxMouseHandler(); + + void resume(); + void suspend(); + +private Q_SLOTS: + void socketActivated(); + +private: + QSocketNotifier *mouseNotifier; + int mouseFD; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSE_QWS_H diff --git a/src/gui/embedded/qmousetslib_qws.cpp b/src/gui/embedded/qmousetslib_qws.cpp new file mode 100644 index 0000000000..8794eed2f2 --- /dev/null +++ b/src/gui/embedded/qmousetslib_qws.cpp @@ -0,0 +1,371 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmousetslib_qws.h" + +#if !defined(QT_NO_QWS_MOUSE_TSLIB) || defined(QT_PLUGIN) + +#include +#include +#include "qsocketnotifier.h" +#include "qscreen_qws.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +#ifdef TSLIBMOUSEHANDLER_DEBUG +# include +#endif + +/*! + \internal + + \class QWSTslibMouseHandler + \ingroup qws + + \brief The QWSTslibMouseHandler class implements a mouse driver + for the Universal Touch Screen Library, tslib. + + QWSTslibMouseHandler inherits the QWSCalibratedMouseHandler class, + providing calibration and noise reduction functionality in + addition to generating mouse events, for devices using the + Universal Touch Screen Library. + + To be able to compile this mouse handler, \l{Qt for Embedded Linux} + must be configured with the \c -qt-mouse-tslib option, see the + \l{Pointer Handling} documentation for details. In addition, the tslib + headers and library must be present in the build environment. The + tslib sources can be downloaded from \l + {http://tslib.berlios.de/}. Use the \c -L and \c -I options + with \c configure to explicitly specify the location of the + library and its headers: + + \snippet doc/src/snippets/code/src_gui_embedded_qmousetslib_qws.cpp 0 + + In order to use this mouse handler, tslib must also be correctly + installed on the target machine. This includes providing a \c + ts.conf configuration file and setting the necessary environment + variables, see the README file provided with tslib for details. + + The ts.conf file will usually contain the following two lines + + \snippet doc/src/snippets/code/src_gui_embedded_qmousetslib_qws.cpp 1 + + To make \l{Qt for Embedded Linux} explicitly choose the tslib mouse + handler, set the QWS_MOUSE_PROTO environment variable. + + \sa {Pointer Handling}, {Qt for Embedded Linux} +*/ + +class QWSTslibMouseHandlerPrivate : public QObject +{ + Q_OBJECT +public: + QWSTslibMouseHandlerPrivate(QWSTslibMouseHandler *h, + const QString &device); + ~QWSTslibMouseHandlerPrivate(); + + void suspend(); + void resume(); + + void calibrate(const QWSPointerCalibrationData *data); + void clearCalibration(); + +private: + QWSTslibMouseHandler *handler; + struct tsdev *dev; + QSocketNotifier *mouseNotifier; + int jitter_limit; + + struct ts_sample lastSample; + bool wasPressed; + int lastdx; + int lastdy; + + bool calibrated; + QString devName; + + bool open(); + void close(); + inline bool get_sample(struct ts_sample *sample); + +private slots: + void readMouseData(); +}; + +QWSTslibMouseHandlerPrivate::QWSTslibMouseHandlerPrivate(QWSTslibMouseHandler *h, + const QString &device) + : handler(h), dev(0), mouseNotifier(0), jitter_limit(3) +{ + QStringList args = device.split(QLatin1Char(':'), QString::SkipEmptyParts); + QRegExp jitterRegex(QLatin1String("^jitter_limit=(\\d+)$")); + int index = args.indexOf(jitterRegex); + if (index >= 0) { + jitter_limit = jitterRegex.cap(1).toInt(); + args.removeAt(index); + } + + devName = args.join(QString()); + + if (devName.isNull()) { + const char *str = getenv("TSLIB_TSDEVICE"); + if (str) + devName = QString::fromLocal8Bit(str); + } + + if (devName.isNull()) + devName = QLatin1String("/dev/ts"); + + if (!open()) + return; + + calibrated = true; + + int fd = ts_fd(dev); + mouseNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); + connect(mouseNotifier, SIGNAL(activated(int)),this, SLOT(readMouseData())); + resume(); +} + +QWSTslibMouseHandlerPrivate::~QWSTslibMouseHandlerPrivate() +{ + close(); +} + +bool QWSTslibMouseHandlerPrivate::open() +{ + dev = ts_open(devName.toLocal8Bit().constData(), 1); + if (!dev) { + qCritical("QWSTslibMouseHandlerPrivate: ts_open() failed" + " with error: '%s'", strerror(errno)); + qCritical("Please check your tslib installation!"); + return false; + } + + if (ts_config(dev)) { + qCritical("QWSTslibMouseHandlerPrivate: ts_config() failed" + " with error: '%s'", strerror(errno)); + qCritical("Please check your tslib installation!"); + close(); + return false; + } + + return true; +} + +void QWSTslibMouseHandlerPrivate::close() +{ + if (dev) + ts_close(dev); +} + +void QWSTslibMouseHandlerPrivate::suspend() +{ + if (mouseNotifier) + mouseNotifier->setEnabled(false); +} + +void QWSTslibMouseHandlerPrivate::resume() +{ + memset(&lastSample, 0, sizeof(lastSample)); + wasPressed = false; + lastdx = 0; + lastdy = 0; + if (mouseNotifier) + mouseNotifier->setEnabled(true); +} + +bool QWSTslibMouseHandlerPrivate::get_sample(struct ts_sample *sample) +{ + if (!calibrated) + return (ts_read_raw(dev, sample, 1) == 1); + + return (ts_read(dev, sample, 1) == 1); +} + +void QWSTslibMouseHandlerPrivate::readMouseData() +{ + if (!qt_screen) + return; + + for(;;) { + struct ts_sample sample = lastSample; + bool pressed = wasPressed; + + // Fast return if there's no events. + if (!get_sample(&sample)) + return; + pressed = (sample.pressure > 0); + + // Only return last sample unless there's a press/release event. + while (pressed == wasPressed) { + if (!get_sample(&sample)) + break; + pressed = (sample.pressure > 0); + } + + // work around missing coordinates on mouse release in raw mode + if (!calibrated && !pressed && sample.x == 0 && sample.y == 0) { + sample.x = lastSample.x; + sample.y = lastSample.y; + } + + int dx = sample.x - lastSample.x; + int dy = sample.y - lastSample.y; + + // Remove small movements in oppsite direction + if (dx * lastdx < 0 && qAbs(dx) < jitter_limit) { + sample.x = lastSample.x; + dx = 0; + } + if (dy * lastdy < 0 && qAbs(dy) < jitter_limit) { + sample.y = lastSample.y; + dy = 0; + } + + if (wasPressed == pressed && dx == 0 && dy == 0) + return; + +#ifdef TSLIBMOUSEHANDLER_DEBUG + qDebug() << "last" << QPoint(lastSample.x, lastSample.y) + << "curr" << QPoint(sample.x, sample.y) + << "dx,dy" << QPoint(dx, dy) + << "ddx,ddy" << QPoint(dx*lastdx, dy*lastdy) + << "pressed" << wasPressed << pressed; +#endif + + lastSample = sample; + wasPressed = pressed; + if (dx != 0) + lastdx = dx; + if (dy != 0) + lastdy = dy; + + const QPoint p(sample.x, sample.y); + if (calibrated) { + // tslib should do all the translation and filtering, so we send a + // "raw" mouse event + handler->QWSMouseHandler::mouseChanged(p, pressed); + } else { + handler->sendFiltered(p, pressed); + } + } +} + +void QWSTslibMouseHandlerPrivate::clearCalibration() +{ + suspend(); + close(); + handler->QWSCalibratedMouseHandler::clearCalibration(); + calibrated = false; + open(); + resume(); +} + +void QWSTslibMouseHandlerPrivate::calibrate(const QWSPointerCalibrationData *data) +{ + suspend(); + close(); + // default implementation writes to /etc/pointercal + // using the same format as the tslib linear module. + handler->QWSCalibratedMouseHandler::calibrate(data); + calibrated = true; + open(); + resume(); +} + +/*! + \internal +*/ +QWSTslibMouseHandler::QWSTslibMouseHandler(const QString &driver, + const QString &device) + : QWSCalibratedMouseHandler(driver, device) +{ + d = new QWSTslibMouseHandlerPrivate(this, device); +} + +/*! + \internal +*/ +QWSTslibMouseHandler::~QWSTslibMouseHandler() +{ + delete d; +} + +/*! + \reimp +*/ +void QWSTslibMouseHandler::suspend() +{ + d->suspend(); +} + +/*! + \reimp +*/ +void QWSTslibMouseHandler::resume() +{ + d->resume(); +} + +/*! + \reimp +*/ +void QWSTslibMouseHandler::clearCalibration() +{ + d->clearCalibration(); +} + +/*! + \reimp +*/ +void QWSTslibMouseHandler::calibrate(const QWSPointerCalibrationData *data) +{ + d->calibrate(data); +} + +QT_END_NAMESPACE + +#include "qmousetslib_qws.moc" + +#endif //QT_NO_QWS_MOUSE_TSLIB diff --git a/src/gui/embedded/qmousetslib_qws.h b/src/gui/embedded/qmousetslib_qws.h new file mode 100644 index 0000000000..fc6206c03f --- /dev/null +++ b/src/gui/embedded/qmousetslib_qws.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSETSLIB_QWS_H +#define QMOUSETSLIB_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_QWS_MOUSE_TSLIB) || defined(QT_PLUGIN) + +class QWSTslibMouseHandlerPrivate; + +class QWSTslibMouseHandler : public QWSCalibratedMouseHandler +{ +public: + explicit QWSTslibMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + ~QWSTslibMouseHandler(); + + void suspend(); + void resume(); + + void calibrate(const QWSPointerCalibrationData *data); + void clearCalibration(); + +protected: + friend class QWSTslibMouseHandlerPrivate; + QWSTslibMouseHandlerPrivate *d; +}; + + +#endif // QT_NO_QWS_MOUSE_TSLIB +QT_END_NAMESPACE +QT_END_HEADER + +#endif // QMOUSETSLIB_QWS_H diff --git a/src/gui/embedded/qmousevfb_qws.cpp b/src/gui/embedded/qmousevfb_qws.cpp new file mode 100644 index 0000000000..76eff3efe1 --- /dev/null +++ b/src/gui/embedded/qmousevfb_qws.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QWS_MOUSE_QVFB + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include // overrides QT_OPEN + +QT_BEGIN_NAMESPACE + +QVFbMouseHandler::QVFbMouseHandler(const QString &driver, const QString &device) + : QObject(), QWSMouseHandler(driver, device) +{ + QString mouseDev = device; + if (device.isEmpty()) + mouseDev = QLatin1String("/dev/vmouse"); + + mouseFD = QT_OPEN(mouseDev.toLatin1().constData(), O_RDWR | O_NDELAY); + if (mouseFD == -1) { + perror("QVFbMouseHandler::QVFbMouseHandler"); + qWarning("QVFbMouseHander: Unable to open device %s", + qPrintable(mouseDev)); + return; + } + + // Clear pending input + char buf[2]; + while (QT_READ(mouseFD, buf, 1) > 0) { } + + mouseIdx = 0; + + mouseNotifier = new QSocketNotifier(mouseFD, QSocketNotifier::Read, this); + connect(mouseNotifier, SIGNAL(activated(int)),this, SLOT(readMouseData())); +} + +QVFbMouseHandler::~QVFbMouseHandler() +{ + if (mouseFD >= 0) + QT_CLOSE(mouseFD); +} + +void QVFbMouseHandler::resume() +{ + mouseNotifier->setEnabled(true); +} + +void QVFbMouseHandler::suspend() +{ + mouseNotifier->setEnabled(false); +} + +void QVFbMouseHandler::readMouseData() +{ + int n; + do { + n = QT_READ(mouseFD, mouseBuf+mouseIdx, mouseBufSize-mouseIdx); + if (n > 0) + mouseIdx += n; + } while (n > 0); + + int idx = 0; + static const int packetsize = sizeof(QPoint) + 2*sizeof(int); + while (mouseIdx-idx >= packetsize) { + uchar *mb = mouseBuf+idx; + QPoint mousePos = *reinterpret_cast(mb); + mb += sizeof(QPoint); + int bstate = *reinterpret_cast(mb); + mb += sizeof(int); + int wheel = *reinterpret_cast(mb); +// limitToScreen(mousePos); + mouseChanged(mousePos, bstate, wheel); + idx += packetsize; + } + + int surplus = mouseIdx - idx; + for (int i = 0; i < surplus; i++) + mouseBuf[i] = mouseBuf[idx+i]; + mouseIdx = surplus; +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_MOUSE_QVFB diff --git a/src/gui/embedded/qmousevfb_qws.h b/src/gui/embedded/qmousevfb_qws.h new file mode 100644 index 0000000000..f2e7bd14fc --- /dev/null +++ b/src/gui/embedded/qmousevfb_qws.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEVFB_QWS_H +#define QMOUSEVFB_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MOUSE_QVFB + +class QSocketNotifier; + +class QVFbMouseHandler : public QObject, public QWSMouseHandler { + Q_OBJECT +public: + QVFbMouseHandler(const QString &driver = QString(), + const QString &device = QString()); + ~QVFbMouseHandler(); + + void resume(); + void suspend(); + +private: + int mouseFD; + int mouseIdx; + enum {mouseBufSize = 128}; + uchar mouseBuf[mouseBufSize]; + QSocketNotifier *mouseNotifier; + +private Q_SLOTS: + void readMouseData(); +}; +#endif // QT_NO_QWS_MOUSE_QVFB + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOUSEVFB_QWS_H diff --git a/src/gui/embedded/qscreen_qws.cpp b/src/gui/embedded/qscreen_qws.cpp new file mode 100644 index 0000000000..90561fd06d --- /dev/null +++ b/src/gui/embedded/qscreen_qws.cpp @@ -0,0 +1,3347 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qscreen_qws.h" + +#include "qcolormap.h" +#include "qscreendriverfactory_qws.h" +#include "qwindowsystem_qws.h" +#include "qwidget.h" +#include "qcolor.h" +#include "qpixmap.h" +#include "qvarlengtharray.h" +#include "qwsdisplay_qws.h" +#include "qpainter.h" +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// #define QT_USE_MEMCPY_DUFF + +#ifndef QT_NO_QWS_CURSOR +Q_GUI_EXPORT QScreenCursor * qt_screencursor = 0; +#endif +Q_GUI_EXPORT QScreen * qt_screen = 0; + +ClearCacheFunc QScreen::clearCacheFunc = 0; + +#ifndef QT_NO_QWS_CURSOR +/*! + \class QScreenCursor + \ingroup qws + + \brief The QScreenCursor class is a base class for screen cursors + in Qt for Embedded Linux. + + Note that this class is non-portable, and that it is only + available in \l{Qt for Embedded Linux}. + + QScreenCursor implements a software cursor, but can be subclassed + to support hardware cursors as well. When deriving from the + QScreenCursor class it is important to maintain the cursor's + image, position, hot spot (the point within the cursor's image + that will be the position of the associated mouse events) and + visibility as well as informing whether it is hardware accelerated + or not. + + Note that there may only be one screen cursor at a time. Use the + static instance() function to retrieve a pointer to the current + screen cursor. Typically, the cursor is constructed by the QScreen + class or one of its descendants when it is initializing the + device; the QScreenCursor class should never be instantiated + explicitly. + + Use the move() function to change the position of the cursor, and + the set() function to alter its image or its hot spot. In + addition, you can find out whether the cursor is accelerated or + not, using the isAccelerated() function, and the boundingRect() + function returns the cursor's bounding rectangle. + + The cursor's appearance can be controlled using the isVisible(), + hide() and show() functions; alternatively the QWSServer class + provides some means of controlling the cursor's appearance using + the QWSServer::isCursorVisible() and QWSServer::setCursorVisible() + functions. + + \sa QScreen, QWSServer +*/ + +/*! + \fn static QScreenCursor* QScreenCursor::instance() + \since 4.2 + + Returns a pointer to the application's unique screen cursor. +*/ + +/*! + Constructs a screen cursor +*/ +QScreenCursor::QScreenCursor() +{ + pos = QPoint(qt_screen->deviceWidth()/2, qt_screen->deviceHeight()/2); + size = QSize(0,0); + enable = true; + hwaccel = false; + supportsAlpha = true; +} + +/*! + Destroys the screen cursor. +*/ +QScreenCursor::~QScreenCursor() +{ +} + +/*! + Hides the cursor from the screen. + + \sa show() +*/ +void QScreenCursor::hide() +{ + if (enable) { + enable = false; + if (!hwaccel) + qt_screen->exposeRegion(boundingRect(), 0); + } +} + +/*! + Shows the mouse cursor. + + \sa hide() +*/ +void QScreenCursor::show() +{ + if (!enable) { + enable = true; + if (!hwaccel) + qt_screen->exposeRegion(boundingRect(), 0); + } +} + +/*! + Sets the cursor's image to be the given \a image. + + The \a hotx and \a hoty parameters define the cursor's hot spot, + i.e., the point within the cursor's image that will be the + position of the associated mouse events. + + \sa move() +*/ +void QScreenCursor::set(const QImage &image, int hotx, int hoty) +{ + const QRect r = boundingRect(); + + hotspot = QPoint(hotx, hoty); + // These are in almost all cases the fastest formats to blend + QImage::Format f; + switch (qt_screen->depth()) { + case 12: + f = QImage::Format_ARGB4444_Premultiplied; + break; + case 15: + f = QImage::Format_ARGB8555_Premultiplied; + break; + case 16: + f = QImage::Format_ARGB8565_Premultiplied; + break; + case 18: + f = QImage::Format_ARGB6666_Premultiplied; + break; + default: + f = QImage::Format_ARGB32_Premultiplied; + } + + cursor = image.convertToFormat(f); + + size = image.size(); + + if (enable && !hwaccel) + qt_screen->exposeRegion(r | boundingRect(), 0); +} + +/*! + Moves the mouse cursor to the given position, i.e., (\a x, \a y). + + Note that the given position defines the top-left corner of the + cursor's image, i.e., not the cursor's hot spot (the position of + the associated mouse events). + + \sa set() +*/ +void QScreenCursor::move(int x, int y) +{ + QRegion r = boundingRect(); + pos = QPoint(x,y); + if (enable && !hwaccel) { + r |= boundingRect(); + qt_screen->exposeRegion(r, 0); + } +} + + +/*! + \fn void QScreenCursor::initSoftwareCursor () + + Initializes the screen cursor. + + This function is typically called from the screen driver when + initializing the device. Alternatively, the cursor can be set + directly using the pointer returned by the static instance() + function. + + \sa QScreen::initDevice() +*/ +void QScreenCursor::initSoftwareCursor() +{ + qt_screencursor = new QScreenCursor; +} + + +#endif // QT_NO_QWS_CURSOR + + +/*! + \fn QRect QScreenCursor::boundingRect () const + + Returns the cursor's bounding rectangle. +*/ + +/*! + \internal + \fn bool QScreenCursor::enabled () +*/ + +/*! + \fn QImage QScreenCursor::image () const + + Returns the cursor's image. +*/ + + +/*! + \fn bool QScreenCursor::isAccelerated () const + + Returns true if the cursor is accelerated; otherwise false. +*/ + +/*! + \fn bool QScreenCursor::isVisible () const + + Returns true if the cursor is visible; otherwise false. +*/ + +/*! + \internal + \fn bool QScreenCursor::supportsAlphaCursor () const +*/ + +/* + \variable QScreenCursor::cursor + + \brief the cursor's image. + + \sa image() +*/ + +/* + \variable QScreenCursor::size + + \brief the cursor's size +*/ + +/* + \variable QScreenCursor::pos + + \brief the cursor's position, i.e., the position of the top-left + corner of the crsor's image + + \sa set(), move() +*/ + +/* + \variable QScreenCursor::hotspot + + \brief the cursor's hotspot, i.e., the point within the cursor's + image that will be the position of the associated mouse events. + + \sa set(), move() +*/ + +/* + \variable QScreenCursor::enable + + \brief whether the cursor is visible or not + + \sa isVisible() +*/ + +/* + \variable QScreenCursor::hwaccel + + \brief holds whether the cursor is accelerated or not + + If the cursor is not accelerated, its image will be included by + the screen when it composites the window surfaces. + + \sa isAccelerated() + +*/ + +/* + \variable QScreenCursor::supportsAlpha +*/ + +/*! + \internal + \macro qt_screencursor + \relates QScreenCursor + + A global pointer referring to the unique screen cursor. It is + equivalent to the pointer returned by the + QScreenCursor::instance() function. +*/ + + + +class QScreenPrivate +{ +public: + QScreenPrivate(QScreen *parent, QScreen::ClassId id = QScreen::CustomClass); + ~QScreenPrivate(); + + inline QImage::Format preferredImageFormat() const; + + typedef void (*SolidFillFunc)(QScreen*, const QColor&, const QRegion&); + typedef void (*BlitFunc)(QScreen*, const QImage&, const QPoint&, const QRegion&); + + SolidFillFunc solidFill; + BlitFunc blit; + + QPoint offset; + QList subScreens; + QPixmapDataFactory* pixmapFactory; + QGraphicsSystem* graphicsSystem; + QWSGraphicsSystem defaultGraphicsSystem; //### + QImage::Format pixelFormat; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + bool fb_is_littleEndian; +#endif +#ifdef QT_QWS_CLIENTBLIT + bool supportsBlitInClients; +#endif + int classId; + QScreen *q_ptr; +}; + +template +static void solidFill_template(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + T *dest = reinterpret_cast(screen->base()); + const T c = qt_colorConvert(color.rgba(), 0); + const int stride = screen->linestep(); + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + qt_rectfill(dest, c, r.x(), r.y(), r.width(), r.height(), stride); + } +} + +#ifdef QT_QWS_DEPTH_GENERIC +static void solidFill_rgb_32bpp(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + quint32 *dest = reinterpret_cast(screen->base()); + const quint32 c = qt_convertToRgb(color.rgba()); + + const int stride = screen->linestep(); + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + qt_rectfill(dest, c, r.x(), r.y(), r.width(), r.height(), stride); + } +} + +static void solidFill_rgb_16bpp(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + quint16 *dest = reinterpret_cast(screen->base()); + const quint16 c = qt_convertToRgb(color.rgba()); + + const int stride = screen->linestep(); + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + qt_rectfill(dest, c, r.x(), r.y(), r.width(), r.height(), stride); + } +} +#endif // QT_QWS_DEPTH_GENERIC + +#ifdef QT_QWS_DEPTH_4 +static inline void qt_rectfill_gray4(quint8 *dest, quint8 value, + int x, int y, int width, int height, + int stride) +{ + const int pixelsPerByte = 2; + dest += y * stride + x / pixelsPerByte; + const int doAlign = x & 1; + const int doTail = (width - doAlign) & 1; + const int width8 = (width - doAlign) / pixelsPerByte; + + for (int j = 0; j < height; ++j) { + if (doAlign) + *dest = (*dest & 0xf0) | (value & 0x0f); + if (width8) + qt_memfill(dest + doAlign, value, width8); + if (doTail) { + quint8 *d = dest + doAlign + width8; + *d = (*d & 0x0f) | (value & 0xf0); + } + dest += stride; + } +} + +static void solidFill_gray4(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + quint8 *dest = reinterpret_cast(screen->base()); + const quint8 c = qGray(color.rgba()) >> 4; + const quint8 c8 = (c << 4) | c; + + const int stride = screen->linestep(); + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + qt_rectfill_gray4(dest, c8, r.x(), r.y(), r.width(), r.height(), + stride); + } +} +#endif // QT_QWS_DEPTH_4 + +#ifdef QT_QWS_DEPTH_1 +static inline void qt_rectfill_mono(quint8 *dest, quint8 value, + int x, int y, int width, int height, + int stride) +{ + const int pixelsPerByte = 8; + const int alignWidth = qMin(width, (8 - (x & 7)) & 7); + const int doAlign = (alignWidth > 0 ? 1 : 0); + const int alignStart = pixelsPerByte - 1 - (x & 7); + const int alignStop = alignStart - (alignWidth - 1); + const quint8 alignMask = ((1 << alignWidth) - 1) << alignStop; + const int tailWidth = (width - alignWidth) & 7; + const int doTail = (tailWidth > 0 ? 1 : 0); + const quint8 tailMask = (1 << (pixelsPerByte - tailWidth)) - 1; + const int width8 = (width - alignWidth) / pixelsPerByte; + + dest += y * stride + x / pixelsPerByte; + stride -= (doAlign + width8); + + for (int j = 0; j < height; ++j) { + if (doAlign) { + *dest = (*dest & ~alignMask) | (value & alignMask); + ++dest; + } + if (width8) { + qt_memfill(dest, value, width8); + dest += width8; + } + if (doTail) + *dest = (*dest & tailMask) | (value & ~tailMask); + dest += stride; + } +} + +static void solidFill_mono(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + quint8 *dest = reinterpret_cast(screen->base()); + const quint8 c8 = (qGray(color.rgba()) >> 7) * 0xff; + + const int stride = screen->linestep(); + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + qt_rectfill_mono(dest, c8, r.x(), r.y(), r.width(), r.height(), + stride); + } +} +#endif // QT_QWS_DEPTH_1 + +void qt_solidFill_setup(QScreen *screen, const QColor &color, + const QRegion ®ion) +{ + switch (screen->depth()) { +#ifdef QT_QWS_DEPTH_32 + case 32: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->solidFill = solidFill_template; + else + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_24 + case 24: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->solidFill = solidFill_template; + else + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_18 + case 18: + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_16 + case 16: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->solidFill = solidFill_template; + else + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_15 + case 15: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->solidFill = solidFill_template; + else + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_12 + case 12: + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_8 + case 8: + screen->d_ptr->solidFill = solidFill_template; + break; +#endif +#ifdef QT_QWS_DEPTH_4 + case 4: + screen->d_ptr->solidFill = solidFill_gray4; + break; +#endif +#ifdef QT_QWS_DEPTH_1 + case 1: + screen->d_ptr->solidFill = solidFill_mono; + break; +#endif + default: + qFatal("solidFill_setup(): Screen depth %d not supported!", + screen->depth()); + screen->d_ptr->solidFill = 0; + break; + } + screen->d_ptr->solidFill(screen, color, region); +} + +template +static void blit_template(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + DST *dest = reinterpret_cast(screen->base()); + const int screenStride = screen->linestep(); + const int imageStride = image.bytesPerLine(); + + if (region.rectCount() == 1) { + const QRect r = region.boundingRect(); + const SRC *src = reinterpret_cast(image.scanLine(r.y())) + + r.x(); + qt_rectconvert(dest, src, + r.x() + topLeft.x(), r.y() + topLeft.y(), + r.width(), r.height(), + screenStride, imageStride); + } else { + const QVector rects = region.rects(); + + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i); + const SRC *src = reinterpret_cast(image.scanLine(r.y())) + + r.x(); + qt_rectconvert(dest, src, + r.x() + topLeft.x(), r.y() + topLeft.y(), + r.width(), r.height(), + screenStride, imageStride); + } + } +} + +#ifdef QT_QWS_DEPTH_32 +static void blit_32(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; +#ifdef QT_QWS_DEPTH_16 + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; +#endif + default: + qCritical("blit_32(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_32 + +#ifdef QT_QWS_DEPTH_24 +static void blit_24(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB888: + blit_template(screen, image, topLeft, region); + return; +#ifdef QT_QWS_DEPTH_16 + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; +#endif + default: + qCritical("blit_24(): Image format %d not supported!", image.format()); + } +} + +static void blit_qrgb888(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB888: + blit_template(screen, image, topLeft, region); + return; +#ifdef QT_QWS_DEPTH_16 + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; +#endif + default: + qCritical("blit_24(): Image format %d not supported!", image.format()); + break; + } +} +#endif // QT_QWS_DEPTH_24 + +#ifdef QT_QWS_DEPTH_18 +static void blit_18(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB666: + blit_template(screen, image, topLeft, region); + return; +#ifdef QT_QWS_DEPTH_16 + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; +#endif + default: + qCritical("blit_18(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_18 + +#if (Q_BYTE_ORDER == Q_BIG_ENDIAN) && (defined(QT_QWS_DEPTH_16) || defined(QT_QWS_DEPTH_15)) +class quint16LE +{ +public: + inline quint16LE(quint32 v) { + data = ((v & 0xff00) >> 8) | ((v & 0x00ff) << 8); + } + + inline quint16LE(int v) { + data = ((v & 0xff00) >> 8) | ((v & 0x00ff) << 8); + } + + inline quint16LE(quint16 v) { + data = ((v & 0xff00) >> 8) | ((v & 0x00ff) << 8); + } + + inline quint16LE(qrgb555 v) { + data = (( (quint16)v & 0xff00) >> 8) | + (( (quint16)v & 0x00ff) << 8); + } + + inline bool operator==(const quint16LE &v) const + { + return data == v.data; + } + +private: + quint16 data; +}; +#endif + +#ifdef QT_QWS_DEPTH_16 +static void blit_16(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + // ### This probably doesn't work but it's a case which should never happen + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_16(): Image format %d not supported!", image.format()); + } +} + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +static void blit_16_bigToLittleEndian(QScreen *screen, const QImage &image, + const QPoint &topLeft, + const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_16_bigToLittleEndian(): Image format %d not supported!", image.format()); + } +} + +#endif // Q_BIG_ENDIAN +#endif // QT_QWS_DEPTH_16 + +#ifdef QT_QWS_DEPTH_15 +static void blit_15(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB555: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_15(): Image format %d not supported!", image.format()); + } +} + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +static void blit_15_bigToLittleEndian(QScreen *screen, const QImage &image, + const QPoint &topLeft, + const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB555: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_15_bigToLittleEndian(): Image format %d not supported!", image.format()); + } +} +#endif // Q_BIG_ENDIAN +#endif // QT_QWS_DEPTH_15 + + +#ifdef QT_QWS_DEPTH_12 +static void blit_12(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_ARGB4444_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB444: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_12(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_12 + +#ifdef QT_QWS_DEPTH_8 +static void blit_8(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_ARGB4444_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB444: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_8(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_8 + +#ifdef QT_QWS_DEPTH_4 + +struct qgray4 { quint8 dummy; } Q_PACKED; + +template +Q_STATIC_TEMPLATE_FUNCTION inline quint8 qt_convertToGray4(SRC color); + +template <> +inline quint8 qt_convertToGray4(quint32 color) +{ + return qGray(color) >> 4; +} + +template <> +inline quint8 qt_convertToGray4(quint16 color) +{ + const int r = (color & 0xf800) >> 11; + const int g = (color & 0x07e0) >> 6; // only keep 5 bit + const int b = (color & 0x001f); + return (r * 11 + g * 16 + b * 5) >> 6; +} + +template <> +inline quint8 qt_convertToGray4(qrgb444 color) +{ + return qt_convertToGray4(quint32(color)); +} + +template <> +inline quint8 qt_convertToGray4(qargb4444 color) +{ + return qt_convertToGray4(quint32(color)); +} + +template +Q_STATIC_TEMPLATE_FUNCTION inline void qt_rectconvert_gray4(qgray4 *dest4, const SRC *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + const int pixelsPerByte = 2; + quint8 *dest8 = reinterpret_cast(dest4) + + y * dstStride + x / pixelsPerByte; + const int doAlign = x & 1; + const int doTail = (width - doAlign) & 1; + const int width8 = (width - doAlign) / pixelsPerByte; + const int count8 = (width8 + 3) / 4; + + srcStride = srcStride / sizeof(SRC) - width; + dstStride -= (width8 + doAlign); + + for (int i = 0; i < height; ++i) { + if (doAlign) { + *dest8 = (*dest8 & 0xf0) | qt_convertToGray4(*src++); + ++dest8; + } + if (count8) { + int n = count8; + switch (width8 & 0x03) // duff's device + { + case 0: do { *dest8++ = qt_convertToGray4(src[0]) << 4 + | qt_convertToGray4(src[1]); + src += 2; + case 3: *dest8++ = qt_convertToGray4(src[0]) << 4 + | qt_convertToGray4(src[1]); + src += 2; + case 2: *dest8++ = qt_convertToGray4(src[0]) << 4 + | qt_convertToGray4(src[1]); + src += 2; + case 1: *dest8++ = qt_convertToGray4(src[0]) << 4 + | qt_convertToGray4(src[1]); + src += 2; + } while (--n > 0); + } + } + + if (doTail) + *dest8 = qt_convertToGray4(*src++) << 4 | (*dest8 & 0x0f); + + dest8 += dstStride; + src += srcStride; + } +} + +template <> +void qt_rectconvert(qgray4 *dest, const quint32 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_gray4(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qgray4 *dest, const quint16 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_gray4(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qgray4 *dest, const qrgb444 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_gray4(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qgray4 *dest, const qargb4444 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_gray4(dest, src, x, y, width, height, + dstStride, srcStride); +} + +static void blit_4(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB444: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_ARGB4444_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_4(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_4 + +#ifdef QT_QWS_DEPTH_1 + +struct qmono { quint8 dummy; } Q_PACKED; + +template +Q_STATIC_TEMPLATE_FUNCTION inline quint8 qt_convertToMono(SRC color); + +template <> +inline quint8 qt_convertToMono(quint32 color) +{ + return qGray(color) >> 7; +} + +template <> +inline quint8 qt_convertToMono(quint16 color) +{ + return (qGray(qt_colorConvert(color, 0)) >> 7); +} + +template <> +inline quint8 qt_convertToMono(qargb4444 color) +{ + return (qGray(quint32(color)) >> 7); +} + +template <> +inline quint8 qt_convertToMono(qrgb444 color) +{ + return (qGray(quint32(color)) >> 7); +} + +template +inline void qt_rectconvert_mono(qmono *dest, const SRC *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + const int pixelsPerByte = 8; + quint8 *dest8 = reinterpret_cast(dest) + + y * dstStride + x / pixelsPerByte; + const int alignWidth = qMin(width, (8 - (x & 7)) & 7); + const int doAlign = (alignWidth > 0 ? 1 : 0); + const int alignStart = pixelsPerByte - 1 - (x & 7); + const int alignStop = alignStart - (alignWidth - 1); + const quint8 alignMask = ((1 << alignWidth) - 1) << alignStop; + const int tailWidth = (width - alignWidth) & 7; + const int doTail = (tailWidth > 0 ? 1 : 0); + const quint8 tailMask = (1 << (pixelsPerByte - tailWidth)) - 1; + const int width8 = (width - alignWidth) / pixelsPerByte; + + srcStride = srcStride / sizeof(SRC) - (width8 * 8 + alignWidth); + dstStride -= (width8 + doAlign); + + for (int j = 0; j < height; ++j) { + if (doAlign) { + quint8 d = *dest8 & ~alignMask; + for (int i = alignStart; i >= alignStop; --i) + d |= qt_convertToMono(*src++) << i; + *dest8++ = d; + } + for (int i = 0; i < width8; ++i) { + *dest8 = (qt_convertToMono(src[0]) << 7) + | (qt_convertToMono(src[1]) << 6) + | (qt_convertToMono(src[2]) << 5) + | (qt_convertToMono(src[3]) << 4) + | (qt_convertToMono(src[4]) << 3) + | (qt_convertToMono(src[5]) << 2) + | (qt_convertToMono(src[6]) << 1) + | (qt_convertToMono(src[7])); + src += 8; + ++dest8; + } + if (doTail) { + quint8 d = *dest8 & tailMask; + switch (tailWidth) { + case 7: d |= qt_convertToMono(src[6]) << 1; + case 6: d |= qt_convertToMono(src[5]) << 2; + case 5: d |= qt_convertToMono(src[4]) << 3; + case 4: d |= qt_convertToMono(src[3]) << 4; + case 3: d |= qt_convertToMono(src[2]) << 5; + case 2: d |= qt_convertToMono(src[1]) << 6; + case 1: d |= qt_convertToMono(src[0]) << 7; + } + *dest8 = d; + } + + dest8 += dstStride; + src += srcStride; + } +} + +template <> +void qt_rectconvert(qmono *dest, const quint32 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_mono(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qmono *dest, const quint16 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_mono(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qmono *dest, const qrgb444 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_mono(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qmono *dest, const qargb4444 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_mono(dest, src, x, y, width, height, + dstStride, srcStride); +} + +static void blit_1(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB444: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_ARGB4444_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_1(): Image format %d not supported!", image.format()); + } +} +#endif // QT_QWS_DEPTH_1 + +#ifdef QT_QWS_DEPTH_GENERIC + +static void blit_rgb(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (image.format()) { + case QImage::Format_ARGB32_Premultiplied: + blit_template(screen, image, topLeft, region); + return; + case QImage::Format_RGB16: + blit_template(screen, image, topLeft, region); + return; + default: + qCritical("blit_rgb(): Image format %d not supported!", image.format()); + } +} + +void qt_set_generic_blit(QScreen *screen, int bpp, + int len_red, int len_green, int len_blue, int len_alpha, + int off_red, int off_green, int off_blue, int off_alpha) +{ + qrgb::bpp = bpp / 8; + qrgb::len_red = len_red; + qrgb::len_green = len_green; + qrgb::len_blue = len_blue; + qrgb::len_alpha = len_alpha; + qrgb::off_red = off_red; + qrgb::off_green = off_green; + qrgb::off_blue = off_blue; + qrgb::off_alpha = off_alpha; + screen->d_ptr->blit = blit_rgb; + if (bpp == 16) + screen->d_ptr->solidFill = solidFill_rgb_16bpp; + else if (bpp == 32) + screen->d_ptr->solidFill = solidFill_rgb_32bpp; +} + +#endif // QT_QWS_DEPTH_GENERIC + +void qt_blit_setup(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion) +{ + switch (screen->depth()) { +#ifdef QT_QWS_DEPTH_32 + case 32: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->blit = blit_32; + else + screen->d_ptr->blit = blit_template; + break; +#endif +#ifdef QT_QWS_DEPTH_24 + case 24: + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->blit = blit_qrgb888; + else + screen->d_ptr->blit = blit_24; + break; +#endif +#ifdef QT_QWS_DEPTH_18 + case 18: + screen->d_ptr->blit = blit_18; + break; +#endif +#ifdef QT_QWS_DEPTH_16 + case 16: +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (screen->d_ptr->fb_is_littleEndian) + screen->d_ptr->blit = blit_16_bigToLittleEndian; + else +#endif + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->blit = blit_16; + else + screen->d_ptr->blit = blit_template; + break; +#endif +#ifdef QT_QWS_DEPTH_15 + case 15: +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (screen->d_ptr->fb_is_littleEndian) + screen->d_ptr->blit = blit_15_bigToLittleEndian; + else +#endif // Q_BIG_ENDIAN + if (screen->pixelType() == QScreen::NormalPixel) + screen->d_ptr->blit = blit_15; + else + screen->d_ptr->blit = blit_template; + break; +#endif +#ifdef QT_QWS_DEPTH_12 + case 12: + screen->d_ptr->blit = blit_12; + break; +#endif +#ifdef QT_QWS_DEPTH_8 + case 8: + screen->d_ptr->blit = blit_8; + break; +#endif +#ifdef QT_QWS_DEPTH_4 + case 4: + screen->d_ptr->blit = blit_4; + break; +#endif +#ifdef QT_QWS_DEPTH_1 + case 1: + screen->d_ptr->blit = blit_1; + break; +#endif + default: + qFatal("blit_setup(): Screen depth %d not supported!", + screen->depth()); + screen->d_ptr->blit = 0; + break; + } + screen->d_ptr->blit(screen, image, topLeft, region); +} + +QScreenPrivate::QScreenPrivate(QScreen *parent, QScreen::ClassId id) + : defaultGraphicsSystem(QWSGraphicsSystem(parent)), + pixelFormat(QImage::Format_Invalid), +#ifdef QT_QWS_CLIENTBLIT + supportsBlitInClients(false), +#endif + classId(id), q_ptr(parent) +{ + solidFill = qt_solidFill_setup; + blit = qt_blit_setup; +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + fb_is_littleEndian = false; +#endif + pixmapFactory = 0; + graphicsSystem = &defaultGraphicsSystem; +} + +QScreenPrivate::~QScreenPrivate() +{ +} + +QImage::Format QScreenPrivate::preferredImageFormat() const +{ + if (pixelFormat > QImage::Format_Indexed8) + return pixelFormat; + + if (q_ptr->depth() <= 16) + return QImage::Format_RGB16; + else + return QImage::Format_ARGB32_Premultiplied; +} + +/*! + \class QScreen + \ingroup qws + + \brief The QScreen class is a base class for screen drivers in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several screen + protocols, see the \l{Qt for Embedded Linux Display Management}{display + management} documentation for details. Custom screen drivers can + be implemented by subclassing the QScreen class and creating a + screen driver plugin (derived from QScreenDriverPlugin). The + default implementation of the QScreenDriverFactory class + will automatically detect the plugin, and load the driver into the + server application at run-time using Qt's \l {How to Create Qt + Plugins}{plugin system}. + + When rendering, the default behavior is for each + client to render its widgets as well as its decorations into + memory, while the server copies the memory content to the device's + framebuffer using the screen driver. See the \l{Qt for Embedded Linux + Architecture} overview for details (note that it is possible for + the clients to manipulate and control the underlying hardware + directly as well). + + Starting with Qt 4.2, it is also possible to add an + accelerated graphics driver to take advantage of available + hardware resources. See the \l{Adding an Accelerated Graphics + Driver to Qt for Embedded Linux} documentation for details. + + \tableofcontents + + \section1 Framebuffer Management + + When a \l{Qt for Embedded Linux} application starts running, it + calls the screen driver's connect() function to map the + framebuffer and the accelerated drivers that the graphics card + control registers. The connect() function should then read out the + parameters of the framebuffer and use them as required to set this + class's protected variables. + + The initDevice() function can be reimplemented to initialize the + graphics card. Note, however, that connect() is called \e before + the initDevice() function, so, for some hardware configurations, + some of the initialization that would normally be done in the + initDevice() function might have to be done in the connect() + function. + + Likewise, just before a \l{Qt for Embedded Linux} application + exits, it calls the screen driver's disconnect() function. The + server application will in addition call the shutdownDevice() + function before it calls disconnect(). Note that the default + implementation of the shutdownDevice() function only hides the + mouse cursor. + + QScreen also provides the save() and restore() functions, making + it possible to save and restore the state of the graphics + card. Note that the default implementations do nothing. Hardware + screen drivers should reimplement these functions to save (and + restore) its registers, enabling switching between virtual + consoles. + + In addition, you can use the base() function to retrieve a pointer + to the beginning of the framebuffer, and the region() function to + retrieve the framebuffer's region. Use the onCard() function to + determine whether the framebuffer is within the graphics card's + memory, and the totalSize() function to determine the size of the + available graphics card memory (including the screen). Finally, + you can use the offset() function to retrieve the offset between + the framebuffer's coordinates and the application's coordinate + system. + + \section1 Palette Management + + QScreen provides several functions to retrieve information about + the color palette: The clut() function returns a pointer to the + color lookup table (i.e. its color palette). Use the colorCount() + function to determine the number of entries in this table, and the + alloc() function to retrieve the palette index of the color that + is the closest match to a given RGB value. + + To determine if the screen driver supports a given color depth, + use the supportsDepth() function that returns true of the + specified depth is supported. + + \section1 Drawing on Screen + + When a screen update is required, the \l{Qt for Embedded Linux} server runs + through all the top-level windows that intersect with the region + that is about to be updated, and ensures that the associated + clients have updated their memory buffer. Then the server calls + the exposeRegion() function that composes the window surfaces and + copies the content of memory to screen by calling the blit() and + solidFill() functions. + + The blit() function copies a given region in a given image to a + specified point using device coordinates, while the solidFill() + function fills the given region of the screen with the specified + color. Note that normally there is no need to call either of these + functions explicitly. + + In addition, QScreen provides the blank() function that can be + reimplemented to prevent any contents from being displayed on the + screen, and the setDirty() function that can be reimplemented to + indicate that a given rectangle of the screen has been + altered. Note that the default implementations of these functions + do nothing. + + Reimplement the mapFromDevice() and mapToDevice() functions to + map objects from the framebuffer coordinate system to the + coordinate space used by the application, and vice versa. Be aware + that the default implementations simply return the given objects + as they are. + + \section1 Properties + + \table + \header \o Property \o Functions + \row + \o Size + \o + + The size of the screen can be retrieved using the screenSize() + function. The size is returned in bytes. + + The framebuffer's logical width and height can be retrieved using + width() and height(), respectively. These functions return values + are given in pixels. Alternatively, the physicalWidth() and + physicalHeight() function returns the same metrics in + millimeters. QScreen also provides the deviceWidth() and + deviceHeight() functions returning the physical width and height + of the device in pixels. Note that the latter metrics can differ + from the ones used if the display is centered within the + framebuffer. + + \row + \o Resolution + \o + + Reimplement the setMode() function to be able to set the + framebuffer to a new resolution (width and height) and bit depth. + + The current depth of the framebuffer can be always be retrieved + using the depth() function. Use the pixmapDepth() function to + obtain the preferred depth for pixmaps. + + \row + \o Pixmap Alignment + \o + + Use the pixmapOffsetAlignment() function to retrieve the value to + which the start address of pixmaps held in the graphics card's + memory, should be aligned. + + Use the pixmapLinestepAlignment() to retrieve the value to which + the \e {individual scanlines} of pixmaps should be aligned. + + \row + \o Image Display + \o + + The isInterlaced() function tells whether the screen is displaying + images progressively, and the isTransformed() function whether it + is rotated. The transformOrientation() function can be + reimplemented to return the current rotation. + + \row + \o Scanlines + \o + + Use the linestep() function to retrieve the length of each + scanline of the framebuffer. + + \row + \o Pixel Type + \o + + The pixelType() function returns the screen's pixel storage format as + described by the PixelType enum. + + \endtable + + \section1 Subclassing and Initial Values + + You need to set the following members when implementing a subclass of QScreen: + + \table + \header \o Member \o Initial Value + \row \o \l{QScreen::}{data} \o A pointer to the framebuffer if possible; + 0 otherwise. + \row \o \l{QScreen::}{lstep} \o The number of bytes between each scanline + in the framebuffer. + \row \o \l{QScreen::}{w} \o The logical screen width in pixels. + \row \o \l{QScreen::}{h} \o The logical screen height in pixels. + \row \o \l{QScreen::}{dw} \o The real screen width in pixels. + \row \o \l{QScreen::}{dh} \o The real screen height in pixels. + \row \o \l{QScreen::}{d} \o The number of bits per pixel. + \row \o \l{QScreen::}{physWidth} \o The screen width in millimeters. + \row \o \l{QScreen::}{physHeight} \o The screen height in millimeters. + \endtable + + The logical screen values are the same as the real screen values unless the + screen is transformed in some way; e.g., rotated. + + See also the \l{Accelerated Graphics Driver Example} for an example that + shows how to initialize these values. + + \sa QScreenDriverPlugin, QScreenDriverFactory, {Qt for Embedded Linux Display + Management} +*/ + +/*! + \enum QScreen::PixelType + + This enum describes the pixel storage format of the screen, + i.e. the order of the red (R), green (G) and blue (B) components + of a pixel. + + \value NormalPixel Red-green-blue (RGB) + \value BGRPixel Blue-green-red (BGR) + + \sa pixelType() +*/ + +/*! + \enum QScreen::ClassId + + This enum defines the class identifiers for the known screen subclasses. + + \value LinuxFBClass QLinuxFBScreen + \value TransformedClass QTransformedScreen + \value VNCClass QVNCScreen + \value MultiClass QMultiScreen + \value VFbClass QVFbScreen + \value DirectFBClass QDirectFBScreen + \value SvgalibClass QSvgalibScreen + \value ProxyClass QProxyScreen + \value GLClass QGLScreen + \value CustomClass Unknown QScreen subclass + + \sa classId() +*/ + +/*! + \variable QScreen::screenclut + \brief the color table + + Initialize this variable in a subclass using a paletted screen mode, + and initialize its partner, QScreen::screencols. + + \sa screencols +*/ + +/*! + \variable QScreen::screencols + \brief the number of entries in the color table + + Initialize this variable in a subclass using a paletted screen mode, + and initialize its partner, QScreen::screenclut. + + \sa screenclut +*/ + +/*! + \variable QScreen::data + \brief points to the first visible pixel in the frame buffer. + + You must initialize this variable if you are using the default + implementation of non-buffered painting Qt::WA_PaintOnScreen, + QPixmap::grabWindow() or QDirectPainter::frameBuffer(). If you + initialize this variable, you must also initialize QScreen::size and + QScreen::mapsize. + + \sa QScreen::size, QScreen::mapsize +*/ + +/*! + \variable QScreen::w + \brief the logical width of the screen. + + This variable \e{must} be initialized by a subclass. +*/ + +/*! + \variable QScreen::lstep + \brief the number of bytes representing a line in the frame buffer. + + i.e., \e{line step}. \c {data[lstep * 2]} is the address of the + first visible pixel in the third line of the frame buffer. + + \sa data +*/ + +/*! + \variable QScreen::h + \brief the logical height of the screen. + + This variable \e{must} be initialized by a subclass. +*/ + +/*! + \variable QScreen::d + \brief the pixel depth + + This is the number of significant bits used to set a pixel + color. This variable \e{must} be initialized by a subclass. +*/ + +/*! + \variable QScreen::pixeltype + \brief set to BGRPixel + + Set this variable to BGRPixel in a subclass, if the screen pixel + format is a BGR type and you have used setPixelFormat() to set the + pixel format to the corresponding RGB format. e.g., you have set the + pixel format to QImage::Format_RGB555, but your screen really uses + BGR, not RGB. +*/ + +/*! + \variable QScreen::grayscale + \brief the gray scale screen mode flag + + Set this variable to true in a subclass, if you are using a + grayscale screen mode. e.g., in an 8-bit mode where you don't want + to use the palette, but you want to use the grayscales. +*/ + +/*! + \variable QScreen::dw + \brief the device width + + This is the number of pixels in a row of the physical screen. It + \e{must} be initialized by a subclass. Normally, it should be set to + the logical width QScreen::w, but it might be different, e.g., if + you are doing rotations in software. + + \sa QScreen::w +*/ + +/*! + \variable QScreen::dh + \brief the device height + + This is the number of pixels in a column of the physical screen. It + \e{must} be initialized by a subclass. Normally, it should be set to + the logical height QScreen::h, but it might be different, e.g., if + you are doing rotations in software. + + \sa QScreen::h +*/ + +/*! + \variable QScreen::size + \brief the number of bytes in the visible region of the frame buffer + + This is the number of bytes in the visible part of the block pointed + to by the QScreen::data pointer. You must initialize this variable + if you initialize the QScreen::data pointer. + + \sa QScreen::data, QScreen::mapsize +*/ + +/*! + \variable QScreen::mapsize + \brief the total number of bytes in the frame buffer + + This is the total number of bytes in the block pointed to by the + QScreen::data pointer. You must initialize this variable if you + initialize the QScreen::data pointer. + + \sa QScreen::data, QScreen::size +*/ + +/*! + \variable QScreen::physWidth + \brief the physical width of the screen in millimeters. + + Currently, this variable is used when calculating the screen DPI, + which in turn is used when deciding the actual font size Qt is + using. +*/ + +/*! + \variable QScreen::physHeight + \brief the physical height of the screen in millimeters. + + Currently, this variable is used when calculating the screen DPI, + which in turn is used when deciding the actual font size Qt is + using. +*/ + +/*! + \fn static QScreen* QScreen::instance() + + Returns a pointer to the application's QScreen instance. + + If this screen consists of several subscreens, operations to the + returned instance will affect all its subscreens. Use the + subscreens() function to retrieve access to a particular + subscreen. + + \sa subScreens(), subScreenIndexAt() +*/ + +/*! + \fn QList QScreen::subScreens() const + \since 4.2 + + Returns a list of this screen's subscreens. Use the + subScreenIndexAt() function to retrieve the index of a screen at a + given position. + + Note that if \e this screen consists of several subscreens, + operations to \e this instance will affect all subscreens by + default. + + \sa instance(), subScreenIndexAt() +*/ + +/*! + \fn int QScreen::physicalWidth() const + \since 4.2 + + Returns the physical width of the screen in millimeters. + + \sa width(), deviceWidth(), physicalHeight() +*/ + +/*! + \fn int QScreen::physicalHeight() const + \since 4.2 + + Returns the physical height of the screen in millimeters. + + \sa height(), deviceHeight(), physicalWidth() +*/ + +/*! + \fn virtual bool QScreen::initDevice() = 0 + + This function is called by the \l{Qt for Embedded Linux} server to + initialize the framebuffer. Note that a server application will call the + connect() function prior to this function. + + Implement this function to make accelerated drivers set up the + graphics card. Return true to indicate success and false to indicate + failure. + + \sa shutdownDevice(), connect() +*/ + +/*! + \fn virtual bool QScreen::connect(const QString &displaySpec) = 0 + + This function is called by every \l{Qt for Embedded Linux} + application on startup, and must be implemented to map in the + framebuffer and the accelerated drivers that the graphics card + control registers. Note that connect must be called \e before + the initDevice() function. + + Ensure that true is returned if a connection to the screen device + is made. Otherwise, return false. Upon making the connection, the + function should read out the parameters of the framebuffer and use + them as required to set this class's protected variables. + + The \a displaySpec argument is passed by the QWS_DISPLAY + environment variable or the -display command line parameter, and + has the following syntax: + + \snippet doc/src/snippets/code/src_gui_embedded_qscreen_qws.cpp 0 + + For example, to use the mach64 driver on fb1 as display 2: + + \snippet doc/src/snippets/code/src_gui_embedded_qscreen_qws.cpp 1 + + See \l{Qt for Embedded Linux Display Management} for more details. + + \sa disconnect(), initDevice(), {Running Qt for Embedded Linux Applications} +*/ + +/*! + \fn QScreen::disconnect() + + This function is called by every \l{Qt for Embedded Linux} application + before exiting, and must be implemented to unmap the + framebuffer. Note that a server application will call the + shutdownDevice() function prior to this function. + + \sa connect(), shutdownDevice(), {Running Qt for Embedded Linux + Applications} +*/ + +/*! + \fn QScreen::setMode(int width, int height, int depth) + + Implement this function to reset the framebuffer's resolution (\a + width and \a height) and bit \a depth. + + After the resolution has been set, existing paint engines will be + invalid and the framebuffer should be completely redrawn. In a + multiple-process situation, all other applications must be + notified to reset their mode and update themselves accordingly. +*/ + +/*! + \fn QScreen::blank(bool on) + + Prevents the screen driver form displaying any content on the + screen. + + Note that the default implementation does nothing. + + Reimplement this function to prevent the screen driver from + displaying any contents on the screen if \a on is true; otherwise + the contents is expected to be shown. + + \sa blit() +*/ + +/*! + \fn int QScreen::pixmapOffsetAlignment() + + Returns the value (in bits) to which the start address of pixmaps + held in the graphics card's memory, should be aligned. + + Note that the default implementation returns 64; reimplement this + function to override the return value, e.g., when implementing an + accelerated driver (see the \l {Adding an Accelerated Graphics + Driver to Qt for Embedded Linux}{Adding an Accelerated Graphics Driver} + documentation for details). + + \sa pixmapLinestepAlignment() +*/ + +/*! + \fn int QScreen::pixmapLinestepAlignment() + + Returns the value (in bits) to which individual scanlines of + pixmaps held in the graphics card's memory, should be + aligned. + + Note that the default implementation returns 64; reimplement this + function to override the return value, e.g., when implementing an + accelerated driver (see the \l {Adding an Accelerated Graphics + Driver to Qt for Embedded Linux}{Adding an Accelerated Graphics Driver} + documentation for details). + + \sa pixmapOffsetAlignment() +*/ + +/*! + \fn QScreen::width() const + + Returns the logical width of the framebuffer in pixels. + + \sa deviceWidth(), physicalWidth(), height() +*/ + +/*! + \fn int QScreen::height() const + + Returns the logical height of the framebuffer in pixels. + + \sa deviceHeight(), physicalHeight(), width() +*/ + +/*! + \fn QScreen::depth() const + + Returns the depth of the framebuffer, in bits per pixel. + + Note that the returned depth is the number of bits each pixel + fills rather than the number of significant bits, so 24bpp and + 32bpp express the same range of colors (8 bits of red, green and + blue). + + \sa clut(), pixmapDepth() +*/ + +/*! + \fn int QScreen::pixmapDepth() const + + Returns the preferred depth for pixmaps, in bits per pixel. + + \sa depth() +*/ + +/*! + \fn QScreen::linestep() const + + Returns the length of each scanline of the framebuffer in bytes. + + \sa isInterlaced() +*/ + +/*! + \fn QScreen::deviceWidth() const + + Returns the physical width of the framebuffer device in pixels. + + Note that the returned width can differ from the width which + \l{Qt for Embedded Linux} will actually use, that is if the display is + centered within the framebuffer. + + \sa width(), physicalWidth(), deviceHeight() +*/ + +/*! + \fn QScreen::deviceHeight() const + + Returns the full height of the framebuffer device in pixels. + + Note that the returned height can differ from the height which + \l{Qt for Embedded Linux} will actually use, that is if the display is + centered within the framebuffer. + + \sa height(), physicalHeight(), deviceWidth() +*/ + +/*! + \fn uchar *QScreen::base() const + + Returns a pointer to the beginning of the framebuffer. + + \sa onCard(), region(), totalSize() +*/ + +/*! + \fn uchar *QScreen::cache(int) + + \internal + + This function is used to store pixmaps in graphics memory for the + use of the accelerated drivers. See QLinuxFbScreen (where the + caching is implemented) for more information. +*/ + +/*! + \fn QScreen::uncache(uchar *) + + \internal + + This function is called on pixmap destruction to remove them from + graphics card memory. +*/ + +/*! + \fn QScreen::screenSize() const + + Returns the size of the screen in bytes. + + The screen size is always located at the beginning of framebuffer + memory, i.e. it can also be retrieved using the base() function. + + \sa base(), region() +*/ + +/*! + \fn QScreen::totalSize() const + + Returns the size of the available graphics card memory (including + the screen) in bytes. + + \sa onCard() +*/ + +// Unaccelerated screen/driver setup. Can be overridden by accelerated +// drivers + +/*! + \fn QScreen::QScreen(int displayId) + + Constructs a new screen driver. + + The \a displayId identifies the \l{Qt for Embedded Linux} server to connect + to. +*/ + +/*! + \fn QScreen::clut() + + Returns a pointer to the screen's color lookup table (i.e. its + color palette). + + Note that this function only apply in paletted modes like 8-bit, + i.e. in modes where only the palette indexes (and not the actual + color values) are stored in memory. + + \sa alloc(), depth(), colorCount() +*/ + +/*! + \obsolete + \fn int QScreen::numCols() + + \sa colorCount() +*/ + +/*! + \since 4.6 + \fn int QScreen::colorCount() + + Returns the number of entries in the screen's color lookup table + (i.e. its color palette). A pointer to the color table can be + retrieved using the clut() function. + + \sa clut(), alloc() +*/ + +/*! + \since 4.4 + + Constructs a new screen driver. + + The \a display_id identifies the \l{Qt for Embedded Linux} + server to connect to. The \a classId specifies the class + identifier. +*/ +QScreen::QScreen(int display_id, ClassId classId) + : screencols(0), data(0), entries(0), entryp(0), lowest(0), + w(0), lstep(0), h(0), d(1), pixeltype(NormalPixel), grayscale(false), + dw(0), dh(0), size(0), mapsize(0), displayId(display_id), + physWidth(0), physHeight(0), d_ptr(new QScreenPrivate(this, classId)) +{ + clearCacheFunc = 0; +} + +QScreen::QScreen(int display_id) + : screencols(0), data(0), entries(0), entryp(0), lowest(0), + w(0), lstep(0), h(0), d(1), pixeltype(NormalPixel), grayscale(false), + dw(0), dh(0), size(0), mapsize(0), displayId(display_id), + physWidth(0), physHeight(0), d_ptr(new QScreenPrivate(this)) +{ + clearCacheFunc = 0; +} + +/*! + Destroys this screen driver. +*/ + +QScreen::~QScreen() +{ + delete d_ptr; +} + +/*! + This function is called by the \l{Qt for Embedded Linux} server before it + calls the disconnect() function when exiting. + + Note that the default implementation only hides the mouse cursor; + reimplement this function to do the necessary graphics card + specific cleanup. + + \sa initDevice(), disconnect() +*/ + +void QScreen::shutdownDevice() +{ +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->hide(); +#endif +} + +extern bool qws_accel; //in qapplication_qws.cpp + +/*! + \fn PixelType QScreen::pixelType() const + + Returns the pixel storage format of the screen. +*/ + +/*! + Returns the pixel format of the screen, or \c QImage::Format_Invalid + if the pixel format is not a supported image format. + +*/ +QImage::Format QScreen::pixelFormat() const +{ + return d_ptr->pixelFormat; +} + +/*! + Sets the screen's pixel format to \a format. + */ +void QScreen::setPixelFormat(QImage::Format format) +{ + d_ptr->pixelFormat = format; +} + + +/*! + \fn int QScreen::alloc(unsigned int red, unsigned int green, unsigned int blue) + + Returns the index in the screen's palette which is the closest + match to the given RGB value (\a red, \a green, \a blue). + + Note that this function only apply in paletted modes like 8-bit, + i.e. in modes where only the palette indexes (and not the actual + color values) are stored in memory. + + \sa clut(), colorCount() +*/ + +int QScreen::alloc(unsigned int r,unsigned int g,unsigned int b) +{ + int ret = 0; + if (d == 8) { + if (grayscale) + return qGray(r, g, b); + + // First we look to see if we match a default color + const int pos = (r + 25) / 51 * 36 + (g + 25) / 51 * 6 + (b + 25) / 51; + if (pos < screencols && screenclut[pos] == qRgb(r, g, b)) { + return pos; + } + + // search for nearest color + unsigned int mindiff = 0xffffffff; + unsigned int diff; + int dr,dg,db; + + for (int loopc = 0; loopc < screencols; ++loopc) { + dr = qRed(screenclut[loopc]) - r; + dg = qGreen(screenclut[loopc]) - g; + db = qBlue(screenclut[loopc]) - b; + diff = dr*dr + dg*dg + db*db; + + if (diff < mindiff) { + ret = loopc; + if (!diff) + break; + mindiff = diff; + } + } + } else if (d == 4) { + ret = qGray(r, g, b) >> 4; + } else if (d == 1) { + ret = qGray(r, g, b) >= 128; + } else { + qFatal("cannot alloc %dbpp color", d); + } + + return ret; +} + +/*! + Saves the current state of the graphics card. + + For example, hardware screen drivers should reimplement the save() + and restore() functions to save and restore its registers, + enabling swintching between virtual consoles. + + Note that the default implementation does nothing. + + \sa restore() +*/ + +void QScreen::save() +{ +} + +/*! + Restores the previously saved state of the graphics card. + + For example, hardware screen drivers should reimplement the save() + and restore() functions to save and restore its registers, + enabling swintching between virtual consoles. + + Note that the default implementation does nothing. + + \sa save() +*/ + +void QScreen::restore() +{ +} + +void QScreen::blank(bool) +{ +} + +/*! + \internal +*/ + +void QScreen::set(unsigned int, unsigned int, unsigned int, unsigned int) +{ +} + +/*! + \fn bool QScreen::supportsDepth(int depth) const + + Returns true if the screen supports the specified color \a depth; + otherwise returns false. + + \sa clut() +*/ + +bool QScreen::supportsDepth(int d) const +{ + if (false) { + //Just to simplify the ifdeffery +#ifdef QT_QWS_DEPTH_1 + } else if(d==1) { + return true; +#endif +#ifdef QT_QWS_DEPTH_4 + } else if(d==4) { + return true; +#endif +#ifdef QT_QWS_DEPTH_8 + } else if(d==8) { + return true; +#endif +#ifdef QT_QWS_DEPTH_16 + } else if(d==16) { + return true; +#endif +#ifdef QT_QWS_DEPTH_15 + } else if (d == 15) { + return true; +#endif +#ifdef QT_QWS_DEPTH_18 + } else if(d==18 || d==19) { + return true; +#endif +#ifdef QT_QWS_DEPTH_24 + } else if(d==24) { + return true; +#endif +#ifdef QT_QWS_DEPTH_32 + } else if(d==32) { + return true; +#endif + } + return false; +} + +/*! + \fn bool QScreen::onCard(const unsigned char *buffer) const + + Returns true if the specified \a buffer is within the graphics + card's memory; otherwise returns false (i.e. if it's in main RAM). + + \sa base(), totalSize() +*/ + +bool QScreen::onCard(const unsigned char * p) const +{ + long t=(unsigned long)p; + long bmin=(unsigned long)data; + if (t < bmin) + return false; + if(t >= bmin+mapsize) + return false; + return true; +} + +/*! + \fn bool QScreen::onCard(const unsigned char * buffer, ulong& offset) const + \overload + + If the specified \a buffer is within the graphics card's memory, + this function stores the offset from the start of graphics card + memory (in bytes), in the location specified by the \a offset + parameter. +*/ + +bool QScreen::onCard(const unsigned char * p, ulong& offset) const +{ + long t=(unsigned long)p; + long bmin=(unsigned long)data; + if (t < bmin) + return false; + long o = t - bmin; + if (o >= mapsize) + return false; + offset = o; + return true; +} + +/* +#if !defined(QT_NO_QWS_REPEATER) + { "Repeater", qt_get_screen_repeater, 0 }, +#endif +#if defined(QT_QWS_EE) + { "EE", qt_get_screen_ee, 0 }, +#endif + +*/ + +/* +Given a display_id (number of the \l{Qt for Embedded Linux} server to connect to) +and a spec (e.g. Mach64:/dev/fb0) return a QScreen-descendant. +The QScreenDriverFactory is queried for a suitable driver and, if found, +asked to create a driver. +People writing new graphics drivers should either hook their own +QScreen-descendant into QScreenDriverFactory or use the QScreenDriverPlugin +to make a dynamically loadable driver. +*/ + +Q_GUI_EXPORT QScreen* qt_get_screen(int display_id, const char *spec) +{ + QString displaySpec = QString::fromAscii(spec); + QString driver = displaySpec; + int colon = displaySpec.indexOf(QLatin1Char(':')); + if (colon >= 0) + driver.truncate(colon); + driver = driver.trimmed(); + + bool foundDriver = false; + QString driverName = driver; + + QStringList driverList; + if (!driver.isEmpty()) + driverList << driver; + else + driverList = QScreenDriverFactory::keys(); + + for (int i = 0; i < driverList.size(); ++i) { + const QString driverName = driverList.at(i); + qt_screen = QScreenDriverFactory::create(driverName, display_id); + if (qt_screen) { + foundDriver = true; + if (qt_screen->connect(displaySpec)) { + return qt_screen; + } else { + delete qt_screen; + qt_screen = 0; + } + } + } + + if (driver.isNull()) + qFatal("No suitable driver found"); + else if (foundDriver) + qFatal("%s: driver cannot connect", driver.toLatin1().constData()); + else + qFatal("%s: driver not found", driver.toLatin1().constData()); + + return 0; +} + +#ifndef QT_NO_QWS_CURSOR +static void blendCursor(QImage *dest, const QImage &cursor, const QPoint &offset) +{ + QRasterBuffer rb; + rb.prepare(dest); + + QSpanData spanData; + spanData.init(&rb, 0); + spanData.type = QSpanData::Texture; + spanData.initTexture(&cursor, 256); + spanData.dx = -offset.x(); + spanData.dy = -offset.y(); + if (!spanData.blend) + return; + + const QRect rect = QRect(offset, cursor.size()) + & QRect(QPoint(0, 0), dest->size()); + const int w = rect.width(); + const int h = rect.height(); + + QVarLengthArray spans(h); + for (int i = 0; i < h; ++i) { + spans[i].x = rect.x(); + spans[i].len = w; + spans[i].y = rect.y() + i; + spans[i].coverage = 255; + } + spanData.blend(h, spans.constData(), &spanData); +} +#endif // QT_NO_QWS_CURSOR + +/*! + \fn void QScreen::exposeRegion(QRegion region, int windowIndex) + + This function is called by the \l{Qt for Embedded Linux} server whenever a + screen update is required. \a region is the area on the screen + that must be updated, and \a windowIndex is the index into + QWSServer::clientWindows() of the window that required the + update. QWSWindow::state() gives more information about the cause. + + The default implementation composes the + affected windows and paints the given \a region on screen by + calling the blit() and solidFill() functions + + This function can be reimplemented to perform composition in + hardware, or to perform transition effects. + For simpler hardware acceleration, or to interface with + this is typically done by reimplementing the blit() and + solidFill() functions instead. + + Note that there is no need to call this function explicitly. + + \sa blit(), solidFill(), blank() +*/ +void QScreen::exposeRegion(QRegion r, int windowIndex) +{ + r &= region(); + if (r.isEmpty()) + return; + + int changing = windowIndex; + // when we have just lowered a window, we have to expose all the windows below where the + // window used to be. + if (changing && qwsServer->clientWindows().at(changing)->state() == QWSWindow::Lowering) + changing = 0; +#ifdef QTOPIA_PERFTEST + static enum { PerfTestUnknown, PerfTestOn, PerfTestOff } perfTestState = PerfTestUnknown; + if(PerfTestUnknown == perfTestState) { + if(::getenv("QTOPIA_PERFTEST")) + perfTestState = PerfTestOn; + else + perfTestState = PerfTestOff; + } + if(PerfTestOn == perfTestState) { + QWSWindow *changed = qwsServer->clientWindows().at(changing); + if(!changed->client()->identity().isEmpty()) + qDebug() << "Performance : expose_region :" + << changed->client()->identity() + << r.boundingRect() << ": " + << qPrintable( QTime::currentTime().toString( "h:mm:ss.zzz" ) ); + } +#endif + + const QRect bounds = r.boundingRect(); + QRegion blendRegion; + QImage *blendBuffer = 0; + +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor && !qt_screencursor->isAccelerated()) { + blendRegion = r & qt_screencursor->boundingRect(); + } +#endif + compose(0, r, blendRegion, &blendBuffer, changing); + + if (blendBuffer && !blendBuffer->isNull()) { + const QPoint offset = blendRegion.boundingRect().topLeft(); +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor && !qt_screencursor->isAccelerated()) { + const QRect cursorRect = qt_screencursor->boundingRect(); + if (blendRegion.intersects(cursorRect)) { + blendCursor(blendBuffer, qt_screencursor->image(), + cursorRect.topLeft() - offset); + } + } +#endif // QT_NO_QWS_CURSOR + blit(*blendBuffer, offset, blendRegion); + delete blendBuffer; + } + + if (r.rectCount() == 1) { + setDirty(r.boundingRect()); + } else { + const QVector rects = r.rects(); + for (int i = 0; i < rects.size(); ++i) + setDirty(rects.at(i)); + } +} + +/*! + \fn void QScreen::blit(const QImage &image, const QPoint &topLeft, const QRegion ®ion) + + Copies the given \a region in the given \a image to the point + specified by \a topLeft using device coordinates. + + This function is called from the exposeRegion() function; it is + not intended to be called explicitly. + + Reimplement this function to make use of \l{Adding an Accelerated + Graphics Driver to Qt for Embedded Linux}{accelerated hardware}. Note that + this function must be reimplemented if the framebuffer format is + not supported by \l{Qt for Embedded Linux} (See the + \l{Qt for Embedded Linux Display Management}{Display Management} + documentation for more details). + + \sa exposeRegion(), solidFill(), blank() +*/ +void QScreen::blit(const QImage &img, const QPoint &topLeft, const QRegion ®) +{ + const QRect bound = (region() & QRect(topLeft, img.size())).boundingRect(); + QWSDisplay::grab(); + d_ptr->blit(this, img, topLeft - offset(), + (reg & bound).translated(-topLeft)); + QWSDisplay::ungrab(); +} + +#ifdef QT_QWS_CLIENTBLIT +/*! + Returns true if this screen driver supports calling QScreen::blit() and + QScreen::setDirty() directly from non-server applications, otherwise returns + false. + + If available, this is used to optimize the performance of non-occluded, opaque + client windows by removing the server round trip when they are updated. + + \sa setSupportsBlitInClients() + */ +bool QScreen::supportsBlitInClients() const +{ + return d_ptr->supportsBlitInClients; +} + +/*! + If \a supported, the screen driver is marked as supporting blitting directly + from non-server applications. + + \sa supportsBlitInClients() + */ +void QScreen::setSupportsBlitInClients(bool supported) +{ + d_ptr->supportsBlitInClients = supported; +} +#endif + +/*! + \internal +*/ + +void QScreen::blit(QWSWindow *win, const QRegion &clip) +{ + QWSWindowSurface *surface = win->windowSurface(); + if (!surface) + return; + + const QImage &img = surface->image(); + if (img.isNull()) + return; + + const QRegion rgn = clip & win->paintedRegion(); + if (rgn.isEmpty()) + return; + + surface->lock(); + blit(img, win->requestedRegion().boundingRect().topLeft(), rgn); + surface->unlock(); +} + +struct fill_data { + quint32 color; + uchar *data; + int lineStep; + int x; + int y; + int w; + int h; +}; + +/*! + Fills the given \a region of the screen with the specified \a + color. + + This function is called from the exposeRegion() function; it is + not intended to be called explicitly. + + Reimplement this function to make use of \l{Adding an Accelerated + Graphics Driver to Qt for Embedded Linux}{accelerated hardware}. Note that + this function must be reimplemented if the framebuffer format is + not supported by \l{Qt for Embedded Linux} (See the + \l{Qt for Embedded Linux Display Management}{Display Management} + documentation for more details). + + \sa exposeRegion(), blit(), blank() +*/ +// the base class implementation works in device coordinates, so that transformed drivers can use it +void QScreen::solidFill(const QColor &color, const QRegion ®ion) +{ + QWSDisplay::grab(); + d_ptr->solidFill(this, color, + region.translated(-offset()) & QRect(0, 0, dw, dh)); + QWSDisplay::ungrab(); +} + +/*! + \since 4.2 + + Creates and returns a new window surface matching the given \a + key. + + The server application will call this function whenever it needs + to create a server side representation of a window, e.g. when + copying the content of memory to the screen using the screen + driver. + + Note that this function must be reimplemented when adding an + accelerated graphics driver. See the + \l{Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + {Adding an Accelerated Graphics Driver} documentation for details. + + \sa {Qt for Embedded Linux Architecture} +*/ +QWSWindowSurface* QScreen::createSurface(const QString &key) const +{ +#ifndef QT_NO_PAINTONSCREEN + if (key == QLatin1String("OnScreen")) + return new QWSOnScreenSurface; + else +#endif + if (key == QLatin1String("mem")) + return new QWSLocalMemSurface; +#ifndef QT_NO_QWS_MULTIPROCESS + else if (key == QLatin1String("shm")) + return new QWSSharedMemSurface; +#endif +#ifndef QT_NO_PAINT_DEBUG + else if (key == QLatin1String("Yellow")) + return new QWSYellowSurface; +#endif +#ifndef QT_NO_DIRECTPAINTER + else if (key == QLatin1String("DirectPainter")) + return new QWSDirectPainterSurface; +#endif + + return 0; +} + +#ifndef QT_NO_PAINTONSCREEN +bool QScreen::isWidgetPaintOnScreen(const QWidget *w) +{ + static int doOnScreen = -1; + if (doOnScreen == -1) { + const QByteArray env = qgetenv("QT_ONSCREEN_PAINT"); + if (env == "force") + doOnScreen = 2; + else + doOnScreen = (env.toInt() > 0 ? 1 : 0); + } + + if (doOnScreen == 2) // force + return true; + + if (doOnScreen == 0 && !w->testAttribute(Qt::WA_PaintOnScreen)) + return false; + + return w->d_func()->isOpaque; +} +#endif + +/*! + \overload + + Creates and returns a new window surface for the given \a widget. +*/ +QWSWindowSurface* QScreen::createSurface(QWidget *widget) const +{ +#ifndef QT_NO_PAINTONSCREEN + if (isWidgetPaintOnScreen(widget) && base()) + return new QWSOnScreenSurface(widget); + else +#endif + if (QApplication::type() == QApplication::GuiServer) + return new QWSLocalMemSurface(widget); +#ifndef QT_NO_QWS_MULTIPROCESS + else + return new QWSSharedMemSurface(widget); +#endif + + return 0; +} + +void QScreen::compose(int level, const QRegion &exposed, QRegion &blend, + QImage **blendbuffer, int changing_level) +{ + QRect exposed_bounds = exposed.boundingRect(); + QWSWindow *win = 0; + do { + win = qwsServer->clientWindows().value(level); // null is background + ++level; + } while (win && !win->paintedRegion().boundingRect().intersects(exposed_bounds)); + + QWSWindowSurface *surface = (win ? win->windowSurface() : 0); + bool above_changing = level <= changing_level; // 0 is topmost + + QRegion exposedBelow = exposed; + bool opaque = true; + + if (win) { + opaque = win->isOpaque() || !surface->isBuffered(); + if (opaque) { + exposedBelow -= win->paintedRegion(); + if (above_changing || !surface->isBuffered()) + blend -= exposed & win->paintedRegion(); + } else { + blend += exposed & win->paintedRegion(); + } + } + if (win && !exposedBelow.isEmpty()) { + compose(level, exposedBelow, blend, blendbuffer, changing_level); + } else { + QSize blendSize = blend.boundingRect().size(); + if (!blendSize.isNull()) { + *blendbuffer = new QImage(blendSize, d_ptr->preferredImageFormat()); + } + } + + const QRegion blitRegion = exposed - blend; + if (!win) + paintBackground(blitRegion); + else if (!above_changing && surface->isBuffered()) + blit(win, blitRegion); + + QRegion blendRegion = exposed & blend; + + if (win) + blendRegion &= win->paintedRegion(); + if (!blendRegion.isEmpty()) { + + QPoint off = blend.boundingRect().topLeft(); + + QRasterBuffer rb; + rb.prepare(*blendbuffer); + QSpanData spanData; + spanData.init(&rb, 0); + if (!win) { + const QImage::Format format = (*blendbuffer)->format(); + switch (format) { + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_ARGB32: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + spanData.rasterBuffer->compositionMode = QPainter::CompositionMode_Source; + break; + default: + break; + } + spanData.setup(qwsServer->backgroundBrush(), 256, QPainter::CompositionMode_Source); + spanData.dx = off.x(); + spanData.dy = off.y(); + } else if (!surface->isBuffered()) { + return; + } else { + const QImage &img = surface->image(); + QPoint winoff = off - win->requestedRegion().boundingRect().topLeft(); + // convert win->opacity() from scale [0..255] to [0..256] + int const_alpha = win->opacity(); + const_alpha += (const_alpha >> 7); + spanData.type = QSpanData::Texture; + spanData.initTexture(&img, const_alpha); + spanData.dx = winoff.x(); + spanData.dy = winoff.y(); + } + if (!spanData.blend) + return; + + if (surface) + surface->lock(); + const QVector rects = blendRegion.rects(); + const int nspans = 256; + QT_FT_Span spans[nspans]; + for (int i = 0; i < rects.size(); ++i) { + int y = rects.at(i).y() - off.y(); + int ye = y + rects.at(i).height(); + int x = rects.at(i).x() - off.x(); + int len = rects.at(i).width(); + while (y < ye) { + int n = qMin(nspans, ye - y); + int i = 0; + while (i < n) { + spans[i].x = x; + spans[i].len = len; + spans[i].y = y + i; + spans[i].coverage = 255; + ++i; + } + spanData.blend(n, spans, &spanData); + y += n; + } + } + if (surface) + surface->unlock(); + } +} + +void QScreen::paintBackground(const QRegion &r) +{ + const QBrush &bg = qwsServer->backgroundBrush(); + Qt::BrushStyle bs = bg.style(); + if (bs == Qt::NoBrush || r.isEmpty()) + return; + + if (bs == Qt::SolidPattern) { + solidFill(bg.color(), r); + } else { + const QRect br = r.boundingRect(); + QImage img(br.size(), d_ptr->preferredImageFormat()); + QPoint off = br.topLeft(); + QRasterBuffer rb; + rb.prepare(&img); + QSpanData spanData; + spanData.init(&rb, 0); + spanData.setup(bg, 256, QPainter::CompositionMode_Source); + spanData.dx = off.x(); + spanData.dy = off.y(); + Q_ASSERT(spanData.blend); + + const QVector rects = r.rects(); + const int nspans = 256; + QT_FT_Span spans[nspans]; + for (int i = 0; i < rects.size(); ++i) { + int y = rects.at(i).y() - off.y(); + int ye = y + rects.at(i).height(); + int x = rects.at(i).x() - off.x(); + int len = rects.at(i).width(); + while (y < ye) { + int n = qMin(nspans, ye - y); + int i = 0; + while (i < n) { + spans[i].x = x; + spans[i].len = len; + spans[i].y = y + i; + spans[i].coverage = 255; + ++i; + } + spanData.blend(n, spans, &spanData); + y += n; + } + } + blit(img, br.topLeft(), r); + } +} + +/*! + \fn virtual int QScreen::sharedRamSize(void *) + + \internal +*/ + +/*! + \fn QScreen::setDirty(const QRect& rectangle) + + Marks the given \a rectangle as dirty. + + Note that the default implementation does nothing; reimplement + this function to indicate that the given \a rectangle has been + altered. +*/ + +void QScreen::setDirty(const QRect&) +{ +} + +/*! + \fn QScreen::isTransformed() const + + Returns true if the screen is transformed (for instance, rotated + 90 degrees); otherwise returns false. + + \sa transformOrientation(), isInterlaced() +*/ + +bool QScreen::isTransformed() const +{ + return false; +} + +/*! + \fn QScreen::isInterlaced() const + + Returns true if the display is interlaced (i.e. is displaying + images progressively like a television screen); otherwise returns + false. + + If the display is interlaced, the drawing is altered to look + better. + + \sa isTransformed(), linestep() +*/ + +bool QScreen::isInterlaced() const +{ + return false;//qws_screen_is_interlaced;; +} + +/*! + \fn QScreen::mapToDevice(const QSize &size) const + + Maps the given \a size from the coordinate space used by the + application to the framebuffer coordinate system. Note that the + default implementation simply returns the given \a size as it is. + + Reimplement this function to use the given device's coordinate + system when mapping. + + \sa mapFromDevice() +*/ + +QSize QScreen::mapToDevice(const QSize &s) const +{ + return s; +} + +/*! + \fn QScreen::mapFromDevice(const QSize &size) const + + Maps the given \a size from the framebuffer coordinate system to + the coordinate space used by the application. Note that the + default implementation simply returns the given \a size as it is. + + Reimplement this function to use the given device's coordinate + system when mapping. + + \sa mapToDevice() +*/ + +QSize QScreen::mapFromDevice(const QSize &s) const +{ + return s; +} + +/*! + \fn QScreen::mapToDevice(const QPoint &point, const QSize &screenSize) const + \overload + + Maps the given \a point from the coordinate space used by the + application to the framebuffer coordinate system, passing the + device's \a screenSize as argument. Note that the default + implementation returns the given \a point as it is. +*/ + +QPoint QScreen::mapToDevice(const QPoint &p, const QSize &) const +{ + return p; +} + +/*! + \fn QScreen::mapFromDevice(const QPoint &point, const QSize &screenSize) const + \overload + + Maps the given \a point from the framebuffer coordinate system to + the coordinate space used by the application, passing the device's + \a screenSize as argument. Note that the default implementation + simply returns the given \a point as it is. +*/ + +QPoint QScreen::mapFromDevice(const QPoint &p, const QSize &) const +{ + return p; +} + +/*! + \fn QScreen::mapToDevice(const QRect &rectangle, const QSize &screenSize) const + \overload + + Maps the given \a rectangle from the coordinate space used by the + application to the framebuffer coordinate system, passing the + device's \a screenSize as argument. Note that the default + implementation returns the given \a rectangle as it is. +*/ + +QRect QScreen::mapToDevice(const QRect &r, const QSize &) const +{ + return r; +} + +/*! + \fn QScreen::mapFromDevice(const QRect &rectangle, const QSize &screenSize) const + \overload + + Maps the given \a rectangle from the framebuffer coordinate system to + the coordinate space used by the application, passing the device's + \a screenSize as argument. Note that the default implementation + simply returns the given \a rectangle as it is. +*/ + +QRect QScreen::mapFromDevice(const QRect &r, const QSize &) const +{ + return r; +} + +/*! + \fn QScreen::mapToDevice(const QImage &image) const + \overload + + Maps the given \a image from the coordinate space used by the + application to the framebuffer coordinate system. Note that the + default implementation returns the given \a image as it is. +*/ + +QImage QScreen::mapToDevice(const QImage &i) const +{ + return i; +} + +/*! + \fn QScreen::mapFromDevice(const QImage &image) const + \overload + + Maps the given \a image from the framebuffer coordinate system to + the coordinate space used by the application. Note that the + default implementation simply returns the given \a image as it is. +*/ + +QImage QScreen::mapFromDevice(const QImage &i) const +{ + return i; +} + +/*! + \fn QScreen::mapToDevice(const QRegion ®ion, const QSize &screenSize) const + \overload + + Maps the given \a region from the coordinate space used by the + application to the framebuffer coordinate system, passing the + device's \a screenSize as argument. Note that the default + implementation returns the given \a region as it is. +*/ + +QRegion QScreen::mapToDevice(const QRegion &r, const QSize &) const +{ + return r; +} + +/*! + \fn QScreen::mapFromDevice(const QRegion ®ion, const QSize &screenSize) const + \overload + + Maps the given \a region from the framebuffer coordinate system to + the coordinate space used by the application, passing the device's + \a screenSize as argument. Note that the default implementation + simply returns the given \a region as it is. +*/ + +QRegion QScreen::mapFromDevice(const QRegion &r, const QSize &) const +{ + return r; +} + +/*! + \fn QScreen::transformOrientation() const + + Returns the current rotation as an integer value. + + Note that the default implementation returns 0; reimplement this + function to override this value. + + \sa isTransformed() +*/ + +int QScreen::transformOrientation() const +{ + return 0; +} + +int QScreen::pixmapDepth() const +{ + return depth(); +} + +/*! + \internal +*/ +int QScreen::memoryNeeded(const QString&) +{ + return 0; +} + +/*! + \internal +*/ +void QScreen::haltUpdates() +{ +} + +/*! + \internal +*/ +void QScreen::resumeUpdates() +{ +} + +/*! + \fn QRegion QScreen::region() const + \since 4.2 + + Returns the region covered by this screen driver. + + \sa base(), screenSize() +*/ + +/*! + \internal +*/ +void QScreen::setOffset(const QPoint &p) +{ + d_ptr->offset = p; +} + +/*! + \since 4.2 + + Returns the logical offset of the screen, i.e., the offset between + (0,0) in screen coordinates and the application coordinate system. +*/ +QPoint QScreen::offset() const +{ + return d_ptr->offset; +} + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +void QScreen::setFrameBufferLittleEndian(bool littleEndian) +{ + d_ptr->fb_is_littleEndian = littleEndian; +} + +bool QScreen::frameBufferLittleEndian() const +{ + return d_ptr->fb_is_littleEndian; +} +#endif + +/*! + \fn int QScreen::subScreenIndexAt(const QPoint &position) const + \since 4.2 + + Returns the index of the subscreen at the given \a position; + returns -1 if no screen is found. + + The index identifies the subscreen in the list of pointers + returned by the subScreens() function. + + \sa instance(), subScreens() +*/ +int QScreen::subScreenIndexAt(const QPoint &p) const +{ + const QList screens = subScreens(); + const int n = screens.count(); + for (int i = 0; i < n; ++i) { + if (screens.at(i)->region().contains(p)) + return i; + } + + return -1; +} + +#if 0 +#ifdef QT_LOADABLE_MODULES +#include + +// ### needs update after driver init changes + +static QScreen * qt_dodriver(char * driver,char * a,unsigned char * b) + +{ + char buf[200]; + strcpy(buf,"/etc/qws/drivers/"); + qstrcpy(buf+17,driver); + qDebug("Attempting driver %s",driver); + + void * handle; + handle=dlopen(buf,RTLD_LAZY); + if(handle==0) { + qFatal("Module load error"); + } + QScreen *(*qt_get_screen_func)(char *,unsigned char *); + qt_get_screen_func=dlsym(handle,"qt_get_screen"); + if(qt_get_screen_func==0) { + qFatal("Couldn't get symbol"); + } + QScreen * ret=qt_get_screen_func(a,b); + return ret; +} + +static QScreen * qt_do_entry(char * entry) +{ + unsigned char config[256]; + + FILE * f=fopen(entry,"r"); + if(!f) { + return 0; + } + + int r=fread(config,256,1,f); + if(r<1) + return 0; + + fclose(f); + + unsigned short vendorid=*((unsigned short int *)config); + unsigned short deviceid=*(((unsigned short int *)config)+1); + if(config[0xb]!=3) + return 0; + + if(vendorid==0x1002) { + if(deviceid==0x4c4d) { + qDebug("Compaq Armada/IBM Thinkpad's Mach64 card"); + return qt_dodriver("mach64.so",entry,config); + } else if(deviceid==0x4742) { + qDebug("Desktop Rage Pro Mach64 card"); + return qt_dodriver("mach64.so",entry,config); + } else { + qDebug("Unrecognised ATI card id %x",deviceid); + return 0; + } + } else { + qDebug("Unrecognised vendor"); + } + return 0; +} + +extern bool qws_accel; + +/// ** NOT SUPPPORTED ** + +QScreen * qt_probe_bus() +{ + if(!qws_accel) { + return qt_dodriver("unaccel.so",0,0); + } + + QT_DIR *dirptr = QT_OPENDIR("/proc/bus/pci"); + if(!dirptr) + return qt_dodriver("unaccel.so",0,0); + QT_DIR * dirptr2; + QT_DIRENT *cards; + + QT_DIRENT *busses = QT_READDIR(dirptr); + + while(busses) { + if(busses->d_name[0]!='.') { + char buf[100]; + strcpy(buf,"/proc/bus/pci/"); + qstrcpy(buf+14,busses->d_name); + int p=strlen(buf); + dirptr2 = QT_OPENDIR(buf); + if(dirptr2) { + cards = QT_READDIR(dirptr2); + while(cards) { + if(cards->d_name[0]!='.') { + buf[p]='/'; + qstrcpy(buf+p+1,cards->d_name); + QScreen * ret=qt_do_entry(buf); + if(ret) + return ret; + } + cards = QT_READDIR(dirptr2); + } + QT_CLOSEDIR(dirptr2); + } + } + busses = QT_READDIR(dirptr); + } + QT_CLOSEDIR(dirptr); + + return qt_dodriver("unaccel.so",0,0); +} + +#else + +char *qt_qws_hardcoded_slot = "/proc/bus/pci/01/00.0"; + +const unsigned char* qt_probe_bus() +{ + const char * slot; + slot=::getenv("QWS_CARD_SLOT"); + if(!slot) + slot=qt_qws_hardcoded_slot; + if (slot) { + static unsigned char config[256]; + FILE * f=fopen(slot,"r"); + if(!f) { + qDebug("Open failure for %s",slot); + slot=0; + } else { + int r=fread((char*)config,256,1,f); + fclose(f); + if(r<1) { + qDebug("Read failure"); + return 0; + } else { + return config; + } + } + } + return 0; +} + +#endif + +#endif // 0 + +/*! + \internal + \since 4.4 +*/ +void QScreen::setPixmapDataFactory(QPixmapDataFactory *factory) +{ + static bool shownWarning = false; + if (!shownWarning) { + qWarning("QScreen::setPixmapDataFactory() is deprecated - use setGraphicsSystem() instead"); + shownWarning = true; + } + + d_ptr->pixmapFactory = factory; +} + +/*! + \internal + \since 4.4 +*/ +QPixmapDataFactory* QScreen::pixmapDataFactory() const +{ + return d_ptr->pixmapFactory; +} + +/*! + \internal + \since 4.5 +*/ +void QScreen::setGraphicsSystem(QGraphicsSystem* system) +{ + d_ptr->graphicsSystem = system; +} + +/*! + \internal + \since 4.5 +*/ +QGraphicsSystem* QScreen::graphicsSystem() const +{ + return d_ptr->graphicsSystem; +} + +/*! + \since 4.4 + + Returns the class identifier for the screen object. +*/ +QScreen::ClassId QScreen::classId() const +{ + return static_cast(d_ptr->classId); +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qscreen_qws.h b/src/gui/embedded/qscreen_qws.h new file mode 100644 index 0000000000..85c775e377 --- /dev/null +++ b/src/gui/embedded/qscreen_qws.h @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREEN_QWS_H +#define QSCREEN_QWS_H + +#include +#include +#include +#include +#include +#include +#include + +struct fb_cmap; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QScreenCursor; +class QBrush; +class QWSWindow; +class QWSWindowSurface; +class QGraphicsSystem; +class QPixmapData; + +#ifndef QT_QWS_DEPTH16_RGB +#define QT_QWS_DEPTH16_RGB 565 +#endif +static const int qt_rbits = (QT_QWS_DEPTH16_RGB/100); +static const int qt_gbits = (QT_QWS_DEPTH16_RGB/10%10); +static const int qt_bbits = (QT_QWS_DEPTH16_RGB%10); +static const int qt_red_shift = qt_bbits+qt_gbits-(8-qt_rbits); +static const int qt_green_shift = qt_bbits-(8-qt_gbits); +static const int qt_neg_blue_shift = 8-qt_bbits; +static const int qt_blue_mask = (1<> qt_neg_blue_shift; + + return (tb & qt_blue_mask) | (tg & qt_green_mask) | (tr & qt_red_mask); +} + +inline ushort qt_convRgbTo16(QRgb c) +{ + const int tr = qRed(c) << qt_red_shift; + const int tg = qGreen(c) << qt_green_shift; + const int tb = qBlue(c) >> qt_neg_blue_shift; + + return (tb & qt_blue_mask) | (tg & qt_green_mask) | (tr & qt_red_mask); +} + +inline QRgb qt_conv16ToRgb(ushort c) +{ + const int r=(c & qt_red_mask); + const int g=(c & qt_green_mask); + const int b=(c & qt_blue_mask); + const int tr = r >> qt_red_shift | r >> qt_red_rounding_shift; + const int tg = g >> qt_green_shift | g >> qt_green_rounding_shift; + const int tb = b << qt_neg_blue_shift | b >> qt_blue_rounding_shift; + + return qRgb(tr,tg,tb); +} + +inline void qt_conv16ToRgb(ushort c, int& r, int& g, int& b) +{ + const int tr=(c & qt_red_mask); + const int tg=(c & qt_green_mask); + const int tb=(c & qt_blue_mask); + r = tr >> qt_red_shift | tr >> qt_red_rounding_shift; + g = tg >> qt_green_shift | tg >> qt_green_rounding_shift; + b = tb << qt_neg_blue_shift | tb >> qt_blue_rounding_shift; +} + +const int SourceSolid=0; +const int SourcePixmap=1; + +#ifndef QT_NO_QWS_CURSOR + +class QScreenCursor; +extern QScreenCursor *qt_screencursor; +extern bool qws_sw_cursor; + +class Q_GUI_EXPORT QScreenCursor +{ +public: + QScreenCursor(); + virtual ~QScreenCursor(); + + virtual void set(const QImage &image, int hotx, int hoty); + virtual void move(int x, int y); + virtual void show(); + virtual void hide(); + + bool supportsAlphaCursor() const { return supportsAlpha; } + + static bool enabled() { return qws_sw_cursor; } + + QRect boundingRect() const { return QRect(pos - hotspot, size); } + QImage image() const { return cursor; } + bool isVisible() const { return enable; } + bool isAccelerated() const { return hwaccel; } + + static void initSoftwareCursor(); + static QScreenCursor* instance() { return qt_screencursor; } + +protected: + QImage cursor; + + QSize size; + QPoint pos; + QPoint hotspot; + uint enable : 1; + uint hwaccel : 1; + uint supportsAlpha : 1; + +private: + friend class QProxyScreenCursor; +}; + +#endif // QT_NO_QWS_CURSOR + +// A (used) chunk of offscreen memory + +class QPoolEntry +{ +public: + unsigned int start; + unsigned int end; + int clientId; +}; + +class QScreen; +class QScreenPrivate; +class QPixmapDataFactory; + +extern Q_GUI_EXPORT QScreen *qt_screen; +typedef void(*ClearCacheFunc)(QScreen *obj, int); + +class Q_GUI_EXPORT QScreen { + +public: + enum ClassId { LinuxFBClass, TransformedClass, VNCClass, MultiClass, + VFbClass, DirectFBClass, SvgalibClass, ProxyClass, + GLClass, IntfbClass, CustomClass = 1024 }; + + QScreen(int display_id, ClassId classId); + explicit QScreen(int display_id); + virtual ~QScreen(); + static QScreen* instance() { return qt_screen; } + virtual bool initDevice() = 0; + virtual bool connect(const QString &displaySpec) = 0; + virtual void disconnect() = 0; + virtual void shutdownDevice(); + virtual void setMode(int,int,int) = 0; + virtual bool supportsDepth(int) const; + + virtual void save(); + virtual void restore(); + virtual void blank(bool on); + + virtual int pixmapOffsetAlignment() { return 64; } + virtual int pixmapLinestepAlignment() { return 64; } + virtual int sharedRamSize(void *) { return 0; } + + virtual bool onCard(const unsigned char *) const; + virtual bool onCard(const unsigned char *, ulong& out_offset) const; + + enum PixelType { NormalPixel, BGRPixel }; + + // sets a single color in the colormap + virtual void set(unsigned int,unsigned int,unsigned int,unsigned int); + // allocates a color + virtual int alloc(unsigned int,unsigned int,unsigned int); + + int width() const { return w; } + int height() const { return h; } + int depth() const { return d; } + virtual int pixmapDepth() const; + PixelType pixelType() const { return pixeltype; } + int linestep() const { return lstep; } + int deviceWidth() const { return dw; } + int deviceHeight() const { return dh; } + uchar * base() const { return data; } + // Ask for memory from card cache with alignment + virtual uchar * cache(int) { return 0; } + virtual void uncache(uchar *) {} + + QImage::Format pixelFormat() const; + + int screenSize() const { return size; } + int totalSize() const { return mapsize; } + + QRgb * clut() { return screenclut; } +#ifdef QT_DEPRECATED + QT_DEPRECATED int numCols() { return screencols; } +#endif + int colorCount() { return screencols; } + + virtual QSize mapToDevice(const QSize &) const; + virtual QSize mapFromDevice(const QSize &) const; + virtual QPoint mapToDevice(const QPoint &, const QSize &) const; + virtual QPoint mapFromDevice(const QPoint &, const QSize &) const; + virtual QRect mapToDevice(const QRect &, const QSize &) const; + virtual QRect mapFromDevice(const QRect &, const QSize &) const; + virtual QImage mapToDevice(const QImage &) const; + virtual QImage mapFromDevice(const QImage &) const; + virtual QRegion mapToDevice(const QRegion &, const QSize &) const; + virtual QRegion mapFromDevice(const QRegion &, const QSize &) const; + virtual int transformOrientation() const; + virtual bool isTransformed() const; + virtual bool isInterlaced() const; + + virtual void setDirty(const QRect&); + + virtual int memoryNeeded(const QString&); + + virtual void haltUpdates(); + virtual void resumeUpdates(); + + // composition manager methods + virtual void exposeRegion(QRegion r, int changing); + + // these work directly on the screen + virtual void blit(const QImage &img, const QPoint &topLeft, const QRegion ®ion); + virtual void solidFill(const QColor &color, const QRegion ®ion); + void blit(QWSWindow *bs, const QRegion &clip); + + virtual QWSWindowSurface* createSurface(QWidget *widget) const; + virtual QWSWindowSurface* createSurface(const QString &key) const; + + virtual QList subScreens() const { return QList(); } + virtual QRegion region() const { return QRect(offset(), QSize(w, h)); } + int subScreenIndexAt(const QPoint &p) const; + + void setOffset(const QPoint &p); + QPoint offset() const; + + int physicalWidth() const { return physWidth; } // physical display size in mm + int physicalHeight() const { return physHeight; } // physical display size in mm + + QPixmapDataFactory* pixmapDataFactory() const; // Deprecated, will be removed in 4.6 + QGraphicsSystem* graphicsSystem() const; + +#ifdef QT_QWS_CLIENTBLIT + bool supportsBlitInClients() const; + void setSupportsBlitInClients(bool); +#endif + + ClassId classId() const; + +protected: + void setPixelFormat(QImage::Format format); + void setPixmapDataFactory(QPixmapDataFactory *factory); // Deprecated, will be removed in 4.6 + void setGraphicsSystem(QGraphicsSystem* system); + + QRgb screenclut[256]; + int screencols; + + uchar * data; + + // Table of allocated lumps, kept in sorted highest-to-lowest order + // The table itself is allocated at the bottom of offscreen memory + // i.e. it's similar to having a stack (the table) and a heap + // (the allocated blocks). Freed space is implicitly described + // by the gaps between the allocated lumps (this saves entries and + // means we don't need to worry about coalescing freed lumps) + + QPoolEntry * entries; + int * entryp; + unsigned int * lowest; + + int w; + int lstep; + int h; + int d; + PixelType pixeltype; + bool grayscale; + + int dw; + int dh; + + int size; // Screen size + int mapsize; // Total mapped memory + + int displayId; + + int physWidth; + int physHeight; + + friend class QWSServer; + friend class QWSServerPrivate; + static ClearCacheFunc clearCacheFunc; + +private: + void compose(int level, const QRegion &exposed, QRegion &blend, + QImage **blendbuffer, int changing_level); + void paintBackground(const QRegion &); + + friend class QWSOnScreenSurface; + static bool isWidgetPaintOnScreen(const QWidget *w); + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + void setFrameBufferLittleEndian(bool littleEndian); + bool frameBufferLittleEndian() const; + friend class QVNCScreen; + friend class QLinuxFbScreen; + friend class QVFbScreen; + friend class QProxyScreen; + friend class QIntfbScreen; +#endif + friend void qt_solidFill_setup(QScreen*, const QColor&, const QRegion&); + friend void qt_blit_setup(QScreen *screen, const QImage &image, + const QPoint &topLeft, const QRegion ®ion); +#ifdef QT_QWS_DEPTH_GENERIC + friend void qt_set_generic_blit(QScreen *screen, int bpp, + int len_red, int len_green, int len_blue, + int len_alpha, int off_red, int off_green, + int off_blue, int off_alpha); +#endif + + QScreenPrivate *d_ptr; +}; + +// This lives in loadable modules + +#ifndef QT_LOADABLE_MODULES +extern "C" QScreen * qt_get_screen(int display_id, const char* spec); +#endif + +// This is in main lib, loads the right module, calls qt_get_screen +// In non-loadable cases just aliases to qt_get_screen + +const unsigned char * qt_probe_bus(); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREEN_QWS_H diff --git a/src/gui/embedded/qscreendriverfactory_qws.cpp b/src/gui/embedded/qscreendriverfactory_qws.cpp new file mode 100644 index 0000000000..ea194e56c5 --- /dev/null +++ b/src/gui/embedded/qscreendriverfactory_qws.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreendriverfactory_qws.h" + +#include "qscreen_qws.h" +#include "qapplication.h" +#include "qscreenlinuxfb_qws.h" +#include "qscreentransformed_qws.h" +#include "qscreenvfb_qws.h" +#include "qscreenmulti_qws_p.h" +#include "qscreenqnx_qws.h" +#include "qscreenintegrityfb_qws.h" +#include +#include "private/qfactoryloader_p.h" +#include "qscreendriverplugin_qws.h" +#ifndef QT_NO_QWS_DIRECTFB +#include "qdirectfbscreen.h" +#endif +#ifndef QT_NO_QWS_VNC +#include "qscreenvnc_qws.h" +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QScreenDriverFactoryInterface_iid, + QLatin1String("/gfxdrivers"), Qt::CaseInsensitive)) + +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + +/*! + \class QScreenDriverFactory + \ingroup qws + + \brief The QScreenDriverFactory class creates screen drivers in + Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QScreenDriverFactory is used to detect and instantiate the + available screen drivers, allowing \l{Qt for Embedded Linux} to load the + preferred driver into the server application at runtime. The + create() function returns a QScreen object representing the screen + driver identified by a given key. The valid keys (i.e. the + supported drivers) can be retrieved using the keys() function. + + + \l{Qt for Embedded Linux} provides several built-in screen drivers. In + addition, custom screen drivers can be added using Qt's plugin + mechanism, i.e. by subclassing the QScreen class and creating a + screen driver plugin (QScreenDriverPlugin). See the + \l{Qt for Embedded Linux Display Management}{display management} + documentation for details. + + \sa QScreen, QScreenDriverPlugin +*/ + +/*! + Creates the screen driver specified by the given \a key, using the + display specified by the given \a displayId. + + Note that the keys are case-insensitive. + + \sa keys() +*/ +QScreen *QScreenDriverFactory::create(const QString& key, int displayId) +{ + QString driver = key.toLower(); +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_QNX) + if (driver == QLatin1String("qnx") || driver.isEmpty()) + return new QQnxScreen(displayId); +#endif +#if defined(Q_OS_INTEGRITY) && !defined(QT_NO_QWS_INTEGRITY) + if (driver == QLatin1String("integrityfb") || driver.isEmpty()) + return new QIntfbScreen(displayId); +#endif +#ifndef QT_NO_QWS_QVFB + if (driver == QLatin1String("qvfb") || driver.isEmpty()) + return new QVFbScreen(displayId); +#endif +#ifndef QT_NO_QWS_LINUXFB + if (driver == QLatin1String("linuxfb") || driver.isEmpty()) + return new QLinuxFbScreen(displayId); +#endif +#ifndef QT_NO_QWS_DIRECTFB + if (driver == QLatin1String("directfb") || driver.isEmpty()) + return new QDirectFBScreen(displayId); +#endif +#ifndef QT_NO_QWS_TRANSFORMED + if (driver == QLatin1String("transformed")) + return new QTransformedScreen(displayId); +#endif +#ifndef QT_NO_QWS_VNC + if (driver == QLatin1String("vnc")) + return new QVNCScreen(displayId); +#endif +#ifndef QT_NO_QWS_MULTISCREEN + if (driver == QLatin1String("multi")) + return new QMultiScreen(displayId); +#endif +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + + if (QScreenDriverFactoryInterface *factory = qobject_cast(loader()->instance(key))) + return factory->create(driver, displayId); + +#endif +#endif + return 0; +} + +/*! + Returns the list of valid keys, i.e. the available screen drivers. + + \sa create() +*/ +QStringList QScreenDriverFactory::keys() +{ + QStringList list; + +#if defined(Q_OS_QNX) && !defined(QT_NO_QWS_QNX) + list << QLatin1String("QNX"); +#endif +#if defined(Q_OS_INTEGRITY) && !defined(QT_NO_QWS_INTEGRITY) + list << QLatin1String("INTEGRITYFB"); +#endif +#ifndef QT_NO_QWS_QVFB + list << QLatin1String("QVFb"); +#endif +#ifndef QT_NO_QWS_LINUXFB + list << QLatin1String("LinuxFb"); +#endif +#ifndef QT_NO_QWS_TRANSFORMED + list << QLatin1String("Transformed"); +#endif +#ifndef QT_NO_QWS_VNC + list << QLatin1String("VNC"); +#endif +#ifndef QT_NO_QWS_MULTISCREEN + list << QLatin1String("Multi"); +#endif + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + QStringList plugins = loader()->keys(); + for (int i = 0; i < plugins.size(); ++i) { +# ifdef QT_NO_QWS_QVFB + // give QVFb top priority for autodetection + if (plugins.at(i) == QLatin1String("QVFb")) + list.prepend(plugins.at(i)); + else +# endif + if (!list.contains(plugins.at(i))) + list += plugins.at(i); + } +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + return list; +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qscreendriverfactory_qws.h b/src/gui/embedded/qscreendriverfactory_qws.h new file mode 100644 index 0000000000..072ae9bad8 --- /dev/null +++ b/src/gui/embedded/qscreendriverfactory_qws.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENDRIVERFACTORY_QWS_H +#define QSCREENDRIVERFACTORY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QScreen; + +class Q_GUI_EXPORT QScreenDriverFactory +{ +public: + static QStringList keys(); + static QScreen *create(const QString&, int); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENDRIVERFACTORY_QWS_H diff --git a/src/gui/embedded/qscreendriverplugin_qws.cpp b/src/gui/embedded/qscreendriverplugin_qws.cpp new file mode 100644 index 0000000000..d7822c8ffc --- /dev/null +++ b/src/gui/embedded/qscreendriverplugin_qws.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreendriverplugin_qws.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY + +/*! + \class QScreenDriverPlugin + \ingroup plugins + \ingroup qws + + \brief The QScreenDriverPlugin class is an abstract base class for + screen driver plugins in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + \l{Qt for Embedded Linux} provides ready-made drivers for several screen + protocols, see the \l{Qt for Embedded Linux Display Management}{display + management} documentation for details. Custom screen drivers can be + implemented by subclassing the QScreen class and creating a screen + driver plugin. + + A screen driver plugin can be created by subclassing + QScreenDriverPlugin and reimplementing the pure virtual keys() and + create() functions. By exporting the derived class using the + Q_EXPORT_PLUGIN2() macro, The default implementation of the + QScreenDriverFactory class will automatically detect the plugin + and load the driver into the server application at run-time. See + \l{How to Create Qt Plugins} for details. + + \sa QScreen, QScreenDriverFactory +*/ + +/*! + \fn QStringList QScreenDriverPlugin::keys() const + + Implement this function to return the list of valid keys, i.e. the + screen drivers supported by this plugin. + + \l{Qt for Embedded Linux} provides ready-made drivers for several screen + protocols, see the \l{Qt for Embedded Linux Display Management}{display + management} documentation for details. + + \sa create() +*/ + +/*! + Constructs a screen driver plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QScreenDriverPlugin::QScreenDriverPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys this screen driver plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QScreenDriverPlugin::~QScreenDriverPlugin() +{ +} + + +/*! + \fn QScreen* QScreenDriverPlugin::create(const QString &key, int displayId) + + Implement this function to create a driver matching the type + specified by the given \a key and \a displayId parameters. Note + that keys are case-insensitive. + + \sa keys() +*/ + +#endif // QT_NO_LIBRARY + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qscreendriverplugin_qws.h b/src/gui/embedded/qscreendriverplugin_qws.h new file mode 100644 index 0000000000..039a8aabf1 --- /dev/null +++ b/src/gui/embedded/qscreendriverplugin_qws.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENDRIVERPLUGIN_QWS_H +#define QSCREENDRIVERPLUGIN_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LIBRARY + +class QScreen; + +struct Q_GUI_EXPORT QScreenDriverFactoryInterface : public QFactoryInterface +{ + virtual QScreen* create(const QString& driver, int displayId) = 0; +}; + +#define QScreenDriverFactoryInterface_iid "com.trolltech.Qt.QScreenDriverFactoryInterface" +Q_DECLARE_INTERFACE(QScreenDriverFactoryInterface, QScreenDriverFactoryInterface_iid) + +class Q_GUI_EXPORT QScreenDriverPlugin : public QObject, public QScreenDriverFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QScreenDriverFactoryInterface:QFactoryInterface) +public: + explicit QScreenDriverPlugin(QObject *parent = 0); + ~QScreenDriverPlugin(); + + virtual QStringList keys() const = 0; + virtual QScreen *create(const QString& driver, int displayId) = 0; +}; + +#endif // QT_NO_LIBRARY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENDRIVERPLUGIN_QWS_H diff --git a/src/gui/embedded/qscreenintegrityfb_qws.cpp b/src/gui/embedded/qscreenintegrityfb_qws.cpp new file mode 100644 index 0000000000..7b24490353 --- /dev/null +++ b/src/gui/embedded/qscreenintegrityfb_qws.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QWS_INTEGRITYFB + +#include +#include +#include +#include +#include +#include "qmouseintegrity_qws.h" +#include "qkbdintegrity_qws.h" +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QIntfbScreenPrivate +{ +public: + QIntfbScreenPrivate(); + ~QIntfbScreenPrivate(); + + FBHandle handle; + struct FBInfoStruct fbinfo; + + QWSMouseHandler *mouse; +#ifndef QT_NO_QWS_KEYBOARD + QWSKeyboardHandler *keyboard; +#endif +}; + +QIntfbScreenPrivate::QIntfbScreenPrivate() + : mouse(0) + +{ +#ifndef QT_NO_QWS_KEYBOARD + keyboard = 0; +#endif +} + +QIntfbScreenPrivate::~QIntfbScreenPrivate() +{ + delete mouse; +#ifndef QT_NO_QWS_KEYBOARD + delete keyboard; +#endif +} + +/*! + \internal + + \class QIntfbScreen + \ingroup qws + + \brief The QIntfbScreen class implements a screen driver for the + INTEGRITY framebuffer drivers. + + Note that this class is only available in \l{Qt for INTEGRITY}. + Custom screen drivers can be added by subclassing the + QScreenDriverPlugin class, using the QScreenDriverFactory class to + dynamically load the driver into the application, but there should + only be one screen object per application. + + \sa QScreen, QScreenDriverPlugin, {Running Applications} +*/ + +/*! + \fn bool QIntfbScreen::connect(const QString & displaySpec) + \reimp +*/ + +/*! + \fn void QIntfbScreen::disconnect() + \reimp +*/ + +/*! + \fn bool QIntfbScreen::initDevice() + \reimp +*/ + +/*! + \fn void QIntfbScreen::restore() + \reimp +*/ + +/*! + \fn void QIntfbScreen::save() + \reimp +*/ + +/*! + \fn void QIntfbScreen::setDirty(const QRect & r) + \reimp +*/ + +/*! + \fn void QIntfbScreen::setMode(int nw, int nh, int nd) + \reimp +*/ + +/*! + \fn void QIntfbScreen::shutdownDevice() + \reimp +*/ + +/*! + \fn QIntfbScreen::QIntfbScreen(int displayId) + + Constructs a QVNCScreen object. The \a displayId argument + identifies the Qt for Embedded Linux server to connect to. +*/ +QIntfbScreen::QIntfbScreen(int display_id) + : QScreen(display_id, IntfbClass), d_ptr(new QIntfbScreenPrivate) +{ + d_ptr->handle = 0; + data = 0; +} + +/*! + Destroys this QIntfbScreen object. +*/ +QIntfbScreen::~QIntfbScreen() +{ + delete d_ptr; +} + +static QIntfbScreen *connected = 0; + +bool QIntfbScreen::connect(const QString &displaySpec) +{ + FBDriver *fbdev; + + CheckSuccess(gh_FB_get_driver(0, &fbdev)); + CheckSuccess(gh_FB_init_device(fbdev, 0, &d_ptr->handle)); + CheckSuccess(gh_FB_get_info(d_ptr->handle, &d_ptr->fbinfo)); + + data = (uchar *)d_ptr->fbinfo.start; + + d = d_ptr->fbinfo.bitsperpixel; + switch (d) { + case 1: + setPixelFormat(QImage::Format_Mono); + break; + case 8: + setPixelFormat(QImage::Format_Indexed8); + break; + case 12: + setPixelFormat(QImage::Format_RGB444); + break; + case 15: + setPixelFormat(QImage::Format_RGB555); + break; + case 16: + setPixelFormat(QImage::Format_RGB16); + break; + case 18: + setPixelFormat(QImage::Format_RGB666); + break; + case 24: + setPixelFormat(QImage::Format_RGB888); +#ifdef QT_QWS_DEPTH_GENERIC +#if Q_BYTE_ORDER != Q_BIG_ENDIAN + qt_set_generic_blit(this, 24, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + d_ptr->fbinfo.redoffset, + d_ptr->fbinfo.greenoffset, + d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset); +#else + qt_set_generic_blit(this, 24, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + 16 - d_ptr->fbinfo.redoffset, + 16 - d_ptr->fbinfo.greenoffset, + 16 - d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset); +#endif +#endif + break; + case 32: + setPixelFormat(QImage::Format_ARGB32_Premultiplied); +#ifdef QT_QWS_DEPTH_GENERIC +#if Q_BYTE_ORDER != Q_BIG_ENDIAN + qt_set_generic_blit(this, 32, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + d_ptr->fbinfo.redoffset, + d_ptr->fbinfo.greenoffset, + d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset); +#else + qt_set_generic_blit(this, 32, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + 24 - d_ptr->fbinfo.redoffset, + 24 - d_ptr->fbinfo.greenoffset, + 24 - d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset ? 24 - d_ptr->fbinfo.alphaoffset : 0); +#endif +#endif + break; + } + + dw = w = d_ptr->fbinfo.width; + dh = h = d_ptr->fbinfo.height; + + /* assumes no padding */ + lstep = w * ((d + 7) >> 3); + + mapsize = size = h * lstep; + + /* default values */ + int dpi = 72; + physWidth = qRound(dw * 25.4 / dpi); + physHeight = qRound(dh * 25.4 / dpi); + + qDebug("Connected to INTEGRITYfb server: %d x %d x %d %dx%dmm (%dx%ddpi)", + w, h, d, physWidth, physHeight, qRound(dw*25.4/physWidth), qRound(dh*25.4/physHeight) ); + + + QWSServer::setDefaultMouse("integrity"); + QWSServer::setDefaultKeyboard("integrity"); + + connected = this; + + return true; +} + +void QIntfbScreen::disconnect() +{ + connected = 0; +} + +bool QIntfbScreen::initDevice() +{ + + CheckSuccess(gh_FB_set_info(d_ptr->handle, &d_ptr->fbinfo, false)); + CheckSuccess(gh_FB_get_info(d_ptr->handle, &d_ptr->fbinfo)); + data = (uchar *)d_ptr->fbinfo.start; + d = d_ptr->fbinfo.bitsperpixel; + dw = w = d_ptr->fbinfo.width; + dh = h = d_ptr->fbinfo.height; + mapsize = d_ptr->fbinfo.length; + /* assumes no padding */ + lstep = w * ((d + 7) >> 3); + + mapsize = size = h * lstep; + + data = (uchar *)d_ptr->fbinfo.start; + + d = d_ptr->fbinfo.bitsperpixel; + switch (d) { + case 1: + setPixelFormat(QImage::Format_Mono); + break; + case 8: + setPixelFormat(QImage::Format_Indexed8); + break; + case 12: + setPixelFormat(QImage::Format_RGB444); + break; + case 15: + setPixelFormat(QImage::Format_RGB555); + break; + case 16: + setPixelFormat(QImage::Format_RGB16); + break; + case 18: + setPixelFormat(QImage::Format_RGB666); + break; + case 24: + setPixelFormat(QImage::Format_RGB888); + break; + case 32: + setPixelFormat(QImage::Format_ARGB32_Premultiplied); + break; + } +#ifdef QT_QWS_DEPTH_GENERIC +#if defined(__BIG_ENDIAN__) + qt_set_generic_blit(this, d, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + 24 - d_ptr->fbinfo.redoffset, + 24 - d_ptr->fbinfo.greenoffset, + 24 - d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset ? 24 - d_ptr->fbinfo.alphaoffset : 0); +#else + qt_set_generic_blit(this, d, + d_ptr->fbinfo.redbits, + d_ptr->fbinfo.greenbits, + d_ptr->fbinfo.bluebits, + d_ptr->fbinfo.alphabits, + d_ptr->fbinfo.redoffset, + d_ptr->fbinfo.greenoffset, + d_ptr->fbinfo.blueoffset, + d_ptr->fbinfo.alphaoffset); +#endif +#endif + +#ifndef QT_NO_QWS_CURSOR + QScreenCursor::initSoftwareCursor(); +#endif + return true; +} + +void QIntfbScreen::shutdownDevice() +{ + gh_FB_close(d_ptr->handle); +} + +void QIntfbScreen::setMode(int ,int ,int) +{ +} + +// save the state of the graphics card +// This is needed so that e.g. we can restore the palette when switching +// between linux virtual consoles. +void QIntfbScreen::save() +{ + // nothing to do. +} + +// restore the state of the graphics card. +void QIntfbScreen::restore() +{ +} +void QIntfbScreen::setDirty(const QRect& rect) +{ + FBRect fbrect; + fbrect.dx = rect.x(); + fbrect.dy = rect.y(); + fbrect.width = rect.width(); + fbrect.height = rect.height(); + gh_FB_expose(d_ptr->handle, &fbrect); +} + +void QIntfbScreen::setBrightness(int b) +{ + if (connected) { + } +} + +void QIntfbScreen::blank(bool on) +{ +} + +#endif // QT_NO_QWS_INTEGRITYFB + +QT_END_NAMESPACE + diff --git a/src/gui/embedded/qscreenintegrityfb_qws.h b/src/gui/embedded/qscreenintegrityfb_qws.h new file mode 100644 index 0000000000..06b39671f4 --- /dev/null +++ b/src/gui/embedded/qscreenintegrityfb_qws.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENINTEGRITYFB_QWS_H +#define QSCREENINTEGRITYFB_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_INTEGRITYFB + +class QIntfbScreenPrivate; + +class Q_GUI_EXPORT QIntfbScreen : public QScreen +{ +public: + explicit QIntfbScreen(int display_id); + virtual ~QIntfbScreen(); + virtual bool initDevice(); + virtual bool connect(const QString &displaySpec); + virtual void disconnect(); + virtual void shutdownDevice(); + virtual void save(); + virtual void restore(); + virtual void setMode(int nw,int nh,int nd); + virtual void setDirty(const QRect& r); + virtual void blank(bool); + static void setBrightness(int b); + +private: + QIntfbScreenPrivate *d_ptr; +}; + +#endif // QT_NO_QWS_INTEGRITYFB + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENINTEGRITYFB_QWS_H diff --git a/src/gui/embedded/qscreenlinuxfb_qws.cpp b/src/gui/embedded/qscreenlinuxfb_qws.cpp new file mode 100644 index 0000000000..a566f5294c --- /dev/null +++ b/src/gui/embedded/qscreenlinuxfb_qws.cpp @@ -0,0 +1,1386 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreenlinuxfb_qws.h" + +#ifndef QT_NO_QWS_LINUXFB +//#include "qmemorymanager_qws.h" +#include "qwsdisplay_qws.h" +#include "qpixmap.h" +#include +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qwindowsystem_qws.h" + +#if !defined(Q_OS_DARWIN) && !defined(Q_OS_FREEBSD) +#include + +#ifdef __i386__ +#include +#endif +#endif + +QT_BEGIN_NAMESPACE + +extern int qws_client_id; + +//#define DEBUG_CACHE + +class QLinuxFbScreenPrivate : public QObject +{ +public: + QLinuxFbScreenPrivate(); + ~QLinuxFbScreenPrivate(); + + void openTty(); + void closeTty(); + + int fd; + int startupw; + int startuph; + int startupd; + bool blank; + QLinuxFbScreen::DriverTypes driverType; + + bool doGraphicsMode; +#ifdef QT_QWS_DEPTH_GENERIC + bool doGenericColors; +#endif + int ttyfd; + long oldKdMode; + QString ttyDevice; + QString displaySpec; +}; + +QLinuxFbScreenPrivate::QLinuxFbScreenPrivate() + : fd(-1), blank(true), doGraphicsMode(true), +#ifdef QT_QWS_DEPTH_GENERIC + doGenericColors(false), +#endif + ttyfd(-1), oldKdMode(KD_TEXT) +{ + QWSSignalHandler::instance()->addObject(this); +} + +QLinuxFbScreenPrivate::~QLinuxFbScreenPrivate() +{ + closeTty(); +} + +void QLinuxFbScreenPrivate::openTty() +{ + const char *const devs[] = {"/dev/tty0", "/dev/tty", "/dev/console", 0}; + + if (ttyDevice.isEmpty()) { + for (const char * const *dev = devs; *dev; ++dev) { + ttyfd = QT_OPEN(*dev, O_RDWR); + if (ttyfd != -1) + break; + } + } else { + ttyfd = QT_OPEN(ttyDevice.toAscii().constData(), O_RDWR); + } + + if (ttyfd == -1) + return; + + if (doGraphicsMode) { + ioctl(ttyfd, KDGETMODE, &oldKdMode); + if (oldKdMode != KD_GRAPHICS) { + int ret = ioctl(ttyfd, KDSETMODE, KD_GRAPHICS); + if (ret == -1) + doGraphicsMode = false; + } + } + + // No blankin' screen, no blinkin' cursor!, no cursor! + const char termctl[] = "\033[9;0]\033[?33l\033[?25l\033[?1c"; + QT_WRITE(ttyfd, termctl, sizeof(termctl)); +} + +void QLinuxFbScreenPrivate::closeTty() +{ + if (ttyfd == -1) + return; + + if (doGraphicsMode) + ioctl(ttyfd, KDSETMODE, oldKdMode); + + // Blankin' screen, blinkin' cursor! + const char termctl[] = "\033[9;15]\033[?33h\033[?25h\033[?0c"; + QT_WRITE(ttyfd, termctl, sizeof(termctl)); + + QT_CLOSE(ttyfd); + ttyfd = -1; +} + +/*! + \enum QLinuxFbScreen::DriverTypes + + This enum describes the driver type. + + \value GenericDriver Generic Linux framebuffer driver + \value EInk8Track e-Ink framebuffer driver using the 8Track chipset + */ + +/*! + \fn QLinuxFbScreen::fixupScreenInfo(fb_fix_screeninfo &finfo, fb_var_screeninfo &vinfo) + + Adjust the values returned by the framebuffer driver, to work + around driver bugs or nonstandard behavior in certain drivers. + \a finfo and \a vinfo specify the fixed and variable screen info + returned by the driver. + */ +void QLinuxFbScreen::fixupScreenInfo(fb_fix_screeninfo &finfo, fb_var_screeninfo &vinfo) +{ + // 8Track e-ink devices (as found in Sony PRS-505) lie + // about their bit depth -- they claim they're 1 bit per + // pixel while the only supported mode is 8 bit per pixel + // grayscale. + // Caused by this, they also miscalculate their line length. + if(!strcmp(finfo.id, "8TRACKFB") && vinfo.bits_per_pixel == 1) { + vinfo.bits_per_pixel = 8; + finfo.line_length = vinfo.xres; + } +} + +/*! + \internal + + \class QLinuxFbScreen + \ingroup qws + + \brief The QLinuxFbScreen class implements a screen driver for the + Linux framebuffer. + + Note that this class is only available in \l{Qt for Embedded Linux}. + Custom screen drivers can be added by subclassing the + QScreenDriverPlugin class, using the QScreenDriverFactory class to + dynamically load the driver into the application, but there should + only be one screen object per application. + + The QLinuxFbScreen class provides the cache() function allocating + off-screen graphics memory, and the complementary uncache() + function releasing the allocated memory. The latter function will + first sync the graphics card to ensure the memory isn't still + being used by a command in the graphics card FIFO queue. The + deleteEntry() function deletes the given memory block without such + synchronization. Given the screen instance and client id, the + memory can also be released using the clearCache() function, but + this should only be necessary if a client exits abnormally. + + In addition, when in paletted graphics modes, the set() function + provides the possibility of setting a specified color index to a + given RGB value. + + The QLinuxFbScreen class also acts as a factory for the + unaccelerated screen cursor and the unaccelerated raster-based + implementation of QPaintEngine (\c QRasterPaintEngine); + accelerated drivers for Linux should derive from this class. + + \sa QScreen, QScreenDriverPlugin, {Running Applications} +*/ + +/*! + \fn bool QLinuxFbScreen::useOffscreen() + \internal +*/ + +// Unaccelerated screen/driver setup. Can be overridden by accelerated +// drivers + +/*! + \fn QLinuxFbScreen::QLinuxFbScreen(int displayId) + + Constructs a QLinuxFbScreen object. The \a displayId argument + identifies the Qt for Embedded Linux server to connect to. +*/ + +QLinuxFbScreen::QLinuxFbScreen(int display_id) + : QScreen(display_id, LinuxFBClass), d_ptr(new QLinuxFbScreenPrivate) +{ + canaccel=false; + clearCacheFunc = &clearCache; +#ifdef QT_QWS_CLIENTBLIT + setSupportsBlitInClients(true); +#endif +} + +/*! + Destroys this QLinuxFbScreen object. +*/ + +QLinuxFbScreen::~QLinuxFbScreen() +{ +} + +/*! + \reimp + + This is called by \l{Qt for Embedded Linux} clients to map in the framebuffer. + It should be reimplemented by accelerated drivers to map in + graphics card registers; those drivers should then call this + function in order to set up offscreen memory management. The + device is specified in \a displaySpec; e.g. "/dev/fb". + + \sa disconnect() +*/ + +bool QLinuxFbScreen::connect(const QString &displaySpec) +{ + d_ptr->displaySpec = displaySpec; + + const QStringList args = displaySpec.split(QLatin1Char(':')); + + if (args.contains(QLatin1String("nographicsmodeswitch"))) + d_ptr->doGraphicsMode = false; + +#ifdef QT_QWS_DEPTH_GENERIC + if (args.contains(QLatin1String("genericcolors"))) + d_ptr->doGenericColors = true; +#endif + + QRegExp ttyRegExp(QLatin1String("tty=(.*)")); + if (args.indexOf(ttyRegExp) != -1) + d_ptr->ttyDevice = ttyRegExp.cap(1); + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +#ifndef QT_QWS_FRAMEBUFFER_LITTLE_ENDIAN + if (args.contains(QLatin1String("littleendian"))) +#endif + QScreen::setFrameBufferLittleEndian(true); +#endif + + QString dev = QLatin1String("/dev/fb0"); + foreach(QString d, args) { + if (d.startsWith(QLatin1Char('/'))) { + dev = d; + break; + } + } + + if (access(dev.toLatin1().constData(), R_OK|W_OK) == 0) + d_ptr->fd = QT_OPEN(dev.toLatin1().constData(), O_RDWR); + if (d_ptr->fd == -1) { + if (QApplication::type() == QApplication::GuiServer) { + perror("QScreenLinuxFb::connect"); + qCritical("Error opening framebuffer device %s", qPrintable(dev)); + return false; + } + if (access(dev.toLatin1().constData(), R_OK) == 0) + d_ptr->fd = QT_OPEN(dev.toLatin1().constData(), O_RDONLY); + } + + ::fb_fix_screeninfo finfo; + ::fb_var_screeninfo vinfo; + //####################### + // Shut up Valgrind + memset(&vinfo, 0, sizeof(vinfo)); + memset(&finfo, 0, sizeof(finfo)); + //####################### + + /* Get fixed screen information */ + if (d_ptr->fd != -1 && ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) { + perror("QLinuxFbScreen::connect"); + qWarning("Error reading fixed information"); + return false; + } + + d_ptr->driverType = strcmp(finfo.id, "8TRACKFB") ? GenericDriver : EInk8Track; + + if (finfo.type == FB_TYPE_VGA_PLANES) { + qWarning("VGA16 video mode not supported"); + return false; + } + + /* Get variable screen information */ + if (d_ptr->fd != -1 && ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) { + perror("QLinuxFbScreen::connect"); + qWarning("Error reading variable information"); + return false; + } + + fixupScreenInfo(finfo, vinfo); + + grayscale = vinfo.grayscale; + d = vinfo.bits_per_pixel; + if (d == 24) { + d = vinfo.red.length + vinfo.green.length + vinfo.blue.length; + if (d <= 0) + d = 24; // reset if color component lengths are not reported + } else if (d == 16) { + d = vinfo.red.length + vinfo.green.length + vinfo.blue.length; + if (d <= 0) + d = 16; + } + lstep = finfo.line_length; + + int xoff = vinfo.xoffset; + int yoff = vinfo.yoffset; + const char* qwssize; + if((qwssize=::getenv("QWS_SIZE")) && sscanf(qwssize,"%dx%d",&w,&h)==2) { + if (d_ptr->fd != -1) { + if ((uint)w > vinfo.xres) w = vinfo.xres; + if ((uint)h > vinfo.yres) h = vinfo.yres; + } + dw=w; + dh=h; + int xxoff, yyoff; + if (sscanf(qwssize, "%*dx%*d+%d+%d", &xxoff, &yyoff) == 2) { + if (xxoff < 0 || xxoff + w > vinfo.xres) + xxoff = vinfo.xres - w; + if (yyoff < 0 || yyoff + h > vinfo.yres) + yyoff = vinfo.yres - h; + xoff += xxoff; + yoff += yyoff; + } else { + xoff += (vinfo.xres - w)/2; + yoff += (vinfo.yres - h)/2; + } + } else { + dw=w=vinfo.xres; + dh=h=vinfo.yres; + } + + if (w == 0 || h == 0) { + qWarning("QScreenLinuxFb::connect(): Unable to find screen geometry, " + "will use 320x240."); + dw = w = 320; + dh = h = 240; + } + + setPixelFormat(vinfo); + + // Handle display physical size spec. + QStringList displayArgs = displaySpec.split(QLatin1Char(':')); + QRegExp mmWidthRx(QLatin1String("mmWidth=?(\\d+)")); + int dimIdxW = displayArgs.indexOf(mmWidthRx); + QRegExp mmHeightRx(QLatin1String("mmHeight=?(\\d+)")); + int dimIdxH = displayArgs.indexOf(mmHeightRx); + if (dimIdxW >= 0) { + mmWidthRx.exactMatch(displayArgs.at(dimIdxW)); + physWidth = mmWidthRx.cap(1).toInt(); + if (dimIdxH < 0) + physHeight = dh*physWidth/dw; + } + if (dimIdxH >= 0) { + mmHeightRx.exactMatch(displayArgs.at(dimIdxH)); + physHeight = mmHeightRx.cap(1).toInt(); + if (dimIdxW < 0) + physWidth = dw*physHeight/dh; + } + if (dimIdxW < 0 && dimIdxH < 0) { + if (vinfo.width != 0 && vinfo.height != 0 + && vinfo.width != UINT_MAX && vinfo.height != UINT_MAX) { + physWidth = vinfo.width; + physHeight = vinfo.height; + } else { + const int dpi = 72; + physWidth = qRound(dw * 25.4 / dpi); + physHeight = qRound(dh * 25.4 / dpi); + } + } + + dataoffset = yoff * lstep + xoff * d / 8; + //qDebug("Using %dx%dx%d screen",w,h,d); + + /* Figure out the size of the screen in bytes */ + size = h * lstep; + + mapsize = finfo.smem_len; + + data = (unsigned char *)-1; + if (d_ptr->fd != -1) + data = (unsigned char *)mmap(0, mapsize, PROT_READ | PROT_WRITE, + MAP_SHARED, d_ptr->fd, 0); + + if ((long)data == -1) { + if (QApplication::type() == QApplication::GuiServer) { + perror("QLinuxFbScreen::connect"); + qWarning("Error: failed to map framebuffer device to memory."); + return false; + } + data = 0; + } else { + data += dataoffset; + } + + canaccel = useOffscreen(); + if(canaccel) + setupOffScreen(); + + // Now read in palette + if((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4)) { + screencols= (vinfo.bits_per_pixel==8) ? 256 : 16; + int loopc; + ::fb_cmap startcmap; + startcmap.start=0; + startcmap.len=screencols; + startcmap.red=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + startcmap.green=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + startcmap.blue=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + startcmap.transp=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + if (d_ptr->fd == -1 || ioctl(d_ptr->fd, FBIOGETCMAP, &startcmap)) { + perror("QLinuxFbScreen::connect"); + qWarning("Error reading palette from framebuffer, using default palette"); + createPalette(startcmap, vinfo, finfo); + } + int bits_used = 0; + for(loopc=0;loopc> 8, + startcmap.green[loopc] >> 8, + startcmap.blue[loopc] >> 8); + bits_used |= startcmap.red[loopc] + | startcmap.green[loopc] + | startcmap.blue[loopc]; + } + // WORKAROUND: Some framebuffer drivers only return 8 bit + // color values, so we need to not bit shift them.. + if ((bits_used & 0x00ff) && !(bits_used & 0xff00)) { + for(loopc=0;loopcfd); +} + +// #define DEBUG_VINFO + +void QLinuxFbScreen::createPalette(fb_cmap &cmap, fb_var_screeninfo &vinfo, fb_fix_screeninfo &finfo) +{ + if((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4)) { + screencols= (vinfo.bits_per_pixel==8) ? 256 : 16; + cmap.start=0; + cmap.len=screencols; + cmap.red=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + cmap.green=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + cmap.blue=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + cmap.transp=(unsigned short int *) + malloc(sizeof(unsigned short int)*screencols); + + if (screencols==16) { + if (finfo.type == FB_TYPE_PACKED_PIXELS) { + // We'll setup a grayscale cmap for 4bpp linear + int val = 0; + for (int idx = 0; idx < 16; ++idx, val += 17) { + cmap.red[idx] = (val<<8)|val; + cmap.green[idx] = (val<<8)|val; + cmap.blue[idx] = (val<<8)|val; + screenclut[idx]=qRgb(val, val, val); + } + } else { + // Default 16 colour palette + // Green is now trolltech green so certain images look nicer + // black d_gray l_gray white red green blue cyan magenta yellow + unsigned char reds[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0xFF, 0xA2, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0x7F, 0x00, 0x00, 0x00, 0x82 }; + unsigned char greens[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0x00, 0xC5, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F }; + unsigned char blues[16] = { 0x00, 0x7F, 0xBF, 0xFF, 0x00, 0x11, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x7F, 0x7F, 0x7F, 0x00, 0x00 }; + + for (int idx = 0; idx < 16; ++idx) { + cmap.red[idx] = ((reds[idx]) << 8)|reds[idx]; + cmap.green[idx] = ((greens[idx]) << 8)|greens[idx]; + cmap.blue[idx] = ((blues[idx]) << 8)|blues[idx]; + cmap.transp[idx] = 0; + screenclut[idx]=qRgb(reds[idx], greens[idx], blues[idx]); + } + } + } else { + if (grayscale) { + // Build grayscale palette + int i; + for(i=0;iopenTty(); + + // Grab current mode so we can reset it + fb_var_screeninfo vinfo; + fb_fix_screeninfo finfo; + //####################### + // Shut up Valgrind + memset(&vinfo, 0, sizeof(vinfo)); + memset(&finfo, 0, sizeof(finfo)); + //####################### + + if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) { + perror("QLinuxFbScreen::initDevice"); + qFatal("Error reading variable information in card init"); + return false; + } + +#ifdef DEBUG_VINFO + qDebug("Greyscale %d",vinfo.grayscale); + qDebug("Nonstd %d",vinfo.nonstd); + qDebug("Red %d %d %d",vinfo.red.offset,vinfo.red.length, + vinfo.red.msb_right); + qDebug("Green %d %d %d",vinfo.green.offset,vinfo.green.length, + vinfo.green.msb_right); + qDebug("Blue %d %d %d",vinfo.blue.offset,vinfo.blue.length, + vinfo.blue.msb_right); + qDebug("Transparent %d %d %d",vinfo.transp.offset,vinfo.transp.length, + vinfo.transp.msb_right); +#endif + + if (ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) { + perror("QLinuxFbScreen::initDevice"); + qCritical("Error reading fixed information in card init"); + // It's not an /error/ as such, though definitely a bad sign + // so we return true + return true; + } + + fixupScreenInfo(finfo, vinfo); + + d_ptr->startupw=vinfo.xres; + d_ptr->startuph=vinfo.yres; + d_ptr->startupd=vinfo.bits_per_pixel; + grayscale = vinfo.grayscale; + +#ifdef __i386__ + // Now init mtrr + if(!::getenv("QWS_NOMTRR")) { + int mfd=QT_OPEN("/proc/mtrr",O_WRONLY,0); + // MTRR entry goes away when file is closed - i.e. + // hopefully when QWS is killed + if(mfd != -1) { + mtrr_sentry sentry; + sentry.base=(unsigned long int)finfo.smem_start; + //qDebug("Physical framebuffer address %p",(void*)finfo.smem_start); + // Size needs to be in 4k chunks, but that's not always + // what we get thanks to graphics card registers. Write combining + // these is Not Good, so we write combine what we can + // (which is not much - 4 megs on an 8 meg card, it seems) + unsigned int size=finfo.smem_len; + size=size >> 22; + size=size << 22; + sentry.size=size; + sentry.type=MTRR_TYPE_WRCOMB; + if(ioctl(mfd,MTRRIOC_ADD_ENTRY,&sentry)==-1) { + //printf("Couldn't add mtrr entry for %lx %lx, %s\n", + //sentry.base,sentry.size,strerror(errno)); + } + } + + // Should we close mfd here? + //QT_CLOSE(mfd); + } +#endif + if ((vinfo.bits_per_pixel==8) || (vinfo.bits_per_pixel==4) || (finfo.visual==FB_VISUAL_DIRECTCOLOR)) + { + fb_cmap cmap; + createPalette(cmap, vinfo, finfo); + if (ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap)) { + perror("QLinuxFbScreen::initDevice"); + qWarning("Error writing palette to framebuffer"); + } + free(cmap.red); + free(cmap.green); + free(cmap.blue); + free(cmap.transp); + } + + if (canaccel) { + *entryp=0; + *lowest = mapsize; + insert_entry(*entryp, *lowest, *lowest); // dummy entry to mark start + } + + shared->fifocount = 0; + shared->buffer_offset = 0xffffffff; // 0 would be a sensible offset (screen) + shared->linestep = 0; + shared->cliptop = 0xffffffff; + shared->clipleft = 0xffffffff; + shared->clipright = 0xffffffff; + shared->clipbottom = 0xffffffff; + shared->rop = 0xffffffff; + +#ifdef QT_QWS_DEPTH_GENERIC + if (pixelFormat() == QImage::Format_Invalid && screencols == 0 + && d_ptr->doGenericColors) + { + qt_set_generic_blit(this, vinfo.bits_per_pixel, + vinfo.red.length, vinfo.green.length, + vinfo.blue.length, vinfo.transp.length, + vinfo.red.offset, vinfo.green.offset, + vinfo.blue.offset, vinfo.transp.offset); + } +#endif + +#ifndef QT_NO_QWS_CURSOR + QScreenCursor::initSoftwareCursor(); +#endif + blank(false); + + return true; +} + +/* + The offscreen memory manager's list of entries is stored at the bottom + of the offscreen memory area and consistes of a series of QPoolEntry's, + each of which keep track of a block of allocated memory. Unallocated memory + is implicitly indicated by the gap between blocks indicated by QPoolEntry's. + The memory manager looks through any unallocated memory before the end + of currently-allocated memory to see if a new block will fit in the gap; + if it doesn't it allocated it from the end of currently-allocated memory. + Memory is allocated from the top of the framebuffer downwards; if it hits + the list of entries then offscreen memory is full and further allocations + are made from main RAM (and hence unaccelerated). Allocated memory can + be seen as a sort of upside-down stack; lowest keeps track of the + bottom of the stack. +*/ + +void QLinuxFbScreen::delete_entry(int pos) +{ + if (pos > *entryp || pos < 0) { + qWarning("Attempt to delete odd pos! %d %d", pos, *entryp); + return; + } + +#ifdef DEBUG_CACHE + qDebug("Remove entry: %d", pos); +#endif + + QPoolEntry *qpe = &entries[pos]; + if (qpe->start <= *lowest) { + // Lowest goes up again + *lowest = entries[pos-1].start; +#ifdef DEBUG_CACHE + qDebug(" moved lowest to %d", *lowest); +#endif + } + + (*entryp)--; + if (pos == *entryp) + return; + + int size = (*entryp)-pos; + memmove(&entries[pos], &entries[pos+1], size*sizeof(QPoolEntry)); +} + +void QLinuxFbScreen::insert_entry(int pos, int start, int end) +{ + if (pos > *entryp) { + qWarning("Attempt to insert odd pos! %d %d",pos,*entryp); + return; + } + +#ifdef DEBUG_CACHE + qDebug("Insert entry: %d, %d -> %d", pos, start, end); +#endif + + if (start < (int)*lowest) { + *lowest = start; +#ifdef DEBUG_CACHE + qDebug(" moved lowest to %d", *lowest); +#endif + } + + if (pos == *entryp) { + entries[pos].start = start; + entries[pos].end = end; + entries[pos].clientId = qws_client_id; + (*entryp)++; + return; + } + + int size=(*entryp)-pos; + memmove(&entries[pos+1],&entries[pos],size*sizeof(QPoolEntry)); + entries[pos].start=start; + entries[pos].end=end; + entries[pos].clientId=qws_client_id; + (*entryp)++; +} + +/*! + \fn uchar * QLinuxFbScreen::cache(int amount) + + Requests the specified \a amount of offscreen graphics card memory + from the memory manager, and returns a pointer to the data within + the framebuffer (or 0 if there is no free memory). + + Note that the display is locked while memory is allocated in order to + preserve the memory pool's integrity. + + Use the QScreen::onCard() function to retrieve an offset (in + bytes) from the start of graphics card memory for the returned + pointer. + + \sa uncache(), clearCache(), deleteEntry() +*/ + +uchar * QLinuxFbScreen::cache(int amount) +{ + if (!canaccel || entryp == 0) + return 0; + + qt_fbdpy->grab(); + + int startp = cacheStart + (*entryp+1) * sizeof(QPoolEntry); + if (startp >= (int)*lowest) { + // We don't have room for another cache QPoolEntry. +#ifdef DEBUG_CACHE + qDebug("No room for pool entry in VRAM"); +#endif + qt_fbdpy->ungrab(); + return 0; + } + + int align = pixmapOffsetAlignment(); + + if (*entryp > 1) { + // Try to find a gap in the allocated blocks. + for (int loopc = 0; loopc < *entryp-1; loopc++) { + int freestart = entries[loopc+1].end; + int freeend = entries[loopc].start; + if (freestart != freeend) { + while (freestart % align) { + freestart++; + } + int len=freeend-freestart; + if (len >= amount) { + insert_entry(loopc+1, freestart, freestart+amount); + qt_fbdpy->ungrab(); + return data+freestart; + } + } + } + } + + // No free blocks in already-taken memory; get some more + // if we can + int newlowest = (*lowest)-amount; + if (newlowest % align) { + newlowest -= align; + while (newlowest % align) { + newlowest++; + } + } + if (startp >= newlowest) { + qt_fbdpy->ungrab(); +#ifdef DEBUG_CACHE + qDebug("No VRAM available for %d bytes", amount); +#endif + return 0; + } + insert_entry(*entryp, newlowest, *lowest); + qt_fbdpy->ungrab(); + + return data + newlowest; +} + +/*! + \fn void QLinuxFbScreen::uncache(uchar * memoryBlock) + + Deletes the specified \a memoryBlock allocated from the graphics + card memory. + + Note that the display is locked while memory is unallocated in + order to preserve the memory pool's integrity. + + This function will first sync the graphics card to ensure the + memory isn't still being used by a command in the graphics card + FIFO queue. It is possible to speed up a driver by overriding this + function to avoid syncing. For example, the driver might delay + deleting the memory until it detects that all commands dealing + with the memory are no longer in the queue. Note that it will then + be up to the driver to ensure that the specified \a memoryBlock no + longer is being used. + + \sa cache(), deleteEntry(), clearCache() + */ +void QLinuxFbScreen::uncache(uchar * c) +{ + // need to sync graphics card + + deleteEntry(c); +} + +/*! + \fn void QLinuxFbScreen::deleteEntry(uchar * memoryBlock) + + Deletes the specified \a memoryBlock allocated from the graphics + card memory. + + \sa uncache(), cache(), clearCache() +*/ +void QLinuxFbScreen::deleteEntry(uchar * c) +{ + qt_fbdpy->grab(); + unsigned long pos=(unsigned long)c; + pos-=((unsigned long)data); + unsigned int hold=(*entryp); + for(unsigned int loopc=1;loopcungrab(); + return; + } + } + qt_fbdpy->ungrab(); + qWarning("Attempt to delete unknown offset %ld",pos); +} + +/*! + Removes all entries from the cache for the specified screen \a + instance and client identified by the given \a clientId. + + Calling this function should only be necessary if a client exits + abnormally. + + \sa cache(), uncache(), deleteEntry() +*/ +void QLinuxFbScreen::clearCache(QScreen *instance, int clientId) +{ + QLinuxFbScreen *screen = (QLinuxFbScreen *)instance; + if (!screen->canaccel || !screen->entryp) + return; + qt_fbdpy->grab(); + for (int loopc = 0; loopc < *(screen->entryp); loopc++) { + if (screen->entries[loopc].clientId == clientId) { + screen->delete_entry(loopc); + loopc--; + } + } + qt_fbdpy->ungrab(); +} + + +void QLinuxFbScreen::setupOffScreen() +{ + // Figure out position of offscreen memory + // Set up pool entries pointer table and 64-bit align it + int psize = size; + + // hw: this causes the limitation of cursors to 64x64 + // the cursor should rather use the normal pixmap mechanism + psize += 4096; // cursor data + psize += 8; // for alignment + psize &= ~0x7; // align + + unsigned long pos = (unsigned long)data; + pos += psize; + entryp = ((int *)pos); + lowest = ((unsigned int *)pos)+1; + pos += (sizeof(int))*4; + entries = (QPoolEntry *)pos; + + // beginning of offscreen memory available for pixmaps. + cacheStart = psize + 4*sizeof(int) + sizeof(QPoolEntry); +} + +/*! + \reimp + + This is called by the \l{Qt for Embedded Linux} server when it shuts + down, and should be inherited if you need to do any card-specific cleanup. + The default version hides the screen cursor and reenables the blinking + cursor and screen blanking. +*/ + +void QLinuxFbScreen::shutdownDevice() +{ + // Causing crashes. Not needed. + //setMode(startupw,startuph,startupd); +/* + if (startupd == 8) { + ioctl(fd,FBIOPUTCMAP,startcmap); + free(startcmap->red); + free(startcmap->green); + free(startcmap->blue); + free(startcmap->transp); + delete startcmap; + startcmap = 0; + } +*/ + d_ptr->closeTty(); +} + +/*! + \fn void QLinuxFbScreen::set(unsigned int index,unsigned int red,unsigned int green,unsigned int blue) + + Sets the specified color \a index to the specified RGB value, (\a + red, \a green, \a blue), when in paletted graphics modes. +*/ + +void QLinuxFbScreen::set(unsigned int i,unsigned int r,unsigned int g,unsigned int b) +{ + if (d_ptr->fd != -1) { + fb_cmap cmap; + cmap.start=i; + cmap.len=1; + cmap.red=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.green=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.blue=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.transp=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.red[0]=r << 8; + cmap.green[0]=g << 8; + cmap.blue[0]=b << 8; + cmap.transp[0]=0; + ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap); + free(cmap.red); + free(cmap.green); + free(cmap.blue); + free(cmap.transp); + } + screenclut[i] = qRgb(r, g, b); +} + +/*! + \reimp + + Sets the framebuffer to a new resolution and bit depth. The width is + in \a nw, the height is in \a nh, and the depth is in \a nd. After + doing this any currently-existing paint engines will be invalid and the + screen should be completely redrawn. In a multiple-process + Embedded Qt situation you must signal all other applications to + call setMode() to the same mode and redraw. +*/ + +void QLinuxFbScreen::setMode(int nw,int nh,int nd) +{ + if (d_ptr->fd == -1) + return; + + fb_fix_screeninfo finfo; + fb_var_screeninfo vinfo; + //####################### + // Shut up Valgrind + memset(&vinfo, 0, sizeof(vinfo)); + memset(&finfo, 0, sizeof(finfo)); + //####################### + + if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) { + perror("QLinuxFbScreen::setMode"); + qFatal("Error reading variable information in mode change"); + } + + vinfo.xres=nw; + vinfo.yres=nh; + vinfo.bits_per_pixel=nd; + + if (ioctl(d_ptr->fd, FBIOPUT_VSCREENINFO, &vinfo)) { + perror("QLinuxFbScreen::setMode"); + qCritical("Error writing variable information in mode change"); + } + + if (ioctl(d_ptr->fd, FBIOGET_VSCREENINFO, &vinfo)) { + perror("QLinuxFbScreen::setMode"); + qFatal("Error reading changed variable information in mode change"); + } + + if (ioctl(d_ptr->fd, FBIOGET_FSCREENINFO, &finfo)) { + perror("QLinuxFbScreen::setMode"); + qFatal("Error reading fixed information"); + } + + fixupScreenInfo(finfo, vinfo); + disconnect(); + connect(d_ptr->displaySpec); + exposeRegion(region(), 0); +} + +// save the state of the graphics card +// This is needed so that e.g. we can restore the palette when switching +// between linux virtual consoles. + +/*! + \reimp + + This doesn't do anything; accelerated drivers may wish to reimplement + it to save graphics cards registers. It's called by the + \l{Qt for Embedded Linux} server when the virtual console is switched. +*/ + +void QLinuxFbScreen::save() +{ + // nothing to do. +} + + +// restore the state of the graphics card. +/*! + \reimp + + This is called when the virtual console is switched back to + \l{Qt for Embedded Linux} and restores the palette. +*/ +void QLinuxFbScreen::restore() +{ + if (d_ptr->fd == -1) + return; + + if ((d == 8) || (d == 4)) { + fb_cmap cmap; + cmap.start=0; + cmap.len=screencols; + cmap.red=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.green=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.blue=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + cmap.transp=(unsigned short int *) + malloc(sizeof(unsigned short int)*256); + for (int loopc = 0; loopc < screencols; loopc++) { + cmap.red[loopc] = qRed(screenclut[loopc]) << 8; + cmap.green[loopc] = qGreen(screenclut[loopc]) << 8; + cmap.blue[loopc] = qBlue(screenclut[loopc]) << 8; + cmap.transp[loopc] = 0; + } + ioctl(d_ptr->fd, FBIOPUTCMAP, &cmap); + free(cmap.red); + free(cmap.green); + free(cmap.blue); + free(cmap.transp); + } +} + +/*! + \fn int QLinuxFbScreen::sharedRamSize(void * end) + \internal +*/ + +// This works like the QScreenCursor code. end points to the end +// of our shared structure, we return the amount of memory we reserved +int QLinuxFbScreen::sharedRamSize(void * end) +{ + shared=(QLinuxFb_Shared *)end; + shared--; + return sizeof(QLinuxFb_Shared); +} + +/*! + \reimp +*/ +void QLinuxFbScreen::setDirty(const QRect &r) +{ + if(d_ptr->driverType == EInk8Track) { + // e-Ink displays need a trigger to actually show what is + // in their framebuffer memory. The 8-Track driver does this + // by adding custom IOCTLs - FBIO_EINK_DISP_PIC (0x46a2) takes + // an argument specifying whether or not to flash the screen + // while updating. + // There doesn't seem to be a way to tell it to just update + // a subset of the screen. + if(r.left() == 0 && r.top() == 0 && r.width() == dw && r.height() == dh) + ioctl(d_ptr->fd, 0x46a2, 1); + else + ioctl(d_ptr->fd, 0x46a2, 0); + } +} + +/*! + \reimp +*/ +void QLinuxFbScreen::blank(bool on) +{ + if (d_ptr->blank == on) + return; + +#if defined(QT_QWS_IPAQ) + if (on) + system("apm -suspend"); +#else + if (d_ptr->fd == -1) + return; +// Some old kernel versions don't have this. These defines should go +// away eventually +#if defined(FBIOBLANK) +#if defined(VESA_POWERDOWN) && defined(VESA_NO_BLANKING) + ioctl(d_ptr->fd, FBIOBLANK, on ? VESA_POWERDOWN : VESA_NO_BLANKING); +#else + ioctl(d_ptr->fd, FBIOBLANK, on ? 1 : 0); +#endif +#endif +#endif + + d_ptr->blank = on; +} + +void QLinuxFbScreen::setPixelFormat(struct fb_var_screeninfo info) +{ + const fb_bitfield rgba[4] = { info.red, info.green, + info.blue, info.transp }; + + QImage::Format format = QImage::Format_Invalid; + + switch (d) { + case 32: { + const fb_bitfield argb8888[4] = {{16, 8, 0}, {8, 8, 0}, + {0, 8, 0}, {24, 8, 0}}; + const fb_bitfield abgr8888[4] = {{0, 8, 0}, {8, 8, 0}, + {16, 8, 0}, {24, 8, 0}}; + if (memcmp(rgba, argb8888, 4 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_ARGB32; + } else if (memcmp(rgba, argb8888, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB32; + } else if (memcmp(rgba, abgr8888, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB32; + pixeltype = QScreen::BGRPixel; + } + break; + } + case 24: { + const fb_bitfield rgb888[4] = {{16, 8, 0}, {8, 8, 0}, + {0, 8, 0}, {0, 0, 0}}; + const fb_bitfield bgr888[4] = {{0, 8, 0}, {8, 8, 0}, + {16, 8, 0}, {0, 0, 0}}; + if (memcmp(rgba, rgb888, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB888; + } else if (memcmp(rgba, bgr888, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB888; + pixeltype = QScreen::BGRPixel; + } + break; + } + case 18: { + const fb_bitfield rgb666[4] = {{12, 6, 0}, {6, 6, 0}, + {0, 6, 0}, {0, 0, 0}}; + if (memcmp(rgba, rgb666, 3 * sizeof(fb_bitfield)) == 0) + format = QImage::Format_RGB666; + break; + } + case 16: { + const fb_bitfield rgb565[4] = {{11, 5, 0}, {5, 6, 0}, + {0, 5, 0}, {0, 0, 0}}; + const fb_bitfield bgr565[4] = {{0, 5, 0}, {5, 6, 0}, + {11, 5, 0}, {0, 0, 0}}; + if (memcmp(rgba, rgb565, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB16; + } else if (memcmp(rgba, bgr565, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB16; + pixeltype = QScreen::BGRPixel; + } + break; + } + case 15: { + const fb_bitfield rgb1555[4] = {{10, 5, 0}, {5, 5, 0}, + {0, 5, 0}, {15, 1, 0}}; + const fb_bitfield bgr1555[4] = {{0, 5, 0}, {5, 5, 0}, + {10, 5, 0}, {15, 1, 0}}; + if (memcmp(rgba, rgb1555, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB555; + } else if (memcmp(rgba, bgr1555, 3 * sizeof(fb_bitfield)) == 0) { + format = QImage::Format_RGB555; + pixeltype = QScreen::BGRPixel; + } + break; + } + case 12: { + const fb_bitfield rgb444[4] = {{8, 4, 0}, {4, 4, 0}, + {0, 4, 0}, {0, 0, 0}}; + if (memcmp(rgba, rgb444, 3 * sizeof(fb_bitfield)) == 0) + format = QImage::Format_RGB444; + break; + } + case 8: + break; + case 1: + format = QImage::Format_Mono; //###: LSB??? + break; + default: + break; + } + + QScreen::setPixelFormat(format); +} + +bool QLinuxFbScreen::useOffscreen() +{ + // Not done for 8Track because on e-Ink displays, + // everything is offscreen anyway + if (d_ptr->driverType == EInk8Track || ((mapsize - size) < 16*1024)) + return false; + + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_LINUXFB diff --git a/src/gui/embedded/qscreenlinuxfb_qws.h b/src/gui/embedded/qscreenlinuxfb_qws.h new file mode 100644 index 0000000000..4142bb5dd4 --- /dev/null +++ b/src/gui/embedded/qscreenlinuxfb_qws.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENLINUXFB_QWS_H +#define QSCREENLINUXFB_QWS_H + +#include + +struct fb_cmap; +struct fb_var_screeninfo; +struct fb_fix_screeninfo; + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_LINUXFB + +class QLinuxFb_Shared +{ +public: + volatile int lastop; + volatile int optype; + volatile int fifocount; // Accel drivers only + volatile int fifomax; + volatile int forecol; // Foreground colour caching + volatile unsigned int buffer_offset; // Destination + volatile int linestep; + volatile int cliptop; // Clip rectangle + volatile int clipleft; + volatile int clipright; + volatile int clipbottom; + volatile unsigned int rop; + +}; + +class QLinuxFbScreenPrivate; + +class Q_GUI_EXPORT QLinuxFbScreen : public QScreen +{ +public: + explicit QLinuxFbScreen(int display_id); + virtual ~QLinuxFbScreen(); + + virtual bool initDevice(); + virtual bool connect(const QString &displaySpec); + + virtual bool useOffscreen(); + + enum DriverTypes { GenericDriver, EInk8Track }; + + virtual void disconnect(); + virtual void shutdownDevice(); + virtual void setMode(int,int,int); + virtual void save(); + virtual void restore(); + virtual void blank(bool on); + virtual void set(unsigned int,unsigned int,unsigned int,unsigned int); + virtual uchar * cache(int); + virtual void uncache(uchar *); + virtual int sharedRamSize(void *); + virtual void setDirty(const QRect&); + + QLinuxFb_Shared * shared; + +protected: + + void deleteEntry(uchar *); + + bool canaccel; + int dataoffset; + int cacheStart; + + virtual void fixupScreenInfo(fb_fix_screeninfo &finfo, fb_var_screeninfo &vinfo); + static void clearCache(QScreen *instance, int); + +private: + + void delete_entry(int); + void insert_entry(int,int,int); + void setupOffScreen(); + void createPalette(fb_cmap &cmap, fb_var_screeninfo &vinfo, fb_fix_screeninfo &finfo); + void setPixelFormat(struct fb_var_screeninfo); + + QLinuxFbScreenPrivate *d_ptr; +}; + +#endif // QT_NO_QWS_LINUXFB + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENLINUXFB_QWS_H diff --git a/src/gui/embedded/qscreenmulti_qws.cpp b/src/gui/embedded/qscreenmulti_qws.cpp new file mode 100644 index 0000000000..8a27a7cb82 --- /dev/null +++ b/src/gui/embedded/qscreenmulti_qws.cpp @@ -0,0 +1,486 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreenmulti_qws_p.h" + +#ifndef QT_NO_QWS_MULTISCREEN + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_CURSOR + +class QMultiScreenCursor : public QScreenCursor +{ +public: + QMultiScreenCursor() : currentCursor(qt_screencursor) { enable = false; } + ~QMultiScreenCursor() { qt_screencursor = 0; } + + void set(const QImage &image, int hotx, int hoty); + void move(int x, int y); + void show(); + void hide(); + + void addCursor(QScreenCursor *cursor); + +private: + void setCurrentCursor(QScreenCursor *newCursor); + + QScreenCursor *currentCursor; + QList cursors; +}; + +void QMultiScreenCursor::set(const QImage &image, int hotx, int hoty) +{ + QScreenCursor::set(image, hotx, hoty); + if (currentCursor) + currentCursor->set(image, hotx, hoty); +} + +void QMultiScreenCursor::setCurrentCursor(QScreenCursor *newCursor) +{ + *((QScreenCursor*)this) = *newCursor; + currentCursor = newCursor; +} + +// XXX: this is a mess! +void QMultiScreenCursor::move(int x, int y) +{ + const int oldIndex = qt_screen->subScreenIndexAt(pos); + QScreenCursor::move(x, y); // updates pos + const int newIndex = qt_screen->subScreenIndexAt(pos); + + if (!currentCursor && oldIndex != -1) + setCurrentCursor(cursors.at(oldIndex)); + QScreenCursor *oldCursor = currentCursor; + + if (oldIndex != -1) { + const QScreen *oldScreen = qt_screen->subScreens().at(oldIndex); + if (newIndex == -1 || oldScreen->region().contains(pos)) { + oldCursor->move(x, y); + return; + } + } + + if (newIndex != -1) { + QScreenCursor *newCursor = cursors.at(newIndex); + newCursor->set(cursor, hotspot.x(), hotspot.y()); + + if (oldCursor) { + if (oldCursor->isVisible()) + newCursor->show(); + oldCursor->hide(); + } + + newCursor->move(x, y); + + setCurrentCursor(newCursor); + } +} + +void QMultiScreenCursor::show() +{ + if (currentCursor) + currentCursor->show(); +} + +void QMultiScreenCursor::hide() +{ + if (currentCursor) + currentCursor->hide(); +} + +void QMultiScreenCursor::addCursor(QScreenCursor *cursor) +{ + cursors.append(cursor); +} + +#endif + +class QMultiScreenPrivate +{ +public: + QMultiScreenPrivate() +#ifndef QT_NO_QWS_CURSOR + : cursor(0) +#endif + {} + ~QMultiScreenPrivate() + { +#ifndef QT_NO_QWS_CURSOR + delete cursor; +#endif + } + + QList screens; + QRegion region; +#ifndef QT_NO_QWS_CURSOR + QMultiScreenCursor *cursor; +#endif +}; + +QMultiScreen::QMultiScreen(int displayId) + : QScreen(displayId, MultiClass), d_ptr(new QMultiScreenPrivate) +{ +} + +QMultiScreen::~QMultiScreen() +{ + delete d_ptr; +} + +bool QMultiScreen::initDevice() +{ + bool ok = true; + +#ifndef QT_NO_QWS_CURSOR + d_ptr->cursor = new QMultiScreenCursor; +#endif + + const int n = d_ptr->screens.count(); + for (int i = 0; i < n; ++i) { + QScreen *s = d_ptr->screens.at(i); + ok = s->initDevice() && ok; +#ifndef QT_NO_QWS_CURSOR + d_ptr->cursor->addCursor(qt_screencursor); // XXX +#endif + } + +#ifndef QT_NO_QWS_CURSOR + // XXX + qt_screencursor = d_ptr->cursor; +#endif + + return ok; +} + +static int getDisplayId(const QString &spec) +{ + QRegExp regexp(QLatin1String(":(\\d+)\\b")); + if (regexp.lastIndexIn(spec) != -1) { + const QString capture = regexp.cap(1); + return capture.toInt(); + } + return 0; +} + +static QPoint filterDisplayOffset(QString &spec) +{ + QRegExp regexp(QLatin1String(":offset=(\\d+),(\\d+)\\b")); + if (regexp.indexIn(spec) == -1) + return QPoint(); + + const int x = regexp.cap(1).toInt(); + const int y = regexp.cap(2).toInt(); + spec.remove(regexp.pos(0), regexp.matchedLength()); + return QPoint(x, y); +} + +bool QMultiScreen::connect(const QString &displaySpec) +{ + QString dSpec = displaySpec; + if (dSpec.startsWith(QLatin1String("Multi:"), Qt::CaseInsensitive)) + dSpec = dSpec.mid(QString::fromLatin1("Multi:").size()); + + const QString displayIdSpec = QString::fromLatin1(" :%1").arg(displayId); + if (dSpec.endsWith(displayIdSpec)) + dSpec = dSpec.left(dSpec.size() - displayIdSpec.size()); + + QStringList specs = dSpec.split(QLatin1Char(' '), QString::SkipEmptyParts); + foreach (QString spec, specs) { + const int id = getDisplayId(spec); + if (spec.startsWith("vnc:", Qt::CaseInsensitive)) { + spec.append(":noDisablePainting"); + } + const QPoint offset = filterDisplayOffset(spec); + QScreen *s = qt_get_screen(id, spec.toLatin1().constData()); + s->setOffset(offset); + addSubScreen(s); + } + + QScreen *firstScreen = d_ptr->screens.at(0); + Q_ASSERT(firstScreen); + + // XXX + QScreen::d = firstScreen->depth(); + + QScreen::lstep = 0; + QScreen::data = 0; + QScreen::size = 0; + + QScreen::w = d_ptr->region.boundingRect().width(); + QScreen::h = d_ptr->region.boundingRect().height(); + + QScreen::dw = QScreen::w; + QScreen::dh = QScreen::h; + + // XXX - Extend the physical size based on the first screen + // to encompass all screens, so that code that uses the multi + // screen to calculate dpi values will get the right numbers. + QScreen::physWidth = firstScreen->physicalWidth() * w / firstScreen->width(); + QScreen::physHeight = firstScreen->physicalHeight() * h / firstScreen->height(); + + // XXXXX + qt_screen = this; + + return true; +} + +void QMultiScreen::disconnect() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->disconnect(); +} + +void QMultiScreen::shutdownDevice() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->shutdownDevice(); +} + +void QMultiScreen::setMode(int, int, int) +{ + return; +} + +bool QMultiScreen::supportsDepth(int) const +{ + return false; +} + +void QMultiScreen::save() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->save(); +} + +void QMultiScreen::restore() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->restore(); +} + +void QMultiScreen::blank(bool on) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->blank(on); +} + +bool QMultiScreen::onCard(const unsigned char *ptr) const +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + if (d_ptr->screens.at(i)->onCard(ptr)) + return true; + return false; +} + +bool QMultiScreen::onCard(const unsigned char *ptr, ulong &offset) const +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + if (d_ptr->screens.at(i)->onCard(ptr, offset)) + return true; + return false; +} + +bool QMultiScreen::isInterlaced() const +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + if (d_ptr->screens.at(i)->isInterlaced()) + return true; + + return false; +} + +int QMultiScreen::memoryNeeded(const QString &string) +{ + int total = 0; + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + total += d_ptr->screens.at(i)->memoryNeeded(string); + return total; +} + +int QMultiScreen::sharedRamSize(void *arg) +{ + int total = 0; + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + total += d_ptr->screens.at(i)->sharedRamSize(arg); + return total; +} + +void QMultiScreen::haltUpdates() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->haltUpdates(); +} + +void QMultiScreen::resumeUpdates() +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) + d_ptr->screens.at(i)->resumeUpdates(); +} + +void QMultiScreen::exposeRegion(QRegion region, int changing) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + const QRegion r = region & screen->region(); + if (r.isEmpty()) + continue; + screen->exposeRegion(r, changing); + } +} + +void QMultiScreen::solidFill(const QColor &color, const QRegion ®ion) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + const QRegion r = region & screen->region(); + if (r.isEmpty()) + continue; + screen->solidFill(color, r); + } +} + +void QMultiScreen::blit(const QImage &img, const QPoint &topLeft, + const QRegion ®ion) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + const QRegion r = region & screen->region(); + if (r.isEmpty()) + continue; + screen->blit(img, topLeft, r); + } +} + +void QMultiScreen::blit(QWSWindow *bs, const QRegion &clip) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + const QRegion r = clip & screen->region(); + if (r.isEmpty()) + continue; + screen->blit(bs, r); + } +} + +void QMultiScreen::setDirty(const QRect &rect) +{ + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + const QRegion r = screen->region() & rect; + if (r.isEmpty()) + continue; + screen->setDirty(r.boundingRect()); + } +} + + +QWSWindowSurface* QMultiScreen::createSurface(const QString &key) const +{ + QWSWindowSurface* surf = 0; + const int n = d_ptr->screens.size(); + for (int i = 0; i < n; ++i) { + QScreen *screen = d_ptr->screens.at(i); + surf = screen->createSurface(key); + if (surf) + break; + } + return surf; +} + + +QWSWindowSurface* QMultiScreen::createSurface(QWidget *widget) const +{ + const QPoint midpoint = (widget->frameGeometry().topLeft() + + widget->frameGeometry().bottomRight()) / 2; + int index = subScreenIndexAt(midpoint); + if (index == -1) + index = 0; // XXX + return d_ptr->screens.at(index)->createSurface(widget); +} + +QList QMultiScreen::subScreens() const +{ + return d_ptr->screens; +} + +QRegion QMultiScreen::region() const +{ + return d_ptr->region; +} + +void QMultiScreen::addSubScreen(QScreen *screen) +{ + d_ptr->screens.append(screen); + d_ptr->region += screen->region(); +} + +void QMultiScreen::removeSubScreen(QScreen *screen) +{ + d_ptr->screens.removeAll(screen); + d_ptr->region -= screen->region(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_MULTISCREEN diff --git a/src/gui/embedded/qscreenmulti_qws_p.h b/src/gui/embedded/qscreenmulti_qws_p.h new file mode 100644 index 0000000000..b7eba7e374 --- /dev/null +++ b/src/gui/embedded/qscreenmulti_qws_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMULTISCREEN_QWS_P_H +#define QMULTISCREEN_QWS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_MULTISCREEN + +class QMultiScreenPrivate; + +class QMultiScreen : public QScreen +{ +public: + QMultiScreen(int displayId); + ~QMultiScreen(); + bool initDevice(); + bool connect(const QString &displaySpec); + void disconnect(); + void shutdownDevice(); + void setMode(int,int,int); + bool supportsDepth(int) const; + + void save(); + void restore(); + void blank(bool on); + + bool onCard(const unsigned char *) const; + bool onCard(const unsigned char *, ulong& out_offset) const; + + bool isInterlaced() const; + + int memoryNeeded(const QString&); + int sharedRamSize(void *); + + void haltUpdates(); + void resumeUpdates(); + + void exposeRegion(QRegion r, int changing); + + void blit(const QImage &img, const QPoint &topLeft, const QRegion ®ion); + void solidFill(const QColor &color, const QRegion ®ion); + void blit(QWSWindow *bs, const QRegion &clip); + void setDirty(const QRect&); + + QWSWindowSurface* createSurface(QWidget *widget) const; + QWSWindowSurface* createSurface(const QString &key) const; + + QList subScreens() const; + QRegion region() const; + +private: + void addSubScreen(QScreen *screen); + void removeSubScreen(QScreen *screen); + + QMultiScreenPrivate *d_ptr; +}; + + +QT_END_NAMESPACE +#endif // QT_NO_QWS_MULTISCREEN +#endif // QMULTISCREEN_QWS_P_H diff --git a/src/gui/embedded/qscreenproxy_qws.cpp b/src/gui/embedded/qscreenproxy_qws.cpp new file mode 100644 index 0000000000..706524ad19 --- /dev/null +++ b/src/gui/embedded/qscreenproxy_qws.cpp @@ -0,0 +1,635 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QT_NO_QWS_PROXYSCREEN + +#include + +QT_BEGIN_NAMESPACE +#ifndef QT_NO_QWS_CURSOR + +/*! + \class QProxyScreenCursor + \since 4.5 + \ingroup qws + \brief The QProxyScreenCursor class provides a generic interface to + QScreenCursor implementations. +*/ + +/*! + Constructs a proxy screen cursor. +*/ +QProxyScreenCursor::QProxyScreenCursor() + : QScreenCursor(), realCursor(0), d_ptr(0) +{ +} + +/*! + Destroys the proxy screen cursor. +*/ +QProxyScreenCursor::~QProxyScreenCursor() +{ +} + +/*! + Sets the real screen cursor to be used for the proxy screen cursor to + the \a cursor specified. + + \sa screenCursor() +*/ +void QProxyScreenCursor::setScreenCursor(QScreenCursor *cursor) +{ + realCursor = cursor; + configure(); +} + +/*! + Returns the real screen cursor used by the proxy screen cursor. + + \sa setScreenCursor() +*/ +QScreenCursor* QProxyScreenCursor::screenCursor() const +{ + return realCursor; +} + +/*! + \reimp +*/ +void QProxyScreenCursor::set(const QImage &image, int hotx, int hoty) +{ + if (realCursor) { + hotspot = QPoint(hotx, hoty); + cursor = image; + size = image.size(); + realCursor->set(image, hotx, hoty); + } else { + QScreenCursor::set(image, hotx, hoty); + } +} + +/*! + \reimp +*/ +void QProxyScreenCursor::move(int x, int y) +{ + if (realCursor) { + pos = QPoint(x, y); + realCursor->move(x, y); + } else { + QScreenCursor::move(x, y); + } +} + +/*! + \reimp +*/ +void QProxyScreenCursor::show() +{ + if (realCursor) { + realCursor->show(); + enable = true; + } else { + QScreenCursor::show(); + } +} + +/*! + \reimp +*/ +void QProxyScreenCursor::hide() +{ + if (realCursor) { + realCursor->hide(); + enable = false; + } else { + QScreenCursor::hide(); + } +} + +/*! + \internal +*/ +void QProxyScreenCursor::configure() +{ + if (!realCursor) + return; + + cursor = realCursor->cursor; + size = realCursor->size; + pos = realCursor->pos; + hotspot = realCursor->hotspot; + enable = realCursor->enable; + hwaccel = realCursor->hwaccel; + supportsAlpha = realCursor->supportsAlpha; +} + +#endif // QT_NO_QWS_CURSOR + +/*! + \class QProxyScreen + \ingroup qws + \brief The QProxyScreen class provides a generic interface to QScreen implementations. +*/ + +/*! + \fn QProxyScreen::QProxyScreen(int displayId, ClassId classId) + + Constructs a proxy screen with the given \a displayId and \a classId. +*/ +QProxyScreen::QProxyScreen(int displayId, QScreen::ClassId classId) + : QScreen(displayId, classId), realScreen(0), d_ptr(0) +{ +} + +/*! + Destroys the proxy screen. +*/ +QProxyScreen::~QProxyScreen() +{ +} + +/*! + Sets the real \a screen to be used by the proxy screen. + + \sa screen() +*/ +void QProxyScreen::setScreen(QScreen *screen) +{ + realScreen = screen; + configure(); +} + +/*! + Returns the real screen used by the proxy screen. + + \sa setScreen() +*/ +QScreen* QProxyScreen::screen() const +{ + return realScreen; +} + + +/*! + \internal +*/ +void QProxyScreen::configure() +{ + if (!realScreen) + return; + + d = realScreen->depth(); + w = realScreen->width(); + h = realScreen->height(); + dw = realScreen->deviceWidth(); + dh = realScreen->deviceHeight(); + lstep = realScreen->linestep(); + data = realScreen->base(); + lstep = realScreen->linestep(); + size = realScreen->screenSize(); + physWidth = realScreen->physicalWidth(); + physHeight = realScreen->physicalHeight(); + pixeltype = realScreen->pixelType(); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + setFrameBufferLittleEndian(realScreen->frameBufferLittleEndian()); +#endif + + setOffset(realScreen->offset()); + setPixelFormat(realScreen->pixelFormat()); + +#ifdef QT_QWS_CLIENTBLIT + setSupportsBlitInClients(realScreen->supportsBlitInClients()); +#endif +} + +/*! + \internal + Returns the display ID that corresponds to the given \a spec. +*/ +static int getDisplayId(const QString &spec) +{ + QRegExp regexp(QLatin1String(":(\\d+)\\b")); + if (regexp.lastIndexIn(spec) != -1) { + const QString capture = regexp.cap(1); + return capture.toInt(); + } + return 0; +} + +/*! + \reimp +*/ +bool QProxyScreen::connect(const QString &displaySpec) +{ + const int id = getDisplayId(displaySpec); + realScreen = qt_get_screen(id, displaySpec.toLatin1().constData()); + configure(); + + return true; +} + +/*! + \reimp +*/ +void QProxyScreen::exposeRegion(QRegion r, int changing) +{ + if (!realScreen) { + QScreen::exposeRegion(r, changing); + return; + } + + realScreen->exposeRegion(r, changing); + r &= realScreen->region(); + + const QVector rects = r.rects(); + for (int i = 0; i < rects.size(); ++i) + setDirty(rects.at(i)); +} + +/*! + \reimp +*/ +void QProxyScreen::blit(const QImage &image, const QPoint &topLeft, + const QRegion ®ion) +{ + if (!realScreen) { + QScreen::blit(image, topLeft, region); + return; + } + + realScreen->blit(image, topLeft, region); +} + +/*! + \reimp +*/ +void QProxyScreen::solidFill(const QColor &color, const QRegion ®ion) +{ + if (!realScreen) { + QScreen::solidFill(color, region); + return; + } + realScreen->solidFill(color, region); +} + +/*! + \reimp +*/ +QSize QProxyScreen::mapToDevice(const QSize &s) const +{ + if (!realScreen) + return QScreen::mapToDevice(s); + + return realScreen->mapToDevice(s); +} + +/*! + \reimp +*/ +QSize QProxyScreen::mapFromDevice(const QSize &s) const +{ + if (!realScreen) + return QScreen::mapFromDevice(s); + + return realScreen->mapFromDevice(s); +} + +/*! + \reimp +*/ +QPoint QProxyScreen::mapToDevice(const QPoint &p, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapToDevice(p, s); + + return realScreen->mapToDevice(p, s); +} + +/*! + \reimp +*/ +QPoint QProxyScreen::mapFromDevice(const QPoint &p, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapFromDevice(p, s); + + return realScreen->mapFromDevice(p, s); +} + +/*! + \reimp +*/ +QRect QProxyScreen::mapToDevice(const QRect &r, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapToDevice(r, s); + + return realScreen->mapToDevice(r, s); +} + +/*! + \reimp +*/ +QRect QProxyScreen::mapFromDevice(const QRect &r, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapFromDevice(r, s); + + return realScreen->mapFromDevice(r, s); +} + +/*! + \reimp +*/ +QRegion QProxyScreen::mapToDevice(const QRegion &r, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapToDevice(r, s); + + return realScreen->mapToDevice(r, s); +} + +/*! + \reimp +*/ +QRegion QProxyScreen::mapFromDevice(const QRegion &r, const QSize &s) const +{ + if (!realScreen) + return QScreen::mapFromDevice(r, s); + + return realScreen->mapFromDevice(r, s); +} + +/*! + \reimp +*/ +void QProxyScreen::disconnect() +{ + if (realScreen) { + realScreen->disconnect(); + delete realScreen; + realScreen = 0; + } +} + +/*! +*/ +bool QProxyScreen::initDevice() +{ + if (realScreen) + return realScreen->initDevice(); + + return false; +} + +/*! + \reimp +*/ +void QProxyScreen::shutdownDevice() +{ + if (realScreen) + realScreen->shutdownDevice(); +} + +/*! + \reimp +*/ +void QProxyScreen::setMode(int w,int h, int d) +{ + if (realScreen) { + realScreen->setMode(w, h, d); + } else { + QScreen::dw = QScreen::w = w; + QScreen::dh = QScreen::h = h; + QScreen::d = d; + } + configure(); + exposeRegion(region(), 0); +} + +/*! + \reimp +*/ +bool QProxyScreen::supportsDepth(int depth) const +{ + if (realScreen) + return realScreen->supportsDepth(depth); + return false; +} + +/*! + \reimp +*/ +void QProxyScreen::save() +{ + if (realScreen) + realScreen->save(); + QScreen::save(); +} + +/*! + \reimp +*/ +void QProxyScreen::restore() +{ + if (realScreen) + realScreen->restore(); + QScreen::restore(); +} + +/*! + \reimp +*/ +void QProxyScreen::blank(bool on) +{ + if (realScreen) + realScreen->blank(on); +} + +/*! + \reimp +*/ +bool QProxyScreen::onCard(const unsigned char *ptr) const +{ + if (realScreen) + return realScreen->onCard(ptr); + return false; +} + +/*! + \reimp +*/ +bool QProxyScreen::onCard(const unsigned char *ptr, ulong &offset) const +{ + if (realScreen) + return realScreen->onCard(ptr, offset); + return false; +} + +/*! + \reimp +*/ +bool QProxyScreen::isInterlaced() const +{ + if (realScreen) + return realScreen->isInterlaced(); + return false; +} + +/*! + \reimp +*/ +bool QProxyScreen::isTransformed() const +{ + if (realScreen) + return realScreen->isTransformed(); + return QScreen::isTransformed(); +} + +/*! + \reimp +*/ +int QProxyScreen::transformOrientation() const +{ + if (realScreen) + return realScreen->transformOrientation(); + return QScreen::transformOrientation(); +} + +/*! +\internal +*/ +int QProxyScreen::memoryNeeded(const QString &str) +{ + if (realScreen) + return realScreen->memoryNeeded(str); + else + return QScreen::memoryNeeded(str); +} + +/*! +\internal +*/ +int QProxyScreen::sharedRamSize(void *ptr) +{ + if (realScreen) + return realScreen->sharedRamSize(ptr); + else + return QScreen::sharedRamSize(ptr); +} + +/*! +\internal +*/ +void QProxyScreen::haltUpdates() +{ + if (realScreen) + realScreen->haltUpdates(); +} + +/*! +\internal +*/ +void QProxyScreen::resumeUpdates() +{ + if (realScreen) + realScreen->resumeUpdates(); +} + +/*! + \reimp +*/ +void QProxyScreen::setDirty(const QRect &rect) +{ + if (realScreen) + realScreen->setDirty(rect); +} + +/*! + \reimp +*/ +QWSWindowSurface* QProxyScreen::createSurface(QWidget *widget) const +{ + if (realScreen) + return realScreen->createSurface(widget); + + return QScreen::createSurface(widget); +} + +/*! + \reimp +*/ +QWSWindowSurface* QProxyScreen::createSurface(const QString &key) const +{ + if (realScreen) + return realScreen->createSurface(key); + + return QScreen::createSurface(key); +} + +/*! + \reimp +*/ +QList QProxyScreen::subScreens() const +{ + if (realScreen) + return realScreen->subScreens(); + + return QScreen::subScreens(); +} + +/*! + \reimp +*/ +QRegion QProxyScreen::region() const +{ + if (realScreen) + return realScreen->region(); + else + return QScreen::region(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_PROXYSCREEN diff --git a/src/gui/embedded/qscreenproxy_qws.h b/src/gui/embedded/qscreenproxy_qws.h new file mode 100644 index 0000000000..bde36aa6bd --- /dev/null +++ b/src/gui/embedded/qscreenproxy_qws.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYSCREEN_QWS_H +#define QPROXYSCREEN_QWS_H + +#include + +#ifndef QT_NO_QWS_PROXYSCREEN + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QProxyScreenPrivate; + +#ifndef QT_NO_QWS_CURSOR + +class QProxyScreenCursorPrivate; + +class Q_GUI_EXPORT QProxyScreenCursor : public QScreenCursor +{ +public: + QProxyScreenCursor(); + ~QProxyScreenCursor(); + + void setScreenCursor(QScreenCursor *cursor); + QScreenCursor* screenCursor() const; + + void set(const QImage &image, int hotx, int hoty); + void move(int x, int y); + void show(); + void hide(); + +private: + void configure(); + + QScreenCursor *realCursor; + QProxyScreenCursorPrivate *d_ptr; +}; + +#endif // QT_NO_QWS_CURSOR + +class Q_GUI_EXPORT QProxyScreen : public QScreen +{ +public: + QProxyScreen(int display_id, ClassId = ProxyClass); + ~QProxyScreen(); + + void setScreen(QScreen *screen); + QScreen *screen() const; + + QSize mapToDevice(const QSize &s) const; + QSize mapFromDevice(const QSize &s) const; + + QPoint mapToDevice(const QPoint &, const QSize &) const; + QPoint mapFromDevice(const QPoint &, const QSize &) const; + + QRect mapToDevice(const QRect &, const QSize &) const; + QRect mapFromDevice(const QRect &, const QSize &) const; + + QRegion mapToDevice(const QRegion &, const QSize &) const; + QRegion mapFromDevice(const QRegion &, const QSize &) const; + + bool connect(const QString &displaySpec); + bool initDevice(); + void shutdownDevice(); + void disconnect(); + + void setMode(int width, int height, int depth); + bool supportsDepth(int) const; + + void save(); + void restore(); + void blank(bool on); + + bool onCard(const unsigned char *) const; + bool onCard(const unsigned char *, ulong& out_offset) const; + + bool isInterlaced() const; + bool isTransformed() const; + int transformOrientation() const; + + int memoryNeeded(const QString&); + int sharedRamSize(void *); + + void haltUpdates(); + void resumeUpdates(); + + void exposeRegion(QRegion r, int changing); + void blit(const QImage &img, const QPoint &topLeft, const QRegion ®ion); + void solidFill(const QColor &color, const QRegion ®ion); + void setDirty(const QRect&); + + QWSWindowSurface* createSurface(QWidget *widget) const; + QWSWindowSurface* createSurface(const QString &key) const; + + QList subScreens() const; + QRegion region() const; + +private: + void configure(); + + QScreen *realScreen; + QProxyScreenPrivate *d_ptr; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_QWS_PROXYSCREEN +#endif // QPROXYSCREEN_QWS_H diff --git a/src/gui/embedded/qscreenqnx_qws.cpp b/src/gui/embedded/qscreenqnx_qws.cpp new file mode 100644 index 0000000000..7101a71d83 --- /dev/null +++ b/src/gui/embedded/qscreenqnx_qws.cpp @@ -0,0 +1,450 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreenqnx_qws.h" +#include "qdebug.h" + +#include + +QT_BEGIN_NAMESPACE + +// This struct holds all the pointers to QNX's internals +struct QQnxScreenContext +{ + inline QQnxScreenContext() + : device(0), display(0), layer(0), hwSurface(0), memSurface(0), context(0) + {} + + gf_dev_t device; + gf_dev_info_t deviceInfo; + gf_display_t display; + gf_display_info_t displayInfo; + gf_layer_t layer; + gf_surface_t hwSurface; + gf_surface_t memSurface; + gf_surface_info_t memSurfaceInfo; + gf_context_t context; +}; + +/*! + \class QQnxScreen + \preliminary + \ingroup qws + \since 4.6 + \internal + + \brief The QQnxScreen class implements a screen driver + for QNX io-display based devices. + + Note - you never have to instanciate this class, the QScreenDriverFactory + does that for us based on the \c{QWS_DISPLAY} environment variable. + + To activate this driver, set \c{QWS_DISPLAY} to \c{qnx}. + + Example: + \c{QWS_DISPLAY=qnx; export QWS_DISPLAY} + + By default, the main layer of the first display of the first device is used. + If you have multiple graphic cards, multiple displays or multiple layers and + don't want to connect to the default, you can override that with setting + the corresponding options \c{device}, \c{display} or \c{layer} in the \c{QWS_DISPLAY} variable: + + \c{QWS_DISPLAY=qnx:device=3:display=4:layer=5} + + In addition, it is suggested to set the physical width and height of the display. + QQnxScreen will use that information to compute the dots per inch (DPI) in order to render + fonts correctly. If this informaiton is omitted, QQnxScreen defaults to 72 dpi. + + \c{QWS_DISPLAY=qnx:mmWidth=120:mmHeight=80} + + \c{mmWidth} and \c{mmHeight} are the physical width/height of the screen in millimeters. + + \sa QScreen, QScreenDriverPlugin, {Running Qt for Embedded Linux Applications}{Running Applications} +*/ + +/*! + Constructs a QQnxScreen object. The \a display_id argument + identifies the Qt for Embedded Linux server to connect to. +*/ +QQnxScreen::QQnxScreen(int display_id) + : QScreen(display_id), d(new QQnxScreenContext) +{ +} + +/*! + Destroys this QQnxScreen object. +*/ +QQnxScreen::~QQnxScreen() +{ + delete d; +} + +/*! \reimp +*/ +bool QQnxScreen::initDevice() +{ + // implement this if you have multiple processes that want to access the display + // (not required if QT_NO_QWS_MULTIPROCESS is set) + return true; +} + +/*! \internal + Attaches to the named device \a name. +*/ +static bool attachDevice(QQnxScreenContext * const d, const char *name) +{ + int ret = gf_dev_attach(&d->device, name, &d->deviceInfo); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_dev_attach(%s) failed with error code %d", name, ret); + return false; + } + return true; +} + +/*! \internal + Attaches to the display at index \a displayIndex. + */ +static bool attachDisplay(QQnxScreenContext * const d, int displayIndex) +{ + int ret = gf_display_attach(&d->display, d->device, displayIndex, &d->displayInfo); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_display_attach(%d) failed with error code %d", + displayIndex, ret); + return false; + } + return true; +} + +/*! \internal + Attaches to the layer \a layerIndex. + */ +static bool attachLayer(QQnxScreenContext * const d, int layerIndex) +{ + int ret = gf_layer_attach(&d->layer, d->display, layerIndex, 0); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_layer_attach(%d) failed with error code %d", layerIndex, + ret); + return false; + } + gf_layer_enable(d->layer); + + return true; +} + +/*! \internal + Creates a new hardware surface (usually on the Gfx card memory) with the dimensions \a w * \a h. + */ +static bool createHwSurface(QQnxScreenContext * const d, int w, int h) +{ + int ret = gf_surface_create_layer(&d->hwSurface, &d->layer, 1, 0, + w, h, GF_FORMAT_ARGB8888, 0, 0); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_surface_create_layer(%dx%d) failed with error code %d", + w, h, ret); + return false; + } + + gf_layer_set_surfaces(d->layer, &d->hwSurface, 1); + + ret = gf_layer_update(d->layer, 0); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_layer_update() failed with error code %d\n", ret); + return false; + } + + return true; +} + +/*! \internal + Creates an in-memory, linear accessible surface of dimensions \a w * \a h. + This is the main surface that QWS blits to. + */ +static bool createMemSurface(QQnxScreenContext * const d, int w, int h) +{ + // Note: gf_surface_attach() could also be used, so we'll create the buffer + // and let the surface point to it. Here, we use surface_create instead. + + int ret = gf_surface_create(&d->memSurface, d->device, w, h, + GF_FORMAT_ARGB8888, 0, + GF_SURFACE_CREATE_CPU_FAST_ACCESS | GF_SURFACE_CREATE_CPU_LINEAR_ACCESSIBLE + | GF_SURFACE_PHYS_CONTIG | GF_SURFACE_CREATE_SHAREABLE); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_surface_create(%dx%d) failed with error code %d", + w, h, ret); + return false; + } + + gf_surface_get_info(d->memSurface, &d->memSurfaceInfo); + + if (d->memSurfaceInfo.sid == unsigned(GF_SID_INVALID)) { + qWarning("QQnxScreen: gf_surface_get_info() failed."); + return false; + } + + return true; +} + +/* \internal + Creates a QNX gf context and sets our memory surface on it. + */ +static bool createContext(QQnxScreenContext * const d) +{ + int ret = gf_context_create(&d->context); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_context_create() failed with error code %d", ret); + return false; + } + + ret = gf_context_set_surface(d->context, d->memSurface); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_context_set_surface() failed with error code %d", ret); + return false; + } + + return true; +} + +/*! \reimp + Connects to QNX's io-display based device based on the \a displaySpec parameters + from the \c{QWS_DISPLAY} environment variable. See the QQnxScreen class documentation + for possible parameters. + + \sa QQnxScreen + */ +bool QQnxScreen::connect(const QString &displaySpec) +{ + const QStringList params = displaySpec.split(QLatin1Char(':'), QString::SkipEmptyParts); + + bool isOk = false; + QRegExp deviceRegExp(QLatin1String("^device=(.+)$")); + if (params.indexOf(deviceRegExp) != -1) { + isOk = attachDevice(d, deviceRegExp.cap(1).toLocal8Bit().constData()); + } else { + // no device specified - attach to device 0 (the default) + isOk = attachDevice(d, GF_DEVICE_INDEX(0)); + } + + if (!isOk) + return false; + + qDebug("QQnxScreen: Attached to Device, number of displays: %d", d->deviceInfo.ndisplays); + + // default to display 0 + int displayIndex = 0; + QRegExp displayRegexp(QLatin1String("^display=(\\d+)$")); + if (params.indexOf(displayRegexp) != -1) { + displayIndex = displayRegexp.cap(1).toInt(); + } + + if (!attachDisplay(d, displayIndex)) + return false; + + qDebug("QQnxScreen: Attached to Display %d, resolution %dx%d, refresh %d Hz", + displayIndex, d->displayInfo.xres, d->displayInfo.yres, + d->displayInfo.refresh); + + + // default to main_layer_index from the displayInfo struct + int layerIndex = 0; + QRegExp layerRegexp(QLatin1String("^layer=(\\d+)$")); + if (params.indexOf(layerRegexp) != -1) { + layerIndex = layerRegexp.cap(1).toInt(); + } else { + layerIndex = d->displayInfo.main_layer_index; + } + + if (!attachLayer(d, layerIndex)) + return false; + + // tell QWSDisplay the width and height of the display + w = dw = d->displayInfo.xres; + h = dh = d->displayInfo.yres; + + // we only support 32 bit displays for now. + QScreen::d = 32; + + // assume 72 dpi as default, to calculate the physical dimensions if not specified + const int defaultDpi = 72; + + // Handle display physical size spec. + QRegExp mmWidthRegexp(QLatin1String("^mmWidth=(\\d+)$")); + if (params.indexOf(mmWidthRegexp) == -1) { + physWidth = qRound(dw * 25.4 / defaultDpi); + } else { + physWidth = mmWidthRegexp.cap(1).toInt(); + } + + QRegExp mmHeightRegexp(QLatin1String("^mmHeight=(\\d+)$")); + if (params.indexOf(mmHeightRegexp) == -1) { + physHeight = qRound(dh * 25.4 / defaultDpi); + } else { + physHeight = mmHeightRegexp.cap(1).toInt(); + } + + // create a hardware surface with our dimensions. In the old days, it was possible + // to get a pointer directly to the hw surface, so we could blit directly. Now, we + // have to use one indirection more, because it's not guaranteed that the hw surface + // is mappable into our process. + if (!createHwSurface(d, w, h)) + return false; + + // create an in-memory linear surface that is used by QWS. QWS will blit directly in here. + if (!createMemSurface(d, w, h)) + return false; + + // set the address of the in-memory buffer that QWS is blitting to + data = d->memSurfaceInfo.vaddr; + // set the line stepping + lstep = d->memSurfaceInfo.stride; + + // the overall size of the in-memory buffer is linestep * height + size = mapsize = lstep * h; + + // create a QNX drawing context + if (!createContext(d)) + return false; + + // we're always using a software cursor for now. Initialize it here. + QScreenCursor::initSoftwareCursor(); + + // done, the driver should be connected to the display now. + return true; +} + +/*! \reimp + */ +void QQnxScreen::disconnect() +{ + if (d->context) + gf_context_free(d->context); + + if (d->memSurface) + gf_surface_free(d->memSurface); + + if (d->hwSurface) + gf_surface_free(d->hwSurface); + + if (d->layer) + gf_layer_detach(d->layer); + + if (d->display) + gf_display_detach(d->display); + + if (d->device) + gf_dev_detach(d->device); + + d->memSurface = 0; + d->hwSurface = 0; + d->context = 0; + d->layer = 0; + d->display = 0; + d->device = 0; +} + +/*! \reimp + */ +void QQnxScreen::shutdownDevice() +{ +} + + +/*! \reimp + QQnxScreen doesn't support setting the mode, use io-display instead. + */ +void QQnxScreen::setMode(int,int,int) +{ + qWarning("QQnxScreen: Unable to change mode, use io-display instead."); +} + +/*! \reimp + */ +bool QQnxScreen::supportsDepth(int depth) const +{ + // only 32-bit for the moment + return depth == 32; +} + +/*! \reimp + */ +void QQnxScreen::exposeRegion(QRegion r, int changing) +{ + // here is where the actual magic happens. QWS will call exposeRegion whenever + // a region on the screen is dirty and needs to be updated on the actual screen. + + // first, call the parent implementation. The parent implementation will update + // the region on our in-memory surface + QScreen::exposeRegion(r, changing); + + // now our in-memory surface should be up to date with the latest changes. + // the code below copies the region from the in-memory surface to the hardware. + + // just get the bounding rectangle of the region. Most screen updates are rectangular + // anyways. Code could be optimized to blit each and every member of the region + // individually, but in real life, the speed-up is neglectable + const QRect br = r.boundingRect(); + if (br.isEmpty()) + return; // ignore empty regions because gf_draw_blit2 doesn't like 0x0 dimensions + + // start drawing. + int ret = gf_draw_begin(d->context); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_draw_begin() failed with error code %d", ret); + return; + } + + // blit the changed region from the memory surface to the hardware surface + ret = gf_draw_blit2(d->context, d->memSurface, d->hwSurface, + br.x(), br.y(), br.right(), br.bottom(), br.x(), br.y()); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_draw_blit2() failed with error code %d", ret); + } + + // flush all drawing commands (in our case, a single blit) + ret = gf_draw_flush(d->context); + if (ret != GF_ERR_OK) { + qWarning("QQnxScreen: gf_draw_flush() failed with error code %d", ret); + } + + // tell QNX that we're done drawing. + gf_draw_end(d->context); +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qscreenqnx_qws.h b/src/gui/embedded/qscreenqnx_qws.h new file mode 100644 index 0000000000..35915ae794 --- /dev/null +++ b/src/gui/embedded/qscreenqnx_qws.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENQNX_QWS_H +#define QSCREENQNX_QWS_H + +#include + +#ifndef QT_NO_QWS_QNX + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +struct QQnxScreenContext; + +class QQnxScreen : public QScreen +{ +public: + explicit QQnxScreen(int display_id); + ~QQnxScreen(); + + bool initDevice(); + bool connect(const QString &displaySpec); + void disconnect(); + void shutdownDevice(); + void setMode(int,int,int); + bool supportsDepth(int) const; + + void exposeRegion(QRegion r, int changing); + +private: + QQnxScreenContext * const d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_QWS_QNX + +#endif diff --git a/src/gui/embedded/qscreentransformed_qws.cpp b/src/gui/embedded/qscreentransformed_qws.cpp new file mode 100644 index 0000000000..f37ab8084b --- /dev/null +++ b/src/gui/embedded/qscreentransformed_qws.cpp @@ -0,0 +1,748 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qscreentransformed_qws.h" + +#ifndef QT_NO_QWS_TRANSFORMED +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +//#define QT_REGION_DEBUG + +#ifdef QT_REGION_DEBUG +#include +#endif + +class QTransformedScreenPrivate +{ +public: + QTransformedScreenPrivate(QTransformedScreen *parent); + + void configure(); + + QTransformedScreen::Transformation transformation; +#ifdef QT_QWS_DEPTH_GENERIC + bool doGenericColors; +#endif + QTransformedScreen *q; +}; + +QTransformedScreenPrivate::QTransformedScreenPrivate(QTransformedScreen *parent) + : transformation(QTransformedScreen::None), +#ifdef QT_QWS_DEPTH_GENERIC + doGenericColors(false), +#endif + q(parent) +{ +} + +extern "C" +#ifndef QT_BUILD_GUI_LIB +Q_DECL_EXPORT +#endif +void qws_setScreenTransformation(QScreen *that, int t) +{ + QTransformedScreen *tscreen = static_cast(that); + tscreen->setTransformation((QTransformedScreen::Transformation)t); +} + +// --------------------------------------------------------------------------- +// Transformed Screen +// --------------------------------------------------------------------------- + +/*! + \internal + + \class QTransformedScreen + \ingroup qws + + \brief The QTransformedScreen class implements a screen driver for + a transformed screen. + + Note that this class is only available in \l{Qt for Embedded Linux}. + Custom screen drivers can be added by subclassing the + QScreenDriverPlugin class, using the QScreenDriverFactory class to + dynamically load the driver into the application, but there should + only be one screen object per application. + + Use the QScreen::isTransformed() function to determine if a screen + is transformed. The QTransformedScreen class itself provides means + of rotating the screen with its setTransformation() function; the + transformation() function returns the currently set rotation in + terms of the \l Transformation enum (which describes the various + available rotation settings). Alternatively, QTransformedScreen + provides an implementation of the QScreen::transformOrientation() + function, returning the current rotation as an integer value. + + \sa QScreen, QScreenDriverPlugin, {Running Applications} +*/ + +/*! + \enum QTransformedScreen::Transformation + + This enum describes the various rotations a transformed screen can + have. + + \value None No rotation + \value Rot90 90 degrees rotation + \value Rot180 180 degrees rotation + \value Rot270 270 degrees rotation +*/ + +/*! + \fn bool QTransformedScreen::isTransformed() const + \reimp +*/ + +/*! + Constructs a QTransformedScreen object. The \a displayId argument + identifies the Qt for Embedded Linux server to connect to. +*/ +QTransformedScreen::QTransformedScreen(int displayId) + : QProxyScreen(displayId, QScreen::TransformedClass) +{ + d_ptr = new QTransformedScreenPrivate(this); + d_ptr->transformation = None; + +#ifdef QT_REGION_DEBUG + qDebug() << "QTransformedScreen::QTransformedScreen"; +#endif +} + +void QTransformedScreenPrivate::configure() +{ + // ###: works because setTransformation recalculates unconditionally + q->setTransformation(transformation); +} + +/*! + Destroys the QTransformedScreen object. +*/ +QTransformedScreen::~QTransformedScreen() +{ + delete d_ptr; +} + +static int getDisplayId(const QString &spec) +{ + QRegExp regexp(QLatin1String(":(\\d+)\\b")); + if (regexp.lastIndexIn(spec) != -1) { + const QString capture = regexp.cap(1); + return capture.toInt(); + } + return 0; +} + +static QTransformedScreen::Transformation filterTransformation(QString &spec) +{ + QRegExp regexp(QLatin1String("\\bRot(\\d+):?\\b"), Qt::CaseInsensitive); + if (regexp.indexIn(spec) == -1) + return QTransformedScreen::None; + + const int degrees = regexp.cap(1).toInt(); + spec.remove(regexp.pos(0), regexp.matchedLength()); + + return static_cast(degrees / 90); +} + +/*! + \reimp +*/ +bool QTransformedScreen::connect(const QString &displaySpec) +{ + QString dspec = displaySpec.trimmed(); + if (dspec.startsWith(QLatin1String("Transformed:"), Qt::CaseInsensitive)) + dspec = dspec.mid(QString::fromLatin1("Transformed:").size()); + else if (!dspec.compare(QLatin1String("Transformed"), Qt::CaseInsensitive)) + dspec = QString(); + + const QString displayIdSpec = QString::fromLatin1(" :%1").arg(displayId); + if (dspec.endsWith(displayIdSpec)) + dspec = dspec.left(dspec.size() - displayIdSpec.size()); + + d_ptr->transformation = filterTransformation(dspec); + + QString driver = dspec; + int colon = driver.indexOf(QLatin1Char(':')); + if (colon >= 0) + driver.truncate(colon); + + if (!QScreenDriverFactory::keys().contains(driver, Qt::CaseInsensitive)) + if (!dspec.isEmpty()) + dspec.prepend(QLatin1Char(':')); + + const int id = getDisplayId(dspec); + QScreen *s = qt_get_screen(id, dspec.toLatin1().constData()); + setScreen(s); + +#ifdef QT_QWS_DEPTH_GENERIC + d_ptr->doGenericColors = dspec.contains(QLatin1String("genericcolors")); +#endif + + d_ptr->configure(); + + // XXX + qt_screen = this; + + return true; +} + +/*! + Returns the currently set rotation. + + \sa setTransformation(), QScreen::transformOrientation() +*/ +QTransformedScreen::Transformation QTransformedScreen::transformation() const +{ + return d_ptr->transformation; +} + +/*! + \reimp +*/ +int QTransformedScreen::transformOrientation() const +{ + return (int)d_ptr->transformation; +} + +/*! + \reimp +*/ +void QTransformedScreen::exposeRegion(QRegion region, int changing) +{ + if (!data || d_ptr->transformation == None) { + QProxyScreen::exposeRegion(region, changing); + return; + } + QScreen::exposeRegion(region, changing); +} + +/*! + Rotates this screen object according to the specified \a transformation. + + \sa transformation() +*/ +void QTransformedScreen::setTransformation(Transformation transformation) +{ + d_ptr->transformation = transformation; + QSize size = mapFromDevice(QSize(dw, dh)); + w = size.width(); + h = size.height(); + + const QScreen *s = screen(); + size = mapFromDevice(QSize(s->physicalWidth(), s->physicalHeight())); + physWidth = size.width(); + physHeight = size.height(); + +#ifdef QT_REGION_DEBUG + qDebug() << "QTransformedScreen::setTransformation" << transformation + << "size" << w << h << "dev size" << dw << dh; +#endif + +} + +static inline QRect correctNormalized(const QRect &r) { + const int x1 = qMin(r.left(), r.right()); + const int x2 = qMax(r.left(), r.right()); + const int y1 = qMin(r.top(), r.bottom()); + const int y2 = qMax(r.top(), r.bottom()); + + return QRect( QPoint(x1,y1), QPoint(x2,y2) ); +} + +template +static inline void blit90(QScreen *screen, const QImage &image, + const QRect &rect, const QPoint &topLeft) +{ + const SRC *src = (const SRC*)(image.scanLine(rect.top())) + rect.left(); + DST *dest = (DST*)(screen->base() + topLeft.y() * screen->linestep()) + + topLeft.x(); + qt_memrotate90(src, rect.width(), rect.height(), image.bytesPerLine(), + dest, screen->linestep()); +} + +template +static inline void blit180(QScreen *screen, const QImage &image, + const QRect &rect, const QPoint &topLeft) +{ + const SRC *src = (const SRC*)(image.scanLine(rect.top())) + rect.left(); + DST *dest = (DST*)(screen->base() + topLeft.y() * screen->linestep()) + + topLeft.x(); + qt_memrotate180(src, rect.width(), rect.height(), image.bytesPerLine(), + dest, screen->linestep()); +} + +template +static inline void blit270(QScreen *screen, const QImage &image, + const QRect &rect, const QPoint &topLeft) +{ + const SRC *src = (const SRC *)(image.scanLine(rect.top())) + rect.left(); + DST *dest = (DST*)(screen->base() + topLeft.y() * screen->linestep()) + + topLeft.x(); + qt_memrotate270(src, rect.width(), rect.height(), image.bytesPerLine(), + dest, screen->linestep()); +} + +typedef void (*BlitFunc)(QScreen *, const QImage &, const QRect &, const QPoint &); + +#define SET_BLIT_FUNC(dst, src, rotation, func) \ +do { \ + switch (rotation) { \ + case Rot90: \ + func = blit90; \ + break; \ + case Rot180: \ + func = blit180; \ + break; \ + case Rot270: \ + func = blit270; \ + break; \ + default: \ + break; \ + } \ +} while (0) + +/*! + \reimp +*/ +void QTransformedScreen::blit(const QImage &image, const QPoint &topLeft, + const QRegion ®ion) +{ + const Transformation trans = d_ptr->transformation; + if (trans == None) { + QProxyScreen::blit(image, topLeft, region); + return; + } + + const QVector rects = region.rects(); + const QRect bound = QRect(0, 0, QScreen::w, QScreen::h) + & QRect(topLeft, image.size()); + + BlitFunc func = 0; +#ifdef QT_QWS_DEPTH_GENERIC + if (d_ptr->doGenericColors && depth() == 16) { + if (image.depth() == 16) + SET_BLIT_FUNC(qrgb_generic16, quint16, trans, func); + else + SET_BLIT_FUNC(qrgb_generic16, quint32, trans, func); + } else +#endif + switch (depth()) { +#ifdef QT_QWS_DEPTH_32 + case 32: +#ifdef QT_QWS_DEPTH_16 + if (image.depth() == 16) + SET_BLIT_FUNC(quint32, quint16, trans, func); + else +#endif + SET_BLIT_FUNC(quint32, quint32, trans, func); + break; +#endif +#if defined(QT_QWS_DEPTH_24) || defined(QT_QWS_DEPTH18) + case 24: + case 18: + SET_BLIT_FUNC(quint24, quint24, trans, func); + break; +#endif +#if defined(QT_QWS_DEPTH_16) || defined(QT_QWS_DEPTH_15) || defined(QT_QWS_DEPTH_12) + case 16: +#if defined QT_QWS_ROTATE_BGR + if (pixelType() == BGRPixel && image.depth() == 16) { + SET_BLIT_FUNC(qbgr565, quint16, trans, func); + break; + } //fall-through here!!! +#endif + case 15: +#if defined QT_QWS_ROTATE_BGR + if (pixelType() == BGRPixel && image.format() == QImage::Format_RGB555) { + SET_BLIT_FUNC(qbgr555, qrgb555, trans, func); + break; + } //fall-through here!!! +#endif + case 12: + if (image.depth() == 16) + SET_BLIT_FUNC(quint16, quint16, trans, func); + else + SET_BLIT_FUNC(quint16, quint32, trans, func); + break; +#endif +#ifdef QT_QWS_DEPTH_8 + case 8: + if (image.format() == QImage::Format_RGB444) + SET_BLIT_FUNC(quint8, qrgb444, trans, func); + else if (image.depth() == 16) + SET_BLIT_FUNC(quint8, quint16, trans, func); + else + SET_BLIT_FUNC(quint8, quint32, trans, func); + break; +#endif + default: + return; + } + if (!func) + return; + + QWSDisplay::grab(); + for (int i = 0; i < rects.size(); ++i) { + const QRect r = rects.at(i) & bound; + + QPoint dst; + switch (trans) { + case Rot90: + dst = mapToDevice(r.topRight(), QSize(w, h)); + break; + case Rot180: + dst = mapToDevice(r.bottomRight(), QSize(w, h)); + break; + case Rot270: + dst = mapToDevice(r.bottomLeft(), QSize(w, h)); + break; + default: + break; + } + func(this, image, r.translated(-topLeft), dst); + } + QWSDisplay::ungrab(); + +} + +/*! + \reimp +*/ +void QTransformedScreen::solidFill(const QColor &color, const QRegion ®ion) +{ + const QRegion tr = mapToDevice(region, QSize(w,h)); + + Q_ASSERT(tr.boundingRect() == mapToDevice(region.boundingRect(), QSize(w,h))); + +#ifdef QT_REGION_DEBUG + qDebug() << "QTransformedScreen::solidFill region" << region << "transformed" << tr; +#endif + QProxyScreen::solidFill(color, tr); +} + +/*! + \reimp +*/ +QSize QTransformedScreen::mapToDevice(const QSize &s) const +{ + switch (d_ptr->transformation) { + case None: + case Rot180: + break; + case Rot90: + case Rot270: + return QSize(s.height(), s.width()); + break; + } + return s; +} + +/*! + \reimp +*/ +QSize QTransformedScreen::mapFromDevice(const QSize &s) const +{ + switch (d_ptr->transformation) { + case None: + case Rot180: + break; + case Rot90: + case Rot270: + return QSize(s.height(), s.width()); + break; + } + return s; +} + +/*! + \reimp +*/ +QPoint QTransformedScreen::mapToDevice(const QPoint &p, const QSize &s) const +{ + QPoint rp(p); + + switch (d_ptr->transformation) { + case None: + break; + case Rot90: + rp.setX(p.y()); + rp.setY(s.width() - p.x() - 1); + break; + case Rot180: + rp.setX(s.width() - p.x() - 1); + rp.setY(s.height() - p.y() - 1); + break; + case Rot270: + rp.setX(s.height() - p.y() - 1); + rp.setY(p.x()); + break; + } + + return rp; +} + +/*! + \reimp +*/ +QPoint QTransformedScreen::mapFromDevice(const QPoint &p, const QSize &s) const +{ + QPoint rp(p); + + switch (d_ptr->transformation) { + case None: + break; + case Rot90: + rp.setX(s.height() - p.y() - 1); + rp.setY(p.x()); + break; + case Rot180: + rp.setX(s.width() - p.x() - 1); + rp.setY(s.height() - p.y() - 1); + break; + case Rot270: + rp.setX(p.y()); + rp.setY(s.width() - p.x() - 1); + break; + } + + return rp; +} + +/*! + \reimp +*/ +QRect QTransformedScreen::mapToDevice(const QRect &r, const QSize &s) const +{ + if (r.isNull()) + return QRect(); + + QRect tr; + switch (d_ptr->transformation) { + case None: + tr = r; + break; + case Rot90: + tr.setCoords(r.y(), s.width() - r.x() - 1, + r.bottom(), s.width() - r.right() - 1); + break; + case Rot180: + tr.setCoords(s.width() - r.x() - 1, s.height() - r.y() - 1, + s.width() - r.right() - 1, s.height() - r.bottom() - 1); + break; + case Rot270: + tr.setCoords(s.height() - r.y() - 1, r.x(), + s.height() - r.bottom() - 1, r.right()); + break; + } + + return correctNormalized(tr); +} + +/*! + \reimp +*/ +QRect QTransformedScreen::mapFromDevice(const QRect &r, const QSize &s) const +{ + if (r.isNull()) + return QRect(); + + QRect tr; + switch (d_ptr->transformation) { + case None: + tr = r; + break; + case Rot90: + tr.setCoords(s.height() - r.y() - 1, r.x(), + s.height() - r.bottom() - 1, r.right()); + break; + case Rot180: + tr.setCoords(s.width() - r.x() - 1, s.height() - r.y() - 1, + s.width() - r.right() - 1, s.height() - r.bottom() - 1); + break; + case Rot270: + tr.setCoords(r.y(), s.width() - r.x() - 1, + r.bottom(), s.width() - r.right() - 1); + break; + } + + return correctNormalized(tr); +} + +/*! + \reimp +*/ +QRegion QTransformedScreen::mapToDevice(const QRegion &rgn, const QSize &s) const +{ + if (d_ptr->transformation == None) + return QProxyScreen::mapToDevice(rgn, s); + +#ifdef QT_REGION_DEBUG + qDebug() << "mapToDevice size" << s << "rgn: " << rgn; +#endif + QRect tr; + QRegion trgn; + QVector a = rgn.rects(); + const QRect *r = a.data(); + + int w = s.width(); + int h = s.height(); + int size = a.size(); + + switch (d_ptr->transformation) { + case None: + break; + case Rot90: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(r->y(), w - r->x() - 1, + r->bottom(), w - r->right() - 1); + trgn |= correctNormalized(tr); + } + break; + case Rot180: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(w - r->x() - 1, h - r->y() - 1, + w - r->right() - 1, h - r->bottom() - 1); + trgn |= correctNormalized(tr); + } + break; + case Rot270: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(h - r->y() - 1, r->x(), + h - r->bottom() - 1, r->right()); + trgn |= correctNormalized(tr); + } + break; + } +#ifdef QT_REGION_DEBUG + qDebug() << "mapToDevice trgn: " << trgn; +#endif + return trgn; +} + +/*! + \reimp +*/ +QRegion QTransformedScreen::mapFromDevice(const QRegion &rgn, const QSize &s) const +{ + if (d_ptr->transformation == None) + return QProxyScreen::mapFromDevice(rgn, s); + +#ifdef QT_REGION_DEBUG + qDebug() << "fromDevice: realRegion count: " << rgn.rects().size() << " isEmpty? " << rgn.isEmpty() << " bounds:" << rgn.boundingRect(); +#endif + QRect tr; + QRegion trgn; + QVector a = rgn.rects(); + const QRect *r = a.data(); + + int w = s.width(); + int h = s.height(); + int size = a.size(); + + switch (d_ptr->transformation) { + case None: + break; + case Rot90: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(h - r->y() - 1, r->x(), + h - r->bottom() - 1, r->right()); + trgn |= correctNormalized(tr); + } + break; + case Rot180: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(w - r->x() - 1, h - r->y() - 1, + w - r->right() - 1, h - r->bottom() - 1); + trgn |= correctNormalized(tr); + } + break; + case Rot270: + for (int i = 0; i < size; i++, r++) { + tr.setCoords(r->y(), w - r->x() - 1, + r->bottom(), w - r->right() - 1); + trgn |= correctNormalized(tr); + } + break; + } +#ifdef QT_REGION_DEBUG + qDebug() << "fromDevice: transRegion count: " << trgn.rects().size() << " isEmpty? " << trgn.isEmpty() << " bounds:" << trgn.boundingRect(); +#endif + return trgn; +} + +/*! + \reimp +*/ +void QTransformedScreen::setDirty(const QRect& rect) +{ + const QRect r = mapToDevice(rect, QSize(width(), height())); + QProxyScreen::setDirty(r); +} + +/*! + \reimp +*/ +QRegion QTransformedScreen::region() const +{ + QRegion deviceRegion = QProxyScreen::region(); + return mapFromDevice(deviceRegion, QSize(deviceWidth(), deviceHeight())); +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_TRANSFORMED diff --git a/src/gui/embedded/qscreentransformed_qws.h b/src/gui/embedded/qscreentransformed_qws.h new file mode 100644 index 0000000000..70f5063387 --- /dev/null +++ b/src/gui/embedded/qscreentransformed_qws.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENTRANSFORMED_QWS_H +#define QSCREENTRANSFORMED_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_TRANSFORMED + +class QTransformedScreenPrivate; + +class Q_AUTOTEST_EXPORT QTransformedScreen : public QProxyScreen +{ +public: + explicit QTransformedScreen(int display_id); + ~QTransformedScreen(); + + enum Transformation { None, Rot90, Rot180, Rot270 }; + + void setTransformation(Transformation t); + Transformation transformation() const; + int transformOrientation() const; + + QSize mapToDevice(const QSize &s) const; + QSize mapFromDevice(const QSize &s) const; + + QPoint mapToDevice(const QPoint &, const QSize &) const; + QPoint mapFromDevice(const QPoint &, const QSize &) const; + + QRect mapToDevice(const QRect &, const QSize &) const; + QRect mapFromDevice(const QRect &, const QSize &) const; + + QRegion mapToDevice(const QRegion &, const QSize &) const; + QRegion mapFromDevice(const QRegion &, const QSize &) const; + + bool connect(const QString &displaySpec); + + bool isTransformed() const { return transformation() != None; } + + void exposeRegion(QRegion region, int changing); + void blit(const QImage &img, const QPoint &topLeft, const QRegion ®ion); + void solidFill(const QColor &color, const QRegion ®ion); + void setDirty(const QRect&); + + QRegion region() const; + +private: + friend class QTransformedScreenPrivate; + QTransformedScreenPrivate *d_ptr; +}; + +#endif // QT_NO_QWS_TRANSFORMED + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENTRANSFORMED_QWS_H diff --git a/src/gui/embedded/qscreenvfb_qws.cpp b/src/gui/embedded/qscreenvfb_qws.cpp new file mode 100644 index 0000000000..1c8829d500 --- /dev/null +++ b/src/gui/embedded/qscreenvfb_qws.cpp @@ -0,0 +1,445 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QWS_QVFB + +#define QTOPIA_QVFB_BRIGHTNESS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QVFbScreenPrivate +{ +public: + QVFbScreenPrivate(); + ~QVFbScreenPrivate(); + + bool success; + unsigned char *shmrgn; + int brightness; + bool blank; + QVFbHeader *hdr; + QWSMouseHandler *mouse; +#ifndef QT_NO_QWS_KEYBOARD + QWSKeyboardHandler *keyboard; +#endif +}; + +QVFbScreenPrivate::QVFbScreenPrivate() + : mouse(0) + +{ +#ifndef QT_NO_QWS_KEYBOARD + keyboard = 0; +#endif + brightness = 255; + blank = false; +} + +QVFbScreenPrivate::~QVFbScreenPrivate() +{ + delete mouse; +#ifndef QT_NO_QWS_KEYBOARD + delete keyboard; +#endif +} + +/*! + \internal + + \class QVFbScreen + \ingroup qws + + \brief The QVFbScreen class implements a screen driver for the + virtual framebuffer. + + Note that this class is only available in \l{Qt for Embedded Linux}. + Custom screen drivers can be added by subclassing the + QScreenDriverPlugin class, using the QScreenDriverFactory class to + dynamically load the driver into the application, but there should + only be one screen object per application. + + The Qt for Embedded Linux platform provides a \l{The Virtual + Framebuffer}{virtual framebuffer} for development and debugging; + the virtual framebuffer allows Qt for Embedded Linux applications to be + developed on a desktop machine, without switching between consoles + and X11. + + \sa QScreen, QScreenDriverPlugin, {Running Applications} +*/ + +/*! + \fn bool QVFbScreen::connect(const QString & displaySpec) + \reimp +*/ + +/*! + \fn void QVFbScreen::disconnect() + \reimp +*/ + +/*! + \fn bool QVFbScreen::initDevice() + \reimp +*/ + +/*! + \fn void QVFbScreen::restore() + \reimp +*/ + +/*! + \fn void QVFbScreen::save() + \reimp +*/ + +/*! + \fn void QVFbScreen::setDirty(const QRect & r) + \reimp +*/ + +/*! + \fn void QVFbScreen::setMode(int nw, int nh, int nd) + \reimp +*/ + +/*! + \fn void QVFbScreen::shutdownDevice() + \reimp +*/ + +/*! + \fn QVFbScreen::QVFbScreen(int displayId) + + Constructs a QVNCScreen object. The \a displayId argument + identifies the Qt for Embedded Linux server to connect to. +*/ +QVFbScreen::QVFbScreen(int display_id) + : QScreen(display_id, VFbClass), d_ptr(new QVFbScreenPrivate) +{ + d_ptr->shmrgn = 0; + d_ptr->hdr = 0; + data = 0; +} + +/*! + Destroys this QVFbScreen object. +*/ +QVFbScreen::~QVFbScreen() +{ + delete d_ptr; +} + +static QVFbScreen *connected = 0; + +bool QVFbScreen::connect(const QString &displaySpec) +{ + QStringList displayArgs = displaySpec.split(QLatin1Char(':')); + if (displayArgs.contains(QLatin1String("Gray"))) + grayscale = true; + + key_t key = ftok(QT_VFB_MOUSE_PIPE(displayId).toLocal8Bit(), 'b'); + + if (key == -1) + return false; + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +#ifndef QT_QWS_FRAMEBUFFER_LITTLE_ENDIAN + if (displayArgs.contains(QLatin1String("littleendian"))) +#endif + QScreen::setFrameBufferLittleEndian(true); +#endif + + int shmId = shmget(key, 0, 0); + if (shmId != -1) + d_ptr->shmrgn = (unsigned char *)shmat(shmId, 0, 0); + else + return false; + + if ((long)d_ptr->shmrgn == -1 || d_ptr->shmrgn == 0) { + qDebug("No shmrgn %ld", (long)d_ptr->shmrgn); + return false; + } + + d_ptr->hdr = (QVFbHeader *)d_ptr->shmrgn; + data = d_ptr->shmrgn + d_ptr->hdr->dataoffset; + + dw = w = d_ptr->hdr->width; + dh = h = d_ptr->hdr->height; + d = d_ptr->hdr->depth; + + switch (d) { + case 1: + setPixelFormat(QImage::Format_Mono); + break; + case 8: + setPixelFormat(QImage::Format_Indexed8); + break; + case 12: + setPixelFormat(QImage::Format_RGB444); + break; + case 15: + setPixelFormat(QImage::Format_RGB555); + break; + case 16: + setPixelFormat(QImage::Format_RGB16); + break; + case 18: + setPixelFormat(QImage::Format_RGB666); + break; + case 24: + setPixelFormat(QImage::Format_RGB888); + break; + case 32: + setPixelFormat(QImage::Format_ARGB32_Premultiplied); + break; + } + + lstep = d_ptr->hdr->linestep; + + // Handle display physical size spec. + int dimIdxW = -1; + int dimIdxH = -1; + for (int i = 0; i < displayArgs.size(); ++i) { + if (displayArgs.at(i).startsWith(QLatin1String("mmWidth"))) { + dimIdxW = i; + break; + } + } + for (int i = 0; i < displayArgs.size(); ++i) { + if (displayArgs.at(i).startsWith(QLatin1String("mmHeight"))) { + dimIdxH = i; + break; + } + } + if (dimIdxW >= 0) { + bool ok; + int pos = 7; + if (displayArgs.at(dimIdxW).at(pos) == QLatin1Char('=')) + ++pos; + int pw = displayArgs.at(dimIdxW).mid(pos).toInt(&ok); + if (ok) { + physWidth = pw; + if (dimIdxH < 0) + physHeight = dh*physWidth/dw; + } + } + if (dimIdxH >= 0) { + bool ok; + int pos = 8; + if (displayArgs.at(dimIdxH).at(pos) == QLatin1Char('=')) + ++pos; + int ph = displayArgs.at(dimIdxH).mid(pos).toInt(&ok); + if (ok) { + physHeight = ph; + if (dimIdxW < 0) + physWidth = dw*physHeight/dh; + } + } + if (dimIdxW < 0 && dimIdxH < 0) { + const int dpi = 72; + physWidth = qRound(dw * 25.4 / dpi); + physHeight = qRound(dh * 25.4 / dpi); + } + + qDebug("Connected to VFB server %s: %d x %d x %d %dx%dmm (%dx%ddpi)", displaySpec.toLatin1().data(), + w, h, d, physWidth, physHeight, qRound(dw*25.4/physWidth), qRound(dh*25.4/physHeight) ); + + size = lstep * h; + mapsize = size; + screencols = d_ptr->hdr->numcols; + memcpy(screenclut, d_ptr->hdr->clut, sizeof(QRgb) * screencols); + + connected = this; + + if (qgetenv("QT_QVFB_BGR").toInt()) + pixeltype = BGRPixel; + + return true; +} + +void QVFbScreen::disconnect() +{ + connected = 0; + if ((long)d_ptr->shmrgn != -1 && d_ptr->shmrgn) { + if (qApp->type() == QApplication::GuiServer && d_ptr->hdr->dataoffset >= (int)sizeof(QVFbHeader)) { + d_ptr->hdr->serverVersion = 0; + } + shmdt((char*)d_ptr->shmrgn); + } +} + +bool QVFbScreen::initDevice() +{ +#ifndef QT_NO_QWS_MOUSE_QVFB + const QString mouseDev = QT_VFB_MOUSE_PIPE(displayId); + d_ptr->mouse = new QVFbMouseHandler(QLatin1String("QVFbMouse"), mouseDev); + qwsServer->setDefaultMouse("None"); + if (d_ptr->mouse) + d_ptr->mouse->setScreen(this); +#endif + +#if !defined(QT_NO_QWS_KBD_QVFB) && !defined(QT_NO_QWS_KEYBOARD) + const QString keyboardDev = QT_VFB_KEYBOARD_PIPE(displayId); + d_ptr->keyboard = new QVFbKeyboardHandler(keyboardDev); + qwsServer->setDefaultKeyboard("None"); +#endif + + if (d_ptr->hdr->dataoffset >= (int)sizeof(QVFbHeader)) + d_ptr->hdr->serverVersion = QT_VERSION; + + if(d==8) { + screencols=256; + if (grayscale) { + // Build grayscale palette + for(int loopc=0;loopc<256;loopc++) { + screenclut[loopc]=qRgb(loopc,loopc,loopc); + } + } else { + // 6x6x6 216 color cube + int idx = 0; + for(int ir = 0x0; ir <= 0xff; ir+=0x33) { + for(int ig = 0x0; ig <= 0xff; ig+=0x33) { + for(int ib = 0x0; ib <= 0xff; ib+=0x33) { + screenclut[idx]=qRgb(ir, ig, ib); + idx++; + } + } + } + screencols=idx; + } + memcpy(d_ptr->hdr->clut, screenclut, sizeof(QRgb) * screencols); + d_ptr->hdr->numcols = screencols; + } else if (d == 4) { + int val = 0; + for (int idx = 0; idx < 16; idx++, val += 17) { + screenclut[idx] = qRgb(val, val, val); + } + screencols = 16; + memcpy(d_ptr->hdr->clut, screenclut, sizeof(QRgb) * screencols); + d_ptr->hdr->numcols = screencols; + } else if (d == 1) { + screencols = 2; + screenclut[1] = qRgb(0xff, 0xff, 0xff); + screenclut[0] = qRgb(0, 0, 0); + memcpy(d_ptr->hdr->clut, screenclut, sizeof(QRgb) * screencols); + d_ptr->hdr->numcols = screencols; + } + +#ifndef QT_NO_QWS_CURSOR + QScreenCursor::initSoftwareCursor(); +#endif + return true; +} + +void QVFbScreen::shutdownDevice() +{ +} + +void QVFbScreen::setMode(int ,int ,int) +{ +} + +// save the state of the graphics card +// This is needed so that e.g. we can restore the palette when switching +// between linux virtual consoles. +void QVFbScreen::save() +{ + // nothing to do. +} + +// restore the state of the graphics card. +void QVFbScreen::restore() +{ +} +void QVFbScreen::setDirty(const QRect& rect) +{ + const QRect r = rect.translated(-offset()); + d_ptr->hdr->dirty = true; + d_ptr->hdr->update = d_ptr->hdr->update.united(r); +} + +void QVFbScreen::setBrightness(int b) +{ + if (connected) { + connected->d_ptr->brightness = b; + + QVFbHeader *hdr = connected->d_ptr->hdr; + if (hdr->viewerVersion < 0x040400) // brightness not supported + return; + + const int br = connected->d_ptr->blank ? 0 : b; + if (hdr->brightness != br) { + hdr->brightness = br; + connected->setDirty(connected->region().boundingRect()); + } + } +} + +void QVFbScreen::blank(bool on) +{ + d_ptr->blank = on; + setBrightness(connected->d_ptr->brightness); +} + +#endif // QT_NO_QWS_QVFB + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qscreenvfb_qws.h b/src/gui/embedded/qscreenvfb_qws.h new file mode 100644 index 0000000000..dc5ff54a1b --- /dev/null +++ b/src/gui/embedded/qscreenvfb_qws.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCREENVFB_QWS_H +#define QSCREENVFB_QWS_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_QVFB + +class QVFbScreenPrivate; + +class Q_GUI_EXPORT QVFbScreen : public QScreen +{ +public: + explicit QVFbScreen(int display_id); + virtual ~QVFbScreen(); + virtual bool initDevice(); + virtual bool connect(const QString &displaySpec); + virtual void disconnect(); + virtual void shutdownDevice(); + virtual void save(); + virtual void restore(); + virtual void setMode(int nw,int nh,int nd); + virtual void setDirty(const QRect& r); + virtual void blank(bool); +#ifdef QTOPIA_QVFB_BRIGHTNESS + static void setBrightness(int b); +#endif + +private: + QVFbScreenPrivate *d_ptr; +}; + +#endif // QT_NO_QWS_QVFB + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCREENVFB_QWS_H diff --git a/src/gui/embedded/qsoundqss_qws.cpp b/src/gui/embedded/qsoundqss_qws.cpp new file mode 100644 index 0000000000..bd6da77d4c --- /dev/null +++ b/src/gui/embedded/qsoundqss_qws.cpp @@ -0,0 +1,1530 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsoundqss_qws.h" + +#ifndef QT_NO_SOUND +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +extern int errno; + +QT_BEGIN_NAMESPACE + +#define QT_QWS_SOUND_16BIT 1 // or 0, or undefined for always 0 +#define QT_QWS_SOUND_STEREO 1 // or 0, or undefined for always 0 + +// Zaurus SL5000D doesn't seem to return any error if setting to 44000 and it fails, +// however 44100 works, 44100 is more common that 44000. +static int sound_speed = 44100; +#ifndef QT_NO_QWS_SOUNDSERVER +extern int qws_display_id; +#endif + +static char *zeroMem = 0; + +struct QRiffChunk { + char id[4]; + quint32 size; + char data[4/*size*/]; +}; + +#if defined(QT_QWS_IPAQ) +static const int sound_fragment_size = 12; +#else +static const int sound_fragment_size = 12; +#endif +static const int sound_buffer_size = 1 << sound_fragment_size; +// nb. there will be an sound startup delay of +// 2^sound_fragment_size / sound_speed seconds. +// (eg. sound_fragment_size==12, sound_speed==44000 means 0.093s delay) + +#ifdef QT_QWS_SOUND_STEREO +static int sound_stereo=QT_QWS_SOUND_STEREO; +#else +static const int sound_stereo=0; +#endif +#ifdef QT_QWS_SOUND_16BIT +static bool sound_16bit=QT_QWS_SOUND_16BIT; +#else +static const bool sound_16bit=false; +#endif + +#ifndef QT_NO_QWS_SOUNDSERVER +class QWSSoundServerClient : public QObject { + Q_OBJECT + +public: + QWSSoundServerClient(QWS_SOCK_BASE *s, QObject* parent); + ~QWSSoundServerClient(); + +public slots: + void sendSoundCompleted(int, int); + void sendDeviceReady(int, int); + void sendDeviceError(int, int, int); + +signals: + void play(int, int, const QString&); + void play(int, int, const QString&, int, int); + void playRaw(int, int, const QString&, int, int, int, int); + + void pause(int, int); + void stop(int, int); + void resume(int, int); + void setVolume(int, int, int, int); + void setMute(int, int, bool); + + void stopAll(int); + + void playPriorityOnly(bool); + + void setSilent( bool ); + +private slots: + void tryReadCommand(); + +private: + void sendClientMessage(QString msg); + int mCurrentID; + int left, right; + bool priExist; + static int lastId; + static int nextId() { return ++lastId; } + QPointer socket; +}; + +int QWSSoundServerClient::lastId = 0; + +QWSSoundServerClient::QWSSoundServerClient(QWS_SOCK_BASE *s, QObject* parent) : + QObject( parent ) +{ + socket = s; + priExist = false; + mCurrentID = nextId(); + connect(socket,SIGNAL(readyRead()), + this,SLOT(tryReadCommand())); + connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater())); +} + +QWSSoundServerClient::~QWSSoundServerClient() +{ + if (priExist) + playPriorityOnly(false); + emit stopAll(mCurrentID); + if (socket) + socket->deleteLater(); +} + +static QString getStringTok(QString &in) +{ + int pos = in.indexOf(QLatin1Char(' ')); + QString ret; + if (pos > 0) { + ret = in.left(pos); + in = in.mid(pos+1); + } else { + ret = in; + in = QString::null; + } + return ret; +} + +static int getNumTok(QString &in) +{ + return getStringTok(in).toInt(); +} + +void QWSSoundServerClient::tryReadCommand() +{ + while ( socket->canReadLine() ) { + QString l = QString::fromAscii(socket->readLine()); + l.truncate(l.length()-1); // chomp + QString functionName = getStringTok(l); + int soundid = getNumTok(l); + if (functionName == QLatin1String("PLAY")) { + emit play(mCurrentID, soundid, l); + } else if (functionName == QLatin1String("PLAYEXTEND")) { + int volume = getNumTok(l); + int flags = getNumTok(l); + emit play(mCurrentID, soundid, l, volume, flags); + } else if (functionName == QLatin1String("PLAYRAW")) { + int chs = getNumTok(l); + int freq = getNumTok(l); + int bitspersample = getNumTok(l); + int flags = getNumTok(l); + emit playRaw(mCurrentID, soundid, l, freq, chs, bitspersample, flags); + } else if (functionName == QLatin1String("PAUSE")) { + emit pause(mCurrentID, soundid); + } else if (functionName == QLatin1String("STOP")) { + emit stop(mCurrentID, soundid); + } else if (functionName == QLatin1String("RESUME")) { + emit resume(mCurrentID, soundid); + } else if (functionName == QLatin1String("SETVOLUME")) { + int left = getNumTok(l); + int right = getNumTok(l); + emit setVolume(mCurrentID, soundid, left, right); + } else if (functionName == QLatin1String("MUTE")) { + emit setMute(mCurrentID, soundid, true); + } else if (functionName == QLatin1String("UNMUTE")) { + emit setMute(mCurrentID, soundid, false); + } else if (functionName == QLatin1String("PRIORITYONLY")) { + bool sPri = soundid != 0; + if (sPri != priExist) { + priExist = sPri; + emit playPriorityOnly(sPri); + } + } else if(functionName == QLatin1String("SILENT")) { + emit setSilent( soundid != 0 ); + } + } +} + +void QWSSoundServerClient::sendClientMessage(QString msg) +{ +#ifndef QT_NO_TEXTCODEC + QByteArray u = msg.toUtf8(); +#else + QByteArray u = msg.toLatin1(); +#endif + socket->write(u.data(), u.length()); + socket->flush(); +} + +void QWSSoundServerClient::sendSoundCompleted(int gid, int sid) +{ + if (gid == mCurrentID) + sendClientMessage(QLatin1String("SOUNDCOMPLETED ") + + QString::number(sid) + QLatin1Char('\n')); +} + +void QWSSoundServerClient::sendDeviceReady(int gid, int sid) +{ + if (gid == mCurrentID) + sendClientMessage(QLatin1String("DEVICEREADY ") + + QString::number(sid) + QLatin1Char('\n')); +} + +void QWSSoundServerClient::sendDeviceError(int gid, int sid, int err) +{ + if (gid == mCurrentID) + sendClientMessage(QLatin1String("DEVICEERROR ") + + QString::number(sid) + QLatin1Char(' ') + + QString::number(err) + QLatin1Char('\n')); +} +#endif + +static const int maxVolume = 100; +static const int runinLength = 2*sound_buffer_size; +class QWSSoundServerProvider { +public: + QWSSoundServerProvider(int w, int s) + : mWid(w), mSid(s), mMuted(false) + { + leftVolume = maxVolume>>1; + rightVolume = maxVolume>>1; + isPriority = false; + samples_due = 0; + max1 = max2 = out = 0;// = sound_buffer_size; + data = data1; + max = &max1; + sampleRunin = 0; + dev = -1; + } + + virtual ~QWSSoundServerProvider() { + } + + int groupId() const { return mWid; } + int soundId() const { return mSid; } + + void startSampleRunin() { + // inteded to provide even audio return from mute/pause/dead samples. + //sampleRunin = runinLength; // or more? + } + + + void setVolume(int lv, int rv) { + leftVolume = qMin(maxVolume, qMax(0, lv)); + rightVolume = qMin(maxVolume, qMax(0, rv)); + } + + void setMute(bool m) { mMuted = m; } + bool muted() { return mMuted; } + + void setPriority(bool p) { + if (p != isPriority) { + isPriority = p; // currently meaningless. + } + } + + + static void setPlayPriorityOnly(bool p) + { + if (p) + priorityExists++; + else + priorityExists--; + + if (priorityExists < 0) + qDebug("QSS: got more priority offs than ons"); + } + + // return -1 for file broken, give up. + // else return sampels ready for playing. + // argument is max samples server is looking for, + // in terms of current device status. + virtual int readySamples(int) = 0; + + int getSample(int off, int bps) { + + // + // 16-bit audio data is converted to native endian so that it can be scaled + // Yes, this is ugly on a BigEndian machine + // Perhaps it shouldn't be scaled at all + // + return (bps == 1) ? (data[out+off] - 128) * 128 : qToLittleEndian(((short*)data)[(out/2)+off]); + } + + int add(int* mixl, int* mixr, int count) + { + int bytesPerSample = chunkdata.wBitsPerSample >> 3; + + if ( mMuted ) { + sampleRunin -= qMin(sampleRunin,count); + while (count && (dev != -1)) { + if (out >= *max) { + // switch buffers + out = 0; + if (data == data1 && max2 != 0) { + data = data2; + max = &max2; + max1 = 0; + } else if (data == data2 && max1 != 0) { + data = data1; + max = &max1; + max2 = 0; + } else { + qDebug("QSS Read Error: both buffers empty"); + return 0; + } + } + samples_due += sound_speed; + while (count && samples_due >= chunkdata.samplesPerSec) { + samples_due -= chunkdata.samplesPerSec; + count--; + } + out += bytesPerSample * chunkdata.channels; + } + return count; + } + + // This shouldn't be the case + if ( !mixl || !mixr ) + return 0; + + int lVolNum = leftVolume, lVolDen = maxVolume; + int rVolNum = rightVolume, rVolDen = maxVolume; + if (priorityExists > 0 && !isPriority) { + lVolNum = 0; // later, make this gradually fade in and out. + lVolDen = 5; + rVolNum = 0; + rVolDen = 5; + } + + while (count && (dev != -1)) { + if (out >= *max) { + // switch buffers + out = 0; + if (data == data1 && max2 != 0) { + data = data2; + max = &max2; + max1 = 0; + } else if (data == data2 && max1 != 0) { + data = data1; + max = &max1; + max2 = 0; + } else { + qDebug("QSS Read Error: both buffers empty"); + return 0; + } + } + samples_due += sound_speed; + if (count && samples_due >= chunkdata.samplesPerSec) { + int l = getSample(0,bytesPerSample)*lVolNum/lVolDen; + int r = (chunkdata.channels == 2) ? getSample(1,bytesPerSample)*rVolNum/rVolDen : l; + if (!sound_stereo && chunkdata.channels == 2) + l += r; + if (sampleRunin) { + while (sampleRunin && count && samples_due >= chunkdata.samplesPerSec) { + mixl++; + if (sound_stereo) + mixr++; + samples_due -= chunkdata.samplesPerSec; + sampleRunin--; + count--; + } + } + while (count && samples_due >= chunkdata.samplesPerSec) { + *mixl++ += l; + if (sound_stereo) + *mixr++ += r; + samples_due -= chunkdata.samplesPerSec; + count--; + } + } + + // optimize out manipulation of sample if downsampling and we skip it + out += bytesPerSample * chunkdata.channels; + } + + return count; + } + + virtual bool finished() const = 0; + + bool equal(int wid, int sid) + { + return (wid == mWid && sid == mSid); + } + +protected: + + char * prepareBuffer( int &size) + { + // keep reading as long as there is 50 % or more room in off buffer. + if (data == data1 && (max2<<1 < sound_buffer_size)) { + size=sound_buffer_size - max2; + return (char *)data2; + } else if (data == data2 && (max1<<1 < sound_buffer_size)) { + size=sound_buffer_size - max1; + return (char *)data1; + } else { + size = 0; + return 0; + } + } + + void updateBuffer(int read) + { + // always reads to off buffer. + if (read >= 0) { + if (data == data2) { + max1 = read; + } else { + max2 = read; + } + } + } + + int devSamples() + { + int possible = (((max1+max2-out) / ((chunkdata.wBitsPerSample>>3)*chunkdata.channels)) + *sound_speed)/chunkdata.samplesPerSec; + + return possible; + } + + + struct { + qint16 formatTag; + qint16 channels; + qint32 samplesPerSec; + qint32 avgBytesPerSec; + qint16 blockAlign; + qint16 wBitsPerSample; + } chunkdata; + int dev; + int samples_due; +private: + int mWid; + int mSid; + int leftVolume; + int rightVolume; + bool isPriority; + static int priorityExists; + int *max; + uchar *data; + uchar data1[sound_buffer_size+4]; // +4 to handle badly aligned input data + uchar data2[sound_buffer_size+4]; // +4 to handle badly aligned input data + int out, max1, max2; + int sampleRunin; + bool mMuted; +}; + +int QWSSoundServerProvider::priorityExists = 0; + +class QWSSoundServerBucket : public QWSSoundServerProvider { +public: + QWSSoundServerBucket(int d, int wid, int sid) + : QWSSoundServerProvider(wid, sid) + { + dev = d; + wavedata_remaining = -1; + mFinishedRead = false; + mInsufficientSamples = false; + } + ~QWSSoundServerBucket() + { + //dev->close(); + ::close(dev); + } + bool finished() const + { + //return !max; + return mInsufficientSamples && mFinishedRead ; + } + int readySamples(int) + { + int size; + char *dest = prepareBuffer(size); + // may want to change this to something like + // if (data == data1 && max2<<1 < sound_buffer_size + // || + // data == data2 && max1<<1 < sound_buffer_size) + // so will keep filling off buffer while there is +50% space left + if (size > 0 && dest != 0) { + while ( wavedata_remaining < 0 ) { + //max = 0; + wavedata_remaining = -1; + // Keep reading chunks... + const int n = sizeof(chunk)-sizeof(chunk.data); + int nr = ::read(dev, (void*)&chunk,n); + if ( nr != n ) { + // XXX check error? or don't we care? + wavedata_remaining = 0; + mFinishedRead = true; + } else if ( qstrncmp(chunk.id,"data",4) == 0 ) { + wavedata_remaining = qToLittleEndian( chunk.size ); + + //out = max = sound_buffer_size; + + } else if ( qstrncmp(chunk.id,"RIFF",4) == 0 ) { + char d[4]; + if ( read(dev, d, 4) != 4 ) { + // XXX check error? or don't we care? + //qDebug("couldn't read riff"); + mInsufficientSamples = true; + mFinishedRead = true; + return 0; + } else if ( qstrncmp(d,"WAVE",4) != 0 ) { + // skip + if ( chunk.size > 1000000000 || lseek(dev,chunk.size-4, SEEK_CUR) == -1 ) { + //qDebug("oversized wav chunk"); + mFinishedRead = true; + } + } + } else if ( qstrncmp(chunk.id,"fmt ",4) == 0 ) { + if ( ::read(dev,(char*)&chunkdata,sizeof(chunkdata)) != sizeof(chunkdata) ) { + // XXX check error? or don't we care? + //qDebug("couldn't ready chunkdata"); + mFinishedRead = true; + } + +#define WAVE_FORMAT_PCM 1 + else + { + /* + ** Endian Fix the chuck data + */ + chunkdata.formatTag = qToLittleEndian( chunkdata.formatTag ); + chunkdata.channels = qToLittleEndian( chunkdata.channels ); + chunkdata.samplesPerSec = qToLittleEndian( chunkdata.samplesPerSec ); + chunkdata.avgBytesPerSec = qToLittleEndian( chunkdata.avgBytesPerSec ); + chunkdata.blockAlign = qToLittleEndian( chunkdata.blockAlign ); + chunkdata.wBitsPerSample = qToLittleEndian( chunkdata.wBitsPerSample ); + if ( chunkdata.formatTag != WAVE_FORMAT_PCM ) { + qWarning("WAV file: UNSUPPORTED FORMAT %d",chunkdata.formatTag); + mFinishedRead = true; + } + } + } else { + // ignored chunk + if ( chunk.size > 1000000000 || lseek(dev, chunk.size, SEEK_CUR) == -1) { + //qDebug("chunk size too big"); + mFinishedRead = true; + } + } + } + // this looks wrong. + if (wavedata_remaining <= 0) { + mFinishedRead = true; + } + + } + // may want to change this to something like + // if (data == data1 && max2<<1 < sound_buffer_size + // || + // data == data2 && max1<<1 < sound_buffer_size) + // so will keep filling off buffer while there is +50% space left + + if (wavedata_remaining) { + if (size > 0 && dest != 0) { + int read = ::read(dev, dest, qMin(size, wavedata_remaining)); + // XXX check error? or don't we care? + wavedata_remaining -= read; + updateBuffer(read); + if (read <= 0) // data unexpectidly ended + mFinishedRead = true; + } + } + int possible = devSamples(); + if (possible == 0) + mInsufficientSamples = true; + return possible; + } + +protected: + QRiffChunk chunk; + int wavedata_remaining; + bool mFinishedRead; + bool mInsufficientSamples; +}; + +class QWSSoundServerStream : public QWSSoundServerProvider { +public: + QWSSoundServerStream(int d,int c, int f, int b, + int wid, int sid) + : QWSSoundServerProvider(wid, sid) + { + chunkdata.channels = c; + chunkdata.samplesPerSec = f; + chunkdata.wBitsPerSample = b; + dev = d; + //fcntl( dev, F_SETFL, O_NONBLOCK ); + lasttime = 0; + } + + ~QWSSoundServerStream() + { + if (dev != -1) { + ::close(dev); + dev = -1; + } + } + + bool finished() const + { + return (dev == -1); + } + + + int readySamples(int) + { + int size; + char *dest = prepareBuffer(size); + if (size > 0 && dest != 0 && dev != -1) { + + int read = ::read(dev, dest, size); + if (read < 0) { + switch(errno) { + case EAGAIN: + case EINTR: + // means read may yet succeed on the next attempt + break; + default: + // unexpected error, fail. + ::close(dev); + dev = -1; + } + } else if (read == 0) { + // 0 means writer has closed dev and/or + // file is at end. + ::close(dev); + dev = -1; + } else { + updateBuffer(read); + } + } + int possible = devSamples(); + if (possible == 0) + startSampleRunin(); + return possible; + } + +protected: + time_t lasttime; +}; + +#ifndef QT_NO_QWS_SOUNDSERVER +QWSSoundServerSocket::QWSSoundServerSocket(QObject *parent) : + QWSServerSocket(QT_VFB_SOUND_PIPE(qws_display_id), parent) +{ + connect(this, SIGNAL(newConnection()), this, SLOT(newConnection())); +} + + +#ifdef QT3_SUPPORT +QWSSoundServerSocket::QWSSoundServerSocket(QObject *parent, const char *name) : + QWSServerSocket(QT_VFB_SOUND_PIPE(qws_display_id), parent) +{ + if (name) + setObjectName(QString::fromAscii(name)); + connect(this, SIGNAL(newConnection()), this, SLOT(newConnection())); +} +#endif + +void QWSSoundServerSocket::newConnection() +{ + while (QWS_SOCK_BASE *sock = nextPendingConnection()) { + QWSSoundServerClient* client = new QWSSoundServerClient(sock,this); + + connect(client, SIGNAL(play(int,int,QString)), + this, SIGNAL(playFile(int,int,QString))); + connect(client, SIGNAL(play(int,int,QString,int,int)), + this, SIGNAL(playFile(int,int,QString,int,int))); + connect(client, SIGNAL(playRaw(int,int,QString,int,int,int,int)), + this, SIGNAL(playRawFile(int,int,QString,int,int,int,int))); + + connect(client, SIGNAL(pause(int,int)), + this, SIGNAL(pauseFile(int,int))); + connect(client, SIGNAL(stop(int,int)), + this, SIGNAL(stopFile(int,int))); + connect(client, SIGNAL(playPriorityOnly(bool)), + this, SIGNAL(playPriorityOnly(bool))); + connect(client, SIGNAL(stopAll(int)), + this, SIGNAL(stopAll(int))); + connect(client, SIGNAL(resume(int,int)), + this, SIGNAL(resumeFile(int,int))); + + connect(client, SIGNAL(setSilent(bool)), + this, SIGNAL(setSilent(bool))); + + connect(client, SIGNAL(setMute(int,int,bool)), + this, SIGNAL(setMute(int,int,bool))); + connect(client, SIGNAL(setVolume(int,int,int,int)), + this, SIGNAL(setVolume(int,int,int,int))); + + connect(this, SIGNAL(soundFileCompleted(int,int)), + client, SLOT(sendSoundCompleted(int,int))); + connect(this, SIGNAL(deviceReady(int,int)), + client, SLOT(sendDeviceReady(int,int))); + connect(this, SIGNAL(deviceError(int,int,int)), + client, SLOT(sendDeviceError(int,int,int))); + } +} + +#endif + +class QWSSoundServerPrivate : public QObject { + Q_OBJECT + +public: + QWSSoundServerPrivate(QObject* parent=0, const char* name=0) : + QObject(parent) + { + timerId = 0; + if (name) + setObjectName(QString::fromAscii(name)); +#ifndef QT_NO_QWS_SOUNDSERVER + server = new QWSSoundServerSocket(this); + + connect(server, SIGNAL(playFile(int,int,QString)), + this, SLOT(playFile(int,int,QString))); + connect(server, SIGNAL(playFile(int,int,QString,int,int)), + this, SLOT(playFile(int,int,QString,int,int))); + connect(server, SIGNAL(playRawFile(int,int,QString,int,int,int,int)), + this, SLOT(playRawFile(int,int,QString,int,int,int,int))); + + connect(server, SIGNAL(pauseFile(int,int)), + this, SLOT(pauseFile(int,int))); + connect(server, SIGNAL(stopFile(int,int)), + this, SLOT(stopFile(int,int))); + connect(server, SIGNAL(stopAll(int)), + this, SLOT(stopAll(int))); + connect(server, SIGNAL(playPriorityOnly(bool)), + this, SLOT(playPriorityOnly(bool))); + connect(server, SIGNAL(resumeFile(int,int)), + this, SLOT(resumeFile(int,int))); + + connect( server, SIGNAL(setSilent(bool)), + this, SLOT(setSilent(bool))); + + connect(server, SIGNAL(setMute(int,int,bool)), + this, SLOT(setMute(int,int,bool))); + connect(server, SIGNAL(setVolume(int,int,int,int)), + this, SLOT(setVolume(int,int,int,int))); + + connect(this, SIGNAL(soundFileCompleted(int,int)), + server, SIGNAL(soundFileCompleted(int,int))); + connect(this, SIGNAL(deviceReady(int,int)), + server, SIGNAL(deviceReady(int,int))); + connect(this, SIGNAL(deviceError(int,int,int)), + server, SIGNAL(deviceError(int,int,int))); + +#endif + silent = false; + fd = -1; + unwritten = 0; + can_GETOSPACE = true; + } + + ~QWSSoundServerPrivate() + { + qDeleteAll(active); + qDeleteAll(inactive); + } + +signals: + void soundFileCompleted(int, int); + void deviceReady(int, int); + void deviceError(int, int, int); + +public slots: + void playRawFile(int wid, int sid, const QString &filename, int freq, int channels, int bitspersample, int flags); + void playFile(int wid, int sid, const QString& filename); + void playFile(int wid, int sid, const QString& filename, int v, int flags); + void checkPresetVolumes(int wid, int sid, QWSSoundServerProvider *p); + void pauseFile(int wid, int sid); + void resumeFile(int wid, int sid); + void stopFile(int wid, int sid); + void stopAll(int wid); + void setVolume(int wid, int sid, int lv, int rv); + void setMute(int wid, int sid, bool m); + void playPriorityOnly(bool p); + void sendCompletedSignals(); + void feedDevice(int fd); + void setSilent( bool enabled ); + +protected: + void timerEvent(QTimerEvent* event); + +private: + int openFile(int wid, int sid, const QString& filename); + bool openDevice(); + void closeDevice() + { + if (fd >= 0) { + ::close(fd); + fd = -1; + } + } + + QList active; + QList inactive; + struct PresetVolume { + int wid; + int sid; + int left; + int right; + bool mute; + }; + QList volumes; + struct CompletedInfo { + CompletedInfo( ) : groupId( 0 ), soundId( 0 ) { } + CompletedInfo( int _groupId, int _soundId ) : groupId( _groupId ), soundId( _soundId ) { } + int groupId; + int soundId; + }; + QList completed; + + bool silent; + + int fd; + int unwritten; + int timerId; + char* cursor; + short data[sound_buffer_size*2]; + bool can_GETOSPACE; +#ifndef QT_NO_QWS_SOUNDSERVER + QWSSoundServerSocket *server; +#endif +}; + +void QWSSoundServerPrivate::setSilent( bool enabled ) +{ + // Close output device + closeDevice(); + if( !unwritten && !active.count() ) { + sendCompletedSignals(); + } + // Stop processing audio + killTimer( timerId ); + silent = enabled; + // If audio remaining, open output device and continue processing + if( unwritten || active.count() ) { + openDevice(); + } +} + +void QWSSoundServerPrivate::timerEvent(QTimerEvent* event) +{ + // qDebug("QSS timer event"); + if( event->timerId() == timerId ) { + if (fd >= 0) + feedDevice(fd); + if (fd < 0) { + killTimer(timerId); + timerId = 0; + } + } +} + +void QWSSoundServerPrivate::playRawFile(int wid, int sid, const QString &filename, + int freq, int channels, int bitspersample, int flags) +{ +#ifdef QT_NO_QWS_SOUNDSERVER + Q_UNUSED(flags); +#endif + int f = openFile(wid, sid, filename); + if ( f ) { + QWSSoundServerStream *b = new QWSSoundServerStream(f, channels, freq, bitspersample, wid, sid); + // check preset volumes. + checkPresetVolumes(wid, sid, b); +#ifndef QT_NO_QWS_SOUNDSERVER + b->setPriority((flags & QWSSoundClient::Priority) == QWSSoundClient::Priority); +#endif + active.append(b); + emit deviceReady(wid, sid); + } +} + +void QWSSoundServerPrivate::playFile(int wid, int sid, const QString& filename) +{ + int f = openFile(wid, sid, filename); + if ( f ) { + QWSSoundServerProvider *b = new QWSSoundServerBucket(f, wid, sid); + checkPresetVolumes(wid, sid, b); + active.append( b ); + emit deviceReady(wid, sid); + } +} + +void QWSSoundServerPrivate::playFile(int wid, int sid, const QString& filename, + int v, int flags) +{ +#ifdef QT_NO_QWS_SOUNDSERVER + Q_UNUSED(flags); +#endif + int f = openFile(wid, sid, filename); + if ( f ) { + QWSSoundServerProvider *b = new QWSSoundServerBucket(f, wid, sid); + checkPresetVolumes(wid, sid, b); + b->setVolume(v, v); +#ifndef QT_NO_QWS_SOUNDSERVER + b->setPriority((flags & QWSSoundClient::Priority) == QWSSoundClient::Priority); +#endif + active.append(b); + emit deviceReady(wid, sid); + } +} + +void QWSSoundServerPrivate::checkPresetVolumes(int wid, int sid, QWSSoundServerProvider *p) +{ + QList::Iterator it = volumes.begin(); + while (it != volumes.end()) { + PresetVolume v = *it; + if (v.wid == wid && v.sid == sid) { + p->setVolume(v.left, v.right); + p->setMute(v.mute); + it = volumes.erase(it); + return; + } else { + ++it; + } + } +} + +void QWSSoundServerPrivate::pauseFile(int wid, int sid) +{ + QWSSoundServerProvider *bucket; + for (int i = 0; i < active.size(); ++i ) { + bucket = active.at(i); + if (bucket->equal(wid, sid)) { + // found bucket.... + active.removeAt(i); + inactive.append(bucket); + return; + } + } +} + +void QWSSoundServerPrivate::resumeFile(int wid, int sid) +{ + QWSSoundServerProvider *bucket; + for (int i = 0; i < inactive.size(); ++i ) { + bucket = inactive.at(i); + if (bucket->equal(wid, sid)) { + // found bucket.... + inactive.removeAt(i); + active.append(bucket); + return; + } + } +} + +void QWSSoundServerPrivate::stopFile(int wid, int sid) +{ + QWSSoundServerProvider *bucket; + for (int i = 0; i < active.size(); ++i ) { + bucket = active.at(i); + if (bucket->equal(wid, sid)) { + active.removeAt(i); + delete bucket; + return; + } + } + for (int i = 0; i < inactive.size(); ++i ) { + bucket = inactive.at(i); + if (bucket->equal(wid, sid)) { + inactive.removeAt(i); + delete bucket; + return; + } + } +} + +void QWSSoundServerPrivate::stopAll(int wid) +{ + QWSSoundServerProvider *bucket; + if (!active.isEmpty()) { + QList::Iterator it = active.begin(); + while (it != active.end()) { + bucket = *it; + if (bucket->groupId() == wid) { + it = active.erase(it); + delete bucket; + } else { + ++it; + } + } + } + if (!inactive.isEmpty()) { + QList::Iterator it = inactive.begin(); + while (it != inactive.end()) { + bucket = *it; + if (bucket->groupId() == wid) { + it = inactive.erase(it); + delete bucket; + } else { + ++it; + } + } + } +} + +void QWSSoundServerPrivate::setVolume(int wid, int sid, int lv, int rv) +{ + QWSSoundServerProvider *bucket; + for( int i = 0; i < active.size(); ++i ) { + bucket = active.at(i); + if (bucket->equal(wid, sid)) { + bucket->setVolume(lv,rv); + return; + } + } + // If gotten here, then it means wid/sid wasn't set up yet. + // first find and remove current preset volumes, then add this one. + QList::Iterator it = volumes.begin(); + while (it != volumes.end()) { + PresetVolume v = *it; + if (v.wid == wid && v.sid == sid) + it = volumes.erase(it); + else + ++it; + } + // and then add this volume + PresetVolume nv; + nv.wid = wid; + nv.sid = sid; + nv.left = lv; + nv.right = rv; + nv.mute = false; + volumes.append(nv); +} + +void QWSSoundServerPrivate::setMute(int wid, int sid, bool m) +{ + QWSSoundServerProvider *bucket; + for( int i = 0; i < active.size(); ++i ) { + bucket = active.at(i); + if (bucket->equal(wid, sid)) { + bucket->setMute(m); + return; + } + } + // if gotten here then setting is being applied before item + // is created. + QList::Iterator it = volumes.begin(); + while (it != volumes.end()) { + PresetVolume v = *it; + if (v.wid == wid && v.sid == sid) { + (*it).mute = m; + return; + } + } + if (m) { + PresetVolume nv; + nv.wid = wid; + nv.sid = sid; + nv.left = maxVolume>>1; + nv.right = maxVolume>>1; + nv.mute = true; + volumes.append(nv); + } +} + +void QWSSoundServerPrivate::playPriorityOnly(bool p) +{ + QWSSoundServerProvider::setPlayPriorityOnly(p); +} + +void QWSSoundServerPrivate::sendCompletedSignals() +{ + while( !completed.isEmpty() ) { + emit soundFileCompleted( (*completed.begin()).groupId, + (*completed.begin()).soundId ); + completed.erase( completed.begin() ); + } +} + + +int QWSSoundServerPrivate::openFile(int wid, int sid, const QString& filename) +{ + stopFile(wid, sid); // close and re-open. + int f = QT_OPEN(QFile::encodeName(filename), O_RDONLY|O_NONBLOCK); + if (f == -1) { + // XXX check ferror, check reason. + qDebug("Failed opening \"%s\"",filename.toLatin1().data()); +#ifndef QT_NO_QWS_SOUNDSERVER + emit deviceError(wid, sid, (int)QWSSoundClient::ErrOpeningFile ); +#endif + } else if ( openDevice() ) { + return f; + } +#ifndef QT_NO_QWS_SOUNDSERVER + emit deviceError(wid, sid, (int)QWSSoundClient::ErrOpeningAudioDevice ); +#endif + return 0; +} + +bool QWSSoundServerPrivate::openDevice() +{ + if (fd < 0) { + if( silent ) { + fd = QT_OPEN( "/dev/null", O_WRONLY ); + // Emulate write to audio device + int delay = 1000*(sound_buffer_size>>(sound_stereo+sound_16bit))/sound_speed/2; + timerId = startTimer(delay); + + return true; + } + // + // Don't block open right away. + // + bool openOkay = false; + if ((fd = QT_OPEN("/dev/dsp", O_WRONLY|O_NONBLOCK)) != -1) { + int flags = fcntl(fd, F_GETFL); + flags &= ~O_NONBLOCK; + openOkay = (fcntl(fd, F_SETFL, flags) == 0); + } + if (!openOkay) { + qDebug("Failed opening audio device"); + return false; + } + + // Setup soundcard at 16 bit mono + int v; + //v=0x00010000+sound_fragment_size; + // um the media player did this instead. + v=0x10000 * 4 + sound_fragment_size; + if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &v)) + qWarning("Could not set fragments to %08x",v); +#ifdef QT_QWS_SOUND_16BIT + // + // Use native endian + // Since we have manipulated the data volume the data + // is now in native format, even though its stored + // as little endian in the WAV file + // + v=AFMT_S16_NE; if (ioctl(fd, SNDCTL_DSP_SETFMT, &v)) + qWarning("Could not set format %d",v); + if (AFMT_S16_NE != v) + qDebug("Want format %d got %d", AFMT_S16_LE, v); +#else + v=AFMT_U8; if (ioctl(fd, SNDCTL_DSP_SETFMT, &v)) + qWarning("Could not set format %d",v); + if (AFMT_U8 != v) + qDebug("Want format %d got %d", AFMT_U8, v); +#endif + v=sound_stereo; if (ioctl(fd, SNDCTL_DSP_STEREO, &v)) + qWarning("Could not set stereo %d",v); + if (sound_stereo != v) + qDebug("Want stereo %d got %d", sound_stereo, v); +#ifdef QT_QWS_SOUND_STEREO + sound_stereo=v; +#endif + v=sound_speed; if (ioctl(fd, SNDCTL_DSP_SPEED, &sound_speed)) + qWarning("Could not set speed %d",v); + if (v != sound_speed) + qDebug("Want speed %d got %d", v, sound_speed); + + int delay = 1000*(sound_buffer_size>>(sound_stereo+sound_16bit)) + /sound_speed/2; + // qDebug("QSS delay: %d", delay); + timerId = startTimer(delay); + + // + // Check system volume + // + int mixerHandle = QT_OPEN( "/dev/mixer", O_RDWR|O_NONBLOCK ); + if ( mixerHandle >= 0 ) { + int volume; + ioctl( mixerHandle, MIXER_READ(0), &volume ); + close( mixerHandle ); + if ( volume < 1<<(sound_stereo+sound_16bit) ) + qDebug("Want sound at %d got %d", + 1<<(sound_stereo+sound_16bit), volume); + } else + qDebug( "get volume of audio device failed" ); + + } + return true; +} + +void QWSSoundServerPrivate::feedDevice(int fd) +{ + if ( !unwritten && active.size() == 0 ) { + closeDevice(); + sendCompletedSignals(); + return; + } else { + sendCompletedSignals(); + } + + QWSSoundServerProvider* bucket; + + // find out how much audio is possible + int available = sound_buffer_size; + QList running; + for (int i = 0; i < active.size(); ++i) { + bucket = active.at(i); + int ready = bucket->readySamples(available); + if (ready > 0) { + available = qMin(available, ready); + running.append(bucket); + } + } + + audio_buf_info info; + if (can_GETOSPACE && ioctl(fd,SNDCTL_DSP_GETOSPACE,&info)) { + can_GETOSPACE = false; + fcntl(fd, F_SETFL, O_NONBLOCK); + } + if (!can_GETOSPACE) + info.fragments = 4; // #### configurable? + if (info.fragments > 0) { + if (!unwritten) { + int left[sound_buffer_size]; + memset(left,0,available*sizeof(int)); + int right[sound_buffer_size]; + if ( sound_stereo ) + memset(right,0,available*sizeof(int)); + + if (running.size() > 0) { + // should do volume mod here in regards to each bucket to avoid flattened/bad peaks. + for (int i = 0; i < running.size(); ++i ) { + bucket = running.at(i); + int unused = bucket->add(left,right,available); + if (unused > 0) { + // this error is quite serious, as + // it will really screw up mixing. + qDebug("provider lied about samples ready"); + } + } + if ( sound_16bit ) { + short *d = (short*)data; + for (int i=0; i::Iterator it = active.begin(); + while (it != active.end()) { + bucket = *it; + if (bucket->finished()) { + completed.append(CompletedInfo(bucket->groupId(), bucket->soundId())); + it = active.erase(it); + delete bucket; + } else { + ++it; + } + } +} + + +QWSSoundServer::QWSSoundServer(QObject* parent) : + QObject(parent) +{ + d = new QWSSoundServerPrivate(this); + + connect( d, SIGNAL(soundFileCompleted(int,int)), + this, SLOT(translateSoundCompleted(int,int)) ); +} + +void QWSSoundServer::playFile( int sid, const QString& filename ) +{ + //wid == 0, as it is the server initiating rather than a client + // if wid was passable, would accidently collide with server + // sockect's wids. + d->playFile(0, sid, filename); +} + +void QWSSoundServer::pauseFile( int sid ) +{ + d->pauseFile(0, sid); +} + +void QWSSoundServer::stopFile( int sid ) +{ + d->stopFile(0, sid); +} + +void QWSSoundServer::resumeFile( int sid ) +{ + d->resumeFile(0, sid); +} + +QWSSoundServer::~QWSSoundServer() +{ + d->stopAll(0); +} + +void QWSSoundServer::translateSoundCompleted( int, int sid ) +{ + emit soundCompleted( sid ); +} + +#ifndef QT_NO_QWS_SOUNDSERVER +QWSSoundClient::QWSSoundClient(QObject* parent) : + QWSSocket(parent) +{ + connectToLocalFile(QT_VFB_SOUND_PIPE(qws_display_id)); + QObject::connect(this,SIGNAL(readyRead()), + this,SLOT(tryReadCommand())); + if( state() == QWS_SOCK_BASE::ConnectedState ) QTimer::singleShot(1, this, SIGNAL(connected())); + else QTimer::singleShot(1, this, SLOT(emitConnectionRefused())); +} + +QWSSoundClient::~QWSSoundClient( ) +{ + flush(); +} + +void QWSSoundClient::reconnect() +{ + connectToLocalFile(QT_VFB_SOUND_PIPE(qws_display_id)); + if( state() == QWS_SOCK_BASE::ConnectedState ) emit connected(); + else emit error( QTcpSocket::ConnectionRefusedError ); +} + +void QWSSoundClient::sendServerMessage(QString msg) +{ +#ifndef QT_NO_TEXTCODEC + QByteArray u = msg.toUtf8(); +#else + QByteArray u = msg.toLatin1(); +#endif + write(u.data(), u.length()); + flush(); +} + +void QWSSoundClient::play( int id, const QString& filename ) +{ + QFileInfo fi(filename); + sendServerMessage(QLatin1String("PLAY ") + + QString::number(id) + QLatin1Char(' ') + + fi.absoluteFilePath() + QLatin1Char('\n')); +} + +void QWSSoundClient::play( int id, const QString& filename, int volume, int flags) +{ + QFileInfo fi(filename); + sendServerMessage(QLatin1String("PLAYEXTEND ") + + QString::number(id) + QLatin1Char(' ') + + QString::number(volume) + QLatin1Char(' ') + + QString::number(flags) + QLatin1Char(' ') + + fi.absoluteFilePath() + QLatin1Char('\n')); +} + +void QWSSoundClient::pause( int id ) +{ + sendServerMessage(QLatin1String("PAUSE ") + + QString::number(id) + QLatin1Char('\n')); +} + +void QWSSoundClient::stop( int id ) +{ + sendServerMessage(QLatin1String("STOP ") + + QString::number(id) + QLatin1Char('\n')); +} + +void QWSSoundClient::resume( int id ) +{ + sendServerMessage(QLatin1String("RESUME ") + + QString::number(id) + QLatin1Char('\n')); +} + +void QWSSoundClient::playRaw( int id, const QString& filename, + int freq, int chs, int bitspersample, int flags) +{ + QFileInfo fi(filename); + sendServerMessage(QLatin1String("PLAYRAW ") + + QString::number(id) + QLatin1Char(' ') + + QString::number(chs) + QLatin1Char(' ') + + QString::number(freq) + QLatin1Char(' ') + + QString::number(bitspersample) + QLatin1Char(' ') + + QString::number(flags) + QLatin1Char(' ') + + fi.absoluteFilePath() + QLatin1Char('\n')); +} + +void QWSSoundClient::setMute( int id, bool m ) +{ + sendServerMessage(QLatin1String(m ? "MUTE " : "UNMUTE ") + + QString::number(id) + QLatin1Char('\n')); +} + +void QWSSoundClient::setVolume( int id, int leftVol, int rightVol ) +{ + sendServerMessage(QLatin1String("SETVOLUME ") + + QString::number(id) + QLatin1Char(' ') + + QString::number(leftVol) + QLatin1Char(' ') + + QString::number(rightVol) + QLatin1Char('\n')); +} + +void QWSSoundClient::playPriorityOnly( bool pri ) +{ + sendServerMessage(QLatin1String("PRIORITYONLY ") + + QString::number(pri ? 1 : 0) + QLatin1Char('\n')); +} + +void QWSSoundClient::setSilent( bool enable ) +{ + sendServerMessage(QLatin1String("SILENT ") + + QString::number( enable ? 1 : 0 ) + QLatin1Char('\n')); +} + +void QWSSoundClient::tryReadCommand() +{ + while ( canReadLine() ) { + QString l = QString::fromAscii(readLine()); + l.truncate(l.length()-1); // chomp + QStringList token = l.split(QLatin1Char(' ')); + if (token[0] == QLatin1String("SOUNDCOMPLETED")) { + emit soundCompleted(token[1].toInt()); + } else if (token[0] == QLatin1String("DEVICEREADY")) { + emit deviceReady(token[1].toInt()); + } else if (token[0] == QLatin1String("DEVICEERROR")) { + emit deviceError(token[1].toInt(),(DeviceErrors)token[2].toInt()); + } + } +} + +void QWSSoundClient::emitConnectionRefused() +{ + emit error( QTcpSocket::ConnectionRefusedError ); +} +#endif + +QT_END_NAMESPACE + +#include "qsoundqss_qws.moc" + +#endif // QT_NO_SOUND diff --git a/src/gui/embedded/qsoundqss_qws.h b/src/gui/embedded/qsoundqss_qws.h new file mode 100644 index 0000000000..c802a72384 --- /dev/null +++ b/src/gui/embedded/qsoundqss_qws.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOUNDQSS_QWS_H +#define QSOUNDQSS_QWS_H + +#include + +#ifndef QT_NO_SOUND + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if defined(QT_NO_NETWORK) || defined(QT_NO_DNS) +#define QT_NO_QWS_SOUNDSERVER +#endif + +#ifndef Q_OS_MAC + +class QWSSoundServerPrivate; + +class Q_GUI_EXPORT QWSSoundServer : public QObject { + Q_OBJECT +public: + explicit QWSSoundServer(QObject *parent=0); + ~QWSSoundServer(); + void playFile( int id, const QString& filename ); + void stopFile( int id ); + void pauseFile( int id ); + void resumeFile( int id ); + +Q_SIGNALS: + void soundCompleted( int ); + +private Q_SLOTS: + void translateSoundCompleted( int, int ); + +private: + QWSSoundServerPrivate* d; +}; + +#ifndef QT_NO_QWS_SOUNDSERVER +class Q_GUI_EXPORT QWSSoundClient : public QWSSocket { + Q_OBJECT +public: + + enum SoundFlags { + Priority = 0x01, + Streaming = 0x02 // currently ignored, but but could set up so both Raw and non raw can be done streaming or not. + }; + enum DeviceErrors { + ErrOpeningAudioDevice = 0x01, + ErrOpeningFile = 0x02, + ErrReadingFile = 0x04 + }; + explicit QWSSoundClient(QObject* parent=0); + ~QWSSoundClient( ); + void reconnect(); + void play( int id, const QString& filename ); + void play( int id, const QString& filename, int volume, int flags = 0 ); + void playRaw( int id, const QString&, int, int, int, int flags = 0 ); + + void pause( int id ); + void stop( int id ); + void resume( int id ); + void setVolume( int id, int left, int right ); + void setMute( int id, bool m ); + + // to be used by server only, to protect phone conversation/rings. + void playPriorityOnly(bool); + + // If silent, tell sound server to release audio device + // Otherwise, allow sound server to regain audio device + void setSilent(bool); + +Q_SIGNALS: + void soundCompleted(int); + void deviceReady(int id); + void deviceError(int id, QWSSoundClient::DeviceErrors); + +private Q_SLOTS: + void tryReadCommand(); + void emitConnectionRefused(); + +private: + void sendServerMessage(QString msg); +}; + +class QWSSoundServerSocket : public QWSServerSocket { + Q_OBJECT + +public: + explicit QWSSoundServerSocket(QObject *parent=0); +public Q_SLOTS: + void newConnection(); + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QWSSoundServerSocket(QObject *parent, const char *name); +#endif + +Q_SIGNALS: + void playFile(int, int, const QString&); + void playFile(int, int, const QString&, int, int); + void playRawFile(int, int, const QString&, int, int, int, int); + void pauseFile(int, int); + void stopFile(int, int); + void resumeFile(int, int); + void setVolume(int, int, int, int); + void setMute(int, int, bool); + + void stopAll(int); + + void playPriorityOnly(bool); + + void setSilent(bool); + + void soundFileCompleted(int, int); + void deviceReady(int, int); + void deviceError(int, int, int); +}; +#endif + +#endif // Q_OS_MAC + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SOUND + +#endif // QSOUNDQSS_QWS_H diff --git a/src/gui/embedded/qtransportauth_qws.cpp b/src/gui/embedded/qtransportauth_qws.cpp new file mode 100644 index 0000000000..81e67a9e90 --- /dev/null +++ b/src/gui/embedded/qtransportauth_qws.cpp @@ -0,0 +1,1563 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtransportauth_qws.h" +#include "qtransportauth_qws_p.h" + +#ifndef QT_NO_SXE + +#include "../../3rdparty/md5/md5.h" +#include "../../3rdparty/md5/md5.cpp" +#include "qwsutils_qws.h" +#include "qwssocket_qws.h" +#include "qwscommand_qws_p.h" +#include "qwindowsystem_qws.h" +#include "qbuffer.h" +#include "qthread.h" +#include "qabstractsocket.h" +#include "qlibraryinfo.h" +#include "qfile.h" +#include "qdebug.h" +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define BUF_SIZE 512 + +QT_BEGIN_NAMESPACE + +/*! + \internal + memset for security purposes, guaranteed not to be optimized away + http://www.faqs.org/docs/Linux-HOWTO/Secure-Programs-HOWTO.html +*/ +Q_GUI_EXPORT void *guaranteed_memset(void *v,int c,size_t n) +{ + volatile char *p = (char *)v; while (n--) *p++=c; return v; +} + +/*! + \class QTransportAuth + \internal + + \brief Authenticate a message transport. + + For performance reasons, message authentication is tied to an individual + message transport instance. For example in connection oriented transports + the authentication cookie can be cached against the connection avoiding + the overhead of authentication on every message. + + For each process there is one instance of the QTransportAuth object. + For server processes it can determine the \link secure-exe-environ.html SXE + Program Identity \endlink and provide access to policy data to determine if + the message should be forwarded for action. If not actioned, the message + may be treated as being from a flawed or malicious process. + + Retrieve the instance with the getInstance() method. The constructor is + disabled and instances of QTransportAuth should never be constructed by + calling classes. + + To make the Authentication easier to use a proxied QIODevice is provided + which uses an internal QBuffer. + + In the server code first get a pointer to a QTransportAuth::Data object + using the connectTransport() method: + + \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 0 + + Here it is asserted that the transport is trusted. See the assumptions + listed in the \link secure-exe-environ.html SXE documentation \endlink + + Then proxy in the authentication device: + + \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 1 + + In the client code it is similar. Use the connectTransport() method + just the same then proxy in the authentication device instead of the + socket in write calls: + + \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 2 +*/ + +static int hmac_md5( + unsigned char* text, /* pointer to data stream */ + int text_length, /* length of data stream */ + const unsigned char* key, /* pointer to authentication key */ + int key_length, /* length of authentication key */ + unsigned char * digest /* caller digest to be filled in */ + ); + + + +#define KEY_CACHE_SIZE 30 + +const char * const errorStrings[] = { + "pending identity verification", + "message too small to carry auth data", + "cache miss on connection oriented transport", + "no magic bytes on message", + "key not found for prog id", + "authorization key match failed", + "key out of date" +}; + +const char *QTransportAuth::errorString( const Data &d ) +{ + if (( d.status & ErrMask ) == Success ) + return "success"; + int e = d.status & ErrMask; + if ( e > OutOfDate ) + return "unknown"; + return errorStrings[e]; +} + +SxeRegistryLocker::SxeRegistryLocker( QObject *reg ) + : m_success( false ) + , m_reg( 0 ) +{ + if ( reg ) + if ( !QMetaObject::invokeMethod( reg, "lockManifest", Q_RETURN_ARG(bool, m_success) )) + m_success = false; + m_reg = reg; +} + +SxeRegistryLocker::~SxeRegistryLocker() +{ + if ( m_success ) + QMetaObject::invokeMethod( m_reg, "unlockManifest" ); +} + + +QTransportAuthPrivate::QTransportAuthPrivate() + : keyInitialised(false) + , m_packageRegistry( 0 ) +{ +} + +QTransportAuthPrivate::~QTransportAuthPrivate() +{ +} + +/*! + \internal + Construct a new QTransportAuth +*/ +QTransportAuth::QTransportAuth() : QObject(*new QTransportAuthPrivate) +{ + // qDebug( "creating transport auth" ); +} + +/*! + \internal + Destructor +*/ +QTransportAuth::~QTransportAuth() +{ + // qDebug( "deleting transport auth" ); +} + +/*! + Set the process key for this currently running Qt Extended process to + the \a authdata. \a authdata should be sizeof(struct AuthCookie) + in length and contain the key and program id. Use this method + when setting or changing the SXE identity of the current program. +*/ +void QTransportAuth::setProcessKey( const char *authdata ) +{ + Q_D(QTransportAuth); + ::memcpy(&d->authKey, authdata, sizeof(struct AuthCookie)); + QFile proc_key( QLatin1String("/proc/self/lids_key") ); + // where proc key exists use that instead + if ( proc_key.open( QIODevice::ReadOnly )) + { + qint64 kb = proc_key.read( (char*)&d->authKey.key, QSXE_KEY_LEN ); +#ifdef QTRANSPORTAUTH_DEBUG + qDebug( "Using %li bytes of /proc/%i/lids_key\n", (long int)kb, getpid() ); +#else + Q_UNUSED( kb ); +#endif + } + d->keyInitialised = true; +} + + +/*! + Apply \a key as the process key for the currently running application. + + \a prog is current ignored + + Deprecated function +*/ +void QTransportAuth::setProcessKey( const char *key, const char *prog ) +{ + Q_UNUSED(prog); + setProcessKey( key ); +#ifdef QTRANSPORTAUTH_DEBUG + char displaybuf[QSXE_KEY_LEN*2+1]; + hexstring( displaybuf, (const unsigned char *)key, QSXE_KEY_LEN ); + qDebug() << "key" << displaybuf << "set"; +#endif +} + +/*! + Register \a pr as a policy handler object. The object pointed to + by \a pr should have a slot as follows + \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 3 + All requests received by this server will then generate a call to + this slot, and may be processed for policy compliance. +*/ +void QTransportAuth::registerPolicyReceiver( QObject *pr ) +{ + // not every policy receiver needs setup - no error if this fails + QMetaObject::invokeMethod( pr, "setupPolicyCheck" ); + + connect( this, SIGNAL(policyCheck(QTransportAuth::Data&,QString)), + pr, SLOT(policyCheck(QTransportAuth::Data&,QString)), Qt::DirectConnection ); +} + +/*! + Unregister the \a pr from being a policy handler. No more policyCheck signals + are received by this object. +*/ +void QTransportAuth::unregisterPolicyReceiver( QObject *pr ) +{ + disconnect( pr ); + // not every policy receiver needs tear down - no error if this fails + QMetaObject::invokeMethod( pr, "teardownPolicyCheck" ); +} + +/*! + Record a new transport connection with \a properties and \a descriptor. + + The calling code is responsible for destroying the returned data when the + tranport connection is closed. +*/ +QTransportAuth::Data *QTransportAuth::connectTransport( unsigned char properties, int descriptor ) +{ + Data *data = new Data(properties, descriptor); + data->status = Pending; + return data; +} + +/*! + Is the transport trusted. This is true iff data written into the + transport medium cannot be intercepted or modified by another process. + This is for example true for Unix Domain Sockets, but not for shared + memory or UDP sockets. + + There is of course an underlying assumption that the kernel implementing + the transport is sound, ie it cannot be compromised by writing to + /dev/kmem or loading untrusted modules +*/ +inline bool QTransportAuth::Data::trusted() const +{ + return (bool)(properties & Trusted); +} + +/*! + Assert that the transport is trusted. + + For example with respect to shared memory, if it is ensured that no untrusted + root processes are running, and that unix permissions have been set such that + any untrusted non-root processes do not have access rights, then a shared + memory transport could be asserted to be trusted. + + \sa trusted() +*/ +inline void QTransportAuth::Data::setTrusted( bool t ) +{ + properties = t ? properties | Trusted : properties & ~Trusted; +} + +/*! + Is the transport connection oriented. This is true iff once a connection + has been accepted, and state established, then further messages over the + transport are guaranteed to have come from the original connecting entity. + This is for example true for Unix Domain Sockets, but not + for shared memory or UDP sockets. + + By extension if the transport is not trusted() then it should not be + assumed to be connection oriented, since spoofed connection information + could be created. For example if we assume the TCP/IP transport is + trusted, it can be treated as connection oriented; but this is only the + case if intervening routers are trusted. + + Connection oriented transports have authorization cached against the + connection, and thus authorization is only done at connect time. +*/ +inline bool QTransportAuth::Data::connection() const +{ + return (bool)(properties & Connection); +} + +/*! + Assert that the transport is connection oriented. + + \sa connection() +*/ +inline void QTransportAuth::Data::setConnection( bool t ) +{ + properties = t ? properties | Connection : properties & ~Connection; +} + +/*! + Return a pointer to the instance of this process's QTransportAuth object +*/ +QTransportAuth *QTransportAuth::getInstance() +{ + static QTransportAuth theInstance; + + return &theInstance; +} + +/*! + Set the full path to the key file + + Since this is normally relative to Qtopia::qpeDir() this needs to be + set within the Qt Extended framework. + + The keyfile should be protected by file permissions or by MAC rules + such that it can only be read/written by the "qpe" server process +*/ +void QTransportAuth::setKeyFilePath( const QString &path ) +{ + Q_D(QTransportAuth); + d->m_keyFilePath = path; +} + +QString QTransportAuth::keyFilePath() const +{ + Q_D(const QTransportAuth); + return d->m_keyFilePath; +} + +void QTransportAuth::setLogFilePath( const QString &path ) +{ + Q_D(QTransportAuth); + d->m_logFilePath = path; +} + +QString QTransportAuth::logFilePath() const +{ + Q_D(const QTransportAuth); + return d->m_logFilePath; +} + +void QTransportAuth::setPackageRegistry( QObject *registry ) +{ + Q_D(QTransportAuth); + d->m_packageRegistry = registry; +} + +bool QTransportAuth::isDiscoveryMode() const +{ +#if defined(SXE_DISCOVERY) + static bool checked = false; + static bool yesItIs = false; + + if ( checked ) return yesItIs; + + yesItIs = ( getenv( "SXE_DISCOVERY_MODE" ) != 0 ); + if ( yesItIs ) + { + qWarning("SXE Discovery mode on, ALLOWING ALL requests and logging to %s", + qPrintable(logFilePath())); + QFile::remove( logFilePath() ); + } + checked = true; + return yesItIs; +#else + return false; +#endif +} + +/*! + \internal + Return the authorizer device mapped to this client. Note that this + could probably all be void* instead of QWSClient* for generality. + Until the need for that rears its head its QWSClient* to save the casts. + + #### OK the need has arrived, but the public API is frozen. +*/ +QIODevice *QTransportAuth::passThroughByClient( QWSClient *client ) const +{ + Q_D(const QTransportAuth); + + if ( client == 0 ) return 0; + if ( d->buffersByClient.contains( client )) + { + return d->buffersByClient[client]; + } + // qWarning( "buffer not found for client %p", client ); + return 0; +} + +/*! + \internal + Return a QIODevice pointer (to an internal QBuffer) which can be used + to receive data after authorization on transport \a d. + + The return QIODevice will act as a pass-through. + + The data will be consumed from \a iod and forwarded on to the returned + QIODevice which can be connected to readyRead() signal handlers in + place of the original QIODevice \a iod. + + This will be called in the server process to handle incoming + authenticated requests. + + The returned QIODevice will take ownership of \a data which will be deleted + when the QIODevice is delected. + + \sa setTargetDevice() +*/ +QAuthDevice *QTransportAuth::recvBuf( QTransportAuth::Data *data, QIODevice *iod ) +{ + return new QAuthDevice( iod, data, QAuthDevice::Receive ); +} + +/*! + Return a QIODevice pointer (to an internal QBuffer) which can be used + to write data onto, for authorization on transport \a d. + + The return QIODevice will act as a pass-through. + + The data written to the return QIODevice will be forwarded on to the + returned QIODevice. In the case of a QTcpSocket, this will cause it + to send out the data with the authentication information on it. + + This will be called in the client process to generate outgoing + authenticated requests. + + The returned QIODevice will take ownership of \a data which will be deleted + when the QIODevice is delected. + + \sa setTargetDevice() +*/ +QAuthDevice *QTransportAuth::authBuf( QTransportAuth::Data *data, QIODevice *iod ) +{ + return new QAuthDevice( iod, data, QAuthDevice::Send ); +} + +const unsigned char *QTransportAuth::getClientKey( unsigned char progId ) +{ + Q_D(QTransportAuth); + return d->getClientKey( progId ); +} + +void QTransportAuth::invalidateClientKeyCache() +{ + Q_D(QTransportAuth); + d->invalidateClientKeyCache(); +} + +QMutex *QTransportAuth::getKeyFileMutex() +{ + Q_D(QTransportAuth); + return &d->keyfileMutex; +} + +/* + \internal + Respond to the destroyed(QObject*) signal of the QAuthDevice's + client object and remove it from the buffersByClient lookup hash. +*/ +void QTransportAuth::bufferDestroyed( QObject *cli ) +{ + Q_D(QTransportAuth); + if ( cli == NULL ) return; + + if ( d->buffersByClient.contains( cli )) + { + d->buffersByClient.remove( cli ); + // qDebug( "@@@@@@@ client %p removed @@@@@@@@@", cli ); + } + // qDebug( " client count %d", d->buffersByClient.count() ); +} + +bool QTransportAuth::authorizeRequest( QTransportAuth::Data &d, const QString &request ) +{ + bool isAuthorized = true; + + if ( !request.isEmpty() && request != QLatin1String("Unknown") ) + { + d.status &= QTransportAuth::ErrMask; // clear the status + emit policyCheck( d, request ); + isAuthorized = (( d.status & QTransportAuth::StatusMask ) == QTransportAuth::Allow ); + } +#if defined(SXE_DISCOVERY) + if (isDiscoveryMode()) { +#ifndef QT_NO_TEXTSTREAM + if (!logFilePath().isEmpty()) { + QFile log( logFilePath() ); + if (!log.open(QIODevice::WriteOnly | QIODevice::Append)) { + qWarning("Could not write to log in discovery mode: %s", + qPrintable(logFilePath())); + } else { + QTextStream ts( &log ); + ts << d.progId << '\t' << ( isAuthorized ? "Allow" : "Deny" ) << '\t' << request << endl; + } + } +#endif + isAuthorized = true; + } +#endif + if ( !isAuthorized ) + { + qWarning( "%s - denied: for Program Id %u [PID %d]" + , qPrintable(request), d.progId, d.processId ); + + char linkTarget[BUF_SIZE]=""; + char exeLink[BUF_SIZE]=""; + char cmdlinePath[BUF_SIZE]=""; + char cmdline[BUF_SIZE]=""; + + //get executable from /proc/pid/exe + snprintf( exeLink, BUF_SIZE, "/proc/%d/exe", d.processId ); + if ( -1 == ::readlink( exeLink, linkTarget, BUF_SIZE - 1 ) ) + { + qWarning( "SXE:- Error encountered in retrieving executable link target from /proc/%u/exe : %s", + d.processId, strerror(errno) ); + snprintf( linkTarget, BUF_SIZE, "%s", linkTarget ); + } + + //get cmdline from proc/pid/cmdline + snprintf( cmdlinePath, BUF_SIZE, "/proc/%d/cmdline", d.processId ); + int cmdlineFd = QT_OPEN( cmdlinePath, O_RDONLY ); + if ( cmdlineFd == -1 ) + { + qWarning( "SXE:- Error encountered in opening /proc/%u/cmdline: %s", + d.processId, strerror(errno) ); + snprintf( cmdline, BUF_SIZE, "%s", "Unknown" ); + } + else + { + if ( -1 == QT_READ(cmdlineFd, cmdline, BUF_SIZE - 1 ) ) + { + qWarning( "SXE:- Error encountered in reading /proc/%u/cmdline : %s", + d.processId, strerror(errno) ); + snprintf( cmdline, BUF_SIZE, "%s", "Unknown" ); + } + QT_CLOSE( cmdlineFd ); + } + + syslog( LOG_ERR | LOG_LOCAL6, "%s // PID:%u // ProgId:%u // Exe:%s // Request:%s // Cmdline:%s", + "", d.processId, d.progId, linkTarget, qPrintable(request), cmdline); + } + + return isAuthorized; +} + +inline bool __fileOpen( QFile *f ) +{ +#ifdef QTRANSPORTAUTH_DEBUG + if ( f->open( QIODevice::ReadOnly )) + { + qDebug( "Opened file: %s\n", qPrintable( f->fileName() )); + return true; + } + else + { + qWarning( "Could not open file: %s\n", qPrintable( f->fileName() )); + return false; + } +#else + return ( f->open( QIODevice::ReadOnly )); +#endif +} + +/*! + \internal + Find client keys for the \a progId. If it is cached should be very + fast, otherwise requires a read of the secret key file + + In the success case a pointer to the keys is returned. The pointer is + to storage allocated for the internal cache and must be used asap. + + The list returned is a sequence of one or more keys which match the + progId. There is no separator, each 16 byte sequence represents a key. + The sequence is followed by two iterations of the SXE magic + bytes,eg 0xBA, 0xD4, 0xD4, 0xBA, 0xBA, 0xD4, 0xD4, 0xBA + + NULL is returned in the following cases: + \list + \o the keyfiles could not be accessed - error condition + \o there was no key for the supplied program id - key auth failed + \endlist + + Note that for the keyfiles, there is multi-thread and multi-process + concurrency issues: they can be read by the qpe process when + QTransportAuth calls getClientKey to verify a request, and they can be + read or written by the packagemanager when updating package data. + + To protect against this, the keyfileMutex & SxeRegistryLocker is used. + + The sxe_installer tool can also update inode and device numbers in + the manifest file, but this only occurs outside of normal operation, + so qpe and packagemanager are never running when this occurs. +*/ +const unsigned char *QTransportAuthPrivate::getClientKey(unsigned char progId) +{ + int manifestMatchCount = 0; + struct IdBlock mr; + int total_size = 0; + char *result = 0; + char *result_ptr; + int keysFound = 0; + bool foundKey; + int keysRead = 0; + struct usr_key_entry keys_list[128]; + + if ( keyCache.contains( progId )) + return (const unsigned char *)keyCache[progId]; + + SxeRegistryLocker rlock( m_packageRegistry ); + + // ### Qt 4.3: this is hacky - see documentation for setKeyFilePath + QString manifestPath = m_keyFilePath + QLatin1String("/manifest"); + QString actualKeyPath = QLatin1String("/proc/lids/keys"); + bool noFailOnKeyMissing = true; + if ( !QFile::exists( actualKeyPath )) { + actualKeyPath = m_keyFilePath + QLatin1String( "/" QSXE_KEYFILE ); + } + QFile kf( actualKeyPath ); + QFile mn( manifestPath ); + if ( !__fileOpen( &mn )) + goto key_not_found; + // first find how much storage is needed + while ( mn.read( (char*)&mr, sizeof(struct IdBlock)) > 0 ) + if ( mr.progId == progId ) + manifestMatchCount++; + if ( manifestMatchCount == 0 ) + goto key_not_found; + if ( !__fileOpen( &kf )) + { + noFailOnKeyMissing = false; + goto key_not_found; + } + total_size = 2 * QSXE_MAGIC_BYTES + manifestMatchCount * QSXE_KEY_LEN; + result = (char*)malloc( total_size ); + Q_CHECK_PTR( result ); + mn.seek( 0 ); + result_ptr = result; + /* reading whole key array in is much more efficient, 99% case is this loop only + executes once, should not have more than 128 keyed items */ + while (( keysRead = kf.read( (char*)keys_list, sizeof(struct usr_key_entry)*128 )) > 0 ) + { + /* qDebug("PID %d: getClientKey() - read %d bytes = %d keys from %s", getpid(), keysRead, + keysRead/sizeof(struct usr_key_entry), qPrintable(actualKeyPath)); */ + keysRead /= sizeof(struct usr_key_entry); + while ( mn.read( (char*)&mr, sizeof(struct IdBlock)) > 0 ) + { + if ( mr.progId == progId ) + { + foundKey = false; + for ( int i = 0; i < keysRead; ++i ) + { + /* if ( i == 0 ) + qDebug() << " pid" << getpid() << "looking for device" << (dev_t)mr.device << "inode" << (ino_t)mr.inode; + qDebug() << " pid" << getpid() << "trying device" << keys_list[i].dev << "inode" << keys_list[i].ino; */ + if ( keys_list[i].ino == (ino_t)mr.inode && keys_list[i].dev == (dev_t)mr.device ) + { + memcpy( result_ptr, keys_list[i].key, QSXE_KEY_LEN ); + result_ptr += QSXE_KEY_LEN; + foundKey = true; + break; + } + } + if ( foundKey ) + { + keysFound++; + if ( keysFound == manifestMatchCount ) + break; + } + } + } + } + if ( result_ptr == result ) // nothing found! + goto key_not_found; + // 2 x magic bytes sentinel at end of sequence + for ( int i = 0; i < 2; ++i ) + for ( int j = 0; j < QSXE_MAGIC_BYTES; ++j ) + *result_ptr++ = magic[j]; + keyCache.insert( progId, result, total_size / 10 ); + /* qDebug( "PID %d : Found %d client keys for prog %u", getpid(), keysFound, progId ); */ + goto success_out; + +key_not_found: + if ( noFailOnKeyMissing ) // return an "empty" set of keys in this case + { + if ( result == 0 ) + { + result = (char*)malloc( 2 * QSXE_MAGIC_BYTES ); + Q_CHECK_PTR( result ); + } + result_ptr = result; + for ( int i = 0; i < 2; ++i ) + for ( int j = 0; j < QSXE_MAGIC_BYTES; ++j ) + *result_ptr++ = magic[j]; + return (unsigned char *)result; + } + qWarning( "PID %d : Not found client key for prog %u", getpid(), progId ); + if ( result ) + { + free( result ); + result = 0; + } +success_out: + if ( mn.isOpen() ) + mn.close(); + if ( kf.isOpen() ) + kf.close(); + return (unsigned char *)result; +} + +void QTransportAuthPrivate::invalidateClientKeyCache() +{ + keyfileMutex.lock(); + keyCache.clear(); + keyfileMutex.unlock(); +} + +//////////////////////////////////////////////////////////////////////// +//// +//// RequestAnalyzer definition +//// + + +RequestAnalyzer::RequestAnalyzer() + : moreData( false ) + , dataSize( 0 ) +{ +} + +RequestAnalyzer::~RequestAnalyzer() +{ +} + +/*! + Analzye the data in the\a msgQueue according to some protocol + and produce a request string for policy analysis. + + If enough data is in the queue for analysis of a complete message, + return a non-null string, and set a flag so requireMoreData() will + return false; otherwise return a null string and requireMoreData() + return true. + + The amount of bytes analyzed is then available via bytesAnalyzed(). + + A null string is also returned in the case where the message was + corrupt and could not be analyzed. In this case requireMoreData() + returns false. + +Note: this method will modify the msgQueue and pull off the data + deemed to be corrupt, in the case of corrupt data. + + In all other cases the msgQueue is left alone. The calling code + should then pull off the analyzed data. Use bytesAnalzyed() to + find how much data to pull off the queue. +*/ +QString RequestAnalyzer::analyze( QByteArray *msgQueue ) +{ +#ifdef Q_WS_QWS + dataSize = 0; + moreData = false; + QBuffer cmdBuf( msgQueue ); + cmdBuf.open( QIODevice::ReadOnly | QIODevice::Unbuffered ); + QWSCommand::Type command_type = (QWSCommand::Type)(qws_read_uint( &cmdBuf )); + QWSCommand *command = QWSCommand::factory(command_type); + // if NULL, factory will have already printed warning for bogus + // command_type just purge the bad stuff and attempt to recover + if ( command == NULL ) + { + *msgQueue = msgQueue->mid( sizeof(int) ); + return QString(); + } + QString request = QLatin1String(qws_getCommandTypeString(command_type)); +#ifndef QT_NO_COP + if ( !command->read( &cmdBuf )) + { + // not all command arrived yet - come back later + delete command; + moreData = true; + return QString(); + } + if ( command_type == QWSCommand::QCopSend ) + { + QWSQCopSendCommand *sendCommand = static_cast(command); + request += QString::fromLatin1("/QCop/%1/%2").arg( sendCommand->channel ).arg( sendCommand->message ); + } + if ( command_type == QWSCommand::QCopRegisterChannel ) + { + QWSQCopRegisterChannelCommand *registerCommand = static_cast(command); + request += QString::fromLatin1("/QCop/RegisterChannel/%1").arg( registerCommand->channel ); + } +#endif + dataSize = QWS_PROTOCOL_ITEM_SIZE( *command ); + delete command; + return request; +#else + Q_UNUSED(msgQueue); + return QString(); +#endif +} + +//////////////////////////////////////////////////////////////////////// +//// +//// AuthDevice definition +//// + +/*! + Constructs a new auth device for the transport \a data and I/O device \a parent. + + Incoming or outgoing data will be authenticated according to the auth direction \a dir. + + The auth device will take ownership of the transport \a data and delete it when the device + is destroyed. +*/ +QAuthDevice::QAuthDevice( QIODevice *parent, QTransportAuth::Data *data, AuthDirection dir ) + : QIODevice( parent ) + , d( data ) + , way( dir ) + , m_target( parent ) + , m_client( 0 ) + , m_bytesAvailable( 0 ) + , m_skipWritten( 0 ) + , analyzer( 0 ) +{ + if ( dir == Receive ) // server side + { + connect( m_target, SIGNAL(readyRead()), + this, SLOT(recvReadyRead())); + } else { + connect( m_target, SIGNAL(readyRead()), + this, SIGNAL(readyRead())); + } + connect( m_target, SIGNAL(bytesWritten(qint64)), + this, SLOT(targetBytesWritten(qint64)) ); + open( QIODevice::ReadWrite | QIODevice::Unbuffered ); +} + +QAuthDevice::~QAuthDevice() +{ + if ( analyzer ) + delete analyzer; + delete d; +} + +/*! + \internal + Store a pointer to the related device or instance which this + authorizer is proxying for +*/ +void QAuthDevice::setClient( QObject *cli ) +{ + m_client = cli; + QTransportAuth::getInstance()->d_func()->buffersByClient[cli] = this; + QObject::connect( cli, SIGNAL(destroyed(QObject*)), + QTransportAuth::getInstance(), SLOT(bufferDestroyed(QObject*)) ); + // qDebug( "@@@@@@@@@@@@ client set %p @@@@@@@@@", cli ); + // qDebug( " client count %d", QTransportAuth::getInstance()->d_func()->buffersByClient.count() ); +} + +QObject *QAuthDevice::client() const +{ + return m_client; +} + +/* + \fn void QAuthDevice::authViolation(QTransportAuth::Data &) + + This signal is emitted if an authorization failure is generated, as + described in checkAuth(); + + \sa checkAuth() +*/ + + +/* + \fn void QAuthDevice::policyCheck(QTransportAuth::Data &transport, const QString &request ) + + This signal is emitted when a transport successfully delivers a request + and gives the opportunity to either deny or accept the request. + + This signal must be connected in the same thread, ie it cannot be queued. + + As soon as all handlers connected to this signal are processed the Allow or + Deny state on the \a transport is checked, and the request is allowed or denied + accordingly. + + \sa checkAuth() +*/ + +/*! + \internal + Reimplement QIODevice writeData method. + + For client end, when the device is written to the incoming data is + processed and an authentication header calculated. This is pushed + into the target device, followed by the actual incoming data (the + payload). + + For server end, it is a fatal error to write to the device. +*/ +qint64 QAuthDevice::writeData(const char *data, qint64 len) +{ + if ( way == Receive ) // server + return m_target->write( data, len ); + // client +#ifdef QTRANSPORTAUTH_DEBUG + char displaybuf[1024]; +#endif + char header[QSXE_HEADER_LEN]; + ::memset( header, 0, QSXE_HEADER_LEN ); + qint64 bytes = 0; + if ( QTransportAuth::getInstance()->authToMessage( *d, header, data, len )) + { + m_target->write( header, QSXE_HEADER_LEN ); +#ifdef QTRANSPORTAUTH_DEBUG + hexstring( displaybuf, (const unsigned char *)header, QSXE_HEADER_LEN ); + qDebug( "%d QAuthDevice::writeData - CLIENT: Header written: %s", getpid(), displaybuf ); +#endif + m_skipWritten += QSXE_HEADER_LEN; + } + m_target->write( data, len ); + bytes += len; +#ifdef QTRANSPORTAUTH_DEBUG + int bytesToDisplay = bytes; + const unsigned char *dataptr = (const unsigned char *)data; + while ( bytesToDisplay > 0 ) + { + int amt = bytes < 500 ? bytes : 500; + hexstring( displaybuf, dataptr, amt ); + qDebug( "%d QAuthDevice::writeData - CLIENT: %s", getpid(), bytes > 0 ? displaybuf : "(null)" ); + dataptr += 500; + bytesToDisplay -= 500; + } +#endif + if ( m_target->inherits( "QAbstractSocket" )) + static_cast(m_target)->flush(); + return bytes; +} + +/*! + Reimplement from QIODevice + + Read data out of the internal message queue, reduce the queue by the amount + read. Note that the amount available is only ever the size of a command + (although a command can be very big) since we need to check at command + boundaries for new authentication headers. +*/ +qint64 QAuthDevice::readData( char *data, qint64 maxSize ) +{ + if ( way == Send ) // client + return m_target->read( data, maxSize ); + if ( msgQueue.size() == 0 ) + return 0; +#ifdef QTRANSPORTAUTH_DEBUG + char displaybuf[1024]; + hexstring( displaybuf, reinterpret_cast(msgQueue.constData()), + msgQueue.size() > 500 ? 500 : msgQueue.size() ); + qDebug() << getpid() << "QAuthDevice::readData() buffered/requested/avail" + << msgQueue.size() << maxSize << m_bytesAvailable << displaybuf; +#endif + Q_ASSERT( m_bytesAvailable <= msgQueue.size() ); + qint64 bytes = ( maxSize > m_bytesAvailable ) ? m_bytesAvailable : maxSize; + ::memcpy( data, msgQueue.constData(), bytes ); + msgQueue = msgQueue.mid( bytes ); + m_bytesAvailable -= bytes; + return bytes; +} + +/*! + \internal + Receive readyRead signal from the target recv device. In response + authorize the data, and write results out to the recvBuf() device + for processing by the application. Trigger the readyRead signal. + + Authorizing involves first checking the transport is valid, ie the + handshake has either already been done and is cached on a trusted + transport, or was valid with this message; then second passing the + string representation of the service request up to any policyReceivers + + If either of these fail, the message is denied. In discovery mode + denied messages are allowed, but the message is logged. +*/ +void QAuthDevice::recvReadyRead() +{ + qint64 bytes = m_target->bytesAvailable(); + if ( bytes <= 0 ) return; + open( QIODevice::ReadWrite | QIODevice::Unbuffered ); + QUnixSocket *usock = static_cast(m_target); + QUnixSocketMessage msg = usock->read(); + msgQueue.append( msg.bytes() ); + d->processId = msg.processId(); + // if "fragmented" packet 1/2 way through start of a command, ie + // in the QWS msg type, cant do anything, come back later when + // there's more of the packet + if ( msgQueue.size() < (int)sizeof(int) ) + { + // qDebug() << "returning: msg size too small" << msgQueue.size(); + return; + } +#ifdef QTRANSPORTAUTH_DEBUG + char displaybuf[1024]; + hexstring( displaybuf, reinterpret_cast(msgQueue.constData()), + msgQueue.size() > 500 ? 500 : msgQueue.size() ); + qDebug( "%d ***** SERVER read %lli bytes - msg %s", getpid(), bytes, displaybuf ); +#endif + + bool bufHasMessages = msgQueue.size() >= (int)sizeof(int); + while ( bufHasMessages ) + { + unsigned char saveStatus = d->status; + if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::NoSuchKey ) + { + QTransportAuth::getInstance()->authorizeRequest( *d, QLatin1String("NoSuchKey") ); + break; + } + if ( !QTransportAuth::getInstance()->authFromMessage( *d, msgQueue, msgQueue.size() )) + { + // not all arrived yet? come back later + if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::TooSmall ) + { + d->status = saveStatus; + return; + } + } + if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::NoMagic ) + { + // no msg auth header, don't change the success status for connections + if ( d->connection() ) + d->status = saveStatus; + } + else + { + // msg auth header detected and auth determined, remove hdr + msgQueue = msgQueue.mid( QSXE_HEADER_LEN ); + } + if ( !authorizeMessage() ) + break; + bufHasMessages = msgQueue.size() >= (int)sizeof(int); + } +} + +/** + \internal + Handle bytesWritten signals from the underlying target device. + We adjust the target's value for bytes that are part of auth packets. +*/ +void QAuthDevice::targetBytesWritten( qint64 bytes ) +{ + if ( m_skipWritten >= bytes ) { + m_skipWritten -= bytes; + bytes = 0; + } else if ( m_skipWritten > 0 ) { + bytes -= m_skipWritten; + m_skipWritten = 0; + } + if ( bytes > 0 ) { + emit bytesWritten( bytes ); + } +} + +/** + \internal + Pre-process the message to determine what QWS command it is. This + information is used as the "request" for the purposes of authorization. + + The request and other data on the connection (id, PID, etc.) are forwarded + to all policy listeners by emitting a signal. + + The signal must be processed synchronously because on return the allow/deny + status is used immediately to either drop or continue processing the message. +*/ +bool QAuthDevice::authorizeMessage() +{ + if ( analyzer == NULL ) + analyzer = new RequestAnalyzer(); + QString request = (*analyzer)( &msgQueue ); + if ( analyzer->requireMoreData() ) + return false; + bool isAuthorized = true; + + if ( !request.isEmpty() && request != QLatin1String("Unknown") ) + { + isAuthorized = QTransportAuth::getInstance()->authorizeRequest( *d, request ); + } + + bool moreToProcess = ( msgQueue.size() - analyzer->bytesAnalyzed() ) > (int)sizeof(int); + if ( isAuthorized ) + { +#ifdef QTRANSPORTAUTH_DEBUG + qDebug() << getpid() << "SERVER authorized: releasing" << analyzer->bytesAnalyzed() << "byte command" << request; +#endif + m_bytesAvailable = analyzer->bytesAnalyzed(); + emit QIODevice::readyRead(); + return moreToProcess; + } + else + { + msgQueue = msgQueue.mid( analyzer->bytesAnalyzed() ); + } + + return true; +} + +void QAuthDevice::setRequestAnalyzer( RequestAnalyzer *ra ) +{ + Q_ASSERT( ra ); + if ( analyzer ) + delete analyzer; + analyzer = ra; +} + +/*! + \internal + Add authentication header to the beginning of a message + + Note that the per-process auth cookie is used. This key should be rewritten in + the binary image of the executable at install time to make it unique. + + For this to be secure some mechanism (eg MAC kernel or other + permissions) must prevent other processes from reading the key. + + The buffer must have AUTH_SPACE(0) bytes spare at the beginning for the + authentication header to be added. + + Returns true if header successfully added. Will fail if the + per-process key has not yet been set with setProcessKey() +*/ +bool QTransportAuth::authToMessage( QTransportAuth::Data &d, char *hdr, const char *msg, int msgLen ) +{ + // qDebug( "authToMessage(): prog id %u", d.progId ); + // only authorize connection oriented transports once, unless key has changed + if ( d.connection() && ((d.status & QTransportAuth::ErrMask) != QTransportAuth::Pending) && + d_func()->authKey.progId == d.progId ) + return false; + d.progId = d_func()->authKey.progId; + // If Unix socket credentials are being used the key wont be set + if ( !d_func()->keyInitialised ) + return false; + unsigned char digest[QSXE_KEY_LEN]; + char *msgPtr = hdr; + // magic always goes on the beginning + for ( int m = 0; m < QSXE_MAGIC_BYTES; ++m ) + *msgPtr++ = magic[m]; + hdr[ QSXE_LEN_IDX ] = (unsigned char)msgLen; + if ( !d.trusted()) + { + // Use HMAC + int rc = hmac_md5( (unsigned char *)msg, msgLen, d_func()->authKey.key, QSXE_KEY_LEN, digest ); + if ( rc == -1 ) + return false; + memcpy( hdr + QSXE_KEY_IDX, digest, QSXE_KEY_LEN ); + } + else + { + memcpy( hdr + QSXE_KEY_IDX, d_func()->authKey.key, QSXE_KEY_LEN ); + } + + hdr[ QSXE_PROG_IDX ] = d_func()->authKey.progId; + +#ifdef QTRANSPORTAUTH_DEBUG + char keydisplay[QSXE_KEY_LEN*2+1]; + hexstring( keydisplay, d_func()->authKey.key, QSXE_KEY_LEN ); + + qDebug( "%d CLIENT Auth to message %s against prog id %u and key %s\n", + getpid(), msg, d_func()->authKey.progId, keydisplay ); +#endif + + // TODO implement sequence to prevent replay attack, not required + // for trusted transports + hdr[ QSXE_SEQ_IDX ] = 1; // dummy sequence + + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::Success; + return true; +} + + +/*! + Check authorization on the \a msg, which must be of size \a msgLen, + for the transport \a d. + + If able to determine authorization, return the program identity of + the message source in the reference \a progId, and return true. + + Otherwise return false. + + If data is being received on a socket, it may be that more data is yet + needed before authentication can proceed. + + Also the message may not be an authenticated at all. + + In these cases the method returns false to indicate authorization could + not be determined: + \list + \i The message is too small to carry the authentication data + (status TooSmall is set on the \a d transport ) + \i The 4 magic bytes are missing from the message start + (status NoMagic is set on the \a d transport ) + \i The message is too small to carry the auth + claimed payload + (status TooSmall is set on the \a d transport ) + \endlist + + If however the authentication header (preceded by the magic bytes) and + any authenticated payload is received the method will determine the + authentication status, and return true. + + In the following cases as well as returning true it will also emit + an authViolation(): + \list + \i If the program id claimed by the message is not found in the key file + (status NoSuchKey is set on the \a d transport ) + \i The authentication token failed against the claimed program id: + \list + \i in the case of trusted transports, the secret did not match + \i in the case of untrusted transports the HMAC code did not match + \endlist + (status FailMatch is set on the \a d transport ) + \endlist + + In these cases the authViolation( QTransportAuth::Data d ) signal is emitted + and the error string can be obtained from the status like this: + \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 4 +*/ +bool QTransportAuth::authFromMessage( QTransportAuth::Data &d, const char *msg, int msgLen ) +{ + if ( msgLen < QSXE_MAGIC_BYTES ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall; + return false; + } + // if no magic bytes, exit straight away + int m; + const unsigned char *mptr = reinterpret_cast(msg); + for ( m = 0; m < QSXE_MAGIC_BYTES; ++m ) + { + if ( *mptr++ != magic[m] ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoMagic; + return false; + } + } + + if ( msgLen < AUTH_SPACE(1) ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall; + return false; + } + + // At this point we know the header is at least long enough to contain valid auth + // data, however the data may be spoofed. If it is not verified then the status will + // be set to uncertified so the spoofed data will not be relied on. However we want to + // know the program id which is being reported (even if it might be spoofed) for + // policy debugging purposes. So set it here, rather than after verification. + d.progId = msg[QSXE_PROG_IDX]; + +#ifdef QTRANSPORTAUTH_DEBUG + char authhdr[QSXE_HEADER_LEN*2+1]; + hexstring( authhdr, reinterpret_cast(msg), QSXE_HEADER_LEN ); + qDebug( "%d SERVER authFromMessage(): message header is %s", + getpid(), authhdr ); +#endif + + unsigned char authLen = (unsigned char)(msg[ QSXE_LEN_IDX ]); + + if ( msgLen < AUTH_SPACE(authLen) ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall; + return false; + } + + bool isCached = d_func()->keyCache.contains( d.progId ); + const unsigned char *clientKey = d_func()->getClientKey( d.progId ); + if ( clientKey == NULL ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoSuchKey; + return false; + } + +#ifdef QTRANSPORTAUTH_DEBUG + char keydisplay[QSXE_KEY_LEN*2+1]; + hexstring( keydisplay, clientKey, QSXE_KEY_LEN ); + qDebug( "\t\tauthFromMessage(): message %s against prog id %u and key %s\n", + AUTH_DATA(msg), ((unsigned int)d.progId), keydisplay ); +#endif + + const unsigned char *auth_tok; + unsigned char digest[QSXE_KEY_LEN]; + bool multi_tok = false; + + bool need_to_recheck=false; + do + { + if ( !d.trusted()) + { + hmac_md5( AUTH_DATA(msg), authLen, clientKey, QSXE_KEY_LEN, digest ); + auth_tok = digest; + } + else + { + auth_tok = clientKey; + multi_tok = true; // 1 or more keys are in the clientKey + } + while( true ) + { + if ( memcmp( auth_tok, magic, QSXE_MAGIC_BYTES ) == 0 + && memcmp( auth_tok + QSXE_MAGIC_BYTES, magic, QSXE_MAGIC_BYTES ) == 0 ) + break; + if ( memcmp( msg + QSXE_KEY_IDX, auth_tok, QSXE_KEY_LEN ) == 0 ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::Success; + return true; + } + if ( !multi_tok ) + break; + auth_tok += QSXE_KEY_LEN; + } + //the keys cached on d.progId may not contain the binary key because the cache entry was made + //before the binary had first started, must search for client key again. + if ( isCached ) + { + d_func()->keyCache.remove(d.progId); + isCached = false; + +#ifdef QTRANSPORTAUTH_DEBUG + qDebug() << "QTransportAuth::authFromMessage(): key not found in set of keys cached" + << "against prog Id =" << d.progId << ". Re-obtaining client key. "; +#endif + clientKey = d_func()->getClientKey( d.progId ); + if ( clientKey == NULL ) + { + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoSuchKey; + return false; + } + need_to_recheck = true; + } + else + { + need_to_recheck = false; + } + } while( need_to_recheck ); + + d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::FailMatch; + qWarning() << "QTransportAuth::authFromMessage():failed authentication"; + FAREnforcer::getInstance()->logAuthAttempt( QDateTime::currentDateTime() ); + emit authViolation( d ); + return false; +} + + +#ifdef QTRANSPORTAUTH_DEBUG +/*! + sprintf into hex - dest \a buf, src \a key, \a key_len is length of key. + + The target buf should be [ key_len * 2 + 1 ] in size +*/ +void hexstring( char *buf, const unsigned char* key, size_t key_len ) +{ + unsigned int i, p; + for ( i = 0, p = 0; i < key_len; i++, p+=2 ) + { + unsigned char lo_nibble = key[i] & 0x0f; + unsigned char hi_nibble = key[i] >> 4; + buf[p] = (int)hi_nibble > 9 ? hi_nibble-10 + 'A' : hi_nibble + '0'; + buf[p+1] = (int)lo_nibble > 9 ? lo_nibble-10 + 'A' : lo_nibble + '0'; + } + buf[p] = '\0'; +} +#endif + +/* + HMAC MD5 as listed in RFC 2104 + + This code is taken from: + + http://www.faqs.org/rfcs/rfc2104.html + + with the allowance for keys other than length 16 removed, but otherwise + a straight cut-and-paste. + + The HMAC_MD5 transform looks like: + + \snippet doc/src/snippets/code/src.gui.embedded.qtransportauth_qws.cpp 5 + + \list + \i where K is an n byte key + \i ipad is the byte 0x36 repeated 64 times + \i opad is the byte 0x5c repeated 64 times + \i and text is the data being protected + \endlist + + Hardware is available with accelerated implementations of HMAC-MD5 and + HMAC-SHA1. Where this hardware is available, this routine should be + replaced with a call into the accelerated version. +*/ + +static int hmac_md5( + unsigned char* text, /* pointer to data stream */ + int text_length, /* length of data stream */ + const unsigned char* key, /* pointer to authentication key */ + int key_length, /* length of authentication key */ + unsigned char * digest /* caller digest to be filled in */ + ) +{ + MD5Context context; + unsigned char k_ipad[65]; /* inner padding - * key XORd with ipad */ + unsigned char k_opad[65]; /* outer padding - * key XORd with opad */ + int i; + + /* in this implementation key_length == 16 */ + if ( key_length != 16 ) + { + fprintf( stderr, "Key length was %d - must be 16 bytes", key_length ); + return 0; + } + + /* start out by storing key in pads */ + memset( k_ipad, 0, sizeof k_ipad ); + memset( k_opad, 0, sizeof k_opad ); + memcpy( k_ipad, key, key_length ); + memcpy( k_opad, key, key_length ); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* perform inner MD5 */ + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, text, text_length); /* then text of datagram */ + MD5Final(&context, digest); /* finish up 1st pass */ + + /* perform outer MD5 */ + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st * hash */ + MD5Final(&context, digest); /* finish up 2nd pass */ + return 1; +} + + +const int FAREnforcer::minutelyRate = 4; //allowed number of false authentication attempts per minute +const QString FAREnforcer::FARMessage = QLatin1String("FAR_Exceeded"); +const QString FAREnforcer::SxeTag = QLatin1String(""); +const int FAREnforcer::minute = 60; + +FAREnforcer::FAREnforcer():authAttempts() +{ + QDateTime nullDateTime = QDateTime(); + for (int i = 0; i < minutelyRate; i++ ) + authAttempts << nullDateTime; +} + + +FAREnforcer *FAREnforcer::getInstance() +{ + static FAREnforcer theInstance; + return &theInstance; +} + +void FAREnforcer::logAuthAttempt( QDateTime time ) +{ + QDateTime dt = authAttempts.takeFirst(); + + authAttempts.append( time ); + if ( dt.secsTo( authAttempts.last() ) <= minute ) + { +#if defined(SXE_DISCOVERY) + if ( QTransportAuth::getInstance()->isDiscoveryMode() ) { + static QBasicAtomicInt reported = Q_BASIC_ATOMIC_INITIALIZER(0); + if ( reported.testAndSetRelaxed(0,1) ) { +#ifndef QT_NO_TEXTSTREAM + QString logFilePath = QTransportAuth::getInstance()->logFilePath(); + if ( !logFilePath.isEmpty() ) { + QFile log( logFilePath ); + if ( !log.open(QIODevice::WriteOnly | QIODevice::Append) ) { + qWarning("Could not write to log in discovery mode: %s", + qPrintable(logFilePath) ); + } else { + QTextStream ts( &log ); + ts << "\t\tWarning: False Authentication Rate of " << minutelyRate << "\n" + << "\t\tserver connections/authentications per minute has been exceeded,\n" + << "\t\tno further warnings will be issued\n"; + } + } + } +#endif + reset(); + return; + } +#endif + syslog( LOG_ERR | LOG_LOCAL6, "%s %s", + qPrintable( FAREnforcer::SxeTag ), + qPrintable( FAREnforcer::FARMessage ) ); + reset(); + } +} + +void FAREnforcer::reset() +{ + QDateTime nullDateTime = QDateTime(); + for (int i = 0; i < minutelyRate; i++ ) + authAttempts[i] = nullDateTime; +} + +QT_END_NAMESPACE + +#include "moc_qtransportauth_qws_p.cpp" + +#endif // QT_NO_SXE diff --git a/src/gui/embedded/qtransportauth_qws.h b/src/gui/embedded/qtransportauth_qws.h new file mode 100644 index 0000000000..cd89e0f3aa --- /dev/null +++ b/src/gui/embedded/qtransportauth_qws.h @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTRANSPORTAUTH_QWS_H +#define QTRANSPORTAUTH_QWS_H + +#include + +#if !defined(QT_NO_SXE) || defined(SXE_INSTALLER) + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAuthDevice; +class QWSClient; +class QIODevice; +class QTransportAuthPrivate; +class QMutex; + +class Q_GUI_EXPORT QTransportAuth : public QObject +{ + Q_OBJECT +public: + static QTransportAuth *getInstance(); + + enum Result { + // Error codes + Pending = 0x00, + TooSmall = 0x01, + CacheMiss = 0x02, + NoMagic = 0x03, + NoSuchKey = 0x04, + FailMatch = 0x05, + OutOfDate = 0x06, + // reserved for expansion + Success = 0x1e, + ErrMask = 0x1f, + + // Verification codes + Allow = 0x20, + Deny = 0x40, + Ask = 0x60, + // reserved + StatusMask = 0xe0 + }; + + enum Properties { + Trusted = 0x01, + Connection = 0x02, + UnixStreamSock = 0x04, + SharedMemory = 0x08, + MessageQueue = 0x10, + UDP = 0x20, + TCP = 0x40, + UserDefined = 0x80, + TransportType = 0xfc + }; + + struct Data + { + Data() { processId = -1; } + Data( unsigned char p, int d ) + : properties( p ) + , descriptor( d ) + , processId( -1 ) + { + if (( properties & TransportType ) == TCP || + ( properties & TransportType ) == UnixStreamSock ) + properties |= Connection; + } + + unsigned char properties; + unsigned char progId; + unsigned char status; + unsigned int descriptor; // socket fd or shmget key + pid_t processId; + + bool trusted() const; + void setTrusted( bool ); + bool connection() const; + void setConnection( bool ); + }; + + static const char *errorString( const QTransportAuth::Data & ); + + QTransportAuth::Data *connectTransport( unsigned char, int ); + + QAuthDevice *authBuf( QTransportAuth::Data *, QIODevice * ); + QAuthDevice *recvBuf( QTransportAuth::Data *, QIODevice * ); + QIODevice *passThroughByClient( QWSClient * ) const; + + void setKeyFilePath( const QString & ); + QString keyFilePath() const; + const unsigned char *getClientKey( unsigned char progId ); + void invalidateClientKeyCache(); + QMutex *getKeyFileMutex(); + void setLogFilePath( const QString & ); + QString logFilePath() const; + void setPackageRegistry( QObject *registry ); + bool isDiscoveryMode() const; + void setProcessKey( const char * ); + void setProcessKey( const char *, const char * ); + void registerPolicyReceiver( QObject * ); + void unregisterPolicyReceiver( QObject * ); + + bool authToMessage( QTransportAuth::Data &d, char *hdr, const char *msg, int msgLen ); + bool authFromMessage( QTransportAuth::Data &d, const char *msg, int msgLen ); + + bool authorizeRequest( QTransportAuth::Data &d, const QString &request ); + +Q_SIGNALS: + void policyCheck( QTransportAuth::Data &, const QString & ); + void authViolation( QTransportAuth::Data & ); +private Q_SLOTS: + void bufferDestroyed( QObject * ); + +private: + // users should never construct their own + QTransportAuth(); + ~QTransportAuth(); + + friend class QAuthDevice; + Q_DECLARE_PRIVATE(QTransportAuth) +}; + +class Q_GUI_EXPORT RequestAnalyzer +{ +public: + RequestAnalyzer(); + virtual ~RequestAnalyzer(); + QString operator()( QByteArray *data ) { return analyze( data ); } + bool requireMoreData() const { return moreData; } + qint64 bytesAnalyzed() const { return dataSize; } +protected: + virtual QString analyze( QByteArray * ); + bool moreData; + qint64 dataSize; +}; + +/*! + \internal + \class QAuthDevice + + \brief Pass-through QIODevice sub-class for authentication. + + Use this class to forward on or receive forwarded data over a real + device for authentication. +*/ +class Q_GUI_EXPORT QAuthDevice : public QIODevice +{ + Q_OBJECT +public: + enum AuthDirection { + Receive, + Send + }; + QAuthDevice( QIODevice *, QTransportAuth::Data *, AuthDirection ); + ~QAuthDevice(); + void setTarget( QIODevice *t ) { m_target = t; } + QIODevice *target() const { return m_target; } + void setClient( QObject* ); + QObject *client() const; + void setRequestAnalyzer( RequestAnalyzer * ); + bool isSequential() const; + bool atEnd() const; + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + bool seek( qint64 ); + QByteArray & buffer(); + +protected: + qint64 readData( char *, qint64 ); + qint64 writeData(const char *, qint64 ); +private Q_SLOTS: + void recvReadyRead(); + void targetBytesWritten( qint64 ); +private: + bool authorizeMessage(); + + QTransportAuth::Data *d; + AuthDirection way; + QIODevice *m_target; + QObject *m_client; + QByteArray msgQueue; + qint64 m_bytesAvailable; + qint64 m_skipWritten; + + RequestAnalyzer *analyzer; +}; + +inline bool QAuthDevice::isSequential() const +{ + return true; +} + +inline bool QAuthDevice::seek( qint64 ) +{ + return false; +} + +inline bool QAuthDevice::atEnd() const +{ + return msgQueue.isEmpty(); +} + +inline qint64 QAuthDevice::bytesAvailable() const +{ + if ( way == Receive ) + return m_bytesAvailable; + else + return ( m_target ? m_target->bytesAvailable() : 0 ); +} + +inline qint64 QAuthDevice::bytesToWrite() const +{ + return msgQueue.size(); +} + +inline QByteArray &QAuthDevice::buffer() +{ + return msgQueue; +} + + + + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SXE +#endif // QTRANSPORTAUTH_QWS_H diff --git a/src/gui/embedded/qtransportauth_qws_p.h b/src/gui/embedded/qtransportauth_qws_p.h new file mode 100644 index 0000000000..9cdbaaf26a --- /dev/null +++ b/src/gui/embedded/qtransportauth_qws_p.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTRANSPORTAUTH_QWS_P_H +#define QTRANSPORTAUTH_QWS_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 + +#ifndef QT_NO_SXE + +#include "qtransportauth_qws.h" +#include "qtransportauthdefs_qws.h" +#include "qbuffer.h" + +#include +#include +#include "private/qobject_p.h" + +#include + +QT_BEGIN_NAMESPACE + +// Uncomment to generate debug output +// #define QTRANSPORTAUTH_DEBUG 1 + +#ifdef QTRANSPORTAUTH_DEBUG +void hexstring( char *buf, const unsigned char* key, size_t sz ); +#endif + +// proj id for ftok usage in sxe +#define SXE_PROJ 10022 + +/*! + \internal + memset for security purposes, guaranteed not to be optimized away + http://www.faqs.org/docs/Linux-HOWTO/Secure-Programs-HOWTO.html +*/ +void *guaranteed_memset(void *v,int c,size_t n); + +class QUnixSocketMessage; + +/*! + \internal + \class AuthCookie + Struct to carry process authentication key and id +*/ +#define QSXE_HEADER_LEN 24 + +/*! + \macro AUTH_ID + Macro to manage authentication header. Format of header is: + \table + \header \i BYTES \i CONTENT + \row \i 0-3 \i magic numbers + \row \i 4 \i length of authenticated data (max 255 bytes) + \row i\ 5 \i reserved + \row \i 6-21 \i MAC digest, or shared secret in case of simple auth + \row \i 22 \i program id + \row \i 23 \i sequence number + \endtable + Total length of the header is 24 bytes + + However this may change. Instead of coding these numbers use the AUTH_ID, + AUTH_KEY, AUTH_DATA and AUTH_SPACE macros. +*/ + +#define AUTH_ID(k) ((unsigned char)(k[QSXE_KEY_LEN])) +#define AUTH_KEY(k) ((unsigned char *)(k)) + +#define AUTH_DATA(x) (unsigned char *)((x) + QSXE_HEADER_LEN) +#define AUTH_SPACE(x) ((x) + QSXE_HEADER_LEN) +#define QSXE_LEN_IDX 4 +#define QSXE_KEY_IDX 6 +#define QSXE_PROG_IDX 22 +#define QSXE_SEQ_IDX 23 + +class SxeRegistryLocker : public QObject +{ + Q_OBJECT +public: + SxeRegistryLocker( QObject * ); + ~SxeRegistryLocker(); + bool success() const { return m_success; } +private: + bool m_success; + QObject *m_reg; +}; + +class QTransportAuthPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QTransportAuth) +public: + QTransportAuthPrivate(); + ~QTransportAuthPrivate(); + + const unsigned char *getClientKey( unsigned char progId ); + void invalidateClientKeyCache(); + + bool keyInitialised; + QString m_logFilePath; + QString m_keyFilePath; + QObject *m_packageRegistry; + AuthCookie authKey; + QCache keyCache; + QHash< QObject*, QIODevice*> buffersByClient; + QMutex keyfileMutex; +}; + +/*! + \internal + Enforces the False Authentication Rate. If more than 4 authentications + are received per minute the sxemonitor is notified that the FAR has been exceeded +*/ +class FAREnforcer +{ + public: + static FAREnforcer *getInstance(); + void logAuthAttempt( QDateTime time = QDateTime::currentDateTime() ); + void reset(); + +#ifndef TEST_FAR_ENFORCER + private: +#endif + FAREnforcer(); + FAREnforcer( const FAREnforcer & ); + FAREnforcer &operator=(FAREnforcer const & ); + + static const QString FARMessage; + static const int minutelyRate; + static const QString SxeTag; + static const int minute; + + QList authAttempts; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_SXE +#endif // QTRANSPORTAUTH_QWS_P_H + diff --git a/src/gui/embedded/qtransportauthdefs_qws.h b/src/gui/embedded/qtransportauthdefs_qws.h new file mode 100644 index 0000000000..a6d39fb425 --- /dev/null +++ b/src/gui/embedded/qtransportauthdefs_qws.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTRANSPORTAUTHDEFS_QWS_H +#define QTRANSPORTAUTHDEFS_QWS_H + +#include +#include + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#define QSXE_KEY_LEN 16 +#define QSXE_MAGIC_BYTES 4 + +// Number of bytes of each message to authenticate. Just need to ensure +// that the command at the beginning hasn't been tampered with. This value +// does not matter for trusted transports. +#define AMOUNT_TO_AUTHENTICATE 200 + +#define AUTH_ID(k) ((unsigned char)(k[QSXE_KEY_LEN])) +#define AUTH_KEY(k) ((unsigned char *)(k)) + +// must be a largish -ve number under any endianess when cast as an int +const unsigned char magic[QSXE_MAGIC_BYTES] = { 0xBA, 0xD4, 0xD4, 0xBA }; +const int magicInt = 0xBAD4D4BA; + +#define QSXE_KEYFILE "keyfile" + +/* + Header in above format, less the magic bytes. + Useful for reading off the socket +*/ +struct AuthHeader +{ + unsigned char len; + unsigned char pad; + unsigned char digest[QSXE_KEY_LEN]; + unsigned char id; + unsigned char seq; +}; + +/* + Header in a form suitable for authentication routines +*/ +struct AuthMessage +{ + AuthMessage() + { + ::memset( authData, 0, sizeof(authData) ); + ::memcpy( pad_magic, magic, QSXE_MAGIC_BYTES ); + } + unsigned char pad_magic[QSXE_MAGIC_BYTES]; + union { + AuthHeader hdr; + char authData[sizeof(AuthHeader)]; + }; + char payLoad[AMOUNT_TO_AUTHENTICATE]; +}; + +/** + Auth data as stored in _key +*/ +struct AuthCookie +{ + unsigned char key[QSXE_KEY_LEN]; + unsigned char pad; + unsigned char progId; +}; + +/* + Auth data as written to the key file - SUPERSEDED by usr_key_entry + + This is still used internally for some functions, ie the socket + related calls. +*/ +struct AuthRecord +{ + union { + AuthCookie auth; + char data[sizeof(struct AuthCookie)]; + }; + time_t change_time; +}; + +/*! + \class usr_key_entry + This comes from the SXE kernel patch file include/linux/lidsif.h + + This is the (new) data record for the key file (version 2). + + The key file is (now) either /proc/lids/keys (and the per-process + keys in /proc//lids_key) OR for desktop/development ONLY (not + for production) it is $QPEDIR/etc/keyfile + + The key file maps keys to files. + + File are identified by inode and device numbers, not paths. + + (See the "installs" file for path to inode/device mapping) +*/ +struct usr_key_entry +{ + char key[QSXE_KEY_LEN]; + ino_t ino; + dev_t dev; +}; + + +/*! + \class IdBlock + \brief Data record for the manifest file. + The manifest file maps program id's to files +*/ +struct IdBlock +{ + quint64 inode; + quint64 device; + unsigned char pad; + unsigned char progId; + unsigned short installId; + unsigned int keyOffset; + qint64 install_time; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTRANSPORTAUTHDEFS_QWS_H + diff --git a/src/gui/embedded/qunixsocket.cpp b/src/gui/embedded/qunixsocket.cpp new file mode 100644 index 0000000000..03319cbb0a --- /dev/null +++ b/src/gui/embedded/qunixsocket.cpp @@ -0,0 +1,1800 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qunixsocket_p.h" + +// #define QUNIXSOCKET_DEBUG 1 + +#include +#include +#include +#include "private/qcore_unix_p.h" // overrides QT_OPEN + +#ifdef QUNIXSOCKET_DEBUG +#include +#endif + +extern "C" { +#include +#include +#include +#include +#include +}; + +#define UNIX_PATH_MAX 108 // From unix(7) + +#ifdef QT_LINUXBASE +// LSB doesn't declare ucred +struct ucred +{ + pid_t pid; /* PID of sending process. */ + uid_t uid; /* UID of sending process. */ + gid_t gid; /* GID of sending process. */ +}; + +// LSB doesn't define the ones below +#ifndef SO_PASSCRED +# define SO_PASSCRED 16 +#endif +#ifndef SCM_CREDENTIALS +# define SCM_CREDENTIALS 0x02 +#endif +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0x40 +#endif +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0x4000 +#endif + +#endif // QT_LINUXBASE + +QT_BEGIN_NAMESPACE + +/////////////////////////////////////////////////////////////////////////////// +// class QUnixSocketRights +/////////////////////////////////////////////////////////////////////////////// +/*! + \class QUnixSocketRights + \internal + + \brief The QUnixSocketRights class encapsulates QUnixSocket rights data. + \omit + \ingroup Platform::DeviceSpecific + \ingroup Platform::OS + \ingroup Platform::Communications + \endomit + \ingroup qws + + \l QUnixSocket allows you to transfer Unix file descriptors between processes. + A file descriptor is referred to as "rights data" as it allows one process to + transfer its right to access a resource to another. + + The Unix system verifies resource permissions only when the resource is first + opened. For example, consider a file on disk readable only by the user "qt". + A process running as user "qt" will be able to open this file for reading. + If, while the process was still reading from the file, the ownership was + changed from user "qt" to user "root", the process would be allowed to + continue reading from the file, even though attempting to reopen the file + would be denied. Permissions are associated with special descriptors called + file descriptors which are returned to a process after it initially opens a + resource. + + File descriptors can be duplicated within a process through the dup(2) system + call. File descriptors can be passed between processes using the + \l QUnixSocket class in the same way. Even though the receiving process never + opened the resource directly, it has the same permissions to access it as the + process that did. + + \sa QUnixSocket + */ +struct QUnixSocketRightsPrivate : public QSharedData +{ + virtual ~QUnixSocketRightsPrivate() { +#ifdef QUNIXSOCKET_DEBUG + int closerv = +#endif + QT_CLOSE(fd); +#ifdef QUNIXSOCKET_DEBUG + if(0 != closerv) { + qDebug() << "QUnixSocketRightsPrivate: Unable to close managed" + " file descriptor (" << ::strerror(errno) << ')'; + } +#endif + } + + int fd; +}; + +/*! + Create a new QUnixSocketRights instance containing the file descriptor \a fd. + \a fd will be dup(2)'d internally, so the application is free to close \a fd + following this call. + + If the dup(2) fails, or you pass an invalid \a fd, an + \l {QUnixSocketRights::isValid()}{invalid } object will be + constructed. + + QUnixSocketRights instances are immutable and the internal file descriptor + will be shared between any copies made of this object. The system will + close(2) the file descriptor once it is no longer needed. + */ +QUnixSocketRights::QUnixSocketRights(int fd) +{ + d = new QUnixSocketRightsPrivate(); + if(-1 == fd) { + d->fd = -1; + } else { + d->fd = qt_safe_dup(fd); +#ifdef QUNIXSOCKET_DEBUG + if(-1 == d->fd) { + qDebug() << "QUnixSocketRights: Unable to duplicate fd " + << fd << " (" << ::strerror(errno) << ')'; + } +#endif + } +} + +/*! + \internal + + Construct a QUnixSocketRights instance on \a fd without dup(2)'ing the file + descriptor. + */ +QUnixSocketRights::QUnixSocketRights(int fd,int) +{ + Q_ASSERT(-1 != fd); + d = new QUnixSocketRightsPrivate(); + d->fd = fd; +} + +/*! + Destroys the QUnixSocketRights instance. + */ +QUnixSocketRights::~QUnixSocketRights() +{ +} + +/*! + Create a copy of \a other. + */ +QUnixSocketRights & +QUnixSocketRights::operator=(const QUnixSocketRights & other) +{ + d = other.d; + return *this; +} + +/*! + Create a copy of \a other. + */ +QUnixSocketRights::QUnixSocketRights(const QUnixSocketRights & other) +: d(other.d) +{ +} + +/*! + Returns true if this QUnixSocketRights instance is managing a valid file + descriptor. This method is equivalent to (-1 != peekFd()). + + \sa QUnixSocketRights::peekFd() + */ +bool QUnixSocketRights::isValid() const +{ + return d->fd != -1; +} + +/*! + Return a duplicate of the file descriptor contained in this object. If this + is an \l {QUnixSocketRights::isValid()}{invalid } object, or the + dup(2) call fails, an invalid file descriptor (-1) will be returned. + + \sa QUnixSocketRights::peekFd() + */ +int QUnixSocketRights::dupFd() const +{ + if(-1 == d->fd) return -1; + + int rv = qt_safe_dup(d->fd); + +#ifdef QUNIXSOCKET_DEBUG + if(-1 == rv) + qDebug() << "QUnixSocketRights: Unable to duplicate managed file " + "descriptor (" << ::strerror(errno) << ')'; +#endif + + return rv; +} + +/*! + Returns the file descriptor contained in this object. If this + is an \l {QUnixSocketRights::isValid()}{invalid } object an invalid + file descriptor (-1) will be returned. + + The lifetime of this file descriptor is tied to the lifetime of the + QUnixSocketRights instance. The file descriptor returned by this method + \e may be close(2)'d when the QUnixSocketRights instance is destroyed. If + you want to continue to use the file descriptor use + \l QUnixSocketRights::dupFd() instead. + + \sa QUnixSocketRights::dupFd() + */ +int QUnixSocketRights::peekFd() const +{ + return d->fd; +} + +/////////////////////////////////////////////////////////////////////////////// +// class QUnixSocketMessage +/////////////////////////////////////////////////////////////////////////////// +struct QUnixSocketMessagePrivate : public QSharedData +{ + QUnixSocketMessagePrivate() + : state(Default), vec(0), iovecLen(0), dataSize(0) {} + QUnixSocketMessagePrivate(const QByteArray & b) + : bytes(b), state(Default), vec(0), iovecLen(0), dataSize(0) {} + QUnixSocketMessagePrivate(const QByteArray & b, + const QList & r) + : bytes(b), rights(r), state(Default), vec(0), iovecLen(0), dataSize(0) {} + + int size() const { return vec ? dataSize : bytes.size(); } + void removeBytes( unsigned int ); + + QByteArray bytes; + QList rights; + + enum AncillaryDataState { + Default = 0x00, + Truncated = 0x01, + Credential = 0x02 + }; + AncillaryDataState state; + + pid_t pid; + gid_t gid; + uid_t uid; + + ::iovec *vec; + int iovecLen; // number of vectors in array + int dataSize; // total size of vectors = payload +}; + +/*! + \internal + Remove \a bytesToDequeue bytes from the front of this message +*/ +void QUnixSocketMessagePrivate::removeBytes( unsigned int bytesToDequeue ) +{ + if ( vec ) + { + ::iovec *vecPtr = vec; + if ( bytesToDequeue > (unsigned int)dataSize ) bytesToDequeue = dataSize; + while ( bytesToDequeue > 0 && iovecLen > 0 ) + { + if ( vecPtr->iov_len > bytesToDequeue ) + { + // dequeue the bytes by taking them off the front of the + // current vector. since we don't own the iovec, its okay + // to "leak" this away by pointing past it + char **base = reinterpret_cast(&(vecPtr->iov_base)); + *base += bytesToDequeue; + vecPtr->iov_len -= bytesToDequeue; + bytesToDequeue = 0; + } + else + { + // dequeue bytes by skipping a whole vector. again, its ok + // to lose the pointers to this data + bytesToDequeue -= vecPtr->iov_len; + iovecLen--; + vecPtr++; + } + } + dataSize -= bytesToDequeue; + if ( iovecLen == 0 ) vec = 0; + } + else + { + bytes.remove(0, bytesToDequeue ); + } +} + + +/*! + \class QUnixSocketMessage + \internal + + \brief The QUnixSocketMessage class encapsulates a message sent or received + through the QUnixSocket class. + \omit + \ingroup Platform::DeviceSpecific + \ingroup Platform::OS + \ingroup Platform::Communications + \endomit + \ingroup qws + + In addition to transmitting regular byte stream data, messages sent over Unix + domain sockets may have special ancillary properties. QUnixSocketMessage + instances allow programmers to retrieve and control these properties. + + Every QUnixSocketMessage sent has an associated set of credentials. A + message's credentials consist of the process id, the user id and the group id + of the sending process. Normally these credentials are set automatically for + you by the QUnixSocketMessage class and can be queried by the receiving + process using the \l QUnixSocketMessage::processId(), + \l QUnixSocketMessage::userId() and \l QUnixSocketMessage::groupId() methods + respectively. + + Advanced applications may wish to change the credentials that their message + is sent with, and may do so though the \l QUnixSocketMessage::setProcessId(), + \l QUnixSocketMessage::setUserId() and \l QUnixSocketMessage::setGroupId() + methods. The validity of these credentials is verified by the system kernel. + Only the root user can send messages with credentials that are not his own. + Sending of the message will fail for any non-root user who attempts to + fabricate credentials. Note that this failure is enforced by the system + kernel - receivers can trust the accuracy of credential data! + + Unix domain socket messages may also be used to transmit Unix file descriptors + between processes. In this context, file descriptors are known as rights data + and are encapsulated by the \l QUnixSocketRights class. Senders can set the + file descriptors to transmit using the \l QUnixSocketMessage::setRights() and + receivers can retrieve this data through a call to + \l QUnixSocketMessage::rights(). \l QUnixSocket and \l QUnixSocketRights + discuss the specific copy and ordering semantic associated with rights data. + + QUnixSocketMessage messages are sent by the \l QUnixSocket::write() method. + Like any normal network message, attempting to transmit an empty + QUnixSocketMessage will succeed, but result in a no-op. Limitations in the + Unix domain protocol semantic will cause a transmission of a + QUnixSocketMessage with rights data, but no byte data portion, to fail. + + \sa QUnixSocket QUnixSocketRights + */ + +/*! + Construct an empty QUnixSocketMessage. This instance will have not data and + no rights information. The message's credentials will be set to the + application's default credentials. + */ +QUnixSocketMessage::QUnixSocketMessage() +: d(new QUnixSocketMessagePrivate()) +{ +} + +/*! + Construct a QUnixSocketMessage with an initial data payload of \a bytes. The + message's credentials will be set to the application's default credentials. + */ +QUnixSocketMessage::QUnixSocketMessage(const QByteArray & bytes) +: d(new QUnixSocketMessagePrivate(bytes)) +{ +} + +/*! + Construct a QUnixSocketMessage with an initial data payload of \a bytes and + an initial rights payload of \a rights. The message's credentials will be set + to the application's default credentials. + + A message with rights data but an empty data payload cannot be transmitted + by the system. + */ +QUnixSocketMessage::QUnixSocketMessage(const QByteArray & bytes, + const QList & rights) +: d(new QUnixSocketMessagePrivate(bytes, rights)) +{ +} + +/*! + Create a copy of \a other. + */ +QUnixSocketMessage::QUnixSocketMessage(const QUnixSocketMessage & other) +: d(other.d) +{ +} + +/*! + \fn QUnixSocketMessage::QUnixSocketMessage(const iovec* data, int vecLen) + + Construct a QUnixSocketMessage with an initial data payload of \a + data which points to an array of \a vecLen iovec structures. The + message's credentials will be set to the application's default + credentials. + + This method can be used to avoid the overhead of copying buffers of data + and will directly send the data pointed to by \a data on the socket. It also + avoids the syscall overhead of making a number of small socket write calls, + if a number of data items can be delivered with one write. + + Caller must ensure the iovec * \a data remains valid until the message + is flushed. Caller retains ownership of the iovec structs. + */ +QUnixSocketMessage::QUnixSocketMessage(const ::iovec* data, int vecLen ) +: d(new QUnixSocketMessagePrivate()) +{ + for ( int v = 0; v < vecLen; v++ ) + d->dataSize += data[v].iov_len; + d->vec = const_cast(data); + d->iovecLen = vecLen; +} + +/*! + Assign the contents of \a other to this object. + */ +QUnixSocketMessage & QUnixSocketMessage::operator=(const QUnixSocketMessage & other) +{ + d = other.d; + return *this; +} + +/*! + Destroy this instance. + */ +QUnixSocketMessage::~QUnixSocketMessage() +{ +} + +/*! + Set the data portion of the message to \a bytes. + + \sa QUnixSocketMessage::bytes() + */ +void QUnixSocketMessage::setBytes(const QByteArray & bytes) +{ + d.detach(); + d->bytes = bytes; +} + +/*! + Set the rights portion of the message to \a rights. + + A message with rights data but an empty byte data payload cannot be + transmitted by the system. + + \sa QUnixSocketMessage::rights() + */ +void QUnixSocketMessage::setRights(const QList & rights) +{ + d.detach(); + d->rights = rights; +} + +/*! + Return the rights portion of the message. + + \sa QUnixSocketMessage::setRights() + */ +const QList & QUnixSocketMessage::rights() const +{ + return d->rights; +} + +/*! + Returns true if the rights portion of the message was truncated on reception + due to insufficient buffer size. The rights buffer size can be adjusted + through calls to the \l QUnixSocket::setRightsBufferSize() method. + \l QUnixSocket contains a discussion of the buffering and truncation + characteristics of the Unix domain protocol. + + \sa QUnixSocket QUnixSocket::setRightsBufferSize() + */ +bool QUnixSocketMessage::rightsWereTruncated() const +{ + return d->state & QUnixSocketMessagePrivate::Truncated; +} + +/*! + Return the data portion of the message. + + \sa QUnixSocketMessage::setBytes() + */ +const QByteArray & QUnixSocketMessage::bytes() const +{ + return d->bytes; +} + +/*! + Returns the process id credential associated with this message. + + \sa QUnixSocketMessage::setProcessId() + */ +pid_t QUnixSocketMessage::processId() const +{ + if(QUnixSocketMessagePrivate::Credential & d->state) + return d->pid; + else + return ::getpid(); +} + +/*! + Returns the user id credential associated with this message. + + \sa QUnixSocketMessage::setUserId() + */ +uid_t QUnixSocketMessage::userId() const +{ + if(QUnixSocketMessagePrivate::Credential & d->state) + return d->uid; + else + return ::geteuid(); +} + +/*! + Returns the group id credential associated with this message. + + \sa QUnixSocketMessage::setGroupId() + */ +gid_t QUnixSocketMessage::groupId() const +{ + if(QUnixSocketMessagePrivate::Credential & d->state) + return d->gid; + else + return ::getegid(); +} + +/*! + Set the process id credential associated with this message to \a pid. Unless + you are the root user, setting a fraudulant credential will cause this message + to fail. + + \sa QUnixSocketMessage::processId() + */ +void QUnixSocketMessage::setProcessId(pid_t pid) +{ + if(!(d->state & QUnixSocketMessagePrivate::Credential)) { + d->state = (QUnixSocketMessagePrivate::AncillaryDataState)( d->state | QUnixSocketMessagePrivate::Credential ); + d->uid = ::geteuid(); + d->gid = ::getegid(); + } + d->pid = pid; +} + +/*! + Set the user id credential associated with this message to \a uid. Unless + you are the root user, setting a fraudulant credential will cause this message + to fail. + + \sa QUnixSocketMessage::userId() + */ +void QUnixSocketMessage::setUserId(uid_t uid) +{ + if(!(d->state & QUnixSocketMessagePrivate::Credential)) { + d->state = (QUnixSocketMessagePrivate::AncillaryDataState)( d->state | QUnixSocketMessagePrivate::Credential ); + d->pid = ::getpid(); + d->gid = ::getegid(); + } + d->uid = uid; +} + +/*! + Set the group id credential associated with this message to \a gid. Unless + you are the root user, setting a fraudulant credential will cause this message + to fail. + + \sa QUnixSocketMessage::groupId() + */ +void QUnixSocketMessage::setGroupId(gid_t gid) +{ + if(!(d->state & QUnixSocketMessagePrivate::Credential)) { + d->state = (QUnixSocketMessagePrivate::AncillaryDataState)( d->state | QUnixSocketMessagePrivate::Credential ); + d->pid = ::getpid(); + d->uid = ::geteuid(); + } + d->gid = gid; +} + +/*! + Return true if this message is valid. A message with rights data but an empty + byte data payload cannot be transmitted by the system and is marked as + invalid. + */ +bool QUnixSocketMessage::isValid() const +{ + return d->rights.isEmpty() || !d->bytes.isEmpty(); +} + +/////////////////////////////////////////////////////////////////////////////// +// class QUnixSocket +/////////////////////////////////////////////////////////////////////////////// +#define QUNIXSOCKET_DEFAULT_READBUFFER 1024 +#define QUNIXSOCKET_DEFAULT_ANCILLARYBUFFER 0 + +/*! + \class QUnixSocket + \internal + + \brief The QUnixSocket class provides a Unix domain socket. + + \omit + \ingroup Platform::DeviceSpecific + \ingroup Platform::OS + \ingroup Platform::Communications + \endomit + \ingroup qws + + Unix domain sockets provide an efficient mechanism for communications between + Unix processes on the same machine. Unix domain sockets support a reliable, + stream-oriented, connection-oriented transport protocol, much like TCP + sockets. Unlike IP based sockets, the connection endpoint of a Unix domain + socket is a file on disk of type socket. + + In addition to transporting raw data bytes, Unix domain sockets are able to + transmit special ancillary data. The two types of ancillary data supported + by the QUnixSocket class are: + + \list + \o Credential Data - Allows a receiver + to reliably identify the process sending each message. + \o \l {QUnixSocketRights}{Rights Data } - Allows Unix file descriptors + to be transmitted between processes. + \endlist + + Because of the need to support ancillary data, QUnixSocket is not a QIODevice, + like QTcpSocket and QUdpSocket. Instead, QUnixSocket contains a number of + read and write methods that clients must invoke directly. Rather than + returning raw data bytes, \l QUnixSocket::read() returns \l QUnixSocketMessage + instances that encapsulate the message's byte data and any other ancillary + data. + + Ancillary data is transmitted "out of band". Every \l QUnixSocketMessage + received will have credential data associated with it that the client can + access through calls to \l QUnixSocketMessage::processId(), + \l QUnixSocketMessage::groupId() and \l QUnixSocketMessage::userId(). + Likewise, message creators can set the credential data to send through calls + to \l QUnixSocketMessage::setProcessId(), \l QUnixSocketMessage::setGroupId() + and \l QUnixSocketMessage::setUserId() respectively. The authenticity of the + credential values is verified by the system kernel and cannot be fabricated + by unprivileged processes. Only processes running as the root user can + specify credential data that does not match the sending process. + + Unix file descriptors, known as "rights data", transmitted between processes + appear as though they had been dup(2)'d between the two. As Unix + domain sockets present a continuous stream of bytes to the receiver, the + rights data - which is transmitted out of band - must be "slotted" in at some + point. The rights data is logically associated with the first byte - called + the anchor byte - of the \l QUnixSocketMessage to which they are attached. + Received rights data will be available from the + \l QUnixSocketMessage::rights() method for the \l QUnixSocketMessage + instance that contains the anchor byte. + + In addition to a \l QUnixSocket::write() that takes a \l QUnixSocketMessage + instance - allowing a client to transmit both byte and rights data - a + number of convenience overloads are provided for use when only transmitting + simple byte data. Unix requires that at least one byte of raw data be + transmitted in order to send rights data. A \l QUnixSocketMessage instance + with rights data, but no byte data, cannot be transmitted. + + Unix sockets present a stream interface, such that, for example, a single + six byte transmission might be received as two three byte messages. Rights + data, on the other hand, is conceptually transmitted as unfragmentable + datagrams. If the receiving buffer is not large enough to contain all the + transmitted rights information, the data is truncated and irretreivably lost. + Users should use the \l QUnixSocket::setRightsBufferSize() method to control + the buffer size used for this data, and develop protocols that avoid the + problem. If the buffer size is too small and rights data is truncated, + the \l QUnixSocketMessage::rightsWereTruncated() flag will be set. + + \sa QUnixSocketMessage QUnixSocketRights +*/ + +/*! + \enum QUnixSocket::SocketError + + The SocketError enumeration represents the various errors that can occur on + a Unix domain socket. The most recent error for the socket is available + through the \l QUnixSocket::error() method. + + \value NoError No error has occurred. + \value InvalidPath An invalid path endpoint was passed to + \l QUnixSocket::connect(). As defined by unix(7), invalid paths + include an empty path, or what more than 107 characters long. + \value ResourceError An error acquiring or manipulating the system's socket + resources occurred. For example, if the process runs out of available + socket descriptors, a ResourceError will occur. + \value NonexistentPath The endpoing passed to \l QUnixSocket::connect() does + not refer to a Unix domain socket entity on disk. + \value ConnectionRefused The connection to the specified endpoint was refused. + Generally this means that there is no server listening on that + endpoint. + \value UnknownError An unknown error has occurred. + \value ReadFailure An error occurred while reading bytes from the connection. + \value WriteFailure An error occurred while writing bytes into the connection. + */ + +/*! + \enum QUnixSocket::SocketState + + The SocketState enumeration represents the connection state of a QUnixSocket + instance. + + \value UnconnectedState The connection is not established. + \value ConnectedState The connection is established. + \value ClosingState The connection is being closed, following a call to + \l QUnixSocket::close(). While closing, any pending data will be + transmitted, but further writes by the application will be refused. + */ + +/* + \fn QUnixSocket::bytesWritten(qint64 bytes) + + This signal is emitted every time a payload of data has been written to the + connection. The \a bytes argument is set to the number of bytes that were + written in this payload. + + \sa QUnixSocket::readyRead() +*/ + +/* + \fn QUnixSocket::readyRead() + + This signal is emitted once every time new data is available for reading from + the connection. It will only be emitted again once new data is available. + + \sa QUnixSocket::bytesWritten() +*/ + +/*! + \fn QUnixSocket::stateChanged(SocketState socketState) + + This signal is emitted each time the socket changes connection state. + \a socketState will be set to the socket's new state. +*/ + +class QUnixSocketPrivate : public QObject { +Q_OBJECT +public: + QUnixSocketPrivate(QUnixSocket * _me) + : me(_me), fd(-1), readNotifier(0), writeNotifier(0), + state(QUnixSocket::UnconnectedState), error(QUnixSocket::NoError), + writeQueueBytes(0), messageValid(false), dataBuffer(0), + dataBufferLength(0), dataBufferCapacity(0), ancillaryBuffer(0), + ancillaryBufferCount(0), closingTimer(0) { + QObject::connect(this, SIGNAL(readyRead()), me, SIGNAL(readyRead())); + QObject::connect(this, SIGNAL(bytesWritten(qint64)), + me, SIGNAL(bytesWritten(qint64))); + } + ~QUnixSocketPrivate() + { + if(dataBuffer) + delete [] dataBuffer; + if(ancillaryBuffer) + delete [] ancillaryBuffer; + } + + enum { CausedAbort = 0x70000000 }; + + QUnixSocket * me; + + int fd; + + QSocketNotifier * readNotifier; + QSocketNotifier * writeNotifier; + + QUnixSocket::SocketState state; + QUnixSocket::SocketError error; + + QQueue writeQueue; + unsigned int writeQueueBytes; + + bool messageValid; + ::msghdr message; + inline void flushAncillary() + { + if(!messageValid) return; + ::cmsghdr * h = (::cmsghdr *)CMSG_FIRSTHDR(&(message)); + while(h) { + + if(SCM_RIGHTS == h->cmsg_type) { + int * fds = (int *)CMSG_DATA(h); + int numFds = (h->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + for(int ii = 0; ii < numFds; ++ii) + QT_CLOSE(fds[ii]); + } + + h = (::cmsghdr *)CMSG_NXTHDR(&(message), h); + } + + messageValid = false; + } + + + char * dataBuffer; + unsigned int dataBufferLength; + unsigned int dataBufferCapacity; + + char * ancillaryBuffer; + inline unsigned int ancillaryBufferCapacity() + { + return CMSG_SPACE(sizeof(::ucred)) + CMSG_SPACE(sizeof(int) * ancillaryBufferCount); + } + unsigned int ancillaryBufferCount; + + QByteArray address; + + int closingTimer; + + virtual void timerEvent(QTimerEvent *) + { + me->abort(); + killTimer(closingTimer); + closingTimer = 0; + } +signals: + void readyRead(); + void bytesWritten(qint64); + +public slots: + void readActivated(); + qint64 writeActivated(); +}; + +/*! + Construct a QUnixSocket instance, with \a parent. + + The read buffer is initially set to 1024 bytes, and the rights buffer to 0 + entries. + + \sa QUnixSocket::readBufferSize() QUnixSocket::rightsBufferSize() + */ +QUnixSocket::QUnixSocket(QObject * parent) +: QIODevice(parent), d(new QUnixSocketPrivate(this)) +{ + setOpenMode(QIODevice::NotOpen); + setReadBufferSize(QUNIXSOCKET_DEFAULT_READBUFFER); + setRightsBufferSize(QUNIXSOCKET_DEFAULT_ANCILLARYBUFFER); +} + +/*! + Construct a QUnixSocket instance, with \a parent. + + The read buffer is initially set to \a readBufferSize bytes, and the rights + buffer to \a rightsBufferSize entries. + + \sa QUnixSocket::readBufferSize() QUnixSocket::rightsBufferSize() + */ +QUnixSocket::QUnixSocket(qint64 readBufferSize, qint64 rightsBufferSize, + QObject * parent) +: QIODevice(parent), d(new QUnixSocketPrivate(this)) +{ + Q_ASSERT(readBufferSize > 0 && rightsBufferSize >= 0); + + setOpenMode(QIODevice::NotOpen); + setReadBufferSize(readBufferSize); + setRightsBufferSize(rightsBufferSize); +} + +/*! + Destroys the QUnixSocket instance. Any unsent data is discarded. + */ +QUnixSocket::~QUnixSocket() +{ + abort(); + delete d; +} + +/*! + Attempt to connect to \a path. + + This method is synchronous and will return true if the connection succeeds and + false otherwise. In the case of failure, \l QUnixSocket::error() will be set + accordingly. + + Any existing connection will be aborted, and all pending data will be + discarded. + + \sa QUnixSocket::close() QUnixSocket::abort() QUnixSocket::error() + */ +bool QUnixSocket::connect(const QByteArray & path) +{ + int _true; + int crv; +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Connect requested to '" + << path << '\''; +#endif + + abort(); // Reset any existing connection + + if(UnconnectedState != d->state) // abort() caused a signal and someone messed + // with us. We'll assume they know what + // they're doing and bail. Alternative is to + // have a special "Connecting" state + return false; + + + if(path.isEmpty() || path.size() > UNIX_PATH_MAX) { + d->error = InvalidPath; + return false; + } + + // Create the socket + d->fd = ::socket(PF_UNIX, SOCK_STREAM, 0); + if(-1 == d->fd) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to create socket (" + << strerror(errno) << ')'; +#endif + d->error = ResourceError; + goto connect_error; + } + + // Set socket options + _true = 1; + crv = ::setsockopt(d->fd, SOL_SOCKET, SO_PASSCRED, (void *)&_true, + sizeof(int)); + if(-1 == crv) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to configure socket (" + << ::strerror(errno) << ')'; +#endif + d->error = ResourceError; + + goto connect_error; + } + + // Construct our unix address + struct ::sockaddr_un addr; + addr.sun_family = AF_UNIX; + ::memcpy(addr.sun_path, path.data(), path.size()); + if(path.size() < UNIX_PATH_MAX) + addr.sun_path[path.size()] = '\0'; + + // Attempt the connect + crv = ::connect(d->fd, (sockaddr *)&addr, sizeof(sockaddr_un)); + if(-1 == crv) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to connect (" + << ::strerror(errno) << ')'; +#endif + if(ECONNREFUSED == errno) + d->error = ConnectionRefused; + else if(ENOENT == errno) + d->error = NonexistentPath; + else + d->error = UnknownError; + + goto connect_error; + } + + // We're connected! + d->address = path; + d->state = ConnectedState; + d->readNotifier = new QSocketNotifier(d->fd, QSocketNotifier::Read, d); + d->writeNotifier = new QSocketNotifier(d->fd, QSocketNotifier::Write, d); + QObject::connect(d->readNotifier, SIGNAL(activated(int)), + d, SLOT(readActivated())); + QObject::connect(d->writeNotifier, SIGNAL(activated(int)), + d, SLOT(writeActivated())); + d->readNotifier->setEnabled(true); + d->writeNotifier->setEnabled(false); + setOpenMode(QIODevice::ReadWrite); + emit stateChanged(ConnectedState); + +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Connected to " << path; +#endif + return true; + +connect_error: // Cleanup failed connection + if(-1 != d->fd) { +#ifdef QUNIXSOCKET_DEBUG + int closerv = +#endif + QT_CLOSE(d->fd); +#ifdef QUNIXSOCKET_DEBUG + if(0 != closerv) { + qDebug() << "QUnixSocket: Unable to close file descriptor after " + "failed connect (" << ::strerror(errno) << ')'; + } +#endif + } + d->fd = -1; + return false; +} + +/*! + Sets the socket descriptor to use to \a socketDescriptor, bypassing + QUnixSocket's connection infrastructure, and return true on success and false + on failure. \a socketDescriptor must be in the connected state, and must be + a Unix domain socket descriptor. Following a successful call to this method, + the QUnixSocket instance will be in the Connected state and will have assumed + ownership of \a socketDescriptor. + + Any existing connection will be aborted, and all pending data will be + discarded. + + \sa QUnixSocket::connect() +*/ +bool QUnixSocket::setSocketDescriptor(int socketDescriptor) +{ + abort(); + + if(UnconnectedState != state()) // See QUnixSocket::connect() + return false; + + // Attempt to set the socket options + if(-1 == socketDescriptor) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: User provided socket is invalid"; +#endif + d->error = ResourceError; + return false; + } + + // Set socket options + int _true = 1; + int crv = ::setsockopt(socketDescriptor, SOL_SOCKET, + SO_PASSCRED, (void *)&_true, sizeof(int)); + if(-1 == crv) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to configure client provided socket (" + << ::strerror(errno) << ')'; +#endif + d->error = ResourceError; + + return false; + } + + d->fd = socketDescriptor; + d->state = ConnectedState; + d->address = QByteArray(); + setOpenMode(QIODevice::ReadWrite); + d->readNotifier = new QSocketNotifier(d->fd, QSocketNotifier::Read, d); + d->writeNotifier = new QSocketNotifier(d->fd, QSocketNotifier::Write, d); + QObject::connect(d->readNotifier, SIGNAL(activated(int)), + d, SLOT(readActivated())); + QObject::connect(d->writeNotifier, SIGNAL(activated(int)), + d, SLOT(writeActivated())); + d->readNotifier->setEnabled(true); + d->writeNotifier->setEnabled(false); + emit stateChanged(d->state); + + return true; +} + +/*! + Returns the socket descriptor currently in use. This method will return -1 + if the QUnixSocket instance is in the UnconnectedState \l {QUnixSocket::state()}{state. } + + \sa QUnixSocket::setSocketDescriptor() + */ +int QUnixSocket::socketDescriptor() const +{ + return d->fd; +} + +/*! + Abort the connection. This will immediately disconnect (if connected) and + discard any pending data. Following a call to QUnixSocket::abort() the + object will always be in the disconnected \link QUnixSocket::state() state. + \endlink + + \sa QUnixSocket::close() +*/ +void QUnixSocket::abort() +{ + setOpenMode(QIODevice::NotOpen); + + // We want to be able to use QUnixSocket::abort() to cleanup our state but + // also preserve the error message that caused the abort. It is not + // possible to reorder code to do this: + // abort(); + // d->error = SomeError + // as QUnixSocket::abort() might emit a signal and we need the error to be + // set within that signal. So, if we want an error message to be preserved + // across a *single* call to abort(), we set the + // QUnixSocketPrivate::CausedAbort flag in the error. + if(d->error & QUnixSocketPrivate::CausedAbort) + d->error = (QUnixSocket::SocketError)(d->error & + ~QUnixSocketPrivate::CausedAbort); + else + d->error = NoError; + + if( UnconnectedState == d->state) return; + +#ifdef QUNIXSOCKET_DEBUG + int closerv = +#endif + ::close(d->fd); +#ifdef QUNIXSOCKET_DEBUG + if(0 != closerv) { + qDebug() << "QUnixSocket: Unable to close socket during abort (" + << strerror(errno) << ')'; + } +#endif + + // Reset variables + d->fd = -1; + d->state = UnconnectedState; + d->dataBufferLength = 0; + d->flushAncillary(); + d->address = QByteArray(); + if(d->readNotifier) { + d->readNotifier->setEnabled(false); + d->readNotifier->deleteLater(); + } + if(d->writeNotifier) { + d->writeNotifier->setEnabled(false); + d->writeNotifier->deleteLater(); + } + d->readNotifier = 0; + d->writeNotifier = 0; + d->writeQueue.clear(); + d->writeQueueBytes = 0; + if(d->closingTimer) { + d->killTimer(d->closingTimer); + } + d->closingTimer = 0; + emit stateChanged(d->state); +} + +/*! + Close the connection. The instance will enter the Closing + \l {QUnixSocket::state()}{state } until all pending data has been + transmitted, at which point it will enter the Unconnected state. + + Even if there is no pending data for transmission, the object will never + jump directly to Disconnect without first passing through the + Closing state. + + \sa QUnixSocket::abort() + */ +void QUnixSocket::close() +{ + if(ConnectedState != state()) return; + + d->state = ClosingState; + if(d->writeQueue.isEmpty()) { + d->closingTimer = d->startTimer(0); // Start a timer to "fake" + // completing writes + } + emit stateChanged(d->state); +} + +/*! + This function writes as much as possible from the internal write buffer to + the underlying socket, without blocking. If any data was written, this + function returns true; otherwise false is returned. +*/ +// Note! docs partially copied from QAbstractSocket::flush() +bool QUnixSocket::flush() +{ + // This needs to have the same semantics as QAbstractSocket, if it is to + // be used interchangeably with that class. + if (d->writeQueue.isEmpty()) + return false; + + d->writeActivated(); + return true; +} + +/*! + Returns the last error to have occurred on this object. This method is not + destructive, so multiple calls to QUnixSocket::error() will return the same + value. The error is only reset by a call to \l QUnixSocket::connect() or + \l QUnixSocket::abort() + */ +QUnixSocket::SocketError QUnixSocket::error() const +{ + return (QUnixSocket::SocketError) + (d->error & ~QUnixSocketPrivate::CausedAbort); +} + +/*! + Returns the connection state of this instance. + */ +QUnixSocket::SocketState QUnixSocket::state() const +{ + return d->state; +} + +/*! + Returns the Unix path address passed to \l QUnixSocket::connect(). This + method will return an empty path if the object is in the Unconnected + \l {QUnixSocket::state()}{state } or was connected through a call + to \l QUnixSocket::setSocketDescriptor() + + \sa QUnixSocket::connect() QUnixSocket::setSocketDescriptor() + */ +QByteArray QUnixSocket::address() const +{ + return d->address; +} + +/*! + Returns the number of bytes available for immediate retrieval through a call + to \l QUnixSocket::read(). + */ +qint64 QUnixSocket::bytesAvailable() const +{ + return QIODevice::bytesAvailable() + d->dataBufferLength; +} + +/*! + Returns the number of enqueued bytes still to be written to the socket. + */ +qint64 QUnixSocket::bytesToWrite() const +{ + return d->writeQueueBytes; +} + +/*! + Returns the size of the read buffer in bytes. The read buffer size + determines the amount of byte data that can be read from the socket in one go. + The read buffer size caps the maximum value that can be returned by + \l QUnixSocket::bytesAvailable() and will always be greater than zero. By + default, the read buffer size is 1024 bytes. + + The size of the read buffer is independent of the rights buffer, which can be + queried by \l QUnixSocket::rightsBufferSize(). + + \sa QUnixSocket::setReadBufferSize() + */ +qint64 QUnixSocket::readBufferSize() const +{ + return d->dataBufferCapacity; +} + +/*! + Sets the \a size of the socket's read buffer in bytes. + + The size of the read buffer is independent of the rights buffer, which can be + set by \l QUnixSocket::setRightsBufferSize(). + + Attempting to reduce the buffer size while bytes are available for reading + (ie. while the buffer is in use) will fail. + + \sa QUnixSocket::readBufferSize() + */ +void QUnixSocket::setReadBufferSize(qint64 size) +{ + Q_ASSERT(size > 0); + if(size == d->dataBufferCapacity || d->dataBufferLength) return; + if(d->dataBuffer) delete [] d->dataBuffer; + d->dataBuffer = new char[size]; + d->dataBufferCapacity = size; +} + +/*! + Returns the size of the rights buffer in rights entries. The rights buffer + size determines the number of rights transferences that can be received in + any message. Unlike byte stream data which can be fragmented into many + smaller messages if the \link QUnixSocket::readBufferSize() read buffer + \endlink is not large enough to contain all the available data, rights data + is transmitted as unfragmentable datagrams. If the rights buffer is not + large enough to contain this unfragmentable datagram, the datagram will be + truncated and rights data irretrievably lost. If truncation occurs, the + \l QUnixSocketMessage::rightsWereTruncated() flag will be set. By default + the rights buffer size is 0 entries - rights data cannot be received. + + The size of the rights buffer is independent of the read buffer, which can be + queried by \l QUnixSocket::readBufferSize(). + + \sa QUnixSocket::setRightsBufferSize() + */ +qint64 QUnixSocket::rightsBufferSize() const +{ + return d->ancillaryBufferCount; +} + +/*! + Sets the \a size of the socket's rights buffer in rights entries. + + The size of the rights buffer is independent of the read buffer, which can be + set by \l QUnixSocket::setReadBufferSize(). + + Attempting to reduce the buffer size while bytes are available for reading + (ie. while the buffer is in use) will fail. + + \sa QUnixSocket::rightsBufferSize() + */ +void QUnixSocket::setRightsBufferSize(qint64 size) +{ + Q_ASSERT(size >= 0); + + if((size == d->ancillaryBufferCount || d->dataBufferLength) && + d->ancillaryBuffer) + return; + + qint64 byteSize = CMSG_SPACE(sizeof(::ucred)) + + CMSG_SPACE(size * sizeof(int)); + + if(d->ancillaryBuffer) delete [] d->ancillaryBuffer; + d->ancillaryBuffer = new char[byteSize]; + d->ancillaryBufferCount = size; +} + +/*! + \overload + + Writes \a socketdata to the socket. In addition to failing if the socket + is not in the Connected state, writing will fail if \a socketdata is + \l {QUnixSocketMessage::isValid()}{invalid. } + + Writes through the QUnixSocket class are asynchronous. Rather than being + written immediately, data is enqueued and written once the application + reenters the Qt event loop and the socket becomes available for writing. + Thus, this method will only fail if the socket is not in the Connected state + - it is illegal to attempt a write on a Unconnected or Closing socket. + + Applications can monitor the progress of data writes through the + \l QUnixSocket::bytesWritten() signal and \l QUnixSocket::bytesToWrite() + method. + + \sa QUnixSocketMessage + */ +qint64 QUnixSocket::write(const QUnixSocketMessage & socketdata) +{ + if(ConnectedState != state() || !socketdata.isValid()) return -1; + if(socketdata.d->size() == 0) return 0; + + d->writeQueue.enqueue(socketdata); + d->writeQueueBytes += socketdata.d->size(); + d->writeNotifier->setEnabled(true); + + return socketdata.d->size(); +} + +/*! + Return the next available message, or an empty message if none is available. + + To avoid retrieving empty messages, applications should connect to the + \l QUnixSocket::readyRead() signal to be notified when new messages are + available or periodically poll the \l QUnixSocket::bytesAvailable() method. + + \sa QUnixSocket::readyRead() QUnixSocket::bytesAvailable() + */ +QUnixSocketMessage QUnixSocket::read() +{ + QUnixSocketMessage data; + if(!d->dataBufferLength) + return data; + + data.d->state = QUnixSocketMessagePrivate::Credential; + + // Bytes are easy + data.setBytes(QByteArray(d->dataBuffer, d->dataBufferLength)); + + // Extract ancillary data + QList a; + + ::cmsghdr * h = (::cmsghdr *)CMSG_FIRSTHDR(&(d->message)); + while(h) { + + if(SCM_CREDENTIALS == h->cmsg_type) { + ::ucred * cred = (::ucred *)CMSG_DATA(h); +#ifdef QUNIXSOCKET_DEBUG + qDebug( "Credentials recd: pid %lu - gid %lu - uid %lu", + cred->pid, cred->gid, cred->uid ); +#endif + data.d->pid = cred->pid; + data.d->gid = cred->gid; + data.d->uid = cred->uid; + + } else if(SCM_RIGHTS == h->cmsg_type) { + + int * fds = (int *)CMSG_DATA(h); + int numFds = (h->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + for(int ii = 0; ii < numFds; ++ii) { + QUnixSocketRights qusr(fds[ii], 0); + a.append(qusr); + } + + } else { + +#ifdef QUNIXSOCKET_DEBUG + qFatal("QUnixSocket: Unknown ancillary data type (%d) received.", + h->cmsg_type); +#endif + + } + + h = (::cmsghdr *)CMSG_NXTHDR(&(d->message), h); + } + + if(d->message.msg_flags & MSG_CTRUNC) { + data.d->state = (QUnixSocketMessagePrivate::AncillaryDataState)(QUnixSocketMessagePrivate::Truncated | + QUnixSocketMessagePrivate::Credential ); + } + + if(!a.isEmpty()) + data.d->rights = a; + + d->dataBufferLength = 0; + d->messageValid = false; + d->readNotifier->setEnabled(true); + + return data; +} + +/*! \internal */ +bool QUnixSocket::isSequential() const +{ + return true; +} + +/*! \internal */ +bool QUnixSocket::waitForReadyRead(int msecs) +{ + if(UnconnectedState == d->state) + return false; + + if(d->messageValid) { + return true; + } + + Q_ASSERT(-1 != d->fd); + + int timeout = msecs; + struct timeval tv; + struct timeval *ptrTv = 0; + QTime stopWatch; + + stopWatch.start(); + + do + { + fd_set readset; + + FD_ZERO(&readset); + FD_SET(d->fd, &readset); + + if(-1 != msecs) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + ptrTv = &tv; + } + + int rv = ::select(d->fd + 1, &readset, 0, 0, ptrTv); + switch(rv) { + case 0: + // timeout + return false; + case 1: + // ok + d->readActivated(); + return true; + default: + if (errno != EINTR) + abort(); // error + break; + } + + timeout = msecs - stopWatch.elapsed(); + } + while (timeout > 0); + + return false; +} + +bool QUnixSocket::waitForBytesWritten(int msecs) +{ + if(UnconnectedState == d->state) + return false; + + Q_ASSERT(-1 != d->fd); + + if ( d->writeQueue.isEmpty() ) + return true; + + QTime stopWatch; + stopWatch.start(); + + while ( true ) + { + fd_set fdwrite; + FD_ZERO(&fdwrite); + FD_SET(d->fd, &fdwrite); + int timeout = msecs < 0 ? 0 : msecs - stopWatch.elapsed(); + struct timeval tv; + struct timeval *ptrTv = 0; + if ( -1 != msecs ) + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + ptrTv = &tv; + } + + int rv = ::select(d->fd + 1, 0, &fdwrite, 0, ptrTv); + switch ( rv ) + { + case 0: + // timeout + return false; + case 1: + { + // ok to write + qint64 bytesWritten = d->writeActivated(); + if (bytesWritten == 0) { + // We need to retry + int delay = 1; + do { + if (-1 != msecs) { + timeout = msecs - stopWatch.elapsed(); + if (timeout <= 0) { + // We have exceeded our allotted time + return false; + } else { + if (delay > timeout) + delay = timeout; + } + } + + // Pause before we make another attempt to send + ::usleep(delay * 1000); + if (delay < 1024) + delay *= 2; + + bytesWritten = d->writeActivated(); + } while (bytesWritten == 0); + } + return (bytesWritten != -1); + } + default: + // error - or an uncaught signal!!!!!!!!! + if ( rv == EINTR ) + continue; + abort(); + return false; + } + } + return false; // fix warnings +} + +/*! \internal */ +bool QUnixSocket::canReadLine() const +{ + for(unsigned int ii = 0; ii < d->dataBufferLength; ++ii) + if(d->dataBuffer[ii] == '\n') return true; + return false; +} + +/*! \internal */ +qint64 QUnixSocket::readData(char * data, qint64 maxSize) +{ + Q_ASSERT(data); + if(0 >= maxSize) return 0; + if(!d->dataBufferLength) return 0; + + // Read data + unsigned int size = d->dataBufferLength>maxSize?maxSize:d->dataBufferLength; + memcpy(data, d->dataBuffer, size); + if(size == d->dataBufferLength) { + d->dataBufferLength = 0; + } else { + memmove(d->dataBuffer, d->dataBuffer + size, d->dataBufferLength - size); + d->dataBufferLength -= size; + } + + + // Flush ancillary + d->flushAncillary(); + + if(0 == d->dataBufferLength) + d->readNotifier->setEnabled(true); + + return size; +} + +/*! \internal */ +qint64 QUnixSocket::writeData (const char * data, qint64 maxSize) +{ + return write(QUnixSocketMessage(QByteArray(data, maxSize))); +} + +qint64 QUnixSocketPrivate::writeActivated() +{ + writeNotifier->setEnabled(false); + + QUnixSocketMessage & m = writeQueue.head(); + const QList & a = m.rights(); + + // + // Construct the message + // + ::iovec vec; + if ( !m.d->vec ) // message does not already have an iovec + { + vec.iov_base = (void *)m.bytes().constData(); + vec.iov_len = m.bytes().size(); + } + + // Allocate the control buffer + ::msghdr sendmessage; + ::bzero(&sendmessage, sizeof(::msghdr)); + if ( m.d->vec ) + { + sendmessage.msg_iov = m.d->vec; + sendmessage.msg_iovlen = m.d->iovecLen; + } + else + { + sendmessage.msg_iov = &vec; + sendmessage.msg_iovlen = 1; + } + unsigned int required = CMSG_SPACE(sizeof(::ucred)) + + a.size() * CMSG_SPACE(sizeof(int)); + sendmessage.msg_control = new char[required]; + ::bzero(sendmessage.msg_control, required); + sendmessage.msg_controllen = required; + + // Create ancillary buffer + ::cmsghdr * h = CMSG_FIRSTHDR(&sendmessage); + + if(m.d->state & QUnixSocketMessagePrivate::Credential) { + h->cmsg_len = CMSG_LEN(sizeof(::ucred)); + h->cmsg_level = SOL_SOCKET; + h->cmsg_type = SCM_CREDENTIALS; + ((::ucred *)CMSG_DATA(h))->pid = m.d->pid; + ((::ucred *)CMSG_DATA(h))->gid = m.d->gid; + ((::ucred *)CMSG_DATA(h))->uid = m.d->uid; + h = CMSG_NXTHDR(&sendmessage, h); + } else { + sendmessage.msg_controllen -= CMSG_SPACE(sizeof(::ucred)); + } + + for(int ii = 0; ii < a.count(); ++ii) { + const QUnixSocketRights & r = a.at(ii); + + if(r.isValid()) { + h->cmsg_len = CMSG_LEN(sizeof(int)); + h->cmsg_level = SOL_SOCKET; + h->cmsg_type = SCM_RIGHTS; + *((int *)CMSG_DATA(h)) = r.peekFd(); + h = CMSG_NXTHDR(&sendmessage, h); + } else { + sendmessage.msg_controllen -= CMSG_SPACE(sizeof(int)); + } + } + +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Transmitting message (length" << m.d->size() << ')'; +#endif + ::ssize_t s = ::sendmsg(fd, &sendmessage, MSG_DONTWAIT | MSG_NOSIGNAL); +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Transmitted message (" << s << ')'; +#endif + + if(-1 == s) { + if(EAGAIN == errno || EWOULDBLOCK == errno || EINTR == errno) { + writeNotifier->setEnabled(true); + } else if(EPIPE == errno) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Remote side disconnected during transmit " + "(" << ::strerror(errno) << ')'; +#endif + me->abort(); + } else { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to transmit data (" + << ::strerror(errno) << ')'; +#endif + error = (QUnixSocket::SocketError)(QUnixSocket::WriteFailure | + CausedAbort); + me->abort(); + } + } else if(s != m.d->size()) { + + // A partial transmission + writeNotifier->setEnabled(true); + delete [] (char *)sendmessage.msg_control; + m.d->rights = QList(); + m.d->removeBytes( s ); + writeQueueBytes -= s; + emit bytesWritten(s); + return s; + + } else { + + // Success! + writeQueue.dequeue(); + Q_ASSERT(writeQueueBytes >= (unsigned)s); + writeQueueBytes -= s; + emit bytesWritten(s); + + } + + delete [] (char *)sendmessage.msg_control; + if(-1 != s && !writeQueue.isEmpty()) + return writeActivated(); + else if(QUnixSocket::ClosingState == me->state() && writeQueue.isEmpty()) + me->abort(); + + if((-1 == s) && (EAGAIN == errno || EWOULDBLOCK == errno || EINTR == errno)) + // Return zero bytes written to indicate retry may be required + return 0; + else + return s; +} + +void QUnixSocketPrivate::readActivated() +{ +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: readActivated"; +#endif + readNotifier->setEnabled(false); + + ::iovec vec; + vec.iov_base = dataBuffer; + vec.iov_len = dataBufferCapacity; + + bzero(&message, sizeof(::msghdr)); + message.msg_iov = &vec; + message.msg_iovlen = 1; + message.msg_controllen = ancillaryBufferCapacity(); + message.msg_control = ancillaryBuffer; + + int flags = 0; +#ifdef MSG_CMSG_CLOEXEC + flags = MSG_CMSG_CLOEXEC; +#endif + + int recvrv = ::recvmsg(fd, &message, flags); +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Received message (" << recvrv << ')'; +#endif + if(-1 == recvrv) { +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: Unable to receive data (" + << ::strerror(errno) << ')'; +#endif + error = (QUnixSocket::SocketError)(QUnixSocket::ReadFailure | + CausedAbort); + me->abort(); + } else if(0 == recvrv) { + me->abort(); + } else { + Q_ASSERT(recvrv); + Q_ASSERT((unsigned)recvrv <= dataBufferCapacity); + dataBufferLength = recvrv; + messageValid = true; + +#ifdef QUNIXSOCKET_DEBUG + qDebug() << "QUnixSocket: readyRead() " << dataBufferLength; +#endif + emit readyRead(); + } +} + +QT_END_NAMESPACE + +#include "qunixsocket.moc" diff --git a/src/gui/embedded/qunixsocket_p.h b/src/gui/embedded/qunixsocket_p.h new file mode 100644 index 0000000000..cfa463001f --- /dev/null +++ b/src/gui/embedded/qunixsocket_p.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUNIXSOCKET_P_H +#define QUNIXSOCKET_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 +#include +#include +#include + +extern "C" { +#include +}; + +QT_BEGIN_NAMESPACE + +class QUnixSocketRights; +class QUnixSocketRightsPrivate; +class QUnixSocketPrivate; +class QUnixSocketMessagePrivate; +struct iovec; + +class Q_GUI_EXPORT QUnixSocketRights { +public: + QUnixSocketRights(int); + ~QUnixSocketRights(); + + QUnixSocketRights(const QUnixSocketRights &); + QUnixSocketRights & operator=(const QUnixSocketRights &); + + bool isValid() const; + + int dupFd() const; + int peekFd() const; + +private: + friend class QUnixSocket; + QUnixSocketRights(int,int); + QSharedDataPointer d; +}; + +class Q_GUI_EXPORT QUnixSocketMessage { +public: + QUnixSocketMessage(); + QUnixSocketMessage(const QByteArray &); + QUnixSocketMessage(const QByteArray &, const QList &); + QUnixSocketMessage(const QUnixSocketMessage &); + QUnixSocketMessage(const iovec*, int); + QUnixSocketMessage & operator=(const QUnixSocketMessage &); + ~QUnixSocketMessage(); + + void setBytes(const QByteArray &); + void setRights(const QList &); + + const QList & rights() const; + bool rightsWereTruncated() const; + + const QByteArray & bytes() const; + + pid_t processId() const; + uid_t userId() const; + gid_t groupId() const; + + void setProcessId(pid_t); + void setUserId(uid_t); + void setGroupId(gid_t); + + bool isValid() const; +private: + friend class QUnixSocket; + friend class QUnixSocketPrivate; + QSharedDataPointer d; +}; + +class Q_GUI_EXPORT QUnixSocket : public QIODevice +{ + Q_OBJECT +public: + QUnixSocket(QObject * = 0); + QUnixSocket(qint64, qint64, QObject * = 0); + virtual ~QUnixSocket(); + + enum SocketState { + UnconnectedState = QAbstractSocket::UnconnectedState, + HostLookupState = QAbstractSocket::HostLookupState, + ConnectingState = QAbstractSocket::ConnectingState, + ConnectedState = QAbstractSocket::ConnectedState, + BoundState = QAbstractSocket::BoundState, + ClosingState = QAbstractSocket::ClosingState, + ListeningState = QAbstractSocket::ListeningState, + }; + + enum SocketError { NoError, InvalidPath, ResourceError, + NonexistentPath, ConnectionRefused, UnknownError, + ReadFailure, WriteFailure }; + + bool connect(const QByteArray & path); + bool setSocketDescriptor(int socketDescriptor); + int socketDescriptor() const; + void abort(); + void close(); + + bool flush(); + + SocketError error() const; + + SocketState state() const; + QByteArray address() const; + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + qint64 rightsBufferSize() const; + void setRightsBufferSize(qint64 size); + + bool canReadLine() const; + + qint64 write(const char * data, qint64 maxSize) + { return QIODevice::write(data, maxSize); } + qint64 write(const QByteArray & byteArray) + { return QIODevice::write(byteArray); } + qint64 read(char * data, qint64 maxSize) + { return QIODevice::read(data, maxSize); } + QByteArray read(qint64 maxSize) + { return QIODevice::read(maxSize); } + + qint64 write(const QUnixSocketMessage &); + QUnixSocketMessage read(); + + virtual bool isSequential() const; + virtual bool waitForReadyRead(int msec = 300); + virtual bool waitForBytesWritten(int msec = 300); + +Q_SIGNALS: + void stateChanged(SocketState socketState); + +protected: + virtual qint64 readData(char * data, qint64 maxSize); + virtual qint64 writeData (const char * data, qint64 maxSize); + +private: + QUnixSocket(const QUnixSocket &); + QUnixSocket & operator=(const QUnixSocket &); + + QUnixSocketPrivate * d; +}; + +QT_END_NAMESPACE + +#endif // QUNIXSOCKET_P_H diff --git a/src/gui/embedded/qunixsocketserver.cpp b/src/gui/embedded/qunixsocketserver.cpp new file mode 100644 index 0000000000..36060fdd29 --- /dev/null +++ b/src/gui/embedded/qunixsocketserver.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qunixsocketserver_p.h" + +// #define QUNIXSOCKETSERVER_DEBUG + +#ifdef QUNIXSOCKETSERVER_DEBUG +#include +#endif + +#include + +extern "C" { +#include +#include +#include +#include +#include +}; + +#define UNIX_PATH_MAX 108 // From unix(7) + +QT_BEGIN_NAMESPACE + +class QUnixSocketServerPrivate : public QObject +{ +Q_OBJECT +public: + QUnixSocketServerPrivate(QUnixSocketServer * parent) + : QObject(), me(parent), fd(-1), maxConns(30), + error(QUnixSocketServer::NoError), acceptNotifier(0) + {} + + QUnixSocketServer * me; + int fd; + int maxConns; + QByteArray address; + QUnixSocketServer::ServerError error; + QSocketNotifier * acceptNotifier; +public slots: + void acceptActivated(); +}; + +/*! + \class QUnixSocketServer + \internal + + \brief The QUnixSocketServer class provides a Unix domain socket based server. + \omit + \ingroup Platform::DeviceSpecific + \ingroup Platform::OS + \ingroup Platform::Communications + \endomit + \ingroup qws + + This class makes it possible to accept incoming Unix domain socket + connections. Call \l QUnixSocketServer::listen() to have the server listen + for incoming connections on a specified path. The pure virtual + \l QUnixSocketServer::incomingConnection() is called each time a new + connection is established. Users must inherit from QUnixSocketServer and + implement this method. + + If an error occurs, \l QUnixSocketServer::serverError() returns the type of + error. Errors can only occur during server establishment - that is, during a + call to \l QUnixSocketServer::listen(). Calling \l QUnixSocketServer::close() + causes QUnixSocketServer to stop listening for connections and reset its + state. + + QUnixSocketServer is often used in conjunction with the \l QUnixSocket class. + + \sa QUnixSocket +*/ + +/*! + \enum QUnixSocketServer::ServerError + + The ServerError enumeration represents the errors that can occur during server + establishment. The most recent error can be retrieved through a call to + \l QUnixSocketServer::serverError(). + + \value NoError No error has occurred. + \value InvalidPath An invalid path endpoint was passed to + \l QUnixSocketServer::listen(). As defined by unix(7), invalid paths + include an empty path, or what more than 107 characters long. + \value ResourceError An error acquiring or manipulating the system's socket + resources occurred. For example, if the process runs out of available + socket descriptors, a ResourceError will occur. + \value BindError The server was unable to bind to the specified path. + \value ListenError The server was unable to listen on the specified path for + incoming connections. + */ + +/*! + Create a new Unix socket server with the given \a parent. + */ +QUnixSocketServer::QUnixSocketServer(QObject *parent) +: QObject(parent), d(0) +{ +} + +/*! + Stops listening for incoming connection and destroys the Unix socket server. + */ +QUnixSocketServer::~QUnixSocketServer() +{ + close(); + if(d) + delete d; +} + +/*! + Stop listening for incoming connections and resets the Unix socket server's + state. Calling this method while \l {QUnixSocketServer::isListening()}{not listening } for incoming connections is a no-op. + + \sa QUnixSocketServer::listen() + */ +void QUnixSocketServer::close() +{ + if(!d) + return; + + if(d->acceptNotifier) { + d->acceptNotifier->setEnabled(false); + delete d->acceptNotifier; + } + d->acceptNotifier = 0; + + if(-1 != d->fd) { +#ifdef QUNIXSOCKET_DEBUG + int closerv = +#endif + ::close(d->fd); +#ifdef QUNIXSOCKET_DEBUG + if(0 != closerv) { + qDebug() << "QUnixSocketServer: Unable to close socket (" + << strerror(errno) << ')'; + } +#endif + } + d->fd = -1; + d->address = QByteArray(); + d->error = NoError; +} + +/*! + Returns the last server error. Errors may only occur within a call to + \l QUnixSocketServer::listen(), and only when such a call fails. + + This method is not destructive, so multiple calls to + QUnixSocketServer::serverError() will return the same value. The error is + only reset by an explicit call to \l QUnixSocketServer::close() or + by further calls to \l QUnixSocketServer::listen(). + */ +QUnixSocketServer::ServerError QUnixSocketServer::serverError() const +{ + if(!d) + return NoError; + + return d->error; +} + +/*! + Returns true if this server is listening for incoming connections, false + otherwise. + + \sa QUnixSocketServer::listen() + */ +bool QUnixSocketServer::isListening() const +{ + if(!d) + return false; + + return (-1 != d->fd); +} + +/*! + Tells the server to listen for incoming connections on \a path. Returns true + if it successfully initializes, false otherwise. In the case of failure, the + \l QUnixSocketServer::serverError() error status is set accordingly. + + Calling this method while the server is already running will result in the + server begin reset, and then attempting to listen on \a path. This will not + affect connections established prior to the server being reset, but further + incoming connections on the previous path will be refused. + + The server can be explicitly reset by a call to \l QUnixSocketServer::close(). + + \sa QUnixSocketServer::close() + */ +bool QUnixSocketServer::listen(const QByteArray & path) +{ + if(d) { + close(); // Any existing server is destroyed + } else { + d = new QUnixSocketServerPrivate(this); + } + + if(path.isEmpty() || path.size() > UNIX_PATH_MAX) { + d->error = InvalidPath; + return false; + } + unlink( path ); // ok if this fails + + // Create the socket + d->fd = ::socket(PF_UNIX, SOCK_STREAM, 0); + if(-1 == d->fd) { +#ifdef QUNIXSOCKETSERVER_DEBUG + qDebug() << "QUnixSocketServer: Unable to create socket (" + << strerror(errno) << ')'; +#endif + close(); + d->error = ResourceError; + return false; + } + + // Construct our unix address + struct ::sockaddr_un addr; + addr.sun_family = AF_UNIX; + ::memcpy(addr.sun_path, path.data(), path.size()); + if(path.size() < UNIX_PATH_MAX) + addr.sun_path[path.size()] = '\0'; + + // Attempt to bind + if(-1 == ::bind(d->fd, (sockaddr *)&addr, sizeof(sockaddr_un))) { +#ifdef QUNIXSOCKETSERVER_DEBUG + qDebug() << "QUnixSocketServer: Unable to bind socket (" + << strerror(errno) << ')'; +#endif + close(); + d->error = BindError; + return false; + } + + // Listen to socket + if(-1 == ::listen(d->fd, d->maxConns)) { +#ifdef QUNIXSOCKETSERVER_DEBUG + qDebug() << "QUnixSocketServer: Unable to listen socket (" + << strerror(errno) << ')'; +#endif + close(); + d->error = ListenError; + return false; + } + + // Success! + d->address = path; + d->acceptNotifier = new QSocketNotifier(d->fd, QSocketNotifier::Read, d); + d->acceptNotifier->setEnabled(true); + QObject::connect(d->acceptNotifier, SIGNAL(activated(int)), + d, SLOT(acceptActivated())); + + return true; +} + +/*! + Returns the Unix path on which this server is listening. If this server is + not listening, and empty address will be returned. + */ +QByteArray QUnixSocketServer::serverAddress() const +{ + if(!d) + return QByteArray(); + return d->address; +} + +int QUnixSocketServer::socketDescriptor() const +{ + if (!d) + return -1; + return d->fd; +} + + +/*! + Returns the maximum length the queue of pending connections may grow to. That + is, the maximum number of clients attempting to connect for which the Unix + socket server has not yet accepted and passed to + \l QUnixSocketServer::incomingConnection(). If a connection request arrives + with the queue full, the client may receive a connection refused notification. + + By default a queue length of 30 is used. + + \sa QUnixSocketServer::setMaxPendingConnections() + */ +int QUnixSocketServer::maxPendingConnections() const +{ + if(!d) + return 30; + + return d->maxConns; +} + +/*! + Sets the maximum length the queue of pending connections may grow to + \a numConnections. This value will only apply to + \l QUnixSocketServer::listen() calls made following the value change - it will + not be retroactively applied. + + \sa QUnixSocketServer::maxPendingConnections() + */ +void QUnixSocketServer::setMaxPendingConnections(int numConnections) +{ + Q_ASSERT(numConnections >= 1); + if(!d) + d = new QUnixSocketServerPrivate(this); + + d->maxConns = numConnections; +} + +/*! + \fn void QUnixSocketServer::incomingConnection(int socketDescriptor) + + This method is invoked each time a new incoming connection is established with + the server. Clients must reimplement this function in their QUnixSocketServer + derived class to handle the connection. + + A common approach to handling the connection is to pass \a socketDescriptor to + a QUnixSocket instance. + + \sa QUnixSocket + */ + +void QUnixSocketServerPrivate::acceptActivated() +{ + ::sockaddr_un r; + socklen_t len = sizeof(sockaddr_un); + int connsock = ::accept(fd, (sockaddr *)&r, &len); +#ifdef QUNIXSOCKETSERVER_DEBUG + qDebug() << "QUnixSocketServer: Accept connection " << connsock; +#endif + if(-1 != connsock) + me->incomingConnection(connsock); +} + +QT_END_NAMESPACE + +#include "qunixsocketserver.moc" diff --git a/src/gui/embedded/qunixsocketserver_p.h b/src/gui/embedded/qunixsocketserver_p.h new file mode 100644 index 0000000000..d7b0a45782 --- /dev/null +++ b/src/gui/embedded/qunixsocketserver_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUNIXSOCKETSERVER_P_H +#define QUNIXSOCKETSERVER_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 + +QT_BEGIN_NAMESPACE + +class QUnixSocketServerPrivate; +class Q_GUI_EXPORT QUnixSocketServer : public QObject +{ + Q_OBJECT +public: + enum ServerError { NoError, InvalidPath, ResourceError, BindError, + ListenError }; + + QUnixSocketServer(QObject *parent=0); + virtual ~QUnixSocketServer(); + + void close(); + + ServerError serverError() const; + + bool isListening() const; + bool listen(const QByteArray & path); + + int socketDescriptor() const; + QByteArray serverAddress() const; + + int maxPendingConnections() const; + void setMaxPendingConnections(int numConnections); + +protected: + virtual void incomingConnection(int socketDescriptor) = 0; + +private: + QUnixSocketServer(const QUnixSocketServer &); + QUnixSocketServer & operator=(const QUnixSocketServer &); + + friend class QUnixSocketServerPrivate; + QUnixSocketServerPrivate * d; +}; + + +QT_END_NAMESPACE +#endif // QUNIXSOCKETSERVER_P_H + diff --git a/src/gui/embedded/qvfbhdr.h b/src/gui/embedded/qvfbhdr.h new file mode 100644 index 0000000000..84f82acd69 --- /dev/null +++ b/src/gui/embedded/qvfbhdr.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVFBHDR_H +#define QVFBHDR_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_QWS_TEMP_DIR +# define QT_QWS_TEMP_DIR QLatin1String("/tmp") +#endif + +#ifdef QT_PRIVATE_QWS +#define QT_VFB_DATADIR(DISPLAY) QString::fromLatin1("%1/qtembedded-%2-%3") \ + .arg(QT_QWS_TEMP_DIR).arg(getuid()).arg(DISPLAY) +#define QT_VFB_MOUSE_PIPE(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/qtvfb_mouse")) +#define QT_VFB_KEYBOARD_PIPE(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/qtvfb_keyboard")) +#define QT_VFB_MAP(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/qtvfb_map")) +#define QT_VFB_SOUND_PIPE(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/qt_soundserver")) +#define QTE_PIPE(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/QtEmbedded")) +#define QTE_PIPE_QVFB(DISPLAY) QTE_PIPE(DISPLAY) +#else +#define QT_VFB_DATADIR(DISPLAY) QString::fromLatin1("%1/qtembedded-%2") \ + .arg(QT_QWS_TEMP_DIR).arg(DISPLAY) +#define QT_VFB_MOUSE_PIPE(DISPLAY) QString::fromLatin1("%1/.qtvfb_mouse-%2") \ + .arg(QT_QWS_TEMP_DIR).arg(DISPLAY) +#define QT_VFB_KEYBOARD_PIPE(DISPLAY) QString::fromLatin1("%1/.qtvfb_keyboard-%2") \ + .arg(QT_QWS_TEMP_DIR).arg(DISPLAY) +#define QT_VFB_MAP(DISPLAY) QString::fromLatin1("%1/.qtvfb_map-%2") \ + .arg(QT_QWS_TEMP_DIR).arg(DISPLAY) +#define QT_VFB_SOUND_PIPE(DISPLAY) QString::fromLatin1("%1/.qt_soundserver-%2") \ + .arg(QT_QWS_TEMP_DIR).arg(DISPLAY) +#define QTE_PIPE(DISPLAY) QT_VFB_DATADIR(DISPLAY) \ + .append(QLatin1String("/QtEmbedded-%1")).arg(DISPLAY) +#define QTE_PIPE_QVFB(DISPLAY) QTE_PIPE(DISPLAY) +#endif + +struct QVFbHeader +{ + int width; + int height; + int depth; + int linestep; + int dataoffset; + QRect update; + bool dirty; + int numcols; + QRgb clut[256]; + int viewerVersion; + int serverVersion; + int brightness; // since 4.4.0 + WId windowId; // since 4.5.0 +}; + +struct QVFbKeyData +{ + unsigned int keycode; + Qt::KeyboardModifiers modifiers; + unsigned short int unicode; + bool press; + bool repeat; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QVFBHDR_H diff --git a/src/gui/embedded/qwindowsystem_p.h b/src/gui/embedded/qwindowsystem_p.h new file mode 100644 index 0000000000..31190033f2 --- /dev/null +++ b/src/gui/embedded/qwindowsystem_p.h @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSYSTEM_QWS_P_H +#define QWINDOWSYSTEM_QWS_P_H + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QWSServer class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#include "qwindowsystem_qws.h" +#include "qbrush.h" +#include "qwsproperty_qws.h" +#include "qwscommand_qws_p.h" +#include "QtCore/qbasictimer.h" + +QT_BEGIN_NAMESPACE + +class QWSServerPrivate : public QObjectPrivate { + friend class QCopChannel; + friend class QWSMouseHandler; + friend class QWSWindow; + friend class QWSDisplay; + friend class QWSInputMethod; + Q_DECLARE_PUBLIC(QWSServer) + +public: + QWSServerPrivate() + : screensaverintervals(0) + , screensavereventblocklevel(-1), screensaverblockevents(false) + , saver(0), cursorClient(0), mouseState(0), nReserved(0) + , doClientIsActive(false) + { + } + ~QWSServerPrivate() + { + closeDisplay(); + + qDeleteAll(deletedWindows); + delete [] screensaverintervals; + delete saver; + + qDeleteAll(windows); + windows.clear(); + + delete bgBrush; + bgBrush = 0; + } + QTime screensavertime; + QTimer* screensavertimer; + int* screensaverintervals; + int screensavereventblocklevel; + bool screensaverblockevents; + bool screensaverblockevent( int index, int *screensaverinterval, bool isDown ); + QWSScreenSaver* saver; + QWSClient *cursorClient; + int mouseState; +// bool prevWin; + QList deletedWindows; + QList crashedClientIds; + + void update_regions(); +//private functions moved from class + +private: + void initServer(int flags); +#ifndef QT_NO_COP + static void sendQCopEvent(QWSClient *c, const QString &ch, + const QString &msg, const QByteArray &data, + bool response = false); +#endif + void move_region(const QWSRegionMoveCommand *); + void set_altitude(const QWSChangeAltitudeCommand *); + void set_opacity(const QWSSetOpacityCommand *); + void request_focus(const QWSRequestFocusCommand *); + QRegion reserve_region(QWSWindow *window, const QRegion ®ion); + void request_region(int winId, const QString &surfaceKey, + const QByteArray &surfaceData, + const QRegion ®ion); + void repaint_region(int winId, int windowFlags, bool opaque, const QRegion &); + void destroy_region(const QWSRegionDestroyCommand *); + void name_region(const QWSRegionNameCommand *); + void set_identity(const QWSIdentifyCommand *); +#ifndef QT_NO_QWS_PROPERTIES + bool get_property(int winId, int property, const char *&data, int &len); +#endif +#ifndef QT_NO_QWS_INPUTMETHODS + void im_response(const QWSIMResponseCommand *); + + void im_update(const QWSIMUpdateCommand *); + + void send_im_mouse(const QWSIMMouseCommand *); +#endif + // not in ifndef as this results in more readable functions. + static void sendKeyEventUnfiltered(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat); + static void sendMouseEventUnfiltered(const QPoint &pos, int state, int wheel = 0); + static void emergency_cleanup(); + + static QBrush *bgBrush; + + void sendMaxWindowRectEvents(const QRect &rect); + + void invokeIdentify(const QWSIdentifyCommand *cmd, QWSClient *client); + void invokeCreate(QWSCreateCommand *cmd, QWSClient *client); + void invokeRegionName(const QWSRegionNameCommand *cmd, QWSClient *client); + void invokeRegion(QWSRegionCommand *cmd, QWSClient *client); + void invokeRegionMove(const QWSRegionMoveCommand *cmd, QWSClient *client); + void invokeRegionDestroy(const QWSRegionDestroyCommand *cmd, QWSClient *client); + void invokeSetAltitude(const QWSChangeAltitudeCommand *cmd, QWSClient *client); + void invokeSetOpacity(const QWSSetOpacityCommand *cmd, QWSClient *client); +#ifndef QT_NO_QWS_PROPERTIES + void invokeAddProperty(QWSAddPropertyCommand *cmd); + void invokeSetProperty(QWSSetPropertyCommand *cmd); + void invokeRemoveProperty(QWSRemovePropertyCommand *cmd); + void invokeGetProperty(QWSGetPropertyCommand *cmd, QWSClient *client); +#endif //QT_NO_QWS_PROPERTIES + void invokeSetSelectionOwner(QWSSetSelectionOwnerCommand *cmd); + void invokeConvertSelection(QWSConvertSelectionCommand *cmd); + void invokeSetFocus(const QWSRequestFocusCommand *cmd, QWSClient *client); + + void initIO(); + void setFocus(QWSWindow*, bool gain); +#ifndef QT_NO_QWS_CURSOR + void invokeDefineCursor(QWSDefineCursorCommand *cmd, QWSClient *client); + void invokeSelectCursor(QWSSelectCursorCommand *cmd, QWSClient *client); + void invokePositionCursor(QWSPositionCursorCommand *cmd, QWSClient *client); +#endif + void invokeGrabMouse(QWSGrabMouseCommand *cmd, QWSClient *client); + void invokeGrabKeyboard(QWSGrabKeyboardCommand *cmd, QWSClient *client); +#ifndef QT_NO_SOUND + void invokePlaySound(QWSPlaySoundCommand *cmd, QWSClient *client); +#endif +#ifndef QT_NO_COP + void invokeRegisterChannel(QWSQCopRegisterChannelCommand *cmd, + QWSClient *client); + void invokeQCopSend(QWSQCopSendCommand *cmd, QWSClient *client); +#endif + void invokeRepaintRegion(QWSRepaintRegionCommand *cmd, + QWSClient *client); +#ifndef QT_NO_QWSEMBEDWIDGET + void invokeEmbed(QWSEmbedCommand *cmd, QWSClient *client); +#endif +#ifndef QT_NO_QWS_INPUTMETHODS + void invokeIMResponse(const QWSIMResponseCommand *cmd, + QWSClient *client); + void invokeIMUpdate(const QWSIMUpdateCommand *cmd, + QWSClient *client); +#endif + void invokeFont(const QWSFontCommand *cmd, QWSClient *client); + void invokeScreenTransform(const QWSScreenTransformCommand *cmd, + QWSClient *client); + + QWSMouseHandler* newMouseHandler(const QString& spec); + void openDisplay(); + void closeDisplay(); + + void showCursor(); + void hideCursor(); + void initializeCursor(); + + void resetEngine(); + +//private Q_SLOTS: + +#ifndef QT_NO_QWS_MULTIPROCESS + void _q_clientClosed(); + void _q_doClient(); + void _q_deleteWindowsLater(); +#endif + + void _q_screenSaverWake(); + void _q_screenSaverSleep(); + void _q_screenSaverTimeout(); +#ifndef QT_NO_QWS_MULTIPROCESS + void _q_newConnection(); +#endif + +//other private moved from class + + void disconnectClient(QWSClient *); + void screenSave(int level); + void doClient(QWSClient *); + typedef QMap::Iterator ClientIterator; + typedef QMap ClientMap; + void handleWindowClose(QWSWindow *w); + void releaseMouse(QWSWindow* w); + void releaseKeyboard(QWSWindow* w); + void updateClientCursorPos(); + + uchar* sharedram; + int ramlen; + + ClientMap clientMap; +#ifndef QT_NO_QWS_PROPERTIES + QWSPropertyManager propertyManager; +#endif + struct SelectionOwner { + int windowid; + struct Time { + void set(int h, int m, int s, int s2) { + hour = h; minute = m; sec = s; ms = s2; + } + int hour, minute, sec, ms; + } time; + } selectionOwner; + QTime timer; + int* screensaverinterval; + + QWSWindow *focusw; + QWSWindow *mouseGrabber; + bool mouseGrabbing; + bool inputMethodMouseGrabbed; + int swidth, sheight, sdepth; +#ifndef QT_NO_QWS_CURSOR + bool haveviscurs; + QWSCursor *cursor; // cursor currently shown + QWSCursor *nextCursor; // cursor to show once grabbing is off +#endif + + bool disablePainting; + QList mousehandlers; +#ifndef QT_NO_QWS_KEYBOARD + QList keyboardhandlers; +#endif + + QList commandQueue; + + // Window management + QList windows; // first=topmost + int nReserved; + QWSWindow* newWindow(int id, QWSClient* client); + QWSWindow* findWindow(int windowid, QWSClient* client = 0); + void moveWindowRegion(QWSWindow*, int dx, int dy); + void setWindowRegion(QWSWindow*, const QRegion &r); + void raiseWindow(QWSWindow *, int = 0); + void lowerWindow(QWSWindow *, int = -1); + void exposeRegion(const QRegion &, int index = 0); + + void setCursor(QWSCursor *curs); + + // multimedia +#ifndef QT_NO_SOUND + QWSSoundServer *soundserver; +#endif +#ifndef QT_NO_COP + QMap > channels; +#endif + +#ifndef QT_NO_QWS_MULTIPROCESS + QWSServerSocket *ssocket; +#endif + + // filename -> refcount + QMap fontReferenceCount; + QBasicTimer fontCleanupTimer; + void referenceFont(QWSClientPrivate *client, const QByteArray &font); + void dereferenceFont(QWSClientPrivate *client, const QByteArray &font); + void cleanupFonts(bool force = false); + void sendFontRemovedEvent(const QByteArray &font); + + bool doClientIsActive; + QList pendingDoClients; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/embedded/qwindowsystem_qws.cpp b/src/gui/embedded/qwindowsystem_qws.cpp new file mode 100644 index 0000000000..0d1ae0d2d2 --- /dev/null +++ b/src/gui/embedded/qwindowsystem_qws.cpp @@ -0,0 +1,4960 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "qwindowsystem_qws.h" +#include "qwsevent_qws.h" +#include "qwscommand_qws_p.h" +#include "qtransportauth_qws_p.h" +#include "qwsutils_qws.h" +#include "qwscursor_qws.h" +#include "qwsdisplay_qws.h" +#include "qmouse_qws.h" +#include "qcopchannel_qws.h" +#include "qwssocket_qws.h" + +#include "qapplication.h" +#include "private/qapplication_p.h" +#include "qsocketnotifier.h" +#include "qpolygon.h" +#include "qimage.h" +#include "qcursor.h" +#include +#include "qscreen_qws.h" +#include "qwindowdefs.h" +#include "private/qlock_p.h" +#include "qwslock_p.h" +#include "qfile.h" +#include "qtimer.h" +#include "qpen.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qinputcontext.h" +#include "qpainter.h" + +#include + +#include "qkbddriverfactory_qws.h" +#include "qmousedriverfactory_qws.h" + +#include +#include + +#include +#include + +#include "qwindowsystem_p.h" + + +#include +#include +#include + +#ifndef QT_NO_QWS_MULTIPROCESS +#include +#include +#endif + +#if !defined(QT_NO_SOUND) && !defined(Q_OS_DARWIN) +#ifdef QT_USE_OLD_QWS_SOUND +#include +#include +#include +#include +#else +#include "qsoundqss_qws.h" +#endif +#endif + +//#define QWS_DEBUG_FONTCLEANUP + +QT_BEGIN_NAMESPACE + +QWSServer Q_GUI_EXPORT *qwsServer=0; +static QWSServerPrivate *qwsServerPrivate=0; + +#define MOUSE 0 +#define KEY 1 +//#define EVENT_BLOCK_DEBUG + +QWSScreenSaver::~QWSScreenSaver() +{ +} + +extern QByteArray qws_display_spec; +extern void qt_init_display(); //qapplication_qws.cpp +extern QString qws_qtePipeFilename(); + +extern void qt_client_enqueue(const QWSEvent *); //qapplication_qws.cpp +extern QList *qt_get_server_queue(); + +Q_GLOBAL_STATIC_WITH_ARGS(QString, defaultMouse, (QLatin1String("Auto"))) +Q_GLOBAL_STATIC_WITH_ARGS(QString, defaultKeyboard, (QLatin1String("TTY"))) +static const int FontCleanupInterval = 60 * 1000; + +static int qws_keyModifiers = 0; + +static QWSWindow *keyboardGrabber; +static bool keyboardGrabbing; + +static int get_object_id(int count = 1) +{ + static int next=1000; + int n = next; + next += count; + return n; +} +#ifndef QT_NO_QWS_INPUTMETHODS +static QWSInputMethod *current_IM = 0; + +static QWSWindow *current_IM_composing_win = 0; +static int current_IM_winId = -1; +static bool force_reject_strokeIM = false; +#endif + +static void cleanupFontsDir(); + +//#define QWS_REGION_DEBUG + +/*! + \class QWSScreenSaver + \ingroup qws + + \brief The QWSScreenSaver class is a base class for screensavers + in Qt for Embedded Linux. + + When running \l{Qt for Embedded Linux} applications, it is the server + application that installs and controls the screensaver. + \l{Qt for Embedded Linux} supports multilevel screen saving; i.e., it is possible to + specify several different levels of screen responsiveness. For + example, you can choose to first turn off the light before you + fully activate the screensaver. + + Note that there exists no default screensaver implementation. + + To create a custom screensaver, derive from this class and + reimplement the restore() and save() functions. These functions + are called whenever the screensaver is activated or deactivated, + respectively. Once an instance of your custom screensaver is + created, you can use the QWSServer::setScreenSaver() function to + install it. + + \sa QWSServer, QScreen, {Qt for Embedded Linux} +*/ + +/*! + \fn QWSScreenSaver::~QWSScreenSaver() + + Reimplement this function to destroy the screensaver. +*/ + +/*! + \fn QWSScreenSaver::restore() + + Implement this function to deactivate the screensaver, restoring + the previously saved screen. + + \sa save(), QWSServer::screenSaverActivate() +*/ + +/*! + \fn QWSScreenSaver::save(int level) + + Implement this function to activate the screensaver, saving the + current screen. + + \l{Qt for Embedded Linux} supports multilevel screen saving; i.e., it is + possible to specify several different levels of screen + responsiveness. For example, you can choose to first turn off the + light before you fully activate the screensaver. Use the + QWSServer::setScreenSaverIntervals() to specify the time intervals + between the different levels. + + This function should return true if the screensaver successfully + enters the given \a level; otherwise it should return false. + + \sa restore(), QWSServer::screenSaverActivate() +*/ + +class QWSWindowPrivate +{ +public: + QWSWindowPrivate(); + +#ifdef QT_QWS_CLIENTBLIT + QRegion directPaintRegion; +#endif + QRegion allocatedRegion; +#ifndef QT_NO_QWSEMBEDWIDGET + QList embedded; + QWSWindow *embedder; +#endif + QWSWindow::State state; + Qt::WindowFlags windowFlags; + QRegion dirtyOnScreen; + bool painted; +}; + +QWSWindowPrivate::QWSWindowPrivate() + : +#ifndef QT_NO_QWSEMBEDWIDGET + embedder(0), state(QWSWindow::NoState), +#endif + painted(false) +{ +} + +/*! + \class QWSWindow + \ingroup qws + + \brief The QWSWindow class encapsulates a top-level window in + Qt for Embedded Linux. + + When you run a \l{Qt for Embedded Linux} application, it either runs as a + server or connects to an existing server. As applications add and + remove windows, the server process maintains information about + each window. In \l{Qt for Embedded Linux}, top-level windows are + encapsulated as QWSWindow objects. Note that you should never + construct the QWSWindow class yourself; the current top-level + windows can be retrieved using the QWSServer::clientWindows() + function. + + With a window at hand, you can retrieve its caption, name, opacity + and ID using the caption(), name(), opacity() and winId() + functions, respectively. Use the client() function to retrieve a + pointer to the client that owns the window. + + Use the isVisible() function to find out if the window is + visible. You can find out if the window is completely obscured by + another window or by the bounds of the screen, using the + isFullyObscured() function. The isOpaque() function returns true + if the window has an alpha channel equal to 255. Finally, the + requestedRegion() function returns the region of the display the + window wants to draw on. + + \sa QWSServer, QWSClient, {Qt for Embedded Linux Architecture} +*/ + +/*! + \fn int QWSWindow::winId() const + + Returns the window's ID. + + \sa name(), caption() +*/ + +/*! + \fn const QString &QWSWindow::name() const + + Returns the window's name, which is taken from the \l {QWidget::}{objectName()} + at the time of \l {QWidget::}{show()}. + + \sa caption(), winId() +*/ + +/*! + \fn const QString &QWSWindow::caption() const + + Returns the window's caption. + + \sa name(), winId() +*/ + +/*! + \fn QWSClient* QWSWindow::client() const + + Returns a reference to the QWSClient object that owns this window. + + \sa requestedRegion() +*/ + +/*! + \fn QRegion QWSWindow::requestedRegion() const + + Returns the region that the window has requested to draw onto, + including any window decorations. + + \sa client() +*/ + +/*! + \fn bool QWSWindow::isVisible() const + + Returns true if the window is visible; otherwise returns false. + + \sa isFullyObscured() +*/ + +/*! + \fn bool QWSWindow::isOpaque() const + + Returns true if the window is opaque, i.e., if its alpha channel + equals 255; otherwise returns false. + + \sa opacity() +*/ + +/*! + \fn uint QWSWindow::opacity () const + + Returns the window's alpha channel value. + + \sa isOpaque() +*/ + +/*! + \fn bool QWSWindow::isPartiallyObscured() const + \internal + + Returns true if the window is partially obsured by another window + or by the bounds of the screen; otherwise returns false. +*/ + +/*! + \fn bool QWSWindow::isFullyObscured() const + + Returns true if the window is completely obsured by another window + or by the bounds of the screen; otherwise returns false. + + \sa isVisible() +*/ + +/*! + \fn QWSWindowSurface* QWSWindow::windowSurface() const + \internal +*/ + +QWSWindow::QWSWindow(int i, QWSClient* client) + : id(i), modified(false), + onTop(false), c(client), last_focus_time(0), _opacity(255), + opaque(true), d(new QWSWindowPrivate) +{ + surface = 0; +} + + +/*! + \enum QWSWindow::State + + This enum describes the state of a window. Most of the + transitional states are set just before a call to + QScreen::exposeRegion() and reset immediately afterwards. + + \value NoState Initial state before the window is properly initialized. + \value Hidden The window is not visible. + \value Showing The window is being shown. + \value Visible The window is visible, and not in a transition. + \value Hiding The window is being hidden. + \value Raising The windoe is being raised. + \value Lowering The window is being raised. + \value Moving The window is being moved. + \value ChangingGeometry The window's geometry is being changed. + \value Destroyed The window is destroyed. + + \sa state(), QScreen::exposeRegion() +*/ + +/*! + Returns the current state of the window. + + \since 4.3 +*/ +QWSWindow::State QWSWindow::state() const +{ + return d->state; +} + +/*! + Returns the window flags of the window. This value is only available + after the first paint event. + + \since 4.3 +*/ +Qt::WindowFlags QWSWindow::windowFlags() const +{ + return d->windowFlags; +} + +/*! + Returns the region that has been repainted since the previous + QScreen::exposeRegion(), and needs to be copied to the screen. + \since 4.3 +*/ +QRegion QWSWindow::dirtyOnScreen() const +{ + return d->dirtyOnScreen; +} + +void QWSWindow::createSurface(const QString &key, const QByteArray &data) +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (surface && !surface->isBuffered()) + c->removeUnbufferedSurface(); +#endif + + delete surface; + surface = qt_screen->createSurface(key); + surface->setPermanentState(data); + +#ifndef QT_NO_QWS_MULTIPROCESS + if (!surface->isBuffered()) + c->addUnbufferedSurface(); +#endif +} + +/*! + \internal + Raises the window above all other windows except "Stay on top" windows. +*/ +void QWSWindow::raise() +{ + qwsServerPrivate->raiseWindow(this); +#ifndef QT_NO_QWSEMBEDWIDGET + const int n = d->embedded.size(); + for (int i = 0; i < n; ++i) + d->embedded.at(i)->raise(); +#endif +} + +/*! + \internal + Lowers the window below other windows. +*/ +void QWSWindow::lower() +{ + qwsServerPrivate->lowerWindow(this); +#ifndef QT_NO_QWSEMBEDWIDGET + const int n = d->embedded.size(); + for (int i = 0; i < n; ++i) + d->embedded.at(i)->lower(); +#endif +} + +/*! + \internal + Shows the window. +*/ +void QWSWindow::show() +{ + operation(QWSWindowOperationEvent::Show); +#ifndef QT_NO_QWSEMBEDWIDGET + const int n = d->embedded.size(); + for (int i = 0; i < n; ++i) + d->embedded.at(i)->show(); +#endif +} + +/*! + \internal + Hides the window. +*/ +void QWSWindow::hide() +{ + operation(QWSWindowOperationEvent::Hide); +#ifndef QT_NO_QWSEMBEDWIDGET + const int n = d->embedded.size(); + for (int i = 0; i < n; ++i) + d->embedded.at(i)->hide(); +#endif +} + +/*! + \internal + Make this the active window (i.e., sets the keyboard focus to this + window). +*/ +void QWSWindow::setActiveWindow() +{ + qwsServerPrivate->setFocus(this, true); +#ifndef QT_NO_QWSEMBEDWIDGET + const int n = d->embedded.size(); + for (int i = 0; i < n; ++i) + d->embedded.at(i)->setActiveWindow(); +#endif +} + +void QWSWindow::setName(const QString &n) +{ + rgnName = n; +} + +/*! + \internal + Sets the window's caption to \a c. +*/ +void QWSWindow::setCaption(const QString &c) +{ + rgnCaption = c; +} + + +static int global_focus_time_counter=100; + +void QWSWindow::focus(bool get) +{ + if (get) + last_focus_time = global_focus_time_counter++; + if (c) { + QWSFocusEvent event; + event.simpleData.window = id; + event.simpleData.get_focus = get; + c->sendEvent(&event); + } +} + +void QWSWindow::operation(QWSWindowOperationEvent::Operation o) +{ + if (!c) + return; + QWSWindowOperationEvent event; + event.simpleData.window = id; + event.simpleData.op = o; + c->sendEvent(&event); +} + +/*! + \internal + Destructor. +*/ +QWSWindow::~QWSWindow() +{ +#ifndef QT_NO_QWS_INPUTMETHODS + if (current_IM_composing_win == this) + current_IM_composing_win = 0; +#endif +#ifndef QT_NO_QWSEMBEDWIDGET + QWSWindow *embedder = d->embedder; + if (embedder) { + embedder->d->embedded.removeAll(this); + d->embedder = 0; + } + while (!d->embedded.isEmpty()) + stopEmbed(d->embedded.first()); +#endif + +#ifndef QT_NO_QWS_MULTIPROCESS + if (surface && !surface->isBuffered()) { + if (c && c->d_func()) // d_func() will be 0 if client is deleted + c->removeUnbufferedSurface(); + } +#endif + + delete surface; + delete d; +} + +/*! + \internal + + Returns the region that the window is allowed to draw onto, + including any window decorations but excluding regions covered by + other windows. + + \sa paintedRegion(), requestedRegion() +*/ +QRegion QWSWindow::allocatedRegion() const +{ + return d->allocatedRegion; +} + +#ifdef QT_QWS_CLIENTBLIT +QRegion QWSWindow::directPaintRegion() const +{ + return d->directPaintRegion; +} + +inline void QWSWindow::setDirectPaintRegion(const QRegion &r) +{ + d->directPaintRegion = r; +} +#endif + +/*! + \internal + + Returns the region that the window is known to have drawn into. + + \sa allocatedRegion(), requestedRegion() +*/ +QRegion QWSWindow::paintedRegion() const +{ + return (d->painted ? d->allocatedRegion : QRegion()); +} + +inline void QWSWindow::setAllocatedRegion(const QRegion ®ion) +{ + d->allocatedRegion = region; +} + +#ifndef QT_NO_QWSEMBEDWIDGET +inline void QWSWindow::startEmbed(QWSWindow *w) +{ + d->embedded.append(w); + w->d->embedder = this; +} + +inline void QWSWindow::stopEmbed(QWSWindow *w) +{ + w->d->embedder = 0; + w->client()->sendEmbedEvent(w->winId(), QWSEmbedEvent::Region, QRegion()); + d->embedded.removeAll(w); +} +#endif // QT_NO_QWSEMBEDWIDGET + +/********************************************************************* + * + * Class: QWSClient + * + *********************************************************************/ + +class QWSClientPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QWSClient) + +public: + QWSClientPrivate(); + ~QWSClientPrivate(); + + void setLockId(int id); + void unlockCommunication(); + +private: +#ifndef QT_NO_QWS_MULTIPROCESS + QWSLock *clientLock; + bool shutdown; + int numUnbufferedSurfaces; +#endif + QSet usedFonts; + friend class QWSServerPrivate; +}; + +QWSClientPrivate::QWSClientPrivate() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + clientLock = 0; + shutdown = false; + numUnbufferedSurfaces = 0; +#endif +} + +QWSClientPrivate::~QWSClientPrivate() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + delete clientLock; +#endif +} + +void QWSClientPrivate::setLockId(int id) +{ +#ifdef QT_NO_QWS_MULTIPROCESS + Q_UNUSED(id); +#else + clientLock = new QWSLock(id); +#endif +} + +void QWSClientPrivate::unlockCommunication() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (clientLock) + clientLock->unlock(QWSLock::Communication); +#endif +} + +/*! + \class QWSClient + \ingroup qws + + \brief The QWSClient class encapsulates a client process in Qt for Embedded Linux. + + When you run a \l{Qt for Embedded Linux} application, it either runs as a + server or connects to an existing server. The server and client + processes have different responsibilities: The client process + performs all application specific operations. The server process + is responsible for managing the clients as well as taking care of + the pointer handling, character input, and screen output. In + addition, the server provides functionality to handle input + methods. + + As applications add and remove windows, the server process + maintains information about each window. In \l{Qt for Embedded Linux}, + top-level windows are encapsulated as QWSWindow objects. A list of + the current windows can be retrieved using the + QWSServer::clientWindows() function, and each window can tell + which client that owns it through its QWSWindow::client() + function. + + A QWSClient object has an unique ID that can be retrieved using + its clientId() function. QWSClient also provides the identity() + function which typically returns the name of this client's running + application. + + \sa QWSServer, QWSWindow, {Qt for Embedded Linux Architecture} +*/ + +/*! + \internal +*/ +//always use frame buffer +QWSClient::QWSClient(QObject* parent, QWS_SOCK_BASE* sock, int id) + : QObject(*new QWSClientPrivate, parent), command(0), cid(id) +{ +#ifdef QT_NO_QWS_MULTIPROCESS + Q_UNUSED(sock); + isClosed = false; +#else + csocket = 0; + if (!sock) { + socketDescriptor = -1; + isClosed = false; + } else { + csocket = static_cast(sock); //### + isClosed = false; + + csocket->flush(); + socketDescriptor = csocket->socketDescriptor(); + connect(csocket, SIGNAL(readyRead()), this, SIGNAL(readyRead())); + connect(csocket, SIGNAL(disconnected()), this, SLOT(closeHandler())); + connect(csocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorHandler())); + } +#endif //QT_NO_QWS_MULTIPROCESS +} + +/*! + \internal +*/ +QWSClient::~QWSClient() +{ + qDeleteAll(cursors); + delete command; +#ifndef QT_NO_QWS_MULTIPROCESS + delete csocket; +#endif +} + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSClient::removeUnbufferedSurface() +{ + Q_D(QWSClient); + --d->numUnbufferedSurfaces; +} + +void QWSClient::addUnbufferedSurface() +{ + Q_D(QWSClient); + ++d->numUnbufferedSurfaces; +} +#endif // QT_NO_QWS_MULTIPROCESS + +/*! + \internal +*/ +void QWSClient::setIdentity(const QString& i) +{ + id = i; +} + +void QWSClient::closeHandler() +{ + isClosed = true; + emit connectionClosed(); +} + +void QWSClient::errorHandler() +{ +#if defined(QWS_SOCKET_DEBUG) + qDebug("Client %p error %s", this, csocket ? csocket->errorString().toLatin1().constData() : "(no socket)"); +#endif + isClosed = true; +//####Do we need to clean out the pipes? + + emit connectionClosed(); +} + +/*! + \internal +*/ +int QWSClient::socket() const +{ + return socketDescriptor; +} + +/*! + \internal +*/ +void QWSClient::sendEvent(QWSEvent* event) +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) { + // qDebug() << "QWSClient::sendEvent type " << event->type << " socket state " << csocket->state(); + if ((QAbstractSocket::SocketState)(csocket->state()) == QAbstractSocket::ConnectedState) { + event->write(csocket); + } + } + else +#endif + { + qt_client_enqueue(event); + } +} + +/*! + \internal +*/ +void QWSClient::sendRegionEvent(int winid, QRegion rgn, int type +#ifdef QT_QWS_CLIENTBLIT + , int id +#endif + ) +{ +#ifndef QT_NO_QWS_MULTIPROCESS + Q_D(QWSClient); + if (d->clientLock) + d->clientLock->lock(QWSLock::RegionEvent); +#endif + + QWSRegionEvent event; + event.setData(winid, rgn, type); +#ifdef QT_QWS_CLIENTBLIT + event.simpleData.id = id; +#endif + +// qDebug() << "Sending Region event to" << winid << "rgn" << rgn << "type" << type; + + sendEvent(&event); +} + +extern int qt_servershmid; + +/*! + \internal +*/ +void QWSClient::sendConnectedEvent(const char *display_spec) +{ + QWSConnectedEvent event; + event.simpleData.window = 0; + event.simpleData.len = strlen(display_spec) + 1; + event.simpleData.clientId = cid; + event.simpleData.servershmid = qt_servershmid; + char * tmp=(char *)display_spec; + event.setData(tmp, event.simpleData.len); + sendEvent(&event); +} + +/*! + \internal +*/ +void QWSClient::sendMaxWindowRectEvent(const QRect &rect) +{ + QWSMaxWindowRectEvent event; + event.simpleData.window = 0; + event.simpleData.rect = rect; + sendEvent(&event); +} + +/*! + \internal +*/ +#ifndef QT_NO_QWS_PROPERTIES +void QWSClient::sendPropertyNotifyEvent(int property, int state) +{ + QWSPropertyNotifyEvent event; + event.simpleData.window = 0; // not used yet + event.simpleData.property = property; + event.simpleData.state = state; + sendEvent(&event); +} + +/*! + \internal +*/ +void QWSClient::sendPropertyReplyEvent(int property, int len, const char *data) +{ + QWSPropertyReplyEvent event; + event.simpleData.window = 0; // not used yet + event.simpleData.property = property; + event.simpleData.len = len; + event.setData(data, len); + sendEvent(&event); +} +#endif //QT_NO_QWS_PROPERTIES + +/*! + \internal +*/ +void QWSClient::sendSelectionClearEvent(int windowid) +{ + QWSSelectionClearEvent event; + event.simpleData.window = windowid; + sendEvent(&event); +} + +/*! + \internal +*/ +void QWSClient::sendSelectionRequestEvent(QWSConvertSelectionCommand *cmd, int windowid) +{ + QWSSelectionRequestEvent event; + event.simpleData.window = windowid; + event.simpleData.requestor = cmd->simpleData.requestor; + event.simpleData.property = cmd->simpleData.selection; + event.simpleData.mimeTypes = cmd->simpleData.mimeTypes; + sendEvent(&event); +} + +#ifndef QT_NO_QWSEMBEDWIDGET +/*! + \internal +*/ +void QWSClient::sendEmbedEvent(int windowid, QWSEmbedEvent::Type type, + const QRegion ®ion) +{ + QWSEmbedEvent event; + event.setData(windowid, type, region); + sendEvent(&event); +} +#endif // QT_NO_QWSEMBEDWIDGET + +/*! + \fn void QWSClient::connectionClosed() + \internal +*/ + +/*! + \fn void QWSClient::readyRead(); + \internal +*/ + +/*! + \fn int QWSClient::clientId () const + + Returns an integer uniquely identfying this client. +*/ + +/*! + \fn QString QWSClient::identity () const + + Returns the name of this client's running application. +*/ +/********************************************************************* + * + * Class: QWSServer + * + *********************************************************************/ + +/*! + \class QWSServer + \brief The QWSServer class encapsulates a server process in Qt for Embedded Linux. + + \ingroup qws + + When you run a \l{Qt for Embedded Linux} application, it either runs as a + server or connects to an existing server. The server and client + processes have different responsibilities: The client process + performs all application specific operations. The server process + is responsible for managing the clients as well as taking care of + the pointer handling, character input, and screen output. In + addition, the server provides functionality to handle input + methods. + + In \l{Qt for Embedded Linux}, all system generated events are passed to the + server application which then propagates the event to the + appropriate client. See the \l{Qt for Embedded Linux Architecture} + documentation for details. + + Note that this class is instantiated by QApplication for + \l{Qt for Embedded Linux} server processes; you should never construct this + class yourself. Use the instance() function to retrieve a pointer + to the server object. + + Note that the static functions of the QWSServer class can only be + used in the server process. + + \tableofcontents + + \section1 Client Administration + + As applications add and remove windows, the server process + maintains information about each window. In \l{Qt for Embedded Linux}, + top-level windows are encapsulated as QWSWindow objects. Each + window can tell which client that owns it through its + QWSWindow::client() function. Use the clientWindows() function to + retrieve a list of the current top-level windows. Given a + particular position on the display, the window containing it can + be retrieved using the windowAt() function. + + QWSServer also provides the windowEvent() signal which is emitted + whenever something happens to a top level window; the WindowEvent + enum describes the various types of events that the signal + recognizes. In addition, the server class provides the + markedText() signal which is emitted whenever some text has been + selected in any of the windows, passing the selection as + parameter. + + The QCopChannel class and the QCOP communication protocol enable + transfer of messages between clients. QWSServer provides the + newChannel() and removedChannel() signals that is emitted whenever + a new QCopChannel object is created or destroyed, respectively. + + See also: QWSWindow, QWSClient and QCopChannel. + + + \section1 Mouse Handling + + The mouse driver (represented by an instance of the + QWSMouseHandler class) is loaded by the server application when it + starts running, using Qt's \l {How to Create Qt Plugins}{plugin + system}. A mouse driver receives mouse events from the device and + encapsulates each event with an instance of the QWSEvent class + which it then passes to the server. + + The openMouse() function opens the mouse devices specified by the + QWS_MOUSE_PROTO environment variable, and the setMouseHandler() + functions sets the primary mouse driver. Alternatively, the static + setDefaultMouse() function provides means of specifying the mouse + driver to use if the QWS_MOUSE_PROTO variable is not defined (note + that the default is otherwise platform dependent). The primary + mouse driver can be retrieved using the static mouseHandler() + function. Use the closeMouse() function to delete the mouse + drivers. + + In addition, the QWSServer class can control the flow of mouse + input using the suspendMouse() and resumeMouse() functions. + + See also: QWSMouseHandler and \l{Qt for Embedded Linux Pointer Handling}. + + \section1 Keyboard Handling + + The keyboard driver (represented by an instance of the + QWSKeyboardHandler class) is loaded by the server application when + it starts running, using Qt's \l {How to Create Qt Plugins}{plugin + system}. A keyboard driver receives keyboard events from the + device and encapsulates each event with an instance of the + QWSEvent class which it then passes to the server. + + The openKeyboard() function opens the keyboard devices specified + by the QWS_KEYBOARD environment variable, and the + setKeyboardHandler() functions sets the primary keyboard + driver. Alternatively, the static setDefaultKeyboard() function + provides means of specifying the keyboard driver to use if the + QWS_KEYBOARD variable is not defined (note again that the default + is otherwise platform dependent). The primary keyboard driver can + be retrieved using the static keyboardHandler() function. Use the + closeKeyboard() function to delete the keyboard drivers. + + In addition, the QWSServer class can handle key events from both + physical and virtual keyboards using the processKeyEvent() and + sendKeyEvent() functions, respectively. Use the + addKeyboardFilter() function to filter the key events from + physical keyboard drivers, the most recently added filter can be + removed and deleted using the removeKeyboardFilter() function. + + See also: QWSKeyboardHandler and \l{Qt for Embedded Linux Character Input}. + + \section1 Display Handling + + When a screen update is required, the server runs through all the + top-level windows that intersect with the region that is about to + be updated, and ensures that the associated clients have updated + their memory buffer. Then the server uses the screen driver + (represented by an instance of the QScreen class) to copy the + content of the memory to the screen. + + In addition, the QWSServer class provides some means of managing + the screen output: Use the refresh() function to refresh the + entire display, or alternatively a specified region of it. The + enablePainting() function can be used to disable (and enable) + painting onto the screen. QWSServer also provide the + setMaxWindowRect() function restricting the area of the screen + which \l{Qt for Embedded Linux} applications will consider to be the + maximum area to use for windows. To set the brush used as the + background in the absence of obscuring windows, QWSServer provides + the static setBackground() function. The corresponding + backgroundBrush() function returns the currently set brush. + + QWSServer also controls the screen saver: Use the setScreenSaver() + to install a custom screen saver derived from the QWSScreenSaver + class. Once installed, the screensaver can be activated using the + screenSaverActivate() function, and the screenSaverActive() + function returns its current status. Use the + setScreenSaverInterval() function to specify the timeout interval. + \l{Qt for Embedded Linux} also supports multilevel screen saving, use the + setScreenSaverIntervals() function to specify the various levels + and their timeout intervals. + + Finally, the QWSServer class controls the cursor's appearance, + i.e., use the setCursorVisible() function to hide or show the + cursor, and the isCursorVisible() function to determine whether + the cursor is visible on the display or not. + + See also: QScreen and \l{Qt for Embedded Linux Display Management}. + + \section1 Input Method Handling + + Whenever the server receives an event, it queries its stack of + top-level windows to find the window containing the event's + position (each window can identify the client application that + created it). Then the server forwards the event to the appropriate + client. If an input method is installed, it is used as a filter + between the server and the client application. + + Derive from the QWSInputMethod class to create custom input + methods, and use the server's setCurrentInputMethod() function to + install it. Use the sendIMEvent() and sendIMQuery() functions to + send input method events and queries. + + QWSServer provides the IMMouse enum describing the various mouse + events recognized by the QWSInputMethod::mouseHandler() + function. The latter function allows subclasses of QWSInputMethod + to handle mouse events within the preedit text. + + \sa QWSInputMethod +*/ + +/*! + \enum QWSServer::IMState + \obsolete + + This enum describes the various states of an input method. + + \value IMCompose Composing. + \value IMStart Equivalent to IMCompose. + \value IMEnd Finished composing. + + \sa QWSInputMethod::sendIMEvent() +*/ + +/*! + \enum QWSServer::IMMouse + + This enum describes the various types of mouse events recognized + by the QWSInputMethod::mouseHandler() function. + + \value MousePress An event generated by pressing a mouse button. + \value MouseRelease An event generated by relasing a mouse button. + \value MouseMove An event generated by moving the mouse cursor. + \value MouseOutside This value is only reserved, i.e., it is not used in + current implementations. + + \sa QWSInputMethod, setCurrentInputMethod() +*/ + +/*! + \enum QWSServer::ServerFlags + \internal + + This enum is used to pass various options to the window system + server. + + \value DisableKeyboard Ignore all keyboard input. + \value DisableMouse Ignore all mouse input. +*/ + +/*! + \enum QWSServer::WindowEvent + + This enum specifies the various events that can occur in a + top-level window. + + \value Create A new window has been created (by the QWidget constructor). + \value Destroy The window has been closed and deleted (by the QWidget destructor). + \value Hide The window has been hidden using the QWidget::hide() function. + \value Show The window has been shown using the QWidget::show() function or similar. + \value Raise The window has been raised to the top of the desktop. + \value Lower The window has been lowered. + \value Geometry The window has changed size or position. + \value Active The window has become the active window (i.e., it has keyboard focus). + \value Name The window has been named. + + \sa windowEvent() +*/ + +/*! + \fn void QWSServer::markedText(const QString &selection) + + This signal is emitted whenever some text is selected in any of + the running applications, passing the selected text in the \a + selection parameter. + + \sa windowEvent() +*/ + +/*! + \fn const QList &QWSServer::clientWindows() + + Returns the list of current top-level windows. + + Note that the collection of top-level windows changes as + applications add and remove widgets so it should not be stored for + future use. The windows are sorted in stacking order from top-most + to bottom-most. + + Use the QWSWindow::client() function to retrieve the client + application that owns a given window. + + \sa windowAt(), instance() +*/ + +/*! + \fn void QWSServer::newChannel(const QString& channel) + + This signal is emitted whenever a new QCopChannel object is + created, passing the channel's name in the \a channel parameter. + + \sa removedChannel() +*/ + +/*! + \fn void QWSServer::removedChannel(const QString& channel) + + This signal is emitted immediately after the given the QCopChannel + object specified by \a channel, is destroyed. + + Note that a channel is not destroyed until all its listeners have + been unregistered. + + \sa newChannel() +*/ + +/*! + \fn QWSServer::QWSServer(int flags, QObject *parent) + \internal + + Construct a QWSServer object with the given \a parent. The \a + flags are used for keyboard and mouse settings. + + \warning This class is instantiated by QApplication for + \l{Qt for Embedded Linux} server processes. You should never construct + this class yourself. + + \sa {Running Applications} +*/ + +/*! + \fn static QWSServer* QWSServer::instance() + \since 4.2 + + Returns a pointer to the server instance. + + Note that the pointer will be 0 if the application is not the + server, i.e., if the QApplication::type() function doesn't return + QApplication::GuiServer. + + \sa clientWindows(), windowAt() +*/ + +struct QWSCommandStruct +{ + QWSCommandStruct(QWSCommand *c, QWSClient *cl) :command(c),client(cl){} + ~QWSCommandStruct() { delete command; } + + QWSCommand *command; + QWSClient *client; + +}; + +QWSServer::QWSServer(int flags, QObject *parent) : + QObject(*new QWSServerPrivate, parent) +{ + Q_D(QWSServer); + QT_TRY { + d->initServer(flags); + } QT_CATCH(...) { + qwsServer = 0; + qwsServerPrivate = 0; + QT_RETHROW; + } +} + +#ifdef QT3_SUPPORT +/*! + Use the two-argument overload and call the + QObject::setObjectName() function instead. +*/ +QWSServer::QWSServer(int flags, QObject *parent, const char *name) : + QObject(*new QWSServerPrivate, parent) +{ + Q_D(QWSServer); + setObjectName(QString::fromAscii(name)); + d->initServer(flags); +} +#endif + + +#ifndef QT_NO_QWS_MULTIPROCESS +static void ignoreSignal(int) {} // Used to eat SIGPIPE signals below +#endif + +bool QWSServerPrivate::screensaverblockevent( int index, int *screensaverinterval, bool isDown ) +{ + static bool ignoreEvents[2] = { false, false }; + if ( isDown ) { + if ( !ignoreEvents[index] ) { + bool wake = false; + if ( screensaverintervals ) { + if ( screensaverinterval != screensaverintervals ) { + wake = true; + } + } + if ( screensaverblockevents && wake ) { +#ifdef EVENT_BLOCK_DEBUG + qDebug( "waking the screen" ); +#endif + ignoreEvents[index] = true; + } else if ( !screensaverblockevents ) { +#ifdef EVENT_BLOCK_DEBUG + qDebug( "the screen was already awake" ); +#endif + ignoreEvents[index] = false; + } + } + } else { + if ( ignoreEvents[index] ) { +#ifdef EVENT_BLOCK_DEBUG + qDebug( "mouseup?" ); +#endif + ignoreEvents[index] = false; + return true; + } + } + return ignoreEvents[index]; +} + +void QWSServerPrivate::initServer(int flags) +{ + Q_Q(QWSServer); + Q_ASSERT(!qwsServer); + qwsServer = q; + qwsServerPrivate = this; + disablePainting = false; +#ifndef QT_NO_QWS_MULTIPROCESS + ssocket = new QWSServerSocket(qws_qtePipeFilename(), q); + QObject::connect(ssocket, SIGNAL(newConnection()), q, SLOT(_q_newConnection())); + + if ( !ssocket->isListening()) { + perror("QWSServerPrivate::initServer: server socket not listening"); + qFatal("Failed to bind to %s", qws_qtePipeFilename().toLatin1().constData()); + } + + struct linger tmp; + tmp.l_onoff=1; + tmp.l_linger=0; + setsockopt(ssocket->socketDescriptor(),SOL_SOCKET,SO_LINGER,(char *)&tmp,sizeof(tmp)); + + + signal(SIGPIPE, ignoreSignal); //we get it when we read +#endif + focusw = 0; + mouseGrabber = 0; + mouseGrabbing = false; + inputMethodMouseGrabbed = false; + keyboardGrabber = 0; + keyboardGrabbing = false; +#ifndef QT_NO_QWS_CURSOR + haveviscurs = false; + cursor = 0; + nextCursor = 0; +#endif + +#ifndef QT_NO_QWS_MULTIPROCESS + + if (!geteuid()) { +#if defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) + if(mount(0,"/var/shm", "shm", 0, 0)) { + /* This just confuses people with 2.2 kernels + if (errno != EBUSY) + qDebug("Failed mounting shm fs on /var/shm: %s",strerror(errno)); + */ + } +#endif + } +#endif + + // no selection yet + selectionOwner.windowid = -1; + selectionOwner.time.set(-1, -1, -1, -1); + + cleanupFontsDir(); + + // initialize the font database + // from qfontdatabase_qws.cpp + extern void qt_qws_init_fontdb(); + qt_qws_init_fontdb(); + + openDisplay(); + + screensavertimer = new QTimer(q); + screensavertimer->setSingleShot(true); + QObject::connect(screensavertimer, SIGNAL(timeout()), q, SLOT(_q_screenSaverTimeout())); + _q_screenSaverWake(); + + clientMap[-1] = new QWSClient(q, 0, 0); + + if (!bgBrush) + bgBrush = new QBrush(QColor(0x20, 0xb0, 0x50)); + + initializeCursor(); + + // input devices + if (!(flags&QWSServer::DisableMouse)) { + q->openMouse(); + } +#ifndef QT_NO_QWS_KEYBOARD + if (!(flags&QWSServer::DisableKeyboard)) { + q->openKeyboard(); + } +#endif + +#if !defined(QT_NO_SOUND) && !defined(QT_EXTERNAL_SOUND_SERVER) && !defined(Q_OS_DARWIN) + soundserver = new QWSSoundServer(q); +#endif +} + +/*! + \internal + Destructs this server. +*/ +QWSServer::~QWSServer() +{ + closeMouse(); +#ifndef QT_NO_QWS_KEYBOARD + closeKeyboard(); +#endif + d_func()->cleanupFonts(/*force =*/true); +} + +/*! + \internal + */ +void QWSServer::timerEvent(QTimerEvent *e) +{ + Q_D(QWSServer); + if (e->timerId() == d->fontCleanupTimer.timerId()) { + d->cleanupFonts(); + d->fontCleanupTimer.stop(); + } else { + QObject::timerEvent(e); + } +} + +const QList &QWSServer::clientWindows() +{ + Q_D(QWSServer); + return d->windows; +} + +/*! + \internal +*/ +void QWSServerPrivate::releaseMouse(QWSWindow* w) +{ + if (w && mouseGrabber == w) { + mouseGrabber = 0; + mouseGrabbing = false; +#ifndef QT_NO_QWS_CURSOR + if (nextCursor) { + // Not grabbing -> set the correct cursor + setCursor(nextCursor); + nextCursor = 0; + } +#endif + } +} + +/*! + \internal +*/ +void QWSServerPrivate::releaseKeyboard(QWSWindow* w) +{ + if (keyboardGrabber == w) { + keyboardGrabber = 0; + keyboardGrabbing = false; + } +} + +void QWSServerPrivate::handleWindowClose(QWSWindow *w) +{ + w->shuttingDown(); + if (focusw == w) + setFocus(w,false); + if (mouseGrabber == w) + releaseMouse(w); + if (keyboardGrabber == w) + releaseKeyboard(w); +} + + +#ifndef QT_NO_QWS_MULTIPROCESS +/*! + \internal +*/ +void QWSServerPrivate::_q_newConnection() +{ + Q_Q(QWSServer); + while (QWS_SOCK_BASE *sock = ssocket->nextPendingConnection()) { + int socket = sock->socketDescriptor(); + sock->setParent(0); + + QWSClient *client = new QWSClient(q,sock, get_object_id()); + clientMap[socket] = client; + +#ifndef QT_NO_SXE +#ifdef QTRANSPORTAUTH_DEBUG + qDebug( "Transport auth connected: unix stream socket %d", socket ); +#endif + // get a handle to the per-process authentication service + QTransportAuth *a = QTransportAuth::getInstance(); + + // assert that this transport is trusted + QTransportAuth::Data *d = a->connectTransport( + QTransportAuth::UnixStreamSock | + QTransportAuth::Trusted, socket ); + + QAuthDevice *ad = a->recvBuf( d, sock ); + ad->setClient(client); + + QObject::connect(ad, SIGNAL(readyRead()), + q, SLOT(_q_doClient())); + + QObject::connect(client, SIGNAL(connectionClosed()), + q, SLOT(_q_clientClosed())); +#else + QObject::connect(client, SIGNAL(readyRead()), + q, SLOT(_q_doClient())); + QObject::connect(client, SIGNAL(connectionClosed()), + q, SLOT(_q_clientClosed())); +#endif // QT_NO_SXE + + client->sendConnectedEvent(qws_display_spec.constData()); + + if (clientMap.contains(socket)) { + QList screens = qt_screen->subScreens(); + if (screens.isEmpty()) + screens.append(qt_screen); + for (int i = 0; i < screens.size(); ++i) { + const QApplicationPrivate *ap = QApplicationPrivate::instance(); + QScreen *screen = screens.at(i); + const QRect rect = ap->maxWindowRect(screen); + if (!rect.isEmpty()) + client->sendMaxWindowRectEvent(rect); + if (screen->isTransformed()) { + QWSScreenTransformationEvent event; + event.simpleData.screen = i; + event.simpleData.transformation = screen->transformOrientation(); + client->sendEvent(&event); + } + } + } + + // pre-provide some object id's + QWSCreateCommand cmd(30); + invokeCreate(&cmd, client); + } +} +/*! + \internal +*/ +void QWSServerPrivate::_q_clientClosed() +{ + Q_Q(QWSServer); + QWSClient* cl = (QWSClient*)q->sender(); + + // Remove any queued commands for this client + int i = 0; + while (i < commandQueue.size()) { + QWSCommandStruct *cs = commandQueue.at(i); + if (cs->client == cl) { + commandQueue.removeAt(i); + delete cs; + } else { + ++i; + } + } + +#ifndef QT_NO_COP + // Enfore unsubscription from all channels. + QCopChannel::detach(cl); +#endif + + // Shut down all windows for this client + for (int i = 0; i < windows.size(); ++i) { + QWSWindow* w = windows.at(i); + if (w->forClient(cl)) + w->shuttingDown(); + } + + // Delete all windows for this client + QRegion exposed; + i = 0; + while (i < windows.size()) { + QWSWindow* w = windows.at(i); + if (w->forClient(cl)) { + windows.takeAt(i); + w->c = 0; //so we don't send events to it anymore + releaseMouse(w); + releaseKeyboard(w); + exposed += w->allocatedRegion(); +// rgnMan->remove(w->allocationIndex()); + if (focusw == w) + setFocus(focusw,0); + if (mouseGrabber == w) + releaseMouse(w); + if (i < nReserved) + --nReserved; +#ifndef QT_NO_QWS_PROPERTIES + propertyManager.removeProperties(w->winId()); +#endif + emit q->windowEvent(w, QWSServer::Destroy); + w->d->state = QWSWindow::Destroyed; //??? + deletedWindows.append(w); + } else { + ++i; + } + } + if (deletedWindows.count()) + QTimer::singleShot(0, q, SLOT(_q_deleteWindowsLater())); + + QWSClientPrivate *clientPrivate = cl->d_func(); + if (!clientPrivate->shutdown) { +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "client" << cl->clientId() << "crashed"; +#endif + // this would be the place to emit a signal to notify about the + // crash of a client + crashedClientIds.append(cl->clientId()); + fontCleanupTimer.start(10, q_func()); + } + clientPrivate->shutdown = true; + + while (!clientPrivate->usedFonts.isEmpty()) { + const QByteArray font = *clientPrivate->usedFonts.begin(); +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "dereferencing font" << font << "from disconnected client"; +#endif + dereferenceFont(clientPrivate, font); + } + clientPrivate->usedFonts.clear(); + + //qDebug("removing client %d with socket %d", cl->clientId(), cl->socket()); + clientMap.remove(cl->socket()); + if (cl == cursorClient) + cursorClient = 0; + if (qt_screen->clearCacheFunc) + (qt_screen->clearCacheFunc)(qt_screen, cl->clientId()); // remove any remaining cache entries. + cl->deleteLater(); + + update_regions(); + exposeRegion(exposed); +} + +void QWSServerPrivate::_q_deleteWindowsLater() +{ + qDeleteAll(deletedWindows); + deletedWindows.clear(); +} + +#endif //QT_NO_QWS_MULTIPROCESS + +void QWSServerPrivate::referenceFont(QWSClientPrivate *client, const QByteArray &font) +{ + if (!client->usedFonts.contains(font)) { + client->usedFonts.insert(font); + + ++fontReferenceCount[font]; +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "Client" << client->q_func()->clientId() << "added font" << font; + qDebug() << "Refcount is" << fontReferenceCount[font]; +#endif + } +} + +void QWSServerPrivate::dereferenceFont(QWSClientPrivate *client, const QByteArray &font) +{ + if (client->usedFonts.contains(font)) { + client->usedFonts.remove(font); + + Q_ASSERT(fontReferenceCount[font]); + if (!--fontReferenceCount[font] && !fontCleanupTimer.isActive()) + fontCleanupTimer.start(FontCleanupInterval, q_func()); + +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "Client" << client->q_func()->clientId() << "removed font" << font; + qDebug() << "Refcount is" << fontReferenceCount[font]; +#endif + } +} + +static void cleanupFontsDir() +{ + static bool dontDelete = !qgetenv("QWS_KEEP_FONTS").isEmpty(); + if (dontDelete) + return; + + extern QString qws_fontCacheDir(); + QDir dir(qws_fontCacheDir(), QLatin1String("*.qsf")); + for (uint i = 0; i < dir.count(); ++i) { +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "removing stale font file" << dir[i]; +#endif + dir.remove(dir[i]); + } +} + +void QWSServerPrivate::cleanupFonts(bool force) +{ + static bool dontDelete = !qgetenv("QWS_KEEP_FONTS").isEmpty(); + if (dontDelete) + return; + +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "cleanupFonts()"; +#endif + if (!fontReferenceCount.isEmpty()) { + QMap::Iterator it = fontReferenceCount.begin(); + while (it != fontReferenceCount.end()) { + if (it.value() && !force) { + ++it; + continue; + } + + const QByteArray &fontName = it.key(); +#if defined(QWS_DEBUG_FONTCLEANUP) + qDebug() << "removing unused font file" << fontName; +#endif + QT_TRY { + QFile::remove(QFile::decodeName(fontName)); + sendFontRemovedEvent(fontName); + + it = fontReferenceCount.erase(it); + } QT_CATCH(...) { + // so we were not able to remove the font. + // don't be angry and just continue with the next ones. + ++it; + } + } + } + + if (crashedClientIds.isEmpty()) + return; + + QList removedFonts; +#if !defined(QT_NO_QWS_QPF2) && !defined(QT_FONTS_ARE_RESOURCES) + removedFonts = QFontEngineQPF::cleanUpAfterClientCrash(crashedClientIds); +#endif + crashedClientIds.clear(); + + for (int i = 0; i < removedFonts.count(); ++i) + sendFontRemovedEvent(removedFonts.at(i)); +} + +void QWSServerPrivate::sendFontRemovedEvent(const QByteArray &font) +{ + QWSFontEvent event; + event.simpleData.type = QWSFontEvent::FontRemoved; + event.setData(font.constData(), font.length(), false); + + QMap::const_iterator it = clientMap.constBegin(); + for (; it != clientMap.constEnd(); ++it) + (*it)->sendEvent(&event); +} + +/*! + \internal +*/ +QWSCommand* QWSClient::readMoreCommand() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + QIODevice *socket = 0; +#endif +#ifndef QT_NO_SXE + if (socketDescriptor != -1) // not server socket + socket = QTransportAuth::getInstance()->passThroughByClient( this ); +#if QTRANSPORTAUTH_DEBUG + if (socket) { + char displaybuf[1024]; + qint64 bytes = socket->bytesAvailable(); + if ( bytes > 511 ) bytes = 511; + hexstring( displaybuf, ((unsigned char *)(reinterpret_cast(socket)->buffer().constData())), bytes ); + qDebug( "readMoreCommand: %lli bytes - %s", socket->bytesAvailable(), displaybuf ); + } +#endif +#endif // QT_NO_SXE + +#ifndef QT_NO_QWS_MULTIPROCESS + if (!socket) + socket = csocket; // server socket + if (socket) { + // read next command + if (!command) { + int command_type = qws_read_uint(socket); + + if (command_type >= 0) + command = QWSCommand::factory(command_type); + } + if (command) { + if (command->read(socket)) { + // Finished reading a whole command. + QWSCommand* result = command; + command = 0; + return result; + } + } + + // Not finished reading a whole command. + return 0; + } else +#endif // QT_NO_QWS_MULTIPROCESS + { + QList *serverQueue = qt_get_server_queue(); + return serverQueue->isEmpty() ? 0 : serverQueue->takeFirst(); + } +} + + +/*! + \internal +*/ +void QWSServer::processEventQueue() +{ + if (qwsServerPrivate) + qwsServerPrivate->doClient(qwsServerPrivate->clientMap.value(-1)); +} + + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSServerPrivate::_q_doClient() +{ + Q_Q(QWSServer); + + QWSClient* client; +#ifndef QT_NO_SXE + QAuthDevice *ad = qobject_cast(q->sender()); + if (ad) + client = (QWSClient*)ad->client(); + else +#endif + client = (QWSClient*)q->sender(); + + if (doClientIsActive) { + pendingDoClients.append(client); + return; + } + doClientIsActive = true; + + doClient(client); + + while (!pendingDoClients.isEmpty()) { + doClient(pendingDoClients.takeFirst()); + } + + doClientIsActive = false; +} +#endif // QT_NO_QWS_MULTIPROCESS + +void QWSServerPrivate::doClient(QWSClient *client) +{ + QWSCommand* command=client->readMoreCommand(); + + while (command) { + QWSCommandStruct *cs = new QWSCommandStruct(command, client); + commandQueue.append(cs); + // Try for some more... + command=client->readMoreCommand(); + } + + while (!commandQueue.isEmpty()) { + QWSCommandStruct *cs = commandQueue.takeAt(0); + switch (cs->command->type) { + case QWSCommand::Identify: + invokeIdentify((QWSIdentifyCommand*)cs->command, cs->client); + break; + case QWSCommand::Create: + invokeCreate((QWSCreateCommand*)cs->command, cs->client); + break; +#ifndef QT_NO_QWS_MULTIPROCESS + case QWSCommand::Shutdown: + cs->client->d_func()->shutdown = true; + break; +#endif + case QWSCommand::RegionName: + invokeRegionName((QWSRegionNameCommand*)cs->command, cs->client); + break; + case QWSCommand::Region: + invokeRegion((QWSRegionCommand*)cs->command, cs->client); + cs->client->d_func()->unlockCommunication(); + break; + case QWSCommand::RegionMove: + invokeRegionMove((QWSRegionMoveCommand*)cs->command, cs->client); + cs->client->d_func()->unlockCommunication(); + break; + case QWSCommand::RegionDestroy: + invokeRegionDestroy((QWSRegionDestroyCommand*)cs->command, cs->client); + break; +#ifndef QT_NO_QWS_PROPERTIES + case QWSCommand::AddProperty: + invokeAddProperty((QWSAddPropertyCommand*)cs->command); + break; + case QWSCommand::SetProperty: + invokeSetProperty((QWSSetPropertyCommand*)cs->command); + break; + case QWSCommand::RemoveProperty: + invokeRemoveProperty((QWSRemovePropertyCommand*)cs->command); + break; + case QWSCommand::GetProperty: + invokeGetProperty((QWSGetPropertyCommand*)cs->command, cs->client); + break; +#endif + case QWSCommand::SetSelectionOwner: + invokeSetSelectionOwner((QWSSetSelectionOwnerCommand*)cs->command); + break; + case QWSCommand::RequestFocus: + invokeSetFocus((QWSRequestFocusCommand*)cs->command, cs->client); + break; + case QWSCommand::ChangeAltitude: + invokeSetAltitude((QWSChangeAltitudeCommand*)cs->command, + cs->client); + cs->client->d_func()->unlockCommunication(); + break; + case QWSCommand::SetOpacity: + invokeSetOpacity((QWSSetOpacityCommand*)cs->command, + cs->client); + break; + +#ifndef QT_NO_QWS_CURSOR + case QWSCommand::DefineCursor: + invokeDefineCursor((QWSDefineCursorCommand*)cs->command, cs->client); + break; + case QWSCommand::SelectCursor: + invokeSelectCursor((QWSSelectCursorCommand*)cs->command, cs->client); + break; + case QWSCommand::PositionCursor: + invokePositionCursor((QWSPositionCursorCommand*)cs->command, cs->client); + break; +#endif + case QWSCommand::GrabMouse: + invokeGrabMouse((QWSGrabMouseCommand*)cs->command, cs->client); + break; + case QWSCommand::GrabKeyboard: + invokeGrabKeyboard((QWSGrabKeyboardCommand*)cs->command, cs->client); + break; +#if !defined(QT_NO_SOUND) && !defined(Q_OS_DARWIN) + case QWSCommand::PlaySound: + invokePlaySound((QWSPlaySoundCommand*)cs->command, cs->client); + break; +#endif +#ifndef QT_NO_COP + case QWSCommand::QCopRegisterChannel: + invokeRegisterChannel((QWSQCopRegisterChannelCommand*)cs->command, + cs->client); + break; + case QWSCommand::QCopSend: + invokeQCopSend((QWSQCopSendCommand*)cs->command, cs->client); + break; +#endif +#ifndef QT_NO_QWS_INPUTMETHODS + case QWSCommand::IMUpdate: + invokeIMUpdate((QWSIMUpdateCommand*)cs->command, cs->client); + break; + case QWSCommand::IMResponse: + invokeIMResponse((QWSIMResponseCommand*)cs->command, cs->client); + break; + case QWSCommand::IMMouse: + { + if (current_IM) { + QWSIMMouseCommand *cmd = (QWSIMMouseCommand *) cs->command; + current_IM->mouseHandler(cmd->simpleData.index, + cmd->simpleData.state); + } + } + break; +#endif + case QWSCommand::Font: + invokeFont((QWSFontCommand *)cs->command, cs->client); + break; + case QWSCommand::RepaintRegion: + invokeRepaintRegion((QWSRepaintRegionCommand*)cs->command, + cs->client); + cs->client->d_func()->unlockCommunication(); + break; +#ifndef QT_NO_QWSEMBEDWIDGET + case QWSCommand::Embed: + invokeEmbed(static_cast(cs->command), + cs->client); + break; +#endif + case QWSCommand::ScreenTransform: + invokeScreenTransform(static_cast(cs->command), + cs->client); + break; + } + delete cs; + } +} + + +void QWSServerPrivate::showCursor() +{ +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->show(); +#endif +} + +void QWSServerPrivate::hideCursor() +{ +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->hide(); +#endif +} + +/*! + \fn void QWSServer::enablePainting(bool enable) + + Enables painting onto the screen if \a enable is true; otherwise + painting is disabled. + + \sa {Qt for Embedded Linux Architecture#Drawing on Screen}{Qt for Embedded Linux + Architecture} +*/ +void QWSServer::enablePainting(bool enable) +{ + Q_D(QWSServer); + + if (d->disablePainting == !enable) + return; + + d->disablePainting = !enable; + + if (enable) { + // Reset the server side allocated regions to ensure update_regions() + // will send out region events. + for (int i = 0; i < d->windows.size(); ++i) { + QWSWindow *w = d->windows.at(i); + w->setAllocatedRegion(QRegion()); +#ifdef QT_QWS_CLIENTBLIT + w->setDirectPaintRegion(QRegion()); +#endif + } + d->update_regions(); + d->showCursor(); + } else { + // Disable painting by clients by taking away their allocated region. + // To ensure mouse events are still delivered to the correct windows, + // the allocated regions are not modified on the server. + for (int i = 0; i < d->windows.size(); ++i) { + QWSWindow *w = d->windows.at(i); + w->client()->sendRegionEvent(w->winId(), QRegion(), + QWSRegionEvent::Allocation); +#ifdef QT_QWS_CLIENTBLIT + w->client()->sendRegionEvent(w->winId(), QRegion(), + QWSRegionEvent::DirectPaint); +#endif + } + d->hideCursor(); + } +} + +/*! + Refreshes the display by making the screen driver update the + entire display. + + \sa QScreen::exposeRegion() +*/ +void QWSServer::refresh() +{ + Q_D(QWSServer); + d->exposeRegion(QScreen::instance()->region()); +//### send repaint to non-buffered windows +} + +/*! + \fn void QWSServer::refresh(QRegion & region) + \overload + + Refreshes the given \a region of the display. +*/ +void QWSServer::refresh(QRegion & r) +{ + Q_D(QWSServer); + d->exposeRegion(r); +//### send repaint to non-buffered windows +} + +/*! + \fn void QWSServer::setMaxWindowRect(const QRect& rectangle) + + Sets the maximum area of the screen that \l{Qt for Embedded Linux} + applications can use, to be the given \a rectangle. + + Note that this function can only be used in the server process. + + \sa QWidget::showMaximized() +*/ +void QWSServer::setMaxWindowRect(const QRect &rect) +{ + QList subScreens = qt_screen->subScreens(); + if (subScreens.isEmpty() && qt_screen != 0) + subScreens.append(qt_screen); + + for (int i = 0; i < subScreens.size(); ++i) { + const QScreen *screen = subScreens.at(i); + const QRect r = (screen->region() & rect).boundingRect(); + if (r.isEmpty()) + continue; + + QApplicationPrivate *ap = QApplicationPrivate::instance(); + if (ap->maxWindowRect(screen) != r) { + ap->setMaxWindowRect(screen, i, r); + qwsServerPrivate->sendMaxWindowRectEvents(r); + } + } +} + +/*! + \internal +*/ +void QWSServerPrivate::sendMaxWindowRectEvents(const QRect &rect) +{ + QMap::const_iterator it = clientMap.constBegin(); + for (; it != clientMap.constEnd(); ++it) + (*it)->sendMaxWindowRectEvent(rect); +} + +/*! + \fn void QWSServer::setDefaultMouse(const char *mouseDriver) + + Sets the mouse driver that will be used if the QWS_MOUSE_PROTO + environment variable is not defined, to be the given \a + mouseDriver. + + Note that the default is platform-dependent. This function can + only be used in the server process. + + + \sa setMouseHandler(), {Qt for Embedded Linux Pointer Handling} +*/ +void QWSServer::setDefaultMouse(const char *m) +{ + *defaultMouse() = QString::fromAscii(m); +} + +/*! + \fn void QWSServer::setDefaultKeyboard(const char *keyboardDriver) + + Sets the keyboard driver that will be used if the QWS_KEYBOARD + environment variable is not defined, to be the given \a + keyboardDriver. + + Note that the default is platform-dependent. This function can + only be used in the server process. + + \sa setKeyboardHandler(), {Qt for Embedded Linux Character Input} +*/ +void QWSServer::setDefaultKeyboard(const char *k) +{ + *defaultKeyboard() = QString::fromAscii(k); +} + +#ifndef QT_NO_QWS_CURSOR +static bool prevWin; +#endif + + +extern int *qt_last_x,*qt_last_y; + + +/*! + \internal + + Send a mouse event. \a pos is the screen position where the mouse + event occurred and \a state is a mask indicating which buttons are + pressed. + + \a pos is in device coordinates +*/ +void QWSServer::sendMouseEvent(const QPoint& pos, int state, int wheel) +{ + bool block = qwsServerPrivate->screensaverblockevent(MOUSE, qwsServerPrivate->screensaverinterval, state); +#ifdef EVENT_BLOCK_DEBUG + qDebug() << "sendMouseEvent" << pos.x() << pos.y() << state << (block ? "block" : "pass"); +#endif + + if (state || wheel) + qwsServerPrivate->_q_screenSaverWake(); + + if ( block ) + return; + + QPoint tpos; + // transformations + if (qt_screen->isTransformed()) { + QSize s = QSize(qt_screen->deviceWidth(), qt_screen->deviceHeight()); + tpos = qt_screen->mapFromDevice(pos, s); + } else { + tpos = pos; + } + + if (qt_last_x) { + *qt_last_x = tpos.x(); + *qt_last_y = tpos.y(); + } + QWSServer::mousePosition = tpos; + qwsServerPrivate->mouseState = state; + +#ifndef QT_NO_QWS_INPUTMETHODS + const int btnMask = Qt::LeftButton | Qt::RightButton | Qt::MidButton; + int stroke_count; // number of strokes to keep shown. + if (force_reject_strokeIM || !current_IM) + { + stroke_count = 0; + } else { + stroke_count = current_IM->filter(tpos, state, wheel); + } + + if (stroke_count == 0) { + if (state&btnMask) + force_reject_strokeIM = true; + QWSServerPrivate::sendMouseEventUnfiltered(tpos, state, wheel); + } + // stop force reject after stroke ends. + if (state&btnMask && force_reject_strokeIM) + force_reject_strokeIM = false; + // on end of stroke, force_rejct + // and once a stroke is rejected, do not try again till pen is lifted +#else + QWSServerPrivate::sendMouseEventUnfiltered(tpos, state, wheel); +#endif // end QT_NO_QWS_FSIM +} + +void QWSServerPrivate::sendMouseEventUnfiltered(const QPoint &pos, int state, int wheel) +{ + const int btnMask = Qt::LeftButton | Qt::RightButton | Qt::MidButton; + QWSMouseEvent event; + + QWSWindow *win = qwsServer->windowAt(pos); + + QWSClient *serverClient = qwsServerPrivate->clientMap.value(-1); + QWSClient *winClient = win ? win->client() : 0; + + + bool imMouse = false; +#ifndef QT_NO_QWS_INPUTMETHODS + // check for input method window + if (current_IM && current_IM_winId != -1) { + QWSWindow *kbw = keyboardGrabber ? keyboardGrabber : + qwsServerPrivate->focusw; + + imMouse = kbw == win; + if ( !imMouse ) { + QWidget *target = winClient == serverClient ? + QApplication::widgetAt(pos) : 0; + imMouse = target && (target->testAttribute(Qt::WA_InputMethodTransparent)); + } + } +#endif + + //If grabbing window disappears, grab is still active until + //after mouse release. + if ( qwsServerPrivate->mouseGrabber && (!imMouse || qwsServerPrivate->inputMethodMouseGrabbed)) { + win = qwsServerPrivate->mouseGrabber; + winClient = win ? win->client() : 0; + } + event.simpleData.window = win ? win->id : 0; + +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->move(pos.x(),pos.y()); + + // Arrow cursor over desktop + // prevWin remembers if the last event was over a window + if (!win && prevWin) { + if (!qwsServerPrivate->mouseGrabber) + qwsServerPrivate->setCursor(QWSCursor::systemCursor(Qt::ArrowCursor)); + else + qwsServerPrivate->nextCursor = QWSCursor::systemCursor(Qt::ArrowCursor); + prevWin = false; + } + // reset prevWin + if (win && !prevWin) + prevWin = true; +#endif + + if ((state&btnMask) && !qwsServerPrivate->mouseGrabbing) { + qwsServerPrivate->mouseGrabber = win; + if (imMouse) + qwsServerPrivate->inputMethodMouseGrabbed = true; + } + if (!(state&btnMask)) + qwsServerPrivate->inputMethodMouseGrabbed = false; + + event.simpleData.x_root=pos.x(); + event.simpleData.y_root=pos.y(); + event.simpleData.state=state | qws_keyModifiers; + event.simpleData.delta = wheel; + event.simpleData.time=qwsServerPrivate->timer.elapsed(); + + static int oldstate = 0; + +#ifndef QT_NO_QWS_INPUTMETHODS + //tell the input method if we click on a different window that is not IM transparent + bool isPress = state > oldstate; + if (isPress && !imMouse && current_IM && current_IM_winId != -1) + current_IM->mouseHandler(-1, QWSServer::MouseOutside); +#endif + + if (serverClient) + serverClient->sendEvent(&event); + if (winClient && winClient != serverClient) + winClient->sendEvent(&event); + + if ( !imMouse ) { + // Make sure that if we leave a window, that window gets one last mouse + // event so that it knows the mouse has left. + QWSClient *oldClient = qwsServer->d_func()->cursorClient; + if (oldClient && oldClient != winClient && oldClient != serverClient) { + event.simpleData.state = oldstate | qws_keyModifiers; + oldClient->sendEvent(&event); + } + } + + oldstate = state; + if ( !imMouse ) + qwsServer->d_func()->cursorClient = winClient; + + if (!(state&btnMask) && !qwsServerPrivate->mouseGrabbing) + qwsServerPrivate->releaseMouse(qwsServerPrivate->mouseGrabber); +} + +/*! + Returns the primary mouse driver. + + Note that this function can only be used in the server process. + + \sa setMouseHandler(), openMouse(), closeMouse() +*/ +QWSMouseHandler *QWSServer::mouseHandler() +{ + if (qwsServerPrivate->mousehandlers.empty()) + return 0; + return qwsServerPrivate->mousehandlers.first(); +} + +/*! + \since 4.5 + + Returns list of all mouse handlers + + Note that this function can only be used in the server process. + + \sa mouseHandler(), setMouseHandler(), openMouse(), closeMouse() +*/ +const QList& QWSServer::mouseHandlers() +{ + return qwsServerPrivate->mousehandlers; +} + + +// called by QWSMouseHandler constructor, not user code. +/*! + \fn void QWSServer::setMouseHandler(QWSMouseHandler* driver) + + Sets the primary mouse driver to be the given \a driver. + + \l{Qt for Embedded Linux} provides several ready-made mouse drivers, and + custom drivers are typically added using Qt's plugin + mechanism. See the \l{Qt for Embedded Linux Pointer Handling} documentation + for details. + + Note that this function can only be used in the server process. + + \sa mouseHandler(), setDefaultMouse() +*/ +void QWSServer::setMouseHandler(QWSMouseHandler* mh) +{ + if (!mh) + return; + qwsServerPrivate->mousehandlers.removeAll(mh); + qwsServerPrivate->mousehandlers.prepend(mh); +} + +/*! + \internal + \obsolete + Caller owns data in list, and must delete contents +*/ +QList * QWSServer::windowList() +{ + QList * ret=new QList; + for (int i=0; i < qwsServerPrivate->windows.size(); ++i) { + QWSWindow *window = qwsServerPrivate->windows.at(i); + QWSInternalWindowInfo * qwi=new QWSInternalWindowInfo(); + qwi->winid=window->winId(); + qwi->clientid=window->client()->clientId(); + ret->append(qwi); + } + return ret; +} + +#ifndef QT_NO_COP +/*! + \internal +*/ +void QWSServerPrivate::sendQCopEvent(QWSClient *c, const QString &ch, + const QString &msg, const QByteArray &data, + bool response) +{ + Q_ASSERT(c); + + QWSQCopMessageEvent event; + event.channel = ch.toLatin1(); + event.message = msg.toLatin1(); + event.data = data; + event.simpleData.is_response = response; + event.simpleData.lchannel = ch.length(); + event.simpleData.lmessage = msg.length(); + event.simpleData.ldata = data.size(); + int l = event.simpleData.lchannel + event.simpleData.lmessage + + event.simpleData.ldata; + + // combine channel, message and data into one block of raw bytes + char *tmp = new char [l]; + char *d = tmp; + memcpy(d, event.channel.constData(), event.simpleData.lchannel); + d += event.simpleData.lchannel; + memcpy(d, event.message.constData(), event.simpleData.lmessage); + d += event.simpleData.lmessage; + memcpy(d, data.constData(), event.simpleData.ldata); + + event.setDataDirect(tmp, l); + + c->sendEvent(&event); +} +#endif + +/*! + \fn QWSWindow *QWSServer::windowAt(const QPoint& position) + + Returns the window containing the given \a position. + + Note that if there is no window under the specified point this + function returns 0. + + \sa clientWindows(), instance() +*/ +QWSWindow *QWSServer::windowAt(const QPoint& pos) +{ + Q_D(QWSServer); + for (int i=0; iwindows.size(); ++i) { + QWSWindow* w = d->windows.at(i); + if (w->allocatedRegion().contains(pos)) + return w; + } + return 0; +} + +#ifndef QT_NO_QWS_KEYBOARD +static int keyUnicode(int keycode) +{ + int code = 0xffff; + + if (keycode >= Qt::Key_A && keycode <= Qt::Key_Z) + code = keycode - Qt::Key_A + 'a'; + else if (keycode >= Qt::Key_0 && keycode <= Qt::Key_9) + code = keycode - Qt::Key_0 + '0'; + + return code; +} +#endif + +/*! + Sends the given key event. The key is identified by its \a unicode + value and the given \a keycode, \a modifiers, \a isPress and \a + autoRepeat parameters. + + Use this function to send key events generated by "virtual + keyboards" (note that the processKeyEvent() function is + impelemented using this function). + + The \a keycode parameter is the Qt keycode value as defined by the + Qt::Key enum. The \a modifiers is an OR combination of + Qt::KeyboardModifier values, indicating whether \gui + Shift/Alt/Ctrl keys are pressed. The \a isPress parameter is true + if the event is a key press event and \a autoRepeat is true if the + event is caused by an auto-repeat mechanism and not an actual key + press. + + Note that this function can only be used in the server process. + + \sa processKeyEvent(), {Qt for Embedded Linux Character Input} +*/ +void QWSServer::sendKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat) +{ + qws_keyModifiers = modifiers; + + if (isPress) { + if (keycode != Qt::Key_F34 && keycode != Qt::Key_F35) + qwsServerPrivate->_q_screenSaverWake(); + } + +#ifndef QT_NO_QWS_INPUTMETHODS + + if (!current_IM || !current_IM->filter(unicode, keycode, modifiers, isPress, autoRepeat)) + QWSServerPrivate::sendKeyEventUnfiltered(unicode, keycode, modifiers, isPress, autoRepeat); +#else + QWSServerPrivate::sendKeyEventUnfiltered(unicode, keycode, modifiers, isPress, autoRepeat); +#endif +} + +void QWSServerPrivate::sendKeyEventUnfiltered(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat) +{ + + QWSKeyEvent event; + QWSWindow *win = keyboardGrabber ? keyboardGrabber : + qwsServerPrivate->focusw; + + event.simpleData.window = win ? win->winId() : 0; + + event.simpleData.unicode = +#ifndef QT_NO_QWS_KEYBOARD + unicode < 0 ? keyUnicode(keycode) : +#endif + unicode; + event.simpleData.keycode = keycode; + event.simpleData.modifiers = modifiers; + event.simpleData.is_press = isPress; + event.simpleData.is_auto_repeat = autoRepeat; + + QWSClient *serverClient = qwsServerPrivate->clientMap.value(-1); + QWSClient *winClient = win ? win->client() : 0; + if (serverClient) + serverClient->sendEvent(&event); + if (winClient && winClient != serverClient) + winClient->sendEvent(&event); +} + +/*! + \internal +*/ +void QWSServer::beginDisplayReconfigure() +{ + qwsServer->enablePainting(false); +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->hide(); +#endif + QWSDisplay::grab(true); + qt_screen->disconnect(); +} + +/*! + \internal +*/ +void QWSServer::endDisplayReconfigure() +{ + qt_screen->connect(QString()); + qwsServerPrivate->swidth = qt_screen->deviceWidth(); + qwsServerPrivate->sheight = qt_screen->deviceHeight(); + + QWSDisplay::ungrab(); +#ifndef QT_NO_QWS_CURSOR + if (qt_screencursor) + qt_screencursor->show(); +#endif + QApplicationPrivate *ap = QApplicationPrivate::instance(); + ap->setMaxWindowRect(qt_screen, 0, + QRect(0, 0, qt_screen->width(), qt_screen->height())); + QSize olds = qApp->desktop()->size(); + qApp->desktop()->resize(qt_screen->width(), qt_screen->height()); + qApp->postEvent(qApp->desktop(), new QResizeEvent(qApp->desktop()->size(), olds)); + qwsServer->enablePainting(true); + qwsServer->refresh(); + qDebug("Desktop size: %dx%d", qApp->desktop()->width(), qApp->desktop()->height()); +} + +void QWSServerPrivate::resetEngine() +{ +#ifndef QT_NO_QWS_CURSOR + if (!qt_screencursor) + return; + qt_screencursor->hide(); + qt_screencursor->show(); +#endif +} + + +#ifndef QT_NO_QWS_CURSOR +/*! + \fn void QWSServer::setCursorVisible(bool visible) + + Shows the cursor if \a visible is true: otherwise the cursor is + hidden. + + Note that this function can only be used in the server process. + + \sa isCursorVisible() +*/ +void QWSServer::setCursorVisible(bool vis) +{ + if (qwsServerPrivate && qwsServerPrivate->haveviscurs != vis) { + QWSCursor* c = qwsServerPrivate->cursor; + qwsServerPrivate->setCursor(QWSCursor::systemCursor(Qt::BlankCursor)); + qwsServerPrivate->haveviscurs = vis; + qwsServerPrivate->setCursor(c); + } +} + +/*! + Returns true if the cursor is visible; otherwise returns false. + + Note that this function can only be used in the server process. + + \sa setCursorVisible() +*/ +bool QWSServer::isCursorVisible() +{ + return qwsServerPrivate ? qwsServerPrivate->haveviscurs : true; +} +#endif + +#ifndef QT_NO_QWS_INPUTMETHODS + + +/*! + \fn void QWSServer::sendIMEvent(const QInputMethodEvent *event) + + Sends the given input method \a event. + + The \c QInputMethodEvent class is derived from QWSEvent, i.e., it + is a QWSEvent object of the QWSEvent::IMEvent type. + + If there is a window actively composing the preedit string, the + event is sent to that window. Otherwise, the event is sent to the + window currently in focus. + + \sa sendIMQuery(), QWSInputMethod::sendEvent() +*/ +void QWSServer::sendIMEvent(const QInputMethodEvent *ime) +{ + QWSIMEvent event; + + QWSWindow *win = keyboardGrabber ? keyboardGrabber : + qwsServerPrivate->focusw; + + //if currently composing then event must go to the composing window + + if (current_IM_composing_win) + win = current_IM_composing_win; + + event.simpleData.window = win ? win->winId() : 0; + event.simpleData.replaceFrom = ime->replacementStart();; + event.simpleData.replaceLength = ime->replacementLength(); + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QDataStream out(&buffer); + + out << ime->preeditString(); + out << ime->commitString(); + + const QList &attributes = ime->attributes(); + for (int i = 0; i < attributes.count(); ++i) { + const QInputMethodEvent::Attribute &a = attributes.at(i); + out << a.type << a.start << a.length << a.value; + } + event.setData(buffer.data(), buffer.size()); + QWSClient *serverClient = qwsServerPrivate->clientMap.value(-1); + if (serverClient) + serverClient->sendEvent(&event); + if (win && win->client() && win->client() != serverClient) + win->client()->sendEvent(&event); + + current_IM_composing_win = ime->preeditString().isEmpty() ? 0 : win; + current_IM_winId = win ? win->winId() : 0; +} + + +/*! + Sends an input method query for the given \a property. + + To receive responses to input method queries, the virtual + QWSInputMethod::queryResponse() function must be reimplemented in + a QWSInputMethod subclass that is activated using the + setCurrentInputMethod() function. + + \sa sendIMEvent(), setCurrentInputMethod() +*/ +void QWSServer::sendIMQuery(int property) +{ + QWSIMQueryEvent event; + + QWSWindow *win = keyboardGrabber ? keyboardGrabber : + qwsServerPrivate->focusw; + if (current_IM_composing_win) + win = current_IM_composing_win; + + event.simpleData.window = win ? win->winId() : 0; + event.simpleData.property = property; + if (win && win->client()) + win->client()->sendEvent(&event); +} + + + +/*! + \fn void QWSServer::setCurrentInputMethod(QWSInputMethod *method) + + Sets the current input method to be the given \a method. + + Note that this function can only be used in the server process. + + \sa sendIMQuery(), sendIMEvent() +*/ +void QWSServer::setCurrentInputMethod(QWSInputMethod *im) +{ + if (current_IM) + current_IM->reset(); //??? send an update event instead ? + current_IM = im; +} + +/*! + \fn static void QWSServer::resetInputMethod() + + \internal +*/ + +#endif //QT_NO_QWS_INPUTMETHODS + +#ifndef QT_NO_QWS_PROPERTIES +/*! + \internal +*/ +void QWSServer::sendPropertyNotifyEvent(int property, int state) +{ + Q_D(QWSServer); + QWSServerPrivate::ClientIterator it = d->clientMap.begin(); + while (it != d->clientMap.end()) { + QWSClient *cl = *it; + ++it; + cl->sendPropertyNotifyEvent(property, state); + } +} +#endif + +void QWSServerPrivate::invokeIdentify(const QWSIdentifyCommand *cmd, QWSClient *client) +{ + client->setIdentity(cmd->id); +#ifndef QT_NO_QWS_MULTIPROCESS + if (client->clientId() > 0) + client->d_func()->setLockId(cmd->simpleData.idLock); +#endif +} + +void QWSServerPrivate::invokeCreate(QWSCreateCommand *cmd, QWSClient *client) +{ + QWSCreationEvent event; + event.simpleData.objectid = get_object_id(cmd->count); + event.simpleData.count = cmd->count; + client->sendEvent(&event); +} + +void QWSServerPrivate::invokeRegionName(const QWSRegionNameCommand *cmd, QWSClient *client) +{ + Q_Q(QWSServer); + QWSWindow* changingw = findWindow(cmd->simpleData.windowid, client); + if (changingw && (changingw->name() != cmd->name || changingw->caption() !=cmd->caption)) { + changingw->setName(cmd->name); + changingw->setCaption(cmd->caption); + emit q->windowEvent(changingw, QWSServer::Name); + } +} + +void QWSServerPrivate::invokeRegion(QWSRegionCommand *cmd, QWSClient *client) +{ +#ifdef QWS_REGION_DEBUG + qDebug("QWSServer::invokeRegion %d rects (%d)", + cmd->simpleData.nrectangles, cmd->simpleData.windowid); +#endif + + QWSWindow* changingw = findWindow(cmd->simpleData.windowid, 0); + if (!changingw) { + qWarning("Invalid window handle %08x",cmd->simpleData.windowid); + return; + } + if (!changingw->forClient(client)) { + qWarning("Disabled: clients changing other client's window region"); + return; + } + + request_region(cmd->simpleData.windowid, cmd->surfaceKey, cmd->surfaceData, + cmd->region); +} + +void QWSServerPrivate::invokeRegionMove(const QWSRegionMoveCommand *cmd, QWSClient *client) +{ + Q_Q(QWSServer); + QWSWindow* changingw = findWindow(cmd->simpleData.windowid, 0); + if (!changingw) { + qWarning("invokeRegionMove: Invalid window handle %d",cmd->simpleData.windowid); + return; + } + if (!changingw->forClient(client)) { + qWarning("Disabled: clients changing other client's window region"); + return; + } + +// changingw->setNeedAck(true); + moveWindowRegion(changingw, cmd->simpleData.dx, cmd->simpleData.dy); + emit q->windowEvent(changingw, QWSServer::Geometry); +} + +void QWSServerPrivate::invokeRegionDestroy(const QWSRegionDestroyCommand *cmd, QWSClient *client) +{ + Q_Q(QWSServer); + QWSWindow* changingw = findWindow(cmd->simpleData.windowid, 0); + if (!changingw) { + qWarning("invokeRegionDestroy: Invalid window handle %d",cmd->simpleData.windowid); + return; + } + if (!changingw->forClient(client)) { + qWarning("Disabled: clients changing other client's window region"); + return; + } + + setWindowRegion(changingw, QRegion()); +// rgnMan->remove(changingw->allocationIndex()); + for (int i = 0; i < windows.size(); ++i) { + if (windows.at(i) == changingw) { + windows.takeAt(i); + if (i < nReserved) + --nReserved; + break; + } + } + + handleWindowClose(changingw); +#ifndef QT_NO_QWS_PROPERTIES + propertyManager.removeProperties(changingw->winId()); +#endif + emit q->windowEvent(changingw, QWSServer::Destroy); + delete changingw; +} + +void QWSServerPrivate::invokeSetFocus(const QWSRequestFocusCommand *cmd, QWSClient *client) +{ + int winId = cmd->simpleData.windowid; + int gain = cmd->simpleData.flag; + + if (gain != 0 && gain != 1) { + qWarning("Only 0(lose) and 1(gain) supported"); + return; + } + + QWSWindow* changingw = findWindow(winId, 0); + if (!changingw) + return; + + if (!changingw->forClient(client)) { + qWarning("Disabled: clients changing other client's focus"); + return; + } + + setFocus(changingw, gain); +} + +void QWSServerPrivate::setFocus(QWSWindow* changingw, bool gain) +{ + Q_Q(QWSServer); +#ifndef QT_NO_QWS_INPUTMETHODS + /* + This is the logic: + QWSWindow *loser = 0; + if (gain && focusw != changingw) + loser = focusw; + else if (!gain && focusw == changingw) + loser = focusw; + But these five lines can be reduced to one: + */ + if (current_IM) { + QWSWindow *loser = (!gain == (focusw==changingw)) ? focusw : 0; + if (loser && loser->winId() == current_IM_winId) + current_IM->updateHandler(QWSInputMethod::FocusOut); + } +#endif + if (gain) { + if (focusw != changingw) { + if (focusw) focusw->focus(0); + focusw = changingw; + focusw->focus(1); + emit q->windowEvent(focusw, QWSServer::Active); + } + } else if (focusw == changingw) { + if (changingw->client()) + changingw->focus(0); + focusw = 0; + // pass focus to window which most recently got it... + QWSWindow* bestw=0; + for (int i=0; ihidden() && + (!bestw || bestw->focusPriority() < w->focusPriority())) + bestw = w; + } + if (!bestw && changingw->focusPriority()) { // accept focus back? + bestw = changingw; // must be the only one + } + focusw = bestw; + if (focusw) { + focusw->focus(1); + emit q->windowEvent(focusw, QWSServer::Active); + } + } +} + + + +void QWSServerPrivate::invokeSetOpacity(const QWSSetOpacityCommand *cmd, QWSClient *client) +{ + Q_UNUSED( client ); + int winId = cmd->simpleData.windowid; + int opacity = cmd->simpleData.opacity; + + QWSWindow* changingw = findWindow(winId, 0); + + if (!changingw) { + qWarning("invokeSetOpacity: Invalid window handle %d", winId); + return; + } + + int altitude = windows.indexOf(changingw); + const bool wasOpaque = changingw->isOpaque(); + changingw->_opacity = opacity; + if (wasOpaque != changingw->isOpaque()) + update_regions(); + exposeRegion(changingw->allocatedRegion(), altitude); +} + +void QWSServerPrivate::invokeSetAltitude(const QWSChangeAltitudeCommand *cmd, + QWSClient *client) +{ + Q_UNUSED(client); + + int winId = cmd->simpleData.windowid; + int alt = cmd->simpleData.altitude; + bool fixed = cmd->simpleData.fixed; +#if 0 + qDebug("QWSServer::invokeSetAltitude winId %d alt %d)", winId, alt); +#endif + + if (alt < -1 || alt > 1) { + qWarning("QWSServer::invokeSetAltitude Only lower, raise and stays-on-top supported"); + return; + } + + QWSWindow* changingw = findWindow(winId, 0); + if (!changingw) { + qWarning("invokeSetAltitude: Invalid window handle %d", winId); + return; + } + + if (fixed && alt >= 1) { + changingw->onTop = true; + } + if (alt == QWSChangeAltitudeCommand::Lower) + changingw->lower(); + else + changingw->raise(); + +// if (!changingw->forClient(client)) { +// refresh(); +// } +} + +#ifndef QT_NO_QWS_PROPERTIES +void QWSServerPrivate::invokeAddProperty(QWSAddPropertyCommand *cmd) +{ + propertyManager.addProperty(cmd->simpleData.windowid, cmd->simpleData.property); +} + +void QWSServerPrivate::invokeSetProperty(QWSSetPropertyCommand *cmd) +{ + Q_Q(QWSServer); + if (propertyManager.setProperty(cmd->simpleData.windowid, + cmd->simpleData.property, + cmd->simpleData.mode, + cmd->data, + cmd->rawLen)) { + q->sendPropertyNotifyEvent(cmd->simpleData.property, + QWSPropertyNotifyEvent::PropertyNewValue); +#ifndef QT_NO_QWS_INPUTMETHODS + if (cmd->simpleData.property == QT_QWS_PROPERTY_MARKEDTEXT) { + QString s((const QChar*)cmd->data, cmd->rawLen/2); + emit q->markedText(s); + } +#endif + } +} + +void QWSServerPrivate::invokeRemoveProperty(QWSRemovePropertyCommand *cmd) +{ + Q_Q(QWSServer); + if (propertyManager.removeProperty(cmd->simpleData.windowid, + cmd->simpleData.property)) { + q->sendPropertyNotifyEvent(cmd->simpleData.property, + QWSPropertyNotifyEvent::PropertyDeleted); + } +} + + +bool QWSServerPrivate:: get_property(int winId, int property, const char *&data, int &len) +{ + return propertyManager.getProperty(winId, property, data, len); +} + + +void QWSServerPrivate::invokeGetProperty(QWSGetPropertyCommand *cmd, QWSClient *client) +{ + const char *data; + int len; + + if (propertyManager.getProperty(cmd->simpleData.windowid, + cmd->simpleData.property, + data, len)) { + client->sendPropertyReplyEvent(cmd->simpleData.property, len, data); + } else { + client->sendPropertyReplyEvent(cmd->simpleData.property, -1, 0); + } +} +#endif //QT_NO_QWS_PROPERTIES + +void QWSServerPrivate::invokeSetSelectionOwner(QWSSetSelectionOwnerCommand *cmd) +{ + qDebug("QWSServer::invokeSetSelectionOwner"); + + SelectionOwner so; + so.windowid = cmd->simpleData.windowid; + so.time.set(cmd->simpleData.hour, cmd->simpleData.minute, + cmd->simpleData.sec, cmd->simpleData.ms); + + if (selectionOwner.windowid != -1) { + QWSWindow *win = findWindow(selectionOwner.windowid, 0); + if (win) + win->client()->sendSelectionClearEvent(selectionOwner.windowid); + else + qDebug("couldn't find window %d", selectionOwner.windowid); + } + + selectionOwner = so; +} + +void QWSServerPrivate::invokeConvertSelection(QWSConvertSelectionCommand *cmd) +{ + qDebug("QWSServer::invokeConvertSelection"); + + if (selectionOwner.windowid != -1) { + QWSWindow *win = findWindow(selectionOwner.windowid, 0); + if (win) + win->client()->sendSelectionRequestEvent(cmd, selectionOwner.windowid); + else + qDebug("couldn't find window %d", selectionOwner.windowid); + } +} + +#ifndef QT_NO_QWS_CURSOR +void QWSServerPrivate::invokeDefineCursor(QWSDefineCursorCommand *cmd, QWSClient *client) +{ + if (cmd->simpleData.height > 64 || cmd->simpleData.width > 64) { + qDebug("Cannot define cursor size > 64x64"); + return; + } + + delete client->cursors.take(cmd->simpleData.id); + + int dataLen = cmd->simpleData.height * ((cmd->simpleData.width+7) / 8); + + if (dataLen > 0 && cmd->data) { + QWSCursor *curs = new QWSCursor(cmd->data, cmd->data + dataLen, + cmd->simpleData.width, cmd->simpleData.height, + cmd->simpleData.hotX, cmd->simpleData.hotY); + client->cursors.insert(cmd->simpleData.id, curs); + } +} + +void QWSServerPrivate::invokeSelectCursor(QWSSelectCursorCommand *cmd, QWSClient *client) +{ + int id = cmd->simpleData.id; + QWSCursor *curs = 0; + if (id <= Qt::LastCursor) { + curs = QWSCursor::systemCursor(id); + } + else { + QWSCursorMap cursMap = client->cursors; + QWSCursorMap::Iterator it = cursMap.find(id); + if (it != cursMap.end()) { + curs = it.value(); + } + } + if (curs == 0) { + curs = QWSCursor::systemCursor(Qt::ArrowCursor); + } + + QWSWindow* win = findWindow(cmd->simpleData.windowid, 0); + if (mouseGrabber) { + // If the mouse is being grabbed, we don't want just anyone to + // be able to change the cursor. We do want the cursor to be set + // correctly once mouse grabbing is stopped though. + if (win != mouseGrabber) + nextCursor = curs; + else + setCursor(curs); + } else if (win && win->allocatedRegion().contains(QWSServer::mousePosition)) { //##################### cursor + // A non-grabbing window can only set the cursor shape if the + // cursor is within its allocated region. + setCursor(curs); + } +} + +void QWSServerPrivate::invokePositionCursor(QWSPositionCursorCommand *cmd, QWSClient *) +{ + Q_Q(QWSServer); + QPoint newPos(cmd->simpleData.newX, cmd->simpleData.newY); + if (newPos != QWSServer::mousePosition) + q->sendMouseEvent(newPos, qwsServer->d_func()->mouseState); +} +#endif + +void QWSServerPrivate::invokeGrabMouse(QWSGrabMouseCommand *cmd, QWSClient *client) +{ + QWSWindow* win = findWindow(cmd->simpleData.windowid, 0); + if (!win) + return; + + if (cmd->simpleData.grab) { + if (!mouseGrabber || mouseGrabber->client() == client) { + mouseGrabbing = true; + mouseGrabber = win; + } + } else { + releaseMouse(mouseGrabber); + } +} + +void QWSServerPrivate::invokeGrabKeyboard(QWSGrabKeyboardCommand *cmd, QWSClient *client) +{ + QWSWindow* win = findWindow(cmd->simpleData.windowid, 0); + if (!win) + return; + + if (cmd->simpleData.grab) { + if (!keyboardGrabber || (keyboardGrabber->client() == client)) { + keyboardGrabbing = true; + keyboardGrabber = win; + } + } else { + releaseKeyboard(keyboardGrabber); + } +} + +#if !defined(QT_NO_SOUND) +void QWSServerPrivate::invokePlaySound(QWSPlaySoundCommand *cmd, QWSClient *) +{ +#if !defined(QT_EXTERNAL_SOUND_SERVER) && !defined(Q_OS_DARWIN) + soundserver->playFile( 1, cmd->filename ); +#else + Q_UNUSED(cmd); +#endif +} +#endif + +#ifndef QT_NO_COP +void QWSServerPrivate::invokeRegisterChannel(QWSQCopRegisterChannelCommand *cmd, + QWSClient *client) +{ + // QCopChannel will force us to emit the newChannel signal if this channel + // didn't already exist. + QCopChannel::registerChannel(cmd->channel, client); +} + +void QWSServerPrivate::invokeQCopSend(QWSQCopSendCommand *cmd, QWSClient *client) +{ + QCopChannel::answer(client, cmd->channel, cmd->message, cmd->data); +} + +#endif + +#ifndef QT_NO_QWS_INPUTMETHODS +void QWSServer::resetInputMethod() +{ + if (current_IM && qwsServer) { + current_IM->reset(); + } +} + +void QWSServerPrivate::invokeIMResponse(const QWSIMResponseCommand *cmd, + QWSClient *) +{ + if (current_IM) + current_IM->queryResponse(cmd->simpleData.property, cmd->result); +} + +void QWSServerPrivate::invokeIMUpdate(const QWSIMUpdateCommand *cmd, + QWSClient *) +{ + if (cmd->simpleData.type == QWSInputMethod::FocusIn) + current_IM_winId = cmd->simpleData.windowid; + + if (current_IM && (current_IM_winId == cmd->simpleData.windowid || cmd->simpleData.windowid == -1)) + current_IM->updateHandler(cmd->simpleData.type); +} + +#endif + +void QWSServerPrivate::invokeFont(const QWSFontCommand *cmd, QWSClient *client) +{ + QWSClientPrivate *priv = client->d_func(); + if (cmd->simpleData.type == QWSFontCommand::StartedUsingFont) { + referenceFont(priv, cmd->fontName); + } else if (cmd->simpleData.type == QWSFontCommand::StoppedUsingFont) { + dereferenceFont(priv, cmd->fontName); + } +} + +void QWSServerPrivate::invokeRepaintRegion(QWSRepaintRegionCommand * cmd, + QWSClient *) +{ + QRegion r; + r.setRects(cmd->rectangles,cmd->simpleData.nrectangles); + repaint_region(cmd->simpleData.windowid, cmd->simpleData.windowFlags, cmd->simpleData.opaque, r); +} + +#ifndef QT_NO_QWSEMBEDWIDGET +void QWSServerPrivate::invokeEmbed(QWSEmbedCommand *cmd, QWSClient *client) +{ + // Should find these two windows in a single loop + QWSWindow *embedder = findWindow(cmd->simpleData.embedder, client); + QWSWindow *embedded = findWindow(cmd->simpleData.embedded); + + if (!embedder) { + qWarning("QWSServer: Embed command from window %i failed: No such id.", + static_cast(cmd->simpleData.embedder)); + return; + } + + if (!embedded) { + qWarning("QWSServer: Embed command on window %i failed: No such id.", + static_cast(cmd->simpleData.embedded)); + return; + } + + switch (cmd->simpleData.type) { + case QWSEmbedEvent::StartEmbed: + embedder->startEmbed(embedded); + windows.removeAll(embedded); + windows.insert(windows.indexOf(embedder), embedded); + break; + case QWSEmbedEvent::StopEmbed: + embedder->stopEmbed(embedded); + break; + case QWSEmbedEvent::Region: + break; + } + + embedded->client()->sendEmbedEvent(embedded->winId(), + cmd->simpleData.type, cmd->region); + const QRegion oldAllocated = embedded->allocatedRegion(); + update_regions(); + exposeRegion(oldAllocated - embedded->allocatedRegion(), + windows.indexOf(embedded)); +} +#endif // QT_NO_QWSEMBEDWIDGET + +void QWSServerPrivate::invokeScreenTransform(const QWSScreenTransformCommand *cmd, + QWSClient *client) +{ + Q_UNUSED(client); + + QWSScreenTransformationEvent event; + event.simpleData.screen = cmd->simpleData.screen; + event.simpleData.transformation = cmd->simpleData.transformation; + + QMap::const_iterator it = clientMap.constBegin(); + for (; it != clientMap.constEnd(); ++it) + (*it)->sendEvent(&event); +} + +QWSWindow* QWSServerPrivate::newWindow(int id, QWSClient* client) +{ + Q_Q(QWSServer); + // Make a new window, put it on top. + QWSWindow* w = new QWSWindow(id,client); + + // insert after "stays on top" windows + bool added = false; + for (int i = nReserved; i < windows.size(); ++i) { + QWSWindow *win = windows.at(i); + if (!win->onTop) { + windows.insert(i, w); + added = true; + break; + } + } + if (!added) + windows.append(w); + emit q->windowEvent(w, QWSServer::Create); + return w; +} + +QWSWindow* QWSServerPrivate::findWindow(int windowid, QWSClient* client) +{ + for (int i=0; iwinId() == windowid) + return w; + } + if (client) + return newWindow(windowid,client); + else + return 0; +} + +void QWSServerPrivate::raiseWindow(QWSWindow *changingw, int /*alt*/) +{ + Q_Q(QWSServer); + if (changingw == windows.first()) + return; + QWSWindow::State oldstate = changingw->d->state; + changingw->d->state = QWSWindow::Raising; + // Expose regions previously overlapped by transparent windows + const QRegion bound = changingw->allocatedRegion(); + QRegion expose; + int windowPos = 0; + + //change position in list: + for (int i = 0; i < windows.size(); ++i) { + QWSWindow *w = windows.at(i); + if (w == changingw) { + windowPos = i; + windows.takeAt(i); + break; + } + if (!w->isOpaque()) + expose += (w->allocatedRegion() & bound); + } + + bool onTop = changingw->onTop; + +#ifndef QT_NO_QWSEMBEDWIDGET + // an embedded window is on top if the embedder is on top + QWSWindow *embedder = changingw->d->embedder; + while (!onTop && embedder) { + onTop = embedder->onTop; + embedder = embedder->d->embedder; + } +#endif + + int newPos = -1; + if (onTop) { + windows.insert(nReserved, changingw); + newPos = nReserved; + } else { + // insert after "stays on top" windows + bool in = false; + for (int i = nReserved; i < windows.size(); ++i) { + QWSWindow *w = windows.at(i); + if (!w->onTop) { + windows.insert(i, changingw); + in = true; + newPos = i; + break; + } + } + if (!in) { + windows.append(changingw); + newPos = windows.size()-1; + } + } + + if (windowPos != newPos) { + update_regions(); + if (!expose.isEmpty()) + exposeRegion(expose, newPos); + } + changingw->d->state = oldstate; + emit q->windowEvent(changingw, QWSServer::Raise); +} + +void QWSServerPrivate::lowerWindow(QWSWindow *changingw, int /*alt*/) +{ + Q_Q(QWSServer); + if (changingw == windows.last()) + return; + QWSWindow::State oldstate = changingw->d->state; + changingw->d->state = QWSWindow::Lowering; + + int i = windows.indexOf(changingw); + int newIdx = windows.size()-1; + windows.move(i, newIdx); + + const QRegion bound = changingw->allocatedRegion(); + + update_regions(); + + // Expose regions previously overlapped by transparent window + if (!changingw->isOpaque()) { + QRegion expose; + for (int j = i; j < windows.size() - 1; ++j) + expose += (windows.at(j)->allocatedRegion() & bound); + if (!expose.isEmpty()) + exposeRegion(expose, newIdx); + } + + changingw->d->state = oldstate; + emit q->windowEvent(changingw, QWSServer::Lower); +} + +void QWSServerPrivate::update_regions() +{ + if (disablePainting) + return; + + QRegion available = QRect(0, 0, qt_screen->width(), qt_screen->height()); + QRegion transparentRegion; + + // only really needed if there are unbuffered surfaces... + const bool doLock = (clientMap.size() > 1); + if (doLock) + QWSDisplay::grab(true); + + for (int i = 0; i < windows.count(); ++i) { + QWSWindow *w = windows.at(i); + QRegion r = (w->requested_region & available); + +#ifndef QT_NO_QWSEMBEDWIDGET + // Subtract regions needed for embedded windows + const int n = w->d->embedded.size(); + for (int i = 0; i < n; ++i) + r -= w->d->embedded.at(i)->allocatedRegion(); + + // Limited to the embedder region + if (w->d->embedder) + r &= w->d->embedder->requested_region; +#endif // QT_NO_QWSEMBEDWIDGET + + QWSWindowSurface *surface = w->windowSurface(); + const bool opaque = w->isOpaque() + && (w->d->painted || !surface || !surface->isBuffered()); + + if (!opaque) { + transparentRegion += r; + } else { + if (surface && (surface->isRegionReserved() || !surface->isBuffered())) + r -= transparentRegion; + available -= r; + } + + if (r != w->allocatedRegion()) { + w->setAllocatedRegion(r); + w->client()->sendRegionEvent(w->winId(), r, + QWSRegionEvent::Allocation); + } + +#ifdef QT_QWS_CLIENTBLIT +#ifdef QT_NO_QWS_CURSOR + // This optimization only really works when there isn't a crazy cursor + // wizzing around. + QRegion directPaint = (r - transparentRegion); // in gloal coords + if(directPaint != w->directPaintRegion()) { + w->setDirectPaintRegion(directPaint); + static int id = 0; + surface->setDirectRegion(directPaint, ++id); + w->client()->sendRegionEvent(w->winId(), directPaint, + QWSRegionEvent::DirectPaint, id); + } +#endif +#endif + } + + if (doLock) + QWSDisplay::ungrab(); +} + +void QWSServerPrivate::moveWindowRegion(QWSWindow *changingw, int dx, int dy) +{ + if (!changingw) + return; + + QWSWindow::State oldState = changingw->d->state; + changingw->d->state = QWSWindow::Moving; + const QRegion oldRegion(changingw->allocatedRegion()); + changingw->requested_region.translate(dx, dy); + + // hw: Even if the allocated region doesn't change, the requested region + // region has changed and we need to send region events. + // Resetting the allocated region to force update_regions to send events. + changingw->setAllocatedRegion(QRegion()); + update_regions(); + const QRegion newRegion(changingw->allocatedRegion()); + + QWSWindowSurface *surface = changingw->windowSurface(); + QRegion expose; + if (surface) + expose = surface->move(QPoint(dx, dy), changingw->allocatedRegion()); + else + expose = oldRegion + newRegion; + + if (!changingw->d->painted && !expose.isEmpty()) + expose = oldRegion - newRegion; + + int idx = windows.indexOf(changingw); + exposeRegion(expose, idx); + changingw->d->state = oldState; +} + +/*! + Changes the requested region of window \a changingw to \a r + If \a changingw is 0, the server's reserved region is changed. +*/ +void QWSServerPrivate::setWindowRegion(QWSWindow* changingw, const QRegion &r) +{ + if (!changingw) { + qWarning("Not implemented in this release"); + return; + } + + if (changingw->requested_region == r) + return; + + const QRegion oldRegion(changingw->allocatedRegion()); + changingw->requested_region = r; + update_regions(); + const QRegion newRegion(changingw->allocatedRegion()); + + int idx = windows.indexOf(changingw); + exposeRegion(oldRegion - newRegion, idx); +} + + +void QWSServerPrivate::exposeRegion(const QRegion &r, int changing) +{ + if (disablePainting) + return; + + if (r.isEmpty()) + return; + + static bool initial = true; + if (initial) { + changing = 0; + initial = false; + qt_screen->exposeRegion(qt_screen->region(), changing); + } else { + qt_screen->exposeRegion(r, changing); + } +} + +/*! + Closes all pointer devices (specified by the QWS_MOUSE_PROTO + environment variable) by deleting the associated mouse drivers. + + \sa openMouse(), mouseHandler() +*/ +void QWSServer::closeMouse() +{ + Q_D(QWSServer); + qDeleteAll(d->mousehandlers); + d->mousehandlers.clear(); +} + +/*! + Opens the mouse devices specified by the QWS_MOUSE_PROTO + environment variable. Be advised that closeMouse() is called first + to delete all the existing mouse handlers. This behaviour could be + the cause of problems if you were not expecting it. + + \sa closeMouse(), mouseHandler() +*/ +void QWSServer::openMouse() +{ + Q_D(QWSServer); + QString mice = QString::fromLatin1(qgetenv("QWS_MOUSE_PROTO")); +#if defined(QT_QWS_CASSIOPEIA) + if (mice.isEmpty()) + mice = QLatin1String("TPanel:/dev/tpanel"); +#endif + if (mice.isEmpty()) + mice = *defaultMouse(); + closeMouse(); + bool needviscurs = true; + if (mice != QLatin1String("None")) { + const QStringList mouse = mice.split(QLatin1Char(' ')); + for (int i = mouse.size() - 1; i >= 0; --i) { + QWSMouseHandler *handler = d->newMouseHandler(mouse.at(i)); + setMouseHandler(handler); + /* XXX handle mouse cursor visibility sensibly + if (!h->inherits("QCalibratedMouseHandler")) + needviscurs = true; + */ + } + } +#ifndef QT_NO_QWS_CURSOR + setCursorVisible(needviscurs); +#else + Q_UNUSED(needviscurs) +#endif +} + +/*! + Suspends pointer handling by deactivating all the mouse drivers + registered by the QWS_MOUSE_PROTO environment variable. + + + \sa resumeMouse(), QWSMouseHandler::suspend() +*/ +void QWSServer::suspendMouse() +{ + Q_D(QWSServer); + for (int i=0; i < d->mousehandlers.size(); ++i) + d->mousehandlers.at(i)->suspend(); +} + +/*! + Resumes pointer handling by reactivating all the mouse drivers + registered by the QWS_MOUSE_PROTO environment variable. + + \sa suspendMouse(), QWSMouseHandler::resume() +*/ +void QWSServer::resumeMouse() +{ + Q_D(QWSServer); + for (int i=0; i < d->mousehandlers.size(); ++i) + d->mousehandlers.at(i)->resume(); +} + + + +QWSMouseHandler* QWSServerPrivate::newMouseHandler(const QString& spec) +{ + int c = spec.indexOf(QLatin1Char(':')); + QString mouseProto; + QString mouseDev; + if (c >= 0) { + mouseProto = spec.left(c); + mouseDev = spec.mid(c+1); + } else { + mouseProto = spec; + } + + int screen = -1; + const QList regexps = QList() + << QRegExp(QLatin1String(":screen=(\\d+)\\b")) + << QRegExp(QLatin1String("\\bscreen=(\\d+):")); + for (int i = 0; i < regexps.size(); ++i) { + QRegExp regexp = regexps.at(i); + if (regexp.indexIn(mouseDev) == -1) + continue; + screen = regexp.cap(1).toInt(); + mouseDev.remove(regexp.pos(0), regexp.matchedLength()); + break; + } + + QWSMouseHandler *handler = 0; + handler = QMouseDriverFactory::create(mouseProto, mouseDev); + if (screen != -1) + handler->setScreen(qt_screen->subScreens().at(screen)); + + return handler; +} + +#ifndef QT_NO_QWS_KEYBOARD + +/*! + Closes all the keyboard devices (specified by the QWS_KEYBOARD + environment variable) by deleting the associated keyboard + drivers. + + \sa openKeyboard(), keyboardHandler() +*/ +void QWSServer::closeKeyboard() +{ + Q_D(QWSServer); + qDeleteAll(d->keyboardhandlers); + d->keyboardhandlers.clear(); +} + +/*! + Returns the primary keyboard driver. + + Note that this function can only be used in the server process. + + \sa setKeyboardHandler(), openKeyboard(), closeKeyboard() +*/ +QWSKeyboardHandler* QWSServer::keyboardHandler() +{ + return qwsServerPrivate->keyboardhandlers.first(); +} + +/*! + \fn void QWSServer::setKeyboardHandler(QWSKeyboardHandler* driver) + + Sets the primary keyboard driver to be the given \a driver. + + \l{Qt for Embedded Linux} provides several ready-made keyboard drivers, and + custom drivers are typically added using Qt's plugin + mechanism. See the \l{Qt for Embedded Linux Character Input} documentation + for details. + + Note that this function can only be used in the server process. + + \sa keyboardHandler(), setDefaultKeyboard() +*/ +void QWSServer::setKeyboardHandler(QWSKeyboardHandler* kh) +{ + if (!kh) + return; + qwsServerPrivate->keyboardhandlers.removeAll(kh); + qwsServerPrivate->keyboardhandlers.prepend(kh); +} + +/*! + Opens the keyboard devices specified by the QWS_KEYBOARD + environment variable. + + \sa closeKeyboard(), keyboardHandler() +*/ +void QWSServer::openKeyboard() +{ + QString keyboards = QString::fromLatin1(qgetenv("QWS_KEYBOARD")); +#if defined(QT_QWS_CASSIOPEIA) + if (keyboards.isEmpty()) + keyboards = QLatin1String("Buttons"); +#endif + if (keyboards.isEmpty()) + keyboards = *defaultKeyboard(); + + closeKeyboard(); + if (keyboards == QLatin1String("None")) + return; + + QString device; + QString type; + QStringList keyboard = keyboards.split(QLatin1Char(' ')); + for (int i = keyboard.size() - 1; i >= 0; --i) { + const QString spec = keyboard.at(i); + int colon=spec.indexOf(QLatin1Char(':')); + if (colon>=0) { + type = spec.left(colon); + device = spec.mid(colon+1); + } else { + type = spec; + device = QString(); + } + QWSKeyboardHandler *handler = QKbdDriverFactory::create(type, device); + setKeyboardHandler(handler); + } +} + +#endif //QT_NO_QWS_KEYBOARD + +QPoint QWSServer::mousePosition; +QBrush *QWSServerPrivate::bgBrush = 0; + +void QWSServerPrivate::move_region(const QWSRegionMoveCommand *cmd) +{ + QWSClient *serverClient = clientMap.value(-1); + invokeRegionMove(cmd, serverClient); +} + +void QWSServerPrivate::set_altitude(const QWSChangeAltitudeCommand *cmd) +{ + QWSClient *serverClient = clientMap.value(-1); + invokeSetAltitude(cmd, serverClient); +} + +void QWSServerPrivate::set_opacity(const QWSSetOpacityCommand *cmd) +{ + QWSClient *serverClient = clientMap.value(-1); + invokeSetOpacity(cmd, serverClient); +} + + +void QWSServerPrivate::request_focus(const QWSRequestFocusCommand *cmd) +{ + invokeSetFocus(cmd, clientMap.value(-1)); +} + +void QWSServerPrivate::set_identity(const QWSIdentifyCommand *cmd) +{ + invokeIdentify(cmd, clientMap.value(-1)); +} + +void QWSServerPrivate::repaint_region(int wid, int windowFlags, bool opaque, + const QRegion ®ion) +{ + QWSWindow* changingw = findWindow(wid, 0); + if (!changingw) { + return; + } + + const bool isOpaque = changingw->opaque; + const bool wasPainted = changingw->d->painted; + changingw->opaque = opaque; + changingw->d->windowFlags = QFlag(windowFlags); + changingw->d->dirtyOnScreen |= region; + changingw->d->painted = true; + if (isOpaque != opaque || !wasPainted) + update_regions(); + + int level = windows.indexOf(changingw); + exposeRegion(region, level); + changingw->d->dirtyOnScreen = QRegion(); +} + +QRegion QWSServerPrivate::reserve_region(QWSWindow *win, const QRegion ®ion) +{ + QRegion r = region; + + int oldPos = windows.indexOf(win); + int newPos = oldPos < nReserved ? nReserved - 1 : nReserved; + for (int i = 0; i < nReserved; ++i) { + if (i != oldPos) { + QWSWindow *w = windows.at(i); + r -= w->requested_region; + } + } + windows.move(oldPos, newPos); + nReserved = newPos + 1; + + return r; +} + +void QWSServerPrivate::request_region(int wid, const QString &surfaceKey, + const QByteArray &surfaceData, + const QRegion ®ion) +{ + QWSWindow *changingw = findWindow(wid, 0); + if (!changingw) + return; + + Q_Q(QWSServer); + QWSWindow::State windowState = QWSWindow::NoState; + + if (region.isEmpty()) { + windowState = QWSWindow::Hiding; + emit q->windowEvent(changingw, QWSServer::Hide); + } + + const bool wasOpaque = changingw->opaque; + + changingw->createSurface(surfaceKey, surfaceData); + QWSWindowSurface *surface = changingw->windowSurface(); + + changingw->opaque = surface->isOpaque(); + + QRegion r; + if (surface->isRegionReserved()) + r = reserve_region(changingw, region); + else + r = region; + + if (!region.isEmpty()) { + if (changingw->isVisible()) + windowState = QWSWindow::ChangingGeometry; + else + windowState = QWSWindow::Showing; + } + changingw->d->state = windowState; + + if (!r.isEmpty() && wasOpaque != changingw->opaque && surface->isBuffered()) + changingw->requested_region = QRegion(); // XXX: force update_regions + + const QRegion oldAllocated = changingw->allocatedRegion(); + setWindowRegion(changingw, r); + if (oldAllocated == changingw->allocatedRegion()) { + // Always send region event to the requesting window even if the + // region didn't change. This is necessary as the client will reset + // the clip region until an event is received. + changingw->client()->sendRegionEvent(wid, changingw->allocatedRegion(), + QWSRegionEvent::Allocation); + } + + surface->QWindowSurface::setGeometry(r.boundingRect()); + + if (windowState == QWSWindow::Showing) + emit q->windowEvent(changingw, QWSServer::Show); + else if (windowState == QWSWindow::ChangingGeometry) + emit q->windowEvent(changingw, QWSServer::Geometry); + if (windowState == QWSWindow::Hiding) { + handleWindowClose(changingw); + changingw->d->state = QWSWindow::Hidden; + changingw->d->painted = false; + } else { + changingw->d->state = QWSWindow::Visible; + } +} + +void QWSServerPrivate::destroy_region(const QWSRegionDestroyCommand *cmd) +{ + invokeRegionDestroy(cmd, clientMap.value(-1)); +} + +void QWSServerPrivate::name_region(const QWSRegionNameCommand *cmd) +{ + invokeRegionName(cmd, clientMap.value(-1)); +} + +#ifndef QT_NO_QWS_INPUTMETHODS +void QWSServerPrivate::im_response(const QWSIMResponseCommand *cmd) + { + invokeIMResponse(cmd, clientMap.value(-1)); +} + +void QWSServerPrivate::im_update(const QWSIMUpdateCommand *cmd) +{ + invokeIMUpdate(cmd, clientMap.value(-1)); +} + +void QWSServerPrivate::send_im_mouse(const QWSIMMouseCommand *cmd) +{ + if (current_IM) + current_IM->mouseHandler(cmd->simpleData.index, cmd->simpleData.state); +} +#endif + +void QWSServerPrivate::openDisplay() +{ + qt_init_display(); + +// rgnMan = qt_fbdpy->regionManager(); + swidth = qt_screen->deviceWidth(); + sheight = qt_screen->deviceHeight(); +} + +void QWSServerPrivate::closeDisplay() +{ + if (qt_screen) + qt_screen->shutdownDevice(); +} + +/*! + Returns the brush used as background in the absence of obscuring + windows. + + \sa setBackground() +*/ +const QBrush &QWSServer::backgroundBrush() const +{ + return *QWSServerPrivate::bgBrush; +} + +/*! + Sets the brush used as background in the absence of obscuring + windows, to be the given \a brush. + + Note that this function can only be used in the server process. + + \sa backgroundBrush() +*/ +void QWSServer::setBackground(const QBrush &brush) +{ + if (!QWSServerPrivate::bgBrush) + QWSServerPrivate::bgBrush = new QBrush(brush); + else + *QWSServerPrivate::bgBrush = brush; + if (!qwsServer) + return; + qt_screen->exposeRegion(QRect(0,0,qt_screen->width(), qt_screen->height()), 0); +} + + +#ifdef QT3_SUPPORT +/*! + \fn void QWSServer::setDesktopBackground(const QImage &image) + + Sets the image used as background in the absence of obscuring + windows, to be the given \a image. + + Use the setBackground() function instead. + + \oldcode + QImage image; + setDesktopBackground(image); + \newcode + QImage image; + setBackground(QBrush(image)); + \endcode +*/ +void QWSServer::setDesktopBackground(const QImage &img) +{ + if (img.isNull()) + setBackground(Qt::NoBrush); + else + setBackground(QBrush(QPixmap::fromImage(img))); +} + +/*! + \fn void QWSServer::setDesktopBackground(const QColor &color) + \overload + + Sets the color used as background in the absence of obscuring + windows, to be the given \a color. + + Use the setBackground() function instead. + + \oldcode + QColor color; + setDesktopBackground(color); + \newcode + QColor color; + setBackground(QBrush(color)); + \endcode +*/ +void QWSServer::setDesktopBackground(const QColor &c) +{ + setBackground(QBrush(c)); +} +#endif //QT3_SUPPORT + +/*! + \internal + */ +void QWSServer::startup(int flags) +{ + if (qwsServer) + return; + unlink(qws_qtePipeFilename().toLatin1().constData()); + (void)new QWSServer(flags); +} + +/*! + \internal +*/ + +void QWSServer::closedown() +{ + QScopedPointer server(qwsServer); + qwsServer = 0; + QT_TRY { + unlink(qws_qtePipeFilename().toLatin1().constData()); + } QT_CATCH(const std::bad_alloc &) { + // ### TODO - what to do when we run out of memory + // when calling toLatin1? + } +} + +void QWSServerPrivate::emergency_cleanup() +{ +#ifndef QT_NO_QWS_KEYBOARD + if (qwsServer) + qwsServer->closeKeyboard(); +#endif +} + +#ifndef QT_NO_QWS_KEYBOARD +static QList *keyFilters = 0; + +/*! + Processes the given key event. The key is identified by its \a + unicode value and the given \a keycode, \a modifiers, \a isPress + and \a autoRepeat parameters. + + The \a keycode parameter is the Qt keycode value as defined by the + Qt::Key enum. The \a modifiers is an OR combination of + Qt::KeyboardModifier values, indicating whether \gui + Shift/Alt/Ctrl keys are pressed. The \a isPress parameter is true + if the event is a key press event and \a autoRepeat is true if the + event is caused by an auto-repeat mechanism and not an actual key + press. + + This function is typically called internally by keyboard drivers. + Note that this function can only be used in the server process. + + \sa sendKeyEvent(), {Qt for Embedded Linux Character Input} +*/ +void QWSServer::processKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat) +{ + bool block; + // Don't block the POWER or LIGHT keys + if ( keycode == Qt::Key_F34 || keycode == Qt::Key_F35 ) + block = false; + else + block = qwsServerPrivate->screensaverblockevent(KEY, qwsServerPrivate->screensaverinterval, isPress); + +#ifdef EVENT_BLOCK_DEBUG + qDebug() << "processKeyEvent" << unicode << keycode << modifiers << isPress << autoRepeat << (block ? "block" : "pass"); +#endif + + // If we press a key and it's going to be blocked, wake up the screen + if ( block && isPress ) + qwsServerPrivate->_q_screenSaverWake(); + + if ( block ) + return; + + if (keyFilters) { + for (int i = 0; i < keyFilters->size(); ++i) { + QWSServer::KeyboardFilter *keyFilter = keyFilters->at(i); + if (keyFilter->filter(unicode, keycode, modifiers, isPress, autoRepeat)) + return; + } + } + sendKeyEvent(unicode, keycode, modifiers, isPress, autoRepeat); +} + +/*! + \fn void QWSServer::addKeyboardFilter(KeyboardFilter *filter) + + Activates the given keyboard \a filter all key events generated by + physical keyboard drivers (i.e., events sent using the + processKeyEvent() function). + + Note that the filter is not invoked for keys generated by \e + virtual keyboard drivers (i.e., events sent using the + sendKeyEvent() function). + + Note that this function can only be used in the server process. + + \sa removeKeyboardFilter() +*/ +void QWSServer::addKeyboardFilter(KeyboardFilter *f) +{ + if (!keyFilters) + keyFilters = new QList; + if (f) { + keyFilters->prepend(f); + } +} + +/* +//####### + We should probably obsolete the whole keyboard filter thing since + it's not useful for input methods anyway + + We could do removeKeyboardFilter(KeyboardFilter *f), but + the "remove and delete the filter" concept does not match "user + remembers the pointer". +*/ + +/*! + Removes and deletes the most recently added filter. + + Note that the programmer is responsible for removing each added + keyboard filter. + + Note that this function can only be used in the server process. + + \sa addKeyboardFilter() +*/ +void QWSServer::removeKeyboardFilter() +{ + if (!keyFilters || keyFilters->isEmpty()) + return; + delete keyFilters->takeAt(0); +} +#endif // QT_NO_QWS_KEYBOARD + +/*! + \fn void QWSServer::setScreenSaverIntervals(int* intervals) + + Specifies the time \a intervals (in milliseconds) between the + different levels of screen responsiveness. + + \l{Qt for Embedded Linux} supports multilevel screen saving, i.e., it is + possible to specify several different levels of screen + responsiveness by implementing the QWSScreenSaver::save() + function. For example, you can choose to first turn off the light + before you fully activate the screensaver. See the QWSScreenSaver + documentation for details. + + Note that an interval of 0 milliseconds will turn off the + screensaver, and that the \a intervals array must be 0-terminated. + This function can only be used in the server process. + + \sa setScreenSaverInterval(), setScreenSaverBlockLevel() +*/ +void QWSServer::setScreenSaverIntervals(int* ms) +{ + if (!qwsServerPrivate) + return; + + delete [] qwsServerPrivate->screensaverintervals; + if (ms) { + int* t=ms; + int n=0; + while (*t++) n++; + if (n) { + n++; // the 0 + qwsServerPrivate->screensaverintervals = new int[n]; + memcpy(qwsServerPrivate->screensaverintervals, ms, n*sizeof(int)); + } else { + qwsServerPrivate->screensaverintervals = 0; + } + } else { + qwsServerPrivate->screensaverintervals = 0; + } + qwsServerPrivate->screensaverinterval = 0; + + qwsServerPrivate->screensavertimer->stop(); + qt_screen->blank(false); + qwsServerPrivate->_q_screenSaverWake(); +} + +/*! + \fn void QWSServer::setScreenSaverInterval(int milliseconds) + + Sets the timeout interval for the screensaver to the specified \a + milliseconds. To turn off the screensaver, set the timout interval + to 0. + + Note that this function can only be used in the server process. + + \sa setScreenSaverIntervals(), setScreenSaverBlockLevel() +*/ +void QWSServer::setScreenSaverInterval(int ms) +{ + int v[2]; + v[0] = ms; + v[1] = 0; + setScreenSaverIntervals(v); +} + +/*! + Block the key or mouse event that wakes the system from level \a eventBlockLevel or higher. + To completely disable event blocking (the default behavior), set \a eventBlockLevel to -1. + + The algorithm blocks the "down", "up" as well as any "repeat" events for the same key + but will not block other key events after the initial "down" event. For mouse events, the + algorithm blocks all mouse events until an event with no buttons pressed is received. + + There are 2 keys that are never blocked, Qt::Key_F34 (POWER) and Qt::Key_F35 (LIGHT). + + Example usage: + + \snippet doc/src/snippets/code/src_gui_embedded_qwindowsystem_qws.cpp 0 + + Note that this function can only be used in the server process. + + \sa setScreenSaverIntervals(), setScreenSaverInterval() +*/ +void QWSServer::setScreenSaverBlockLevel(int eventBlockLevel) +{ + if (!qwsServerPrivate) + return; + qwsServerPrivate->screensavereventblocklevel = eventBlockLevel; +#ifdef EVENT_BLOCK_DEBUG + qDebug() << "QWSServer::setScreenSaverBlockLevel() " << eventBlockLevel; +#endif +} + +extern bool qt_disable_lowpriority_timers; //in qeventloop_unix.cpp + +void QWSServerPrivate::_q_screenSaverWake() +{ + if (screensaverintervals) { + if (screensaverinterval != screensaverintervals) { + if (saver) saver->restore(); + screensaverinterval = screensaverintervals; + screensaverblockevents = false; + } else { + if (!screensavertimer->isActive()) { + qt_screen->blank(false); + if (saver) saver->restore(); + } + } + screensavertimer->start(*screensaverinterval); + screensavertime.start(); + } + qt_disable_lowpriority_timers=false; +} + +void QWSServerPrivate::_q_screenSaverSleep() +{ + qt_screen->blank(true); +#if !defined(QT_QWS_IPAQ) && !defined(QT_QWS_EBX) + screensavertimer->stop(); +#else + if (screensaverinterval) { + screensavertimer->start(*screensaverinterval); + screensavertime.start(); + } else { + screensavertimer->stop(); + } +#endif + qt_disable_lowpriority_timers=true; +} + +/*! + \fn void QWSServer::setScreenSaver(QWSScreenSaver* screenSaver) + + Installs the given \a screenSaver, deleting the current screen + saver. + + Note that this function can only be used in the server process. + + \sa screenSaverActivate(), setScreenSaverInterval(), setScreenSaverIntervals(), setScreenSaverBlockLevel() +*/ +void QWSServer::setScreenSaver(QWSScreenSaver* ss) +{ + QWSServerPrivate *qd = qwsServer->d_func(); + delete qd->saver; + qd->saver = ss; +} + +void QWSServerPrivate::screenSave(int level) +{ + if (saver) { + // saver->save() may call QCoreApplication::processEvents, + // block event before calling saver->save(). + bool oldScreensaverblockevents = screensaverblockevents; + if (*screensaverinterval >= 1000) { + screensaverblockevents = (screensavereventblocklevel >= 0 && screensavereventblocklevel <= level); +#ifdef EVENT_BLOCK_DEBUG + if (screensaverblockevents) + qDebug("ready to block events"); +#endif + } + int *oldScreensaverinterval = screensaverinterval; + if (saver->save(level)) { + // only update screensaverinterval if it hasn't already changed + if (oldScreensaverinterval == screensaverinterval) { + if (screensaverinterval && screensaverinterval[1]) { + screensavertimer->start(*++screensaverinterval); + screensavertime.start(); + } else { + screensaverinterval = 0; + } + } + } else { + // restore previous state + screensaverblockevents = oldScreensaverblockevents; + + // for some reason, the saver don't want us to change to the + // next level, so we'll stay at this level for another interval + if (screensaverinterval && *screensaverinterval) { + screensavertimer->start(*screensaverinterval); + screensavertime.start(); + } + } + } else { + screensaverinterval = 0;//screensaverintervals; + screensaverblockevents = false; + _q_screenSaverSleep(); + } +} + +void QWSServerPrivate::_q_screenSaverTimeout() +{ + if (screensaverinterval) { + if (screensavertime.elapsed() > *screensaverinterval*2) { + // bogus (eg. unsuspend, system time changed) + _q_screenSaverWake(); // try again + return; + } + screenSave(screensaverinterval - screensaverintervals); + } +} + +/*! + Returns true if the screen saver is active; otherwise returns + false. + + Note that this function can only be used in the server process. + + \sa screenSaverActivate() +*/ +bool QWSServer::screenSaverActive() +{ + return qwsServerPrivate->screensaverinterval + && !qwsServerPrivate->screensavertimer->isActive(); +} + +/*! + \internal +*/ +void QWSServer::updateWindowRegions() const +{ + qwsServerPrivate->update_regions(); +} + +/*! + Activates the screen saver if \a activate is true; otherwise it is + deactivated. + + Note that this function can only be used in the server process. + + \sa screenSaverActive(), setScreenSaver() +*/ +void QWSServer::screenSaverActivate(bool activate) +{ + if (activate) + qwsServerPrivate->_q_screenSaverSleep(); + else + qwsServerPrivate->_q_screenSaverWake(); +} + +void QWSServerPrivate::disconnectClient(QWSClient *c) +{ + QTimer::singleShot(0, c, SLOT(closeHandler())); +} + +void QWSServerPrivate::updateClientCursorPos() +{ + Q_Q(QWSServer); + QWSWindow *win = qwsServerPrivate->mouseGrabber ? qwsServerPrivate->mouseGrabber : qwsServer->windowAt(QWSServer::mousePosition); + QWSClient *winClient = win ? win->client() : 0; + if (winClient && winClient != cursorClient) + q->sendMouseEvent(QWSServer::mousePosition, mouseState); +} + +#ifndef QT_NO_QWS_INPUTMETHODS + +/*! + \class QWSInputMethod + \preliminary + \ingroup qws + + \brief The QWSInputMethod class provides international input methods + in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + A \l{Qt for Embedded Linux} application requires a server application to be + running, or to be the server application itself. All system + generated events, including keyboard and mouse events, are passed + to the server application which then propagates the event to the + appropriate client. + + An input method consists of a filter and optionally a graphical + interface, and is used to filter input events between the server + and the client application. + + \tableofcontents + + \section1 Creating Custom Input Methods + + To implement a custom input method, derive from the QWSInputMethod + class, and use the server's \l + {QWSServer::}{setCurrentInputMethod()} function to install it. + + When subclassing QWSInputMethod, you can reimplement the filter() + functions to handle input from both physical and virtual keyboards + as well as mouse devices. Note that the default implementations do + nothing. Use the setInputResolution() function to control the + number of bits shifted when filtering mouse input, i.e., when + going from pointer resolution to screen resolution (the current + resolution can be retrieved using the inputResolutionShift() + function). + + Reimplement the reset() function to restore the state of the input + method. Note that the default implementation calls the sendEvent() + function with empty preedit and commit strings if the input method + is in compose mode (i.e., if the input method is actively + composing a preedit string). + + To receive replies to an input method query (sent using the + sendQuery() function), you must reimplement the queryResponse() + function, while the mouseHandler() function must be reimplemented + if you want to handle mouse events within the preedit + text. Reimplement the updateHandler() function to handle update + events including resets and focus changes. The UpdateType enum + describes the various types of update events recognized by the + input method. + + \section1 Using Input Methods + + In addition to the filter(), reset(), queryResponse(), + mouseHandler() and updateHandler() function mentioned in the + previous section, the QWSInputMethod provides several other + functions helping the window system to manage the installed input + methods. + + The sendEvent() function sends the given event to the focus + widget, while the sendPreeditString() function sends the given + preedit text (encapsulated by an event). QWSInputMethod also + provides the sendCommitString() convenience function which sends + an event encapsulating the given commit string to the current + focus widget, and the sendMouseEvent() function which sends the + given mouse event. + + Finally, the QWSInputMethod class provides the sendQuery() + function for sending input method queries. This function + encapsulates the event with a QWSEvent instance of the \l + {QWSEvent::}{IMQuery} type. + + \sa QWSServer, {Qt for Embedded Linux Architecture} +*/ + +/*! + Constructs a new input method. + + Use the QWSServer::setCurrentInputMethod() function to install it. +*/ + +QWSInputMethod::QWSInputMethod() +{ + +} + +/*! + Destroys this input method, uninstalling it if it is installed. +*/ +QWSInputMethod::~QWSInputMethod() +{ + if (current_IM == this) + current_IM = 0; +} + +/*! + Filters the key input identified by the given \a unicode, \a + keycode, \a modifiers, \a isPress and \a autoRepeat parameters. + + Note that the default implementation does nothing; reimplement + this function to handle input from both physical and virtual + devices. + + The \a keycode is a Qt::Key value, and the \a modifiers is an OR + combination of Qt::KeyboardModifiers. The \a isPress parameter is + telling whether the input is a key press or key release, and the + \a autoRepeat parameter determines whether the input is + autorepeated ( i.e., in which case the + QWSKeyboardHandler::beginAutoRepeat() function has been called). + + To block the event from further processing, return true when + reimplementing this function; the default implementation returns + false. + + \sa setInputResolution(), inputResolutionShift() +*/ +bool QWSInputMethod::filter(int unicode, int keycode, int modifiers, bool isPress, bool autoRepeat) +{ + Q_UNUSED(unicode); + Q_UNUSED(keycode); + Q_UNUSED(modifiers); + Q_UNUSED(isPress); + Q_UNUSED(autoRepeat); + return false; +} + +/*! + \overload + + Filters the mouse input identified by the given \a position, \a + state, and \a wheel parameters. +*/ +bool QWSInputMethod::filter(const QPoint &position, int state, int wheel) +{ + Q_UNUSED(position); + Q_UNUSED(state); + Q_UNUSED(wheel); + return false; +} + +/*! + Resets the state of the input method. + + If the input method is in compose mode, i.e., the input method is + actively composing a preedit string, the default implementation + calls sendEvent() with empty preedit and commit strings; otherwise + it does nothing. Reimplement this function to alter this behavior. + + \sa sendEvent() +*/ +void QWSInputMethod::reset() +{ + if (current_IM_composing_win) { + QInputMethodEvent ime; + sendEvent(&ime); + } +} + +/*! + \enum QWSInputMethod::UpdateType + + This enum describes the various types of update events recognized + by the input method. + + \value Update The input widget is updated in some way; use sendQuery() with + Qt::ImMicroFocus as an argument for more information. + \value FocusIn A new input widget receives focus. + \value FocusOut The input widget loses focus. + \value Reset The input method should be reset. + \value Destroyed The input widget is destroyed. + + \sa updateHandler() +*/ + +/*! + Handles update events including resets and focus changes. The + update events are specified by the given \a type which is one of + the UpdateType enum values. + + Note that reimplementations of this function must call the base + implementation for all cases that it does not handle itself. + + \sa UpdateType +*/ +void QWSInputMethod::updateHandler(int type) +{ + switch (type) { + case FocusOut: + case Reset: + reset(); + break; + + default: + break; + } +} + + +/*! + Receive replies to an input method query. + + Note that the default implementation does nothing; reimplement + this function to receive such replies. + + Internally, an input method query is passed encapsulated by an \l + {QWSEvent::IMQuery}{IMQuery} event generated by the sendQuery() + function. The queried property and the result is passed in the \a + property and \a result parameters. + + \sa sendQuery(), QWSServer::sendIMQuery() +*/ +void QWSInputMethod::queryResponse(int property, const QVariant &result) +{ + Q_UNUSED(property); + Q_UNUSED(result); +} + + + +/*! + \fn void QWSInputMethod::mouseHandler(int offset, int state) + + Handles mouse events within the preedit text. + + Note that the default implementation resets the input method on + all mouse presses; reimplement this function to alter this + behavior. + + The \a offset parameter specifies the position of the mouse event + within the string, and \a state specifies the type of the mouse + event as described by the QWSServer::IMMouse enum. If \a state is + less than 0, the mouse event is inside the associated widget, but + outside the preedit text. When clicking in a different widget, the + \a state is QWSServer::MouseOutside. + + \sa sendPreeditString(), reset() +*/ +void QWSInputMethod::mouseHandler(int, int state) +{ + if (state == QWSServer::MousePress || state == QWSServer::MouseOutside) + reset(); +} + + +/*! + Sends an event encapsulating the given \a preeditString, to the + focus widget. + + The specified \a selectionLength is the number of characters to be + marked as selected (starting at the given \a cursorPosition). If + \a selectionLength is negative, the text \e before \a + cursorPosition is marked. + + The preedit string is marked with QInputContext::PreeditFormat, + and the selected part is marked with + QInputContext::SelectionFormat. + + Sending an input method event with a non-empty preedit string will + cause the input method to enter compose mode. Sending an input + method event with an empty preedit string will cause the input + method to leave compose mode, i.e., the input method will no longer + be actively composing the preedit string. + + Internally, the event is represented by a QWSEvent object of the + \l {QWSEvent::IMEvent}{IMEvent} type. + + \sa sendEvent(), sendCommitString() +*/ + +void QWSInputMethod::sendPreeditString(const QString &preeditString, int cursorPosition, int selectionLength) +{ + QList attributes; + + int selPos = cursorPosition; + if (selectionLength == 0) { + selPos = 0; + } else if (selectionLength < 0) { + selPos += selectionLength; + selectionLength = -selectionLength; + } + if (selPos > 0) + attributes += QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selPos, + QVariant(int(QInputContext::PreeditFormat))); + + if (selectionLength) + attributes += QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selPos, selectionLength, + QVariant(int(QInputContext::SelectionFormat))); + + if (selPos + selectionLength < preeditString.length()) + attributes += QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + selPos + selectionLength, + preeditString.length() - selPos - selectionLength, + QVariant(int(QInputContext::PreeditFormat))); + + attributes += QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursorPosition, 0, QVariant()); + + QInputMethodEvent ime(preeditString, attributes); + qwsServer->sendIMEvent(&ime); +} + +/*! + \fn void QWSInputMethod::sendCommitString(const QString &commitString, int replaceFromPosition, int replaceLength) + + Sends an event encapsulating the given \a commitString, to the + focus widget. + + Note that this will cause the input method to leave compose mode, + i.e., the input method will no longer be actively composing the + preedit string. + + If the specified \a replaceLength is greater than 0, the commit + string will replace the given number of characters of the + receiving widget's previous text, starting at the given \a + replaceFromPosition relative to the start of the current preedit + string. + + Internally, the event is represented by a QWSEvent object of the + \l {QWSEvent::IMEvent}{IMEvent} type. + + \sa sendEvent(), sendPreeditString() +*/ +void QWSInputMethod::sendCommitString(const QString &commitString, int replaceFrom, int replaceLength) +{ + QInputMethodEvent ime; + ime.setCommitString(commitString, replaceFrom, replaceLength); + qwsServer->sendIMEvent(&ime); +} + +/*! + \fn QWSInputMethod::sendIMEvent(QWSServer::IMState state, const QString &text, int cursorPosition, int selectionLength) + \obsolete + + Sends a QInputMethodEvent object to the focus widget. + + If the specified \a state is QWSServer::IMCompose, \a text is a + preedit string, \a cursorPosition is the cursor's position within + the preedit string, and \a selectionLength is the number of + characters (starting at \a cursorPosition) that should be marked + as selected by the input widget receiving the event. If the + specified \a state is QWSServer::IMEnd, \a text is a commit + string. + + Use sendEvent(), sendPreeditString() or sendCommitString() instead. +*/ + +/*! + \fn QWSInputMethod::sendEvent(const QInputMethodEvent *event) + + Sends the given \a event to the focus widget. + + The \c QInputMethodEvent class is derived from QWSEvent, i.e., the + given \a event is a QWSEvent object of the \l + {QWSEvent::IMEvent}{IMEvent} type. + + \sa sendPreeditString(), sendCommitString(), reset() +*/ + + +/*! + \fn void QWSInputMethod::sendQuery(int property) + + Sends an input method query (internally encapsulated by a QWSEvent + of the \l {QWSEvent::IMQuery}{IMQuery} type) for the specified \a + property. + + To receive responses to input method queries, the virtual + queryResponse() function must be reimplemented. + + \sa queryResponse(), QWSServer::sendIMQuery() +*/ + +/*! + Sets and returns the number of bits shifted to go from pointer + resolution to screen resolution when filtering mouse input. + + If \a isHigh is true and the device has a pointer device + resolution twice or more of the screen resolution, the positions + passed to the filter() function will be presented at the higher + resolution; otherwise the resolution will be equal to that of the + screen resolution. + + \sa inputResolutionShift(), filter() +*/ +uint QWSInputMethod::setInputResolution(bool isHigh) +{ + mIResolution = isHigh; + return inputResolutionShift(); +} + +/*! + Returns the number of bits shifted to go from pointer resolution + to screen resolution when filtering mouse input. + + \sa setInputResolution(), filter() +*/ +uint QWSInputMethod::inputResolutionShift() const +{ + return 0; // default for devices with single resolution. +} + +/*! + \fn void QWSInputMethod::sendMouseEvent( const QPoint &position, int state, int wheel ) + + Sends a mouse event specified by the given \a position, \a state + and \a wheel parameters. + + The given \a position will be transformed if the screen + coordinates do not match the pointer device coordinates. + + Note that the event will be not be tested by the active input + method, but calling the QWSServer::sendMouseEvent() function will + make the current input method filter the event. + + \sa mouseHandler(), sendEvent() +*/ +void QWSInputMethod::sendMouseEvent( const QPoint &pos, int state, int wheel ) +{ + if (qt_last_x) { + *qt_last_x = pos.x(); + *qt_last_y = pos.y(); + } + QWSServer::mousePosition = pos; + qwsServerPrivate->mouseState = state; + QWSServerPrivate::sendMouseEventUnfiltered(pos, state, wheel); +} +#endif // QT_NO_QWS_INPUTMETHODS + +/*! + \fn QWSWindow::QWSWindow(int i, QWSClient * client) + \internal + + Constructs a new top-level window, associated with the client \a + client and giving it the id \a i. +*/ + +/*! + \fn QWSServer::windowEvent(QWSWindow * window, QWSServer::WindowEvent eventType) + + This signal is emitted whenever something happens to a top-level + window (e.g., it's created or destroyed), passing a pointer to the + window and the event's type in the \a window and \a eventType + parameters, respectively. + + \sa markedText() +*/ + +/*! + \class QWSServer::KeyboardFilter + \ingroup qws + + \brief The KeyboardFilter class is a base class for global + keyboard event filters in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + In \l{Qt for Embedded Linux}, all system generated events, including + keyboard events, are passed to the server application which then + propagates the event to the appropriate client. The KeyboardFilter + class is used to implement a global, low-level filter on the + server side. The server applies the filter to all keyboard events + before passing them on to the clients: + + \image qwsserver_keyboardfilter.png + + This feature can, for example, be used to filter things like APM + (advanced power management) suspended from a button without having + to filter for it in all applications. + + To add a new keyboard filter you must first create the filter by + deriving from this class, reimplementing the pure virtual filter() + function. Then you can install the filter on the server using + QWSServer's \l {QWSServer::}{addKeyboardFilter()} + function. QWSServer also provides a \l + {QWSServer::}{removeKeyboardFilter()} function. + + \sa {Qt for Embedded Linux Architecture}, QWSServer, QWSInputMethod +*/ + +/*! + \fn QWSServer::KeyboardFilter::~KeyboardFilter() + + Destroys the keyboard filter. +*/ + +/*! + \fn bool QWSServer::KeyboardFilter::filter(int unicode, int keycode, int modifiers, bool isPress, bool autoRepeat) + + Implement this function to return true if a given key event should + be stopped from being processed any further; otherwise it should + return false. + + A key event can be identified by the given \a unicode value and + the \a keycode, \a modifiers, \a isPress and \a autoRepeat + parameters. + + The \a keycode parameter is the Qt keycode value as defined by the + Qt::Key enum. The \a modifiers is an OR combination of + Qt::KeyboardModifier values, indicating whether \gui + Shift/Alt/Ctrl keys are pressed. The \a isPress parameter is true + if the event is a key press event and \a autoRepeat is true if the + event is caused by an auto-repeat mechanism and not an actual key + press. +*/ + +QT_END_NAMESPACE + +#include "moc_qwindowsystem_qws.cpp" diff --git a/src/gui/embedded/qwindowsystem_qws.h b/src/gui/embedded/qwindowsystem_qws.h new file mode 100644 index 0000000000..65e1683d38 --- /dev/null +++ b/src/gui/embedded/qwindowsystem_qws.h @@ -0,0 +1,508 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSYSTEM_QWS_H +#define QWINDOWSYSTEM_QWS_H + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +struct QWSWindowPrivate; +class QWSCursor; +class QWSClient; +class QWSRegionManager; +class QBrush; +class QVariant; +class QInputMethodEvent; +class QWSInputMethod; +class QWSBackingStore; +class QWSWindowSurface; + +#ifdef QT3_SUPPORT +class QImage; +class QColor; +#endif + +class QWSInternalWindowInfo +{ +public: + int winid; + unsigned int clientid; + QString name; // Corresponds to QObject name of top-level widget +}; + + +class Q_GUI_EXPORT QWSScreenSaver +{ +public: + virtual ~QWSScreenSaver(); + virtual void restore()=0; + virtual bool save(int level)=0; +}; + + +class Q_GUI_EXPORT QWSWindow +{ + friend class QWSServer; + friend class QWSServerPrivate; + +public: + QWSWindow(int i, QWSClient* client); + ~QWSWindow(); + + int winId() const { return id; } + const QString &name() const { return rgnName; } + const QString &caption() const { return rgnCaption; } + QWSClient* client() const { return c; } + const QRegion &requestedRegion() const { return requested_region; } + QRegion allocatedRegion() const; + QRegion paintedRegion() const; + bool isVisible() const { return !requested_region.isEmpty(); } + bool isPartiallyObscured() const { return requested_region != allocatedRegion(); } + bool isFullyObscured() const { return allocatedRegion().isEmpty(); } + + enum State { NoState, Hidden, Showing, Visible, Hiding, Raising, Lowering, Moving, ChangingGeometry, Destroyed }; + State state() const; + Qt::WindowFlags windowFlags() const; + QRegion dirtyOnScreen() const; + + void raise(); + void lower(); + void show(); + void hide(); + void setActiveWindow(); + + bool isOpaque() const {return opaque && _opacity == 255;} + uint opacity() const { return _opacity; } + + QWSWindowSurface* windowSurface() const { return surface; } + +private: + bool hidden() const { return requested_region.isEmpty(); } + bool forClient(const QWSClient* cl) const { return cl==c; } + + void setName(const QString &n); + void setCaption(const QString &c); + + void focus(bool get); + int focusPriority() const { return last_focus_time; } + void operation(QWSWindowOperationEvent::Operation o); + void shuttingDown() { last_focus_time=0; } + +#ifdef QT_QWS_CLIENTBLIT + QRegion directPaintRegion() const; + inline void setDirectPaintRegion(const QRegion &topmost); +#endif + inline void setAllocatedRegion(const QRegion ®ion); + + void createSurface(const QString &key, const QByteArray &data); + +#ifndef QT_NO_QWSEMBEDWIDGET + void startEmbed(QWSWindow *window); + void stopEmbed(QWSWindow *window); +#endif + +private: + int id; + QString rgnName; + QString rgnCaption; + bool modified; + bool onTop; + QWSClient* c; + QRegion requested_region; + QRegion exposed; + int last_focus_time; + QWSWindowSurface *surface; + uint _opacity; + bool opaque; + QWSWindowPrivate *d; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QRegion requested() const { return requested_region; } +// inline QT3_SUPPORT QRegion allocation() const { return allocated_region; } +#endif +}; + + +#ifndef QT_NO_SOUND +class QWSSoundServer; +#ifdef QT_USE_OLD_QWS_SOUND +class QWSSoundServerData; + +class Q_GUI_EXPORT QWSSoundServer : public QObject { + Q_OBJECT +public: + QWSSoundServer(QObject* parent); + ~QWSSoundServer(); + void playFile(const QString& filename); +private Q_SLOTS: + void feedDevice(int fd); +private: + QWSSoundServerData* d; +}; +#endif +#endif + + +/********************************************************************* + * + * Class: QWSServer + * + *********************************************************************/ + +class QWSMouseHandler; +struct QWSCommandStruct; +class QWSServerPrivate; +class QWSServer; + +extern Q_GUI_EXPORT QWSServer *qwsServer; + +class Q_GUI_EXPORT QWSServer : public QObject +{ + friend class QCopChannel; + friend class QWSMouseHandler; + friend class QWSWindow; + friend class QWSDisplay; + friend class QWSInputMethod; + Q_OBJECT + Q_DECLARE_PRIVATE(QWSServer) +public: + explicit QWSServer(int flags = 0, QObject *parent=0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QWSServer(int flags, QObject *parent, const char *name); +#endif + ~QWSServer(); + enum ServerFlags { DisableKeyboard = 0x01, + DisableMouse = 0x02 }; + + static void sendKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat); +#ifndef QT_NO_QWS_KEYBOARD + static void processKeyEvent(int unicode, int keycode, Qt::KeyboardModifiers modifiers, + bool isPress, bool autoRepeat); +#endif + + static QWSServer* instance() { return qwsServer; } + +#ifndef QT_NO_QWS_INPUTMETHODS +#ifdef QT3_SUPPORT + enum IMState { IMCompose, IMEnd, IMStart = IMCompose }; +#endif + enum IMMouse { MousePress, MouseRelease, MouseMove, MouseOutside }; //MouseMove reserved but not used + void sendIMEvent(const QInputMethodEvent*); + void sendIMQuery(int property); +#endif + +#ifndef QT_NO_QWS_KEYBOARD + class KeyboardFilter + { + public: + virtual ~KeyboardFilter() {} + virtual bool filter(int unicode, int keycode, int modifiers, + bool isPress, bool autoRepeat)=0; + }; + static void addKeyboardFilter(KeyboardFilter *f); + static void removeKeyboardFilter(); +#endif + +#ifndef QT_NO_QWS_INPUTMETHODS + static void setCurrentInputMethod(QWSInputMethod *im); + static void resetInputMethod(); +#endif + + static void setDefaultMouse(const char *); + static void setDefaultKeyboard(const char *); + static void setMaxWindowRect(const QRect&); + static void sendMouseEvent(const QPoint& pos, int state, int wheel = 0); + + static void setBackground(const QBrush &); +#ifdef QT3_SUPPORT + static QT3_SUPPORT void setDesktopBackground(const QImage &img); + static QT3_SUPPORT void setDesktopBackground(const QColor &); +#endif + static QWSMouseHandler *mouseHandler(); + static const QList& mouseHandlers(); + static void setMouseHandler(QWSMouseHandler*); +#ifndef QT_NO_QWS_KEYBOARD + static QWSKeyboardHandler* keyboardHandler(); + static void setKeyboardHandler(QWSKeyboardHandler* kh); +#endif + QWSWindow *windowAt(const QPoint& pos); + + const QList &clientWindows(); + + void openMouse(); + void closeMouse(); + void suspendMouse(); + void resumeMouse(); +#ifndef QT_NO_QWS_KEYBOARD + void openKeyboard(); + void closeKeyboard(); +#endif + + static void setScreenSaver(QWSScreenSaver*); + static void setScreenSaverIntervals(int* ms); + static void setScreenSaverInterval(int); + static void setScreenSaverBlockLevel(int); + static bool screenSaverActive(); + static void screenSaverActivate(bool); + + // the following are internal. + void refresh(); + void refresh(QRegion &); + + void enablePainting(bool); + static void processEventQueue(); + static QList * windowList(); + + void sendPropertyNotifyEvent(int property, int state); + + static QPoint mousePosition; + + static void startup(int flags); + static void closedown(); + + static void beginDisplayReconfigure(); + static void endDisplayReconfigure(); + +#ifndef QT_NO_QWS_CURSOR + static void setCursorVisible(bool); + static bool isCursorVisible(); +#endif + + const QBrush &backgroundBrush() const; + + enum WindowEvent { Create=0x0001, Destroy=0x0002, Hide=0x0004, Show=0x0008, + Raise=0x0010, Lower=0x0020, Geometry=0x0040, Active = 0x0080, + Name=0x0100 }; + +Q_SIGNALS: + void windowEvent(QWSWindow *w, QWSServer::WindowEvent e); + +#ifndef QT_NO_COP + void newChannel(const QString& channel); + void removedChannel(const QString& channel); + +#endif +#ifndef QT_NO_QWS_INPUTMETHODS + void markedText(const QString &); +#endif + +protected: + void timerEvent(QTimerEvent *e); + +private: + friend class QApplicationPrivate; + void updateWindowRegions() const; + +#ifdef QT3_SUPPORT +#ifndef QT_NO_QWS_KEYBOARD + static inline QT3_SUPPORT void setKeyboardFilter(QWSServer::KeyboardFilter *f) + { if (f) addKeyboardFilter(f); else removeKeyboardFilter(); } +#endif +#endif + +private: +#ifndef QT_NO_QWS_MULTIPROCESS + Q_PRIVATE_SLOT(d_func(), void _q_clientClosed()) + Q_PRIVATE_SLOT(d_func(), void _q_doClient()) + Q_PRIVATE_SLOT(d_func(), void _q_deleteWindowsLater()) +#endif + + Q_PRIVATE_SLOT(d_func(), void _q_screenSaverWake()) + Q_PRIVATE_SLOT(d_func(), void _q_screenSaverSleep()) + Q_PRIVATE_SLOT(d_func(), void _q_screenSaverTimeout()) + +#ifndef QT_NO_QWS_MULTIPROCESS + Q_PRIVATE_SLOT(d_func(), void _q_newConnection()) +#endif +}; + +#ifndef QT_NO_QWS_INPUTMETHODS +class Q_GUI_EXPORT QWSInputMethod : public QObject +{ + Q_OBJECT +public: + QWSInputMethod(); + virtual ~QWSInputMethod(); + + enum UpdateType {Update, FocusIn, FocusOut, Reset, Destroyed}; + + virtual bool filter(int unicode, int keycode, int modifiers, + bool isPress, bool autoRepeat); + + virtual bool filter(const QPoint &, int state, int wheel); + + virtual void reset(); + virtual void updateHandler(int type); + virtual void mouseHandler(int pos, int state); + virtual void queryResponse(int property, const QVariant&); + +protected: + uint setInputResolution(bool isHigh); + uint inputResolutionShift() const; + // needed for required transform + void sendMouseEvent(const QPoint &pos, int state, int wheel); + + void sendEvent(const QInputMethodEvent*); + void sendPreeditString(const QString &preeditString, int cursorPosition, int selectionLength = 0); + void sendCommitString(const QString &commitString, int replaceFrom = 0, int replaceLength = 0); + void sendQuery(int property); + +#ifdef QT3_SUPPORT + inline void sendIMEvent(QWSServer::IMState, const QString& txt, int cpos, int selLen = 0); +#endif +private: + bool mIResolution; +}; + +inline void QWSInputMethod::sendEvent(const QInputMethodEvent *ime) +{ + qwsServer->sendIMEvent(ime); +} +#ifdef QT3_SUPPORT +inline void QWSInputMethod::sendIMEvent(QWSServer::IMState state, const QString& txt, int cpos, int selLen) +{ + if (state == QWSServer::IMCompose) sendPreeditString(txt, cpos, selLen); else sendCommitString(txt); +} +#endif + +inline void QWSInputMethod::sendQuery(int property) +{ + qwsServer->sendIMQuery(property); +} + +// mouse events not inline as involve transformations. +#endif // QT_NO_QWS_INPUTMETHODS + + + +/********************************************************************* + * + * Class: QWSClient + * + *********************************************************************/ + +struct QWSMouseEvent; + +typedef QMap QWSCursorMap; + +class QWSClientPrivate; +class QWSCommand; +class QWSConvertSelectionCommand; + +class Q_GUI_EXPORT QWSClient : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWSClient) +public: + QWSClient(QObject* parent, QWS_SOCK_BASE *, int id); + ~QWSClient(); + + int socket() const; + + void setIdentity(const QString&); + QString identity() const { return id; } + + void sendEvent(QWSEvent* event); + void sendConnectedEvent(const char *display_spec); + void sendMaxWindowRectEvent(const QRect &rect); + void sendPropertyNotifyEvent(int property, int state); + void sendPropertyReplyEvent(int property, int len, const char *data); + void sendSelectionClearEvent(int windowid); + void sendSelectionRequestEvent(QWSConvertSelectionCommand *cmd, int windowid); +#ifndef QT_QWS_CLIENTBLIT + void sendRegionEvent(int winid, QRegion rgn, int type); +#else + void sendRegionEvent(int winid, QRegion rgn, int type, int id = 0); +#endif +#ifndef QT_NO_QWSEMBEDWIDGET + void sendEmbedEvent(int winid, QWSEmbedEvent::Type type, + const QRegion ®ion = QRegion()); +#endif + QWSCommand* readMoreCommand(); + + int clientId() const { return cid; } + + QWSCursorMap cursors; // cursors defined by this client +Q_SIGNALS: + void connectionClosed(); + void readyRead(); +private Q_SLOTS: + void closeHandler(); + void errorHandler(); + +private: +#ifndef QT_NO_QWS_MULTIPROCESS + friend class QWSWindow; + void removeUnbufferedSurface(); + void addUnbufferedSurface(); +#endif + +private: + int socketDescriptor; +#ifndef QT_NO_QWS_MULTIPROCESS + QWSSocket *csocket; +#endif + QWSCommand* command; + uint isClosed : 1; + QString id; + int cid; + + friend class QWSServerPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWINDOWSYSTEM_QWS_H diff --git a/src/gui/embedded/qwscommand_qws.cpp b/src/gui/embedded/qwscommand_qws.cpp new file mode 100644 index 0000000000..71d3a06dbe --- /dev/null +++ b/src/gui/embedded/qwscommand_qws.cpp @@ -0,0 +1,609 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwscommand_qws_p.h" +#include "qtransportauth_qws.h" +#include "qtransportauth_qws_p.h" + +#include + +// #define QWSCOMMAND_DEBUG 1 // Uncomment to debug client/server communication + +#ifdef QWSCOMMAND_DEBUG +# include +# include "qfile.h" +# include +#endif + +QT_BEGIN_NAMESPACE + +#ifdef QWSCOMMAND_DEBUG +// QWSHexDump -[ start ]--------------------------------------------- +# define QWSHEXDUMP_MAX 32 +class QWSHexDump +{ +public: + + QWSHexDump(const void *address, int len, int wrapAt = 16) + : wrap(wrapAt), dataSize(len) + { + init(); + data = reinterpret_cast(address); + if (len < 0) + dataSize = 0; + } + + QWSHexDump(const char *str, int len = -1, int wrapAt = 16) + : wrap(wrapAt), dataSize(len) + { + init(); + data = str; + if (len == -1) + dataSize = str ? strlen(str) : 0; + } + + QWSHexDump(const QByteArray &array, int wrapAt = 16) + : wrap(wrapAt) + { + init(); + data = array.data(); + dataSize = array.size(); + } + + // Sets a customized prefix for the hexdump + void setPrefix(const char *str) { prefix = str; } + + // Sets number of bytes to cluster together + void setClusterSize(uint num) { clustering = num; } + + // Output hexdump to a text stream + void intoTextStream(QTextStream &strm) { + outstrm = &strm; + hexDump(); + } + + // Output hexdump to a QString + QString toString(); + +protected: + void init(); + void hexDump(); + void sideviewDump(int at); + +private: + uint wrap; + uint clustering; + uint dataSize; + int dataWidth; + const char *data; + const char *prefix; + bool dirty; + + char sideviewLayout[QWSHEXDUMP_MAX + 1]; + char sideview[15]; + + QTextStream *outstrm; +}; + +void QWSHexDump::init() +{ + prefix = "> "; // Standard line prefix + clustering = 2; // Word-size clustering by default + if (wrap > QWSHEXDUMP_MAX) // No wider than QWSHexDump_MAX bytes + wrap = QWSHEXDUMP_MAX; +} + +void QWSHexDump::hexDump() +{ + *outstrm << '(' << dataSize << " bytes):\n" << prefix; + sprintf(sideviewLayout, " [%%-%us]", wrap); + dataWidth = (2 * wrap) + (wrap / clustering); + + dirty = false; + uint wrapIndex = 0; + for (uint i = 0; i < dataSize; i++) { + uint c = static_cast(data[i]); + sideview[wrapIndex = i%wrap] = isprint(c) ? c : '.'; + + if (wrapIndex && (wrapIndex % clustering == 0)) + *outstrm << ' '; + + outstrm->setFieldWidth(2); + outstrm->setPadChar('0'); + outstrm->setNumberFlags( QTextStream::ShowBase ); + *outstrm << hex << c; + dirty = true; + + if (wrapIndex == wrap-1) { + sideviewDump(wrapIndex); + wrapIndex = 0; + if (i+1 < dataSize) + *outstrm << endl << prefix; + } + + } + sideviewDump(wrapIndex); +} + +void QWSHexDump::sideviewDump(int at) +{ + if (dirty) { + dirty = false; + ++at; + sideview[at] = '\0'; + int currentWidth = (2 * at) + (at / clustering) - (at%clustering?0:1); + int missing = qMax(dataWidth - currentWidth, 0); + while (missing--) + *outstrm << ' '; + + *outstrm << " ["; + outstrm->setPadChar(' '); + outstrm->setFieldWidth(wrap); + outstrm->setFieldAlignment( QTextStream::AlignLeft ); + *outstrm << sideview; + *outstrm << ']'; + } +} + +// Output hexdump to a QString +QString QWSHexDump::toString() { + QString result; + QTextStream strm(&result, QFile::WriteOnly); + outstrm = &strm; + hexDump(); + return result; +} + +#ifndef QT_NO_DEBUG +QDebug &operator<<(QDebug &dbg, QWSHexDump *hd) { + if (!hd) + return dbg << "QWSHexDump(0x0)"; + QString result = hd->toString(); + dbg.nospace() << result; + return dbg.space(); +} + +// GCC & Intel wont handle references here +QDebug operator<<(QDebug dbg, QWSHexDump hd) { + return dbg << &hd; +} +#endif +// QWSHexDump -[ end ]----------------------------------------------- + + +QDebug &operator<<(QDebug &dbg, QWSCommand::Type tp) +{ + dbg << qws_getCommandTypeString( tp ); + return dbg; +} + +#define N_EVENTS 19 +const char * eventNames[N_EVENTS] = { + "NoEvent", + "Connected", + "Mouse", "Focus", "Key", + "Region", + "Creation", + "PropertyNotify", + "PropertyReply", + "SelectionClear", + "SelectionRequest", + "SelectionNotify", + "MaxWindowRect", + "QCopMessage", + "WindowOperation", + "IMEvent", + "IMQuery", + "IMInit", + "Font" + }; + +class QWSServer; +extern QWSServer *qwsServer; +#endif + +const char *qws_getCommandTypeString( QWSCommand::Type tp ) +{ + const char *typeStr; + switch(tp) { + case QWSCommand::Create: + typeStr = "Create"; + break; + case QWSCommand::Shutdown: + typeStr = "Shutdown"; + break; + case QWSCommand::Region: + typeStr = "Region"; + break; + case QWSCommand::RegionMove: + typeStr = "RegionMove"; + break; + case QWSCommand::RegionDestroy: + typeStr = "RegionDestroy"; + break; + case QWSCommand::SetProperty: + typeStr = "SetProperty"; + break; + case QWSCommand::AddProperty: + typeStr = "AddProperty"; + break; + case QWSCommand::RemoveProperty: + typeStr = "RemoveProperty"; + break; + case QWSCommand::GetProperty: + typeStr = "GetProperty"; + break; + case QWSCommand::SetSelectionOwner: + typeStr = "SetSelectionOwner"; + break; + case QWSCommand::ConvertSelection: + typeStr = "ConvertSelection"; + break; + case QWSCommand::RequestFocus: + typeStr = "RequestFocus"; + break; + case QWSCommand::ChangeAltitude: + typeStr = "ChangeAltitude"; + break; + case QWSCommand::SetOpacity: + typeStr = "SetOpacity"; + break; + case QWSCommand::DefineCursor: + typeStr = "DefineCursor"; + break; + case QWSCommand::SelectCursor: + typeStr = "SelectCursor"; + break; + case QWSCommand::PositionCursor: + typeStr = "PositionCursor"; + break; + case QWSCommand::GrabMouse: + typeStr = "GrabMouse"; + break; + case QWSCommand::PlaySound: + typeStr = "PlaySound"; + break; + case QWSCommand::QCopRegisterChannel: + typeStr = "QCopRegisterChannel"; + break; + case QWSCommand::QCopSend: + typeStr = "QCopSend"; + break; + case QWSCommand::RegionName: + typeStr = "RegionName"; + break; + case QWSCommand::Identify: + typeStr = "Identify"; + break; + case QWSCommand::GrabKeyboard: + typeStr = "GrabKeyboard"; + break; + case QWSCommand::RepaintRegion: + typeStr = "RepaintRegion"; + break; + case QWSCommand::IMMouse: + typeStr = "IMMouse"; + break; + case QWSCommand::IMUpdate: + typeStr = "IMUpdate"; + break; + case QWSCommand::IMResponse: + typeStr = "IMResponse"; + break; + case QWSCommand::Font: + typeStr = "Font"; + break; + case QWSCommand::Unknown: + default: + typeStr = "Unknown"; + break; + } + return typeStr; +} + + +/********************************************************************* + * + * Functions to read/write commands on/from a socket + * + *********************************************************************/ + +#ifndef QT_NO_QWS_MULTIPROCESS +void qws_write_command(QIODevice *socket, int type, char *simpleData, int simpleLen, + char *rawData, int rawLen) +{ +#ifdef QWSCOMMAND_DEBUG + if (simpleLen) qDebug() << "WRITE simpleData " << QWSHexDump(simpleData, simpleLen); + if (rawLen > 0) qDebug() << "WRITE rawData " << QWSHexDump(rawData, rawLen); +#endif + +#ifndef QT_NO_SXE + QTransportAuth *a = QTransportAuth::getInstance(); + // ###### as soon as public API can be modified get rid of horrible casts + QIODevice *ad = a->passThroughByClient(reinterpret_cast(socket)); + if (ad) + socket = ad; +#endif + + qws_write_uint(socket, type); + + if (rawLen > MAX_COMMAND_SIZE) { + qWarning("qws_write_command: Message of size %d too big. " + "Truncated to %d", rawLen, MAX_COMMAND_SIZE); + rawLen = MAX_COMMAND_SIZE; + } + + qws_write_uint(socket, rawLen == -1 ? 0 : rawLen); + + if (simpleData && simpleLen) + socket->write(simpleData, simpleLen); + + if (rawLen && rawData) + socket->write(rawData, rawLen); +} + +/* + command format: [type][rawLen][simpleData][rawData] + type is already read when entering this function +*/ + +bool qws_read_command(QIODevice *socket, char *&simpleData, int &simpleLen, + char *&rawData, int &rawLen, int &bytesRead) +{ + + // read rawLen + if (rawLen == -1) { + rawLen = qws_read_uint(socket); + if (rawLen == -1) + return false; + } + + // read simpleData, assumes socket is capable of buffering all the data + if (simpleLen && !rawData) { + if (socket->bytesAvailable() < uint(simpleLen)) + return false; + int tmp = socket->read(simpleData, simpleLen); + Q_ASSERT(tmp == simpleLen); + Q_UNUSED(tmp); + } + + if (rawLen > MAX_COMMAND_SIZE) { + socket->close(); + qWarning("qws_read_command: Won't read command of length %d, " + "connection closed.", rawLen); + return false; + } + + // read rawData + if (rawLen && !rawData) { + rawData = new char[rawLen]; + bytesRead = 0; + } + if (bytesRead < rawLen && socket->bytesAvailable()) + bytesRead += socket->read(rawData + bytesRead, rawLen - bytesRead); + + return (bytesRead == rawLen); +} +#endif + +/********************************************************************* + * + * QWSCommand base class - only use derived classes from that + * + *********************************************************************/ +QWSProtocolItem::~QWSProtocolItem() { + if (deleteRaw) + delete []rawDataPtr; +} + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSProtocolItem::write(QIODevice *s) { +#ifdef QWSCOMMAND_DEBUG + if (!qwsServer) + qDebug() << "QWSProtocolItem::write sending type " << static_cast(type); + else + qDebug() << "QWSProtocolItem::write sending event " << (type < N_EVENTS ? eventNames[type] : "unknown"); +#endif + qws_write_command(s, type, simpleDataPtr, simpleLen, rawDataPtr, rawLen); +} + +bool QWSProtocolItem::read(QIODevice *s) { +#ifdef QWSCOMMAND_DEBUG + QLatin1String reread( (rawLen == -1) ? "" : "REREAD"); + if (qwsServer) + qDebug() << "QWSProtocolItem::read reading type " << static_cast(type) << reread; + else + qDebug() << "QWSProtocolItem::read reading event " << (type < N_EVENTS ? eventNames[type] : "unknown") << reread; + //qDebug("QWSProtocolItem::read reading event %s", type < N_EVENTS ? eventNames[type] : "unknown"); +#endif + bool b = qws_read_command(s, simpleDataPtr, simpleLen, rawDataPtr, rawLen, bytesRead); + if (b) { + setData(rawDataPtr, rawLen, false); + deleteRaw = true; + } +#ifdef QWSCOMMAND_DEBUG + else + { + qDebug() << "error in reading command " << static_cast(type); + } +#endif + return b; +} +#endif // QT_NO_QWS_MULTIPROCESS + +void QWSProtocolItem::copyFrom(const QWSProtocolItem *item) { + if (this == item) + return; + simpleLen = item->simpleLen; + memcpy(simpleDataPtr, item->simpleDataPtr, simpleLen); + setData(item->rawDataPtr, item->rawLen); +} + +void QWSProtocolItem::setData(const char *data, int len, bool allocateMem) { + if (deleteRaw) + delete [] rawDataPtr; + if (!data || len <= 0) { + rawDataPtr = 0; + rawLen = 0; + return; + } + if (allocateMem) { + rawDataPtr = new char[len]; + memcpy(rawDataPtr, data, len); + deleteRaw = true; + } else { + rawDataPtr = const_cast(data); + deleteRaw = false; + } + rawLen = len; +} + +QWSCommand *QWSCommand::factory(int type) +{ + QWSCommand *command = 0; + switch (type) { + case QWSCommand::Create: + command = new QWSCreateCommand; + break; + case QWSCommand::Shutdown: + command = new QWSCommand(type, 0, 0); + break; + case QWSCommand::Region: + command = new QWSRegionCommand; + break; + case QWSCommand::RegionMove: + command = new QWSRegionMoveCommand; + break; + case QWSCommand::RegionDestroy: + command = new QWSRegionDestroyCommand; + break; + case QWSCommand::AddProperty: + command = new QWSAddPropertyCommand; + break; + case QWSCommand::SetProperty: + command = new QWSSetPropertyCommand; + break; + case QWSCommand::RemoveProperty: + command = new QWSRemovePropertyCommand; + break; + case QWSCommand::GetProperty: + command = new QWSGetPropertyCommand; + break; + case QWSCommand::SetSelectionOwner: + command = new QWSSetSelectionOwnerCommand; + break; + case QWSCommand::RequestFocus: + command = new QWSRequestFocusCommand; + break; + case QWSCommand::ChangeAltitude: + command = new QWSChangeAltitudeCommand; + break; + case QWSCommand::SetOpacity: + command = new QWSSetOpacityCommand; + break; + case QWSCommand::DefineCursor: + command = new QWSDefineCursorCommand; + break; + case QWSCommand::SelectCursor: + command = new QWSSelectCursorCommand; + break; + case QWSCommand::GrabMouse: + command = new QWSGrabMouseCommand; + break; + case QWSCommand::GrabKeyboard: + command = new QWSGrabKeyboardCommand; + break; +#ifndef QT_NO_SOUND + case QWSCommand::PlaySound: + command = new QWSPlaySoundCommand; + break; +#endif +#ifndef QT_NO_COP + case QWSCommand::QCopRegisterChannel: + command = new QWSQCopRegisterChannelCommand; + break; + case QWSCommand::QCopSend: + command = new QWSQCopSendCommand; + break; +#endif + case QWSCommand::RegionName: + command = new QWSRegionNameCommand; + break; + case QWSCommand::Identify: + command = new QWSIdentifyCommand; + break; + case QWSCommand::RepaintRegion: + command = new QWSRepaintRegionCommand; + break; +#ifndef QT_NO_QWS_INPUTMETHODS + case QWSCommand::IMUpdate: + command = new QWSIMUpdateCommand; + break; + + case QWSCommand::IMMouse: + command = new QWSIMMouseCommand; + break; + + case QWSCommand::IMResponse: + command = new QWSIMResponseCommand; + break; +#endif + case QWSCommand::PositionCursor: + command = new QWSPositionCursorCommand; + break; +#ifndef QT_NO_QWSEMBEDWIDGET + case QWSCommand::Embed: + command = new QWSEmbedCommand; + break; +#endif + case QWSCommand::Font: + command = new QWSFontCommand; + break; + case QWSCommand::ScreenTransform: + command = new QWSScreenTransformCommand; + break; + default: + qWarning("QWSCommand::factory : Type error - got %08x!", type); + } + return command; +} + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qwscommand_qws_p.h b/src/gui/embedded/qwscommand_qws_p.h new file mode 100644 index 0000000000..70ecc6be6d --- /dev/null +++ b/src/gui/embedded/qwscommand_qws_p.h @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSCOMMAND_QWS_P_H +#define QWSCOMMAND_QWS_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. +// + +// When reading commands "off the wire" in the server, the rawLen is read +// and then that many bytes are allocated. If the rawLen is corrupted (or +// the protocol is being attacked) too many bytes can be allocated. Set +// a hard limit here for security. +#define MAX_COMMAND_SIZE (16 * 1024) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qwsprotocolitem_qws.h" + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QRect; + +/********************************************************************* + * + * Functions to read/write commands on/from a socket + * + *********************************************************************/ +#ifndef QT_NO_QWS_MULTIPROCESS +void qws_write_command(QIODevice *socket, int type, char *simpleData, int simpleLen, char *rawData, int rawLen); +bool qws_read_command(QIODevice *socket, char *&simpleData, int &simpleLen, char *&rawData, int &rawLen, int &bytesRead); +#endif + +struct QWSCommand : QWSProtocolItem +{ + QWSCommand(int t, int len, char *ptr) : QWSProtocolItem(t,len,ptr) {} + + enum Type { + Unknown = 0, + Create, + Shutdown, + Region, + RegionMove, + RegionDestroy, + SetProperty, + AddProperty, + RemoveProperty, + GetProperty, + SetSelectionOwner, + ConvertSelection, + RequestFocus, + ChangeAltitude, + SetOpacity, + DefineCursor, + SelectCursor, + PositionCursor, + GrabMouse, + PlaySound, + QCopRegisterChannel, + QCopSend, + RegionName, + Identify, + GrabKeyboard, + RepaintRegion, + IMMouse, + IMUpdate, + IMResponse, + Embed, + Font, + ScreenTransform + }; + static QWSCommand *factory(int type); +}; + +const char *qws_getCommandTypeString( QWSCommand::Type tp ); + +#ifndef QT_NO_DEBUG +class QDebug; +QDebug &operator<<(QDebug &dbg, QWSCommand::Type tp); +#endif // QT_NO_DEBUG + +/********************************************************************* + * + * Commands + * + *********************************************************************/ + +struct QWSIdentifyCommand : public QWSCommand +{ + QWSIdentifyCommand() : + QWSCommand(QWSCommand::Identify, + sizeof(simpleData), reinterpret_cast(&simpleData)) + { + simpleData.idLen = 0; + simpleData.idLock = -1; + } + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + if ( simpleData.idLen > MAX_COMMAND_SIZE ) + { + qWarning( "Identify command - name length %d - too big!", simpleData.idLen ); + simpleData.idLen = MAX_COMMAND_SIZE; + } + if ( simpleData.idLen * int(sizeof(QChar)) > len ) + { + qWarning( "Identify command - name length %d - buffer size %d - buffer overrun!", simpleData.idLen, len ); + } + else + { + id = QString(reinterpret_cast(d), simpleData.idLen); + } + } + + void setId(const QString& i, int lock) + { + id = i; + simpleData.idLen = id.length(); + simpleData.idLock = lock; + setData(reinterpret_cast(id.unicode()), simpleData.idLen*2, true); + } + + struct SimpleData { + int idLen; + int idLock; + } simpleData; + QString id; +}; + +struct QWSCreateCommand : public QWSCommand +{ + QWSCreateCommand(int n = 1) : + QWSCommand(QWSCommand::Create, sizeof(count), + reinterpret_cast(&count)), count(n) {} + int count; +}; + +struct QWSRegionNameCommand : public QWSCommand +{ + QWSRegionNameCommand() : + QWSCommand(QWSCommand::RegionName, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + if ( simpleData.nameLen > MAX_COMMAND_SIZE ) + { + qWarning( "region name command - name length too big!" ); + simpleData.nameLen = MAX_COMMAND_SIZE; + } + if ( simpleData.captionLen > MAX_COMMAND_SIZE ) + { + qWarning( "region name command - caption length too big!" ); + simpleData.captionLen = MAX_COMMAND_SIZE; + } + if ( simpleData.nameLen + simpleData.captionLen > len ) + { + qWarning( "region name command - name length %d - caption length %d - buffer size %d - buffer overrun!", + simpleData.nameLen, simpleData.captionLen, len ); + + } + else + { + name = QString(reinterpret_cast(d), simpleData.nameLen/2); + d += simpleData.nameLen; + caption = QString(reinterpret_cast(d), simpleData.captionLen/2); + } + } + + void setName(const QString& n, const QString &c) + { + name = n; + caption = c; + int l = simpleData.nameLen = name.length()*2; + l += simpleData.captionLen = caption.length()*2; + char *d = new char[l]; + memcpy(d, name.unicode(), simpleData.nameLen); + memcpy(d+simpleData.nameLen, caption.unicode(), simpleData.captionLen); + setData(d, l, true); + delete[] d; + } + + struct SimpleData { + int windowid; + int nameLen; + int captionLen; + } simpleData; + QString name; + QString caption; +}; + +struct QWSRegionCommand : public QWSCommand +{ + QWSRegionCommand() : + QWSCommand(QWSCommand::Region, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem = true) { + QWSCommand::setData(d, len, allocateMem); + + if( simpleData.nrectangles * int(sizeof(QRect)) + simpleData.surfacekeylength * int(sizeof(QChar)) + simpleData.surfacedatalength * int(sizeof(char)) > len ) + { + qWarning( "region command - rectangle count %d - surface key length %d - region data size %d - buffer size %d - buffer overrun!", + simpleData.nrectangles, simpleData.surfacekeylength, simpleData.surfacedatalength, len ); + } + else + { + char *ptr = rawDataPtr; + + region.setRects(reinterpret_cast(ptr), simpleData.nrectangles); + ptr += simpleData.nrectangles * sizeof(QRect); + + surfaceKey = QString(reinterpret_cast(ptr), + simpleData.surfacekeylength); + ptr += simpleData.surfacekeylength * sizeof(QChar); + + surfaceData = QByteArray(ptr, simpleData.surfacedatalength); + } + } + + void setData(int id, const QString &key, const QByteArray &data, + const QRegion ®) + { + surfaceKey = key; + surfaceData = data; + region = reg; + + const QVector rects = reg.rects(); + + simpleData.windowid = id; + simpleData.surfacekeylength = key.size(); + simpleData.surfacedatalength = data.size(); + simpleData.nrectangles = rects.count(); + + QVarLengthArray buffer; + buffer.append(reinterpret_cast(rects.constData()), + rects.count() * sizeof(QRect)); + buffer.append(reinterpret_cast(key.constData()), + key.size() * sizeof(QChar)); + buffer.append(data, data.size()); + + QWSCommand::setData(buffer.constData(), buffer.size(), true); + } + + /* XXX this will pad out in a compiler dependent way, + should move nrectangles to before windowtype, and + add reserved bytes. + Symptom will be valgrind reported uninitialized memory usage + */ + struct SimpleData { + int windowid; + int surfacekeylength; + int surfacedatalength; + int nrectangles; + } simpleData; + + QString surfaceKey; + QByteArray surfaceData; + QRegion region; +}; + +struct QWSSetOpacityCommand : public QWSCommand +{ + QWSSetOpacityCommand() : + QWSCommand(QWSCommand::SetOpacity, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + uchar opacity; + } simpleData; +}; + +struct QWSRegionMoveCommand : public QWSCommand +{ + QWSRegionMoveCommand() : + QWSCommand(QWSCommand::RegionMove, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int dx; + int dy; + } simpleData; + +}; + +struct QWSRegionDestroyCommand : public QWSCommand +{ + QWSRegionDestroyCommand() : + QWSCommand(QWSCommand::RegionDestroy, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + } simpleData; + +}; + +struct QWSRequestFocusCommand : public QWSCommand +{ + QWSRequestFocusCommand() : + QWSCommand(QWSCommand::RequestFocus, sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int flag; + } simpleData; +}; + +struct QWSChangeAltitudeCommand : public QWSCommand +{ + QWSChangeAltitudeCommand() : + QWSCommand(QWSCommand::ChangeAltitude, sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + enum Altitude { + Lower = -1, + Raise = 0, + StaysOnTop = 1 + }; + + struct SimpleData { + int windowid; + Altitude altitude; + bool fixed; + } simpleData; + +}; + + +struct QWSAddPropertyCommand : public QWSCommand +{ + QWSAddPropertyCommand() : + QWSCommand(QWSCommand::AddProperty, sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid, property; + } simpleData; + +}; + +struct QWSSetPropertyCommand : public QWSCommand +{ + QWSSetPropertyCommand() : + QWSCommand(QWSCommand::SetProperty, sizeof(simpleData), + reinterpret_cast(&simpleData)) { data = 0; } + + void setData(const char *d, int len, bool allocateMem = true) { + QWSCommand::setData(d, len, allocateMem); + data = rawDataPtr; + } + + struct SimpleData { + int windowid, property, mode; + } simpleData; + + char *data; +}; + +struct QWSRepaintRegionCommand : public QWSCommand +{ + QWSRepaintRegionCommand() : + QWSCommand(QWSCommand::RepaintRegion, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem = true) { + QWSCommand::setData(d, len, allocateMem); + + if( simpleData.nrectangles * int(sizeof(QRect)) > len ) + { + qWarning( "repaint region command - region rectangle count %d - buffer size %d - buffer overrun", + simpleData.nrectangles, len ); + + simpleData.nrectangles = len / sizeof(QRect); + } + rectangles = reinterpret_cast(rawDataPtr); + } + + struct SimpleData { + int windowid; + int windowFlags; + bool opaque; + int nrectangles; + } simpleData; + + QRect * rectangles; + +}; + +struct QWSRemovePropertyCommand : public QWSCommand +{ + QWSRemovePropertyCommand() : + QWSCommand(QWSCommand::RemoveProperty, sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid, property; + } simpleData; + +}; + +struct QWSGetPropertyCommand : public QWSCommand +{ + QWSGetPropertyCommand() : + QWSCommand(QWSCommand::GetProperty, sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid, property; + } simpleData; + +}; + +struct QWSSetSelectionOwnerCommand : public QWSCommand +{ + QWSSetSelectionOwnerCommand() : + QWSCommand(QWSCommand::SetSelectionOwner, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int hour, minute, sec, ms; // time + } simpleData; + +}; + +struct QWSConvertSelectionCommand : public QWSCommand +{ + QWSConvertSelectionCommand() : + QWSCommand(QWSCommand::ConvertSelection, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int requestor; // requestor window of the selection + int selection; // property on requestor into which the selection should be stored + int mimeTypes; // property ion requestor in which the mimetypes, in which the selection may be, are stored + } simpleData; + +}; + +struct QWSDefineCursorCommand : public QWSCommand +{ + QWSDefineCursorCommand() : + QWSCommand(QWSCommand::DefineCursor, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem = true) { + QWSCommand::setData(d, len, allocateMem); + data = reinterpret_cast(rawDataPtr); + if (simpleData.height * ((simpleData.width+7) / 8) > len) { + qWarning("define cursor command - width %d height %d- buffer size %d - buffer overrun", + simpleData.width, simpleData.height, len ); + simpleData.width = simpleData.height = 0; + } + } + + struct SimpleData { + int width; + int height; + int hotX; + int hotY; + int id; + } simpleData; + + unsigned char *data; +}; + +struct QWSSelectCursorCommand : public QWSCommand +{ + QWSSelectCursorCommand() : + QWSCommand(QWSCommand::SelectCursor, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int id; + } simpleData; +}; + +struct QWSPositionCursorCommand : public QWSCommand +{ + QWSPositionCursorCommand() : + QWSCommand(QWSCommand::PositionCursor, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int newX; + int newY; + } simpleData; +}; + +struct QWSGrabMouseCommand : public QWSCommand +{ + QWSGrabMouseCommand() : + QWSCommand(QWSCommand::GrabMouse, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + bool grab; // grab or ungrab? + } simpleData; +}; + +struct QWSGrabKeyboardCommand : public QWSCommand +{ + QWSGrabKeyboardCommand() : + QWSCommand(QWSCommand::GrabKeyboard, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + bool grab; // grab or ungrab? + } simpleData; +}; + +#ifndef QT_NO_SOUND +struct QWSPlaySoundCommand : public QWSCommand +{ + QWSPlaySoundCommand() : + QWSCommand(QWSCommand::PlaySound, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + filename = QString(reinterpret_cast(rawDataPtr),len/2); + } + void setFileName(const QString& n) + { + setData(reinterpret_cast(n.unicode()), n.length()*2, true); + } + + struct SimpleData { + int windowid; + } simpleData; + QString filename; +}; +#endif + + +#ifndef QT_NO_COP +struct QWSQCopRegisterChannelCommand : public QWSCommand +{ + QWSQCopRegisterChannelCommand() : + QWSCommand(QWSCommand::QCopRegisterChannel, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + if ( simpleData.chLen > MAX_COMMAND_SIZE ) + { + qWarning( "Command channel name too large!" ); + simpleData.chLen = MAX_COMMAND_SIZE; + } + if( simpleData.chLen * int(sizeof(QChar)) > len ) + { + qWarning( "register qcop channel command - channel name length %d - buffer size %d - buffer overrun!", simpleData.chLen, len ); + } + else + { + channel = QString(reinterpret_cast(d), simpleData.chLen); + } + } + + void setChannel(const QString& n) + { + channel = n; + simpleData.chLen = channel.length(); + setData(reinterpret_cast(channel.unicode()), simpleData.chLen*2, true); + } + + struct SimpleData { + int chLen; + } simpleData; + QString channel; +}; + +struct QWSQCopSendCommand : public QWSCommand +{ + QWSQCopSendCommand() : + QWSCommand(QWSCommand::QCopSend, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + + if( simpleData.clen * int(sizeof(QChar)) + simpleData.mlen * int(sizeof(QChar)) + simpleData.dlen * int(sizeof(char)) > len ) + { + qWarning( "qcop send command - channel name length %d - message name length %d - data size %d - buffer size %d - buffer overrun!", + simpleData.clen, simpleData.mlen, simpleData.dlen, len ); + } + else + { + const QChar *cd = reinterpret_cast(d); + channel = QString(cd,simpleData.clen); cd += simpleData.clen; + message = QString(cd,simpleData.mlen); + d += simpleData.clen*sizeof(QChar) + simpleData.mlen*sizeof(QChar); + data = QByteArray(d, simpleData.dlen); + } + } + + void setMessage(const QString &c, const QString &m, + const QByteArray &data) + { + this->channel = c; + this->message = m; + this->data = data; + simpleData.clen = c.length(); + simpleData.mlen = m.length(); + simpleData.dlen = data.size(); + int l = simpleData.clen*sizeof(QChar); + l += simpleData.mlen*sizeof(QChar); + l += simpleData.dlen; + char *tmp = new char[l]; + char *d = tmp; + memcpy(d, c.unicode(), simpleData.clen*sizeof(QChar)); + d += simpleData.clen*sizeof(QChar); + memcpy(d, m.unicode(), simpleData.mlen*sizeof(QChar)); + d += simpleData.mlen*sizeof(QChar); + memcpy(d, data.data(), simpleData.dlen); + QWSCommand::setData(tmp, l, false); + deleteRaw = true; + } + + struct SimpleData { + int clen; + int mlen; + int dlen; + } simpleData; + QString channel; + QString message; + QByteArray data; +}; + +#endif + + +#ifndef QT_NO_QWS_INPUTMETHODS + +struct QWSIMMouseCommand : public QWSCommand +{ + QWSIMMouseCommand() : + QWSCommand(QWSCommand::IMMouse, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int state; + int index; + } simpleData; +}; + + +struct QWSIMResponseCommand : public QWSCommand +{ + QWSIMResponseCommand() : + QWSCommand(QWSCommand::IMResponse, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + + QByteArray tmp = QByteArray::fromRawData(d, len); + QDataStream s(tmp); + s >> result; + } + + void setResult(const QVariant & v) + { + QByteArray tmp; + QDataStream s(&tmp, QIODevice::WriteOnly); + s << v; + setData(tmp.data(), tmp.size(), true); + } + + struct SimpleData { + int windowid; + int property; + } simpleData; + + QVariant result; +}; + +struct QWSIMUpdateCommand: public QWSCommand +{ + QWSIMUpdateCommand() : + QWSCommand(QWSCommand::IMUpdate, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int windowid; + int type; + int widgetid; + } simpleData; +}; + +#endif + +#ifndef QT_NO_QWSEMBEDWIDGET +struct QWSEmbedCommand : public QWSCommand +{ + QWSEmbedCommand() : QWSCommand(QWSCommand::Embed, + sizeof(simpleData), + reinterpret_cast(&simpleData)) + {} + + void setData(const char *d, int len, bool allocateMem = true) + { + QWSCommand::setData(d, len, allocateMem); + + if( simpleData.rects * int(sizeof(QRect)) > len ) + { + qWarning( "embed command - region rectangle count %d - buffer size %d - buffer overrun!", + simpleData.rects, len ); + } + else + { + region.setRects(reinterpret_cast(rawDataPtr), + simpleData.rects); + } + } + + void setData(WId embedder, WId embedded, QWSEmbedEvent::Type type, + const QRegion reg = QRegion()) + { + simpleData.embedder = embedder; + simpleData.embedded = embedded; + simpleData.type = type; + + region = reg; + const QVector rects = reg.rects(); + simpleData.rects = rects.count(); + + QWSCommand::setData(reinterpret_cast(rects.constData()), + rects.count() * sizeof(QRect)); + } + + struct { + WId embedder; + WId embedded; + QWSEmbedEvent::Type type; + int rects; + } simpleData; + + QRegion region; +}; +#endif // QT_NO_QWSEMBEDWIDGET + +struct QWSFontCommand : public QWSCommand +{ + enum CommandType { + StartedUsingFont, + StoppedUsingFont + }; + + QWSFontCommand() : + QWSCommand(QWSCommand::Font, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem) { + QWSCommand::setData(d, len, allocateMem); + + fontName = QByteArray(d, len); + } + + void setFontName(const QByteArray &name) + { + setData(name.constData(), name.size(), true); + } + + struct SimpleData { + int type; + } simpleData; + + QByteArray fontName; +}; + +struct QWSScreenTransformCommand : public QWSCommand +{ + QWSScreenTransformCommand() : + QWSCommand(QWSCommand::ScreenTransform, + sizeof(simpleData), reinterpret_cast(&simpleData)) {} + + void setTransformation(int screen, int transformation) + { + simpleData.screen = screen; + simpleData.transformation = transformation; + } + + struct SimpleData { + int screen; + int transformation; + } simpleData; +}; + +QT_END_NAMESPACE + +#endif // QWSCOMMAND_QWS_P_H diff --git a/src/gui/embedded/qwscursor_qws.cpp b/src/gui/embedded/qwscursor_qws.cpp new file mode 100644 index 0000000000..0313e00391 --- /dev/null +++ b/src/gui/embedded/qwscursor_qws.cpp @@ -0,0 +1,654 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcursor.h" +#include "qbitmap.h" +#include "qscreen_qws.h" +#include "qapplication.h" +#include "qwindowsystem_qws.h" +#include "qwindowsystem_p.h" +#include "qwscursor_qws.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_CURSOR +static QWSCursor *systemCursorTable[Qt::LastCursor+1]; +static bool systemCursorTableInit = false; + +// 16 x 16 +static const uchar cur_arrow_bits[] = { + 0x07, 0x00, 0x39, 0x00, 0xc1, 0x01, 0x02, 0x0e, 0x02, 0x10, 0x02, 0x08, + 0x04, 0x04, 0x04, 0x02, 0x04, 0x04, 0x88, 0x08, 0x48, 0x11, 0x28, 0x22, + 0x10, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00 }; +static const uchar mcur_arrow_bits[] = { + 0x07, 0x00, 0x3f, 0x00, 0xff, 0x01, 0xfe, 0x0f, 0xfe, 0x1f, 0xfe, 0x0f, + 0xfc, 0x07, 0xfc, 0x03, 0xfc, 0x07, 0xf8, 0x0f, 0x78, 0x1f, 0x38, 0x3e, + 0x10, 0x7c, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00 }; + +static const unsigned char cur_up_arrow_bits[] = { + 0x80, 0x00, 0x40, 0x01, 0x40, 0x01, 0x20, 0x02, 0x20, 0x02, 0x10, 0x04, + 0x10, 0x04, 0x08, 0x08, 0x78, 0x0f, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0, 0x01}; +static const unsigned char mcur_up_arrow_bits[] = { + 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01, 0xe0, 0x03, 0xe0, 0x03, 0xf0, 0x07, + 0xf0, 0x07, 0xf8, 0x0f, 0xf8, 0x0f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01}; + +static const unsigned char cur_cross_bits[] = { + 0xc0, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x7f, 0x7f, 0x01, 0x40, 0x7f, 0x7f, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x40, 0x01, 0x40, 0x01, 0xc0, 0x01, 0x00, 0x00}; +static const unsigned char mcur_cross_bits[] = { + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; + +static const uchar cur_ibeam_bits[] = { + 0x00, 0x00, 0xe0, 0x03, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_ibeam_bits[] = { + 0xf0, 0x07, 0xf0, 0x07, 0xf0, 0x07, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xf0, 0x07, 0xf0, 0x07, 0xf0, 0x07, 0x00, 0x00 }; + +static const uchar cur_ver_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0xc0, 0x03, 0xe0, 0x07, 0xf0, 0x0f, + 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xf0, 0x0f, + 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00 }; +static const uchar mcur_ver_bits[] = { + 0x00, 0x00, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, + 0xfc, 0x7f, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xfc, 0x7f, 0xf8, 0x3f, + 0xf0, 0x1f, 0xe0, 0x0f, 0xc0, 0x07, 0x80, 0x03 }; + +static const uchar cur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0x30, 0x18, + 0x38, 0x38, 0xfc, 0x7f, 0xfc, 0x7f, 0x38, 0x38, 0x30, 0x18, 0x20, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0x60, 0x0c, 0x70, 0x1c, 0x78, 0x3c, + 0xfc, 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfc, 0x7f, 0x78, 0x3c, + 0x70, 0x1c, 0x60, 0x0c, 0x40, 0x04, 0x00, 0x00 }; +static const uchar cur_bdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, + 0x00, 0x37, 0x88, 0x23, 0xd8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0xf8, 0x00, + 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_bdiag_bits[] = { + 0x00, 0x00, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7f, 0x00, 0x7e, 0x04, 0x7f, + 0x8c, 0x7f, 0xdc, 0x77, 0xfc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, + 0xfc, 0x03, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00 }; +static const uchar cur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, + 0xf8, 0x00, 0xd8, 0x01, 0x88, 0x23, 0x00, 0x37, 0x00, 0x3e, 0x00, 0x3c, + 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, + 0xfc, 0x41, 0xfc, 0x63, 0xdc, 0x77, 0x8c, 0x7f, 0x04, 0x7f, 0x00, 0x7e, + 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x7f, 0x00, 0x00 }; +static const uchar cur_blank_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +// 20 x 20 +static const uchar forbidden_bits[] = { + 0x00,0x00,0x00,0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xf0,0x00,0x38,0xc0,0x01, + 0x7c,0x80,0x03,0xec,0x00,0x03,0xce,0x01,0x07,0x86,0x03,0x06,0x06,0x07,0x06, + 0x06,0x0e,0x06,0x06,0x1c,0x06,0x0e,0x38,0x07,0x0c,0x70,0x03,0x1c,0xe0,0x03, + 0x38,0xc0,0x01,0xf0,0xe0,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00,0x00,0x00,0x00 }; + +static const uchar forbiddenm_bits[] = { + 0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xff,0x00,0xf8,0xff,0x01,0xfc,0xf0,0x03, + 0xfe,0xc0,0x07,0xfe,0x81,0x07,0xff,0x83,0x0f,0xcf,0x07,0x0f,0x8f,0x0f,0x0f, + 0x0f,0x1f,0x0f,0x0f,0x3e,0x0f,0x1f,0xfc,0x0f,0x1e,0xf8,0x07,0x3e,0xf0,0x07, + 0xfc,0xe0,0x03,0xf8,0xff,0x01,0xf0,0xff,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00}; + +// 32 x 32 +static const uchar wait_data_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0x04, 0x40, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x08, 0x20, 0x00, + 0x00, 0x08, 0x20, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x08, 0x20, 0x00, + 0x00, 0x50, 0x15, 0x00, 0x00, 0xa0, 0x0a, 0x00, 0x00, 0x40, 0x05, 0x00, + 0x00, 0x80, 0x02, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, + 0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x21, 0x00, 0x00, 0x88, 0x22, 0x00, + 0x00, 0x48, 0x25, 0x00, 0x00, 0xa8, 0x2a, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0x04, 0x40, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar wait_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xc0, 0x07, 0x00, + 0x00, 0x80, 0x03, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0xe0, 0x0f, 0x00, + 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar hsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03, + 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar hsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00, + 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07, + 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00, + 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar vsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar vsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar phand_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, + 0x7e, 0x04, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x70, 0x08, 0x00, 0x00, + 0x08, 0x08, 0x00, 0x00, 0x70, 0x14, 0x00, 0x00, 0x08, 0x22, 0x00, 0x00, + 0x30, 0x41, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x40, 0x12, 0x00, 0x00, + 0x80, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar phandm_bits[] = { + 0xfe, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x1f, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, + 0xfc, 0x1f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, + 0xf8, 0xff, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, + 0xc0, 0x1f, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar size_all_data_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x81, 0x40, 0x00, 0x80, 0x81, 0xc0, 0x00, + 0xc0, 0xff, 0xff, 0x01, 0x80, 0x81, 0xc0, 0x00, 0x00, 0x81, 0x40, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar size_all_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc2, 0x21, 0x00, + 0x00, 0xc3, 0x61, 0x00, 0x80, 0xc3, 0xe1, 0x00, 0xc0, 0xff, 0xff, 0x01, + 0xe0, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0x01, 0x80, 0xc3, 0xe1, 0x00, + 0x00, 0xc3, 0x61, 0x00, 0x00, 0xc2, 0x21, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar whatsthis_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0xf0, 0x07, 0x00, + 0x09, 0x18, 0x0e, 0x00, 0x11, 0x1c, 0x0e, 0x00, 0x21, 0x1c, 0x0e, 0x00, + 0x41, 0x1c, 0x0e, 0x00, 0x81, 0x1c, 0x0e, 0x00, 0x01, 0x01, 0x07, 0x00, + 0x01, 0x82, 0x03, 0x00, 0xc1, 0xc7, 0x01, 0x00, 0x49, 0xc0, 0x01, 0x00, + 0x95, 0xc0, 0x01, 0x00, 0x93, 0xc0, 0x01, 0x00, 0x21, 0x01, 0x00, 0x00, + 0x20, 0xc1, 0x01, 0x00, 0x40, 0xc2, 0x01, 0x00, 0x40, 0x02, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static const uchar whatsthism_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0x00, 0x07, 0xf8, 0x0f, 0x00, + 0x0f, 0xfc, 0x1f, 0x00, 0x1f, 0x3e, 0x1f, 0x00, 0x3f, 0x3e, 0x1f, 0x00, + 0x7f, 0x3e, 0x1f, 0x00, 0xff, 0x3e, 0x1f, 0x00, 0xff, 0x9d, 0x0f, 0x00, + 0xff, 0xc3, 0x07, 0x00, 0xff, 0xe7, 0x03, 0x00, 0x7f, 0xe0, 0x03, 0x00, + 0xf7, 0xe0, 0x03, 0x00, 0xf3, 0xe0, 0x03, 0x00, 0xe1, 0xe1, 0x03, 0x00, + 0xe0, 0xe1, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + +static const uchar busy_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x41, 0xe0, 0xff, 0x00, 0x81, 0x20, 0x80, 0x00, 0x01, 0xe1, 0xff, 0x00, + 0x01, 0x42, 0x40, 0x00, 0xc1, 0x47, 0x40, 0x00, 0x49, 0x40, 0x55, 0x00, + 0x95, 0x80, 0x2a, 0x00, 0x93, 0x00, 0x15, 0x00, 0x21, 0x01, 0x0a, 0x00, + 0x20, 0x01, 0x11, 0x00, 0x40, 0x82, 0x20, 0x00, 0x40, 0x42, 0x44, 0x00, + 0x80, 0x41, 0x4a, 0x00, 0x00, 0x40, 0x55, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0x20, 0x80, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uchar busym_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x7f, 0xe0, 0xff, 0x00, 0xff, 0xe0, 0xff, 0x00, 0xff, 0xe1, 0xff, 0x00, + 0xff, 0xc3, 0x7f, 0x00, 0xff, 0xc7, 0x7f, 0x00, 0x7f, 0xc0, 0x7f, 0x00, + 0xf7, 0x80, 0x3f, 0x00, 0xf3, 0x00, 0x1f, 0x00, 0xe1, 0x01, 0x0e, 0x00, + 0xe0, 0x01, 0x1f, 0x00, 0xc0, 0x83, 0x3f, 0x00, 0xc0, 0xc3, 0x7f, 0x00, + 0x80, 0xc1, 0x7f, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0xe0, 0xff, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// 16 x 16 +static const uchar openhand_bits[] = { + 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92, + 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20, + 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00}; +static const uchar openhandm_bits[] = { + 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff, + 0xfe,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f, + 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00}; +static const uchar closedhand_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50, + 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10, + 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}; +static const uchar closedhandm_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f, + 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f, + 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00}; + +#endif + +void QWSServerPrivate::initializeCursor() +{ + Q_Q(QWSServer); + // setup system cursors +#ifndef QT_NO_QWS_CURSOR +// qt_screen->initCursor(sharedram + ramlen,true); + + // default cursor + cursor = 0; + setCursor(QWSCursor::systemCursor(Qt::ArrowCursor)); +#endif + q->sendMouseEvent(QPoint(swidth/2, sheight/2), 0); +} + +void QWSServerPrivate::setCursor(QWSCursor *curs) +{ +#ifdef QT_NO_QWS_CURSOR + Q_UNUSED(curs); +#else + if (cursor == curs) + return; + + cursor = curs; + + if (!haveviscurs || !curs) + curs = QWSCursor::systemCursor(Qt::BlankCursor); + + if (qt_screencursor) { + qt_screencursor->set(curs->image(), + curs->hotSpot().x(), + curs->hotSpot().y()); + } +#endif +} + +#ifndef QT_NO_QWS_CURSOR +static void cleanupSystemCursorTable() +{ + for (int i = 0; i <= Qt::LastCursor; i++) + if (systemCursorTable[i]) { + delete systemCursorTable[i]; + systemCursorTable[i] = 0; + } +} +#endif + +void QWSCursor::createSystemCursor(int id) +{ +#ifdef QT_NO_QWS_CURSOR + Q_UNUSED(id); +#else + if (!systemCursorTableInit) { + for (int i = 0; i <= Qt::LastCursor; i++) + systemCursorTable[i] = 0; + qAddPostRoutine(cleanupSystemCursorTable); + systemCursorTableInit = true; + } + switch (id) { + // 16x16 cursors + case Qt::ArrowCursor: + systemCursorTable[Qt::ArrowCursor] = + new QWSCursor(cur_arrow_bits, mcur_arrow_bits, 16, 16, 0, 0); + break; + + case Qt::UpArrowCursor: + systemCursorTable[Qt::UpArrowCursor] = + new QWSCursor(cur_up_arrow_bits, mcur_up_arrow_bits, 16, 16, 7, 0); + break; + + case Qt::CrossCursor: + systemCursorTable[Qt::CrossCursor] = + new QWSCursor(cur_cross_bits, mcur_cross_bits, 16, 16, 7, 7); + break; + + case Qt::IBeamCursor: + systemCursorTable[Qt::IBeamCursor] = + new QWSCursor(cur_ibeam_bits, mcur_ibeam_bits, 16, 16, 7, 7); + break; + + case Qt::SizeVerCursor: + systemCursorTable[Qt::SizeVerCursor] = + new QWSCursor(cur_ver_bits, mcur_ver_bits, 16, 16, 7, 7); + break; + + case Qt::SizeHorCursor: + systemCursorTable[Qt::SizeHorCursor] = + new QWSCursor(cur_hor_bits, mcur_hor_bits, 16, 16, 7, 7); + break; + + case Qt::SizeBDiagCursor: + systemCursorTable[Qt::SizeBDiagCursor] = + new QWSCursor(cur_bdiag_bits, mcur_bdiag_bits, 16, 16, 7, 7); + break; + + case Qt::SizeFDiagCursor: + systemCursorTable[Qt::SizeFDiagCursor] = + new QWSCursor(cur_fdiag_bits, mcur_fdiag_bits, 16, 16, 7, 7); + break; + + case Qt::BlankCursor: + systemCursorTable[Qt::BlankCursor] = + new QWSCursor(0, 0, 0, 0, 0, 0); + break; + + // 20x20 cursors + case Qt::ForbiddenCursor: + systemCursorTable[Qt::ForbiddenCursor] = + new QWSCursor(forbidden_bits, forbiddenm_bits, 20, 20, 10, 10); + break; + + // 32x32 cursors + case Qt::WaitCursor: + systemCursorTable[Qt::WaitCursor] = + new QWSCursor(wait_data_bits, wait_mask_bits, 32, 32, 15, 15); + break; + + case Qt::SplitVCursor: + systemCursorTable[Qt::SplitVCursor] = + new QWSCursor(vsplit_bits, vsplitm_bits, 32, 32, 15, 15); + break; + + case Qt::SplitHCursor: + systemCursorTable[Qt::SplitHCursor] = + new QWSCursor(hsplit_bits, hsplitm_bits, 32, 32, 15, 15); + break; + + case Qt::SizeAllCursor: + systemCursorTable[Qt::SizeAllCursor] = + new QWSCursor(size_all_data_bits, size_all_mask_bits, 32, 32, 15, 15); + break; + + case Qt::PointingHandCursor: + systemCursorTable[Qt::PointingHandCursor] = + new QWSCursor(phand_bits, phandm_bits, 32, 32, 0, 0); + break; + + case Qt::WhatsThisCursor: + systemCursorTable[Qt::WhatsThisCursor] = + new QWSCursor(whatsthis_bits, whatsthism_bits, 32, 32, 0, 0); + break; + case Qt::BusyCursor: + systemCursorTable[Qt::BusyCursor] = + new QWSCursor(busy_bits, busym_bits, 32, 32, 0, 0); + break; + + case Qt::OpenHandCursor: + systemCursorTable[Qt::OpenHandCursor] = + new QWSCursor(openhand_bits, openhandm_bits, 16, 16, 8, 8); + break; + case Qt::ClosedHandCursor: + systemCursorTable[Qt::ClosedHandCursor] = + new QWSCursor(closedhand_bits, closedhandm_bits, 16, 16, 8, 8); + break; + default: + qWarning("Unknown system cursor %d", id); + } +#endif +} + +QWSCursor *QWSCursor::systemCursor(int id) +{ + QWSCursor *cursor = 0; +#ifdef QT_NO_QWS_CURSOR + Q_UNUSED(id); +#else + if (id >= 0 && id <= Qt::LastCursor) { + if (!systemCursorTable[id]) + createSystemCursor(id); + cursor = systemCursorTable[id]; + } + + if (cursor == 0) { + if (!systemCursorTable[Qt::ArrowCursor]) + createSystemCursor(Qt::ArrowCursor); + cursor = systemCursorTable[Qt::ArrowCursor]; + } +#endif + return cursor; +} + +void QWSCursor::set(const uchar *data, const uchar *mask, + int width, int height, int hx, int hy) +{ +#ifdef QT_NO_QWS_CURSOR + Q_UNUSED(data); + Q_UNUSED(mask); + Q_UNUSED(width); + Q_UNUSED(height); + Q_UNUSED(hx); + Q_UNUSED(hy); +#else + hot.setX(hx); + hot.setY(hy); + + cursor = QImage(width,height, QImage::Format_Indexed8); + + if (!width || !height || !data || !mask || cursor.isNull()) + return; + + cursor.setColorCount(3); + cursor.setColor(0, 0xff000000); + cursor.setColor(1, 0xffffffff); + cursor.setColor(2, 0x00000000); + + int bytesPerLine = (width + 7) / 8; + int p = 0; + int d, m; + + int x = -1, w = 0; + + uchar *cursor_data = cursor.bits(); + int bpl = cursor.bytesPerLine(); + for (int i = 0; i < height; i++) + { + for (int j = 0; j < bytesPerLine; j++, data++, mask++) + { + for (int b = 0; b < 8 && j*8+b < width; b++) + { + d = *data & (1 << b); + m = *mask & (1 << b); + if (d && m) p = 0; + else if (!d && m) p = 1; + else p = 2; + cursor_data[j*8+b] = p; + + // calc region + if (x < 0 && m) + x = j*8+b; + else if (x >= 0 && !m) { + x = -1; + w = 0; + } + if (m) + w++; + } + } + if (x >= 0) { + x = -1; + w = 0; + } + cursor_data += bpl; + } + + if (qt_screencursor && qt_screencursor->supportsAlphaCursor()) + createDropShadow(5, 2); +#endif +} + +// now we're really silly +void QWSCursor::createDropShadow(int dropx, int dropy) +{ + //#### +#if 1 || defined(QT_NO_QWS_CURSOR) || defined(QT_NO_QWS_ALHPA_CURSOR) + Q_UNUSED(dropx); + Q_UNUSED(dropy); +#else + if (cursor.width() + dropx > 64 || cursor.height() + dropy > 64) + return; + + if (!cursor.hasAlphaBuffer()) { + cursor.setAlphaBuffer(true); + + const int nblur=4; + const int darkness=140; + + QImage drop(cursor.width()+dropx+nblur, cursor.height()+dropy+nblur, 8, 18); + drop.setColor(0, 0xff000000); // bg (black) + drop.setColor(1, 0xffffffff); // fg (white) + for (int i=0; i<16; i++) { + drop.setColor(2+i, (darkness*i/16)<<24); + } + drop.fill(2); // all trans + QImage drop2 = drop.copy(); + + int cp; + + // made solid shadow + for (int row = 0; row < cursor.height(); row++) { + for (int col = 0; col < cursor.width(); col++) { + cp = cursor.pixelIndex(col, row); + if (cp != 2) + drop.setPixel(col+dropx, row+dropy, 17); + } + } + + // blur shadow + for (int blur=0; blur +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWSCursor +{ +public: + QWSCursor() {} + QWSCursor(const uchar *data, const uchar *mask, int width, int height, + int hotX, int hotY) + { set(data, mask, width, height, hotX, hotY); } + + void set(const uchar *data, const uchar *mask, + int width, int height, int hotX, int hotY); + + QPoint hotSpot() const { return hot; } + QImage &image() { return cursor; } + + static QWSCursor *systemCursor(int id); + +private: + static void createSystemCursor(int id); + void createDropShadow(int dropx, int dropy); + +private: + QPoint hot; + QImage cursor; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSCURSOR_QWS_H diff --git a/src/gui/embedded/qwsdisplay_qws.h b/src/gui/embedded/qwsdisplay_qws.h new file mode 100644 index 0000000000..81acb425c9 --- /dev/null +++ b/src/gui/embedded/qwsdisplay_qws.h @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSDISPLAY_QWS_H +#define QWSDISPLAY_QWS_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWSEvent; +class QWSMouseEvent; +class QWSQCopMessageEvent; +class QVariant; +class QLock; + +class QWSWindowInfo +{ + +public: + + int winid; + unsigned int clientid; + QString name; + +}; + +#define QT_QWS_PROPERTY_CONVERTSELECTION 999 +#define QT_QWS_PROPERTY_WINDOWNAME 998 +#define QT_QWS_PROPERTY_MARKEDTEXT 997 + +class QWSDisplay; +extern Q_GUI_EXPORT QWSDisplay *qt_fbdpy; + +class Q_GUI_EXPORT QWSDisplay +{ +public: + QWSDisplay(); + ~QWSDisplay(); + + static QWSDisplay* instance() { return qt_fbdpy; } + + bool eventPending() const; + QWSEvent *getEvent(); +// QWSRegionManager *regionManager() const; + + uchar* frameBuffer() const; + int width() const; + int height() const; + int depth() const; + int pixmapDepth() const; + bool supportsDepth(int) const; + + uchar *sharedRam() const; + int sharedRamSize() const; + +#ifndef QT_NO_QWS_PROPERTIES + void addProperty(int winId, int property); + void setProperty(int winId, int property, int mode, const QByteArray &data); + void setProperty(int winId, int property, int mode, const char * data); + void removeProperty(int winId, int property); + bool getProperty(int winId, int property, char *&data, int &len); +#endif // QT_NO_QWS_PROPERTIES + + QList windowList(); + int windowAt(const QPoint &); + + void setIdentity(const QString &appName); + void nameRegion(int winId, const QString& n, const QString &c); + void requestRegion(int winId, const QString &surfacekey, + const QByteArray &surfaceData, + const QRegion ®ion); + void repaintRegion(int winId, int windowFlags, bool opaque, QRegion); + void moveRegion(int winId, int dx, int dy); + void destroyRegion(int winId); + void requestFocus(int winId, bool get); + void setAltitude(int winId, int altitude, bool fixed = false); + void setOpacity(int winId, int opacity); + int takeId(); + void setSelectionOwner(int winId, const QTime &time); + void convertSelection(int winId, int selectionProperty, const QString &mimeTypes); + void defineCursor(int id, const QBitmap &curs, const QBitmap &mask, + int hotX, int hotY); + void destroyCursor(int id); + void selectCursor(QWidget *w, unsigned int id); + void setCursorPosition(int x, int y); + void grabMouse(QWidget *w, bool grab); + void grabKeyboard(QWidget *w, bool grab); + void playSoundFile(const QString&); + void registerChannel(const QString &channel); + void sendMessage(const QString &channel, const QString &msg, + const QByteArray &data); + void flushCommands(); +#ifndef QT_NO_QWS_INPUTMETHODS + void sendIMUpdate(int type, int winId, int widgetid); + void resetIM(); + void sendIMResponse(int winId, int property, const QVariant &result); + void sendIMMouseEvent(int index, bool isPress); +#endif + QWSQCopMessageEvent* waitForQCopResponse(); + void sendFontCommand(int type, const QByteArray &fontName); + + void setWindowCaption(QWidget *w, const QString &); + + // Lock display for access only by this process + static bool initLock(const QString &filename, bool create = false); + static bool grabbed(); + static void grab(); + static void grab(bool write); + static void ungrab(); + + static void setTransformation(int transformation, int screenNo = -1); + static void setRawMouseEventFilter(void (*filter)(QWSMouseEvent *)); + +private: + friend int qt_fork_qapplication(); + friend void qt_app_reinit( const QString& newAppName ); + friend class QApplication; + friend class QCopChannel; + friend class QWSEmbedWidget; + friend class QWSEmbedWidgetPrivate; + class Data; + friend class Data; + Data *d; + + friend class QWSMemorySurface; + friend class QWSOnScreenSurface; + friend class QWSDirectPainterSurface; + int getPropertyLen; + char *getPropertyData; + static QLock *lock; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSDISPLAY_QWS_H diff --git a/src/gui/embedded/qwsdisplay_qws_p.h b/src/gui/embedded/qwsdisplay_qws_p.h new file mode 100644 index 0000000000..a217a2af7c --- /dev/null +++ b/src/gui/embedded/qwsdisplay_qws_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSDISPLAY_QWS_P_H +#define QWSDISPLAY_QWS_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 "qwsdisplay_qws.h" +#include "qwssocket_qws.h" +#include "qwsevent_qws.h" +#include +#include "qwscommand_qws_p.h" +#include "qwslock_p.h" + +QT_BEGIN_NAMESPACE + +class QWSDisplay::Data +{ +public: + Data(QObject* parent, bool singleProcess = false); + ~Data(); + + void flush(); + + bool queueNotEmpty(); + QWSEvent *dequeue(); + QWSEvent *peek(); + + bool directServerConnection(); + void fillQueue(); +#ifndef QT_NO_QWS_MULTIPROCESS + void connectToPipe(); + void waitForConnection(); + void waitForPropertyReply(); + void waitForRegionAck(int winId); + void waitForRegionEvents(int winId, bool ungrabDisplay); + bool hasPendingRegionEvents() const; +#endif + void waitForCreation(); +#ifndef QT_NO_COP + void waitForQCopResponse(); +#endif + void init(); + void reinit( const QString& newAppName ); + void create(int n = 1); + + void flushCommands(); + void sendCommand(QWSCommand & cmd); + void sendSynchronousCommand(QWSCommand & cmd); + + QWSEvent *readMore(); + + int takeId(); + + void setMouseFilter(void (*filter)(QWSMouseEvent*)); + + //####public data members + +// QWSRegionManager *rgnMan; + uchar *sharedRam; +#ifndef QT_NO_QWS_MULTIPROCESS + QWSSharedMemory shm; +#endif + int sharedRamSize; + +#ifndef QT_NO_QWS_MULTIPROCESS + static QWSLock *clientLock; + + static bool lockClient(QWSLock::LockType, int timeout = -1); + static void unlockClient(QWSLock::LockType); + static bool waitClient(QWSLock::LockType, int timeout = -1); + static QWSLock* getClientLock(); +#endif // QT_NO_QWS_MULTIPROCESS + +private: +#ifndef QT_NO_QWS_MULTIPROCESS + QWSSocket *csocket; +#endif + QList queue; + +#if 0 + void debugQueue() { + for (int i = 0; i < queue.size(); ++i) { + QWSEvent *e = queue.at(i); + qDebug( " ev %d type %d sl %d rl %d", i, e->type, e->simpleLen, e->rawLen); + } + } +#endif + + QWSConnectedEvent* connected_event; + QWSMouseEvent* mouse_event; + int region_events_count; + int mouse_state; + int mouse_winid; + QPoint region_offset; + int region_offset_window; +#ifndef QT_NO_COP + QWSQCopMessageEvent *qcop_response; +#endif + QWSEvent* current_event; + QList unused_identifiers; +#ifdef QAPPLICATION_EXTRA_DEBUG + int mouse_event_count; +#endif + void (*mouseFilter)(QWSMouseEvent *); + + enum { VariableEvent=-1 }; + +}; + +QT_END_NAMESPACE + +#endif // QWSDISPLAY_QWS_P_H diff --git a/src/gui/embedded/qwsembedwidget.cpp b/src/gui/embedded/qwsembedwidget.cpp new file mode 100644 index 0000000000..9005ea4400 --- /dev/null +++ b/src/gui/embedded/qwsembedwidget.cpp @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwsembedwidget.h" + +#ifndef QT_NO_QWSEMBEDWIDGET + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// TODO: +// Must remove window decorations from the embedded window +// Focus In/Out, Keyboard/Mouse... +// +// BUG: what if my parent change parent? + +class QWSEmbedWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QWSEmbedWidget); + +public: + QWSEmbedWidgetPrivate(int winId); + void updateWindow(); + void resize(const QSize &size); + + QWidget *window; + WId windowId; + WId embeddedId; +}; + +QWSEmbedWidgetPrivate::QWSEmbedWidgetPrivate(int winId) + : window(0), windowId(0), embeddedId(winId) +{ +} + +void QWSEmbedWidgetPrivate::updateWindow() +{ + Q_Q(QWSEmbedWidget); + + QWidget *win = q->window(); + if (win == window) + return; + + if (window) { + window->removeEventFilter(q); + QWSEmbedCommand command; + command.setData(windowId, embeddedId, QWSEmbedEvent::StopEmbed); + QWSDisplay::instance()->d->sendCommand(command); + } + + window = win; + if (!window) + return; + windowId = window->winId(); + + QWSEmbedCommand command; + command.setData(windowId, embeddedId, QWSEmbedEvent::StartEmbed); + QWSDisplay::instance()->d->sendCommand(command); + window->installEventFilter(q); + q->installEventFilter(q); +} + +void QWSEmbedWidgetPrivate::resize(const QSize &size) +{ + if (!window) + return; + + Q_Q(QWSEmbedWidget); + + QWSEmbedCommand command; + command.setData(windowId, embeddedId, QWSEmbedEvent::Region, + QRect(q->mapToGlobal(QPoint(0, 0)), size)); + QWSDisplay::instance()->d->sendCommand(command); +} + +/*! + \class QWSEmbedWidget + \since 4.2 + \ingroup qws + \ingroup advanced + + \brief The QWSEmbedWidget class enables embedded top-level widgets + in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + QWSEmbedWidget inherits QWidget and acts as any other widget, but + in addition it is capable of embedding another top-level widget. + + An example of use is when painting directly onto the screen using + the QDirectPainter class. Then the reserved region can be embedded + into an instance of the QWSEmbedWidget class, providing for + example event handling and size policies for the reserved region. + + All that is required to embed a top-level widget is its window ID. + + \sa {Qt for Embedded Linux Architecture} +*/ + +/*! + Constructs a widget with the given \a parent, embedding the widget + identified by the given window \a id. +*/ +QWSEmbedWidget::QWSEmbedWidget(WId id, QWidget *parent) + : QWidget(*new QWSEmbedWidgetPrivate(id), parent, 0) +{ + Q_D(QWSEmbedWidget); + d->updateWindow(); +} + +/*! + Destroys this widget. +*/ +QWSEmbedWidget::~QWSEmbedWidget() +{ + Q_D(QWSEmbedWidget); + if (!d->window) + return; + + QWSEmbedCommand command; + command.setData(d->windowId, d->embeddedId, QWSEmbedEvent::StopEmbed); + QWSDisplay::instance()->d->sendCommand(command); +} + +/*! + \reimp +*/ +bool QWSEmbedWidget::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QWSEmbedWidget); + if (object == d->window && event->type() == QEvent::Move) + resizeEvent(0); + else if (object == this && event->type() == QEvent::Hide) + d->resize(QSize()); + return QWidget::eventFilter(object, event); +} + +/*! + \reimp +*/ +void QWSEmbedWidget::changeEvent(QEvent *event) +{ + Q_D(QWSEmbedWidget); + if (event->type() == QEvent::ParentChange) + d->updateWindow(); +} + +/*! + \reimp +*/ +void QWSEmbedWidget::resizeEvent(QResizeEvent*) +{ + Q_D(QWSEmbedWidget); + d->resize(rect().size()); +} + +/*! + \reimp +*/ +void QWSEmbedWidget::moveEvent(QMoveEvent*) +{ + resizeEvent(0); +} + +/*! + \reimp +*/ +void QWSEmbedWidget::hideEvent(QHideEvent*) +{ + Q_D(QWSEmbedWidget); + d->resize(QSize()); +} + +/*! + \reimp +*/ +void QWSEmbedWidget::showEvent(QShowEvent*) +{ + Q_D(QWSEmbedWidget); + d->resize(rect().size()); +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWSEMBEDWIDGET diff --git a/src/gui/embedded/qwsembedwidget.h b/src/gui/embedded/qwsembedwidget.h new file mode 100644 index 0000000000..50237a6963 --- /dev/null +++ b/src/gui/embedded/qwsembedwidget.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSEMBEDWIDGET_H +#define QWSEMBEDWIDGET_H + +#include + +#ifndef QT_NO_QWSEMBEDWIDGET + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWSEmbedWidgetPrivate; + +class Q_GUI_EXPORT QWSEmbedWidget : public QWidget +{ + Q_OBJECT + +public: + QWSEmbedWidget(WId winId, QWidget *parent = 0); + ~QWSEmbedWidget(); + +protected: + bool eventFilter(QObject *object, QEvent *event); + void changeEvent(QEvent *event); + void resizeEvent(QResizeEvent *event); + void moveEvent(QMoveEvent *event); + void hideEvent(QHideEvent *event); + void showEvent(QShowEvent *event); + +private: + Q_DECLARE_PRIVATE(QWSEmbedWidget) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_QWSEMBEDWIDGET +#endif // QWSEMBEDWIDGET_H diff --git a/src/gui/embedded/qwsevent_qws.cpp b/src/gui/embedded/qwsevent_qws.cpp new file mode 100644 index 0000000000..bcecafed40 --- /dev/null +++ b/src/gui/embedded/qwsevent_qws.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwsevent_qws.h" + +QT_BEGIN_NAMESPACE + +QWSEvent *QWSEvent::factory(int type) +{ + QWSEvent *event = 0; + switch (type) { + case QWSEvent::Connected: + event = new QWSConnectedEvent; + break; + case QWSEvent::MaxWindowRect: + event = new QWSMaxWindowRectEvent; + break; + case QWSEvent::Mouse: + event = new QWSMouseEvent; + break; + case QWSEvent::Focus: + event = new QWSFocusEvent; + break; + case QWSEvent::Key: + event = new QWSKeyEvent; + break; + case QWSEvent::Region: + event = new QWSRegionEvent; + break; + case QWSEvent::Creation: + event = new QWSCreationEvent; + break; +#ifndef QT_NO_QWS_PROPERTIES + case QWSEvent::PropertyNotify: + event = new QWSPropertyNotifyEvent; + break; + case QWSEvent::PropertyReply: + event = new QWSPropertyReplyEvent; + break; +#endif // QT_NO_QWS_PROPERTIES + case QWSEvent::SelectionClear: + event = new QWSSelectionClearEvent; + break; + case QWSEvent::SelectionRequest: + event = new QWSSelectionRequestEvent; + break; + case QWSEvent::SelectionNotify: + event = new QWSSelectionNotifyEvent; + break; +#ifndef QT_NO_COP + case QWSEvent::QCopMessage: + event = new QWSQCopMessageEvent; + break; +#endif + case QWSEvent::WindowOperation: + event = new QWSWindowOperationEvent; + break; + +#ifndef QT_NO_QWS_INPUTMETHODS + case QWSEvent::IMEvent: + event = new QWSIMEvent; + break; + case QWSEvent::IMQuery: + event = new QWSIMQueryEvent; + break; + case QWSEvent::IMInit: + event = new QWSIMInitEvent; + break; +#endif +#ifndef QT_NO_QWSEMBEDWIDGET + case QWSEvent::Embed: + event = new QWSEmbedEvent; + break; +#endif + case QWSEvent::Font: + event = new QWSFontEvent; + break; + case QWSEvent::ScreenTransformation: + event = new QWSScreenTransformationEvent; + break; + default: + qCritical("QWSEvent::factory() : Unknown event type %08x!", type); + } + return event; +} + +/*! + \class QWSEvent + \ingroup qws + + \brief The QWSEvent class encapsulates an event in Qt for Embedded Linux. + + When running a \l{Qt for Embedded Linux} application, it either runs as a + server or connects to an existing server. All system generated + events are passed to the server application which then propagates + the event to the appropriate client. + + Whenever the server receives an event, it queries its stack of + top-level windows to find the window containing the event's + position. Each window can identify the client application that + created it, and returns its ID to the server upon + request. Finally, the server forwards the event, encapsulated by + an instance of the QWSEvent class, to the appropriate client. + + \image qt-embedded-client.png + + The server communicates with the client applications over the UNIX + domain socket. You can retrieve direct access to all the events a + client receives from the server, by reimplementing QApplication's + \l {QApplication::}{qwsEventFilter()} function. + + QWSEvent provides the \l Type enum specifying the origin of the + event. Internally, each type is represented by a QWSEvent + subclass, e.g., \c QWSKeyEvent. + + \sa QWSServer, QWSClient, {Qt for Embedded Linux Architecture} +*/ + +/*! + \enum QWSEvent::Type + + This enum describes the origin of the event. + + \value NoEvent No event has occurred. + \value Connected An application has connected to the server. + \value Mouse A mouse button is pressed or released, or the mouse cursor is moved. + See also \l{Qt for Embedded Linux Pointer Handling}. + \value Focus A window has lost or received focus. + \value Key A key is pressed or released. See also \l{Qt for Embedded Linux Character Input}. + \value Region A region has changed. + \value Creation The server has created an ID, typically for a window. + \value PropertyNotify A property has changed. + \value PropertyReply The server is responding to a request for a property's value. + \value SelectionClear A selection is deleted. + \value SelectionRequest The server has queried for a selection. + \value SelectionNotify A new selection has been created. + \value MaxWindowRect The server has changed the maximum window for an application. + \value QCopMessage A new Qt Cop message has appeared. See also QCopChannel + \value WindowOperation A window operation, e.g. resizing, has occurred. + \value IMEvent An input method has been used to enter text for languages with + non-Latin alphabets. See also QWSInputMethod. + \value IMQuery An input method query for a specified property has occurred. + See also QWSInputMethod. + \value NEvent The number of events has changed. + \value Embed An event used internally to implement embedded windows. See also + QWSEmbedWidget. + \value ScreenTransformation An event used internally to notify the client processes + that the screen has changed for example, rotation, etc. + \omitvalue Font + \omitvalue IMInit +*/ + +/*! + \fn QWSMouseEvent *QWSEvent::asMouse() + \internal +*/ + +/*! + \fn int QWSEvent::window() + \internal +*/ + +/*! + \fn int QWSEvent::window() const + \internal +*/ + +/*! + \fn QWSEvent *QWSEvent::factory(int type) + \internal +*/ + +/*! + \fn QWSEvent::QWSEvent( int t, int len, char * ptr) + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/gui/embedded/qwsevent_qws.h b/src/gui/embedded/qwsevent_qws.h new file mode 100644 index 0000000000..f439ee5843 --- /dev/null +++ b/src/gui/embedded/qwsevent_qws.h @@ -0,0 +1,459 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSEVENT_QWS_H +#define QWSEVENT_QWS_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +struct QWSMouseEvent; + +struct QWSEvent : QWSProtocolItem { + + QWSEvent(int t, int len, char *ptr) : QWSProtocolItem(t,len,ptr) {} + + + + enum Type { + NoEvent, + Connected, + Mouse, + Focus, + Key, + Region, + Creation, + PropertyNotify, + PropertyReply, + SelectionClear, + SelectionRequest, + SelectionNotify, + MaxWindowRect, + QCopMessage, + WindowOperation, + IMEvent, + IMQuery, + IMInit, + Embed, + Font, + ScreenTransformation, + NEvent + }; + + QWSMouseEvent *asMouse() + { return type == Mouse ? reinterpret_cast(this) : 0; } + int window() { return *(reinterpret_cast(simpleDataPtr)); } + int window() const { return *(reinterpret_cast(simpleDataPtr)); } + static QWSEvent *factory(int type); +}; + + +//All events must start with windowID + +struct QWSConnectedEvent : QWSEvent { + QWSConnectedEvent() + : QWSEvent(QWSEvent::Connected, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + display = reinterpret_cast(rawDataPtr); + } + + struct SimpleData { + int window; + int len; + int clientId; + int servershmid; + } simpleData; + + char *display; +}; + +struct QWSMaxWindowRectEvent : QWSEvent { + QWSMaxWindowRectEvent() + : QWSEvent(MaxWindowRect, sizeof(simpleData), reinterpret_cast(&simpleData)) { } + struct SimpleData { + int window; + QRect rect; + } simpleData; +}; + +struct QWSMouseEvent : QWSEvent { + QWSMouseEvent() + : QWSEvent(QWSEvent::Mouse, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int window; + int x_root, y_root, state, delta; + int time; // milliseconds + } simpleData; +}; + +struct QWSFocusEvent : QWSEvent { + QWSFocusEvent() + : QWSEvent(QWSEvent::Focus, sizeof(simpleData), reinterpret_cast(&simpleData)) + { memset(reinterpret_cast(&simpleData),0,sizeof(simpleData)); } + struct SimpleData { + int window; + uint get_focus:1; + } simpleData; +}; + +struct QWSKeyEvent: QWSEvent { + QWSKeyEvent() + : QWSEvent(QWSEvent::Key, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int window; + uint keycode; + Qt::KeyboardModifiers modifiers; + ushort unicode; + uint is_press:1; + uint is_auto_repeat:1; + } simpleData; +}; + + +struct QWSCreationEvent : QWSEvent { + QWSCreationEvent() + : QWSEvent(QWSEvent::Creation, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int objectid; + int count; + } simpleData; +}; + +#ifndef QT_NO_QWS_PROPERTIES +struct QWSPropertyNotifyEvent : QWSEvent { + QWSPropertyNotifyEvent() + : QWSEvent(QWSEvent::PropertyNotify, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + enum State { + PropertyNewValue, + PropertyDeleted + }; + struct SimpleData { + int window; + int property; + int state; + } simpleData; +}; +#endif + +struct QWSSelectionClearEvent : QWSEvent { + QWSSelectionClearEvent() + : QWSEvent(QWSEvent::SelectionClear, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int window; + } simpleData; +}; + +struct QWSSelectionRequestEvent : QWSEvent { + QWSSelectionRequestEvent() + : QWSEvent(QWSEvent::SelectionRequest, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int window; + int requestor; // window which wants the selection + int property; // property on requestor into which the selection should be stored, normally QWSProperty::PropSelection + int mimeTypes; // Value is stored in the property mimeType on the requestor window. This value may contain + // multiple mimeTypes separated by ;; where the order reflects the priority + } simpleData; +}; + +struct QWSSelectionNotifyEvent : QWSEvent { + QWSSelectionNotifyEvent() + : QWSEvent(QWSEvent::SelectionNotify, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + struct SimpleData { + int window; + int requestor; // the window which wanted the selection and to which this event is sent + int property; // property of requestor in which the data of the selection is stored + int mimeType; // a property on the requestor in which the mime type in which the selection is, is stored + } simpleData; +}; + +//complex events: + +struct QWSRegionEvent : QWSEvent { + QWSRegionEvent() + : QWSEvent(QWSEvent::Region, sizeof(simpleData), + reinterpret_cast(&simpleData)) + { memset(reinterpret_cast(&simpleData),0,sizeof(simpleData)); } + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + rectangles = reinterpret_cast(rawDataPtr); + } + + void setData(int winId, const QRegion ®ion, uint type) { + const QVector rects = region.rects(); + setData(reinterpret_cast(rects.constData()), + rects.size() * sizeof(QRect)); + simpleData.window = winId; + simpleData.nrectangles = rects.size(); + simpleData.type = type; +#ifdef QT_QWS_CLIENTBLIT + simpleData.id = 0; +#endif + } + + enum Type {Allocation +#ifdef QT_QWS_CLIENTBLIT + , DirectPaint +#endif + }; + struct SimpleData { + int window; + int nrectangles; +#ifdef QT_QWS_CLIENTBLIT + int id; +#endif + uint type:8; + } simpleData; + + QRect *rectangles; +}; + +#ifndef QT_NO_QWSEMBEDWIDGET +struct QWSEmbedEvent : QWSEvent +{ + QWSEmbedEvent() : QWSEvent(QWSEvent::Embed, sizeof(simpleData), + reinterpret_cast(&simpleData)) + {} + + enum Type { StartEmbed = 1, StopEmbed = 2, Region = 4 }; + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + region.setRects(reinterpret_cast(rawDataPtr), + simpleData.nrectangles); + } + + void setData(int winId, Type type, const QRegion ® = QRegion()) { + simpleData.window = winId; + simpleData.nrectangles = reg.rects().size(); + simpleData.type = type; + region = reg; + const QVector rects = reg.rects(); + QWSEvent::setData(reinterpret_cast(rects.data()), + rects.size() * sizeof(QRect)); + } + + struct SimpleData { + int window; + int nrectangles; + Type type; + } simpleData; + + QRegion region; +}; +#endif // QT_NO_QWSEMBEDWIDGET + +#ifndef QT_NO_QWS_PROPERTIES +struct QWSPropertyReplyEvent : QWSEvent { + QWSPropertyReplyEvent() + : QWSEvent(QWSEvent::PropertyReply, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + data = reinterpret_cast(rawDataPtr); + } + + struct SimpleData { + int window; + int property; + int len; + } simpleData; + char *data; +}; +#endif //QT_NO_QWS_PROPERTIES + +#ifndef QT_NO_COP +struct QWSQCopMessageEvent : QWSEvent { + QWSQCopMessageEvent() + : QWSEvent(QWSEvent::QCopMessage, sizeof(simpleData), + reinterpret_cast(&simpleData)) + { memset(reinterpret_cast(&simpleData),0,sizeof(simpleData)); } + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + char* p = rawDataPtr; + channel = QByteArray(p, simpleData.lchannel); + p += simpleData.lchannel; + message = QByteArray(p, simpleData.lmessage); + p += simpleData.lmessage; + data = QByteArray(p, simpleData.ldata); + } + + void setDataDirect(const char *d, int len) { + QWSEvent::setData(d, len, false); + deleteRaw = true; + } + + struct SimpleData { + bool is_response; + int lchannel; + int lmessage; + int ldata; + } simpleData; + + QByteArray channel; + QByteArray message; + QByteArray data; +}; + +#endif + +struct QWSWindowOperationEvent : QWSEvent { + QWSWindowOperationEvent() + : QWSEvent(WindowOperation, sizeof(simpleData), reinterpret_cast(&simpleData)) { } + + enum Operation { Show, Hide, ShowMaximized, ShowNormal, ShowMinimized, Close }; + struct SimpleData { + int window; + Operation op; + } simpleData; +}; + +#ifndef QT_NO_QWS_INPUTMETHODS + + +struct QWSIMEvent : QWSEvent { + QWSIMEvent() + : QWSEvent(IMEvent, sizeof(simpleData), reinterpret_cast(&simpleData)) + { memset(reinterpret_cast(&simpleData),0,sizeof(simpleData)); } + + struct SimpleData { + int window; + int replaceFrom; + int replaceLength; + } simpleData; + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + streamingData = QByteArray::fromRawData(rawDataPtr, len); + } + QByteArray streamingData; +}; + + +struct QWSIMInitEvent : QWSEvent { + QWSIMInitEvent() + : QWSEvent(IMInit, sizeof(simpleData), reinterpret_cast(&simpleData)) + { memset(reinterpret_cast(&simpleData),0,sizeof(simpleData)); } + + struct SimpleData { + int window; + int existence; + } simpleData; + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + streamingData = QByteArray::fromRawData(rawDataPtr, len); + } + QByteArray streamingData; +}; + + +struct QWSIMQueryEvent : QWSEvent { + QWSIMQueryEvent() + : QWSEvent(QWSEvent::IMQuery, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int window; + int property; + } simpleData; + +}; + +#endif + +struct QWSFontEvent : QWSEvent { + QWSFontEvent() + : QWSEvent(QWSEvent::Font, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + enum EventType { + FontRemoved + }; + + void setData(const char *d, int len, bool allocateMem = true) { + QWSEvent::setData(d, len, allocateMem); + fontName = QByteArray::fromRawData(rawDataPtr, len); + } + + struct SimpleData { + uchar type; + } simpleData; + QByteArray fontName; +}; + +struct QWSScreenTransformationEvent : QWSEvent { + QWSScreenTransformationEvent() + : QWSEvent(QWSEvent::ScreenTransformation, sizeof(simpleData), + reinterpret_cast(&simpleData)) {} + + struct SimpleData { + int screen; + int transformation; + } simpleData; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSEVENT_QWS_H diff --git a/src/gui/embedded/qwslock.cpp b/src/gui/embedded/qwslock.cpp new file mode 100644 index 0000000000..324d81362d --- /dev/null +++ b/src/gui/embedded/qwslock.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwslock_p.h" + +#ifndef QT_NO_QWS_MULTIPROCESS + +#include "qwssignalhandler_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_OS_LINUX +#include +#endif +#include + +#include + +QT_BEGIN_NAMESPACE + +#ifdef QT_NO_SEMAPHORE +#error QWSLock currently requires semaphores +#endif + +QWSLock::QWSLock() +{ + semId = semget(IPC_PRIVATE, 3, IPC_CREAT | 0666); + + if (semId == -1) { + perror("QWSLock::QWSLock"); + qFatal("Unable to create semaphore"); + } + QWSSignalHandler::instance()->addSemaphore(semId); + + qt_semun semval; + semval.val = 1; + + if (semctl(semId, BackingStore, SETVAL, semval) == -1) { + perror("QWSLock::QWSLock"); + qFatal("Unable to initialize backingstore semaphore"); + } + lockCount[BackingStore] = 0; + + if (semctl(semId, Communication, SETVAL, semval) == -1) { + perror("QWSLock::QWSLock"); + qFatal("Unable to initialize communication semaphore"); + } + lockCount[Communication] = 0; + + semval.val = 0; + if (semctl(semId, RegionEvent, SETVAL, semval) == -1) { + perror("QWSLock::QWSLock"); + qFatal("Unable to initialize region event semaphore"); + } +} + +QWSLock::QWSLock(int id) +{ + semId = id; + QWSSignalHandler::instance()->addSemaphore(semId); + lockCount[0] = lockCount[1] = 0; +} + +QWSLock::~QWSLock() +{ + if (semId == -1) + return; + QWSSignalHandler::instance()->removeSemaphore(semId); +} + +static bool forceLock(int semId, int semNum, int) +{ + int ret; + do { + sembuf sops = { semNum, -1, 0 }; + + // As the BackingStore lock is a mutex, and only one process may own + // the lock, it's safe to use SEM_UNDO. On the other hand, the + // Communication lock is locked by the client but unlocked by the + // server and therefore can't use SEM_UNDO. + if (semNum == QWSLock::BackingStore) + sops.sem_flg |= SEM_UNDO; + + ret = semop(semId, &sops, 1); + if (ret == -1 && errno != EINTR) + qDebug("QWSLock::lock: %s", strerror(errno)); + } while (ret == -1 && errno == EINTR); + + return (ret != -1); +} + +static bool up(int semId, int semNum) +{ + int ret; + do { + sembuf sops = { semNum, 1, 0 }; + ret = semop(semId, &sops, 1); + if (ret == -1 && errno != EINTR) + qDebug("QWSLock::up: %s", strerror(errno)); + } while (ret == -1 && errno == EINTR); + + return (ret != -1); +} + +static bool down(int semId, int semNum) +{ + int ret; + do { + sembuf sops = { semNum, -1, 0 }; + ret = semop(semId, &sops, 1); + if (ret == -1 && errno != EINTR) + qDebug("QWSLock::down: %s", strerror(errno)); + } while (ret == -1 && errno == EINTR); + + return (ret != -1); +} + +static int getValue(int semId, int semNum) +{ + int ret; + do { + ret = semctl(semId, semNum, GETVAL, 0); + if (ret == -1 && errno != EINTR) + qDebug("QWSLock::getValue: %s", strerror(errno)); + } while (ret == -1 && errno == EINTR); + + return ret; +} + +bool QWSLock::lock(LockType type, int timeout) +{ + if (type == RegionEvent) + return up(semId, RegionEvent); + + if (hasLock(type)) { + ++lockCount[type]; + return true; + } + + if (!forceLock(semId, type, timeout)) + return false; + ++lockCount[type]; + return true; +} + +bool QWSLock::hasLock(LockType type) +{ + if (type == RegionEvent) + return (getValue(semId, RegionEvent) == 0); + + return (lockCount[type] > 0); +} + +void QWSLock::unlock(LockType type) +{ + if (type == RegionEvent) { + down(semId, RegionEvent); + return; + } + + if (hasLock(type)) { + --lockCount[type]; + if (hasLock(type)) + return; + } + + const int semNum = type; + int ret; + do { + sembuf sops = {semNum, 1, 0}; + if (semNum == QWSLock::BackingStore) + sops.sem_flg |= SEM_UNDO; + + ret = semop(semId, &sops, 1); + if (ret == -1 && errno != EINTR) + qDebug("QWSLock::unlock: %s", strerror(errno)); + } while (ret == -1 && errno == EINTR); +} + +bool QWSLock::wait(LockType type, int timeout) +{ + bool ok = forceLock(semId, type, timeout); + if (ok) + unlock(type); + return ok; +} + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_MULTIPROCESS diff --git a/src/gui/embedded/qwslock_p.h b/src/gui/embedded/qwslock_p.h new file mode 100644 index 0000000000..397053776a --- /dev/null +++ b/src/gui/embedded/qwslock_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSLOCK_P_H +#define QWSLOCK_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_MULTIPROCESS + +class QWSLock +{ +public: + enum LockType { BackingStore, Communication, RegionEvent }; + + QWSLock(); + QWSLock(int lockId); + ~QWSLock(); + + bool lock(LockType type, int timeout = -1); + void unlock(LockType type); + bool wait(LockType type, int timeout = -1); + bool hasLock(LockType type); + int id() const { return semId; } + +private: + int semId; + int lockCount[2]; +}; + + +QT_END_NAMESPACE +#endif // QT_NO_QWS_MULTIPROCESS +#endif // QWSLOCK_P_H diff --git a/src/gui/embedded/qwsmanager_p.h b/src/gui/embedded/qwsmanager_p.h new file mode 100644 index 0000000000..e5db3ef4b2 --- /dev/null +++ b/src/gui/embedded/qwsmanager_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSMANAGER_P_H +#define QWSMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qregion.h" +#include "QtGui/qdecoration_qws.h" + +#ifndef QT_NO_QWS_MANAGER + +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +class QWidget; +class QMenu; + +class QWSManagerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QWSManager) +public: + QWSManagerPrivate(); + + int activeRegion; + QWidget *managed; + QMenu *popup; + + enum MenuAction { + NormalizeAction, + TitleAction, + BottomRightAction, + MinimizeAction, + MaximizeAction, + CloseAction, + LastMenuAction + }; + QAction *menuActions[LastMenuAction]; + + static QWidget *active; + static QPoint mousePos; + + // Region caching to avoid getting a regiontype's + // QRegion for each mouse move event + int previousRegionType; + bool previousRegionRepainted; // Hover/Press handled + bool entireDecorationNeedsRepaint; + struct RegionCaching { + int regionType; + QRegion region; + Qt::WindowFlags windowFlags; + QRect windowGeometry; + } cached_region; + + bool newCachedRegion(const QPoint &pos); + int cachedRegionAt() + { return cached_region.regionType; } + + void dirtyRegion(int decorationRegion, + QDecoration::DecorationState state, + const QRegion &clip = QRegion()); + void clearDirtyRegions(); + + QList dirtyRegions; + QList dirtyStates; + QRegion dirtyClip; +}; + +#endif // QT_NO_QWS_MANAGER + +QT_END_NAMESPACE + +#endif // QWSMANAGER_P_H diff --git a/src/gui/embedded/qwsmanager_qws.cpp b/src/gui/embedded/qwsmanager_qws.cpp new file mode 100644 index 0000000000..5b5695264b --- /dev/null +++ b/src/gui/embedded/qwsmanager_qws.cpp @@ -0,0 +1,537 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwsmanager_qws.h" + +#ifndef QT_NO_QWS_MANAGER + +#include "qdrawutil.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qwidget.h" +#include "qmenu.h" +#include "qpainter.h" +#include "private/qpainter_p.h" +#include "qregion.h" +#include "qevent.h" +#include "qcursor.h" +#include "qwsdisplay_qws.h" +#include "qdesktopwidget.h" + +#include +#include +#include +#include +#include "qdecorationfactory_qws.h" + +#include "qlayout.h" + +#include "qwsmanager_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QWidget *QWSManagerPrivate::active = 0; +QPoint QWSManagerPrivate::mousePos; + + +QWSManagerPrivate::QWSManagerPrivate() + : QObjectPrivate(), activeRegion(QDecoration::None), managed(0), popup(0), + previousRegionType(0), previousRegionRepainted(false), entireDecorationNeedsRepaint(false) +{ + cached_region.regionType = 0; +} + +QRegion &QWSManager::cachedRegion() +{ + return d_func()->cached_region.region; +} + +/*! + \class QWSManager + \ingroup qws + \internal +*/ + +/*! + +*/ +QWSManager::QWSManager(QWidget *w) + : QObject(*new QWSManagerPrivate, (QObject*)0) +{ + d_func()->managed = w; + +} + +QWSManager::~QWSManager() +{ + Q_D(QWSManager); +#ifndef QT_NO_MENU + if (d->popup) + delete d->popup; +#endif + if (d->managed == QWSManagerPrivate::active) + QWSManagerPrivate::active = 0; +} + +QWidget *QWSManager::widget() +{ + Q_D(QWSManager); + return d->managed; +} + +QWidget *QWSManager::grabbedMouse() +{ + return QWSManagerPrivate::active; +} + +QRegion QWSManager::region() +{ + Q_D(QWSManager); + return QApplication::qwsDecoration().region(d->managed, d->managed->geometry()); +} + +bool QWSManager::event(QEvent *e) +{ + if (QObject::event(e)) + return true; + + switch (e->type()) { + case QEvent::MouseMove: + mouseMoveEvent((QMouseEvent*)e); + break; + + case QEvent::MouseButtonPress: + mousePressEvent((QMouseEvent*)e); + break; + + case QEvent::MouseButtonRelease: + mouseReleaseEvent((QMouseEvent*)e); + break; + + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent((QMouseEvent*)e); + break; + + case QEvent::Paint: + paintEvent((QPaintEvent*)e); + break; + + default: + return false; + break; + } + + return true; +} + +void QWSManager::mousePressEvent(QMouseEvent *e) +{ + Q_D(QWSManager); + d->mousePos = e->globalPos(); + d->activeRegion = QApplication::qwsDecoration().regionAt(d->managed, d->mousePos); + if(d->cached_region.regionType) + d->previousRegionRepainted |= repaintRegion(d->cached_region.regionType, QDecoration::Pressed); + + if (d->activeRegion == QDecoration::Menu) { + QPoint pos = (QApplication::layoutDirection() == Qt::LeftToRight + ? d->managed->geometry().topLeft() + : d->managed->geometry().topRight()); + menu(pos); + } + if (d->activeRegion != QDecoration::None && + d->activeRegion != QDecoration::Menu) { + d->active = d->managed; + d->managed->grabMouse(); + } + if (d->activeRegion != QDecoration::None && + d->activeRegion != QDecoration::Close && + d->activeRegion != QDecoration::Minimize && + d->activeRegion != QDecoration::Menu) { + d->managed->raise(); + } + + if (e->button() == Qt::RightButton) { + menu(e->globalPos()); + } +} + +void QWSManager::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QWSManager); + d->managed->releaseMouse(); + if (d->cached_region.regionType && d->previousRegionRepainted && QApplication::mouseButtons() == 0) { + bool doesHover = repaintRegion(d->cached_region.regionType, QDecoration::Hover); + if (!doesHover) { + repaintRegion(d->cached_region.regionType, QDecoration::Normal); + d->previousRegionRepainted = false; + } + } + + if (e->button() == Qt::LeftButton) { + //handleMove(); + int itm = QApplication::qwsDecoration().regionAt(d->managed, e->globalPos()); + int activatedItem = d->activeRegion; + d->activeRegion = QDecoration::None; + d->active = 0; + if (activatedItem == itm) + QApplication::qwsDecoration().regionClicked(d->managed, itm); + } else if (d->activeRegion == QDecoration::None) { + d->active = 0; + } +} + +void QWSManager::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QWSManager); + if (e->button() == Qt::LeftButton) + QApplication::qwsDecoration().regionDoubleClicked(d->managed, + QApplication::qwsDecoration().regionAt(d->managed, e->globalPos())); +} + +static inline Qt::CursorShape regionToShape(int region) +{ + if (region == QDecoration::None) + return Qt::ArrowCursor; + + static const struct { + int region; + Qt::CursorShape shape; + } r2s[] = { + { QDecoration::TopLeft, Qt::SizeFDiagCursor }, + { QDecoration::Top, Qt::SizeVerCursor}, + { QDecoration::TopRight, Qt::SizeBDiagCursor}, + { QDecoration::Left, Qt::SizeHorCursor}, + { QDecoration::Right, Qt::SizeHorCursor}, + { QDecoration::BottomLeft, Qt::SizeBDiagCursor}, + { QDecoration::Bottom, Qt::SizeVerCursor}, + { QDecoration::BottomRight, Qt::SizeFDiagCursor}, + { QDecoration::None, Qt::ArrowCursor} + }; + + int i = 0; + while (region != r2s[i].region && r2s[i].region) + ++i; + return r2s[i].shape; +} + +void QWSManager::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QWSManager); + if (d->newCachedRegion(e->globalPos())) { + if(d->previousRegionType && d->previousRegionRepainted) + repaintRegion(d->previousRegionType, QDecoration::Normal); + if(d->cached_region.regionType) { + d->previousRegionRepainted = repaintRegion(d->cached_region.regionType, QDecoration::Hover); + } + } + + +#ifndef QT_NO_CURSOR + if (d->managed->minimumSize() != d->managed->maximumSize()) { + QWSDisplay *qwsd = QApplication::desktop()->qwsDisplay(); + qwsd->selectCursor(d->managed, regionToShape(d->cachedRegionAt())); + } +#endif //QT_NO_CURSOR + + if (d->activeRegion) + handleMove(e->globalPos()); +} + +void QWSManager::handleMove(QPoint g) +{ + Q_D(QWSManager); + + // don't allow dragging to where the user probably cannot click! + QApplicationPrivate *ap = QApplicationPrivate::instance(); + const QRect maxWindowRect = ap->maxWindowRect(qt_screen); + if (maxWindowRect.isValid()) { + if (g.x() < maxWindowRect.x()) + g.setX(maxWindowRect.x()); + if (g.y() < maxWindowRect.y()) + g.setY(maxWindowRect.y()); + if (g.x() > maxWindowRect.right()) + g.setX(maxWindowRect.right()); + if (g.y() > maxWindowRect.bottom()) + g.setY(maxWindowRect.bottom()); + } + + if (g == d->mousePos) + return; + + if ( d->managed->isMaximized() ) + return; + + int x = d->managed->geometry().x(); + int y = d->managed->geometry().y(); + int w = d->managed->width(); + int h = d->managed->height(); + + QRect geom(d->managed->geometry()); + + QPoint delta = g - d->mousePos; + d->mousePos = g; + + if (d->activeRegion == QDecoration::Title) { + geom = QRect(x + delta.x(), y + delta.y(), w, h); + } else { + bool keepTop = true; + bool keepLeft = true; + switch (d->activeRegion) { + case QDecoration::Top: + geom.setTop(geom.top() + delta.y()); + keepTop = false; + break; + case QDecoration::Bottom: + geom.setBottom(geom.bottom() + delta.y()); + keepTop = true; + break; + case QDecoration::Left: + geom.setLeft(geom.left() + delta.x()); + keepLeft = false; + break; + case QDecoration::Right: + geom.setRight(geom.right() + delta.x()); + keepLeft = true; + break; + case QDecoration::TopRight: + geom.setTopRight(geom.topRight() + delta); + keepLeft = true; + keepTop = false; + break; + case QDecoration::TopLeft: + geom.setTopLeft(geom.topLeft() + delta); + keepLeft = false; + keepTop = false; + break; + case QDecoration::BottomLeft: + geom.setBottomLeft(geom.bottomLeft() + delta); + keepLeft = false; + keepTop = true; + break; + case QDecoration::BottomRight: + geom.setBottomRight(geom.bottomRight() + delta); + keepLeft = true; + keepTop = true; + break; + default: + return; + } + + QSize newSize = QLayout::closestAcceptableSize(d->managed, geom.size()); + + int dx = newSize.width() - geom.width(); + int dy = newSize.height() - geom.height(); + + if (keepTop) { + geom.setBottom(geom.bottom() + dy); + d->mousePos.ry() += dy; + } else { + geom.setTop(geom.top() - dy); + d->mousePos.ry() -= dy; + } + if (keepLeft) { + geom.setRight(geom.right() + dx); + d->mousePos.rx() += dx; + } else { + geom.setLeft(geom.left() - dx); + d->mousePos.rx() -= dx; + } + } + if (geom != d->managed->geometry()) { + QApplication::sendPostedEvents(); + d->managed->setGeometry(geom); + } +} + +void QWSManager::paintEvent(QPaintEvent *) +{ + Q_D(QWSManager); + d->dirtyRegion(QDecoration::All, QDecoration::Normal); +} + +void QWSManagerPrivate::dirtyRegion(int decorationRegion, + QDecoration::DecorationState state, + const QRegion &clip) +{ + QTLWExtra *topextra = managed->d_func()->extra->topextra; + QWidgetBackingStore *bs = topextra->backingStore.data(); + const bool pendingUpdateRequest = bs->isDirty(); + + if (decorationRegion == QDecoration::All) { + if (clip.isEmpty()) + entireDecorationNeedsRepaint = true; + dirtyRegions.clear(); + dirtyStates.clear(); + } + int i = dirtyRegions.indexOf(decorationRegion); + if (i >= 0) { + dirtyRegions.removeAt(i); + dirtyStates.removeAt(i); + } + + dirtyRegions.append(decorationRegion); + dirtyStates.append(state); + if (!entireDecorationNeedsRepaint) + dirtyClip += clip; + + if (!pendingUpdateRequest) + QApplication::postEvent(managed, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); +} + +void QWSManagerPrivate::clearDirtyRegions() +{ + dirtyRegions.clear(); + dirtyStates.clear(); + dirtyClip = QRegion(); + entireDecorationNeedsRepaint = false; +} + +bool QWSManager::repaintRegion(int decorationRegion, QDecoration::DecorationState state) +{ + Q_D(QWSManager); + + d->dirtyRegion(decorationRegion, state); + return true; +} + +void QWSManager::menu(const QPoint &pos) +{ +#ifdef QT_NO_MENU + Q_UNUSED(pos); +#else + Q_D(QWSManager); + if (d->popup) + delete d->popup; + + // Basic window operation menu + d->popup = new QMenu(); + QApplication::qwsDecoration().buildSysMenu(d->managed, d->popup); + connect(d->popup, SIGNAL(triggered(QAction*)), SLOT(menuTriggered(QAction*))); + + d->popup->popup(pos); + d->activeRegion = QDecoration::None; +#endif // QT_NO_MENU +} + +void QWSManager::menuTriggered(QAction *action) +{ +#ifdef QT_NO_MENU + Q_UNUSED(action); +#else + Q_D(QWSManager); + QApplication::qwsDecoration().menuTriggered(d->managed, action); + d->popup->deleteLater(); + d->popup = 0; +#endif +} + +void QWSManager::startMove() +{ + Q_D(QWSManager); + d->mousePos = QCursor::pos(); + d->activeRegion = QDecoration::Title; + d->active = d->managed; + d->managed->grabMouse(); +} + +void QWSManager::startResize() +{ + Q_D(QWSManager); + d->activeRegion = QDecoration::BottomRight; + d->active = d->managed; + d->managed->grabMouse(); +} + +void QWSManager::maximize() +{ + Q_D(QWSManager); + // find out how much space the decoration needs + const int screen = QApplication::desktop()->screenNumber(d->managed); + const QRect desk = QApplication::desktop()->availableGeometry(screen); + QRect dummy(0, 0, 1, 1); + QRect nr; + QRegion r = QApplication::qwsDecoration().region(d->managed, dummy); + if (r.isEmpty()) { + nr = desk; + } else { + r += dummy; // make sure we get the full window region in case of 0 width borders + QRect rect = r.boundingRect(); + nr = QRect(desk.x()-rect.x(), desk.y()-rect.y(), + desk.width() - (rect.width()==1 ? 0 : rect.width()-1), // ==1 -> dummy + desk.height() - (rect.height()==1 ? 0 : rect.height()-1)); + } + d->managed->setGeometry(nr); +} + +bool QWSManagerPrivate::newCachedRegion(const QPoint &pos) +{ + // Check if anything has changed that would affect the region caching + if (managed->windowFlags() == cached_region.windowFlags + && managed->geometry() == cached_region.windowGeometry + && cached_region.region.contains(pos)) + return false; + + // Update the cached region + int reg = QApplication::qwsDecoration().regionAt(managed, pos); + if (QWidget::mouseGrabber()) + reg = QDecoration::None; + + previousRegionType = cached_region.regionType; + cached_region.regionType = reg; + cached_region.region = QApplication::qwsDecoration().region(managed, managed->geometry(), + reg); + // Make room for borders around the widget, even if the decoration doesn't have a frame. + if (reg && !(reg & int(QDecoration::Borders))) { + cached_region.region -= QApplication::qwsDecoration().region(managed, managed->geometry(), QDecoration::Borders); + } + cached_region.windowFlags = managed->windowFlags(); + cached_region.windowGeometry = managed->geometry(); +// QRect rec = cached_region.region.boundingRect(); +// qDebug("Updated cached region: 0x%04x (%d, %d) (%d, %d, %d, %d)", +// reg, pos.x(), pos.y(), rec.x(), rec.y(), rec.right(), rec.bottom()); + return true; +} + +QT_END_NAMESPACE + +#endif //QT_NO_QWS_MANAGER diff --git a/src/gui/embedded/qwsmanager_qws.h b/src/gui/embedded/qwsmanager_qws.h new file mode 100644 index 0000000000..5a33bfd038 --- /dev/null +++ b/src/gui/embedded/qwsmanager_qws.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSMANAGER_QWS_H +#define QWSMANAGER_QWS_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QWS_MANAGER + +class QAction; +class QPixmap; +class QWidget; +class QPopupMenu; +class QRegion; +class QMouseEvent; +class QWSManagerPrivate; + +class Q_GUI_EXPORT QWSManager : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWSManager) +public: + explicit QWSManager(QWidget *); + ~QWSManager(); + + static QDecoration *newDefaultDecoration(); + + QWidget *widget(); + static QWidget *grabbedMouse(); + void maximize(); + void startMove(); + void startResize(); + + QRegion region(); + QRegion &cachedRegion(); + +protected Q_SLOTS: + void menuTriggered(QAction *action); + +protected: + void handleMove(QPoint g); + + virtual bool event(QEvent *e); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseDoubleClickEvent(QMouseEvent *); + virtual void paintEvent(QPaintEvent *); + bool repaintRegion(int region, QDecoration::DecorationState state); + + void menu(const QPoint &); + +private: + friend class QWidget; + friend class QETWidget; + friend class QWidgetPrivate; + friend class QApplication; + friend class QApplicationPrivate; + friend class QWidgetBackingStore; + friend class QWSWindowSurface; + friend class QGLDrawable; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +#endif // QT_NO_QWS_MANAGER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSMANAGER_QWS_H diff --git a/src/gui/embedded/qwsproperty_qws.cpp b/src/gui/embedded/qwsproperty_qws.cpp new file mode 100644 index 0000000000..b5ce4c52be --- /dev/null +++ b/src/gui/embedded/qwsproperty_qws.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwsproperty_qws.h" + +#ifndef QT_NO_QWS_PROPERTIES +#include "qwscommand_qws_p.h" +#include "qwindowsystem_qws.h" +#include "qhash.h" +#include "qalgorithms.h" +#include "qbytearray.h" + +#include + +QT_BEGIN_NAMESPACE + +class QWSPropertyManager::Data { +public: + QByteArray find(int winId, int property) + { + return properties.value(winId).value(property); + } + + typedef QHash > PropertyHash; + PropertyHash properties; +}; + +/********************************************************************* + * + * Class: QWSPropertyManager + * + *********************************************************************/ + +QWSPropertyManager::QWSPropertyManager() +{ + d = new Data; +} + +QWSPropertyManager::~QWSPropertyManager() +{ + delete d; +} + +bool QWSPropertyManager::setProperty(int winId, int property, int mode, const char *data, int len) +{ + QHash props = d->properties.value(winId); + QHash::iterator it = props.find(property); + if (it == props.end()) + return false; + + switch (mode) { + case PropReplace: + d->properties[winId][property] = QByteArray(data, len); + break; + case PropAppend: + d->properties[winId][property].append(data); + break; + case PropPrepend: + d->properties[winId][property].prepend(data); + break; + } + return true; +} + +bool QWSPropertyManager::hasProperty(int winId, int property) +{ + return d->properties.value(winId).contains(property); +} + +bool QWSPropertyManager::removeProperty(int winId, int property) +{ + QWSPropertyManager::Data::PropertyHash::iterator it = d->properties.find(winId); + if (it == d->properties.end()) + return false; + return d->properties[winId].remove( property ); +} + +bool QWSPropertyManager::addProperty(int winId, int property) +{ + if( !d->properties[winId].contains(property) ) + d->properties[winId][property] = QByteArray(); // only add if it doesn't exist + return true; +} + +bool QWSPropertyManager::getProperty(int winId, int property, const char *&data, int &len) +{ + QHash props = d->properties.value(winId); + QHash::iterator it = props.find(property); + if (it == props.end()) { + data = 0; + len = -1; + return false; + } + data = it.value().constData(); + len = it.value().length(); + + return true; +} + +bool QWSPropertyManager::removeProperties(int winId) +{ + return d->properties.remove(winId); +} + +QT_END_NAMESPACE + +#endif //QT_NO_QWS_PROPERTIES diff --git a/src/gui/embedded/qwsproperty_qws.h b/src/gui/embedded/qwsproperty_qws.h new file mode 100644 index 0000000000..4a779e0905 --- /dev/null +++ b/src/gui/embedded/qwsproperty_qws.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSPROPERTY_QWS_H +#define QWSPROPERTY_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +/********************************************************************* + * + * Class: QWSPropertyManager + * + *********************************************************************/ + +#ifndef QT_NO_QWS_PROPERTIES + +class QWSPropertyManager +{ +public: + enum Mode { + PropReplace = 0, + PropPrepend, + PropAppend + }; + + // pre-defined properties + enum Atom { + PropSelection = 0 + }; + + QWSPropertyManager(); + ~QWSPropertyManager(); + + bool setProperty(int winId, int property, int mode, const char *data, int len); + bool hasProperty(int winId, int property); + bool removeProperty(int winId, int property); + bool addProperty(int winId, int property); + bool getProperty(int winId, int property, const char *&data, int &len); + bool removeProperties(int winId); + +private: + class Data; + Data* d; +}; + +#endif // QT_NO_QWS_PROPERTIES + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSPROPERTY_QWS_H diff --git a/src/gui/embedded/qwsprotocolitem_qws.h b/src/gui/embedded/qwsprotocolitem_qws.h new file mode 100644 index 0000000000..0afd4c2a02 --- /dev/null +++ b/src/gui/embedded/qwsprotocolitem_qws.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSPROTOCOLITEM_QWS_H +#define QWSPROTOCOLITEM_QWS_H + +/********************************************************************* + * + * QWSCommand base class - only use derived classes from that + * + *********************************************************************/ + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QIODevice; + +struct QWSProtocolItem +{ + // ctor - dtor + QWSProtocolItem(int t, int len, char *ptr) : type(t), + simpleLen(len), rawLen(-1), deleteRaw(false), simpleDataPtr(ptr), + rawDataPtr(0), bytesRead(0) { } + virtual ~QWSProtocolItem(); + + // data + int type; + int simpleLen; + int rawLen; + bool deleteRaw; + + // functions +#ifndef QT_NO_QWS_MULTIPROCESS + void write(QIODevice *s); + bool read(QIODevice *s); +#endif + void copyFrom(const QWSProtocolItem *item); + + virtual void setData(const char *data, int len, bool allocateMem = true); + + char *simpleDataPtr; + char *rawDataPtr; + // temp variables + int bytesRead; +}; + +// This should probably be a method on QWSProtocolItem, but this way avoids +// changing the API of this apparently public header +// size = (int)type + (int)rawLenSize + simpleLen + rawLen +#define QWS_PROTOCOL_ITEM_SIZE( item ) \ + (2 * sizeof(int)) + ((item).simpleDataPtr ? (item).simpleLen : 0) + ((item).rawDataPtr ? (item).rawLen : 0) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSPROTOCOLITEM_QWS_H diff --git a/src/gui/embedded/qwssharedmemory.cpp b/src/gui/embedded/qwssharedmemory.cpp new file mode 100644 index 0000000000..07c46254bd --- /dev/null +++ b/src/gui/embedded/qwssharedmemory.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwssharedmemory_p.h" + +#if !defined(QT_NO_QWS_MULTIPROCESS) + +#include + +QT_BEGIN_NAMESPACE + +QWSSharedMemory::QWSSharedMemory() + : shmBase(0), shmSize(0), character(0), shmId(-1), key(-1) +{ +} + + +QWSSharedMemory::~QWSSharedMemory() +{ + detach(); +} + +/* + man page says: + On Linux, it is possible to attach a shared memory segment even if it + is already marked to be deleted. However, POSIX.1-2001 does not spec- + ify this behaviour and many other implementations do not support it. +*/ + +bool QWSSharedMemory::create(int size) +{ + if (shmId != -1) + detach(); + shmId = shmget(IPC_PRIVATE, size, IPC_CREAT|0600); + + if (shmId == -1) { +#ifdef QT_SHM_DEBUG + perror("QWSSharedMemory::create allocating shared memory"); + qWarning("Error allocating shared memory of size %d", size); +#endif + return false; + } + shmBase = shmat(shmId,0,0); + shmctl(shmId, IPC_RMID, 0); + if (shmBase == (void*)-1) { +#ifdef QT_SHM_DEBUG + perror("QWSSharedMemory::create attaching to shared memory"); + qWarning("Error attaching to shared memory id %d", shmId); +#endif + shmBase = 0; + return false; + } + return true; +} + +bool QWSSharedMemory::attach(int id) +{ + if (shmId == id) + return id != -1; + if (shmId != -1) + detach(); + + shmBase = shmat(id,0,0); + if (shmBase == (void*)-1) { +#ifdef QT_SHM_DEBUG + perror("QWSSharedMemory::attach attaching to shared memory"); + qWarning("Error attaching to shared memory 0x%x of size %d", + id, size()); +#endif + shmBase = 0; + return false; + } + shmId = id; + return true; +} + + +void QWSSharedMemory::detach () +{ + if (!shmBase) + return; + shmdt (shmBase); + shmBase = 0; + shmSize = 0; + shmId = -1; +} + +void QWSSharedMemory::setPermissions (mode_t mode) +{ + struct shmid_ds shm; + shmctl (shmId, IPC_STAT, &shm); + shm.shm_perm.mode = mode; + shmctl (shmId, IPC_SET, &shm); +} + +int QWSSharedMemory::size () const +{ + struct shmid_ds shm; + shmctl (shmId, IPC_STAT, &shm); + return shm.shm_segsz; +} + + +// old API + + + +QWSSharedMemory::QWSSharedMemory (int size, const QString &filename, char c) +{ + shmSize = size; + shmFile = filename; + shmBase = 0; + shmId = -1; + character = c; + key = ftok (shmFile.toLatin1().constData(), c); +} + + + +bool QWSSharedMemory::create () +{ + shmId = shmget (key, shmSize, IPC_CREAT | 0666); + return (shmId != -1); +} + +void QWSSharedMemory::destroy () +{ + if (shmId != -1) + shmctl(shmId, IPC_RMID, 0); +} + +bool QWSSharedMemory::attach () +{ + if (shmId == -1) + shmId = shmget (key, shmSize, 0); + + shmBase = shmat (shmId, 0, 0); + if ((long)shmBase == -1) + shmBase = 0; + + return (long)shmBase != 0; +} + + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_MULTIPROCESS diff --git a/src/gui/embedded/qwssharedmemory_p.h b/src/gui/embedded/qwssharedmemory_p.h new file mode 100644 index 0000000000..591d92a012 --- /dev/null +++ b/src/gui/embedded/qwssharedmemory_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSSHAREDMEMORY_P_H +#define QWSSHAREDMEMORY_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 "qplatformdefs.h" +#include "QtCore/qstring.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_QWS_MULTIPROCESS) + +class QWSSharedMemory { +public: + + QWSSharedMemory(); + ~QWSSharedMemory(); + + void setPermissions(mode_t mode); + int size() const; + void *address() { return shmBase; } + + int id() const { return shmId; } + + void detach(); + + bool create(int size); + bool attach(int id); + + //bool create(int size, const QString &filename, char c = 'Q'); + //bool attach(const QString &filename, char c = 'Q'); +// old API + + QWSSharedMemory(int, const QString &, char c = 'Q'); + void * base() { return address(); } + + bool create(); + void destroy(); + + bool attach(); + +private: + void *shmBase; + int shmSize; + QString shmFile; + char character; + int shmId; + key_t key; +}; + +#endif // QT_NO_QWS_MULTIPROCESS + +QT_END_NAMESPACE + +#endif // QWSSHAREDMEMORY_P_H diff --git a/src/gui/embedded/qwssignalhandler.cpp b/src/gui/embedded/qwssignalhandler.cpp new file mode 100644 index 0000000000..cc18bebdfb --- /dev/null +++ b/src/gui/embedded/qwssignalhandler.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwssignalhandler_p.h" + +#ifndef QT_NO_QWS_SIGNALHANDLER + +#include +#ifndef QT_NO_QWS_MULTIPROCESS +# include +# include + +# include +#endif +#include + +QT_BEGIN_NAMESPACE + +class QWSSignalHandlerPrivate : public QWSSignalHandler +{ +public: + QWSSignalHandlerPrivate() : QWSSignalHandler() {} +}; + + +Q_GLOBAL_STATIC(QWSSignalHandlerPrivate, signalHandlerInstance); + + +QWSSignalHandler* QWSSignalHandler::instance() +{ + return signalHandlerInstance(); +} + +QWSSignalHandler::QWSSignalHandler() +{ + const int signums[] = { SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, + SIGSEGV, SIGTERM, SIGBUS }; + const int n = sizeof(signums)/sizeof(int); + + for (int i = 0; i < n; ++i) { + const int signum = signums[i]; + qt_sighandler_t old = signal(signum, handleSignal); + if (old == SIG_IGN) // don't remove shm and semaphores when ignored + signal(signum, old); + else + oldHandlers[signum] = (old == SIG_ERR ? SIG_DFL : old); + } +} + +QWSSignalHandler::~QWSSignalHandler() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + while (!semaphores.isEmpty()) + removeSemaphore(semaphores.last()); +#endif +} + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSSignalHandler::removeSemaphore(int semno) +{ + const int index = semaphores.lastIndexOf(semno); + if (index != -1) { + qt_semun semval; + semval.val = 0; + semctl(semaphores.at(index), 0, IPC_RMID, semval); + semaphores.remove(index); + } +} +#endif // QT_NO_QWS_MULTIPROCESS + +void QWSSignalHandler::handleSignal(int signum) +{ + QWSSignalHandler *h = instance(); + + signal(signum, h->oldHandlers[signum]); + +#ifndef QT_NO_QWS_MULTIPROCESS + qt_semun semval; + semval.val = 0; + for (int i = 0; i < h->semaphores.size(); ++i) + semctl(h->semaphores.at(i), 0, IPC_RMID, semval); +#endif + + h->objects.clear(); + raise(signum); +} + +QT_END_NAMESPACE + +#endif // QT_QWS_NO_SIGNALHANDLER diff --git a/src/gui/embedded/qwssignalhandler_p.h b/src/gui/embedded/qwssignalhandler_p.h new file mode 100644 index 0000000000..e933d06b6a --- /dev/null +++ b/src/gui/embedded/qwssignalhandler_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSSIGNALHANDLER_P_H +#define QWSSIGNALHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +#ifndef QT_NO_QWS_SIGNALHANDLER + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +typedef void (*qt_sighandler_t)(int); + +class QWSSignalHandlerPrivate; + +class Q_GUI_EXPORT QWSSignalHandler +{ +public: + static QWSSignalHandler* instance(); + + ~QWSSignalHandler(); + +#ifndef QT_NO_QWS_MULTIPROCESS + inline void addSemaphore(int semno) { semaphores.append(semno); } + void removeSemaphore(int semno); +#endif + inline void addObject(QObject *object) { (void)objects.add(object); } + +private: + QWSSignalHandler(); + static void handleSignal(int signal); + QMap oldHandlers; +#ifndef QT_NO_QWS_MULTIPROCESS + QVector semaphores; +#endif + QObjectCleanupHandler objects; + + friend class QWSSignalHandlerPrivate; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_SIGNALHANDLER + +#endif // QWSSIGNALHANDLER_P_H diff --git a/src/gui/embedded/qwssocket_qws.cpp b/src/gui/embedded/qwssocket_qws.cpp new file mode 100644 index 0000000000..463af6c336 --- /dev/null +++ b/src/gui/embedded/qwssocket_qws.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qwssocket_qws.h" + +#ifndef QT_NO_QWS_MULTIPROCESS + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MIPSEL__ +# ifndef SOCK_DGRAM +# define SOCK_DGRAM 1 +# endif +# ifndef SOCK_STREAM +# define SOCK_STREAM 2 +# endif +#endif + +#if defined(Q_OS_SOLARIS) || defined (QT_LINUXBASE) +// uff-da apparently Solaris doesn't have the SUN_LEN macro, here is +// an implementation of it... +# ifndef SUN_LEN +# define SUN_LEN(su) \ + sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path) +# endif + +// nor the POSIX names of UNIX domain sockets *sigh* +# ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +# endif +# ifndef PF_LOCAL +# define PF_LOCAL PF_UNIX +# endif +#endif // Q_OS_SOLARIS || QT_LINUXBASE + +QT_BEGIN_NAMESPACE + +/*********************************************************************** + * + * QWSSocket + * + **********************************************************************/ +QWSSocket::QWSSocket(QObject *parent) + : QWS_SOCK_BASE(parent) +{ +#ifndef QT_NO_SXE + QObject::connect( this, SIGNAL(stateChanged(SocketState)), + this, SLOT(forwardStateChange(SocketState))); +#endif +} + +QWSSocket::~QWSSocket() +{ +} + +#ifndef QT_NO_SXE +QString QWSSocket::errorString() +{ + switch (QUnixSocket::error()) { + case NoError: + return QString(); + case InvalidPath: + case NonexistentPath: + return QLatin1String("Bad path"); // NO_TR + default: + return QLatin1String("Bad socket"); // NO TR + } +} + +void QWSSocket::forwardStateChange(QUnixSocket::SocketState st ) +{ + switch ( st ) + { + case ConnectedState: + emit connected(); + break; + case ClosingState: + break; + case UnconnectedState: + emit disconnected(); + break; + default: + // nothing + break; + } + if ( QUnixSocket::error() != NoError ) + emit error((QAbstractSocket::SocketError)0); +} +#endif + +bool QWSSocket::connectToLocalFile(const QString &file) +{ +#ifndef QT_NO_SXE + bool result = QUnixSocket::connect( file.toLocal8Bit() ); + if ( !result ) + { + perror( "QWSSocketAuth::connectToLocalFile could not connect:" ); + emit error(QAbstractSocket::ConnectionRefusedError); + return false; + } + return true; +#else + // create socket + int s = ::socket(PF_LOCAL, SOCK_STREAM, 0); + + // connect to socket + struct sockaddr_un a; + memset(&a, 0, sizeof(a)); + a.sun_family = PF_LOCAL; + strncpy(a.sun_path, file.toLocal8Bit().constData(), sizeof(a.sun_path) - 1); + int r = ::connect(s, (struct sockaddr*)&a, SUN_LEN(&a)); + if (r == 0) { + setSocketDescriptor(s); + } else { + perror("QWSSocket::connectToLocalFile could not connect:"); + ::close(s); + emit error(ConnectionRefusedError); + return false; + } +#endif + return true; +} + + +/*********************************************************************** + * + * QWSServerSocket + * + **********************************************************************/ +QWSServerSocket::QWSServerSocket(const QString& file, QObject *parent) +#ifndef QT_NO_SXE + : QUnixSocketServer(parent) +#else + : QTcpServer(parent) +#endif +{ + init(file); +} + +void QWSServerSocket::init(const QString &file) +{ +#ifndef QT_NO_SXE + QByteArray fn = file.toLocal8Bit(); + bool result = QUnixSocketServer::listen( fn ); + if ( !result ) + { + QUnixSocketServer::ServerError err = serverError(); + switch ( err ) + { + case InvalidPath: + qWarning("QWSServerSocket:: invalid path %s", qPrintable(file)); + break; + case ResourceError: + case BindError: + case ListenError: + qWarning("QWSServerSocket:: could not listen on path %s", qPrintable(file)); + break; + default: + break; + } + } +#else + int backlog = 16; //##### + +// create socket + int s = ::socket(PF_LOCAL, SOCK_STREAM, 0); + if (s == -1) { + perror("QWSServerSocket::init"); + qWarning("QWSServerSocket: unable to create socket."); + return; + } + + QByteArray fn = file.toLocal8Bit(); + unlink(fn.constData()); // doesn't have to succeed + + // bind socket + struct sockaddr_un a; + memset(&a, 0, sizeof(a)); + a.sun_family = PF_LOCAL; + strncpy(a.sun_path, fn.constData(), sizeof(a.sun_path) - 1); + int r = ::bind(s, (struct sockaddr*)&a, SUN_LEN(&a)); + if (r < 0) { + perror("QWSServerSocket::init"); + qWarning("QWSServerSocket: could not bind to file %s", fn.constData()); + ::close(s); + return; + } + + if (chmod(fn.constData(), 0600) < 0) { + perror("QWSServerSocket::init"); + qWarning("Could not set permissions of %s", fn.constData()); + ::close(s); + return; + } + + // listen + if (::listen(s, backlog) == 0) { + if (!setSocketDescriptor(s)) + qWarning( "QWSServerSocket could not set descriptor %d : %s", s, errorString().toLatin1().constData()); + } else { + perror("QWSServerSocket::init"); + qWarning("QWSServerSocket: could not listen to file %s", fn.constData()); + ::close(s); + } +#endif +} + +QWSServerSocket::~QWSServerSocket() +{ +} + +#ifndef QT_NO_SXE + +void QWSServerSocket::incomingConnection(int socketDescriptor) +{ + inboundConnections.append( socketDescriptor ); + emit newConnection(); +} + + +QWSSocket *QWSServerSocket::nextPendingConnection() +{ + QMutexLocker locker( &ssmx ); + if ( inboundConnections.count() == 0 ) + return 0; + QWSSocket *s = new QWSSocket(); + s->setSocketDescriptor( inboundConnections.takeFirst() ); + return s; +} + +#endif // QT_NO_SXE + +QT_END_NAMESPACE + +#endif //QT_NO_QWS_MULTIPROCESS diff --git a/src/gui/embedded/qwssocket_qws.h b/src/gui/embedded/qwssocket_qws.h new file mode 100644 index 0000000000..4f90564bdb --- /dev/null +++ b/src/gui/embedded/qwssocket_qws.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSSOCKET_QWS_H +#define QWSSOCKET_QWS_H + +#include +#include + +#ifndef QT_NO_QWS_MULTIPROCESS + +#ifndef QT_NO_SXE +#include +#include +#include +#else +#include +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + + +class QWSSocket : public QWS_SOCK_BASE +{ + Q_OBJECT +public: + explicit QWSSocket(QObject *parent=0); + ~QWSSocket(); + + bool connectToLocalFile(const QString &file); + +#ifndef QT_NO_SXE + QString errorString(); +Q_SIGNALS: + void connected(); + void disconnected(); + void error(QAbstractSocket::SocketError); +private Q_SLOTS: + void forwardStateChange(SocketState); +#endif + +private: + Q_DISABLE_COPY(QWSSocket) +}; + + +class QWSServerSocket : public QWS_SOCK_SERVER_BASE +{ + Q_OBJECT +public: + QWSServerSocket(const QString& file, QObject *parent=0); + ~QWSServerSocket(); + +#ifndef QT_NO_SXE + QWSSocket *nextPendingConnection(); +Q_SIGNALS: + void newConnection(); +protected: + void incomingConnection(int socketDescriptor); +private: + QMutex ssmx; + QList inboundConnections; +#endif + +private: + Q_DISABLE_COPY(QWSServerSocket) + + void init(const QString &file); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_QWS_MULTIPROCESS + +#endif // QWSSOCKET_QWS_H diff --git a/src/gui/embedded/qwsutils_qws.h b/src/gui/embedded/qwsutils_qws.h new file mode 100644 index 0000000000..c1295082fd --- /dev/null +++ b/src/gui/embedded/qwsutils_qws.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSUTILS_QWS_H +#define QWSUTILS_QWS_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SXE +#define QWS_SOCK_BASE QUnixSocket +#define QWS_SOCK_SERVER_BASE QUnixSocketServer +class QUnixSocket; +class QUnixSocketServer; +#else +#define QWS_SOCK_BASE QTcpSocket +#define QWS_SOCK_SERVER_BASE QTcpServer +class QTcpSocket; +class QTcpServer; +#endif +class QWSSocket; +class QWSServerSocket; + +/******************************************************************** + * + * Convenient socket functions + * + ********************************************************************/ +#ifndef QT_NO_QWS_MULTIPROCESS +inline int qws_read_uint(QIODevice *socket) +{ + if (!socket || socket->bytesAvailable() < (int)sizeof(int)) + return -1; + + int i; + socket->read(reinterpret_cast(&i), sizeof(i)); + + return i; +} + +inline void qws_write_uint(QIODevice *socket, int i) +{ + if (!socket) + return; + + socket->write(reinterpret_cast(&i), sizeof(i)); +} + +#endif // QT_NO_QWS_MULTIPROCESS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWSUTILS_QWS_H diff --git a/src/gui/graphicsview/graphicsview.pri b/src/gui/graphicsview/graphicsview.pri new file mode 100644 index 0000000000..547d7ce7ae --- /dev/null +++ b/src/gui/graphicsview/graphicsview.pri @@ -0,0 +1,52 @@ +# Qt graphicsview module +HEADERS += graphicsview/qgraphicsgridlayout.h \ + graphicsview/qgraphicsitem.h \ + graphicsview/qgraphicsitem_p.h \ + graphicsview/qgraphicsitemanimation.h \ + graphicsview/qgraphicslayout.h \ + graphicsview/qgraphicslayout_p.h \ + graphicsview/qgraphicslayoutitem.h \ + graphicsview/qgraphicslayoutitem_p.h \ + graphicsview/qgraphicslinearlayout.h \ + graphicsview/qgraphicsproxywidget.h \ + graphicsview/qgraphicsscene.h \ + graphicsview/qgraphicsscene_bsp_p.h \ + graphicsview/qgraphicsscene_p.h \ + graphicsview/qgraphicsscenebsptreeindex_p.h \ + graphicsview/qgraphicssceneevent.h \ + graphicsview/qgraphicssceneindex_p.h \ + graphicsview/qgraphicsscenelinearindex_p.h \ + graphicsview/qgraphicstransform.h \ + graphicsview/qgraphicstransform_p.h \ + graphicsview/qgraphicsview.h \ + graphicsview/qgraphicsview_p.h \ + graphicsview/qgraphicswidget.h \ + graphicsview/qgraphicswidget_p.h \ + graphicsview/qgridlayoutengine_p.h \ + graphicsview/qgraph_p.h \ + graphicsview/qsimplex_p.h \ + graphicsview/qgraphicsanchorlayout_p.h \ + graphicsview/qgraphicsanchorlayout.h + +SOURCES += graphicsview/qgraphicsgridlayout.cpp \ + graphicsview/qgraphicsitem.cpp \ + graphicsview/qgraphicsitemanimation.cpp \ + graphicsview/qgraphicslayout.cpp \ + graphicsview/qgraphicslayout_p.cpp \ + graphicsview/qgraphicslayoutitem.cpp \ + graphicsview/qgraphicslinearlayout.cpp \ + graphicsview/qgraphicsproxywidget.cpp \ + graphicsview/qgraphicsscene.cpp \ + graphicsview/qgraphicsscene_bsp.cpp \ + graphicsview/qgraphicsscenebsptreeindex.cpp \ + graphicsview/qgraphicssceneevent.cpp \ + graphicsview/qgraphicssceneindex.cpp \ + graphicsview/qgraphicsscenelinearindex.cpp \ + graphicsview/qgraphicstransform.cpp \ + graphicsview/qgraphicsview.cpp \ + graphicsview/qgraphicswidget.cpp \ + graphicsview/qgraphicswidget_p.cpp \ + graphicsview/qgridlayoutengine.cpp \ + graphicsview/qsimplex_p.cpp \ + graphicsview/qgraphicsanchorlayout_p.cpp \ + graphicsview/qgraphicsanchorlayout.cpp diff --git a/src/gui/graphicsview/qgraph_p.h b/src/gui/graphicsview/qgraph_p.h new file mode 100644 index 0000000000..094fbd1c2a --- /dev/null +++ b/src/gui/graphicsview/qgraph_p.h @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPH_P_H +#define QGRAPH_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 +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +template +class Graph +{ +public: + Graph() {} + + class const_iterator { + public: + const_iterator(const Graph *graph, bool begin) : g(graph){ + if (begin) { + row = g->m_graph.constBegin(); + //test if the graph is empty + if (row != g->m_graph.constEnd()) + { + column = (*row)->constBegin(); + } + } else { + row = g->m_graph.constEnd(); + } + } + + inline Vertex *operator*() { + return column.key(); + } + + inline Vertex *from() const { + return row.key(); + } + + inline Vertex *to() const { + return column.key(); + } + + inline bool operator==(const const_iterator &o) const { return !(*this != o); } + inline bool operator!=(const const_iterator &o) const { + if (row == g->m_graph.end()) { + return row != o.row; + } else { + return row != o.row || column != o.column; + } + } + inline const_iterator& operator=(const const_iterator &o) const { row = o.row; column = o.column; return *this;} + + // prefix + const_iterator &operator++() { + if (row != g->m_graph.constEnd()) { + ++column; + if (column == (*row)->constEnd()) { + ++row; + if (row != g->m_graph.constEnd()) { + column = (*row)->constBegin(); + } + } + } + return *this; + } + + private: + const Graph *g; + Q_TYPENAME QHash * >::const_iterator row; + Q_TYPENAME QHash::const_iterator column; + }; + + const_iterator constBegin() const { + return const_iterator(this,true); + } + + const_iterator constEnd() const { + return const_iterator(this,false); + } + + /*! + * \internal + * + * If there is an edge between \a first and \a second, it will return a structure + * containing the data associated with the edge, otherwise it will return 0. + * + */ + EdgeData *edgeData(Vertex* first, Vertex* second) { + QHash *row = m_graph.value(first); + return row ? row->value(second) : 0; + } + + void createEdge(Vertex *first, Vertex *second, EdgeData *data) + { + // Creates a bidirectional edge +#if defined(QT_DEBUG) && 0 + qDebug("Graph::createEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + if (edgeData(first, second)) { +#ifdef QT_DEBUG + qWarning("%s-%s already has an edge", qPrintable(first->toString()), qPrintable(second->toString())); +#endif + } + createDirectedEdge(first, second, data); + createDirectedEdge(second, first, data); + } + + void removeEdge(Vertex *first, Vertex *second) + { + // Removes a bidirectional edge +#if defined(QT_DEBUG) && 0 + qDebug("Graph::removeEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + EdgeData *data = edgeData(first, second); + removeDirectedEdge(first, second); + removeDirectedEdge(second, first); + if (data) delete data; + } + + EdgeData *takeEdge(Vertex* first, Vertex* second) + { +#if defined(QT_DEBUG) && 0 + qDebug("Graph::takeEdge(): %s", + qPrintable(QString::fromAscii("%1-%2") + .arg(first->toString()).arg(second->toString()))); +#endif + // Removes a bidirectional edge + EdgeData *data = edgeData(first, second); + if (data) { + removeDirectedEdge(first, second); + removeDirectedEdge(second, first); + } + return data; + } + + QList adjacentVertices(Vertex *vertex) const + { + QHash *row = m_graph.value(vertex); + QList l; + if (row) + l = row->keys(); + return l; + } + + QSet vertices() const { + QSet setOfVertices; + for (const_iterator it = constBegin(); it != constEnd(); ++it) { + setOfVertices.insert(*it); + } + return setOfVertices; + } + + QList > connections() const { + QList > conns; + for (const_iterator it = constBegin(); it != constEnd(); ++it) { + Vertex *from = it.from(); + Vertex *to = it.to(); + // do not return (from,to) *and* (to,from) + if (from < to) { + conns.append(qMakePair(from, to)); + } + } + return conns; + } + +#if defined(QT_DEBUG) + QString serializeToDot() { // traversal + QString strVertices; + QString edges; + + QSet setOfVertices = vertices(); + for (Q_TYPENAME QSet::const_iterator it = setOfVertices.begin(); it != setOfVertices.end(); ++it) { + Vertex *v = *it; + QList adjacents = adjacentVertices(v); + for (int i = 0; i < adjacents.count(); ++i) { + Vertex *v1 = adjacents.at(i); + EdgeData *data = edgeData(v, v1); + bool forward = data->from == v; + if (forward) { + edges += QString::fromAscii("\"%1\"->\"%2\" [label=\"[%3,%4,%5,%6,%7]\" color=\"#000000\"] \n") + .arg(v->toString()) + .arg(v1->toString()) + .arg(data->minSize) + .arg(data->minPrefSize) + .arg(data->prefSize) + .arg(data->maxPrefSize) + .arg(data->maxSize) + ; + } + } + strVertices += QString::fromAscii("\"%1\" [label=\"%2\"]\n").arg(v->toString()).arg(v->toString()); + } + return QString::fromAscii("%1\n%2\n").arg(strVertices).arg(edges); + } +#endif + +protected: + void createDirectedEdge(Vertex *from, Vertex *to, EdgeData *data) + { + QHash *adjacentToFirst = m_graph.value(from); + if (!adjacentToFirst) { + adjacentToFirst = new QHash(); + m_graph.insert(from, adjacentToFirst); + } + adjacentToFirst->insert(to, data); + } + + void removeDirectedEdge(Vertex *from, Vertex *to) + { + QHash *adjacentToFirst = m_graph.value(from); + Q_ASSERT(adjacentToFirst); + + adjacentToFirst->remove(to); + if (adjacentToFirst->isEmpty()) { + //nobody point to 'from' so we can remove it from the graph + m_graph.remove(from); + delete adjacentToFirst; + } + } + +private: + QHash *> m_graph; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.cpp b/src/gui/graphicsview/qgraphicsanchorlayout.cpp new file mode 100644 index 0000000000..9bb5424641 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsAnchorLayout + \brief The QGraphicsAnchorLayout class provides a layout where one can anchor widgets + together in Graphics View. + \since 4.6 + \ingroup appearance + \ingroup geomanagement + \ingroup graphicsview-api + + The anchor layout allows developers to specify how widgets should be placed relative to + each other, and to the layout itself. The specification is made by adding anchors to the + layout by calling addAnchor(), addAnchors() or addCornerAnchors(). + + Existing anchors in the layout can be accessed with the anchor() function. + Items that are anchored are automatically added to the layout, and if items + are removed, all their anchors will be automatically removed. + + \div {class="float-left"} + \inlineimage simpleanchorlayout-example.png Using an anchor layout to align simple colored widgets. + \enddiv + + Anchors are always set up between edges of an item, where the "center" is also considered to + be an edge. Consider the following example: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors + + Here, the right edge of item \c a is anchored to the left edge of item \c b and the bottom + edge of item \c a is anchored to the top edge of item \c b, with the result that + item \c b will be placed diagonally to the right and below item \c b. + + The addCornerAnchors() function provides a simpler way of anchoring the corners + of two widgets than the two individual calls to addAnchor() shown in the code + above. Here, we see how a widget can be anchored to the top-left corner of the enclosing + layout: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor + + In cases where anchors are used to match the widths or heights of widgets, it is + convenient to use the addAnchors() function. As with the other functions for specifying + anchors, it can also be used to anchor a widget to a layout. + + \clearfloat + \section1 Size Hints and Size Policies in an Anchor Layout + + QGraphicsAnchorLayout respects each item's size hints and size policies. + Note that there are some properties of QSizePolicy that are \l{Known issues}{not respected}. + + \section1 Spacing within an Anchor Layout + + The layout may distribute some space between the items. If the spacing has not been + explicitly specified, the actual amount of space will usually be 0. + + However, if the first edge is the \e opposite of the second edge (e.g., the right edge + of the first widget is anchored to the left edge of the second widget), the size of the + anchor will be queried from the style through a pixel metric: + \l{QStyle::}{PM_LayoutHorizontalSpacing} for horizontal anchors and + \l{QStyle::}{PM_LayoutVerticalSpacing} for vertical anchors. + + If the spacing is negative, the items will overlap to some extent. + + + \section1 Known issues + There are some features that QGraphicsAnchorLayout currently does not support. + This might change in the future, so avoid using these features if you want to + avoid any future regressions in behaviour: + \list + + \o Stretch factors are not respected. + + \o QSizePolicy::ExpandFlag is not respected. + + \o Height for width is not respected. + + \endlist + + \sa QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayout +*/ + +/*! + \class QGraphicsAnchor + \brief The QGraphicsAnchor class represents an anchor between two items in a + QGraphicsAnchorLayout. + \since 4.6 + \ingroup appearance + \ingroup geomanagement + \ingroup graphicsview-api + + The graphics anchor provides an API that enables you to query and manipulate the + properties an anchor has. When an anchor is added to the layout with + QGraphicsAnchorLayout::addAnchor(), a QGraphicsAnchor instance is returned where the properties + are initialized to their default values. The properties can then be further changed, and they + will be picked up the next time the layout is activated. + + \sa QGraphicsAnchorLayout::anchor() + +*/ +#include "qgraphicsanchorlayout_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +QGraphicsAnchor::QGraphicsAnchor(QGraphicsAnchorLayout *parentLayout) + : QObject(*(new QGraphicsAnchorPrivate)) +{ + Q_D(QGraphicsAnchor); + Q_ASSERT(parentLayout); + d->layoutPrivate = parentLayout->d_func(); +} + +/*! + Removes the QGraphicsAnchor object from the layout and destroys it. +*/ +QGraphicsAnchor::~QGraphicsAnchor() +{ +} + +/*! + \property QGraphicsAnchor::sizePolicy + \brief the size policy for the QGraphicsAnchor. + + By setting the size policy on an anchor you can configure how the anchor can resize itself + from its preferred spacing. For instance, if the anchor has the size policy + QSizePolicy::Minimum, the spacing is the minimum size of the anchor. However, its size + can grow up to the anchors maximum size. If the default size policy is QSizePolicy::Fixed, + the anchor can neither grow or shrink, which means that the only size the anchor can have + is the spacing. QSizePolicy::Fixed is the default size policy. + QGraphicsAnchor always has a minimum spacing of 0 and a very large maximum spacing. + + \sa QGraphicsAnchor::spacing +*/ + +void QGraphicsAnchor::setSizePolicy(QSizePolicy::Policy policy) +{ + Q_D(QGraphicsAnchor); + d->setSizePolicy(policy); +} + +QSizePolicy::Policy QGraphicsAnchor::sizePolicy() const +{ + Q_D(const QGraphicsAnchor); + return d->sizePolicy; +} + +/*! + \property QGraphicsAnchor::spacing + \brief the preferred space between items in the QGraphicsAnchorLayout. + + Depending on the anchor type, the default spacing is either + 0 or a value returned from the style. + + \sa QGraphicsAnchorLayout::addAnchor() +*/ +void QGraphicsAnchor::setSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchor); + d->setSpacing(spacing); +} + +qreal QGraphicsAnchor::spacing() const +{ + Q_D(const QGraphicsAnchor); + return d->spacing(); +} + +void QGraphicsAnchor::unsetSpacing() +{ + Q_D(QGraphicsAnchor); + d->unsetSpacing(); +} + +/*! + Constructs a QGraphicsAnchorLayout instance. \a parent is passed to + QGraphicsLayout's constructor. + */ +QGraphicsAnchorLayout::QGraphicsAnchorLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsAnchorLayoutPrivate(), parent) +{ + Q_D(QGraphicsAnchorLayout); + d->createLayoutEdges(); +} + +/*! + Destroys the QGraphicsAnchorLayout object. +*/ +QGraphicsAnchorLayout::~QGraphicsAnchorLayout() +{ + Q_D(QGraphicsAnchorLayout); + + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = d->items.at(i); + removeAt(i); + if (item) { + if (item->ownedByLayout()) + delete item; + } + } + + d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Horizontal); + d->removeCenterConstraints(this, QGraphicsAnchorLayoutPrivate::Vertical); + d->deleteLayoutEdges(); + + Q_ASSERT(d->itemCenterConstraints[0].isEmpty()); + Q_ASSERT(d->itemCenterConstraints[1].isEmpty()); + Q_ASSERT(d->items.isEmpty()); + Q_ASSERT(d->m_vertexList.isEmpty()); +} + +/*! + Creates an anchor between the edge \a firstEdge of item \a firstItem and the edge \a secondEdge + of item \a secondItem. The spacing of the anchor is picked up from the style. Anchors + between a layout edge and an item edge will have a size of 0. + If there is already an anchor between the edges, the the new anchor will replace the old one. + + \a firstItem and \a secondItem are automatically added to the layout if they are not part + of the layout. This means that count() can increase by up to 2. + + The spacing an anchor will get depends on the type of anchor. For instance, anchors from the + Right edge of one item to the Left edge of another (or vice versa) will use the default + horizontal spacing. The same behaviour applies to Bottom to Top anchors, (but they will use + the default vertical spacing). For all other anchor combinations, the spacing will be 0. + All anchoring functions will follow this rule. + + The spacing can also be set manually by using QGraphicsAnchor::setSpacing() method. + + Calling this function where \a firstItem or \a secondItem are ancestors of the layout have + undefined behaviour. + + \sa addAnchors(), addCornerAnchors() + */ +QGraphicsAnchor * +QGraphicsAnchorLayout::addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsAnchor *a = d->addAnchor(firstItem, firstEdge, secondItem, secondEdge); + invalidate(); + return a; +} + +/*! + Returns the anchor between the anchor points defined by \a firstItem and \a firstEdge and + \a secondItem and \a secondEdge. If there is no such anchor, the function will return 0. +*/ +QGraphicsAnchor * +QGraphicsAnchorLayout::anchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge) +{ + Q_D(QGraphicsAnchorLayout); + return d->getAnchor(firstItem, firstEdge, secondItem, secondEdge); +} + +/*! + Creates two anchors between \a firstItem and \a secondItem specified by the corners, + \a firstCorner and \a secondCorner, where one is for the horizontal edge and another + one for the vertical edge. + + This is a convenience function, since anchoring corners can be expressed as anchoring + two edges. For instance: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor in two steps + + This can also be achieved with the following line of code: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding a corner anchor + + If there is already an anchor between the edge pairs, it will be replaced by the anchors that + this function specifies. + + \a firstItem and \a secondItem are automatically added to the layout if they are not part of the + layout. This means that count() can increase by up to 2. + + \sa addAnchor(), addAnchors() +*/ +void QGraphicsAnchorLayout::addCornerAnchors(QGraphicsLayoutItem *firstItem, + Qt::Corner firstCorner, + QGraphicsLayoutItem *secondItem, + Qt::Corner secondCorner) +{ + Q_D(QGraphicsAnchorLayout); + + // Horizontal anchor + Qt::AnchorPoint firstEdge = (firstCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft); + Qt::AnchorPoint secondEdge = (secondCorner & 1 ? Qt::AnchorRight: Qt::AnchorLeft); + if (d->addAnchor(firstItem, firstEdge, secondItem, secondEdge)) { + // Vertical anchor + firstEdge = (firstCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop); + secondEdge = (secondCorner & 2 ? Qt::AnchorBottom: Qt::AnchorTop); + d->addAnchor(firstItem, firstEdge, secondItem, secondEdge); + + invalidate(); + } +} + +/*! + Anchors two or four edges of \a firstItem with the corresponding + edges of \a secondItem, so that \a firstItem has the same size as + \a secondItem in the dimensions specified by \a orientations. + + For example, the following example anchors the left and right edges of two items + to match their widths: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes in two steps + + This can also be achieved using the following line of code: + + \snippet examples/graphicsview/simpleanchorlayout/main.cpp adding anchors to match sizes + + \sa addAnchor(), addCornerAnchors() +*/ +void QGraphicsAnchorLayout::addAnchors(QGraphicsLayoutItem *firstItem, + QGraphicsLayoutItem *secondItem, + Qt::Orientations orientations) +{ + bool ok = true; + if (orientations & Qt::Horizontal) { + // Currently, if the first is ok, then the rest of the calls should be ok + ok = addAnchor(secondItem, Qt::AnchorLeft, firstItem, Qt::AnchorLeft) != 0; + if (ok) + addAnchor(firstItem, Qt::AnchorRight, secondItem, Qt::AnchorRight); + } + if (orientations & Qt::Vertical && ok) { + addAnchor(secondItem, Qt::AnchorTop, firstItem, Qt::AnchorTop); + addAnchor(firstItem, Qt::AnchorBottom, secondItem, Qt::AnchorBottom); + } +} + +/*! + Sets the default horizontal spacing for the anchor layout to \a spacing. + + \sa horizontalSpacing(), setVerticalSpacing(), setSpacing() +*/ +void QGraphicsAnchorLayout::setHorizontalSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[0] = spacing; + invalidate(); +} + +/*! + Sets the default vertical spacing for the anchor layout to \a spacing. + + \sa verticalSpacing(), setHorizontalSpacing(), setSpacing() +*/ +void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[1] = spacing; + invalidate(); +} + +/*! + Sets the default horizontal and the default vertical spacing for the anchor layout to \a spacing. + + If an item is anchored with no spacing associated with the anchor, it will use the default + spacing. + + QGraphicsAnchorLayout does not support negative spacings. Setting a negative value will unset the + previous spacing and make the layout use the spacing provided by the current widget style. + + \sa setHorizontalSpacing(), setVerticalSpacing() +*/ +void QGraphicsAnchorLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + + d->spacings[0] = d->spacings[1] = spacing; + invalidate(); +} + +/*! + Returns the default horizontal spacing for the anchor layout. + + \sa verticalSpacing(), setHorizontalSpacing() +*/ +qreal QGraphicsAnchorLayout::horizontalSpacing() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->styleInfo().defaultSpacing(Qt::Horizontal); +} + +/*! + Returns the default vertical spacing for the anchor layout. + + \sa horizontalSpacing(), setVerticalSpacing() +*/ +qreal QGraphicsAnchorLayout::verticalSpacing() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->styleInfo().defaultSpacing(Qt::Vertical); +} + +/*! + \reimp +*/ +void QGraphicsAnchorLayout::setGeometry(const QRectF &geom) +{ + Q_D(QGraphicsAnchorLayout); + + QGraphicsLayout::setGeometry(geom); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Horizontal); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Vertical); + d->setItemsGeometries(geom); +} + +/*! + Removes the layout item at \a index without destroying it. Ownership of + the item is transferred to the caller. + + Removing an item will also remove any of the anchors associated with it. + + \sa itemAt(), count() +*/ +void QGraphicsAnchorLayout::removeAt(int index) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayoutItem *item = d->items.value(index); + + if (!item) + return; + + // Removing an item affects both horizontal and vertical graphs + d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Horizontal); + d->removeCenterConstraints(item, QGraphicsAnchorLayoutPrivate::Vertical); + d->removeAnchors(item); + d->items.remove(index); + + item->setParentLayoutItem(0); + invalidate(); +} + +/*! + \reimp +*/ +int QGraphicsAnchorLayout::count() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.size(); +} + +/*! + \reimp +*/ +QGraphicsLayoutItem *QGraphicsAnchorLayout::itemAt(int index) const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.value(index); +} + +/*! + \reimp +*/ +void QGraphicsAnchorLayout::invalidate() +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayout::invalidate(); + d->calculateGraphCacheDirty = true; + d->styleInfoDirty = true; +} + +/*! + \reimp +*/ +QSizeF QGraphicsAnchorLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(constraint); + Q_D(const QGraphicsAnchorLayout); + + // Some setup calculations are delayed until the information is + // actually needed, avoiding unnecessary recalculations when + // adding multiple anchors. + + // sizeHint() / effectiveSizeHint() already have a cache + // mechanism, using invalidate() to force recalculation. However + // sizeHint() is called three times after invalidation (for max, + // min and pref), but we just need do our setup once. + + const_cast(d)->calculateGraphs(); + + // ### apply constraint! + QSizeF engineSizeHint( + d->sizeHints[QGraphicsAnchorLayoutPrivate::Horizontal][which], + d->sizeHints[QGraphicsAnchorLayoutPrivate::Vertical][which]); + + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + + return engineSizeHint + QSizeF(left + right, top + bottom); +} + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.h b/src/gui/graphicsview/qgraphicsanchorlayout.h new file mode 100644 index 0000000000..cdb14bc3ba --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSANCHORLAYOUT_H +#define QGRAPHICSANCHORLAYOUT_H + +#include +#include + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsAnchorPrivate; +class QGraphicsAnchorLayout; +class QGraphicsAnchorLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsAnchor : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing RESET unsetSpacing) + Q_PROPERTY(QSizePolicy::Policy sizePolicy READ sizePolicy WRITE setSizePolicy) +public: + void setSpacing(qreal spacing); + void unsetSpacing(); + qreal spacing() const; + void setSizePolicy(QSizePolicy::Policy policy); + QSizePolicy::Policy sizePolicy() const; + ~QGraphicsAnchor(); +private: + QGraphicsAnchor(QGraphicsAnchorLayout *parent); + + Q_DECLARE_PRIVATE(QGraphicsAnchor) + + friend class QGraphicsAnchorLayoutPrivate; + friend struct AnchorData; +}; + +class Q_GUI_EXPORT QGraphicsAnchorLayout : public QGraphicsLayout +{ +public: + QGraphicsAnchorLayout(QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsAnchorLayout(); + + QGraphicsAnchor *addAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + QGraphicsAnchor *anchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + + void addCornerAnchors(QGraphicsLayoutItem *firstItem, Qt::Corner firstCorner, + QGraphicsLayoutItem *secondItem, Qt::Corner secondCorner); + + void addAnchors(QGraphicsLayoutItem *firstItem, + QGraphicsLayoutItem *secondItem, + Qt::Orientations orientations = Qt::Horizontal | Qt::Vertical); + + void setHorizontalSpacing(qreal spacing); + void setVerticalSpacing(qreal spacing); + void setSpacing(qreal spacing); + qreal horizontalSpacing() const; + qreal verticalSpacing() const; + + void removeAt(int index); + void setGeometry(const QRectF &rect); + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + + void invalidate(); +protected: + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +private: + Q_DISABLE_COPY(QGraphicsAnchorLayout) + Q_DECLARE_PRIVATE(QGraphicsAnchorLayout) + + friend class QGraphicsAnchor; +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp new file mode 100644 index 0000000000..48cbec3d62 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -0,0 +1,3015 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#ifdef QT_DEBUG +#include +#endif + +#include "qgraphicsanchorlayout_p.h" + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +// To ensure that all variables inside the simplex solver are non-negative, +// we limit the size of anchors in the interval [-limit, limit]. Then before +// sending them to the simplex solver we add "limit" as an offset, so that +// they are actually calculated in the interval [0, 2 * limit] +// To avoid numerical errors in platforms where we use single precision, +// we use a tighter limit for the variables range. +const qreal g_offset = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32; + +QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) + : QObjectPrivate(version), layoutPrivate(0), data(0), + sizePolicy(QSizePolicy::Fixed), preferredSize(0), + hasSize(true) +{ +} + +QGraphicsAnchorPrivate::~QGraphicsAnchorPrivate() +{ + if (data) { + // The QGraphicsAnchor was already deleted at this moment. We must clean + // the dangling pointer to avoid double deletion in the AnchorData dtor. + data->graphicsAnchor = 0; + + layoutPrivate->removeAnchor(data->from, data->to); + } +} + +void QGraphicsAnchorPrivate::setSizePolicy(QSizePolicy::Policy policy) +{ + if (sizePolicy != policy) { + sizePolicy = policy; + layoutPrivate->q_func()->invalidate(); + } +} + +void QGraphicsAnchorPrivate::setSpacing(qreal value) +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return; + } + + if (hasSize && (preferredSize == value)) + return; + + // The anchor has an user-defined size + hasSize = true; + preferredSize = value; + + layoutPrivate->q_func()->invalidate(); +} + +void QGraphicsAnchorPrivate::unsetSpacing() +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return; + } + + // Return to standard direction + hasSize = false; + + layoutPrivate->q_func()->invalidate(); +} + +qreal QGraphicsAnchorPrivate::spacing() const +{ + if (!data) { + qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist."); + return 0; + } + + return preferredSize; +} + + +static void applySizePolicy(QSizePolicy::Policy policy, + qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, + qreal *minSize, qreal *prefSize, + qreal *maxSize) +{ + // minSize, prefSize and maxSize are initialized + // with item's preferred Size: this is QSizePolicy::Fixed. + // + // Then we check each flag to find the resultant QSizePolicy, + // according to the following table: + // + // constant value + // QSizePolicy::Fixed 0 + // QSizePolicy::Minimum GrowFlag + // QSizePolicy::Maximum ShrinkFlag + // QSizePolicy::Preferred GrowFlag | ShrinkFlag + // QSizePolicy::Ignored GrowFlag | ShrinkFlag | IgnoreFlag + + if (policy & QSizePolicy::ShrinkFlag) + *minSize = minSizeHint; + else + *minSize = prefSizeHint; + + if (policy & QSizePolicy::GrowFlag) + *maxSize = maxSizeHint; + else + *maxSize = prefSizeHint; + + // Note that these two initializations are affected by the previous flags + if (policy & QSizePolicy::IgnoreFlag) + *prefSize = *minSize; + else + *prefSize = prefSizeHint; +} + +AnchorData::~AnchorData() +{ + if (graphicsAnchor) { + // Remove reference to ourself to avoid double removal in + // QGraphicsAnchorPrivate dtor. + graphicsAnchor->d_func()->data = 0; + + delete graphicsAnchor; + } +} + + +void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) +{ + QSizePolicy::Policy policy; + qreal minSizeHint; + qreal prefSizeHint; + qreal maxSizeHint; + + if (item) { + // It is an internal anchor, fetch size information from the item + if (isLayoutAnchor) { + minSize = 0; + prefSize = 0; + maxSize = QWIDGETSIZE_MAX; + if (isCenterAnchor) + maxSize /= 2; + + minPrefSize = prefSize; + maxPrefSize = maxSize; + return; + } else { + if (orientation == QGraphicsAnchorLayoutPrivate::Horizontal) { + policy = item->sizePolicy().horizontalPolicy(); + minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).width(); + prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).width(); + maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).width(); + } else { + policy = item->sizePolicy().verticalPolicy(); + minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).height(); + prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).height(); + maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).height(); + } + + if (isCenterAnchor) { + minSizeHint /= 2; + prefSizeHint /= 2; + maxSizeHint /= 2; + } + } + } else { + // It is a user-created anchor, fetch size information from the associated QGraphicsAnchor + Q_ASSERT(graphicsAnchor); + QGraphicsAnchorPrivate *anchorPrivate = graphicsAnchor->d_func(); + + // Policy, min and max sizes are straightforward + policy = anchorPrivate->sizePolicy; + minSizeHint = 0; + maxSizeHint = QWIDGETSIZE_MAX; + + // Preferred Size + if (anchorPrivate->hasSize) { + // Anchor has user-defined size + prefSizeHint = anchorPrivate->preferredSize; + } else { + // Fetch size information from style + const Qt::Orientation orient = Qt::Orientation(QGraphicsAnchorLayoutPrivate::edgeOrientation(from->m_edge) + 1); + qreal s = styleInfo->defaultSpacing(orient); + if (s < 0) { + QSizePolicy::ControlType controlTypeFrom = from->m_item->sizePolicy().controlType(); + QSizePolicy::ControlType controlTypeTo = to->m_item->sizePolicy().controlType(); + s = styleInfo->perItemSpacing(controlTypeFrom, controlTypeTo, orient); + + // ### Currently we do not support negative anchors inside the graph. + // To avoid those being created by a negative style spacing, we must + // make this test. + if (s < 0) + s = 0; + } + prefSizeHint = s; + } + } + + // Fill minSize, prefSize and maxSize based on policy and sizeHints + applySizePolicy(policy, minSizeHint, prefSizeHint, maxSizeHint, + &minSize, &prefSize, &maxSize); + + minPrefSize = prefSize; + maxPrefSize = maxSize; + + // Set the anchor effective sizes to preferred. + // + // Note: The idea here is that all items should remain at their + // preferred size unless where that's impossible. In cases where + // the item is subject to restrictions (anchored to the layout + // edges, for instance), the simplex solver will be run to + // recalculate and override the values we set here. + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; +} + +void ParallelAnchorData::updateChildrenSizes() +{ + firstEdge->sizeAtMinimum = sizeAtMinimum; + firstEdge->sizeAtPreferred = sizeAtPreferred; + firstEdge->sizeAtMaximum = sizeAtMaximum; + + if (secondForward()) { + secondEdge->sizeAtMinimum = sizeAtMinimum; + secondEdge->sizeAtPreferred = sizeAtPreferred; + secondEdge->sizeAtMaximum = sizeAtMaximum; + } else { + secondEdge->sizeAtMinimum = -sizeAtMinimum; + secondEdge->sizeAtPreferred = -sizeAtPreferred; + secondEdge->sizeAtMaximum = -sizeAtMaximum; + } + + firstEdge->updateChildrenSizes(); + secondEdge->updateChildrenSizes(); +} + +/* + \internal + + Initialize the parallel anchor size hints using the sizeHint information from + its children. + + Note that parallel groups can lead to unfeasibility, so during calculation, we can + find out one unfeasibility. Because of that this method return boolean. This can't + happen in sequential, so there the method is void. + */ +bool ParallelAnchorData::calculateSizeHints() +{ + // Normalize second child sizes. + // A negative anchor of sizes min, minPref, pref, maxPref and max, is equivalent + // to a forward anchor of sizes -max, -maxPref, -pref, -minPref, -min + qreal secondMin; + qreal secondMinPref; + qreal secondPref; + qreal secondMaxPref; + qreal secondMax; + + if (secondForward()) { + secondMin = secondEdge->minSize; + secondMinPref = secondEdge->minPrefSize; + secondPref = secondEdge->prefSize; + secondMaxPref = secondEdge->maxPrefSize; + secondMax = secondEdge->maxSize; + } else { + secondMin = -secondEdge->maxSize; + secondMinPref = -secondEdge->maxPrefSize; + secondPref = -secondEdge->prefSize; + secondMaxPref = -secondEdge->minPrefSize; + secondMax = -secondEdge->minSize; + } + + minSize = qMax(firstEdge->minSize, secondMin); + maxSize = qMin(firstEdge->maxSize, secondMax); + + // This condition means that the maximum size of one anchor being simplified is smaller than + // the minimum size of the other anchor. The consequence is that there won't be a valid size + // for this parallel setup. + if (minSize > maxSize) { + return false; + } + + // Preferred size calculation + // The calculation of preferred size is done as follows: + // + // 1) Check whether one of the child anchors is the layout structural anchor + // If so, we can simply copy the preferred information from the other child, + // after bounding it to our minimum and maximum sizes. + // If not, then we proceed with the actual calculations. + // + // 2) The whole algorithm for preferred size calculation is based on the fact + // that, if a given anchor cannot remain at its preferred size, it'd rather + // grow than shrink. + // + // What happens though is that while this affirmative is true for simple + // anchors, it may not be true for sequential anchors that have one or more + // reversed anchors inside it. That happens because when a sequential anchor + // grows, any reversed anchors inside it may be required to shrink, something + // we try to avoid, as said above. + // + // To overcome this, besides their actual preferred size "prefSize", each anchor + // exports what we call "minPrefSize" and "maxPrefSize". These two values define + // a surrounding interval where, if required to move, the anchor would rather + // remain inside. + // + // For standard anchors, this area simply represents the region between + // prefSize and maxSize, which makes sense since our first affirmation. + // For composed anchors, these values are calculated as to reduce the global + // "damage", that is, to reduce the total deviation and the total amount of + // anchors that had to shrink. + + if (firstEdge->isLayoutAnchor) { + prefSize = qBound(minSize, secondPref, maxSize); + minPrefSize = qBound(minSize, secondMinPref, maxSize); + maxPrefSize = qBound(minSize, secondMaxPref, maxSize); + } else if (secondEdge->isLayoutAnchor) { + prefSize = qBound(minSize, firstEdge->prefSize, maxSize); + minPrefSize = qBound(minSize, firstEdge->minPrefSize, maxSize); + maxPrefSize = qBound(minSize, firstEdge->maxPrefSize, maxSize); + } else { + // Calculate the intersection between the "preferred" regions of each child + const qreal lowerBoundary = + qBound(minSize, qMax(firstEdge->minPrefSize, secondMinPref), maxSize); + const qreal upperBoundary = + qBound(minSize, qMin(firstEdge->maxPrefSize, secondMaxPref), maxSize); + const qreal prefMean = + qBound(minSize, (firstEdge->prefSize + secondPref) / 2, maxSize); + + if (lowerBoundary < upperBoundary) { + // If there is an intersection between the two regions, this intersection + // will be used as the preferred region of the parallel anchor itself. + // The preferred size will be the bounded average between the two preferred + // sizes. + prefSize = qBound(lowerBoundary, prefMean, upperBoundary); + minPrefSize = lowerBoundary; + maxPrefSize = upperBoundary; + } else { + // If there is no intersection, we have to attribute "damage" to at least + // one of the children. The minimum total damage is achieved in points + // inside the region that extends from (1) the upper boundary of the lower + // region to (2) the lower boundary of the upper region. + // Then, we expose this region as _our_ preferred region and once again, + // use the bounded average as our preferred size. + prefSize = qBound(upperBoundary, prefMean, lowerBoundary); + minPrefSize = upperBoundary; + maxPrefSize = lowerBoundary; + } + } + + // See comment in AnchorData::refreshSizeHints() about sizeAt* values + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; + + return true; +} + +/*! + \internal + returns the factor in the interval [-1, 1]. + -1 is at Minimum + 0 is at Preferred + 1 is at Maximum +*/ +static QPair getFactor(qreal value, qreal min, + qreal minPref, qreal pref, + qreal maxPref, qreal max) +{ + QGraphicsAnchorLayoutPrivate::Interval interval; + qreal lower; + qreal upper; + + if (value < minPref) { + interval = QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred; + lower = min; + upper = minPref; + } else if (value < pref) { + interval = QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred; + lower = minPref; + upper = pref; + } else if (value < maxPref) { + interval = QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred; + lower = pref; + upper = maxPref; + } else { + interval = QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum; + lower = maxPref; + upper = max; + } + + qreal progress; + if (upper == lower) { + progress = 0; + } else { + progress = (value - lower) / (upper - lower); + } + + return qMakePair(interval, progress); +} + +static qreal interpolate(const QPair &factor, + qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max) +{ + qreal lower; + qreal upper; + + switch (factor.first) { + case QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred: + lower = min; + upper = minPref; + break; + case QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred: + lower = minPref; + upper = pref; + break; + case QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred: + lower = pref; + upper = maxPref; + break; + case QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum: + lower = maxPref; + upper = max; + break; + } + + return lower + factor.second * (upper - lower); +} + +void SequentialAnchorData::updateChildrenSizes() +{ + // Band here refers if the value is in the Minimum To Preferred + // band (the lower band) or the Preferred To Maximum (the upper band). + + const QPair minFactor = + getFactor(sizeAtMinimum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + const QPair prefFactor = + getFactor(sizeAtPreferred, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + const QPair maxFactor = + getFactor(sizeAtMaximum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); + + // XXX This is not safe if Vertex simplification takes place after the sequential + // anchor is created. In that case, "prev" will be a group-vertex, different from + // "from" or "to", that _contains_ one of them. + AnchorVertex *prev = from; + + for (int i = 0; i < m_edges.count(); ++i) { + AnchorData *e = m_edges.at(i); + + const bool edgeIsForward = (e->from == prev); + if (edgeIsForward) { + e->sizeAtMinimum = interpolate(minFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + prev = e->to; + } else { + Q_ASSERT(prev == e->to); + e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + prev = e->from; + } + + e->updateChildrenSizes(); + } +} + +void SequentialAnchorData::calculateSizeHints() +{ + minSize = 0; + prefSize = 0; + maxSize = 0; + minPrefSize = 0; + maxPrefSize = 0; + + AnchorVertex *prev = from; + + for (int i = 0; i < m_edges.count(); ++i) { + AnchorData *edge = m_edges.at(i); + + const bool edgeIsForward = (edge->from == prev); + if (edgeIsForward) { + minSize += edge->minSize; + prefSize += edge->prefSize; + maxSize += edge->maxSize; + minPrefSize += edge->minPrefSize; + maxPrefSize += edge->maxPrefSize; + prev = edge->to; + } else { + Q_ASSERT(prev == edge->to); + minSize -= edge->maxSize; + prefSize -= edge->prefSize; + maxSize -= edge->minSize; + minPrefSize -= edge->maxPrefSize; + maxPrefSize -= edge->minPrefSize; + prev = edge->from; + } + } + + // See comment in AnchorData::refreshSizeHints() about sizeAt* values + sizeAtMinimum = prefSize; + sizeAtPreferred = prefSize; + sizeAtMaximum = prefSize; +} + +#ifdef QT_DEBUG +void AnchorData::dump(int indent) { + if (type == Parallel) { + qDebug("%*s type: parallel:", indent, ""); + ParallelAnchorData *p = static_cast(this); + p->firstEdge->dump(indent+2); + p->secondEdge->dump(indent+2); + } else if (type == Sequential) { + SequentialAnchorData *s = static_cast(this); + int kids = s->m_edges.count(); + qDebug("%*s type: sequential(%d):", indent, "", kids); + for (int i = 0; i < kids; ++i) { + s->m_edges.at(i)->dump(indent+2); + } + } else { + qDebug("%*s type: Normal:", indent, ""); + } +} + +#endif + +QSimplexConstraint *GraphPath::constraint(const GraphPath &path) const +{ + // Calculate + QSet cPositives; + QSet cNegatives; + QSet intersection; + + cPositives = positives + path.negatives; + cNegatives = negatives + path.positives; + + intersection = cPositives & cNegatives; + + cPositives -= intersection; + cNegatives -= intersection; + + // Fill + QSimplexConstraint *c = new QSimplexConstraint; + QSet::iterator i; + for (i = cPositives.begin(); i != cPositives.end(); ++i) + c->variables.insert(*i, 1.0); + + for (i = cNegatives.begin(); i != cNegatives.end(); ++i) + c->variables.insert(*i, -1.0); + + return c; +} + +#ifdef QT_DEBUG +QString GraphPath::toString() const +{ + QString string(QLatin1String("Path: ")); + foreach(AnchorData *edge, positives) + string += QString::fromAscii(" (+++) %1").arg(edge->toString()); + + foreach(AnchorData *edge, negatives) + string += QString::fromAscii(" (---) %1").arg(edge->toString()); + + return string; +} +#endif + +QGraphicsAnchorLayoutPrivate::QGraphicsAnchorLayoutPrivate() + : calculateGraphCacheDirty(true), styleInfoDirty(true) +{ + for (int i = 0; i < NOrientations; ++i) { + for (int j = 0; j < 3; ++j) { + sizeHints[i][j] = -1; + } + interpolationProgress[i] = -1; + + spacings[i] = -1; + graphHasConflicts[i] = false; + + layoutFirstVertex[i] = 0; + layoutCentralVertex[i] = 0; + layoutLastVertex[i] = 0; + } +} + +Qt::AnchorPoint QGraphicsAnchorLayoutPrivate::oppositeEdge(Qt::AnchorPoint edge) +{ + switch (edge) { + case Qt::AnchorLeft: + edge = Qt::AnchorRight; + break; + case Qt::AnchorRight: + edge = Qt::AnchorLeft; + break; + case Qt::AnchorTop: + edge = Qt::AnchorBottom; + break; + case Qt::AnchorBottom: + edge = Qt::AnchorTop; + break; + default: + break; + } + return edge; +} + + +/*! + * \internal + * + * helper function in order to avoid overflowing anchor sizes + * the returned size will never be larger than FLT_MAX + * + */ +inline static qreal checkAdd(qreal a, qreal b) +{ + if (FLT_MAX - b < a) + return FLT_MAX; + return a + b; +} + +/*! + \internal + + Adds \a newAnchor to the graph. + + Returns the newAnchor itself if it could be added without further changes to the graph. If a + new parallel anchor had to be created, then returns the new parallel anchor. If a parallel anchor + had to be created and it results in an unfeasible setup, \a feasible is set to false, otherwise + true. + + Note that in the case a new parallel anchor is created, it might also take over some constraints + from its children anchors. +*/ +AnchorData *QGraphicsAnchorLayoutPrivate::addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible) +{ + Orientation orientation = Orientation(newAnchor->orientation); + Graph &g = graph[orientation]; + *feasible = true; + + // If already exists one anchor where newAnchor is supposed to be, we create a parallel + // anchor. + if (AnchorData *oldAnchor = g.takeEdge(newAnchor->from, newAnchor->to)) { + ParallelAnchorData *parallel = new ParallelAnchorData(oldAnchor, newAnchor); + + // The parallel anchor will "replace" its children anchors in + // every center constraint that they appear. + + // ### If the dependent (center) anchors had reference(s) to their constraints, we + // could avoid traversing all the itemCenterConstraints. + QList &constraints = itemCenterConstraints[orientation]; + + AnchorData *children[2] = { oldAnchor, newAnchor }; + QList *childrenConstraints[2] = { ¶llel->m_firstConstraints, + ¶llel->m_secondConstraints }; + + for (int i = 0; i < 2; ++i) { + AnchorData *child = children[i]; + QList *childConstraints = childrenConstraints[i]; + + // We need to fix the second child constraints if the parallel group will have the + // opposite direction of the second child anchor. For the point of view of external + // entities, this anchor was reversed. So if at some point we say that the parallel + // has a value of 20, this mean that the second child (when reversed) will be + // assigned -20. + const bool needsReverse = i == 1 && !parallel->secondForward(); + + if (!child->isCenterAnchor) + continue; + + parallel->isCenterAnchor = true; + + for (int j = 0; j < constraints.count(); ++j) { + QSimplexConstraint *c = constraints[j]; + if (c->variables.contains(child)) { + childConstraints->append(c); + qreal v = c->variables.take(child); + if (needsReverse) + v *= -1; + c->variables.insert(parallel, v); + } + } + } + + // At this point we can identify that the parallel anchor is not feasible, e.g. one + // anchor minimum size is bigger than the other anchor maximum size. + *feasible = parallel->calculateSizeHints(); + newAnchor = parallel; + } + + g.createEdge(newAnchor->from, newAnchor->to, newAnchor); + return newAnchor; +} + +/*! + \internal + + Takes the sequence of vertices described by (\a before, \a vertices, \a after) and removes + all anchors connected to the vertices in \a vertices, returning one simplified anchor between + \a before and \a after. + + Note that this function doesn't add the created anchor to the graph. This should be done by + the caller. +*/ +static AnchorData *createSequence(Graph *graph, + AnchorVertex *before, + const QVector &vertices, + AnchorVertex *after) +{ +#if defined(QT_DEBUG) && 0 + QString strVertices; + for (int i = 0; i < vertices.count(); ++i) { + strVertices += QString::fromAscii("%1 - ").arg(vertices.at(i)->toString()); + } + QString strPath = QString::fromAscii("%1 - %2%3").arg(before->toString(), strVertices, after->toString()); + qDebug("simplifying [%s] to [%s - %s]", qPrintable(strPath), qPrintable(before->toString()), qPrintable(after->toString())); +#endif + + AnchorVertex *prev = before; + QVector edges; + + // Take from the graph, the edges that will be simplificated + for (int i = 0; i < vertices.count(); ++i) { + AnchorVertex *next = vertices.at(i); + AnchorData *ad = graph->takeEdge(prev, next); + Q_ASSERT(ad); + edges.append(ad); + prev = next; + } + + // Take the last edge (not covered in the loop above) + AnchorData *ad = graph->takeEdge(vertices.last(), after); + Q_ASSERT(ad); + edges.append(ad); + + // Create sequence + SequentialAnchorData *sequence = new SequentialAnchorData(vertices, edges); + sequence->from = before; + sequence->to = after; + + sequence->calculateSizeHints(); + + return sequence; +} + +/*! + \internal + + The purpose of this function is to simplify the graph. + Simplification serves two purposes: + 1. Reduce the number of edges in the graph, (thus the number of variables to the equation + solver is reduced, and the solver performs better). + 2. Be able to do distribution of sequences of edges more intelligently (esp. with sequential + anchors) + + It is essential that it must be possible to restore simplified anchors back to their "original" + form. This is done by restoreSimplifiedAnchor(). + + There are two types of simplification that can be done: + 1. Sequential simplification + Sequential simplification means that all sequences of anchors will be merged into one single + anchor. Only anhcors that points in the same direction will be merged. + 2. Parallel simplification + If a simplified sequential anchor is about to be inserted between two vertices in the graph + and there already exist an anchor between those two vertices, a parallel anchor will be + created that serves as a placeholder for the sequential anchor and the anchor that was + already between the two vertices. + + The process of simplification can be described as: + + 1. Simplify all sequences of anchors into one anchor. + If no further simplification was done, go to (3) + - If there already exist an anchor where the sequential anchor is supposed to be inserted, + take that anchor out of the graph + - Then create a parallel anchor that holds the sequential anchor and the anchor just taken + out of the graph. + 2. Go to (1) + 3. Done + + When creating the parallel anchors, the algorithm might identify unfeasible situations. In this + case the simplification process stops and returns false. Otherwise returns true. +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyGraph(Orientation orientation) +{ + if (items.isEmpty()) + return true; + +#if defined(QT_DEBUG) && 0 + qDebug("Simplifying Graph for %s", + orientation == Horizontal ? "Horizontal" : "Vertical"); + + static int count = 0; + if (orientation == Horizontal) { + count++; + dumpGraph(QString::fromAscii("%1-full").arg(count)); + } +#endif + + // Vertex simplification + if (!simplifyVertices(orientation)) { + restoreVertices(orientation); + return false; + } + + // Anchor simplification + bool dirty; + bool feasible = true; + do { + dirty = simplifyGraphIteration(orientation, &feasible); + } while (dirty && feasible); + + // Note that if we are not feasible, we fallback and make sure that the graph is fully restored + if (!feasible) { + restoreSimplifiedGraph(orientation); + restoreVertices(orientation); + return false; + } + +#if defined(QT_DEBUG) && 0 + dumpGraph(QString::fromAscii("%1-simplified-%2").arg(count).arg( + QString::fromAscii(orientation == Horizontal ? "Horizontal" : "Vertical"))); +#endif + + return true; +} + +static AnchorVertex *replaceVertex_helper(AnchorData *data, AnchorVertex *oldV, AnchorVertex *newV) +{ + AnchorVertex *other; + if (data->from == oldV) { + data->from = newV; + other = data->to; + } else { + data->to = newV; + other = data->from; + } + return other; +} + +bool QGraphicsAnchorLayoutPrivate::replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList &edges) +{ + Graph &g = graph[orientation]; + bool feasible = true; + + for (int i = 0; i < edges.count(); ++i) { + AnchorData *ad = edges[i]; + AnchorVertex *otherV = replaceVertex_helper(ad, oldV, newV); + +#if defined(QT_DEBUG) + ad->name = QString::fromAscii("%1 --to--> %2").arg(ad->from->toString()).arg(ad->to->toString()); +#endif + + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(ad, &newFeasible); + feasible &= newFeasible; + + if (newAnchor != ad) { + // A parallel was created, we mark that in the list of anchors created by vertex + // simplification. This is needed because we want to restore them in a separate step + // from the restoration of anchor simplification. + anchorsFromSimplifiedVertices[orientation].append(newAnchor); + } + + g.takeEdge(oldV, otherV); + } + + return feasible; +} + +/*! + \internal +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + Graph &g = graph[orientation]; + + // We'll walk through vertices + QStack stack; + stack.push(layoutFirstVertex[orientation]); + QSet visited; + + while (!stack.isEmpty()) { + AnchorVertex *v = stack.pop(); + visited.insert(v); + + // Each adjacent of 'v' is a possible vertex to be merged. So we traverse all of + // them. Since once a merge is made, we might add new adjacents, and we don't want to + // pass two times through one adjacent. The 'index' is used to track our position. + QList adjacents = g.adjacentVertices(v); + int index = 0; + + while (index < adjacents.count()) { + AnchorVertex *next = adjacents.at(index); + index++; + + AnchorData *data = g.edgeData(v, next); + const bool bothLayoutVertices = v->m_item == q && next->m_item == q; + const bool zeroSized = !data->minSize && !data->maxSize; + + if (!bothLayoutVertices && zeroSized) { + + // Create a new vertex pair, note that we keep a list of those vertices so we can + // easily process them when restoring the graph. + AnchorVertexPair *newV = new AnchorVertexPair(v, next, data); + simplifiedVertices[orientation].append(newV); + + // Collect the anchors of both vertices, the new vertex pair will take their place + // in those anchors + const QList &vAdjacents = g.adjacentVertices(v); + const QList &nextAdjacents = g.adjacentVertices(next); + + for (int i = 0; i < vAdjacents.count(); ++i) { + AnchorVertex *adjacent = vAdjacents.at(i); + if (adjacent != next) { + AnchorData *ad = g.edgeData(v, adjacent); + newV->m_firstAnchors.append(ad); + } + } + + for (int i = 0; i < nextAdjacents.count(); ++i) { + AnchorVertex *adjacent = nextAdjacents.at(i); + if (adjacent != v) { + AnchorData *ad = g.edgeData(next, adjacent); + newV->m_secondAnchors.append(ad); + + // We'll also add new vertices to the adjacent list of the new 'v', to be + // created as a vertex pair and replace the current one. + if (!adjacents.contains(adjacent)) + adjacents.append(adjacent); + } + } + + // ### merge this loop into the ones that calculated m_firstAnchors/m_secondAnchors? + // Make newV take the place of v and next + bool feasible = replaceVertex(orientation, v, newV, newV->m_firstAnchors); + feasible &= replaceVertex(orientation, next, newV, newV->m_secondAnchors); + + // Update the layout vertex information if one of the vertices is a layout vertex. + AnchorVertex *layoutVertex = 0; + if (v->m_item == q) + layoutVertex = v; + else if (next->m_item == q) + layoutVertex = next; + + if (layoutVertex) { + // Layout vertices always have m_item == q... + newV->m_item = q; + changeLayoutVertex(orientation, layoutVertex, newV); + } + + g.takeEdge(v, next); + + // If a non-feasibility is found, we leave early and cancel the simplification + if (!feasible) + return false; + + v = newV; + visited.insert(newV); + + } else if (!visited.contains(next) && !stack.contains(next)) { + // If the adjacent is not fit for merge and it wasn't visited by the outermost + // loop, we add it to the stack. + stack.push(next); + } + } + } + + return true; +} + +/*! + \internal + + One iteration of the simplification algorithm. Returns true if another iteration is needed. + + The algorithm walks the graph in depth-first order, and only collects vertices that has two + edges connected to it. If the vertex does not have two edges or if it is a layout edge, it + will take all the previously collected vertices and try to create a simplified sequential + anchor representing all the previously collected vertices. Once the simplified anchor is + inserted, the collected list is cleared in order to find the next sequence to simplify. + + Note that there are some catches to this that are not covered by the above explanation, see + the function comments for more details. +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutPrivate::Orientation orientation, + bool *feasible) +{ + Q_Q(QGraphicsAnchorLayout); + Graph &g = graph[orientation]; + + QSet visited; + QStack > stack; + stack.push(qMakePair(static_cast(0), layoutFirstVertex[orientation])); + QVector candidates; + + // Walk depth-first, in the stack we store start of the candidate sequence (beforeSequence) + // and the vertex to be visited. + while (!stack.isEmpty()) { + QPair pair = stack.pop(); + AnchorVertex *beforeSequence = pair.first; + AnchorVertex *v = pair.second; + + // The basic idea is to determine whether we found an end of sequence, + // if that's the case, we stop adding vertices to the candidate list + // and do a simplification step. + // + // A vertex can trigger an end of sequence if + // (a) it is a layout vertex, we don't simplify away the layout vertices; + // (b) it does not have exactly 2 adjacents; + // (c) its next adjacent is already visited (a cycle in the graph). + // (d) the next anchor is a center anchor. + + const QList &adjacents = g.adjacentVertices(v); + const bool isLayoutVertex = v->m_item == q; + AnchorVertex *afterSequence = v; + bool endOfSequence = false; + + // + // Identify the end cases. + // + + // Identifies cases (a) and (b) + endOfSequence = isLayoutVertex || adjacents.count() != 2; + + if (!endOfSequence) { + // This is a tricky part. We peek at the next vertex to find out whether + // + // - we already visited the next vertex (c); + // - the next anchor is a center (d). + // + // Those are needed to identify the remaining end of sequence cases. Note that unlike + // (a) and (b), we preempt the end of sequence by looking into the next vertex. + + // Peek at the next vertex + AnchorVertex *after; + if (candidates.isEmpty()) + after = (beforeSequence == adjacents.last() ? adjacents.first() : adjacents.last()); + else + after = (candidates.last() == adjacents.last() ? adjacents.first() : adjacents.last()); + + // ### At this point we assumed that candidates will not contain 'after', this may not hold + // when simplifying FLOATing anchors. + Q_ASSERT(!candidates.contains(after)); + + const AnchorData *data = g.edgeData(v, after); + Q_ASSERT(data); + const bool cycleFound = visited.contains(after); + + // Now cases (c) and (d)... + endOfSequence = cycleFound || data->isCenterAnchor; + + if (!endOfSequence) { + // If it's not an end of sequence, then the vertex didn't trigger neither of the + // previously three cases, so it can be added to the candidates list. + candidates.append(v); + } else if (cycleFound && (beforeSequence != after)) { + afterSequence = after; + candidates.append(v); + } + } + + // + // Add next non-visited vertices to the stack. + // + for (int i = 0; i < adjacents.count(); ++i) { + AnchorVertex *next = adjacents.at(i); + if (visited.contains(next)) + continue; + + // If current vertex is an end of sequence, and it'll reset the candidates list. So + // the next vertices will build candidates lists with the current vertex as 'before' + // vertex. If it's not an end of sequence, we keep the original 'before' vertex, + // since we are keeping the candidates list. + if (endOfSequence) + stack.push(qMakePair(v, next)); + else + stack.push(qMakePair(beforeSequence, next)); + } + + visited.insert(v); + + if (!endOfSequence || candidates.isEmpty()) + continue; + + // + // Create a sequence for (beforeSequence, candidates, afterSequence). + // + + // One restriction we have is to not simplify half of an anchor and let the other half + // unsimplified. So we remove center edges before and after the sequence. + const AnchorData *firstAnchor = g.edgeData(beforeSequence, candidates.first()); + if (firstAnchor->isCenterAnchor) { + beforeSequence = candidates.first(); + candidates.remove(0); + + // If there's not candidates to be simplified, leave. + if (candidates.isEmpty()) + continue; + } + + const AnchorData *lastAnchor = g.edgeData(candidates.last(), afterSequence); + if (lastAnchor->isCenterAnchor) { + afterSequence = candidates.last(); + candidates.remove(candidates.count() - 1); + + if (candidates.isEmpty()) + continue; + } + + // + // Add the sequence to the graph. + // + + AnchorData *sequence = createSequence(&g, beforeSequence, candidates, afterSequence); + + // If 'beforeSequence' and 'afterSequence' already had an anchor between them, we'll + // create a parallel anchor between the new sequence and the old anchor. + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(sequence, &newFeasible); + + if (!newFeasible) { + *feasible = false; + return false; + } + + // When a new parallel anchor is create in the graph, we finish the iteration and return + // true to indicate a new iteration is needed. This happens because a parallel anchor + // changes the number of adjacents one vertex has, possibly opening up oportunities for + // building candidate lists (when adjacents == 2). + if (newAnchor != sequence) + return true; + + // If there was no parallel simplification, we'll keep walking the graph. So we clear the + // candidates list to start again. + candidates.clear(); + } + + return false; +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedAnchor(AnchorData *edge) +{ +#if 0 + static const char *anchortypes[] = {"Normal", + "Sequential", + "Parallel"}; + qDebug("Restoring %s edge.", anchortypes[int(edge->type)]); +#endif + + Graph &g = graph[edge->orientation]; + + if (edge->type == AnchorData::Normal) { + g.createEdge(edge->from, edge->to, edge); + + } else if (edge->type == AnchorData::Sequential) { + SequentialAnchorData *sequence = static_cast(edge); + + for (int i = 0; i < sequence->m_edges.count(); ++i) { + AnchorData *data = sequence->m_edges.at(i); + restoreSimplifiedAnchor(data); + } + + delete sequence; + + } else if (edge->type == AnchorData::Parallel) { + + // Skip parallel anchors that were created by vertex simplification, they will be processed + // later, when restoring vertex simplification. + // ### we could improve this check bit having a bit inside 'edge' + if (anchorsFromSimplifiedVertices[edge->orientation].contains(edge)) + return; + + ParallelAnchorData* parallel = static_cast(edge); + restoreSimplifiedConstraints(parallel); + + // ### Because of the way parallel anchors are created in the anchor simplification + // algorithm, we know that one of these will be a sequence, so it'll be safe if the other + // anchor create an edge between the same vertices as the parallel. + Q_ASSERT(parallel->firstEdge->type == AnchorData::Sequential + || parallel->secondEdge->type == AnchorData::Sequential); + restoreSimplifiedAnchor(parallel->firstEdge); + restoreSimplifiedAnchor(parallel->secondEdge); + + delete parallel; + } +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedConstraints(ParallelAnchorData *parallel) +{ + if (!parallel->isCenterAnchor) + return; + + for (int i = 0; i < parallel->m_firstConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_firstConstraints.at(i); + qreal v = c->variables[parallel]; + c->variables.remove(parallel); + c->variables.insert(parallel->firstEdge, v); + } + + // When restoring, we might have to revert constraints back. See comments on + // addAnchorMaybeParallel(). + const bool needsReverse = !parallel->secondForward(); + + for (int i = 0; i < parallel->m_secondConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_secondConstraints.at(i); + qreal v = c->variables[parallel]; + if (needsReverse) + v *= -1; + c->variables.remove(parallel); + c->variables.insert(parallel->secondEdge, v); + } +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedGraph(Orientation orientation) +{ +#if 0 + qDebug("Restoring Simplified Graph for %s", + orientation == Horizontal ? "Horizontal" : "Vertical"); +#endif + + // Restore anchor simplification + Graph &g = graph[orientation]; + QList > connections = g.connections(); + for (int i = 0; i < connections.count(); ++i) { + AnchorVertex *v1 = connections.at(i).first; + AnchorVertex *v2 = connections.at(i).second; + AnchorData *edge = g.edgeData(v1, v2); + + // We restore only sequential anchors and parallels that were not created by + // vertex simplification. + if (edge->type == AnchorData::Sequential + || (edge->type == AnchorData::Parallel && + !anchorsFromSimplifiedVertices[orientation].contains(edge))) { + + g.takeEdge(v1, v2); + restoreSimplifiedAnchor(edge); + } + } + + restoreVertices(orientation); +} + +void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + Graph &g = graph[orientation]; + QList &toRestore = simplifiedVertices[orientation]; + + // Since we keep a list of parallel anchors and vertices that were created during vertex + // simplification, we can now iterate on those lists instead of traversing the graph + // recursively. + + // First, restore the constraints changed when we created parallel anchors. Note that this + // works at this point because the constraints doesn't depend on vertex information and at + // this point it's always safe to identify whether the second child is forward or backwards. + // In the next step, we'll change the anchors vertices so that would not be possible anymore. + QList ¶llelAnchors = anchorsFromSimplifiedVertices[orientation]; + + for (int i = parallelAnchors.count() - 1; i >= 0; --i) { + ParallelAnchorData *parallel = static_cast(parallelAnchors.at(i)); + restoreSimplifiedConstraints(parallel); + } + + // Then, we will restore the vertices in the inverse order of creation, this way we ensure that + // the vertex being restored was not wrapped by another simplification. + for (int i = toRestore.count() - 1; i >= 0; --i) { + AnchorVertexPair *pair = toRestore.at(i); + QList adjacents = g.adjacentVertices(pair); + + // Restore the removed edge, this will also restore both vertices 'first' and 'second' to + // the graph structure. + AnchorVertex *first = pair->m_first; + AnchorVertex *second = pair->m_second; + g.createEdge(first, second, pair->m_removedAnchor); + + // Restore the anchors for the first child vertex + for (int j = 0; j < pair->m_firstAnchors.count(); ++j) { + AnchorData *ad = pair->m_firstAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, first); + g.createEdge(ad->from, ad->to, ad); + } + + // Restore the anchors for the second child vertex + for (int j = 0; j < pair->m_secondAnchors.count(); ++j) { + AnchorData *ad = pair->m_secondAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, second); + g.createEdge(ad->from, ad->to, ad); + } + + for (int j = 0; j < adjacents.count(); ++j) { + g.takeEdge(pair, adjacents.at(j)); + } + + // The pair simplified a layout vertex, so place back the correct vertex in the variable + // that track layout vertices + if (pair->m_item == q) { + AnchorVertex *layoutVertex = first->m_item == q ? first : second; + Q_ASSERT(layoutVertex->m_item == q); + changeLayoutVertex(orientation, pair, layoutVertex); + } + + delete pair; + } + qDeleteAll(parallelAnchors); + parallelAnchors.clear(); + toRestore.clear(); +} + +QGraphicsAnchorLayoutPrivate::Orientation +QGraphicsAnchorLayoutPrivate::edgeOrientation(Qt::AnchorPoint edge) +{ + return edge > Qt::AnchorRight ? Vertical : Horizontal; +} + +/*! + \internal + + Create internal anchors to connect the layout edges (Left to Right and + Top to Bottom). + + These anchors doesn't have size restrictions, that will be enforced by + other anchors and items in the layout. +*/ +void QGraphicsAnchorLayoutPrivate::createLayoutEdges() +{ + Q_Q(QGraphicsAnchorLayout); + QGraphicsLayoutItem *layout = q; + + // Horizontal + AnchorData *data = new AnchorData; + addAnchor_helper(layout, Qt::AnchorLeft, layout, + Qt::AnchorRight, data); + data->maxSize = QWIDGETSIZE_MAX; + + // Save a reference to layout vertices + layoutFirstVertex[Horizontal] = internalVertex(layout, Qt::AnchorLeft); + layoutCentralVertex[Horizontal] = 0; + layoutLastVertex[Horizontal] = internalVertex(layout, Qt::AnchorRight); + + // Vertical + data = new AnchorData; + addAnchor_helper(layout, Qt::AnchorTop, layout, + Qt::AnchorBottom, data); + data->maxSize = QWIDGETSIZE_MAX; + + // Save a reference to layout vertices + layoutFirstVertex[Vertical] = internalVertex(layout, Qt::AnchorTop); + layoutCentralVertex[Vertical] = 0; + layoutLastVertex[Vertical] = internalVertex(layout, Qt::AnchorBottom); +} + +void QGraphicsAnchorLayoutPrivate::deleteLayoutEdges() +{ + Q_Q(QGraphicsAnchorLayout); + + Q_ASSERT(!internalVertex(q, Qt::AnchorHorizontalCenter)); + Q_ASSERT(!internalVertex(q, Qt::AnchorVerticalCenter)); + + removeAnchor_helper(internalVertex(q, Qt::AnchorLeft), + internalVertex(q, Qt::AnchorRight)); + removeAnchor_helper(internalVertex(q, Qt::AnchorTop), + internalVertex(q, Qt::AnchorBottom)); +} + +void QGraphicsAnchorLayoutPrivate::createItemEdges(QGraphicsLayoutItem *item) +{ + items.append(item); + + // Create horizontal and vertical internal anchors for the item and + // refresh its size hint / policy values. + AnchorData *data = new AnchorData; + addAnchor_helper(item, Qt::AnchorLeft, item, Qt::AnchorRight, data); + data->refreshSizeHints(); + + data = new AnchorData; + addAnchor_helper(item, Qt::AnchorTop, item, Qt::AnchorBottom, data); + data->refreshSizeHints(); +} + +/*! + \internal + + By default, each item in the layout is represented internally as + a single anchor in each direction. For instance, from Left to Right. + + However, to support anchorage of items to the center of items, we + must split this internal anchor into two half-anchors. From Left + to Center and then from Center to Right, with the restriction that + these anchors must have the same time at all times. +*/ +void QGraphicsAnchorLayoutPrivate::createCenterAnchors( + QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge) +{ + Q_Q(QGraphicsAnchorLayout); + + Orientation orientation; + switch (centerEdge) { + case Qt::AnchorHorizontalCenter: + orientation = Horizontal; + break; + case Qt::AnchorVerticalCenter: + orientation = Vertical; + break; + default: + // Don't create center edges unless needed + return; + } + + // Check if vertex already exists + if (internalVertex(item, centerEdge)) + return; + + // Orientation code + Qt::AnchorPoint firstEdge; + Qt::AnchorPoint lastEdge; + + if (orientation == Horizontal) { + firstEdge = Qt::AnchorLeft; + lastEdge = Qt::AnchorRight; + } else { + firstEdge = Qt::AnchorTop; + lastEdge = Qt::AnchorBottom; + } + + AnchorVertex *first = internalVertex(item, firstEdge); + AnchorVertex *last = internalVertex(item, lastEdge); + Q_ASSERT(first && last); + + // Create new anchors + QSimplexConstraint *c = new QSimplexConstraint; + + AnchorData *data = new AnchorData; + c->variables.insert(data, 1.0); + addAnchor_helper(item, firstEdge, item, centerEdge, data); + data->isCenterAnchor = true; + data->dependency = AnchorData::Master; + data->refreshSizeHints(); + + data = new AnchorData; + c->variables.insert(data, -1.0); + addAnchor_helper(item, centerEdge, item, lastEdge, data); + data->isCenterAnchor = true; + data->dependency = AnchorData::Slave; + data->refreshSizeHints(); + + itemCenterConstraints[orientation].append(c); + + // Remove old one + removeAnchor_helper(first, last); + + if (item == q) { + layoutCentralVertex[orientation] = internalVertex(q, centerEdge); + } +} + +void QGraphicsAnchorLayoutPrivate::removeCenterAnchors( + QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, + bool substitute) +{ + Q_Q(QGraphicsAnchorLayout); + + Orientation orientation; + switch (centerEdge) { + case Qt::AnchorHorizontalCenter: + orientation = Horizontal; + break; + case Qt::AnchorVerticalCenter: + orientation = Vertical; + break; + default: + // Don't remove edges that not the center ones + return; + } + + // Orientation code + Qt::AnchorPoint firstEdge; + Qt::AnchorPoint lastEdge; + + if (orientation == Horizontal) { + firstEdge = Qt::AnchorLeft; + lastEdge = Qt::AnchorRight; + } else { + firstEdge = Qt::AnchorTop; + lastEdge = Qt::AnchorBottom; + } + + AnchorVertex *center = internalVertex(item, centerEdge); + if (!center) + return; + AnchorVertex *first = internalVertex(item, firstEdge); + + Q_ASSERT(first); + Q_ASSERT(center); + + Graph &g = graph[orientation]; + + + AnchorData *oldData = g.edgeData(first, center); + // Remove center constraint + for (int i = itemCenterConstraints[orientation].count() - 1; i >= 0; --i) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(oldData)) { + delete itemCenterConstraints[orientation].takeAt(i); + break; + } + } + + if (substitute) { + // Create the new anchor that should substitute the left-center-right anchors. + AnchorData *data = new AnchorData; + addAnchor_helper(item, firstEdge, item, lastEdge, data); + data->refreshSizeHints(); + + // Remove old anchors + removeAnchor_helper(first, center); + removeAnchor_helper(center, internalVertex(item, lastEdge)); + + } else { + // this is only called from removeAnchors() + // first, remove all non-internal anchors + QList adjacents = g.adjacentVertices(center); + for (int i = 0; i < adjacents.count(); ++i) { + AnchorVertex *v = adjacents.at(i); + if (v->m_item != item) { + removeAnchor_helper(center, internalVertex(v->m_item, v->m_edge)); + } + } + // when all non-internal anchors is removed it will automatically merge the + // center anchor into a left-right (or top-bottom) anchor. We must also delete that. + // by this time, the center vertex is deleted and merged into a non-centered internal anchor + removeAnchor_helper(first, internalVertex(item, lastEdge)); + } + + if (item == q) { + layoutCentralVertex[orientation] = 0; + } +} + + +void QGraphicsAnchorLayoutPrivate::removeCenterConstraints(QGraphicsLayoutItem *item, + Orientation orientation) +{ + // Remove the item center constraints associated to this item + // ### This is a temporary solution. We should probably use a better + // data structure to hold items and/or their associated constraints + // so that we can remove those easily + + AnchorVertex *first = internalVertex(item, orientation == Horizontal ? + Qt::AnchorLeft : + Qt::AnchorTop); + AnchorVertex *center = internalVertex(item, orientation == Horizontal ? + Qt::AnchorHorizontalCenter : + Qt::AnchorVerticalCenter); + + // Skip if no center constraints exist + if (!center) + return; + + Q_ASSERT(first); + AnchorData *internalAnchor = graph[orientation].edgeData(first, center); + + // Look for our anchor in all item center constraints, then remove it + for (int i = 0; i < itemCenterConstraints[orientation].size(); ++i) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(internalAnchor)) { + delete itemCenterConstraints[orientation].takeAt(i); + break; + } + } +} + +/*! + * \internal + * Implements the high level "addAnchor" feature. Called by the public API + * addAnchor method. + * + * The optional \a spacing argument defines the size of the anchor. If not provided, + * the anchor size is either 0 or not-set, depending on type of anchor created (see + * matrix below). + * + * All anchors that remain with size not-set will assume the standard spacing, + * set either by the layout style or through the "setSpacing" layout API. + */ +QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::addAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + qreal *spacing) +{ + Q_Q(QGraphicsAnchorLayout); + if ((firstItem == 0) || (secondItem == 0)) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor NULL items"); + return 0; + } + + if (firstItem == secondItem) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor the item to itself"); + return 0; + } + + if (edgeOrientation(secondEdge) != edgeOrientation(firstEdge)) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "Cannot anchor edges of different orientations"); + return 0; + } + + const QGraphicsLayoutItem *parentWidget = q->parentLayoutItem(); + if (firstItem == parentWidget || secondItem == parentWidget) { + qWarning("QGraphicsAnchorLayout::addAnchor(): " + "You cannot add the parent of the layout to the layout."); + return 0; + } + + // In QGraphicsAnchorLayout, items are represented in its internal + // graph as four anchors that connect: + // - Left -> HCenter + // - HCenter-> Right + // - Top -> VCenter + // - VCenter -> Bottom + + // Ensure that the internal anchors have been created for both items. + if (firstItem != q && !items.contains(firstItem)) { + createItemEdges(firstItem); + addChildLayoutItem(firstItem); + } + if (secondItem != q && !items.contains(secondItem)) { + createItemEdges(secondItem); + addChildLayoutItem(secondItem); + } + + // Create center edges if needed + createCenterAnchors(firstItem, firstEdge); + createCenterAnchors(secondItem, secondEdge); + + // Use heuristics to find out what the user meant with this anchor. + correctEdgeDirection(firstItem, firstEdge, secondItem, secondEdge); + + AnchorData *data = new AnchorData; + QGraphicsAnchor *graphicsAnchor = acquireGraphicsAnchor(data); + + addAnchor_helper(firstItem, firstEdge, secondItem, secondEdge, data); + + if (spacing) { + graphicsAnchor->setSpacing(*spacing); + } else { + // If firstItem or secondItem is the layout itself, the spacing will default to 0. + // Otherwise, the following matrix is used (questionmark means that the spacing + // is queried from the style): + // from + // to Left HCenter Right + // Left 0 0 ? + // HCenter 0 0 0 + // Right ? 0 0 + if (firstItem == q + || secondItem == q + || pickEdge(firstEdge, Horizontal) == Qt::AnchorHorizontalCenter + || oppositeEdge(firstEdge) != secondEdge) { + graphicsAnchor->setSpacing(0); + } else { + graphicsAnchor->unsetSpacing(); + } + } + + return graphicsAnchor; +} + +/* + \internal + + This method adds an AnchorData to the internal graph. It is responsible for doing + the boilerplate part of such task. + + If another AnchorData exists between the mentioned vertices, it is deleted and + the new one is inserted. +*/ +void QGraphicsAnchorLayoutPrivate::addAnchor_helper(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + AnchorData *data) +{ + Q_Q(QGraphicsAnchorLayout); + + const Orientation orientation = edgeOrientation(firstEdge); + + // Create or increase the reference count for the related vertices. + AnchorVertex *v1 = addInternalVertex(firstItem, firstEdge); + AnchorVertex *v2 = addInternalVertex(secondItem, secondEdge); + + // Remove previous anchor + if (graph[orientation].edgeData(v1, v2)) { + removeAnchor_helper(v1, v2); + } + + // If its an internal anchor, set the associated item + if (firstItem == secondItem) + data->item = firstItem; + + data->orientation = orientation; + + // Create a bi-directional edge in the sense it can be transversed both + // from v1 or v2. "data" however is shared between the two references + // so we still know that the anchor direction is from 1 to 2. + data->from = v1; + data->to = v2; +#ifdef QT_DEBUG + data->name = QString::fromAscii("%1 --to--> %2").arg(v1->toString()).arg(v2->toString()); +#endif + // ### bit to track internal anchors, since inside AnchorData methods + // we don't have access to the 'q' pointer. + data->isLayoutAnchor = (data->item == q); + + graph[orientation].createEdge(v1, v2, data); +} + +QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::getAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge) +{ + // Do not expose internal anchors + if (firstItem == secondItem) + return 0; + + const Orientation orientation = edgeOrientation(firstEdge); + AnchorVertex *v1 = internalVertex(firstItem, firstEdge); + AnchorVertex *v2 = internalVertex(secondItem, secondEdge); + + QGraphicsAnchor *graphicsAnchor = 0; + + AnchorData *data = graph[orientation].edgeData(v1, v2); + if (data) { + // We could use "acquireGraphicsAnchor" here, but to avoid a regression where + // an internal anchor was wrongly exposed, I want to ensure no new + // QGraphicsAnchor instances are created by this call. + // This assumption must hold because anchors are either user-created (and already + // have their public object created), or they are internal (and must not reach + // this point). + Q_ASSERT(data->graphicsAnchor); + graphicsAnchor = data->graphicsAnchor; + } + return graphicsAnchor; +} + +/*! + * \internal + * + * Implements the high level "removeAnchor" feature. Called by + * the QAnchorData destructor. + */ +void QGraphicsAnchorLayoutPrivate::removeAnchor(AnchorVertex *firstVertex, + AnchorVertex *secondVertex) +{ + Q_Q(QGraphicsAnchorLayout); + + // Save references to items while it's safe to assume the vertices exist + QGraphicsLayoutItem *firstItem = firstVertex->m_item; + QGraphicsLayoutItem *secondItem = secondVertex->m_item; + + // Delete the anchor (may trigger deletion of center vertices) + removeAnchor_helper(firstVertex, secondVertex); + + // Ensure no dangling pointer is left behind + firstVertex = secondVertex = 0; + + // Checking if the item stays in the layout or not + bool keepFirstItem = false; + bool keepSecondItem = false; + + QPair v; + int refcount = -1; + + if (firstItem != q) { + for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) { + v = m_vertexList.value(qMakePair(firstItem, static_cast(i))); + if (v.first) { + if (i == Qt::AnchorHorizontalCenter || i == Qt::AnchorVerticalCenter) + refcount = 2; + else + refcount = 1; + + if (v.second > refcount) { + keepFirstItem = true; + break; + } + } + } + } else + keepFirstItem = true; + + if (secondItem != q) { + for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) { + v = m_vertexList.value(qMakePair(secondItem, static_cast(i))); + if (v.first) { + if (i == Qt::AnchorHorizontalCenter || i == Qt::AnchorVerticalCenter) + refcount = 2; + else + refcount = 1; + + if (v.second > refcount) { + keepSecondItem = true; + break; + } + } + } + } else + keepSecondItem = true; + + if (!keepFirstItem) + q->removeAt(items.indexOf(firstItem)); + + if (!keepSecondItem) + q->removeAt(items.indexOf(secondItem)); + + // Removing anchors invalidates the layout + q->invalidate(); +} + +/* + \internal + + Implements the low level "removeAnchor" feature. Called by + private methods. +*/ +void QGraphicsAnchorLayoutPrivate::removeAnchor_helper(AnchorVertex *v1, AnchorVertex *v2) +{ + Q_ASSERT(v1 && v2); + + // Remove edge from graph + const Orientation o = edgeOrientation(v1->m_edge); + graph[o].removeEdge(v1, v2); + + // Decrease vertices reference count (may trigger a deletion) + removeInternalVertex(v1->m_item, v1->m_edge); + removeInternalVertex(v2->m_item, v2->m_edge); +} + +AnchorVertex *QGraphicsAnchorLayoutPrivate::addInternalVertex(QGraphicsLayoutItem *item, + Qt::AnchorPoint edge) +{ + QPair pair(item, edge); + QPair v = m_vertexList.value(pair); + + if (!v.first) { + Q_ASSERT(v.second == 0); + v.first = new AnchorVertex(item, edge); + } + v.second++; + m_vertexList.insert(pair, v); + return v.first; +} + +/** + * \internal + * + * returns the AnchorVertex that was dereferenced, also when it was removed. + * returns 0 if it did not exist. + */ +void QGraphicsAnchorLayoutPrivate::removeInternalVertex(QGraphicsLayoutItem *item, + Qt::AnchorPoint edge) +{ + QPair pair(item, edge); + QPair v = m_vertexList.value(pair); + + if (!v.first) { + qWarning("This item with this edge is not in the graph"); + return; + } + + v.second--; + if (v.second == 0) { + // Remove reference and delete vertex + m_vertexList.remove(pair); + delete v.first; + } else { + // Update reference count + m_vertexList.insert(pair, v); + + if ((v.second == 2) && + ((edge == Qt::AnchorHorizontalCenter) || + (edge == Qt::AnchorVerticalCenter))) { + removeCenterAnchors(item, edge, true); + } + } +} + +void QGraphicsAnchorLayoutPrivate::removeVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge) +{ + if (AnchorVertex *v = internalVertex(item, edge)) { + Graph &g = graph[edgeOrientation(edge)]; + const QList allVertices = graph[edgeOrientation(edge)].adjacentVertices(v); + AnchorVertex *v2; + foreach (v2, allVertices) { + g.removeEdge(v, v2); + removeInternalVertex(item, edge); + removeInternalVertex(v2->m_item, v2->m_edge); + } + } +} + +void QGraphicsAnchorLayoutPrivate::removeAnchors(QGraphicsLayoutItem *item) +{ + // remove the center anchor first!! + removeCenterAnchors(item, Qt::AnchorHorizontalCenter, false); + removeVertex(item, Qt::AnchorLeft); + removeVertex(item, Qt::AnchorRight); + + removeCenterAnchors(item, Qt::AnchorVerticalCenter, false); + removeVertex(item, Qt::AnchorTop); + removeVertex(item, Qt::AnchorBottom); +} + +/*! + \internal + + Use heuristics to determine the correct orientation of a given anchor. + + After API discussions, we decided we would like expressions like + anchor(A, Left, B, Right) to mean the same as anchor(B, Right, A, Left). + The problem with this is that anchors could become ambiguous, for + instance, what does the anchor A, B of size X mean? + + "pos(B) = pos(A) + X" or "pos(A) = pos(B) + X" ? + + To keep the API user friendly and at the same time, keep our algorithm + deterministic, we use an heuristic to determine a direction for each + added anchor and then keep it. The heuristic is based on the fact + that people usually avoid overlapping items, therefore: + + "A, RIGHT to B, LEFT" means that B is to the LEFT of A. + "B, LEFT to A, RIGHT" is corrected to the above anchor. + + Special correction is also applied when one of the items is the + layout. We handle Layout Left as if it was another items's Right + and Layout Right as another item's Left. +*/ +void QGraphicsAnchorLayoutPrivate::correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + Qt::AnchorPoint &firstEdge, + QGraphicsLayoutItem *&secondItem, + Qt::AnchorPoint &secondEdge) +{ + Q_Q(QGraphicsAnchorLayout); + + if ((firstItem != q) && (secondItem != q)) { + // If connection is between widgets (not the layout itself) + // Ensure that "right-edges" sit to the left of "left-edges". + if (firstEdge < secondEdge) { + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } + } else if (firstItem == q) { + // If connection involves the right or bottom of a layout, ensure + // the layout is the second item. + if ((firstEdge == Qt::AnchorRight) || (firstEdge == Qt::AnchorBottom)) { + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } + } else if ((secondEdge != Qt::AnchorRight) && (secondEdge != Qt::AnchorBottom)) { + // If connection involves the left, center or top of layout, ensure + // the layout is the first item. + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } +} + +QLayoutStyleInfo &QGraphicsAnchorLayoutPrivate::styleInfo() const +{ + if (styleInfoDirty) { + Q_Q(const QGraphicsAnchorLayout); + //### Fix this if QGV ever gets support for Metal style or different Aqua sizes. + QWidget *wid = 0; + + QGraphicsLayoutItem *parent = q->parentLayoutItem(); + while (parent && parent->isLayout()) { + parent = parent->parentLayoutItem(); + } + QGraphicsWidget *w = 0; + if (parent) { + QGraphicsItem *parentItem = parent->graphicsItem(); + if (parentItem && parentItem->isWidget()) + w = static_cast(parentItem); + } + + QStyle *style = w ? w->style() : QApplication::style(); + cachedStyleInfo = QLayoutStyleInfo(style, wid); + cachedStyleInfo.setDefaultSpacing(Qt::Horizontal, spacings[0]); + cachedStyleInfo.setDefaultSpacing(Qt::Vertical, spacings[1]); + + styleInfoDirty = false; + } + return cachedStyleInfo; +} + +/*! + \internal + + Called on activation. Uses Linear Programming to define minimum, preferred + and maximum sizes for the layout. Also calculates the sizes that each item + should assume when the layout is in one of such situations. +*/ +void QGraphicsAnchorLayoutPrivate::calculateGraphs() +{ + if (!calculateGraphCacheDirty) + return; + calculateGraphs(Horizontal); + calculateGraphs(Vertical); + calculateGraphCacheDirty = false; +} + +// ### Maybe getGraphParts could return the variables when traversing, at least +// for trunk... +QList getVariables(QList constraints) +{ + QSet variableSet; + for (int i = 0; i < constraints.count(); ++i) { + const QSimplexConstraint *c = constraints.at(i); + foreach (QSimplexVariable *var, c->variables.keys()) { + variableSet += static_cast(var); + } + } + return variableSet.toList(); +} + +/*! + \internal + + Calculate graphs is the method that puts together all the helper routines + so that the AnchorLayout can calculate the sizes of each item. + + In a nutshell it should do: + + 1) Refresh anchor nominal sizes, that is, the size that each anchor would + have if no other restrictions applied. This is done by quering the + layout style and the sizeHints of the items belonging to the layout. + + 2) Simplify the graph by grouping together parallel and sequential anchors + into "group anchors". These have equivalent minimum, preferred and maximum + sizeHints as the anchors they replace. + + 3) Check if we got to a trivial case. In some cases, the whole graph can be + simplified into a single anchor. If so, use this information. If not, + then call the Simplex solver to calculate the anchors sizes. + + 4) Once the root anchors had its sizes calculated, propagate that to the + anchors they represent. +*/ +void QGraphicsAnchorLayoutPrivate::calculateGraphs( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + lastCalculationUsedSimplex[orientation] = false; +#endif + + static bool simplificationEnabled = qgetenv("QT_ANCHORLAYOUT_NO_SIMPLIFICATION").isEmpty(); + + // Reset the nominal sizes of each anchor based on the current item sizes + refreshAllSizeHints(orientation); + + // Simplify the graph + if (simplificationEnabled && !simplifyGraph(orientation)) { + qWarning("QGraphicsAnchorLayout: anchor setup is not feasible."); + graphHasConflicts[orientation] = true; + return; + } + + // Traverse all graph edges and store the possible paths to each vertex + findPaths(orientation); + + // From the paths calculated above, extract the constraints that the current + // anchor setup impose, to our Linear Programming problem. + constraintsFromPaths(orientation); + + // Split the constraints and anchors into groups that should be fed to the + // simplex solver independently. Currently we find two groups: + // + // 1) The "trunk", that is, the set of anchors (items) that are connected + // to the two opposite sides of our layout, and thus need to stretch in + // order to fit in the current layout size. + // + // 2) The floating or semi-floating anchors (items) that are those which + // are connected to only one (or none) of the layout sides, thus are not + // influenced by the layout size. + QList > parts = getGraphParts(orientation); + + // Now run the simplex solver to calculate Minimum, Preferred and Maximum sizes + // of the "trunk" set of constraints and variables. + // ### does trunk always exist? empty = trunk is the layout left->center->right + QList trunkConstraints = parts.at(0); + QList trunkVariables = getVariables(trunkConstraints); + + // For minimum and maximum, use the path between the two layout sides as the + // objective function. + AnchorVertex *v = layoutLastVertex[orientation]; + GraphPath trunkPath = graphPaths[orientation].value(v); + + bool feasible = calculateTrunk(orientation, trunkPath, trunkConstraints, trunkVariables); + + // For the other parts that not the trunk, solve only for the preferred size + // that is the size they will remain at, since they are not stretched by the + // layout. + + // Skipping the first (trunk) + for (int i = 1; i < parts.count(); ++i) { + if (!feasible) + break; + + QList partConstraints = parts.at(i); + QList partVariables = getVariables(partConstraints); + Q_ASSERT(!partVariables.isEmpty()); + feasible &= calculateNonTrunk(partConstraints, partVariables); + } + + // Propagate the new sizes down the simplified graph, ie. tell the + // group anchors to set their children anchors sizes. + updateAnchorSizes(orientation); + + graphHasConflicts[orientation] = !feasible; + + // Clean up our data structures. They are not needed anymore since + // distribution uses just interpolation. + qDeleteAll(constraints[orientation]); + constraints[orientation].clear(); + graphPaths[orientation].clear(); // ### + + if (simplificationEnabled) + restoreSimplifiedGraph(orientation); +} + +/*! + \internal + + Shift all the constraints by a certain amount. This allows us to deal with negative values in + the linear program if they are bounded by a certain limit. Functions should be careful to + call it again with a negative amount, to shift the constraints back. +*/ +static void shiftConstraints(const QList &constraints, qreal amount) +{ + for (int i = 0; i < constraints.count(); ++i) { + QSimplexConstraint *c = constraints.at(i); + qreal multiplier = 0; + foreach (qreal v, c->variables.values()) { + multiplier += v; + } + c->constant += multiplier * amount; + } +} + +/*! + \internal + + Calculate the sizes for all anchors which are part of the trunk. This works + on top of a (possibly) simplified graph. +*/ +bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const GraphPath &path, + const QList &constraints, + const QList &variables) +{ + bool feasible = true; + bool needsSimplex = !constraints.isEmpty(); + +#if 0 + qDebug("Simplex %s for trunk of %s", needsSimplex ? "used" : "NOT used", + orientation == Horizontal ? "Horizontal" : "Vertical"); +#endif + + if (needsSimplex) { + + QList sizeHintConstraints = constraintsFromSizeHints(variables); + QList allConstraints = constraints + sizeHintConstraints; + + shiftConstraints(allConstraints, g_offset); + + // Solve min and max size hints + qreal min, max; + feasible = solveMinMax(allConstraints, path, &min, &max); + + if (feasible) { + solvePreferred(constraints, variables); + + // Calculate and set the preferred size for the layout, + // from the edge sizes that were calculated above. + qreal pref(0.0); + foreach (const AnchorData *ad, path.positives) { + pref += ad->sizeAtPreferred; + } + foreach (const AnchorData *ad, path.negatives) { + pref -= ad->sizeAtPreferred; + } + + sizeHints[orientation][Qt::MinimumSize] = min; + sizeHints[orientation][Qt::PreferredSize] = pref; + sizeHints[orientation][Qt::MaximumSize] = max; + } + + qDeleteAll(sizeHintConstraints); + shiftConstraints(constraints, -g_offset); + + } else { + // No Simplex is necessary because the path was simplified all the way to a single + // anchor. + Q_ASSERT(path.positives.count() == 1); + Q_ASSERT(path.negatives.count() == 0); + + AnchorData *ad = path.positives.toList()[0]; + ad->sizeAtMinimum = ad->minSize; + ad->sizeAtPreferred = ad->prefSize; + ad->sizeAtMaximum = ad->maxSize; + + sizeHints[orientation][Qt::MinimumSize] = ad->sizeAtMinimum; + sizeHints[orientation][Qt::PreferredSize] = ad->sizeAtPreferred; + sizeHints[orientation][Qt::MaximumSize] = ad->sizeAtMaximum; + } + +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + lastCalculationUsedSimplex[orientation] = needsSimplex; +#endif + + return feasible; +} + +/*! + \internal +*/ +bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList &constraints, + const QList &variables) +{ + shiftConstraints(constraints, g_offset); + bool feasible = solvePreferred(constraints, variables); + + if (feasible) { + // Propagate size at preferred to other sizes. Semi-floats always will be + // in their sizeAtPreferred. + for (int j = 0; j < variables.count(); ++j) { + AnchorData *ad = variables.at(j); + Q_ASSERT(ad); + ad->sizeAtMinimum = ad->sizeAtPreferred; + ad->sizeAtMaximum = ad->sizeAtPreferred; + } + } + + shiftConstraints(constraints, -g_offset); + return feasible; +} + +/*! + \internal + + Traverse the graph refreshing the size hints. Edges will query their associated + item or graphicsAnchor for their size hints. +*/ +void QGraphicsAnchorLayoutPrivate::refreshAllSizeHints(Orientation orientation) +{ + Graph &g = graph[orientation]; + QList > vertices = g.connections(); + + QLayoutStyleInfo styleInf = styleInfo(); + for (int i = 0; i < vertices.count(); ++i) { + AnchorData *data = g.edgeData(vertices.at(i).first, vertices.at(i).second); + data->refreshSizeHints(&styleInf); + } +} + +/*! + \internal + + This method walks the graph using a breadth-first search to find paths + between the root vertex and each vertex on the graph. The edges + directions in each path are considered and they are stored as a + positive edge (left-to-right) or negative edge (right-to-left). + + The list of paths is used later to generate a list of constraints. + */ +void QGraphicsAnchorLayoutPrivate::findPaths(Orientation orientation) +{ + QQueue > queue; + + QSet visited; + + AnchorVertex *root = layoutFirstVertex[orientation]; + + graphPaths[orientation].insert(root, GraphPath()); + + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + while(!queue.isEmpty()) { + QPair pair = queue.dequeue(); + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (visited.contains(edge)) + continue; + + visited.insert(edge); + GraphPath current = graphPaths[orientation].value(pair.first); + + if (edge->from == pair.first) + current.positives.insert(edge); + else + current.negatives.insert(edge); + + graphPaths[orientation].insert(pair.second, current); + + foreach (AnchorVertex *v, + graph[orientation].adjacentVertices(pair.second)) { + queue.enqueue(qMakePair(pair.second, v)); + } + } + + // We will walk through every reachable items (non-float) store them in a temporary set. + // We them create a set of all items and subtract the non-floating items from the set in + // order to get the floating items. The floating items is then stored in m_floatItems + identifyFloatItems(visited, orientation); +} + +/*! + \internal + + Each vertex on the graph that has more than one path to it + represents a contra int to the sizes of the items in these paths. + + This method walks the list of paths to each vertex, generate + the constraints and store them in a list so they can be used later + by the Simplex solver. +*/ +void QGraphicsAnchorLayoutPrivate::constraintsFromPaths(Orientation orientation) +{ + foreach (AnchorVertex *vertex, graphPaths[orientation].uniqueKeys()) + { + int valueCount = graphPaths[orientation].count(vertex); + if (valueCount == 1) + continue; + + QList pathsToVertex = graphPaths[orientation].values(vertex); + for (int i = 1; i < valueCount; ++i) { + constraints[orientation] += \ + pathsToVertex[0].constraint(pathsToVertex.at(i)); + } + } +} + +/*! + \internal +*/ +void QGraphicsAnchorLayoutPrivate::updateAnchorSizes(Orientation orientation) +{ + Graph &g = graph[orientation]; + const QList > &vertices = g.connections(); + + for (int i = 0; i < vertices.count(); ++i) { + AnchorData *ad = g.edgeData(vertices.at(i).first, vertices.at(i).second); + ad->updateChildrenSizes(); + } +} + +/*! + \internal + + Create LP constraints for each anchor based on its minimum and maximum + sizes, as specified in its size hints +*/ +QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHints( + const QList &anchors) +{ + if (anchors.isEmpty()) + return QList(); + + // Look for the layout edge. That can be either the first half in case the + // layout is split in two, or the whole layout anchor. + Orientation orient = Orientation(anchors.first()->orientation); + AnchorData *layoutEdge = 0; + if (layoutCentralVertex[orient]) { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutCentralVertex[orient]); + } else { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutLastVertex[orient]); + } + + // If maxSize is less then "infinite", that means there are other anchors + // grouped together with this one. We can't ignore its maximum value so we + // set back the variable to NULL to prevent the continue condition from being + // satisfied in the loop below. + const qreal expectedMax = layoutCentralVertex[orient] ? QWIDGETSIZE_MAX / 2 : QWIDGETSIZE_MAX; + qreal actualMax; + if (layoutEdge->from == layoutFirstVertex[orient]) { + actualMax = layoutEdge->maxSize; + } else { + actualMax = -layoutEdge->minSize; + } + if (actualMax != expectedMax) { + layoutEdge = 0; + } + + // For each variable, create constraints based on size hints + QList anchorConstraints; + bool unboundedProblem = true; + for (int i = 0; i < anchors.size(); ++i) { + AnchorData *ad = anchors.at(i); + + // Anchors that have their size directly linked to another one don't need constraints + // For exammple, the second half of an item has exactly the same size as the first half + // thus constraining the latter is enough. + if (ad->dependency == AnchorData::Slave) + continue; + + // To use negative variables inside simplex, we shift them so the minimum negative value is + // mapped to zero before solving. To make sure that it works, we need to guarantee that the + // variables are all inside a certain boundary. + qreal boundedMin = qBound(-g_offset, ad->minSize, g_offset); + qreal boundedMax = qBound(-g_offset, ad->maxSize, g_offset); + + if ((boundedMin == boundedMax) || qFuzzyCompare(boundedMin, boundedMax)) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMin; + c->ratio = QSimplexConstraint::Equal; + anchorConstraints += c; + unboundedProblem = false; + } else { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMin; + c->ratio = QSimplexConstraint::MoreOrEqual; + anchorConstraints += c; + + // We avoid adding restrictions to the layout internal anchors. That's + // to prevent unnecessary fair distribution from happening due to this + // artificial restriction. + if (ad == layoutEdge) + continue; + + c = new QSimplexConstraint; + c->variables.insert(ad, 1.0); + c->constant = boundedMax; + c->ratio = QSimplexConstraint::LessOrEqual; + anchorConstraints += c; + unboundedProblem = false; + } + } + + // If no upper boundary restriction was added, add one to avoid unbounded problem + if (unboundedProblem) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(layoutEdge, 1.0); + // The maximum size that the layout can take + c->constant = g_offset; + c->ratio = QSimplexConstraint::LessOrEqual; + anchorConstraints += c; + } + + return anchorConstraints; +} + +/*! + \internal +*/ +QList< QList > +QGraphicsAnchorLayoutPrivate::getGraphParts(Orientation orientation) +{ + Q_ASSERT(layoutFirstVertex[orientation] && layoutLastVertex[orientation]); + + AnchorData *edgeL1 = 0; + AnchorData *edgeL2 = 0; + + // The layout may have a single anchor between Left and Right or two half anchors + // passing through the center + if (layoutCentralVertex[orientation]) { + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutCentralVertex[orientation]); + edgeL2 = graph[orientation].edgeData(layoutCentralVertex[orientation], layoutLastVertex[orientation]); + } else { + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutLastVertex[orientation]); + } + + QLinkedList remainingConstraints; + for (int i = 0; i < constraints[orientation].count(); ++i) { + remainingConstraints += constraints[orientation].at(i); + } + for (int i = 0; i < itemCenterConstraints[orientation].count(); ++i) { + remainingConstraints += itemCenterConstraints[orientation].at(i); + } + + QList trunkConstraints; + QSet trunkVariables; + + trunkVariables += edgeL1; + if (edgeL2) + trunkVariables += edgeL2; + + bool dirty; + do { + dirty = false; + + QLinkedList::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + QSimplexConstraint *c = *it; + bool match = false; + + // Check if this constraint have some overlap with current + // trunk variables... + foreach (QSimplexVariable *ad, trunkVariables) { + if (c->variables.contains(ad)) { + match = true; + break; + } + } + + // If so, we add it to trunk, and erase it from the + // remaining constraints. + if (match) { + trunkConstraints += c; + trunkVariables += QSet::fromList(c->variables.keys()); + it = remainingConstraints.erase(it); + dirty = true; + } else { + // Note that we don't erase the constraint if it's not + // a match, since in a next iteration of a do-while we + // can pass on it again and it will be a match. + // + // For example: if trunk share a variable with + // remainingConstraints[1] and it shares with + // remainingConstraints[0], we need a second iteration + // of the do-while loop to match both. + ++it; + } + } + } while (dirty); + + QList< QList > result; + result += trunkConstraints; + + if (!remainingConstraints.isEmpty()) { + QList nonTrunkConstraints; + QLinkedList::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + nonTrunkConstraints += *it; + ++it; + } + result += nonTrunkConstraints; + } + + return result; +} + +/*! + \internal + + Use all visited Anchors on findPaths() so we can identify non-float Items. +*/ +void QGraphicsAnchorLayoutPrivate::identifyFloatItems(const QSet &visited, Orientation orientation) +{ + QSet nonFloating; + + foreach (const AnchorData *ad, visited) + identifyNonFloatItems_helper(ad, &nonFloating); + + QSet allItems; + foreach (QGraphicsLayoutItem *item, items) + allItems.insert(item); + m_floatItems[orientation] = allItems - nonFloating; +} + + +/*! + \internal + + Given an anchor, if it is an internal anchor and Normal we must mark it's item as non-float. + If the anchor is Sequential or Parallel, we must iterate on its children recursively until we reach + internal anchors (items). +*/ +void QGraphicsAnchorLayoutPrivate::identifyNonFloatItems_helper(const AnchorData *ad, QSet *nonFloatingItemsIdentifiedSoFar) +{ + Q_Q(QGraphicsAnchorLayout); + + switch(ad->type) { + case AnchorData::Normal: + if (ad->item && ad->item != q) + nonFloatingItemsIdentifiedSoFar->insert(ad->item); + break; + case AnchorData::Sequential: + foreach (const AnchorData *d, static_cast(ad)->m_edges) + identifyNonFloatItems_helper(d, nonFloatingItemsIdentifiedSoFar); + break; + case AnchorData::Parallel: + identifyNonFloatItems_helper(static_cast(ad)->firstEdge, nonFloatingItemsIdentifiedSoFar); + identifyNonFloatItems_helper(static_cast(ad)->secondEdge, nonFloatingItemsIdentifiedSoFar); + break; + } +} + +/*! + \internal + + Use the current vertices distance to calculate and set the geometry of + each item. +*/ +void QGraphicsAnchorLayoutPrivate::setItemsGeometries(const QRectF &geom) +{ + Q_Q(QGraphicsAnchorLayout); + AnchorVertex *firstH, *secondH, *firstV, *secondV; + + qreal top; + qreal left; + qreal right; + + q->getContentsMargins(&left, &top, &right, 0); + const Qt::LayoutDirection visualDir = visualDirection(); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + + left += geom.left(); + top += geom.top(); + right = geom.right() - right; + + foreach (QGraphicsLayoutItem *item, items) { + QRectF newGeom; + QSizeF itemPreferredSize = item->effectiveSizeHint(Qt::PreferredSize); + if (m_floatItems[Horizontal].contains(item)) { + newGeom.setLeft(0); + newGeom.setRight(itemPreferredSize.width()); + } else { + firstH = internalVertex(item, Qt::AnchorLeft); + secondH = internalVertex(item, Qt::AnchorRight); + + if (visualDir == Qt::LeftToRight) { + newGeom.setLeft(left + firstH->distance); + newGeom.setRight(left + secondH->distance); + } else { + newGeom.setLeft(right - secondH->distance); + newGeom.setRight(right - firstH->distance); + } + } + + if (m_floatItems[Vertical].contains(item)) { + newGeom.setTop(0); + newGeom.setBottom(itemPreferredSize.height()); + } else { + firstV = internalVertex(item, Qt::AnchorTop); + secondV = internalVertex(item, Qt::AnchorBottom); + + newGeom.setTop(top + firstV->distance); + newGeom.setBottom(top + secondV->distance); + } + + item->setGeometry(newGeom); + } +} + +/*! + \internal + + Calculate the position of each vertex based on the paths to each of + them as well as the current edges sizes. +*/ +void QGraphicsAnchorLayoutPrivate::calculateVertexPositions( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ + QQueue > queue; + QSet visited; + + // Get root vertex + AnchorVertex *root = layoutFirstVertex[orientation]; + + root->distance = 0; + visited.insert(root); + + // Add initial edges to the queue + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + // Do initial calculation required by "interpolateEdge()" + setupEdgesInterpolation(orientation); + + // Traverse the graph and calculate vertex positions + while (!queue.isEmpty()) { + QPair pair = queue.dequeue(); + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (visited.contains(pair.second)) + continue; + + visited.insert(pair.second); + interpolateEdge(pair.first, edge); + + QList adjacents = graph[orientation].adjacentVertices(pair.second); + for (int i = 0; i < adjacents.count(); ++i) { + if (!visited.contains(adjacents.at(i))) + queue.enqueue(qMakePair(pair.second, adjacents.at(i))); + } + } +} + +/*! + \internal + + Calculate interpolation parameters based on current Layout Size. + Must be called once before calling "interpolateEdgeSize()" for + the edges. +*/ +void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( + Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + qreal current; + current = (orientation == Horizontal) ? q->contentsRect().width() : q->contentsRect().height(); + + QPair result; + result = getFactor(current, + sizeHints[orientation][Qt::MinimumSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::MaximumSize]); + + interpolationInterval[orientation] = result.first; + interpolationProgress[orientation] = result.second; +} + +/*! + \internal + + Calculate the current Edge size based on the current Layout size and the + size the edge is supposed to have when the layout is at its: + + - minimum size, + - preferred size, + - maximum size. + + These three key values are calculated in advance using linear + programming (more expensive) or the simplification algorithm, then + subsequential resizes of the parent layout require a simple + interpolation. +*/ +void QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorVertex *base, AnchorData *edge) +{ + const Orientation orientation = Orientation(edge->orientation); + const QPair factor(interpolationInterval[orientation], + interpolationProgress[orientation]); + + qreal edgeDistance = interpolate(factor, edge->sizeAtMinimum, edge->sizeAtPreferred, + edge->sizeAtPreferred, edge->sizeAtPreferred, + edge->sizeAtMaximum); + + Q_ASSERT(edge->from == base || edge->to == base); + + // Calculate the distance for the vertex opposite to the base + if (edge->from == base) { + edge->to->distance = base->distance + edgeDistance; + } else { + edge->from->distance = base->distance - edgeDistance; + } +} + +bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList &constraints, + GraphPath path, qreal *min, qreal *max) +{ + QSimplex simplex; + bool feasible = simplex.setConstraints(constraints); + if (feasible) { + // Obtain the objective constraint + QSimplexConstraint objective; + QSet::const_iterator iter; + for (iter = path.positives.constBegin(); iter != path.positives.constEnd(); ++iter) + objective.variables.insert(*iter, 1.0); + + for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter) + objective.variables.insert(*iter, -1.0); + + const qreal objectiveOffset = (path.positives.count() - path.negatives.count()) * g_offset; + simplex.setObjective(&objective); + + // Calculate minimum values + *min = simplex.solveMin() - objectiveOffset; + + // Save sizeAtMinimum results + QList variables = getVariables(constraints); + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast(variables.at(i)); + ad->sizeAtMinimum = ad->result - g_offset; + } + + // Calculate maximum values + *max = simplex.solveMax() - objectiveOffset; + + // Save sizeAtMaximum results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast(variables.at(i)); + ad->sizeAtMaximum = ad->result - g_offset; + } + } + return feasible; +} + +enum slackType { Grower = -1, Shrinker = 1 }; +static QPair createSlack(QSimplexConstraint *sizeConstraint, + qreal interval, slackType type) +{ + QSimplexVariable *slack = new QSimplexVariable; + sizeConstraint->variables.insert(slack, type); + + QSimplexConstraint *limit = new QSimplexConstraint; + limit->variables.insert(slack, 1.0); + limit->ratio = QSimplexConstraint::LessOrEqual; + limit->constant = interval; + + return qMakePair(slack, limit); +} + +bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList &constraints, + const QList &variables) +{ + QList preferredConstraints; + QList preferredVariables; + QSimplexConstraint objective; + + // Fill the objective coefficients for this variable. In the + // end the objective function will be + // + // z = n * (A_shrinker_hard + A_grower_hard + B_shrinker_hard + B_grower_hard + ...) + + // (A_shrinker_soft + A_grower_soft + B_shrinker_soft + B_grower_soft + ...) + // + // where n is the number of variables that have + // slacks. Note that here we use the number of variables + // as coefficient, this is to mark the "shrinker slack + // variable" less likely to get value than the "grower + // slack variable". + + // This will fill the values for the structural constraints + // and we now fill the values for the slack constraints (one per variable), + // which have this form (the constant A_pref was set when creating the slacks): + // + // A + A_shrinker_hard + A_shrinker_soft - A_grower_hard - A_grower_soft = A_pref + // + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = variables.at(i); + + // The layout original structure anchors are not relevant in preferred size calculation + if (ad->isLayoutAnchor) + continue; + + // By default, all variables are equal to their preferred size. If they have room to + // grow or shrink, such flexibility will be added by the additional variables below. + QSimplexConstraint *sizeConstraint = new QSimplexConstraint; + preferredConstraints += sizeConstraint; + sizeConstraint->variables.insert(ad, 1.0); + sizeConstraint->constant = ad->prefSize + g_offset; + + // Can easily shrink + QPair slack; + const qreal softShrinkInterval = ad->prefSize - ad->minPrefSize; + if (softShrinkInterval) { + slack = createSlack(sizeConstraint, softShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } + + // Can easily grow + const qreal softGrowInterval = ad->maxPrefSize - ad->prefSize; + if (softGrowInterval) { + slack = createSlack(sizeConstraint, softGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } + + // Can shrink if really necessary + const qreal hardShrinkInterval = ad->minPrefSize - ad->minSize; + if (hardShrinkInterval) { + slack = createSlack(sizeConstraint, hardShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + + // Can grow if really necessary + const qreal hardGrowInterval = ad->maxSize - ad->maxPrefSize; + if (hardGrowInterval) { + slack = createSlack(sizeConstraint, hardGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + } + + QSimplex *simplex = new QSimplex; + bool feasible = simplex->setConstraints(constraints + preferredConstraints); + if (feasible) { + simplex->setObjective(&objective); + + // Calculate minimum values + simplex->solveMin(); + + // Save sizeAtPreferred results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = variables.at(i); + ad->sizeAtPreferred = ad->result - g_offset; + } + + // Make sure we delete the simplex solver -before- we delete the + // constraints used by it. + delete simplex; + } + // Delete constraints and variables we created. + qDeleteAll(preferredConstraints); + qDeleteAll(preferredVariables); + + return feasible; +} + +/*! + \internal + Returns true if there are no arrangement that satisfies all constraints. + Otherwise returns false. + + \sa addAnchor() +*/ +bool QGraphicsAnchorLayoutPrivate::hasConflicts() const +{ + QGraphicsAnchorLayoutPrivate *that = const_cast(this); + that->calculateGraphs(); + + bool floatConflict = !m_floatItems[0].isEmpty() || !m_floatItems[1].isEmpty(); + + return graphHasConflicts[0] || graphHasConflicts[1] || floatConflict; +} + +#ifdef QT_DEBUG +void QGraphicsAnchorLayoutPrivate::dumpGraph(const QString &name) +{ + QFile file(QString::fromAscii("anchorlayout.%1.dot").arg(name)); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + qWarning("Could not write to %s", file.fileName().toLocal8Bit().constData()); + + QString str = QString::fromAscii("digraph anchorlayout {\nnode [shape=\"rect\"]\n%1}"); + QString dotContents = graph[0].serializeToDot(); + dotContents += graph[1].serializeToDot(); + file.write(str.arg(dotContents).toLocal8Bit()); + + file.close(); +} +#endif + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h new file mode 100644 index 0000000000..9a91c3c8df --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSANCHORLAYOUT_P_H +#define QGRAPHICSANCHORLAYOUT_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 +#include + +#include "qgraphicslayout_p.h" +#include "qgraphicsanchorlayout.h" +#include "qgraph_p.h" +#include "qsimplex_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +/* + The public QGraphicsAnchorLayout interface represents an anchorage point + as a pair of a and a . + + Internally though, it has a graph of anchorage points (vertices) and + anchors (edges), represented by the AnchorVertex and AnchorData structs + respectively. +*/ + +/*! + \internal + + Represents a vertex (anchorage point) in the internal graph +*/ +struct AnchorVertex { + enum Type { + Normal = 0, + Pair + }; + + AnchorVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge) + : m_item(item), m_edge(edge), m_type(Normal) {} + + AnchorVertex() + : m_item(0), m_edge(Qt::AnchorPoint(0)), m_type(Normal) {} + +#ifdef QT_DEBUG + inline QString toString() const; +#endif + + QGraphicsLayoutItem *m_item; + Qt::AnchorPoint m_edge; + uint m_type : 1; + + // Current distance from this vertex to the layout edge (Left or Top) + // Value is calculated from the current anchors sizes. + qreal distance; +}; + +/*! + \internal + + Represents an edge (anchor) in the internal graph. +*/ +struct AnchorData : public QSimplexVariable { + enum Type { + Normal = 0, + Sequential, + Parallel + }; + + enum Dependency { + Independent = 0, + Master, + Slave + }; + + AnchorData() + : QSimplexVariable(), from(0), to(0), + minSize(0), prefSize(0), maxSize(0), + minPrefSize(0), maxPrefSize(0), + sizeAtMinimum(0), sizeAtPreferred(0), + sizeAtMaximum(0), item(0), graphicsAnchor(0), + type(Normal), isLayoutAnchor(false), + isCenterAnchor(false), orientation(0), + dependency(Independent) {} + virtual ~AnchorData(); + + virtual void updateChildrenSizes() {} + void refreshSizeHints(const QLayoutStyleInfo *styleInfo = 0); + +#ifdef QT_DEBUG + void dump(int indent = 2); + inline QString toString() const; + QString name; +#endif + + // Anchor is semantically directed + AnchorVertex *from; + AnchorVertex *to; + + // Nominal sizes + // These are the intrinsic size restrictions for a given item. They are + // used as input for the calculation of the actual sizes. + // These values are filled by the refreshSizeHints method, based on the + // anchor size policy, the size hints of the item it (possibly) represents + // and the layout spacing information. + qreal minSize; + qreal prefSize; + qreal maxSize; + + qreal minPrefSize; + qreal maxPrefSize; + + // Calculated sizes + // These attributes define which sizes should that anchor be in when the + // layout is at its minimum, preferred or maximum sizes. Values are + // calculated by the Simplex solver based on the current layout setup. + qreal sizeAtMinimum; + qreal sizeAtPreferred; + qreal sizeAtMaximum; + + // References to the classes that represent this anchor in the public world + // An anchor may represent a LayoutItem, it may also be acessible externally + // through a GraphicsAnchor "handler". + QGraphicsLayoutItem *item; + QGraphicsAnchor *graphicsAnchor; + + uint type : 2; // either Normal, Sequential or Parallel + uint isLayoutAnchor : 1; // if this anchor is an internal layout anchor + uint isCenterAnchor : 1; + uint orientation : 1; + uint dependency : 2; // either Independent, Master or Slave +}; + +#ifdef QT_DEBUG +inline QString AnchorData::toString() const +{ + return QString::fromAscii("Anchor(%1)").arg(name); +} +#endif + +struct SequentialAnchorData : public AnchorData +{ + SequentialAnchorData(const QVector &vertices, const QVector &edges) + : AnchorData(), m_children(vertices), m_edges(edges) + { + type = AnchorData::Sequential; + orientation = m_edges.at(0)->orientation; +#ifdef QT_DEBUG + name = QString::fromAscii("%1 -- %2").arg(vertices.first()->toString(), vertices.last()->toString()); +#endif + } + + virtual void updateChildrenSizes(); + void calculateSizeHints(); + + QVector m_children; // list of vertices in the sequence + QVector m_edges; // keep the list of edges too. +}; + +struct ParallelAnchorData : public AnchorData +{ + ParallelAnchorData(AnchorData *first, AnchorData *second) + : AnchorData(), firstEdge(first), secondEdge(second) + { + type = AnchorData::Parallel; + orientation = first->orientation; + + // This assert whether the child anchors share their vertices + Q_ASSERT(((first->from == second->from) && (first->to == second->to)) || + ((first->from == second->to) && (first->to == second->from))); + + // Our convention will be that the parallel group anchor will have the same + // direction as the first anchor. + from = first->from; + to = first->to; +#ifdef QT_DEBUG + name = QString::fromAscii("%1 | %2").arg(first->toString(), second->toString()); +#endif + } + + virtual void updateChildrenSizes(); + bool calculateSizeHints(); + + bool secondForward() const { + // We have the convention that the first children will define the direction of the + // pararell group. Note that we can't rely on 'this->from' or 'this->to' because they + // might be changed by vertex simplification. + return firstEdge->from == secondEdge->from; + } + + AnchorData* firstEdge; + AnchorData* secondEdge; + + QList m_firstConstraints; + QList m_secondConstraints; +}; + +struct AnchorVertexPair : public AnchorVertex { + AnchorVertexPair(AnchorVertex *v1, AnchorVertex *v2, AnchorData *data) + : AnchorVertex(), m_first(v1), m_second(v2), m_removedAnchor(data) { + m_type = AnchorVertex::Pair; + } + + AnchorVertex *m_first; + AnchorVertex *m_second; + + AnchorData *m_removedAnchor; + QList m_firstAnchors; + QList m_secondAnchors; +}; + +#ifdef QT_DEBUG +inline QString AnchorVertex::toString() const +{ + if (!this) { + return QLatin1String("NULL"); + } else if (m_type == Pair) { + const AnchorVertexPair *vp = static_cast(this); + return QString::fromAscii("(%1, %2)").arg(vp->m_first->toString()).arg(vp->m_second->toString()); + } else if (!m_item) { + return QString::fromAscii("NULL_%1").arg(quintptr(this)); + } + QString edge; + switch (m_edge) { + case Qt::AnchorLeft: + edge = QLatin1String("Left"); + break; + case Qt::AnchorHorizontalCenter: + edge = QLatin1String("HorizontalCenter"); + break; + case Qt::AnchorRight: + edge = QLatin1String("Right"); + break; + case Qt::AnchorTop: + edge = QLatin1String("Top"); + break; + case Qt::AnchorVerticalCenter: + edge = QLatin1String("VerticalCenter"); + break; + case Qt::AnchorBottom: + edge = QLatin1String("Bottom"); + break; + default: + edge = QLatin1String("None"); + break; + } + QString itemName; + if (m_item->isLayout()) { + itemName = QLatin1String("layout"); + } else { + if (QGraphicsItem *item = m_item->graphicsItem()) { + itemName = item->data(0).toString(); + } + } + edge.insert(0, QLatin1String("%1_")); + return edge.arg(itemName); +} +#endif + +/*! + \internal + + Representation of a valid path for a given vertex in the graph. + In this struct, "positives" is the set of anchors that have been + traversed in the forward direction, while "negatives" is the set + with the ones walked backwards. + + This paths are compared against each other to produce LP Constraints, + the exact order in which the anchors were traversed is not relevant. +*/ +class GraphPath +{ +public: + GraphPath() {} + + QSimplexConstraint *constraint(const GraphPath &path) const; +#ifdef QT_DEBUG + QString toString() const; +#endif + QSet positives; + QSet negatives; +}; + +class QGraphicsAnchorLayoutPrivate; +/*! + \internal +*/ +class QGraphicsAnchorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsAnchor) + +public: + explicit QGraphicsAnchorPrivate(int version = QObjectPrivateVersion); + ~QGraphicsAnchorPrivate(); + + void setSpacing(qreal value); + void unsetSpacing(); + qreal spacing() const; + + void setSizePolicy(QSizePolicy::Policy policy); + + QGraphicsAnchorLayoutPrivate *layoutPrivate; + AnchorData *data; + + // Size information for user controlled anchor + QSizePolicy::Policy sizePolicy; + qreal preferredSize; + + uint hasSize : 1; // if false, get size from style. +}; + + + + +/*! + \internal + + QGraphicsAnchorLayout private methods and attributes. +*/ +class Q_AUTOTEST_EXPORT QGraphicsAnchorLayoutPrivate : public QGraphicsLayoutPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsAnchorLayout) + +public: + // When the layout geometry is different from its Minimum, Preferred + // or Maximum values, interpolation is used to calculate the geometries + // of the items. + // + // Interval represents which interpolation interval are we operating in. + enum Interval { + MinimumToMinPreferred = 0, + MinPreferredToPreferred, + PreferredToMaxPreferred, + MaxPreferredToMaximum + }; + + // Several structures internal to the layout are duplicated to handle + // both Horizontal and Vertical restrictions. + // + // Orientation is used to reference the right structure in each context + enum Orientation { + Horizontal = 0, + Vertical, + NOrientations + }; + + QGraphicsAnchorLayoutPrivate(); + + static QGraphicsAnchorLayoutPrivate *get(QGraphicsAnchorLayout *q) + { + return q ? q->d_func() : 0; + } + + static Qt::AnchorPoint oppositeEdge( + Qt::AnchorPoint edge); + + static Orientation edgeOrientation(Qt::AnchorPoint edge); + + static Qt::AnchorPoint pickEdge(Qt::AnchorPoint edge, Orientation orientation) + { + if (orientation == Vertical && int(edge) <= 2) + return (Qt::AnchorPoint)(edge + 3); + else if (orientation == Horizontal && int(edge) >= 3) { + return (Qt::AnchorPoint)(edge - 3); + } + return edge; + } + + // Init methods + void createLayoutEdges(); + void deleteLayoutEdges(); + void createItemEdges(QGraphicsLayoutItem *item); + void createCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge); + void removeCenterAnchors(QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, bool substitute = true); + void removeCenterConstraints(QGraphicsLayoutItem *item, Orientation orientation); + + QGraphicsAnchor *acquireGraphicsAnchor(AnchorData *data) + { + Q_Q(QGraphicsAnchorLayout); + if (!data->graphicsAnchor) { + data->graphicsAnchor = new QGraphicsAnchor(q); + data->graphicsAnchor->d_func()->data = data; + } + return data->graphicsAnchor; + } + + // function used by the 4 API functions + QGraphicsAnchor *addAnchor(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + qreal *spacing = 0); + + // Helper for Anchor Manipulation methods + void addAnchor_helper(QGraphicsLayoutItem *firstItem, + Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, + Qt::AnchorPoint secondEdge, + AnchorData *data); + + QGraphicsAnchor *getAnchor(QGraphicsLayoutItem *firstItem, Qt::AnchorPoint firstEdge, + QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge); + + void removeAnchor(AnchorVertex *firstVertex, AnchorVertex *secondVertex); + void removeAnchor_helper(AnchorVertex *v1, AnchorVertex *v2); + + void removeAnchors(QGraphicsLayoutItem *item); + + void removeVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + + void correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + Qt::AnchorPoint &firstEdge, + QGraphicsLayoutItem *&secondItem, + Qt::AnchorPoint &secondEdge); + + QLayoutStyleInfo &styleInfo() const; + + AnchorData *addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible); + + // Activation + void calculateGraphs(); + void calculateGraphs(Orientation orientation); + + // Simplification + bool simplifyGraph(Orientation orientation); + bool simplifyVertices(Orientation orientation); + bool simplifyGraphIteration(Orientation orientation, bool *feasible); + + bool replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList &edges); + + + void restoreSimplifiedGraph(Orientation orientation); + void restoreSimplifiedAnchor(AnchorData *edge); + void restoreSimplifiedConstraints(ParallelAnchorData *parallel); + void restoreVertices(Orientation orientation); + + bool calculateTrunk(Orientation orientation, const GraphPath &trunkPath, + const QList &constraints, + const QList &variables); + bool calculateNonTrunk(const QList &constraints, + const QList &variables); + + // Support functions for calculateGraph() + void refreshAllSizeHints(Orientation orientation); + void findPaths(Orientation orientation); + void constraintsFromPaths(Orientation orientation); + void updateAnchorSizes(Orientation orientation); + QList constraintsFromSizeHints(const QList &anchors); + QList > getGraphParts(Orientation orientation); + void identifyFloatItems(const QSet &visited, Orientation orientation); + void identifyNonFloatItems_helper(const AnchorData *ad, QSet *nonFloatingItemsIdentifiedSoFar); + + inline AnchorVertex *internalVertex(const QPair &itemEdge) const + { + return m_vertexList.value(itemEdge).first; + } + + inline AnchorVertex *internalVertex(const QGraphicsLayoutItem *item, Qt::AnchorPoint edge) const + { + return internalVertex(qMakePair(const_cast(item), edge)); + } + + inline void changeLayoutVertex(Orientation orientation, AnchorVertex *oldV, AnchorVertex *newV) + { + if (layoutFirstVertex[orientation] == oldV) + layoutFirstVertex[orientation] = newV; + else if (layoutCentralVertex[orientation] == oldV) + layoutCentralVertex[orientation] = newV; + else if (layoutLastVertex[orientation] == oldV) + layoutLastVertex[orientation] = newV; + } + + + AnchorVertex *addInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + void removeInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); + + // Geometry interpolation methods + void setItemsGeometries(const QRectF &geom); + + void calculateVertexPositions(Orientation orientation); + void setupEdgesInterpolation(Orientation orientation); + void interpolateEdge(AnchorVertex *base, AnchorData *edge); + + // Linear Programming solver methods + bool solveMinMax(const QList &constraints, + GraphPath path, qreal *min, qreal *max); + bool solvePreferred(const QList &constraints, + const QList &variables); + bool hasConflicts() const; + +#ifdef QT_DEBUG + void dumpGraph(const QString &name = QString()); +#endif + + + qreal spacings[NOrientations]; + // Size hints from simplex engine + qreal sizeHints[2][3]; + + // Items + QVector items; + + // Mapping between high level anchorage points (Item, Edge) to low level + // ones (Graph Vertices) + + QHash, QPair > m_vertexList; + + // Internal graph of anchorage points and anchors, for both orientations + Graph graph[2]; + + AnchorVertex *layoutFirstVertex[2]; + AnchorVertex *layoutCentralVertex[2]; + AnchorVertex *layoutLastVertex[2]; + + // Combined anchors in order of creation + QList simplifiedVertices[2]; + QList anchorsFromSimplifiedVertices[2]; + + // Graph paths and constraints, for both orientations + QMultiHash graphPaths[2]; + QList constraints[2]; + QList itemCenterConstraints[2]; + + // The interpolation interval and progress based on the current size + // as well as the key values (minimum, preferred and maximum) + Interval interpolationInterval[2]; + qreal interpolationProgress[2]; + + bool graphHasConflicts[2]; + QSet m_floatItems[2]; + +#if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) + bool lastCalculationUsedSimplex[2]; +#endif + + uint calculateGraphCacheDirty : 1; + mutable uint styleInfoDirty : 1; + mutable QLayoutStyleInfo cachedStyleInfo; + + friend class QGraphicsAnchorPrivate; +}; + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/gui/graphicsview/qgraphicsgridlayout.cpp b/src/gui/graphicsview/qgraphicsgridlayout.cpp new file mode 100644 index 0000000000..f6eec1d405 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsgridlayout.cpp @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsGridLayout + \brief The QGraphicsGridLayout class provides a grid layout for managing + widgets in Graphics View. + \since 4.4 + + \ingroup graphicsview-api + + The most common way to use QGraphicsGridLayout is to construct an object + on the heap with no parent, add widgets and layouts by calling addItem(), + and finally assign the layout to a widget by calling + QGraphicsWidget::setLayout(). QGraphicsGridLayout automatically computes + the dimensions of the grid as you add items. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsgridlayout.cpp 0 + + The layout takes ownership of the items. In some cases when the layout + item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a + ambiguity in ownership because the layout item belongs to two ownership hierarchies. + See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle + this. + You can access each item in the layout by calling count() and itemAt(). Calling + removeAt() will remove an item from the layout, without + destroying it. + + \section1 Size Hints and Size Policies in QGraphicsGridLayout + + QGraphicsGridLayout respects each item's size hints and size policies, + and when a cell in the grid has more space than the items can fill, each item + is arranged according to the layout's alignment for that item. You can set + an alignment for each item by calling setAlignment(), and check the + alignment for any item by calling alignment(). You can also set the alignment + for an entire row or column by calling setRowAlignment() and setColumnAlignment() + respectively. By default, items are aligned to the top left. + + + \sa QGraphicsLinearLayout, QGraphicsWidget +*/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qapplication.h" +#include "qwidget.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicsgridlayout.h" +#include "qgraphicswidget.h" +#include "qgridlayoutengine_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsGridLayoutPrivate : public QGraphicsLayoutPrivate +{ +public: + QGraphicsGridLayoutPrivate() { } + QLayoutStyleInfo styleInfo() const; + + QGridLayoutEngine engine; +#ifdef QT_DEBUG + void dump(int indent) const; +#endif +}; + +Q_GLOBAL_STATIC(QWidget, globalStyleInfoWidget); + +QLayoutStyleInfo QGraphicsGridLayoutPrivate::styleInfo() const +{ + QGraphicsItem *item = parentItem(); + QStyle *style = (item && item->isWidget()) ? static_cast(item)->style() : QApplication::style(); + return QLayoutStyleInfo(style, globalStyleInfoWidget()); +} + +/*! + Constructs a QGraphicsGridLayout instance. \a parent is passed to + QGraphicsLayout's constructor. +*/ +QGraphicsGridLayout::QGraphicsGridLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsGridLayoutPrivate(), parent) +{ +} + +/*! + Destroys the QGraphicsGridLayout object. +*/ +QGraphicsGridLayout::~QGraphicsGridLayout() +{ + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = itemAt(i); + // The following lines can be removed, but this removes the item + // from the layout more efficiently than the implementation of + // ~QGraphicsLayoutItem. + removeAt(i); + if (item) { + item->setParentLayoutItem(0); + if (item->ownedByLayout()) + delete item; + } + } +} + +/*! + Adds \a item to the grid on \a row and \a column. You can specify a + \a rowSpan and \a columnSpan and an optional \a alignment. +*/ +void QGraphicsGridLayout::addItem(QGraphicsLayoutItem *item, int row, int column, + int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + if (row < 0 || column < 0) { + qWarning("QGraphicsGridLayout::addItem: invalid row/column: %d", + row < 0 ? row : column); + return; + } + if (columnSpan < 1 || rowSpan < 1) { + qWarning("QGraphicsGridLayout::addItem: invalid row span/column span: %d", + rowSpan < 1 ? rowSpan : columnSpan); + return; + } + if (!item) { + qWarning("QGraphicsGridLayout::addItem: cannot add null item"); + return; + } + if (item == this) { + qWarning("QGraphicsGridLayout::addItem: cannot insert itself"); + return; + } + + d->addChildLayoutItem(item); + + new QGridLayoutItem(&d->engine, item, row, column, rowSpan, columnSpan, alignment); + invalidate(); +} + +/*! + \fn QGraphicsGridLayout::addItem(QGraphicsLayoutItem *item, int row, int column, Qt::Alignment alignment = 0) + + Adds \a item to the grid on \a row and \a column. You can specify + an optional \a alignment for \a item. +*/ + +/*! + Sets the default horizontal spacing for the grid layout to \a spacing. +*/ +void QGraphicsGridLayout::setHorizontalSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the default horizontal spacing for the grid layout. +*/ +qreal QGraphicsGridLayout::horizontalSpacing() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.spacing(d->styleInfo(), Qt::Horizontal); +} + +/*! + Sets the default vertical spacing for the grid layout to \a spacing. +*/ +void QGraphicsGridLayout::setVerticalSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Vertical); + invalidate(); +} + +/*! + Returns the default vertical spacing for the grid layout. +*/ +qreal QGraphicsGridLayout::verticalSpacing() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.spacing(d->styleInfo(), Qt::Vertical); +} + +/*! + Sets the grid layout's default spacing, both vertical and + horizontal, to \a spacing. + + \sa rowSpacing(), columnSpacing() +*/ +void QGraphicsGridLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setSpacing(spacing, Qt::Horizontal | Qt::Vertical); + invalidate(); +} + +/*! + Sets the spacing for \a row to \a spacing. +*/ +void QGraphicsGridLayout::setRowSpacing(int row, qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSpacing(row, spacing, Qt::Vertical); + invalidate(); +} + +/*! + Returns the row spacing for \a row. +*/ +qreal QGraphicsGridLayout::rowSpacing(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSpacing(row, Qt::Vertical); +} + +/*! + Sets the spacing for \a column to \a spacing. +*/ +void QGraphicsGridLayout::setColumnSpacing(int column, qreal spacing) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSpacing(column, spacing, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the column spacing for \a column. +*/ +qreal QGraphicsGridLayout::columnSpacing(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSpacing(column, Qt::Horizontal); +} + +/*! + Sets the stretch factor for \a row to \a stretch. +*/ +void QGraphicsGridLayout::setRowStretchFactor(int row, int stretch) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowStretchFactor(row, stretch, Qt::Vertical); + invalidate(); +} + +/*! + Returns the stretch factor for \a row. +*/ +int QGraphicsGridLayout::rowStretchFactor(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowStretchFactor(row, Qt::Vertical); +} + +/*! + Sets the stretch factor for \a column to \a stretch. +*/ +void QGraphicsGridLayout::setColumnStretchFactor(int column, int stretch) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowStretchFactor(column, stretch, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the stretch factor for \a column. +*/ +int QGraphicsGridLayout::columnStretchFactor(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowStretchFactor(column, Qt::Horizontal); +} + +/*! + Sets the minimum height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowMinimumHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the minimum height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowMinimumHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MinimumSize, row, Qt::Vertical); +} + +/*! + Sets the preferred height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowPreferredHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::PreferredSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the preferred height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowPreferredHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::PreferredSize, row, Qt::Vertical); +} + +/*! + Sets the maximum height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowMaximumHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MaximumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Returns the maximum height for row, \a row. +*/ +qreal QGraphicsGridLayout::rowMaximumHeight(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MaximumSize, row, Qt::Vertical); +} + +/*! + Sets the fixed height for row, \a row, to \a height. +*/ +void QGraphicsGridLayout::setRowFixedHeight(int row, qreal height) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, row, height, Qt::Vertical); + d->engine.setRowSizeHint(Qt::MaximumSize, row, height, Qt::Vertical); + invalidate(); +} + +/*! + Sets the minimum width for \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnMinimumWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the minimum width for \a column. +*/ +qreal QGraphicsGridLayout::columnMinimumWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MinimumSize, column, Qt::Horizontal); +} + +/*! + Sets the preferred width for \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnPreferredWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::PreferredSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the preferred width for \a column. +*/ +qreal QGraphicsGridLayout::columnPreferredWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::PreferredSize, column, Qt::Horizontal); +} + +/*! + Sets the maximum width of \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnMaximumWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MaximumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the maximum width for \a column. +*/ +qreal QGraphicsGridLayout::columnMaximumWidth(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowSizeHint(Qt::MaximumSize, column, Qt::Horizontal); +} + +/*! + Sets the fixed width of \a column to \a width. +*/ +void QGraphicsGridLayout::setColumnFixedWidth(int column, qreal width) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowSizeHint(Qt::MinimumSize, column, width, Qt::Horizontal); + d->engine.setRowSizeHint(Qt::MaximumSize, column, width, Qt::Horizontal); + invalidate(); +} + +/*! + Sets the alignment of \a row to \a alignment. +*/ +void QGraphicsGridLayout::setRowAlignment(int row, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowAlignment(row, alignment, Qt::Vertical); + invalidate(); +} + +/*! + Returns the alignment of \a row. +*/ +Qt::Alignment QGraphicsGridLayout::rowAlignment(int row) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowAlignment(row, Qt::Vertical); +} + +/*! + Sets the alignment for \a column to \a alignment. +*/ +void QGraphicsGridLayout::setColumnAlignment(int column, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setRowAlignment(column, alignment, Qt::Horizontal); + invalidate(); +} + +/*! + Returns the alignment for \a column. +*/ +Qt::Alignment QGraphicsGridLayout::columnAlignment(int column) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.rowAlignment(column, Qt::Horizontal); +} + +/*! + Sets the alignment for \a item to \a alignment. +*/ +void QGraphicsGridLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment) +{ + Q_D(QGraphicsGridLayout); + d->engine.setAlignment(item, alignment); + invalidate(); +} + +/*! + Returns the alignment for \a item. +*/ +Qt::Alignment QGraphicsGridLayout::alignment(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.alignment(item); +} + +/*! + Returns the number of rows in the grid layout. This is always one more + than the index of the last row that is occupied by a layout item (empty + rows are counted except for those at the end). +*/ +int QGraphicsGridLayout::rowCount() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.effectiveLastRow(Qt::Vertical) + 1; +} + +/*! + Returns the number of columns in the grid layout. This is always one more + than the index of the last column that is occupied by a layout item (empty + columns are counted except for those at the end). +*/ +int QGraphicsGridLayout::columnCount() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.effectiveLastRow(Qt::Horizontal) + 1; +} + +/*! + Returns a pointer to the layout item at (\a row, \a column). +*/ +QGraphicsLayoutItem *QGraphicsGridLayout::itemAt(int row, int column) const +{ + Q_D(const QGraphicsGridLayout); + if (row < 0 || row >= rowCount() || column < 0 || column >= columnCount()) { + qWarning("QGraphicsGridLayout::itemAt: invalid row, column %d, %d", row, column); + return 0; + } + if (QGridLayoutItem *item = d->engine.itemAt(row, column)) + return item->layoutItem(); + return 0; +} + +/*! + Returns the number of layout items in this grid layout. +*/ +int QGraphicsGridLayout::count() const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.itemCount(); +} + +/*! + Returns the layout item at \a index, or 0 if there is no layout item at + this index. +*/ +QGraphicsLayoutItem *QGraphicsGridLayout::itemAt(int index) const +{ + Q_D(const QGraphicsGridLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsGridLayout::itemAt: invalid index %d", index); + return 0; + } + QGraphicsLayoutItem *item = 0; + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) + item = gridItem->layoutItem(); + return item; +} + +/*! + Removes the layout item at \a index without destroying it. Ownership of + the item is transferred to the caller. + + \sa addItem() +*/ +void QGraphicsGridLayout::removeAt(int index) +{ + Q_D(QGraphicsGridLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsGridLayout::removeAt: invalid index %d", index); + return; + } + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) { + if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem()) + layoutItem->setParentLayoutItem(0); + d->engine.removeItem(gridItem); + + // recalculate rowInfo.count if we remove an item that is on the right/bottommost row + for (int j = 0; j < NOrientations; ++j) { + // 0: Hor, 1: Ver + const Qt::Orientation orient = (j == 0 ? Qt::Horizontal : Qt::Vertical); + const int oldCount = d->engine.rowCount(orient); + if (gridItem->lastRow(orient) == oldCount - 1) { + const int newCount = d->engine.effectiveLastRow(orient) + 1; + d->engine.removeRows(newCount, oldCount - newCount, orient); + } + } + + delete gridItem; + invalidate(); + } +} + +/*! + Removes the layout item \a item without destroying it. + Ownership of the item is transferred to the caller. + + \sa addItem() +*/ +void QGraphicsGridLayout::removeItem(QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsGridLayout); + int index = d->engine.indexOf(item); + removeAt(index); +} +/*! + \reimp +*/ +void QGraphicsGridLayout::invalidate() +{ + Q_D(QGraphicsGridLayout); + d->engine.invalidate(); + QGraphicsLayout::invalidate(); +} + +#ifdef QT_DEBUG +void QGraphicsGridLayoutPrivate::dump(int indent) const +{ + if (qt_graphicsLayoutDebug()) { + engine.dump(indent + 1); + } +} +#endif + +/*! + Sets the bounding geometry of the grid layout to \a rect. +*/ +void QGraphicsGridLayout::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsGridLayout); + QGraphicsLayout::setGeometry(rect); + QRectF effectiveRect = geometry(); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + Qt::LayoutDirection visualDir = d->visualDirection(); + d->engine.setVisualDirection(visualDir); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + effectiveRect.adjust(+left, +top, -right, -bottom); + d->engine.setGeometries(d->styleInfo(), effectiveRect); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + static int counter = 0; + qDebug("==== BEGIN DUMP OF QGraphicsGridLayout (%d)====", counter++); + d->dump(1); + qDebug("==== END DUMP OF QGraphicsGridLayout ===="); + } +#endif +} + +/*! + \reimp +*/ +QSizeF QGraphicsGridLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsGridLayout); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + const QSizeF extraMargins(left + right, top + bottom); + return d->engine.sizeHint(d->styleInfo(), which , constraint - extraMargins) + extraMargins; +} + + +#if 0 +// ### kill? (implement and kill?) +QRect QGraphicsGridLayout::cellRect(int row, int column, int rowSpan, int columnSpan) const +{ + Q_D(const QGraphicsGridLayout); + return QRect(); +// return d->engine.cellRect(parentLayoutable(), contentsGeometry(), row, column, rowSpan, columnSpan); +} + +QSizePolicy::ControlTypes QGraphicsGridLayout::controlTypes(LayoutSide side) const +{ + Q_D(const QGraphicsGridLayout); + return d->engine.controlTypes(side); +} +#endif + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsgridlayout.h b/src/gui/graphicsview/qgraphicsgridlayout.h new file mode 100644 index 0000000000..f493eb6153 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsgridlayout.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSGRIDLAYOUT_H +#define QGRAPHICSGRIDLAYOUT_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsGridLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsGridLayout : public QGraphicsLayout +{ +public: + QGraphicsGridLayout(QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsGridLayout(); + + void addItem(QGraphicsLayoutItem *item, int row, int column, int rowSpan, int columnSpan, + Qt::Alignment alignment = 0); + inline void addItem(QGraphicsLayoutItem *item, int row, int column, Qt::Alignment alignment = 0); + + void setHorizontalSpacing(qreal spacing); + qreal horizontalSpacing() const; + void setVerticalSpacing(qreal spacing); + qreal verticalSpacing() const; + void setSpacing(qreal spacing); + + void setRowSpacing(int row, qreal spacing); + qreal rowSpacing(int row) const; + void setColumnSpacing(int column, qreal spacing); + qreal columnSpacing(int column) const; + + void setRowStretchFactor(int row, int stretch); + int rowStretchFactor(int row) const; + void setColumnStretchFactor(int column, int stretch); + int columnStretchFactor(int column) const; + + void setRowMinimumHeight(int row, qreal height); + qreal rowMinimumHeight(int row) const; + void setRowPreferredHeight(int row, qreal height); + qreal rowPreferredHeight(int row) const; + void setRowMaximumHeight(int row, qreal height); + qreal rowMaximumHeight(int row) const; + void setRowFixedHeight(int row, qreal height); + + void setColumnMinimumWidth(int column, qreal width); + qreal columnMinimumWidth(int column) const; + void setColumnPreferredWidth(int column, qreal width); + qreal columnPreferredWidth(int column) const; + void setColumnMaximumWidth(int column, qreal width); + qreal columnMaximumWidth(int column) const; + void setColumnFixedWidth(int column, qreal width); + + void setRowAlignment(int row, Qt::Alignment alignment); + Qt::Alignment rowAlignment(int row) const; + void setColumnAlignment(int column, Qt::Alignment alignment); + Qt::Alignment columnAlignment(int column) const; + + void setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *item) const; + + int rowCount() const; + int columnCount() const; + + QGraphicsLayoutItem *itemAt(int row, int column) const; + + // inherited from QGraphicsLayout + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + void removeAt(int index); + void removeItem(QGraphicsLayoutItem *item); + + void invalidate(); + + // inherited from QGraphicsLayoutItem + void setGeometry(const QRectF &rect); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + // #### + //QRect cellRect(int row, int column, int rowSpan = 1, int columnSpan = 1) const; + //QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + +private: + Q_DISABLE_COPY(QGraphicsGridLayout) + Q_DECLARE_PRIVATE(QGraphicsGridLayout) +}; + +inline void QGraphicsGridLayout::addItem(QGraphicsLayoutItem *aitem, int arow, int acolumn, Qt::Alignment aalignment) +{ addItem(aitem, arow, acolumn, 1, 1, aalignment); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp new file mode 100644 index 0000000000..e67fe82045 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -0,0 +1,11597 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsItem + \brief The QGraphicsItem class is the base class for all graphical + items in a QGraphicsScene. + \since 4.2 + + \ingroup graphicsview-api + + It provides a light-weight foundation for writing your own custom items. + This includes defining the item's geometry, collision detection, its + painting implementation and item interaction through its event handlers. + QGraphicsItem is part of the \l{Graphics View Framework} + + \image graphicsview-items.png + + For convenience, Qt provides a set of standard graphics items for the most + common shapes. These are: + + \list + \o QGraphicsEllipseItem provides an ellipse item + \o QGraphicsLineItem provides a line item + \o QGraphicsPathItem provides an arbitrary path item + \o QGraphicsPixmapItem provides a pixmap item + \o QGraphicsPolygonItem provides a polygon item + \o QGraphicsRectItem provides a rectangular item + \o QGraphicsSimpleTextItem provides a simple text label item + \o QGraphicsTextItem provides an advanced text browser item + \endlist + + All of an item's geometric information is based on its local coordinate + system. The item's position, pos(), is the only function that does not + operate in local coordinates, as it returns a position in parent + coordinates. \l {The Graphics View Coordinate System} describes the coordinate + system in detail. + + You can set whether an item should be visible (i.e., drawn, and accepting + events), by calling setVisible(). Hiding an item will also hide its + children. Similarly, you can enable or disable an item by calling + setEnabled(). If you disable an item, all its children will also be + disabled. By default, items are both visible and enabled. To toggle + whether an item is selected or not, first enable selection by setting + the ItemIsSelectable flag, and then call setSelected(). Normally, + selection is toggled by the scene, as a result of user interaction. + + To write your own graphics item, you first create a subclass of + QGraphicsItem, and then start by implementing its two pure virtual public + functions: boundingRect(), which returns an estimate of the area painted + by the item, and paint(), which implements the actual painting. For + example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 0 + + The boundingRect() function has many different purposes. + QGraphicsScene bases its item index on boundingRect(), and + QGraphicsView uses it both for culling invisible items, and for + determining the area that needs to be recomposed when drawing + overlapping items. In addition, QGraphicsItem's collision + detection mechanisms use boundingRect() to provide an efficient + cut-off. The fine grained collision algorithm in + collidesWithItem() is based on calling shape(), which returns an + accurate outline of the item's shape as a QPainterPath. + + QGraphicsScene expects all items boundingRect() and shape() to + remain unchanged unless it is notified. If you want to change an + item's geometry in any way, you must first call + prepareGeometryChange() to allow QGraphicsScene to update its + bookkeeping. + + Collision detection can be done in two ways: + + \list 1 + + \o Reimplement shape() to return an accurate shape for your item, + and rely on the default implementation of collidesWithItem() to do + shape-shape intersection. This can be rather expensive if the + shapes are complex. + + \o Reimplement collidesWithItem() to provide your own custom item + and shape collision algorithm. + + \endlist + + The contains() function can be called to determine whether the item \e + contains a point or not. This function can also be reimplemented by the + item. The default behavior of contains() is based on calling shape(). + + Items can contain other items, and also be contained by other items. All + items can have a parent item and a list of children. Unless the item has + no parent, its position is in \e parent coordinates (i.e., the parent's + local coordinates). Parent items propagate both their position and their + transformation to all children. + + \img graphicsview-parentchild.png + + \target Transformations + \section1 Transformations + + QGraphicsItem supports projective transformations in addition to its base + position, pos(). There are several ways to change an item's transformation. + For simple transformations, you can call either of the convenience + functions setRotation() or setScale(), or you can pass any transformation + matrix to setTransform(). For advanced transformation control you also have + the option of setting several combined transformations by calling + setTransformations(). + + Item transformations accumulate from parent to child, so if both a parent + and child item are rotated 90 degrees, the child's total transformation + will be 180 degrees. Similarly, if the item's parent is scaled to 2x its + original size, its children will also be twice as large. An item's + transformation does not affect its own local geometry; all geometry + functions (e.g., contains(), update(), and all the mapping functions) still + operate in local coordinates. For convenience, QGraphicsItem provides the + functions sceneTransform(), which returns the item's total transformation + matrix (including its position and all parents' positions and + transformations), and scenePos(), which returns its position in scene + coordinates. To reset an item's matrix, call resetTransform(). + + Certain transformation operations produce a different outcome depending on + the order in which they are applied. For example, if you scale an + transform, and then rotate it, you may get a different result than if the + transform was rotated first. However, the order you set the transformation + properties on QGraphicsItem does not affect the resulting transformation; + QGraphicsItem always applies the properties in a fixed, defined order: + + \list + \o The item's base transform is applied (transform()) + \o The item's transformations list is applied in order (transformations()) + \o The item is rotated relative to its transform origin point (rotation(), transformOriginPoint()) + \o The item is scaled relative to its transform origin point (scale(), transformOriginPoint()) + \endlist + + \section1 Painting + + The paint() function is called by QGraphicsView to paint the item's + contents. The item has no background or default fill of its own; whatever + is behind the item will shine through all areas that are not explicitly + painted in this function. You can call update() to schedule a repaint, + optionally passing the rectangle that needs a repaint. Depending on + whether or not the item is visible in a view, the item may or may not be + repainted; there is no equivalent to QWidget::repaint() in QGraphicsItem. + + Items are painted by the view, starting with the parent items and then + drawing children, in ascending stacking order. You can set an item's + stacking order by calling setZValue(), and test it by calling + zValue(), where items with low z-values are painted before items with + high z-values. Stacking order applies to sibling items; parents are always + drawn before their children. + + \section1 Sorting + + All items are drawn in a defined, stable order, and this same order decides + which items will receive mouse input first when you click on the scene. + Normally you don't have to worry about sorting, as the items follow a + "natural order", following the logical structure of the scene. + + An item's children are stacked on top of the parent, and sibling items are + stacked by insertion order (i.e., in the same order that they were either + added to the scene, or added to the same parent). If you add item A, and + then B, then B will be on top of A. If you then add C, the items' stacking + order will be A, then B, then C. + + \image graphicsview-zorder.png + + This example shows the stacking order of all limbs of the robot from the + \l{graphicsview/dragdroprobot}{Drag and Drop Robot} example. The torso is + the root item (all other items are children or descendants of the torso), + so it is drawn first. Next, the head is drawn, as it is the first item in + the torso's list of children. Then the upper left arm is drawn. As the + lower arm is a child of the upper arm, the lower arm is then drawn, + followed by the upper arm's next sibling, which is the upper right arm, and + so on. + + For advanced users, there are ways to alter how your items are sorted: + + \list + \o You can call setZValue() on an item to explicitly stack it on top of, or + under, other sibling items. The default Z value for an item is 0. Items + with the same Z value are stacked by insertion order. + + \o You can call stackBefore() to reorder the list of children. This will + directly modify the insertion order. + + \o You can set the ItemStacksBehindParent flag to stack a child item behind + its parent. + \endlist + + The stacking order of two sibling items also counts for each item's + children and descendant items. So if one item is on top of another, then + all its children will also be on top of all the other item's children as + well. + + \section1 Events + + QGraphicsItem receives events from QGraphicsScene through the virtual + function sceneEvent(). This function distributes the most common events + to a set of convenience event handlers: + + \list + \o contextMenuEvent() handles context menu events + \o focusInEvent() and focusOutEvent() handle focus in and out events + \o hoverEnterEvent(), hoverMoveEvent(), and hoverLeaveEvent() handles + hover enter, move and leave events + \o inputMethodEvent() handles input events, for accessibility support + \o keyPressEvent() and keyReleaseEvent() handle key press and release events + \o mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), and + mouseDoubleClickEvent() handles mouse press, move, release, click and + doubleclick events + \endlist + + You can filter events for any other item by installing event filters. This + functionality is separate from Qt's regular event filters (see + QObject::installEventFilter()), which only work on subclasses of QObject. After + installing your item as an event filter for another item by calling + installSceneEventFilter(), the filtered events will be received by the virtual + function sceneEventFilter(). You can remove item event filters by calling + removeSceneEventFilter(). + + \section1 Custom Data + + Sometimes it's useful to register custom data with an item, be it a custom + item, or a standard item. You can call setData() on any item to store data + in it using a key-value pair (the key being an integer, and the value is a + QVariant). To get custom data from an item, call data(). This + functionality is completely untouched by Qt itself; it is provided for the + user's convenience. + + \sa QGraphicsScene, QGraphicsView, {Graphics View Framework} +*/ + +/*! + \variable QGraphicsItem::Type + + The type value returned by the virtual type() function in standard + graphics item classes in Qt. All such standard graphics item + classes in Qt are associated with a unique value for Type, + e.g. the value returned by QGraphicsPathItem::type() is 2. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 18 +*/ + +/*! + \variable QGraphicsItem::UserType + + The lowest permitted type value for custom items (subclasses + of QGraphicsItem or any of the standard items). This value is + used in conjunction with a reimplementation of QGraphicsItem::type() + and declaring a Type enum value. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 1 + + \note UserType = 65536 +*/ + +/*! + \enum QGraphicsItem::GraphicsItemFlag + + This enum describes different flags that you can set on an item to + toggle different features in the item's behavior. + + All flags are disabled by default. + + \value ItemIsMovable The item supports interactive movement using + the mouse. By clicking on the item and then dragging, the item + will move together with the mouse cursor. If the item has + children, all children are also moved. If the item is part of a + selection, all selected items are also moved. This feature is + provided as a convenience through the base implementation of + QGraphicsItem's mouse event handlers. + + \value ItemIsSelectable The item supports selection. Enabling this + feature will enable setSelected() to toggle selection for the + item. It will also let the item be selected automatically as a + result of calling QGraphicsScene::setSelectionArea(), by clicking + on an item, or by using rubber band selection in QGraphicsView. + + \value ItemIsFocusable The item supports keyboard input focus (i.e., it is + an input item). Enabling this flag will allow the item to accept focus, + which again allows the delivery of key events to + QGraphicsItem::keyPressEvent() and QGraphicsItem::keyReleaseEvent(). + + \value ItemClipsToShape The item clips to its own shape. The item cannot + draw or receive mouse, tablet, drag and drop or hover events outside its + shape. It is disabled by default. This behavior is enforced by + QGraphicsView::drawItems() or QGraphicsScene::drawItems(). This flag was + introduced in Qt 4.3. + + \value ItemClipsChildrenToShape The item clips the painting of all its + descendants to its own shape. Items that are either direct or indirect + children of this item cannot draw outside this item's shape. By default, + this flag is disabled; children can draw anywhere. This behavior is + enforced by QGraphicsView::drawItems() or + QGraphicsScene::drawItems(). This flag was introduced in Qt 4.3. + + \value ItemIgnoresTransformations The item ignores inherited + transformations (i.e., its position is still anchored to its parent, but + the parent or view rotation, zoom or shear transformations are ignored). + This flag is useful for keeping text label items horizontal and unscaled, + so they will still be readable if the view is transformed. When set, the + item's view geometry and scene geometry will be maintained separately. You + must call deviceTransform() to map coordinates and detect collisions in + the view. By default, this flag is disabled. This flag was introduced in + Qt 4.3. \note With this flag set you can still scale the item itself, and + that scale transformation will influence the item's children. + + \value ItemIgnoresParentOpacity The item ignores its parent's opacity. The + item's effective opacity is the same as its own; it does not combine with + the parent's opacity. This flags allows your item to keep its absolute + opacity even if the parent is semitransparent. This flag was introduced in + Qt 4.5. + + \value ItemDoesntPropagateOpacityToChildren The item doesn't propagate its + opacity to its children. This flag allows you to create a semitransparent + item that does not affect the opacity of its children. This flag was + introduced in Qt 4.5. + + \value ItemStacksBehindParent The item is stacked behind its parent. By + default, child items are stacked on top of the parent item. But setting + this flag, the child will be stacked behind it. This flag is useful for + drop shadow effects and for decoration objects that follow the parent + item's geometry without drawing on top of it. This flag was introduced + in Qt 4.5. + + \value ItemUsesExtendedStyleOption The item makes use of either + \l{QStyleOptionGraphicsItem::} {exposedRect} or + \l{QStyleOptionGraphicsItem::} {matrix} in + QStyleOptionGraphicsItem. By default, the + \l{QStyleOptionGraphicsItem::} {exposedRect} is initialized to the + item's boundingRect() and the + \l{QStyleOptionGraphicsItem::}{matrix} is untransformed. You can + enable this flag for the style options to be set up with more + fine-grained values. Note that + QStyleOptionGraphicsItem::levelOfDetail is unaffected by this flag + and always initialized to 1. Use + QStyleOptionGraphicsItem::levelOfDetailFromTransform() if you need + a higher value. This flag was introduced in Qt 4.6. + + \value ItemHasNoContents The item does not paint anything (i.e., calling + paint() on the item has no effect). You should set this flag on items that + do not need to be painted to ensure that Graphics View avoids unnecessary + painting preparations. This flag was introduced in Qt 4.6. + + \value ItemSendsGeometryChanges The item enables itemChange() + notifications for ItemPositionChange, ItemPositionHasChanged, + ItemMatrixChange, ItemTransformChange, ItemTransformHasChanged, + ItemRotationChange, ItemRotationHasChanged, ItemScaleChange, ItemScaleHasChanged, + ItemTransformOriginPointChange, and ItemTransformOriginPointHasChanged. For + performance reasons, these notifications are disabled by default. You must + enable this flag to receive notifications for position and transform + changes. This flag was introduced in Qt 4.6. + + \value ItemAcceptsInputMethod The item supports input methods typically + used for Asian languages. + This flag was introduced in Qt 4.6. + + \value ItemNegativeZStacksBehindParent The item automatically + stacks behind it's parent if it's z-value is negative. This flag + enables setZValue() to toggle ItemStacksBehindParent. This flag + was introduced in Qt 4.6. + + \value ItemIsPanel The item is a panel. A panel provides activation and + contained focus handling. Only one panel can be active at a time (see + QGraphicsItem::isActive()). When no panel is active, QGraphicsScene + activates all non-panel items. Window items (i.e., + QGraphicsItem::isWindow() returns true) are panels. This flag was + introduced in Qt 4.6. + + \omitvalue ItemIsFocusScope \omit Internal only (for now). \endomit + + \value ItemSendsScenePositionChanges The item enables itemChange() + notifications for ItemScenePositionHasChanged. For performance reasons, + these notifications are disabled by default. You must enable this flag + to receive notifications for scene position changes. This flag was + introduced in Qt 4.6. + + \omitvalue ItemStopsClickFocusPropagation \omit The item stops propagating + click focus to items underneath when being clicked on. This flag + allows you create a non-focusable item that can be clicked on without + changing the focus. \endomit + + \omitvalue ItemStopsFocusHandling \omit Same as + ItemStopsClickFocusPropagation, but also suppresses focus-out. This flag + allows you to completely take over focus handling. + This flag was introduced in Qt 4.7. \endomit +*/ + +/*! + \enum QGraphicsItem::GraphicsItemChange + + This enum describes the state changes that are notified by + QGraphicsItem::itemChange(). The notifications are sent as the state + changes, and in some cases, adjustments can be made (see the documentation + for each change for details). + + Note: Be careful with calling functions on the QGraphicsItem itself inside + itemChange(), as certain function calls can lead to unwanted + recursion. For example, you cannot call setPos() in itemChange() on an + ItemPositionChange notification, as the setPos() function will again call + itemChange(ItemPositionChange). Instead, you can return the new, adjusted + position from itemChange(). + + \value ItemEnabledChange The item's enabled state changes. If the item is + presently enabled, it will become disabled, and vice verca. The value + argument is the new enabled state (i.e., true or false). Do not call + setEnabled() in itemChange() as this notification is delivered. Instead, + you can return the new state from itemChange(). + + \value ItemEnabledHasChanged The item's enabled state has changed. The + value argument is the new enabled state (i.e., true or false). Do not call + setEnabled() in itemChange() as this notification is delivered. The return + value is ignored. + + \value ItemMatrixChange The item's affine transformation matrix is + changing. This value is obsolete; you can use ItemTransformChange instead. + + \value ItemPositionChange The item's position changes. This notification + is sent if the ItemSendsGeometryChanges flag is enabled, and when the + item's local position changes, relative to its parent (i.e., as a result + of calling setPos() or moveBy()). The value argument is the new position + (i.e., a QPointF). You can call pos() to get the original position. Do + not call setPos() or moveBy() in itemChange() as this notification is + delivered; instead, you can return the new, adjusted position from + itemChange(). After this notification, QGraphicsItem immediately sends the + ItemPositionHasChanged notification if the position changed. + + \value ItemPositionHasChanged The item's position has changed. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + after the item's local position, relative to its parent, has changed. The + value argument is the new position (the same as pos()), and QGraphicsItem + ignores the return value for this notification (i.e., a read-only + notification). + + \value ItemTransformChange The item's transformation matrix changes. This + notification is send if the ItemSendsGeometryChanges flag is enabled, and + when the item's local transformation matrix changes (i.e., as a result of + calling setTransform(). The value argument is the new matrix (i.e., a + QTransform); to get the old matrix, call transform(). Do not call + setTransform() or set any of the transformation properties in itemChange() + as this notification is delivered; instead, you can return the new matrix + from itemChange(). This notification is not sent if you change the + transformation properties. + + \value ItemTransformHasChanged The item's transformation matrix has + changed either because setTransform is called, or one of the + transformation properties is changed. This notification is sent if the + ItemSendsGeometryChanges flag is enabled, and after the item's local + transformation matrix has changed. The value argument is the new matrix + (same as transform()), and QGraphicsItem ignores the return value for this + notification (i.e., a read-only notification). + + \value ItemRotationChange The item's rotation property changes. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + when the item's rotation property changes (i.e., as a result of calling + setRotation()). The value argument is the new rotation (i.e., a double); + to get the old rotation, call rotation(). Do not call setRotation() in + itemChange() as this notification is delivered; instead, you can return + the new rotation from itemChange(). + + \value ItemRotationHasChanged The item's rotation property has changed. + This notification is sent if the ItemSendsGeometryChanges flag is enabled, + and after the item's rotation property has changed. The value argument is + the new rotation (i.e., a double), and QGraphicsItem ignores the return + value for this notification (i.e., a read-only notification). Do not call + setRotation() in itemChange() as this notification is delivered. + + \value ItemScaleChange The item's scale property changes. This notification + is sent if the ItemSendsGeometryChanges flag is enabled, and when the item's + scale property changes (i.e., as a result of calling setScale()). The value + argument is the new scale (i.e., a double); to get the old scale, call + scale(). Do not call setScale() in itemChange() as this notification is + delivered; instead, you can return the new scale from itemChange(). + + \value ItemScaleHasChanged The item's scale property has changed. This + notification is sent if the ItemSendsGeometryChanges flag is enabled, and + after the item's scale property has changed. The value argument is the new + scale (i.e., a double), and QGraphicsItem ignores the return value for this + notification (i.e., a read-only notification). Do not call setScale() in + itemChange() as this notification is delivered. + + \value ItemTransformOriginPointChange The item's transform origin point + property changes. This notification is sent if the ItemSendsGeometryChanges + flag is enabled, and when the item's transform origin point property changes + (i.e., as a result of calling setTransformOriginPoint()). The value argument + is the new origin point (i.e., a QPointF); to get the old origin point, call + transformOriginPoint(). Do not call setTransformOriginPoint() in itemChange() + as this notification is delivered; instead, you can return the new transform + origin point from itemChange(). + + \value ItemTransformOriginPointHasChanged The item's transform origin point + property has changed. This notification is sent if the ItemSendsGeometryChanges + flag is enabled, and after the item's transform origin point property has + changed. The value argument is the new origin point (i.e., a QPointF), and + QGraphicsItem ignores the return value for this notification (i.e., a read-only + notification). Do not call setTransformOriginPoint() in itemChange() as this + notification is delivered. + + \value ItemSelectedChange The item's selected state changes. If the item is + presently selected, it will become unselected, and vice verca. The value + argument is the new selected state (i.e., true or false). Do not call + setSelected() in itemChange() as this notification is delivered; instead, you + can return the new selected state from itemChange(). + + \value ItemSelectedHasChanged The item's selected state has changed. The + value argument is the new selected state (i.e., true or false). Do not + call setSelected() in itemChange() as this notification is delivered. The + return value is ignored. + + \value ItemVisibleChange The item's visible state changes. If the item is + presently visible, it will become invisible, and vice verca. The value + argument is the new visible state (i.e., true or false). Do not call + setVisible() in itemChange() as this notification is delivered; instead, + you can return the new visible state from itemChange(). + + \value ItemVisibleHasChanged The item's visible state has changed. The + value argument is the new visible state (i.e., true or false). Do not call + setVisible() in itemChange() as this notification is delivered. The return + value is ignored. + + \value ItemParentChange The item's parent changes. The value argument is + the new parent item (i.e., a QGraphicsItem pointer). Do not call + setParentItem() in itemChange() as this notification is delivered; + instead, you can return the new parent from itemChange(). + + \value ItemParentHasChanged The item's parent has changed. The value + argument is the new parent (i.e., a pointer to a QGraphicsItem). Do not + call setParentItem() in itemChange() as this notification is + delivered. The return value is ignored. + + \value ItemChildAddedChange A child is added to this item. The value + argument is the new child item (i.e., a QGraphicsItem pointer). Do not + pass this item to any item's setParentItem() function as this notification + is delivered. The return value is unused; you cannot adjust anything in + this notification. Note that the new child might not be fully constructed + when this notification is sent; calling pure virtual functions on + the child can lead to a crash. + + \value ItemChildRemovedChange A child is removed from this item. The value + argument is the child item that is about to be removed (i.e., a + QGraphicsItem pointer). The return value is unused; you cannot adjust + anything in this notification. + + \value ItemSceneChange The item is moved to a new scene. This notification is + also sent when the item is added to its initial scene, and when it is removed. + The item's scene() is the old scene (or 0 if the item has not been added to a + scene yet). The value argument is the new scene (i.e., a QGraphicsScene + pointer), or a null pointer if the item is removed from a scene. Do not + override this change by passing this item to QGraphicsScene::addItem() as this + notification is delivered; instead, you can return the new scene from + itemChange(). Use this feature with caution; objecting to a scene change can + quickly lead to unwanted recursion. + + \value ItemSceneHasChanged The item's scene has changed. The item's scene() is + the new scene. This notification is also sent when the item is added to its + initial scene, and when it is removed.The value argument is the new scene + (i.e., a pointer to a QGraphicsScene). Do not call setScene() in itemChange() + as this notification is delivered. The return value is ignored. + + \value ItemCursorChange The item's cursor changes. The value argument is + the new cursor (i.e., a QCursor). Do not call setCursor() in itemChange() + as this notification is delivered. Instead, you can return a new cursor + from itemChange(). + + \value ItemCursorHasChanged The item's cursor has changed. The value + argument is the new cursor (i.e., a QCursor). Do not call setCursor() as + this notification is delivered. The return value is ignored. + + \value ItemToolTipChange The item's tooltip changes. The value argument is + the new tooltip (i.e., a QToolTip). Do not call setToolTip() in + itemChange() as this notification is delivered. Instead, you can return a + new tooltip from itemChange(). + + \value ItemToolTipHasChanged The item's tooltip has changed. The value + argument is the new tooltip (i.e., a QToolTip). Do not call setToolTip() + as this notification is delivered. The return value is ignored. + + \value ItemFlagsChange The item's flags change. The value argument is the + new flags (i.e., a quint32). Do not call setFlags() in itemChange() as + this notification is delivered. Instead, you can return the new flags from + itemChange(). + + \value ItemFlagsHaveChanged The item's flags have changed. The value + argument is the new flags (i.e., a quint32). Do not call setFlags() in + itemChange() as this notification is delivered. The return value is + ignored. + + \value ItemZValueChange The item's Z-value changes. The value argument is + the new Z-value (i.e., a double). Do not call setZValue() in itemChange() + as this notification is delivered. Instead, you can return a new Z-value + from itemChange(). + + \value ItemZValueHasChanged The item's Z-value has changed. The value + argument is the new Z-value (i.e., a double). Do not call setZValue() as + this notification is delivered. The return value is ignored. + + \value ItemOpacityChange The item's opacity changes. The value argument is + the new opacity (i.e., a double). Do not call setOpacity() in itemChange() + as this notification is delivered. Instead, you can return a new opacity + from itemChange(). + + \value ItemOpacityHasChanged The item's opacity has changed. The value + argument is the new opacity (i.e., a double). Do not call setOpacity() as + this notification is delivered. The return value is ignored. + + \value ItemScenePositionHasChanged The item's scene position has changed. + This notification is sent if the ItemSendsScenePositionChanges flag is + enabled, and after the item's scene position has changed (i.e., the + position or transformation of the item itself or the position or + transformation of any ancestor has changed). The value argument is the + new scene position (the same as scenePos()), and QGraphicsItem ignores + the return value for this notification (i.e., a read-only notification). +*/ + +/*! + \enum QGraphicsItem::CacheMode + \since 4.4 + + This enum describes QGraphicsItem's cache modes. Caching is used to speed + up rendering by allocating and rendering to an off-screen pixel buffer, + which can be reused when the item requires redrawing. For some paint + devices, the cache is stored directly in graphics memory, which makes + rendering very quick. + + \value NoCache The default; all item caching is + disabled. QGraphicsItem::paint() is called every time the item needs + redrawing. + + \value ItemCoordinateCache Caching is enabled for the item's logical + (local) coordinate system. QGraphicsItem creates an off-screen pixel + buffer with a configurable size / resolution that you can pass to + QGraphicsItem::setCacheMode(). Rendering quality will typically degrade, + depending on the resolution of the cache and the item transformation. The + first time the item is redrawn, it will render itself into the cache, and + the cache is then reused for every subsequent expose. The cache is also + reused as the item is transformed. To adjust the resolution of the cache, + you can call setCacheMode() again. + + \value DeviceCoordinateCache Caching is enabled at the paint device level, + in device coordinates. This mode is for items that can move, but are not + rotated, scaled or sheared. If the item is transformed directly or + indirectly, the cache will be regenerated automatically. Unlike + ItemCoordinateCacheMode, DeviceCoordinateCache always renders at maximum + quality. + + \sa QGraphicsItem::setCacheMode() +*/ + +/*! + \enum QGraphicsItem::Extension + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. See also extension(), + supportsExtension() and setExtension(). +*/ + +/*! + \enum QGraphicsItem::PanelModality + \since 4.6 + + This enum specifies the behavior of a modal panel. A modal panel + is one that blocks input to other panels. Note that items that + are children of a modal panel are not blocked. + + The values are: + + \value NonModal The panel is not modal and does not block input to + other panels. This is the default value for panels. + + \value PanelModal The panel is modal to a single item hierarchy + and blocks input to its parent pane, all grandparent panels, and + all siblings of its parent and grandparent panels. + + \value SceneModal The window is modal to the entire scene and + blocks input to all panels. + + \sa QGraphicsItem::setPanelModality(), QGraphicsItem::panelModality(), QGraphicsItem::ItemIsPanel +*/ + +#include "qgraphicsitem.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsscene.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicswidget.h" +#include "qgraphicsproxywidget.h" +#include "qgraphicsscenebsptreeindex_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include +#include +#endif + +#include + +#include + +QT_BEGIN_NAMESPACE + +static inline void _q_adjustRect(QRect *rect) +{ + Q_ASSERT(rect); + if (!rect->width()) + rect->adjust(0, 0, 1, 0); + if (!rect->height()) + rect->adjust(0, 0, 0, 1); +} + +/* + ### Move this into QGraphicsItemPrivate + */ +class QGraphicsItemCustomDataStore +{ +public: + QMap > data; +}; +Q_GLOBAL_STATIC(QGraphicsItemCustomDataStore, qt_dataStore) + +/*! + \internal + + Returns a QPainterPath of \a path when stroked with the \a pen. + Ignoring dash pattern. +*/ +static QPainterPath qt_graphicsItem_shapeFromPath(const QPainterPath &path, const QPen &pen) +{ + // We unfortunately need this hack as QPainterPathStroker will set a width of 1.0 + // if we pass a value of 0.0 to QPainterPathStroker::setWidth() + const qreal penWidthZero = qreal(0.00000001); + + if (path == QPainterPath()) + return path; + QPainterPathStroker ps; + ps.setCapStyle(pen.capStyle()); + if (pen.widthF() <= 0.0) + ps.setWidth(penWidthZero); + else + ps.setWidth(pen.widthF()); + ps.setJoinStyle(pen.joinStyle()); + ps.setMiterLimit(pen.miterLimit()); + QPainterPath p = ps.createStroke(path); + p.addPath(path); + return p; +} + +/*! + \internal + + Propagates the ancestor flag \a flag with value \a enabled to all this + item's children. If \a root is false, the flag is also set on this item + (default is true). +*/ +void QGraphicsItemPrivate::updateAncestorFlag(QGraphicsItem::GraphicsItemFlag childFlag, + AncestorFlag flag, bool enabled, bool root) +{ + Q_Q(QGraphicsItem); + if (root) { + // For root items only. This is the item that has either enabled or + // disabled \a childFlag, or has been reparented. + switch (int(childFlag)) { + case -2: + flag = AncestorFiltersChildEvents; + enabled = q->filtersChildEvents(); + break; + case -1: + flag = AncestorHandlesChildEvents; + enabled = q->handlesChildEvents(); + break; + case QGraphicsItem::ItemClipsChildrenToShape: + flag = AncestorClipsChildren; + enabled = flags & QGraphicsItem::ItemClipsChildrenToShape; + break; + case QGraphicsItem::ItemIgnoresTransformations: + flag = AncestorIgnoresTransformations; + enabled = flags & QGraphicsItem::ItemIgnoresTransformations; + break; + default: + return; + } + + if (parent) { + // Inherit the enabled-state from our parents. + if ((parent->d_ptr->ancestorFlags & flag) + || (int(parent->d_ptr->flags & childFlag) == childFlag) + || (childFlag == -1 && parent->d_ptr->handlesChildEvents) + || (childFlag == -2 && parent->d_ptr->filtersDescendantEvents)) { + enabled = true; + ancestorFlags |= flag; + } else { + ancestorFlags &= ~flag; + } + } else { + // Top-level root items don't have any ancestors, so there are no + // ancestor flags either. + ancestorFlags = 0; + } + } else { + // Don't set or propagate the ancestor flag if it's already correct. + if (((ancestorFlags & flag) && enabled) || (!(ancestorFlags & flag) && !enabled)) + return; + + // Set the flag. + if (enabled) + ancestorFlags |= flag; + else + ancestorFlags &= ~flag; + + // Don't process children if the item has the main flag set on itself. + if ((childFlag != -1 && int(flags & childFlag) == childFlag) + || (int(childFlag) == -1 && handlesChildEvents) + || (int(childFlag) == -2 && filtersDescendantEvents)) + return; + } + + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updateAncestorFlag(childFlag, flag, enabled, false); +} + +void QGraphicsItemPrivate::updateAncestorFlags() +{ + int flags = 0; + if (parent) { + // Inherit the parent's ancestor flags. + QGraphicsItemPrivate *pd = parent->d_ptr.data(); + flags = pd->ancestorFlags; + + // Add in flags from the parent. + if (pd->filtersDescendantEvents) + flags |= AncestorFiltersChildEvents; + if (pd->handlesChildEvents) + flags |= AncestorHandlesChildEvents; + if (pd->flags & QGraphicsItem::ItemClipsChildrenToShape) + flags |= AncestorClipsChildren; + if (pd->flags & QGraphicsItem::ItemIgnoresTransformations) + flags |= AncestorIgnoresTransformations; + } + + if (ancestorFlags == flags) + return; // No change; stop propagation. + ancestorFlags = flags; + + // Propagate to children recursively. + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updateAncestorFlags(); +} + +/*! + \internal + + Propagates item group membership. +*/ +void QGraphicsItemPrivate::setIsMemberOfGroup(bool enabled) +{ + Q_Q(QGraphicsItem); + isMemberOfGroup = enabled; + if (!qgraphicsitem_cast(q)) { + foreach (QGraphicsItem *child, children) + child->d_func()->setIsMemberOfGroup(enabled); + } +} + +/*! + \internal + + Maps any item pos properties of \a event to \a item's coordinate system. +*/ +void QGraphicsItemPrivate::remapItemPos(QEvent *event, QGraphicsItem *item) +{ + Q_Q(QGraphicsItem); + switch (event->type()) { + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + mouseEvent->setPos(item->mapFromItem(q, mouseEvent->pos())); + mouseEvent->setLastPos(item->mapFromItem(q, mouseEvent->pos())); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (mouseEvent->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent->setButtonDownPos(button, item->mapFromItem(q, mouseEvent->buttonDownPos(button))); + } + } + break; + } + case QEvent::GraphicsSceneWheel: { + QGraphicsSceneWheelEvent *wheelEvent = static_cast(event); + wheelEvent->setPos(item->mapFromItem(q, wheelEvent->pos())); + break; + } + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *contextEvent = static_cast(event); + contextEvent->setPos(item->mapFromItem(q, contextEvent->pos())); + break; + } + case QEvent::GraphicsSceneHoverMove: { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + hoverEvent->setPos(item->mapFromItem(q, hoverEvent->pos())); + break; + } + default: + break; + } +} + +/*! + \internal + + Maps the point \a pos from scene to item coordinates. If \a view is passed and the item + is untransformable, this function will correctly map \a pos from the scene using the + view's transformation. +*/ +QPointF QGraphicsItemPrivate::genericMapFromScene(const QPointF &pos, + const QWidget *viewport) const +{ + Q_Q(const QGraphicsItem); + if (!itemIsUntransformable()) + return q->mapFromScene(pos); + QGraphicsView *view = 0; + if (viewport) + view = qobject_cast(viewport->parentWidget()); + if (!view) + return q->mapFromScene(pos); + // ### More ping pong than needed. + return q->deviceTransform(view->viewportTransform()).inverted().map(view->mapFromScene(pos)); +} + +/*! + \internal + + Combines this item's position and transform onto \a transform. + + If you need to change this function (e.g., adding more transformation + modes / options), make sure to change all places marked with COMBINE. +*/ +void QGraphicsItemPrivate::combineTransformToParent(QTransform *x, const QTransform *viewTransform) const +{ + // COMBINE + if (viewTransform && itemIsUntransformable()) { + *x = q_ptr->deviceTransform(*viewTransform); + } else { + if (transformData) + *x *= transformData->computedFullTransform(); + if (!pos.isNull()) + *x *= QTransform::fromTranslate(pos.x(), pos.y()); + } +} + +/*! + \internal + + Combines this item's position and transform onto \a transform. + + If you need to change this function (e.g., adding more transformation + modes / options), make sure to change QGraphicsItem::deviceTransform() as + well. +*/ +void QGraphicsItemPrivate::combineTransformFromParent(QTransform *x, const QTransform *viewTransform) const +{ + // COMBINE + if (viewTransform && itemIsUntransformable()) { + *x = q_ptr->deviceTransform(*viewTransform); + } else { + x->translate(pos.x(), pos.y()); + if (transformData) + *x = transformData->computedFullTransform(x); + } +} + +void QGraphicsItemPrivate::updateSceneTransformFromParent() +{ + if (parent) { + Q_ASSERT(!parent->d_ptr->dirtySceneTransform); + if (parent->d_ptr->sceneTransformTranslateOnly) { + sceneTransform = QTransform::fromTranslate(parent->d_ptr->sceneTransform.dx() + pos.x(), + parent->d_ptr->sceneTransform.dy() + pos.y()); + } else { + sceneTransform = parent->d_ptr->sceneTransform; + sceneTransform.translate(pos.x(), pos.y()); + } + if (transformData) { + sceneTransform = transformData->computedFullTransform(&sceneTransform); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else { + sceneTransformTranslateOnly = parent->d_ptr->sceneTransformTranslateOnly; + } + } else if (!transformData) { + sceneTransform = QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransformTranslateOnly = 1; + } else if (transformData->onlyTransform) { + sceneTransform = transformData->transform; + if (!pos.isNull()) + sceneTransform *= QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else if (pos.isNull()) { + sceneTransform = transformData->computedFullTransform(); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } else { + sceneTransform = QTransform::fromTranslate(pos.x(), pos.y()); + sceneTransform = transformData->computedFullTransform(&sceneTransform); + sceneTransformTranslateOnly = (sceneTransform.type() <= QTransform::TxTranslate); + } + dirtySceneTransform = 0; +} + +/*! + \internal + + This helper function helped us add input method query support in + Qt 4.4.1 without having to reimplement the inputMethodQuery() + function in QGraphicsProxyWidget. ### Qt 5: Remove. We cannot + remove it in 4.5+ even if we do reimplement the function properly, + because apps compiled with 4.4 will not be able to call the + reimplementation. +*/ +QVariant QGraphicsItemPrivate::inputMethodQueryHelper(Qt::InputMethodQuery query) const +{ + Q_UNUSED(query); + return QVariant(); +} + +/*! + \internal + + Make sure not to trigger any pure virtual function calls (e.g., + prepareGeometryChange) if the item is in its destructor, i.e. + inDestructor is 1. +*/ +void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent, const QVariant *newParentVariant, + const QVariant *thisPointerVariant) +{ + Q_Q(QGraphicsItem); + if (newParent == parent) + return; + + if (isWidget) + static_cast(this)->fixFocusChainBeforeReparenting((newParent && + newParent->isWidget()) ? static_cast(newParent) : 0, + scene); + if (scene) { + // Deliver the change to the index + if (scene->d_func()->indexMethod != QGraphicsScene::NoIndex) + scene->d_func()->index->itemChange(q, QGraphicsItem::ItemParentChange, newParent); + + // Disable scene pos notifications for old ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, false); + } + + if (subFocusItem && parent) { + // Make sure none of the old parents point to this guy. + subFocusItem->d_ptr->clearSubFocus(parent); + } + + // We anticipate geometry changes. If the item is deleted, it will be + // removed from the index at a later stage, and the whole scene will be + // updated. + if (!inDestructor) + q_ptr->prepareGeometryChange(); + + if (parent) { + // Remove from current parent + parent->d_ptr->removeChild(q); + if (thisPointerVariant) + parent->itemChange(QGraphicsItem::ItemChildRemovedChange, *thisPointerVariant); + } + + // Update toplevelitem list. If this item is being deleted, its parent + // will be 0 but we don't want to register/unregister it in the TLI list. + if (scene && !inDestructor) { + if (parent && !newParent) { + scene->d_func()->registerTopLevelItem(q); + } else if (!parent && newParent) { + scene->d_func()->unregisterTopLevelItem(q); + } + } + + // Ensure any last parent focus scope does not point to this item or any of + // its descendents. + QGraphicsItem *p = parent; + QGraphicsItem *parentFocusScopeItem = 0; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) { + // If this item's focus scope's focus scope item points + // to this item or a descendent, then clear it. + QGraphicsItem *fsi = p->d_ptr->focusScopeItem; + if (q_ptr == fsi || q_ptr->isAncestorOf(fsi)) { + parentFocusScopeItem = fsi; + p->d_ptr->focusScopeItem = 0; + fsi->d_ptr->focusScopeItemChange(false); + } + break; + } + p = p->d_ptr->parent; + } + + // Update graphics effect optimization flag + if (newParent && (graphicsEffect || mayHaveChildWithGraphicsEffect)) + newParent->d_ptr->updateChildWithGraphicsEffectFlagRecursively(); + + // Update focus scope item ptr in new scope. + QGraphicsItem *newFocusScopeItem = subFocusItem ? subFocusItem : parentFocusScopeItem; + if (newFocusScopeItem && newParent) { + if (subFocusItem) { + // Find the subFocusItem's topmost focus scope. + QGraphicsItem *ancestorScope = 0; + QGraphicsItem *p = subFocusItem->d_ptr->parent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) + ancestorScope = p; + if (p->d_ptr->flags & QGraphicsItem::ItemIsPanel) + break; + p = p->d_ptr->parent; + } + if (ancestorScope) + newFocusScopeItem = ancestorScope; + } + + QGraphicsItem *p = newParent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemIsFocusScope) { + p->d_ptr->focusScopeItem = newFocusScopeItem; + newFocusScopeItem->d_ptr->focusScopeItemChange(true); + // Ensure the new item is no longer the subFocusItem. The + // only way to set focus on a child of a focus scope is + // by setting focus on the scope itself. + if (subFocusItem && !p->focusItem()) + subFocusItem->d_ptr->clearSubFocus(); + break; + } + p = p->d_ptr->parent; + } + } + + // Resolve depth. + invalidateDepthRecursively(); + + if ((parent = newParent)) { + if (parent->d_func()->scene && parent->d_func()->scene != scene) { + // Move this item to its new parent's scene + parent->d_func()->scene->addItem(q); + } else if (!parent->d_func()->scene && scene) { + // Remove this item from its former scene + scene->removeItem(q); + } + + parent->d_ptr->addChild(q); + if (thisPointerVariant) + parent->itemChange(QGraphicsItem::ItemChildAddedChange, *thisPointerVariant); + if (scene) { + // Re-enable scene pos notifications for new ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, true); + } + + // Propagate dirty flags to the new parent + markParentDirty(/*updateBoundingRect=*/true); + + // Inherit ancestor flags from the new parent. + updateAncestorFlags(); + + // Update item visible / enabled. + if (parent->d_ptr->visible != visible) { + if (!parent->d_ptr->visible || !explicitlyHidden) + setVisibleHelper(parent->d_ptr->visible, /* explicit = */ false, /* update = */ false); + } + if (parent->isEnabled() != enabled) { + if (!parent->d_ptr->enabled || !explicitlyDisabled) + setEnabledHelper(parent->d_ptr->enabled, /* explicit = */ false, /* update = */ false); + } + + // Auto-activate if visible and the parent is active. + if (visible && parent->isActive()) + q->setActive(true); + } else { + // Inherit ancestor flags from the new parent. + updateAncestorFlags(); + + if (!inDestructor) { + // Update item visible / enabled. + if (!visible && !explicitlyHidden) + setVisibleHelper(true, /* explicit = */ false); + if (!enabled && !explicitlyDisabled) + setEnabledHelper(true, /* explicit = */ false); + } + } + + dirtySceneTransform = 1; + if (!inDestructor && (transformData || (newParent && newParent->d_ptr->transformData))) + transformChanged(); + + // Restore the sub focus chain. + if (subFocusItem) { + subFocusItem->d_ptr->setSubFocus(newParent); + if (parent && parent->isActive()) + subFocusItem->setFocus(); + } + + // Deliver post-change notification + if (newParentVariant) + q->itemChange(QGraphicsItem::ItemParentHasChanged, *newParentVariant); + + if (isObject) + emit static_cast(q)->parentChanged(); +} + +/*! + \internal + + Returns the bounding rect of this item's children (excluding itself). +*/ +void QGraphicsItemPrivate::childrenBoundingRectHelper(QTransform *x, QRectF *rect, QGraphicsItem *topMostEffectItem) +{ + Q_Q(QGraphicsItem); + + QRectF childrenRect; + QRectF *result = rect; + rect = &childrenRect; + const bool setTopMostEffectItem = !topMostEffectItem; + + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *child = children.at(i); + QGraphicsItemPrivate *childd = child->d_ptr.data(); + if (setTopMostEffectItem) + topMostEffectItem = child; + bool hasPos = !childd->pos.isNull(); + if (hasPos || childd->transformData) { + // COMBINE + QTransform matrix = childd->transformToParent(); + if (x) + matrix *= *x; + *rect |= matrix.mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem)); + if (!childd->children.isEmpty()) + childd->childrenBoundingRectHelper(&matrix, rect, topMostEffectItem); + } else { + if (x) + *rect |= x->mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem)); + else + *rect |= child->d_ptr->effectiveBoundingRect(topMostEffectItem); + if (!childd->children.isEmpty()) + childd->childrenBoundingRectHelper(x, rect, topMostEffectItem); + } + } + + if (flags & QGraphicsItem::ItemClipsChildrenToShape){ + if (x) + *rect &= x->mapRect(q->boundingRect()); + else + *rect &= q->boundingRect(); + } + + *result |= *rect; +} + +void QGraphicsItemPrivate::initStyleOption(QStyleOptionGraphicsItem *option, const QTransform &worldTransform, + const QRegion &exposedRegion, bool allItems) const +{ + Q_ASSERT(option); + Q_Q(const QGraphicsItem); + + // Initialize standard QStyleOption values. + const QRectF brect = q->boundingRect(); + option->state = QStyle::State_None; + option->rect = brect.toRect(); + option->levelOfDetail = 1; + option->exposedRect = brect; + if (selected) + option->state |= QStyle::State_Selected; + if (enabled) + option->state |= QStyle::State_Enabled; + if (q->hasFocus()) + option->state |= QStyle::State_HasFocus; + if (scene) { + if (scene->d_func()->hoverItems.contains(q_ptr)) + option->state |= QStyle::State_MouseOver; + if (q == scene->mouseGrabberItem()) + option->state |= QStyle::State_Sunken; + } + + if (!(flags & QGraphicsItem::ItemUsesExtendedStyleOption)) + return; + + // Initialize QStyleOptionGraphicsItem specific values (matrix, exposedRect). + option->matrix = worldTransform.toAffine(); //### discards perspective + + if (!allItems) { + // Determine the item's exposed area + option->exposedRect = QRectF(); + const QTransform reverseMap = worldTransform.inverted(); + const QVector exposedRects(exposedRegion.rects()); + for (int i = 0; i < exposedRects.size(); ++i) { + option->exposedRect |= reverseMap.mapRect(QRectF(exposedRects.at(i))); + if (option->exposedRect.contains(brect)) + break; + } + option->exposedRect &= brect; + } +} + +/*! + \internal + + Empty all cached pixmaps from the pixmap cache. +*/ +void QGraphicsItemCache::purge() +{ + QPixmapCache::remove(key); + key = QPixmapCache::Key(); + QMutableMapIterator it(deviceData); + while (it.hasNext()) { + DeviceData &data = it.next().value(); + QPixmapCache::remove(data.key); + data.cacheIndent = QPoint(); + } + deviceData.clear(); + allExposed = true; + exposed.clear(); +} + +/*! + Constructs a QGraphicsItem with the given \a parent item. + It does not modify the parent object returned by QObject::parent(). + + If \a parent is 0, you can add the item to a scene by calling + QGraphicsScene::addItem(). The item will then become a top-level item. + + \sa QGraphicsScene::addItem(), setParentItem() +*/ +QGraphicsItem::QGraphicsItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : d_ptr(new QGraphicsItemPrivate) +{ + d_ptr->q_ptr = this; + setParentItem(parent); + + if (scene && parent && parent->scene() != scene) { + qWarning("QGraphicsItem::QGraphicsItem: ignoring scene (%p), which is" + " different from parent's scene (%p)", + scene, parent->scene()); + return; + } + if (scene && !parent) + scene->addItem(this); +} + +/*! + \internal +*/ +QGraphicsItem::QGraphicsItem(QGraphicsItemPrivate &dd, QGraphicsItem *parent, + QGraphicsScene *scene) + : d_ptr(&dd) +{ + d_ptr->q_ptr = this; + setParentItem(parent); + + if (scene && parent && parent->scene() != scene) { + qWarning("QGraphicsItem::QGraphicsItem: ignoring scene (%p), which is" + " different from parent's scene (%p)", + scene, parent->scene()); + return; + } + if (scene && !parent) + scene->addItem(this); +} + +/*! + Destroys the QGraphicsItem and all its children. If this item is currently + associated with a scene, the item will be removed from the scene before it + is deleted. + + \note It is more efficient to remove the item from the QGraphicsScene before + destroying the item. +*/ +QGraphicsItem::~QGraphicsItem() +{ + if (d_ptr->isObject) { + QGraphicsObject *o = static_cast(this); + QObjectPrivate *p = QObjectPrivate::get(o); + p->wasDeleted = true; + if (p->declarativeData) { + QAbstractDeclarativeData::destroyed(p->declarativeData, o); + p->declarativeData = 0; + } + } + + d_ptr->inDestructor = 1; + d_ptr->removeExtraItemCache(); + +#ifndef QT_NO_GESTURES + if (d_ptr->isObject && !d_ptr->gestureContext.isEmpty()) { + QGraphicsObject *o = static_cast(this); + if (QGestureManager *manager = QGestureManager::instance()) { + foreach (Qt::GestureType type, d_ptr->gestureContext.keys()) + manager->cleanupCachedGestures(o, type); + } + } +#endif + + clearFocus(); + + // Update focus scope item ptr. + QGraphicsItem *p = d_ptr->parent; + while (p) { + if (p->flags() & ItemIsFocusScope) { + if (p->d_ptr->focusScopeItem == this) + p->d_ptr->focusScopeItem = 0; + break; + } + p = p->d_ptr->parent; + } + + if (!d_ptr->children.isEmpty()) { + while (!d_ptr->children.isEmpty()) + delete d_ptr->children.first(); + Q_ASSERT(d_ptr->children.isEmpty()); + } + + if (d_ptr->scene) { + d_ptr->scene->d_func()->removeItemHelper(this); + } else { + d_ptr->resetFocusProxy(); + setParentItem(0); + } + +#ifndef QT_NO_GRAPHICSEFFECT + delete d_ptr->graphicsEffect; +#endif //QT_NO_GRAPHICSEFFECT + if (d_ptr->transformData) { + for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) { + QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i); + static_cast(t->d_ptr.data())->item = 0; + delete t; + } + } + delete d_ptr->transformData; + + if (QGraphicsItemCustomDataStore *dataStore = qt_dataStore()) + dataStore->data.remove(this); +} + +/*! + Returns the current scene for the item, or 0 if the item is not stored in + a scene. + + To add or move an item to a scene, call QGraphicsScene::addItem(). +*/ +QGraphicsScene *QGraphicsItem::scene() const +{ + return d_ptr->scene; +} + +/*! + Returns a pointer to this item's item group, or 0 if this item is not + member of a group. + + \sa QGraphicsItemGroup, QGraphicsScene::createItemGroup() +*/ +QGraphicsItemGroup *QGraphicsItem::group() const +{ + if (!d_ptr->isMemberOfGroup) + return 0; + QGraphicsItem *parent = const_cast(this); + while ((parent = parent->d_ptr->parent)) { + if (QGraphicsItemGroup *group = qgraphicsitem_cast(parent)) + return group; + } + // Unreachable; if d_ptr->isMemberOfGroup is != 0, then one parent of this + // item is a group item. + return 0; +} + +/*! + Adds this item to the item group \a group. If \a group is 0, this item is + removed from any current group and added as a child of the previous + group's parent. + + \sa group(), QGraphicsScene::createItemGroup() +*/ +void QGraphicsItem::setGroup(QGraphicsItemGroup *group) +{ + if (!group) { + if (QGraphicsItemGroup *group = this->group()) + group->removeFromGroup(this); + } else { + group->addToGroup(this); + } +} + +/*! + Returns a pointer to this item's parent item. If this item does not have a + parent, 0 is returned. + + \sa setParentItem(), childItems() +*/ +QGraphicsItem *QGraphicsItem::parentItem() const +{ + return d_ptr->parent; +} + +/*! + Returns this item's top-level item. The top-level item is the item's + topmost ancestor item whose parent is 0. If an item has no parent, its own + pointer is returned (i.e., a top-level item is its own top-level item). + + \sa parentItem() +*/ +QGraphicsItem *QGraphicsItem::topLevelItem() const +{ + QGraphicsItem *parent = const_cast(this); + while (QGraphicsItem *grandPa = parent->parentItem()) + parent = grandPa; + return parent; +} + +/*! + \since 4.6 + + Returns a pointer to the item's parent, cast to a QGraphicsObject. returns 0 if the parent item + is not a QGraphicsObject. + + \sa parentItem(), childItems() +*/ +QGraphicsObject *QGraphicsItem::parentObject() const +{ + QGraphicsItem *p = d_ptr->parent; + return (p && p->d_ptr->isObject) ? static_cast(p) : 0; +} + +/*! + \since 4.4 + + Returns a pointer to the item's parent widget. The item's parent widget is + the closest parent item that is a widget. + + \sa parentItem(), childItems() +*/ +QGraphicsWidget *QGraphicsItem::parentWidget() const +{ + QGraphicsItem *p = parentItem(); + while (p && !p->isWidget()) + p = p->parentItem(); + return (p && p->isWidget()) ? static_cast(p) : 0; +} + +/*! + \since 4.4 + + Returns a pointer to the item's top level widget (i.e., the item's + ancestor whose parent is 0, or whose parent is not a widget), or 0 if this + item does not have a top level widget. If the item is its own top level + widget, this function returns a pointer to the item itself. +*/ +QGraphicsWidget *QGraphicsItem::topLevelWidget() const +{ + if (const QGraphicsWidget *p = parentWidget()) + return p->topLevelWidget(); + return isWidget() ? static_cast(const_cast(this)) : 0; +} + +/*! + \since 4.4 + + Returns the item's window, or 0 if this item does not have a window. If + the item is a window, it will return itself. Otherwise it will return the + closest ancestor that is a window. + + \sa QGraphicsWidget::isWindow() +*/ +QGraphicsWidget *QGraphicsItem::window() const +{ + QGraphicsItem *p = panel(); + if (p && p->isWindow()) + return static_cast(p); + return 0; +} + +/*! + \since 4.6 + + Returns the item's panel, or 0 if this item does not have a panel. If the + item is a panel, it will return itself. Otherwise it will return the + closest ancestor that is a panel. + + \sa isPanel(), ItemIsPanel +*/ +QGraphicsItem *QGraphicsItem::panel() const +{ + if (d_ptr->flags & ItemIsPanel) + return const_cast(this); + return d_ptr->parent ? d_ptr->parent->panel() : 0; +} + +/*! + \since 4.6 + + Return the graphics item cast to a QGraphicsObject, if the class is actually a + graphics object, 0 otherwise. +*/ +QGraphicsObject *QGraphicsItem::toGraphicsObject() +{ + return d_ptr->isObject ? static_cast(this) : 0; +} + +/*! + \since 4.6 + + Return the graphics item cast to a QGraphicsObject, if the class is actually a + graphics object, 0 otherwise. +*/ +const QGraphicsObject *QGraphicsItem::toGraphicsObject() const +{ + return d_ptr->isObject ? static_cast(this) : 0; +} + +/*! + Sets this item's parent item to \a newParent. If this item already + has a parent, it is first removed from the previous parent. If \a + newParent is 0, this item will become a top-level item. + + Note that this implicitly adds this graphics item to the scene of + the parent. You should not \l{QGraphicsScene::addItem()}{add} the + item to the scene yourself. + + Calling this function on an item that is an ancestor of \a newParent + have undefined behaviour. + + \sa parentItem(), childItems() +*/ +void QGraphicsItem::setParentItem(QGraphicsItem *newParent) +{ + if (newParent == this) { + qWarning("QGraphicsItem::setParentItem: cannot assign %p as a parent of itself", this); + return; + } + if (newParent == d_ptr->parent) + return; + + const QVariant newParentVariant(itemChange(QGraphicsItem::ItemParentChange, + QVariant::fromValue(newParent))); + newParent = qvariant_cast(newParentVariant); + if (newParent == d_ptr->parent) + return; + + const QVariant thisPointerVariant(QVariant::fromValue(this)); + d_ptr->setParentItemHelper(newParent, &newParentVariant, &thisPointerVariant); +} + +/*! + \obsolete + + Use childItems() instead. + + \sa setParentItem() +*/ +QList QGraphicsItem::children() const +{ + return childItems(); +} + +/*! + \since 4.4 + + Returns a list of this item's children. + + The items are sorted by stacking order. This takes into account both the + items' insertion order and their Z-values. + + \sa setParentItem(), zValue(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsItem::childItems() const +{ + const_cast(this)->d_ptr->ensureSortedChildren(); + return d_ptr->children; +} + +/*! + \since 4.4 + Returns true if this item is a widget (i.e., QGraphicsWidget); otherwise, + returns false. +*/ +bool QGraphicsItem::isWidget() const +{ + return d_ptr->isWidget; +} + +/*! + \since 4.4 + Returns true if the item is a QGraphicsWidget window, otherwise returns + false. + + \sa QGraphicsWidget::windowFlags() +*/ +bool QGraphicsItem::isWindow() const +{ + return d_ptr->isWidget && (static_cast(this)->windowType() & Qt::Window); +} + +/*! + \since 4.6 + Returns true if the item is a panel; otherwise returns false. + + \sa QGraphicsItem::panel(), ItemIsPanel +*/ +bool QGraphicsItem::isPanel() const +{ + return d_ptr->flags & ItemIsPanel; +} + +/*! + Returns this item's flags. The flags describe what configurable features + of the item are enabled and not. For example, if the flags include + ItemIsFocusable, the item can accept input focus. + + By default, no flags are enabled. + + \sa setFlags(), setFlag() +*/ +QGraphicsItem::GraphicsItemFlags QGraphicsItem::flags() const +{ + return GraphicsItemFlags(d_ptr->flags); +} + +/*! + If \a enabled is true, the item flag \a flag is enabled; otherwise, it is + disabled. + + \sa flags(), setFlags() +*/ +void QGraphicsItem::setFlag(GraphicsItemFlag flag, bool enabled) +{ + if (enabled) + setFlags(GraphicsItemFlags(d_ptr->flags) | flag); + else + setFlags(GraphicsItemFlags(d_ptr->flags) & ~flag); +} + +/*! + \internal + + Sets the flag \a flag on \a item and all its children, to \a enabled. +*/ +static void _q_qgraphicsItemSetFlag(QGraphicsItem *item, QGraphicsItem::GraphicsItemFlag flag, + bool enabled) +{ + if (item->flags() & flag) { + // If this item already has the correct flag set, we don't have to + // propagate it. + return; + } + item->setFlag(flag, enabled); + foreach (QGraphicsItem *child, item->children()) + _q_qgraphicsItemSetFlag(child, flag, enabled); +} + +/*! + Sets the item flags to \a flags. All flags in \a flags are enabled; all + flags not in \a flags are disabled. + + If the item had focus and \a flags does not enable ItemIsFocusable, the + item loses focus as a result of calling this function. Similarly, if the + item was selected, and \a flags does not enabled ItemIsSelectable, the + item is automatically unselected. + + By default, no flags are enabled. (QGraphicsWidget enables the + ItemSendsGeometryChanges flag by default in order to track position + changes.) + + \sa flags(), setFlag() +*/ +void QGraphicsItem::setFlags(GraphicsItemFlags flags) +{ + // Notify change and check for adjustment. + if (quint32(d_ptr->flags) == quint32(flags)) + return; + flags = GraphicsItemFlags(itemChange(ItemFlagsChange, quint32(flags)).toUInt()); + if (quint32(d_ptr->flags) == quint32(flags)) + return; + if (d_ptr->scene && d_ptr->scene->d_func()->indexMethod != QGraphicsScene::NoIndex) + d_ptr->scene->d_func()->index->itemChange(this, ItemFlagsChange, &flags); + + // Flags that alter the geometry of the item (or its children). + const quint32 geomChangeFlagsMask = (ItemClipsChildrenToShape | ItemClipsToShape | ItemIgnoresTransformations | ItemIsSelectable); + bool fullUpdate = (quint32(flags) & geomChangeFlagsMask) != (d_ptr->flags & geomChangeFlagsMask); + if (fullUpdate) + d_ptr->updatePaintedViewBoundingRects(/*children=*/true); + + // Keep the old flags to compare the diff. + GraphicsItemFlags oldFlags = GraphicsItemFlags(d_ptr->flags); + + // Update flags. + d_ptr->flags = flags; + + if (!(d_ptr->flags & ItemIsFocusable) && hasFocus()) { + // Clear focus on the item if it has focus when the focusable flag + // is unset. + clearFocus(); + } + + if (!(d_ptr->flags & ItemIsSelectable) && isSelected()) { + // Unselect the item if it is selected when the selectable flag is + // unset. + setSelected(false); + } + + if ((flags & ItemClipsChildrenToShape) != (oldFlags & ItemClipsChildrenToShape)) { + // Item children clipping changes. Propagate the ancestor flag to + // all children. + d_ptr->updateAncestorFlag(ItemClipsChildrenToShape); + // The childrenBoundingRect is clipped to the boundingRect in case of ItemClipsChildrenToShape, + // which means we have to invalidate the cached childrenBoundingRect whenever this flag changes. + d_ptr->dirtyChildrenBoundingRect = 1; + d_ptr->markParentDirty(true); + } + + if ((flags & ItemIgnoresTransformations) != (oldFlags & ItemIgnoresTransformations)) { + // Item children clipping changes. Propagate the ancestor flag to + // all children. + d_ptr->updateAncestorFlag(ItemIgnoresTransformations); + } + + if ((flags & ItemNegativeZStacksBehindParent) != (oldFlags & ItemNegativeZStacksBehindParent)) { + // NB! We change the flags directly here, so we must also update d_ptr->flags. + // Note that this has do be done before the ItemStacksBehindParent check + // below; otherwise we will loose the change. + + // Update stack-behind. + if (d_ptr->z < qreal(0.0)) + flags |= ItemStacksBehindParent; + else + flags &= ~ItemStacksBehindParent; + d_ptr->flags = flags; + } + + if ((flags & ItemStacksBehindParent) != (oldFlags & ItemStacksBehindParent)) { + // NB! This check has to come after the ItemNegativeZStacksBehindParent + // check above. Be careful. + + // Ensure child item sorting is up to date when toggling this flag. + if (d_ptr->parent) + d_ptr->parent->d_ptr->needSortChildren = 1; + else if (d_ptr->scene) + d_ptr->scene->d_func()->needSortTopLevelItems = 1; + } + + if ((flags & ItemAcceptsInputMethod) != (oldFlags & ItemAcceptsInputMethod)) { + // Update input method sensitivity in any views. + if (d_ptr->scene) + d_ptr->scene->d_func()->updateInputMethodSensitivityInViews(); + } + + + if ((d_ptr->panelModality != NonModal) + && d_ptr->scene + && (flags & ItemIsPanel) != (oldFlags & ItemIsPanel)) { + // update the panel's modal state + if (flags & ItemIsPanel) + d_ptr->scene->d_func()->enterModal(this); + else + d_ptr->scene->d_func()->leaveModal(this); + } + + if (d_ptr->scene) { + if ((flags & ItemSendsScenePositionChanges) != (oldFlags & ItemSendsScenePositionChanges)) { + if (flags & ItemSendsScenePositionChanges) + d_ptr->scene->d_func()->registerScenePosItem(this); + else + d_ptr->scene->d_func()->unregisterScenePosItem(this); + } + d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true); + } + + // Notify change. + itemChange(ItemFlagsHaveChanged, quint32(flags)); +} + +/*! + \since 4.4 + Returns the cache mode for this item. The default mode is NoCache (i.e., + cache is disabled and all painting is immediate). + + \sa setCacheMode() +*/ +QGraphicsItem::CacheMode QGraphicsItem::cacheMode() const +{ + return QGraphicsItem::CacheMode(d_ptr->cacheMode); +} + +/*! + \since 4.4 + Sets the item's cache mode to \a mode. + + The optional \a logicalCacheSize argument is used only by + ItemCoordinateCache mode, and describes the resolution of the cache + buffer; if \a logicalCacheSize is (100, 100), QGraphicsItem will fit the + item into 100x100 pixels in graphics memory, regardless of the logical + size of the item itself. By default QGraphicsItem uses the size of + boundingRect(). For all other cache modes than ItemCoordinateCache, \a + logicalCacheSize is ignored. + + Caching can speed up rendering if your item spends a significant time + redrawing itself. In some cases the cache can also slow down rendering, in + particular when the item spends less time redrawing than QGraphicsItem + spends redrawing from the cache. When enabled, the item's paint() function + will be called only once for each call to update(); for any subsequent + repaint requests, the Graphics View framework will redraw from the + cache. This approach works particularly well with QGLWidget, which stores + all the cache as OpenGL textures. + + Be aware that QPixmapCache's cache limit may need to be changed to obtain + optimal performance. + + You can read more about the different cache modes in the CacheMode + documentation. + + \sa CacheMode, QPixmapCache::setCacheLimit() +*/ +void QGraphicsItem::setCacheMode(CacheMode mode, const QSize &logicalCacheSize) +{ + CacheMode lastMode = CacheMode(d_ptr->cacheMode); + d_ptr->cacheMode = mode; + bool noVisualChange = (mode == NoCache && lastMode == NoCache) + || (mode == NoCache && lastMode == DeviceCoordinateCache) + || (mode == DeviceCoordinateCache && lastMode == NoCache) + || (mode == DeviceCoordinateCache && lastMode == DeviceCoordinateCache); + if (mode == NoCache) { + d_ptr->removeExtraItemCache(); + } else { + QGraphicsItemCache *cache = d_ptr->extraItemCache(); + + // Reset old cache + cache->purge(); + + if (mode == ItemCoordinateCache) { + if (lastMode == mode && cache->fixedSize == logicalCacheSize) + noVisualChange = true; + cache->fixedSize = logicalCacheSize; + } + } + if (!noVisualChange) + update(); +} + +/*! + \since 4.6 + + Returns the modality for this item. +*/ +QGraphicsItem::PanelModality QGraphicsItem::panelModality() const +{ + return d_ptr->panelModality; +} + +/*! + \since 4.6 + + Sets the modality for this item to \a panelModality. + + Changing the modality of a visible item takes effect immediately. +*/ +void QGraphicsItem::setPanelModality(PanelModality panelModality) +{ + if (d_ptr->panelModality == panelModality) + return; + + PanelModality previousModality = d_ptr->panelModality; + bool enterLeaveModal = (isPanel() && d_ptr->scene && isVisible()); + if (enterLeaveModal && panelModality == NonModal) + d_ptr->scene->d_func()->leaveModal(this); + d_ptr->panelModality = panelModality; + if (enterLeaveModal && d_ptr->panelModality != NonModal) + d_ptr->scene->d_func()->enterModal(this, previousModality); +} + +/*! + \since 4.6 + + Returns true if this item is blocked by a modal panel, false otherwise. If \a blockingPanel is + non-zero, \a blockingPanel will be set to the modal panel that is blocking this item. If this + item is not blocked, \a blockingPanel will not be set by this function. + + This function always returns false for items not in a scene. + + \sa panelModality() setPanelModality() PanelModality +*/ +bool QGraphicsItem::isBlockedByModalPanel(QGraphicsItem **blockingPanel) const +{ + if (!d_ptr->scene) + return false; + + + QGraphicsItem *dummy = 0; + if (!blockingPanel) + blockingPanel = &dummy; + + QGraphicsScenePrivate *scene_d = d_ptr->scene->d_func(); + if (scene_d->modalPanels.isEmpty()) + return false; + + // ### + if (!scene_d->popupWidgets.isEmpty() && scene_d->popupWidgets.first() == this) + return false; + + for (int i = 0; i < scene_d->modalPanels.count(); ++i) { + QGraphicsItem *modalPanel = scene_d->modalPanels.at(i); + if (modalPanel->panelModality() == QGraphicsItem::SceneModal) { + // Scene modal panels block all non-descendents. + if (modalPanel != this && !modalPanel->isAncestorOf(this)) { + *blockingPanel = modalPanel; + return true; + } + } else { + // Window modal panels block ancestors and siblings/cousins. + if (modalPanel != this + && !modalPanel->isAncestorOf(this) + && commonAncestorItem(modalPanel)) { + *blockingPanel = modalPanel; + return true; + } + } + } + return false; +} + +#ifndef QT_NO_TOOLTIP +/*! + Returns the item's tool tip, or an empty QString if no tool tip has been + set. + + \sa setToolTip(), QToolTip +*/ +QString QGraphicsItem::toolTip() const +{ + return d_ptr->extra(QGraphicsItemPrivate::ExtraToolTip).toString(); +} + +/*! + Sets the item's tool tip to \a toolTip. If \a toolTip is empty, the item's + tool tip is cleared. + + \sa toolTip(), QToolTip +*/ +void QGraphicsItem::setToolTip(const QString &toolTip) +{ + const QVariant toolTipVariant(itemChange(ItemToolTipChange, toolTip)); + d_ptr->setExtra(QGraphicsItemPrivate::ExtraToolTip, toolTipVariant.toString()); + itemChange(ItemToolTipHasChanged, toolTipVariant); +} +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_CURSOR +/*! + Returns the current cursor shape for the item. The mouse cursor + will assume this shape when it's over this item. See the \link + Qt::CursorShape list of predefined cursor objects\endlink for a + range of useful shapes. + + An editor item might want to use an I-beam cursor: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 2 + + If no cursor has been set, the cursor of the item beneath is used. + + \sa setCursor(), hasCursor(), unsetCursor(), QWidget::cursor, + QApplication::overrideCursor() +*/ +QCursor QGraphicsItem::cursor() const +{ + return qvariant_cast(d_ptr->extra(QGraphicsItemPrivate::ExtraCursor)); +} + +/*! + Sets the current cursor shape for the item to \a cursor. The mouse cursor + will assume this shape when it's over this item. See the \link + Qt::CursorShape list of predefined cursor objects\endlink for a range of + useful shapes. + + An editor item might want to use an I-beam cursor: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 3 + + If no cursor has been set, the cursor of the item beneath is used. + + \sa cursor(), hasCursor(), unsetCursor(), QWidget::cursor, + QApplication::overrideCursor() +*/ +void QGraphicsItem::setCursor(const QCursor &cursor) +{ + const QVariant cursorVariant(itemChange(ItemCursorChange, QVariant::fromValue(cursor))); + d_ptr->setExtra(QGraphicsItemPrivate::ExtraCursor, qvariant_cast(cursorVariant)); + d_ptr->hasCursor = 1; + if (d_ptr->scene) { + d_ptr->scene->d_func()->allItemsUseDefaultCursor = false; + foreach (QGraphicsView *view, d_ptr->scene->views()) { + view->viewport()->setMouseTracking(true); + // Note: Some of this logic is duplicated in QGraphicsView's mouse events. + if (view->underMouse()) { + foreach (QGraphicsItem *itemUnderCursor, view->items(view->mapFromGlobal(QCursor::pos()))) { + if (itemUnderCursor->hasCursor()) { + QMetaObject::invokeMethod(view, "_q_setViewportCursor", + Q_ARG(QCursor, itemUnderCursor->cursor())); + break; + } + } + break; + } + } + } + itemChange(ItemCursorHasChanged, cursorVariant); +} + +/*! + Returns true if this item has a cursor set; otherwise, false is returned. + + By default, items don't have any cursor set. cursor() will return a + standard pointing arrow cursor. + + \sa unsetCursor() +*/ +bool QGraphicsItem::hasCursor() const +{ + return d_ptr->hasCursor; +} + +/*! + Clears the cursor from this item. + + \sa hasCursor(), setCursor() +*/ +void QGraphicsItem::unsetCursor() +{ + d_ptr->unsetExtra(QGraphicsItemPrivate::ExtraCursor); + d_ptr->hasCursor = 0; + if (d_ptr->scene) { + foreach (QGraphicsView *view, d_ptr->scene->views()) { + if (view->underMouse() && view->itemAt(view->mapFromGlobal(QCursor::pos())) == this) { + QMetaObject::invokeMethod(view, "_q_unsetViewportCursor"); + break; + } + } + } +} + +#endif // QT_NO_CURSOR + +/*! + Returns true if the item is visible; otherwise, false is returned. + + Note that the item's general visibility is unrelated to whether or not it + is actually being visualized by a QGraphicsView. + + \sa setVisible() +*/ +bool QGraphicsItem::isVisible() const +{ + return d_ptr->visible; +} + +/*! + \since 4.4 + Returns true if the item is visible to \a parent; otherwise, false is + returned. \a parent can be 0, in which case this function will return + whether the item is visible to the scene or not. + + An item may not be visible to its ancestors even if isVisible() is true. If + any ancestor is hidden, the item itself will be implicitly hidden, in which + case this function will return false. + + \sa isVisible(), setVisible() +*/ +bool QGraphicsItem::isVisibleTo(const QGraphicsItem *parent) const +{ + if (!d_ptr->visible) + return false; + if (parent == this) + return true; + if (parentItem() && parentItem()->isVisibleTo(parent)) + return true; + if (!parent && !parentItem()) + return true; + return false; +} + +/*! + \internal + + Sets this item's visibility to \a newVisible. If \a explicitly is true, + this item will be "explicitly" \a newVisible; otherwise, it.. will not be. +*/ +void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly, bool update) +{ + Q_Q(QGraphicsItem); + + // Update explicit bit. + if (explicitly) + explicitlyHidden = newVisible ? 0 : 1; + + // Check if there's nothing to do. + if (visible == quint32(newVisible)) + return; + + // Don't show child if parent is not visible + if (parent && newVisible && !parent->d_ptr->visible) + return; + + // Modify the property. + const QVariant newVisibleVariant(q_ptr->itemChange(QGraphicsItem::ItemVisibleChange, + quint32(newVisible))); + newVisible = newVisibleVariant.toBool(); + if (visible == quint32(newVisible)) + return; + visible = newVisible; + + // Schedule redrawing + if (update) { + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (c) + c->purge(); + if (scene) { +#ifndef QT_NO_GRAPHICSEFFECT + invalidateParentGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + scene->d_func()->markDirty(q_ptr, QRectF(), /*invalidateChildren=*/false, /*force=*/true); + } + } + + // Certain properties are dropped as an item becomes invisible. + bool hasFocus = q_ptr->hasFocus(); + if (!newVisible) { + if (scene) { + if (scene->d_func()->mouseGrabberItems.contains(q)) + q->ungrabMouse(); + if (scene->d_func()->keyboardGrabberItems.contains(q)) + q->ungrabKeyboard(); + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) + scene->d_func()->leaveModal(q_ptr); + } + if (hasFocus && scene) { + // Hiding the closest non-panel ancestor of the focus item + QGraphicsItem *focusItem = scene->focusItem(); + bool clear = true; + if (isWidget && !focusItem->isPanel()) { + do { + if (focusItem == q_ptr) { + clear = !static_cast(q_ptr)->focusNextPrevChild(true); + break; + } + } while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel()); + } + if (clear) + clearFocusHelper(/* giveFocusToParent = */ false); + } + if (q_ptr->isSelected()) + q_ptr->setSelected(false); + } else { + geometryChanged = 1; + paintedViewBoundingRectsNeedRepaint = 1; + if (scene) { + if (isWidget) { + QGraphicsWidget *widget = static_cast(q_ptr); + if (widget->windowType() == Qt::Popup) + scene->d_func()->addPopup(widget); + } + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) { + scene->d_func()->enterModal(q_ptr); + } + } + } + + // Update children with explicitly = false. + const bool updateChildren = update && !(flags & QGraphicsItem::ItemClipsChildrenToShape); + foreach (QGraphicsItem *child, children) { + if (!newVisible || !child->d_ptr->explicitlyHidden) + child->d_ptr->setVisibleHelper(newVisible, false, updateChildren); + } + + // Update activation + if (scene && q->isPanel()) { + if (newVisible) { + if (parent && parent->isActive()) + q->setActive(true); + } else { + if (q->isActive()) + scene->setActivePanel(parent); + } + } + + // Enable subfocus + if (scene) { + if (newVisible) { + // Item is shown + QGraphicsItem *p = parent; + bool done = false; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + QGraphicsItem *fsi = p->d_ptr->focusScopeItem; + if (q_ptr == fsi || q_ptr->isAncestorOf(fsi)) { + done = true; + while (fsi->d_ptr->focusScopeItem && fsi->d_ptr->focusScopeItem->isVisible()) + fsi = fsi->d_ptr->focusScopeItem; + fsi->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ true, + /* focusFromHide = */ false); + } + break; + } + p = p->d_ptr->parent; + } + if (!done) { + QGraphicsItem *fi = subFocusItem; + if (fi && fi != scene->focusItem()) { + scene->setFocusItem(fi); + } else if (flags & QGraphicsItem::ItemIsFocusScope && + !scene->focusItem() && + q->isAncestorOf(scene->d_func()->lastFocusItem)) { + q_ptr->setFocus(); + } + } + } else { + // Item is hidden + if (hasFocus) { + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + if (p->d_ptr->visible) { + p->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ true, + /* focusFromHide = */ true); + } + break; + } + p = p->d_ptr->parent; + } + } + } + } + + // Deliver post-change notification. + q_ptr->itemChange(QGraphicsItem::ItemVisibleHasChanged, newVisibleVariant); + + if (isObject) + emit static_cast(q_ptr)->visibleChanged(); +} + +/*! + If \a visible is true, the item is made visible. Otherwise, the item is + made invisible. Invisible items are not painted, nor do they receive any + events. In particular, mouse events pass right through invisible items, + and are delivered to any item that may be behind. Invisible items are also + unselectable, they cannot take input focus, and are not detected by + QGraphicsScene's item location functions. + + If an item becomes invisible while grabbing the mouse, (i.e., while it is + receiving mouse events,) it will automatically lose the mouse grab, and + the grab is not regained by making the item visible again; it must receive + a new mouse press to regain the mouse grab. + + Similarly, an invisible item cannot have focus, so if the item has focus + when it becomes invisible, it will lose focus, and the focus is not + regained by simply making the item visible again. + + If you hide a parent item, all its children will also be hidden. If you + show a parent item, all children will be shown, unless they have been + explicitly hidden (i.e., if you call setVisible(false) on a child, it will + not be reshown even if its parent is hidden, and then shown again). + + Items are visible by default; it is unnecessary to call + setVisible() on a new item. + + \sa isVisible(), show(), hide() +*/ +void QGraphicsItem::setVisible(bool visible) +{ + d_ptr->setVisibleHelper(visible, /* explicit = */ true); +} + +/*! + \fn void QGraphicsItem::hide() + + Hides the item. (Items are visible by default.) + + This convenience function is equivalent to calling \c setVisible(false). + + \sa show(), setVisible() +*/ + +/*! + \fn void QGraphicsItem::show() + + Shows the item. (Items are visible by default.) + + This convenience function is equivalent to calling \c setVisible(true). + + \sa hide(), setVisible() +*/ + +/*! + Returns true if the item is enabled; otherwise, false is returned. + + \sa setEnabled() +*/ +bool QGraphicsItem::isEnabled() const +{ + return d_ptr->enabled; +} + +/*! + \internal + + Sets this item's visibility to \a newEnabled. If \a explicitly is true, + this item will be "explicitly" \a newEnabled; otherwise, it.. will not be. +*/ +void QGraphicsItemPrivate::setEnabledHelper(bool newEnabled, bool explicitly, bool update) +{ + // Update explicit bit. + if (explicitly) + explicitlyDisabled = newEnabled ? 0 : 1; + + // Check if there's nothing to do. + if (enabled == quint32(newEnabled)) + return; + + // Certain properties are dropped when an item is disabled. + if (!newEnabled) { + if (scene && scene->mouseGrabberItem() == q_ptr) + q_ptr->ungrabMouse(); + if (q_ptr->hasFocus()) { + // Disabling the closest non-panel ancestor of the focus item + // causes focus to pop to the next item, otherwise it's cleared. + QGraphicsItem *focusItem = scene->focusItem(); + bool clear = true; + if (isWidget && !focusItem->isPanel() && q_ptr->isAncestorOf(focusItem)) { + do { + if (focusItem == q_ptr) { + clear = !static_cast(q_ptr)->focusNextPrevChild(true); + break; + } + } while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel()); + } + if (clear) + q_ptr->clearFocus(); + } + if (q_ptr->isSelected()) + q_ptr->setSelected(false); + } + + // Modify the property. + const QVariant newEnabledVariant(q_ptr->itemChange(QGraphicsItem::ItemEnabledChange, + quint32(newEnabled))); + enabled = newEnabledVariant.toBool(); + + // Schedule redraw. + if (update) + q_ptr->update(); + + foreach (QGraphicsItem *child, children) { + if (!newEnabled || !child->d_ptr->explicitlyDisabled) + child->d_ptr->setEnabledHelper(newEnabled, /* explicitly = */ false); + } + + // Deliver post-change notification. + q_ptr->itemChange(QGraphicsItem::ItemEnabledHasChanged, newEnabledVariant); + + if (isObject) + emit static_cast(q_ptr)->enabledChanged(); +} + +/*! + If \a enabled is true, the item is enabled; otherwise, it is disabled. + + Disabled items are visible, but they do not receive any events, and cannot + take focus nor be selected. Mouse events are discarded; they are not + propagated unless the item is also invisible, or if it does not accept + mouse events (see acceptedMouseButtons()). A disabled item cannot become the + mouse grabber, and as a result of this, an item loses the grab if it + becomes disabled when grabbing the mouse, just like it loses focus if it + had focus when it was disabled. + + Disabled items are traditionally drawn using grayed-out colors (see \l + QPalette::Disabled). + + If you disable a parent item, all its children will also be disabled. If + you enable a parent item, all children will be enabled, unless they have + been explicitly disabled (i.e., if you call setEnabled(false) on a child, + it will not be reenabled if its parent is disabled, and then enabled + again). + + Items are enabled by default. + + \note If you install an event filter, you can still intercept events + before they are delivered to items; this mechanism disregards the item's + enabled state. + + \sa isEnabled() +*/ +void QGraphicsItem::setEnabled(bool enabled) +{ + d_ptr->setEnabledHelper(enabled, /* explicitly = */ true); +} + +/*! + Returns true if this item is selected; otherwise, false is returned. + + Items that are in a group inherit the group's selected state. + + Items are not selected by default. + + \sa setSelected(), QGraphicsScene::setSelectionArea() +*/ +bool QGraphicsItem::isSelected() const +{ + if (QGraphicsItemGroup *group = this->group()) + return group->isSelected(); + return d_ptr->selected; +} + +/*! + If \a selected is true and this item is selectable, this item is selected; + otherwise, it is unselected. + + If the item is in a group, the whole group's selected state is toggled by + this function. If the group is selected, all items in the group are also + selected, and if the group is not selected, no item in the group is + selected. + + Only visible, enabled, selectable items can be selected. If \a selected + is true and this item is either invisible or disabled or unselectable, + this function does nothing. + + By default, items cannot be selected. To enable selection, set the + ItemIsSelectable flag. + + This function is provided for convenience, allowing individual toggling of + the selected state of an item. However, a more common way of selecting + items is to call QGraphicsScene::setSelectionArea(), which will call this + function for all visible, enabled, and selectable items within a specified + area on the scene. + + \sa isSelected(), QGraphicsScene::selectedItems() +*/ +void QGraphicsItem::setSelected(bool selected) +{ + if (QGraphicsItemGroup *group = this->group()) { + group->setSelected(selected); + return; + } + + if (!(d_ptr->flags & ItemIsSelectable) || !d_ptr->enabled || !d_ptr->visible) + selected = false; + if (d_ptr->selected == selected) + return; + const QVariant newSelectedVariant(itemChange(ItemSelectedChange, quint32(selected))); + bool newSelected = newSelectedVariant.toBool(); + if (d_ptr->selected == newSelected) + return; + d_ptr->selected = newSelected; + + update(); + if (d_ptr->scene) { + QGraphicsScenePrivate *sceneD = d_ptr->scene->d_func(); + if (selected) { + sceneD->selectedItems << this; + } else { + // QGraphicsScene::selectedItems() lazily pulls out all items that are + // no longer selected. + } + if (!sceneD->selectionChanging) + emit d_ptr->scene->selectionChanged(); + } + + // Deliver post-change notification. + itemChange(QGraphicsItem::ItemSelectedHasChanged, newSelectedVariant); +} + +/*! + \since 4.5 + + Returns this item's local opacity, which is between 0.0 (transparent) and + 1.0 (opaque). This value is combined with parent and ancestor values into + the effectiveOpacity(). The effective opacity decides how the item is + rendered. + + The opacity property decides the state of the painter passed to the + paint() function. If the item is cached, i.e., ItemCoordinateCache or + DeviceCoordinateCache, the effective property will be applied to the item's + cache as it is rendered. + + The default opacity is 1.0; fully opaque. + + \sa setOpacity(), paint(), ItemIgnoresParentOpacity, + ItemDoesntPropagateOpacityToChildren +*/ +qreal QGraphicsItem::opacity() const +{ + return d_ptr->opacity; +} + +/*! + \since 4.5 + + Returns this item's \e effective opacity, which is between 0.0 + (transparent) and 1.0 (opaque). This value is a combination of this item's + local opacity, and its parent and ancestors' opacities. The effective + opacity decides how the item is rendered. + + \sa opacity(), setOpacity(), paint(), ItemIgnoresParentOpacity, + ItemDoesntPropagateOpacityToChildren +*/ +qreal QGraphicsItem::effectiveOpacity() const +{ + return d_ptr->effectiveOpacity(); +} + +/*! + \since 4.5 + + Sets this item's local \a opacity, between 0.0 (transparent) and 1.0 + (opaque). The item's local opacity is combined with parent and ancestor + opacities into the effectiveOpacity(). + + By default, opacity propagates from parent to child, so if a parent's + opacity is 0.5 and the child is also 0.5, the child's effective opacity + will be 0.25. + + The opacity property decides the state of the painter passed to the + paint() function. If the item is cached, i.e., ItemCoordinateCache or + DeviceCoordinateCache, the effective property will be applied to the + item's cache as it is rendered. + + There are two item flags that affect how the item's opacity is combined + with the parent: ItemIgnoresParentOpacity and + ItemDoesntPropagateOpacityToChildren. + + \sa opacity(), effectiveOpacity() +*/ +void QGraphicsItem::setOpacity(qreal opacity) +{ + // Notify change. + const QVariant newOpacityVariant(itemChange(ItemOpacityChange, opacity)); + + // Normalized opacity + qreal newOpacity = qBound(qreal(0), newOpacityVariant.toReal(), qreal(1)); + + // No change? Done. + if (newOpacity == d_ptr->opacity) + return; + + bool wasFullyTransparent = d_ptr->isOpacityNull(); + d_ptr->opacity = newOpacity; + + // Notify change. + itemChange(ItemOpacityHasChanged, newOpacityVariant); + + // Update. + if (d_ptr->scene) { +#ifndef QT_NO_GRAPHICSEFFECT + d_ptr->invalidateParentGraphicsEffectsRecursively(); + if (!(d_ptr->flags & ItemDoesntPropagateOpacityToChildren)) + d_ptr->invalidateChildGraphicsEffectsRecursively(QGraphicsItemPrivate::OpacityChanged); +#endif //QT_NO_GRAPHICSEFFECT + d_ptr->scene->d_func()->markDirty(this, QRectF(), + /*invalidateChildren=*/true, + /*force=*/false, + /*ignoreOpacity=*/d_ptr->isOpacityNull()); + if (wasFullyTransparent) + d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + } + + if (d_ptr->isObject) + emit static_cast(this)->opacityChanged(); +} + +/*! + Returns a pointer to this item's effect if it has one; otherwise 0. + + \since 4.6 +*/ +#ifndef QT_NO_GRAPHICSEFFECT +QGraphicsEffect *QGraphicsItem::graphicsEffect() const +{ + return d_ptr->graphicsEffect; +} + +/*! + Sets \a effect as the item's effect. If there already is an effect installed + on this item, QGraphicsItem will delete the existing effect before installing + the new \a effect. + + If \a effect is the installed on a different item, setGraphicsEffect() will remove + the effect from the item and install it on this item. + + QGraphicsItem takes ownership of \a effect. + + \note This function will apply the effect on itself and all its children. + + \since 4.6 +*/ +void QGraphicsItem::setGraphicsEffect(QGraphicsEffect *effect) +{ + if (d_ptr->graphicsEffect == effect) + return; + + if (d_ptr->graphicsEffect) { + delete d_ptr->graphicsEffect; + d_ptr->graphicsEffect = 0; + } else if (d_ptr->parent) { + d_ptr->parent->d_ptr->updateChildWithGraphicsEffectFlagRecursively(); + } + + if (effect) { + // Set new effect. + QGraphicsEffectSourcePrivate *sourced = new QGraphicsItemEffectSourcePrivate(this); + QGraphicsEffectSource *source = new QGraphicsEffectSource(*sourced); + d_ptr->graphicsEffect = effect; + effect->d_func()->setGraphicsEffectSource(source); + prepareGeometryChange(); + } +} +#endif //QT_NO_GRAPHICSEFFECT + +void QGraphicsItemPrivate::updateChildWithGraphicsEffectFlagRecursively() +{ +#ifndef QT_NO_GRAPHICSEFFECT + QGraphicsItemPrivate *itemPrivate = this; + do { + // parent chain already notified? + if (itemPrivate->mayHaveChildWithGraphicsEffect) + return; + itemPrivate->mayHaveChildWithGraphicsEffect = 1; + } while ((itemPrivate = itemPrivate->parent ? itemPrivate->parent->d_ptr.data() : 0)); +#endif +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of the given item space rect. + If the item has no effect, the rect is returned unmodified. + If the item has an effect, the effective rect can be extend beyond the + item's bounding rect, depending on the effect. + + \sa boundingRect() +*/ +QRectF QGraphicsItemPrivate::effectiveBoundingRect(const QRectF &rect) const +{ +#ifndef QT_NO_GRAPHICSEFFECT + Q_Q(const QGraphicsItem); + QGraphicsEffect *effect = graphicsEffect; + if (scene && effect && effect->isEnabled()) { + if (scene->d_func()->views.isEmpty()) + return effect->boundingRectFor(rect); + QRectF sceneRect = q->mapRectToScene(rect); + QRectF sceneEffectRect; + foreach (QGraphicsView *view, scene->views()) { + QRectF deviceRect = view->d_func()->mapRectFromScene(sceneRect); + QRect deviceEffectRect = effect->boundingRectFor(deviceRect).toAlignedRect(); + sceneEffectRect |= view->d_func()->mapRectToScene(deviceEffectRect); + } + return q->mapRectFromScene(sceneEffectRect); + } +#endif //QT_NO_GRAPHICSEFFECT + return rect; +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of the item. + If the item has no effect, this is the same as the item's bounding rect. + If the item has an effect, the effective rect can be larger than the item's + bouding rect, depending on the effect. + + \sa boundingRect() +*/ +QRectF QGraphicsItemPrivate::effectiveBoundingRect(QGraphicsItem *topMostEffectItem) const +{ +#ifndef QT_NO_GRAPHICSEFFECT + Q_Q(const QGraphicsItem); + QRectF brect = effectiveBoundingRect(q_ptr->boundingRect()); + if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren || topMostEffectItem == q) + return brect; + + const QGraphicsItem *effectParent = parent; + while (effectParent) { + QGraphicsEffect *effect = effectParent->d_ptr->graphicsEffect; + if (scene && effect && effect->isEnabled()) { + const QRectF brectInParentSpace = q->mapRectToItem(effectParent, brect); + const QRectF effectRectInParentSpace = effectParent->d_ptr->effectiveBoundingRect(brectInParentSpace); + brect = effectParent->mapRectToItem(q, effectRectInParentSpace); + } + if (effectParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren + || topMostEffectItem == effectParent) { + return brect; + } + effectParent = effectParent->d_ptr->parent; + } + + return brect; +#else //QT_NO_GRAPHICSEFFECT + return q_ptr->boundingRect(); +#endif //QT_NO_GRAPHICSEFFECT + +} + +/*! + \internal + \since 4.6 + Returns the effective bounding rect of this item in scene coordinates, + by combining sceneTransform() with boundingRect(), taking into account + the effect that the item might have. + + If the item has no effect, this is the same as sceneBoundingRect(). + + \sa effectiveBoundingRect(), sceneBoundingRect() +*/ +QRectF QGraphicsItemPrivate::sceneEffectiveBoundingRect() const +{ + // Find translate-only offset + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = q_ptr; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF br = effectiveBoundingRect(); + br.translate(offset); + return !parentItem ? br : parentItem->sceneTransform().mapRect(br); +} + +/*! + Returns true if this item can accept drag and drop events; otherwise, + returns false. By default, items do not accept drag and drop events; items + are transparent to drag and drop. + + \sa setAcceptDrops() +*/ +bool QGraphicsItem::acceptDrops() const +{ + return d_ptr->acceptDrops; +} + +/*! + If \a on is true, this item will accept drag and drop events; otherwise, + it is transparent for drag and drop events. By default, items do not + accept drag and drop events. + + \sa acceptDrops() +*/ +void QGraphicsItem::setAcceptDrops(bool on) +{ + d_ptr->acceptDrops = on; +} + +/*! + Returns the mouse buttons that this item accepts mouse events for. By + default, all mouse buttons are accepted. + + If an item accepts a mouse button, it will become the mouse + grabber item when a mouse press event is delivered for that mouse + button. However, if the item does not accept the button, + QGraphicsScene will forward the mouse events to the first item + beneath it that does. + + \sa setAcceptedMouseButtons(), mousePressEvent() +*/ +Qt::MouseButtons QGraphicsItem::acceptedMouseButtons() const +{ + return Qt::MouseButtons(d_ptr->acceptedMouseButtons); +} + +/*! + Sets the mouse \a buttons that this item accepts mouse events for. + + By default, all mouse buttons are accepted. If an item accepts a + mouse button, it will become the mouse grabber item when a mouse + press event is delivered for that button. However, if the item + does not accept the mouse button, QGraphicsScene will forward the + mouse events to the first item beneath it that does. + + To disable mouse events for an item (i.e., make it transparent for mouse + events), call setAcceptedMouseButtons(0). + + \sa acceptedMouseButtons(), mousePressEvent() +*/ +void QGraphicsItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) +{ + if (Qt::MouseButtons(d_ptr->acceptedMouseButtons) != buttons) { + if (buttons == 0 && d_ptr->scene && d_ptr->scene->mouseGrabberItem() == this + && d_ptr->scene->d_func()->lastMouseGrabberItemHasImplicitMouseGrab) { + ungrabMouse(); + } + d_ptr->acceptedMouseButtons = quint32(buttons); + } +} + +/*! + \since 4.4 + + Returns true if an item accepts hover events + (QGraphicsSceneHoverEvent); otherwise, returns false. By default, + items do not accept hover events. + + \sa setAcceptedMouseButtons() +*/ +bool QGraphicsItem::acceptHoverEvents() const +{ + return d_ptr->acceptsHover; +} + +/*! + \obsolete + + Call acceptHoverEvents() instead. +*/ +bool QGraphicsItem::acceptsHoverEvents() const +{ + return d_ptr->acceptsHover; +} + +/*! + \since 4.4 + + If \a enabled is true, this item will accept hover events; + otherwise, it will ignore them. By default, items do not accept + hover events. + + Hover events are delivered when there is no current mouse grabber + item. They are sent when the mouse cursor enters an item, when it + moves around inside the item, and when the cursor leaves an + item. Hover events are commonly used to highlight an item when + it's entered, and for tracking the mouse cursor as it hovers over + the item (equivalent to QWidget::mouseTracking). + + Parent items receive hover enter events before their children, and + leave events after their children. The parent does not receive a + hover leave event if the cursor enters a child, though; the parent + stays "hovered" until the cursor leaves its area, including its + children's areas. + + If a parent item handles child events, it will receive hover move, + drag move, and drop events as the cursor passes through its + children, but it does not receive hover enter and hover leave, nor + drag enter and drag leave events on behalf of its children. + + A QGraphicsWidget with window decorations will accept hover events + regardless of the value of acceptHoverEvents(). + + \sa acceptHoverEvents(), hoverEnterEvent(), hoverMoveEvent(), + hoverLeaveEvent() +*/ +void QGraphicsItem::setAcceptHoverEvents(bool enabled) +{ + if (d_ptr->acceptsHover == quint32(enabled)) + return; + d_ptr->acceptsHover = quint32(enabled); + if (d_ptr->acceptsHover && d_ptr->scene && d_ptr->scene->d_func()->allItemsIgnoreHoverEvents) { + d_ptr->scene->d_func()->allItemsIgnoreHoverEvents = false; + d_ptr->scene->d_func()->enableMouseTrackingOnViews(); + } +} + +/*! + \obsolete + + Use setAcceptHoverEvents(\a enabled) instead. +*/ +void QGraphicsItem::setAcceptsHoverEvents(bool enabled) +{ + setAcceptHoverEvents(enabled); +} + +/*! \since 4.6 + + Returns true if an item accepts \l{QTouchEvent}{touch events}; + otherwise, returns false. By default, items do not accept touch events. + + \sa setAcceptTouchEvents() +*/ +bool QGraphicsItem::acceptTouchEvents() const +{ + return d_ptr->acceptTouchEvents; +} + +/*! + \since 4.6 + + If \a enabled is true, this item will accept \l{QTouchEvent}{touch events}; + otherwise, it will ignore them. By default, items do not accept + touch events. +*/ +void QGraphicsItem::setAcceptTouchEvents(bool enabled) +{ + if (d_ptr->acceptTouchEvents == quint32(enabled)) + return; + d_ptr->acceptTouchEvents = quint32(enabled); + if (d_ptr->acceptTouchEvents && d_ptr->scene && d_ptr->scene->d_func()->allItemsIgnoreTouchEvents) { + d_ptr->scene->d_func()->allItemsIgnoreTouchEvents = false; + d_ptr->scene->d_func()->enableTouchEventsOnViews(); + } +} + +/*! + \since 4.6 + + Returns true if this item filters child events (i.e., all events + intended for any of its children are instead sent to this item); + otherwise, false is returned. + + The default value is false; child events are not filtered. + + \sa setFiltersChildEvents() +*/ +bool QGraphicsItem::filtersChildEvents() const +{ + return d_ptr->filtersDescendantEvents; +} + +/*! + \since 4.6 + + If \a enabled is true, this item is set to filter all events for + all its children (i.e., all events intented for any of its + children are instead sent to this item); otherwise, if \a enabled + is false, this item will only handle its own events. The default + value is false. + + \sa filtersChildEvents() +*/ +void QGraphicsItem::setFiltersChildEvents(bool enabled) +{ + if (d_ptr->filtersDescendantEvents == enabled) + return; + + d_ptr->filtersDescendantEvents = enabled; + d_ptr->updateAncestorFlag(QGraphicsItem::GraphicsItemFlag(-2)); +} + +/*! + \obsolete + + Returns true if this item handles child events (i.e., all events + intended for any of its children are instead sent to this item); + otherwise, false is returned. + + This property is useful for item groups; it allows one item to + handle events on behalf of its children, as opposed to its + children handling their events individually. + + The default is to return false; children handle their own events. + The exception for this is if the item is a QGraphicsItemGroup, then + it defaults to return true. + + \sa setHandlesChildEvents() +*/ +bool QGraphicsItem::handlesChildEvents() const +{ + return d_ptr->handlesChildEvents; +} + +/*! + \obsolete + + If \a enabled is true, this item is set to handle all events for + all its children (i.e., all events intented for any of its + children are instead sent to this item); otherwise, if \a enabled + is false, this item will only handle its own events. The default + value is false. + + This property is useful for item groups; it allows one item to + handle events on behalf of its children, as opposed to its + children handling their events individually. + + If a child item accepts hover events, its parent will receive + hover move events as the cursor passes through the child, but it + does not receive hover enter and hover leave events on behalf of + its child. + + \sa handlesChildEvents() +*/ +void QGraphicsItem::setHandlesChildEvents(bool enabled) +{ + if (d_ptr->handlesChildEvents == enabled) + return; + + d_ptr->handlesChildEvents = enabled; + d_ptr->updateAncestorFlag(QGraphicsItem::GraphicsItemFlag(-1)); +} +/*! + \since 4.6 + Returns true if this item is active; otherwise returns false. + + An item can only be active if the scene is active. An item is active + if it is, or is a descendent of, an active panel. Items in non-active + panels are not active. + + Items that are not part of a panel follow scene activation when the + scene has no active panel. + + Only active items can gain input focus. + + \sa QGraphicsScene::isActive(), QGraphicsScene::activePanel(), panel(), isPanel() +*/ +bool QGraphicsItem::isActive() const +{ + if (!d_ptr->scene || !d_ptr->scene->isActive()) + return false; + return panel() == d_ptr->scene->activePanel(); +} + +/*! + \since 4.6 + + If \a active is true, and the scene is active, this item's panel will be + activated. Otherwise, the panel is deactivated. + + If the item is not part of an active scene, \a active will decide what + happens to the panel when the scene becomes active or the item is added to + the scene. If true, the item's panel will be activated when the item is + either added to the scene or the scene is activated. Otherwise, the item + will stay inactive independent of the scene's activated state. + + \sa isPanel(), QGraphicsScene::setActivePanel(), QGraphicsScene::isActive() +*/ +void QGraphicsItem::setActive(bool active) +{ + d_ptr->explicitActivate = 1; + d_ptr->wantsActive = active; + if (d_ptr->scene) { + if (active) { + // Activate this item. + d_ptr->scene->setActivePanel(this); + } else { + // Deactivate this item, and reactivate the last active item + // (if any). + QGraphicsItem *lastActive = d_ptr->scene->d_func()->lastActivePanel; + d_ptr->scene->setActivePanel(lastActive != this ? lastActive : 0); + } + } +} + +/*! + Returns true if this item is active, and it or its \l{focusProxy()}{focus + proxy} has keyboard input focus; otherwise, returns false. + + \sa focusItem(), setFocus(), QGraphicsScene::setFocusItem(), isActive() +*/ +bool QGraphicsItem::hasFocus() const +{ + if (!d_ptr->scene || !d_ptr->scene->isActive()) + return false; + + if (d_ptr->focusProxy) + return d_ptr->focusProxy->hasFocus(); + + if (d_ptr->scene->d_func()->focusItem != this) + return false; + + return panel() == d_ptr->scene->d_func()->activePanel; +} + +/*! + Gives keyboard input focus to this item. The \a focusReason argument will + be passed into any \l{QFocusEvent}{focus event} generated by this function; + it is used to give an explanation of what caused the item to get focus. + + Only enabled items that set the ItemIsFocusable flag can accept keyboard + focus. + + If this item is not visible, not active, or not associated with a scene, + it will not gain immediate input focus. However, it will be registered as + the preferred focus item for its subtree of items, should it later become + visible. + + As a result of calling this function, this item will receive a + \l{focusInEvent()}{focus in event} with \a focusReason. If another item + already has focus, that item will first receive a \l{focusOutEvent()} + {focus out event} indicating that it has lost input focus. + + \sa clearFocus(), hasFocus(), focusItem(), focusProxy() +*/ +void QGraphicsItem::setFocus(Qt::FocusReason focusReason) +{ + d_ptr->setFocusHelper(focusReason, /* climb = */ true, /* focusFromHide = */ false); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::setFocusHelper(Qt::FocusReason focusReason, bool climb, bool focusFromHide) +{ + // Disabled / unfocusable items cannot accept focus. + if (!q_ptr->isEnabled() || !(flags & QGraphicsItem::ItemIsFocusable)) + return; + + // Find focus proxy. + QGraphicsItem *f = q_ptr; + while (f->d_ptr->focusProxy) + f = f->d_ptr->focusProxy; + + // Return if it already has focus. + if (scene && scene->focusItem() == f) + return; + + // Update focus scope item ptr. + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + QGraphicsItem *oldFocusScopeItem = p->d_ptr->focusScopeItem; + p->d_ptr->focusScopeItem = q_ptr; + if (!p->focusItem() && !focusFromHide) { + if (oldFocusScopeItem) + oldFocusScopeItem->d_ptr->focusScopeItemChange(false); + focusScopeItemChange(true); + // If you call setFocus on a child of a focus scope that + // doesn't currently have a focus item, then stop. + return; + } + break; + } + p = p->d_ptr->parent; + } + + if (climb) { + while (f->d_ptr->focusScopeItem && f->d_ptr->focusScopeItem->isVisible()) + f = f->d_ptr->focusScopeItem; + } + + // Update the child focus chain. + QGraphicsItem *commonAncestor = 0; + if (scene && scene->focusItem()) { + commonAncestor = scene->focusItem()->commonAncestorItem(f); + scene->focusItem()->d_ptr->clearSubFocus(scene->focusItem(), commonAncestor); + } + + f->d_ptr->setSubFocus(f, commonAncestor); + + // Update the scene's focus item. + if (scene) { + QGraphicsItem *p = q_ptr->panel(); + if ((!p && scene->isActive()) || (p && p->isActive())) { + // Visible items immediately gain focus from scene. + scene->d_func()->setFocusItemHelper(f, focusReason); + } + } +} + +/*! + Takes keyboard input focus from the item. + + If it has focus, a \l{focusOutEvent()}{focus out event} is sent to this + item to tell it that it is about to lose the focus. + + Only items that set the ItemIsFocusable flag, or widgets that set an + appropriate focus policy, can accept keyboard focus. + + \sa setFocus(), hasFocus(), QGraphicsWidget::focusPolicy +*/ +void QGraphicsItem::clearFocus() +{ + d_ptr->clearFocusHelper(/* giveFocusToParent = */ true); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::clearFocusHelper(bool giveFocusToParent) +{ + if (giveFocusToParent) { + // Pass focus to the closest parent focus scope + if (!inDestructor) { + QGraphicsItem *p = parent; + while (p) { + if (p->flags() & QGraphicsItem::ItemIsFocusScope) { + if (p->d_ptr->focusScopeItem == q_ptr) { + p->d_ptr->focusScopeItem = 0; + if (!q_ptr->hasFocus()) //if it has focus, focusScopeItemChange is called elsewhere + focusScopeItemChange(false); + } + if (q_ptr->hasFocus()) + p->d_ptr->setFocusHelper(Qt::OtherFocusReason, /* climb = */ false, + /* focusFromHide = */ false); + return; + } + p = p->d_ptr->parent; + } + } + } + + if (q_ptr->hasFocus()) { + // Invisible items with focus must explicitly clear subfocus. + clearSubFocus(q_ptr); + + // If this item has the scene's input focus, clear it. + scene->setFocusItem(0); + } +} + +/*! + \since 4.6 + + Returns this item's focus proxy, or 0 if this item has no + focus proxy. + + \sa setFocusProxy(), setFocus(), hasFocus() +*/ +QGraphicsItem *QGraphicsItem::focusProxy() const +{ + return d_ptr->focusProxy; +} + +/*! + \since 4.6 + + Sets the item's focus proxy to \a item. + + If an item has a focus proxy, the focus proxy will receive + input focus when the item gains input focus. The item itself + will still have focus (i.e., hasFocus() will return true), + but only the focus proxy will receive the keyboard input. + + A focus proxy can itself have a focus proxy, and so on. In + such case, keyboard input will be handled by the outermost + focus proxy. + + The focus proxy \a item must belong to the same scene as + this item. + + \sa focusProxy(), setFocus(), hasFocus() +*/ +void QGraphicsItem::setFocusProxy(QGraphicsItem *item) +{ + if (item == d_ptr->focusProxy) + return; + if (item == this) { + qWarning("QGraphicsItem::setFocusProxy: cannot assign self as focus proxy"); + return; + } + if (item) { + if (item->d_ptr->scene != d_ptr->scene) { + qWarning("QGraphicsItem::setFocusProxy: focus proxy must be in same scene"); + return; + } + for (QGraphicsItem *f = item->focusProxy(); f != 0; f = f->focusProxy()) { + if (f == this) { + qWarning("QGraphicsItem::setFocusProxy: %p is already in the focus proxy chain", item); + return; + } + } + } + + QGraphicsItem *lastFocusProxy = d_ptr->focusProxy; + if (lastFocusProxy) + lastFocusProxy->d_ptr->focusProxyRefs.removeOne(&d_ptr->focusProxy); + d_ptr->focusProxy = item; + if (item) + item->d_ptr->focusProxyRefs << &d_ptr->focusProxy; +} + +/*! + \since 4.6 + + If this item, a child or descendant of this item currently has input + focus, this function will return a pointer to that item. If + no descendant has input focus, 0 is returned. + + \sa hasFocus(), setFocus(), QWidget::focusWidget() +*/ +QGraphicsItem *QGraphicsItem::focusItem() const +{ + return d_ptr->subFocusItem; +} + +/*! + \internal + + Returns this item's focus scope item. +*/ +QGraphicsItem *QGraphicsItem::focusScopeItem() const +{ + return d_ptr->focusScopeItem; +} + +/*! + \since 4.4 + Grabs the mouse input. + + This item will receive all mouse events for the scene until any of the + following events occurs: + + \list + \o The item becomes invisible + \o The item is removed from the scene + \o The item is deleted + \o The item call ungrabMouse() + \o Another item calls grabMouse(); the item will regain the mouse grab + when the other item calls ungrabMouse(). + \endlist + + When an item gains the mouse grab, it receives a QEvent::GrabMouse + event. When it loses the mouse grab, it receives a QEvent::UngrabMouse + event. These events can be used to detect when your item gains or loses + the mouse grab through other means than receiving mouse button events. + + It is almost never necessary to explicitly grab the mouse in Qt, as Qt + grabs and releases it sensibly. In particular, Qt grabs the mouse when you + press a mouse button, and keeps the mouse grabbed until you release the + last mouse button. Also, Qt::Popup widgets implicitly call grabMouse() + when shown, and ungrabMouse() when hidden. + + Note that only visible items can grab mouse input. Calling grabMouse() on + an invisible item has no effect. + + Keyboard events are not affected. + + \sa QGraphicsScene::mouseGrabberItem(), ungrabMouse(), grabKeyboard() +*/ +void QGraphicsItem::grabMouse() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::grabMouse: cannot grab mouse without scene"); + return; + } + if (!d_ptr->visible) { + qWarning("QGraphicsItem::grabMouse: cannot grab mouse while invisible"); + return; + } + d_ptr->scene->d_func()->grabMouse(this); +} + +/*! + \since 4.4 + Releases the mouse grab. + + \sa grabMouse(), ungrabKeyboard() +*/ +void QGraphicsItem::ungrabMouse() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::ungrabMouse: cannot ungrab mouse without scene"); + return; + } + d_ptr->scene->d_func()->ungrabMouse(this); +} + +/*! + \since 4.4 + Grabs the keyboard input. + + The item will receive all keyboard input to the scene until one of the + following events occur: + + \list + \o The item becomes invisible + \o The item is removed from the scene + \o The item is deleted + \o The item calls ungrabKeyboard() + \o Another item calls grabKeyboard(); the item will regain the keyboard grab + when the other item calls ungrabKeyboard(). + \endlist + + When an item gains the keyboard grab, it receives a QEvent::GrabKeyboard + event. When it loses the keyboard grab, it receives a + QEvent::UngrabKeyboard event. These events can be used to detect when your + item gains or loses the keyboard grab through other means than gaining + input focus. + + It is almost never necessary to explicitly grab the keyboard in Qt, as Qt + grabs and releases it sensibly. In particular, Qt grabs the keyboard when + your item gains input focus, and releases it when your item loses input + focus, or when the item is hidden. + + Note that only visible items can grab keyboard input. Calling + grabKeyboard() on an invisible item has no effect. + + Keyboard events are not affected. + + \sa ungrabKeyboard(), grabMouse(), setFocus() +*/ +void QGraphicsItem::grabKeyboard() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::grabKeyboard: cannot grab keyboard without scene"); + return; + } + if (!d_ptr->visible) { + qWarning("QGraphicsItem::grabKeyboard: cannot grab keyboard while invisible"); + return; + } + d_ptr->scene->d_func()->grabKeyboard(this); +} + +/*! + \since 4.4 + Releases the keyboard grab. + + \sa grabKeyboard(), ungrabMouse() +*/ +void QGraphicsItem::ungrabKeyboard() +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::ungrabKeyboard: cannot ungrab keyboard without scene"); + return; + } + d_ptr->scene->d_func()->ungrabKeyboard(this); +} + +/*! + Returns the position of the item in parent coordinates. If the item has no + parent, its position is given in scene coordinates. + + The position of the item describes its origin (local coordinate + (0, 0)) in parent coordinates; this function returns the same as + mapToParent(0, 0). + + For convenience, you can also call scenePos() to determine the + item's position in scene coordinates, regardless of its parent. + + \sa x(), y(), setPos(), transform(), {The Graphics View Coordinate System} +*/ +QPointF QGraphicsItem::pos() const +{ + return d_ptr->pos; +} + +/*! + \fn QGraphicsItem::x() const + + This convenience function is equivalent to calling pos().x(). + + \sa y() +*/ + +/*! + \since 4.6 + + Set's the \a x coordinate of the item's position. Equivalent to + calling setPos(x, y()). + + \sa x(), setPos() +*/ +void QGraphicsItem::setX(qreal x) +{ + if (d_ptr->inDestructor) + return; + + if (qIsNaN(x)) + return; + + setPos(QPointF(x, d_ptr->pos.y())); +} + +/*! + \fn QGraphicsItem::y() const + + This convenience function is equivalent to calling pos().y(). + + \sa x() +*/ + +/*! + \since 4.6 + + Set's the \a y coordinate of the item's position. Equivalent to + calling setPos(x(), y). + + \sa x(), setPos() +*/ +void QGraphicsItem::setY(qreal y) +{ + if (d_ptr->inDestructor) + return; + + if (qIsNaN(y)) + return; + + setPos(QPointF(d_ptr->pos.x(), y)); +} + +/*! + Returns the item's position in scene coordinates. This is + equivalent to calling \c mapToScene(0, 0). + + \sa pos(), sceneTransform(), {The Graphics View Coordinate System} +*/ +QPointF QGraphicsItem::scenePos() const +{ + return mapToScene(0, 0); +} + +/*! + \internal + + Sets the position \a pos. +*/ +void QGraphicsItemPrivate::setPosHelper(const QPointF &pos) +{ + Q_Q(QGraphicsItem); + inSetPosHelper = 1; + if (scene) + q->prepareGeometryChange(); + QPointF oldPos = this->pos; + this->pos = pos; + dirtySceneTransform = 1; + inSetPosHelper = 0; + if (isObject) { + if (pos.x() != oldPos.x()) + emit static_cast(q_ptr)->xChanged(); + if (pos.y() != oldPos.y()) + emit static_cast(q_ptr)->yChanged(); + } +} + +/*! + \internal + + Sets the transform \a transform. +*/ +void QGraphicsItemPrivate::setTransformHelper(const QTransform &transform) +{ + q_ptr->prepareGeometryChange(); + transformData->transform = transform; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + Sets the position of the item to \a pos, which is in parent + coordinates. For items with no parent, \a pos is in scene + coordinates. + + The position of the item describes its origin (local coordinate + (0, 0)) in parent coordinates. + + \sa pos(), scenePos(), {The Graphics View Coordinate System} +*/ +void QGraphicsItem::setPos(const QPointF &pos) +{ + if (d_ptr->pos == pos) + return; + + if (d_ptr->inDestructor) + return; + + // Update and repositition. + if (!(d_ptr->flags & (ItemSendsGeometryChanges | ItemSendsScenePositionChanges))) { + d_ptr->setPosHelper(pos); + if (d_ptr->isWidget) + static_cast(this)->d_func()->setGeometryFromSetPos(); + if (d_ptr->scenePosDescendants) + d_ptr->sendScenePosChange(); + return; + } + + // Notify the item that the position is changing. + const QVariant newPosVariant(itemChange(ItemPositionChange, QVariant::fromValue(pos))); + QPointF newPos = newPosVariant.toPointF(); + if (newPos == d_ptr->pos) + return; + + // Update and repositition. + d_ptr->setPosHelper(newPos); + + // Send post-notification. + itemChange(QGraphicsItem::ItemPositionHasChanged, newPosVariant); + d_ptr->sendScenePosChange(); +} + +/*! + \fn void QGraphicsItem::setPos(qreal x, qreal y) + \overload + + This convenience function is equivalent to calling setPos(QPointF(\a x, \a + y)). +*/ + +/*! + \fn void QGraphicsItem::moveBy(qreal dx, qreal dy) + + Moves the item by \a dx points horizontally, and \a dy point + vertically. This function is equivalent to calling setPos(pos() + + QPointF(\a dx, \a dy)). +*/ + +/*! + If this item is part of a scene that is viewed by a QGraphicsView, this + convenience function will attempt to scroll the view to ensure that \a + rect is visible inside the view's viewport. If \a rect is a null rect (the + default), QGraphicsItem will default to the item's bounding rect. \a xmargin + and \a ymargin are the number of pixels the view should use for margins. + + If the specified rect cannot be reached, the contents are scrolled to the + nearest valid position. + + If this item is not viewed by a QGraphicsView, this function does nothing. + + \sa QGraphicsView::ensureVisible() +*/ +void QGraphicsItem::ensureVisible(const QRectF &rect, int xmargin, int ymargin) +{ + if (d_ptr->scene) { + QRectF sceneRect; + if (!rect.isNull()) + sceneRect = sceneTransform().mapRect(rect); + else + sceneRect = sceneBoundingRect(); + foreach (QGraphicsView *view, d_ptr->scene->d_func()->views) + view->ensureVisible(sceneRect, xmargin, ymargin); + } +} + +/*! + \fn void QGraphicsItem::ensureVisible(qreal x, qreal y, qreal w, qreal h, + int xmargin = 50, int ymargin = 50) + + This convenience function is equivalent to calling + ensureVisible(QRectF(\a x, \a y, \a w, \a h), \a xmargin, \a ymargin): +*/ + +/*! + \obsolete + + Returns the item's affine transformation matrix. This is a subset or the + item's full transformation matrix, and might not represent the item's full + transformation. + + Use transform() instead. + + \sa setTransform(), sceneTransform() +*/ +QMatrix QGraphicsItem::matrix() const +{ + return transform().toAffine(); +} + +/*! + \since 4.3 + + Returns this item's transformation matrix. + + The transformation matrix is combined with the item's rotation(), scale() + and transformations() into a combined transformations for the item. + + The default transformation matrix is an identity matrix. + + \sa setTransform(), sceneTransform() +*/ +QTransform QGraphicsItem::transform() const +{ + if (!d_ptr->transformData) + return QTransform(); + return d_ptr->transformData->transform; +} + +/*! + \since 4.6 + + Returns the clockwise rotation, in degrees, around the Z axis. The default + value is 0 (i.e., the item is not rotated). + + The rotation is combined with the item's scale(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa setRotation(), transformOriginPoint(), {Transformations} +*/ +qreal QGraphicsItem::rotation() const +{ + if (!d_ptr->transformData) + return 0; + return d_ptr->transformData->rotation; +} + +/*! + \since 4.6 + + Sets the clockwise rotation \a angle, in degrees, around the Z axis. The + default value is 0 (i.e., the item is not rotated). Assigning a negative + value will rotate the item counter-clockwise. Normally the rotation angle + is in the range (-360, 360), but it's also possible to assign values + outside of this range (e.g., a rotation of 370 degrees is the same as a + rotation of 10 degrees). + + The item is rotated around its transform origin point, which by default + is (0, 0). You can select a different transformation origin by calling + setTransformOriginPoint(). + + The rotation is combined with the item's scale(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa rotation(), setTransformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setRotation(qreal angle) +{ + prepareGeometryChange(); + qreal newRotation = angle; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the rotation is changing. + const QVariant newRotationVariant(itemChange(ItemRotationChange, angle)); + newRotation = newRotationVariant.toReal(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->rotation == newRotation) + return; + + d_ptr->transformData->rotation = newRotation; + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemRotationHasChanged, newRotation); + + if (d_ptr->isObject) + emit static_cast(this)->rotationChanged(); + + d_ptr->transformChanged(); +} + +/*! + \since 4.6 + + Returns the scale factor of the item. The default scale factor is 1.0 + (i.e., the item is not scaled). + + The scale is combined with the item's rotation(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa setScale(), rotation(), {Transformations} +*/ +qreal QGraphicsItem::scale() const +{ + if (!d_ptr->transformData) + return 1.; + return d_ptr->transformData->scale; +} + +/*! + \since 4.6 + + Sets the scale \a factor of the item. The default scale factor is 1.0 + (i.e., the item is not scaled). A scale factor of 0.0 will collapse the + item to a single point. If you provide a negative scale factor, the + item will be flipped and mirrored (i.e., rotated 180 degrees). + + The item is scaled around its transform origin point, which by default + is (0, 0). You can select a different transformation origin by calling + setTransformOriginPoint(). + + The scale is combined with the item's rotation(), transform() and + transformations() to map the item's coordinate system to the parent item. + + \sa scale(), setTransformOriginPoint(), {Transformations Example} +*/ +void QGraphicsItem::setScale(qreal factor) +{ + prepareGeometryChange(); + qreal newScale = factor; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the scale is changing. + const QVariant newScaleVariant(itemChange(ItemScaleChange, factor)); + newScale = newScaleVariant.toReal(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->scale == newScale) + return; + + d_ptr->transformData->scale = newScale; + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemScaleHasChanged, newScale); + + if (d_ptr->isObject) + emit static_cast(this)->scaleChanged(); + + d_ptr->transformChanged(); +} + + +/*! + \since 4.6 + + Returns a list of graphics transforms that currently apply to this item. + + QGraphicsTransform is for applying and controlling a chain of individual + transformation operations on an item. It's particularly useful in + animations, where each transform operation needs to be interpolated + independently, or differently. + + The transformations are combined with the item's rotation(), scale() and + transform() to map the item's coordinate system to the parent item. + + \sa scale(), rotation(), transformOriginPoint(), {Transformations} +*/ +QList QGraphicsItem::transformations() const +{ + if (!d_ptr->transformData) + return QList(); + return d_ptr->transformData->graphicsTransforms; +} + +/*! + \since 4.6 + + Sets a list of graphics \a transformations (QGraphicsTransform) that + currently apply to this item. + + If all you want is to rotate or scale an item, you should call setRotation() + or setScale() instead. If you want to set an arbitrary transformation on + an item, you can call setTransform(). + + QGraphicsTransform is for applying and controlling a chain of individual + transformation operations on an item. It's particularly useful in + animations, where each transform operation needs to be interpolated + independently, or differently. + + The transformations are combined with the item's rotation(), scale() and + transform() to map the item's coordinate system to the parent item. + + \sa scale(), setTransformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setTransformations(const QList &transformations) +{ + prepareGeometryChange(); + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + d_ptr->transformData->graphicsTransforms = transformations; + for (int i = 0; i < transformations.size(); ++i) + transformations.at(i)->d_func()->setItem(this); + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + d_ptr->transformChanged(); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::prependGraphicsTransform(QGraphicsTransform *t) +{ + if (!transformData) + transformData = new QGraphicsItemPrivate::TransformData; + if (!transformData->graphicsTransforms.contains(t)) + transformData->graphicsTransforms.prepend(t); + + Q_Q(QGraphicsItem); + t->d_func()->setItem(q); + transformData->onlyTransform = false; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::appendGraphicsTransform(QGraphicsTransform *t) +{ + if (!transformData) + transformData = new QGraphicsItemPrivate::TransformData; + if (!transformData->graphicsTransforms.contains(t)) + transformData->graphicsTransforms.append(t); + + Q_Q(QGraphicsItem); + t->d_func()->setItem(q); + transformData->onlyTransform = false; + dirtySceneTransform = 1; + transformChanged(); +} + +/*! + \since 4.6 + + Returns the origin point for the transformation in item coordinates. + + The default is QPointF(0,0). + + \sa setTransformOriginPoint(), {Transformations} +*/ +QPointF QGraphicsItem::transformOriginPoint() const +{ + if (!d_ptr->transformData) + return QPointF(0,0); + return QPointF(d_ptr->transformData->xOrigin, d_ptr->transformData->yOrigin); +} + +/*! + \since 4.6 + + Sets the \a origin point for the transformation in item coordinates. + + \sa transformOriginPoint(), {Transformations} +*/ +void QGraphicsItem::setTransformOriginPoint(const QPointF &origin) +{ + prepareGeometryChange(); + QPointF newOrigin = origin; + + if (d_ptr->flags & ItemSendsGeometryChanges) { + // Notify the item that the origin point is changing. + const QVariant newOriginVariant(itemChange(ItemTransformOriginPointChange, + QVariant::fromValue(origin))); + newOrigin = newOriginVariant.toPointF(); + } + + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + if (d_ptr->transformData->xOrigin == newOrigin.x() + && d_ptr->transformData->yOrigin == newOrigin.y()) { + return; + } + + d_ptr->transformData->xOrigin = newOrigin.x(); + d_ptr->transformData->yOrigin = newOrigin.y(); + d_ptr->transformData->onlyTransform = false; + d_ptr->dirtySceneTransform = 1; + + // Send post-notification. + if (d_ptr->flags & ItemSendsGeometryChanges) + itemChange(ItemTransformOriginPointHasChanged, QVariant::fromValue(newOrigin)); +} + +/*! + \fn void QGraphicsItem::setTransformOriginPoint(qreal x, qreal y) + + \since 4.6 + \overload + + Sets the origin point for the transformation in item coordinates. + This is equivalent to calling setTransformOriginPoint(QPointF(\a x, \a y)). + + \sa setTransformOriginPoint(), {Transformations} +*/ + + +/*! + \obsolete + + Use sceneTransform() instead. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate System} +*/ +QMatrix QGraphicsItem::sceneMatrix() const +{ + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform.toAffine(); +} + + +/*! + \since 4.3 + + Returns this item's scene transformation matrix. This matrix can be used + to map coordinates and geometrical shapes from this item's local + coordinate system to the scene's coordinate system. To map coordinates + from the scene, you must first invert the returned matrix. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 4 + + Unlike transform(), which returns only an item's local transformation, this + function includes the item's (and any parents') position, and all the transfomation properties. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate System}, {Transformations} +*/ +QTransform QGraphicsItem::sceneTransform() const +{ + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform; +} + +/*! + \since 4.3 + + Returns this item's device transformation matrix, using \a + viewportTransform to map from scene to device coordinates. This matrix can + be used to map coordinates and geometrical shapes from this item's local + coordinate system to the viewport's (or any device's) coordinate + system. To map coordinates from the viewport, you must first invert the + returned matrix. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 5 + + This function is the same as combining this item's scene transform with + the view's viewport transform, but it also understands the + ItemIgnoresTransformations flag. The device transform can be used to do + accurate coordinate mapping (and collision detection) for untransformable + items. + + \sa transform(), setTransform(), scenePos(), {The Graphics View Coordinate + System}, itemTransform() +*/ +QTransform QGraphicsItem::deviceTransform(const QTransform &viewportTransform) const +{ + // Ensure we return the standard transform if we're not untransformable. + if (!d_ptr->itemIsUntransformable()) { + d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform * viewportTransform; + } + + // Find the topmost item that ignores view transformations. + const QGraphicsItem *untransformedAncestor = this; + QList parents; + while (untransformedAncestor && ((untransformedAncestor->d_ptr->ancestorFlags + & QGraphicsItemPrivate::AncestorIgnoresTransformations))) { + parents.prepend(untransformedAncestor); + untransformedAncestor = untransformedAncestor->parentItem(); + } + + if (!untransformedAncestor) { + // Assert in debug mode, continue in release. + Q_ASSERT_X(untransformedAncestor, "QGraphicsItem::deviceTransform", + "Invalid object structure!"); + return QTransform(); + } + + // First translate the base untransformable item. + untransformedAncestor->d_ptr->ensureSceneTransform(); + QPointF mappedPoint = (untransformedAncestor->d_ptr->sceneTransform * viewportTransform).map(QPointF(0, 0)); + + // COMBINE + QTransform matrix = QTransform::fromTranslate(mappedPoint.x(), mappedPoint.y()); + if (untransformedAncestor->d_ptr->transformData) + matrix = untransformedAncestor->d_ptr->transformData->computedFullTransform(&matrix); + + // Then transform and translate all children. + for (int i = 0; i < parents.size(); ++i) { + const QGraphicsItem *parent = parents.at(i); + parent->d_ptr->combineTransformFromParent(&matrix); + } + + return matrix; +} + +/*! + \since 4.5 + + Returns a QTransform that maps coordinates from this item to \a other. If + \a ok is not null, and if there is no such transform, the boolean pointed + to by \a ok will be set to false; otherwise it will be set to true. + + This transform provides an alternative to the mapToItem() or mapFromItem() + functions, by returning the appropriate transform so that you can map + shapes and coordinates yourself. It also helps you write more efficient + code when repeatedly mapping between the same two items. + + \note In rare circumstances, there is no transform that maps between two + items. + + \sa mapToItem(), mapFromItem(), deviceTransform() +*/ +QTransform QGraphicsItem::itemTransform(const QGraphicsItem *other, bool *ok) const +{ + // Catch simple cases first. + if (other == 0) { + qWarning("QGraphicsItem::itemTransform: null pointer passed"); + return QTransform(); + } + if (other == this) { + if (ok) + *ok = true; + return QTransform(); + } + + QGraphicsItem *parent = d_ptr->parent; + const QGraphicsItem *otherParent = other->d_ptr->parent; + + // This is other's child + if (parent == other) { + if (ok) + *ok = true; + QTransform x; + d_ptr->combineTransformFromParent(&x); + return x; + } + + // This is other's parent + if (otherParent == this) { + const QPointF &otherPos = other->d_ptr->pos; + if (other->d_ptr->transformData) { + QTransform otherToParent; + other->d_ptr->combineTransformFromParent(&otherToParent); + return otherToParent.inverted(ok); + } + if (ok) + *ok = true; + return QTransform::fromTranslate(-otherPos.x(), -otherPos.y()); + } + + // Siblings + if (parent == otherParent) { + // COMBINE + const QPointF &itemPos = d_ptr->pos; + const QPointF &otherPos = other->d_ptr->pos; + if (!d_ptr->transformData && !other->d_ptr->transformData) { + QPointF delta = itemPos - otherPos; + if (ok) + *ok = true; + return QTransform::fromTranslate(delta.x(), delta.y()); + } + + QTransform itemToParent; + d_ptr->combineTransformFromParent(&itemToParent); + QTransform otherToParent; + other->d_ptr->combineTransformFromParent(&otherToParent); + return itemToParent * otherToParent.inverted(ok); + } + + // Find the closest common ancestor. If the two items don't share an + // ancestor, then the only way is to combine their scene transforms. + const QGraphicsItem *commonAncestor = commonAncestorItem(other); + if (!commonAncestor) { + d_ptr->ensureSceneTransform(); + other->d_ptr->ensureSceneTransform(); + return d_ptr->sceneTransform * other->d_ptr->sceneTransform.inverted(ok); + } + + // If the two items are cousins (in sibling branches), map both to the + // common ancestor, and combine the two transforms. + bool cousins = other != commonAncestor && this != commonAncestor; + if (cousins) { + bool good = false; + QTransform thisToScene = itemTransform(commonAncestor, &good); + QTransform otherToScene(Qt::Uninitialized); + if (good) + otherToScene = other->itemTransform(commonAncestor, &good); + if (!good) { + if (ok) + *ok = false; + return QTransform(); + } + return thisToScene * otherToScene.inverted(ok); + } + + // One is an ancestor of the other; walk the chain. + bool parentOfOther = isAncestorOf(other); + const QGraphicsItem *child = parentOfOther ? other : this; + const QGraphicsItem *root = parentOfOther ? this : other; + + QTransform x; + const QGraphicsItem *p = child; + do { + p->d_ptr.data()->combineTransformToParent(&x); + } while ((p = p->d_ptr->parent) && p != root); + if (parentOfOther) + return x.inverted(ok); + if (ok) + *ok = true; + return x; +} + +/*! + \obsolete + + Sets the item's affine transformation matrix. This is a subset or the + item's full transformation matrix, and might not represent the item's full + transformation. + + Use setTransform() instead. + + \sa transform(), {The Graphics View Coordinate System} +*/ +void QGraphicsItem::setMatrix(const QMatrix &matrix, bool combine) +{ + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + QTransform newTransform(combine ? QTransform(matrix) * d_ptr->transformData->transform : QTransform(matrix)); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + if (!(d_ptr->flags & ItemSendsGeometryChanges)) { + d_ptr->setTransformHelper(newTransform); + return; + } + + // Notify the item that the transformation matrix is changing. + const QVariant newMatrixVariant = QVariant::fromValue(newTransform.toAffine()); + newTransform = QTransform(qvariant_cast(itemChange(ItemMatrixChange, newMatrixVariant))); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + d_ptr->setTransformHelper(newTransform); + + // Send post-notification. + itemChange(ItemTransformHasChanged, QVariant::fromValue(newTransform)); +} + +/*! + \since 4.3 + + Sets the item's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + To simplify interation with items using a transformed view, QGraphicsItem + provides mapTo... and mapFrom... functions that can translate between + items' and the scene's coordinates. For example, you can call mapToScene() + to map an item coordiate to a scene coordinate, or mapFromScene() to map + from scene coordinates to item coordinates. + + The transformation matrix is combined with the item's rotation(), scale() + and transformations() into a combined transformation that maps the item's + coordinate system to its parent. + + \sa transform(), setRotation(), setScale(), setTransformOriginPoint(), {The Graphics View Coordinate System}, {Transformations} +*/ +void QGraphicsItem::setTransform(const QTransform &matrix, bool combine) +{ + if (!d_ptr->transformData) + d_ptr->transformData = new QGraphicsItemPrivate::TransformData; + + QTransform newTransform(combine ? matrix * d_ptr->transformData->transform : matrix); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + if (!(d_ptr->flags & (ItemSendsGeometryChanges | ItemSendsScenePositionChanges))) { + d_ptr->setTransformHelper(newTransform); + if (d_ptr->scenePosDescendants) + d_ptr->sendScenePosChange(); + return; + } + + // Notify the item that the transformation matrix is changing. + const QVariant newTransformVariant(itemChange(ItemTransformChange, + QVariant::fromValue(newTransform))); + newTransform = qvariant_cast(newTransformVariant); + if (d_ptr->transformData->transform == newTransform) + return; + + // Update and set the new transformation. + d_ptr->setTransformHelper(newTransform); + + // Send post-notification. + itemChange(ItemTransformHasChanged, newTransformVariant); + d_ptr->sendScenePosChange(); +} + +/*! + \obsolete + + Use resetTransform() instead. +*/ +void QGraphicsItem::resetMatrix() +{ + resetTransform(); +} + +/*! + \since 4.3 + + Resets this item's transformation matrix to the identity matrix or + all the transformation properties to their default values. + This is equivalent to calling \c setTransform(QTransform()). + + \sa setTransform(), transform() +*/ +void QGraphicsItem::resetTransform() +{ + setTransform(QTransform(), false); +} + +/*! + \obsolete + + Use + + \code + setRotation(rotation() + angle); + \endcode + + instead. + + Rotates the current item transformation \a angle degrees clockwise around + its origin. To translate around an arbitrary point (x, y), you need to + combine translation and rotation with setTransform(). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 6 + + \sa setTransform(), transform(), scale(), shear(), translate() +*/ +void QGraphicsItem::rotate(qreal angle) +{ + setTransform(QTransform().rotate(angle), true); +} + +/*! + \obsolete + + Use + + \code + setTransform(QTransform::fromScale(sx, sy), true); + \endcode + + instead. + + Scales the current item transformation by (\a sx, \a sy) around its + origin. To scale from an arbitrary point (x, y), you need to combine + translation and scaling with setTransform(). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 7 + + \sa setTransform(), transform() +*/ +void QGraphicsItem::scale(qreal sx, qreal sy) +{ + setTransform(QTransform::fromScale(sx, sy), true); +} + +/*! + \obsolete + + Use + + \code + setTransform(QTransform().shear(sh, sv), true); + \endcode + + instead. + + Shears the current item transformation by (\a sh, \a sv). + + \sa setTransform(), transform() +*/ +void QGraphicsItem::shear(qreal sh, qreal sv) +{ + setTransform(QTransform().shear(sh, sv), true); +} + +/*! + \obsolete + + Use setPos() or setTransformOriginPoint() instead. For identical + behavior, use + + \code + setTransform(QTransform::fromTranslate(dx, dy), true); + \endcode + + Translates the current item transformation by (\a dx, \a dy). + + If all you want is to move an item, you should call moveBy() or + setPos() instead; this function changes the item's translation, + which is conceptually separate from its position. + + \sa setTransform(), transform() +*/ +void QGraphicsItem::translate(qreal dx, qreal dy) +{ + setTransform(QTransform::fromTranslate(dx, dy), true); +} + +/*! + This virtual function is called twice for all items by the + QGraphicsScene::advance() slot. In the first phase, all items are called + with \a phase == 0, indicating that items on the scene are about to + advance, and then all items are called with \a phase == 1. Reimplement + this function to update your item if you need simple scene-controlled + animation. + + The default implementation does nothing. + + For individual item animation, an alternative to this function is to + either use QGraphicsItemAnimation, or to multiple-inherit from QObject and + QGraphicsItem, and animate your item using QObject::startTimer() and + QObject::timerEvent(). + + \sa QGraphicsItemAnimation, QTimeLine +*/ +void QGraphicsItem::advance(int phase) +{ + Q_UNUSED(phase); +} + +/*! + Returns the Z-value of the item. The Z-value affects the stacking order of + sibling (neighboring) items. + + The default Z-value is 0. + + \sa setZValue(), {QGraphicsItem#Sorting}{Sorting}, stackBefore(), ItemStacksBehindParent +*/ +qreal QGraphicsItem::zValue() const +{ + return d_ptr->z; +} + +/*! + Sets the Z-value of the item to \a z. The Z value decides the stacking + order of sibling (neighboring) items. A sibling item of high Z value will + always be drawn on top of another sibling item with a lower Z value. + + If you restore the Z value, the item's insertion order will decide its + stacking order. + + The Z-value does not affect the item's size in any way. + + The default Z-value is 0. + + \sa zValue(), {QGraphicsItem#Sorting}{Sorting}, stackBefore(), ItemStacksBehindParent +*/ +void QGraphicsItem::setZValue(qreal z) +{ + const QVariant newZVariant(itemChange(ItemZValueChange, z)); + qreal newZ = newZVariant.toReal(); + if (newZ == d_ptr->z) + return; + + if (d_ptr->scene && d_ptr->scene->d_func()->indexMethod != QGraphicsScene::NoIndex) { + // Z Value has changed, we have to notify the index. + d_ptr->scene->d_func()->index->itemChange(this, ItemZValueChange, &newZ); + } + + d_ptr->z = newZ; + if (d_ptr->parent) + d_ptr->parent->d_ptr->needSortChildren = 1; + else if (d_ptr->scene) + d_ptr->scene->d_func()->needSortTopLevelItems = 1; + + if (d_ptr->scene) + d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true); + + itemChange(ItemZValueHasChanged, newZVariant); + + if (d_ptr->flags & ItemNegativeZStacksBehindParent) + setFlag(QGraphicsItem::ItemStacksBehindParent, z < qreal(0.0)); + + if (d_ptr->isObject) + emit static_cast(this)->zChanged(); +} + +/*! + \internal + + Ensures that the list of children is sorted by insertion order, and that + the siblingIndexes are packed (no gaps), and start at 0. + + ### This function is almost identical to + QGraphicsScenePrivate::ensureSequentialTopLevelSiblingIndexes(). +*/ +void QGraphicsItemPrivate::ensureSequentialSiblingIndex() +{ + if (!sequentialOrdering) { + qSort(children.begin(), children.end(), insertionOrder); + sequentialOrdering = 1; + needSortChildren = 1; + } + if (holesInSiblingIndex) { + holesInSiblingIndex = 0; + for (int i = 0; i < children.size(); ++i) + children[i]->d_ptr->siblingIndex = i; + } +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::sendScenePosChange() +{ + Q_Q(QGraphicsItem); + if (scene) { + if (flags & QGraphicsItem::ItemSendsScenePositionChanges) + q->itemChange(QGraphicsItem::ItemScenePositionHasChanged, q->scenePos()); + if (scenePosDescendants) { + foreach (QGraphicsItem *item, scene->d_func()->scenePosItems) { + if (q->isAncestorOf(item)) + item->itemChange(QGraphicsItem::ItemScenePositionHasChanged, item->scenePos()); + } + } + } +} + +/*! + \since 4.6 + + Stacks this item before \a sibling, which must be a sibling item (i.e., the + two items must share the same parent item, or must both be toplevel items). + The \a sibling must have the same Z value as this item, otherwise calling + this function will have no effect. + + By default, all sibling items are stacked by insertion order (i.e., the + first item you add is drawn before the next item you add). If two items' Z + values are different, then the item with the highest Z value is drawn on + top. When the Z values are the same, the insertion order will decide the + stacking order. + + \sa setZValue(), ItemStacksBehindParent, {QGraphicsItem#Sorting}{Sorting} +*/ +void QGraphicsItem::stackBefore(const QGraphicsItem *sibling) +{ + if (sibling == this) + return; + if (!sibling || d_ptr->parent != sibling->parentItem()) { + qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling); + return; + } + QList *siblings = d_ptr->parent + ? &d_ptr->parent->d_ptr->children + : (d_ptr->scene ? &d_ptr->scene->d_func()->topLevelItems : 0); + if (!siblings) { + qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling); + return; + } + + // First, make sure that the sibling indexes have no holes. This also + // marks the children list for sorting. + if (d_ptr->parent) + d_ptr->parent->d_ptr->ensureSequentialSiblingIndex(); + else + d_ptr->scene->d_func()->ensureSequentialTopLevelSiblingIndexes(); + + // Only move items with the same Z value, and that need moving. + int siblingIndex = sibling->d_ptr->siblingIndex; + int myIndex = d_ptr->siblingIndex; + if (myIndex >= siblingIndex) { + siblings->move(myIndex, siblingIndex); + // Fixup the insertion ordering. + for (int i = 0; i < siblings->size(); ++i) { + int &index = siblings->at(i)->d_ptr->siblingIndex; + if (i != siblingIndex && index >= siblingIndex && index <= myIndex) + ++index; + } + d_ptr->siblingIndex = siblingIndex; + for (int i = 0; i < siblings->size(); ++i) { + int &index = siblings->at(i)->d_ptr->siblingIndex; + if (i != siblingIndex && index >= siblingIndex && index <= myIndex) + siblings->at(i)->d_ptr->siblingOrderChange(); + } + d_ptr->siblingOrderChange(); + } +} + +/*! + Returns the bounding rect of this item's descendants (i.e., its + children, their children, etc.) in local coordinates. The + rectangle will contain all descendants after they have been mapped + to local coordinates. If the item has no children, this function + returns an empty QRectF. + + This does not include this item's own bounding rect; it only returns + its descendants' accumulated bounding rect. If you need to include this + item's bounding rect, you can add boundingRect() to childrenBoundingRect() + using QRectF::operator|(). + + This function is linear in complexity; it determines the size of the + returned bounding rect by iterating through all descendants. + + \sa boundingRect(), sceneBoundingRect() +*/ +QRectF QGraphicsItem::childrenBoundingRect() const +{ + if (!d_ptr->dirtyChildrenBoundingRect) + return d_ptr->childrenBoundingRect; + + d_ptr->childrenBoundingRect = QRectF(); + d_ptr->childrenBoundingRectHelper(0, &d_ptr->childrenBoundingRect, 0); + d_ptr->dirtyChildrenBoundingRect = 0; + return d_ptr->childrenBoundingRect; +} + +/*! + \fn virtual QRectF QGraphicsItem::boundingRect() const = 0 + + This pure virtual function defines the outer bounds of the item as + a rectangle; all painting must be restricted to inside an item's + bounding rect. QGraphicsView uses this to determine whether the + item requires redrawing. + + Although the item's shape can be arbitrary, the bounding rect is + always rectangular, and it is unaffected by the items' + transformation. + + If you want to change the item's bounding rectangle, you must first call + prepareGeometryChange(). This notifies the scene of the imminent change, + so that its can update its item geometry index; otherwise, the scene will + be unaware of the item's new geometry, and the results are undefined + (typically, rendering artifacts are left around in the view). + + Reimplement this function to let QGraphicsView determine what + parts of the widget, if any, need to be redrawn. + + Note: For shapes that paint an outline / stroke, it is important + to include half the pen width in the bounding rect. It is not + necessary to compensate for antialiasing, though. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 8 + + \sa boundingRegion(), shape(), contains(), {The Graphics View Coordinate + System}, prepareGeometryChange() +*/ + +/*! + Returns the bounding rect of this item in scene coordinates, by combining + sceneTransform() with boundingRect(). + + \sa boundingRect(), {The Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::sceneBoundingRect() const +{ + // Find translate-only offset + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = this; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF br = boundingRect(); + br.translate(offset); + if (!parentItem) + return br; + if (parentItem->d_ptr->hasTranslateOnlySceneTransform()) { + br.translate(parentItem->d_ptr->sceneTransform.dx(), parentItem->d_ptr->sceneTransform.dy()); + return br; + } + return parentItem->d_ptr->sceneTransform.mapRect(br); +} + +/*! + Returns the shape of this item as a QPainterPath in local + coordinates. The shape is used for many things, including collision + detection, hit tests, and for the QGraphicsScene::items() functions. + + The default implementation calls boundingRect() to return a simple + rectangular shape, but subclasses can reimplement this function to return + a more accurate shape for non-rectangular items. For example, a round item + may choose to return an elliptic shape for better collision detection. For + example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 9 + + The outline of a shape can vary depending on the width and style of the + pen used when drawing. If you want to include this outline in the item's + shape, you can create a shape from the stroke using QPainterPathStroker. + + This function is called by the default implementations of contains() and + collidesWithPath(). + + \sa boundingRect(), contains(), prepareGeometryChange(), QPainterPathStroker +*/ +QPainterPath QGraphicsItem::shape() const +{ + QPainterPath path; + path.addRect(boundingRect()); + return path; +} + +/*! + Returns true if this item is clipped. An item is clipped if it has either + set the \l ItemClipsToShape flag, or if it or any of its ancestors has set + the \l ItemClipsChildrenToShape flag. + + Clipping affects the item's appearance (i.e., painting), as well as mouse + and hover event delivery. + + \sa clipPath(), shape(), setFlags() +*/ +bool QGraphicsItem::isClipped() const +{ + Q_D(const QGraphicsItem); + return (d->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) + || (d->flags & QGraphicsItem::ItemClipsToShape); +} + +/*! + \since 4.5 + + Returns this item's clip path, or an empty QPainterPath if this item is + not clipped. The clip path constrains the item's appearance and + interaction (i.e., restricts the area the item can draw, and it also + restricts the area that the item receives events). + + You can enable clipping by setting the ItemClipsToShape or + ItemClipsChildrenToShape flags. The item's clip path is calculated by + intersecting all clipping ancestors' shapes. If the item sets + ItemClipsToShape, the final clip is intersected with the item's own shape. + + \note Clipping introduces a performance penalty for all items involved; + you should generally avoid using clipping if you can (e.g., if your items + always draw inside boundingRect() or shape() boundaries, clipping is not + necessary). + + \sa isClipped(), shape(), setFlags() +*/ +QPainterPath QGraphicsItem::clipPath() const +{ + Q_D(const QGraphicsItem); + if (!isClipped()) + return QPainterPath(); + + const QRectF thisBoundingRect(boundingRect()); + if (thisBoundingRect.isEmpty()) + return QPainterPath(); + + QPainterPath clip; + // Start with the item's bounding rect. + clip.addRect(thisBoundingRect); + + if (d->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + const QGraphicsItem *parent = this; + const QGraphicsItem *lastParent = this; + + // Intersect any in-between clips starting at the top and moving downwards. + while ((parent = parent->d_ptr->parent)) { + if (parent->d_ptr->flags & ItemClipsChildrenToShape) { + // Map clip to the current parent and intersect with its shape/clipPath + clip = lastParent->itemTransform(parent).map(clip); + clip = clip.intersected(parent->shape()); + if (clip.isEmpty()) + return clip; + lastParent = parent; + } + + if (!(parent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) + break; + } + + if (lastParent != this) { + // Map clip back to the item's transform. + // ### what if itemtransform fails + clip = lastParent->itemTransform(this).map(clip); + } + } + + if (d->flags & ItemClipsToShape) + clip = clip.intersected(shape()); + + return clip; +} + +/*! + Returns true if this item contains \a point, which is in local + coordinates; otherwise, false is returned. It is most often called from + QGraphicsView to determine what item is under the cursor, and for that + reason, the implementation of this function should be as light-weight as + possible. + + By default, this function calls shape(), but you can reimplement it in a + subclass to provide a (perhaps more efficient) implementation. + + \sa shape(), boundingRect(), collidesWithPath() +*/ +bool QGraphicsItem::contains(const QPointF &point) const +{ + return isClipped() ? clipPath().contains(point) : shape().contains(point); +} + +/*! + + Returns true if this item collides with \a other; otherwise + returns false. + + The \a mode is applied to \a other, and the resulting shape or + bounding rectangle is then compared to this item's shape. The + default value for \a mode is Qt::IntersectsItemShape; \a other + collides with this item if it either intersects, contains, or is + contained by this item's shape (see Qt::ItemSelectionMode for + details). + + The default implementation is based on shape intersection, and it calls + shape() on both items. Because the complexity of arbitrary shape-shape + intersection grows with an order of magnitude when the shapes are complex, + this operation can be noticably time consuming. You have the option of + reimplementing this function in a subclass of QGraphicsItem to provide a + custom algorithm. This allows you to make use of natural constraints in + the shapes of your own items, in order to improve the performance of the + collision detection. For instance, two untransformed perfectly circular + items' collision can be determined very efficiently by comparing their + positions and radii. + + Keep in mind that when reimplementing this function and calling shape() or + boundingRect() on \a other, the returned coordinates must be mapped to + this item's coordinate system before any intersection can take place. + + \sa contains(), shape() +*/ +bool QGraphicsItem::collidesWithItem(const QGraphicsItem *other, Qt::ItemSelectionMode mode) const +{ + if (other == this) + return true; + if (!other) + return false; + // The items share the same clip if their closest clipper is the same, or + // if one clips the other. + bool clips = (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren); + bool otherClips = (other->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren); + if (clips || otherClips) { + const QGraphicsItem *closestClipper = isAncestorOf(other) ? this : parentItem(); + while (closestClipper && !(closestClipper->flags() & ItemClipsChildrenToShape)) + closestClipper = closestClipper->parentItem(); + const QGraphicsItem *otherClosestClipper = other->isAncestorOf(this) ? other : other->parentItem(); + while (otherClosestClipper && !(otherClosestClipper->flags() & ItemClipsChildrenToShape)) + otherClosestClipper = otherClosestClipper->parentItem(); + if (closestClipper == otherClosestClipper) { + d_ptr->localCollisionHack = 1; + bool res = collidesWithPath(mapFromItem(other, other->shape()), mode); + d_ptr->localCollisionHack = 0; + return res; + } + } + + QPainterPath otherShape = other->isClipped() ? other->clipPath() : other->shape(); + return collidesWithPath(mapFromItem(other, otherShape), mode); +} + +/*! + Returns true if this item collides with \a path. + + The collision is determined by \a mode. The default value for \a mode is + Qt::IntersectsItemShape; \a path collides with this item if it either + intersects, contains, or is contained by this item's shape. + + Note that this function checks whether the item's shape or + bounding rectangle (depending on \a mode) is contained within \a + path, and not whether \a path is contained within the items shape + or bounding rectangle. + + \sa collidesWithItem(), contains(), shape() +*/ +bool QGraphicsItem::collidesWithPath(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + if (path.isEmpty()) { + // No collision with empty paths. + return false; + } + + QRectF rectA(boundingRect()); + _q_adjustRect(&rectA); + QRectF rectB(path.controlPointRect()); + _q_adjustRect(&rectB); + if (!rectA.intersects(rectB)) { + // This we can determine efficiently. If the two rects neither + // intersect nor contain eachother, then the two items do not collide. + return false; + } + + // For further testing, we need this item's shape or bounding rect. + QPainterPath thisShape; + if (mode == Qt::IntersectsItemShape || mode == Qt::ContainsItemShape) + thisShape = (isClipped() && !d_ptr->localCollisionHack) ? clipPath() : shape(); + else + thisShape.addRect(rectA); + + if (thisShape == QPainterPath()) { + // Empty shape? No collision. + return false; + } + + // Use QPainterPath boolean operations to determine the collision, O(N*logN). + if (mode == Qt::IntersectsItemShape || mode == Qt::IntersectsItemBoundingRect) + return path.intersects(thisShape); + return path.contains(thisShape); +} + +/*! + Returns a list of all items that collide with this item. + + The way collisions are detected is determined by applying \a mode + to items that are compared to this item, i.e., each item's shape + or bounding rectangle is checked against this item's shape. The + default value for \a mode is Qt::IntersectsItemShape. + + \sa collidesWithItem() +*/ +QList QGraphicsItem::collidingItems(Qt::ItemSelectionMode mode) const +{ + if (d_ptr->scene) + return d_ptr->scene->collidingItems(this, mode); + return QList(); +} + +/*! + Returns true if this item's bounding rect is completely obscured by the + opaque shape of any of colliding items above it (i.e., with a higher Z + value than this item). + + Its implementation is based on calling isObscuredBy(), which you can + reimplement to provide a custom obscurity algorithm. + + \sa opaqueArea() +*/ +bool QGraphicsItem::isObscured() const +{ + return isObscured(QRectF()); +} + +/*! + \internal + + Item obscurity helper function. + + Returns true if the subrect \a rect of \a item's bounding rect is obscured + by \a other (i.e., \a other's opaque area covers \a item's \a rect + completely. \a other is assumed to already be "on top of" \a item + wrt. stacking order. +*/ +static bool qt_QGraphicsItem_isObscured(const QGraphicsItem *item, + const QGraphicsItem *other, + const QRectF &rect) +{ + return other->mapToItem(item, other->opaqueArea()).contains(rect); +} + +/*! + \overload + \since 4.3 + + Returns true if \a rect is completely obscured by the opaque shape of any + of colliding items above it (i.e., with a higher Z value than this item). + + Unlike the default isObscured() function, this function does not call + isObscuredBy(). + + \sa opaqueArea() +*/ +bool QGraphicsItem::isObscured(const QRectF &rect) const +{ + Q_D(const QGraphicsItem); + if (!d->scene) + return false; + + QRectF br = boundingRect(); + QRectF testRect = rect.isNull() ? br : rect; + + foreach (QGraphicsItem *item, d->scene->items(mapToScene(br), Qt::IntersectsItemBoundingRect)) { + if (item == this) + break; + if (qt_QGraphicsItem_isObscured(this, item, testRect)) + return true; + } + return false; +} + +/*! + \fn bool QGraphicsItem::isObscured(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling isObscured(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns true if this item's bounding rect is completely obscured by the + opaque shape of \a item. + + The base implementation maps \a item's opaqueArea() to this item's + coordinate system, and then checks if this item's boundingRect() is fully + contained within the mapped shape. + + You can reimplement this function to provide a custom algorithm for + determining whether this item is obscured by \a item. + + \sa opaqueArea(), isObscured() +*/ +bool QGraphicsItem::isObscuredBy(const QGraphicsItem *item) const +{ + if (!item) + return false; + return qt_closestItemFirst(item, this) + && qt_QGraphicsItem_isObscured(this, item, boundingRect()); +} + +/*! + This virtual function returns a shape representing the area where this + item is opaque. An area is opaque if it is filled using an opaque brush or + color (i.e., not transparent). + + This function is used by isObscuredBy(), which is called by underlying + items to determine if they are obscured by this item. + + The default implementation returns an empty QPainterPath, indicating that + this item is completely transparent and does not obscure any other items. + + \sa isObscuredBy(), isObscured(), shape() +*/ +QPainterPath QGraphicsItem::opaqueArea() const +{ + return QPainterPath(); +} + +/*! + \since 4.4 + + Returns the bounding region for this item. The coordinate space of the + returned region depends on \a itemToDeviceTransform. If you pass an + identity QTransform as a parameter, this function will return a local + coordinate region. + + The bounding region describes a coarse outline of the item's visual + contents. Although it's expensive to calculate, it's also more precise + than boundingRect(), and it can help to avoid unnecessary repainting when + an item is updated. This is particularly efficient for thin items (e.g., + lines or simple polygons). You can tune the granularity for the bounding + region by calling setBoundingRegionGranularity(). The default granularity + is 0; in which the item's bounding region is the same as its bounding + rect. + + \a itemToDeviceTransform is the transformation from item coordinates to + device coordinates. If you want this function to return a QRegion in scene + coordinates, you can pass sceneTransform() as an argument. + + \sa boundingRegionGranularity() +*/ +QRegion QGraphicsItem::boundingRegion(const QTransform &itemToDeviceTransform) const +{ + // ### Ideally we would have a better way to generate this region, + // preferably something in the lines of QPainterPath::toRegion(QTransform) + // coupled with a way to generate a painter path from a set of painter + // operations (e.g., QPicture::toPainterPath() or so). The current + // approach generates a bitmap with the size of the item's bounding rect + // in device coordinates, scaled by b.r.granularity, then paints the item + // into the bitmap, converts the result to a QRegion and scales the region + // back to device space with inverse granularity. + qreal granularity = boundingRegionGranularity(); + QRect deviceRect = itemToDeviceTransform.mapRect(boundingRect()).toRect(); + _q_adjustRect(&deviceRect); + if (granularity == 0.0) + return QRegion(deviceRect); + + int pad = 1; + QSize bitmapSize(qMax(1, int(deviceRect.width() * granularity) + pad * 2), + qMax(1, int(deviceRect.height() * granularity) + pad * 2)); + QImage mask(bitmapSize, QImage::Format_ARGB32_Premultiplied); + mask.fill(0); + QPainter p(&mask); + p.setRenderHints(QPainter::Antialiasing); + + // Transform painter (### this code is from QGraphicsScene::drawItemHelper + // and doesn't work properly with perspective transformations). + QPointF viewOrigo = itemToDeviceTransform.map(QPointF(0, 0)); + QPointF offset = viewOrigo - deviceRect.topLeft(); + p.scale(granularity, granularity); + p.translate(offset); + p.translate(pad, pad); + p.setWorldTransform(itemToDeviceTransform, true); + p.translate(itemToDeviceTransform.inverted().map(QPointF(0, 0))); + + // Render + QStyleOptionGraphicsItem option; + const_cast(this)->paint(&p, &option, 0); + p.end(); + + // Transform QRegion back to device space + QTransform unscale = QTransform::fromScale(1 / granularity, 1 / granularity); + QRegion r; + QBitmap colorMask = QBitmap::fromImage(mask.createMaskFromColor(0)); + foreach (const QRect &rect, QRegion( colorMask ).rects()) { + QRect xrect = unscale.mapRect(rect).translated(deviceRect.topLeft() - QPoint(pad, pad)); + r += xrect.adjusted(-1, -1, 1, 1) & deviceRect; + } + return r; +} + +/*! + \since 4.4 + + Returns the item's bounding region granularity; a value between and + including 0 and 1. The default value is 0 (i.e., the lowest granularity, + where the bounding region corresponds to the item's bounding rectangle). + +\omit +### NOTE +\endomit + + \sa setBoundingRegionGranularity() +*/ +qreal QGraphicsItem::boundingRegionGranularity() const +{ + return d_ptr->hasBoundingRegionGranularity + ? qvariant_cast(d_ptr->extra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity)) + : 0; +} + +/*! + \since 4.4 + Sets the bounding region granularity to \a granularity; a value between + and including 0 and 1. The default value is 0 (i.e., the lowest + granularity, where the bounding region corresponds to the item's bounding + rectangle). + + The granularity is used by boundingRegion() to calculate how fine the + bounding region of the item should be. The highest achievable granularity + is 1, where boundingRegion() will return the finest outline possible for + the respective device (e.g., for a QGraphicsView viewport, this gives you + a pixel-perfect bounding region). The lowest possible granularity is + 0. The value of \a granularity describes the ratio between device + resolution and the resolution of the bounding region (e.g., a value of + 0.25 will provide a region where each chunk corresponds to 4x4 device + units / pixels). + + \sa boundingRegionGranularity() +*/ +void QGraphicsItem::setBoundingRegionGranularity(qreal granularity) +{ + if (granularity < 0.0 || granularity > 1.0) { + qWarning("QGraphicsItem::setBoundingRegionGranularity: invalid granularity %g", granularity); + return; + } + if (granularity == 0.0) { + d_ptr->unsetExtra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity); + d_ptr->hasBoundingRegionGranularity = 0; + return; + } + d_ptr->hasBoundingRegionGranularity = 1; + d_ptr->setExtra(QGraphicsItemPrivate::ExtraBoundingRegionGranularity, + QVariant::fromValue(granularity)); +} + +/*! + \fn virtual void QGraphicsItem::paint(QPainter *painter, const + QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0 + + This function, which is usually called by QGraphicsView, paints the + contents of an item in local coordinates. + + Reimplement this function in a QGraphicsItem subclass to provide the + item's painting implementation, using \a painter. The \a option parameter + provides style options for the item, such as its state, exposed area and + its level-of-detail hints. The \a widget argument is optional. If + provided, it points to the widget that is being painted on; otherwise, it + is 0. For cached painting, \a widget is always 0. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 10 + + The painter's pen is 0-width by default, and its pen is initialized to the + QPalette::Text brush from the paint device's palette. The brush is + initialized to QPalette::Window. + + Make sure to constrain all painting inside the boundaries of + boundingRect() to avoid rendering artifacts (as QGraphicsView does not + clip the painter for you). In particular, when QPainter renders the + outline of a shape using an assigned QPen, half of the outline will be + drawn outside, and half inside, the shape you're rendering (e.g., with a + pen width of 2 units, you must draw outlines 1 unit inside + boundingRect()). QGraphicsItem does not support use of cosmetic pens with + a non-zero width. + + All painting is done in local coordinates. + + \sa setCacheMode(), QPen::width(), {Item Coordinates}, ItemUsesExtendedStyleOption +*/ + +/*! + \internal + Returns true if we can discard an update request; otherwise false. +*/ +bool QGraphicsItemPrivate::discardUpdateRequest(bool ignoreVisibleBit, bool ignoreDirtyBit, + bool ignoreOpacity) const +{ + // No scene, or if the scene is updating everything, means we have nothing + // to do. The only exception is if the scene tracks the growing scene rect. + return !scene + || (!visible && !ignoreVisibleBit && !this->ignoreVisible) + || (!ignoreDirtyBit && fullUpdatePending) + || (!ignoreOpacity && !this->ignoreOpacity && childrenCombineOpacity() && isFullyTransparent()); +} + +/*! + \internal +*/ +int QGraphicsItemPrivate::depth() const +{ + if (itemDepth == -1) + const_cast(this)->resolveDepth(); + + return itemDepth; +} + +/*! + \internal +*/ +#ifndef QT_NO_GRAPHICSEFFECT +void QGraphicsItemPrivate::invalidateParentGraphicsEffectsRecursively() +{ + QGraphicsItemPrivate *itemPrivate = this; + do { + if (itemPrivate->graphicsEffect) { + itemPrivate->notifyInvalidated = 1; + + if (!itemPrivate->updateDueToGraphicsEffect) + static_cast(itemPrivate->graphicsEffect->d_func()->source->d_func())->invalidateCache(); + } + } while ((itemPrivate = itemPrivate->parent ? itemPrivate->parent->d_ptr.data() : 0)); +} + +void QGraphicsItemPrivate::invalidateChildGraphicsEffectsRecursively(QGraphicsItemPrivate::InvalidateReason reason) +{ + if (!mayHaveChildWithGraphicsEffect) + return; + + for (int i = 0; i < children.size(); ++i) { + QGraphicsItemPrivate *childPrivate = children.at(i)->d_ptr.data(); + if (reason == OpacityChanged && (childPrivate->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + if (childPrivate->graphicsEffect) { + childPrivate->notifyInvalidated = 1; + static_cast(childPrivate->graphicsEffect->d_func()->source->d_func())->invalidateCache(); + } + + childPrivate->invalidateChildGraphicsEffectsRecursively(reason); + } +} +#endif //QT_NO_GRAPHICSEFFECT + +/*! + \internal +*/ +void QGraphicsItemPrivate::invalidateDepthRecursively() +{ + if (itemDepth == -1) + return; + + itemDepth = -1; + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->invalidateDepthRecursively(); +} + +/*! + \internal + + Resolves the stacking depth of this object and all its ancestors. +*/ +void QGraphicsItemPrivate::resolveDepth() +{ + if (!parent) + itemDepth = 0; + else { + if (parent->d_ptr->itemDepth == -1) + parent->d_ptr->resolveDepth(); + itemDepth = parent->d_ptr->itemDepth + 1; + } +} + +/*! + \internal + + ### This function is almost identical to + QGraphicsScenePrivate::registerTopLevelItem(). +*/ +void QGraphicsItemPrivate::addChild(QGraphicsItem *child) +{ + // Remove all holes from the sibling index list. Now the max index + // number is equal to the size of the children list. + ensureSequentialSiblingIndex(); + needSortChildren = 1; // ### maybe 0 + child->d_ptr->siblingIndex = children.size(); + children.append(child); + if (isObject) + emit static_cast(q_ptr)->childrenChanged(); +} + +/*! + \internal + + ### This function is almost identical to + QGraphicsScenePrivate::unregisterTopLevelItem(). +*/ +void QGraphicsItemPrivate::removeChild(QGraphicsItem *child) +{ + // When removing elements in the middle of the children list, + // there will be a "gap" in the list of sibling indexes (0,1,3,4). + if (!holesInSiblingIndex) + holesInSiblingIndex = child->d_ptr->siblingIndex != children.size() - 1; + if (sequentialOrdering && !holesInSiblingIndex) + children.removeAt(child->d_ptr->siblingIndex); + else + children.removeOne(child); + // NB! Do not use children.removeAt(child->d_ptr->siblingIndex) because + // the child is not guaranteed to be at the index after the list is sorted. + // (see ensureSortedChildren()). + child->d_ptr->siblingIndex = -1; + if (isObject) + emit static_cast(q_ptr)->childrenChanged(); +} + +/*! + \internal +*/ +QGraphicsItemCache *QGraphicsItemPrivate::maybeExtraItemCache() const +{ + return (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); +} + +/*! + \internal +*/ +QGraphicsItemCache *QGraphicsItemPrivate::extraItemCache() const +{ + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (!c) { + QGraphicsItemPrivate *that = const_cast(this); + c = new QGraphicsItemCache; + that->setExtra(ExtraCacheData, QVariant::fromValue(c)); + } + return c; +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::removeExtraItemCache() +{ + QGraphicsItemCache *c = (QGraphicsItemCache *)qvariant_cast(extra(ExtraCacheData)); + if (c) { + c->purge(); + delete c; + } + unsetExtra(ExtraCacheData); +} + +void QGraphicsItemPrivate::updatePaintedViewBoundingRects(bool updateChildren) +{ + if (!scene) + return; + + for (int i = 0; i < scene->d_func()->views.size(); ++i) { + QGraphicsViewPrivate *viewPrivate = scene->d_func()->views.at(i)->d_func(); + QRect rect = paintedViewBoundingRects.value(viewPrivate->viewport); + rect.translate(viewPrivate->dirtyScrollOffset); + viewPrivate->updateRect(rect); + } + + if (updateChildren) { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->updatePaintedViewBoundingRects(true); + } +} + +// Traverses all the ancestors up to the top-level and updates the pointer to +// always point to the top-most item that has a dirty scene transform. +// It then backtracks to the top-most dirty item and start calculating the +// scene transform by combining the item's transform (+pos) with the parent's +// cached scene transform (which we at this point know for sure is valid). +void QGraphicsItemPrivate::ensureSceneTransformRecursive(QGraphicsItem **topMostDirtyItem) +{ + Q_ASSERT(topMostDirtyItem); + + if (dirtySceneTransform) + *topMostDirtyItem = q_ptr; + + if (parent) + parent->d_ptr->ensureSceneTransformRecursive(topMostDirtyItem); + + if (*topMostDirtyItem == q_ptr) { + if (!dirtySceneTransform) + return; // OK, neither my ancestors nor I have dirty scene transforms. + *topMostDirtyItem = 0; + } else if (*topMostDirtyItem) { + return; // Continue backtrack. + } + + // This item and all its descendants have dirty scene transforms. + // We're about to validate this item's scene transform, so we have to + // invalidate all the children; otherwise there's no way for the descendants + // to detect that the ancestor has changed. + invalidateChildrenSceneTransform(); + + // COMBINE my transform with the parent's scene transform. + updateSceneTransformFromParent(); + Q_ASSERT(!dirtySceneTransform); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::setSubFocus(QGraphicsItem *rootItem, QGraphicsItem *stopItem) +{ + // Update focus child chain. Stop at panels, or if this item + // is hidden, stop at the first item with a visible parent. + QGraphicsItem *parent = rootItem ? rootItem : q_ptr; + if (parent->panel() != q_ptr->panel()) + return; + + do { + // Clear any existing ancestor's subFocusItem. + if (parent != q_ptr && parent->d_ptr->subFocusItem) { + if (parent->d_ptr->subFocusItem == q_ptr) + break; + parent->d_ptr->subFocusItem->d_ptr->clearSubFocus(0, stopItem); + } + parent->d_ptr->subFocusItem = q_ptr; + parent->d_ptr->subFocusItemChange(); + } while (!parent->isPanel() && (parent = parent->d_ptr->parent) && (visible || !parent->d_ptr->visible)); + + if (scene && !scene->isActive()) { + scene->d_func()->passiveFocusItem = subFocusItem; + scene->d_func()->lastFocusItem = subFocusItem; + } +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::clearSubFocus(QGraphicsItem *rootItem, QGraphicsItem *stopItem) +{ + // Reset sub focus chain. + QGraphicsItem *parent = rootItem ? rootItem : q_ptr; + do { + if (parent->d_ptr->subFocusItem != q_ptr) + break; + parent->d_ptr->subFocusItem = 0; + if (parent != stopItem && !parent->isAncestorOf(stopItem)) + parent->d_ptr->subFocusItemChange(); + } while (!parent->isPanel() && (parent = parent->d_ptr->parent)); +} + +/*! + \internal + + Sets the focusProxy pointer to 0 for all items that have this item as their + focusProxy. ### Qt 5: Use QPointer instead. +*/ +void QGraphicsItemPrivate::resetFocusProxy() +{ + for (int i = 0; i < focusProxyRefs.size(); ++i) + *focusProxyRefs.at(i) = 0; + focusProxyRefs.clear(); +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when subFocusItem + changes. +*/ +void QGraphicsItemPrivate::subFocusItemChange() +{ +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when an item + becomes a focusScopeItem (or is no longer a focusScopeItem). +*/ +void QGraphicsItemPrivate::focusScopeItemChange(bool isSubFocusItem) +{ + Q_UNUSED(isSubFocusItem); +} + +/*! + \internal + + Subclasses can reimplement this function to be notified when its + siblingIndex order is changed. +*/ +void QGraphicsItemPrivate::siblingOrderChange() +{ +} + +/*! + \internal + + Tells us if it is a proxy widget +*/ +bool QGraphicsItemPrivate::isProxyWidget() const +{ + return false; +} + +/*! + Schedules a redraw of the area covered by \a rect in this item. You can + call this function whenever your item needs to be redrawn, such as if it + changes appearance or size. + + This function does not cause an immediate paint; instead it schedules a + paint request that is processed by QGraphicsView after control reaches the + event loop. The item will only be redrawn if it is visible in any + associated view. + + As a side effect of the item being repainted, other items that overlap the + area \a rect may also be repainted. + + If the item is invisible (i.e., isVisible() returns false), this function + does nothing. + + \sa paint(), boundingRect() +*/ +void QGraphicsItem::update(const QRectF &rect) +{ + if (rect.isEmpty() && !rect.isNull()) + return; + + // Make sure we notify effects about invalidated source. +#ifndef QT_NO_GRAPHICSEFFECT + d_ptr->invalidateParentGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + if (CacheMode(d_ptr->cacheMode) != NoCache) { + // Invalidate cache. + QGraphicsItemCache *cache = d_ptr->extraItemCache(); + if (!cache->allExposed) { + if (rect.isNull()) { + cache->allExposed = true; + cache->exposed.clear(); + } else { + cache->exposed.append(rect); + } + } + // Only invalidate cache; item is already dirty. + if (d_ptr->fullUpdatePending) + return; + } + + if (d_ptr->scene) + d_ptr->scene->d_func()->markDirty(this, rect); +} + +/*! + \since 4.4 + Scrolls the contents of \a rect by \a dx, \a dy. If \a rect is a null rect + (the default), the item's bounding rect is scrolled. + + Scrolling provides a fast alternative to simply redrawing when the + contents of the item (or parts of the item) are shifted vertically or + horizontally. Depending on the current transformation and the capabilities + of the paint device (i.e., the viewport), this operation may consist of + simply moving pixels from one location to another using memmove(). In most + cases this is faster than rerendering the entire area. + + After scrolling, the item will issue an update for the newly exposed + areas. If scrolling is not supported (e.g., you are rendering to an OpenGL + viewport, which does not benefit from scroll optimizations), this function + is equivalent to calling update(\a rect). + + \bold{Note:} Scrolling is only supported when QGraphicsItem::ItemCoordinateCache + is enabled; in all other cases calling this function is equivalent to calling + update(\a rect). If you for sure know that the item is opaque and not overlapped + by other items, you can map the \a rect to viewport coordinates and scroll the + viewport. + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 19 + + \sa boundingRect() +*/ +void QGraphicsItem::scroll(qreal dx, qreal dy, const QRectF &rect) +{ + Q_D(QGraphicsItem); + if (dx == 0.0 && dy == 0.0) + return; + if (!d->scene) + return; + + // Accelerated scrolling means moving pixels from one location to another + // and only redraw the newly exposed area. The following requirements must + // be fulfilled in order to do that: + // + // 1) Item is opaque. + // 2) Item is not overlapped by other items. + // + // There's (yet) no way to detect whether an item is opaque or not, which means + // we cannot do accelerated scrolling unless the cache is enabled. In case of using + // DeviceCoordinate cache we also have to take the device transform into account in + // order to determine whether we can do accelerated scrolling or not. That's left out + // for simplicity here, but it is definitely something we can consider in the future + // as a performance improvement. + if (d->cacheMode != QGraphicsItem::ItemCoordinateCache + || !qFuzzyIsNull(dx - int(dx)) || !qFuzzyIsNull(dy - int(dy))) { + update(rect); + return; + } + + QGraphicsItemCache *cache = d->extraItemCache(); + if (cache->allExposed || cache->fixedSize.isValid()) { + // Cache is either invalidated or item is scaled (see QGraphicsItem::setCacheMode). + update(rect); + return; + } + + // Find pixmap in cache. + QPixmap cachedPixmap; + if (!QPixmapCache::find(cache->key, &cachedPixmap)) { + update(rect); + return; + } + + QRect scrollRect = (rect.isNull() ? boundingRect() : rect).toAlignedRect(); + if (!scrollRect.intersects(cache->boundingRect)) + return; // Nothing to scroll. + + // Remove from cache to avoid deep copy when modifying. + QPixmapCache::remove(cache->key); + + QRegion exposed; + cachedPixmap.scroll(dx, dy, scrollRect.translated(-cache->boundingRect.topLeft()), &exposed); + + // Reinsert into cache. + cache->key = QPixmapCache::insert(cachedPixmap); + + // Translate the existing expose. + for (int i = 0; i < cache->exposed.size(); ++i) { + QRectF &e = cache->exposed[i]; + if (!rect.isNull() && !e.intersects(rect)) + continue; + e.translate(dx, dy); + } + + // Append newly exposed areas. Note that the exposed region is currently + // in pixmap coordinates, so we have to translate it to item coordinates. + exposed.translate(cache->boundingRect.topLeft()); + const QVector exposedRects = exposed.rects(); + for (int i = 0; i < exposedRects.size(); ++i) + cache->exposed += exposedRects.at(i); + + // Trigger update. This will redraw the newly exposed area and make sure + // the pixmap is re-blitted in case there are overlapping items. + d->scene->d_func()->markDirty(this, rect); +} + +/*! + \fn void QGraphicsItem::update(qreal x, qreal y, qreal width, qreal height) + \overload + + This convenience function is equivalent to calling update(QRectF(\a x, \a + y, \a width, \a height)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to \a + item's coordinate system, and returns the mapped coordinate. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), transform(), mapFromItem(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPointF &point) const +{ + if (item) + return itemTransform(item).map(point); + return mapToScene(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToItem(\a item, + QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to its + parent's coordinate system, and returns the mapped coordinate. If the item + has no parent, \a point will be mapped to the scene's coordinate system. + + \sa mapToItem(), mapToScene(), transform(), mapFromParent(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToParent(const QPointF &point) const +{ + // COMBINE + if (!d_ptr->transformData) + return point + d_ptr->pos; + return d_ptr->transformToParent().map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToParent(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToParent(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's coordinate system, to the + scene's coordinate system, and returns the mapped coordinate. + + \sa mapToItem(), mapToParent(), transform(), mapFromScene(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapToScene(const QPointF &point) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return QPointF(point.x() + d_ptr->sceneTransform.dx(), point.y() + d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapToScene(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapToScene(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped rectangle as a polygon. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return itemTransform(item).map(rect); + return mapToScene(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped rectangle as a + polygon. If the item has no parent, \a rect will be mapped to the scene's + coordinate system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(d_ptr->pos); + return d_ptr->transformToParent().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped rectangle as a polygon. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapToScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapToScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + If \a item is 0, this function returns the same as mapRectToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return itemTransform(item).mapRect(rect); + return mapRectToScene(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(d_ptr->pos); + return d_ptr->transformToParent().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's coordinate system, to + the scene coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectToScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectToScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectToScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped rectangle as a new + rectangle (i.e., the bounding rectangle of the resulting polygon). + + If \a item is 0, this function returns the same as mapRectFromScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return item->itemTransform(this).mapRect(rect); + return mapRectFromScene(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a new rectangle (i.e., the bounding rectangle of the resulting + polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromParent(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + \since 4.5 + + Maps the rectangle \a rect, which is in scene coordinates, to this item's + coordinate system, and returns the mapped rectangle as a new rectangle + (i.e., the bounding rectangle of the resulting polygon). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QRectF QGraphicsItem::mapRectFromScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().mapRect(rect); +} + +/*! + \fn QRectF QGraphicsItem::mapRectFromScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.5 + + This convenience function is equivalent to calling mapRectFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped polygon. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPolygonF &polygon) const +{ + if (item) + return itemTransform(item).map(polygon); + return mapToScene(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped polygon. If the + item has no parent, \a polygon will be mapped to the scene's coordinate + system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToParent(const QPolygonF &polygon) const +{ + // COMBINE + if (!d_ptr->transformData) + return polygon.translated(d_ptr->pos); + return d_ptr->transformToParent().map(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped polygon. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPolygonF QGraphicsItem::mapToScene(const QPolygonF &polygon) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return polygon.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(polygon); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + \a item's coordinate system, and returns the mapped path. + + If \a item is 0, this function returns the same as mapToScene(). + + \sa itemTransform(), mapToParent(), mapToScene(), mapFromItem(), {The + Graphics View Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToItem(const QGraphicsItem *item, const QPainterPath &path) const +{ + if (item) + return itemTransform(item).map(path); + return mapToScene(path); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + its parent's coordinate system, and returns the mapped path. If the + item has no parent, \a path will be mapped to the scene's coordinate + system. + + \sa mapToScene(), mapToItem(), mapFromParent(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToParent(const QPainterPath &path) const +{ + // COMBINE + if (!d_ptr->transformData) + return path.translated(d_ptr->pos); + return d_ptr->transformToParent().map(path); +} + +/*! + Maps the path \a path, which is in this item's coordinate system, to + the scene's coordinate system, and returns the mapped path. + + \sa mapToParent(), mapToItem(), mapFromScene(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapToScene(const QPainterPath &path) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return path.translated(d_ptr->sceneTransform.dx(), d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.map(path); +} + +/*! + Maps the point \a point, which is in \a item's coordinate system, to this + item's coordinate system, and returns the mapped coordinate. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapFromParent(), mapFromScene(), transform(), mapToItem(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPointF &point) const +{ + if (item) + return item->itemTransform(this).map(point); + return mapFromScene(point); +} + +/*! + \fn QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapFromItem(\a item, + QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped + coordinate. + + \sa mapFromItem(), mapFromScene(), transform(), mapToParent(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromParent(const QPointF &point) const +{ + // COMBINE + if (d_ptr->transformData) + return d_ptr->transformToParent().inverted().map(point); + return point - d_ptr->pos; +} + +/*! + \fn QPointF QGraphicsItem::mapFromParent(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling + mapFromParent(QPointF(\a x, \a y)). +*/ + +/*! + Maps the point \a point, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped + coordinate. + + \sa mapFromItem(), mapFromParent(), transform(), mapToScene(), {The Graphics + View Coordinate System} +*/ +QPointF QGraphicsItem::mapFromScene(const QPointF &point) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return QPointF(point.x() - d_ptr->sceneTransform.dx(), point.y() - d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(point); +} + +/*! + \fn QPointF QGraphicsItem::mapFromScene(qreal x, qreal y) const + \overload + + This convenience function is equivalent to calling mapFromScene(QPointF(\a + x, \a y)). +*/ + +/*! + Maps the rectangle \a rect, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped rectangle as a + polygon. + + If \a item is 0, this function returns the same as mapFromScene() + + \sa itemTransform(), mapToItem(), mapFromParent(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QRectF &rect) const +{ + if (item) + return item->itemTransform(this).map(rect); + return mapFromScene(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromItem(item, QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a polygon. + + \sa mapToParent(), mapFromItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromParent(const QRectF &rect) const +{ + // COMBINE + if (!d_ptr->transformData) + return rect.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromParent(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromItem(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the rectangle \a rect, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped rectangle + as a polygon. + + \sa mapToScene(), mapFromItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromScene(const QRectF &rect) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return rect.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(rect); +} + +/*! + \fn QPolygonF QGraphicsItem::mapFromScene(qreal x, qreal y, qreal w, qreal h) const + \since 4.3 + + This convenience function is equivalent to calling mapFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Maps the polygon \a polygon, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped polygon. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapToItem(), mapFromParent(), transform(), {The + Graphics View Coordinate System} +*/ +QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPolygonF &polygon) const +{ + if (item) + return item->itemTransform(this).map(polygon); + return mapFromScene(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped polygon. + + \sa mapToParent(), mapToItem(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromParent(const QPolygonF &polygon) const +{ + // COMBINE + if (!d_ptr->transformData) + return polygon.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(polygon); +} + +/*! + Maps the polygon \a polygon, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped polygon. + + \sa mapToScene(), mapFromParent(), transform(), {The Graphics View Coordinate + System} +*/ +QPolygonF QGraphicsItem::mapFromScene(const QPolygonF &polygon) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return polygon.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(polygon); +} + +/*! + Maps the path \a path, which is in \a item's coordinate system, to + this item's coordinate system, and returns the mapped path. + + If \a item is 0, this function returns the same as mapFromScene(). + + \sa itemTransform(), mapFromParent(), mapFromScene(), mapToItem(), {The + Graphics View Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromItem(const QGraphicsItem *item, const QPainterPath &path) const +{ + if (item) + return item->itemTransform(this).map(path); + return mapFromScene(path); +} + +/*! + Maps the path \a path, which is in this item's parent's coordinate + system, to this item's coordinate system, and returns the mapped path. + + \sa mapFromScene(), mapFromItem(), mapToParent(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromParent(const QPainterPath &path) const +{ + // COMBINE + if (!d_ptr->transformData) + return path.translated(-d_ptr->pos); + return d_ptr->transformToParent().inverted().map(path); +} + +/*! + Maps the path \a path, which is in this item's scene's coordinate + system, to this item's coordinate system, and returns the mapped path. + + \sa mapFromParent(), mapFromItem(), mapToScene(), {The Graphics View + Coordinate System} +*/ +QPainterPath QGraphicsItem::mapFromScene(const QPainterPath &path) const +{ + if (d_ptr->hasTranslateOnlySceneTransform()) + return path.translated(-d_ptr->sceneTransform.dx(), -d_ptr->sceneTransform.dy()); + return d_ptr->sceneTransform.inverted().map(path); +} + +/*! + Returns true if this item is an ancestor of \a child (i.e., if this item + is \a child's parent, or one of \a child's parent's ancestors). + + \sa parentItem() +*/ +bool QGraphicsItem::isAncestorOf(const QGraphicsItem *child) const +{ + if (!child || child == this) + return false; + if (child->d_ptr->depth() < d_ptr->depth()) + return false; + const QGraphicsItem *ancestor = child; + while ((ancestor = ancestor->d_ptr->parent)) { + if (ancestor == this) + return true; + } + return false; +} + +/*! + \since 4.4 + + Returns the closest common ancestor item of this item and \a other, or 0 + if either \a other is 0, or there is no common ancestor. + + \sa isAncestorOf() +*/ +QGraphicsItem *QGraphicsItem::commonAncestorItem(const QGraphicsItem *other) const +{ + if (!other) + return 0; + if (other == this) + return const_cast(this); + const QGraphicsItem *thisw = this; + const QGraphicsItem *otherw = other; + int thisDepth = d_ptr->depth(); + int otherDepth = other->d_ptr->depth(); + while (thisDepth > otherDepth) { + thisw = thisw->d_ptr->parent; + --thisDepth; + } + while (otherDepth > thisDepth) { + otherw = otherw->d_ptr->parent; + --otherDepth; + } + while (thisw && thisw != otherw) { + thisw = thisw->d_ptr->parent; + otherw = otherw->d_ptr->parent; + } + return const_cast(thisw); +} + +/*! + \since 4,4 + Returns true if this item is currently under the mouse cursor in one of + the views; otherwise, false is returned. + + \sa QGraphicsScene::views(), QCursor::pos() +*/ +bool QGraphicsItem::isUnderMouse() const +{ + Q_D(const QGraphicsItem); + if (!d->scene) + return false; + + QPoint cursorPos = QCursor::pos(); + foreach (QGraphicsView *view, d->scene->views()) { + if (contains(mapFromScene(view->mapToScene(view->mapFromGlobal(cursorPos))))) + return true; + } + return false; +} + +/*! + Returns this item's custom data for the key \a key as a QVariant. + + Custom item data is useful for storing arbitrary properties in any + item. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 11 + + Qt does not use this feature for storing data; it is provided solely + for the convenience of the user. + + \sa setData() +*/ +QVariant QGraphicsItem::data(int key) const +{ + QGraphicsItemCustomDataStore *store = qt_dataStore(); + if (!store->data.contains(this)) + return QVariant(); + return store->data.value(this).value(key); +} + +/*! + Sets this item's custom data for the key \a key to \a value. + + Custom item data is useful for storing arbitrary properties for any + item. Qt does not use this feature for storing data; it is provided solely + for the convenience of the user. + + \sa data() +*/ +void QGraphicsItem::setData(int key, const QVariant &value) +{ + qt_dataStore()->data[this][key] = value; +} + +/*! + \fn T qgraphicsitem_cast(QGraphicsItem *item) + \relates QGraphicsItem + \since 4.2 + + Returns the given \a item cast to type T if \a item is of type T; + otherwise, 0 is returned. + + \note To make this function work correctly with custom items, reimplement + the \l{QGraphicsItem::}{type()} function for each custom QGraphicsItem + subclass. + + \sa QGraphicsItem::type(), QGraphicsItem::UserType +*/ + +/*! + Returns the type of an item as an int. All standard graphicsitem classes + are associated with a unique value; see QGraphicsItem::Type. This type + information is used by qgraphicsitem_cast() to distinguish between types. + + The default implementation (in QGraphicsItem) returns UserType. + + To enable use of qgraphicsitem_cast() with a custom item, reimplement this + function and declare a Type enum value equal to your custom item's type. + Custom items must return a value larger than or equal to UserType (65536). + + For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp QGraphicsItem type + + \sa UserType +*/ +int QGraphicsItem::type() const +{ + return (int)UserType; +} + +/*! + Installs an event filter for this item on \a filterItem, causing + all events for this item to first pass through \a filterItem's + sceneEventFilter() function. + + To filter another item's events, install this item as an event filter + for the other item. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 12 + + An item can only filter events for other items in the same + scene. Also, an item cannot filter its own events; instead, you + can reimplement sceneEvent() directly. + + Items must belong to a scene for scene event filters to be installed and + used. + + \sa removeSceneEventFilter(), sceneEventFilter(), sceneEvent() +*/ +void QGraphicsItem::installSceneEventFilter(QGraphicsItem *filterItem) +{ + if (!d_ptr->scene) { + qWarning("QGraphicsItem::installSceneEventFilter: event filters can only be installed" + " on items in a scene."); + return; + } + if (d_ptr->scene != filterItem->scene()) { + qWarning("QGraphicsItem::installSceneEventFilter: event filters can only be installed" + " on items in the same scene."); + return; + } + d_ptr->scene->d_func()->installSceneEventFilter(this, filterItem); +} + +/*! + Removes an event filter on this item from \a filterItem. + + \sa installSceneEventFilter() +*/ +void QGraphicsItem::removeSceneEventFilter(QGraphicsItem *filterItem) +{ + if (!d_ptr->scene || d_ptr->scene != filterItem->scene()) + return; + d_ptr->scene->d_func()->removeSceneEventFilter(this, filterItem); +} + +/*! + Filters events for the item \a watched. \a event is the filtered + event. + + Reimplementing this function in a subclass makes it possible + for the item to be used as an event filter for other items, + intercepting all the events send to those items before they are + able to respond. + + Reimplementations must return true to prevent further processing of + a given event, ensuring that it will not be delivered to the watched + item, or return false to indicate that the event should be propagated + further by the event system. + + \sa installSceneEventFilter() +*/ +bool QGraphicsItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) +{ + Q_UNUSED(watched); + Q_UNUSED(event); + return false; +} + +/*! + This virtual function receives events to this item. Reimplement + this function to intercept events before they are dispatched to + the specialized event handlers contextMenuEvent(), focusInEvent(), + focusOutEvent(), hoverEnterEvent(), hoverMoveEvent(), + hoverLeaveEvent(), keyPressEvent(), keyReleaseEvent(), + mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(), and + mouseDoubleClickEvent(). + + Returns true if the event was recognized and handled; otherwise, (e.g., if + the event type was not recognized,) false is returned. + + \a event is the intercepted event. +*/ +bool QGraphicsItem::sceneEvent(QEvent *event) +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents) { + if (event->type() == QEvent::HoverEnter || event->type() == QEvent::HoverLeave + || event->type() == QEvent::DragEnter || event->type() == QEvent::DragLeave) { + // Hover enter and hover leave events for children are ignored; + // hover move events are forwarded. + return true; + } + + QGraphicsItem *handler = this; + do { + handler = handler->d_ptr->parent; + Q_ASSERT(handler); + } while (handler->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents); + // Forward the event to the closest parent that handles child + // events, mapping existing item-local coordinates to its + // coordinate system. + d_ptr->remapItemPos(event, handler); + handler->sceneEvent(event); + return true; + } + + if (event->type() == QEvent::FocusOut) { + focusOutEvent(static_cast(event)); + return true; + } + + if (!d_ptr->visible) { + // Eaten + return true; + } + + switch (event->type()) { + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneContextMenu: + contextMenuEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragEnter: + dragEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragMove: + dragMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragLeave: + dragLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDrop: + dropEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverEnter: + hoverEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverMove: + hoverMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverLeave: + hoverLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseMove: + mouseMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMousePress: + mousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + mouseDoubleClickEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneWheel: + wheelEvent(static_cast(event)); + break; + case QEvent::KeyPress: { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + bool res = false; + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) { + if (d_ptr->isWidget) { + res = static_cast(this)->focusNextPrevChild(false); + } else if (d_ptr->scene) { + res = d_ptr->scene->focusNextPrevChild(false); + } + } else if (k->key() == Qt::Key_Tab) { + if (d_ptr->isWidget) { + res = static_cast(this)->focusNextPrevChild(true); + } else if (d_ptr->scene) { + res = d_ptr->scene->focusNextPrevChild(true); + } + } + if (!res) + event->ignore(); + return true; + } + } + keyPressEvent(static_cast(event)); + break; + } + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + break; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + // Propagate panel activation. + if (d_ptr->scene) { + for (int i = 0; i < d_ptr->children.size(); ++i) { + QGraphicsItem *child = d_ptr->children.at(i); + if (child->isVisible() && !child->isPanel()) { + if (!(child->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents)) + d_ptr->scene->sendEvent(child, event); + } + } + } + break; + default: + return false; + } + + return true; +} + +/*! + This event handler can be reimplemented in a subclass to process context + menu events. The \a event parameter contains details about the event to + be handled. + + If you ignore the event, (i.e., by calling QEvent::ignore(),) \a event + will propagate to any item beneath this item. If no items accept the + event, it will be ignored by the scene, and propagate to the view. + + It's common to open a QMenu in response to receiving a context menu + event. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 13 + + The default implementation ignores the event. + + \sa sceneEvent() +*/ +void QGraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag enter events for this item. Drag enter events are generated as the + cursor enters the item's area. + + By accepting the event, (i.e., by calling QEvent::accept(),) the item will + accept drop events, in addition to receiving drag move and drag + leave. Otherwise, the event will be ignored and propagate to the item + beneath. If the event is accepted, the item will receive a drag move event + before control goes back to the event loop. + + A common implementation of dragEnterEvent accepts or ignores \a event + depending on the associated mime data in \a event. Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 14 + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dropEvent(), dragMoveEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragEnterEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag leave events for this item. Drag leave events are generated as the + cursor leaves the item's area. Most often you will not need to reimplement + this function, but it can be useful for resetting state in your item + (e.g., highlighting). + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dragEnterEvent(), dropEvent(), dragMoveEvent() +*/ +void QGraphicsItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragLeaveEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drag move events for this item. Drag move events are generated as the + cursor moves around inside the item's area. Most often you will not need + to reimplement this function; it is used to indicate that only parts of + the item can accept drops. + + Calling QEvent::ignore() or QEvent::accept() on \a event toggles whether + or not the item will accept drops at the position from the event. By + default, \a event is accepted, indicating that the item allows drops at + the specified position. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dropEvent(), dragEnterEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dragMoveEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + drop events for this item. Items can only receive drop events if the last + drag move event was accepted. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + Items do not receive drag and drop events by default; to enable this + feature, call \c setAcceptDrops(true). + + The default implementation does nothing. + + \sa dragEnterEvent(), dragMoveEvent(), dragLeaveEvent() +*/ +void QGraphicsItem::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsItem); + // binary compatibility workaround between 4.4 and 4.5 + if (d->isProxyWidget()) + static_cast(this)->dropEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + focus in events for this item. The default implementation calls + ensureVisible(). + + \sa focusOutEvent(), sceneEvent(), setFocus() +*/ +void QGraphicsItem::focusInEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + focus out events for this item. The default implementation does nothing. + + \sa focusInEvent(), sceneEvent(), setFocus() +*/ +void QGraphicsItem::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover enter events for this item. The default implementation calls + update(); otherwise it does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverMoveEvent(), hoverLeaveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover move events for this item. The default implementation does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverEnterEvent(), hoverLeaveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + hover leave events for this item. The default implementation calls + update(); otherwise it does nothing. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no effect. + + \sa hoverEnterEvent(), hoverMoveEvent(), sceneEvent(), setAcceptHoverEvents() +*/ +void QGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + update(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive key press events for this item. The default implementation + ignores the event. If you reimplement this handler, the event will by + default be accepted. + + Note that key events are only received for items that set the + ItemIsFocusable flag, and that have keyboard input focus. + + \sa keyReleaseEvent(), setFocus(), QGraphicsScene::setFocusItem(), + sceneEvent() +*/ +void QGraphicsItem::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + key release events for this item. The default implementation + ignores the event. If you reimplement this handler, the event will by + default be accepted. + + Note that key events are only received for items that set the + ItemIsFocusable flag, and that have keyboard input focus. + + \sa keyPressEvent(), setFocus(), QGraphicsScene::setFocusItem(), + sceneEvent() +*/ +void QGraphicsItem::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse press events for this item. Mouse press events are + only delivered to items that accept the mouse button that is + pressed. By default, an item accepts all mouse buttons, but you + can change this by calling setAcceptedMouseButtons(). + + The mouse press event decides which item should become the mouse + grabber (see QGraphicsScene::mouseGrabberItem()). If you do not + reimplement this function, the press event will propagate to any + topmost item beneath this item, and no other mouse events will be + delivered to this item. + + If you do reimplement this function, \a event will by default be + accepted (see QEvent::accept()), and this item is then the mouse + grabber. This allows the item to receive future move, release and + doubleclick events. If you call QEvent::ignore() on \a event, this + item will lose the mouse grab, and \a event will propagate to any + topmost item beneath. No further mouse events will be delivered to + this item unless a new mouse press event is received. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mousePressEvent() in your reimplementation. + + The event is \l{QEvent::ignore()}d for items that are neither + \l{QGraphicsItem::ItemIsMovable}{movable} nor + \l{QGraphicsItem::ItemIsSelectable}{selectable}. + + \sa mouseMoveEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), sceneEvent() +*/ +void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && (flags() & ItemIsSelectable)) { + bool multiSelect = (event->modifiers() & Qt::ControlModifier) != 0; + if (!multiSelect) { + if (!d_ptr->selected) { + if (QGraphicsScene *scene = d_ptr->scene) { + ++scene->d_func()->selectionChanging; + scene->clearSelection(); + --scene->d_func()->selectionChanging; + } + setSelected(true); + } + } + } else if (!(flags() & ItemIsMovable)) { + event->ignore(); + } + if (d_ptr->isWidget) { + // Qt::Popup closes when you click outside. + QGraphicsWidget *w = static_cast(this); + if ((w->windowFlags() & Qt::Popup) == Qt::Popup) { + event->accept(); + if (!w->rect().contains(event->pos())) + w->close(); + } + } +} + +/*! + obsolete +*/ +bool _qt_movableAncestorIsSelected(const QGraphicsItem *item) +{ + const QGraphicsItem *parent = item->parentItem(); + return parent && (((parent->flags() & QGraphicsItem::ItemIsMovable) && parent->isSelected()) || _qt_movableAncestorIsSelected(parent)); +} + +bool QGraphicsItemPrivate::movableAncestorIsSelected(const QGraphicsItem *item) +{ + const QGraphicsItem *parent = item->d_ptr->parent; + return parent && (((parent->flags() & QGraphicsItem::ItemIsMovable) && parent->isSelected()) || _qt_movableAncestorIsSelected(parent)); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse move events for this item. If you do receive this + event, you can be certain that this item also received a mouse + press event, and that this item is the current mouse grabber. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mouseMoveEvent() in your reimplementation. + + Please note that mousePressEvent() decides which graphics item it + is that receives mouse events. See the mousePressEvent() + description for details. + + \sa mousePressEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), sceneEvent() +*/ +void QGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) && (flags() & ItemIsMovable)) { + // Determine the list of items that need to be moved. + QList selectedItems; + QMap initialPositions; + if (d_ptr->scene) { + selectedItems = d_ptr->scene->selectedItems(); + initialPositions = d_ptr->scene->d_func()->movingItemsInitialPositions; + if (initialPositions.isEmpty()) { + foreach (QGraphicsItem *item, selectedItems) + initialPositions[item] = item->pos(); + initialPositions[this] = pos(); + } + d_ptr->scene->d_func()->movingItemsInitialPositions = initialPositions; + } + + // Find the active view. + QGraphicsView *view = 0; + if (event->widget()) + view = qobject_cast(event->widget()->parentWidget()); + + // Move all selected items + int i = 0; + bool movedMe = false; + while (i <= selectedItems.size()) { + QGraphicsItem *item = 0; + if (i < selectedItems.size()) + item = selectedItems.at(i); + else + item = this; + if (item == this) { + // Slightly clumsy-looking way to ensure that "this" is part + // of the list of items to move, this is to avoid allocations + // (appending this item to the list of selected items causes a + // detach). + if (movedMe) + break; + movedMe = true; + } + + if ((item->flags() & ItemIsMovable) && !QGraphicsItemPrivate::movableAncestorIsSelected(item)) { + QPointF currentParentPos; + QPointF buttonDownParentPos; + if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorIgnoresTransformations) { + // Items whose ancestors ignore transformations need to + // map screen coordinates to local coordinates, then map + // those to the parent. + QTransform viewToItemTransform = (item->deviceTransform(view->viewportTransform())).inverted(); + currentParentPos = mapToParent(viewToItemTransform.map(QPointF(view->mapFromGlobal(event->screenPos())))); + buttonDownParentPos = mapToParent(viewToItemTransform.map(QPointF(view->mapFromGlobal(event->buttonDownScreenPos(Qt::LeftButton))))); + } else if (item->flags() & ItemIgnoresTransformations) { + // Root items that ignore transformations need to + // calculate their diff by mapping viewport coordinates + // directly to parent coordinates. + // COMBINE + QTransform itemTransform; + if (item->d_ptr->transformData) + itemTransform = item->d_ptr->transformData->computedFullTransform(); + itemTransform.translate(item->d_ptr->pos.x(), item->d_ptr->pos.y()); + QTransform viewToParentTransform = itemTransform + * (item->sceneTransform() * view->viewportTransform()).inverted(); + currentParentPos = viewToParentTransform.map(QPointF(view->mapFromGlobal(event->screenPos()))); + buttonDownParentPos = viewToParentTransform.map(QPointF(view->mapFromGlobal(event->buttonDownScreenPos(Qt::LeftButton)))); + } else { + // All other items simply map from the scene. + currentParentPos = item->mapToParent(item->mapFromScene(event->scenePos())); + buttonDownParentPos = item->mapToParent(item->mapFromScene(event->buttonDownScenePos(Qt::LeftButton))); + } + + item->setPos(initialPositions.value(item) + currentParentPos - buttonDownParentPos); + + if (item->flags() & ItemIsSelectable) + item->setSelected(true); + } + ++i; + } + + } else { + event->ignore(); + } +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse release events for this item. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation handles basic item interaction, such as + selection and moving. If you want to keep the base implementation + when reimplementing this function, call + QGraphicsItem::mouseReleaseEvent() in your reimplementation. + + Please note that mousePressEvent() decides which graphics item it + is that receives mouse events. See the mousePressEvent() + description for details. + + \sa mousePressEvent(), mouseMoveEvent(), mouseDoubleClickEvent(), + sceneEvent() +*/ +void QGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (flags() & ItemIsSelectable) { + bool multiSelect = (event->modifiers() & Qt::ControlModifier) != 0; + if (event->scenePos() == event->buttonDownScenePos(Qt::LeftButton)) { + // The item didn't move + if (multiSelect) { + setSelected(!isSelected()); + } else { + bool selectionChanged = false; + if (QGraphicsScene *scene = d_ptr->scene) { + ++scene->d_func()->selectionChanging; + // Clear everything but this item. Bypass + // QGraphicsScene::clearSelection()'s default behavior by + // temporarily removing this item from the selection list. + if (d_ptr->selected) { + scene->d_func()->selectedItems.remove(this); + foreach (QGraphicsItem *item, scene->d_func()->selectedItems) { + if (item->isSelected()) { + selectionChanged = true; + break; + } + } + } + scene->clearSelection(); + if (d_ptr->selected) + scene->d_func()->selectedItems.insert(this); + --scene->d_func()->selectionChanging; + if (selectionChanged) + emit d_ptr->scene->selectionChanged(); + } + setSelected(true); + } + } + } + if (d_ptr->scene && !event->buttons()) + d_ptr->scene->d_func()->movingItemsInitialPositions.clear(); +} + +/*! + This event handler, for event \a event, can be reimplemented to + receive mouse doubleclick events for this item. + + When doubleclicking an item, the item will first receive a mouse + press event, followed by a release event (i.e., a click), then a + doubleclick event, and finally a release event. + + Calling QEvent::ignore() or QEvent::accept() on \a event has no + effect. + + The default implementation calls mousePressEvent(). If you want to + keep the base implementation when reimplementing this function, + call QGraphicsItem::mouseDoubleClickEvent() in your + reimplementation. + + Note that an item will not receive double click events if it is + neither \l {QGraphicsItem::ItemIsSelectable}{selectable} nor + \l{QGraphicsItem::ItemIsMovable}{movable} (single mouse clicks are + ignored in this case, and that stops the generation of double + clicks). + + \sa mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), sceneEvent() +*/ +void QGraphicsItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + mousePressEvent(event); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + wheel events for this item. If you reimplement this function, \a event + will be accepted by default. + + If you ignore the event, (i.e., by calling QEvent::ignore(),) it will + propagate to any item beneath this item. If no items accept the event, it + will be ignored by the scene, and propagate to the view (e.g., the view's + vertical scroll bar). + + The default implementation ignores the event. + + \sa sceneEvent() +*/ +void QGraphicsItem::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented to receive + input method events for this item. The default implementation ignores the + event. + + \sa inputMethodQuery(), sceneEvent() +*/ +void QGraphicsItem::inputMethodEvent(QInputMethodEvent *event) +{ + event->ignore(); +} + +/*! + This method is only relevant for input items. It is used by the + input method to query a set of properties of the item to be able + to support complex input method operations, such as support for + surrounding text and reconversions. \a query specifies which + property is queried. + + \sa inputMethodEvent(), QInputMethodEvent, QInputContext +*/ +QVariant QGraphicsItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + if (isWidget()) { + // ### Qt 5: Remove. The reimplementation in + // QGraphicsProxyWidget solves this problem (but requires a + // recompile to take effect). + return d_ptr->inputMethodQueryHelper(query); + } + + Q_UNUSED(query); + return QVariant(); +} + +/*! + Returns the current input method hints of this item. + + Input method hints are only relevant for input items. + The hints are used by the input method to indicate how it should operate. + For example, if the Qt::ImhNumbersOnly flag is set, the input method may change + its visual components to reflect that only numbers can be entered. + + The effect may vary between input method implementations. + + \since 4.6 + + \sa setInputMethodHints(), inputMethodQuery(), QInputContext +*/ +Qt::InputMethodHints QGraphicsItem::inputMethodHints() const +{ + Q_D(const QGraphicsItem); + return d->imHints; +} + +/*! + Sets the current input method hints of this item to \a hints. + + \since 4.6 + + \sa inputMethodHints(), inputMethodQuery(), QInputContext +*/ +void QGraphicsItem::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QGraphicsItem); + d->imHints = hints; + if (!hasFocus()) + return; + d->scene->d_func()->updateInputMethodSensitivityInViews(); +#if !defined(QT_NO_IM) && (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) + QWidget *fw = QApplication::focusWidget(); + if (!fw) + return; + for (int i = 0 ; i < scene()->views().count() ; ++i) + if (scene()->views().at(i) == fw) + if (QInputContext *inputContext = fw->inputContext()) + inputContext->update(); +#endif +} + +/*! + Updates the item's micro focus. + + \since 4.7 + + \sa QInputContext +*/ +void QGraphicsItem::updateMicroFocus() +{ +#if !defined(QT_NO_IM) && (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) + if (QWidget *fw = QApplication::focusWidget()) { + if (scene()) { + for (int i = 0 ; i < scene()->views().count() ; ++i) { + if (scene()->views().at(i) == fw) + if (QInputContext *inputContext = fw->inputContext()) + inputContext->update(); + } + } +#ifndef QT_NO_ACCESSIBILITY + // ##### is this correct + QAccessible::updateAccessibility(fw, 0, QAccessible::StateChanged); +#endif + } +#endif +} + +/*! + This virtual function is called by QGraphicsItem to notify custom items + that some part of the item's state changes. By reimplementing this + function, your can react to a change, and in some cases, (depending on \a + change,) adjustments can be made. + + \a change is the parameter of the item that is changing. \a value is the + new value; the type of the value depends on \a change. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 15 + + The default implementation does nothing, and returns \a value. + + Note: Certain QGraphicsItem functions cannot be called in a + reimplementation of this function; see the GraphicsItemChange + documentation for details. + + \sa GraphicsItemChange +*/ +QVariant QGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_UNUSED(change); + return value; +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +bool QGraphicsItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +void QGraphicsItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal + + Note: This is provided as a hook to avoid future problems related + to adding virtual functions. +*/ +QVariant QGraphicsItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \internal + + Adds this item to the scene's index. Called in conjunction with + removeFromIndex() to ensure the index bookkeeping is correct when + the item's position, transformation or shape changes. +*/ +void QGraphicsItem::addToIndex() +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + // ### add to child index only if applicable + return; + } + if (d_ptr->scene) + d_ptr->scene->d_func()->index->addItem(this); +} + +/*! + \internal + + Removes this item from the scene's index. Called in conjunction + with addToIndex() to ensure the index bookkeeping is correct when + the item's position, transformation or shape changes. +*/ +void QGraphicsItem::removeFromIndex() +{ + if (d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) { + // ### remove from child index only if applicable + return; + } + if (d_ptr->scene) + d_ptr->scene->d_func()->index->removeItem(this); +} + +/*! + Prepares the item for a geometry change. Call this function before + changing the bounding rect of an item to keep QGraphicsScene's index up to + date. + + prepareGeometryChange() will call update() if this is necessary. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 16 + + \sa boundingRect() +*/ +void QGraphicsItem::prepareGeometryChange() +{ + if (d_ptr->inDestructor) + return; + if (d_ptr->scene) { + d_ptr->scene->d_func()->dirtyGrowingItemsBoundingRect = true; + d_ptr->geometryChanged = 1; + d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + d_ptr->notifyBoundingRectChanged = !d_ptr->inSetPosHelper; + + QGraphicsScenePrivate *scenePrivate = d_ptr->scene->d_func(); + scenePrivate->index->prepareBoundingRectChange(this); + scenePrivate->markDirty(this, QRectF(), /*invalidateChildren=*/true, /*force=*/false, + /*ignoreOpacity=*/ false, /*removingItemFromScene=*/ false, + /*updateBoundingRect=*/true); + + // For compatibility reasons, we have to update the item's old geometry + // if someone is connected to the changed signal or the scene has no views. + // Note that this has to be done *after* markDirty to ensure that + // _q_processDirtyItems is called before _q_emitUpdated. + if (scenePrivate->isSignalConnected(scenePrivate->changedSignalIndex) + || scenePrivate->views.isEmpty()) { + if (d_ptr->hasTranslateOnlySceneTransform()) { + d_ptr->scene->update(boundingRect().translated(d_ptr->sceneTransform.dx(), + d_ptr->sceneTransform.dy())); + } else { + d_ptr->scene->update(d_ptr->sceneTransform.mapRect(boundingRect())); + } + } + } + + d_ptr->markParentDirty(/*updateBoundingRect=*/true); +} + +/*! + \internal + + Highlights \a item as selected. + + NOTE: This function is a duplicate of qt_graphicsItem_highlightSelected() in + qgraphicssvgitem.cpp! +*/ +static void qt_graphicsItem_highlightSelected( + QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option) +{ + const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1)); + if (qFuzzyIsNull(qMax(murect.width(), murect.height()))) + return; + + const QRectF mbrect = painter->transform().mapRect(item->boundingRect()); + if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0)) + return; + + qreal itemPenWidth; + switch (item->type()) { + case QGraphicsEllipseItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsPathItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsPolygonItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsRectItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsSimpleTextItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + case QGraphicsLineItem::Type: + itemPenWidth = static_cast(item)->pen().widthF(); + break; + default: + itemPenWidth = 1.0; + } + const qreal pad = itemPenWidth / 2; + + const qreal penWidth = 0; // cosmetic pen + + const QColor fgcolor = option->palette.windowText().color(); + const QColor bgcolor( // ensure good contrast against fgcolor + fgcolor.red() > 127 ? 0 : 255, + fgcolor.green() > 127 ? 0 : 255, + fgcolor.blue() > 127 ? 0 : 255); + + painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); + + painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); +} + +/*! + \class QGraphicsObject + \brief The QGraphicsObject class provides a base class for all graphics items that + require signals, slots and properties. + \since 4.6 + \ingroup graphicsview-api + + The class extends a QGraphicsItem with QObject's signal/slot and property mechanisms. + It maps many of QGraphicsItem's basic setters and getters to properties and adds notification + signals for many of them. + + \section1 Parents and Children + + Each graphics object can be constructed with a parent item. This ensures that the + item will be destroyed when its parent item is destroyed. Although QGraphicsObject + inherits from both QObject and QGraphicsItem, you should use the functions provided + by QGraphicsItem, \e not QObject, to manage the relationships between parent and + child items. + + The relationships between items can be explored using the parentItem() and childItems() + functions. In the hierarchy of items in a scene, the parentObject() and parentWidget() + functions are the equivalent of the QWidget::parent() and QWidget::parentWidget() + functions for QWidget subclasses. + + \sa QGraphicsWidget +*/ + +/*! + Constructs a QGraphicsObject with \a parent. +*/ +QGraphicsObject::QGraphicsObject(QGraphicsItem *parent) + : QGraphicsItem(parent) +{ + QGraphicsItem::d_ptr->isObject = true; +} + +/*! + \internal +*/ +QGraphicsObject::QGraphicsObject(QGraphicsItemPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene) + : QGraphicsItem(dd, parent, scene) +{ + QGraphicsItem::d_ptr->isObject = true; +} + +#ifndef QT_NO_GESTURES +/*! + Subscribes the graphics object to the given \a gesture with specific \a flags. + + \sa ungrabGesture(), QGestureEvent +*/ +void QGraphicsObject::grabGesture(Qt::GestureType gesture, Qt::GestureFlags flags) +{ + bool contains = QGraphicsItem::d_ptr->gestureContext.contains(gesture); + QGraphicsItem::d_ptr->gestureContext.insert(gesture, flags); + if (!contains && QGraphicsItem::d_ptr->scene) + QGraphicsItem::d_ptr->scene->d_func()->grabGesture(this, gesture); +} + +/*! + Unsubscribes the graphics object from the given \a gesture. + + \sa grabGesture(), QGestureEvent +*/ +void QGraphicsObject::ungrabGesture(Qt::GestureType gesture) +{ + if (QGraphicsItem::d_ptr->gestureContext.remove(gesture) && QGraphicsItem::d_ptr->scene) + QGraphicsItem::d_ptr->scene->d_func()->ungrabGesture(this, gesture); +} +#endif // QT_NO_GESTURES + +/*! + Updates the item's micro focus. This is slot for convenience. + + \since 4.7 + + \sa QInputContext +*/ +void QGraphicsObject::updateMicroFocus() +{ + QGraphicsItem::updateMicroFocus(); +} + +void QGraphicsItemPrivate::children_append(QDeclarativeListProperty *list, QGraphicsObject *item) +{ + if (item) { + QGraphicsObject *graphicsObject = static_cast(list->object); + if (QGraphicsItemPrivate::get(graphicsObject)->sendParentChangeNotification) { + item->setParentItem(graphicsObject); + } else { + QGraphicsItemPrivate::get(item)->setParentItemHelper(graphicsObject, 0, 0); + } + } +} + +int QGraphicsItemPrivate::children_count(QDeclarativeListProperty *list) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + return d->children.count(); +} + +QGraphicsObject *QGraphicsItemPrivate::children_at(QDeclarativeListProperty *list, int index) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + if (index >= 0 && index < d->children.count()) + return d->children.at(index)->toGraphicsObject(); + else + return 0; +} + +void QGraphicsItemPrivate::children_clear(QDeclarativeListProperty *list) +{ + QGraphicsItemPrivate *d = QGraphicsItemPrivate::get(static_cast(list->object)); + int childCount = d->children.count(); + if (d->sendParentChangeNotification) { + for (int index = 0; index < childCount; index++) + d->children.at(0)->setParentItem(0); + } else { + for (int index = 0; index < childCount; index++) + QGraphicsItemPrivate::get(d->children.at(0))->setParentItemHelper(0, 0, 0); + } +} + +/*! + Returns a list of this item's children. + + The items are sorted by stacking order. This takes into account both the + items' insertion order and their Z-values. + +*/ +QDeclarativeListProperty QGraphicsItemPrivate::childrenList() +{ + Q_Q(QGraphicsItem); + if (isObject) { + QGraphicsObject *that = static_cast(q); + return QDeclarativeListProperty(that, &children, children_append, + children_count, children_at, children_clear); + } else { + //QGraphicsItem is not supported for this property + return QDeclarativeListProperty(); + } +} + +/*! + \internal + Returns the width of the item + Reimplemented by QGraphicsWidget +*/ +qreal QGraphicsItemPrivate::width() const +{ + return 0; +} + +/*! + \internal + Set the width of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::setWidth(qreal w) +{ + Q_UNUSED(w); +} + +/*! + \internal + Reset the width of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::resetWidth() +{ +} + +/*! + \internal + Returns the height of the item + Reimplemented by QGraphicsWidget +*/ +qreal QGraphicsItemPrivate::height() const +{ + return 0; +} + +/*! + \internal + Set the height of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::setHeight(qreal h) +{ + Q_UNUSED(h); +} + +/*! + \internal + Reset the height of the item + Reimplemented by QGraphicsWidget +*/ +void QGraphicsItemPrivate::resetHeight() +{ +} + +/*! + \property QGraphicsObject::children + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::width + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::height + \since 4.7 + \internal +*/ + +/*! + \property QGraphicsObject::parent + \brief the parent of the item + + \note The item's parent is set independently of the parent object returned + by QObject::parent(). + + \sa QGraphicsItem::setParentItem(), QGraphicsItem::parentObject() +*/ + +/*! + \property QGraphicsObject::opacity + \brief the opacity of the item + + \sa QGraphicsItem::setOpacity(), QGraphicsItem::opacity() +*/ + +/*! + \fn QGraphicsObject::opacityChanged() + + This signal gets emitted whenever the opacity of the item changes + + \sa QGraphicsItem::opacity() +*/ + +/*! + \fn QGraphicsObject::parentChanged() + + This signal gets emitted whenever the parent of the item changes +*/ + +/*! + \property QGraphicsObject::pos + \brief the position of the item + + Describes the items position. + + \sa QGraphicsItem::setPos(), QGraphicsItem::pos() +*/ + +/*! + \property QGraphicsObject::x + \brief the x position of the item + + Describes the items x position. + + \sa QGraphicsItem::setX(), setPos(), xChanged() +*/ + +/*! + \fn QGraphicsObject::xChanged() + + This signal gets emitted whenever the x position of the item changes + + \sa pos() +*/ + +/*! + \property QGraphicsObject::y + \brief the y position of the item + + Describes the items y position. + + \sa QGraphicsItem::setY(), setPos(), yChanged() +*/ + +/*! + \fn QGraphicsObject::yChanged() + + This signal gets emitted whenever the y position of the item changes. + + \sa pos() +*/ + +/*! + \property QGraphicsObject::z + \brief the z value of the item + + Describes the items z value. + + \sa QGraphicsItem::setZValue(), zValue(), zChanged() +*/ + +/*! + \fn QGraphicsObject::zChanged() + + This signal gets emitted whenever the z value of the item changes. + + \sa pos() +*/ + +/*! + \property QGraphicsObject::rotation + This property holds the rotation of the item in degrees. + + This specifies how many degrees to rotate the item around its transformOrigin. + The default rotation is 0 degrees (i.e. not rotated at all). +*/ + +/*! + \fn QGraphicsObject::rotationChanged() + + This signal gets emitted whenever the roation of the item changes. +*/ + +/*! + \property QGraphicsObject::scale + This property holds the scale of the item. + + A scale of less than 1 means the item will be displayed smaller than + normal, and a scale of greater than 1 means the item will be + displayed larger than normal. A negative scale means the item will + be mirrored. + + By default, items are displayed at a scale of 1 (i.e. at their + normal size). + + Scaling is from the item's transformOrigin. +*/ + +/*! + \fn void QGraphicsObject::scaleChanged() + + This signal is emitted when the scale of the item changes. +*/ + + +/*! + \property QGraphicsObject::enabled + \brief whether the item is enabled or not + + This property is declared in QGraphicsItem. + + By default, this property is true. + + \sa QGraphicsItem::isEnabled(), QGraphicsItem::setEnabled() + \sa QGraphicsObject::enabledChanged() +*/ + +/*! + \fn void QGraphicsObject::enabledChanged() + + This signal gets emitted whenever the item get's enabled or disabled. + + \sa isEnabled() +*/ + +/*! + \property QGraphicsObject::visible + \brief whether the item is visible or not + + This property is declared in QGraphicsItem. + + By default, this property is true. + + \sa QGraphicsItem::isVisible(), QGraphicsItem::setVisible(), visibleChanged() +*/ + +/*! + \fn QGraphicsObject::visibleChanged() + + This signal gets emitted whenever the visibility of the item changes + + \sa visible +*/ + +/*! + \fn const QObjectList &QGraphicsObject::children() const + \internal + + This function returns the same value as QObject::children(). It's + provided to differentiate between the obsolete member + QGraphicsItem::children() and QObject::children(). QGraphicsItem now + provides childItems() instead. +*/ + +/*! + \property QGraphicsObject::transformOriginPoint + \brief the transformation origin + + This property sets a specific point in the items coordiante system as the + origin for scale and rotation. + + \sa scale, rotation, QGraphicsItem::transformOriginPoint() +*/ + +/*! + \fn void QGraphicsObject::widthChanged() + \internal +*/ + +/*! + \fn void QGraphicsObject::heightChanged() + \internal +*/ + +/*! + + \fn QGraphicsObject::childrenChanged() + + This signal gets emitted whenever the children list changes + \internal +*/ + +/*! + \property QGraphicsObject::effect + \since 4.7 + \brief the effect attached to this item + + \sa QGraphicsItem::setGraphicsEffect(), QGraphicsItem::graphicsEffect() +*/ + +/*! + \class QAbstractGraphicsShapeItem + \brief The QAbstractGraphicsShapeItem class provides a common base for + all path items. + \since 4.2 + \ingroup graphicsview-api + + This class does not fully implement an item by itself; in particular, it + does not implement boundingRect() and paint(), which are inherited by + QGraphicsItem. + + You can subclass this item to provide a simple base implementation of + accessors for the item's pen and brush. + + \sa QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsPathItem, + QGraphicsPolygonItem, QGraphicsTextItem, QGraphicsLineItem, + QGraphicsPixmapItem, {Graphics View Framework} +*/ + +class QAbstractGraphicsShapeItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QAbstractGraphicsShapeItem) +public: + + QBrush brush; + QPen pen; + + // Cached bounding rectangle + mutable QRectF boundingRect; +}; + +/*! + Constructs a QAbstractGraphicsShapeItem. \a parent is passed to + QGraphicsItem's constructor. +*/ +QAbstractGraphicsShapeItem::QAbstractGraphicsShapeItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QAbstractGraphicsShapeItemPrivate, parent, scene) +{ +} + +/*! + \internal +*/ +QAbstractGraphicsShapeItem::QAbstractGraphicsShapeItem(QAbstractGraphicsShapeItemPrivate &dd, + QGraphicsItem *parent, + QGraphicsScene *scene) + : QGraphicsItem(dd, parent, scene) +{ +} + +/*! + Destroys a QAbstractGraphicsShapeItem. +*/ +QAbstractGraphicsShapeItem::~QAbstractGraphicsShapeItem() +{ +} + +/*! + Returns the item's pen. If no pen has been set, this function returns + QPen(), a default black solid line pen with 0 width. +*/ +QPen QAbstractGraphicsShapeItem::pen() const +{ + Q_D(const QAbstractGraphicsShapeItem); + return d->pen; +} + +/*! + Sets the pen for this item to \a pen. + + The pen is used to draw the item's outline. + + \sa pen() +*/ +void QAbstractGraphicsShapeItem::setPen(const QPen &pen) +{ + Q_D(QAbstractGraphicsShapeItem); + if (d->pen == pen) + return; + prepareGeometryChange(); + d->pen = pen; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the item's brush, or an empty brush if no brush has been set. + + \sa setBrush() +*/ +QBrush QAbstractGraphicsShapeItem::brush() const +{ + Q_D(const QAbstractGraphicsShapeItem); + return d->brush; +} + +/*! + Sets the item's brush to \a brush. + + The item's brush is used to fill the item. + + If you use a brush with a QGradient, the gradient + is relative to the item's coordinate system. + + \sa brush() +*/ +void QAbstractGraphicsShapeItem::setBrush(const QBrush &brush) +{ + Q_D(QAbstractGraphicsShapeItem); + if (d->brush == brush) + return; + d->brush = brush; + update(); +} + +/*! + \reimp +*/ +bool QAbstractGraphicsShapeItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QAbstractGraphicsShapeItem::opaqueArea() const +{ + Q_D(const QAbstractGraphicsShapeItem); + if (d->brush.isOpaque()) + return isClipped() ? clipPath() : shape(); + return QGraphicsItem::opaqueArea(); +} + +/*! + \class QGraphicsPathItem + \brief The QGraphicsPathItem class provides a path item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's path, pass a QPainterPath to QGraphicsPathItem's + constructor, or call the setPath() function. The path() function + returns the current path. + + \image graphicsview-pathitem.png + + QGraphicsPathItem uses the path to provide a reasonable + implementation of boundingRect(), shape(), and contains(). The + paint() function draws the path using the item's associated pen + and brush, which you can set by calling the setPen() and + setBrush() functions. + + \sa QGraphicsRectItem, QGraphicsEllipseItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsPathItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPathItem) +public: + QPainterPath path; +}; + +/*! + Constructs a QGraphicsPath item using \a path as the default path. \a + parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPathItem::QGraphicsPathItem(const QPainterPath &path, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPathItemPrivate, parent, scene) +{ + if (!path.isEmpty()) + setPath(path); +} + +/*! + Constructs a QGraphicsPath. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPathItem::QGraphicsPathItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPathItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPathItem. +*/ +QGraphicsPathItem::~QGraphicsPathItem() +{ +} + +/*! + Returns the item's path as a QPainterPath. If no item has been set, an + empty QPainterPath is returned. + + \sa setPath() +*/ +QPainterPath QGraphicsPathItem::path() const +{ + Q_D(const QGraphicsPathItem); + return d->path; +} + +/*! + Sets the item's path to be the given \a path. + + \sa path() +*/ +void QGraphicsPathItem::setPath(const QPainterPath &path) +{ + Q_D(QGraphicsPathItem); + if (d->path == path) + return; + prepareGeometryChange(); + d->path = path; + d->boundingRect = QRectF(); + update(); +} + +/*! + \reimp +*/ +QRectF QGraphicsPathItem::boundingRect() const +{ + Q_D(const QGraphicsPathItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0) + d->boundingRect = d->path.controlPointRect(); + else { + d->boundingRect = shape().controlPointRect(); + } + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPathItem::shape() const +{ + Q_D(const QGraphicsPathItem); + return qt_graphicsItem_shapeFromPath(d->path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsPathItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPathItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsPathItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawPath(d->path); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPathItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPathItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsPathItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsPathItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPathItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPathItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsRectItem + \brief The QGraphicsRectItem class provides a rectangle item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's rectangle, pass a QRectF to QGraphicsRectItem's + constructor, or call the setRect() function. The rect() function + returns the current rectangle. + + \image graphicsview-rectitem.png + + QGraphicsRectItem uses the rectangle and the pen width to provide + a reasonable implementation of boundingRect(), shape(), and + contains(). The paint() function draws the rectangle using the + item's associated pen and brush, which you can set by calling the + setPen() and setBrush() functions. + + \note The rendering of invalid rectangles, such as those with negative + widths or heights, is undefined. If you cannot be sure that you are + using valid rectangles (for example, if you are creating + rectangles using data from an unreliable source) then you should + use QRectF::normalized() to create normalized rectangles, and use + those instead. + + \sa QGraphicsPathItem, QGraphicsEllipseItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsRectItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsRectItem) +public: + QRectF rect; +}; + +/*! + Constructs a QGraphicsRectItem, using \a rect as the default rectangle. + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(const QRectF &rect, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ + setRect(rect); +} + +/*! + \fn QGraphicsRectItem::QGraphicsRectItem(qreal x, qreal y, qreal width, qreal height, + QGraphicsItem *parent) + + Constructs a QGraphicsRectItem with a default rectangle defined + by (\a x, \a y) and the given \a width and \a height. + + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(qreal x, qreal y, qreal w, qreal h, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ + setRect(QRectF(x, y, w, h)); +} + +/*! + Constructs a QGraphicsRectItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsRectItem::QGraphicsRectItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsRectItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsRectItem. +*/ +QGraphicsRectItem::~QGraphicsRectItem() +{ +} + +/*! + Returns the item's rectangle. + + \sa setRect() +*/ +QRectF QGraphicsRectItem::rect() const +{ + Q_D(const QGraphicsRectItem); + return d->rect; +} + +/*! + \fn void QGraphicsRectItem::setRect(const QRectF &rectangle) + + Sets the item's rectangle to be the given \a rectangle. + + \sa rect() +*/ +void QGraphicsRectItem::setRect(const QRectF &rect) +{ + Q_D(QGraphicsRectItem); + if (d->rect == rect) + return; + prepareGeometryChange(); + d->rect = rect; + d->boundingRect = QRectF(); + update(); +} + +/*! + \fn void QGraphicsRectItem::setRect(qreal x, qreal y, qreal width, qreal height) + \fn void QGraphicsEllipseItem::setRect(qreal x, qreal y, qreal width, qreal height) + + Sets the item's rectangle to the rectangle defined by (\a x, \a y) + and the given \a width and \a height. + + This convenience function is equivalent to calling \c + {setRect(QRectF(x, y, width, height))} + + \sa rect() +*/ + +/*! + \reimp +*/ +QRectF QGraphicsRectItem::boundingRect() const +{ + Q_D(const QGraphicsRectItem); + if (d->boundingRect.isNull()) { + qreal halfpw = pen().widthF() / 2; + d->boundingRect = d->rect; + if (halfpw > 0.0) + d->boundingRect.adjust(-halfpw, -halfpw, halfpw, halfpw); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsRectItem::shape() const +{ + Q_D(const QGraphicsRectItem); + QPainterPath path; + path.addRect(d->rect); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsRectItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsRectItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawRect(d->rect); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsRectItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsRectItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsRectItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsRectItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsRectItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsRectItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsEllipseItem + \brief The QGraphicsEllipseItem class provides an ellipse item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + QGraphicsEllipseItem respresents an ellipse with a fill and an outline, + and you can also use it for ellipse segments (see startAngle(), + spanAngle()). + + \table + \row + \o \inlineimage graphicsview-ellipseitem.png + \o \inlineimage graphicsview-ellipseitem-pie.png + \endtable + + To set the item's ellipse, pass a QRectF to QGraphicsEllipseItem's + constructor, or call setRect(). The rect() function returns the + current ellipse geometry. + + QGraphicsEllipseItem uses the rect and the pen width to provide a + reasonable implementation of boundingRect(), shape(), and contains(). The + paint() function draws the ellipse using the item's associated pen and + brush, which you can set by calling setPen() and setBrush(). + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsPolygonItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsEllipseItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsEllipseItem) +public: + inline QGraphicsEllipseItemPrivate() + : startAngle(0), spanAngle(360 * 16) + { } + + QRectF rect; + int startAngle; + int spanAngle; +}; + +/*! + Constructs a QGraphicsEllipseItem using \a rect as the default rectangle. + \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(const QRectF &rect, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ + setRect(rect); +} + +/*! + \fn QGraphicsEllipseItem::QGraphicsEllipseItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent) + \since 4.3 + + Constructs a QGraphicsEllipseItem using the rectangle defined by (\a x, \a + y) and the given \a width and \a height, as the default rectangle. \a + parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(qreal x, qreal y, qreal w, qreal h, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ + setRect(x,y,w,h); +} + + + +/*! + Constructs a QGraphicsEllipseItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsEllipseItem::QGraphicsEllipseItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsEllipseItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsEllipseItem. +*/ +QGraphicsEllipseItem::~QGraphicsEllipseItem() +{ +} + +/*! + Returns the item's ellipse geometry as a QRectF. + + \sa setRect(), QPainter::drawEllipse() +*/ +QRectF QGraphicsEllipseItem::rect() const +{ + Q_D(const QGraphicsEllipseItem); + return d->rect; +} + +/*! + Sets the item's ellipse geometry to \a rect. The rectangle's left edge + defines the left edge of the ellipse, and the rectangle's top edge + describes the top of the ellipse. The height and width of the rectangle + describe the height and width of the ellipse. + + \sa rect(), QPainter::drawEllipse() +*/ +void QGraphicsEllipseItem::setRect(const QRectF &rect) +{ + Q_D(QGraphicsEllipseItem); + if (d->rect == rect) + return; + prepareGeometryChange(); + d->rect = rect; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the start angle for an ellipse segment in 16ths of a degree. This + angle is used together with spanAngle() for representing an ellipse + segment (a pie). By default, the start angle is 0. + + \sa setStartAngle(), spanAngle() +*/ +int QGraphicsEllipseItem::startAngle() const +{ + Q_D(const QGraphicsEllipseItem); + return d->startAngle; +} + +/*! + Sets the start angle for an ellipse segment to \a angle, which is in 16ths + of a degree. This angle is used together with spanAngle() for representing + an ellipse segment (a pie). By default, the start angle is 0. + + \sa startAngle(), setSpanAngle(), QPainter::drawPie() +*/ +void QGraphicsEllipseItem::setStartAngle(int angle) +{ + Q_D(QGraphicsEllipseItem); + if (angle != d->startAngle) { + prepareGeometryChange(); + d->boundingRect = QRectF(); + d->startAngle = angle; + update(); + } +} + +/*! + Returns the span angle of an ellipse segment in 16ths of a degree. This + angle is used together with startAngle() for representing an ellipse + segment (a pie). By default, this function returns 5760 (360 * 16, a full + ellipse). + + \sa setSpanAngle(), startAngle() +*/ +int QGraphicsEllipseItem::spanAngle() const +{ + Q_D(const QGraphicsEllipseItem); + return d->spanAngle; +} + +/*! + Sets the span angle for an ellipse segment to \a angle, which is in 16ths + of a degree. This angle is used together with startAngle() to represent an + ellipse segment (a pie). By default, the span angle is 5760 (360 * 16, a + full ellipse). + + \sa spanAngle(), setStartAngle(), QPainter::drawPie() +*/ +void QGraphicsEllipseItem::setSpanAngle(int angle) +{ + Q_D(QGraphicsEllipseItem); + if (angle != d->spanAngle) { + prepareGeometryChange(); + d->boundingRect = QRectF(); + d->spanAngle = angle; + update(); + } +} + +/*! + \reimp +*/ +QRectF QGraphicsEllipseItem::boundingRect() const +{ + Q_D(const QGraphicsEllipseItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0 && d->spanAngle == 360 * 16) + d->boundingRect = d->rect; + else + d->boundingRect = shape().controlPointRect(); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsEllipseItem::shape() const +{ + Q_D(const QGraphicsEllipseItem); + QPainterPath path; + if (d->rect.isNull()) + return path; + if (d->spanAngle != 360 * 16) { + path.moveTo(d->rect.center()); + path.arcTo(d->rect, d->startAngle / 16.0, d->spanAngle / 16.0); + } else { + path.addEllipse(d->rect); + } + + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsEllipseItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsEllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsEllipseItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + if ((d->spanAngle != 0) && (qAbs(d->spanAngle) % (360 * 16) == 0)) + painter->drawEllipse(d->rect); + else + painter->drawPie(d->rect, d->startAngle, d->spanAngle); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsEllipseItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsEllipseItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsEllipseItem::type() const +{ + return Type; +} + + +/*! + \internal +*/ +bool QGraphicsEllipseItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsEllipseItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsEllipseItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsPolygonItem + \brief The QGraphicsPolygonItem class provides a polygon item that you + can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's polygon, pass a QPolygonF to + QGraphicsPolygonItem's constructor, or call the setPolygon() + function. The polygon() function returns the current polygon. + + \image graphicsview-polygonitem.png + + QGraphicsPolygonItem uses the polygon and the pen width to provide + a reasonable implementation of boundingRect(), shape(), and + contains(). The paint() function draws the polygon using the + item's associated pen and brush, which you can set by calling the + setPen() and setBrush() functions. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsLineItem, QGraphicsPixmapItem, {Graphics + View Framework} +*/ + +class QGraphicsPolygonItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPolygonItem) +public: + inline QGraphicsPolygonItemPrivate() + : fillRule(Qt::OddEvenFill) + { } + + QPolygonF polygon; + Qt::FillRule fillRule; +}; + +/*! + Constructs a QGraphicsPolygonItem with \a polygon as the default + polygon. \a parent is passed to QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPolygonItem::QGraphicsPolygonItem(const QPolygonF &polygon, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPolygonItemPrivate, parent, scene) +{ + setPolygon(polygon); +} + +/*! + Constructs a QGraphicsPolygonItem. \a parent is passed to + QAbstractGraphicsShapeItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPolygonItem::QGraphicsPolygonItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsPolygonItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPolygonItem. +*/ +QGraphicsPolygonItem::~QGraphicsPolygonItem() +{ +} + +/*! + Returns the item's polygon, or an empty polygon if no polygon + has been set. + + \sa setPolygon() +*/ +QPolygonF QGraphicsPolygonItem::polygon() const +{ + Q_D(const QGraphicsPolygonItem); + return d->polygon; +} + +/*! + Sets the item's polygon to be the given \a polygon. + + \sa polygon() +*/ +void QGraphicsPolygonItem::setPolygon(const QPolygonF &polygon) +{ + Q_D(QGraphicsPolygonItem); + if (d->polygon == polygon) + return; + prepareGeometryChange(); + d->polygon = polygon; + d->boundingRect = QRectF(); + update(); +} + +/*! + Returns the fill rule of the polygon. The default fill rule is + Qt::OddEvenFill. + + \sa setFillRule(), QPainterPath::fillRule(), QPainter::drawPolygon() +*/ +Qt::FillRule QGraphicsPolygonItem::fillRule() const +{ + Q_D(const QGraphicsPolygonItem); + return d->fillRule; +} + +/*! + Sets the fill rule of the polygon to \a rule. The default fill rule is + Qt::OddEvenFill. + + \sa fillRule(), QPainterPath::fillRule(), QPainter::drawPolygon() +*/ +void QGraphicsPolygonItem::setFillRule(Qt::FillRule rule) +{ + Q_D(QGraphicsPolygonItem); + if (rule != d->fillRule) { + d->fillRule = rule; + update(); + } +} + +/*! + \reimp +*/ +QRectF QGraphicsPolygonItem::boundingRect() const +{ + Q_D(const QGraphicsPolygonItem); + if (d->boundingRect.isNull()) { + qreal pw = pen().widthF(); + if (pw == 0.0) + d->boundingRect = d->polygon.boundingRect(); + else + d->boundingRect = shape().controlPointRect(); + } + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPolygonItem::shape() const +{ + Q_D(const QGraphicsPolygonItem); + QPainterPath path; + path.addPolygon(d->polygon); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsPolygonItem::contains(const QPointF &point) const +{ + return QAbstractGraphicsShapeItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsPolygonItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->setBrush(d->brush); + painter->drawPolygon(d->polygon, d->fillRule); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPolygonItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPolygonItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsPolygonItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsPolygonItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPolygonItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPolygonItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsLineItem + \brief The QGraphicsLineItem class provides a line item that you can add to a + QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's line, pass a QLineF to QGraphicsLineItem's + constructor, or call the setLine() function. The line() function + returns the current line. By default the line is black with a + width of 0, but you can change this by calling setPen(). + + \img graphicsview-lineitem.png + + QGraphicsLineItem uses the line and the pen width to provide a reasonable + implementation of boundingRect(), shape(), and contains(). The paint() + function draws the line using the item's associated pen. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsPolygonItem, QGraphicsPixmapItem, + {Graphics View Framework} +*/ + +class QGraphicsLineItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLineItem) +public: + QLineF line; + QPen pen; +}; + +/*! + Constructs a QGraphicsLineItem, using \a line as the default line. \a + parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(const QLineF &line, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ + setLine(line); +} + +/*! + Constructs a QGraphicsLineItem, using the line between (\a x1, \a y1) and + (\a x2, \a y2) as the default line. \a parent is passed to + QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ + setLine(x1, y1, x2, y2); +} + + + +/*! + Constructs a QGraphicsLineItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsLineItem::QGraphicsLineItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsLineItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsLineItem. +*/ +QGraphicsLineItem::~QGraphicsLineItem() +{ +} + +/*! + Returns the item's pen, or a black solid 0-width pen if no pen has + been set. + + \sa setPen() +*/ +QPen QGraphicsLineItem::pen() const +{ + Q_D(const QGraphicsLineItem); + return d->pen; +} + +/*! + Sets the item's pen to \a pen. If no pen is set, the line will be painted + using a black solid 0-width pen. + + \sa pen() +*/ +void QGraphicsLineItem::setPen(const QPen &pen) +{ + Q_D(QGraphicsLineItem); + if (d->pen == pen) + return; + prepareGeometryChange(); + d->pen = pen; + update(); +} + +/*! + Returns the item's line, or a null line if no line has been set. + + \sa setLine() +*/ +QLineF QGraphicsLineItem::line() const +{ + Q_D(const QGraphicsLineItem); + return d->line; +} + +/*! + Sets the item's line to be the given \a line. + + \sa line() +*/ +void QGraphicsLineItem::setLine(const QLineF &line) +{ + Q_D(QGraphicsLineItem); + if (d->line == line) + return; + prepareGeometryChange(); + d->line = line; + update(); +} + +/*! + \fn void QGraphicsLineItem::setLine(qreal x1, qreal y1, qreal x2, qreal y2) + \overload + + Sets the item's line to be the line between (\a x1, \a y1) and (\a + x2, \a y2). + + This is the same as calling \c {setLine(QLineF(x1, y1, x2, y2))}. +*/ + +/*! + \reimp +*/ +QRectF QGraphicsLineItem::boundingRect() const +{ + Q_D(const QGraphicsLineItem); + if (d->pen.widthF() == 0.0) { + const qreal x1 = d->line.p1().x(); + const qreal x2 = d->line.p2().x(); + const qreal y1 = d->line.p1().y(); + const qreal y2 = d->line.p2().y(); + qreal lx = qMin(x1, x2); + qreal rx = qMax(x1, x2); + qreal ty = qMin(y1, y2); + qreal by = qMax(y1, y2); + return QRectF(lx, ty, rx - lx, by - ty); + } + return shape().controlPointRect(); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsLineItem::shape() const +{ + Q_D(const QGraphicsLineItem); + QPainterPath path; + if (d->line == QLineF()) + return path; + + path.moveTo(d->line.p1()); + path.lineTo(d->line.p2()); + return qt_graphicsItem_shapeFromPath(path, d->pen); +} + +/*! + \reimp +*/ +bool QGraphicsLineItem::contains(const QPointF &point) const +{ + return QGraphicsItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsLineItem); + Q_UNUSED(widget); + painter->setPen(d->pen); + painter->drawLine(d->line); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsLineItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsLineItem::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsLineItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsLineItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsLineItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsLineItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsPixmapItem + \brief The QGraphicsPixmapItem class provides a pixmap item that you can add to + a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's pixmap, pass a QPixmap to QGraphicsPixmapItem's + constructor, or call the setPixmap() function. The pixmap() + function returns the current pixmap. + + QGraphicsPixmapItem uses pixmap's optional alpha mask to provide a + reasonable implementation of boundingRect(), shape(), and contains(). + + \image graphicsview-pixmapitem.png + + The pixmap is drawn at the item's (0, 0) coordinate, as returned by + offset(). You can change the drawing offset by calling setOffset(). + + You can set the pixmap's transformation mode by calling + setTransformationMode(). By default, Qt::FastTransformation is used, which + provides fast, non-smooth scaling. Qt::SmoothTransformation enables + QPainter::SmoothPixmapTransform on the painter, and the quality depends on + the platform and viewport. The result is usually not as good as calling + QPixmap::scale() directly. Call transformationMode() to get the current + transformation mode for the item. + + \sa QGraphicsPathItem, QGraphicsRectItem, QGraphicsEllipseItem, + QGraphicsTextItem, QGraphicsPolygonItem, QGraphicsLineItem, + {Graphics View Framework} +*/ + +/*! + \enum QGraphicsPixmapItem::ShapeMode + + This enum describes how QGraphicsPixmapItem calculates its shape and + opaque area. + + The default value is MaskShape. + + \value MaskShape The shape is determined by calling QPixmap::mask(). + This shape includes only the opaque pixels of the pixmap. + Because the shape is more complex, however, it can be slower than the other modes, + and uses more memory. + + \value BoundingRectShape The shape is determined by tracing the outline of + the pixmap. This is the fastest shape mode, but it does not take into account + any transparent areas on the pixmap. + + \value HeuristicMaskShape The shape is determine by calling + QPixmap::createHeuristicMask(). The performance and memory consumption + is similar to MaskShape. +*/ +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +class QGraphicsPixmapItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsPixmapItem) +public: + QGraphicsPixmapItemPrivate() + : transformationMode(Qt::FastTransformation), + shapeMode(QGraphicsPixmapItem::MaskShape), + hasShape(false) + {} + + QPixmap pixmap; + Qt::TransformationMode transformationMode; + QPointF offset; + QGraphicsPixmapItem::ShapeMode shapeMode; + QPainterPath shape; + bool hasShape; + + void updateShape() + { + shape = QPainterPath(); + switch (shapeMode) { + case QGraphicsPixmapItem::MaskShape: { + QBitmap mask = pixmap.mask(); + if (!mask.isNull()) { + shape = qt_regionToPath(QRegion(mask).translated(offset.toPoint())); + break; + } + // FALL THROUGH + } + case QGraphicsPixmapItem::BoundingRectShape: + shape.addRect(QRectF(offset.x(), offset.y(), pixmap.width(), pixmap.height())); + break; + case QGraphicsPixmapItem::HeuristicMaskShape: +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + shape = qt_regionToPath(QRegion(pixmap.createHeuristicMask()).translated(offset.toPoint())); +#else + shape.addRect(QRectF(offset.x(), offset.y(), pixmap.width(), pixmap.height())); +#endif + break; + } + } +}; + +/*! + Constructs a QGraphicsPixmapItem, using \a pixmap as the default pixmap. + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPixmapItem::QGraphicsPixmapItem(const QPixmap &pixmap, + QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsPixmapItemPrivate, parent, scene) +{ + setPixmap(pixmap); +} + +/*! + Constructs a QGraphicsPixmapItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsPixmapItem::QGraphicsPixmapItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsPixmapItemPrivate, parent, scene) +{ +} + +/*! + Destroys the QGraphicsPixmapItem. +*/ +QGraphicsPixmapItem::~QGraphicsPixmapItem() +{ +} + +/*! + Sets the item's pixmap to \a pixmap. + + \sa pixmap() +*/ +void QGraphicsPixmapItem::setPixmap(const QPixmap &pixmap) +{ + Q_D(QGraphicsPixmapItem); + prepareGeometryChange(); + d->pixmap = pixmap; + d->hasShape = false; + update(); +} + +/*! + Returns the item's pixmap, or an invalid QPixmap if no pixmap has been + set. + + \sa setPixmap() +*/ +QPixmap QGraphicsPixmapItem::pixmap() const +{ + Q_D(const QGraphicsPixmapItem); + return d->pixmap; +} + +/*! + Returns the transformation mode of the pixmap. The default mode is + Qt::FastTransformation, which provides quick transformation with no + smoothing. + + \sa setTransformationMode() +*/ +Qt::TransformationMode QGraphicsPixmapItem::transformationMode() const +{ + Q_D(const QGraphicsPixmapItem); + return d->transformationMode; +} + +/*! + Sets the pixmap item's transformation mode to \a mode, and toggles an + update of the item. The default mode is Qt::FastTransformation, which + provides quick transformation with no smoothing. + + Qt::SmoothTransformation enables QPainter::SmoothPixmapTransform on the + painter, and the quality depends on the platform and viewport. The result + is usually not as good as calling QPixmap::scale() directly. + + \sa transformationMode() +*/ +void QGraphicsPixmapItem::setTransformationMode(Qt::TransformationMode mode) +{ + Q_D(QGraphicsPixmapItem); + if (mode != d->transformationMode) { + d->transformationMode = mode; + update(); + } +} + +/*! + Returns the pixmap item's \e offset, which defines the point of the + top-left corner of the pixmap, in local coordinates. + + \sa setOffset() +*/ +QPointF QGraphicsPixmapItem::offset() const +{ + Q_D(const QGraphicsPixmapItem); + return d->offset; +} + +/*! + Sets the pixmap item's offset to \a offset. QGraphicsPixmapItem will draw + its pixmap using \a offset for its top-left corner. + + \sa offset() +*/ +void QGraphicsPixmapItem::setOffset(const QPointF &offset) +{ + Q_D(QGraphicsPixmapItem); + if (d->offset == offset) + return; + prepareGeometryChange(); + d->offset = offset; + d->hasShape = false; + update(); +} + +/*! + \fn void QGraphicsPixmapItem::setOffset(qreal x, qreal y) + \since 4.3 + + This convenience function is equivalent to calling setOffset(QPointF(\a x, \a y)). +*/ + +/*! + \reimp +*/ +QRectF QGraphicsPixmapItem::boundingRect() const +{ + Q_D(const QGraphicsPixmapItem); + if (d->pixmap.isNull()) + return QRectF(); + if (d->flags & ItemIsSelectable) { + qreal pw = 1.0; + return QRectF(d->offset, d->pixmap.size()).adjusted(-pw/2, -pw/2, pw/2, pw/2); + } else { + return QRectF(d->offset, d->pixmap.size()); + } +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPixmapItem::shape() const +{ + Q_D(const QGraphicsPixmapItem); + if (!d->hasShape) { + QGraphicsPixmapItemPrivate *thatD = const_cast(d); + thatD->updateShape(); + thatD->hasShape = true; + } + return d_func()->shape; +} + +/*! + \reimp +*/ +bool QGraphicsPixmapItem::contains(const QPointF &point) const +{ + return QGraphicsItem::contains(point); +} + +/*! + \reimp +*/ +void QGraphicsPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_D(QGraphicsPixmapItem); + Q_UNUSED(widget); + + painter->setRenderHint(QPainter::SmoothPixmapTransform, + (d->transformationMode == Qt::SmoothTransformation)); + + painter->drawPixmap(d->offset, d->pixmap); + + if (option->state & QStyle::State_Selected) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsPixmapItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsPixmapItem::opaqueArea() const +{ + return shape(); +} + +/*! + \reimp +*/ +int QGraphicsPixmapItem::type() const +{ + return Type; +} + +/*! + Returns the item's shape mode. The shape mode describes how + QGraphicsPixmapItem calculates its shape. The default mode is MaskShape. + + \sa setShapeMode(), ShapeMode +*/ +QGraphicsPixmapItem::ShapeMode QGraphicsPixmapItem::shapeMode() const +{ + return d_func()->shapeMode; +} + +/*! + Sets the item's shape mode to \a mode. The shape mode describes how + QGraphicsPixmapItem calculates its shape. The default mode is MaskShape. + + \sa shapeMode(), ShapeMode +*/ +void QGraphicsPixmapItem::setShapeMode(ShapeMode mode) +{ + Q_D(QGraphicsPixmapItem); + if (d->shapeMode == mode) + return; + d->shapeMode = mode; + d->hasShape = false; +} + +/*! + \internal +*/ +bool QGraphicsPixmapItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsPixmapItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsPixmapItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsTextItem + \brief The QGraphicsTextItem class provides a text item that you can add to + a QGraphicsScene to display formatted text. + \since 4.2 + \ingroup graphicsview-api + + If you only need to show plain text in an item, consider using QGraphicsSimpleTextItem + instead. + + To set the item's text, pass a QString to QGraphicsTextItem's + constructor, or call setHtml()/setPlainText(). + + QGraphicsTextItem uses the text's formatted size and the associated font + to provide a reasonable implementation of boundingRect(), shape(), + and contains(). You can set the font by calling setFont(). + + It is possible to make the item editable by setting the Qt::TextEditorInteraction flag + using setTextInteractionFlags(). + + The item's preferred text width can be set using setTextWidth() and obtained + using textWidth(). + + \note In order to align HTML text in the center, the item's text width must be set. + + \img graphicsview-textitem.png + + \note QGraphicsTextItem accepts \l{QGraphicsItem::acceptHoverEvents()}{hover events} + by default. You can change this with \l{QGraphicsItem::}{setAcceptHoverEvents()}. + + \sa QGraphicsSimpleTextItem, QGraphicsPathItem, QGraphicsRectItem, + QGraphicsEllipseItem, QGraphicsPixmapItem, QGraphicsPolygonItem, + QGraphicsLineItem, {Graphics View Framework} +*/ + +class QGraphicsTextItemPrivate +{ +public: + QGraphicsTextItemPrivate() + : control(0), pageNumber(0), useDefaultImpl(false), tabChangesFocus(false), clickCausedFocus(0) + { } + + mutable QTextControl *control; + QTextControl *textControl() const; + + inline QPointF controlOffset() const + { return QPointF(0., pageNumber * control->document()->pageSize().height()); } + inline void sendControlEvent(QEvent *e) + { if (control) control->processEvent(e, controlOffset()); } + + void _q_updateBoundingRect(const QSizeF &); + void _q_update(QRectF); + void _q_ensureVisible(QRectF); + bool _q_mouseOnEdge(QGraphicsSceneMouseEvent *); + + QRectF boundingRect; + int pageNumber; + bool useDefaultImpl; + bool tabChangesFocus; + + uint clickCausedFocus : 1; + + QGraphicsTextItem *qq; +}; + + +/*! + Constructs a QGraphicsTextItem, using \a text as the default plain + text. \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsTextItem::QGraphicsTextItem(const QString &text, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsObject(*new QGraphicsItemPrivate, parent, scene), dd(new QGraphicsTextItemPrivate) +{ + dd->qq = this; + if (!text.isEmpty()) + setPlainText(text); + setAcceptDrops(true); + setAcceptHoverEvents(true); + setFlags(ItemUsesExtendedStyleOption); +} + +/*! + Constructs a QGraphicsTextItem. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsTextItem::QGraphicsTextItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsObject(*new QGraphicsItemPrivate, parent, scene), dd(new QGraphicsTextItemPrivate) +{ + dd->qq = this; + setAcceptDrops(true); + setAcceptHoverEvents(true); + setFlag(ItemUsesExtendedStyleOption); +} + +/*! + Destroys the QGraphicsTextItem. +*/ +QGraphicsTextItem::~QGraphicsTextItem() +{ + delete dd; +} + +/*! + Returns the item's text converted to HTML, or an empty QString if no text has been set. + + \sa setHtml() +*/ +QString QGraphicsTextItem::toHtml() const +{ +#ifndef QT_NO_TEXTHTMLPARSER + if (dd->control) + return dd->control->toHtml(); +#endif + return QString(); +} + +/*! + Sets the item's text to \a text, assuming that text is HTML formatted. If + the item has keyboard input focus, this function will also call + ensureVisible() to ensure that the text is visible in all viewports. + + \sa toHtml(), hasFocus(), QGraphicsSimpleTextItem +*/ +void QGraphicsTextItem::setHtml(const QString &text) +{ + dd->textControl()->setHtml(text); +} + +/*! + Returns the item's text converted to plain text, or an empty QString if no text has been set. + + \sa setPlainText() +*/ +QString QGraphicsTextItem::toPlainText() const +{ + if (dd->control) + return dd->control->toPlainText(); + return QString(); +} + +/*! + Sets the item's text to \a text. If the item has keyboard input focus, + this function will also call ensureVisible() to ensure that the text is + visible in all viewports. + + \sa toHtml(), hasFocus() +*/ +void QGraphicsTextItem::setPlainText(const QString &text) +{ + dd->textControl()->setPlainText(text); +} + +/*! + Returns the item's font, which is used to render the text. + + \sa setFont() +*/ +QFont QGraphicsTextItem::font() const +{ + if (!dd->control) + return QFont(); + return dd->control->document()->defaultFont(); +} + +/*! + Sets the font used to render the text item to \a font. + + \sa font() +*/ +void QGraphicsTextItem::setFont(const QFont &font) +{ + dd->textControl()->document()->setDefaultFont(font); +} + +/*! + Sets the color for unformatted text to \a col. +*/ +void QGraphicsTextItem::setDefaultTextColor(const QColor &col) +{ + QTextControl *c = dd->textControl(); + QPalette pal = c->palette(); + QColor old = pal.color(QPalette::Text); + pal.setColor(QPalette::Text, col); + c->setPalette(pal); + if (old != col) + update(); +} + +/*! + Returns the default text color that is used to for unformatted text. +*/ +QColor QGraphicsTextItem::defaultTextColor() const +{ + return dd->textControl()->palette().color(QPalette::Text); +} + +/*! + \reimp +*/ +QRectF QGraphicsTextItem::boundingRect() const +{ + return dd->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsTextItem::shape() const +{ + if (!dd->control) + return QPainterPath(); + QPainterPath path; + path.addRect(dd->boundingRect); + return path; +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::contains(const QPointF &point) const +{ + return dd->boundingRect.contains(point); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(widget); + if (dd->control) { + painter->save(); + QRectF r = option->exposedRect; + painter->translate(-dd->controlOffset()); + r.translate(dd->controlOffset()); + + QTextDocument *doc = dd->control->document(); + QTextDocumentLayout *layout = qobject_cast(doc->documentLayout()); + + // the layout might need to expand the root frame to + // the viewport if NoWrap is set + if (layout) + layout->setViewport(dd->boundingRect); + + dd->control->drawContents(painter, r); + + if (layout) + layout->setViewport(QRect()); + + painter->restore(); + } + + if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus)) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsTextItem::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsTextItem::type() const +{ + return Type; +} + +/*! + Sets the preferred width for the item's text. If the actual text + is wider than the specified width then it will be broken into + multiple lines. + + If \a width is set to -1 then the text will not be broken into + multiple lines unless it is enforced through an explicit line + break or a new paragraph. + + The default value is -1. + + Note that QGraphicsTextItem keeps a QTextDocument internally, + which is used to calculate the text width. + + \sa textWidth(), QTextDocument::setTextWidth() +*/ +void QGraphicsTextItem::setTextWidth(qreal width) +{ + dd->textControl()->setTextWidth(width); +} + +/*! + Returns the text width. + + The width is calculated with the QTextDocument that + QGraphicsTextItem keeps internally. + + \sa setTextWidth(), QTextDocument::textWidth() +*/ +qreal QGraphicsTextItem::textWidth() const +{ + if (!dd->control) + return -1; + return dd->control->textWidth(); +} + +/*! + Adjusts the text item to a reasonable size. +*/ +void QGraphicsTextItem::adjustSize() +{ + if (dd->control) + dd->control->adjustSize(); +} + +/*! + Sets the text document \a document on the item. +*/ +void QGraphicsTextItem::setDocument(QTextDocument *document) +{ + dd->textControl()->setDocument(document); + dd->_q_updateBoundingRect(dd->control->size()); +} + +/*! + Returns the item's text document. +*/ +QTextDocument *QGraphicsTextItem::document() const +{ + return dd->textControl()->document(); +} + +/*! + \reimp +*/ +bool QGraphicsTextItem::sceneEvent(QEvent *event) +{ + QEvent::Type t = event->type(); + if (!dd->tabChangesFocus && (t == QEvent::KeyPress || t == QEvent::KeyRelease)) { + int k = ((QKeyEvent *)event)->key(); + if (k == Qt::Key_Tab || k == Qt::Key_Backtab) { + dd->sendControlEvent(event); + return true; + } + } + bool result = QGraphicsItem::sceneEvent(event); + + // Ensure input context is updated. + switch (event->type()) { + case QEvent::ContextMenu: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDrop: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::KeyPress: + case QEvent::KeyRelease: + // Reset the focus widget's input context, regardless + // of how this item gained or lost focus. + if (QWidget *fw = qApp->focusWidget()) { +#ifndef QT_NO_IM + if (QInputContext *qic = fw->inputContext()) { + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) + qic->reset(); + else + qic->update(); + } +#endif //QT_NO_IM + } + break; + case QEvent::ShortcutOverride: + dd->sendControlEvent(event); + return true; + default: + break; + } + + return result; +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if ((QGraphicsItem::d_ptr->flags & (ItemIsSelectable | ItemIsMovable)) + && (event->buttons() & Qt::LeftButton) && dd->_q_mouseOnEdge(event)) { + // User left-pressed on edge of selectable/movable item, use + // base impl. + dd->useDefaultImpl = true; + } else if (event->buttons() == event->button() + && dd->control->textInteractionFlags() == Qt::NoTextInteraction) { + // User pressed first button on non-interactive item. + dd->useDefaultImpl = true; + } + if (dd->useDefaultImpl) { + QGraphicsItem::mousePressEvent(event); + if (!event->isAccepted()) + dd->useDefaultImpl = false; + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseMoveEvent(event); + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseReleaseEvent(event); + if (dd->control->textInteractionFlags() == Qt::NoTextInteraction + && !event->buttons()) { + // User released last button on non-interactive item. + dd->useDefaultImpl = false; + } else if ((event->buttons() & Qt::LeftButton) == 0) { + // User released the left button on an interactive item. + dd->useDefaultImpl = false; + } + return; + } + + QWidget *widget = event->widget(); + if (widget && (dd->control->textInteractionFlags() & Qt::TextEditable) && boundingRect().contains(event->pos())) { + qt_widget_private(widget)->handleSoftwareInputPanel(event->button(), dd->clickCausedFocus); + } + dd->clickCausedFocus = 0; + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (dd->useDefaultImpl) { + QGraphicsItem::mouseDoubleClickEvent(event); + return; + } + + if (!hasFocus()) { + QGraphicsItem::mouseDoubleClickEvent(event); + return; + } + + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::keyPressEvent(QKeyEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::keyReleaseEvent(QKeyEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::focusInEvent(QFocusEvent *event) +{ + dd->sendControlEvent(event); + if (event->reason() == Qt::MouseFocusReason) { + dd->clickCausedFocus = 1; + } + update(); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::focusOutEvent(QFocusEvent *event) +{ + dd->sendControlEvent(event); + update(); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::inputMethodEvent(QInputMethodEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsTextItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + dd->sendControlEvent(event); +} + +/*! + \reimp +*/ +QVariant QGraphicsTextItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + QVariant v; + if (dd->control) + v = dd->control->inputMethodQuery(query); + if (v.type() == QVariant::RectF) + v = v.toRectF().translated(-dd->controlOffset()); + else if (v.type() == QVariant::PointF) + v = v.toPointF() - dd->controlOffset(); + else if (v.type() == QVariant::Rect) + v = v.toRect().translated(-dd->controlOffset().toPoint()); + else if (v.type() == QVariant::Point) + v = v.toPoint() - dd->controlOffset().toPoint(); + return v; +} + +/*! + \internal +*/ +bool QGraphicsTextItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsTextItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsTextItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_update(QRectF rect) +{ + if (rect.isValid()) { + rect.translate(-controlOffset()); + } else { + rect = boundingRect; + } + if (rect.intersects(boundingRect)) + qq->update(rect); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_updateBoundingRect(const QSizeF &size) +{ + if (!control) return; // can't happen + const QSizeF pageSize = control->document()->pageSize(); + // paged items have a constant (page) size + if (size == boundingRect.size() || pageSize.height() != -1) + return; + qq->prepareGeometryChange(); + boundingRect.setSize(size); + qq->update(); +} + +/*! + \internal +*/ +void QGraphicsTextItemPrivate::_q_ensureVisible(QRectF rect) +{ + if (qq->hasFocus()) { + rect.translate(-controlOffset()); + qq->ensureVisible(rect, /*xmargin=*/0, /*ymargin=*/0); + } +} + +QTextControl *QGraphicsTextItemPrivate::textControl() const +{ + if (!control) { + QGraphicsTextItem *that = const_cast(qq); + control = new QTextControl(that); + control->setTextInteractionFlags(Qt::NoTextInteraction); + + QObject::connect(control, SIGNAL(updateRequest(QRectF)), + qq, SLOT(_q_update(QRectF))); + QObject::connect(control, SIGNAL(documentSizeChanged(QSizeF)), + qq, SLOT(_q_updateBoundingRect(QSizeF))); + QObject::connect(control, SIGNAL(visibilityRequest(QRectF)), + qq, SLOT(_q_ensureVisible(QRectF))); + QObject::connect(control, SIGNAL(linkActivated(QString)), + qq, SIGNAL(linkActivated(QString))); + QObject::connect(control, SIGNAL(linkHovered(QString)), + qq, SIGNAL(linkHovered(QString))); + + const QSizeF pgSize = control->document()->pageSize(); + if (pgSize.height() != -1) { + qq->prepareGeometryChange(); + that->dd->boundingRect.setSize(pgSize); + qq->update(); + } else { + that->dd->_q_updateBoundingRect(control->size()); + } + } + return control; +} + +/*! + \internal +*/ +bool QGraphicsTextItemPrivate::_q_mouseOnEdge(QGraphicsSceneMouseEvent *event) +{ + QPainterPath path; + path.addRect(qq->boundingRect()); + + QPainterPath docPath; + const QTextFrameFormat format = control->document()->rootFrame()->frameFormat(); + docPath.addRect( + qq->boundingRect().adjusted( + format.leftMargin(), + format.topMargin(), + -format.rightMargin(), + -format.bottomMargin())); + + return path.subtracted(docPath).contains(event->pos()); +} + +/*! + \fn QGraphicsTextItem::linkActivated(const QString &link) + + This signal is emitted when the user clicks on a link on a text item + that enables Qt::LinksAccessibleByMouse or Qt::LinksAccessibleByKeyboard. + \a link is the link that was clicked. + + \sa setTextInteractionFlags() +*/ + +/*! + \fn QGraphicsTextItem::linkHovered(const QString &link) + + This signal is emitted when the user hovers over a link on a text item + that enables Qt::LinksAccessibleByMouse. \a link is + the link that was hovered over. + + \sa setTextInteractionFlags() +*/ + +/*! + Sets the flags \a flags to specify how the text item should react to user + input. + + The default for a QGraphicsTextItem is Qt::NoTextInteraction. This function + also affects the ItemIsFocusable QGraphicsItem flag by setting it if \a flags + is different from Qt::NoTextInteraction and clearing it otherwise. + + By default, the text is read-only. To transform the item into an editor, + set the Qt::TextEditable flag. +*/ +void QGraphicsTextItem::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + if (flags == Qt::NoTextInteraction) + setFlags(this->flags() & ~(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemAcceptsInputMethod)); + else + setFlags(this->flags() | QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemAcceptsInputMethod); + + dd->textControl()->setTextInteractionFlags(flags); +} + +/*! + Returns the current text interaction flags. + + \sa setTextInteractionFlags() +*/ +Qt::TextInteractionFlags QGraphicsTextItem::textInteractionFlags() const +{ + if (!dd->control) + return Qt::NoTextInteraction; + return dd->control->textInteractionFlags(); +} + +/*! + \since 4.5 + + If \a b is true, the \gui Tab key will cause the widget to change focus; + otherwise, the tab key will insert a tab into the document. + + In some occasions text edits should not allow the user to input tabulators + or change indentation using the \gui Tab key, as this breaks the focus + chain. The default is false. + + \sa tabChangesFocus(), ItemIsFocusable, textInteractionFlags() +*/ +void QGraphicsTextItem::setTabChangesFocus(bool b) +{ + dd->tabChangesFocus = b; +} + +/*! + \since 4.5 + + Returns true if the \gui Tab key will cause the widget to change focus; + otherwise, false is returned. + + By default, this behavior is disabled, and this function will return false. + + \sa setTabChangesFocus() +*/ +bool QGraphicsTextItem::tabChangesFocus() const +{ + return dd->tabChangesFocus; +} + +/*! + \property QGraphicsTextItem::openExternalLinks + + Specifies whether QGraphicsTextItem should automatically open links using + QDesktopServices::openUrl() instead of emitting the + linkActivated signal. + + The default value is false. +*/ +void QGraphicsTextItem::setOpenExternalLinks(bool open) +{ + dd->textControl()->setOpenExternalLinks(open); +} + +bool QGraphicsTextItem::openExternalLinks() const +{ + if (!dd->control) + return false; + return dd->control->openExternalLinks(); +} + +/*! + \property QGraphicsTextItem::textCursor + + This property represents the visible text cursor in an editable + text item. + + By default, if the item's text has not been set, this property + contains a null text cursor; otherwise it contains a text cursor + placed at the start of the item's document. +*/ +void QGraphicsTextItem::setTextCursor(const QTextCursor &cursor) +{ + dd->textControl()->setTextCursor(cursor); +} + +QTextCursor QGraphicsTextItem::textCursor() const +{ + if (!dd->control) + return QTextCursor(); + return dd->control->textCursor(); +} + +class QGraphicsSimpleTextItemPrivate : public QAbstractGraphicsShapeItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSimpleTextItem) +public: + inline QGraphicsSimpleTextItemPrivate() { + pen.setStyle(Qt::NoPen); + brush.setStyle(Qt::SolidPattern); + } + QString text; + QFont font; + QRectF boundingRect; + + void updateBoundingRect(); +}; + +static QRectF setupTextLayout(QTextLayout *layout) +{ + layout->setCacheEnabled(true); + layout->beginLayout(); + while (layout->createLine().isValid()) + ; + layout->endLayout(); + qreal maxWidth = 0; + qreal y = 0; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + maxWidth = qMax(maxWidth, line.naturalTextWidth()); + line.setPosition(QPointF(0, y)); + y += line.height(); + } + return QRectF(0, 0, maxWidth, y); +} + +void QGraphicsSimpleTextItemPrivate::updateBoundingRect() +{ + Q_Q(QGraphicsSimpleTextItem); + QRectF br; + if (text.isEmpty()) { + br = QRectF(); + } else { + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + QStackTextEngine engine(tmp, font); + QTextLayout layout(&engine); + br = setupTextLayout(&layout); + } + if (br != boundingRect) { + q->prepareGeometryChange(); + boundingRect = br; + q->update(); + } +} + +/*! + \class QGraphicsSimpleTextItem + \brief The QGraphicsSimpleTextItem class provides a simple text path item + that you can add to a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + To set the item's text, you can either pass a QString to + QGraphicsSimpleTextItem's constructor, or call setText() to change the + text later. To set the text fill color, call setBrush(). + + The simple text item can have both a fill and an outline; setBrush() will + set the text fill (i.e., text color), and setPen() sets the pen that will + be used to draw the text outline. (The latter can be slow, especially for + complex pens, and items with long text content.) If all you want is to + draw a simple line of text, you should call setBrush() only, and leave the + pen unset; QGraphicsSimpleTextItem's pen is by default Qt::NoPen. + + QGraphicsSimpleTextItem uses the text's formatted size and the associated + font to provide a reasonable implementation of boundingRect(), shape(), + and contains(). You can set the font by calling setFont(). + + QGraphicsSimpleText does not display rich text; instead, you can use + QGraphicsTextItem, which provides full text control capabilities. + + \img graphicsview-simpletextitem.png + + \sa QGraphicsTextItem, QGraphicsPathItem, QGraphicsRectItem, + QGraphicsEllipseItem, QGraphicsPixmapItem, QGraphicsPolygonItem, + QGraphicsLineItem, {Graphics View Framework} +*/ + +/*! + Constructs a QGraphicsSimpleTextItem. + + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsSimpleTextItem::QGraphicsSimpleTextItem(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsSimpleTextItemPrivate, parent, scene) +{ +} + +/*! + Constructs a QGraphicsSimpleTextItem, using \a text as the default plain text. + + \a parent is passed to QGraphicsItem's constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsSimpleTextItem::QGraphicsSimpleTextItem(const QString &text, QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QAbstractGraphicsShapeItem(*new QGraphicsSimpleTextItemPrivate, parent, scene) +{ + setText(text); +} + +/*! + Destroys the QGraphicsSimpleTextItem. +*/ +QGraphicsSimpleTextItem::~QGraphicsSimpleTextItem() +{ +} + +/*! + Sets the item's text to \a text. The text will be displayed as + plain text. Newline characters ('\n') as well as characters of + type QChar::LineSeparator will cause item to break the text into + multiple lines. +*/ +void QGraphicsSimpleTextItem::setText(const QString &text) +{ + Q_D(QGraphicsSimpleTextItem); + if (d->text == text) + return; + d->text = text; + d->updateBoundingRect(); + update(); +} + +/*! + Returns the item's text. +*/ +QString QGraphicsSimpleTextItem::text() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->text; +} + +/*! + Sets the font that is used to draw the item's text to \a font. +*/ +void QGraphicsSimpleTextItem::setFont(const QFont &font) +{ + Q_D(QGraphicsSimpleTextItem); + d->font = font; + d->updateBoundingRect(); +} + +/*! + Returns the font that is used to draw the item's text. +*/ +QFont QGraphicsSimpleTextItem::font() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->font; +} + +/*! + \reimp +*/ +QRectF QGraphicsSimpleTextItem::boundingRect() const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->boundingRect; +} + +/*! + \reimp +*/ +QPainterPath QGraphicsSimpleTextItem::shape() const +{ + Q_D(const QGraphicsSimpleTextItem); + QPainterPath path; + path.addRect(d->boundingRect); + return path; +} + +/*! + \reimp +*/ +bool QGraphicsSimpleTextItem::contains(const QPointF &point) const +{ + Q_D(const QGraphicsSimpleTextItem); + return d->boundingRect.contains(point); +} + +/*! + \reimp +*/ +void QGraphicsSimpleTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(widget); + Q_D(QGraphicsSimpleTextItem); + + painter->setFont(d->font); + + QString tmp = d->text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + QStackTextEngine engine(tmp, d->font); + QTextLayout layout(&engine); + setupTextLayout(&layout); + + QPen p; + p.setBrush(d->brush); + painter->setPen(p); + if (d->pen.style() == Qt::NoPen && d->brush.style() == Qt::SolidPattern) { + painter->setBrush(Qt::NoBrush); + } else { + QTextLayout::FormatRange range; + range.start = 0; + range.length = layout.text().length(); + range.format.setTextOutline(d->pen); + QList formats; + formats.append(range); + layout.setAdditionalFormats(formats); + } + + layout.draw(painter, QPointF(0, 0)); + + if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus)) + qt_graphicsItem_highlightSelected(this, painter, option); +} + +/*! + \reimp +*/ +bool QGraphicsSimpleTextItem::isObscuredBy(const QGraphicsItem *item) const +{ + return QAbstractGraphicsShapeItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsSimpleTextItem::opaqueArea() const +{ + return QAbstractGraphicsShapeItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsSimpleTextItem::type() const +{ + return Type; +} + +/*! + \internal +*/ +bool QGraphicsSimpleTextItem::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension); + return false; +} + +/*! + \internal +*/ +void QGraphicsSimpleTextItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +/*! + \internal +*/ +QVariant QGraphicsSimpleTextItem::extension(const QVariant &variant) const +{ + Q_UNUSED(variant); + return QVariant(); +} + +/*! + \class QGraphicsItemGroup + \brief The QGraphicsItemGroup class provides a container that treats + a group of items as a single item. + \since 4.2 + \ingroup graphicsview-api + + A QGraphicsItemGroup is a special type of compound item that + treats itself and all its children as one item (i.e., all events + and geometries for all children are merged together). It's common + to use item groups in presentation tools, when the user wants to + group several smaller items into one big item in order to simplify + moving and copying of items. + + If all you want is to store items inside other items, you can use + any QGraphicsItem directly by passing a suitable parent to + setParentItem(). + + The boundingRect() function of QGraphicsItemGroup returns the + bounding rectangle of all items in the item group. + QGraphicsItemGroup ignores the ItemIgnoresTransformations flag on + its children (i.e., with respect to the geometry of the group + item, the children are treated as if they were transformable). + + There are two ways to construct an item group. The easiest and + most common approach is to pass a list of items (e.g., all + selected items) to QGraphicsScene::createItemGroup(), which + returns a new QGraphicsItemGroup item. The other approach is to + manually construct a QGraphicsItemGroup item, add it to the scene + calling QGraphicsScene::addItem(), and then add items to the group + manually, one at a time by calling addToGroup(). To dismantle + ("ungroup") an item group, you can either call + QGraphicsScene::destroyItemGroup(), or you can manually remove all + items from the group by calling removeFromGroup(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsitem.cpp 17 + + The operation of adding and removing items preserves the items' + scene-relative position and transformation, as opposed to calling + setParentItem(), where only the child item's parent-relative + position and transformation are kept. + + The addtoGroup() function reparents the target item to this item + group, keeping the item's position and transformation intact + relative to the scene. Visually, this means that items added via + addToGroup() will remain completely unchanged as a result of this + operation, regardless of the item or the group's current position + or transformation; although the item's position and matrix are + likely to change. + + The removeFromGroup() function has similar semantics to + setParentItem(); it reparents the item to the parent item of the + item group. As with addToGroup(), the item's scene-relative + position and transformation remain intact. + + \sa QGraphicsItem, {Graphics View Framework} +*/ + +class QGraphicsItemGroupPrivate : public QGraphicsItemPrivate +{ +public: + QRectF itemsBoundingRect; +}; + +/*! + Constructs a QGraphicsItemGroup. \a parent is passed to QGraphicsItem's + constructor. + + \sa QGraphicsScene::addItem() +*/ +QGraphicsItemGroup::QGraphicsItemGroup(QGraphicsItem *parent +#ifndef Q_QDOC + // obsolete argument + , QGraphicsScene *scene +#endif + ) + : QGraphicsItem(*new QGraphicsItemGroupPrivate, parent, scene) +{ + setHandlesChildEvents(true); +} + +/*! + Destroys the QGraphicsItemGroup. +*/ +QGraphicsItemGroup::~QGraphicsItemGroup() +{ +} + +/*! + Adds the given \a item to this item group. The item will be + reparented to this group, but its position and transformation + relative to the scene will stay intact. + + \sa removeFromGroup(), QGraphicsScene::createItemGroup() +*/ +void QGraphicsItemGroup::addToGroup(QGraphicsItem *item) +{ + Q_D(QGraphicsItemGroup); + if (!item) { + qWarning("QGraphicsItemGroup::addToGroup: cannot add null item"); + return; + } + if (item == this) { + qWarning("QGraphicsItemGroup::addToGroup: cannot add a group to itself"); + return; + } + + // COMBINE + bool ok; + QTransform itemTransform = item->itemTransform(this, &ok); + + if (!ok) { + qWarning("QGraphicsItemGroup::addToGroup: could not find a valid transformation from item to group coordinates"); + return; + } + + QTransform newItemTransform(itemTransform); + item->setPos(mapFromItem(item, 0, 0)); + item->setParentItem(this); + + // removing position from translation component of the new transform + if (!item->pos().isNull()) + newItemTransform *= QTransform::fromTranslate(-item->x(), -item->y()); + + // removing additional transformations properties applied with itemTransform() + QPointF origin = item->transformOriginPoint(); + QMatrix4x4 m; + QList transformList = item->transformations(); + for (int i = 0; i < transformList.size(); ++i) + transformList.at(i)->applyTo(&m); + newItemTransform *= m.toTransform().inverted(); + newItemTransform.translate(origin.x(), origin.y()); + newItemTransform.rotate(-item->rotation()); + newItemTransform.scale(1/item->scale(), 1/item->scale()); + newItemTransform.translate(-origin.x(), -origin.y()); + + // ### Expensive, we could maybe use dirtySceneTransform bit for optimization + + item->setTransform(newItemTransform); + item->d_func()->setIsMemberOfGroup(true); + prepareGeometryChange(); + d->itemsBoundingRect |= itemTransform.mapRect(item->boundingRect() | item->childrenBoundingRect()); + update(); +} + +/*! + Removes the specified \a item from this group. The item will be + reparented to this group's parent item, or to 0 if this group has + no parent. Its position and transformation relative to the scene + will stay intact. + + \sa addToGroup(), QGraphicsScene::destroyItemGroup() +*/ +void QGraphicsItemGroup::removeFromGroup(QGraphicsItem *item) +{ + Q_D(QGraphicsItemGroup); + if (!item) { + qWarning("QGraphicsItemGroup::removeFromGroup: cannot remove null item"); + return; + } + + QGraphicsItem *newParent = d_ptr->parent; + + // COMBINE + bool ok; + QTransform itemTransform; + if (newParent) + itemTransform = item->itemTransform(newParent, &ok); + else + itemTransform = item->sceneTransform(); + + QPointF oldPos = item->mapToItem(newParent, 0, 0); + item->setParentItem(newParent); + item->setPos(oldPos); + + // removing position from translation component of the new transform + if (!item->pos().isNull()) + itemTransform *= QTransform::fromTranslate(-item->x(), -item->y()); + + // removing additional transformations properties applied + // with itemTransform() or sceneTransform() + QPointF origin = item->transformOriginPoint(); + QMatrix4x4 m; + QList transformList = item->transformations(); + for (int i = 0; i < transformList.size(); ++i) + transformList.at(i)->applyTo(&m); + itemTransform *= m.toTransform().inverted(); + itemTransform.translate(origin.x(), origin.y()); + itemTransform.rotate(-item->rotation()); + itemTransform.scale(1 / item->scale(), 1 / item->scale()); + itemTransform.translate(-origin.x(), -origin.y()); + + // ### Expensive, we could maybe use dirtySceneTransform bit for optimization + + item->setTransform(itemTransform); + item->d_func()->setIsMemberOfGroup(item->group() != 0); + + // ### Quite expensive. But removeFromGroup() isn't called very often. + prepareGeometryChange(); + d->itemsBoundingRect = childrenBoundingRect(); +} + +/*! + \reimp + + Returns the bounding rect of this group item, and all its children. +*/ +QRectF QGraphicsItemGroup::boundingRect() const +{ + Q_D(const QGraphicsItemGroup); + return d->itemsBoundingRect; +} + +/*! + \reimp +*/ +void QGraphicsItemGroup::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + Q_UNUSED(widget); + if (option->state & QStyle::State_Selected) { + Q_D(QGraphicsItemGroup); + painter->setBrush(Qt::NoBrush); + painter->drawRect(d->itemsBoundingRect); + } +} + +/*! + \reimp +*/ +bool QGraphicsItemGroup::isObscuredBy(const QGraphicsItem *item) const +{ + return QGraphicsItem::isObscuredBy(item); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsItemGroup::opaqueArea() const +{ + return QGraphicsItem::opaqueArea(); +} + +/*! + \reimp +*/ +int QGraphicsItemGroup::type() const +{ + return Type; +} + +#ifndef QT_NO_GRAPHICSEFFECT +QRectF QGraphicsItemEffectSourcePrivate::boundingRect(Qt::CoordinateSystem system) const +{ + const bool deviceCoordinates = (system == Qt::DeviceCoordinates); + if (!info && deviceCoordinates) { + // Device coordinates without info not yet supported. + qWarning("QGraphicsEffectSource::boundingRect: Not yet implemented, lacking device context"); + return QRectF(); + } + + QRectF rect = item->boundingRect(); + if (!item->d_ptr->children.isEmpty()) + rect |= item->childrenBoundingRect(); + + if (deviceCoordinates) { + Q_ASSERT(info->painter); + rect = info->painter->worldTransform().mapRect(rect); + } + + return rect; +} + +void QGraphicsItemEffectSourcePrivate::draw(QPainter *painter) +{ + if (!info) { + qWarning("QGraphicsEffectSource::draw: Can only begin as a result of QGraphicsEffect::draw"); + return; + } + + Q_ASSERT(item->d_ptr->scene); + QGraphicsScenePrivate *scened = item->d_ptr->scene->d_func(); + if (painter == info->painter) { + scened->draw(item, painter, info->viewTransform, info->transformPtr, info->exposedRegion, + info->widget, info->opacity, info->effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } else { + QTransform effectTransform = info->painter->worldTransform().inverted(); + effectTransform *= painter->worldTransform(); + scened->draw(item, painter, info->viewTransform, info->transformPtr, info->exposedRegion, + info->widget, info->opacity, &effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } +} + +// sourceRect must be in the given coordinate system +QRect QGraphicsItemEffectSourcePrivate::paddedEffectRect(Qt::CoordinateSystem system, QGraphicsEffect::PixmapPadMode mode, const QRectF &sourceRect, bool *unpadded) const +{ + QRectF effectRectF; + + if (unpadded) + *unpadded = false; + + if (mode == QGraphicsEffect::PadToEffectiveBoundingRect) { + if (info) { + QRectF deviceRect = system == Qt::DeviceCoordinates ? sourceRect : info->painter->worldTransform().mapRect(sourceRect); + effectRectF = item->graphicsEffect()->boundingRectFor(deviceRect); + if (unpadded) + *unpadded = (effectRectF.size() == sourceRect.size()); + if (info && system == Qt::LogicalCoordinates) + effectRectF = info->painter->worldTransform().inverted().mapRect(effectRectF); + } else { + // no choice but to send a logical coordinate bounding rect to boundingRectFor + effectRectF = item->graphicsEffect()->boundingRectFor(sourceRect); + } + } else if (mode == QGraphicsEffect::PadToTransparentBorder) { + // adjust by 1.5 to account for cosmetic pens + effectRectF = sourceRect.adjusted(-1.5, -1.5, 1.5, 1.5); + } else { + effectRectF = sourceRect; + if (unpadded) + *unpadded = true; + } + + return effectRectF.toAlignedRect(); +} + +QPixmap QGraphicsItemEffectSourcePrivate::pixmap(Qt::CoordinateSystem system, QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const +{ + const bool deviceCoordinates = (system == Qt::DeviceCoordinates); + if (!info && deviceCoordinates) { + // Device coordinates without info not yet supported. + qWarning("QGraphicsEffectSource::pixmap: Not yet implemented, lacking device context"); + return QPixmap(); + } + if (!item->d_ptr->scene) + return QPixmap(); + QGraphicsScenePrivate *scened = item->d_ptr->scene->d_func(); + + bool unpadded; + const QRectF sourceRect = boundingRect(system); + QRect effectRect = paddedEffectRect(system, mode, sourceRect, &unpadded); + + if (offset) + *offset = effectRect.topLeft(); + + bool untransformed = !deviceCoordinates + || info->painter->worldTransform().type() <= QTransform::TxTranslate; + if (untransformed && unpadded && isPixmap()) { + if (offset) + *offset = boundingRect(system).topLeft().toPoint(); + return static_cast(item)->pixmap(); + } + + if (effectRect.isEmpty()) + return QPixmap(); + + QPixmap pixmap(effectRect.size()); + pixmap.fill(Qt::transparent); + QPainter pixmapPainter(&pixmap); + pixmapPainter.setRenderHints(info ? info->painter->renderHints() : QPainter::TextAntialiasing); + + QTransform effectTransform = QTransform::fromTranslate(-effectRect.x(), -effectRect.y()); + if (deviceCoordinates && info->effectTransform) + effectTransform *= *info->effectTransform; + + if (!info) { + // Logical coordinates without info. + QTransform sceneTransform = item->sceneTransform(); + QTransform newEffectTransform = sceneTransform.inverted(); + newEffectTransform *= effectTransform; + scened->draw(item, &pixmapPainter, 0, &sceneTransform, 0, 0, qreal(1.0), + &newEffectTransform, false, true); + } else if (deviceCoordinates) { + // Device coordinates with info. + scened->draw(item, &pixmapPainter, info->viewTransform, info->transformPtr, 0, + info->widget, info->opacity, &effectTransform, info->wasDirtySceneTransform, + info->drawItem); + } else { + // Item coordinates with info. + QTransform newEffectTransform = info->transformPtr->inverted(); + newEffectTransform *= effectTransform; + scened->draw(item, &pixmapPainter, info->viewTransform, info->transformPtr, 0, + info->widget, info->opacity, &newEffectTransform, info->wasDirtySceneTransform, + info->drawItem); + } + + pixmapPainter.end(); + + return pixmap; +} +#endif //QT_NO_GRAPHICSEFFECT + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QGraphicsItem *item) +{ + if (!item) { + debug << "QGraphicsItem(0)"; + return debug; + } + + if (QGraphicsObject *o = item->toGraphicsObject()) + debug << o->metaObject()->className(); + else + debug << "QGraphicsItem"; + debug << "(this =" << (void*)item + << ", parent =" << (void*)item->parentItem() + << ", pos =" << item->pos() + << ", z =" << item->zValue() << ", flags = " + << item->flags() << ")"; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsObject *item) +{ + if (!item) { + debug << "QGraphicsObject(0)"; + return debug; + } + + debug.nospace() << item->metaObject()->className() << '(' << (void*)item; + if (!item->objectName().isEmpty()) + debug << ", name = " << item->objectName(); + debug.nospace() << ", parent = " << ((void*)item->parentItem()) + << ", pos = " << item->pos() + << ", z = " << item->zValue() << ", flags = " + << item->flags() << ')'; + return debug.space(); +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemChange change) +{ + const char *str = "UnknownChange"; + switch (change) { + case QGraphicsItem::ItemChildAddedChange: + str = "ItemChildAddedChange"; + break; + case QGraphicsItem::ItemChildRemovedChange: + str = "ItemChildRemovedChange"; + break; + case QGraphicsItem::ItemCursorChange: + str = "ItemCursorChange"; + break; + case QGraphicsItem::ItemCursorHasChanged: + str = "ItemCursorHasChanged"; + break; + case QGraphicsItem::ItemEnabledChange: + str = "ItemEnabledChange"; + break; + case QGraphicsItem::ItemEnabledHasChanged: + str = "ItemEnabledHasChanged"; + break; + case QGraphicsItem::ItemFlagsChange: + str = "ItemFlagsChange"; + break; + case QGraphicsItem::ItemFlagsHaveChanged: + str = "ItemFlagsHaveChanged"; + break; + case QGraphicsItem::ItemMatrixChange: + str = "ItemMatrixChange"; + break; + case QGraphicsItem::ItemParentChange: + str = "ItemParentChange"; + break; + case QGraphicsItem::ItemParentHasChanged: + str = "ItemParentHasChanged"; + break; + case QGraphicsItem::ItemPositionChange: + str = "ItemPositionChange"; + break; + case QGraphicsItem::ItemPositionHasChanged: + str = "ItemPositionHasChanged"; + break; + case QGraphicsItem::ItemSceneChange: + str = "ItemSceneChange"; + break; + case QGraphicsItem::ItemSceneHasChanged: + str = "ItemSceneHasChanged"; + break; + case QGraphicsItem::ItemSelectedChange: + str = "ItemSelectedChange"; + break; + case QGraphicsItem::ItemSelectedHasChanged: + str = "ItemSelectedHasChanged"; + break; + case QGraphicsItem::ItemToolTipChange: + str = "ItemToolTipChange"; + break; + case QGraphicsItem::ItemToolTipHasChanged: + str = "ItemToolTipHasChanged"; + break; + case QGraphicsItem::ItemTransformChange: + str = "ItemTransformChange"; + break; + case QGraphicsItem::ItemTransformHasChanged: + str = "ItemTransformHasChanged"; + break; + case QGraphicsItem::ItemVisibleChange: + str = "ItemVisibleChange"; + break; + case QGraphicsItem::ItemVisibleHasChanged: + str = "ItemVisibleHasChanged"; + break; + case QGraphicsItem::ItemZValueChange: + str = "ItemZValueChange"; + break; + case QGraphicsItem::ItemZValueHasChanged: + str = "ItemZValueHasChanged"; + break; + case QGraphicsItem::ItemOpacityChange: + str = "ItemOpacityChange"; + break; + case QGraphicsItem::ItemOpacityHasChanged: + str = "ItemOpacityHasChanged"; + break; + case QGraphicsItem::ItemScenePositionHasChanged: + str = "ItemScenePositionHasChanged"; + break; + case QGraphicsItem::ItemRotationChange: + str = "ItemRotationChange"; + break; + case QGraphicsItem::ItemRotationHasChanged: + str = "ItemRotationHasChanged"; + break; + case QGraphicsItem::ItemScaleChange: + str = "ItemScaleChange"; + break; + case QGraphicsItem::ItemScaleHasChanged: + str = "ItemScaleHasChanged"; + break; + case QGraphicsItem::ItemTransformOriginPointChange: + str = "ItemTransformOriginPointChange"; + break; + case QGraphicsItem::ItemTransformOriginPointHasChanged: + str = "ItemTransformOriginPointHasChanged"; + break; + } + debug << str; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag) +{ + const char *str = "UnknownFlag"; + switch (flag) { + case QGraphicsItem::ItemIsMovable: + str = "ItemIsMovable"; + break; + case QGraphicsItem::ItemIsSelectable: + str = "ItemIsSelectable"; + break; + case QGraphicsItem::ItemIsFocusable: + str = "ItemIsFocusable"; + break; + case QGraphicsItem::ItemClipsToShape: + str = "ItemClipsToShape"; + break; + case QGraphicsItem::ItemClipsChildrenToShape: + str = "ItemClipsChildrenToShape"; + break; + case QGraphicsItem::ItemIgnoresTransformations: + str = "ItemIgnoresTransformations"; + break; + case QGraphicsItem::ItemIgnoresParentOpacity: + str = "ItemIgnoresParentOpacity"; + break; + case QGraphicsItem::ItemDoesntPropagateOpacityToChildren: + str = "ItemDoesntPropagateOpacityToChildren"; + break; + case QGraphicsItem::ItemStacksBehindParent: + str = "ItemStacksBehindParent"; + break; + case QGraphicsItem::ItemUsesExtendedStyleOption: + str = "ItemUsesExtendedStyleOption"; + break; + case QGraphicsItem::ItemHasNoContents: + str = "ItemHasNoContents"; + break; + case QGraphicsItem::ItemSendsGeometryChanges: + str = "ItemSendsGeometryChanges"; + break; + case QGraphicsItem::ItemAcceptsInputMethod: + str = "ItemAcceptsInputMethod"; + break; + case QGraphicsItem::ItemNegativeZStacksBehindParent: + str = "ItemNegativeZStacksBehindParent"; + break; + case QGraphicsItem::ItemIsPanel: + str = "ItemIsPanel"; + break; + case QGraphicsItem::ItemIsFocusScope: + str = "ItemIsFocusScope"; + break; + case QGraphicsItem::ItemSendsScenePositionChanges: + str = "ItemSendsScenePositionChanges"; + break; + case QGraphicsItem::ItemStopsClickFocusPropagation: + str = "ItemStopsClickFocusPropagation"; + break; + case QGraphicsItem::ItemStopsFocusHandling: + str = "ItemStopsFocusHandling"; + break; + } + debug << str; + return debug; +} + +QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlags flags) +{ + debug << '('; + bool f = false; + for (int i = 0; i < 17; ++i) { + if (flags & (1 << i)) { + if (f) + debug << '|'; + f = true; + debug << QGraphicsItem::GraphicsItemFlag(int(flags & (1 << i))); + } + } + debug << ')'; + return debug; +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qgraphicsitem.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h new file mode 100644 index 0000000000..67c9cd3e2a --- /dev/null +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -0,0 +1,1172 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEM_H +#define QGRAPHICSITEM_H + +#include +#include +#include +#include +#include +#include +#include + +class tst_QGraphicsItem; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QBrush; +class QCursor; +class QFocusEvent; +class QGraphicsEffect; +class QGraphicsItemGroup; +class QGraphicsObject; +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneDragDropEvent; +class QGraphicsSceneEvent; +class QGraphicsSceneHoverEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QGraphicsScene; +class QGraphicsTransform; +class QGraphicsWidget; +class QInputMethodEvent; +class QKeyEvent; +class QMatrix; +class QMenu; +class QPainter; +class QPen; +class QPointF; +class QRectF; +class QStyleOptionGraphicsItem; + +class QGraphicsItemPrivate; +class Q_GUI_EXPORT QGraphicsItem +{ +public: + enum GraphicsItemFlag { + ItemIsMovable = 0x1, + ItemIsSelectable = 0x2, + ItemIsFocusable = 0x4, + ItemClipsToShape = 0x8, + ItemClipsChildrenToShape = 0x10, + ItemIgnoresTransformations = 0x20, + ItemIgnoresParentOpacity = 0x40, + ItemDoesntPropagateOpacityToChildren = 0x80, + ItemStacksBehindParent = 0x100, + ItemUsesExtendedStyleOption = 0x200, + ItemHasNoContents = 0x400, + ItemSendsGeometryChanges = 0x800, + ItemAcceptsInputMethod = 0x1000, + ItemNegativeZStacksBehindParent = 0x2000, + ItemIsPanel = 0x4000, + ItemIsFocusScope = 0x8000, // internal + ItemSendsScenePositionChanges = 0x10000, + ItemStopsClickFocusPropagation = 0x20000, + ItemStopsFocusHandling = 0x40000 + // NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag. + }; + Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag) + + enum GraphicsItemChange { + ItemPositionChange, + ItemMatrixChange, + ItemVisibleChange, + ItemEnabledChange, + ItemSelectedChange, + ItemParentChange, + ItemChildAddedChange, + ItemChildRemovedChange, + ItemTransformChange, + ItemPositionHasChanged, + ItemTransformHasChanged, + ItemSceneChange, + ItemVisibleHasChanged, + ItemEnabledHasChanged, + ItemSelectedHasChanged, + ItemParentHasChanged, + ItemSceneHasChanged, + ItemCursorChange, + ItemCursorHasChanged, + ItemToolTipChange, + ItemToolTipHasChanged, + ItemFlagsChange, + ItemFlagsHaveChanged, + ItemZValueChange, + ItemZValueHasChanged, + ItemOpacityChange, + ItemOpacityHasChanged, + ItemScenePositionHasChanged, + ItemRotationChange, + ItemRotationHasChanged, + ItemScaleChange, + ItemScaleHasChanged, + ItemTransformOriginPointChange, + ItemTransformOriginPointHasChanged + }; + + enum CacheMode { + NoCache, + ItemCoordinateCache, + DeviceCoordinateCache + }; + + enum PanelModality + { + NonModal, + PanelModal, + SceneModal + }; + + QGraphicsItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + virtual ~QGraphicsItem(); + + QGraphicsScene *scene() const; + + QGraphicsItem *parentItem() const; + QGraphicsItem *topLevelItem() const; + QGraphicsObject *parentObject() const; + QGraphicsWidget *parentWidget() const; + QGraphicsWidget *topLevelWidget() const; + QGraphicsWidget *window() const; + QGraphicsItem *panel() const; + void setParentItem(QGraphicsItem *parent); + QList children() const; // ### obsolete + QList childItems() const; + bool isWidget() const; + bool isWindow() const; + bool isPanel() const; + + QGraphicsObject *toGraphicsObject(); + const QGraphicsObject *toGraphicsObject() const; + + QGraphicsItemGroup *group() const; + void setGroup(QGraphicsItemGroup *group); + + GraphicsItemFlags flags() const; + void setFlag(GraphicsItemFlag flag, bool enabled = true); + void setFlags(GraphicsItemFlags flags); + + CacheMode cacheMode() const; + void setCacheMode(CacheMode mode, const QSize &cacheSize = QSize()); + + PanelModality panelModality() const; + void setPanelModality(PanelModality panelModality); + bool isBlockedByModalPanel(QGraphicsItem **blockingPanel = 0) const; + +#ifndef QT_NO_TOOLTIP + QString toolTip() const; + void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_CURSOR + QCursor cursor() const; + void setCursor(const QCursor &cursor); + bool hasCursor() const; + void unsetCursor(); +#endif + + bool isVisible() const; + bool isVisibleTo(const QGraphicsItem *parent) const; + void setVisible(bool visible); + inline void hide() { setVisible(false); } + inline void show() { setVisible(true); } + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isSelected() const; + void setSelected(bool selected); + + bool acceptDrops() const; + void setAcceptDrops(bool on); + + qreal opacity() const; + qreal effectiveOpacity() const; + void setOpacity(qreal opacity); + +#ifndef QT_NO_GRAPHICSEFFECT + // Effect + QGraphicsEffect *graphicsEffect() const; + void setGraphicsEffect(QGraphicsEffect *effect); +#endif //QT_NO_GRAPHICSEFFECT + + Qt::MouseButtons acceptedMouseButtons() const; + void setAcceptedMouseButtons(Qt::MouseButtons buttons); + + bool acceptsHoverEvents() const; // ### obsolete + void setAcceptsHoverEvents(bool enabled); // ### obsolete + bool acceptHoverEvents() const; + void setAcceptHoverEvents(bool enabled); + bool acceptTouchEvents() const; + void setAcceptTouchEvents(bool enabled); + + bool filtersChildEvents() const; + void setFiltersChildEvents(bool enabled); + + bool handlesChildEvents() const; + void setHandlesChildEvents(bool enabled); + + bool isActive() const; + void setActive(bool active); + + bool hasFocus() const; + void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); + void clearFocus(); + + QGraphicsItem *focusProxy() const; + void setFocusProxy(QGraphicsItem *item); + + QGraphicsItem *focusItem() const; + QGraphicsItem *focusScopeItem() const; + + void grabMouse(); + void ungrabMouse(); + void grabKeyboard(); + void ungrabKeyboard(); + + // Positioning in scene coordinates + QPointF pos() const; + inline qreal x() const { return pos().x(); } + void setX(qreal x); + inline qreal y() const { return pos().y(); } + void setY(qreal y); + QPointF scenePos() const; + void setPos(const QPointF &pos); + inline void setPos(qreal x, qreal y); + inline void moveBy(qreal dx, qreal dy) { setPos(pos().x() + dx, pos().y() + dy); } + + void ensureVisible(const QRectF &rect = QRectF(), int xmargin = 50, int ymargin = 50); + inline void ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50); + + // Local transformation + QMatrix matrix() const; + QMatrix sceneMatrix() const; + void setMatrix(const QMatrix &matrix, bool combine = false); + void resetMatrix(); + QTransform transform() const; + QTransform sceneTransform() const; + QTransform deviceTransform(const QTransform &viewportTransform) const; + QTransform itemTransform(const QGraphicsItem *other, bool *ok = 0) const; + void setTransform(const QTransform &matrix, bool combine = false); + void resetTransform(); + + void rotate(qreal angle); // ### obsolete + void scale(qreal sx, qreal sy); // ### obsolete + void shear(qreal sh, qreal sv); // ### obsolete + void translate(qreal dx, qreal dy); // ### obsolete + + void setRotation(qreal angle); + qreal rotation() const; + + void setScale(qreal scale); + qreal scale() const; + + QList transformations() const; + void setTransformations(const QList &transformations); + + QPointF transformOriginPoint() const; + void setTransformOriginPoint(const QPointF &origin); + inline void setTransformOriginPoint(qreal ax, qreal ay) + { setTransformOriginPoint(QPointF(ax,ay)); } + + virtual void advance(int phase); + + // Stacking order + qreal zValue() const; + void setZValue(qreal z); + void stackBefore(const QGraphicsItem *sibling); + + // Hit test + virtual QRectF boundingRect() const = 0; + QRectF childrenBoundingRect() const; + QRectF sceneBoundingRect() const; + virtual QPainterPath shape() const; + bool isClipped() const; + QPainterPath clipPath() const; + virtual bool contains(const QPointF &point) const; + virtual bool collidesWithItem(const QGraphicsItem *other, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + virtual bool collidesWithPath(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList collidingItems(Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + bool isObscured() const; + bool isObscured(const QRectF &rect) const; // ### Qt 5: merge with isObscured(), add QRectF arg to isObscuredBy() + inline bool isObscured(qreal x, qreal y, qreal w, qreal h) const; + virtual bool isObscuredBy(const QGraphicsItem *item) const; + virtual QPainterPath opaqueArea() const; + + QRegion boundingRegion(const QTransform &itemToDeviceTransform) const; + qreal boundingRegionGranularity() const; + void setBoundingRegionGranularity(qreal granularity); + + // Drawing + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) = 0; + void update(const QRectF &rect = QRectF()); + inline void update(qreal x, qreal y, qreal width, qreal height); + void scroll(qreal dx, qreal dy, const QRectF &rect = QRectF()); + + // Coordinate mapping + QPointF mapToItem(const QGraphicsItem *item, const QPointF &point) const; + QPointF mapToParent(const QPointF &point) const; + QPointF mapToScene(const QPointF &point) const; + QPolygonF mapToItem(const QGraphicsItem *item, const QRectF &rect) const; + QPolygonF mapToParent(const QRectF &rect) const; + QPolygonF mapToScene(const QRectF &rect) const; + QRectF mapRectToItem(const QGraphicsItem *item, const QRectF &rect) const; + QRectF mapRectToParent(const QRectF &rect) const; + QRectF mapRectToScene(const QRectF &rect) const; + QPolygonF mapToItem(const QGraphicsItem *item, const QPolygonF &polygon) const; + QPolygonF mapToParent(const QPolygonF &polygon) const; + QPolygonF mapToScene(const QPolygonF &polygon) const; + QPainterPath mapToItem(const QGraphicsItem *item, const QPainterPath &path) const; + QPainterPath mapToParent(const QPainterPath &path) const; + QPainterPath mapToScene(const QPainterPath &path) const; + QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const; + QPointF mapFromParent(const QPointF &point) const; + QPointF mapFromScene(const QPointF &point) const; + QPolygonF mapFromItem(const QGraphicsItem *item, const QRectF &rect) const; + QPolygonF mapFromParent(const QRectF &rect) const; + QPolygonF mapFromScene(const QRectF &rect) const; + QRectF mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const; + QRectF mapRectFromParent(const QRectF &rect) const; + QRectF mapRectFromScene(const QRectF &rect) const; + QPolygonF mapFromItem(const QGraphicsItem *item, const QPolygonF &polygon) const; + QPolygonF mapFromParent(const QPolygonF &polygon) const; + QPolygonF mapFromScene(const QPolygonF &polygon) const; + QPainterPath mapFromItem(const QGraphicsItem *item, const QPainterPath &path) const; + QPainterPath mapFromParent(const QPainterPath &path) const; + QPainterPath mapFromScene(const QPainterPath &path) const; + + inline QPointF mapToItem(const QGraphicsItem *item, qreal x, qreal y) const; + inline QPointF mapToParent(qreal x, qreal y) const; + inline QPointF mapToScene(qreal x, qreal y) const; + inline QPolygonF mapToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapToParent(qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapToScene(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToParent(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectToScene(qreal x, qreal y, qreal w, qreal h) const; + inline QPointF mapFromItem(const QGraphicsItem *item, qreal x, qreal y) const; + inline QPointF mapFromParent(qreal x, qreal y) const; + inline QPointF mapFromScene(qreal x, qreal y) const; + inline QPolygonF mapFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapFromParent(qreal x, qreal y, qreal w, qreal h) const; + inline QPolygonF mapFromScene(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromItem(const QGraphicsItem *item, qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromParent(qreal x, qreal y, qreal w, qreal h) const; + inline QRectF mapRectFromScene(qreal x, qreal y, qreal w, qreal h) const; + + bool isAncestorOf(const QGraphicsItem *child) const; + QGraphicsItem *commonAncestorItem(const QGraphicsItem *other) const; + bool isUnderMouse() const; + + // Custom data + QVariant data(int key) const; + void setData(int key, const QVariant &value); + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + + enum { + Type = 1, + UserType = 65536 + }; + virtual int type() const; + + void installSceneEventFilter(QGraphicsItem *filterItem); + void removeSceneEventFilter(QGraphicsItem *filterItem); + +protected: + void updateMicroFocus(); + virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event); + virtual bool sceneEvent(QEvent *event); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dropEvent(QGraphicsSceneDragDropEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *event); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + enum Extension { + UserExtension = 0x80000000 + }; + virtual bool supportsExtension(Extension extension) const; + virtual void setExtension(Extension extension, const QVariant &variant); + virtual QVariant extension(const QVariant &variant) const; + +protected: + QGraphicsItem(QGraphicsItemPrivate &dd, + QGraphicsItem *parent, QGraphicsScene *scene); + QScopedPointer d_ptr; + + void addToIndex(); + void removeFromIndex(); + void prepareGeometryChange(); + +private: + Q_DISABLE_COPY(QGraphicsItem) + Q_DECLARE_PRIVATE(QGraphicsItem) + friend class QGraphicsItemGroup; + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsSceneFindItemBspTreeVisitor; + friend class QGraphicsSceneBspTree; + friend class QGraphicsView; + friend class QGraphicsViewPrivate; + friend class QGraphicsObject; + friend class QGraphicsWidget; + friend class QGraphicsWidgetPrivate; + friend class QGraphicsProxyWidgetPrivate; + friend class QGraphicsSceneIndex; + friend class QGraphicsSceneIndexPrivate; + friend class QGraphicsSceneBspTreeIndex; + friend class QGraphicsSceneBspTreeIndexPrivate; + friend class QGraphicsItemEffectSourcePrivate; + friend class QGraphicsTransformPrivate; +#ifndef QT_NO_GESTURES + friend class QGestureManager; +#endif + friend class ::tst_QGraphicsItem; + friend bool qt_closestLeaf(const QGraphicsItem *, const QGraphicsItem *); + friend bool qt_closestItemFirst(const QGraphicsItem *, const QGraphicsItem *); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsItem::GraphicsItemFlags) +Q_DECLARE_INTERFACE(QGraphicsItem, "com.trolltech.Qt.QGraphicsItem") + +inline void QGraphicsItem::setPos(qreal ax, qreal ay) +{ setPos(QPointF(ax, ay)); } +inline void QGraphicsItem::ensureVisible(qreal ax, qreal ay, qreal w, qreal h, int xmargin, int ymargin) +{ ensureVisible(QRectF(ax, ay, w, h), xmargin, ymargin); } +inline void QGraphicsItem::update(qreal ax, qreal ay, qreal width, qreal height) +{ update(QRectF(ax, ay, width, height)); } +inline bool QGraphicsItem::isObscured(qreal ax, qreal ay, qreal w, qreal h) const +{ return isObscured(QRectF(ax, ay, w, h)); } +inline QPointF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal ax, qreal ay) const +{ return mapToItem(item, QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapToParent(qreal ax, qreal ay) const +{ return mapToParent(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapToScene(qreal ax, qreal ay) const +{ return mapToScene(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal ax, qreal ay) const +{ return mapFromItem(item, QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromParent(qreal ax, qreal ay) const +{ return mapFromParent(QPointF(ax, ay)); } +inline QPointF QGraphicsItem::mapFromScene(qreal ax, qreal ay) const +{ return mapFromScene(QPointF(ax, ay)); } +inline QPolygonF QGraphicsItem::mapToItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToItem(item, QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapToParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToParent(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapToScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapToScene(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToItem(item, QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToParent(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectToScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectToScene(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromItem(item, QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromParent(QRectF(ax, ay, w, h)); } +inline QPolygonF QGraphicsItem::mapFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromScene(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromItem(const QGraphicsItem *item, qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromItem(item, QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromParent(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromParent(QRectF(ax, ay, w, h)); } +inline QRectF QGraphicsItem::mapRectFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapRectFromScene(QRectF(ax, ay, w, h)); } + + +class Q_GUI_EXPORT QGraphicsObject : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_PROPERTY(QGraphicsObject * parent READ parentObject WRITE setParentItem NOTIFY parentChanged DESIGNABLE false) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + Q_PROPERTY(QPointF pos READ pos WRITE setPos FINAL) + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged FINAL) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged FINAL) + Q_PROPERTY(qreal z READ zValue WRITE setZValue NOTIFY zChanged FINAL) + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) + Q_PROPERTY(QPointF transformOriginPoint READ transformOriginPoint WRITE setTransformOriginPoint) +#ifndef QT_NO_GRAPHICSEFFECT + Q_PROPERTY(QGraphicsEffect *effect READ graphicsEffect WRITE setGraphicsEffect) +#endif + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), QDeclarativeListProperty children READ childrenList DESIGNABLE false NOTIFY childrenChanged) + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), qreal width READ width WRITE setWidth NOTIFY widthChanged RESET resetWidth FINAL) + Q_PRIVATE_PROPERTY(QGraphicsItem::d_func(), qreal height READ height WRITE setHeight NOTIFY heightChanged RESET resetHeight FINAL) + Q_CLASSINFO("DefaultProperty", "children") + Q_INTERFACES(QGraphicsItem) +public: + QGraphicsObject(QGraphicsItem *parent = 0); + + // ### Qt 5: Disambiguate +#ifdef Q_NO_USING_KEYWORD + const QObjectList &children() const { return QObject::children(); } +#else + using QObject::children; +#endif + +#ifndef QT_NO_GESTURES + void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags()); + void ungrabGesture(Qt::GestureType type); +#endif + +protected Q_SLOTS: + void updateMicroFocus(); + +Q_SIGNALS: + void parentChanged(); + void opacityChanged(); + void visibleChanged(); + void enabledChanged(); + void xChanged(); + void yChanged(); + void zChanged(); + void rotationChanged(); + void scaleChanged(); + void childrenChanged(); + void widthChanged(); + void heightChanged(); + +protected: + QGraphicsObject(QGraphicsItemPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene); +private: + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; +}; + + +class QAbstractGraphicsShapeItemPrivate; +class Q_GUI_EXPORT QAbstractGraphicsShapeItem : public QGraphicsItem +{ +public: + QAbstractGraphicsShapeItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QAbstractGraphicsShapeItem(); + + QPen pen() const; + void setPen(const QPen &pen); + + QBrush brush() const; + void setBrush(const QBrush &brush); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + +protected: + QAbstractGraphicsShapeItem(QAbstractGraphicsShapeItemPrivate &dd, + QGraphicsItem *parent, QGraphicsScene *scene); + +private: + Q_DISABLE_COPY(QAbstractGraphicsShapeItem) + Q_DECLARE_PRIVATE(QAbstractGraphicsShapeItem) +}; + +class QGraphicsPathItemPrivate; +class Q_GUI_EXPORT QGraphicsPathItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsPathItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPathItem(const QPainterPath &path, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPathItem(); + + QPainterPath path() const; + void setPath(const QPainterPath &path); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 2 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPathItem) + Q_DECLARE_PRIVATE(QGraphicsPathItem) +}; + +class QGraphicsRectItemPrivate; +class Q_GUI_EXPORT QGraphicsRectItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsRectItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsRectItem(const QRectF &rect, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsRectItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsRectItem(); + + QRectF rect() const; + void setRect(const QRectF &rect); + inline void setRect(qreal x, qreal y, qreal w, qreal h); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 3 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsRectItem) + Q_DECLARE_PRIVATE(QGraphicsRectItem) +}; + +inline void QGraphicsRectItem::setRect(qreal ax, qreal ay, qreal w, qreal h) +{ setRect(QRectF(ax, ay, w, h)); } + +class QGraphicsEllipseItemPrivate; +class Q_GUI_EXPORT QGraphicsEllipseItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsEllipseItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsEllipseItem(const QRectF &rect, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsEllipseItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsEllipseItem(); + + QRectF rect() const; + void setRect(const QRectF &rect); + inline void setRect(qreal x, qreal y, qreal w, qreal h); + + int startAngle() const; + void setStartAngle(int angle); + + int spanAngle() const; + void setSpanAngle(int angle); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 4 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsEllipseItem) + Q_DECLARE_PRIVATE(QGraphicsEllipseItem) +}; + +inline void QGraphicsEllipseItem::setRect(qreal ax, qreal ay, qreal w, qreal h) +{ setRect(QRectF(ax, ay, w, h)); } + +class QGraphicsPolygonItemPrivate; +class Q_GUI_EXPORT QGraphicsPolygonItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsPolygonItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPolygonItem(const QPolygonF &polygon, + QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPolygonItem(); + + QPolygonF polygon() const; + void setPolygon(const QPolygonF &polygon); + + Qt::FillRule fillRule() const; + void setFillRule(Qt::FillRule rule); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 5 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPolygonItem) + Q_DECLARE_PRIVATE(QGraphicsPolygonItem) +}; + +class QGraphicsLineItemPrivate; +class Q_GUI_EXPORT QGraphicsLineItem : public QGraphicsItem +{ +public: + QGraphicsLineItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsLineItem(const QLineF &line, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsLineItem(); + + QPen pen() const; + void setPen(const QPen &pen); + + QLineF line() const; + void setLine(const QLineF &line); + inline void setLine(qreal x1, qreal y1, qreal x2, qreal y2) + { setLine(QLineF(x1, y1, x2, y2)); } + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 6 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsLineItem) + Q_DECLARE_PRIVATE(QGraphicsLineItem) +}; + +class QGraphicsPixmapItemPrivate; +class Q_GUI_EXPORT QGraphicsPixmapItem : public QGraphicsItem +{ +public: + enum ShapeMode { + MaskShape, + BoundingRectShape, + HeuristicMaskShape + }; + + QGraphicsPixmapItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsPixmapItem(); + + QPixmap pixmap() const; + void setPixmap(const QPixmap &pixmap); + + Qt::TransformationMode transformationMode() const; + void setTransformationMode(Qt::TransformationMode mode); + + QPointF offset() const; + void setOffset(const QPointF &offset); + inline void setOffset(qreal x, qreal y); + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 7 }; + int type() const; + + ShapeMode shapeMode() const; + void setShapeMode(ShapeMode mode); + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsPixmapItem) + Q_DECLARE_PRIVATE(QGraphicsPixmapItem) +}; + +inline void QGraphicsPixmapItem::setOffset(qreal ax, qreal ay) +{ setOffset(QPointF(ax, ay)); } + +class QGraphicsTextItemPrivate; +class QTextDocument; +class QTextCursor; +class Q_GUI_EXPORT QGraphicsTextItem : public QGraphicsObject +{ + Q_OBJECT + QDOC_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + QDOC_PROPERTY(QTextCursor textCursor READ textCursor WRITE setTextCursor) + +public: + QGraphicsTextItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsTextItem(const QString &text, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsTextItem(); + + QString toHtml() const; + void setHtml(const QString &html); + + QString toPlainText() const; + void setPlainText(const QString &text); + + QFont font() const; + void setFont(const QFont &font); + + void setDefaultTextColor(const QColor &c); + QColor defaultTextColor() const; + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 8 }; + int type() const; + + void setTextWidth(qreal width); + qreal textWidth() const; + + void adjustSize(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + void setTabChangesFocus(bool b); + bool tabChangesFocus() const; + + void setOpenExternalLinks(bool open); + bool openExternalLinks() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + +Q_SIGNALS: + void linkActivated(const QString &); + void linkHovered(const QString &); + +protected: + bool sceneEvent(QEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + void dropEvent(QGraphicsSceneDragDropEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsTextItem) + Q_PRIVATE_SLOT(dd, void _q_updateBoundingRect(const QSizeF &)) + Q_PRIVATE_SLOT(dd, void _q_update(QRectF)) + Q_PRIVATE_SLOT(dd, void _q_ensureVisible(QRectF)) + QGraphicsTextItemPrivate *dd; + friend class QGraphicsTextItemPrivate; +}; + +class QGraphicsSimpleTextItemPrivate; +class Q_GUI_EXPORT QGraphicsSimpleTextItem : public QAbstractGraphicsShapeItem +{ +public: + QGraphicsSimpleTextItem(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + QGraphicsSimpleTextItem(const QString &text, QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsSimpleTextItem(); + + void setText(const QString &text); + QString text() const; + + void setFont(const QFont &font); + QFont font() const; + + QRectF boundingRect() const; + QPainterPath shape() const; + bool contains(const QPointF &point) const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 9 }; + int type() const; + +protected: + bool supportsExtension(Extension extension) const; + void setExtension(Extension extension, const QVariant &variant); + QVariant extension(const QVariant &variant) const; + +private: + Q_DISABLE_COPY(QGraphicsSimpleTextItem) + Q_DECLARE_PRIVATE(QGraphicsSimpleTextItem) +}; + +class QGraphicsItemGroupPrivate; +class Q_GUI_EXPORT QGraphicsItemGroup : public QGraphicsItem +{ +public: + QGraphicsItemGroup(QGraphicsItem *parent = 0 +#ifndef Q_QDOC + // ### obsolete argument + , QGraphicsScene *scene = 0 +#endif + ); + ~QGraphicsItemGroup(); + + void addToGroup(QGraphicsItem *item); + void removeFromGroup(QGraphicsItem *item); + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + bool isObscuredBy(const QGraphicsItem *item) const; + QPainterPath opaqueArea() const; + + enum { Type = 10 }; + int type() const; + +private: + Q_DISABLE_COPY(QGraphicsItemGroup) + Q_DECLARE_PRIVATE(QGraphicsItemGroup) +}; + +template inline T qgraphicsitem_cast(QGraphicsItem *item) +{ + return int(static_cast(0)->Type) == int(QGraphicsItem::Type) + || (item && int(static_cast(0)->Type) == item->type()) ? static_cast(item) : 0; +} + +template inline T qgraphicsitem_cast(const QGraphicsItem *item) +{ + return int(static_cast(0)->Type) == int(QGraphicsItem::Type) + || (item && int(static_cast(0)->Type) == item->type()) ? static_cast(item) : 0; +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem *item); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsObject *item); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemChange change); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlags flags); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGraphicsItem *) +Q_DECLARE_METATYPE(QGraphicsScene *) + +QT_BEGIN_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSITEM_H diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h new file mode 100644 index 0000000000..90ff43f93c --- /dev/null +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -0,0 +1,891 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEM_P_H +#define QGRAPHICSITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsitem.h" +#include "qset.h" +#include "qpixmapcache.h" +#include +#include "qgraphicstransform.h" +#include + +#include +#include + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsItemPrivate; + +#ifndef QDECLARATIVELISTPROPERTY +#define QDECLARATIVELISTPROPERTY +template +class QDeclarativeListProperty { +public: + typedef void (*AppendFunction)(QDeclarativeListProperty *, T*); + typedef int (*CountFunction)(QDeclarativeListProperty *); + typedef T *(*AtFunction)(QDeclarativeListProperty *, int); + typedef void (*ClearFunction)(QDeclarativeListProperty *); + + QDeclarativeListProperty() + : object(0), data(0), append(0), count(0), at(0), clear(0), dummy1(0), dummy2(0) {} + QDeclarativeListProperty(QObject *o, QList &list) + : object(o), data(&list), append(qlist_append), count(qlist_count), at(qlist_at), + clear(qlist_clear), dummy1(0), dummy2(0) {} + QDeclarativeListProperty(QObject *o, void *d, AppendFunction a, CountFunction c = 0, AtFunction t = 0, + ClearFunction r = 0) + : object(o), data(d), append(a), count(c), at(t), clear(r), dummy1(0), dummy2(0) {} + + bool operator==(const QDeclarativeListProperty &o) const { + return object == o.object && + data == o.data && + append == o.append && + count == o.count && + at == o.at && + clear == o.clear; + } + + QObject *object; + void *data; + + AppendFunction append; + + CountFunction count; + AtFunction at; + + ClearFunction clear; + + void *dummy1; + void *dummy2; + +private: + static void qlist_append(QDeclarativeListProperty *p, T *v) { + ((QList *)p->data)->append(v); + } + static int qlist_count(QDeclarativeListProperty *p) { + return ((QList *)p->data)->count(); + } + static T *qlist_at(QDeclarativeListProperty *p, int idx) { + return ((QList *)p->data)->at(idx); + } + static void qlist_clear(QDeclarativeListProperty *p) { + return ((QList *)p->data)->clear(); + } +}; +#endif + +class QGraphicsItemCache +{ +public: + QGraphicsItemCache() : allExposed(false) { } + + // ItemCoordinateCache only + QRect boundingRect; + QSize fixedSize; + QPixmapCache::Key key; + + // DeviceCoordinateCache only + struct DeviceData { + DeviceData() {} + QTransform lastTransform; + QPoint cacheIndent; + QPixmapCache::Key key; + }; + QMap deviceData; + + // List of logical exposed rects + QVector exposed; + bool allExposed; + + // Empty cache + void purge(); +}; + +class Q_GUI_EXPORT QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsItem) +public: + enum Extra { + ExtraToolTip, + ExtraCursor, + ExtraCacheData, + ExtraMaxDeviceCoordCacheSize, + ExtraBoundingRegionGranularity + }; + + enum AncestorFlag { + NoFlag = 0, + AncestorHandlesChildEvents = 0x1, + AncestorClipsChildren = 0x2, + AncestorIgnoresTransformations = 0x4, + AncestorFiltersChildEvents = 0x8 + }; + + inline QGraphicsItemPrivate() + : z(0), + opacity(1.), + scene(0), + parent(0), + transformData(0), + graphicsEffect(0), + index(-1), + siblingIndex(-1), + itemDepth(-1), + focusProxy(0), + subFocusItem(0), + focusScopeItem(0), + imHints(Qt::ImhNone), + panelModality(QGraphicsItem::NonModal), + acceptedMouseButtons(0x1f), + visible(1), + explicitlyHidden(0), + enabled(1), + explicitlyDisabled(0), + selected(0), + acceptsHover(0), + acceptDrops(0), + isMemberOfGroup(0), + handlesChildEvents(0), + itemDiscovered(0), + hasCursor(0), + ancestorFlags(0), + cacheMode(0), + hasBoundingRegionGranularity(0), + isWidget(0), + dirty(0), + dirtyChildren(0), + localCollisionHack(0), + inSetPosHelper(0), + needSortChildren(0), + allChildrenDirty(0), + fullUpdatePending(0), + dirtyChildrenBoundingRect(1), + flags(0), + paintedViewBoundingRectsNeedRepaint(0), + dirtySceneTransform(1), + geometryChanged(1), + inDestructor(0), + isObject(0), + ignoreVisible(0), + ignoreOpacity(0), + acceptTouchEvents(0), + acceptedTouchBeginEvent(0), + filtersDescendantEvents(0), + sceneTransformTranslateOnly(0), + notifyBoundingRectChanged(0), + notifyInvalidated(0), + mouseSetsFocus(1), + explicitActivate(0), + wantsActive(0), + holesInSiblingIndex(0), + sequentialOrdering(1), + updateDueToGraphicsEffect(0), + scenePosDescendants(0), + pendingPolish(0), + mayHaveChildWithGraphicsEffect(0), + isDeclarativeItem(0), + sendParentChangeNotification(0), + globalStackingOrder(-1), + q_ptr(0) + { + } + + inline virtual ~QGraphicsItemPrivate() + { } + + static const QGraphicsItemPrivate *get(const QGraphicsItem *item) + { + return item->d_ptr.data(); + } + static QGraphicsItemPrivate *get(QGraphicsItem *item) + { + return item->d_ptr.data(); + } + + void updateChildWithGraphicsEffectFlagRecursively(); + void updateAncestorFlag(QGraphicsItem::GraphicsItemFlag childFlag, + AncestorFlag flag = NoFlag, bool enabled = false, bool root = true); + void updateAncestorFlags(); + void setIsMemberOfGroup(bool enabled); + void remapItemPos(QEvent *event, QGraphicsItem *item); + QPointF genericMapFromScene(const QPointF &pos, const QWidget *viewport) const; + inline bool itemIsUntransformable() const + { + return (flags & QGraphicsItem::ItemIgnoresTransformations) + || (ancestorFlags & AncestorIgnoresTransformations); + } + + void combineTransformToParent(QTransform *x, const QTransform *viewTransform = 0) const; + void combineTransformFromParent(QTransform *x, const QTransform *viewTransform = 0) const; + virtual void updateSceneTransformFromParent(); + + // ### Qt 5: Remove. Workaround for reimplementation added after Qt 4.4. + virtual QVariant inputMethodQueryHelper(Qt::InputMethodQuery query) const; + static bool movableAncestorIsSelected(const QGraphicsItem *item); + + virtual void setPosHelper(const QPointF &pos); + void setTransformHelper(const QTransform &transform); + void prependGraphicsTransform(QGraphicsTransform *t); + void appendGraphicsTransform(QGraphicsTransform *t); + void setVisibleHelper(bool newVisible, bool explicitly, bool update = true); + void setEnabledHelper(bool newEnabled, bool explicitly, bool update = true); + bool discardUpdateRequest(bool ignoreVisibleBit = false, + bool ignoreDirtyBit = false, bool ignoreOpacity = false) const; + virtual void transformChanged() {} + int depth() const; +#ifndef QT_NO_GRAPHICSEFFECT + enum InvalidateReason { + OpacityChanged + }; + void invalidateParentGraphicsEffectsRecursively(); + void invalidateChildGraphicsEffectsRecursively(InvalidateReason reason); +#endif //QT_NO_GRAPHICSEFFECT + void invalidateDepthRecursively(); + void resolveDepth(); + void addChild(QGraphicsItem *child); + void removeChild(QGraphicsItem *child); + QDeclarativeListProperty childrenList(); + void setParentItemHelper(QGraphicsItem *parent, const QVariant *newParentVariant, + const QVariant *thisPointerVariant); + void childrenBoundingRectHelper(QTransform *x, QRectF *rect, QGraphicsItem *topMostEffectItem); + void initStyleOption(QStyleOptionGraphicsItem *option, const QTransform &worldTransform, + const QRegion &exposedRegion, bool allItems = false) const; + QRectF effectiveBoundingRect(QGraphicsItem *topMostEffectItem = 0) const; + QRectF sceneEffectiveBoundingRect() const; + + QRectF effectiveBoundingRect(const QRectF &rect) const; + + virtual void resolveFont(uint inheritedMask) + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->resolveFont(inheritedMask); + } + + virtual void resolvePalette(uint inheritedMask) + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->resolveFont(inheritedMask); + } + + virtual bool isProxyWidget() const; + + inline QVariant extra(Extra type) const + { + for (int i = 0; i < extras.size(); ++i) { + const ExtraStruct &extra = extras.at(i); + if (extra.type == type) + return extra.value; + } + return QVariant(); + } + + inline void setExtra(Extra type, const QVariant &value) + { + int index = -1; + for (int i = 0; i < extras.size(); ++i) { + if (extras.at(i).type == type) { + index = i; + break; + } + } + + if (index == -1) { + extras << ExtraStruct(type, value); + } else { + extras[index].value = value; + } + } + + inline void unsetExtra(Extra type) + { + for (int i = 0; i < extras.size(); ++i) { + if (extras.at(i).type == type) { + extras.removeAt(i); + return; + } + } + } + + struct ExtraStruct { + ExtraStruct(Extra type, QVariant value) + : type(type), value(value) + { } + + Extra type; + QVariant value; + + bool operator<(Extra extra) const + { return type < extra; } + }; + + QList extras; + + QGraphicsItemCache *maybeExtraItemCache() const; + QGraphicsItemCache *extraItemCache() const; + void removeExtraItemCache(); + + void updatePaintedViewBoundingRects(bool updateChildren); + void ensureSceneTransformRecursive(QGraphicsItem **topMostDirtyItem); + inline void ensureSceneTransform() + { + QGraphicsItem *that = q_func(); + ensureSceneTransformRecursive(&that); + } + + inline bool hasTranslateOnlySceneTransform() + { + ensureSceneTransform(); + return sceneTransformTranslateOnly; + } + + inline void invalidateChildrenSceneTransform() + { + for (int i = 0; i < children.size(); ++i) + children.at(i)->d_ptr->dirtySceneTransform = 1; + } + + inline qreal calcEffectiveOpacity() const + { + qreal o = opacity; + QGraphicsItem *p = parent; + int myFlags = flags; + while (p) { + int parentFlags = p->d_ptr->flags; + + // If I have a parent, and I don't ignore my parent's opacity, and my + // parent propagates to me, then combine my local opacity with my parent's + // effective opacity into my effective opacity. + if ((myFlags & QGraphicsItem::ItemIgnoresParentOpacity) + || (parentFlags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren)) { + break; + } + + o *= p->d_ptr->opacity; + p = p->d_ptr->parent; + myFlags = parentFlags; + } + return o; + } + + inline bool isOpacityNull() const + { return (opacity < qreal(0.001)); } + + static inline bool isOpacityNull(qreal opacity) + { return (opacity < qreal(0.001)); } + + inline bool isFullyTransparent() const + { + if (isOpacityNull()) + return true; + if (!parent) + return false; + + return isOpacityNull(calcEffectiveOpacity()); + } + + inline qreal effectiveOpacity() const { + if (!parent || !opacity) + return opacity; + + return calcEffectiveOpacity(); + } + + inline qreal combineOpacityFromParent(qreal parentOpacity) const + { + if (parent && !(flags & QGraphicsItem::ItemIgnoresParentOpacity) + && !(parent->d_ptr->flags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren)) { + return parentOpacity * opacity; + } + return opacity; + } + + inline bool childrenCombineOpacity() const + { + if (!children.size()) + return true; + if (flags & QGraphicsItem::ItemDoesntPropagateOpacityToChildren) + return false; + + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity) + return false; + } + return true; + } + + inline bool childrenClippedToShape() const + { return (flags & QGraphicsItem::ItemClipsChildrenToShape) || children.isEmpty(); } + + inline bool isInvisible() const + { + return !visible || (childrenCombineOpacity() && isFullyTransparent()); + } + + inline void markParentDirty(bool updateBoundingRect = false); + + void setFocusHelper(Qt::FocusReason focusReason, bool climb, bool focusFromHide); + void clearFocusHelper(bool giveFocusToParent); + void setSubFocus(QGraphicsItem *rootItem = 0, QGraphicsItem *stopItem = 0); + void clearSubFocus(QGraphicsItem *rootItem = 0, QGraphicsItem *stopItem = 0); + void resetFocusProxy(); + virtual void subFocusItemChange(); + virtual void focusScopeItemChange(bool isSubFocusItem); + + static void children_append(QDeclarativeListProperty *list, QGraphicsObject *item); + static int children_count(QDeclarativeListProperty *list); + static QGraphicsObject *children_at(QDeclarativeListProperty *list, int); + static void children_clear(QDeclarativeListProperty *list); + + inline QTransform transformToParent() const; + inline void ensureSortedChildren(); + static inline bool insertionOrder(QGraphicsItem *a, QGraphicsItem *b); + void ensureSequentialSiblingIndex(); + inline void sendScenePosChange(); + virtual void siblingOrderChange(); + + // Private Properties + virtual qreal width() const; + virtual void setWidth(qreal); + virtual void resetWidth(); + + virtual qreal height() const; + virtual void setHeight(qreal); + virtual void resetHeight(); + + QRectF childrenBoundingRect; + QRectF needsRepaint; + QMap paintedViewBoundingRects; + QPointF pos; + qreal z; + qreal opacity; + QGraphicsScene *scene; + QGraphicsItem *parent; + QList children; + struct TransformData; + TransformData *transformData; + QGraphicsEffect *graphicsEffect; + QTransform sceneTransform; + int index; + int siblingIndex; + int itemDepth; // Lazily calculated when calling depth(). + QGraphicsItem *focusProxy; + QList focusProxyRefs; + QGraphicsItem *subFocusItem; + QGraphicsItem *focusScopeItem; + Qt::InputMethodHints imHints; + QGraphicsItem::PanelModality panelModality; +#ifndef QT_NO_GESTURES + QMap gestureContext; +#endif + + // Packed 32 bits + quint32 acceptedMouseButtons : 5; + quint32 visible : 1; + quint32 explicitlyHidden : 1; + quint32 enabled : 1; + quint32 explicitlyDisabled : 1; + quint32 selected : 1; + quint32 acceptsHover : 1; + quint32 acceptDrops : 1; + quint32 isMemberOfGroup : 1; + quint32 handlesChildEvents : 1; + quint32 itemDiscovered : 1; + quint32 hasCursor : 1; + quint32 ancestorFlags : 4; + quint32 cacheMode : 2; + quint32 hasBoundingRegionGranularity : 1; + quint32 isWidget : 1; + quint32 dirty : 1; + quint32 dirtyChildren : 1; + quint32 localCollisionHack : 1; + quint32 inSetPosHelper : 1; + quint32 needSortChildren : 1; + quint32 allChildrenDirty : 1; + quint32 fullUpdatePending : 1; + quint32 dirtyChildrenBoundingRect : 1; + + // Packed 32 bits + quint32 flags : 19; + quint32 paintedViewBoundingRectsNeedRepaint : 1; + quint32 dirtySceneTransform : 1; + quint32 geometryChanged : 1; + quint32 inDestructor : 1; + quint32 isObject : 1; + quint32 ignoreVisible : 1; + quint32 ignoreOpacity : 1; + quint32 acceptTouchEvents : 1; + quint32 acceptedTouchBeginEvent : 1; + quint32 filtersDescendantEvents : 1; + quint32 sceneTransformTranslateOnly : 1; + quint32 notifyBoundingRectChanged : 1; + quint32 notifyInvalidated : 1; + + // New 32 bits + quint32 mouseSetsFocus : 1; + quint32 explicitActivate : 1; + quint32 wantsActive : 1; + quint32 holesInSiblingIndex : 1; + quint32 sequentialOrdering : 1; + quint32 updateDueToGraphicsEffect : 1; + quint32 scenePosDescendants : 1; + quint32 pendingPolish : 1; + quint32 mayHaveChildWithGraphicsEffect : 1; + quint32 isDeclarativeItem : 1; + quint32 sendParentChangeNotification : 1; + quint32 padding : 21; + + // Optional stacking order + int globalStackingOrder; + QGraphicsItem *q_ptr; +}; + +struct QGraphicsItemPrivate::TransformData +{ + QTransform transform; + qreal scale; + qreal rotation; + qreal xOrigin; + qreal yOrigin; + QList graphicsTransforms; + bool onlyTransform; + + TransformData() : + scale(1.0), rotation(0.0), + xOrigin(0.0), yOrigin(0.0), + onlyTransform(true) + { } + + QTransform computedFullTransform(QTransform *postmultiplyTransform = 0) const + { + if (onlyTransform) { + if (!postmultiplyTransform || postmultiplyTransform->isIdentity()) + return transform; + if (transform.isIdentity()) + return *postmultiplyTransform; + return transform * *postmultiplyTransform; + } + + QTransform x(transform); + if (!graphicsTransforms.isEmpty()) { + QMatrix4x4 m; + for (int i = 0; i < graphicsTransforms.size(); ++i) + graphicsTransforms.at(i)->applyTo(&m); + x *= m.toTransform(); + } + x.translate(xOrigin, yOrigin); + x.rotate(rotation); + x.scale(scale, scale); + x.translate(-xOrigin, -yOrigin); + if (postmultiplyTransform) + x *= *postmultiplyTransform; + return x; + } +}; + +struct QGraphicsItemPaintInfo +{ + inline QGraphicsItemPaintInfo(const QTransform *const xform1, const QTransform *const xform2, + const QTransform *const xform3, + QRegion *r, QWidget *w, QStyleOptionGraphicsItem *opt, + QPainter *p, qreal o, bool b1, bool b2) + : viewTransform(xform1), transformPtr(xform2), effectTransform(xform3), exposedRegion(r), widget(w), + option(opt), painter(p), opacity(o), wasDirtySceneTransform(b1), drawItem(b2) + {} + + const QTransform *viewTransform; + const QTransform *transformPtr; + const QTransform *effectTransform; + QRegion *exposedRegion; + QWidget *widget; + QStyleOptionGraphicsItem *option; + QPainter *painter; + qreal opacity; + quint32 wasDirtySceneTransform : 1; + quint32 drawItem : 1; +}; + +#ifndef QT_NO_GRAPHICSEFFECT +class QGraphicsItemEffectSourcePrivate : public QGraphicsEffectSourcePrivate +{ +public: + QGraphicsItemEffectSourcePrivate(QGraphicsItem *i) + : QGraphicsEffectSourcePrivate(), item(i), info(0) + {} + + inline void detach() + { + item->d_ptr->graphicsEffect = 0; + item->prepareGeometryChange(); + } + + inline const QGraphicsItem *graphicsItem() const + { return item; } + + inline const QWidget *widget() const + { return 0; } + + inline void update() { + item->d_ptr->updateDueToGraphicsEffect = true; + item->update(); + item->d_ptr->updateDueToGraphicsEffect = false; + } + + inline void effectBoundingRectChanged() + { item->prepareGeometryChange(); } + + inline bool isPixmap() const + { + return item->type() == QGraphicsPixmapItem::Type + && !(item->flags() & QGraphicsItem::ItemIsSelectable) + && item->d_ptr->children.size() == 0; + //|| (item->d_ptr->isObject && qobject_cast(q_func())); + } + + inline const QStyleOption *styleOption() const + { return info ? info->option : 0; } + + inline QRect deviceRect() const + { + if (!info || !info->widget) { + qWarning("QGraphicsEffectSource::deviceRect: Not yet implemented, lacking device context"); + return QRect(); + } + return info->widget->rect(); + } + + QRectF boundingRect(Qt::CoordinateSystem system) const; + void draw(QPainter *); + QPixmap pixmap(Qt::CoordinateSystem system, + QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const; + QRect paddedEffectRect(Qt::CoordinateSystem system, QGraphicsEffect::PixmapPadMode mode, const QRectF &sourceRect, bool *unpadded = 0) const; + + QGraphicsItem *item; + QGraphicsItemPaintInfo *info; + QTransform lastEffectTransform; +}; +#endif //QT_NO_GRAPHICSEFFECT + +/*! + Returns true if \a item1 is on top of \a item2. + The items don't need to be siblings. + + \internal +*/ +inline bool qt_closestItemFirst(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + // Siblings? Just check their z-values. + const QGraphicsItemPrivate *d1 = item1->d_ptr.data(); + const QGraphicsItemPrivate *d2 = item2->d_ptr.data(); + if (d1->parent == d2->parent) + return qt_closestLeaf(item1, item2); + + // Find common ancestor, and each item's ancestor closest to the common + // ancestor. + int item1Depth = d1->depth(); + int item2Depth = d2->depth(); + const QGraphicsItem *p = item1; + const QGraphicsItem *t1 = item1; + while (item1Depth > item2Depth && (p = p->d_ptr->parent)) { + if (p == item2) { + // item2 is one of item1's ancestors; item1 is on top + return !(t1->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); + } + t1 = p; + --item1Depth; + } + p = item2; + const QGraphicsItem *t2 = item2; + while (item2Depth > item1Depth && (p = p->d_ptr->parent)) { + if (p == item1) { + // item1 is one of item2's ancestors; item1 is not on top + return (t2->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent); + } + t2 = p; + --item2Depth; + } + + // item1Ancestor is now at the same level as item2Ancestor, but not the same. + const QGraphicsItem *p1 = t1; + const QGraphicsItem *p2 = t2; + while (t1 && t1 != t2) { + p1 = t1; + p2 = t2; + t1 = t1->d_ptr->parent; + t2 = t2->d_ptr->parent; + } + + // in case we have a common ancestor, we compare the immediate children in the ancestor's path. + // otherwise we compare the respective items' topLevelItems directly. + return qt_closestLeaf(p1, p2); +} + +/*! + Returns true if \a item2 is on top of \a item1. + The items don't need to be siblings. + + \internal +*/ +inline bool qt_closestItemLast(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + return qt_closestItemFirst(item2, item1); +} + +/*! + \internal +*/ +inline bool qt_closestLeaf(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ + // Return true if sibling item1 is on top of item2. + const QGraphicsItemPrivate *d1 = item1->d_ptr.data(); + const QGraphicsItemPrivate *d2 = item2->d_ptr.data(); + bool f1 = d1->flags & QGraphicsItem::ItemStacksBehindParent; + bool f2 = d2->flags & QGraphicsItem::ItemStacksBehindParent; + if (f1 != f2) + return f2; + if (d1->z != d2->z) + return d1->z > d2->z; + return d1->siblingIndex > d2->siblingIndex; +} + +/*! + \internal +*/ +inline bool qt_notclosestLeaf(const QGraphicsItem *item1, const QGraphicsItem *item2) +{ return qt_closestLeaf(item2, item1); } + +/* + return the full transform of the item to the parent. This include the position and all the transform data +*/ +inline QTransform QGraphicsItemPrivate::transformToParent() const +{ + QTransform matrix; + combineTransformToParent(&matrix); + return matrix; +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::ensureSortedChildren() +{ + if (needSortChildren) { + needSortChildren = 0; + sequentialOrdering = 1; + if (children.isEmpty()) + return; + qSort(children.begin(), children.end(), qt_notclosestLeaf); + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->d_ptr->siblingIndex != i) { + sequentialOrdering = 0; + break; + } + } + } +} + +/*! + \internal +*/ +inline bool QGraphicsItemPrivate::insertionOrder(QGraphicsItem *a, QGraphicsItem *b) +{ + return a->d_ptr->siblingIndex < b->d_ptr->siblingIndex; +} + +/*! + \internal +*/ +inline void QGraphicsItemPrivate::markParentDirty(bool updateBoundingRect) +{ + QGraphicsItemPrivate *parentp = this; +#ifndef QT_NO_GRAPHICSEFFECT + if (updateBoundingRect && parentp->graphicsEffect && !parentp->inSetPosHelper) { + parentp->notifyInvalidated = 1; + static_cast(parentp->graphicsEffect->d_func() + ->source->d_func())->invalidateCache(); + } +#endif + while (parentp->parent) { + parentp = parentp->parent->d_ptr.data(); + parentp->dirtyChildren = 1; + + if (updateBoundingRect) { + parentp->dirtyChildrenBoundingRect = 1; + // ### Only do this if the parent's effect applies to the entire subtree. + parentp->notifyBoundingRectChanged = 1; + } +#ifndef QT_NO_GRAPHICSEFFECT + if (parentp->graphicsEffect) { + if (updateBoundingRect) { + static_cast(parentp->graphicsEffect->d_func() + ->source->d_func())->invalidateCache(); + parentp->notifyInvalidated = 1; + } + if (parentp->scene && parentp->graphicsEffect->isEnabled()) { + parentp->dirty = 1; + parentp->fullUpdatePending = 1; + } + } +#endif + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/gui/graphicsview/qgraphicsitemanimation.cpp b/src/gui/graphicsview/qgraphicsitemanimation.cpp new file mode 100644 index 0000000000..7eedc8dd3d --- /dev/null +++ b/src/gui/graphicsview/qgraphicsitemanimation.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsItemAnimation + \brief The QGraphicsItemAnimation class provides simple animation + support for QGraphicsItem. + \since 4.2 + \ingroup graphicsview-api + \deprecated + + The QGraphicsItemAnimation class animates a QGraphicsItem. You can + schedule changes to the item's transformation matrix at + specified steps. The QGraphicsItemAnimation class has a + current step value. When this value changes the transformations + scheduled at that step are performed. The current step of the + animation is set with the \c setStep() function. + + QGraphicsItemAnimation will do a simple linear interpolation + between the nearest adjacent scheduled changes to calculate the + matrix. For instance, if you set the position of an item at values + 0.0 and 1.0, the animation will show the item moving in a straight + line between these positions. The same is true for scaling and + rotation. + + It is usual to use the class with a QTimeLine. The timeline's + \l{QTimeLine::}{valueChanged()} signal is then connected to the + \c setStep() slot. For example, you can set up an item for rotation + by calling \c setRotationAt() for different step values. + The animations timeline is set with the setTimeLine() function. + + An example animation with a timeline follows: + + \snippet doc/src/snippets/timeline/main.cpp 0 + + Note that steps lie between 0.0 and 1.0. It may be necessary to use + \l{QTimeLine::}{setUpdateInterval()}. The default update interval + is 40 ms. A scheduled transformation cannot be removed when set, + so scheduling several transformations of the same kind (e.g., + rotations) at the same step is not recommended. + + \sa QTimeLine, {Graphics View Framework} +*/ + +#include "qgraphicsitemanimation.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsItemAnimationPrivate +{ +public: + inline QGraphicsItemAnimationPrivate() + : q(0), timeLine(0), item(0), step(0) + { } + + QGraphicsItemAnimation *q; + + QPointer timeLine; + QGraphicsItem *item; + + QPointF startPos; + QMatrix startMatrix; + + qreal step; + + struct Pair { + Pair(qreal a, qreal b) : step(a), value(b) {} + bool operator <(const Pair &other) const + { return step < other.step; } + bool operator==(const Pair &other) const + { return step == other.step; } + qreal step; + qreal value; + }; + QList xPosition; + QList yPosition; + QList rotation; + QList verticalScale; + QList horizontalScale; + QList verticalShear; + QList horizontalShear; + QList xTranslation; + QList yTranslation; + + qreal linearValueForStep(qreal step, QList *source, qreal defaultValue = 0); + void insertUniquePair(qreal step, qreal value, QList *binList, const char* method); +}; + +qreal QGraphicsItemAnimationPrivate::linearValueForStep(qreal step, QList *source, qreal defaultValue) +{ + if (source->isEmpty()) + return defaultValue; + step = qMin(qMax(step, 0), 1); + + if (step == 1) + return source->last().value; + + qreal stepBefore = 0; + qreal stepAfter = 1; + qreal valueBefore = source->first().step == 0 ? source->first().value : defaultValue; + qreal valueAfter = source->last().value; + + // Find the closest step and value before the given step. + for (int i = 0; i < source->size() && step >= source->at(i).step; ++i) { + stepBefore = source->at(i).step; + valueBefore = source->at(i).value; + } + + // Find the closest step and value after the given step. + for (int j = source->size() - 1; j >= 0 && step < source->at(j).step; --j) { + stepAfter = source->at(j).step; + valueAfter = source->at(j).value; + } + + // Do a simple linear interpolation. + return valueBefore + (valueAfter - valueBefore) * ((step - stepBefore) / (stepAfter - stepBefore)); +} + +void QGraphicsItemAnimationPrivate::insertUniquePair(qreal step, qreal value, QList *binList, const char* method) +{ + if (step < 0.0 || step > 1.0) { + qWarning("QGraphicsItemAnimation::%s: invalid step = %f", method, step); + return; + } + + Pair pair(step, value); + + QList::iterator result = qBinaryFind(binList->begin(), binList->end(), pair); + if (result != binList->end()) + result->value = value; + else { + *binList << pair; + qSort(binList->begin(), binList->end()); + } +} + +/*! + Constructs an animation object with the given \a parent. +*/ +QGraphicsItemAnimation::QGraphicsItemAnimation(QObject *parent) + : QObject(parent), d(new QGraphicsItemAnimationPrivate) +{ + d->q = this; +} + +/*! + Destroys the animation object. +*/ +QGraphicsItemAnimation::~QGraphicsItemAnimation() +{ + delete d; +} + +/*! + Returns the item on which the animation object operates. + + \sa setItem() +*/ +QGraphicsItem *QGraphicsItemAnimation::item() const +{ + return d->item; +} + +/*! + Sets the specified \a item to be used in the animation. + + \sa item() +*/ +void QGraphicsItemAnimation::setItem(QGraphicsItem *item) +{ + d->item = item; + d->startPos = d->item->pos(); +} + +/*! + Returns the timeline object used to control the rate at which the animation + occurs. + + \sa setTimeLine() +*/ +QTimeLine *QGraphicsItemAnimation::timeLine() const +{ + return d->timeLine; +} + +/*! + Sets the timeline object used to control the rate of animation to the \a timeLine + specified. + + \sa timeLine() +*/ +void QGraphicsItemAnimation::setTimeLine(QTimeLine *timeLine) +{ + if (d->timeLine == timeLine) + return; + if (d->timeLine) + delete d->timeLine; + if (!timeLine) + return; + d->timeLine = timeLine; + connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(setStep(qreal))); +} + +/*! + Returns the position of the item at the given \a step value. + + \sa setPosAt() +*/ +QPointF QGraphicsItemAnimation::posAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::posAt: invalid step = %f", step); + + return QPointF(d->linearValueForStep(step, &d->xPosition, d->startPos.x()), + d->linearValueForStep(step, &d->yPosition, d->startPos.y())); +} + +/*! + \fn void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &point) + + Sets the position of the item at the given \a step value to the \a point specified. + + \sa posAt() +*/ +void QGraphicsItemAnimation::setPosAt(qreal step, const QPointF &pos) +{ + d->insertUniquePair(step, pos.x(), &d->xPosition, "setPosAt"); + d->insertUniquePair(step, pos.y(), &d->yPosition, "setPosAt"); +} + +/*! + Returns all explicitly inserted positions. + + \sa posAt(), setPosAt() +*/ +QList > QGraphicsItemAnimation::posList() const +{ + QList > list; + for (int i = 0; i < d->xPosition.size(); ++i) + list << QPair(d->xPosition.at(i).step, QPointF(d->xPosition.at(i).value, d->yPosition.at(i).value)); + + return list; +} + +/*! + Returns the matrix used to transform the item at the specified \a step value. +*/ +QMatrix QGraphicsItemAnimation::matrixAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::matrixAt: invalid step = %f", step); + + QMatrix matrix; + if (!d->rotation.isEmpty()) + matrix.rotate(rotationAt(step)); + if (!d->verticalScale.isEmpty()) + matrix.scale(horizontalScaleAt(step), verticalScaleAt(step)); + if (!d->verticalShear.isEmpty()) + matrix.shear(horizontalShearAt(step), verticalShearAt(step)); + if (!d->xTranslation.isEmpty()) + matrix.translate(xTranslationAt(step), yTranslationAt(step)); + return matrix; +} + +/*! + Returns the angle at which the item is rotated at the specified \a step value. + + \sa setRotationAt() +*/ +qreal QGraphicsItemAnimation::rotationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::rotationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->rotation); +} + +/*! + Sets the rotation of the item at the given \a step value to the \a angle specified. + + \sa rotationAt() +*/ +void QGraphicsItemAnimation::setRotationAt(qreal step, qreal angle) +{ + d->insertUniquePair(step, angle, &d->rotation, "setRotationAt"); +} + +/*! + Returns all explicitly inserted rotations. + + \sa rotationAt(), setRotationAt() +*/ +QList > QGraphicsItemAnimation::rotationList() const +{ + QList > list; + for (int i = 0; i < d->rotation.size(); ++i) + list << QPair(d->rotation.at(i).step, d->rotation.at(i).value); + + return list; +} + +/*! + Returns the horizontal translation of the item at the specified \a step value. + + \sa setTranslationAt() +*/ +qreal QGraphicsItemAnimation::xTranslationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::xTranslationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->xTranslation); +} + +/*! + Returns the vertical translation of the item at the specified \a step value. + + \sa setTranslationAt() +*/ +qreal QGraphicsItemAnimation::yTranslationAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::yTranslationAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->yTranslation); +} + +/*! + Sets the translation of the item at the given \a step value using the horizontal + and vertical coordinates specified by \a dx and \a dy. + + \sa xTranslationAt(), yTranslationAt() +*/ +void QGraphicsItemAnimation::setTranslationAt(qreal step, qreal dx, qreal dy) +{ + d->insertUniquePair(step, dx, &d->xTranslation, "setTranslationAt"); + d->insertUniquePair(step, dy, &d->yTranslation, "setTranslationAt"); +} + +/*! + Returns all explicitly inserted translations. + + \sa xTranslationAt(), yTranslationAt(), setTranslationAt() +*/ +QList > QGraphicsItemAnimation::translationList() const +{ + QList > list; + for (int i = 0; i < d->xTranslation.size(); ++i) + list << QPair(d->xTranslation.at(i).step, QPointF(d->xTranslation.at(i).value, d->yTranslation.at(i).value)); + + return list; +} + +/*! + Returns the vertical scale for the item at the specified \a step value. + + \sa setScaleAt() +*/ +qreal QGraphicsItemAnimation::verticalScaleAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::verticalScaleAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->verticalScale, 1); +} + +/*! + Returns the horizontal scale for the item at the specified \a step value. + + \sa setScaleAt() +*/ +qreal QGraphicsItemAnimation::horizontalScaleAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::horizontalScaleAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->horizontalScale, 1); +} + +/*! + Sets the scale of the item at the given \a step value using the horizontal and + vertical scale factors specified by \a sx and \a sy. + + \sa verticalScaleAt(), horizontalScaleAt() +*/ +void QGraphicsItemAnimation::setScaleAt(qreal step, qreal sx, qreal sy) +{ + d->insertUniquePair(step, sx, &d->horizontalScale, "setScaleAt"); + d->insertUniquePair(step, sy, &d->verticalScale, "setScaleAt"); +} + +/*! + Returns all explicitly inserted scales. + + \sa verticalScaleAt(), horizontalScaleAt(), setScaleAt() +*/ +QList > QGraphicsItemAnimation::scaleList() const +{ + QList > list; + for (int i = 0; i < d->horizontalScale.size(); ++i) + list << QPair(d->horizontalScale.at(i).step, QPointF(d->horizontalScale.at(i).value, d->verticalScale.at(i).value)); + + return list; +} + +/*! + Returns the vertical shear for the item at the specified \a step value. + + \sa setShearAt() +*/ +qreal QGraphicsItemAnimation::verticalShearAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::verticalShearAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->verticalShear, 0); +} + +/*! + Returns the horizontal shear for the item at the specified \a step value. + + \sa setShearAt() +*/ +qreal QGraphicsItemAnimation::horizontalShearAt(qreal step) const +{ + if (step < 0.0 || step > 1.0) + qWarning("QGraphicsItemAnimation::horizontalShearAt: invalid step = %f", step); + + return d->linearValueForStep(step, &d->horizontalShear, 0); +} + +/*! + Sets the shear of the item at the given \a step value using the horizontal and + vertical shear factors specified by \a sh and \a sv. + + \sa verticalShearAt(), horizontalShearAt() +*/ +void QGraphicsItemAnimation::setShearAt(qreal step, qreal sh, qreal sv) +{ + d->insertUniquePair(step, sh, &d->horizontalShear, "setShearAt"); + d->insertUniquePair(step, sv, &d->verticalShear, "setShearAt"); +} + +/*! + Returns all explicitly inserted shears. + + \sa verticalShearAt(), horizontalShearAt(), setShearAt() +*/ +QList > QGraphicsItemAnimation::shearList() const +{ + QList > list; + for (int i = 0; i < d->horizontalShear.size(); ++i) + list << QPair(d->horizontalShear.at(i).step, QPointF(d->horizontalShear.at(i).value, d->verticalShear.at(i).value)); + + return list; +} + +/*! + Clears the scheduled transformations used for the animation, but + retains the item and timeline. +*/ +void QGraphicsItemAnimation::clear() +{ + d->xPosition.clear(); + d->yPosition.clear(); + d->rotation.clear(); + d->verticalScale.clear(); + d->horizontalScale.clear(); + d->verticalShear.clear(); + d->horizontalShear.clear(); + d->xTranslation.clear(); + d->yTranslation.clear(); +} + +/*! + \fn void QGraphicsItemAnimation::setStep(qreal step) + + Sets the current \a step value for the animation, causing the + transformations scheduled at this step to be performed. +*/ +void QGraphicsItemAnimation::setStep(qreal x) +{ + if (x < 0.0 || x > 1.0) { + qWarning("QGraphicsItemAnimation::setStep: invalid step = %f", x); + return; + } + + beforeAnimationStep(x); + + d->step = x; + if (d->item) { + if (!d->xPosition.isEmpty() || !d->yPosition.isEmpty()) + d->item->setPos(posAt(x)); + if (!d->rotation.isEmpty() + || !d->verticalScale.isEmpty() + || !d->horizontalScale.isEmpty() + || !d->verticalShear.isEmpty() + || !d->horizontalShear.isEmpty() + || !d->xTranslation.isEmpty() + || !d->yTranslation.isEmpty()) { + d->item->setMatrix(d->startMatrix * matrixAt(x)); + } + } + + afterAnimationStep(x); +} + +/*! + Resets the item to its starting position and transformation. + + \obsolete + + You can call setStep(0) instead. +*/ +void QGraphicsItemAnimation::reset() +{ + if (!d->item) + return; + d->startPos = d->item->pos(); + d->startMatrix = d->item->matrix(); +} + +/*! + \fn void QGraphicsItemAnimation::beforeAnimationStep(qreal step) + + This method is meant to be overridden by subclassed that needs to + execute additional code before a new step takes place. The + animation \a step is provided for use in cases where the action + depends on its value. +*/ +void QGraphicsItemAnimation::beforeAnimationStep(qreal step) +{ + Q_UNUSED(step); +} + +/*! + \fn void QGraphicsItemAnimation::afterAnimationStep(qreal step) + + This method is meant to be overridden in subclasses that need to + execute additional code after a new step has taken place. The + animation \a step is provided for use in cases where the action + depends on its value. +*/ +void QGraphicsItemAnimation::afterAnimationStep(qreal step) +{ + Q_UNUSED(step); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsitemanimation.h b/src/gui/graphicsview/qgraphicsitemanimation.h new file mode 100644 index 0000000000..5dfbf4b81e --- /dev/null +++ b/src/gui/graphicsview/qgraphicsitemanimation.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSITEMANIMATION_H +#define QGRAPHICSITEMANIMATION_H + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsItem; +class QMatrix; +class QPointF; +class QTimeLine; +template struct QPair; + +class QGraphicsItemAnimationPrivate; +class Q_GUI_EXPORT QGraphicsItemAnimation : public QObject +{ + Q_OBJECT +public: + QGraphicsItemAnimation(QObject *parent = 0); + virtual ~QGraphicsItemAnimation(); + + QGraphicsItem *item() const; + void setItem(QGraphicsItem *item); + + QTimeLine *timeLine() const; + void setTimeLine(QTimeLine *timeLine); + + QPointF posAt(qreal step) const; + QList > posList() const; + void setPosAt(qreal step, const QPointF &pos); + + QMatrix matrixAt(qreal step) const; + + qreal rotationAt(qreal step) const; + QList > rotationList() const; + void setRotationAt(qreal step, qreal angle); + + qreal xTranslationAt(qreal step) const; + qreal yTranslationAt(qreal step) const; + QList > translationList() const; + void setTranslationAt(qreal step, qreal dx, qreal dy); + + qreal verticalScaleAt(qreal step) const; + qreal horizontalScaleAt(qreal step) const; + QList > scaleList() const; + void setScaleAt(qreal step, qreal sx, qreal sy); + + qreal verticalShearAt(qreal step) const; + qreal horizontalShearAt(qreal step) const; + QList > shearList() const; + void setShearAt(qreal step, qreal sh, qreal sv); + + void clear(); + +public Q_SLOTS: + void setStep(qreal x); + void reset(); + +protected: + virtual void beforeAnimationStep(qreal step); + virtual void afterAnimationStep(qreal step); + +private: + Q_DISABLE_COPY(QGraphicsItemAnimation) + QGraphicsItemAnimationPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_GRAPHICSVIEW +#endif diff --git a/src/gui/graphicsview/qgraphicslayout.cpp b/src/gui/graphicsview/qgraphicslayout.cpp new file mode 100644 index 0000000000..904a3deda2 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayout.cpp @@ -0,0 +1,451 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" + +#ifndef QT_NO_GRAPHICSVIEW +#include "qgraphicslayout.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslayoutitem_p.h" +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicsscene.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGraphicsLayout + \brief The QGraphicsLayout class provides the base class for all layouts + in Graphics View. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsLayout is an abstract class that defines a virtual API for + arranging QGraphicsWidget children and other QGraphicsLayoutItem objects + for a QGraphicsWidget. QGraphicsWidget assigns responsibility to a + QGraphicsLayout through QGraphicsWidget::setLayout(). As the widget + is resized, the layout will automatically arrange the widget's children. + QGraphicsLayout inherits QGraphicsLayoutItem, so, it can be managed by + any layout, including its own subclasses. + + \section1 Writing a Custom Layout + + You can use QGraphicsLayout as a base to write your own custom layout + (e.g., a flowlayout), but it is more common to use one of its subclasses + instead - QGraphicsLinearLayout or QGraphicsGridLayout. When creating + a custom layout, the following functions must be reimplemented as a bare + minimum: + + \table + \header \o Function \o Description + \row \o QGraphicsLayoutItem::setGeometry() + \o Notifies you when the geometry of the layout is set. You can + store the geometry in your own layout class in a reimplementation + of this function. + \row \o QGraphicsLayoutItem::sizeHint() + \o Returns the layout's size hints. + \row \o QGraphicsLayout::count() + \o Returns the number of items in your layout. + \row \o QGraphicsLayout::itemAt() + \o Returns a pointer to an item in your layout. + \row \o QGraphicsLayout::removeAt() + \o Removes an item from your layout without destroying it. + \endtable + + For more details on how to implement each function, refer to the individual + function documentation. + + Each layout defines its own API for arranging widgets and layout items. + For example, with a grid layout, you require a row and a + column index with optional row and column spans, alignment, spacing, and more. + A linear layout, however, requires a single row or column index to position its + items. For a grid layout, the order of insertion does not affect the layout in + any way, but for a linear layout, the order is essential. When writing your own + layout subclass, you are free to choose the API that best suits your layout. + + \section1 Activating the Layout + + When the layout's geometry changes, QGraphicsLayout immediately rearranges + all of its managed items by calling setGeometry() on each item. This + rearrangement is called \e activating the layout. + + QGraphicsLayout updates its own geometry to match the contentsRect() of the + QGraphicsLayoutItem it is managing. Thus, it will automatically rearrange all + its items when the widget is resized. QGraphicsLayout caches the sizes of all + its managed items to avoid calling setGeometry() too often. + + \note A QGraphicsLayout will have the same geometry as the contentsRect() + of the widget (not the layout) it is assigned to. + + \section2 Activating the Layout Implicitly + + The layout can be activated implicitly using one of two ways: by calling + activate() or by calling invalidate(). Calling activate() activates the layout + immediately. In contrast, calling invalidate() is delayed, as it posts a + \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed widget. Due + to event compression, the activate() will only be called once after control has + returned to the event loop. This is referred to as \e invalidating the layout. + Invalidating the layout also invalidates any cached information. Also, the + invalidate() function is a virtual function. So, you can invalidate your own + cache in a subclass of QGraphicsLayout by reimplementing this function. + + \section1 Event Handling + + QGraphicsLayout listens to events for the widget it manages through the + virtual widgetEvent() event handler. When the layout is assigned to a + widget, all events delivered to the widget are first processed by + widgetEvent(). This allows the layout to be aware of any relevant state + changes on the widget such as visibility changes or layout direction changes. + + \section1 Margin Handling + + The margins of a QGraphicsLayout can be modified by reimplementing + setContentsMargins() and getContentsMargins(). + +*/ + +/*! + Contructs a QGraphicsLayout object. + + \a parent is passed to QGraphicsLayoutItem's constructor and the + QGraphicsLayoutItem's isLayout argument is set to \e true. + + If \a parent is a QGraphicsWidget the layout will be installed + on that widget. (Note that installing a layout will delete the old one + installed.) +*/ +QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayoutItem(*new QGraphicsLayoutPrivate) +{ + setParentLayoutItem(parent); + if (parent && !parent->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QGraphicsItem *itemParent = parent->graphicsItem(); + if (itemParent && itemParent->isWidget()) { + static_cast(itemParent)->d_func()->setLayout_helper(this); + } else { + qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" + " neither a QGraphicsWidget nor QGraphicsLayout"); + } + } + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); + setOwnedByLayout(true); +} + +/*! + \internal +*/ +QGraphicsLayout::QGraphicsLayout(QGraphicsLayoutPrivate &dd, QGraphicsLayoutItem *parent) + : QGraphicsLayoutItem(dd) +{ + setParentLayoutItem(parent); + if (parent && !parent->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QGraphicsItem *itemParent = parent->graphicsItem(); + if (itemParent && itemParent->isWidget()) { + static_cast(itemParent)->d_func()->setLayout_helper(this); + } else { + qWarning("QGraphicsLayout::QGraphicsLayout: Attempt to create a layout with a parent that is" + " neither a QGraphicsWidget nor QGraphicsLayout"); + } + } + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, QSizePolicy::DefaultType); + setOwnedByLayout(true); +} + +/*! + Destroys the QGraphicsLayout object. +*/ +QGraphicsLayout::~QGraphicsLayout() +{ +} + +/*! + Sets the contents margins to \a left, \a top, \a right and \a bottom. The + default contents margins for toplevel layouts are style dependent + (by querying the pixelMetric for QStyle::PM_LayoutLeftMargin, + QStyle::PM_LayoutTopMargin, QStyle::PM_LayoutRightMargin and + QStyle::PM_LayoutBottomMargin). + + For sublayouts the default margins are 0. + + Changing the contents margins automatically invalidates the layout. + + \sa invalidate() +*/ +void QGraphicsLayout::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsLayout); + if (d->left == left && d->top == top && d->right == right && d->bottom == bottom) + return; + d->left = left; + d->right = right; + d->top = top; + d->bottom = bottom; + invalidate(); +} + +/*! + \reimp +*/ +void QGraphicsLayout::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsLayout); + d->getMargin(left, d->left, QStyle::PM_LayoutLeftMargin); + d->getMargin(top, d->top, QStyle::PM_LayoutTopMargin); + d->getMargin(right, d->right, QStyle::PM_LayoutRightMargin); + d->getMargin(bottom, d->bottom, QStyle::PM_LayoutBottomMargin); +} + +/*! + Activates the layout, causing all items in the layout to be immediately + rearranged. This function is based on calling count() and itemAt(), and + then calling setGeometry() on all items sequentially. When activated, + the layout will adjust its geometry to its parent's contentsRect(). + The parent will then invalidate any layout of its own. + + If called in sequence or recursively, e.g., by one of the arranged items + in response to being resized, this function will do nothing. + + Note that the layout is free to use geometry caching to optimize this + process. To forcefully invalidate any such cache, you can call + invalidate() before calling activate(). + + \sa invalidate() +*/ +void QGraphicsLayout::activate() +{ + Q_D(QGraphicsLayout); + if (d->activated) + return; + + d->activateRecursive(this); + + // we don't call activate on a sublayout, but somebody might. + // Therefore, we walk to the parentitem of the toplevel layout. + QGraphicsLayoutItem *parentItem = this; + while (parentItem && parentItem->isLayout()) + parentItem = parentItem->parentLayoutItem(); + if (!parentItem) + return; + Q_ASSERT(!parentItem->isLayout()); + + setGeometry(parentItem->contentsRect()); // relayout children + + // ### bug, should be parentItem ? + parentLayoutItem()->updateGeometry(); // bubble up; will set activated to false + // ### too many resizes? maybe we should walk up the chain to the + // ### top-level layouted layoutItem and call activate there. +} + +/*! + Returns true if the layout is currently being activated; otherwise, + returns false. If the layout is being activated, this means that it is + currently in the process of rearranging its items (i.e., the activate() + function has been called, and has not yet returned). + + \sa activate(), invalidate() +*/ +bool QGraphicsLayout::isActivated() const +{ + Q_D(const QGraphicsLayout); + return d->activated; +} + +/*! + Clears any cached geometry and size hint information in the layout, and + posts a \l{QEvent::LayoutRequest}{LayoutRequest} event to the managed + parent QGraphicsLayoutItem. + + \sa activate(), setGeometry() +*/ +void QGraphicsLayout::invalidate() +{ + // only mark layouts as invalid (activated = false) if we can post a LayoutRequest event. + QGraphicsLayoutItem *layoutItem = this; + while (layoutItem && layoutItem->isLayout()) { + // we could call updateGeometry(), but what if that method + // does not call the base implementation? In addition, updateGeometry() + // does more than we need. + layoutItem->d_func()->sizeHintCacheDirty = true; + layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; + layoutItem = layoutItem->parentLayoutItem(); + } + if (layoutItem) { + layoutItem->d_func()->sizeHintCacheDirty = true; + layoutItem->d_func()->sizeHintWithConstraintCacheDirty = true; + } + + bool postIt = layoutItem ? !layoutItem->isLayout() : false; + if (postIt) { + layoutItem = this; + while (layoutItem && layoutItem->isLayout() + && static_cast(layoutItem)->d_func()->activated) { + static_cast(layoutItem)->d_func()->activated = false; + layoutItem = layoutItem->parentLayoutItem(); + } + if (layoutItem && !layoutItem->isLayout()) { + // If a layout has a parent that is not a layout it must be a QGraphicsWidget. + QApplication::postEvent(static_cast(layoutItem), new QEvent(QEvent::LayoutRequest)); + } + } +} + +/*! + \reimp +*/ +void QGraphicsLayout::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); + if (QGraphicsLayoutItem *parentItem = parentLayoutItem()) { + if (parentItem->isLayout()) { + parentItem->updateGeometry(); + } else { + invalidate(); + } + } +} + +/*! + This virtual event handler receives all events for the managed + widget. QGraphicsLayout uses this event handler to listen for layout + related events such as geometry changes, layout changes or layout + direction changes. + + \a e is a pointer to the event. + + You can reimplement this event handler to track similar events for your + own custom layout. + + \sa QGraphicsWidget::event(), QGraphicsItem::sceneEvent() +*/ +void QGraphicsLayout::widgetEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::GraphicsSceneResize: + if (isActivated()) { + setGeometry(parentLayoutItem()->contentsRect()); + } else { + activate(); // relies on that activate() will call updateGeometry() + } + break; + case QEvent::LayoutRequest: + activate(); + break; + case QEvent::LayoutDirectionChange: + invalidate(); + break; + default: + break; + } +} + +/*! + \fn virtual int QGraphicsLayout::count() const = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to return the number of items in the layout. + + The subclass is free to decide how to store the items. + + \sa itemAt(), removeAt() +*/ + +/*! + \fn virtual QGraphicsLayoutItem *QGraphicsLayout::itemAt(int i) const = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to return a pointer to the item at index \a i. The + reimplementation can assume that \a i is valid (i.e., it respects the + value of count()). + Together with count(), it is provided as a means of iterating over all items in a layout. + + The subclass is free to decide how to store the items, and the visual arrangement + does not have to be reflected through this function. + + \sa count(), removeAt() +*/ + +/*! + \fn virtual void QGraphicsLayout::removeAt(int index) = 0 + + This pure virtual function must be reimplemented in a subclass of + QGraphicsLayout to remove the item at \a index. The + reimplementation can assume that \a index is valid (i.e., it + respects the value of count()). + + The implementation must ensure that the parentLayoutItem() of + the removed item does not point to this layout, since the item is + considered to be removed from the layout hierarchy. + + If the layout is to be reused between applications, we recommend + that the layout deletes the item, but the graphics view framework + does not depend on this. + + The subclass is free to decide how to store the items. + + \sa itemAt(), count() +*/ + +/*! + \since 4.6 + + This function is a convenience function provided for custom layouts, and will go through + all items in the layout and reparent their graphics items to the closest QGraphicsWidget + ancestor of the layout. + + If \a layoutItem is already in a different layout, it will be removed from that layout. + + If custom layouts want special behaviour they can ignore to use this function, and implement + their own behaviour. + + \sa graphicsItem() + */ +void QGraphicsLayout::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) +{ + Q_D(QGraphicsLayout); + d->addChildLayoutItem(layoutItem); +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicslayout.h b/src/gui/graphicsview/qgraphicslayout.h new file mode 100644 index 0000000000..c622fb881e --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayout.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUT_H +#define QGRAPHICSLAYOUT_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLayoutPrivate; +class QGraphicsLayoutItem; +class QGraphicsWidget; + +class Q_GUI_EXPORT QGraphicsLayout : public QGraphicsLayoutItem +{ +public: + QGraphicsLayout(QGraphicsLayoutItem *parent = 0); + ~QGraphicsLayout(); + + void setContentsMargins(qreal left, qreal top, qreal right, qreal bottom); + void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + + void activate(); + bool isActivated() const; + virtual void invalidate(); + virtual void updateGeometry(); + + virtual void widgetEvent(QEvent *e); + + virtual int count() const = 0; + virtual QGraphicsLayoutItem *itemAt(int i) const = 0; + virtual void removeAt(int index) = 0; + +protected: + QGraphicsLayout(QGraphicsLayoutPrivate &, QGraphicsLayoutItem *); + void addChildLayoutItem(QGraphicsLayoutItem *layoutItem); + +private: + Q_DISABLE_COPY(QGraphicsLayout) + Q_DECLARE_PRIVATE(QGraphicsLayout) + friend class QGraphicsWidget; +}; + +Q_DECLARE_INTERFACE(QGraphicsLayout, "com.trolltech.Qt.QGraphicsLayout") + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/gui/graphicsview/qgraphicslayout_p.cpp b/src/gui/graphicsview/qgraphicslayout_p.cpp new file mode 100644 index 0000000000..c325602cc6 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayout_p.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout_p.h" +#include "qgraphicslayout.h" +#include "qgraphicswidget.h" +#include "qapplication.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + + \a mw is the new parent. all items in the layout will be a child of \a mw. + */ +void QGraphicsLayoutPrivate::reparentChildItems(QGraphicsItem *newParent) +{ + Q_Q(QGraphicsLayout); + int n = q->count(); + //bool mwVisible = mw && mw->isVisible(); + for (int i = 0; i < n; ++i) { + QGraphicsLayoutItem *layoutChild = q->itemAt(i); + if (!layoutChild) { + // Skip stretch items + continue; + } + if (layoutChild->isLayout()) { + QGraphicsLayout *l = static_cast(layoutChild); + l->d_func()->reparentChildItems(newParent); + } else if (QGraphicsItem *itemChild = layoutChild->graphicsItem()){ + QGraphicsItem *childParent = itemChild->parentItem(); +#ifdef QT_DEBUG + if (childParent && childParent != newParent && itemChild->isWidget() && qt_graphicsLayoutDebug()) { + QGraphicsWidget *w = static_cast(layoutChild); + qWarning("QGraphicsLayout::addChildLayout: widget %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().constData()); + } +#endif + if (childParent != newParent) + itemChild->setParentItem(newParent); + } + } +} + +void QGraphicsLayoutPrivate::getMargin(qreal *result, qreal userMargin, QStyle::PixelMetric pm) const +{ + if (!result) + return; + Q_Q(const QGraphicsLayout); + + QGraphicsLayoutItem *parent = q->parentLayoutItem(); + if (userMargin >= 0.0) { + *result = userMargin; + } else if (!parent) { + *result = 0.0; + } else if (parent->isLayout()) { // sublayouts have 0 margin by default + *result = 0.0; + } else { + *result = 0.0; + if (QGraphicsItem *layoutParentItem = parentItem()) { + if (layoutParentItem->isWidget()) + *result = (qreal)static_cast(layoutParentItem)->style()->pixelMetric(pm, 0); + } + } +} + +Qt::LayoutDirection QGraphicsLayoutPrivate::visualDirection() const +{ + if (QGraphicsItem *maybeWidget = parentItem()) { + if (maybeWidget->isWidget()) + return static_cast(maybeWidget)->layoutDirection(); + } + return QApplication::layoutDirection(); +} + +static bool removeLayoutItemFromLayout(QGraphicsLayout *lay, QGraphicsLayoutItem *layoutItem) +{ + if (!lay) + return false; + + for (int i = lay->count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *child = lay->itemAt(i); + if (child && child->isLayout()) { + if (removeLayoutItemFromLayout(static_cast(child), layoutItem)) + return true; + } else if (child == layoutItem) { + lay->removeAt(i); + return true; + } + } + return false; +} + +/*! + \internal + + This function is called from subclasses to add a layout item \a layoutItem + to a layout. + + It takes care of automatically reparenting graphics items, if needed. + + If \a layoutItem is a is already in a layout, it will remove it from that layout. + +*/ +void QGraphicsLayoutPrivate::addChildLayoutItem(QGraphicsLayoutItem *layoutItem) +{ + Q_Q(QGraphicsLayout); + if (QGraphicsLayoutItem *maybeLayout = layoutItem->parentLayoutItem()) { + if (maybeLayout->isLayout()) + removeLayoutItemFromLayout(static_cast(maybeLayout), layoutItem); + } + layoutItem->setParentLayoutItem(q); + if (layoutItem->isLayout()) { + if (QGraphicsItem *parItem = parentItem()) { + static_cast(layoutItem)->d_func()->reparentChildItems(parItem); + } + } else { + if (QGraphicsItem *item = layoutItem->graphicsItem()) { + QGraphicsItem *newParent = parentItem(); + QGraphicsItem *oldParent = item->parentItem(); + if (oldParent == newParent || !newParent) + return; + +#ifdef QT_DEBUG + if (oldParent && item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + qWarning("QGraphicsLayout::addChildLayoutItem: %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().constData()); + } +#endif + + item->setParentItem(newParent); + } + } +} + +void QGraphicsLayoutPrivate::activateRecursive(QGraphicsLayoutItem *item) +{ + if (item->isLayout()) { + QGraphicsLayout *layout = static_cast(item); + if (layout->d_func()->activated) + layout->invalidate(); + + for (int i = layout->count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *childItem = layout->itemAt(i); + if (childItem) + activateRecursive(childItem); + } + layout->d_func()->activated = true; + } +} + + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicslayout_p.h b/src/gui/graphicsview/qgraphicslayout_p.h new file mode 100644 index 0000000000..5358ec3cf4 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayout_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUT_P_H +#define QGRAPHICSLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicslayoutitem_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class QGraphicsWidget; + +#ifdef QT_DEBUG +inline bool qt_graphicsLayoutDebug() +{ + static int checked_env = -1; + if(checked_env == -1) + checked_env = !!qgetenv("QT_GRAPHICSLAYOUT_DEBUG").toInt(); + return checked_env; +} +#endif + + +class QLayoutStyleInfo +{ +public: + inline QLayoutStyleInfo() { invalidate(); } + inline QLayoutStyleInfo(QStyle *style, QWidget *widget) + : m_valid(true), m_style(style), m_widget(widget) + { + Q_ASSERT(style); + if (widget) //### + m_styleOption.initFrom(widget); + m_defaultSpacing[0] = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + m_defaultSpacing[1] = style->pixelMetric(QStyle::PM_LayoutVerticalSpacing); + } + + inline void invalidate() { m_valid = false; m_style = 0; m_widget = 0; } + + inline QStyle *style() const { return m_style; } + inline QWidget *widget() const { return m_widget; } + + inline bool operator==(const QLayoutStyleInfo &other) + { return m_style == other.m_style && m_widget == other.m_widget; } + inline bool operator!=(const QLayoutStyleInfo &other) + { return !(*this == other); } + + inline void setDefaultSpacing(Qt::Orientation o, qreal spacing){ + if (spacing >= 0) + m_defaultSpacing[o - 1] = spacing; + } + + inline qreal defaultSpacing(Qt::Orientation o) const { + return m_defaultSpacing[o - 1]; + } + + inline qreal perItemSpacing(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation) const + { + Q_ASSERT(style()); + return style()->layoutSpacing(control1, control2, orientation, &m_styleOption, widget()); + } +private: + bool m_valid; + QStyle *m_style; + QWidget *m_widget; + QStyleOption m_styleOption; + qreal m_defaultSpacing[2]; +}; + +class Q_AUTOTEST_EXPORT QGraphicsLayoutPrivate : public QGraphicsLayoutItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLayout) + +public: + QGraphicsLayoutPrivate() : QGraphicsLayoutItemPrivate(0, true), left(-1.0), top(-1.0), right(-1.0), bottom(-1.0), + activated(true) { } + + void reparentChildItems(QGraphicsItem *newParent); + void getMargin(qreal *result, qreal userMargin, QStyle::PixelMetric pm) const; + Qt::LayoutDirection visualDirection() const; + + void addChildLayoutItem(QGraphicsLayoutItem *item); + void activateRecursive(QGraphicsLayoutItem *item); + + qreal left, top, right, bottom; + bool activated; +}; + + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/gui/graphicsview/qgraphicslayoutitem.cpp b/src/gui/graphicsview/qgraphicslayoutitem.cpp new file mode 100644 index 0000000000..c064e01502 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayoutitem.cpp @@ -0,0 +1,935 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicsscene.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslayoutitem_p.h" +#include "qwidget.h" +#include "qgraphicswidget.h" + +#include + +QT_BEGIN_NAMESPACE + +/* + COMBINE_SIZE() is identical to combineSize(), except that it + doesn't evaluate 'size' unless necessary. +*/ +#define COMBINE_SIZE(result, size) \ + do { \ + if ((result).width() < 0 || (result).height() < 0) \ + combineSize((result), (size)); \ + } while (false) + +static void combineSize(QSizeF &result, const QSizeF &size) +{ + if (result.width() < 0) + result.setWidth(size.width()); + if (result.height() < 0) + result.setHeight(size.height()); +} + +static void boundSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() < result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() < result.height()) + result.setHeight(size.height()); +} + +static void expandSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() > result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() > result.height()) + result.setHeight(size.height()); +} + +static void normalizeHints(qreal &minimum, qreal &preferred, qreal &maximum, qreal &descent) +{ + if (minimum >= 0 && maximum >= 0 && minimum > maximum) + minimum = maximum; + + if (preferred >= 0) { + if (minimum >= 0 && preferred < minimum) { + preferred = minimum; + } else if (maximum >= 0 && preferred > maximum) { + preferred = maximum; + } + } + + if (minimum >= 0 && descent > minimum) + descent = minimum; +} + +/*! + \internal +*/ +QGraphicsLayoutItemPrivate::QGraphicsLayoutItemPrivate(QGraphicsLayoutItem *par, bool layout) + : parent(par), userSizeHints(0), isLayout(layout), ownedByLayout(false), graphicsItem(0) +{ +} + +/*! + \internal +*/ +QGraphicsLayoutItemPrivate::~QGraphicsLayoutItemPrivate() +{ + // Remove any lazily allocated data + delete[] userSizeHints; +} + +/*! + \internal +*/ +void QGraphicsLayoutItemPrivate::init() +{ + sizeHintCacheDirty = true; + sizeHintWithConstraintCacheDirty = true; +} + +/*! + \internal +*/ +QSizeF *QGraphicsLayoutItemPrivate::effectiveSizeHints(const QSizeF &constraint) const +{ + Q_Q(const QGraphicsLayoutItem); + QSizeF *sizeHintCache; + const bool hasConstraint = constraint.width() >= 0 || constraint.height() >= 0; + if (hasConstraint) { + if (!sizeHintWithConstraintCacheDirty && constraint == cachedConstraint) + return cachedSizeHintsWithConstraints; + sizeHintCache = cachedSizeHintsWithConstraints; + } else { + if (!sizeHintCacheDirty) + return cachedSizeHints; + sizeHintCache = cachedSizeHints; + } + + for (int i = 0; i < Qt::NSizeHints; ++i) { + sizeHintCache[i] = constraint; + if (userSizeHints) + combineSize(sizeHintCache[i], userSizeHints[i]); + } + + QSizeF &minS = sizeHintCache[Qt::MinimumSize]; + QSizeF &prefS = sizeHintCache[Qt::PreferredSize]; + QSizeF &maxS = sizeHintCache[Qt::MaximumSize]; + QSizeF &descentS = sizeHintCache[Qt::MinimumDescent]; + + normalizeHints(minS.rwidth(), prefS.rwidth(), maxS.rwidth(), descentS.rwidth()); + normalizeHints(minS.rheight(), prefS.rheight(), maxS.rheight(), descentS.rheight()); + + // if the minimum, preferred and maximum sizes contradict each other + // (e.g. the minimum is larger than the maximum) we give priority to + // the maximum size, then the minimum size and finally the preferred size + COMBINE_SIZE(maxS, q->sizeHint(Qt::MaximumSize, maxS)); + combineSize(maxS, QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + expandSize(maxS, prefS); + expandSize(maxS, minS); + boundSize(maxS, QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); + + COMBINE_SIZE(minS, q->sizeHint(Qt::MinimumSize, minS)); + expandSize(minS, QSizeF(0, 0)); + boundSize(minS, prefS); + boundSize(minS, maxS); + + COMBINE_SIZE(prefS, q->sizeHint(Qt::PreferredSize, prefS)); + expandSize(prefS, minS); + boundSize(prefS, maxS); + + // Not supported yet + // COMBINE_SIZE(descentS, q->sizeHint(Qt::MinimumDescent, constraint)); + + if (hasConstraint) { + cachedConstraint = constraint; + sizeHintWithConstraintCacheDirty = false; + } else { + sizeHintCacheDirty = false; + } + return sizeHintCache; +} + + +/*! + \internal + + Returns the parent item of this layout, or 0 if this layout is + not installed on any widget. + + If this is the item that the layout is installed on, it will return "itself". + + If the layout is a sub-layout, this function returns the parent + widget of the parent layout. + + Note that it will traverse up the layout item hierarchy instead of just calling + QGraphicsItem::parentItem(). This is on purpose. + + \sa parent() +*/ +QGraphicsItem *QGraphicsLayoutItemPrivate::parentItem() const +{ + Q_Q(const QGraphicsLayoutItem); + + const QGraphicsLayoutItem *parent = q; + while (parent && parent->isLayout()) { + parent = parent->parentLayoutItem(); + } + return parent ? parent->graphicsItem() : 0; +} + +/*! + \internal + + Ensures that userSizeHints is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsLayoutItemPrivate::ensureUserSizeHints() +{ + if (!userSizeHints) + userSizeHints = new QSizeF[Qt::NSizeHints]; +} + +/*! + \internal + + Sets the user size hint \a which to \a size. Use an invalid size to unset the size hint. + */ +void QGraphicsLayoutItemPrivate::setSize(Qt::SizeHint which, const QSizeF &size) +{ + Q_Q(QGraphicsLayoutItem); + + if (userSizeHints) { + if (size == userSizeHints[which]) + return; + } else if (size.width() < 0 && size.height() < 0) { + return; + } + + ensureUserSizeHints(); + userSizeHints[which] = size; + q->updateGeometry(); +} + +/*! + \internal + + Sets the width of the user size hint \a which to \a width. + */ +void QGraphicsLayoutItemPrivate::setSizeComponent( + Qt::SizeHint which, SizeComponent component, qreal value) +{ + Q_Q(QGraphicsLayoutItem); + ensureUserSizeHints(); + qreal &userValue = (component == Width) + ? userSizeHints[which].rwidth() + : userSizeHints[which].rheight(); + if (value == userValue) + return; + userValue = value; + q->updateGeometry(); +} + + +bool QGraphicsLayoutItemPrivate::hasHeightForWidth() const +{ + Q_Q(const QGraphicsLayoutItem); + if (isLayout) { + const QGraphicsLayout *l = static_cast(q); + for (int i = l->count() - 1; i >= 0; --i) { + if (QGraphicsLayoutItemPrivate::get(l->itemAt(i))->hasHeightForWidth()) + return true; + } + } else if (QGraphicsItem *item = q->graphicsItem()) { + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (w->layout()) { + return QGraphicsLayoutItemPrivate::get(w->layout())->hasHeightForWidth(); + } + } + } + return q->sizePolicy().hasHeightForWidth(); +} + +bool QGraphicsLayoutItemPrivate::hasWidthForHeight() const +{ + Q_Q(const QGraphicsLayoutItem); + if (isLayout) { + const QGraphicsLayout *l = static_cast(q); + for (int i = l->count() - 1; i >= 0; --i) { + if (QGraphicsLayoutItemPrivate::get(l->itemAt(i))->hasWidthForHeight()) + return true; + } + } else if (QGraphicsItem *item = q->graphicsItem()) { + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (w->layout()) { + return QGraphicsLayoutItemPrivate::get(w->layout())->hasWidthForHeight(); + } + } + } + return q->sizePolicy().hasWidthForHeight(); +} + +/*! + \class QGraphicsLayoutItem + \brief The QGraphicsLayoutItem class can be inherited to allow your custom + items to be managed by layouts. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsLayoutItem is an abstract class that defines a set of virtual + functions describing sizes, size policies, and size hints for any object + arranged by QGraphicsLayout. The API contains functions relevant + for both the item itself and for the user of the item as most of + QGraphicsLayoutItem's functions are also part of the subclass' public API. + + In most cases, existing layout-aware classes such as QGraphicsWidget and + QGraphicsLayout already provide the functionality you require. However, + subclassing these classes will enable you to create both graphical + elements that work well with layouts (QGraphicsWidget) or custom layouts + (QGraphicsLayout). + + \section1 Subclassing QGraphicsLayoutItem + + If you create a subclass of QGraphicsLayoutItem and reimplement its + virtual functions, you will enable the layout to resize and position your + item along with other QGraphicsLayoutItems including QGraphicsWidget + and QGraphicsLayout. + + You can start by reimplementing important functions: the protected + sizeHint() function, as well as the public setGeometry() + function. If you want your items to be aware of immediate geometry + changes, you can also reimplement updateGeometry(). + + The geometry, size hint, and size policy affect the item's size and + position. Calling setGeometry() will always resize and reposition the item + immediately. Normally, this function is called by QGraphicsLayout after + the layout has been activated, but it can also be called by the item's user + at any time. + + The sizeHint() function returns the item' minimum, preferred and maximum + size hints. You can override these properties by calling setMinimumSize(), + setPreferredSize() or setMaximumSize(). You can also use functions such as + setMinimumWidth() or setMaximumHeight() to set only the width or height + component if desired. + + The effectiveSizeHint() function, on the other hand, returns a size hint + for any given Qt::SizeHint, and guarantees that the returned size is bound + to the minimum and maximum sizes and size hints. You can set the item's + vertical and horizontal size policy by calling setSizePolicy(). The + sizePolicy property is used by the layout system to describe how this item + prefers to grow or shrink. + + \section1 Nesting QGraphicsLayoutItems + + QGraphicsLayoutItems can be nested within other QGraphicsLayoutItems, + similar to layouts that can contain sublayouts. This is done either by + passing a QGraphicsLayoutItem pointer to QGraphicsLayoutItem's + protected constructor, or by calling setParentLayoutItem(). The + parentLayoutItem() function returns a pointer to the item's layoutItem + parent. If the item's parent is 0 or if the parent does not inherit + from QGraphicsItem, the parentLayoutItem() function then returns 0. + isLayout() returns true if the QGraphicsLayoutItem subclass is itself a + layout, or false otherwise. + + Qt uses QGraphicsLayoutItem to provide layout functionality in the + \l{Graphics View Framework}, but in the future its use may spread + throughout Qt itself. + + \sa QGraphicsWidget, QGraphicsLayout, QGraphicsLinearLayout, + QGraphicsGridLayout +*/ + +/*! + Constructs the QGraphicsLayoutItem object. \a parent becomes the object's + parent. If \a isLayout is true the item is a layout, otherwise + \a isLayout is false. +*/ +QGraphicsLayoutItem::QGraphicsLayoutItem(QGraphicsLayoutItem *parent, bool isLayout) + : d_ptr(new QGraphicsLayoutItemPrivate(parent, isLayout)) +{ + Q_D(QGraphicsLayoutItem); + d->init(); + d->sizePolicy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->q_ptr = this; +} + +/*! + \internal +*/ +QGraphicsLayoutItem::QGraphicsLayoutItem(QGraphicsLayoutItemPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QGraphicsLayoutItem); + d->init(); + d->q_ptr = this; +} + +/*! + Destroys the QGraphicsLayoutItem object. +*/ +QGraphicsLayoutItem::~QGraphicsLayoutItem() +{ + QGraphicsLayoutItem *parentLI = parentLayoutItem(); + if (parentLI && parentLI->isLayout()) { + QGraphicsLayout *lay = static_cast(parentLI); + // this is not optimal + for (int i = lay->count() - 1; i >= 0; --i) { + if (lay->itemAt(i) == this) { + lay->removeAt(i); + break; + } + } + } +} + +/*! + \fn virtual QSizeF QGraphicsLayoutItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const = 0; + + This pure virtual function returns the size hint for \a which of the + QGraphicsLayoutItem, using the width or height of \a constraint to + constrain the output. + + Reimplement this function in a subclass of QGraphicsLayoutItem to + provide the necessary size hints for your items. + + \sa effectiveSizeHint() +*/ + +/*! + Sets the size policy to \a policy. The size policy describes how the item + should grow horizontally and vertically when arranged in a layout. + + QGraphicsLayoutItem's default size policy is (QSizePolicy::Fixed, + QSizePolicy::Fixed, QSizePolicy::DefaultType), but it is common for + subclasses to change the default. For example, QGraphicsWidget defaults + to (QSizePolicy::Preferred, QSizePolicy::Preferred, + QSizePolicy::DefaultType). + + \sa sizePolicy(), QWidget::sizePolicy() +*/ +void QGraphicsLayoutItem::setSizePolicy(const QSizePolicy &policy) +{ + Q_D(QGraphicsLayoutItem); + if (d->sizePolicy == policy) + return; + d->sizePolicy = policy; + updateGeometry(); +} + +/*! + \overload + + This function is equivalent to calling + setSizePolicy(QSizePolicy(\a hPolicy, \a vPolicy, \a controlType)). + + \sa sizePolicy(), QWidget::sizePolicy() +*/ +void QGraphicsLayoutItem::setSizePolicy(QSizePolicy::Policy hPolicy, + QSizePolicy::Policy vPolicy, + QSizePolicy::ControlType controlType) +{ + setSizePolicy(QSizePolicy(hPolicy, vPolicy, controlType)); +} + +/*! + Returns the current size policy. + + \sa setSizePolicy(), QWidget::sizePolicy() +*/ +QSizePolicy QGraphicsLayoutItem::sizePolicy() const +{ + Q_D(const QGraphicsLayoutItem); + return d->sizePolicy; +} + +/*! + Sets the minimum size to \a size. This property overrides sizeHint() for + Qt::MinimumSize and ensures that effectiveSizeHint() will never return + a size smaller than \a size. In order to unset the minimum size, use an + invalid size. + + \sa minimumSize(), maximumSize(), preferredSize(), Qt::MinimumSize, + sizeHint(), setMinimumWidth(), setMinimumHeight() +*/ +void QGraphicsLayoutItem::setMinimumSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::MinimumSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setMinimumSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setMinimumSize(QSizeF(\a w, \a h)). + + \sa minimumSize(), setMaximumSize(), setPreferredSize(), sizeHint() +*/ + +/*! + Returns the minimum size. + + \sa setMinimumSize(), preferredSize(), maximumSize(), Qt::MinimumSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::minimumSize() const +{ + return effectiveSizeHint(Qt::MinimumSize); +} + +/*! + Sets the minimum width to \a width. + + \sa minimumWidth(), setMinimumSize(), minimumSize() +*/ +void QGraphicsLayoutItem::setMinimumWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::MinimumSize, d_ptr->Width, width); +} + +/*! + Sets the minimum height to \a height. + + \sa minimumHeight(), setMinimumSize(), minimumSize() +*/ +void QGraphicsLayoutItem::setMinimumHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::MinimumSize, d_ptr->Height, height); +} + + +/*! + Sets the preferred size to \a size. This property overrides sizeHint() for + Qt::PreferredSize and provides the default value for effectiveSizeHint(). + In order to unset the preferred size, use an invalid size. + + \sa preferredSize(), minimumSize(), maximumSize(), Qt::PreferredSize, + sizeHint() +*/ +void QGraphicsLayoutItem::setPreferredSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::PreferredSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setPreferredSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setPreferredSize(QSizeF(\a w, \a h)). + + \sa preferredSize(), setMaximumSize(), setMinimumSize(), sizeHint() +*/ + +/*! + Returns the preferred size. + + \sa setPreferredSize(), minimumSize(), maximumSize(), Qt::PreferredSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::preferredSize() const +{ + return effectiveSizeHint(Qt::PreferredSize); +} + +/*! + Sets the preferred height to \a height. + + \sa preferredWidth(), setPreferredSize(), preferredSize() +*/ +void QGraphicsLayoutItem::setPreferredHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::PreferredSize, d_ptr->Height, height); +} + +/*! + Sets the preferred width to \a width. + + \sa preferredHeight(), setPreferredSize(), preferredSize() +*/ +void QGraphicsLayoutItem::setPreferredWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::PreferredSize, d_ptr->Width, width); +} + +/*! + Sets the maximum size to \a size. This property overrides sizeHint() for + Qt::MaximumSize and ensures that effectiveSizeHint() will never return a + size larger than \a size. In order to unset the maximum size, use an + invalid size. + + \sa maximumSize(), minimumSize(), preferredSize(), Qt::MaximumSize, + sizeHint() +*/ +void QGraphicsLayoutItem::setMaximumSize(const QSizeF &size) +{ + d_ptr->setSize(Qt::MaximumSize, size); +} + +/*! + \fn QGraphicsLayoutItem::setMaximumSize(qreal w, qreal h) + + This convenience function is equivalent to calling + setMaximumSize(QSizeF(\a w, \a h)). + + \sa maximumSize(), setMinimumSize(), setPreferredSize(), sizeHint() +*/ + +/*! + Returns the maximum size. + + \sa setMaximumSize(), minimumSize(), preferredSize(), Qt::MaximumSize, + sizeHint() +*/ +QSizeF QGraphicsLayoutItem::maximumSize() const +{ + return effectiveSizeHint(Qt::MaximumSize); +} + +/*! + Sets the maximum width to \a width. + + \sa maximumWidth(), setMaximumSize(), maximumSize() +*/ +void QGraphicsLayoutItem::setMaximumWidth(qreal width) +{ + d_ptr->setSizeComponent(Qt::MaximumSize, d_ptr->Width, width); +} + +/*! + Sets the maximum height to \a height. + + \sa maximumHeight(), setMaximumSize(), maximumSize() +*/ +void QGraphicsLayoutItem::setMaximumHeight(qreal height) +{ + d_ptr->setSizeComponent(Qt::MaximumSize, d_ptr->Height, height); +} + +/*! + \fn qreal QGraphicsLayoutItem::minimumWidth() const + + Returns the minimum width. + + \sa setMinimumWidth(), setMinimumSize(), minimumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::minimumHeight() const + + Returns the minimum height. + + \sa setMinimumHeight(), setMinimumSize(), minimumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::preferredWidth() const + + Returns the preferred width. + + \sa setPreferredWidth(), setPreferredSize(), preferredSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::preferredHeight() const + + Returns the preferred height. + + \sa setPreferredHeight(), setPreferredSize(), preferredSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::maximumWidth() const + + Returns the maximum width. + + \sa setMaximumWidth(), setMaximumSize(), maximumSize() +*/ + +/*! + \fn qreal QGraphicsLayoutItem::maximumHeight() const + + Returns the maximum height. + + \sa setMaximumHeight(), setMaximumSize(), maximumSize() +*/ + +/*! + \fn virtual void QGraphicsLayoutItem::setGeometry(const QRectF &rect) + + This virtual function sets the geometry of the QGraphicsLayoutItem to + \a rect, which is in parent coordinates (e.g., the top-left corner of \a rect + is equivalent to the item's position in parent coordinates). + + You must reimplement this function in a subclass of QGraphicsLayoutItem to + receive geometry updates. The layout will call this function when it does a + rearrangement. + + If \a rect is outside of the bounds of minimumSize and maximumSize, it + will be adjusted to its closest size so that it is within the legal + bounds. + + \sa geometry() +*/ +void QGraphicsLayoutItem::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsLayoutItem); + QSizeF effectiveSize = rect.size().expandedTo(effectiveSizeHint(Qt::MinimumSize)) + .boundedTo(effectiveSizeHint(Qt::MaximumSize)); + d->geom = QRectF(rect.topLeft(), effectiveSize); +} + +/*! + \fn QRectF QGraphicsLayoutItem::geometry() const + + Returns the item's geometry (e.g., position and size) as a + QRectF. This function is equivalent to QRectF(pos(), size()). + + \sa setGeometry() +*/ +QRectF QGraphicsLayoutItem::geometry() const +{ + Q_D(const QGraphicsLayoutItem); + return d->geom; +} + +/*! + This virtual function provides the \a left, \a top, \a right and \a bottom + contents margins for this QGraphicsLayoutItem. The default implementation + assumes all contents margins are 0. The parameters point to values stored + in qreals. If any of the pointers is 0, that value will not be updated. + + \sa QGraphicsWidget::setContentsMargins() +*/ +void QGraphicsLayoutItem::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + if (left) + *left = 0; + if (top) + *top = 0; + if (right) + *right = 0; + if (bottom) + *bottom = 0; +} + +/*! + Returns the contents rect in local coordinates. + + The contents rect defines the subrectangle used by an associated layout + when arranging subitems. This function is a convenience function that + adjusts the item's geometry() by its contents margins. Note that + getContentsMargins() is a virtual function that you can reimplement to + return the item's contents margins. + + \sa getContentsMargins(), geometry() +*/ +QRectF QGraphicsLayoutItem::contentsRect() const +{ + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + return QRectF(QPointF(), geometry().size()).adjusted(+left, +top, -right, -bottom); +} + +/*! + Returns the effective size hint for this QGraphicsLayoutItem. + + \a which is the size hint in question. + \a constraint is an optional argument that defines a special constrain + when calculating the effective size hint. By default, \a constraint is + QSizeF(-1, -1), which means there is no constraint to the size hint. + + If you want to specify the widget's size hint for a given width or height, + you can provide the fixed dimension in \a constraint. This is useful for + widgets that can grow only either vertically or horizontally, and need to + set either their width or their height to a special value. + + For example, a text paragraph item fit into a column width of 200 may + grow vertically. You can pass QSizeF(200, -1) as a constraint to get a + suitable minimum, preferred and maximum height). + + You can adjust the effective size hint by reimplementing sizeHint() + in a QGraphicsLayoutItem subclass, or by calling one of the following + functions: setMinimumSize(), setPreferredSize, or setMaximumSize() + (or a combination of both). + + This function caches each of the size hints and guarantees that + sizeHint() will be called only once for each value of \a which - unless + \a constraint is not specified and updateGeometry() has been called. + + \sa sizeHint() +*/ +QSizeF QGraphicsLayoutItem::effectiveSizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsLayoutItem); + + if (!d->userSizeHints && constraint.isValid()) + return constraint; + + // ### should respect size policy??? + return d_ptr->effectiveSizeHints(constraint)[which]; +} + +/*! + This virtual function discards any cached size hint information. You + should always call this function if you change the return value of the + sizeHint() function. Subclasses must always call the base implementation + when reimplementing this function. + + \sa effectiveSizeHint() +*/ +void QGraphicsLayoutItem::updateGeometry() +{ + Q_D(QGraphicsLayoutItem); + d->sizeHintCacheDirty = true; + d->sizeHintWithConstraintCacheDirty = true; +} + +/*! + Returns the parent of this QGraphicsLayoutItem, or 0 if there is no parent, + or if the parent does not inherit from QGraphicsLayoutItem + (QGraphicsLayoutItem is often used through multiple inheritance with + QObject-derived classes). + + \sa setParentLayoutItem() +*/ +QGraphicsLayoutItem *QGraphicsLayoutItem::parentLayoutItem() const +{ + return d_func()->parent; +} + +/*! + Sets the parent of this QGraphicsLayoutItem to \a parent. + + \sa parentLayoutItem() +*/ +void QGraphicsLayoutItem::setParentLayoutItem(QGraphicsLayoutItem *parent) +{ + d_func()->parent = parent; +} + +/*! + Returns true if this QGraphicsLayoutItem is a layout (e.g., is inherited + by an object that arranges other QGraphicsLayoutItem objects); otherwise + returns false. + + \sa QGraphicsLayout +*/ +bool QGraphicsLayoutItem::isLayout() const +{ + return d_func()->isLayout; +} + +/*! + \since 4.6 + + Returns whether a layout should delete this item in its destructor. + If its true, then the layout will delete it. If its false, then it is + assumed that another object has the ownership of it, and the layout won't + delete this item. + + If the item inherits both QGraphicsItem and QGraphicsLayoutItem (such + as QGraphicsWidget does) the item is really part of two ownership + hierarchies. This property informs what the layout should do with its + child items when it is destructed. In the case of QGraphicsWidget, it + is preferred that when the layout is deleted it won't delete its children + (since they are also part of the graphics item hierarchy). + + By default this value is initialized to false in QGraphicsLayoutItem, + but it is overridden by QGraphicsLayout to return true. This is because + QGraphicsLayout is not normally part of the QGraphicsItem hierarchy, so the + parent layout should delete it. + Subclasses might override this default behaviour by calling + setOwnedByLayout(true). + + \sa setOwnedByLayout() +*/ +bool QGraphicsLayoutItem::ownedByLayout() const +{ + return d_func()->ownedByLayout; +} +/*! + \since 4.6 + + Sets whether a layout should delete this item in its destructor or not. + \a ownership must be true to in order for the layout to delete it. + \sa ownedByLayout() +*/ +void QGraphicsLayoutItem::setOwnedByLayout(bool ownership) +{ + d_func()->ownedByLayout = ownership; +} + +/*! + * Returns the QGraphicsItem that this layout item represents. + * For QGraphicsWidget it will return itself. For custom items it can return an + * aggregated value. + * + * \sa setGraphicsItem() + */ +QGraphicsItem *QGraphicsLayoutItem::graphicsItem() const +{ + return d_func()->graphicsItem; +} + +/*! + * If the QGraphicsLayoutItem represents a QGraphicsItem, and it wants to take + * advantage of the automatic reparenting capabilities of QGraphicsLayout it + * should set this value. + * Note that if you delete \a item and not delete the layout item, you are + * responsible of calling setGraphicsItem(0) in order to avoid having a + * dangling pointer. + * + * \sa graphicsItem() + */ +void QGraphicsLayoutItem::setGraphicsItem(QGraphicsItem *item) +{ + d_func()->graphicsItem = item; +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicslayoutitem.h b/src/gui/graphicsview/qgraphicslayoutitem.h new file mode 100644 index 0000000000..7112dd06fc --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayoutitem.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUTITEM_H +#define QGRAPHICSLAYOUTITEM_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLayoutItemPrivate; +class QGraphicsItem; +class Q_GUI_EXPORT QGraphicsLayoutItem +{ +public: + QGraphicsLayoutItem(QGraphicsLayoutItem *parent = 0, bool isLayout = false); + virtual ~QGraphicsLayoutItem(); + + void setSizePolicy(const QSizePolicy &policy); + void setSizePolicy(QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy, QSizePolicy::ControlType controlType = QSizePolicy::DefaultType); + QSizePolicy sizePolicy() const; + + void setMinimumSize(const QSizeF &size); + inline void setMinimumSize(qreal w, qreal h); + QSizeF minimumSize() const; + void setMinimumWidth(qreal width); + inline qreal minimumWidth() const; + void setMinimumHeight(qreal height); + inline qreal minimumHeight() const; + + void setPreferredSize(const QSizeF &size); + inline void setPreferredSize(qreal w, qreal h); + QSizeF preferredSize() const; + void setPreferredWidth(qreal width); + inline qreal preferredWidth() const; + void setPreferredHeight(qreal height); + inline qreal preferredHeight() const; + + void setMaximumSize(const QSizeF &size); + inline void setMaximumSize(qreal w, qreal h); + QSizeF maximumSize() const; + void setMaximumWidth(qreal width); + inline qreal maximumWidth() const; + void setMaximumHeight(qreal height); + inline qreal maximumHeight() const; + + virtual void setGeometry(const QRectF &rect); + QRectF geometry() const; + virtual void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + QRectF contentsRect() const; + + QSizeF effectiveSizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + virtual void updateGeometry(); //### rename to sizeHintChanged() + + QGraphicsLayoutItem *parentLayoutItem() const; + void setParentLayoutItem(QGraphicsLayoutItem *parent); + + bool isLayout() const; + // ###Qt5: Make automatic reparenting work regardless of item/object/widget type. + QGraphicsItem *graphicsItem() const; + bool ownedByLayout() const; + +protected: + void setGraphicsItem(QGraphicsItem *item); + void setOwnedByLayout(bool ownedByLayout); + QGraphicsLayoutItem(QGraphicsLayoutItemPrivate &dd); + + virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const = 0; + QScopedPointer d_ptr; + +private: + QSizeF *effectiveSizeHints(const QSizeF &constraint) const; + Q_DECLARE_PRIVATE(QGraphicsLayoutItem) + + friend class QGraphicsLayout; +}; + +Q_DECLARE_INTERFACE(QGraphicsLayoutItem, "com.trolltech.Qt.QGraphicsLayoutItem") + +inline void QGraphicsLayoutItem::setMinimumSize(qreal aw, qreal ah) +{ setMinimumSize(QSizeF(aw, ah)); } +inline void QGraphicsLayoutItem::setPreferredSize(qreal aw, qreal ah) +{ setPreferredSize(QSizeF(aw, ah)); } +inline void QGraphicsLayoutItem::setMaximumSize(qreal aw, qreal ah) +{ setMaximumSize(QSizeF(aw, ah)); } + +inline qreal QGraphicsLayoutItem::minimumWidth() const +{ return effectiveSizeHint(Qt::MinimumSize).width(); } +inline qreal QGraphicsLayoutItem::minimumHeight() const +{ return effectiveSizeHint(Qt::MinimumSize).height(); } + +inline qreal QGraphicsLayoutItem::preferredWidth() const +{ return effectiveSizeHint(Qt::PreferredSize).width(); } +inline qreal QGraphicsLayoutItem::preferredHeight() const +{ return effectiveSizeHint(Qt::PreferredSize).height(); } + +inline qreal QGraphicsLayoutItem::maximumWidth() const +{ return effectiveSizeHint(Qt::MaximumSize).width(); } +inline qreal QGraphicsLayoutItem::maximumHeight() const +{ return effectiveSizeHint(Qt::MaximumSize).height(); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/graphicsview/qgraphicslayoutitem_p.h b/src/gui/graphicsview/qgraphicslayoutitem_p.h new file mode 100644 index 0000000000..85481b7a46 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslayoutitem_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLAYOUTITEM_P_H +#define QGRAPHICSLAYOUTITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class Q_AUTOTEST_EXPORT QGraphicsLayoutItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsLayoutItem) +public: + virtual ~QGraphicsLayoutItemPrivate(); + QGraphicsLayoutItemPrivate(QGraphicsLayoutItem *parent, bool isLayout); + static QGraphicsLayoutItemPrivate *get(QGraphicsLayoutItem *q) { return q->d_func();} + static const QGraphicsLayoutItemPrivate *get(const QGraphicsLayoutItem *q) { return q->d_func();} + + void init(); + QSizeF *effectiveSizeHints(const QSizeF &constraint) const; + QGraphicsItem *parentItem() const; + void ensureUserSizeHints(); + void setSize(Qt::SizeHint which, const QSizeF &size); + enum SizeComponent { Width, Height }; + void setSizeComponent(Qt::SizeHint which, SizeComponent component, qreal value); + + bool hasHeightForWidth() const; + bool hasWidthForHeight() const; + + QSizePolicy sizePolicy; + QGraphicsLayoutItem *parent; + + QSizeF *userSizeHints; + mutable QSizeF cachedSizeHints[Qt::NSizeHints]; + mutable QSizeF cachedConstraint; + mutable QSizeF cachedSizeHintsWithConstraints[Qt::NSizeHints]; + + mutable quint32 sizeHintCacheDirty : 1; + mutable quint32 sizeHintWithConstraintCacheDirty : 1; + quint32 isLayout : 1; + quint32 ownedByLayout : 1; + + QGraphicsLayoutItem *q_ptr; + QRectF geom; + QGraphicsItem *graphicsItem; +}; + +QT_END_NAMESPACE + +#endif //QGRAPHICSLAYOUTITEM_P_H + diff --git a/src/gui/graphicsview/qgraphicslinearlayout.cpp b/src/gui/graphicsview/qgraphicslinearlayout.cpp new file mode 100644 index 0000000000..5591638395 --- /dev/null +++ b/src/gui/graphicsview/qgraphicslinearlayout.cpp @@ -0,0 +1,568 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsLinearLayout + \brief The QGraphicsLinearLayout class provides a horizontal or vertical + layout for managing widgets in Graphics View. + \since 4.4 + \ingroup graphicsview-api + + The default orientation for a linear layout is Qt::Horizontal. You can + choose a vertical orientation either by calling setOrientation(), or by + passing Qt::Vertical to QGraphicsLinearLayout's constructor. + + The most common way to use QGraphicsLinearLayout is to construct an object + on the heap with no parent, add widgets and layouts by calling addItem(), + and finally assign the layout to a widget by calling + QGraphicsWidget::setLayout(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicslinearlayout.cpp 0 + + You can add widgets, layouts, stretches (addStretch(), insertStretch() or + setStretchFactor()), and spacings (setItemSpacing()) to a linear + layout. The layout takes ownership of the items. In some cases when the layout + item also inherits from QGraphicsItem (such as QGraphicsWidget) there will be a + ambiguity in ownership because the layout item belongs to two ownership hierarchies. + See the documentation of QGraphicsLayoutItem::setOwnedByLayout() how to handle + this. + You can access each item in the layout by calling count() and itemAt(). Calling + removeAt() or removeItem() will remove an item from the layout, without + destroying it. + + \section1 Size Hints and Size Policies in QGraphicsLinearLayout + + QGraphicsLinearLayout respects each item's size hints and size policies, + and when the layout contains more space than the items can fill, each item + is arranged according to the layout's alignment for that item. You can set + an alignment for each item by calling setAlignment(), and check the + alignment for any item by calling alignment(). By default, items are + aligned to the top left. + + \section1 Spacing within QGraphicsLinearLayout + + Between the items, the layout distributes some space. The actual amount of + space depends on the managed widget's current style, but the common + spacing is 4. You can also set your own spacing by calling setSpacing(), + and get the current spacing value by calling spacing(). If you want to + configure individual spacing for your items, you can call setItemSpacing(). + + \section1 Stretch Factor in QGraphicsLinearLayout + + You can assign a stretch factor to each item to control how much space it + will get compared to the other items. By default, two identical widgets + arranged in a linear layout will have the same size, but if the first + widget has a stretch factor of 1 and the second widget has a stretch + factor of 2, the first widget will get 1/3 of the available space, and the + second will get 2/3. + + QGraphicsLinearLayout calculates the distribution of sizes by adding up + the stretch factors of all items, and then dividing the available space + accordingly. The default stretch factor is 0 for all items; a factor of 0 + means the item does not have any defined stretch factor; effectively this + is the same as setting the stretch factor to 1. The stretch factor only + applies to the available space in the lengthwise direction of the layout + (following its orientation). If you want to control both the item's + horizontal and vertical stretch, you can use QGraphicsGridLayout instead. + + \section1 QGraphicsLinearLayout Compared to Other Layouts + + QGraphicsLinearLayout is very similar to QVBoxLayout and QHBoxLayout, but + in contrast to these classes, it is used to manage QGraphicsWidget and + QGraphicsLayout instead of QWidget and QLayout. + + \sa QGraphicsGridLayout, QGraphicsWidget +*/ + +#include "qapplication.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qwidget.h" +#include "qgraphicslayout_p.h" +#include "qgraphicslayoutitem.h" +#include "qgraphicslinearlayout.h" +#include "qgraphicswidget.h" +#include "qgridlayoutengine_p.h" +#ifdef QT_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +class QGraphicsLinearLayoutPrivate : public QGraphicsLayoutPrivate +{ +public: + QGraphicsLinearLayoutPrivate(Qt::Orientation orientation) : orientation(orientation) { } + + void removeGridItem(QGridLayoutItem *gridItem); + QLayoutStyleInfo styleInfo() const; + void fixIndex(int *index) const; + int gridRow(int index) const; + int gridColumn(int index) const; + + Qt::Orientation orientation; + QGridLayoutEngine engine; +}; + +void QGraphicsLinearLayoutPrivate::removeGridItem(QGridLayoutItem *gridItem) +{ + int index = gridItem->firstRow(orientation); + engine.removeItem(gridItem); + engine.removeRows(index, 1, orientation); +} + +void QGraphicsLinearLayoutPrivate::fixIndex(int *index) const +{ + int count = engine.rowCount(orientation); + if (uint(*index) > uint(count)) + *index = count; +} + +int QGraphicsLinearLayoutPrivate::gridRow(int index) const +{ + if (orientation == Qt::Horizontal) + return 0; + return int(qMin(uint(index), uint(engine.rowCount()))); +} + +int QGraphicsLinearLayoutPrivate::gridColumn(int index) const +{ + if (orientation == Qt::Vertical) + return 0; + return int(qMin(uint(index), uint(engine.columnCount()))); +} + +Q_GLOBAL_STATIC(QWidget, globalStyleInfoWidget) + +QLayoutStyleInfo QGraphicsLinearLayoutPrivate::styleInfo() const +{ + QGraphicsItem *item = parentItem(); + QStyle *style = (item && item->isWidget()) ? static_cast(item)->style() : QApplication::style(); + return QLayoutStyleInfo(style, globalStyleInfoWidget()); +} + +/*! + Constructs a QGraphicsLinearLayout instance. You can pass the + \a orientation for the layout, either horizontal or vertical, and + \a parent is passed to QGraphicsLayout's constructor. +*/ +QGraphicsLinearLayout::QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(orientation), parent) +{ +} + +/*! + Constructs a QGraphicsLinearLayout instance using Qt::Horizontal + orientation. \a parent is passed to QGraphicsLayout's constructor. +*/ +QGraphicsLinearLayout::QGraphicsLinearLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(*new QGraphicsLinearLayoutPrivate(Qt::Horizontal), parent) +{ +} + +/*! + Destroys the QGraphicsLinearLayout object. +*/ +QGraphicsLinearLayout::~QGraphicsLinearLayout() +{ + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = itemAt(i); + // The following lines can be removed, but this removes the item + // from the layout more efficiently than the implementation of + // ~QGraphicsLayoutItem. + removeAt(i); + if (item) { + item->setParentLayoutItem(0); + if (item->ownedByLayout()) + delete item; + } + } +} + +/*! + Change the layout orientation to \a orientation. Changing the layout + orientation will automatically invalidate the layout. + + \sa orientation() +*/ +void QGraphicsLinearLayout::setOrientation(Qt::Orientation orientation) +{ + Q_D(QGraphicsLinearLayout); + if (orientation != d->orientation) { + d->engine.transpose(); + d->orientation = orientation; + invalidate(); + } +} + +/*! + Returns the layout orientation. + \sa setOrientation() + */ +Qt::Orientation QGraphicsLinearLayout::orientation() const +{ + Q_D(const QGraphicsLinearLayout); + return d->orientation; +} + +/*! + \fn void QGraphicsLinearLayout::addItem(QGraphicsLayoutItem *item) + + This convenience function is equivalent to calling + insertItem(-1, \a item). +*/ + +/*! + \fn void QGraphicsLinearLayout::addStretch(int stretch) + + This convenience function is equivalent to calling + insertStretch(-1, \a stretch). +*/ + +/*! + Inserts \a item into the layout at \a index, or before any item that is + currently at \a index. + + \sa addItem(), itemAt(), insertStretch(), setItemSpacing() +*/ +void QGraphicsLinearLayout::insertItem(int index, QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::insertItem: cannot insert null item"); + return; + } + if (item == this) { + qWarning("QGraphicsLinearLayout::insertItem: cannot insert itself"); + return; + } + Q_ASSERT(item); + + //the order of the following instructions is very important because + //invalidating the layout before adding the child item will make the layout happen + //before we try to paint the item + invalidate(); + d->addChildLayoutItem(item); + + d->fixIndex(&index); + d->engine.insertRow(index, d->orientation); + new QGridLayoutItem(&d->engine, item, d->gridRow(index), d->gridColumn(index), 1, 1, 0, index); +} + +/*! + Inserts a stretch of \a stretch at \a index, or before any item that is + currently at \a index. + + \sa addStretch(), setStretchFactor(), setItemSpacing(), insertItem() +*/ +void QGraphicsLinearLayout::insertStretch(int index, int stretch) +{ + Q_D(QGraphicsLinearLayout); + d->fixIndex(&index); + d->engine.insertRow(index, d->orientation); + d->engine.setRowStretchFactor(index, stretch, d->orientation); + invalidate(); +} + +/*! + Removes \a item from the layout without destroying it. Ownership of + \a item is transferred to the caller. + + \sa removeAt(), insertItem() +*/ +void QGraphicsLinearLayout::removeItem(QGraphicsLayoutItem *item) +{ + Q_D(QGraphicsLinearLayout); + if (QGridLayoutItem *gridItem = d->engine.findLayoutItem(item)) { + item->setParentLayoutItem(0); + d->removeGridItem(gridItem); + delete gridItem; + invalidate(); + } +} + +/*! + Removes the item at \a index without destroying it. Ownership of the item + is transferred to the caller. + + \sa removeItem(), insertItem() +*/ +void QGraphicsLinearLayout::removeAt(int index) +{ + Q_D(QGraphicsLinearLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsLinearLayout::removeAt: invalid index %d", index); + return; + } + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) { + if (QGraphicsLayoutItem *layoutItem = gridItem->layoutItem()) + layoutItem->setParentLayoutItem(0); + d->removeGridItem(gridItem); + delete gridItem; + invalidate(); + } +} + +/*! + Sets the layout's spacing to \a spacing. Spacing refers to the + vertical and horizontal distances between items. + + \sa setItemSpacing(), setStretchFactor(), QGraphicsGridLayout::setSpacing() +*/ +void QGraphicsLinearLayout::setSpacing(qreal spacing) +{ + Q_D(QGraphicsLinearLayout); + if (spacing < 0) { + qWarning("QGraphicsLinearLayout::setSpacing: invalid spacing %g", spacing); + return; + } + d->engine.setSpacing(spacing, Qt::Horizontal | Qt::Vertical); + invalidate(); +} + +/*! + Returns the layout's spacing. Spacing refers to the + vertical and horizontal distances between items. + + \sa setSpacing() + */ +qreal QGraphicsLinearLayout::spacing() const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.spacing(d->styleInfo(), d->orientation); +} + +/*! + Sets the spacing after item at \a index to \a spacing. +*/ +void QGraphicsLinearLayout::setItemSpacing(int index, qreal spacing) +{ + Q_D(QGraphicsLinearLayout); + d->engine.setRowSpacing(index, spacing, d->orientation); + invalidate(); +} +/*! + Returns the spacing after item at \a index. +*/ +qreal QGraphicsLinearLayout::itemSpacing(int index) const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.rowSpacing(index, d->orientation); +} + +/*! + Sets the stretch factor for \a item to \a stretch. If an item's stretch + factor changes, this function will invalidate the layout. + + Setting \a stretch to 0 removes the stretch factor from the item, and is + effectively equivalent to setting \a stretch to 1. + + \sa stretchFactor() +*/ +void QGraphicsLinearLayout::setStretchFactor(QGraphicsLayoutItem *item, int stretch) +{ + Q_D(QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::setStretchFactor: cannot assign" + " a stretch factor to a null item"); + return; + } + if (stretchFactor(item) == stretch) + return; + d->engine.setStretchFactor(item, stretch, d->orientation); + invalidate(); +} + +/*! + Returns the stretch factor for \a item. The default stretch factor is 0, + meaning that the item has no assigned stretch factor. + + \sa setStretchFactor() +*/ +int QGraphicsLinearLayout::stretchFactor(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsLinearLayout); + if (!item) { + qWarning("QGraphicsLinearLayout::setStretchFactor: cannot return" + " a stretch factor for a null item"); + return 0; + } + return d->engine.stretchFactor(item, d->orientation); +} + +/*! + Sets the alignment of \a item to \a alignment. If \a item's alignment + changes, the layout is automatically invalidated. + + \sa alignment(), invalidate() +*/ +void QGraphicsLinearLayout::setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment) +{ + Q_D(QGraphicsLinearLayout); + if (this->alignment(item) == alignment) + return; + d->engine.setAlignment(item, alignment); + invalidate(); +} + +/*! + Returns the alignment for \a item. The default alignment is + Qt::AlignTop | Qt::AlignLeft. + + The alignment decides how the item is positioned within its assigned space + in the case where there's more space available in the layout than the + widgets can occupy. + + \sa setAlignment() +*/ +Qt::Alignment QGraphicsLinearLayout::alignment(QGraphicsLayoutItem *item) const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.alignment(item); +} + +#if 0 // ### +QSizePolicy::ControlTypes QGraphicsLinearLayout::controlTypes(LayoutSide side) const +{ + return d->engine.controlTypes(side); +} +#endif + +/*! + \reimp +*/ +int QGraphicsLinearLayout::count() const +{ + Q_D(const QGraphicsLinearLayout); + return d->engine.itemCount(); +} + +/*! + \reimp + When iterating from 0 and up, it will return the items in the visual arranged order. +*/ +QGraphicsLayoutItem *QGraphicsLinearLayout::itemAt(int index) const +{ + Q_D(const QGraphicsLinearLayout); + if (index < 0 || index >= d->engine.itemCount()) { + qWarning("QGraphicsLinearLayout::itemAt: invalid index %d", index); + return 0; + } + QGraphicsLayoutItem *item = 0; + if (QGridLayoutItem *gridItem = d->engine.itemAt(index)) + item = gridItem->layoutItem(); + return item; +} + +/*! + \reimp +*/ +void QGraphicsLinearLayout::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsLinearLayout); + QGraphicsLayout::setGeometry(rect); + QRectF effectiveRect = geometry(); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + Qt::LayoutDirection visualDir = d->visualDirection(); + d->engine.setVisualDirection(visualDir); + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + effectiveRect.adjust(+left, +top, -right, -bottom); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + static int counter = 0; + qDebug() << counter++ << "QGraphicsLinearLayout::setGeometry - " << rect; + dump(1); + } +#endif + d->engine.setGeometries(d->styleInfo(), effectiveRect); +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + qDebug() << "post dump"; + dump(1); + } +#endif +} + +/*! + \reimp +*/ +QSizeF QGraphicsLinearLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsLinearLayout); + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + const QSizeF extraMargins(left + right, top + bottom); + return d->engine.sizeHint(d->styleInfo(), which , constraint - extraMargins) + extraMargins; +} + +/*! + \reimp +*/ +void QGraphicsLinearLayout::invalidate() +{ + Q_D(QGraphicsLinearLayout); + d->engine.invalidate(); + QGraphicsLayout::invalidate(); +} + +/*! + \internal +*/ +void QGraphicsLinearLayout::dump(int indent) const +{ +#ifdef QT_DEBUG + if (qt_graphicsLayoutDebug()) { + Q_D(const QGraphicsLinearLayout); + qDebug("%*s%s layout", indent, "", + d->orientation == Qt::Horizontal ? "Horizontal" : "Vertical"); + d->engine.dump(indent + 1); + } +#else + Q_UNUSED(indent); +#endif +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicslinearlayout.h b/src/gui/graphicsview/qgraphicslinearlayout.h new file mode 100644 index 0000000000..60749dbdaf --- /dev/null +++ b/src/gui/graphicsview/qgraphicslinearlayout.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSLINEARLAYOUT_H +#define QGRAPHICSLINEARLAYOUT_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsLinearLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsLinearLayout : public QGraphicsLayout +{ +public: + QGraphicsLinearLayout(QGraphicsLayoutItem *parent = 0); + QGraphicsLinearLayout(Qt::Orientation orientation, QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsLinearLayout(); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + inline void addItem(QGraphicsLayoutItem *item) { insertItem(-1, item); } + inline void addStretch(int stretch = 1) { insertStretch(-1, stretch); } + + void insertItem(int index, QGraphicsLayoutItem *item); + void insertStretch(int index, int stretch = 1); + + void removeItem(QGraphicsLayoutItem *item); + void removeAt(int index); + + void setSpacing(qreal spacing); + qreal spacing() const; + void setItemSpacing(int index, qreal spacing); + qreal itemSpacing(int index) const; + + void setStretchFactor(QGraphicsLayoutItem *item, int stretch); + int stretchFactor(QGraphicsLayoutItem *item) const; + + void setAlignment(QGraphicsLayoutItem *item, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *item) const; + + void setGeometry(const QRectF &rect); + + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + + void invalidate(); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +#if 0 // ### + Q5SizePolicy::ControlTypes controlTypes(LayoutSide side) const; +#endif + + void dump(int indent = 0) const; + +protected: +#if 0 + QSize contentsSizeHint(Qt::SizeHint which, const QSize &constraint = QSize()) const; +#endif + +private: + Q_DISABLE_COPY(QGraphicsLinearLayout) + Q_DECLARE_PRIVATE(QGraphicsLinearLayout) +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/gui/graphicsview/qgraphicsproxywidget.cpp b/src/gui/graphicsview/qgraphicsproxywidget.cpp new file mode 100644 index 0000000000..e6142e66ac --- /dev/null +++ b/src/gui/graphicsview/qgraphicsproxywidget.cpp @@ -0,0 +1,1570 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicslayout.h" +#include "qgraphicsproxywidget.h" +#include "private/qgraphicsproxywidget_p.h" +#include "private/qwidget_p.h" +#include "private/qapplication_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +//#define GRAPHICSPROXYWIDGET_DEBUG + +/*! + \class QGraphicsProxyWidget + \brief The QGraphicsProxyWidget class provides a proxy layer for embedding + a QWidget in a QGraphicsScene. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsProxyWidget embeds QWidget-based widgets, for example, a + QPushButton, QFontComboBox, or even QFileDialog, into + QGraphicsScene. It forwards events between the two objects and + translates between QWidget's integer-based geometry and + QGraphicsWidget's qreal-based geometry. QGraphicsProxyWidget + supports all core features of QWidget, including tab focus, + keyboard input, Drag & Drop, and popups. You can also embed + complex widgets, e.g., widgets with subwidgets. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 0 + + QGraphicsProxyWidget takes care of automatically embedding popup children + of embedded widgets through creating a child proxy for each popup. This + means that when an embedded QComboBox shows its popup list, a new + QGraphicsProxyWidget is created automatically, embedding the popup, and + positioning it correctly. This only works if the popup is child of the + embedded widget (for example QToolButton::setMenu() requires the QMenu instance + to be child of the QToolButton). + + \section1 Embedding a Widget with QGraphicsProxyWidget + + There are two ways to embed a widget using QGraphicsProxyWidget. The most + common way is to pass a widget pointer to QGraphicsScene::addWidget() + together with any relevant \l Qt::WindowFlags. This function returns a + pointer to a QGraphicsProxyWidget. You can then choose to reparent or + position either the proxy, or the embedded widget itself. + + For example, in the code snippet below, we embed a group box into the proxy: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 1 + + The image below is the output obtained with its contents margin and + contents rect labeled. + + \image qgraphicsproxywidget-embed.png + + Alternatively, you can start by creating a new QGraphicsProxyWidget item, + and then call setWidget() to embed a QWidget later. The widget() function + returns a pointer to the embedded widget. QGraphicsProxyWidget shares + ownership with QWidget, so if either of the two widgets are destroyed, the + other widget will be automatically destroyed as well. + + \section1 Synchronizing Widget States + + QGraphicsProxyWidget keeps its state in sync with the embedded widget. For + example, if the proxy is hidden or disabled, the embedded widget will be + hidden or disabled as well, and vice versa. When the widget is embedded by + calling addWidget(), QGraphicsProxyWidget copies the state from the widget + into the proxy, and after that, the two will stay synchronized where + possible. By default, when you embed a widget into a proxy, both the widget + and the proxy will be visible because a QGraphicsWidget is visible when + created (you do not have to call show()). If you explicitly hide the + embedded widget, the proxy will also become invisible. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsproxywidget.cpp 2 + + QGraphicsProxyWidget maintains symmetry for the following states: + + \table + \header \o QWidget state \o QGraphicsProxyWidget state \o Notes + \row \o QWidget::enabled + \o QGraphicsProxyWidget::enabled + \o + \row \o QWidget::visible + \o QGraphicsProxyWidget::visible + \o The explicit state is also symmetric. + \row \o QWidget::geometry + \o QGraphicsProxyWidget::geometry + \o Geometry is only guaranteed to be symmetric while + the embedded widget is visible. + \row \o QWidget::layoutDirection + \o QGraphicsProxyWidget::layoutDirection + \o + \row \o QWidget::style + \o QGraphicsProxyWidget::style + \o + \row \o QWidget::palette + \o QGraphicsProxyWidget::palette + \o + \row \o QWidget::font + \o QGraphicsProxyWidget::font + \o + \row \o QWidget::cursor + \o QGraphicsProxyWidget::cursor + \o The embedded widget overrides the proxy widget + cursor. The proxy cursor changes depending on + which embedded subwidget is currently under the + mouse. + \row \o QWidget::sizeHint() + \o QGraphicsProxyWidget::sizeHint() + \o All size hint functionality from the embedded + widget is forwarded by the proxy. + \row \o QWidget::getContentsMargins() + \o QGraphicsProxyWidget::getContentsMargins() + \o Updated once by setWidget(). + \row \o QWidget::windowTitle + \o QGraphicsProxyWidget::windowTitle + \o Updated once by setWidget(). + \endtable + + \note QGraphicsScene keeps the embedded widget in a special state that + prevents it from disturbing other widgets (both embedded and not embedded) + while the widget is embedded. In this state, the widget may differ slightly + in behavior from when it is not embedded. + + \warning This class is provided for convenience when bridging + QWidgets and QGraphicsItems, it should not be used for + high-performance scenarios. + + \sa QGraphicsScene::addWidget(), QGraphicsWidget +*/ + +extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); +Q_GUI_EXPORT extern bool qt_tab_all_widgets; + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::init() +{ + Q_Q(QGraphicsProxyWidget); + q->setFocusPolicy(Qt::WheelFocus); + q->setAcceptDrops(true); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::sendWidgetMouseEvent(QGraphicsSceneHoverEvent *event) +{ + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); + mouseEvent.setPos(event->pos()); + mouseEvent.setScreenPos(event->screenPos()); + mouseEvent.setButton(Qt::NoButton); + mouseEvent.setButtons(0); + mouseEvent.setModifiers(event->modifiers()); + sendWidgetMouseEvent(&mouseEvent); + event->setAccepted(mouseEvent.isAccepted()); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::sendWidgetMouseEvent(QGraphicsSceneMouseEvent *event) +{ + if (!event || !widget || !widget->isVisible()) + return; + Q_Q(QGraphicsProxyWidget); + + // Find widget position and receiver. + QPointF pos = event->pos(); + QPointer alienWidget = widget->childAt(pos.toPoint()); + QPointer receiver = alienWidget ? alienWidget : widget; + + if (QWidgetPrivate::nearestGraphicsProxyWidget(receiver) != q) + return; //another proxywidget will handle the events + + // Translate QGraphicsSceneMouse events to QMouseEvents. + QEvent::Type type = QEvent::None; + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + type = QEvent::MouseButtonPress; + if (!embeddedMouseGrabber) + embeddedMouseGrabber = receiver; + else + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseRelease: + type = QEvent::MouseButtonRelease; + if (embeddedMouseGrabber) + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseDoubleClick: + type = QEvent::MouseButtonDblClick; + if (!embeddedMouseGrabber) + embeddedMouseGrabber = receiver; + else + receiver = embeddedMouseGrabber; + break; + case QEvent::GraphicsSceneMouseMove: + type = QEvent::MouseMove; + if (embeddedMouseGrabber) + receiver = embeddedMouseGrabber; + break; + default: + Q_ASSERT_X(false, "QGraphicsProxyWidget", "internal error"); + break; + } + + if (!lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(embeddedMouseGrabber ? embeddedMouseGrabber : receiver, 0); + lastWidgetUnderMouse = receiver; + } + + // Map event position from us to the receiver + pos = mapToReceiver(pos, receiver); + + // Send mouse event. + QMouseEvent *mouseEvent = QMouseEvent::createExtendedMouseEvent(type, pos, + receiver->mapToGlobal(pos.toPoint()), event->button(), + event->buttons(), event->modifiers()); + + QWidget *embeddedMouseGrabberPtr = (QWidget *)embeddedMouseGrabber; + QApplicationPrivate::sendMouseEvent(receiver, mouseEvent, alienWidget, widget, + &embeddedMouseGrabberPtr, lastWidgetUnderMouse, event->spontaneous()); + embeddedMouseGrabber = embeddedMouseGrabberPtr; + + // Handle enter/leave events when last button is released from mouse + // grabber child widget. + if (embeddedMouseGrabber && type == QEvent::MouseButtonRelease && !event->buttons()) { + Q_Q(QGraphicsProxyWidget); + if (q->rect().contains(event->pos()) && q->acceptsHoverEvents()) + lastWidgetUnderMouse = alienWidget ? alienWidget : widget; + else // released on the frame our outside the item, or doesn't accept hover events. + lastWidgetUnderMouse = 0; + + QApplicationPrivate::dispatchEnterLeave(lastWidgetUnderMouse, embeddedMouseGrabber); + embeddedMouseGrabber = 0; + +#ifndef QT_NO_CURSOR + // ### Restore the cursor, don't override it. + if (!lastWidgetUnderMouse) + q->unsetCursor(); +#endif + } + + event->setAccepted(mouseEvent->isAccepted()); + delete mouseEvent; +} + +void QGraphicsProxyWidgetPrivate::sendWidgetKeyEvent(QKeyEvent *event) +{ + Q_Q(QGraphicsProxyWidget); + if (!event || !widget || !widget->isVisible()) + return; + + QPointer receiver = widget->focusWidget(); + if (!receiver) + receiver = widget; + Q_ASSERT(receiver); + + do { + bool res = QApplication::sendEvent(receiver, event); + if ((res && event->isAccepted()) || (q->isWindow() && receiver == widget)) + break; + receiver = receiver->parentWidget(); + } while (receiver); +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::removeSubFocusHelper(QWidget *widget, Qt::FocusReason reason) +{ + QFocusEvent event(QEvent::FocusOut, reason); + QPointer widgetGuard = widget; + QApplication::sendEvent(widget, &event); + if (widgetGuard && event.isAccepted()) + QApplication::sendEvent(widget->style(), &event); +} + +/*! + \internal + + Reimplemented from QGraphicsItemPrivate. ### Qt 5: Move impl to + reimplementation QGraphicsProxyWidget::inputMethodQuery(). +*/ +QVariant QGraphicsProxyWidgetPrivate::inputMethodQueryHelper(Qt::InputMethodQuery query) const +{ + Q_Q(const QGraphicsProxyWidget); + if (!widget || !q->hasFocus()) + return QVariant(); + + QWidget *focusWidget = widget->focusWidget(); + if (!focusWidget) + focusWidget = widget; + QVariant v = focusWidget->inputMethodQuery(query); + QPointF focusWidgetPos = q->subWidgetRect(focusWidget).topLeft(); + switch (v.type()) { + case QVariant::RectF: + v = v.toRectF().translated(focusWidgetPos); + break; + case QVariant::PointF: + v = v.toPointF() + focusWidgetPos; + break; + case QVariant::Rect: + v = v.toRect().translated(focusWidgetPos.toPoint()); + break; + case QVariant::Point: + v = v.toPoint() + focusWidgetPos.toPoint(); + break; + default: + break; + } + return v; +} + +/*! + \internal + Some of the logic is shared with QApplicationPrivate::focusNextPrevChild_helper +*/ +QWidget *QGraphicsProxyWidgetPrivate::findFocusChild(QWidget *child, bool next) const +{ + if (!widget) + return 0; + + // Run around the focus chain until we find a widget that can take tab focus. + if (!child) { + child = next ? (QWidget *)widget : widget->d_func()->focus_prev; + } else { + child = next ? child->d_func()->focus_next : child->d_func()->focus_prev; + if ((next && child == widget) || (!next && child == widget->d_func()->focus_prev)) { + return 0; + } + } + + QWidget *oldChild = child; + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + do { + if (child->isEnabled() + && child->isVisibleTo(widget) + && ((child->focusPolicy() & focus_flag) == focus_flag) + && !(child->d_func()->extra && child->d_func()->extra->focus_proxy)) { + return child; + } + child = next ? child->d_func()->focus_next : child->d_func()->focus_prev; + } while (child != oldChild && !(next && child == widget) && !(!next && child == widget->d_func()->focus_prev)); + return 0; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::_q_removeWidgetSlot() +{ + Q_Q(QGraphicsProxyWidget); + widget = 0; + delete q; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateWidgetGeometryFromProxy() +{ +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateProxyGeometryFromWidget() +{ + Q_Q(QGraphicsProxyWidget); + if (!widget) + return; + + QRectF widgetGeometry = widget->geometry(); + QWidget *parentWidget = widget->parentWidget(); + if (widget->isWindow()) { + QGraphicsProxyWidget *proxyParent = 0; + if (parentWidget && (proxyParent = qobject_cast(q->parentWidget()))) { + // Nested window proxy (e.g., combobox popup), map widget to the + // parent widget's global coordinates, and map that to the parent + // proxy's child coordinates. + widgetGeometry.moveTo(proxyParent->subWidgetRect(parentWidget).topLeft() + + parentWidget->mapFromGlobal(widget->pos())); + } + } + + // Adjust to size hint if the widget has never been resized. + if (!widget->size().isValid()) + widgetGeometry.setSize(widget->sizeHint()); + + // Assign new geometry. + posChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + q->setGeometry(widgetGeometry); + posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; +} + +/*! + \internal +*/ +void QGraphicsProxyWidgetPrivate::updateProxyInputMethodAcceptanceFromWidget() +{ + Q_Q(QGraphicsProxyWidget); + if (!widget) + return; + + QWidget *focusWidget = widget->focusWidget(); + if (!focusWidget) + focusWidget = widget; + q->setFlag(QGraphicsItem::ItemAcceptsInputMethod, + focusWidget->testAttribute(Qt::WA_InputMethodEnabled)); +} + +/*! + \internal + + Embeds \a subWin as a subwindow of this proxy widget. \a subWin must be a top-level + widget and a descendant of the widget managed by this proxy. A separate subproxy + will be created as a child of this proxy widget to manage \a subWin. +*/ +void QGraphicsProxyWidgetPrivate::embedSubWindow(QWidget *subWin) +{ + QWExtra *extra; + if (!((extra = subWin->d_func()->extra) && extra->proxyWidget)) { + QGraphicsProxyWidget *subProxy = new QGraphicsProxyWidget(q_func(), subWin->windowFlags()); + subProxy->d_func()->setWidget_helper(subWin, false); + } +} + +/*! + \internal + + Removes ("unembeds") \a subWin and deletes the proxy holder item. This can + happen when QWidget::setParent() reparents the embedded window out of + "embedded space". +*/ +void QGraphicsProxyWidgetPrivate::unembedSubWindow(QWidget *subWin) +{ + foreach (QGraphicsItem *child, children) { + if (child->isWidget()) { + if (QGraphicsProxyWidget *proxy = qobject_cast(static_cast(child))) { + if (proxy->widget() == subWin) { + proxy->setWidget(0); + scene->removeItem(proxy); + delete proxy; + return; + } + } + } + } +} + +bool QGraphicsProxyWidgetPrivate::isProxyWidget() const +{ + return true; +} + +/*! + \internal +*/ +QPointF QGraphicsProxyWidgetPrivate::mapToReceiver(const QPointF &pos, const QWidget *receiver) const +{ + QPointF p = pos; + // Map event position from us to the receiver, preserving its + // precision (don't use QWidget::mapFrom here). + while (receiver && receiver != widget) { + p -= QPointF(receiver->pos()); + receiver = receiver->parentWidget(); + } + return p; +} + +/*! + Constructs a new QGraphicsProxy widget. \a parent and \a wFlags are passed + to QGraphicsItem's constructor. +*/ +QGraphicsProxyWidget::QGraphicsProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsWidget(*new QGraphicsProxyWidgetPrivate, parent, 0, wFlags) +{ + Q_D(QGraphicsProxyWidget); + d->init(); +} + +/*! + Destroys the proxy widget and any embedded widget. +*/ +QGraphicsProxyWidget::~QGraphicsProxyWidget() +{ + Q_D(QGraphicsProxyWidget); + if (d->widget) { + QObject::disconnect(d->widget, SIGNAL(destroyed()), this, SLOT(_q_removeWidgetSlot())); + delete d->widget; + } +} + +/*! + Embeds \a widget into this proxy widget. The embedded widget must reside + exclusively either inside or outside of Graphics View. You cannot embed a + widget as long as it is is visible elsewhere in the UI, at the same time. + + \a widget must be a top-level widget whose parent is 0. + + When the widget is embedded, its state (e.g., visible, enabled, geometry, + size hints) is copied into the proxy widget. If the embedded widget is + explicitly hidden or disabled, the proxy widget will become explicitly + hidden or disabled after embedding is complete. The class documentation + has a full overview over the shared state. + + QGraphicsProxyWidget's window flags determine whether the widget, after + embedding, will be given window decorations or not. + + After this function returns, QGraphicsProxyWidget will keep its state + synchronized with that of \a widget whenever possible. + + If a widget is already embedded by this proxy when this function is + called, that widget will first be automatically unembedded. Passing 0 for + the \a widget argument will only unembed the widget, and the ownership of + the currently embedded widget will be passed on to the caller. + Every child widget that are embedded will also be embedded and their proxy + widget destroyed. + + Note that widgets with the Qt::WA_PaintOnScreen widget attribute + set and widgets that wrap an external application or controller + cannot be embedded. Examples are QGLWidget and QAxWidget. + + \sa widget() +*/ +void QGraphicsProxyWidget::setWidget(QWidget *widget) +{ + Q_D(QGraphicsProxyWidget); + d->setWidget_helper(widget, true); +} + +void QGraphicsProxyWidgetPrivate::setWidget_helper(QWidget *newWidget, bool autoShow) +{ + Q_Q(QGraphicsProxyWidget); + if (newWidget == widget) + return; + if (widget) { + QObject::disconnect(widget, SIGNAL(destroyed()), q, SLOT(_q_removeWidgetSlot())); + widget->removeEventFilter(q); + widget->setAttribute(Qt::WA_DontShowOnScreen, false); + widget->d_func()->extra->proxyWidget = 0; + resolveFont(inheritedFontResolveMask); + resolvePalette(inheritedPaletteResolveMask); + widget->update(); + + foreach (QGraphicsItem *child, q->childItems()) { + if (child->d_ptr->isProxyWidget()) { + QGraphicsProxyWidget *childProxy = static_cast(child); + QWidget * parent = childProxy->widget(); + while (parent->parentWidget() != 0) { + if (parent == widget) + break; + parent = parent->parentWidget(); + } + if (!childProxy->widget() || parent != widget) + continue; + childProxy->setWidget(0); + delete childProxy; + } + } + + widget = 0; +#ifndef QT_NO_CURSOR + q->unsetCursor(); +#endif + q->setAcceptHoverEvents(false); + if (!newWidget) + q->update(); + } + if (!newWidget) + return; + if (!newWidget->isWindow()) { + QWExtra *extra = newWidget->parentWidget()->d_func()->extra; + if (!extra || !extra->proxyWidget) { + qWarning("QGraphicsProxyWidget::setWidget: cannot embed widget %p " + "which is not a toplevel widget, and is not a child of an embedded widget", newWidget); + return; + } + } + + // Register this proxy within the widget's private. + // ### This is a bit backdoorish + QWExtra *extra = newWidget->d_func()->extra; + if (!extra) { + newWidget->d_func()->createExtra(); + extra = newWidget->d_func()->extra; + } + QGraphicsProxyWidget **proxyWidget = &extra->proxyWidget; + if (*proxyWidget) { + if (*proxyWidget != q) { + qWarning("QGraphicsProxyWidget::setWidget: cannot embed widget %p" + "; already embedded", newWidget); + } + return; + } + *proxyWidget = q; + + newWidget->setAttribute(Qt::WA_DontShowOnScreen); + newWidget->ensurePolished(); + // Do not wait for this widget to close before the app closes ### + // shouldn't this widget inherit the attribute? + newWidget->setAttribute(Qt::WA_QuitOnClose, false); + q->setAcceptHoverEvents(true); + + if (newWidget->testAttribute(Qt::WA_NoSystemBackground)) + q->setAttribute(Qt::WA_NoSystemBackground); + if (newWidget->testAttribute(Qt::WA_OpaquePaintEvent)) + q->setAttribute(Qt::WA_OpaquePaintEvent); + + widget = newWidget; + + // Changes only go from the widget to the proxy. + enabledChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + visibleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + posChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + + if ((autoShow && !newWidget->testAttribute(Qt::WA_WState_ExplicitShowHide)) || !newWidget->testAttribute(Qt::WA_WState_Hidden)) { + newWidget->show(); + } + + // Copy the state from the widget onto the proxy. +#ifndef QT_NO_CURSOR + if (newWidget->testAttribute(Qt::WA_SetCursor)) + q->setCursor(widget->cursor()); +#endif + q->setEnabled(newWidget->isEnabled()); + q->setVisible(newWidget->isVisible()); + q->setLayoutDirection(newWidget->layoutDirection()); + if (newWidget->testAttribute(Qt::WA_SetStyle)) + q->setStyle(widget->style()); + + resolveFont(inheritedFontResolveMask); + resolvePalette(inheritedPaletteResolveMask); + + if (!newWidget->testAttribute(Qt::WA_Resized)) + newWidget->adjustSize(); + + int left, top, right, bottom; + newWidget->getContentsMargins(&left, &top, &right, &bottom); + q->setContentsMargins(left, top, right, bottom); + q->setWindowTitle(newWidget->windowTitle()); + + // size policies and constraints.. + q->setSizePolicy(newWidget->sizePolicy()); + QSize sz = newWidget->minimumSize(); + q->setMinimumSize(sz.isNull() ? QSizeF() : QSizeF(sz)); + sz = newWidget->maximumSize(); + q->setMaximumSize(sz.isNull() ? QSizeF() : QSizeF(sz)); + + updateProxyGeometryFromWidget(); + + updateProxyInputMethodAcceptanceFromWidget(); + + // Hook up the event filter to keep the state up to date. + newWidget->installEventFilter(q); + QObject::connect(newWidget, SIGNAL(destroyed()), q, SLOT(_q_removeWidgetSlot())); + + // Changes no longer go only from the widget to the proxy. + enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; +} + +/*! + Returns a pointer to the embedded widget. + + \sa setWidget() +*/ +QWidget *QGraphicsProxyWidget::widget() const +{ + Q_D(const QGraphicsProxyWidget); + return d->widget; +} + +/*! + Returns the rectangle for \a widget, which must be a descendant of + widget(), or widget() itself, in this proxy item's local coordinates. + + If no widget is embedded, \a widget is 0, or \a widget is not a + descendant of the embedded widget, this function returns an empty QRectF. + + \sa widget() +*/ +QRectF QGraphicsProxyWidget::subWidgetRect(const QWidget *widget) const +{ + Q_D(const QGraphicsProxyWidget); + if (!widget || !d->widget) + return QRectF(); + if (d->widget == widget || d->widget->isAncestorOf(widget)) + return QRectF(widget->mapTo(d->widget, QPoint(0, 0)), widget->size()); + return QRectF(); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::setGeometry(const QRectF &rect) +{ + Q_D(QGraphicsProxyWidget); + bool proxyResizesWidget = !d->posChangeMode && !d->sizeChangeMode; + if (proxyResizesWidget) { + d->posChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->sizeChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + } + QGraphicsWidget::setGeometry(rect); + if (proxyResizesWidget) { + d->posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + d->sizeChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } +} + +/*! + \reimp +*/ +QVariant QGraphicsProxyWidget::itemChange(GraphicsItemChange change, + const QVariant &value) +{ + Q_D(QGraphicsProxyWidget); + + switch (change) { + case ItemPositionChange: + // The item's position is either changed directly on the proxy, in + // which case the position change should propagate to the widget, + // otherwise it happens as a side effect when filtering QEvent::Move. + if (!d->posChangeMode) + d->posChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemPositionHasChanged: + // Move the internal widget if we're in widget-to-proxy + // mode. Otherwise the widget has already moved. + if (d->widget && d->posChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->move(value.toPoint()); + if (d->posChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->posChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + case ItemVisibleChange: + if (!d->visibleChangeMode) + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemVisibleHasChanged: + if (d->widget && d->visibleChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->setVisible(isVisible()); + if (d->visibleChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + case ItemEnabledChange: + if (!d->enabledChangeMode) + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + break; + case ItemEnabledHasChanged: + if (d->widget && d->enabledChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->setEnabled(isEnabled()); + if (d->enabledChangeMode == QGraphicsProxyWidgetPrivate::ProxyToWidgetMode) + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + break; + default: + break; + } + return QGraphicsWidget::itemChange(change, value); +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::event(QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return QGraphicsWidget::event(event); + + switch (event->type()) { + case QEvent::StyleChange: + // Propagate style changes to the embedded widget. + if (!d->styleChangeMode) { + d->styleChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->widget->setStyle(style()); + d->styleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::FontChange: { + // Propagate to widget. + QWidgetPrivate *wd = d->widget->d_func(); + int mask = d->font.resolve() | d->inheritedFontResolveMask; + wd->inheritedFontResolveMask = mask; + wd->resolveFont(); + break; + } + case QEvent::PaletteChange: { + // Propagate to widget. + QWidgetPrivate *wd = d->widget->d_func(); + int mask = d->palette.resolve() | d->inheritedPaletteResolveMask; + wd->inheritedPaletteResolveMask = mask; + wd->resolvePalette(); + break; + } + case QEvent::InputMethod: { + // Forward input method events if the focus widget enables + // input methods. + // ### Qt 4.5: this code must also go into a reimplementation + // of inputMethodEvent(). + QWidget *focusWidget = d->widget->focusWidget(); + if (focusWidget && focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) + QApplication::sendEvent(focusWidget, event); + break; + } + case QEvent::ShortcutOverride: { + QWidget *focusWidget = d->widget->focusWidget(); + while (focusWidget) { + QApplication::sendEvent(focusWidget, event); + if (event->isAccepted()) + return true; + focusWidget = focusWidget->parentWidget(); + } + return false; + } + case QEvent::KeyPress: { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + QWidget *focusWidget = d->widget->focusWidget(); + while (focusWidget) { + bool res = QApplication::sendEvent(focusWidget, event); + if ((res && event->isAccepted()) || (isWindow() && focusWidget == d->widget)) { + event->accept(); + break; + } + focusWidget = focusWidget->parentWidget(); + } + return true; + } + } + break; + } +#ifndef QT_NO_TOOLTIP + case QEvent::GraphicsSceneHelp: { + // Propagate the help event (for tooltip) to the widget under mouse + if (d->lastWidgetUnderMouse) { + QGraphicsSceneHelpEvent *he = static_cast(event); + QPoint pos = d->mapToReceiver(mapFromScene(he->scenePos()), d->lastWidgetUnderMouse).toPoint(); + QHelpEvent e(QEvent::ToolTip, pos, he->screenPos()); + QApplication::sendEvent(d->lastWidgetUnderMouse, &e); + event->setAccepted(e.isAccepted()); + return e.isAccepted(); + } + break; + } + case QEvent::ToolTipChange: { + // Propagate tooltip change to the widget + if (!d->tooltipChangeMode) { + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::ProxyToWidgetMode; + d->widget->setToolTip(toolTip()); + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + } +#endif + default: + break; + } + return QGraphicsWidget::event(event); +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + + if (object == d->widget) { + switch (event->type()) { + case QEvent::LayoutRequest: + updateGeometry(); + break; + case QEvent::Resize: + // If the widget resizes itself, we resize the proxy too. + // Prevent feed-back by checking the geometry change mode. + if (!d->sizeChangeMode) + d->updateProxyGeometryFromWidget(); + break; + case QEvent::Move: + // If the widget moves itself, we move the proxy too. Prevent + // feed-back by checking the geometry change mode. + if (!d->posChangeMode) + d->updateProxyGeometryFromWidget(); + break; + case QEvent::Hide: + case QEvent::Show: + // If the widget toggles its visible state, the proxy will follow. + if (!d->visibleChangeMode) { + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setVisible(event->type() == QEvent::Show); + d->visibleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::EnabledChange: + // If the widget toggles its enabled state, the proxy will follow. + if (!d->enabledChangeMode) { + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setEnabled(d->widget->isEnabled()); + d->enabledChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; + case QEvent::StyleChange: + // Propagate style changes to the proxy. + if (!d->styleChangeMode) { + d->styleChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setStyle(d->widget->style()); + d->styleChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTipChange: + // Propagate tooltip change to the proxy. + if (!d->tooltipChangeMode) { + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::WidgetToProxyMode; + setToolTip(d->widget->toolTip()); + d->tooltipChangeMode = QGraphicsProxyWidgetPrivate::NoMode; + } + break; +#endif + default: + break; + } + } + return QGraphicsWidget::eventFilter(object, event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hideEvent(QHideEvent *event) +{ + Q_UNUSED(event); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QGraphicsProxyWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (!event || !d->widget || !d->widget->isVisible() || !hasFocus()) + return; + + // Find widget position and receiver. + QPointF pos = event->pos(); + QPointer alienWidget = d->widget->childAt(pos.toPoint()); + QPointer receiver = alienWidget ? alienWidget : d->widget; + + // Map event position from us to the receiver + pos = d->mapToReceiver(pos, receiver); + + QPoint globalPos = receiver->mapToGlobal(pos.toPoint()); + //If the receiver by-pass the proxy its popups + //will be top level QWidgets therefore they need + //the screen position. mapToGlobal expect the widget to + //have proper coordinates in regards of the windowing system + //but it's not true because the widget is embedded. + if (bypassGraphicsProxyWidget(receiver)) + globalPos = event->screenPos(); + + // Send mouse event. ### Doesn't propagate the event. + QContextMenuEvent contextMenuEvent(QContextMenuEvent::Reason(event->reason()), + pos.toPoint(), globalPos, event->modifiers()); + QApplication::sendEvent(receiver, &contextMenuEvent); + + event->setAccepted(contextMenuEvent.isAccepted()); +} +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return; + + QDragEnterEvent proxyDragEnter(event->pos().toPoint(), event->dropAction(), event->mimeData(), event->buttons(), event->modifiers()); + proxyDragEnter.setAccepted(event->isAccepted()); + QApplication::sendEvent(d->widget, &proxyDragEnter); + event->setAccepted(proxyDragEnter.isAccepted()); + if (proxyDragEnter.isAccepted()) // we discard answerRect + event->setDropAction(proxyDragEnter.dropAction()); +#endif +} +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_UNUSED(event); +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsProxyWidget); + if (!d->widget || !d->dragDropWidget) + return; + QDragLeaveEvent proxyDragLeave; + QApplication::sendEvent(d->dragDropWidget, &proxyDragLeave); + d->dragDropWidget = 0; +#endif +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (!d->widget) + return; + QPointF p = event->pos(); + event->ignore(); + QPointer subWidget = d->widget->childAt(p.toPoint()); + QPointer receiver = subWidget ? subWidget : d->widget; + bool eventDelivered = false; + for (; receiver; receiver = receiver->parentWidget()) { + if (!receiver->isEnabled() || !receiver->acceptDrops()) + continue; + // Map event position from us to the receiver + QPoint receiverPos = d->mapToReceiver(p, receiver).toPoint(); + if (receiver != d->dragDropWidget) { + // Try to enter before we leave + QDragEnterEvent dragEnter(receiverPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + dragEnter.setDropAction(event->proposedAction()); + QApplication::sendEvent(receiver, &dragEnter); + event->setAccepted(dragEnter.isAccepted()); + event->setDropAction(dragEnter.dropAction()); + if (!event->isAccepted()) { + // propagate to the parent widget + continue; + } + + d->lastDropAction = event->dropAction(); + + if (d->dragDropWidget) { + QDragLeaveEvent dragLeave; + QApplication::sendEvent(d->dragDropWidget, &dragLeave); + } + d->dragDropWidget = receiver; + } + + QDragMoveEvent dragMove(receiverPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + event->setDropAction(d->lastDropAction); + QApplication::sendEvent(receiver, &dragMove); + event->setAccepted(dragMove.isAccepted()); + event->setDropAction(dragMove.dropAction()); + if (event->isAccepted()) + d->lastDropAction = event->dropAction(); + eventDelivered = true; + break; + } + + if (!eventDelivered) { + if (d->dragDropWidget) { + // Leave the last drag drop item + QDragLeaveEvent dragLeave; + QApplication::sendEvent(d->dragDropWidget, &dragLeave); + d->dragDropWidget = 0; + } + // Propagate + event->setDropAction(Qt::IgnoreAction); + } +#endif +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::dropEvent(QGraphicsSceneDragDropEvent *event) +{ +#ifdef QT_NO_DRAGANDDROP + Q_UNUSED(event); +#else + Q_D(QGraphicsProxyWidget); + if (d->widget && d->dragDropWidget) { + QPoint widgetPos = d->mapToReceiver(event->pos(), d->dragDropWidget).toPoint(); + QDropEvent dropEvent(widgetPos, event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); + QApplication::sendEvent(d->dragDropWidget, &dropEvent); + event->setAccepted(dropEvent.isAccepted()); + d->dragDropWidget = 0; + } +#endif +} +#endif + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + Q_D(QGraphicsProxyWidget); + // If hoverMove was compressed away, make sure we update properly here. + if (d->lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(0, d->lastWidgetUnderMouse); + d->lastWidgetUnderMouse = 0; + } +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::hoverMoveEvent"; +#endif + // Ignore events on the window frame. + if (!d->widget || !rect().contains(event->pos())) { + if (d->lastWidgetUnderMouse) { + QApplicationPrivate::dispatchEnterLeave(0, d->lastWidgetUnderMouse); + d->lastWidgetUnderMouse = 0; + } + return; + } + + d->embeddedMouseGrabber = 0; + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::grabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::ungrabMouseEvent(QEvent *event) +{ + Q_D(QGraphicsProxyWidget); + Q_UNUSED(event); + d->embeddedMouseGrabber = 0; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseMoveEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mousePressEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseDoubleClickEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void QGraphicsProxyWidget::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::wheelEvent"; +#endif + if (!d->widget) + return; + + QPointF pos = event->pos(); + QPointer receiver = d->widget->childAt(pos.toPoint()); + if (!receiver) + receiver = d->widget; + + // Map event position from us to the receiver + pos = d->mapToReceiver(pos, receiver); + + // Send mouse event. + QWheelEvent wheelEvent(pos.toPoint(), event->screenPos(), event->delta(), + event->buttons(), event->modifiers(), event->orientation()); + QPointer focusWidget = d->widget->focusWidget(); + extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); + qt_sendSpontaneousEvent(receiver, &wheelEvent); + event->setAccepted(wheelEvent.isAccepted()); + + // ### Remove, this should be done by proper focusIn/focusOut events. + if (focusWidget && !focusWidget->hasFocus()) { + focusWidget->update(); + focusWidget = d->widget->focusWidget(); + if (focusWidget && focusWidget->hasFocus()) + focusWidget->update(); + } +} +#endif + +/*! + \reimp +*/ +void QGraphicsProxyWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::mouseReleaseEvent"; +#endif + d->sendWidgetMouseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::keyPressEvent(QKeyEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::keyPressEvent"; +#endif + d->sendWidgetKeyEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QGraphicsProxyWidget); +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::keyReleaseEvent"; +#endif + d->sendWidgetKeyEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::focusInEvent(QFocusEvent *event) +{ +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::focusInEvent"; +#endif + Q_D(QGraphicsProxyWidget); + + if (d->focusFromWidgetToProxy) { + // Prevent recursion when the proxy autogains focus through the + // embedded widget calling setFocus(). ### Could be done with event + // filter on FocusIn instead? + return; + } + + d->proxyIsGivingFocus = true; + + switch (event->reason()) { + case Qt::TabFocusReason: { + if (QWidget *focusChild = d->findFocusChild(0, true)) + focusChild->setFocus(event->reason()); + break; + } + case Qt::BacktabFocusReason: + if (QWidget *focusChild = d->findFocusChild(0, false)) + focusChild->setFocus(event->reason()); + break; + default: + if (d->widget && d->widget->focusWidget()) { + d->widget->focusWidget()->setFocus(event->reason()); + } + break; + } + + d->proxyIsGivingFocus = false; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::focusOutEvent(QFocusEvent *event) +{ +#ifdef GRAPHICSPROXYWIDGET_DEBUG + qDebug() << "QGraphicsProxyWidget::focusOutEvent"; +#endif + Q_D(QGraphicsProxyWidget); + if (d->widget) { + // We need to explicitly remove subfocus from the embedded widget's + // focus widget. + if (QWidget *focusWidget = d->widget->focusWidget()) + d->removeSubFocusHelper(focusWidget, event->reason()); + } +} + +/*! + \reimp +*/ +bool QGraphicsProxyWidget::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsProxyWidget); + if (!d->widget || !d->scene) + return QGraphicsWidget::focusNextPrevChild(next); + + Qt::FocusReason reason = next ? Qt::TabFocusReason : Qt::BacktabFocusReason; + QWidget *lastFocusChild = d->widget->focusWidget(); + if (QWidget *newFocusChild = d->findFocusChild(lastFocusChild, next)) { + newFocusChild->setFocus(reason); + return true; + } + + return QGraphicsWidget::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +QSizeF QGraphicsProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsProxyWidget); + if (!d->widget) + return QGraphicsWidget::sizeHint(which, constraint); + + QSizeF sh; + switch (which) { + case Qt::PreferredSize: + if (QLayout *l = d->widget->layout()) + sh = l->sizeHint(); + else + sh = d->widget->sizeHint(); + break; + case Qt::MinimumSize: + if (QLayout *l = d->widget->layout()) + sh = l->minimumSize(); + else + sh = d->widget->minimumSizeHint(); + break; + case Qt::MaximumSize: + if (QLayout *l = d->widget->layout()) + sh = l->maximumSize(); + else + sh = QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + break; + case Qt::MinimumDescent: + sh = constraint; + break; + default: + break; + } + return sh; +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + Q_D(QGraphicsProxyWidget); + if (d->widget) { + if (d->sizeChangeMode != QGraphicsProxyWidgetPrivate::WidgetToProxyMode) + d->widget->resize(event->newSize().toSize()); + } + QGraphicsWidget::resizeEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsProxyWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_D(QGraphicsProxyWidget); + Q_UNUSED(widget); + if (!d->widget || !d->widget->isVisible()) + return; + + // Filter out repaints on the window frame. + const QRect exposedWidgetRect = (option->exposedRect & rect()).toAlignedRect(); + if (exposedWidgetRect.isEmpty()) + return; + + // Disable QPainter's default pen being cosmetic. This allows widgets and + // styles to follow Qt's existing defaults without getting ugly cosmetic + // lines when scaled. + bool restore = !(painter->renderHints() & QPainter::NonCosmeticDefaultPen); + painter->setRenderHints(QPainter::NonCosmeticDefaultPen, true); + + d->widget->render(painter, exposedWidgetRect.topLeft(), exposedWidgetRect); + + // Restore the render hints if necessary. + if (restore) + painter->setRenderHints(QPainter::NonCosmeticDefaultPen, false); +} + +/*! + \reimp +*/ +int QGraphicsProxyWidget::type() const +{ + return Type; +} + +/*! + \since 4.5 + + Creates a proxy widget for the given \a child of the widget + contained in this proxy. + + This function makes it possible to acquire proxies for + non top-level widgets. For instance, you can embed a dialog, + and then transform only one of its widgets. + + If the widget is already embedded, return the existing proxy widget. + + \sa newProxyWidget(), QGraphicsScene::addWidget() +*/ +QGraphicsProxyWidget *QGraphicsProxyWidget::createProxyForChildWidget(QWidget *child) +{ + QGraphicsProxyWidget *proxy = child->graphicsProxyWidget(); + if (proxy) + return proxy; + if (!child->parentWidget()) { + qWarning("QGraphicsProxyWidget::createProxyForChildWidget: top-level widget not in a QGraphicsScene"); + return 0; + } + + QGraphicsProxyWidget *parentProxy = createProxyForChildWidget(child->parentWidget()); + if (!parentProxy) + return 0; + + if (!QMetaObject::invokeMethod(parentProxy, "newProxyWidget", Qt::DirectConnection, + Q_RETURN_ARG(QGraphicsProxyWidget*, proxy), Q_ARG(const QWidget*, child))) + return 0; + proxy->setParent(parentProxy); + proxy->setWidget(child); + return proxy; +} + +/*! + \fn QGraphicsProxyWidget *QGraphicsProxyWidget::newProxyWidget(const QWidget *child) + \since 4.5 + + Creates a proxy widget for the given \a child of the widget contained in this + proxy. + + You should not call this function directly; use + QGraphicsProxyWidget::createProxyForChildWidget() instead. + + This function is a fake virtual slot that you can reimplement in + your subclass in order to control how new proxy widgets are + created. The default implementation returns a proxy created with + the QGraphicsProxyWidget() constructor with this proxy widget as + the parent. + + \sa createProxyForChildWidget() +*/ +QGraphicsProxyWidget *QGraphicsProxyWidget::newProxyWidget(const QWidget *) +{ + return new QGraphicsProxyWidget(this); +} + + + +QT_END_NAMESPACE + +#include "moc_qgraphicsproxywidget.cpp" + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsproxywidget.h b/src/gui/graphicsview/qgraphicsproxywidget.h new file mode 100644 index 0000000000..b2ffe2f7d1 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsproxywidget.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSPROXYWIDGET_H +#define QGRAPHICSPROXYWIDGET_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsProxyWidgetPrivate; + +class Q_GUI_EXPORT QGraphicsProxyWidget : public QGraphicsWidget +{ + Q_OBJECT +public: + QGraphicsProxyWidget(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + ~QGraphicsProxyWidget(); + + void setWidget(QWidget *widget); + QWidget *widget() const; + + QRectF subWidgetRect(const QWidget *widget) const; + + void setGeometry(const QRectF &rect); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + enum { + Type = 12 + }; + int type() const; + + QGraphicsProxyWidget *createProxyForChildWidget(QWidget *child); + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + bool event(QEvent *event); + bool eventFilter(QObject *object, QEvent *event); + + void showEvent(QShowEvent *event); + void hideEvent(QHideEvent *event); + +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); +#endif + +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + void dropEvent(QGraphicsSceneDragDropEvent *event); +#endif + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void grabMouseEvent(QEvent *event); + void ungrabMouseEvent(QEvent *event); + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QGraphicsSceneWheelEvent *event); +#endif + + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + bool focusNextPrevChild(bool next); + // ### Qt 4.5: + // QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + // void inputMethodEvent(QInputMethodEvent *event); + + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void resizeEvent(QGraphicsSceneResizeEvent *event); + +protected Q_SLOTS: + QGraphicsProxyWidget *newProxyWidget(const QWidget *); + +private: + Q_DISABLE_COPY(QGraphicsProxyWidget) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QGraphicsProxyWidget) + Q_PRIVATE_SLOT(d_func(), void _q_removeWidgetSlot()) + + friend class QWidget; + friend class QWidgetPrivate; + friend class QGraphicsItem; +}; + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/gui/graphicsview/qgraphicsproxywidget_p.h b/src/gui/graphicsview/qgraphicsproxywidget_p.h new file mode 100644 index 0000000000..e1a96b0021 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsproxywidget_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSPROXYWIDGET_P_H +#define QGRAPHICSPROXYWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsproxywidget.h" +#include "private/qgraphicswidget_p.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsProxyWidgetPrivate : public QGraphicsWidgetPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsProxyWidget) +public: + QGraphicsProxyWidgetPrivate() + : dragDropWidget(0), + posChangeMode(NoMode), + sizeChangeMode(NoMode), + visibleChangeMode(NoMode), + enabledChangeMode(NoMode), + styleChangeMode(NoMode), + paletteChangeMode(NoMode), + tooltipChangeMode(NoMode), + focusFromWidgetToProxy(0) + { } + void init(); + void sendWidgetMouseEvent(QGraphicsSceneMouseEvent *event); + void sendWidgetMouseEvent(QGraphicsSceneHoverEvent *event); + void sendWidgetKeyEvent(QKeyEvent *event); + void setWidget_helper(QWidget *widget, bool autoShow); + + QWidget *findFocusChild(QWidget *child, bool next) const; + void removeSubFocusHelper(QWidget *widget, Qt::FocusReason reason); + + // ### Qt 5: Remove. Workaround for reimplementation added after Qt 4.4. + QVariant inputMethodQueryHelper(Qt::InputMethodQuery query) const; + + void _q_removeWidgetSlot(); + + void embedSubWindow(QWidget *); + void unembedSubWindow(QWidget *); + + bool isProxyWidget() const; + + QPointer widget; + QPointer lastWidgetUnderMouse; + QPointer embeddedMouseGrabber; + QWidget *dragDropWidget; + Qt::DropAction lastDropAction; + + void updateWidgetGeometryFromProxy(); + void updateProxyGeometryFromWidget(); + + void updateProxyInputMethodAcceptanceFromWidget(); + + QPointF mapToReceiver(const QPointF &pos, const QWidget *receiver) const; + + enum ChangeMode { + NoMode, + ProxyToWidgetMode, + WidgetToProxyMode + }; + quint32 posChangeMode : 2; + quint32 sizeChangeMode : 2; + quint32 visibleChangeMode : 2; + quint32 enabledChangeMode : 2; + quint32 styleChangeMode : 2; + quint32 paletteChangeMode : 2; + quint32 tooltipChangeMode : 2; + quint32 focusFromWidgetToProxy : 1; + quint32 proxyIsGivingFocus : 1; +}; + +QT_END_NAMESPACE + +#endif + +#endif diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp new file mode 100644 index 0000000000..0713d09296 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -0,0 +1,6491 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsScene + \brief The QGraphicsScene class provides a surface for managing a large + number of 2D graphical items. + \since 4.2 + \ingroup graphicsview-api + + + The class serves as a container for QGraphicsItems. It is used together + with QGraphicsView for visualizing graphical items, such as lines, + rectangles, text, or even custom items, on a 2D surface. QGraphicsScene is + part of the \l{Graphics View Framework}. + + QGraphicsScene also provides functionality that lets you efficiently + determine both the location of items, and for determining what items are + visible within an arbitrary area on the scene. With the QGraphicsView + widget, you can either visualize the whole scene, or zoom in and view only + parts of the scene. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 0 + + Note that QGraphicsScene has no visual appearance of its own; it only + manages the items. You need to create a QGraphicsView widget to visualize + the scene. + + To add items to a scene, you start off by constructing a QGraphicsScene + object. Then, you have two options: either add your existing QGraphicsItem + objects by calling addItem(), or you can call one of the convenience + functions addEllipse(), addLine(), addPath(), addPixmap(), addPolygon(), + addRect(), or addText(), which all return a pointer to the newly added item. + The dimensions of the items added with these functions are relative to the + item's coordinate system, and the items position is initialized to (0, + 0) in the scene. + + You can then visualize the scene using QGraphicsView. When the scene + changes, (e.g., when an item moves or is transformed) QGraphicsScene + emits the changed() signal. To remove an item, call removeItem(). + + QGraphicsScene uses an indexing algorithm to manage the location of items + efficiently. By default, a BSP (Binary Space Partitioning) tree is used; an + algorithm suitable for large scenes where most items remain static (i.e., + do not move around). You can choose to disable this index by calling + setItemIndexMethod(). For more information about the available indexing + algorithms, see the itemIndexMethod property. + + The scene's bounding rect is set by calling setSceneRect(). Items can be + placed at any position on the scene, and the size of the scene is by + default unlimited. The scene rect is used only for internal bookkeeping, + maintaining the scene's item index. If the scene rect is unset, + QGraphicsScene will use the bounding area of all items, as returned by + itemsBoundingRect(), as the scene rect. However, itemsBoundingRect() is a + relatively time consuming function, as it operates by collecting + positional information for every item on the scene. Because of this, you + should always set the scene rect when operating on large scenes. + + One of QGraphicsScene's greatest strengths is its ability to efficiently + determine the location of items. Even with millions of items on the scene, + the items() functions can determine the location of an item within few + milliseconds. There are several overloads to items(): one that finds items + at a certain position, one that finds items inside or intersecting with a + polygon or a rectangle, and more. The list of returned items is sorted by + stacking order, with the topmost item being the first item in the list. + For convenience, there is also an itemAt() function that returns the + topmost item at a given position. + + QGraphicsScene maintains selection information for the scene. To select + items, call setSelectionArea(), and to clear the current selection, call + clearSelection(). Call selectedItems() to get the list of all selected + items. + + \section1 Event Handling and Propagation + + Another responsibility that QGraphicsScene has, is to propagate events + from QGraphicsView. To send an event to a scene, you construct an event + that inherits QEvent, and then send it using, for example, + QApplication::sendEvent(). event() is responsible for dispatching + the event to the individual items. Some common events are handled by + convenience event handlers. For example, key press events are handled by + keyPressEvent(), and mouse press events are handled by mousePressEvent(). + + Key events are delivered to the \e {focus item}. To set the focus item, + you can either call setFocusItem(), passing an item that accepts focus, or + the item itself can call QGraphicsItem::setFocus(). Call focusItem() to + get the current focus item. For compatibility with widgets, the scene also + maintains its own focus information. By default, the scene does not have + focus, and all key events are discarded. If setFocus() is called, or if an + item on the scene gains focus, the scene automatically gains focus. If the + scene has focus, hasFocus() will return true, and key events will be + forwarded to the focus item, if any. If the scene loses focus, (i.e., + someone calls clearFocus()) while an item has focus, the scene will + maintain its item focus information, and once the scene regains focus, it + will make sure the last focus item regains focus. + + For mouse-over effects, QGraphicsScene dispatches \e {hover + events}. If an item accepts hover events (see + QGraphicsItem::acceptHoverEvents()), it will receive a \l + {QEvent::}{GraphicsSceneHoverEnter} event when the mouse enters + its area. As the mouse continues moving inside the item's area, + QGraphicsScene will send it \l {QEvent::}{GraphicsSceneHoverMove} + events. When the mouse leaves the item's area, the item will + receive a \l {QEvent::}{GraphicsSceneHoverLeave} event. + + All mouse events are delivered to the current \e {mouse grabber} + item. An item becomes the scene's mouse grabber if it accepts + mouse events (see QGraphicsItem::acceptedMouseButtons()) and it + receives a mouse press. It stays the mouse grabber until it + receives a mouse release when no other mouse buttons are + pressed. You can call mouseGrabberItem() to determine what item is + currently grabbing the mouse. + + \sa QGraphicsItem, QGraphicsView +*/ + +/*! + \enum QGraphicsScene::SceneLayer + \since 4.3 + + This enum describes the rendering layers in a QGraphicsScene. When + QGraphicsScene draws the scene contents, it renders each of these layers + separately, in order. + + Each layer represents a flag that can be OR'ed together when calling + functions such as invalidate() or QGraphicsView::invalidateScene(). + + \value ItemLayer The item layer. QGraphicsScene renders all items are in + this layer by calling the virtual function drawItems(). The item layer is + drawn after the background layer, but before the foreground layer. + + \value BackgroundLayer The background layer. QGraphicsScene renders the + scene's background in this layer by calling the virtual function + drawBackground(). The background layer is drawn first of all layers. + + \value ForegroundLayer The foreground layer. QGraphicsScene renders the + scene's foreground in this layer by calling the virtual function + drawForeground(). The foreground layer is drawn last of all layers. + + \value AllLayers All layers; this value represents a combination of all + three layers. + + \sa invalidate(), QGraphicsView::invalidateScene() +*/ + +/*! + \enum QGraphicsScene::ItemIndexMethod + + This enum describes the indexing algorithms QGraphicsScene provides for + managing positional information about items on the scene. + + \value BspTreeIndex A Binary Space Partitioning tree is applied. All + QGraphicsScene's item location algorithms are of an order close to + logarithmic complexity, by making use of binary search. Adding, moving and + removing items is logarithmic. This approach is best for static scenes + (i.e., scenes where most items do not move). + + \value NoIndex No index is applied. Item location is of linear complexity, + as all items on the scene are searched. Adding, moving and removing items, + however, is done in constant time. This approach is ideal for dynamic + scenes, where many items are added, moved or removed continuously. + + \sa setItemIndexMethod(), bspTreeDepth +*/ + +#include "qgraphicsscene.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" +#include "qgraphicsitem_p.h" +#include "qgraphicslayout.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicssceneindex_p.h" +#include "qgraphicsscenebsptreeindex_p.h" +#include "qgraphicsscenelinearindex_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#endif +#include +#include +#include + +// #define GESTURE_DEBUG +#ifndef GESTURE_DEBUG +# define DEBUG if (0) qDebug +#else +# define DEBUG qDebug +#endif + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +static void _q_hoverFromMouseEvent(QGraphicsSceneHoverEvent *hover, const QGraphicsSceneMouseEvent *mouseEvent) +{ + hover->setWidget(mouseEvent->widget()); + hover->setPos(mouseEvent->pos()); + hover->setScenePos(mouseEvent->scenePos()); + hover->setScreenPos(mouseEvent->screenPos()); + hover->setLastPos(mouseEvent->lastPos()); + hover->setLastScenePos(mouseEvent->lastScenePos()); + hover->setLastScreenPos(mouseEvent->lastScreenPos()); + hover->setModifiers(mouseEvent->modifiers()); + hover->setAccepted(mouseEvent->isAccepted()); +} + +/*! + \internal +*/ +QGraphicsScenePrivate::QGraphicsScenePrivate() + : indexMethod(QGraphicsScene::BspTreeIndex), + index(0), + lastItemCount(0), + hasSceneRect(false), + dirtyGrowingItemsBoundingRect(true), + updateAll(false), + calledEmitUpdated(false), + processDirtyItemsEmitted(false), + needSortTopLevelItems(true), + holesInTopLevelSiblingIndex(false), + topLevelSequentialOrdering(true), + scenePosDescendantsUpdatePending(false), + stickyFocus(false), + hasFocus(false), + lastMouseGrabberItemHasImplicitMouseGrab(false), + allItemsIgnoreHoverEvents(true), + allItemsUseDefaultCursor(true), + painterStateProtection(true), + sortCacheEnabled(false), + allItemsIgnoreTouchEvents(true), + selectionChanging(0), + rectAdjust(2), + focusItem(0), + lastFocusItem(0), + passiveFocusItem(0), + tabFocusFirst(0), + activePanel(0), + lastActivePanel(0), + activationRefCount(0), + childExplicitActivation(0), + lastMouseGrabberItem(0), + dragDropItem(0), + enterWidget(0), + lastDropAction(Qt::IgnoreAction), + style(0) +{ +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::init() +{ + Q_Q(QGraphicsScene); + + index = new QGraphicsSceneBspTreeIndex(q); + + // Keep this index so we can check for connected slots later on. + changedSignalIndex = signalIndex("changed(QList)"); + processDirtyItemsIndex = q->metaObject()->indexOfSlot("_q_processDirtyItems()"); + polishItemsIndex = q->metaObject()->indexOfSlot("_q_polishItems()"); + + qApp->d_func()->scene_list.append(q); + q->update(); +} + +/*! + \internal +*/ +QGraphicsScenePrivate *QGraphicsScenePrivate::get(QGraphicsScene *q) +{ + return q->d_func(); +} + +void QGraphicsScenePrivate::_q_emitUpdated() +{ + Q_Q(QGraphicsScene); + calledEmitUpdated = false; + + if (dirtyGrowingItemsBoundingRect) { + if (!hasSceneRect) { + const QRectF oldGrowingItemsBoundingRect = growingItemsBoundingRect; + growingItemsBoundingRect |= q->itemsBoundingRect(); + if (oldGrowingItemsBoundingRect != growingItemsBoundingRect) + emit q->sceneRectChanged(growingItemsBoundingRect); + } + dirtyGrowingItemsBoundingRect = false; + } + + // Ensure all views are connected if anything is connected. This disables + // the optimization that items send updates directly to the views, but it + // needs to happen in order to keep compatibility with the behavior from + // Qt 4.4 and backward. + if (isSignalConnected(changedSignalIndex)) { + for (int i = 0; i < views.size(); ++i) { + QGraphicsView *view = views.at(i); + if (!view->d_func()->connectedToScene) { + view->d_func()->connectedToScene = true; + q->connect(q, SIGNAL(changed(QList)), + views.at(i), SLOT(updateScene(QList))); + } + } + } else { + if (views.isEmpty()) { + updateAll = false; + return; + } + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->processPendingUpdates(); + // It's important that we update all views before we dispatch, hence two for-loops. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->dispatchPendingUpdateRequests(); + return; + } + + // Notify the changes to anybody interested. + QList oldUpdatedRects; + oldUpdatedRects = updateAll ? (QList() << q->sceneRect()) : updatedRects; + updateAll = false; + updatedRects.clear(); + emit q->changed(oldUpdatedRects); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::addChild(). +*/ +void QGraphicsScenePrivate::registerTopLevelItem(QGraphicsItem *item) +{ + item->d_ptr->ensureSequentialSiblingIndex(); + needSortTopLevelItems = true; // ### maybe false + item->d_ptr->siblingIndex = topLevelItems.size(); + topLevelItems.append(item); +} + +/*! + \internal + + ### This function is almost identical to QGraphicsItemPrivate::removeChild(). +*/ +void QGraphicsScenePrivate::unregisterTopLevelItem(QGraphicsItem *item) +{ + if (!holesInTopLevelSiblingIndex) + holesInTopLevelSiblingIndex = item->d_ptr->siblingIndex != topLevelItems.size() - 1; + if (topLevelSequentialOrdering && !holesInTopLevelSiblingIndex) + topLevelItems.removeAt(item->d_ptr->siblingIndex); + else + topLevelItems.removeOne(item); + // NB! Do not use topLevelItems.removeAt(item->d_ptr->siblingIndex) because + // the item is not guaranteed to be at the index after the list is sorted + // (see ensureSortedTopLevelItems()). + item->d_ptr->siblingIndex = -1; + if (topLevelSequentialOrdering) + topLevelSequentialOrdering = !holesInTopLevelSiblingIndex; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_polishItems() +{ + if (unpolishedItems.isEmpty()) + return; + + const QVariant booleanTrueVariant(true); + QGraphicsItem *item = 0; + QGraphicsItemPrivate *itemd = 0; + const int oldUnpolishedCount = unpolishedItems.count(); + + for (int i = 0; i < oldUnpolishedCount; ++i) { + item = unpolishedItems.at(i); + if (!item) + continue; + itemd = item->d_ptr.data(); + itemd->pendingPolish = false; + if (!itemd->explicitlyHidden) { + item->itemChange(QGraphicsItem::ItemVisibleChange, booleanTrueVariant); + item->itemChange(QGraphicsItem::ItemVisibleHasChanged, booleanTrueVariant); + } + if (itemd->isWidget) { + QEvent event(QEvent::Polish); + QApplication::sendEvent((QGraphicsWidget *)item, &event); + } + } + + if (unpolishedItems.count() == oldUnpolishedCount) { + // No new items were added to the vector. + unpolishedItems.clear(); + } else { + // New items were appended; keep them and remove the old ones. + unpolishedItems.remove(0, oldUnpolishedCount); + unpolishedItems.squeeze(); + QMetaObject::invokeMethod(q_ptr, "_q_polishItems", Qt::QueuedConnection); + } +} + +void QGraphicsScenePrivate::_q_processDirtyItems() +{ + processDirtyItemsEmitted = false; + + if (updateAll) { + Q_ASSERT(calledEmitUpdated); + // No need for further processing (except resetting the dirty states). + // The growingItemsBoundingRect is updated in _q_emitUpdated. + for (int i = 0; i < topLevelItems.size(); ++i) + resetDirtyItem(topLevelItems.at(i), /*recursive=*/true); + return; + } + + const bool wasPendingSceneUpdate = calledEmitUpdated; + const QRectF oldGrowingItemsBoundingRect = growingItemsBoundingRect; + + // Process items recursively. + for (int i = 0; i < topLevelItems.size(); ++i) + processDirtyItemsRecursive(topLevelItems.at(i)); + + dirtyGrowingItemsBoundingRect = false; + if (!hasSceneRect && oldGrowingItemsBoundingRect != growingItemsBoundingRect) + emit q_func()->sceneRectChanged(growingItemsBoundingRect); + + if (wasPendingSceneUpdate) + return; + + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->processPendingUpdates(); + + if (calledEmitUpdated) { + // We did a compatibility QGraphicsScene::update in processDirtyItemsRecursive + // and we cannot wait for the control to reach the eventloop before the + // changed signal is emitted, so we emit it now. + _q_emitUpdated(); + } + + // Immediately dispatch all pending update requests on the views. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->dispatchPendingUpdateRequests(); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setScenePosItemEnabled(QGraphicsItem *item, bool enabled) +{ + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = enabled; + p = p->d_ptr->parent; + } + if (!enabled && !scenePosDescendantsUpdatePending) { + scenePosDescendantsUpdatePending = true; + QMetaObject::invokeMethod(q_func(), "_q_updateScenePosDescendants", Qt::QueuedConnection); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::registerScenePosItem(QGraphicsItem *item) +{ + scenePosItems.insert(item); + setScenePosItemEnabled(item, true); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::unregisterScenePosItem(QGraphicsItem *item) +{ + scenePosItems.remove(item); + setScenePosItemEnabled(item, false); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_updateScenePosDescendants() +{ + foreach (QGraphicsItem *item, scenePosItems) { + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = 1; + p = p->d_ptr->parent; + } + } + scenePosDescendantsUpdatePending = false; +} + +/*! + \internal + + Schedules an item for removal. This function leaves some stale indexes + around in the BSP tree if called from the item's destructor; these will + be cleaned up the next time someone triggers purgeRemovedItems(). + + Note: This function might get called from QGraphicsItem's destructor. \a item is + being destroyed, so we cannot call any pure virtual functions on it (such + as boundingRect()). Also, it is unnecessary to update the item's own state + in any way. +*/ +void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) +{ + Q_Q(QGraphicsScene); + + // Clear focus on the item to remove any reference in the focusWidget chain. + item->clearFocus(); + + markDirty(item, QRectF(), /*invalidateChildren=*/false, /*force=*/false, + /*ignoreOpacity=*/false, /*removingItemFromScene=*/true); + + if (item->d_ptr->inDestructor) { + // The item is actually in its destructor, we call the special method in the index. + index->deleteItem(item); + } else { + // Can potentially call item->boundingRect() (virtual function), that's why + // we only can call this function if the item is not in its destructor. + index->removeItem(item); + } + + item->d_ptr->clearSubFocus(); + + if (item->flags() & QGraphicsItem::ItemSendsScenePositionChanges) + unregisterScenePosItem(item); + + QGraphicsScene *oldScene = item->d_func()->scene; + item->d_func()->scene = 0; + + //We need to remove all children first because they might use their parent + //attributes (e.g. sceneTransform). + if (!item->d_ptr->inDestructor) { + // Remove all children recursively + for (int i = 0; i < item->d_ptr->children.size(); ++i) + q->removeItem(item->d_ptr->children.at(i)); + } + + if (!item->d_ptr->inDestructor && item == tabFocusFirst) { + QGraphicsWidget *widget = static_cast(item); + widget->d_func()->fixFocusChainBeforeReparenting(0, oldScene, 0); + } + + // Unregister focus proxy. + item->d_ptr->resetFocusProxy(); + + // Remove from parent, or unregister from toplevels. + if (QGraphicsItem *parentItem = item->parentItem()) { + if (parentItem->scene()) { + Q_ASSERT_X(parentItem->scene() == q, "QGraphicsScene::removeItem", + "Parent item's scene is different from this item's scene"); + item->setParentItem(0); + } + } else { + unregisterTopLevelItem(item); + } + + // Reset the mouse grabber and focus item data. + if (item == focusItem) + focusItem = 0; + if (item == lastFocusItem) + lastFocusItem = 0; + if (item == passiveFocusItem) + passiveFocusItem = 0; + if (item == activePanel) { + // ### deactivate... + activePanel = 0; + } + if (item == lastActivePanel) + lastActivePanel = 0; + + // Cancel active touches + { + QMap::iterator it = itemForTouchPointId.begin(); + while (it != itemForTouchPointId.end()) { + if (it.value() == item) { + sceneCurrentTouchPoints.remove(it.key()); + it = itemForTouchPointId.erase(it); + } else { + ++it; + } + } + } + + // Disable selectionChanged() for individual items + ++selectionChanging; + int oldSelectedItemsSize = selectedItems.size(); + + // Update selected & hovered item bookkeeping + selectedItems.remove(item); + hoverItems.removeAll(item); + cachedItemsUnderMouse.removeAll(item); + if (item->d_ptr->pendingPolish) { + const int unpolishedIndex = unpolishedItems.indexOf(item); + if (unpolishedIndex != -1) + unpolishedItems[unpolishedIndex] = 0; + item->d_ptr->pendingPolish = false; + } + resetDirtyItem(item); + + //We remove all references of item from the sceneEventFilter arrays + QMultiMap::iterator iterator = sceneEventFilters.begin(); + while (iterator != sceneEventFilters.end()) { + if (iterator.value() == item || iterator.key() == item) + iterator = sceneEventFilters.erase(iterator); + else + ++iterator; + } + + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + leaveModal(item); + + // Reset the mouse grabber and focus item data. + if (mouseGrabberItems.contains(item)) + ungrabMouse(item, /* item is dying */ item->d_ptr->inDestructor); + + // Reset the keyboard grabber + if (keyboardGrabberItems.contains(item)) + ungrabKeyboard(item, /* item is dying */ item->d_ptr->inDestructor); + + // Reset the last mouse grabber item + if (item == lastMouseGrabberItem) + lastMouseGrabberItem = 0; + + // Reset the current drop item + if (item == dragDropItem) + dragDropItem = 0; + + // Reenable selectionChanged() for individual items + --selectionChanging; + if (!selectionChanging && selectedItems.size() != oldSelectedItemsSize) + emit q->selectionChanged(); + +#ifndef QT_NO_GESTURES + QHash::iterator it; + for (it = gestureTargets.begin(); it != gestureTargets.end();) { + if (it.value() == item) + it = gestureTargets.erase(it); + else + ++it; + } + + QGraphicsObject *dummy = static_cast(item); + cachedTargetItems.removeOne(dummy); + cachedItemGestures.remove(dummy); + cachedAlreadyDeliveredGestures.remove(dummy); + + foreach (Qt::GestureType gesture, item->d_ptr->gestureContext.keys()) + ungrabGesture(item, gesture); +#endif // QT_NO_GESTURES +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent) +{ + Q_Q(QGraphicsScene); + if (item && item->scene() != q) { + qWarning("QGraphicsScene::setActivePanel: item %p must be part of this scene", + item); + return; + } + + // Ensure the scene has focus when we change panel activation. + q->setFocus(Qt::ActiveWindowFocusReason); + + // Find the item's panel. + QGraphicsItem *panel = item ? item->panel() : 0; + lastActivePanel = panel ? activePanel : 0; + if (panel == activePanel || (!q->isActive() && !duringActivationEvent)) + return; + + // Deactivate the last active panel. + if (activePanel) { + if (QGraphicsItem *fi = activePanel->focusItem()) { + // Remove focus from the current focus item. + if (fi == q->focusItem()) + q->setFocusItem(0, Qt::ActiveWindowFocusReason); + } + + QEvent event(QEvent::WindowDeactivate); + q->sendEvent(activePanel, &event); + } else if (panel && !duringActivationEvent) { + // Deactivate the scene if changing activation to a panel. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } + + // Update activate state. + activePanel = panel; + QEvent event(QEvent::ActivationChange); + QApplication::sendEvent(q, &event); + + // Activate + if (panel) { + QEvent event(QEvent::WindowActivate); + q->sendEvent(panel, &event); + + // Set focus on the panel's focus item. + if (QGraphicsItem *focusItem = panel->focusItem()) + focusItem->setFocus(Qt::ActiveWindowFocusReason); + } else if (q->isActive()) { + // Activate the scene + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, + Qt::FocusReason focusReason) +{ + Q_Q(QGraphicsScene); + if (item == focusItem) + return; + + // Clear focus if asked to set focus on something that can't + // accept input focus. + if (item && (!(item->flags() & QGraphicsItem::ItemIsFocusable) + || !item->isVisible() || !item->isEnabled())) { + item = 0; + } + + // Set focus on the scene if an item requests focus. + if (item) { + q->setFocus(focusReason); + if (item == focusItem) + return; + } + + if (focusItem) { + lastFocusItem = focusItem; + +#ifndef QT_NO_IM + if (lastFocusItem + && (lastFocusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + // Close any external input method panel. This happens + // automatically by removing WA_InputMethodEnabled on + // the views, but if we are changing focus, we have to + // do it ourselves. + for (int i = 0; i < views.size(); ++i) + if (views.at(i)->inputContext()) + views.at(i)->inputContext()->reset(); + } + + focusItem = 0; + QFocusEvent event(QEvent::FocusOut, focusReason); + sendEvent(lastFocusItem, &event); +#endif //QT_NO_IM + } + + // This handles the case that the item has been removed from the + // scene in response to the FocusOut event. + if (item && item->scene() != q) + item = 0; + + if (item) + focusItem = item; + updateInputMethodSensitivityInViews(); + if (item) { + QFocusEvent event(QEvent::FocusIn, focusReason); + sendEvent(item, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::addPopup(QGraphicsWidget *widget) +{ + Q_ASSERT(widget); + Q_ASSERT(!popupWidgets.contains(widget)); + popupWidgets << widget; + if (QGraphicsWidget *focusWidget = widget->focusWidget()) { + focusWidget->setFocus(Qt::PopupFocusReason); + } else { + grabKeyboard((QGraphicsItem *)widget); + if (focusItem && popupWidgets.size() == 1) { + QFocusEvent event(QEvent::FocusOut, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } + } + grabMouse((QGraphicsItem *)widget); +} + +/*! + \internal + + Remove \a widget from the popup list. Important notes: + + \a widget is guaranteed to be in the list of popups, but it might not be + the last entry; you can hide any item in the pop list before the others, + and this must cause all later mouse grabbers to lose the grab. +*/ +void QGraphicsScenePrivate::removePopup(QGraphicsWidget *widget, bool itemIsDying) +{ + Q_ASSERT(widget); + int index = popupWidgets.indexOf(widget); + Q_ASSERT(index != -1); + + for (int i = popupWidgets.size() - 1; i >= index; --i) { + QGraphicsWidget *widget = popupWidgets.takeLast(); + ungrabMouse(widget, itemIsDying); + if (focusItem && popupWidgets.isEmpty()) { + QFocusEvent event(QEvent::FocusIn, Qt::PopupFocusReason); + sendEvent(focusItem, &event); + } else if (keyboardGrabberItems.contains(static_cast(widget))) { + ungrabKeyboard(static_cast(widget), itemIsDying); + } + if (!itemIsDying && widget->isVisible()) { + widget->QGraphicsItem::d_ptr->setVisibleHelper(false, /* explicit = */ false); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabMouse(QGraphicsItem *item, bool implicit) +{ + // Append to list of mouse grabber items, and send a mouse grab event. + if (mouseGrabberItems.contains(item)) { + if (mouseGrabberItems.last() == item) { + Q_ASSERT(!implicit); + if (!lastMouseGrabberItemHasImplicitMouseGrab) { + qWarning("QGraphicsItem::grabMouse: already a mouse grabber"); + } else { + // Upgrade to an explicit mouse grab + lastMouseGrabberItemHasImplicitMouseGrab = false; + } + } else { + qWarning("QGraphicsItem::grabMouse: already blocked by mouse grabber: %p", + mouseGrabberItems.last()); + } + return; + } + + // Send ungrab event to the last grabber. + if (!mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + if (lastMouseGrabberItemHasImplicitMouseGrab) { + // Implicit mouse grab is immediately lost. + last->ungrabMouse(); + } else { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabMouse); + sendEvent(last, &ungrabEvent); + } + } + + mouseGrabberItems << item; + lastMouseGrabberItemHasImplicitMouseGrab = implicit; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabMouse); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabMouse(QGraphicsItem *item, bool itemIsDying) +{ + int index = mouseGrabberItems.indexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabMouse: not a mouse grabber"); + return; + } + + if (item != mouseGrabberItems.last()) { + // Recursively ungrab the next mouse grabber until we reach this item + // to ensure state consistency. + ungrabMouse(mouseGrabberItems.at(index + 1), itemIsDying); + } + if (!popupWidgets.isEmpty() && item == popupWidgets.last()) { + // If the item is a popup, go via removePopup to ensure state + // consistency and that it gets hidden correctly - beware that + // removePopup() reenters this function to continue removing the grab. + removePopup((QGraphicsWidget *)item, itemIsDying); + return; + } + + // Send notification about mouse ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabMouse); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. Whenever this happens, we + // reset the implicitGrab (there can be only ever be one implicit grabber + // in a scene, and it is always the latest grabber; if the implicit grab + // is lost, it is not automatically regained. + mouseGrabberItems.takeLast(); + lastMouseGrabberItemHasImplicitMouseGrab = false; + + // Send notification about mouse regrab. ### It's unfortunate that all the + // items get a GrabMouse event, but this is a rare case with a simple + // implementation and it does ensure a consistent state. + if (!itemIsDying && !mouseGrabberItems.isEmpty()) { + QGraphicsItem *last = mouseGrabberItems.last(); + QEvent event(QEvent::GrabMouse); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearMouseGrabber() +{ + if (!mouseGrabberItems.isEmpty()) + mouseGrabberItems.first()->ungrabMouse(); + lastMouseGrabberItem = 0; +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::grabKeyboard(QGraphicsItem *item) +{ + if (keyboardGrabberItems.contains(item)) { + if (keyboardGrabberItems.last() == item) + qWarning("QGraphicsItem::grabKeyboard: already a keyboard grabber"); + else + qWarning("QGraphicsItem::grabKeyboard: already blocked by keyboard grabber: %p", + keyboardGrabberItems.last()); + return; + } + + // Send ungrab event to the last grabber. + if (!keyboardGrabberItems.isEmpty()) { + // Just send ungrab event to current grabber. + QEvent ungrabEvent(QEvent::UngrabKeyboard); + sendEvent(keyboardGrabberItems.last(), &ungrabEvent); + } + + keyboardGrabberItems << item; + + // Send grab event to current grabber. + QEvent grabEvent(QEvent::GrabKeyboard); + sendEvent(item, &grabEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::ungrabKeyboard(QGraphicsItem *item, bool itemIsDying) +{ + int index = keyboardGrabberItems.lastIndexOf(item); + if (index == -1) { + qWarning("QGraphicsItem::ungrabKeyboard: not a keyboard grabber"); + return; + } + if (item != keyboardGrabberItems.last()) { + // Recursively ungrab the topmost keyboard grabber until we reach this + // item to ensure state consistency. + ungrabKeyboard(keyboardGrabberItems.at(index + 1), itemIsDying); + } + + // Send notification about keyboard ungrab. + if (!itemIsDying) { + QEvent event(QEvent::UngrabKeyboard); + sendEvent(item, &event); + } + + // Remove the item from the list of grabbers. + keyboardGrabberItems.takeLast(); + + // Send notification about mouse regrab. + if (!itemIsDying && !keyboardGrabberItems.isEmpty()) { + QGraphicsItem *last = keyboardGrabberItems.last(); + QEvent event(QEvent::GrabKeyboard); + sendEvent(last, &event); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::clearKeyboardGrabber() +{ + if (!keyboardGrabberItems.isEmpty()) + ungrabKeyboard(keyboardGrabberItems.first()); +} + +void QGraphicsScenePrivate::enableMouseTrackingOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setMouseTracking(true); +} + +/*! + Returns all items for the screen position in \a event. +*/ +QList QGraphicsScenePrivate::itemsAtPosition(const QPoint &/*screenPos*/, + const QPointF &scenePos, + QWidget *widget) const +{ + Q_Q(const QGraphicsScene); + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + if (!view) + return q->items(scenePos, Qt::IntersectsItemShape, Qt::DescendingOrder, QTransform()); + + const QRectF pointRect(scenePos, QSizeF(1, 1)); + if (!view->isTransformed()) + return q->items(pointRect, Qt::IntersectsItemShape, Qt::DescendingOrder); + + const QTransform viewTransform = view->viewportTransform(); + return q->items(pointRect, Qt::IntersectsItemShape, + Qt::DescendingOrder, viewTransform); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::storeMouseButtonsForMouseGrabber(QGraphicsSceneMouseEvent *event) +{ + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + mouseGrabberButtonDownPos.insert(Qt::MouseButton(i), + mouseGrabberItems.last()->d_ptr->genericMapFromScene(event->scenePos(), + event->widget())); + mouseGrabberButtonDownScenePos.insert(Qt::MouseButton(i), event->scenePos()); + mouseGrabberButtonDownScreenPos.insert(Qt::MouseButton(i), event->screenPos()); + } + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::installSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + sceneEventFilters.insert(watched, filter); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::removeSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter) +{ + if (!sceneEventFilters.contains(watched)) + return; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(watched); + QMultiMap::Iterator end = sceneEventFilters.upperBound(watched); + do { + if (it.value() == filter) + it = sceneEventFilters.erase(it); + else + ++it; + } while (it != end); +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterDescendantEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) { + QGraphicsItem *parent = item->parentItem(); + while (parent) { + if (parent->d_ptr->filtersDescendantEvents && parent->sceneEventFilter(item, event)) + return true; + if (!(parent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorFiltersChildEvents)) + return false; + parent = parent->parentItem(); + } + } + return false; +} + +/*! + \internal +*/ +bool QGraphicsScenePrivate::filterEvent(QGraphicsItem *item, QEvent *event) +{ + if (item && !sceneEventFilters.contains(item)) + return false; + + QMultiMap::Iterator it = sceneEventFilters.lowerBound(item); + QMultiMap::Iterator end = sceneEventFilters.upperBound(item); + while (it != end) { + // ### The filterer and filteree might both be deleted. + if (it.value()->sceneEventFilter(it.key(), event)) + return true; + ++it; + } + return false; +} + +/*! + \internal + + This is the final dispatch point for any events from the scene to the + item. It filters the event first - if the filter returns true, the event + is considered to have been eaten by the filter, and is therefore stopped + (the default filter returns false). Then/otherwise, if the item is + enabled, the event is sent; otherwise it is stopped. +*/ +bool QGraphicsScenePrivate::sendEvent(QGraphicsItem *item, QEvent *event) +{ + if (QGraphicsObject *object = item->toGraphicsObject()) { +#ifndef QT_NO_GESTURES + QGestureManager *gestureManager = QApplicationPrivate::instance()->gestureManager; + if (gestureManager) { + if (gestureManager->filterEvent(object, event)) + return true; + } +#endif // QT_NO_GESTURES + } + + if (filterEvent(item, event)) + return false; + if (filterDescendantEvent(item, event)) + return false; + if (!item || !item->isEnabled()) + return false; + if (QGraphicsObject *o = item->toGraphicsObject()) { + bool spont = event->spontaneous(); + if (spont ? qt_sendSpontaneousEvent(o, event) : QApplication::sendEvent(o, event)) + return true; + event->spont = spont; + } + return item->sceneEvent(event); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::cloneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QGraphicsSceneDragDropEvent *source) +{ + dest->setWidget(source->widget()); + dest->setPos(source->pos()); + dest->setScenePos(source->scenePos()); + dest->setScreenPos(source->screenPos()); + dest->setButtons(source->buttons()); + dest->setModifiers(source->modifiers()); + dest->setPossibleActions(source->possibleActions()); + dest->setProposedAction(source->proposedAction()); + dest->setDropAction(source->dropAction()); + dest->setSource(source->source()); + dest->setMimeData(source->mimeData()); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendDragDropEvent(QGraphicsItem *item, + QGraphicsSceneDragDropEvent *dragDropEvent) +{ + dragDropEvent->setPos(item->d_ptr->genericMapFromScene(dragDropEvent->scenePos(), dragDropEvent->widget())); + sendEvent(item, dragDropEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendHoverEvent(QEvent::Type type, QGraphicsItem *item, + QGraphicsSceneHoverEvent *hoverEvent) +{ + QGraphicsSceneHoverEvent event(type); + event.setWidget(hoverEvent->widget()); + event.setPos(item->d_ptr->genericMapFromScene(hoverEvent->scenePos(), hoverEvent->widget())); + event.setScenePos(hoverEvent->scenePos()); + event.setScreenPos(hoverEvent->screenPos()); + event.setLastPos(item->d_ptr->genericMapFromScene(hoverEvent->lastScenePos(), hoverEvent->widget())); + event.setLastScenePos(hoverEvent->lastScenePos()); + event.setLastScreenPos(hoverEvent->lastScreenPos()); + event.setModifiers(hoverEvent->modifiers()); + sendEvent(item, &event); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + if (mouseEvent->button() == 0 && mouseEvent->buttons() == 0 && lastMouseGrabberItemHasImplicitMouseGrab) { + // ### This is a temporary fix for until we get proper mouse + // grab events. + clearMouseGrabber(); + return; + } + + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + return; + + for (int i = 0x1; i <= 0x10; i <<= 1) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget()))); + mouseEvent->setButtonDownScenePos(button, mouseGrabberButtonDownScenePos.value(button, mouseEvent->scenePos())); + mouseEvent->setButtonDownScreenPos(button, mouseGrabberButtonDownScreenPos.value(button, mouseEvent->screenPos())); + } + mouseEvent->setPos(item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget())); + mouseEvent->setLastPos(item->d_ptr->genericMapFromScene(mouseEvent->lastScenePos(), mouseEvent->widget())); + sendEvent(item, mouseEvent); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_Q(QGraphicsScene); + + // Ignore by default, unless we find a mouse grabber that accepts it. + mouseEvent->ignore(); + + // Deliver to any existing mouse grabber. + if (!mouseGrabberItems.isEmpty()) { + if (mouseGrabberItems.last()->isBlockedByModalPanel()) + return; + // The event is ignored by default, but we disregard the event's + // accepted state after delivery; the mouse is grabbed, after all. + sendMouseEvent(mouseEvent); + return; + } + + // Start by determining the number of items at the current position. + // Reuse value from earlier calculations if possible. + if (cachedItemsUnderMouse.isEmpty()) { + cachedItemsUnderMouse = itemsAtPosition(mouseEvent->screenPos(), + mouseEvent->scenePos(), + mouseEvent->widget()); + } + + // Update window activation. + QGraphicsItem *topItem = cachedItemsUnderMouse.value(0); + QGraphicsWidget *newActiveWindow = topItem ? topItem->window() : 0; + if (newActiveWindow && newActiveWindow->isBlockedByModalPanel(&topItem)) { + // pass activation to the blocking modal window + newActiveWindow = topItem ? topItem->window() : 0; + } + + if (newActiveWindow != q->activeWindow()) + q->setActiveWindow(newActiveWindow); + + // Set focus on the topmost enabled item that can take focus. + bool setFocus = false; + + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (item->isBlockedByModalPanel() + || (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling)) { + // Make sure we don't clear focus. + setFocus = true; + break; + } + if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable))) { + if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) { + setFocus = true; + if (item != q->focusItem() && item->d_ptr->mouseSetsFocus) + q->setFocusItem(item, Qt::MouseFocusReason); + break; + } + } + if (item->isPanel()) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation) + break; + } + + // Check for scene modality. + bool sceneModality = false; + for (int i = 0; i < modalPanels.size(); ++i) { + if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) { + sceneModality = true; + break; + } + } + + // If nobody could take focus, clear it. + if (!stickyFocus && !setFocus && !sceneModality) + q->setFocusItem(0, Qt::MouseFocusReason); + + // Any item will do. + if (sceneModality && cachedItemsUnderMouse.isEmpty()) + cachedItemsUnderMouse << modalPanels.first(); + + // Find a mouse grabber by sending mouse press events to all mouse grabber + // candidates one at a time, until the event is accepted. It's accepted by + // default, so the receiver has to explicitly ignore it for it to pass + // through. + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (!(item->acceptedMouseButtons() & mouseEvent->button())) { + // Skip items that don't accept the event's mouse button. + continue; + } + + // Check if this item is blocked by a modal panel and deliver the mouse event to the + // blocking panel instead of this item if blocked. + (void) item->isBlockedByModalPanel(&item); + + grabMouse(item, /* implicit = */ true); + mouseEvent->accept(); + + // check if the item we are sending to are disabled (before we send the event) + bool disabled = !item->isEnabled(); + bool isPanel = item->isPanel(); + if (mouseEvent->type() == QEvent::GraphicsSceneMouseDoubleClick + && item != lastMouseGrabberItem && lastMouseGrabberItem) { + // If this item is different from the item that received the last + // mouse event, and mouseEvent is a doubleclick event, then the + // event is converted to a press. Known limitation: + // Triple-clicking will not generate a doubleclick, though. + QGraphicsSceneMouseEvent mousePress(QEvent::GraphicsSceneMousePress); + mousePress.spont = mouseEvent->spont; + mousePress.accept(); + mousePress.setButton(mouseEvent->button()); + mousePress.setButtons(mouseEvent->buttons()); + mousePress.setScreenPos(mouseEvent->screenPos()); + mousePress.setScenePos(mouseEvent->scenePos()); + mousePress.setModifiers(mouseEvent->modifiers()); + mousePress.setWidget(mouseEvent->widget()); + mousePress.setButtonDownPos(mouseEvent->button(), + mouseEvent->buttonDownPos(mouseEvent->button())); + mousePress.setButtonDownScenePos(mouseEvent->button(), + mouseEvent->buttonDownScenePos(mouseEvent->button())); + mousePress.setButtonDownScreenPos(mouseEvent->button(), + mouseEvent->buttonDownScreenPos(mouseEvent->button())); + sendMouseEvent(&mousePress); + mouseEvent->setAccepted(mousePress.isAccepted()); + } else { + sendMouseEvent(mouseEvent); + } + + bool dontSendUngrabEvents = mouseGrabberItems.isEmpty() || mouseGrabberItems.last() != item; + if (disabled) { + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + break; + } + if (mouseEvent->isAccepted()) { + if (!mouseGrabberItems.isEmpty()) + storeMouseButtonsForMouseGrabber(mouseEvent); + lastMouseGrabberItem = item; + return; + } + ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents); + + // Don't propagate through panels. + if (isPanel) + break; + } + + // Is the event still ignored? Then the mouse press goes to the scene. + // Reset the mouse grabber, clear the selection, clear focus, and leave + // the event ignored so that it can propagate through the originating + // view. + if (!mouseEvent->isAccepted()) { + clearMouseGrabber(); + + QGraphicsView *view = mouseEvent->widget() ? qobject_cast(mouseEvent->widget()->parentWidget()) : 0; + bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag; + if (!dontClearSelection) { + // Clear the selection if the originating view isn't in scroll + // hand drag mode. The view will clear the selection if no drag + // happened. + q->clearSelection(); + } + } +} + +/*! + \internal + + Ensures that the list of toplevels is sorted by insertion order, and that + the siblingIndexes are packed (no gaps), and start at 0. + + ### This function is almost identical to + QGraphicsItemPrivate::ensureSequentialSiblingIndex(). +*/ +void QGraphicsScenePrivate::ensureSequentialTopLevelSiblingIndexes() +{ + if (!topLevelSequentialOrdering) { + qSort(topLevelItems.begin(), topLevelItems.end(), QGraphicsItemPrivate::insertionOrder); + topLevelSequentialOrdering = true; + needSortTopLevelItems = 1; + } + if (holesInTopLevelSiblingIndex) { + holesInTopLevelSiblingIndex = 0; + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems[i]->d_ptr->siblingIndex = i; + } +} + +/*! + \internal + + Set the font and propagate the changes if the font is different from the + current font. +*/ +void QGraphicsScenePrivate::setFont_helper(const QFont &font) +{ + if (this->font == font && this->font.resolve() == font.resolve()) + return; + updateFont(font); +} + +/*! + \internal + + Resolve the scene's font against the application font, and propagate the + changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolveFont() +{ + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + updateFont(resolvedFont); +} + +/*! + \internal + + Update the font, and whether or not it has changed, reresolve all fonts in + the scene. +*/ +void QGraphicsScenePrivate::updateFont(const QFont &font) +{ + Q_Q(QGraphicsScene); + + // Update local font setting. + this->font = font; + + // Resolve the fonts of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolveFont(font.resolve()); + } + } + + // Send the scene a FontChange event. + QEvent event(QEvent::FontChange); + QApplication::sendEvent(q, &event); +} + +/*! + \internal + + Set the palette and propagate the changes if the palette is different from + the current palette. +*/ +void QGraphicsScenePrivate::setPalette_helper(const QPalette &palette) +{ + if (this->palette == palette && this->palette.resolve() == palette.resolve()) + return; + updatePalette(palette); +} + +/*! + \internal + + Resolve the scene's palette against the application palette, and propagate + the changes too all items in the scene. +*/ +void QGraphicsScenePrivate::resolvePalette() +{ + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + updatePalette(resolvedPalette); +} + +/*! + \internal + + Update the palette, and whether or not it has changed, reresolve all + palettes in the scene. +*/ +void QGraphicsScenePrivate::updatePalette(const QPalette &palette) +{ + Q_Q(QGraphicsScene); + + // Update local palette setting. + this->palette = palette; + + // Resolve the palettes of all top-level widget items, or widget items + // whose parent is not a widget. + foreach (QGraphicsItem *item, q->items()) { + if (!item->parentItem()) { + // Resolvefont for an item is a noop operation, but + // every item can be a widget, or can have a widget + // childre. + item->d_ptr->resolvePalette(palette.resolve()); + } + } + + // Send the scene a PaletteChange event. + QEvent event(QEvent::PaletteChange); + QApplication::sendEvent(q, &event); +} + +/*! + Constructs a QGraphicsScene object. The \a parent parameter is + passed to QObject's constructor. +*/ +QGraphicsScene::QGraphicsScene(QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); +} + +/*! + Constructs a QGraphicsScene object, using \a sceneRect for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(const QRectF &sceneRect, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(sceneRect); +} + +/*! + Constructs a QGraphicsScene object, using the rectangle specified + by (\a x, \a y), and the given \a width and \a height for its + scene rectangle. The \a parent parameter is passed to QObject's + constructor. + + \sa sceneRect +*/ +QGraphicsScene::QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent) + : QObject(*new QGraphicsScenePrivate, parent) +{ + d_func()->init(); + setSceneRect(x, y, width, height); +} + +/*! + Removes and deletes all items from the scene object + before destroying the scene object. The scene object + is removed from the application's global scene list, + and it is removed from all associated views. +*/ +QGraphicsScene::~QGraphicsScene() +{ + Q_D(QGraphicsScene); + + // Remove this scene from qApp's global scene list. + qApp->d_func()->scene_list.removeAll(this); + + clear(); + + // Remove this scene from all associated views. + for (int j = 0; j < d->views.size(); ++j) + d->views.at(j)->setScene(0); +} + +/*! + \property QGraphicsScene::sceneRect + \brief the scene rectangle; the bounding rectangle of the scene + + The scene rectangle defines the extent of the scene. It is + primarily used by QGraphicsView to determine the view's default + scrollable area, and by QGraphicsScene to manage item indexing. + + If unset, or if set to a null QRectF, sceneRect() will return the largest + bounding rect of all items on the scene since the scene was created (i.e., + a rectangle that grows when items are added to or moved in the scene, but + never shrinks). + + \sa width(), height(), QGraphicsView::sceneRect +*/ +QRectF QGraphicsScene::sceneRect() const +{ + Q_D(const QGraphicsScene); + if (d->hasSceneRect) + return d->sceneRect; + + if (d->dirtyGrowingItemsBoundingRect) { + // Lazily update the growing items bounding rect + QGraphicsScenePrivate *thatd = const_cast(d); + QRectF oldGrowingBoundingRect = thatd->growingItemsBoundingRect; + thatd->growingItemsBoundingRect |= itemsBoundingRect(); + thatd->dirtyGrowingItemsBoundingRect = false; + if (oldGrowingBoundingRect != thatd->growingItemsBoundingRect) + emit const_cast(this)->sceneRectChanged(thatd->growingItemsBoundingRect); + } + return d->growingItemsBoundingRect; +} +void QGraphicsScene::setSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsScene); + if (rect != d->sceneRect) { + d->hasSceneRect = !rect.isNull(); + d->sceneRect = rect; + emit sceneRectChanged(d->hasSceneRect ? rect : d->growingItemsBoundingRect); + } +} + +/*! + \fn qreal QGraphicsScene::width() const + + This convenience function is equivalent to calling sceneRect().width(). + + \sa height() +*/ + +/*! + \fn qreal QGraphicsScene::height() const + + This convenience function is equivalent to calling \c sceneRect().height(). + + \sa width() +*/ + +/*! + Renders the \a source rect from scene into \a target, using \a painter. This + function is useful for capturing the contents of the scene onto a paint + device, such as a QImage (e.g., to take a screenshot), or for printing + with QPrinter. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 1 + + If \a source is a null rect, this function will use sceneRect() to + determine what to render. If \a target is a null rect, the dimensions of \a + painter's paint device will be used. + + The source rect contents will be transformed according to \a + aspectRatioMode to fit into the target rect. By default, the aspect ratio + is kept, and \a source is scaled to fit in \a target. + + \sa QGraphicsView::render() +*/ +void QGraphicsScene::render(QPainter *painter, const QRectF &target, const QRectF &source, + Qt::AspectRatioMode aspectRatioMode) +{ + // ### Switch to using the recursive rendering algorithm instead. + + // Default source rect = scene rect + QRectF sourceRect = source; + if (sourceRect.isNull()) + sourceRect = sceneRect(); + + // Default target rect = device rect + QRectF targetRect = target; + if (targetRect.isNull()) { + if (painter->device()->devType() == QInternal::Picture) + targetRect = sourceRect; + else + targetRect.setRect(0, 0, painter->device()->width(), painter->device()->height()); + } + + // Find the ideal x / y scaling ratio to fit \a source into \a target. + qreal xratio = targetRect.width() / sourceRect.width(); + qreal yratio = targetRect.height() / sourceRect.height(); + + // Scale according to the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Find all items to draw, and reverse the list (we want to draw + // in reverse order). + QList itemList = items(sourceRect, Qt::IntersectsItemBoundingRect); + QGraphicsItem **itemArray = new QGraphicsItem *[itemList.size()]; + int numItems = itemList.size(); + for (int i = 0; i < numItems; ++i) + itemArray[numItems - i - 1] = itemList.at(i); + itemList.clear(); + + painter->save(); + + // Transform the painter. + painter->setClipRect(targetRect, Qt::IntersectClip); + QTransform painterTransform; + painterTransform *= QTransform() + .translate(targetRect.left(), targetRect.top()) + .scale(xratio, yratio) + .translate(-sourceRect.left(), -sourceRect.top()); + painter->setWorldTransform(painterTransform, true); + + // Two unit vectors. + QLineF v1(0, 0, 1, 0); + QLineF v2(0, 0, 0, 1); + + // Generate the style options + QStyleOptionGraphicsItem *styleOptionArray = new QStyleOptionGraphicsItem[numItems]; + for (int i = 0; i < numItems; ++i) + itemArray[i]->d_ptr->initStyleOption(&styleOptionArray[i], painterTransform, targetRect.toRect()); + + // Render the scene. + drawBackground(painter, sourceRect); + drawItems(painter, numItems, itemArray, styleOptionArray); + drawForeground(painter, sourceRect); + + delete [] itemArray; + delete [] styleOptionArray; + + painter->restore(); +} + +/*! + \property QGraphicsScene::itemIndexMethod + \brief the item indexing method. + + QGraphicsScene applies an indexing algorithm to the scene, to speed up + item discovery functions like items() and itemAt(). Indexing is most + efficient for static scenes (i.e., where items don't move around). For + dynamic scenes, or scenes with many animated items, the index bookkeeping + can outweight the fast lookup speeds. + + For the common case, the default index method BspTreeIndex works fine. If + your scene uses many animations and you are experiencing slowness, you can + disable indexing by calling \c setItemIndexMethod(NoIndex). + + \sa bspTreeDepth +*/ +QGraphicsScene::ItemIndexMethod QGraphicsScene::itemIndexMethod() const +{ + Q_D(const QGraphicsScene); + return d->indexMethod; +} +void QGraphicsScene::setItemIndexMethod(ItemIndexMethod method) +{ + Q_D(QGraphicsScene); + if (d->indexMethod == method) + return; + + d->indexMethod = method; + + QList oldItems = d->index->items(Qt::DescendingOrder); + delete d->index; + if (method == BspTreeIndex) + d->index = new QGraphicsSceneBspTreeIndex(this); + else + d->index = new QGraphicsSceneLinearIndex(this); + for (int i = oldItems.size() - 1; i >= 0; --i) + d->index->addItem(oldItems.at(i)); +} + +/*! + \property QGraphicsScene::bspTreeDepth + \brief the depth of QGraphicsScene's BSP index tree + \since 4.3 + + This property has no effect when NoIndex is used. + + This value determines the depth of QGraphicsScene's BSP tree. The depth + directly affects QGraphicsScene's performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, QGraphicsScene can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as QGraphicsScene retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 2 + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + + \sa itemIndexMethod +*/ +int QGraphicsScene::bspTreeDepth() const +{ + Q_D(const QGraphicsScene); + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + return bspTree ? bspTree->bspTreeDepth() : 0; +} +void QGraphicsScene::setBspTreeDepth(int depth) +{ + Q_D(QGraphicsScene); + if (depth < 0) { + qWarning("QGraphicsScene::setBspTreeDepth: invalid depth %d ignored; must be >= 0", depth); + return; + } + + QGraphicsSceneBspTreeIndex *bspTree = qobject_cast(d->index); + if (!bspTree) { + qWarning("QGraphicsScene::setBspTreeDepth: can not apply if indexing method is not BSP"); + return; + } + bspTree->setBspTreeDepth(depth); +} + +/*! + \property QGraphicsScene::sortCacheEnabled + \brief whether sort caching is enabled + \since 4.5 + \obsolete + + Since Qt 4.6, this property has no effect. +*/ +bool QGraphicsScene::isSortCacheEnabled() const +{ + Q_D(const QGraphicsScene); + return d->sortCacheEnabled; +} +void QGraphicsScene::setSortCacheEnabled(bool enabled) +{ + Q_D(QGraphicsScene); + if (d->sortCacheEnabled == enabled) + return; + d->sortCacheEnabled = enabled; +} + +/*! + Calculates and returns the bounding rect of all items on the scene. This + function works by iterating over all items, and because if this, it can + be slow for large scenes. + + \sa sceneRect() +*/ +QRectF QGraphicsScene::itemsBoundingRect() const +{ + // Does not take untransformable items into account. + QRectF boundingRect; + foreach (QGraphicsItem *item, items()) + boundingRect |= item->sceneBoundingRect(); + return boundingRect; +} + +/*! + Returns a list of all items in the scene in descending stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items() const +{ + Q_D(const QGraphicsScene); + return d->index->items(Qt::DescendingOrder); +} + +/*! + Returns an ordered list of all items on the scene. \a order decides the + stacking order. + + \sa addItem(), removeItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(Qt::SortOrder order) const +{ + Q_D(const QGraphicsScene); + return d->index->items(order); +} + +/*! + \obsolete + + Returns all visible items at position \a pos in the scene. The items are + listed in descending stacking order (i.e., the first item in the list is the + top-most item, and the last item is the bottom-most item). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder); +} + +/*! + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a rectangle. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rectangle are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rectangle, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rectangle, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode) const + \obsolete + \since 4.3 + + This convenience function is equivalent to calling items(QRectF(\a x, \a y, \a w, \a h), \a mode). + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + \fn QList QGraphicsScene::items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the rectangle defined by \a x, \a y, + \a w and \a h, in a list sorted using \a order. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. +*/ + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the polygon \a polygon. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const + \overload + \obsolete + + Returns all visible items that, depending on \a path, are either inside or + intersect with the path \a path. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, Qt::DescendingOrder); +} + +/*! + \fn QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are at + the specified \a pos in a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with \a pos are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(pos, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a rect and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(rect, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a polygon and return + a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(polygon, mode, order, deviceTransform); +} + +/*! + \fn QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform) const + \overload + \since 4.6 + + \brief Returns all visible items that, depending on \a mode, are + either inside or intersect with the specified \a path and return a + list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa itemAt(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsScene); + return d->index->items(path, mode, order, deviceTransform); +} + +/*! + Returns a list of all items that collide with \a item. Collisions are + determined by calling QGraphicsItem::collidesWithItem(); the collision + detection is determined by \a mode. By default, all items whose shape + intersects \a item or is contained inside \a item's shape are returned. + + The items are returned in descending stacking order (i.e., the first item + in the list is the uppermost item, and the last item is the lowermost + item). + + \sa items(), itemAt(), QGraphicsItem::collidesWithItem(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsScene::collidingItems(const QGraphicsItem *item, + Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::collidingItems: cannot find collisions for null item"); + return QList(); + } + + // Does not support ItemIgnoresTransformations. + QList tmp; + foreach (QGraphicsItem *itemInVicinity, d->index->estimateItems(item->sceneBoundingRect(), Qt::DescendingOrder)) { + if (item != itemInVicinity && item->collidesWithItem(itemInVicinity, mode)) + tmp << itemInVicinity; + } + return tmp; +} + +/*! + \overload + \obsolete + + Returns the topmost visible item at the specified \a position, or 0 if + there are no items at this position. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position) const +{ + QList itemsAtPoint = items(position); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \since 4.6 + + Returns the topmost visible item at the specified \a position, or 0 + if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa items(), collidingItems(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position, const QTransform &deviceTransform) const +{ + QList itemsAtPoint = items(position, Qt::IntersectsItemShape, + Qt::DescendingOrder, deviceTransform); + return itemsAtPoint.isEmpty() ? 0 : itemsAtPoint.first(); +} + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y, const QTransform &deviceTransform) const + \overload + \since 4.6 + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y), deviceTransform)}. +*/ + +/*! + \fn QGraphicsScene::itemAt(qreal x, qreal y) const + \overload + \obsolete + + Returns the topmost item at the position specified by (\a x, \a + y), or 0 if there are no items at this position. + + This convenience function is equivalent to calling \c + {itemAt(QPointF(x, y))}. + + This function is deprecated and returns incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ + +/*! + Returns a list of all currently selected items. The items are + returned in no particular order. + + \sa setSelectionArea() +*/ +QList QGraphicsScene::selectedItems() const +{ + Q_D(const QGraphicsScene); + + // Optimization: Lazily removes items that are not selected. + QGraphicsScene *that = const_cast(this); + QSet actuallySelectedSet; + foreach (QGraphicsItem *item, that->d_func()->selectedItems) { + if (item->isSelected()) + actuallySelectedSet << item; + } + + that->d_func()->selectedItems = actuallySelectedSet; + + return d->selectedItems.values(); +} + +/*! + Returns the selection area that was previously set with + setSelectionArea(), or an empty QPainterPath if no selection area has been + set. + + \sa setSelectionArea() +*/ +QPainterPath QGraphicsScene::selectionArea() const +{ + Q_D(const QGraphicsScene); + return d->selectionArea; +} + +/*! + \since 4.6 + + Sets the selection area to \a path. All items within this area are + immediately selected, and all items outside are unselected. You can get + the list of all selected items by calling selectedItems(). + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + For an item to be selected, it must be marked as \e selectable + (QGraphicsItem::ItemIsSelectable). + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform) +{ + setSelectionArea(path, Qt::IntersectsItemShape, deviceTransform); +} + +/*! + \obsolete + \overload + + Sets the selection area to \a path. + + This function is deprecated and leads to incorrect results if the scene + contains items that ignore transformations. Use the overload that takes + a QTransform instead. +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path) +{ + setSelectionArea(path, Qt::IntersectsItemShape, QTransform()); +} + +/*! + \obsolete + \overload + \since 4.3 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode) +{ + setSelectionArea(path, mode, QTransform()); +} + +/*! + \overload + \since 4.6 + + Sets the selection area to \a path using \a mode to determine if items are + included in the selection area. + + \a deviceTransform is the transformation that applies to the view, and needs to + be provided if the scene contains items that ignore transformations. + + \sa clearSelection(), selectionArea() +*/ +void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) +{ + Q_D(QGraphicsScene); + + // Note: with boolean path operations, we can improve performance here + // quite a lot by "growing" the old path instead of replacing it. That + // allows us to only check the intersect area for changes, instead of + // reevaluating the whole path over again. + d->selectionArea = path; + + QSet unselectItems = d->selectedItems; + + // Disable emitting selectionChanged() for individual items. + ++d->selectionChanging; + bool changed = false; + + // Set all items in path to selected. + foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) { + if (item->flags() & QGraphicsItem::ItemIsSelectable) { + if (!item->isSelected()) + changed = true; + unselectItems.remove(item); + item->setSelected(true); + } + } + + // Unselect all items outside path. + foreach (QGraphicsItem *item, unselectItems) { + item->setSelected(false); + changed = true; + } + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + Clears the current selection. + + \sa setSelectionArea(), selectedItems() +*/ +void QGraphicsScene::clearSelection() +{ + Q_D(QGraphicsScene); + + // Disable emitting selectionChanged + ++d->selectionChanging; + bool changed = !d->selectedItems.isEmpty(); + + foreach (QGraphicsItem *item, d->selectedItems) + item->setSelected(false); + d->selectedItems.clear(); + + // Reenable emitting selectionChanged() for individual items. + --d->selectionChanging; + + if (!d->selectionChanging && changed) + emit selectionChanged(); +} + +/*! + \since 4.4 + + Removes and deletes all items from the scene, but otherwise leaves the + state of the scene unchanged. + + \sa addItem() +*/ +void QGraphicsScene::clear() +{ + Q_D(QGraphicsScene); + // NB! We have to clear the index before deleting items; otherwise the + // index might try to access dangling item pointers. + d->index->clear(); + // NB! QGraphicsScenePrivate::unregisterTopLevelItem() removes items + while (!d->topLevelItems.isEmpty()) + delete d->topLevelItems.first(); + Q_ASSERT(d->topLevelItems.isEmpty()); + d->lastItemCount = 0; + d->allItemsIgnoreHoverEvents = true; + d->allItemsUseDefaultCursor = true; + d->allItemsIgnoreTouchEvents = true; +} + +/*! + Groups all items in \a items into a new QGraphicsItemGroup, and returns a + pointer to the group. The group is created with the common ancestor of \a + items as its parent, and with position (0, 0). The items are all + reparented to the group, and their positions and transformations are + mapped to the group. If \a items is empty, this function will return an + empty top-level QGraphicsItemGroup. + + QGraphicsScene has ownership of the group item; you do not need to delete + it. To dismantle (ungroup) a group, call destroyItemGroup(). + + \sa destroyItemGroup(), QGraphicsItemGroup::addToGroup() +*/ +QGraphicsItemGroup *QGraphicsScene::createItemGroup(const QList &items) +{ + // Build a list of the first item's ancestors + QList ancestors; + int n = 0; + if (!items.isEmpty()) { + QGraphicsItem *parent = items.at(n++); + while ((parent = parent->parentItem())) + ancestors.append(parent); + } + + // Find the common ancestor for all items + QGraphicsItem *commonAncestor = 0; + if (!ancestors.isEmpty()) { + while (n < items.size()) { + int commonIndex = -1; + QGraphicsItem *parent = items.at(n++); + do { + int index = ancestors.indexOf(parent, qMax(0, commonIndex)); + if (index != -1) { + commonIndex = index; + break; + } + } while ((parent = parent->parentItem())); + + if (commonIndex == -1) { + commonAncestor = 0; + break; + } + + commonAncestor = ancestors.at(commonIndex); + } + } + + // Create a new group at that level + QGraphicsItemGroup *group = new QGraphicsItemGroup(commonAncestor); + if (!commonAncestor) + addItem(group); + foreach (QGraphicsItem *item, items) + group->addToGroup(item); + return group; +} + +/*! + Reparents all items in \a group to \a group's parent item, then removes \a + group from the scene, and finally deletes it. The items' positions and + transformations are mapped from the group to the group's parent. + + \sa createItemGroup(), QGraphicsItemGroup::removeFromGroup() +*/ +void QGraphicsScene::destroyItemGroup(QGraphicsItemGroup *group) +{ + foreach (QGraphicsItem *item, group->children()) + group->removeFromGroup(item); + removeItem(group); + delete group; +} + +/*! + Adds or moves the \a item and all its childen to this scene. + This scene takes ownership of the \a item. + + If the item is visible (i.e., QGraphicsItem::isVisible() returns + true), QGraphicsScene will emit changed() once control goes back + to the event loop. + + If the item is already in a different scene, it will first be + removed from its old scene, and then added to this scene as a + top-level. + + QGraphicsScene will send ItemSceneChange notifications to \a item + while it is added to the scene. If item does not currently belong + to a scene, only one notification is sent. If it does belong to + scene already (i.e., it is moved to this scene), QGraphicsScene + will send an addition notification as the item is removed from its + previous scene. + + If the item is a panel, the scene is active, and there is no + active panel in the scene, then the item will be activated. + + \sa removeItem(), addEllipse(), addLine(), addPath(), addPixmap(), + addRect(), addText(), addWidget(), {QGraphicsItem#Sorting}{Sorting} +*/ +void QGraphicsScene::addItem(QGraphicsItem *item) +{ + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::addItem: cannot add null item"); + return; + } + if (item->d_ptr->scene == this) { + qWarning("QGraphicsScene::addItem: item has already been added to this scene"); + return; + } + // Remove this item from its existing scene + if (QGraphicsScene *oldScene = item->d_ptr->scene) + oldScene->removeItem(item); + + // Notify the item that its scene is changing, and allow the item to + // react. + const QVariant newSceneVariant(item->itemChange(QGraphicsItem::ItemSceneChange, + QVariant::fromValue(this))); + QGraphicsScene *targetScene = qvariant_cast(newSceneVariant); + if (targetScene != this) { + if (targetScene && item->d_ptr->scene != targetScene) + targetScene->addItem(item); + return; + } + + // QDeclarativeItems do not rely on initial itemChanged message, as the componentComplete + // function allows far more opportunity for delayed-construction optimization. + if (!item->d_ptr->isDeclarativeItem) { + if (d->unpolishedItems.isEmpty()) { + QMetaMethod method = metaObject()->method(d->polishItemsIndex); + method.invoke(this, Qt::QueuedConnection); + } + d->unpolishedItems.append(item); + item->d_ptr->pendingPolish = true; + } + + // Detach this item from its parent if the parent's scene is different + // from this scene. + if (QGraphicsItem *itemParent = item->d_ptr->parent) { + if (itemParent->d_ptr->scene != this) + item->setParentItem(0); + } + + // Add the item to this scene + item->d_func()->scene = targetScene; + + // Add the item in the index + d->index->addItem(item); + + // Add to list of toplevels if this item is a toplevel. + if (!item->d_ptr->parent) + d->registerTopLevelItem(item); + + // Add to list of items that require an update. We cannot assume that the + // item is fully constructed, so calling item->update() can lead to a pure + // virtual function call to boundingRect(). + d->markDirty(item); + d->dirtyGrowingItemsBoundingRect = true; + + // Disable selectionChanged() for individual items + ++d->selectionChanging; + int oldSelectedItemSize = d->selectedItems.size(); + + // Enable mouse tracking if the item accepts hover events or has a cursor set. + if (d->allItemsIgnoreHoverEvents && d->itemAcceptsHoverEvents_helper(item)) { + d->allItemsIgnoreHoverEvents = false; + d->enableMouseTrackingOnViews(); + } +#ifndef QT_NO_CURSOR + if (d->allItemsUseDefaultCursor && item->d_ptr->hasCursor) { + d->allItemsUseDefaultCursor = false; + if (d->allItemsIgnoreHoverEvents) // already enabled otherwise + d->enableMouseTrackingOnViews(); + } +#endif //QT_NO_CURSOR + + // Enable touch events if the item accepts touch events. + if (d->allItemsIgnoreTouchEvents && item->d_ptr->acceptTouchEvents) { + d->allItemsIgnoreTouchEvents = false; + d->enableTouchEventsOnViews(); + } + +#ifndef QT_NO_GESTURES + foreach (Qt::GestureType gesture, item->d_ptr->gestureContext.keys()) + d->grabGesture(item, gesture); +#endif + + // Update selection lists + if (item->isSelected()) + d->selectedItems << item; + if (item->isWidget() && item->isVisible() && static_cast(item)->windowType() == Qt::Popup) + d->addPopup(static_cast(item)); + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + d->enterModal(item); + + // Update creation order focus chain. Make sure to leave the widget's + // internal tab order intact. + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!d->tabFocusFirst) { + // No first tab focus widget - make this the first tab focus + // widget. + d->tabFocusFirst = widget; + } else if (!widget->parentWidget()) { + // Adding a widget that is not part of a tab focus chain. + QGraphicsWidget *last = d->tabFocusFirst->d_func()->focusPrev; + QGraphicsWidget *lastNew = widget->d_func()->focusPrev; + last->d_func()->focusNext = widget; + widget->d_func()->focusPrev = last; + d->tabFocusFirst->d_func()->focusPrev = lastNew; + lastNew->d_func()->focusNext = d->tabFocusFirst; + } + } + + // Add all children recursively + item->d_ptr->ensureSortedChildren(); + for (int i = 0; i < item->d_ptr->children.size(); ++i) + addItem(item->d_ptr->children.at(i)); + + // Resolve font and palette. + item->d_ptr->resolveFont(d->font.resolve()); + item->d_ptr->resolvePalette(d->palette.resolve()); + + + // Reenable selectionChanged() for individual items + --d->selectionChanging; + if (!d->selectionChanging && d->selectedItems.size() != oldSelectedItemSize) + emit selectionChanged(); + + // Deliver post-change notification + item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + + // Update explicit activation + bool autoActivate = true; + if (!d->childExplicitActivation && item->d_ptr->explicitActivate) + d->childExplicitActivation = item->d_ptr->wantsActive ? 1 : 2; + if (d->childExplicitActivation && item->isPanel()) { + if (d->childExplicitActivation == 1) + setActivePanel(item); + else + autoActivate = false; + d->childExplicitActivation = 0; + } else if (!item->d_ptr->parent) { + d->childExplicitActivation = 0; + } + + // Auto-activate this item's panel if nothing else has been activated + if (autoActivate) { + if (!d->lastActivePanel && !d->activePanel && item->isPanel()) { + if (isActive()) + setActivePanel(item); + else + d->lastActivePanel = item; + } + } + + if (item->d_ptr->flags & QGraphicsItem::ItemSendsScenePositionChanges) + d->registerScenePosItem(item); + + // Ensure that newly added items that have subfocus set, gain + // focus automatically if there isn't a focus item already. + if (!d->focusItem && item != d->lastFocusItem && item->focusItem() == item) + item->focusItem()->setFocus(); + + d->updateInputMethodSensitivityInViews(); +} + +/*! + Creates and adds an ellipse item to the scene, and returns the item + pointer. The geometry of the ellipse is defined by \a rect, and its pen + and brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addLine(), addPath(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsEllipseItem *QGraphicsScene::addEllipse(const QRectF &rect, const QPen &pen, const QBrush &brush) +{ + QGraphicsEllipseItem *item = new QGraphicsEllipseItem(rect); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + \fn QGraphicsEllipseItem *QGraphicsScene::addEllipse(qreal x, qreal y, qreal w, qreal h, const QPen &pen, const QBrush &brush) + \since 4.3 + + This convenience function is equivalent to calling addEllipse(QRectF(\a x, + \a y, \a w, \a h), \a pen, \a brush). +*/ + +/*! + Creates and adds a line item to the scene, and returns the item + pointer. The geometry of the line is defined by \a line, and its pen + is initialized to \a pen. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addPath(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsLineItem *QGraphicsScene::addLine(const QLineF &line, const QPen &pen) +{ + QGraphicsLineItem *item = new QGraphicsLineItem(line); + item->setPen(pen); + addItem(item); + return item; +} + +/*! + \fn QGraphicsLineItem *QGraphicsScene::addLine(qreal x1, qreal y1, qreal x2, qreal y2, const QPen &pen) + \since 4.3 + + This convenience function is equivalent to calling addLine(QLineF(\a x1, + \a y1, \a x2, \a y2), \a pen). +*/ + +/*! + Creates and adds a path item to the scene, and returns the item + pointer. The geometry of the path is defined by \a path, and its pen and + brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPathItem *QGraphicsScene::addPath(const QPainterPath &path, const QPen &pen, const QBrush &brush) +{ + QGraphicsPathItem *item = new QGraphicsPathItem(path); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + Creates and adds a pixmap item to the scene, and returns the item + pointer. The pixmap is defined by \a pixmap. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPath(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPixmapItem *QGraphicsScene::addPixmap(const QPixmap &pixmap) +{ + QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); + addItem(item); + return item; +} + +/*! + Creates and adds a polygon item to the scene, and returns the item + pointer. The polygon is defined by \a polygon, and its pen and + brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPath(), addRect(), addText(), addItem(), + addWidget() +*/ +QGraphicsPolygonItem *QGraphicsScene::addPolygon(const QPolygonF &polygon, + const QPen &pen, const QBrush &brush) +{ + QGraphicsPolygonItem *item = new QGraphicsPolygonItem(polygon); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + Creates and adds a rectangle item to the scene, and returns the item + pointer. The geometry of the rectangle is defined by \a rect, and its pen + and brush are initialized to \a pen and \a brush. + + Note that the item's geometry is provided in item coordinates, and its + position is initialized to (0, 0). For example, if a QRect(50, 50, 100, + 100) is added, its top-left corner will be at (50, 50) relative to the + origin in the items coordinate system. + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addText(), + addItem(), addWidget() +*/ +QGraphicsRectItem *QGraphicsScene::addRect(const QRectF &rect, const QPen &pen, const QBrush &brush) +{ + QGraphicsRectItem *item = new QGraphicsRectItem(rect); + item->setPen(pen); + item->setBrush(brush); + addItem(item); + return item; +} + +/*! + \fn QGraphicsRectItem *QGraphicsScene::addRect(qreal x, qreal y, qreal w, qreal h, const QPen &pen, const QBrush &brush) + \since 4.3 + + This convenience function is equivalent to calling addRect(QRectF(\a x, + \a y, \a w, \a h), \a pen, \a brush). +*/ + +/*! + Creates and adds a text item to the scene, and returns the item + pointer. The text string is initialized to \a text, and its font + is initialized to \a font. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addItem(), addWidget() +*/ +QGraphicsTextItem *QGraphicsScene::addText(const QString &text, const QFont &font) +{ + QGraphicsTextItem *item = new QGraphicsTextItem(text); + item->setFont(font); + addItem(item); + return item; +} + +/*! + Creates and adds a QGraphicsSimpleTextItem to the scene, and returns the + item pointer. The text string is initialized to \a text, and its font is + initialized to \a font. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addItem(), addWidget() +*/ +QGraphicsSimpleTextItem *QGraphicsScene::addSimpleText(const QString &text, const QFont &font) +{ + QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem(text); + item->setFont(font); + addItem(item); + return item; +} + +/*! + Creates a new QGraphicsProxyWidget for \a widget, adds it to the scene, + and returns a pointer to the proxy. \a wFlags set the default window flags + for the embedding proxy widget. + + The item's position is initialized to (0, 0). + + If the item is visible (i.e., QGraphicsItem::isVisible() returns true), + QGraphicsScene will emit changed() once control goes back to the event + loop. + + Note that widgets with the Qt::WA_PaintOnScreen widget attribute + set and widgets that wrap an external application or controller + are not supported. Examples are QGLWidget and QAxWidget. + + \sa addEllipse(), addLine(), addPixmap(), addPixmap(), addRect(), + addText(), addSimpleText(), addItem() +*/ +QGraphicsProxyWidget *QGraphicsScene::addWidget(QWidget *widget, Qt::WindowFlags wFlags) +{ + QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(0, wFlags); + proxy->setWidget(widget); + addItem(proxy); + return proxy; +} + +/*! + Removes the item \a item and all its children from the scene. The + ownership of \a item is passed on to the caller (i.e., + QGraphicsScene will no longer delete \a item when destroyed). + + \sa addItem() +*/ +void QGraphicsScene::removeItem(QGraphicsItem *item) +{ + // ### Refactoring: This function shares much functionality with _q_removeItemLater() + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::removeItem: cannot remove 0-item"); + return; + } + if (item->scene() != this) { + qWarning("QGraphicsScene::removeItem: item %p's scene (%p)" + " is different from this scene (%p)", + item, item->scene(), this); + return; + } + + // Notify the item that it's scene is changing to 0, allowing the item to + // react. + const QVariant newSceneVariant(item->itemChange(QGraphicsItem::ItemSceneChange, + QVariant::fromValue(0))); + QGraphicsScene *targetScene = qvariant_cast(newSceneVariant); + if (targetScene != 0 && targetScene != this) { + targetScene->addItem(item); + return; + } + + d->removeItemHelper(item); + + // Deliver post-change notification + item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + + d->updateInputMethodSensitivityInViews(); +} + +/*! + When the scene is active, this functions returns the scene's current focus + item, or 0 if no item currently has focus. When the scene is inactive, this + functions returns the item that will gain input focus when the scene becomes + active. + + The focus item receives keyboard input when the scene receives a + key event. + + \sa setFocusItem(), QGraphicsItem::hasFocus(), isActive() +*/ +QGraphicsItem *QGraphicsScene::focusItem() const +{ + Q_D(const QGraphicsScene); + return isActive() ? d->focusItem : d->passiveFocusItem; +} + +/*! + Sets the scene's focus item to \a item, with the focus reason \a + focusReason, after removing focus from any previous item that may have had + focus. + + If \a item is 0, or if it either does not accept focus (i.e., it does not + have the QGraphicsItem::ItemIsFocusable flag enabled), or is not visible + or not enabled, this function only removes focus from any previous + focusitem. + + If item is not 0, and the scene does not currently have focus (i.e., + hasFocus() returns false), this function will call setFocus() + automatically. + + \sa focusItem(), hasFocus(), setFocus() +*/ +void QGraphicsScene::setFocusItem(QGraphicsItem *item, Qt::FocusReason focusReason) +{ + Q_D(QGraphicsScene); + if (item) + item->setFocus(focusReason); + else + d->setFocusItemHelper(item, focusReason); +} + +/*! + Returns true if the scene has focus; otherwise returns false. If the scene + has focus, it will will forward key events from QKeyEvent to any item that + has focus. + + \sa setFocus(), setFocusItem() +*/ +bool QGraphicsScene::hasFocus() const +{ + Q_D(const QGraphicsScene); + return d->hasFocus; +} + +/*! + Sets focus on the scene by sending a QFocusEvent to the scene, passing \a + focusReason as the reason. If the scene regains focus after having + previously lost it while an item had focus, the last focus item will + receive focus with \a focusReason as the reason. + + If the scene already has focus, this function does nothing. + + \sa hasFocus(), clearFocus(), setFocusItem() +*/ +void QGraphicsScene::setFocus(Qt::FocusReason focusReason) +{ + Q_D(QGraphicsScene); + if (d->hasFocus || !isActive()) + return; + QFocusEvent event(QEvent::FocusIn, focusReason); + QCoreApplication::sendEvent(this, &event); +} + +/*! + Clears focus from the scene. If any item has focus when this function is + called, it will lose focus, and regain focus again once the scene regains + focus. + + A scene that does not have focus ignores key events. + + \sa hasFocus(), setFocus(), setFocusItem() +*/ +void QGraphicsScene::clearFocus() +{ + Q_D(QGraphicsScene); + if (d->hasFocus) { + d->hasFocus = false; + d->passiveFocusItem = d->focusItem; + setFocusItem(0, Qt::OtherFocusReason); + } +} + +/*! + \property QGraphicsScene::stickyFocus + \brief whether clicking into the scene background will clear focus + + \since 4.6 + + In a QGraphicsScene with stickyFocus set to true, focus will remain + unchanged when the user clicks into the scene background or on an item + that does not accept focus. Otherwise, focus will be cleared. + + By default, this property is false. + + Focus changes in response to a mouse press. You can reimplement + mousePressEvent() in a subclass of QGraphicsScene to toggle this property + based on where the user has clicked. + + \sa clearFocus(), setFocusItem() +*/ +void QGraphicsScene::setStickyFocus(bool enabled) +{ + Q_D(QGraphicsScene); + d->stickyFocus = enabled; +} +bool QGraphicsScene::stickyFocus() const +{ + Q_D(const QGraphicsScene); + return d->stickyFocus; +} + +/*! + Returns the current mouse grabber item, or 0 if no item is currently + grabbing the mouse. The mouse grabber item is the item that receives all + mouse events sent to the scene. + + An item becomes a mouse grabber when it receives and accepts a + mouse press event, and it stays the mouse grabber until either of + the following events occur: + + \list + \o If the item receives a mouse release event when there are no other + buttons pressed, it loses the mouse grab. + \o If the item becomes invisible (i.e., someone calls \c {item->setVisible(false)}), + or if it becomes disabled (i.e., someone calls \c {item->setEnabled(false)}), + it loses the mouse grab. + \o If the item is removed from the scene, it loses the mouse grab. + \endlist + + If the item loses its mouse grab, the scene will ignore all mouse events + until a new item grabs the mouse (i.e., until a new item receives a mouse + press event). +*/ +QGraphicsItem *QGraphicsScene::mouseGrabberItem() const +{ + Q_D(const QGraphicsScene); + return !d->mouseGrabberItems.isEmpty() ? d->mouseGrabberItems.last() : 0; +} + +/*! + \property QGraphicsScene::backgroundBrush + \brief the background brush of the scene. + + Set this property to changes the scene's background to a different color, + gradient or texture. The default background brush is Qt::NoBrush. The + background is drawn before (behind) the items. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 3 + + QGraphicsScene::render() calls drawBackground() to draw the scene + background. For more detailed control over how the background is drawn, + you can reimplement drawBackground() in a subclass of QGraphicsScene. +*/ +QBrush QGraphicsScene::backgroundBrush() const +{ + Q_D(const QGraphicsScene); + return d->backgroundBrush; +} +void QGraphicsScene::setBackgroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsScene); + d->backgroundBrush = brush; + foreach (QGraphicsView *view, d->views) { + view->resetCachedContent(); + view->viewport()->update(); + } + update(); +} + +/*! + \property QGraphicsScene::foregroundBrush + \brief the foreground brush of the scene. + + Change this property to set the scene's foreground to a different + color, gradient or texture. + + The foreground is drawn after (on top of) the items. The default + foreground brush is Qt::NoBrush ( i.e. the foreground is not + drawn). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 4 + + QGraphicsScene::render() calls drawForeground() to draw the scene + foreground. For more detailed control over how the foreground is + drawn, you can reimplement the drawForeground() function in a + QGraphicsScene subclass. +*/ +QBrush QGraphicsScene::foregroundBrush() const +{ + Q_D(const QGraphicsScene); + return d->foregroundBrush; +} +void QGraphicsScene::setForegroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsScene); + d->foregroundBrush = brush; + foreach (QGraphicsView *view, views()) + view->viewport()->update(); + update(); +} + +/*! + This method is used by input methods to query a set of properties of + the scene to be able to support complex input method operations as support + for surrounding text and reconversions. + + The \a query parameter specifies which property is queried. + + \sa QWidget::inputMethodQuery() +*/ +QVariant QGraphicsScene::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QGraphicsScene); + if (!d->focusItem || !(d->focusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) + return QVariant(); + const QTransform matrix = d->focusItem->sceneTransform(); + QVariant value = d->focusItem->inputMethodQuery(query); + if (value.type() == QVariant::RectF) + value = matrix.mapRect(value.toRectF()); + else if (value.type() == QVariant::PointF) + value = matrix.map(value.toPointF()); + else if (value.type() == QVariant::Rect) + value = matrix.mapRect(value.toRect()); + else if (value.type() == QVariant::Point) + value = matrix.map(value.toPoint()); + return value; +} + +/*! + \fn void QGraphicsScene::update(const QRectF &rect) + Schedules a redraw of the area \a rect on the scene. + + \sa sceneRect(), changed() +*/ +void QGraphicsScene::update(const QRectF &rect) +{ + Q_D(QGraphicsScene); + if (d->updateAll || (rect.isEmpty() && !rect.isNull())) + return; + + // Check if anyone's connected; if not, we can send updates directly to + // the views. Otherwise or if there are no views, use old behavior. + bool directUpdates = !(d->isSignalConnected(d->changedSignalIndex)) && !d->views.isEmpty(); + if (rect.isNull()) { + d->updateAll = true; + d->updatedRects.clear(); + if (directUpdates) { + // Update all views. + for (int i = 0; i < d->views.size(); ++i) + d->views.at(i)->d_func()->fullUpdatePending = true; + } + } else { + if (directUpdates) { + // Update all views. + for (int i = 0; i < d->views.size(); ++i) { + QGraphicsView *view = d->views.at(i); + if (view->isTransformed()) + view->d_func()->updateRectF(view->viewportTransform().mapRect(rect)); + else + view->d_func()->updateRectF(rect); + } + } else { + d->updatedRects << rect; + } + } + + if (!d->calledEmitUpdated) { + d->calledEmitUpdated = true; + QMetaObject::invokeMethod(this, "_q_emitUpdated", Qt::QueuedConnection); + } +} + +/*! + \fn void QGraphicsScene::update(qreal x, qreal y, qreal w, qreal h) + \overload + \since 4.3 + + This function is equivalent to calling update(QRectF(\a x, \a y, \a w, + \a h)); +*/ + +/*! + Invalidates and schedules a redraw of the \a layers in \a rect on the + scene. Any cached content in \a layers is unconditionally invalidated and + redrawn. + + You can use this function overload to notify QGraphicsScene of changes to + the background or the foreground of the scene. This function is commonly + used for scenes with tile-based backgrounds to notify changes when + QGraphicsView has enabled + \l{QGraphicsView::CacheBackground}{CacheBackground}. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsscene.cpp 5 + + Note that QGraphicsView currently supports background caching only (see + QGraphicsView::CacheBackground). This function is equivalent to calling + update() if any layer but BackgroundLayer is passed. + + \sa QGraphicsView::resetCachedContent() +*/ +void QGraphicsScene::invalidate(const QRectF &rect, SceneLayers layers) +{ + foreach (QGraphicsView *view, views()) + view->invalidateScene(rect, layers); + update(rect); +} + +/*! + \fn void QGraphicsScene::invalidate(qreal x, qreal y, qreal w, qreal h, SceneLayers layers) + \overload + \since 4.3 + + This convenience function is equivalent to calling invalidate(QRectF(\a x, \a + y, \a w, \a h), \a layers); +*/ + +/*! + Returns a list of all the views that display this scene. + + \sa QGraphicsView::scene() +*/ +QList QGraphicsScene::views() const +{ + Q_D(const QGraphicsScene); + return d->views; +} + +/*! + This slot \e advances the scene by one step, by calling + QGraphicsItem::advance() for all items on the scene. This is done in two + phases: in the first phase, all items are notified that the scene is about + to change, and in the second phase all items are notified that they can + move. In the first phase, QGraphicsItem::advance() is called passing a + value of 0 as an argument, and 1 is passed in the second phase. + + \sa QGraphicsItem::advance(), QGraphicsItemAnimation, QTimeLine +*/ +void QGraphicsScene::advance() +{ + for (int i = 0; i < 2; ++i) { + foreach (QGraphicsItem *item, items()) + item->advance(i); + } +} + +/*! + Processes the event \a event, and dispatches it to the respective + event handlers. + + In addition to calling the convenience event handlers, this + function is responsible for converting mouse move events to hover + events for when there is no mouse grabber item. Hover events are + delivered directly to items; there is no convenience function for + them. + + Unlike QWidget, QGraphicsScene does not have the convenience functions + \l{QWidget::}{enterEvent()} and \l{QWidget::}{leaveEvent()}. Use this + function to obtain those events instead. + + \sa contextMenuEvent(), keyPressEvent(), keyReleaseEvent(), + mousePressEvent(), mouseMoveEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), focusInEvent(), focusOutEvent() +*/ +bool QGraphicsScene::event(QEvent *event) +{ + Q_D(QGraphicsScene); + + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + // Reset the under-mouse list to ensure that this event gets fresh + // item-under-mouse data. Be careful about this list; if people delete + // items from inside event handlers, this list can quickly end up + // having stale pointers in it. We need to clear it before dispatching + // events that use it. + // ### this should only be cleared if we received a new mouse move event, + // which relies on us fixing the replay mechanism in QGraphicsView. + d->cachedItemsUnderMouse.clear(); + default: + break; + } + + switch (event->type()) { + case QEvent::GraphicsSceneDragEnter: + dragEnterEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragMove: + dragMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDragLeave: + dragLeaveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneDrop: + dropEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneContextMenu: + contextMenuEvent(static_cast(event)); + break; + case QEvent::KeyPress: + if (!d->focusItem) { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + bool res = false; + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) { + res = focusNextPrevChild(false); + } else if (k->key() == Qt::Key_Tab) { + res = focusNextPrevChild(true); + } + if (!res) + event->ignore(); + return true; + } + } + } + keyPressEvent(static_cast(event)); + break; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast(event)); + break; + case QEvent::ShortcutOverride: { + QGraphicsItem *parent = focusItem(); + while (parent) { + d->sendEvent(parent, event); + if (event->isAccepted()) + return true; + parent = parent->parentItem(); + } + } + return false; + case QEvent::GraphicsSceneMouseMove: + { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + d->lastSceneMousePos = mouseEvent->scenePos(); + mouseMoveEvent(mouseEvent); + break; + } + case QEvent::GraphicsSceneMousePress: + mousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + mouseDoubleClickEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneWheel: + wheelEvent(static_cast(event)); + break; + case QEvent::FocusIn: + focusInEvent(static_cast(event)); + break; + case QEvent::FocusOut: + focusOutEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + d->lastSceneMousePos = hoverEvent->scenePos(); + d->dispatchHoverEvent(hoverEvent); + break; + } + case QEvent::Leave: + // hackieshly unpacking the viewport pointer from the leave event. + d->leaveScene(reinterpret_cast(event->d)); + break; + case QEvent::GraphicsSceneHelp: + helpEvent(static_cast(event)); + break; + case QEvent::InputMethod: + inputMethodEvent(static_cast(event)); + break; + case QEvent::WindowActivate: + if (!d->activationRefCount++) { + if (d->lastActivePanel) { + // Activate the last panel. + d->setActivePanelHelper(d->lastActivePanel, true); + } else if (d->tabFocusFirst && d->tabFocusFirst->isPanel()) { + // Activate the panel of the first item in the tab focus + // chain. + d->setActivePanelHelper(d->tabFocusFirst, true); + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::WindowDeactivate: + if (!--d->activationRefCount) { + if (d->activePanel) { + // Deactivate the active panel (but keep it so we can + // reactivate it later). + QGraphicsItem *lastActivePanel = d->activePanel; + d->setActivePanelHelper(0, true); + d->lastActivePanel = lastActivePanel; + } else { + // Activate all toplevel items. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + sendEvent(item, &event); + } + } + } + break; + case QEvent::ApplicationFontChange: { + // Resolve the existing scene font. + d->resolveFont(); + break; + } + case QEvent::FontChange: + // Update the entire scene when the font changes. + update(); + break; + case QEvent::ApplicationPaletteChange: { + // Resolve the existing scene palette. + d->resolvePalette(); + break; + } + case QEvent::PaletteChange: + // Update the entire scene when the palette changes. + update(); + break; + case QEvent::StyleChange: + // Reresolve all widgets' styles. Update all top-level widgets' + // geometries that do not have an explicit style set. + update(); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + d->touchEventHandler(static_cast(event)); + break; +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + case QEvent::GestureOverride: + d->gestureEventHandler(static_cast(event)); + break; +#endif // QT_NO_GESTURES + default: + return QObject::event(event); + } + return true; +} + +/*! + \reimp + + QGraphicsScene filters QApplication's events to detect palette and font + changes. +*/ +bool QGraphicsScene::eventFilter(QObject *watched, QEvent *event) +{ + if (watched != qApp) + return false; + + switch (event->type()) { + case QEvent::ApplicationPaletteChange: + QApplication::postEvent(this, new QEvent(QEvent::ApplicationPaletteChange)); + break; + case QEvent::ApplicationFontChange: + QApplication::postEvent(this, new QEvent(QEvent::ApplicationFontChange)); + break; + default: + break; + } + return false; +} + +/*! + This event handler, for event \a contextMenuEvent, can be reimplemented in + a subclass to receive context menu events. The default implementation + forwards the event to the topmost item that accepts context menu events at + the position of the event. If no items accept context menu events at this + position, the event is ignored. + + \sa QGraphicsItem::contextMenuEvent() +*/ +void QGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent) +{ + Q_D(QGraphicsScene); + // Ignore by default. + contextMenuEvent->ignore(); + + // Send the event to all items at this position until one item accepts the + // event. + foreach (QGraphicsItem *item, d->itemsAtPosition(contextMenuEvent->screenPos(), + contextMenuEvent->scenePos(), + contextMenuEvent->widget())) { + contextMenuEvent->setPos(item->d_ptr->genericMapFromScene(contextMenuEvent->scenePos(), + contextMenuEvent->widget())); + contextMenuEvent->accept(); + if (!d->sendEvent(item, contextMenuEvent)) + break; + + if (contextMenuEvent->isAccepted()) + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag enter events for the scene. + + The default implementation accepts the event and prepares the scene to + accept drag move events. + + \sa QGraphicsItem::dragEnterEvent(), dragMoveEvent(), dragLeaveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + d->dragDropItem = 0; + d->lastDropAction = Qt::IgnoreAction; + event->accept(); +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag move events for the scene. + + \sa QGraphicsItem::dragMoveEvent(), dragEnterEvent(), dragLeaveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + event->ignore(); + + if (!d->mouseGrabberItems.isEmpty()) { + // Mouse grabbers that start drag events lose the mouse grab. + d->clearMouseGrabber(); + d->mouseGrabberButtonDownPos.clear(); + d->mouseGrabberButtonDownScenePos.clear(); + d->mouseGrabberButtonDownScreenPos.clear(); + } + + bool eventDelivered = false; + + // Find the topmost enabled items under the cursor. They are all + // candidates for accepting drag & drop events. + foreach (QGraphicsItem *item, d->itemsAtPosition(event->screenPos(), + event->scenePos(), + event->widget())) { + if (!item->isEnabled() || !item->acceptDrops()) + continue; + + if (item != d->dragDropItem) { + // Enter the new drag drop item. If it accepts the event, we send + // the leave to the parent item. + QGraphicsSceneDragDropEvent dragEnter(QEvent::GraphicsSceneDragEnter); + d->cloneDragDropEvent(&dragEnter, event); + dragEnter.setDropAction(event->proposedAction()); + d->sendDragDropEvent(item, &dragEnter); + event->setAccepted(dragEnter.isAccepted()); + event->setDropAction(dragEnter.dropAction()); + if (!event->isAccepted()) { + // Propagate to the item under + continue; + } + + d->lastDropAction = event->dropAction(); + + if (d->dragDropItem) { + // Leave the last drag drop item. A perfect implementation + // would set the position of this event to the point where + // this event and the last event intersect with the item's + // shape, but that's not easy to do. :-) + QGraphicsSceneDragDropEvent dragLeave(QEvent::GraphicsSceneDragLeave); + d->cloneDragDropEvent(&dragLeave, event); + d->sendDragDropEvent(d->dragDropItem, &dragLeave); + } + + // We've got a new drag & drop item + d->dragDropItem = item; + } + + // Send the move event. + event->setDropAction(d->lastDropAction); + event->accept(); + d->sendDragDropEvent(item, event); + if (event->isAccepted()) + d->lastDropAction = event->dropAction(); + eventDelivered = true; + break; + } + + if (!eventDelivered) { + if (d->dragDropItem) { + // Leave the last drag drop item + QGraphicsSceneDragDropEvent dragLeave(QEvent::GraphicsSceneDragLeave); + d->cloneDragDropEvent(&dragLeave, event); + d->sendDragDropEvent(d->dragDropItem, &dragLeave); + d->dragDropItem = 0; + } + // Propagate + event->setDropAction(Qt::IgnoreAction); + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drag leave events for the scene. + + \sa QGraphicsItem::dragLeaveEvent(), dragEnterEvent(), dragMoveEvent(), + dropEvent() +*/ +void QGraphicsScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_D(QGraphicsScene); + if (d->dragDropItem) { + // Leave the last drag drop item + d->sendDragDropEvent(d->dragDropItem, event); + d->dragDropItem = 0; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a subclass + to receive drop events for the scene. + + \sa QGraphicsItem::dropEvent(), dragEnterEvent(), dragMoveEvent(), + dragLeaveEvent() +*/ +void QGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent *event) +{ + Q_UNUSED(event); + Q_D(QGraphicsScene); + if (d->dragDropItem) { + // Drop on the last drag drop item + d->sendDragDropEvent(d->dragDropItem, event); + d->dragDropItem = 0; + } +} + +/*! + This event handler, for event \a focusEvent, can be reimplemented in a + subclass to receive focus in events. + + The default implementation sets focus on the scene, and then on the last + focus item. + + \sa QGraphicsItem::focusOutEvent() +*/ +void QGraphicsScene::focusInEvent(QFocusEvent *focusEvent) +{ + Q_D(QGraphicsScene); + + d->hasFocus = true; + switch (focusEvent->reason()) { + case Qt::TabFocusReason: + if (!focusNextPrevChild(true)) + focusEvent->ignore(); + break; + case Qt::BacktabFocusReason: + if (!focusNextPrevChild(false)) + focusEvent->ignore(); + break; + default: + if (d->passiveFocusItem) { + // Set focus on the last focus item + setFocusItem(d->passiveFocusItem, focusEvent->reason()); + } + break; + } +} + +/*! + This event handler, for event \a focusEvent, can be reimplemented in a + subclass to receive focus out events. + + The default implementation removes focus from any focus item, then removes + focus from the scene. + + \sa QGraphicsItem::focusInEvent() +*/ +void QGraphicsScene::focusOutEvent(QFocusEvent *focusEvent) +{ + Q_D(QGraphicsScene); + d->hasFocus = false; + d->passiveFocusItem = d->focusItem; + setFocusItem(0, focusEvent->reason()); + + // Remove all popups when the scene loses focus. + if (!d->popupWidgets.isEmpty()) + d->removePopup(d->popupWidgets.first()); +} + +/*! + This event handler, for event \a helpEvent, can be + reimplemented in a subclass to receive help events. The events + are of type QEvent::ToolTip, which are created when a tooltip is + requested. + + The default implementation shows the tooltip of the topmost + item, i.e., the item with the highest z-value, at the mouse + cursor position. If no item has a tooltip set, this function + does nothing. + + \sa QGraphicsItem::toolTip(), QGraphicsSceneHelpEvent +*/ +void QGraphicsScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) +{ +#ifdef QT_NO_TOOLTIP + Q_UNUSED(helpEvent); +#else + // Find the first item that does tooltips + Q_D(QGraphicsScene); + QList itemsAtPos = d->itemsAtPosition(helpEvent->screenPos(), + helpEvent->scenePos(), + helpEvent->widget()); + QGraphicsItem *toolTipItem = 0; + for (int i = 0; i < itemsAtPos.size(); ++i) { + QGraphicsItem *tmp = itemsAtPos.at(i); + if (tmp->d_func()->isProxyWidget()) { + // if the item is a proxy widget, the event is forwarded to it + sendEvent(tmp, helpEvent); + if (helpEvent->isAccepted()) + return; + } + if (!tmp->toolTip().isEmpty()) { + toolTipItem = tmp; + break; + } + } + + // Show or hide the tooltip + QString text; + QPoint point; + if (toolTipItem && !toolTipItem->toolTip().isEmpty()) { + text = toolTipItem->toolTip(); + point = helpEvent->screenPos(); + } + QToolTip::showText(point, text, helpEvent->widget()); + helpEvent->setAccepted(!text.isEmpty()); +#endif +} + +bool QGraphicsScenePrivate::itemAcceptsHoverEvents_helper(const QGraphicsItem *item) const +{ + return (item->d_ptr->acceptsHover + || (item->d_ptr->isWidget + && static_cast(item)->d_func()->hasDecoration())) + && !item->isBlockedByModalPanel(); +} + +/*! + This event handler, for event \a hoverEvent, can be reimplemented in a + subclass to receive hover enter events. The default implementation + forwards the event to the topmost item that accepts hover events at the + scene position from the event. + + \sa QGraphicsItem::hoverEvent(), QGraphicsItem::setAcceptHoverEvents() +*/ +bool QGraphicsScenePrivate::dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEvent) +{ + if (allItemsIgnoreHoverEvents) + return false; + + // Find the first item that accepts hover events, reusing earlier + // calculated data is possible. + if (cachedItemsUnderMouse.isEmpty()) { + cachedItemsUnderMouse = itemsAtPosition(hoverEvent->screenPos(), + hoverEvent->scenePos(), + hoverEvent->widget()); + } + + QGraphicsItem *item = 0; + for (int i = 0; i < cachedItemsUnderMouse.size(); ++i) { + QGraphicsItem *tmp = cachedItemsUnderMouse.at(i); + if (itemAcceptsHoverEvents_helper(tmp)) { + item = tmp; + break; + } + } + + // Find the common ancestor item for the new topmost hoverItem and the + // last item in the hoverItem list. + QGraphicsItem *commonAncestorItem = (item && !hoverItems.isEmpty()) ? item->commonAncestorItem(hoverItems.last()) : 0; + while (commonAncestorItem && !itemAcceptsHoverEvents_helper(commonAncestorItem)) + commonAncestorItem = commonAncestorItem->parentItem(); + if (commonAncestorItem && commonAncestorItem->panel() != item->panel()) { + // The common ancestor isn't in the same panel as the two hovered + // items. + commonAncestorItem = 0; + } + + // Check if the common ancestor item is known. + int index = commonAncestorItem ? hoverItems.indexOf(commonAncestorItem) : -1; + // Send hover leaves to any existing hovered children of the common + // ancestor item. + for (int i = hoverItems.size() - 1; i > index; --i) { + QGraphicsItem *lastItem = hoverItems.takeLast(); + if (itemAcceptsHoverEvents_helper(lastItem)) + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, hoverEvent); + } + + // Item is a child of a known item. Generate enter events for the + // missing links. + QList parents; + QGraphicsItem *parent = item; + while (parent && parent != commonAncestorItem) { + parents.prepend(parent); + if (parent->isPanel()) { + // Stop at the panel - we don't deliver beyond this point. + break; + } + parent = parent->parentItem(); + } + for (int i = 0; i < parents.size(); ++i) { + parent = parents.at(i); + hoverItems << parent; + if (itemAcceptsHoverEvents_helper(parent)) + sendHoverEvent(QEvent::GraphicsSceneHoverEnter, parent, hoverEvent); + } + + // Generate a move event for the item itself + if (item + && !hoverItems.isEmpty() + && item == hoverItems.last()) { + sendHoverEvent(QEvent::GraphicsSceneHoverMove, item, hoverEvent); + return true; + } + return false; +} + +/*! + \internal + + Handles all actions necessary to clean up the scene when the mouse leaves + the view. +*/ +void QGraphicsScenePrivate::leaveScene(QWidget *viewport) +{ +#ifndef QT_NO_TOOLTIP + QToolTip::hideText(); +#endif + QGraphicsView *view = qobject_cast(viewport->parent()); + // Send HoverLeave events to all existing hover items, topmost first. + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setWidget(viewport); + + if (view) { + QPoint cursorPos = QCursor::pos(); + hoverEvent.setScenePos(view->mapToScene(viewport->mapFromGlobal(cursorPos))); + hoverEvent.setLastScenePos(hoverEvent.scenePos()); + hoverEvent.setScreenPos(cursorPos); + hoverEvent.setLastScreenPos(hoverEvent.screenPos()); + } + + while (!hoverItems.isEmpty()) { + QGraphicsItem *lastItem = hoverItems.takeLast(); + if (itemAcceptsHoverEvents_helper(lastItem)) + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, &hoverEvent); + } +} + +/*! + This event handler, for event \a keyEvent, can be reimplemented in a + subclass to receive keypress events. The default implementation forwards + the event to current focus item. + + \sa QGraphicsItem::keyPressEvent(), focusItem() +*/ +void QGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) +{ + // ### Merge this function with keyReleaseEvent; they are identical + // ### (except this comment). + Q_D(QGraphicsScene); + QGraphicsItem *item = !d->keyboardGrabberItems.isEmpty() ? d->keyboardGrabberItems.last() : 0; + if (!item) + item = focusItem(); + if (item) { + QGraphicsItem *p = item; + do { + // Accept the event by default + keyEvent->accept(); + // Send it; QGraphicsItem::keyPressEvent ignores it. If the event + // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; + if (!d->sendEvent(p, keyEvent)) + break; + } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); + } else { + keyEvent->ignore(); + } +} + +/*! + This event handler, for event \a keyEvent, can be reimplemented in a + subclass to receive key release events. The default implementation + forwards the event to current focus item. + + \sa QGraphicsItem::keyReleaseEvent(), focusItem() +*/ +void QGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) +{ + // ### Merge this function with keyPressEvent; they are identical (except + // ### this comment). + Q_D(QGraphicsScene); + QGraphicsItem *item = !d->keyboardGrabberItems.isEmpty() ? d->keyboardGrabberItems.last() : 0; + if (!item) + item = focusItem(); + if (item) { + QGraphicsItem *p = item; + do { + // Accept the event by default + keyEvent->accept(); + // Send it; QGraphicsItem::keyPressEvent ignores it. If the event + // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; + if (!d->sendEvent(p, keyEvent)) + break; + } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); + } else { + keyEvent->ignore(); + } +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse press events for the scene. + + The default implementation depends on the state of the scene. If + there is a mouse grabber item, then the event is sent to the mouse + grabber. Otherwise, it is forwarded to the topmost item that + accepts mouse events at the scene position from the event, and + that item promptly becomes the mouse grabber item. + + If there is no item at the given position on the scene, the + selection area is reset, any focus item loses its input focus, and + the event is then ignored. + + \sa QGraphicsItem::mousePressEvent(), + QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + // Dispatch hover events + QGraphicsSceneHoverEvent hover; + _q_hoverFromMouseEvent(&hover, mouseEvent); + d->dispatchHoverEvent(&hover); + } + + d->mousePressEventHandler(mouseEvent); +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse move events for the scene. + + The default implementation depends on the mouse grabber state. If there is + a mouse grabber item, the event is sent to the mouse grabber. If there + are any items that accept hover events at the current position, the event + is translated into a hover event and accepted; otherwise it's ignored. + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseReleaseEvent(), + QGraphicsItem::mouseDoubleClickEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + if (mouseEvent->buttons()) + return; + QGraphicsSceneHoverEvent hover; + _q_hoverFromMouseEvent(&hover, mouseEvent); + mouseEvent->setAccepted(d->dispatchHoverEvent(&hover)); + return; + } + + // Forward the event to the mouse grabber + d->sendMouseEvent(mouseEvent); + mouseEvent->accept(); +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse release events for the scene. + + The default implementation depends on the mouse grabber state. If + there is no mouse grabber, the event is ignored. Otherwise, if + there is a mouse grabber item, the event is sent to the mouse + grabber. If this mouse release represents the last pressed button + on the mouse, the mouse grabber item then loses the mouse grab. + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseMoveEvent(), + QGraphicsItem::mouseDoubleClickEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + if (d->mouseGrabberItems.isEmpty()) { + mouseEvent->ignore(); + return; + } + + // Forward the event to the mouse grabber + d->sendMouseEvent(mouseEvent); + mouseEvent->accept(); + + // Reset the mouse grabber when the last mouse button has been released. + if (!mouseEvent->buttons()) { + if (!d->mouseGrabberItems.isEmpty()) { + d->lastMouseGrabberItem = d->mouseGrabberItems.last(); + if (d->lastMouseGrabberItemHasImplicitMouseGrab) + d->mouseGrabberItems.last()->ungrabMouse(); + } else { + d->lastMouseGrabberItem = 0; + } + + // Generate a hoverevent + QGraphicsSceneHoverEvent hoverEvent; + _q_hoverFromMouseEvent(&hoverEvent, mouseEvent); + d->dispatchHoverEvent(&hoverEvent); + } +} + +/*! + This event handler, for event \a mouseEvent, can be reimplemented + in a subclass to receive mouse doubleclick events for the scene. + + If someone doubleclicks on the scene, the scene will first receive + a mouse press event, followed by a release event (i.e., a click), + then a doubleclick event, and finally a release event. If the + doubleclick event is delivered to a different item than the one + that received the first press and release, it will be delivered as + a press event. However, tripleclick events are not delivered as + doubleclick events in this case. + + The default implementation is similar to mousePressEvent(). + + \sa QGraphicsItem::mousePressEvent(), QGraphicsItem::mouseMoveEvent(), + QGraphicsItem::mouseReleaseEvent(), QGraphicsItem::setAcceptedMouseButtons() +*/ +void QGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent) +{ + Q_D(QGraphicsScene); + d->mousePressEventHandler(mouseEvent); +} + +/*! + This event handler, for event \a wheelEvent, can be reimplemented in a + subclass to receive mouse wheel events for the scene. + + By default, the event is delivered to the topmost visible item under the + cursor. If ignored, the event propagates to the item beneath, and again + until the event is accepted, or it reaches the scene. If no items accept + the event, it is ignored. + + \sa QGraphicsItem::wheelEvent() +*/ +void QGraphicsScene::wheelEvent(QGraphicsSceneWheelEvent *wheelEvent) +{ + Q_D(QGraphicsScene); + QList wheelCandidates = d->itemsAtPosition(wheelEvent->screenPos(), + wheelEvent->scenePos(), + wheelEvent->widget()); + +#ifdef Q_WS_MAC + // On Mac, ignore the event if the first item under the mouse is not the last opened + // popup (or one of its descendant) + if (!d->popupWidgets.isEmpty() && !wheelCandidates.isEmpty() && wheelCandidates.first() != d->popupWidgets.back() && !d->popupWidgets.back()->isAncestorOf(wheelCandidates.first())) { + wheelEvent->accept(); + return; + } +#else + // Find the first popup under the mouse (including the popup's descendants) starting from the last. + // Remove all popups after the one found, or all or them if no popup is under the mouse. + // Then continue with the event. + QList::const_iterator iter = d->popupWidgets.end(); + while (--iter >= d->popupWidgets.begin() && !wheelCandidates.isEmpty()) { + if (wheelCandidates.first() == *iter || (*iter)->isAncestorOf(wheelCandidates.first())) + break; + d->removePopup(*iter); + } +#endif + + bool hasSetFocus = false; + foreach (QGraphicsItem *item, wheelCandidates) { + if (!hasSetFocus && item->isEnabled() + && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { + if (item->isWidget() && static_cast(item)->focusPolicy() == Qt::WheelFocus) { + hasSetFocus = true; + if (item != focusItem()) + setFocusItem(item, Qt::MouseFocusReason); + } + } + + wheelEvent->setPos(item->d_ptr->genericMapFromScene(wheelEvent->scenePos(), + wheelEvent->widget())); + wheelEvent->accept(); + bool isPanel = item->isPanel(); + d->sendEvent(item, wheelEvent); + if (isPanel || wheelEvent->isAccepted()) + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive input method events for the scene. + + The default implementation forwards the event to the focusItem(). + If no item currently has focus or the current focus item does not + accept input methods, this function does nothing. + + \sa QGraphicsItem::inputMethodEvent() +*/ +void QGraphicsScene::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QGraphicsScene); + if (d->focusItem && (d->focusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) + d->sendEvent(d->focusItem, event); +} + +/*! + Draws the background of the scene using \a painter, before any items and + the foreground are drawn. Reimplement this function to provide a custom + background for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture, or gradient for the + background, you can call setBackgroundBrush() instead. + + \sa drawForeground(), drawItems() +*/ +void QGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->backgroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, backgroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +/*! + Draws the foreground of the scene using \a painter, after the background + and all items have been drawn. Reimplement this function to provide a + custom foreground for the scene. + + All painting is done in \e scene coordinates. The \a rect + parameter is the exposed rectangle. + + If all you want is to define a color, texture or gradient for the + foreground, you can call setForegroundBrush() instead. + + \sa drawBackground(), drawItems() +*/ +void QGraphicsScene::drawForeground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsScene); + + if (d->foregroundBrush.style() != Qt::NoBrush) { + if (d->painterStateProtection) + painter->save(); + painter->setBrushOrigin(0, 0); + painter->fillRect(rect, foregroundBrush()); + if (d->painterStateProtection) + painter->restore(); + } +} + +static void _q_paintItem(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool useWindowOpacity, bool painterStateProtection) +{ + if (!item->isWidget()) { + item->paint(painter, option, widget); + return; + } + QGraphicsWidget *widgetItem = static_cast(item); + QGraphicsProxyWidget *proxy = qobject_cast(widgetItem); + const qreal windowOpacity = (proxy && proxy->widget() && useWindowOpacity) + ? proxy->widget()->windowOpacity() : 1.0; + const qreal oldPainterOpacity = painter->opacity(); + + if (qFuzzyIsNull(windowOpacity)) + return; + // Set new painter opacity. + if (windowOpacity < 1.0) + painter->setOpacity(oldPainterOpacity * windowOpacity); + + // set layoutdirection on the painter + Qt::LayoutDirection oldLayoutDirection = painter->layoutDirection(); + painter->setLayoutDirection(widgetItem->layoutDirection()); + + if (widgetItem->isWindow() && widgetItem->windowType() != Qt::Popup && widgetItem->windowType() != Qt::ToolTip + && !(widgetItem->windowFlags() & Qt::FramelessWindowHint)) { + if (painterStateProtection) + painter->save(); + widgetItem->paintWindowFrame(painter, option, widget); + if (painterStateProtection) + painter->restore(); + } else if (widgetItem->autoFillBackground()) { + painter->fillRect(option->exposedRect, widgetItem->palette().window()); + } + + widgetItem->paint(painter, option, widget); + + // Restore layoutdirection on the painter. + painter->setLayoutDirection(oldLayoutDirection); + // Restore painter opacity. + if (windowOpacity < 1.0) + painter->setOpacity(oldPainterOpacity); +} + +static void _q_paintIntoCache(QPixmap *pix, QGraphicsItem *item, const QRegion &pixmapExposed, + const QTransform &itemToPixmap, QPainter::RenderHints renderHints, + const QStyleOptionGraphicsItem *option, bool painterStateProtection) +{ + QPixmap subPix; + QPainter pixmapPainter; + QRect br = pixmapExposed.boundingRect(); + + // Don't use subpixmap if we get a full update. + if (pixmapExposed.isEmpty() || (pixmapExposed.rectCount() == 1 && br.contains(pix->rect()))) { + pix->fill(Qt::transparent); + pixmapPainter.begin(pix); + } else { + subPix = QPixmap(br.size()); + subPix.fill(Qt::transparent); + pixmapPainter.begin(&subPix); + pixmapPainter.translate(-br.topLeft()); + if (!pixmapExposed.isEmpty()) { + // Applied to subPix; paint is adjusted to the coordinate space is + // correct. + pixmapPainter.setClipRegion(pixmapExposed); + } + } + + pixmapPainter.setRenderHints(pixmapPainter.renderHints(), false); + pixmapPainter.setRenderHints(renderHints, true); + pixmapPainter.setWorldTransform(itemToPixmap, true); + + // Render. + _q_paintItem(item, &pixmapPainter, option, 0, false, painterStateProtection); + pixmapPainter.end(); + + if (!subPix.isNull()) { + // Blit the subpixmap into the main pixmap. + pixmapPainter.begin(pix); + pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source); + pixmapPainter.setClipRegion(pixmapExposed); + pixmapPainter.drawPixmap(br.topLeft(), subPix); + pixmapPainter.end(); + } +} + +// Copied from qpaintengine_vg.cpp +// Returns true for 90, 180, and 270 degree rotations. +static inline bool transformIsSimple(const QTransform& transform) +{ + QTransform::TransformationType type = transform.type(); + if (type <= QTransform::TxScale) { + return true; + } else if (type == QTransform::TxRotate) { + // Check for 90, and 270 degree rotations. + qreal m11 = transform.m11(); + qreal m12 = transform.m12(); + qreal m21 = transform.m21(); + qreal m22 = transform.m22(); + if (m11 == 0.0f && m22 == 0.0f) { + if (m12 == 1.0f && m21 == -1.0f) + return true; // 90 degrees. + else if (m12 == -1.0f && m21 == 1.0f) + return true; // 270 degrees. + else if (m12 == -1.0f && m21 == -1.0f) + return true; // 90 degrees inverted y. + else if (m12 == 1.0f && m21 == 1.0f) + return true; // 270 degrees inverted y. + } + } + return false; +} + +/*! + \internal + + Draws items directly, or using cache. +*/ +void QGraphicsScenePrivate::drawItemHelper(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool painterStateProtection) +{ + QGraphicsItemPrivate *itemd = item->d_ptr.data(); + QGraphicsItem::CacheMode cacheMode = QGraphicsItem::CacheMode(itemd->cacheMode); + + // Render directly, using no cache. + if (cacheMode == QGraphicsItem::NoCache +#ifdef Q_WS_X11 + || !X11->use_xrender +#endif + ) { + _q_paintItem(static_cast(item), painter, option, widget, true, painterStateProtection); + return; + } + + const qreal oldPainterOpacity = painter->opacity(); + qreal newPainterOpacity = oldPainterOpacity; + QGraphicsProxyWidget *proxy = item->isWidget() ? qobject_cast(static_cast(item)) : 0; + if (proxy && proxy->widget()) { + const qreal windowOpacity = proxy->widget()->windowOpacity(); + if (windowOpacity < 1.0) + newPainterOpacity *= windowOpacity; + } + + // Item's (local) bounding rect + QRectF brect = item->boundingRect(); + QRectF adjustedBrect(brect); + _q_adjustRect(&adjustedBrect); + if (adjustedBrect.isEmpty()) + return; + + // Fetch the off-screen transparent buffer and exposed area info. + QPixmapCache::Key pixmapKey; + QPixmap pix; + bool pixmapFound; + QGraphicsItemCache *itemCache = itemd->extraItemCache(); + if (cacheMode == QGraphicsItem::ItemCoordinateCache) { + pixmapKey = itemCache->key; + } else { + pixmapKey = itemCache->deviceData.value(widget).key; + } + + // Find pixmap in cache. + pixmapFound = QPixmapCache::find(pixmapKey, &pix); + + // Render using item coordinate cache mode. + if (cacheMode == QGraphicsItem::ItemCoordinateCache) { + QSize pixmapSize; + bool fixedCacheSize = false; + QRect br = brect.toAlignedRect(); + if ((fixedCacheSize = itemCache->fixedSize.isValid())) { + pixmapSize = itemCache->fixedSize; + } else { + pixmapSize = br.size(); + } + + // Create or recreate the pixmap. + int adjust = itemCache->fixedSize.isValid() ? 0 : 2; + QSize adjustSize(adjust*2, adjust*2); + br.adjust(-adjust, -adjust, adjust, adjust); + if (pix.isNull() || (!fixedCacheSize && (pixmapSize + adjustSize) != pix.size())) { + pix = QPixmap(pixmapSize + adjustSize); + itemCache->boundingRect = br; + itemCache->exposed.clear(); + itemCache->allExposed = true; + } else if (itemCache->boundingRect != br) { + itemCache->boundingRect = br; + itemCache->exposed.clear(); + itemCache->allExposed = true; + } + + // Redraw any newly exposed areas. + if (itemCache->allExposed || !itemCache->exposed.isEmpty()) { + + //We know that we will modify the pixmap, removing it from the cache + //will detach the one we have and avoid a deep copy + if (pixmapFound) + QPixmapCache::remove(pixmapKey); + + // Fit the item's bounding rect into the pixmap's coordinates. + QTransform itemToPixmap; + if (fixedCacheSize) { + const QPointF scale(pixmapSize.width() / brect.width(), pixmapSize.height() / brect.height()); + itemToPixmap.scale(scale.x(), scale.y()); + } + itemToPixmap.translate(-br.x(), -br.y()); + + // Generate the item's exposedRect and map its list of expose + // rects to device coordinates. + styleOptionTmp = *option; + QRegion pixmapExposed; + QRectF exposedRect; + if (!itemCache->allExposed) { + for (int i = 0; i < itemCache->exposed.size(); ++i) { + QRectF r = itemCache->exposed.at(i); + exposedRect |= r; + pixmapExposed += itemToPixmap.mapRect(r).toAlignedRect(); + } + } else { + exposedRect = brect; + } + styleOptionTmp.exposedRect = exposedRect; + + // Render. + _q_paintIntoCache(&pix, item, pixmapExposed, itemToPixmap, painter->renderHints(), + &styleOptionTmp, painterStateProtection); + + // insert this pixmap into the cache. + itemCache->key = QPixmapCache::insert(pix); + + // Reset expose data. + itemCache->allExposed = false; + itemCache->exposed.clear(); + } + + // Redraw the exposed area using the transformed painter. Depending on + // the hardware, this may be a server-side operation, or an expensive + // qpixmap-image-transform-pixmap roundtrip. + if (newPainterOpacity != oldPainterOpacity) { + painter->setOpacity(newPainterOpacity); + painter->drawPixmap(br.topLeft(), pix); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(br.topLeft(), pix); + } + return; + } + + // Render using device coordinate cache mode. + if (cacheMode == QGraphicsItem::DeviceCoordinateCache) { + // Find the item's bounds in device coordinates. + QRectF deviceBounds = painter->worldTransform().mapRect(brect); + QRect deviceRect = deviceBounds.toRect().adjusted(-1, -1, 1, 1); + if (deviceRect.isEmpty()) + return; + QRect viewRect = widget ? widget->rect() : QRect(); + if (widget && !viewRect.intersects(deviceRect)) + return; + + // Resort to direct rendering if the device rect exceeds the + // (optional) maximum bounds. (QGraphicsSvgItem uses this). + QSize maximumCacheSize = + itemd->extra(QGraphicsItemPrivate::ExtraMaxDeviceCoordCacheSize).toSize(); + if (!maximumCacheSize.isEmpty() + && (deviceRect.width() > maximumCacheSize.width() + || deviceRect.height() > maximumCacheSize.height())) { + _q_paintItem(static_cast(item), painter, option, widget, + oldPainterOpacity != newPainterOpacity, painterStateProtection); + return; + } + + // Create or reuse offscreen pixmap, possibly scroll/blit from the old one. + // If the world transform is rotated we always recreate the cache to avoid + // wrong blending. + bool pixModified = false; + QGraphicsItemCache::DeviceData *deviceData = &itemCache->deviceData[widget]; + bool invertable = true; + QTransform diff = deviceData->lastTransform.inverted(&invertable); + if (invertable) + diff *= painter->worldTransform(); + deviceData->lastTransform = painter->worldTransform(); + bool allowPartialCacheExposure = false; + bool simpleTransform = invertable && diff.type() <= QTransform::TxTranslate + && transformIsSimple(painter->worldTransform()); + if (!simpleTransform) { + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + deviceData->cacheIndent = QPoint(); + pix = QPixmap(); + } else if (!viewRect.isNull()) { + allowPartialCacheExposure = deviceData->cacheIndent != QPoint(); + } + + // Allow partial cache exposure if the device rect isn't fully contained and + // deviceRect is 20% taller or wider than the viewRect. + if (!allowPartialCacheExposure && !viewRect.isNull() && !viewRect.contains(deviceRect)) { + allowPartialCacheExposure = (viewRect.width() * 1.2 < deviceRect.width()) + || (viewRect.height() * 1.2 < deviceRect.height()); + } + + QRegion scrollExposure; + if (allowPartialCacheExposure) { + // Part of pixmap is drawn. Either device contains viewrect (big + // item covers whole screen) or parts of device are outside the + // viewport. In either case the device rect must be the intersect + // between the two. + int dx = deviceRect.left() < viewRect.left() ? viewRect.left() - deviceRect.left() : 0; + int dy = deviceRect.top() < viewRect.top() ? viewRect.top() - deviceRect.top() : 0; + QPoint newCacheIndent(dx, dy); + deviceRect &= viewRect; + + if (pix.isNull()) { + deviceData->cacheIndent = QPoint(); + itemCache->allExposed = true; + itemCache->exposed.clear(); + pixModified = true; + } + + // Copy / "scroll" the old pixmap onto the new ole and calculate + // scrolled exposure. + if (newCacheIndent != deviceData->cacheIndent || deviceRect.size() != pix.size()) { + QPoint diff = newCacheIndent - deviceData->cacheIndent; + QPixmap newPix(deviceRect.size()); + // ### Investigate removing this fill (test with Plasma and + // graphicssystem raster). + newPix.fill(Qt::transparent); + if (!pix.isNull()) { + QPainter newPixPainter(&newPix); + newPixPainter.drawPixmap(-diff, pix); + newPixPainter.end(); + } + QRegion exposed; + exposed += newPix.rect(); + if (!pix.isNull()) + exposed -= QRect(-diff, pix.size()); + scrollExposure = exposed; + + pix = newPix; + pixModified = true; + } + deviceData->cacheIndent = newCacheIndent; + } else { + // Full pixmap is drawn. + deviceData->cacheIndent = QPoint(); + + // Auto-adjust the pixmap size. + if (deviceRect.size() != pix.size()) { + // exposed needs to cover the whole pixmap + pix = QPixmap(deviceRect.size()); + pixModified = true; + itemCache->allExposed = true; + itemCache->exposed.clear(); + } + } + + // Check for newly invalidated areas. + if (itemCache->allExposed || !itemCache->exposed.isEmpty() || !scrollExposure.isEmpty()) { + //We know that we will modify the pixmap, removing it from the cache + //will detach the one we have and avoid a deep copy + if (pixmapFound) + QPixmapCache::remove(pixmapKey); + + // Construct an item-to-pixmap transform. + QPointF p = deviceRect.topLeft(); + QTransform itemToPixmap = painter->worldTransform(); + if (!p.isNull()) + itemToPixmap *= QTransform::fromTranslate(-p.x(), -p.y()); + + // Map the item's logical expose to pixmap coordinates. + QRegion pixmapExposed = scrollExposure; + if (!itemCache->allExposed) { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + pixmapExposed += itemToPixmap.mapRect(exposed.at(i)).toRect().adjusted(-1, -1, 1, 1); + } + + // Calculate the style option's exposedRect. + QRectF br; + if (itemCache->allExposed) { + br = item->boundingRect(); + } else { + const QVector &exposed = itemCache->exposed; + for (int i = 0; i < exposed.size(); ++i) + br |= exposed.at(i); + QTransform pixmapToItem = itemToPixmap.inverted(); + foreach (QRect r, scrollExposure.rects()) + br |= pixmapToItem.mapRect(r); + } + styleOptionTmp = *option; + styleOptionTmp.exposedRect = br.adjusted(-1, -1, 1, 1); + + // Render the exposed areas. + _q_paintIntoCache(&pix, item, pixmapExposed, itemToPixmap, painter->renderHints(), + &styleOptionTmp, painterStateProtection); + + // Reset expose data. + pixModified = true; + itemCache->allExposed = false; + itemCache->exposed.clear(); + } + + if (pixModified) { + // Insert this pixmap into the cache. + deviceData->key = QPixmapCache::insert(pix); + } + + // Redraw the exposed area using an untransformed painter. This + // effectively becomes a bitblit that does not transform the cache. + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + if (newPainterOpacity != oldPainterOpacity) { + painter->setOpacity(newPainterOpacity); + painter->drawPixmap(deviceRect.topLeft(), pix); + painter->setOpacity(oldPainterOpacity); + } else { + painter->drawPixmap(deviceRect.topLeft(), pix); + } + painter->setWorldTransform(restoreTransform); + return; + } +} + +void QGraphicsScenePrivate::drawItems(QPainter *painter, const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget) +{ + // Make sure we don't have unpolished items before we draw. + if (!unpolishedItems.isEmpty()) + _q_polishItems(); + + updateAll = false; + QRectF exposedSceneRect; + if (exposedRegion && indexMethod != QGraphicsScene::NoIndex) { + exposedSceneRect = exposedRegion->boundingRect().adjusted(-1, -1, 1, 1); + if (viewTransform) + exposedSceneRect = viewTransform->inverted().mapRect(exposedSceneRect); + } + const QList tli = index->estimateTopLevelItems(exposedSceneRect, Qt::AscendingOrder); + for (int i = 0; i < tli.size(); ++i) + drawSubtreeRecursive(tli.at(i), painter, viewTransform, exposedRegion, widget); +} + +void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter *painter, + const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget, + qreal parentOpacity, const QTransform *const effectTransform) +{ + Q_ASSERT(item); + + if (!item->d_ptr->visible) + return; + + const bool itemHasContents = !(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (!itemHasContents && !itemHasChildren) + return; // Item has neither contents nor children!(?) + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) + return; + + QTransform transform(Qt::Uninitialized); + QTransform *transformPtr = 0; + bool translateOnlyTransform = false; +#define ENSURE_TRANSFORM_PTR \ + if (!transformPtr) { \ + Q_ASSERT(!itemIsUntransformable); \ + if (viewTransform) { \ + transform = item->d_ptr->sceneTransform; \ + transform *= *viewTransform; \ + transformPtr = &transform; \ + } else { \ + transformPtr = &item->d_ptr->sceneTransform; \ + translateOnlyTransform = item->d_ptr->sceneTransformTranslateOnly; \ + } \ + } + + // Update the item's scene transform if the item is transformable; + // otherwise calculate the full transform, + bool wasDirtyParentSceneTransform = false; + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + if (itemIsUntransformable) { + transform = item->deviceTransform(viewTransform ? *viewTransform : QTransform()); + transformPtr = &transform; + } else if (item->d_ptr->dirtySceneTransform) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + wasDirtyParentSceneTransform = true; + } + + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + bool drawItem = itemHasContents && !itemIsFullyTransparent; + if (drawItem) { + const QRectF brect = adjustedItemEffectiveBoundingRect(item); + ENSURE_TRANSFORM_PTR + QRect viewBoundingRect = translateOnlyTransform ? brect.translated(transformPtr->dx(), transformPtr->dy()).toAlignedRect() + : transformPtr->mapRect(brect).toAlignedRect(); + viewBoundingRect.adjust(-int(rectAdjust), -int(rectAdjust), rectAdjust, rectAdjust); + if (widget) + item->d_ptr->paintedViewBoundingRects.insert(widget, viewBoundingRect); + drawItem = exposedRegion ? exposedRegion->intersects(viewBoundingRect) + : !viewBoundingRect.normalized().isEmpty(); + if (!drawItem) { + if (!itemHasChildren) + return; + if (itemClipsChildrenToShape) { + if (wasDirtyParentSceneTransform) + item->d_ptr->invalidateChildrenSceneTransform(); + return; + } + } + } // else we know for sure this item has children we must process. + + if (itemHasChildren && itemClipsChildrenToShape) + ENSURE_TRANSFORM_PTR; + +#ifndef QT_NO_GRAPHICSEFFECT + if (item->d_ptr->graphicsEffect && item->d_ptr->graphicsEffect->isEnabled()) { + ENSURE_TRANSFORM_PTR; + QGraphicsItemPaintInfo info(viewTransform, transformPtr, effectTransform, exposedRegion, widget, &styleOptionTmp, + painter, opacity, wasDirtyParentSceneTransform, itemHasContents && !itemIsFullyTransparent); + QGraphicsEffectSource *source = item->d_ptr->graphicsEffect->d_func()->source; + QGraphicsItemEffectSourcePrivate *sourced = static_cast + (source->d_func()); + sourced->info = &info; + const QTransform restoreTransform = painter->worldTransform(); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); + painter->setOpacity(opacity); + + if (sourced->currentCachedSystem() != Qt::LogicalCoordinates + && sourced->lastEffectTransform != painter->worldTransform()) + { + if (sourced->lastEffectTransform.type() <= QTransform::TxTranslate + && painter->worldTransform().type() <= QTransform::TxTranslate) + { + QRectF sourceRect = sourced->boundingRect(Qt::DeviceCoordinates); + QRect effectRect = sourced->paddedEffectRect(Qt::DeviceCoordinates, sourced->currentCachedMode(), sourceRect); + + sourced->setCachedOffset(effectRect.topLeft()); + } else { + sourced->invalidateCache(QGraphicsEffectSourcePrivate::TransformChanged); + } + + sourced->lastEffectTransform = painter->worldTransform(); + } + + item->d_ptr->graphicsEffect->draw(painter); + painter->setWorldTransform(restoreTransform); + sourced->info = 0; + } else +#endif //QT_NO_GRAPHICSEFFECT + { + draw(item, painter, viewTransform, transformPtr, exposedRegion, widget, opacity, + effectTransform, wasDirtyParentSceneTransform, drawItem); + } +} + +static inline void setClip(QPainter *painter, QGraphicsItem *item) +{ + painter->save(); + QRectF clipRect; + const QPainterPath clipPath(item->shape()); + if (QPathClipper::pathToRect(clipPath, &clipRect)) + painter->setClipRect(clipRect, Qt::IntersectClip); + else + painter->setClipPath(clipPath, Qt::IntersectClip); +} + +static inline void setWorldTransform(QPainter *painter, const QTransform *const transformPtr, + const QTransform *effectTransform) +{ + Q_ASSERT(transformPtr); + if (effectTransform) + painter->setWorldTransform(*transformPtr * *effectTransform); + else + painter->setWorldTransform(*transformPtr); +} + +void QGraphicsScenePrivate::draw(QGraphicsItem *item, QPainter *painter, const QTransform *const viewTransform, + const QTransform *const transformPtr, QRegion *exposedRegion, QWidget *widget, + qreal opacity, const QTransform *effectTransform, + bool wasDirtyParentSceneTransform, bool drawItem) +{ + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + bool setChildClip = itemClipsChildrenToShape; + bool itemHasChildrenStackedBehind = false; + + int i = 0; + if (itemHasChildren) { + if (itemClipsChildrenToShape) + setWorldTransform(painter, transformPtr, effectTransform); + + item->d_ptr->ensureSortedChildren(); + // Items with the 'ItemStacksBehindParent' flag are put in front of the list + // so all we have to do is to check the first item. + itemHasChildrenStackedBehind = (item->d_ptr->children.at(0)->d_ptr->flags + & QGraphicsItem::ItemStacksBehindParent); + + if (itemHasChildrenStackedBehind) { + if (itemClipsChildrenToShape) { + setClip(painter, item); + setChildClip = false; + } + + // Draw children behind + for (i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (!(child->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent)) + break; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + drawSubtreeRecursive(child, painter, viewTransform, exposedRegion, widget, opacity, effectTransform); + } + } + } + + // Draw item + if (drawItem) { + Q_ASSERT(!itemIsFullyTransparent); + Q_ASSERT(!(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents)); + Q_ASSERT(transformPtr); + item->d_ptr->initStyleOption(&styleOptionTmp, *transformPtr, exposedRegion + ? *exposedRegion : QRegion(), exposedRegion == 0); + + const bool itemClipsToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsToShape; + bool restorePainterClip = false; + + if (!itemHasChildren || !itemClipsChildrenToShape) { + // Item does not have children or clip children to shape. + setWorldTransform(painter, transformPtr, effectTransform); + if ((restorePainterClip = itemClipsToShape)) + setClip(painter, item); + } else if (itemHasChildrenStackedBehind){ + // Item clips children to shape and has children stacked behind, which means + // the painter is already clipped to the item's shape. + if (itemClipsToShape) { + // The clip is already correct. Ensure correct world transform. + setWorldTransform(painter, transformPtr, effectTransform); + } else { + // Remove clip (this also ensures correct world transform). + painter->restore(); + setChildClip = true; + } + } else if (itemClipsToShape) { + // Item clips children and itself to shape. It does not have hildren stacked + // behind, which means the clip has not yet been set. We set it now and re-use it + // for the children. + setClip(painter, item); + setChildClip = false; + } + + if (painterStateProtection && !restorePainterClip) + painter->save(); + + painter->setOpacity(opacity); + if (!item->d_ptr->cacheMode && !item->d_ptr->isWidget) + item->paint(painter, &styleOptionTmp, widget); + else + drawItemHelper(item, painter, &styleOptionTmp, widget, painterStateProtection); + + if (painterStateProtection || restorePainterClip) + painter->restore(); + + static int drawRect = qgetenv("QT_DRAW_SCENE_ITEM_RECTS").toInt(); + if (drawRect) { + QPen oldPen = painter->pen(); + QBrush oldBrush = painter->brush(); + quintptr ptr = reinterpret_cast(item); + const QColor color = QColor::fromHsv(ptr % 255, 255, 255); + painter->setPen(color); + painter->setBrush(Qt::NoBrush); + painter->drawRect(adjustedItemBoundingRect(item)); + painter->setPen(oldPen); + painter->setBrush(oldBrush); + } + } + + // Draw children in front + if (itemHasChildren) { + if (setChildClip) + setClip(painter, item); + + for (; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + drawSubtreeRecursive(child, painter, viewTransform, exposedRegion, widget, opacity, effectTransform); + } + + // Restore child clip + if (itemClipsChildrenToShape) + painter->restore(); + } +} + +void QGraphicsScenePrivate::markDirty(QGraphicsItem *item, const QRectF &rect, bool invalidateChildren, + bool force, bool ignoreOpacity, bool removingItemFromScene, + bool updateBoundingRect) +{ + Q_ASSERT(item); + if (updateAll) + return; + + if (removingItemFromScene && !ignoreOpacity && !item->d_ptr->ignoreOpacity) { + // If any of the item's ancestors ignore opacity, it means that the opacity + // was set to 0 (and the update request has not yet been processed). That + // also means that we have to ignore the opacity for the item itself; otherwise + // things like: parent->setOpacity(0); scene->removeItem(child) won't work. + // Note that we only do this when removing items from the scene. In all other + // cases the ignoreOpacity bit propagates properly in processDirtyItems, but + // since the item is removed immediately it won't be processed there. + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + if (p->d_ptr->ignoreOpacity) { + item->d_ptr->ignoreOpacity = true; + break; + } + p = p->d_ptr->parent; + } + } + + if (item->d_ptr->discardUpdateRequest(/*ignoreVisibleBit=*/force, + /*ignoreDirtyBit=*/removingItemFromScene || invalidateChildren, + /*ignoreOpacity=*/ignoreOpacity)) { + if (item->d_ptr->dirty) { + // The item is already marked as dirty and will be processed later. However, + // we have to make sure ignoreVisible and ignoreOpacity are set properly; + // otherwise things like: item->update(); item->hide() (force is now true) + // won't work as expected. + if (force) + item->d_ptr->ignoreVisible = 1; + if (ignoreOpacity) + item->d_ptr->ignoreOpacity = 1; + } + return; + } + + const bool fullItemUpdate = rect.isNull(); + if (!fullItemUpdate && rect.isEmpty()) + return; + + if (!processDirtyItemsEmitted) { + QMetaMethod method = q_ptr->metaObject()->method(processDirtyItemsIndex); + method.invoke(q_ptr, Qt::QueuedConnection); +// QMetaObject::invokeMethod(q_ptr, "_q_processDirtyItems", Qt::QueuedConnection); + processDirtyItemsEmitted = true; + } + + if (removingItemFromScene) { + // Note that this function can be called from the item's destructor, so + // do NOT call any virtual functions on it within this block. + if (isSignalConnected(changedSignalIndex) || views.isEmpty()) { + // This block of code is kept for compatibility. Since 4.5, by default + // QGraphicsView does not connect the signal and we use the below + // method of delivering updates. + q_func()->update(); + return; + } + + for (int i = 0; i < views.size(); ++i) { + QGraphicsViewPrivate *viewPrivate = views.at(i)->d_func(); + QRect rect = item->d_ptr->paintedViewBoundingRects.value(viewPrivate->viewport); + rect.translate(viewPrivate->dirtyScrollOffset); + viewPrivate->updateRect(rect); + } + return; + } + + bool hasNoContents = item->d_ptr->flags & QGraphicsItem::ItemHasNoContents; + if (!hasNoContents) { + item->d_ptr->dirty = 1; + if (fullItemUpdate) + item->d_ptr->fullUpdatePending = 1; + else if (!item->d_ptr->fullUpdatePending) + item->d_ptr->needsRepaint |= rect; + } else if (item->d_ptr->graphicsEffect) { + invalidateChildren = true; + } + + if (invalidateChildren) { + item->d_ptr->allChildrenDirty = 1; + item->d_ptr->dirtyChildren = 1; + } + + if (force) + item->d_ptr->ignoreVisible = 1; + if (ignoreOpacity) + item->d_ptr->ignoreOpacity = 1; + + if (!updateBoundingRect) + item->d_ptr->markParentDirty(); +} + +static inline bool updateHelper(QGraphicsViewPrivate *view, QGraphicsItemPrivate *item, + const QRectF &rect, bool itemIsUntransformable) +{ + Q_ASSERT(view); + Q_ASSERT(item); + + QGraphicsItem *itemq = static_cast(item->q_ptr); + QGraphicsView *viewq = static_cast(view->q_ptr); + + if (itemIsUntransformable) { + const QTransform xform = itemq->deviceTransform(viewq->viewportTransform()); + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(xform.mapRect(rect)); + return view->updateRegion(rect, xform); + } + + if (item->sceneTransformTranslateOnly && view->identityMatrix) { + const qreal dx = item->sceneTransform.dx(); + const qreal dy = item->sceneTransform.dy(); + QRectF r(rect); + r.translate(dx - view->horizontalScroll(), dy - view->verticalScroll()); + return view->updateRectF(r); + } + + if (!viewq->isTransformed()) { + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(item->sceneTransform.mapRect(rect)); + return view->updateRegion(rect, item->sceneTransform); + } + + QTransform xform = item->sceneTransform; + xform *= viewq->viewportTransform(); + if (!item->hasBoundingRegionGranularity) + return view->updateRectF(xform.mapRect(rect)); + return view->updateRegion(rect, xform); +} + +void QGraphicsScenePrivate::processDirtyItemsRecursive(QGraphicsItem *item, bool dirtyAncestorContainsChildren, + qreal parentOpacity) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(item); + Q_ASSERT(!updateAll); + + if (!item->d_ptr->dirty && !item->d_ptr->dirtyChildren) { + resetDirtyItem(item); + return; + } + + const bool itemIsHidden = !item->d_ptr->ignoreVisible && !item->d_ptr->visible; + if (itemIsHidden) { + resetDirtyItem(item, /*recursive=*/true); + return; + } + + bool itemHasContents = !(item->d_ptr->flags & QGraphicsItem::ItemHasNoContents); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (!itemHasContents) { + if (!itemHasChildren) { + resetDirtyItem(item); + return; // Item has neither contents nor children!(?) + } + if (item->d_ptr->graphicsEffect) + itemHasContents = true; + } + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = !item->d_ptr->ignoreOpacity + && QGraphicsItemPrivate::isOpacityNull(opacity); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) { + resetDirtyItem(item, /*recursive=*/itemHasChildren); + return; + } + + bool wasDirtyParentSceneTransform = item->d_ptr->dirtySceneTransform; + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + if (wasDirtyParentSceneTransform && !itemIsUntransformable) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + } + + const bool wasDirtyParentViewBoundingRects = item->d_ptr->paintedViewBoundingRectsNeedRepaint; + if (itemIsFullyTransparent || !itemHasContents || dirtyAncestorContainsChildren) { + // Make sure we don't process invisible items or items with no content. + item->d_ptr->dirty = 0; + item->d_ptr->fullUpdatePending = 0; + // Might have a dirty view bounding rect otherwise. + if (itemIsFullyTransparent || !itemHasContents) + item->d_ptr->paintedViewBoundingRectsNeedRepaint = 0; + } + + if (!hasSceneRect && item->d_ptr->geometryChanged && item->d_ptr->visible) { + // Update growingItemsBoundingRect. + if (item->d_ptr->sceneTransformTranslateOnly) { + growingItemsBoundingRect |= item->boundingRect().translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy()); + } else { + growingItemsBoundingRect |= item->d_ptr->sceneTransform.mapRect(item->boundingRect()); + } + } + + // Process item. + if (item->d_ptr->dirty || item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + const bool useCompatUpdate = views.isEmpty() || isSignalConnected(changedSignalIndex); + const QRectF itemBoundingRect = adjustedItemEffectiveBoundingRect(item); + + if (useCompatUpdate && !itemIsUntransformable && qFuzzyIsNull(item->boundingRegionGranularity())) { + // This block of code is kept for compatibility. Since 4.5, by default + // QGraphicsView does not connect the signal and we use the below + // method of delivering updates. + if (item->d_ptr->sceneTransformTranslateOnly) { + q->update(itemBoundingRect.translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy())); + } else { + QRectF rect = item->d_ptr->sceneTransform.mapRect(itemBoundingRect); + if (!rect.isEmpty()) + q->update(rect); + } + } else { + QRectF dirtyRect; + bool uninitializedDirtyRect = true; + + for (int j = 0; j < views.size(); ++j) { + QGraphicsView *view = views.at(j); + QGraphicsViewPrivate *viewPrivate = view->d_func(); + QRect &paintedViewBoundingRect = item->d_ptr->paintedViewBoundingRects[viewPrivate->viewport]; + if (viewPrivate->fullUpdatePending + || viewPrivate->viewportUpdateMode == QGraphicsView::NoViewportUpdate) { + // Okay, if we have a full update pending or no viewport update, this item's + // paintedViewBoundingRect will be updated correctly in the next paintEvent if + // it is inside the viewport, but for now we can pretend that it is outside. + paintedViewBoundingRect = QRect(-1, -1, -1, -1); + continue; + } + + if (item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + paintedViewBoundingRect.translate(viewPrivate->dirtyScrollOffset); + if (!viewPrivate->updateRect(paintedViewBoundingRect)) + paintedViewBoundingRect = QRect(-1, -1, -1, -1); // Outside viewport. + } + + if (!item->d_ptr->dirty) + continue; + + if (!item->d_ptr->paintedViewBoundingRectsNeedRepaint + && paintedViewBoundingRect.x() == -1 && paintedViewBoundingRect.y() == -1 + && paintedViewBoundingRect.width() == -1 && paintedViewBoundingRect.height() == -1) { + continue; // Outside viewport. + } + + if (uninitializedDirtyRect) { + dirtyRect = itemBoundingRect; + if (!item->d_ptr->fullUpdatePending) { + _q_adjustRect(&item->d_ptr->needsRepaint); + dirtyRect &= item->d_ptr->needsRepaint; + } + uninitializedDirtyRect = false; + } + + if (dirtyRect.isEmpty()) + continue; // Discard updates outside the bounding rect. + + if (!updateHelper(viewPrivate, item->d_ptr.data(), dirtyRect, itemIsUntransformable) + && item->d_ptr->paintedViewBoundingRectsNeedRepaint) { + paintedViewBoundingRect = QRect(-1, -1, -1, -1); // Outside viewport. + } + } + } + } + + // Process children. + if (itemHasChildren && item->d_ptr->dirtyChildren) { + const bool itemClipsChildrenToShape = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; + // Items with no content are threated as 'dummy' items which means they are never drawn and + // 'processed', so the painted view bounding rect is never up-to-date. This means that whenever + // such an item changes geometry, its children have to take care of the update regardless + // of whether the item clips children to shape or not. + const bool bypassUpdateClip = !itemHasContents && wasDirtyParentViewBoundingRects; + if (itemClipsChildrenToShape && !bypassUpdateClip) { + // Make sure child updates are clipped to the item's bounding rect. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->setUpdateClip(item); + } + if (!dirtyAncestorContainsChildren) { + dirtyAncestorContainsChildren = item->d_ptr->fullUpdatePending + && itemClipsChildrenToShape; + } + const bool allChildrenDirty = item->d_ptr->allChildrenDirty; + const bool parentIgnoresVisible = item->d_ptr->ignoreVisible; + const bool parentIgnoresOpacity = item->d_ptr->ignoreOpacity; + for (int i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (wasDirtyParentViewBoundingRects) + child->d_ptr->paintedViewBoundingRectsNeedRepaint = 1; + if (parentIgnoresVisible) + child->d_ptr->ignoreVisible = 1; + if (parentIgnoresOpacity) + child->d_ptr->ignoreOpacity = 1; + if (allChildrenDirty) { + child->d_ptr->dirty = 1; + child->d_ptr->fullUpdatePending = 1; + child->d_ptr->dirtyChildren = 1; + child->d_ptr->allChildrenDirty = 1; + } + processDirtyItemsRecursive(child, dirtyAncestorContainsChildren, opacity); + } + + if (itemClipsChildrenToShape) { + // Reset updateClip. + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->setUpdateClip(0); + } + } else if (wasDirtyParentSceneTransform) { + item->d_ptr->invalidateChildrenSceneTransform(); + } + + resetDirtyItem(item); +} + +/*! + \obsolete + + Paints the given \a items using the provided \a painter, after the + background has been drawn, and before the foreground has been + drawn. All painting is done in \e scene coordinates. Before + drawing each item, the painter must be transformed using + QGraphicsItem::sceneTransform(). + + The \a options parameter is the list of style option objects for + each item in \a items. The \a numItems parameter is the number of + items in \a items and options in \a options. The \a widget + parameter is optional; if specified, it should point to the widget + that is being painted on. + + The default implementation prepares the painter matrix, and calls + QGraphicsItem::paint() on all items. Reimplement this function to + provide custom painting of all items for the scene; gaining + complete control over how each item is drawn. In some cases this + can increase drawing performance significantly. + + Example: + + \snippet doc/src/snippets/graphicssceneadditemsnippet.cpp 0 + + Since Qt 4.6, this function is not called anymore unless + the QGraphicsView::IndirectPainting flag is given as an Optimization + flag. + + \sa drawBackground(), drawForeground() +*/ +void QGraphicsScene::drawItems(QPainter *painter, + int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[], QWidget *widget) +{ + Q_D(QGraphicsScene); + // Make sure we don't have unpolished items before we draw. + if (!d->unpolishedItems.isEmpty()) + d->_q_polishItems(); + + const qreal opacity = painter->opacity(); + QTransform viewTransform = painter->worldTransform(); + Q_UNUSED(options); + + // Determine view, expose and flags. + QGraphicsView *view = widget ? qobject_cast(widget->parentWidget()) : 0; + QRegion *expose = 0; + const quint32 oldRectAdjust = d->rectAdjust; + if (view) { + d->updateAll = false; + expose = &view->d_func()->exposedRegion; + if (view->d_func()->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + d->rectAdjust = 1; + else + d->rectAdjust = 2; + } + + // Find all toplevels, they are already sorted. + QList topLevelItems; + for (int i = 0; i < numItems; ++i) { + QGraphicsItem *item = items[i]->topLevelItem(); + if (!item->d_ptr->itemDiscovered) { + topLevelItems << item; + item->d_ptr->itemDiscovered = 1; + d->drawSubtreeRecursive(item, painter, &viewTransform, expose, widget); + } + } + + d->rectAdjust = oldRectAdjust; + // Reset discovery bits. + for (int i = 0; i < topLevelItems.size(); ++i) + topLevelItems.at(i)->d_ptr->itemDiscovered = 0; + + painter->setWorldTransform(viewTransform); + painter->setOpacity(opacity); +} + +/*! + \since 4.4 + + Finds a new widget to give the keyboard focus to, as appropriate for Tab + and Shift+Tab, and returns true if it can find a new widget, or false if + it cannot. If \a next is true, this function searches forward; if \a next + is false, it searches backward. + + You can reimplement this function in a subclass of QGraphicsScene to + provide fine-grained control over how tab focus passes inside your + scene. The default implementation is based on the tab focus chain defined + by QGraphicsWidget::setTabOrder(). +*/ +bool QGraphicsScene::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsScene); + + QGraphicsItem *item = focusItem(); + if (item && !item->isWidget()) { + // Tab out of the scene. + return false; + } + if (!item) { + if (d->lastFocusItem && !d->lastFocusItem->isWidget()) { + // Restore focus to the last focusable non-widget item that had + // focus. + setFocusItem(d->lastFocusItem, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + } + if (!d->tabFocusFirst) { + // No widgets... + return false; + } + + // The item must be a widget. + QGraphicsWidget *widget = 0; + if (!item) { + widget = next ? d->tabFocusFirst : d->tabFocusFirst->d_func()->focusPrev; + } else { + QGraphicsWidget *test = static_cast(item); + widget = next ? test->d_func()->focusNext : test->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } + QGraphicsWidget *widgetThatHadFocus = widget; + + // Run around the focus chain until we find a widget that can take tab focus. + do { + if (widget->flags() & QGraphicsItem::ItemIsFocusable + && widget->isEnabled() && widget->isVisibleTo(0) + && (widget->focusPolicy() & Qt::TabFocus) + && (!item || !item->isPanel() || item->isAncestorOf(widget)) + ) { + setFocusItem(widget, next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; + } + widget = next ? widget->d_func()->focusNext : widget->d_func()->focusPrev; + if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev)) + return false; + } while (widget != widgetThatHadFocus); + + return false; +} + +/*! + \fn QGraphicsScene::changed(const QList ®ion) + + This signal is emitted by QGraphicsScene when control reaches the + event loop, if the scene content changes. The \a region parameter + contains a list of scene rectangles that indicate the area that + has been changed. + + \sa QGraphicsView::updateScene() +*/ + +/*! + \fn QGraphicsScene::sceneRectChanged(const QRectF &rect) + + This signal is emitted by QGraphicsScene whenever the scene rect changes. + The \a rect parameter is the new scene rectangle. + + \sa QGraphicsView::updateSceneRect() +*/ + +/*! + \fn QGraphicsScene::selectionChanged() + \since 4.3 + + This signal is emitted by QGraphicsScene whenever the selection + changes. You can call selectedItems() to get the new list of selected + items. + + The selection changes whenever an item is selected or unselected, a + selection area is set, cleared or otherwise changed, if a preselected item + is added to the scene, or if a selected item is removed from the scene. + + QGraphicsScene emits this signal only once for group selection operations. + For example, if you set a selection area, select or unselect a + QGraphicsItemGroup, or if you add or remove from the scene a parent item + that contains several selected items, selectionChanged() is emitted only + once after the operation has completed (instead of once for each item). + + \sa setSelectionArea(), selectedItems(), QGraphicsItem::setSelected() +*/ + +/*! + \since 4.4 + + Returns the scene's style, or the same as QApplication::style() if the + scene has not been explicitly assigned a style. + + \sa setStyle() +*/ +QStyle *QGraphicsScene::style() const +{ + Q_D(const QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + return d->style ? d->style : QApplication::style(); +} + +/*! + \since 4.4 + + Sets or replaces the style of the scene to \a style, and reparents the + style to this scene. Any previously assigned style is deleted. The scene's + style defaults to QApplication::style(), and serves as the default for all + QGraphicsWidget items in the scene. + + Changing the style, either directly by calling this function, or + indirectly by calling QApplication::setStyle(), will automatically update + the style for all widgets in the scene that do not have a style explicitly + assigned to them. + + If \a style is 0, QGraphicsScene will revert to QApplication::style(). + + \sa style() +*/ +void QGraphicsScene::setStyle(QStyle *style) +{ + Q_D(QGraphicsScene); + // ### This function, and the use of styles in general, is non-reentrant. + if (style == d->style) + return; + + // Delete the old style, + delete d->style; + if ((d->style = style)) + d->style->setParent(this); + + // Notify the scene. + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(this, &event); + + // Notify all widgets that don't have a style explicitly set. + foreach (QGraphicsItem *item, items()) { + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (!widget->testAttribute(Qt::WA_SetStyle)) + QApplication::sendEvent(widget, &event); + } + } +} + +/*! + \property QGraphicsScene::font + \since 4.4 + \brief the scene's default font + + This property provides the scene's font. The scene font defaults to, + and resolves all its entries from, QApplication::font. + + If the scene's font changes, either directly through setFont() or + indirectly when the application font changes, QGraphicsScene first + sends itself a \l{QEvent::FontChange}{FontChange} event, and it then + sends \l{QEvent::FontChange}{FontChange} events to all top-level + widget items in the scene. These items respond by resolving their own + fonts to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their fonts. + + Changing the scene font, (directly or indirectly through + QApplication::setFont(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::font, QApplication::setFont(), palette, style() +*/ +QFont QGraphicsScene::font() const +{ + Q_D(const QGraphicsScene); + return d->font; +} +void QGraphicsScene::setFont(const QFont &font) +{ + Q_D(QGraphicsScene); + QFont naturalFont = QApplication::font(); + naturalFont.resolve(0); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/*! + \property QGraphicsScene::palette + \since 4.4 + \brief the scene's default palette + + This property provides the scene's palette. The scene palette defaults to, + and resolves all its entries from, QApplication::palette. + + If the scene's palette changes, either directly through setPalette() or + indirectly when the application palette changes, QGraphicsScene first + sends itself a \l{QEvent::PaletteChange}{PaletteChange} event, and it then + sends \l{QEvent::PaletteChange}{PaletteChange} events to all top-level + widget items in the scene. These items respond by resolving their own + palettes to the scene, and they then notify their children, who again + notify their children, and so on, until all widget items have updated + their palettes. + + Changing the scene palette, (directly or indirectly through + QApplication::setPalette(),) automatically schedules a redraw the entire + scene. + + \sa QWidget::palette, QApplication::setPalette(), font, style() +*/ +QPalette QGraphicsScene::palette() const +{ + Q_D(const QGraphicsScene); + return d->palette; +} +void QGraphicsScene::setPalette(const QPalette &palette) +{ + Q_D(QGraphicsScene); + QPalette naturalPalette = QApplication::palette(); + naturalPalette.resolve(0); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \since 4.6 + + Returns true if the scene is active (e.g., it's viewed by + at least one QGraphicsView that is active); otherwise returns false. + + \sa QGraphicsItem::isActive(), QWidget::isActiveWindow() +*/ +bool QGraphicsScene::isActive() const +{ + Q_D(const QGraphicsScene); + return d->activationRefCount > 0; +} + +/*! + \since 4.6 + Returns the current active panel, or 0 if no panel is currently active. + + \sa QGraphicsScene::setActivePanel() +*/ +QGraphicsItem *QGraphicsScene::activePanel() const +{ + Q_D(const QGraphicsScene); + return d->activePanel; +} + +/*! + \since 4.6 + Activates \a item, which must be an item in this scene. You + can also pass 0 for \a item, in which case QGraphicsScene will + deactivate any currently active panel. + + If the scene is currently inactive, \a item remains inactive until the + scene becomes active (or, ir \a item is 0, no item will be activated). + + \sa activePanel(), isActive(), QGraphicsItem::isActive() +*/ +void QGraphicsScene::setActivePanel(QGraphicsItem *item) +{ + Q_D(QGraphicsScene); + d->setActivePanelHelper(item, false); +} + +/*! + \since 4.4 + + Returns the current active window, or 0 if no window is currently + active. + + \sa QGraphicsScene::setActiveWindow() +*/ +QGraphicsWidget *QGraphicsScene::activeWindow() const +{ + Q_D(const QGraphicsScene); + if (d->activePanel && d->activePanel->isWindow()) + return static_cast(d->activePanel); + return 0; +} + +/*! + \since 4.4 + Activates \a widget, which must be a widget in this scene. You can also + pass 0 for \a widget, in which case QGraphicsScene will deactivate any + currently active window. + + \sa activeWindow(), QGraphicsWidget::isActiveWindow() +*/ +void QGraphicsScene::setActiveWindow(QGraphicsWidget *widget) +{ + if (widget && widget->scene() != this) { + qWarning("QGraphicsScene::setActiveWindow: widget %p must be part of this scene", + widget); + return; + } + + // Activate the widget's panel (all windows are panels). + QGraphicsItem *panel = widget ? widget->panel() : 0; + setActivePanel(panel); + + // Raise + if (panel) { + QList siblingWindows; + QGraphicsItem *parent = panel->parentItem(); + // Raise ### inefficient for toplevels + foreach (QGraphicsItem *sibling, parent ? parent->children() : items()) { + if (sibling != panel && sibling->isWindow()) + siblingWindows << sibling; + } + + // Find the highest z value. + qreal z = panel->zValue(); + for (int i = 0; i < siblingWindows.size(); ++i) + z = qMax(z, siblingWindows.at(i)->zValue()); + + // This will probably never overflow. + const qreal litt = qreal(0.001); + panel->setZValue(z + litt); + } +} + +/*! + \since 4.6 + + Sends event \a event to item \a item through possible event filters. + + The event is sent only if the item is enabled. + + Returns \c false if the event was filtered or if the item is disabled. + Otherwise returns the value that was returned from the event handler. + + \sa QGraphicsItem::sceneEvent(), QGraphicsItem::sceneEventFilter() +*/ +bool QGraphicsScene::sendEvent(QGraphicsItem *item, QEvent *event) +{ + Q_D(QGraphicsScene); + if (!item) { + qWarning("QGraphicsScene::sendEvent: cannot send event to a null item"); + return false; + } + if (item->scene() != this) { + qWarning("QGraphicsScene::sendEvent: item %p's scene (%p)" + " is different from this scene (%p)", + item, item->scene(), this); + return false; + } + return d->sendEvent(item, event); +} + +void QGraphicsScenePrivate::addView(QGraphicsView *view) +{ + views << view; +#ifndef QT_NO_GESTURES + foreach (Qt::GestureType gesture, grabbedGestures.keys()) + view->viewport()->grabGesture(gesture); +#endif +} + +void QGraphicsScenePrivate::removeView(QGraphicsView *view) +{ + views.removeAll(view); +} + +void QGraphicsScenePrivate::updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent) +{ + QList touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + touchPoint.setRect(item->mapFromScene(touchPoint.sceneRect()).boundingRect()); + touchPoint.setStartPos(item->d_ptr->genericMapFromScene(touchPoint.startScenePos(), touchEvent->widget())); + touchPoint.setLastPos(item->d_ptr->genericMapFromScene(touchPoint.lastScenePos(), touchEvent->widget())); + } + touchEvent->setTouchPoints(touchPoints); +} + +int QGraphicsScenePrivate::findClosestTouchPointId(const QPointF &scenePos) +{ + int closestTouchPointId = -1; + qreal closestDistance = qreal(0.); + foreach (const QTouchEvent::TouchPoint &touchPoint, sceneCurrentTouchPoints) { + qreal distance = QLineF(scenePos, touchPoint.scenePos()).length(); + if (closestTouchPointId == -1|| distance < closestDistance) { + closestTouchPointId = touchPoint.id(); + closestDistance = distance; + } + } + return closestTouchPointId; +} + +void QGraphicsScenePrivate::touchEventHandler(QTouchEvent *sceneTouchEvent) +{ + typedef QPair > StatesAndTouchPoints; + QHash itemsNeedingEvents; + + for (int i = 0; i < sceneTouchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = sceneTouchEvent->touchPoints().at(i); + + // update state + QGraphicsItem *item = 0; + if (touchPoint.state() == Qt::TouchPointPressed) { + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchPad) { + // on touch-pad devices, send all touch points to the same item + item = itemForTouchPointId.isEmpty() + ? 0 + : itemForTouchPointId.constBegin().value(); + } + + if (!item) { + // determine which item this touch point will go to + cachedItemsUnderMouse = itemsAtPosition(touchPoint.screenPos().toPoint(), + touchPoint.scenePos(), + sceneTouchEvent->widget()); + item = cachedItemsUnderMouse.isEmpty() ? 0 : cachedItemsUnderMouse.first(); + } + + if (sceneTouchEvent->deviceType() == QTouchEvent::TouchScreen) { + // on touch-screens, combine this touch point with the closest one we find + int closestTouchPointId = findClosestTouchPointId(touchPoint.scenePos()); + QGraphicsItem *closestItem = itemForTouchPointId.value(closestTouchPointId); + if (!item || (closestItem && cachedItemsUnderMouse.contains(closestItem))) + item = closestItem; + } + if (!item) + continue; + + itemForTouchPointId.insert(touchPoint.id(), item); + sceneCurrentTouchPoints.insert(touchPoint.id(), touchPoint); + } else if (touchPoint.state() == Qt::TouchPointReleased) { + item = itemForTouchPointId.take(touchPoint.id()); + if (!item) + continue; + + sceneCurrentTouchPoints.remove(touchPoint.id()); + } else { + item = itemForTouchPointId.value(touchPoint.id()); + if (!item) + continue; + Q_ASSERT(sceneCurrentTouchPoints.contains(touchPoint.id())); + sceneCurrentTouchPoints[touchPoint.id()] = touchPoint; + } + + StatesAndTouchPoints &statesAndTouchPoints = itemsNeedingEvents[item]; + statesAndTouchPoints.first |= touchPoint.state(); + statesAndTouchPoints.second.append(touchPoint); + } + + if (itemsNeedingEvents.isEmpty()) { + sceneTouchEvent->accept(); + return; + } + + bool ignoreSceneTouchEvent = true; + QHash::ConstIterator it = itemsNeedingEvents.constBegin(); + const QHash::ConstIterator end = itemsNeedingEvents.constEnd(); + for (; it != end; ++it) { + QGraphicsItem *item = it.key(); + + (void) item->isBlockedByModalPanel(&item); + + // determine event type from the state mask + QEvent::Type eventType; + switch (it.value().first) { + case Qt::TouchPointPressed: + // all touch points have pressed state + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + // all touch points have released state + eventType = QEvent::TouchEnd; + break; + case Qt::TouchPointStationary: + // don't send the event if nothing changed + continue; + default: + // all other combinations + eventType = QEvent::TouchUpdate; + break; + } + + QTouchEvent touchEvent(eventType); + touchEvent.setWidget(sceneTouchEvent->widget()); + touchEvent.setDeviceType(sceneTouchEvent->deviceType()); + touchEvent.setModifiers(sceneTouchEvent->modifiers()); + touchEvent.setTouchPointStates(it.value().first); + touchEvent.setTouchPoints(it.value().second); + + switch (touchEvent.type()) { + case QEvent::TouchBegin: + { + // if the TouchBegin handler recurses, we assume that means the event + // has been implicitly accepted and continue to send touch events + item->d_ptr->acceptedTouchBeginEvent = true; + bool res = sendTouchBeginEvent(item, &touchEvent) + && touchEvent.isAccepted(); + if (!res) { + // forget about these touch points, we didn't handle them + for (int i = 0; i < touchEvent.touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = touchEvent.touchPoints().at(i); + itemForTouchPointId.remove(touchPoint.id()); + sceneCurrentTouchPoints.remove(touchPoint.id()); + } + ignoreSceneTouchEvent = false; + } + break; + } + default: + if (item->d_ptr->acceptedTouchBeginEvent) { + updateTouchPointsForItem(item, &touchEvent); + (void) sendEvent(item, &touchEvent); + ignoreSceneTouchEvent = false; + } + break; + } + } + sceneTouchEvent->setAccepted(ignoreSceneTouchEvent); +} + +bool QGraphicsScenePrivate::sendTouchBeginEvent(QGraphicsItem *origin, QTouchEvent *touchEvent) +{ + Q_Q(QGraphicsScene); + + if (cachedItemsUnderMouse.isEmpty() || cachedItemsUnderMouse.first() != origin) { + const QTouchEvent::TouchPoint &firstTouchPoint = touchEvent->touchPoints().first(); + cachedItemsUnderMouse = itemsAtPosition(firstTouchPoint.screenPos().toPoint(), + firstTouchPoint.scenePos(), + touchEvent->widget()); + } + Q_ASSERT(cachedItemsUnderMouse.first() == origin); + + // Set focus on the topmost enabled item that can take focus. + bool setFocus = false; + + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { + if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) { + setFocus = true; + if (item != q->focusItem()) + q->setFocusItem(item, Qt::MouseFocusReason); + break; + } + } + if (item->isPanel()) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation) + break; + if (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling) { + // Make sure we don't clear focus. + setFocus = true; + break; + } + } + + // If nobody could take focus, clear it. + if (!stickyFocus && !setFocus) + q->setFocusItem(0, Qt::MouseFocusReason); + + bool res = false; + bool eventAccepted = touchEvent->isAccepted(); + foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + // first, try to deliver the touch event + updateTouchPointsForItem(item, touchEvent); + bool acceptTouchEvents = item->acceptTouchEvents(); + touchEvent->setAccepted(acceptTouchEvents); + res = acceptTouchEvents && sendEvent(item, touchEvent); + eventAccepted = touchEvent->isAccepted(); + if (itemForTouchPointId.value(touchEvent->touchPoints().first().id()) == 0) { + // item was deleted + item = 0; + } else { + item->d_ptr->acceptedTouchBeginEvent = (res && eventAccepted); + } + touchEvent->spont = false; + if (res && eventAccepted) { + // the first item to accept the TouchBegin gets an implicit grab. + for (int i = 0; i < touchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = touchEvent->touchPoints().at(i); + itemForTouchPointId[touchPoint.id()] = item; // can be zero + } + break; + } + if (item && item->isPanel()) + break; + } + + touchEvent->setAccepted(eventAccepted); + return res; +} + +void QGraphicsScenePrivate::enableTouchEventsOnViews() +{ + foreach (QGraphicsView *view, views) + view->viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true); +} + +void QGraphicsScenePrivate::updateInputMethodSensitivityInViews() +{ + for (int i = 0; i < views.size(); ++i) + views.at(i)->d_func()->updateInputMethodSensitivity(); +} + +void QGraphicsScenePrivate::enterModal(QGraphicsItem *panel, QGraphicsItem::PanelModality previousModality) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QGraphicsItem::PanelModality panelModality = panel->d_ptr->panelModality; + if (previousModality != QGraphicsItem::NonModal) { + // the panel is changing from one modality type to another... temporarily set it back so + // that blockedPanels is populated correctly + panel->d_ptr->panelModality = previousModality; + } + + QSet blockedPanels; + QList items = q->items(); // ### store panels separately + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + // blockedPanels contains all currently blocked panels + + if (previousModality != QGraphicsItem::NonModal) { + // reset the modality to the proper value, since we changed it above + panel->d_ptr->panelModality = panelModality; + // remove this panel so that it will be reinserted at the front of the stack + modalPanels.removeAll(panel); + } + + modalPanels.prepend(panel); + + if (!hoverItems.isEmpty()) { + // send GraphicsSceneHoverLeave events to newly blocked hoverItems + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); + } + + if (!mouseGrabberItems.isEmpty() && lastMouseGrabberItemHasImplicitMouseGrab) { + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + ungrabMouse(item, /*itemIsDying =*/ false); + } + + QEvent windowBlockedEvent(QEvent::WindowBlocked); + QEvent windowUnblockedEvent(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel()) { + if (!blockedPanels.contains(item) && item->isBlockedByModalPanel()) { + // send QEvent::WindowBlocked to newly blocked panels + sendEvent(item, &windowBlockedEvent); + } else if (blockedPanels.contains(item) && !item->isBlockedByModalPanel()) { + // send QEvent::WindowUnblocked to unblocked panels when downgrading + // a panel from SceneModal to PanelModal + sendEvent(item, &windowUnblockedEvent); + } + } + } +} + +void QGraphicsScenePrivate::leaveModal(QGraphicsItem *panel) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QSet blockedPanels; + QList items = q->items(); // ### same as above + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + + modalPanels.removeAll(panel); + + QEvent e(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && blockedPanels.contains(item) && !item->isBlockedByModalPanel()) + sendEvent(item, &e); + } + + // send GraphicsSceneHoverEnter events to newly unblocked items + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); +} + +#ifndef QT_NO_GESTURES +void QGraphicsScenePrivate::gestureTargetsAtHotSpots(const QSet &gestures, + Qt::GestureFlag flag, + QHash > *targets, + QSet *itemsSet, + QSet *normal, + QSet *conflicts) +{ + QSet normalGestures; // that are not in conflicted state. + foreach (QGesture *gesture, gestures) { + if (!gesture->hasHotSpot()) + continue; + const Qt::GestureType gestureType = gesture->gestureType(); + QList items = itemsAtPosition(QPoint(), gesture->d_func()->sceneHotSpot, 0); + for (int j = 0; j < items.size(); ++j) { + QGraphicsItem *item = items.at(j); + + // Check if the item is blocked by a modal panel and use it as + // a target instead of this item. + (void) item->isBlockedByModalPanel(&item); + + if (QGraphicsObject *itemobj = item->toGraphicsObject()) { + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + QMap::const_iterator it = + d->gestureContext.find(gestureType); + if (it != d->gestureContext.end() && (!flag || (it.value() & flag))) { + if (normalGestures.contains(gesture)) { + normalGestures.remove(gesture); + if (conflicts) + conflicts->insert(gesture); + } else { + normalGestures.insert(gesture); + } + if (targets) + (*targets)[itemobj].insert(gesture); + if (itemsSet) + (*itemsSet).insert(itemobj); + } + } + // Don't propagate through panels. + if (item->isPanel()) + break; + } + } + if (normal) + *normal = normalGestures; +} + +void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) +{ + QWidget *viewport = event->widget(); + if (!viewport) + return; + QGraphicsView *graphicsView = qobject_cast(viewport->parent()); + if (!graphicsView) + return; + + QList allGestures = event->gestures(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Gestures:" << allGestures; + + QSet startedGestures; + QPoint delta = viewport->mapFromGlobal(QPoint()); + QTransform toScene = QTransform::fromTranslate(delta.x(), delta.y()) + * graphicsView->viewportTransform().inverted(); + foreach (QGesture *gesture, allGestures) { + // cache scene coordinates of the hot spot + if (gesture->hasHotSpot()) { + gesture->d_func()->sceneHotSpot = toScene.map(gesture->hotSpot()); + } else { + gesture->d_func()->sceneHotSpot = QPointF(); + } + + QGraphicsObject *target = gestureTargets.value(gesture, 0); + if (!target) { + // when we are not in started mode but don't have a target + // then the only one interested in gesture is the view/scene + if (gesture->state() == Qt::GestureStarted) + startedGestures.insert(gesture); + } + } + + if (!startedGestures.isEmpty()) { + QSet normalGestures; // that have just one target + QSet conflictedGestures; // that have multiple possible targets + gestureTargetsAtHotSpots(startedGestures, Qt::GestureFlag(0), &cachedItemGestures, 0, + &normalGestures, &conflictedGestures); + cachedTargetItems = cachedItemGestures.keys(); + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Normal gestures:" << normalGestures + << "Conflicting gestures:" << conflictedGestures; + + // deliver conflicted gestures as override events AND remember + // initial gesture targets + if (!conflictedGestures.isEmpty()) { + for (int i = 0; i < cachedTargetItems.size(); ++i) { + QWeakPointer item = cachedTargetItems.at(i); + + // get gestures to deliver to the current item + QSet gestures = conflictedGestures & cachedItemGestures.value(item.data()); + if (gestures.isEmpty()) + continue; + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering override to" + << item.data() << gestures; + // send gesture override + QGestureEvent ev(gestures.toList()); + ev.t = QEvent::GestureOverride; + ev.setWidget(event->widget()); + // mark event and individual gestures as ignored + ev.ignore(); + foreach(QGesture *g, gestures) + ev.setAccepted(g, false); + sendEvent(item.data(), &ev); + // mark all accepted gestures to deliver them as normal gesture events + foreach (QGesture *g, gestures) { + if (ev.isAccepted() || ev.isAccepted(g)) { + conflictedGestures.remove(g); + // mark the item as a gesture target + if (item) { + gestureTargets.insert(g, item.data()); + QHash >::iterator it, e; + it = cachedItemGestures.begin(); + e = cachedItemGestures.end(); + for(; it != e; ++it) + it.value().remove(g); + cachedItemGestures[item.data()].insert(g); + } + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "override was accepted:" + << g << item.data(); + } + // remember the first item that received the override event + // as it most likely become a target if no one else accepts + // the override event + if (!gestureTargets.contains(g) && item) + gestureTargets.insert(g, item.data()); + + } + if (conflictedGestures.isEmpty()) + break; + } + } + // remember the initial target item for each gesture that was not in + // the conflicted state. + if (!normalGestures.isEmpty()) { + for (int i = 0; i < cachedTargetItems.size() && !normalGestures.isEmpty(); ++i) { + QGraphicsObject *item = cachedTargetItems.at(i); + + // get gestures to deliver to the current item + foreach (QGesture *g, cachedItemGestures.value(item)) { + if (!gestureTargets.contains(g)) { + gestureTargets.insert(g, item); + normalGestures.remove(g); + } + } + } + } + } + + + // deliver all gesture events + QSet undeliveredGestures; + QSet parentPropagatedGestures; + foreach (QGesture *gesture, allGestures) { + if (QGraphicsObject *target = gestureTargets.value(gesture, 0)) { + cachedItemGestures[target].insert(gesture); + cachedTargetItems.append(target); + undeliveredGestures.insert(gesture); + QGraphicsItemPrivate *d = target->QGraphicsItem::d_func(); + const Qt::GestureFlags flags = d->gestureContext.value(gesture->gestureType()); + if (flags & Qt::IgnoredGesturesPropagateToParent) + parentPropagatedGestures.insert(gesture); + } else { + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "no target for" << gesture << "at" + << gesture->hotSpot() << gesture->d_func()->sceneHotSpot; + } + } + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + for (int i = 0; i < cachedTargetItems.size(); ++i) { + QWeakPointer receiver = cachedTargetItems.at(i); + QSet gestures = + undeliveredGestures & cachedItemGestures.value(receiver.data()); + gestures -= cachedAlreadyDeliveredGestures.value(receiver.data()); + + if (gestures.isEmpty()) + continue; + + cachedAlreadyDeliveredGestures[receiver.data()] += gestures; + const bool isPanel = receiver.data()->isPanel(); + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering to" + << receiver.data() << gestures; + QGestureEvent ev(gestures.toList()); + ev.setWidget(event->widget()); + sendEvent(receiver.data(), &ev); + QSet ignoredGestures; + foreach (QGesture *g, gestures) { + if (!ev.isAccepted() && !ev.isAccepted(g)) { + // if the gesture was ignored by its target, we will update the + // targetItems list with a possible target items (items that + // want to receive partial gestures). + // ### wont' work if the target was destroyed in the event + // we will just stop delivering it. + if (receiver && receiver.data() == gestureTargets.value(g, 0)) + ignoredGestures.insert(g); + } else { + if (receiver && g->state() == Qt::GestureStarted) { + // someone accepted the propagated initial GestureStarted + // event, let it be the new target for all following events. + gestureTargets[g] = receiver.data(); + } + undeliveredGestures.remove(g); + } + } + if (undeliveredGestures.isEmpty()) + break; + + // ignoredGestures list is only filled when delivering to the gesture + // target item, so it is safe to assume item == target. + if (!ignoredGestures.isEmpty() && !isPanel) { + // look for new potential targets for gestures that were ignored + // and should be propagated. + + QSet targetsSet = cachedTargetItems.toSet(); + + if (receiver) { + // first if the gesture should be propagated to parents only + for (QSet::iterator it = ignoredGestures.begin(); + it != ignoredGestures.end();) { + if (parentPropagatedGestures.contains(*it)) { + QGesture *gesture = *it; + const Qt::GestureType gestureType = gesture->gestureType(); + QGraphicsItem *item = receiver.data(); + while (item) { + if (QGraphicsObject *obj = item->toGraphicsObject()) { + if (item->d_func()->gestureContext.contains(gestureType)) { + targetsSet.insert(obj); + cachedItemGestures[obj].insert(gesture); + } + } + if (item->isPanel()) + break; + item = item->parentItem(); + } + + it = ignoredGestures.erase(it); + continue; + } + ++it; + } + } + + gestureTargetsAtHotSpots(ignoredGestures, Qt::ReceivePartialGestures, + &cachedItemGestures, &targetsSet, 0, 0); + + cachedTargetItems = targetsSet.toList(); + qSort(cachedTargetItems.begin(), cachedTargetItems.end(), qt_closestItemFirst); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "new targets:" << cachedTargetItems; + i = -1; // start delivery again + continue; + } + } + + foreach (QGesture *g, startedGestures) { + if (g->gestureCancelPolicy() == QGesture::CancelAllInContext) { + DEBUG() << "lets try to cancel some"; + // find gestures in context in Qt::GestureStarted or Qt::GestureUpdated state and cancel them + cancelGesturesForChildren(g); + } + } + + // forget about targets for gestures that have ended + foreach (QGesture *g, allGestures) { + switch (g->state()) { + case Qt::GestureFinished: + case Qt::GestureCanceled: + gestureTargets.remove(g); + break; + default: + break; + } + } + + cachedTargetItems.clear(); + cachedItemGestures.clear(); + cachedAlreadyDeliveredGestures.clear(); +} + +void QGraphicsScenePrivate::cancelGesturesForChildren(QGesture *original) +{ + Q_ASSERT(original); + QGraphicsItem *originalItem = gestureTargets.value(original); + if (originalItem == 0) // we only act on accepted gestures, which implies it has a target. + return; + + // iterate over all active gestures and for each find the owner + // if the owner is part of our sub-hierarchy, cancel it. + + QSet canceledGestures; + QHash::Iterator iter = gestureTargets.begin(); + while (iter != gestureTargets.end()) { + QGraphicsObject *item = iter.value(); + // note that we don't touch the gestures for our originalItem + if (item != originalItem && originalItem->isAncestorOf(item)) { + DEBUG() << " found a gesture to cancel" << iter.key(); + iter.key()->d_func()->state = Qt::GestureCanceled; + canceledGestures << iter.key(); + } + ++iter; + } + + // sort them per target item by cherry picking from almostCanceledGestures and delivering + QSet almostCanceledGestures = canceledGestures; + QSet::Iterator setIter; + while (!almostCanceledGestures.isEmpty()) { + QGraphicsObject *target = 0; + QSet gestures; + setIter = almostCanceledGestures.begin(); + // sort per target item + while (setIter != almostCanceledGestures.end()) { + QGraphicsObject *item = gestureTargets.value(*setIter); + if (target == 0) + target = item; + if (target == item) { + gestures << *setIter; + setIter = almostCanceledGestures.erase(setIter); + } else { + ++setIter; + } + } + Q_ASSERT(target); + + QList list = gestures.toList(); + QGestureEvent ev(list); + sendEvent(target, &ev); + + foreach (QGesture *g, list) { + if (ev.isAccepted() || ev.isAccepted(g)) + gestures.remove(g); + } + + foreach (QGesture *g, gestures) { + if (!g->hasHotSpot()) + continue; + + QList items = itemsAtPosition(QPoint(), g->d_func()->sceneHotSpot, 0); + for (int j = 0; j < items.size(); ++j) { + QGraphicsObject *item = items.at(j)->toGraphicsObject(); + if (!item) + continue; + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + if (d->gestureContext.contains(g->gestureType())) { + QList list; + list << g; + QGestureEvent ev(list); + sendEvent(item, &ev); + if (ev.isAccepted() || ev.isAccepted(g)) + break; // successfully delivered + } + } + } + } + + QGestureManager *gestureManager = QApplicationPrivate::instance()->gestureManager; + Q_ASSERT(gestureManager); // it would be very odd if we got called without a manager. + for (setIter = canceledGestures.begin(); setIter != canceledGestures.end(); ++setIter) { + gestureManager->recycle(*setIter); + gestureTargets.remove(*setIter); + } +} + +void QGraphicsScenePrivate::grabGesture(QGraphicsItem *, Qt::GestureType gesture) +{ + (void)QGestureManager::instance(); // create a gesture manager + if (!grabbedGestures[gesture]++) { + foreach (QGraphicsView *view, views) + view->viewport()->grabGesture(gesture); + } +} + +void QGraphicsScenePrivate::ungrabGesture(QGraphicsItem *item, Qt::GestureType gesture) +{ + // we know this can only be an object + Q_ASSERT(item->d_ptr->isObject); + QGraphicsObject *obj = static_cast(item); + QGestureManager::instance()->cleanupCachedGestures(obj, gesture); + if (!--grabbedGestures[gesture]) { + foreach (QGraphicsView *view, views) + view->viewport()->ungrabGesture(gesture); + } +} +#endif // QT_NO_GESTURES + +QT_END_NAMESPACE + +#include "moc_qgraphicsscene.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsscene.h b/src/gui/graphicsview/qgraphicsscene.h new file mode 100644 index 0000000000..c17ab96074 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscene.h @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENE_H +#define QGRAPHICSSCENE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +template class QList; +class QFocusEvent; +class QFont; +class QFontMetrics; +class QGraphicsEllipseItem; +class QGraphicsItem; +class QGraphicsItemGroup; +class QGraphicsLineItem; +class QGraphicsPathItem; +class QGraphicsPixmapItem; +class QGraphicsPolygonItem; +class QGraphicsProxyWidget; +class QGraphicsRectItem; +class QGraphicsSceneContextMenuEvent; +class QGraphicsSceneDragDropEvent; +class QGraphicsSceneEvent; +class QGraphicsSceneHelpEvent; +class QGraphicsSceneHoverEvent; +class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; +class QGraphicsSimpleTextItem; +class QGraphicsTextItem; +class QGraphicsView; +class QGraphicsWidget; +class QGraphicsSceneIndex; +class QHelpEvent; +class QInputMethodEvent; +class QKeyEvent; +class QLineF; +class QPainterPath; +class QPixmap; +class QPointF; +class QPolygonF; +class QRectF; +class QSizeF; +class QStyle; +class QStyleOptionGraphicsItem; + +class QGraphicsScenePrivate; +class Q_GUI_EXPORT QGraphicsScene : public QObject +{ + Q_OBJECT + Q_PROPERTY(QBrush backgroundBrush READ backgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush foregroundBrush READ foregroundBrush WRITE setForegroundBrush) + Q_PROPERTY(ItemIndexMethod itemIndexMethod READ itemIndexMethod WRITE setItemIndexMethod) + Q_PROPERTY(QRectF sceneRect READ sceneRect WRITE setSceneRect) + Q_PROPERTY(int bspTreeDepth READ bspTreeDepth WRITE setBspTreeDepth) + Q_PROPERTY(QPalette palette READ palette WRITE setPalette) + Q_PROPERTY(QFont font READ font WRITE setFont) + Q_PROPERTY(bool sortCacheEnabled READ isSortCacheEnabled WRITE setSortCacheEnabled) + Q_PROPERTY(bool stickyFocus READ stickyFocus WRITE setStickyFocus) + +public: + enum ItemIndexMethod { + BspTreeIndex, + NoIndex = -1 + }; + + enum SceneLayer { + ItemLayer = 0x1, + BackgroundLayer = 0x2, + ForegroundLayer = 0x4, + AllLayers = 0xffff + }; + Q_DECLARE_FLAGS(SceneLayers, SceneLayer) + + QGraphicsScene(QObject *parent = 0); + QGraphicsScene(const QRectF &sceneRect, QObject *parent = 0); + QGraphicsScene(qreal x, qreal y, qreal width, qreal height, QObject *parent = 0); + virtual ~QGraphicsScene(); + + QRectF sceneRect() const; + inline qreal width() const { return sceneRect().width(); } + inline qreal height() const { return sceneRect().height(); } + void setSceneRect(const QRectF &rect); + inline void setSceneRect(qreal x, qreal y, qreal w, qreal h) + { setSceneRect(QRectF(x, y, w, h)); } + + void render(QPainter *painter, + const QRectF &target = QRectF(), const QRectF &source = QRectF(), + Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); + + ItemIndexMethod itemIndexMethod() const; + void setItemIndexMethod(ItemIndexMethod method); + + bool isSortCacheEnabled() const; + void setSortCacheEnabled(bool enabled); + + int bspTreeDepth() const; + void setBspTreeDepth(int depth); + + QRectF itemsBoundingRect() const; + + QList items() const; + QList items(Qt::SortOrder order) const; // ### Qt 5: unify + + QList items(const QPointF &pos, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QRectF &rect, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + + QList items(const QPointF &pos) const; // ### obsolete + QList items(const QRectF &rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; // ### obsolete + + QList collidingItems(const QGraphicsItem *item, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + + QGraphicsItem *itemAt(const QPointF &pos) const; // ### obsolete + QGraphicsItem *itemAt(const QPointF &pos, const QTransform &deviceTransform) const; + + inline QList items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const + { return items(QRectF(x, y, w, h), mode); } // ### obsolete + inline QList items(qreal x, qreal y, qreal w, qreal h, Qt::ItemSelectionMode mode, Qt::SortOrder order, + const QTransform &deviceTransform = QTransform()) const + { return items(QRectF(x, y, w, h), mode, order, deviceTransform); } + inline QGraphicsItem *itemAt(qreal x, qreal y) const // ### obsolete + { return itemAt(QPointF(x, y)); } + inline QGraphicsItem *itemAt(qreal x, qreal y, const QTransform &deviceTransform) const + { return itemAt(QPointF(x, y), deviceTransform); } + + QList selectedItems() const; + QPainterPath selectionArea() const; + void setSelectionArea(const QPainterPath &path); // ### obsolete + void setSelectionArea(const QPainterPath &path, const QTransform &deviceTransform); + void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode); // ### obsolete + void setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode, const QTransform &deviceTransform); + + QGraphicsItemGroup *createItemGroup(const QList &items); + void destroyItemGroup(QGraphicsItemGroup *group); + + void addItem(QGraphicsItem *item); + QGraphicsEllipseItem *addEllipse(const QRectF &rect, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsLineItem *addLine(const QLineF &line, const QPen &pen = QPen()); + QGraphicsPathItem *addPath(const QPainterPath &path, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap); + QGraphicsPolygonItem *addPolygon(const QPolygonF &polygon, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsRectItem *addRect(const QRectF &rect, const QPen &pen = QPen(), const QBrush &brush = QBrush()); + QGraphicsTextItem *addText(const QString &text, const QFont &font = QFont()); + QGraphicsSimpleTextItem *addSimpleText(const QString &text, const QFont &font = QFont()); + QGraphicsProxyWidget *addWidget(QWidget *widget, Qt::WindowFlags wFlags = 0); + inline QGraphicsEllipseItem *addEllipse(qreal x, qreal y, qreal w, qreal h, const QPen &pen = QPen(), const QBrush &brush = QBrush()) + { return addEllipse(QRectF(x, y, w, h), pen, brush); } + inline QGraphicsLineItem *addLine(qreal x1, qreal y1, qreal x2, qreal y2, const QPen &pen = QPen()) + { return addLine(QLineF(x1, y1, x2, y2), pen); } + inline QGraphicsRectItem *addRect(qreal x, qreal y, qreal w, qreal h, const QPen &pen = QPen(), const QBrush &brush = QBrush()) + { return addRect(QRectF(x, y, w, h), pen, brush); } + void removeItem(QGraphicsItem *item); + + QGraphicsItem *focusItem() const; + void setFocusItem(QGraphicsItem *item, Qt::FocusReason focusReason = Qt::OtherFocusReason); + bool hasFocus() const; + void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); + void clearFocus(); + + void setStickyFocus(bool enabled); + bool stickyFocus() const; + + QGraphicsItem *mouseGrabberItem() const; + + QBrush backgroundBrush() const; + void setBackgroundBrush(const QBrush &brush); + + QBrush foregroundBrush() const; + void setForegroundBrush(const QBrush &brush); + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + QList views() const; + + inline void update(qreal x, qreal y, qreal w, qreal h) + { update(QRectF(x, y, w, h)); } + inline void invalidate(qreal x, qreal y, qreal w, qreal h, SceneLayers layers = AllLayers) + { invalidate(QRectF(x, y, w, h), layers); } + + QStyle *style() const; + void setStyle(QStyle *style); + + QFont font() const; + void setFont(const QFont &font); + + QPalette palette() const; + void setPalette(const QPalette &palette); + + bool isActive() const; + QGraphicsItem *activePanel() const; + void setActivePanel(QGraphicsItem *item); + QGraphicsWidget *activeWindow() const; + void setActiveWindow(QGraphicsWidget *widget); + + bool sendEvent(QGraphicsItem *item, QEvent *event); + +public Q_SLOTS: + void update(const QRectF &rect = QRectF()); + void invalidate(const QRectF &rect = QRectF(), SceneLayers layers = AllLayers); + void advance(); + void clearSelection(); + void clear(); + +protected: + bool event(QEvent *event); + bool eventFilter(QObject *watched, QEvent *event); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); + virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); + virtual void dropEvent(QGraphicsSceneDragDropEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); + virtual void helpEvent(QGraphicsSceneHelpEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *event); + + virtual void drawBackground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[], + QWidget *widget = 0); + +protected Q_SLOTS: + bool focusNextPrevChild(bool next); + +Q_SIGNALS: + void changed(const QList ®ion); + void sceneRectChanged(const QRectF &rect); + void selectionChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsScene) + Q_DISABLE_COPY(QGraphicsScene) + Q_PRIVATE_SLOT(d_func(), void _q_emitUpdated()) + Q_PRIVATE_SLOT(d_func(), void _q_polishItems()) + Q_PRIVATE_SLOT(d_func(), void _q_processDirtyItems()) + Q_PRIVATE_SLOT(d_func(), void _q_updateScenePosDescendants()) + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsObject; + friend class QGraphicsView; + friend class QGraphicsViewPrivate; + friend class QGraphicsWidget; + friend class QGraphicsWidgetPrivate; + friend class QGraphicsEffect; + friend class QGraphicsSceneIndex; + friend class QGraphicsSceneIndexPrivate; + friend class QGraphicsSceneBspTreeIndex; + friend class QGraphicsSceneBspTreeIndexPrivate; + friend class QGraphicsItemEffectSourcePrivate; +#ifndef QT_NO_GESTURES + friend class QGesture; +#endif +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsScene::SceneLayers) + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/graphicsview/qgraphicsscene_bsp.cpp b/src/gui/graphicsview/qgraphicsscene_bsp.cpp new file mode 100644 index 0000000000..3716debcc4 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscene_bsp.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicsscene_bsp_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneInsertItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QGraphicsItem *item; + + void visit(QList *items) + { items->prepend(item); } +}; + +class QGraphicsSceneRemoveItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QGraphicsItem *item; + + void visit(QList *items) + { items->removeAll(item); } +}; + +class QGraphicsSceneFindItemBspTreeVisitor : public QGraphicsSceneBspTreeVisitor +{ +public: + QList *foundItems; + bool onlyTopLevelItems; + + void visit(QList *items) + { + for (int i = 0; i < items->size(); ++i) { + QGraphicsItem *item = items->at(i); + if (onlyTopLevelItems && item->d_ptr->parent) + item = item->topLevelItem(); + if (!item->d_func()->itemDiscovered && item->d_ptr->visible) { + item->d_func()->itemDiscovered = 1; + foundItems->prepend(item); + } + } + } +}; + +QGraphicsSceneBspTree::QGraphicsSceneBspTree() + : leafCnt(0) +{ + insertVisitor = new QGraphicsSceneInsertItemBspTreeVisitor; + removeVisitor = new QGraphicsSceneRemoveItemBspTreeVisitor; + findVisitor = new QGraphicsSceneFindItemBspTreeVisitor; +} + +QGraphicsSceneBspTree::~QGraphicsSceneBspTree() +{ + delete insertVisitor; + delete removeVisitor; + delete findVisitor; +} + +void QGraphicsSceneBspTree::initialize(const QRectF &rect, int depth) +{ + this->rect = rect; + leafCnt = 0; + nodes.resize((1 << (depth + 1)) - 1); + nodes.fill(Node()); + leaves.resize(1 << depth); + leaves.fill(QList()); + + initialize(rect, depth, 0); +} + +void QGraphicsSceneBspTree::clear() +{ + leafCnt = 0; + nodes.clear(); + leaves.clear(); +} + +void QGraphicsSceneBspTree::insertItem(QGraphicsItem *item, const QRectF &rect) +{ + insertVisitor->item = item; + climbTree(insertVisitor, rect); +} + +void QGraphicsSceneBspTree::removeItem(QGraphicsItem *item, const QRectF &rect) +{ + removeVisitor->item = item; + climbTree(removeVisitor, rect); +} + +void QGraphicsSceneBspTree::removeItems(const QSet &items) +{ + for (int i = 0; i < leaves.size(); ++i) { + QList newItemList; + const QList &oldItemList = leaves[i]; + for (int j = 0; j < oldItemList.size(); ++j) { + QGraphicsItem *item = oldItemList.at(j); + if (!items.contains(item)) + newItemList << item; + } + leaves[i] = newItemList; + } +} + +QList QGraphicsSceneBspTree::items(const QRectF &rect, bool onlyTopLevelItems) const +{ + QList tmp; + findVisitor->foundItems = &tmp; + findVisitor->onlyTopLevelItems = onlyTopLevelItems; + climbTree(findVisitor, rect); + // Reset discovery bits. + for (int i = 0; i < tmp.size(); ++i) + tmp.at(i)->d_ptr->itemDiscovered = 0; + return tmp; +} + +int QGraphicsSceneBspTree::leafCount() const +{ + return leafCnt; +} + +QString QGraphicsSceneBspTree::debug(int index) const +{ + const Node *node = &nodes.at(index); + + QString tmp; + if (node->type == Node::Leaf) { + QRectF rect = rectForIndex(index); + if (!leaves[node->leafIndex].isEmpty()) { + tmp += QString::fromLatin1("[%1, %2, %3, %4] contains %5 items\n") + .arg(rect.left()).arg(rect.top()) + .arg(rect.width()).arg(rect.height()) + .arg(leaves[node->leafIndex].size()); + } + } else { + if (node->type == Node::Horizontal) { + tmp += debug(firstChildIndex(index)); + tmp += debug(firstChildIndex(index) + 1); + } else { + tmp += debug(firstChildIndex(index)); + tmp += debug(firstChildIndex(index) + 1); + } + } + + return tmp; +} + +void QGraphicsSceneBspTree::initialize(const QRectF &rect, int depth, int index) +{ + Node *node = &nodes[index]; + if (index == 0) { + node->type = Node::Horizontal; + node->offset = rect.center().x(); + } + + if (depth) { + Node::Type type; + QRectF rect1, rect2; + qreal offset1, offset2; + + if (node->type == Node::Horizontal) { + type = Node::Vertical; + rect1.setRect(rect.left(), rect.top(), rect.width(), rect.height() / 2); + rect2.setRect(rect1.left(), rect1.bottom(), rect1.width(), rect.height() - rect1.height()); + offset1 = rect1.center().x(); + offset2 = rect2.center().x(); + } else { + type = Node::Horizontal; + rect1.setRect(rect.left(), rect.top(), rect.width() / 2, rect.height()); + rect2.setRect(rect1.right(), rect1.top(), rect.width() - rect1.width(), rect1.height()); + offset1 = rect1.center().y(); + offset2 = rect2.center().y(); + } + + int childIndex = firstChildIndex(index); + + Node *child = &nodes[childIndex]; + child->offset = offset1; + child->type = type; + + child = &nodes[childIndex + 1]; + child->offset = offset2; + child->type = type; + + initialize(rect1, depth - 1, childIndex); + initialize(rect2, depth - 1, childIndex + 1); + } else { + node->type = Node::Leaf; + node->leafIndex = leafCnt++; + } +} + +void QGraphicsSceneBspTree::climbTree(QGraphicsSceneBspTreeVisitor *visitor, const QRectF &rect, int index) const +{ + if (nodes.isEmpty()) + return; + + const Node &node = nodes.at(index); + const int childIndex = firstChildIndex(index); + + switch (node.type) { + case Node::Leaf: { + visitor->visit(const_cast*>(&leaves[node.leafIndex])); + break; + } + case Node::Vertical: + if (rect.left() < node.offset) { + climbTree(visitor, rect, childIndex); + if (rect.right() >= node.offset) + climbTree(visitor, rect, childIndex + 1); + } else { + climbTree(visitor, rect, childIndex + 1); + } + break; + case Node::Horizontal: + if (rect.top() < node.offset) { + climbTree(visitor, rect, childIndex); + if (rect.bottom() >= node.offset) + climbTree(visitor, rect, childIndex + 1); + } else { + climbTree(visitor, rect, childIndex + 1); + } + } +} + +QRectF QGraphicsSceneBspTree::rectForIndex(int index) const +{ + if (index <= 0) + return rect; + + int parentIdx = parentIndex(index); + QRectF rect = rectForIndex(parentIdx); + const Node *parent = &nodes.at(parentIdx); + + if (parent->type == Node::Horizontal) { + if (index & 1) + rect.setRight(parent->offset); + else + rect.setLeft(parent->offset); + } else { + if (index & 1) + rect.setBottom(parent->offset); + else + rect.setTop(parent->offset); + } + + return rect; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsscene_bsp_p.h b/src/gui/graphicsview/qgraphicsscene_bsp_p.h new file mode 100644 index 0000000000..4f230eff79 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscene_bsp_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEBSPTREE_P_H +#define QGRAPHICSSCENEBSPTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsItem; +class QGraphicsSceneBspTreeVisitor; +class QGraphicsSceneInsertItemBspTreeVisitor; +class QGraphicsSceneRemoveItemBspTreeVisitor; +class QGraphicsSceneFindItemBspTreeVisitor; + +class QGraphicsSceneBspTree +{ +public: + struct Node + { + enum Type { Horizontal, Vertical, Leaf }; + union { + qreal offset; + int leafIndex; + }; + Type type; + }; + + QGraphicsSceneBspTree(); + ~QGraphicsSceneBspTree(); + + void initialize(const QRectF &rect, int depth); + void clear(); + + void insertItem(QGraphicsItem *item, const QRectF &rect); + void removeItem(QGraphicsItem *item, const QRectF &rect); + void removeItems(const QSet &items); + + QList items(const QRectF &rect, bool onlyTopLevelItems = false) const; + int leafCount() const; + + inline int firstChildIndex(int index) const + { return index * 2 + 1; } + + inline int parentIndex(int index) const + { return index > 0 ? ((index & 1) ? ((index - 1) / 2) : ((index - 2) / 2)) : -1; } + + QString debug(int index) const; + +private: + void initialize(const QRectF &rect, int depth, int index); + void climbTree(QGraphicsSceneBspTreeVisitor *visitor, const QRectF &rect, int index = 0) const; + QRectF rectForIndex(int index) const; + + QVector nodes; + QVector > leaves; + int leafCnt; + QRectF rect; + + QGraphicsSceneInsertItemBspTreeVisitor *insertVisitor; + QGraphicsSceneRemoveItemBspTreeVisitor *removeVisitor; + QGraphicsSceneFindItemBspTreeVisitor *findVisitor; +}; + +class QGraphicsSceneBspTreeVisitor +{ +public: + virtual ~QGraphicsSceneBspTreeVisitor() { } + virtual void visit(QList *items) = 0; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif // QGRAPHICSSCENEBSPTREE_P_H diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h new file mode 100644 index 0000000000..9460a4dc51 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENE_P_H +#define QGRAPHICSSCENE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsscene.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" +#include "qgraphicsitem_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneIndex; +class QGraphicsView; +class QGraphicsWidget; + +class Q_AUTOTEST_EXPORT QGraphicsScenePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsScene) +public: + QGraphicsScenePrivate(); + void init(); + + static QGraphicsScenePrivate *get(QGraphicsScene *q); + + int changedSignalIndex; + int processDirtyItemsIndex; + int polishItemsIndex; + + QGraphicsScene::ItemIndexMethod indexMethod; + QGraphicsSceneIndex *index; + + int lastItemCount; + + QRectF sceneRect; + + quint32 hasSceneRect : 1; + quint32 dirtyGrowingItemsBoundingRect : 1; + quint32 updateAll : 1; + quint32 calledEmitUpdated : 1; + quint32 processDirtyItemsEmitted : 1; + quint32 needSortTopLevelItems : 1; + quint32 holesInTopLevelSiblingIndex : 1; + quint32 topLevelSequentialOrdering : 1; + quint32 scenePosDescendantsUpdatePending : 1; + quint32 stickyFocus : 1; + quint32 hasFocus : 1; + quint32 lastMouseGrabberItemHasImplicitMouseGrab : 1; + quint32 allItemsIgnoreHoverEvents : 1; + quint32 allItemsUseDefaultCursor : 1; + quint32 painterStateProtection : 1; + quint32 sortCacheEnabled : 1; // for compatibility + quint32 allItemsIgnoreTouchEvents : 1; + quint32 padding : 15; + + QRectF growingItemsBoundingRect; + + void _q_emitUpdated(); + QList updatedRects; + + QPainterPath selectionArea; + int selectionChanging; + QSet selectedItems; + QVector unpolishedItems; + QList topLevelItems; + + QMap movingItemsInitialPositions; + void registerTopLevelItem(QGraphicsItem *item); + void unregisterTopLevelItem(QGraphicsItem *item); + void _q_updateLater(); + void _q_polishItems(); + + void _q_processDirtyItems(); + + QSet scenePosItems; + void setScenePosItemEnabled(QGraphicsItem *item, bool enabled); + void registerScenePosItem(QGraphicsItem *item); + void unregisterScenePosItem(QGraphicsItem *item); + void _q_updateScenePosDescendants(); + + void removeItemHelper(QGraphicsItem *item); + + QBrush backgroundBrush; + QBrush foregroundBrush; + + quint32 rectAdjust; + QGraphicsItem *focusItem; + QGraphicsItem *lastFocusItem; + QGraphicsItem *passiveFocusItem; + QGraphicsWidget *tabFocusFirst; + QGraphicsItem *activePanel; + QGraphicsItem *lastActivePanel; + int activationRefCount; + int childExplicitActivation; + void setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent); + void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason); + + QList popupWidgets; + void addPopup(QGraphicsWidget *widget); + void removePopup(QGraphicsWidget *widget, bool itemIsDying = false); + + QGraphicsItem *lastMouseGrabberItem; + QList mouseGrabberItems; + void grabMouse(QGraphicsItem *item, bool implicit = false); + void ungrabMouse(QGraphicsItem *item, bool itemIsDying = false); + void clearMouseGrabber(); + + QList keyboardGrabberItems; + void grabKeyboard(QGraphicsItem *item); + void ungrabKeyboard(QGraphicsItem *item, bool itemIsDying = false); + void clearKeyboardGrabber(); + + QGraphicsItem *dragDropItem; + QGraphicsWidget *enterWidget; + Qt::DropAction lastDropAction; + QList cachedItemsUnderMouse; + QList hoverItems; + QPointF lastSceneMousePos; + void enableMouseTrackingOnViews(); + QMap mouseGrabberButtonDownPos; + QMap mouseGrabberButtonDownScenePos; + QMap mouseGrabberButtonDownScreenPos; + QList itemsAtPosition(const QPoint &screenPos, + const QPointF &scenePos, + QWidget *widget) const; + void storeMouseButtonsForMouseGrabber(QGraphicsSceneMouseEvent *event); + + QList views; + void addView(QGraphicsView *view); + void removeView(QGraphicsView *view); + + QMultiMap sceneEventFilters; + void installSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter); + void removeSceneEventFilter(QGraphicsItem *watched, QGraphicsItem *filter); + bool filterDescendantEvent(QGraphicsItem *item, QEvent *event); + bool filterEvent(QGraphicsItem *item, QEvent *event); + bool sendEvent(QGraphicsItem *item, QEvent *event); + + bool dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEvent); + bool itemAcceptsHoverEvents_helper(const QGraphicsItem *item) const; + void leaveScene(QWidget *viewport); + + void cloneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QGraphicsSceneDragDropEvent *source); + void sendDragDropEvent(QGraphicsItem *item, + QGraphicsSceneDragDropEvent *dragDropEvent); + void sendHoverEvent(QEvent::Type type, QGraphicsItem *item, + QGraphicsSceneHoverEvent *hoverEvent); + void sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent); + void mousePressEventHandler(QGraphicsSceneMouseEvent *mouseEvent); + QGraphicsWidget *windowForItem(const QGraphicsItem *item) const; + + void drawItemHelper(QGraphicsItem *item, QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget, + bool painterStateProtection); + + void drawItems(QPainter *painter, const QTransform *const viewTransform, + QRegion *exposedRegion, QWidget *widget); + + void drawSubtreeRecursive(QGraphicsItem *item, QPainter *painter, const QTransform *const, + QRegion *exposedRegion, QWidget *widget, qreal parentOpacity = qreal(1.0), + const QTransform *const effectTransform = 0); + void draw(QGraphicsItem *, QPainter *, const QTransform *const, const QTransform *const, + QRegion *, QWidget *, qreal, const QTransform *const, bool, bool); + + void markDirty(QGraphicsItem *item, const QRectF &rect = QRectF(), bool invalidateChildren = false, + bool force = false, bool ignoreOpacity = false, bool removingItemFromScene = false, + bool updateBoundingRect = false); + void processDirtyItemsRecursive(QGraphicsItem *item, bool dirtyAncestorContainsChildren = false, + qreal parentOpacity = qreal(1.0)); + + inline void resetDirtyItem(QGraphicsItem *item, bool recursive = false) + { + Q_ASSERT(item); + item->d_ptr->dirty = 0; + item->d_ptr->paintedViewBoundingRectsNeedRepaint = 0; + item->d_ptr->geometryChanged = 0; + if (!item->d_ptr->dirtyChildren) + recursive = false; + item->d_ptr->dirtyChildren = 0; + item->d_ptr->needsRepaint = QRectF(); + item->d_ptr->allChildrenDirty = 0; + item->d_ptr->fullUpdatePending = 0; + item->d_ptr->ignoreVisible = 0; + item->d_ptr->ignoreOpacity = 0; +#ifndef QT_NO_GRAPHICSEFFECT + QGraphicsEffect::ChangeFlags flags; + if (item->d_ptr->notifyBoundingRectChanged) { + flags |= QGraphicsEffect::SourceBoundingRectChanged; + item->d_ptr->notifyBoundingRectChanged = 0; + } + if (item->d_ptr->notifyInvalidated) { + flags |= QGraphicsEffect::SourceInvalidated; + item->d_ptr->notifyInvalidated = 0; + } +#endif //QT_NO_GRAPHICSEFFECT + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + resetDirtyItem(item->d_ptr->children.at(i), recursive); + } +#ifndef QT_NO_GRAPHICSEFFECT + if (flags && item->d_ptr->graphicsEffect) + item->d_ptr->graphicsEffect->sourceChanged(flags); +#endif //QT_NO_GRAPHICSEFFECT + } + + inline void ensureSortedTopLevelItems() + { + if (needSortTopLevelItems) { + qSort(topLevelItems.begin(), topLevelItems.end(), qt_notclosestLeaf); + topLevelSequentialOrdering = false; + needSortTopLevelItems = false; + } + } + + void ensureSequentialTopLevelSiblingIndexes(); + + QStyle *style; + QFont font; + void setFont_helper(const QFont &font); + void resolveFont(); + void updateFont(const QFont &font); + QPalette palette; + void setPalette_helper(const QPalette &palette); + void resolvePalette(); + void updatePalette(const QPalette &palette); + + QStyleOptionGraphicsItem styleOptionTmp; + + QMap sceneCurrentTouchPoints; + QMap itemForTouchPointId; + static void updateTouchPointsForItem(QGraphicsItem *item, QTouchEvent *touchEvent); + int findClosestTouchPointId(const QPointF &scenePos); + void touchEventHandler(QTouchEvent *touchEvent); + bool sendTouchBeginEvent(QGraphicsItem *item, QTouchEvent *touchEvent); + void enableTouchEventsOnViews(); + + QList cachedTargetItems; +#ifndef QT_NO_GESTURES + QHash > cachedItemGestures; + QHash > cachedAlreadyDeliveredGestures; + QHash gestureTargets; + QHash grabbedGestures; + void gestureEventHandler(QGestureEvent *event); + void gestureTargetsAtHotSpots(const QSet &gestures, + Qt::GestureFlag flag, + QHash > *targets, + QSet *itemsSet = 0, + QSet *normal = 0, + QSet *conflicts = 0); + void cancelGesturesForChildren(QGesture *original); + void grabGesture(QGraphicsItem *, Qt::GestureType gesture); + void ungrabGesture(QGraphicsItem *, Qt::GestureType gesture); +#endif // QT_NO_GESTURES + + void updateInputMethodSensitivityInViews(); + + QList modalPanels; + void enterModal(QGraphicsItem *item, + QGraphicsItem::PanelModality panelModality = QGraphicsItem::NonModal); + void leaveModal(QGraphicsItem *item); +}; + +// QRectF::intersects() returns false always if either the source or target +// rectangle's width or height are 0. This works around that problem. +static inline void _q_adjustRect(QRectF *rect) +{ + Q_ASSERT(rect); + if (!rect->width()) + rect->adjust(qreal(-0.00001), 0, qreal(0.00001), 0); + if (!rect->height()) + rect->adjust(0, qreal(-0.00001), 0, qreal(0.00001)); +} + +static inline QRectF adjustedItemBoundingRect(const QGraphicsItem *item) +{ + Q_ASSERT(item); + QRectF boundingRect(item->boundingRect()); + _q_adjustRect(&boundingRect); + return boundingRect; +} + +static inline QRectF adjustedItemEffectiveBoundingRect(const QGraphicsItem *item) +{ + Q_ASSERT(item); + QRectF boundingRect(QGraphicsItemPrivate::get(item)->effectiveBoundingRect()); + _q_adjustRect(&boundingRect); + return boundingRect; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/gui/graphicsview/qgraphicsscenebsptreeindex.cpp b/src/gui/graphicsview/qgraphicsscenebsptreeindex.cpp new file mode 100644 index 0000000000..92e4a55952 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscenebsptreeindex.cpp @@ -0,0 +1,719 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneBspTreeIndex + \brief The QGraphicsSceneBspTreeIndex class provides an implementation of + a BSP indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + + \internal + + QGraphicsSceneBspTreeIndex index use a BSP(Binary Space Partitioning) + implementation to discover items quickly. This implementation is + very efficient for static scene. It has a depth that you can set. + The depth directly affects performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, the index can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as the index retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + + \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex +*/ + +#include + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +static inline int intmaxlog(int n) +{ + return (n > 0 ? qMax(qCeil(qLn(qreal(n)) / qLn(qreal(2))), 5) : 0); +} + +/*! + Constructs a private scene bsp index. +*/ +QGraphicsSceneBspTreeIndexPrivate::QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene) + : QGraphicsSceneIndexPrivate(scene), + bspTreeDepth(0), + indexTimerId(0), + restartIndexTimer(false), + regenerateIndex(true), + lastItemCount(0), + purgePending(false), + sortCacheEnabled(false), + updatingSortCache(false) +{ +} + + +/*! + This method will update the BSP index by removing the items from the temporary + unindexed list and add them in the indexedItems list. This will also + update the growingItemsBoundingRect if needed. This will update the BSP + implementation as well. + + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::_q_updateIndex() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (!indexTimerId) + return; + + q->killTimer(indexTimerId); + indexTimerId = 0; + + purgeRemovedItems(); + + // Add unindexedItems to indexedItems + for (int i = 0; i < unindexedItems.size(); ++i) { + if (QGraphicsItem *item = unindexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + if (!freeItemIndexes.isEmpty()) { + int freeIndex = freeItemIndexes.takeFirst(); + item->d_func()->index = freeIndex; + indexedItems[freeIndex] = item; + } else { + item->d_func()->index = indexedItems.size(); + indexedItems << item; + } + } + } + + // Determine whether we should regenerate the BSP tree. + if (bspTreeDepth == 0) { + int oldDepth = intmaxlog(lastItemCount); + bspTreeDepth = intmaxlog(indexedItems.size()); + static const int slack = 100; + if (bsp.leafCount() == 0 || (oldDepth != bspTreeDepth && qAbs(lastItemCount - indexedItems.size()) > slack)) { + // ### Crude algorithm. + regenerateIndex = true; + } + } + + // Regenerate the tree. + if (regenerateIndex) { + regenerateIndex = false; + bsp.initialize(sceneRect, bspTreeDepth); + unindexedItems = indexedItems; + lastItemCount = indexedItems.size(); + } + + // Insert all unindexed items into the tree. + for (int i = 0; i < unindexedItems.size(); ++i) { + if (QGraphicsItem *item = unindexedItems.at(i)) { + if (item->d_ptr->itemIsUntransformable()) { + untransformableItems << item; + continue; + } + if (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) + continue; + + bsp.insertItem(item, item->d_ptr->sceneEffectiveBoundingRect()); + } + } + unindexedItems.clear(); +} + + +/*! + \internal + + Removes stale pointers from all data structures. +*/ +void QGraphicsSceneBspTreeIndexPrivate::purgeRemovedItems() +{ + if (!purgePending && removedItems.isEmpty()) + return; + + // Remove stale items from the BSP tree. + bsp.removeItems(removedItems); + // Purge this list. + removedItems.clear(); + freeItemIndexes.clear(); + for (int i = 0; i < indexedItems.size(); ++i) { + if (!indexedItems.at(i)) + freeItemIndexes << i; + } + purgePending = false; +} + +/*! + \internal + + Starts or restarts the timer used for reindexing unindexed items. +*/ +void QGraphicsSceneBspTreeIndexPrivate::startIndexTimer(int interval) +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (indexTimerId) { + restartIndexTimer = true; + } else { + indexTimerId = q->startTimer(interval); + } +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::resetIndex() +{ + purgeRemovedItems(); + for (int i = 0; i < indexedItems.size(); ++i) { + if (QGraphicsItem *item = indexedItems.at(i)) { + item->d_ptr->index = -1; + Q_ASSERT(!item->d_ptr->itemDiscovered); + unindexedItems << item; + } + } + indexedItems.clear(); + freeItemIndexes.clear(); + untransformableItems.clear(); + regenerateIndex = true; + startIndexTimer(); +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::climbTree(QGraphicsItem *item, int *stackingOrder) +{ + if (!item->d_ptr->children.isEmpty()) { + QList childList = item->d_ptr->children; + qSort(childList.begin(), childList.end(), qt_closestLeaf); + for (int i = 0; i < childList.size(); ++i) { + QGraphicsItem *item = childList.at(i); + if (!(item->flags() & QGraphicsItem::ItemStacksBehindParent)) + climbTree(childList.at(i), stackingOrder); + } + item->d_ptr->globalStackingOrder = (*stackingOrder)++; + for (int i = 0; i < childList.size(); ++i) { + QGraphicsItem *item = childList.at(i); + if (item->flags() & QGraphicsItem::ItemStacksBehindParent) + climbTree(childList.at(i), stackingOrder); + } + } else { + item->d_ptr->globalStackingOrder = (*stackingOrder)++; + } +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::_q_updateSortCache() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + _q_updateIndex(); + + if (!sortCacheEnabled || !updatingSortCache) + return; + + updatingSortCache = false; + int stackingOrder = 0; + + QList topLevels; + const QList items = q->items(); + for (int i = 0; i < items.size(); ++i) { + QGraphicsItem *item = items.at(i); + if (item && !item->d_ptr->parent) + topLevels << item; + } + + qSort(topLevels.begin(), topLevels.end(), qt_closestLeaf); + for (int i = 0; i < topLevels.size(); ++i) + climbTree(topLevels.at(i), &stackingOrder); +} + +/*! + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::invalidateSortCache() +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (!sortCacheEnabled || updatingSortCache) + return; + + updatingSortCache = true; + QMetaObject::invokeMethod(q, "_q_updateSortCache", Qt::QueuedConnection); +} + +void QGraphicsSceneBspTreeIndexPrivate::addItem(QGraphicsItem *item, bool recursive) +{ + if (!item) + return; + + // Prevent reusing a recently deleted pointer: purge all removed item from our lists. + purgeRemovedItems(); + + // Invalidate any sort caching; arrival of a new item means we need to resort. + // Update the scene's sort cache settings. + item->d_ptr->globalStackingOrder = -1; + invalidateSortCache(); + + // Indexing requires sceneBoundingRect(), but because \a item might + // not be completely constructed at this point, we need to store it in + // a temporary list and schedule an indexing for later. + if (item->d_ptr->index == -1) { + Q_ASSERT(!unindexedItems.contains(item)); + unindexedItems << item; + startIndexTimer(0); + } else { + Q_ASSERT(indexedItems.contains(item)); + qWarning("QGraphicsSceneBspTreeIndex::addItem: item has already been added to this BSP"); + } + + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + addItem(item->d_ptr->children.at(i), recursive); + } +} + +void QGraphicsSceneBspTreeIndexPrivate::removeItem(QGraphicsItem *item, bool recursive, + bool moveToUnindexedItems) +{ + if (!item) + return; + + if (item->d_ptr->index != -1) { + Q_ASSERT(item->d_ptr->index < indexedItems.size()); + Q_ASSERT(indexedItems.at(item->d_ptr->index) == item); + Q_ASSERT(!item->d_ptr->itemDiscovered); + freeItemIndexes << item->d_ptr->index; + indexedItems[item->d_ptr->index] = 0; + item->d_ptr->index = -1; + + if (item->d_ptr->itemIsUntransformable()) { + untransformableItems.removeOne(item); + } else if (item->d_ptr->inDestructor) { + // Avoid virtual function calls from the destructor. + purgePending = true; + removedItems << item; + } else if (!(item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + bsp.removeItem(item, item->d_ptr->sceneEffectiveBoundingRect()); + } + } else { + unindexedItems.removeOne(item); + } + invalidateSortCache(); // ### Only do this when removing from BSP? + + Q_ASSERT(item->d_ptr->index == -1); + Q_ASSERT(!indexedItems.contains(item)); + Q_ASSERT(!unindexedItems.contains(item)); + Q_ASSERT(!untransformableItems.contains(item)); + + if (moveToUnindexedItems) + addItem(item); + + if (recursive) { + for (int i = 0; i < item->d_ptr->children.size(); ++i) + removeItem(item->d_ptr->children.at(i), recursive, moveToUnindexedItems); + } +} + +QList QGraphicsSceneBspTreeIndexPrivate::estimateItems(const QRectF &rect, Qt::SortOrder order, + bool onlyTopLevelItems) +{ + Q_Q(QGraphicsSceneBspTreeIndex); + if (onlyTopLevelItems && rect.isNull()) + return q->QGraphicsSceneIndex::estimateTopLevelItems(rect, order); + + purgeRemovedItems(); + _q_updateSortCache(); + Q_ASSERT(unindexedItems.isEmpty()); + + QList rectItems = bsp.items(rect, onlyTopLevelItems); + if (onlyTopLevelItems) { + for (int i = 0; i < untransformableItems.size(); ++i) { + QGraphicsItem *item = untransformableItems.at(i); + if (!item->d_ptr->parent) { + rectItems << item; + } else { + item = item->topLevelItem(); + if (!rectItems.contains(item)) + rectItems << item; + } + } + } else { + rectItems += untransformableItems; + } + + sortItems(&rectItems, order, sortCacheEnabled, onlyTopLevelItems); + return rectItems; +} + +/*! + Sort a list of \a itemList in a specific \a order and use the cache if requested. + + \internal +*/ +void QGraphicsSceneBspTreeIndexPrivate::sortItems(QList *itemList, Qt::SortOrder order, + bool sortCacheEnabled, bool onlyTopLevelItems) +{ + if (order == Qt::SortOrder(-1)) + return; + + if (onlyTopLevelItems) { + if (order == Qt::DescendingOrder) + qSort(itemList->begin(), itemList->end(), qt_closestLeaf); + else if (order == Qt::AscendingOrder) + qSort(itemList->begin(), itemList->end(), qt_notclosestLeaf); + return; + } + + if (sortCacheEnabled) { + if (order == Qt::DescendingOrder) { + qSort(itemList->begin(), itemList->end(), closestItemFirst_withCache); + } else if (order == Qt::AscendingOrder) { + qSort(itemList->begin(), itemList->end(), closestItemLast_withCache); + } + } else { + if (order == Qt::DescendingOrder) { + qSort(itemList->begin(), itemList->end(), qt_closestItemFirst); + } else if (order == Qt::AscendingOrder) { + qSort(itemList->begin(), itemList->end(), qt_closestItemLast); + } + } +} + +/*! + Constructs a BSP scene index for the given \a scene. +*/ +QGraphicsSceneBspTreeIndex::QGraphicsSceneBspTreeIndex(QGraphicsScene *scene) + : QGraphicsSceneIndex(*new QGraphicsSceneBspTreeIndexPrivate(scene), scene) +{ + +} + +QGraphicsSceneBspTreeIndex::~QGraphicsSceneBspTreeIndex() +{ + Q_D(QGraphicsSceneBspTreeIndex); + for (int i = 0; i < d->indexedItems.size(); ++i) { + // Ensure item bits are reset properly. + if (QGraphicsItem *item = d->indexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + item->d_ptr->index = -1; + } + } +} + +/*! + \internal + Clear the all the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::clear() +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->bsp.clear(); + d->lastItemCount = 0; + d->freeItemIndexes.clear(); + for (int i = 0; i < d->indexedItems.size(); ++i) { + // Ensure item bits are reset properly. + if (QGraphicsItem *item = d->indexedItems.at(i)) { + Q_ASSERT(!item->d_ptr->itemDiscovered); + item->d_ptr->index = -1; + } + } + d->indexedItems.clear(); + d->unindexedItems.clear(); + d->untransformableItems.clear(); + d->regenerateIndex = true; +} + +/*! + Add the \a item into the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::addItem(QGraphicsItem *item) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->addItem(item); +} + +/*! + Remove the \a item from the BSP index. +*/ +void QGraphicsSceneBspTreeIndex::removeItem(QGraphicsItem *item) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->removeItem(item); +} + +/*! + \internal + Update the BSP when the \a item 's bounding rect has changed. +*/ +void QGraphicsSceneBspTreeIndex::prepareBoundingRectChange(const QGraphicsItem *item) +{ + if (!item) + return; + + if (item->d_ptr->index == -1 || item->d_ptr->itemIsUntransformable() + || (item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)) { + return; // Item is not in BSP tree; nothing to do. + } + + Q_D(QGraphicsSceneBspTreeIndex); + QGraphicsItem *thatItem = const_cast(item); + d->removeItem(thatItem, /*recursive=*/false, /*moveToUnindexedItems=*/true); + for (int i = 0; i < item->d_ptr->children.size(); ++i) // ### Do we really need this? + prepareBoundingRectChange(item->d_ptr->children.at(i)); +} + +/*! + Returns an estimation visible items that are either inside or + intersect with the specified \a rect and return a list sorted using \a order. + + \a deviceTransform is the transformation apply to the view. + +*/ +QList QGraphicsSceneBspTreeIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return const_cast(d)->estimateItems(rect, order); +} + +QList QGraphicsSceneBspTreeIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return const_cast(d)->estimateItems(rect, order, /*onlyTopLevels=*/true); +} + +/*! + \fn QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; + + Return all items in the BSP index and sort them using \a order. +*/ +QList QGraphicsSceneBspTreeIndex::items(Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneBspTreeIndex); + const_cast(d)->purgeRemovedItems(); + QList itemList; + + // If freeItemIndexes is empty, we know there are no holes in indexedItems and + // unindexedItems. + if (d->freeItemIndexes.isEmpty()) { + if (d->unindexedItems.isEmpty()) { + itemList = d->indexedItems; + } else { + itemList = d->indexedItems + d->unindexedItems; + } + } else { + // Rebuild the list of items to avoid holes. ### We could also just + // compress the item lists at this point. + foreach (QGraphicsItem *item, d->indexedItems + d->unindexedItems) { + if (item) + itemList << item; + } + } + if (order != -1) { + //We sort descending order + d->sortItems(&itemList, order, d->sortCacheEnabled); + } + return itemList; +} + +/*! + \property QGraphicsSceneBspTreeIndex::bspTreeDepth + \brief the depth of the BSP index tree + \since 4.6 + + This value determines the depth of BSP tree. The depth + directly affects performance and memory usage; the latter + growing exponentially with the depth of the tree. With an optimal tree + depth, the index can instantly determine the locality of items, even + for scenes with thousands or millions of items. This also greatly improves + rendering performance. + + By default, the value is 0, in which case Qt will guess a reasonable + default depth based on the size, location and number of items in the + scene. If these parameters change frequently, however, you may experience + slowdowns as the index retunes the depth internally. You can avoid + potential slowdowns by fixating the tree depth through setting this + property. + + The depth of the tree and the size of the scene rectangle decide the + granularity of the scene's partitioning. The size of each scene segment is + determined by the following algorithm: + + The BSP tree has an optimal size when each segment contains between 0 and + 10 items. + +*/ +int QGraphicsSceneBspTreeIndex::bspTreeDepth() +{ + Q_D(const QGraphicsSceneBspTreeIndex); + return d->bspTreeDepth; +} + +void QGraphicsSceneBspTreeIndex::setBspTreeDepth(int depth) +{ + Q_D(QGraphicsSceneBspTreeIndex); + if (d->bspTreeDepth == depth) + return; + d->bspTreeDepth = depth; + d->resetIndex(); +} + +/*! + \internal + + This method react to the \a rect change of the scene and + reset the BSP tree index. +*/ +void QGraphicsSceneBspTreeIndex::updateSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsSceneBspTreeIndex); + d->sceneRect = rect; + d->resetIndex(); +} + +/*! + \internal + + This method react to the \a change of the \a item and use the \a value to + update the BSP tree if necessary. +*/ +void QGraphicsSceneBspTreeIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) +{ + Q_D(QGraphicsSceneBspTreeIndex); + switch (change) { + case QGraphicsItem::ItemFlagsChange: { + // Handle ItemIgnoresTransformations + QGraphicsItem::GraphicsItemFlags newFlags = *static_cast(value); + bool ignoredTransform = item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations; + bool willIgnoreTransform = newFlags & QGraphicsItem::ItemIgnoresTransformations; + bool clipsChildren = item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape; + bool willClipChildren = newFlags & QGraphicsItem::ItemClipsChildrenToShape; + if ((ignoredTransform != willIgnoreTransform) || (clipsChildren != willClipChildren)) { + QGraphicsItem *thatItem = const_cast(item); + // Remove item and its descendants from the index and append + // them to the list of unindexed items. Then, when the index + // is updated, they will be put into the bsp-tree or the list + // of untransformable items. + d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); + } + break; + } + case QGraphicsItem::ItemZValueChange: + d->invalidateSortCache(); + break; + case QGraphicsItem::ItemParentChange: { + d->invalidateSortCache(); + // Handle ItemIgnoresTransformations + const QGraphicsItem *newParent = static_cast(value); + bool ignoredTransform = item->d_ptr->itemIsUntransformable(); + bool willIgnoreTransform = (item->d_ptr->flags & QGraphicsItem::ItemIgnoresTransformations) + || (newParent && newParent->d_ptr->itemIsUntransformable()); + bool ancestorClippedChildren = item->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren; + bool ancestorWillClipChildren = newParent + && ((newParent->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape) + || (newParent->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren)); + if ((ignoredTransform != willIgnoreTransform) || (ancestorClippedChildren != ancestorWillClipChildren)) { + QGraphicsItem *thatItem = const_cast(item); + // Remove item and its descendants from the index and append + // them to the list of unindexed items. Then, when the index + // is updated, they will be put into the bsp-tree or the list + // of untransformable items. + d->removeItem(thatItem, /*recursive=*/true, /*moveToUnidexedItems=*/true); + } + break; + } + default: + break; + } +} +/*! + \reimp + + Used to catch the timer event. + + \internal +*/ +bool QGraphicsSceneBspTreeIndex::event(QEvent *event) +{ + Q_D(QGraphicsSceneBspTreeIndex); + switch (event->type()) { + case QEvent::Timer: + if (d->indexTimerId && static_cast(event)->timerId() == d->indexTimerId) { + if (d->restartIndexTimer) { + d->restartIndexTimer = false; + } else { + // this call will kill the timer + d->_q_updateIndex(); + } + } + // Fallthrough intended - support timers in subclasses. + default: + return QObject::event(event); + } + return true; +} + +QT_END_NAMESPACE + +#include "moc_qgraphicsscenebsptreeindex_p.cpp" + +#endif // QT_NO_GRAPHICSVIEW + diff --git a/src/gui/graphicsview/qgraphicsscenebsptreeindex_p.h b/src/gui/graphicsview/qgraphicsscenebsptreeindex_p.h new file mode 100644 index 0000000000..fadf9d8a74 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscenebsptreeindex_p.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QGRAPHICSBSPTREEINDEX_H +#define QGRAPHICSBSPTREEINDEX_H + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include "qgraphicssceneindex_p.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene_bsp_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +static const int QGRAPHICSSCENE_INDEXTIMER_TIMEOUT = 2000; + +class QGraphicsScene; +class QGraphicsSceneBspTreeIndexPrivate; + +class Q_AUTOTEST_EXPORT QGraphicsSceneBspTreeIndex : public QGraphicsSceneIndex +{ + Q_OBJECT + Q_PROPERTY(int bspTreeDepth READ bspTreeDepth WRITE setBspTreeDepth) +public: + QGraphicsSceneBspTreeIndex(QGraphicsScene *scene = 0); + ~QGraphicsSceneBspTreeIndex(); + + QList estimateItems(const QRectF &rect, Qt::SortOrder order) const; + QList estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const; + QList items(Qt::SortOrder order = Qt::DescendingOrder) const; + + int bspTreeDepth(); + void setBspTreeDepth(int depth); + +protected Q_SLOTS: + void updateSceneRect(const QRectF &rect); + +protected: + bool event(QEvent *event); + void clear(); + + void addItem(QGraphicsItem *item); + void removeItem(QGraphicsItem *item); + void prepareBoundingRectChange(const QGraphicsItem *item); + + void itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value); + +private : + Q_DECLARE_PRIVATE(QGraphicsSceneBspTreeIndex) + Q_DISABLE_COPY(QGraphicsSceneBspTreeIndex) + Q_PRIVATE_SLOT(d_func(), void _q_updateSortCache()) + Q_PRIVATE_SLOT(d_func(), void _q_updateIndex()) + + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; +}; + +class QGraphicsSceneBspTreeIndexPrivate : public QGraphicsSceneIndexPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneBspTreeIndex) +public: + QGraphicsSceneBspTreeIndexPrivate(QGraphicsScene *scene); + + QGraphicsSceneBspTree bsp; + QRectF sceneRect; + int bspTreeDepth; + int indexTimerId; + bool restartIndexTimer; + bool regenerateIndex; + int lastItemCount; + + QList indexedItems; + QList unindexedItems; + QList untransformableItems; + QList freeItemIndexes; + + bool purgePending; + QSet removedItems; + void purgeRemovedItems(); + + void _q_updateIndex(); + void startIndexTimer(int interval = QGRAPHICSSCENE_INDEXTIMER_TIMEOUT); + void resetIndex(); + + void _q_updateSortCache(); + bool sortCacheEnabled; + bool updatingSortCache; + void invalidateSortCache(); + void addItem(QGraphicsItem *item, bool recursive = false); + void removeItem(QGraphicsItem *item, bool recursive = false, bool moveToUnindexedItems = false); + QList estimateItems(const QRectF &, Qt::SortOrder, bool b = false); + + static void climbTree(QGraphicsItem *item, int *stackingOrder); + + static inline bool closestItemFirst_withCache(const QGraphicsItem *item1, const QGraphicsItem *item2) + { + return item1->d_ptr->globalStackingOrder < item2->d_ptr->globalStackingOrder; + } + static inline bool closestItemLast_withCache(const QGraphicsItem *item1, const QGraphicsItem *item2) + { + return item1->d_ptr->globalStackingOrder >= item2->d_ptr->globalStackingOrder; + } + + static void sortItems(QList *itemList, Qt::SortOrder order, + bool cached, bool onlyTopLevelItems = false); +}; + +static inline bool QRectF_intersects(const QRectF &s, const QRectF &r) +{ + qreal xp = s.left(); + qreal yp = s.top(); + qreal w = s.width(); + qreal h = s.height(); + qreal l1 = xp; + qreal r1 = xp; + if (w < 0) + l1 += w; + else + r1 += w; + + qreal l2 = r.left(); + qreal r2 = r.left(); + if (w < 0) + l2 += r.width(); + else + r2 += r.width(); + + if (l1 >= r2 || l2 >= r1) + return false; + + qreal t1 = yp; + qreal b1 = yp; + if (h < 0) + t1 += h; + else + b1 += h; + + qreal t2 = r.top(); + qreal b2 = r.top(); + if (r.height() < 0) + t2 += r.height(); + else + b2 += r.height(); + + return !(t1 >= b2 || t2 >= b1); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif // QGRAPHICSBSPTREEINDEX_H diff --git a/src/gui/graphicsview/qgraphicssceneevent.cpp b/src/gui/graphicsview/qgraphicssceneevent.cpp new file mode 100644 index 0000000000..1b1f3db0aa --- /dev/null +++ b/src/gui/graphicsview/qgraphicssceneevent.cpp @@ -0,0 +1,1674 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneEvent + \brief The QGraphicsSceneEvent class provides a base class for all + graphics view related events. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives Qt mouse, keyboard, and drag and + drop events (QMouseEvent, QKeyEvent, QDragEvent, etc.), it + translates them into instances of QGraphicsSceneEvent subclasses + and forwards them to the QGraphicsScene it displays. The scene + then forwards the events to the relevant items. + + For example, when a QGraphicsView receives a QMouseEvent of type + MousePress as a response to a user click, the view sends a + QGraphicsSceneMouseEvent of type GraphicsSceneMousePress to the + underlying QGraphicsScene through its + \l{QGraphicsScene::}{mousePressEvent()} function. The default + QGraphicsScene::mousePressEvent() implementation determines which + item was clicked and forwards the event to + QGraphicsItem::mousePressEvent(). + + \omit ### Beskrive widget() \endomit + + Subclasses such as QGraphicsSceneMouseEvent and + QGraphicsSceneContextMenuEvent provide the coordinates from the + original QEvent in screen, scene, and item coordinates (see + \l{QGraphicsSceneMouseEvent::}{screenPos()}, + \l{QGraphicsSceneMouseEvent::}{scenePos()}, and + \l{QGraphicsSceneMouseEvent::}{pos()}). The item coordinates are + set by the QGraphicsScene before it forwards the event to the + event to a QGraphicsItem. The mouse events also add the + possibility to retrieve the coordinates from the last event + received by the view (see + \l{QGraphicsSceneMouseEvent::}{lastScreenPos()}, + \l{QGraphicsSceneMouseEvent::}{lastScenePos()}, and + \l{QGraphicsSceneMouseEvent::}{lastPos()}). + + \sa QEvent +*/ + +/*! + \class QGraphicsSceneMouseEvent + \brief The QGraphicsSceneMouseEvent class provides mouse events + in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QMouseEvent, it translates it to a + QGraphicsSceneMouseEvent. The event is then forwarded to the + QGraphicsScene associated with the view. If the event is not + handled by the scene, the view may use it, e.g., for the + \l{QGraphicsView::}{DragMode}. + + In addition to containing the item, scene, and screen coordinates + of the event (as pos(), scenePos(), and screenPos()), mouse + events also contain the coordinates of the previous mouse + event received by the view. These can be retrieved with + lastPos(), lastScreenPos(), and lastScenePos(). + + \sa QGraphicsSceneContextMenuEvent, + QGraphicsSceneHoverEvent, QGraphicsSceneWheelEvent, + QMouseEvent +*/ + +/*! + \class QGraphicsSceneWheelEvent + \brief The QGraphicsSceneWheelEvent class provides wheel events + in the graphics view framework. + \brief The QGraphicsSceneWheelEvent class provides wheel events in the + graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + \l{QWheelEvent}{QWheelEvent}s received by a QGraphicsView are translated + into QGraphicsSceneWheelEvents; it translates the QWheelEvent::globalPos() + into item, scene, and screen coordinates (pos(), scenePos(), and + screenPos()). + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneContextMenuEvent, + QGraphicsSceneHoverEvent, QWheelEvent +*/ + +/*! + \class QGraphicsSceneContextMenuEvent + \brief The QGraphicsSceneContextMenuEvent class provides context + menu events in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + A QContextMenuEvent received by a QGraphicsView is translated + into a QGraphicsSceneContextMenuEvent. The + QContextMenuEvent::globalPos() is translated into item, scene, and + screen coordinates (pos(), scenePos(), and screenPos()). + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneWheelEvent, + QContextMenuEvent +*/ + +/*! + \enum QGraphicsSceneContextMenuEvent::Reason + + This enum describes the reason why the context event was sent. + + \value Mouse The mouse caused the event to be sent. On most + platforms, this means the right mouse button was clicked. + + \value Keyboard The keyboard caused this event to be sent. On + Windows and Mac OS X, this means the menu button was pressed. + + \value Other The event was sent by some other means (i.e. not + by the mouse or keyboard). +*/ + +/*! + \class QGraphicsSceneHoverEvent + \brief The QGraphicsSceneHoverEvent class provides hover events + in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QHoverEvent event, it translates + it into QGraphicsSceneHoverEvent. The event is then forwarded to + the QGraphicsScene associated with the view. + + \sa QGraphicsSceneMouseEvent, QGraphicsSceneContextMenuEvent, + QGraphicsSceneWheelEvent, QHoverEvent +*/ + +/*! + \class QGraphicsSceneHelpEvent + \brief The QGraphicsSceneHelpEvent class provides events when a + tooltip is requested. + \since 4.2 + \ingroup graphicsview-api + + When a QGraphicsView receives a QEvent of type + QEvent::ToolTip, it creates a QGraphicsSceneHelpEvent, which is + forwarded to the scene. You can set a tooltip on a QGraphicsItem + with \l{QGraphicsItem::}{setToolTip()}; by default QGraphicsScene + displays the tooltip of the QGraphicsItem with the highest + z-value (i.e, the top-most item) under the mouse position. + + QGraphicsView does not forward events when + \l{QWhatsThis}{"What's This"} and \l{QStatusTipEvent}{status tip} + help is requested. If you need this, you can reimplement + QGraphicsView::viewportEvent() and forward QStatusTipEvent + events and \l{QEvent}{QEvents} of type QEvent::WhatsThis to the + scene. + + \sa QEvent +*/ + +/*! + \class QGraphicsSceneDragDropEvent + \brief The QGraphicsSceneDragDropEvent class provides events for + drag and drop in the graphics view framework. + \since 4.2 + \ingroup graphicsview-api + + QGraphicsView inherits the drag and drop functionality provided + by QWidget. When it receives a drag and drop event, it translates + it to a QGraphicsSceneDragDropEvent. + + QGraphicsSceneDragDropEvent stores events of type + GraphicsSceneDragEnter, GraphicsSceneDragLeave, + GraphicsSceneDragMove, or GraphicsSceneDrop. + + QGraphicsSceneDragDropEvent contains the position of the mouse + cursor in both item, scene, and screen coordinates; this can be + retrieved with pos(), scenePos(), and screenPos(). + + The scene sends the event to the first QGraphicsItem under the + mouse cursor that accepts drops; a graphics item is set to accept + drops with \l{QGraphicsItem::}{setAcceptDrops()}. +*/ + +/*! + \class QGraphicsSceneResizeEvent + \brief The QGraphicsSceneResizeEvent class provides events for widget + resizing in the graphics view framework. + \since 4.4 + \ingroup graphicsview-api + + A QGraphicsWidget sends itself a QGraphicsSceneResizeEvent immediately + when its geometry changes. + + It's similar to QResizeEvent, but its sizes, oldSize() and newSize(), use + QSizeF instead of QSize. + + \sa QGraphicsWidget::setGeometry(), QGraphicsWidget::resize() +*/ + +/*! + \class QGraphicsSceneMoveEvent + \brief The QGraphicsSceneMoveEvent class provides events for widget + moving in the graphics view framework. + \since 4.4 + \ingroup graphicsview-api + + A QGraphicsWidget sends itself a QGraphicsSceneMoveEvent immediately when + its local position changes. The delivery is implemented as part of + QGraphicsItem::itemChange(). + + It's similar to QMoveEvent, but its positions, oldPos() and newPos(), use + QPointF instead of QPoint. + + \sa QGraphicsItem::setPos(), QGraphicsItem::ItemPositionChange, + QGraphicsItem::ItemPositionHasChanged +*/ + +#include "qgraphicssceneevent.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#ifndef QT_NO_DEBUG +#include +#endif +#include +#include +#include +#include +#include "qgraphicsview.h" +#include "qgraphicsitem.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneEventPrivate +{ +public: + inline QGraphicsSceneEventPrivate() + : widget(0), + q_ptr(0) + { } + + inline virtual ~QGraphicsSceneEventPrivate() + { } + + QWidget *widget; + QGraphicsSceneEvent *q_ptr; +}; + +/*! + \internal + + Constructs a generic graphics scene event of the specified \a type. +*/ +QGraphicsSceneEvent::QGraphicsSceneEvent(Type type) + : QEvent(type), d_ptr(new QGraphicsSceneEventPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + \internal + + Constructs a generic graphics scene event. +*/ +QGraphicsSceneEvent::QGraphicsSceneEvent(QGraphicsSceneEventPrivate &dd, Type type) + : QEvent(type), d_ptr(&dd) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys the event. +*/ +QGraphicsSceneEvent::~QGraphicsSceneEvent() +{ +} + +/*! + Returns the widget where the event originated, or 0 if the event + originates from another application. +*/ +QWidget *QGraphicsSceneEvent::widget() const +{ + return d_ptr->widget; +} + +/*! + \internal + + Sets the \a widget related to this event. + + \sa widget() +*/ +void QGraphicsSceneEvent::setWidget(QWidget *widget) +{ + d_ptr->widget = widget; +} + +class QGraphicsSceneMouseEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneMouseEvent) +public: + inline QGraphicsSceneMouseEventPrivate() + : button(Qt::NoButton), + buttons(0), modifiers(0) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + QPointF lastPos; + QPointF lastScenePos; + QPoint lastScreenPos; + QMap buttonDownPos; + QMap buttonDownScenePos; + QMap buttonDownScreenPos; + Qt::MouseButton button; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; +}; + +/*! + \internal + + Constructs a generic graphics scene mouse event of the specified \a type. +*/ +QGraphicsSceneMouseEvent::QGraphicsSceneMouseEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneMouseEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneMouseEvent::~QGraphicsSceneMouseEvent() +{ +} + +/*! + Returns the mouse cursor position in item coordinates. + + \sa scenePos(), screenPos(), lastPos() +*/ +QPointF QGraphicsSceneMouseEvent::pos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->pos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->pos = pos; +} + +/*! + Returns the mouse cursor position in scene coordinates. + + \sa pos(), screenPos(), lastScenePos() +*/ +QPointF QGraphicsSceneMouseEvent::scenePos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->scenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->scenePos = pos; +} + +/*! + Returns the mouse cursor position in screen coordinates. + + \sa pos(), scenePos(), lastScreenPos() +*/ +QPoint QGraphicsSceneMouseEvent::screenPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->screenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->screenPos = pos; +} + +/*! + Returns the mouse cursor position in item coordinates where the specified + \a button was clicked. + + \sa buttonDownScenePos(), buttonDownScreenPos(), pos() +*/ +QPointF QGraphicsSceneMouseEvent::buttonDownPos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownPos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownPos(Qt::MouseButton button, const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownPos.insert(button, pos); +} + +/*! + Returns the mouse cursor position in scene coordinates where the + specified \a button was clicked. + + \sa buttonDownPos(), buttonDownScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneMouseEvent::buttonDownScenePos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownScenePos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownScenePos(Qt::MouseButton button, const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownScenePos.insert(button, pos); +} + +/*! + Returns the mouse cursor position in screen coordinates where the + specified \a button was clicked. + + \sa screenPos(), buttonDownPos(), buttonDownScenePos() +*/ +QPoint QGraphicsSceneMouseEvent::buttonDownScreenPos(Qt::MouseButton button) const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttonDownScreenPos.value(button); +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtonDownScreenPos(Qt::MouseButton button, const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttonDownScreenPos.insert(button, pos); +} + +/*! + Returns the last recorded mouse cursor position in item + coordinates. + + \sa lastScenePos(), lastScreenPos(), pos() +*/ +QPointF QGraphicsSceneMouseEvent::lastPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastPos = pos; +} + +/*! + Returns the last recorded mouse cursor position in scene + coordinates. The last recorded position is the position of + the previous mouse event received by the view that created + the event. + + \sa lastPos(), lastScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneMouseEvent::lastScenePos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastScenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastScenePos = pos; +} + +/*! + Returns the last recorded mouse cursor position in screen + coordinates. The last recorded position is the position of + the previous mouse event received by the view that created + the event. + + \sa lastPos(), lastScenePos(), screenPos() +*/ +QPoint QGraphicsSceneMouseEvent::lastScreenPos() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->lastScreenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setLastScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneMouseEvent); + d->lastScreenPos = pos; +} + +/*! + Returns the combination of mouse buttons that were pressed at the + time the event was sent. + + \sa button(), modifiers() +*/ +Qt::MouseButtons QGraphicsSceneMouseEvent::buttons() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->buttons; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneMouseEvent); + d->buttons = buttons; +} + +/*! + Returns the mouse button (if any) that caused the event. + + \sa buttons(), modifiers() +*/ +Qt::MouseButton QGraphicsSceneMouseEvent::button() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->button; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setButton(Qt::MouseButton button) +{ + Q_D(QGraphicsSceneMouseEvent); + d->button = button; +} + +/*! + Returns the keyboard modifiers in use at the time the event was + sent. + + \sa buttons(), button() +*/ +Qt::KeyboardModifiers QGraphicsSceneMouseEvent::modifiers() const +{ + Q_D(const QGraphicsSceneMouseEvent); + return d->modifiers; +} + +/*! + \internal +*/ +void QGraphicsSceneMouseEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneMouseEvent); + d->modifiers = modifiers; +} + +class QGraphicsSceneWheelEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneWheelEvent) +public: + inline QGraphicsSceneWheelEventPrivate() + : buttons(0), modifiers(0), delta(0), orientation(Qt::Horizontal) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; + int delta; + Qt::Orientation orientation; +}; + +/*! + \internal + + Constructs a QGraphicsSceneWheelEvent of type \a type, which + is always QEvent::GraphicsSceneWheel. +*/ +QGraphicsSceneWheelEvent::QGraphicsSceneWheelEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneWheelEventPrivate, type) +{ +} + +/*! + Destroys the QGraphicsSceneWheelEvent. +*/ +QGraphicsSceneWheelEvent::~QGraphicsSceneWheelEvent() +{ +} + +/*! + Returns the position of the cursor in item coordinates when the + wheel event occurred. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneWheelEvent::pos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->pos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->pos = pos; +} + +/*! + Returns the position of the cursor in scene coordinates when the wheel + event occurred. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneWheelEvent::scenePos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->scenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the cursor in screen coordinates when the wheel + event occurred. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneWheelEvent::screenPos() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->screenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneWheelEvent); + d->screenPos = pos; +} + +/*! + Returns the mouse buttons that were pressed when the wheel event occurred. + + \sa modifiers() +*/ +Qt::MouseButtons QGraphicsSceneWheelEvent::buttons() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->buttons; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneWheelEvent); + d->buttons = buttons; +} + +/*! + Returns the keyboard modifiers that were active when the wheel event + occurred. + + \sa buttons() +*/ +Qt::KeyboardModifiers QGraphicsSceneWheelEvent::modifiers() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->modifiers; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneWheelEvent); + d->modifiers = modifiers; +} + +/*! + Returns the distance that the wheel is rotated, in eighths (1/8s) + of a degree. A positive value indicates that the wheel was + rotated forwards away from the user; a negative value indicates + that the wheel was rotated backwards toward the user. + + Most mouse types work in steps of 15 degrees, in which case the delta + value is a multiple of 120 (== 15 * 8). +*/ +int QGraphicsSceneWheelEvent::delta() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->delta; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setDelta(int delta) +{ + Q_D(QGraphicsSceneWheelEvent); + d->delta = delta; +} + +/*! + Returns the wheel orientation. +*/ +Qt::Orientation QGraphicsSceneWheelEvent::orientation() const +{ + Q_D(const QGraphicsSceneWheelEvent); + return d->orientation; +} + +/*! + \internal +*/ +void QGraphicsSceneWheelEvent::setOrientation(Qt::Orientation orientation) +{ + Q_D(QGraphicsSceneWheelEvent); + d->orientation = orientation; +} + +class QGraphicsSceneContextMenuEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneContextMenuEvent) + public: + inline QGraphicsSceneContextMenuEventPrivate() + : modifiers(0), reason(QGraphicsSceneContextMenuEvent::Other) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::KeyboardModifiers modifiers; + QGraphicsSceneContextMenuEvent::Reason reason; +}; + +/*! + \internal + + Constructs a graphics scene context menu event of the specified \a type. +*/ +QGraphicsSceneContextMenuEvent::QGraphicsSceneContextMenuEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneContextMenuEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneContextMenuEvent::~QGraphicsSceneContextMenuEvent() +{ +} + +/*! + Returns the position of the mouse cursor in item coordinates at the moment + the context menu was requested. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneContextMenuEvent::pos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->pos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setPos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in item coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the moment the + the context menu was requested. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneContextMenuEvent::scenePos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in scene coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the moment the + the context menu was requested. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneContextMenuEvent::screenPos() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneContextMenuEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the context menu to the given \a point + in screen coordinates. +*/ +void QGraphicsSceneContextMenuEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->screenPos = pos; +} + +/*! + Returns the keyboard modifiers in use when the context menu was requested. +*/ +Qt::KeyboardModifiers QGraphicsSceneContextMenuEvent::modifiers() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->modifiers; +} + +/*! + \internal + + Sets the keyboard modifiers associated with the context menu to the \a + modifiers specified. +*/ +void QGraphicsSceneContextMenuEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->modifiers = modifiers; +} + +/*! + Returns the reason for the context menu event. + + \sa QGraphicsSceneContextMenuEvent::Reason +*/ +QGraphicsSceneContextMenuEvent::Reason QGraphicsSceneContextMenuEvent::reason() const +{ + Q_D(const QGraphicsSceneContextMenuEvent); + return d->reason; +} + +/*! + \internal + Sets the reason for the context menu event to \a reason. + + \sa reason() +*/ +void QGraphicsSceneContextMenuEvent::setReason(Reason reason) +{ + Q_D(QGraphicsSceneContextMenuEvent); + d->reason = reason; +} + +class QGraphicsSceneHoverEventPrivate : public QGraphicsSceneEventPrivate +{ +public: + QPointF pos; + QPointF scenePos; + QPoint screenPos; + QPointF lastPos; + QPointF lastScenePos; + QPoint lastScreenPos; + Qt::KeyboardModifiers modifiers; +}; + +/*! + \internal + + Constructs a graphics scene hover event of the specified \a type. +*/ +QGraphicsSceneHoverEvent::QGraphicsSceneHoverEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneHoverEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneHoverEvent::~QGraphicsSceneHoverEvent() +{ +} + +/*! + Returns the position of the mouse cursor in item coordinates at the moment + the hover event was sent. + + \sa scenePos(), screenPos() +*/ +QPointF QGraphicsSceneHoverEvent::pos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->pos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setPos(const QPointF &point) + \internal + + Sets the position associated with the hover event to the given \a point in + item coordinates. +*/ +void QGraphicsSceneHoverEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the + moment the hover event was sent. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneHoverEvent::scenePos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the hover event to the given \a point in + scene coordinates. +*/ +void QGraphicsSceneHoverEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the + moment the hover event was sent. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneHoverEvent::screenPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the hover event to the given \a point in + screen coordinates. +*/ +void QGraphicsSceneHoverEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->screenPos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded mouse cursor position in item coordinates. + + \sa lastScenePos(), lastScreenPos(), pos() +*/ +QPointF QGraphicsSceneHoverEvent::lastPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastPos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastPos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded, the scene coordinates of the previous mouse or + hover event received by the view, that created the event mouse cursor + position in scene coordinates. + + \sa lastPos(), lastScreenPos(), scenePos() +*/ +QPointF QGraphicsSceneHoverEvent::lastScenePos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastScenePos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastScenePos = pos; +} + +/*! + \since 4.4 + + Returns the last recorded mouse cursor position in screen coordinates. The + last recorded position is the position of the previous mouse or hover + event received by the view that created the event. + + \sa lastPos(), lastScenePos(), screenPos() +*/ +QPoint QGraphicsSceneHoverEvent::lastScreenPos() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->lastScreenPos; +} + +/*! + \internal +*/ +void QGraphicsSceneHoverEvent::setLastScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHoverEvent); + d->lastScreenPos = pos; +} + +/*! + \since 4.4 + + Returns the keyboard modifiers at the moment the hover event was sent. +*/ +Qt::KeyboardModifiers QGraphicsSceneHoverEvent::modifiers() const +{ + Q_D(const QGraphicsSceneHoverEvent); + return d->modifiers; +} + +/*! + \fn void QGraphicsSceneHoverEvent::setModifiers(Qt::KeyboardModifiers modifiers) + \internal + + Sets the modifiers for the current hover event to \a modifiers. +*/ +void QGraphicsSceneHoverEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneHoverEvent); + d->modifiers = modifiers; +} + +class QGraphicsSceneHelpEventPrivate : public QGraphicsSceneEventPrivate +{ +public: + QPointF scenePos; + QPoint screenPos; +}; + +/*! + \internal + + Constructs a graphics scene help event of the specified \a type. +*/ +QGraphicsSceneHelpEvent::QGraphicsSceneHelpEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneHelpEventPrivate, type) +{ +} + +/*! + Destroys the event. +*/ +QGraphicsSceneHelpEvent::~QGraphicsSceneHelpEvent() +{ +} + +/*! + Returns the position of the mouse cursor in scene coordinates at the + moment the help event was sent. + + \sa screenPos() +*/ +QPointF QGraphicsSceneHelpEvent::scenePos() const +{ + Q_D(const QGraphicsSceneHelpEvent); + return d->scenePos; +} + +/*! + \fn void QGraphicsSceneHelpEvent::setScenePos(const QPointF &point) + \internal + + Sets the position associated with the context menu to the given \a point + in scene coordinates. +*/ +void QGraphicsSceneHelpEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneHelpEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse cursor in screen coordinates at the + moment the help event was sent. + + \sa scenePos() +*/ +QPoint QGraphicsSceneHelpEvent::screenPos() const +{ + Q_D(const QGraphicsSceneHelpEvent); + return d->screenPos; +} + +/*! + \fn void QGraphicsSceneHelpEvent::setScreenPos(const QPoint &point) + \internal + + Sets the position associated with the context menu to the given \a point + in screen coordinates. +*/ +void QGraphicsSceneHelpEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneHelpEvent); + d->screenPos = pos; +} + +class QGraphicsSceneDragDropEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneDragDropEvent) +public: + inline QGraphicsSceneDragDropEventPrivate() + : source(0), mimeData(0) + { } + + QPointF pos; + QPointF scenePos; + QPoint screenPos; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; + Qt::DropActions possibleActions; + Qt::DropAction proposedAction; + Qt::DropAction dropAction; + QWidget *source; + const QMimeData *mimeData; +}; + +/*! + \internal + + Constructs a new QGraphicsSceneDragDropEvent of the + specified \a type. The type can be either + QEvent::GraphicsSceneDragEnter, QEvent::GraphicsSceneDragLeave, + QEvent::GraphicsSceneDragMove, or QEvent::GraphicsSceneDrop. +*/ +QGraphicsSceneDragDropEvent::QGraphicsSceneDragDropEvent(Type type) + : QGraphicsSceneEvent(*new QGraphicsSceneDragDropEventPrivate, type) +{ +} + +/*! + Destroys the object. +*/ +QGraphicsSceneDragDropEvent::~QGraphicsSceneDragDropEvent() +{ +} + +/*! + Returns the mouse position of the event relative to the + view that sent the event. + + \sa QGraphicsView, screenPos(), scenePos() +*/ +QPointF QGraphicsSceneDragDropEvent::pos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->pos; +} + +/*! + \internal + Sets the position of the mouse to \a pos; this should be + relative to the widget that generated the event, which normally + is a QGraphicsView. + + \sa pos(), setScenePos(), setScreenPos() +*/ + +void QGraphicsSceneDragDropEvent::setPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->pos = pos; +} + +/*! + Returns the position of the mouse in scene coordinates. + + \sa pos(), screenPos() +*/ +QPointF QGraphicsSceneDragDropEvent::scenePos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->scenePos; +} + +/*! + \internal + Sets the scene position of the mouse to \a pos. + + \sa scenePos(), setScreenPos(), setPos() +*/ +void QGraphicsSceneDragDropEvent::setScenePos(const QPointF &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->scenePos = pos; +} + +/*! + Returns the position of the mouse relative to the screen. + + \sa pos(), scenePos() +*/ +QPoint QGraphicsSceneDragDropEvent::screenPos() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->screenPos; +} + +/*! + \internal + Sets the mouse position relative to the screen to \a pos. + + \sa screenPos(), setScenePos(), setPos() +*/ +void QGraphicsSceneDragDropEvent::setScreenPos(const QPoint &pos) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->screenPos = pos; +} + +/*! + Returns a Qt::MouseButtons value indicating which buttons + were pressed on the mouse when this mouse event was + generated. + + \sa Qt::MouseButtons +*/ +Qt::MouseButtons QGraphicsSceneDragDropEvent::buttons() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->buttons; +} + +/*! + \internal + Sets the mouse buttons that were pressed when the event was + created to \a buttons. + + \sa Qt::MouseButtons, buttons() +*/ +void QGraphicsSceneDragDropEvent::setButtons(Qt::MouseButtons buttons) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->buttons = buttons; +} + +/*! + Returns the keyboard modifiers that were pressed when the drag + and drop event was created. + + \sa Qt::KeyboardModifiers +*/ +Qt::KeyboardModifiers QGraphicsSceneDragDropEvent::modifiers() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->modifiers; +} + +/*! + \internal + Sets the keyboard modifiers that were pressed when the event + was created to \a modifiers. + + \sa Qt::KeyboardModifiers, modifiers() +*/ + +void QGraphicsSceneDragDropEvent::setModifiers(Qt::KeyboardModifiers modifiers) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->modifiers = modifiers; +} + +/*! + Returns the possible drop actions that the drag and + drop can result in. + + \sa Qt::DropActions +*/ + +Qt::DropActions QGraphicsSceneDragDropEvent::possibleActions() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->possibleActions; +} + +/*! + \internal + Sets the possible drop actions that the drag can + result in to \a actions. + + \sa Qt::DropActions, possibleActions() +*/ +void QGraphicsSceneDragDropEvent::setPossibleActions(Qt::DropActions actions) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->possibleActions = actions; +} + +/*! + Returns the drop action that is proposed, i.e., preferred. + The action must be one of the possible actions as defined by + \c possibleActions(). + + \sa Qt::DropAction, possibleActions() +*/ + +Qt::DropAction QGraphicsSceneDragDropEvent::proposedAction() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->proposedAction; +} + +/*! + \internal + Sets the proposed action to \a action. The proposed action + is a Qt::DropAction that is one of the possible actions as + given by \c possibleActions(). + + \sa proposedAction(), Qt::DropAction, possibleActions() +*/ + +void QGraphicsSceneDragDropEvent::setProposedAction(Qt::DropAction action) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->proposedAction = action; +} + +/*! + Sets the proposed action as accepted, i.e, the drop action + is set to the proposed action. This is equal to: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicssceneevent.cpp 0 + + When using this function, one should not call \c accept(). + + \sa dropAction(), setDropAction(), proposedAction() +*/ + +void QGraphicsSceneDragDropEvent::acceptProposedAction() +{ + Q_D(QGraphicsSceneDragDropEvent); + d->dropAction = d->proposedAction; +} + +/*! + Returns the action that was performed in this drag and drop. + This should be set by the receiver of the drop and is + returned by QDrag::exec(). + + \sa setDropAction(), acceptProposedAction() +*/ + +Qt::DropAction QGraphicsSceneDragDropEvent::dropAction() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->dropAction; +} + +/*! + This function lets the receiver of the drop set the drop + action that was performed to \a action, which should be one + of the + \l{QGraphicsSceneDragDropEvent::possibleActions()}{possible + actions}. Call \c accept() in stead of \c + acceptProposedAction() if you use this function. + + \sa dropAction(), accept(), possibleActions() +*/ +void QGraphicsSceneDragDropEvent::setDropAction(Qt::DropAction action) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->dropAction = action; +} + +/*! + This function returns the QGraphicsView that created the + QGraphicsSceneDragDropEvent. +*/ +QWidget *QGraphicsSceneDragDropEvent::source() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->source; +} + +/*! + \internal + This function set the source widget, i.e., the widget that + created the drop event, to \a source. +*/ +void QGraphicsSceneDragDropEvent::setSource(QWidget *source) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->source = source; +} + +/*! + This function returns the MIME data of the event. +*/ +const QMimeData *QGraphicsSceneDragDropEvent::mimeData() const +{ + Q_D(const QGraphicsSceneDragDropEvent); + return d->mimeData; +} + +/*! + \internal + This function sets the MIME data for the event. +*/ +void QGraphicsSceneDragDropEvent::setMimeData(const QMimeData *data) +{ + Q_D(QGraphicsSceneDragDropEvent); + d->mimeData = data; +} + +class QGraphicsSceneResizeEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneResizeEvent) +public: + inline QGraphicsSceneResizeEventPrivate() + { } + + QSizeF oldSize; + QSizeF newSize; +}; + +/*! + Constructs a QGraphicsSceneResizeEvent. +*/ +QGraphicsSceneResizeEvent::QGraphicsSceneResizeEvent() + : QGraphicsSceneEvent(*new QGraphicsSceneResizeEventPrivate, QEvent::GraphicsSceneResize) +{ +} + +/*! + Destroys the QGraphicsSceneResizeEvent. +*/ +QGraphicsSceneResizeEvent::~QGraphicsSceneResizeEvent() +{ +} + +/*! + Returns the old size (i.e., the size immediately before the widget was + resized). + + \sa newSize(), QGraphicsWidget::resize() +*/ +QSizeF QGraphicsSceneResizeEvent::oldSize() const +{ + Q_D(const QGraphicsSceneResizeEvent); + return d->oldSize; +} + +/*! + \internal +*/ +void QGraphicsSceneResizeEvent::setOldSize(const QSizeF &size) +{ + Q_D(QGraphicsSceneResizeEvent); + d->oldSize = size; +} + +/*! + Returns the new size (i.e., the current size). + + \sa oldSize(), QGraphicsWidget::resize() +*/ +QSizeF QGraphicsSceneResizeEvent::newSize() const +{ + Q_D(const QGraphicsSceneResizeEvent); + return d->newSize; +} + +/*! + \internal +*/ +void QGraphicsSceneResizeEvent::setNewSize(const QSizeF &size) +{ + Q_D(QGraphicsSceneResizeEvent); + d->newSize = size; +} + +class QGraphicsSceneMoveEventPrivate : public QGraphicsSceneEventPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneMoveEvent) +public: + inline QGraphicsSceneMoveEventPrivate() + { } + + QPointF oldPos; + QPointF newPos; +}; + +/*! + Constructs a QGraphicsSceneMoveEvent. +*/ +QGraphicsSceneMoveEvent::QGraphicsSceneMoveEvent() + : QGraphicsSceneEvent(*new QGraphicsSceneMoveEventPrivate, QEvent::GraphicsSceneMove) +{ +} + +/*! + Destroys the QGraphicsSceneMoveEvent. +*/ +QGraphicsSceneMoveEvent::~QGraphicsSceneMoveEvent() +{ +} + +/*! + Returns the old position (i.e., the position immediately before the widget + was moved). + + \sa newPos(), QGraphicsItem::setPos() +*/ +QPointF QGraphicsSceneMoveEvent::oldPos() const +{ + Q_D(const QGraphicsSceneMoveEvent); + return d->oldPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMoveEvent::setOldPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMoveEvent); + d->oldPos = pos; +} + +/*! + Returns the new position (i.e., the current position). + + \sa oldPos(), QGraphicsItem::setPos() +*/ +QPointF QGraphicsSceneMoveEvent::newPos() const +{ + Q_D(const QGraphicsSceneMoveEvent); + return d->newPos; +} + +/*! + \internal +*/ +void QGraphicsSceneMoveEvent::setNewPos(const QPointF &pos) +{ + Q_D(QGraphicsSceneMoveEvent); + d->newPos = pos; +} + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicssceneevent.h b/src/gui/graphicsview/qgraphicssceneevent.h new file mode 100644 index 0000000000..afaa33c9ce --- /dev/null +++ b/src/gui/graphicsview/qgraphicssceneevent.h @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEEVENT_H +#define QGRAPHICSSCENEEVENT_H + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QMimeData; +class QPointF; +class QSizeF; +class QWidget; + +class QGraphicsSceneEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneEvent : public QEvent +{ +public: + QGraphicsSceneEvent(Type type); + ~QGraphicsSceneEvent(); + + QWidget *widget() const; + void setWidget(QWidget *widget); + +protected: + QGraphicsSceneEvent(QGraphicsSceneEventPrivate &dd, Type type = None); + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QGraphicsSceneEvent) +private: + Q_DISABLE_COPY(QGraphicsSceneEvent) +}; + +class QGraphicsSceneMouseEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneMouseEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneMouseEvent(Type type = None); + ~QGraphicsSceneMouseEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + QPointF buttonDownPos(Qt::MouseButton button) const; + void setButtonDownPos(Qt::MouseButton button, const QPointF &pos); + + QPointF buttonDownScenePos(Qt::MouseButton button) const; + void setButtonDownScenePos(Qt::MouseButton button, const QPointF &pos); + + QPoint buttonDownScreenPos(Qt::MouseButton button) const; + void setButtonDownScreenPos(Qt::MouseButton button, const QPoint &pos); + + QPointF lastPos() const; + void setLastPos(const QPointF &pos); + + QPointF lastScenePos() const; + void setLastScenePos(const QPointF &pos); + + QPoint lastScreenPos() const; + void setLastScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneMouseEvent) + Q_DISABLE_COPY(QGraphicsSceneMouseEvent) +}; + +class QGraphicsSceneWheelEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneWheelEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneWheelEvent(Type type = None); + ~QGraphicsSceneWheelEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + int delta() const; + void setDelta(int delta); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneWheelEvent) + Q_DISABLE_COPY(QGraphicsSceneWheelEvent) +}; + +class QGraphicsSceneContextMenuEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneContextMenuEvent : public QGraphicsSceneEvent +{ +public: + enum Reason { Mouse, Keyboard, Other }; + + QGraphicsSceneContextMenuEvent(Type type = None); + ~QGraphicsSceneContextMenuEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + Reason reason() const; + void setReason(Reason reason); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneContextMenuEvent) + Q_DISABLE_COPY(QGraphicsSceneContextMenuEvent) +}; + +class QGraphicsSceneHoverEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneHoverEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneHoverEvent(Type type = None); + ~QGraphicsSceneHoverEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + QPointF lastPos() const; + void setLastPos(const QPointF &pos); + + QPointF lastScenePos() const; + void setLastScenePos(const QPointF &pos); + + QPoint lastScreenPos() const; + void setLastScreenPos(const QPoint &pos); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneHoverEvent) + Q_DISABLE_COPY(QGraphicsSceneHoverEvent) +}; + +class QGraphicsSceneHelpEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneHelpEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneHelpEvent(Type type = None); + ~QGraphicsSceneHelpEvent(); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneHelpEvent) + Q_DISABLE_COPY(QGraphicsSceneHelpEvent) +}; + +class QGraphicsSceneDragDropEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneDragDropEvent : public QGraphicsSceneEvent +{ +public: + QGraphicsSceneDragDropEvent(Type type = None); + ~QGraphicsSceneDragDropEvent(); + + QPointF pos() const; + void setPos(const QPointF &pos); + + QPointF scenePos() const; + void setScenePos(const QPointF &pos); + + QPoint screenPos() const; + void setScreenPos(const QPoint &pos); + + Qt::MouseButtons buttons() const; + void setButtons(Qt::MouseButtons buttons); + + Qt::KeyboardModifiers modifiers() const; + void setModifiers(Qt::KeyboardModifiers modifiers); + + Qt::DropActions possibleActions() const; + void setPossibleActions(Qt::DropActions actions); + + Qt::DropAction proposedAction() const; + void setProposedAction(Qt::DropAction action); + void acceptProposedAction(); + + Qt::DropAction dropAction() const; + void setDropAction(Qt::DropAction action); + + QWidget *source() const; + void setSource(QWidget *source); + + const QMimeData *mimeData() const; + void setMimeData(const QMimeData *data); + +private: + Q_DECLARE_PRIVATE(QGraphicsSceneDragDropEvent) + Q_DISABLE_COPY(QGraphicsSceneDragDropEvent) +}; + +class QGraphicsSceneResizeEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneResizeEvent : public QGraphicsSceneEvent +{ + Q_DECLARE_PRIVATE(QGraphicsSceneResizeEvent) + Q_DISABLE_COPY(QGraphicsSceneResizeEvent) +public: + QGraphicsSceneResizeEvent(); + ~QGraphicsSceneResizeEvent(); + + QSizeF oldSize() const; + void setOldSize(const QSizeF &size); + + QSizeF newSize() const; + void setNewSize(const QSizeF &size); +}; + +class QGraphicsSceneMoveEventPrivate; +class Q_GUI_EXPORT QGraphicsSceneMoveEvent : public QGraphicsSceneEvent +{ + Q_DECLARE_PRIVATE(QGraphicsSceneMoveEvent) + Q_DISABLE_COPY(QGraphicsSceneMoveEvent) +public: + QGraphicsSceneMoveEvent(); + ~QGraphicsSceneMoveEvent(); + + QPointF oldPos() const; + void setOldPos(const QPointF &pos); + + QPointF newPos() const; + void setNewPos(const QPointF &pos); +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/graphicsview/qgraphicssceneindex.cpp b/src/gui/graphicsview/qgraphicssceneindex.cpp new file mode 100644 index 0000000000..964e9cb0ef --- /dev/null +++ b/src/gui/graphicsview/qgraphicssceneindex.cpp @@ -0,0 +1,648 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! + \class QGraphicsSceneIndex + \brief The QGraphicsSceneIndex class provides a base class to implement + a custom indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + + \internal + + The QGraphicsSceneIndex class provides a base class to implement + a custom indexing algorithm for discovering items in QGraphicsScene. You + need to subclass it and reimplement addItem, removeItem, estimateItems + and items in order to have an functional indexing. + + \sa QGraphicsScene, QGraphicsView +*/ + +#include "qdebug.h" +#include "qgraphicsscene.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene_p.h" +#include "qgraphicswidget.h" +#include "qgraphicssceneindex_p.h" +#include "qgraphicsscenebsptreeindex_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneIndexRectIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = true; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene rect to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QRectF itemRect = (deviceTransform * transform.inverted()).mapRect(sceneRect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = itemRect.contains(brect) && itemRect != brect; + else + keep = itemRect.intersects(brect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath itemPath; + itemPath.addRect(itemRect); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + const QRectF itemSceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = sceneRect != brect && sceneRect.contains(itemSceneBoundingRect); + else + keep = sceneRect.intersects(itemSceneBoundingRect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath rectPath; + rectPath.addRect(sceneRect); + if (itemd->sceneTransformTranslateOnly) + rectPath.translate(-itemd->sceneTransform.dx(), -itemd->sceneTransform.dy()); + else + rectPath = itemd->sceneTransform.inverted().map(rectPath); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, rectPath, mode); + } + } + return keep; + } + + QRectF sceneRect; +}; + +class QGraphicsSceneIndexPointIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = false; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene point to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QPointF itemPoint = (deviceTransform * transform.inverted()).map(scenePoint); + keep = brect.contains(itemPoint); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath pointPath; + pointPath.addRect(QRectF(itemPoint, QSizeF(1, 1))); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, pointPath, mode); + } + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + QRectF sceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + keep = sceneBoundingRect.intersects(QRectF(scenePoint, QSizeF(1, 1))); + if (keep) { + QPointF p = itemd->sceneTransformTranslateOnly + ? QPointF(scenePoint.x() - itemd->sceneTransform.dx(), + scenePoint.y() - itemd->sceneTransform.dy()) + : itemd->sceneTransform.inverted().map(scenePoint); + keep = item->contains(p); + } + } + + return keep; + } + + QPointF scenePoint; +}; + +class QGraphicsSceneIndexPathIntersector : public QGraphicsSceneIndexIntersector +{ +public: + bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const + { + QRectF brect = item->boundingRect(); + _q_adjustRect(&brect); + + // ### Add test for this (without making things slower?) + Q_UNUSED(exposeRect); + + bool keep = true; + const QGraphicsItemPrivate *itemd = QGraphicsItemPrivate::get(item); + if (itemd->itemIsUntransformable()) { + // Untransformable items; map the scene rect to item coordinates. + const QTransform transform = item->deviceTransform(deviceTransform); + QPainterPath itemPath = (deviceTransform * transform.inverted()).map(scenePath); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = itemPath.contains(brect); + else + keep = itemPath.intersects(brect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } else { + Q_ASSERT(!itemd->dirtySceneTransform); + const QRectF itemSceneBoundingRect = itemd->sceneTransformTranslateOnly + ? brect.translated(itemd->sceneTransform.dx(), + itemd->sceneTransform.dy()) + : itemd->sceneTransform.mapRect(brect); + if (mode == Qt::ContainsItemShape || mode == Qt::ContainsItemBoundingRect) + keep = scenePath.contains(itemSceneBoundingRect); + else + keep = scenePath.intersects(itemSceneBoundingRect); + if (keep && (mode == Qt::ContainsItemShape || mode == Qt::IntersectsItemShape)) { + QPainterPath itemPath = itemd->sceneTransformTranslateOnly + ? scenePath.translated(-itemd->sceneTransform.dx(), + -itemd->sceneTransform.dy()) + : itemd->sceneTransform.inverted().map(scenePath); + keep = QGraphicsSceneIndexPrivate::itemCollidesWithPath(item, itemPath, mode); + } + } + return keep; + } + + QPainterPath scenePath; +}; + +/*! + Constructs a private scene index. +*/ +QGraphicsSceneIndexPrivate::QGraphicsSceneIndexPrivate(QGraphicsScene *scene) : scene(scene) +{ + pointIntersector = new QGraphicsSceneIndexPointIntersector; + rectIntersector = new QGraphicsSceneIndexRectIntersector; + pathIntersector = new QGraphicsSceneIndexPathIntersector; +} + +/*! + Destructor of private scene index. +*/ +QGraphicsSceneIndexPrivate::~QGraphicsSceneIndexPrivate() +{ + delete pointIntersector; + delete rectIntersector; + delete pathIntersector; +} + +/*! + \internal + + Checks if item collides with the path and mode, but also checks that if it + doesn't collide, maybe its frame rect will. +*/ +bool QGraphicsSceneIndexPrivate::itemCollidesWithPath(const QGraphicsItem *item, + const QPainterPath &path, + Qt::ItemSelectionMode mode) +{ + if (item->collidesWithPath(path, mode)) + return true; + if (item->isWidget()) { + // Check if this is a window, and if its frame rect collides. + const QGraphicsWidget *widget = static_cast(item); + if (widget->isWindow()) { + QRectF frameRect = widget->windowFrameRect(); + QPainterPath framePath; + framePath.addRect(frameRect); + bool intersects = path.intersects(frameRect); + if (mode == Qt::IntersectsItemShape || mode == Qt::IntersectsItemBoundingRect) + return intersects || path.contains(frameRect.topLeft()) + || framePath.contains(path.elementAt(0)); + return !intersects && path.contains(frameRect.topLeft()); + } + } + return false; +} + +/*! + \internal + This function returns the items in ascending order. +*/ +void QGraphicsSceneIndexPrivate::recursive_items_helper(QGraphicsItem *item, QRectF exposeRect, + QGraphicsSceneIndexIntersector *intersector, + QList *items, + const QTransform &viewTransform, + Qt::ItemSelectionMode mode, + qreal parentOpacity) const +{ + Q_ASSERT(item); + if (!item->d_ptr->visible) + return; + + const qreal opacity = item->d_ptr->combineOpacityFromParent(parentOpacity); + const bool itemIsFullyTransparent = QGraphicsItemPrivate::isOpacityNull(opacity); + const bool itemHasChildren = !item->d_ptr->children.isEmpty(); + if (itemIsFullyTransparent && (!itemHasChildren || item->d_ptr->childrenCombineOpacity())) + return; + + // Update the item's scene transform if dirty. + const bool itemIsUntransformable = item->d_ptr->itemIsUntransformable(); + const bool wasDirtyParentSceneTransform = item->d_ptr->dirtySceneTransform && !itemIsUntransformable; + if (wasDirtyParentSceneTransform) { + item->d_ptr->updateSceneTransformFromParent(); + Q_ASSERT(!item->d_ptr->dirtySceneTransform); + } + + const bool itemClipsChildrenToShape = (item->d_ptr->flags & QGraphicsItem::ItemClipsChildrenToShape); + bool processItem = !itemIsFullyTransparent; + if (processItem) { + processItem = intersector->intersect(item, exposeRect, mode, viewTransform); + if (!processItem && (!itemHasChildren || itemClipsChildrenToShape)) { + if (wasDirtyParentSceneTransform) + item->d_ptr->invalidateChildrenSceneTransform(); + return; + } + } // else we know for sure this item has children we must process. + + int i = 0; + if (itemHasChildren) { + // Sort children. + item->d_ptr->ensureSortedChildren(); + + // Clip to shape. + if (itemClipsChildrenToShape && !itemIsUntransformable) { + QPainterPath mappedShape = item->d_ptr->sceneTransformTranslateOnly + ? item->shape().translated(item->d_ptr->sceneTransform.dx(), + item->d_ptr->sceneTransform.dy()) + : item->d_ptr->sceneTransform.map(item->shape()); + exposeRect &= mappedShape.controlPointRect(); + } + + // Process children behind + for (i = 0; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (!(child->d_ptr->flags & QGraphicsItem::ItemStacksBehindParent)) + break; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + recursive_items_helper(child, exposeRect, intersector, items, viewTransform, + mode, opacity); + } + } + + // Process item + if (processItem) + items->append(item); + + // Process children in front + if (itemHasChildren) { + for (; i < item->d_ptr->children.size(); ++i) { + QGraphicsItem *child = item->d_ptr->children.at(i); + if (wasDirtyParentSceneTransform) + child->d_ptr->dirtySceneTransform = 1; + if (itemIsFullyTransparent && !(child->d_ptr->flags & QGraphicsItem::ItemIgnoresParentOpacity)) + continue; + recursive_items_helper(child, exposeRect, intersector, items, viewTransform, + mode, opacity); + } + } +} + +void QGraphicsSceneIndexPrivate::init() +{ + if (!scene) + return; + + QObject::connect(scene, SIGNAL(sceneRectChanged(QRectF)), + q_func(), SLOT(updateSceneRect(QRectF))); +} + +/*! + Constructs an abstract scene index for a given \a scene. +*/ +QGraphicsSceneIndex::QGraphicsSceneIndex(QGraphicsScene *scene) +: QObject(*new QGraphicsSceneIndexPrivate(scene), scene) +{ + d_func()->init(); +} + +/*! + \internal +*/ +QGraphicsSceneIndex::QGraphicsSceneIndex(QGraphicsSceneIndexPrivate &dd, QGraphicsScene *scene) + : QObject(dd, scene) +{ + d_func()->init(); +} + +/*! + Destroys the scene index. +*/ +QGraphicsSceneIndex::~QGraphicsSceneIndex() +{ + +} + +/*! + Returns the scene of this index. +*/ +QGraphicsScene* QGraphicsSceneIndex::scene() const +{ + Q_D(const QGraphicsSceneIndex); + return d->scene; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPointF &pos, + Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + Returns all visible items that, depending on \a mode, are at the specified + \a pos and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with \a pos are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine the + list to get an exact result. If you want to implement your own refinement + algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + + Q_D(const QGraphicsSceneIndex); + QList itemList; + d->pointIntersector->scenePoint = pos; + d->items_helper(QRectF(pos, QSizeF(1, 1)), d->pointIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QRectF &rect, + Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a rect and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QRectF exposeRect = rect; + _q_adjustRect(&exposeRect); + QList itemList; + d->rectIntersector->sceneRect = rect; + d->items_helper(exposeRect, d->rectIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPolygonF + &polygon, Qt::ItemSelectionMode mode, Qt::SortOrder order, const + QTransform &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a polygon and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QList itemList; + QRectF exposeRect = polygon.boundingRect(); + _q_adjustRect(&exposeRect); + QPainterPath path; + path.addPolygon(polygon); + d->pathIntersector->scenePath = path; + d->items_helper(exposeRect, d->pathIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + \fn QList QGraphicsSceneIndex::items(const QPainterPath + &path, Qt::ItemSelectionMode mode, Qt::SortOrder order, const QTransform + &deviceTransform) const + + \overload + + Returns all visible items that, depending on \a mode, are either inside or + intersect with the specified \a path and return a list sorted using \a order. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \a deviceTransform is the transformation apply to the view. + + This method use the estimation of the index (estimateItems) and refine + the list to get an exact result. If you want to implement your own + refinement algorithm you can reimplement this method. + + \sa estimateItems() + +*/ +QList QGraphicsSceneIndex::items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform) const +{ + Q_D(const QGraphicsSceneIndex); + QList itemList; + QRectF exposeRect = path.controlPointRect(); + _q_adjustRect(&exposeRect); + d->pathIntersector->scenePath = path; + d->items_helper(exposeRect, d->pathIntersector, &itemList, deviceTransform, mode, order); + return itemList; +} + +/*! + This virtual function return an estimation of items at position \a point. + This method return a list sorted using \a order. +*/ +QList QGraphicsSceneIndex::estimateItems(const QPointF &point, Qt::SortOrder order) const +{ + return estimateItems(QRectF(point, QSize(1, 1)), order); +} + +QList QGraphicsSceneIndex::estimateTopLevelItems(const QRectF &rect, Qt::SortOrder order) const +{ + Q_D(const QGraphicsSceneIndex); + Q_UNUSED(rect); + QGraphicsScenePrivate *scened = d->scene->d_func(); + scened->ensureSortedTopLevelItems(); + if (order == Qt::DescendingOrder) { + QList sorted; + for (int i = scened->topLevelItems.size() - 1; i >= 0; --i) + sorted << scened->topLevelItems.at(i); + return sorted; + } + return scened->topLevelItems; +} + +/*! + \fn QList QGraphicsSceneIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const + + This pure virtual function all items in the index and sort them using + \a order. +*/ + + +/*! + Notifies the index that the scene's scene rect has changed. \a rect + is thew new scene rect. + + \sa QGraphicsScene::sceneRect() +*/ +void QGraphicsSceneIndex::updateSceneRect(const QRectF &rect) +{ + Q_UNUSED(rect); +} + +/*! + This virtual function removes all items in the scene index. +*/ +void QGraphicsSceneIndex::clear() +{ + const QList allItems = items(); + for (int i = 0 ; i < allItems.size(); ++i) + removeItem(allItems.at(i)); +} + +/*! + \fn virtual void QGraphicsSceneIndex::addItem(QGraphicsItem *item) = 0 + + This pure virtual function inserts an \a item to the scene index. + + \sa removeItem(), deleteItem() +*/ + +/*! + \fn virtual void QGraphicsSceneIndex::removeItem(QGraphicsItem *item) = 0 + + This pure virtual function removes an \a item to the scene index. + + \sa addItem(), deleteItem() +*/ + +/*! + This method is called when an \a item has been deleted. + The default implementation call removeItem. Be carefull, + if your implementation of removeItem use pure virtual method + of QGraphicsItem like boundingRect(), then you should reimplement + this method. + + \sa addItem(), removeItem() +*/ +void QGraphicsSceneIndex::deleteItem(QGraphicsItem *item) +{ + removeItem(item); +} + +/*! + This virtual function is called by QGraphicsItem to notify the index + that some part of the \a item 's state changes. By reimplementing this + function, your can react to a change, and in some cases, (depending on \a + change,) adjustments in the index can be made. + + \a change is the parameter of the item that is changing. \a value is the + value that changed; the type of the value depends on \a change. + + The default implementation does nothing. + + \sa QGraphicsItem::GraphicsItemChange +*/ +void QGraphicsSceneIndex::itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange change, const void *const value) +{ + Q_UNUSED(item); + Q_UNUSED(change); + Q_UNUSED(value); +} + +/*! + Notify the index for a geometry change of an \a item. + + \sa QGraphicsItem::prepareGeometryChange() +*/ +void QGraphicsSceneIndex::prepareBoundingRectChange(const QGraphicsItem *item) +{ + Q_UNUSED(item); +} + +QT_END_NAMESPACE + +#include "moc_qgraphicssceneindex_p.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicssceneindex_p.h b/src/gui/graphicsview/qgraphicssceneindex_p.h new file mode 100644 index 0000000000..e498e822fa --- /dev/null +++ b/src/gui/graphicsview/qgraphicssceneindex_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENEINDEX_H +#define QGRAPHICSSCENEINDEX_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsscene_p.h" +#include "qgraphicsscene.h" +#include + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsSceneIndexIntersector; +class QGraphicsSceneIndexPointIntersector; +class QGraphicsSceneIndexRectIntersector; +class QGraphicsSceneIndexPathIntersector; +class QGraphicsSceneIndexPrivate; +class QPointF; +class QRectF; +template class QList; + +class Q_AUTOTEST_EXPORT QGraphicsSceneIndex : public QObject +{ + Q_OBJECT + +public: + QGraphicsSceneIndex(QGraphicsScene *scene = 0); + virtual ~QGraphicsSceneIndex(); + + QGraphicsScene *scene() const; + + virtual QList items(Qt::SortOrder order = Qt::DescendingOrder) const = 0; + virtual QList items(const QPointF &pos, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QRectF &rect, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QPolygonF &polygon, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList items(const QPainterPath &path, Qt::ItemSelectionMode mode, + Qt::SortOrder order, const QTransform &deviceTransform = QTransform()) const; + virtual QList estimateItems(const QPointF &point, Qt::SortOrder order) const; + virtual QList estimateItems(const QRectF &rect, Qt::SortOrder order) const = 0; + virtual QList estimateTopLevelItems(const QRectF &, Qt::SortOrder order) const; + +protected Q_SLOTS: + virtual void updateSceneRect(const QRectF &rect); + +protected: + virtual void clear(); + virtual void addItem(QGraphicsItem *item) = 0; + virtual void removeItem(QGraphicsItem *item) = 0; + virtual void deleteItem(QGraphicsItem *item); + + virtual void itemChange(const QGraphicsItem *item, QGraphicsItem::GraphicsItemChange, const void *const value); + virtual void prepareBoundingRectChange(const QGraphicsItem *item); + + QGraphicsSceneIndex(QGraphicsSceneIndexPrivate &dd, QGraphicsScene *scene); + + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsSceneBspTreeIndex; +private: + Q_DISABLE_COPY(QGraphicsSceneIndex) + Q_DECLARE_PRIVATE(QGraphicsSceneIndex) +}; + +class QGraphicsSceneIndexPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsSceneIndex) +public: + QGraphicsSceneIndexPrivate(QGraphicsScene *scene); + ~QGraphicsSceneIndexPrivate(); + + void init(); + static bool itemCollidesWithPath(const QGraphicsItem *item, const QPainterPath &path, Qt::ItemSelectionMode mode); + + void recursive_items_helper(QGraphicsItem *item, QRectF exposeRect, + QGraphicsSceneIndexIntersector *intersector, QList *items, + const QTransform &viewTransform, + Qt::ItemSelectionMode mode, qreal parentOpacity = 1.0) const; + inline void items_helper(const QRectF &rect, QGraphicsSceneIndexIntersector *intersector, + QList *items, const QTransform &viewTransform, + Qt::ItemSelectionMode mode, Qt::SortOrder order) const; + + QGraphicsScene *scene; + QGraphicsSceneIndexPointIntersector *pointIntersector; + QGraphicsSceneIndexRectIntersector *rectIntersector; + QGraphicsSceneIndexPathIntersector *pathIntersector; +}; + +inline void QGraphicsSceneIndexPrivate::items_helper(const QRectF &rect, QGraphicsSceneIndexIntersector *intersector, + QList *items, const QTransform &viewTransform, + Qt::ItemSelectionMode mode, Qt::SortOrder order) const +{ + Q_Q(const QGraphicsSceneIndex); + const QList tli = q->estimateTopLevelItems(rect, Qt::AscendingOrder); + for (int i = 0; i < tli.size(); ++i) + recursive_items_helper(tli.at(i), rect, intersector, items, viewTransform, mode); + if (order == Qt::DescendingOrder) { + const int n = items->size(); + for (int i = 0; i < n / 2; ++i) + items->swap(i, n - i - 1); + } +} + +class QGraphicsSceneIndexIntersector +{ +public: + QGraphicsSceneIndexIntersector() { } + virtual ~QGraphicsSceneIndexIntersector() { } + virtual bool intersect(const QGraphicsItem *item, const QRectF &exposeRect, Qt::ItemSelectionMode mode, + const QTransform &deviceTransform) const = 0; +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSCENEINDEX_H diff --git a/src/gui/graphicsview/qgraphicsscenelinearindex.cpp b/src/gui/graphicsview/qgraphicsscenelinearindex.cpp new file mode 100644 index 0000000000..074d43961a --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscenelinearindex.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsSceneLinearIndex + \brief The QGraphicsSceneLinearIndex class provides an implementation of + a linear indexing algorithm for discovering items in QGraphicsScene. + \since 4.6 + \ingroup graphicsview-api + \internal + + QGraphicsSceneLinearIndex index is default linear implementation to discover items. + It basically store all items in a list and return them to the scene. + + \sa QGraphicsScene, QGraphicsView, QGraphicsSceneIndex, QGraphicsSceneBspTreeIndex +*/ + +#include + +/*! + \fn QGraphicsSceneLinearIndex::QGraphicsSceneLinearIndex(QGraphicsScene *scene = 0): + + Construct a linear index for the given \a scene. +*/ + +/*! + \fn QList QGraphicsSceneLinearIndex::items(Qt::SortOrder order = Qt::DescendingOrder) const; + + Return all items in the index and sort them using \a order. +*/ + + +/*! + \fn virtual QList QGraphicsSceneLinearIndex::estimateItems(const QRectF &rect, Qt::SortOrder order) const + + Returns an estimation visible items that are either inside or + intersect with the specified \a rect and return a list sorted using \a order. +*/ + +/*! + \fn void QGraphicsSceneLinearIndex::clear() + \internal + Clear the all the BSP index. +*/ + +/*! + \fn virtual void QGraphicsSceneLinearIndex::addItem(QGraphicsItem *item) + + Add the \a item into the index. +*/ + +/*! + \fn virtual void QGraphicsSceneLinearIndex::removeItem(QGraphicsItem *item) + + Add the \a item from the index. +*/ + diff --git a/src/gui/graphicsview/qgraphicsscenelinearindex_p.h b/src/gui/graphicsview/qgraphicsscenelinearindex_p.h new file mode 100644 index 0000000000..ef72f57faf --- /dev/null +++ b/src/gui/graphicsview/qgraphicsscenelinearindex_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSCENELINEARINDEX_H +#define QGRAPHICSSCENELINEARINDEX_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_AUTOTEST_EXPORT QGraphicsSceneLinearIndex : public QGraphicsSceneIndex +{ + Q_OBJECT + +public: + QGraphicsSceneLinearIndex(QGraphicsScene *scene = 0) : QGraphicsSceneIndex(scene) + { } + + QList items(Qt::SortOrder order = Qt::DescendingOrder) const + { Q_UNUSED(order); return m_items; } + + virtual QList estimateItems(const QRectF &rect, Qt::SortOrder order) const + { + Q_UNUSED(rect); + Q_UNUSED(order); + return m_items; + } + +protected : + virtual void clear() + { m_items.clear(); } + + virtual void addItem(QGraphicsItem *item) + { m_items << item; } + + virtual void removeItem(QGraphicsItem *item) + { m_items.removeOne(item); } + +private: + QList m_items; +}; + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSCENELINEARINDEX_H diff --git a/src/gui/graphicsview/qgraphicstransform.cpp b/src/gui/graphicsview/qgraphicstransform.cpp new file mode 100644 index 0000000000..513c41f46f --- /dev/null +++ b/src/gui/graphicsview/qgraphicstransform.cpp @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QGraphicsTransform + \brief The QGraphicsTransform class is an abstract base class for building + advanced transformations on QGraphicsItems. + \since 4.6 + \ingroup graphicsview-api + + As an alternative to QGraphicsItem::transform, QGraphicsTransform lets you + create and control advanced transformations that can be configured + independently using specialized properties. + + QGraphicsItem allows you to assign any number of QGraphicsTransform + instances to one QGraphicsItem. Each QGraphicsTransform is applied in + order, one at a time, to the QGraphicsItem it's assigned to. + + QGraphicsTransform is particularly useful for animations. Whereas + QGraphicsItem::setTransform() lets you assign any transform directly to an + item, there is no direct way to interpolate between two different + transformations (e.g., when transitioning between two states, each for + which the item has a different arbitrary transform assigned). Using + QGraphicsTransform you can interpolate the property values of each + independent transformation. The resulting operation is then combined into a + single transform which is applied to QGraphicsItem. + + Transformations are computed in true 3D space using QMatrix4x4. + When the transformation is applied to a QGraphicsItem, it will be + projected back to a 2D QTransform. When multiple QGraphicsTransform + objects are applied to a QGraphicsItem, all of the transformations + are computed in true 3D space, with the projection back to 2D + only occurring after the last QGraphicsTransform is applied. + The exception to this is QGraphicsRotation, which projects back to + 2D after each rotation to preserve the perspective effect around + the X and Y axes. + + If you want to create your own configurable transformation, you can create + a subclass of QGraphicsTransform (or any or the existing subclasses), and + reimplement the pure virtual applyTo() function, which takes a pointer to a + QMatrix4x4. Each operation you would like to apply should be exposed as + properties (e.g., customTransform->setVerticalShear(2.5)). Inside you + reimplementation of applyTo(), you can modify the provided transform + respectively. + + QGraphicsTransform can be used together with QGraphicsItem::setTransform(), + QGraphicsItem::setRotation(), and QGraphicsItem::setScale(). + + \sa QGraphicsItem::transform(), QGraphicsScale, QGraphicsRotation +*/ + +#include "qgraphicstransform.h" +#include "qgraphicsitem_p.h" +#include "qgraphicstransform_p.h" +#include +#include +#include + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE +void QGraphicsTransformPrivate::setItem(QGraphicsItem *i) +{ + if (item == i) + return; + + if (item) { + Q_Q(QGraphicsTransform); + QGraphicsItemPrivate *d_ptr = item->d_ptr.data(); + + item->prepareGeometryChange(); + Q_ASSERT(d_ptr->transformData); + d_ptr->transformData->graphicsTransforms.removeAll(q); + d_ptr->dirtySceneTransform = 1; + item = 0; + } + + item = i; +} + +void QGraphicsTransformPrivate::updateItem(QGraphicsItem *item) +{ + item->prepareGeometryChange(); + item->d_ptr->dirtySceneTransform = 1; +} + +/*! + Constructs a new QGraphicsTransform with the given \a parent. +*/ +QGraphicsTransform::QGraphicsTransform(QObject *parent) + : QObject(*new QGraphicsTransformPrivate, parent) +{ +} + +/*! + Destroys the graphics transform. +*/ +QGraphicsTransform::~QGraphicsTransform() +{ + Q_D(QGraphicsTransform); + d->setItem(0); +} + +/*! + \internal +*/ +QGraphicsTransform::QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent) + : QObject(p, parent) +{ +} + +/*! + \fn void QGraphicsTransform::applyTo(QMatrix4x4 *matrix) const + + This pure virtual method has to be reimplemented in derived classes. + + It applies this transformation to \a matrix. + + \sa QGraphicsItem::transform(), QMatrix4x4::toTransform() +*/ + +/*! + Notifies that this transform operation has changed its parameters in such a + way that applyTo() will return a different result than before. + + When implementing you own custom graphics transform, you must call this + function every time you change a parameter, to let QGraphicsItem know that + its transformation needs to be updated. + + \sa applyTo() +*/ +void QGraphicsTransform::update() +{ + Q_D(QGraphicsTransform); + if (d->item) + d->updateItem(d->item); +} + +/*! + \class QGraphicsScale + \brief The QGraphicsScale class provides a scale transformation. + \since 4.6 + + QGraphicsScene provides certain parameters to help control how the scale + should be applied. + + The origin is the point that the item is scaled from (i.e., it stays fixed + relative to the parent as the rest of the item grows). By default the + origin is QPointF(0, 0). + + The parameters xScale, yScale, and zScale describe the scale factors to + apply in horizontal, vertical, and depth directions. They can take on any + value, including 0 (to collapse the item to a point) or negative value. + A negative xScale value will mirror the item horizontally. A negative yScale + value will flip the item vertically. A negative zScale will flip the + item end for end. + + \sa QGraphicsTransform, QGraphicsItem::setScale(), QTransform::scale() +*/ + +class QGraphicsScalePrivate : public QGraphicsTransformPrivate +{ +public: + QGraphicsScalePrivate() + : xScale(1), yScale(1), zScale(1) {} + QVector3D origin; + qreal xScale; + qreal yScale; + qreal zScale; +}; + +/*! + Constructs an empty QGraphicsScale object with the given \a parent. +*/ +QGraphicsScale::QGraphicsScale(QObject *parent) + : QGraphicsTransform(*new QGraphicsScalePrivate, parent) +{ +} + +/*! + Destroys the graphics scale. +*/ +QGraphicsScale::~QGraphicsScale() +{ +} + +/*! + \property QGraphicsScale::origin + \brief the origin of the scale in 3D space. + + All scaling will be done relative to this point (i.e., this point + will stay fixed, relative to the parent, when the item is scaled). + + \sa xScale, yScale, zScale +*/ +QVector3D QGraphicsScale::origin() const +{ + Q_D(const QGraphicsScale); + return d->origin; +} +void QGraphicsScale::setOrigin(const QVector3D &point) +{ + Q_D(QGraphicsScale); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +/*! + \property QGraphicsScale::xScale + \brief the horizontal scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be mirrored horizontally around its + origin. + + \sa yScale, zScale, origin +*/ +qreal QGraphicsScale::xScale() const +{ + Q_D(const QGraphicsScale); + return d->xScale; +} +void QGraphicsScale::setXScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->xScale == scale) + return; + d->xScale = scale; + update(); + emit xScaleChanged(); + emit scaleChanged(); +} + +/*! + \property QGraphicsScale::yScale + \brief the vertical scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be flipped vertically around its + origin. + + \sa xScale, zScale, origin +*/ +qreal QGraphicsScale::yScale() const +{ + Q_D(const QGraphicsScale); + return d->yScale; +} +void QGraphicsScale::setYScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->yScale == scale) + return; + d->yScale = scale; + update(); + emit yScaleChanged(); + emit scaleChanged(); +} + +/*! + \property QGraphicsScale::zScale + \brief the depth scale factor. + + The scale factor can be any real number; the default value is 1.0. If you + set the factor to 0.0, the item will be collapsed to a single point. If you + provide a negative value, the item will be flipped end for end around its + origin. + + \sa xScale, yScale, origin +*/ +qreal QGraphicsScale::zScale() const +{ + Q_D(const QGraphicsScale); + return d->zScale; +} +void QGraphicsScale::setZScale(qreal scale) +{ + Q_D(QGraphicsScale); + if (d->zScale == scale) + return; + d->zScale = scale; + update(); + emit zScaleChanged(); + emit scaleChanged(); +} + +/*! + \reimp +*/ +void QGraphicsScale::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QGraphicsScale); + matrix->translate(d->origin); + matrix->scale(d->xScale, d->yScale, d->zScale); + matrix->translate(-d->origin); +} + +/*! + \fn QGraphicsScale::originChanged() + + QGraphicsScale emits this signal when its origin changes. + + \sa QGraphicsScale::origin +*/ + +/*! + \fn QGraphicsScale::xScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l xScale property changes. +*/ + +/*! + \fn QGraphicsScale::yScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l yScale property changes. +*/ + +/*! + \fn QGraphicsScale::zScaleChanged() + \since 4.7 + + This signal is emitted whenever the \l zScale property changes. +*/ + +/*! + \fn QGraphicsScale::scaleChanged() + + This signal is emitted whenever the xScale, yScale, or zScale + of the object changes. + + \sa QGraphicsScale::xScale, QGraphicsScale::yScale + \sa QGraphicsScale::zScale +*/ + +/*! + \class QGraphicsRotation + \brief The QGraphicsRotation class provides a rotation transformation around + a given axis. + \since 4.6 + + You can provide the desired axis by assigning a QVector3D to the axis property + or by passing a member if Qt::Axis to the setAxis convenience function. + By default the axis is (0, 0, 1) i.e., rotation around the Z axis. + + The angle property, which is provided by QGraphicsRotation, now + describes the number of degrees to rotate around this axis. + + QGraphicsRotation provides certain parameters to help control how the + rotation should be applied. + + The origin is the point that the item is rotated around (i.e., it stays + fixed relative to the parent as the rest of the item is rotated). By + default the origin is QPointF(0, 0). + + The angle property provides the number of degrees to rotate the item + clockwise around the origin. This value also be negative, indicating a + counter-clockwise rotation. For animation purposes it may also be useful to + provide rotation angles exceeding (-360, 360) degrees, for instance to + animate how an item rotates several times. + + Note: the final rotation is the combined effect of a rotation in + 3D space followed by a projection back to 2D. If several rotations + are performed in succession, they will not behave as expected unless + they were all around the Z axis. + + \sa QGraphicsTransform, QGraphicsItem::setRotation(), QTransform::rotate() +*/ + +class QGraphicsRotationPrivate : public QGraphicsTransformPrivate +{ +public: + QGraphicsRotationPrivate() + : angle(0), axis(0, 0, 1) {} + QVector3D origin; + qreal angle; + QVector3D axis; +}; + +/*! + Constructs a new QGraphicsRotation with the given \a parent. +*/ +QGraphicsRotation::QGraphicsRotation(QObject *parent) + : QGraphicsTransform(*new QGraphicsRotationPrivate, parent) +{ +} + +/*! + Destroys the graphics rotation. +*/ +QGraphicsRotation::~QGraphicsRotation() +{ +} + +/*! + \property QGraphicsRotation::origin + \brief the origin of the rotation in 3D space. + + All rotations will be done relative to this point (i.e., this point + will stay fixed, relative to the parent, when the item is rotated). + + \sa angle +*/ +QVector3D QGraphicsRotation::origin() const +{ + Q_D(const QGraphicsRotation); + return d->origin; +} +void QGraphicsRotation::setOrigin(const QVector3D &point) +{ + Q_D(QGraphicsRotation); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +/*! + \property QGraphicsRotation::angle + \brief the angle for clockwise rotation, in degrees. + + The angle can be any real number; the default value is 0.0. A value of 180 + will rotate 180 degrees, clockwise. If you provide a negative number, the + item will be rotated counter-clockwise. Normally the rotation angle will be + in the range (-360, 360), but you can also provide numbers outside of this + range (e.g., a angle of 370 degrees gives the same result as 10 degrees). + Setting the angle to NaN results in no rotation. + + \sa origin +*/ +qreal QGraphicsRotation::angle() const +{ + Q_D(const QGraphicsRotation); + return d->angle; +} +void QGraphicsRotation::setAngle(qreal angle) +{ + Q_D(QGraphicsRotation); + if (d->angle == angle) + return; + d->angle = angle; + update(); + emit angleChanged(); +} + +/*! + \fn QGraphicsRotation::originChanged() + + This signal is emitted whenever the origin has changed. + + \sa QGraphicsRotation::origin +*/ + +/*! + \fn void QGraphicsRotation::angleChanged() + + This signal is emitted whenever the angle has changed. + + \sa QGraphicsRotation::angle +*/ + +/*! + \property QGraphicsRotation::axis + \brief a rotation axis, specified by a vector in 3D space. + + This can be any axis in 3D space. By default the axis is (0, 0, 1), + which is aligned with the Z axis. If you provide another axis, + QGraphicsRotation will provide a transformation that rotates + around this axis. For example, if you would like to rotate an item + around its X axis, you could pass (1, 0, 0) as the axis. + + \sa QTransform, QGraphicsRotation::angle +*/ +QVector3D QGraphicsRotation::axis() const +{ + Q_D(const QGraphicsRotation); + return d->axis; +} +void QGraphicsRotation::setAxis(const QVector3D &axis) +{ + Q_D(QGraphicsRotation); + if (d->axis == axis) + return; + d->axis = axis; + update(); + emit axisChanged(); +} + +/*! + \fn void QGraphicsRotation::setAxis(Qt::Axis axis) + + Convenience function to set the axis to \a axis. + + Note: the Qt::YAxis rotation for QTransform is inverted from the + correct mathematical rotation in 3D space. The QGraphicsRotation + class implements a correct mathematical rotation. The following + two sequences of code will perform the same transformation: + + \code + QTransform t; + t.rotate(45, Qt::YAxis); + + QGraphicsRotation r; + r.setAxis(Qt::YAxis); + r.setAngle(-45); + \endcode +*/ +void QGraphicsRotation::setAxis(Qt::Axis axis) +{ + switch (axis) + { + case Qt::XAxis: + setAxis(QVector3D(1, 0, 0)); + break; + case Qt::YAxis: + setAxis(QVector3D(0, 1, 0)); + break; + case Qt::ZAxis: + setAxis(QVector3D(0, 0, 1)); + break; + } +} + +/*! + \reimp +*/ +void QGraphicsRotation::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QGraphicsRotation); + + if (d->angle == 0. || d->axis.isNull() || qIsNaN(d->angle)) + return; + + matrix->translate(d->origin); + matrix->projectedRotate(d->angle, d->axis.x(), d->axis.y(), d->axis.z()); + matrix->translate(-d->origin); +} + +/*! + \fn void QGraphicsRotation::axisChanged() + + This signal is emitted whenever the axis of the object changes. + + \sa QGraphicsRotation::axis +*/ + +#include "moc_qgraphicstransform.cpp" + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicstransform.h b/src/gui/graphicsview/qgraphicstransform.h new file mode 100644 index 0000000000..e2dd05b5be --- /dev/null +++ b/src/gui/graphicsview/qgraphicstransform.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSTRANSFORM_H +#define QGRAPHICSTRANSFORM_H + +#include +#include +#include +#include + +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsItem; +class QGraphicsTransformPrivate; + +class Q_GUI_EXPORT QGraphicsTransform : public QObject +{ + Q_OBJECT +public: + QGraphicsTransform(QObject *parent = 0); + ~QGraphicsTransform(); + + virtual void applyTo(QMatrix4x4 *matrix) const = 0; + +protected Q_SLOTS: + void update(); + +protected: + QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent); + +private: + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + Q_DECLARE_PRIVATE(QGraphicsTransform) +}; + +class QGraphicsScalePrivate; + +class Q_GUI_EXPORT QGraphicsScale : public QGraphicsTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal xScale READ xScale WRITE setXScale NOTIFY xScaleChanged) + Q_PROPERTY(qreal yScale READ yScale WRITE setYScale NOTIFY yScaleChanged) + Q_PROPERTY(qreal zScale READ zScale WRITE setZScale NOTIFY zScaleChanged) +public: + QGraphicsScale(QObject *parent = 0); + ~QGraphicsScale(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal xScale() const; + void setXScale(qreal); + + qreal yScale() const; + void setYScale(qreal); + + qreal zScale() const; + void setZScale(qreal); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void xScaleChanged(); + void yScaleChanged(); + void zScaleChanged(); + void scaleChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsScale) +}; + +class QGraphicsRotationPrivate; + +class Q_GUI_EXPORT QGraphicsRotation : public QGraphicsTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged) + Q_PROPERTY(QVector3D axis READ axis WRITE setAxis NOTIFY axisChanged) +public: + QGraphicsRotation(QObject *parent = 0); + ~QGraphicsRotation(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal angle() const; + void setAngle(qreal); + + QVector3D axis() const; + void setAxis(const QVector3D &axis); + void setAxis(Qt::Axis axis); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void angleChanged(); + void axisChanged(); + +private: + Q_DECLARE_PRIVATE(QGraphicsRotation) +}; + +QT_END_NAMESPACE + +QT_END_HEADER +#endif //QT_NO_GRAPHICSVIEW + +#endif // QFXTRANSFORM_H diff --git a/src/gui/graphicsview/qgraphicstransform_p.h b/src/gui/graphicsview/qgraphicstransform_p.h new file mode 100644 index 0000000000..2a0bf859e3 --- /dev/null +++ b/src/gui/graphicsview/qgraphicstransform_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSTRANSFORM_P_H +#define QGRAPHICSTRANSFORM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#ifndef QT_NO_GRAPHICSVIEW +QT_BEGIN_NAMESPACE + +class QGraphicsItem; + +class QGraphicsTransformPrivate : public QObjectPrivate { +public: + Q_DECLARE_PUBLIC(QGraphicsTransform) + + QGraphicsTransformPrivate() + : QObjectPrivate(), item(0) {} + + QGraphicsItem *item; + + void setItem(QGraphicsItem *item); + static void updateItem(QGraphicsItem *item); +}; + +QT_END_NAMESPACE +#endif //QT_NO_GRAPHCISVIEW + +#endif // QGRAPHICSTRANSFORM_P_H diff --git a/src/gui/graphicsview/qgraphicsview.cpp b/src/gui/graphicsview/qgraphicsview.cpp new file mode 100644 index 0000000000..488a36af49 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsview.cpp @@ -0,0 +1,3880 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +static const int QGRAPHICSVIEW_REGION_RECT_THRESHOLD = 50; + +static const int QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS = 503; // largest prime < 2^9 + +/*! + \class QGraphicsView + \brief The QGraphicsView class provides a widget for displaying the + contents of a QGraphicsScene. + \since 4.2 + \ingroup graphicsview-api + + + QGraphicsView visualizes the contents of a QGraphicsScene in a scrollable + viewport. To create a scene with geometrical items, see QGraphicsScene's + documentation. QGraphicsView is part of the \l{Graphics View Framework}. + + To visualize a scene, you start by constructing a QGraphicsView object, + passing the address of the scene you want to visualize to QGraphicsView's + constructor. Alternatively, you can call setScene() to set the scene at a + later point. After you call show(), the view will by default scroll to the + center of the scene and display any items that are visible at this + point. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 0 + + You can explicitly scroll to any position on the scene by using the + scroll bars, or by calling centerOn(). By passing a point to centerOn(), + QGraphicsView will scroll its viewport to ensure that the point is + centered in the view. An overload is provided for scrolling to a + QGraphicsItem, in which case QGraphicsView will see to that the center of + the item is centered in the view. If all you want is to ensure that a + certain area is visible, (but not necessarily centered,) you can call + ensureVisible() instead. + + QGraphicsView can be used to visualize a whole scene, or only parts of it. + The visualized area is by default detected automatically when the view is + displayed for the first time (by calling + QGraphicsScene::itemsBoundingRect()). To set the visualized area rectangle + yourself, you can call setSceneRect(). This will adjust the scroll bars' + ranges appropriately. Note that although the scene supports a virtually + unlimited size, the range of the scroll bars will never exceed the range of + an integer (INT_MIN, INT_MAX). + + QGraphicsView visualizes the scene by calling render(). By default, the + items are drawn onto the viewport by using a regular QPainter, and using + default render hints. To change the default render hints that + QGraphicsView passes to QPainter when painting items, you can call + setRenderHints(). + + By default, QGraphicsView provides a regular QWidget for the viewport + widget. You can access this widget by calling viewport(), or you can + replace it by calling setViewport(). To render using OpenGL, simply call + setViewport(new QGLWidget). QGraphicsView takes ownership of the viewport + widget. + + QGraphicsView supports affine transformations, using QTransform. You can + either pass a matrix to setTransform(), or you can call one of the + convenience functions rotate(), scale(), translate() or shear(). The most + two common transformations are scaling, which is used to implement + zooming, and rotation. QGraphicsView keeps the center of the view fixed + during a transformation. Because of the scene alignment (setAligment()), + translating the view will have no visual impact. + + You can interact with the items on the scene by using the mouse and + keyboard. QGraphicsView translates the mouse and key events into \e scene + events, (events that inherit QGraphicsSceneEvent,), and forward them to + the visualized scene. In the end, it's the individual item that handles + the events and reacts to them. For example, if you click on a selectable + item, the item will typically let the scene know that it has been + selected, and it will also redraw itself to display a selection + rectangle. Similiary, if you click and drag the mouse to move a movable + item, it's the item that handles the mouse moves and moves itself. Item + interaction is enabled by default, and you can toggle it by calling + setInteractive(). + + You can also provide your own custom scene interaction, by creating a + subclass of QGraphicsView, and reimplementing the mouse and key event + handlers. To simplify how you programmatically interact with items in the + view, QGraphicsView provides the mapping functions mapToScene() and + mapFromScene(), and the item accessors items() and itemAt(). These + functions allow you to map points, rectangles, polygons and paths between + view coordinates and scene coordinates, and to find items on the scene + using view coordinates. + + \img graphicsview-view.png + + \sa QGraphicsScene, QGraphicsItem, QGraphicsSceneEvent +*/ + +/*! + \enum QGraphicsView::ViewportAnchor + + This enums describe the possible anchors that QGraphicsView can + use when the user resizes the view or when the view is + transformed. + + \value NoAnchor No anchor, i.e. the view leaves the scene's + position unchanged. + \value AnchorViewCenter The scene point at the center of the view + is used as the anchor. + \value AnchorUnderMouse The point under the mouse is used as the anchor. + + \sa resizeAnchor, transformationAnchor +*/ + +/*! + \enum QGraphicsView::ViewportUpdateMode + + \since 4.3 + + This enum describes how QGraphicsView updates its viewport when the scene + contents change or are exposed. + + \value FullViewportUpdate When any visible part of the scene changes or is + reexposed, QGraphicsView will update the entire viewport. This approach is + fastest when QGraphicsView spends more time figuring out what to draw than + it would spend drawing (e.g., when very many small items are repeatedly + updated). This is the preferred update mode for viewports that do not + support partial updates, such as QGLWidget, and for viewports that need to + disable scroll optimization. + + \value MinimalViewportUpdate QGraphicsView will determine the minimal + viewport region that requires a redraw, minimizing the time spent drawing + by avoiding a redraw of areas that have not changed. This is + QGraphicsView's default mode. Although this approach provides the best + performance in general, if there are many small visible changes on the + scene, QGraphicsView might end up spending more time finding the minimal + approach than it will spend drawing. + + \value SmartViewportUpdate QGraphicsView will attempt to find an optimal + update mode by analyzing the areas that require a redraw. + + \value BoundingRectViewportUpdate The bounding rectangle of all changes in + the viewport will be redrawn. This mode has the advantage that + QGraphicsView searches only one region for changes, minimizing time spent + determining what needs redrawing. The disadvantage is that areas that have + not changed also need to be redrawn. + + \value NoViewportUpdate QGraphicsView will never update its viewport when + the scene changes; the user is expected to control all updates. This mode + disables all (potentially slow) item visibility testing in QGraphicsView, + and is suitable for scenes that either require a fixed frame rate, or where + the viewport is otherwise updated externally. + + \sa viewportUpdateMode +*/ + +/*! + \enum QGraphicsView::OptimizationFlag + + \since 4.3 + + This enum describes flags that you can enable to improve rendering + performance in QGraphicsView. By default, none of these flags are set. + Note that setting a flag usually imposes a side effect, and this effect + can vary between paint devices and platforms. + + \value DontClipPainter This value is obsolete and has no effect. + + \value DontSavePainterState When rendering, QGraphicsView protects the + painter state (see QPainter::save()) when rendering the background or + foreground, and when rendering each item. This allows you to leave the + painter in an altered state (i.e., you can call QPainter::setPen() or + QPainter::setBrush() without restoring the state after painting). However, + if the items consistently do restore the state, you should enable this + flag to prevent QGraphicsView from doing the same. + + \value DontAdjustForAntialiasing Disables QGraphicsView's antialiasing + auto-adjustment of exposed areas. Items that render antialiased lines on + the boundaries of their QGraphicsItem::boundingRect() can end up rendering + parts of the line outside. To prevent rendering artifacts, QGraphicsView + expands all exposed regions by 2 pixels in all directions. If you enable + this flag, QGraphicsView will no longer perform these adjustments, + minimizing the areas that require redrawing, which improves performance. A + common side effect is that items that do draw with antialiasing can leave + painting traces behind on the scene as they are moved. + + \value IndirectPainting Since Qt 4.6, restore the old painting algorithm + that calls QGraphicsView::drawItems() and QGraphicsScene::drawItems(). + To be used only for compatibility with old code. +*/ + +/*! + \enum QGraphicsView::CacheModeFlag + + This enum describes the flags that you can set for a QGraphicsView's cache + mode. + + \value CacheNone All painting is done directly onto the viewport. + + \value CacheBackground The background is cached. This affects both custom + backgrounds, and backgrounds based on the backgroundBrush property. When + this flag is enabled, QGraphicsView will allocate one pixmap with the full + size of the viewport. + + \sa cacheMode +*/ + +/*! + \enum QGraphicsView::DragMode + + This enum describes the default action for the view when pressing and + dragging the mouse over the viewport. + + \value NoDrag Nothing happens; the mouse event is ignored. + + \value ScrollHandDrag The cursor changes into a pointing hand, and + dragging the mouse around will scroll the scrolbars. This mode works both + in \l{QGraphicsView::interactive}{interactive} and non-interactive mode. + + \value RubberBandDrag A rubber band will appear. Dragging the mouse will + set the rubber band geometry, and all items covered by the rubber band are + selected. This mode is disabled for non-interactive views. + + \sa dragMode, QGraphicsScene::setSelectionArea() +*/ + +#include "qgraphicsview.h" +#include "qgraphicsview_p.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicsitem.h" +#include "qgraphicsitem_p.h" +#include "qgraphicsscene.h" +#include "qgraphicsscene_p.h" +#include "qgraphicssceneevent.h" +#include "qgraphicswidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_X11 +#include +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +inline int q_round_bound(qreal d) //### (int)(qreal) INT_MAX != INT_MAX for single precision +{ + if (d <= (qreal) INT_MIN) + return INT_MIN; + else if (d >= (qreal) INT_MAX) + return INT_MAX; + return d >= 0.0 ? int(d + 0.5) : int(d - int(d-1) + 0.5) + int(d-1); +} + +void QGraphicsViewPrivate::translateTouchEvent(QGraphicsViewPrivate *d, QTouchEvent *touchEvent) +{ + QList touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + // the scene will set the item local pos, startPos, lastPos, and rect before delivering to + // an item, but for now those functions are returning the view's local coordinates + touchPoint.setSceneRect(d->mapToScene(touchPoint.rect())); + touchPoint.setStartScenePos(d->mapToScene(touchPoint.startPos())); + touchPoint.setLastScenePos(d->mapToScene(touchPoint.lastPos())); + + // screenPos, startScreenPos, lastScreenPos, and screenRect are already set + } + + touchEvent->setTouchPoints(touchPoints); +} + +/*! + \internal +*/ +QGraphicsViewPrivate::QGraphicsViewPrivate() + : renderHints(QPainter::TextAntialiasing), + dragMode(QGraphicsView::NoDrag), + sceneInteractionAllowed(true), hasSceneRect(false), + connectedToScene(false), + useLastMouseEvent(false), + identityMatrix(true), + dirtyScroll(true), + accelerateScrolling(true), + keepLastCenterPoint(true), + transforming(false), + handScrolling(false), + mustAllocateStyleOptions(false), + mustResizeBackgroundPixmap(true), + fullUpdatePending(true), + hasUpdateClip(false), + mousePressButton(Qt::NoButton), + leftIndent(0), topIndent(0), + lastMouseEvent(QEvent::None, QPoint(), Qt::NoButton, 0, 0), + alignment(Qt::AlignCenter), + transformationAnchor(QGraphicsView::AnchorViewCenter), resizeAnchor(QGraphicsView::NoAnchor), + viewportUpdateMode(QGraphicsView::MinimalViewportUpdate), + optimizationFlags(0), + scene(0), +#ifndef QT_NO_RUBBERBAND + rubberBanding(false), + rubberBandSelectionMode(Qt::IntersectsItemShape), +#endif + handScrollMotions(0), cacheMode(0), +#ifndef QT_NO_CURSOR + hasStoredOriginalCursor(false), +#endif + lastDragDropEvent(0), + updateSceneSlotReimplementedChecked(false) +{ + styleOptions.reserve(QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::recalculateContentSize() +{ + Q_Q(QGraphicsView); + + QSize maxSize = q->maximumViewportSize(); + int width = maxSize.width(); + int height = maxSize.height(); + QRectF viewRect = matrix.mapRect(q->sceneRect()); + + bool frameOnlyAround = (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, 0, q)); + if (frameOnlyAround) { + if (hbarpolicy == Qt::ScrollBarAlwaysOn) + height -= frameWidth * 2; + if (vbarpolicy == Qt::ScrollBarAlwaysOn) + width -= frameWidth * 2; + } + + // Adjust the maximum width and height of the viewport based on the width + // of visible scroll bars. + int scrollBarExtent = q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, q); + if (frameOnlyAround) + scrollBarExtent += frameWidth * 2; + + bool useHorizontalScrollBar = (viewRect.width() > width) && hbarpolicy != Qt::ScrollBarAlwaysOff; + bool useVerticalScrollBar = (viewRect.height() > height) && vbarpolicy != Qt::ScrollBarAlwaysOff; + if (useHorizontalScrollBar && !useVerticalScrollBar) { + if (viewRect.height() > height - scrollBarExtent) + useVerticalScrollBar = true; + } + if (useVerticalScrollBar && !useHorizontalScrollBar) { + if (viewRect.width() > width - scrollBarExtent) + useHorizontalScrollBar = true; + } + if (useHorizontalScrollBar && hbarpolicy != Qt::ScrollBarAlwaysOn) + height -= scrollBarExtent; + if (useVerticalScrollBar && vbarpolicy != Qt::ScrollBarAlwaysOn) + width -= scrollBarExtent; + + // Setting the ranges of these scroll bars can/will cause the values to + // change, and scrollContentsBy() will be called correspondingly. This + // will reset the last center point. + QPointF savedLastCenterPoint = lastCenterPoint; + + // Remember the former indent settings + qreal oldLeftIndent = leftIndent; + qreal oldTopIndent = topIndent; + + // If the whole scene fits horizontally, we center the scene horizontally, + // and ignore the horizontal scroll bars. + int left = q_round_bound(viewRect.left()); + int right = q_round_bound(viewRect.right() - width); + if (left >= right) { + hbar->setRange(0, 0); + + switch (alignment & Qt::AlignHorizontal_Mask) { + case Qt::AlignLeft: + leftIndent = -viewRect.left(); + break; + case Qt::AlignRight: + leftIndent = width - viewRect.width() - viewRect.left() - 1; + break; + case Qt::AlignHCenter: + default: + leftIndent = width / 2 - (viewRect.left() + viewRect.right()) / 2; + break; + } + } else { + hbar->setRange(left, right); + hbar->setPageStep(width); + hbar->setSingleStep(width / 20); + leftIndent = 0; + } + + // If the whole scene fits vertically, we center the scene vertically, and + // ignore the vertical scroll bars. + int top = q_round_bound(viewRect.top()); + int bottom = q_round_bound(viewRect.bottom() - height); + if (top >= bottom) { + vbar->setRange(0, 0); + + switch (alignment & Qt::AlignVertical_Mask) { + case Qt::AlignTop: + topIndent = -viewRect.top(); + break; + case Qt::AlignBottom: + topIndent = height - viewRect.height() - viewRect.top() - 1; + break; + case Qt::AlignVCenter: + default: + topIndent = height / 2 - (viewRect.top() + viewRect.bottom()) / 2; + break; + } + } else { + vbar->setRange(top, bottom); + vbar->setPageStep(height); + vbar->setSingleStep(height / 20); + topIndent = 0; + } + + // Restorethe center point from before the ranges changed. + lastCenterPoint = savedLastCenterPoint; + + // Issue a full update if the indents change. + // ### If the transform is still the same, we can get away with just a + // scroll instead. + if (oldLeftIndent != leftIndent || oldTopIndent != topIndent) { + dirtyScroll = true; + updateAll(); + } else if (q->isRightToLeft() && !leftIndent) { + // In reverse mode, the horizontal scroll always changes after the content + // size has changed, as the scroll is calculated by summing the min and + // max values of the range and subtracting the current value. In normal + // mode the scroll remains unchanged unless the indent has changed. + dirtyScroll = true; + } + + if (cacheMode & QGraphicsView::CacheBackground) { + // Invalidate the background pixmap + mustResizeBackgroundPixmap = true; + } +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::centerView(QGraphicsView::ViewportAnchor anchor) +{ + Q_Q(QGraphicsView); + switch (anchor) { + case QGraphicsView::AnchorUnderMouse: { + if (q->underMouse()) { + // Last scene pos: lastMouseMoveScenePoint + // Current mouse pos: + QPointF transformationDiff = q->mapToScene(viewport->rect().center()) + - q->mapToScene(viewport->mapFromGlobal(QCursor::pos())); + q->centerOn(lastMouseMoveScenePoint + transformationDiff); + } else { + q->centerOn(lastCenterPoint); + } + break; + } + case QGraphicsView::AnchorViewCenter: + q->centerOn(lastCenterPoint); + break; + case QGraphicsView::NoAnchor: + break; + } +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::updateLastCenterPoint() +{ + Q_Q(QGraphicsView); + lastCenterPoint = q->mapToScene(viewport->rect().center()); +} + +/*! + \internal + + Returns the horizontal scroll value (the X value of the left edge of the + viewport). +*/ +qint64 QGraphicsViewPrivate::horizontalScroll() const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return scrollX; +} + +/*! + \internal + + Returns the vertical scroll value (the X value of the top edge of the + viewport). +*/ +qint64 QGraphicsViewPrivate::verticalScroll() const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return scrollY; +} + +/*! + \internal + + Maps the given rectangle to the scene using QTransform::mapRect() +*/ +QRectF QGraphicsViewPrivate::mapRectToScene(const QRect &rect) const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + QRectF scrolled = QRectF(rect.translated(scrollX, scrollY)); + return identityMatrix ? scrolled : matrix.inverted().mapRect(scrolled); +} + + +/*! + \internal + + Maps the given rectangle from the scene using QTransform::mapRect() +*/ +QRectF QGraphicsViewPrivate::mapRectFromScene(const QRectF &rect) const +{ + if (dirtyScroll) + const_cast(this)->updateScroll(); + return (identityMatrix ? rect : matrix.mapRect(rect)).translated(-scrollX, -scrollY); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::updateScroll() +{ + Q_Q(QGraphicsView); + scrollX = qint64(-leftIndent); + if (q->isRightToLeft()) { + if (!leftIndent) { + scrollX += hbar->minimum(); + scrollX += hbar->maximum(); + scrollX -= hbar->value(); + } + } else { + scrollX += hbar->value(); + } + + scrollY = qint64(vbar->value() - topIndent); + + dirtyScroll = false; +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::replayLastMouseEvent() +{ + if (!useLastMouseEvent || !scene) + return; + mouseMoveEventHandler(&lastMouseEvent); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::storeMouseEvent(QMouseEvent *event) +{ + useLastMouseEvent = true; + lastMouseEvent = QMouseEvent(QEvent::MouseMove, event->pos(), event->globalPos(), + event->button(), event->buttons(), event->modifiers()); +} + +void QGraphicsViewPrivate::mouseMoveEventHandler(QMouseEvent *event) +{ + Q_Q(QGraphicsView); + + storeMouseEvent(event); + lastMouseEvent.setAccepted(false); + + if (!sceneInteractionAllowed) + return; + if (handScrolling) + return; + if (!scene) + return; + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); + mouseEvent.setWidget(viewport); + mouseEvent.setButtonDownScenePos(mousePressButton, mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(mousePressButton, mousePressScreenPoint); + mouseEvent.setScenePos(q->mapToScene(event->pos())); + mouseEvent.setScreenPos(event->globalPos()); + mouseEvent.setLastScenePos(lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + lastMouseMoveScenePoint = mouseEvent.scenePos(); + lastMouseMoveScreenPoint = mouseEvent.screenPos(); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(scene, &mouseEvent); + else + QApplication::sendEvent(scene, &mouseEvent); + + // Remember whether the last event was accepted or not. + lastMouseEvent.setAccepted(mouseEvent.isAccepted()); + + if (mouseEvent.isAccepted() && mouseEvent.buttons() != 0) { + // The event was delivered to a mouse grabber; the press is likely to + // have set a cursor, and we must not change it. + return; + } + +#ifndef QT_NO_CURSOR + // If all the items ignore hover events, we don't look-up any items + // in QGraphicsScenePrivate::dispatchHoverEvent, hence the + // cachedItemsUnderMouse list will be empty. We therefore do the look-up + // for cursor items here if not all items use the default cursor. + if (scene->d_func()->allItemsIgnoreHoverEvents && !scene->d_func()->allItemsUseDefaultCursor + && scene->d_func()->cachedItemsUnderMouse.isEmpty()) { + scene->d_func()->cachedItemsUnderMouse = scene->d_func()->itemsAtPosition(mouseEvent.screenPos(), + mouseEvent.scenePos(), + mouseEvent.widget()); + } + // Find the topmost item under the mouse with a cursor. + foreach (QGraphicsItem *item, scene->d_func()->cachedItemsUnderMouse) { + if (item->hasCursor()) { + _q_setViewportCursor(item->cursor()); + return; + } + } + + // No items with cursors found; revert to the view cursor. + if (hasStoredOriginalCursor) { + // Restore the original viewport cursor. + hasStoredOriginalCursor = false; + viewport->setCursor(originalCursor); + } +#endif +} + +/*! + \internal +*/ +#ifndef QT_NO_RUBBERBAND +QRegion QGraphicsViewPrivate::rubberBandRegion(const QWidget *widget, const QRect &rect) const +{ + QStyleHintReturnMask mask; + QStyleOptionRubberBand option; + option.initFrom(widget); + option.rect = rect; + option.opaque = false; + option.shape = QRubberBand::Rectangle; + + QRegion tmp; + tmp += rect; + if (widget->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, widget, &mask)) + tmp &= mask.region; + return tmp; +} +#endif + +/*! + \internal +*/ +#ifndef QT_NO_CURSOR +void QGraphicsViewPrivate::_q_setViewportCursor(const QCursor &cursor) +{ + if (!hasStoredOriginalCursor) { + hasStoredOriginalCursor = true; + originalCursor = viewport->cursor(); + } + viewport->setCursor(cursor); +} +#endif + +/*! + \internal +*/ +#ifndef QT_NO_CURSOR +void QGraphicsViewPrivate::_q_unsetViewportCursor() +{ + Q_Q(QGraphicsView); + foreach (QGraphicsItem *item, q->items(lastMouseEvent.pos())) { + if (item->hasCursor()) { + _q_setViewportCursor(item->cursor()); + return; + } + } + + // Restore the original viewport cursor. + if (hasStoredOriginalCursor) { + hasStoredOriginalCursor = false; + if (dragMode == QGraphicsView::ScrollHandDrag) + viewport->setCursor(Qt::OpenHandCursor); + else + viewport->setCursor(originalCursor); + } +} +#endif + +/*! + \internal +*/ +void QGraphicsViewPrivate::storeDragDropEvent(const QGraphicsSceneDragDropEvent *event) +{ + delete lastDragDropEvent; + lastDragDropEvent = new QGraphicsSceneDragDropEvent(event->type()); + lastDragDropEvent->setScenePos(event->scenePos()); + lastDragDropEvent->setScreenPos(event->screenPos()); + lastDragDropEvent->setButtons(event->buttons()); + lastDragDropEvent->setModifiers(event->modifiers()); + lastDragDropEvent->setPossibleActions(event->possibleActions()); + lastDragDropEvent->setProposedAction(event->proposedAction()); + lastDragDropEvent->setDropAction(event->dropAction()); + lastDragDropEvent->setMimeData(event->mimeData()); + lastDragDropEvent->setWidget(event->widget()); + lastDragDropEvent->setSource(event->source()); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::populateSceneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QDropEvent *source) +{ +#ifndef QT_NO_DRAGANDDROP + Q_Q(QGraphicsView); + dest->setScenePos(q->mapToScene(source->pos())); + dest->setScreenPos(q->mapToGlobal(source->pos())); + dest->setButtons(source->mouseButtons()); + dest->setModifiers(source->keyboardModifiers()); + dest->setPossibleActions(source->possibleActions()); + dest->setProposedAction(source->proposedAction()); + dest->setDropAction(source->dropAction()); + dest->setMimeData(source->mimeData()); + dest->setWidget(viewport); + dest->setSource(source->source()); +#else + Q_UNUSED(dest) + Q_UNUSED(source) +#endif +} + +/*! + \internal +*/ +QRect QGraphicsViewPrivate::mapToViewRect(const QGraphicsItem *item, const QRectF &rect) const +{ + Q_Q(const QGraphicsView); + if (dirtyScroll) + const_cast(this)->updateScroll(); + + if (item->d_ptr->itemIsUntransformable()) { + QTransform itv = item->deviceTransform(q->viewportTransform()); + return itv.mapRect(rect).toAlignedRect(); + } + + // Translate-only + // COMBINE + QPointF offset; + const QGraphicsItem *parentItem = item; + const QGraphicsItemPrivate *itemd; + do { + itemd = parentItem->d_ptr.data(); + if (itemd->transformData) + break; + offset += itemd->pos; + } while ((parentItem = itemd->parent)); + + QRectF baseRect = rect.translated(offset.x(), offset.y()); + if (!parentItem) { + if (identityMatrix) { + baseRect.translate(-scrollX, -scrollY); + return baseRect.toAlignedRect(); + } + return matrix.mapRect(baseRect).translated(-scrollX, -scrollY).toAlignedRect(); + } + + QTransform tr = parentItem->sceneTransform(); + if (!identityMatrix) + tr *= matrix; + QRectF r = tr.mapRect(baseRect); + r.translate(-scrollX, -scrollY); + return r.toAlignedRect(); +} + +/*! + \internal +*/ +QRegion QGraphicsViewPrivate::mapToViewRegion(const QGraphicsItem *item, const QRectF &rect) const +{ + Q_Q(const QGraphicsView); + if (dirtyScroll) + const_cast(this)->updateScroll(); + + // Accurate bounding region + QTransform itv = item->deviceTransform(q->viewportTransform()); + return item->boundingRegion(itv) & itv.mapRect(rect).toAlignedRect(); +} + +/*! + \internal +*/ +void QGraphicsViewPrivate::processPendingUpdates() +{ + if (!scene) + return; + + if (fullUpdatePending) { + viewport->update(); + } else if (viewportUpdateMode == QGraphicsView::BoundingRectViewportUpdate) { + viewport->update(dirtyBoundingRect); + } else { + viewport->update(dirtyRegion); // Already adjusted in updateRect/Region. + } + + dirtyBoundingRect = QRect(); + dirtyRegion = QRegion(); +} + +static inline bool intersectsViewport(const QRect &r, int width, int height) +{ return !(r.left() > width) && !(r.right() < 0) && !(r.top() >= height) && !(r.bottom() < 0); } + +static inline bool containsViewport(const QRect &r, int width, int height) +{ return r.left() <= 0 && r.top() <= 0 && r.right() >= width - 1 && r.bottom() >= height - 1; } + +static inline void QRect_unite(QRect *rect, const QRect &other) +{ + if (rect->isEmpty()) { + *rect = other; + } else { + rect->setCoords(qMin(rect->left(), other.left()), qMin(rect->top(), other.top()), + qMax(rect->right(), other.right()), qMax(rect->bottom(), other.bottom())); + } +} + +/* + Calling this function results in update rects being clipped to the item's + bounding rect. Note that updates prior to this function call is not clipped. + The clip is removed by passing 0. +*/ +void QGraphicsViewPrivate::setUpdateClip(QGraphicsItem *item) +{ + Q_Q(QGraphicsView); + // We simply ignore the request if the update mode is either FullViewportUpdate + // or NoViewportUpdate; in that case there's no point in clipping anything. + if (!item || viewportUpdateMode == QGraphicsView::NoViewportUpdate + || viewportUpdateMode == QGraphicsView::FullViewportUpdate) { + hasUpdateClip = false; + return; + } + + // Calculate the clip (item's bounding rect in view coordinates). + // Optimized version of: + // QRect clip = item->deviceTransform(q->viewportTransform()) + // .mapRect(item->boundingRect()).toAlignedRect(); + QRect clip; + if (item->d_ptr->itemIsUntransformable()) { + QTransform xform = item->deviceTransform(q->viewportTransform()); + clip = xform.mapRect(item->boundingRect()).toAlignedRect(); + } else if (item->d_ptr->sceneTransformTranslateOnly && identityMatrix) { + QRectF r(item->boundingRect()); + r.translate(item->d_ptr->sceneTransform.dx() - horizontalScroll(), + item->d_ptr->sceneTransform.dy() - verticalScroll()); + clip = r.toAlignedRect(); + } else if (!q->isTransformed()) { + clip = item->d_ptr->sceneTransform.mapRect(item->boundingRect()).toAlignedRect(); + } else { + QTransform xform = item->d_ptr->sceneTransform; + xform *= q->viewportTransform(); + clip = xform.mapRect(item->boundingRect()).toAlignedRect(); + } + + if (hasUpdateClip) { + // Intersect with old clip. + updateClip &= clip; + } else { + updateClip = clip; + hasUpdateClip = true; + } +} + +bool QGraphicsViewPrivate::updateRegion(const QRectF &rect, const QTransform &xform) +{ + if (rect.isEmpty()) + return false; + + if (viewportUpdateMode != QGraphicsView::MinimalViewportUpdate + && viewportUpdateMode != QGraphicsView::SmartViewportUpdate) { + // No point in updating with QRegion granularity; use the rect instead. + return updateRectF(xform.mapRect(rect)); + } + + // Update mode is either Minimal or Smart, so we have to do a potentially slow operation, + // which is clearly documented here: QGraphicsItem::setBoundingRegionGranularity. + const QRegion region = xform.map(QRegion(rect.toAlignedRect())); + QRect viewRect = region.boundingRect(); + const bool dontAdjustForAntialiasing = optimizationFlags & QGraphicsView::DontAdjustForAntialiasing; + if (dontAdjustForAntialiasing) + viewRect.adjust(-1, -1, 1, 1); + else + viewRect.adjust(-2, -2, 2, 2); + if (!intersectsViewport(viewRect, viewport->width(), viewport->height())) + return false; // Update region for sure outside viewport. + + const QVector &rects = region.rects(); + for (int i = 0; i < rects.size(); ++i) { + viewRect = rects.at(i); + if (dontAdjustForAntialiasing) + viewRect.adjust(-1, -1, 1, 1); + else + viewRect.adjust(-2, -2, 2, 2); + if (hasUpdateClip) + viewRect &= updateClip; + dirtyRegion += viewRect; + } + + return true; +} + +// NB! Assumes the rect 'r' is already aligned and adjusted for antialiasing. +// For QRectF use updateRectF(const QRectF &) to ensure proper adjustments. +bool QGraphicsViewPrivate::updateRect(const QRect &r) +{ + if (fullUpdatePending || viewportUpdateMode == QGraphicsView::NoViewportUpdate + || !intersectsViewport(r, viewport->width(), viewport->height())) { + return false; + } + + switch (viewportUpdateMode) { + case QGraphicsView::FullViewportUpdate: + fullUpdatePending = true; + viewport->update(); + break; + case QGraphicsView::BoundingRectViewportUpdate: + if (hasUpdateClip) + QRect_unite(&dirtyBoundingRect, r & updateClip); + else + QRect_unite(&dirtyBoundingRect, r); + if (containsViewport(dirtyBoundingRect, viewport->width(), viewport->height())) { + fullUpdatePending = true; + viewport->update(); + } + break; + case QGraphicsView::SmartViewportUpdate: // ### DEPRECATE + case QGraphicsView::MinimalViewportUpdate: + if (hasUpdateClip) + dirtyRegion += r & updateClip; + else + dirtyRegion += r; + break; + default: + break; + } + + return true; +} + +QStyleOptionGraphicsItem *QGraphicsViewPrivate::allocStyleOptionsArray(int numItems) +{ + if (mustAllocateStyleOptions || (numItems > styleOptions.capacity())) + // too many items, let's allocate on-the-fly + return new QStyleOptionGraphicsItem[numItems]; + + // expand only whenever necessary + if (numItems > styleOptions.size()) + styleOptions.resize(numItems); + + mustAllocateStyleOptions = true; + return styleOptions.data(); +} + +void QGraphicsViewPrivate::freeStyleOptionsArray(QStyleOptionGraphicsItem *array) +{ + mustAllocateStyleOptions = false; + if (array != styleOptions.data()) + delete [] array; +} + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +/*! + ### Adjustments in findItems: mapToScene(QRect) forces us to adjust the + input rectangle by (0, 0, 1, 1), because it uses QRect::bottomRight() + (etc) when mapping the rectangle to a polygon (which is _wrong_). In + addition, as QGraphicsItem::boundingRect() is defined in logical space, + but the default pen for QPainter is cosmetic with a width of 0, QPainter + is at risk of painting 1 pixel outside the bounding rect. Therefore we + must search for items with an adjustment of (-1, -1, 1, 1). +*/ +QList QGraphicsViewPrivate::findItems(const QRegion &exposedRegion, bool *allItems, + const QTransform &viewTransform) const +{ + Q_Q(const QGraphicsView); + + // Step 1) If all items are contained within the expose region, then + // return a list of all visible items. ### the scene's growing bounding + // rect does not take into account untransformable items. + const QRectF exposedRegionSceneBounds = q->mapToScene(exposedRegion.boundingRect().adjusted(-1, -1, 1, 1)) + .boundingRect(); + if (exposedRegionSceneBounds.contains(scene->sceneRect())) { + Q_ASSERT(allItems); + *allItems = true; + + // All items are guaranteed within the exposed region. + return scene->items(Qt::AscendingOrder); + } + + // Step 2) If the expose region is a simple rect and the view is only + // translated or scaled, search for items using + // QGraphicsScene::items(QRectF). + bool simpleRectLookup = exposedRegion.rectCount() == 1 && matrix.type() <= QTransform::TxScale; + if (simpleRectLookup) { + return scene->items(exposedRegionSceneBounds, + Qt::IntersectsItemBoundingRect, + Qt::AscendingOrder, viewTransform); + } + + // If the region is complex or the view has a complex transform, adjust + // the expose region, convert it to a path, and then search for items + // using QGraphicsScene::items(QPainterPath); + QRegion adjustedRegion; + foreach (const QRect &r, exposedRegion.rects()) + adjustedRegion += r.adjusted(-1, -1, 1, 1); + + const QPainterPath exposedScenePath(q->mapToScene(qt_regionToPath(adjustedRegion))); + return scene->items(exposedScenePath, Qt::IntersectsItemBoundingRect, + Qt::AscendingOrder, viewTransform); +} + +/*! + \internal + + Enables input methods for the view if and only if the current focus item of + the scene accepts input methods. Call function whenever that condition has + potentially changed. +*/ +void QGraphicsViewPrivate::updateInputMethodSensitivity() +{ + Q_Q(QGraphicsView); + QGraphicsItem *focusItem = 0; + bool enabled = scene && (focusItem = scene->focusItem()) + && (focusItem->d_ptr->flags & QGraphicsItem::ItemAcceptsInputMethod); + q->setAttribute(Qt::WA_InputMethodEnabled, enabled); + q->viewport()->setAttribute(Qt::WA_InputMethodEnabled, enabled); + + if (!enabled) { + q->setInputMethodHints(0); + return; + } + + QGraphicsProxyWidget *proxy = focusItem->d_ptr->isWidget && focusItem->d_ptr->isProxyWidget() + ? static_cast(focusItem) : 0; + if (!proxy) { + q->setInputMethodHints(focusItem->inputMethodHints()); + } else if (QWidget *widget = proxy->widget()) { + if (QWidget *fw = widget->focusWidget()) + widget = fw; + q->setInputMethodHints(widget->inputMethodHints()); + } else { + q->setInputMethodHints(0); + } +} + +/*! + Constructs a QGraphicsView. \a parent is passed to QWidget's constructor. +*/ +QGraphicsView::QGraphicsView(QWidget *parent) + : QAbstractScrollArea(*new QGraphicsViewPrivate, parent) +{ + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + Constructs a QGraphicsView and sets the visualized scene to \a + scene. \a parent is passed to QWidget's constructor. +*/ +QGraphicsView::QGraphicsView(QGraphicsScene *scene, QWidget *parent) + : QAbstractScrollArea(*new QGraphicsViewPrivate, parent) +{ + setScene(scene); + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + \internal + */ +QGraphicsView::QGraphicsView(QGraphicsViewPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + setViewport(0); + setAcceptDrops(true); + setBackgroundRole(QPalette::Base); + // Investigate leaving these disabled by default. + setAttribute(Qt::WA_InputMethodEnabled); + viewport()->setAttribute(Qt::WA_InputMethodEnabled); +} + +/*! + Destructs the QGraphicsView object. +*/ +QGraphicsView::~QGraphicsView() +{ + Q_D(QGraphicsView); + if (d->scene) + d->scene->d_func()->views.removeAll(this); + delete d->lastDragDropEvent; +} + +/*! + \reimp +*/ +QSize QGraphicsView::sizeHint() const +{ + Q_D(const QGraphicsView); + if (d->scene) { + QSizeF baseSize = d->matrix.mapRect(sceneRect()).size(); + baseSize += QSizeF(d->frameWidth * 2, d->frameWidth * 2); + return baseSize.boundedTo((3 * QApplication::desktop()->size()) / 4).toSize(); + } + return QAbstractScrollArea::sizeHint(); +} + +/*! + \property QGraphicsView::renderHints + \brief the default render hints for the view + + These hints are + used to initialize QPainter before each visible item is drawn. QPainter + uses render hints to toggle rendering features such as antialiasing and + smooth pixmap transformation. + + QPainter::TextAntialiasing is enabled by default. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 1 +*/ +QPainter::RenderHints QGraphicsView::renderHints() const +{ + Q_D(const QGraphicsView); + return d->renderHints; +} +void QGraphicsView::setRenderHints(QPainter::RenderHints hints) +{ + Q_D(QGraphicsView); + if (hints == d->renderHints) + return; + d->renderHints = hints; + d->updateAll(); +} + +/*! + If \a enabled is true, the render hint \a hint is enabled; otherwise it + is disabled. + + \sa renderHints +*/ +void QGraphicsView::setRenderHint(QPainter::RenderHint hint, bool enabled) +{ + Q_D(QGraphicsView); + QPainter::RenderHints oldHints = d->renderHints; + if (enabled) + d->renderHints |= hint; + else + d->renderHints &= ~hint; + if (oldHints != d->renderHints) + d->updateAll(); +} + +/*! + \property QGraphicsView::alignment + \brief the alignment of the scene in the view when the whole + scene is visible. + + If the whole scene is visible in the view, (i.e., there are no visible + scroll bars,) the view's alignment will decide where the scene will be + rendered in the view. For example, if the alignment is Qt::AlignCenter, + which is default, the scene will be centered in the view, and if the + alignment is (Qt::AlignLeft | Qt::AlignTop), the scene will be rendered in + the top-left corner of the view. +*/ +Qt::Alignment QGraphicsView::alignment() const +{ + Q_D(const QGraphicsView); + return d->alignment; +} +void QGraphicsView::setAlignment(Qt::Alignment alignment) +{ + Q_D(QGraphicsView); + if (d->alignment != alignment) { + d->alignment = alignment; + d->recalculateContentSize(); + } +} + +/*! + \property QGraphicsView::transformationAnchor + \brief how the view should position the scene during transformations. + + QGraphicsView uses this property to decide how to position the scene in + the viewport when the transformation matrix changes, and the coordinate + system of the view is transformed. The default behavior, AnchorViewCenter, + ensures that the scene point at the center of the view remains unchanged + during transformations (e.g., when rotating, the scene will appear to + rotate around the center of the view). + + Note that the effect of this property is noticeable when only a part of the + scene is visible (i.e., when there are scroll bars). Otherwise, if the + whole scene fits in the view, QGraphicsScene uses the view \l alignment to + position the scene in the view. + + \sa alignment, resizeAnchor +*/ +QGraphicsView::ViewportAnchor QGraphicsView::transformationAnchor() const +{ + Q_D(const QGraphicsView); + return d->transformationAnchor; +} +void QGraphicsView::setTransformationAnchor(ViewportAnchor anchor) +{ + Q_D(QGraphicsView); + d->transformationAnchor = anchor; + + // Ensure mouse tracking is enabled in the case we are using AnchorUnderMouse + // in order to have up-to-date information for centering the view. + if (d->transformationAnchor == AnchorUnderMouse) + d->viewport->setMouseTracking(true); +} + +/*! + \property QGraphicsView::resizeAnchor + \brief how the view should position the scene when the view is resized. + + QGraphicsView uses this property to decide how to position the scene in + the viewport when the viewport widget's size changes. The default + behavior, NoAnchor, leaves the scene's position unchanged during a resize; + the top-left corner of the view will appear to be anchored while resizing. + + Note that the effect of this property is noticeable when only a part of the + scene is visible (i.e., when there are scroll bars). Otherwise, if the + whole scene fits in the view, QGraphicsScene uses the view \l alignment to + position the scene in the view. + + \sa alignment, transformationAnchor, Qt::WNorthWestGravity +*/ +QGraphicsView::ViewportAnchor QGraphicsView::resizeAnchor() const +{ + Q_D(const QGraphicsView); + return d->resizeAnchor; +} +void QGraphicsView::setResizeAnchor(ViewportAnchor anchor) +{ + Q_D(QGraphicsView); + d->resizeAnchor = anchor; + + // Ensure mouse tracking is enabled in the case we are using AnchorUnderMouse + // in order to have up-to-date information for centering the view. + if (d->resizeAnchor == AnchorUnderMouse) + d->viewport->setMouseTracking(true); +} + +/*! + \property QGraphicsView::viewportUpdateMode + \brief how the viewport should update its contents. + + \since 4.3 + + QGraphicsView uses this property to decide how to update areas of the + scene that have been reexposed or changed. Usually you do not need to + modify this property, but there are some cases where doing so can improve + rendering performance. See the ViewportUpdateMode documentation for + specific details. + + The default value is MinimalViewportUpdate, where QGraphicsView will + update as small an area of the viewport as possible when the contents + change. + + \sa ViewportUpdateMode, cacheMode +*/ +QGraphicsView::ViewportUpdateMode QGraphicsView::viewportUpdateMode() const +{ + Q_D(const QGraphicsView); + return d->viewportUpdateMode; +} +void QGraphicsView::setViewportUpdateMode(ViewportUpdateMode mode) +{ + Q_D(QGraphicsView); + d->viewportUpdateMode = mode; +} + +/*! + \property QGraphicsView::optimizationFlags + \brief flags that can be used to tune QGraphicsView's performance. + + \since 4.3 + + QGraphicsView uses clipping, extra bounding rect adjustments, and certain + other aids to improve rendering quality and performance for the common + case graphics scene. However, depending on the target platform, the scene, + and the viewport in use, some of these operations can degrade performance. + + The effect varies from flag to flag; see the OptimizationFlags + documentation for details. + + By default, no optimization flags are enabled. + + \sa setOptimizationFlag() +*/ +QGraphicsView::OptimizationFlags QGraphicsView::optimizationFlags() const +{ + Q_D(const QGraphicsView); + return d->optimizationFlags; +} +void QGraphicsView::setOptimizationFlags(OptimizationFlags flags) +{ + Q_D(QGraphicsView); + d->optimizationFlags = flags; +} + +/*! + Enables \a flag if \a enabled is true; otherwise disables \a flag. + + \sa optimizationFlags +*/ +void QGraphicsView::setOptimizationFlag(OptimizationFlag flag, bool enabled) +{ + Q_D(QGraphicsView); + if (enabled) + d->optimizationFlags |= flag; + else + d->optimizationFlags &= ~flag; +} + +/*! + \property QGraphicsView::dragMode + \brief the behavior for dragging the mouse over the scene while + the left mouse button is pressed. + + This property defines what should happen when the user clicks on the scene + background and drags the mouse (e.g., scrolling the viewport contents + using a pointing hand cursor, or selecting multiple items with a rubber + band). The default value, NoDrag, does nothing. + + This behavior only affects mouse clicks that are not handled by any item. + You can define a custom behavior by creating a subclass of QGraphicsView + and reimplementing mouseMoveEvent(). +*/ +QGraphicsView::DragMode QGraphicsView::dragMode() const +{ + Q_D(const QGraphicsView); + return d->dragMode; +} +void QGraphicsView::setDragMode(DragMode mode) +{ + Q_D(QGraphicsView); + if (d->dragMode == mode) + return; + +#ifndef QT_NO_CURSOR + if (d->dragMode == ScrollHandDrag) + viewport()->unsetCursor(); +#endif + + // If dragMode is unset while dragging, e.g. via a keyEvent, we + // don't unset the handScrolling state. When enabling scrolling + // again the mouseMoveEvent will automatically start scrolling, + // without a mousePress + if (d->dragMode == ScrollHandDrag && mode == NoDrag && d->handScrolling) + d->handScrolling = false; + + d->dragMode = mode; + +#ifndef QT_NO_CURSOR + if (d->dragMode == ScrollHandDrag) { + // Forget the stored viewport cursor when we enter scroll hand drag mode. + d->hasStoredOriginalCursor = false; + viewport()->setCursor(Qt::OpenHandCursor); + } +#endif +} + +#ifndef QT_NO_RUBBERBAND +/*! + \property QGraphicsView::rubberBandSelectionMode + \brief the behavior for selecting items with a rubber band selection rectangle. + \since 4.3 + + This property defines how items are selected when using the RubberBandDrag + drag mode. + + The default value is Qt::IntersectsItemShape; all items whose shape + intersects with or is contained by the rubber band are selected. + + \sa dragMode, items() +*/ +Qt::ItemSelectionMode QGraphicsView::rubberBandSelectionMode() const +{ + Q_D(const QGraphicsView); + return d->rubberBandSelectionMode; +} +void QGraphicsView::setRubberBandSelectionMode(Qt::ItemSelectionMode mode) +{ + Q_D(QGraphicsView); + d->rubberBandSelectionMode = mode; +} +#endif + +/*! + \property QGraphicsView::cacheMode + \brief which parts of the view are cached + + QGraphicsView can cache pre-rendered content in a QPixmap, which is then + drawn onto the viewport. The purpose of such caching is to speed up the + total rendering time for areas that are slow to render. Texture, gradient + and alpha blended backgrounds, for example, can be notibly slow to render; + especially with a transformed view. The CacheBackground flag enables + caching of the view's background. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 2 + + The cache is invalidated every time the view is transformed. However, when + scrolling, only partial invalidation is required. + + By default, nothing is cached. + + \sa resetCachedContent(), QPixmapCache +*/ +QGraphicsView::CacheMode QGraphicsView::cacheMode() const +{ + Q_D(const QGraphicsView); + return d->cacheMode; +} +void QGraphicsView::setCacheMode(CacheMode mode) +{ + Q_D(QGraphicsView); + if (mode == d->cacheMode) + return; + d->cacheMode = mode; + resetCachedContent(); +} + +/*! + Resets any cached content. Calling this function will clear + QGraphicsView's cache. If the current cache mode is \l CacheNone, this + function does nothing. + + This function is called automatically for you when the backgroundBrush or + QGraphicsScene::backgroundBrush properties change; you only need to call + this function if you have reimplemented QGraphicsScene::drawBackground() + or QGraphicsView::drawBackground() to draw a custom background, and need + to trigger a full redraw. + + \sa cacheMode() +*/ +void QGraphicsView::resetCachedContent() +{ + Q_D(QGraphicsView); + if (d->cacheMode == CacheNone) + return; + + if (d->cacheMode & CacheBackground) { + // Background caching is enabled. + d->mustResizeBackgroundPixmap = true; + d->updateAll(); + } else if (d->mustResizeBackgroundPixmap) { + // Background caching is disabled. + // Cleanup, free some resources. + d->mustResizeBackgroundPixmap = false; + d->backgroundPixmap = QPixmap(); + d->backgroundPixmapExposed = QRegion(); + } +} + +/*! + Invalidates and schedules a redraw of \a layers inside \a rect. \a rect is + in scene coordinates. Any cached content for \a layers inside \a rect is + unconditionally invalidated and redrawn. + + You can call this function to notify QGraphicsView of changes to the + background or the foreground of the scene. It is commonly used for scenes + with tile-based backgrounds to notify changes when QGraphicsView has + enabled background caching. + + Note that QGraphicsView currently supports background caching only (see + QGraphicsView::CacheBackground). This function is equivalent to calling update() if any + layer but QGraphicsScene::BackgroundLayer is passed. + + \sa QGraphicsScene::invalidate(), update() +*/ +void QGraphicsView::invalidateScene(const QRectF &rect, QGraphicsScene::SceneLayers layers) +{ + Q_D(QGraphicsView); + if ((layers & QGraphicsScene::BackgroundLayer) && !d->mustResizeBackgroundPixmap) { + QRect viewRect = mapFromScene(rect).boundingRect(); + if (viewport()->rect().intersects(viewRect)) { + // The updated background area is exposed; schedule this area for + // redrawing. + d->backgroundPixmapExposed += viewRect; + if (d->scene) + d->scene->update(rect); + } + } +} + +/*! + \property QGraphicsView::interactive + \brief whether the view allowed scene interaction. + + If enabled, this view is set to allow scene interaction. Otherwise, this + view will not allow interaction, and any mouse or key events are ignored + (i.e., it will act as a read-only view). + + By default, this property is true. +*/ +bool QGraphicsView::isInteractive() const +{ + Q_D(const QGraphicsView); + return d->sceneInteractionAllowed; +} +void QGraphicsView::setInteractive(bool allowed) +{ + Q_D(QGraphicsView); + d->sceneInteractionAllowed = allowed; +} + +/*! + Returns a pointer to the scene that is currently visualized in the + view. If no scene is currently visualized, 0 is returned. + + \sa setScene() +*/ +QGraphicsScene *QGraphicsView::scene() const +{ + Q_D(const QGraphicsView); + return d->scene; +} + +/*! + Sets the current scene to \a scene. If \a scene is already being + viewed, this function does nothing. + + When a scene is set on a view, the QGraphicsScene::changed() signal + is automatically connected to this view's updateScene() slot, and the + view's scroll bars are adjusted to fit the size of the scene. +*/ +void QGraphicsView::setScene(QGraphicsScene *scene) +{ + Q_D(QGraphicsView); + if (d->scene == scene) + return; + + // Always update the viewport when the scene changes. + d->updateAll(); + + // Remove the previously assigned scene. + if (d->scene) { + disconnect(d->scene, SIGNAL(changed(QList)), + this, SLOT(updateScene(QList))); + disconnect(d->scene, SIGNAL(sceneRectChanged(QRectF)), + this, SLOT(updateSceneRect(QRectF))); + d->scene->d_func()->removeView(this); + d->connectedToScene = false; + + if (isActiveWindow() && isVisible()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } + if(hasFocus()) + d->scene->clearFocus(); + } + + // Assign the new scene and update the contents (scrollbars, etc.)). + if ((d->scene = scene)) { + connect(d->scene, SIGNAL(sceneRectChanged(QRectF)), + this, SLOT(updateSceneRect(QRectF))); + d->updateSceneSlotReimplementedChecked = false; + d->scene->d_func()->addView(this); + d->recalculateContentSize(); + d->lastCenterPoint = sceneRect().center(); + d->keepLastCenterPoint = true; + // We are only interested in mouse tracking if items accept + // hover events or use non-default cursors. + if (!d->scene->d_func()->allItemsIgnoreHoverEvents + || !d->scene->d_func()->allItemsUseDefaultCursor) { + d->viewport->setMouseTracking(true); + } + + // enable touch events if any items is interested in them + if (!d->scene->d_func()->allItemsIgnoreTouchEvents) + d->viewport->setAttribute(Qt::WA_AcceptTouchEvents); + + if (isActiveWindow() && isVisible()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } + } else { + d->recalculateContentSize(); + } + + d->updateInputMethodSensitivity(); + + if (d->scene && hasFocus()) + d->scene->setFocus(); +} + +/*! + \property QGraphicsView::sceneRect + \brief the area of the scene visualized by this view. + + The scene rectangle defines the extent of the scene, and in the view's case, + this means the area of the scene that you can navigate using the scroll + bars. + + If unset, or if a null QRectF is set, this property has the same value as + QGraphicsScene::sceneRect, and it changes with + QGraphicsScene::sceneRect. Otherwise, the view's scene rect is unaffected + by the scene. + + Note that, although the scene supports a virtually unlimited size, the + range of the scroll bars will never exceed the range of an integer + (INT_MIN, INT_MAX). When the scene is larger than the scroll bars' values, + you can choose to use translate() to navigate the scene instead. + + By default, this property contains a rectangle at the origin with zero + width and height. + + \sa QGraphicsScene::sceneRect +*/ +QRectF QGraphicsView::sceneRect() const +{ + Q_D(const QGraphicsView); + if (d->hasSceneRect) + return d->sceneRect; + if (d->scene) + return d->scene->sceneRect(); + return QRectF(); +} +void QGraphicsView::setSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsView); + d->hasSceneRect = !rect.isNull(); + d->sceneRect = rect; + d->recalculateContentSize(); +} + +/*! + Returns the current transformation matrix for the view. If no current + transformation is set, the identity matrix is returned. + + \sa setMatrix(), transform(), rotate(), scale(), shear(), translate() +*/ +QMatrix QGraphicsView::matrix() const +{ + Q_D(const QGraphicsView); + return d->matrix.toAffine(); +} + +/*! + Sets the view's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + The transformation matrix tranforms the scene into view coordinates. Using + the default transformation, provided by the identity matrix, one pixel in + the view represents one unit in the scene (e.g., a 10x10 rectangular item + is drawn using 10x10 pixels in the view). If a 2x2 scaling matrix is + applied, the scene will be drawn in 1:2 (e.g., a 10x10 rectangular item is + then drawn using 20x20 pixels in the view). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 3 + + To simplify interation with items using a transformed view, QGraphicsView + provides mapTo... and mapFrom... functions that can translate between + scene and view coordinates. For example, you can call mapToScene() to map + a view coordinate to a floating point scene coordinate, or mapFromScene() + to map from floating point scene coordinates to view coordinates. + + \sa matrix(), setTransform(), rotate(), scale(), shear(), translate() +*/ +void QGraphicsView::setMatrix(const QMatrix &matrix, bool combine) +{ + setTransform(QTransform(matrix), combine); +} + +/*! + Resets the view transformation matrix to the identity matrix. + + \sa resetTransform() +*/ +void QGraphicsView::resetMatrix() +{ + resetTransform(); +} + +/*! + Rotates the current view transformation \a angle degrees clockwise. + + \sa setTransform(), transform(), scale(), shear(), translate() +*/ +void QGraphicsView::rotate(qreal angle) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.rotate(angle); + setTransform(matrix); +} + +/*! + Scales the current view transformation by (\a sx, \a sy). + + \sa setTransform(), transform(), rotate(), shear(), translate() +*/ +void QGraphicsView::scale(qreal sx, qreal sy) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.scale(sx, sy); + setTransform(matrix); +} + +/*! + Shears the current view transformation by (\a sh, \a sv). + + \sa setTransform(), transform(), rotate(), scale(), translate() +*/ +void QGraphicsView::shear(qreal sh, qreal sv) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.shear(sh, sv); + setTransform(matrix); +} + +/*! + Translates the current view transformation by (\a dx, \a dy). + + \sa setTransform(), transform(), rotate(), shear() +*/ +void QGraphicsView::translate(qreal dx, qreal dy) +{ + Q_D(QGraphicsView); + QTransform matrix = d->matrix; + matrix.translate(dx, dy); + setTransform(matrix); +} + +/*! + Scrolls the contents of the viewport to ensure that the scene + coordinate \a pos, is centered in the view. + + Because \a pos is a floating point coordinate, and the scroll bars operate + on integer coordinates, the centering is only an approximation. + + \note If the item is close to or outside the border, it will be visible + in the view, but not centered. + + \sa ensureVisible() +*/ +void QGraphicsView::centerOn(const QPointF &pos) +{ + Q_D(QGraphicsView); + qreal width = viewport()->width(); + qreal height = viewport()->height(); + QPointF viewPoint = d->matrix.map(pos); + QPointF oldCenterPoint = pos; + + if (!d->leftIndent) { + if (isRightToLeft()) { + qint64 horizontal = 0; + horizontal += horizontalScrollBar()->minimum(); + horizontal += horizontalScrollBar()->maximum(); + horizontal -= int(viewPoint.x() - width / 2.0); + horizontalScrollBar()->setValue(horizontal); + } else { + horizontalScrollBar()->setValue(int(viewPoint.x() - width / 2.0)); + } + } + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewPoint.y() - height / 2.0)); + d->lastCenterPoint = oldCenterPoint; +} + +/*! + \fn QGraphicsView::centerOn(qreal x, qreal y) + \overload + + This function is provided for convenience. It's equivalent to calling + centerOn(QPointF(\a x, \a y)). +*/ + +/*! + \overload + + Scrolls the contents of the viewport to ensure that \a item + is centered in the view. + + \sa ensureVisible() +*/ +void QGraphicsView::centerOn(const QGraphicsItem *item) +{ + centerOn(item->sceneBoundingRect().center()); +} + +/*! + Scrolls the contents of the viewport so that the scene rectangle \a rect + is visible, with margins specified in pixels by \a xmargin and \a + ymargin. If the specified rect cannot be reached, the contents are + scrolled to the nearest valid position. The default value for both margins + is 50 pixels. + + \sa centerOn() +*/ +void QGraphicsView::ensureVisible(const QRectF &rect, int xmargin, int ymargin) +{ + Q_D(QGraphicsView); + qreal width = viewport()->width(); + qreal height = viewport()->height(); + QRectF viewRect = d->matrix.mapRect(rect); + + qreal left = d->horizontalScroll(); + qreal right = left + width; + qreal top = d->verticalScroll(); + qreal bottom = top + height; + + if (viewRect.left() <= left + xmargin) { + // need to scroll from the left + if (!d->leftIndent) + horizontalScrollBar()->setValue(int(viewRect.left() - xmargin - 0.5)); + } + if (viewRect.right() >= right - xmargin) { + // need to scroll from the right + if (!d->leftIndent) + horizontalScrollBar()->setValue(int(viewRect.right() - width + xmargin + 0.5)); + } + if (viewRect.top() <= top + ymargin) { + // need to scroll from the top + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewRect.top() - ymargin - 0.5)); + } + if (viewRect.bottom() >= bottom - ymargin) { + // need to scroll from the bottom + if (!d->topIndent) + verticalScrollBar()->setValue(int(viewRect.bottom() - height + ymargin + 0.5)); + } +} + +/*! + \fn QGraphicsView::ensureVisible(qreal x, qreal y, qreal w, qreal h, + int xmargin, int ymargin) + \overload + + This function is provided for convenience. It's equivalent to calling + ensureVisible(QRectF(\a x, \a y, \a w, \a h), \a xmargin, \a ymargin). +*/ + +/*! + \overload + + Scrolls the contents of the viewport so that the center of item \a item is + visible, with margins specified in pixels by \a xmargin and \a ymargin. If + the specified point cannot be reached, the contents are scrolled to the + nearest valid position. The default value for both margins is 50 pixels. + + \sa centerOn() +*/ +void QGraphicsView::ensureVisible(const QGraphicsItem *item, int xmargin, int ymargin) +{ + ensureVisible(item->sceneBoundingRect(), xmargin, ymargin); +} + +/*! + Scales the view matrix and scrolls the scroll bars to ensure that the + scene rectangle \a rect fits inside the viewport. \a rect must be inside + the scene rect; otherwise, fitInView() cannot guarantee that the whole + rect is visible. + + This function keeps the view's rotation, translation, or shear. The view + is scaled according to \a aspectRatioMode. \a rect will be centered in the + view if it does not fit tightly. + + It's common to call fitInView() from inside a reimplementation of + resizeEvent(), to ensure that the whole scene, or parts of the scene, + scales automatically to fit the new size of the viewport as the view is + resized. Note though, that calling fitInView() from inside resizeEvent() + can lead to unwanted resize recursion, if the new transformation toggles + the automatic state of the scrollbars. You can toggle the scrollbar + policies to always on or always off to prevent this (see + horizontalScrollBarPolicy() and verticalScrollBarPolicy()). + + If \a rect is empty, or if the viewport is too small, this + function will do nothing. + + \sa setTransform(), ensureVisible(), centerOn() +*/ +void QGraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode) +{ + Q_D(QGraphicsView); + if (!d->scene || rect.isNull()) + return; + + // Reset the view scale to 1:1. + QRectF unity = d->matrix.mapRect(QRectF(0, 0, 1, 1)); + if (unity.isEmpty()) + return; + scale(1 / unity.width(), 1 / unity.height()); + + // Find the ideal x / y scaling ratio to fit \a rect in the view. + int margin = 2; + QRectF viewRect = viewport()->rect().adjusted(margin, margin, -margin, -margin); + if (viewRect.isEmpty()) + return; + QRectF sceneRect = d->matrix.mapRect(rect); + if (sceneRect.isEmpty()) + return; + qreal xratio = viewRect.width() / sceneRect.width(); + qreal yratio = viewRect.height() / sceneRect.height(); + + // Respect the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Scale and center on the center of \a rect. + scale(xratio, yratio); + centerOn(rect.center()); +} + +/*! + \fn void QGraphicsView::fitInView(qreal x, qreal y, qreal w, qreal h, + Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) + + \overload + + This convenience function is equivalent to calling + fitInView(QRectF(\a x, \a y, \a w, \a h), \a aspectRatioMode). + + \sa ensureVisible(), centerOn() +*/ + +/*! + \overload + + Ensures that \a item fits tightly inside the view, scaling the view + according to \a aspectRatioMode. + + \sa ensureVisible(), centerOn() +*/ +void QGraphicsView::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode) +{ + QPainterPath path = item->isClipped() ? item->clipPath() : item->shape(); + if (item->d_ptr->hasTranslateOnlySceneTransform()) { + path.translate(item->d_ptr->sceneTransform.dx(), item->d_ptr->sceneTransform.dy()); + fitInView(path.boundingRect(), aspectRatioMode); + } else { + fitInView(item->d_ptr->sceneTransform.map(path).boundingRect(), aspectRatioMode); + } +} + +/*! + Renders the \a source rect, which is in view coordinates, from the scene + into \a target, which is in paint device coordinates, using \a + painter. This function is useful for capturing the contents of the view + onto a paint device, such as a QImage (e.g., to take a screenshot), or for + printing to QPrinter. For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 4 + + If \a source is a null rect, this function will use viewport()->rect() to + determine what to draw. If \a target is a null rect, the full dimensions + of \a painter's paint device (e.g., for a QPrinter, the page size) will be + used. + + The source rect contents will be transformed according to \a + aspectRatioMode to fit into the target rect. By default, the aspect ratio + is kept, and \a source is scaled to fit in \a target. + + \sa QGraphicsScene::render() +*/ +void QGraphicsView::render(QPainter *painter, const QRectF &target, const QRect &source, + Qt::AspectRatioMode aspectRatioMode) +{ + // ### Switch to using the recursive rendering algorithm instead. + + Q_D(QGraphicsView); + if (!d->scene || !(painter && painter->isActive())) + return; + + // Default source rect = viewport rect + QRect sourceRect = source; + if (source.isNull()) + sourceRect = viewport()->rect(); + + // Default target rect = device rect + QRectF targetRect = target; + if (target.isNull()) { + if (painter->device()->devType() == QInternal::Picture) + targetRect = sourceRect; + else + targetRect.setRect(0, 0, painter->device()->width(), painter->device()->height()); + } + + // Find the ideal x / y scaling ratio to fit \a source into \a target. + qreal xratio = targetRect.width() / sourceRect.width(); + qreal yratio = targetRect.height() / sourceRect.height(); + + // Scale according to the aspect ratio mode. + switch (aspectRatioMode) { + case Qt::KeepAspectRatio: + xratio = yratio = qMin(xratio, yratio); + break; + case Qt::KeepAspectRatioByExpanding: + xratio = yratio = qMax(xratio, yratio); + break; + case Qt::IgnoreAspectRatio: + break; + } + + // Find all items to draw, and reverse the list (we want to draw + // in reverse order). + QPolygonF sourceScenePoly = mapToScene(sourceRect.adjusted(-1, -1, 1, 1)); + QList itemList = d->scene->items(sourceScenePoly, + Qt::IntersectsItemBoundingRect); + QGraphicsItem **itemArray = new QGraphicsItem *[itemList.size()]; + int numItems = itemList.size(); + for (int i = 0; i < numItems; ++i) + itemArray[numItems - i - 1] = itemList.at(i); + itemList.clear(); + + // Setup painter matrix. + QTransform moveMatrix = QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + QTransform painterMatrix = d->matrix * moveMatrix; + painterMatrix *= QTransform() + .translate(targetRect.left(), targetRect.top()) + .scale(xratio, yratio) + .translate(-sourceRect.left(), -sourceRect.top()); + + // Generate the style options + QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems); + for (int i = 0; i < numItems; ++i) + itemArray[i]->d_ptr->initStyleOption(&styleOptionArray[i], painterMatrix, targetRect.toRect()); + + painter->save(); + + // Clip in device coordinates to avoid QRegion transformations. + painter->setClipRect(targetRect); + QPainterPath path; + path.addPolygon(sourceScenePoly); + path.closeSubpath(); + painter->setClipPath(painterMatrix.map(path), Qt::IntersectClip); + + // Transform the painter. + painter->setTransform(painterMatrix, true); + + // Render the scene. + QRectF sourceSceneRect = sourceScenePoly.boundingRect(); + drawBackground(painter, sourceSceneRect); + drawItems(painter, numItems, itemArray, styleOptionArray); + drawForeground(painter, sourceSceneRect); + + delete [] itemArray; + d->freeStyleOptionsArray(styleOptionArray); + + painter->restore(); +} + +/*! + Returns a list of all the items in the associated scene, in descending + stacking order (i.e., the first item in the returned list is the uppermost + item). + + \sa QGraphicsScene::items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items() const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(); +} + +/*! + Returns a list of all the items at the position \a pos in the view. The + items are listed in descending stacking order (i.e., the first item in the + list is the uppermost item, and the last item is the lowermost item). \a + pos is in viewport coordinates. + + This function is most commonly called from within mouse event handlers in + a subclass in QGraphicsView. \a pos is in untransformed viewport + coordinates, just like QMouseEvent::pos(). + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 5 + + \sa QGraphicsScene::items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPoint &pos) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + // ### Unify these two, and use the items(QPointF) version in + // QGraphicsScene instead. The scene items function could use the viewport + // transform to map the point to a rect/polygon. + if ((d->identityMatrix || d->matrix.type() <= QTransform::TxScale)) { + // Use the rect version + QTransform xinv = viewportTransform().inverted(); + return d->scene->items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewportTransform()); + } + // Use the polygon version + return d->scene->items(mapToScene(pos.x(), pos.y(), 1, 1), + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewportTransform()); +} + +/*! + \fn QGraphicsView::items(int x, int y) const + + This function is provided for convenience. It's equivalent to calling + items(QPoint(\a x, \a y)). +*/ + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a rect. \a rect is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a rect are returned. + + The items are sorted in descending stacking order (i.e., the first item in + the returned list is the uppermost item). + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QRect &rect, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(rect), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + \fn QList QGraphicsView::items(int x, int y, int w, int h, Qt::ItemSelectionMode mode) const + \since 4.3 + + This convenience function is equivalent to calling items(QRectF(\a x, \a + y, \a w, \a h), \a mode). +*/ + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a polygon. \a polygon is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a polygon are returned. + + The items are sorted by descending stacking order (i.e., the first item in + the returned list is the uppermost item). + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPolygon &polygon, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(polygon), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + \overload + + Returns a list of all the items that, depending on \a mode, are either + contained by or intersect with \a path. \a path is in viewport + coordinates. + + The default value for \a mode is Qt::IntersectsItemShape; all items whose + exact shape intersects with or is contained by \a path are returned. + + \sa itemAt(), items(), mapToScene(), {QGraphicsItem#Sorting}{Sorting} +*/ +QList QGraphicsView::items(const QPainterPath &path, Qt::ItemSelectionMode mode) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QList(); + return d->scene->items(mapToScene(path), mode, Qt::DescendingOrder, viewportTransform()); +} + +/*! + Returns the item at position \a pos, which is in viewport coordinates. + If there are several items at this position, this function returns + the topmost item. + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 6 + + \sa items(), {QGraphicsItem#Sorting}{Sorting} +*/ +QGraphicsItem *QGraphicsView::itemAt(const QPoint &pos) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return 0; + QList itemsAtPos = items(pos); + return itemsAtPos.isEmpty() ? 0 : itemsAtPos.first(); +} + +/*! + \overload + \fn QGraphicsItem *QGraphicsView::itemAt(int x, int y) const + + This function is provided for convenience. It's equivalent to + calling itemAt(QPoint(\a x, \a y)). +*/ + +/*! + Returns the viewport coordinate \a point mapped to scene coordinates. + + Note: It can be useful to map the whole rectangle covered by the pixel at + \a point instead of the point itself. To do this, you can call + mapToScene(QRect(\a point, QSize(2, 2))). + + \sa mapFromScene() +*/ +QPointF QGraphicsView::mapToScene(const QPoint &point) const +{ + Q_D(const QGraphicsView); + QPointF p = point; + p.rx() += d->horizontalScroll(); + p.ry() += d->verticalScroll(); + return d->identityMatrix ? p : d->matrix.inverted().map(p); +} + +/*! + \fn QGraphicsView::mapToScene(int x, int y) const + + This function is provided for convenience. It's equivalent to calling + mapToScene(QPoint(\a x, \a y)). +*/ + +/*! + Returns the viewport rectangle \a rect mapped to a scene coordinate + polygon. + + \sa mapFromScene() +*/ +QPolygonF QGraphicsView::mapToScene(const QRect &rect) const +{ + Q_D(const QGraphicsView); + if (!rect.isValid()) + return QPolygonF(); + + QPointF scrollOffset(d->horizontalScroll(), d->verticalScroll()); + QRect r = rect.adjusted(0, 0, 1, 1); + QPointF tl = scrollOffset + r.topLeft(); + QPointF tr = scrollOffset + r.topRight(); + QPointF br = scrollOffset + r.bottomRight(); + QPointF bl = scrollOffset + r.bottomLeft(); + + QPolygonF poly(4); + if (!d->identityMatrix) { + QTransform x = d->matrix.inverted(); + poly[0] = x.map(tl); + poly[1] = x.map(tr); + poly[2] = x.map(br); + poly[3] = x.map(bl); + } else { + poly[0] = tl; + poly[1] = tr; + poly[2] = br; + poly[3] = bl; + } + return poly; +} + +/*! + \fn QGraphicsView::mapToScene(int x, int y, int w, int h) const + + This function is provided for convenience. It's equivalent to calling + mapToScene(QRect(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns the viewport polygon \a polygon mapped to a scene coordinate + polygon. + + \sa mapFromScene() +*/ +QPolygonF QGraphicsView::mapToScene(const QPolygon &polygon) const +{ + QPolygonF poly; + foreach (const QPoint &point, polygon) + poly << mapToScene(point); + return poly; +} + +/*! + Returns the viewport painter path \a path mapped to a scene coordinate + painter path. + + \sa mapFromScene() +*/ +QPainterPath QGraphicsView::mapToScene(const QPainterPath &path) const +{ + Q_D(const QGraphicsView); + QTransform matrix = QTransform::fromTranslate(d->horizontalScroll(), d->verticalScroll()); + matrix *= d->matrix.inverted(); + return matrix.map(path); +} + +/*! + Returns the scene coordinate \a point to viewport coordinates. + + \sa mapToScene() +*/ +QPoint QGraphicsView::mapFromScene(const QPointF &point) const +{ + Q_D(const QGraphicsView); + QPointF p = d->identityMatrix ? point : d->matrix.map(point); + p.rx() -= d->horizontalScroll(); + p.ry() -= d->verticalScroll(); + return p.toPoint(); +} + +/*! + \fn QGraphicsView::mapFromScene(qreal x, qreal y) const + + This function is provided for convenience. It's equivalent to + calling mapFromScene(QPointF(\a x, \a y)). +*/ + +/*! + Returns the scene rectangle \a rect to a viewport coordinate + polygon. + + \sa mapToScene() +*/ +QPolygon QGraphicsView::mapFromScene(const QRectF &rect) const +{ + Q_D(const QGraphicsView); + QPointF tl; + QPointF tr; + QPointF br; + QPointF bl; + if (!d->identityMatrix) { + const QTransform &x = d->matrix; + tl = x.map(rect.topLeft()); + tr = x.map(rect.topRight()); + br = x.map(rect.bottomRight()); + bl = x.map(rect.bottomLeft()); + } else { + tl = rect.topLeft(); + tr = rect.topRight(); + br = rect.bottomRight(); + bl = rect.bottomLeft(); + } + QPointF scrollOffset(d->horizontalScroll(), d->verticalScroll()); + tl -= scrollOffset; + tr -= scrollOffset; + br -= scrollOffset; + bl -= scrollOffset; + + QPolygon poly(4); + poly[0] = tl.toPoint(); + poly[1] = tr.toPoint(); + poly[2] = br.toPoint(); + poly[3] = bl.toPoint(); + return poly; +} + +/*! + \fn QGraphicsView::mapFromScene(qreal x, qreal y, qreal w, qreal h) const + + This function is provided for convenience. It's equivalent to + calling mapFromScene(QRectF(\a x, \a y, \a w, \a h)). +*/ + +/*! + Returns the scene coordinate polygon \a polygon to a viewport coordinate + polygon. + + \sa mapToScene() +*/ +QPolygon QGraphicsView::mapFromScene(const QPolygonF &polygon) const +{ + QPolygon poly; + foreach (const QPointF &point, polygon) + poly << mapFromScene(point); + return poly; +} + +/*! + Returns the scene coordinate painter path \a path to a viewport coordinate + painter path. + + \sa mapToScene() +*/ +QPainterPath QGraphicsView::mapFromScene(const QPainterPath &path) const +{ + Q_D(const QGraphicsView); + QTransform matrix = d->matrix; + matrix *= QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + return matrix.map(path); +} + +/*! + \reimp +*/ +QVariant QGraphicsView::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QGraphicsView); + if (!d->scene) + return QVariant(); + + QVariant value = d->scene->inputMethodQuery(query); + if (value.type() == QVariant::RectF) + value = d->mapRectFromScene(value.toRectF()); + else if (value.type() == QVariant::PointF) + value = mapFromScene(value.toPointF()); + else if (value.type() == QVariant::Rect) + value = d->mapRectFromScene(value.toRect()).toRect(); + else if (value.type() == QVariant::Point) + value = mapFromScene(value.toPoint()); + return value; +} + +/*! + \property QGraphicsView::backgroundBrush + \brief the background brush of the scene. + + This property sets the background brush for the scene in this view. It is + used to override the scene's own background, and defines the behavior of + drawBackground(). To provide custom background drawing for this view, you + can reimplement drawBackground() instead. + + By default, this property contains a brush with the Qt::NoBrush pattern. + + \sa QGraphicsScene::backgroundBrush, foregroundBrush +*/ +QBrush QGraphicsView::backgroundBrush() const +{ + Q_D(const QGraphicsView); + return d->backgroundBrush; +} +void QGraphicsView::setBackgroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsView); + d->backgroundBrush = brush; + d->updateAll(); + + if (d->cacheMode & CacheBackground) { + // Invalidate the background pixmap + d->mustResizeBackgroundPixmap = true; + } +} + +/*! + \property QGraphicsView::foregroundBrush + \brief the foreground brush of the scene. + + This property sets the foreground brush for the scene in this view. It is + used to override the scene's own foreground, and defines the behavior of + drawForeground(). To provide custom foreground drawing for this view, you + can reimplement drawForeground() instead. + + By default, this property contains a brush with the Qt::NoBrush pattern. + + \sa QGraphicsScene::foregroundBrush, backgroundBrush +*/ +QBrush QGraphicsView::foregroundBrush() const +{ + Q_D(const QGraphicsView); + return d->foregroundBrush; +} +void QGraphicsView::setForegroundBrush(const QBrush &brush) +{ + Q_D(QGraphicsView); + d->foregroundBrush = brush; + d->updateAll(); +} + +/*! + Schedules an update of the scene rectangles \a rects. + + \sa QGraphicsScene::changed() +*/ +void QGraphicsView::updateScene(const QList &rects) +{ + // ### Note: Since 4.5, this slot is only called if the user explicitly + // establishes a connection between the scene and the view, as the scene + // and view are no longer connected. We need to keep it working (basically + // leave it as it is), but the new delivery path is through + // QGraphicsScenePrivate::itemUpdate(). + Q_D(QGraphicsView); + if (d->fullUpdatePending || d->viewportUpdateMode == QGraphicsView::NoViewportUpdate) + return; + + // Extract and reset dirty scene rect info. + QVector dirtyViewportRects; + const QVector &dirtyRects = d->dirtyRegion.rects(); + for (int i = 0; i < dirtyRects.size(); ++i) + dirtyViewportRects += dirtyRects.at(i); + d->dirtyRegion = QRegion(); + d->dirtyBoundingRect = QRect(); + + bool fullUpdate = !d->accelerateScrolling || d->viewportUpdateMode == QGraphicsView::FullViewportUpdate; + bool boundingRectUpdate = (d->viewportUpdateMode == QGraphicsView::BoundingRectViewportUpdate) + || (d->viewportUpdateMode == QGraphicsView::SmartViewportUpdate + && ((dirtyViewportRects.size() + rects.size()) >= QGRAPHICSVIEW_REGION_RECT_THRESHOLD)); + + QRegion updateRegion; + QRect boundingRect; + QRect viewportRect = viewport()->rect(); + bool redraw = false; + QTransform transform = viewportTransform(); + + // Convert scene rects to viewport rects. + foreach (const QRectF &rect, rects) { + QRect xrect = transform.mapRect(rect).toAlignedRect(); + if (!(d->optimizationFlags & DontAdjustForAntialiasing)) + xrect.adjust(-2, -2, 2, 2); + else + xrect.adjust(-1, -1, 1, 1); + if (!viewportRect.intersects(xrect)) + continue; + dirtyViewportRects << xrect; + } + + foreach (const QRect &rect, dirtyViewportRects) { + // Add the exposed rect to the update region. In rect update + // mode, we only count the bounding rect of items. + if (!boundingRectUpdate) { + updateRegion += rect; + } else { + boundingRect |= rect; + } + redraw = true; + if (fullUpdate) { + // If fullUpdate is true and we found a visible dirty rect, + // we're done. + break; + } + } + + if (!redraw) + return; + + if (fullUpdate) + viewport()->update(); + else if (boundingRectUpdate) + viewport()->update(boundingRect); + else + viewport()->update(updateRegion); +} + +/*! + Notifies QGraphicsView that the scene's scene rect has changed. \a rect + is the new scene rect. If the view already has an explicitly set scene + rect, this function does nothing. + + \sa sceneRect, QGraphicsScene::sceneRectChanged() +*/ +void QGraphicsView::updateSceneRect(const QRectF &rect) +{ + Q_D(QGraphicsView); + if (!d->hasSceneRect) { + d->sceneRect = rect; + d->recalculateContentSize(); + } +} + +/*! + This slot is called by QAbstractScrollArea after setViewport() has been + called. Reimplement this function in a subclass of QGraphicsView to + initialize the new viewport \a widget before it is used. + + \sa setViewport() +*/ +void QGraphicsView::setupViewport(QWidget *widget) +{ + Q_D(QGraphicsView); + + if (!widget) { + qWarning("QGraphicsView::setupViewport: cannot initialize null widget"); + return; + } + + const bool isGLWidget = widget->inherits("QGLWidget"); + + d->accelerateScrolling = !(isGLWidget); + + widget->setFocusPolicy(Qt::StrongFocus); + + if (!isGLWidget) { + // autoFillBackground enables scroll acceleration. + widget->setAutoFillBackground(true); + } + + // We are only interested in mouse tracking if items + // accept hover events or use non-default cursors or if + // AnchorUnderMouse is used as transformation or resize anchor. + if ((d->scene && (!d->scene->d_func()->allItemsIgnoreHoverEvents + || !d->scene->d_func()->allItemsUseDefaultCursor)) + || d->transformationAnchor == AnchorUnderMouse + || d->resizeAnchor == AnchorUnderMouse) { + widget->setMouseTracking(true); + } + + // enable touch events if any items is interested in them + if (d->scene && !d->scene->d_func()->allItemsIgnoreTouchEvents) + widget->setAttribute(Qt::WA_AcceptTouchEvents); + +#ifndef QT_NO_GESTURES + if (d->scene) { + foreach (Qt::GestureType gesture, d->scene->d_func()->grabbedGestures.keys()) + widget->grabGesture(gesture); + } +#endif + + widget->setAcceptDrops(acceptDrops()); +} + +/*! + \reimp +*/ +bool QGraphicsView::event(QEvent *event) +{ + Q_D(QGraphicsView); + + if (d->sceneInteractionAllowed) { + switch (event->type()) { + case QEvent::ShortcutOverride: + if (d->scene) + return QApplication::sendEvent(d->scene, event); + break; + case QEvent::KeyPress: + if (d->scene) { + QKeyEvent *k = static_cast(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + // Send the key events to the scene. This will invoke the + // scene's tab focus handling, and if the event is + // accepted, we return (prevent further event delivery), + // and the base implementation will call QGraphicsView's + // focusNextPrevChild() function. If the event is ignored, + // we fall back to standard tab focus handling. + QApplication::sendEvent(d->scene, event); + if (event->isAccepted()) + return true; + // Ensure the event doesn't propagate just because the + // scene ignored it. If the event propagates, then tab + // handling will be called twice (this and parent). + event->accept(); + } + } + break; + default: + break; + } + } + + return QAbstractScrollArea::event(event); +} + +/*! + \reimp +*/ +bool QGraphicsView::viewportEvent(QEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene) + return QAbstractScrollArea::viewportEvent(event); + + switch (event->type()) { + case QEvent::Enter: + QApplication::sendEvent(d->scene, event); + break; + case QEvent::WindowActivate: + QApplication::sendEvent(d->scene, event); + break; + case QEvent::WindowDeactivate: + // ### This is a temporary fix for until we get proper mouse + // grab events. mouseGrabberItem should be set to 0 if we lose + // the mouse grab. + // Remove all popups when the scene loses focus. + if (!d->scene->d_func()->popupWidgets.isEmpty()) + d->scene->d_func()->removePopup(d->scene->d_func()->popupWidgets.first()); + QApplication::sendEvent(d->scene, event); + break; + case QEvent::Show: + if (d->scene && isActiveWindow()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } + break; + case QEvent::Hide: + // spontaneous event will generate a WindowDeactivate. + if (!event->spontaneous() && d->scene && isActiveWindow()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } + break; + case QEvent::Leave: + // ### This is a temporary fix for until we get proper mouse grab + // events. activeMouseGrabberItem should be set to 0 if we lose the + // mouse grab. + if ((QApplication::activePopupWidget() && QApplication::activePopupWidget() != window()) + || (QApplication::activeModalWidget() && QApplication::activeModalWidget() != window()) + || (QApplication::activeWindow() != window())) { + if (!d->scene->d_func()->popupWidgets.isEmpty()) + d->scene->d_func()->removePopup(d->scene->d_func()->popupWidgets.first()); + } + d->useLastMouseEvent = false; + // a hack to pass a viewport pointer to the scene inside the leave event + Q_ASSERT(event->d == 0); + event->d = reinterpret_cast(viewport()); + QApplication::sendEvent(d->scene, event); + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *toolTip = static_cast(event); + QGraphicsSceneHelpEvent helpEvent(QEvent::GraphicsSceneHelp); + helpEvent.setWidget(viewport()); + helpEvent.setScreenPos(toolTip->globalPos()); + helpEvent.setScenePos(mapToScene(toolTip->pos())); + QApplication::sendEvent(d->scene, &helpEvent); + toolTip->setAccepted(helpEvent.isAccepted()); + return true; + } +#endif + case QEvent::Paint: + // Reset full update + d->fullUpdatePending = false; + d->dirtyScrollOffset = QPoint(); + if (d->scene) { + // Check if this view reimplements the updateScene slot; if it + // does, we can't do direct update delivery and have to fall back + // to connecting the changed signal. + if (!d->updateSceneSlotReimplementedChecked) { + d->updateSceneSlotReimplementedChecked = true; + const QMetaObject *mo = metaObject(); + if (mo != &QGraphicsView::staticMetaObject) { + if (mo->indexOfSlot("updateScene(QList)") + != QGraphicsView::staticMetaObject.indexOfSlot("updateScene(QList)")) { + connect(d->scene, SIGNAL(changed(QList)), + this, SLOT(updateScene(QList))); + } + } + } + } + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + if (!isEnabled()) + return false; + + if (d->scene && d->sceneInteractionAllowed) { + // Convert and deliver the touch event to the scene. + QTouchEvent *touchEvent = static_cast(event); + touchEvent->setWidget(viewport()); + QGraphicsViewPrivate::translateTouchEvent(d, touchEvent); + (void) QApplication::sendEvent(d->scene, touchEvent); + } + + return true; + } +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + case QEvent::GestureOverride: + { + if (!isEnabled()) + return false; + + if (d->scene && d->sceneInteractionAllowed) { + QGestureEvent *gestureEvent = static_cast(event); + gestureEvent->setWidget(viewport()); + (void) QApplication::sendEvent(d->scene, gestureEvent); + } + return true; + } +#endif // QT_NO_GESTURES + default: + break; + } + + return QAbstractScrollArea::viewportEvent(event); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + \reimp +*/ +void QGraphicsView::contextMenuEvent(QContextMenuEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + + QGraphicsSceneContextMenuEvent contextEvent(QEvent::GraphicsSceneContextMenu); + contextEvent.setWidget(viewport()); + contextEvent.setScenePos(d->mousePressScenePoint); + contextEvent.setScreenPos(d->mousePressScreenPoint); + contextEvent.setModifiers(event->modifiers()); + contextEvent.setReason((QGraphicsSceneContextMenuEvent::Reason)(event->reason())); + contextEvent.setAccepted(event->isAccepted()); + QApplication::sendEvent(d->scene, &contextEvent); + event->setAccepted(contextEvent.isAccepted()); +} +#endif // QT_NO_CONTEXTMENU + +/*! + \reimp +*/ +void QGraphicsView::dropEvent(QDropEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDrop); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + event->setAccepted(sceneEvent.isAccepted()); + if (sceneEvent.isAccepted()) + event->setDropAction(sceneEvent.dropAction()); + + delete d->lastDragDropEvent; + d->lastDragDropEvent = 0; + +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragEnterEvent(QDragEnterEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Disable replaying of mouse move events. + d->useLastMouseEvent = false; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragEnter); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Store it for later use. + d->storeDragDropEvent(&sceneEvent); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + if (sceneEvent.isAccepted()) { + event->setAccepted(true); + event->setDropAction(sceneEvent.dropAction()); + } +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragLeaveEvent(QDragLeaveEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + if (!d->lastDragDropEvent) { + qWarning("QGraphicsView::dragLeaveEvent: drag leave received before drag enter"); + return; + } + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragLeave); + sceneEvent.setScenePos(d->lastDragDropEvent->scenePos()); + sceneEvent.setScreenPos(d->lastDragDropEvent->screenPos()); + sceneEvent.setButtons(d->lastDragDropEvent->buttons()); + sceneEvent.setModifiers(d->lastDragDropEvent->modifiers()); + sceneEvent.setPossibleActions(d->lastDragDropEvent->possibleActions()); + sceneEvent.setProposedAction(d->lastDragDropEvent->proposedAction()); + sceneEvent.setDropAction(d->lastDragDropEvent->dropAction()); + sceneEvent.setMimeData(d->lastDragDropEvent->mimeData()); + sceneEvent.setWidget(d->lastDragDropEvent->widget()); + sceneEvent.setSource(d->lastDragDropEvent->source()); + delete d->lastDragDropEvent; + d->lastDragDropEvent = 0; + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Accept the originating event if the scene accepted the scene event. + if (sceneEvent.isAccepted()) + event->setAccepted(true); +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::dragMoveEvent(QDragMoveEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + // Generate a scene event. + QGraphicsSceneDragDropEvent sceneEvent(QEvent::GraphicsSceneDragMove); + d->populateSceneDragDropEvent(&sceneEvent, event); + + // Store it for later use. + d->storeDragDropEvent(&sceneEvent); + + // Send it to the scene. + QApplication::sendEvent(d->scene, &sceneEvent); + + // Ignore the originating event if the scene ignored the scene event. + event->setAccepted(sceneEvent.isAccepted()); + if (sceneEvent.isAccepted()) + event->setDropAction(sceneEvent.dropAction()); +#else + Q_UNUSED(event) +#endif +} + +/*! + \reimp +*/ +void QGraphicsView::focusInEvent(QFocusEvent *event) +{ + Q_D(QGraphicsView); + d->updateInputMethodSensitivity(); + QAbstractScrollArea::focusInEvent(event); + if (d->scene) + QApplication::sendEvent(d->scene, event); + // Pass focus on if the scene cannot accept focus. + if (!d->scene || !event->isAccepted()) + QAbstractScrollArea::focusInEvent(event); +} + +/*! + \reimp +*/ +bool QGraphicsView::focusNextPrevChild(bool next) +{ + return QAbstractScrollArea::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +void QGraphicsView::focusOutEvent(QFocusEvent *event) +{ + Q_D(QGraphicsView); + QAbstractScrollArea::focusOutEvent(event); + if (d->scene) + QApplication::sendEvent(d->scene, event); +} + +/*! + \reimp +*/ +void QGraphicsView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) { + QAbstractScrollArea::keyPressEvent(event); + return; + } + QApplication::sendEvent(d->scene, event); + if (!event->isAccepted()) + QAbstractScrollArea::keyPressEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + QApplication::sendEvent(d->scene, event); + if (!event->isAccepted()) + QAbstractScrollArea::keyReleaseEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) + return; + + d->storeMouseEvent(event); + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + d->mousePressButton = event->button(); + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseDoubleClick); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(mapToScene(d->mousePressViewPoint)); + mouseEvent.setScreenPos(d->mousePressScreenPoint); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setAccepted(false); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); +} + +/*! + \reimp +*/ +void QGraphicsView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + + // Store this event for replaying, finding deltas, and for + // scroll-dragging; even in non-interactive mode, scroll hand dragging is + // allowed, so we store the event at the very top of this function. + d->storeMouseEvent(event); + d->lastMouseEvent.setAccepted(false); + + if (d->sceneInteractionAllowed) { + // Store some of the event's button-down data. + d->mousePressViewPoint = event->pos(); + d->mousePressScenePoint = mapToScene(d->mousePressViewPoint); + d->mousePressScreenPoint = event->globalPos(); + d->lastMouseMoveScenePoint = d->mousePressScenePoint; + d->lastMouseMoveScreenPoint = d->mousePressScreenPoint; + d->mousePressButton = event->button(); + + if (d->scene) { + // Convert and deliver the mouse event to the scene. + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMousePress); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(d->mousePressScenePoint); + mouseEvent.setScreenPos(d->mousePressScreenPoint); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); + + // Update the original mouse event accepted state. + bool isAccepted = mouseEvent.isAccepted(); + event->setAccepted(isAccepted); + + // Update the last mouse event accepted state. + d->lastMouseEvent.setAccepted(isAccepted); + + if (isAccepted) + return; + } + } + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && !d->rubberBanding) { + if (d->sceneInteractionAllowed) { + // Rubberbanding is only allowed in interactive mode. + event->accept(); + d->rubberBanding = true; + d->rubberBandRect = QRect(); + if (d->scene) { + // Initiating a rubber band always clears the selection. + d->scene->clearSelection(); + } + } + } else +#endif + if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) { + // Left-button press in scroll hand mode initiates hand scrolling. + event->accept(); + d->handScrolling = true; + d->handScrollMotions = 0; +#ifndef QT_NO_CURSOR + viewport()->setCursor(Qt::ClosedHandCursor); +#endif + } +} + +/*! + \reimp +*/ +void QGraphicsView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && d->sceneInteractionAllowed) { + d->storeMouseEvent(event); + if (d->rubberBanding) { + // Check for enough drag distance + if ((d->mousePressViewPoint - event->pos()).manhattanLength() + < QApplication::startDragDistance()) { + return; + } + + // Update old rubberband + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate && !d->rubberBandRect.isEmpty()) { + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + + // Stop rubber banding if the user has let go of all buttons (even + // if we didn't get the release events). + if (!event->buttons()) { + d->rubberBanding = false; + d->rubberBandRect = QRect(); + return; + } + + // Update rubberband position + const QPoint &mp = d->mousePressViewPoint; + QPoint ep = event->pos(); + d->rubberBandRect = QRect(qMin(mp.x(), ep.x()), qMin(mp.y(), ep.y()), + qAbs(mp.x() - ep.x()) + 1, qAbs(mp.y() - ep.y()) + 1); + + // Update new rubberband + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate){ + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + // Set the new selection area + QPainterPath selectionArea; + selectionArea.addPolygon(mapToScene(d->rubberBandRect)); + selectionArea.closeSubpath(); + if (d->scene) + d->scene->setSelectionArea(selectionArea, d->rubberBandSelectionMode, + viewportTransform()); + return; + } + } else +#endif // QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::ScrollHandDrag) { + if (d->handScrolling) { + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + QPoint delta = event->pos() - d->lastMouseEvent.pos(); + hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x())); + vBar->setValue(vBar->value() - delta.y()); + + // Detect how much we've scrolled to disambiguate scrolling from + // clicking. + ++d->handScrollMotions; + } + } + + d->mouseMoveEventHandler(event); +} + +/*! + \reimp +*/ +void QGraphicsView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QGraphicsView); + +#ifndef QT_NO_RUBBERBAND + if (d->dragMode == QGraphicsView::RubberBandDrag && d->sceneInteractionAllowed && !event->buttons()) { + if (d->rubberBanding) { + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate){ + if (d->viewportUpdateMode != FullViewportUpdate) + viewport()->update(d->rubberBandRegion(viewport(), d->rubberBandRect)); + else + d->updateAll(); + } + d->rubberBanding = false; + d->rubberBandRect = QRect(); + } + } else +#endif + if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) { +#ifndef QT_NO_CURSOR + // Restore the open hand cursor. ### There might be items + // under the mouse that have a valid cursor at this time, so + // we could repeat the steps from mouseMoveEvent(). + viewport()->setCursor(Qt::OpenHandCursor); +#endif + d->handScrolling = false; + + if (d->scene && d->sceneInteractionAllowed && !d->lastMouseEvent.isAccepted() && d->handScrollMotions <= 6) { + // If we've detected very little motion during the hand drag, and + // no item accepted the last event, we'll interpret that as a + // click to the scene, and reset the selection. + d->scene->clearSelection(); + } + } + + d->storeMouseEvent(event); + + if (!d->sceneInteractionAllowed) + return; + + if (!d->scene) + return; + + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseRelease); + mouseEvent.setWidget(viewport()); + mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint); + mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint); + mouseEvent.setScenePos(mapToScene(event->pos())); + mouseEvent.setScreenPos(event->globalPos()); + mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint); + mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint); + mouseEvent.setButtons(event->buttons()); + mouseEvent.setButton(event->button()); + mouseEvent.setModifiers(event->modifiers()); + mouseEvent.setAccepted(false); + if (event->spontaneous()) + qt_sendSpontaneousEvent(d->scene, &mouseEvent); + else + QApplication::sendEvent(d->scene, &mouseEvent); + + // Update the last mouse event selected state. + d->lastMouseEvent.setAccepted(mouseEvent.isAccepted()); + +#ifndef QT_NO_CURSOR + if (mouseEvent.isAccepted() && mouseEvent.buttons() == 0 && viewport()->testAttribute(Qt::WA_SetCursor)) { + // The last mouse release on the viewport will trigger clearing the cursor. + d->_q_unsetViewportCursor(); + } +#endif +} + +#ifndef QT_NO_WHEELEVENT +/*! + \reimp +*/ +void QGraphicsView::wheelEvent(QWheelEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene || !d->sceneInteractionAllowed) { + QAbstractScrollArea::wheelEvent(event); + return; + } + + event->ignore(); + + QGraphicsSceneWheelEvent wheelEvent(QEvent::GraphicsSceneWheel); + wheelEvent.setWidget(viewport()); + wheelEvent.setScenePos(mapToScene(event->pos())); + wheelEvent.setScreenPos(event->globalPos()); + wheelEvent.setButtons(event->buttons()); + wheelEvent.setModifiers(event->modifiers()); + wheelEvent.setDelta(event->delta()); + wheelEvent.setOrientation(event->orientation()); + wheelEvent.setAccepted(false); + QApplication::sendEvent(d->scene, &wheelEvent); + event->setAccepted(wheelEvent.isAccepted()); + if (!event->isAccepted()) + QAbstractScrollArea::wheelEvent(event); +} +#endif // QT_NO_WHEELEVENT + +/*! + \reimp +*/ +void QGraphicsView::paintEvent(QPaintEvent *event) +{ + Q_D(QGraphicsView); + if (!d->scene) { + QAbstractScrollArea::paintEvent(event); + return; + } + + // Set up painter state protection. + d->scene->d_func()->painterStateProtection = !(d->optimizationFlags & DontSavePainterState); + + // Determine the exposed region + d->exposedRegion = event->region(); + QRectF exposedSceneRect = mapToScene(d->exposedRegion.boundingRect()).boundingRect(); + + // Set up the painter + QPainter painter(viewport()); +#ifndef QT_NO_RUBBERBAND + if (d->rubberBanding && !d->rubberBandRect.isEmpty()) + painter.save(); +#endif + // Set up render hints + painter.setRenderHints(painter.renderHints(), false); + painter.setRenderHints(d->renderHints, true); + + // Set up viewport transform + const bool viewTransformed = isTransformed(); + if (viewTransformed) + painter.setWorldTransform(viewportTransform()); + const QTransform viewTransform = painter.worldTransform(); + + // Draw background + if ((d->cacheMode & CacheBackground) +#ifdef Q_WS_X11 + && X11->use_xrender +#endif + ) { + // Recreate the background pixmap, and flag the whole background as + // exposed. + if (d->mustResizeBackgroundPixmap) { + d->backgroundPixmap = QPixmap(viewport()->size()); + QBrush bgBrush = viewport()->palette().brush(viewport()->backgroundRole()); + if (!bgBrush.isOpaque()) + d->backgroundPixmap.fill(Qt::transparent); + QPainter p(&d->backgroundPixmap); + p.fillRect(0, 0, d->backgroundPixmap.width(), d->backgroundPixmap.height(), bgBrush); + d->backgroundPixmapExposed = QRegion(viewport()->rect()); + d->mustResizeBackgroundPixmap = false; + } + + // Redraw exposed areas + if (!d->backgroundPixmapExposed.isEmpty()) { + QPainter backgroundPainter(&d->backgroundPixmap); + backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip); + if (viewTransformed) + backgroundPainter.setTransform(viewTransform); + QRectF backgroundExposedSceneRect = mapToScene(d->backgroundPixmapExposed.boundingRect()).boundingRect(); + drawBackground(&backgroundPainter, backgroundExposedSceneRect); + d->backgroundPixmapExposed = QRegion(); + } + + // Blit the background from the background pixmap + if (viewTransformed) { + painter.setWorldTransform(QTransform()); + painter.drawPixmap(QPoint(), d->backgroundPixmap); + painter.setWorldTransform(viewTransform); + } else { + painter.drawPixmap(QPoint(), d->backgroundPixmap); + } + } else { + if (!(d->optimizationFlags & DontSavePainterState)) + painter.save(); + drawBackground(&painter, exposedSceneRect); + if (!(d->optimizationFlags & DontSavePainterState)) + painter.restore(); + } + + // Items + if (!(d->optimizationFlags & IndirectPainting)) { + const quint32 oldRectAdjust = d->scene->d_func()->rectAdjust; + if (d->optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + d->scene->d_func()->rectAdjust = 1; + else + d->scene->d_func()->rectAdjust = 2; + d->scene->d_func()->drawItems(&painter, viewTransformed ? &viewTransform : 0, + &d->exposedRegion, viewport()); + d->scene->d_func()->rectAdjust = oldRectAdjust; + // Make sure the painter's world transform is restored correctly when + // drawing without painter state protection (DontSavePainterState). + // We only change the worldTransform() so there's no need to do a full-blown + // save() and restore(). Also note that we don't have to do this in case of + // IndirectPainting (the else branch), because in that case we always save() + // and restore() in QGraphicsScene::drawItems(). + if (!d->scene->d_func()->painterStateProtection) + painter.setOpacity(1.0); + painter.setWorldTransform(viewTransform); + } else { + // Make sure we don't have unpolished items before we draw + if (!d->scene->d_func()->unpolishedItems.isEmpty()) + d->scene->d_func()->_q_polishItems(); + // We reset updateAll here (after we've issued polish events) + // so that we can discard update requests coming from polishEvent(). + d->scene->d_func()->updateAll = false; + + // Find all exposed items + bool allItems = false; + QList itemList = d->findItems(d->exposedRegion, &allItems, viewTransform); + if (!itemList.isEmpty()) { + // Generate the style options. + const int numItems = itemList.size(); + QGraphicsItem **itemArray = &itemList[0]; // Relies on QList internals, but is perfectly valid. + QStyleOptionGraphicsItem *styleOptionArray = d->allocStyleOptionsArray(numItems); + QTransform transform(Qt::Uninitialized); + for (int i = 0; i < numItems; ++i) { + QGraphicsItem *item = itemArray[i]; + QGraphicsItemPrivate *itemd = item->d_ptr.data(); + itemd->initStyleOption(&styleOptionArray[i], viewTransform, d->exposedRegion, allItems); + // Cache the item's area in view coordinates. + // Note that we have to do this here in case the base class implementation + // (QGraphicsScene::drawItems) is not called. If it is, we'll do this + // operation twice, but that's the price one has to pay for using indirect + // painting :-/. + const QRectF brect = adjustedItemEffectiveBoundingRect(item); + if (!itemd->itemIsUntransformable()) { + transform = item->sceneTransform(); + if (viewTransformed) + transform *= viewTransform; + } else { + transform = item->deviceTransform(viewTransform); + } + itemd->paintedViewBoundingRects.insert(d->viewport, transform.mapRect(brect).toRect()); + } + // Draw the items. + drawItems(&painter, numItems, itemArray, styleOptionArray); + d->freeStyleOptionsArray(styleOptionArray); + } + } + + // Foreground + drawForeground(&painter, exposedSceneRect); + +#ifndef QT_NO_RUBBERBAND + // Rubberband + if (d->rubberBanding && !d->rubberBandRect.isEmpty()) { + painter.restore(); + QStyleOptionRubberBand option; + option.initFrom(viewport()); + option.rect = d->rubberBandRect; + option.shape = QRubberBand::Rectangle; + + QStyleHintReturnMask mask; + if (viewport()->style()->styleHint(QStyle::SH_RubberBand_Mask, &option, viewport(), &mask)) { + // painter clipping for masked rubberbands + painter.setClipRegion(mask.region, Qt::IntersectClip); + } + + viewport()->style()->drawControl(QStyle::CE_RubberBand, &option, &painter, viewport()); + } +#endif + + painter.end(); + + // Restore painter state protection. + d->scene->d_func()->painterStateProtection = true; +} + +/*! + \reimp +*/ +void QGraphicsView::resizeEvent(QResizeEvent *event) +{ + Q_D(QGraphicsView); + // Save the last center point - the resize may scroll the view, which + // changes the center point. + QPointF oldLastCenterPoint = d->lastCenterPoint; + + QAbstractScrollArea::resizeEvent(event); + d->recalculateContentSize(); + + // Restore the center point again. + if (d->resizeAnchor == NoAnchor && !d->keepLastCenterPoint) { + d->updateLastCenterPoint(); + } else { + d->lastCenterPoint = oldLastCenterPoint; + } + d->centerView(d->resizeAnchor); + d->keepLastCenterPoint = false; + + if (d->cacheMode & CacheBackground) { + // Invalidate the background pixmap + d->mustResizeBackgroundPixmap = true; + } +} + +/*! + \reimp +*/ +void QGraphicsView::scrollContentsBy(int dx, int dy) +{ + Q_D(QGraphicsView); + d->dirtyScroll = true; + if (d->transforming) + return; + if (isRightToLeft()) + dx = -dx; + + if (d->viewportUpdateMode != QGraphicsView::NoViewportUpdate) { + if (d->viewportUpdateMode != QGraphicsView::FullViewportUpdate) { + if (d->accelerateScrolling) { +#ifndef QT_NO_RUBBERBAND + // Update new and old rubberband regions + if (!d->rubberBandRect.isEmpty()) { + QRegion rubberBandRegion(d->rubberBandRegion(viewport(), d->rubberBandRect)); + rubberBandRegion += rubberBandRegion.translated(-dx, -dy); + viewport()->update(rubberBandRegion); + } +#endif + d->dirtyScrollOffset.rx() += dx; + d->dirtyScrollOffset.ry() += dy; + d->dirtyRegion.translate(dx, dy); + viewport()->scroll(dx, dy); + } else { + d->updateAll(); + } + } else { + d->updateAll(); + } + } + + d->updateLastCenterPoint(); + + if ((d->cacheMode & CacheBackground) +#ifdef Q_WS_X11 + && X11->use_xrender +#endif + ) { + // Scroll the background pixmap + QRegion exposed; + if (!d->backgroundPixmap.isNull()) + d->backgroundPixmap.scroll(dx, dy, d->backgroundPixmap.rect(), &exposed); + + // Invalidate the background pixmap + d->backgroundPixmapExposed.translate(dx, dy); + d->backgroundPixmapExposed += exposed; + } + + // Always replay on scroll. + if (d->sceneInteractionAllowed) + d->replayLastMouseEvent(); +} + +/*! + \reimp +*/ +void QGraphicsView::showEvent(QShowEvent *event) +{ + Q_D(QGraphicsView); + d->recalculateContentSize(); + d->centerView(d->transformationAnchor); + QAbstractScrollArea::showEvent(event); +} + +/*! + \reimp +*/ +void QGraphicsView::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QGraphicsView); + if (d->scene) + QApplication::sendEvent(d->scene, event); +} + +/*! + Draws the background of the scene using \a painter, before any items and + the foreground are drawn. Reimplement this function to provide a custom + background for this view. + + If all you want is to define a color, texture or gradient for the + background, you can call setBackgroundBrush() instead. + + All painting is done in \e scene coordinates. \a rect is the exposed + rectangle. + + The default implementation fills \a rect using the view's backgroundBrush. + If no such brush is defined (the default), the scene's drawBackground() + function is called instead. + + \sa drawForeground(), QGraphicsScene::drawBackground() +*/ +void QGraphicsView::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsView); + if (d->scene && d->backgroundBrush.style() == Qt::NoBrush) { + d->scene->drawBackground(painter, rect); + return; + } + + painter->fillRect(rect, d->backgroundBrush); +} + +/*! + Draws the foreground of the scene using \a painter, after the background + and all items are drawn. Reimplement this function to provide a custom + foreground for this view. + + If all you want is to define a color, texture or gradient for the + foreground, you can call setForegroundBrush() instead. + + All painting is done in \e scene coordinates. \a rect is the exposed + rectangle. + + The default implementation fills \a rect using the view's foregroundBrush. + If no such brush is defined (the default), the scene's drawForeground() + function is called instead. + + \sa drawBackground(), QGraphicsScene::drawForeground() +*/ +void QGraphicsView::drawForeground(QPainter *painter, const QRectF &rect) +{ + Q_D(QGraphicsView); + if (d->scene && d->foregroundBrush.style() == Qt::NoBrush) { + d->scene->drawForeground(painter, rect); + return; + } + + painter->fillRect(rect, d->foregroundBrush); +} + +/*! + \obsolete + + Draws the items \a items in the scene using \a painter, after the + background and before the foreground are drawn. \a numItems is the number + of items in \a items and options in \a options. \a options is a list of + styleoptions; one for each item. Reimplement this function to provide + custom item drawing for this view. + + The default implementation calls the scene's drawItems() function. + + Since Qt 4.6, this function is not called anymore unless + the QGraphicsView::IndirectPainting flag is given as an Optimization + flag. + + \sa drawForeground(), drawBackground(), QGraphicsScene::drawItems() +*/ +void QGraphicsView::drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[]) +{ + Q_D(QGraphicsView); + if (d->scene) { + QWidget *widget = painter->device() == viewport() ? viewport() : 0; + d->scene->drawItems(painter, numItems, items, options, widget); + } +} + +/*! + Returns the current transformation matrix for the view. If no current + transformation is set, the identity matrix is returned. + + \sa setTransform(), rotate(), scale(), shear(), translate() +*/ +QTransform QGraphicsView::transform() const +{ + Q_D(const QGraphicsView); + return d->matrix; +} + +/*! + Returns a matrix that maps viewport coordinates to scene coordinates. + + \sa mapToScene(), mapFromScene() +*/ +QTransform QGraphicsView::viewportTransform() const +{ + Q_D(const QGraphicsView); + QTransform moveMatrix = QTransform::fromTranslate(-d->horizontalScroll(), -d->verticalScroll()); + return d->identityMatrix ? moveMatrix : d->matrix * moveMatrix; +} + +/*! + \since 4.6 + + Returns true if the view is transformed (i.e., a non-identity transform + has been assigned, or the scrollbars are adjusted). + + \sa setTransform(), horizontalScrollBar(), verticalScrollBar() +*/ +bool QGraphicsView::isTransformed() const +{ + Q_D(const QGraphicsView); + return !d->identityMatrix || d->horizontalScroll() || d->verticalScroll(); +} + +/*! + Sets the view's current transformation matrix to \a matrix. + + If \a combine is true, then \a matrix is combined with the current matrix; + otherwise, \a matrix \e replaces the current matrix. \a combine is false + by default. + + The transformation matrix tranforms the scene into view coordinates. Using + the default transformation, provided by the identity matrix, one pixel in + the view represents one unit in the scene (e.g., a 10x10 rectangular item + is drawn using 10x10 pixels in the view). If a 2x2 scaling matrix is + applied, the scene will be drawn in 1:2 (e.g., a 10x10 rectangular item is + then drawn using 20x20 pixels in the view). + + Example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicsview.cpp 7 + + To simplify interation with items using a transformed view, QGraphicsView + provides mapTo... and mapFrom... functions that can translate between + scene and view coordinates. For example, you can call mapToScene() to map + a view coordiate to a floating point scene coordinate, or mapFromScene() + to map from floating point scene coordinates to view coordinates. + + \sa transform(), rotate(), scale(), shear(), translate() +*/ +void QGraphicsView::setTransform(const QTransform &matrix, bool combine ) +{ + Q_D(QGraphicsView); + QTransform oldMatrix = d->matrix; + if (!combine) + d->matrix = matrix; + else + d->matrix = matrix * d->matrix; + if (oldMatrix == d->matrix) + return; + + d->identityMatrix = d->matrix.isIdentity(); + d->transforming = true; + if (d->scene) { + d->recalculateContentSize(); + d->centerView(d->transformationAnchor); + } else { + d->updateLastCenterPoint(); + } + + if (d->sceneInteractionAllowed) + d->replayLastMouseEvent(); + d->transforming = false; + + // Any matrix operation requires a full update. + d->updateAll(); +} + +/*! + Resets the view transformation to the identity matrix. + + \sa transform(), setTransform() +*/ +void QGraphicsView::resetTransform() +{ + setTransform(QTransform()); +} + +QPointF QGraphicsViewPrivate::mapToScene(const QPointF &point) const +{ + QPointF p = point; + p.rx() += horizontalScroll(); + p.ry() += verticalScroll(); + return identityMatrix ? p : matrix.inverted().map(p); +} + +QRectF QGraphicsViewPrivate::mapToScene(const QRectF &rect) const +{ + QPointF scrollOffset(horizontalScroll(), verticalScroll()); + QPointF tl = scrollOffset + rect.topLeft(); + QPointF tr = scrollOffset + rect.topRight(); + QPointF br = scrollOffset + rect.bottomRight(); + QPointF bl = scrollOffset + rect.bottomLeft(); + + QPolygonF poly(4); + if (!identityMatrix) { + QTransform x = matrix.inverted(); + poly[0] = x.map(tl); + poly[1] = x.map(tr); + poly[2] = x.map(br); + poly[3] = x.map(bl); + } else { + poly[0] = tl; + poly[1] = tr; + poly[2] = br; + poly[3] = bl; + } + return poly.boundingRect(); +} + +QT_END_NAMESPACE + +#include "moc_qgraphicsview.cpp" + +#endif // QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicsview.h b/src/gui/graphicsview/qgraphicsview.h new file mode 100644 index 0000000000..b2a7bdb226 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsview.h @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSVIEW_H +#define QGRAPHICSVIEW_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsItem; +class QPainterPath; +class QPolygonF; +class QStyleOptionGraphicsItem; + +class QGraphicsViewPrivate; +class Q_GUI_EXPORT QGraphicsView : public QAbstractScrollArea +{ + Q_OBJECT + Q_FLAGS(QPainter::RenderHints CacheMode OptimizationFlags) + Q_ENUMS(ViewportAnchor DragMode ViewportUpdateMode) + Q_PROPERTY(QBrush backgroundBrush READ backgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush foregroundBrush READ foregroundBrush WRITE setForegroundBrush) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive) + Q_PROPERTY(QRectF sceneRect READ sceneRect WRITE setSceneRect) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(QPainter::RenderHints renderHints READ renderHints WRITE setRenderHints) + Q_PROPERTY(DragMode dragMode READ dragMode WRITE setDragMode) + Q_PROPERTY(CacheMode cacheMode READ cacheMode WRITE setCacheMode) + Q_PROPERTY(ViewportAnchor transformationAnchor READ transformationAnchor WRITE setTransformationAnchor) + Q_PROPERTY(ViewportAnchor resizeAnchor READ resizeAnchor WRITE setResizeAnchor) + Q_PROPERTY(ViewportUpdateMode viewportUpdateMode READ viewportUpdateMode WRITE setViewportUpdateMode) +#ifndef QT_NO_RUBBERBAND + Q_PROPERTY(Qt::ItemSelectionMode rubberBandSelectionMode READ rubberBandSelectionMode WRITE setRubberBandSelectionMode) +#endif + Q_PROPERTY(OptimizationFlags optimizationFlags READ optimizationFlags WRITE setOptimizationFlags) + +public: + enum ViewportAnchor { + NoAnchor, + AnchorViewCenter, + AnchorUnderMouse + }; + + enum CacheModeFlag { + CacheNone = 0x0, + CacheBackground = 0x1 + }; + Q_DECLARE_FLAGS(CacheMode, CacheModeFlag) + + enum DragMode { + NoDrag, + ScrollHandDrag, + RubberBandDrag + }; + + enum ViewportUpdateMode { + FullViewportUpdate, + MinimalViewportUpdate, + SmartViewportUpdate, + NoViewportUpdate, + BoundingRectViewportUpdate + }; + + enum OptimizationFlag { + DontClipPainter = 0x1, // obsolete + DontSavePainterState = 0x2, + DontAdjustForAntialiasing = 0x4, + IndirectPainting = 0x8 + }; + Q_DECLARE_FLAGS(OptimizationFlags, OptimizationFlag) + + QGraphicsView(QWidget *parent = 0); + QGraphicsView(QGraphicsScene *scene, QWidget *parent = 0); + ~QGraphicsView(); + + QSize sizeHint() const; + + QPainter::RenderHints renderHints() const; + void setRenderHint(QPainter::RenderHint hint, bool enabled = true); + void setRenderHints(QPainter::RenderHints hints); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + + ViewportAnchor transformationAnchor() const; + void setTransformationAnchor(ViewportAnchor anchor); + + ViewportAnchor resizeAnchor() const; + void setResizeAnchor(ViewportAnchor anchor); + + ViewportUpdateMode viewportUpdateMode() const; + void setViewportUpdateMode(ViewportUpdateMode mode); + + OptimizationFlags optimizationFlags() const; + void setOptimizationFlag(OptimizationFlag flag, bool enabled = true); + void setOptimizationFlags(OptimizationFlags flags); + + DragMode dragMode() const; + void setDragMode(DragMode mode); + +#ifndef QT_NO_RUBBERBAND + Qt::ItemSelectionMode rubberBandSelectionMode() const; + void setRubberBandSelectionMode(Qt::ItemSelectionMode mode); +#endif + + CacheMode cacheMode() const; + void setCacheMode(CacheMode mode); + void resetCachedContent(); + + bool isInteractive() const; + void setInteractive(bool allowed); + + QGraphicsScene *scene() const; + void setScene(QGraphicsScene *scene); + + QRectF sceneRect() const; + void setSceneRect(const QRectF &rect); + inline void setSceneRect(qreal x, qreal y, qreal w, qreal h); + + QMatrix matrix() const; + void setMatrix(const QMatrix &matrix, bool combine = false); + void resetMatrix(); + QTransform transform() const; + QTransform viewportTransform() const; + bool isTransformed() const; + void setTransform(const QTransform &matrix, bool combine = false); + void resetTransform(); + void rotate(qreal angle); + void scale(qreal sx, qreal sy); + void shear(qreal sh, qreal sv); + void translate(qreal dx, qreal dy); + + void centerOn(const QPointF &pos); + inline void centerOn(qreal x, qreal y); + void centerOn(const QGraphicsItem *item); + void ensureVisible(const QRectF &rect, int xmargin = 50, int ymargin = 50); + inline void ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50); + void ensureVisible(const QGraphicsItem *item, int xmargin = 50, int ymargin = 50); + void fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + inline void fitInView(qreal x, qreal y, qreal w, qreal h, + Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + void fitInView(const QGraphicsItem *item, + Qt::AspectRatioMode aspectRadioMode = Qt::IgnoreAspectRatio); + + void render(QPainter *painter, const QRectF &target = QRectF(), const QRect &source = QRect(), + Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); + + QList items() const; + QList items(const QPoint &pos) const; + inline QList items(int x, int y) const; + QList items(const QRect &rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + inline QList items(int x, int y, int w, int h, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList items(const QPolygon &polygon, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QList items(const QPainterPath &path, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const; + QGraphicsItem *itemAt(const QPoint &pos) const; + inline QGraphicsItem *itemAt(int x, int y) const; + + QPointF mapToScene(const QPoint &point) const; + QPolygonF mapToScene(const QRect &rect) const; + QPolygonF mapToScene(const QPolygon &polygon) const; + QPainterPath mapToScene(const QPainterPath &path) const; + QPoint mapFromScene(const QPointF &point) const; + QPolygon mapFromScene(const QRectF &rect) const; + QPolygon mapFromScene(const QPolygonF &polygon) const; + QPainterPath mapFromScene(const QPainterPath &path) const; + inline QPointF mapToScene(int x, int y) const; + inline QPolygonF mapToScene(int x, int y, int w, int h) const; + inline QPoint mapFromScene(qreal x, qreal y) const; + inline QPolygon mapFromScene(qreal x, qreal y, qreal w, qreal h) const; + + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + QBrush backgroundBrush() const; + void setBackgroundBrush(const QBrush &brush); + + QBrush foregroundBrush() const; + void setForegroundBrush(const QBrush &brush); + +public Q_SLOTS: + void updateScene(const QList &rects); + void invalidateScene(const QRectF &rect = QRectF(), QGraphicsScene::SceneLayers layers = QGraphicsScene::AllLayers); + void updateSceneRect(const QRectF &rect); + +protected Q_SLOTS: + void setupViewport(QWidget *widget); + +protected: + QGraphicsView(QGraphicsViewPrivate &, QWidget *parent = 0); + bool event(QEvent *event); + bool viewportEvent(QEvent *event); + +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event); +#endif + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + void focusInEvent(QFocusEvent *event); + bool focusNextPrevChild(bool next); + void focusOutEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event); +#endif + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + void scrollContentsBy(int dx, int dy); + void showEvent(QShowEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + + virtual void drawBackground(QPainter *painter, const QRectF &rect); + virtual void drawForeground(QPainter *painter, const QRectF &rect); + virtual void drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[]); + +private: + Q_DECLARE_PRIVATE(QGraphicsView) + Q_DISABLE_COPY(QGraphicsView) +#ifndef QT_NO_CURSOR + Q_PRIVATE_SLOT(d_func(), void _q_setViewportCursor(const QCursor &)) + Q_PRIVATE_SLOT(d_func(), void _q_unsetViewportCursor()) +#endif + friend class QGraphicsSceneWidget; + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsItemPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsView::CacheMode) +Q_DECLARE_OPERATORS_FOR_FLAGS(QGraphicsView::OptimizationFlags) + +inline void QGraphicsView::setSceneRect(qreal ax, qreal ay, qreal aw, qreal ah) +{ setSceneRect(QRectF(ax, ay, aw, ah)); } +inline void QGraphicsView::centerOn(qreal ax, qreal ay) +{ centerOn(QPointF(ax, ay)); } +inline void QGraphicsView::ensureVisible(qreal ax, qreal ay, qreal aw, qreal ah, int xmargin, int ymargin) +{ ensureVisible(QRectF(ax, ay, aw, ah), xmargin, ymargin); } +inline void QGraphicsView::fitInView(qreal ax, qreal ay, qreal w, qreal h, Qt::AspectRatioMode mode) +{ fitInView(QRectF(ax, ay, w, h), mode); } +inline QList QGraphicsView::items(int ax, int ay) const +{ return items(QPoint(ax, ay)); } +inline QList QGraphicsView::items(int ax, int ay, int w, int h, Qt::ItemSelectionMode mode) const +{ return items(QRect(ax, ay, w, h), mode); } +inline QGraphicsItem *QGraphicsView::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } +inline QPointF QGraphicsView::mapToScene(int ax, int ay) const +{ return mapToScene(QPoint(ax, ay)); } +inline QPolygonF QGraphicsView::mapToScene(int ax, int ay, int w, int h) const +{ return mapToScene(QRect(ax, ay, w, h)); } +inline QPoint QGraphicsView::mapFromScene(qreal ax, qreal ay) const +{ return mapFromScene(QPointF(ax, ay)); } +inline QPolygon QGraphicsView::mapFromScene(qreal ax, qreal ay, qreal w, qreal h) const +{ return mapFromScene(QRectF(ax, ay, w, h)); } + +#endif // QT_NO_GRAPHICSVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSVIEW_H diff --git a/src/gui/graphicsview/qgraphicsview_p.h b/src/gui/graphicsview/qgraphicsview_p.h new file mode 100644 index 0000000000..5572b7e770 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsview_p.h @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSVIEW_P_H +#define QGRAPHICSVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicsview.h" + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +#include +#include +#include "qgraphicssceneevent.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QGraphicsViewPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsView) +public: + QGraphicsViewPrivate(); + + void recalculateContentSize(); + void centerView(QGraphicsView::ViewportAnchor anchor); + + QPainter::RenderHints renderHints; + + QGraphicsView::DragMode dragMode; + + quint32 sceneInteractionAllowed : 1; + quint32 hasSceneRect : 1; + quint32 connectedToScene : 1; + quint32 useLastMouseEvent : 1; + quint32 identityMatrix : 1; + quint32 dirtyScroll : 1; + quint32 accelerateScrolling : 1; + quint32 keepLastCenterPoint : 1; + quint32 transforming : 1; + quint32 handScrolling : 1; + quint32 mustAllocateStyleOptions : 1; + quint32 mustResizeBackgroundPixmap : 1; + quint32 fullUpdatePending : 1; + quint32 hasUpdateClip : 1; + quint32 padding : 18; + + QRectF sceneRect; + void updateLastCenterPoint(); + + qint64 horizontalScroll() const; + qint64 verticalScroll() const; + + QRectF mapRectToScene(const QRect &rect) const; + QRectF mapRectFromScene(const QRectF &rect) const; + + QRect updateClip; + QPointF mousePressItemPoint; + QPointF mousePressScenePoint; + QPoint mousePressViewPoint; + QPoint mousePressScreenPoint; + QPointF lastMouseMoveScenePoint; + QPoint lastMouseMoveScreenPoint; + QPoint dirtyScrollOffset; + Qt::MouseButton mousePressButton; + QTransform matrix; + qint64 scrollX, scrollY; + void updateScroll(); + + qreal leftIndent; + qreal topIndent; + + // Replaying mouse events + QMouseEvent lastMouseEvent; + void replayLastMouseEvent(); + void storeMouseEvent(QMouseEvent *event); + void mouseMoveEventHandler(QMouseEvent *event); + + QPointF lastCenterPoint; + Qt::Alignment alignment; + + QGraphicsView::ViewportAnchor transformationAnchor; + QGraphicsView::ViewportAnchor resizeAnchor; + QGraphicsView::ViewportUpdateMode viewportUpdateMode; + QGraphicsView::OptimizationFlags optimizationFlags; + + QPointer scene; +#ifndef QT_NO_RUBBERBAND + QRect rubberBandRect; + QRegion rubberBandRegion(const QWidget *widget, const QRect &rect) const; + bool rubberBanding; + Qt::ItemSelectionMode rubberBandSelectionMode; +#endif + int handScrollMotions; + + QGraphicsView::CacheMode cacheMode; + + QVector styleOptions; + QStyleOptionGraphicsItem *allocStyleOptionsArray(int numItems); + void freeStyleOptionsArray(QStyleOptionGraphicsItem *array); + + QBrush backgroundBrush; + QBrush foregroundBrush; + QPixmap backgroundPixmap; + QRegion backgroundPixmapExposed; + +#ifndef QT_NO_CURSOR + QCursor originalCursor; + bool hasStoredOriginalCursor; + void _q_setViewportCursor(const QCursor &cursor); + void _q_unsetViewportCursor(); +#endif + + QGraphicsSceneDragDropEvent *lastDragDropEvent; + void storeDragDropEvent(const QGraphicsSceneDragDropEvent *event); + void populateSceneDragDropEvent(QGraphicsSceneDragDropEvent *dest, + QDropEvent *source); + + QRect mapToViewRect(const QGraphicsItem *item, const QRectF &rect) const; + QRegion mapToViewRegion(const QGraphicsItem *item, const QRectF &rect) const; + QRegion dirtyRegion; + QRect dirtyBoundingRect; + void processPendingUpdates(); + inline void updateAll() + { + viewport->update(); + fullUpdatePending = true; + dirtyBoundingRect = QRect(); + dirtyRegion = QRegion(); + } + + inline void dispatchPendingUpdateRequests() + { +#ifdef Q_WS_MAC + // QWidget::update() works slightly different on the Mac without the raster engine; + // it's not part of our backing store so it needs special threatment. + if (QApplicationPrivate::graphics_system_name != QLatin1String("raster")) { + // At this point either HIViewSetNeedsDisplay (Carbon) or setNeedsDisplay: YES (Cocoa) + // is called, which means there's a pending update request. We want to dispatch it + // now because otherwise graphics view updates would require two + // round-trips in the event loop before the item is painted. + extern void qt_mac_dispatchPendingUpdateRequests(QWidget *); + qt_mac_dispatchPendingUpdateRequests(viewport->window()); + } else +#endif // !Q_WS_MAC + { + if (qt_widget_private(viewport)->paintOnScreen()) + QCoreApplication::sendPostedEvents(viewport, QEvent::UpdateRequest); + else + QCoreApplication::sendPostedEvents(viewport->window(), QEvent::UpdateRequest); + } + } + + void setUpdateClip(QGraphicsItem *); + + inline bool updateRectF(const QRectF &rect) + { + if (rect.isEmpty()) + return false; + if (optimizationFlags & QGraphicsView::DontAdjustForAntialiasing) + return updateRect(rect.toAlignedRect().adjusted(-1, -1, 1, 1)); + return updateRect(rect.toAlignedRect().adjusted(-2, -2, 2, 2)); + } + + bool updateRect(const QRect &rect); + bool updateRegion(const QRectF &rect, const QTransform &xform); + bool updateSceneSlotReimplementedChecked; + QRegion exposedRegion; + + QList findItems(const QRegion &exposedRegion, bool *allItems, + const QTransform &viewTransform) const; + + QPointF mapToScene(const QPointF &point) const; + QRectF mapToScene(const QRectF &rect) const; + static void translateTouchEvent(QGraphicsViewPrivate *d, QTouchEvent *touchEvent); + void updateInputMethodSensitivity(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GRAPHICSVIEW + +#endif diff --git a/src/gui/graphicsview/qgraphicswidget.cpp b/src/gui/graphicsview/qgraphicswidget.cpp new file mode 100644 index 0000000000..675a5c5c77 --- /dev/null +++ b/src/gui/graphicsview/qgraphicswidget.cpp @@ -0,0 +1,2388 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include "qgraphicswidget.h" +#include "qgraphicswidget_p.h" +#include "qgraphicslayout.h" +#include "qgraphicslayout_p.h" +#include "qgraphicsscene.h" +#include "qgraphicssceneevent.h" + +#ifndef QT_NO_ACTION +#include +#endif +#include +#include +#ifndef QT_NO_SHORTCUT +#include +#endif +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QGraphicsWidget + \brief The QGraphicsWidget class is the base class for all widget + items in a QGraphicsScene. + \since 4.4 + \ingroup graphicsview-api + + QGraphicsWidget is an extended base item that provides extra functionality + over QGraphicsItem. It is similar to QWidget in many ways: + + \list + \o Provides a \l palette, a \l font and a \l style(). + \o Has a defined geometry(). + \o Supports layouts with setLayout() and layout(). + \o Supports shortcuts and actions with grabShortcut() and insertAction() + \endlist + + Unlike QGraphicsItem, QGraphicsWidget is not an abstract class; you can + create instances of a QGraphicsWidget without having to subclass it. + This approach is useful for widgets that only serve the purpose of + organizing child widgets into a layout. + + QGraphicsWidget can be used as a base item for your own custom item if + you require advanced input focus handling, e.g., tab focus and activation, or + layouts. + + Since QGraphicsWidget resembles QWidget and has similar API, it is + easier to port a widget from QWidget to QGraphicsWidget, instead of + QGraphicsItem. + + \note QWidget-based widgets can be directly embedded into a + QGraphicsScene using QGraphicsProxyWidget. + + Noticeable differences between QGraphicsWidget and QWidget are: + + \table + \header \o QGraphicsWidget + \o QWidget + \row \o Coordinates and geometry are defined with qreals (doubles or + floats, depending on the platform). + \o QWidget uses integer geometry (QPoint, QRect). + \row \o The widget is already visible by default; you do not have to + call show() to display the widget. + \o QWidget is hidden by default until you call show(). + \row \o A subset of widget attributes are supported. + \o All widget attributes are supported. + \row \o A top-level item's style defaults to QGraphicsScene::style + \o A top-level widget's style defaults to QApplication::style + \row \o Graphics View provides a custom drag and drop framework, different + from QWidget. + \o Standard drag and drop framework. + \row \o Widget items do not support modality. + \o Full modality support. + \endtable + + QGraphicsWidget supports a subset of Qt's widget attributes, + (Qt::WidgetAttribute), as shown in the table below. Any attributes not + listed in this table are unsupported, or otherwise unused. + + \table + \header \o Widget Attribute \o Usage + \row \o Qt::WA_SetLayoutDirection + \o Set by setLayoutDirection(), cleared by + unsetLayoutDirection(). You can test this attribute to + check if the widget has been explicitly assigned a + \l{QGraphicsWidget::layoutDirection()} + {layoutDirection}. If the attribute is not set, the + \l{QGraphicsWidget::layoutDirection()} + {layoutDirection()} is inherited. + \row \o Qt::WA_RightToLeft + \o Toggled by setLayoutDirection(). Inherited from the + parent/scene. If set, the widget's layout will order + horizontally arranged widgets from right to left. + \row \o Qt::WA_SetStyle + \o Set and cleared by setStyle(). If this attribute is + set, the widget has been explicitly assigned a style. + If it is unset, the widget will use the scene's or the + application's style. + \row \o Qt::WA_Resized + \o Set by setGeometry() and resize(). + \row \o Qt::WA_SetPalette + \o Set by setPalette(). + \row \o Qt::WA_SetFont + \o Set by setPalette(). + \row \o Qt::WA_WindowPropagation + \o Enables propagation to window widgets. + \endtable + + Although QGraphicsWidget inherits from both QObject and QGraphicsItem, + you should use the functions provided by QGraphicsItem, \e not QObject, to + manage the relationships between parent and child items. These functions + control the stacking order of items as well as their ownership. + + \note The QObject::parent() should always return 0 for QGraphicsWidgets, + but this policy is not strictly defined. + + \sa QGraphicsProxyWidget, QGraphicsItem, {Widgets and Layouts} +*/ + +/*! + Constructs a QGraphicsWidget instance. The optional \a parent argument is + passed to QGraphicsItem's constructor. The optional \a wFlags argument + specifies the widget's window flags (e.g., whether the widget should be a + window, a tool, a popup, etc). +*/ +QGraphicsWidget::QGraphicsWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) + : QGraphicsObject(*new QGraphicsWidgetPrivate, 0, 0), QGraphicsLayoutItem(0, false) +{ + Q_D(QGraphicsWidget); + d->init(parent, wFlags); +} + +/*! + \internal + + Constructs a new QGraphicsWidget, using \a dd as parent. +*/ +QGraphicsWidget::QGraphicsWidget(QGraphicsWidgetPrivate &dd, QGraphicsItem *parent, QGraphicsScene *scene, Qt::WindowFlags wFlags) + : QGraphicsObject(dd, 0, scene), QGraphicsLayoutItem(0, false) +{ + Q_D(QGraphicsWidget); + d->init(parent, wFlags); +} + +/* + \internal + \class QGraphicsWidgetStyles + + We use this thread-safe class to maintain a hash of styles for widgets + styles. Note that QApplication::style() itself isn't thread-safe, QStyle + isn't thread-safe, and we don't have a thread-safe factory for creating + the default style, nor cloning a style. +*/ +class QGraphicsWidgetStyles +{ +public: + QStyle *styleForWidget(const QGraphicsWidget *widget) const + { + QMutexLocker locker(&mutex); + return styles.value(widget, 0); + } + + void setStyleForWidget(QGraphicsWidget *widget, QStyle *style) + { + QMutexLocker locker(&mutex); + if (style) + styles[widget] = style; + else + styles.remove(widget); + } + +private: + QMap styles; + mutable QMutex mutex; +}; +Q_GLOBAL_STATIC(QGraphicsWidgetStyles, widgetStyles) + +/*! + Destroys the QGraphicsWidget instance. +*/ +QGraphicsWidget::~QGraphicsWidget() +{ + Q_D(QGraphicsWidget); +#ifndef QT_NO_ACTION + // Remove all actions from this widget + for (int i = 0; i < d->actions.size(); ++i) { + QActionPrivate *apriv = d->actions.at(i)->d_func(); + apriv->graphicsWidgets.removeAll(this); + } + d->actions.clear(); +#endif + + if (QGraphicsScene *scn = scene()) { + QGraphicsScenePrivate *sceneD = scn->d_func(); + if (sceneD->tabFocusFirst == this) + sceneD->tabFocusFirst = (d->focusNext == this ? 0 : d->focusNext); + } + d->focusPrev->d_func()->focusNext = d->focusNext; + d->focusNext->d_func()->focusPrev = d->focusPrev; + + // Play it really safe + d->focusNext = this; + d->focusPrev = this; + + clearFocus(); + + //we check if we have a layout previously + if (d->layout) { + QGraphicsLayout *temp = d->layout; + foreach (QGraphicsItem * item, childItems()) { + // In case of a custom layout which doesn't remove and delete items, we ensure that + // the parent layout item does not point to the deleted layout. This code is here to + // avoid regression from 4.4 to 4.5, because according to 4.5 docs it is not really needed. + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (widget->parentLayoutItem() == d->layout) + widget->setParentLayoutItem(0); + } + } + d->layout = 0; + delete temp; + } + + // Remove this graphics widget from widgetStyles + widgetStyles()->setStyleForWidget(this, 0); +} + +/*! + \property QGraphicsWidget::size + \brief the size of the widget + + Calling resize() resizes the widget to a \a size bounded by minimumSize() + and maximumSize(). This property only affects the widget's width and + height (e.g., its right and bottom edges); the widget's position and + top-left corner remains unaffected. + + Resizing a widget triggers the widget to immediately receive a + \l{QEvent::GraphicsSceneResize}{GraphicsSceneResize} event with the + widget's old and new size. If the widget has a layout assigned when this + event arrives, the layout will be activated and it will automatically + update any child widgets's geometry. + + This property does not affect any layout of the parent widget. If the + widget itself is managed by a parent layout; e.g., it has a parent widget + with a layout assigned, that layout will not activate. + + By default, this property contains a size with zero width and height. + + \sa setGeometry(), QGraphicsSceneResizeEvent, QGraphicsLayout +*/ +QSizeF QGraphicsWidget::size() const +{ + return QGraphicsLayoutItem::geometry().size(); +} + +void QGraphicsWidget::resize(const QSizeF &size) +{ + setGeometry(QRectF(pos(), size)); +} + +/*! + \fn void QGraphicsWidget::resize(qreal w, qreal h) + + This convenience function is equivalent to calling resize(QSizeF(w, h)). + + \sa setGeometry(), setTransform() +*/ + +/*! + \property QGraphicsWidget::sizePolicy + \brief the size policy for the widget + \sa sizePolicy(), setSizePolicy(), QWidget::sizePolicy() +*/ + +/*! + \fn QGraphicsWidget::geometryChanged() + + This signal gets emitted whenever the geometry is changed in setGeometry(). +*/ + +/*! + \property QGraphicsWidget::geometry + \brief the geometry of the widget + + Sets the item's geometry to \a rect. The item's position and size are + modified as a result of calling this function. The item is first moved, + then resized. + + A side effect of calling this function is that the widget will receive + a move event and a resize event. Also, if the widget has a layout + assigned, the layout will activate. + + \sa geometry(), resize() +*/ +void QGraphicsWidget::setGeometry(const QRectF &rect) +{ + QGraphicsWidgetPrivate *wd = QGraphicsWidget::d_func(); + QGraphicsLayoutItemPrivate *d = QGraphicsLayoutItem::d_ptr.data(); + QRectF newGeom; + QPointF oldPos = d->geom.topLeft(); + if (!wd->inSetPos) { + setAttribute(Qt::WA_Resized); + newGeom = rect; + newGeom.setSize(rect.size().expandedTo(effectiveSizeHint(Qt::MinimumSize)) + .boundedTo(effectiveSizeHint(Qt::MaximumSize))); + if (newGeom == d->geom) + return; + + // setPos triggers ItemPositionChange, which can adjust position + wd->inSetGeometry = 1; + setPos(newGeom.topLeft()); + wd->inSetGeometry = 0; + newGeom.moveTopLeft(pos()); + + if (newGeom == d->geom) + return; + + // Update and prepare to change the geometry (remove from index) if the size has changed. + if (wd->scene) { + if (rect.topLeft() == d->geom.topLeft()) { + prepareGeometryChange(); + } + } + } + + // Update the layout item geometry + bool moved = oldPos != pos(); + if (moved) { + // Send move event. + QGraphicsSceneMoveEvent event; + event.setOldPos(oldPos); + event.setNewPos(pos()); + QApplication::sendEvent(this, &event); + if (wd->inSetPos) { + //set the new pos + d->geom.moveTopLeft(pos()); + emit geometryChanged(); + return; + } + } + QSizeF oldSize = size(); + QGraphicsLayoutItem::setGeometry(newGeom); + // Send resize event + bool resized = newGeom.size() != oldSize; + if (resized) { + QGraphicsSceneResizeEvent re; + re.setOldSize(oldSize); + re.setNewSize(newGeom.size()); + if (oldSize.width() != newGeom.size().width()) + emit widthChanged(); + if (oldSize.height() != newGeom.size().height()) + emit heightChanged(); + QApplication::sendEvent(this, &re); + } + emit geometryChanged(); +} + +/*! + \fn QRectF QGraphicsWidget::rect() const + + Returns the item's local rect as a QRectF. This function is equivalent + to QRectF(QPointF(), size()). + + \sa setGeometry(), resize() +*/ + +/*! + \fn void QGraphicsWidget::setGeometry(qreal x, qreal y, qreal w, qreal h) + + This convenience function is equivalent to calling setGeometry(QRectF( + \a x, \a y, \a w, \a h)). + + \sa geometry(), resize() +*/ + +/*! + \property QGraphicsWidget::minimumSize + \brief the minimum size of the widget + + \sa setMinimumSize(), minimumSize(), preferredSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::preferredSize + \brief the preferred size of the widget + + \sa setPreferredSize(), preferredSize(), minimumSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::maximumSize + \brief the maximum size of the widget + + \sa setMaximumSize(), maximumSize(), minimumSize, preferredSize +*/ + +/*! + Sets the widget's contents margins to \a left, \a top, \a right and \a + bottom. + + Contents margins are used by the assigned layout to define the placement + of subwidgets and layouts. Margins are particularly useful for widgets + that constrain subwidgets to only a section of its own geometry. For + example, a group box with a layout will place subwidgets inside its frame, + but below the title. + + Changing a widget's contents margins will always trigger an update(), and + any assigned layout will be activated automatically. The widget will then + receive a \l{QEvent::ContentsRectChange}{ContentsRectChange} event. + + \sa getContentsMargins(), setGeometry() +*/ +void QGraphicsWidget::setContentsMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsWidget); + + if (!d->margins && left == 0 && top == 0 && right == 0 && bottom == 0) + return; + d->ensureMargins(); + if (left == d->margins[d->Left] + && top == d->margins[d->Top] + && right == d->margins[d->Right] + && bottom == d->margins[d->Bottom]) + return; + + d->margins[d->Left] = left; + d->margins[d->Top] = top; + d->margins[d->Right] = right; + d->margins[d->Bottom] = bottom; + + if (QGraphicsLayout *l = d->layout) + l->invalidate(); + else + updateGeometry(); + + QEvent e(QEvent::ContentsRectChange); + QApplication::sendEvent(this, &e); +} + +/*! + Gets the widget's contents margins. The margins are stored in \a left, \a + top, \a right and \a bottom, as pointers to qreals. Each argument can + be \e {omitted} by passing 0. + + \sa setContentsMargins() +*/ +void QGraphicsWidget::getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsWidget); + if (left || top || right || bottom) + d->ensureMargins(); + if (left) + *left = d->margins[d->Left]; + if (top) + *top = d->margins[d->Top]; + if (right) + *right = d->margins[d->Right]; + if (bottom) + *bottom = d->margins[d->Bottom]; +} + +/*! + Sets the widget's window frame margins to \a left, \a top, \a right and + \a bottom. The default frame margins are provided by the style, and they + depend on the current window flags. + + If you would like to draw your own window decoration, you can set your + own frame margins to override the default margins. + + \sa unsetWindowFrameMargins(), getWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::setWindowFrameMargins(qreal left, qreal top, qreal right, qreal bottom) +{ + Q_D(QGraphicsWidget); + + if (!d->windowFrameMargins && left == 0 && top == 0 && right == 0 && bottom == 0) + return; + d->ensureWindowFrameMargins(); + bool unchanged = + d->windowFrameMargins[d->Left] == left + && d->windowFrameMargins[d->Top] == top + && d->windowFrameMargins[d->Right] == right + && d->windowFrameMargins[d->Bottom] == bottom; + if (d->setWindowFrameMargins && unchanged) + return; + if (!unchanged) + prepareGeometryChange(); + d->windowFrameMargins[d->Left] = left; + d->windowFrameMargins[d->Top] = top; + d->windowFrameMargins[d->Right] = right; + d->windowFrameMargins[d->Bottom] = bottom; + d->setWindowFrameMargins = true; +} + +/*! + Gets the widget's window frame margins. The margins are stored in \a left, + \a top, \a right and \a bottom as pointers to qreals. Each argument can + be \e {omitted} by passing 0. + + \sa setWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::getWindowFrameMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const +{ + Q_D(const QGraphicsWidget); + if (left || top || right || bottom) + d->ensureWindowFrameMargins(); + if (left) + *left = d->windowFrameMargins[d->Left]; + if (top) + *top = d->windowFrameMargins[d->Top]; + if (right) + *right = d->windowFrameMargins[d->Right]; + if (bottom) + *bottom = d->windowFrameMargins[d->Bottom]; +} + +/*! + Resets the window frame margins to the default value, provided by the style. + + \sa setWindowFrameMargins(), getWindowFrameMargins(), windowFrameRect() +*/ +void QGraphicsWidget::unsetWindowFrameMargins() +{ + Q_D(QGraphicsWidget); + if ((d->windowFlags & Qt::Window) && (d->windowFlags & Qt::WindowType_Mask) != Qt::Popup && + (d->windowFlags & Qt::WindowType_Mask) != Qt::ToolTip && !(d->windowFlags & Qt::FramelessWindowHint)) { + QStyleOptionTitleBar bar; + d->initStyleOptionTitleBar(&bar); + QStyle *style = this->style(); + qreal margin = style->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth); + qreal titleBarHeight = d->titleBarHeight(bar); + setWindowFrameMargins(margin, titleBarHeight, margin, margin); + } else { + setWindowFrameMargins(0, 0, 0, 0); + } + d->setWindowFrameMargins = false; +} + +/*! + Returns the widget's geometry in parent coordinates including any window + frame. + + \sa windowFrameRect(), getWindowFrameMargins(), setWindowFrameMargins() +*/ +QRectF QGraphicsWidget::windowFrameGeometry() const +{ + Q_D(const QGraphicsWidget); + return d->windowFrameMargins + ? geometry().adjusted(-d->windowFrameMargins[d->Left], -d->windowFrameMargins[d->Top], + d->windowFrameMargins[d->Right], d->windowFrameMargins[d->Bottom]) + : geometry(); +} + +/*! + Returns the widget's local rect including any window frame. + + \sa windowFrameGeometry(), getWindowFrameMargins(), setWindowFrameMargins() +*/ +QRectF QGraphicsWidget::windowFrameRect() const +{ + Q_D(const QGraphicsWidget); + return d->windowFrameMargins + ? rect().adjusted(-d->windowFrameMargins[d->Left], -d->windowFrameMargins[d->Top], + d->windowFrameMargins[d->Right], d->windowFrameMargins[d->Bottom]) + : rect(); +} + +/*! + Populates a style option object for this widget based on its current + state, and stores the output in \a option. The default implementation + populates \a option with the following properties. + + \table + \header + \o Style Option Property + \o Value + \row + \o state & QStyle::State_Enabled + \o Corresponds to QGraphicsItem::isEnabled(). + \row + \o state & QStyle::State_HasFocus + \o Corresponds to QGraphicsItem::hasFocus(). + \row + \o state & QStyle::State_MouseOver + \o Corresponds to QGraphicsItem::isUnderMouse(). + \row + \o direction + \o Corresponds to QGraphicsWidget::layoutDirection(). + \row + \o rect + \o Corresponds to QGraphicsWidget::rect().toRect(). + \row + \o palette + \o Corresponds to QGraphicsWidget::palette(). + \row + \o fontMetrics + \o Corresponds to QFontMetrics(QGraphicsWidget::font()). + \endtable + + Subclasses of QGraphicsWidget should call the base implementation, and + then test the type of \a option using qstyleoption_cast<>() or test + QStyleOption::Type before storing widget-specific options. + + For example: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 0 + + \sa QStyleOption::initFrom() +*/ +void QGraphicsWidget::initStyleOption(QStyleOption *option) const +{ + Q_ASSERT(option); + + option->state = QStyle::State_None; + if (isEnabled()) + option->state |= QStyle::State_Enabled; + if (hasFocus()) + option->state |= QStyle::State_HasFocus; + // if (window->testAttribute(Qt::WA_KeyboardFocusChange)) // ### Window + // option->state |= QStyle::State_KeyboardFocusChange; + if (isUnderMouse()) + option->state |= QStyle::State_MouseOver; + if (QGraphicsWidget *w = window()) { + if (w->isActiveWindow()) + option->state |= QStyle::State_Active; + } + if (isWindow()) + option->state |= QStyle::State_Window; + /* + ### +#ifdef Q_WS_MAC + extern bool qt_mac_can_clickThrough(const QGraphicsWidget *w); //qwidget_mac.cpp + if (!(option->state & QStyle::State_Active) && !qt_mac_can_clickThrough(widget)) + option->state &= ~QStyle::State_Enabled; + + switch (QMacStyle::widgetSizePolicy(widget)) { + case QMacStyle::SizeSmall: + option->state |= QStyle::State_Small; + break; + case QMacStyle::SizeMini: + option->state |= QStyle::State_Mini; + break; + default: + ; + } +#endif +#ifdef QT_KEYPAD_NAVIGATION + if (widget->hasEditFocus()) + state |= QStyle::State_HasEditFocus; +#endif + */ + option->direction = layoutDirection(); + option->rect = rect().toRect(); // ### truncation! + option->palette = palette(); + if (!isEnabled()) { + option->palette.setCurrentColorGroup(QPalette::Disabled); + } else if (isActiveWindow()) { + option->palette.setCurrentColorGroup(QPalette::Active); + } else { + option->palette.setCurrentColorGroup(QPalette::Inactive); + } + option->fontMetrics = QFontMetrics(font()); +} + +/*! + \reimp +*/ +QSizeF QGraphicsWidget::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_D(const QGraphicsWidget); + QSizeF sh; + if (d->layout) { + QSizeF marginSize(0,0); + if (d->margins) { + marginSize = QSizeF(d->margins[d->Left] + d->margins[d->Right], + d->margins[d->Top] + d->margins[d->Bottom]); + } + sh = d->layout->effectiveSizeHint(which, constraint - marginSize); + sh += marginSize; + } else { + switch (which) { + case Qt::MinimumSize: + sh = QSizeF(0, 0); + break; + case Qt::PreferredSize: + sh = QSizeF(50, 50); //rather arbitrary + break; + case Qt::MaximumSize: + sh = QSizeF(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + break; + default: + qWarning("QGraphicsWidget::sizeHint(): Don't know how to handle the value of 'which'"); + break; + } + } + return sh; +} + +/*! + \property QGraphicsWidget::layout + \brief The layout of the widget + + Any existing layout manager is deleted before the new layout is assigned. If + \a layout is 0, the widget is left without a layout. Existing subwidgets' + geometries will remain unaffected. + + QGraphicsWidget takes ownership of \a layout. + + All widgets that are currently managed by \a layout or all of its + sublayouts, are automatically reparented to this item. The layout is then + invalidated, and the child widget geometries are adjusted according to + this item's geometry() and contentsMargins(). Children who are not + explicitly managed by \a layout remain unaffected by the layout after + it has been assigned to this widget. + + If no layout is currently managing this widget, layout() will return 0. + +*/ + +/*! + \fn void QGraphicsWidget::layoutChanged() + This signal gets emitted whenever the layout of the item changes + \internal +*/ + +/*! + Returns this widget's layout, or 0 if no layout is currently managing this + widget. + + \sa setLayout() +*/ +QGraphicsLayout *QGraphicsWidget::layout() const +{ + Q_D(const QGraphicsWidget); + return d->layout; +} + +/*! + \fn void QGraphicsWidget::setLayout(QGraphicsLayout *layout) + + Sets the layout for this widget to \a layout. Any existing layout manager + is deleted before the new layout is assigned. If \a layout is 0, the + widget is left without a layout. Existing subwidgets' geometries will + remain unaffected. + + All widgets that are currently managed by \a layout or all of its + sublayouts, are automatically reparented to this item. The layout is then + invalidated, and the child widget geometries are adjusted according to + this item's geometry() and contentsMargins(). Children who are not + explicitly managed by \a layout remain unaffected by the layout after + it has been assigned to this widget. + + QGraphicsWidget takes ownership of \a layout. + + \sa layout(), QGraphicsLinearLayout::addItem(), QGraphicsLayout::invalidate() +*/ +void QGraphicsWidget::setLayout(QGraphicsLayout *l) +{ + Q_D(QGraphicsWidget); + if (d->layout == l) + return; + d->setLayout_helper(l); + if (!l) + return; + + // Prevent assigning a layout that is already assigned to another widget. + QGraphicsLayoutItem *oldParent = l->parentLayoutItem(); + if (oldParent && oldParent != this) { + qWarning("QGraphicsWidget::setLayout: Attempting to set a layout on %s" + " \"%s\", when the layout already has a parent", + metaObject()->className(), qPrintable(objectName())); + return; + } + + // Install and activate the layout. + l->setParentLayoutItem(this); + l->d_func()->reparentChildItems(this); + l->invalidate(); + emit layoutChanged(); +} + +/*! + Adjusts the size of the widget to its effective preferred size hint. + + This function is called implicitly when the item is shown for the first + time. + + \sa effectiveSizeHint(), Qt::MinimumSize +*/ +void QGraphicsWidget::adjustSize() +{ + QSizeF sz = effectiveSizeHint(Qt::PreferredSize); + // What if sz is not valid?! + if (sz.isValid()) + resize(sz); +} + +/*! + \property QGraphicsWidget::layoutDirection + \brief the layout direction for this widget. + + This property modifies this widget's and all of its descendants' + Qt::WA_RightToLeft attribute. It also sets this widget's + Qt::WA_SetLayoutDirection attribute. + + The widget's layout direction determines the order in which the layout + manager horizontally arranges subwidgets of this widget. The default + value depends on the language and locale of the application, and is + typically in the same direction as words are read and written. With + Qt::LeftToRight, the layout starts placing subwidgets from the left + side of this widget towards the right. Qt::RightToLeft does the opposite - + the layout will place widgets starting from the right edge moving towards + the left. + + Subwidgets inherit their layout direction from the parent. Top-level + widget items inherit their layout direction from + QGraphicsScene::layoutDirection. If you change a widget's layout direction + by calling setLayoutDirection(), the widget will send itself a + \l{QEvent::LayoutDirectionChange}{LayoutDirectionChange} event, and then + propagate the new layout direction to all its descendants. + + \sa QWidget::layoutDirection, QApplication::layoutDirection +*/ +Qt::LayoutDirection QGraphicsWidget::layoutDirection() const +{ + return testAttribute(Qt::WA_RightToLeft) ? Qt::RightToLeft : Qt::LeftToRight; +} +void QGraphicsWidget::setLayoutDirection(Qt::LayoutDirection direction) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetLayoutDirection, true); + d->setLayoutDirection_helper(direction); +} +void QGraphicsWidget::unsetLayoutDirection() +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetLayoutDirection, false); + d->resolveLayoutDirection(); +} + +/*! + Returns a pointer to the widget's style. If this widget does not have any + explicitly assigned style, the scene's style is returned instead. In turn, + if the scene does not have any assigned style, this function returns + QApplication::style(). + + \sa setStyle() +*/ +QStyle *QGraphicsWidget::style() const +{ + if (QStyle *style = widgetStyles()->styleForWidget(this)) + return style; + // ### This is not thread-safe. QApplication::style() is not thread-safe. + return scene() ? scene()->style() : QApplication::style(); +} + +/*! + Sets the widget's style to \a style. QGraphicsWidget does \e not take + ownership of \a style. + + If no style is assigned, or \a style is 0, the widget will use + QGraphicsScene::style() (if this has been set). Otherwise the widget will + use QApplication::style(). + + This function sets the Qt::WA_SetStyle attribute if \a style is not 0; + otherwise it clears the attribute. + + \sa style() +*/ +void QGraphicsWidget::setStyle(QStyle *style) +{ + setAttribute(Qt::WA_SetStyle, style != 0); + widgetStyles()->setStyleForWidget(this, style); + + // Deliver StyleChange to the widget itself (doesn't propagate). + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(this, &event); +} + +/*! + \property QGraphicsWidget::font + \brief the widgets' font + + This property provides the widget's font. + + QFont consists of font properties that have been explicitly defined and + properties implicitly inherited from the widget's parent. Hence, font() + can return a different font compared to the one set with setFont(). + This scheme allows you to define single entries in a font without + affecting the font's inherited entries. + + When a widget's font changes, it resolves its entries against its + parent widget. If the widget does not have a parent widget, it resolves + its entries against the scene. The widget then sends itself a + \l{QEvent::FontChange}{FontChange} event and notifies all its + descendants so that they can resolve their fonts as well. + + By default, this property contains the application's default font. + + \sa QApplication::font(), QGraphicsScene::font, QFont::resolve() +*/ +QFont QGraphicsWidget::font() const +{ + Q_D(const QGraphicsWidget); + QFont fnt = d->font; + fnt.resolve(fnt.resolve() | d->inheritedFontResolveMask); + return fnt; +} +void QGraphicsWidget::setFont(const QFont &font) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetFont, font.resolve() != 0); + + QFont naturalFont = d->naturalWidgetFont(); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/*! + \property QGraphicsWidget::palette + \brief the widget's palette + + This property provides the widget's palette. The palette provides colors + and brushes for color groups (e.g., QPalette::Button) and states (e.g., + QPalette::Inactive), loosely defining the general look of the widget and + its children. + + QPalette consists of color groups that have been explicitly defined, and + groups that are implicitly inherited from the widget's parent. Because of + this, palette() can return a different palette than what has been set with + setPalette(). This scheme allows you to define single entries in a palette + without affecting the palette's inherited entries. + + When a widget's palette changes, it resolves its entries against its + parent widget, or if it doesn't have a parent widget, it resolves against + the scene. It then sends itself a \l{QEvent::PaletteChange}{PaletteChange} + event, and notifies all its descendants so they can resolve their palettes + as well. + + By default, this property contains the application's default palette. + + \sa QApplication::palette(), QGraphicsScene::palette, QPalette::resolve() +*/ +QPalette QGraphicsWidget::palette() const +{ + Q_D(const QGraphicsWidget); + return d->palette; +} +void QGraphicsWidget::setPalette(const QPalette &palette) +{ + Q_D(QGraphicsWidget); + setAttribute(Qt::WA_SetPalette, palette.resolve() != 0); + + QPalette naturalPalette = d->naturalWidgetPalette(); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \property QGraphicsWidget::autoFillBackground + \brief whether the widget background is filled automatically + \since 4.7 + + If enabled, this property will cause Qt to fill the background of the + widget before invoking the paint() method. The color used is defined by the + QPalette::Window color role from the widget's \l{QPalette}{palette}. + + In addition, Windows are always filled with QPalette::Window, unless the + WA_OpaquePaintEvent or WA_NoSystemBackground attributes are set. + + By default, this property is false. + + \sa Qt::WA_OpaquePaintEvent, Qt::WA_NoSystemBackground, +*/ +bool QGraphicsWidget::autoFillBackground() const +{ + Q_D(const QGraphicsWidget); + return d->autoFillBackground; +} +void QGraphicsWidget::setAutoFillBackground(bool enabled) +{ + Q_D(QGraphicsWidget); + if (d->autoFillBackground != enabled) { + d->autoFillBackground = enabled; + update(); + } +} + +/*! + If this widget is currently managed by a layout, this function notifies + the layout that the widget's size hints have changed and the layout + may need to resize and reposition the widget accordingly. + + Call this function if the widget's sizeHint() has changed. + + \sa QGraphicsLayout::invalidate() +*/ +void QGraphicsWidget::updateGeometry() +{ + QGraphicsLayoutItem::updateGeometry(); + QGraphicsLayoutItem *parentItem = parentLayoutItem(); + + if (parentItem && parentItem->isLayout()) { + parentItem->updateGeometry(); + } else { + if (parentItem) { + QGraphicsWidget *parentWid = parentWidget(); //### + if (parentWid->isVisible()) + QApplication::postEvent(parentWid, new QEvent(QEvent::LayoutRequest)); + } + bool wasResized = testAttribute(Qt::WA_Resized); + resize(size()); // this will restrict the size + setAttribute(Qt::WA_Resized, wasResized); + } +} + +/*! + \reimp + + QGraphicsWidget uses the base implementation of this function to catch and + deliver events related to state changes in the item. Because of this, it is + very important that subclasses call the base implementation. + + \a change specifies the type of change, and \a value is the new value. + + For example, QGraphicsWidget uses ItemVisibleChange to deliver + \l{QEvent::Show} {Show} and \l{QEvent::Hide}{Hide} events, + ItemPositionHasChanged to deliver \l{QEvent::Move}{Move} events, + and ItemParentChange both to deliver \l{QEvent::ParentChange} + {ParentChange} events, and for managing the focus chain. + + QGraphicsWidget enables the ItemSendsGeometryChanges flag by default in + order to track position changes. + + \sa QGraphicsItem::itemChange() +*/ +QVariant QGraphicsWidget::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_D(QGraphicsWidget); + switch (change) { + case ItemEnabledHasChanged: { + // Send EnabledChange after the enabled state has changed. + QEvent event(QEvent::EnabledChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemVisibleChange: + if (value.toBool()) { + // Send Show event before the item has been shown. + QShowEvent event; + QApplication::sendEvent(this, &event); + bool resized = testAttribute(Qt::WA_Resized); + if (!resized) { + adjustSize(); + setAttribute(Qt::WA_Resized, false); + } + } + break; + case ItemVisibleHasChanged: + if (!value.toBool()) { + // Send Hide event after the item has been hidden. + QHideEvent event; + QApplication::sendEvent(this, &event); + } + break; + case ItemPositionHasChanged: + d->setGeometryFromSetPos(); + break; + case ItemParentChange: { + // Deliver ParentAboutToChange. + QEvent event(QEvent::ParentAboutToChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemParentHasChanged: { + // Deliver ParentChange. + QEvent event(QEvent::ParentChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemCursorHasChanged: { + // Deliver CursorChange. + QEvent event(QEvent::CursorChange); + QApplication::sendEvent(this, &event); + break; + } + case ItemToolTipHasChanged: { + // Deliver ToolTipChange. + QEvent event(QEvent::ToolTipChange); + QApplication::sendEvent(this, &event); + break; + } + default: + break; + } + return QGraphicsItem::itemChange(change, value); +} + +/*! + \internal + + This virtual function is used to notify changes to any property (both + dynamic properties, and registered with Q_PROPERTY) in the + widget. Depending on the property itself, the notification can be + delivered before or after the value has changed. + + \a propertyName is the name of the property (e.g., "size" or "font"), and + \a value is the (proposed) new value of the property. The function returns + the new value, which may be different from \a value if the notification + supports adjusting the property value. The base implementation simply + returns \a value for any \a propertyName. + + QGraphicsWidget delivers notifications for the following properties: + + \table \o propertyName \o Property + \row \o layoutDirection \o QGraphicsWidget::layoutDirection + \row \o size \o QGraphicsWidget::size + \row \o font \o QGraphicsWidget::font + \row \o palette \o QGraphicsWidget::palette + \endtable + + \sa itemChange() +*/ +QVariant QGraphicsWidget::propertyChange(const QString &propertyName, const QVariant &value) +{ + Q_UNUSED(propertyName); + return value; +} + +/*! + QGraphicsWidget's implementation of sceneEvent() simply passes \a event to + QGraphicsWidget::event(). You can handle all events for your widget in + event() or in any of the convenience functions; you should not have to + reimplement this function in a subclass of QGraphicsWidget. + + \sa QGraphicsItem::sceneEvent() +*/ +bool QGraphicsWidget::sceneEvent(QEvent *event) +{ + return QGraphicsItem::sceneEvent(event); +} + +/*! + This event handler, for \a event, receives events for the window frame if + this widget is a window. Its base implementation provides support for + default window frame interaction such as moving, resizing, etc. + + You can reimplement this handler in a subclass of QGraphicsWidget to + provide your own custom window frame interaction support. + + Returns true if \a event has been recognized and processed; otherwise, + returns false. + + \sa event() +*/ +bool QGraphicsWidget::windowFrameEvent(QEvent *event) +{ + Q_D(QGraphicsWidget); + switch (event->type()) { + case QEvent::GraphicsSceneMousePress: + d->windowFrameMousePressEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneMouseMove: + d->ensureWindowData(); + if (d->windowData->grabbedSection != Qt::NoSection) { + d->windowFrameMouseMoveEvent(static_cast(event)); + event->accept(); + } + break; + case QEvent::GraphicsSceneMouseRelease: + d->windowFrameMouseReleaseEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverMove: + d->windowFrameHoverMoveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneHoverLeave: + d->windowFrameHoverLeaveEvent(static_cast(event)); + break; + default: + break; + } + return event->isAccepted(); +} + +/*! + \since 4.4 + + Returns the window frame section at position \a pos, or + Qt::NoSection if there is no window frame section at this + position. + + This function is used in QGraphicsWidget's base implementation for window + frame interaction. + + You can reimplement this function if you want to customize how a window + can be interactively moved or resized. For instance, if you only want to + allow a window to be resized by the bottom right corner, you can + reimplement this function to return Qt::NoSection for all sections except + Qt::BottomRightSection. + + \sa windowFrameEvent(), paintWindowFrame(), windowFrameGeometry() +*/ +Qt::WindowFrameSection QGraphicsWidget::windowFrameSectionAt(const QPointF &pos) const +{ + Q_D(const QGraphicsWidget); + + const QRectF r = windowFrameRect(); + if (!r.contains(pos)) + return Qt::NoSection; + + const qreal left = r.left(); + const qreal top = r.top(); + const qreal right = r.right(); + const qreal bottom = r.bottom(); + const qreal x = pos.x(); + const qreal y = pos.y(); + + const qreal cornerMargin = 20; + //### Not sure of this one, it should be the same value for all edges. + const qreal windowFrameWidth = d->windowFrameMargins + ? d->windowFrameMargins[d->Left] : 0; + + Qt::WindowFrameSection s = Qt::NoSection; + if (x <= left + cornerMargin) { + if (y <= top + windowFrameWidth || (x <= left + windowFrameWidth && y <= top + cornerMargin)) { + s = Qt::TopLeftSection; + } else if (y >= bottom - windowFrameWidth || (x <= left + windowFrameWidth && y >= bottom - windowFrameWidth)) { + s = Qt::BottomLeftSection; + } else if (x <= left + windowFrameWidth) { + s = Qt::LeftSection; + } + } else if (x >= right - cornerMargin) { + if (y <= top + windowFrameWidth || (x >= right - windowFrameWidth && y <= top + cornerMargin)) { + s = Qt::TopRightSection; + } else if (y >= bottom - windowFrameWidth || (x >= right - windowFrameWidth && y >= bottom - windowFrameWidth)) { + s = Qt::BottomRightSection; + } else if (x >= right - windowFrameWidth) { + s = Qt::RightSection; + } + } else if (y <= top + windowFrameWidth) { + s = Qt::TopSection; + } else if (y >= bottom - windowFrameWidth) { + s = Qt::BottomSection; + } + if (s == Qt::NoSection) { + QRectF r1 = r; + r1.setHeight(d->windowFrameMargins + ? d->windowFrameMargins[d->Top] : 0); + if (r1.contains(pos)) + s = Qt::TitleBarArea; + } + return s; +} + +/*! + \reimp + + Handles the \a event. QGraphicsWidget handles the following + events: + + \table \o Event \o Usage + \row \o Polish + \o Delivered to the widget some time after it has been + shown. + \row \o GraphicsSceneMove + \o Delivered to the widget after its local position has + changed. + \row \o GraphicsSceneResize + \o Delivered to the widget after its size has changed. + \row \o Show + \o Delivered to the widget before it has been shown. + \row \o Hide + \o Delivered to the widget after it has been hidden. + \row \o PaletteChange + \o Delivered to the widget after its palette has changed. + \row \o FontChange + \o Delivered to the widget after its font has changed. + \row \o EnabledChange + \o Delivered to the widget after its enabled state has + changed. + \row \o StyleChange + \o Delivered to the widget after its style has changed. + \row \o LayoutDirectionChange + \o Delivered to the widget after its layout direction has + changed. + \row \o ContentsRectChange + \o Delivered to the widget after its contents margins/ + contents rect has changed. + \endtable +*/ +bool QGraphicsWidget::event(QEvent *event) +{ + Q_D(QGraphicsWidget); + // Forward the event to the layout first. + if (d->layout) + d->layout->widgetEvent(event); + + // Handle the event itself. + switch (event->type()) { + case QEvent::GraphicsSceneMove: + moveEvent(static_cast(event)); + break; + case QEvent::GraphicsSceneResize: + resizeEvent(static_cast(event)); + break; + case QEvent::Show: + showEvent(static_cast(event)); + break; + case QEvent::Hide: + hideEvent(static_cast(event)); + break; + case QEvent::Polish: + polishEvent(); + d->polished = true; + if (!d->font.isCopyOf(QApplication::font())) + d->updateFont(d->font); + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + update(); + break; + // Taken from QWidget::event + case QEvent::ActivationChange: + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::ParentChange: + case QEvent::ContentsRectChange: + case QEvent::LayoutDirectionChange: + changeEvent(event); + break; + case QEvent::Close: + closeEvent((QCloseEvent *)event); + break; + case QEvent::GrabMouse: + grabMouseEvent(event); + break; + case QEvent::UngrabMouse: + ungrabMouseEvent(event); + break; + case QEvent::GrabKeyboard: + grabKeyboardEvent(event); + break; + case QEvent::UngrabKeyboard: + ungrabKeyboardEvent(event); + break; + case QEvent::GraphicsSceneMousePress: + if (d->hasDecoration() && windowFrameEvent(event)) + return true; + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + d->ensureWindowData(); + if (d->hasDecoration() && d->windowData->grabbedSection != Qt::NoSection) + return windowFrameEvent(event); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: + if (d->hasDecoration()) { + windowFrameEvent(event); + // Filter out hover events if they were sent to us only because of the + // decoration (special case in QGraphicsScenePrivate::dispatchHoverEvent). + if (!acceptsHoverEvents()) + return true; + } + break; + default: + break; + } + return QObject::event(event); +} + +/*! + This event handler can be reimplemented to handle state changes. + + The state being changed in this event can be retrieved through \a event. + + Change events include: QEvent::ActivationChange, QEvent::EnabledChange, + QEvent::FontChange, QEvent::StyleChange, QEvent::PaletteChange, + QEvent::ParentChange, QEvent::LayoutDirectionChange, and + QEvent::ContentsRectChange. +*/ +void QGraphicsWidget::changeEvent(QEvent *event) +{ + Q_D(QGraphicsWidget); + switch (event->type()) { + case QEvent::StyleChange: + // ### Don't unset if the margins are explicitly set. + unsetWindowFrameMargins(); + if (d->layout) + d->layout->invalidate(); + case QEvent::FontChange: + update(); + updateGeometry(); + break; + case QEvent::PaletteChange: + update(); + break; + case QEvent::ParentChange: + d->resolveFont(d->inheritedFontResolveMask); + d->resolvePalette(d->inheritedPaletteResolveMask); + break; + default: + break; + } +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive widget close events. The default implementation accepts the + event. + + \sa close(), QCloseEvent +*/ +void QGraphicsWidget::closeEvent(QCloseEvent *event) +{ + event->accept(); +} + +/*! + \reimp +*/ +void QGraphicsWidget::focusInEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + if (focusPolicy() != Qt::NoFocus) + update(); +} + +/*! + Finds a new widget to give the keyboard focus to, as appropriate for Tab + and Shift+Tab, and returns true if it can find a new widget; returns false + otherwise. If \a next is true, this function searches forward; if \a next + is false, it searches backward. + + Sometimes, you will want to reimplement this function to provide special + focus handling for your widget and its subwidgets. For example, a web + browser might reimplement it to move its current active link forward or + backward, and call the base implementation only when it reaches the last + or first link on the page. + + Child widgets call focusNextPrevChild() on their parent widgets, but only + the window that contains the child widgets decides where to redirect + focus. By reimplementing this function for an object, you gain control of + focus traversal for all child widgets. + + \sa focusPolicy() +*/ +bool QGraphicsWidget::focusNextPrevChild(bool next) +{ + Q_D(QGraphicsWidget); + // Let the parent's focusNextPrevChild implementation decide what to do. + QGraphicsWidget *parent = 0; + if (!isWindow() && (parent = parentWidget())) + return parent->focusNextPrevChild(next); + if (!d->scene) + return false; + if (d->scene->focusNextPrevChild(next)) + return true; + if (isWindow()) { + setFocus(next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + if (hasFocus()) + return true; + } + return false; +} + +/*! + \reimp +*/ +void QGraphicsWidget::focusOutEvent(QFocusEvent *event) +{ + Q_UNUSED(event); + if (focusPolicy() != Qt::NoFocus) + update(); +} + +/*! + This event handler, for \l{QEvent::Hide}{Hide} events, is delivered after + the widget has been hidden, for example, setVisible(false) has been called + for the widget or one of its ancestors when the widget was previously + shown. + + You can reimplement this event handler to detect when your widget is + hidden. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa showEvent(), QWidget::hideEvent(), ItemVisibleChange +*/ +void QGraphicsWidget::hideEvent(QHideEvent *event) +{ + ///### focusNextPrevChild(true), don't lose focus when the focus widget + // is hidden. + Q_UNUSED(event); +} + +/*! + This event handler, for \l{QEvent::GraphicsSceneMove}{GraphicsSceneMove} + events, is delivered after the widget has moved (e.g., its local position + has changed). + + This event is only delivered when the item is moved locally. Calling + setTransform() or moving any of the item's ancestors does not affect the + item's local position. + + You can reimplement this event handler to detect when your widget has + moved. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa ItemPositionChange, ItemPositionHasChanged +*/ +void QGraphicsWidget::moveEvent(QGraphicsSceneMoveEvent *event) +{ + // ### Last position is always == current position + Q_UNUSED(event); +} + +/*! + This event is delivered to the item by the scene at some point after it + has been constructed, but before it is shown or otherwise accessed through + the scene. You can use this event handler to do last-minute initializations + of the widget which require the item to be fully constructed. + + The base implementation does nothing. +*/ +void QGraphicsWidget::polishEvent() +{ +} + +/*! + This event handler, for + \l{QEvent::GraphicsSceneResize}{GraphicsSceneResize} events, is + delivered after the widget has been resized (i.e., its local size has + changed). \a event contains both the old and the new size. + + This event is only delivered when the widget is resized locally; calling + setTransform() on the widget or any of its ancestors or view, does not + affect the widget's local size. + + You can reimplement this event handler to detect when your widget has been + resized. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa geometry(), setGeometry() +*/ +void QGraphicsWidget::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \l{QEvent::Show}{Show} events, is delivered before + the widget has been shown, for example, setVisible(true) has been called + for the widget or one of its ancestors when the widget was previously + hidden. + + You can reimplement this event handler to detect when your widget is + shown. Calling QEvent::accept() or QEvent::ignore() on \a event has no + effect. + + \sa hideEvent(), QWidget::showEvent(), ItemVisibleChange +*/ +void QGraphicsWidget::showEvent(QShowEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsWidget::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + \reimp +*/ +void QGraphicsWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::GrabMouse events. + + \sa grabMouse(), grabKeyboard() +*/ +void QGraphicsWidget::grabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::UngrabMouse events. + + \sa ungrabMouse(), ungrabKeyboard() +*/ +void QGraphicsWidget::ungrabMouseEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::GrabKeyboard events. + + \sa grabKeyboard(), grabMouse() +*/ +void QGraphicsWidget::grabKeyboardEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + This event handler, for \a event, can be reimplemented in a subclass to + receive notifications for Qt::UngrabKeyboard events. + + \sa ungrabKeyboard(), ungrabMouse() +*/ +void QGraphicsWidget::ungrabKeyboardEvent(QEvent *event) +{ + Q_UNUSED(event); +} + +/*! + Returns the widgets window type. + + \sa windowFlags(), isWindow(), isPanel() +*/ +Qt::WindowType QGraphicsWidget::windowType() const +{ + return Qt::WindowType(int(windowFlags()) & Qt::WindowType_Mask); +} + +/*! + \property QGraphicsWidget::windowFlags + \brief the widget's window flags + + Window flags are a combination of a window type (e.g., Qt::Dialog) and + several flags giving hints on the behavior of the window. The behavior + is platform-dependent. + + By default, this property contains no window flags. + + Windows are panels. If you set the Qt::Window flag, the ItemIsPanel flag + will be set automatically. If you clear the Qt::Window flag, the + ItemIsPanel flag is also cleared. Note that the ItemIsPanel flag can be + set independently of Qt::Window. + + \sa isWindow(), isPanel() +*/ +Qt::WindowFlags QGraphicsWidget::windowFlags() const +{ + Q_D(const QGraphicsWidget); + return d->windowFlags; +} +void QGraphicsWidget::setWindowFlags(Qt::WindowFlags wFlags) +{ + Q_D(QGraphicsWidget); + if (d->windowFlags == wFlags) + return; + bool wasPopup = (d->windowFlags & Qt::WindowType_Mask) == Qt::Popup; + + d->adjustWindowFlags(&wFlags); + d->windowFlags = wFlags; + if (!d->setWindowFrameMargins) + unsetWindowFrameMargins(); + + setFlag(ItemIsPanel, d->windowFlags & Qt::Window); + + bool isPopup = (d->windowFlags & Qt::WindowType_Mask) == Qt::Popup; + if (d->scene && isVisible() && wasPopup != isPopup) { + // Popup state changed; update implicit mouse grab. + if (!isPopup) + d->scene->d_func()->removePopup(this); + else + d->scene->d_func()->addPopup(this); + } + + if (d->scene && d->scene->d_func()->allItemsIgnoreHoverEvents && d->hasDecoration()) { + d->scene->d_func()->allItemsIgnoreHoverEvents = false; + d->scene->d_func()->enableMouseTrackingOnViews(); + } +} + +/*! + Returns true if this widget's window is in the active window, or if the + widget does not have a window but is in an active scene (i.e., a scene + that currently has focus). + + The active window is the window that either contains a child widget that + currently has input focus, or that itself has input focus. + + \sa QGraphicsScene::activeWindow(), QGraphicsScene::setActiveWindow(), isActive() +*/ +bool QGraphicsWidget::isActiveWindow() const +{ + return isActive(); +} + +/*! + \property QGraphicsWidget::windowTitle + \brief This property holds the window title (caption). + + This property is only used for windows. + + By default, if no title has been set, this property contains an + empty string. +*/ +void QGraphicsWidget::setWindowTitle(const QString &title) +{ + Q_D(QGraphicsWidget); + d->ensureWindowData(); + d->windowData->windowTitle = title; +} +QString QGraphicsWidget::windowTitle() const +{ + Q_D(const QGraphicsWidget); + return d->windowData ? d->windowData->windowTitle : QString(); +} + +/*! + \property QGraphicsWidget::focusPolicy + \brief the way the widget accepts keyboard focus + + The focus policy is Qt::TabFocus if the widget accepts keyboard focus by + tabbing, Qt::ClickFocus if the widget accepts focus by clicking, + Qt::StrongFocus if it accepts both, and Qt::NoFocus (the default) if it + does not accept focus at all. + + You must enable keyboard focus for a widget if it processes keyboard + events. This is normally done from the widget's constructor. For instance, + the QLineEdit constructor calls setFocusPolicy(Qt::StrongFocus). + + If you enable a focus policy (i.e., not Qt::NoFocus), QGraphicsWidget will + automatically enable the ItemIsFocusable flag. Setting Qt::NoFocus on a + widget will clear the ItemIsFocusable flag. If the widget currently has + keyboard focus, the widget will automatically lose focus. + + \sa focusInEvent(), focusOutEvent(), keyPressEvent(), keyReleaseEvent(), enabled +*/ +Qt::FocusPolicy QGraphicsWidget::focusPolicy() const +{ + Q_D(const QGraphicsWidget); + return d->focusPolicy; +} +void QGraphicsWidget::setFocusPolicy(Qt::FocusPolicy policy) +{ + Q_D(QGraphicsWidget); + if (d->focusPolicy == policy) + return; + d->focusPolicy = policy; + if (hasFocus() && policy == Qt::NoFocus) + clearFocus(); + setFlag(ItemIsFocusable, policy != Qt::NoFocus); +} + +/*! + If this widget, a child or descendant of this widget currently has input + focus, this function will return a pointer to that widget. If + no descendant widget has input focus, 0 is returned. + + \sa QGraphicsItem::focusItem(), QWidget::focusWidget() +*/ +QGraphicsWidget *QGraphicsWidget::focusWidget() const +{ + Q_D(const QGraphicsWidget); + if (d->subFocusItem && d->subFocusItem->d_ptr->isWidget) + return static_cast(d->subFocusItem); + return 0; +} + +#ifndef QT_NO_SHORTCUT +/*! + \since 4.5 + + Adds a shortcut to Qt's shortcut system that watches for the given key \a + sequence in the given \a context. If the \a context is + Qt::ApplicationShortcut, the shortcut applies to the application as a + whole. Otherwise, it is either local to this widget, Qt::WidgetShortcut, + or to the window itself, Qt::WindowShortcut. For widgets that are not part + of a window (i.e., top-level widgets and their children), + Qt::WindowShortcut shortcuts apply to the scene. + + If the same key \a sequence has been grabbed by several widgets, + when the key \a sequence occurs a QEvent::Shortcut event is sent + to all the widgets to which it applies in a non-deterministic + order, but with the ``ambiguous'' flag set to true. + + \warning You should not normally need to use this function; + instead create \l{QAction}s with the shortcut key sequences you + require (if you also want equivalent menu options and toolbar + buttons), or create \l{QShortcut}s if you just need key sequences. + Both QAction and QShortcut handle all the event filtering for you, + and provide signals which are triggered when the user triggers the + key sequence, so are much easier to use than this low-level + function. + + \sa releaseShortcut() setShortcutEnabled() QWidget::grabShortcut() +*/ +int QGraphicsWidget::grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context) +{ + Q_ASSERT(qApp); + if (sequence.isEmpty()) + return 0; + // ### setAttribute(Qt::WA_GrabbedShortcut); + return qApp->d_func()->shortcutMap.addShortcut(this, sequence, context); +} + +/*! + \since 4.5 + + Removes the shortcut with the given \a id from Qt's shortcut + system. The widget will no longer receive QEvent::Shortcut events + for the shortcut's key sequence (unless it has other shortcuts + with the same key sequence). + + \warning You should not normally need to use this function since + Qt's shortcut system removes shortcuts automatically when their + parent widget is destroyed. It is best to use QAction or + QShortcut to handle shortcuts, since they are easier to use than + this low-level function. Note also that this is an expensive + operation. + + \sa grabShortcut() setShortcutEnabled() , QWidget::releaseShortcut() +*/ +void QGraphicsWidget::releaseShortcut(int id) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.removeShortcut(id, this, 0); +} + +/*! + \since 4.5 + + If \a enabled is true, the shortcut with the given \a id is + enabled; otherwise the shortcut is disabled. + + \warning You should not normally need to use this function since + Qt's shortcut system enables/disables shortcuts automatically as + widgets become hidden/visible and gain or lose focus. It is best + to use QAction or QShortcut to handle shortcuts, since they are + easier to use than this low-level function. + + \sa grabShortcut() releaseShortcut(), QWidget::setShortcutEnabled() +*/ +void QGraphicsWidget::setShortcutEnabled(int id, bool enabled) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutEnabled(enabled, id, this, 0); +} + +/*! + \since 4.5 + + If \a enabled is true, auto repeat of the shortcut with the + given \a id is enabled; otherwise it is disabled. + + \sa grabShortcut() releaseShortcut() QWidget::setShortcutAutoRepeat() +*/ +void QGraphicsWidget::setShortcutAutoRepeat(int id, bool enabled) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutAutoRepeat(enabled, id, this, 0); +} +#endif + +#ifndef QT_NO_ACTION +/*! + \since 4.5 + + Appends the action \a action to this widget's list of actions. + + All QGraphicsWidgets have a list of \l{QAction}s, however they can be + represented graphically in many different ways. The default use of the + QAction list (as returned by actions()) is to create a context QMenu. + + A QGraphicsWidget should only have one of each action and adding an action + it already has will not cause the same action to be in the widget twice. + + \sa removeAction(), insertAction(), actions(), QWidget::addAction() +*/ +void QGraphicsWidget::addAction(QAction *action) +{ + insertAction(0, action); +} + +/*! + \since 4.5 + + Appends the actions \a actions to this widget's list of actions. + + \sa removeAction(), QMenu, addAction(), QWidget::addActions() +*/ +void QGraphicsWidget::addActions(QList actions) +{ + for (int i = 0; i < actions.count(); ++i) + insertAction(0, actions.at(i)); +} + +/*! + \since 4.5 + + Inserts the action \a action to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QGraphicsWidget should only have one of each action. + + \sa removeAction(), addAction(), QMenu, actions(), + QWidget::insertActions() +*/ +void QGraphicsWidget::insertAction(QAction *before, QAction *action) +{ + if (!action) { + qWarning("QWidget::insertAction: Attempt to insert null action"); + return; + } + + Q_D(QGraphicsWidget); + int index = d->actions.indexOf(action); + if (index != -1) + d->actions.removeAt(index); + + int pos = d->actions.indexOf(before); + if (pos < 0) { + before = 0; + pos = d->actions.size(); + } + d->actions.insert(pos, action); + + if (index == -1) { + QActionPrivate *apriv = action->d_func(); + apriv->graphicsWidgets.append(this); + } + + QActionEvent e(QEvent::ActionAdded, action, before); + QApplication::sendEvent(this, &e); +} + +/*! + \since 4.5 + + Inserts the actions \a actions to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QGraphicsWidget can have at most one of each action. + + \sa removeAction(), QMenu, insertAction(), QWidget::insertActions() +*/ +void QGraphicsWidget::insertActions(QAction *before, QList actions) +{ + for (int i = 0; i < actions.count(); ++i) + insertAction(before, actions.at(i)); +} + +/*! + \since 4.5 + + Removes the action \a action from this widget's list of actions. + + \sa insertAction(), actions(), insertAction(), QWidget::removeAction() +*/ +void QGraphicsWidget::removeAction(QAction *action) +{ + if (!action) + return; + + Q_D(QGraphicsWidget); + + QActionPrivate *apriv = action->d_func(); + apriv->graphicsWidgets.removeAll(this); + + if (d->actions.removeAll(action)) { + QActionEvent e(QEvent::ActionRemoved, action); + QApplication::sendEvent(this, &e); + } +} + +/*! + \since 4.5 + + Returns the (possibly empty) list of this widget's actions. + + \sa insertAction(), removeAction(), QWidget::actions(), + QAction::associatedWidgets(), QAction::associatedGraphicsWidgets() +*/ +QList QGraphicsWidget::actions() const +{ + Q_D(const QGraphicsWidget); + return d->actions; +} +#endif + +/*! + Moves the \a second widget around the ring of focus widgets so that + keyboard focus moves from the \a first widget to the \a second widget when + the Tab key is pressed. + + Note that since the tab order of the \a second widget is changed, you + should order a chain like this: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 1 + + \e not like this: + + \snippet doc/src/snippets/code/src_gui_graphicsview_qgraphicswidget.cpp 2 + + If \a first is 0, this indicates that \a second should be the first widget + to receive input focus should the scene gain Tab focus (i.e., the user + hits Tab so that focus passes into the scene). If \a second is 0, this + indicates that \a first should be the first widget to gain focus if the + scene gained BackTab focus. + + By default, tab order is defined implicitly using widget creation order. + + \sa focusPolicy, {Keyboard Focus} +*/ +void QGraphicsWidget::setTabOrder(QGraphicsWidget *first, QGraphicsWidget *second) +{ + if (!first && !second) { + qWarning("QGraphicsWidget::setTabOrder(0, 0) is undefined"); + return; + } + if ((first && second) && first->scene() != second->scene()) { + qWarning("QGraphicsWidget::setTabOrder: scenes %p and %p are different", + first->scene(), second->scene()); + return; + } + QGraphicsScene *scene = first ? first->scene() : second->scene(); + if (!scene && (!first || !second)) { + qWarning("QGraphicsWidget::setTabOrder: assigning tab order from/to the" + " scene requires the item to be in a scene."); + return; + } + + // If either first or second are 0, the scene's tabFocusFirst is updated + // to point to the first item in the scene's focus chain. Then first or + // second are set to point to tabFocusFirst. + QGraphicsScenePrivate *sceneD = scene->d_func(); + if (!first) { + sceneD->tabFocusFirst = second; + return; + } + if (!second) { + sceneD->tabFocusFirst = first->d_func()->focusNext; + return; + } + + // Both first and second are != 0. + QGraphicsWidget *firstFocusNext = first->d_func()->focusNext; + if (firstFocusNext == second) { + // Nothing to do. + return; + } + + // Update the focus chain. + QGraphicsWidget *secondFocusPrev = second->d_func()->focusPrev; + QGraphicsWidget *secondFocusNext = second->d_func()->focusNext; + firstFocusNext->d_func()->focusPrev = second; + first->d_func()->focusNext = second; + second->d_func()->focusNext = firstFocusNext; + second->d_func()->focusPrev = first; + secondFocusPrev->d_func()->focusNext = secondFocusNext; + secondFocusNext->d_func()->focusPrev = secondFocusPrev; + + Q_ASSERT(first->d_func()->focusNext->d_func()->focusPrev == first); + Q_ASSERT(first->d_func()->focusPrev->d_func()->focusNext == first); + + Q_ASSERT(second->d_func()->focusNext->d_func()->focusPrev == second); + Q_ASSERT(second->d_func()->focusPrev->d_func()->focusNext == second); + +} + +/*! + If \a on is true, this function enables \a attribute; otherwise + \a attribute is disabled. + + See the class documentation for QGraphicsWidget for a complete list of + which attributes are supported, and what they are for. + + \sa testAttribute(), QWidget::setAttribute() +*/ +void QGraphicsWidget::setAttribute(Qt::WidgetAttribute attribute, bool on) +{ + Q_D(QGraphicsWidget); + // ### most flags require some immediate action + // ### we might want to qWarn use of unsupported attributes + // ### we might want to not use Qt::WidgetAttribute, but roll our own instead + d->setAttribute(attribute, on); +} + +/*! + Returns true if \a attribute is enabled for this widget; otherwise, + returns false. + + \sa setAttribute() +*/ +bool QGraphicsWidget::testAttribute(Qt::WidgetAttribute attribute) const +{ + Q_D(const QGraphicsWidget); + return d->testAttribute(attribute); +} + +/*! + \reimp +*/ +int QGraphicsWidget::type() const +{ + return Type; +} + +/*! + \reimp +*/ +void QGraphicsWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(widget); +} + +/*! + This virtual function is called by QGraphicsScene to draw the window frame + for windows using \a painter, \a option, and \a widget, in local + coordinates. The base implementation uses the current style to render the + frame and title bar. + + You can reimplement this function in a subclass of QGraphicsWidget to + provide custom rendering of the widget's window frame. + + \sa QGraphicsItem::paint() +*/ +void QGraphicsWidget::paintWindowFrame(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) +{ + const bool fillBackground = !testAttribute(Qt::WA_OpaquePaintEvent) + && !testAttribute(Qt::WA_NoSystemBackground); + QGraphicsProxyWidget *proxy = qobject_cast(this); + const bool embeddedWidgetFillsOwnBackground = proxy && proxy->widget(); + + if (rect().contains(option->exposedRect)) { + if (fillBackground && !embeddedWidgetFillsOwnBackground) + painter->fillRect(option->exposedRect, palette().window()); + return; + } + + Q_D(QGraphicsWidget); + + QRect windowFrameRect = QRect(QPoint(), windowFrameGeometry().size().toSize()); + QStyleOptionTitleBar bar; + bar.QStyleOption::operator=(*option); + d->initStyleOptionTitleBar(&bar); // this clear flags in bar.state + d->ensureWindowData(); + if (d->windowData->buttonMouseOver) + bar.state |= QStyle::State_MouseOver; + else + bar.state &= ~QStyle::State_MouseOver; + if (d->windowData->buttonSunken) + bar.state |= QStyle::State_Sunken; + else + bar.state &= ~QStyle::State_Sunken; + + bar.rect = windowFrameRect; + + // translate painter to make the style happy + const QPointF styleOrigin = this->windowFrameRect().topLeft(); + painter->translate(styleOrigin); + +#ifdef Q_WS_MAC + const QSize pixmapSize = windowFrameRect.size(); + if (pixmapSize.width() <= 0 || pixmapSize.height() <= 0) + return; + QPainter *realPainter = painter; + QPixmap pm(pixmapSize); + painter = new QPainter(&pm); +#endif + + // Fill background + QStyleHintReturnMask mask; + bool setMask = style()->styleHint(QStyle::SH_WindowFrame_Mask, &bar, widget, &mask) && !mask.region.isEmpty(); + bool hasBorder = !style()->styleHint(QStyle::SH_TitleBar_NoBorder, &bar, widget); + int frameWidth = style()->pixelMetric(QStyle::PM_MDIFrameWidth, &bar, widget); + if (setMask) { + painter->save(); + painter->setClipRegion(mask.region, Qt::IntersectClip); + } + if (fillBackground) { + if (embeddedWidgetFillsOwnBackground) { + // Don't fill the background twice. + QPainterPath windowFrameBackground; + windowFrameBackground.addRect(windowFrameRect); + // Adjust with 0.5 to avoid border artifacts between + // widget background and frame background. + windowFrameBackground.addRect(rect().translated(-styleOrigin).adjusted(0.5, 0.5, -0.5, -0.5)); + painter->fillPath(windowFrameBackground, palette().window()); + } else { + painter->fillRect(windowFrameRect, palette().window()); + } + } + painter->setRenderHint(QPainter::NonCosmeticDefaultPen); + + // Draw title + int height = (int)d->titleBarHeight(bar); + bar.rect.setHeight(height); + if (hasBorder) // Frame is painted by PE_FrameWindow + bar.rect.adjust(frameWidth, frameWidth, -frameWidth, 0); + + painter->save(); + painter->setFont(QApplication::font("QWorkspaceTitleBar")); + style()->drawComplexControl(QStyle::CC_TitleBar, &bar, painter, widget); + painter->restore(); + if (setMask) + painter->restore(); + // Draw window frame + QStyleOptionFrame frameOptions; + frameOptions.QStyleOption::operator=(*option); + initStyleOption(&frameOptions); + if (!hasBorder) + painter->setClipRect(windowFrameRect.adjusted(0, +height, 0, 0), Qt::IntersectClip); + if (hasFocus()) { + frameOptions.state |= QStyle::State_HasFocus; + } else { + frameOptions.state &= ~QStyle::State_HasFocus; + } + bool isActive = isActiveWindow(); + if (isActive) { + frameOptions.state |= QStyle::State_Active; + } else { + frameOptions.state &= ~QStyle::State_Active; + } + + frameOptions.palette.setCurrentColorGroup(isActive ? QPalette::Active : QPalette::Normal); + frameOptions.rect = windowFrameRect; + frameOptions.lineWidth = style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, 0, widget); + frameOptions.midLineWidth = 1; + style()->drawPrimitive(QStyle::PE_FrameWindow, &frameOptions, painter, widget); + +#ifdef Q_WS_MAC + realPainter->drawPixmap(QPoint(), pm); + delete painter; +#endif +} + +/*! + \reimp +*/ +QRectF QGraphicsWidget::boundingRect() const +{ + return windowFrameRect(); +} + +/*! + \reimp +*/ +QPainterPath QGraphicsWidget::shape() const +{ + QPainterPath path; + path.addRect(rect()); + return path; +} + +/*! + Call this function to close the widget. + + Returns true if the widget was closed; otherwise returns false. + This slot will first send a QCloseEvent to the widget, which may or may + not accept the event. If the event was ignored, nothing happens. If the + event was accepted, it will hide() the widget. + + If the widget has the Qt::WA_DeleteOnClose attribute set it will be + deleted. +*/ +bool QGraphicsWidget::close() +{ + QCloseEvent closeEvent; + QApplication::sendEvent(this, &closeEvent); + if (!closeEvent.isAccepted()) { + return false; + } + // hide + if (isVisible()) { + hide(); + } + if (testAttribute(Qt::WA_DeleteOnClose)) { + deleteLater(); + } + return true; +} + +#ifdef Q_NO_USING_KEYWORD +/*! + \fn const QObjectList &QGraphicsWidget::children() const + \internal + + This function returns the same value as QObject::children(). It's + provided to differentiate between the obsolete member + QGraphicsItem::children() and QObject::children(). QGraphicsItem now + provides childItems() instead. +*/ +#endif + +#if 0 +void QGraphicsWidget::dumpFocusChain() +{ + qDebug() << "=========== Dumping focus chain =============="; + int i = 0; + QGraphicsWidget *next = this; + QSet visited; + do { + if (!next) { + qWarning("Found a focus chain that is not circular, (next == 0)"); + break; + } + qDebug() << i++ << QString::number(uint(next), 16) << next->className() << next->data(0) << QString::fromAscii("focusItem:%1").arg(next->hasFocus() ? '1' : '0') << QLatin1String("next:") << next->d_func()->focusNext->data(0) << QLatin1String("prev:") << next->d_func()->focusPrev->data(0); + if (visited.contains(next)) { + qWarning("Already visited this node. However, I expected to dump until I found myself."); + break; + } + visited << next; + next = next->d_func()->focusNext; + } while (next != this); +} +#endif + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicswidget.h b/src/gui/graphicsview/qgraphicswidget.h new file mode 100644 index 0000000000..6cbe579608 --- /dev/null +++ b/src/gui/graphicsview/qgraphicswidget.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSWIDGET_H +#define QGRAPHICSWIDGET_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFont; +class QFontMetrics; +class QGraphicsLayout; +class QGraphicsSceneMoveEvent; +class QGraphicsWidgetPrivate; +class QGraphicsSceneResizeEvent; +class QStyle; +class QStyleOption; + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsWidgetPrivate; + +class Q_GUI_EXPORT QGraphicsWidget : public QGraphicsObject, public QGraphicsLayoutItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem QGraphicsLayoutItem) + Q_PROPERTY(QPalette palette READ palette WRITE setPalette) + Q_PROPERTY(QFont font READ font WRITE setFont) + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection RESET unsetLayoutDirection) + Q_PROPERTY(QSizeF size READ size WRITE resize NOTIFY geometryChanged) + Q_PROPERTY(QSizeF minimumSize READ minimumSize WRITE setMinimumSize) + Q_PROPERTY(QSizeF preferredSize READ preferredSize WRITE setPreferredSize) + Q_PROPERTY(QSizeF maximumSize READ maximumSize WRITE setMaximumSize) + Q_PROPERTY(QSizePolicy sizePolicy READ sizePolicy WRITE setSizePolicy) + Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy) + Q_PROPERTY(Qt::WindowFlags windowFlags READ windowFlags WRITE setWindowFlags) + Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle) + Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry NOTIFY geometryChanged) + Q_PROPERTY(bool autoFillBackground READ autoFillBackground WRITE setAutoFillBackground) + Q_PROPERTY(QGraphicsLayout* layout READ layout WRITE setLayout NOTIFY layoutChanged) +public: + QGraphicsWidget(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0); + ~QGraphicsWidget(); + QGraphicsLayout *layout() const; + void setLayout(QGraphicsLayout *layout); + void adjustSize(); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection direction); + void unsetLayoutDirection(); + + QStyle *style() const; + void setStyle(QStyle *style); + + QFont font() const; + void setFont(const QFont &font); + + QPalette palette() const; + void setPalette(const QPalette &palette); + + bool autoFillBackground() const; + void setAutoFillBackground(bool enabled); + + void resize(const QSizeF &size); + inline void resize(qreal w, qreal h) { resize(QSizeF(w, h)); } + QSizeF size() const; + + void setGeometry(const QRectF &rect); + inline void setGeometry(qreal x, qreal y, qreal w, qreal h); + inline QRectF rect() const { return QRectF(QPointF(), size()); } + + void setContentsMargins(qreal left, qreal top, qreal right, qreal bottom); + void getContentsMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + + void setWindowFrameMargins(qreal left, qreal top, qreal right, qreal bottom); + void getWindowFrameMargins(qreal *left, qreal *top, qreal *right, qreal *bottom) const; + void unsetWindowFrameMargins(); + QRectF windowFrameGeometry() const; + QRectF windowFrameRect() const; + + // Window handling + Qt::WindowFlags windowFlags() const; + Qt::WindowType windowType() const; + void setWindowFlags(Qt::WindowFlags wFlags); + bool isActiveWindow() const; + void setWindowTitle(const QString &title); + QString windowTitle() const; + + // Focus handling + Qt::FocusPolicy focusPolicy() const; + void setFocusPolicy(Qt::FocusPolicy policy); + static void setTabOrder(QGraphicsWidget *first, QGraphicsWidget *second); + QGraphicsWidget *focusWidget() const; + +#ifndef QT_NO_SHORTCUT + int grabShortcut(const QKeySequence &sequence, Qt::ShortcutContext context = Qt::WindowShortcut); + void releaseShortcut(int id); + void setShortcutEnabled(int id, bool enabled = true); + void setShortcutAutoRepeat(int id, bool enabled = true); +#endif + +#ifndef QT_NO_ACTION + //actions + void addAction(QAction *action); + void addActions(QList actions); + void insertAction(QAction *before, QAction *action); + void insertActions(QAction *before, QList actions); + void removeAction(QAction *action); + QList actions() const; +#endif + + void setAttribute(Qt::WidgetAttribute attribute, bool on = true); + bool testAttribute(Qt::WidgetAttribute attribute) const; + + enum { + Type = 11 + }; + int type() const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual void paintWindowFrame(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + QRectF boundingRect() const; + QPainterPath shape() const; + +#if 0 + void dumpFocusChain(); +#endif + + // ### Qt 5: Disambiguate +#ifdef Q_NO_USING_KEYWORD + const QObjectList &children() const { return QObject::children(); } +#else + using QObject::children; +#endif + +Q_SIGNALS: + void geometryChanged(); + void layoutChanged(); + +public Q_SLOTS: + bool close(); + +protected: + virtual void initStyleOption(QStyleOption *option) const; + + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void updateGeometry(); + + // Notification + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + virtual QVariant propertyChange(const QString &propertyName, const QVariant &value); + + // Scene events + bool sceneEvent(QEvent *event); + virtual bool windowFrameEvent(QEvent *e); + virtual Qt::WindowFrameSection windowFrameSectionAt(const QPointF& pos) const; + + // Base event handlers + bool event(QEvent *event); + //virtual void actionEvent(QActionEvent *event); + virtual void changeEvent(QEvent *event); + virtual void closeEvent(QCloseEvent *event); + //void create(WId window = 0, bool initializeWindow = true, bool destroyOldWindow = true); + //void destroy(bool destroyWindow = true, bool destroySubWindows = true); + void focusInEvent(QFocusEvent *event); + virtual bool focusNextPrevChild(bool next); + void focusOutEvent(QFocusEvent *event); + virtual void hideEvent(QHideEvent *event); + //virtual bool macEvent(EventHandlerCallRef caller, EventRef event); + //virtual int metric(PaintDeviceMetric m ) const; + virtual void moveEvent(QGraphicsSceneMoveEvent *event); + virtual void polishEvent(); + //virtual bool qwsEvent(QWSEvent *event); + //void resetInputContext (); + virtual void resizeEvent(QGraphicsSceneResizeEvent *event); + virtual void showEvent(QShowEvent *event); + //virtual void tabletEvent(QTabletEvent *event); + //virtual bool winEvent(MSG *message, long *result); + //virtual bool x11Event(XEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual void grabMouseEvent(QEvent *event); + virtual void ungrabMouseEvent(QEvent *event); + virtual void grabKeyboardEvent(QEvent *event); + virtual void ungrabKeyboardEvent(QEvent *event); + QGraphicsWidget(QGraphicsWidgetPrivate &, QGraphicsItem *parent, QGraphicsScene *, Qt::WindowFlags wFlags = 0); + +private: + Q_DISABLE_COPY(QGraphicsWidget) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QGraphicsWidget) + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QGraphicsView; + friend class QGraphicsItem; + friend class QGraphicsItemPrivate; + friend class QGraphicsLayout; + friend class QWidget; + friend class QApplication; +}; + +inline void QGraphicsWidget::setGeometry(qreal ax, qreal ay, qreal aw, qreal ah) +{ setGeometry(QRectF(ax, ay, aw, ah)); } + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif + diff --git a/src/gui/graphicsview/qgraphicswidget_p.cpp b/src/gui/graphicsview/qgraphicswidget_p.cpp new file mode 100644 index 0000000000..45800551cb --- /dev/null +++ b/src/gui/graphicsview/qgraphicswidget_p.cpp @@ -0,0 +1,910 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include +#include +#include "qgraphicswidget_p.h" +#include "qgraphicslayoutitem_p.h" +#include "qgraphicslayout.h" +#include "qgraphicsscene_p.h" +#include +#include +#include +#include +#include +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +# include +#endif + +QT_BEGIN_NAMESPACE + +void QGraphicsWidgetPrivate::init(QGraphicsItem *parentItem, Qt::WindowFlags wFlags) +{ + Q_Q(QGraphicsWidget); + + attributes = 0; + isWidget = 1; // QGraphicsItem::isWidget() returns true. + focusNext = focusPrev = q; + focusPolicy = Qt::NoFocus; + + adjustWindowFlags(&wFlags); + windowFlags = wFlags; + + if (parentItem) + setParentItemHelper(parentItem, 0, 0); + + q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::DefaultType)); + q->setGraphicsItem(q); + + resolveLayoutDirection(); + q->unsetWindowFrameMargins(); + flags |= QGraphicsItem::ItemUsesExtendedStyleOption; + flags |= QGraphicsItem::ItemSendsGeometryChanges; + if (windowFlags & Qt::Window) + flags |= QGraphicsItem::ItemIsPanel; +} + +qreal QGraphicsWidgetPrivate::titleBarHeight(const QStyleOptionTitleBar &options) const +{ + Q_Q(const QGraphicsWidget); + int height = q->style()->pixelMetric(QStyle::PM_TitleBarHeight, &options); +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + if (qobject_cast(q->style())) { + height -=4; + } +#endif + return (qreal)height; +} + +/*! + \internal +*/ +QGraphicsWidgetPrivate::~QGraphicsWidgetPrivate() +{ + // Remove any lazily allocated data + delete[] margins; + delete[] windowFrameMargins; + delete windowData; +} + +/*! + \internal + + Ensures that margins is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureMargins() const +{ + if (!margins) { + margins = new qreal[4]; + for (int i = 0; i < 4; ++i) + margins[i] = 0; + } +} + +/*! + \internal + + Ensures that windowFrameMargins is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureWindowFrameMargins() const +{ + if (!windowFrameMargins) { + windowFrameMargins = new qreal[4]; + for (int i = 0; i < 4; ++i) + windowFrameMargins[i] = 0; + } +} + +/*! + \internal + + Ensures that windowData is allocated. + This function must be called before any dereferencing. +*/ +void QGraphicsWidgetPrivate::ensureWindowData() +{ + if (!windowData) + windowData = new WindowData; +} + +void QGraphicsWidgetPrivate::setPalette_helper(const QPalette &palette) +{ + if (this->palette == palette && this->palette.resolve() == palette.resolve()) + return; + updatePalette(palette); +} + +void QGraphicsWidgetPrivate::resolvePalette(uint inheritedMask) +{ + inheritedPaletteResolveMask = inheritedMask; + QPalette naturalPalette = naturalWidgetPalette(); + QPalette resolvedPalette = palette.resolve(naturalPalette); + updatePalette(resolvedPalette); +} + +void QGraphicsWidgetPrivate::updatePalette(const QPalette &palette) +{ + Q_Q(QGraphicsWidget); + // Update local palette setting. + this->palette = palette; + + // Calculate new mask. + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) + inheritedPaletteResolveMask = 0; + int mask = palette.resolve() | inheritedPaletteResolveMask; + + // Propagate to children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + w->d_func()->resolvePalette(mask); + } else { + item->d_ptr->resolvePalette(mask); + } + } + + // Notify change. + QEvent event(QEvent::PaletteChange); + QApplication::sendEvent(q, &event); +} + +void QGraphicsWidgetPrivate::setLayoutDirection_helper(Qt::LayoutDirection direction) +{ + Q_Q(QGraphicsWidget); + if ((direction == Qt::RightToLeft) == (testAttribute(Qt::WA_RightToLeft))) + return; + q->setAttribute(Qt::WA_RightToLeft, (direction == Qt::RightToLeft)); + + // Propagate this change to all children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *widget = static_cast(item); + if (widget->parentWidget() && !widget->testAttribute(Qt::WA_SetLayoutDirection)) + widget->d_func()->setLayoutDirection_helper(direction); + } + } + + // Send the notification event to this widget item. + QEvent e(QEvent::LayoutDirectionChange); + QApplication::sendEvent(q, &e); +} + +void QGraphicsWidgetPrivate::resolveLayoutDirection() +{ + Q_Q(QGraphicsWidget); + if (q->testAttribute(Qt::WA_SetLayoutDirection)) { + return; + } + if (QGraphicsWidget *parentWidget = q->parentWidget()) { + setLayoutDirection_helper(parentWidget->layoutDirection()); + } else if (scene) { + // ### shouldn't the scene have a layoutdirection really? how does + // ### QGraphicsWidget get changes from QApplication::layoutDirection? + setLayoutDirection_helper(QApplication::layoutDirection()); + } else { + setLayoutDirection_helper(QApplication::layoutDirection()); + } +} + +QPalette QGraphicsWidgetPrivate::naturalWidgetPalette() const +{ + Q_Q(const QGraphicsWidget); + QPalette palette; + if (QGraphicsWidget *parent = q->parentWidget()) { + palette = parent->palette(); + } else if (scene) { + palette = scene->palette(); + } + palette.resolve(0); + return palette; +} + +void QGraphicsWidgetPrivate::setFont_helper(const QFont &font) +{ + if (this->font == font && this->font.resolve() == font.resolve()) + return; + updateFont(font); +} + +void QGraphicsWidgetPrivate::resolveFont(uint inheritedMask) +{ + Q_Q(QGraphicsWidget); + inheritedFontResolveMask = inheritedMask; + if (QGraphicsWidget *p = q->parentWidget()) + inheritedFontResolveMask |= p->d_func()->inheritedFontResolveMask; + QFont naturalFont = naturalWidgetFont(); + QFont resolvedFont = font.resolve(naturalFont); + updateFont(resolvedFont); +} + +void QGraphicsWidgetPrivate::updateFont(const QFont &font) +{ + Q_Q(QGraphicsWidget); + // Update the local font setting. + this->font = font; + + // Calculate new mask. + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) + inheritedFontResolveMask = 0; + int mask = font.resolve() | inheritedFontResolveMask; + + // Propagate to children. + for (int i = 0; i < children.size(); ++i) { + QGraphicsItem *item = children.at(i); + if (item->isWidget()) { + QGraphicsWidget *w = static_cast(item); + if (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + w->d_func()->resolveFont(mask); + } else { + item->d_ptr->resolveFont(mask); + } + } + + if (!polished) + return; + // Notify change. + QEvent event(QEvent::FontChange); + QApplication::sendEvent(q, &event); +} + +QFont QGraphicsWidgetPrivate::naturalWidgetFont() const +{ + Q_Q(const QGraphicsWidget); + QFont naturalFont; // ### no application font support + if (QGraphicsWidget *parent = q->parentWidget()) { + naturalFont = parent->font(); + } else if (scene) { + naturalFont = scene->font(); + } + naturalFont.resolve(0); + return naturalFont; +} + +void QGraphicsWidgetPrivate::initStyleOptionTitleBar(QStyleOptionTitleBar *option) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + q->initStyleOption(option); + option->rect.setHeight(titleBarHeight(*option)); + option->titleBarFlags = windowFlags; + option->subControls = QStyle::SC_TitleBarCloseButton | QStyle::SC_TitleBarLabel | QStyle::SC_TitleBarSysMenu; + option->activeSubControls = windowData->hoveredSubControl; + bool isActive = q->isActiveWindow(); + if (isActive) { + option->state |= QStyle::State_Active; + option->titleBarState = Qt::WindowActive; + option->titleBarState |= QStyle::State_Active; + } else { + option->state &= ~QStyle::State_Active; + option->titleBarState = Qt::WindowNoState; + } + QFont windowTitleFont = QApplication::font("QWorkspaceTitleBar"); + QRect textRect = q->style()->subControlRect(QStyle::CC_TitleBar, option, QStyle::SC_TitleBarLabel, 0); + option->text = QFontMetrics(windowTitleFont).elidedText( + windowData->windowTitle, Qt::ElideRight, textRect.width()); +} + +void QGraphicsWidgetPrivate::adjustWindowFlags(Qt::WindowFlags *flags) +{ + bool customize = (*flags & (Qt::CustomizeWindowHint + | Qt::FramelessWindowHint + | Qt::WindowTitleHint + | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowContextHelpButtonHint)); + + uint type = (*flags & Qt::WindowType_Mask); + if (customize) + ; + else if (type == Qt::Dialog || type == Qt::Sheet) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowContextHelpButtonHint; + else if (type == Qt::Tool) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint; + else if (type == Qt::Window || type == Qt::SubWindow) + *flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint; +} + +void QGraphicsWidgetPrivate::windowFrameMouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + if (windowData->grabbedSection != Qt::NoSection) { + if (windowData->grabbedSection == Qt::TitleBarArea) { + windowData->buttonSunken = false; + QStyleOptionTitleBar bar; + initStyleOptionTitleBar(&bar); + // make sure that the coordinates (rect and pos) we send to the style are positive. + bar.rect = q->windowFrameRect().toRect(); + bar.rect.moveTo(0,0); + bar.rect.setHeight(q->style()->pixelMetric(QStyle::PM_TitleBarHeight, &bar)); + QPointF pos = event->pos(); + if (windowFrameMargins) { + pos.rx() += windowFrameMargins[Left]; + pos.ry() += windowFrameMargins[Top]; + } + bar.subControls = QStyle::SC_TitleBarCloseButton; + if (q->style()->subControlRect(QStyle::CC_TitleBar, &bar, + QStyle::SC_TitleBarCloseButton, + event->widget()).contains(pos.toPoint())) { + q->close(); + } + } + if (!(static_cast(event)->buttons())) + windowData->grabbedSection = Qt::NoSection; + event->accept(); + } +} + +void QGraphicsWidgetPrivate::windowFrameMousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + if (event->button() != Qt::LeftButton) + return; + + ensureWindowData(); + windowData->startGeometry = q->geometry(); + windowData->grabbedSection = q->windowFrameSectionAt(event->pos()); + ensureWindowData(); + if (windowData->grabbedSection == Qt::TitleBarArea + && windowData->hoveredSubControl == QStyle::SC_TitleBarCloseButton) { + windowData->buttonSunken = true; + q->update(); + } + event->setAccepted(windowData->grabbedSection != Qt::NoSection); +} + +/*! + Used to calculate the + Precondition: + \a widget should support either hfw or wfh + + If \a heightForWidth is set to false, this function will query the width for height + instead. \a width will then be interpreted as height, \a minh and \a maxh will be interpreted + as minimum width and maximum width. + */ +static qreal minimumHeightForWidth(qreal width, qreal minh, qreal maxh, + const QGraphicsWidget *widget, + bool heightForWidth = true) +{ + qreal minimumHeightForWidth = -1; + const bool hasHFW = QGraphicsLayoutItemPrivate::get(widget)->hasHeightForWidth(); + if (hasHFW == heightForWidth) { + minimumHeightForWidth = hasHFW + ? widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(width, -1)).height() + : widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, width)).width(); //"width" is here height! + } else { + // widthForHeight + const qreal constraint = width; + while (maxh - minh > 0.1) { + qreal middle = minh + (maxh - minh)/2; + // ### really bad, if we are a widget with a layout it will call + // layout->effectiveSizeHint(Qt::MiniumumSize), which again will call + // sizeHint three times because of how the cache works + qreal hfw = hasHFW + ? widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(middle, -1)).height() + : widget->effectiveSizeHint(Qt::MinimumSize, QSizeF(-1, middle)).width(); + if (hfw > constraint) { + minh = middle; + } else if (hfw <= constraint) { + maxh = middle; + } + } + minimumHeightForWidth = maxh; + } + return minimumHeightForWidth; +} + +static qreal minimumWidthForHeight(qreal height, qreal minw, qreal maxw, + const QGraphicsWidget *widget) +{ + return minimumHeightForWidth(height, minw, maxw, widget, false); +} + +static QSizeF closestAcceptableSize(const QSizeF &proposed, + const QGraphicsWidget *widget) +{ + const QSizeF current = widget->size(); + + qreal minw = proposed.width(); + qreal maxw = current.width(); + qreal minh = proposed.height(); + qreal maxh = current.height(); + + qreal middlew = maxw; + qreal middleh = maxh; + qreal min_hfw; + min_hfw = minimumHeightForWidth(maxw, minh, maxh, widget); + + do { + if (maxw - minw < 0.1) { + // we still havent found anything, cut off binary search + minw = maxw; + minh = maxh; + } + middlew = minw + (maxw - minw)/2.0; + middleh = minh + (maxh - minh)/2.0; + + min_hfw = minimumHeightForWidth(middlew, minh, maxh, widget); + + if (min_hfw > middleh) { + minw = middlew; + minh = middleh; + } else if (min_hfw <= middleh) { + maxw = middlew; + maxh = middleh; + } + } while (maxw != minw); + + min_hfw = minimumHeightForWidth(middlew, minh, maxh, widget); + + QSizeF result; + if (min_hfw < maxh) { + result = QSizeF(middlew, min_hfw); + } else { + // Needed because of the cut-off we do above. + result = QSizeF(minimumWidthForHeight(maxh, proposed.width(), current.width(), widget), maxh); + } + return result; +} + +static void _q_boundGeometryToSizeConstraints(const QRectF &startGeometry, + QRectF *rect, Qt::WindowFrameSection section, + const QSizeF &min, const QSizeF &max, + const QGraphicsWidget *widget) +{ + const QRectF proposedRect = *rect; + qreal width = qBound(min.width(), proposedRect.width(), max.width()); + qreal height = qBound(min.height(), proposedRect.height(), max.height()); + + const bool hasHFW = QGraphicsLayoutItemPrivate::get(widget)->hasHeightForWidth(); + const bool hasWFH = QGraphicsLayoutItemPrivate::get(widget)->hasWidthForHeight(); + + const bool widthChanged = proposedRect.width() != widget->size().width(); + const bool heightChanged = proposedRect.height() != widget->size().height(); + + if (hasHFW || hasWFH) { + if (widthChanged || heightChanged) { + qreal minExtent; + qreal maxExtent; + qreal constraint; + qreal proposed; + if (hasHFW) { + minExtent = min.height(); + maxExtent = max.height(); + constraint = width; + proposed = proposedRect.height(); + } else { + // width for height + minExtent = min.width(); + maxExtent = max.width(); + constraint = height; + proposed = proposedRect.width(); + } + if (minimumHeightForWidth(constraint, minExtent, maxExtent, widget, hasHFW) > proposed) { + QSizeF effectiveSize = closestAcceptableSize(QSizeF(width, height), widget); + width = effectiveSize.width(); + height = effectiveSize.height(); + } + } + } + + switch (section) { + case Qt::LeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.top(), + qRound(width), startGeometry.height()); + break; + case Qt::TopLeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.bottom() - qRound(height), + qRound(width), qRound(height)); + break; + case Qt::TopSection: + rect->setRect(startGeometry.left(), startGeometry.bottom() - qRound(height), + startGeometry.width(), qRound(height)); + break; + case Qt::TopRightSection: + rect->setTop(rect->bottom() - qRound(height)); + rect->setWidth(qRound(width)); + break; + case Qt::RightSection: + rect->setWidth(qRound(width)); + break; + case Qt::BottomRightSection: + rect->setWidth(qRound(width)); + rect->setHeight(qRound(height)); + break; + case Qt::BottomSection: + rect->setHeight(qRound(height)); + break; + case Qt::BottomLeftSection: + rect->setRect(startGeometry.right() - qRound(width), startGeometry.top(), + qRound(width), qRound(height)); + break; + default: + break; + } +} + +void QGraphicsWidgetPrivate::windowFrameMouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QGraphicsWidget); + ensureWindowData(); + if (!(event->buttons() & Qt::LeftButton) || windowData->hoveredSubControl != QStyle::SC_TitleBarLabel) + return; + + QLineF delta(q->mapFromScene(event->buttonDownScenePos(Qt::LeftButton)), event->pos()); + QLineF parentDelta(q->mapToParent(delta.p1()), q->mapToParent(delta.p2())); + QLineF parentXDelta(q->mapToParent(QPointF(delta.p1().x(), 0)), q->mapToParent(QPointF(delta.p2().x(), 0))); + QLineF parentYDelta(q->mapToParent(QPointF(0, delta.p1().y())), q->mapToParent(QPointF(0, delta.p2().y()))); + + QRectF newGeometry; + switch (windowData->grabbedSection) { + case Qt::LeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentXDelta.dx(), parentXDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), delta.dy())); + break; + case Qt::TopLeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentDelta.dx(), parentDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), delta.dy())); + break; + case Qt::TopSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentYDelta.dx(), parentYDelta.dy()), + windowData->startGeometry.size() - QSizeF(0, delta.dy())); + break; + case Qt::TopRightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentYDelta.dx(), parentYDelta.dy()), + windowData->startGeometry.size() - QSizeF(-delta.dx(), delta.dy())); + break; + case Qt::RightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(delta.dx(), 0)); + break; + case Qt::BottomRightSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(delta.dx(), delta.dy())); + break; + case Qt::BottomSection: + newGeometry = QRectF(windowData->startGeometry.topLeft(), + windowData->startGeometry.size() + QSizeF(0, delta.dy())); + break; + case Qt::BottomLeftSection: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentXDelta.dx(), parentXDelta.dy()), + windowData->startGeometry.size() - QSizeF(delta.dx(), -delta.dy())); + break; + case Qt::TitleBarArea: + newGeometry = QRectF(windowData->startGeometry.topLeft() + + QPointF(parentDelta.dx(), parentDelta.dy()), + windowData->startGeometry.size()); + break; + case Qt::NoSection: + break; + } + + if (windowData->grabbedSection != Qt::NoSection) { + _q_boundGeometryToSizeConstraints(windowData->startGeometry, &newGeometry, + windowData->grabbedSection, + q->effectiveSizeHint(Qt::MinimumSize), + q->effectiveSizeHint(Qt::MaximumSize), + q); + q->setGeometry(newGeometry); + } +} + +void QGraphicsWidgetPrivate::windowFrameHoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_Q(QGraphicsWidget); + if (!hasDecoration()) + return; + + ensureWindowData(); + + if (q->rect().contains(event->pos())) { + if (windowData->buttonMouseOver || windowData->hoveredSubControl != QStyle::SC_None) + windowFrameHoverLeaveEvent(event); + return; + } + + bool wasMouseOver = windowData->buttonMouseOver; + QRect oldButtonRect = windowData->buttonRect; + windowData->buttonRect = QRect(); + windowData->buttonMouseOver = false; + QPointF pos = event->pos(); + QStyleOptionTitleBar bar; + // make sure that the coordinates (rect and pos) we send to the style are positive. + if (windowFrameMargins) { + pos.rx() += windowFrameMargins[Left]; + pos.ry() += windowFrameMargins[Top]; + } + initStyleOptionTitleBar(&bar); + bar.rect = q->windowFrameRect().toRect(); + bar.rect.moveTo(0,0); + bar.rect.setHeight(int(titleBarHeight(bar))); + + Qt::CursorShape cursorShape = Qt::ArrowCursor; + bool needsSetCursorCall = true; + switch (q->windowFrameSectionAt(event->pos())) { + case Qt::TopLeftSection: + case Qt::BottomRightSection: + cursorShape = Qt::SizeFDiagCursor; + break; + case Qt::TopRightSection: + case Qt::BottomLeftSection: + cursorShape = Qt::SizeBDiagCursor; + break; + case Qt::LeftSection: + case Qt::RightSection: + cursorShape = Qt::SizeHorCursor; + break; + case Qt::TopSection: + case Qt::BottomSection: + cursorShape = Qt::SizeVerCursor; + break; + case Qt::TitleBarArea: + windowData->buttonRect = q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarCloseButton, 0); +#ifdef Q_WS_MAC + // On mac we should hover if we are in the 'area' of the buttons + windowData->buttonRect |= q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarMinButton, 0); + windowData->buttonRect |= q->style()->subControlRect( + QStyle::CC_TitleBar, &bar, QStyle::SC_TitleBarMaxButton, 0); +#endif + if (windowData->buttonRect.contains(pos.toPoint())) + windowData->buttonMouseOver = true; + event->ignore(); + break; + default: + needsSetCursorCall = false; + event->ignore(); + } +#ifndef QT_NO_CURSOR + if (needsSetCursorCall) + q->setCursor(cursorShape); +#endif + // update buttons if we hover over them + windowData->hoveredSubControl = q->style()->hitTestComplexControl(QStyle::CC_TitleBar, &bar, pos.toPoint(), 0); + if (windowData->hoveredSubControl != QStyle::SC_TitleBarCloseButton) + windowData->hoveredSubControl = QStyle::SC_TitleBarLabel; + + if (windowData->buttonMouseOver != wasMouseOver) { + if (!oldButtonRect.isNull()) + q->update(QRectF(oldButtonRect).translated(q->windowFrameRect().topLeft())); + if (!windowData->buttonRect.isNull()) + q->update(QRectF(windowData->buttonRect).translated(q->windowFrameRect().topLeft())); + } +} + +void QGraphicsWidgetPrivate::windowFrameHoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); + Q_Q(QGraphicsWidget); + if (hasDecoration()) { + // ### restore the cursor, don't override it +#ifndef QT_NO_CURSOR + q->unsetCursor(); +#endif + + ensureWindowData(); + + bool needsUpdate = false; + if (windowData->hoveredSubControl == QStyle::SC_TitleBarCloseButton + || windowData->buttonMouseOver) + needsUpdate = true; + + // update the hover state (of buttons etc...) + windowData->hoveredSubControl = QStyle::SC_None; + windowData->buttonMouseOver = false; + windowData->buttonRect = QRect(); + if (needsUpdate) + q->update(windowData->buttonRect); + } +} + +bool QGraphicsWidgetPrivate::hasDecoration() const +{ + return (windowFlags & Qt::Window) && (windowFlags & Qt::WindowTitleHint); +} + +/** + * is called after a reparent has taken place to fix up the focus chain(s) + */ +void QGraphicsWidgetPrivate::fixFocusChainBeforeReparenting(QGraphicsWidget *newParent, QGraphicsScene *oldScene, QGraphicsScene *newScene) +{ + Q_Q(QGraphicsWidget); + + Q_ASSERT(focusNext && focusPrev); + + QGraphicsWidget *n = q; //last one in 'new' list + QGraphicsWidget *o = 0; //last one in 'old' list + + QGraphicsWidget *w = focusNext; + + QGraphicsWidget *firstOld = 0; + bool wasPreviousNew = true; + + while (w != q) { + bool isCurrentNew = q->isAncestorOf(w); + if (isCurrentNew) { + if (!wasPreviousNew) { + n->d_func()->focusNext = w; + w->d_func()->focusPrev = n; + } + n = w; + } else /*if (!isCurrentNew)*/ { + if (wasPreviousNew) { + if (o) { + o->d_func()->focusNext = w; + w->d_func()->focusPrev = o; + } else { + firstOld = w; + } + } + o = w; + } + w = w->d_func()->focusNext; + wasPreviousNew = isCurrentNew; + } + + // repair the 'old' chain + if (firstOld) { + o->d_func()->focusNext = firstOld; + firstOld->d_func()->focusPrev = o; + } + + // update tabFocusFirst for oldScene if the item is going to be removed from oldScene + if (newParent) + newScene = newParent->scene(); + + if (oldScene && newScene != oldScene) + oldScene->d_func()->tabFocusFirst = (firstOld && firstOld->scene() == oldScene) ? firstOld : 0; + + QGraphicsItem *topLevelItem = newParent ? newParent->topLevelItem() : 0; + QGraphicsWidget *topLevel = 0; + if (topLevelItem && topLevelItem->isWidget()) + topLevel = static_cast(topLevelItem); + + if (topLevel && newParent) { + QGraphicsWidget *last = topLevel->d_func()->focusPrev; + // link last with new chain + last->d_func()->focusNext = q; + focusPrev = last; + + // link last in chain with + topLevel->d_func()->focusPrev = n; + n->d_func()->focusNext = topLevel; + } else { + // q is the start of the focus chain + n->d_func()->focusNext = q; + focusPrev = n; + } + +} + +void QGraphicsWidgetPrivate::setLayout_helper(QGraphicsLayout *l) +{ + delete (this->layout); + layout = l; + if (!l) { + Q_Q(QGraphicsWidget); + q->updateGeometry(); + } +} + +qreal QGraphicsWidgetPrivate::width() const +{ + Q_Q(const QGraphicsWidget); + return q->geometry().width(); +} + +void QGraphicsWidgetPrivate::setWidth(qreal w) +{ + if (qIsNaN(w)) + return; + Q_Q(QGraphicsWidget); + if (q->geometry().width() == w) + return; + + QRectF oldGeom = q->geometry(); + + q->setGeometry(QRectF(q->x(), q->y(), w, height())); +} + +void QGraphicsWidgetPrivate::resetWidth() +{ + Q_Q(QGraphicsWidget); + q->setGeometry(QRectF(q->x(), q->y(), 0, height())); +} + +qreal QGraphicsWidgetPrivate::height() const +{ + Q_Q(const QGraphicsWidget); + return q->geometry().height(); +} + +void QGraphicsWidgetPrivate::setHeight(qreal h) +{ + if (qIsNaN(h)) + return; + Q_Q(QGraphicsWidget); + if (q->geometry().height() == h) + return; + + QRectF oldGeom = q->geometry(); + + q->setGeometry(QRectF(q->x(), q->y(), width(), h)); +} + +void QGraphicsWidgetPrivate::resetHeight() +{ + Q_Q(QGraphicsWidget); + q->setGeometry(QRectF(q->x(), q->y(), width(), 0)); +} + +void QGraphicsWidgetPrivate::setGeometryFromSetPos() +{ + if (inSetGeometry) + return; + Q_Q(QGraphicsWidget); + inSetPos = 1; + // Ensure setGeometry is called (avoid recursion when setPos is + // called from within setGeometry). + q->setGeometry(QRectF(pos, q->size())); + inSetPos = 0 ; +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgraphicswidget_p.h b/src/gui/graphicsview/qgraphicswidget_p.h new file mode 100644 index 0000000000..4379eafbde --- /dev/null +++ b/src/gui/graphicsview/qgraphicswidget_p.h @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSWIDGET_P_H +#define QGRAPHICSWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "qgraphicsitem_p.h" +#include "qgraphicswidget.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayout; +class QStyleOptionTitleBar; + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +class QGraphicsWidgetPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsWidget) +public: + QGraphicsWidgetPrivate() + : margins(0), + layout(0), + inheritedPaletteResolveMask(0), + inheritedFontResolveMask(0), + inSetGeometry(0), + polished(0), + inSetPos(0), + autoFillBackground(0), + focusPolicy(Qt::NoFocus), + focusNext(0), + focusPrev(0), + windowFlags(0), + windowData(0), + setWindowFrameMargins(false), + windowFrameMargins(0) + { } + virtual ~QGraphicsWidgetPrivate(); + + void init(QGraphicsItem *parentItem, Qt::WindowFlags wFlags); + qreal titleBarHeight(const QStyleOptionTitleBar &options) const; + + // Margins + enum {Left, Top, Right, Bottom}; + mutable qreal *margins; + void ensureMargins() const; + + void fixFocusChainBeforeReparenting(QGraphicsWidget *newParent, QGraphicsScene *oldScene, QGraphicsScene *newScene = 0); + void setLayout_helper(QGraphicsLayout *l); + + // Layouts + QGraphicsLayout *layout; + void setLayoutDirection_helper(Qt::LayoutDirection direction); + void resolveLayoutDirection(); + + // Style + QPalette palette; + uint inheritedPaletteResolveMask; + void setPalette_helper(const QPalette &palette); + void resolvePalette(uint inheritedMask); + void updatePalette(const QPalette &palette); + QPalette naturalWidgetPalette() const; + QFont font; + uint inheritedFontResolveMask; + void setFont_helper(const QFont &font); + void resolveFont(uint inheritedMask); + void updateFont(const QFont &font); + QFont naturalWidgetFont() const; + + // Window specific + void initStyleOptionTitleBar(QStyleOptionTitleBar *option); + void adjustWindowFlags(Qt::WindowFlags *wFlags); + void windowFrameMouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void windowFrameMousePressEvent(QGraphicsSceneMouseEvent *event); + void windowFrameMouseMoveEvent(QGraphicsSceneMouseEvent *event); + void windowFrameHoverMoveEvent(QGraphicsSceneHoverEvent *event); + void windowFrameHoverLeaveEvent(QGraphicsSceneHoverEvent *event); + bool hasDecoration() const; + + // Private Properties + qreal width() const; + void setWidth(qreal); + void resetWidth(); + + qreal height() const; + void setHeight(qreal); + void resetHeight(); + void setGeometryFromSetPos(); + + // State + inline int attributeToBitIndex(Qt::WidgetAttribute att) const + { + int bit = -1; + switch (att) { + case Qt::WA_SetLayoutDirection: bit = 0; break; + case Qt::WA_RightToLeft: bit = 1; break; + case Qt::WA_SetStyle: bit = 2; break; + case Qt::WA_Resized: bit = 3; break; + case Qt::WA_DeleteOnClose: bit = 4; break; + case Qt::WA_NoSystemBackground: bit = 5; break; + case Qt::WA_OpaquePaintEvent: bit = 6; break; + case Qt::WA_SetPalette: bit = 7; break; + case Qt::WA_SetFont: bit = 8; break; + case Qt::WA_WindowPropagation: bit = 9; break; + default: break; + } + return bit; + } + inline void setAttribute(Qt::WidgetAttribute att, bool value) + { + int bit = attributeToBitIndex(att); + if (bit == -1) { + qWarning("QGraphicsWidget::setAttribute: unsupported attribute %d", int(att)); + return; + } + if (value) + attributes |= (1 << bit); + else + attributes &= ~(1 << bit); + } + inline bool testAttribute(Qt::WidgetAttribute att) const + { + int bit = attributeToBitIndex(att); + if (bit == -1) + return false; + return (attributes & (1 << bit)) != 0; + } + quint32 attributes : 10; + quint32 inSetGeometry : 1; + quint32 polished: 1; + quint32 inSetPos : 1; + quint32 autoFillBackground : 1; + + // Focus + Qt::FocusPolicy focusPolicy; + QGraphicsWidget *focusNext; + QGraphicsWidget *focusPrev; + + // Windows + Qt::WindowFlags windowFlags; + struct WindowData { + QString windowTitle; + QStyle::SubControl hoveredSubControl; + Qt::WindowFrameSection grabbedSection; + uint buttonMouseOver : 1; + uint buttonSunken : 1; + QRectF startGeometry; + QRect buttonRect; + WindowData() + : hoveredSubControl(QStyle::SC_None) + , grabbedSection(Qt::NoSection) + , buttonMouseOver(false) + , buttonSunken(false) + {} + } *windowData; + void ensureWindowData(); + + bool setWindowFrameMargins; + mutable qreal *windowFrameMargins; + void ensureWindowFrameMargins() const; + +#ifndef QT_NO_ACTION + QList actions; +#endif +}; + +#endif //!defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW + +QT_END_NAMESPACE + +#endif //QGRAPHICSWIDGET_P_H + diff --git a/src/gui/graphicsview/qgridlayoutengine.cpp b/src/gui/graphicsview/qgridlayoutengine.cpp new file mode 100644 index 0000000000..b8586cef21 --- /dev/null +++ b/src/gui/graphicsview/qgridlayoutengine.cpp @@ -0,0 +1,1742 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#ifndef QT_NO_GRAPHICSVIEW + +#include + +#include "qgraphicslayoutitem.h" +#include "qgridlayoutengine_p.h" +#include "qstyleoption.h" +#include "qvarlengtharray.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +template +static void insertOrRemoveItems(QVector &items, int index, int delta) +{ + int count = items.count(); + if (index < count) { + if (delta > 0) { + items.insert(index, delta, T()); + } else if (delta < 0) { + items.remove(index, qMin(-delta, count - index)); + } + } +} + +static qreal growthFactorBelowPreferredSize(qreal desired, qreal sumAvailable, qreal sumDesired) +{ + Q_ASSERT(sumDesired != 0.0); + return desired * qPow(sumAvailable / sumDesired, desired / sumDesired); +} + +static qreal fixedDescent(qreal descent, qreal ascent, qreal targetSize) +{ + if (descent < 0.0) + return -1.0; + + Q_ASSERT(descent >= 0.0); + Q_ASSERT(ascent >= 0.0); + Q_ASSERT(targetSize >= ascent + descent); + + qreal extra = targetSize - (ascent + descent); + return descent + (extra / 2.0); +} + +static qreal compare(const QGridLayoutBox &box1, const QGridLayoutBox &box2, int which) +{ + qreal size1 = box1.q_sizes(which); + qreal size2 = box2.q_sizes(which); + + if (which == MaximumSize) { + return size2 - size1; + } else { + return size1 - size2; + } +} + +void QGridLayoutBox::add(const QGridLayoutBox &other, int stretch, qreal spacing) +{ + Q_ASSERT(q_minimumDescent < 0.0); + + q_minimumSize += other.q_minimumSize + spacing; + q_preferredSize += other.q_preferredSize + spacing; + q_maximumSize += ((stretch == 0) ? other.q_preferredSize : other.q_maximumSize) + spacing; +} + +void QGridLayoutBox::combine(const QGridLayoutBox &other) +{ + q_minimumDescent = qMax(q_minimumDescent, other.q_minimumDescent); + q_minimumAscent = qMax(q_minimumAscent, other.q_minimumAscent); + + q_minimumSize = qMax(q_minimumAscent + q_minimumDescent, + qMax(q_minimumSize, other.q_minimumSize)); + qreal maxMax; + if (q_maximumSize == FLT_MAX && other.q_maximumSize != FLT_MAX) + maxMax = other.q_maximumSize; + else if (other.q_maximumSize == FLT_MAX && q_maximumSize != FLT_MAX) + maxMax = q_maximumSize; + else + maxMax = qMax(q_maximumSize, other.q_maximumSize); + + q_maximumSize = qMax(q_minimumSize, maxMax); + q_preferredSize = qBound(q_minimumSize, qMax(q_preferredSize, other.q_preferredSize), + q_maximumSize); +} + +void QGridLayoutBox::normalize() +{ + q_maximumSize = qMax(qreal(0.0), q_maximumSize); + q_minimumSize = qBound(qreal(0.0), q_minimumSize, q_maximumSize); + q_preferredSize = qBound(q_minimumSize, q_preferredSize, q_maximumSize); + q_minimumDescent = qMin(q_minimumDescent, q_minimumSize); + + Q_ASSERT((q_minimumDescent < 0.0) == (q_minimumAscent < 0.0)); +} + +#ifdef QT_DEBUG +void QGridLayoutBox::dump(int indent) const +{ + qDebug("%*sBox (%g <= %g <= %g [%g/%g])", indent, "", q_minimumSize, q_preferredSize, + q_maximumSize, q_minimumAscent, q_minimumDescent); +} +#endif + +bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2) +{ + for (int i = 0; i < NSizes; ++i) { + if (box1.q_sizes(i) != box2.q_sizes(i)) + return false; + } + return box1.q_minimumDescent == box2.q_minimumDescent + && box1.q_minimumAscent == box2.q_minimumAscent; +} + +void QGridLayoutRowData::reset(int count) +{ + ignore.fill(false, count); + boxes.fill(QGridLayoutBox(), count); + multiCellMap.clear(); + stretches.fill(0, count); + spacings.fill(0.0, count); + hasIgnoreFlag = false; +} + +void QGridLayoutRowData::distributeMultiCells(const QGridLayoutRowInfo &rowInfo) +{ + MultiCellMap::const_iterator i = multiCellMap.constBegin(); + for (; i != multiCellMap.constEnd(); ++i) { + int start = i.key().first; + int span = i.key().second; + int end = start + span; + const QGridLayoutBox &box = i.value().q_box; + int stretch = i.value().q_stretch; + + QGridLayoutBox totalBox = this->totalBox(start, end); + QVarLengthArray extras(span); + QVarLengthArray dummy(span); + QVarLengthArray newSizes(span); + + for (int j = 0; j < NSizes; ++j) { + qreal extra = compare(box, totalBox, j); + if (extra > 0.0) { + calculateGeometries(start, end, box.q_sizes(j), dummy.data(), newSizes.data(), + 0, totalBox, rowInfo); + + for (int k = 0; k < span; ++k) + extras[k].q_sizes(j) = newSizes[k]; + } + } + + for (int k = 0; k < span; ++k) { + boxes[start + k].combine(extras[k]); + if (stretch != 0) + stretches[start + k] = qMax(stretches[start + k], stretch); + } + } + multiCellMap.clear(); +} + +void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSize, qreal *positions, + qreal *sizes, qreal *descents, + const QGridLayoutBox &totalBox, + const QGridLayoutRowInfo &rowInfo) +{ + Q_ASSERT(end > start); + + targetSize = qMax(totalBox.q_minimumSize, targetSize); + + int n = end - start; + QVarLengthArray newSizes(n); + QVarLengthArray factors(n); + qreal sumFactors = 0.0; + int sumStretches = 0; + qreal sumAvailable; + + for (int i = 0; i < n; ++i) { + if (stretches[start + i] > 0) + sumStretches += stretches[start + i]; + } + + if (targetSize < totalBox.q_preferredSize) { + stealBox(start, end, MinimumSize, positions, sizes); + + sumAvailable = targetSize - totalBox.q_minimumSize; + if (sumAvailable > 0.0) { + qreal sumDesired = totalBox.q_preferredSize - totalBox.q_minimumSize; + + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) { + factors[i] = 0.0; + continue; + } + + const QGridLayoutBox &box = boxes.at(start + i); + qreal desired = box.q_preferredSize - box.q_minimumSize; + factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired); + sumFactors += factors[i]; + } + + for (int i = 0; i < n; ++i) { + Q_ASSERT(sumFactors > 0.0); + qreal delta = sumAvailable * factors[i] / sumFactors; + newSizes[i] = sizes[i] + delta; + } + } + } else { + bool isLargerThanMaximum = (targetSize > totalBox.q_maximumSize); + if (isLargerThanMaximum) { + stealBox(start, end, MaximumSize, positions, sizes); + sumAvailable = targetSize - totalBox.q_maximumSize; + } else { + stealBox(start, end, PreferredSize, positions, sizes); + sumAvailable = targetSize - totalBox.q_preferredSize; + } + + if (sumAvailable > 0.0) { + qreal sumCurrentAvailable = sumAvailable; + bool somethingHasAMaximumSize = false; + + qreal sumSizes = 0.0; + for (int i = 0; i < n; ++i) + sumSizes += sizes[i]; + + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) { + newSizes[i] = 0.0; + factors[i] = 0.0; + continue; + } + + const QGridLayoutBox &box = boxes.at(start + i); + qreal boxSize; + + qreal desired; + if (isLargerThanMaximum) { + boxSize = box.q_maximumSize; + desired = rowInfo.boxes.value(start + i).q_maximumSize - boxSize; + } else { + boxSize = box.q_preferredSize; + desired = box.q_maximumSize - boxSize; + } + if (desired == 0.0) { + newSizes[i] = sizes[i]; + factors[i] = 0.0; + } else { + Q_ASSERT(desired > 0.0); + + int stretch = stretches[start + i]; + if (sumStretches == 0) { + if (hasIgnoreFlag) { + factors[i] = (stretch < 0) ? 1.0 : 0.0; + } else { + factors[i] = (stretch < 0) ? sizes[i] : 0.0; + } + } else if (stretch == sumStretches) { + factors[i] = 1.0; + } else if (stretch <= 0) { + factors[i] = 0.0; + } else { + qreal ultimateSize; + qreal ultimateSumSizes; + qreal x = ((stretch * sumSizes) + - (sumStretches * boxSize)) + / (sumStretches - stretch); + if (x >= 0.0) { + ultimateSize = boxSize + x; + ultimateSumSizes = sumSizes + x; + } else { + ultimateSize = boxSize; + ultimateSumSizes = (sumStretches * boxSize) + / stretch; + } + + /* + We multiply these by 1.5 to give some space for a smooth transition + (at the expense of the stretch factors, which are not fully respected + during the transition). + */ + ultimateSize = ultimateSize * 3 / 2; + ultimateSumSizes = ultimateSumSizes * 3 / 2; + + qreal beta = ultimateSumSizes - sumSizes; + if (!beta) { + factors[i] = 1; + } else { + qreal alpha = qMin(sumCurrentAvailable, beta); + qreal ultimateFactor = (stretch * ultimateSumSizes / sumStretches) + - (boxSize); + qreal transitionalFactor = sumCurrentAvailable * (ultimateSize - boxSize) / beta; + + factors[i] = ((alpha * ultimateFactor) + + ((beta - alpha) * transitionalFactor)) / beta; + } + + } + sumFactors += factors[i]; + if (desired < sumCurrentAvailable) + somethingHasAMaximumSize = true; + + newSizes[i] = -1.0; + } + } + + bool keepGoing = somethingHasAMaximumSize; + while (keepGoing) { + keepGoing = false; + + for (int i = 0; i < n; ++i) { + if (newSizes[i] >= 0.0) + continue; + + qreal maxBoxSize; + if (isLargerThanMaximum) + maxBoxSize = rowInfo.boxes.value(start + i).q_maximumSize; + else + maxBoxSize = boxes.at(start + i).q_maximumSize; + + qreal avail = sumCurrentAvailable * factors[i] / sumFactors; + if (sizes[i] + avail >= maxBoxSize) { + newSizes[i] = maxBoxSize; + sumCurrentAvailable -= maxBoxSize - sizes[i]; + sumFactors -= factors[i]; + keepGoing = (sumCurrentAvailable > 0.0); + if (!keepGoing) + break; + } + } + } + + for (int i = 0; i < n; ++i) { + if (newSizes[i] < 0.0) { + qreal delta = (sumFactors == 0.0) ? 0.0 + : sumCurrentAvailable * factors[i] / sumFactors; + newSizes[i] = sizes[i] + delta; + } + } + } + } + + if (sumAvailable > 0) { + qreal offset = 0; + for (int i = 0; i < n; ++i) { + qreal delta = newSizes[i] - sizes[i]; + positions[i] += offset; + sizes[i] += delta; + offset += delta; + } + +#if 0 // some "pixel allocation" + int surplus = targetSize - (positions[n - 1] + sizes[n - 1]); + Q_ASSERT(surplus >= 0 && surplus <= n); + + int prevSurplus = -1; + while (surplus > 0 && surplus != prevSurplus) { + prevSurplus = surplus; + + int offset = 0; + for (int i = 0; i < n; ++i) { + const QGridLayoutBox &box = boxes.at(start + i); + int delta = (!ignore.testBit(start + i) && surplus > 0 + && factors[i] > 0 && sizes[i] < box.q_maximumSize) + ? 1 : 0; + + positions[i] += offset; + sizes[i] += delta; + offset += delta; + surplus -= delta; + } + } + Q_ASSERT(surplus == 0); +#endif + } + + if (descents) { + for (int i = 0; i < n; ++i) { + if (ignore.testBit(start + i)) + continue; + const QGridLayoutBox &box = boxes.at(start + i); + descents[i] = fixedDescent(box.q_minimumDescent, box.q_minimumAscent, sizes[i]); + } + } +} + +QGridLayoutBox QGridLayoutRowData::totalBox(int start, int end) const +{ + QGridLayoutBox result; + if (start < end) { + result.q_maximumSize = 0.0; + qreal nextSpacing = 0.0; + for (int i = start; i < end; ++i) { + result.add(boxes.at(i), stretches.at(i), nextSpacing); + nextSpacing = spacings.at(i); + } + } + return result; +} + +void QGridLayoutRowData::stealBox(int start, int end, int which, qreal *positions, qreal *sizes) +{ + qreal offset = 0.0; + qreal nextSpacing = 0.0; + + for (int i = start; i < end; ++i) { + qreal avail = 0.0; + + if (!ignore.testBit(i)) { + const QGridLayoutBox &box = boxes.at(i); + avail = box.q_sizes(which); + offset += nextSpacing; + nextSpacing = spacings.at(i); + } + + *positions++ = offset; + *sizes++ = avail; + offset += avail; + } +} + +#ifdef QT_DEBUG +void QGridLayoutRowData::dump(int indent) const +{ + qDebug("%*sData", indent, ""); + + for (int i = 0; i < ignore.count(); ++i) { + qDebug("%*s Row %d (stretch %d, spacing %g)", indent, "", i, stretches.at(i), + spacings.at(i)); + if (ignore.testBit(i)) + qDebug("%*s Ignored", indent, ""); + boxes.at(i).dump(indent + 2); + } + + MultiCellMap::const_iterator it = multiCellMap.constBegin(); + while (it != multiCellMap.constEnd()) { + qDebug("%*s Multi-cell entry <%d, %d> (stretch %d)", indent, "", it.key().first, + it.key().second, it.value().q_stretch); + it.value().q_box.dump(indent + 2); + } +} +#endif + +QGridLayoutItem::QGridLayoutItem(QGridLayoutEngine *engine, QGraphicsLayoutItem *layoutItem, + int row, int column, int rowSpan, int columnSpan, + Qt::Alignment alignment, int itemAtIndex) + : q_engine(engine), q_layoutItem(layoutItem), q_alignment(alignment) +{ + q_firstRows[Hor] = column; + q_firstRows[Ver] = row; + q_rowSpans[Hor] = columnSpan; + q_rowSpans[Ver] = rowSpan; + q_stretches[Hor] = -1; + q_stretches[Ver] = -1; + + q_engine->insertItem(this, itemAtIndex); +} + +int QGridLayoutItem::firstRow(Qt::Orientation orientation) const +{ + return q_firstRows[orientation == Qt::Vertical]; +} + +int QGridLayoutItem::firstColumn(Qt::Orientation orientation) const +{ + return q_firstRows[orientation == Qt::Horizontal]; +} + +int QGridLayoutItem::lastRow(Qt::Orientation orientation) const +{ + return firstRow(orientation) + rowSpan(orientation) - 1; +} + +int QGridLayoutItem::lastColumn(Qt::Orientation orientation) const +{ + return firstColumn(orientation) + columnSpan(orientation) - 1; +} + +int QGridLayoutItem::rowSpan(Qt::Orientation orientation) const +{ + return q_rowSpans[orientation == Qt::Vertical]; +} + +int QGridLayoutItem::columnSpan(Qt::Orientation orientation) const +{ + return q_rowSpans[orientation == Qt::Horizontal]; +} + +void QGridLayoutItem::setFirstRow(int row, Qt::Orientation orientation) +{ + q_firstRows[orientation == Qt::Vertical] = row; +} + +void QGridLayoutItem::setRowSpan(int rowSpan, Qt::Orientation orientation) +{ + q_rowSpans[orientation == Qt::Vertical] = rowSpan; +} + +int QGridLayoutItem::stretchFactor(Qt::Orientation orientation) const +{ + int stretch = q_stretches[orientation == Qt::Vertical]; + if (stretch >= 0) + return stretch; + + QSizePolicy::Policy policy = sizePolicy(orientation); + + if (policy & QSizePolicy::ExpandFlag) { + return 1; + } else if (policy & QSizePolicy::GrowFlag) { + return -1; // because we max it up + } else { + return 0; + } +} + +void QGridLayoutItem::setStretchFactor(int stretch, Qt::Orientation orientation) +{ + Q_ASSERT(stretch >= 0); // ### deal with too big stretches + q_stretches[orientation == Qt::Vertical] = stretch; +} + +QSizePolicy::Policy QGridLayoutItem::sizePolicy(Qt::Orientation orientation) const +{ + QSizePolicy sizePolicy(q_layoutItem->sizePolicy()); + return (orientation == Qt::Horizontal) ? sizePolicy.horizontalPolicy() + : sizePolicy.verticalPolicy(); +} + +/* + returns true if the size policy returns true for either hasHeightForWidth() + or hasWidthForHeight() + */ +bool QGridLayoutItem::hasDynamicConstraint() const +{ + return QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasHeightForWidth() + || QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasWidthForHeight(); +} + +Qt::Orientation QGridLayoutItem::dynamicConstraintOrientation() const +{ + if (QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasHeightForWidth()) + return Qt::Vertical; + else //if (QGraphicsLayoutItemPrivate::get(q_layoutItem)->hasWidthForHeight()) + return Qt::Horizontal; +} + +QSizePolicy::ControlTypes QGridLayoutItem::controlTypes(LayoutSide /* side */) const +{ + return q_layoutItem->sizePolicy().controlType(); +} + +QSizeF QGridLayoutItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + return q_layoutItem->effectiveSizeHint(which, constraint); +} + +QGridLayoutBox QGridLayoutItem::box(Qt::Orientation orientation, qreal constraint) const +{ + QGridLayoutBox result; + QSizePolicy::Policy policy = sizePolicy(orientation); + + if (orientation == Qt::Horizontal) { + QSizeF constraintSize(-1.0, constraint); + + result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).width(); + + if (policy & QSizePolicy::ShrinkFlag) { + result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).width(); + } else { + result.q_minimumSize = result.q_preferredSize; + } + + if (policy & (QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag)) { + result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).width(); + } else { + result.q_maximumSize = result.q_preferredSize; + } + } else { + QSizeF constraintSize(constraint, -1.0); + + result.q_preferredSize = sizeHint(Qt::PreferredSize, constraintSize).height(); + + if (policy & QSizePolicy::ShrinkFlag) { + result.q_minimumSize = sizeHint(Qt::MinimumSize, constraintSize).height(); + } else { + result.q_minimumSize = result.q_preferredSize; + } + + if (policy & (QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag)) { + result.q_maximumSize = sizeHint(Qt::MaximumSize, constraintSize).height(); + } else { + result.q_maximumSize = result.q_preferredSize; + } + + result.q_minimumDescent = sizeHint(Qt::MinimumDescent, constraintSize).height(); + if (result.q_minimumDescent >= 0.0) + result.q_minimumAscent = result.q_minimumSize - result.q_minimumDescent; + } + if (policy & QSizePolicy::IgnoreFlag) + result.q_preferredSize = result.q_minimumSize; + + return result; +} + +QRectF QGridLayoutItem::geometryWithin(qreal x, qreal y, qreal width, qreal height, + qreal rowDescent) const +{ + rowDescent = -1.0; // ### This disables the descent + + QGridLayoutBox vBox = box(Qt::Vertical); + if (vBox.q_minimumDescent < 0.0 || rowDescent < 0.0) { + qreal cellWidth = width; + qreal cellHeight = height; + + + QSizeF size = effectiveMaxSize(QSizeF(-1,-1)); + if (hasDynamicConstraint()) { + if (dynamicConstraintOrientation() == Qt::Vertical) { + if (size.width() > cellWidth) + size = effectiveMaxSize(QSizeF(cellWidth, -1)); + } else if (size.height() > cellHeight) { + size = effectiveMaxSize(QSizeF(-1, cellHeight)); + } + } + size = size.boundedTo(QSizeF(cellWidth, cellHeight)); + width = size.width(); + height = size.height(); + + Qt::Alignment align = q_engine->effectiveAlignment(this); + switch (align & Qt::AlignHorizontal_Mask) { + case Qt::AlignHCenter: + x += (cellWidth - width)/2; + break; + case Qt::AlignRight: + x += cellWidth - width; + break; + default: + break; + } + switch (align & Qt::AlignVertical_Mask) { + case Qt::AlignVCenter: + y += (cellHeight - height)/2; + break; + case Qt::AlignBottom: + y += cellHeight - height; + break; + default: + break; + } + return QRectF(x, y, width, height); + } else { + qreal descent = vBox.q_minimumDescent; + qreal ascent = vBox.q_minimumSize - descent; + return QRectF(x, y + height - rowDescent - ascent, width, ascent + descent); + } +} + +void QGridLayoutItem::setGeometry(const QRectF &rect) +{ + q_layoutItem->setGeometry(rect); +} + +void QGridLayoutItem::transpose() +{ + qSwap(q_firstRows[Hor], q_firstRows[Ver]); + qSwap(q_rowSpans[Hor], q_rowSpans[Ver]); + qSwap(q_stretches[Hor], q_stretches[Ver]); +} + +void QGridLayoutItem::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation) +{ + int oldFirstRow = firstRow(orientation); + if (oldFirstRow >= row) { + setFirstRow(oldFirstRow + delta, orientation); + } else if (lastRow(orientation) >= row) { + setRowSpan(rowSpan(orientation) + delta, orientation); + } +} +/*! + \internal + returns the effective maximumSize, will take the sizepolicy into + consideration. (i.e. if sizepolicy does not have QSizePolicy::Grow, then + maxSizeHint will be the preferredSize) + Note that effectiveSizeHint does not take sizePolicy into consideration, + (since it only evaluates the hints, as the name implies) +*/ +QSizeF QGridLayoutItem::effectiveMaxSize(const QSizeF &constraint) const +{ + QSizeF size = constraint; + bool vGrow = (sizePolicy(Qt::Vertical) & QSizePolicy::GrowFlag) == QSizePolicy::GrowFlag; + bool hGrow = (sizePolicy(Qt::Horizontal) & QSizePolicy::GrowFlag) == QSizePolicy::GrowFlag; + if (!vGrow || !hGrow) { + QSizeF pref = layoutItem()->effectiveSizeHint(Qt::PreferredSize, constraint); + if (!vGrow) + size.setHeight(pref.height()); + if (!hGrow) + size.setWidth(pref.width()); + } + + if (!size.isValid()) { + QSizeF maxSize = layoutItem()->effectiveSizeHint(Qt::MaximumSize, size); + if (size.width() == -1) + size.setWidth(maxSize.width()); + if (size.height() == -1) + size.setHeight(maxSize.height()); + } + return size; +} + +#ifdef QT_DEBUG +void QGridLayoutItem::dump(int indent) const +{ + qDebug("%*s%p (%d, %d) %d x %d", indent, "", q_layoutItem, firstRow(), firstColumn(), + rowSpan(), columnSpan()); + + if (q_stretches[Hor] >= 0) + qDebug("%*s Horizontal stretch: %d", indent, "", q_stretches[Hor]); + if (q_stretches[Ver] >= 0) + qDebug("%*s Vertical stretch: %d", indent, "", q_stretches[Ver]); + if (q_alignment != 0) + qDebug("%*s Alignment: %x", indent, "", uint(q_alignment)); + qDebug("%*s Horizontal size policy: %x Vertical size policy: %x", + indent, "", sizePolicy(Qt::Horizontal), sizePolicy(Qt::Vertical)); +} +#endif + +void QGridLayoutRowInfo::insertOrRemoveRows(int row, int delta) +{ + count += delta; + + insertOrRemoveItems(stretches, row, delta); + insertOrRemoveItems(spacings, row, delta); + insertOrRemoveItems(alignments, row, delta); + insertOrRemoveItems(boxes, row, delta); +} + +#ifdef QT_DEBUG +void QGridLayoutRowInfo::dump(int indent) const +{ + qDebug("%*sInfo (count: %d)", indent, "", count); + for (int i = 0; i < count; ++i) { + QString message; + + if (stretches.value(i).value() >= 0) + message += QString::fromAscii(" stretch %1").arg(stretches.value(i).value()); + if (spacings.value(i).value() >= 0.0) + message += QString::fromAscii(" spacing %1").arg(spacings.value(i).value()); + if (alignments.value(i) != 0) + message += QString::fromAscii(" alignment %1").arg(int(alignments.value(i)), 16); + + if (!message.isEmpty() || boxes.value(i) != QGridLayoutBox()) { + qDebug("%*s Row %d:%s", indent, "", i, qPrintable(message)); + if (boxes.value(i) != QGridLayoutBox()) + boxes.value(i).dump(indent + 1); + } + } +} +#endif + +QGridLayoutEngine::QGridLayoutEngine() +{ + m_visualDirection = Qt::LeftToRight; + invalidate(); +} + +int QGridLayoutEngine::rowCount(Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Vertical].count; +} + +int QGridLayoutEngine::columnCount(Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Horizontal].count; +} + +int QGridLayoutEngine::itemCount() const +{ + return q_items.count(); +} + +QGridLayoutItem *QGridLayoutEngine::itemAt(int index) const +{ + Q_ASSERT(index >= 0 && index < itemCount()); + return q_items.at(index); +} + +int QGridLayoutEngine::indexOf(QGraphicsLayoutItem *item) const +{ + for (int i = 0; i < q_items.size(); ++i) { + if (item == q_items.at(i)->layoutItem()) + return i; + } + return -1; +} + +int QGridLayoutEngine::effectiveFirstRow(Qt::Orientation orientation) const +{ + ensureEffectiveFirstAndLastRows(); + return q_cachedEffectiveFirstRows[orientation == Qt::Vertical]; +} + +int QGridLayoutEngine::effectiveLastRow(Qt::Orientation orientation) const +{ + ensureEffectiveFirstAndLastRows(); + return q_cachedEffectiveLastRows[orientation == Qt::Vertical]; +} + +void QGridLayoutEngine::setSpacing(qreal spacing, Qt::Orientations orientations) +{ + Q_ASSERT(spacing >= 0.0); + if (orientations & Qt::Horizontal) + q_defaultSpacings[Hor].setUserValue(spacing); + if (orientations & Qt::Vertical) + q_defaultSpacings[Ver].setUserValue(spacing); + + invalidate(); +} + +qreal QGridLayoutEngine::spacing(const QLayoutStyleInfo &styleInfo, Qt::Orientation orientation) const +{ + if (q_defaultSpacings[orientation == Qt::Vertical].isDefault()) { + QStyle *style = styleInfo.style(); + QStyleOption option; + option.initFrom(styleInfo.widget()); + qreal defaultSpacing = (qreal)style->pixelMetric(orientation == Qt::Vertical ? QStyle::PM_LayoutVerticalSpacing + : QStyle::PM_LayoutHorizontalSpacing, &option, styleInfo.widget()); + q_defaultSpacings[orientation == Qt::Vertical].setCachedValue(defaultSpacing); + } + return q_defaultSpacings[orientation == Qt::Vertical].value(); +} + +void QGridLayoutEngine::setRowSpacing(int row, qreal spacing, Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.spacings.count()) + rowInfo.spacings.resize(row + 1); + if (spacing >= 0) + rowInfo.spacings[row].setUserValue(spacing); + else + rowInfo.spacings[row] = QLayoutParameter(); + invalidate(); +} + +qreal QGridLayoutEngine::rowSpacing(int row, Qt::Orientation orientation) const +{ + QLayoutParameter spacing = q_infos[orientation == Qt::Vertical].spacings.value(row); + if (!spacing.isDefault()) + return spacing.value(); + return q_defaultSpacings[orientation == Qt::Vertical].value(); +} + +void QGridLayoutEngine::setRowStretchFactor(int row, int stretch, Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(stretch >= 0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.stretches.count()) + rowInfo.stretches.resize(row + 1); + rowInfo.stretches[row].setUserValue(stretch); +} + +int QGridLayoutEngine::rowStretchFactor(int row, Qt::Orientation orientation) const +{ + QStretchParameter stretch = q_infos[orientation == Qt::Vertical].stretches.value(row); + if (!stretch.isDefault()) + return stretch.value(); + return 0; +} + +void QGridLayoutEngine::setStretchFactor(QGraphicsLayoutItem *layoutItem, int stretch, + Qt::Orientation orientation) +{ + Q_ASSERT(stretch >= 0); + + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + item->setStretchFactor(stretch, orientation); +} + +int QGridLayoutEngine::stretchFactor(QGraphicsLayoutItem *layoutItem, Qt::Orientation orientation) const +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + return item->stretchFactor(orientation); + return 0; +} + +void QGridLayoutEngine::setRowSizeHint(Qt::SizeHint which, int row, qreal size, + Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(size >= 0.0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.boxes.count()) + rowInfo.boxes.resize(row + 1); + rowInfo.boxes[row].q_sizes(which) = size; +} + +qreal QGridLayoutEngine::rowSizeHint(Qt::SizeHint which, int row, Qt::Orientation orientation) const +{ + return q_infos[orientation == Qt::Vertical].boxes.value(row).q_sizes(which); +} + +void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment, + Qt::Orientation orientation) +{ + Q_ASSERT(row >= 0); + + maybeExpandGrid(row, -1, orientation); + + QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + if (row >= rowInfo.alignments.count()) + rowInfo.alignments.resize(row + 1); + rowInfo.alignments[row] = alignment; +} + +Qt::Alignment QGridLayoutEngine::rowAlignment(int row, Qt::Orientation orientation) const +{ + Q_ASSERT(row >= 0); + return q_infos[orientation == Qt::Vertical].alignments.value(row); +} + +void QGridLayoutEngine::setAlignment(QGraphicsLayoutItem *layoutItem, Qt::Alignment alignment) +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + item->setAlignment(alignment); + invalidate(); +} + +Qt::Alignment QGridLayoutEngine::alignment(QGraphicsLayoutItem *layoutItem) const +{ + if (QGridLayoutItem *item = findLayoutItem(layoutItem)) + return item->alignment(); + return 0; +} + +Qt::Alignment QGridLayoutEngine::effectiveAlignment(const QGridLayoutItem *layoutItem) const +{ + Qt::Alignment align = layoutItem->alignment(); + if (!(align & Qt::AlignVertical_Mask)) { + // no vertical alignment, respect the row alignment + int y = layoutItem->firstRow(); + align |= (rowAlignment(y, Qt::Vertical) & Qt::AlignVertical_Mask); + } + if (!(align & Qt::AlignHorizontal_Mask)) { + // no horizontal alignment, respect the column alignment + int x = layoutItem->firstColumn(); + align |= (rowAlignment(x, Qt::Horizontal) & Qt::AlignHorizontal_Mask); + } + return align; +} + +/*! + \internal + The \a index is only used by QGraphicsLinearLayout to ensure that itemAt() reflects the order + of visual arrangement. Strictly speaking it does not have to, but most people expect it to. + (And if it didn't we would have to add itemArrangedAt(int index) or something..) + */ +void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index) +{ + maybeExpandGrid(item->lastRow(), item->lastColumn()); + + if (index == -1) + q_items.append(item); + else + q_items.insert(index, item); + + for (int i = item->firstRow(); i <= item->lastRow(); ++i) { + for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { + if (itemAt(i, j)) + qWarning("QGridLayoutEngine::addItem: Cell (%d, %d) already taken", i, j); + setItemAt(i, j, item); + } + } +} + +void QGridLayoutEngine::addItem(QGridLayoutItem *item) +{ + insertItem(item, -1); +} + +void QGridLayoutEngine::removeItem(QGridLayoutItem *item) +{ + Q_ASSERT(q_items.contains(item)); + + invalidate(); + + for (int i = item->firstRow(); i <= item->lastRow(); ++i) { + for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { + if (itemAt(i, j) == item) + setItemAt(i, j, 0); + } + } + + q_items.removeAll(item); +} + +QGridLayoutItem *QGridLayoutEngine::findLayoutItem(QGraphicsLayoutItem *layoutItem) const +{ + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + if (item->layoutItem() == layoutItem) + return item; + } + return 0; +} + +QGridLayoutItem *QGridLayoutEngine::itemAt(int row, int column, Qt::Orientation orientation) const +{ + if (orientation == Qt::Horizontal) + qSwap(row, column); + if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount())) + return 0; + return q_grid.at((row * internalGridColumnCount()) + column); +} + +void QGridLayoutEngine::invalidate() +{ + q_cachedEffectiveFirstRows[Hor] = -1; + q_cachedEffectiveFirstRows[Ver] = -1; + q_cachedEffectiveLastRows[Hor] = -1; + q_cachedEffectiveLastRows[Ver] = -1; + q_cachedDataForStyleInfo.invalidate(); + q_cachedSize = QSizeF(); + q_cachedConstraintOrientation = UnknownConstraint; +} + +static void visualRect(QRectF *geom, Qt::LayoutDirection dir, const QRectF &contentsRect) +{ + if (dir == Qt::RightToLeft) + geom->moveRight(contentsRect.right() - (geom->left() - contentsRect.left())); +} + +void QGridLayoutEngine::setGeometries(const QLayoutStyleInfo &styleInfo, + const QRectF &contentsGeometry) +{ + if (rowCount() < 1 || columnCount() < 1) + return; + + ensureGeometries(styleInfo, contentsGeometry.size()); + + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + + qreal x = q_xx[item->firstColumn()]; + qreal y = q_yy[item->firstRow()]; + qreal width = q_widths[item->lastColumn()]; + qreal height = q_heights[item->lastRow()]; + + if (item->columnSpan() != 1) + width += q_xx[item->lastColumn()] - x; + if (item->rowSpan() != 1) + height += q_yy[item->lastRow()] - y; + + QRectF geom = item->geometryWithin(contentsGeometry.x() + x, contentsGeometry.y() + y, + width, height, q_descents[item->lastRow()]); + visualRect(&geom, visualDirection(), contentsGeometry); + item->setGeometry(geom); + } +} + +// ### candidate for deletion +QRectF QGridLayoutEngine::cellRect(const QLayoutStyleInfo &styleInfo, + const QRectF &contentsGeometry, int row, int column, int rowSpan, + int columnSpan) const +{ + if (uint(row) >= uint(rowCount()) || uint(column) >= uint(columnCount()) + || rowSpan < 1 || columnSpan < 1) + return QRectF(); + + ensureGeometries(styleInfo, contentsGeometry.size()); + + int lastColumn = qMax(column + columnSpan, columnCount()) - 1; + int lastRow = qMax(row + rowSpan, rowCount()) - 1; + + qreal x = q_xx[column]; + qreal y = q_yy[row]; + qreal width = q_widths[lastColumn]; + qreal height = q_heights[lastRow]; + + if (columnSpan != 1) + width += q_xx[lastColumn] - x; + if (rowSpan != 1) + height += q_yy[lastRow] - y; + + return QRectF(contentsGeometry.x() + x, contentsGeometry.y() + y, width, height); +} + +QSizeF QGridLayoutEngine::sizeHint(const QLayoutStyleInfo &styleInfo, Qt::SizeHint which, + const QSizeF &constraint) const +{ + QGridLayoutBox sizehint_totalBoxes[NOrientations]; + + bool sizeHintCalculated = false; + + if (hasDynamicConstraint() && rowCount() > 0 && columnCount() > 0) { + if (constraintOrientation() == Qt::Vertical) { + //We have items whose height depends on their width + if (constraint.width() >= 0) { + if (q_cachedDataForStyleInfo != styleInfo) + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + else + sizehint_totalBoxes[Hor] = q_totalBoxes[Hor]; + QVector sizehint_xx; + QVector sizehint_widths; + + sizehint_xx.resize(columnCount()); + sizehint_widths.resize(columnCount()); + qreal width = constraint.width(); + //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as + //constraints to find the row heights + q_columnData.calculateGeometries(0, columnCount(), width, sizehint_xx.data(), sizehint_widths.data(), + 0, sizehint_totalBoxes[Hor], q_infos[Hor]); + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, sizehint_xx.data(), sizehint_widths.data(), Qt::Vertical); + sizeHintCalculated = true; + } + } else { + if (constraint.height() >= 0) { + //We have items whose width depends on their height + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + QVector sizehint_yy; + QVector sizehint_heights; + + sizehint_yy.resize(rowCount()); + sizehint_heights.resize(rowCount()); + qreal height = constraint.height(); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as + //constraints to find the column widths + q_rowData.calculateGeometries(0, rowCount(), height, sizehint_yy.data(), sizehint_heights.data(), + 0, sizehint_totalBoxes[Ver], q_infos[Ver]); + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, sizehint_yy.data(), sizehint_heights.data(), Qt::Horizontal); + sizeHintCalculated = true; + } + } + } + + if (!sizeHintCalculated) { + //No items with height for width, so it doesn't matter which order we do these in + if (q_cachedDataForStyleInfo != styleInfo) { + ensureColumnAndRowData(&q_columnData, &sizehint_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + ensureColumnAndRowData(&q_rowData, &sizehint_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + } else { + sizehint_totalBoxes[Hor] = q_totalBoxes[Hor]; + sizehint_totalBoxes[Ver] = q_totalBoxes[Ver]; + } + } + + switch (which) { + case Qt::MinimumSize: + return QSizeF(sizehint_totalBoxes[Hor].q_minimumSize, sizehint_totalBoxes[Ver].q_minimumSize); + case Qt::PreferredSize: + return QSizeF(sizehint_totalBoxes[Hor].q_preferredSize, sizehint_totalBoxes[Ver].q_preferredSize); + case Qt::MaximumSize: + return QSizeF(sizehint_totalBoxes[Hor].q_maximumSize, sizehint_totalBoxes[Ver].q_maximumSize); + case Qt::MinimumDescent: + return QSizeF(-1.0, sizehint_totalBoxes[Hor].q_minimumDescent); // ### doesn't work + default: + break; + } + return QSizeF(); +} + +QSizePolicy::ControlTypes QGridLayoutEngine::controlTypes(LayoutSide side) const +{ + Qt::Orientation orientation = (side == Top || side == Bottom) ? Qt::Vertical : Qt::Horizontal; + int row = (side == Top || side == Left) ? effectiveFirstRow(orientation) + : effectiveLastRow(orientation); + QSizePolicy::ControlTypes result = 0; + + for (int column = columnCount(orientation) - 1; column >= 0; --column) { + if (QGridLayoutItem *item = itemAt(row, column, orientation)) + result |= item->controlTypes(side); + } + return result; +} + +void QGridLayoutEngine::transpose() +{ + invalidate(); + + for (int i = q_items.count() - 1; i >= 0; --i) + q_items.at(i)->transpose(); + + qSwap(q_defaultSpacings[Hor], q_defaultSpacings[Ver]); + qSwap(q_infos[Hor], q_infos[Ver]); + + regenerateGrid(); +} + +void QGridLayoutEngine::setVisualDirection(Qt::LayoutDirection direction) +{ + m_visualDirection = direction; +} + +Qt::LayoutDirection QGridLayoutEngine::visualDirection() const +{ + return m_visualDirection; +} + +#ifdef QT_DEBUG +void QGridLayoutEngine::dump(int indent) const +{ + qDebug("%*sEngine", indent, ""); + + qDebug("%*s Items (%d)", indent, "", q_items.count()); + int i; + for (i = 0; i < q_items.count(); ++i) + q_items.at(i)->dump(indent + 2); + + qDebug("%*s Grid (%d x %d)", indent, "", internalGridRowCount(), + internalGridColumnCount()); + for (int row = 0; row < internalGridRowCount(); ++row) { + QString message = QLatin1String("[ "); + for (int column = 0; column < internalGridColumnCount(); ++column) { + message += QString::number(q_items.indexOf(itemAt(row, column))).rightJustified(3); + message += QLatin1Char(' '); + } + message += QLatin1Char(']'); + qDebug("%*s %s", indent, "", qPrintable(message)); + } + + if (q_defaultSpacings[Hor].value() >= 0.0 || q_defaultSpacings[Ver].value() >= 0.0) + qDebug("%*s Default spacings: %g %g", indent, "", q_defaultSpacings[Hor].value(), + q_defaultSpacings[Ver].value()); + + qDebug("%*s Column and row info", indent, ""); + q_infos[Hor].dump(indent + 2); + q_infos[Ver].dump(indent + 2); + + qDebug("%*s Column and row data", indent, ""); + q_columnData.dump(indent + 2); + q_rowData.dump(indent + 2); + + qDebug("%*s Geometries output", indent, ""); + QVector *cellPos = &q_yy; + for (int pass = 0; pass < 2; ++pass) { + QString message; + for (i = 0; i < cellPos->count(); ++i) { + message += QLatin1String((message.isEmpty() ? "[" : ", ")); + message += QString::number(cellPos->at(i)); + } + message += QLatin1Char(']'); + qDebug("%*s %s %s", indent, "", (pass == 0 ? "rows:" : "columns:"), qPrintable(message)); + cellPos = &q_xx; + } +} +#endif + +void QGridLayoutEngine::maybeExpandGrid(int row, int column, Qt::Orientation orientation) +{ + invalidate(); // ### move out of here? + + if (orientation == Qt::Horizontal) + qSwap(row, column); + + if (row < rowCount() && column < columnCount()) + return; + + int oldGridRowCount = internalGridRowCount(); + int oldGridColumnCount = internalGridColumnCount(); + + q_infos[Ver].count = qMax(row + 1, rowCount()); + q_infos[Hor].count = qMax(column + 1, columnCount()); + + int newGridRowCount = internalGridRowCount(); + int newGridColumnCount = internalGridColumnCount(); + + int newGridSize = newGridRowCount * newGridColumnCount; + if (newGridSize != q_grid.count()) { + q_grid.resize(newGridSize); + + if (newGridColumnCount != oldGridColumnCount) { + for (int i = oldGridRowCount - 1; i >= 1; --i) { + for (int j = oldGridColumnCount - 1; j >= 0; --j) { + int oldIndex = (i * oldGridColumnCount) + j; + int newIndex = (i * newGridColumnCount) + j; + + Q_ASSERT(newIndex > oldIndex); + q_grid[newIndex] = q_grid[oldIndex]; + q_grid[oldIndex] = 0; + } + } + } + } +} + +void QGridLayoutEngine::regenerateGrid() +{ + q_grid.fill(0); + + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + + for (int j = item->firstRow(); j <= item->lastRow(); ++j) { + for (int k = item->firstColumn(); k <= item->lastColumn(); ++k) { + setItemAt(j, k, item); + } + } + } +} + +void QGridLayoutEngine::setItemAt(int row, int column, QGridLayoutItem *item) +{ + Q_ASSERT(row >= 0 && row < rowCount()); + Q_ASSERT(column >= 0 && column < columnCount()); + q_grid[(row * internalGridColumnCount()) + column] = item; +} + +void QGridLayoutEngine::insertOrRemoveRows(int row, int delta, Qt::Orientation orientation) +{ + int oldRowCount = rowCount(orientation); + Q_ASSERT(uint(row) <= uint(oldRowCount)); + + invalidate(); + + // appending rows (or columns) is easy + if (row == oldRowCount && delta > 0) { + maybeExpandGrid(oldRowCount + delta - 1, -1, orientation); + return; + } + + q_infos[orientation == Qt::Vertical].insertOrRemoveRows(row, delta); + + for (int i = q_items.count() - 1; i >= 0; --i) + q_items.at(i)->insertOrRemoveRows(row, delta, orientation); + + q_grid.resize(internalGridRowCount() * internalGridColumnCount()); + regenerateGrid(); +} + +void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData, const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const +{ + const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; + const QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + const QGridLayoutRowInfo &columnInfo = q_infos[orientation == Qt::Horizontal]; + LayoutSide top = (orientation == Qt::Vertical) ? Top : Left; + LayoutSide bottom = (orientation == Qt::Vertical) ? Bottom : Right; + + QStyle *style = styleInfo.style(); + QStyleOption option; + option.initFrom(styleInfo.widget()); + + const QLayoutParameter &defaultSpacing = q_defaultSpacings[orientation == Qt::Vertical]; + qreal innerSpacing = 0.0; + if (style) + innerSpacing = (qreal)style->pixelMetric(orientation == Qt::Vertical ? QStyle::PM_LayoutVerticalSpacing + : QStyle::PM_LayoutHorizontalSpacing, + &option, styleInfo.widget()); + if (innerSpacing >= 0.0) + defaultSpacing.setCachedValue(innerSpacing); + + for (int row = 0; row < rowInfo.count; ++row) { + bool rowIsEmpty = true; + bool rowIsIdenticalToPrevious = (row > 0); + + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item = itemAt(row, column, orientation); + + if (rowIsIdenticalToPrevious && item != itemAt(row - 1, column, orientation)) + rowIsIdenticalToPrevious = false; + + if (item) + rowIsEmpty = false; + } + + if ((rowIsEmpty || rowIsIdenticalToPrevious) + && rowInfo.spacings.value(row).isDefault() + && rowInfo.stretches.value(row).isDefault() + && rowInfo.boxes.value(row) == QGridLayoutBox()) + rowData->ignore.setBit(row, true); + + if (rowInfo.spacings.value(row).isUser()) { + rowData->spacings[row] = rowInfo.spacings.at(row).value(); + } else if (!defaultSpacing.isDefault()) { + rowData->spacings[row] = defaultSpacing.value(); + } + + rowData->stretches[row] = rowInfo.stretches.value(row).value(); + } + + struct RowAdHocData { + int q_row; + unsigned int q_hasButtons : 8; + unsigned int q_hasNonButtons : 8; + + inline RowAdHocData() : q_row(-1), q_hasButtons(false), q_hasNonButtons(false) {} + inline void init(int row) { + this->q_row = row; + q_hasButtons = false; + q_hasNonButtons = false; + } + inline bool hasOnlyButtons() const { return q_hasButtons && !q_hasNonButtons; } + inline bool hasOnlyNonButtons() const { return q_hasNonButtons && !q_hasButtons; } + }; + RowAdHocData lastRowAdHocData; + RowAdHocData nextToLastRowAdHocData; + RowAdHocData nextToNextToLastRowAdHocData; + + rowData->hasIgnoreFlag = false; + for (int row = 0; row < rowInfo.count; ++row) { + if (rowData->ignore.testBit(row)) + continue; + + QGridLayoutBox &rowBox = rowData->boxes[row]; + if (option.state & QStyle::State_Window) { + nextToNextToLastRowAdHocData = nextToLastRowAdHocData; + nextToLastRowAdHocData = lastRowAdHocData; + lastRowAdHocData.init(row); + } + + bool userRowStretch = rowInfo.stretches.value(row).isUser(); + int &rowStretch = rowData->stretches[row]; + + bool hasIgnoreFlag = true; + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item = itemAt(row, column, orientation); + if (item) { + int itemRow = item->firstRow(orientation); + int itemColumn = item->firstColumn(orientation); + + if (itemRow == row && itemColumn == column) { + int itemStretch = item->stretchFactor(orientation); + if (!(item->sizePolicy(orientation) & QSizePolicy::IgnoreFlag)) + hasIgnoreFlag = false; + int itemRowSpan = item->rowSpan(orientation); + + int effectiveRowSpan = 1; + for (int i = 1; i < itemRowSpan; ++i) { + if (!rowData->ignore.testBit(i)) + ++effectiveRowSpan; + } + + QGridLayoutBox *box; + if (effectiveRowSpan == 1) { + box = &rowBox; + if (!userRowStretch && itemStretch != 0) + rowStretch = qMax(rowStretch, itemStretch); + } else { + QGridLayoutMultiCellData &multiCell = + rowData->multiCellMap[qMakePair(row, effectiveRowSpan)]; + box = &multiCell.q_box; + multiCell.q_stretch = itemStretch; + } + // Items with constraints need to be passed the constraint + if (colSizes && colPositions && item->hasDynamicConstraint() && orientation == item->dynamicConstraintOrientation()) { + /* Get the width of the item by summing up the widths of the columns that it spans. + * We need to have already calculated the widths of the columns by calling + * q_columns->calculateGeometries() before hand and passing the value in the colSizes + * and colPositions parameters. + * The variable name is still colSizes even when it actually has the row sizes + */ + qreal length = colSizes[item->lastColumn(orientation)]; + if (item->columnSpan(orientation) != 1) + length += colPositions[item->lastColumn(orientation)] - colPositions[item->firstColumn(orientation)]; + box->combine(item->box(orientation, length)); + } else { + box->combine(item->box(orientation)); + } + + if (effectiveRowSpan == 1) { + QSizePolicy::ControlTypes controls = item->controlTypes(top); + if (controls & ButtonMask) + lastRowAdHocData.q_hasButtons = true; + if (controls & ~ButtonMask) + lastRowAdHocData.q_hasNonButtons = true; + } + } + } + } + if (row < rowInfo.boxes.count()) { + QGridLayoutBox rowBoxInfo = rowInfo.boxes.at(row); + rowBoxInfo.normalize(); + rowBox.q_minimumSize = qMax(rowBox.q_minimumSize, rowBoxInfo.q_minimumSize); + rowBox.q_maximumSize = qMax(rowBox.q_minimumSize, + (rowBoxInfo.q_maximumSize != FLT_MAX ? + rowBoxInfo.q_maximumSize : rowBox.q_maximumSize)); + rowBox.q_preferredSize = qBound(rowBox.q_minimumSize, + qMax(rowBox.q_preferredSize, rowBoxInfo.q_preferredSize), + rowBox.q_maximumSize); + } + if (hasIgnoreFlag) + rowData->hasIgnoreFlag = true; + } + + /* + Heuristic: Detect button boxes that don't use QSizePolicy::ButtonBox. + This is somewhat ad hoc but it usually does the trick. + */ + bool lastRowIsButtonBox = (lastRowAdHocData.hasOnlyButtons() + && nextToLastRowAdHocData.hasOnlyNonButtons()); + bool lastTwoRowsIsButtonBox = (lastRowAdHocData.hasOnlyButtons() + && nextToLastRowAdHocData.hasOnlyButtons() + && nextToNextToLastRowAdHocData.hasOnlyNonButtons() + && orientation == Qt::Vertical); + + if (defaultSpacing.isDefault()) { + int prevRow = -1; + for (int row = 0; row < rowInfo.count; ++row) { + if (rowData->ignore.testBit(row)) + continue; + + if (prevRow != -1 && !rowInfo.spacings.value(prevRow).isUser()) { + qreal &rowSpacing = rowData->spacings[prevRow]; + for (int column = 0; column < columnInfo.count; ++column) { + QGridLayoutItem *item1 = itemAt(prevRow, column, orientation); + QGridLayoutItem *item2 = itemAt(row, column, orientation); + + if (item1 && item2 && item1 != item2) { + QSizePolicy::ControlTypes controls1 = item1->controlTypes(bottom); + QSizePolicy::ControlTypes controls2 = item2->controlTypes(top); + + if (controls2 & QSizePolicy::PushButton) { + if ((row == nextToLastRowAdHocData.q_row && lastTwoRowsIsButtonBox) + || (row == lastRowAdHocData.q_row && lastRowIsButtonBox)) { + controls2 &= ~QSizePolicy::PushButton; + controls2 |= QSizePolicy::ButtonBox; + } + } + + qreal spacing = style->combinedLayoutSpacing(controls1, controls2, + orientation, &option, + styleInfo.widget()); + if (orientation == Qt::Horizontal) { + qreal width1 = rowData->boxes.at(prevRow).q_minimumSize; + qreal width2 = rowData->boxes.at(row).q_minimumSize; + QRectF rect1 = item1->geometryWithin(0.0, 0.0, width1, FLT_MAX, -1.0); + QRectF rect2 = item2->geometryWithin(0.0, 0.0, width2, FLT_MAX, -1.0); + spacing -= (width1 - (rect1.x() + rect1.width())) + rect2.x(); + } else { + const QGridLayoutBox &box1 = rowData->boxes.at(prevRow); + const QGridLayoutBox &box2 = rowData->boxes.at(row); + qreal height1 = box1.q_minimumSize; + qreal height2 = box2.q_minimumSize; + qreal rowDescent1 = fixedDescent(box1.q_minimumDescent, + box1.q_minimumAscent, height1); + qreal rowDescent2 = fixedDescent(box2.q_minimumDescent, + box2.q_minimumAscent, height2); + QRectF rect1 = item1->geometryWithin(0.0, 0.0, FLT_MAX, height1, + rowDescent1); + QRectF rect2 = item2->geometryWithin(0.0, 0.0, FLT_MAX, height2, + rowDescent2); + spacing -= (height1 - (rect1.y() + rect1.height())) + rect2.y(); + } + rowSpacing = qMax(spacing, rowSpacing); + } + } + } + prevRow = row; + } + } else if (lastRowIsButtonBox || lastTwoRowsIsButtonBox) { + /* + Even for styles that define a uniform spacing, we cheat a + bit and use the window margin as the spacing. This + significantly improves the look of dialogs. + */ + int prevRow = lastRowIsButtonBox ? nextToLastRowAdHocData.q_row + : nextToNextToLastRowAdHocData.q_row; + if (!defaultSpacing.isUser() && !rowInfo.spacings.value(prevRow).isUser()) { + qreal windowMargin = style->pixelMetric(orientation == Qt::Vertical + ? QStyle::PM_LayoutBottomMargin + : QStyle::PM_LayoutRightMargin, + &option, styleInfo.widget()); + + qreal &rowSpacing = rowData->spacings[prevRow]; + rowSpacing = qMax(windowMargin, rowSpacing); + } + } +} + +void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const +{ + if (q_cachedEffectiveFirstRows[Hor] == -1 && !q_items.isEmpty()) { + int rowCount = this->rowCount(); + int columnCount = this->columnCount(); + + q_cachedEffectiveFirstRows[Ver] = rowCount; + q_cachedEffectiveFirstRows[Hor] = columnCount; + q_cachedEffectiveLastRows[Ver] = -1; + q_cachedEffectiveLastRows[Hor] = -1; + + for (int i = q_items.count() - 1; i >= 0; --i) { + const QGridLayoutItem *item = q_items.at(i); + + for (int j = 0; j < NOrientations; ++j) { + Qt::Orientation orientation = (j == Hor) ? Qt::Horizontal : Qt::Vertical; + if (item->firstRow(orientation) < q_cachedEffectiveFirstRows[j]) + q_cachedEffectiveFirstRows[j] = item->firstRow(orientation); + if (item->lastRow(orientation) > q_cachedEffectiveLastRows[j]) + q_cachedEffectiveLastRows[j] = item->lastRow(orientation); + } + } + } +} + +void QGridLayoutEngine::ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox, + const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const +{ + rowData->reset(rowCount(orientation)); + fillRowData(rowData, styleInfo, colPositions, colSizes, orientation); + const QGridLayoutRowInfo &rowInfo = q_infos[orientation == Qt::Vertical]; + rowData->distributeMultiCells(rowInfo); + *totalBox = rowData->totalBox(0, rowCount(orientation)); + //We have items whose width depends on their height +} + +/** + returns false if the layout has contradicting constraints (i.e. some items with a horizontal + constraint and other items with a vertical constraint) + */ +bool QGridLayoutEngine::ensureDynamicConstraint() const +{ + if (q_cachedConstraintOrientation == UnknownConstraint) { + for (int i = q_items.count() - 1; i >= 0; --i) { + QGridLayoutItem *item = q_items.at(i); + if (item->hasDynamicConstraint()) { + Qt::Orientation itemConstraintOrientation = item->dynamicConstraintOrientation(); + if (q_cachedConstraintOrientation == UnknownConstraint) { + q_cachedConstraintOrientation = itemConstraintOrientation; + } else if (q_cachedConstraintOrientation != itemConstraintOrientation) { + q_cachedConstraintOrientation = UnfeasibleConstraint; + qWarning("QGridLayoutEngine: Unfeasible, cannot mix horizontal and" + " vertical constraint in the same layout"); + return false; + } + } + } + if (q_cachedConstraintOrientation == UnknownConstraint) + q_cachedConstraintOrientation = NoConstraint; + } + return true; +} + +bool QGridLayoutEngine::hasDynamicConstraint() const +{ + if (!ensureDynamicConstraint()) + return false; + return q_cachedConstraintOrientation != NoConstraint; +} + +/* + * return value is only valid if hasConstraint() returns true + */ +Qt::Orientation QGridLayoutEngine::constraintOrientation() const +{ + (void)ensureDynamicConstraint(); + return (Qt::Orientation)q_cachedConstraintOrientation; +} + +void QGridLayoutEngine::ensureGeometries(const QLayoutStyleInfo &styleInfo, + const QSizeF &size) const +{ + if (q_cachedDataForStyleInfo == styleInfo && q_cachedSize == size) + return; + + q_cachedDataForStyleInfo = styleInfo; + q_cachedSize = size; + + q_xx.resize(columnCount()); + q_widths.resize(columnCount()); + q_yy.resize(rowCount()); + q_heights.resize(rowCount()); + q_descents.resize(rowCount()); + + if (constraintOrientation() != Qt::Horizontal) { + //We might have items whose width depends on their height + ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Hor], styleInfo, NULL, NULL, Qt::Horizontal); + //Calculate column widths and positions, and put results in q_xx.data() and q_widths.data() so that we can use this information as + //constraints to find the row heights + q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), + 0, q_totalBoxes[Hor], q_infos[Hor] ); + ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], styleInfo, q_xx.data(), q_widths.data(), Qt::Vertical); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() + q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + } else { + //We have items whose height depends on their width + ensureColumnAndRowData(&q_rowData, &q_totalBoxes[Ver], styleInfo, NULL, NULL, Qt::Vertical); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() so that we can use this information as + //constraints to find the column widths + q_rowData.calculateGeometries(0, rowCount(), size.height(), q_yy.data(), q_heights.data(), + q_descents.data(), q_totalBoxes[Ver], q_infos[Ver]); + ensureColumnAndRowData(&q_columnData, &q_totalBoxes[Hor], styleInfo, q_yy.data(), q_heights.data(), Qt::Horizontal); + //Calculate row heights and positions, and put results in q_yy.data() and q_heights.data() + q_columnData.calculateGeometries(0, columnCount(), size.width(), q_xx.data(), q_widths.data(), + 0, q_totalBoxes[Hor], q_infos[Hor]); + } +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSVIEW diff --git a/src/gui/graphicsview/qgridlayoutengine_p.h b/src/gui/graphicsview/qgridlayoutengine_p.h new file mode 100644 index 0000000000..c5b35f59e8 --- /dev/null +++ b/src/gui/graphicsview/qgridlayoutengine_p.h @@ -0,0 +1,458 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRIDLAYOUTENGINE_P_H +#define QGRIDLAYOUTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the graphics view layout classes. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qalgorithms.h" +#include "qbitarray.h" +#include "qlist.h" +#include "qmap.h" +#include "qpair.h" +#include "qvector.h" +#include "qgraphicslayout_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QGraphicsLayoutItem; +class QStyle; +class QWidget; + +// ### deal with Descent in a similar way +enum { + MinimumSize = Qt::MinimumSize, + PreferredSize = Qt::PreferredSize, + MaximumSize = Qt::MaximumSize, + NSizes +}; + +// do not reorder +enum { + Hor, + Ver, + NOrientations +}; + +// do not reorder +enum LayoutSide { + Left, + Top, + Right, + Bottom +}; + +enum { + NoConstraint, + HorizontalConstraint, // Width depends on the height + VerticalConstraint, // Height depends on the width + UnknownConstraint, // need to update cache + UnfeasibleConstraint // not feasible, it be has some items with Vertical and others with Horizontal constraints +}; + +template +class QLayoutParameter +{ +public: + enum State { Default, User, Cached }; + + inline QLayoutParameter() : q_value(T()), q_state(Default) {} + inline QLayoutParameter(T value, State state = Default) : q_value(value), q_state(state) {} + + inline void setUserValue(T value) { + q_value = value; + q_state = User; + } + inline void setCachedValue(T value) const { + if (q_state != User) { + q_value = value; + q_state = Cached; + } + } + inline T value() const { return q_value; } + inline T value(T defaultValue) const { return isUser() ? q_value : defaultValue; } + inline bool isDefault() const { return q_state == Default; } + inline bool isUser() const { return q_state == User; } + inline bool isCached() const { return q_state == Cached; } + +private: + mutable T q_value; + mutable State q_state; +}; + +class QStretchParameter : public QLayoutParameter +{ +public: + QStretchParameter() : QLayoutParameter(-1) {} + +}; + +class QGridLayoutBox +{ +public: + inline QGridLayoutBox() + : q_minimumSize(0), q_preferredSize(0), q_maximumSize(FLT_MAX), + q_minimumDescent(-1), q_minimumAscent(-1) {} + + void add(const QGridLayoutBox &other, int stretch, qreal spacing); + void combine(const QGridLayoutBox &other); + void normalize(); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + // This code could use the union-struct-array trick, but a compiler + // bug prevents this from working. + qreal q_minimumSize; + qreal q_preferredSize; + qreal q_maximumSize; + qreal q_minimumDescent; + qreal q_minimumAscent; + inline qreal &q_sizes(int which) + { + qreal *t; + switch (which) { + case Qt::MinimumSize: + t = &q_minimumSize; + break; + case Qt::PreferredSize: + t = &q_preferredSize; + break; + case Qt::MaximumSize: + t = &q_maximumSize; + break; + case Qt::MinimumDescent: + t = &q_minimumDescent; + break; + case (Qt::MinimumDescent + 1): + t = &q_minimumAscent; + break; + default: + t = 0; + break; + } + return *t; + } + inline const qreal &q_sizes(int which) const + { + const qreal *t; + switch (which) { + case Qt::MinimumSize: + t = &q_minimumSize; + break; + case Qt::PreferredSize: + t = &q_preferredSize; + break; + case Qt::MaximumSize: + t = &q_maximumSize; + break; + case Qt::MinimumDescent: + t = &q_minimumDescent; + break; + case (Qt::MinimumDescent + 1): + t = &q_minimumAscent; + break; + default: + t = 0; + break; + } + return *t; + } +}; + +bool operator==(const QGridLayoutBox &box1, const QGridLayoutBox &box2); +inline bool operator!=(const QGridLayoutBox &box1, const QGridLayoutBox &box2) + { return !operator==(box1, box2); } + +class QGridLayoutMultiCellData +{ +public: + inline QGridLayoutMultiCellData() : q_stretch(-1) {} + + QGridLayoutBox q_box; + int q_stretch; +}; + +typedef QMap, QGridLayoutMultiCellData> MultiCellMap; + +class QGridLayoutRowInfo; + +class QGridLayoutRowData +{ +public: + void reset(int count); + void distributeMultiCells(const QGridLayoutRowInfo &rowInfo); + void calculateGeometries(int start, int end, qreal targetSize, qreal *positions, qreal *sizes, + qreal *descents, const QGridLayoutBox &totalBox, + const QGridLayoutRowInfo &rowInfo); + QGridLayoutBox totalBox(int start, int end) const; + void stealBox(int start, int end, int which, qreal *positions, qreal *sizes); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + + QBitArray ignore; // ### rename q_ + QVector boxes; + MultiCellMap multiCellMap; + QVector stretches; + QVector spacings; + bool hasIgnoreFlag; +}; + +class QGridLayoutEngine; + +class QGridLayoutItem +{ +public: + QGridLayoutItem(QGridLayoutEngine *engine, QGraphicsLayoutItem *layoutItem, int row, int column, + int rowSpan = 1, int columnSpan = 1, Qt::Alignment alignment = 0, + int itemAtIndex = -1); + + inline int firstRow() const { return q_firstRows[Ver]; } + inline int firstColumn() const { return q_firstRows[Hor]; } + inline int rowSpan() const { return q_rowSpans[Ver]; } + inline int columnSpan() const { return q_rowSpans[Hor]; } + inline int lastRow() const { return firstRow() + rowSpan() - 1; } + inline int lastColumn() const { return firstColumn() + columnSpan() - 1; } + + int firstRow(Qt::Orientation orientation) const; + int firstColumn(Qt::Orientation orientation) const; + int lastRow(Qt::Orientation orientation) const; + int lastColumn(Qt::Orientation orientation) const; + int rowSpan(Qt::Orientation orientation) const; + int columnSpan(Qt::Orientation orientation) const; + void setFirstRow(int row, Qt::Orientation orientation = Qt::Vertical); + void setRowSpan(int rowSpan, Qt::Orientation orientation = Qt::Vertical); + + int stretchFactor(Qt::Orientation orientation) const; + void setStretchFactor(int stretch, Qt::Orientation orientation); + + inline Qt::Alignment alignment() const { return q_alignment; } + inline void setAlignment(Qt::Alignment alignment) { q_alignment = alignment; } + + QSizePolicy::Policy sizePolicy(Qt::Orientation orientation) const; + + bool hasDynamicConstraint() const; + Qt::Orientation dynamicConstraintOrientation() const; + + QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + QGridLayoutBox box(Qt::Orientation orientation, qreal constraint = -1.0) const; + QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent) const; + + QGraphicsLayoutItem *layoutItem() const { return q_layoutItem; } + + void setGeometry(const QRectF &rect); + void transpose(); + void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical); + QSizeF effectiveMaxSize(const QSizeF &constraint) const; + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + +private: + QGridLayoutEngine *q_engine; // ### needed? + QGraphicsLayoutItem *q_layoutItem; + int q_firstRows[NOrientations]; + int q_rowSpans[NOrientations]; + int q_stretches[NOrientations]; + Qt::Alignment q_alignment; +}; + +class QGridLayoutRowInfo +{ +public: + inline QGridLayoutRowInfo() : count(0) {} + + void insertOrRemoveRows(int row, int delta); + +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + + int count; + QVector stretches; + QVector > spacings; + QVector alignments; + QVector boxes; +}; + +class QGridLayoutEngine +{ +public: + QGridLayoutEngine(); + inline ~QGridLayoutEngine() { qDeleteAll(q_items); } + + int rowCount(Qt::Orientation orientation) const; + int columnCount(Qt::Orientation orientation) const; + inline int rowCount() const { return q_infos[Ver].count; } + inline int columnCount() const { return q_infos[Hor].count; } + // returns the number of items inserted, which may be less than (rowCount * columnCount) + int itemCount() const; + QGridLayoutItem *itemAt(int index) const; + int indexOf(QGraphicsLayoutItem *item) const; + + int effectiveFirstRow(Qt::Orientation orientation = Qt::Vertical) const; + int effectiveLastRow(Qt::Orientation orientation = Qt::Vertical) const; + + void setSpacing(qreal spacing, Qt::Orientations orientations); + qreal spacing(const QLayoutStyleInfo &styleInfo, Qt::Orientation orientation) const; + // ### setSpacingAfterRow(), spacingAfterRow() + void setRowSpacing(int row, qreal spacing, Qt::Orientation orientation = Qt::Vertical); + qreal rowSpacing(int row, Qt::Orientation orientation = Qt::Vertical) const; + + void setRowStretchFactor(int row, int stretch, Qt::Orientation orientation = Qt::Vertical); + int rowStretchFactor(int row, Qt::Orientation orientation = Qt::Vertical) const; + + void setStretchFactor(QGraphicsLayoutItem *layoutItem, int stretch, + Qt::Orientation orientation); + int stretchFactor(QGraphicsLayoutItem *layoutItem, Qt::Orientation orientation) const; + + void setRowSizeHint(Qt::SizeHint which, int row, qreal size, + Qt::Orientation orientation = Qt::Vertical); + qreal rowSizeHint(Qt::SizeHint which, int row, + Qt::Orientation orientation = Qt::Vertical) const; + + void setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation); + Qt::Alignment rowAlignment(int row, Qt::Orientation orientation) const; + + void setAlignment(QGraphicsLayoutItem *layoutItem, Qt::Alignment alignment); + Qt::Alignment alignment(QGraphicsLayoutItem *layoutItem) const; + Qt::Alignment effectiveAlignment(const QGridLayoutItem *layoutItem) const; + + + void insertItem(QGridLayoutItem *item, int index); + void addItem(QGridLayoutItem *item); + void removeItem(QGridLayoutItem *item); + QGridLayoutItem *findLayoutItem(QGraphicsLayoutItem *layoutItem) const; + QGridLayoutItem *itemAt(int row, int column, Qt::Orientation orientation = Qt::Vertical) const; + inline void insertRow(int row, Qt::Orientation orientation = Qt::Vertical) + { insertOrRemoveRows(row, +1, orientation); } + inline void removeRows(int row, int count, Qt::Orientation orientation) + { insertOrRemoveRows(row, -count, orientation); } + + void invalidate(); + void setGeometries(const QLayoutStyleInfo &styleInfo, const QRectF &contentsGeometry); + QRectF cellRect(const QLayoutStyleInfo &styleInfo, const QRectF &contentsGeometry, int row, + int column, int rowSpan, int columnSpan) const; + QSizeF sizeHint(const QLayoutStyleInfo &styleInfo, Qt::SizeHint which, + const QSizeF &constraint) const; + + // heightForWidth / widthForHeight support + QSizeF dynamicallyConstrainedSizeHint(Qt::SizeHint which, const QSizeF &constraint) const; + bool ensureDynamicConstraint() const; + bool hasDynamicConstraint() const; + Qt::Orientation constraintOrientation() const; + + + QSizePolicy::ControlTypes controlTypes(LayoutSide side) const; + void transpose(); + void setVisualDirection(Qt::LayoutDirection direction); + Qt::LayoutDirection visualDirection() const; +#ifdef QT_DEBUG + void dump(int indent = 0) const; +#endif + +private: + static int grossRoundUp(int n) { return ((n + 2) | 0x3) - 2; } + + void maybeExpandGrid(int row, int column, Qt::Orientation orientation = Qt::Vertical); + void regenerateGrid(); + inline int internalGridRowCount() const { return grossRoundUp(rowCount()); } + inline int internalGridColumnCount() const { return grossRoundUp(columnCount()); } + void setItemAt(int row, int column, QGridLayoutItem *item); + void insertOrRemoveRows(int row, int delta, Qt::Orientation orientation = Qt::Vertical); + void fillRowData(QGridLayoutRowData *rowData, const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation = Qt::Vertical) const; + void ensureEffectiveFirstAndLastRows() const; + void ensureColumnAndRowData(QGridLayoutRowData *rowData, QGridLayoutBox *totalBox, + const QLayoutStyleInfo &styleInfo, + qreal *colPositions, qreal *colSizes, + Qt::Orientation orientation) const; + + void ensureGeometries(const QLayoutStyleInfo &styleInfo, const QSizeF &size) const; + + // User input + QVector q_grid; + QList q_items; + QLayoutParameter q_defaultSpacings[NOrientations]; + QGridLayoutRowInfo q_infos[NOrientations]; + Qt::LayoutDirection m_visualDirection; + + // Lazily computed from the above user input + mutable int q_cachedEffectiveFirstRows[NOrientations]; + mutable int q_cachedEffectiveLastRows[NOrientations]; + mutable quint8 q_cachedConstraintOrientation : 3; + + // Layout item input + mutable QLayoutStyleInfo q_cachedDataForStyleInfo; + mutable QGridLayoutRowData q_columnData; + mutable QGridLayoutRowData q_rowData; + mutable QGridLayoutBox q_totalBoxes[NOrientations]; + + // Output + mutable QSizeF q_cachedSize; + mutable QVector q_xx; + mutable QVector q_yy; + mutable QVector q_widths; + mutable QVector q_heights; + mutable QVector q_descents; + + friend class QGridLayoutItem; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/graphicsview/qsimplex_p.cpp b/src/gui/graphicsview/qsimplex_p.cpp new file mode 100644 index 0000000000..d2d9646b90 --- /dev/null +++ b/src/gui/graphicsview/qsimplex_p.cpp @@ -0,0 +1,673 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsimplex_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QSimplex + + The QSimplex class is a Linear Programming problem solver based on the two-phase + simplex method. + + It takes a set of QSimplexConstraints as its restrictive constraints and an + additional QSimplexConstraint as its objective function. Then methods to maximize + and minimize the problem solution are provided. + + The two-phase simplex method is based on the following steps: + First phase: + 1.a) Modify the original, complex, and possibly not feasible problem, into a new, + easy to solve problem. + 1.b) Set as the objective of the new problem, a feasible solution for the original + complex problem. + 1.c) Run simplex to optimize the modified problem and check whether a solution for + the original problem exists. + + Second phase: + 2.a) Go back to the original problem with the feasibl (but not optimal) solution + found in the first phase. + 2.b) Set the original objective. + 3.c) Run simplex to optimize the original problem towards its optimal solution. +*/ + +/*! + \internal +*/ +QSimplex::QSimplex() : objective(0), rows(0), columns(0), firstArtificial(0), matrix(0) +{ +} + +/*! + \internal +*/ +QSimplex::~QSimplex() +{ + clearDataStructures(); +} + +/*! + \internal +*/ +void QSimplex::clearDataStructures() +{ + if (matrix == 0) + return; + + // Matrix + rows = 0; + columns = 0; + firstArtificial = 0; + free(matrix); + matrix = 0; + + // Constraints + for (int i = 0; i < constraints.size(); ++i) { + delete constraints[i]->helper.first; + delete constraints[i]->artificial; + delete constraints[i]; + } + constraints.clear(); + + // Other + variables.clear(); + objective = 0; +} + +/*! + \internal + Sets the new constraints in the simplex solver and returns whether the problem + is feasible. + + This method sets the new constraints, normalizes them, creates the simplex matrix + and runs the first simplex phase. +*/ +bool QSimplex::setConstraints(const QList newConstraints) +{ + //////////////////////////// + // Reset to initial state // + //////////////////////////// + clearDataStructures(); + + if (newConstraints.isEmpty()) + return true; // we are ok with no constraints + + // Make deep copy of constraints. We need this copy because we may change + // them in the simplification method. + for (int i = 0; i < newConstraints.size(); ++i) { + QSimplexConstraint *c = new QSimplexConstraint; + c->constant = newConstraints[i]->constant; + c->ratio = newConstraints[i]->ratio; + c->variables = newConstraints[i]->variables; + constraints << c; + } + + // Remove constraints of type Var == K and replace them for their value. + if (!simplifyConstraints(&constraints)) { + qWarning() << "QSimplex: No feasible solution!"; + clearDataStructures(); + return false; + } + + /////////////////////////////////////// + // Prepare variables and constraints // + /////////////////////////////////////// + + // Set Variables direct mapping. + // "variables" is a list that provides a stable, indexed list of all variables + // used in this problem. + QSet variablesSet; + for (int i = 0; i < constraints.size(); ++i) + variablesSet += \ + QSet::fromList(constraints[i]->variables.keys()); + variables = variablesSet.toList(); + + // Set Variables reverse mapping + // We also need to be able to find the index for a given variable, to do that + // we store in each variable its index. + for (int i = 0; i < variables.size(); ++i) { + // The variable "0" goes at the column "1", etc... + variables[i]->index = i + 1; + } + + // Normalize Constraints + // In this step, we prepare the constraints in two ways: + // Firstly, we modify all constraints of type "LessOrEqual" or "MoreOrEqual" + // by the adding slack or surplus variables and making them "Equal" constraints. + // Secondly, we need every single constraint to have a direct, easy feasible + // solution. Constraints that have slack variables are already easy to solve, + // to all the others we add artificial variables. + // + // At the end we modify the constraints as follows: + // - LessOrEqual: SLACK variable is added. + // - Equal: ARTIFICIAL variable is added. + // - More or Equal: ARTIFICIAL and SURPLUS variables are added. + int variableIndex = variables.size(); + QList artificialList; + + for (int i = 0; i < constraints.size(); ++i) { + QSimplexVariable *slack; + QSimplexVariable *surplus; + QSimplexVariable *artificial; + + Q_ASSERT(constraints[i]->helper.first == 0); + Q_ASSERT(constraints[i]->artificial == 0); + + switch(constraints[i]->ratio) { + case QSimplexConstraint::LessOrEqual: + slack = new QSimplexVariable; + slack->index = ++variableIndex; + constraints[i]->helper.first = slack; + constraints[i]->helper.second = 1.0; + break; + case QSimplexConstraint::MoreOrEqual: + surplus = new QSimplexVariable; + surplus->index = ++variableIndex; + constraints[i]->helper.first = surplus; + constraints[i]->helper.second = -1.0; + // fall through + case QSimplexConstraint::Equal: + artificial = new QSimplexVariable; + constraints[i]->artificial = artificial; + artificialList += constraints[i]->artificial; + break; + } + } + + // All original, slack and surplus have already had its index set + // at this point. We now set the index of the artificial variables + // as to ensure they are at the end of the variable list and therefore + // can be easily removed at the end of this method. + firstArtificial = variableIndex + 1; + for (int i = 0; i < artificialList.size(); ++i) + artificialList[i]->index = ++variableIndex; + artificialList.clear(); + + ///////////////////////////// + // Fill the Simplex matrix // + ///////////////////////////// + + // One for each variable plus the Basic and BFS columns (first and last) + columns = variableIndex + 2; + // One for each constraint plus the objective function + rows = constraints.size() + 1; + + matrix = (qreal *)malloc(sizeof(qreal) * columns * rows); + if (!matrix) { + qWarning() << "QSimplex: Unable to allocate memory!"; + return false; + } + for (int i = columns * rows - 1; i >= 0; --i) + matrix[i] = 0.0; + + // Fill Matrix + for (int i = 1; i <= constraints.size(); ++i) { + QSimplexConstraint *c = constraints[i - 1]; + + if (c->artificial) { + // Will use artificial basic variable + setValueAt(i, 0, c->artificial->index); + setValueAt(i, c->artificial->index, 1.0); + + if (c->helper.second != 0.0) { + // Surplus variable + setValueAt(i, c->helper.first->index, c->helper.second); + } + } else { + // Slack is used as the basic variable + Q_ASSERT(c->helper.second == 1.0); + setValueAt(i, 0, c->helper.first->index); + setValueAt(i, c->helper.first->index, 1.0); + } + + QHash::const_iterator iter; + for (iter = c->variables.constBegin(); + iter != c->variables.constEnd(); + ++iter) { + setValueAt(i, iter.key()->index, iter.value()); + } + + setValueAt(i, columns - 1, c->constant); + } + + // Set objective for the first-phase Simplex. + // Z = -1 * sum_of_artificial_vars + for (int j = firstArtificial; j < columns - 1; ++j) + setValueAt(0, j, 1.0); + + // Maximize our objective (artificial vars go to zero) + solveMaxHelper(); + + // If there is a solution where the sum of all artificial + // variables is zero, then all of them can be removed and yet + // we will have a feasible (but not optimal) solution for the + // original problem. + // Otherwise, we clean up our structures and report there is + // no feasible solution. + if ((valueAt(0, columns - 1) != 0.0) && (qAbs(valueAt(0, columns - 1)) > 0.00001)) { + qWarning() << "QSimplex: No feasible solution!"; + clearDataStructures(); + return false; + } + + // Remove artificial variables. We already have a feasible + // solution for the first problem, thus we don't need them + // anymore. + clearColumns(firstArtificial, columns - 2); + + return true; +} + +/*! + \internal + + Run simplex on the current matrix with the current objective. + + This is the iterative method. The matrix lines are combined + as to modify the variable values towards the best solution possible. + The method returns when the matrix is in the optimal state. +*/ +void QSimplex::solveMaxHelper() +{ + reducedRowEchelon(); + while (iterate()) ; +} + +/*! + \internal +*/ +void QSimplex::setObjective(QSimplexConstraint *newObjective) +{ + objective = newObjective; +} + +/*! + \internal +*/ +void QSimplex::clearRow(int rowIndex) +{ + qreal *item = matrix + rowIndex * columns; + for (int i = 0; i < columns; ++i) + item[i] = 0.0; +} + +/*! + \internal +*/ +void QSimplex::clearColumns(int first, int last) +{ + for (int i = 0; i < rows; ++i) { + qreal *row = matrix + i * columns; + for (int j = first; j <= last; ++j) + row[j] = 0.0; + } +} + +/*! + \internal +*/ +void QSimplex::dumpMatrix() +{ + qDebug("---- Simplex Matrix ----\n"); + + QString str(QLatin1String(" ")); + for (int j = 0; j < columns; ++j) + str += QString::fromAscii(" <%1 >").arg(j, 2); + qDebug("%s", qPrintable(str)); + for (int i = 0; i < rows; ++i) { + str = QString::fromAscii("Row %1:").arg(i, 2); + + qreal *row = matrix + i * columns; + for (int j = 0; j < columns; ++j) + str += QString::fromAscii("%1").arg(row[j], 7, 'f', 2); + qDebug("%s", qPrintable(str)); + } + qDebug("------------------------\n"); +} + +/*! + \internal +*/ +void QSimplex::combineRows(int toIndex, int fromIndex, qreal factor) +{ + if (!factor) + return; + + qreal *from = matrix + fromIndex * columns; + qreal *to = matrix + toIndex * columns; + + for (int j = 1; j < columns; ++j) { + qreal value = from[j]; + + // skip to[j] = to[j] + factor*0.0 + if (value == 0.0) + continue; + + to[j] += factor * value; + + // ### Avoid Numerical errors + if (qAbs(to[j]) < 0.0000000001) + to[j] = 0.0; + } +} + +/*! + \internal +*/ +int QSimplex::findPivotColumn() +{ + qreal min = 0; + int minIndex = -1; + + for (int j = 0; j < columns-1; ++j) { + if (valueAt(0, j) < min) { + min = valueAt(0, j); + minIndex = j; + } + } + + return minIndex; +} + +/*! + \internal + + For a given pivot column, find the pivot row. That is, the row with the + minimum associated "quotient" where: + + - quotient is the division of the value in the last column by the value + in the pivot column. + - rows with value less or equal to zero are ignored + - if two rows have the same quotient, lines are chosen based on the + highest variable index (value in the first column) + + The last condition avoids a bug where artificial variables would be + left behind for the second-phase simplex, and with 'good' + constraints would be removed before it, what would lead to incorrect + results. +*/ +int QSimplex::pivotRowForColumn(int column) +{ + qreal min = qreal(999999999999.0); // ### + int minIndex = -1; + + for (int i = 1; i < rows; ++i) { + qreal divisor = valueAt(i, column); + if (divisor <= 0) + continue; + + qreal quotient = valueAt(i, columns - 1) / divisor; + if (quotient < min) { + min = quotient; + minIndex = i; + } else if ((quotient == min) && (valueAt(i, 0) > valueAt(minIndex, 0))) { + minIndex = i; + } + } + + return minIndex; +} + +/*! + \internal +*/ +void QSimplex::reducedRowEchelon() +{ + for (int i = 1; i < rows; ++i) { + int factorInObjectiveRow = valueAt(i, 0); + combineRows(0, i, -1 * valueAt(0, factorInObjectiveRow)); + } +} + +/*! + \internal + + Does one iteration towards a better solution for the problem. + See 'solveMaxHelper'. +*/ +bool QSimplex::iterate() +{ + // Find Pivot column + int pivotColumn = findPivotColumn(); + if (pivotColumn == -1) + return false; + + // Find Pivot row for column + int pivotRow = pivotRowForColumn(pivotColumn); + if (pivotRow == -1) { + qWarning() << "QSimplex: Unbounded problem!"; + return false; + } + + // Normalize Pivot Row + qreal pivot = valueAt(pivotRow, pivotColumn); + if (pivot != 1.0) + combineRows(pivotRow, pivotRow, (1.0 - pivot) / pivot); + + // Update other rows + for (int row=0; row < rows; ++row) { + if (row == pivotRow) + continue; + + combineRows(row, pivotRow, -1 * valueAt(row, pivotColumn)); + } + + // Update first column + setValueAt(pivotRow, 0, pivotColumn); + + // dumpMatrix(); + // qDebug("------------ end of iteration --------------\n"); + return true; +} + +/*! + \internal + + Both solveMin and solveMax are interfaces to this method. + + The enum solverFactor admits 2 values: Minimum (-1) and Maximum (+1). + + This method sets the original objective and runs the second phase + Simplex to obtain the optimal solution for the problem. As the internal + simplex solver is only able to _maximize_ objectives, we handle the + minimization case by inverting the original objective and then + maximizing it. +*/ +qreal QSimplex::solver(solverFactor factor) +{ + // Remove old objective + clearRow(0); + + // Set new objective in the first row of the simplex matrix + qreal resultOffset = 0; + QHash::const_iterator iter; + for (iter = objective->variables.constBegin(); + iter != objective->variables.constEnd(); + ++iter) { + + // Check if the variable was removed in the simplification process. + // If so, we save its offset to the objective function and skip adding + // it to the matrix. + if (iter.key()->index == -1) { + resultOffset += iter.value() * iter.key()->result; + continue; + } + + setValueAt(0, iter.key()->index, -1 * factor * iter.value()); + } + + solveMaxHelper(); + collectResults(); + +#ifdef QT_DEBUG + for (int i = 0; i < constraints.size(); ++i) { + Q_ASSERT(constraints[i]->isSatisfied()); + } +#endif + + // Return the value calculated by the simplex plus the value of the + // fixed variables. + return (factor * valueAt(0, columns - 1)) + resultOffset; +} + +/*! + \internal + Minimize the original objective. +*/ +qreal QSimplex::solveMin() +{ + return solver(Minimum); +} + +/*! + \internal + Maximize the original objective. +*/ +qreal QSimplex::solveMax() +{ + return solver(Maximum); +} + +/*! + \internal + + Reads results from the simplified matrix and saves them in the + "result" member of each QSimplexVariable. +*/ +void QSimplex::collectResults() +{ + // All variables are zero unless overridden below. + + // ### Is this really needed? Is there any chance that an + // important variable remains as non-basic at the end of simplex? + for (int i = 0; i < variables.size(); ++i) + variables[i]->result = 0; + + // Basic variables + // Update the variable indicated in the first column with the value + // in the last column. + for (int i = 1; i < rows; ++i) { + int index = valueAt(i, 0) - 1; + if (index < variables.size()) + variables[index]->result = valueAt(i, columns - 1); + } +} + +/*! + \internal + + Looks for single-valued variables and remove them from the constraints list. +*/ +bool QSimplex::simplifyConstraints(QList *constraints) +{ + QHash results; // List of single-valued variables + bool modified = true; // Any chance more optimization exists? + + while (modified) { + modified = false; + + // For all constraints + QList::iterator iter = constraints->begin(); + while (iter != constraints->end()) { + QSimplexConstraint *c = *iter; + if ((c->ratio == QSimplexConstraint::Equal) && (c->variables.count() == 1)) { + // Check whether this is a constraint of type Var == K + // If so, save its value to "results". + QSimplexVariable *variable = c->variables.constBegin().key(); + qreal result = c->constant / c->variables.value(variable); + + results.insert(variable, result); + variable->result = result; + variable->index = -1; + modified = true; + + } + + // Replace known values among their variables + QHash::const_iterator r; + for (r = results.constBegin(); r != results.constEnd(); ++r) { + if (c->variables.contains(r.key())) { + c->constant -= r.value() * c->variables.take(r.key()); + modified = true; + } + } + + // Keep it normalized + if (c->constant < 0) + c->invert(); + + if (c->variables.isEmpty()) { + // If constraint became empty due to substitution, delete it. + if (c->isSatisfied() == false) + // We must ensure that the constraint soon to be deleted would not + // make the problem unfeasible if left behind. If that's the case, + // we return false so the simplex solver can properly report that. + return false; + + delete c; + iter = constraints->erase(iter); + } else { + ++iter; + } + } + } + + return true; +} + +void QSimplexConstraint::invert() +{ + constant = -constant; + ratio = Ratio(2 - ratio); + + QHash::iterator iter; + for (iter = variables.begin(); iter != variables.end(); ++iter) { + iter.value() = -iter.value(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/graphicsview/qsimplex_p.h b/src/gui/graphicsview/qsimplex_p.h new file mode 100644 index 0000000000..e6eced311e --- /dev/null +++ b/src/gui/graphicsview/qsimplex_p.h @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIMPLEX_P_H +#define QSIMPLEX_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 +#include + +QT_BEGIN_NAMESPACE + +struct QSimplexVariable +{ + QSimplexVariable() : result(0), index(0) {} + + qreal result; + int index; +}; + + +/*! + \internal + + Representation of a LP constraint like: + + (c1 * X1) + (c2 * X2) + ... = K + or <= K + or >= K + + Where (ci, Xi) are the pairs in "variables" and K the real "constant". +*/ +struct QSimplexConstraint +{ + QSimplexConstraint() : constant(0), ratio(Equal), artificial(0) {} + + enum Ratio { + LessOrEqual = 0, + Equal, + MoreOrEqual + }; + + QHash variables; + qreal constant; + Ratio ratio; + + QPair helper; + QSimplexVariable * artificial; + + void invert(); + + bool isSatisfied() { + qreal leftHandSide(0); + + QHash::const_iterator iter; + for (iter = variables.constBegin(); iter != variables.constEnd(); ++iter) { + leftHandSide += iter.value() * iter.key()->result; + } + + Q_ASSERT(constant > 0 || qFuzzyCompare(1, 1 + constant)); + + if ((leftHandSide == constant) || qAbs(leftHandSide - constant) < 0.0000001) + return true; + + switch (ratio) { + case LessOrEqual: + return leftHandSide < constant; + case MoreOrEqual: + return leftHandSide > constant; + default: + return false; + } + } + +#ifdef QT_DEBUG + QString toString() { + QString result; + result += QString::fromAscii("-- QSimplexConstraint %1 --").arg(quintptr(this), 0, 16); + + QHash::const_iterator iter; + for (iter = variables.constBegin(); iter != variables.constEnd(); ++iter) { + result += QString::fromAscii(" %1 x %2").arg(iter.value()).arg(quintptr(iter.key()), 0, 16); + } + + switch (ratio) { + case LessOrEqual: + result += QString::fromAscii(" (less) <= %1").arg(constant); + break; + case MoreOrEqual: + result += QString::fromAscii(" (more) >= %1").arg(constant); + break; + default: + result += QString::fromAscii(" (eqal) == %1").arg(constant); + } + + return result; + } +#endif +}; + +class QSimplex +{ +public: + QSimplex(); + virtual ~QSimplex(); + + qreal solveMin(); + qreal solveMax(); + + bool setConstraints(const QList constraints); + void setObjective(QSimplexConstraint *objective); + + void dumpMatrix(); + +private: + // Matrix handling + qreal valueAt(int row, int column); + void setValueAt(int row, int column, qreal value); + void clearRow(int rowIndex); + void clearColumns(int first, int last); + void combineRows(int toIndex, int fromIndex, qreal factor); + + // Simplex + bool simplifyConstraints(QList *constraints); + int findPivotColumn(); + int pivotRowForColumn(int column); + void reducedRowEchelon(); + bool iterate(); + + // Helpers + void clearDataStructures(); + void solveMaxHelper(); + enum solverFactor { Minimum = -1, Maximum = 1 }; + qreal solver(solverFactor factor); + void collectResults(); + + QList constraints; + QList variables; + QSimplexConstraint *objective; + + int rows; + int columns; + int firstArtificial; + + qreal *matrix; +}; + +inline qreal QSimplex::valueAt(int rowIndex, int columnIndex) +{ + return matrix[rowIndex * columns + columnIndex]; +} + +inline void QSimplex::setValueAt(int rowIndex, int columnIndex, qreal value) +{ + matrix[rowIndex * columns + columnIndex] = value; +} + +QT_END_NAMESPACE + +#endif // QSIMPLEX_P_H diff --git a/src/gui/gui.pro b/src/gui/gui.pro new file mode 100644 index 0000000000..8f72fead8d --- /dev/null +++ b/src/gui/gui.pro @@ -0,0 +1,223 @@ +TARGET = QtGui +QPRO_PWD = $$PWD +QT = core +DEFINES += QT_BUILD_GUI_LIB QT_NO_USING_NAMESPACE +win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x65000000 +irix-cc*:QMAKE_CXXFLAGS += -no_prelink -ptused + +!win32:!embedded:!qpa:!mac:!symbian:CONFIG += x11 + +unix|win32-g++*:QMAKE_PKGCONFIG_REQUIRES = QtCore + +include(../qbase.pri) + +contains(QT_CONFIG, x11sm):CONFIG += x11sm + +#platforms +x11:include(kernel/x11.pri) +mac:include(kernel/mac.pri) +win32:include(kernel/win.pri) +embedded:include(embedded/embedded.pri) +symbian { + include(kernel/symbian.pri) + include(s60framework/s60framework.pri) +} + +#modules +include(animation/animation.pri) +include(kernel/kernel.pri) +include(image/image.pri) +include(painting/painting.pri) +include(text/text.pri) +include(styles/styles.pri) +include(widgets/widgets.pri) +include(dialogs/dialogs.pri) +include(accessible/accessible.pri) +include(itemviews/itemviews.pri) +include(inputmethod/inputmethod.pri) +include(graphicsview/graphicsview.pri) +include(util/util.pri) +include(statemachine/statemachine.pri) +include(math3d/math3d.pri) +include(effects/effects.pri) + +include(egl/egl.pri) +win32:!wince*: DEFINES += QT_NO_EGL +embedded: QT += network + +QMAKE_LIBS += $$QMAKE_LIBS_GUI + +contains(DEFINES,QT_EVAL):include($$QT_SOURCE_TREE/src/corelib/eval.pri) + +QMAKE_DYNAMIC_LIST_FILE = $$PWD/QtGui.dynlist + +DEFINES += Q_INTERNAL_QAPP_SRC +symbian { + TARGET.UID3=0x2001B2DD + + # ro-section in gui can exceed default allocated space, so move rw-section a little further + QMAKE_LFLAGS.ARMCC += --rw-base 0x800000 + QMAKE_LFLAGS.GCCE += -Tdata 0x800000 +} + +neon:*-g++* { + DEFINES += QT_HAVE_NEON + HEADERS += $$NEON_HEADERS + + DRAWHELPER_NEON_ASM_FILES = $$NEON_ASM + + neon_compiler.commands = $$QMAKE_CXX -c + neon_compiler.commands += $(CXXFLAGS) -mfpu=neon $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + neon_compiler.dependency_type = TYPE_C + neon_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + neon_compiler.input = DRAWHELPER_NEON_ASM_FILES NEON_SOURCES + neon_compiler.variable_out = OBJECTS + neon_compiler.name = compiling[neon] ${QMAKE_FILE_IN} + silent:neon_compiler.commands = @echo compiling[neon] ${QMAKE_FILE_IN} && $$neon_compiler.commands + QMAKE_EXTRA_COMPILERS += neon_compiler +} + +win32:!contains(QT_CONFIG, directwrite) { + DEFINES += QT_NO_DIRECTWRITE +} + +contains(QMAKE_MAC_XARCH, no) { + DEFINES += QT_NO_MAC_XARCH +} else { + win32-g++*|!win32:!win32-icc*:!macx-icc* { + mmx { + mmx_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + mmx_compiler.commands += -Xarch_i386 -mmmx + mmx_compiler.commands += -Xarch_x86_64 -mmmx + } else { + mmx_compiler.commands += -mmmx + } + + mmx_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + mmx_compiler.dependency_type = TYPE_C + mmx_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + mmx_compiler.input = MMX_SOURCES + mmx_compiler.variable_out = OBJECTS + mmx_compiler.name = compiling[mmx] ${QMAKE_FILE_IN} + silent:mmx_compiler.commands = @echo compiling[mmx] ${QMAKE_FILE_IN} && $$mmx_compiler.commands + QMAKE_EXTRA_COMPILERS += mmx_compiler + } + 3dnow { + mmx3dnow_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + mmx3dnow_compiler.commands += -Xarch_i386 -m3dnow -Xarch_i386 -mmmx + mmx3dnow_compiler.commands += -Xarch_x86_64 -m3dnow -Xarch_x86_64 -mmmx + } else { + mmx3dnow_compiler.commands += -m3dnow -mmmx + } + + mmx3dnow_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + mmx3dnow_compiler.dependency_type = TYPE_C + mmx3dnow_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + mmx3dnow_compiler.input = MMX3DNOW_SOURCES + mmx3dnow_compiler.variable_out = OBJECTS + mmx3dnow_compiler.name = compiling[mmx3dnow] ${QMAKE_FILE_IN} + silent:mmx3dnow_compiler.commands = @echo compiling[mmx3dnow] ${QMAKE_FILE_IN} && $$mmx3dnow_compiler.commands + QMAKE_EXTRA_COMPILERS += mmx3dnow_compiler + sse { + sse3dnow_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + sse3dnow_compiler.commands += -Xarch_i386 -m3dnow -Xarch_i386 -msse + sse3dnow_compiler.commands += -Xarch_x86_64 -m3dnow -Xarch_x86_64 -msse + } else { + sse3dnow_compiler.commands += -m3dnow -msse + } + + sse3dnow_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + sse3dnow_compiler.dependency_type = TYPE_C + sse3dnow_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + sse3dnow_compiler.input = SSE3DNOW_SOURCES + sse3dnow_compiler.variable_out = OBJECTS + sse3dnow_compiler.name = compiling[sse3dnow] ${QMAKE_FILE_IN} + silent:sse3dnow_compiler.commands = @echo compiling[sse3dnow] ${QMAKE_FILE_IN} && $$sse3dnow_compiler.commands + QMAKE_EXTRA_COMPILERS += sse3dnow_compiler + } + } + sse { + sse_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + sse_compiler.commands += -Xarch_i386 -msse + sse_compiler.commands += -Xarch_x86_64 -msse + } else { + sse_compiler.commands += -msse + } + + sse_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + sse_compiler.dependency_type = TYPE_C + sse_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + sse_compiler.input = SSE_SOURCES + sse_compiler.variable_out = OBJECTS + sse_compiler.name = compiling[sse] ${QMAKE_FILE_IN} + silent:sse_compiler.commands = @echo compiling[sse] ${QMAKE_FILE_IN} && $$sse_compiler.commands + QMAKE_EXTRA_COMPILERS += sse_compiler + } + sse2 { + sse2_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + sse2_compiler.commands += -Xarch_i386 -msse2 + sse2_compiler.commands += -Xarch_x86_64 -msse2 + } else { + sse2_compiler.commands += -msse2 + } + + sse2_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + sse2_compiler.dependency_type = TYPE_C + sse2_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + sse2_compiler.input = SSE2_SOURCES + sse2_compiler.variable_out = OBJECTS + sse2_compiler.name = compiling[sse2] ${QMAKE_FILE_IN} + silent:sse2_compiler.commands = @echo compiling[sse2] ${QMAKE_FILE_IN} && $$sse2_compiler.commands + QMAKE_EXTRA_COMPILERS += sse2_compiler + } + ssse3 { + ssse3_compiler.commands = $$QMAKE_CXX -c -Winline + + mac { + ssse3_compiler.commands += -Xarch_i386 -mssse3 + ssse3_compiler.commands += -Xarch_x86_64 -mssse3 + } else { + ssse3_compiler.commands += -mssse3 + } + + ssse3_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + ssse3_compiler.dependency_type = TYPE_C + ssse3_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + ssse3_compiler.input = SSSE3_SOURCES + ssse3_compiler.variable_out = OBJECTS + ssse3_compiler.name = compiling[ssse3] ${QMAKE_FILE_IN} + silent:ssse3_compiler.commands = @echo compiling[ssse3] ${QMAKE_FILE_IN} && $$ssse3_compiler.commands + QMAKE_EXTRA_COMPILERS += ssse3_compiler + } + iwmmxt { + iwmmxt_compiler.commands = $$QMAKE_CXX -c -Winline + iwmmxt_compiler.commands += -mcpu=iwmmxt + iwmmxt_compiler.commands += $(CXXFLAGS) $(INCPATH) ${QMAKE_FILE_IN} -o ${QMAKE_FILE_OUT} + iwmmxt_compiler.dependency_type = TYPE_C + iwmmxt_compiler.output = ${QMAKE_VAR_OBJECTS_DIR}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_OBJ)} + iwmmxt_compiler.input = IWMMXT_SOURCES + iwmmxt_compiler.variable_out = OBJECTS + iwmmxt_compiler.name = compiling[iwmmxt] ${QMAKE_FILE_IN} + silent:iwmmxt_compiler.commands = @echo compiling[iwmmxt] ${QMAKE_FILE_IN} && $$iwmmxt_compiler.commands + QMAKE_EXTRA_COMPILERS += iwmmxt_compiler + } + } else { + mmx: SOURCES += $$MMX_SOURCES + 3dnow: SOURCES += $$MMX3DNOW_SOURCES + 3dnow:sse: SOURCES += $$SSE3DNOW_SOURCES + sse: SOURCES += $$SSE_SOURCES + sse2: SOURCES += $$SSE2_SOURCES + ssse3: SOURCES += $$SSSE3_SOURCES + iwmmxt: SOURCES += $$IWMMXT_SOURCES + } +} diff --git a/src/gui/image/image.pri b/src/gui/image/image.pri new file mode 100644 index 0000000000..72738c9fa8 --- /dev/null +++ b/src/gui/image/image.pri @@ -0,0 +1,115 @@ +# -*-mode:sh-*- +# Qt image handling + +# Qt kernel module + +HEADERS += \ + image/qbitmap.h \ + image/qicon.h \ + image/qicon_p.h \ + image/qiconloader_p.h \ + image/qiconengine.h \ + image/qiconengineplugin.h \ + image/qimage.h \ + image/qimage_p.h \ + image/qimageiohandler.h \ + image/qimagereader.h \ + image/qimagewriter.h \ + image/qmovie.h \ + image/qnativeimage_p.h \ + image/qpaintengine_pic_p.h \ + image/qpicture.h \ + image/qpicture_p.h \ + image/qpictureformatplugin.h \ + image/qpixmap.h \ + image/qpixmap_raster_p.h \ + image/qpixmap_blitter_p.h \ + image/qpixmapcache.h \ + image/qpixmapcache_p.h \ + image/qpixmapdata_p.h \ + image/qpixmapdatafactory_p.h \ + image/qpixmapfilter_p.h \ + image/qimagepixmapcleanuphooks_p.h \ + image/qvolatileimage_p.h \ + image/qvolatileimagedata_p.h \ + image/qnativeimagehandleprovider_p.h + +SOURCES += \ + image/qbitmap.cpp \ + image/qicon.cpp \ + image/qiconloader.cpp \ + image/qimage.cpp \ + image/qimageiohandler.cpp \ + image/qimagereader.cpp \ + image/qimagewriter.cpp \ + image/qpaintengine_pic.cpp \ + image/qpicture.cpp \ + image/qpictureformatplugin.cpp \ + image/qpixmap.cpp \ + image/qpixmapcache.cpp \ + image/qpixmapdata.cpp \ + image/qpixmapdatafactory.cpp \ + image/qpixmapfilter.cpp \ + image/qiconengine.cpp \ + image/qiconengineplugin.cpp \ + image/qmovie.cpp \ + image/qpixmap_raster.cpp \ + image/qpixmap_blitter.cpp \ + image/qnativeimage.cpp \ + image/qimagepixmapcleanuphooks.cpp \ + image/qvolatileimage.cpp + +win32 { + SOURCES += image/qpixmap_win.cpp +} +else:embedded { + SOURCES += image/qpixmap_qws.cpp +} +else:qpa { + SOURCES += image/qpixmap_qpa.cpp +} +else:x11 { + HEADERS += image/qpixmap_x11_p.h + SOURCES += image/qpixmap_x11.cpp +} +else:mac { + HEADERS += image/qpixmap_mac_p.h + SOURCES += image/qpixmap_mac.cpp +} +else:symbian { + HEADERS += image/qpixmap_s60_p.h + SOURCES += image/qpixmap_s60.cpp +} + +!symbian|contains(S60_VERSION, 3.1)|contains(S60_VERSION, 3.2) { + SOURCES += image/qvolatileimagedata.cpp +} +else { + SOURCES += image/qvolatileimagedata_symbian.cpp +} + +# Built-in image format support +HEADERS += \ + image/qbmphandler_p.h \ + image/qppmhandler_p.h \ + image/qxbmhandler_p.h \ + image/qxpmhandler_p.h + +SOURCES += \ + image/qbmphandler.cpp \ + image/qppmhandler.cpp \ + image/qxbmhandler.cpp \ + image/qxpmhandler.cpp + +!contains(QT_CONFIG, no-png):include($$PWD/qpnghandler.pri) +else:DEFINES *= QT_NO_IMAGEFORMAT_PNG + +contains(QT_CONFIG, jpeg):include($$PWD/qjpeghandler.pri) +contains(QT_CONFIG, mng):include($$PWD/qmnghandler.pri) +contains(QT_CONFIG, tiff):include($$PWD/qtiffhandler.pri) +contains(QT_CONFIG, gif):include($$PWD/qgifhandler.pri) + +# SIMD +NEON_SOURCES += image/qimage_neon.cpp +SSE2_SOURCES += image/qimage_sse2.cpp +SSSE3_SOURCES += image/qimage_ssse3.cpp diff --git a/src/gui/image/qbitmap.cpp b/src/gui/image/qbitmap.cpp new file mode 100644 index 0000000000..260b397140 --- /dev/null +++ b/src/gui/image/qbitmap.cpp @@ -0,0 +1,411 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbitmap.h" +#include "qpixmapdata_p.h" +#include "qimage.h" +#include "qvariant.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QBitmap + \brief The QBitmap class provides monochrome (1-bit depth) pixmaps. + + \ingroup painting + \ingroup shared + + The QBitmap class is a monochrome off-screen paint device used + mainly for creating custom QCursor and QBrush objects, + constructing QRegion objects, and for setting masks for pixmaps + and widgets. + + QBitmap is a QPixmap subclass ensuring a depth of 1, except for + null objects which have a depth of 0. If a pixmap with a depth + greater than 1 is assigned to a bitmap, the bitmap will be + dithered automatically. + + Use the QColor objects Qt::color0 and Qt::color1 when drawing on a + QBitmap object (or a QPixmap object with depth 1). + + Painting with Qt::color0 sets the bitmap bits to 0, and painting + with Qt::color1 sets the bits to 1. For a bitmap, 0-bits indicate + background (or transparent pixels) and 1-bits indicate foreground + (or opaque pixels). Use the clear() function to set all the bits + to Qt::color0. Note that using the Qt::black and Qt::white colors + make no sense because the QColor::pixel() value is not necessarily + 0 for black and 1 for white. + + The QBitmap class provides the transformed() function returning a + transformed copy of the bitmap; use the QTransform argument to + translate, scale, shear, and rotate the bitmap. In addition, + QBitmap provides the static fromData() function which returns a + bitmap constructed from the given \c uchar data, and the static + fromImage() function returning a converted copy of a QImage + object. + + Just like the QPixmap class, QBitmap is optimized by the use of + implicit data sharing. For more information, see the \l {Implicit + Data Sharing} documentation. + + \sa QPixmap, QImage, QImageReader, QImageWriter +*/ + +/*! \typedef QBitmap::DataPtr + \internal + */ + +/*! + Constructs a null bitmap. + + \sa QPixmap::isNull() +*/ +QBitmap::QBitmap() + : QPixmap(QSize(0, 0), QPixmapData::BitmapType) +{ +} + +/*! + \fn QBitmap::QBitmap(int width, int height) + + Constructs a bitmap with the given \a width and \a height. The pixels + inside are uninitialized. + + \sa clear() +*/ + +QBitmap::QBitmap(int w, int h) + : QPixmap(QSize(w, h), QPixmapData::BitmapType) +{ +} + +/*! + Constructs a bitmap with the given \a size. The pixels in the + bitmap are uninitialized. + + \sa clear() +*/ + +QBitmap::QBitmap(const QSize &size) + : QPixmap(size, QPixmapData::BitmapType) +{ +} + +/*! + \fn QBitmap::clear() + + Clears the bitmap, setting all its bits to Qt::color0. +*/ + +/*! + Constructs a bitmap that is a copy of the given \a pixmap. + + If the pixmap has a depth greater than 1, the resulting bitmap + will be dithered automatically. + + \sa QPixmap::depth(), fromImage(), fromData() +*/ + +QBitmap::QBitmap(const QPixmap &pixmap) +{ + QBitmap::operator=(pixmap); +} + +/*! + \fn QBitmap::QBitmap(const QImage &image) + + Constructs a bitmap that is a copy of the given \a image. + + Use the static fromImage() function instead. +*/ + +/*! + Constructs a bitmap from the file specified by the given \a + fileName. If the file does not exist, or has an unknown format, + the bitmap becomes a null bitmap. + + The \a fileName and \a format parameters are passed on to the + QPixmap::load() function. If the file format uses more than 1 bit + per pixel, the resulting bitmap will be dithered automatically. + + \sa QPixmap::isNull(), QImageReader::imageFormat() +*/ + +QBitmap::QBitmap(const QString& fileName, const char *format) + : QPixmap(QSize(0, 0), QPixmapData::BitmapType) +{ + load(fileName, format, Qt::MonoOnly); +} + +/*! + \overload + + Assigns the given \a pixmap to this bitmap and returns a reference + to this bitmap. + + If the pixmap has a depth greater than 1, the resulting bitmap + will be dithered automatically. + + \sa QPixmap::depth() + */ + +QBitmap &QBitmap::operator=(const QPixmap &pixmap) +{ + if (pixmap.isNull()) { // a null pixmap + QBitmap bm(0, 0); + QBitmap::operator=(bm); + } else if (pixmap.depth() == 1) { // 1-bit pixmap + QPixmap::operator=(pixmap); // shallow assignment + } else { // n-bit depth pixmap + QImage image; + image = pixmap.toImage(); // convert pixmap to image + *this = fromImage(image); // will dither image + } + return *this; +} + + +#ifdef QT3_SUPPORT +QBitmap::QBitmap(int w, int h, const uchar *bits, bool isXbitmap) +{ + *this = fromData(QSize(w, h), bits, isXbitmap ? QImage::Format_MonoLSB : QImage::Format_Mono); +} + + +QBitmap::QBitmap(const QSize &size, const uchar *bits, bool isXbitmap) +{ + *this = fromData(size, bits, isXbitmap ? QImage::Format_MonoLSB : QImage::Format_Mono); +} +#endif + +/*! + Destroys the bitmap. +*/ +QBitmap::~QBitmap() +{ +} + +/*! + \fn void QBitmap::swap(QBitmap &other) + \since 4.8 + + Swaps bitmap \a other with this bitmap. This operation is very + fast and never fails. +*/ + +/*! + Returns the bitmap as a QVariant. +*/ +QBitmap::operator QVariant() const +{ + return QVariant(QVariant::Bitmap, this); +} + +/*! + \fn QBitmap &QBitmap::operator=(const QImage &image) + \overload + + Converts the given \a image to a bitmap, and assigns the result to + this bitmap. Returns a reference to the bitmap. + + Use the static fromImage() function instead. +*/ + +/*! + Returns a copy of the given \a image converted to a bitmap using + the specified image conversion \a flags. + + \sa fromData() +*/ +QBitmap QBitmap::fromImage(const QImage &image, Qt::ImageConversionFlags flags) +{ + if (image.isNull()) + return QBitmap(); + + QImage img = image.convertToFormat(QImage::Format_MonoLSB, flags); + + // make sure image.color(0) == Qt::color0 (white) + // and image.color(1) == Qt::color1 (black) + const QRgb c0 = QColor(Qt::black).rgb(); + const QRgb c1 = QColor(Qt::white).rgb(); + if (img.color(0) == c0 && img.color(1) == c1) { + img.invertPixels(); + img.setColor(0, c1); + img.setColor(1, c0); + } + + QGraphicsSystem* gs = QApplicationPrivate::graphicsSystem(); + QScopedPointer data(gs ? gs->createPixmapData(QPixmapData::BitmapType) + : QGraphicsSystem::createDefaultPixmapData(QPixmapData::BitmapType)); + + data->fromImage(img, flags | Qt::MonoOnly); + return QPixmap(data.take()); +} + +/*! + Constructs a bitmap with the given \a size, and sets the contents to + the \a bits supplied. + + The bitmap data has to be byte aligned and provided in in the bit + order specified by \a monoFormat. The mono format must be either + QImage::Format_Mono or QImage::Format_MonoLSB. Use + QImage::Format_Mono to specify data on the XBM format. + + \sa fromImage() + +*/ +QBitmap QBitmap::fromData(const QSize &size, const uchar *bits, QImage::Format monoFormat) +{ + Q_ASSERT(monoFormat == QImage::Format_Mono || monoFormat == QImage::Format_MonoLSB); + + QImage image(size, monoFormat); + image.setColor(0, QColor(Qt::color0).rgb()); + image.setColor(1, QColor(Qt::color1).rgb()); + + // Need to memcpy each line separatly since QImage is 32bit aligned and + // this data is only byte aligned... + int bytesPerLine = (size.width() + 7) / 8; + for (int y = 0; y < size.height(); ++y) + memcpy(image.scanLine(y), bits + bytesPerLine * y, bytesPerLine); + return QBitmap::fromImage(image); +} + +/*! + Returns a copy of this bitmap, transformed according to the given + \a matrix. + + \sa QPixmap::transformed() + */ +QBitmap QBitmap::transformed(const QTransform &matrix) const +{ + QBitmap bm = QPixmap::transformed(matrix); + return bm; +} + +/*! + \overload + \obsolete + + This convenience function converts the \a matrix to a QTransform + and calls the overloaded function. +*/ +QBitmap QBitmap::transformed(const QMatrix &matrix) const +{ + return transformed(QTransform(matrix)); +} + +#ifdef QT3_SUPPORT +/*! + \fn QBitmap QBitmap::xForm(const QMatrix &matrix) const + + Returns a copy of this bitmap, transformed according to the given + \a matrix. + + Use transformed() instead. +*/ + +/*! + \fn QBitmap::QBitmap(const QSize &size, bool clear) + + Constructs a bitmap with the given \a size. If \a clear is true, + the bits are initialized to Qt::color0. + + Use the corresponding QBitmap() constructor instead, and then call + the clear() function if the \a clear parameter is true. +*/ + +/*! + \fn QBitmap::QBitmap(int width, int height, bool clear) + + Constructs a bitmap with the given \a width and \a height. If \a + clear is true, the bits are initialized to Qt::color0. + + Use the corresponding QBitmap() constructor instead, and then call + the clear() function if the \a clear parameter is true. +*/ + +/*! + \fn QBitmap::QBitmap(int width, int height, const uchar *bits, bool isXbitmap) + + Constructs a bitmap with the given \a width and \a height, and + sets the contents to the \a bits supplied. The \a isXbitmap flag + should be true if \a bits was generated by the X11 bitmap + program. + + Use the static fromData() function instead. If \a isXbitmap is + true, use the default bit order(QImage_FormatMonoLSB) otherwise + use QImage::Format_Mono. + + \omit + The X bitmap bit order is little endian. The QImage + documentation discusses bit order of monochrome images. Opposed to + QImage, the data has to be byte aligned. + + Example (creates an arrow bitmap): + \snippet doc/src/snippets/code/src_gui_image_qbitmap.cpp 0 + \endomit +*/ + + +/*! + \fn QBitmap::QBitmap(const QSize &size, const uchar *bits, bool isXbitmap) + + \overload + + Constructs a bitmap with the given \a size, and sets the contents + to the \a bits supplied. The \a isXbitmap flag should be true if + \a bits was generated by the X11 bitmap program. + + \omit + The X bitmap bit order is little endian. The QImage documentation + discusses bit order of monochrome images. + \endomit + + Use the static fromData() function instead. If \a isXbitmap is + true, use the default bit order(QImage_FormatMonoLSB) otherwise + use QImage::Format_Mono. +*/ +#endif + +QT_END_NAMESPACE diff --git a/src/gui/image/qbitmap.h b/src/gui/image/qbitmap.h new file mode 100644 index 0000000000..f1771e3e43 --- /dev/null +++ b/src/gui/image/qbitmap.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBITMAP_H +#define QBITMAP_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVariant; + +class Q_GUI_EXPORT QBitmap : public QPixmap +{ +public: + QBitmap(); + QBitmap(const QPixmap &); + QBitmap(int w, int h); + explicit QBitmap(const QSize &); + explicit QBitmap(const QString &fileName, const char *format=0); + ~QBitmap(); + + QBitmap &operator=(const QPixmap &); + inline void swap(QBitmap &other) { QPixmap::swap(other); } // prevent QBitmap<->QPixmap swaps + operator QVariant() const; + + inline void clear() { fill(Qt::color0); } + + static QBitmap fromImage(const QImage &image, Qt::ImageConversionFlags flags = Qt::AutoColor); + static QBitmap fromData(const QSize &size, const uchar *bits, + QImage::Format monoFormat = QImage::Format_MonoLSB); + + QBitmap transformed(const QMatrix &) const; + QBitmap transformed(const QTransform &matrix) const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT_CONSTRUCTOR QBitmap(int w, int h, bool clear); + inline QT3_SUPPORT_CONSTRUCTOR QBitmap(const QSize &, bool clear); + QT3_SUPPORT_CONSTRUCTOR QBitmap(int w, int h, const uchar *bits, bool isXbitmap=false); + QT3_SUPPORT_CONSTRUCTOR QBitmap(const QSize &, const uchar *bits, bool isXbitmap=false); + inline QT3_SUPPORT QBitmap xForm(const QMatrix &matrix) const { return transformed(QTransform(matrix)); } + QT3_SUPPORT_CONSTRUCTOR QBitmap(const QImage &image) { *this = fromImage(image); } + QT3_SUPPORT QBitmap &operator=(const QImage &image) { *this = fromImage(image); return *this; } +#endif + + typedef QExplicitlySharedDataPointer DataPtr; +}; +Q_DECLARE_SHARED(QBitmap) + +#ifdef QT3_SUPPORT +inline QBitmap::QBitmap(int w, int h, bool clear) + : QPixmap(QSize(w, h), 1) +{ + if (clear) this->clear(); +} + +inline QBitmap::QBitmap(const QSize &size, bool clear) + : QPixmap(size, 1) +{ + if (clear) this->clear(); +} +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QBITMAP_H diff --git a/src/gui/image/qbmphandler.cpp b/src/gui/image/qbmphandler.cpp new file mode 100644 index 0000000000..6dea9d9917 --- /dev/null +++ b/src/gui/image/qbmphandler.cpp @@ -0,0 +1,837 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qbmphandler_p.h" + +#ifndef QT_NO_IMAGEFORMAT_BMP + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static void swapPixel01(QImage *image) // 1-bpp: swap 0 and 1 pixels +{ + int i; + if (image->depth() == 1 && image->colorCount() == 2) { + register uint *p = (uint *)image->bits(); + int nbytes = image->byteCount(); + for (i=0; icolor(0); // swap color 0 and 1 + image->setColor(0, image->color(1)); + image->setColor(1, t); + } +} + +/* + QImageIO::defineIOHandler("BMP", "^BM", 0, + read_bmp_image, write_bmp_image); +*/ + +/***************************************************************************** + BMP (DIB) image read/write functions + *****************************************************************************/ + +const int BMP_FILEHDR_SIZE = 14; // size of BMP_FILEHDR data + +static QDataStream &operator>>(QDataStream &s, BMP_FILEHDR &bf) +{ // read file header + s.readRawData(bf.bfType, 2); + s >> bf.bfSize >> bf.bfReserved1 >> bf.bfReserved2 >> bf.bfOffBits; + return s; +} + +static QDataStream &operator<<(QDataStream &s, const BMP_FILEHDR &bf) +{ // write file header + s.writeRawData(bf.bfType, 2); + s << bf.bfSize << bf.bfReserved1 << bf.bfReserved2 << bf.bfOffBits; + return s; +} + + +const int BMP_OLD = 12; // old Windows/OS2 BMP size +const int BMP_WIN = 40; // new Windows BMP size +const int BMP_OS2 = 64; // new OS/2 BMP size + +const int BMP_RGB = 0; // no compression +const int BMP_RLE8 = 1; // run-length encoded, 8 bits +const int BMP_RLE4 = 2; // run-length encoded, 4 bits +const int BMP_BITFIELDS = 3; // RGB values encoded in data as bit-fields + + +static QDataStream &operator>>(QDataStream &s, BMP_INFOHDR &bi) +{ + s >> bi.biSize; + if (bi.biSize == BMP_WIN || bi.biSize == BMP_OS2) { + s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount; + s >> bi.biCompression >> bi.biSizeImage; + s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter; + s >> bi.biClrUsed >> bi.biClrImportant; + } + else { // probably old Windows format + qint16 w, h; + s >> w >> h >> bi.biPlanes >> bi.biBitCount; + bi.biWidth = w; + bi.biHeight = h; + bi.biCompression = BMP_RGB; // no compression + bi.biSizeImage = 0; + bi.biXPelsPerMeter = bi.biYPelsPerMeter = 0; + bi.biClrUsed = bi.biClrImportant = 0; + } + return s; +} + +static QDataStream &operator<<(QDataStream &s, const BMP_INFOHDR &bi) +{ + s << bi.biSize; + s << bi.biWidth << bi.biHeight; + s << bi.biPlanes; + s << bi.biBitCount; + s << bi.biCompression; + s << bi.biSizeImage; + s << bi.biXPelsPerMeter << bi.biYPelsPerMeter; + s << bi.biClrUsed << bi.biClrImportant; + return s; +} + +static int calc_shift(int mask) +{ + int result = 0; + while (mask && !(mask & 1)) { + result++; + mask >>= 1; + } + return result; +} + +static bool read_dib_fileheader(QDataStream &s, BMP_FILEHDR &bf) +{ + // read BMP file header + s >> bf; + if (s.status() != QDataStream::Ok) + return false; + + // check header + if (qstrncmp(bf.bfType,"BM",2) != 0) + return false; + + return true; +} + +static bool read_dib_infoheader(QDataStream &s, BMP_INFOHDR &bi) +{ + s >> bi; // read BMP info header + if (s.status() != QDataStream::Ok) + return false; + + int nbits = bi.biBitCount; + int comp = bi.biCompression; + if (!(nbits == 1 || nbits == 4 || nbits == 8 || nbits == 16 || nbits == 24 || nbits == 32) || + bi.biPlanes != 1 || comp > BMP_BITFIELDS) + return false; // weird BMP image + if (!(comp == BMP_RGB || (nbits == 4 && comp == BMP_RLE4) || + (nbits == 8 && comp == BMP_RLE8) || ((nbits == 16 || nbits == 32) && comp == BMP_BITFIELDS))) + return false; // weird compression type + + return true; +} + +static bool read_dib_body(QDataStream &s, const BMP_INFOHDR &bi, int offset, int startpos, QImage &image) +{ + QIODevice* d = s.device(); + if (d->atEnd()) // end of stream/file + return false; +#if 0 + qDebug("offset...........%d", offset); + qDebug("startpos.........%d", startpos); + qDebug("biSize...........%d", bi.biSize); + qDebug("biWidth..........%d", bi.biWidth); + qDebug("biHeight.........%d", bi.biHeight); + qDebug("biPlanes.........%d", bi.biPlanes); + qDebug("biBitCount.......%d", bi.biBitCount); + qDebug("biCompression....%d", bi.biCompression); + qDebug("biSizeImage......%d", bi.biSizeImage); + qDebug("biXPelsPerMeter..%d", bi.biXPelsPerMeter); + qDebug("biYPelsPerMeter..%d", bi.biYPelsPerMeter); + qDebug("biClrUsed........%d", bi.biClrUsed); + qDebug("biClrImportant...%d", bi.biClrImportant); +#endif + int w = bi.biWidth, h = bi.biHeight, nbits = bi.biBitCount; + int t = bi.biSize, comp = bi.biCompression; + int red_mask = 0; + int green_mask = 0; + int blue_mask = 0; + int red_shift = 0; + int green_shift = 0; + int blue_shift = 0; + int red_scale = 0; + int green_scale = 0; + int blue_scale = 0; + + int ncols = 0; + int depth = 0; + QImage::Format format; + switch (nbits) { + case 32: + case 24: + case 16: + depth = 32; + format = QImage::Format_RGB32; + break; + case 8: + case 4: + depth = 8; + format = QImage::Format_Indexed8; + break; + default: + depth = 1; + format = QImage::Format_Mono; + } + + if (bi.biHeight < 0) + h = -h; // support images with negative height + + if (image.size() != QSize(w, h) || image.format() != format) { + image = QImage(w, h, format); + if (image.isNull()) // could not create image + return false; + } + + if (depth != 32) { + ncols = bi.biClrUsed ? bi.biClrUsed : 1 << nbits; + if (ncols > 256) // sanity check - don't run out of mem if color table is broken + return false; + image.setColorCount(ncols); + } + + image.setDotsPerMeterX(bi.biXPelsPerMeter); + image.setDotsPerMeterY(bi.biYPelsPerMeter); + + if (!d->isSequential()) + d->seek(startpos + BMP_FILEHDR_SIZE + bi.biSize); // goto start of colormap + + if (ncols > 0) { // read color table + uchar rgb[4]; + int rgb_len = t == BMP_OLD ? 3 : 4; + for (int i=0; iread((char *)rgb, rgb_len) != rgb_len) + return false; + image.setColor(i, qRgb(rgb[2],rgb[1],rgb[0])); + if (d->atEnd()) // truncated file + return false; + } + } else if (comp == BMP_BITFIELDS && (nbits == 16 || nbits == 32)) { + if (d->read((char *)&red_mask, sizeof(red_mask)) != sizeof(red_mask)) + return false; + if (d->read((char *)&green_mask, sizeof(green_mask)) != sizeof(green_mask)) + return false; + if (d->read((char *)&blue_mask, sizeof(blue_mask)) != sizeof(blue_mask)) + return false; + red_shift = calc_shift(red_mask); + red_scale = 256 / ((red_mask >> red_shift) + 1); + green_shift = calc_shift(green_mask); + green_scale = 256 / ((green_mask >> green_shift) + 1); + blue_shift = calc_shift(blue_mask); + blue_scale = 256 / ((blue_mask >> blue_shift) + 1); + } else if (comp == BMP_RGB && (nbits == 24 || nbits == 32)) { + blue_mask = 0x000000ff; + green_mask = 0x0000ff00; + red_mask = 0x00ff0000; + blue_shift = 0; + green_shift = 8; + red_shift = 16; + blue_scale = green_scale = red_scale = 1; + } else if (comp == BMP_RGB && nbits == 16) { + blue_mask = 0x001f; + green_mask = 0x03e0; + red_mask = 0x7c00; + blue_shift = 0; + green_shift = 2; + red_shift = 7; + red_scale = 1; + green_scale = 1; + blue_scale = 8; + } + + // offset can be bogus, be careful + if (offset>=0 && startpos + offset > d->pos()) { + if (!d->isSequential()) + d->seek(startpos + offset); // start of image data + } + + int bpl = image.bytesPerLine(); + uchar *data = image.bits(); + + if (nbits == 1) { // 1 bit BMP image + while (--h >= 0) { + if (d->read((char*)(data + h*bpl), bpl) != bpl) + break; + } + if (ncols == 2 && qGray(image.color(0)) < qGray(image.color(1))) + swapPixel01(&image); // pixel 0 is white! + } + + else if (nbits == 4) { // 4 bit BMP image + int buflen = ((w+7)/8)*4; + uchar *buf = new uchar[buflen]; + if (comp == BMP_RLE4) { // run length compression + int x=0, y=0, c, i; + quint8 b; + register uchar *p = data + (h-1)*bpl; + const uchar *endp = p + w; + while (y < h) { + if (!d->getChar((char *)&b)) + break; + if (b == 0) { // escape code + if (!d->getChar((char *)&b) || b == 1) { + y = h; // exit loop + } else switch (b) { + case 0: // end of line + x = 0; + y++; + p = data + (h-y-1)*bpl; + break; + case 2: // delta (jump) + { + quint8 tmp; + d->getChar((char *)&tmp); + x += tmp; + d->getChar((char *)&tmp); + y += tmp; + } + + // Protection + if ((uint)x >= (uint)w) + x = w-1; + if ((uint)y >= (uint)h) + y = h-1; + + p = data + (h-y-1)*bpl + x; + break; + default: // absolute mode + // Protection + if (p + b > endp) + b = endp-p; + + i = (c = b)/2; + while (i--) { + d->getChar((char *)&b); + *p++ = b >> 4; + *p++ = b & 0x0f; + } + if (c & 1) { + unsigned char tmp; + d->getChar((char *)&tmp); + *p++ = tmp >> 4; + } + if ((((c & 3) + 1) & 2) == 2) + d->getChar(0); // align on word boundary + x += c; + } + } else { // encoded mode + // Protection + if (p + b > endp) + b = endp-p; + + i = (c = b)/2; + d->getChar((char *)&b); // 2 pixels to be repeated + while (i--) { + *p++ = b >> 4; + *p++ = b & 0x0f; + } + if (c & 1) + *p++ = b >> 4; + x += c; + } + } + } else if (comp == BMP_RGB) { // no compression + memset(data, 0, h*bpl); + while (--h >= 0) { + if (d->read((char*)buf,buflen) != buflen) + break; + register uchar *p = data + h*bpl; + uchar *b = buf; + for (int i=0; i> 4; + *p++ = *b++ & 0x0f; + } + if (w & 1) // the last nibble + *p = *b >> 4; + } + } + delete [] buf; + } + + else if (nbits == 8) { // 8 bit BMP image + if (comp == BMP_RLE8) { // run length compression + int x=0, y=0; + quint8 b; + register uchar *p = data + (h-1)*bpl; + const uchar *endp = p + w; + while (y < h) { + if (!d->getChar((char *)&b)) + break; + if (b == 0) { // escape code + if (!d->getChar((char *)&b) || b == 1) { + y = h; // exit loop + } else switch (b) { + case 0: // end of line + x = 0; + y++; + p = data + (h-y-1)*bpl; + break; + case 2: // delta (jump) + // Protection + if ((uint)x >= (uint)w) + x = w-1; + if ((uint)y >= (uint)h) + y = h-1; + + { + quint8 tmp; + d->getChar((char *)&tmp); + x += tmp; + d->getChar((char *)&tmp); + y += tmp; + } + p = data + (h-y-1)*bpl + x; + break; + default: // absolute mode + // Protection + if (p + b > endp) + b = endp-p; + + if (d->read((char *)p, b) != b) + return false; + if ((b & 1) == 1) + d->getChar(0); // align on word boundary + x += b; + p += b; + } + } else { // encoded mode + // Protection + if (p + b > endp) + b = endp-p; + + char tmp; + d->getChar(&tmp); + memset(p, tmp, b); // repeat pixel + x += b; + p += b; + } + } + } else if (comp == BMP_RGB) { // uncompressed + while (--h >= 0) { + if (d->read((char *)data + h*bpl, bpl) != bpl) + break; + } + } + } + + else if (nbits == 16 || nbits == 24 || nbits == 32) { // 16,24,32 bit BMP image + register QRgb *p; + QRgb *end; + uchar *buf24 = new uchar[bpl]; + int bpl24 = ((w*nbits+31)/32)*4; + uchar *b; + int c; + + while (--h >= 0) { + p = (QRgb *)(data + h*bpl); + end = p + w; + if (d->read((char *)buf24,bpl24) != bpl24) + break; + b = buf24; + while (p < end) { + c = *(uchar*)b | (*(uchar*)(b+1)<<8); + if (nbits != 16) + c |= *(uchar*)(b+2)<<16; + *p++ = qRgb(((c & red_mask) >> red_shift) * red_scale, + ((c & green_mask) >> green_shift) * green_scale, + ((c & blue_mask) >> blue_shift) * blue_scale); + b += nbits/8; + } + } + delete[] buf24; + } + + if (bi.biHeight < 0) { + // Flip the image + uchar *buf = new uchar[bpl]; + h = -bi.biHeight; + for (int y = 0; y < h/2; ++y) { + memcpy(buf, data + y*bpl, bpl); + memcpy(data + y*bpl, data + (h-y-1)*bpl, bpl); + memcpy(data + (h-y-1)*bpl, buf, bpl); + } + delete [] buf; + } + + return true; +} + +// this is also used in qmime_win.cpp +bool qt_write_dib(QDataStream &s, QImage image) +{ + int nbits; + int bpl_bmp; + int bpl = image.bytesPerLine(); + + QIODevice* d = s.device(); + if (!d->isWritable()) + return false; + + if (image.depth() == 8 && image.colorCount() <= 16) { + bpl_bmp = (((bpl+1)/2+3)/4)*4; + nbits = 4; + } else if (image.depth() == 32) { + bpl_bmp = ((image.width()*24+31)/32)*4; + nbits = 24; +#ifdef Q_WS_QWS + } else if (image.depth() == 1 || image.depth() == 8) { + // Qt for Embedded Linux doesn't word align. + bpl_bmp = ((image.width()*image.depth()+31)/32)*4; + nbits = image.depth(); +#endif + } else { + bpl_bmp = bpl; + nbits = image.depth(); + } + + BMP_INFOHDR bi; + bi.biSize = BMP_WIN; // build info header + bi.biWidth = image.width(); + bi.biHeight = image.height(); + bi.biPlanes = 1; + bi.biBitCount = nbits; + bi.biCompression = BMP_RGB; + bi.biSizeImage = bpl_bmp*image.height(); + bi.biXPelsPerMeter = image.dotsPerMeterX() ? image.dotsPerMeterX() + : 2834; // 72 dpi default + bi.biYPelsPerMeter = image.dotsPerMeterY() ? image.dotsPerMeterY() : 2834; + bi.biClrUsed = image.colorCount(); + bi.biClrImportant = image.colorCount(); + s << bi; // write info header + if (s.status() != QDataStream::Ok) + return false; + + if (image.depth() != 32) { // write color table + uchar *color_table = new uchar[4*image.colorCount()]; + uchar *rgb = color_table; + QVector c = image.colorTable(); + for (int i=0; iwrite((char *)color_table, 4*image.colorCount()) == -1) { + delete [] color_table; + return false; + } + delete [] color_table; + } + + if (image.format() == QImage::Format_MonoLSB) + image = image.convertToFormat(QImage::Format_Mono); + + int y; + + if (nbits == 1 || nbits == 8) { // direct output +#ifdef Q_WS_QWS + // Qt for Embedded Linux doesn't word align. + int pad = bpl_bmp - bpl; + char padding[4]; +#endif + for (y=image.height()-1; y>=0; y--) { + if (d->write((char*)image.scanLine(y), bpl) == -1) + return false; +#ifdef Q_WS_QWS + if (d->write(padding, pad) == -1) + return false; +#endif + } + return true; + } + + uchar *buf = new uchar[bpl_bmp]; + uchar *b, *end; + register uchar *p; + + memset(buf, 0, bpl_bmp); + for (y=image.height()-1; y>=0; y--) { // write the image bits + if (nbits == 4) { // convert 8 -> 4 bits + p = image.scanLine(y); + b = buf; + end = b + image.width()/2; + while (b < end) { + *b++ = (*p << 4) | (*(p+1) & 0x0f); + p += 2; + } + if (image.width() & 1) + *b = *p << 4; + } else { // 32 bits + QRgb *p = (QRgb *)image.scanLine(y); + QRgb *end = p + image.width(); + b = buf; + while (p < end) { + *b++ = qBlue(*p); + *b++ = qGreen(*p); + *b++ = qRed(*p); + p++; + } + } + if (bpl_bmp != d->write((char*)buf, bpl_bmp)) { + delete[] buf; + return false; + } + } + delete[] buf; + return true; +} + +// this is also used in qmime_win.cpp +bool qt_read_dib(QDataStream &s, QImage &image) +{ + BMP_INFOHDR bi; + if (!read_dib_infoheader(s, bi)) + return false; + return read_dib_body(s, bi, -1, -BMP_FILEHDR_SIZE, image); +} + +QBmpHandler::QBmpHandler() + : state(Ready) +{ +} + +bool QBmpHandler::readHeader() +{ + state = Error; + + QIODevice *d = device(); + QDataStream s(d); + startpos = d->pos(); + + // Intel byte order + s.setByteOrder(QDataStream::LittleEndian); + + // read BMP file header + if (!read_dib_fileheader(s, fileHeader)) + return false; + + // read BMP info header + if (!read_dib_infoheader(s, infoHeader)) + return false; + + state = ReadHeader; + return true; +} + +bool QBmpHandler::canRead() const +{ + if (state == Ready && !canRead(device())) + return false; + + if (state != Error) { + setFormat("bmp"); + return true; + } + + return false; +} + +bool QBmpHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QBmpHandler::canRead() called with 0 pointer"); + return false; + } + + char head[2]; + if (device->peek(head, sizeof(head)) != sizeof(head)) + return false; + + return (qstrncmp(head, "BM", 2) == 0); +} + +bool QBmpHandler::read(QImage *image) +{ + if (state == Error) + return false; + + if (!image) { + qWarning("QBmpHandler::read: cannot read into null pointer"); + return false; + } + + if (state == Ready && !readHeader()) { + state = Error; + return false; + } + + QIODevice *d = device(); + QDataStream s(d); + + // Intel byte order + s.setByteOrder(QDataStream::LittleEndian); + + // read image + if (!read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image)) + return false; + + state = Ready; + return true; +} + +bool QBmpHandler::write(const QImage &img) +{ + QImage image; + switch (img.format()) { + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + image = img.convertToFormat(QImage::Format_ARGB32); + break; + case QImage::Format_RGB16: + case QImage::Format_RGB888: + case QImage::Format_RGB666: + case QImage::Format_RGB555: + case QImage::Format_RGB444: + image = img.convertToFormat(QImage::Format_RGB32); + break; + default: + image = img; + } + + QIODevice *d = device(); + QDataStream s(d); + BMP_FILEHDR bf; + int bpl_bmp; + int bpl = image.bytesPerLine(); + + // Code partially repeated in qt_write_dib + if (image.depth() == 8 && image.colorCount() <= 16) { + bpl_bmp = (((bpl+1)/2+3)/4)*4; + } else if (image.depth() == 32) { + bpl_bmp = ((image.width()*24+31)/32)*4; + } else { + bpl_bmp = bpl; + } + + // Intel byte order + s.setByteOrder(QDataStream::LittleEndian); + + // build file header + memcpy(bf.bfType, "BM", 2); + + // write file header + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = BMP_FILEHDR_SIZE + BMP_WIN + image.colorCount() * 4; + bf.bfSize = bf.bfOffBits + bpl_bmp*image.height(); + s << bf; + + // write image + return qt_write_dib(s, image); +} + +bool QBmpHandler::supportsOption(ImageOption option) const +{ + return option == Size + || option == ImageFormat; +} + +QVariant QBmpHandler::option(ImageOption option) const +{ + if (option == Size) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + return QSize(infoHeader.biWidth, infoHeader.biHeight); + } else if (option == ImageFormat) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + QImage::Format format; + switch (infoHeader.biBitCount) { + case 32: + case 24: + case 16: + format = QImage::Format_RGB32; + break; + case 8: + case 4: + format = QImage::Format_Indexed8; + break; + default: + format = QImage::Format_Mono; + } + return format; + } + return QVariant(); +} + +void QBmpHandler::setOption(ImageOption option, const QVariant &value) +{ + Q_UNUSED(option); + Q_UNUSED(value); +} + +QByteArray QBmpHandler::name() const +{ + return "bmp"; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_BMP diff --git a/src/gui/image/qbmphandler_p.h b/src/gui/image/qbmphandler_p.h new file mode 100644 index 0000000000..3bf08e8395 --- /dev/null +++ b/src/gui/image/qbmphandler_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBMPHANDLER_P_H +#define QBMPHANDLER_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 "QtGui/qimageiohandler.h" + +#ifndef QT_NO_IMAGEFORMAT_BMP + +QT_BEGIN_NAMESPACE + +struct BMP_FILEHDR { // BMP file header + char bfType[2]; // "BM" + qint32 bfSize; // size of file + qint16 bfReserved1; + qint16 bfReserved2; + qint32 bfOffBits; // pointer to the pixmap bits +}; + +struct BMP_INFOHDR { // BMP information header + qint32 biSize; // size of this struct + qint32 biWidth; // pixmap width + qint32 biHeight; // pixmap height + qint16 biPlanes; // should be 1 + qint16 biBitCount; // number of bits per pixel + qint32 biCompression; // compression method + qint32 biSizeImage; // size of image + qint32 biXPelsPerMeter; // horizontal resolution + qint32 biYPelsPerMeter; // vertical resolution + qint32 biClrUsed; // number of colors used + qint32 biClrImportant; // number of important colors +}; + +class QBmpHandler : public QImageIOHandler +{ +public: + QBmpHandler(); + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + bool readHeader(); + enum State { + Ready, + ReadHeader, + Error + }; + State state; + BMP_FILEHDR fileHeader; + BMP_INFOHDR infoHeader; + int startpos; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_BMP + +#endif // QBMPHANDLER_P_H diff --git a/src/gui/image/qgifhandler.cpp b/src/gui/image/qgifhandler.cpp new file mode 100644 index 0000000000..7cb7373b44 --- /dev/null +++ b/src/gui/image/qgifhandler.cpp @@ -0,0 +1,1214 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +** WARNING: +** A separate license from Unisys may be required to use the gif +** reader. See http://www.unisys.com/about__unisys/lzw/ +** for information from Unisys +** +****************************************************************************/ + +#include "qgifhandler_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#define Q_TRANSPARENT 0x00ffffff + +// avoid going through QImage::scanLine() which calls detach +#define FAST_SCAN_LINE(bits, bpl, y) (bits + (y) * bpl) + + +/* + Incremental image decoder for GIF image format. + + This subclass of QImageFormat decodes GIF format images, + including animated GIFs. Internally in +*/ + +class QGIFFormat { +public: + QGIFFormat(); + ~QGIFFormat(); + + int decode(QImage *image, const uchar* buffer, int length, + int *nextFrameDelay, int *loopCount); + static void scan(QIODevice *device, QVector *imageSizes, int *loopCount); + + bool newFrame; + bool partialNewFrame; + +private: + void fillRect(QImage *image, int x, int y, int w, int h, QRgb col); + inline QRgb color(uchar index) const; + + // GIF specific stuff + QRgb* globalcmap; + QRgb* localcmap; + QImage backingstore; + unsigned char hold[16]; + bool gif89; + int count; + int ccount; + int expectcount; + enum State { + Header, + LogicalScreenDescriptor, + GlobalColorMap, + LocalColorMap, + Introducer, + ImageDescriptor, + TableImageLZWSize, + ImageDataBlockSize, + ImageDataBlock, + ExtensionLabel, + GraphicControlExtension, + ApplicationExtension, + NetscapeExtensionBlockSize, + NetscapeExtensionBlock, + SkipBlockSize, + SkipBlock, + Done, + Error + } state; + int gncols; + int lncols; + int ncols; + int lzwsize; + bool lcmap; + int swidth, sheight; + int width, height; + int left, top, right, bottom; + enum Disposal { NoDisposal, DoNotChange, RestoreBackground, RestoreImage }; + Disposal disposal; + bool disposed; + int trans_index; + bool gcmap; + int bgcol; + int interlace; + int accum; + int bitcount; + + enum { max_lzw_bits=12 }; // (poor-compiler's static const int) + + int code_size, clear_code, end_code, max_code_size, max_code; + int firstcode, oldcode, incode; + short* table[2]; + short* stack; + short *sp; + bool needfirst; + int x, y; + int frame; + bool out_of_bounds; + bool digress; + void nextY(unsigned char *bits, int bpl); + void disposePrevious(QImage *image); +}; + +/*! + Constructs a QGIFFormat. +*/ +QGIFFormat::QGIFFormat() +{ + globalcmap = 0; + localcmap = 0; + lncols = 0; + gncols = 0; + disposal = NoDisposal; + out_of_bounds = false; + disposed = true; + frame = -1; + state = Header; + count = 0; + lcmap = false; + newFrame = false; + partialNewFrame = false; + table[0] = 0; + table[1] = 0; + stack = 0; +} + +/*! + Destroys a QGIFFormat. +*/ +QGIFFormat::~QGIFFormat() +{ + if (globalcmap) delete[] globalcmap; + if (localcmap) delete[] localcmap; + delete [] stack; +} + +void QGIFFormat::disposePrevious(QImage *image) +{ + if (out_of_bounds) { + // flush anything that survived + // ### Changed: QRect(0, 0, swidth, sheight) + } + + // Handle disposal of previous image before processing next one + + if (disposed) return; + + int l = qMin(swidth-1,left); + int r = qMin(swidth-1,right); + int t = qMin(sheight-1,top); + int b = qMin(sheight-1,bottom); + + switch (disposal) { + case NoDisposal: + break; + case DoNotChange: + break; + case RestoreBackground: + if (trans_index>=0) { + // Easy: we use the transparent color + fillRect(image, l, t, r-l+1, b-t+1, Q_TRANSPARENT); + } else if (bgcol>=0) { + // Easy: we use the bgcol given + fillRect(image, l, t, r-l+1, b-t+1, color(bgcol)); + } else { + // Impossible: We don't know of a bgcol - use pixel 0 + QRgb *bits = (QRgb*)image->bits(); + fillRect(image, l, t, r-l+1, b-t+1, bits[0]); + } + // ### Changed: QRect(l, t, r-l+1, b-t+1) + break; + case RestoreImage: { + if (frame >= 0) { + for (int ln=t; ln<=b; ln++) { + memcpy(image->scanLine(ln)+l, + backingstore.scanLine(ln-t), + (r-l+1)*sizeof(QRgb)); + } + // ### Changed: QRect(l, t, r-l+1, b-t+1) + } + } + } + disposal = NoDisposal; // Until an extension says otherwise. + + disposed = true; +} + +/*! + This function decodes some data into image changes. + + Returns the number of bytes consumed. +*/ +int QGIFFormat::decode(QImage *image, const uchar *buffer, int length, + int *nextFrameDelay, int *loopCount) +{ + // We are required to state that + // "The Graphics Interchange Format(c) is the Copyright property of + // CompuServe Incorporated. GIF(sm) is a Service Mark property of + // CompuServe Incorporated." + + if (!stack) { + stack = new short[(1 << max_lzw_bits) * 4]; + table[0] = &stack[(1 << max_lzw_bits) * 2]; + table[1] = &stack[(1 << max_lzw_bits) * 3]; + } + + image->detach(); + int bpl = image->bytesPerLine(); + unsigned char *bits = image->bits(); + +#define LM(l, m) (((m)<<8)|l) + digress = false; + const int initial = length; + while (!digress && length) { + length--; + unsigned char ch=*buffer++; + switch (state) { + case Header: + hold[count++]=ch; + if (count==6) { + // Header + gif89=(hold[3]!='8' || hold[4]!='7'); + state=LogicalScreenDescriptor; + count=0; + } + break; + case LogicalScreenDescriptor: + hold[count++]=ch; + if (count==7) { + // Logical Screen Descriptor + swidth=LM(hold[0], hold[1]); + sheight=LM(hold[2], hold[3]); + gcmap=!!(hold[4]&0x80); + //UNUSED: bpchan=(((hold[4]&0x70)>>3)+1); + //UNUSED: gcmsortflag=!!(hold[4]&0x08); + gncols=2<<(hold[4]&0x7); + bgcol=(gcmap) ? hold[5] : -1; + //aspect=hold[6] ? double(hold[6]+15)/64.0 : 1.0; + + trans_index = -1; + count=0; + ncols=gncols; + if (gcmap) { + ccount=0; + state=GlobalColorMap; + globalcmap = new QRgb[gncols+1]; // +1 for trans_index + globalcmap[gncols] = Q_TRANSPARENT; + } else { + state=Introducer; + } + } + break; + case GlobalColorMap: case LocalColorMap: + hold[count++]=ch; + if (count==3) { + QRgb rgb = qRgb(hold[0], hold[1], hold[2]); + if (state == LocalColorMap) { + if (ccount < lncols) + localcmap[ccount] = rgb; + } else { + globalcmap[ccount] = rgb; + } + if (++ccount >= ncols) { + if (state == LocalColorMap) + state=TableImageLZWSize; + else + state=Introducer; + } + count=0; + } + break; + case Introducer: + hold[count++]=ch; + switch (ch) { + case ',': + state=ImageDescriptor; + break; + case '!': + state=ExtensionLabel; + break; + case ';': + // ### Changed: QRect(0, 0, swidth, sheight) + state=Done; + break; + default: + digress=true; + // Unexpected Introducer - ignore block + state=Error; + } + break; + case ImageDescriptor: + hold[count++]=ch; + if (count==10) { + int newleft=LM(hold[1], hold[2]); + int newtop=LM(hold[3], hold[4]); + int newwidth=LM(hold[5], hold[6]); + int newheight=LM(hold[7], hold[8]); + + // disbelieve ridiculous logical screen sizes, + // unless the image frames are also large. + if (swidth/10 > qMax(newwidth,200)) + swidth = -1; + if (sheight/10 > qMax(newheight,200)) + sheight = -1; + + if (swidth <= 0) + swidth = newleft + newwidth; + if (sheight <= 0) + sheight = newtop + newheight; + + QImage::Format format = trans_index >= 0 ? QImage::Format_ARGB32 : QImage::Format_RGB32; + if (image->isNull()) { + (*image) = QImage(swidth, sheight, format); + bpl = image->bytesPerLine(); + bits = image->bits(); + memset(bits, 0, image->byteCount()); + } + + disposePrevious(image); + disposed = false; + + left = newleft; + top = newtop; + width = newwidth; + height = newheight; + + right=qMax(0, qMin(left+width, swidth)-1); + bottom=qMax(0, qMin(top+height, sheight)-1); + lcmap=!!(hold[9]&0x80); + interlace=!!(hold[9]&0x40); + //bool lcmsortflag=!!(hold[9]&0x20); + lncols=lcmap ? (2<<(hold[9]&0x7)) : 0; + if (lncols) { + if (localcmap) + delete [] localcmap; + localcmap = new QRgb[lncols+1]; + localcmap[lncols] = Q_TRANSPARENT; + ncols = lncols; + } else { + ncols = gncols; + } + frame++; + if (frame == 0) { + if (left || top || width= 0) { + fillRect(image, 0, 0, swidth, sheight, color(trans_index)); + // ### Changed: QRect(0, 0, swidth, sheight) + } else if (bgcol>=0) { + fillRect(image, 0, 0, swidth, sheight, color(bgcol)); + // ### Changed: QRect(0, 0, swidth, sheight) + } + } + } + + if (disposal == RestoreImage) { + int l = qMin(swidth-1,left); + int r = qMin(swidth-1,right); + int t = qMin(sheight-1,top); + int b = qMin(sheight-1,bottom); + int w = r-l+1; + int h = b-t+1; + + if (backingstore.width() < w + || backingstore.height() < h) { + // We just use the backing store as a byte array + backingstore = QImage(qMax(backingstore.width(), w), + qMax(backingstore.height(), h), + QImage::Format_RGB32); + memset(bits, 0, image->byteCount()); + } + const int dest_bpl = backingstore.bytesPerLine(); + unsigned char *dest_data = backingstore.bits(); + for (int ln=0; ln=swidth || y>=sheight; + } + break; + case TableImageLZWSize: { + lzwsize=ch; + if (lzwsize > max_lzw_bits) { + state=Error; + } else { + code_size=lzwsize+1; + clear_code=1<=code_size && state==ImageDataBlock) { + int code=accum&((1<>=code_size; + + if (code==clear_code) { + if (!needfirst) { + code_size=lzwsize+1; + max_code_size=2*clear_code; + max_code=clear_code+2; + } + needfirst=true; + } else if (code==end_code) { + bitcount = -32768; + // Left the block end arrive + } else { + if (needfirst) { + firstcode=oldcode=code; + if (!out_of_bounds && image->height() > y && firstcode!=trans_index) + ((QRgb*)FAST_SCAN_LINE(bits, bpl, y))[x] = color(firstcode); + x++; + if (x>=swidth) out_of_bounds = true; + needfirst=false; + if (x>=left+width) { + x=left; + out_of_bounds = left>=swidth || y>=sheight; + nextY(bits, bpl); + } + } else { + incode=code; + if (code>=max_code) { + *sp++=firstcode; + code=oldcode; + } + while (code>=clear_code+2) { + if (code >= max_code) { + state = Error; + return -1; + } + *sp++=table[1][code]; + if (code==table[0][code]) { + state=Error; + return -1; + } + if (sp-stack>=(1<<(max_lzw_bits))*2) { + state=Error; + return -1; + } + code=table[0][code]; + } + if (code < 0) { + state = Error; + return -1; + } + + *sp++=firstcode=table[1][code]; + code=max_code; + if (code<(1<=max_code_size) + && (max_code_size<(1<height(); + const QRgb *map = lcmap ? localcmap : globalcmap; + QRgb *line = 0; + if (!out_of_bounds && h > y) + line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y); + while (sp>stack) { + const uchar index = *(--sp); + if (!out_of_bounds && h > y && index!=trans_index) { + if (index > ncols) + line[x] = Q_TRANSPARENT; + else + line[x] = map ? map[index] : 0; + } + x++; + if (x>=swidth) out_of_bounds = true; + if (x>=left+width) { + x=left; + out_of_bounds = left>=swidth || y>=sheight; + nextY(bits, bpl); + if (!out_of_bounds && h > y) + line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y); + } + } + } + } + } + partialNewFrame = true; + if (count==expectcount) { + count=0; + state=ImageDataBlockSize; + } + break; + case ExtensionLabel: + switch (ch) { + case 0xf9: + state=GraphicControlExtension; + break; + case 0xff: + state=ApplicationExtension; + break; +#if 0 + case 0xfe: + state=CommentExtension; + break; + case 0x01: + break; +#endif + default: + state=SkipBlockSize; + } + count=0; + break; + case ApplicationExtension: + if (count<11) hold[count]=ch; + count++; + if (count==hold[0]+1) { + if (qstrncmp((char*)(hold+1), "NETSCAPE", 8)==0) { + // Looping extension + state=NetscapeExtensionBlockSize; + } else { + state=SkipBlockSize; + } + count=0; + } + break; + case NetscapeExtensionBlockSize: + expectcount=ch; + count=0; + if (expectcount) state=NetscapeExtensionBlock; + else state=Introducer; + break; + case NetscapeExtensionBlock: + if (count<3) hold[count]=ch; + count++; + if (count==expectcount) { + *loopCount = hold[1]+hold[2]*256; + state=SkipBlockSize; // Ignore further blocks + } + break; + case GraphicControlExtension: + if (count<5) hold[count]=ch; + count++; + if (count==hold[0]+1) { + disposePrevious(image); + disposal=Disposal((hold[1]>>2)&0x7); + //UNUSED: waitforuser=!!((hold[1]>>1)&0x1); + int delay=count>3 ? LM(hold[2], hold[3]) : 1; + // IE and mozilla use a minimum delay of 10. With the minimum delay of 10 + // we are compatible to them and avoid huge loads on the app and xserver. + *nextFrameDelay = (delay < 2 ? 10 : delay) * 10; + + bool havetrans=hold[1]&0x1; + trans_index = havetrans ? hold[4] : -1; + + count=0; + state=SkipBlockSize; + } + break; + case SkipBlockSize: + expectcount=ch; + count=0; + if (expectcount) state=SkipBlock; + else state=Introducer; + break; + case SkipBlock: + count++; + if (count==expectcount) state=SkipBlockSize; + break; + case Done: + digress=true; + /* Netscape ignores the junk, so we do too. + length++; // Unget + state=Error; // More calls to this is an error + */ + break; + case Error: + return -1; // Called again after done. + } + } + return initial-length; +} + +/*! + Scans through the data stream defined by \a device and returns the image + sizes found in the stream in the \a imageSizes vector. +*/ +void QGIFFormat::scan(QIODevice *device, QVector *imageSizes, int *loopCount) +{ + if (!device) + return; + + qint64 oldPos = device->pos(); + if (!device->seek(0)) + return; + + int colorCount = 0; + int localColorCount = 0; + int globalColorCount = 0; + int colorReadCount = 0; + bool localColormap = false; + bool globalColormap = false; + int count = 0; + int blockSize = 0; + int imageWidth = 0; + int imageHeight = 0; + bool done = false; + uchar hold[16]; + State state = Header; + + const int readBufferSize = 40960; // 40k read buffer + QByteArray readBuffer(device->read(readBufferSize)); + + if (readBuffer.isEmpty()) { + device->seek(oldPos); + return; + } + + // This is a specialized version of the state machine from decode(), + // which doesn't do any image decoding or mallocing, and has an + // optimized way of skipping SkipBlocks, ImageDataBlocks and + // Global/LocalColorMaps. + + while (!readBuffer.isEmpty()) { + int length = readBuffer.size(); + const uchar *buffer = (const uchar *) readBuffer.constData(); + while (!done && length) { + length--; + uchar ch = *buffer++; + switch (state) { + case Header: + hold[count++] = ch; + if (count == 6) { + state = LogicalScreenDescriptor; + count = 0; + } + break; + case LogicalScreenDescriptor: + hold[count++] = ch; + if (count == 7) { + imageWidth = LM(hold[0], hold[1]); + imageHeight = LM(hold[2], hold[3]); + globalColormap = !!(hold[4] & 0x80); + globalColorCount = 2 << (hold[4] & 0x7); + count = 0; + colorCount = globalColorCount; + if (globalColormap) { + int colorTableSize = 3 * globalColorCount; + if (length >= colorTableSize) { + // skip the global color table in one go + length -= colorTableSize; + buffer += colorTableSize; + state = Introducer; + } else { + colorReadCount = 0; + state = GlobalColorMap; + } + } else { + state=Introducer; + } + } + break; + case GlobalColorMap: + case LocalColorMap: + hold[count++] = ch; + if (count == 3) { + if (++colorReadCount >= colorCount) { + if (state == LocalColorMap) + state = TableImageLZWSize; + else + state = Introducer; + } + count = 0; + } + break; + case Introducer: + hold[count++] = ch; + switch (ch) { + case 0x2c: + state = ImageDescriptor; + break; + case 0x21: + state = ExtensionLabel; + break; + case 0x3b: + state = Done; + break; + default: + done = true; + state = Error; + } + break; + case ImageDescriptor: + hold[count++] = ch; + if (count == 10) { + int newLeft = LM(hold[1], hold[2]); + int newTop = LM(hold[3], hold[4]); + int newWidth = LM(hold[5], hold[6]); + int newHeight = LM(hold[7], hold[8]); + + if (imageWidth/10 > qMax(newWidth,200)) + imageWidth = -1; + if (imageHeight/10 > qMax(newHeight,200)) + imageHeight = -1; + + if (imageWidth <= 0) + imageWidth = newLeft + newWidth; + if (imageHeight <= 0) + imageHeight = newTop + newHeight; + + *imageSizes << QSize(imageWidth, imageHeight); + + localColormap = !!(hold[9] & 0x80); + localColorCount = localColormap ? (2 << (hold[9] & 0x7)) : 0; + if (localColorCount) + colorCount = localColorCount; + else + colorCount = globalColorCount; + + count = 0; + if (localColormap) { + int colorTableSize = 3 * localColorCount; + if (length >= colorTableSize) { + // skip the local color table in one go + length -= colorTableSize; + buffer += colorTableSize; + state = TableImageLZWSize; + } else { + colorReadCount = 0; + state = LocalColorMap; + } + } else { + state = TableImageLZWSize; + } + } + break; + case TableImageLZWSize: + if (ch > max_lzw_bits) + state = Error; + else + state = ImageDataBlockSize; + count = 0; + break; + case ImageDataBlockSize: + blockSize = ch; + if (blockSize) { + if (length >= blockSize) { + // we can skip the block in one go + length -= blockSize; + buffer += blockSize; + count = 0; + } else { + state = ImageDataBlock; + } + } else { + state = Introducer; + } + break; + case ImageDataBlock: + ++count; + if (count == blockSize) { + count = 0; + state = ImageDataBlockSize; + } + break; + case ExtensionLabel: + switch (ch) { + case 0xf9: + state = GraphicControlExtension; + break; + case 0xff: + state = ApplicationExtension; + break; + default: + state = SkipBlockSize; + } + count = 0; + break; + case ApplicationExtension: + if (count < 11) + hold[count] = ch; + ++count; + if (count == hold[0] + 1) { + if (qstrncmp((char*)(hold+1), "NETSCAPE", 8) == 0) + state=NetscapeExtensionBlockSize; + else + state=SkipBlockSize; + count = 0; + } + break; + case GraphicControlExtension: + if (count < 5) + hold[count] = ch; + ++count; + if (count == hold[0] + 1) { + count = 0; + state = SkipBlockSize; + } + break; + case NetscapeExtensionBlockSize: + blockSize = ch; + count = 0; + if (blockSize) + state = NetscapeExtensionBlock; + else + state = Introducer; + break; + case NetscapeExtensionBlock: + if (count < 3) + hold[count] = ch; + count++; + if (count == blockSize) { + *loopCount = LM(hold[1], hold[2]); + state = SkipBlockSize; + } + break; + case SkipBlockSize: + blockSize = ch; + count = 0; + if (blockSize) { + if (length >= blockSize) { + // we can skip the block in one go + length -= blockSize; + buffer += blockSize; + } else { + state = SkipBlock; + } + } else { + state = Introducer; + } + break; + case SkipBlock: + ++count; + if (count == blockSize) + state = SkipBlockSize; + break; + case Done: + done = true; + break; + case Error: + device->seek(oldPos); + return; + } + } + readBuffer = device->read(readBufferSize); + } + device->seek(oldPos); + return; +} + +void QGIFFormat::fillRect(QImage *image, int col, int row, int w, int h, QRgb color) +{ + if (w>0) { + for (int j=0; jscanLine(j+row); + for (int i=0; ichanged(QRect(%d, %d, %d, %d))", left, y, right-left+1, my+1); + y+=8; + if (y>bottom) { + interlace++; y=top+4; + if (y > bottom) { // for really broken GIFs with bottom < 5 + interlace=2; + y = top + 2; + if (y > bottom) { // for really broken GIF with bottom < 3 + interlace = 0; + y = top + 1; + } + } + } + } break; + case 2: { + int i; + my = qMin(3, bottom-y); + // Don't dup with transparency + if (trans_index < 0) { + for (i=1; i<=my; i++) { + memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb), + (right-left+1)*sizeof(QRgb)); + } + } + + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, my + 1); + // } + y+=8; + if (y>bottom) { + interlace++; y=top+2; + // handle broken GIF with bottom < 3 + if (y > bottom) { + interlace = 3; + y = top + 1; + } + } + } break; + case 3: { + int i; + my = qMin(1, bottom-y); + // Don't dup with transparency + if (trans_index < 0) { + for (i=1; i<=my; i++) { + memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb), + (right-left+1)*sizeof(QRgb)); + } + } + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, my + 1); + // } + y+=4; + if (y>bottom) { interlace++; y=top+1; } + } break; + case 4: + // if (!out_of_bounds) { + // ### Changed: QRect(left, y, right - left + 1, 1); + // } + y+=2; + } + + // Consume bogus extra lines + if (y >= sheight) out_of_bounds=true; //y=bottom; +} + +inline QRgb QGIFFormat::color(uchar index) const +{ + if (index == trans_index || index > ncols) + return Q_TRANSPARENT; + + QRgb *map = lcmap ? localcmap : globalcmap; + return map ? map[index] : 0; +} + +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- + +QGifHandler::QGifHandler() +{ + gifFormat = new QGIFFormat; + nextDelay = 100; + loopCnt = -1; + frameNumber = -1; + scanIsCached = false; +} + +QGifHandler::~QGifHandler() +{ + delete gifFormat; +} + +// Does partial decode if necessary, just to see if an image is coming + +bool QGifHandler::imageIsComing() const +{ + const int GifChunkSize = 4096; + + while (!gifFormat->partialNewFrame) { + if (buffer.isEmpty()) { + buffer += device()->read(GifChunkSize); + if (buffer.isEmpty()) + break; + } + + int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), + &nextDelay, &loopCnt); + if (decoded == -1) + break; + buffer.remove(0, decoded); + } + return gifFormat->partialNewFrame; +} + +bool QGifHandler::canRead() const +{ + if (canRead(device()) || imageIsComing()) { + setFormat("gif"); + return true; + } + + return false; +} + +bool QGifHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QGifHandler::canRead() called with no device"); + return false; + } + + char head[6]; + if (device->peek(head, sizeof(head)) == sizeof(head)) + return qstrncmp(head, "GIF87a", 6) == 0 + || qstrncmp(head, "GIF89a", 6) == 0; + return false; +} + +bool QGifHandler::read(QImage *image) +{ + const int GifChunkSize = 4096; + + while (!gifFormat->newFrame) { + if (buffer.isEmpty()) { + buffer += device()->read(GifChunkSize); + if (buffer.isEmpty()) + break; + } + + int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), + &nextDelay, &loopCnt); + if (decoded == -1) + break; + buffer.remove(0, decoded); + } + if (gifFormat->newFrame || (gifFormat->partialNewFrame && device()->atEnd())) { + *image = lastImage; + ++frameNumber; + gifFormat->newFrame = false; + gifFormat->partialNewFrame = false; + return true; + } + + return false; +} + +bool QGifHandler::write(const QImage &image) +{ + Q_UNUSED(image); + return false; +} + +bool QGifHandler::supportsOption(ImageOption option) const +{ + if (!device() || device()->isSequential()) + return option == Animation; + else + return option == Size + || option == Animation; +} + +QVariant QGifHandler::option(ImageOption option) const +{ + if (option == Size) { + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + // 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(); + // after the last frame has been read, the next size is undefined + if (frameNumber >= imageSizes.count() - 1) + return QVariant(); + // and the last case: the size of the next frame + return imageSizes.at(frameNumber + 1); + } else if (option == Animation) { + return true; + } + return QVariant(); +} + +void QGifHandler::setOption(ImageOption option, const QVariant &value) +{ + Q_UNUSED(option); + Q_UNUSED(value); +} + +int QGifHandler::nextImageDelay() const +{ + return nextDelay; +} + +int QGifHandler::imageCount() const +{ + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + return imageSizes.count(); +} + +int QGifHandler::loopCount() const +{ + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes, &loopCnt); + scanIsCached = true; + } + + if (loopCnt == 0) + return -1; + else if (loopCnt == -1) + return 0; + else + return loopCnt; +} + +int QGifHandler::currentImageNumber() const +{ + return frameNumber; +} + +QByteArray QGifHandler::name() const +{ + return "gif"; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qgifhandler.pri b/src/gui/image/qgifhandler.pri new file mode 100644 index 0000000000..6eb0751372 --- /dev/null +++ b/src/gui/image/qgifhandler.pri @@ -0,0 +1,4 @@ +# common to plugin and built-in forms +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qgifhandler_p.h +SOURCES += $$PWD/qgifhandler.cpp diff --git a/src/gui/image/qgifhandler_p.h b/src/gui/image/qgifhandler_p.h new file mode 100644 index 0000000000..0ea4ccfc38 --- /dev/null +++ b/src/gui/image/qgifhandler_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +** WARNING: +** A separate license from Unisys may be required to use the gif +** reader. See http://www.unisys.com/about__unisys/lzw/ +** for information from Unisys +** +****************************************************************************/ + +#ifndef QGIFHANDLER_P_H +#define QGIFHANDLER_P_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGIFFormat; +class QGifHandler : public QImageIOHandler +{ +public: + QGifHandler(); + ~QGifHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + + int imageCount() const; + int loopCount() const; + int nextImageDelay() const; + int currentImageNumber() const; + +private: + bool imageIsComing() const; + QGIFFormat *gifFormat; + QString fileName; + mutable QByteArray buffer; + mutable QImage lastImage; + + mutable int nextDelay; + mutable int loopCnt; + int frameNumber; + mutable QVector imageSizes; + mutable bool scanIsCached; +}; + +QT_END_NAMESPACE + +#endif // QGIFHANDLER_P_H diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp new file mode 100644 index 0000000000..7182062e59 --- /dev/null +++ b/src/gui/image/qicon.cpp @@ -0,0 +1,1258 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qicon.h" +#include "qicon_p.h" +#include "qiconengine.h" +#include "qiconengineplugin.h" +#include "private/qfactoryloader_p.h" +#include "private/qiconloader_p.h" +#include "qapplication.h" +#include "qstyleoption.h" +#include "qpainter.h" +#include "qfileinfo.h" +#include "qstyle.h" +#include "qpixmapcache.h" +#include "qvariant.h" +#include "qcache.h" +#include "qdebug.h" +#include "private/qguiplatformplugin_p.h" + +#ifdef Q_WS_MAC +#include +#include +#endif + +#ifdef Q_WS_X11 +#include "private/qt_x11_p.h" +#include "private/qkde_p.h" +#endif + +#include "private/qstylehelper_p.h" + +#ifndef QT_NO_ICON +QT_BEGIN_NAMESPACE + +/*! + \enum QIcon::Mode + + This enum type describes the mode for which a pixmap is intended + to be used. The currently defined modes are: + + \value Normal + Display the pixmap when the user is + not interacting with the icon, but the + functionality represented by the icon is available. + \value Disabled + Display the pixmap when the + functionality represented by the icon is not available. + \value Active + Display the pixmap when the + functionality represented by the icon is available and + the user is interacting with the icon, for example, moving the + mouse over it or clicking it. + \value Selected + Display the pixmap when the item represented by the icon is + selected. +*/ + +/*! + \enum QIcon::State + + This enum describes the state for which a pixmap is intended to be + used. The \e state can be: + + \value Off Display the pixmap when the widget is in an "off" state + \value On Display the pixmap when the widget is in an "on" state +*/ + +static QBasicAtomicInt serialNumCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +static void qt_cleanup_icon_cache(); +typedef QCache IconCache; +Q_GLOBAL_STATIC_WITH_INITIALIZER(IconCache, qtIconCache, qAddPostRoutine(qt_cleanup_icon_cache)) + +static void qt_cleanup_icon_cache() +{ + qtIconCache()->clear(); +} + +QIconPrivate::QIconPrivate() + : engine(0), ref(1), + serialNum(serialNumCounter.fetchAndAddRelaxed(1)), + detach_no(0), + engine_version(2), + v1RefCount(0) +{ +} + +QPixmapIconEngine::QPixmapIconEngine() +{ +} + +QPixmapIconEngine::QPixmapIconEngine(const QPixmapIconEngine &other) + : QIconEngineV2(other), pixmaps(other.pixmaps) +{ +} + +QPixmapIconEngine::~QPixmapIconEngine() +{ +} + +void QPixmapIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + QSize pixmapSize = rect.size(); +#if defined(Q_WS_MAC) + pixmapSize *= qt_mac_get_scalefactor(); +#endif + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); +} + +static inline int area(const QSize &s) { return s.width() * s.height(); } + +// returns the smallest of the two that is still larger than or equal to size. +static QPixmapIconEngineEntry *bestSizeMatch( const QSize &size, QPixmapIconEngineEntry *pa, QPixmapIconEngineEntry *pb) +{ + int s = area(size); + if (pa->size == QSize() && pa->pixmap.isNull()) { + pa->pixmap = QPixmap(pa->fileName); + pa->size = pa->pixmap.size(); + } + int a = area(pa->size); + if (pb->size == QSize() && pb->pixmap.isNull()) { + pb->pixmap = QPixmap(pb->fileName); + pb->size = pb->pixmap.size(); + } + int b = area(pb->size); + int res = a; + if (qMin(a,b) >= s) + res = qMin(a,b); + else + res = qMax(a,b); + if (res == a) + return pa; + return pb; +} + +QPixmapIconEngineEntry *QPixmapIconEngine::tryMatch(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + QPixmapIconEngineEntry *pe = 0; + for (int i = 0; i < pixmaps.count(); ++i) + if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) { + if (pe) + pe = bestSizeMatch(size, &pixmaps[i], pe); + else + pe = &pixmaps[i]; + } + return pe; +} + + +QPixmapIconEngineEntry *QPixmapIconEngine::bestMatch(const QSize &size, QIcon::Mode mode, QIcon::State state, bool sizeOnly) +{ + QPixmapIconEngineEntry *pe = tryMatch(size, mode, state); + while (!pe){ + QIcon::State oppositeState = (state == QIcon::On) ? QIcon::Off : QIcon::On; + if (mode == QIcon::Disabled || mode == QIcon::Selected) { + QIcon::Mode oppositeMode = (mode == QIcon::Disabled) ? QIcon::Selected : QIcon::Disabled; + if ((pe = tryMatch(size, QIcon::Normal, state))) + break; + if ((pe = tryMatch(size, QIcon::Active, state))) + break; + if ((pe = tryMatch(size, mode, oppositeState))) + break; + if ((pe = tryMatch(size, QIcon::Normal, oppositeState))) + break; + if ((pe = tryMatch(size, QIcon::Active, oppositeState))) + break; + if ((pe = tryMatch(size, oppositeMode, state))) + break; + if ((pe = tryMatch(size, oppositeMode, oppositeState))) + break; + } else { + QIcon::Mode oppositeMode = (mode == QIcon::Normal) ? QIcon::Active : QIcon::Normal; + if ((pe = tryMatch(size, oppositeMode, state))) + break; + if ((pe = tryMatch(size, mode, oppositeState))) + break; + if ((pe = tryMatch(size, oppositeMode, oppositeState))) + break; + if ((pe = tryMatch(size, QIcon::Disabled, state))) + break; + if ((pe = tryMatch(size, QIcon::Selected, state))) + break; + if ((pe = tryMatch(size, QIcon::Disabled, oppositeState))) + break; + if ((pe = tryMatch(size, QIcon::Selected, oppositeState))) + break; + } + + if (!pe) + return pe; + } + + if (sizeOnly ? (pe->size.isNull() || !pe->size.isValid()) : pe->pixmap.isNull()) { + pe->pixmap = QPixmap(pe->fileName); + if (!pe->pixmap.isNull()) + pe->size = pe->pixmap.size(); + } + + return pe; +} + +QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + QPixmap pm; + QPixmapIconEngineEntry *pe = bestMatch(size, mode, state, false); + if (pe) + pm = pe->pixmap; + + if (pm.isNull()) { + int idx = pixmaps.count(); + while (--idx >= 0) { + if (pe == &pixmaps[idx]) { + pixmaps.remove(idx); + break; + } + } + if (pixmaps.isEmpty()) + return pm; + else + return pixmap(size, mode, state); + } + + QSize actualSize = pm.size(); + if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) + actualSize.scale(size, Qt::KeepAspectRatio); + + QString key = QLatin1Literal("qt_") + % HexString(pm.cacheKey()) + % HexString(pe->mode) + % HexString(QApplication::palette().cacheKey()) + % HexString(actualSize.width()) + % HexString(actualSize.height()); + + if (mode == QIcon::Active) { + if (QPixmapCache::find(key % HexString(mode), pm)) + return pm; // horray + if (QPixmapCache::find(key % HexString(QIcon::Normal), pm)) { + QStyleOption opt(0); + opt.palette = QApplication::palette(); + QPixmap active = QApplication::style()->generatedIconPixmap(QIcon::Active, pm, &opt); + if (pm.cacheKey() == active.cacheKey()) + return pm; + } + } + + if (!QPixmapCache::find(key % HexString(mode), pm)) { + if (pm.size() != actualSize) + pm = pm.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (pe->mode != mode && mode != QIcon::Normal) { + QStyleOption opt(0); + opt.palette = QApplication::palette(); + QPixmap generated = QApplication::style()->generatedIconPixmap(mode, pm, &opt); + if (!generated.isNull()) + pm = generated; + } + QPixmapCache::insert(key % HexString(mode), pm); + } + return pm; +} + +QSize QPixmapIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + QSize actualSize; + if (QPixmapIconEngineEntry *pe = bestMatch(size, mode, state, true)) + actualSize = pe->size; + + if (actualSize.isNull()) + return actualSize; + + if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) + actualSize.scale(size, Qt::KeepAspectRatio); + return actualSize; +} + +void QPixmapIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) +{ + if (!pixmap.isNull()) { + QPixmapIconEngineEntry *pe = tryMatch(pixmap.size(), mode, state); + if(pe && pe->size == pixmap.size()) { + pe->pixmap = pixmap; + pe->fileName.clear(); + } else { + pixmaps += QPixmapIconEngineEntry(pixmap, mode, state); + } + } +} + +void QPixmapIconEngine::addFile(const QString &fileName, const QSize &_size, QIcon::Mode mode, QIcon::State state) +{ + if (!fileName.isEmpty()) { + QSize size = _size; + QPixmap pixmap; + + QString abs = fileName; + if (fileName.at(0) != QLatin1Char(':')) + abs = QFileInfo(fileName).absoluteFilePath(); + + for (int i = 0; i < pixmaps.count(); ++i) { + if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) { + QPixmapIconEngineEntry *pe = &pixmaps[i]; + if(size == QSize()) { + pixmap = QPixmap(abs); + size = pixmap.size(); + } + if (pe->size == QSize() && pe->pixmap.isNull()) { + pe->pixmap = QPixmap(pe->fileName); + pe->size = pe->pixmap.size(); + } + if(pe->size == size) { + pe->pixmap = pixmap; + pe->fileName = abs; + return; + } + } + } + QPixmapIconEngineEntry e(abs, size, mode, state); + e.pixmap = pixmap; + pixmaps += e; + } +} + +QString QPixmapIconEngine::key() const +{ + return QLatin1String("QPixmapIconEngine"); +} + +QIconEngineV2 *QPixmapIconEngine::clone() const +{ + return new QPixmapIconEngine(*this); +} + +bool QPixmapIconEngine::read(QDataStream &in) +{ + int num_entries; + QPixmap pm; + QString fileName; + QSize sz; + uint mode; + uint state; + + in >> num_entries; + for (int i=0; i < num_entries; ++i) { + if (in.atEnd()) { + pixmaps.clear(); + return false; + } + in >> pm; + in >> fileName; + in >> sz; + in >> mode; + in >> state; + if (pm.isNull()) { + addFile(fileName, sz, QIcon::Mode(mode), QIcon::State(state)); + } else { + QPixmapIconEngineEntry pe(fileName, sz, QIcon::Mode(mode), QIcon::State(state)); + pe.pixmap = pm; + pixmaps += pe; + } + } + return true; +} + +bool QPixmapIconEngine::write(QDataStream &out) const +{ + int num_entries = pixmaps.size(); + out << num_entries; + for (int i=0; i < num_entries; ++i) { + if (pixmaps.at(i).pixmap.isNull()) + out << QPixmap(pixmaps.at(i).fileName); + else + out << pixmaps.at(i).pixmap; + out << pixmaps.at(i).fileName; + out << pixmaps.at(i).size; + out << (uint) pixmaps.at(i).mode; + out << (uint) pixmaps.at(i).state; + } + return true; +} + +void QPixmapIconEngine::virtual_hook(int id, void *data) +{ + switch (id) { + case QIconEngineV2::AvailableSizesHook: { + QIconEngineV2::AvailableSizesArgument &arg = + *reinterpret_cast(data); + arg.sizes.clear(); + for (int i = 0; i < pixmaps.size(); ++i) { + QPixmapIconEngineEntry &pe = pixmaps[i]; + if (pe.size == QSize() && pe.pixmap.isNull()) { + pe.pixmap = QPixmap(pe.fileName); + pe.size = pe.pixmap.size(); + } + if (pe.mode == arg.mode && pe.state == arg.state && !pe.size.isEmpty()) + arg.sizes.push_back(pe.size); + } + break; + } + default: + QIconEngineV2::virtual_hook(id, data); + } +} + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QIconEngineFactoryInterface_iid, QLatin1String("/iconengines"), Qt::CaseInsensitive)) +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loaderV2, + (QIconEngineFactoryInterfaceV2_iid, QLatin1String("/iconengines"), Qt::CaseInsensitive)) +#endif + + + +/*! + \class QIcon + + \brief The QIcon class provides scalable icons in different modes + and states. + + \ingroup painting + \ingroup shared + + + A QIcon can generate smaller, larger, active, and disabled pixmaps + from the set of pixmaps it is given. Such pixmaps are used by Qt + widgets to show an icon representing a particular action. + + The simplest use of QIcon is to create one from a QPixmap file or + resource, and then use it, allowing Qt to work out all the required + icon styles and sizes. For example: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 0 + + To undo a QIcon, simply set a null icon in its place: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 1 + + Use the QImageReader::supportedImageFormats() and + QImageWriter::supportedImageFormats() functions to retrieve a + complete list of the supported file formats. + + When you retrieve a pixmap using pixmap(QSize, Mode, State), and no + pixmap for this given size, mode and state has been added with + addFile() or addPixmap(), then QIcon will generate one on the + fly. This pixmap generation happens in a QIconEngineV2. The default + engine scales pixmaps down if required, but never up, and it uses + the current style to calculate a disabled appearance. By using + custom icon engines, you can customize every aspect of generated + icons. With QIconEnginePluginV2 it is possible to register different + icon engines for different file suffixes, making it possible for + third parties to provide additional icon engines to those included + with Qt. + + \note Since Qt 4.2, an icon engine that supports SVG is included. + + \section1 Making Classes that Use QIcon + + If you write your own widgets that have an option to set a small + pixmap, consider allowing a QIcon to be set for that pixmap. The + Qt class QToolButton is an example of such a widget. + + Provide a method to set a QIcon, and when you draw the icon, choose + whichever pixmap is appropriate for the current state of your widget. + For example: + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 2 + + You might also make use of the \c Active mode, perhaps making your + widget \c Active when the mouse is over the widget (see \l + QWidget::enterEvent()), while the mouse is pressed pending the + release that will activate the function, or when it is the currently + selected item. If the widget can be toggled, the "On" mode might be + used to draw a different icon. + + \img icon.png QIcon + + \sa {fowler}{GUI Design Handbook: Iconic Label}, {Icons Example} +*/ + + +/*! + Constructs a null icon. +*/ +QIcon::QIcon() + : d(0) +{ +} + +/*! + Constructs an icon from a \a pixmap. + */ +QIcon::QIcon(const QPixmap &pixmap) + :d(0) +{ + addPixmap(pixmap); +} + +/*! + Constructs a copy of \a other. This is very fast. +*/ +QIcon::QIcon(const QIcon &other) + :d(other.d) +{ + if (d) + d->ref.ref(); +} + +/*! + Constructs an icon from the file with the given \a fileName. The + file will be loaded on demand. + + If \a fileName contains a relative path (e.g. the filename only) + the relevant file must be found relative to the runtime working + directory. + + The file name can be either refer to an actual file on disk or to + one of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed images and other resource files in the application's + executable. + + Use the QImageReader::supportedImageFormats() and + QImageWriter::supportedImageFormats() functions to retrieve a + complete list of the supported file formats. +*/ +QIcon::QIcon(const QString &fileName) + : d(0) +{ + addFile(fileName); +} + + +/*! + Creates an icon with a specific icon \a engine. The icon takes + ownership of the engine. +*/ +QIcon::QIcon(QIconEngine *engine) + :d(new QIconPrivate) +{ + d->engine_version = 1; + d->engine = engine; + d->v1RefCount = new QAtomicInt(1); +} + +/*! + Creates an icon with a specific icon \a engine. The icon takes + ownership of the engine. +*/ +QIcon::QIcon(QIconEngineV2 *engine) + :d(new QIconPrivate) +{ + d->engine_version = 2; + d->engine = engine; +} + +/*! + Destroys the icon. +*/ +QIcon::~QIcon() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Assigns the \a other icon to this icon and returns a reference to + this icon. +*/ +QIcon &QIcon::operator=(const QIcon &other) +{ + if (other.d) + other.d->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = other.d; + return *this; +} + +/*! + \fn void QIcon::swap(QIcon &other) + \since 4.8 + + Swaps icon \a other with this icon. This operation is very + fast and never fails. +*/ + +/*! + Returns the icon as a QVariant. +*/ +QIcon::operator QVariant() const +{ + return QVariant(QVariant::Icon, this); +} + +/*! \obsolete + + Returns a number that identifies the contents of this + QIcon object. Distinct QIcon objects can have + the same serial number if they refer to the same contents + (but they don't have to). Also, the serial number of + a QIcon object may change during its lifetime. + + Use cacheKey() instead. + + A null icon always has a serial number of 0. + + Serial numbers are mostly useful in conjunction with caching. + + \sa QPixmap::serialNumber() +*/ + +int QIcon::serialNumber() const +{ + return d ? d->serialNum : 0; +} + +/*! + Returns a number that identifies the contents of this QIcon + object. Distinct QIcon objects can have the same key if + they refer to the same contents. + \since 4.3 + + The cacheKey() will change when the icon is altered via + addPixmap() or addFile(). + + Cache keys are mostly useful in conjunction with caching. + + \sa QPixmap::cacheKey() +*/ +qint64 QIcon::cacheKey() const +{ + if (!d) + return 0; + return (((qint64) d->serialNum) << 32) | ((qint64) (d->detach_no)); +} + +/*! + Returns a pixmap with the requested \a size, \a mode, and \a + state, generating one if necessary. The pixmap might be smaller than + requested, but never larger. + + \sa actualSize(), paint() +*/ +QPixmap QIcon::pixmap(const QSize &size, Mode mode, State state) const +{ + if (!d) + return QPixmap(); + return d->engine->pixmap(size, mode, state); +} + +/*! + \fn QPixmap QIcon::pixmap(int w, int h, Mode mode = Normal, State state = Off) const + + \overload + + Returns a pixmap of size QSize(\a w, \a h). The pixmap might be smaller than + requested, but never larger. +*/ + +/*! + \fn QPixmap QIcon::pixmap(int extent, Mode mode = Normal, State state = Off) const + + \overload + + Returns a pixmap of size QSize(\a extent, \a extent). The pixmap might be smaller + than requested, but never larger. +*/ + +/*! Returns the actual size of the icon for the requested \a size, \a + mode, and \a state. The result might be smaller than requested, but + never larger. + + \sa pixmap(), paint() +*/ +QSize QIcon::actualSize(const QSize &size, Mode mode, State state) const +{ + if (!d) + return QSize(); + return d->engine->actualSize(size, mode, state); +} + + +/*! + Uses the \a painter to paint the icon with specified \a alignment, + required \a mode, and \a state into the rectangle \a rect. + + \sa actualSize(), pixmap() +*/ +void QIcon::paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, Mode mode, State state) const +{ + if (!d || !painter) + return; + QRect alignedRect = QStyle::alignedRect(painter->layoutDirection(), alignment, d->engine->actualSize(rect.size(), mode, state), rect); + d->engine->paint(painter, alignedRect, mode, state); +} + +/*! + \fn void QIcon::paint(QPainter *painter, int x, int y, int w, int h, Qt::Alignment alignment, + Mode mode, State state) const + + \overload + + Paints the icon into the rectangle QRect(\a x, \a y, \a w, \a h). +*/ + +/*! + Returns true if the icon is empty; otherwise returns false. + + An icon is empty if it has neither a pixmap nor a filename. + + Note: Even a non-null icon might not be able to create valid + pixmaps, eg. if the file does not exist or cannot be read. +*/ +bool QIcon::isNull() const +{ + return !d; +} + +/*!\internal + */ +bool QIcon::isDetached() const +{ + return !d || d->ref == 1; +} + +/*! \internal + */ +void QIcon::detach() +{ + if (d) { + if (d->ref != 1) { + QIconPrivate *x = new QIconPrivate; + if (d->engine_version > 1) { + QIconEngineV2 *engine = static_cast(d->engine); + x->engine = engine->clone(); + } else { + x->engine = d->engine; + x->v1RefCount = d->v1RefCount; + x->v1RefCount->ref(); + } + x->engine_version = d->engine_version; + if (!d->ref.deref()) + delete d; + d = x; + } + ++d->detach_no; + } +} + +/*! + Adds \a pixmap to the icon, as a specialization for \a mode and + \a state. + + Custom icon engines are free to ignore additionally added + pixmaps. + + \sa addFile() +*/ +void QIcon::addPixmap(const QPixmap &pixmap, Mode mode, State state) +{ + if (pixmap.isNull()) + return; + if (!d) { + d = new QIconPrivate; + d->engine = new QPixmapIconEngine; + } else { + detach(); + } + d->engine->addPixmap(pixmap, mode, state); +} + + +/*! Adds an image from the file with the given \a fileName to the + icon, as a specialization for \a size, \a mode and \a state. The + file will be loaded on demand. Note: custom icon engines are free + to ignore additionally added pixmaps. + + If \a fileName contains a relative path (e.g. the filename only) + the relevant file must be found relative to the runtime working + directory. + + The file name can be either refer to an actual file on disk or to + one of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed images and other resource files in the application's + executable. + + Use the QImageReader::supportedImageFormats() and + QImageWriter::supportedImageFormats() functions to retrieve a + complete list of the supported file formats. + + Note: When you add a non-empty filename to a QIcon, the icon becomes + non-null, even if the file doesn't exist or points to a corrupt file. + + \sa addPixmap() + */ +void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State state) +{ + if (fileName.isEmpty()) + return; + if (!d) { +#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + QFileInfo info(fileName); + QString suffix = info.suffix(); + if (!suffix.isEmpty()) { + // first try version 2 engines.. + if (QIconEngineFactoryInterfaceV2 *factory = qobject_cast(loaderV2()->instance(suffix))) { + if (QIconEngine *engine = factory->create(fileName)) { + d = new QIconPrivate; + d->engine = engine; + } + } + // ..then fall back and try to load version 1 engines + if (!d) { + if (QIconEngineFactoryInterface *factory = qobject_cast(loader()->instance(suffix))) { + if (QIconEngine *engine = factory->create(fileName)) { + d = new QIconPrivate; + d->engine = engine; + d->engine_version = 1; + d->v1RefCount = new QAtomicInt(1); + } + } + } + } +#endif + // ...then fall back to the default engine + if (!d) { + d = new QIconPrivate; + d->engine = new QPixmapIconEngine; + } + } else { + detach(); + } + d->engine->addFile(fileName, size, mode, state); +} + +/*! + \since 4.5 + + Returns a list of available icon sizes for the specified \a mode and + \a state. +*/ +QList QIcon::availableSizes(Mode mode, State state) const +{ + if (!d || !d->engine || d->engine_version < 2) + return QList(); + QIconEngineV2 *engine = static_cast(d->engine); + return engine->availableSizes(mode, state); +} + +/*! + \since 4.7 + + Returns the name used to create the icon, if available. + + Depending on the way the icon was created, it may have an associated + name. This is the case for icons created with fromTheme() or icons + using a QIconEngine which supports the QIconEngineV2::IconNameHook. + + \sa fromTheme(), QIconEngine +*/ +QString QIcon::name() const +{ + if (!d || !d->engine || d->engine_version < 2) + return QString(); + QIconEngineV2 *engine = static_cast(d->engine); + return engine->iconName(); +} + +/*! + \since 4.6 + + Sets the search paths for icon themes to \a paths. + \sa themeSearchPaths(), fromTheme(), setThemeName() +*/ +void QIcon::setThemeSearchPaths(const QStringList &paths) +{ + QIconLoader::instance()->setThemeSearchPath(paths); +} + +/*! + \since 4.6 + + Returns the search paths for icon themes. + + The default value will depend on the platform: + + On X11, the search path will use the XDG_DATA_DIRS environment + variable if available. + + By default all platforms will have the resource directory + \c{:\icons} as a fallback. You can use "rcc -project" to generate a + resource file from your icon theme. + + \sa setThemeSearchPaths(), fromTheme(), setThemeName() +*/ +QStringList QIcon::themeSearchPaths() +{ + return QIconLoader::instance()->themeSearchPaths(); +} + +/*! + \since 4.6 + + Sets the current icon theme to \a name. + + The \a name should correspond to a directory name in the + themeSearchPath() containing an index.theme + file describing it's contents. + + \sa themeSearchPaths(), themeName() +*/ +void QIcon::setThemeName(const QString &name) +{ + QIconLoader::instance()->setThemeName(name); +} + +/*! + \since 4.6 + + Returns the name of the current icon theme. + + On X11, the current icon theme depends on your desktop + settings. On other platforms it is not set by default. + + \sa setThemeName(), themeSearchPaths(), fromTheme(), + hasThemeIcon() +*/ +QString QIcon::themeName() +{ + return QIconLoader::instance()->themeName(); +} + +/*! + \since 4.6 + + Returns the QIcon corresponding to \a name in the current + icon theme. If no such icon is found in the current theme + \a fallback is returned instead. + + The latest version of the freedesktop icon specification and naming + specification can be obtained here: + + \list + \o \l{http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html} + \o \l{http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html} + \endlist + + To fetch an icon from the current icon theme: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 3 + + Or if you want to provide a guaranteed fallback for platforms that + do not support theme icons, you can use the second argument: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 4 + + \note By default, only X11 will support themed icons. In order to + use themed icons on Mac and Windows, you will have to bundle a + compliant theme in one of your themeSearchPaths() and set the + appropriate themeName(). + + \sa themeName(), setThemeName(), themeSearchPaths() +*/ +QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback) +{ + QIcon icon; + + if (qtIconCache()->contains(name)) { + icon = *qtIconCache()->object(name); + } else { + QIcon *cachedIcon = new QIcon(new QIconLoaderEngine(name)); + qtIconCache()->insert(name, cachedIcon); + icon = *cachedIcon; + } + + // Note the qapp check is to allow lazy loading of static icons + // Supporting fallbacks will not work for this case. + if (qApp && icon.availableSizes().isEmpty()) + return fallback; + + return icon; +} + +/*! + \since 4.6 + + Returns true if there is an icon available for \a name in the + current icon theme, otherwise returns false. + + \sa themeSearchPaths(), fromTheme(), setThemeName() +*/ +bool QIcon::hasThemeIcon(const QString &name) +{ + QIcon icon = fromTheme(name); + + return !icon.isNull(); +} + + +/***************************************************************************** + QIcon stream functions + *****************************************************************************/ +#if !defined(QT_NO_DATASTREAM) +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QIcon &icon) + \relates QIcon + \since 4.2 + + Writes the given \a icon to the given \a stream as a PNG + image. If the icon contains more than one image, all images will + be written to the stream. Note that writing the stream to a file + will not produce a valid image file. +*/ + +QDataStream &operator<<(QDataStream &s, const QIcon &icon) +{ + if (s.version() >= QDataStream::Qt_4_3) { + if (icon.isNull()) { + s << QString(); + } else { + if (icon.d->engine_version > 1) { + QIconEngineV2 *engine = static_cast(icon.d->engine); + s << engine->key(); + engine->write(s); + } else { + // not really supported + qWarning("QIcon: Cannot stream QIconEngine. Use QIconEngineV2 instead."); + } + } + } else if (s.version() == QDataStream::Qt_4_2) { + if (icon.isNull()) { + s << 0; + } else { + QPixmapIconEngine *engine = static_cast(icon.d->engine); + int num_entries = engine->pixmaps.size(); + s << num_entries; + for (int i=0; i < num_entries; ++i) { + s << engine->pixmaps.at(i).pixmap; + s << engine->pixmaps.at(i).fileName; + s << engine->pixmaps.at(i).size; + s << (uint) engine->pixmaps.at(i).mode; + s << (uint) engine->pixmaps.at(i).state; + } + } + } else { + s << QPixmap(icon.pixmap(22,22)); + } + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QIcon &icon) + \relates QIcon + \since 4.2 + + Reads an image, or a set of images, from the given \a stream into + the given \a icon. +*/ + +QDataStream &operator>>(QDataStream &s, QIcon &icon) +{ + if (s.version() >= QDataStream::Qt_4_3) { + icon = QIcon(); + QString key; + s >> key; + if (key == QLatin1String("QPixmapIconEngine")) { + icon.d = new QIconPrivate; + QIconEngineV2 *engine = new QPixmapIconEngine; + icon.d->engine = engine; + engine->read(s); + } else if (key == QLatin1String("QIconLoaderEngine")) { + icon.d = new QIconPrivate; + QIconEngineV2 *engine = new QIconLoaderEngine(); + icon.d->engine = engine; + engine->read(s); +#if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + } else if (QIconEngineFactoryInterfaceV2 *factory = qobject_cast(loaderV2()->instance(key))) { + if (QIconEngineV2 *engine= factory->create()) { + icon.d = new QIconPrivate; + icon.d->engine = engine; + engine->read(s); + } +#endif + } + } else if (s.version() == QDataStream::Qt_4_2) { + icon = QIcon(); + int num_entries; + QPixmap pm; + QString fileName; + QSize sz; + uint mode; + uint state; + + s >> num_entries; + for (int i=0; i < num_entries; ++i) { + s >> pm; + s >> fileName; + s >> sz; + s >> mode; + s >> state; + if (pm.isNull()) + icon.addFile(fileName, sz, QIcon::Mode(mode), QIcon::State(state)); + else + icon.addPixmap(pm, QIcon::Mode(mode), QIcon::State(state)); + } + } else { + QPixmap pm; + s >> pm; + icon.addPixmap(pm); + } + return s; +} + +#endif //QT_NO_DATASTREAM + + +#ifdef QT3_SUPPORT + +static int widths[2] = { 22, 32 }; +static int heights[2] = { 22, 32 }; + +static QSize pixmapSizeHelper(QIcon::Size which) +{ + int i = 0; + if (which == QIcon::Large) + i = 1; + return QSize(widths[i], heights[i]); +} + +/*! + \enum QIcon::Size + \compat + + \value Small Use QStyle::pixelMetric(QStyle::PM_SmallIconSize) instead. + \value Large Use QStyle::pixelMetric(QStyle::PM_LargeIconSize) instead. + \value Automatic N/A. +*/ + +/*! + Use pixmap(QSize(...), \a mode, \a state), where the first + argument is an appropriate QSize instead of a \l Size value. + + \sa pixmapSize() +*/ +QPixmap QIcon::pixmap(Size size, Mode mode, State state) const +{ return pixmap(pixmapSizeHelper(size), mode, state); } + +/*! + Use pixmap(QSize(...), mode, \a state), where the first argument + is an appropriate QSize instead of a \l Size value, and the + second argument is QIcon::Normal or QIcon::Disabled, depending on + the value of \a enabled. + + \sa pixmapSize() +*/ +QPixmap QIcon::pixmap(Size size, bool enabled, State state) const +{ return pixmap(pixmapSizeHelper(size), enabled ? Normal : Disabled, state); } + +/*! + Use one of the other pixmap() overloads. +*/ +QPixmap QIcon::pixmap() const +{ return pixmap(pixmapSizeHelper(Small), Normal, Off); } + +/*! + The pixmap() function now takes a QSize instead of a QIcon::Size, + so there is no need for this function in new code. +*/ +void QIcon::setPixmapSize(Size which, const QSize &size) +{ + int i = 0; + if (which == Large) + i = 1; + widths[i] = size.width(); + heights[i] = size.height(); +} + +/*! + Use QStyle::pixelMetric() with QStyle::PM_SmallIconSize or + QStyle::PM_LargeIconSize as the first argument, depending on \a + which. +*/ +QSize QIcon::pixmapSize(Size which) +{ + return pixmapSizeHelper(which); +} + +/*! + \fn void QIcon::reset(const QPixmap &pixmap, Size size) + + Use the constructor that takes a QPixmap and operator=(). +*/ + +/*! + \fn void QIcon::setPixmap(const QPixmap &pixmap, Size size, Mode mode, State state) + + Use addPixmap(\a pixmap, \a mode, \a state) instead. The \a size + parameter is ignored. +*/ + +/*! + \fn void QIcon::setPixmap(const QString &fileName, Size size, Mode mode, State state) + + Use addFile(\a fileName, \a mode, \a state) instead. The \a size + parameter is ignored. +*/ + +#endif // QT3_SUPPORT + +/*! + \fn DataPtr &QIcon::data_ptr() + \internal +*/ + +/*! + \typedef QIcon::DataPtr + \internal +*/ + +QT_END_NAMESPACE +#endif //QT_NO_ICON diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h new file mode 100644 index 0000000000..170559182b --- /dev/null +++ b/src/gui/image/qicon.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICON_H +#define QICON_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QIconPrivate; +class QIconEngine; +class QIconEngineV2; + +class Q_GUI_EXPORT QIcon +{ +public: + enum Mode { Normal, Disabled, Active, Selected }; + enum State { On, Off }; + + QIcon(); + QIcon(const QPixmap &pixmap); + QIcon(const QIcon &other); + explicit QIcon(const QString &fileName); // file or resource name + explicit QIcon(QIconEngine *engine); + explicit QIcon(QIconEngineV2 *engine); + ~QIcon(); + QIcon &operator=(const QIcon &other); +#ifdef Q_COMPILER_RVALUE_REFS + inline QIcon &operator=(QIcon &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QIcon &other) { qSwap(d, other.d); } + + operator QVariant() const; + + QPixmap pixmap(const QSize &size, Mode mode = Normal, State state = Off) const; + inline QPixmap pixmap(int w, int h, Mode mode = Normal, State state = Off) const + { return pixmap(QSize(w, h), mode, state); } + inline QPixmap pixmap(int extent, Mode mode = Normal, State state = Off) const + { return pixmap(QSize(extent, extent), mode, state); } + + QSize actualSize(const QSize &size, Mode mode = Normal, State state = Off) const; + + QString name() const; + + void paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment = Qt::AlignCenter, Mode mode = Normal, State state = Off) const; + inline void paint(QPainter *painter, int x, int y, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, Mode mode = Normal, State state = Off) const + { paint(painter, QRect(x, y, w, h), alignment, mode, state); } + + bool isNull() const; + bool isDetached() const; + void detach(); + + int serialNumber() const; + qint64 cacheKey() const; + + void addPixmap(const QPixmap &pixmap, Mode mode = Normal, State state = Off); + void addFile(const QString &fileName, const QSize &size = QSize(), Mode mode = Normal, State state = Off); + + QList availableSizes(Mode mode = Normal, State state = Off) const; + + static QIcon fromTheme(const QString &name, const QIcon &fallback = QIcon()); + static bool hasThemeIcon(const QString &name); + + static QStringList themeSearchPaths(); + static void setThemeSearchPaths(const QStringList &searchpath); + + static QString themeName(); + static void setThemeName(const QString &path); + + +#ifdef QT3_SUPPORT + enum Size { Small, Large, Automatic = Small }; + static QT3_SUPPORT void setPixmapSize(Size which, const QSize &size); + static QT3_SUPPORT QSize pixmapSize(Size which); + inline QT3_SUPPORT void reset(const QPixmap &pixmap, Size /*size*/) { *this = QIcon(pixmap); } + inline QT3_SUPPORT void setPixmap(const QPixmap &pixmap, Size, Mode mode = Normal, State state = Off) + { addPixmap(pixmap, mode, state); } + inline QT3_SUPPORT void setPixmap(const QString &fileName, Size, Mode mode = Normal, State state = Off) + { addPixmap(QPixmap(fileName), mode, state); } + QT3_SUPPORT QPixmap pixmap(Size size, Mode mode, State state = Off) const; + QT3_SUPPORT QPixmap pixmap(Size size, bool enabled, State state = Off) const; + QT3_SUPPORT QPixmap pixmap() const; +#endif + + Q_DUMMY_COMPARISON_OPERATOR(QIcon) + +private: + QIconPrivate *d; +#if !defined(QT_NO_DATASTREAM) + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QIcon &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QIcon &); +#endif + +public: + typedef QIconPrivate * DataPtr; + inline DataPtr &data_ptr() { return d; } +}; + +Q_DECLARE_SHARED(QIcon) +Q_DECLARE_TYPEINFO(QIcon, Q_MOVABLE_TYPE); + +#if !defined(QT_NO_DATASTREAM) +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QIcon &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QIcon &); +#endif + +#ifdef QT3_SUPPORT +typedef QIcon QIconSet; +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QICON_H diff --git a/src/gui/image/qicon_p.h b/src/gui/image/qicon_p.h new file mode 100644 index 0000000000..0bf7e65cc1 --- /dev/null +++ b/src/gui/image/qicon_p.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICON_P_H +#define QICON_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 +#include +#include +#include +#include +#include + +#ifndef QT_NO_ICON +QT_BEGIN_NAMESPACE + +class QIconPrivate +{ +public: + QIconPrivate(); + + ~QIconPrivate() { + if (engine_version == 1) { + if (!v1RefCount->deref()) { + delete engine; + delete v1RefCount; + } + } else if (engine_version == 2) { + delete engine; + } + } + + QIconEngine *engine; + + QAtomicInt ref; + int serialNum; + int detach_no; + int engine_version; + + QAtomicInt *v1RefCount; +}; + + +struct QPixmapIconEngineEntry +{ + QPixmapIconEngineEntry():mode(QIcon::Normal), state(QIcon::Off){} + QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) + :pixmap(pm), size(pm.size()), mode(m), state(s){} + QPixmapIconEngineEntry(const QString &file, const QSize &sz = QSize(), QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) + :fileName(file), size(sz), mode(m), state(s){} + QPixmap pixmap; + QString fileName; + QSize size; + QIcon::Mode mode; + QIcon::State state; + bool isNull() const {return (fileName.isEmpty() && pixmap.isNull()); } +}; + + + +class QPixmapIconEngine : public QIconEngineV2 { +public: + QPixmapIconEngine(); + QPixmapIconEngine(const QPixmapIconEngine &); + ~QPixmapIconEngine(); + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QPixmapIconEngineEntry *bestMatch(const QSize &size, QIcon::Mode mode, QIcon::State state, bool sizeOnly); + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state); + void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state); + + // v2 functions + QString key() const; + QIconEngineV2 *clone() const; + bool read(QDataStream &in); + bool write(QDataStream &out) const; + void virtual_hook(int id, void *data); + +private: + QPixmapIconEngineEntry *tryMatch(const QSize &size, QIcon::Mode mode, QIcon::State state); + QVector pixmaps; + + friend QDataStream &operator<<(QDataStream &s, const QIcon &icon); + friend class QIconThemeEngine; +}; + +QT_END_NAMESPACE +#endif //QT_NO_ICON +#endif // QICON_P_H diff --git a/src/gui/image/qiconengine.cpp b/src/gui/image/qiconengine.cpp new file mode 100644 index 0000000000..6168a83940 --- /dev/null +++ b/src/gui/image/qiconengine.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qiconengine.h" +#include "qpainter.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QIconEngine + + \brief The QIconEngine class provides an abstract base class for QIcon renderers. + + \ingroup painting + + \bold {Use QIconEngineV2 instead.} + + An icon engine provides the rendering functions for a QIcon. Each icon has a + corresponding icon engine that is responsible for drawing the icon with a + requested size, mode and state. + + The icon is rendered by the paint() function, and the icon can additionally be + obtained as a pixmap with the pixmap() function (the default implementation + simply uses paint() to achieve this). The addPixmap() function can be used to + add new pixmaps to the icon engine, and is used by QIcon to add specialized + custom pixmaps. + + The paint(), pixmap(), and addPixmap() functions are all virtual, and can + therefore be reimplemented in subclasses of QIconEngine. + + \sa QIconEngineV2, QIconEnginePlugin + +*/ + +/*! + \fn virtual void QIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) = 0; + + Uses the given \a painter to paint the icon with the required \a mode and + \a state into the rectangle \a rect. +*/ + +/*! Returns the actual size of the icon the engine provides for the + requested \a size, \a mode and \a state. The default implementation + returns the given \a size. + */ +QSize QIconEngine::actualSize(const QSize &size, QIcon::Mode /*mode*/, QIcon::State /*state*/) +{ + return size; +} + + +/*! + Destroys the icon engine. + */ +QIconEngine::~QIconEngine() +{ +} + + +/*! + Returns the icon as a pixmap with the required \a size, \a mode, + and \a state. The default implementation creates a new pixmap and + calls paint() to fill it. +*/ +QPixmap QIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + QPixmap pm(size); + { + QPainter p(&pm); + paint(&p, QRect(QPoint(0,0),size), mode, state); + } + return pm; +} + +/*! + Called by QIcon::addPixmap(). Adds a specialized \a pixmap for the given + \a mode and \a state. The default pixmap-based engine stores any supplied + pixmaps, and it uses them instead of scaled pixmaps if the size of a pixmap + matches the size of icon requested. Custom icon engines that implement + scalable vector formats are free to ignores any extra pixmaps. + */ +void QIconEngine::addPixmap(const QPixmap &/*pixmap*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) +{ +} + + +/*! Called by QIcon::addFile(). Adds a specialized pixmap from the + file with the given \a fileName, \a size, \a mode and \a state. The + default pixmap-based engine stores any supplied file names, and it + loads the pixmaps on demand instead of using scaled pixmaps if the + size of a pixmap matches the size of icon requested. Custom icon + engines that implement scalable vector formats are free to ignores + any extra files. + */ +void QIconEngine::addFile(const QString &/*fileName*/, const QSize &/*size*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) +{ +} + + + +// version 2 functions + + +/*! + \class QIconEngineV2 + + \brief The QIconEngineV2 class provides an abstract base class for QIcon renderers. + + \ingroup painting + \since 4.3 + + An icon engine renders \l{QIcon}s. With icon engines, you can + customize icons. Qt provides a default engine that makes icons + adhere to the current style by scaling the icons and providing a + disabled appearance. + + An engine is installed on an icon either through a QIcon + constructor or through a QIconEnginePluginV2. The plugins are used + by Qt if a specific engine is not given when the icon is created. + See the QIconEngineV2 class description to learn how to create + icon engine plugins. + + An icon engine provides the rendering functions for a QIcon. Each + icon has a corresponding icon engine that is responsible for drawing + the icon with a requested size, mode and state. + + QIconEngineV2 extends the API of QIconEngine to allow streaming of + the icon engine contents, and should be used instead of QIconEngine + for implementing new icon engines. + + \sa QIconEnginePluginV2 + +*/ + +/*! + \enum QIconEngineV2::IconEngineHook + \since 4.5 + + These enum values are used for virtual_hook() to allow additional + queries to icon engine without breaking binary compatibility. + + \value AvailableSizesHook Allows to query the sizes of the + contained pixmaps for pixmap-based engines. The \a data argument + of the virtual_hook() function is a AvailableSizesArgument pointer + that should be filled with icon sizes. Engines that work in terms + of a scalable, vectorial format normally return an empty list. + + \value IconNameHook Allows to query the name used to create the + icon, for example when instantiating an icon using + QIcon::fromTheme(). + + \sa virtual_hook() + */ + +/*! + \class QIconEngineV2::AvailableSizesArgument + \since 4.5 + + This struct represents arguments to virtual_hook() function when + \a id parameter is QIconEngineV2::AvailableSizesHook. + + \sa virtual_hook(), QIconEngineV2::IconEngineHook + */ + +/*! + \variable QIconEngineV2::AvailableSizesArgument::mode + \brief the requested mode of an image. + + \sa QIcon::Mode +*/ + +/*! + \variable QIconEngineV2::AvailableSizesArgument::state + \brief the requested state of an image. + + \sa QIcon::State +*/ + +/*! + \variable QIconEngineV2::AvailableSizesArgument::sizes + + \brief image sizes that are available with specified \a mode and + \a state. This is an output parameter and is filled after call to + virtual_hook(). Engines that work in terms of a scalable, + vectorial format normally return an empty list. +*/ + + +/*! + Returns a key that identifies this icon engine. + */ +QString QIconEngineV2::key() const +{ + return QString(); +} + +/*! + Returns a clone of this icon engine. + */ +QIconEngineV2 *QIconEngineV2::clone() const +{ + return 0; +} + +/*! + Reads icon engine contents from the QDataStream \a in. Returns + true if the contents were read; otherwise returns false. + + QIconEngineV2's default implementation always return false. + */ +bool QIconEngineV2::read(QDataStream &) +{ + return false; +} + +/*! + Writes the contents of this engine to the QDataStream \a out. + Returns true if the contents were written; otherwise returns false. + + QIconEngineV2's default implementation always return false. + */ +bool QIconEngineV2::write(QDataStream &) const +{ + return false; +} + +/*! + \since 4.5 + + Additional method to allow extending QIconEngineV2 without + adding new virtual methods (and without breaking binary compatibility). + The actual action and format of \a data depends on \a id argument + which is in fact a constant from IconEngineHook enum. + + \sa IconEngineHook +*/ +void QIconEngineV2::virtual_hook(int id, void *data) +{ + switch (id) { + case QIconEngineV2::AvailableSizesHook: { + QIconEngineV2::AvailableSizesArgument &arg = + *reinterpret_cast(data); + arg.sizes.clear(); + break; + } + default: + break; + } +} + +/*! + \since 4.5 + + Returns sizes of all images that are contained in the engine for the + specific \a mode and \a state. + + \note This is a helper method and the actual work is done by + virtual_hook() method, hence this method depends on icon engine support + and may not work with all icon engines. + */ +QList QIconEngineV2::availableSizes(QIcon::Mode mode, QIcon::State state) +{ + AvailableSizesArgument arg; + arg.mode = mode; + arg.state = state; + virtual_hook(QIconEngineV2::AvailableSizesHook, reinterpret_cast(&arg)); + return arg.sizes; +} + +/*! + \since 4.7 + + Returns the name used to create the engine, if available. + + \note This is a helper method and the actual work is done by + virtual_hook() method, hence this method depends on icon engine support + and may not work with all icon engines. + */ +QString QIconEngineV2::iconName() +{ + QString name; + virtual_hook(QIconEngineV2::IconNameHook, reinterpret_cast(&name)); + return name; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qiconengine.h b/src/gui/image/qiconengine.h new file mode 100644 index 0000000000..12caea8a20 --- /dev/null +++ b/src/gui/image/qiconengine.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICONENGINE_H +#define QICONENGINE_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QIconEngine +{ +public: + virtual ~QIconEngine(); + virtual void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) = 0; + virtual QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + + virtual void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state); + virtual void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state); + +#if 0 + virtual int frameCount(QIcon::Mode fromMode, QIcon::State fromState, QIcon::Mode toMode, QIcon::State toState); + virtual void paintFrame(QPainter *painter, const QRect &rect, int frameNumber, QIcon::Mode fromMode, QIcon::State fromState, QIcon::Mode toMode, QIcon::State toState); +#endif +}; + +// ### Qt 5: move the below into QIconEngine +class Q_GUI_EXPORT QIconEngineV2 : public QIconEngine +{ +public: + virtual QString key() const; + virtual QIconEngineV2 *clone() const; + virtual bool read(QDataStream &in); + virtual bool write(QDataStream &out) const; + virtual void virtual_hook(int id, void *data); + +public: + enum IconEngineHook { AvailableSizesHook = 1, IconNameHook }; + + struct AvailableSizesArgument + { + QIcon::Mode mode; + QIcon::State state; + QList sizes; + }; + + // ### Qt 5: make this function const and virtual. + QList availableSizes(QIcon::Mode mode = QIcon::Normal, + QIcon::State state = QIcon::Off); + + // ### Qt 5: make this function const and virtual. + QString iconName(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QICONENGINE_H diff --git a/src/gui/image/qiconengineplugin.cpp b/src/gui/image/qiconengineplugin.cpp new file mode 100644 index 0000000000..7c8c3a3c1a --- /dev/null +++ b/src/gui/image/qiconengineplugin.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qiconengineplugin.h" +#include "qiconengine.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QIconEnginePlugin + \brief The QIconEnginePlugin class provides an abstract base for custom QIconEngine plugins. + + \ingroup plugins + + \bold {Use QIconEnginePluginV2 instead.} + + The icon engine plugin is a simple plugin interface that makes it easy to + create custom icon engines that can be loaded dynamically into applications + through QIcon. QIcon uses the file or resource name's suffix to determine + what icon engine to use. + + Writing a icon engine plugin is achieved by subclassing this base class, + reimplementing the pure virtual functions keys() and create(), and + exporting the class with the Q_EXPORT_PLUGIN2() macro. + + \sa {How to Create Qt Plugins} +*/ + +/*! + \fn QStringList QIconEnginePlugin::keys() const + + Returns a list of icon engine keys that this plugin supports. The keys correspond + to the suffix of the file or resource name used when the plugin was created. + Keys are case insensitive. + + \sa create() +*/ + +/*! + \fn QIconEngine* QIconEnginePlugin::create(const QString& filename) + + Creates and returns a QIconEngine object for the icon with the given + \a filename. + + \sa keys() +*/ + +/*! + Constructs a icon engine plugin with the given \a parent. This is invoked + automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QIconEnginePlugin::QIconEnginePlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the icon engine plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QIconEnginePlugin::~QIconEnginePlugin() +{ +} + +// version 2 + +/*! + \class QIconEnginePluginV2 + \brief The QIconEnginePluginV2 class provides an abstract base for custom QIconEngineV2 plugins. + + \ingroup plugins + \since 4.3 + + Icon engine plugins produces \l{QIconEngine}s for \l{QIcon}s; an + icon engine is used to render the icon. The keys that identifies + the engines the plugin can create are suffixes of + icon filenames; they are returned by keys(). The create() function + receives the icon filename to return an engine for; it should + return 0 if it cannot produce an engine for the file. + + Writing an icon engine plugin is achieved by inheriting + QIconEnginePluginV2, reimplementing keys() and create(), and + adding the Q_EXPORT_PLUGIN2() macro. + + You should ensure that you do not duplicate keys. Qt will query + the plugins for icon engines in the order in which the plugins are + found during plugin search (see the plugins \l{How to Create Qt + Plugins}{overview document}). + + \sa {How to Create Qt Plugins} +*/ + +/*! + \fn QStringList QIconEnginePluginV2::keys() const + + Returns a list of icon engine keys that this plugin supports. The keys correspond + to the suffix of the file or resource name used when the plugin was created. + Keys are case insensitive. + + \sa create() +*/ + +/*! + \fn QIconEngineV2* QIconEnginePluginV2::create(const QString& filename = QString()) + + Creates and returns a QIconEngine object for the icon with the given + \a filename. + + \sa keys() +*/ + +/*! + Constructs a icon engine plugin with the given \a parent. This is invoked + automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QIconEnginePluginV2::QIconEnginePluginV2(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the icon engine plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QIconEnginePluginV2::~QIconEnginePluginV2() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qiconengineplugin.h b/src/gui/image/qiconengineplugin.h new file mode 100644 index 0000000000..e892a38f7b --- /dev/null +++ b/src/gui/image/qiconengineplugin.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICONENGINEPLUGIN_H +#define QICONENGINEPLUGIN_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QIconEngine; +class QIconEngineV2; + +struct Q_GUI_EXPORT QIconEngineFactoryInterface : public QFactoryInterface +{ + virtual QIconEngine *create(const QString &filename) = 0; +}; + +#define QIconEngineFactoryInterface_iid \ + "com.trolltech.Qt.QIconEngineFactoryInterface" +Q_DECLARE_INTERFACE(QIconEngineFactoryInterface, QIconEngineFactoryInterface_iid) + +class Q_GUI_EXPORT QIconEnginePlugin : public QObject, public QIconEngineFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QIconEngineFactoryInterface:QFactoryInterface) +public: + QIconEnginePlugin(QObject *parent = 0); + ~QIconEnginePlugin(); + + virtual QStringList keys() const = 0; + virtual QIconEngine *create(const QString &filename) = 0; +}; + +// ### Qt 5: remove version 2 +struct Q_GUI_EXPORT QIconEngineFactoryInterfaceV2 : public QFactoryInterface +{ + virtual QIconEngineV2 *create(const QString &filename = QString()) = 0; +}; + +#define QIconEngineFactoryInterfaceV2_iid \ + "com.trolltech.Qt.QIconEngineFactoryInterfaceV2" +Q_DECLARE_INTERFACE(QIconEngineFactoryInterfaceV2, QIconEngineFactoryInterfaceV2_iid) + +class Q_GUI_EXPORT QIconEnginePluginV2 : public QObject, public QIconEngineFactoryInterfaceV2 +{ + Q_OBJECT + Q_INTERFACES(QIconEngineFactoryInterfaceV2:QFactoryInterface) +public: + QIconEnginePluginV2(QObject *parent = 0); + ~QIconEnginePluginV2(); + + virtual QStringList keys() const = 0; + virtual QIconEngineV2 *create(const QString &filename = QString()) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QICONENGINEPLUGIN_H diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp new file mode 100644 index 0000000000..0a42f0af78 --- /dev/null +++ b/src/gui/image/qiconloader.cpp @@ -0,0 +1,570 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QT_NO_ICON +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_WS_MAC +#include +#endif + +#ifdef Q_WS_X11 +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) + +/* Theme to use in last resort, if the theme does not have the icon, neither the parents */ +static QString fallbackTheme() +{ +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_GNOME) { + return QLatin1String("gnome"); + } else if (X11->desktopEnvironment == DE_KDE) { + return X11->desktopVersion >= 4 + ? QString::fromLatin1("oxygen") + : QString::fromLatin1("crystalsvg"); + } else { + return QLatin1String("hicolor"); + } +#endif + return QString(); +} + +QIconLoader::QIconLoader() : + m_themeKey(1), m_supportsSvg(false), m_initialized(false) +{ +} + +// We lazily initialize the loader to make static icons +// work. Though we do not officially support this. +void QIconLoader::ensureInitialized() +{ + if (!m_initialized) { + m_initialized = true; + + Q_ASSERT(qApp); + + m_systemTheme = qt_guiPlatformPlugin()->systemIconThemeName(); + if (m_systemTheme.isEmpty()) + m_systemTheme = fallbackTheme(); +#ifndef QT_NO_LIBRARY + QFactoryLoader iconFactoryLoader(QIconEngineFactoryInterfaceV2_iid, + QLatin1String("/iconengines"), + Qt::CaseInsensitive); + if (iconFactoryLoader.keys().contains(QLatin1String("svg"))) + m_supportsSvg = true; +#endif //QT_NO_LIBRARY + } +} + +QIconLoader *QIconLoader::instance() +{ + return iconLoaderInstance(); +} + +// Queries the system theme and invalidates existing +// icons if the theme has changed. +void QIconLoader::updateSystemTheme() +{ + // Only change if this is not explicitly set by the user + if (m_userTheme.isEmpty()) { + QString theme = qt_guiPlatformPlugin()->systemIconThemeName(); + if (theme.isEmpty()) + theme = fallbackTheme(); + if (theme != m_systemTheme) { + m_systemTheme = theme; + invalidateKey(); + } + } +} + +void QIconLoader::setThemeName(const QString &themeName) +{ + m_userTheme = themeName; + invalidateKey(); +} + +void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) +{ + m_iconDirs = searchPaths; + themeList.clear(); + invalidateKey(); +} + +QStringList QIconLoader::themeSearchPaths() const +{ + if (m_iconDirs.isEmpty()) { + m_iconDirs = qt_guiPlatformPlugin()->iconThemeSearchPaths(); + // Always add resource directory as search path + m_iconDirs.append(QLatin1String(":/icons")); + } + return m_iconDirs; +} + +QIconTheme::QIconTheme(const QString &themeName) + : m_valid(false) +{ + QFile themeIndex; + + QList keyList; + QStringList iconDirs = QIcon::themeSearchPaths(); + for ( int i = 0 ; i < iconDirs.size() ; ++i) { + QDir iconDir(iconDirs[i]); + QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; + themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); + if (themeIndex.exists()) { + m_contentDir = themeDir; + m_valid = true; + break; + } + } +#ifndef QT_NO_SETTINGS + if (themeIndex.exists()) { + const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); + QStringListIterator keyIterator(indexReader.allKeys()); + while (keyIterator.hasNext()) { + + const QString key = keyIterator.next(); + if (key.endsWith(QLatin1String("/Size"))) { + // Note the QSettings ini-format does not accept + // slashes in key names, hence we have to cheat + if (int size = indexReader.value(key).toInt()) { + QString directoryKey = key.left(key.size() - 5); + QIconDirInfo dirInfo(directoryKey); + dirInfo.size = size; + QString type = indexReader.value(directoryKey + + QLatin1String("/Type") + ).toString(); + + if (type == QLatin1String("Fixed")) + dirInfo.type = QIconDirInfo::Fixed; + else if (type == QLatin1String("Scalable")) + dirInfo.type = QIconDirInfo::Scalable; + else + dirInfo.type = QIconDirInfo::Threshold; + + dirInfo.threshold = indexReader.value(directoryKey + + QLatin1String("/Threshold"), + 2).toInt(); + + dirInfo.minSize = indexReader.value(directoryKey + + QLatin1String("/MinSize"), + size).toInt(); + + dirInfo.maxSize = indexReader.value(directoryKey + + QLatin1String("/MaxSize"), + size).toInt(); + m_keyList.append(dirInfo); + } + } + } + + // Parent themes provide fallbacks for missing icons + m_parents = indexReader.value( + QLatin1String("Icon Theme/Inherits")).toStringList(); + + // Ensure a default platform fallback for all themes + if (m_parents.isEmpty()) + m_parents.append(fallbackTheme()); + + // Ensure that all themes fall back to hicolor + if (!m_parents.contains(QLatin1String("hicolor"))) + m_parents.append(QLatin1String("hicolor")); + } +#endif //QT_NO_SETTINGS +} + +QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const +{ + QThemeIconEntries entries; + Q_ASSERT(!themeName.isEmpty()); + + QPixmap pixmap; + + // Used to protect against potential recursions + visited << themeName; + + QIconTheme theme = themeList.value(themeName); + if (!theme.isValid()) { + theme = QIconTheme(themeName); + if (!theme.isValid()) + theme = QIconTheme(fallbackTheme()); + + themeList.insert(themeName, theme); + } + + QString contentDir = theme.contentDir() + QLatin1Char('/'); + QList subDirs = theme.keyList(); + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + + // Add all relevant files + for (int i = 0; i < subDirs.size() ; ++i) { + const QIconDirInfo &dirInfo = subDirs.at(i); + QString subdir = dirInfo.path; + QDir currentDir(contentDir + subdir); + if (currentDir.exists(iconName + pngext)) { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + pngext); + // Notice we ensure that pixmap entries always come before + // scalable to preserve search order afterwards + entries.prepend(iconEntry); + } else if (m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + } + } + + if (entries.isEmpty()) { + const QStringList parents = theme.parents(); + // Search recursively through inherited themes + for (int i = 0 ; i < parents.size() ; ++i) { + + const QString parentTheme = parents.at(i).trimmed(); + + if (!visited.contains(parentTheme)) // guard against recursion + entries = findIconHelper(parentTheme, iconName, visited); + + if (!entries.isEmpty()) // success + break; + } + } + return entries; +} + +QThemeIconEntries QIconLoader::loadIcon(const QString &name) const +{ + if (!themeName().isEmpty()) { + QStringList visited; + return findIconHelper(themeName(), name, visited); + } + + return QThemeIconEntries(); +} + + +// -------- Icon Loader Engine -------- // + + +QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) + : m_iconName(iconName), m_key(0) +{ +} + +QIconLoaderEngine::~QIconLoaderEngine() +{ + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + Q_ASSERT(m_entries.size() == 0); +} + +QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other) + : QIconEngineV2(other), + m_iconName(other.m_iconName), + m_key(0) +{ +} + +QIconEngineV2 *QIconLoaderEngine::clone() const +{ + return new QIconLoaderEngine(*this); +} + +bool QIconLoaderEngine::read(QDataStream &in) { + in >> m_iconName; + return true; +} + +bool QIconLoaderEngine::write(QDataStream &out) const +{ + out << m_iconName; + return true; +} + +bool QIconLoaderEngine::hasIcon() const +{ + return !(m_entries.isEmpty()); +} + +// Lazily load the icon +void QIconLoaderEngine::ensureLoaded() +{ + + iconLoaderInstance()->ensureInitialized(); + + if (!(iconLoaderInstance()->themeKey() == m_key)) { + + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + + Q_ASSERT(m_entries.size() == 0); + m_entries = iconLoaderInstance()->loadIcon(m_iconName); + m_key = iconLoaderInstance()->themeKey(); + } +} + +void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, + QIcon::Mode mode, QIcon::State state) +{ + QSize pixmapSize = rect.size(); +#if defined(Q_WS_MAC) + pixmapSize *= qt_mac_get_scalefactor(); +#endif + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return dir.size == iconsize; + + } else if (dir.type == QIconDirInfo::Scalable) { + return dir.size <= dir.maxSize && + iconsize >= dir.minSize; + + } else if (dir.type == QIconDirInfo::Threshold) { + return iconsize >= dir.size - dir.threshold && + iconsize <= dir.size + dir.threshold; + } + + Q_ASSERT(1); // Not a valid value + return false; +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return qAbs(dir.size - iconsize); + + } else if (dir.type == QIconDirInfo::Scalable) { + if (iconsize < dir.minSize) + return dir.minSize - iconsize; + else if (iconsize > dir.maxSize) + return iconsize - dir.maxSize; + else + return 0; + + } else if (dir.type == QIconDirInfo::Threshold) { + if (iconsize < dir.size - dir.threshold) + return dir.minSize - iconsize; + else if (iconsize > dir.size + dir.threshold) + return iconsize - dir.maxSize; + else return 0; + } + + Q_ASSERT(1); // Not a valid value + return INT_MAX; +} + +QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size) +{ + int iconsize = qMin(size.width(), size.height()); + + // Note that m_entries are sorted so that png-files + // come first + + // Search for exact matches first + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize)) { + return entry; + } + } + + // Find the minimum distance icon + int minimalSize = INT_MAX; + QIconLoaderEngineEntry *closestMatch = 0; + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize); + if (distance < minimalSize) { + minimalSize = distance; + closestMatch = entry; + } + } + return closestMatch; +} + +/* + * Returns the actual icon size. For scalable svg's this is equivalent + * to the requested size. Otherwise the closest match is returned but + * we can never return a bigger size than the requested size. + * + */ +QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) { + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable) + return size; + else { + int result = qMin(dir.size, qMin(size.width(), size.height())); + return QSize(result, result); + } + } + return QIconEngineV2::actualSize(size, mode, state); +} + +QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + // Ensure that basePixmap is lazily initialized before generating the + // key, otherwise the cache key is not unique + if (basePixmap.isNull()) + basePixmap.load(filename); + + int actualSize = qMin(size.width(), size.height()); + + QString key = QLatin1Literal("$qt_theme_") + % HexString(basePixmap.cacheKey()) + % HexString(mode) + % HexString(qApp->palette().cacheKey()) + % HexString(actualSize); + + QPixmap cachedPixmap; + if (QPixmapCache::find(key, &cachedPixmap)) { + return cachedPixmap; + } else { + QStyleOption opt(0); + opt.palette = qApp->palette(); + cachedPixmap = qApp->style()->generatedIconPixmap(mode, basePixmap, &opt); + QPixmapCache::insert(key, cachedPixmap); + } + return cachedPixmap; +} + +QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + if (svgIcon.isNull()) + svgIcon = QIcon(filename); + + // Simply reuse svg icon engine + return svgIcon.pixmap(size, mode, state); +} + +QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) + return entry->pixmap(size, mode, state); + + return QPixmap(); +} + +QString QIconLoaderEngine::key() const +{ + return QLatin1String("QIconLoaderEngine"); +} + +void QIconLoaderEngine::virtual_hook(int id, void *data) +{ + ensureLoaded(); + + switch (id) { + case QIconEngineV2::AvailableSizesHook: + { + QIconEngineV2::AvailableSizesArgument &arg + = *reinterpret_cast(data); + const QList directoryKey = iconLoaderInstance()->theme().keyList(); + arg.sizes.clear(); + + // Gets all sizes from the DirectoryInfo entries + for (int i = 0 ; i < m_entries.size() ; ++i) { + int size = m_entries.at(i)->dir.size; + arg.sizes.append(QSize(size, size)); + } + } + break; + case QIconEngineV2::IconNameHook: + { + QString &name = *reinterpret_cast(data); + name = m_iconName; + } + break; + default: + QIconEngineV2::virtual_hook(id, data); + } +} + +QT_END_NAMESPACE + +#endif //QT_NO_ICON diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h new file mode 100644 index 0000000000..00a3976b40 --- /dev/null +++ b/src/gui/image/qiconloader_p.h @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDESKTOPICON_P_H +#define QDESKTOPICON_P_H + +#ifndef QT_NO_ICON +// +// 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 +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QIconLoader; + +struct QIconDirInfo +{ + enum Type { Fixed, Scalable, Threshold }; + QIconDirInfo(const QString &_path = QString()) : + path(_path), + size(0), + maxSize(0), + minSize(0), + threshold(0), + type(Threshold) {} + QString path; + short size; + short maxSize; + short minSize; + short threshold; + Type type : 4; +}; + +class QIconLoaderEngineEntry + { +public: + virtual ~QIconLoaderEngineEntry() {} + virtual QPixmap pixmap(const QSize &size, + QIcon::Mode mode, + QIcon::State state) = 0; + QString filename; + QIconDirInfo dir; + static int count; +}; + +struct ScalableEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIcon svgIcon; +}; + +struct PixmapEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QPixmap basePixmap; +}; + +typedef QList QThemeIconEntries; + +class QIconLoaderEngine : public QIconEngineV2 +{ +public: + QIconLoaderEngine(const QString& iconName = QString()); + ~QIconLoaderEngine(); + + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIconEngineV2 *clone() const; + bool read(QDataStream &in); + bool write(QDataStream &out) const; + +private: + QString key() const; + bool hasIcon() const; + void ensureLoaded(); + void virtual_hook(int id, void *data); + QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngine(const QIconLoaderEngine &other); + QThemeIconEntries m_entries; + QString m_iconName; + uint m_key; + + friend class QIconLoader; +}; + +class QIconTheme +{ +public: + QIconTheme(const QString &name); + QIconTheme() : m_valid(false) {} + QStringList parents() { return m_parents; } + QList keyList() { return m_keyList; } + QString contentDir() { return m_contentDir; } + bool isValid() { return m_valid; } + +private: + QString m_contentDir; + QList m_keyList; + QStringList m_parents; + bool m_valid; +}; + +class QIconLoader : public QObject +{ +public: + QIconLoader(); + QThemeIconEntries loadIcon(const QString &iconName) const; + uint themeKey() const { return m_themeKey; } + + QString themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } + void setThemeName(const QString &themeName); + QIconTheme theme() { return themeList.value(themeName()); } + void setThemeSearchPath(const QStringList &searchPaths); + QStringList themeSearchPaths() const; + QIconDirInfo dirInfo(int dirindex); + static QIconLoader *instance(); + void updateSystemTheme(); + void invalidateKey() { m_themeKey++; } + void ensureInitialized(); + +private: + QThemeIconEntries findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const; + uint m_themeKey; + bool m_supportsSvg; + bool m_initialized; + + mutable QString m_userTheme; + mutable QString m_systemTheme; + mutable QStringList m_iconDirs; + mutable QHash themeList; +}; + +QT_END_NAMESPACE + +#endif // QDESKTOPICON_P_H + +#endif //QT_NO_ICON diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp new file mode 100644 index 0000000000..50b372ea63 --- /dev/null +++ b/src/gui/image/qimage.cpp @@ -0,0 +1,6717 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qimage.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qmap.h" +#include "qmatrix.h" +#include "qtransform.h" +#include "qimagereader.h" +#include "qimagewriter.h" +#include "qstringlist.h" +#include "qvariant.h" +#include "qimagepixmapcleanuphooks_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +static inline bool checkPixelSize(const QImage::Format format) +{ + switch (format) { + case QImage::Format_ARGB8565_Premultiplied: + return (sizeof(qargb8565) == 3); + case QImage::Format_RGB666: + return (sizeof(qrgb666) == 3); + case QImage::Format_ARGB6666_Premultiplied: + return (sizeof(qargb6666) == 3); + case QImage::Format_RGB555: + return (sizeof(qrgb555) == 2); + case QImage::Format_ARGB8555_Premultiplied: + return (sizeof(qargb8555) == 3); + case QImage::Format_RGB888: + return (sizeof(qrgb888) == 3); + case QImage::Format_RGB444: + return (sizeof(qrgb444) == 2); + case QImage::Format_ARGB4444_Premultiplied: + return (sizeof(qargb4444) == 2); + default: + return true; + } +} + +#if defined(Q_CC_DEC) && defined(__alpha) && (__DECCXX_VER-0 >= 50190001) +#pragma message disable narrowptr +#endif + + +#define QIMAGE_SANITYCHECK_MEMORY(image) \ + if ((image).isNull()) { \ + qWarning("QImage: out of memory, returning null image"); \ + return QImage(); \ + } + + +static QImage rotated90(const QImage &src); +static QImage rotated180(const QImage &src); +static QImage rotated270(const QImage &src); + +// ### Qt 5: remove +Q_GUI_EXPORT qint64 qt_image_id(const QImage &image) +{ + return image.cacheKey(); +} + +const QVector *qt_image_colortable(const QImage &image) +{ + return &image.d->colortable; +} + +QBasicAtomicInt qimage_serial_number = Q_BASIC_ATOMIC_INITIALIZER(1); + +QImageData::QImageData() + : ref(0), width(0), height(0), depth(0), nbytes(0), data(0), +#ifdef QT3_SUPPORT + jumptable(0), +#endif + format(QImage::Format_ARGB32), bytes_per_line(0), + ser_no(qimage_serial_number.fetchAndAddRelaxed(1)), + detach_no(0), + dpmx(qt_defaultDpiX() * 100 / qreal(2.54)), + dpmy(qt_defaultDpiY() * 100 / qreal(2.54)), + offset(0, 0), own_data(true), ro_data(false), has_alpha_clut(false), + is_cached(false), paintEngine(0) +{ +} + +/*! \fn QImageData * QImageData::create(const QSize &size, QImage::Format format, int numColors) + + \internal + + Creates a new image data. + Returns 0 if invalid parameters are give or anything else failed. +*/ +QImageData * QImageData::create(const QSize &size, QImage::Format format, int numColors) +{ + if (!size.isValid() || numColors < 0 || format == QImage::Format_Invalid) + return 0; // invalid parameter(s) + + if (!checkPixelSize(format)) { + qWarning("QImageData::create(): Invalid pixel size for format %i", + format); + return 0; + } + + uint width = size.width(); + uint height = size.height(); + uint depth = qt_depthForFormat(format); + + switch (format) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + numColors = 2; + break; + case QImage::Format_Indexed8: + numColors = qBound(0, numColors, 256); + break; + default: + numColors = 0; + break; + } + + const int bytes_per_line = ((width * depth + 31) >> 5) << 2; // bytes per scanline (must be multiple of 4) + + // sanity check for potential overflows + if (INT_MAX/depth < width + || bytes_per_line <= 0 + || height <= 0 + || INT_MAX/uint(bytes_per_line) < height + || INT_MAX/sizeof(uchar *) < uint(height)) + return 0; + + QScopedPointer d(new QImageData); + d->colortable.resize(numColors); + if (depth == 1) { + d->colortable[0] = QColor(Qt::black).rgba(); + d->colortable[1] = QColor(Qt::white).rgba(); + } else { + for (int i = 0; i < numColors; ++i) + d->colortable[i] = 0; + } + + d->width = width; + d->height = height; + d->depth = depth; + d->format = format; + d->has_alpha_clut = false; + d->is_cached = false; + + d->bytes_per_line = bytes_per_line; + + d->nbytes = d->bytes_per_line*height; + d->data = (uchar *)malloc(d->nbytes); + + if (!d->data) { + return 0; + } + + d->ref.ref(); + return d.take(); + +} + +QImageData::~QImageData() +{ + if (is_cached) + QImagePixmapCleanupHooks::executeImageHooks((((qint64) ser_no) << 32) | ((qint64) detach_no)); + delete paintEngine; + if (data && own_data) + free(data); +#ifdef QT3_SUPPORT + if (jumptable) + free(jumptable); + jumptable = 0; +#endif + data = 0; +} + + +bool QImageData::checkForAlphaPixels() const +{ + bool has_alpha_pixels = false; + + switch (format) { + + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + has_alpha_pixels = has_alpha_clut; + break; + + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: { + uchar *bits = data; + for (int y=0; y, and the QRgb typedef is equivalent to an unsigned + int containing an ARGB quadruplet on the format 0xAARRGGBB. + + 32-bit images have no color table; instead, each pixel contains an + QRgb value. There are three different types of 32-bit images + storing RGB (i.e. 0xffRRGGBB), ARGB and premultiplied ARGB + values respectively. In the premultiplied format the red, green, + and blue channels are multiplied by the alpha component divided by + 255. + + An image's format can be retrieved using the format() + function. Use the convertToFormat() functions to convert an image + into another format. The allGray() and isGrayscale() functions + tell whether a color image can safely be converted to a grayscale + image. + + \section1 Image Transformations + + QImage supports a number of functions for creating a new image + that is a transformed version of the original: The + createAlphaMask() function builds and returns a 1-bpp mask from + the alpha buffer in this image, and the createHeuristicMask() + function creates and returns a 1-bpp heuristic mask for this + image. The latter function works by selecting a color from one of + the corners, then chipping away pixels of that color starting at + all the edges. + + The mirrored() function returns a mirror of the image in the + desired direction, the scaled() returns a copy of the image scaled + to a rectangle of the desired measures, and the rgbSwapped() function + constructs a BGR image from a RGB image. + + The scaledToWidth() and scaledToHeight() functions return scaled + copies of the image. + + The transformed() function returns a copy of the image that is + transformed with the given transformation matrix and + transformation mode: Internally, the transformation matrix is + adjusted to compensate for unwanted translation, + i.e. transformed() returns the smallest image containing all + transformed points of the original image. The static trueMatrix() + function returns the actual matrix used for transforming the + image. + + There are also functions for changing attributes of an image + in-place: + + \table + \header \o Function \o Description + \row + \o setDotsPerMeterX() + \o Defines the aspect ratio by setting the number of pixels that fit + horizontally in a physical meter. + \row + \o setDotsPerMeterY() + \o Defines the aspect ratio by setting the number of pixels that fit + vertically in a physical meter. + \row + \o fill() + \o Fills the entire image with the given pixel value. + \row + \o invertPixels() + \o Inverts all pixel values in the image using the given InvertMode value. + \row + \o setColorTable() + \o Sets the color table used to translate color indexes. Only + monochrome and 8-bit formats. + \row + \o setColorCount() + \o Resizes the color table. Only monochrome and 8-bit formats. + + \endtable + + \section1 Legal Information + + For smooth scaling, the transformed() functions use code based on + smooth scaling algorithm by Daniel M. Duley. + + \legalese + Copyright (C) 2004, 2005 Daniel M. Duley + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + \endlegalese + + \sa QImageReader, QImageWriter, QPixmap, QSvgRenderer, {Image Composition Example}, + {Image Viewer Example}, {Scribble Example}, {Pixelator Example} +*/ + +/*! + \enum QImage::Endian + \compat + + This enum type is used to describe the endianness of the CPU and + graphics hardware. It is provided here for compatibility with earlier versions of Qt. + + Use the \l Format enum instead. The \l Format enum specify the + endianess for monchrome formats, but for other formats the + endianess is not relevant. + + \value IgnoreEndian Endianness does not matter. Useful for some + operations that are independent of endianness. + \value BigEndian Most significant bit first or network byte order, as on SPARC, PowerPC, and Motorola CPUs. + \value LittleEndian Least significant bit first or little endian byte order, as on Intel x86. +*/ + +/*! + \enum QImage::InvertMode + + This enum type is used to describe how pixel values should be + inverted in the invertPixels() function. + + \value InvertRgb Invert only the RGB values and leave the alpha + channel unchanged. + + \value InvertRgba Invert all channels, including the alpha channel. + + \sa invertPixels() +*/ + +/*! + \enum QImage::Format + + The following image formats are available in Qt. Values greater + than QImage::Format_RGB16 were added in Qt 4.4. See the notes + after the table. + + \value Format_Invalid The image is invalid. + \value Format_Mono The image is stored using 1-bit per pixel. Bytes are + packed with the most significant bit (MSB) first. + \value Format_MonoLSB The image is stored using 1-bit per pixel. Bytes are + packed with the less significant bit (LSB) first. + + \value Format_Indexed8 The image is stored using 8-bit indexes + into a colormap. + + \value Format_RGB32 The image is stored using a 32-bit RGB format (0xffRRGGBB). + + \value Format_ARGB32 The image is stored using a 32-bit ARGB + format (0xAARRGGBB). + + \value Format_ARGB32_Premultiplied The image is stored using a premultiplied 32-bit + ARGB format (0xAARRGGBB), i.e. the red, + green, and blue channels are multiplied + by the alpha component divided by 255. (If RR, GG, or BB + has a higher value than the alpha channel, the results are + undefined.) Certain operations (such as image composition + using alpha blending) are faster using premultiplied ARGB32 + than with plain ARGB32. + + \value Format_RGB16 The image is stored using a 16-bit RGB format (5-6-5). + + \value Format_ARGB8565_Premultiplied The image is stored using a + premultiplied 24-bit ARGB format (8-5-6-5). + \value Format_RGB666 The image is stored using a 24-bit RGB format (6-6-6). + The unused most significant bits is always zero. + \value Format_ARGB6666_Premultiplied The image is stored using a + premultiplied 24-bit ARGB format (6-6-6-6). + \value Format_RGB555 The image is stored using a 16-bit RGB format (5-5-5). + The unused most significant bit is always zero. + \value Format_ARGB8555_Premultiplied The image is stored using a + premultiplied 24-bit ARGB format (8-5-5-5). + \value Format_RGB888 The image is stored using a 24-bit RGB format (8-8-8). + \value Format_RGB444 The image is stored using a 16-bit RGB format (4-4-4). + The unused bits are always zero. + \value Format_ARGB4444_Premultiplied The image is stored using a + premultiplied 16-bit ARGB format (4-4-4-4). + + \note Drawing into a QImage with QImage::Format_Indexed8 is not + supported. + + \note Do not render into ARGB32 images using QPainter. Using + QImage::Format_ARGB32_Premultiplied is significantly faster. + + \sa format(), convertToFormat() +*/ + +/***************************************************************************** + QImage member functions + *****************************************************************************/ + +// table to flip bits +static const uchar bitflip[256] = { + /* + open OUT, "| fmt"; + for $i (0..255) { + print OUT (($i >> 7) & 0x01) | (($i >> 5) & 0x02) | + (($i >> 3) & 0x04) | (($i >> 1) & 0x08) | + (($i << 7) & 0x80) | (($i << 5) & 0x40) | + (($i << 3) & 0x20) | (($i << 1) & 0x10), ", "; + } + close OUT; + */ + 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 +}; + +const uchar *qt_get_bitflip_array() // called from QPixmap code +{ + return bitflip; +} + +#if defined(QT3_SUPPORT) +static QImage::Format formatFor(int depth, QImage::Endian bitOrder) +{ + QImage::Format format; + if (depth == 1) { + format = bitOrder == QImage::BigEndian ? QImage::Format_Mono : QImage::Format_MonoLSB; + } else if (depth == 8) { + format = QImage::Format_Indexed8; + } else if (depth == 32) { + format = QImage::Format_RGB32; + } else if (depth == 24) { + format = QImage::Format_RGB888; + } else if (depth == 16) { + format = QImage::Format_RGB16; + } else { + qWarning("QImage: Depth %d not supported", depth); + format = QImage::Format_Invalid; + } + return format; +} +#endif + +/*! + Constructs a null image. + + \sa isNull() +*/ + +QImage::QImage() + : QPaintDevice() +{ + d = 0; +} + +/*! + Constructs an image with the given \a width, \a height and \a + format. + + A \l{isNull()}{null} image will be returned if memory cannot be allocated. + + \warning This will create a QImage with uninitialized data. Call + fill() to fill the image with an appropriate pixel value before + drawing onto it with QPainter. +*/ +QImage::QImage(int width, int height, Format format) + : QPaintDevice() +{ + d = QImageData::create(QSize(width, height), format, 0); +} + +/*! + Constructs an image with the given \a size and \a format. + + A \l{isNull()}{null} image is returned if memory cannot be allocated. + + \warning This will create a QImage with uninitialized data. Call + fill() to fill the image with an appropriate pixel value before + drawing onto it with QPainter. +*/ +QImage::QImage(const QSize &size, Format format) + : QPaintDevice() +{ + d = QImageData::create(size, format, 0); +} + + + +QImageData *QImageData::create(uchar *data, int width, int height, int bpl, QImage::Format format, bool readOnly) +{ + QImageData *d = 0; + + if (format == QImage::Format_Invalid) + return d; + + if (!checkPixelSize(format)) { + qWarning("QImageData::create(): Invalid pixel size for format %i", + format); + return 0; + } + + const int depth = qt_depthForFormat(format); + const int calc_bytes_per_line = ((width * depth + 31)/32) * 4; + const int min_bytes_per_line = (width * depth + 7)/8; + + if (bpl <= 0) + bpl = calc_bytes_per_line; + + if (width <= 0 || height <= 0 || !data + || INT_MAX/sizeof(uchar *) < uint(height) + || INT_MAX/uint(depth) < uint(width) + || bpl <= 0 + || height <= 0 + || bpl < min_bytes_per_line + || INT_MAX/uint(bpl) < uint(height)) + return d; // invalid parameter(s) + + d = new QImageData; + d->ref.ref(); + + d->own_data = false; + d->ro_data = readOnly; + d->data = data; + d->width = width; + d->height = height; + d->depth = depth; + d->format = format; + + d->bytes_per_line = bpl; + d->nbytes = d->bytes_per_line * height; + + return d; +} + +/*! + Constructs an image with the given \a width, \a height and \a + format, that uses an existing memory buffer, \a data. The \a width + and \a height must be specified in pixels, \a data must be 32-bit aligned, + and each scanline of data in the image must also be 32-bit aligned. + + The buffer must remain valid throughout the life of the + QImage. The image does not delete the buffer at destruction. + + If \a format is an indexed color format, the image color table is + initially empty and must be sufficiently expanded with + setColorCount() or setColorTable() before the image is used. +*/ +QImage::QImage(uchar* data, int width, int height, Format format) + : QPaintDevice() +{ + d = QImageData::create(data, width, height, 0, format, false); +} + +/*! + Constructs an image with the given \a width, \a height and \a + format, that uses an existing read-only memory buffer, \a + data. The \a width and \a height must be specified in pixels, \a + data must be 32-bit aligned, and each scanline of data in the + image must also be 32-bit aligned. + + The buffer must remain valid throughout the life of the QImage and + all copies that have not been modified or otherwise detached from + the original buffer. The image does not delete the buffer at + destruction. + + If \a format is an indexed color format, the image color table is + initially empty and must be sufficiently expanded with + setColorCount() or setColorTable() before the image is used. + + Unlike the similar QImage constructor that takes a non-const data buffer, + this version will never alter the contents of the buffer. For example, + calling QImage::bits() will return a deep copy of the image, rather than + the buffer passed to the constructor. This allows for the efficiency of + constructing a QImage from raw data, without the possibility of the raw + data being changed. +*/ +QImage::QImage(const uchar* data, int width, int height, Format format) + : QPaintDevice() +{ + d = QImageData::create(const_cast(data), width, height, 0, format, true); +} + +/*! + Constructs an image with the given \a width, \a height and \a + format, that uses an existing memory buffer, \a data. The \a width + and \a height must be specified in pixels. \a bytesPerLine + specifies the number of bytes per line (stride). + + The buffer must remain valid throughout the life of the + QImage. The image does not delete the buffer at destruction. + + If \a format is an indexed color format, the image color table is + initially empty and must be sufficiently expanded with + setColorCount() or setColorTable() before the image is used. +*/ +QImage::QImage(uchar *data, int width, int height, int bytesPerLine, Format format) + :QPaintDevice() +{ + d = QImageData::create(data, width, height, bytesPerLine, format, false); +} + + +/*! + Constructs an image with the given \a width, \a height and \a + format, that uses an existing memory buffer, \a data. The \a width + and \a height must be specified in pixels. \a bytesPerLine + specifies the number of bytes per line (stride). + + The buffer must remain valid throughout the life of the + QImage. The image does not delete the buffer at destruction. + + If \a format is an indexed color format, the image color table is + initially empty and must be sufficiently expanded with + setColorCount() or setColorTable() before the image is used. + + Unlike the similar QImage constructor that takes a non-const data buffer, + this version will never alter the contents of the buffer. For example, + calling QImage::bits() will return a deep copy of the image, rather than + the buffer passed to the constructor. This allows for the efficiency of + constructing a QImage from raw data, without the possibility of the raw + data being changed. +*/ + +QImage::QImage(const uchar *data, int width, int height, int bytesPerLine, Format format) + :QPaintDevice() +{ + d = QImageData::create(const_cast(data), width, height, bytesPerLine, format, true); +} + +/*! + Constructs an image and tries to load the image from the file with + the given \a fileName. + + The loader attempts to read the image using the specified \a + format. If the \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + + If the loading of the image failed, this object is a null image. + + The file name can either refer to an actual file on disk or to one + of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed images and other resource files in the application's + executable. + + \sa isNull(), {QImage#Reading and Writing Image Files}{Reading and Writing Image Files} +*/ + +QImage::QImage(const QString &fileName, const char *format) + : QPaintDevice() +{ + d = 0; + load(fileName, format); +} + +/*! + Constructs an image and tries to load the image from the file with + the given \a fileName. + + The loader attempts to read the image using the specified \a + format. If the \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + + If the loading of the image failed, this object is a null image. + + The file name can either refer to an actual file on disk or to one + of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed images and other resource files in the application's + executable. + + You can disable this constructor by defining \c + QT_NO_CAST_FROM_ASCII when you compile your applications. This can + be useful, for example, if you want to ensure that all + user-visible strings go through QObject::tr(). + + \sa QString::fromAscii(), isNull(), {QImage#Reading and Writing + Image Files}{Reading and Writing Image Files} +*/ +#ifndef QT_NO_CAST_FROM_ASCII +QImage::QImage(const char *fileName, const char *format) + : QPaintDevice() +{ + // ### Qt 5: if you remove the QImage(const QByteArray &) QT3_SUPPORT + // constructor, remove this constructor as well. The constructor here + // exists so that QImage("foo.png") compiles without ambiguity. + d = 0; + load(QString::fromAscii(fileName), format); +} +#endif + +#ifndef QT_NO_IMAGEFORMAT_XPM +extern bool qt_read_xpm_image_or_array(QIODevice *device, const char * const *source, QImage &image); + +/*! + Constructs an image from the given \a xpm image. + + Make sure that the image is a valid XPM image. Errors are silently + ignored. + + Note that it's possible to squeeze the XPM variable a little bit + by using an unusual declaration: + + \snippet doc/src/snippets/code/src_gui_image_qimage.cpp 2 + + The extra \c const makes the entire definition read-only, which is + slightly more efficient (e.g., when the code is in a shared + library) and able to be stored in ROM with the application. +*/ + +QImage::QImage(const char * const xpm[]) + : QPaintDevice() +{ + d = 0; + if (!xpm) + return; + if (!qt_read_xpm_image_or_array(0, xpm, *this)) + // Issue: Warning because the constructor may be ambigious + qWarning("QImage::QImage(), XPM is not supported"); +} +#endif // QT_NO_IMAGEFORMAT_XPM + +/*! + \fn QImage::QImage(const QByteArray &data) + + Use the static fromData() function instead. + + \oldcode + QByteArray data; + ... + QImage image(data); + \newcode + QByteArray data; + ... + QImage image = QImage::fromData(data); + \endcode +*/ + + +/*! + Constructs a shallow copy of the given \a image. + + For more information about shallow copies, see the \l {Implicit + Data Sharing} documentation. + + \sa copy() +*/ + +QImage::QImage(const QImage &image) + : QPaintDevice() +{ + if (image.paintingActive()) { + d = 0; + operator=(image.copy()); + } else { + d = image.d; + if (d) + d->ref.ref(); + } +} + +#ifdef QT3_SUPPORT +/*! + \fn QImage::QImage(int width, int height, int depth, int numColors, Endian bitOrder) + + Constructs an image with the given \a width, \a height, \a depth, + \a numColors colors and \a bitOrder. + + Use the constructor that accepts a width, a height and a format + (i.e. specifying the depth and bit order), in combination with the + setColorCount() function, instead. + + \oldcode + QImage image(width, height, depth, numColors); + \newcode + QImage image(width, height, format); + + // For 8 bit images the default number of colors is 256. If + // another number of colors is required it can be specified + // using the setColorCount() function. + image.setColorCount(numColors); + \endcode +*/ + +QImage::QImage(int w, int h, int depth, int colorCount, Endian bitOrder) + : QPaintDevice() +{ + d = QImageData::create(QSize(w, h), formatFor(depth, bitOrder), colorCount); +} + +/*! + Constructs an image with the given \a size, \a depth, \a numColors + and \a bitOrder. + + Use the constructor that accepts a size and a format + (i.e. specifying the depth and bit order), in combination with the + setColorCount() function, instead. + + \oldcode + QSize mySize(width, height); + QImage image(mySize, depth, numColors); + \newcode + QSize mySize(width, height); + QImage image(mySize, format); + + // For 8 bit images the default number of colors is 256. If + // another number of colors is required it can be specified + // using the setColorCount() function. + image.setColorCount(numColors); + \endcode +*/ +QImage::QImage(const QSize& size, int depth, int numColors, Endian bitOrder) + : QPaintDevice() +{ + d = QImageData::create(size, formatFor(depth, bitOrder), numColors); +} + +/*! + \fn QImage::QImage(uchar* data, int width, int height, int depth, const QRgb* colortable, int numColors, Endian bitOrder) + + Constructs an image with the given \a width, \a height, depth, \a + colortable, \a numColors and \a bitOrder, that uses an existing + memory buffer, \a data. + + Use the constructor that accepts a uchar pointer, a width, a + height and a format (i.e. specifying the depth and bit order), in + combination with the setColorTable() function, instead. + + \oldcode + uchar *myData; + QRgb *myColorTable; + + QImage image(myData, width, height, depth, + myColorTable, numColors, IgnoreEndian); + \newcode + uchar *myData; + QVector myColorTable; + + QImage image(myData, width, height, format); + image.setColorTable(myColorTable); + \endcode +*/ +QImage::QImage(uchar* data, int w, int h, int depth, const QRgb* colortable, int numColors, Endian bitOrder) + : QPaintDevice() +{ + d = 0; + Format f = formatFor(depth, bitOrder); + if (f == Format_Invalid) + return; + + const int bytes_per_line = ((w*depth+31)/32)*4; // bytes per scanline + if (w <= 0 || h <= 0 || numColors < 0 || !data + || INT_MAX/sizeof(uchar *) < uint(h) + || INT_MAX/uint(depth) < uint(w) + || bytes_per_line <= 0 + || INT_MAX/uint(bytes_per_line) < uint(h)) + return; // invalid parameter(s) + d = new QImageData; + d->ref.ref(); + + d->own_data = false; + d->data = data; + d->width = w; + d->height = h; + d->depth = depth; + d->format = f; + if (depth == 32) + numColors = 0; + + d->bytes_per_line = bytes_per_line; + d->nbytes = d->bytes_per_line * h; + if (colortable) { + d->colortable.resize(numColors); + for (int i = 0; i < numColors; ++i) + d->colortable[i] = colortable[i]; + } else if (numColors) { + setColorCount(numColors); + } +} + +#ifdef Q_WS_QWS + +/*! + \fn QImage::QImage(uchar* data, int width, int height, int depth, int bytesPerLine, const QRgb* colortable, int numColors, Endian bitOrder) + + Constructs an image with the given \a width, \a height, \a depth, + \a bytesPerLine, \a colortable, \a numColors and \a bitOrder, that + uses an existing memory buffer, \a data. The image does not delete + the buffer at destruction. + + \warning This constructor is only available in Qt for Embedded Linux. + + The data has to be 32-bit aligned, and each scanline of data in the image + must also be 32-bit aligned, so it's no longer possible to specify a custom + \a bytesPerLine value. +*/ +QImage::QImage(uchar* data, int w, int h, int depth, int bpl, const QRgb* colortable, int numColors, Endian bitOrder) + : QPaintDevice() +{ + d = 0; + Format f = formatFor(depth, bitOrder); + if (f == Format_Invalid) + return; + if (!data || w <= 0 || h <= 0 || depth <= 0 || numColors < 0 + || INT_MAX/sizeof(uchar *) < uint(h) + || INT_MAX/uint(depth) < uint(w) + || bpl <= 0 + || INT_MAX/uint(bpl) < uint(h)) + return; // invalid parameter(s) + + d = new QImageData; + d->ref.ref(); + d->own_data = false; + d->data = data; + d->width = w; + d->height = h; + d->depth = depth; + d->format = f; + if (depth == 32) + numColors = 0; + d->bytes_per_line = bpl; + d->nbytes = d->bytes_per_line * h; + if (colortable) { + d->colortable.resize(numColors); + for (int i = 0; i < numColors; ++i) + d->colortable[i] = colortable[i]; + } else if (numColors) { + setColorCount(numColors); + } +} +#endif // Q_WS_QWS +#endif // QT3_SUPPORT + +/*! + Destroys the image and cleans up. +*/ + +QImage::~QImage() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Assigns a shallow copy of the given \a image to this image and + returns a reference to this image. + + For more information about shallow copies, see the \l {Implicit + Data Sharing} documentation. + + \sa copy(), QImage() +*/ + +QImage &QImage::operator=(const QImage &image) +{ + if (image.paintingActive()) { + operator=(image.copy()); + } else { + if (image.d) + image.d->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = image.d; + } + return *this; +} + +/*! + \fn void QImage::swap(QImage &other) + \since 4.8 + + Swaps image \a other with this image. This operation is very + fast and never fails. +*/ + +/*! + \internal +*/ +int QImage::devType() const +{ + return QInternal::Image; +} + +/*! + Returns the image as a QVariant. +*/ +QImage::operator QVariant() const +{ + return QVariant(QVariant::Image, this); +} + +/*! + \internal + + If multiple images share common data, this image makes a copy of + the data and detaches itself from the sharing mechanism, making + sure that this image is the only one referring to the data. + + Nothing is done if there is just a single reference. + + \sa copy(), isDetached(), {Implicit Data Sharing} +*/ +void QImage::detach() +{ + if (d) { + if (d->is_cached && d->ref == 1) + QImagePixmapCleanupHooks::executeImageHooks(cacheKey()); + + if (d->ref != 1 || d->ro_data) + *this = copy(); + + if (d) + ++d->detach_no; + } +} + + +/*! + \fn QImage QImage::copy(int x, int y, int width, int height) const + \overload + + The returned image is copied from the position (\a x, \a y) in + this image, and will always have the given \a width and \a height. + In areas beyond this image, pixels are set to 0. + +*/ + +/*! + \fn QImage QImage::copy(const QRect& rectangle) const + + Returns a sub-area of the image as a new image. + + The returned image is copied from the position (\a + {rectangle}.x(), \a{rectangle}.y()) in this image, and will always + have the size of the given \a rectangle. + + In areas beyond this image, pixels are set to 0. For 32-bit RGB + images, this means black; for 32-bit ARGB images, this means + transparent black; for 8-bit images, this means the color with + index 0 in the color table which can be anything; for 1-bit + images, this means Qt::color0. + + If the given \a rectangle is a null rectangle the entire image is + copied. + + \sa QImage() +*/ +QImage QImage::copy(const QRect& r) const +{ + if (!d) + return QImage(); + + if (r.isNull()) { + QImage image(d->width, d->height, d->format); + if (image.isNull()) + return image; + + // Qt for Embedded Linux can create images with non-default bpl + // make sure we don't crash. + if (image.d->nbytes != d->nbytes) { + int bpl = image.bytesPerLine(); + for (int i = 0; i < height(); i++) + memcpy(image.scanLine(i), scanLine(i), bpl); + } else + memcpy(image.bits(), bits(), d->nbytes); + image.d->colortable = d->colortable; + image.d->dpmx = d->dpmx; + image.d->dpmy = d->dpmy; + image.d->offset = d->offset; + image.d->has_alpha_clut = d->has_alpha_clut; +#ifndef QT_NO_IMAGE_TEXT + image.d->text = d->text; +#endif + return image; + } + + int x = r.x(); + int y = r.y(); + int w = r.width(); + int h = r.height(); + + int dx = 0; + int dy = 0; + if (w <= 0 || h <= 0) + return QImage(); + + QImage image(w, h, d->format); + if (image.isNull()) + return image; + + if (x < 0 || y < 0 || x + w > d->width || y + h > d->height) { + // bitBlt will not cover entire image - clear it. + image.fill(0); + if (x < 0) { + dx = -x; + x = 0; + } + if (y < 0) { + dy = -y; + y = 0; + } + } + + image.d->colortable = d->colortable; + + int pixels_to_copy = qMax(w - dx, 0); + if (x > d->width) + pixels_to_copy = 0; + else if (pixels_to_copy > d->width - x) + pixels_to_copy = d->width - x; + int lines_to_copy = qMax(h - dy, 0); + if (y > d->height) + lines_to_copy = 0; + else if (lines_to_copy > d->height - y) + lines_to_copy = d->height - y; + + bool byteAligned = true; + if (d->format == Format_Mono || d->format == Format_MonoLSB) + byteAligned = !(dx & 7) && !(x & 7) && !(pixels_to_copy & 7); + + if (byteAligned) { + const uchar *src = d->data + ((x * d->depth) >> 3) + y * d->bytes_per_line; + uchar *dest = image.d->data + ((dx * d->depth) >> 3) + dy * image.d->bytes_per_line; + const int bytes_to_copy = (pixels_to_copy * d->depth) >> 3; + for (int i = 0; i < lines_to_copy; ++i) { + memcpy(dest, src, bytes_to_copy); + src += d->bytes_per_line; + dest += image.d->bytes_per_line; + } + } else if (d->format == Format_Mono) { + const uchar *src = d->data + y * d->bytes_per_line; + uchar *dest = image.d->data + dy * image.d->bytes_per_line; + for (int i = 0; i < lines_to_copy; ++i) { + for (int j = 0; j < pixels_to_copy; ++j) { + if (src[(x + j) >> 3] & (0x80 >> ((x + j) & 7))) + dest[(dx + j) >> 3] |= (0x80 >> ((dx + j) & 7)); + else + dest[(dx + j) >> 3] &= ~(0x80 >> ((dx + j) & 7)); + } + src += d->bytes_per_line; + dest += image.d->bytes_per_line; + } + } else { // Format_MonoLSB + Q_ASSERT(d->format == Format_MonoLSB); + const uchar *src = d->data + y * d->bytes_per_line; + uchar *dest = image.d->data + dy * image.d->bytes_per_line; + for (int i = 0; i < lines_to_copy; ++i) { + for (int j = 0; j < pixels_to_copy; ++j) { + if (src[(x + j) >> 3] & (0x1 << ((x + j) & 7))) + dest[(dx + j) >> 3] |= (0x1 << ((dx + j) & 7)); + else + dest[(dx + j) >> 3] &= ~(0x1 << ((dx + j) & 7)); + } + src += d->bytes_per_line; + dest += image.d->bytes_per_line; + } + } + + image.d->dpmx = dotsPerMeterX(); + image.d->dpmy = dotsPerMeterY(); + image.d->offset = offset(); + image.d->has_alpha_clut = d->has_alpha_clut; +#ifndef QT_NO_IMAGE_TEXT + image.d->text = d->text; +#endif + return image; +} + + +/*! + \fn bool QImage::isNull() const + + Returns true if it is a null image, otherwise returns false. + + A null image has all parameters set to zero and no allocated data. +*/ +bool QImage::isNull() const +{ + return !d; +} + +/*! + \fn int QImage::width() const + + Returns the width of the image. + + \sa {QImage#Image Information}{Image Information} +*/ +int QImage::width() const +{ + return d ? d->width : 0; +} + +/*! + \fn int QImage::height() const + + Returns the height of the image. + + \sa {QImage#Image Information}{Image Information} +*/ +int QImage::height() const +{ + return d ? d->height : 0; +} + +/*! + \fn QSize QImage::size() const + + Returns the size of the image, i.e. its width() and height(). + + \sa {QImage#Image Information}{Image Information} +*/ +QSize QImage::size() const +{ + return d ? QSize(d->width, d->height) : QSize(0, 0); +} + +/*! + \fn QRect QImage::rect() const + + Returns the enclosing rectangle (0, 0, width(), height()) of the + image. + + \sa {QImage#Image Information}{Image Information} +*/ +QRect QImage::rect() const +{ + return d ? QRect(0, 0, d->width, d->height) : QRect(); +} + +/*! + Returns the depth of the image. + + The image depth is the number of bits used to store a single + pixel, also called bits per pixel (bpp). + + The supported depths are 1, 8, 16, 24 and 32. + + \sa bitPlaneCount(), convertToFormat(), {QImage#Image Formats}{Image Formats}, + {QImage#Image Information}{Image Information} + +*/ +int QImage::depth() const +{ + return d ? d->depth : 0; +} + +/*! + \obsolete + \fn int QImage::numColors() const + + Returns the size of the color table for the image. + + \sa setColorCount() +*/ +int QImage::numColors() const +{ + return d ? d->colortable.size() : 0; +} + +/*! + \since 4.6 + \fn int QImage::colorCount() const + + Returns the size of the color table for the image. + + Notice that colorCount() returns 0 for 32-bpp images because these + images do not use color tables, but instead encode pixel values as + ARGB quadruplets. + + \sa setColorCount(), {QImage#Image Information}{Image Information} +*/ +int QImage::colorCount() const +{ + return d ? d->colortable.size() : 0; +} + + +#ifdef QT3_SUPPORT +/*! + \fn QImage::Endian QImage::bitOrder() const + + Returns the bit order for the image. If it is a 1-bpp image, this + function returns either QImage::BigEndian or + QImage::LittleEndian. Otherwise, this function returns + QImage::IgnoreEndian. + + Use the format() function instead for the monochrome formats. For + non-monochrome formats the bit order is irrelevant. +*/ + +/*! + Returns a pointer to the scanline pointer table. This is the + beginning of the data block for the image. + Returns 0 in case of an error. + + Use the bits() or scanLine() function instead. +*/ +uchar **QImage::jumpTable() +{ + if (!d) + return 0; + detach(); + + // in case detach() ran out of memory.. + if (!d) + return 0; + + if (!d->jumptable) { + d->jumptable = (uchar **)malloc(d->height*sizeof(uchar *)); + if (!d->jumptable) + return 0; + uchar *data = d->data; + int height = d->height; + uchar **p = d->jumptable; + while (height--) { + *p++ = data; + data += d->bytes_per_line; + } + } + return d->jumptable; +} + +/*! + \overload +*/ +const uchar * const *QImage::jumpTable() const +{ + if (!d) + return 0; + if (!d->jumptable) { + d->jumptable = (uchar **)malloc(d->height*sizeof(uchar *)); + if (!d->jumptable) + return 0; + uchar *data = d->data; + int height = d->height; + uchar **p = d->jumptable; + while (height--) { + *p++ = data; + data += d->bytes_per_line; + } + } + return d->jumptable; +} +#endif + +/*! + Sets the color table used to translate color indexes to QRgb + values, to the specified \a colors. + + When the image is used, the color table must be large enough to + have entries for all the pixel/index values present in the image, + otherwise the results are undefined. + + \sa colorTable(), setColor(), {QImage#Image Transformations}{Image + Transformations} +*/ +void QImage::setColorTable(const QVector colors) +{ + if (!d) + return; + detach(); + + // In case detach() ran out of memory + if (!d) + return; + + d->colortable = colors; + d->has_alpha_clut = false; + for (int i = 0; i < d->colortable.size(); ++i) { + if (qAlpha(d->colortable.at(i)) != 255) { + d->has_alpha_clut = true; + break; + } + } +} + +/*! + Returns a list of the colors contained in the image's color table, + or an empty list if the image does not have a color table + + \sa setColorTable(), colorCount(), color() +*/ +QVector QImage::colorTable() const +{ + return d ? d->colortable : QVector(); +} + + +/*! + \obsolete + Returns the number of bytes occupied by the image data. + + \sa byteCount() +*/ +int QImage::numBytes() const +{ + return d ? d->nbytes : 0; +} + +/*! + \since 4.6 + Returns the number of bytes occupied by the image data. + + \sa bytesPerLine(), bits(), {QImage#Image Information}{Image + Information} +*/ +int QImage::byteCount() const +{ + return d ? d->nbytes : 0; +} + +/*! + Returns the number of bytes per image scanline. + + This is equivalent to byteCount() / height(). + + \sa scanLine() +*/ +int QImage::bytesPerLine() const +{ + return (d && d->height) ? d->nbytes / d->height : 0; +} + + +/*! + Returns the color in the color table at index \a i. The first + color is at index 0. + + The colors in an image's color table are specified as ARGB + quadruplets (QRgb). Use the qAlpha(), qRed(), qGreen(), and + qBlue() functions to get the color value components. + + \sa setColor(), pixelIndex(), {QImage#Pixel Manipulation}{Pixel + Manipulation} +*/ +QRgb QImage::color(int i) const +{ + Q_ASSERT(i < colorCount()); + return d ? d->colortable.at(i) : QRgb(uint(-1)); +} + +/*! + \fn void QImage::setColor(int index, QRgb colorValue) + + Sets the color at the given \a index in the color table, to the + given to \a colorValue. The color value is an ARGB quadruplet. + + If \a index is outside the current size of the color table, it is + expanded with setColorCount(). + + \sa color(), colorCount(), setColorTable(), {QImage#Pixel Manipulation}{Pixel + Manipulation} +*/ +void QImage::setColor(int i, QRgb c) +{ + if (!d) + return; + if (i < 0 || d->depth > 8 || i >= 1<depth) { + qWarning("QImage::setColor: Index out of bound %d", i); + return; + } + detach(); + + // In case detach() run out of memory + if (!d) + return; + + if (i >= d->colortable.size()) + setColorCount(i+1); + d->colortable[i] = c; + d->has_alpha_clut |= (qAlpha(c) != 255); +} + +/*! + Returns a pointer to the pixel data at the scanline with index \a + i. The first scanline is at index 0. + + The scanline data is aligned on a 32-bit boundary. + + \warning If you are accessing 32-bpp image data, cast the returned + pointer to \c{QRgb*} (QRgb has a 32-bit size) and use it to + read/write the pixel value. You cannot use the \c{uchar*} pointer + directly, because the pixel format depends on the byte order on + the underlying platform. Use qRed(), qGreen(), qBlue(), and + qAlpha() to access the pixels. + + \sa bytesPerLine(), bits(), {QImage#Pixel Manipulation}{Pixel + Manipulation}, constScanLine() +*/ +uchar *QImage::scanLine(int i) +{ + if (!d) + return 0; + + detach(); + + // In case detach() ran out of memory + if (!d) + return 0; + + return d->data + i * d->bytes_per_line; +} + +/*! + \overload +*/ +const uchar *QImage::scanLine(int i) const +{ + if (!d) + return 0; + + Q_ASSERT(i >= 0 && i < height()); + return d->data + i * d->bytes_per_line; +} + + +/*! + Returns a pointer to the pixel data at the scanline with index \a + i. The first scanline is at index 0. + + The scanline data is aligned on a 32-bit boundary. + + Note that QImage uses \l{Implicit Data Sharing} {implicit data + sharing}, but this function does \e not perform a deep copy of the + shared pixel data, because the returned data is const. + + \sa scanLine(), constBits() + \since 4.7 +*/ +const uchar *QImage::constScanLine(int i) const +{ + if (!d) + return 0; + + Q_ASSERT(i >= 0 && i < height()); + return d->data + i * d->bytes_per_line; +} + +/*! + Returns a pointer to the first pixel data. This is equivalent to + scanLine(0). + + Note that QImage uses \l{Implicit Data Sharing} {implicit data + sharing}. This function performs a deep copy of the shared pixel + data, thus ensuring that this QImage is the only one using the + current return value. + + \sa scanLine(), byteCount(), constBits() +*/ +uchar *QImage::bits() +{ + if (!d) + return 0; + detach(); + + // In case detach ran out of memory... + if (!d) + return 0; + + return d->data; +} + +/*! + \overload + + Note that QImage uses \l{Implicit Data Sharing} {implicit data + sharing}, but this function does \e not perform a deep copy of the + shared pixel data, because the returned data is const. +*/ +const uchar *QImage::bits() const +{ + return d ? d->data : 0; +} + + +/*! + Returns a pointer to the first pixel data. + + Note that QImage uses \l{Implicit Data Sharing} {implicit data + sharing}, but this function does \e not perform a deep copy of the + shared pixel data, because the returned data is const. + + \sa bits(), constScanLine() + \since 4.7 +*/ +const uchar *QImage::constBits() const +{ + return d ? d->data : 0; +} + +/*! + \fn void QImage::reset() + + Resets all image parameters and deallocates the image data. + + Assign a null image instead. + + \oldcode + QImage image; + image.reset(); + \newcode + QImage image; + image = QImage(); + \endcode +*/ + +/*! + \fn void QImage::fill(uint pixelValue) + + Fills the entire image with the given \a pixelValue. + + If the depth of this image is 1, only the lowest bit is used. If + you say fill(0), fill(2), etc., the image is filled with 0s. If + you say fill(1), fill(3), etc., the image is filled with 1s. If + the depth is 8, the lowest 8 bits are used and if the depth is 16 + the lowest 16 bits are used. + + Note: QImage::pixel() returns the color of the pixel at the given + coordinates while QColor::pixel() returns the pixel value of the + underlying window system (essentially an index value), so normally + you will want to use QImage::pixel() to use a color from an + existing image or QColor::rgb() to use a specific color. + + \sa depth(), {QImage#Image Transformations}{Image Transformations} +*/ + +void QImage::fill(uint pixel) +{ + if (!d) + return; + + detach(); + + // In case detach() ran out of memory + if (!d) + return; + + if (d->depth == 1 || d->depth == 8) { + int w = d->width; + if (d->depth == 1) { + if (pixel & 1) + pixel = 0xffffffff; + else + pixel = 0; + w = (w + 7) / 8; + } else { + pixel &= 0xff; + } + qt_rectfill(d->data, pixel, 0, 0, + w, d->height, d->bytes_per_line); + return; + } else if (d->depth == 16) { + qt_rectfill(reinterpret_cast(d->data), pixel, + 0, 0, d->width, d->height, d->bytes_per_line); + return; + } else if (d->depth == 24) { + qt_rectfill(reinterpret_cast(d->data), pixel, + 0, 0, d->width, d->height, d->bytes_per_line); + return; + } + + if (d->format == Format_RGB32) + pixel |= 0xff000000; + + qt_rectfill(reinterpret_cast(d->data), pixel, + 0, 0, d->width, d->height, d->bytes_per_line); +} + + +/*! + \fn void QImage::fill(Qt::GlobalColor color) + + \overload + + \since 4.8 + */ + +void QImage::fill(Qt::GlobalColor color) +{ + fill(QColor(color)); +} + + + +/*! + \fn void QImage::fill(Qt::GlobalColor color) + + \overload + + Fills the entire image with the given \a color. + + If the depth of the image is 1, the image will be filled with 1 if + \a color equals Qt::color1; it will otherwise be filled with 0. + + If the depth of the image is 8, the image will be filled with the + index corresponding the \a color in the color table if present; it + will otherwise be filled with 0. + + \since 4.8 +*/ + +void QImage::fill(const QColor &color) +{ + if (!d) + return; + detach(); + + // In case we run out of memory + if (!d) + return; + + if (d->depth == 32) { + uint pixel = color.rgba(); + if (d->format == QImage::Format_ARGB32_Premultiplied) + pixel = PREMUL(pixel); + fill((uint) pixel); + + } else if (d->depth == 16 && d->format == QImage::Format_RGB16) { + qrgb565 p(color.rgba()); + fill((uint) p.rawValue()); + + } else if (d->depth == 1) { + if (color == Qt::color1) + fill((uint) 1); + else + fill((uint) 0); + + } else if (d->depth == 8) { + uint pixel = 0; + for (int i=0; icolortable.size(); ++i) { + if (color.rgba() == d->colortable.at(i)) { + pixel = i; + break; + } + } + fill(pixel); + + } else { + QPainter p(this); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(rect(), color); + } + +} + + + + + + +/*! + Inverts all pixel values in the image. + + The given invert \a mode only have a meaning when the image's + depth is 32. The default \a mode is InvertRgb, which leaves the + alpha channel unchanged. If the \a mode is InvertRgba, the alpha + bits are also inverted. + + Inverting an 8-bit image means to replace all pixels using color + index \e i with a pixel using color index 255 minus \e i. The same + is the case for a 1-bit image. Note that the color table is \e not + changed. + + \sa {QImage#Image Transformations}{Image Transformations} +*/ + +void QImage::invertPixels(InvertMode mode) +{ + if (!d) + return; + + detach(); + + // In case detach() ran out of memory + if (!d) + return; + + if (depth() != 32) { + // number of used bytes pr line + int bpl = (d->width * d->depth + 7) / 8; + int pad = d->bytes_per_line - bpl; + uchar *sl = d->data; + for (int y=0; yheight; ++y) { + for (int x=0; xdata; + quint32 *end = (quint32*)(d->data + d->nbytes); + uint xorbits = (mode == InvertRgba) ? 0xffffffff : 0x00ffffff; + while (p < end) + *p++ ^= xorbits; + } +} + +/*! + \fn void QImage::invertPixels(bool invertAlpha) + + Use the invertPixels() function that takes a QImage::InvertMode + parameter instead. +*/ + +/*! \fn QImage::Endian QImage::systemByteOrder() + + Determines the host computer byte order. Returns + QImage::LittleEndian (LSB first) or QImage::BigEndian (MSB first). + + This function is no longer relevant for QImage. Use QSysInfo + instead. +*/ + +// Windows defines these +#if defined(write) +# undef write +#endif +#if defined(close) +# undef close +#endif +#if defined(read) +# undef read +#endif + +/*! + \obsolete + Resizes the color table to contain \a numColors entries. + + \sa setColorCount() +*/ + +void QImage::setNumColors(int numColors) +{ + setColorCount(numColors); +} + +/*! + \since 4.6 + Resizes the color table to contain \a colorCount entries. + + If the color table is expanded, all the extra colors will be set to + transparent (i.e qRgba(0, 0, 0, 0)). + + When the image is used, the color table must be large enough to + have entries for all the pixel/index values present in the image, + otherwise the results are undefined. + + \sa colorCount(), colorTable(), setColor(), {QImage#Image + Transformations}{Image Transformations} +*/ + +void QImage::setColorCount(int colorCount) +{ + if (!d) { + qWarning("QImage::setColorCount: null image"); + return; + } + + detach(); + + // In case detach() ran out of memory + if (!d) + return; + + if (colorCount == d->colortable.size()) + return; + if (colorCount <= 0) { // use no color table + d->colortable = QVector(); + return; + } + int nc = d->colortable.size(); + d->colortable.resize(colorCount); + for (int i = nc; i < colorCount; ++i) + d->colortable[i] = 0; +} + +/*! + Returns the format of the image. + + \sa {QImage#Image Formats}{Image Formats} +*/ +QImage::Format QImage::format() const +{ + return d ? d->format : Format_Invalid; +} + + +#ifdef QT3_SUPPORT +/*! + Returns true if alpha buffer mode is enabled; otherwise returns + false. + + Use the hasAlphaChannel() function instead. + +*/ +bool QImage::hasAlphaBuffer() const +{ + if (!d) + return false; + + switch (d->format) { + case Format_ARGB32: + case Format_ARGB32_Premultiplied: + case Format_ARGB8565_Premultiplied: + case Format_ARGB8555_Premultiplied: + case Format_ARGB6666_Premultiplied: + case Format_ARGB4444_Premultiplied: + return true; + default: + return false; + } +} + +/*! + Enables alpha buffer mode if \a enable is true, otherwise disables + it. The alpha buffer is used to set a mask when a QImage is + translated to a QPixmap. + + If a monochrome or indexed 8-bit image has alpha channels in their + color tables they will automatically detect that they have an + alpha channel, so this function is not required. To force alpha + channels on 32-bit images, use the convertToFormat() function. +*/ + +void QImage::setAlphaBuffer(bool enable) +{ + if (!d + || d->format == Format_Mono + || d->format == Format_MonoLSB + || d->format == Format_Indexed8) + return; + if (enable && (d->format == Format_ARGB32 || + d->format == Format_ARGB32_Premultiplied || + d->format == Format_ARGB8565_Premultiplied || + d->format == Format_ARGB6666_Premultiplied || + d->format == Format_ARGB8555_Premultiplied || + d->format == Format_ARGB4444_Premultiplied)) + { + return; + } + if (!enable && (d->format == Format_RGB32 || + d->format == Format_RGB555 || + d->format == Format_RGB666 || + d->format == Format_RGB888 || + d->format == Format_RGB444)) + { + return; + } + detach(); + d->format = (enable ? Format_ARGB32 : Format_RGB32); +} + + +/*! + \fn bool QImage::create(int width, int height, int depth, int numColors, Endian bitOrder) + + Sets the image \a width, \a height, \a depth, its number of colors + (in \a numColors), and bit order. Returns true if successful, or + false if the parameters are incorrect or if memory cannot be + allocated. + + The \a width and \a height is limited to 32767. \a depth must be + 1, 8, or 32. If \a depth is 1, \a bitOrder must be set to + either QImage::LittleEndian or QImage::BigEndian. For other depths + \a bitOrder must be QImage::IgnoreEndian. + + This function allocates a color table and a buffer for the image + data. The image data is not initialized. The image buffer is + allocated as a single block that consists of a table of scanLine() + pointers (jumpTable()) and the image data (bits()). + + Use a QImage constructor instead. +*/ +bool QImage::create(int width, int height, int depth, int numColors, Endian bitOrder) +{ + if (d && !d->ref.deref()) + delete d; + d = QImageData::create(QSize(width, height), formatFor(depth, bitOrder), numColors); + return true; +} + +/*! + \fn bool QImage::create(const QSize& size, int depth, int numColors, Endian bitOrder) + \overload + + The width and height are specified in the \a size argument. + + Use a QImage constructor instead. +*/ +bool QImage::create(const QSize& size, int depth, int numColors, QImage::Endian bitOrder) +{ + if (d && !d->ref.deref()) + delete d; + d = QImageData::create(size, formatFor(depth, bitOrder), numColors); + return true; +} +#endif // QT3_SUPPORT + +/***************************************************************************** + Internal routines for converting image depth. + *****************************************************************************/ + +typedef void (*Image_Converter)(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags); + +typedef bool (*InPlace_Image_Converter)(QImageData *data, Qt::ImageConversionFlags); + +static void convert_ARGB_to_ARGB_PM(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_ARGB32); + Q_ASSERT(dest->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const int src_pad = (src->bytes_per_line >> 2) - src->width; + const int dest_pad = (dest->bytes_per_line >> 2) - dest->width; + const QRgb *src_data = (QRgb *) src->data; + QRgb *dest_data = (QRgb *) dest->data; + + for (int i = 0; i < src->height; ++i) { + const QRgb *end = src_data + src->width; + while (src_data < end) { + *dest_data = PREMUL(*src_data); + ++src_data; + ++dest_data; + } + src_data += src_pad; + dest_data += dest_pad; + } +} + +static bool convert_ARGB_to_ARGB_PM_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_ARGB32); + + const int pad = (data->bytes_per_line >> 2) - data->width; + QRgb *rgb_data = (QRgb *) data->data; + + for (int i = 0; i < data->height; ++i) { + const QRgb *end = rgb_data + data->width; + while (rgb_data < end) { + *rgb_data = PREMUL(*rgb_data); + ++rgb_data; + } + rgb_data += pad; + } + data->format = QImage::Format_ARGB32_Premultiplied; + return true; +} + +static bool convert_indexed8_to_ARGB_PM_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 32; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; // end of src + quint32 *dest_data = (quint32 *) (newData + nbytes); // end of dest > end of src + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 2) - width; + if (data->colortable.size() == 0) { + data->colortable.resize(256); + for (int i = 0; i < 256; ++i) + data->colortable[i] = qRgb(i, i, i); + } else { + for (int i = 0; i < data->colortable.size(); ++i) + data->colortable[i] = PREMUL(data->colortable.at(i)); + + // Fill the rest of the table in case src_data > colortable.size() + const int oldSize = data->colortable.size(); + const QRgb lastColor = data->colortable.at(oldSize - 1); + data->colortable.insert(oldSize, 256 - oldSize, lastColor); + } + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + *dest_data = data->colortable.at(*src_data); + } + } + + data->colortable = QVector(); + data->format = QImage::Format_ARGB32_Premultiplied; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_indexed8_to_RGB_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 32; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; + quint32 *dest_data = (quint32 *) (newData + nbytes); + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 2) - width; + if (data->colortable.size() == 0) { + data->colortable.resize(256); + for (int i = 0; i < 256; ++i) + data->colortable[i] = qRgb(i, i, i); + } else { + // Fill the rest of the table in case src_data > colortable.size() + const int oldSize = data->colortable.size(); + const QRgb lastColor = data->colortable.at(oldSize - 1); + data->colortable.insert(oldSize, 256 - oldSize, lastColor); + } + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + *dest_data = (quint32) data->colortable.at(*src_data); + } + } + + data->colortable = QVector(); + data->format = QImage::Format_RGB32; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_indexed8_to_RGB16_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_Indexed8); + const int depth = 16; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, nbytes); + if (!newData) + return false; + + data->data = newData; + + // start converting from the end because the end image is bigger than the source + uchar *src_data = newData + data->nbytes; + quint16 *dest_data = (quint16 *) (newData + nbytes); + const int width = data->width; + const int src_pad = data->bytes_per_line - width; + const int dest_pad = (dst_bytes_per_line >> 1) - width; + + quint16 colorTableRGB16[256]; + if (data->colortable.isEmpty()) { + for (int i = 0; i < 256; ++i) + colorTableRGB16[i] = qt_colorConvert(qRgb(i, i, i), 0); + } else { + // 1) convert the existing colors to RGB16 + const int tableSize = data->colortable.size(); + for (int i = 0; i < tableSize; ++i) + colorTableRGB16[i] = qt_colorConvert(data->colortable.at(i), 0); + data->colortable = QVector(); + + // 2) fill the rest of the table in case src_data > colortable.size() + const quint16 lastColor = colorTableRGB16[tableSize - 1]; + for (int i = tableSize; i < 256; ++i) + colorTableRGB16[i] = lastColor; + } + + for (int i = 0; i < data->height; ++i) { + src_data -= src_pad; + dest_data -= dest_pad; + for (int pixI = 0; pixI < width; ++pixI) { + --src_data; + --dest_data; + *dest_data = colorTableRGB16[*src_data]; + } + } + + data->format = QImage::Format_RGB16; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = nbytes; + + return true; +} + +static bool convert_RGB_to_RGB16_inplace(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_RGB32); + const int depth = 16; + + const int dst_bytes_per_line = ((data->width * depth + 31) >> 5) << 2; + const int src_bytes_per_line = data->bytes_per_line; + quint32 *src_data = (quint32 *) data->data; + quint16 *dst_data = (quint16 *) data->data; + + for (int i = 0; i < data->height; ++i) { + qt_memconvert(dst_data, src_data, data->width); + src_data = (quint32 *) (((char*)src_data) + src_bytes_per_line); + dst_data = (quint16 *) (((char*)dst_data) + dst_bytes_per_line); + } + data->format = QImage::Format_RGB16; + data->bytes_per_line = dst_bytes_per_line; + data->depth = depth; + data->nbytes = dst_bytes_per_line * data->height; + uchar *const newData = (uchar *)realloc(data->data, data->nbytes); + if (newData) { + data->data = newData; + return true; + } else { + return false; + } +} + +static void convert_ARGB_PM_to_ARGB(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(dest->format == QImage::Format_ARGB32); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const int src_pad = (src->bytes_per_line >> 2) - src->width; + const int dest_pad = (dest->bytes_per_line >> 2) - dest->width; + const QRgb *src_data = (QRgb *) src->data; + QRgb *dest_data = (QRgb *) dest->data; + + for (int i = 0; i < src->height; ++i) { + const QRgb *end = src_data + src->width; + while (src_data < end) { + *dest_data = INV_PREMUL(*src_data); + ++src_data; + ++dest_data; + } + src_data += src_pad; + dest_data += dest_pad; + } +} + +static void convert_ARGB_PM_to_RGB(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(dest->format == QImage::Format_RGB32); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const int src_pad = (src->bytes_per_line >> 2) - src->width; + const int dest_pad = (dest->bytes_per_line >> 2) - dest->width; + const QRgb *src_data = (QRgb *) src->data; + QRgb *dest_data = (QRgb *) dest->data; + + for (int i = 0; i < src->height; ++i) { + const QRgb *end = src_data + src->width; + while (src_data < end) { + *dest_data = 0xff000000 | INV_PREMUL(*src_data); + ++src_data; + ++dest_data; + } + src_data += src_pad; + dest_data += dest_pad; + } +} + +static void swap_bit_order(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_Mono || src->format == QImage::Format_MonoLSB); + Q_ASSERT(dest->format == QImage::Format_Mono || dest->format == QImage::Format_MonoLSB); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + Q_ASSERT(src->nbytes == dest->nbytes); + Q_ASSERT(src->bytes_per_line == dest->bytes_per_line); + + dest->colortable = src->colortable; + + const uchar *src_data = src->data; + const uchar *end = src->data + src->nbytes; + uchar *dest_data = dest->data; + while (src_data < end) { + *dest_data = bitflip[*src_data]; + ++src_data; + ++dest_data; + } +} + +static void mask_alpha_converter(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const int src_pad = (src->bytes_per_line >> 2) - src->width; + const int dest_pad = (dest->bytes_per_line >> 2) - dest->width; + const uint *src_data = (const uint *)src->data; + uint *dest_data = (uint *)dest->data; + + for (int i = 0; i < src->height; ++i) { + const uint *end = src_data + src->width; + while (src_data < end) { + *dest_data = *src_data | 0xff000000; + ++src_data; + ++dest_data; + } + src_data += src_pad; + dest_data += dest_pad; + } +} + +static QVector fix_color_table(const QVector &ctbl, QImage::Format format) +{ + QVector colorTable = ctbl; + if (format == QImage::Format_RGB32) { + // check if the color table has alpha + for (int i = 0; i < colorTable.size(); ++i) + if (qAlpha(colorTable.at(i) != 0xff)) + colorTable[i] = colorTable.at(i) | 0xff000000; + } else if (format == QImage::Format_ARGB32_Premultiplied) { + // check if the color table has alpha + for (int i = 0; i < colorTable.size(); ++i) + colorTable[i] = PREMUL(colorTable.at(i)); + } + return colorTable; +} + +// +// dither_to_1: Uses selected dithering algorithm. +// + +static void dither_to_Mono(QImageData *dst, const QImageData *src, + Qt::ImageConversionFlags flags, bool fromalpha) +{ + Q_ASSERT(src->width == dst->width); + Q_ASSERT(src->height == dst->height); + Q_ASSERT(dst->format == QImage::Format_Mono || dst->format == QImage::Format_MonoLSB); + + dst->colortable.clear(); + dst->colortable.append(0xffffffff); + dst->colortable.append(0xff000000); + + enum { Threshold, Ordered, Diffuse } dithermode; + + if (fromalpha) { + if ((flags & Qt::AlphaDither_Mask) == Qt::DiffuseAlphaDither) + dithermode = Diffuse; + else if ((flags & Qt::AlphaDither_Mask) == Qt::OrderedAlphaDither) + dithermode = Ordered; + else + dithermode = Threshold; + } else { + if ((flags & Qt::Dither_Mask) == Qt::ThresholdDither) + dithermode = Threshold; + else if ((flags & Qt::Dither_Mask) == Qt::OrderedDither) + dithermode = Ordered; + else + dithermode = Diffuse; + } + + int w = src->width; + int h = src->height; + int d = src->depth; + uchar gray[256]; // gray map for 8 bit images + bool use_gray = (d == 8); + if (use_gray) { // make gray map + if (fromalpha) { + // Alpha 0x00 -> 0 pixels (white) + // Alpha 0xFF -> 1 pixels (black) + for (int i = 0; i < src->colortable.size(); i++) + gray[i] = (255 - (src->colortable.at(i) >> 24)); + } else { + // Pixel 0x00 -> 1 pixels (black) + // Pixel 0xFF -> 0 pixels (white) + for (int i = 0; i < src->colortable.size(); i++) + gray[i] = qGray(src->colortable.at(i)); + } + } + + uchar *dst_data = dst->data; + int dst_bpl = dst->bytes_per_line; + const uchar *src_data = src->data; + int src_bpl = src->bytes_per_line; + + switch (dithermode) { + case Diffuse: { + QScopedArrayPointer lineBuffer(new int[w * 2]); + int *line1 = lineBuffer.data(); + int *line2 = lineBuffer.data() + w; + int bmwidth = (w+7)/8; + + int *b1, *b2; + int wbytes = w * (d/8); + register const uchar *p = src->data; + const uchar *end = p + wbytes; + b2 = line2; + if (use_gray) { // 8 bit image + while (p < end) + *b2++ = gray[*p++]; + } else { // 32 bit image + if (fromalpha) { + while (p < end) { + *b2++ = 255 - (*(uint*)p >> 24); + p += 4; + } + } else { + while (p < end) { + *b2++ = qGray(*(uint*)p); + p += 4; + } + } + } + for (int y=0; ydata + (y+1)*src->bytes_per_line; + end = p + wbytes; + b2 = line2; + if (use_gray) { // 8 bit image + while (p < end) + *b2++ = gray[*p++]; + } else { // 24 bit image + if (fromalpha) { + while (p < end) { + *b2++ = 255 - (*(uint*)p >> 24); + p += 4; + } + } else { + while (p < end) { + *b2++ = qGray(*(uint*)p); + p += 4; + } + } + } + } + + int err; + uchar *p = dst->data + y*dst->bytes_per_line; + memset(p, 0, bmwidth); + b1 = line1; + b2 = line2; + int bit = 7; + for (int x=1; x<=w; x++) { + if (*b1 < 128) { // black pixel + err = *b1++; + *p |= 1 << bit; + } else { // white pixel + err = *b1++ - 255; + } + if (bit == 0) { + p++; + bit = 7; + } else { + bit--; + } + if (x < w) + *b1 += (err*7)>>4; // spread error to right pixel + if (not_last_line) { + b2[0] += (err*5)>>4; // pixel below + if (x > 1) + b2[-1] += (err*3)>>4; // pixel below left + if (x < w) + b2[1] += err>>4; // pixel below right + } + b2++; + } + } + } break; + case Ordered: { + + memset(dst->data, 0, dst->nbytes); + if (d == 32) { + for (int i=0; i> 24) >= qt_bayer_matrix[j++&15][i&15]) + *m |= 1 << bit; + if (bit == 0) { + m++; + bit = 7; + } else { + bit--; + } + } + } else { + while (p < end) { + if ((uint)qGray(*p++) < qt_bayer_matrix[j++&15][i&15]) + *m |= 1 << bit; + if (bit == 0) { + m++; + bit = 7; + } else { + bit--; + } + } + } + dst_data += dst_bpl; + src_data += src_bpl; + } + } else + /* (d == 8) */ { + for (int i=0; idata, 0, dst->nbytes); + if (d == 32) { + for (int i=0; i> 24) >= 128) + *m |= 1 << bit; // Set mask "on" + if (bit == 0) { + m++; + bit = 7; + } else { + bit--; + } + } + } else { + while (p < end) { + if (qGray(*p++) < 128) + *m |= 1 << bit; // Set pixel "black" + if (bit == 0) { + m++; + bit = 7; + } else { + bit--; + } + } + } + dst_data += dst_bpl; + src_data += src_bpl; + } + } else + if (d == 8) { + for (int i=0; iformat == QImage::Format_MonoLSB) { + // need to swap bit order + uchar *sl = dst->data; + int bpl = (dst->width + 7) * dst->depth / 8; + int pad = dst->bytes_per_line - bpl; + for (int y=0; yheight; ++y) { + for (int x=0; x tmp(QImageData::create(QSize(src->width, src->height), QImage::Format_ARGB32)); + convert_ARGB_PM_to_ARGB(tmp.data(), src, flags); + dither_to_Mono(dst, tmp.data(), flags, false); +} + +// +// convert_32_to_8: Converts a 32 bits depth (true color) to an 8 bit +// image with a colormap. If the 32 bit image has more than 256 colors, +// we convert the red,green and blue bytes into a single byte encoded +// as 6 shades of each of red, green and blue. +// +// if dithering is needed, only 1 color at most is available for alpha. +// +struct QRgbMap { + inline QRgbMap() : used(0) { } + uchar pix; + uchar used; + QRgb rgb; +}; + +static void convert_RGB_to_Indexed8(QImageData *dst, const QImageData *src, Qt::ImageConversionFlags flags) +{ + Q_ASSERT(src->format == QImage::Format_RGB32 || src->format == QImage::Format_ARGB32); + Q_ASSERT(dst->format == QImage::Format_Indexed8); + Q_ASSERT(src->width == dst->width); + Q_ASSERT(src->height == dst->height); + + bool do_quant = (flags & Qt::DitherMode_Mask) == Qt::PreferDither + || src->format == QImage::Format_ARGB32; + uint alpha_mask = src->format == QImage::Format_RGB32 ? 0xff000000 : 0; + + const int tablesize = 997; // prime + QRgbMap table[tablesize]; + int pix=0; + + if (!dst->colortable.isEmpty()) { + QVector ctbl = dst->colortable; + dst->colortable.resize(256); + // Preload palette into table. + // Almost same code as pixel insertion below + for (int i = 0; i < dst->colortable.size(); ++i) { + // Find in table... + QRgb p = ctbl.at(i) | alpha_mask; + int hash = p % tablesize; + for (;;) { + if (table[hash].used) { + if (table[hash].rgb == p) { + // Found previous insertion - use it + break; + } else { + // Keep searching... + if (++hash == tablesize) hash = 0; + } + } else { + // Cannot be in table + Q_ASSERT (pix != 256); // too many colors + // Insert into table at this unused position + dst->colortable[pix] = p; + table[hash].pix = pix++; + table[hash].rgb = p; + table[hash].used = 1; + break; + } + } + } + } + + if ((flags & Qt::DitherMode_Mask) != Qt::PreferDither) { + dst->colortable.resize(256); + const uchar *src_data = src->data; + uchar *dest_data = dst->data; + for (int y = 0; y < src->height; y++) { // check if <= 256 colors + const QRgb *s = (const QRgb *)src_data; + uchar *b = dest_data; + for (int x = 0; x < src->width; ++x) { + QRgb p = s[x] | alpha_mask; + int hash = p % tablesize; + for (;;) { + if (table[hash].used) { + if (table[hash].rgb == (p)) { + // Found previous insertion - use it + break; + } else { + // Keep searching... + if (++hash == tablesize) hash = 0; + } + } else { + // Cannot be in table + if (pix == 256) { // too many colors + do_quant = true; + // Break right out + x = src->width; + y = src->height; + } else { + // Insert into table at this unused position + dst->colortable[pix] = p; + table[hash].pix = pix++; + table[hash].rgb = p; + table[hash].used = 1; + } + break; + } + } + *b++ = table[hash].pix; // May occur once incorrectly + } + src_data += src->bytes_per_line; + dest_data += dst->bytes_per_line; + } + } + int numColors = do_quant ? 256 : pix; + + dst->colortable.resize(numColors); + + if (do_quant) { // quantization needed + +#define MAX_R 5 +#define MAX_G 5 +#define MAX_B 5 +#define INDEXOF(r,g,b) (((r)*(MAX_G+1)+(g))*(MAX_B+1)+(b)) + + for (int rc=0; rc<=MAX_R; rc++) // build 6x6x6 color cube + for (int gc=0; gc<=MAX_G; gc++) + for (int bc=0; bc<=MAX_B; bc++) + dst->colortable[INDEXOF(rc,gc,bc)] = 0xff000000 | qRgb(rc*255/MAX_R, gc*255/MAX_G, bc*255/MAX_B); + + const uchar *src_data = src->data; + uchar *dest_data = dst->data; + if ((flags & Qt::Dither_Mask) == Qt::ThresholdDither) { + for (int y = 0; y < src->height; y++) { + const QRgb *p = (const QRgb *)src_data; + const QRgb *end = p + src->width; + uchar *b = dest_data; + + while (p < end) { +#define DITHER(p,m) ((uchar) ((p * (m) + 127) / 255)) + *b++ = + INDEXOF( + DITHER(qRed(*p), MAX_R), + DITHER(qGreen(*p), MAX_G), + DITHER(qBlue(*p), MAX_B) + ); +#undef DITHER + p++; + } + src_data += src->bytes_per_line; + dest_data += dst->bytes_per_line; + } + } else if ((flags & Qt::Dither_Mask) == Qt::DiffuseDither) { + int* line1[3]; + int* line2[3]; + int* pv[3]; + QScopedArrayPointer lineBuffer(new int[src->width * 9]); + line1[0] = lineBuffer.data(); + line2[0] = lineBuffer.data() + src->width; + line1[1] = lineBuffer.data() + src->width * 2; + line2[1] = lineBuffer.data() + src->width * 3; + line1[2] = lineBuffer.data() + src->width * 4; + line2[2] = lineBuffer.data() + src->width * 5; + pv[0] = lineBuffer.data() + src->width * 6; + pv[1] = lineBuffer.data() + src->width * 7; + pv[2] = lineBuffer.data() + src->width * 8; + + int endian = (QSysInfo::ByteOrder == QSysInfo::BigEndian); + for (int y = 0; y < src->height; y++) { + const uchar* q = src_data; + const uchar* q2 = y < src->height - 1 ? q + src->bytes_per_line : src->data; + uchar *b = dest_data; + for (int chan = 0; chan < 3; chan++) { + int *l1 = (y&1) ? line2[chan] : line1[chan]; + int *l2 = (y&1) ? line1[chan] : line2[chan]; + if (y == 0) { + for (int i = 0; i < src->width; i++) + l1[i] = q[i*4+chan+endian]; + } + if (y+1 < src->height) { + for (int i = 0; i < src->width; i++) + l2[i] = q2[i*4+chan+endian]; + } + // Bi-directional error diffusion + if (y&1) { + for (int x = 0; x < src->width; x++) { + int pix = qMax(qMin(5, (l1[x] * 5 + 128)/ 255), 0); + int err = l1[x] - pix * 255 / 5; + pv[chan][x] = pix; + + // Spread the error around... + if (x + 1< src->width) { + l1[x+1] += (err*7)>>4; + l2[x+1] += err>>4; + } + l2[x]+=(err*5)>>4; + if (x>1) + l2[x-1]+=(err*3)>>4; + } + } else { + for (int x = src->width; x-- > 0;) { + int pix = qMax(qMin(5, (l1[x] * 5 + 128)/ 255), 0); + int err = l1[x] - pix * 255 / 5; + pv[chan][x] = pix; + + // Spread the error around... + if (x > 0) { + l1[x-1] += (err*7)>>4; + l2[x-1] += err>>4; + } + l2[x]+=(err*5)>>4; + if (x + 1 < src->width) + l2[x+1]+=(err*3)>>4; + } + } + } + if (endian) { + for (int x = 0; x < src->width; x++) { + *b++ = INDEXOF(pv[0][x],pv[1][x],pv[2][x]); + } + } else { + for (int x = 0; x < src->width; x++) { + *b++ = INDEXOF(pv[2][x],pv[1][x],pv[0][x]); + } + } + src_data += src->bytes_per_line; + dest_data += dst->bytes_per_line; + } + } else { // OrderedDither + for (int y = 0; y < src->height; y++) { + const QRgb *p = (const QRgb *)src_data; + const QRgb *end = p + src->width; + uchar *b = dest_data; + + int x = 0; + while (p < end) { + uint d = qt_bayer_matrix[y & 15][x & 15] << 8; + +#define DITHER(p, d, m) ((uchar) ((((256 * (m) + (m) + 1)) * (p) + (d)) >> 16)) + *b++ = + INDEXOF( + DITHER(qRed(*p), d, MAX_R), + DITHER(qGreen(*p), d, MAX_G), + DITHER(qBlue(*p), d, MAX_B) + ); +#undef DITHER + + p++; + x++; + } + src_data += src->bytes_per_line; + dest_data += dst->bytes_per_line; + } + } + + if (src->format != QImage::Format_RGB32 + && src->format != QImage::Format_RGB16) { + const int trans = 216; + Q_ASSERT(dst->colortable.size() > trans); + dst->colortable[trans] = 0; + QScopedPointer mask(QImageData::create(QSize(src->width, src->height), QImage::Format_Mono)); + dither_to_Mono(mask.data(), src, flags, true); + uchar *dst_data = dst->data; + const uchar *mask_data = mask->data; + for (int y = 0; y < src->height; y++) { + for (int x = 0; x < src->width ; x++) { + if (!(mask_data[x>>3] & (0x80 >> (x & 7)))) + dst_data[x] = trans; + } + mask_data += mask->bytes_per_line; + dst_data += dst->bytes_per_line; + } + dst->has_alpha_clut = true; + } + +#undef MAX_R +#undef MAX_G +#undef MAX_B +#undef INDEXOF + + } +} + +static void convert_ARGB_PM_to_Indexed8(QImageData *dst, const QImageData *src, Qt::ImageConversionFlags flags) +{ + QScopedPointer tmp(QImageData::create(QSize(src->width, src->height), QImage::Format_ARGB32)); + convert_ARGB_PM_to_ARGB(tmp.data(), src, flags); + convert_RGB_to_Indexed8(dst, tmp.data(), flags); +} + +static void convert_ARGB_to_Indexed8(QImageData *dst, const QImageData *src, Qt::ImageConversionFlags flags) +{ + convert_RGB_to_Indexed8(dst, src, flags); +} + +static void convert_Indexed8_to_X32(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_Indexed8); + Q_ASSERT(dest->format == QImage::Format_RGB32 + || dest->format == QImage::Format_ARGB32 + || dest->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + QVector colorTable = fix_color_table(src->colortable, dest->format); + if (colorTable.size() == 0) { + colorTable.resize(256); + for (int i=0; i<256; ++i) + colorTable[i] = qRgb(i, i, i); + } + + int w = src->width; + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + int tableSize = colorTable.size() - 1; + for (int y = 0; y < src->height; y++) { + uint *p = (uint *)dest_data; + const uchar *b = src_data; + uint *end = p + w; + + while (p < end) + *p++ = colorTable.at(qMin(tableSize, *b++)); + + src_data += src->bytes_per_line; + dest_data += dest->bytes_per_line; + } +} + +static void convert_Mono_to_X32(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_Mono || src->format == QImage::Format_MonoLSB); + Q_ASSERT(dest->format == QImage::Format_RGB32 + || dest->format == QImage::Format_ARGB32 + || dest->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + QVector colorTable = fix_color_table(src->colortable, dest->format); + + // Default to black / white colors + if (colorTable.size() < 2) { + if (colorTable.size() == 0) + colorTable << 0xff000000; + colorTable << 0xffffffff; + } + + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + if (src->format == QImage::Format_Mono) { + for (int y = 0; y < dest->height; y++) { + register uint *p = (uint *)dest_data; + for (int x = 0; x < dest->width; x++) + *p++ = colorTable.at((src_data[x>>3] >> (7 - (x & 7))) & 1); + + src_data += src->bytes_per_line; + dest_data += dest->bytes_per_line; + } + } else { + for (int y = 0; y < dest->height; y++) { + register uint *p = (uint *)dest_data; + for (int x = 0; x < dest->width; x++) + *p++ = colorTable.at((src_data[x>>3] >> (x & 7)) & 1); + + src_data += src->bytes_per_line; + dest_data += dest->bytes_per_line; + } + } +} + + +static void convert_Mono_to_Indexed8(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_Mono || src->format == QImage::Format_MonoLSB); + Q_ASSERT(dest->format == QImage::Format_Indexed8); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + QVector ctbl = src->colortable; + if (ctbl.size() > 2) { + ctbl.resize(2); + } else if (ctbl.size() < 2) { + if (ctbl.size() == 0) + ctbl << 0xff000000; + ctbl << 0xffffffff; + } + dest->colortable = ctbl; + dest->has_alpha_clut = src->has_alpha_clut; + + + const uchar *src_data = src->data; + uchar *dest_data = dest->data; + if (src->format == QImage::Format_Mono) { + for (int y = 0; y < dest->height; y++) { + register uchar *p = dest_data; + for (int x = 0; x < dest->width; x++) + *p++ = (src_data[x>>3] >> (7 - (x & 7))) & 1; + src_data += src->bytes_per_line; + dest_data += dest->bytes_per_line; + } + } else { + for (int y = 0; y < dest->height; y++) { + register uchar *p = dest_data; + for (int x = 0; x < dest->width; x++) + *p++ = (src_data[x>>3] >> (x & 7)) & 1; + src_data += src->bytes_per_line; + dest_data += dest->bytes_per_line; + } + } +} + +#define CONVERT_DECL(DST, SRC) \ + static void convert_##SRC##_to_##DST(QImageData *dest, \ + const QImageData *src, \ + Qt::ImageConversionFlags) \ + { \ + qt_rectconvert(reinterpret_cast(dest->data), \ + reinterpret_cast(src->data), \ + 0, 0, src->width, src->height, \ + dest->bytes_per_line, src->bytes_per_line); \ + } + +CONVERT_DECL(quint32, quint16) +CONVERT_DECL(quint16, quint32) +CONVERT_DECL(quint32, qargb8565) +CONVERT_DECL(qargb8565, quint32) +CONVERT_DECL(quint32, qrgb555) +CONVERT_DECL(qrgb666, quint32) +CONVERT_DECL(quint32, qrgb666) +CONVERT_DECL(qargb6666, quint32) +CONVERT_DECL(quint32, qargb6666) +CONVERT_DECL(qrgb555, quint32) +#if !defined(Q_WS_QWS) || (defined(QT_QWS_DEPTH_15) && defined(QT_QWS_DEPTH_16)) +CONVERT_DECL(quint16, qrgb555) +CONVERT_DECL(qrgb555, quint16) +#endif +CONVERT_DECL(quint32, qrgb888) +CONVERT_DECL(qrgb888, quint32) +CONVERT_DECL(quint32, qargb8555) +CONVERT_DECL(qargb8555, quint32) +CONVERT_DECL(quint32, qrgb444) +CONVERT_DECL(qrgb444, quint32) +CONVERT_DECL(quint32, qargb4444) +CONVERT_DECL(qargb4444, quint32) +#undef CONVERT_DECL +#define CONVERT_PTR(DST, SRC) convert_##SRC##_to_##DST + +/* + Format_Invalid, + Format_Mono, + Format_MonoLSB, + Format_Indexed8, + Format_RGB32, + Format_ARGB32, + Format_ARGB32_Premultiplied, + Format_RGB16, + Format_ARGB8565_Premultiplied, + Format_RGB666, + Format_ARGB6666_Premultiplied, + Format_RGB555, + Format_ARGB8555_Premultiplied, + Format_RGB888 + Format_RGB444 + Format_ARGB4444_Premultiplied +*/ + + +// first index source, second dest +static Image_Converter converter_map[QImage::NImageFormats][QImage::NImageFormats] = +{ + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + { + 0, + 0, + swap_bit_order, + convert_Mono_to_Indexed8, + convert_Mono_to_X32, + convert_Mono_to_X32, + convert_Mono_to_X32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_Mono + + { + 0, + swap_bit_order, + 0, + convert_Mono_to_Indexed8, + convert_Mono_to_X32, + convert_Mono_to_X32, + convert_Mono_to_X32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_MonoLSB + + { + 0, + convert_X_to_Mono, + convert_X_to_Mono, + 0, + convert_Indexed8_to_X32, + convert_Indexed8_to_X32, + convert_Indexed8_to_X32, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_Indexed8 + + { + 0, + convert_X_to_Mono, + convert_X_to_Mono, + convert_RGB_to_Indexed8, + 0, + mask_alpha_converter, + mask_alpha_converter, + CONVERT_PTR(quint16, quint32), + CONVERT_PTR(qargb8565, quint32), + CONVERT_PTR(qrgb666, quint32), + CONVERT_PTR(qargb6666, quint32), + CONVERT_PTR(qrgb555, quint32), + CONVERT_PTR(qargb8555, quint32), + CONVERT_PTR(qrgb888, quint32), + CONVERT_PTR(qrgb444, quint32), + CONVERT_PTR(qargb4444, quint32) + }, // Format_RGB32 + + { + 0, + convert_X_to_Mono, + convert_X_to_Mono, + convert_ARGB_to_Indexed8, + mask_alpha_converter, + 0, + convert_ARGB_to_ARGB_PM, + CONVERT_PTR(quint16, quint32), + CONVERT_PTR(qargb8565, quint32), + CONVERT_PTR(qrgb666, quint32), + CONVERT_PTR(qargb6666, quint32), + CONVERT_PTR(qrgb555, quint32), + CONVERT_PTR(qargb8555, quint32), + CONVERT_PTR(qrgb888, quint32), + CONVERT_PTR(qrgb444, quint32), + CONVERT_PTR(qargb4444, quint32) + }, // Format_ARGB32 + + { + 0, + convert_ARGB_PM_to_Mono, + convert_ARGB_PM_to_Mono, + convert_ARGB_PM_to_Indexed8, + convert_ARGB_PM_to_RGB, + convert_ARGB_PM_to_ARGB, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_ARGB32_Premultiplied + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, quint16), + CONVERT_PTR(quint32, quint16), + CONVERT_PTR(quint32, quint16), + 0, + 0, + 0, + 0, +#if !defined(Q_WS_QWS) || (defined(QT_QWS_DEPTH_15) && defined(QT_QWS_DEPTH_16)) + CONVERT_PTR(qrgb555, quint16), +#else + 0, +#endif + 0, + 0, + 0, + 0 + }, // Format_RGB16 + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qargb8565), + CONVERT_PTR(quint32, qargb8565), + CONVERT_PTR(quint32, qargb8565), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_ARGB8565_Premultiplied + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qrgb666), + CONVERT_PTR(quint32, qrgb666), + CONVERT_PTR(quint32, qrgb666), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_RGB666 + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qargb6666), + CONVERT_PTR(quint32, qargb6666), + CONVERT_PTR(quint32, qargb6666), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_ARGB6666_Premultiplied + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qrgb555), + CONVERT_PTR(quint32, qrgb555), + CONVERT_PTR(quint32, qrgb555), +#if !defined(Q_WS_QWS) || (defined(QT_QWS_DEPTH_15) && defined(QT_QWS_DEPTH_16)) + CONVERT_PTR(quint16, qrgb555), +#else + 0, +#endif + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_RGB555 + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qargb8555), + CONVERT_PTR(quint32, qargb8555), + CONVERT_PTR(quint32, qargb8555), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_ARGB8555_Premultiplied + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qrgb888), + CONVERT_PTR(quint32, qrgb888), + CONVERT_PTR(quint32, qrgb888), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_RGB888 + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qrgb444), + CONVERT_PTR(quint32, qrgb444), + CONVERT_PTR(quint32, qrgb444), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + }, // Format_RGB444 + + { + 0, + 0, + 0, + 0, + CONVERT_PTR(quint32, qargb4444), + CONVERT_PTR(quint32, qargb4444), + CONVERT_PTR(quint32, qargb4444), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + } // Format_ARGB4444_Premultiplied +}; + +static InPlace_Image_Converter inplace_converter_map[QImage::NImageFormats][QImage::NImageFormats] = +{ + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_Mono + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_MonoLSB + { + 0, + 0, + 0, + 0, + 0, + convert_indexed8_to_RGB_inplace, + convert_indexed8_to_ARGB_PM_inplace, + convert_indexed8_to_RGB16_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_Indexed8 + { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + convert_RGB_to_RGB16_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_ARGB32 + { + 0, + 0, + 0, + 0, + 0, + 0, + convert_ARGB_to_ARGB_PM_inplace, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, // Format_ARGB32 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB32_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB16 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB8565_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB666 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB6666_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB555 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_ARGB8555_Premultiplied + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB888 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, // Format_RGB444 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + } // Format_ARGB4444_Premultiplied +}; + +void qInitImageConversions() +{ + const uint features = qDetectCPUFeatures(); + Q_UNUSED(features); + +#ifdef QT_HAVE_SSE2 + if (features & SSE2) { + extern bool convert_ARGB_to_ARGB_PM_inplace_sse2(QImageData *data, Qt::ImageConversionFlags); + inplace_converter_map[QImage::Format_ARGB32][QImage::Format_ARGB32_Premultiplied] = convert_ARGB_to_ARGB_PM_inplace_sse2; + } +#endif +#ifdef QT_HAVE_SSSE3 + if (features & SSSE3) { + extern void convert_RGB888_to_RGB32_ssse3(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags); + converter_map[QImage::Format_RGB888][QImage::Format_RGB32] = convert_RGB888_to_RGB32_ssse3; + converter_map[QImage::Format_RGB888][QImage::Format_ARGB32] = convert_RGB888_to_RGB32_ssse3; + converter_map[QImage::Format_RGB888][QImage::Format_ARGB32_Premultiplied] = convert_RGB888_to_RGB32_ssse3; + } +#endif +#ifdef QT_HAVE_NEON + if (features & NEON) { + extern void convert_RGB888_to_RGB32_neon(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags); + converter_map[QImage::Format_RGB888][QImage::Format_RGB32] = convert_RGB888_to_RGB32_neon; + converter_map[QImage::Format_RGB888][QImage::Format_ARGB32] = convert_RGB888_to_RGB32_neon; + converter_map[QImage::Format_RGB888][QImage::Format_ARGB32_Premultiplied] = convert_RGB888_to_RGB32_neon; + } +#endif +} + +void qGamma_correct_back_to_linear_cs(QImage *image) +{ + extern uchar qt_pow_rgb_gamma[256]; + + // gamma correct the pixels back to linear color space... + int h = image->height(); + int w = image->width(); + + for (int y=0; yscanLine(y); + for (int x=0; xformat == format) + return *this; + + if (format == Format_Invalid || d->format == Format_Invalid) + return QImage(); + + const Image_Converter *converterPtr = &converter_map[d->format][format]; + Image_Converter converter = *converterPtr; + if (converter) { + QImage image(d->width, d->height, format); + + QIMAGE_SANITYCHECK_MEMORY(image); + + image.setDotsPerMeterY(dotsPerMeterY()); + image.setDotsPerMeterX(dotsPerMeterX()); + +#if !defined(QT_NO_IMAGE_TEXT) + image.d->text = d->text; +#endif // !QT_NO_IMAGE_TEXT + + converter(image.d, d, flags); + return image; + } + + Q_ASSERT(format != QImage::Format_ARGB32); + Q_ASSERT(d->format != QImage::Format_ARGB32); + + QImage image = convertToFormat(Format_ARGB32, flags); + return image.convertToFormat(format, flags); +} + + + +static inline int pixel_distance(QRgb p1, QRgb p2) { + int r1 = qRed(p1); + int g1 = qGreen(p1); + int b1 = qBlue(p1); + int a1 = qAlpha(p1); + + int r2 = qRed(p2); + int g2 = qGreen(p2); + int b2 = qBlue(p2); + int a2 = qAlpha(p2); + + return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2) + abs(a1 - a2); +} + +static inline int closestMatch(QRgb pixel, const QVector &clut) { + int idx = 0; + int current_distance = INT_MAX; + for (int i=0; i &clut) { + QImage dest(src.size(), format); + dest.setColorTable(clut); + +#if !defined(QT_NO_IMAGE_TEXT) + QString textsKeys = src.text(); + QStringList textKeyList = textsKeys.split(QLatin1Char('\n'), QString::SkipEmptyParts); + foreach (const QString &textKey, textKeyList) { + QStringList textKeySplitted = textKey.split(QLatin1String(": ")); + dest.setText(textKeySplitted[0], textKeySplitted[1]); + } +#endif // !QT_NO_IMAGE_TEXT + + int h = src.height(); + int w = src.width(); + + QHash cache; + + if (format == QImage::Format_Indexed8) { + for (int y=0; y table = clut; + table.resize(2); + for (int y=0; y &colorTable, Qt::ImageConversionFlags flags) const +{ + if (d->format == format) + return *this; + + if (format <= QImage::Format_Indexed8 && depth() == 32) { + return convertWithPalette(*this, format, colorTable); + } + + const Image_Converter *converterPtr = &converter_map[d->format][format]; + Image_Converter converter = *converterPtr; + if (!converter) + return QImage(); + + QImage image(d->width, d->height, format); + QIMAGE_SANITYCHECK_MEMORY(image); + +#if !defined(QT_NO_IMAGE_TEXT) + image.d->text = d->text; +#endif // !QT_NO_IMAGE_TEXT + + converter(image.d, d, flags); + return image; +} + +#ifdef QT3_SUPPORT +/*! + Converts the depth (bpp) of the image to the given \a depth and + returns the converted image. The original image is not changed. + Returns this image if \a depth is equal to the image depth, or a + null image if this image cannot be converted. The \a depth + argument must be 1, 8 or 32. If the image needs to be modified to + fit in a lower-resolution result (e.g. converting from 32-bit to + 8-bit), use the \a flags to specify how you'd prefer this to + happen. + + Use the convertToFormat() function instead. +*/ + +QImage QImage::convertDepth(int depth, Qt::ImageConversionFlags flags) const +{ + if (!d || d->depth == depth) + return *this; + + Format format = formatFor (depth, QImage::LittleEndian); + return convertToFormat(format, flags); +} +#endif + +/*! + \fn bool QImage::valid(const QPoint &pos) const + + Returns true if \a pos is a valid coordinate pair within the + image; otherwise returns false. + + \sa rect(), QRect::contains() +*/ + +/*! + \overload + + Returns true if QPoint(\a x, \a y) is a valid coordinate pair + within the image; otherwise returns false. +*/ +bool QImage::valid(int x, int y) const +{ + return d + && x >= 0 && x < d->width + && y >= 0 && y < d->height; +} + +/*! + \fn int QImage::pixelIndex(const QPoint &position) const + + Returns the pixel index at the given \a position. + + If \a position is not valid, or if the image is not a paletted + image (depth() > 8), the results are undefined. + + \sa valid(), depth(), {QImage#Pixel Manipulation}{Pixel Manipulation} +*/ + +/*! + \overload + + Returns the pixel index at (\a x, \a y). +*/ +int QImage::pixelIndex(int x, int y) const +{ + if (!d || x < 0 || x >= d->width || y < 0 || y >= height()) { + qWarning("QImage::pixelIndex: coordinate (%d,%d) out of range", x, y); + return -12345; + } + const uchar * s = scanLine(y); + switch(d->format) { + case Format_Mono: + return (*(s + (x >> 3)) >> (7- (x & 7))) & 1; + case Format_MonoLSB: + return (*(s + (x >> 3)) >> (x & 7)) & 1; + case Format_Indexed8: + return (int)s[x]; + default: + qWarning("QImage::pixelIndex: Not applicable for %d-bpp images (no palette)", d->depth); + } + return 0; +} + + +/*! + \fn QRgb QImage::pixel(const QPoint &position) const + + Returns the color of the pixel at the given \a position. + + If the \a position is not valid, the results are undefined. + + \warning This function is expensive when used for massive pixel + manipulations. + + \sa setPixel(), valid(), {QImage#Pixel Manipulation}{Pixel + Manipulation} +*/ + +/*! + \overload + + Returns the color of the pixel at coordinates (\a x, \a y). +*/ +QRgb QImage::pixel(int x, int y) const +{ + if (!d || x < 0 || x >= d->width || y < 0 || y >= height()) { + qWarning("QImage::pixel: coordinate (%d,%d) out of range", x, y); + return 12345; + } + const uchar * s = scanLine(y); + switch(d->format) { + case Format_Mono: + return d->colortable.at((*(s + (x >> 3)) >> (7- (x & 7))) & 1); + case Format_MonoLSB: + return d->colortable.at((*(s + (x >> 3)) >> (x & 7)) & 1); + case Format_Indexed8: + return d->colortable.at((int)s[x]); + case Format_ARGB8565_Premultiplied: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_RGB666: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_ARGB6666_Premultiplied: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_RGB555: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_ARGB8555_Premultiplied: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_RGB888: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_RGB444: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_ARGB4444_Premultiplied: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + case Format_RGB16: + return qt_colorConvert(reinterpret_cast(s)[x], 0); + default: + return ((QRgb*)s)[x]; + } +} + + +/*! + \fn void QImage::setPixel(const QPoint &position, uint index_or_rgb) + + Sets the pixel index or color at the given \a position to \a + index_or_rgb. + + If the image's format is either monochrome or 8-bit, the given \a + index_or_rgb value must be an index in the image's color table, + otherwise the parameter must be a QRgb value. + + If \a position is not a valid coordinate pair in the image, or if + \a index_or_rgb >= colorCount() in the case of monochrome and + 8-bit images, the result is undefined. + + \warning This function is expensive due to the call of the internal + \c{detach()} function called within; if performance is a concern, we + recommend the use of \l{QImage::}{scanLine()} to access pixel data + directly. + + \sa pixel(), {QImage#Pixel Manipulation}{Pixel Manipulation} +*/ + +/*! + \overload + + Sets the pixel index or color at (\a x, \a y) to \a index_or_rgb. +*/ +void QImage::setPixel(int x, int y, uint index_or_rgb) +{ + if (!d || x < 0 || x >= width() || y < 0 || y >= height()) { + qWarning("QImage::setPixel: coordinate (%d,%d) out of range", x, y); + return; + } + // detach is called from within scanLine + uchar * s = scanLine(y); + const quint32p p = quint32p::fromRawData(index_or_rgb); + switch(d->format) { + case Format_Mono: + case Format_MonoLSB: + if (index_or_rgb > 1) { + qWarning("QImage::setPixel: Index %d out of range", index_or_rgb); + } else if (format() == Format_MonoLSB) { + if (index_or_rgb==0) + *(s + (x >> 3)) &= ~(1 << (x & 7)); + else + *(s + (x >> 3)) |= (1 << (x & 7)); + } else { + if (index_or_rgb==0) + *(s + (x >> 3)) &= ~(1 << (7-(x & 7))); + else + *(s + (x >> 3)) |= (1 << (7-(x & 7))); + } + break; + case Format_Indexed8: + if (index_or_rgb >= (uint)d->colortable.size()) { + qWarning("QImage::setPixel: Index %d out of range", index_or_rgb); + return; + } + s[x] = index_or_rgb; + break; + case Format_RGB32: + //make sure alpha is 255, we depend on it in qdrawhelper for cases + // when image is set as a texture pattern on a qbrush + ((uint *)s)[x] = uint(255 << 24) | index_or_rgb; + break; + case Format_ARGB32: + case Format_ARGB32_Premultiplied: + ((uint *)s)[x] = index_or_rgb; + break; + case Format_RGB16: + ((quint16 *)s)[x] = qt_colorConvert(p, 0); + break; + case Format_ARGB8565_Premultiplied: + ((qargb8565*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_RGB666: + ((qrgb666*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_ARGB6666_Premultiplied: + ((qargb6666*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_RGB555: + ((qrgb555*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_ARGB8555_Premultiplied: + ((qargb8555*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_RGB888: + ((qrgb888*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_RGB444: + ((qrgb444*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_ARGB4444_Premultiplied: + ((qargb4444*)s)[x] = qt_colorConvert(p, 0); + break; + case Format_Invalid: + case NImageFormats: + Q_ASSERT(false); + } +} + +#ifdef QT3_SUPPORT +/*! + Converts the bit order of the image to the given \a bitOrder and + returns the converted image. The original image is not changed. + Returns this image if the given \a bitOrder is equal to the image + current bit order, or a null image if this image cannot be + converted. + + Use convertToFormat() instead. +*/ + +QImage QImage::convertBitOrder(Endian bitOrder) const +{ + if (!d || isNull() || d->depth != 1 || !(bitOrder == BigEndian || bitOrder == LittleEndian)) + return QImage(); + + if ((d->format == Format_Mono && bitOrder == BigEndian) + || (d->format == Format_MonoLSB && bitOrder == LittleEndian)) + return *this; + + QImage image(d->width, d->height, d->format == Format_Mono ? Format_MonoLSB : Format_Mono); + + const uchar *data = d->data; + const uchar *end = data + d->nbytes; + uchar *ndata = image.d->data; + while (data < end) + *ndata++ = bitflip[*data++]; + + image.setDotsPerMeterX(dotsPerMeterX()); + image.setDotsPerMeterY(dotsPerMeterY()); + + image.d->colortable = d->colortable; + return image; +} +#endif +/*! + Returns true if all the colors in the image are shades of gray + (i.e. their red, green and blue components are equal); otherwise + false. + + Note that this function is slow for images without color table. + + \sa isGrayscale() +*/ +bool QImage::allGray() const +{ + if (!d) + return true; + + if (d->depth == 32) { + int p = width()*height(); + const QRgb* b = (const QRgb*)bits(); + while (p--) + if (!qIsGray(*b++)) + return false; + } else if (d->depth == 16) { + int p = width()*height(); + const ushort* b = (const ushort *)bits(); + while (p--) + if (!qIsGray(qt_colorConvert(*b++, 0))) + return false; + } else if (d->format == QImage::Format_RGB888) { + int p = width()*height(); + const qrgb888* b = (const qrgb888 *)bits(); + while (p--) + if (!qIsGray(qt_colorConvert(*b++, 0))) + return false; + } else { + if (d->colortable.isEmpty()) + return true; + for (int i = 0; i < colorCount(); i++) + if (!qIsGray(d->colortable.at(i))) + return false; + } + return true; +} + +/*! + For 32-bit images, this function is equivalent to allGray(). + + For 8-bpp images, this function returns true if color(i) is + QRgb(i, i, i) for all indexes of the color table; otherwise + returns false. + + \sa allGray(), {QImage#Image Formats}{Image Formats} +*/ +bool QImage::isGrayscale() const +{ + if (!d) + return false; + + switch (depth()) { + case 32: + case 24: + case 16: + return allGray(); + case 8: { + for (int i = 0; i < colorCount(); i++) + if (d->colortable.at(i) != qRgb(i,i,i)) + return false; + return true; + } + } + return false; +} + + +/*! + \fn QImage QImage::smoothScale(int width, int height, Qt::AspectRatioMode mode) const + + Use scaled() instead. + + \oldcode + QImage image; + image.smoothScale(width, height, mode); + \newcode + QImage image; + image.scaled(width, height, mode, Qt::SmoothTransformation); + \endcode +*/ + +/*! + \fn QImage QImage::smoothScale(const QSize &size, Qt::AspectRatioMode mode) const + \overload + + Use scaled() instead. + + \oldcode + QImage image; + image.smoothScale(size, mode); + \newcode + QImage image; + image.scaled(size, mode, Qt::SmoothTransformation); + \endcode +*/ + +/*! + \fn QImage QImage::scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, + Qt::TransformationMode transformMode) const + \overload + + Returns a copy of the image scaled to a rectangle with the given + \a width and \a height according to the given \a aspectRatioMode + and \a transformMode. + + If either the \a width or the \a height is zero or negative, this + function returns a null image. +*/ + +/*! + \fn QImage QImage::scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, + Qt::TransformationMode transformMode) const + + Returns a copy of the image scaled to a rectangle defined by the + given \a size according to the given \a aspectRatioMode and \a + transformMode. + + \image qimage-scaling.png + + \list + \i If \a aspectRatioMode is Qt::IgnoreAspectRatio, the image + is scaled to \a size. + \i If \a aspectRatioMode is Qt::KeepAspectRatio, the image is + scaled to a rectangle as large as possible inside \a size, preserving the aspect ratio. + \i If \a aspectRatioMode is Qt::KeepAspectRatioByExpanding, + the image is scaled to a rectangle as small as possible + outside \a size, preserving the aspect ratio. + \endlist + + If the given \a size is empty, this function returns a null image. + + \sa isNull(), {QImage#Image Transformations}{Image + Transformations} +*/ +QImage QImage::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode) const +{ + if (!d) { + qWarning("QImage::scaled: Image is a null image"); + return QImage(); + } + if (s.isEmpty()) + return QImage(); + + QSize newSize = size(); + newSize.scale(s, aspectMode); + if (newSize == size()) + return *this; + + QTransform wm = QTransform::fromScale((qreal)newSize.width() / width(), (qreal)newSize.height() / height()); + QImage img = transformed(wm, mode); + return img; +} + +/*! + \fn QImage QImage::scaledToWidth(int width, Qt::TransformationMode mode) const + + Returns a scaled copy of the image. The returned image is scaled + to the given \a width using the specified transformation \a + mode. + + This function automatically calculates the height of the image so + that its aspect ratio is preserved. + + If the given \a width is 0 or negative, a null image is returned. + + \sa {QImage#Image Transformations}{Image Transformations} +*/ +QImage QImage::scaledToWidth(int w, Qt::TransformationMode mode) const +{ + if (!d) { + qWarning("QImage::scaleWidth: Image is a null image"); + return QImage(); + } + if (w <= 0) + return QImage(); + + qreal factor = (qreal) w / width(); + QTransform wm = QTransform::fromScale(factor, factor); + return transformed(wm, mode); +} + +/*! + \fn QImage QImage::scaledToHeight(int height, Qt::TransformationMode mode) const + + Returns a scaled copy of the image. The returned image is scaled + to the given \a height using the specified transformation \a + mode. + + This function automatically calculates the width of the image so that + the ratio of the image is preserved. + + If the given \a height is 0 or negative, a null image is returned. + + \sa {QImage#Image Transformations}{Image Transformations} +*/ +QImage QImage::scaledToHeight(int h, Qt::TransformationMode mode) const +{ + if (!d) { + qWarning("QImage::scaleHeight: Image is a null image"); + return QImage(); + } + if (h <= 0) + return QImage(); + + qreal factor = (qreal) h / height(); + QTransform wm = QTransform::fromScale(factor, factor); + return transformed(wm, mode); +} + + +/*! + \fn QMatrix QImage::trueMatrix(const QMatrix &matrix, int width, int height) + + Returns the actual matrix used for transforming an image with the + given \a width, \a height and \a matrix. + + When transforming an image using the transformed() function, the + transformation matrix is internally adjusted to compensate for + unwanted translation, i.e. transformed() returns the smallest + image containing all transformed points of the original image. + This function returns the modified matrix, which maps points + correctly from the original image into the new image. + + \sa transformed(), {QImage#Image Transformations}{Image + Transformations} +*/ +QMatrix QImage::trueMatrix(const QMatrix &matrix, int w, int h) +{ + return trueMatrix(QTransform(matrix), w, h).toAffine(); +} + +/*! + Returns a copy of the image that is transformed using the given + transformation \a matrix and transformation \a mode. + + The transformation \a matrix is internally adjusted to compensate + for unwanted translation; i.e. the image produced is the smallest + image that contains all the transformed points of the original + image. Use the trueMatrix() function to retrieve the actual matrix + used for transforming an image. + + \sa trueMatrix(), {QImage#Image Transformations}{Image + Transformations} +*/ +QImage QImage::transformed(const QMatrix &matrix, Qt::TransformationMode mode) const +{ + return transformed(QTransform(matrix), mode); +} + +/*! + Builds and returns a 1-bpp mask from the alpha buffer in this + image. Returns a null image if the image's format is + QImage::Format_RGB32. + + The \a flags argument is a bitwise-OR of the + Qt::ImageConversionFlags, and controls the conversion + process. Passing 0 for flags sets all the default options. + + The returned image has little-endian bit order (i.e. the image's + format is QImage::Format_MonoLSB), which you can convert to + big-endian (QImage::Format_Mono) using the convertToFormat() + function. + + \sa createHeuristicMask(), {QImage#Image Transformations}{Image + Transformations} +*/ +QImage QImage::createAlphaMask(Qt::ImageConversionFlags flags) const +{ + if (!d || d->format == QImage::Format_RGB32) + return QImage(); + + if (d->depth == 1) { + // A monochrome pixmap, with alpha channels on those two colors. + // Pretty unlikely, so use less efficient solution. + return convertToFormat(Format_Indexed8, flags).createAlphaMask(flags); + } + + QImage mask(d->width, d->height, Format_MonoLSB); + if (!mask.isNull()) + dither_to_Mono(mask.d, d, flags, true); + return mask; +} + +#ifndef QT_NO_IMAGE_HEURISTIC_MASK +/*! + Creates and returns a 1-bpp heuristic mask for this image. + + The function works by selecting a color from one of the corners, + then chipping away pixels of that color starting at all the edges. + The four corners vote for which color is to be masked away. In + case of a draw (this generally means that this function is not + applicable to the image), the result is arbitrary. + + The returned image has little-endian bit order (i.e. the image's + format is QImage::Format_MonoLSB), which you can convert to + big-endian (QImage::Format_Mono) using the convertToFormat() + function. + + If \a clipTight is true (the default) the mask is just large + enough to cover the pixels; otherwise, the mask is larger than the + data pixels. + + Note that this function disregards the alpha buffer. + + \sa createAlphaMask(), {QImage#Image Transformations}{Image + Transformations} +*/ + +QImage QImage::createHeuristicMask(bool clipTight) const +{ + if (!d) + return QImage(); + + if (d->depth != 32) { + QImage img32 = convertToFormat(Format_RGB32); + return img32.createHeuristicMask(clipTight); + } + +#define PIX(x,y) (*((QRgb*)scanLine(y)+x) & 0x00ffffff) + + int w = width(); + int h = height(); + QImage m(w, h, Format_MonoLSB); + QIMAGE_SANITYCHECK_MEMORY(m); + m.setColorCount(2); + m.setColor(0, QColor(Qt::color0).rgba()); + m.setColor(1, QColor(Qt::color1).rgba()); + m.fill(0xff); + + QRgb background = PIX(0,0); + if (background != PIX(w-1,0) && + background != PIX(0,h-1) && + background != PIX(w-1,h-1)) { + background = PIX(w-1,0); + if (background != PIX(w-1,h-1) && + background != PIX(0,h-1) && + PIX(0,h-1) == PIX(w-1,h-1)) { + background = PIX(w-1,h-1); + } + } + + int x,y; + bool done = false; + uchar *ypp, *ypc, *ypn; + while(!done) { + done = true; + ypn = m.scanLine(0); + ypc = 0; + for (y = 0; y < h; y++) { + ypp = ypc; + ypc = ypn; + ypn = (y == h-1) ? 0 : m.scanLine(y+1); + QRgb *p = (QRgb *)scanLine(y); + for (x = 0; x < w; x++) { + // slowness here - it's possible to do six of these tests + // together in one go. oh well. + if ((x == 0 || y == 0 || x == w-1 || y == h-1 || + !(*(ypc + ((x-1) >> 3)) & (1 << ((x-1) & 7))) || + !(*(ypc + ((x+1) >> 3)) & (1 << ((x+1) & 7))) || + !(*(ypp + (x >> 3)) & (1 << (x & 7))) || + !(*(ypn + (x >> 3)) & (1 << (x & 7)))) && + ( (*(ypc + (x >> 3)) & (1 << (x & 7)))) && + ((*p & 0x00ffffff) == background)) { + done = false; + *(ypc + (x >> 3)) &= ~(1 << (x & 7)); + } + p++; + } + } + } + + if (!clipTight) { + ypn = m.scanLine(0); + ypc = 0; + for (y = 0; y < h; y++) { + ypp = ypc; + ypc = ypn; + ypn = (y == h-1) ? 0 : m.scanLine(y+1); + QRgb *p = (QRgb *)scanLine(y); + for (x = 0; x < w; x++) { + if ((*p & 0x00ffffff) != background) { + if (x > 0) + *(ypc + ((x-1) >> 3)) |= (1 << ((x-1) & 7)); + if (x < w-1) + *(ypc + ((x+1) >> 3)) |= (1 << ((x+1) & 7)); + if (y > 0) + *(ypp + (x >> 3)) |= (1 << (x & 7)); + if (y < h-1) + *(ypn + (x >> 3)) |= (1 << (x & 7)); + } + p++; + } + } + } + +#undef PIX + + return m; +} +#endif //QT_NO_IMAGE_HEURISTIC_MASK + +/*! + Creates and returns a mask for this image based on the given \a + color value. If the \a mode is MaskInColor (the default value), + all pixels matching \a color will be opaque pixels in the mask. If + \a mode is MaskOutColor, all pixels matching the given color will + be transparent. + + \sa createAlphaMask(), createHeuristicMask() +*/ + +QImage QImage::createMaskFromColor(QRgb color, Qt::MaskMode mode) const +{ + if (!d) + return QImage(); + QImage maskImage(size(), QImage::Format_MonoLSB); + QIMAGE_SANITYCHECK_MEMORY(maskImage); + maskImage.fill(0); + uchar *s = maskImage.bits(); + + if (depth() == 32) { + for (int h = 0; h < d->height; h++) { + const uint *sl = (uint *) scanLine(h); + for (int w = 0; w < d->width; w++) { + if (sl[w] == color) + *(s + (w >> 3)) |= (1 << (w & 7)); + } + s += maskImage.bytesPerLine(); + } + } else { + for (int h = 0; h < d->height; h++) { + for (int w = 0; w < d->width; w++) { + if ((uint) pixel(w, h) == color) + *(s + (w >> 3)) |= (1 << (w & 7)); + } + s += maskImage.bytesPerLine(); + } + } + if (mode == Qt::MaskOutColor) + maskImage.invertPixels(); + return maskImage; +} + + +/* + This code is contributed by Philipp Lang, + GeneriCom Software Germany (www.generi.com) + under the terms of the QPL, Version 1.0 +*/ + +/*! + \fn QImage QImage::mirror(bool horizontal, bool vertical) const + + Use mirrored() instead. +*/ + +/*! + Returns a mirror of the image, mirrored in the horizontal and/or + the vertical direction depending on whether \a horizontal and \a + vertical are set to true or false. + + Note that the original image is not changed. + + \sa {QImage#Image Transformations}{Image Transformations} +*/ +QImage QImage::mirrored(bool horizontal, bool vertical) const +{ + if (!d) + return QImage(); + + if ((d->width <= 1 && d->height <= 1) || (!horizontal && !vertical)) + return *this; + + int w = d->width; + int h = d->height; + // Create result image, copy colormap + QImage result(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(result); + + // check if we ran out of of memory.. + if (!result.d) + return QImage(); + + result.d->colortable = d->colortable; + result.d->has_alpha_clut = d->has_alpha_clut; + + if (depth() == 1) + w = (w+7)/8; + int dxi = horizontal ? -1 : 1; + int dxs = horizontal ? w-1 : 0; + int dyi = vertical ? -1 : 1; + int dy = vertical ? h-1: 0; + + // 1 bit, 8 bit + if (d->depth == 1 || d->depth == 8) { + for (int sy = 0; sy < h; sy++, dy += dyi) { + quint8* ssl = (quint8*)(d->data + sy*d->bytes_per_line); + quint8* dsl = (quint8*)(result.d->data + dy*result.d->bytes_per_line); + int dx = dxs; + for (int sx = 0; sx < w; sx++, dx += dxi) + dsl[dx] = ssl[sx]; + } + } + // 16 bit + else if (d->depth == 16) { + for (int sy = 0; sy < h; sy++, dy += dyi) { + quint16* ssl = (quint16*)(d->data + sy*d->bytes_per_line); + quint16* dsl = (quint16*)(result.d->data + dy*result.d->bytes_per_line); + int dx = dxs; + for (int sx = 0; sx < w; sx++, dx += dxi) + dsl[dx] = ssl[sx]; + } + } + // 24 bit + else if (d->depth == 24) { + for (int sy = 0; sy < h; sy++, dy += dyi) { + quint24* ssl = (quint24*)(d->data + sy*d->bytes_per_line); + quint24* dsl = (quint24*)(result.d->data + dy*result.d->bytes_per_line); + int dx = dxs; + for (int sx = 0; sx < w; sx++, dx += dxi) + dsl[dx] = ssl[sx]; + } + } + // 32 bit + else if (d->depth == 32) { + for (int sy = 0; sy < h; sy++, dy += dyi) { + quint32* ssl = (quint32*)(d->data + sy*d->bytes_per_line); + quint32* dsl = (quint32*)(result.d->data + dy*result.d->bytes_per_line); + int dx = dxs; + for (int sx = 0; sx < w; sx++, dx += dxi) + dsl[dx] = ssl[sx]; + } + } + + // special handling of 1 bit images for horizontal mirroring + if (horizontal && d->depth == 1) { + int shift = width() % 8; + for (int y = h-1; y >= 0; y--) { + quint8* a0 = (quint8*)(result.d->data + y*d->bytes_per_line); + // Swap bytes + quint8* a = a0+dxs; + while (a >= a0) { + *a = bitflip[*a]; + a--; + } + // Shift bits if unaligned + if (shift != 0) { + a = a0+dxs; + quint8 c = 0; + if (format() == Format_MonoLSB) { + while (a >= a0) { + quint8 nc = *a << shift; + *a = (*a >> (8-shift)) | c; + --a; + c = nc; + } + } else { + while (a >= a0) { + quint8 nc = *a >> shift; + *a = (*a << (8-shift)) | c; + --a; + c = nc; + } + } + } + } + } + + return result; +} + +/*! + \fn QImage QImage::swapRGB() const + + Use rgbSwapped() instead. + + \omit + Returns a QImage in which the values of the red and blue + components of all pixels have been swapped, effectively converting + an RGB image to an BGR image. The original QImage is not changed. + \endomit +*/ + +/*! + Returns a QImage in which the values of the red and blue + components of all pixels have been swapped, effectively converting + an RGB image to an BGR image. + + The original QImage is not changed. + + \sa {QImage#Image Transformations}{Image Transformations} +*/ +QImage QImage::rgbSwapped() const +{ + if (isNull()) + return *this; + QImage res; + switch (d->format) { + case Format_Invalid: + case NImageFormats: + Q_ASSERT(false); + break; + case Format_Mono: + case Format_MonoLSB: + case Format_Indexed8: + res = copy(); + for (int i = 0; i < res.d->colortable.size(); i++) { + QRgb c = res.d->colortable.at(i); + res.d->colortable[i] = QRgb(((c << 16) & 0xff0000) | ((c >> 16) & 0xff) | (c & 0xff00ff00)); + } + break; + case Format_RGB32: + case Format_ARGB32: + case Format_ARGB32_Premultiplied: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + uint *q = (uint*)res.scanLine(i); + uint *p = (uint*)constScanLine(i); + uint *end = p + d->width; + while (p < end) { + *q = ((*p << 16) & 0xff0000) | ((*p >> 16) & 0xff) | (*p & 0xff00ff00); + p++; + q++; + } + } + break; + case Format_RGB16: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + ushort *q = (ushort*)res.scanLine(i); + const ushort *p = (const ushort*)constScanLine(i); + const ushort *end = p + d->width; + while (p < end) { + *q = ((*p << 11) & 0xf800) | ((*p >> 11) & 0x1f) | (*p & 0x07e0); + p++; + q++; + } + } + break; + case Format_ARGB8565_Premultiplied: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + const quint8 *p = constScanLine(i); + quint8 *q = res.scanLine(i); + const quint8 *end = p + d->width * sizeof(qargb8565); + while (p < end) { + q[0] = p[0]; + q[1] = (p[1] & 0xe0) | (p[2] >> 3); + q[2] = (p[2] & 0x07) | (p[1] << 3); + p += sizeof(qargb8565); + q += sizeof(qargb8565); + } + } + break; + case Format_RGB666: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + qrgb666 *q = reinterpret_cast(res.scanLine(i)); + const qrgb666 *p = reinterpret_cast(constScanLine(i)); + const qrgb666 *end = p + d->width; + while (p < end) { + const QRgb rgb = quint32(*p++); + *q++ = qRgb(qBlue(rgb), qGreen(rgb), qRed(rgb)); + } + } + break; + case Format_ARGB6666_Premultiplied: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + const quint8 *p = constScanLine(i); + const quint8 *end = p + d->width * sizeof(qargb6666); + quint8 *q = res.scanLine(i); + while (p < end) { + q[0] = (p[1] >> 4) | ((p[2] & 0x3) << 4) | (p[0] & 0xc0); + q[1] = (p[1] & 0xf) | (p[0] << 4); + q[2] = (p[2] & 0xfc) | ((p[0] >> 4) & 0x3); + p += sizeof(qargb6666); + q += sizeof(qargb6666); + } + } + break; + case Format_RGB555: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + quint16 *q = (quint16*)res.scanLine(i); + const quint16 *p = (const quint16*)constScanLine(i); + const quint16 *end = p + d->width; + while (p < end) { + *q = ((*p << 10) & 0x7c00) | ((*p >> 10) & 0x1f) | (*p & 0x3e0); + p++; + q++; + } + } + break; + case Format_ARGB8555_Premultiplied: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + const quint8 *p = constScanLine(i); + quint8 *q = res.scanLine(i); + const quint8 *end = p + d->width * sizeof(qargb8555); + while (p < end) { + q[0] = p[0]; + q[1] = (p[1] & 0xe0) | (p[2] >> 2); + q[2] = (p[2] & 0x03) | ((p[1] << 2) & 0x7f); + p += sizeof(qargb8555); + q += sizeof(qargb8555); + } + } + break; + case Format_RGB888: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + quint8 *q = res.scanLine(i); + const quint8 *p = constScanLine(i); + const quint8 *end = p + d->width * sizeof(qrgb888); + while (p < end) { + q[0] = p[2]; + q[1] = p[1]; + q[2] = p[0]; + q += sizeof(qrgb888); + p += sizeof(qrgb888); + } + } + break; + case Format_RGB444: + case Format_ARGB4444_Premultiplied: + res = QImage(d->width, d->height, d->format); + QIMAGE_SANITYCHECK_MEMORY(res); + for (int i = 0; i < d->height; i++) { + quint16 *q = reinterpret_cast(res.scanLine(i)); + const quint16 *p = reinterpret_cast(constScanLine(i)); + const quint16 *end = p + d->width; + while (p < end) { + *q = (*p & 0xf0f0) | ((*p & 0x0f) << 8) | ((*p & 0xf00) >> 8); + p++; + q++; + } + } + break; + } + return res; +} + +/*! + Loads an image from the file with the given \a fileName. Returns true if + the image was successfully loaded; otherwise returns false. + + The loader attempts to read the image using the specified \a format, e.g., + PNG or JPG. If \a format is not specified (which is the default), the + loader probes the file for a header to guess the file format. + + The file name can either refer to an actual file on disk or to one + of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed images and other resource files in the application's + executable. + + \sa {QImage#Reading and Writing Image Files}{Reading and Writing Image Files} +*/ + +bool QImage::load(const QString &fileName, const char* format) +{ + if (fileName.isEmpty()) + return false; + + QImage image = QImageReader(fileName, format).read(); + if (!image.isNull()) { + operator=(image); + return true; + } + return false; +} + +/*! + \overload + + This function reads a QImage from the given \a device. This can, + for example, be used to load an image directly into a QByteArray. +*/ + +bool QImage::load(QIODevice* device, const char* format) +{ + QImage image = QImageReader(device, format).read(); + if(!image.isNull()) { + operator=(image); + return true; + } + return false; +} + +/*! + \fn bool QImage::loadFromData(const uchar *data, int len, const char *format) + + Loads an image from the first \a len bytes of the given binary \a + data. Returns true if the image was successfully loaded; otherwise + returns false. + + The loader attempts to read the image using the specified \a format, e.g., + PNG or JPG. If \a format is not specified (which is the default), the + loader probes the file for a header to guess the file format. + + \sa {QImage#Reading and Writing Image Files}{Reading and Writing Image Files} +*/ + +bool QImage::loadFromData(const uchar *data, int len, const char *format) +{ + QImage image = fromData(data, len, format); + if (!image.isNull()) { + operator=(image); + return true; + } + return false; +} + +/*! + \fn bool QImage::loadFromData(const QByteArray &data, const char *format) + + \overload + + Loads an image from the given QByteArray \a data. +*/ + +/*! + \fn QImage QImage::fromData(const uchar *data, int size, const char *format) + + Constructs a QImage from the first \a size bytes of the given + binary \a data. The loader attempts to read the image using the + specified \a format. If \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + binary \a data. The loader attempts to read the image, either using the + optional image \a format specified or by determining the image format from + the data. + + If \a format is not specified (which is the default), the loader probes the + file for a header to determine the file format. If \a format is specified, + it must be one of the values returned by QImageReader::supportedImageFormats(). + + If the loading of the image fails, the image returned will be a null image. + + \sa load(), save(), {QImage#Reading and Writing Image Files}{Reading and Writing Image Files} + */ + +QImage QImage::fromData(const uchar *data, int size, const char *format) +{ + QByteArray a = QByteArray::fromRawData(reinterpret_cast(data), size); + QBuffer b; + b.setData(a); + b.open(QIODevice::ReadOnly); + return QImageReader(&b, format).read(); +} + +/*! + \fn QImage QImage::fromData(const QByteArray &data, const char *format) + + \overload + + Loads an image from the given QByteArray \a data. +*/ + +/*! + Saves the image to the file with the given \a fileName, using the + given image file \a format and \a quality factor. If \a format is + 0, QImage will attempt to guess the format by looking at \a fileName's + suffix. + + The \a quality factor must be in the range 0 to 100 or -1. Specify + 0 to obtain small compressed files, 100 for large uncompressed + files, and -1 (the default) to use the default settings. + + Returns true if the image was successfully saved; otherwise + returns false. + + \sa {QImage#Reading and Writing Image Files}{Reading and Writing + Image Files} +*/ +bool QImage::save(const QString &fileName, const char *format, int quality) const +{ + if (isNull()) + return false; + QImageWriter writer(fileName, format); + return d->doImageIO(this, &writer, quality); +} + +/*! + \overload + + This function writes a QImage to the given \a device. + + This can, for example, be used to save an image directly into a + QByteArray: + + \snippet doc/src/snippets/image/image.cpp 0 +*/ + +bool QImage::save(QIODevice* device, const char* format, int quality) const +{ + if (isNull()) + return false; // nothing to save + QImageWriter writer(device, format); + return d->doImageIO(this, &writer, quality); +} + +/* \internal +*/ + +bool QImageData::doImageIO(const QImage *image, QImageWriter *writer, int quality) const +{ + if (quality > 100 || quality < -1) + qWarning("QPixmap::save: Quality out of range [-1, 100]"); + if (quality >= 0) + writer->setQuality(qMin(quality,100)); + return writer->write(*image); +} + +/***************************************************************************** + QImage stream functions + *****************************************************************************/ +#if !defined(QT_NO_DATASTREAM) +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QImage &image) + \relates QImage + + Writes the given \a image to the given \a stream as a PNG image, + or as a BMP image if the stream's version is 1. Note that writing + the stream to a file will not produce a valid image file. + + \sa QImage::save(), {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QImage &image) +{ + if (s.version() >= 5) { + if (image.isNull()) { + s << (qint32) 0; // null image marker + return s; + } else { + s << (qint32) 1; + // continue ... + } + } + QImageWriter writer(s.device(), s.version() == 1 ? "bmp" : "png"); + writer.write(image); + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QImage &image) + \relates QImage + + Reads an image from the given \a stream and stores it in the given + \a image. + + \sa QImage::load(), {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QImage &image) +{ + if (s.version() >= 5) { + qint32 nullMarker; + s >> nullMarker; + if (!nullMarker) { + image = QImage(); // null image + return s; + } + } + image = QImageReader(s.device(), 0).read(); + return s; +} +#endif // QT_NO_DATASTREAM + + +#ifdef QT3_SUPPORT +/*! + \fn QImage QImage::convertDepthWithPalette(int depth, QRgb* palette, int palette_count, Qt::ImageConversionFlags flags) const + + Returns an image with the given \a depth, using the \a + palette_count colors pointed to by \a palette. If \a depth is 1 or + 8, the returned image will have its color table ordered in the + same way as \a palette. + + If the image needs to be modified to fit in a lower-resolution + result (e.g. converting from 32-bit to 8-bit), use the \a flags to + specify how you'd prefer this to happen. + + Note: currently no closest-color search is made. If colors are + found that are not in the palette, the palette may not be used at + all. This result should not be considered valid because it may + change in future implementations. + + Currently inefficient for non-32-bit images. + + Use the convertToFormat() function in combination with the + setColorTable() function instead. +*/ +QImage QImage::convertDepthWithPalette(int d, QRgb* palette, int palette_count, Qt::ImageConversionFlags flags) const +{ + Format f = formatFor(d, QImage::LittleEndian); + QVector colortable; + for (int i = 0; i < palette_count; ++i) + colortable.append(palette[i]); + return convertToFormat(f, colortable, flags); +} + +/*! + \relates QImage + + Copies a block of pixels from \a src to \a dst. The pixels + copied from source (src) are converted according to + \a flags if it is incompatible with the destination + (\a dst). + + \a sx, \a sy is the top-left pixel in \a src, \a dx, \a dy is the + top-left position in \a dst and \a sw, \a sh is the size of the + copied block. The copying is clipped if areas outside \a src or \a + dst are specified. If \a sw is -1, it is adjusted to + src->width(). Similarly, if \a sh is -1, it is adjusted to + src->height(). + + Currently inefficient for non 32-bit images. + + Use copy() or QPainter::drawImage() instead. +*/ +void bitBlt(QImage *dst, int dx, int dy, const QImage *src, int sx, int sy, int sw, int sh, + Qt::ImageConversionFlags flags) +{ + if (dst->isNull() || src->isNull()) + return; + QPainter p(dst); + p.drawImage(QPoint(dx, dy), *src, QRect(sx, sy, sw, sh), flags); +} +#endif + +/*! + \fn bool QImage::operator==(const QImage & image) const + + Returns true if this image and the given \a image have the same + contents; otherwise returns false. + + The comparison can be slow, unless there is some obvious + difference (e.g. different size or format), in which case the + function will return quickly. + + \sa operator=() +*/ + +bool QImage::operator==(const QImage & i) const +{ + // same object, or shared? + if (i.d == d) + return true; + if (!i.d || !d) + return false; + + // obviously different stuff? + if (i.d->height != d->height || i.d->width != d->width || i.d->format != d->format) + return false; + + if (d->format != Format_RGB32) { + if (d->format >= Format_ARGB32) { // all bits defined + const int n = d->width * d->depth / 8; + if (n == d->bytes_per_line && n == i.d->bytes_per_line) { + if (memcmp(bits(), i.bits(), d->nbytes)) + return false; + } else { + for (int y = 0; y < d->height; ++y) { + if (memcmp(scanLine(y), i.scanLine(y), n)) + return false; + } + } + } else { + const int w = width(); + const int h = height(); + const QVector &colortable = d->colortable; + const QVector &icolortable = i.d->colortable; + for (int y=0; yheight; l++) { + int w = d->width; + const uint *p1 = reinterpret_cast(scanLine(l)); + const uint *p2 = reinterpret_cast(i.scanLine(l)); + while (w--) { + if ((*p1++ & 0x00ffffff) != (*p2++ & 0x00ffffff)) + return false; + } + } + } + return true; +} + + +/*! + \fn bool QImage::operator!=(const QImage & image) const + + Returns true if this image and the given \a image have different + contents; otherwise returns false. + + The comparison can be slow, unless there is some obvious + difference, such as different widths, in which case the function + will return quickly. + + \sa operator=() +*/ + +bool QImage::operator!=(const QImage & i) const +{ + return !(*this == i); +} + + + + +/*! + Returns the number of pixels that fit horizontally in a physical + meter. Together with dotsPerMeterY(), this number defines the + intended scale and aspect ratio of the image. + + \sa setDotsPerMeterX(), {QImage#Image Information}{Image + Information} +*/ +int QImage::dotsPerMeterX() const +{ + return d ? qRound(d->dpmx) : 0; +} + +/*! + Returns the number of pixels that fit vertically in a physical + meter. Together with dotsPerMeterX(), this number defines the + intended scale and aspect ratio of the image. + + \sa setDotsPerMeterY(), {QImage#Image Information}{Image + Information} +*/ +int QImage::dotsPerMeterY() const +{ + return d ? qRound(d->dpmy) : 0; +} + +/*! + Sets the number of pixels that fit horizontally in a physical + meter, to \a x. + + Together with dotsPerMeterY(), this number defines the intended + scale and aspect ratio of the image, and determines the scale + at which QPainter will draw graphics on the image. It does not + change the scale or aspect ratio of the image when it is rendered + on other paint devices. + + \sa dotsPerMeterX(), {QImage#Image Information}{Image Information} +*/ +void QImage::setDotsPerMeterX(int x) +{ + if (!d || !x) + return; + detach(); + + if (d) + d->dpmx = x; +} + +/*! + Sets the number of pixels that fit vertically in a physical meter, + to \a y. + + Together with dotsPerMeterX(), this number defines the intended + scale and aspect ratio of the image, and determines the scale + at which QPainter will draw graphics on the image. It does not + change the scale or aspect ratio of the image when it is rendered + on other paint devices. + + \sa dotsPerMeterY(), {QImage#Image Information}{Image Information} +*/ +void QImage::setDotsPerMeterY(int y) +{ + if (!d || !y) + return; + detach(); + + if (d) + d->dpmy = y; +} + +/*! + \fn QPoint QImage::offset() const + + Returns the number of pixels by which the image is intended to be + offset by when positioning relative to other images. + + \sa setOffset(), {QImage#Image Information}{Image Information} +*/ +QPoint QImage::offset() const +{ + return d ? d->offset : QPoint(); +} + + +/*! + \fn void QImage::setOffset(const QPoint& offset) + + Sets the number of pixels by which the image is intended to be + offset by when positioning relative to other images, to \a offset. + + \sa offset(), {QImage#Image Information}{Image Information} +*/ +void QImage::setOffset(const QPoint& p) +{ + if (!d) + return; + detach(); + + if (d) + d->offset = p; +} +#ifndef QT_NO_IMAGE_TEXT + +/*! + Returns the text keys for this image. + + You can use these keys with text() to list the image text for a + certain key. + + \sa text() +*/ +QStringList QImage::textKeys() const +{ + return d ? QStringList(d->text.keys()) : QStringList(); +} + +/*! + Returns the image text associated with the given \a key. If the + specified \a key is an empty string, the whole image text is + returned, with each key-text pair separated by a newline. + + \sa setText(), textKeys() +*/ +QString QImage::text(const QString &key) const +{ + if (!d) + return QString(); + + if (!key.isEmpty()) + return d->text.value(key); + + QString tmp; + foreach (const QString &key, d->text.keys()) { + if (!tmp.isEmpty()) + tmp += QLatin1String("\n\n"); + tmp += key + QLatin1String(": ") + d->text.value(key).simplified(); + } + return tmp; +} + +/*! + \fn void QImage::setText(const QString &key, const QString &text) + + Sets the image text to the given \a text and associate it with the + given \a key. + + If you just want to store a single text block (i.e., a "comment" + or just a description), you can either pass an empty key, or use a + generic key like "Description". + + The image text is embedded into the image data when you + call save() or QImageWriter::write(). + + Not all image formats support embedded text. You can find out + if a specific image or format supports embedding text + by using QImageWriter::supportsOption(). We give an example: + + \snippet doc/src/snippets/image/supportedformat.cpp 0 + + You can use QImageWriter::supportedImageFormats() to find out + which image formats are available to you. + + \sa text(), textKeys() +*/ +void QImage::setText(const QString &key, const QString &value) +{ + if (!d) + return; + detach(); + + if (d) + d->text.insert(key, value); +} + +/*! + \fn QString QImage::text(const char* key, const char* language) const + \obsolete + + Returns the text recorded for the given \a key in the given \a + language, or in a default language if \a language is 0. + + Use text() instead. + + The language the text is recorded in is no longer relevant since + the text is always set using QString and UTF-8 representation. +*/ +QString QImage::text(const char* key, const char* lang) const +{ + if (!d) + return QString(); + QString k = QString::fromAscii(key); + if (lang && *lang) + k += QLatin1Char('/') + QString::fromAscii(lang); + return d->text.value(k); +} + +/*! + \fn QString QImage::text(const QImageTextKeyLang& keywordAndLanguage) const + \overload + \obsolete + + Returns the text recorded for the given \a keywordAndLanguage. + + Use text() instead. + + The language the text is recorded in is no longer relevant since + the text is always set using QString and UTF-8 representation. +*/ +QString QImage::text(const QImageTextKeyLang& kl) const +{ + if (!d) + return QString(); + QString k = QString::fromAscii(kl.key); + if (!kl.lang.isEmpty()) + k += QLatin1Char('/') + QString::fromAscii(kl.lang); + return d->text.value(k); +} + +/*! + \obsolete + + Returns the language identifiers for which some texts are + recorded. Note that if you want to iterate over the list, you + should iterate over a copy. + + The language the text is recorded in is no longer relevant since + the text is always set using QString and UTF-8 representation. +*/ +QStringList QImage::textLanguages() const +{ + if (!d) + return QStringList(); + QStringList keys = textKeys(); + QStringList languages; + for (int i = 0; i < keys.size(); ++i) { + int index = keys.at(i).indexOf(QLatin1Char('/')); + if (index > 0) + languages += keys.at(i).mid(index+1); + } + + return languages; +} + +/*! + \obsolete + + Returns a list of QImageTextKeyLang objects that enumerate all the + texts key/language pairs set for this image. + + Use textKeys() instead. + + The language the text is recorded in is no longer relevant since + the text is always set using QString and UTF-8 representation. +*/ +QList QImage::textList() const +{ + QList imageTextKeys; + if (!d) + return imageTextKeys; + QStringList keys = textKeys(); + for (int i = 0; i < keys.size(); ++i) { + int index = keys.at(i).indexOf(QLatin1Char('/')); + if (index > 0) { + QImageTextKeyLang tkl; + tkl.key = keys.at(i).left(index).toAscii(); + tkl.lang = keys.at(i).mid(index+1).toAscii(); + imageTextKeys += tkl; + } + } + + return imageTextKeys; +} + +/*! + \fn void QImage::setText(const char* key, const char* language, const QString& text) + \obsolete + + Sets the image text to the given \a text and associate it with the + given \a key. The text is recorded in the specified \a language, + or in a default language if \a language is 0. + + Use setText() instead. + + The language the text is recorded in is no longer relevant since + the text is always set using QString and UTF-8 representation. + + \omit + Records string \a for the keyword \a key. The \a key should be + a portable keyword recognizable by other software - some suggested + values can be found in + \l{http://www.libpng.org/pub/png/spec/1.2/png-1.2-pdg.html#C.Anc-text} + {the PNG specification}. \a s can be any text. \a lang should + specify the language code (see + \l{http://www.rfc-editor.org/rfc/rfc1766.txt}{RFC 1766}) or 0. + \endomit +*/ +void QImage::setText(const char* key, const char* lang, const QString& s) +{ + if (!d) + return; + detach(); + + // In case detach() ran out of memory + if (!d) + return; + + QString k = QString::fromAscii(key); + if (lang && *lang) + k += QLatin1Char('/') + QString::fromAscii(lang); + d->text.insert(k, s); +} + +#endif // QT_NO_IMAGE_TEXT + +/* + Sets the image bits to the \a pixmap contents and returns a + reference to the image. + + If the image shares data with other images, it will first + dereference the shared data. + + Makes a call to QPixmap::convertToImage(). +*/ + +/*! \fn QImage::Endian QImage::systemBitOrder() + + Determines the bit order of the display hardware. Returns + QImage::LittleEndian (LSB first) or QImage::BigEndian (MSB first). + + This function is no longer relevant for QImage. Use QSysInfo + instead. +*/ + + +/*! + \internal + + Used by QPainter to retrieve a paint engine for the image. +*/ + +QPaintEngine *QImage::paintEngine() const +{ + if (!d) + return 0; + + if (!d->paintEngine) { + d->paintEngine = new QRasterPaintEngine(const_cast(this)); + } + + return d->paintEngine; +} + + +/*! + \internal + + Returns the size for the specified \a metric on the device. +*/ +int QImage::metric(PaintDeviceMetric metric) const +{ + if (!d) + return 0; + + switch (metric) { + case PdmWidth: + return d->width; + break; + + case PdmHeight: + return d->height; + break; + + case PdmWidthMM: + return qRound(d->width * 1000 / d->dpmx); + break; + + case PdmHeightMM: + return qRound(d->height * 1000 / d->dpmy); + break; + + case PdmNumColors: + return d->colortable.size(); + break; + + case PdmDepth: + return d->depth; + break; + + case PdmDpiX: + return qRound(d->dpmx * 0.0254); + break; + + case PdmDpiY: + return qRound(d->dpmy * 0.0254); + break; + + case PdmPhysicalDpiX: + return qRound(d->dpmx * 0.0254); + break; + + case PdmPhysicalDpiY: + return qRound(d->dpmy * 0.0254); + break; + + default: + qWarning("QImage::metric(): Unhandled metric type %d", metric); + break; + } + return 0; +} + + + +/***************************************************************************** + QPixmap (and QImage) helper functions + *****************************************************************************/ +/* + This internal function contains the common (i.e. platform independent) code + to do a transformation of pixel data. It is used by QPixmap::transform() and by + QImage::transform(). + + \a trueMat is the true transformation matrix (see QPixmap::trueMatrix()) and + \a xoffset is an offset to the matrix. + + \a msbfirst specifies for 1bpp images, if the MSB or LSB comes first and \a + depth specifies the colordepth of the data. + + \a dptr is a pointer to the destination data, \a dbpl specifies the bits per + line for the destination data, \a p_inc is the offset that we advance for + every scanline and \a dHeight is the height of the destination image. + + \a sprt is the pointer to the source data, \a sbpl specifies the bits per + line of the source data, \a sWidth and \a sHeight are the width and height of + the source data. +*/ + +#undef IWX_MSB +#define IWX_MSB(b) if (trigx < maxws && trigy < maxhs) { \ + if (*(sptr+sbpl*(trigy>>12)+(trigx>>15)) & \ + (1 << (7-((trigx>>12)&7)))) \ + *dptr |= b; \ + } \ + trigx += m11; \ + trigy += m12; + // END OF MACRO +#undef IWX_LSB +#define IWX_LSB(b) if (trigx < maxws && trigy < maxhs) { \ + if (*(sptr+sbpl*(trigy>>12)+(trigx>>15)) & \ + (1 << ((trigx>>12)&7))) \ + *dptr |= b; \ + } \ + trigx += m11; \ + trigy += m12; + // END OF MACRO +#undef IWX_PIX +#define IWX_PIX(b) if (trigx < maxws && trigy < maxhs) { \ + if ((*(sptr+sbpl*(trigy>>12)+(trigx>>15)) & \ + (1 << (7-((trigx>>12)&7)))) == 0) \ + *dptr &= ~b; \ + } \ + trigx += m11; \ + trigy += m12; + // END OF MACRO +bool qt_xForm_helper(const QTransform &trueMat, int xoffset, int type, int depth, + uchar *dptr, int dbpl, int p_inc, int dHeight, + const uchar *sptr, int sbpl, int sWidth, int sHeight) +{ + int m11 = int(trueMat.m11()*4096.0); + int m12 = int(trueMat.m12()*4096.0); + int m21 = int(trueMat.m21()*4096.0); + int m22 = int(trueMat.m22()*4096.0); + int dx = qRound(trueMat.dx()*4096.0); + int dy = qRound(trueMat.dy()*4096.0); + + int m21ydx = dx + (xoffset<<16) + (m11 + m21) / 2; + int m22ydy = dy + (m12 + m22) / 2; + uint trigx; + uint trigy; + uint maxws = sWidth<<12; + uint maxhs = sHeight<<12; + + for (int y=0; y>12)+(trigx>>12)); + trigx += m11; + trigy += m12; + dptr++; + } + break; + + case 16: // 16 bpp transform + while (dptr < maxp) { + if (trigx < maxws && trigy < maxhs) + *((ushort*)dptr) = *((ushort *)(sptr+sbpl*(trigy>>12) + + ((trigx>>12)<<1))); + trigx += m11; + trigy += m12; + dptr++; + dptr++; + } + break; + + case 24: // 24 bpp transform + while (dptr < maxp) { + if (trigx < maxws && trigy < maxhs) { + const uchar *p2 = sptr+sbpl*(trigy>>12) + ((trigx>>12)*3); + dptr[0] = p2[0]; + dptr[1] = p2[1]; + dptr[2] = p2[2]; + } + trigx += m11; + trigy += m12; + dptr += 3; + } + break; + + case 32: // 32 bpp transform + while (dptr < maxp) { + if (trigx < maxws && trigy < maxhs) + *((uint*)dptr) = *((uint *)(sptr+sbpl*(trigy>>12) + + ((trigx>>12)<<2))); + trigx += m11; + trigy += m12; + dptr += 4; + } + break; + + default: { + return false; + } + } + } else { + switch (type) { + case QT_XFORM_TYPE_MSBFIRST: + while (dptr < maxp) { + IWX_MSB(128); + IWX_MSB(64); + IWX_MSB(32); + IWX_MSB(16); + IWX_MSB(8); + IWX_MSB(4); + IWX_MSB(2); + IWX_MSB(1); + dptr++; + } + break; + case QT_XFORM_TYPE_LSBFIRST: + while (dptr < maxp) { + IWX_LSB(1); + IWX_LSB(2); + IWX_LSB(4); + IWX_LSB(8); + IWX_LSB(16); + IWX_LSB(32); + IWX_LSB(64); + IWX_LSB(128); + dptr++; + } + break; +# if defined(Q_WS_WIN) + case QT_XFORM_TYPE_WINDOWSPIXMAP: + while (dptr < maxp) { + IWX_PIX(128); + IWX_PIX(64); + IWX_PIX(32); + IWX_PIX(16); + IWX_PIX(8); + IWX_PIX(4); + IWX_PIX(2); + IWX_PIX(1); + dptr++; + } + break; +# endif + } + } + m21ydx += m21; + m22ydy += m22; + dptr += p_inc; + } + return true; +} +#undef IWX_MSB +#undef IWX_LSB +#undef IWX_PIX + +/*! + \fn QImage QImage::xForm(const QMatrix &matrix) const + + Use transformed() instead. + + \oldcode + QImage image; + ... + image.xForm(matrix); + \newcode + QImage image; + ... + image.transformed(matrix); + \endcode +*/ + +/*! \obsolete + Returns a number that identifies the contents of this + QImage object. Distinct QImage objects can only have the same + serial number if they refer to the same contents (but they don't + have to). + + Use cacheKey() instead. + + \warning The serial number doesn't necessarily change when the + image is altered. This means that it may be dangerous to use + it as a cache key. + + \sa operator==() +*/ + +int QImage::serialNumber() const +{ + if (!d) + return 0; + else + return d->ser_no; +} + +/*! + Returns a number that identifies the contents of this QImage + object. Distinct QImage objects can only have the same key if they + refer to the same contents. + + The key will change when the image is altered. +*/ +qint64 QImage::cacheKey() const +{ + if (!d) + return 0; + else + return (((qint64) d->ser_no) << 32) | ((qint64) d->detach_no); +} + +/*! + \internal + + Returns true if the image is detached; otherwise returns false. + + \sa detach(), {Implicit Data Sharing} +*/ + +bool QImage::isDetached() const +{ + return d && d->ref == 1; +} + + +/*! + \obsolete + Sets the alpha channel of this image to the given \a alphaChannel. + + If \a alphaChannel is an 8 bit grayscale image, the intensity values are + written into this buffer directly. Otherwise, \a alphaChannel is converted + to 32 bit and the intensity of the RGB pixel values is used. + + Note that the image will be converted to the Format_ARGB32_Premultiplied + format if the function succeeds. + + Use one of the composition modes in QPainter::CompositionMode instead. + + \warning This function is expensive. + + \sa alphaChannel(), {QImage#Image Transformations}{Image + Transformations}, {QImage#Image Formats}{Image Formats} +*/ + +void QImage::setAlphaChannel(const QImage &alphaChannel) +{ + if (!d) + return; + + int w = d->width; + int h = d->height; + + if (w != alphaChannel.d->width || h != alphaChannel.d->height) { + qWarning("QImage::setAlphaChannel: " + "Alpha channel must have same dimensions as the target image"); + return; + } + + if (d->paintEngine && d->paintEngine->isActive()) { + qWarning("QImage::setAlphaChannel: " + "Unable to set alpha channel while image is being painted on"); + return; + } + + if (d->format == QImage::Format_ARGB32_Premultiplied) + detach(); + else + *this = convertToFormat(QImage::Format_ARGB32_Premultiplied); + + if (isNull()) + return; + + // Slight optimization since alphachannels are returned as 8-bit grays. + if (alphaChannel.d->depth == 8 && alphaChannel.isGrayscale()) { + const uchar *src_data = alphaChannel.d->data; + const uchar *dest_data = d->data; + for (int y=0; ybytes_per_line; + dest_data += d->bytes_per_line; + } + + } else { + const QImage sourceImage = alphaChannel.convertToFormat(QImage::Format_RGB32); + const uchar *src_data = sourceImage.d->data; + const uchar *dest_data = d->data; + for (int y=0; ybytes_per_line; + dest_data += d->bytes_per_line; + } + } +} + + +/*! + \obsolete + + Returns the alpha channel of the image as a new grayscale QImage in which + each pixel's red, green, and blue values are given the alpha value of the + original image. The color depth of the returned image is 8-bit. + + You can see an example of use of this function in QPixmap's + \l{QPixmap::}{alphaChannel()}, which works in the same way as + this function on QPixmaps. + + Most usecases for this function can be replaced with QPainter and + using composition modes. + + \warning This is an expensive function. + + \sa setAlphaChannel(), hasAlphaChannel(), + {QPixmap#Pixmap Information}{Pixmap}, + {QImage#Image Transformations}{Image Transformations} +*/ + +QImage QImage::alphaChannel() const +{ + if (!d) + return QImage(); + + int w = d->width; + int h = d->height; + + QImage image(w, h, Format_Indexed8); + image.setColorCount(256); + + // set up gray scale table. + for (int i=0; i<256; ++i) + image.setColor(i, qRgb(i, i, i)); + + if (!hasAlphaChannel()) { + image.fill(255); + return image; + } + + if (d->format == Format_Indexed8) { + const uchar *src_data = d->data; + uchar *dest_data = image.d->data; + for (int y=0; ycolortable.at(*src)); + ++dest; + ++src; + } + src_data += d->bytes_per_line; + dest_data += image.d->bytes_per_line; + } + } else { + QImage alpha32 = *this; + if (d->format != Format_ARGB32 && d->format != Format_ARGB32_Premultiplied) + alpha32 = convertToFormat(Format_ARGB32); + + const uchar *src_data = alpha32.d->data; + uchar *dest_data = image.d->data; + for (int y=0; ybytes_per_line; + dest_data += image.d->bytes_per_line; + } + } + + return image; +} + +/*! + Returns true if the image has a format that respects the alpha + channel, otherwise returns false. + + \sa {QImage#Image Information}{Image Information} +*/ +bool QImage::hasAlphaChannel() const +{ + return d && (d->format == Format_ARGB32_Premultiplied + || d->format == Format_ARGB32 + || d->format == Format_ARGB8565_Premultiplied + || d->format == Format_ARGB8555_Premultiplied + || d->format == Format_ARGB6666_Premultiplied + || d->format == Format_ARGB4444_Premultiplied + || (d->has_alpha_clut && (d->format == Format_Indexed8 + || d->format == Format_Mono + || d->format == Format_MonoLSB))); +} + + +/*! + \since 4.7 + Returns the number of bit planes in the image. + + The number of bit planes is the number of bits of color and + transparency information for each pixel. This is different from + (i.e. smaller than) the depth when the image format contains + unused bits. + + \sa depth(), format(), {QImage#Image Formats}{Image Formats} +*/ +int QImage::bitPlaneCount() const +{ + if (!d) + return 0; + int bpc = 0; + switch (d->format) { + case QImage::Format_Invalid: + break; + case QImage::Format_RGB32: + bpc = 24; + break; + case QImage::Format_RGB666: + bpc = 18; + break; + case QImage::Format_RGB555: + bpc = 15; + break; + case QImage::Format_ARGB8555_Premultiplied: + bpc = 23; + break; + case QImage::Format_RGB444: + bpc = 12; + break; + default: + bpc = qt_depthForFormat(d->format); + break; + } + return bpc; +} + + +#ifdef QT3_SUPPORT +#if defined(Q_WS_X11) +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE +#endif + +QImage::Endian QImage::systemBitOrder() +{ +#if defined(Q_WS_X11) + return BitmapBitOrder(X11->display) == MSBFirst ? BigEndian : LittleEndian; +#else + return BigEndian; +#endif +} +#endif + +/*! + \fn QImage QImage::copy(const QRect &rect, Qt::ImageConversionFlags flags) const + \compat + + Use copy() instead. +*/ + +/*! + \fn QImage QImage::copy(int x, int y, int w, int h, Qt::ImageConversionFlags flags) const + \compat + + Use copy() instead. +*/ + +/*! + \fn QImage QImage::scaleWidth(int w) const + \compat + + Use scaledToWidth() instead. +*/ + +/*! + \fn QImage QImage::scaleHeight(int h) const + \compat + + Use scaledToHeight() instead. +*/ + +static QImage smoothScaled(const QImage &source, int w, int h) { + QImage src = source; + if (src.format() == QImage::Format_ARGB32) + src = src.convertToFormat(QImage::Format_ARGB32_Premultiplied); + else if (src.depth() < 32) { + if (src.hasAlphaChannel()) + src = src.convertToFormat(QImage::Format_ARGB32_Premultiplied); + else + src = src.convertToFormat(QImage::Format_RGB32); + } + + return qSmoothScaleImage(src, w, h); +} + + +static QImage rotated90(const QImage &image) { + QImage out(image.height(), image.width(), image.format()); + if (image.colorCount() > 0) + out.setColorTable(image.colorTable()); + int w = image.width(); + int h = image.height(); + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + qt_memrotate270(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_RGB666: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_RGB888: + qt_memrotate270(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_RGB555: + case QImage::Format_RGB16: + case QImage::Format_ARGB4444_Premultiplied: + qt_memrotate270(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_Indexed8: + qt_memrotate270(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + default: + for (int y=0; y 0) + out.setColorTable(image.colorTable()); + int w = image.width(); + int h = image.height(); + switch (image.format()) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + qt_memrotate90(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_RGB666: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_RGB888: + qt_memrotate90(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_RGB555: + case QImage::Format_RGB16: + case QImage::Format_ARGB4444_Premultiplied: + qt_memrotate90(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + case QImage::Format_Indexed8: + qt_memrotate90(reinterpret_cast(image.bits()), + w, h, image.bytesPerLine(), + reinterpret_cast(out.bits()), + out.bytesPerLine()); + break; + default: + for (int y=0; yformat; + + if (complex_xform || mode == Qt::SmoothTransformation) { + if (d->format < QImage::Format_RGB32 || !hasAlphaChannel()) { + switch(d->format) { + case QImage::Format_RGB16: + target_format = Format_ARGB8565_Premultiplied; + break; + case QImage::Format_RGB555: + target_format = Format_ARGB8555_Premultiplied; + break; + case QImage::Format_RGB666: + target_format = Format_ARGB6666_Premultiplied; + break; + case QImage::Format_RGB444: + target_format = Format_ARGB4444_Premultiplied; + break; + default: + target_format = Format_ARGB32_Premultiplied; + break; + } + } + } + + QImage dImage(wd, hd, target_format); + QIMAGE_SANITYCHECK_MEMORY(dImage); + + if (target_format == QImage::Format_MonoLSB + || target_format == QImage::Format_Mono + || target_format == QImage::Format_Indexed8) { + dImage.d->colortable = d->colortable; + dImage.d->has_alpha_clut = d->has_alpha_clut | complex_xform; + } + + dImage.d->dpmx = dotsPerMeterX(); + dImage.d->dpmy = dotsPerMeterY(); + + switch (bpp) { + // initizialize the data + case 8: + if (dImage.d->colortable.size() < 256) { + // colors are left in the color table, so pick that one as transparent + dImage.d->colortable.append(0x0); + memset(dImage.bits(), dImage.d->colortable.size() - 1, dImage.byteCount()); + } else { + memset(dImage.bits(), 0, dImage.byteCount()); + } + break; + case 1: + case 16: + case 24: + case 32: + memset(dImage.bits(), 0x00, dImage.byteCount()); + break; + } + + if (target_format >= QImage::Format_RGB32) { + QPainter p(&dImage); + if (mode == Qt::SmoothTransformation) { + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + } + p.setTransform(mat); + p.drawImage(QPoint(0, 0), *this); + } else { + bool invertible; + mat = mat.inverted(&invertible); // invert matrix + if (!invertible) // error, return null image + return QImage(); + + // create target image (some of the code is from QImage::copy()) + int type = format() == Format_Mono ? QT_XFORM_TYPE_MSBFIRST : QT_XFORM_TYPE_LSBFIRST; + int dbpl = dImage.bytesPerLine(); + qt_xForm_helper(mat, 0, type, bpp, dImage.bits(), dbpl, 0, hd, sptr, sbpl, ws, hs); + } + return dImage; +} + +/*! + \fn QTransform QImage::trueMatrix(const QTransform &matrix, int width, int height) + + Returns the actual matrix used for transforming an image with the + given \a width, \a height and \a matrix. + + When transforming an image using the transformed() function, the + transformation matrix is internally adjusted to compensate for + unwanted translation, i.e. transformed() returns the smallest + image containing all transformed points of the original image. + This function returns the modified matrix, which maps points + correctly from the original image into the new image. + + Unlike the other overload, this function creates transformation + matrices that can be used to perform perspective + transformations on images. + + \sa transformed(), {QImage#Image Transformations}{Image + Transformations} +*/ + +QTransform QImage::trueMatrix(const QTransform &matrix, int w, int h) +{ + const QRectF rect(0, 0, w, h); + const QRect mapped = matrix.mapRect(rect).toAlignedRect(); + const QPoint delta = mapped.topLeft(); + return matrix * QTransform().translate(-delta.x(), -delta.y()); +} + +bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags) +{ + if (format == newFormat) + return true; + + // No in-place conversion if we have to detach + if (ref > 1) + return false; + + const InPlace_Image_Converter *const converterPtr = &inplace_converter_map[format][newFormat]; + InPlace_Image_Converter converter = *converterPtr; + if (converter) + return converter(this, flags); + else + return false; +} + +/*! + \typedef QImage::DataPtr + \internal +*/ + +/*! + \fn DataPtr & QImage::data_ptr() + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h new file mode 100644 index 0000000000..496fe93c54 --- /dev/null +++ b/src/gui/image/qimage.h @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGE_H +#define QIMAGE_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QIODevice; +class QStringList; +class QMatrix; +class QTransform; +class QVariant; +template class QList; +template class QVector; + +struct QImageData; +class QImageDataMisc; // internal +#ifndef QT_NO_IMAGE_TEXT +class Q_GUI_EXPORT QImageTextKeyLang { +public: + QImageTextKeyLang(const char* k, const char* l) : key(k), lang(l) { } + QImageTextKeyLang() { } + + QByteArray key; + QByteArray lang; + + bool operator< (const QImageTextKeyLang& other) const + { return key < other.key || (key==other.key && lang < other.lang); } + bool operator== (const QImageTextKeyLang& other) const + { return key==other.key && lang==other.lang; } + inline bool operator!= (const QImageTextKeyLang &other) const + { return !operator==(other); } +}; +#endif //QT_NO_IMAGE_TEXT + + +class Q_GUI_EXPORT QImage : public QPaintDevice +{ +public: + enum InvertMode { InvertRgb, InvertRgba }; + enum Format { + Format_Invalid, + Format_Mono, + Format_MonoLSB, + Format_Indexed8, + Format_RGB32, + Format_ARGB32, + Format_ARGB32_Premultiplied, + Format_RGB16, + Format_ARGB8565_Premultiplied, + Format_RGB666, + Format_ARGB6666_Premultiplied, + Format_RGB555, + Format_ARGB8555_Premultiplied, + Format_RGB888, + Format_RGB444, + Format_ARGB4444_Premultiplied, +#if 0 + // reserved for future use + Format_RGB15, + Format_Grayscale16, + Format_Grayscale8, + Format_Grayscale4, + Format_Grayscale4LSB, + Format_Grayscale2, + Format_Grayscale2LSB +#endif +#ifndef qdoc + NImageFormats +#endif + }; + + QImage(); + QImage(const QSize &size, Format format); + QImage(int width, int height, Format format); + QImage(uchar *data, int width, int height, Format format); + QImage(const uchar *data, int width, int height, Format format); + QImage(uchar *data, int width, int height, int bytesPerLine, Format format); + QImage(const uchar *data, int width, int height, int bytesPerLine, Format format); + +#ifndef QT_NO_IMAGEFORMAT_XPM + explicit QImage(const char * const xpm[]); +#endif + explicit QImage(const QString &fileName, const char *format = 0); +#ifndef QT_NO_CAST_FROM_ASCII + explicit QImage(const char *fileName, const char *format = 0); +#endif + + QImage(const QImage &); + ~QImage(); + + QImage &operator=(const QImage &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QImage &operator=(QImage &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QImage &other) { qSwap(d, other.d); } + + bool isNull() const; + + int devType() const; + + bool operator==(const QImage &) const; + bool operator!=(const QImage &) const; + operator QVariant() const; + void detach(); + bool isDetached() const; + + QImage copy(const QRect &rect = QRect()) const; + inline QImage copy(int x, int y, int w, int h) const + { return copy(QRect(x, y, w, h)); } + + Format format() const; + + QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) const Q_REQUIRED_RESULT; + QImage convertToFormat(Format f, const QVector &colorTable, Qt::ImageConversionFlags flags = Qt::AutoColor) const Q_REQUIRED_RESULT; + + int width() const; + int height() const; + QSize size() const; + QRect rect() const; + + int depth() const; +#ifdef QT_DEPRECATED + QT_DEPRECATED int numColors() const; +#endif + int colorCount() const; + int bitPlaneCount() const; + + QRgb color(int i) const; + void setColor(int i, QRgb c); +#ifdef QT_DEPRECATED + QT_DEPRECATED void setNumColors(int); +#endif + void setColorCount(int); + + bool allGray() const; + bool isGrayscale() const; + + uchar *bits(); + const uchar *bits() const; + const uchar *constBits() const; +#ifdef QT_DEPRECATED + QT_DEPRECATED int numBytes() const; +#endif + int byteCount() const; + + uchar *scanLine(int); + const uchar *scanLine(int) const; + const uchar *constScanLine(int) const; + int bytesPerLine() const; + + bool valid(int x, int y) const; + bool valid(const QPoint &pt) const; + + int pixelIndex(int x, int y) const; + int pixelIndex(const QPoint &pt) const; + + QRgb pixel(int x, int y) const; + QRgb pixel(const QPoint &pt) const; + + void setPixel(int x, int y, uint index_or_rgb); + void setPixel(const QPoint &pt, uint index_or_rgb); + + QVector colorTable() const; + void setColorTable(const QVector colors); + + void fill(uint pixel); + void fill(const QColor &color); + void fill(Qt::GlobalColor color); + + + bool hasAlphaChannel() const; + void setAlphaChannel(const QImage &alphaChannel); + QImage alphaChannel() const; + QImage createAlphaMask(Qt::ImageConversionFlags flags = Qt::AutoColor) const; +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + QImage createHeuristicMask(bool clipTight = true) const; +#endif + QImage createMaskFromColor(QRgb color, Qt::MaskMode mode = Qt::MaskInColor) const; + + inline QImage scaled(int w, int h, Qt::AspectRatioMode aspectMode = Qt::IgnoreAspectRatio, + Qt::TransformationMode mode = Qt::FastTransformation) const + { return scaled(QSize(w, h), aspectMode, mode); } + QImage scaled(const QSize &s, Qt::AspectRatioMode aspectMode = Qt::IgnoreAspectRatio, + Qt::TransformationMode mode = Qt::FastTransformation) const; + QImage scaledToWidth(int w, Qt::TransformationMode mode = Qt::FastTransformation) const; + QImage scaledToHeight(int h, Qt::TransformationMode mode = Qt::FastTransformation) const; + QImage transformed(const QMatrix &matrix, Qt::TransformationMode mode = Qt::FastTransformation) const; + static QMatrix trueMatrix(const QMatrix &, int w, int h); + QImage transformed(const QTransform &matrix, Qt::TransformationMode mode = Qt::FastTransformation) const; + static QTransform trueMatrix(const QTransform &, int w, int h); + QImage mirrored(bool horizontally = false, bool vertically = true) const; + QImage rgbSwapped() const; + void invertPixels(InvertMode = InvertRgb); + + + bool load(QIODevice *device, const char* format); + bool load(const QString &fileName, const char* format=0); + bool loadFromData(const uchar *buf, int len, const char *format = 0); + inline bool loadFromData(const QByteArray &data, const char* aformat=0) + { return loadFromData(reinterpret_cast(data.constData()), data.size(), aformat); } + + bool save(const QString &fileName, const char* format=0, int quality=-1) const; + bool save(QIODevice *device, const char* format=0, int quality=-1) const; + + static QImage fromData(const uchar *data, int size, const char *format = 0); + inline static QImage fromData(const QByteArray &data, const char *format = 0) + { return fromData(reinterpret_cast(data.constData()), data.size(), format); } + + int serialNumber() const; + qint64 cacheKey() const; + + QPaintEngine *paintEngine() const; + + // Auxiliary data + int dotsPerMeterX() const; + int dotsPerMeterY() const; + void setDotsPerMeterX(int); + void setDotsPerMeterY(int); + QPoint offset() const; + void setOffset(const QPoint&); +#ifndef QT_NO_IMAGE_TEXT + QStringList textKeys() const; + QString text(const QString &key = QString()) const; + void setText(const QString &key, const QString &value); + + // The following functions are obsolete as of 4.1 + QString text(const char* key, const char* lang=0) const; + QList textList() const; + QStringList textLanguages() const; + QString text(const QImageTextKeyLang&) const; + void setText(const char* key, const char* lang, const QString&); +#endif + +#ifdef QT3_SUPPORT + enum Endian { BigEndian, LittleEndian, IgnoreEndian }; + QT3_SUPPORT_CONSTRUCTOR QImage(int width, int height, int depth, int numColors=0, Endian bitOrder=IgnoreEndian); + QT3_SUPPORT_CONSTRUCTOR QImage(const QSize&, int depth, int numColors=0, Endian bitOrder=IgnoreEndian); + QT3_SUPPORT_CONSTRUCTOR QImage(uchar *data, int w, int h, int depth, const QRgb *colortable, int numColors, Endian bitOrder); +#ifdef Q_WS_QWS + QT3_SUPPORT_CONSTRUCTOR QImage(uchar *data, int w, int h, int depth, int pbl, const QRgb *colortable, int numColors, Endian bitOrder); +#endif + inline QT3_SUPPORT Endian bitOrder() const { + Format f = format(); + return f == Format_Mono ? BigEndian : (f == Format_MonoLSB ? LittleEndian : IgnoreEndian); + } + QT3_SUPPORT QImage convertDepth(int, Qt::ImageConversionFlags flags = Qt::AutoColor) const; + QT3_SUPPORT QImage convertDepthWithPalette(int, QRgb* p, int pc, Qt::ImageConversionFlags flags = Qt::AutoColor) const; + QT3_SUPPORT QImage convertBitOrder(Endian) const; + QT3_SUPPORT bool hasAlphaBuffer() const; + QT3_SUPPORT void setAlphaBuffer(bool); + QT3_SUPPORT uchar **jumpTable(); + QT3_SUPPORT const uchar * const *jumpTable() const; + inline QT3_SUPPORT void reset() { *this = QImage(); } + static inline QT3_SUPPORT Endian systemByteOrder() + { return QSysInfo::ByteOrder == QSysInfo::BigEndian ? BigEndian : LittleEndian; } + inline QT3_SUPPORT QImage swapRGB() const { return rgbSwapped(); } + inline QT3_SUPPORT QImage mirror(bool horizontally = false, bool vertically = true) const + { return mirrored(horizontally, vertically); } + QT3_SUPPORT bool create(const QSize&, int depth, int numColors=0, Endian bitOrder=IgnoreEndian); + QT3_SUPPORT bool create(int width, int height, int depth, int numColors=0, Endian bitOrder=IgnoreEndian); + inline QT3_SUPPORT QImage xForm(const QMatrix &matrix) const { return transformed(QTransform(matrix)); } + inline QT3_SUPPORT QImage smoothScale(int w, int h, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) const + { return scaled(QSize(w, h), mode, Qt::SmoothTransformation); } + inline QImage QT3_SUPPORT smoothScale(const QSize &s, Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio) const + { return scaled(s, mode, Qt::SmoothTransformation); } + inline QT3_SUPPORT QImage scaleWidth(int w) const { return scaledToWidth(w); } + inline QT3_SUPPORT QImage scaleHeight(int h) const { return scaledToHeight(h); } + inline QT3_SUPPORT void invertPixels(bool invertAlpha) { invertAlpha ? invertPixels(InvertRgba) : invertPixels(InvertRgb); } + inline QT3_SUPPORT QImage copy(int x, int y, int w, int h, Qt::ImageConversionFlags) const + { return copy(QRect(x, y, w, h)); } + inline QT3_SUPPORT QImage copy(const QRect &rect, Qt::ImageConversionFlags) const + { return copy(rect); } + static QT3_SUPPORT Endian systemBitOrder(); + inline QT3_SUPPORT_CONSTRUCTOR QImage(const QByteArray &data) + { d = 0; *this = QImage::fromData(data); } +#endif + +protected: + virtual int metric(PaintDeviceMetric metric) const; + +private: + friend class QWSOnScreenSurface; + QImageData *d; + + friend class QRasterPixmapData; + friend class QBlittablePixmapData; + friend class QPixmapCacheEntry; + friend Q_GUI_EXPORT qint64 qt_image_id(const QImage &image); + friend const QVector *qt_image_colortable(const QImage &image); + +public: + typedef QImageData * DataPtr; + inline DataPtr &data_ptr() { return d; } +}; + +Q_DECLARE_SHARED(QImage) +Q_DECLARE_TYPEINFO(QImage, Q_MOVABLE_TYPE); + +// Inline functions... + +Q_GUI_EXPORT_INLINE bool QImage::valid(const QPoint &pt) const { return valid(pt.x(), pt.y()); } +Q_GUI_EXPORT_INLINE int QImage::pixelIndex(const QPoint &pt) const { return pixelIndex(pt.x(), pt.y());} +Q_GUI_EXPORT_INLINE QRgb QImage::pixel(const QPoint &pt) const { return pixel(pt.x(), pt.y()); } +Q_GUI_EXPORT_INLINE void QImage::setPixel(const QPoint &pt, uint index_or_rgb) { setPixel(pt.x(), pt.y(), index_or_rgb); } + +// QImage stream functions + +#if !defined(QT_NO_DATASTREAM) +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QImage &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QImage &); +#endif + +#ifdef QT3_SUPPORT +Q_GUI_EXPORT QT3_SUPPORT void bitBlt(QImage* dst, int dx, int dy, const QImage* src, + int sx=0, int sy=0, int sw=-1, int sh=-1, Qt::ImageConversionFlags flags = Qt::AutoColor); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QIMAGE_H diff --git a/src/gui/image/qimage_neon.cpp b/src/gui/image/qimage_neon.cpp new file mode 100644 index 0000000000..89b4bab038 --- /dev/null +++ b/src/gui/image/qimage_neon.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#ifdef QT_HAVE_NEON + +QT_BEGIN_NAMESPACE + +Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len) +{ + if (!len) + return; + + const quint32 *const end = dst + len; + + // align dst on 64 bits + const int offsetToAlignOn8Bytes = (reinterpret_cast(dst) >> 2) & 0x1; + for (int i = 0; i < offsetToAlignOn8Bytes; ++i) { + *dst++ = qRgb(src[0], src[1], src[2]); + src += 3; + } + + if ((len - offsetToAlignOn8Bytes) >= 8) { + const quint32 *const simdEnd = end - 7; + register uint8x8_t fullVector asm ("d3") = vdup_n_u8(0xff); + do { +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + asm volatile ( + "vld3.8 { d4, d5, d6 }, [%[SRC]] !\n\t" + "vst4.8 { d3, d4, d5, d6 }, [%[DST],:64] !\n\t" + : [DST]"+r" (dst), [SRC]"+r" (src) + : "w"(fullVector) + : "memory", "d4", "d5", "d6" + ); +#else + asm volatile ( + "vld3.8 { d0, d1, d2 }, [%[SRC]] !\n\t" + "vswp d0, d2\n\t" + "vst4.8 { d0, d1, d2, d3 }, [%[DST],:64] !\n\t" + : [DST]"+r" (dst), [SRC]"+r" (src) + : "w"(fullVector) + : "memory", "d0", "d1", "d2" + ); +#endif + } while (dst < simdEnd); + } + + while (dst != end) { + *dst++ = qRgb(src[0], src[1], src[2]); + src += 3; + } +} + +void convert_RGB888_to_RGB32_neon(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_RGB888); + Q_ASSERT(dest->format == QImage::Format_RGB32 || dest->format == QImage::Format_ARGB32 || dest->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const uchar *src_data = (uchar *) src->data; + quint32 *dest_data = (quint32 *) dest->data; + + for (int i = 0; i < src->height; ++i) { + qt_convert_rgb888_to_rgb32_neon(dest_data, src_data, src->width); + src_data += src->bytes_per_line; + dest_data = (quint32 *)((uchar*)dest_data + dest->bytes_per_line); + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_NEON diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h new file mode 100644 index 0000000000..db6620b39c --- /dev/null +++ b/src/gui/image/qimage_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGE_P_H +#define QIMAGE_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 + +#include + +#ifndef QT_NO_IMAGE_TEXT +#include +#endif + +QT_BEGIN_NAMESPACE + +class QImageWriter; + +struct Q_GUI_EXPORT QImageData { // internal image data + QImageData(); + ~QImageData(); + static QImageData *create(const QSize &size, QImage::Format format, int numColors = 0); + static QImageData *create(uchar *data, int w, int h, int bpl, QImage::Format format, bool readOnly); + + QAtomicInt ref; + + int width; + int height; + int depth; + int nbytes; // number of bytes data + QVector colortable; + uchar *data; +#ifdef QT3_SUPPORT + uchar **jumptable; +#endif + QImage::Format format; + int bytes_per_line; + int ser_no; // serial number + int detach_no; + + qreal dpmx; // dots per meter X (or 0) + qreal dpmy; // dots per meter Y (or 0) + QPoint offset; // offset in pixels + + uint own_data : 1; + uint ro_data : 1; + uint has_alpha_clut : 1; + uint is_cached : 1; + + bool checkForAlphaPixels() const; + + // Convert the image in-place, minimizing memory reallocation + // Return false if the conversion cannot be done in-place. + bool convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags); + +#ifndef QT_NO_IMAGE_TEXT + QMap text; +#endif + bool doImageIO(const QImage *image, QImageWriter* io, int quality) const; + + QPaintEngine *paintEngine; +}; + +void qInitImageConversions(); +Q_GUI_EXPORT void qGamma_correct_back_to_linear_cs(QImage *image); + +inline int qt_depthForFormat(QImage::Format format) +{ + int depth = 0; + switch(format) { + case QImage::Format_Invalid: + case QImage::NImageFormats: + Q_ASSERT(false); + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + depth = 1; + break; + case QImage::Format_Indexed8: + depth = 8; + break; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + depth = 32; + break; + case QImage::Format_RGB555: + case QImage::Format_RGB16: + case QImage::Format_RGB444: + case QImage::Format_ARGB4444_Premultiplied: + depth = 16; + break; + case QImage::Format_RGB666: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_RGB888: + depth = 24; + break; + } + return depth; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/image/qimage_sse2.cpp b/src/gui/image/qimage_sse2.cpp new file mode 100644 index 0000000000..e9bbb6fa36 --- /dev/null +++ b/src/gui/image/qimage_sse2.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qimage.h" +#include +#include +#include +#include + +#ifdef QT_HAVE_SSE2 + +QT_BEGIN_NAMESPACE + +bool convert_ARGB_to_ARGB_PM_inplace_sse2(QImageData *data, Qt::ImageConversionFlags) +{ + Q_ASSERT(data->format == QImage::Format_ARGB32); + + // extra pixels on each line + const int spare = data->width & 3; + // width in pixels of the pad at the end of each line + const int pad = (data->bytes_per_line >> 2) - data->width; + const int iter = data->width >> 2; + int height = data->height; + + const __m128i alphaMask = _mm_set1_epi32(0xff000000); + const __m128i nullVector = _mm_setzero_si128(); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + + __m128i *d = reinterpret_cast<__m128i*>(data->data); + while (height--) { + const __m128i *end = d + iter; + + for (; d != end; ++d) { + const __m128i srcVector = _mm_loadu_si128(d); + const __m128i srcVectorAlpha = _mm_and_si128(srcVector, alphaMask); + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, alphaMask)) == 0xffff) { + // opaque, data is unchanged + } else if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, nullVector)) == 0xffff) { + // fully transparent + _mm_storeu_si128(d, nullVector); + } else { + __m128i alphaChannel = _mm_srli_epi32(srcVector, 24); + alphaChannel = _mm_or_si128(alphaChannel, _mm_slli_epi32(alphaChannel, 16)); + + __m128i result; + BYTE_MUL_SSE2(result, srcVector, alphaChannel, colorMask, half); + result = _mm_or_si128(_mm_andnot_si128(alphaMask, result), srcVectorAlpha); + _mm_storeu_si128(d, result); + } + } + + QRgb *p = reinterpret_cast(d); + QRgb *pe = p+spare; + for (; p != pe; ++p) { + if (*p < 0x00ffffff) + *p = 0; + else if (*p < 0xff000000) + *p = PREMUL(*p); + } + + d = reinterpret_cast<__m128i*>(p+pad); + } + + data->format = QImage::Format_ARGB32_Premultiplied; + return true; +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSE2 diff --git a/src/gui/image/qimage_ssse3.cpp b/src/gui/image/qimage_ssse3.cpp new file mode 100644 index 0000000000..81e817eb73 --- /dev/null +++ b/src/gui/image/qimage_ssse3.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#ifdef QT_HAVE_SSSE3 + +QT_BEGIN_NAMESPACE + +// Convert a scanline of RGB888 (src) to RGB32 (dst) +// src must be at least len * 3 bytes +// dst must be at least len * 4 bytes +Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len) +{ + quint32 *const end = dst + len; + + // Prologue, align dst to 16 bytes. The alignment is done on dst because it has 4 store() + // for each 3 load() of src. + const int offsetToAlignOn16Bytes = (4 - ((reinterpret_cast(dst) >> 2) & 0x3)) & 0x3; + const int prologLength = qMin(len, offsetToAlignOn16Bytes); + + for (int i = 0; i < prologLength; ++i) { + *dst++ = qRgb(src[0], src[1], src[2]); + src += 3; + } + + // Mask the 4 first colors of the RGB888 vector + const __m128i shuffleMask = _mm_set_epi8(0xff, 9, 10, 11, 0xff, 6, 7, 8, 0xff, 3, 4, 5, 0xff, 0, 1, 2); + + // Mask the 4 last colors of a RGB888 vector with an offset of 1 (so the last 3 bytes are RGB) + const __m128i shuffleMaskEnd = _mm_set_epi8(0xff, 13, 14, 15, 0xff, 10, 11, 12, 0xff, 7, 8, 9, 0xff, 4, 5, 6); + + // Mask to have alpha = 0xff + const __m128i alphaMask = _mm_set1_epi32(0xff000000); + + __m128i *inVectorPtr = (__m128i *)src; + __m128i *dstVectorPtr = (__m128i *)dst; + + const int simdRoundCount = (len - prologLength) / 16; // one iteration in the loop converts 16 pixels + for (int i = 0; i < simdRoundCount; ++i) { + /* + RGB888 has 5 pixels per vector, + 1 byte from the next pixel. The idea here is + to load vectors of RGB888 and use palignr to select a vector out of two vectors. + + After 3 loads of RGB888 and 3 stores of RGB32, we have 4 pixels left in the last + vector of RGB888, we can mask it directly to get a last store or RGB32. After that, + the first next byte is a R, and we can loop for the next 16 pixels. + + The conversion itself is done with a byte permutation (pshufb). + */ + __m128i firstSrcVector = _mm_lddqu_si128(inVectorPtr); + __m128i outputVector = _mm_shuffle_epi8(firstSrcVector, shuffleMask); + _mm_store_si128(dstVectorPtr, _mm_or_si128(outputVector, alphaMask)); + ++inVectorPtr; + ++dstVectorPtr; + + // There are 4 unused bytes left in srcVector, we need to load the next 16 bytes + // and load the next input with palignr + __m128i secondSrcVector = _mm_lddqu_si128(inVectorPtr); + __m128i srcVector = _mm_alignr_epi8(secondSrcVector, firstSrcVector, 12); + outputVector = _mm_shuffle_epi8(srcVector, shuffleMask); + _mm_store_si128(dstVectorPtr, _mm_or_si128(outputVector, alphaMask)); + ++inVectorPtr; + ++dstVectorPtr; + firstSrcVector = secondSrcVector; + + // We now have 8 unused bytes left in firstSrcVector + secondSrcVector = _mm_lddqu_si128(inVectorPtr); + srcVector = _mm_alignr_epi8(secondSrcVector, firstSrcVector, 8); + outputVector = _mm_shuffle_epi8(srcVector, shuffleMask); + _mm_store_si128(dstVectorPtr, _mm_or_si128(outputVector, alphaMask)); + ++inVectorPtr; + ++dstVectorPtr; + + // There are now 12 unused bytes in firstSrcVector. + // We can mask them directly, almost there. + outputVector = _mm_shuffle_epi8(secondSrcVector, shuffleMaskEnd); + _mm_store_si128(dstVectorPtr, _mm_or_si128(outputVector, alphaMask)); + ++dstVectorPtr; + } + src = (uchar *)inVectorPtr; + dst = (quint32 *)dstVectorPtr; + + while (dst != end) { + *dst++ = qRgb(src[0], src[1], src[2]); + src += 3; + } +} + +void convert_RGB888_to_RGB32_ssse3(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags) +{ + Q_ASSERT(src->format == QImage::Format_RGB888); + Q_ASSERT(dest->format == QImage::Format_RGB32 || dest->format == QImage::Format_ARGB32 || dest->format == QImage::Format_ARGB32_Premultiplied); + Q_ASSERT(src->width == dest->width); + Q_ASSERT(src->height == dest->height); + + const uchar *src_data = (uchar *) src->data; + quint32 *dest_data = (quint32 *) dest->data; + + for (int i = 0; i < src->height; ++i) { + qt_convert_rgb888_to_rgb32_ssse3(dest_data, src_data, src->width); + src_data += src->bytes_per_line; + dest_data = (quint32 *)((uchar*)dest_data + dest->bytes_per_line); + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSSE3 diff --git a/src/gui/image/qimageiohandler.cpp b/src/gui/image/qimageiohandler.cpp new file mode 100644 index 0000000000..da1d236998 --- /dev/null +++ b/src/gui/image/qimageiohandler.cpp @@ -0,0 +1,570 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QImageIOHandler + \brief The QImageIOHandler class defines the common image I/O + interface for all image formats in Qt. + \reentrant + + Qt uses QImageIOHandler for reading and writing images through + QImageReader and QImageWriter. You can also derive from this class + to write your own image format handler using Qt's plugin mechanism. + + Call setDevice() to assign a device to the handler, and + setFormat() to assign a format to it. One QImageIOHandler may + support more than one image format. canRead() returns true if an + image can be read from the device, and read() and write() return + true if reading or writing an image was completed successfully. + + QImageIOHandler also has support for animations formats, through + the functions loopCount(), imageCount(), nextImageDelay() and + currentImageNumber(). + + In order to determine what options an image handler supports, Qt + will call supportsOption() and setOption(). Make sure to + reimplement these functions if you can provide support for any of + the options in the ImageOption enum. + + To write your own image handler, you must at least reimplement + canRead() and read(). Then create a QImageIOPlugin that + can create the handler. Finally, install your plugin, and + QImageReader and QImageWriter will then automatically load the + plugin, and start using it. + + \sa QImageIOPlugin, QImageReader, QImageWriter +*/ + +/*! \enum QImageIOHandler::ImageOption + + This enum describes the different options supported by + QImageIOHandler. Some options are used to query an image for + properties, and others are used to toggle the way in which an + image should be written. + + \value Size The original size of an image. A handler that supports + this option is expected to read the size of the image from the + image metadata, and return this size from option() as a QSize. + + \value ClipRect The clip rect, or ROI (Region Of Interest). A + handler that supports this option is expected to only read the + provided QRect area from the original image in read(), before any + other transformation is applied. + + \value ScaledSize The scaled size of the image. A handler that + supports this option is expected to scale the image to the + provided size (a QSize), after applying any clip rect + transformation (ClipRect). If the handler does not support this + option, QImageReader will perform the scaling after the image has + been read. + + \value ScaledClipRect The scaled clip rect (or ROI, Region Of + Interest) of the image. A handler that supports this option is + expected to apply the provided clip rect (a QRect), after applying + any scaling (ScaleSize) or regular clipping (ClipRect). If the + handler does not support this option, QImageReader will apply the + scaled clip rect after the image has been read. + + \value Description The image description. Some image formats, + such as GIF and PNG, allow embedding of text + or comments into the image data (e.g., for storing copyright + information). It's common that the text is stored in key-value + pairs, but some formats store all text in one continuous block. + QImageIOHandler returns the text as one + QString, where keys and values are separated by a ':', and + keys-value pairs are separated by two newlines (\\n\\n). For example, + "Title: Sunset\\n\\nAuthor: Jim Smith\\nSarah Jones\\n\\n". Formats that + store text in a single block can use "Description" as the key. + + \value CompressionRatio The compression ratio of the image data. A + handler that supports this option is expected to set its + compression rate depending on the value of this option (an int) + when writing. + + \value Gamma The gamma level of the image. A handler that supports + this option is expected to set the image gamma level depending on + the value of this option (a float) when writing. + + \value Quality The quality level of the image. A handler that + supports this option is expected to set the image quality level + depending on the value of this option (an int) when writing. + + \value Name The name of the image. A handler that supports this + option is expected to read the name from the image metadata and + return this as a QString, or when writing an image it is expected + to store the name in the image metadata. + + \value SubType The subtype of the image. A handler that supports + this option can use the subtype value to help when reading and + writing images. For example, a PPM handler may have a subtype + value of "ppm" or "ppmraw". + + \value IncrementalReading A handler that supports this option is + expected to read the image in several passes, as if it was an + animation. QImageReader will treat the image as an animation. + + \value Endianness The endianness of the image. Certain image + formats can be stored as BigEndian or LittleEndian. A handler that + supports Endianness uses the value of this option to determine how + the image should be stored. + + \value Animation Image formats that support animation return + true for this value in supportsOption(); otherwise, false is returned. + + \value BackgroundColor Certain image formats allow the + background color to be specified. A handler that supports + BackgroundColor initializes the background color to this option + (a QColor) when reading an image. + + \value ImageFormat The image's data format returned by the handler. + This can be any of the formats listed in QImage::Format. +*/ + +/*! + \class QImageIOPlugin + \brief The QImageIOPlugin class defines an interface for writing + an image format plugin. + \reentrant + + \ingroup plugins + + QImageIOPlugin is a factory for creating QImageIOHandler objects, + which are used internally by QImageReader and QImageWriter to add + support for different image formats to Qt. + + Writing an image I/O plugin is achieved by subclassing this + base class, reimplementing the pure virtual functions capabilities(), + create(), and keys(), and exporting the class with the + Q_EXPORT_PLUGIN2() macro. See \l{How to Create Qt Plugins} for details. + + An image format plugin can support three capabilities: reading (\l + CanRead), writing (\l CanWrite) and \e incremental reading (\l + CanReadIncremental). Reimplement capabilities() in you subclass to + expose the capabilities of your image format. + + create() should create an instance of your QImageIOHandler + subclass, with the provided device and format properly set, and + return this handler. You must also reimplement keys() so that Qt + knows which image formats your plugin supports. + + Different plugins can support different capabilities. For example, + you may have one plugin that supports reading the GIF format, and + another that supports writing. Qt will select the correct plugin + for the job, depending on the return value of capabilities(). If + several plugins support the same capability, Qt will select one + arbitrarily. + + \sa QImageIOHandler, {How to Create Qt Plugins} +*/ + +/*! + \enum QImageIOPlugin::Capability + + This enum describes the capabilities of a QImageIOPlugin. + + \value CanRead The plugin can read images. + \value CanWrite The plugin can write images. + \value CanReadIncremental The plugin can read images incrementally. +*/ + +/*! + \class QImageIOHandlerFactoryInterface + \brief The QImageIOHandlerFactoryInterface class provides the factory + interface for QImageIOPlugin. + \reentrant + + \internal + + \sa QImageIOPlugin +*/ + +#include "qimageiohandler.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; + +class QImageIOHandlerPrivate +{ + Q_DECLARE_PUBLIC(QImageIOHandler) +public: + QImageIOHandlerPrivate(QImageIOHandler *q); + virtual ~QImageIOHandlerPrivate(); + + QIODevice *device; + mutable QByteArray format; + + QImageIOHandler *q_ptr; +}; + +QImageIOHandlerPrivate::QImageIOHandlerPrivate(QImageIOHandler *q) +{ + device = 0; + q_ptr = q; +} + +QImageIOHandlerPrivate::~QImageIOHandlerPrivate() +{ +} + +/*! + Constructs a QImageIOHandler object. +*/ +QImageIOHandler::QImageIOHandler() + : d_ptr(new QImageIOHandlerPrivate(this)) +{ +} + +/*! \internal + + Constructs a QImageIOHandler object, using the private member \a + dd. +*/ +QImageIOHandler::QImageIOHandler(QImageIOHandlerPrivate &dd) + : d_ptr(&dd) +{ +} + +/*! + Destructs the QImageIOHandler object. +*/ +QImageIOHandler::~QImageIOHandler() +{ +} + +/*! + Sets the device of the QImageIOHandler to \a device. The image + handler will use this device when reading and writing images. + + The device can only be set once and must be set before calling + canRead(), read(), write(), etc. If you need to read multiple + files, construct multiple instances of the appropriate + QImageIOHandler subclass. + + \sa device() +*/ +void QImageIOHandler::setDevice(QIODevice *device) +{ + Q_D(QImageIOHandler); + d->device = device; +} + +/*! + Returns the device currently assigned to the QImageIOHandler. If + not device has been assigned, 0 is returned. +*/ +QIODevice *QImageIOHandler::device() const +{ + Q_D(const QImageIOHandler); + return d->device; +} + +/*! + Sets the format of the QImageIOHandler to \a format. The format is + most useful for handlers that support multiple image formats. + + \sa format() +*/ +void QImageIOHandler::setFormat(const QByteArray &format) +{ + Q_D(QImageIOHandler); + d->format = format; +} + +/*! + Sets the format of the QImageIOHandler to \a format. The format is + most useful for handlers that support multiple image formats. + + This function is declared const so that it can be called from canRead(). + + \sa format() +*/ +void QImageIOHandler::setFormat(const QByteArray &format) const +{ + Q_D(const QImageIOHandler); + d->format = format; +} + +/*! + Returns the format that is currently assigned to + QImageIOHandler. If no format has been assigned, an empty string + is returned. + + \sa setFormat() +*/ +QByteArray QImageIOHandler::format() const +{ + Q_D(const QImageIOHandler); + return d->format; +} + +/*! + \fn bool QImageIOHandler::read(QImage *image) + + Read an image from the device, and stores it in \a image. + Returns true if the image is successfully read; otherwise returns + false. + + For image formats that support incremental loading, and for animation + formats, the image handler can assume that \a image points to the + previous frame. + + \sa canRead() +*/ + +/*! + \fn bool QImageIOHandler::canRead() const + + Returns true if an image can be read from the device (i.e., the + image format is supported, the device can be read from and the + initial header information suggests that the image can be read); + otherwise returns false. + + When reimplementing canRead(), make sure that the I/O device + (device()) is left in its original state (e.g., by using peek() + rather than read()). + + \sa read(), QIODevice::peek() +*/ + +/*! + \obsolete + + Use format() instead. +*/ + +QByteArray QImageIOHandler::name() const +{ + return format(); +} + +/*! + Writes the image \a image to the assigned device. Returns true on + success; otherwise returns false. + + The default implementation does nothing, and simply returns false. +*/ +bool QImageIOHandler::write(const QImage &image) +{ + Q_UNUSED(image); + return false; +} + +/*! + Sets the option \a option with the value \a value. + + \sa option(), ImageOption +*/ +void QImageIOHandler::setOption(ImageOption option, const QVariant &value) +{ + Q_UNUSED(option); + Q_UNUSED(value); +} + +/*! + Returns the value assigned to \a option as a QVariant. The type of + the value depends on the option. For example, option(Size) returns + a QSize variant. + + \sa setOption(), supportsOption() +*/ +QVariant QImageIOHandler::option(ImageOption option) const +{ + Q_UNUSED(option); + return QVariant(); +} + +/*! + Returns true if the QImageIOHandler supports the option \a option; + otherwise returns false. For example, if the QImageIOHandler + supports the \l Size option, supportsOption(Size) must return + true. + + \sa setOption(), option() +*/ +bool QImageIOHandler::supportsOption(ImageOption option) const +{ + Q_UNUSED(option); + return false; +} + +/*! + For image formats that support animation, this function returns + the sequence number of the current image in the animation. If + this function is called before any image is read(), -1 is + returned. The number of the first image in the sequence is 0. + + If the image format does not support animation, 0 is returned. + + \sa read() +*/ +int QImageIOHandler::currentImageNumber() const +{ + return 0; +} + +/*! + Returns the rect of the current image. If no rect is defined for the + image, and empty QRect() is returned. + + This function is useful for animations, where only parts of the frame + may be updated at a time. +*/ +QRect QImageIOHandler::currentImageRect() const +{ + return QRect(); +} + +/*! + For image formats that support animation, this function returns + the number of images in the animation. If the image format does + not support animation, or if it is unable to determine the number + of images, 0 is returned. + + The default implementation returns 1 if canRead() returns true; + otherwise 0 is returned. +*/ +int QImageIOHandler::imageCount() const +{ + return canRead() ? 1 : 0; +} + +/*! + For image formats that support animation, this function jumps to the + next image. + + The default implementation does nothing, and returns false. +*/ +bool QImageIOHandler::jumpToNextImage() +{ + return false; +} + +/*! + For image formats that support animation, this function jumps to the image + whose sequence number is \a imageNumber. The next call to read() will + attempt to read this image. + + The default implementation does nothing, and returns false. +*/ +bool QImageIOHandler::jumpToImage(int imageNumber) +{ + Q_UNUSED(imageNumber); + return false; +} + +/*! + For image formats that support animation, this function returns + the number of times the animation should loop. If the image format + does not support animation, 0 is returned. +*/ +int QImageIOHandler::loopCount() const +{ + return 0; +} + +/*! + For image formats that support animation, this function returns + the number of milliseconds to wait until reading the next + image. If the image format does not support animation, 0 is + returned. +*/ +int QImageIOHandler::nextImageDelay() const +{ + return 0; +} + +/*! + Constructs an image plugin with the given \a parent. This is + invoked automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QImageIOPlugin::QImageIOPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the picture format plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QImageIOPlugin::~QImageIOPlugin() +{ +} + +/*! \fn QImageIOPlugin::capabilities(QIODevice *device, const QByteArray &format) const + + Returns the capabilities on the plugin, based on the data in \a + device and the format \a format. For example, if the + QImageIOHandler supports the BMP format, and the data in the + device starts with the characters "BM", this function should + return \l CanRead. If \a format is "bmp" and the handler supports + both reading and writing, this function should return \l CanRead | + \l CanWrite. +*/ + +/*! + \fn QImageIOPlugin::keys() const + + Returns the list of image keys this plugin supports. + + These keys are usually the names of the image formats that are implemented + in the plugin (e.g., "jpg" or "gif"). + + \sa capabilities() +*/ + +/*! + \fn QImageIOHandler *QImageIOPlugin::create(QIODevice *device, const QByteArray &format) const + + Creates and returns a QImageIOHandler subclass, with \a device + and \a format set. The \a format must come from the list returned by keys(). + Format names are case sensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE diff --git a/src/gui/image/qimageiohandler.h b/src/gui/image/qimageiohandler.h new file mode 100644 index 0000000000..05e1853f97 --- /dev/null +++ b/src/gui/image/qimageiohandler.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGEIOHANDLER_H +#define QIMAGEIOHANDLER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QImage; +class QRect; +class QSize; +class QVariant; + +class QImageIOHandlerPrivate; +class Q_GUI_EXPORT QImageIOHandler +{ + Q_DECLARE_PRIVATE(QImageIOHandler) +public: + QImageIOHandler(); + virtual ~QImageIOHandler(); + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void setFormat(const QByteArray &format); + void setFormat(const QByteArray &format) const; + QByteArray format() const; + + virtual QByteArray name() const; + + virtual bool canRead() const = 0; + virtual bool read(QImage *image) = 0; + virtual bool write(const QImage &image); + + enum ImageOption { + Size, + ClipRect, + Description, + ScaledClipRect, + ScaledSize, + CompressionRatio, + Gamma, + Quality, + Name, + SubType, + IncrementalReading, + Endianness, + Animation, + BackgroundColor, + ImageFormat + }; + virtual QVariant option(ImageOption option) const; + virtual void setOption(ImageOption option, const QVariant &value); + virtual bool supportsOption(ImageOption option) const; + + // incremental loading + virtual bool jumpToNextImage(); + virtual bool jumpToImage(int imageNumber); + virtual int loopCount() const; + virtual int imageCount() const; + virtual int nextImageDelay() const; + virtual int currentImageNumber() const; + virtual QRect currentImageRect() const; + +protected: + QImageIOHandler(QImageIOHandlerPrivate &dd); + QScopedPointer d_ptr; +private: + Q_DISABLE_COPY(QImageIOHandler) +}; + +struct Q_GUI_EXPORT QImageIOHandlerFactoryInterface : public QFactoryInterface +{ + virtual QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const = 0; +}; + +#define QImageIOHandlerFactoryInterface_iid "com.trolltech.Qt.QImageIOHandlerFactoryInterface" +Q_DECLARE_INTERFACE(QImageIOHandlerFactoryInterface, QImageIOHandlerFactoryInterface_iid) + +class Q_GUI_EXPORT QImageIOPlugin : public QObject, public QImageIOHandlerFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QImageIOHandlerFactoryInterface:QFactoryInterface) +public: + explicit QImageIOPlugin(QObject *parent = 0); + virtual ~QImageIOPlugin(); + + enum Capability { + CanRead = 0x1, + CanWrite = 0x2, + CanReadIncremental = 0x4 + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + virtual Capabilities capabilities(QIODevice *device, const QByteArray &format) const = 0; + virtual QStringList keys() const = 0; + virtual QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const = 0; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QImageIOPlugin::Capabilities) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QIMAGEIOHANDLER_H diff --git a/src/gui/image/qimagepixmapcleanuphooks.cpp b/src/gui/image/qimagepixmapcleanuphooks.cpp new file mode 100644 index 0000000000..cd7b4682b7 --- /dev/null +++ b/src/gui/image/qimagepixmapcleanuphooks.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qimagepixmapcleanuphooks_p.h" +#include "private/qpixmapdata_p.h" +#include "private/qimage_p.h" + + +QT_BEGIN_NAMESPACE + +// Legacy, single instance hooks: ### Qt 5: remove +typedef void (*_qt_pixmap_cleanup_hook)(int); +typedef void (*_qt_pixmap_cleanup_hook_64)(qint64); +typedef void (*_qt_image_cleanup_hook)(int); +Q_GUI_EXPORT _qt_pixmap_cleanup_hook qt_pixmap_cleanup_hook = 0; +Q_GUI_EXPORT _qt_pixmap_cleanup_hook_64 qt_pixmap_cleanup_hook_64 = 0; +Q_GUI_EXPORT _qt_image_cleanup_hook qt_image_cleanup_hook = 0; +Q_GUI_EXPORT _qt_image_cleanup_hook_64 qt_image_cleanup_hook_64 = 0; + +Q_GLOBAL_STATIC(QImagePixmapCleanupHooks, qt_image_and_pixmap_cleanup_hooks) + +QImagePixmapCleanupHooks *QImagePixmapCleanupHooks::instance() +{ + return qt_image_and_pixmap_cleanup_hooks(); +} + +void QImagePixmapCleanupHooks::addPixmapDataModificationHook(_qt_pixmap_cleanup_hook_pmd hook) +{ + pixmapModificationHooks.append(hook); +} + +void QImagePixmapCleanupHooks::addPixmapDataDestructionHook(_qt_pixmap_cleanup_hook_pmd hook) +{ + pixmapDestructionHooks.append(hook); +} + + +void QImagePixmapCleanupHooks::addImageHook(_qt_image_cleanup_hook_64 hook) +{ + imageHooks.append(hook); +} + +void QImagePixmapCleanupHooks::removePixmapDataModificationHook(_qt_pixmap_cleanup_hook_pmd hook) +{ + pixmapModificationHooks.removeAll(hook); +} + +void QImagePixmapCleanupHooks::removePixmapDataDestructionHook(_qt_pixmap_cleanup_hook_pmd hook) +{ + pixmapDestructionHooks.removeAll(hook); +} + +void QImagePixmapCleanupHooks::removeImageHook(_qt_image_cleanup_hook_64 hook) +{ + imageHooks.removeAll(hook); +} + +void QImagePixmapCleanupHooks::executePixmapDataModificationHooks(QPixmapData* pmd) +{ + QImagePixmapCleanupHooks *h = qt_image_and_pixmap_cleanup_hooks(); + // the global destructor for the pixmap and image hooks might have + // been called already if the app is "leaking" global + // pixmaps/images + if (!h) + return; + for (int i = 0; i < h->pixmapModificationHooks.count(); ++i) + h->pixmapModificationHooks[i](pmd); + + if (qt_pixmap_cleanup_hook_64) + qt_pixmap_cleanup_hook_64(pmd->cacheKey()); +} + +void QImagePixmapCleanupHooks::executePixmapDataDestructionHooks(QPixmapData* pmd) +{ + QImagePixmapCleanupHooks *h = qt_image_and_pixmap_cleanup_hooks(); + // the global destructor for the pixmap and image hooks might have + // been called already if the app is "leaking" global + // pixmaps/images + if (!h) + return; + for (int i = 0; i < h->pixmapDestructionHooks.count(); ++i) + h->pixmapDestructionHooks[i](pmd); + + if (qt_pixmap_cleanup_hook_64) + qt_pixmap_cleanup_hook_64(pmd->cacheKey()); +} + +void QImagePixmapCleanupHooks::executeImageHooks(qint64 key) +{ + for (int i = 0; i < qt_image_and_pixmap_cleanup_hooks()->imageHooks.count(); ++i) + qt_image_and_pixmap_cleanup_hooks()->imageHooks[i](key); + + if (qt_image_cleanup_hook_64) + qt_image_cleanup_hook_64(key); +} + + +void QImagePixmapCleanupHooks::enableCleanupHooks(QPixmapData *pixmapData) +{ + pixmapData->is_cached = true; +} + +void QImagePixmapCleanupHooks::enableCleanupHooks(const QPixmap &pixmap) +{ + enableCleanupHooks(const_cast(pixmap).data_ptr().data()); +} + +void QImagePixmapCleanupHooks::enableCleanupHooks(const QImage &image) +{ + const_cast(image).data_ptr()->is_cached = true; +} + +bool QImagePixmapCleanupHooks::isImageCached(const QImage &image) +{ + return const_cast(image).data_ptr()->is_cached; +} + +bool QImagePixmapCleanupHooks::isPixmapCached(const QPixmap &pixmap) +{ + return const_cast(pixmap).data_ptr().data()->is_cached; +} + + + +QT_END_NAMESPACE diff --git a/src/gui/image/qimagepixmapcleanuphooks_p.h b/src/gui/image/qimagepixmapcleanuphooks_p.h new file mode 100644 index 0000000000..aa6a986f08 --- /dev/null +++ b/src/gui/image/qimagepixmapcleanuphooks_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGEPIXMAP_CLEANUPHOOKS_P_H +#define QIMAGEPIXMAP_CLEANUPHOOKS_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 + +QT_BEGIN_NAMESPACE + +typedef void (*_qt_image_cleanup_hook_64)(qint64); +typedef void (*_qt_pixmap_cleanup_hook_pmd)(QPixmapData*); + + +class QImagePixmapCleanupHooks; + +class Q_GUI_EXPORT QImagePixmapCleanupHooks +{ +public: + static QImagePixmapCleanupHooks *instance(); + + static void enableCleanupHooks(const QImage &image); + static void enableCleanupHooks(const QPixmap &pixmap); + static void enableCleanupHooks(QPixmapData *pixmapData); + + static bool isImageCached(const QImage &image); + static bool isPixmapCached(const QPixmap &pixmap); + + // Gets called when a pixmap data is about to be modified: + void addPixmapDataModificationHook(_qt_pixmap_cleanup_hook_pmd); + + // Gets called when a pixmap data is about to be destroyed: + void addPixmapDataDestructionHook(_qt_pixmap_cleanup_hook_pmd); + + // Gets called when an image is about to be modified or destroyed: + void addImageHook(_qt_image_cleanup_hook_64); + + void removePixmapDataModificationHook(_qt_pixmap_cleanup_hook_pmd); + void removePixmapDataDestructionHook(_qt_pixmap_cleanup_hook_pmd); + void removeImageHook(_qt_image_cleanup_hook_64); + + static void executePixmapDataModificationHooks(QPixmapData*); + static void executePixmapDataDestructionHooks(QPixmapData*); + static void executeImageHooks(qint64 key); + +private: + QList<_qt_image_cleanup_hook_64> imageHooks; + QList<_qt_pixmap_cleanup_hook_pmd> pixmapModificationHooks; + QList<_qt_pixmap_cleanup_hook_pmd> pixmapDestructionHooks; +}; + +QT_END_NAMESPACE + +#endif // QIMAGEPIXMAP_CLEANUPHOOKS_P_H diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp new file mode 100644 index 0000000000..0a0dc35988 --- /dev/null +++ b/src/gui/image/qimagereader.cpp @@ -0,0 +1,1515 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QIMAGEREADER_DEBUG + +/*! + \class QImageReader + \brief The QImageReader class provides a format independent interface + for reading images from files or other devices. + + \reentrant + \ingroup painting + \ingroup io + + The most common way to read images is through QImage and QPixmap's + constructors, or by calling QImage::load() and + QPixmap::load(). QImageReader is a specialized class which gives + you more control when reading images. For example, you can read an + image into a specific size by calling setScaledSize(), and you can + select a clip rect, effectively loading only parts of an image, by + calling setClipRect(). Depending on the underlying support in the + image format, this can save memory and speed up loading of images. + + To read an image, you start by constructing a QImageReader object. + Pass either a file name or a device pointer, and the image format + to QImageReader's constructor. You can then set several options, + such as the clip rect (by calling setClipRect()) and scaled size + (by calling setScaledSize()). canRead() returns the image if the + QImageReader can read the image (i.e., the image format is + supported and the device is open for reading). Call read() to read + the image. + + If any error occurs when reading the image, read() will return a + null QImage. You can then call error() to find the type of error + that occurred, or errorString() to get a human readable + description of what went wrong. + + Call supportedImageFormats() for a list of formats that + QImageReader can read. QImageReader supports all built-in image + formats, in addition to any image format plugins that support + reading. + + QImageReader autodetects the image format by default, by looking at the + provided (optional) format string, the file name suffix, and the data + stream contents. You can enable or disable this feature, by calling + setAutoDetectImageFormat(). + + \sa QImageWriter, QImageIOHandler, QImageIOPlugin +*/ + +/*! + \enum QImageReader::ImageReaderError + + This enum describes the different types of errors that can occur + when reading images with QImageReader. + + \value FileNotFoundError QImageReader was used with a file name, + but not file was found with that name. This can also happen if the + file name contained no extension, and the file with the correct + extension is not supported by Qt. + + \value DeviceError QImageReader encountered a device error when + reading the image. You can consult your particular device for more + details on what went wrong. + + \value UnsupportedFormatError Qt does not support the requested + image format. + + \value InvalidDataError The image data was invalid, and + QImageReader was unable to read an image from it. The can happen + if the image file is damaged. + + \value UnknownError An unknown error occurred. If you get this + value after calling read(), it is most likely caused by a bug in + QImageReader. +*/ +#include "qimagereader.h" + +#include +#ifdef QIMAGEREADER_DEBUG +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// factory loader +#include +#include + +// image handlers +#include +#include +#include +#include +#ifndef QT_NO_IMAGEFORMAT_PNG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF +#include +#endif +#ifdef QT_BUILTIN_GIF_READER +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QImageIOHandlerFactoryInterface_iid, QLatin1String("/imageformats"))) +#endif + +enum _qt_BuiltInFormatType { +#ifndef QT_NO_IMAGEFORMAT_PNG + _qt_PngFormat, +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + _qt_JpgFormat, +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + _qt_MngFormat, +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + _qt_TifFormat, +#endif +#ifdef QT_BUILTIN_GIF_READER + _qt_GifFormat, +#endif + _qt_BmpFormat, +#ifndef QT_NO_IMAGEFORMAT_PPM + _qt_PpmFormat, + _qt_PgmFormat, + _qt_PbmFormat, +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + _qt_XbmFormat, +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + _qt_XpmFormat, +#endif + _qt_NumFormats, + _qt_NoFormat = -1 +}; + +struct _qt_BuiltInFormatStruct +{ + _qt_BuiltInFormatType type; + const char *extension; +}; + +static const _qt_BuiltInFormatStruct _qt_BuiltInFormats[] = { +#ifndef QT_NO_IMAGEFORMAT_PNG + {_qt_PngFormat, "png"}, +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + {_qt_JpgFormat, "jpg"}, +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + {_qt_MngFormat, "mng"}, +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + {_qt_TifFormat, "tif"}, +#endif +#ifdef QT_BUILTIN_GIF_READER + {_qt_GifFormat, "gif"}, +#endif + {_qt_BmpFormat, "bmp"}, +#ifndef QT_NO_IMAGEFORMAT_PPM + {_qt_PpmFormat, "ppm"}, + {_qt_PgmFormat, "pgm"}, + {_qt_PbmFormat, "pbm"}, +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + {_qt_XbmFormat, "xbm"}, +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + {_qt_XpmFormat, "xpm"}, +#endif + {_qt_NoFormat, ""} +}; + +static QImageIOHandler *createReadHandlerHelper(QIODevice *device, + const QByteArray &format, + bool autoDetectImageFormat, + bool ignoresFormatAndExtension) +{ + if (!autoDetectImageFormat && format.isEmpty()) + return 0; + + QByteArray form = format.toLower(); + QImageIOHandler *handler = 0; + +#ifndef QT_NO_LIBRARY + // check if we have plugins that support the image format + QFactoryLoader *l = loader(); + QStringList keys = l->keys(); +#endif + QByteArray suffix; + +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler( device =" << (void *)device << ", format =" << format << ")," + << keys.size() << "plugins available: " << keys; +#endif + +#ifndef QT_NO_LIBRARY + int suffixPluginIndex = -1; + if (device && format.isEmpty() && autoDetectImageFormat && !ignoresFormatAndExtension) { + // if there's no format, see if \a device is a file, and if so, find + // the file suffix and find support for that format among our plugins. + // this allows plugins to override our built-in handlers. + if (QFile *file = qobject_cast(device)) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: device is a file:" << file->fileName(); +#endif + if (!(suffix = QFileInfo(file->fileName()).suffix().toLower().toLatin1()).isEmpty()) { + int index = keys.indexOf(QString::fromLatin1(suffix)); + if (index != -1) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: suffix recognized; the" + << suffix << "plugin might be able to read this"; +#endif + suffixPluginIndex = index; + } + } + } + } +#endif // QT_NO_LIBRARY + + QByteArray testFormat = !form.isEmpty() ? form : suffix; + + if (ignoresFormatAndExtension) + testFormat = QByteArray(); + +#ifndef QT_NO_LIBRARY + if (suffixPluginIndex != -1) { + // check if the plugin that claims support for this format can load + // from this device with this format. + const qint64 pos = device ? device->pos() : 0; + QImageIOPlugin *plugin = qobject_cast(l->instance(QString::fromLatin1(suffix))); + if (plugin && plugin->capabilities(device, testFormat) & QImageIOPlugin::CanRead) { + handler = plugin->create(device, testFormat); +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: using the" << suffix + << "plugin"; +#endif + } + if (device && !device->isSequential()) + device->seek(pos); + } + + if (!handler && !testFormat.isEmpty() && !ignoresFormatAndExtension) { + // check if any plugin supports the format (they are not allowed to + // read from the device yet). + const qint64 pos = device ? device->pos() : 0; + + if (autoDetectImageFormat) { + for (int i = 0; i < keys.size(); ++i) { + if (i != suffixPluginIndex) { + QImageIOPlugin *plugin = qobject_cast(l->instance(keys.at(i))); + if (plugin && plugin->capabilities(device, testFormat) & QImageIOPlugin::CanRead) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: the" << keys.at(i) << "plugin can read this format"; +#endif + handler = plugin->create(device, testFormat); + break; + } + } + } + } else { + QImageIOPlugin *plugin = qobject_cast(l->instance(QLatin1String(testFormat))); + if (plugin && plugin->capabilities(device, testFormat) & QImageIOPlugin::CanRead) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: the" << testFormat << "plugin can read this format"; +#endif + handler = plugin->create(device, testFormat); + } + } + if (device && !device->isSequential()) + device->seek(pos); + } + +#endif // QT_NO_LIBRARY + + // if we don't have a handler yet, check if we have built-in support for + // the format + if (!handler && !testFormat.isEmpty()) { + if (false) { +#ifndef QT_NO_IMAGEFORMAT_PNG + } else if (testFormat == "png") { + handler = new QPngHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + } else if (testFormat == "jpg" || testFormat == "jpeg") { + handler = new QJpegHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + } else if (testFormat == "mng") { + handler = new QMngHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + } else if (testFormat == "tif" || testFormat == "tiff") { + handler = new QTiffHandler; +#endif +#ifdef QT_BUILTIN_GIF_READER + } else if (testFormat == "gif") { + handler = new QGifHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_BMP + } else if (testFormat == "bmp") { + handler = new QBmpHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + } else if (testFormat == "xpm") { + handler = new QXpmHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + } else if (testFormat == "xbm") { + handler = new QXbmHandler; + handler->setOption(QImageIOHandler::SubType, testFormat); +#endif +#ifndef QT_NO_IMAGEFORMAT_PPM + } else if (testFormat == "pbm" || testFormat == "pbmraw" || testFormat == "pgm" + || testFormat == "pgmraw" || testFormat == "ppm" || testFormat == "ppmraw") { + handler = new QPpmHandler; + handler->setOption(QImageIOHandler::SubType, testFormat); +#endif + } + +#ifdef QIMAGEREADER_DEBUG + if (handler) + qDebug() << "QImageReader::createReadHandler: using the built-in handler for" << testFormat; +#endif + } + +#ifndef QT_NO_LIBRARY + if (!handler && (autoDetectImageFormat || ignoresFormatAndExtension)) { + // check if any of our plugins recognize the file from its contents. + const qint64 pos = device ? device->pos() : 0; + for (int i = 0; i < keys.size(); ++i) { + if (i != suffixPluginIndex) { + QImageIOPlugin *plugin = qobject_cast(l->instance(keys.at(i))); + if (plugin && plugin->capabilities(device, QByteArray()) & QImageIOPlugin::CanRead) { + handler = plugin->create(device, testFormat); +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: the" << keys.at(i) << "plugin can read this data"; +#endif + break; + } + } + } + if (device && !device->isSequential()) + device->seek(pos); + } +#endif // QT_NO_LIBRARY + + if (!handler && (autoDetectImageFormat || ignoresFormatAndExtension)) { + // check if any of our built-in handlers recognize the file from its + // contents. + int currentFormat = 0; + if (!suffix.isEmpty()) { + // If reading from a file with a suffix, start testing our + // built-in handler for that suffix first. + for (int i = 0; i < _qt_NumFormats; ++i) { + if (_qt_BuiltInFormats[i].extension == suffix) { + currentFormat = i; + break; + } + } + } + + QByteArray subType; + int numFormats = _qt_NumFormats; + while (device && numFormats >= 0) { + const _qt_BuiltInFormatStruct *formatStruct = &_qt_BuiltInFormats[currentFormat]; + + const qint64 pos = device->pos(); + switch (formatStruct->type) { +#ifndef QT_NO_IMAGEFORMAT_PNG + case _qt_PngFormat: + if (QPngHandler::canRead(device)) + handler = new QPngHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + case _qt_JpgFormat: + if (QJpegHandler::canRead(device)) + handler = new QJpegHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + case _qt_MngFormat: + if (QMngHandler::canRead(device)) + handler = new QMngHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + case _qt_TifFormat: + if (QTiffHandler::canRead(device)) + handler = new QTiffHandler; + break; +#endif +#ifdef QT_BUILTIN_GIF_READER + case _qt_GifFormat: + if (QGifHandler::canRead(device)) + handler = new QGifHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_BMP + case _qt_BmpFormat: + if (QBmpHandler::canRead(device)) + handler = new QBmpHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + case _qt_XpmFormat: + if (QXpmHandler::canRead(device)) + handler = new QXpmHandler; + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_PPM + case _qt_PbmFormat: + case _qt_PgmFormat: + case _qt_PpmFormat: + if (QPpmHandler::canRead(device, &subType)) { + handler = new QPpmHandler; + handler->setOption(QImageIOHandler::SubType, subType); + } + break; +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + case _qt_XbmFormat: + if (QXbmHandler::canRead(device)) + handler = new QXbmHandler; + break; +#endif + default: + break; + } + if (!device->isSequential()) + device->seek(pos); + + if (handler) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: the" << formatStruct->extension + << "built-in handler can read this data"; +#endif + break; + } + + --numFormats; + ++currentFormat; + currentFormat %= _qt_NumFormats; + } + } + + if (!handler) { +#ifdef QIMAGEREADER_DEBUG + qDebug() << "QImageReader::createReadHandler: no handlers found. giving up."; +#endif + // no handler: give up. + return 0; + } + + handler->setDevice(device); + if (!form.isEmpty()) + handler->setFormat(form); + return handler; +} + +class QImageReaderPrivate +{ +public: + QImageReaderPrivate(QImageReader *qq); + ~QImageReaderPrivate(); + + // device + QByteArray format; + bool autoDetectImageFormat; + bool ignoresFormatAndExtension; + QIODevice *device; + bool deleteDevice; + QImageIOHandler *handler; + bool initHandler(); + + // image options + QRect clipRect; + QSize scaledSize; + QRect scaledClipRect; + int quality; + QMap text; + void getText(); + + // error + QImageReader::ImageReaderError imageReaderError; + QString errorString; + + QImageReader *q; +}; + +/*! + \internal +*/ +QImageReaderPrivate::QImageReaderPrivate(QImageReader *qq) + : autoDetectImageFormat(true), ignoresFormatAndExtension(false) +{ + device = 0; + deleteDevice = false; + handler = 0; + quality = -1; + imageReaderError = QImageReader::UnknownError; + + q = qq; +} + +/*! + \internal +*/ +QImageReaderPrivate::~QImageReaderPrivate() +{ + if (deleteDevice) + delete device; + delete handler; +} + +/*! + \internal +*/ +bool QImageReaderPrivate::initHandler() +{ + // check some preconditions + if (!device || (!deleteDevice && !device->isOpen() && !device->open(QIODevice::ReadOnly))) { + imageReaderError = QImageReader::DeviceError; + errorString = QLatin1String(QT_TRANSLATE_NOOP(QImageReader, "Invalid device")); + return false; + } + + // probe the file extension + if (deleteDevice && !device->isOpen() && !device->open(QIODevice::ReadOnly) && autoDetectImageFormat) { + QList extensions = QImageReader::supportedImageFormats(); + if (!format.isEmpty()) { + // Try the most probable extension first + int currentFormatIndex = extensions.indexOf(format.toLower()); + if (currentFormatIndex > 0) + extensions.swap(0, currentFormatIndex); + } + + int currentExtension = 0; + + QFile *file = static_cast(device); + QString fileName = file->fileName(); + + do { + file->setFileName(fileName + QLatin1Char('.') + + QString::fromLatin1(extensions.at(currentExtension++).constData())); + file->open(QIODevice::ReadOnly); + } while (!file->isOpen() && currentExtension < extensions.size()); + + if (!device->isOpen()) { + imageReaderError = QImageReader::FileNotFoundError; + errorString = QLatin1String(QT_TRANSLATE_NOOP(QImageReader, "File not found")); + file->setFileName(fileName); // restore the old file name + return false; + } + } + + // assign a handler + if (!handler && (handler = createReadHandlerHelper(device, format, autoDetectImageFormat, ignoresFormatAndExtension)) == 0) { + imageReaderError = QImageReader::UnsupportedFormatError; + errorString = QLatin1String(QT_TRANSLATE_NOOP(QImageReader, "Unsupported image format")); + return false; + } + return true; +} + +/*! + \internal +*/ +void QImageReaderPrivate::getText() +{ + if (!text.isEmpty() || (!handler && !initHandler()) || !handler->supportsOption(QImageIOHandler::Description)) + return; + foreach (QString pair, handler->option(QImageIOHandler::Description).toString().split( + QLatin1String("\n\n"))) { + int index = pair.indexOf(QLatin1Char(':')); + if (index >= 0 && pair.indexOf(QLatin1Char(' ')) < index) { + text.insert(QLatin1String("Description"), pair.simplified()); + } else { + QString key = pair.left(index); + text.insert(key, pair.mid(index + 2).simplified()); + } + } +} + +/*! + Constructs an empty QImageReader object. Before reading an image, + call setDevice() or setFileName(). +*/ +QImageReader::QImageReader() + : d(new QImageReaderPrivate(this)) +{ +} + +/*! + Constructs a QImageReader object with the device \a device and the + image format \a format. +*/ +QImageReader::QImageReader(QIODevice *device, const QByteArray &format) + : d(new QImageReaderPrivate(this)) +{ + d->device = device; + d->format = format; +} + +/*! + Constructs a QImageReader object with the file name \a fileName + and the image format \a format. + + \sa setFileName() +*/ +QImageReader::QImageReader(const QString &fileName, const QByteArray &format) + : d(new QImageReaderPrivate(this)) +{ + QFile *file = new QFile(fileName); + d->device = file; + d->deleteDevice = true; + d->format = format; +} + +/*! + Destructs the QImageReader object. +*/ +QImageReader::~QImageReader() +{ + delete d; +} + +/*! + Sets the format QImageReader will use when reading images, to \a + format. \a format is a case insensitive text string. Example: + + \snippet doc/src/snippets/code/src_gui_image_qimagereader.cpp 0 + + You can call supportedImageFormats() for the full list of formats + QImageReader supports. + + \sa format() +*/ +void QImageReader::setFormat(const QByteArray &format) +{ + d->format = format; +} + +/*! + Returns the format QImageReader uses for reading images. + + You can call this function after assigning a device to the + reader to determine the format of the device. For example: + + \snippet doc/src/snippets/code/src_gui_image_qimagereader.cpp 1 + + If the reader cannot read any image from the device (e.g., there is no + image there, or the image has already been read), or if the format is + unsupported, this function returns an empty QByteArray(). + + \sa setFormat(), supportedImageFormats() +*/ +QByteArray QImageReader::format() const +{ + if (d->format.isEmpty()) { + if (!d->initHandler()) + return QByteArray(); + return d->handler->canRead() ? d->handler->format() : QByteArray(); + } + + return d->format; +} + +/*! + If \a enabled is true, image format autodetection is enabled; otherwise, + it is disabled. By default, autodetection is enabled. + + QImageReader uses an extensive approach to detecting the image format; + firstly, if you pass a file name to QImageReader, it will attempt to + detect the file extension if the given file name does not point to an + existing file, by appending supported default extensions to the given file + name, one at a time. It then uses the following approach to detect the + image format: + + \list + + \o Image plugins are queried first, based on either the optional format + string, or the file name suffix (if the source device is a file). No + content detection is done at this stage. QImageReader will choose the + first plugin that supports reading for this format. + + \o If no plugin supports the image format, Qt's built-in handlers are + checked based on either the optional format string, or the file name + suffix. + + \o If no capable plugins or built-in handlers are found, each plugin is + tested by inspecting the content of the data stream. + + \o If no plugins could detect the image format based on data contents, + each built-in image handler is tested by inspecting the contents. + + \o Finally, if all above approaches fail, QImageReader will report failure + when trying to read the image. + + \endlist + + By disabling image format autodetection, QImageReader will only query the + plugins and built-in handlers based on the format string (i.e., no file + name extensions are tested). + + \sa QImageIOHandler::canRead(), QImageIOPlugin::capabilities() +*/ +void QImageReader::setAutoDetectImageFormat(bool enabled) +{ + d->autoDetectImageFormat = enabled; +} + +/*! + Returns true if image format autodetection is enabled on this image + reader; otherwise returns false. By default, autodetection is enabled. + + \sa setAutoDetectImageFormat() +*/ +bool QImageReader::autoDetectImageFormat() const +{ + return d->autoDetectImageFormat; +} + + +/*! + If \a ignored is set to true, then the image reader will ignore + specified formats or file extensions and decide which plugin to + use only based on the contents in the datastream. + + Setting this flag means that all image plugins gets loaded. Each + plugin will read the first bytes in the image data and decide if + the plugin is compatible or not. + + This also disables auto detecting the image format. + + \sa decideFormatFromContent() +*/ + +void QImageReader::setDecideFormatFromContent(bool ignored) +{ + d->ignoresFormatAndExtension = ignored; +} + + +/*! + Returns whether the image reader should decide which plugin to use + only based on the contents of the datastream rather than on the file + extension. + + \sa setDecideFormatFromContent() +*/ + +bool QImageReader::decideFormatFromContent() const +{ + return d->ignoresFormatAndExtension; +} + + +/*! + Sets QImageReader's device to \a device. If a device has already + been set, the old device is removed from QImageReader and is + otherwise left unchanged. + + If the device is not already open, QImageReader will attempt to + open the device in \l QIODevice::ReadOnly mode by calling + open(). Note that this does not work for certain devices, such as + QProcess, QTcpSocket and QUdpSocket, where more logic is required + to open the device. + + \sa device(), setFileName() +*/ +void QImageReader::setDevice(QIODevice *device) +{ + if (d->device && d->deleteDevice) + delete d->device; + d->device = device; + d->deleteDevice = false; + delete d->handler; + d->handler = 0; + d->text.clear(); +} + +/*! + Returns the device currently assigned to QImageReader, or 0 if no + device has been assigned. +*/ +QIODevice *QImageReader::device() const +{ + return d->device; +} + +/*! + Sets the file name of QImageReader to \a fileName. Internally, + QImageReader will create a QFile object and open it in \l + QIODevice::ReadOnly mode, and use this when reading images. + + If \a fileName does not include a file extension (e.g., .png or .bmp), + QImageReader will cycle through all supported extensions until it finds + a matching file. + + \sa fileName(), setDevice(), supportedImageFormats() +*/ +void QImageReader::setFileName(const QString &fileName) +{ + setDevice(new QFile(fileName)); + d->deleteDevice = true; +} + +/*! + If the currently assigned device is a QFile, or if setFileName() + has been called, this function returns the name of the file + QImageReader reads from. Otherwise (i.e., if no device has been + assigned or the device is not a QFile), an empty QString is + returned. + + \sa setFileName(), setDevice() +*/ +QString QImageReader::fileName() const +{ + QFile *file = qobject_cast(d->device); + return file ? file->fileName() : QString(); +} + +/*! + \since 4.2 + + This is an image format specific function that sets the quality + level of the image to \a quality. For image formats that do not + support setting the quality, this value is ignored. + + The value range of \a quality depends on the image format. For + example, the "jpeg" format supports a quality range from 0 (low + quality, high compression) to 100 (high quality, low compression). + + \sa quality() +*/ +void QImageReader::setQuality(int quality) +{ + d->quality = quality; +} + +/*! + \since 4.2 + + Returns the quality level of the image. + + \sa setQuality() +*/ +int QImageReader::quality() const +{ + return d->quality; +} + + +/*! + Returns the size of the image, without actually reading the image + contents. + + If the image format does not support this feature, this function returns + an invalid size. Qt's built-in image handlers all support this feature, + but custom image format plugins are not required to do so. + + \sa QImageIOHandler::ImageOption, QImageIOHandler::option(), QImageIOHandler::supportsOption() +*/ +QSize QImageReader::size() const +{ + if (!d->initHandler()) + return QSize(); + + if (d->handler->supportsOption(QImageIOHandler::Size)) + return d->handler->option(QImageIOHandler::Size).toSize(); + + return QSize(); +} + +/*! + \since 4.5 + + Returns the format of the image, without actually reading the image + contents. The format describes the image format \l QImageReader::read() + returns, not the format of the actual image. + + If the image format does not support this feature, this function returns + an invalid format. + + \sa QImageIOHandler::ImageOption, QImageIOHandler::option(), QImageIOHandler::supportsOption() +*/ +QImage::Format QImageReader::imageFormat() const +{ + if (!d->initHandler()) + return QImage::Format_Invalid; + + if (d->handler->supportsOption(QImageIOHandler::ImageFormat)) + return (QImage::Format)d->handler->option(QImageIOHandler::ImageFormat).toInt(); + + return QImage::Format_Invalid; +} + +/*! + \since 4.1 + + Returns the text keys for this image. You can use + these keys with text() to list the image text for + a certain key. + + Support for this option is implemented through + QImageIOHandler::Description. + + \sa text(), QImageWriter::setText(), QImage::textKeys() +*/ +QStringList QImageReader::textKeys() const +{ + d->getText(); + return d->text.keys(); +} + +/*! + \since 4.1 + + Returns the image text associated with \a key. + + Support for this option is implemented through + QImageIOHandler::Description. + + \sa textKeys(), QImageWriter::setText() +*/ +QString QImageReader::text(const QString &key) const +{ + d->getText(); + return d->text.value(key); +} + +/*! + Sets the image clip rect (also known as the ROI, or Region Of + Interest) to \a rect. The coordinates of \a rect are relative to + the untransformed image size, as returned by size(). + + \sa clipRect(), setScaledSize(), setScaledClipRect() +*/ +void QImageReader::setClipRect(const QRect &rect) +{ + d->clipRect = rect; +} + +/*! + Returns the clip rect (also known as the ROI, or Region Of + Interest) of the image. If no clip rect has been set, an invalid + QRect is returned. + + \sa setClipRect() +*/ +QRect QImageReader::clipRect() const +{ + return d->clipRect; +} + +/*! + Sets the scaled size of the image to \a size. The scaling is + performed after the initial clip rect, but before the scaled clip + rect is applied. The algorithm used for scaling depends on the + image format. By default (i.e., if the image format does not + support scaling), QImageReader will use QImage::scale() with + Qt::SmoothScaling. + + \sa scaledSize(), setClipRect(), setScaledClipRect() +*/ +void QImageReader::setScaledSize(const QSize &size) +{ + d->scaledSize = size; +} + +/*! + Returns the scaled size of the image. + + \sa setScaledSize() +*/ +QSize QImageReader::scaledSize() const +{ + return d->scaledSize; +} + +/*! + Sets the scaled clip rect to \a rect. The scaled clip rect is the + clip rect (also known as ROI, or Region Of Interest) that is + applied after the image has been scaled. + + \sa scaledClipRect(), setScaledSize() +*/ +void QImageReader::setScaledClipRect(const QRect &rect) +{ + d->scaledClipRect = rect; +} + +/*! + Returns the scaled clip rect of the image. + + \sa setScaledClipRect() +*/ +QRect QImageReader::scaledClipRect() const +{ + return d->scaledClipRect; +} + +/*! + \since 4.1 + + Sets the background color to \a color. + Image formats that support this operation are expected to + initialize the background to \a color before reading an image. + + \sa backgroundColor(), read() +*/ +void QImageReader::setBackgroundColor(const QColor &color) +{ + if (!d->initHandler()) + return; + if (d->handler->supportsOption(QImageIOHandler::BackgroundColor)) + d->handler->setOption(QImageIOHandler::BackgroundColor, color); +} + +/*! + \since 4.1 + + Returns the background color that's used when reading an image. + If the image format does not support setting the background color + an invalid color is returned. + + \sa setBackgroundColor(), read() +*/ +QColor QImageReader::backgroundColor() const +{ + if (!d->initHandler()) + return QColor(); + if (d->handler->supportsOption(QImageIOHandler::BackgroundColor)) + return qvariant_cast(d->handler->option(QImageIOHandler::BackgroundColor)); + return QColor(); +} + +/*! + \since 4.1 + + Returns true if the image format supports animation; + otherwise, false is returned. + + \sa QMovie::supportedFormats() +*/ +bool QImageReader::supportsAnimation() const +{ + if (!d->initHandler()) + return false; + if (d->handler->supportsOption(QImageIOHandler::Animation)) + return d->handler->option(QImageIOHandler::Animation).toBool(); + return false; +} + +/*! + Returns true if an image can be read for the device (i.e., the + image format is supported, and the device seems to contain valid + data); otherwise returns false. + + canRead() is a lightweight function that only does a quick test to + see if the image data is valid. read() may still return false + after canRead() returns true, if the image data is corrupt. + + For images that support animation, canRead() returns false when + all frames have been read. + + \sa read(), supportedImageFormats() +*/ +bool QImageReader::canRead() const +{ + if (!d->initHandler()) + return false; + + return d->handler->canRead(); +} + +/*! + Reads an image from the device. On success, the image that was + read is returned; otherwise, a null QImage is returned. You can + then call error() to find the type of error that occurred, or + errorString() to get a human readable description of the error. + + For image formats that support animation, calling read() + repeatedly will return the next frame. When all frames have been + read, a null image will be returned. + + \sa canRead(), supportedImageFormats(), supportsAnimation(), QMovie +*/ +QImage QImageReader::read() +{ + // Because failed image reading might have side effects, we explicitly + // return a null image instead of the image we've just created. + QImage image; + return read(&image) ? image : QImage(); +} + +/*! + \overload + + Reads an image from the device into \a image, which must point to a + QImage. Returns true on success; otherwise, returns false. + + If \a image has same format and size as the image data that is about to be + read, this function may not need to allocate a new image before + reading. Because of this, it can be faster than the other read() overload, + which always constructs a new image; especially when reading several + images with the same format and size. + + \snippet doc/src/snippets/code/src_gui_image_qimagereader.cpp 2 + + For image formats that support animation, calling read() repeatedly will + return the next frame. When all frames have been read, a null image will + be returned. + + \sa canRead(), supportedImageFormats(), supportsAnimation(), QMovie +*/ +bool QImageReader::read(QImage *image) +{ + if (!image) { + qWarning("QImageReader::read: cannot read into null pointer"); + return false; + } + + if (!d->handler && !d->initHandler()) + return false; + + // set the handler specific options. + if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) { + if ((d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) + || d->clipRect.isNull()) { + // Only enable the ScaledSize option if there is no clip rect, or + // if the handler also supports ClipRect. + d->handler->setOption(QImageIOHandler::ScaledSize, d->scaledSize); + } + } + if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) + d->handler->setOption(QImageIOHandler::ClipRect, d->clipRect); + if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) + d->handler->setOption(QImageIOHandler::ScaledClipRect, d->scaledClipRect); + if (d->handler->supportsOption(QImageIOHandler::Quality)) + d->handler->setOption(QImageIOHandler::Quality, d->quality); + + // read the image + if (!d->handler->read(image)) { + d->imageReaderError = InvalidDataError; + d->errorString = QLatin1String(QT_TRANSLATE_NOOP(QImageReader, "Unable to read image data")); + return false; + } + + // provide default implementations for any unsupported image + // options + if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) { + if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) { + if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + // all features are supported by the handler; nothing to do. + } else { + // the image is already scaled, so apply scaled clipping. + if (!d->scaledClipRect.isNull()) + *image = image->copy(d->scaledClipRect); + } + } else { + if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + // supports scaled clipping but not scaling, most + // likely a broken handler. + } else { + if (d->scaledSize.isValid()) { + *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + if (d->scaledClipRect.isValid()) { + *image = image->copy(d->scaledClipRect); + } + } + } + } else { + if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) { + // in this case, there's nothing we can do. if the + // plugin supports scaled size but not ClipRect, then + // we have to ignore ClipRect." + + if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + // nothing to do (ClipRect is ignored!) + } else { + // provide all workarounds. + if (d->scaledClipRect.isValid()) { + *image = image->copy(d->scaledClipRect); + } + } + } else { + if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) { + // this makes no sense; a handler that supports + // ScaledClipRect but not ScaledSize is broken, and we + // can't work around it. + } else { + // provide all workarounds. + if (d->clipRect.isValid()) + *image = image->copy(d->clipRect); + if (d->scaledSize.isValid()) + *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (d->scaledClipRect.isValid()) + *image = image->copy(d->scaledClipRect); + } + } + } + + return true; +} + +/*! + For image formats that support animation, this function steps over the + current image, returning true if successful or false if there is no + following image in the animation. + + The default implementation calls read(), then discards the resulting + image, but the image handler may have a more efficient way of implementing + this operation. + + \sa jumpToImage(), QImageIOHandler::jumpToNextImage() +*/ +bool QImageReader::jumpToNextImage() +{ + if (!d->initHandler()) + return false; + return d->handler->jumpToNextImage(); +} + +/*! + For image formats that support animation, this function skips to the image + whose sequence number is \a imageNumber, returning true if successful + or false if the corresponding image cannot be found. + + The next call to read() will attempt to read this image. + + \sa jumpToNextImage(), QImageIOHandler::jumpToImage() +*/ +bool QImageReader::jumpToImage(int imageNumber) +{ + if (!d->initHandler()) + return false; + return d->handler->jumpToImage(imageNumber); +} + +/*! + For image formats that support animation, this function returns the number + of times the animation should loop. If this function returns -1, it can + either mean the animation should loop forever, or that an error occurred. + If an error occurred, canRead() will return false. + + \sa supportsAnimation(), QImageIOHandler::loopCount(), canRead() +*/ +int QImageReader::loopCount() const +{ + if (!d->initHandler()) + return -1; + return d->handler->loopCount(); +} + +/*! + For image formats that support animation, this function returns the total + number of images in the animation. If the format does not support + animation, 0 is returned. + + This function returns -1 if an error occurred. + + \sa supportsAnimation(), QImageIOHandler::imageCount(), canRead() +*/ +int QImageReader::imageCount() const +{ + if (!d->initHandler()) + return -1; + return d->handler->imageCount(); +} + +/*! + For image formats that support animation, this function returns the number + of milliseconds to wait until displaying the next frame in the animation. + If the image format doesn't support animation, 0 is returned. + + This function returns -1 if an error occurred. + + \sa supportsAnimation(), QImageIOHandler::nextImageDelay(), canRead() +*/ +int QImageReader::nextImageDelay() const +{ + if (!d->initHandler()) + return -1; + return d->handler->nextImageDelay(); +} + +/*! + For image formats that support animation, this function returns the + sequence number of the current frame. If the image format doesn't support + animation, 0 is returned. + + This function returns -1 if an error occurred. + + \sa supportsAnimation(), QImageIOHandler::currentImageNumber(), canRead() +*/ +int QImageReader::currentImageNumber() const +{ + if (!d->initHandler()) + return -1; + return d->handler->currentImageNumber(); +} + +/*! + For image formats that support animation, this function returns + the rect for the current frame. Otherwise, a null rect is returned. + + \sa supportsAnimation(), QImageIOHandler::currentImageRect() +*/ +QRect QImageReader::currentImageRect() const +{ + if (!d->initHandler()) + return QRect(); + return d->handler->currentImageRect(); +} + +/*! + Returns the type of error that occurred last. + + \sa ImageReaderError, errorString() +*/ +QImageReader::ImageReaderError QImageReader::error() const +{ + return d->imageReaderError; +} + +/*! + Returns a human readable description of the last error that + occurred. + + \sa error() +*/ +QString QImageReader::errorString() const +{ + if (d->errorString.isEmpty()) + return QLatin1String(QT_TRANSLATE_NOOP(QImageReader, "Unknown error")); + return d->errorString; +} + +/*! + \since 4.2 + + Returns true if the reader supports \a option; otherwise returns + false. + + Different image formats support different options. Call this function to + determine whether a certain option is supported by the current format. For + example, the PNG format allows you to embed text into the image's metadata + (see text()), and the BMP format allows you to determine the image's size + without loading the whole image into memory (see size()). + + \snippet doc/src/snippets/code/src_gui_image_qimagereader.cpp 3 + + \sa QImageWriter::supportsOption() +*/ +bool QImageReader::supportsOption(QImageIOHandler::ImageOption option) const +{ + if (!d->initHandler()) + return false; + return d->handler->supportsOption(option); +} + +/*! + If supported, this function returns the image format of the file + \a fileName. Otherwise, an empty string is returned. +*/ +QByteArray QImageReader::imageFormat(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) + return QByteArray(); + + return imageFormat(&file); +} + +/*! + If supported, this function returns the image format of the device + \a device. Otherwise, an empty string is returned. + + \sa QImageReader::autoDetectImageFormat() +*/ +QByteArray QImageReader::imageFormat(QIODevice *device) +{ + QByteArray format; + QImageIOHandler *handler = createReadHandlerHelper(device, format, /* autoDetectImageFormat = */ true, false); + if (handler) { + if (handler->canRead()) + format = handler->format(); + delete handler; + } + return format; +} + +/*! + Returns the list of image formats supported by QImageReader. + + By default, Qt can read the following formats: + + \table + \header \o Format \o Description + \row \o BMP \o Windows Bitmap + \row \o GIF \o Graphic Interchange Format (optional) + \row \o JPG \o Joint Photographic Experts Group + \row \o JPEG \o Joint Photographic Experts Group + \row \o MNG \o Multiple-image Network Graphics + \row \o PNG \o Portable Network Graphics + \row \o PBM \o Portable Bitmap + \row \o PGM \o Portable Graymap + \row \o PPM \o Portable Pixmap + \row \o TIFF \o Tagged Image File Format + \row \o XBM \o X11 Bitmap + \row \o XPM \o X11 Pixmap + \row \o SVG \o Scalable Vector Graphics + \endtable + + Reading and writing SVG files is supported through Qt's + \l{QtSvg Module}{SVG Module}. + + To configure Qt with GIF support, pass \c -qt-gif to the \c + configure script or check the appropriate option in the graphical + installer. + + Note that the QApplication instance must be created before this function is + called. + + \sa setFormat(), QImageWriter::supportedImageFormats(), QImageIOPlugin +*/ +QList QImageReader::supportedImageFormats() +{ + QSet formats; + for (int i = 0; i < _qt_NumFormats; ++i) + formats << _qt_BuiltInFormats[i].extension; + +#ifndef QT_NO_LIBRARY + QFactoryLoader *l = loader(); + QStringList keys = l->keys(); + + for (int i = 0; i < keys.count(); ++i) { + QImageIOPlugin *plugin = qobject_cast(l->instance(keys.at(i))); + if (plugin && plugin->capabilities(0, keys.at(i).toLatin1()) & QImageIOPlugin::CanRead) + formats << keys.at(i).toLatin1(); + } +#endif // QT_NO_LIBRARY + + QList sortedFormats; + for (QSet::ConstIterator it = formats.constBegin(); it != formats.constEnd(); ++it) + sortedFormats << *it; + + qSort(sortedFormats); + return sortedFormats; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qimagereader.h b/src/gui/image/qimagereader.h new file mode 100644 index 0000000000..cdac4555ca --- /dev/null +++ b/src/gui/image/qimagereader.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGEREADER_H +#define QIMAGEREADER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QColor; +class QIODevice; +class QRect; +class QSize; +class QStringList; + +class QImageReaderPrivate; +class Q_GUI_EXPORT QImageReader +{ +public: + enum ImageReaderError { + UnknownError, + FileNotFoundError, + DeviceError, + UnsupportedFormatError, + InvalidDataError + }; + + QImageReader(); + explicit QImageReader(QIODevice *device, const QByteArray &format = QByteArray()); + explicit QImageReader(const QString &fileName, const QByteArray &format = QByteArray()); + ~QImageReader(); + + void setFormat(const QByteArray &format); + QByteArray format() const; + + void setAutoDetectImageFormat(bool enabled); + bool autoDetectImageFormat() const; + + void setDecideFormatFromContent(bool ignored); + bool decideFormatFromContent() const; + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void setFileName(const QString &fileName); + QString fileName() const; + + QSize size() const; + + QImage::Format imageFormat() const; + + QStringList textKeys() const; + QString text(const QString &key) const; + + void setClipRect(const QRect &rect); + QRect clipRect() const; + + void setScaledSize(const QSize &size); + QSize scaledSize() const; + + void setQuality(int quality); + int quality() const; + + void setScaledClipRect(const QRect &rect); + QRect scaledClipRect() const; + + void setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + + bool supportsAnimation() const; + + bool canRead() const; + QImage read(); + bool read(QImage *image); + + bool jumpToNextImage(); + bool jumpToImage(int imageNumber); + int loopCount() const; + int imageCount() const; + int nextImageDelay() const; + int currentImageNumber() const; + QRect currentImageRect() const; + + ImageReaderError error() const; + QString errorString() const; + + bool supportsOption(QImageIOHandler::ImageOption option) const; + + static QByteArray imageFormat(const QString &fileName); + static QByteArray imageFormat(QIODevice *device); + static QList supportedImageFormats(); + +private: + Q_DISABLE_COPY(QImageReader) + QImageReaderPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QIMAGEREADER_H diff --git a/src/gui/image/qimagewriter.cpp b/src/gui/image/qimagewriter.cpp new file mode 100644 index 0000000000..504260aa47 --- /dev/null +++ b/src/gui/image/qimagewriter.cpp @@ -0,0 +1,734 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QImageWriter + \brief The QImageWriter class provides a format independent interface + for writing images to files or other devices. + + \reentrant + \ingroup painting + \ingroup io + + QImageWriter supports setting format specific options, such as the + gamma level, compression level and quality, prior to storing the + image. If you do not need such options, you can use QImage::save() + or QPixmap::save() instead. + + To store an image, you start by constructing a QImageWriter + object. Pass either a file name or a device pointer, and the + image format to QImageWriter's constructor. You can then set + several options, such as the gamma level (by calling setGamma()) + and quality (by calling setQuality()). canWrite() returns true if + QImageWriter can write the image (i.e., the image format is + supported and the device is open for writing). Call write() to + write the image to the device. + + If any error occurs when writing the image, write() will return + false. You can then call error() to find the type of error that + occurred, or errorString() to get a human readable description of + what went wrong. + + Call supportedImageFormats() for a list of formats that + QImageWriter can write. QImageWriter supports all built-in image + formats, in addition to any image format plugins that support + writing. + + \sa QImageReader, QImageIOHandler, QImageIOPlugin +*/ + +/*! + \enum QImageWriter::ImageWriterError + + This enum describes errors that can occur when writing images with + QImageWriter. + + \value DeviceError QImageWriter encountered a device error when + writing the image data. Consult your device for more details on + what went wrong. + + \value UnsupportedFormatError Qt does not support the requested + image format. + + \value UnknownError An unknown error occurred. If you get this + value after calling write(), it is most likely caused by a bug in + QImageWriter. +*/ + +#include "qimagewriter.h" + +#include +#include +#include +#include +#include +#include + +// factory loader +#include +#include + +// image handlers +#include +#include +#include +#include +#ifndef QT_NO_IMAGEFORMAT_PNG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG +#include +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF +#include +#endif +#ifdef QT_BUILTIN_GIF_READER +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QImageIOHandlerFactoryInterface_iid, QLatin1String("/imageformats"))) +#endif + +static QImageIOHandler *createWriteHandlerHelper(QIODevice *device, + const QByteArray &format) +{ + QByteArray form = format.toLower(); + QByteArray suffix; + QImageIOHandler *handler = 0; + +#ifndef QT_NO_LIBRARY + // check if any plugins can write the image + QFactoryLoader *l = loader(); + QStringList keys = l->keys(); + int suffixPluginIndex = -1; +#endif + + if (device && format.isEmpty()) { + // if there's no format, see if \a device is a file, and if so, find + // the file suffix and find support for that format among our plugins. + // this allows plugins to override our built-in handlers. + if (QFile *file = qobject_cast(device)) { + if (!(suffix = QFileInfo(file->fileName()).suffix().toLower().toLatin1()).isEmpty()) { +#ifndef QT_NO_LIBRARY + int index = keys.indexOf(QString::fromLatin1(suffix)); + if (index != -1) + suffixPluginIndex = index; +#endif + } + } + } + + QByteArray testFormat = !form.isEmpty() ? form : suffix; + +#ifndef QT_NO_LIBRARY + if (suffixPluginIndex != -1) { + // when format is missing, check if we can find a plugin for the + // suffix. + QImageIOPlugin *plugin = qobject_cast(l->instance(QString::fromLatin1(suffix))); + if (plugin && (plugin->capabilities(device, suffix) & QImageIOPlugin::CanWrite)) + handler = plugin->create(device, suffix); + } +#endif // QT_NO_LIBRARY + + // check if any built-in handlers can write the image + if (!handler && !testFormat.isEmpty()) { + if (false) { +#ifndef QT_NO_IMAGEFORMAT_PNG + } else if (testFormat == "png") { + handler = new QPngHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + } else if (testFormat == "jpg" || testFormat == "jpeg") { + handler = new QJpegHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + } else if (testFormat == "mng") { + handler = new QMngHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + } else if (testFormat == "tif" || testFormat == "tiff") { + handler = new QTiffHandler; +#endif +#ifdef QT_BUILTIN_GIF_READER + } else if (testFormat == "gif") { + handler = new QGifHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_BMP + } else if (testFormat == "bmp") { + handler = new QBmpHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + } else if (testFormat == "xpm") { + handler = new QXpmHandler; +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + } else if (testFormat == "xbm") { + handler = new QXbmHandler; + handler->setOption(QImageIOHandler::SubType, testFormat); +#endif +#ifndef QT_NO_IMAGEFORMAT_PPM + } else if (testFormat == "pbm" || testFormat == "pbmraw" || testFormat == "pgm" + || testFormat == "pgmraw" || testFormat == "ppm" || testFormat == "ppmraw") { + handler = new QPpmHandler; + handler->setOption(QImageIOHandler::SubType, testFormat); +#endif + } + } + +#ifndef QT_NO_LIBRARY + if (!testFormat.isEmpty()) { + for (int i = 0; i < keys.size(); ++i) { + QImageIOPlugin *plugin = qobject_cast(l->instance(keys.at(i))); + if (plugin && (plugin->capabilities(device, testFormat) & QImageIOPlugin::CanWrite)) { + delete handler; + handler = plugin->create(device, testFormat); + break; + } + } + } +#endif // QT_NO_LIBRARY + + if (!handler) + return 0; + + handler->setDevice(device); + if (!testFormat.isEmpty()) + handler->setFormat(testFormat); + return handler; +} + +class QImageWriterPrivate +{ +public: + QImageWriterPrivate(QImageWriter *qq); + + // device + QByteArray format; + QIODevice *device; + bool deleteDevice; + QImageIOHandler *handler; + + // image options + int quality; + int compression; + float gamma; + QString description; + QString text; + + // error + QImageWriter::ImageWriterError imageWriterError; + QString errorString; + + QImageWriter *q; +}; + +/*! + \internal +*/ +QImageWriterPrivate::QImageWriterPrivate(QImageWriter *qq) +{ + device = 0; + deleteDevice = false; + handler = 0; + quality = -1; + compression = 0; + gamma = 0.0; + imageWriterError = QImageWriter::UnknownError; + errorString = QT_TRANSLATE_NOOP(QImageWriter, QLatin1String("Unknown error")); + + q = qq; +} + +/*! + Constructs an empty QImageWriter object. Before writing, you must + call setFormat() to set an image format, then setDevice() or + setFileName(). +*/ +QImageWriter::QImageWriter() + : d(new QImageWriterPrivate(this)) +{ +} + +/*! + Constructs a QImageWriter object using the device \a device and + image format \a format. +*/ +QImageWriter::QImageWriter(QIODevice *device, const QByteArray &format) + : d(new QImageWriterPrivate(this)) +{ + d->device = device; + d->format = format; +} + +/*! + Constructs a QImageWriter objects that will write to a file with + the name \a fileName, using the image format \a format. If \a + format is not provided, QImageWriter will detect the image format + by inspecting the extension of \a fileName. +*/ +QImageWriter::QImageWriter(const QString &fileName, const QByteArray &format) + : d(new QImageWriterPrivate(this)) +{ + QFile *file = new QFile(fileName); + d->device = file; + d->deleteDevice = true; + d->format = format; +} + +/*! + Destructs the QImageWriter object. +*/ +QImageWriter::~QImageWriter() +{ + if (d->deleteDevice) + delete d->device; + delete d->handler; + delete d; +} + +/*! + Sets the format QImageWriter will use when writing images, to \a + format. \a format is a case insensitive text string. Example: + + \snippet doc/src/snippets/code/src_gui_image_qimagewriter.cpp 0 + + You can call supportedImageFormats() for the full list of formats + QImageWriter supports. + + \sa format() +*/ +void QImageWriter::setFormat(const QByteArray &format) +{ + d->format = format; +} + +/*! + Returns the format QImageWriter uses for writing images. + + \sa setFormat() +*/ +QByteArray QImageWriter::format() const +{ + return d->format; +} + +/*! + Sets QImageWriter's device to \a device. If a device has already + been set, the old device is removed from QImageWriter and is + otherwise left unchanged. + + If the device is not already open, QImageWriter will attempt to + open the device in \l QIODevice::WriteOnly mode by calling + open(). Note that this does not work for certain devices, such as + QProcess, QTcpSocket and QUdpSocket, where more logic is required + to open the device. + + \sa device(), setFileName() +*/ +void QImageWriter::setDevice(QIODevice *device) +{ + if (d->device && d->deleteDevice) + delete d->device; + + d->device = device; + d->deleteDevice = false; + delete d->handler; + d->handler = 0; +} + +/*! + Returns the device currently assigned to QImageWriter, or 0 if no + device has been assigned. +*/ +QIODevice *QImageWriter::device() const +{ + return d->device; +} + +/*! + Sets the file name of QImageWriter to \a fileName. Internally, + QImageWriter will create a QFile and open it in \l + QIODevice::WriteOnly mode, and use this file when writing images. + + \sa fileName(), setDevice() +*/ +void QImageWriter::setFileName(const QString &fileName) +{ + setDevice(new QFile(fileName)); + d->deleteDevice = true; +} + +/*! + If the currently assigned device is a QFile, or if setFileName() + has been called, this function returns the name of the file + QImageWriter writes to. Otherwise (i.e., if no device has been + assigned or the device is not a QFile), an empty QString is + returned. + + \sa setFileName(), setDevice() +*/ +QString QImageWriter::fileName() const +{ + QFile *file = qobject_cast(d->device); + return file ? file->fileName() : QString(); +} + +/*! + This is an image format specific function that sets the quality + level of the image to \a quality. For image formats that do not + support setting the quality, this value is ignored. + + The value range of \a quality depends on the image format. For + example, the "jpeg" format supports a quality range from 0 (low + quality, high compression) to 100 (high quality, low compression). + + \sa quality() +*/ +void QImageWriter::setQuality(int quality) +{ + d->quality = quality; +} + +/*! + Returns the quality level of the image. + + \sa setQuality() +*/ +int QImageWriter::quality() const +{ + return d->quality; +} + +/*! + This is an image format specific function that set the compression + of an image. For image formats that do not support setting the + compression, this value is ignored. + + The value range of \a compression depends on the image format. For + example, the "tiff" format supports two values, 0(no compression) and + 1(LZW-compression). + + \sa compression() +*/ +void QImageWriter::setCompression(int compression) +{ + d->compression = compression; +} + +/*! + Returns the compression of the image. + + \sa setCompression() +*/ +int QImageWriter::compression() const +{ + return d->compression; +} + +/*! + This is an image format specific function that sets the gamma + level of the image to \a gamma. For image formats that do not + support setting the gamma level, this value is ignored. + + The value range of \a gamma depends on the image format. For + example, the "png" format supports a gamma range from 0.0 to 1.0. + + \sa quality() +*/ +void QImageWriter::setGamma(float gamma) +{ + d->gamma = gamma; +} + +/*! + Returns the gamma level of the image. + + \sa setGamma() +*/ +float QImageWriter::gamma() const +{ + return d->gamma; +} + +/*! + \obsolete + + Use setText() instead. + + This is an image format specific function that sets the + description of the image to \a description. For image formats that + do not support setting the description, this value is ignored. + + The contents of \a description depends on the image format. + + \sa description() +*/ +void QImageWriter::setDescription(const QString &description) +{ + d->description = description; +} + +/*! + \obsolete + + Use QImageReader::text() instead. + + Returns the description of the image. + + \sa setDescription() +*/ +QString QImageWriter::description() const +{ + return d->description; +} + +/*! + \since 4.1 + + Sets the image text associated with the key \a key to + \a text. This is useful for storing copyright information + or other information about the image. Example: + + \snippet doc/src/snippets/code/src_gui_image_qimagewriter.cpp 1 + + If you want to store a single block of data + (e.g., a comment), you can pass an empty key, or use + a generic key like "Description". + + The key and text will be embedded into the + image data after calling write(). + + Support for this option is implemented through + QImageIOHandler::Description. + + \sa QImage::setText(), QImageReader::text() +*/ +void QImageWriter::setText(const QString &key, const QString &text) +{ + if (!d->description.isEmpty()) + d->description += QLatin1String("\n\n"); + d->description += key.simplified() + QLatin1String(": ") + text.simplified(); +} + +/*! + Returns true if QImageWriter can write the image; i.e., the image + format is supported and the assigned device is open for reading. + + \sa write(), setDevice(), setFormat() +*/ +bool QImageWriter::canWrite() const +{ + if (d->device && !d->handler && (d->handler = createWriteHandlerHelper(d->device, d->format)) == 0) { + d->imageWriterError = QImageWriter::UnsupportedFormatError; + d->errorString = QT_TRANSLATE_NOOP(QImageWriter, + QLatin1String("Unsupported image format")); + return false; + } + if (d->device && !d->device->isOpen()) + d->device->open(QIODevice::WriteOnly); + if (!d->device || !d->device->isWritable()) { + d->imageWriterError = QImageWriter::DeviceError; + d->errorString = QT_TRANSLATE_NOOP(QImageWriter, + QLatin1String("Device not writable")); + return false; + } + return true; +} + +/*! + Writes the image \a image to the assigned device or file + name. Returns true on success; otherwise returns false. If the + operation fails, you can call error() to find the type of error + that occurred, or errorString() to get a human readable + description of the error. + + \sa canWrite(), error(), errorString() +*/ +bool QImageWriter::write(const QImage &image) +{ + if (!canWrite()) + return false; + + if (d->handler->supportsOption(QImageIOHandler::Quality)) + d->handler->setOption(QImageIOHandler::Quality, d->quality); + if (d->handler->supportsOption(QImageIOHandler::CompressionRatio)) + d->handler->setOption(QImageIOHandler::CompressionRatio, d->compression); + if (d->handler->supportsOption(QImageIOHandler::Gamma)) + d->handler->setOption(QImageIOHandler::Gamma, d->gamma); + if (!d->description.isEmpty() && d->handler->supportsOption(QImageIOHandler::Description)) + d->handler->setOption(QImageIOHandler::Description, d->description); + + if (!d->handler->write(image)) + return false; + if (QFile *file = qobject_cast(d->device)) + file->flush(); + return true; +} + +/*! + Returns the type of error that last occurred. + + \sa ImageWriterError, errorString() +*/ +QImageWriter::ImageWriterError QImageWriter::error() const +{ + return d->imageWriterError; +} + +/*! + Returns a human readable description of the last error that occurred. + + \sa error() +*/ +QString QImageWriter::errorString() const +{ + return d->errorString; +} + +/*! + \since 4.2 + + Returns true if the writer supports \a option; otherwise returns + false. + + Different image formats support different options. Call this function to + determine whether a certain option is supported by the current format. For + example, the PNG format allows you to embed text into the image's metadata + (see text()). + + \snippet doc/src/snippets/code/src_gui_image_qimagewriter.cpp 2 + + Options can be tested after the writer has been associated with a format. + + \sa QImageReader::supportsOption(), setFormat() +*/ +bool QImageWriter::supportsOption(QImageIOHandler::ImageOption option) const +{ + if (!d->handler && (d->handler = createWriteHandlerHelper(d->device, d->format)) == 0) { + d->imageWriterError = QImageWriter::UnsupportedFormatError; + d->errorString = QT_TRANSLATE_NOOP(QImageWriter, + QLatin1String("Unsupported image format")); + return false; + } + + return d->handler->supportsOption(option); +} + +/*! + Returns the list of image formats supported by QImageWriter. + + By default, Qt can write the following formats: + + \table + \header \o Format \o Description + \row \o BMP \o Windows Bitmap + \row \o JPG \o Joint Photographic Experts Group + \row \o JPEG \o Joint Photographic Experts Group + \row \o PNG \o Portable Network Graphics + \row \o PPM \o Portable Pixmap + \row \o TIFF \o Tagged Image File Format + \row \o XBM \o X11 Bitmap + \row \o XPM \o X11 Pixmap + \endtable + + Reading and writing SVG files is supported through Qt's + \l{QtSvg Module}{SVG Module}. + + Note that the QApplication instance must be created before this function is + called. + + \sa setFormat(), QImageReader::supportedImageFormats(), QImageIOPlugin +*/ +QList QImageWriter::supportedImageFormats() +{ + QSet formats; + formats << "bmp"; +#ifndef QT_NO_IMAGEFORMAT_PPM + formats << "ppm"; +#endif +#ifndef QT_NO_IMAGEFORMAT_XBM + formats << "xbm"; +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + formats << "xpm"; +#endif +#ifndef QT_NO_IMAGEFORMAT_PNG + formats << "png"; +#endif +#ifndef QT_NO_IMAGEFORMAT_JPEG + formats << "jpg" << "jpeg"; +#endif +#ifndef QT_NO_IMAGEFORMAT_MNG + formats << "mng"; +#endif +#ifndef QT_NO_IMAGEFORMAT_TIFF + formats << "tif" << "tiff"; +#endif +#ifdef QT_BUILTIN_GIF_READER + formats << "gif"; +#endif + +#ifndef QT_NO_LIBRARY + QFactoryLoader *l = loader(); + QStringList keys = l->keys(); + for (int i = 0; i < keys.count(); ++i) { + QImageIOPlugin *plugin = qobject_cast(l->instance(keys.at(i))); + if (plugin && (plugin->capabilities(0, keys.at(i).toLatin1()) & QImageIOPlugin::CanWrite) != 0) + formats << keys.at(i).toLatin1(); + } +#endif // QT_NO_LIBRARY + + QList sortedFormats; + for (QSet::ConstIterator it = formats.constBegin(); it != formats.constEnd(); ++it) + sortedFormats << *it; + + qSort(sortedFormats); + return sortedFormats; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qimagewriter.h b/src/gui/image/qimagewriter.h new file mode 100644 index 0000000000..5e1b9bf677 --- /dev/null +++ b/src/gui/image/qimagewriter.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QIMAGEWRITER_H +#define QIMAGEWRITER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QIODevice; +class QImage; + +class QImageWriterPrivate; +class Q_GUI_EXPORT QImageWriter +{ +public: + enum ImageWriterError { + UnknownError, + DeviceError, + UnsupportedFormatError + }; + + QImageWriter(); + explicit QImageWriter(QIODevice *device, const QByteArray &format); + explicit QImageWriter(const QString &fileName, const QByteArray &format = QByteArray()); + ~QImageWriter(); + + void setFormat(const QByteArray &format); + QByteArray format() const; + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void setFileName(const QString &fileName); + QString fileName() const; + + void setQuality(int quality); + int quality() const; + + void setCompression(int compression); + int compression() const; + + void setGamma(float gamma); + float gamma() const; + + // Obsolete as of 4.1 + void setDescription(const QString &description); + QString description() const; + + void setText(const QString &key, const QString &text); + + bool canWrite() const; + bool write(const QImage &image); + + ImageWriterError error() const; + QString errorString() const; + + bool supportsOption(QImageIOHandler::ImageOption option) const; + + static QList supportedImageFormats(); + +private: + Q_DISABLE_COPY(QImageWriter) + QImageWriterPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QIMAGEWRITER_H diff --git a/src/gui/image/qjpeghandler.cpp b/src/gui/image/qjpeghandler.cpp new file mode 100644 index 0000000000..d57e0c1244 --- /dev/null +++ b/src/gui/image/qjpeghandler.cpp @@ -0,0 +1,915 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qjpeghandler_p.h" + +#include +#include +#include +#include +#include + +#include // jpeglib needs this to be pre-included +#include + +#ifdef FAR +#undef FAR +#endif + +// including jpeglib.h seems to be a little messy +extern "C" { +// mingw includes rpcndr.h but does not define boolean +#if defined(Q_OS_WIN) && defined(Q_CC_GNU) +# if defined(__RPCNDR_H__) && !defined(boolean) + typedef unsigned char boolean; +# define HAVE_BOOLEAN +# endif +#endif + +#define XMD_H // shut JPEGlib up +#if defined(Q_OS_UNIXWARE) +# define HAVE_BOOLEAN // libjpeg under Unixware seems to need this +#endif +#include +#ifdef const +# undef const // remove crazy C hackery in jconfig.h +#endif +} + +QT_BEGIN_NAMESPACE + +void QT_FASTCALL convert_rgb888_to_rgb32_C(quint32 *dst, const uchar *src, int len) +{ + // Expand 24->32 bpp. + for (int i = 0; i < len; ++i) { + *dst++ = qRgb(src[0], src[1], src[2]); + src += 3; + } +} + +typedef void (QT_FASTCALL *Rgb888ToRgb32Converter)(quint32 *dst, const uchar *src, int len); + +static Rgb888ToRgb32Converter rgb888ToRgb32ConverterPtr = convert_rgb888_to_rgb32_C; + +struct my_error_mgr : public jpeg_error_mgr { + jmp_buf setjmp_buffer; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static void my_error_exit (j_common_ptr cinfo) +{ + my_error_mgr* myerr = (my_error_mgr*) cinfo->err; + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + qWarning("%s", buffer); + longjmp(myerr->setjmp_buffer, 1); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + + +static const int max_buf = 4096; + +struct my_jpeg_source_mgr : public jpeg_source_mgr { + // Nothing dynamic - cannot rely on destruction over longjump + QIODevice *device; + JOCTET buffer[max_buf]; + const QBuffer *memDevice; + +public: + my_jpeg_source_mgr(QIODevice *device); +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static void qt_init_source(j_decompress_ptr) +{ +} + +static boolean qt_fill_input_buffer(j_decompress_ptr cinfo) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + qint64 num_read = 0; + if (src->memDevice) { + src->next_input_byte = (const JOCTET *)(src->memDevice->data().constData() + src->memDevice->pos()); + num_read = src->memDevice->data().size() - src->memDevice->pos(); + src->device->seek(src->memDevice->data().size()); + } else { + src->next_input_byte = src->buffer; + num_read = src->device->read((char*)src->buffer, max_buf); + } + if (num_read <= 0) { + // Insert a fake EOI marker - as per jpeglib recommendation + src->next_input_byte = src->buffer; + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + src->bytes_in_buffer = 2; + } else { + src->bytes_in_buffer = num_read; + } +#if defined(Q_OS_UNIXWARE) + return B_TRUE; +#else + return true; +#endif +} + +static void qt_skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + + // `dumb' implementation from jpeglib + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->bytes_in_buffer) { // Should not happen in case of memDevice + num_bytes -= (long) src->bytes_in_buffer; + (void) qt_fill_input_buffer(cinfo); + /* note we assume that qt_fill_input_buffer will never return false, + * so suspension need not be handled. + */ + } + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; + } +} + +static void qt_term_source(j_decompress_ptr cinfo) +{ + my_jpeg_source_mgr* src = (my_jpeg_source_mgr*)cinfo->src; + if (!src->device->isSequential()) + src->device->seek(src->device->pos() - src->bytes_in_buffer); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +inline my_jpeg_source_mgr::my_jpeg_source_mgr(QIODevice *device) +{ + jpeg_source_mgr::init_source = qt_init_source; + jpeg_source_mgr::fill_input_buffer = qt_fill_input_buffer; + jpeg_source_mgr::skip_input_data = qt_skip_input_data; + jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart; + jpeg_source_mgr::term_source = qt_term_source; + this->device = device; + memDevice = qobject_cast(device); + bytes_in_buffer = 0; + next_input_byte = buffer; +} + + +inline static bool read_jpeg_size(int &w, int &h, j_decompress_ptr cinfo) +{ + (void) jpeg_calc_output_dimensions(cinfo); + + w = cinfo->output_width; + h = cinfo->output_height; + return true; +} + +#define HIGH_QUALITY_THRESHOLD 50 + +inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cinfo) +{ + + bool result = true; + switch (cinfo->output_components) { + case 1: + format = QImage::Format_Indexed8; + break; + case 3: + case 4: + format = QImage::Format_RGB32; + break; + default: + result = false; + break; + } + cinfo->output_scanline = cinfo->output_height; + return result; +} + +static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, + const QSize& size) +{ + QImage::Format format; + switch (info->output_components) { + case 1: + format = QImage::Format_Indexed8; + break; + case 3: + case 4: + format = QImage::Format_RGB32; + break; + default: + return false; // unsupported format + } + + if (dest->size() != size || dest->format() != format) { + *dest = QImage(size, format); + + if (format == QImage::Format_Indexed8) { + dest->setColorCount(256); + for (int i = 0; i < 256; i++) + dest->setColor(i, qRgb(i,i,i)); + } + } + + return !dest->isNull(); +} + +static bool read_jpeg_image(QImage *outImage, + QSize scaledSize, QRect scaledClipRect, + QRect clipRect, int inQuality, j_decompress_ptr info, struct my_error_mgr* err ) +{ + if (!setjmp(err->setjmp_buffer)) { + // -1 means default quality. + int quality = inQuality; + if (quality < 0) + quality = 75; + + // If possible, merge the scaledClipRect into either scaledSize + // or clipRect to avoid doing a separate scaled clipping pass. + // Best results are achieved by clipping before scaling, not after. + if (!scaledClipRect.isEmpty()) { + if (scaledSize.isEmpty() && clipRect.isEmpty()) { + // No clipping or scaling before final clip. + clipRect = scaledClipRect; + scaledClipRect = QRect(); + } else if (scaledSize.isEmpty()) { + // Clipping, but no scaling: combine the clip regions. + scaledClipRect.translate(clipRect.topLeft()); + clipRect = scaledClipRect.intersected(clipRect); + scaledClipRect = QRect(); + } else if (clipRect.isEmpty()) { + // No clipping, but scaling: if we can map back to an + // integer pixel boundary, then clip before scaling. + if ((info->image_width % scaledSize.width()) == 0 && + (info->image_height % scaledSize.height()) == 0) { + int x = scaledClipRect.x() * info->image_width / + scaledSize.width(); + int y = scaledClipRect.y() * info->image_height / + scaledSize.height(); + int width = (scaledClipRect.right() + 1) * + info->image_width / scaledSize.width() - x; + int height = (scaledClipRect.bottom() + 1) * + info->image_height / scaledSize.height() - y; + clipRect = QRect(x, y, width, height); + scaledSize = scaledClipRect.size(); + scaledClipRect = QRect(); + } + } else { + // Clipping and scaling: too difficult to figure out, + // and not a likely use case, so do it the long way. + } + } + + // Determine the scale factor to pass to libjpeg for quick downscaling. + if (!scaledSize.isEmpty()) { + if (clipRect.isEmpty()) { + info->scale_denom = + qMin(info->image_width / scaledSize.width(), + info->image_height / scaledSize.height()); + } else { + info->scale_denom = + qMin(clipRect.width() / scaledSize.width(), + clipRect.height() / scaledSize.height()); + } + if (info->scale_denom < 2) { + info->scale_denom = 1; + } else if (info->scale_denom < 4) { + info->scale_denom = 2; + } else if (info->scale_denom < 8) { + info->scale_denom = 4; + } else { + info->scale_denom = 8; + } + info->scale_num = 1; + if (!clipRect.isEmpty()) { + // Correct the scale factor so that we clip accurately. + // It is recommended that the clip rectangle be aligned + // on an 8-pixel boundary for best performance. + while (info->scale_denom > 1 && + ((clipRect.x() % info->scale_denom) != 0 || + (clipRect.y() % info->scale_denom) != 0 || + (clipRect.width() % info->scale_denom) != 0 || + (clipRect.height() % info->scale_denom) != 0)) { + info->scale_denom /= 2; + } + } + } + + // If high quality not required, use fast decompression + if( quality < HIGH_QUALITY_THRESHOLD ) { + info->dct_method = JDCT_IFAST; + info->do_fancy_upsampling = FALSE; + } + + (void) jpeg_calc_output_dimensions(info); + + // Determine the clip region to extract. + QRect imageRect(0, 0, info->output_width, info->output_height); + QRect clip; + if (clipRect.isEmpty()) { + clip = imageRect; + } else if (info->scale_denom == info->scale_num) { + clip = clipRect.intersected(imageRect); + } else { + // The scale factor was corrected above to ensure that + // we don't miss pixels when we scale the clip rectangle. + clip = QRect(clipRect.x() / int(info->scale_denom), + clipRect.y() / int(info->scale_denom), + clipRect.width() / int(info->scale_denom), + clipRect.height() / int(info->scale_denom)); + clip = clip.intersected(imageRect); + } + + // Allocate memory for the clipped QImage. + if (!ensureValidImage(outImage, info, clip.size())) + longjmp(err->setjmp_buffer, 1); + + // Avoid memcpy() overhead if grayscale with no clipping. + bool quickGray = (info->output_components == 1 && + clip == imageRect); + if (!quickGray) { + // Ask the jpeg library to allocate a temporary row. + // The library will automatically delete it for us later. + // The libjpeg docs say we should do this before calling + // jpeg_start_decompress(). We can't use "new" here + // because we are inside the setjmp() block and an error + // in the jpeg input stream would cause a memory leak. + JSAMPARRAY rows = (info->mem->alloc_sarray) + ((j_common_ptr)info, JPOOL_IMAGE, + info->output_width * info->output_components, 1); + + (void) jpeg_start_decompress(info); + + while (info->output_scanline < info->output_height) { + int y = int(info->output_scanline) - clip.y(); + if (y >= clip.height()) + break; // We've read the entire clip region, so abort. + + (void) jpeg_read_scanlines(info, rows, 1); + + if (y < 0) + continue; // Haven't reached the starting line yet. + + if (info->output_components == 3) { + uchar *in = rows[0] + clip.x() * 3; + QRgb *out = (QRgb*)outImage->scanLine(y); + rgb888ToRgb32ConverterPtr(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; + } + } else if (info->output_components == 1) { + // Grayscale. + memcpy(outImage->scanLine(y), + rows[0] + clip.x(), clip.width()); + } + } + } else { + // Load unclipped grayscale data directly into the QImage. + (void) jpeg_start_decompress(info); + while (info->output_scanline < info->output_height) { + uchar *row = outImage->scanLine(info->output_scanline); + (void) jpeg_read_scanlines(info, &row, 1); + } + } + + if (info->output_scanline == info->output_height) + (void) jpeg_finish_decompress(info); + + if (info->density_unit == 1) { + outImage->setDotsPerMeterX(int(100. * info->X_density / 2.54)); + outImage->setDotsPerMeterY(int(100. * info->Y_density / 2.54)); + } else if (info->density_unit == 2) { + outImage->setDotsPerMeterX(int(100. * info->X_density)); + outImage->setDotsPerMeterY(int(100. * info->Y_density)); + } + + if (scaledSize.isValid() && scaledSize != clip.size()) { + *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, quality >= HIGH_QUALITY_THRESHOLD ? Qt::SmoothTransformation : Qt::FastTransformation); + } + + if (!scaledClipRect.isEmpty()) + *outImage = outImage->copy(scaledClipRect); + return !outImage->isNull(); + } + else + return false; +} + +struct my_jpeg_destination_mgr : public jpeg_destination_mgr { + // Nothing dynamic - cannot rely on destruction over longjump + QIODevice *device; + JOCTET buffer[max_buf]; + +public: + my_jpeg_destination_mgr(QIODevice *); +}; + + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static void qt_init_destination(j_compress_ptr) +{ +} + +static boolean qt_empty_output_buffer(j_compress_ptr cinfo) +{ + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + + int written = dest->device->write((char*)dest->buffer, max_buf); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); + + dest->next_output_byte = dest->buffer; + dest->free_in_buffer = max_buf; + +#if defined(Q_OS_UNIXWARE) + return B_TRUE; +#else + return true; +#endif +} + +static void qt_term_destination(j_compress_ptr cinfo) +{ + my_jpeg_destination_mgr* dest = (my_jpeg_destination_mgr*)cinfo->dest; + qint64 n = max_buf - dest->free_in_buffer; + + qint64 written = dest->device->write((char*)dest->buffer, n); + if (written == -1) + (*cinfo->err->error_exit)((j_common_ptr)cinfo); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device) +{ + jpeg_destination_mgr::init_destination = qt_init_destination; + jpeg_destination_mgr::empty_output_buffer = qt_empty_output_buffer; + jpeg_destination_mgr::term_destination = qt_term_destination; + this->device = device; + next_output_byte = buffer; + free_in_buffer = max_buf; +} + + +static bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQuality) +{ + bool success = false; + const QVector cmap = image.colorTable(); + + struct jpeg_compress_struct cinfo; + JSAMPROW row_pointer[1]; + row_pointer[0] = 0; + + struct my_jpeg_destination_mgr *iod_dest = new my_jpeg_destination_mgr(device); + struct my_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = my_error_exit; + + if (!setjmp(jerr.setjmp_buffer)) { + // WARNING: + // this if loop is inside a setjmp/longjmp branch + // do not create C++ temporaries here because the destructor may never be called + // if you allocate memory, make sure that you can free it (row_pointer[0]) + jpeg_create_compress(&cinfo); + + cinfo.dest = iod_dest; + + cinfo.image_width = image.width(); + cinfo.image_height = image.height(); + + bool gray=false; + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + gray = true; + for (int i = image.colorCount(); gray && i--;) { + gray = gray & (qRed(cmap[i]) == qGreen(cmap[i]) && + qRed(cmap[i]) == qBlue(cmap[i])); + } + cinfo.input_components = gray ? 1 : 3; + cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB; + break; + default: + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } + + jpeg_set_defaults(&cinfo); + + qreal diffInch = qAbs(image.dotsPerMeterX()*2.54/100. - qRound(image.dotsPerMeterX()*2.54/100.)) + + qAbs(image.dotsPerMeterY()*2.54/100. - qRound(image.dotsPerMeterY()*2.54/100.)); + qreal diffCm = (qAbs(image.dotsPerMeterX()/100. - qRound(image.dotsPerMeterX()/100.)) + + qAbs(image.dotsPerMeterY()/100. - qRound(image.dotsPerMeterY()/100.)))*2.54; + if (diffInch < diffCm) { + cinfo.density_unit = 1; // dots/inch + cinfo.X_density = qRound(image.dotsPerMeterX()*2.54/100.); + cinfo.Y_density = qRound(image.dotsPerMeterY()*2.54/100.); + } else { + cinfo.density_unit = 2; // dots/cm + cinfo.X_density = (image.dotsPerMeterX()+50) / 100; + cinfo.Y_density = (image.dotsPerMeterY()+50) / 100; + } + + + int quality = sourceQuality >= 0 ? qMin(sourceQuality,100) : 75; +#if defined(Q_OS_UNIXWARE) + jpeg_set_quality(&cinfo, quality, B_TRUE /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, B_TRUE); +#else + jpeg_set_quality(&cinfo, quality, true /* limit to baseline-JPEG values */); + jpeg_start_compress(&cinfo, true); +#endif + + row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components]; + int w = cinfo.image_width; + while (cinfo.next_scanline < cinfo.image_height) { + uchar *row = row_pointer[0]; + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + if (gray) { + const uchar* data = image.constScanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i=0; i> 3)) & (1 << (i & 7))); + row[i] = qRed(cmap[bit]); + } + } else { + for (int i=0; i> 3)) & (1 << (7 -(i & 7)))); + row[i] = qRed(cmap[bit]); + } + } + } else { + const uchar* data = image.constScanLine(cinfo.next_scanline); + if (image.format() == QImage::Format_MonoLSB) { + for (int i=0; i> 3)) & (1 << (i & 7))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } else { + for (int i=0; i> 3)) & (1 << (7 -(i & 7)))); + *row++ = qRed(cmap[bit]); + *row++ = qGreen(cmap[bit]); + *row++ = qBlue(cmap[bit]); + } + } + } + break; + case QImage::Format_Indexed8: + if (gray) { + const uchar* pix = image.constScanLine(cinfo.next_scanline); + for (int i=0; idevice()); + + if(state == ReadHeader) + { + bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, &info, &err); + state = success ? Ready : Error; + return success; + } + + return false; + +} + +QJpegHandler::QJpegHandler() + : d(new QJpegHandlerPrivate(this)) +{ + const uint features = qDetectCPUFeatures(); + Q_UNUSED(features); +#if defined(QT_HAVE_NEON) + // from qimage_neon.cpp + Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_neon(quint32 *dst, const uchar *src, int len); + + if (features & NEON) + rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon; +#endif // QT_HAVE_NEON +#if defined(QT_HAVE_SSSE3) + // from qimage_ssse3.cpp + Q_GUI_EXPORT void QT_FASTCALL qt_convert_rgb888_to_rgb32_ssse3(quint32 *dst, const uchar *src, int len); + + if (features & SSSE3) + rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_ssse3; +#endif // QT_HAVE_SSSE3 +} + +QJpegHandler::~QJpegHandler() +{ + delete d; +} + +bool QJpegHandler::canRead() const +{ + if(d->state == QJpegHandlerPrivate::Ready && !canRead(device())) + return false; + + if (d->state != QJpegHandlerPrivate::Error) { + setFormat("jpeg"); + return true; + } + + return false; +} + +bool QJpegHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QJpegHandler::canRead() called with no device"); + return false; + } + + char buffer[2]; + if (device->peek(buffer, 2) != 2) + return false; + return uchar(buffer[0]) == 0xff && uchar(buffer[1]) == 0xd8; +} + +bool QJpegHandler::read(QImage *image) +{ + if (!canRead()) + return false; + return d->read(image); +} + +bool QJpegHandler::write(const QImage &image) +{ + return write_jpeg_image(image, device(), d->quality); +} + +bool QJpegHandler::supportsOption(ImageOption option) const +{ + return option == Quality + || option == ScaledSize + || option == ScaledClipRect + || option == ClipRect + || option == Size + || option == ImageFormat; +} + +QVariant QJpegHandler::option(ImageOption option) const +{ + switch(option) { + case Quality: + return d->quality; + case ScaledSize: + return d->scaledSize; + case ScaledClipRect: + return d->scaledClipRect; + case ClipRect: + return d->clipRect; + case Size: + d->readJpegHeader(device()); + return d->size; + case ImageFormat: + d->readJpegHeader(device()); + return d->format; + default: + return QVariant(); + } +} + +void QJpegHandler::setOption(ImageOption option, const QVariant &value) +{ + switch(option) { + case Quality: + d->quality = value.toInt(); + break; + case ScaledSize: + d->scaledSize = value.toSize(); + break; + case ScaledClipRect: + d->scaledClipRect = value.toRect(); + break; + case ClipRect: + d->clipRect = value.toRect(); + break; + default: + break; + } +} + +QByteArray QJpegHandler::name() const +{ + return "jpeg"; +} + + + + +QT_END_NAMESPACE diff --git a/src/gui/image/qjpeghandler.pri b/src/gui/image/qjpeghandler.pri new file mode 100644 index 0000000000..3cb35c95ed --- /dev/null +++ b/src/gui/image/qjpeghandler.pri @@ -0,0 +1,10 @@ +# common to plugin and built-in forms +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qjpeghandler_p.h +SOURCES += $$PWD/qjpeghandler.cpp +contains(QT_CONFIG, system-jpeg) { + if(unix|win32-g++*): LIBS += -ljpeg + else:win32: LIBS += libjpeg.lib +} else { + include($$PWD/../../3rdparty/libjpeg.pri) +} diff --git a/src/gui/image/qjpeghandler_p.h b/src/gui/image/qjpeghandler_p.h new file mode 100644 index 0000000000..0bd7894482 --- /dev/null +++ b/src/gui/image/qjpeghandler_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJPEGHANDLER_P_H +#define QJPEGHANDLER_P_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QJpegHandlerPrivate; +class QJpegHandler : public QImageIOHandler +{ +public: + QJpegHandler(); + ~QJpegHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + QJpegHandlerPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QJPEGHANDLER_P_H diff --git a/src/gui/image/qmnghandler.cpp b/src/gui/image/qmnghandler.cpp new file mode 100644 index 0000000000..6b918af0c3 --- /dev/null +++ b/src/gui/image/qmnghandler.cpp @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmnghandler_p.h" + +#include "qimage.h" +#include "qvariant.h" +#include "qcolor.h" + +#define MNG_USE_SO +#include + +QT_BEGIN_NAMESPACE + +class QMngHandlerPrivate +{ + Q_DECLARE_PUBLIC(QMngHandler) + public: + bool haveReadNone; + bool haveReadAll; + mng_handle hMNG; + QImage image; + int elapsed; + int nextDelay; + int iterCount; + int frameIndex; + int nextIndex; + int frameCount; + mng_uint32 iStyle; + mng_bool readData(mng_ptr pBuf, mng_uint32 iSize, mng_uint32p pRead); + mng_bool writeData(mng_ptr pBuf, mng_uint32 iSize, mng_uint32p pWritten); + mng_bool processHeader(mng_uint32 iWidth, mng_uint32 iHeight); + QMngHandlerPrivate(QMngHandler *q_ptr); + ~QMngHandlerPrivate(); + bool getNextImage(QImage *result); + bool writeImage(const QImage &image); + int currentImageNumber() const; + int imageCount() const; + bool jumpToImage(int imageNumber); + bool jumpToNextImage(); + int nextImageDelay() const; + bool setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + QMngHandler *q_ptr; +}; + +static mng_bool myerror(mng_handle /*hMNG*/, + mng_int32 iErrorcode, + mng_int8 /*iSeverity*/, + mng_chunkid iChunkname, + mng_uint32 /*iChunkseq*/, + mng_int32 iExtra1, + mng_int32 iExtra2, + mng_pchar zErrortext) +{ + qWarning("MNG error %d: %s; chunk %c%c%c%c; subcode %d:%d", + iErrorcode,zErrortext, + (iChunkname>>24)&0xff, + (iChunkname>>16)&0xff, + (iChunkname>>8)&0xff, + (iChunkname>>0)&0xff, + iExtra1,iExtra2); + return TRUE; +} + +static mng_ptr myalloc(mng_size_t iSize) +{ +#if defined(Q_OS_WINCE) + mng_ptr ptr = malloc(iSize); + memset(ptr, 0, iSize); + return ptr; +#else + return (mng_ptr)calloc(1, iSize); +#endif +} + +static void myfree(mng_ptr pPtr, mng_size_t /*iSize*/) +{ + free(pPtr); +} + +static mng_bool myopenstream(mng_handle) +{ + return MNG_TRUE; +} + +static mng_bool myclosestream(mng_handle hMNG) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + pMydata->haveReadAll = true; + return MNG_TRUE; +} + +static mng_bool myreaddata(mng_handle hMNG, + mng_ptr pBuf, + mng_uint32 iSize, + mng_uint32p pRead) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + return pMydata->readData(pBuf, iSize, pRead); +} + +static mng_bool mywritedata(mng_handle hMNG, + mng_ptr pBuf, + mng_uint32 iSize, + mng_uint32p pWritten) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + return pMydata->writeData(pBuf, iSize, pWritten); +} + +static mng_bool myprocessheader(mng_handle hMNG, + mng_uint32 iWidth, + mng_uint32 iHeight) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + return pMydata->processHeader(iWidth, iHeight); +} + +static mng_ptr mygetcanvasline(mng_handle hMNG, + mng_uint32 iLinenr) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + return (mng_ptr)pMydata->image.scanLine(iLinenr); +} + +static mng_bool myrefresh(mng_handle /*hMNG*/, + mng_uint32 /*iX*/, + mng_uint32 /*iY*/, + mng_uint32 /*iWidth*/, + mng_uint32 /*iHeight*/) +{ + return MNG_TRUE; +} + +static mng_uint32 mygettickcount(mng_handle hMNG) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + return pMydata->elapsed++; +} + +static mng_bool mysettimer(mng_handle hMNG, + mng_uint32 iMsecs) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + pMydata->elapsed += iMsecs; + pMydata->nextDelay = iMsecs; + return MNG_TRUE; +} + +static mng_bool myprocessterm(mng_handle hMNG, + mng_uint8 iTermaction, + mng_uint8 /*iIteraction*/, + mng_uint32 /*iDelay*/, + mng_uint32 iItermax) +{ + QMngHandlerPrivate *pMydata = reinterpret_cast(mng_get_userdata(hMNG)); + if (iTermaction == 3) + pMydata->iterCount = iItermax; + return MNG_TRUE; +} + +static mng_bool mytrace(mng_handle, + mng_int32 iFuncnr, + mng_int32 iFuncseq, + mng_pchar zFuncname) +{ + qDebug("mng trace: iFuncnr: %d iFuncseq: %d zFuncname: %s", iFuncnr, iFuncseq, zFuncname); + return MNG_TRUE; +} + +QMngHandlerPrivate::QMngHandlerPrivate(QMngHandler *q_ptr) + : haveReadNone(true), haveReadAll(false), elapsed(0), nextDelay(0), iterCount(1), + frameIndex(-1), nextIndex(0), frameCount(0), q_ptr(q_ptr) +{ + iStyle = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? MNG_CANVAS_BGRA8 : MNG_CANVAS_ARGB8; + // Initialize libmng + hMNG = mng_initialize((mng_ptr)this, myalloc, myfree, mytrace); + if (hMNG) { + // Set callback functions + mng_setcb_errorproc(hMNG, myerror); + mng_setcb_openstream(hMNG, myopenstream); + mng_setcb_closestream(hMNG, myclosestream); + mng_setcb_readdata(hMNG, myreaddata); + mng_setcb_writedata(hMNG, mywritedata); + mng_setcb_processheader(hMNG, myprocessheader); + mng_setcb_getcanvasline(hMNG, mygetcanvasline); + mng_setcb_refresh(hMNG, myrefresh); + mng_setcb_gettickcount(hMNG, mygettickcount); + mng_setcb_settimer(hMNG, mysettimer); + mng_setcb_processterm(hMNG, myprocessterm); + mng_set_doprogressive(hMNG, MNG_FALSE); + mng_set_suspensionmode(hMNG, MNG_TRUE); + } +} + +QMngHandlerPrivate::~QMngHandlerPrivate() +{ + mng_cleanup(&hMNG); +} + +mng_bool QMngHandlerPrivate::readData(mng_ptr pBuf, mng_uint32 iSize, mng_uint32p pRead) +{ + Q_Q(QMngHandler); + *pRead = q->device()->read((char *)pBuf, iSize); + return (*pRead > 0) ? MNG_TRUE : MNG_FALSE; +} + +mng_bool QMngHandlerPrivate::writeData(mng_ptr pBuf, mng_uint32 iSize, mng_uint32p pWritten) +{ + Q_Q(QMngHandler); + *pWritten = q->device()->write((char *)pBuf, iSize); + return MNG_TRUE; +} + +mng_bool QMngHandlerPrivate::processHeader(mng_uint32 iWidth, mng_uint32 iHeight) +{ + if (mng_set_canvasstyle(hMNG, iStyle) != MNG_NOERROR) + return MNG_FALSE; + image = QImage(iWidth, iHeight, QImage::Format_ARGB32); + image.fill(0); + return MNG_TRUE; +} + +bool QMngHandlerPrivate::getNextImage(QImage *result) +{ + mng_retcode ret; + if (haveReadNone) { + haveReadNone = false; + ret = mng_readdisplay(hMNG); + } else { + ret = mng_display_resume(hMNG); + } + if ((MNG_NOERROR == ret) || (MNG_NEEDTIMERWAIT == ret)) { + *result = image; + frameIndex = nextIndex++; + if (haveReadAll && (frameCount == 0)) + frameCount = nextIndex; + return true; + } + return false; +} + +bool QMngHandlerPrivate::writeImage(const QImage &image) +{ + mng_reset(hMNG); + if (mng_create(hMNG) != MNG_NOERROR) + return false; + + this->image = image.convertToFormat(QImage::Format_ARGB32); + int w = image.width(); + int h = image.height(); + + if ( + // width, height, ticks, layercount, framecount, playtime, simplicity + (mng_putchunk_mhdr(hMNG, w, h, 1000, 0, 0, 0, 7) == MNG_NOERROR) && + // termination_action, action_after_iterations, delay, iteration_max + (mng_putchunk_term(hMNG, 3, 0, 1, 0x7FFFFFFF) == MNG_NOERROR) && + // width, height, bitdepth, colortype, compression, filter, interlace + (mng_putchunk_ihdr(hMNG, w, h, 8, 6, 0, 0, 0) == MNG_NOERROR) && + // width, height, colortype, bitdepth, compression, filter, interlace, canvasstyle, getcanvasline + (mng_putimgdata_ihdr(hMNG, w, h, 6, 8, 0, 0, 0, iStyle, mygetcanvasline) == MNG_NOERROR) && + (mng_putchunk_iend(hMNG) == MNG_NOERROR) && + (mng_putchunk_mend(hMNG) == MNG_NOERROR) && + (mng_write(hMNG) == MNG_NOERROR) + ) + return true; + return false; +} + +int QMngHandlerPrivate::currentImageNumber() const +{ +// return mng_get_currentframe(hMNG) % imageCount(); not implemented, apparently + return frameIndex; +} + +int QMngHandlerPrivate::imageCount() const +{ +// return mng_get_totalframes(hMNG); not implemented, apparently + if (haveReadAll) + return frameCount; + return 0; // Don't know +} + +bool QMngHandlerPrivate::jumpToImage(int imageNumber) +{ + if (imageNumber == nextIndex) + return true; + + if ((imageNumber == 0) && haveReadAll && (nextIndex == frameCount)) { + // Loop! + nextIndex = 0; + return true; + } + if (mng_display_freeze(hMNG) == MNG_NOERROR) { + if (mng_display_goframe(hMNG, imageNumber) == MNG_NOERROR) { + nextIndex = imageNumber; + return true; + } + } + return false; +} + +bool QMngHandlerPrivate::jumpToNextImage() +{ + return jumpToImage((currentImageNumber()+1) % imageCount()); +} + +int QMngHandlerPrivate::nextImageDelay() const +{ + return nextDelay; +} + +bool QMngHandlerPrivate::setBackgroundColor(const QColor &color) +{ + mng_uint16 iRed = (mng_uint16)(color.red() << 8); + mng_uint16 iBlue = (mng_uint16)(color.blue() << 8); + mng_uint16 iGreen = (mng_uint16)(color.green() << 8); + return (mng_set_bgcolor(hMNG, iRed, iBlue, iGreen) == MNG_NOERROR); +} + +QColor QMngHandlerPrivate::backgroundColor() const +{ + mng_uint16 iRed; + mng_uint16 iBlue; + mng_uint16 iGreen; + if (mng_get_bgcolor(hMNG, &iRed, &iBlue, &iGreen) == MNG_NOERROR) + return QColor((iRed >> 8) & 0xFF, (iGreen >> 8) & 0xFF, (iBlue >> 8) & 0xFF); + return QColor(); +} + +QMngHandler::QMngHandler() + : d_ptr(new QMngHandlerPrivate(this)) +{ +} + +QMngHandler::~QMngHandler() +{ +} + +/*! \reimp */ +bool QMngHandler::canRead() const +{ + Q_D(const QMngHandler); + if ((!d->haveReadNone + && (!d->haveReadAll || (d->haveReadAll && (d->nextIndex < d->frameCount)))) + || canRead(device())) + { + setFormat("mng"); + return true; + } + return false; +} + +/*! \internal */ +bool QMngHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QMngHandler::canRead() called with no device"); + return false; + } + + return device->peek(8) == "\x8A\x4D\x4E\x47\x0D\x0A\x1A\x0A"; +} + +/*! \reimp */ +QByteArray QMngHandler::name() const +{ + return "mng"; +} + +/*! \reimp */ +bool QMngHandler::read(QImage *image) +{ + Q_D(QMngHandler); + return canRead() ? d->getNextImage(image) : false; +} + +/*! \reimp */ +bool QMngHandler::write(const QImage &image) +{ + Q_D(QMngHandler); + return d->writeImage(image); +} + +/*! \reimp */ +int QMngHandler::currentImageNumber() const +{ + Q_D(const QMngHandler); + return d->currentImageNumber(); +} + +/*! \reimp */ +int QMngHandler::imageCount() const +{ + Q_D(const QMngHandler); + return d->imageCount(); +} + +/*! \reimp */ +bool QMngHandler::jumpToImage(int imageNumber) +{ + Q_D(QMngHandler); + return d->jumpToImage(imageNumber); +} + +/*! \reimp */ +bool QMngHandler::jumpToNextImage() +{ + Q_D(QMngHandler); + return d->jumpToNextImage(); +} + +/*! \reimp */ +int QMngHandler::loopCount() const +{ + Q_D(const QMngHandler); + if (d->iterCount == 0x7FFFFFFF) + return -1; // infinite loop + return d->iterCount-1; +} + +/*! \reimp */ +int QMngHandler::nextImageDelay() const +{ + Q_D(const QMngHandler); + return d->nextImageDelay(); +} + +/*! \reimp */ +QVariant QMngHandler::option(ImageOption option) const +{ + Q_D(const QMngHandler); + if (option == QImageIOHandler::Animation) + return true; + else if (option == QImageIOHandler::BackgroundColor) + return d->backgroundColor(); + return QVariant(); +} + +/*! \reimp */ +void QMngHandler::setOption(ImageOption option, const QVariant & value) +{ + Q_D(QMngHandler); + if (option == QImageIOHandler::BackgroundColor) + d->setBackgroundColor(qvariant_cast(value)); +} + +/*! \reimp */ +bool QMngHandler::supportsOption(ImageOption option) const +{ + if (option == QImageIOHandler::Animation) + return true; + else if (option == QImageIOHandler::BackgroundColor) + return true; + return false; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qmnghandler.pri b/src/gui/image/qmnghandler.pri new file mode 100644 index 0000000000..ffb98de92b --- /dev/null +++ b/src/gui/image/qmnghandler.pri @@ -0,0 +1,10 @@ +# common to plugin and built-in forms +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qmnghandler_p.h +SOURCES += $$PWD/qmnghandler.cpp +contains(QT_CONFIG, system-mng) { + if(unix|win32-g++*):LIBS += -lmng + else:win32: LIBS += libmng.lib +} else { + include($$PWD/../../3rdparty/libmng.pri) +} diff --git a/src/gui/image/qmnghandler_p.h b/src/gui/image/qmnghandler_p.h new file mode 100644 index 0000000000..8436b9610b --- /dev/null +++ b/src/gui/image/qmnghandler_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMNGHANDLER_P_H +#define QMNGHANDLER_P_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QImage; +class QByteArray; +class QIODevice; +class QVariant; +class QMngHandlerPrivate; + +class QMngHandler : public QImageIOHandler +{ + public: + QMngHandler(); + ~QMngHandler(); + virtual bool canRead() const; + virtual QByteArray name() const; + virtual bool read(QImage *image); + virtual bool write(const QImage &image); + virtual int currentImageNumber() const; + virtual int imageCount() const; + virtual bool jumpToImage(int imageNumber); + virtual bool jumpToNextImage(); + virtual int loopCount() const; + virtual int nextImageDelay() const; + static bool canRead(QIODevice *device); + virtual QVariant option(ImageOption option) const; + virtual void setOption(ImageOption option, const QVariant & value); + virtual bool supportsOption(ImageOption option) const; + + private: + Q_DECLARE_PRIVATE(QMngHandler) + QScopedPointer d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QMNGHANDLER_P_H diff --git a/src/gui/image/qmovie.cpp b/src/gui/image/qmovie.cpp new file mode 100644 index 0000000000..8b6e360096 --- /dev/null +++ b/src/gui/image/qmovie.cpp @@ -0,0 +1,1089 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QMovie + + \brief The QMovie class is a convenience class for playing movies + with QImageReader. + + \ingroup painting + + This class is used to show simple animations without sound. If you want + to display video and media content, use the \l{Phonon Module}{Phonon} + multimedia framework instead. + + First, create a QMovie object by passing either the name of a file or a + pointer to a QIODevice containing an animated image format to QMovie's + constructor. You can call isValid() to check if the image data is valid, + before starting the movie. To start the movie, call start(). QMovie will + enter \l Running state, and emit started() and stateChanged(). To get the + current state of the movie, call state(). + + To display the movie in your application, you can pass your QMovie object + to QLabel::setMovie(). Example: + + \snippet doc/src/snippets/code/src_gui_image_qmovie.cpp 0 + + Whenever a new frame is available in the movie, QMovie will emit + updated(). If the size of the frame changes, resized() is emitted. You can + call currentImage() or currentPixmap() to get a copy of the current + frame. When the movie is done, QMovie emits finished(). If any error + occurs during playback (i.e, the image file is corrupt), QMovie will emit + error(). + + You can control the speed of the movie playback by calling setSpeed(), + which takes the percentage of the original speed as an argument. Pause the + movie by calling setPaused(true). QMovie will then enter \l Paused state + and emit stateChanged(). If you call setPaused(false), QMovie will reenter + \l Running state and start the movie again. To stop the movie, call + stop(). + + Certain animation formats allow you to set the background color. You can + call setBackgroundColor() to set the color, or backgroundColor() to + retrieve the current background color. + + currentFrameNumber() returns the sequence number of the current frame. The + first frame in the animation has the sequence number 0. frameCount() + returns the total number of frames in the animation, if the image format + supports this. You can call loopCount() to get the number of times the + movie should loop before finishing. nextFrameDelay() returns the number of + milliseconds the current frame should be displayed. + + QMovie can be instructed to cache frames of an animation by calling + setCacheMode(). + + Call supportedFormats() for a list of formats that QMovie supports. + + \sa QLabel, QImageReader, {Movie Example} +*/ + +/*! \enum QMovie::MovieState + + This enum describes the different states of QMovie. + + \value NotRunning The movie is not running. This is QMovie's initial + state, and the state it enters after stop() has been called or the movie + is finished. + + \value Paused The movie is paused, and QMovie stops emitting updated() or + resized(). This state is entered after calling pause() or + setPaused(true). The current frame number it kept, and the movie will + continue with the next frame when unpause() or setPaused(false) is called. + + \value Running The movie is running. +*/ + +/*! \enum QMovie::CacheMode + + This enum describes the different cache modes of QMovie. + + \value CacheNone No frames are cached (the default). + + \value CacheAll All frames are cached. +*/ + +/*! \fn void QMovie::started() + + This signal is emitted after QMovie::start() has been called, and QMovie + has entered QMovie::Running state. +*/ + +/*! \fn void QMovie::resized(const QSize &size) + + This signal is emitted when the current frame has been resized to \a + size. This effect is sometimes used in animations as an alternative to + replacing the frame. You can call currentImage() or currentPixmap() to get a + copy of the updated frame. +*/ + +/*! \fn void QMovie::updated(const QRect &rect) + + This signal is emitted when the rect \a rect in the current frame has been + updated. You can call currentImage() or currentPixmap() to get a copy of the + updated frame. +*/ + +/*! \fn void QMovie::frameChanged(int frameNumber) + \since 4.1 + + This signal is emitted when the frame number has changed to + \a frameNumber. You can call currentImage() or currentPixmap() to get a + copy of the frame. +*/ + +/*! + \fn void QMovie::stateChanged(QMovie::MovieState state) + + This signal is emitted every time the state of the movie changes. The new + state is specified by \a state. + + \sa QMovie::state() +*/ + +/*! \fn void QMovie::error(QImageReader::ImageReaderError error) + + This signal is emitted by QMovie when the error \a error occurred during + playback. QMovie will stop the movie, and enter QMovie::NotRunning state. +*/ + +/*! \fn void QMovie::finished() + + This signal is emitted when the movie has finished. + + \sa QMovie::stop() +*/ + +#include "qglobal.h" + +#ifndef QT_NO_MOVIE + +#include "qmovie.h" +#include "qimage.h" +#include "qimagereader.h" +#include "qpixmap.h" +#include "qrect.h" +#include "qdatetime.h" +#include "qtimer.h" +#include "qpair.h" +#include "qmap.h" +#include "qlist.h" +#include "qbuffer.h" +#include "qdir.h" +#include "private/qobject_p.h" + +#define QMOVIE_INVALID_DELAY -1 + +QT_BEGIN_NAMESPACE + +class QFrameInfo +{ +public: + QPixmap pixmap; + int delay; + bool endMark; + inline QFrameInfo(bool endMark) + : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(endMark) + { } + + inline QFrameInfo() + : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(false) + { } + + inline QFrameInfo(const QPixmap &pixmap, int delay) + : pixmap(pixmap), delay(delay), endMark(false) + { } + + inline bool isValid() + { + return endMark || !(pixmap.isNull() && (delay == QMOVIE_INVALID_DELAY)); + } + + inline bool isEndMarker() + { return endMark; } + + static inline QFrameInfo endMarker() + { return QFrameInfo(true); } +}; + +class QMoviePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QMovie) + +public: + QMoviePrivate(QMovie *qq); + bool isDone(); + bool next(); + int speedAdjustedDelay(int delay) const; + bool isValid() const; + bool jumpToFrame(int frameNumber); + int frameCount() const; + bool jumpToNextFrame(); + QFrameInfo infoForFrame(int frameNumber); + void reset(); + + inline void enterState(QMovie::MovieState newState) { + movieState = newState; + emit q_func()->stateChanged(newState); + } + + // private slots + void _q_loadNextFrame(); + void _q_loadNextFrame(bool starting); + + QImageReader *reader; + int speed; + QMovie::MovieState movieState; + QRect frameRect; + QPixmap currentPixmap; + int currentFrameNumber; + int nextFrameNumber; + int greatestFrameNumber; + int nextDelay; + int playCounter; + qint64 initialDevicePos; + QMovie::CacheMode cacheMode; + bool haveReadAll; + bool isFirstIteration; + QMap frameMap; + QString absoluteFilePath; + + QTimer nextImageTimer; +}; + +/*! \internal + */ +QMoviePrivate::QMoviePrivate(QMovie *qq) + : reader(0), speed(100), movieState(QMovie::NotRunning), + currentFrameNumber(-1), nextFrameNumber(0), greatestFrameNumber(-1), + nextDelay(0), playCounter(-1), + cacheMode(QMovie::CacheNone), haveReadAll(false), isFirstIteration(true) +{ + q_ptr = qq; + nextImageTimer.setSingleShot(true); +} + +/*! \internal + */ +void QMoviePrivate::reset() +{ + nextImageTimer.stop(); + if (reader->device()) + initialDevicePos = reader->device()->pos(); + currentFrameNumber = -1; + nextFrameNumber = 0; + greatestFrameNumber = -1; + nextDelay = 0; + playCounter = -1; + haveReadAll = false; + isFirstIteration = true; + frameMap.clear(); +} + +/*! \internal + */ +bool QMoviePrivate::isDone() +{ + return (playCounter == 0); +} + +/*! + \internal + + Given the original \a delay, this function returns the + actual number of milliseconds to delay according to + the current speed. E.g. if the speed is 200%, the + result will be half of the original delay. +*/ +int QMoviePrivate::speedAdjustedDelay(int delay) const +{ + return int( (qint64(delay) * qint64(100) ) / qint64(speed) ); +} + +/*! + \internal + + Returns the QFrameInfo for the given \a frameNumber. + + If the frame number is invalid, an invalid QFrameInfo is + returned. + + If the end of the animation has been reached, a + special end marker QFrameInfo is returned. + +*/ +QFrameInfo QMoviePrivate::infoForFrame(int frameNumber) +{ + if (frameNumber < 0) + return QFrameInfo(); // Invalid + + if (haveReadAll && (frameNumber > greatestFrameNumber)) { + if (frameNumber == greatestFrameNumber+1) + return QFrameInfo::endMarker(); + return QFrameInfo(); // Invalid + } + + if (cacheMode == QMovie::CacheNone) { + if (frameNumber != currentFrameNumber+1) { + // Non-sequential frame access + if (!reader->jumpToImage(frameNumber)) { + if (frameNumber == 0) { + // Special case: Attempt to "rewind" so we can loop + // ### This could be implemented as QImageReader::rewind() + if (reader->device()->isSequential()) + return QFrameInfo(); // Invalid + QString fileName = reader->fileName(); + QByteArray format = reader->format(); + QIODevice *device = reader->device(); + QColor bgColor = reader->backgroundColor(); + QSize scaledSize = reader->scaledSize(); + delete reader; + if (fileName.isEmpty()) + reader = new QImageReader(device, format); + else + reader = new QImageReader(absoluteFilePath, format); + (void)reader->canRead(); // Provoke a device->open() call + reader->device()->seek(initialDevicePos); + reader->setBackgroundColor(bgColor); + reader->setScaledSize(scaledSize); + } else { + return QFrameInfo(); // Invalid + } + } + } + if (reader->canRead()) { + // reader says we can read. Attempt to actually read image + QImage anImage = reader->read(); + if (anImage.isNull()) { + // Reading image failed. + return QFrameInfo(); // Invalid + } + if (frameNumber > greatestFrameNumber) + greatestFrameNumber = frameNumber; + QPixmap aPixmap = QPixmap::fromImage(anImage); + int aDelay = reader->nextImageDelay(); + return QFrameInfo(aPixmap, aDelay); + } else if (frameNumber != 0) { + // We've read all frames now. Return an end marker + haveReadAll = true; + return QFrameInfo::endMarker(); + } else { + // No readable frames + haveReadAll = true; + return QFrameInfo(); + } + } + + // CacheMode == CacheAll + if (frameNumber > greatestFrameNumber) { + // Frame hasn't been read from file yet. Try to do it + for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) { + if (reader->canRead()) { + // reader says we can read. Attempt to actually read image + QImage anImage = reader->read(); + if (anImage.isNull()) { + // Reading image failed. + return QFrameInfo(); // Invalid + } + greatestFrameNumber = i; + QPixmap aPixmap = QPixmap::fromImage(anImage); + int aDelay = reader->nextImageDelay(); + QFrameInfo info(aPixmap, aDelay); + // Cache it! + frameMap.insert(i, info); + if (i == frameNumber) { + return info; + } + } else { + // We've read all frames now. Return an end marker + haveReadAll = true; + return QFrameInfo::endMarker(); + } + } + } + // Return info for requested (cached) frame + return frameMap.value(frameNumber); +} + +/*! + \internal + + Attempts to advance the animation to the next frame. + If successful, currentFrameNumber, currentPixmap and + nextDelay are updated accordingly, and true is returned. + Otherwise, false is returned. + When false is returned, isDone() can be called to + determine whether the animation ended gracefully or + an error occurred when reading the frame. +*/ +bool QMoviePrivate::next() +{ + QTime time; + time.start(); + QFrameInfo info = infoForFrame(nextFrameNumber); + if (!info.isValid()) + return false; + if (info.isEndMarker()) { + // We reached the end of the animation. + if (isFirstIteration) { + if (nextFrameNumber == 0) { + // No frames could be read at all (error). + return false; + } + // End of first iteration. Initialize play counter + playCounter = reader->loopCount(); + isFirstIteration = false; + } + // Loop as appropriate + if (playCounter != 0) { + if (playCounter != -1) // Infinite? + playCounter--; // Nope + nextFrameNumber = 0; + return next(); + } + // Loop no more. Done + return false; + } + // Image and delay OK, update internal state + currentFrameNumber = nextFrameNumber++; + QSize scaledSize = reader->scaledSize(); + if (scaledSize.isValid() && (scaledSize != info.pixmap.size())) + currentPixmap = QPixmap::fromImage( info.pixmap.toImage().scaled(scaledSize) ); + else + currentPixmap = info.pixmap; + nextDelay = speedAdjustedDelay(info.delay); + // Adjust delay according to the time it took to read the frame + int processingTime = time.elapsed(); + if (processingTime > nextDelay) + nextDelay = 0; + else + nextDelay = nextDelay - processingTime; + return true; +} + +/*! \internal + */ +void QMoviePrivate::_q_loadNextFrame() +{ + _q_loadNextFrame(false); +} + +void QMoviePrivate::_q_loadNextFrame(bool starting) +{ + Q_Q(QMovie); + if (next()) { + if (starting && movieState == QMovie::NotRunning) { + enterState(QMovie::Running); + emit q->started(); + } + + if (frameRect.size() != currentPixmap.rect().size()) { + frameRect = currentPixmap.rect(); + emit q->resized(frameRect.size()); + } + + emit q->updated(frameRect); + emit q->frameChanged(currentFrameNumber); + + if (movieState == QMovie::Running) + nextImageTimer.start(nextDelay); + } else { + // Could not read another frame + if (!isDone()) { + emit q->error(reader->error()); + } + + // Graceful finish + if (movieState != QMovie::Paused) { + nextFrameNumber = 0; + isFirstIteration = true; + playCounter = -1; + enterState(QMovie::NotRunning); + emit q->finished(); + } + } +} + +/*! + \internal +*/ +bool QMoviePrivate::isValid() const +{ + return (greatestFrameNumber >= 0) // have we seen valid data + || reader->canRead(); // or does the reader see valid data +} + +/*! + \internal +*/ +bool QMoviePrivate::jumpToFrame(int frameNumber) +{ + if (frameNumber < 0) + return false; + if (currentFrameNumber == frameNumber) + return true; + nextFrameNumber = frameNumber; + if (movieState == QMovie::Running) + nextImageTimer.stop(); + _q_loadNextFrame(); + return (nextFrameNumber == currentFrameNumber+1); +} + +/*! + \internal +*/ +int QMoviePrivate::frameCount() const +{ + int result; + if ((result = reader->imageCount()) != 0) + return result; + if (haveReadAll) + return greatestFrameNumber+1; + return 0; // Don't know +} + +/*! + \internal +*/ +bool QMoviePrivate::jumpToNextFrame() +{ + return jumpToFrame(currentFrameNumber+1); +} + +/*! + Constructs a QMovie object, passing the \a parent object to QObject's + constructor. + + \sa setFileName(), setDevice(), setFormat() + */ +QMovie::QMovie(QObject *parent) + : QObject(*new QMoviePrivate(this), parent) +{ + Q_D(QMovie); + d->reader = new QImageReader; + connect(&d->nextImageTimer, SIGNAL(timeout()), this, SLOT(_q_loadNextFrame())); +} + +/*! + Constructs a QMovie object. QMovie will use read image data from \a + device, which it assumes is open and readable. If \a format is not empty, + QMovie will use the image format \a format for decoding the image + data. Otherwise, QMovie will attempt to guess the format. + + The \a parent object is passed to QObject's constructor. + */ +QMovie::QMovie(QIODevice *device, const QByteArray &format, QObject *parent) + : QObject(*new QMoviePrivate(this), parent) +{ + Q_D(QMovie); + d->reader = new QImageReader(device, format); + d->initialDevicePos = device->pos(); + connect(&d->nextImageTimer, SIGNAL(timeout()), this, SLOT(_q_loadNextFrame())); +} + +/*! + Constructs a QMovie object. QMovie will use read image data from \a + fileName. If \a format is not empty, QMovie will use the image format \a + format for decoding the image data. Otherwise, QMovie will attempt to + guess the format. + + The \a parent object is passed to QObject's constructor. + */ +QMovie::QMovie(const QString &fileName, const QByteArray &format, QObject *parent) + : QObject(*new QMoviePrivate(this), parent) +{ + Q_D(QMovie); + d->absoluteFilePath = QDir(fileName).absolutePath(); + d->reader = new QImageReader(fileName, format); + if (d->reader->device()) + d->initialDevicePos = d->reader->device()->pos(); + connect(&d->nextImageTimer, SIGNAL(timeout()), this, SLOT(_q_loadNextFrame())); +} + +/*! + Destructs the QMovie object. +*/ +QMovie::~QMovie() +{ + Q_D(QMovie); + delete d->reader; +} + +/*! + Sets the current device to \a device. QMovie will read image data from + this device when the movie is running. + + \sa device(), setFormat() +*/ +void QMovie::setDevice(QIODevice *device) +{ + Q_D(QMovie); + d->reader->setDevice(device); + d->reset(); +} + +/*! + Returns the device QMovie reads image data from. If no device has + currently been assigned, 0 is returned. + + \sa setDevice(), fileName() +*/ +QIODevice *QMovie::device() const +{ + Q_D(const QMovie); + return d->reader->device(); +} + +/*! + Sets the name of the file that QMovie reads image data from, to \a + fileName. + + \sa fileName(), setDevice(), setFormat() +*/ +void QMovie::setFileName(const QString &fileName) +{ + Q_D(QMovie); + d->absoluteFilePath = QDir(fileName).absolutePath(); + d->reader->setFileName(fileName); + d->reset(); +} + +/*! + Returns the name of the file that QMovie reads image data from. If no file + name has been assigned, or if the assigned device is not a file, an empty + QString is returned. + + \sa setFileName(), device() +*/ +QString QMovie::fileName() const +{ + Q_D(const QMovie); + return d->reader->fileName(); +} + +/*! + Sets the format that QMovie will use when decoding image data, to \a + format. By default, QMovie will attempt to guess the format of the image + data. + + You can call supportedFormats() for the full list of formats + QMovie supports. + + \sa QImageReader::supportedImageFormats() +*/ +void QMovie::setFormat(const QByteArray &format) +{ + Q_D(QMovie); + d->reader->setFormat(format); +} + +/*! + Returns the format that QMovie uses when decoding image data. If no format + has been assigned, an empty QByteArray() is returned. + + \sa setFormat() +*/ +QByteArray QMovie::format() const +{ + Q_D(const QMovie); + return d->reader->format(); +} + +/*! + For image formats that support it, this function sets the background color + to \a color. + + \sa backgroundColor() +*/ +void QMovie::setBackgroundColor(const QColor &color) +{ + Q_D(QMovie); + d->reader->setBackgroundColor(color); +} + +/*! + Returns the background color of the movie. If no background color has been + assigned, an invalid QColor is returned. + + \sa setBackgroundColor() +*/ +QColor QMovie::backgroundColor() const +{ + Q_D(const QMovie); + return d->reader->backgroundColor(); +} + +/*! + Returns the current state of QMovie. + + \sa MovieState, stateChanged() +*/ +QMovie::MovieState QMovie::state() const +{ + Q_D(const QMovie); + return d->movieState; +} + +/*! + Returns the rect of the last frame. If no frame has yet been updated, an + invalid QRect is returned. + + \sa currentImage(), currentPixmap() +*/ +QRect QMovie::frameRect() const +{ + Q_D(const QMovie); + return d->frameRect; +} + +/*! \fn QImage QMovie::framePixmap() const + + Use currentPixmap() instead. +*/ + +/*! \fn void QMovie::pause() + + Use setPaused(true) instead. +*/ + +/*! \fn void QMovie::unpause() + + Use setPaused(false) instead. +*/ + +/*! + Returns the current frame as a QPixmap. + + \sa currentImage(), updated() +*/ +QPixmap QMovie::currentPixmap() const +{ + Q_D(const QMovie); + return d->currentPixmap; +} + +/*! \fn QImage QMovie::frameImage() const + + Use currentImage() instead. +*/ + +/*! + Returns the current frame as a QImage. + + \sa currentPixmap(), updated() +*/ +QImage QMovie::currentImage() const +{ + Q_D(const QMovie); + return d->currentPixmap.toImage(); +} + +/*! + Returns true if the movie is valid (e.g., the image data is readable and + the image format is supported); otherwise returns false. +*/ +bool QMovie::isValid() const +{ + Q_D(const QMovie); + return d->isValid(); +} + +/*! \fn bool QMovie::running() const + + Use state() instead. +*/ + +/*! \fn bool QMovie::isNull() const + + Use isValid() instead. +*/ + +/*! \fn int QMovie::frameNumber() const + + Use currentFrameNumber() instead. +*/ + +/*! \fn bool QMovie::paused() const + + Use state() instead. +*/ + +/*! \fn bool QMovie::finished() const + + Use state() instead. +*/ + +/*! \fn void QMovie::restart() + + Use stop() and start() instead. +*/ + +/*! + \fn void QMovie::step() + + Use jumpToNextFrame() instead. +*/ + +/*! + Returns the number of frames in the movie. + + Certain animation formats do not support this feature, in which + case 0 is returned. +*/ +int QMovie::frameCount() const +{ + Q_D(const QMovie); + return d->frameCount(); +} + +/*! + Returns the number of milliseconds QMovie will wait before updating the + next frame in the animation. +*/ +int QMovie::nextFrameDelay() const +{ + Q_D(const QMovie); + return d->nextDelay; +} + +/*! + Returns the sequence number of the current frame. The number of the first + frame in the movie is 0. +*/ +int QMovie::currentFrameNumber() const +{ + Q_D(const QMovie); + return d->currentFrameNumber; +} + +/*! + Jumps to the next frame. Returns true on success; otherwise returns false. +*/ +bool QMovie::jumpToNextFrame() +{ + Q_D(QMovie); + return d->jumpToNextFrame(); +} + +/*! + Jumps to frame number \a frameNumber. Returns true on success; otherwise + returns false. +*/ +bool QMovie::jumpToFrame(int frameNumber) +{ + Q_D(QMovie); + return d->jumpToFrame(frameNumber); +} + +/*! + Returns the number of times the movie will loop before it finishes. + If the movie will only play once (no looping), loopCount returns 0. + If the movie loops forever, loopCount returns -1. + + Note that, if the image data comes from a sequential device (e.g. a + socket), QMovie can only loop the movie if the cacheMode is set to + QMovie::CacheAll. +*/ +int QMovie::loopCount() const +{ + Q_D(const QMovie); + return d->reader->loopCount(); +} + +/*! + If \a paused is true, QMovie will enter \l Paused state and emit + stateChanged(Paused); otherwise it will enter \l Running state and emit + stateChanged(Running). + + \sa state() +*/ +void QMovie::setPaused(bool paused) +{ + Q_D(QMovie); + if (paused) { + if (d->movieState == NotRunning) + return; + d->enterState(Paused); + d->nextImageTimer.stop(); + } else { + if (d->movieState == Running) + return; + d->enterState(Running); + d->nextImageTimer.start(nextFrameDelay()); + } +} + +/*! + \property QMovie::speed + \brief the movie's speed + + The speed is measured in percentage of the original movie speed. + The default speed is 100%. + Example: + + \snippet doc/src/snippets/code/src_gui_image_qmovie.cpp 1 +*/ +void QMovie::setSpeed(int percentSpeed) +{ + Q_D(QMovie); + d->speed = percentSpeed; +} + +int QMovie::speed() const +{ + Q_D(const QMovie); + return d->speed; +} + +/*! + Starts the movie. QMovie will enter \l Running state, and start emitting + updated() and resized() as the movie progresses. + + If QMovie is in the \l Paused state, this function is equivalent + to calling setPaused(false). If QMovie is already in the \l + Running state, this function does nothing. + + \sa stop(), setPaused() +*/ +void QMovie::start() +{ + Q_D(QMovie); + if (d->movieState == NotRunning) { + d->_q_loadNextFrame(true); + } else if (d->movieState == Paused) { + setPaused(false); + } +} + +/*! + Stops the movie. QMovie enters \l NotRunning state, and stops emitting + updated() and resized(). If start() is called again, the movie will + restart from the beginning. + + If QMovie is already in the \l NotRunning state, this function + does nothing. + + \sa start(), setPaused() +*/ +void QMovie::stop() +{ + Q_D(QMovie); + if (d->movieState == NotRunning) + return; + d->enterState(NotRunning); + d->nextImageTimer.stop(); + d->nextFrameNumber = 0; +} + +/*! + \since 4.1 + + Returns the scaled size of frames. + + \sa QImageReader::scaledSize() +*/ +QSize QMovie::scaledSize() +{ + Q_D(QMovie); + return d->reader->scaledSize(); +} + +/*! + \since 4.1 + + Sets the scaled frame size to \a size. + + \sa QImageReader::setScaledSize() +*/ +void QMovie::setScaledSize(const QSize &size) +{ + Q_D(QMovie); + d->reader->setScaledSize(size); +} + +/*! + \since 4.1 + + Returns the list of image formats supported by QMovie. + + \sa QImageReader::supportedImageFormats() +*/ +QList QMovie::supportedFormats() +{ + QList list = QImageReader::supportedImageFormats(); + QMutableListIterator it(list); + QBuffer buffer; + buffer.open(QIODevice::ReadOnly); + while (it.hasNext()) { + QImageReader reader(&buffer, it.next()); + if (!reader.supportsAnimation()) + it.remove(); + } + return list; +} + +/*! + \property QMovie::cacheMode + \brief the movie's cache mode + + Caching frames can be useful when the underlying animation format handler + that QMovie relies on to decode the animation data does not support + jumping to particular frames in the animation, or even "rewinding" the + animation to the beginning (for looping). Furthermore, if the image data + comes from a sequential device, it is not possible for the underlying + animation handler to seek back to frames whose data has already been read + (making looping altogether impossible). + + To aid in such situations, a QMovie object can be instructed to cache the + frames, at the added memory cost of keeping the frames in memory for the + lifetime of the object. + + By default, this property is set to \l CacheNone. + + \sa QMovie::CacheMode +*/ + +QMovie::CacheMode QMovie::cacheMode() const +{ + Q_D(const QMovie); + return d->cacheMode; +} + +void QMovie::setCacheMode(CacheMode cacheMode) +{ + Q_D(QMovie); + d->cacheMode = cacheMode; +} + +/*! + \internal +*/ +QMovie::CacheMode QMovie::cacheMode() +{ + Q_D(QMovie); + return d->cacheMode; +} + +QT_END_NAMESPACE + +#include "moc_qmovie.cpp" + +#endif // QT_NO_MOVIE diff --git a/src/gui/image/qmovie.h b/src/gui/image/qmovie.h new file mode 100644 index 0000000000..b64df29c41 --- /dev/null +++ b/src/gui/image/qmovie.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOVIE_H +#define QMOVIE_H + +#include + +#ifndef QT_NO_MOVIE + +#include +#include +#include +#include + +#ifdef QT3_SUPPORT +#include +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QByteArray; +class QColor; +class QIODevice; +class QImage; +class QPixmap; +class QRect; +class QSize; + +class QMoviePrivate; +class Q_GUI_EXPORT QMovie : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QMovie) + Q_ENUMS(MovieState CacheMode) + Q_PROPERTY(int speed READ speed WRITE setSpeed) + Q_PROPERTY(CacheMode cacheMode READ cacheMode WRITE setCacheMode) +public: + enum MovieState { + NotRunning, + Paused, + Running + }; + enum CacheMode { + CacheNone, + CacheAll + }; + + QMovie(QObject *parent = 0); + explicit QMovie(QIODevice *device, const QByteArray &format = QByteArray(), QObject *parent = 0); + explicit QMovie(const QString &fileName, const QByteArray &format = QByteArray(), QObject *parent = 0); + ~QMovie(); + + static QList supportedFormats(); + + void setDevice(QIODevice *device); + QIODevice *device() const; + + void setFileName(const QString &fileName); + QString fileName() const; + + void setFormat(const QByteArray &format); + QByteArray format() const; + + void setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + + MovieState state() const; + + QRect frameRect() const; + QImage currentImage() const; + QPixmap currentPixmap() const; + + bool isValid() const; + + bool jumpToFrame(int frameNumber); + int loopCount() const; + int frameCount() const; + int nextFrameDelay() const; + int currentFrameNumber() const; + + int speed() const; + + QSize scaledSize(); + void setScaledSize(const QSize &size); + + CacheMode cacheMode() const; + void setCacheMode(CacheMode mode); + + CacheMode cacheMode(); // ### Qt 5: remove me + +Q_SIGNALS: + void started(); + void resized(const QSize &size); + void updated(const QRect &rect); + void stateChanged(QMovie::MovieState state); + void error(QImageReader::ImageReaderError error); + void finished(); + void frameChanged(int frameNumber); + +public Q_SLOTS: + void start(); + bool jumpToNextFrame(); + void setPaused(bool paused); + void stop(); + void setSpeed(int percentSpeed); + +private: + Q_DISABLE_COPY(QMovie) + Q_PRIVATE_SLOT(d_func(), void _q_loadNextFrame()) + +#ifdef QT3_SUPPORT +public: + inline QT3_SUPPORT bool isNull() const { return isValid(); } + inline QT3_SUPPORT int frameNumber() const { return currentFrameNumber(); } + inline QT3_SUPPORT bool running() const { return state() == Running; } + inline QT3_SUPPORT bool paused() const { return state() == Paused; } + inline QT3_SUPPORT bool finished() const { return state() == NotRunning; } + inline QT3_SUPPORT void restart() { stop(); start(); } + inline QT3_SUPPORT QImage frameImage() const { return currentImage(); } + inline QT3_SUPPORT QPixmap framePixmap() const { return currentPixmap(); } + inline QT3_SUPPORT void step() { jumpToNextFrame(); } + inline QT3_SUPPORT void pause() { setPaused(true); } + inline QT3_SUPPORT void unpause() { setPaused(false); } +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_MOVIE + +#endif // QMOVIE_H diff --git a/src/gui/image/qnativeimage.cpp b/src/gui/image/qnativeimage.cpp new file mode 100644 index 0000000000..8face87a92 --- /dev/null +++ b/src/gui/image/qnativeimage.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qnativeimage_p.h" +#include "qcolormap.h" + +#include "private/qpaintengine_raster_p.h" + +#include "private/qapplication_p.h" +#include "private/qgraphicssystem_p.h" + +#if defined(Q_WS_X11) && !defined(QT_NO_MITSHM) +#include +#include +#include +#include +#endif + +#ifdef Q_WS_MAC +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_WIN +typedef struct { + BITMAPINFOHEADER bmiHeader; + DWORD redMask; + DWORD greenMask; + DWORD blueMask; +} BITMAPINFO_MASK; + + +QNativeImage::QNativeImage(int width, int height, QImage::Format format, bool isTextBuffer, QWidget *) +{ +#ifndef Q_WS_WINCE + Q_UNUSED(isTextBuffer); +#endif + BITMAPINFO_MASK bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biSizeImage = 0; + + if (format == QImage::Format_RGB16) { + bmi.bmiHeader.biBitCount = 16; +#ifdef Q_WS_WINCE + if (isTextBuffer) { + bmi.bmiHeader.biCompression = BI_RGB; + bmi.redMask = 0; + bmi.greenMask = 0; + bmi.blueMask = 0; + } else +#endif + { + bmi.bmiHeader.biCompression = BI_BITFIELDS; + bmi.redMask = 0xF800; + bmi.greenMask = 0x07E0; + bmi.blueMask = 0x001F; + } + } else { + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.redMask = 0; + bmi.greenMask = 0; + bmi.blueMask = 0; + } + + HDC display_dc = GetDC(0); + hdc = CreateCompatibleDC(display_dc); + ReleaseDC(0, display_dc); + Q_ASSERT(hdc); + + uchar *bits = 0; + bitmap = CreateDIBSection(hdc, reinterpret_cast(&bmi), DIB_RGB_COLORS, (void**) &bits, 0, 0); + Q_ASSERT(bitmap); + Q_ASSERT(bits); + + null_bitmap = (HBITMAP)SelectObject(hdc, bitmap); + image = QImage(bits, width, height, format); + + Q_ASSERT(image.paintEngine()->type() == QPaintEngine::Raster); + static_cast(image.paintEngine())->setDC(hdc); + +#ifndef Q_WS_WINCE + GdiFlush(); +#endif +} + +QNativeImage::~QNativeImage() +{ + if (bitmap || hdc) { + Q_ASSERT(hdc); + Q_ASSERT(bitmap); + if (null_bitmap) + SelectObject(hdc, null_bitmap); + DeleteDC(hdc); + DeleteObject(bitmap); + } +} + +QImage::Format QNativeImage::systemFormat() +{ + if (QColormap::instance().depth() == 16) + return QImage::Format_RGB16; + return QImage::Format_RGB32; +} + + +#elif defined(Q_WS_X11) && !defined(QT_NO_MITSHM) + +QNativeImage::QNativeImage(int width, int height, QImage::Format format,bool /* isTextBuffer */, QWidget *widget) + : xshmimg(0), xshmpm(0) +{ + if (!X11->use_mitshm) { + image = QImage(width, height, format); + // follow good coding practice and set xshminfo attributes, though values not used in this case + xshminfo.readOnly = true; + xshminfo.shmaddr = 0; + xshminfo.shmid = 0; + xshminfo.shmseg = 0; + return; + } + + QX11Info info = widget->x11Info(); + + int dd = info.depth(); + Visual *vis = (Visual*) info.visual(); + + xshmimg = XShmCreateImage(X11->display, vis, dd, ZPixmap, 0, &xshminfo, width, height); + if (!xshmimg) { + qWarning("QNativeImage: Unable to create shared XImage."); + return; + } + + bool ok; + xshminfo.shmid = shmget(IPC_PRIVATE, xshmimg->bytes_per_line * xshmimg->height, + IPC_CREAT | 0777); + ok = xshminfo.shmid != -1; + if (ok) { + xshmimg->data = (char*)shmat(xshminfo.shmid, 0, 0); + xshminfo.shmaddr = xshmimg->data; + ok = (xshminfo.shmaddr != (char*)-1); + if (ok) + image = QImage((uchar *)xshmimg->data, width, height, format); + } + xshminfo.readOnly = false; + if (ok) { + ok = XShmAttach(X11->display, &xshminfo); + XSync(X11->display, False); + if (shmctl(xshminfo.shmid, IPC_RMID, 0) == -1) + qWarning() << "Error while marking the shared memory segment to be destroyed"; + } + if (!ok) { + qWarning() << "QNativeImage: Unable to attach to shared memory segment."; + if (xshmimg->data) { + free(xshmimg->data); + xshmimg->data = 0; + } + XDestroyImage(xshmimg); + xshmimg = 0; + if (xshminfo.shmaddr) + shmdt(xshminfo.shmaddr); + if (xshminfo.shmid != -1) + shmctl(xshminfo.shmid, IPC_RMID, 0); + return; + } + if (X11->use_mitshm_pixmaps) { + xshmpm = XShmCreatePixmap(X11->display, DefaultRootWindow(X11->display), xshmimg->data, + &xshminfo, width, height, dd); + if (!xshmpm) { + qWarning() << "QNativeImage: Unable to create shared Pixmap."; + } + } +} + + +QNativeImage::~QNativeImage() +{ + if (!xshmimg) + return; + + if (xshmpm) { + XFreePixmap(X11->display, xshmpm); + xshmpm = 0; + } + XShmDetach(X11->display, &xshminfo); + xshmimg->data = 0; + XDestroyImage(xshmimg); + xshmimg = 0; + shmdt(xshminfo.shmaddr); + shmctl(xshminfo.shmid, IPC_RMID, 0); +} + +QImage::Format QNativeImage::systemFormat() +{ + if (QX11Info::appDepth() == 16) + return QImage::Format_RGB16; + return QImage::Format_RGB32; +} + +#elif defined(Q_WS_MAC) + +QNativeImage::QNativeImage(int width, int height, QImage::Format format, bool /* isTextBuffer */, QWidget *widget) + : image(width, height, format) +{ + + uint cgflags = kCGImageAlphaNoneSkipFirst; + switch (format) { + case QImage::Format_ARGB32: + cgflags = kCGImageAlphaFirst; + break; + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + cgflags = kCGImageAlphaPremultipliedFirst; + break; + default: + break; + } + +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + + cg = CGBitmapContextCreate(image.bits(), width, height, 8, image.bytesPerLine(), + QCoreGraphicsPaintEngine::macDisplayColorSpace(widget), cgflags); + CGContextTranslateCTM(cg, 0, height); + CGContextScaleCTM(cg, 1, -1); + + Q_ASSERT(image.paintEngine()->type() == QPaintEngine::Raster); + static_cast(image.paintEngine())->setCGContext(cg); +} + + +QNativeImage::~QNativeImage() +{ + CGContextRelease(cg); +} + +QImage::Format QNativeImage::systemFormat() +{ + return QImage::Format_RGB32; +} + + +#else // other platforms... + +QNativeImage::QNativeImage(int width, int height, QImage::Format format, bool /* isTextBuffer */, QWidget *) + : image(width, height, format) +{ + +} + + +QNativeImage::~QNativeImage() +{ +} + +QImage::Format QNativeImage::systemFormat() +{ +#ifdef Q_WS_QPA + return QApplicationPrivate::platformIntegration()->screens().at(0)->format(); +#else + return QImage::Format_RGB32; +#endif +} + +#endif // platforms + +QT_END_NAMESPACE + diff --git a/src/gui/image/qnativeimage_p.h b/src/gui/image/qnativeimage_p.h new file mode 100644 index 0000000000..12aa0f021a --- /dev/null +++ b/src/gui/image/qnativeimage_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNATIVEIMAGE_P_H +#define QNATIVEIMAGE_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 "qimage.h" + +#ifdef Q_WS_WIN +#include "qt_windows.h" + +#elif defined(Q_WS_X11) +#include + +#elif defined(Q_WS_MAC) +#include + +#endif + +QT_BEGIN_NAMESPACE + +class QWidget; + +class QNativeImage +{ +public: + QNativeImage(int width, int height, QImage::Format format, bool isTextBuffer = false, QWidget *widget = 0); + ~QNativeImage(); + + inline int width() const; + inline int height() const; + + QImage image; + + static QImage::Format systemFormat(); + +#ifdef Q_WS_WIN + HDC hdc; + HBITMAP bitmap; + HBITMAP null_bitmap; + +#elif defined(Q_WS_X11) && !defined(QT_NO_MITSHM) + XImage *xshmimg; + Pixmap xshmpm; + XShmSegmentInfo xshminfo; + +#elif defined(Q_WS_MAC) + CGContextRef cg; +#endif + +private: + Q_DISABLE_COPY(QNativeImage) +}; + +inline int QNativeImage::width() const { return image.width(); } +inline int QNativeImage::height() const { return image.height(); } + +QT_END_NAMESPACE + +#endif // QNATIVEIMAGE_P_H diff --git a/src/gui/image/qnativeimagehandleprovider_p.h b/src/gui/image/qnativeimagehandleprovider_p.h new file mode 100644 index 0000000000..4e6ed3852a --- /dev/null +++ b/src/gui/image/qnativeimagehandleprovider_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNATIVEIMAGEHANDLEPROVIDER_P_H +#define QNATIVEIMAGEHANDLEPROVIDER_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 + +QT_BEGIN_NAMESPACE + +class QNativeImageHandleProvider +{ +public: + virtual void get(void **handle, QString *type) = 0; + virtual void release(void *handle, const QString &type) = 0; +}; + +QT_END_NAMESPACE + +#endif // QNATIVEIMAGEHANDLEPROVIDER_P_H diff --git a/src/gui/image/qpaintengine_pic.cpp b/src/gui/image/qpaintengine_pic.cpp new file mode 100644 index 0000000000..6f0979a4b2 --- /dev/null +++ b/src/gui/image/qpaintengine_pic.cpp @@ -0,0 +1,539 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qpaintengine_p.h" +#include "private/qpainter_p.h" +#include "private/qpicture_p.h" +#include "private/qfont_p.h" + +#ifndef QT_NO_PICTURE + +#include "qbuffer.h" +#include "qbytearray.h" +#include "qdatastream.h" +#include "qmath.h" +#include "qpaintengine_pic_p.h" +#include "qpicture.h" +#include "qpolygon.h" +#include "qrect.h" +#include + +//#define QT_PICTURE_DEBUG +#include + + +QT_BEGIN_NAMESPACE + +class QPicturePaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QPicturePaintEngine) +public: + QDataStream s; + QPainter *pt; + QPicturePrivate *pic_d; +}; + +QPicturePaintEngine::QPicturePaintEngine() + : QPaintEngine(*(new QPicturePaintEnginePrivate), AllFeatures) +{ + Q_D(QPicturePaintEngine); + d->pt = 0; +} + +QPicturePaintEngine::QPicturePaintEngine(QPaintEnginePrivate &dptr) + : QPaintEngine(dptr, AllFeatures) +{ + Q_D(QPicturePaintEngine); + d->pt = 0; +} + +QPicturePaintEngine::~QPicturePaintEngine() +{ +} + +bool QPicturePaintEngine::begin(QPaintDevice *pd) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << "QPicturePaintEngine::begin()"; +#endif + Q_ASSERT(pd); + QPicture *pic = static_cast(pd); + + d->pdev = pd; + d->pic_d = pic->d_func(); + Q_ASSERT(d->pic_d); + + d->s.setDevice(&d->pic_d->pictb); + d->s.setVersion(d->pic_d->formatMajor); + + d->pic_d->pictb.open(QIODevice::WriteOnly | QIODevice::Truncate); + d->s.writeRawData(qt_mfhdr_tag, 4); + d->s << (quint16) 0 << (quint16) d->pic_d->formatMajor << (quint16) d->pic_d->formatMinor; + d->s << (quint8) QPicturePrivate::PdcBegin << (quint8) sizeof(qint32); + d->pic_d->brect = QRect(); + if (d->pic_d->formatMajor >= 4) { + QRect r = pic->boundingRect(); + d->s << (qint32) r.left() << (qint32) r.top() << (qint32) r.width() + << (qint32) r.height(); + } + d->pic_d->trecs = 0; + d->s << (quint32)d->pic_d->trecs; // total number of records + d->pic_d->formatOk = false; + setActive(true); + return true; +} + +bool QPicturePaintEngine::end() +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << "QPicturePaintEngine::end()"; +#endif + d->pic_d->trecs++; + d->s << (quint8) QPicturePrivate::PdcEnd << (quint8) 0; + int cs_start = sizeof(quint32); // pos of checksum word + int data_start = cs_start + sizeof(quint16); + int brect_start = data_start + 2*sizeof(qint16) + 2*sizeof(quint8); + int pos = d->pic_d->pictb.pos(); + d->pic_d->pictb.seek(brect_start); + if (d->pic_d->formatMajor >= 4) { // bounding rectangle + QRect r = static_cast(d->pdev)->boundingRect(); + d->s << (qint32) r.left() << (qint32) r.top() << (qint32) r.width() + << (qint32) r.height(); + } + d->s << (quint32) d->pic_d->trecs; // write number of records + d->pic_d->pictb.seek(cs_start); + QByteArray buf = d->pic_d->pictb.buffer(); + quint16 cs = (quint16) qChecksum(buf.constData() + data_start, pos - data_start); + d->s << cs; // write checksum + d->pic_d->pictb.close(); + setActive(false); + return true; +} + +#define SERIALIZE_CMD(c) \ + d->pic_d->trecs++; \ + d->s << (quint8) c; \ + d->s << (quint8) 0; \ + pos = d->pic_d->pictb.pos() + +void QPicturePaintEngine::updatePen(const QPen &pen) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updatePen(): width:" << pen.width() << "style:" + << pen.style() << "color:" << pen.color(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetPen); + if (d->pic_d->in_memory_only) { + int index = d->pic_d->pen_list.size(); + d->pic_d->pen_list.append(pen); + d->s << index; + } else { + d->s << pen; + } + writeCmdLength(pos, QRect(), false); +} + +void QPicturePaintEngine::updateCompositionMode(QPainter::CompositionMode cmode) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateCompositionMode():" << cmode; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetCompositionMode); + d->s << (qint32)cmode; + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateClipEnabled(bool enabled) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateClipEnabled():" << enabled; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetClipEnabled); + d->s << enabled; + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateOpacity(qreal opacity) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateOpacity():" << opacity; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetOpacity); + d->s << double(opacity); + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateBrush(const QBrush &brush) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateBrush(): style:" << brush.style(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetBrush); + if (d->pic_d->in_memory_only) { + int index = d->pic_d->brush_list.size(); + d->pic_d->brush_list.append(brush); + d->s << index; + } else { + d->s << brush; + } + writeCmdLength(pos, QRect(), false); +} + +void QPicturePaintEngine::updateBrushOrigin(const QPointF &p) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateBrushOrigin(): " << p; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetBrushOrigin); + d->s << p; + writeCmdLength(pos, QRect(), false); +} + +void QPicturePaintEngine::updateFont(const QFont &font) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateFont(): pt sz:" << font.pointSize(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetFont); + QFont fnt = font; + d->s << fnt; + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateBackground(Qt::BGMode bgMode, const QBrush &bgBrush) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateBackground(): mode:" << bgMode << "style:" << bgBrush.style(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetBkColor); + d->s << bgBrush.color(); + writeCmdLength(pos, QRect(), false); + + SERIALIZE_CMD(QPicturePrivate::PdcSetBkMode); + d->s << (qint8) bgMode; + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateMatrix(const QTransform &matrix) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateMatrix():" << matrix; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetWMatrix); + d->s << matrix << (qint8) false; + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateClipRegion(const QRegion ®ion, Qt::ClipOperation op) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateClipRegion(): op:" << op + << "bounding rect:" << region.boundingRect(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetClipRegion); + d->s << region << qint8(op); + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateClipPath(const QPainterPath &path, Qt::ClipOperation op) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateClipPath(): op:" << op + << "bounding rect:" << path.boundingRect(); +#endif + int pos; + + SERIALIZE_CMD(QPicturePrivate::PdcSetClipPath); + d->s << path << qint8(op); + writeCmdLength(pos, QRectF(), false); +} + +void QPicturePaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> updateRenderHints(): " << hints; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcSetRenderHint); + d->s << (quint32) hints; + writeCmdLength(pos, QRect(), false); +} + +void QPicturePaintEngine::writeCmdLength(int pos, const QRectF &r, bool corr) +{ + Q_D(QPicturePaintEngine); + int newpos = d->pic_d->pictb.pos(); // new position + int length = newpos - pos; + QRectF br(r); + + if (length < 255) { // write 8-bit length + d->pic_d->pictb.seek(pos - 1); // position to right index + d->s << (quint8)length; + } else { // write 32-bit length + d->s << (quint32)0; // extend the buffer + d->pic_d->pictb.seek(pos - 1); // position to right index + d->s << (quint8)255; // indicate 32-bit length + char *p = d->pic_d->pictb.buffer().data(); + memmove(p+pos+4, p+pos, length); // make room for 4 byte + d->s << (quint32)length; + newpos += 4; + } + d->pic_d->pictb.seek(newpos); // set to new position + + if (br.width() > 0.0 || br.height() > 0.0) { + if (corr) { // widen bounding rect + int w2 = painter()->pen().width() / 2; + br.setCoords(br.left() - w2, br.top() - w2, + br.right() + w2, br.bottom() + w2); + } + br = painter()->transform().mapRect(br); + if (painter()->hasClipping()) { + QRect cr = painter()->clipRegion().boundingRect(); + br &= cr; + } + + if (br.width() > 0.0 || br.height() > 0.0) { + int minx = qFloor(br.left()); + int miny = qFloor(br.top()); + int maxx = qCeil(br.right()); + int maxy = qCeil(br.bottom()); + + if (d->pic_d->brect.width() > 0 || d->pic_d->brect.height() > 0) { + minx = qMin(minx, d->pic_d->brect.left()); + miny = qMin(miny, d->pic_d->brect.top()); + maxx = qMax(maxx, d->pic_d->brect.x() + d->pic_d->brect.width()); + maxy = qMax(maxy, d->pic_d->brect.y() + d->pic_d->brect.height()); + d->pic_d->brect = QRect(minx, miny, maxx - minx, maxy - miny); + } else { + d->pic_d->brect = QRect(minx, miny, maxx - minx, maxy - miny); + } + } + } +} + +void QPicturePaintEngine::drawEllipse(const QRectF &rect) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawEllipse():" << rect; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawEllipse); + d->s << rect; + writeCmdLength(pos, rect, true); +} + +void QPicturePaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawPath():" << path.boundingRect(); +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawPath); + d->s << path; + writeCmdLength(pos, path.boundingRect(), true); +} + +void QPicturePaintEngine::drawPolygon(const QPointF *points, int numPoints, PolygonDrawMode mode) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawPolygon(): size=" << numPoints; +#endif + int pos; + + QPolygonF polygon; + for (int i=0; is << polygon; + } else { + SERIALIZE_CMD(QPicturePrivate::PdcDrawPolygon); + d->s << polygon; + d->s << (qint8)(mode == OddEvenMode ? 0 : 1); + } + + writeCmdLength(pos, polygon.boundingRect(), true); +} + +void QPicturePaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawPixmap():" << r; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawPixmap); + + if (d->pic_d->in_memory_only) { + int index = d->pic_d->pixmap_list.size(); + d->pic_d->pixmap_list.append(pm); + d->s << r << index << sr; + } else { + d->s << r << pm << sr; + } + writeCmdLength(pos, r, false); +} + +void QPicturePaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawTiledPixmap():" << r << s; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawTiledPixmap); + if (d->pic_d->in_memory_only) { + int index = d->pic_d->pixmap_list.size(); + d->pic_d->pixmap_list.append(pixmap); + d->s << r << index << s; + } else { + d->s << r << pixmap << s; + } + writeCmdLength(pos, r, false); +} + +void QPicturePaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawImage():" << r << sr; +#endif + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawImage); + if (d->pic_d->in_memory_only) { + int index = d->pic_d->image_list.size(); + d->pic_d->image_list.append(image); + d->s << r << index << sr << (quint32) flags; + } else { + d->s << r << image << sr << (quint32) flags; + } + writeCmdLength(pos, r, false); +} + +void QPicturePaintEngine::drawTextItem(const QPointF &p , const QTextItem &ti) +{ + Q_D(QPicturePaintEngine); +#ifdef QT_PICTURE_DEBUG + qDebug() << " -> drawTextItem():" << p << ti.text(); +#endif + + const QTextItemInt &si = static_cast(ti); + if (si.chars == 0) + QPaintEngine::drawTextItem(p, ti); // Draw as path + + if (d->pic_d->formatMajor >= 9) { + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawTextItem); + QFont fnt = ti.font(); + fnt.setUnderline(false); + fnt.setStrikeOut(false); + fnt.setOverline(false); + + qreal justificationWidth = 0; + if (si.justified) + justificationWidth = si.width.toReal(); + + d->s << p << ti.text() << fnt << ti.renderFlags() << double(fnt.d->dpi)/qt_defaultDpi() << justificationWidth; + writeCmdLength(pos, /*brect=*/QRectF(), /*corr=*/false); + } else if (d->pic_d->formatMajor >= 8) { + // old old (buggy) format + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawTextItem); + d->s << QPointF(p.x(), p.y() - ti.ascent()) << ti.text() << ti.font() << ti.renderFlags(); + writeCmdLength(pos, /*brect=*/QRectF(), /*corr=*/false); + } else { + // old (buggy) format + int pos; + SERIALIZE_CMD(QPicturePrivate::PdcDrawText2); + d->s << p << ti.text(); + writeCmdLength(pos, QRectF(p, QSizeF(1,1)), true); + } +} + +void QPicturePaintEngine::updateState(const QPaintEngineState &state) +{ + QPaintEngine::DirtyFlags flags = state.state(); + if (flags & DirtyPen) updatePen(state.pen()); + if (flags & DirtyBrush) updateBrush(state.brush()); + if (flags & DirtyBrushOrigin) updateBrushOrigin(state.brushOrigin()); + if (flags & DirtyFont) updateFont(state.font()); + if (flags & DirtyBackground) updateBackground(state.backgroundMode(), state.backgroundBrush()); + if (flags & DirtyTransform) updateMatrix(state.transform()); + if (flags & DirtyClipEnabled) updateClipEnabled(state.isClipEnabled()); + if (flags & DirtyClipRegion) updateClipRegion(state.clipRegion(), state.clipOperation()); + if (flags & DirtyClipPath) updateClipPath(state.clipPath(), state.clipOperation()); + if (flags & DirtyHints) updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) updateCompositionMode(state.compositionMode()); + if (flags & DirtyOpacity) updateOpacity(state.opacity()); +} + +QT_END_NAMESPACE + +#endif // QT_NO_PICTURE diff --git a/src/gui/image/qpaintengine_pic_p.h b/src/gui/image/qpaintengine_pic_p.h new file mode 100644 index 0000000000..0d6a327fb7 --- /dev/null +++ b/src/gui/image/qpaintengine_pic_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_PIC_P_H +#define QPAINTENGINE_PIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "QtGui/qpaintengine.h" + +#ifndef QT_NO_PICTURE + +QT_BEGIN_NAMESPACE + +class QPicturePaintEnginePrivate; +class QBuffer; + +class QPicturePaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QPicturePaintEngine) +public: + QPicturePaintEngine(); + ~QPicturePaintEngine(); + + bool begin(QPaintDevice *pdev); + bool end(); + + void updateState(const QPaintEngineState &state); + + void updatePen(const QPen &pen); + void updateBrush(const QBrush &brush); + void updateBrushOrigin(const QPointF &origin); + void updateFont(const QFont &font); + void updateBackground(Qt::BGMode bgmode, const QBrush &bgBrush); + void updateMatrix(const QTransform &matrix); + void updateClipRegion(const QRegion ®ion, Qt::ClipOperation op); + void updateClipPath(const QPainterPath &path, Qt::ClipOperation op); + void updateRenderHints(QPainter::RenderHints hints); + void updateCompositionMode(QPainter::CompositionMode cmode); + void updateClipEnabled(bool enabled); + void updateOpacity(qreal opacity); + + void drawEllipse(const QRectF &rect); + void drawPath(const QPainterPath &path); + void drawPolygon(const QPointF *points, int numPoints, PolygonDrawMode mode); +#ifdef Q_NO_USING_KEYWORD + inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) + { QPaintEngine::drawPolygon(points, pointCount, mode); } +#else + using QPaintEngine::drawPolygon; +#endif + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + void drawImage(const QRectF &r, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + void drawTextItem(const QPointF &p, const QTextItem &ti); + + Type type() const { return Picture; } + +protected: + QPicturePaintEngine(QPaintEnginePrivate &dptr); + +private: + Q_DISABLE_COPY(QPicturePaintEngine) + + void writeCmdLength(int pos, const QRectF &r, bool corr); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PICTURE + +#endif // QPAINTENGINE_PIC_P_H diff --git a/src/gui/image/qpicture.cpp b/src/gui/image/qpicture.cpp new file mode 100644 index 0000000000..3f38c0ce2a --- /dev/null +++ b/src/gui/image/qpicture.cpp @@ -0,0 +1,1999 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpicture.h" +#include + +#ifndef QT_NO_PICTURE + +#include +#include +#include + +#include "qdatastream.h" +#include "qfile.h" +#include "qimage.h" +#include "qmutex.h" +#include "qpainter.h" +#include "qpainterpath.h" +#include "qpixmap.h" +#include "qregion.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +void qt_format_text(const QFont &fnt, const QRectF &_r, + int tf, const QTextOption *opt, const QString& str, QRectF *brect, + int tabstops, int *, int tabarraylen, + QPainter *painter); + +/*! + \class QPicture + \brief The QPicture class is a paint device that records and + replays QPainter commands. + + \ingroup painting + \ingroup shared + + + A picture serializes painter commands to an IO device in a + platform-independent format. They are sometimes referred to as meta-files. + + Qt pictures use a proprietary binary format. Unlike native picture + (meta-file) formats on many window systems, Qt pictures have no + limitations regarding their contents. Everything that can be + painted on a widget or pixmap (e.g., fonts, pixmaps, regions, + transformed graphics, etc.) can also be stored in a picture. + + QPicture is resolution independent, i.e. a QPicture can be + displayed on different devices (for example svg, pdf, ps, printer + and screen) looking the same. This is, for instance, needed for + WYSIWYG print preview. QPicture runs in the default system dpi, + and scales the painter to match differences in resolution + depending on the window system. + + Example of how to record a picture: + \snippet doc/src/snippets/picture/picture.cpp 0 + + Note that the list of painter commands is reset on each call to + the QPainter::begin() function. + + Example of how to replay a picture: + \snippet doc/src/snippets/picture/picture.cpp 1 + + Pictures can also be drawn using play(). Some basic data about a + picture is available, for example, size(), isNull() and + boundingRect(). + + \sa QMovie +*/ + +const char *qt_mfhdr_tag = "QPIC"; // header tag +static const quint16 mfhdr_maj = 11; // major version # +static const quint16 mfhdr_min = 0; // minor version # + +/*! + Constructs an empty picture. + + The \a formatVersion parameter may be used to \e create a QPicture + that can be read by applications that are compiled with earlier + versions of Qt. + + Note that the default formatVersion is -1 which signifies the + current release, i.e. for Qt 4.0 a formatVersion of 7 is the same + as the default formatVersion of -1. + + Reading pictures generated by earlier versions of Qt is not + supported in Qt 4.0. +*/ + +QPicture::QPicture(int formatVersion) + : QPaintDevice(), + d_ptr(new QPicturePrivate) +{ + Q_D(QPicture); + + if (formatVersion == 0) + qWarning("QPicture: invalid format version 0"); + + // still accept the 0 default from before Qt 3.0. + if (formatVersion > 0 && formatVersion != (int)mfhdr_maj) { + d->formatMajor = formatVersion; + d->formatMinor = 0; + d->formatOk = false; + } else { + d->resetFormat(); + } +} + +/*! + Constructs a copy of \a pic. + + This constructor is fast thanks to \l{implicit sharing}. +*/ + +QPicture::QPicture(const QPicture &pic) + : QPaintDevice(), d_ptr(pic.d_ptr) +{ +} + +/*! \internal */ +QPicture::QPicture(QPicturePrivate &dptr) + : QPaintDevice(), + d_ptr(&dptr) +{ +} + +/*! + Destroys the picture. +*/ +QPicture::~QPicture() +{ +} + +/*! + \internal +*/ +int QPicture::devType() const +{ + return QInternal::Picture; +} + +/*! + \fn bool QPicture::isNull() const + + Returns true if the picture contains no data; otherwise returns + false. +*/ + +/*! + \fn uint QPicture::size() const + + Returns the size of the picture data. + + \sa data() +*/ + +/*! + \fn const char* QPicture::data() const + + Returns a pointer to the picture data. The pointer is only valid + until the next non-const function is called on this picture. The + returned pointer is 0 if the picture contains no data. + + \sa size(), isNull() +*/ + + +bool QPicture::isNull() const +{ + return d_func()->pictb.buffer().isNull(); +} + +uint QPicture::size() const +{ + return d_func()->pictb.buffer().size(); +} + +const char* QPicture::data() const +{ + return d_func()->pictb.buffer(); +} + +void QPicture::detach() +{ + d_ptr.detach(); +} + +bool QPicture::isDetached() const +{ + return d_func()->ref == 1; +} + +/*! + Sets the picture data directly from \a data and \a size. This + function copies the input data. + + \sa data(), size() +*/ + +void QPicture::setData(const char* data, uint size) +{ + detach(); + d_func()->pictb.setData(data, size); + d_func()->resetFormat(); // we'll have to check +} + + +/*! + Loads a picture from the file specified by \a fileName and returns + true if successful; otherwise returns false. + + Please note that the \a format parameter has been deprecated and + will have no effect. + + \sa save() +*/ + +bool QPicture::load(const QString &fileName, const char *format) +{ + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) + return false; + return load(&f, format); +} + +/*! + \overload + + \a dev is the device to use for loading. +*/ + +bool QPicture::load(QIODevice *dev, const char *format) +{ + if(format) { +#ifndef QT_NO_PICTUREIO + QPictureIO io(dev, format); + bool result = io.read(); + if (result) { + operator=(io.picture()); + + } else if (format) +#else + bool result = false; +#endif + { + qWarning("QPicture::load: No such picture format: %s", format); + } + return result; + } + + detach(); + QByteArray a = dev->readAll(); + + d_func()->pictb.setData(a); // set byte array in buffer + return d_func()->checkFormat(); +} + +/*! + Saves a picture to the file specified by \a fileName and returns + true if successful; otherwise returns false. + + Please note that the \a format parameter has been deprecated and + will have no effect. + + \sa load() +*/ + +bool QPicture::save(const QString &fileName, const char *format) +{ + if (paintingActive()) { + qWarning("QPicture::save: still being painted on. " + "Call QPainter::end() first"); + return false; + } + + + if(format) { +#ifndef QT_NO_PICTUREIO + QPictureIO io(fileName, format); + bool result = io.write(); + if (result) { + operator=(io.picture()); + } else if (format) +#else + bool result = false; +#endif + { + qWarning("QPicture::save: No such picture format: %s", format); + } + return result; + } + + QFile f(fileName); + if (!f.open(QIODevice::WriteOnly)) + return false; + return save(&f, format); +} + +/*! + \overload + + \a dev is the device to use for saving. +*/ + +bool QPicture::save(QIODevice *dev, const char *format) +{ + if (paintingActive()) { + qWarning("QPicture::save: still being painted on. " + "Call QPainter::end() first"); + return false; + } + + if(format) { +#ifndef QT_NO_PICTUREIO + QPictureIO io(dev, format); + bool result = io.write(); + if (result) { + operator=(io.picture()); + } else if (format) +#else + bool result = false; +#endif + { + qWarning("QPicture::save: No such picture format: %s", format); + } + return result; + } + + dev->write(d_func()->pictb.buffer(), d_func()->pictb.buffer().size()); + return true; +} + +/*! + Returns the picture's bounding rectangle or an invalid rectangle + if the picture contains no data. +*/ + +QRect QPicture::boundingRect() const +{ + Q_D(const QPicture); + // Use override rect where possible. + if (!d->override_rect.isEmpty()) + return d->override_rect; + + if (!d->formatOk) + d_ptr->checkFormat(); + + return d->brect; +} + +/*! + Sets the picture's bounding rectangle to \a r. The automatically + calculated value is overridden. +*/ + +void QPicture::setBoundingRect(const QRect &r) +{ + d_func()->override_rect = r; +} + +/*! + Replays the picture using \a painter, and returns true if + successful; otherwise returns false. + + This function does exactly the same as QPainter::drawPicture() + with (x, y) = (0, 0). +*/ + +bool QPicture::play(QPainter *painter) +{ + Q_D(QPicture); + + if (d->pictb.size() == 0) // nothing recorded + return true; + + if (!d->formatOk && !d->checkFormat()) + return false; + + d->pictb.open(QIODevice::ReadOnly); // open buffer device + QDataStream s; + s.setDevice(&d->pictb); // attach data stream to buffer + s.device()->seek(10); // go directly to the data + s.setVersion(d->formatMajor == 4 ? 3 : d->formatMajor); + + quint8 c, clen; + quint32 nrecords; + s >> c >> clen; + Q_ASSERT(c == QPicturePrivate::PdcBegin); + // bounding rect was introduced in ver 4. Read in checkFormat(). + if (d->formatMajor >= 4) { + qint32 dummy; + s >> dummy >> dummy >> dummy >> dummy; + } + s >> nrecords; + if (!exec(painter, s, nrecords)) { + qWarning("QPicture::play: Format error"); + d->pictb.close(); + return false; + } + d->pictb.close(); + return true; // no end-command +} + + +// +// QFakeDevice is used to create fonts with a custom DPI +// +class QFakeDevice : public QPaintDevice +{ +public: + QFakeDevice() { dpi_x = qt_defaultDpiX(); dpi_y = qt_defaultDpiY(); } + void setDpiX(int dpi) { dpi_x = dpi; } + void setDpiY(int dpi) { dpi_y = dpi; } + QPaintEngine *paintEngine() const { return 0; } + int metric(PaintDeviceMetric m) const + { + switch(m) { + case PdmPhysicalDpiX: + case PdmDpiX: + return dpi_x; + case PdmPhysicalDpiY: + case PdmDpiY: + return dpi_y; + default: + return QPaintDevice::metric(m); + } + } + +private: + int dpi_x; + int dpi_y; +}; + +/*! + \internal + Iterates over the internal picture data and draws the picture using + \a painter. +*/ + +bool QPicture::exec(QPainter *painter, QDataStream &s, int nrecords) +{ + Q_D(QPicture); +#if defined(QT_DEBUG) + int strm_pos; +#endif + quint8 c; // command id + quint8 tiny_len; // 8-bit length descriptor + qint32 len; // 32-bit length descriptor + qint16 i_16, i1_16, i2_16; // parameters... + qint8 i_8; + quint32 ul; + double dbl; + bool bl; + QByteArray str1; + QString str; + QPointF p, p1, p2; + QPoint ip, ip1, ip2; + QRect ir; + QRectF r; + QPolygonF a; + QPolygon ia; + QColor color; + QFont font; + QPen pen; + QBrush brush; + QRegion rgn; + QMatrix wmatrix; + QTransform matrix; + + QTransform worldMatrix = painter->transform(); + worldMatrix.scale(qreal(painter->device()->logicalDpiX()) / qreal(qt_defaultDpiX()), + qreal(painter->device()->logicalDpiY()) / qreal(qt_defaultDpiY())); + painter->setTransform(worldMatrix); + + while (nrecords-- && !s.atEnd()) { + s >> c; // read cmd + s >> tiny_len; // read param length + if (tiny_len == 255) // longer than 254 bytes + s >> len; + else + len = tiny_len; +#if defined(QT_DEBUG) + strm_pos = s.device()->pos(); +#endif + switch (c) { // exec cmd + case QPicturePrivate::PdcNOP: + break; + case QPicturePrivate::PdcDrawPoint: + if (d->formatMajor <= 5) { + s >> ip; + painter->drawPoint(ip); + } else { + s >> p; + painter->drawPoint(p); + } + break; + case QPicturePrivate::PdcDrawPoints: +// ## implement me in the picture paint engine +// s >> a >> i1_32 >> i2_32; +// painter->drawPoints(a.mid(i1_32, i2_32)); + break; + case QPicturePrivate::PdcDrawPath: { + QPainterPath path; + s >> path; + painter->drawPath(path); + break; + } + case QPicturePrivate::PdcDrawLine: + if (d->formatMajor <= 5) { + s >> ip1 >> ip2; + painter->drawLine(ip1, ip2); + } else { + s >> p1 >> p2; + painter->drawLine(p1, p2); + } + break; + case QPicturePrivate::PdcDrawRect: + if (d->formatMajor <= 5) { + s >> ir; + painter->drawRect(ir); + } else { + s >> r; + painter->drawRect(r); + } + break; + case QPicturePrivate::PdcDrawRoundRect: + if (d->formatMajor <= 5) { + s >> ir >> i1_16 >> i2_16; + painter->drawRoundedRect(ir, i1_16, i2_16, Qt::RelativeSize); + } else { + s >> r >> i1_16 >> i2_16; + painter->drawRoundedRect(r, i1_16, i2_16, Qt::RelativeSize); + } + break; + case QPicturePrivate::PdcDrawEllipse: + if (d->formatMajor <= 5) { + s >> ir; + painter->drawEllipse(ir); + } else { + s >> r; + painter->drawEllipse(r); + } + break; + case QPicturePrivate::PdcDrawArc: + if (d->formatMajor <= 5) { + s >> ir; + r = ir; + } else { + s >> r; + } + s >> i1_16 >> i2_16; + painter->drawArc(r, i1_16, i2_16); + break; + case QPicturePrivate::PdcDrawPie: + if (d->formatMajor <= 5) { + s >> ir; + r = ir; + } else { + s >> r; + } + s >> i1_16 >> i2_16; + painter->drawPie(r, i1_16, i2_16); + break; + case QPicturePrivate::PdcDrawChord: + if (d->formatMajor <= 5) { + s >> ir; + r = ir; + } else { + s >> r; + } + s >> i1_16 >> i2_16; + painter->drawChord(r, i1_16, i2_16); + break; + case QPicturePrivate::PdcDrawLineSegments: + s >> ia; + painter->drawLines(ia); + ia.clear(); + break; + case QPicturePrivate::PdcDrawPolyline: + if (d->formatMajor <= 5) { + s >> ia; + painter->drawPolyline(ia); + ia.clear(); + } else { + s >> a; + painter->drawPolyline(a); + a.clear(); + } + break; + case QPicturePrivate::PdcDrawPolygon: + if (d->formatMajor <= 5) { + s >> ia >> i_8; + painter->drawPolygon(ia, i_8 ? Qt::WindingFill : Qt::OddEvenFill); + a.clear(); + } else { + s >> a >> i_8; + painter->drawPolygon(a, i_8 ? Qt::WindingFill : Qt::OddEvenFill); + a.clear(); + } + break; + case QPicturePrivate::PdcDrawCubicBezier: { + s >> ia; + QPainterPath path; + Q_ASSERT(ia.size() == 4); + path.moveTo(ia.at(0)); + path.cubicTo(ia.at(1), ia.at(2), ia.at(3)); + painter->strokePath(path, painter->pen()); + a.clear(); + } + break; + case QPicturePrivate::PdcDrawText: + s >> ip >> str1; + painter->drawText(ip, QString::fromLatin1(str1)); + break; + case QPicturePrivate::PdcDrawTextFormatted: + s >> ir >> i_16 >> str1; + painter->drawText(ir, i_16, QString::fromLatin1(str1)); + break; + case QPicturePrivate::PdcDrawText2: + if (d->formatMajor <= 5) { + s >> ip >> str; + painter->drawText(ip, str); + } else { + s >> p >> str; + painter->drawText(p, str); + } + break; + case QPicturePrivate::PdcDrawText2Formatted: + s >> ir; + s >> i_16; + s >> str; + painter->drawText(ir, i_16, str); + break; + case QPicturePrivate::PdcDrawTextItem: { + s >> p >> str >> font >> ul; + + // the text layout direction is not used here because it's already + // aligned when QPicturePaintEngine::drawTextItem() serializes the + // drawText() call, therefore ul is unsed in this context + + if (d->formatMajor >= 9) { + s >> dbl; + QFont fnt(font); + if (dbl != 1.0) { + QFakeDevice fake; + fake.setDpiX(qRound(dbl*qt_defaultDpiX())); + fake.setDpiY(qRound(dbl*qt_defaultDpiY())); + fnt = QFont(font, &fake); + } + + qreal justificationWidth; + s >> justificationWidth; + + int flags = Qt::TextSingleLine | Qt::TextDontClip | Qt::TextForceLeftToRight; + + QSizeF size(1, 1); + if (justificationWidth > 0) { + size.setWidth(justificationWidth); + flags |= Qt::TextJustificationForced; + flags |= Qt::AlignJustify; + } + + QFontMetrics fm(fnt); + QPointF pt(p.x(), p.y() - fm.ascent()); + qt_format_text(fnt, QRectF(pt, size), flags, /*opt*/0, + str, /*brect=*/0, /*tabstops=*/0, /*...*/0, /*tabarraylen=*/0, painter); + } else { + qt_format_text(font, QRectF(p, QSizeF(1, 1)), Qt::TextSingleLine | Qt::TextDontClip, /*opt*/0, + str, /*brect=*/0, /*tabstops=*/0, /*...*/0, /*tabarraylen=*/0, painter); + } + + break; + } + case QPicturePrivate::PdcDrawPixmap: { + QPixmap pixmap; + if (d->formatMajor < 4) { + s >> ip >> pixmap; + painter->drawPixmap(ip, pixmap); + } else if (d->formatMajor <= 5) { + s >> ir >> pixmap; + painter->drawPixmap(ir, pixmap); + } else { + QRectF sr; + if (d->in_memory_only) { + int index; + s >> r >> index >> sr; + Q_ASSERT(index < d->pixmap_list.size()); + pixmap = d->pixmap_list.at(index); + } else { + s >> r >> pixmap >> sr; + } + painter->drawPixmap(r, pixmap, sr); + } + } + break; + case QPicturePrivate::PdcDrawTiledPixmap: { + QPixmap pixmap; + if (d->in_memory_only) { + int index; + s >> r >> index >> p; + Q_ASSERT(index < d->pixmap_list.size()); + pixmap = d->pixmap_list.at(index); + } else { + s >> r >> pixmap >> p; + } + painter->drawTiledPixmap(r, pixmap, p); + } + break; + case QPicturePrivate::PdcDrawImage: { + QImage image; + if (d->formatMajor < 4) { + s >> p >> image; + painter->drawImage(p, image); + } else if (d->formatMajor <= 5){ + s >> ir >> image; + painter->drawImage(ir, image, QRect(0, 0, ir.width(), ir.height())); + } else { + QRectF sr; + if (d->in_memory_only) { + int index; + s >> r >> index >> sr >> ul; + Q_ASSERT(index < d->image_list.size()); + image = d->image_list.at(index); + } else { + s >> r >> image >> sr >> ul; + } + painter->drawImage(r, image, sr, Qt::ImageConversionFlags(ul)); + } + } + break; + case QPicturePrivate::PdcBegin: + s >> ul; // number of records + if (!exec(painter, s, ul)) + return false; + break; + case QPicturePrivate::PdcEnd: + if (nrecords == 0) + return true; + break; + case QPicturePrivate::PdcSave: + painter->save(); + break; + case QPicturePrivate::PdcRestore: + painter->restore(); + break; + case QPicturePrivate::PdcSetBkColor: + s >> color; + painter->setBackground(color); + break; + case QPicturePrivate::PdcSetBkMode: + s >> i_8; + painter->setBackgroundMode((Qt::BGMode)i_8); + break; + case QPicturePrivate::PdcSetROP: // NOP + s >> i_8; + break; + case QPicturePrivate::PdcSetBrushOrigin: + if (d->formatMajor <= 5) { + s >> ip; + painter->setBrushOrigin(ip); + } else { + s >> p; + painter->setBrushOrigin(p); + } + break; + case QPicturePrivate::PdcSetFont: + s >> font; + painter->setFont(font); + break; + case QPicturePrivate::PdcSetPen: + if (d->in_memory_only) { + int index; + s >> index; + Q_ASSERT(index < d->pen_list.size()); + pen = d->pen_list.at(index); + } else { + s >> pen; + } + painter->setPen(pen); + break; + case QPicturePrivate::PdcSetBrush: + if (d->in_memory_only) { + int index; + s >> index; + Q_ASSERT(index < d->brush_list.size()); + brush = d->brush_list.at(index); + } else { + s >> brush; + } + painter->setBrush(brush); + break; +// #ifdef Q_Q3PAINTER +// case QPicturePrivate::PdcSetTabStops: +// s >> i_16; +// painter->setTabStops(i_16); +// break; +// case QPicturePrivate::PdcSetTabArray: +// s >> i_16; +// if (i_16 == 0) { +// painter->setTabArray(0); +// } else { +// int *ta = new int[i_16]; +// for (int i=0; i> i1_16; +// ta[i] = i1_16; +// } +// painter->setTabArray(ta); +// delete [] ta; +// } +// break; +// #endif + case QPicturePrivate::PdcSetVXform: + s >> i_8; + painter->setViewTransformEnabled(i_8); + break; + case QPicturePrivate::PdcSetWindow: + if (d->formatMajor <= 5) { + s >> ir; + painter->setWindow(ir); + } else { + s >> r; + painter->setWindow(r.toRect()); + } + break; + case QPicturePrivate::PdcSetViewport: + if (d->formatMajor <= 5) { + s >> ir; + painter->setViewport(ir); + } else { + s >> r; + painter->setViewport(r.toRect()); + } + break; + case QPicturePrivate::PdcSetWXform: + s >> i_8; + painter->setMatrixEnabled(i_8); + break; + case QPicturePrivate::PdcSetWMatrix: + if (d->formatMajor >= 8) { + s >> matrix >> i_8; + } else { + s >> wmatrix >> i_8; + matrix = QTransform(wmatrix); + } + // i_8 is always false due to updateXForm() in qpaintengine_pic.cpp + painter->setTransform(matrix * worldMatrix, i_8); + break; +// #ifdef Q_Q3PAINTER +// case QPicturePrivate::PdcSaveWMatrix: +// painter->saveWorldMatrix(); +// break; +// case QPicturePrivate::PdcRestoreWMatrix: +// painter->restoreWorldMatrix(); +// break; +// #endif + case QPicturePrivate::PdcSetClip: + s >> i_8; + painter->setClipping(i_8); + break; + case QPicturePrivate::PdcSetClipRegion: + s >> rgn >> i_8; + if (d->formatMajor >= 9) { + painter->setClipRegion(rgn, Qt::ClipOperation(i_8)); + } else { + painter->setClipRegion(rgn); + } + break; + case QPicturePrivate::PdcSetClipPath: + { + QPainterPath path; + s >> path >> i_8; + painter->setClipPath(path, Qt::ClipOperation(i_8)); + break; + } + case QPicturePrivate::PdcSetRenderHint: + s >> ul; + painter->setRenderHint(QPainter::Antialiasing, + bool(ul & QPainter::Antialiasing)); + painter->setRenderHint(QPainter::SmoothPixmapTransform, + bool(ul & QPainter::SmoothPixmapTransform)); + break; + case QPicturePrivate::PdcSetCompositionMode: + s >> ul; + painter->setCompositionMode((QPainter::CompositionMode)ul); + break; + case QPicturePrivate::PdcSetClipEnabled: + s >> bl; + painter->setClipping(bl); + break; + case QPicturePrivate::PdcSetOpacity: + s >> dbl; + painter->setOpacity(qreal(dbl)); + break; + default: + qWarning("QPicture::play: Invalid command %d", c); + if (len) // skip unknown command + s.device()->seek(s.device()->pos()+len); + } +#if defined(QT_DEBUG) + //qDebug("device->at(): %i, strm_pos: %i len: %i", (int)s.device()->pos(), strm_pos, len); + Q_ASSERT(qint32(s.device()->pos() - strm_pos) == len); +#endif + } + return false; +} + +/*! + \internal + + Internal implementation of the virtual QPaintDevice::metric() + function. + + A picture has the following hard-coded values: numcolors=16777216 + and depth=24. + + \a m is the metric to get. +*/ + +int QPicture::metric(PaintDeviceMetric m) const +{ + int val; + QRect brect = boundingRect(); + switch (m) { + case PdmWidth: + val = brect.width(); + break; + case PdmHeight: + val = brect.height(); + break; + case PdmWidthMM: + val = int(25.4/qt_defaultDpiX()*brect.width()); + break; + case PdmHeightMM: + val = int(25.4/qt_defaultDpiY()*brect.height()); + break; + case PdmDpiX: + case PdmPhysicalDpiX: + val = qt_defaultDpiX(); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + val = qt_defaultDpiY(); + break; + case PdmNumColors: + val = 16777216; + break; + case PdmDepth: + val = 24; + break; + default: + val = 0; + qWarning("QPicture::metric: Invalid metric command"); + } + return val; +} + +/*! + \fn void QPicture::detach() + \internal + Detaches from shared picture data and makes sure that this picture + is the only one referring to the data. + + If multiple pictures share common data, this picture makes a copy + of the data and detaches itself from the sharing mechanism. + Nothing is done if there is just a single reference. +*/ + +/*! \fn bool QPicture::isDetached() const +\internal +*/ + +/*! \internal +### Qt 5 - remove me + */ +void QPicture::detach_helper() +{ + // QExplicitelySharedDataPointer takes care of cloning using + // QPicturePrivate's copy constructor. Do not call detach_helper() anymore + // and remove in Qt 5, please. + Q_ASSERT_X(false, "QPicture::detach_helper()", "Do not call this function"); +} + +/*! + Assigns picture \a p to this picture and returns a reference to + this picture. +*/ +QPicture& QPicture::operator=(const QPicture &p) +{ + d_ptr = p.d_ptr; + return *this; +} + +/*! + \fn void QPicture::swap(QPicture &other) + \since 4.8 + + Swaps picture \a other with this picture. This operation is very + fast and never fails. +*/ + +/*! + \internal + + Constructs a QPicturePrivate +*/ +QPicturePrivate::QPicturePrivate() + : in_memory_only(false) +{ +} + +/*! + \internal + + Copy-Constructs a QPicturePrivate. Needed when detaching. +*/ +QPicturePrivate::QPicturePrivate(const QPicturePrivate &other) + : trecs(other.trecs), + formatOk(other.formatOk), + formatMinor(other.formatMinor), + brect(other.brect), + override_rect(other.override_rect), + in_memory_only(false) +{ + pictb.setData(other.pictb.data(), other.pictb.size()); + if (other.pictb.isOpen()) { + pictb.open(other.pictb.openMode()); + pictb.seek(other.pictb.pos()); + } +} + +/*! + \internal + + Sets formatOk to false and resets the format version numbers to default +*/ + +void QPicturePrivate::resetFormat() +{ + formatOk = false; + formatMajor = mfhdr_maj; + formatMinor = mfhdr_min; +} + + +/*! + \internal + + Checks data integrity and format version number. Set formatOk to + true on success, to false otherwise. Returns the resulting formatOk + value. +*/ +bool QPicturePrivate::checkFormat() +{ + resetFormat(); + + // can't check anything in an empty buffer + if (pictb.size() == 0 || pictb.isOpen()) + return false; + + pictb.open(QIODevice::ReadOnly); // open buffer device + QDataStream s; + s.setDevice(&pictb); // attach data stream to buffer + + char mf_id[4]; // picture header tag + s.readRawData(mf_id, 4); // read actual tag + if (memcmp(mf_id, qt_mfhdr_tag, 4) != 0) { // wrong header id + qWarning("QPicturePaintEngine::checkFormat: Incorrect header"); + pictb.close(); + return false; + } + + int cs_start = sizeof(quint32); // pos of checksum word + int data_start = cs_start + sizeof(quint16); + quint16 cs,ccs; + QByteArray buf = pictb.buffer(); // pointer to data + + s >> cs; // read checksum + ccs = (quint16) qChecksum(buf.constData() + data_start, buf.size() - data_start); + if (ccs != cs) { + qWarning("QPicturePaintEngine::checkFormat: Invalid checksum %x, %x expected", + ccs, cs); + pictb.close(); + return false; + } + + quint16 major, minor; + s >> major >> minor; // read version number + if (major > mfhdr_maj) { // new, incompatible version + qWarning("QPicturePaintEngine::checkFormat: Incompatible version %d.%d", + major, minor); + pictb.close(); + return false; + } + s.setVersion(major != 4 ? major : 3); + + quint8 c, clen; + s >> c >> clen; + if (c == QPicturePrivate::PdcBegin) { + if (!(major >= 1 && major <= 3)) { + qint32 l, t, w, h; + s >> l >> t >> w >> h; + brect = QRect(l, t, w, h); + } + } else { + qWarning("QPicturePaintEngine::checkFormat: Format error"); + pictb.close(); + return false; + } + pictb.close(); + + formatOk = true; // picture seems to be ok + formatMajor = major; + formatMinor = minor; + return true; +} + +/*! \internal */ +QPaintEngine *QPicture::paintEngine() const +{ + if (!d_func()->paintEngine) + const_cast(this)->d_func()->paintEngine.reset(new QPicturePaintEngine); + return d_func()->paintEngine.data(); +} + +/***************************************************************************** + QPicture stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +/*! + \relates QPicture + + Writes picture \a r to the stream \a s and returns a reference to + the stream. +*/ + +QDataStream &operator<<(QDataStream &s, const QPicture &r) +{ + quint32 size = r.d_func()->pictb.buffer().size(); + s << size; + // null picture ? + if (size == 0) + return s; + // just write the whole buffer to the stream + s.writeRawData (r.d_func()->pictb.buffer(), r.d_func()->pictb.buffer().size()); + return s; +} + +/*! + \relates QPicture + + Reads a picture from the stream \a s into picture \a r and returns + a reference to the stream. +*/ + +QDataStream &operator>>(QDataStream &s, QPicture &r) +{ + QDataStream sr; + + // "init"; this code is similar to the beginning of QPicture::cmd() + sr.setDevice(&r.d_func()->pictb); + sr.setVersion(r.d_func()->formatMajor); + quint32 len; + s >> len; + QByteArray data; + if (len > 0) { + data.resize(len); + s.readRawData(data.data(), len); + } + + r.d_func()->pictb.setData(data); + r.d_func()->resetFormat(); + return s; +} +#endif // QT_NO_DATASTREAM + + +#ifndef QT_NO_PICTUREIO + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qregexp.h" +#include "qapplication.h" +#include "qpictureformatplugin.h" +QT_END_INCLUDE_NAMESPACE + +/*! + \obsolete + + Returns a string that specifies the picture format of the file \a + fileName, or 0 if the file cannot be read or if the format is not + recognized. + + \sa load() save() +*/ + +const char* QPicture::pictureFormat(const QString &fileName) +{ + return QPictureIO::pictureFormat(fileName); +} + +/*! + \obsolete + + Returns a list of picture formats that are supported for picture + input. + + \sa outputFormats() inputFormatList() QPictureIO +*/ +QList QPicture::inputFormats() +{ + return QPictureIO::inputFormats(); +} + +static QStringList qToStringList(const QList arr) +{ + QStringList list; + for (int i = 0; i < arr.count(); ++i) + list.append(QString::fromLatin1(arr.at(i))); + return list; +} + +/*! + \obsolete + + Returns a list of picture formats that are supported for picture + input. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/picture/picture.cpp 2 + + \sa outputFormatList() inputFormats() QPictureIO +*/ +QStringList QPicture::inputFormatList() +{ + return qToStringList(QPictureIO::inputFormats()); +} + + +/*! + \obsolete + + Returns a list of picture formats that are supported for picture + output. + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \snippet doc/src/snippets/picture/picture.cpp 3 + + \sa inputFormatList() outputFormats() QPictureIO +*/ +QStringList QPicture::outputFormatList() +{ + return qToStringList(QPictureIO::outputFormats()); +} + +/*! + \obsolete + + Returns a list of picture formats that are supported for picture + output. + + \sa inputFormats() outputFormatList() QPictureIO +*/ +QList QPicture::outputFormats() +{ + return QPictureIO::outputFormats(); +} + +/***************************************************************************** + QPictureIO member functions + *****************************************************************************/ + +/*! + \obsolete + + \class QPictureIO + + \brief The QPictureIO class contains parameters for loading and + saving pictures. + + \ingroup painting + \ingroup io + + QPictureIO contains a QIODevice object that is used for picture data + I/O. The programmer can install new picture file formats in addition + to those that Qt provides. + + You don't normally need to use this class; QPicture::load(), + QPicture::save(). + + \sa QPicture QPixmap QFile +*/ + +struct QPictureIOData +{ + QPicture pi; // picture + int iostat; // IO status + QByteArray frmt; // picture format + QIODevice *iodev; // IO device + QString fname; // file name + QString descr; // picture description + const char *parameters; + int quality; + float gamma; +}; + +/*! + Constructs a QPictureIO object with all parameters set to zero. +*/ + +QPictureIO::QPictureIO() +{ + init(); +} + +/*! + Constructs a QPictureIO object with the I/O device \a ioDevice and a + \a format tag. +*/ + +QPictureIO::QPictureIO(QIODevice *ioDevice, const char *format) +{ + init(); + d->iodev = ioDevice; + d->frmt = format; +} + +/*! + Constructs a QPictureIO object with the file name \a fileName and a + \a format tag. +*/ + +QPictureIO::QPictureIO(const QString &fileName, const char* format) +{ + init(); + d->frmt = format; + d->fname = fileName; +} + +/*! + Contains initialization common to all QPictureIO constructors. +*/ + +void QPictureIO::init() +{ + d = new QPictureIOData(); + d->parameters = 0; + d->quality = -1; // default quality of the current format + d->gamma=0.0f; + d->iostat = 0; + d->iodev = 0; +} + +/*! + Destroys the object and all related data. +*/ + +QPictureIO::~QPictureIO() +{ + if (d->parameters) + delete [] (char*)d->parameters; + delete d; +} + + +/***************************************************************************** + QPictureIO picture handler functions + *****************************************************************************/ + +class QPictureHandler +{ +public: + QPictureHandler(const char *f, const char *h, const QByteArray& fl, + picture_io_handler r, picture_io_handler w); + QByteArray format; // picture format + QRegExp header; // picture header pattern + enum TMode { Untranslated=0, TranslateIn, TranslateInOut } text_mode; + picture_io_handler read_picture; // picture read function + picture_io_handler write_picture; // picture write function + bool obsolete; // support not "published" +}; + +QPictureHandler::QPictureHandler(const char *f, const char *h, const QByteArray& fl, + picture_io_handler r, picture_io_handler w) + : format(f), header(QString::fromLatin1(h)) +{ + text_mode = Untranslated; + if (fl.contains('t')) + text_mode = TranslateIn; + else if (fl.contains('T')) + text_mode = TranslateInOut; + obsolete = fl.contains('O'); + read_picture = r; + write_picture = w; +} + +typedef QList QPHList; +Q_GLOBAL_STATIC(QPHList, pictureHandlers) + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC(QMutex, mutex) +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, factoryLoader, + (QPictureFormatInterface_iid, + QLatin1String("/pictureformats"))) +#endif +void qt_init_picture_plugins() +{ +#ifndef QT_NO_LIBRARY + QMutexLocker locker(mutex()); + QFactoryLoader *loader = factoryLoader(); + QStringList keys = loader->keys(); + for (int i = 0; i < keys.count(); ++i) + if (QPictureFormatInterface *format = qobject_cast(loader->instance(keys.at(i)))) + format->installIOHandler(keys.at(i)); +#endif +} + +static void cleanup() +{ + // make sure that picture handlers are delete before plugin manager + if (QPHList *list = pictureHandlers()) { + qDeleteAll(*list); + list->clear(); + } +} + +void qt_init_picture_handlers() // initialize picture handlers +{ + static QBasicAtomicInt done = Q_BASIC_ATOMIC_INITIALIZER(0); + if (done.testAndSetRelaxed(0, 1)) { + qAddPostRoutine(cleanup); + } +} + +static QPictureHandler *get_picture_handler(const char *format) +{ // get pointer to handler + qt_init_picture_handlers(); + qt_init_picture_plugins(); + if (QPHList *list = pictureHandlers()) { + for (int i = 0; i < list->size(); ++i) { + if (list->at(i)->format == format) + return list->at(i); + } + } + return 0; // no such handler +} + + +/*! + Defines a picture I/O handler for the picture format called \a + format, which is recognized using the regular + expression defined in \a header, read using \a readPicture and + written using \a writePicture. + + \a flags is a string of single-character flags for this format. + The only flag defined currently is T (upper case), so the only + legal value for \a flags are "T" and the empty string. The "T" + flag means that the picture file is a text file, and Qt should treat + all newline conventions as equivalent. (XPM files and some PPM + files are text files for example.) + + \a format is used to select a handler to write a QPicture; \a header + is used to select a handler to read an picture file. + + If \a readPicture is a null pointer, the QPictureIO will not be able + to read pictures in \a format. If \a writePicture is a null pointer, + the QPictureIO will not be able to write pictures in \a format. If + both are null, the QPictureIO object is valid but useless. + + Example: + \snippet doc/src/snippets/picture/picture.cpp 6 + \codeline + \snippet doc/src/snippets/picture/picture.cpp 7 + \codeline + \snippet doc/src/snippets/picture/picture.cpp 8 + + Before the regular expression test, all the 0 bytes in the file header are + converted to 1 bytes. This is done because when Qt was ASCII-based, QRegExp + could not handle 0 bytes in strings. + + The regexp is only applied on the first 14 bytes of the file. + + (Note that if one handlerIO supports writing a format and another + supports reading it, Qt supports both reading and writing. If two + handlers support the same operation, Qt chooses one arbitrarily.) +*/ + +void QPictureIO::defineIOHandler(const char *format, + const char *header, + const char *flags, + picture_io_handler readPicture, + picture_io_handler writePicture) +{ + qt_init_picture_handlers(); + if (QPHList *list = pictureHandlers()) { + QPictureHandler *p; + p = new QPictureHandler(format, header, QByteArray(flags), readPicture, writePicture); + list->prepend(p); + } +} + + +/***************************************************************************** + QPictureIO normal member functions + *****************************************************************************/ + +/*! + Returns the picture currently set. + + \sa setPicture() +*/ +const QPicture &QPictureIO::picture() const { return d->pi; } + +/*! + Returns the picture's IO status. A non-zero value indicates an + error, whereas 0 means that the IO operation was successful. + + \sa setStatus() +*/ +int QPictureIO::status() const { return d->iostat; } + +/*! + Returns the picture format string or 0 if no format has been + explicitly set. +*/ +const char *QPictureIO::format() const { return d->frmt; } + +/*! + Returns the IO device currently set. + + \sa setIODevice() +*/ +QIODevice *QPictureIO::ioDevice() const { return d->iodev; } + +/*! + Returns the file name currently set. + + \sa setFileName() +*/ +QString QPictureIO::fileName() const { return d->fname; } + + +/*! + Returns the picture description string. + + \sa setDescription() +*/ +QString QPictureIO::description() const { return d->descr; } + +/*! + Sets the picture to \a picture. + + \sa picture() +*/ +void QPictureIO::setPicture(const QPicture &picture) +{ + d->pi = picture; +} + +/*! + Sets the picture IO status to \a status. A non-zero value indicates + an error, whereas 0 means that the IO operation was successful. + + \sa status() +*/ +void QPictureIO::setStatus(int status) +{ + d->iostat = status; +} + +/*! + Sets the picture format to \a format for the picture to be read or + written. + + It is necessary to specify a format before writing an picture, but + it is not necessary to specify a format before reading an picture. + + If no format has been set, Qt guesses the picture format before + reading it. If a format is set the picture will only be read if it + has that format. + + \sa read() write() format() +*/ +void QPictureIO::setFormat(const char *format) +{ + d->frmt = format; +} + +/*! + Sets the IO device to be used for reading or writing an picture. + + Setting the IO device allows pictures to be read/written to any + block-oriented QIODevice. + + If \a ioDevice is not null, this IO device will override file name + settings. + + \sa setFileName() +*/ +void QPictureIO::setIODevice(QIODevice *ioDevice) +{ + d->iodev = ioDevice; +} + +/*! + Sets the name of the file to read or write an picture from to \a + fileName. + + \sa setIODevice() +*/ +void QPictureIO::setFileName(const QString &fileName) +{ + d->fname = fileName; +} + +/*! + Returns the quality of the written picture, related to the + compression ratio. + + \sa setQuality() QPicture::save() +*/ +int QPictureIO::quality() const +{ + return d->quality; +} + +/*! + Sets the quality of the written picture to \a q, related to the + compression ratio. + + \a q must be in the range -1..100. Specify 0 to obtain small + compressed files, 100 for large uncompressed files. (-1 signifies + the default compression.) + + \sa quality() QPicture::save() +*/ + +void QPictureIO::setQuality(int q) +{ + d->quality = q; +} + +/*! + Returns the picture's parameters string. + + \sa setParameters() +*/ + +const char *QPictureIO::parameters() const +{ + return d->parameters; +} + +/*! + Sets the picture's parameter string to \a parameters. This is for + picture handlers that require special parameters. + + Although the current picture formats supported by Qt ignore the + parameters string, it may be used in future extensions or by + contributions (for example, JPEG). + + \sa parameters() +*/ + +void QPictureIO::setParameters(const char *parameters) +{ + if (d->parameters) + delete [] (char*)d->parameters; + d->parameters = qstrdup(parameters); +} + +/*! + Sets the gamma value at which the picture will be viewed to \a + gamma. If the picture format stores a gamma value for which the + picture is intended to be used, then this setting will be used to + modify the picture. Setting to 0.0 will disable gamma correction + (i.e. any specification in the file will be ignored). + + The default value is 0.0. + + \sa gamma() +*/ +void QPictureIO::setGamma(float gamma) +{ + d->gamma=gamma; +} + +/*! + Returns the gamma value at which the picture will be viewed. + + \sa setGamma() +*/ +float QPictureIO::gamma() const +{ + return d->gamma; +} + +/*! + Sets the picture description string for picture handlers that support + picture descriptions to \a description. + + Currently, no picture format supported by Qt uses the description + string. +*/ + +void QPictureIO::setDescription(const QString &description) +{ + d->descr = description; +} + + +/*! + Returns a string that specifies the picture format of the file \a + fileName, or null if the file cannot be read or if the format is + not recognized. +*/ + +QByteArray QPictureIO::pictureFormat(const QString &fileName) +{ + QFile file(fileName); + QByteArray format; + if (!file.open(QIODevice::ReadOnly)) + return format; + format = pictureFormat(&file); + file.close(); + return format; +} + +/*! + \overload + + Returns a string that specifies the picture format of the picture read + from IO device \a d, or 0 if the device cannot be read or if the + format is not recognized. + + Make sure that \a d is at the right position in the device (for + example, at the beginning of the file). + + \sa QIODevice::at() +*/ + +QByteArray QPictureIO::pictureFormat(QIODevice *d) +{ + // if you change this change the documentation for defineIOHandler() + const int buflen = 14; + + char buf[buflen]; + char buf2[buflen]; + qt_init_picture_handlers(); + qt_init_picture_plugins(); + int pos = d->pos(); // save position + int rdlen = d->read(buf, buflen); // read a few bytes + + QByteArray format; + if (rdlen != buflen) + return format; + + memcpy(buf2, buf, buflen); + + for (int n = 0; n < rdlen; n++) + if (buf[n] == '\0') + buf[n] = '\001'; + if (rdlen > 0) { + buf[rdlen - 1] = '\0'; + QString bufStr = QString::fromLatin1(buf); + if (QPHList *list = pictureHandlers()) { + for (int i = 0; i < list->size(); ++i) { + if (list->at(i)->header.indexIn(bufStr) != -1) { // try match with headers + format = list->at(i)->format; + break; + } + } + } + } + d->seek(pos); // restore position + return format; +} + +/*! + Returns a sorted list of picture formats that are supported for + picture input. +*/ +QList QPictureIO::inputFormats() +{ + QList result; + + qt_init_picture_handlers(); + qt_init_picture_plugins(); + + if (QPHList *list = pictureHandlers()) { + for (int i = 0; i < list->size(); ++i) { + QPictureHandler *p = list->at(i); + if (p->read_picture && !p->obsolete && !result.contains(p->format)) + result.append(p->format); + } + } + qSort(result); + + return result; +} + +/*! + Returns a sorted list of picture formats that are supported for + picture output. +*/ +QList QPictureIO::outputFormats() +{ + qt_init_picture_handlers(); + qt_init_picture_plugins(); + + QList result; + if (QPHList *list = pictureHandlers()) { + for (int i = 0; i < list->size(); ++i) { + QPictureHandler *p = list->at(i); + if (p->write_picture && !p->obsolete && !result.contains(p->format)) + result.append(p->format); + } + } + return result; +} + + + +/*! + Reads an picture into memory and returns true if the picture was + successfully read; otherwise returns false. + + Before reading an picture you must set an IO device or a file name. + If both an IO device and a file name have been set, the IO device + will be used. + + Setting the picture file format string is optional. + + Note that this function does \e not set the \link format() + format\endlink used to read the picture. If you need that + information, use the pictureFormat() static functions. + + Example: + + \snippet doc/src/snippets/picture/picture.cpp 4 + + \sa setIODevice() setFileName() setFormat() write() QPixmap::load() +*/ +bool QPictureIO::read() +{ + QFile file; + const char *picture_format; + QPictureHandler *h; + + if (d->iodev) { // read from io device + // ok, already open + } else if (!d->fname.isEmpty()) { // read from file + file.setFileName(d->fname); + if (!file.open(QIODevice::ReadOnly)) + return false; // cannot open file + d->iodev = &file; + } else { // no file name or io device + return false; + } + if (d->frmt.isEmpty()) { + // Try to guess format + picture_format = pictureFormat(d->iodev); // get picture format + if (!picture_format) { + if (file.isOpen()) { // unknown format + file.close(); + d->iodev = 0; + } + return false; + } + } else { + picture_format = d->frmt; + } + + h = get_picture_handler(picture_format); + if (file.isOpen()) { +#if !defined(Q_OS_UNIX) + if (h && h->text_mode) { // reopen in translated mode + file.close(); + file.open(QIODevice::ReadOnly | QIODevice::Text); + } + else +#endif + file.seek(0); // position to start + } + d->iostat = 1; // assume error + + if (h && h->read_picture) + (*h->read_picture)(this); + + if (file.isOpen()) { // picture was read using file + file.close(); + d->iodev = 0; + } + return d->iostat == 0; // picture successfully read? +} + + +/*! + Writes an picture to an IO device and returns true if the picture was + successfully written; otherwise returns false. + + Before writing an picture you must set an IO device or a file name. + If both an IO device and a file name have been set, the IO device + will be used. + + The picture will be written using the specified picture format. + + Example: + \snippet doc/src/snippets/picture/picture.cpp 5 + + \sa setIODevice() setFileName() setFormat() read() QPixmap::save() +*/ +bool QPictureIO::write() +{ + if (d->frmt.isEmpty()) + return false; + QPictureHandler *h = get_picture_handler(d->frmt); + if (!h || !h->write_picture) { + qWarning("QPictureIO::write: No such picture format handler: %s", + format()); + return false; + } + QFile file; + if (!d->iodev && !d->fname.isEmpty()) { + file.setFileName(d->fname); + bool translate = h->text_mode==QPictureHandler::TranslateInOut; + QIODevice::OpenMode fmode = translate ? QIODevice::WriteOnly | QIODevice::Text : QIODevice::OpenMode(QIODevice::WriteOnly); + if (!file.open(fmode)) // couldn't create file + return false; + d->iodev = &file; + } + d->iostat = 1; + (*h->write_picture)(this); + if (file.isOpen()) { // picture was written using file + file.close(); + d->iodev = 0; + } + return d->iostat == 0; // picture successfully written? +} +#endif //QT_NO_PICTUREIO + +/*! + \fn QPicture QPicture::copy() const + + Use simple assignment instead. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_PICTURE + +/*! + \typedef QPicture::DataPtr + \internal +*/ + +/*! + \fn DataPtr &QPicture::data_ptr() + \internal +*/ diff --git a/src/gui/image/qpicture.h b/src/gui/image/qpicture.h new file mode 100644 index 0000000000..c478ed962e --- /dev/null +++ b/src/gui/image/qpicture.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPICTURE_H +#define QPICTURE_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PICTURE + +class QPicturePrivate; +class Q_GUI_EXPORT QPicture : public QPaintDevice +{ + Q_DECLARE_PRIVATE(QPicture) +public: + explicit QPicture(int formatVersion = -1); + QPicture(const QPicture &); + ~QPicture(); + + bool isNull() const; + + int devType() const; + uint size() const; + const char* data() const; + virtual void setData(const char* data, uint size); + + bool play(QPainter *p); + + bool load(QIODevice *dev, const char *format = 0); + bool load(const QString &fileName, const char *format = 0); + bool save(QIODevice *dev, const char *format = 0); + bool save(const QString &fileName, const char *format = 0); + + QRect boundingRect() const; + void setBoundingRect(const QRect &r); + + QPicture& operator=(const QPicture &p); +#ifdef Q_COMPILER_RVALUE_REFS + inline QPicture &operator=(QPicture &&other) + { qSwap(d_ptr, other.d_ptr); return *this; } +#endif + inline void swap(QPicture &other) { d_ptr.swap(other.d_ptr); } + void detach(); + bool isDetached() const; + + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &in, const QPicture &p); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QPicture &p); + + static const char* pictureFormat(const QString &fileName); + static QList inputFormats(); + static QList outputFormats(); + static QStringList inputFormatList(); + static QStringList outputFormatList(); + + QPaintEngine *paintEngine() const; + +protected: + QPicture(QPicturePrivate &data); + + int metric(PaintDeviceMetric m) const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QPicture copy() const { QPicture p(*this); p.detach(); return p; } +#endif + +private: + bool exec(QPainter *p, QDataStream &ds, int i); + void detach_helper(); + + QExplicitlySharedDataPointer d_ptr; + friend class QPicturePaintEngine; + friend class Q3Picture; + friend class QAlphaPaintEngine; + friend class QPreviewPaintEngine; + +public: + typedef QExplicitlySharedDataPointer DataPtr; + inline DataPtr &data_ptr() { return d_ptr; } +}; + +Q_DECLARE_SHARED(QPicture) + + +#ifndef QT_NO_PICTUREIO +class QIODevice; +class QPictureIO; +typedef void (*picture_io_handler)(QPictureIO *); // picture IO handler + +struct QPictureIOData; + +class Q_GUI_EXPORT QPictureIO +{ +public: + QPictureIO(); + QPictureIO(QIODevice *ioDevice, const char *format); + QPictureIO(const QString &fileName, const char *format); + ~QPictureIO(); + + const QPicture &picture() const; + int status() const; + const char *format() const; + QIODevice *ioDevice() const; + QString fileName() const; + int quality() const; + QString description() const; + const char *parameters() const; + float gamma() const; + + void setPicture(const QPicture &); + void setStatus(int); + void setFormat(const char *); + void setIODevice(QIODevice *); + void setFileName(const QString &); + void setQuality(int); + void setDescription(const QString &); + void setParameters(const char *); + void setGamma(float); + + bool read(); + bool write(); + + static QByteArray pictureFormat(const QString &fileName); + static QByteArray pictureFormat(QIODevice *); + static QList inputFormats(); + static QList outputFormats(); + + static void defineIOHandler(const char *format, + const char *header, + const char *flags, + picture_io_handler read_picture, + picture_io_handler write_picture); + +private: + Q_DISABLE_COPY(QPictureIO) + + void init(); + + QPictureIOData *d; +}; + +#endif //QT_NO_PICTUREIO + + +/***************************************************************************** + QPicture stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPicture &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPicture &); +#endif + +#endif // QT_NO_PICTURE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPICTURE_H diff --git a/src/gui/image/qpicture_p.h b/src/gui/image/qpicture_p.h new file mode 100644 index 0000000000..e06bf1fb69 --- /dev/null +++ b/src/gui/image/qpicture_p.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPICTURE_P_H +#define QPICTURE_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 "QtCore/qatomic.h" +#include "QtCore/qbuffer.h" +#include "QtCore/qobjectdefs.h" +#include "QtGui/qpicture.h" +#include "QtGui/qpixmap.h" +#include "QtGui/qpen.h" +#include "QtGui/qbrush.h" +#include "QtCore/qrect.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QPaintEngine; + +extern const char *qt_mfhdr_tag; + +class QPicturePrivate +{ + friend class QPicturePaintEngine; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &s, const QPicture &r); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &s, QPicture &r); + +public: + enum PaintCommand { + PdcNOP = 0, // + PdcDrawPoint = 1, // point + PdcDrawFirst = PdcDrawPoint, + PdcMoveTo = 2, // point + PdcLineTo = 3, // point + PdcDrawLine = 4, // point,point + PdcDrawRect = 5, // rect + PdcDrawRoundRect = 6, // rect,ival,ival + PdcDrawEllipse = 7, // rect + PdcDrawArc = 8, // rect,ival,ival + PdcDrawPie = 9, // rect,ival,ival + PdcDrawChord = 10, // rect,ival,ival + PdcDrawLineSegments = 11, // ptarr + PdcDrawPolyline = 12, // ptarr + PdcDrawPolygon = 13, // ptarr,ival + PdcDrawCubicBezier = 14, // ptarr + PdcDrawText = 15, // point,str + PdcDrawTextFormatted = 16, // rect,ival,str + PdcDrawPixmap = 17, // rect,pixmap + PdcDrawImage = 18, // rect,image + PdcDrawText2 = 19, // point,str + PdcDrawText2Formatted = 20, // rect,ival,str + PdcDrawTextItem = 21, // pos,text,font,flags + PdcDrawLast = PdcDrawTextItem, + PdcDrawPoints = 22, // ptarr,ival,ival + PdcDrawWinFocusRect = 23, // rect,color + PdcDrawTiledPixmap = 24, // rect,pixmap,point + PdcDrawPath = 25, // path + + // no painting commands below PdcDrawLast. + + PdcBegin = 30, // + PdcEnd = 31, // + PdcSave = 32, // + PdcRestore = 33, // + PdcSetdev = 34, // device - PRIVATE + PdcSetBkColor = 40, // color + PdcSetBkMode = 41, // ival + PdcSetROP = 42, // ival + PdcSetBrushOrigin = 43, // point + PdcSetFont = 45, // font + PdcSetPen = 46, // pen + PdcSetBrush = 47, // brush + PdcSetTabStops = 48, // ival + PdcSetTabArray = 49, // ival,ivec + PdcSetUnit = 50, // ival + PdcSetVXform = 51, // ival + PdcSetWindow = 52, // rect + PdcSetViewport = 53, // rect + PdcSetWXform = 54, // ival + PdcSetWMatrix = 55, // matrix,ival + PdcSaveWMatrix = 56, + PdcRestoreWMatrix = 57, + PdcSetClip = 60, // ival + PdcSetClipRegion = 61, // rgn + PdcSetClipPath = 62, // path + PdcSetRenderHint = 63, // ival + PdcSetCompositionMode = 64, // ival + PdcSetClipEnabled = 65, // bool + PdcSetOpacity = 66, // qreal + + PdcReservedStart = 0, // codes 0-199 are reserved + PdcReservedStop = 199 // for Qt + }; + + QPicturePrivate(); + QPicturePrivate(const QPicturePrivate &other); + QAtomicInt ref; + + bool checkFormat(); + void resetFormat(); + + QBuffer pictb; + int trecs; + bool formatOk; + int formatMajor; + int formatMinor; + QRect brect; + QRect override_rect; + QScopedPointer paintEngine; + bool in_memory_only; + QList image_list; + QList pixmap_list; + QList brush_list; + QList pen_list; +}; + +QT_END_NAMESPACE + +#endif // QPICTURE_P_H diff --git a/src/gui/image/qpictureformatplugin.cpp b/src/gui/image/qpictureformatplugin.cpp new file mode 100644 index 0000000000..8ece9e9c56 --- /dev/null +++ b/src/gui/image/qpictureformatplugin.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpictureformatplugin.h" +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_PICTURE) +#include "qpicture.h" + +QT_BEGIN_NAMESPACE + +/*! + \obsolete + + \class QPictureFormatPlugin + \brief The QPictureFormatPlugin class provides an abstract base + for custom picture format plugins. + + \ingroup plugins + + The picture format plugin is a simple plugin interface that makes + it easy to create custom picture formats that can be used + transparently by applications. + + Writing an picture format plugin is achieved by subclassing this + base class, reimplementing the pure virtual functions keys(), + loadPicture(), savePicture(), and installIOHandler(), and + exporting the class with the Q_EXPORT_PLUGIN2() macro. + + \sa {How to Create Qt Plugins} +*/ + +/*! + \fn QStringList QPictureFormatPlugin::keys() const + + Returns the list of picture formats this plugin supports. + + \sa installIOHandler() +*/ + +/*! + \fn bool QPictureFormatPlugin::installIOHandler(const QString &format) + + Installs a QPictureIO picture I/O handler for the picture format \a + format. + + \sa keys() +*/ + + +/*! + Constructs an picture format plugin with the given \a parent. + This is invoked automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QPictureFormatPlugin::QPictureFormatPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the picture format plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it is no longer used. +*/ +QPictureFormatPlugin::~QPictureFormatPlugin() +{ +} + + +/*! + Loads the picture stored in the file called \a fileName, with the + given \a format, into *\a picture. Returns true on success; + otherwise returns false. + + \sa savePicture() +*/ +bool QPictureFormatPlugin::loadPicture(const QString &format, const QString &fileName, QPicture *picture) +{ + Q_UNUSED(format) + Q_UNUSED(fileName) + Q_UNUSED(picture) + return false; +} + +/*! + Saves the given \a picture into the file called \a fileName, + using the specified \a format. Returns true on success; otherwise + returns false. + + \sa loadPicture() +*/ +bool QPictureFormatPlugin::savePicture(const QString &format, const QString &fileName, const QPicture &picture) +{ + Q_UNUSED(format) + Q_UNUSED(fileName) + Q_UNUSED(picture) + return false; +} + +#endif // QT_NO_LIBRARY || QT_NO_PICTURE + +QT_END_NAMESPACE diff --git a/src/gui/image/qpictureformatplugin.h b/src/gui/image/qpictureformatplugin.h new file mode 100644 index 0000000000..7b9ada1fe0 --- /dev/null +++ b/src/gui/image/qpictureformatplugin.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPICTUREFORMATPLUGIN_H +#define QPICTUREFORMATPLUGIN_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_PICTURE) + +class QPicture; +class QImage; +class QString; +class QStringList; + +struct Q_GUI_EXPORT QPictureFormatInterface : public QFactoryInterface +{ + virtual bool loadPicture(const QString &format, const QString &filename, QPicture *) = 0; + virtual bool savePicture(const QString &format, const QString &filename, const QPicture &) = 0; + + virtual bool installIOHandler(const QString &) = 0; +}; + +#define QPictureFormatInterface_iid "com.trolltech.Qt.QPictureFormatInterface" +Q_DECLARE_INTERFACE(QPictureFormatInterface, QPictureFormatInterface_iid) + + +class Q_GUI_EXPORT QPictureFormatPlugin : public QObject, public QPictureFormatInterface +{ + Q_OBJECT + Q_INTERFACES(QPictureFormatInterface:QFactoryInterface) +public: + explicit QPictureFormatPlugin(QObject *parent = 0); + ~QPictureFormatPlugin(); + + virtual QStringList keys() const = 0; + virtual bool loadPicture(const QString &format, const QString &filename, QPicture *pic); + virtual bool savePicture(const QString &format, const QString &filename, const QPicture &pic); + virtual bool installIOHandler(const QString &format) = 0; + +}; + +#endif // QT_NO_LIBRARY || QT_NO_PICTURE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPICTUREFORMATPLUGIN_H diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp new file mode 100644 index 0000000000..34804e5311 --- /dev/null +++ b/src/gui/image/qpixmap.cpp @@ -0,0 +1,2297 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qpixmap.h" +#include "qpixmapdata_p.h" +#include "qimagepixmapcleanuphooks_p.h" + +#include "qbitmap.h" +#include "qcolormap.h" +#include "qimage.h" +#include "qwidget.h" +#include "qpainter.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qapplication.h" +#include +#include +#include +#include "qevent.h" +#include "qfile.h" +#include "qfileinfo.h" +#include "qpixmapcache.h" +#include "qdatetime.h" +#include "qimagereader.h" +#include "qimagewriter.h" +#include "qpaintengine.h" +#include "qthread.h" + +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "private/qpixmap_mac_p.h" +#endif + +#ifdef Q_WS_QPA +# include "qplatformintegration_qpa.h" +#endif + +#if defined(Q_WS_X11) +# include "qx11info_x11.h" +# include +# include +#endif + +#if defined(Q_OS_SYMBIAN) +# include +#endif + +#include "qpixmap_raster_p.h" +#include "private/qstylehelper_p.h" + +QT_BEGIN_NAMESPACE + +// ### Qt 5: remove +Q_GUI_EXPORT qint64 qt_pixmap_id(const QPixmap &pixmap) +{ + return pixmap.cacheKey(); +} + +static bool qt_pixmap_thread_test() +{ + if (!qApp) { + qFatal("QPixmap: Must construct a QApplication before a QPaintDevice"); + return false; + } + + if (qApp->thread() != QThread::currentThread()) { + bool fail = false; +#if defined (Q_WS_X11) + if (!QApplication::testAttribute(Qt::AA_X11InitThreads)) + fail = true; +#elif defined (Q_WS_QPA) + if (!QApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedPixmaps)) { + printf("Lighthouse plugin does not support threaded pixmaps!\n"); + fail = true; + } +#else + if (QApplicationPrivate::graphics_system_name != QLatin1String("raster")) + fail = true; +#endif + if (fail) { + qWarning("QPixmap: It is not safe to use pixmaps outside the GUI thread"); + return false; + } + } + return true; +} + +void QPixmap::init(int w, int h, Type type) +{ + init(w, h, int(type)); +} + +extern QApplication::Type qt_appType; + +void QPixmap::init(int w, int h, int type) +{ + if (qt_appType == QApplication::Tty) { + qWarning("QPixmap: Cannot create a QPixmap when no GUI is being used"); + data = 0; + return; + } + + if ((w > 0 && h > 0) || type == QPixmapData::BitmapType) + data = QPixmapData::create(w, h, (QPixmapData::PixelType) type); + else + data = 0; +} + +/*! + \enum QPixmap::ColorMode + + \compat + + This enum type defines the color modes that exist for converting + QImage objects to QPixmap. It is provided here for compatibility + with earlier versions of Qt. + + Use Qt::ImageConversionFlags instead. + + \value Auto Select \c Color or \c Mono on a case-by-case basis. + \value Color Always create colored pixmaps. + \value Mono Always create bitmaps. +*/ + +/*! + Constructs a null pixmap. + + \sa isNull() +*/ + +QPixmap::QPixmap() + : QPaintDevice() +{ + (void) qt_pixmap_thread_test(); + init(0, 0, QPixmapData::PixmapType); +} + +/*! + \fn QPixmap::QPixmap(int width, int height) + + Constructs a pixmap with the given \a width and \a height. If + either \a width or \a height is zero, a null pixmap is + constructed. + + \warning This will create a QPixmap with uninitialized data. Call + fill() to fill the pixmap with an appropriate color before drawing + onto it with QPainter. + + \sa isNull() +*/ + +QPixmap::QPixmap(int w, int h) + : QPaintDevice() +{ + if (!qt_pixmap_thread_test()) + init(0, 0, QPixmapData::PixmapType); + else + init(w, h, QPixmapData::PixmapType); +} + +/*! + \overload + + Constructs a pixmap of the given \a size. + + \warning This will create a QPixmap with uninitialized data. Call + fill() to fill the pixmap with an appropriate color before drawing + onto it with QPainter. +*/ + +QPixmap::QPixmap(const QSize &size) + : QPaintDevice() +{ + if (!qt_pixmap_thread_test()) + init(0, 0, QPixmapData::PixmapType); + else + init(size.width(), size.height(), QPixmapData::PixmapType); +} + +/*! + \internal +*/ +QPixmap::QPixmap(const QSize &s, Type type) +{ + if (!qt_pixmap_thread_test()) + init(0, 0, type); + else + init(s.width(), s.height(), type); +} + +/*! + \internal +*/ +QPixmap::QPixmap(const QSize &s, int type) +{ + if (!qt_pixmap_thread_test()) + init(0, 0, static_cast(type)); + else + init(s.width(), s.height(), static_cast(type)); +} + +/*! + \internal +*/ +QPixmap::QPixmap(QPixmapData *d) + : QPaintDevice(), data(d) +{ +} + +/*! + Constructs a pixmap from the file with the given \a fileName. If the + file does not exist or is of an unknown format, the pixmap becomes a + null pixmap. + + The loader attempts to read the pixmap using the specified \a + format. If the \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + + The file name can either refer to an actual file on disk or to + one of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how + to embed images and other resource files in the application's + executable. + + If the image needs to be modified to fit in a lower-resolution + result (e.g. converting from 32-bit to 8-bit), use the \a + flags to control the conversion. + + The \a fileName, \a format and \a flags parameters are + passed on to load(). This means that the data in \a fileName is + not compiled into the binary. If \a fileName contains a relative + path (e.g. the filename only) the relevant file must be found + relative to the runtime working directory. + + \sa {QPixmap#Reading and Writing Image Files}{Reading and Writing + Image Files} +*/ + +QPixmap::QPixmap(const QString& fileName, const char *format, Qt::ImageConversionFlags flags) + : QPaintDevice() +{ + init(0, 0, QPixmapData::PixmapType); + if (!qt_pixmap_thread_test()) + return; + + load(fileName, format, flags); +} + +/*! + Constructs a pixmap that is a copy of the given \a pixmap. + + \sa copy() +*/ + +QPixmap::QPixmap(const QPixmap &pixmap) + : QPaintDevice() +{ + if (!qt_pixmap_thread_test()) { + init(0, 0, QPixmapData::PixmapType); + return; + } + if (pixmap.paintingActive()) { // make a deep copy + operator=(pixmap.copy()); + } else { + data = pixmap.data; + } +} + +/*! + Constructs a pixmap from the given \a xpm data, which must be a + valid XPM image. + + Errors are silently ignored. + + Note that it's possible to squeeze the XPM variable a little bit + by using an unusual declaration: + + \snippet doc/src/snippets/code/src_gui_image_qpixmap.cpp 0 + + The extra \c const makes the entire definition read-only, which is + slightly more efficient (for example, when the code is in a shared + library) and ROMable when the application is to be stored in ROM. +*/ +#ifndef QT_NO_IMAGEFORMAT_XPM +QPixmap::QPixmap(const char * const xpm[]) + : QPaintDevice() +{ + init(0, 0, QPixmapData::PixmapType); + if (!xpm) + return; + + QImage image(xpm); + if (!image.isNull()) { + if (data && data->pixelType() == QPixmapData::BitmapType) + *this = QBitmap::fromImage(image); + else + *this = fromImage(image); + } +} +#endif + + +/*! + Destroys the pixmap. +*/ + +QPixmap::~QPixmap() +{ + Q_ASSERT(!data || data->ref >= 1); // Catch if ref-counting changes again +} + +/*! + \internal +*/ +int QPixmap::devType() const +{ + return QInternal::Pixmap; +} + +/*! + \fn QPixmap QPixmap::copy(int x, int y, int width, int height) const + \overload + + Returns a deep copy of the subset of the pixmap that is specified + by the rectangle QRect( \a x, \a y, \a width, \a height). +*/ + +/*! + \fn QPixmap QPixmap::copy(const QRect &rectangle) const + + Returns a deep copy of the subset of the pixmap that is specified + by the given \a rectangle. For more information on deep copies, + see the \l {Implicit Data Sharing} documentation. + + If the given \a rectangle is empty, the whole image is copied. + + \sa operator=(), QPixmap(), {QPixmap#Pixmap + Transformations}{Pixmap Transformations} +*/ +QPixmap QPixmap::copy(const QRect &rect) const +{ + if (isNull()) + return QPixmap(); + + QRect r(0, 0, width(), height()); + if (!rect.isEmpty()) + r = r.intersected(rect); + + QPixmapData *d = data->createCompatiblePixmapData(); + d->copy(data.data(), r); + return QPixmap(d); +} + +/*! + \fn QPixmap::scroll(int dx, int dy, int x, int y, int width, int height, QRegion *exposed) + \since 4.6 + + This convenience function is equivalent to calling QPixmap::scroll(\a dx, + \a dy, QRect(\a x, \a y, \a width, \a height), \a exposed). + + \sa QWidget::scroll(), QGraphicsItem::scroll() +*/ + +/*! + \since 4.6 + + Scrolls the area \a rect of this pixmap by (\a dx, \a dy). The exposed + region is left unchanged. You can optionally pass a pointer to an empty + QRegion to get the region that is \a exposed by the scroll operation. + + \snippet doc/src/snippets/code/src_gui_image_qpixmap.cpp 2 + + You cannot scroll while there is an active painter on the pixmap. + + \sa QWidget::scroll(), QGraphicsItem::scroll() +*/ +void QPixmap::scroll(int dx, int dy, const QRect &rect, QRegion *exposed) +{ + if (isNull() || (dx == 0 && dy == 0)) + return; + QRect dest = rect & this->rect(); + QRect src = dest.translated(-dx, -dy) & dest; + if (src.isEmpty()) { + if (exposed) + *exposed += dest; + return; + } + + detach(); + + if (!data->scroll(dx, dy, src)) { + // Fallback + QPixmap pix = *this; + QPainter painter(&pix); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawPixmap(src.translated(dx, dy), *this, src); + painter.end(); + *this = pix; + } + + if (exposed) { + *exposed += dest; + *exposed -= src.translated(dx, dy); + } +} + +/*! + Assigns the given \a pixmap to this pixmap and returns a reference + to this pixmap. + + \sa copy(), QPixmap() +*/ + +QPixmap &QPixmap::operator=(const QPixmap &pixmap) +{ + if (paintingActive()) { + qWarning("QPixmap::operator=: Cannot assign to pixmap during painting"); + return *this; + } + if (pixmap.paintingActive()) { // make a deep copy + *this = pixmap.copy(); + } else { + data = pixmap.data; + } + return *this; +} + +/*! + \fn void QPixmap::swap(QPixmap &other) + \since 4.8 + + Swaps pixmap \a other with this pixmap. This operation is very + fast and never fails. +*/ + +/*! + Returns the pixmap as a QVariant. +*/ +QPixmap::operator QVariant() const +{ + return QVariant(QVariant::Pixmap, this); +} + +/*! + \fn bool QPixmap::operator!() const + + Returns true if this is a null pixmap; otherwise returns false. + + \sa isNull() +*/ + +/*! + \fn QPixmap::operator QImage() const + + Returns the pixmap as a QImage. + + Use the toImage() function instead. +*/ + +/*! + Converts the pixmap to a QImage. Returns a null image if the + conversion fails. + + If the pixmap has 1-bit depth, the returned image will also be 1 + bit deep. Images with more bits will be returned in a format + closely represents the underlying system. Usually this will be + QImage::Format_ARGB32_Premultiplied for pixmaps with an alpha and + QImage::Format_RGB32 or QImage::Format_RGB16 for pixmaps without + alpha. + + Note that for the moment, alpha masks on monochrome images are + ignored. + + \sa fromImage(), {QImage#Image Formats}{Image Formats} +*/ +QImage QPixmap::toImage() const +{ + if (isNull()) + return QImage(); + + return data->toImage(); +} + +/*! + \fn QMatrix QPixmap::trueMatrix(const QTransform &matrix, int width, int height) + + Returns the actual matrix used for transforming a pixmap with the + given \a width, \a height and \a matrix. + + When transforming a pixmap using the transformed() function, the + transformation matrix is internally adjusted to compensate for + unwanted translation, i.e. transformed() returns the smallest + pixmap containing all transformed points of the original + pixmap. This function returns the modified matrix, which maps + points correctly from the original pixmap into the new pixmap. + + \sa transformed(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} +*/ +QTransform QPixmap::trueMatrix(const QTransform &m, int w, int h) +{ + return QImage::trueMatrix(m, w, h); +} + +/*! + \overload + + This convenience function loads the matrix \a m into a + QTransform and calls the overloaded function with the + QTransform and the width \a w and the height \a h. + */ +QMatrix QPixmap::trueMatrix(const QMatrix &m, int w, int h) +{ + return trueMatrix(QTransform(m), w, h).toAffine(); +} + + +/*! + \fn bool QPixmap::isQBitmap() const + + Returns true if this is a QBitmap; otherwise returns false. +*/ + +bool QPixmap::isQBitmap() const +{ + return data->type == QPixmapData::BitmapType; +} + +/*! + \fn bool QPixmap::isNull() const + + Returns true if this is a null pixmap; otherwise returns false. + + A null pixmap has zero width, zero height and no contents. You + cannot draw in a null pixmap. +*/ +bool QPixmap::isNull() const +{ + return !data || data->isNull(); +} + +/*! + \fn int QPixmap::width() const + + Returns the width of the pixmap. + + \sa size(), {QPixmap#Pixmap Information}{Pixmap Information} +*/ +int QPixmap::width() const +{ + return data ? data->width() : 0; +} + +/*! + \fn int QPixmap::height() const + + Returns the height of the pixmap. + + \sa size(), {QPixmap#Pixmap Information}{Pixmap Information} +*/ +int QPixmap::height() const +{ + return data ? data->height() : 0; +} + +/*! + \fn QSize QPixmap::size() const + + Returns the size of the pixmap. + + \sa width(), height(), {QPixmap#Pixmap Information}{Pixmap + Information} +*/ +QSize QPixmap::size() const +{ + return data ? QSize(data->width(), data->height()) : QSize(0, 0); +} + +/*! + \fn QRect QPixmap::rect() const + + Returns the pixmap's enclosing rectangle. + + \sa {QPixmap#Pixmap Information}{Pixmap Information} +*/ +QRect QPixmap::rect() const +{ + return data ? QRect(0, 0, data->width(), data->height()) : QRect(); +} + +/*! + \fn int QPixmap::depth() const + + Returns the depth of the pixmap. + + The pixmap depth is also called bits per pixel (bpp) or bit planes + of a pixmap. A null pixmap has depth 0. + + \sa defaultDepth(), {QPixmap#Pixmap Information}{Pixmap + Information} +*/ +int QPixmap::depth() const +{ + return data ? data->depth() : 0; +} + +/*! + \fn void QPixmap::resize(const QSize &size) + \overload + \compat + + Use QPixmap::copy() instead to get the pixmap with the new size. + + \oldcode + pixmap.resize(size); + \newcode + pixmap = pixmap.copy(QRect(QPoint(0, 0), size)); + \endcode +*/ +#ifdef QT3_SUPPORT +void QPixmap::resize_helper(const QSize &s) +{ + int w = s.width(); + int h = s.height(); + if (w < 1 || h < 1) { + *this = QPixmap(); + return; + } + + if (size() == s) + return; + + // QPixmap.data member may be QRuntimePixmapData so use pixmapData() function to get + // the actual underlaying runtime pixmap data. + QPixmapData *pd = pixmapData(); + + // Create new pixmap + QPixmap pm(QSize(w, h), pd ? pd->type : QPixmapData::PixmapType); + bool uninit = false; +#if defined(Q_WS_X11) + QX11PixmapData *x11Data = pd && pd->classId() == QPixmapData::X11Class ? static_cast(pd) : 0; + if (x11Data) { + pm.x11SetScreen(x11Data->xinfo.screen()); + uninit = x11Data->flags & QX11PixmapData::Uninitialized; + } +#elif defined(Q_WS_MAC) + QMacPixmapData *macData = pd && pd->classId() == QPixmapData::MacClass ? static_cast(pd) : 0; + if (macData) + uninit = macData->uninit; +#endif + if (!uninit && !isNull()) { + // Copy old pixmap + if (hasAlphaChannel()) + pm.fill(Qt::transparent); + QPainter p(&pm); + p.drawPixmap(0, 0, *this, 0, 0, qMin(width(), w), qMin(height(), h)); + } + +#if defined(Q_WS_X11) + if (x11Data && x11Data->x11_mask) { + QPixmapData *newPd = pm.pixmapData(); + QX11PixmapData *pmData = (newPd && newPd->classId() == QPixmapData::X11Class) + ? static_cast(newPd) : 0; + if (pmData) { + pmData->x11_mask = (Qt::HANDLE)XCreatePixmap(X11->display, + RootWindow(x11Data->xinfo.display(), + x11Data->xinfo.screen()), + w, h, 1); + GC gc = XCreateGC(X11->display, pmData->x11_mask, 0, 0); + XCopyArea(X11->display, x11Data->x11_mask, pmData->x11_mask, gc, 0, 0, + qMin(width(), w), qMin(height(), h), 0, 0); + XFreeGC(X11->display, gc); + } + } +#endif + *this = pm; +} +#endif + +/*! + \fn void QPixmap::resize(int width, int height) + \compat + + Use QPixmap::copy() instead to get the pixmap with the new size. + + \oldcode + pixmap.resize(10, 20); + \newcode + pixmap = pixmap.copy(0, 0, 10, 20); + \endcode +*/ + +/*! + \fn bool QPixmap::selfMask() const + \compat + + Returns whether the pixmap is its own mask or not. + + This function is no longer relevant since the concept of self + masking doesn't exists anymore. +*/ + +/*! + Sets a mask bitmap. + + This function merges the \a mask with the pixmap's alpha channel. A pixel + value of 1 on the mask means the pixmap's pixel is unchanged; a value of 0 + means the pixel is transparent. The mask must have the same size as this + pixmap. + + Setting a null mask resets the mask, leaving the previously transparent + pixels black. The effect of this function is undefined when the pixmap is + being painted on. + + \warning This is potentially an expensive operation. + + \sa mask(), {QPixmap#Pixmap Transformations}{Pixmap Transformations}, + QBitmap +*/ +void QPixmap::setMask(const QBitmap &mask) +{ + if (paintingActive()) { + qWarning("QPixmap::setMask: Cannot set mask while pixmap is being painted on"); + return; + } + + if (!mask.isNull() && mask.size() != size()) { + qWarning("QPixmap::setMask() mask size differs from pixmap size"); + return; + } + + if (isNull()) + return; + + if (static_cast(mask).data == data) // trying to selfmask + return; + + detach(); + data->setMask(mask); +} + +#ifndef QT_NO_IMAGE_HEURISTIC_MASK +/*! + Creates and returns a heuristic mask for this pixmap. + + The function works by selecting a color from one of the corners + and then chipping away pixels of that color, starting at all the + edges. If \a clipTight is true (the default) the mask is just + large enough to cover the pixels; otherwise, the mask is larger + than the data pixels. + + The mask may not be perfect but it should be reasonable, so you + can do things such as the following: + + \snippet doc/src/snippets/code/src_gui_image_qpixmap.cpp 1 + + This function is slow because it involves converting to/from a + QImage, and non-trivial computations. + + \sa QImage::createHeuristicMask(), createMaskFromColor() +*/ +QBitmap QPixmap::createHeuristicMask(bool clipTight) const +{ + QBitmap m = QBitmap::fromImage(toImage().createHeuristicMask(clipTight)); + return m; +} +#endif + +/*! + Creates and returns a mask for this pixmap based on the given \a + maskColor. If the \a mode is Qt::MaskInColor, all pixels matching the + maskColor will be transparent. If \a mode is Qt::MaskOutColor, all pixels + matching the maskColor will be opaque. + + This function is slow because it involves converting to/from a + QImage. + + \sa createHeuristicMask(), QImage::createMaskFromColor() +*/ +QBitmap QPixmap::createMaskFromColor(const QColor &maskColor, Qt::MaskMode mode) const +{ + QImage image = toImage().convertToFormat(QImage::Format_ARGB32); + return QBitmap::fromImage(image.createMaskFromColor(maskColor.rgba(), mode)); +} + +/*! \overload + + Creates and returns a mask for this pixmap based on the given \a + maskColor. Same as calling createMaskFromColor(maskColor, + Qt::MaskInColor) + + \sa createHeuristicMask(), QImage::createMaskFromColor() +*/ +QBitmap QPixmap::createMaskFromColor(const QColor &maskColor) const +{ + return createMaskFromColor(maskColor, Qt::MaskInColor); +} + +/*! + Loads a pixmap from the file with the given \a fileName. Returns + true if the pixmap was successfully loaded; otherwise returns + false. + + The loader attempts to read the pixmap using the specified \a + format. If the \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + + The file name can either refer to an actual file on disk or to one + of the application's embedded resources. See the + \l{resources.html}{Resource System} overview for details on how to + embed pixmaps and other resource files in the application's + executable. + + If the data needs to be modified to fit in a lower-resolution + result (e.g. converting from 32-bit to 8-bit), use the \a flags to + control the conversion. + + Note that QPixmaps are automatically added to the QPixmapCache + when loaded from a file; the key used is internal and can not + be acquired. + + \sa loadFromData(), {QPixmap#Reading and Writing Image + Files}{Reading and Writing Image Files} +*/ + +bool QPixmap::load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags) +{ + if (fileName.isEmpty()) + return false; + + QFileInfo info(fileName); + QString key = QLatin1Literal("qt_pixmap") + % info.absoluteFilePath() + % HexString(info.lastModified().toTime_t()) + % HexString(info.size()) + % HexString(data ? data->pixelType() : QPixmapData::PixmapType); + + // Note: If no extension is provided, we try to match the + // file against known plugin extensions + if (!info.completeSuffix().isEmpty() && !info.exists()) + return false; + + if (QPixmapCache::find(key, *this)) + return true; + + QScopedPointer tmp(QPixmapData::create(0, 0, data ? data->type : QPixmapData::PixmapType)); + if (tmp->fromFile(fileName, format, flags)) { + data = tmp.take(); + QPixmapCache::insert(key, *this); + return true; + } + + return false; +} + +/*! + \fn bool QPixmap::loadFromData(const uchar *data, uint len, const char *format, Qt::ImageConversionFlags flags) + + Loads a pixmap from the \a len first bytes of the given binary \a + data. Returns true if the pixmap was loaded successfully; + otherwise returns false. + + The loader attempts to read the pixmap using the specified \a + format. If the \a format is not specified (which is the default), + the loader probes the file for a header to guess the file format. + + If the data needs to be modified to fit in a lower-resolution + result (e.g. converting from 32-bit to 8-bit), use the \a flags to + control the conversion. + + \sa load(), {QPixmap#Reading and Writing Image Files}{Reading and + Writing Image Files} +*/ + +bool QPixmap::loadFromData(const uchar *buf, uint len, const char *format, Qt::ImageConversionFlags flags) +{ + if (len == 0 || buf == 0) + return false; + + if (!data) + data = QPixmapData::create(0, 0, QPixmapData::PixmapType); + + return data->fromData(buf, len, format, flags); +} + +/*! + \fn bool QPixmap::loadFromData(const QByteArray &data, const char *format, Qt::ImageConversionFlags flags) + + \overload + + Loads a pixmap from the binary \a data using the specified \a + format and conversion \a flags. +*/ + + +/*! + Saves the pixmap to the file with the given \a fileName using the + specified image file \a format and \a quality factor. Returns true + if successful; otherwise returns false. + + The \a quality factor must be in the range [0,100] or -1. Specify + 0 to obtain small compressed files, 100 for large uncompressed + files, and -1 to use the default settings. + + If \a format is 0, an image format will be chosen from \a fileName's + suffix. + + \sa {QPixmap#Reading and Writing Image Files}{Reading and Writing + Image Files} +*/ + +bool QPixmap::save(const QString &fileName, const char *format, int quality) const +{ + if (isNull()) + return false; // nothing to save + QImageWriter writer(fileName, format); + return doImageIO(&writer, quality); +} + +/*! + \overload + + This function writes a QPixmap to the given \a device using the + specified image file \a format and \a quality factor. This can be + used, for example, to save a pixmap directly into a QByteArray: + + \snippet doc/src/snippets/image/image.cpp 1 +*/ + +bool QPixmap::save(QIODevice* device, const char* format, int quality) const +{ + if (isNull()) + return false; // nothing to save + QImageWriter writer(device, format); + return doImageIO(&writer, quality); +} + +/*! \internal +*/ +bool QPixmap::doImageIO(QImageWriter *writer, int quality) const +{ + if (quality > 100 || quality < -1) + qWarning("QPixmap::save: quality out of range [-1,100]"); + if (quality >= 0) + writer->setQuality(qMin(quality,100)); + return writer->write(toImage()); +} + + +// The implementation (and documentation) of +// QPixmap::fill(const QWidget *, const QPoint &) +// is in qwidget.cpp + +/*! + \fn void QPixmap::fill(const QWidget *widget, int x, int y) + \overload + + Fills the pixmap with the \a widget's background color or pixmap. + The given point, (\a x, \a y), defines an offset in widget + coordinates to which the pixmap's top-left pixel will be mapped + to. +*/ + +/*! + Fills the pixmap with the given \a color. + + The effect of this function is undefined when the pixmap is + being painted on. + + \sa {QPixmap#Pixmap Transformations}{Pixmap Transformations} +*/ + +void QPixmap::fill(const QColor &color) +{ + if (isNull()) + return; + + // Some people are probably already calling fill while a painter is active, so to not break + // their programs, only print a warning and return when the fill operation could cause a crash. + if (paintingActive() && (color.alpha() != 255) && !hasAlphaChannel()) { + qWarning("QPixmap::fill: Cannot fill while pixmap is being painted on"); + return; + } + + if (data->ref == 1) { + // detach() will also remove this pixmap from caches, so + // it has to be called even when ref == 1. + detach(); + } else { + // Don't bother to make a copy of the data object, since + // it will be filled with new pixel data anyway. + QPixmapData *d = data->createCompatiblePixmapData(); + d->resize(data->width(), data->height()); + data = d; + } + data->fill(color); +} + +/*! \obsolete + Returns a number that identifies the contents of this QPixmap + object. Distinct QPixmap objects can only have the same serial + number if they refer to the same contents (but they don't have + to). + + Use cacheKey() instead. + + \warning The serial number doesn't necessarily change when + the pixmap is altered. This means that it may be dangerous to use + it as a cache key. For caching pixmaps, we recommend using the + QPixmapCache class whenever possible. +*/ +int QPixmap::serialNumber() const +{ + if (isNull()) + return 0; + return data->serialNumber(); +} + +/*! + Returns a number that identifies this QPixmap. Distinct QPixmap + objects can only have the same cache key if they refer to the same + contents. + + The cacheKey() will change when the pixmap is altered. +*/ +qint64 QPixmap::cacheKey() const +{ + if (isNull()) + return 0; + + Q_ASSERT(data); + return data->cacheKey(); +} + +static void sendResizeEvents(QWidget *target) +{ + QResizeEvent e(target->size(), QSize()); + QApplication::sendEvent(target, &e); + + const QObjectList children = target->children(); + for (int i = 0; i < children.size(); ++i) { + QWidget *child = static_cast(children.at(i)); + if (child->isWidgetType() && !child->isWindow() && child->testAttribute(Qt::WA_PendingResizeEvent)) + sendResizeEvents(child); + } +} + +/*! + \fn QPixmap QPixmap::grabWidget(QWidget * widget, const QRect &rectangle) + + Creates a pixmap and paints the given \a widget, restricted by the + given \a rectangle, in it. If the \a widget has any children, then + they are also painted in the appropriate positions. + + If no rectangle is specified (the default) the entire widget is + painted. + + If \a widget is 0, the specified rectangle doesn't overlap the + widget's rectangle, or an error occurs, the function will return a + null QPixmap. If the rectangle is a superset of the given \a + widget, the areas outside the \a widget are covered with the + widget's background. + + This function actually asks \a widget to paint itself (and its + children to paint themselves) by calling paintEvent() with painter + redirection turned on. But QPixmap also provides the grabWindow() + function which is a bit faster by grabbing pixels directly off the + screen. In addition, if there are overlaying windows, + grabWindow(), unlike grabWidget(), will see them. + + \warning Do not grab a widget from its QWidget::paintEvent(). + However, it is safe to grab a widget from another widget's + \l {QWidget::}{paintEvent()}. + + \sa grabWindow() +*/ + +QPixmap QPixmap::grabWidget(QWidget * widget, const QRect &rect) +{ + if (!widget) + return QPixmap(); + + if (widget->testAttribute(Qt::WA_PendingResizeEvent) || !widget->testAttribute(Qt::WA_WState_Created)) + sendResizeEvents(widget); + + widget->d_func()->prepareToRender(QRegion(), + QWidget::DrawWindowBackground | QWidget::DrawChildren | QWidget::IgnoreMask); + + QRect r(rect); + if (r.width() < 0) + r.setWidth(widget->width() - rect.x()); + if (r.height() < 0) + r.setHeight(widget->height() - rect.y()); + + if (!r.intersects(widget->rect())) + return QPixmap(); + + QPixmap res(r.size()); + if (!qt_widget_private(widget)->isOpaque) + res.fill(Qt::transparent); + + widget->d_func()->render(&res, QPoint(), r, QWidget::DrawWindowBackground + | QWidget::DrawChildren | QWidget::IgnoreMask, true); + return res; +} + +/*! + \fn QPixmap QPixmap::grabWidget(QWidget *widget, int x, int y, int + width, int height) + + \overload + + Creates a pixmap and paints the given \a widget, restricted by + QRect(\a x, \a y, \a width, \a height), in it. + + \warning Do not grab a widget from its QWidget::paintEvent(). + However, it is safe to grab a widget from another widget's + \l {QWidget::}{paintEvent()}. +*/ + + +/*! + \since 4.5 + + \enum QPixmap::ShareMode + + This enum type defines the share modes that are available when + creating a QPixmap object from a raw X11 Pixmap handle. + + \value ImplicitlyShared This mode will cause the QPixmap object to + create a copy of the internal data before it is modified, thus + keeping the original X11 pixmap intact. + + \value ExplicitlyShared In this mode, the pixmap data will \e not be + copied before it is modified, which in effect will change the + original X11 pixmap. + + \warning This enum is only used for X11 specific functions; using + it is non-portable. + + \sa QPixmap::fromX11Pixmap() +*/ + +/*! + \since 4.5 + + \fn QPixmap QPixmap::fromX11Pixmap(Qt::HANDLE pixmap, QPixmap::ShareMode mode) + + Creates a QPixmap from the native X11 Pixmap handle \a pixmap, + using \a mode as the share mode. The default share mode is + QPixmap::ImplicitlyShared, which means that a copy of the pixmap is + made if someone tries to modify it by e.g. drawing onto it. + + QPixmap does \e not take ownership of the \a pixmap handle, and + have to be deleted by the user. + + \warning This function is X11 specific; using it is non-portable. + + \sa QPixmap::ShareMode +*/ + + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) + +/*! + Returns the pixmap's handle to the device context. + + Note that, since QPixmap make use of \l {Implicit Data + Sharing}{implicit data sharing}, the detach() function must be + called explicitly to ensure that only \e this pixmap's data is + modified if the pixmap data is shared. + + \warning This function is X11 specific; using it is non-portable. + + \warning Since 4.8, pixmaps do not have an X11 handle unless + created with \l {QPixmap::}{fromX11Pixmap()}, or if the native + graphics system is explicitly enabled. + + \sa detach() + \sa QApplication::setGraphicsSystem() +*/ + +Qt::HANDLE QPixmap::handle() const +{ +#if defined(Q_WS_X11) + const QPixmapData *pd = pixmapData(); + if (pd) { + if (pd->classId() == QPixmapData::X11Class) + return static_cast(pd)->handle(); + else + qWarning("QPixmap::handle(): Pixmap is not an X11 class pixmap"); + } +#endif + return 0; +} +#endif + + +#ifdef QT3_SUPPORT +static Qt::ImageConversionFlags colorModeToFlags(QPixmap::ColorMode mode) +{ + Qt::ImageConversionFlags flags = Qt::AutoColor; + switch (mode) { + case QPixmap::Color: + flags |= Qt::ColorOnly; + break; + case QPixmap::Mono: + flags |= Qt::MonoOnly; + break; + default: + break;// Nothing. + } + return flags; +} + +/*! + Use the constructor that takes a Qt::ImageConversionFlag instead. +*/ + +QPixmap::QPixmap(const QString& fileName, const char *format, ColorMode mode) + : QPaintDevice() +{ + init(0, 0, QPixmapData::PixmapType); + if (!qt_pixmap_thread_test()) + return; + + load(fileName, format, colorModeToFlags(mode)); +} + +/*! + Constructs a pixmap from the QImage \a image. + + Use the static fromImage() function instead. +*/ +QPixmap::QPixmap(const QImage& image) + : QPaintDevice() +{ + init(0, 0, QPixmapData::PixmapType); + if (!qt_pixmap_thread_test()) + return; + + if (data && data->pixelType() == QPixmapData::BitmapType) + *this = QBitmap::fromImage(image); + else + *this = fromImage(image); +} + +/*! + \overload + + Converts the given \a image to a pixmap that is assigned to this + pixmap. + + Use the static fromImage() function instead. +*/ + +QPixmap &QPixmap::operator=(const QImage &image) +{ + if (data && data->pixelType() == QPixmapData::BitmapType) + *this = QBitmap::fromImage(image); + else + *this = fromImage(image); + return *this; +} + +/*! + Use the load() function that takes a Qt::ImageConversionFlag instead. +*/ + +bool QPixmap::load(const QString &fileName, const char *format, ColorMode mode) +{ + return load(fileName, format, colorModeToFlags(mode)); +} + +/*! + Use the loadFromData() function that takes a Qt::ImageConversionFlag instead. +*/ + +bool QPixmap::loadFromData(const uchar *buf, uint len, const char *format, ColorMode mode) +{ + return loadFromData(buf, len, format, colorModeToFlags(mode)); +} + +/*! + Use the static fromImage() function instead. +*/ +bool QPixmap::convertFromImage(const QImage &image, ColorMode mode) +{ + if (data && data->pixelType() == QPixmapData::BitmapType) + *this = QBitmap::fromImage(image, colorModeToFlags(mode)); + else + *this = fromImage(image, colorModeToFlags(mode)); + return !isNull(); +} + +#endif + +/***************************************************************************** + QPixmap stream functions + *****************************************************************************/ +#if !defined(QT_NO_DATASTREAM) +/*! + \relates QPixmap + + Writes the given \a pixmap to the given \a stream as a PNG + image. Note that writing the stream to a file will not produce a + valid image file. + + \sa QPixmap::save(), {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QPixmap &pixmap) +{ + return stream << pixmap.toImage(); +} + +/*! + \relates QPixmap + + Reads an image from the given \a stream into the given \a pixmap. + + \sa QPixmap::load(), {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QPixmap &pixmap) +{ + QImage image; + stream >> image; + + if (image.isNull()) { + pixmap = QPixmap(); + } else if (image.depth() == 1) { + pixmap = QBitmap::fromImage(image); + } else { + pixmap = QPixmap::fromImage(image); + } + return stream; +} + +#endif // QT_NO_DATASTREAM + +#ifdef QT3_SUPPORT +Q_GUI_EXPORT void copyBlt(QPixmap *dst, int dx, int dy, + const QPixmap *src, int sx, int sy, int sw, int sh) +{ + Q_ASSERT_X(dst, "::copyBlt", "Destination pixmap must be non-null"); + Q_ASSERT_X(src, "::copyBlt", "Source pixmap must be non-null"); + + if (src->hasAlphaChannel()) { + if (dst->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) { + QPainter p(dst); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawPixmap(dx, dy, *src, sx, sy, sw, sh); + } else { + QImage image = dst->toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawPixmap(dx, dy, *src, sx, sy, sw, sh); + p.end(); + *dst = QPixmap::fromImage(image); + } + } else { + QPainter p(dst); + p.drawPixmap(dx, dy, *src, sx, sy, sw, sh); + } + +} +#endif + +/*! + \internal +*/ + +bool QPixmap::isDetached() const +{ + return data && data->ref == 1; +} + +/*! \internal + ### Qt5 - remove me. +*/ +void QPixmap::deref() +{ + Q_ASSERT_X(false, "QPixmap::deref()", "Do not call this function anymore!"); +} + +/*! + \fn QImage QPixmap::convertToImage() const + + Use the toImage() function instead. +*/ + +/*! + Replaces this pixmap's data with the given \a image using the + specified \a flags to control the conversion. The \a flags + argument is a bitwise-OR of the \l{Qt::ImageConversionFlags}. + Passing 0 for \a flags sets all the default options. Returns true + if the result is that this pixmap is not null. + + Note: this function was part of Qt 3 support in Qt 4.6 and earlier. + It has been promoted to official API status in 4.7 to support updating + the pixmap's image without creating a new QPixmap as fromImage() would. + + \sa fromImage() + \since 4.7 +*/ +bool QPixmap::convertFromImage(const QImage &image, Qt::ImageConversionFlags flags) +{ + if (image.isNull() || !data) + *this = QPixmap::fromImage(image, flags); + else + data->fromImage(image, flags); + return !isNull(); +} + +/*! + \fn QPixmap QPixmap::xForm(const QMatrix &matrix) const + + Use transformed() instead. +*/ + +/*! + \fn QPixmap QPixmap::scaled(int width, int height, + Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode + transformMode) const + + \overload + + Returns a copy of the pixmap scaled to a rectangle with the given + \a width and \a height according to the given \a aspectRatioMode and + \a transformMode. + + If either the \a width or the \a height is zero or negative, this + function returns a null pixmap. +*/ + +/*! + \fn QPixmap QPixmap::scaled(const QSize &size, Qt::AspectRatioMode + aspectRatioMode, Qt::TransformationMode transformMode) const + + Scales the pixmap to the given \a size, using the aspect ratio and + transformation modes specified by \a aspectRatioMode and \a + transformMode. + + \image qimage-scaling.png + + \list + \i If \a aspectRatioMode is Qt::IgnoreAspectRatio, the pixmap + is scaled to \a size. + \i If \a aspectRatioMode is Qt::KeepAspectRatio, the pixmap is + scaled to a rectangle as large as possible inside \a size, preserving the aspect ratio. + \i If \a aspectRatioMode is Qt::KeepAspectRatioByExpanding, + the pixmap is scaled to a rectangle as small as possible + outside \a size, preserving the aspect ratio. + \endlist + + If the given \a size is empty, this function returns a null + pixmap. + + + In some cases it can be more beneficial to draw the pixmap to a + painter with a scale set rather than scaling the pixmap. This is + the case when the painter is for instance based on OpenGL or when + the scale factor changes rapidly. + + \sa isNull(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} + +*/ +QPixmap QPixmap::scaled(const QSize& s, Qt::AspectRatioMode aspectMode, Qt::TransformationMode mode) const +{ + if (isNull()) { + qWarning("QPixmap::scaled: Pixmap is a null pixmap"); + return QPixmap(); + } + if (s.isEmpty()) + return QPixmap(); + + QSize newSize = size(); + newSize.scale(s, aspectMode); + if (newSize == size()) + return *this; + + QTransform wm = QTransform::fromScale((qreal)newSize.width() / width(), + (qreal)newSize.height() / height()); + QPixmap pix = transformed(wm, mode); + return pix; +} + +/*! + \fn QPixmap QPixmap::scaledToWidth(int width, Qt::TransformationMode + mode) const + + Returns a scaled copy of the image. The returned image is scaled + to the given \a width using the specified transformation \a mode. + The height of the pixmap is automatically calculated so that the + aspect ratio of the pixmap is preserved. + + If \a width is 0 or negative, a null pixmap is returned. + + \sa isNull(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} +*/ +QPixmap QPixmap::scaledToWidth(int w, Qt::TransformationMode mode) const +{ + if (isNull()) { + qWarning("QPixmap::scaleWidth: Pixmap is a null pixmap"); + return copy(); + } + if (w <= 0) + return QPixmap(); + + qreal factor = (qreal) w / width(); + QTransform wm = QTransform::fromScale(factor, factor); + return transformed(wm, mode); +} + +/*! + \fn QPixmap QPixmap::scaledToHeight(int height, + Qt::TransformationMode mode) const + + Returns a scaled copy of the image. The returned image is scaled + to the given \a height using the specified transformation \a mode. + The width of the pixmap is automatically calculated so that the + aspect ratio of the pixmap is preserved. + + If \a height is 0 or negative, a null pixmap is returned. + + \sa isNull(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} +*/ +QPixmap QPixmap::scaledToHeight(int h, Qt::TransformationMode mode) const +{ + if (isNull()) { + qWarning("QPixmap::scaleHeight: Pixmap is a null pixmap"); + return copy(); + } + if (h <= 0) + return QPixmap(); + + qreal factor = (qreal) h / height(); + QTransform wm = QTransform::fromScale(factor, factor); + return transformed(wm, mode); +} + +/*! + Returns a copy of the pixmap that is transformed using the given + transformation \a transform and transformation \a mode. The original + pixmap is not changed. + + The transformation \a transform is internally adjusted to compensate + for unwanted translation; i.e. the pixmap produced is the smallest + pixmap that contains all the transformed points of the original + pixmap. Use the trueMatrix() function to retrieve the actual + matrix used for transforming the pixmap. + + This function is slow because it involves transformation to a + QImage, non-trivial computations and a transformation back to a + QPixmap. + + \sa trueMatrix(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} +*/ +QPixmap QPixmap::transformed(const QTransform &transform, + Qt::TransformationMode mode) const +{ + if (isNull() || transform.type() <= QTransform::TxTranslate) + return *this; + + return data->transformed(transform, mode); +} + +/*! + \overload + + This convenience function loads the \a matrix into a + QTransform and calls the overloaded function. + */ +QPixmap QPixmap::transformed(const QMatrix &matrix, Qt::TransformationMode mode) const +{ + return transformed(QTransform(matrix), mode); +} + + + + + + + + +/*! + \class QPixmap + + \brief The QPixmap class is an off-screen image representation + that can be used as a paint device. + + \ingroup painting + \ingroup shared + + + Qt provides four classes for handling image data: QImage, QPixmap, + QBitmap and QPicture. QImage is designed and optimized for I/O, + and for direct pixel access and manipulation, while QPixmap is + designed and optimized for showing images on screen. QBitmap is + only a convenience class that inherits QPixmap, ensuring a depth + of 1. The isQBitmap() function returns true if a QPixmap object is + really a bitmap, otherwise returns false. Finally, the QPicture class + is a paint device that records and replays QPainter commands. + + A QPixmap can easily be displayed on the screen using QLabel or + one of QAbstractButton's subclasses (such as QPushButton and + QToolButton). QLabel has a pixmap property, whereas + QAbstractButton has an icon property. + + In addition to the ordinary constructors, a QPixmap can be + constructed using the static grabWidget() and grabWindow() + functions which creates a QPixmap and paints the given widget, or + window, into it. + + QPixmap objects can be passed around by value since the QPixmap + class uses implicit data sharing. For more information, see the \l + {Implicit Data Sharing} documentation. QPixmap objects can also be + streamed. + + Note that the pixel data in a pixmap is internal and is managed by + the underlying window system. Because QPixmap is a QPaintDevice + subclass, QPainter can be used to draw directly onto pixmaps. + Pixels can only be accessed through QPainter functions or by + converting the QPixmap to a QImage. However, the fill() function + is available for initializing the entire pixmap with a given color. + + There are functions to convert between QImage and + QPixmap. Typically, the QImage class is used to load an image + file, optionally manipulating the image data, before the QImage + object is converted into a QPixmap to be shown on + screen. Alternatively, if no manipulation is desired, the image + file can be loaded directly into a QPixmap. On Windows, the + QPixmap class also supports conversion between \c HBITMAP and + QPixmap. On Symbian, the QPixmap class also supports conversion + between CFbsBitmap and QPixmap. + + QPixmap provides a collection of functions that can be used to + obtain a variety of information about the pixmap. In addition, + there are several functions that enables transformation of the + pixmap. + + \tableofcontents + + \section1 Reading and Writing Image Files + + QPixmap provides several ways of reading an image file: The file + can be loaded when constructing the QPixmap object, or by using + the load() or loadFromData() functions later on. When loading an + image, the file name can either refer to an actual file on disk or + to one of the application's embedded resources. See \l{The Qt + Resource System} overview for details on how to embed images and + other resource files in the application's executable. + + Simply call the save() function to save a QPixmap object. + + The complete list of supported file formats are available through + the QImageReader::supportedImageFormats() and + QImageWriter::supportedImageFormats() functions. New file formats + can be added as plugins. By default, Qt supports the following + formats: + + \table + \header \o Format \o Description \o Qt's support + \row \o BMP \o Windows Bitmap \o Read/write + \row \o GIF \o Graphic Interchange Format (optional) \o Read + \row \o JPG \o Joint Photographic Experts Group \o Read/write + \row \o JPEG \o Joint Photographic Experts Group \o Read/write + \row \o PNG \o Portable Network Graphics \o Read/write + \row \o PBM \o Portable Bitmap \o Read + \row \o PGM \o Portable Graymap \o Read + \row \o PPM \o Portable Pixmap \o Read/write + \row \o XBM \o X11 Bitmap \o Read/write + \row \o XPM \o X11 Pixmap \o Read/write + \endtable + + \section1 Pixmap Information + + QPixmap provides a collection of functions that can be used to + obtain a variety of information about the pixmap: + + \table + \header + \o \o Available Functions + \row + \o Geometry + \o + The size(), width() and height() functions provide information + about the pixmap's size. The rect() function returns the image's + enclosing rectangle. + + \row + \o Alpha component + \o + + The hasAlphaChannel() returns true if the pixmap has a format that + respects the alpha channel, otherwise returns false. The hasAlpha(), + setMask() and mask() functions are legacy and should not be used. + They are potentially very slow. + + The createHeuristicMask() function creates and returns a 1-bpp + heuristic mask (i.e. a QBitmap) for this pixmap. It works by + selecting a color from one of the corners and then chipping away + pixels of that color, starting at all the edges. The + createMaskFromColor() function creates and returns a mask (i.e. a + QBitmap) for the pixmap based on a given color. + + \row + \o Low-level information + \o + + The depth() function returns the depth of the pixmap. The + defaultDepth() function returns the default depth, i.e. the depth + used by the application on the given screen. + + The cacheKey() function returns a number that uniquely + identifies the contents of the QPixmap object. + + The x11Info() function returns information about the configuration + of the X display used by the screen to which the pixmap currently + belongs. The x11PictureHandle() function returns the X11 Picture + handle of the pixmap for XRender support. Note that the two latter + functions are only available on x11. + + \endtable + + \section1 Pixmap Conversion + + A QPixmap object can be converted into a QImage using the + toImage() function. Likewise, a QImage can be converted into a + QPixmap using the fromImage(). If this is too expensive an + operation, you can use QBitmap::fromImage() instead. + + In addition, on Windows, the QPixmap class supports conversion to + and from HBITMAP: the toWinHBITMAP() function creates a HBITMAP + equivalent to the QPixmap, based on the given HBitmapFormat, and + returns the HBITMAP handle. The fromWinHBITMAP() function returns + a QPixmap that is equivalent to the given bitmap which has the + specified format. The QPixmap class also supports conversion to + and from HICON: the toWinHICON() function creates a HICON equivalent + to the QPixmap, and returns the HICON handle. The fromWinHICON() + function returns a QPixmap that is equivalent to the given icon. + + In addition, on Symbian, the QPixmap class supports conversion to + and from CFbsBitmap: the toSymbianCFbsBitmap() function creates + CFbsBitmap equivalent to the QPixmap, based on given mode and returns + a CFbsBitmap object. The fromSymbianCFbsBitmap() function returns a + QPixmap that is equivalent to the given bitmap and given mode. + + \section1 Pixmap Transformations + + QPixmap supports a number of functions for creating a new pixmap + that is a transformed version of the original: + + The scaled(), scaledToWidth() and scaledToHeight() functions + return scaled copies of the pixmap, while the copy() function + creates a QPixmap that is a plain copy of the original one. + + The transformed() function returns a copy of the pixmap that is + transformed with the given transformation matrix and + transformation mode: Internally, the transformation matrix is + adjusted to compensate for unwanted translation, + i.e. transformed() returns the smallest pixmap containing all + transformed points of the original pixmap. The static trueMatrix() + function returns the actual matrix used for transforming the + pixmap. + + \note When using the native X11 graphics system, the pixmap + becomes invalid when the QApplication instance is destroyed. + + \sa QBitmap, QImage, QImageReader, QImageWriter +*/ + + +/*! + \typedef QPixmap::DataPtr + \internal +*/ + +/*! + \fn DataPtr &QPixmap::data_ptr() + \internal +*/ + +/*! + Returns true if this pixmap has an alpha channel, \e or has a + mask, otherwise returns false. + + \sa hasAlphaChannel(), mask() +*/ +bool QPixmap::hasAlpha() const +{ +#if defined(Q_WS_X11) + if (data && data->hasAlphaChannel()) + return true; + QPixmapData *pd = pixmapData(); + if (pd && pd->classId() == QPixmapData::X11Class) { + QX11PixmapData *x11Data = static_cast(pd); +#ifndef QT_NO_XRENDER + if (x11Data->picture && x11Data->d == 32) + return true; +#endif + if (x11Data->d == 1 || x11Data->x11_mask) + return true; + } + return false; +#else + return data && data->hasAlphaChannel(); +#endif +} + +/*! + Returns true if the pixmap has a format that respects the alpha + channel, otherwise returns false. + + \sa hasAlpha() +*/ +bool QPixmap::hasAlphaChannel() const +{ + return data && data->hasAlphaChannel(); +} + +/*! + \internal +*/ +int QPixmap::metric(PaintDeviceMetric metric) const +{ + return data ? data->metric(metric) : 0; +} + +/*! + \fn void QPixmap::setAlphaChannel(const QPixmap &alphaChannel) + \obsolete + + Sets the alpha channel of this pixmap to the given \a alphaChannel + by converting the \a alphaChannel into 32 bit and using the + intensity of the RGB pixel values. + + The effect of this function is undefined when the pixmap is being + painted on. + + \warning This is potentially an expensive operation. Most usecases + for this function are covered by QPainter and compositionModes + which will normally execute faster. + + \sa alphaChannel(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations} + */ +void QPixmap::setAlphaChannel(const QPixmap &alphaChannel) +{ + if (alphaChannel.isNull()) + return; + + if (paintingActive()) { + qWarning("QPixmap::setAlphaChannel: " + "Cannot set alpha channel while pixmap is being painted on"); + return; + } + + if (width() != alphaChannel.width() && height() != alphaChannel.height()) { + qWarning("QPixmap::setAlphaChannel: " + "The pixmap and the alpha channel pixmap must have the same size"); + return; + } + + detach(); + data->setAlphaChannel(alphaChannel); +} + +/*! + \obsolete + + Returns the alpha channel of the pixmap as a new grayscale QPixmap in which + each pixel's red, green, and blue values are given the alpha value of the + original pixmap. The color depth of the returned pixmap is the system depth + on X11 and 8-bit on Windows and Mac OS X. + + You can use this function while debugging + to get a visible image of the alpha channel. If the pixmap doesn't have an + alpha channel, i.e., the alpha channel's value for all pixels equals + 0xff), a null pixmap is returned. You can check this with the \c isNull() + function. + + We show an example: + + \snippet doc/src/snippets/alphachannel.cpp 0 + + \image alphachannelimage.png The pixmap and channelImage QPixmaps + + \warning This is an expensive operation. The alpha channel of the + pixmap is extracted dynamically from the pixeldata. Most usecases of this + function are covered by QPainter and compositionModes which will normally + execute faster. + + \sa setAlphaChannel(), {QPixmap#Pixmap Information}{Pixmap + Information} +*/ +QPixmap QPixmap::alphaChannel() const +{ + return data ? data->alphaChannel() : QPixmap(); +} + +/*! + \internal +*/ +QPaintEngine *QPixmap::paintEngine() const +{ + return data ? data->paintEngine() : 0; +} + +/*! + \fn QBitmap QPixmap::mask() const + + Extracts a bitmap mask from the pixmap's alpha channel. + + \warning This is potentially an expensive operation. The mask of + the pixmap is extracted dynamically from the pixeldata. + + \sa setMask(), {QPixmap#Pixmap Information}{Pixmap Information} +*/ +QBitmap QPixmap::mask() const +{ + return data ? data->mask() : QBitmap(); +} + +/*! + Returns the default pixmap depth used by the application. + + On Windows and Mac, the default depth is always 32. On X11 and + embedded, the depth of the screen will be returned by this + function. + + \sa depth(), QColormap::depth(), {QPixmap#Pixmap Information}{Pixmap Information} + +*/ +int QPixmap::defaultDepth() +{ +#if defined(Q_WS_QWS) + return QScreen::instance()->depth(); +#elif defined(Q_WS_X11) + return QX11Info::appDepth(); +#elif defined(Q_WS_WINCE) + return QColormap::instance().depth(); +#elif defined(Q_WS_WIN) + return 32; // XXX +#elif defined(Q_WS_MAC) + return 32; +#elif defined(Q_OS_SYMBIAN) + return S60->screenDepth; +#elif defined(Q_WS_QPA) + return 32; //LITE: use graphicssystem (we should do that in general) +#endif +} + +/*! + Detaches the pixmap from shared pixmap data. + + A pixmap is automatically detached by Qt whenever its contents are + about to change. This is done in almost all QPixmap member + functions that modify the pixmap (fill(), fromImage(), + load(), etc.), and in QPainter::begin() on a pixmap. + + There are two exceptions in which detach() must be called + explicitly, that is when calling the handle() or the + x11PictureHandle() function (only available on X11). Otherwise, + any modifications done using system calls, will be performed on + the shared data. + + The detach() function returns immediately if there is just a + single reference or if the pixmap has not been initialized yet. +*/ +void QPixmap::detach() +{ + if (!data) + return; + + // QPixmap.data member may be QRuntimePixmapData so use pixmapData() function to get + // the actual underlaying runtime pixmap data. + QPixmapData *pd = pixmapData(); + QPixmapData::ClassId id = pd->classId(); + if (id == QPixmapData::RasterClass) { + QRasterPixmapData *rasterData = static_cast(pd); + rasterData->image.detach(); + } + + if (data->is_cached && data->ref == 1) + QImagePixmapCleanupHooks::executePixmapDataModificationHooks(data.data()); + +#if defined(Q_WS_MAC) + QMacPixmapData *macData = id == QPixmapData::MacClass ? static_cast(pd) : 0; + if (macData) { + if (macData->cg_mask) { + CGImageRelease(macData->cg_mask); + macData->cg_mask = 0; + } + } +#endif + + if (data->ref != 1) { + *this = copy(); + } + ++data->detach_no; + +#if defined(Q_WS_X11) + if (pd->classId() == QPixmapData::X11Class) { + QX11PixmapData *d = static_cast(pd); + d->flags &= ~QX11PixmapData::Uninitialized; + + // reset the cache data + if (d->hd2) { + XFreePixmap(X11->display, d->hd2); + d->hd2 = 0; + } + } +#elif defined(Q_WS_MAC) + if (macData) { + macData->macReleaseCGImageRef(); + macData->uninit = false; + } +#endif +} + +/*! + \fn QPixmap QPixmap::fromImage(const QImage &image, Qt::ImageConversionFlags flags) + + Converts the given \a image to a pixmap using the specified \a + flags to control the conversion. The \a flags argument is a + bitwise-OR of the \l{Qt::ImageConversionFlags}. Passing 0 for \a + flags sets all the default options. + + In case of monochrome and 8-bit images, the image is first + converted to a 32-bit pixmap and then filled with the colors in + the color table. If this is too expensive an operation, you can + use QBitmap::fromImage() instead. + + \sa fromImageReader(), toImage(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ +QPixmap QPixmap::fromImage(const QImage &image, Qt::ImageConversionFlags flags) +{ + if (image.isNull()) + return QPixmap(); + + QGraphicsSystem* gs = QApplicationPrivate::graphicsSystem(); + QScopedPointer data(gs ? gs->createPixmapData(QPixmapData::PixmapType) + : QGraphicsSystem::createDefaultPixmapData(QPixmapData::PixmapType)); + data->fromImage(image, flags); + return QPixmap(data.take()); +} + +/*! + \fn QPixmap QPixmap::fromImageReader(QImageReader *imageReader, Qt::ImageConversionFlags flags) + + Create a QPixmap from an image read directly from an \a imageReader. + The \a flags argument is a bitwise-OR of the \l{Qt::ImageConversionFlags}. + Passing 0 for \a flags sets all the default options. + + On some systems, reading an image directly to QPixmap can use less memory than + reading a QImage to convert it to QPixmap. + + \sa fromImage(), toImage(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ +QPixmap QPixmap::fromImageReader(QImageReader *imageReader, Qt::ImageConversionFlags flags) +{ + QGraphicsSystem *gs = QApplicationPrivate::graphicsSystem(); + QScopedPointer data(gs ? gs->createPixmapData(QPixmapData::PixmapType) + : QGraphicsSystem::createDefaultPixmapData(QPixmapData::PixmapType)); + data->fromImageReader(imageReader, flags); + return QPixmap(data.take()); +} + +/*! + \fn QPixmap QPixmap::grabWindow(WId window, int x, int y, int + width, int height) + + Creates and returns a pixmap constructed by grabbing the contents + of the given \a window restricted by QRect(\a x, \a y, \a width, + \a height). + + The arguments (\a{x}, \a{y}) specify the offset in the window, + whereas (\a{width}, \a{height}) specify the area to be copied. If + \a width is negative, the function copies everything to the right + border of the window. If \a height is negative, the function + copies everything to the bottom of the window. + + The window system identifier (\c WId) can be retrieved using the + QWidget::winId() function. The rationale for using a window + identifier and not a QWidget, is to enable grabbing of windows + that are not part of the application, window system frames, and so + on. + + The grabWindow() function grabs pixels from the screen, not from + the window, i.e. if there is another window partially or entirely + over the one you grab, you get pixels from the overlying window, + too. The mouse cursor is generally not grabbed. + + Note on X11 that if the given \a window doesn't have the same depth + as the root window, and another window partially or entirely + obscures the one you grab, you will \e not get pixels from the + overlying window. The contents of the obscured areas in the + pixmap will be undefined and uninitialized. + + On Windows Vista and above grabbing a layered window, which is + created by setting the Qt::WA_TranslucentBackground attribute, will + not work. Instead grabbing the desktop widget should work. + + \warning In general, grabbing an area outside the screen is not + safe. This depends on the underlying window system. + + \sa grabWidget(), {Screenshot Example} +*/ + +/*! + \internal +*/ +QPixmapData* QPixmap::pixmapData() const +{ + if (data) { + QPixmapData* pm = data.data(); + return pm->runtimeData() ? pm->runtimeData() : pm; + } + + return 0; +} + + +/*! + \enum QPixmap::HBitmapFormat + + \bold{Win32 only:} This enum defines how the conversion between \c + HBITMAP and QPixmap is performed. + + \warning This enum is only available on Windows. + + \value NoAlpha The alpha channel is ignored and always treated as + being set to fully opaque. This is preferred if the \c HBITMAP is + used with standard GDI calls, such as \c BitBlt(). + + \value PremultipliedAlpha The \c HBITMAP is treated as having an + alpha channel and premultiplied colors. This is preferred if the + \c HBITMAP is accessed through the \c AlphaBlend() GDI function. + + \value Alpha The \c HBITMAP is treated as having a plain alpha + channel. This is the preferred format if the \c HBITMAP is going + to be used as an application icon or systray icon. + + \sa fromWinHBITMAP(), toWinHBITMAP() +*/ + +/*! \fn HBITMAP QPixmap::toWinHBITMAP(HBitmapFormat format) const + \bold{Win32 only:} Creates a \c HBITMAP equivalent to the QPixmap, + based on the given \a format. Returns the \c HBITMAP handle. + + It is the caller's responsibility to free the \c HBITMAP data + after use. + + \warning This function is only available on Windows. + + \sa fromWinHBITMAP(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ + +/*! \fn QPixmap QPixmap::fromWinHBITMAP(HBITMAP bitmap, HBitmapFormat format) + \bold{Win32 only:} Returns a QPixmap that is equivalent to the + given \a bitmap. The conversion is based on the specified \a + format. + + \warning This function is only available on Windows. + + \sa toWinHBITMAP(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} + +*/ + +/*! \fn HICON QPixmap::toWinHICON() const + \since 4.6 + + \bold{Win32 only:} Creates a \c HICON equivalent to the QPixmap. + Returns the \c HICON handle. + + It is the caller's responsibility to free the \c HICON data after use. + + \warning This function is only available on Windows. + + \sa fromWinHICON(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ + +/*! \fn QPixmap QPixmap::fromWinHICON(HICON icon) + \since 4.6 + + \bold{Win32 only:} Returns a QPixmap that is equivalent to the given + \a icon. + + \warning This function is only available on Windows. + + \sa toWinHICON(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} + +*/ + +/*! \fn const QX11Info &QPixmap::x11Info() const + \bold{X11 only:} Returns information about the configuration of + the X display used by the screen to which the pixmap currently belongs. + + \warning This function is only available on X11. + + \sa {QPixmap#Pixmap Information}{Pixmap Information} +*/ + +/*! \fn Qt::HANDLE QPixmap::x11PictureHandle() const + \bold{X11 only:} Returns the X11 Picture handle of the pixmap for + XRender support. + + This function will return 0 if XRender support is not compiled + into Qt, if the XRender extension is not supported on the X11 + display, or if the handle could not be created. Use of this + function is not portable. + + \warning This function is only available on X11. + + \sa {QPixmap#Pixmap Information}{Pixmap Information} +*/ + +/*! \fn int QPixmap::x11SetDefaultScreen(int screen) + \internal +*/ + +/*! \fn void QPixmap::x11SetScreen(int screen) + \internal +*/ + +/*! \fn QRgb* QPixmap::clut() const + \internal +*/ + +/*! \fn int QPixmap::numCols() const + \obsolete + \internal + \sa colorCount() +*/ + +/*! \fn int QPixmap::colorCount() const + \since 4.6 + \internal +*/ + +/*! \fn const uchar* QPixmap::qwsBits() const + \internal + \since 4.1 +*/ + +/*! \fn int QPixmap::qwsBytesPerLine() const + \internal + \since 4.1 +*/ + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap.h b/src/gui/image/qpixmap.h new file mode 100644 index 0000000000..f2e79c14f4 --- /dev/null +++ b/src/gui/image/qpixmap.h @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAP_H +#define QPIXMAP_H + +#include +#include +#include +#include // char*->QString conversion +#include +#include +#include + +QT_BEGIN_HEADER + +#if defined(Q_OS_SYMBIAN) +class CFbsBitmap; +class RSgImage; +#endif + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QImageWriter; +class QImageReader; +class QColor; +class QVariant; +class QX11Info; +class QPixmapData; + +class Q_GUI_EXPORT QPixmap : public QPaintDevice +{ +public: + QPixmap(); + explicit QPixmap(QPixmapData *data); + QPixmap(int w, int h); + QPixmap(const QSize &); + QPixmap(const QString& fileName, const char *format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor); +#ifndef QT_NO_IMAGEFORMAT_XPM + QPixmap(const char * const xpm[]); +#endif + QPixmap(const QPixmap &); + ~QPixmap(); + + QPixmap &operator=(const QPixmap &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QPixmap &operator=(QPixmap &&other) + { qSwap(data, other.data); return *this; } +#endif + inline void swap(QPixmap &other) { qSwap(data, other.data); } + + operator QVariant() const; + + bool isNull() const; // ### Qt 5: make inline + int devType() const; + + int width() const; // ### Qt 5: make inline + int height() const; // ### Qt 5: make inline + QSize size() const; + QRect rect() const; + int depth() const; + + static int defaultDepth(); + + void fill(const QColor &fillColor = Qt::white); + void fill(const QWidget *widget, const QPoint &ofs); + inline void fill(const QWidget *widget, int xofs, int yofs) { fill(widget, QPoint(xofs, yofs)); } + + QBitmap mask() const; + void setMask(const QBitmap &); + + QPixmap alphaChannel() const; + void setAlphaChannel(const QPixmap &); + + bool hasAlpha() const; + bool hasAlphaChannel() const; + +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + QBitmap createHeuristicMask(bool clipTight = true) const; +#endif + QBitmap createMaskFromColor(const QColor &maskColor) const; // ### Qt 5: remove + QBitmap createMaskFromColor(const QColor &maskColor, Qt::MaskMode mode) const; + + static QPixmap grabWindow(WId, int x=0, int y=0, int w=-1, int h=-1); + static QPixmap grabWidget(QWidget *widget, const QRect &rect); + static inline QPixmap grabWidget(QWidget *widget, int x=0, int y=0, int w=-1, int h=-1) + { return grabWidget(widget, QRect(x, y, w, h)); } + + + inline QPixmap scaled(int w, int h, Qt::AspectRatioMode aspectMode = Qt::IgnoreAspectRatio, + Qt::TransformationMode mode = Qt::FastTransformation) const + { return scaled(QSize(w, h), aspectMode, mode); } + QPixmap scaled(const QSize &s, Qt::AspectRatioMode aspectMode = Qt::IgnoreAspectRatio, + Qt::TransformationMode mode = Qt::FastTransformation) const; + QPixmap scaledToWidth(int w, Qt::TransformationMode mode = Qt::FastTransformation) const; + QPixmap scaledToHeight(int h, Qt::TransformationMode mode = Qt::FastTransformation) const; + QPixmap transformed(const QMatrix &, Qt::TransformationMode mode = Qt::FastTransformation) const; + static QMatrix trueMatrix(const QMatrix &m, int w, int h); + QPixmap transformed(const QTransform &, Qt::TransformationMode mode = Qt::FastTransformation) const; + static QTransform trueMatrix(const QTransform &m, int w, int h); + + QImage toImage() const; + static QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags = Qt::AutoColor); + static QPixmap fromImageReader(QImageReader *imageReader, Qt::ImageConversionFlags flags = Qt::AutoColor); + + bool load(const QString& fileName, const char *format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor); + bool loadFromData(const uchar *buf, uint len, const char* format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor); + inline bool loadFromData(const QByteArray &data, const char* format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor); + bool save(const QString& fileName, const char* format = 0, int quality = -1) const; + bool save(QIODevice* device, const char* format = 0, int quality = -1) const; + + bool convertFromImage(const QImage &img, Qt::ImageConversionFlags flags = Qt::AutoColor); + +#if defined(Q_WS_WIN) + enum HBitmapFormat { + NoAlpha, + PremultipliedAlpha, + Alpha + }; + + HBITMAP toWinHBITMAP(HBitmapFormat format = NoAlpha) const; + HICON toWinHICON() const; + + static QPixmap fromWinHBITMAP(HBITMAP hbitmap, HBitmapFormat format = NoAlpha); + static QPixmap fromWinHICON(HICON hicon); +#endif + +#if defined(Q_WS_MAC) + CGImageRef toMacCGImageRef() const; + static QPixmap fromMacCGImageRef(CGImageRef image); +#endif + +#if defined(Q_OS_SYMBIAN) + CFbsBitmap *toSymbianCFbsBitmap() const; + static QPixmap fromSymbianCFbsBitmap(CFbsBitmap *bitmap); + RSgImage* toSymbianRSgImage() const; + static QPixmap fromSymbianRSgImage(RSgImage *sgImage); +#endif + + inline QPixmap copy(int x, int y, int width, int height) const; + QPixmap copy(const QRect &rect = QRect()) const; + + inline void scroll(int dx, int dy, int x, int y, int width, int height, QRegion *exposed = 0); + void scroll(int dx, int dy, const QRect &rect, QRegion *exposed = 0); + + int serialNumber() const; + qint64 cacheKey() const; + + bool isDetached() const; + void detach(); + + bool isQBitmap() const; + +#if defined(Q_WS_QWS) + const uchar *qwsBits() const; + int qwsBytesPerLine() const; + QRgb *clut() const; +#ifdef QT_DEPRECATED + QT_DEPRECATED int numCols() const; +#endif + int colorCount() const; +#elif defined(Q_WS_MAC) + Qt::HANDLE macQDHandle() const; + Qt::HANDLE macQDAlphaHandle() const; + Qt::HANDLE macCGHandle() const; +#elif defined(Q_WS_X11) + enum ShareMode { ImplicitlyShared, ExplicitlyShared }; + + static QPixmap fromX11Pixmap(Qt::HANDLE pixmap, ShareMode mode = ImplicitlyShared); + static int x11SetDefaultScreen(int screen); + void x11SetScreen(int screen); + const QX11Info &x11Info() const; + Qt::HANDLE x11PictureHandle() const; +#endif + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) + Qt::HANDLE handle() const; +#endif + + QPaintEngine *paintEngine() const; + + inline bool operator!() const { return isNull(); } + +protected: + int metric(PaintDeviceMetric) const; + +#ifdef QT3_SUPPORT +public: + enum ColorMode { Auto, Color, Mono }; + QT3_SUPPORT_CONSTRUCTOR QPixmap(const QString& fileName, const char *format, ColorMode mode); + QT3_SUPPORT bool load(const QString& fileName, const char *format, ColorMode mode); + QT3_SUPPORT bool loadFromData(const uchar *buf, uint len, const char* format, ColorMode mode); + QT3_SUPPORT_CONSTRUCTOR QPixmap(const QImage& image); + QT3_SUPPORT QPixmap &operator=(const QImage &); + inline QT3_SUPPORT QImage convertToImage() const { return toImage(); } + QT3_SUPPORT bool convertFromImage(const QImage &, ColorMode mode); + inline QT3_SUPPORT operator QImage() const { return toImage(); } + inline QT3_SUPPORT QPixmap xForm(const QMatrix &matrix) const { return transformed(QTransform(matrix)); } + inline QT3_SUPPORT bool selfMask() const { return false; } +private: + void resize_helper(const QSize &s); +public: + inline QT3_SUPPORT void resize(const QSize &s) { resize_helper(s); } + inline QT3_SUPPORT void resize(int width, int height) { resize_helper(QSize(width, height)); } +#endif + +private: + QExplicitlySharedDataPointer data; + + bool doImageIO(QImageWriter *io, int quality) const; + + // ### Qt5: remove the following three lines + enum Type { PixmapType, BitmapType }; // must match QPixmapData::PixelType + QPixmap(const QSize &s, Type); + void init(int, int, Type = PixmapType); + + QPixmap(const QSize &s, int type); + void init(int, int, int); + void deref(); +#if defined(Q_WS_WIN) + void initAlphaPixmap(uchar *bytes, int length, struct tagBITMAPINFO *bmi); +#endif + Q_DUMMY_COMPARISON_OPERATOR(QPixmap) +#ifdef Q_WS_MAC + friend CGContextRef qt_mac_cg_context(const QPaintDevice*); + friend CGImageRef qt_mac_create_imagemask(const QPixmap&, const QRectF&); + friend IconRef qt_mac_create_iconref(const QPixmap&); + friend quint32 *qt_mac_pixmap_get_base(const QPixmap*); + friend int qt_mac_pixmap_get_bytes_per_line(const QPixmap*); +#endif + friend class QPixmapData; + friend class QX11PixmapData; + friend class QMacPixmapData; + friend class QS60PixmapData; + friend class QBitmap; + friend class QPaintDevice; + friend class QPainter; + friend class QGLWidget; + friend class QX11PaintEngine; + friend class QCoreGraphicsPaintEngine; + friend class QWidgetPrivate; + friend class QRasterBuffer; +#if !defined(QT_NO_DATASTREAM) + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPixmap &); +#endif + friend Q_GUI_EXPORT qint64 qt_pixmap_id(const QPixmap &pixmap); + +public: + QPixmapData* pixmapData() const; + +public: + typedef QExplicitlySharedDataPointer DataPtr; + inline DataPtr &data_ptr() { return data; } +}; + +Q_DECLARE_SHARED(QPixmap) + +inline QPixmap QPixmap::copy(int ax, int ay, int awidth, int aheight) const +{ + return copy(QRect(ax, ay, awidth, aheight)); +} + +inline void QPixmap::scroll(int dx, int dy, int ax, int ay, int awidth, int aheight, QRegion *exposed) +{ + scroll(dx, dy, QRect(ax, ay, awidth, aheight), exposed); +} + +inline bool QPixmap::loadFromData(const QByteArray &buf, const char *format, + Qt::ImageConversionFlags flags) +{ + return loadFromData(reinterpret_cast(buf.constData()), buf.size(), format, flags); +} + +/***************************************************************************** + QPixmap stream functions +*****************************************************************************/ + +#if !defined(QT_NO_DATASTREAM) +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPixmap &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPixmap &); +#endif + +/***************************************************************************** + QPixmap (and QImage) helper functions +*****************************************************************************/ +#ifdef QT3_SUPPORT +QT3_SUPPORT Q_GUI_EXPORT void copyBlt(QPixmap *dst, int dx, int dy, const QPixmap *src, + int sx=0, int sy=0, int sw=-1, int sh=-1); +#endif // QT3_SUPPORT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPIXMAP_H diff --git a/src/gui/image/qpixmap_blitter.cpp b/src/gui/image/qpixmap_blitter.cpp new file mode 100644 index 0000000000..e2cd745e7c --- /dev/null +++ b/src/gui/image/qpixmap_blitter.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmap_blitter_p.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +static int global_ser_no = 0; + +QBlittablePixmapData::QBlittablePixmapData() + : QPixmapData(QPixmapData::PixmapType,BlitterClass), m_engine(0), m_blittable(0) +#ifdef QT_BLITTER_RASTEROVERLAY + ,m_rasterOverlay(0), m_unmergedCopy(0) +#endif //QT_BLITTER_RASTEROVERLAY +{ + setSerialNumber(++global_ser_no); +} + +QBlittablePixmapData::~QBlittablePixmapData() +{ + delete m_blittable; + delete m_engine; +#ifdef QT_BLITTER_RASTEROVERLAY + delete m_rasterOverlay; + delete m_unmergedCopy; +#endif //QT_BLITTER_RASTEROVERLAY +} + +QBlittable *QBlittablePixmapData::blittable() const +{ + if (!m_blittable) { + QBlittablePixmapData *that = const_cast(this); + that->m_blittable = this->createBlittable(QSize(w,h)); + } + + return m_blittable; +} + +void QBlittablePixmapData::setBlittable(QBlittable *blittable) +{ + resize(blittable->size().width(),blittable->size().height()); + m_blittable = blittable; +} + +void QBlittablePixmapData::resize(int width, int height) +{ + + delete m_blittable; + m_blittable = 0; + delete m_engine; + m_engine = 0; +#ifdef Q_WS_QPA + d = QApplicationPrivate::platformIntegration()->screens().at(0)->depth(); +#endif + w = width; + h = height; + is_null = (w <= 0 || h <= 0); +} + +int QBlittablePixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + switch (metric) { + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmWidthMM: + return qRound(w * 25.4 / qt_defaultDpiX()); + case QPaintDevice::PdmHeightMM: + return qRound(h * 25.4 / qt_defaultDpiY()); + case QPaintDevice::PdmDepth: + return 32; + case QPaintDevice::PdmDpiX: // fall-through + case QPaintDevice::PdmPhysicalDpiX: + return qt_defaultDpiX(); + case QPaintDevice::PdmDpiY: // fall-through + case QPaintDevice::PdmPhysicalDpiY: + return qt_defaultDpiY(); + default: + qWarning("QRasterPixmapData::metric(): Unhandled metric type %d", metric); + break; + } + + return 0; +} + +void QBlittablePixmapData::fill(const QColor &color) +{ + //jlind: todo: change when blittables can support non opaque fillRects + if (color.alpha() == 255 && blittable()->capabilities() & QBlittable::SolidRectCapability) { + blittable()->unlock(); + blittable()->fillRect(QRectF(0,0,w,h),color); + }else { + uint pixel; + switch (blittable()->lock()->format()) { + case QImage::Format_ARGB32_Premultiplied: + pixel = PREMUL(color.rgba()); + break; + case QImage::Format_ARGB8565_Premultiplied: + pixel = qargb8565(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB8555_Premultiplied: + pixel = qargb8555(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB6666_Premultiplied: + pixel = qargb6666(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB4444_Premultiplied: + pixel = qargb4444(color.rgba()).rawValue(); + break; + default: + pixel = color.rgba(); + break; + } + //so premultiplied formats are supported and ARGB32 and RGB32 + blittable()->lock()->fill(pixel); + } + +} + +QImage *QBlittablePixmapData::buffer() +{ + return blittable()->lock(); +} + +QImage QBlittablePixmapData::toImage() const +{ + return blittable()->lock()->copy(); +} + +bool QBlittablePixmapData::hasAlphaChannel() const +{ + return blittable()->lock()->hasAlphaChannel(); +} + +void QBlittablePixmapData::fromImage(const QImage &image, + Qt::ImageConversionFlags flags) +{ + resize(image.width(),image.height()); + markRasterOverlay(QRect(0,0,w,h)); + QImage *thisImg = buffer(); + + QImage correctFormatPic = image; + if (correctFormatPic.format() != thisImg->format()) + correctFormatPic = correctFormatPic.convertToFormat(thisImg->format(), flags); + + uchar *mem = thisImg->bits(); + const uchar *bits = correctFormatPic.bits(); + int bytesCopied = 0; + while (bytesCopied < correctFormatPic.byteCount()) { + memcpy(mem,bits,correctFormatPic.bytesPerLine()); + mem += thisImg->bytesPerLine(); + bits += correctFormatPic.bytesPerLine(); + bytesCopied+=correctFormatPic.bytesPerLine(); + } +} + +QPaintEngine *QBlittablePixmapData::paintEngine() const +{ + if (!m_engine) { + QBlittablePixmapData *that = const_cast(this); + that->m_engine = new QBlitterPaintEngine(that); + } + return m_engine; +} + +#ifdef QT_BLITTER_RASTEROVERLAY + +static bool showRasterOverlay = !qgetenv("QT_BLITTER_RASTEROVERLAY").isEmpty(); + +void QBlittablePixmapData::mergeOverlay() +{ + if (m_unmergedCopy || !showRasterOverlay) + return; + m_unmergedCopy = new QImage(buffer()->copy()); + QPainter p(buffer()); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.drawImage(0,0,*overlay()); + p.end(); +} + +void QBlittablePixmapData::unmergeOverlay() +{ + if (!m_unmergedCopy || !showRasterOverlay) + return; + QPainter p(buffer()); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawImage(0,0,*m_unmergedCopy); + p.end(); + + delete m_unmergedCopy; + m_unmergedCopy = 0; +} + +QImage *QBlittablePixmapData::overlay() +{ + if (!m_rasterOverlay|| + m_rasterOverlay->size() != QSize(w,h)){ + m_rasterOverlay = new QImage(w,h,QImage::Format_ARGB32_Premultiplied); + m_rasterOverlay->fill(0x00000000); + uint color = (qrand() % 11)+7; + m_overlayColor = QColor(Qt::GlobalColor(color)); + m_overlayColor.setAlpha(0x88); + + } + return m_rasterOverlay; +} + +void QBlittablePixmapData::markRasterOverlayImpl(const QRectF &rect) +{ + if (!showRasterOverlay) + return; + QRectF transformationRect = clipAndTransformRect(rect); + if(!transformationRect.isEmpty()) { + QPainter p(overlay()); + p.setBrush(m_overlayColor); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(transformationRect,QBrush(m_overlayColor)); + } +} + +void QBlittablePixmapData::unmarkRasterOverlayImpl(const QRectF &rect) +{ + if (!showRasterOverlay) + return; + QRectF transformationRect = clipAndTransformRect(rect); + if (!transformationRect.isEmpty()) { + QPainter p(overlay()); + QColor color(0x00,0x00,0x00,0x00); + p.setBrush(color); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(transformationRect,QBrush(color)); + } +} + +QRectF QBlittablePixmapData::clipAndTransformRect(const QRectF &rect) const +{ + QRectF transformationRect = rect; + paintEngine(); + if (m_engine->state()) { + transformationRect = m_engine->state()->matrix.mapRect(rect); + const QClipData *clipData = m_engine->clip(); + if (clipData) { + if (clipData->hasRectClip) { + transformationRect &= clipData->clipRect; + } else if (clipData->hasRegionClip) { + const QVector rects = clipData->clipRegion.rects(); + for (int i = 0; i < rects.size(); i++) { + transformationRect &= rects.at(i); + } + } + } + } + return transformationRect; +} + +#endif //QT_BLITTER_RASTEROVERLAY + +QT_END_NAMESPACE + +#endif //QT_NO_BLITTABLE diff --git a/src/gui/image/qpixmap_blitter_p.h b/src/gui/image/qpixmap_blitter_p.h new file mode 100644 index 0000000000..9f4260c40d --- /dev/null +++ b/src/gui/image/qpixmap_blitter_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAP_BLITTER_P_H +#define QPIXMAP_BLITTER_P_H + +#include +#include + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QBlittablePixmapData : public QPixmapData +{ +// Q_DECLARE_PRIVATE(QBlittablePixmapData); +public: + QBlittablePixmapData(); + ~QBlittablePixmapData(); + + virtual QBlittable *createBlittable(const QSize &size) const = 0; + QBlittable *blittable() const; + void setBlittable(QBlittable *blittable); + + void resize(int width, int height); + int metric(QPaintDevice::PaintDeviceMetric metric) const; + void fill(const QColor &color); + QImage *buffer(); + QImage toImage() const; + bool hasAlphaChannel() const; + void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + + QPaintEngine *paintEngine() const; + + void markRasterOverlay(const QRectF &); + void markRasterOverlay(const QPointF &, const QTextItem &); + void markRasterOverlay(const QVectorPath &); + void markRasterOverlay(const QRect *rects, int rectCount); + void markRasterOverlay(const QRectF *rects, int rectCount); + void unmarkRasterOverlay(const QRectF &); + +#ifdef QT_BLITTER_RASTEROVERLAY + void mergeOverlay(); + void unmergeOverlay(); + QImage *overlay(); + +#endif //QT_BLITTER_RASTEROVERLAY +protected: + QBlitterPaintEngine *m_engine; + QBlittable *m_blittable; + +#ifdef QT_BLITTER_RASTEROVERLAY + QImage *m_rasterOverlay; + QImage *m_unmergedCopy; + QColor m_overlayColor; + + void markRasterOverlayImpl(const QRectF &); + void unmarkRasterOverlayImpl(const QRectF &); + QRectF clipAndTransformRect(const QRectF &) const; +#endif //QT_BLITTER_RASTEROVERLAY + +}; + +inline void QBlittablePixmapData::markRasterOverlay(const QRectF &rect) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + markRasterOverlayImpl(rect); +#else + Q_UNUSED(rect) +#endif +} + +inline void QBlittablePixmapData::markRasterOverlay(const QVectorPath &path) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + markRasterOverlayImpl(path.convertToPainterPath().boundingRect()); +#else + Q_UNUSED(path) +#endif +} + +inline void QBlittablePixmapData::markRasterOverlay(const QPointF &pos, const QTextItem &ti) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + QFontMetricsF fm(ti.font()); + QRectF rect = fm.tightBoundingRect(ti.text()); + rect.moveBottomLeft(pos); + markRasterOverlay(rect); +#else + Q_UNUSED(pos) + Q_UNUSED(ti) +#endif +} + +inline void QBlittablePixmapData::markRasterOverlay(const QRect *rects, int rectCount) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + for (int i = 0; i < rectCount; i++) { + markRasterOverlay(rects[i]); + } +#else + Q_UNUSED(rects) + Q_UNUSED(rectCount) +#endif +} +inline void QBlittablePixmapData::markRasterOverlay(const QRectF *rects, int rectCount) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + for (int i = 0; i < rectCount; i++) { + markRasterOverlay(rects[i]); + } +#else + Q_UNUSED(rects) + Q_UNUSED(rectCount) +#endif +} + +inline void QBlittablePixmapData::unmarkRasterOverlay(const QRectF &rect) +{ +#ifdef QT_BLITTER_RASTEROVERLAY + unmarkRasterOverlayImpl(rect); +#else + Q_UNUSED(rect) +#endif +} + +QT_END_NAMESPACE +#endif // QT_NO_BLITTABLE +#endif // QPIXMAP_BLITTER_P_H diff --git a/src/gui/image/qpixmap_mac.cpp b/src/gui/image/qpixmap_mac.cpp new file mode 100644 index 0000000000..72e2aa6e04 --- /dev/null +++ b/src/gui/image/qpixmap_mac.cpp @@ -0,0 +1,1195 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmap.h" +#include "qimage.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qmatrix.h" +#include "qtransform.h" +#include "qlibrary.h" +#include "qvarlengtharray.h" +#include "qdebug.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Externals + *****************************************************************************/ +extern const uchar *qt_get_bitflip_array(); //qimage.cpp +extern CGContextRef qt_mac_cg_context(const QPaintDevice *pdev); //qpaintdevice_mac.cpp +extern RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp +extern void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp +extern QRegion qt_mac_convert_mac_region(RgnHandle rgn); //qregion_mac.cpp + +static int qt_pixmap_serial = 0; + +Q_GUI_EXPORT quint32 *qt_mac_pixmap_get_base(const QPixmap *pix) +{ + return static_cast(pix->data.data())->pixels; +} + +Q_GUI_EXPORT int qt_mac_pixmap_get_bytes_per_line(const QPixmap *pix) +{ + return static_cast(pix->data.data())->bytesPerRow; +} + +void qt_mac_cgimage_data_free(void *info, const void *memoryToFree, size_t) +{ + QMacPixmapData *pmdata = static_cast(info); + if (!pmdata) { + free(const_cast(memoryToFree)); + } else { + if (QMacPixmapData::validDataPointers.contains(pmdata) == false) { + free(const_cast(memoryToFree)); + return; + } + if (pmdata->pixels == pmdata->pixelsToFree) { + // something we aren't expecting, just free it. + Q_ASSERT(memoryToFree != pmdata->pixelsToFree); + free(const_cast(memoryToFree)); + } else { + free(pmdata->pixelsToFree); + pmdata->pixelsToFree = static_cast(const_cast(memoryToFree)); + } + pmdata->cg_dataBeingReleased = 0; + } +} + +CGImageRef qt_mac_image_to_cgimage(const QImage &image) +{ + int bitsPerColor = 8; + int bitsPerPixel = 32; + if (image.depth() == 1) { + bitsPerColor = 1; + bitsPerPixel = 1; + } + QCFType provider = + CGDataProviderCreateWithData(0, image.bits(), image.bytesPerLine() * image.height(), + 0); + + uint cgflags = kCGImageAlphaPremultipliedFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + + CGImageRef cgImage = CGImageCreate(image.width(), image.height(), bitsPerColor, bitsPerPixel, + image.bytesPerLine(), + QCoreGraphicsPaintEngine::macGenericColorSpace(), + cgflags, provider, + 0, + 0, + kCGRenderingIntentDefault); + + return cgImage; +} + +/***************************************************************************** + QPixmap member functions + *****************************************************************************/ + +static inline QRgb qt_conv16ToRgb(ushort c) { + static const int qt_rbits = (565/100); + static const int qt_gbits = (565/10%10); + static const int qt_bbits = (565%10); + static const int qt_red_shift = qt_bbits+qt_gbits-(8-qt_rbits); + static const int qt_green_shift = qt_bbits-(8-qt_gbits); + static const int qt_neg_blue_shift = 8-qt_bbits; + static const int qt_blue_mask = (1<> qt_red_shift; + const int tg = g >> qt_green_shift; + const int tb = b << qt_neg_blue_shift; + + return qRgb(tr,tg,tb); +} + +QSet QMacPixmapData::validDataPointers; + +QMacPixmapData::QMacPixmapData(PixelType type) + : QPixmapData(type, MacClass), has_alpha(0), has_mask(0), + uninit(true), pixels(0), pixelsSize(0), pixelsToFree(0), + bytesPerRow(0), cg_data(0), cg_dataBeingReleased(0), cg_mask(0), + pengine(0) +{ +} + +QPixmapData *QMacPixmapData::createCompatiblePixmapData() const +{ + return new QMacPixmapData(pixelType()); +} + +#define BEST_BYTE_ALIGNMENT 16 +#define COMPTUE_BEST_BYTES_PER_ROW(bpr) \ + (((bpr) + (BEST_BYTE_ALIGNMENT - 1)) & ~(BEST_BYTE_ALIGNMENT - 1)) + +void QMacPixmapData::resize(int width, int height) +{ + setSerialNumber(++qt_pixmap_serial); + + w = width; + h = height; + is_null = (w <= 0 || h <= 0); + d = (pixelType() == BitmapType ? 1 : 32); + bool make_null = w <= 0 || h <= 0; // create null pixmap + if (make_null || d == 0) { + w = 0; + h = 0; + is_null = true; + d = 0; + if (!make_null) + qWarning("Qt: QPixmap: Invalid pixmap parameters"); + return; + } + + if (w < 1 || h < 1) + return; + + //create the pixels + bytesPerRow = w * sizeof(quint32); // Minimum bytes per row. + + // Quartz2D likes things as a multple of 16 (for now). + bytesPerRow = COMPTUE_BEST_BYTES_PER_ROW(bytesPerRow); + macCreatePixels(); +} + +#undef COMPUTE_BEST_BYTES_PER_ROW + +void QMacPixmapData::fromImage(const QImage &img, + Qt::ImageConversionFlags flags) +{ + setSerialNumber(++qt_pixmap_serial); + + // the conversion code only handles format >= + // Format_ARGB32_Premultiplied at the moment.. + if (img.format() > QImage::Format_ARGB32_Premultiplied) { + QImage image; + if (img.hasAlphaChannel()) + image = img.convertToFormat(QImage::Format_ARGB32_Premultiplied); + else + image = img.convertToFormat(QImage::Format_RGB32); + fromImage(image, flags); + return; + } + + w = img.width(); + h = img.height(); + is_null = (w <= 0 || h <= 0); + d = (pixelType() == BitmapType ? 1 : img.depth()); + + QImage image = img; + int dd = QPixmap::defaultDepth(); + bool force_mono = (dd == 1 || + (flags & Qt::ColorMode_Mask)==Qt::MonoOnly); + if (force_mono) { // must be monochrome + if (d != 1) { + image = image.convertToFormat(QImage::Format_MonoLSB, flags); // dither + d = 1; + } + } else { // can be both + bool conv8 = false; + if(d > 8 && dd <= 8) { // convert to 8 bit + if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither) + flags = (flags & ~Qt::DitherMode_Mask) + | Qt::PreferDither; + conv8 = true; + } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) { + conv8 = d == 1; // native depth wanted + } else if (d == 1) { + if (image.colorCount() == 2) { + QRgb c0 = image.color(0); // Auto: convert to best + QRgb c1 = image.color(1); + conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255); + } else { + // eg. 1-color monochrome images (they do exist). + conv8 = true; + } + } + if (conv8) { + image = image.convertToFormat(QImage::Format_Indexed8, flags); + d = 8; + } + } + + if (image.depth()==1) { + image.setColor(0, QColor(Qt::color0).rgba()); + image.setColor(1, QColor(Qt::color1).rgba()); + } + + if (d == 16 || d == 24) { + image = image.convertToFormat(QImage::Format_RGB32, flags); + fromImage(image, flags); + return; + } + + // different size or depth, make a new pixmap + resize(w, h); + + quint32 *dptr = pixels, *drow; + const uint dbpr = bytesPerRow; + + const QImage::Format sfmt = image.format(); + const unsigned short sbpr = image.bytesPerLine(); + + // use const_cast to prevent a detach + const uchar *sptr = const_cast(image).bits(), *srow; + + for (int y = 0; y < h; ++y) { + drow = dptr + (y * (dbpr / 4)); + srow = sptr + (y * sbpr); + switch(sfmt) { + case QImage::Format_MonoLSB: + case QImage::Format_Mono:{ + for (int x = 0; x < w; ++x) { + char one_bit = *(srow + (x / 8)); + if (sfmt == QImage::Format_Mono) + one_bit = one_bit >> (7 - (x % 8)); + else + one_bit = one_bit >> (x % 8); + if ((one_bit & 0x01)) + *(drow+x) = 0xFF000000; + else + *(drow+x) = 0xFFFFFFFF; + } + break; + } + case QImage::Format_Indexed8: { + int numColors = image.numColors(); + if (numColors > 0) { + for (int x = 0; x < w; ++x) { + int index = *(srow + x); + *(drow+x) = PREMUL(image.color(qMin(index, numColors))); + } + } + } break; + case QImage::Format_RGB32: + for (int x = 0; x < w; ++x) + *(drow+x) = *(((quint32*)srow) + x) | 0xFF000000; + break; + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + for (int x = 0; x < w; ++x) { + if(sfmt == QImage::Format_RGB32) + *(drow+x) = 0xFF000000 | (*(((quint32*)srow) + x) & 0x00FFFFFF); + else if(sfmt == QImage::Format_ARGB32_Premultiplied) + *(drow+x) = *(((quint32*)srow) + x); + else + *(drow+x) = PREMUL(*(((quint32*)srow) + x)); + } + break; + default: + qWarning("Qt: internal: Oops: Forgot a format [%d] %s:%d", sfmt, + __FILE__, __LINE__); + break; + } + } + if (sfmt != QImage::Format_RGB32) { //setup the alpha + bool alphamap = image.depth() == 32; + if (sfmt == QImage::Format_Indexed8) { + const QVector rgb = image.colorTable(); + for (int i = 0, count = image.colorCount(); i < count; ++i) { + const int alpha = qAlpha(rgb[i]); + if (alpha != 0xff) { + alphamap = true; + break; + } + } + } + macSetHasAlpha(alphamap); + } + uninit = false; +} + +int get_index(QImage * qi,QRgb mycol) +{ + int loopc; + for(loopc=0;loopccolorCount();loopc++) { + if(qi->color(loopc)==mycol) + return loopc; + } + qi->setColorCount(qi->colorCount()+1); + qi->setColor(qi->colorCount(),mycol); + return qi->colorCount(); +} + +QImage QMacPixmapData::toImage() const +{ + QImage::Format format = QImage::Format_MonoLSB; + if (d != 1) //Doesn't support index color modes + format = (has_alpha ? QImage::Format_ARGB32_Premultiplied : + QImage::Format_RGB32); + + QImage image(w, h, format); + quint32 *sptr = pixels, *srow; + const uint sbpr = bytesPerRow; + if (format == QImage::Format_MonoLSB) { + image.fill(0); + image.setColorCount(2); + image.setColor(0, QColor(Qt::color0).rgba()); + image.setColor(1, QColor(Qt::color1).rgba()); + for (int y = 0; y < h; ++y) { + uchar *scanLine = image.scanLine(y); + srow = sptr + (y * (sbpr/4)); + for (int x = 0; x < w; ++x) { + if (!(*(srow + x) & RGB_MASK)) + scanLine[x >> 3] |= (1 << (x & 7)); + } + } + } else { + for (int y = 0; y < h; ++y) { + srow = sptr + (y * (sbpr / 4)); + memcpy(image.scanLine(y), srow, w * 4); + } + + } + + return image; +} + +void QMacPixmapData::fill(const QColor &fillColor) + +{ + { //we don't know what backend to use so we cannot paint here + quint32 *dptr = pixels; + Q_ASSERT_X(dptr, "QPixmap::fill", "No dptr"); + const quint32 colr = PREMUL(fillColor.rgba()); + const int nbytes = bytesPerRow * h; + if (!colr) { + memset(dptr, 0, nbytes); + } else { + for (uint i = 0; i < nbytes / sizeof(quint32); ++i) + *(dptr + i) = colr; + } + } + + // If we had an alpha channel from before, don't + // switch it off. Only go from no alpha to alpha: + if (fillColor.alpha() != 255) + macSetHasAlpha(true); +} + +QPixmap QMacPixmapData::alphaChannel() const +{ + if (!has_alpha) + return QPixmap(); + + QMacPixmapData *alpha = new QMacPixmapData(PixmapType); + alpha->resize(w, h); + macGetAlphaChannel(alpha, false); + return QPixmap(alpha); +} + +void QMacPixmapData::setAlphaChannel(const QPixmap &alpha) +{ + has_mask = true; + QMacPixmapData *alphaData = static_cast(alpha.data.data()); + macSetAlphaChannel(alphaData, false); +} + +QBitmap QMacPixmapData::mask() const +{ + if (!has_mask && !has_alpha) + return QBitmap(); + + QMacPixmapData *mask = new QMacPixmapData(BitmapType); + mask->resize(w, h); + macGetAlphaChannel(mask, true); + return QPixmap(mask); +} + +void QMacPixmapData::setMask(const QBitmap &mask) +{ + if (mask.isNull()) { + QMacPixmapData opaque(PixmapType); + opaque.resize(w, h); + opaque.fill(QColor(255, 255, 255, 255)); + macSetAlphaChannel(&opaque, true); + has_alpha = has_mask = false; + return; + } + + has_alpha = false; + has_mask = true; + QMacPixmapData *maskData = static_cast(mask.data.data()); + macSetAlphaChannel(maskData, true); +} + +int QMacPixmapData::metric(QPaintDevice::PaintDeviceMetric theMetric) const +{ + switch (theMetric) { + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmWidthMM: + return qRound(metric(QPaintDevice::PdmWidth) * 25.4 / qreal(metric(QPaintDevice::PdmDpiX))); + case QPaintDevice::PdmHeightMM: + return qRound(metric(QPaintDevice::PdmHeight) * 25.4 / qreal(metric(QPaintDevice::PdmDpiY))); + case QPaintDevice::PdmNumColors: + return 1 << d; + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmPhysicalDpiX: { + extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_x()); + } + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiY: { + extern float qt_mac_defaultDpi_y(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_y()); + } + case QPaintDevice::PdmDepth: + return d; + default: + qWarning("QPixmap::metric: Invalid metric command"); + } + return 0; +} + +QMacPixmapData::~QMacPixmapData() +{ + validDataPointers.remove(this); + if (cg_mask) { + CGImageRelease(cg_mask); + cg_mask = 0; + } + + delete pengine; // Make sure we aren't drawing on the context anymore. + if (cg_data) { + CGImageRelease(cg_data); + } else if (!cg_dataBeingReleased && pixels != pixelsToFree) { + free(pixels); + } + free(pixelsToFree); +} + +void QMacPixmapData::macSetAlphaChannel(const QMacPixmapData *pix, bool asMask) +{ + if (!pixels || !h || !w || pix->w != w || pix->h != h) + return; + + quint32 *dptr = pixels, *drow; + const uint dbpr = bytesPerRow; + const unsigned short sbpr = pix->bytesPerRow; + quint32 *sptr = pix->pixels, *srow; + for (int y=0; y < h; ++y) { + drow = dptr + (y * (dbpr/4)); + srow = sptr + (y * (sbpr/4)); + if(d == 1) { + for (int x=0; x < w; ++x) { + if((*(srow+x) & RGB_MASK)) + *(drow+x) = 0xFFFFFFFF; + } + } else if(d == 8) { + for (int x=0; x < w; ++x) + *(drow+x) = (*(drow+x) & RGB_MASK) | (*(srow+x) << 24); + } else if(asMask) { + for (int x=0; x < w; ++x) { + if(*(srow+x) & RGB_MASK) + *(drow+x) = (*(drow+x) & RGB_MASK); + else + *(drow+x) = (*(drow+x) & RGB_MASK) | 0xFF000000; + *(drow+x) = PREMUL(*(drow+x)); + } + } else { + for (int x=0; x < w; ++x) { + const uchar alpha = qGray(qRed(*(srow+x)), qGreen(*(srow+x)), qBlue(*(srow+x))); + const uchar destAlpha = qt_div_255(alpha * qAlpha(*(drow+x))); +#if 1 + *(drow+x) = (*(drow+x) & RGB_MASK) | (destAlpha << 24); +#else + *(drow+x) = qRgba(qt_div_255(qRed(*(drow+x) * alpha)), + qt_div_255(qGreen(*(drow+x) * alpha)), + qt_div_255(qBlue(*(drow+x) * alpha)), destAlpha); +#endif + *(drow+x) = PREMUL(*(drow+x)); + } + } + } + macSetHasAlpha(true); +} + +void QMacPixmapData::macGetAlphaChannel(QMacPixmapData *pix, bool asMask) const +{ + quint32 *dptr = pix->pixels, *drow; + const uint dbpr = pix->bytesPerRow; + const unsigned short sbpr = bytesPerRow; + quint32 *sptr = pixels, *srow; + for(int y=0; y < h; ++y) { + drow = dptr + (y * (dbpr/4)); + srow = sptr + (y * (sbpr/4)); + if(asMask) { + for (int x = 0; x < w; ++x) { + if (*(srow + x) & qRgba(0, 0, 0, 255)) + *(drow + x) = 0x00000000; + else + *(drow + x) = 0xFFFFFFFF; + } + } else { + for (int x = 0; x < w; ++x) { + const int alpha = qAlpha(*(srow + x)); + *(drow + x) = qRgb(alpha, alpha, alpha); + } + } + } +} + +void QMacPixmapData::macSetHasAlpha(bool b) +{ + has_alpha = b; + macReleaseCGImageRef(); +} + +void QMacPixmapData::macCreateCGImageRef() +{ + Q_ASSERT(cg_data == 0); + //create the cg data + CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macDisplayColorSpace(); + QCFType provider = CGDataProviderCreateWithData(this, + pixels, bytesPerRow * h, + qt_mac_cgimage_data_free); + validDataPointers.insert(this); + uint cgflags = kCGImageAlphaPremultipliedFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + cg_data = CGImageCreate(w, h, 8, 32, bytesPerRow, colorspace, + cgflags, provider, 0, 0, kCGRenderingIntentDefault); +} + +void QMacPixmapData::macReleaseCGImageRef() +{ + if (!cg_data) + return; // There's nothing we need to do + + cg_dataBeingReleased = cg_data; + CGImageRelease(cg_data); + cg_data = 0; + + if (pixels != pixelsToFree) { + macCreatePixels(); + } else { + pixelsToFree = 0; + } +} + + +// We create our space in memory to paint on here. If we already have existing pixels +// copy them over. This is to preserve the fact that CGImageRef's are immutable. +void QMacPixmapData::macCreatePixels() +{ + const int numBytes = bytesPerRow * h; + quint32 *base_pixels; + if (pixelsToFree && pixelsToFree != pixels) { + // Reuse unused block of memory lying around from a previous callback. + base_pixels = pixelsToFree; + pixelsToFree = 0; + } else { + // We need a block of memory to do stuff with. + base_pixels = static_cast(malloc(numBytes)); + } + + if (pixels) + memcpy(base_pixels, pixels, pixelsSize); + pixels = base_pixels; + pixelsSize = numBytes; +} + +#if 0 +QPixmap QMacPixmapData::transformed(const QTransform &transform, + Qt::TransformationMode mode) const +{ + int w, h; // size of target pixmap + const int ws = width(); + const int hs = height(); + + QTransform mat(transform.m11(), transform.m12(), + transform.m21(), transform.m22(), 0., 0.); + if (transform.m12() == 0.0F && transform.m21() == 0.0F && + transform.m11() >= 0.0F && transform.m22() >= 0.0F) + { + h = int(qAbs(mat.m22()) * hs + 0.9999); + w = int(qAbs(mat.m11()) * ws + 0.9999); + h = qAbs(h); + w = qAbs(w); + } else { // rotation or shearing + QPolygonF a(QRectF(0,0,ws+1,hs+1)); + a = mat.map(a); + QRectF r = a.boundingRect().normalized(); + w = int(r.width() + 0.9999); + h = int(r.height() + 0.9999); + } + mat = QPixmap::trueMatrix(mat, ws, hs); + if (!h || !w) + return QPixmap(); + + // create destination + QMacPixmapData *pm = new QMacPixmapData(pixelType(), w, h); + const quint32 *sptr = pixels; + quint32 *dptr = pm->pixels; + memset(dptr, 0, (pm->bytesPerRow * pm->h)); + + // do the transform + if (mode == Qt::SmoothTransformation) { +#warning QMacPixmapData::transformed not properly implemented + qWarning("QMacPixmapData::transformed not properly implemented"); +#if 0 + QPainter p(&pm); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.setTransform(mat); + p.drawPixmap(0, 0, *this); +#endif + } else { + bool invertible; + mat = mat.inverted(&invertible); + if (!invertible) + return QPixmap(); + + const int bpp = 32; + const int xbpl = (w * bpp) / 8; + if (!qt_xForm_helper(mat, 0, QT_XFORM_TYPE_MSBFIRST, bpp, + (uchar*)dptr, xbpl, (pm->bytesPerRow) - xbpl, + h, (uchar*)sptr, (bytesPerRow), ws, hs)) { + qWarning("QMacPixmapData::transform(): failure"); + return QPixmap(); + } + } + + // update the alpha + pm->macSetHasAlpha(true); + return QPixmap(pm); +} +#endif + +QT_BEGIN_INCLUDE_NAMESPACE +#include +#include +QT_END_INCLUDE_NAMESPACE + +// Load and resolve the symbols we need from OpenGL manually so QtGui doesn't have to link against the OpenGL framework. +typedef CGLError (*PtrCGLChoosePixelFormat)(const CGLPixelFormatAttribute *, CGLPixelFormatObj *, long *); +typedef CGLError (*PtrCGLClearDrawable)(CGLContextObj); +typedef CGLError (*PtrCGLCreateContext)(CGLPixelFormatObj, CGLContextObj, CGLContextObj *); +typedef CGLError (*PtrCGLDestroyContext)(CGLContextObj); +typedef CGLError (*PtrCGLDestroyPixelFormat)(CGLPixelFormatObj); +typedef CGLError (*PtrCGLSetCurrentContext)(CGLContextObj); +typedef CGLError (*PtrCGLSetFullScreen)(CGLContextObj); +typedef void (*PtrglFinish)(); +typedef void (*PtrglPixelStorei)(GLenum, GLint); +typedef void (*PtrglReadBuffer)(GLenum); +typedef void (*PtrglReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *); + +static PtrCGLChoosePixelFormat ptrCGLChoosePixelFormat = 0; +static PtrCGLClearDrawable ptrCGLClearDrawable = 0; +static PtrCGLCreateContext ptrCGLCreateContext = 0; +static PtrCGLDestroyContext ptrCGLDestroyContext = 0; +static PtrCGLDestroyPixelFormat ptrCGLDestroyPixelFormat = 0; +static PtrCGLSetCurrentContext ptrCGLSetCurrentContext = 0; +static PtrCGLSetFullScreen ptrCGLSetFullScreen = 0; +static PtrglFinish ptrglFinish = 0; +static PtrglPixelStorei ptrglPixelStorei = 0; +static PtrglReadBuffer ptrglReadBuffer = 0; +static PtrglReadPixels ptrglReadPixels = 0; + +static bool resolveOpenGLSymbols() +{ + if (ptrCGLChoosePixelFormat == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/OpenGL.framework/OpenGL")); + ptrCGLChoosePixelFormat = (PtrCGLChoosePixelFormat)(library.resolve("CGLChoosePixelFormat")); + ptrCGLClearDrawable = (PtrCGLClearDrawable)(library.resolve("CGLClearDrawable")); + ptrCGLCreateContext = (PtrCGLCreateContext)(library.resolve("CGLCreateContext")); + ptrCGLDestroyContext = (PtrCGLDestroyContext)(library.resolve("CGLDestroyContext")); + ptrCGLDestroyPixelFormat = (PtrCGLDestroyPixelFormat)(library.resolve("CGLDestroyPixelFormat")); + ptrCGLSetCurrentContext = (PtrCGLSetCurrentContext)(library.resolve("CGLSetCurrentContext")); + ptrCGLSetFullScreen = (PtrCGLSetFullScreen)(library.resolve("CGLSetFullScreen")); + ptrglFinish = (PtrglFinish)(library.resolve("glFinish")); + ptrglPixelStorei = (PtrglPixelStorei)(library.resolve("glPixelStorei")); + ptrglReadBuffer = (PtrglReadBuffer)(library.resolve("glReadBuffer")); + ptrglReadPixels = (PtrglReadPixels)(library.resolve("glReadPixels")); + } + return ptrCGLChoosePixelFormat && ptrCGLClearDrawable && ptrCGLCreateContext + && ptrCGLDestroyContext && ptrCGLDestroyPixelFormat && ptrCGLSetCurrentContext + && ptrCGLSetFullScreen && ptrglFinish && ptrglPixelStorei + && ptrglReadBuffer && ptrglReadPixels; +} + +// Inverts the given pixmap in the y direction. +static void qt_mac_flipPixmap(void *data, int rowBytes, int height) +{ + int bottom = height - 1; + void *base = data; + void *buffer = malloc(rowBytes); + + int top = 0; + while ( top < bottom ) + { + void *topP = (void *)((top * rowBytes) + (intptr_t)base); + void *bottomP = (void *)((bottom * rowBytes) + (intptr_t)base); + + bcopy( topP, buffer, rowBytes ); + bcopy( bottomP, topP, rowBytes ); + bcopy( buffer, bottomP, rowBytes ); + + ++top; + --bottom; + } + free(buffer); +} + +// Grabs displayRect from display and places it into buffer. +static void qt_mac_grabDisplayRect(CGDirectDisplayID display, const QRect &displayRect, void *buffer) +{ + if (display == kCGNullDirectDisplay) + return; + + CGLPixelFormatAttribute attribs[] = { + kCGLPFAFullScreen, + kCGLPFADisplayMask, + (CGLPixelFormatAttribute)0, /* Display mask bit goes here */ + (CGLPixelFormatAttribute)0 + }; + + attribs[2] = (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(display); + + // Build a full-screen GL context + CGLPixelFormatObj pixelFormatObj; + long numPixelFormats; + + ptrCGLChoosePixelFormat( attribs, &pixelFormatObj, &numPixelFormats ); + + if (!pixelFormatObj) // No full screen context support + return; + + CGLContextObj glContextObj; + ptrCGLCreateContext(pixelFormatObj, 0, &glContextObj); + ptrCGLDestroyPixelFormat(pixelFormatObj) ; + if (!glContextObj) + return; + + ptrCGLSetCurrentContext(glContextObj); + ptrCGLSetFullScreen(glContextObj) ; + + ptrglReadBuffer(GL_FRONT); + + ptrglFinish(); // Finish all OpenGL commands + ptrglPixelStorei(GL_PACK_ALIGNMENT, 4); // Force 4-byte alignment + ptrglPixelStorei(GL_PACK_ROW_LENGTH, 0); + ptrglPixelStorei(GL_PACK_SKIP_ROWS, 0); + ptrglPixelStorei(GL_PACK_SKIP_PIXELS, 0); + + // Fetch the data in XRGB format, matching the bitmap context. + ptrglReadPixels(GLint(displayRect.x()), GLint(displayRect.y()), + GLint(displayRect.width()), GLint(displayRect.height()), +#ifdef __BIG_ENDIAN__ + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer +#else + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, buffer +#endif + ); + + ptrCGLSetCurrentContext(0); + ptrCGLClearDrawable(glContextObj); // disassociate from full screen + ptrCGLDestroyContext(glContextObj); // and destroy the context +} + +// Returns a pixmap containing the screen contents at rect. +static QPixmap qt_mac_grabScreenRect(const QRect &rect) +{ + if (!resolveOpenGLSymbols()) + return QPixmap(); + + const int maxDisplays = 128; // 128 displays should be enough for everyone. + CGDirectDisplayID displays[maxDisplays]; + CGDisplayCount displayCount; + const CGRect cgRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); + + if (err && displayCount == 0) + return QPixmap(); + + long bytewidth = rect.width() * 4; // Assume 4 bytes/pixel for now + bytewidth = (bytewidth + 3) & ~3; // Align to 4 bytes + QVarLengthArray buffer(rect.height() * bytewidth); + + for (uint i = 0; i < displayCount; ++i) { + const CGRect bounds = CGDisplayBounds(displays[i]); + // Translate to display-local coordinates + QRect displayRect = rect.translated(qRound(-bounds.origin.x), qRound(-bounds.origin.y)); + // Adjust for inverted y axis. + displayRect.moveTop(qRound(bounds.size.height) - displayRect.y() - rect.height()); + qt_mac_grabDisplayRect(displays[i], displayRect, buffer.data()); + } + + qt_mac_flipPixmap(buffer.data(), bytewidth, rect.height()); + QCFType bitmap = CGBitmapContextCreate(buffer.data(), rect.width(), + rect.height(), 8, bytewidth, + QCoreGraphicsPaintEngine::macGenericColorSpace(), + kCGImageAlphaNoneSkipFirst); + QCFType image = CGBitmapContextCreateImage(bitmap); + return QPixmap::fromMacCGImageRef(image); +} + +#ifndef QT_MAC_USE_COCOA // no QuickDraw in 64-bit mode +static QPixmap qt_mac_grabScreenRect_10_3(int x, int y, int w, int h, QWidget *widget) +{ + QPixmap pm = QPixmap(w, h); + extern WindowPtr qt_mac_window_for(const QWidget *); // qwidget_mac.cpp + const BitMap *windowPort = 0; + if((widget->windowType() == Qt::Desktop)) { + GDHandle gdh; + if(!(gdh=GetMainDevice())) + qDebug("Qt: internal: Unexpected condition reached: %s:%d", __FILE__, __LINE__); + windowPort = (BitMap*)(*(*gdh)->gdPMap); + } else { + windowPort = GetPortBitMapForCopyBits(GetWindowPort(qt_mac_window_for(widget))); + } + const BitMap *pixmapPort = GetPortBitMapForCopyBits(static_cast(pm.macQDHandle())); + Rect macSrcRect, macDstRect; + SetRect(&macSrcRect, x, y, x + w, y + h); + SetRect(&macDstRect, 0, 0, w, h); + CopyBits(windowPort, pixmapPort, &macSrcRect, &macDstRect, srcCopy, 0); + return pm; +} +#endif + +QPixmap QPixmap::grabWindow(WId window, int x, int y, int w, int h) +{ + QWidget *widget = QWidget::find(window); + if (widget == 0) + return QPixmap(); + + if(w == -1) + w = widget->width() - x; + if(h == -1) + h = widget->height() - y; + + QPoint globalCoord(0, 0); + globalCoord = widget->mapToGlobal(globalCoord); + QRect rect(globalCoord.x() + x, globalCoord.y() + y, w, h); + +#ifdef QT_MAC_USE_COCOA + return qt_mac_grabScreenRect(rect); +#else +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + return qt_mac_grabScreenRect(rect); + } else +#endif + { + return qt_mac_grabScreenRect_10_3(x, y, w, h, widget); + } +#endif // ifdef Q_WS_MAC64 +} + +/*! \internal + + Returns the QuickDraw CGrafPtr of the pixmap. 0 is returned if it can't + be obtained. Do not hold the pointer around for long as it can be + relocated. + + \warning This function is only available on Mac OS X. + \warning As of Qt 4.6, this function \e{always} returns zero. +*/ + +Qt::HANDLE QPixmap::macQDHandle() const +{ + return 0; +} + +/*! \internal + + Returns the QuickDraw CGrafPtr of the pixmap's alpha channel. 0 is + returned if it can't be obtained. Do not hold the pointer around for + long as it can be relocated. + + \warning This function is only available on Mac OS X. + \warning As of Qt 4.6, this function \e{always} returns zero. +*/ + +Qt::HANDLE QPixmap::macQDAlphaHandle() const +{ + return 0; +} + +/*! \internal + + Returns the CoreGraphics CGContextRef of the pixmap. 0 is returned if + it can't be obtained. It is the caller's responsiblity to + CGContextRelease the context when finished using it. + + \warning This function is only available on Mac OS X. +*/ + +Qt::HANDLE QPixmap::macCGHandle() const +{ + if (isNull()) + return 0; + + if (data->classId() == QPixmapData::MacClass) { + QMacPixmapData *d = static_cast(data.data()); + if (!d->cg_data) + d->macCreateCGImageRef(); + CGImageRef ret = d->cg_data; + CGImageRetain(ret); + return ret; + } else if (data->classId() == QPixmapData::RasterClass) { + return qt_mac_image_to_cgimage(static_cast(data.data())->image); + } + return 0; +} + +bool QMacPixmapData::hasAlphaChannel() const +{ + return has_alpha; +} + +CGImageRef qt_mac_create_imagemask(const QPixmap &pixmap, const QRectF &sr) +{ + QMacPixmapData *px = static_cast(pixmap.data.data()); + if (px->cg_mask) { + if (px->cg_mask_rect == sr) { + CGImageRetain(px->cg_mask); //reference for the caller + return px->cg_mask; + } + CGImageRelease(px->cg_mask); + px->cg_mask = 0; + } + + const int sx = qRound(sr.x()), sy = qRound(sr.y()), sw = qRound(sr.width()), sh = qRound(sr.height()); + const int sbpr = px->bytesPerRow; + const uint nbytes = sw * sh; + // alpha is always 255 for bitmaps, ignore it in this case. + const quint32 mask = px->depth() == 1 ? 0x00ffffff : 0xffffffff; + quint8 *dptr = static_cast(malloc(nbytes)); + quint32 *sptr = px->pixels, *srow; + for(int y = sy, offset=0; y < sh; ++y) { + srow = sptr + (y * (sbpr / 4)); + for(int x = sx; x < sw; ++x) + *(dptr+(offset++)) = (*(srow+x) & mask) ? 255 : 0; + } + QCFType provider = CGDataProviderCreateWithData(0, dptr, nbytes, qt_mac_cgimage_data_free); + px->cg_mask = CGImageMaskCreate(sw, sh, 8, 8, nbytes / sh, provider, 0, 0); + px->cg_mask_rect = sr; + CGImageRetain(px->cg_mask); //reference for the caller + return px->cg_mask; +} + +#ifndef QT_MAC_USE_COCOA +IconRef qt_mac_create_iconref(const QPixmap &px) +{ + if (px.isNull()) + return 0; + + //create icon + IconFamilyHandle iconFamily = reinterpret_cast(NewHandle(0)); + //create data + { + struct { + OSType mac_type; + int width, height, depth; + bool mask; + } images[] = { + { kThumbnail32BitData, 128, 128, 32, false }, + { kThumbnail8BitMask, 128, 128, 8, true }, + { 0, 0, 0, 0, false } //end marker + }; + for(int i = 0; images[i].mac_type; i++) { + //get QPixmap data + QImage scaled_px = px.toImage().scaled(images[i].width, images[i].height); + + quint32 *sptr = (quint32 *) scaled_px.bits(); + quint32 *srow; + uint sbpr = scaled_px.bytesPerLine(); + + //get Handle data + const int dbpr = images[i].width * (images[i].depth/8); + Handle hdl = NewHandle(dbpr*images[i].height); + if(!sptr) { //handle null pixmap + memset((*hdl), '\0', dbpr*images[i].height); + } else if(images[i].mask) { + if(images[i].mac_type == kThumbnail8BitMask) { + for(int y = 0, hindex = 0; y < images[i].height; ++y) { + srow = sptr + (y * (sbpr/4)); + for(int x = 0; x < images[i].width; ++x) + *((*hdl)+(hindex++)) = qAlpha(*(srow+x)); + } + } + } else { + char *dest = (*hdl); +#if defined(__i386__) + if(images[i].depth == 32) { + for(int y = 0; y < images[i].height; ++y) { + uint *source = (uint*)((const uchar*)sptr+(sbpr*y)); + for(int x = 0; x < images[i].width; ++x, dest += 4) + *((uint*)dest) = CFSwapInt32(*(source + x)); + } + } else +#endif + { + for(int y = 0; y < images[i].height; ++y) + memcpy(dest+(y*dbpr), ((const uchar*)sptr+(sbpr*y)), dbpr); + } + } + + //set the family data to the Handle + OSStatus set = SetIconFamilyData(iconFamily, images[i].mac_type, hdl); + if(set != noErr) + qWarning("%s: %d -- Unable to create icon data[%d]!! %ld", + __FILE__, __LINE__, i, long(set)); + DisposeHandle(hdl); + } + } + + //acquire and cleanup + IconRef ret; + static int counter = 0; + const OSType kQtCreator = 'CUTE'; + RegisterIconRefFromIconFamily(kQtCreator, (OSType)counter, iconFamily, &ret); + AcquireIconRef(ret); + UnregisterIconRef(kQtCreator, (OSType)counter); + DisposeHandle(reinterpret_cast(iconFamily)); + counter++; + return ret; + +} +#endif + +/*! \internal */ +QPaintEngine* QMacPixmapData::paintEngine() const +{ + if (!pengine) { + QMacPixmapData *that = const_cast(this); + that->pengine = new QCoreGraphicsPaintEngine(); + } + return pengine; +} + +void QMacPixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + if (data->pixelType() == BitmapType) { + QBitmap::fromImage(toImage().copy(rect)); + return; + } + + const QMacPixmapData *macData = static_cast(data); + + resize(rect.width(), rect.height()); + + has_alpha = macData->has_alpha; + has_mask = macData->has_mask; + uninit = false; + + const int x = rect.x(); + const int y = rect.y(); + char *dest = reinterpret_cast(pixels); + const char *src = reinterpret_cast(macData->pixels + x) + y * macData->bytesPerRow; + for (int i = 0; i < h; ++i) { + memcpy(dest, src, w * 4); + dest += bytesPerRow; + src += macData->bytesPerRow; + } + + has_alpha = macData->has_alpha; + has_mask = macData->has_mask; +} + +bool QMacPixmapData::scroll(int dx, int dy, const QRect &rect) +{ + Q_UNUSED(dx); + Q_UNUSED(dy); + Q_UNUSED(rect); + return false; +} + +/*! + \since 4.2 + + Creates a \c CGImageRef equivalent to the QPixmap. Returns the \c CGImageRef handle. + + It is the caller's responsibility to release the \c CGImageRef data + after use. + + \warning This function is only available on Mac OS X. + + \sa fromMacCGImageRef() +*/ +CGImageRef QPixmap::toMacCGImageRef() const +{ + return (CGImageRef)macCGHandle(); +} + +/*! + \since 4.2 + + Returns a QPixmap that is equivalent to the given \a image. + + \warning This function is only available on Mac OS X. + + \sa toMacCGImageRef(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ +QPixmap QPixmap::fromMacCGImageRef(CGImageRef image) +{ + const size_t w = CGImageGetWidth(image), + h = CGImageGetHeight(image); + QPixmap ret(w, h); + ret.fill(Qt::transparent); + CGRect rect = CGRectMake(0, 0, w, h); + CGContextRef ctx = qt_mac_cg_context(&ret); + qt_mac_drawCGImage(ctx, &rect, image); + CGContextRelease(ctx); + return ret; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_mac_p.h b/src/gui/image/qpixmap_mac_p.h new file mode 100644 index 0000000000..307e38aceb --- /dev/null +++ b/src/gui/image/qpixmap_mac_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAP_MAC_P_H +#define QPIXMAP_MAC_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 +#include +#include + +QT_BEGIN_NAMESPACE + +class QMacPixmapData : public QPixmapData +{ +public: + QMacPixmapData(PixelType type); + ~QMacPixmapData(); + + QPixmapData *createCompatiblePixmapData() const; + + void resize(int width, int height); + void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + void copy(const QPixmapData *data, const QRect &rect); + bool scroll(int dx, int dy, const QRect &rect); + + int metric(QPaintDevice::PaintDeviceMetric metric) const; + void fill(const QColor &color); + QBitmap mask() const; + void setMask(const QBitmap &mask); + bool hasAlphaChannel() const; +// QPixmap transformed(const QTransform &matrix, +// Qt::TransformationMode mode) const; + void setAlphaChannel(const QPixmap &alphaChannel); + QPixmap alphaChannel() const; + QImage toImage() const; + QPaintEngine* paintEngine() const; + +private: + + uint has_alpha : 1, has_mask : 1, uninit : 1; + + void macSetHasAlpha(bool b); + void macGetAlphaChannel(QMacPixmapData *, bool asMask) const; + void macSetAlphaChannel(const QMacPixmapData *, bool asMask); + void macCreateCGImageRef(); + void macCreatePixels(); + void macReleaseCGImageRef(); + /* + pixels stores the pixmap data. pixelsToFree is either 0 or some memory + block that was bound to a CGImageRef and released, and for which the + release callback has been called. There are two uses to pixelsToFree: + + 1. If pixels == pixelsToFree, then we know that the CGImageRef is done\ + with the data and we can modify pixels without breaking CGImageRef's + mutability invariant. + + 2. If pixels != pixelsToFree and pixelsToFree != 0, then we can reuse + pixelsToFree later on instead of malloc'ing memory. + */ + quint32 *pixels; + uint pixelsSize; + quint32 *pixelsToFree; + uint bytesPerRow; + QRectF cg_mask_rect; + CGImageRef cg_data, cg_dataBeingReleased, cg_mask; + static QSet validDataPointers; + + QPaintEngine *pengine; + + friend class QPixmap; + friend class QRasterBuffer; + friend class QRasterPaintEngine; + friend class QCoreGraphicsPaintEngine; + friend CGImageRef qt_mac_create_imagemask(const QPixmap&, const QRectF&); + friend quint32 *qt_mac_pixmap_get_base(const QPixmap*); + friend int qt_mac_pixmap_get_bytes_per_line(const QPixmap*); + friend void qt_mac_cgimage_data_free(void *, const void*, size_t); + friend IconRef qt_mac_create_iconref(const QPixmap&); + friend CGContextRef qt_mac_cg_context(const QPaintDevice*); + friend QColor qcolorForThemeTextColor(ThemeTextColor themeColor); +}; + +QT_END_NAMESPACE + +#endif // QPIXMAP_MAC_P_H diff --git a/src/gui/image/qpixmap_qpa.cpp b/src/gui/image/qpixmap_qpa.cpp new file mode 100644 index 0000000000..61be2169d0 --- /dev/null +++ b/src/gui/image/qpixmap_qpa.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +QPixmap QPixmap::grabWindow(WId window, int x, int y, int w, int h) +{ + return QApplicationPrivate::platformIntegration()->grabWindow(window, x, y, w, h); +} diff --git a/src/gui/image/qpixmap_qws.cpp b/src/gui/image/qpixmap_qws.cpp new file mode 100644 index 0000000000..3c1907089d --- /dev/null +++ b/src/gui/image/qpixmap_qws.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +QPixmap QPixmap::grabWindow(WId window, int x, int y, int w, int h) +{ + QWidget *widget = QWidget::find(window); + if (!widget) + return QPixmap(); + + QRect grabRect = widget->frameGeometry(); + if (!widget->isWindow()) + grabRect.translate(widget->parentWidget()->mapToGlobal(QPoint())); + if (w < 0) + w = widget->width() - x; + if (h < 0) + h = widget->height() - y; + grabRect &= QRect(x, y, w, h).translated(widget->mapToGlobal(QPoint())); + + QScreen *screen = qt_screen; + QDesktopWidget *desktop = QApplication::desktop(); + if (!desktop) + return QPixmap(); + if (desktop->numScreens() > 1) { + const int screenNo = desktop->screenNumber(widget); + if (screenNo != -1) + screen = qt_screen->subScreens().at(screenNo); + grabRect = grabRect.translated(-screen->region().boundingRect().topLeft()); + } + + if (screen->pixelFormat() == QImage::Format_Invalid) { + qWarning("QPixmap::grabWindow(): Unable to copy pixels from framebuffer"); + return QPixmap(); + } + + if (screen->isTransformed()) { + const QSize screenSize(screen->width(), screen->height()); + grabRect = screen->mapToDevice(grabRect, screenSize); + } + + QWSDisplay::grab(false); + QPixmap pixmap; + QImage img(screen->base(), + screen->deviceWidth(), screen->deviceHeight(), + screen->linestep(), screen->pixelFormat()); + img = img.copy(grabRect); + QWSDisplay::ungrab(); + + if (screen->isTransformed()) { + QMatrix matrix; + switch (screen->transformOrientation()) { + case 1: matrix.rotate(90); break; + case 2: matrix.rotate(180); break; + case 3: matrix.rotate(270); break; + default: break; + } + img = img.transformed(matrix); + } + + if (screen->pixelType() == QScreen::BGRPixel) + img = img.rgbSwapped(); + + return QPixmap::fromImage(img); +} + +QRgb* QPixmap::clut() const +{ + if (data && data->classId() == QPixmapData::RasterClass) { + const QRasterPixmapData *d = static_cast(data.data()); + return d->image.colorTable().data(); + } + + return 0; +} + +int QPixmap::numCols() const +{ + return colorCount(); +} + +int QPixmap::colorCount() const +{ + if (data && data->classId() == QPixmapData::RasterClass) { + const QRasterPixmapData *d = static_cast(data.data()); + return d->image.colorCount(); + } + + return 0; +} + +const uchar* QPixmap::qwsBits() const +{ + if (data && data->classId() == QPixmapData::RasterClass) { + const QRasterPixmapData *d = static_cast(data.data()); + return d->image.bits(); + } + + return 0; +} + +int QPixmap::qwsBytesPerLine() const +{ + if (data && data->classId() == QPixmapData::RasterClass) { + const QRasterPixmapData *d = static_cast(data.data()); + return d->image.bytesPerLine(); + } + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_raster.cpp b/src/gui/image/qpixmap_raster.cpp new file mode 100644 index 0000000000..0a4d921795 --- /dev/null +++ b/src/gui/image/qpixmap_raster.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmap.h" + +#include + +#include "qpixmap_raster_p.h" +#include "qnativeimage_p.h" +#include "qimage_p.h" +#include "qpaintengine.h" + +#include "qbitmap.h" +#include "qimage.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const uchar qt_pixmap_bit_mask[] = { 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 }; + +QPixmap qt_toRasterPixmap(const QImage &image) +{ + QPixmapData *data = + new QRasterPixmapData(image.depth() == 1 + ? QPixmapData::BitmapType + : QPixmapData::PixmapType); + + data->fromImage(image, Qt::AutoColor); + + return QPixmap(data); +} + +QPixmap qt_toRasterPixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return QPixmap(); + + if (QPixmap(pixmap).data_ptr()->classId() == QPixmapData::RasterClass) + return pixmap; + + return qt_toRasterPixmap(pixmap.toImage()); +} + +QRasterPixmapData::QRasterPixmapData(PixelType type) + : QPixmapData(type, RasterClass) +{ +} + +QRasterPixmapData::~QRasterPixmapData() +{ +} + +QPixmapData *QRasterPixmapData::createCompatiblePixmapData() const +{ + return new QRasterPixmapData(pixelType()); +} + +void QRasterPixmapData::resize(int width, int height) +{ + QImage::Format format; +#ifdef Q_WS_QWS + if (pixelType() == BitmapType) { + format = QImage::Format_Mono; + } else { + format = QScreen::instance()->pixelFormat(); + if (format == QImage::Format_Invalid) + format = QImage::Format_ARGB32_Premultiplied; + else if (format == QImage::Format_Indexed8) // currently not supported + format = QImage::Format_RGB444; + } +#else + if (pixelType() == BitmapType) + format = QImage::Format_MonoLSB; + else + format = QNativeImage::systemFormat(); +#endif + + image = QImage(width, height, format); + w = width; + h = height; + d = image.depth(); + is_null = (w <= 0 || h <= 0); + + if (pixelType() == BitmapType && !image.isNull()) { + image.setColorCount(2); + image.setColor(0, QColor(Qt::color0).rgba()); + image.setColor(1, QColor(Qt::color1).rgba()); + } + + setSerialNumber(image.serialNumber()); +} + +bool QRasterPixmapData::fromData(const uchar *buffer, uint len, const char *format, + Qt::ImageConversionFlags flags) +{ + QByteArray a = QByteArray::fromRawData(reinterpret_cast(buffer), len); + QBuffer b(&a); + b.open(QIODevice::ReadOnly); + QImage image = QImageReader(&b, format).read(); + if (image.isNull()) + return false; + + createPixmapForImage(image, flags, /* inplace = */true); + return !isNull(); +} + +void QRasterPixmapData::fromImage(const QImage &sourceImage, + Qt::ImageConversionFlags flags) +{ + Q_UNUSED(flags); + QImage image = sourceImage; + createPixmapForImage(image, flags, /* inplace = */false); +} + +void QRasterPixmapData::fromImageReader(QImageReader *imageReader, + Qt::ImageConversionFlags flags) +{ + Q_UNUSED(flags); + QImage image = imageReader->read(); + if (image.isNull()) + return; + + createPixmapForImage(image, flags, /* inplace = */true); +} + +// from qwindowsurface.cpp +extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); + +void QRasterPixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + fromImage(data->toImage(rect).copy(), Qt::NoOpaqueDetection); +} + +bool QRasterPixmapData::scroll(int dx, int dy, const QRect &rect) +{ + if (!image.isNull()) + qt_scrollRectInImage(image, rect, QPoint(dx, dy)); + return true; +} + +void QRasterPixmapData::fill(const QColor &color) +{ + uint pixel; + + if (image.depth() == 1) { + int gray = qGray(color.rgba()); + // Pick the best approximate color in the image's colortable. + if (qAbs(qGray(image.color(0)) - gray) < qAbs(qGray(image.color(1)) - gray)) + pixel = 0; + else + pixel = 1; + } else if (image.depth() >= 15) { + int alpha = color.alpha(); + if (alpha != 255) { + if (!image.hasAlphaChannel()) { + QImage::Format toFormat; +#if !(defined(QT_HAVE_NEON) || defined(QT_ALWAYS_HAVE_SSE2)) + if (image.format() == QImage::Format_RGB16) + toFormat = QImage::Format_ARGB8565_Premultiplied; + else if (image.format() == QImage::Format_RGB666) + toFormat = QImage::Format_ARGB6666_Premultiplied; + else if (image.format() == QImage::Format_RGB555) + toFormat = QImage::Format_ARGB8555_Premultiplied; + else if (image.format() == QImage::Format_RGB444) + toFormat = QImage::Format_ARGB4444_Premultiplied; + else +#endif + toFormat = QImage::Format_ARGB32_Premultiplied; + + if (!image.isNull() && qt_depthForFormat(image.format()) == qt_depthForFormat(toFormat)) { + image.detach(); + image.d->format = toFormat; + } else { + image = QImage(image.width(), image.height(), toFormat); + } + } + + switch (image.format()) { + case QImage::Format_ARGB8565_Premultiplied: + pixel = qargb8565(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB8555_Premultiplied: + pixel = qargb8555(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB6666_Premultiplied: + pixel = qargb6666(color.rgba()).rawValue(); + break; + case QImage::Format_ARGB4444_Premultiplied: + pixel = qargb4444(color.rgba()).rawValue(); + break; + default: + pixel = PREMUL(color.rgba()); + break; + } + } else { + switch (image.format()) { + case QImage::Format_RGB16: + pixel = qt_colorConvert(color.rgba(), 0); + break; + case QImage::Format_RGB444: + pixel = qrgb444(color.rgba()).rawValue(); + break; + case QImage::Format_RGB555: + pixel = qrgb555(color.rgba()).rawValue(); + break; + case QImage::Format_RGB666: + pixel = qrgb666(color.rgba()).rawValue(); + break; + case QImage::Format_RGB888: + pixel = qrgb888(color.rgba()).rawValue(); + break; + default: + pixel = color.rgba(); + break; + } + } + } else { + pixel = 0; + // ### what about 8 bits + } + + image.fill(pixel); +} + +void QRasterPixmapData::setMask(const QBitmap &mask) +{ + if (mask.size().isEmpty()) { + if (image.depth() != 1) { // hw: ???? + image = image.convertToFormat(QImage::Format_RGB32); + } + } else { + const int w = image.width(); + const int h = image.height(); + + switch (image.depth()) { + case 1: { + const QImage imageMask = mask.toImage().convertToFormat(image.format()); + for (int y = 0; y < h; ++y) { + const uchar *mscan = imageMask.scanLine(y); + uchar *tscan = image.scanLine(y); + int bytesPerLine = image.bytesPerLine(); + for (int i = 0; i < bytesPerLine; ++i) + tscan[i] &= mscan[i]; + } + break; + } + default: { + const QImage imageMask = mask.toImage().convertToFormat(QImage::Format_MonoLSB); + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + for (int y = 0; y < h; ++y) { + const uchar *mscan = imageMask.scanLine(y); + QRgb *tscan = (QRgb *)image.scanLine(y); + for (int x = 0; x < w; ++x) { + if (!(mscan[x>>3] & qt_pixmap_bit_mask[x&7])) + tscan[x] = 0; + } + } + break; + } + } + } +} + +bool QRasterPixmapData::hasAlphaChannel() const +{ + return image.hasAlphaChannel(); +} + +QImage QRasterPixmapData::toImage() const +{ + if (!image.isNull()) { + QImageData *data = const_cast(image).data_ptr(); + if (data->paintEngine && data->paintEngine->isActive() + && data->paintEngine->paintDevice() == &image) + { + return image.copy(); + } + } + + return image; +} + +QImage QRasterPixmapData::toImage(const QRect &rect) const +{ + if (rect.isNull()) + return image; + + QRect clipped = rect.intersected(QRect(0, 0, w, h)); + if (d % 8 == 0) + return QImage(image.scanLine(clipped.y()) + clipped.x() * (d / 8), + clipped.width(), clipped.height(), + image.bytesPerLine(), image.format()); + else + return image.copy(clipped); +} + +void QRasterPixmapData::setAlphaChannel(const QPixmap &alphaChannel) +{ + image.setAlphaChannel(alphaChannel.toImage()); +} + +QPaintEngine* QRasterPixmapData::paintEngine() const +{ + return image.paintEngine(); +} + +int QRasterPixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + QImageData *d = image.d; + if (!d) + return 0; + + // override the image dpi with the screen dpi when rendering to a pixmap + switch (metric) { + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmWidthMM: + return qRound(d->width * 25.4 / qt_defaultDpiX()); + case QPaintDevice::PdmHeightMM: + return qRound(d->height * 25.4 / qt_defaultDpiY()); + case QPaintDevice::PdmNumColors: + return d->colortable.size(); + case QPaintDevice::PdmDepth: + return this->d; + case QPaintDevice::PdmDpiX: // fall-through + case QPaintDevice::PdmPhysicalDpiX: + return qt_defaultDpiX(); + case QPaintDevice::PdmDpiY: // fall-through + case QPaintDevice::PdmPhysicalDpiY: + return qt_defaultDpiY(); + default: + qWarning("QRasterPixmapData::metric(): Unhandled metric type %d", metric); + break; + } + + return 0; +} + +void QRasterPixmapData::createPixmapForImage(QImage &sourceImage, Qt::ImageConversionFlags flags, bool inPlace) +{ + QImage::Format format; + if (flags & Qt::NoFormatConversion) + format = sourceImage.format(); + else +#ifdef Q_WS_QWS + if (pixelType() == BitmapType) { + format = QImage::Format_Mono; + } else { + format = QScreen::instance()->pixelFormat(); + if (format == QImage::Format_Invalid) + format = QImage::Format_ARGB32_Premultiplied; + else if (format == QImage::Format_Indexed8) // currently not supported + format = QImage::Format_RGB444; + } + + if (sourceImage.hasAlphaChannel() + && ((flags & Qt::NoOpaqueDetection) + || const_cast(sourceImage).data_ptr()->checkForAlphaPixels())) { + switch (format) { + case QImage::Format_RGB16: + format = QImage::Format_ARGB8565_Premultiplied; + break; + case QImage::Format_RGB666: + format = QImage::Format_ARGB6666_Premultiplied; + break; + case QImage::Format_RGB555: + format = QImage::Format_ARGB8555_Premultiplied; + break; + case QImage::Format_RGB444: + format = QImage::Format_ARGB4444_Premultiplied; + break; + default: + format = QImage::Format_ARGB32_Premultiplied; + break; + } + } else if (format == QImage::Format_Invalid) { + format = QImage::Format_ARGB32_Premultiplied; + } +#else + if (pixelType() == BitmapType) { + format = QImage::Format_MonoLSB; + } else { + if (sourceImage.depth() == 1) { + format = sourceImage.hasAlphaChannel() + ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_RGB32; + } else { + QImage::Format opaqueFormat = QNativeImage::systemFormat(); + QImage::Format alphaFormat = QImage::Format_ARGB32_Premultiplied; + +#if !defined(QT_HAVE_NEON) && !defined(QT_ALWAYS_HAVE_SSE2) + switch (opaqueFormat) { + case QImage::Format_RGB16: + alphaFormat = QImage::Format_ARGB8565_Premultiplied; + break; + default: // We don't care about the others... + break; + } +#endif + + if (!sourceImage.hasAlphaChannel()) { + format = opaqueFormat; + } else if ((flags & Qt::NoOpaqueDetection) == 0 + && !const_cast(sourceImage).data_ptr()->checkForAlphaPixels()) + { + // image has alpha format but is really opaque, so try to do a + // more efficient conversion + if (sourceImage.format() == QImage::Format_ARGB32 + || sourceImage.format() == QImage::Format_ARGB32_Premultiplied) + { + if (!inPlace) + sourceImage.detach(); + sourceImage.d->format = QImage::Format_RGB32; + } + format = opaqueFormat; + } else { + format = alphaFormat; + } + } + } +#endif + + if (inPlace && sourceImage.d->convertInPlace(format, flags)) { + image = sourceImage; + } else { + image = sourceImage.convertToFormat(format); + } + + if (image.d) { + w = image.d->width; + h = image.d->height; + d = image.d->depth; + } else { + w = h = d = 0; + } + is_null = (w <= 0 || h <= 0); + + setSerialNumber(image.serialNumber()); +} + +QImage* QRasterPixmapData::buffer() +{ + return ℑ +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_raster_p.h b/src/gui/image/qpixmap_raster_p.h new file mode 100644 index 0000000000..88bdb32569 --- /dev/null +++ b/src/gui/image/qpixmap_raster_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPDATA_RASTER_P_H +#define QPIXMAPDATA_RASTER_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 +#include + +#ifdef Q_WS_WIN +# include "qt_windows.h" +#endif + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QRasterPixmapData : public QPixmapData +{ +public: + QRasterPixmapData(PixelType type); + ~QRasterPixmapData(); + + QPixmapData *createCompatiblePixmapData() const; + + void resize(int width, int height); + void fromFile(const QString &filename, Qt::ImageConversionFlags flags); + bool fromData(const uchar *buffer, uint len, const char *format, Qt::ImageConversionFlags flags); + void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + void fromImageReader(QImageReader *imageReader, Qt::ImageConversionFlags flags); + + void copy(const QPixmapData *data, const QRect &rect); + bool scroll(int dx, int dy, const QRect &rect); + void fill(const QColor &color); + void setMask(const QBitmap &mask); + bool hasAlphaChannel() const; + void setAlphaChannel(const QPixmap &alphaChannel); + QImage toImage() const; + QImage toImage(const QRect &rect) const; + QPaintEngine* paintEngine() const; + QImage* buffer(); + +protected: + int metric(QPaintDevice::PaintDeviceMetric metric) const; + void createPixmapForImage(QImage &sourceImage, Qt::ImageConversionFlags flags, bool inPlace); + void setImage(const QImage &image); + QImage image; + +private: + friend class QPixmap; + friend class QBitmap; + friend class QPixmapCacheEntry; + friend class QRasterPaintEngine; +}; + +QT_END_NAMESPACE + +#endif // QPIXMAPDATA_RASTER_P_H + + diff --git a/src/gui/image/qpixmap_s60.cpp b/src/gui/image/qpixmap_s60.cpp new file mode 100644 index 0000000000..c8aa003ffa --- /dev/null +++ b/src/gui/image/qpixmap_s60.cpp @@ -0,0 +1,1040 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "qpixmap.h" +#include "qpixmap_raster_p.h" +#include +#include "qpixmap_s60_p.h" +#include "qnativeimage_p.h" +#include "qbitmap.h" +#include "qimage.h" +#include "qimage_p.h" + +#include + +QT_BEGIN_NAMESPACE + +const uchar qt_pixmap_bit_mask[] = { 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 }; + +static bool cleanup_function_registered = false; +static QS60PixmapData *firstPixmap = 0; + +// static +void QS60PixmapData::qt_symbian_register_pixmap(QS60PixmapData *pd) +{ + if (!cleanup_function_registered) { + qAddPostRoutine(qt_symbian_release_pixmaps); + cleanup_function_registered = true; + } + + pd->next = firstPixmap; + pd->prev = 0; + if (firstPixmap) + firstPixmap->prev = pd; + firstPixmap = pd; +} + +// static +void QS60PixmapData::qt_symbian_unregister_pixmap(QS60PixmapData *pd) +{ + if (pd->next) + pd->next->prev = pd->prev; + if (pd->prev) + pd->prev->next = pd->next; + else + firstPixmap = pd->next; +} + +// static +void QS60PixmapData::qt_symbian_release_pixmaps() +{ + // Scan all QS60PixmapData objects in the system and destroy them. + QS60PixmapData *pd = firstPixmap; + while (pd != 0) { + pd->release(); + pd = pd->next; + } +} + +/* + \class QSymbianFbsClient + \since 4.6 + \internal + + Symbian Font And Bitmap server client that is + used to lock the global bitmap heap. Only used in + S60 v3.1 and S60 v3.2. +*/ +_LIT(KFBSERVLargeBitmapAccessName,"FbsLargeBitmapAccess"); +class QSymbianFbsClient +{ +public: + + QSymbianFbsClient() : heapLocked(false) + { + heapLock.OpenGlobal(KFBSERVLargeBitmapAccessName); + } + + ~QSymbianFbsClient() + { + heapLock.Close(); + } + + bool lockHeap() + { + bool wasLocked = heapLocked; + + if (heapLock.Handle() && !heapLocked) { + heapLock.Wait(); + heapLocked = true; + } + + return wasLocked; + } + + bool unlockHeap() + { + bool wasLocked = heapLocked; + + if (heapLock.Handle() && heapLocked) { + heapLock.Signal(); + heapLocked = false; + } + + return wasLocked; + } + + +private: + + RMutex heapLock; + bool heapLocked; +}; + +Q_GLOBAL_STATIC(QSymbianFbsClient, qt_symbianFbsClient); + + + +// QSymbianFbsHeapLock + +QSymbianFbsHeapLock::QSymbianFbsHeapLock(LockAction a) +: action(a), wasLocked(false) +{ + QSysInfo::SymbianVersion symbianVersion = QSysInfo::symbianVersion(); + if (symbianVersion == QSysInfo::SV_9_2 || symbianVersion == QSysInfo::SV_9_3) + wasLocked = qt_symbianFbsClient()->unlockHeap(); +} + +QSymbianFbsHeapLock::~QSymbianFbsHeapLock() +{ + // Do nothing +} + +void QSymbianFbsHeapLock::relock() +{ + QSysInfo::SymbianVersion symbianVersion = QSysInfo::symbianVersion(); + if (wasLocked && (symbianVersion == QSysInfo::SV_9_2 || symbianVersion == QSysInfo::SV_9_3)) + qt_symbianFbsClient()->lockHeap(); +} + +/* + \class QSymbianBitmapDataAccess + \since 4.6 + \internal + + Data access class that is used to locks/unlocks pixel data + when drawing or modifying CFbsBitmap pixel data. +*/ +class QSymbianBitmapDataAccess +{ +public: + + static int heapRefCount; + QSysInfo::SymbianVersion symbianVersion; + + explicit QSymbianBitmapDataAccess() + { + symbianVersion = QSysInfo::symbianVersion(); + }; + + ~QSymbianBitmapDataAccess() {}; + + inline void beginDataAccess(CFbsBitmap *bitmap) + { + if (symbianVersion == QSysInfo::SV_9_2) { + if (heapRefCount == 0) + qt_symbianFbsClient()->lockHeap(); + } else { + bitmap->LockHeap(ETrue); + } + + heapRefCount++; + } + + inline void endDataAccess(CFbsBitmap *bitmap) + { + heapRefCount--; + + if (symbianVersion == QSysInfo::SV_9_2) { + if (heapRefCount == 0) + qt_symbianFbsClient()->unlockHeap(); + } else { + bitmap->UnlockHeap(ETrue); + } + } +}; + +int QSymbianBitmapDataAccess::heapRefCount = 0; + + +#define UPDATE_BUFFER() \ + { \ + beginDataAccess(); \ + endDataAccess(); \ +} + + +static CFbsBitmap* createSymbianCFbsBitmap(const TSize& size, TDisplayMode mode) +{ + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + CFbsBitmap* bitmap = 0; + QT_TRAP_THROWING(bitmap = new (ELeave) CFbsBitmap); + + if (bitmap->Create(size, mode) != KErrNone) { + delete bitmap; + bitmap = 0; + } + + lock.relock(); + + return bitmap; +} + +static CFbsBitmap* uncompress(CFbsBitmap* bitmap) +{ + if(bitmap->IsCompressedInRAM()) { + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + CFbsBitmap *uncompressed = 0; + QT_TRAP_THROWING(uncompressed = new (ELeave) CFbsBitmap); + + if (uncompressed->Create(bitmap->SizeInPixels(), bitmap->DisplayMode()) != KErrNone) { + delete bitmap; + bitmap = 0; + lock.relock(); + + return bitmap; + } + + lock.relock(); + + CFbsBitmapDevice* bitmapDevice = 0; + CFbsBitGc *bitmapGc = 0; + QT_TRAP_THROWING(bitmapDevice = CFbsBitmapDevice::NewL(uncompressed)); + QT_TRAP_THROWING(bitmapGc = CFbsBitGc::NewL()); + bitmapGc->Activate(bitmapDevice); + + bitmapGc->BitBlt(TPoint(), bitmap); + + delete bitmapGc; + delete bitmapDevice; + + return uncompressed; + } else { + return bitmap; + } +} + +QPixmap QPixmap::grabWindow(WId winId, int x, int y, int w, int h) +{ + CWsScreenDevice* screenDevice = S60->screenDevice(); + TSize screenSize = screenDevice->SizeInPixels(); + + TSize srcSize; + // Find out if this is one of our windows. + QSymbianControl *sControl; + sControl = winId->MopGetObject(sControl); + if (sControl && sControl->widget()->windowType() == Qt::Desktop) { + // Grabbing desktop widget + srcSize = screenSize; + } else { + TPoint relativePos = winId->PositionRelativeToScreen(); + x += relativePos.iX; + y += relativePos.iY; + srcSize = winId->Size(); + } + + TRect srcRect(TPoint(x, y), srcSize); + // Clip to the screen + srcRect.Intersection(TRect(screenSize)); + + if (w > 0 && h > 0) { + TRect subRect(TPoint(x, y), TSize(w, h)); + // Clip to the subRect + srcRect.Intersection(subRect); + } + + if (srcRect.IsEmpty()) + return QPixmap(); + + CFbsBitmap* temporary = createSymbianCFbsBitmap(srcRect.Size(), screenDevice->DisplayMode()); + + QPixmap pix; + + if (temporary && screenDevice->CopyScreenToBitmap(temporary, srcRect) == KErrNone) { + pix = QPixmap::fromSymbianCFbsBitmap(temporary); + } + + delete temporary; + return pix; +} + +/*! + \fn CFbsBitmap *QPixmap::toSymbianCFbsBitmap() const + \since 4.6 + + Creates a \c CFbsBitmap that is equivalent to the QPixmap. Internally this + function will try to duplicate the handle instead of copying the data, + however in scenarios where this is not possible the data will be copied. + If the creation fails or the pixmap is null, then this function returns 0. + + It is the caller's responsibility to release the \c CFbsBitmap data + after use either by deleting the bitmap or calling \c Reset(). + + \warning On S60 3.1 and S60 3.2, semi-transparent pixmaps are always copied + and not duplicated. + \warning This function is only available on Symbian OS. + + \sa fromSymbianCFbsBitmap() +*/ +CFbsBitmap *QPixmap::toSymbianCFbsBitmap() const +{ + QPixmapData *data = pixmapData(); + if (!data || data->isNull()) + return 0; + + return reinterpret_cast(data->toNativeType(QPixmapData::FbsBitmap)); +} + +/*! + \fn QPixmap QPixmap::fromSymbianCFbsBitmap(CFbsBitmap *bitmap) + \since 4.6 + + Creates a QPixmap from a \c CFbsBitmap \a bitmap. Internally this function + will try to duplicate the bitmap handle instead of copying the data, however + in scenarios where this is not possible the data will be copied. + To be sure that QPixmap does not modify your original instance, you should + make a copy of your \c CFbsBitmap before calling this function. + If the CFbsBitmap is not valid this function will return a null QPixmap. + For performance reasons it is recommended to use a \a bitmap with a display + mode of EColor16MAP or EColor16MU whenever possible. + + \warning This function is only available on Symbian OS. + + \sa toSymbianCFbsBitmap(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ +QPixmap QPixmap::fromSymbianCFbsBitmap(CFbsBitmap *bitmap) +{ + if (!bitmap) + return QPixmap(); + + QScopedPointer data(QPixmapData::create(0,0, QPixmapData::PixmapType)); + data->fromNativeType(reinterpret_cast(bitmap), QPixmapData::FbsBitmap); + QPixmap pixmap(data.take()); + return pixmap; +} + +QS60PixmapData::QS60PixmapData(PixelType type) : QRasterPixmapData(type), + symbianBitmapDataAccess(new QSymbianBitmapDataAccess), + cfbsBitmap(0), + pengine(0), + bytes(0), + formatLocked(false), + next(0), + prev(0) +{ + qt_symbian_register_pixmap(this); +} + +QS60PixmapData::~QS60PixmapData() +{ + release(); + delete symbianBitmapDataAccess; + qt_symbian_unregister_pixmap(this); +} + +void QS60PixmapData::resize(int width, int height) +{ + if (width <= 0 || height <= 0) { + w = width; + h = height; + is_null = true; + + release(); + return; + } else if (!cfbsBitmap) { + TDisplayMode mode; + if (pixelType() == BitmapType) + mode = EGray2; + else + mode = EColor16MU; + + CFbsBitmap* bitmap = createSymbianCFbsBitmap(TSize(width, height), mode); + fromSymbianBitmap(bitmap); + } else { + + TSize newSize(width, height); + + if(cfbsBitmap->SizeInPixels() != newSize) { + cfbsBitmap->Resize(TSize(width, height)); + if(pengine) { + delete pengine; + pengine = 0; + } + } + + UPDATE_BUFFER(); + } +} + +void QS60PixmapData::release() +{ + if (cfbsBitmap) { + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + delete cfbsBitmap; + lock.relock(); + } + + delete pengine; + image = QImage(); + cfbsBitmap = 0; + pengine = 0; + bytes = 0; +} + +/*! + * Takes ownership of bitmap. Used by window surface + */ +void QS60PixmapData::fromSymbianBitmap(CFbsBitmap* bitmap, bool lockFormat) +{ + Q_ASSERT(bitmap); + + release(); + + cfbsBitmap = bitmap; + formatLocked = lockFormat; + + setSerialNumber(cfbsBitmap->Handle()); + + UPDATE_BUFFER(); + + // Create default palette if needed + if (cfbsBitmap->DisplayMode() == EGray2) { + image.setColorCount(2); + image.setColor(0, QColor(Qt::color0).rgba()); + image.setColor(1, QColor(Qt::color1).rgba()); + + //Symbian thinks set pixels are white/transparent, Qt thinks they are foreground/solid + //So invert mono bitmaps so that masks work correctly. + image.invertPixels(); + } else if (cfbsBitmap->DisplayMode() == EGray256) { + for (int i=0; i < 256; ++i) + image.setColor(i, qRgb(i, i, i)); + } else if (cfbsBitmap->DisplayMode() == EColor256) { + const TColor256Util *palette = TColor256Util::Default(); + for (int i=0; i < 256; ++i) + image.setColor(i, (QRgb)(palette->Color256(i).Value())); + } +} + +QImage QS60PixmapData::toImage(const QRect &r) const +{ + QS60PixmapData *that = const_cast(this); + that->beginDataAccess(); + QImage copy = that->image.copy(r); + that->endDataAccess(); + + return copy; +} + +void QS60PixmapData::fromImage(const QImage &img, Qt::ImageConversionFlags flags) +{ + release(); + + QImage sourceImage; + + if (pixelType() == BitmapType) { + sourceImage = img.convertToFormat(QImage::Format_MonoLSB); + } else { + if (img.depth() == 1) { + sourceImage = img.hasAlphaChannel() + ? img.convertToFormat(QImage::Format_ARGB32_Premultiplied) + : img.convertToFormat(QImage::Format_RGB32); + } else { + + QImage::Format opaqueFormat = QNativeImage::systemFormat(); + QImage::Format alphaFormat = QImage::Format_ARGB32_Premultiplied; + + if (!img.hasAlphaChannel() + || ((flags & Qt::NoOpaqueDetection) == 0 + && !const_cast(img).data_ptr()->checkForAlphaPixels())) { + sourceImage = img.convertToFormat(opaqueFormat); + } else { + sourceImage = img.convertToFormat(alphaFormat); + } + } + } + + + QImage::Format destFormat = sourceImage.format(); + TDisplayMode mode; + switch (destFormat) { + case QImage::Format_MonoLSB: + mode = EGray2; + break; + case QImage::Format_RGB32: + mode = EColor16MU; + break; + case QImage::Format_ARGB32_Premultiplied: + if (S60->supportsPremultipliedAlpha) { + mode = Q_SYMBIAN_ECOLOR16MAP; + break; + } else { + destFormat = QImage::Format_ARGB32; + } + // Fall through intended + case QImage::Format_ARGB32: + mode = EColor16MA; + break; + case QImage::Format_Invalid: + return; + default: + qWarning("Image format not supported: %d", image.format()); + return; + } + + cfbsBitmap = createSymbianCFbsBitmap(TSize(sourceImage.width(), sourceImage.height()), mode); + if (!cfbsBitmap) { + qWarning("Could not create CFbsBitmap"); + release(); + return; + } + + setSerialNumber(cfbsBitmap->Handle()); + + const uchar *sptr = const_cast(sourceImage).bits(); + symbianBitmapDataAccess->beginDataAccess(cfbsBitmap); + uchar *dptr = (uchar*)cfbsBitmap->DataAddress(); + Mem::Copy(dptr, sptr, sourceImage.byteCount()); + symbianBitmapDataAccess->endDataAccess(cfbsBitmap); + + UPDATE_BUFFER(); + + if (destFormat == QImage::Format_MonoLSB) { + image.setColorCount(2); + image.setColor(0, QColor(Qt::color0).rgba()); + image.setColor(1, QColor(Qt::color1).rgba()); + } else { + image.setColorTable(sourceImage.colorTable()); + } +} + +void QS60PixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + const QS60PixmapData *s60Data = static_cast(data); + fromImage(s60Data->toImage(rect), Qt::AutoColor | Qt::OrderedAlphaDither); +} + +bool QS60PixmapData::scroll(int dx, int dy, const QRect &rect) +{ + beginDataAccess(); + bool res = QRasterPixmapData::scroll(dx, dy, rect); + endDataAccess(); + return res; +} + +int QS60PixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + if (!cfbsBitmap) + return 0; + + switch (metric) { + case QPaintDevice::PdmWidth: + return cfbsBitmap->SizeInPixels().iWidth; + case QPaintDevice::PdmHeight: + return cfbsBitmap->SizeInPixels().iHeight; + case QPaintDevice::PdmWidthMM: + return qRound(cfbsBitmap->SizeInPixels().iWidth * 25.4 / qt_defaultDpiX()); + case QPaintDevice::PdmHeightMM: + return qRound(cfbsBitmap->SizeInPixels().iHeight * 25.4 / qt_defaultDpiY()); + case QPaintDevice::PdmNumColors: + return TDisplayModeUtils::NumDisplayModeColors(cfbsBitmap->DisplayMode()); + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmPhysicalDpiX: + return qt_defaultDpiX(); + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiY: + return qt_defaultDpiY(); + case QPaintDevice::PdmDepth: + return TDisplayModeUtils::NumDisplayModeBitsPerPixel(cfbsBitmap->DisplayMode()); + default: + qWarning("QPixmap::metric: Invalid metric command"); + } + return 0; + +} + +void QS60PixmapData::fill(const QColor &color) +{ + if (color.alpha() != 255) { + QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied); + im.fill(PREMUL(color.rgba())); + release(); + fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither); + } else { + beginDataAccess(); + QRasterPixmapData::fill(color); + endDataAccess(); + } +} + +void QS60PixmapData::setMask(const QBitmap &mask) +{ + if (mask.size().isEmpty()) { + if (image.depth() != 1) { + QImage newImage = image.convertToFormat(QImage::Format_RGB32); + release(); + fromImage(newImage, Qt::AutoColor | Qt::OrderedAlphaDither); + } + } else if (image.depth() == 1) { + beginDataAccess(); + QRasterPixmapData::setMask(mask); + endDataAccess(); + } else { + const int w = image.width(); + const int h = image.height(); + + const QImage imageMask = mask.toImage().convertToFormat(QImage::Format_MonoLSB); + QImage newImage = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + for (int y = 0; y < h; ++y) { + const uchar *mscan = imageMask.scanLine(y); + QRgb *tscan = (QRgb *)newImage.scanLine(y); + for (int x = 0; x < w; ++x) { + if (!(mscan[x>>3] & qt_pixmap_bit_mask[x&7])) + tscan[x] = 0; + } + } + release(); + fromImage(newImage, Qt::AutoColor | Qt::OrderedAlphaDither); + } +} + +void QS60PixmapData::setAlphaChannel(const QPixmap &alphaChannel) +{ + QImage img(toImage()); + img.setAlphaChannel(alphaChannel.toImage()); + release(); + fromImage(img, Qt::OrderedDither | Qt::OrderedAlphaDither); +} + +QImage QS60PixmapData::toImage() const +{ + return toImage(QRect()); +} + +QPaintEngine* QS60PixmapData::paintEngine() const +{ + if (!pengine) { + QS60PixmapData *that = const_cast(this); + that->pengine = new QS60PaintEngine(&that->image, that); + } + return pengine; +} + +void QS60PixmapData::beginDataAccess() +{ + if(!cfbsBitmap) + return; + + symbianBitmapDataAccess->beginDataAccess(cfbsBitmap); + + uchar* newBytes = (uchar*)cfbsBitmap->DataAddress(); + + TSize size = cfbsBitmap->SizeInPixels(); + + if (newBytes == bytes && image.width() == size.iWidth && image.height() == size.iHeight) + return; + + bytes = newBytes; + TDisplayMode mode = cfbsBitmap->DisplayMode(); + QImage::Format format = qt_TDisplayMode2Format(mode); + // On S60 3.1, premultiplied alpha pixels are stored in a bitmap with 16MA type. + // S60 window surface needs backing store pixmap for transparent window in ARGB32 format. + // In that case formatLocked is true. + if (!formatLocked && format == QImage::Format_ARGB32) + format = QImage::Format_ARGB32_Premultiplied; // pixel data is actually in premultiplied format + + QVector savedColorTable; + if (!image.isNull()) + savedColorTable = image.colorTable(); + + image = QImage(bytes, size.iWidth, size.iHeight, format); + + // Restore the palette or create a default + if (!savedColorTable.isEmpty()) { + image.setColorTable(savedColorTable); + } + + w = size.iWidth; + h = size.iHeight; + d = image.depth(); + is_null = (w <= 0 || h <= 0); + + if (pengine) { + QS60PaintEngine *engine = static_cast(pengine); + engine->prepare(&image); + } +} + +void QS60PixmapData::endDataAccess(bool readOnly) const +{ + Q_UNUSED(readOnly); + + if(!cfbsBitmap) + return; + + symbianBitmapDataAccess->endDataAccess(cfbsBitmap); +} + +/*! + \since 4.6 + + Returns a QPixmap that wraps given \a sgImage graphics resource. + The data should be valid even when original RSgImage handle has been + closed. + + \warning This function is only available on Symbian OS. + + \sa toSymbianRSgImage(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ + +QPixmap QPixmap::fromSymbianRSgImage(RSgImage *sgImage) +{ + // It is expected that RSgImage will + // CURRENTLY be used in conjuction with + // OpenVG graphics system + // + // Surely things might change in future + + if (!sgImage) + return QPixmap(); + + QScopedPointer data(QPixmapData::create(0,0, QPixmapData::PixmapType)); + data->fromNativeType(reinterpret_cast(sgImage), QPixmapData::SgImage); + QPixmap pixmap(data.take()); + return pixmap; +} + +/*! +\since 4.6 + +Returns a \c RSgImage that is equivalent to the QPixmap by copying the data. + +It is the caller's responsibility to close/delete the \c RSgImage after use. + +\warning This function is only available on Symbian OS. + +\sa fromSymbianRSgImage() +*/ + +RSgImage *QPixmap::toSymbianRSgImage() const +{ + // It is expected that RSgImage will + // CURRENTLY be used in conjuction with + // OpenVG graphics system + // + // Surely things might change in future + + if (isNull()) + return 0; + + RSgImage *sgImage = reinterpret_cast(pixmapData()->toNativeType(QPixmapData::SgImage)); + + return sgImage; +} + +void* QS60PixmapData::toNativeType(NativeType type) +{ + if (type == QPixmapData::SgImage) { + return 0; + } else if (type == QPixmapData::FbsBitmap) { + + if (isNull() || !cfbsBitmap) + return 0; + + bool convertToArgb32 = false; + bool needsCopy = false; + + if (!(S60->supportsPremultipliedAlpha)) { + // Convert argb32_premultiplied to argb32 since Symbian 9.2 does + // not support premultipied format. + + if (image.format() == QImage::Format_ARGB32_Premultiplied) { + needsCopy = true; + convertToArgb32 = true; + } + } + + CFbsBitmap *bitmap = 0; + + TDisplayMode displayMode = cfbsBitmap->DisplayMode(); + + if(displayMode == EGray2) { + //Symbian thinks set pixels are white/transparent, Qt thinks they are foreground/solid + //So invert mono bitmaps so that masks work correctly. + beginDataAccess(); + image.invertPixels(); + endDataAccess(); + needsCopy = true; + } + + if (needsCopy) { + QImage source; + + if (convertToArgb32) { + beginDataAccess(); + source = image.convertToFormat(QImage::Format_ARGB32); + endDataAccess(); + displayMode = EColor16MA; + } else { + source = image; + } + + CFbsBitmap *newBitmap = createSymbianCFbsBitmap(TSize(source.width(), source.height()), displayMode); + const uchar *sptr = source.bits(); + symbianBitmapDataAccess->beginDataAccess(newBitmap); + + uchar *dptr = (uchar*)newBitmap->DataAddress(); + Mem::Copy(dptr, sptr, source.byteCount()); + + symbianBitmapDataAccess->endDataAccess(newBitmap); + + bitmap = newBitmap; + } else { + + QT_TRAP_THROWING(bitmap = new (ELeave) CFbsBitmap); + + TInt err = bitmap->Duplicate(cfbsBitmap->Handle()); + if (err != KErrNone) { + qWarning("Could not duplicate CFbsBitmap"); + delete bitmap; + bitmap = 0; + } + } + + if(displayMode == EGray2) { + // restore pixels + beginDataAccess(); + image.invertPixels(); + endDataAccess(); + } + + return reinterpret_cast(bitmap); + + } + + return 0; +} + +void QS60PixmapData::fromNativeType(void* pixmap, NativeType nativeType) +{ + if (nativeType == QPixmapData::SgImage) { + return; + } else if (nativeType == QPixmapData::FbsBitmap && pixmap) { + + CFbsBitmap *bitmap = reinterpret_cast(pixmap); + + bool deleteSourceBitmap = false; + bool needsCopy = false; + +#ifdef Q_SYMBIAN_HAS_EXTENDED_BITMAP_TYPE + + // Rasterize extended bitmaps + + TUid extendedBitmapType = bitmap->ExtendedBitmapType(); + if (extendedBitmapType != KNullUid) { + CFbsBitmap *rasterBitmap = createSymbianCFbsBitmap(bitmap->SizeInPixels(), EColor16MA); + + CFbsBitmapDevice *rasterBitmapDev = 0; + QT_TRAP_THROWING(rasterBitmapDev = CFbsBitmapDevice::NewL(rasterBitmap)); + + CFbsBitGc *rasterBitmapGc = 0; + TInt err = rasterBitmapDev->CreateContext(rasterBitmapGc); + if (err != KErrNone) { + delete rasterBitmap; + delete rasterBitmapDev; + rasterBitmapDev = 0; + return; + } + + rasterBitmapGc->BitBlt(TPoint( 0, 0), bitmap); + + bitmap = rasterBitmap; + + delete rasterBitmapDev; + delete rasterBitmapGc; + + rasterBitmapDev = 0; + rasterBitmapGc = 0; + + deleteSourceBitmap = true; + } +#endif + + + deleteSourceBitmap = bitmap->IsCompressedInRAM(); + CFbsBitmap *sourceBitmap = uncompress(bitmap); + + TDisplayMode displayMode = sourceBitmap->DisplayMode(); + QImage::Format format = qt_TDisplayMode2Format(displayMode); + + QImage::Format opaqueFormat = QNativeImage::systemFormat(); + QImage::Format alphaFormat = QImage::Format_ARGB32_Premultiplied; + + if (format != opaqueFormat && format != alphaFormat && format != QImage::Format_MonoLSB) + needsCopy = true; + + + type = (format != QImage::Format_MonoLSB) + ? QPixmapData::PixmapType + : QPixmapData::BitmapType; + + if (needsCopy) { + + TSize size = sourceBitmap->SizeInPixels(); + int bytesPerLine = sourceBitmap->ScanLineLength(size.iWidth, displayMode); + + QSymbianBitmapDataAccess da; + da.beginDataAccess(sourceBitmap); + uchar *bytes = (uchar*)sourceBitmap->DataAddress(); + QImage img = QImage(bytes, size.iWidth, size.iHeight, bytesPerLine, format); + img = img.copy(); + da.endDataAccess(sourceBitmap); + + if(displayMode == EGray2) { + //Symbian thinks set pixels are white/transparent, Qt thinks they are foreground/solid + //So invert mono bitmaps so that masks work correctly. + img.invertPixels(); + } else if(displayMode == EColor16M) { + img = img.rgbSwapped(); // EColor16M is BGR + } + + fromImage(img, Qt::AutoColor); + + if(deleteSourceBitmap) + delete sourceBitmap; + } else { + CFbsBitmap* duplicate = 0; + QT_TRAP_THROWING(duplicate = new (ELeave) CFbsBitmap); + + TInt err = duplicate->Duplicate(sourceBitmap->Handle()); + if (err != KErrNone) { + qWarning("Could not duplicate CFbsBitmap"); + + if(deleteSourceBitmap) + delete sourceBitmap; + + delete duplicate; + return; + } + + fromSymbianBitmap(duplicate); + + if(deleteSourceBitmap) + delete sourceBitmap; + } + } +} + +void QS60PixmapData::convertToDisplayMode(int mode) +{ + const TDisplayMode displayMode = static_cast(mode); + if (!cfbsBitmap || cfbsBitmap->DisplayMode() == displayMode) + return; + if (image.depth() != TDisplayModeUtils::NumDisplayModeBitsPerPixel(displayMode)) { + qWarning("Cannot convert display mode due to depth mismatch"); + return; + } + + const TSize size = cfbsBitmap->SizeInPixels(); + QScopedPointer newBitmap(createSymbianCFbsBitmap(size, displayMode)); + + const uchar *sptr = const_cast(image).bits(); + symbianBitmapDataAccess->beginDataAccess(newBitmap.data()); + uchar *dptr = (uchar*)newBitmap->DataAddress(); + Mem::Copy(dptr, sptr, image.byteCount()); + symbianBitmapDataAccess->endDataAccess(newBitmap.data()); + + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + delete cfbsBitmap; + lock.relock(); + cfbsBitmap = newBitmap.take(); + setSerialNumber(cfbsBitmap->Handle()); + UPDATE_BUFFER(); +} + +QPixmapData *QS60PixmapData::createCompatiblePixmapData() const +{ + return new QS60PixmapData(pixelType()); +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_s60_p.h b/src/gui/image/qpixmap_s60_p.h new file mode 100644 index 0000000000..c440bbc33a --- /dev/null +++ b/src/gui/image/qpixmap_s60_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPDATA_S60_P_H +#define QPIXMAPDATA_S60_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 + +QT_BEGIN_NAMESPACE + +class CFbsBitmap; +class CFbsBitmapDevice; +class CFbsBitGc; + +class QSymbianBitmapDataAccess; + +class QSymbianFbsHeapLock +{ +public: + + enum LockAction { + Unlock + }; + + explicit QSymbianFbsHeapLock(LockAction a); + ~QSymbianFbsHeapLock(); + void relock(); + +private: + + LockAction action; + bool wasLocked; +}; + +class QS60PixmapData : public QRasterPixmapData +{ +public: + QS60PixmapData(PixelType type); + ~QS60PixmapData(); + + QPixmapData *createCompatiblePixmapData() const; + + void resize(int width, int height); + void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + void copy(const QPixmapData *data, const QRect &rect); + bool scroll(int dx, int dy, const QRect &rect); + + int metric(QPaintDevice::PaintDeviceMetric metric) const; + void fill(const QColor &color); + void setMask(const QBitmap &mask); + void setAlphaChannel(const QPixmap &alphaChannel); + QImage toImage() const; + QPaintEngine* paintEngine() const; + + void beginDataAccess(); + void endDataAccess(bool readOnly=false) const; + + void* toNativeType(NativeType type); + void fromNativeType(void* pixmap, NativeType type); + + void convertToDisplayMode(int mode); + +private: + void release(); + void fromSymbianBitmap(CFbsBitmap* bitmap, bool lockFormat=false); + QImage toImage(const QRect &r) const; + + QSymbianBitmapDataAccess *symbianBitmapDataAccess; + + CFbsBitmap *cfbsBitmap; + QPaintEngine *pengine; + uchar* bytes; + + bool formatLocked; + + QS60PixmapData *next; + QS60PixmapData *prev; + + static void qt_symbian_register_pixmap(QS60PixmapData *pd); + static void qt_symbian_unregister_pixmap(QS60PixmapData *pd); + static void qt_symbian_release_pixmaps(); + + friend class QPixmap; + friend class QS60WindowSurface; + friend class QS60PaintEngine; + friend class QS60Data; +}; + +QT_END_NAMESPACE + +#endif // QPIXMAPDATA_S60_P_H + diff --git a/src/gui/image/qpixmap_win.cpp b/src/gui/image/qpixmap_win.cpp new file mode 100644 index 0000000000..9c14ac7726 --- /dev/null +++ b/src/gui/image/qpixmap_win.cpp @@ -0,0 +1,477 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmap.h" +#include "qpixmap_raster_p.h" + +#include "qbitmap.h" +#include "qimage.h" +#include "qwidget.h" +#include "qpainter.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qapplication.h" +#include "qevent.h" +#include "qfile.h" +#include "qfileinfo.h" +#include "qdatetime.h" +#include "qpixmapcache.h" +#include "qimagereader.h" +#include "qimagewriter.h" +#include "qdebug.h" +#include "qt_windows.h" + +#if defined(Q_WS_WINCE) +#include +#include "qguifunctions_wince.h" +extern bool qt_wince_is_high_dpi(); +extern bool qt_wince_is_pocket_pc(); +#endif + +#ifndef CAPTUREBLT +#define CAPTUREBLT ((DWORD)0x40000000) +#endif + +QT_BEGIN_NAMESPACE + +QPixmap QPixmap::grabWindow(WId winId, int x, int y, int w, int h ) +{ + RECT r; + GetClientRect(winId, &r); + + if (w < 0) w = r.right - r.left; + if (h < 0) h = r.bottom - r.top; + +#ifdef Q_WS_WINCE_WM + if (qt_wince_is_pocket_pc()) { + QWidget *widget = QWidget::find(winId); + if (qobject_cast(widget)) { + RECT rect = {0,0,0,0}; + AdjustWindowRectEx(&rect, WS_BORDER | WS_CAPTION, FALSE, 0); + int magicNumber = qt_wince_is_high_dpi() ? 4 : 2; + y += rect.top - magicNumber; + } + } +#endif + + // Create and setup bitmap + HDC display_dc = GetDC(0); + HDC bitmap_dc = CreateCompatibleDC(display_dc); + HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h); + HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap); + + // copy data + HDC window_dc = GetDC(winId); + BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, SRCCOPY +#ifndef Q_WS_WINCE + | CAPTUREBLT +#endif + ); + + // clean up all but bitmap + ReleaseDC(winId, window_dc); + SelectObject(bitmap_dc, null_bitmap); + DeleteDC(bitmap_dc); + + QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap); + + DeleteObject(bitmap); + ReleaseDC(0, display_dc); + + return pixmap; +} + +HBITMAP QPixmap::toWinHBITMAP(HBitmapFormat format) const +{ + if (isNull()) + return 0; + + HBITMAP bitmap = 0; + if (data->classId() == QPixmapData::RasterClass) { + QRasterPixmapData* d = static_cast(data.data()); + int w = d->image.width(); + int h = d->image.height(); + + HDC display_dc = GetDC(0); + + // 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; + bmi.bmiHeader.biSizeImage = w * h * 4; + + // Create the pixmap + uchar *pixels = 0; + bitmap = CreateDIBSection(display_dc, &bmi, DIB_RGB_COLORS, (void **) &pixels, 0, 0); + ReleaseDC(0, display_dc); + if (!bitmap) { + qErrnoWarning("QPixmap::toWinHBITMAP(), failed to create dibsection"); + return 0; + } + if (!pixels) { + qErrnoWarning("QPixmap::toWinHBITMAP(), did not allocate pixel data"); + return 0; + } + + // Copy over the data + QImage::Format imageFormat = QImage::Format_ARGB32; + if (format == NoAlpha) + imageFormat = QImage::Format_RGB32; + else if (format == PremultipliedAlpha) + imageFormat = QImage::Format_ARGB32_Premultiplied; + const QImage image = d->image.convertToFormat(imageFormat); + int bytes_per_line = w * 4; + for (int y=0; yfromImage(toImage(), Qt::AutoColor); + return QPixmap(data).toWinHBITMAP(format); + } + return bitmap; +} + +QPixmap QPixmap::fromWinHBITMAP(HBITMAP bitmap, HBitmapFormat format) +{ + // Verify size + BITMAP bitmap_info; + memset(&bitmap_info, 0, sizeof(BITMAP)); + + int res = GetObject(bitmap, sizeof(BITMAP), &bitmap_info); + if (!res) { + qErrnoWarning("QPixmap::fromWinHBITMAP(), failed to get bitmap info"); + return QPixmap(); + } + int w = bitmap_info.bmWidth; + int h = bitmap_info.bmHeight; + + 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; + bmi.bmiHeader.biSizeImage = w * h * 4; + + QImage result; + // Get bitmap bits + uchar *data = (uchar *) qMalloc(bmi.bmiHeader.biSizeImage); + + HDC display_dc = GetDC(0); + if (GetDIBits(display_dc, bitmap, 0, h, data, &bmi, DIB_RGB_COLORS)) { + + QImage::Format imageFormat = QImage::Format_ARGB32_Premultiplied; + uint mask = 0; + if (format == NoAlpha) { + imageFormat = QImage::Format_RGB32; + mask = 0xff000000; + } + + // Create image and copy data into image. + QImage image(w, h, imageFormat); + if (!image.isNull()) { // failed to alloc? + int bytes_per_line = w * sizeof(QRgb); + for (int y=0; y(image.scanLine(y)); + for (int x = 0; x < w ; x++) { + if (qAlpha(scanLine[x]) != 0) { + foundAlpha = true; + break; + } + } + } + if (!foundAlpha) { + //If no alpha was found, we use the mask to set alpha values + DrawIconEx( hdc, 0, 0, icon, w, h, 0, 0, DI_MASK); + QImage mask = qt_fromWinHBITMAP(hdc, winBitmap, w, h); + + for (int y = 0 ; y < h ; y++){ + QRgb *scanlineImage = reinterpret_cast(image.scanLine(y)); + QRgb *scanlineMask = mask.isNull() ? 0 : reinterpret_cast(mask.scanLine(y)); + for (int x = 0; x < w ; x++){ + if (scanlineMask && qRed(scanlineMask[x]) != 0) + scanlineImage[x] = 0; //mask out this pixel + else + scanlineImage[x] |= 0xff000000; // set the alpha channel to 255 + } + } + } + //dispose resources created by iconinfo call + DeleteObject(iconinfo.hbmMask); + DeleteObject(iconinfo.hbmColor); + + SelectObject(hdc, oldhdc); //restore state + DeleteObject(winBitmap); + DeleteDC(hdc); + return QPixmap::fromImage(image); +} +#else //ifndef Q_WS_WINCE +QPixmap QPixmap::fromWinHICON(HICON icon) +{ + HDC screenDevice = GetDC(0); + HDC hdc = CreateCompatibleDC(screenDevice); + ReleaseDC(0, screenDevice); + + ICONINFO iconinfo; + bool result = GetIconInfo(icon, &iconinfo); + if (!result) + qWarning("QPixmap::fromWinHICON(), failed to GetIconInfo()"); + + int w = 0; + int h = 0; + if (!iconinfo.xHotspot || !iconinfo.yHotspot) { + // We could not retrieve the icon size via GetIconInfo, + // so we try again using the icon bitmap. + BITMAP bm; + int result = GetObject(iconinfo.hbmColor, sizeof(BITMAP), &bm); + if (!result) result = GetObject(iconinfo.hbmMask, sizeof(BITMAP), &bm); + if (!result) { + qWarning("QPixmap::fromWinHICON(), failed to retrieve icon size"); + return QPixmap(); + } + w = bm.bmWidth; + h = bm.bmHeight; + } else { + // x and y Hotspot describes the icon center + w = iconinfo.xHotspot * 2; + h = iconinfo.yHotspot * 2; + } + const DWORD dwImageSize = w * h * 4; + + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFO); + bmi.bmiHeader.biWidth = w; + bmi.bmiHeader.biHeight = -h; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = dwImageSize; + + uchar* bits; + + HBITMAP winBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**) &bits, 0, 0); + if (winBitmap ) + memset(bits, 0xff, dwImageSize); + if (!winBitmap) { + qWarning("QPixmap::fromWinHICON(), failed to CreateDIBSection()"); + return QPixmap(); + } + + HGDIOBJ oldhdc = (HBITMAP)SelectObject(hdc, winBitmap); + if (!DrawIconEx( hdc, 0, 0, icon, w, h, 0, 0, DI_NORMAL)) + qWarning("QPixmap::fromWinHICON(), failed to DrawIcon()"); + + uint mask = 0xff000000; + // Create image and copy data into image. + QImage image(w, h, QImage::Format_ARGB32); + + if (!image.isNull()) { // failed to alloc? + int bytes_per_line = w * sizeof(QRgb); + for (int y=0; y < h; ++y) { + QRgb *dest = (QRgb *) image.scanLine(y); + const QRgb *src = (const QRgb *) (bits + y * bytes_per_line); + for (int x=0; x < w; ++x) { + dest[x] = src[x]; + } + } + } + if (!DrawIconEx( hdc, 0, 0, icon, w, h, 0, 0, DI_MASK)) + qWarning("QPixmap::fromWinHICON(), failed to DrawIcon()"); + if (!image.isNull()) { // failed to alloc? + int bytes_per_line = w * sizeof(QRgb); + for (int y=0; y < h; ++y) { + QRgb *dest = (QRgb *) image.scanLine(y); + const QRgb *src = (const QRgb *) (bits + y * bytes_per_line); + for (int x=0; x < w; ++x) { + if (!src[x]) + dest[x] = dest[x] | mask; + } + } + } + SelectObject(hdc, oldhdc); //restore state + DeleteObject(winBitmap); + DeleteDC(hdc); + return QPixmap::fromImage(image); +} +#endif //ifndef Q_WS_WINCE +#endif //ifdef Q_WS_WIN + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_x11.cpp b/src/gui/image/qpixmap_x11.cpp new file mode 100644 index 0000000000..bc468cb7ec --- /dev/null +++ b/src/gui/image/qpixmap_x11.cpp @@ -0,0 +1,2419 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Uncomment the next line to enable the MIT Shared Memory extension +// +// WARNING: This has some problems: +// +// 1. Consumes a 800x600 pixmap +// 2. Qt does not handle the ShmCompletion message, so you will +// get strange effects if you xForm() repeatedly. +// +// #define QT_MITSHM + +#if defined(Q_OS_WIN32) && defined(QT_MITSHM) +#undef QT_MITSHM +#endif + +#include "qplatformdefs.h" + +#include "qdebug.h" +#include "qiodevice.h" +#include "qpixmap_x11_p.h" +#include "qbitmap.h" +#include "qcolormap.h" +#include "qimage.h" +#include "qmatrix.h" +#include "qapplication.h" +#include +#include +#include "qx11info_x11.h" +#include +#include +#include + +#include + +#if defined(Q_CC_MIPS) +# define for if(0){}else for +#endif + +QT_BEGIN_NAMESPACE + +QPixmap qt_toX11Pixmap(const QImage &image) +{ + QPixmapData *data = + new QX11PixmapData(image.depth() == 1 + ? QPixmapData::BitmapType + : QPixmapData::PixmapType); + + data->fromImage(image, Qt::AutoColor); + + return QPixmap(data); +} + +QPixmap qt_toX11Pixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return QPixmap(); + + if (QPixmap(pixmap).data_ptr()->classId() == QPixmapData::X11Class) + return pixmap; + + return qt_toX11Pixmap(pixmap.toImage()); +} + +// For thread-safety: +// image->data does not belong to X11, so we must free it ourselves. + +inline static void qSafeXDestroyImage(XImage *x) +{ + if (x->data) { + free(x->data); + x->data = 0; + } + XDestroyImage(x); +} + +QBitmap QX11PixmapData::mask_to_bitmap(int screen) const +{ + if (!x11_mask) + return QBitmap(); + QPixmap::x11SetDefaultScreen(screen); + QBitmap bm(w, h); + GC gc = XCreateGC(X11->display, bm.handle(), 0, 0); + XCopyArea(X11->display, x11_mask, bm.handle(), gc, 0, 0, + bm.data->width(), bm.data->height(), 0, 0); + XFreeGC(X11->display, gc); + return bm; +} + +Qt::HANDLE QX11PixmapData::bitmap_to_mask(const QBitmap &bitmap, int screen) +{ + if (bitmap.isNull()) + return 0; + QBitmap bm = bitmap; + bm.x11SetScreen(screen); + + Pixmap mask = XCreatePixmap(X11->display, RootWindow(X11->display, screen), + bm.data->width(), bm.data->height(), 1); + GC gc = XCreateGC(X11->display, mask, 0, 0); + XCopyArea(X11->display, bm.handle(), mask, gc, 0, 0, + bm.data->width(), bm.data->height(), 0, 0); + XFreeGC(X11->display, gc); + return mask; +} + + +/***************************************************************************** + MIT Shared Memory Extension support: makes xForm noticeably (~20%) faster. + *****************************************************************************/ + +#if defined(QT_MITSHM) + +static bool xshminit = false; +static XShmSegmentInfo xshminfo; +static XImage *xshmimg = 0; +static Pixmap xshmpm = 0; + +static void qt_cleanup_mitshm() +{ + if (xshmimg == 0) + return; + Display *dpy = QX11Info::appDisplay(); + if (xshmpm) { + XFreePixmap(dpy, xshmpm); + xshmpm = 0; + } + XShmDetach(dpy, &xshminfo); xshmimg->data = 0; + qSafeXDestroyImage(xshmimg); xshmimg = 0; + shmdt(xshminfo.shmaddr); + shmctl(xshminfo.shmid, IPC_RMID, 0); +} + +static bool qt_create_mitshm_buffer(const QPaintDevice* dev, int w, int h) +{ + static int major, minor; + static Bool pixmaps_ok; + Display *dpy = dev->data->xinfo->display(); + int dd = dev->x11Depth(); + Visual *vis = (Visual*)dev->x11Visual(); + + if (xshminit) { + qt_cleanup_mitshm(); + } else { + if (!XShmQueryVersion(dpy, &major, &minor, &pixmaps_ok)) + return false; // MIT Shm not supported + qAddPostRoutine(qt_cleanup_mitshm); + xshminit = true; + } + + xshmimg = XShmCreateImage(dpy, vis, dd, ZPixmap, 0, &xshminfo, w, h); + if (!xshmimg) + return false; + + bool ok; + xshminfo.shmid = shmget(IPC_PRIVATE, + xshmimg->bytes_per_line * xshmimg->height, + IPC_CREAT | 0777); + ok = xshminfo.shmid != -1; + if (ok) { + xshmimg->data = (char*)shmat(xshminfo.shmid, 0, 0); + xshminfo.shmaddr = xshmimg->data; + ok = (xshminfo.shmaddr != (char*)-1); + } + xshminfo.readOnly = false; + if (ok) + ok = XShmAttach(dpy, &xshminfo); + if (!ok) { + qSafeXDestroyImage(xshmimg); + xshmimg = 0; + if (xshminfo.shmaddr) + shmdt(xshminfo.shmaddr); + if (xshminfo.shmid != -1) + shmctl(xshminfo.shmid, IPC_RMID, 0); + return false; + } + if (pixmaps_ok) + xshmpm = XShmCreatePixmap(dpy, DefaultRootWindow(dpy), xshmimg->data, + &xshminfo, w, h, dd); + + return true; +} + +#else + +// If extern, need a dummy. +// +// static bool qt_create_mitshm_buffer(QPaintDevice*, int, int) +// { +// return false; +// } + +#endif // QT_MITSHM + + +/***************************************************************************** + Internal functions + *****************************************************************************/ + +extern const uchar *qt_get_bitflip_array(); // defined in qimage.cpp + +// Returns position of highest bit set or -1 if none +static int highest_bit(uint v) +{ + int i; + uint b = (uint)1 << 31; + for (i=31; ((b & v) == 0) && i>=0; i--) + b >>= 1; + return i; +} + +// Returns position of lowest set bit in 'v' as an integer (0-31), or -1 +static int lowest_bit(uint v) +{ + int i; + ulong lb; + lb = 1; + for (i=0; ((v & lb) == 0) && i<32; i++, lb<<=1) {} + return i==32 ? -1 : i; +} + +// Counts the number of bits set in 'v' +static uint n_bits(uint v) +{ + int i = 0; + while (v) { + v = v & (v - 1); + i++; + } + return i; +} + +static uint *red_scale_table = 0; +static uint *green_scale_table = 0; +static uint *blue_scale_table = 0; + +static void cleanup_scale_tables() +{ + delete[] red_scale_table; + delete[] green_scale_table; + delete[] blue_scale_table; +} + +/* + Could do smart bitshifting, but the "obvious" algorithm only works for + nBits >= 4. This is more robust. +*/ +static void build_scale_table(uint **table, uint nBits) +{ + if (nBits > 7) { + qWarning("build_scale_table: internal error, nBits = %i", nBits); + return; + } + if (!*table) { + static bool firstTable = true; + if (firstTable) { + qAddPostRoutine(cleanup_scale_tables); + firstTable = false; + } + *table = new uint[256]; + } + int maxVal = (1 << nBits) - 1; + int valShift = 8 - nBits; + int i; + for(i = 0 ; i < maxVal + 1 ; i++) + (*table)[i << valShift] = i*255/maxVal; +} + +static int defaultScreen = -1; + +/***************************************************************************** + QPixmap member functions + *****************************************************************************/ + +QBasicAtomicInt qt_pixmap_serial = Q_BASIC_ATOMIC_INITIALIZER(0); +int Q_GUI_EXPORT qt_x11_preferred_pixmap_depth = 0; + +QX11PixmapData::QX11PixmapData(PixelType type) + : QPixmapData(type, X11Class), gl_surface(0), hd(0), + flags(Uninitialized), x11_mask(0), picture(0), mask_picture(0), hd2(0), + share_mode(QPixmap::ImplicitlyShared), pengine(0) +{ +} + +QPixmapData *QX11PixmapData::createCompatiblePixmapData() const +{ + return new QX11PixmapData(pixelType()); +} + +void QX11PixmapData::resize(int width, int height) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = width; + h = height; + is_null = (w <= 0 || h <= 0); + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + QX11InfoData* xd = xinfo.getX11Data(true); + xd->screen = defaultScreen; + xd->depth = QX11Info::appDepth(xd->screen); + xd->cells = QX11Info::appCells(xd->screen); + xd->colormap = QX11Info::appColormap(xd->screen); + xd->defaultColormap = QX11Info::appDefaultColormap(xd->screen); + xd->visual = (Visual *)QX11Info::appVisual(xd->screen); + xd->defaultVisual = QX11Info::appDefaultVisual(xd->screen); + xinfo.setX11Data(xd); + } + + int dd = xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + bool make_null = w <= 0 || h <= 0; // create null pixmap + d = (pixelType() == BitmapType ? 1 : dd); + if (make_null || d == 0) { + w = 0; + h = 0; + is_null = true; + hd = 0; + picture = 0; + d = 0; + if (!make_null) + qWarning("QPixmap: Invalid pixmap parameters"); + return; + } + hd = (Qt::HANDLE)XCreatePixmap(X11->display, + RootWindow(X11->display, xinfo.screen()), + w, h, d); +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(X11->display, PictStandardA1) + : XRenderFindVisualFormat(X11->display, (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(X11->display, hd, format, 0, 0); + } +#endif // QT_NO_XRENDER +} + +struct QX11AlphaDetector +{ + bool hasAlpha() const { + if (checked) + return has; + // Will implicitly also check format and return quickly for opaque types... + checked = true; + has = image->isNull() ? false : const_cast(image)->data_ptr()->checkForAlphaPixels(); + return has; + } + + bool hasXRenderAndAlpha() const { + if (!X11->use_xrender) + return false; + return hasAlpha(); + } + + QX11AlphaDetector(const QImage *i, Qt::ImageConversionFlags flags) + : image(i), checked(false), has(false) + { + if (flags & Qt::NoOpaqueDetection) { + checked = true; + has = image->hasAlphaChannel(); + } + } + + const QImage *image; + mutable bool checked; + mutable bool has; +}; + +void QX11PixmapData::fromImage(const QImage &img, + Qt::ImageConversionFlags flags) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = img.width(); + h = img.height(); + d = img.depth(); + is_null = (w <= 0 || h <= 0); + + if (is_null) { + w = h = 0; + return; + } + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + QX11InfoData* xd = xinfo.getX11Data(true); + xd->screen = defaultScreen; + xd->depth = QX11Info::appDepth(xd->screen); + xd->cells = QX11Info::appCells(xd->screen); + xd->colormap = QX11Info::appColormap(xd->screen); + xd->defaultColormap = QX11Info::appDefaultColormap(xd->screen); + xd->visual = (Visual *)QX11Info::appVisual(xd->screen); + xd->defaultVisual = QX11Info::appDefaultVisual(xd->screen); + xinfo.setX11Data(xd); + } + + if (pixelType() == BitmapType) { + bitmapFromImage(img); + return; + } + + if (uint(w) >= 32768 || uint(h) >= 32768) { + w = h = 0; + is_null = true; + return; + } + + QX11AlphaDetector alphaCheck(&img, flags); + int dd = alphaCheck.hasXRenderAndAlpha() ? 32 : xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + QImage image = img; + + // must be monochrome + if (dd == 1 || (flags & Qt::ColorMode_Mask) == Qt::MonoOnly) { + if (d != 1) { + // dither + image = image.convertToFormat(QImage::Format_MonoLSB, flags); + d = 1; + } + } else { // can be both + bool conv8 = false; + if (d > 8 && dd <= 8) { // convert to 8 bit + if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither) + flags = (flags & ~Qt::DitherMode_Mask) + | Qt::PreferDither; + conv8 = true; + } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) { + conv8 = (d == 1); // native depth wanted + } else if (d == 1) { + if (image.colorCount() == 2) { + QRgb c0 = image.color(0); // Auto: convert to best + QRgb c1 = image.color(1); + conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255); + } else { + // eg. 1-color monochrome images (they do exist). + conv8 = true; + } + } + if (conv8) { + image = image.convertToFormat(QImage::Format_Indexed8, flags); + d = 8; + } + } + + if (d == 1 || d == 16 || d == 24) { + image = image.convertToFormat(QImage::Format_RGB32, flags); + fromImage(image, Qt::AutoColor); + return; + } + + Display *dpy = X11->display; + Visual *visual = (Visual *)xinfo.visual(); + XImage *xi = 0; + bool trucol = (visual->c_class >= TrueColor); + int nbytes = image.byteCount(); + uchar *newbits= 0; + +#ifndef QT_NO_XRENDER + if (alphaCheck.hasXRenderAndAlpha()) { + const QImage &cimage = image; + + d = 32; + + if (QX11Info::appDepth() != d) { + if (xinfo.x11data) { + xinfo.x11data->depth = d; + } else { + QX11InfoData *xd = xinfo.getX11Data(true); + xd->screen = QX11Info::appScreen(); + xd->depth = d; + xd->cells = QX11Info::appCells(); + xd->colormap = QX11Info::appColormap(); + xd->defaultColormap = QX11Info::appDefaultColormap(); + xd->visual = (Visual *)QX11Info::appVisual(); + xd->defaultVisual = QX11Info::appDefaultVisual(); + xinfo.setX11Data(xd); + } + } + + hd = (Qt::HANDLE)XCreatePixmap(dpy, RootWindow(dpy, xinfo.screen()), + w, h, d); + picture = XRenderCreatePicture(X11->display, hd, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), 0, 0); + + xi = XCreateImage(dpy, visual, d, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + xi->data = (char *)newbits; + + switch(cimage.format()) { + case QImage::Format_Indexed8: { + QVector colorTable = cimage.colorTable(); + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const uchar *p = cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = colorTable[p[x]]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + } + break; + case QImage::Format_RGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) + *xidata++ = p[x] | 0xff000000; + } + } + break; + case QImage::Format_ARGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = p[x]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + + } + break; + case QImage::Format_ARGB32_Premultiplied: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + memcpy(xidata, p, w*sizeof(QRgb)); + xidata += w; + } + } + break; + default: + Q_ASSERT(false); + } + + if ((xi->byte_order == MSBFirst) != (QSysInfo::ByteOrder == QSysInfo::BigEndian)) { + uint *xidata = (uint *)xi->data; + uint *xiend = xidata + w*h; + while (xidata < xiend) { + *xidata = (*xidata >> 24) + | ((*xidata >> 8) & 0xff00) + | ((*xidata << 8) & 0xff0000) + | (*xidata << 24); + ++xidata; + } + } + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + + return; + } +#endif // QT_NO_XRENDER + + if (trucol) { // truecolor display + if (image.format() == QImage::Format_ARGB32_Premultiplied) + image = image.convertToFormat(QImage::Format_ARGB32); + + const QImage &cimage = image; + QRgb pix[256]; // pixel translation table + const bool d8 = (d == 8); + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + const uint rbits = highest_bit(red_mask) - lowest_bit(red_mask) + 1; + const uint gbits = highest_bit(green_mask) - lowest_bit(green_mask) + 1; + const uint bbits = highest_bit(blue_mask) - lowest_bit(blue_mask) + 1; + + if (d8) { // setup pixel translation + QVector ctable = cimage.colorTable(); + for (int i=0; i < cimage.colorCount(); i++) { + int r = qRed (ctable[i]); + int g = qGreen(ctable[i]); + int b = qBlue (ctable[i]); + r = red_shift > 0 ? r << red_shift : r >> -red_shift; + g = green_shift > 0 ? g << green_shift : g >> -green_shift; + b = blue_shift > 0 ? b << blue_shift : b >> -blue_shift; + pix[i] = (b & blue_mask) | (g & green_mask) | (r & red_mask) + | ~(blue_mask | green_mask | red_mask); + } + } + + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + int bppc = xi->bits_per_pixel; + + bool contig_bits = n_bits(red_mask) == rbits && + n_bits(green_mask) == gbits && + n_bits(blue_mask) == bbits; + bool dither_tc = + // Want it? + (flags & Qt::Dither_Mask) != Qt::ThresholdDither && + (flags & Qt::DitherMode_Mask) != Qt::AvoidDither && + // Need it? + bppc < 24 && !d8 && + // Can do it? (Contiguous bits?) + contig_bits; + + static bool init=false; + static int D[16][16]; + if (dither_tc && !init) { + // I also contributed this code to XV - WWA. + /* + The dither matrix, D, is obtained with this formula: + + D2 = [0 2] + [3 1] + + + D2*n = [4*Dn 4*Dn+2*Un] + [4*Dn+3*Un 4*Dn+1*Un] + */ + int n,i,j; + init=1; + + /* Set D2 */ + D[0][0]=0; + D[1][0]=2; + D[0][1]=3; + D[1][1]=1; + + /* Expand using recursive definition given above */ + for (n=2; n<16; n*=2) { + for (i=0; ibyte_order == MSBFirst) == (QSysInfo::ByteOrder == QSysInfo::BigEndian); + + if(bppc == 8) // 8 bit + mode = BPP8; + else if(bppc == 16) { // 16 bit MSB/LSB + if(red_shift == 8 && green_shift == 3 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_565; + else if(red_shift == 7 && green_shift == 2 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_555; + else + mode = (xi->byte_order == LSBFirst) ? BPP16_LSB : BPP16_MSB; + } else if(bppc == 24) { // 24 bit MSB/LSB + if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP24_888; + else + mode = (xi->byte_order == LSBFirst) ? BPP24_LSB : BPP24_MSB; + } else if(bppc == 32) { // 32 bit MSB/LSB + if(red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP32_8888; + else + mode = (xi->byte_order == LSBFirst) ? BPP32_LSB : BPP32_MSB; + } else + qFatal("Logic error 3"); + +#define GET_PIXEL \ + uint pixel; \ + if (d8) pixel = pix[*src++]; \ + else { \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask) \ + | ~(blue_mask | green_mask | red_mask); \ + } + +#define GET_PIXEL_DITHER_TC \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + const int thres = D[x%16][y%16]; \ + if (r <= (255-(1<<(8-rbits))) && ((r< thres) \ + r += (1<<(8-rbits)); \ + if (g <= (255-(1<<(8-gbits))) && ((g< thres) \ + g += (1<<(8-gbits)); \ + if (b <= (255-(1<<(8-bbits))) && ((b< thres) \ + b += (1<<(8-bbits)); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + uint pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask); + +// again, optimized case +// can't be optimized that much :( +#define GET_PIXEL_DITHER_TC_OPT(red_shift,green_shift,blue_shift,red_mask,green_mask,blue_mask, \ + rbits,gbits,bbits) \ + const int thres = D[x%16][y%16]; \ + int r = qRed (*p); \ + if (r <= (255-(1<<(8-rbits))) && ((r< thres) \ + r += (1<<(8-rbits)); \ + int g = qGreen(*p); \ + if (g <= (255-(1<<(8-gbits))) && ((g< thres) \ + g += (1<<(8-gbits)); \ + int b = qBlue (*p++); \ + if (b <= (255-(1<<(8-bbits))) && ((b< thres) \ + b += (1<<(8-bbits)); \ + uint pixel = ((r red_shift) & red_mask) \ + | ((g green_shift) & green_mask) \ + | ((b blue_shift) & blue_mask); + +#define CYCLE(body) \ + for (int y=0; ybytes_per_line*y; \ + const QRgb* p = (const QRgb *)src; \ + body \ + } + + if (dither_tc) { + switch (mode) { + case BPP16_565: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x>3,0xf800,0x7e0,0x1f,5,6,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x>3,0x7c00,0x3e0,0x1f,5,5,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x> 8; + } + ) + break; + default: + qFatal("Logic error"); + } + } else { + switch (mode) { + case BPP8: // 8 bit + CYCLE( + Q_UNUSED(p); + for (int x=0; x> 8) & 0xf800) + | ((*p >> 5) & 0x7e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x> 9) & 0x7c00) + | ((*p >> 6) & 0x3e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x> 8; + } + ) + break; + case BPP24_888: // 24 bit MSB + CYCLE( + for (int x=0; x> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP24_LSB: // 24 bit LSB + CYCLE( + for (int x=0; x> 8; + *dst++ = pixel >> 16; + } + ) + break; + case BPP32_8888: + CYCLE( + memcpy(dst, p, w * 4); + ) + break; + case BPP32_MSB: // 32 bit MSB + CYCLE( + for (int x=0; x> 24; + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP32_LSB: // 32 bit LSB + CYCLE( + for (int x=0; x> 8; + *dst++ = pixel >> 16; + *dst++ = pixel >> 24; + } + ) + break; + default: + qFatal("Logic error 2"); + } + } + xi->data = (char *)newbits; + } + + if (d == 8 && !trucol) { // 8 bit pixmap + int pop[256]; // pixel popularity + + if (image.colorCount() == 0) + image.setColorCount(1); + + const QImage &cimage = image; + memset(pop, 0, sizeof(int)*256); // reset popularity array + for (int i = 0; i < h; i++) { // for each scanline... + const uchar* p = cimage.scanLine(i); + const uchar *end = p + w; + while (p < end) // compute popularity + pop[*p++]++; + } + + newbits = (uchar *)malloc(nbytes); // copy image into newbits + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + uchar* p = newbits; + memcpy(p, cimage.bits(), nbytes); // copy image data into newbits + + /* + * The code below picks the most important colors. It is based on the + * diversity algorithm, implemented in XV 3.10. XV is (C) by John Bradley. + */ + + struct PIX { // pixel sort element + uchar r,g,b,n; // color + pad + int use; // popularity + int index; // index in colormap + int mindist; + }; + int ncols = 0; + for (int i=0; i< cimage.colorCount(); i++) { // compute number of colors + if (pop[i] > 0) + ncols++; + } + for (int i = cimage.colorCount(); i < 256; i++) // ignore out-of-range pixels + pop[i] = 0; + + // works since we make sure above to have at least + // one color in the image + if (ncols == 0) + ncols = 1; + + PIX pixarr[256]; // pixel array + PIX pixarr_sorted[256]; // pixel array (sorted) + memset(pixarr, 0, ncols*sizeof(PIX)); + PIX *px = &pixarr[0]; + int maxpop = 0; + int maxpix = 0; + uint j = 0; + QVector ctable = cimage.colorTable(); + for (int i = 0; i < 256; i++) { // init pixel array + if (pop[i] > 0) { + px->r = qRed (ctable[i]); + px->g = qGreen(ctable[i]); + px->b = qBlue (ctable[i]); + px->n = 0; + px->use = pop[i]; + if (pop[i] > maxpop) { // select most popular entry + maxpop = pop[i]; + maxpix = j; + } + px->index = i; + px->mindist = 1000000; + px++; + j++; + } + } + pixarr_sorted[0] = pixarr[maxpix]; + pixarr[maxpix].use = 0; + + for (int i = 1; i < ncols; i++) { // sort pixels + int minpix = -1, mindist = -1; + px = &pixarr_sorted[i-1]; + int r = px->r; + int g = px->g; + int b = px->b; + int dist; + if ((i & 1) || i<10) { // sort on max distance + for (int j=0; juse) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->mindist > mindist) { + mindist = px->mindist; + minpix = j; + } + } + } + } else { // sort on max popularity + for (int j=0; juse) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->use > mindist) { + mindist = px->use; + minpix = j; + } + } + } + } + pixarr_sorted[i] = pixarr[minpix]; + pixarr[minpix].use = 0; + } + + QColormap cmap = QColormap::instance(xinfo.screen()); + uint pix[256]; // pixel translation table + px = &pixarr_sorted[0]; + for (int i = 0; i < ncols; i++) { // allocate colors + QColor c(px->r, px->g, px->b); + pix[px->index] = cmap.pixel(c); + px++; + } + + p = newbits; + for (int i = 0; i < nbytes; i++) { // translate pixels + *p = pix[*p]; + p++; + } + } + + if (!xi) { // X image not created + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + if (xi->bits_per_pixel == 16) { // convert 8 bpp ==> 16 bpp + ushort *p2; + int p2inc = xi->bytes_per_line/sizeof(ushort); + ushort *newerbits = (ushort *)malloc(xi->bytes_per_line * h); + Q_CHECK_PTR(newerbits); + if (!newerbits) // no memory + return; + uchar* p = newbits; + for (int y = 0; y < h; y++) { // OOPS: Do right byte order!! + p2 = newerbits + p2inc*y; + for (int x = 0; x < w; x++) + *p2++ = *p++; + } + free(newbits); + newbits = (uchar *)newerbits; + } else if (xi->bits_per_pixel != 8) { + qWarning("QPixmap::fromImage: Display not supported " + "(bpp=%d)", xi->bits_per_pixel); + } + xi->data = (char *)newbits; + } + + hd = (Qt::HANDLE)XCreatePixmap(X11->display, + RootWindow(X11->display, xinfo.screen()), + w, h, dd); + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + d = dd; + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(X11->display, PictStandardA1) + : XRenderFindVisualFormat(X11->display, (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(X11->display, hd, format, 0, 0); + } +#endif + + if (alphaCheck.hasAlpha()) { + QBitmap m = QBitmap::fromImage(image.createAlphaMask(flags)); + setMask(m); + } +} + +Qt::HANDLE QX11PixmapData::createBitmapFromImage(const QImage &image) +{ + QImage img = image.convertToFormat(QImage::Format_MonoLSB); + const QRgb c0 = QColor(Qt::black).rgb(); + const QRgb c1 = QColor(Qt::white).rgb(); + if (img.color(0) == c0 && img.color(1) == c1) { + img.invertPixels(); + img.setColor(0, c1); + img.setColor(1, c0); + } + + char *bits; + uchar *tmp_bits; + int w = img.width(); + int h = img.height(); + int bpl = (w + 7) / 8; + int ibpl = img.bytesPerLine(); + if (bpl != ibpl) { + tmp_bits = new uchar[bpl*h]; + bits = (char *)tmp_bits; + uchar *p, *b; + int y; + b = tmp_bits; + p = img.scanLine(0); + for (y = 0; y < h; y++) { + memcpy(b, p, bpl); + b += bpl; + p += ibpl; + } + } else { + bits = (char *)img.bits(); + tmp_bits = 0; + } + Qt::HANDLE hd = (Qt::HANDLE)XCreateBitmapFromData(X11->display, + QX11Info::appRootWindow(), + bits, w, h); + if (tmp_bits) // Avoid purify complaint + delete [] tmp_bits; + return hd; +} + +void QX11PixmapData::bitmapFromImage(const QImage &image) +{ + w = image.width(); + h = image.height(); + d = 1; + is_null = (w <= 0 || h <= 0); + hd = createBitmapFromImage(image); +#ifndef QT_NO_XRENDER + if (X11->use_xrender) + picture = XRenderCreatePicture(X11->display, hd, + XRenderFindStandardFormat(X11->display, PictStandardA1), 0, 0); +#endif // QT_NO_XRENDER +} + +void QX11PixmapData::fill(const QColor &fillColor) +{ + if (fillColor.alpha() != 255) { +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + if (!picture || d != 32) + convertToARGB32(/*preserveContents = */false); + + ::Picture src = X11->getSolidFill(xinfo.screen(), fillColor); + XRenderComposite(X11->display, PictOpSrc, src, 0, picture, + 0, 0, width(), height(), + 0, 0, width(), height()); + } else +#endif + { + QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied); + im.fill(PREMUL(fillColor.rgba())); + release(); + fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither); + } + return; + } + + GC gc = XCreateGC(X11->display, hd, 0, 0); + if (depth() == 1) { + XSetForeground(X11->display, gc, qGray(fillColor.rgb()) > 127 ? 0 : 1); + } else if (X11->use_xrender && d >= 24) { + XSetForeground(X11->display, gc, fillColor.rgba()); + } else { + XSetForeground(X11->display, gc, + QColormap::instance(xinfo.screen()).pixel(fillColor)); + } + XFillRectangle(X11->display, hd, gc, 0, 0, width(), height()); + XFreeGC(X11->display, gc); +} + +QX11PixmapData::~QX11PixmapData() +{ + // Cleanup hooks have to be called before the handles are freed + if (is_cached) { + QImagePixmapCleanupHooks::executePixmapDataDestructionHooks(this); + is_cached = false; + } + + release(); +} + +void QX11PixmapData::release() +{ + delete pengine; + pengine = 0; + + if (!X11) { + // At this point, the X server will already have freed our resources, + // so there is nothing to do. + return; + } + + if (x11_mask) { +#ifndef QT_NO_XRENDER + if (mask_picture) + XRenderFreePicture(X11->display, mask_picture); + mask_picture = 0; +#endif + XFreePixmap(X11->display, x11_mask); + x11_mask = 0; + } + + if (hd) { +#ifndef QT_NO_XRENDER + if (picture) { + XRenderFreePicture(X11->display, picture); + picture = 0; + } +#endif // QT_NO_XRENDER + + if (hd2) { + XFreePixmap(xinfo.display(), hd2); + hd2 = 0; + } + if (!(flags & Readonly)) + XFreePixmap(xinfo.display(), hd); + hd = 0; + } +} + +QPixmap QX11PixmapData::alphaChannel() const +{ + if (!hasAlphaChannel()) { + QPixmap pm(w, h); + pm.fill(Qt::white); + return pm; + } + QImage im(toImage()); + return QPixmap::fromImage(im.alphaChannel(), Qt::OrderedDither); +} + +void QX11PixmapData::setAlphaChannel(const QPixmap &alpha) +{ + QImage image(toImage()); + image.setAlphaChannel(alpha.toImage()); + release(); + fromImage(image, Qt::OrderedDither | Qt::OrderedAlphaDither); +} + + +QBitmap QX11PixmapData::mask() const +{ + QBitmap mask; +#ifndef QT_NO_XRENDER + if (picture && d == 32) { + // #### slow - there must be a better way.. + mask = QBitmap::fromImage(toImage().createAlphaMask()); + } else +#endif + if (d == 1) { + QX11PixmapData *that = const_cast(this); + mask = QPixmap(that); + } else { + mask = mask_to_bitmap(xinfo.screen()); + } + return mask; +} + +/*! + Sets a mask bitmap. + + The \a newmask bitmap defines the clip mask for this pixmap. Every + pixel in \a newmask corresponds to a pixel in this pixmap. Pixel + value 1 means opaque and pixel value 0 means transparent. The mask + must have the same size as this pixmap. + + \warning Setting the mask on a pixmap will cause any alpha channel + data to be cleared. For example: + \snippet doc/src/snippets/image/image.cpp 2 + Now, alpha and alphacopy are visually different. + + Setting a null mask resets the mask. + + The effect of this function is undefined when the pixmap is being + painted on. + + \sa mask(), {QPixmap#Pixmap Transformations}{Pixmap + Transformations}, QBitmap +*/ +void QX11PixmapData::setMask(const QBitmap &newmask) +{ + if (newmask.isNull()) { // clear mask +#ifndef QT_NO_XRENDER + if (picture && d == 32) { + QX11PixmapData newData(pixelType()); + newData.resize(w, h); + newData.fill(Qt::black); + XRenderComposite(X11->display, PictOpOver, + picture, 0, newData.picture, + 0, 0, 0, 0, 0, 0, w, h); + release(); + *this = newData; + // the new QX11PixmapData object isn't referenced yet, so + // ref it + ref.ref(); + + // the below is to make sure the QX11PixmapData destructor + // doesn't delete our newly created render picture + newData.hd = 0; + newData.x11_mask = 0; + newData.picture = 0; + newData.mask_picture = 0; + newData.hd2 = 0; + } else +#endif + if (x11_mask) { +#ifndef QT_NO_XRENDER + if (picture) { + XRenderPictureAttributes attrs; + attrs.alpha_map = 0; + XRenderChangePicture(X11->display, picture, CPAlphaMap, + &attrs); + } + if (mask_picture) + XRenderFreePicture(X11->display, mask_picture); + mask_picture = 0; +#endif + XFreePixmap(X11->display, x11_mask); + x11_mask = 0; + } + return; + } + +#ifndef QT_NO_XRENDER + if (picture && d == 32) { + XRenderComposite(X11->display, PictOpSrc, + picture, newmask.x11PictureHandle(), + picture, 0, 0, 0, 0, 0, 0, w, h); + } else +#endif + if (depth() == 1) { + XGCValues vals; + vals.function = GXand; + GC gc = XCreateGC(X11->display, hd, GCFunction, &vals); + XCopyArea(X11->display, newmask.handle(), hd, gc, 0, 0, + width(), height(), 0, 0); + XFreeGC(X11->display, gc); + } else { + // ##### should or the masks together + if (x11_mask) { + XFreePixmap(X11->display, x11_mask); +#ifndef QT_NO_XRENDER + if (mask_picture) + XRenderFreePicture(X11->display, mask_picture); +#endif + } + x11_mask = QX11PixmapData::bitmap_to_mask(newmask, xinfo.screen()); +#ifndef QT_NO_XRENDER + if (picture) { + mask_picture = XRenderCreatePicture(X11->display, x11_mask, + XRenderFindStandardFormat(X11->display, PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = mask_picture; + XRenderChangePicture(X11->display, picture, CPAlphaMap, &attrs); + } +#endif + } +} + +int QX11PixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + switch (metric) { + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmNumColors: + return 1 << d; + case QPaintDevice::PdmDepth: + return d; + case QPaintDevice::PdmWidthMM: { + const int screen = xinfo.screen(); + const int mm = DisplayWidthMM(X11->display, screen) * w + / DisplayWidth(X11->display, screen); + return mm; + } + case QPaintDevice::PdmHeightMM: { + const int screen = xinfo.screen(); + const int mm = (DisplayHeightMM(X11->display, screen) * h) + / DisplayHeight(X11->display, screen); + return mm; + } + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmPhysicalDpiX: + return QX11Info::appDpiX(xinfo.screen()); + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiY: + return QX11Info::appDpiY(xinfo.screen()); + default: + qWarning("QX11PixmapData::metric(): Invalid metric"); + return 0; + } +} + +struct QXImageWrapper +{ + XImage *xi; +}; + +bool QX11PixmapData::canTakeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + // ARGB32_Premultiplied + if (picture && depth() == 32) + return true; + + Visual *visual = (Visual *)xinfo.visual(); + + // RGB32 + if (depth() == 24 && xi->bits_per_pixel == 32 && visual->red_mask == 0xff0000 + && visual->green_mask == 0xff00 && visual->blue_mask == 0xff) + return true; + + // RGB16 + if (depth() == 16 && xi->bits_per_pixel == 16 && visual->red_mask == 0xf800 + && visual->green_mask == 0x7e0 && visual->blue_mask == 0x1f) + return true; + + return false; +} + +QImage QX11PixmapData::takeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + QImage::Format format = QImage::Format_ARGB32_Premultiplied; + if (depth() == 24) + format = QImage::Format_RGB32; + else if (depth() == 16) + format = QImage::Format_RGB16; + + QImage image((uchar *)xi->data, xi->width, xi->height, xi->bytes_per_line, format); + // take ownership + image.data_ptr()->own_data = true; + xi->data = 0; + + // we may have to swap the byte order + if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst) + || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) + { + for (int i=0; i < image.height(); i++) { + if (depth() == 16) { + ushort *p = (ushort*)image.scanLine(i); + ushort *end = p + image.width(); + while (p < end) { + *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff); + p++; + } + } else { + uint *p = (uint*)image.scanLine(i); + uint *end = p + image.width(); + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } + } + } + + // fix-up alpha channel + if (format == QImage::Format_RGB32) { + QRgb *p = (QRgb *)image.bits(); + for (int y = 0; y < xi->height; ++y) { + for (int x = 0; x < xi->width; ++x) + p[x] |= 0xff000000; + p += xi->bytes_per_line / 4; + } + } + + XDestroyImage(xi); + return image; +} + +QImage QX11PixmapData::toImage(const QRect &rect) const +{ + QXImageWrapper xiWrapper; + xiWrapper.xi = XGetImage(X11->display, hd, rect.x(), rect.y(), rect.width(), rect.height(), + AllPlanes, (depth() == 1) ? XYPixmap : ZPixmap); + + Q_CHECK_PTR(xiWrapper.xi); + if (!xiWrapper.xi) + return QImage(); + + if (!x11_mask && canTakeQImageFromXImage(xiWrapper)) + return takeQImageFromXImage(xiWrapper); + + QImage image = toImage(xiWrapper, rect); + qSafeXDestroyImage(xiWrapper.xi); + return image; +} + +/*! + Converts the pixmap to a QImage. Returns a null image if the + conversion fails. + + If the pixmap has 1-bit depth, the returned image will also be 1 + bit deep. If the pixmap has 2- to 8-bit depth, the returned image + has 8-bit depth. If the pixmap has greater than 8-bit depth, the + returned image has 32-bit depth. + + Note that for the moment, alpha masks on monochrome images are + ignored. + + \sa fromImage(), {QImage#Image Formats}{Image Formats} +*/ + +QImage QX11PixmapData::toImage() const +{ + return toImage(QRect(0, 0, w, h)); +} + +QImage QX11PixmapData::toImage(const QXImageWrapper &xiWrapper, const QRect &rect) const +{ + XImage *xi = xiWrapper.xi; + + int d = depth(); + Visual *visual = (Visual *)xinfo.visual(); + bool trucol = (visual->c_class >= TrueColor) && d > 1; + + QImage::Format format = QImage::Format_Mono; + if (d > 1 && d <= 8) { + d = 8; + format = QImage::Format_Indexed8; + } + // we could run into the situation where d == 8 AND trucol is true, which can + // cause problems when converting to and from images. in this case, always treat + // the depth as 32... + if (d > 8 || trucol) { + d = 32; + format = QImage::Format_RGB32; + } + + if (d == 1 && xi->bitmap_bit_order == LSBFirst) + format = QImage::Format_MonoLSB; + if (x11_mask && format == QImage::Format_RGB32) + format = QImage::Format_ARGB32; + + QImage image(xi->width, xi->height, format); + if (image.isNull()) // could not create image + return image; + + QImage alpha; + if (x11_mask) { + if (rect.contains(QRect(0, 0, w, h))) + alpha = mask().toImage(); + else + alpha = mask().toImage().copy(rect); + } + bool ale = alpha.format() == QImage::Format_MonoLSB; + + if (trucol) { // truecolor + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + + const uint red_bits = n_bits(red_mask); + const uint green_bits = n_bits(green_mask); + const uint blue_bits = n_bits(blue_mask); + + static uint red_table_bits = 0; + static uint green_table_bits = 0; + static uint blue_table_bits = 0; + + if (red_bits < 8 && red_table_bits != red_bits) { + build_scale_table(&red_scale_table, red_bits); + red_table_bits = red_bits; + } + if (blue_bits < 8 && blue_table_bits != blue_bits) { + build_scale_table(&blue_scale_table, blue_bits); + blue_table_bits = blue_bits; + } + if (green_bits < 8 && green_table_bits != green_bits) { + build_scale_table(&green_scale_table, green_bits); + green_table_bits = green_bits; + } + + int r, g, b; + + QRgb *dst; + uchar *src; + uint pixel; + int bppc = xi->bits_per_pixel; + + if (bppc > 8 && xi->byte_order == LSBFirst) + bppc++; + + for (int y = 0; y < xi->height; ++y) { + uchar* asrc = x11_mask ? alpha.scanLine(y) : 0; + dst = (QRgb *)image.scanLine(y); + src = (uchar *)xi->data + xi->bytes_per_line*y; + for (int x = 0; x < xi->width; x++) { + switch (bppc) { + case 8: + pixel = *src++; + break; + case 16: // 16 bit MSB + pixel = src[1] | (uint)src[0] << 8; + src += 2; + break; + case 17: // 16 bit LSB + pixel = src[0] | (uint)src[1] << 8; + src += 2; + break; + case 24: // 24 bit MSB + pixel = src[2] | (uint)src[1] << 8 | (uint)src[0] << 16; + src += 3; + break; + case 25: // 24 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16; + src += 3; + break; + case 32: // 32 bit MSB + pixel = src[3] | (uint)src[2] << 8 | (uint)src[1] << 16 | (uint)src[0] << 24; + src += 4; + break; + case 33: // 32 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16 | (uint)src[3] << 24; + src += 4; + break; + default: // should not really happen + x = xi->width; // leave loop + y = xi->height; + pixel = 0; // eliminate compiler warning + qWarning("QPixmap::convertToImage: Invalid depth %d", bppc); + } + if (red_shift > 0) + r = (pixel & red_mask) >> red_shift; + else + r = (pixel & red_mask) << -red_shift; + if (green_shift > 0) + g = (pixel & green_mask) >> green_shift; + else + g = (pixel & green_mask) << -green_shift; + if (blue_shift > 0) + b = (pixel & blue_mask) >> blue_shift; + else + b = (pixel & blue_mask) << -blue_shift; + + if (red_bits < 8) + r = red_scale_table[r]; + if (green_bits < 8) + g = green_scale_table[g]; + if (blue_bits < 8) + b = blue_scale_table[b]; + + if (x11_mask) { + if (ale) { + *dst++ = (asrc[x >> 3] & (1 << (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } else { + *dst++ = (asrc[x >> 3] & (0x80 >> (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } + } else { + *dst++ = qRgb(r, g, b); + } + } + } + } else if (xi->bits_per_pixel == d) { // compatible depth + char *xidata = xi->data; // copy each scanline + int bpl = qMin(image.bytesPerLine(),xi->bytes_per_line); + for (int y=0; yheight; y++) { + memcpy(image.scanLine(y), xidata, bpl); + xidata += xi->bytes_per_line; + } + } else { + /* Typically 2 or 4 bits display depth */ + qWarning("QPixmap::convertToImage: Display not supported (bpp=%d)", + xi->bits_per_pixel); + return QImage(); + } + + if (d == 1) { // bitmap + image.setColorCount(2); + image.setColor(0, qRgb(255,255,255)); + image.setColor(1, qRgb(0,0,0)); + } else if (!trucol) { // pixmap with colormap + register uchar *p; + uchar *end; + uchar use[256]; // pixel-in-use table + uchar pix[256]; // pixel translation table + int ncols, bpl; + memset(use, 0, 256); + memset(pix, 0, 256); + bpl = image.bytesPerLine(); + + if (x11_mask) { // which pixels are used? + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (1 << (x & 7))) + use[*p] = 1; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (0x80 >> (x & 7))) + use[*p] = 1; + ++p; + } + } + } + } else { + for (int i = 0; i < xi->height; i++) { + p = image.scanLine(i); + end = p + bpl; + while (p < end) + use[*p++] = 1; + } + } + ncols = 0; + for (int i = 0; i < 256; i++) { // build translation table + if (use[i]) + pix[i] = ncols++; + } + for (int i = 0; i < xi->height; i++) { // translate pixels + p = image.scanLine(i); + end = p + bpl; + while (p < end) { + *p = pix[*p]; + p++; + } + } + if (x11_mask) { + int trans; + if (ncols < 256) { + trans = ncols++; + image.setColorCount(ncols); // create color table + image.setColor(trans, 0x00000000); + } else { + image.setColorCount(ncols); // create color table + // oh dear... no spare "transparent" pixel. + // use first pixel in image (as good as any). + trans = image.scanLine(0)[0]; + } + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (x & 7)))) + *p = trans; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (7 -(x & 7))))) + *p = trans; + ++p; + } + } + } + } else { + image.setColorCount(ncols); // create color table + } + QVector colors = QColormap::instance(xinfo.screen()).colormap(); + int j = 0; + for (int i=0; i= QTransform::TxProject) { + QImage image = toImage(); + return QPixmap::fromImage(image.transformed(transform, mode)); + } + + uint w = 0; + uint h = 0; // size of target pixmap + uint ws, hs; // size of source pixmap + uchar *dptr; // data in target pixmap + uint dbpl, dbytes; // bytes per line/bytes total + uchar *sptr; // data in original pixmap + int sbpl; // bytes per line in original + int bpp; // bits per pixel + bool depth1 = depth() == 1; + Display *dpy = X11->display; + + ws = width(); + hs = height(); + + QTransform mat(transform.m11(), transform.m12(), transform.m13(), + transform.m21(), transform.m22(), transform.m23(), + 0., 0., 1); + bool complex_xform = false; + qreal scaledWidth; + qreal scaledHeight; + + if (mat.type() <= QTransform::TxScale) { + scaledHeight = qAbs(mat.m22()) * hs + 0.9999; + scaledWidth = qAbs(mat.m11()) * ws + 0.9999; + h = qAbs(int(scaledHeight)); + w = qAbs(int(scaledWidth)); + } else { // rotation or shearing + QPolygonF a(QRectF(0, 0, ws, hs)); + a = mat.map(a); + QRect r = a.boundingRect().toAlignedRect(); + w = r.width(); + h = r.height(); + scaledWidth = w; + scaledHeight = h; + complex_xform = true; + } + mat = QPixmap::trueMatrix(mat, ws, hs); // true matrix + + bool invertible; + mat = mat.inverted(&invertible); // invert matrix + + if (h == 0 || w == 0 || !invertible + || qAbs(scaledWidth) >= 32768 || qAbs(scaledHeight) >= 32768 ) + // error, return null pixmap + return QPixmap(); + +#if defined(QT_MITSHM) + static bool try_once = true; + if (try_once) { + try_once = false; + if (!xshminit) + qt_create_mitshm_buffer(this, 800, 600); + } + + bool use_mitshm = xshmimg && !depth1 && + xshmimg->width >= w && xshmimg->height >= h; +#endif + XImage *xi = XGetImage(X11->display, handle(), 0, 0, ws, hs, AllPlanes, + depth1 ? XYPixmap : ZPixmap); + + if (!xi) + return QPixmap(); + + sbpl = xi->bytes_per_line; + sptr = (uchar *)xi->data; + bpp = xi->bits_per_pixel; + + if (depth1) + dbpl = (w+7)/8; + else + dbpl = ((w*bpp+31)/32)*4; + dbytes = dbpl*h; + +#if defined(QT_MITSHM) + if (use_mitshm) { + dptr = (uchar *)xshmimg->data; + uchar fillbyte = bpp == 8 ? white.pixel() : 0xff; + for (int y=0; ybytes_per_line, fillbyte, dbpl); + } else { +#endif + dptr = (uchar *)malloc(dbytes); // create buffer for bits + Q_CHECK_PTR(dptr); + if (depth1) // fill with zeros + memset(dptr, 0, dbytes); + else if (bpp == 8) // fill with background color + memset(dptr, WhitePixel(X11->display, xinfo.screen()), dbytes); + else + memset(dptr, 0, dbytes); +#if defined(QT_MITSHM) + } +#endif + + // #define QT_DEBUG_XIMAGE +#if defined(QT_DEBUG_XIMAGE) + qDebug("----IMAGE--INFO--------------"); + qDebug("width............. %d", xi->width); + qDebug("height............ %d", xi->height); + qDebug("xoffset........... %d", xi->xoffset); + qDebug("format............ %d", xi->format); + qDebug("byte order........ %d", xi->byte_order); + qDebug("bitmap unit....... %d", xi->bitmap_unit); + qDebug("bitmap bit order.. %d", xi->bitmap_bit_order); + qDebug("depth............. %d", xi->depth); + qDebug("bytes per line.... %d", xi->bytes_per_line); + qDebug("bits per pixel.... %d", xi->bits_per_pixel); +#endif + + int type; + if (xi->bitmap_bit_order == MSBFirst) + type = QT_XFORM_TYPE_MSBFIRST; + else + type = QT_XFORM_TYPE_LSBFIRST; + int xbpl, p_inc; + if (depth1) { + xbpl = (w+7)/8; + p_inc = dbpl - xbpl; + } else { + xbpl = (w*bpp)/8; + p_inc = dbpl - xbpl; +#if defined(QT_MITSHM) + if (use_mitshm) + p_inc = xshmimg->bytes_per_line - xbpl; +#endif + } + + if (!qt_xForm_helper(mat, xi->xoffset, type, bpp, dptr, xbpl, p_inc, h, sptr, sbpl, ws, hs)){ + qWarning("QPixmap::transform: display not supported (bpp=%d)",bpp); + QPixmap pm; + return pm; + } + + qSafeXDestroyImage(xi); + + if (depth1) { // mono bitmap + QBitmap bm = QBitmap::fromData(QSize(w, h), dptr, + BitmapBitOrder(X11->display) == MSBFirst + ? QImage::Format_Mono + : QImage::Format_MonoLSB); + free(dptr); + return bm; + } else { // color pixmap + QX11PixmapData *x11Data = new QX11PixmapData(QPixmapData::PixmapType); + QPixmap pm(x11Data); + x11Data->flags &= ~QX11PixmapData::Uninitialized; + x11Data->xinfo = xinfo; + x11Data->d = d; + x11Data->w = w; + x11Data->h = h; + x11Data->is_null = (w <= 0 || h <= 0); + x11Data->hd = (Qt::HANDLE)XCreatePixmap(X11->display, + RootWindow(X11->display, xinfo.screen()), + w, h, d); + x11Data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + XRenderPictFormat *format = x11Data->d == 32 + ? XRenderFindStandardFormat(X11->display, PictStandardARGB32) + : XRenderFindVisualFormat(X11->display, (Visual *) x11Data->xinfo.visual()); + x11Data->picture = XRenderCreatePicture(X11->display, x11Data->hd, format, 0, 0); + } +#endif // QT_NO_XRENDER + + GC gc = XCreateGC(X11->display, x11Data->hd, 0, 0); +#if defined(QT_MITSHM) + if (use_mitshm) { + XCopyArea(dpy, xshmpm, x11Data->hd, gc, 0, 0, w, h, 0, 0); + } else +#endif + { + xi = XCreateImage(dpy, (Visual*)x11Data->xinfo.visual(), + x11Data->d, + ZPixmap, 0, (char *)dptr, w, h, 32, 0); + XPutImage(dpy, pm.handle(), gc, xi, 0, 0, 0, 0, w, h); + qSafeXDestroyImage(xi); + } + XFreeGC(X11->display, gc); + + if (x11_mask) { // xform mask, too + pm.setMask(mask_to_bitmap(xinfo.screen()).transformed(transform)); + } else if (d != 32 && complex_xform) { // need a mask! + QBitmap mask(ws, hs); + mask.fill(Qt::color1); + pm.setMask(mask.transformed(transform)); + } + return pm; + } +} + +int QPixmap::x11SetDefaultScreen(int screen) +{ + int old = defaultScreen; + defaultScreen = screen; + return old; +} + +void QPixmap::x11SetScreen(int screen) +{ + if (paintingActive()) { + qWarning("QPixmap::x11SetScreen(): Cannot change screens during painting"); + return; + } + + if (isNull()) + return; + + if (data->classId() != QPixmapData::X11Class) + return; + + if (screen < 0) + screen = QX11Info::appScreen(); + + QX11PixmapData *x11Data = static_cast(data.data()); + if (screen == x11Data->xinfo.screen()) + return; // nothing to do + + if (isNull()) { + QX11InfoData* xd = x11Data->xinfo.getX11Data(true); + xd->screen = screen; + xd->depth = QX11Info::appDepth(screen); + xd->cells = QX11Info::appCells(screen); + xd->colormap = QX11Info::appColormap(screen); + xd->defaultColormap = QX11Info::appDefaultColormap(screen); + xd->visual = (Visual *)QX11Info::appVisual(screen); + xd->defaultVisual = QX11Info::appDefaultVisual(screen); + x11Data->xinfo.setX11Data(xd); + return; + } +#if 0 + qDebug("QPixmap::x11SetScreen for %p from %d to %d. Size is %d/%d", x11Data, x11Data->xinfo.screen(), screen, width(), height()); +#endif + + x11SetDefaultScreen(screen); + *this = qt_toX11Pixmap(toImage()); +} + +QPixmap QPixmap::grabWindow(WId window, int x, int y, int w, int h) +{ + if (w == 0 || h == 0) + return QPixmap(); + + Display *dpy = X11->display; + XWindowAttributes window_attr; + if (!XGetWindowAttributes(dpy, window, &window_attr)) + return QPixmap(); + + if (w < 0) + w = window_attr.width - x; + if (h < 0) + h = window_attr.height - y; + + // determine the screen + int scr; + for (scr = 0; scr < ScreenCount(dpy); ++scr) { + if (window_attr.root == RootWindow(dpy, scr)) // found it + break; + } + if (scr >= ScreenCount(dpy)) // sanity check + return QPixmap(); + + + // get the depth of the root window + XWindowAttributes root_attr; + if (!XGetWindowAttributes(dpy, window_attr.root, &root_attr)) + return QPixmap(); + + if (window_attr.depth == root_attr.depth) { + // if the depth of the specified window and the root window are the + // same, grab pixels from the root window (so that we get the any + // overlapping windows and window manager frames) + + // map x and y to the root window + WId unused; + if (!XTranslateCoordinates(dpy, window, window_attr.root, x, y, + &x, &y, &unused)) + return QPixmap(); + + window = window_attr.root; + window_attr = root_attr; + } + + QX11PixmapData *data = new QX11PixmapData(QPixmapData::PixmapType); + + void qt_x11_getX11InfoForWindow(QX11Info * xinfo, const XWindowAttributes &a); + qt_x11_getX11InfoForWindow(&data->xinfo,window_attr); + + data->resize(w, h); + + QPixmap pm(data); + + data->flags &= ~QX11PixmapData::Uninitialized; + pm.x11SetScreen(scr); + + GC gc = XCreateGC(dpy, pm.handle(), 0, 0); + XSetSubwindowMode(dpy, gc, IncludeInferiors); + XCopyArea(dpy, window, pm.handle(), gc, x, y, w, h, 0, 0); + XFreeGC(dpy, gc); + + return pm; +} + +bool QX11PixmapData::hasAlphaChannel() const +{ + return d == 32; +} + +const QX11Info &QPixmap::x11Info() const +{ + if (data && data->classId() == QPixmapData::X11Class) + return static_cast(data.data())->xinfo; + else { + static QX11Info nullX11Info; + return nullX11Info; + } +} + +#if !defined(QT_NO_XRENDER) +static XRenderPictFormat *qt_renderformat_for_depth(const QX11Info &xinfo, int depth) +{ + if (depth == 1) + return XRenderFindStandardFormat(X11->display, PictStandardA1); + else if (depth == 32) + return XRenderFindStandardFormat(X11->display, PictStandardARGB32); + else + return XRenderFindVisualFormat(X11->display, (Visual *)xinfo.visual()); +} +#endif + +QPaintEngine* QX11PixmapData::paintEngine() const +{ + QX11PixmapData *that = const_cast(this); + + if ((flags & Readonly) && share_mode == QPixmap::ImplicitlyShared) { + // if someone wants to draw onto us, copy the shared contents + // and turn it into a fully fledged QPixmap + ::Pixmap hd_copy = XCreatePixmap(X11->display, RootWindow(X11->display, xinfo.screen()), + w, h, d); +#if !defined(QT_NO_XRENDER) + XRenderPictFormat *format = qt_renderformat_for_depth(xinfo, d); + ::Picture picture_copy = XRenderCreatePicture(X11->display, hd_copy, format, 0, 0); + + if (picture && d == 32) { + XRenderComposite(X11->display, PictOpSrc, picture, 0, picture_copy, + 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(X11->display, picture); + that->picture = picture_copy; + } else +#endif + { + GC gc = XCreateGC(X11->display, hd_copy, 0, 0); + XCopyArea(X11->display, hd, hd_copy, gc, 0, 0, w, h, 0, 0); + XFreeGC(X11->display, gc); + } + that->hd = hd_copy; + that->flags &= ~QX11PixmapData::Readonly; + } + + if (!that->pengine) + that->pengine = new QX11PaintEngine; + return that->pengine; +} + +Qt::HANDLE QPixmap::x11PictureHandle() const +{ +#ifndef QT_NO_XRENDER + if (data && data->classId() == QPixmapData::X11Class) + return static_cast(data.data())->picture; + else + return 0; +#else + return 0; +#endif // QT_NO_XRENDER +} + +Qt::HANDLE QX11PixmapData::x11ConvertToDefaultDepth() +{ +#ifndef QT_NO_XRENDER + if (d == QX11Info::appDepth() || !X11->use_xrender) + return hd; + if (!hd2) { + hd2 = XCreatePixmap(xinfo.display(), hd, w, h, QX11Info::appDepth()); + XRenderPictFormat *format = XRenderFindVisualFormat(xinfo.display(), + (Visual*) xinfo.visual()); + Picture pic = XRenderCreatePicture(xinfo.display(), hd2, format, 0, 0); + XRenderComposite(xinfo.display(), PictOpSrc, picture, + XNone, pic, 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(xinfo.display(), pic); + } + return hd2; +#else + return hd; +#endif +} + +void QX11PixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + if (data->pixelType() == BitmapType) { + fromImage(data->toImage().copy(rect), Qt::AutoColor); + return; + } + + const QX11PixmapData *x11Data = static_cast(data); + + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + flags &= ~Uninitialized; + xinfo = x11Data->xinfo; + d = x11Data->d; + w = rect.width(); + h = rect.height(); + is_null = (w <= 0 || h <= 0); + hd = (Qt::HANDLE)XCreatePixmap(X11->display, + RootWindow(X11->display, x11Data->xinfo.screen()), + w, h, d); +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + XRenderPictFormat *format = d == 32 + ? XRenderFindStandardFormat(X11->display, PictStandardARGB32) + : XRenderFindVisualFormat(X11->display, (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(X11->display, hd, format, 0, 0); + } +#endif // QT_NO_XRENDER + if (x11Data->x11_mask) { + x11_mask = XCreatePixmap(X11->display, hd, w, h, 1); +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + mask_picture = XRenderCreatePicture(X11->display, x11_mask, + XRenderFindStandardFormat(X11->display, PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = x11Data->mask_picture; + XRenderChangePicture(X11->display, x11Data->picture, CPAlphaMap, &attrs); + } +#endif + } + +#if !defined(QT_NO_XRENDER) + if (x11Data->picture && x11Data->d == 32) { + XRenderComposite(X11->display, PictOpSrc, + x11Data->picture, 0, picture, + rect.x(), rect.y(), 0, 0, 0, 0, w, h); + } else +#endif + { + GC gc = XCreateGC(X11->display, hd, 0, 0); + XCopyArea(X11->display, x11Data->hd, hd, gc, + rect.x(), rect.y(), w, h, 0, 0); + if (x11Data->x11_mask) { + GC monogc = XCreateGC(X11->display, x11_mask, 0, 0); + XCopyArea(X11->display, x11Data->x11_mask, x11_mask, monogc, + rect.x(), rect.y(), w, h, 0, 0); + XFreeGC(X11->display, monogc); + } + XFreeGC(X11->display, gc); + } +} + +bool QX11PixmapData::scroll(int dx, int dy, const QRect &rect) +{ + GC gc = XCreateGC(X11->display, hd, 0, 0); + XCopyArea(X11->display, hd, hd, gc, + rect.left(), rect.top(), rect.width(), rect.height(), + rect.left() + dx, rect.top() + dy); + XFreeGC(X11->display, gc); + return true; +} + +#if !defined(QT_NO_XRENDER) +void QX11PixmapData::convertToARGB32(bool preserveContents) +{ + if (!X11->use_xrender) + return; + + // Q_ASSERT(count == 1); + if ((flags & Readonly) && share_mode == QPixmap::ExplicitlyShared) + return; + + Pixmap pm = XCreatePixmap(X11->display, RootWindow(X11->display, xinfo.screen()), + w, h, 32); + Picture p = XRenderCreatePicture(X11->display, pm, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), 0, 0); + if (picture) { + if (preserveContents) + XRenderComposite(X11->display, PictOpSrc, picture, 0, p, 0, 0, 0, 0, 0, 0, w, h); + if (!(flags & Readonly)) + XRenderFreePicture(X11->display, picture); + } + if (hd && !(flags & Readonly)) + XFreePixmap(X11->display, hd); + if (x11_mask) { + XFreePixmap(X11->display, x11_mask); + if (mask_picture) + XRenderFreePicture(X11->display, mask_picture); + x11_mask = 0; + mask_picture = 0; + } + hd = pm; + picture = p; + d = 32; +} +#endif + +QPixmap QPixmap::fromX11Pixmap(Qt::HANDLE pixmap, QPixmap::ShareMode mode) +{ + Window root; + int x; + int y; + uint width; + uint height; + uint border_width; + uint depth; + XWindowAttributes win_attribs; + int num_screens = ScreenCount(X11->display); + int screen = 0; + + XGetGeometry(X11->display, pixmap, &root, &x, &y, &width, &height, &border_width, &depth); + XGetWindowAttributes(X11->display, root, &win_attribs); + + for (; screen < num_screens; ++screen) { + if (win_attribs.screen == ScreenOfDisplay(X11->display, screen)) + break; + } + + QX11PixmapData *data = new QX11PixmapData(depth == 1 ? QPixmapData::BitmapType : QPixmapData::PixmapType); + data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + data->flags = QX11PixmapData::Readonly; + data->share_mode = mode; + data->w = width; + data->h = height; + data->is_null = (width <= 0 || height <= 0); + data->d = depth; + data->hd = pixmap; + + if (defaultScreen >= 0 && defaultScreen != screen) { + QX11InfoData* xd = data->xinfo.getX11Data(true); + xd->screen = defaultScreen; + xd->depth = QX11Info::appDepth(xd->screen); + xd->cells = QX11Info::appCells(xd->screen); + xd->colormap = QX11Info::appColormap(xd->screen); + xd->defaultColormap = QX11Info::appDefaultColormap(xd->screen); + xd->visual = (Visual *)QX11Info::appVisual(xd->screen); + xd->defaultVisual = QX11Info::appDefaultVisual(xd->screen); + data->xinfo.setX11Data(xd); + } + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + XRenderPictFormat *format = qt_renderformat_for_depth(data->xinfo, depth); + data->picture = XRenderCreatePicture(X11->display, data->hd, format, 0, 0); + } +#endif // QT_NO_XRENDER + + return QPixmap(data); +} + + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_x11_p.h b/src/gui/image/qpixmap_x11_p.h new file mode 100644 index 0000000000..eb8e5819ad --- /dev/null +++ b/src/gui/image/qpixmap_x11_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPDATA_X11_P_H +#define QPIXMAPDATA_X11_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 +#include + +#include "QtGui/qx11info_x11.h" + +QT_BEGIN_NAMESPACE + +class QX11PaintEngine; + +struct QXImageWrapper; + +class Q_GUI_EXPORT QX11PixmapData : public QPixmapData +{ +public: + QX11PixmapData(PixelType type); +// QX11PixmapData(PixelType type, int width, int height); +// QX11PixmapData(PixelType type, const QImage &image, +// Qt::ImageConversionFlags flags); + ~QX11PixmapData(); + + QPixmapData *createCompatiblePixmapData() const; + + void resize(int width, int height); + void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + void copy(const QPixmapData *data, const QRect &rect); + bool scroll(int dx, int dy, const QRect &rect); + + void fill(const QColor &color); + QBitmap mask() const; + void setMask(const QBitmap &mask); + bool hasAlphaChannel() const; + void setAlphaChannel(const QPixmap &alphaChannel); + QPixmap alphaChannel() const; + QPixmap transformed(const QTransform &transform, + Qt::TransformationMode mode) const; + QImage toImage() const; + QImage toImage(const QRect &rect) const; + QPaintEngine* paintEngine() const; + + Qt::HANDLE handle() const { return hd; } + Qt::HANDLE x11ConvertToDefaultDepth(); + + static Qt::HANDLE createBitmapFromImage(const QImage &image); + + void* gl_surface; +#ifndef QT_NO_XRENDER + void convertToARGB32(bool preserveContents = true); +#endif + +protected: + int metric(QPaintDevice::PaintDeviceMetric metric) const; + +private: + friend class QPixmap; + friend class QBitmap; + friend class QX11PaintEngine; + friend class QX11WindowSurface; + friend class QRasterWindowSurface; + friend class QGLContextPrivate; // Needs to access xinfo, gl_surface & flags + friend class QEglContext; // Needs gl_surface + friend class QGLContext; // Needs gl_surface + friend class QX11GLPixmapData; // Needs gl_surface + friend class QMeeGoLivePixmapData; // Needs gl_surface and flags + friend bool qt_createEGLSurfaceForPixmap(QPixmapData*, bool); // Needs gl_surface + + void release(); + + QImage toImage(const QXImageWrapper &xi, const QRect &rect) const; + + QBitmap mask_to_bitmap(int screen) const; + static Qt::HANDLE bitmap_to_mask(const QBitmap &, int screen); + void bitmapFromImage(const QImage &image); + + bool canTakeQImageFromXImage(const QXImageWrapper &xi) const; + QImage takeQImageFromXImage(const QXImageWrapper &xi) const; + + Qt::HANDLE hd; + + enum Flag { + NoFlags = 0x0, + Uninitialized = 0x1, + Readonly = 0x2, + InvertedWhenBoundToTexture = 0x4, + GlSurfaceCreatedWithAlpha = 0x8 + }; + uint flags; + + QX11Info xinfo; + Qt::HANDLE x11_mask; + Qt::HANDLE picture; + Qt::HANDLE mask_picture; + Qt::HANDLE hd2; // sorted in the default display depth + QPixmap::ShareMode share_mode; + + QX11PaintEngine *pengine; +}; + +QT_END_NAMESPACE + +#endif // QPIXMAPDATA_X11_P_H + diff --git a/src/gui/image/qpixmapcache.cpp b/src/gui/image/qpixmapcache.cpp new file mode 100644 index 0000000000..ae772d8bc3 --- /dev/null +++ b/src/gui/image/qpixmapcache.cpp @@ -0,0 +1,680 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define Q_TEST_QPIXMAPCACHE +#include "qpixmapcache.h" +#include "qobject.h" +#include "qdebug.h" +#include "qpixmapcache_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPixmapCache + + \brief The QPixmapCache class provides an application-wide cache for pixmaps. + + \ingroup painting + + This class is a tool for optimized drawing with QPixmap. You can + use it to store temporary pixmaps that are expensive to generate + without using more storage space than cacheLimit(). Use insert() + to insert pixmaps, find() to find them, and clear() to empty the + cache. + + QPixmapCache contains no member data, only static functions to + access the global pixmap cache. It creates an internal QCache + object for caching the pixmaps. + + The cache associates a pixmap with a user-provided string as a key, + or with a QPixmapCache::Key that the cache generates. + Using QPixmapCache::Key for keys is faster than using strings. The string API is + very convenient for complex keys but the QPixmapCache::Key API will be very + efficient and convenient for a one-to-one object-to-pixmap mapping \mdash in + this case, you can store the keys as members of an object. + + If two pixmaps are inserted into the cache using equal keys then the + last pixmap will replace the first pixmap in the cache. This follows the + behavior of the QHash and QCache classes. + + The cache becomes full when the total size of all pixmaps in the + cache exceeds cacheLimit(). The initial cache limit is + 2048 KB (2 MB) on embedded platforms, 10240 KB (10 MB) on desktop + platforms; you can change this by calling setCacheLimit() with the + required value. + A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of + memory. + + The \e{Qt Quarterly} article + \l{http://qt.nokia.com/doc/qq/qq12-qpixmapcache.html}{Optimizing + with QPixmapCache} explains how to use QPixmapCache to speed up + applications by caching the results of painting. + + \sa QCache, QPixmap +*/ + +#if defined(Q_WS_QWS) || defined(Q_WS_WINCE) +static int cache_limit = 2048; // 2048 KB cache limit for embedded +#else +static int cache_limit = 10240; // 10 MB cache limit for desktop +#endif + +/*! + \class QPixmapCache::Key + \brief The QPixmapCache::Key class can be used for efficient access + to the QPixmapCache. + \since 4.6 + + Use QPixmapCache::insert() to receive an instance of Key generated + by the pixmap cache. You can store the key in your own objects for + a very efficient one-to-one object-to-pixmap mapping. +*/ + +/*! + Constructs an empty Key object. +*/ +QPixmapCache::Key::Key() : d(0) +{ +} + +/*! + \internal + Constructs a copy of \a other. +*/ +QPixmapCache::Key::Key(const Key &other) +{ + if (other.d) + ++(other.d->ref); + d = other.d; +} + +/*! + Destroys the key. +*/ +QPixmapCache::Key::~Key() +{ + if (d && --(d->ref) == 0) + delete d; +} + +/*! + \internal + + Returns true if this key is the same as the given \a key; otherwise returns + false. +*/ +bool QPixmapCache::Key::operator ==(const Key &key) const +{ + return (d == key.d); +} + +/*! + \fn bool QPixmapCache::Key::operator !=(const Key &key) const + \internal +*/ + +/*! + \internal +*/ +QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other) +{ + if (d != other.d) { + if (other.d) + ++(other.d->ref); + if (d && --(d->ref) == 0) + delete d; + d = other.d; + } + return *this; +} + +class QPMCache : public QObject, public QCache +{ + Q_OBJECT +public: + QPMCache(); + ~QPMCache(); + + void timerEvent(QTimerEvent *); + bool insert(const QString& key, const QPixmap &pixmap, int cost); + QPixmapCache::Key insert(const QPixmap &pixmap, int cost); + bool replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost); + bool remove(const QString &key); + bool remove(const QPixmapCache::Key &key); + + void resizeKeyArray(int size); + QPixmapCache::Key createKey(); + void releaseKey(const QPixmapCache::Key &key); + void clear(); + + QPixmap *object(const QString &key) const; + QPixmap *object(const QPixmapCache::Key &key) const; + + static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key) + {return key.d;} + + static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key); + + QList< QPair > allPixmaps() const; + bool flushDetachedPixmaps(bool nt); + +private: + enum { soon_time = 10000, flush_time = 30000 }; + int *keyArray; + int theid; + int ps; + int keyArraySize; + int freeKey; + QHash cacheKeys; + bool t; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qpixmapcache.moc" +QT_END_INCLUDE_NAMESPACE + +uint qHash(const QPixmapCache::Key &k) +{ + return qHash(QPMCache::get(k)->key); +} + +QPMCache::QPMCache() + : QObject(0), + QCache(cache_limit * 1024), + keyArray(0), theid(0), ps(0), keyArraySize(0), freeKey(0), t(false) +{ +} +QPMCache::~QPMCache() +{ + clear(); + free(keyArray); +} + +/* + This is supposed to cut the cache size down by about 25% in a + minute once the application becomes idle, to let any inserted pixmap + remain in the cache for some time before it becomes a candidate for + cleaning-up, and to not cut down the size of the cache while the + cache is in active use. + + When the last detached pixmap has been deleted from the cache, kill the + timer so Qt won't keep the CPU from going into sleep mode. Currently + the timer is not restarted when the pixmap becomes unused, but it does + restart once something else is added (i.e. the cache space is actually needed). + + Returns true if any were removed. +*/ +bool QPMCache::flushDetachedPixmaps(bool nt) +{ + int mc = maxCost(); + setMaxCost(nt ? totalCost() * 3 / 4 : totalCost() -1); + setMaxCost(mc); + ps = totalCost(); + + bool any = false; + QHash::iterator it = cacheKeys.begin(); + while (it != cacheKeys.end()) { + if (!contains(it.value())) { + releaseKey(it.value()); + it = cacheKeys.erase(it); + any = true; + } else { + ++it; + } + } + + return any; +} + +void QPMCache::timerEvent(QTimerEvent *) +{ + bool nt = totalCost() == ps; + if (!flushDetachedPixmaps(nt)) { + killTimer(theid); + theid = 0; + } else if (nt != t) { + killTimer(theid); + theid = startTimer(nt ? soon_time : flush_time); + t = nt; + } +} + + +QPixmap *QPMCache::object(const QString &key) const +{ + QPixmapCache::Key cacheKey = cacheKeys.value(key); + if (!cacheKey.d || !cacheKey.d->isValid) { + const_cast(this)->cacheKeys.remove(key); + return 0; + } + QPixmap *ptr = QCache::object(cacheKey); + //We didn't find the pixmap in the cache, the key is not valid anymore + if (!ptr) { + const_cast(this)->cacheKeys.remove(key); + } + return ptr; +} + +QPixmap *QPMCache::object(const QPixmapCache::Key &key) const +{ + Q_ASSERT(key.d->isValid); + QPixmap *ptr = QCache::object(key); + //We didn't find the pixmap in the cache, the key is not valid anymore + if (!ptr) + const_cast(this)->releaseKey(key); + return ptr; +} + +bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost) +{ + QPixmapCache::Key cacheKey; + QPixmapCache::Key oldCacheKey = cacheKeys.value(key); + //If for the same key we add already a pixmap we should delete it + if (oldCacheKey.d) { + QCache::remove(oldCacheKey); + cacheKeys.remove(key); + } + + //we create a new key the old one has been removed + cacheKey = createKey(); + + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + cacheKeys.insert(key, cacheKey); + if (!theid) { + theid = startTimer(flush_time); + t = false; + } + } else { + //Insertion failed we released the new allocated key + releaseKey(cacheKey); + } + return success; +} + +QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost) +{ + QPixmapCache::Key cacheKey = createKey(); + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + if (!theid) { + theid = startTimer(flush_time); + t = false; + } + } else { + //Insertion failed we released the key and return an invalid one + releaseKey(cacheKey); + } + return cacheKey; +} + +bool QPMCache::replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost) +{ + Q_ASSERT(key.d->isValid); + //If for the same key we had already an entry so we should delete the pixmap and use the new one + QCache::remove(key); + + QPixmapCache::Key cacheKey = createKey(); + + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + if(!theid) { + theid = startTimer(flush_time); + t = false; + } + const_cast(key) = cacheKey; + } else { + //Insertion failed we released the key + releaseKey(cacheKey); + } + return success; +} + +bool QPMCache::remove(const QString &key) +{ + QPixmapCache::Key cacheKey = cacheKeys.value(key); + //The key was not in the cache + if (!cacheKey.d) + return false; + cacheKeys.remove(key); + return QCache::remove(cacheKey); +} + +bool QPMCache::remove(const QPixmapCache::Key &key) +{ + return QCache::remove(key); +} + +void QPMCache::resizeKeyArray(int size) +{ + if (size <= keyArraySize || size == 0) + return; + keyArray = q_check_ptr(reinterpret_cast(realloc(keyArray, + size * sizeof(int)))); + for (int i = keyArraySize; i != size; ++i) + keyArray[i] = i + 1; + keyArraySize = size; +} + +QPixmapCache::Key QPMCache::createKey() +{ + if (freeKey == keyArraySize) + resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2); + int id = freeKey; + freeKey = keyArray[id]; + QPixmapCache::Key key; + QPixmapCache::KeyData *d = QPMCache::getKeyData(&key); + d->key = ++id; + return key; +} + +void QPMCache::releaseKey(const QPixmapCache::Key &key) +{ + if (key.d->key > keyArraySize || key.d->key <= 0) + return; + key.d->key--; + keyArray[key.d->key] = freeKey; + freeKey = key.d->key; + key.d->isValid = false; + key.d->key = 0; +} + +void QPMCache::clear() +{ + free(keyArray); + keyArray = 0; + freeKey = 0; + keyArraySize = 0; + //Mark all keys as invalid + QList keys = QCache::keys(); + for (int i = 0; i < keys.size(); ++i) + keys.at(i).d->isValid = false; + QCache::clear(); +} + +QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key) +{ + if (!key->d) + key->d = new QPixmapCache::KeyData; + return key->d; +} + +QList< QPair > QPMCache::allPixmaps() const +{ + QList< QPair > r; + QHash::const_iterator it = cacheKeys.begin(); + while (it != cacheKeys.end()) { + QPixmap *ptr = QCache::object(it.value()); + if (ptr) + r.append(QPair(it.key(),*ptr)); + ++it; + } + return r; +} + + +Q_GLOBAL_STATIC(QPMCache, pm_cache) + +int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize() +{ + return pm_cache()->size(); +} + +QPixmapCacheEntry::~QPixmapCacheEntry() +{ + pm_cache()->releaseKey(key); +} + +/*! + \obsolete + \overload + + Returns the pixmap associated with the \a key in the cache, or + null if there is no such pixmap. + + \warning If valid, you should copy the pixmap immediately (this is + fast). Subsequent insertions into the cache could cause the + pointer to become invalid. For this reason, we recommend you use + bool find(const QString&, QPixmap*) instead. + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 0 +*/ + +QPixmap *QPixmapCache::find(const QString &key) +{ + return pm_cache()->object(key); +} + + +/*! + \obsolete + + Use bool find(const QString&, QPixmap*) instead. +*/ + +bool QPixmapCache::find(const QString &key, QPixmap& pixmap) +{ + return find(key, &pixmap); +} + +/*! + Looks for a cached pixmap associated with the given \a key in the cache. + If the pixmap is found, the function sets \a pixmap to that pixmap and + returns true; otherwise it leaves \a pixmap alone and returns false. + + \since 4.6 + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 1 +*/ + +bool QPixmapCache::find(const QString &key, QPixmap* pixmap) +{ + QPixmap *ptr = pm_cache()->object(key); + if (ptr && pixmap) + *pixmap = *ptr; + return ptr != 0; +} + +/*! + Looks for a cached pixmap associated with the given \a key in the cache. + If the pixmap is found, the function sets \a pixmap to that pixmap and + returns true; otherwise it leaves \a pixmap alone and returns false. If + the pixmap is not found, it means that the \a key is no longer valid, + so it will be released for the next insertion. + + \since 4.6 +*/ +bool QPixmapCache::find(const Key &key, QPixmap* pixmap) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return false; + QPixmap *ptr = pm_cache()->object(key); + if (ptr && pixmap) + *pixmap = *ptr; + return ptr != 0; +} + +/*! + Inserts a copy of the pixmap \a pixmap associated with the \a key into + the cache. + + All pixmaps inserted by the Qt library have a key starting with + "$qt", so your own pixmap keys should never begin "$qt". + + When a pixmap is inserted and the cache is about to exceed its + limit, it removes pixmaps until there is enough room for the + pixmap to be inserted. + + The oldest pixmaps (least recently accessed in the cache) are + deleted when more space is needed. + + The function returns true if the object was inserted into the + cache; otherwise it returns false. + + \sa setCacheLimit() +*/ + +bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap) +{ + return pm_cache()->insert(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Inserts a copy of the given \a pixmap into the cache and returns a key + that can be used to retrieve it. + + When a pixmap is inserted and the cache is about to exceed its + limit, it removes pixmaps until there is enough room for the + pixmap to be inserted. + + The oldest pixmaps (least recently accessed in the cache) are + deleted when more space is needed. + + \sa setCacheLimit(), replace() + + \since 4.6 +*/ +QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap) +{ + return pm_cache()->insert(pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Replaces the pixmap associated with the given \a key with the \a pixmap + specified. Returns true if the \a pixmap has been correctly inserted into + the cache; otherwise returns false. + + \sa setCacheLimit(), insert() + + \since 4.6 +*/ +bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return false; + return pm_cache()->replace(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Returns the cache limit (in kilobytes). + + The default cache limit is 2048 KB on embedded platforms, 10240 KB on + desktop platforms. + + \sa setCacheLimit() +*/ + +int QPixmapCache::cacheLimit() +{ + return cache_limit; +} + +/*! + Sets the cache limit to \a n kilobytes. + + The default setting is 2048 KB on embedded platforms, 10240 KB on + desktop platforms. + + \sa cacheLimit() +*/ + +void QPixmapCache::setCacheLimit(int n) +{ + cache_limit = n; + pm_cache()->setMaxCost(1024 * cache_limit); +} + +/*! + Removes the pixmap associated with \a key from the cache. +*/ +void QPixmapCache::remove(const QString &key) +{ + pm_cache()->remove(key); +} + +/*! + Removes the pixmap associated with \a key from the cache and releases + the key for a future insertion. + + \since 4.6 +*/ +void QPixmapCache::remove(const Key &key) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return; + pm_cache()->remove(key); +} + +/*! + Removes all pixmaps from the cache. +*/ + +void QPixmapCache::clear() +{ + QT_TRY { + pm_cache()->clear(); + } QT_CATCH(const std::bad_alloc &) { + // if we ran out of memory during pm_cache(), it's no leak, + // so just ignore it. + } +} + +void QPixmapCache::flushDetachedPixmaps() +{ + pm_cache()->flushDetachedPixmaps(true); +} + +int QPixmapCache::totalUsed() +{ + return (pm_cache()->totalCost()+1023) / 1024; +} + +QList< QPair > QPixmapCache::allPixmaps() +{ + return pm_cache()->allPixmaps(); +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmapcache.h b/src/gui/image/qpixmapcache.h new file mode 100644 index 0000000000..77081572b1 --- /dev/null +++ b/src/gui/image/qpixmapcache.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPCACHE_H +#define QPIXMAPCACHE_H + +#include + +#ifdef Q_TEST_QPIXMAPCACHE +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QPixmapCache +{ +public: + class KeyData; + class Q_GUI_EXPORT Key + { + public: + Key(); + Key(const Key &other); + ~Key(); + bool operator ==(const Key &key) const; + inline bool operator !=(const Key &key) const + { return !operator==(key); } + Key &operator =(const Key &other); + + private: + KeyData *d; + friend class QPMCache; + friend class QPixmapCache; + }; + + static int cacheLimit(); + static void setCacheLimit(int); + static QPixmap *find(const QString &key); + static bool find(const QString &key, QPixmap &pixmap); + static bool find(const QString &key, QPixmap *pixmap); + static bool find(const Key &key, QPixmap *pixmap); + static bool insert(const QString &key, const QPixmap &pixmap); + static Key insert(const QPixmap &pixmap); + static bool replace(const Key &key, const QPixmap &pixmap); + static void remove(const QString &key); + static void remove(const Key &key); + static void clear(); + +#ifdef Q_TEST_QPIXMAPCACHE + static void flushDetachedPixmaps(); + static int totalUsed(); + static QList< QPair > allPixmaps(); +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPIXMAPCACHE_H diff --git a/src/gui/image/qpixmapcache_p.h b/src/gui/image/qpixmapcache_p.h new file mode 100644 index 0000000000..b2ca00163c --- /dev/null +++ b/src/gui/image/qpixmapcache_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPCACHE_P_H +#define QPIXMAPCACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "qpixmapcache.h" +#include "qpaintengine.h" +#include +#include +#include "qcache.h" + +QT_BEGIN_NAMESPACE + +uint qHash(const QPixmapCache::Key &k); + +class QPixmapCache::KeyData +{ +public: + KeyData() : isValid(true), key(0), ref(1) {} + KeyData(const KeyData &other) + : isValid(other.isValid), key(other.key), ref(1) {} + ~KeyData() {} + + bool isValid; + int key; + int ref; +}; + +// XXX: hw: is this a general concept we need to abstract? +class QPixmapCacheEntry : public QPixmap +{ +public: + QPixmapCacheEntry(const QPixmapCache::Key &key, const QPixmap &pix) : QPixmap(pix), key(key) + { + QPixmapData *pd = pixmapData(); + if (pd && pd->classId() == QPixmapData::RasterClass) { + QRasterPixmapData *d = static_cast(pd); + if (!d->image.isNull() && d->image.d->paintEngine + && !d->image.d->paintEngine->isActive()) + { + delete d->image.d->paintEngine; + d->image.d->paintEngine = 0; + } + } + } + ~QPixmapCacheEntry(); + QPixmapCache::Key key; +}; + +inline bool qIsDetached(QPixmapCacheEntry &t) { return t.isDetached(); } + +QT_END_NAMESPACE + +#endif // QPIXMAPCACHE_P_H diff --git a/src/gui/image/qpixmapdata.cpp b/src/gui/image/qpixmapdata.cpp new file mode 100644 index 0000000000..8222d714dc --- /dev/null +++ b/src/gui/image/qpixmapdata.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmapdata_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +const uchar qt_pixmap_bit_mask[] = { 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 }; + +QPixmapData *QPixmapData::create(int w, int h, PixelType type) +{ + QPixmapData *data; + QGraphicsSystem* gs = QApplicationPrivate::graphicsSystem(); + if (gs) + data = gs->createPixmapData(static_cast(type)); + else + data = QGraphicsSystem::createDefaultPixmapData(static_cast(type)); + data->resize(w, h); + return data; +} + + +QPixmapData::QPixmapData(PixelType pixelType, int objectId) + : w(0), + h(0), + d(0), + is_null(true), + ref(0), + detach_no(0), + type(pixelType), + id(objectId), + ser_no(0), + is_cached(false) +{ +} + +QPixmapData::~QPixmapData() +{ + // Sometimes the pixmap cleanup hooks will be called from derrived classes, which will + // then set is_cached to false. For example, on X11 QtOpenGL needs to delete the GLXPixmap + // or EGL Pixmap Surface for a given pixmap _before_ the native X11 pixmap is deleted, + // otherwise some drivers will leak the GL surface. In this case, QX11PixmapData will + // call the cleanup hooks itself before deleting the native pixmap and set is_cached to + // false. + if (is_cached) { + QImagePixmapCleanupHooks::executePixmapDataDestructionHooks(this); + is_cached = false; + } +} + +QPixmapData *QPixmapData::createCompatiblePixmapData() const +{ + QPixmapData *d; + QGraphicsSystem *gs = QApplicationPrivate::graphicsSystem(); + if (gs) + d = gs->createPixmapData(pixelType()); + else + d = QGraphicsSystem::createDefaultPixmapData(pixelType()); + return d; +} + +static QImage makeBitmapCompliantIfNeeded(QPixmapData *d, const QImage &image, Qt::ImageConversionFlags flags) +{ + if (d->pixelType() == QPixmapData::BitmapType) { + QImage img = image.convertToFormat(QImage::Format_MonoLSB, flags); + + // make sure image.color(0) == Qt::color0 (white) + // and image.color(1) == Qt::color1 (black) + const QRgb c0 = QColor(Qt::black).rgb(); + const QRgb c1 = QColor(Qt::white).rgb(); + if (img.color(0) == c0 && img.color(1) == c1) { + img.invertPixels(); + img.setColor(0, c1); + img.setColor(1, c0); + } + return img; + } + + return image; +} + +void QPixmapData::fromImageReader(QImageReader *imageReader, + Qt::ImageConversionFlags flags) +{ + const QImage image = imageReader->read(); + fromImage(image, flags); +} + +bool QPixmapData::fromFile(const QString &fileName, const char *format, + Qt::ImageConversionFlags flags) +{ + QImage image = QImageReader(fileName, format).read(); + if (image.isNull()) + return false; + fromImage(makeBitmapCompliantIfNeeded(this, image, flags), flags); + return !isNull(); +} + +bool QPixmapData::fromData(const uchar *buf, uint len, const char *format, Qt::ImageConversionFlags flags) +{ + QByteArray a = QByteArray::fromRawData(reinterpret_cast(buf), len); + QBuffer b(&a); + b.open(QIODevice::ReadOnly); + QImage image = QImageReader(&b, format).read(); + fromImage(makeBitmapCompliantIfNeeded(this, image, flags), flags); + return !isNull(); +} + +void QPixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + fromImage(data->toImage(rect), Qt::NoOpaqueDetection); +} + +bool QPixmapData::scroll(int dx, int dy, const QRect &rect) +{ + Q_UNUSED(dx); + Q_UNUSED(dy); + Q_UNUSED(rect); + return false; +} + +void QPixmapData::setMask(const QBitmap &mask) +{ + if (mask.size().isEmpty()) { + if (depth() != 1) + fromImage(toImage().convertToFormat(QImage::Format_RGB32), + Qt::AutoColor); + } else { + QImage image = toImage(); + const int w = image.width(); + const int h = image.height(); + + switch (image.depth()) { + case 1: { + const QImage imageMask = mask.toImage().convertToFormat(image.format()); + for (int y = 0; y < h; ++y) { + const uchar *mscan = imageMask.scanLine(y); + uchar *tscan = image.scanLine(y); + int bytesPerLine = image.bytesPerLine(); + for (int i = 0; i < bytesPerLine; ++i) + tscan[i] &= mscan[i]; + } + break; + } + default: { + const QImage imageMask = mask.toImage().convertToFormat(QImage::Format_MonoLSB); + image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + for (int y = 0; y < h; ++y) { + const uchar *mscan = imageMask.scanLine(y); + QRgb *tscan = (QRgb *)image.scanLine(y); + for (int x = 0; x < w; ++x) { + if (!(mscan[x>>3] & qt_pixmap_bit_mask[x&7])) + tscan[x] = 0; + } + } + break; + } + } + fromImage(image, Qt::AutoColor); + } +} + +QBitmap QPixmapData::mask() const +{ + if (!hasAlphaChannel()) + return QBitmap(); + + const QImage img = toImage(); + const QImage image = (img.depth() < 32 ? img.convertToFormat(QImage::Format_ARGB32_Premultiplied) : img); + const int w = image.width(); + const int h = image.height(); + + QImage mask(w, h, QImage::Format_MonoLSB); + if (mask.isNull()) // allocation failed + return QBitmap(); + + mask.setColorCount(2); + mask.setColor(0, QColor(Qt::color0).rgba()); + mask.setColor(1, QColor(Qt::color1).rgba()); + + const int bpl = mask.bytesPerLine(); + + for (int y = 0; y < h; ++y) { + const QRgb *src = reinterpret_cast(image.scanLine(y)); + uchar *dest = mask.scanLine(y); + memset(dest, 0, bpl); + for (int x = 0; x < w; ++x) { + if (qAlpha(*src) > 0) + dest[x >> 3] |= qt_pixmap_bit_mask[x & 7]; + ++src; + } + } + + return QBitmap::fromImage(mask); +} + +QPixmap QPixmapData::transformed(const QTransform &matrix, + Qt::TransformationMode mode) const +{ + return QPixmap::fromImage(toImage().transformed(matrix, mode)); +} + +void QPixmapData::setAlphaChannel(const QPixmap &alphaChannel) +{ + QImage image = toImage(); + image.setAlphaChannel(alphaChannel.toImage()); + fromImage(image, Qt::AutoColor); +} + +QPixmap QPixmapData::alphaChannel() const +{ + return QPixmap::fromImage(toImage().alphaChannel()); +} + +void QPixmapData::setSerialNumber(int serNo) +{ + ser_no = serNo; +} + +QImage QPixmapData::toImage(const QRect &rect) const +{ + if (rect.contains(QRect(0, 0, w, h))) + return toImage(); + else + return toImage().copy(rect); +} + +QImage* QPixmapData::buffer() +{ + return 0; +} + +#if defined(Q_OS_SYMBIAN) +void* QPixmapData::toNativeType(NativeType /* type */) +{ + return 0; +} + +void QPixmapData::fromNativeType(void* /* pixmap */, NativeType /* typre */) +{ + return; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmapdata_p.h b/src/gui/image/qpixmapdata_p.h new file mode 100644 index 0000000000..6bbce09b6c --- /dev/null +++ b/src/gui/image/qpixmapdata_p.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPDATA_P_H +#define QPIXMAPDATA_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 +#include + +QT_BEGIN_NAMESPACE + +class QImageReader; + +class Q_GUI_EXPORT QPixmapData +{ +public: + enum PixelType { + // WARNING: Do not change the first two + // Must match QPixmap::Type + PixmapType, BitmapType + }; +#if defined(Q_OS_SYMBIAN) + enum NativeType { + FbsBitmap, + SgImage, + VolatileImage, + NativeImageHandleProvider + }; +#endif + enum ClassId { RasterClass, X11Class, MacClass, DirectFBClass, + OpenGLClass, OpenVGClass, RuntimeClass, BlitterClass, + CustomClass = 1024 }; + + QPixmapData(PixelType pixelType, int classId); + virtual ~QPixmapData(); + + virtual QPixmapData *createCompatiblePixmapData() const; + + virtual void resize(int width, int height) = 0; + virtual void fromImage(const QImage &image, + Qt::ImageConversionFlags flags) = 0; + virtual void fromImageReader(QImageReader *imageReader, + Qt::ImageConversionFlags flags); + + virtual bool fromFile(const QString &filename, const char *format, + Qt::ImageConversionFlags flags); + virtual bool fromData(const uchar *buffer, uint len, const char *format, + Qt::ImageConversionFlags flags); + + virtual void copy(const QPixmapData *data, const QRect &rect); + virtual bool scroll(int dx, int dy, const QRect &rect); + + virtual int metric(QPaintDevice::PaintDeviceMetric metric) const = 0; + virtual void fill(const QColor &color) = 0; + virtual QBitmap mask() const; + virtual void setMask(const QBitmap &mask); + virtual bool hasAlphaChannel() const = 0; + virtual QPixmap transformed(const QTransform &matrix, + Qt::TransformationMode mode) const; + virtual void setAlphaChannel(const QPixmap &alphaChannel); + virtual QPixmap alphaChannel() const; + virtual QImage toImage() const = 0; + virtual QImage toImage(const QRect &rect) const; + virtual QPaintEngine* paintEngine() const = 0; + + inline int serialNumber() const { return ser_no; } + + inline PixelType pixelType() const { return type; } + inline ClassId classId() const { return static_cast(id); } + + virtual QImage* buffer(); + + inline int width() const { return w; } + inline int height() const { return h; } + QT_DEPRECATED inline int numColors() const { return metric(QPaintDevice::PdmNumColors); } + inline int colorCount() const { return metric(QPaintDevice::PdmNumColors); } + inline int depth() const { return d; } + inline bool isNull() const { return is_null; } + inline qint64 cacheKey() const { + int classKey = id; + if (classKey >= 1024) + classKey = -(classKey >> 10); + return ((((qint64) classKey) << 56) + | (((qint64) ser_no) << 32) + | ((qint64) detach_no)); + } + +#if defined(Q_OS_SYMBIAN) + virtual void* toNativeType(NativeType type); + virtual void fromNativeType(void* pixmap, NativeType type); +#endif + + static QPixmapData *create(int w, int h, PixelType type); + + virtual QPixmapData *runtimeData() const { return 0; } + +protected: + + void setSerialNumber(int serNo); + int w; + int h; + int d; + bool is_null; + +private: + friend class QPixmap; + friend class QX11PixmapData; + friend class QS60PixmapData; + friend class QImagePixmapCleanupHooks; // Needs to set is_cached + friend class QGLTextureCache; //Needs to check the reference count + friend class QExplicitlySharedDataPointer; + + QAtomicInt ref; + int detach_no; + + PixelType type; + int id; + int ser_no; + uint is_cached; +}; + +# define QT_XFORM_TYPE_MSBFIRST 0 +# define QT_XFORM_TYPE_LSBFIRST 1 +# if defined(Q_WS_WIN) +# define QT_XFORM_TYPE_WINDOWSPIXMAP 2 +# endif +extern bool qt_xForm_helper(const QTransform&, int, int, int, uchar*, int, int, int, const uchar*, int, int, int); + +QT_END_NAMESPACE + +#endif // QPIXMAPDATA_P_H diff --git a/src/gui/image/qpixmapdatafactory.cpp b/src/gui/image/qpixmapdatafactory.cpp new file mode 100644 index 0000000000..ec53dcb9c1 --- /dev/null +++ b/src/gui/image/qpixmapdatafactory.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpixmapdatafactory_p.h" + +#ifdef Q_WS_QWS +# include +#endif +#ifdef Q_WS_X11 +# include +#endif +#if defined(Q_WS_WIN) +# include +#endif +#ifdef Q_WS_MAC +# include +#endif +#ifdef Q_WS_QPA +# include +#endif +#ifdef Q_OS_SYMBIAN +# include +#endif + +#include "private/qapplication_p.h" +#include "private/qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +#if !defined(Q_WS_QWS) + +class QSimplePixmapDataFactory : public QPixmapDataFactory +{ +public: + ~QSimplePixmapDataFactory() {} + QPixmapData* create(QPixmapData::PixelType type); +}; + +QPixmapData* QSimplePixmapDataFactory::create(QPixmapData::PixelType type) +{ + if (QApplicationPrivate::graphicsSystem()) + return QApplicationPrivate::graphicsSystem()->createPixmapData(type); + +#if defined(Q_WS_X11) + return new QX11PixmapData(type); +#elif defined(Q_WS_WIN) + return new QRasterPixmapData(type); +#elif defined(Q_WS_MAC) + return new QMacPixmapData(type); +#elif defined(Q_WS_QPA) + return new QRasterPixmapData(type); +#elif defined(Q_OS_SYMBIAN) + return new QS60PixmapData(type); +#else +#error QSimplePixmapDataFactory::create() not implemented +#endif +} + +Q_GLOBAL_STATIC(QSimplePixmapDataFactory, factory) + +#endif // !defined(Q_WS_QWS) + +QPixmapDataFactory::~QPixmapDataFactory() +{ +} + +QPixmapDataFactory* QPixmapDataFactory::instance(int screen) +{ + Q_UNUSED(screen); +#ifdef Q_WS_QWS + return QScreen::instance()->pixmapDataFactory(); +#else + return factory(); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qpixmapdatafactory_p.h b/src/gui/image/qpixmapdatafactory_p.h new file mode 100644 index 0000000000..4f2e5c4527 --- /dev/null +++ b/src/gui/image/qpixmapdatafactory_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPDATAFACTORY_P_H +#define QPIXMAPDATAFACTORY_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 +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPixmapData; + +class QPixmapDataFactory +{ +public: + static QPixmapDataFactory* instance(int screen = 0); + virtual ~QPixmapDataFactory(); + + virtual QPixmapData* create(QPixmapData::PixelType type) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPIXMAPDATAFACTORY_P_H diff --git a/src/gui/image/qpixmapfilter.cpp b/src/gui/image/qpixmapfilter.cpp new file mode 100644 index 0000000000..c05afb052e --- /dev/null +++ b/src/gui/image/qpixmapfilter.cpp @@ -0,0 +1,1382 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include + +#include "qpainter.h" +#include "qpixmap.h" +#include "qpixmapfilter_p.h" +#include "qvarlengtharray.h" + +#include "private/qapplication_p.h" +#include "private/qgraphicssystem_p.h" +#include "private/qpaintengineex_p.h" +#include "private/qpaintengine_raster_p.h" +#include "qmath.h" +#include "private/qmath_p.h" +#include "private/qmemrotate_p.h" +#include "private/qdrawhelper_p.h" + +#ifndef QT_NO_GRAPHICSEFFECT +QT_BEGIN_NAMESPACE + +class QPixmapFilterPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QPixmapFilter) +public: + QPixmapFilter::FilterType type; +}; + +/*! + \class QPixmapFilter + \since 4.5 + \ingroup painting + + \brief The QPixmapFilter class provides the basic functionality for + pixmap filter classes. Pixmap filter can be for example colorize or blur. + + QPixmapFilter is the base class for every pixmap filter. QPixmapFilter is + an abstract class and cannot itself be instantiated. It provides a standard + interface for filter processing. + + \internal +*/ + +/*! + \enum QPixmapFilter::FilterType + + \internal + + This enum describes the types of filter that can be applied to pixmaps. + + \value ConvolutionFilter A filter that is used to calculate the convolution + of the image with a kernel. See + QPixmapConvolutionFilter for more information. + \value ColorizeFilter A filter that is used to change the overall color + of an image. See QPixmapColorizeFilter for more + information. + \value DropShadowFilter A filter that is used to add a drop shadow to an + image. See QPixmapDropShadowFilter for more + information. + \value BlurFilter A filter that is used to blur an image using + a simple blur radius. See QPixmapBlurFilter + for more information. + + \value UserFilter The first filter type that can be used for + application-specific purposes. +*/ + + +/*! + Constructs a default QPixmapFilter with the given \a type. + + This constructor should be used when subclassing QPixmapFilter to + create custom user filters. + + \internal +*/ +QPixmapFilter::QPixmapFilter(FilterType type, QObject *parent) + : QObject(*new QPixmapFilterPrivate, parent) +{ + d_func()->type = type; +} + + + +/*! + \internal +*/ +QPixmapFilter::QPixmapFilter(QPixmapFilterPrivate&d, QPixmapFilter::FilterType type, QObject *parent) + : QObject(d, parent) +{ + d_func()->type = type; +} + + +/*! + Destroys the pixmap filter. + + \internal +*/ +QPixmapFilter::~QPixmapFilter() +{ +} + +/*! + Returns the type of the filter. All standard pixmap filter classes + are associated with a unique value. + + \internal +*/ +QPixmapFilter::FilterType QPixmapFilter::type() const +{ + Q_D(const QPixmapFilter); + return d->type; +} + +/*! + Returns the bounding rectangle that is affected by the pixmap + filter if the filter is applied to the specified \a rect. + + \internal +*/ +QRectF QPixmapFilter::boundingRectFor(const QRectF &rect) const +{ + return rect; +} + +/*! + \fn void QPixmapFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const + + Uses \a painter to draw filtered result of \a src at the point + specified by \a p. If \a srcRect is specified the it will + be used as a source rectangle to only draw a part of the source. + + draw() will affect the area which boundingRectFor() returns. + + \internal +*/ + +/*! + \class QPixmapConvolutionFilter + \since 4.5 + \ingroup painting + + \brief The QPixmapConvolutionFilter class provides convolution + filtering for pixmaps. + + QPixmapConvolutionFilter implements a convolution pixmap filter, + which is applied when \l{QPixmapFilter::}{draw()} is called. A + convolution filter lets you distort an image by setting the values + of a matrix of qreal values called its + \l{setConvolutionKernel()}{kernel}. The matrix's values are + usually between -1.0 and 1.0. + + \omit + In convolution filtering, the pixel value is calculated from the + neighboring pixels based on the weighting convolution kernel. + This needs explaining to be useful. + \endomit + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapfilter.cpp 1 + + \sa {Pixmap Filters Example}, QPixmapColorizeFilter, QPixmapDropShadowFilter + + + \internal +*/ + +class QPixmapConvolutionFilterPrivate : public QPixmapFilterPrivate +{ +public: + QPixmapConvolutionFilterPrivate(): convolutionKernel(0), kernelWidth(0), kernelHeight(0), convoluteAlpha(false) {} + ~QPixmapConvolutionFilterPrivate() { + delete[] convolutionKernel; + } + + qreal *convolutionKernel; + int kernelWidth; + int kernelHeight; + bool convoluteAlpha; +}; + + +/*! + Constructs a pixmap convolution filter. + + By default there is no convolution kernel. + + \internal +*/ +QPixmapConvolutionFilter::QPixmapConvolutionFilter(QObject *parent) + : QPixmapFilter(*new QPixmapConvolutionFilterPrivate, ConvolutionFilter, parent) +{ + Q_D(QPixmapConvolutionFilter); + d->convoluteAlpha = true; +} + +/*! + Destructor of pixmap convolution filter. + + \internal +*/ +QPixmapConvolutionFilter::~QPixmapConvolutionFilter() +{ +} + +/*! + Sets convolution kernel with the given number of \a rows and \a columns. + Values from \a kernel are copied to internal data structure. + + To preserve the intensity of the pixmap, the sum of all the + values in the convolution kernel should add up to 1.0. A sum + greater than 1.0 produces a lighter result and a sum less than 1.0 + produces a darker and transparent result. + + \internal +*/ +void QPixmapConvolutionFilter::setConvolutionKernel(const qreal *kernel, int rows, int columns) +{ + Q_D(QPixmapConvolutionFilter); + delete [] d->convolutionKernel; + d->convolutionKernel = new qreal[rows * columns]; + memcpy(d->convolutionKernel, kernel, sizeof(qreal) * rows * columns); + d->kernelWidth = columns; + d->kernelHeight = rows; +} + +/*! + Gets the convolution kernel data. + + \internal +*/ +const qreal *QPixmapConvolutionFilter::convolutionKernel() const +{ + Q_D(const QPixmapConvolutionFilter); + return d->convolutionKernel; +} + +/*! + Gets the number of rows in the convolution kernel. + + \internal +*/ +int QPixmapConvolutionFilter::rows() const +{ + Q_D(const QPixmapConvolutionFilter); + return d->kernelHeight; +} + +/*! + Gets the number of columns in the convolution kernel. + + \internal +*/ +int QPixmapConvolutionFilter::columns() const +{ + Q_D(const QPixmapConvolutionFilter); + return d->kernelWidth; +} + + +/*! + \internal +*/ +QRectF QPixmapConvolutionFilter::boundingRectFor(const QRectF &rect) const +{ + Q_D(const QPixmapConvolutionFilter); + return rect.adjusted(-d->kernelWidth / 2, -d->kernelHeight / 2, (d->kernelWidth - 1) / 2, (d->kernelHeight - 1) / 2); +} + +// Convolutes the image +static void convolute( + QImage *destImage, + const QPointF &pos, + const QImage &srcImage, + const QRectF &srcRect, + QPainter::CompositionMode mode, + qreal *kernel, + int kernelWidth, + int kernelHeight ) +{ + const QImage processImage = (srcImage.format() != QImage::Format_ARGB32_Premultiplied ) ? srcImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) : srcImage; + // TODO: support also other formats directly without copying + + int *fixedKernel = new int[kernelWidth*kernelHeight]; + for(int i = 0; i < kernelWidth*kernelHeight; i++) + { + fixedKernel[i] = (int)(65536 * kernel[i]); + } + QRectF trect = srcRect.isNull() ? processImage.rect() : srcRect; + trect.moveTo(pos); + QRectF bounded = trect.adjusted(-kernelWidth / 2, -kernelHeight / 2, (kernelWidth - 1) / 2, (kernelHeight - 1) / 2); + QRect rect = bounded.toAlignedRect(); + QRect targetRect = rect.intersected(destImage->rect()); + + QRectF srect = srcRect.isNull() ? processImage.rect() : srcRect; + QRectF sbounded = srect.adjusted(-kernelWidth / 2, -kernelHeight / 2, (kernelWidth - 1) / 2, (kernelHeight - 1) / 2); + QPoint srcStartPoint = sbounded.toAlignedRect().topLeft()+(targetRect.topLeft()-rect.topLeft()); + + const uint *sourceStart = (uint*)processImage.scanLine(0); + uint *outputStart = (uint*)destImage->scanLine(0); + + int yk = srcStartPoint.y(); + for (int y = targetRect.top(); y <= targetRect.bottom(); y++) { + uint* output = outputStart + (destImage->bytesPerLine()/sizeof(uint))*y+targetRect.left(); + int xk = srcStartPoint.x(); + for(int x = targetRect.left(); x <= targetRect.right(); x++) { + int r = 0; + int g = 0; + int b = 0; + int a = 0; + + // some out of bounds pre-checking to avoid inner-loop ifs + int kernely = -kernelHeight/2; + int starty = 0; + int endy = kernelHeight; + if(yk+kernely+endy >= srcImage.height()) + endy = kernelHeight-((yk+kernely+endy)-srcImage.height())-1; + if(yk+kernely < 0) + starty = -(yk+kernely); + + int kernelx = -kernelWidth/2; + int startx = 0; + int endx = kernelWidth; + if(xk+kernelx+endx >= srcImage.width()) + endx = kernelWidth-((xk+kernelx+endx)-srcImage.width())-1; + if(xk+kernelx < 0) + startx = -(xk+kernelx); + + for (int ys = starty; ys < endy; ys ++) { + const uint *pix = sourceStart + (processImage.bytesPerLine()/sizeof(uint))*(yk+kernely+ys) + ((xk+kernelx+startx)); + const uint *endPix = pix+endx-startx; + int kernelPos = ys*kernelWidth+startx; + while (pix < endPix) { + int factor = fixedKernel[kernelPos++]; + a += (((*pix) & 0xff000000)>>24) * factor; + r += (((*pix) & 0x00ff0000)>>16) * factor; + g += (((*pix) & 0x0000ff00)>>8 ) * factor; + b += (((*pix) & 0x000000ff) ) * factor; + pix++; + } + } + + r = qBound((int)0, r >> 16, (int)255); + g = qBound((int)0, g >> 16, (int)255); + b = qBound((int)0, b >> 16, (int)255); + a = qBound((int)0, a >> 16, (int)255); + // composition mode checking could be moved outside of loop + if(mode == QPainter::CompositionMode_Source) { + uint color = (a<<24)+(r<<16)+(g<<8)+b; + *output++ = color; + } else { + uint current = *output; + uchar ca = (current&0xff000000)>>24; + uchar cr = (current&0x00ff0000)>>16; + uchar cg = (current&0x0000ff00)>>8; + uchar cb = (current&0x000000ff); + uint color = + (((ca*(255-a) >> 8)+a) << 24)+ + (((cr*(255-a) >> 8)+r) << 16)+ + (((cg*(255-a) >> 8)+g) << 8)+ + (((cb*(255-a) >> 8)+b)); + *output++ = color;; + } + xk++; + } + yk++; + } + delete[] fixedKernel; +} + +/*! + \internal +*/ +void QPixmapConvolutionFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF& srcRect) const +{ + Q_D(const QPixmapConvolutionFilter); + if (!painter->isActive()) + return; + + if(d->kernelWidth<=0 || d->kernelHeight <= 0) + return; + + if (src.isNull()) + return; + + QPixmapFilter *filter = painter->paintEngine() && painter->paintEngine()->isExtended() ? + static_cast(painter->paintEngine())->pixmapFilter(type(), this) : 0; + QPixmapConvolutionFilter *convolutionFilter = static_cast(filter); + if (convolutionFilter) { + convolutionFilter->setConvolutionKernel(d->convolutionKernel, d->kernelWidth, d->kernelHeight); + convolutionFilter->d_func()->convoluteAlpha = d->convoluteAlpha; + convolutionFilter->draw(painter, p, src, srcRect); + return; + } + + // falling back to raster implementation + + QImage *target = 0; + if (painter->paintEngine()->paintDevice()->devType() == QInternal::Image) { + target = static_cast(painter->paintEngine()->paintDevice()); + + QTransform mat = painter->combinedTransform(); + + if (mat.type() > QTransform::TxTranslate) { + // Disabled because of transformation... + target = 0; + } else { + QRasterPaintEngine *pe = static_cast(painter->paintEngine()); + if (pe->clipType() == QRasterPaintEngine::ComplexClip) + // disabled because of complex clipping... + target = 0; + else { + QRectF clip = pe->clipBoundingRect(); + QRectF rect = boundingRectFor(srcRect.isEmpty() ? src.rect() : srcRect); + QTransform x = painter->deviceTransform(); + if (!clip.contains(rect.translated(x.dx() + p.x(), x.dy() + p.y()))) { + target = 0; + } + + } + } + } + + if (target) { + QTransform x = painter->deviceTransform(); + QPointF offset(x.dx(), x.dy()); + + convolute(target, p+offset, src.toImage(), srcRect, QPainter::CompositionMode_SourceOver, d->convolutionKernel, d->kernelWidth, d->kernelHeight); + } else { + QRect srect = srcRect.isNull() ? src.rect() : srcRect.toRect(); + QRect rect = boundingRectFor(srect).toRect(); + QImage result = QImage(rect.size(), QImage::Format_ARGB32_Premultiplied); + QPoint offset = srect.topLeft() - rect.topLeft(); + convolute(&result, + offset, + src.toImage(), + srect, + QPainter::CompositionMode_Source, + d->convolutionKernel, + d->kernelWidth, + d->kernelHeight); + painter->drawImage(p - offset, result); + } +} + +/*! + \class QPixmapBlurFilter + \since 4.6 + \ingroup multimedia + + \brief The QPixmapBlurFilter class provides blur filtering + for pixmaps. + + QPixmapBlurFilter implements a blur pixmap filter, + which is applied when \l{QPixmapFilter::}{draw()} is called. + + The filter lets you specialize the radius of the blur as well + as hints as to whether to prefer performance or quality. + + By default, the blur effect is produced by applying an exponential + filter generated from the specified blurRadius(). Paint engines + may override this with a custom blur that is faster on the + underlying hardware. + + \sa {Pixmap Filters Example}, QPixmapConvolutionFilter, QPixmapDropShadowFilter + + \internal +*/ + +class QPixmapBlurFilterPrivate : public QPixmapFilterPrivate +{ +public: + QPixmapBlurFilterPrivate() : radius(5), hints(QGraphicsBlurEffect::PerformanceHint) {} + + qreal radius; + QGraphicsBlurEffect::BlurHints hints; +}; + + +/*! + Constructs a pixmap blur filter. + + \internal +*/ +QPixmapBlurFilter::QPixmapBlurFilter(QObject *parent) + : QPixmapFilter(*new QPixmapBlurFilterPrivate, BlurFilter, parent) +{ +} + +/*! + Destructor of pixmap blur filter. + + \internal +*/ +QPixmapBlurFilter::~QPixmapBlurFilter() +{ +} + +/*! + Sets the radius of the blur filter. Higher radius produces increased blurriness. + + \internal +*/ +void QPixmapBlurFilter::setRadius(qreal radius) +{ + Q_D(QPixmapBlurFilter); + d->radius = radius; +} + +/*! + Gets the radius of the blur filter. + + \internal +*/ +qreal QPixmapBlurFilter::radius() const +{ + Q_D(const QPixmapBlurFilter); + return d->radius; +} + +/*! + Setting the blur hints to PerformanceHint causes the implementation + to trade off visual quality to blur the image faster. Setting the + blur hints to QualityHint causes the implementation to improve + visual quality at the expense of speed. + + AnimationHint causes the implementation to optimize for animating + the blur radius, possibly by caching blurred versions of the source + pixmap. + + The implementation is free to ignore this value if it only has a single + blur algorithm. + + \internal +*/ +void QPixmapBlurFilter::setBlurHints(QGraphicsBlurEffect::BlurHints hints) +{ + Q_D(QPixmapBlurFilter); + d->hints = hints; +} + +/*! + Gets the blur hints of the blur filter. + + \internal +*/ +QGraphicsBlurEffect::BlurHints QPixmapBlurFilter::blurHints() const +{ + Q_D(const QPixmapBlurFilter); + return d->hints; +} + +const qreal radiusScale = qreal(2.5); + +/*! + \internal +*/ +QRectF QPixmapBlurFilter::boundingRectFor(const QRectF &rect) const +{ + Q_D(const QPixmapBlurFilter); + const qreal delta = radiusScale * d->radius + 1; + return rect.adjusted(-delta, -delta, delta, delta); +} + +template +inline int qt_static_shift(int value) +{ + if (shift == 0) + return value; + else if (shift > 0) + return value << (uint(shift) & 0x1f); + else + return value >> (uint(-shift) & 0x1f); +} + +template +inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + QRgb *pixel = (QRgb *)bptr; + +#define Z_MASK (0xff << zprec) + const int A_zprec = qt_static_shift(*pixel) & Z_MASK; + const int R_zprec = qt_static_shift(*pixel) & Z_MASK; + const int G_zprec = qt_static_shift(*pixel) & Z_MASK; + const int B_zprec = qt_static_shift(*pixel) & Z_MASK; +#undef Z_MASK + + const int zR_zprec = zR >> aprec; + const int zG_zprec = zG >> aprec; + const int zB_zprec = zB >> aprec; + const int zA_zprec = zA >> aprec; + + zR += alpha * (R_zprec - zR_zprec); + zG += alpha * (G_zprec - zG_zprec); + zB += alpha * (B_zprec - zB_zprec); + zA += alpha * (A_zprec - zA_zprec); + +#define ZA_MASK (0xff << (zprec + aprec)) + *pixel = + qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK) + | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK) + | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK) + | qt_static_shift<-zprec - aprec>(zB & ZA_MASK); +#undef ZA_MASK +} + +const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + +template +inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha) +{ + const int A_zprec = int(*(bptr)) << zprec; + const int z_zprec = z >> aprec; + z += alpha * (A_zprec - z_zprec); + *(bptr) = z >> (zprec + aprec); +} + +template +inline void qt_blurrow(QImage & im, int line, int alpha) +{ + uchar *bptr = im.scanLine(line); + + int zR = 0, zG = 0, zB = 0, zA = 0; + + if (alphaOnly && im.format() != QImage::Format_Indexed8) + bptr += alphaIndex; + + const int stride = im.depth() >> 3; + const int im_width = im.width(); + for (int index = 0; index < im_width; ++index) { + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + bptr += stride; + } + + bptr -= stride; + + for (int index = im_width - 2; index >= 0; --index) { + bptr -= stride; + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + } +} + +/* +* expblur(QImage &img, int radius) +* +* Based on exponential blur algorithm by Jani Huhtanen +* +* In-place blur of image 'img' with kernel +* of approximate radius 'radius'. +* +* Blurs with two sided exponential impulse +* response. +* +* aprec = precision of alpha parameter +* in fixed-point format 0.aprec +* +* zprec = precision of state parameters +* zR,zG,zB and zA in fp format 8.zprec +*/ +template +void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0) +{ + // halve the radius if we're using two passes + if (improvedQuality) + radius *= qreal(0.5); + + Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied + || img.format() == QImage::Format_RGB32 + || img.format() == QImage::Format_Indexed8); + + // choose the alpha such that pixels at radius distance from a fully + // saturated pixel will have an alpha component of no greater than + // the cutOffIntensity + const qreal cutOffIntensity = 2; + int alpha = radius <= qreal(1e-5) + ? ((1 << aprec)-1) + : qRound((1<(img, row, alpha); + } + + QImage temp(img.height(), img.width(), img.format()); + if (transposed >= 0) { + if (img.depth() == 8) { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } else { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } + + img_height = temp.height(); + for (int row = 0; row < img_height; ++row) { + for (int i = 0; i <= int(improvedQuality); ++i) + qt_blurrow(temp, row, alpha); + } + + if (transposed == 0) { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } + } else { + img = temp; + } +} +#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) ) +#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) ) + +Q_GUI_EXPORT QImage qt_halfScaled(const QImage &source) +{ + if (source.width() < 2 || source.height() < 2) + return QImage(); + + QImage srcImage = source; + + if (source.format() == QImage::Format_Indexed8) { + // assumes grayscale + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + int sx = srcImage.bytesPerLine(); + int sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + int dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2) + *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2; + } + + return dest; + } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) { + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + int sx = srcImage.bytesPerLine(); + int sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + int dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) { + // alpha + q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3])); + // rgb + const quint16 p16_1 = (p1[2] << 8) | p1[1]; + const quint16 p16_2 = (p1[5] << 8) | p1[4]; + const quint16 p16_3 = (p2[2] << 8) | p2[1]; + const quint16 p16_4 = (p2[5] << 8) | p2[4]; + const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4)); + q[1] = result & 0xff; + q[2] = result >> 8; + } + } + + return dest; + } else if (source.format() != QImage::Format_ARGB32_Premultiplied + && source.format() != QImage::Format_RGB32) + { + srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + + const quint32 *src = reinterpret_cast(const_cast(srcImage).bits()); + int sx = srcImage.bytesPerLine() >> 2; + int sx2 = sx << 1; + + quint32 *dst = reinterpret_cast(dest.bits()); + int dx = dest.bytesPerLine() >> 2; + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const quint32 *p1 = src; + const quint32 *p2 = src + sx; + quint32 *q = dst; + for (int x = ww; x; --x, q++, p1 += 2, p2 += 2) + *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1])); + } + + return dest; +} + +Q_GUI_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0) +{ + if (blurImage.format() != QImage::Format_ARGB32_Premultiplied + && blurImage.format() != QImage::Format_RGB32) + { + blurImage = blurImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + + qreal scale = 1; + if (radius >= 4 && blurImage.width() >= 2 && blurImage.height() >= 2) { + blurImage = qt_halfScaled(blurImage); + scale = 2; + radius *= qreal(0.5); + } + + if (alphaOnly) + expblur<12, 10, true>(blurImage, radius, quality, transposed); + else + expblur<12, 10, false>(blurImage, radius, quality, transposed); + + if (p) { + p->scale(scale, scale); + p->setRenderHint(QPainter::SmoothPixmapTransform); + p->drawImage(QRect(0, 0, blurImage.width(), blurImage.height()), blurImage); + } +} + +Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0) +{ + if (blurImage.format() == QImage::Format_Indexed8) + expblur<12, 10, true>(blurImage, radius, quality, transposed); + else + expblur<12, 10, false>(blurImage, radius, quality, transposed); +} + +Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + +/*! + \internal +*/ +void QPixmapBlurFilter::draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &rect) const +{ + Q_D(const QPixmapBlurFilter); + if (!painter->isActive()) + return; + + if (src.isNull()) + return; + + QRectF srcRect = rect; + if (srcRect.isNull()) + srcRect = src.rect(); + + if (d->radius <= 1) { + painter->drawPixmap(srcRect.translated(p), src, srcRect); + return; + } + + qreal scaledRadius = radiusScale * d->radius; + qreal scale; + if (qt_scaleForTransform(painter->transform(), &scale)) + scaledRadius /= scale; + + QPixmapFilter *filter = painter->paintEngine() && painter->paintEngine()->isExtended() ? + static_cast(painter->paintEngine())->pixmapFilter(type(), this) : 0; + QPixmapBlurFilter *blurFilter = static_cast(filter); + if (blurFilter) { + blurFilter->setRadius(scaledRadius); + blurFilter->setBlurHints(d->hints); + blurFilter->draw(painter, p, src, srcRect); + return; + } + + QImage srcImage; + QImage destImage; + + if (srcRect == src.rect()) { + srcImage = src.toImage(); + } else { + QRect rect = srcRect.toAlignedRect().intersected(src.rect()); + srcImage = src.copy(rect).toImage(); + } + + QTransform transform = painter->worldTransform(); + painter->translate(p); + qt_blurImage(painter, srcImage, scaledRadius, (d->hints & QGraphicsBlurEffect::QualityHint), false); + painter->setWorldTransform(transform); +} + +// grayscales the image to dest (could be same). If rect isn't defined +// destination image size is used to determine the dimension of grayscaling +// process. +static void grayscale(const QImage &image, QImage &dest, const QRect& rect = QRect()) +{ + QRect destRect = rect; + QRect srcRect = rect; + if (rect.isNull()) { + srcRect = dest.rect(); + destRect = dest.rect(); + } + if (&image != &dest) { + destRect.moveTo(QPoint(0, 0)); + } + + unsigned int *data = (unsigned int *)image.bits(); + unsigned int *outData = (unsigned int *)dest.bits(); + + if (dest.size() == image.size() && image.rect() == srcRect) { + // a bit faster loop for grayscaling everything + int pixels = dest.width() * dest.height(); + for (int i = 0; i < pixels; ++i) { + int val = qGray(data[i]); + outData[i] = qRgba(val, val, val, qAlpha(data[i])); + } + } else { + int yd = destRect.top(); + for (int y = srcRect.top(); y <= srcRect.bottom() && y < image.height(); y++) { + data = (unsigned int*)image.scanLine(y); + outData = (unsigned int*)dest.scanLine(yd++); + int xd = destRect.left(); + for (int x = srcRect.left(); x <= srcRect.right() && x < image.width(); x++) { + int val = qGray(data[x]); + outData[xd++] = qRgba(val, val, val, qAlpha(data[x])); + } + } + } +} + +/*! + \class QPixmapColorizeFilter + \since 4.5 + \ingroup painting + + \brief The QPixmapColorizeFilter class provides colorizing + filtering for pixmaps. + + A colorize filter gives the pixmap a tint of its color(). The + filter first grayscales the pixmap and then converts those to + colorized values using QPainter::CompositionMode_Screen with the + chosen color. The alpha-channel is not changed. + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapfilter.cpp 0 + + \sa QPainter::CompositionMode + + \internal +*/ +class QPixmapColorizeFilterPrivate : public QPixmapFilterPrivate +{ + Q_DECLARE_PUBLIC(QPixmapColorizeFilter) +public: + QColor color; + qreal strength; + quint32 opaque : 1; + quint32 alphaBlend : 1; + quint32 padding : 30; +}; + +/*! + Constructs an pixmap colorize filter. + + Default color value for colorizing is QColor(0, 0, 192). + + \internal +*/ +QPixmapColorizeFilter::QPixmapColorizeFilter(QObject *parent) + : QPixmapFilter(*new QPixmapColorizeFilterPrivate, ColorizeFilter, parent) +{ + Q_D(QPixmapColorizeFilter); + d->color = QColor(0, 0, 192); + d->strength = qreal(1); + d->opaque = true; + d->alphaBlend = false; +} + +/*! + Gets the color of the colorize filter. + + \internal +*/ +QColor QPixmapColorizeFilter::color() const +{ + Q_D(const QPixmapColorizeFilter); + return d->color; +} + +/*! + Sets the color of the colorize filter to the \a color specified. + + \internal +*/ +void QPixmapColorizeFilter::setColor(const QColor &color) +{ + Q_D(QPixmapColorizeFilter); + d->color = color; +} + +/*! + Gets the strength of the colorize filter, 1.0 means full colorized while + 0.0 equals to no filtering at all. + + \internal +*/ +qreal QPixmapColorizeFilter::strength() const +{ + Q_D(const QPixmapColorizeFilter); + return d->strength; +} + +/*! + Sets the strength of the colorize filter to \a strength. + + \internal +*/ +void QPixmapColorizeFilter::setStrength(qreal strength) +{ + Q_D(QPixmapColorizeFilter); + d->strength = qBound(qreal(0), strength, qreal(1)); + d->opaque = !qFuzzyIsNull(d->strength); + d->alphaBlend = !qFuzzyIsNull(d->strength - 1); +} + +/*! + \internal +*/ +void QPixmapColorizeFilter::draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect) const +{ + Q_D(const QPixmapColorizeFilter); + + if (src.isNull()) + return; + + QPixmapFilter *filter = painter->paintEngine() && painter->paintEngine()->isExtended() ? + static_cast(painter->paintEngine())->pixmapFilter(type(), this) : 0; + QPixmapColorizeFilter *colorizeFilter = static_cast(filter); + if (colorizeFilter) { + colorizeFilter->setColor(d->color); + colorizeFilter->setStrength(d->strength); + colorizeFilter->draw(painter, dest, src, srcRect); + return; + } + + // falling back to raster implementation + + if (!d->opaque) { + painter->drawPixmap(dest, src, srcRect); + return; + } + + QImage srcImage; + QImage destImage; + + if (srcRect.isNull()) { + srcImage = src.toImage(); + srcImage = srcImage.convertToFormat(srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); + destImage = QImage(srcImage.size(), srcImage.format()); + } else { + QRect rect = srcRect.toAlignedRect().intersected(src.rect()); + + srcImage = src.copy(rect).toImage(); + srcImage = srcImage.convertToFormat(srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); + destImage = QImage(rect.size(), srcImage.format()); + } + + // do colorizing + QPainter destPainter(&destImage); + grayscale(srcImage, destImage, srcImage.rect()); + destPainter.setCompositionMode(QPainter::CompositionMode_Screen); + destPainter.fillRect(srcImage.rect(), d->color); + destPainter.end(); + + if (d->alphaBlend) { + // alpha blending srcImage and destImage + QImage buffer = srcImage; + QPainter bufPainter(&buffer); + bufPainter.setOpacity(d->strength); + bufPainter.drawImage(0, 0, destImage); + bufPainter.end(); + destImage = buffer; + } + + if (srcImage.hasAlphaChannel()) + destImage.setAlphaChannel(srcImage.alphaChannel()); + + painter->drawImage(dest, destImage); +} + +class QPixmapDropShadowFilterPrivate : public QPixmapFilterPrivate +{ +public: + QPixmapDropShadowFilterPrivate() + : offset(8, 8), color(63, 63, 63, 180), radius(1) {} + + QPointF offset; + QColor color; + qreal radius; +}; + +/*! + \class QPixmapDropShadowFilter + \since 4.5 + \ingroup painting + + \brief The QPixmapDropShadowFilter class is a convenience class + for drawing pixmaps with drop shadows. + + The drop shadow is produced by taking a copy of the source pixmap + and applying a color to the copy using a + QPainter::CompositionMode_DestinationIn operation. This produces a + homogeneously-colored pixmap which is then drawn using a + QPixmapConvolutionFilter at an offset. The original pixmap is + drawn on top. + + The QPixmapDropShadowFilter class provides some customization + options to specify how the drop shadow should appear. The color of + the drop shadow can be modified using the setColor() function, the + drop shadow offset can be modified using the setOffset() function, + and the blur radius of the drop shadow can be changed through the + setBlurRadius() function. + + By default, the drop shadow is a dark gray shadow, blurred with a + radius of 1 at an offset of 8 pixels towards the lower right. + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapfilter.cpp 2 + + \sa QPixmapColorizeFilter, QPixmapConvolutionFilter + + \internal + */ + +/*! + Constructs drop shadow filter. + + \internal +*/ +QPixmapDropShadowFilter::QPixmapDropShadowFilter(QObject *parent) + : QPixmapFilter(*new QPixmapDropShadowFilterPrivate, DropShadowFilter, parent) +{ +} + +/*! + Destroys drop shadow filter. + + \internal +*/ +QPixmapDropShadowFilter::~QPixmapDropShadowFilter() +{ +} + +/*! + Returns the radius in pixels of the blur on the drop shadow. + + A smaller radius results in a sharper shadow. + + \sa color(), offset() + + \internal +*/ +qreal QPixmapDropShadowFilter::blurRadius() const +{ + Q_D(const QPixmapDropShadowFilter); + return d->radius; +} + +/*! + Sets the radius in pixels of the blur on the drop shadow to the \a radius specified. + + Using a smaller radius results in a sharper shadow. + + \sa setColor(), setOffset() + + \internal +*/ +void QPixmapDropShadowFilter::setBlurRadius(qreal radius) +{ + Q_D(QPixmapDropShadowFilter); + d->radius = radius; +} + +/*! + Returns the color of the drop shadow. + + \sa blurRadius(), offset() + + \internal +*/ +QColor QPixmapDropShadowFilter::color() const +{ + Q_D(const QPixmapDropShadowFilter); + return d->color; +} + +/*! + Sets the color of the drop shadow to the \a color specified. + + \sa setBlurRadius(), setOffset() + + \internal +*/ +void QPixmapDropShadowFilter::setColor(const QColor &color) +{ + Q_D(QPixmapDropShadowFilter); + d->color = color; +} + +/*! + Returns the shadow offset in pixels. + + \sa blurRadius(), color() + + \internal +*/ +QPointF QPixmapDropShadowFilter::offset() const +{ + Q_D(const QPixmapDropShadowFilter); + return d->offset; +} + +/*! + Sets the shadow offset in pixels to the \a offset specified. + + \sa setBlurRadius(), setColor() + + \internal +*/ +void QPixmapDropShadowFilter::setOffset(const QPointF &offset) +{ + Q_D(QPixmapDropShadowFilter); + d->offset = offset; +} + +/*! + \fn void QPixmapDropShadowFilter::setOffset(qreal dx, qreal dy) + \overload + + Sets the shadow offset in pixels to be the displacement specified by the + horizontal \a dx and vertical \a dy coordinates. + + \sa setBlurRadius(), setColor() + + \internal +*/ + +/*! + \internal + */ +QRectF QPixmapDropShadowFilter::boundingRectFor(const QRectF &rect) const +{ + Q_D(const QPixmapDropShadowFilter); + return rect.united(rect.translated(d->offset).adjusted(-d->radius, -d->radius, d->radius, d->radius)); +} + +/*! + \internal + */ +void QPixmapDropShadowFilter::draw(QPainter *p, + const QPointF &pos, + const QPixmap &px, + const QRectF &src) const +{ + Q_D(const QPixmapDropShadowFilter); + + if (px.isNull()) + return; + + QPixmapFilter *filter = p->paintEngine() && p->paintEngine()->isExtended() ? + static_cast(p->paintEngine())->pixmapFilter(type(), this) : 0; + QPixmapDropShadowFilter *dropShadowFilter = static_cast(filter); + if (dropShadowFilter) { + dropShadowFilter->setColor(d->color); + dropShadowFilter->setBlurRadius(d->radius); + dropShadowFilter->setOffset(d->offset); + dropShadowFilter->draw(p, pos, px, src); + return; + } + + QImage tmp(px.size(), QImage::Format_ARGB32_Premultiplied); + tmp.fill(0); + QPainter tmpPainter(&tmp); + tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); + tmpPainter.drawPixmap(d->offset, px); + tmpPainter.end(); + + // blur the alpha channel + QImage blurred(tmp.size(), QImage::Format_ARGB32_Premultiplied); + blurred.fill(0); + QPainter blurPainter(&blurred); + qt_blurImage(&blurPainter, tmp, d->radius, false, true); + blurPainter.end(); + + tmp = blurred; + + // blacken the image... + tmpPainter.begin(&tmp); + tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + tmpPainter.fillRect(tmp.rect(), d->color); + tmpPainter.end(); + + // draw the blurred drop shadow... + p->drawImage(pos, tmp); + + // Draw the actual pixmap... + p->drawPixmap(pos, px, src); +} + +QT_END_NAMESPACE + +#endif //QT_NO_GRAPHICSEFFECT diff --git a/src/gui/image/qpixmapfilter_p.h b/src/gui/image/qpixmapfilter_p.h new file mode 100644 index 0000000000..961866c19b --- /dev/null +++ b/src/gui/image/qpixmapfilter_p.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPIXMAPFILTER_H +#define QPIXMAPFILTER_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 +#include +#include + +#ifndef QT_NO_GRAPHICSEFFECT +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPainter; +class QPixmapData; + +class QPixmapFilterPrivate; + +class Q_GUI_EXPORT QPixmapFilter : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapFilter) +public: + virtual ~QPixmapFilter() = 0; + + enum FilterType { + ConvolutionFilter, + ColorizeFilter, + DropShadowFilter, + BlurFilter, + + UserFilter = 1024 + }; + + FilterType type() const; + + virtual QRectF boundingRectFor(const QRectF &rect) const; + + virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0; + +protected: + QPixmapFilter(QPixmapFilterPrivate &d, FilterType type, QObject *parent); + QPixmapFilter(FilterType type, QObject *parent); +}; + +class QPixmapConvolutionFilterPrivate; + +class Q_GUI_EXPORT QPixmapConvolutionFilter : public QPixmapFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapConvolutionFilter) + +public: + QPixmapConvolutionFilter(QObject *parent = 0); + ~QPixmapConvolutionFilter(); + + void setConvolutionKernel(const qreal *matrix, int rows, int columns); + + QRectF boundingRectFor(const QRectF &rect) const; + void draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect = QRectF()) const; + +private: + friend class QGLPixmapConvolutionFilter; + friend class QVGPixmapConvolutionFilter; + const qreal *convolutionKernel() const; + int rows() const; + int columns() const; +}; + +class QPixmapBlurFilterPrivate; + +class Q_GUI_EXPORT QPixmapBlurFilter : public QPixmapFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapBlurFilter) + +public: + QPixmapBlurFilter(QObject *parent = 0); + ~QPixmapBlurFilter(); + + void setRadius(qreal radius); + void setBlurHints(QGraphicsBlurEffect::BlurHints hints); + + qreal radius() const; + QGraphicsBlurEffect::BlurHints blurHints() const; + + QRectF boundingRectFor(const QRectF &rect) const; + void draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect = QRectF()) const; + +private: + friend class QGLPixmapBlurFilter; +}; + +class QPixmapColorizeFilterPrivate; + +class Q_GUI_EXPORT QPixmapColorizeFilter : public QPixmapFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapColorizeFilter) + +public: + QPixmapColorizeFilter(QObject *parent = 0); + + void setColor(const QColor& color); + QColor color() const; + + void setStrength(qreal strength); + qreal strength() const; + + void draw(QPainter *painter, const QPointF &dest, const QPixmap &src, const QRectF &srcRect = QRectF()) const; +}; + +class QPixmapDropShadowFilterPrivate; + +class Q_GUI_EXPORT QPixmapDropShadowFilter : public QPixmapFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPixmapDropShadowFilter) + +public: + QPixmapDropShadowFilter(QObject *parent = 0); + ~QPixmapDropShadowFilter(); + + QRectF boundingRectFor(const QRectF &rect) const; + void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const; + + qreal blurRadius() const; + void setBlurRadius(qreal radius); + + QColor color() const; + void setColor(const QColor &color); + + QPointF offset() const; + void setOffset(const QPointF &offset); + inline void setOffset(qreal dx, qreal dy) { setOffset(QPointF(dx, dy)); } +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QT_NO_GRAPHICSEFFECT +#endif // QPIXMAPFILTER_H diff --git a/src/gui/image/qpnghandler.cpp b/src/gui/image/qpnghandler.cpp new file mode 100644 index 0000000000..c2a8b00ef5 --- /dev/null +++ b/src/gui/image/qpnghandler.cpp @@ -0,0 +1,991 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qpnghandler_p.h" + +#ifndef QT_NO_IMAGEFORMAT_PNG +#include +#include +#include +#include +#include +#include +#include + +#ifdef QT_USE_BUNDLED_LIBPNG +#include <../../3rdparty/libpng/png.h> +#include <../../3rdparty/libpng/pngconf.h> +#else +#include +#include +#endif + +#ifdef Q_OS_WINCE +#define CALLBACK_CALL_TYPE __cdecl +#else +#define CALLBACK_CALL_TYPE +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_WINCE) && defined(STANDARDSHELL_UI_MODEL) +# define Q_INTERNAL_WIN_NO_THROW __declspec(nothrow) +#else +# define Q_INTERNAL_WIN_NO_THROW +#endif + +// avoid going through QImage::scanLine() which calls detach +#define FAST_SCAN_LINE(data, bpl, y) (data + (y) * bpl) + +/* + All PNG files load to the minimal QImage equivalent. + + All QImage formats output to reasonably efficient PNG equivalents. + Never to grayscale. +*/ + +class QPngHandlerPrivate +{ +public: + enum State { + Ready, + ReadHeader, + ReadingEnd, + Error + }; + + QPngHandlerPrivate(QPngHandler *qq) + : gamma(0.0), quality(2), png_ptr(0), info_ptr(0), + end_info(0), row_pointers(0), state(Ready), q(qq) + { } + + float gamma; + int quality; + QString description; + QStringList readTexts; + + png_struct *png_ptr; + png_info *info_ptr; + png_info *end_info; + png_byte **row_pointers; + + bool readPngHeader(); + bool readPngImage(QImage *image); + void readPngTexts(png_info *info); + + QImage::Format readImageFormat(); + + State state; + + QPngHandler *q; +}; + + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +class QPNGImageWriter { +public: + explicit QPNGImageWriter(QIODevice*); + ~QPNGImageWriter(); + + enum DisposalMethod { Unspecified, NoDisposal, RestoreBackground, RestoreImage }; + void setDisposalMethod(DisposalMethod); + void setLooping(int loops=0); // 0 == infinity + void setFrameDelay(int msecs); + void setGamma(float); + + bool writeImage(const QImage& img, int x, int y); + bool writeImage(const QImage& img, int quality, const QString &description, int x, int y); + bool writeImage(const QImage& img) + { return writeImage(img, 0, 0); } + bool writeImage(const QImage& img, int quality, const QString &description) + { return writeImage(img, quality, description, 0, 0); } + + QIODevice* device() { return dev; } + +private: + QIODevice* dev; + int frames_written; + DisposalMethod disposal; + int looping; + int ms_delay; + float gamma; +}; + +static +void CALLBACK_CALL_TYPE iod_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) +{ + QPngHandlerPrivate *d = (QPngHandlerPrivate *)png_get_io_ptr(png_ptr); + QIODevice *in = d->q->device(); + + if (d->state == QPngHandlerPrivate::ReadingEnd && !in->isSequential() && (in->size() - in->pos()) < 4 && length == 4) { + // Workaround for certain malformed PNGs that lack the final crc bytes + uchar endcrc[4] = { 0xae, 0x42, 0x60, 0x82 }; + qMemCopy(data, endcrc, 4); + in->seek(in->size()); + return; + } + + while (length) { + int nr = in->read((char*)data, length); + if (nr <= 0) { + png_error(png_ptr, "Read Error"); + return; + } + length -= nr; + } +} + + +static +void CALLBACK_CALL_TYPE qpiw_write_fn(png_structp png_ptr, png_bytep data, png_size_t length) +{ + QPNGImageWriter* qpiw = (QPNGImageWriter*)png_get_io_ptr(png_ptr); + QIODevice* out = qpiw->device(); + + uint nr = out->write((char*)data, length); + if (nr != length) { + png_error(png_ptr, "Write Error"); + return; + } +} + + +static +void CALLBACK_CALL_TYPE qpiw_flush_fn(png_structp /* png_ptr */) +{ +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +static +void setup_qt(QImage& image, png_structp png_ptr, png_infop info_ptr, float screen_gamma=0.0) +{ + if (screen_gamma != 0.0 && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) { + double file_gamma; + png_get_gAMA(png_ptr, info_ptr, &file_gamma); + png_set_gamma(png_ptr, screen_gamma, file_gamma); + } + + png_uint_32 width; + png_uint_32 height; + int bit_depth; + int color_type; + png_bytep trans_alpha = 0; + png_color_16p trans_color_p = 0; + int num_trans; + png_colorp palette = 0; + int num_palette; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); + png_set_interlace_handling(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY) { + // Black & White or 8-bit grayscale + if (bit_depth == 1 && png_get_channels(png_ptr, info_ptr) == 1) { + png_set_invert_mono(png_ptr); + png_read_update_info(png_ptr, info_ptr); + if (image.size() != QSize(width, height) || image.format() != QImage::Format_Mono) { + image = QImage(width, height, QImage::Format_Mono); + if (image.isNull()) + return; + } + image.setColorCount(2); + image.setColor(1, qRgb(0,0,0)); + image.setColor(0, qRgb(255,255,255)); + } else if (bit_depth == 16 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_expand(png_ptr); + png_set_strip_16(png_ptr); + png_set_gray_to_rgb(png_ptr); + if (image.size() != QSize(width, height) || image.format() != QImage::Format_ARGB32) { + image = QImage(width, height, QImage::Format_ARGB32); + if (image.isNull()) + return; + } + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + png_set_swap_alpha(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + } else { + if (bit_depth == 16) + png_set_strip_16(png_ptr); + else if (bit_depth < 8) + png_set_packing(png_ptr); + int ncols = bit_depth < 8 ? 1 << bit_depth : 256; + png_read_update_info(png_ptr, info_ptr); + if (image.size() != QSize(width, height) || image.format() != QImage::Format_Indexed8) { + image = QImage(width, height, QImage::Format_Indexed8); + if (image.isNull()) + return; + } + image.setColorCount(ncols); + for (int i=0; igray; + if (g < ncols) { + image.setColor(g, 0); + } + } + } + } else if (color_type == PNG_COLOR_TYPE_PALETTE + && png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette) + && num_palette <= 256) + { + // 1-bit and 8-bit color + if (bit_depth != 1) + png_set_packing(png_ptr); + png_read_update_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); + QImage::Format format = bit_depth == 1 ? QImage::Format_Mono : QImage::Format_Indexed8; + if (image.size() != QSize(width, height) || image.format() != format) { + image = QImage(width, height, format); + if (image.isNull()) + return; + } + png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + image.setColorCount(num_palette); + int i = 0; + if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color_p) && trans_alpha) { + while (i < num_trans) { + image.setColor(i, qRgba( + palette[i].red, + palette[i].green, + palette[i].blue, + trans_alpha[i] + ) + ); + i++; + } + } + while (i < num_palette) { + image.setColor(i, qRgba( + palette[i].red, + palette[i].green, + palette[i].blue, + 0xff + ) + ); + i++; + } + } else { + // 32-bit + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + png_set_expand(png_ptr); + + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + QImage::Format format = QImage::Format_ARGB32; + // Only add filler if no alpha, or we can get 5 channel data. + if (!(color_type & PNG_COLOR_MASK_ALPHA) + && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_filler(png_ptr, 0xff, QSysInfo::ByteOrder == QSysInfo::BigEndian ? + PNG_FILLER_BEFORE : PNG_FILLER_AFTER); + // We want 4 bytes, but it isn't an alpha channel + format = QImage::Format_RGB32; + } + if (image.size() != QSize(width, height) || image.format() != format) { + image = QImage(width, height, format); + if (image.isNull()) + return; + } + + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) + png_set_swap_alpha(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + } + + // Qt==ARGB==Big(ARGB)==Little(BGRA) + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + png_set_bgr(png_ptr); + } +} + + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif +static void CALLBACK_CALL_TYPE qt_png_warning(png_structp /*png_ptr*/, png_const_charp message) +{ + qWarning("libpng warning: %s", message); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + + +/*! + \internal +*/ +void Q_INTERNAL_WIN_NO_THROW QPngHandlerPrivate::readPngTexts(png_info *info) +{ +#ifndef QT_NO_IMAGE_TEXT + png_textp text_ptr; + int num_text=0; + png_get_text(png_ptr, info, &text_ptr, &num_text); + + while (num_text--) { + QString key, value; + key = QString::fromLatin1(text_ptr->key); +#if defined(PNG_iTXt_SUPPORTED) + if (text_ptr->itxt_length) { + value = QString::fromUtf8(text_ptr->text, int(text_ptr->itxt_length)); + } else +#endif + { + value = QString::fromLatin1(text_ptr->text, int(text_ptr->text_length)); + } + if (!description.isEmpty()) + description += QLatin1String("\n\n"); + description += key + QLatin1String(": ") + value.simplified(); + readTexts.append(key); + readTexts.append(value); + text_ptr++; + } +#endif +} + + +/*! + \internal +*/ +bool Q_INTERNAL_WIN_NO_THROW QPngHandlerPrivate::readPngHeader() +{ + state = Error; + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if (!png_ptr) + return false; + + png_set_error_fn(png_ptr, 0, 0, qt_png_warning); + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, 0, 0); + png_ptr = 0; + return false; + } + + end_info = png_create_info_struct(png_ptr); + if (!end_info) { + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + png_ptr = 0; + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + png_ptr = 0; + return false; + } + + png_set_read_fn(png_ptr, this, iod_read_fn); + png_read_info(png_ptr, info_ptr); + + readPngTexts(info_ptr); + + state = ReadHeader; + return true; +} + +/*! + \internal +*/ +bool Q_INTERNAL_WIN_NO_THROW QPngHandlerPrivate::readPngImage(QImage *outImage) +{ + if (state == Error) + return false; + + if (state == Ready && !readPngHeader()) { + state = Error; + return false; + } + + row_pointers = 0; + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + delete [] row_pointers; + png_ptr = 0; + state = Error; + return false; + } + + setup_qt(*outImage, png_ptr, info_ptr, gamma); + + if (outImage->isNull()) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + delete [] row_pointers; + png_ptr = 0; + state = Error; + return false; + } + + png_uint_32 width; + png_uint_32 height; + int bit_depth; + int color_type; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + 0, 0, 0); + + uchar *data = outImage->bits(); + int bpl = outImage->bytesPerLine(); + row_pointers = new png_bytep[height]; + + for (uint y = 0; y < height; y++) + row_pointers[y] = data + y * bpl; + + png_read_image(png_ptr, row_pointers); + +#if 0 // libpng takes care of this. + png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) + if (outImage->depth()==32 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + QRgb trans = 0xFF000000 | qRgb( + (info_ptr->trans_values.red << 8 >> bit_depth)&0xff, + (info_ptr->trans_values.green << 8 >> bit_depth)&0xff, + (info_ptr->trans_values.blue << 8 >> bit_depth)&0xff); + for (uint y=0; ywidth; x++) { + if (((uint**)jt)[y][x] == trans) { + ((uint**)jt)[y][x] &= 0x00FFFFFF; + } else { + } + } + } + } +#endif + + outImage->setDotsPerMeterX(png_get_x_pixels_per_meter(png_ptr,info_ptr)); + outImage->setDotsPerMeterY(png_get_y_pixels_per_meter(png_ptr,info_ptr)); + + state = ReadingEnd; + png_read_end(png_ptr, end_info); + +#ifndef QT_NO_IMAGE_TEXT + readPngTexts(end_info); + for (int i = 0; i < readTexts.size()-1; i+=2) + outImage->setText(readTexts.at(i), readTexts.at(i+1)); +#endif + + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + delete [] row_pointers; + png_ptr = 0; + state = Ready; + + // sanity check palette entries + if (color_type == PNG_COLOR_TYPE_PALETTE + && outImage->format() == QImage::Format_Indexed8) { + int color_table_size = outImage->colorCount(); + for (int y=0; y<(int)height; ++y) { + uchar *p = FAST_SCAN_LINE(data, bpl, y); + uchar *end = p + width; + while (p < end) { + if (*p >= color_table_size) + *p = 0; + ++p; + } + } + } + + return true; +} + +QImage::Format QPngHandlerPrivate::readImageFormat() +{ + QImage::Format format = QImage::Format_Invalid; + png_uint_32 width, height; + int bit_depth, color_type; + png_colorp palette; + int num_palette; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); + if (color_type == PNG_COLOR_TYPE_GRAY) { + // Black & White or 8-bit grayscale + if (bit_depth == 1 && png_get_channels(png_ptr, info_ptr) == 1) { + format = QImage::Format_Mono; + } else if (bit_depth == 16 && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + format = QImage::Format_ARGB32; + } else { + format = QImage::Format_Indexed8; + } + } else if (color_type == PNG_COLOR_TYPE_PALETTE + && png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette) + && num_palette <= 256) + { + // 1-bit and 8-bit color + if (bit_depth != 1) + png_set_packing(png_ptr); + png_read_update_info(png_ptr, info_ptr); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); + format = bit_depth == 1 ? QImage::Format_Mono : QImage::Format_Indexed8; + } else { + // 32-bit + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + format = QImage::Format_ARGB32; + // Only add filler if no alpha, or we can get 5 channel data. + if (!(color_type & PNG_COLOR_MASK_ALPHA) + && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + // We want 4 bytes, but it isn't an alpha channel + format = QImage::Format_RGB32; + } + } + + return format; +} + +QPNGImageWriter::QPNGImageWriter(QIODevice* iod) : + dev(iod), + frames_written(0), + disposal(Unspecified), + looping(-1), + ms_delay(-1), + gamma(0.0) +{ +} + +QPNGImageWriter::~QPNGImageWriter() +{ +} + +void QPNGImageWriter::setDisposalMethod(DisposalMethod dm) +{ + disposal = dm; +} + +void QPNGImageWriter::setLooping(int loops) +{ + looping = loops; +} + +void QPNGImageWriter::setFrameDelay(int msecs) +{ + ms_delay = msecs; +} + +void QPNGImageWriter::setGamma(float g) +{ + gamma = g; +} + + +#ifndef QT_NO_IMAGE_TEXT +static void set_text(const QImage &image, png_structp png_ptr, png_infop info_ptr, + const QString &description) +{ + QMap text; + foreach (const QString &key, image.textKeys()) { + if (!key.isEmpty()) + text.insert(key, image.text(key)); + } + foreach (const QString &pair, description.split(QLatin1String("\n\n"))) { + int index = pair.indexOf(QLatin1Char(':')); + if (index >= 0 && pair.indexOf(QLatin1Char(' ')) < index) { + QString s = pair.simplified(); + if (!s.isEmpty()) + text.insert(QLatin1String("Description"), s); + } else { + QString key = pair.left(index); + if (!key.simplified().isEmpty()) + text.insert(key, pair.mid(index + 2).simplified()); + } + } + + if (text.isEmpty()) + return; + + png_textp text_ptr = new png_text[text.size()]; + qMemSet(text_ptr, 0, text.size() * sizeof(png_text)); + + QMap::ConstIterator it = text.constBegin(); + int i = 0; + while (it != text.constEnd()) { + text_ptr[i].key = qstrdup(it.key().left(79).toLatin1().constData()); + bool noCompress = (it.value().length() < 40); + +#ifdef PNG_iTXt_SUPPORTED + bool needsItxt = false; + foreach(const QChar c, it.value()) { + uchar ch = c.cell(); + if (c.row() || (ch < 0x20 && ch != '\n') || (ch > 0x7e && ch < 0xa0)) { + needsItxt = true; + break; + } + } + + if (needsItxt) { + text_ptr[i].compression = noCompress ? PNG_ITXT_COMPRESSION_NONE : PNG_ITXT_COMPRESSION_zTXt; + QByteArray value = it.value().toUtf8(); + text_ptr[i].text = qstrdup(value.constData()); + text_ptr[i].itxt_length = value.size(); + text_ptr[i].lang = const_cast("UTF-8"); + text_ptr[i].lang_key = qstrdup(it.key().toUtf8().constData()); + } + else +#endif + { + text_ptr[i].compression = noCompress ? PNG_TEXT_COMPRESSION_NONE : PNG_TEXT_COMPRESSION_zTXt; + QByteArray value = it.value().toLatin1(); + text_ptr[i].text = qstrdup(value.constData()); + text_ptr[i].text_length = value.size(); + } + ++i; + ++it; + } + + png_set_text(png_ptr, info_ptr, text_ptr, i); + for (i = 0; i < text.size(); ++i) { + delete [] text_ptr[i].key; + delete [] text_ptr[i].text; +#ifdef PNG_iTXt_SUPPORTED + delete [] text_ptr[i].lang_key; +#endif + } + delete [] text_ptr; +} +#endif + +bool QPNGImageWriter::writeImage(const QImage& image, int off_x, int off_y) +{ + return writeImage(image, -1, QString(), off_x, off_y); +} + +bool Q_INTERNAL_WIN_NO_THROW QPNGImageWriter::writeImage(const QImage& image, int quality_in, const QString &description, + int off_x_in, int off_y_in) +{ +#ifdef QT_NO_IMAGE_TEXT + Q_UNUSED(description); +#endif + + QPoint offset = image.offset(); + int off_x = off_x_in + offset.x(); + int off_y = off_y_in + offset.y(); + + png_structp png_ptr; + png_infop info_ptr; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0); + if (!png_ptr) { + return false; + } + + png_set_error_fn(png_ptr, 0, 0, qt_png_warning); + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, 0); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; + } + + int quality = quality_in; + if (quality >= 0) { + if (quality > 9) { + qWarning("PNG: Quality %d out of range", quality); + quality = 9; + } + png_set_compression_level(png_ptr, quality); + } + + png_set_write_fn(png_ptr, (void*)this, qpiw_write_fn, qpiw_flush_fn); + + + int color_type = 0; + if (image.colorCount()) + color_type = PNG_COLOR_TYPE_PALETTE; + else if (image.hasAlphaChannel()) + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + else + color_type = PNG_COLOR_TYPE_RGB; + + png_set_IHDR(png_ptr, info_ptr, image.width(), image.height(), + image.depth() == 1 ? 1 : 8, // per channel + color_type, 0, 0, 0); // sets #channels + + if (gamma != 0.0) { + png_set_gAMA(png_ptr, info_ptr, 1.0/gamma); + } + + png_color_8 sig_bit; + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + sig_bit.alpha = image.hasAlphaChannel() ? 8 : 0; + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + + if (image.format() == QImage::Format_MonoLSB) + png_set_packswap(png_ptr); + + if (image.colorCount()) { + // Paletted + int num_palette = qMin(256, image.colorCount()); + png_color palette[256]; + png_byte trans[256]; + int num_trans = 0; + for (int i=0; i 0) + png_set_sig_bytes(png_ptr, 8); + + if (image.dotsPerMeterX() > 0 || image.dotsPerMeterY() > 0) { + png_set_pHYs(png_ptr, info_ptr, + image.dotsPerMeterX(), image.dotsPerMeterY(), + PNG_RESOLUTION_METER); + } + +#ifndef QT_NO_IMAGE_TEXT + set_text(image, png_ptr, info_ptr, description); +#endif + png_write_info(png_ptr, info_ptr); + + if (image.depth() != 1) + png_set_packing(png_ptr); + + if (color_type == PNG_COLOR_TYPE_RGB && image.format() != QImage::Format_RGB888) + png_set_filler(png_ptr, 0, + QSysInfo::ByteOrder == QSysInfo::BigEndian ? + PNG_FILLER_BEFORE : PNG_FILLER_AFTER); + + if (looping >= 0 && frames_written == 0) { + uchar data[13] = "NETSCAPE2.0"; + // 0123456789aBC + data[0xB] = looping%0x100; + data[0xC] = looping/0x100; + png_write_chunk(png_ptr, (png_byte*)"gIFx", data, 13); + } + if (ms_delay >= 0 || disposal!=Unspecified) { + uchar data[4]; + data[0] = disposal; + data[1] = 0; + data[2] = (ms_delay/10)/0x100; // hundredths + data[3] = (ms_delay/10)%0x100; + png_write_chunk(png_ptr, (png_byte*)"gIFg", data, 4); + } + + int height = image.height(); + int width = image.width(); + switch (image.format()) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + case QImage::Format_Indexed8: + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_RGB888: + { + png_bytep* row_pointers = new png_bytep[height]; + for (int y=0; y= 0) { + quality = qMin(quality, 100); + quality = (100-quality) * 9 / 91; // map [0,100] -> [9,0] + } + writer.setGamma(gamma); + return writer.writeImage(image, quality, description); +} + +QPngHandler::QPngHandler() + : d(new QPngHandlerPrivate(this)) +{ +} + +QPngHandler::~QPngHandler() +{ + if (d->png_ptr) + png_destroy_read_struct(&d->png_ptr, &d->info_ptr, &d->end_info); + delete d; +} + +bool QPngHandler::canRead() const +{ + if (d->state == QPngHandlerPrivate::Ready && !canRead(device())) + return false; + + if (d->state != QPngHandlerPrivate::Error) { + setFormat("png"); + return true; + } + + return false; +} + +bool QPngHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QPngHandler::canRead() called with no device"); + return false; + } + + return device->peek(8) == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; +} + +bool QPngHandler::read(QImage *image) +{ + if (!canRead()) + return false; + return d->readPngImage(image); +} + +bool QPngHandler::write(const QImage &image) +{ + return write_png_image(image, device(), d->quality, d->gamma, d->description); +} + +bool QPngHandler::supportsOption(ImageOption option) const +{ + return option == Gamma + || option == Description + || option == ImageFormat + || option == Quality + || option == Size; +} + +QVariant QPngHandler::option(ImageOption option) const +{ + if (d->state == QPngHandlerPrivate::Error) + return QVariant(); + if (d->state == QPngHandlerPrivate::Ready && !d->readPngHeader()) + return QVariant(); + + if (option == Gamma) + return d->gamma; + else if (option == Quality) + return d->quality; + else if (option == Description) + return d->description; + else if (option == Size) + return QSize(png_get_image_width(d->png_ptr, d->info_ptr), + png_get_image_height(d->png_ptr, d->info_ptr)); + else if (option == ImageFormat) + return d->readImageFormat(); + return 0; +} + +void QPngHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == Gamma) + d->gamma = value.toFloat(); + else if (option == Quality) + d->quality = value.toInt(); + else if (option == Description) + d->description = value.toString(); +} + +QByteArray QPngHandler::name() const +{ + return "png"; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_PNG diff --git a/src/gui/image/qpnghandler.pri b/src/gui/image/qpnghandler.pri new file mode 100644 index 0000000000..bedf23ff12 --- /dev/null +++ b/src/gui/image/qpnghandler.pri @@ -0,0 +1,10 @@ +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qpnghandler_p.h +SOURCES += $$PWD/qpnghandler.cpp +contains(QT_CONFIG, system-png) { + if(unix|win32-g++*): LIBS_PRIVATE += -lpng + else:win32: LIBS += libpng.lib + +} else { + include($$PWD/../../3rdparty/libpng.pri) +} diff --git a/src/gui/image/qpnghandler_p.h b/src/gui/image/qpnghandler_p.h new file mode 100644 index 0000000000..3eaf9e0b30 --- /dev/null +++ b/src/gui/image/qpnghandler_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPNGHANDLER_P_H +#define QPNGHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qimageiohandler.h" + +#ifndef QT_NO_IMAGEFORMAT_PNG + +QT_BEGIN_NAMESPACE + +class QPngHandlerPrivate; +class QPngHandler : public QImageIOHandler +{ +public: + QPngHandler(); + ~QPngHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + + static bool canRead(QIODevice *device); + +private: + QPngHandlerPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_PNG +#endif // QPNGHANDLER_P_H diff --git a/src/gui/image/qppmhandler.cpp b/src/gui/image/qppmhandler.cpp new file mode 100644 index 0000000000..72b38222a0 --- /dev/null +++ b/src/gui/image/qppmhandler.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qppmhandler_p.h" + +#ifndef QT_NO_IMAGEFORMAT_PPM + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + PBM/PGM/PPM (ASCII and RAW) image read/write functions + *****************************************************************************/ + +static int read_pbm_int(QIODevice *d) +{ + char c; + int val = -1; + bool digit; + const int buflen = 100; + char buf[buflen]; + for (;;) { + if (!d->getChar(&c)) // end of file + break; + digit = isdigit((uchar) c); + if (val != -1) { + if (digit) { + val = 10*val + c - '0'; + continue; + } else { + if (c == '#') // comment + d->readLine(buf, buflen); + break; + } + } + if (digit) // first digit + val = c - '0'; + else if (isspace((uchar) c)) + continue; + else if (c == '#') + (void)d->readLine(buf, buflen); + else + break; + } + return val; +} + +static bool read_pbm_header(QIODevice *device, char& type, int& w, int& h, int& mcc) +{ + char buf[3]; + if (device->read(buf, 3) != 3) // read P[1-6] + return false; + + if (!(buf[0] == 'P' && isdigit((uchar) buf[1]) && isspace((uchar) buf[2]))) + return false; + + type = buf[1]; + if (type < '1' || type > '6') + return false; + + w = read_pbm_int(device); // get image width + h = read_pbm_int(device); // get image height + + if (type == '1' || type == '4') + mcc = 1; // ignore max color component + else + mcc = read_pbm_int(device); // get max color component + + if (w <= 0 || w > 32767 || h <= 0 || h > 32767 || mcc <= 0) + return false; // weird P.M image + + return true; +} + +static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, QImage *outImage) +{ + int nbits, y; + int pbm_bpl; + bool raw; + + QImage::Format format; + switch (type) { + case '1': // ascii PBM + case '4': // raw PBM + nbits = 1; + format = QImage::Format_Mono; + break; + case '2': // ascii PGM + case '5': // raw PGM + nbits = 8; + format = QImage::Format_Indexed8; + break; + case '3': // ascii PPM + case '6': // raw PPM + nbits = 32; + format = QImage::Format_RGB32; + break; + default: + return false; + } + raw = type >= '4'; + + int maxc = mcc; + if (maxc > 255) + maxc = 255; + if (outImage->size() != QSize(w, h) || outImage->format() != format) { + *outImage = QImage(w, h, format); + if (outImage->isNull()) + return false; + } + + pbm_bpl = (nbits*w+7)/8; // bytes per scanline in PBM + + if (raw) { // read raw data + if (nbits == 32) { // type 6 + pbm_bpl = mcc < 256 ? 3*w : 6*w; + uchar *buf24 = new uchar[pbm_bpl], *b; + QRgb *p; + QRgb *end; + for (y=0; yread((char *)buf24, pbm_bpl) != pbm_bpl) { + delete[] buf24; + return false; + } + p = (QRgb *)outImage->scanLine(y); + end = p + w; + b = buf24; + while (p < end) { + if (mcc < 256) { + *p++ = qRgb(b[0],b[1],b[2]); + b += 3; + } else { + *p++ = qRgb(((int(b[0]) * 256 + int(b[1]) + 1) * 256) / (mcc + 1) - 1, + ((int(b[2]) * 256 + int(b[3]) + 1) * 256) / (mcc + 1) - 1, + ((int(b[4]) * 256 + int(b[5]) + 1) * 256) / (mcc + 1) - 1); + b += 6; + } + } + } + delete[] buf24; + } else { // type 4,5 + for (y=0; yread((char *)outImage->scanLine(y), pbm_bpl) + != pbm_bpl) + return false; + } + } + } else { // read ascii data + register uchar *p; + int n; + for (y=0; yscanLine(y); + n = pbm_bpl; + if (nbits == 1) { + int b; + int bitsLeft = w; + while (n--) { + b = 0; + for (int i=0; i<8; i++) { + if (i < bitsLeft) + b = (b << 1) | (read_pbm_int(device) & 1); + else + b = (b << 1) | (0 & 1); // pad it our self if we need to + } + bitsLeft -= 8; + *p++ = b; + } + } else if (nbits == 8) { + if (mcc == maxc) { + while (n--) { + *p++ = read_pbm_int(device); + } + } else { + while (n--) { + *p++ = read_pbm_int(device) * maxc / mcc; + } + } + } else { // 32 bits + n /= 4; + int r, g, b; + if (mcc == maxc) { + while (n--) { + r = read_pbm_int(device); + g = read_pbm_int(device); + b = read_pbm_int(device); + *((QRgb*)p) = qRgb(r, g, b); + p += 4; + } + } else { + while (n--) { + r = read_pbm_int(device) * maxc / mcc; + g = read_pbm_int(device) * maxc / mcc; + b = read_pbm_int(device) * maxc / mcc; + *((QRgb*)p) = qRgb(r, g, b); + p += 4; + } + } + } + } + } + + if (nbits == 1) { // bitmap + outImage->setColorCount(2); + outImage->setColor(0, qRgb(255,255,255)); // white + outImage->setColor(1, qRgb(0,0,0)); // black + } else if (nbits == 8) { // graymap + outImage->setColorCount(maxc+1); + for (int i=0; i<=maxc; i++) + outImage->setColor(i, qRgb(i*255/maxc,i*255/maxc,i*255/maxc)); + } + + return true; +} + +static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, const QByteArray &sourceFormat) +{ + QByteArray str; + QImage image = sourceImage; + QByteArray format = sourceFormat; + + format = format.left(3); // ignore RAW part + bool gray = format == "pgm"; + + if (format == "pbm") { + image = image.convertToFormat(QImage::Format_Mono); + } else if (image.depth() == 1) { + image = image.convertToFormat(QImage::Format_Indexed8); + } else { + switch (image.format()) { + case QImage::Format_RGB16: + case QImage::Format_RGB666: + case QImage::Format_RGB555: + case QImage::Format_RGB888: + case QImage::Format_RGB444: + image = image.convertToFormat(QImage::Format_RGB32); + break; + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + image = image.convertToFormat(QImage::Format_ARGB32); + break; + default: + break; + } + } + + if (image.depth() == 1 && image.colorCount() == 2) { + if (qGray(image.color(0)) < qGray(image.color(1))) { + // 0=dark/black, 1=light/white - invert + image.detach(); + for (int y=0; ywrite(str, str.length()) != str.length()) + return false; + w = (w+7)/8; + for (uint y=0; ywrite((char*)line, w)) + return false; + } + } + break; + + case 8: { + str.insert(1, gray ? '5' : '6'); + str.append("255\n"); + if (out->write(str, str.length()) != str.length()) + return false; + QVector color = image.colorTable(); + uint bpl = w*(gray ? 1 : 3); + uchar *buf = new uchar[bpl]; + for (uint y=0; ywrite((char*)buf, bpl)) + return false; + } + delete [] buf; + } + break; + + case 32: { + str.insert(1, gray ? '5' : '6'); + str.append("255\n"); + if (out->write(str, str.length()) != str.length()) + return false; + uint bpl = w*(gray ? 1 : 3); + uchar *buf = new uchar[bpl]; + for (uint y=0; ywrite((char*)buf, bpl)) + return false; + } + delete [] buf; + } + break; + + default: + return false; + } + + return true; +} + +QPpmHandler::QPpmHandler() + : state(Ready) +{ +} + +bool QPpmHandler::readHeader() +{ + state = Error; + if (!read_pbm_header(device(), type, width, height, mcc)) + return false; + state = ReadHeader; + return true; +} + +bool QPpmHandler::canRead() const +{ + if (state == Ready && !canRead(device(), &subType)) + return false; + + if (state != Error) { + setFormat(subType); + return true; + } + + return false; +} + +bool QPpmHandler::canRead(QIODevice *device, QByteArray *subType) +{ + if (!device) { + qWarning("QPpmHandler::canRead() called with no device"); + return false; + } + + char head[2]; + if (device->peek(head, sizeof(head)) != sizeof(head)) + return false; + + if (head[0] != 'P') + return false; + + if (head[1] == '1' || head[1] == '4') { + if (subType) + *subType = "pbm"; + } else if (head[1] == '2' || head[1] == '5') { + if (subType) + *subType = "pgm"; + } else if (head[1] == '3' || head[1] == '6') { + if (subType) + *subType = "ppm"; + } else { + return false; + } + return true; +} + +bool QPpmHandler::read(QImage *image) +{ + if (state == Error) + return false; + + if (state == Ready && !readHeader()) { + state = Error; + return false; + } + + if (!read_pbm_body(device(), type, width, height, mcc, image)) { + state = Error; + return false; + } + + state = Ready; + return true; +} + +bool QPpmHandler::write(const QImage &image) +{ + return write_pbm_image(device(), image, subType); +} + +bool QPpmHandler::supportsOption(ImageOption option) const +{ + return option == SubType + || option == Size + || option == ImageFormat; +} + +QVariant QPpmHandler::option(ImageOption option) const +{ + if (option == SubType) { + return subType; + } else if (option == Size) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + return QSize(width, height); + } else if (option == ImageFormat) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + QImage::Format format = QImage::Format_Invalid; + switch (type) { + case '1': // ascii PBM + case '4': // raw PBM + format = QImage::Format_Mono; + break; + case '2': // ascii PGM + case '5': // raw PGM + format = QImage::Format_Indexed8; + break; + case '3': // ascii PPM + case '6': // raw PPM + format = QImage::Format_RGB32; + break; + default: + break; + } + return format; + } + return QVariant(); +} + +void QPpmHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == SubType) + subType = value.toByteArray().toLower(); +} + +QByteArray QPpmHandler::name() const +{ + return subType.isEmpty() ? QByteArray("ppm") : subType; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_PPM diff --git a/src/gui/image/qppmhandler_p.h b/src/gui/image/qppmhandler_p.h new file mode 100644 index 0000000000..03aa7520b5 --- /dev/null +++ b/src/gui/image/qppmhandler_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPPMHANDLER_P_H +#define QPPMHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qimageiohandler.h" + +#ifndef QT_NO_IMAGEFORMAT_PPM + +QT_BEGIN_NAMESPACE + +class QByteArray; +class QPpmHandler : public QImageIOHandler +{ +public: + QPpmHandler(); + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device, QByteArray *subType = 0); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + bool readHeader(); + enum State { + Ready, + ReadHeader, + Error + }; + State state; + char type; + int width; + int height; + int mcc; + mutable QByteArray subType; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_PPM + +#endif // QPPMHANDLER_P_H diff --git a/src/gui/image/qtiffhandler.cpp b/src/gui/image/qtiffhandler.cpp new file mode 100644 index 0000000000..5939ad5830 --- /dev/null +++ b/src/gui/image/qtiffhandler.cpp @@ -0,0 +1,665 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtiffhandler_p.h" +#include +#include +#include +#include +extern "C" { +#include "tiffio.h" +} + +QT_BEGIN_NAMESPACE + +tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) +{ + QIODevice* device = static_cast(fd)->device(); + return device->isReadable() ? device->read(static_cast(buf), size) : -1; +} + +tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size) +{ + return static_cast(fd)->device()->write(static_cast(buf), size); +} + +toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence) +{ + QIODevice *device = static_cast(fd)->device(); + switch (whence) { + case SEEK_SET: + device->seek(off); + break; + case SEEK_CUR: + device->seek(device->pos() + off); + break; + case SEEK_END: + device->seek(device->size() + off); + break; + } + + return device->pos(); +} + +int qtiffCloseProc(thandle_t /*fd*/) +{ + return 0; +} + +toff_t qtiffSizeProc(thandle_t fd) +{ + return static_cast(fd)->device()->size(); +} + +int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/) +{ + return 0; +} + +void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/) +{ +} + +// for 32 bits images +inline void rotate_right_mirror_horizontal(QImage *const image)// rotate right->mirrored horizontal +{ + const int height = image->height(); + const int width = image->width(); + QImage generated(/* width = */ height, /* height = */ width, image->format()); + const uint32 *originalPixel = reinterpret_cast(image->bits()); + uint32 *const generatedPixels = reinterpret_cast(generated.bits()); + for (int row=0; row < height; ++row) { + for (int col=0; col < width; ++col) { + int idx = col * height + row; + generatedPixels[idx] = *originalPixel; + ++originalPixel; + } + } + *image = generated; +} + +inline void rotate_right_mirror_vertical(QImage *const image) // rotate right->mirrored vertical +{ + const int height = image->height(); + const int width = image->width(); + QImage generated(/* width = */ height, /* height = */ width, image->format()); + const int lastCol = width - 1; + const int lastRow = height - 1; + const uint32 *pixel = reinterpret_cast(image->bits()); + uint32 *const generatedBits = reinterpret_cast(generated.bits()); + for (int row=0; row < height; ++row) { + for (int col=0; col < width; ++col) { + int idx = (lastCol - col) * height + (lastRow - row); + generatedBits[idx] = *pixel; + ++pixel; + } + } + *image = generated; +} + +QTiffHandler::QTiffHandler() : QImageIOHandler() +{ + compression = NoCompression; +} + +bool QTiffHandler::canRead() const +{ + if (canRead(device())) { + setFormat("tiff"); + return true; + } + return false; +} + +bool QTiffHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QTiffHandler::canRead() called with no device"); + return false; + } + + // current implementation uses TIFFClientOpen which needs to be + // able to seek, so sequential devices are not supported + QByteArray header = device->peek(4); + return header == QByteArray::fromRawData("\x49\x49\x2A\x00", 4) + || header == QByteArray::fromRawData("\x4D\x4D\x00\x2A", 4); +} + +bool QTiffHandler::read(QImage *image) +{ + if (!canRead()) + return false; + + TIFF *const tiff = TIFFClientOpen("foo", + "r", + this, + qtiffReadProc, + qtiffWriteProc, + qtiffSeekProc, + qtiffCloseProc, + qtiffSizeProc, + qtiffMapProc, + qtiffUnmapProc); + + if (!tiff) { + return false; + } + uint32 width; + uint32 height; + uint16 photometric; + if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width) + || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height) + || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) { + TIFFClose(tiff); + return false; + } + + // BitsPerSample defaults to 1 according to the TIFF spec. + uint16 bitPerSample; + if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample)) + bitPerSample = 1; + uint16 samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel + if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) + samplesPerPixel = 1; + + bool grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE; + if (grayscale && bitPerSample == 1 && samplesPerPixel == 1) { + if (image->size() != QSize(width, height) || image->format() != QImage::Format_Mono) + *image = QImage(width, height, QImage::Format_Mono); + QVector colortable(2); + if (photometric == PHOTOMETRIC_MINISBLACK) { + colortable[0] = 0xff000000; + colortable[1] = 0xffffffff; + } else { + colortable[0] = 0xffffffff; + colortable[1] = 0xff000000; + } + image->setColorTable(colortable); + + if (!image->isNull()) { + for (uint32 y=0; yscanLine(y), y, 0) < 0) { + TIFFClose(tiff); + return false; + } + } + } + } else { + if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1) { + if (image->size() != QSize(width, height) || image->format() != QImage::Format_Indexed8) + *image = QImage(width, height, QImage::Format_Indexed8); + if (!image->isNull()) { + const uint16 tableSize = 256; + QVector qtColorTable(tableSize); + if (grayscale) { + for (int i = 0; i(qMalloc(tableSize * sizeof(uint16))); + uint16 *greenTable = static_cast(qMalloc(tableSize * sizeof(uint16))); + uint16 *blueTable = static_cast(qMalloc(tableSize * sizeof(uint16))); + if (!redTable || !greenTable || !blueTable) { + TIFFClose(tiff); + return false; + } + if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) { + TIFFClose(tiff); + return false; + } + + for (int i = 0; isetColorTable(qtColorTable); + for (uint32 y=0; yscanLine(y), y, 0) < 0) { + TIFFClose(tiff); + return false; + } + } + + // free redTable, greenTable and greenTable done by libtiff + } + } else { + if (image->size() != QSize(width, height) || image->format() != QImage::Format_ARGB32) + *image = QImage(width, height, QImage::Format_ARGB32); + if (!image->isNull()) { + const int stopOnError = 1; + if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast(image->bits()), ORIENTATION_TOPLEFT, stopOnError)) { + for (uint32 y=0; yscanLine(y), width); + } else { + TIFFClose(tiff); + return false; + } + } + } + } + + if (image->isNull()) { + TIFFClose(tiff); + return false; + } + + float resX = 0; + float resY = 0; + uint16 resUnit = RESUNIT_NONE; + if (TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit) + && TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX) + && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) { + + switch(resUnit) { + case RESUNIT_CENTIMETER: + image->setDotsPerMeterX(qRound(resX * 100)); + image->setDotsPerMeterY(qRound(resY * 100)); + break; + case RESUNIT_INCH: + image->setDotsPerMeterX(qRound(resX * (100 / 2.54))); + image->setDotsPerMeterY(qRound(resY * (100 / 2.54))); + break; + default: + // do nothing as defaults have already + // been set within the QImage class + break; + } + } + + // rotate the image if the orientation is defined in the file + uint16 orientationTag; + if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag)) { + if (image->format() == QImage::Format_ARGB32) { + // TIFFReadRGBAImageOriented() flip the image but does not rotate them + switch (orientationTag) { + case 5: + rotate_right_mirror_horizontal(image); + break; + case 6: + rotate_right_mirror_vertical(image); + break; + case 7: + rotate_right_mirror_horizontal(image); + break; + case 8: + rotate_right_mirror_vertical(image); + break; + } + } else { + switch (orientationTag) { + case 1: // default orientation + break; + case 2: // mirror horizontal + *image = image->mirrored(true, false); + break; + case 3: // mirror both + *image = image->mirrored(true, true); + break; + case 4: // mirror vertical + *image = image->mirrored(false, true); + break; + case 5: // rotate right mirror horizontal + { + QMatrix transformation; + transformation.rotate(90); + *image = image->transformed(transformation); + *image = image->mirrored(true, false); + break; + } + case 6: // rotate right + { + QMatrix transformation; + transformation.rotate(90); + *image = image->transformed(transformation); + break; + } + case 7: // rotate right, mirror vertical + { + QMatrix transformation; + transformation.rotate(90); + *image = image->transformed(transformation); + *image = image->mirrored(false, true); + break; + } + case 8: // rotate left + { + QMatrix transformation; + transformation.rotate(270); + *image = image->transformed(transformation); + break; + } + } + } + } + + + TIFFClose(tiff); + return true; +} + +static bool checkGrayscale(const QVector &colorTable) +{ + if (colorTable.size() != 256) + return false; + + const bool increasing = (colorTable.at(0) == 0xff000000); + for (int i = 0; i < 256; ++i) { + if ((increasing && colorTable.at(i) != qRgb(i, i, i)) + || (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i))) + return false; + } + return true; +} + +bool QTiffHandler::write(const QImage &image) +{ + if (!device()->isWritable()) + return false; + + TIFF *const tiff = TIFFClientOpen("foo", + "w", + this, + qtiffReadProc, + qtiffWriteProc, + qtiffSeekProc, + qtiffCloseProc, + qtiffSizeProc, + qtiffMapProc, + qtiffUnmapProc); + if (!tiff) + return false; + + const int width = image.width(); + const int height = image.height(); + + if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width) + || !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height) + || !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { + TIFFClose(tiff); + return false; + } + + // set the resolution + bool resolutionSet = false; + const int dotPerMeterX = image.dotsPerMeterX(); + const int dotPerMeterY = image.dotsPerMeterY(); + if ((dotPerMeterX % 100) == 0 + && (dotPerMeterY % 100) == 0) { + resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER) + && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0) + && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0); + } else { + resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH) + && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast(image.logicalDpiX())) + && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast(image.logicalDpiY())); + } + if (!resolutionSet) { + TIFFClose(tiff); + return false; + } + + // configure image depth + const QImage::Format format = image.format(); + if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) { + uint16 photometric = PHOTOMETRIC_MINISBLACK; + if (image.colorTable().at(0) == 0xffffffff) + photometric = PHOTOMETRIC_MINISWHITE; + if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric) + || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_CCITTRLE) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1)) { + TIFFClose(tiff); + return false; + } + + // try to do the conversion in chunks no greater than 16 MB + int chunks = (width * height / (1024 * 1024 * 16)) + 1; + int chunkHeight = qMax(height / chunks, 1); + + int y = 0; + while (y < height) { + QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono); + + int chunkStart = y; + int chunkEnd = y + chunk.height(); + while (y < chunkEnd) { + if (TIFFWriteScanline(tiff, reinterpret_cast(chunk.scanLine(y - chunkStart)), y) != 1) { + TIFFClose(tiff); + return false; + } + ++y; + } + } + TIFFClose(tiff); + } else if (format == QImage::Format_Indexed8) { + const QVector colorTable = image.colorTable(); + bool isGrayscale = checkGrayscale(colorTable); + if (isGrayscale) { + uint16 photometric = PHOTOMETRIC_MINISBLACK; + if (image.colorTable().at(0) == 0xffffffff) + photometric = PHOTOMETRIC_MINISWHITE; + if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric) + || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_PACKBITS) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) { + TIFFClose(tiff); + return false; + } + } else { + if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE) + || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_PACKBITS) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) { + TIFFClose(tiff); + return false; + } + //// write the color table + // allocate the color tables + uint16 *redTable = static_cast(qMalloc(256 * sizeof(uint16))); + uint16 *greenTable = static_cast(qMalloc(256 * sizeof(uint16))); + uint16 *blueTable = static_cast(qMalloc(256 * sizeof(uint16))); + if (!redTable || !greenTable || !blueTable) { + TIFFClose(tiff); + return false; + } + + // set the color table + const int tableSize = colorTable.size(); + Q_ASSERT(tableSize <= 256); + for (int i = 0; i(chunk.scanLine(y - chunkStart)), y) != 1) { + TIFFClose(tiff); + return false; + } + ++y; + } + } + TIFFClose(tiff); + + } else { + if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) + || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) + || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) { + TIFFClose(tiff); + return false; + } + // try to do the ARGB32 conversion in chunks no greater than 16 MB + int chunks = (width * height * 4 / (1024 * 1024 * 16)) + 1; + int chunkHeight = qMax(height / chunks, 1); + + int y = 0; + while (y < height) { + QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_ARGB32); + + int chunkStart = y; + int chunkEnd = y + chunk.height(); + while (y < chunkEnd) { + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) + convert32BitOrder(chunk.scanLine(y - chunkStart), width); + else + convert32BitOrderBigEndian(chunk.scanLine(y - chunkStart), width); + + if (TIFFWriteScanline(tiff, reinterpret_cast(chunk.scanLine(y - chunkStart)), y) != 1) { + TIFFClose(tiff); + return false; + } + ++y; + } + } + TIFFClose(tiff); + } + + return true; +} + +QByteArray QTiffHandler::name() const +{ + return "tiff"; +} + +QVariant QTiffHandler::option(ImageOption option) const +{ + if (option == Size && canRead()) { + QSize imageSize; + qint64 pos = device()->pos(); + TIFF *tiff = TIFFClientOpen("foo", + "r", + const_cast(this), + qtiffReadProc, + qtiffWriteProc, + qtiffSeekProc, + qtiffCloseProc, + qtiffSizeProc, + qtiffMapProc, + qtiffUnmapProc); + + if (tiff) { + uint32 width = 0; + uint32 height = 0; + TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width); + TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height); + imageSize = QSize(width, height); + TIFFClose(tiff); + } + device()->seek(pos); + if (imageSize.isValid()) + return imageSize; + } else if (option == CompressionRatio) { + return compression; + } else if (option == ImageFormat) { + return QImage::Format_ARGB32; + } + return QVariant(); +} + +void QTiffHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == CompressionRatio && value.type() == QVariant::Int) + compression = value.toInt(); +} + +bool QTiffHandler::supportsOption(ImageOption option) const +{ + return option == CompressionRatio + || option == Size + || option == ImageFormat; +} + +void QTiffHandler::convert32BitOrder(void *buffer, int width) +{ + uint32 *target = reinterpret_cast(buffer); + for (int32 x=0; x> 16) + | (p & 0x0000ff00) + | ((p & 0x000000ff) << 16); + } +} + +void QTiffHandler::convert32BitOrderBigEndian(void *buffer, int width) +{ + uint32 *target = reinterpret_cast(buffer); + for (int32 x=0; x> 24 + | (p & 0x00ff0000) << 8 + | (p & 0x0000ff00) << 8 + | (p & 0x000000ff) << 8; + } +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qtiffhandler.pri b/src/gui/image/qtiffhandler.pri new file mode 100644 index 0000000000..e1cc3ee2b7 --- /dev/null +++ b/src/gui/image/qtiffhandler.pri @@ -0,0 +1,10 @@ +# common to plugin and built-in forms +INCLUDEPATH *= $$PWD +HEADERS += $$PWD/qtiffhandler_p.h +SOURCES += $$PWD/qtiffhandler.cpp +contains(QT_CONFIG, system-tiff) { + if(unix|win32-g++*):LIBS += -ltiff + else:win32: LIBS += libtiff.lib +} else { + include($$PWD/../../3rdparty/libtiff.pri) +} diff --git a/src/gui/image/qtiffhandler_p.h b/src/gui/image/qtiffhandler_p.h new file mode 100644 index 0000000000..700ac37884 --- /dev/null +++ b/src/gui/image/qtiffhandler_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTIFFHANDLER_P_H +#define QTIFFHANDLER_P_H + +#include + +QT_BEGIN_NAMESPACE + +class QTiffHandler : public QImageIOHandler +{ +public: + QTiffHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + + enum Compression { + NoCompression = 0, + LzwCompression = 1 + }; +private: + void convert32BitOrder(void *buffer, int width); + void convert32BitOrderBigEndian(void *buffer, int width); + int compression; +}; + +QT_END_NAMESPACE + +#endif // QTIFFHANDLER_P_H diff --git a/src/gui/image/qvolatileimage.cpp b/src/gui/image/qvolatileimage.cpp new file mode 100644 index 0000000000..098e9a1264 --- /dev/null +++ b/src/gui/image/qvolatileimage.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvolatileimage_p.h" +#include "qvolatileimagedata_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QVolatileImagePaintEnginePrivate : public QRasterPaintEnginePrivate +{ +public: + QVolatileImagePaintEnginePrivate() { } + QVolatileImage *img; +}; + +class QVolatileImagePaintEngine : public QRasterPaintEngine +{ + Q_DECLARE_PRIVATE(QVolatileImagePaintEngine) + +public: + QVolatileImagePaintEngine(QPaintDevice *device, QVolatileImage *img); + bool begin(QPaintDevice *device); + bool end(); + void drawPixmap(const QPointF &p, const QPixmap &pm); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); +}; + +QVolatileImage::QVolatileImage() + : d(new QVolatileImageData) +{ +} + +QVolatileImage::QVolatileImage(int w, int h, QImage::Format format) + : d(new QVolatileImageData(w, h, format)) +{ +} + +QVolatileImage::QVolatileImage(const QImage &sourceImage) + : d(new QVolatileImageData(sourceImage)) +{ +} + +QVolatileImage::QVolatileImage(void *nativeImage, void *nativeMask) + : d(new QVolatileImageData(nativeImage, nativeMask)) +{ +} + +// Need non-inline, non-autogenerated copy ctor, dtor, op= to keep the +// fwd declared QSharedData working. + +QVolatileImage::QVolatileImage(const QVolatileImage &other) + : d(other.d) +{ +} + +QVolatileImage::~QVolatileImage() +{ +} + +QVolatileImage &QVolatileImage::operator=(const QVolatileImage &rhs) +{ + d = rhs.d; + return *this; +} + +bool QVolatileImage::isNull() const +{ + return d->image.isNull(); +} + +QImage::Format QVolatileImage::format() const +{ + return d->image.format(); +} + +int QVolatileImage::width() const +{ + return d->image.width(); +} + +int QVolatileImage::height() const +{ + return d->image.height(); +} + +int QVolatileImage::bytesPerLine() const +{ + return d->image.bytesPerLine(); +} + +int QVolatileImage::byteCount() const +{ + return d->image.byteCount(); +} + +int QVolatileImage::depth() const +{ + return d->image.depth(); +} + +bool QVolatileImage::hasAlphaChannel() const +{ + return d->image.hasAlphaChannel(); +} + +void QVolatileImage::beginDataAccess() const +{ + d->beginDataAccess(); +} + +void QVolatileImage::endDataAccess(bool readOnly) const +{ + d->endDataAccess(readOnly); +} + +/*! + Access to pixel data via bits() or constBits() should be guarded by + begin/endDataAccess(). + */ +uchar *QVolatileImage::bits() +{ + return d->image.bits(); +} + +const uchar *QVolatileImage::constBits() const +{ + return d->image.constBits(); +} + +bool QVolatileImage::ensureFormat(QImage::Format format) +{ + return d->ensureFormat(format); +} + +/*! + This will always perform a copy of the pixel data. + */ +QImage QVolatileImage::toImage() const +{ + d->beginDataAccess(); + QImage newImage = d->image.copy(); // no sharing allowed + d->endDataAccess(true); + return newImage; +} + +/*! + Returns a reference to the image that is potentially using some native + buffer internally. Access to the image's pixel data should be guarded by + begin/endDataAccess(). Use it when there is a need for QImage APIs not provided + by this class. The returned QImage must never be shared or assigned to. + */ +QImage &QVolatileImage::imageRef() // non-const, in order to cause a detach +{ + d->ensureImage(); + return d->image; +} + +void *QVolatileImage::duplicateNativeImage() const +{ + return d->duplicateNativeImage(); +} + +void QVolatileImage::setAlphaChannel(const QPixmap &alphaChannel) +{ + ensureFormat(QImage::Format_ARGB32_Premultiplied); + beginDataAccess(); + imageRef().setAlphaChannel(alphaChannel.toImage()); + endDataAccess(); + d->ensureImage(); +} + +void QVolatileImage::fill(uint pixelValue) +{ + beginDataAccess(); + imageRef().fill(pixelValue); + endDataAccess(); + d->ensureImage(); +} + +void QVolatileImage::copyFrom(QVolatileImage *source, const QRect &rect) +{ + if (source->isNull()) { + return; + } + QRect r = rect; + if (rect.isNull()) { + r = QRect(0, 0, source->width(), source->height()); + } + source->beginDataAccess(); + QImage &srcImgRef(source->imageRef()); + int srcbpl = srcImgRef.bytesPerLine(); + int srcbpp = srcImgRef.depth() / 8; + const uchar *sptr = srcImgRef.constBits() + r.y() * srcbpl; + beginDataAccess(); + QImage &dstImgRef(imageRef()); + int dstbpl = dstImgRef.bytesPerLine(); + uchar *dptr = dstImgRef.bits(); + for (int y = 0; y < r.height(); ++y) { + qMemCopy(dptr, sptr + r.x() * srcbpp, r.width() * srcbpp); + sptr += srcbpl; + dptr += dstbpl; + } + endDataAccess(); + source->endDataAccess(true); +} + +/*! + To be called from the PixmapData's paintEngine(). + */ +QPaintEngine *QVolatileImage::paintEngine() +{ + if (!d->pengine) { + d->pengine = new QVolatileImagePaintEngine(&imageRef(), this); + } + return d->pengine; +} + +QVolatileImagePaintEngine::QVolatileImagePaintEngine(QPaintDevice *device, + QVolatileImage *img) + : QRasterPaintEngine(*(new QVolatileImagePaintEnginePrivate), device) +{ + Q_D(QVolatileImagePaintEngine); + d->img = img; +} + +bool QVolatileImagePaintEngine::begin(QPaintDevice *device) +{ + Q_D(QVolatileImagePaintEngine); + d->img->beginDataAccess(); + return QRasterPaintEngine::begin(device); +} + +bool QVolatileImagePaintEngine::end() +{ + Q_D(QVolatileImagePaintEngine); + bool ret = QRasterPaintEngine::end(); + d->img->endDataAccess(); + return ret; +} + +// For non-RasterClass pixmaps drawPixmap() would call toImage() which is slow in +// our case. Therefore drawPixmap() is rerouted to drawImage(). + +void QVolatileImagePaintEngine::drawPixmap(const QPointF &p, const QPixmap &pm) +{ +#ifdef Q_OS_SYMBIAN + void *nativeData = pm.pixmapData()->toNativeType(QPixmapData::VolatileImage); + if (nativeData) { + QVolatileImage *img = static_cast(nativeData); + img->beginDataAccess(); + QRasterPaintEngine::drawImage(p, img->imageRef()); + img->endDataAccess(true); + } else { + QRasterPaintEngine::drawPixmap(p, pm); + } +#else + QRasterPaintEngine::drawPixmap(p, pm); +#endif +} + +void QVolatileImagePaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ +#ifdef Q_OS_SYMBIAN + void *nativeData = pm.pixmapData()->toNativeType(QPixmapData::VolatileImage); + if (nativeData) { + QVolatileImage *img = static_cast(nativeData); + img->beginDataAccess(); + QRasterPaintEngine::drawImage(r, img->imageRef(), sr); + img->endDataAccess(true); + } else { + QRasterPaintEngine::drawPixmap(r, pm, sr); + } +#else + QRasterPaintEngine::drawPixmap(r, pm, sr); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qvolatileimage_p.h b/src/gui/image/qvolatileimage_p.h new file mode 100644 index 0000000000..fc5d6b115c --- /dev/null +++ b/src/gui/image/qvolatileimage_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVOLATILEIMAGE_P_H +#define QVOLATILEIMAGE_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 +#include + +QT_BEGIN_NAMESPACE + +class QVolatileImageData; + +class Q_GUI_EXPORT QVolatileImage +{ +public: + QVolatileImage(); + QVolatileImage(int w, int h, QImage::Format format); + explicit QVolatileImage(const QImage &sourceImage); + explicit QVolatileImage(void *nativeImage, void *nativeMask = 0); + QVolatileImage(const QVolatileImage &other); + ~QVolatileImage(); + QVolatileImage &operator=(const QVolatileImage &rhs); + + bool isNull() const; + QImage::Format format() const; + int width() const; + int height() const; + int bytesPerLine() const; + int byteCount() const; + int depth() const; + bool hasAlphaChannel() const; + void beginDataAccess() const; + void endDataAccess(bool readOnly = false) const; + uchar *bits(); + const uchar *constBits() const; + bool ensureFormat(QImage::Format format); + QImage toImage() const; + QImage &imageRef(); + QPaintEngine *paintEngine(); + void setAlphaChannel(const QPixmap &alphaChannel); + void fill(uint pixelValue); + void *duplicateNativeImage() const; + void copyFrom(QVolatileImage *source, const QRect &rect); + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // QVOLATILEIMAGE_P_H diff --git a/src/gui/image/qvolatileimagedata.cpp b/src/gui/image/qvolatileimagedata.cpp new file mode 100644 index 0000000000..d7b964c0a6 --- /dev/null +++ b/src/gui/image/qvolatileimagedata.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvolatileimagedata_p.h" +#include + +QT_BEGIN_NAMESPACE + +QVolatileImageData::QVolatileImageData() + : pengine(0) +{ +} + +QVolatileImageData::QVolatileImageData(int w, int h, QImage::Format format) + : pengine(0) +{ + image = QImage(w, h, format); +} + +QVolatileImageData::QVolatileImageData(const QImage &sourceImage) + : pengine(0) +{ + image = sourceImage; +} + +QVolatileImageData::QVolatileImageData(void *, void *) + : pengine(0) +{ + // Not supported. +} + +QVolatileImageData::QVolatileImageData(const QVolatileImageData &other) + : QSharedData() +{ + image = other.image; + // The detach is not mandatory here but we do it nonetheless in order to + // keep the behavior consistent with other platforms. + image.detach(); + pengine = 0; +} + +QVolatileImageData::~QVolatileImageData() +{ + delete pengine; +} + +void QVolatileImageData::beginDataAccess() const +{ + // nothing to do here +} + +void QVolatileImageData::endDataAccess(bool readOnly) const +{ + Q_UNUSED(readOnly); + // nothing to do here +} + +bool QVolatileImageData::ensureFormat(QImage::Format format) +{ + if (image.format() != format) { + image = image.convertToFormat(format); + } + return true; +} + +void *QVolatileImageData::duplicateNativeImage() const +{ + return 0; +} + +void QVolatileImageData::ensureImage() +{ + // nothing to do here +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qvolatileimagedata_p.h b/src/gui/image/qvolatileimagedata_p.h new file mode 100644 index 0000000000..dab1685347 --- /dev/null +++ b/src/gui/image/qvolatileimagedata_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVOLATILEIMAGEDATA_P_H +#define QVOLATILEIMAGEDATA_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 +#include + +#ifdef Q_OS_SYMBIAN +class CFbsBitmap; +#endif + +QT_BEGIN_NAMESPACE + +class QVolatileImageData : public QSharedData +{ +public: + QVolatileImageData(); + QVolatileImageData(int w, int h, QImage::Format format); + QVolatileImageData(const QImage &sourceImage); + QVolatileImageData(void *nativeImage, void *nativeMask); + QVolatileImageData(const QVolatileImageData &other); + ~QVolatileImageData(); + + void beginDataAccess() const; + void endDataAccess(bool readOnly = false) const; + bool ensureFormat(QImage::Format format); + void *duplicateNativeImage() const; + void ensureImage(); + +#ifdef Q_OS_SYMBIAN + void updateImage(); + void initWithBitmap(CFbsBitmap *source); + void applyMask(CFbsBitmap *mask); + void ensureBitmap(); + void release(); + QVolatileImageData *next; + QVolatileImageData *prev; + CFbsBitmap *bitmap; +#endif + QImage image; + QPaintEngine *pengine; +}; + +QT_END_NAMESPACE + +#endif // QVOLATILEIMAGEDATA_P_H diff --git a/src/gui/image/qvolatileimagedata_symbian.cpp b/src/gui/image/qvolatileimagedata_symbian.cpp new file mode 100644 index 0000000000..6e2909bec3 --- /dev/null +++ b/src/gui/image/qvolatileimagedata_symbian.cpp @@ -0,0 +1,474 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvolatileimagedata_p.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static CFbsBitmap *rasterizeBitmap(CFbsBitmap *bitmap, TDisplayMode newMode) +{ + if (!bitmap) { + return 0; + } + QScopedPointer newBitmap(new CFbsBitmap); + if (newBitmap->Create(bitmap->SizeInPixels(), newMode) != KErrNone) { + qWarning("QVolatileImage: Failed to create new bitmap"); + return 0; + } + CFbsBitmapDevice *bitmapDevice = 0; + CFbsBitGc *bitmapGc = 0; + QT_TRAP_THROWING(bitmapDevice = CFbsBitmapDevice::NewL(newBitmap.data())); + QScopedPointer bitmapDevicePtr(bitmapDevice); + QT_TRAP_THROWING(bitmapGc = CFbsBitGc::NewL()); + bitmapGc->Activate(bitmapDevice); + bitmapGc->BitBlt(TPoint(), bitmap); + delete bitmapGc; + return newBitmap.take(); +} + +static inline TDisplayMode format2TDisplayMode(QImage::Format format) +{ + TDisplayMode mode; + switch (format) { + case QImage::Format_MonoLSB: + mode = EGray2; + break; + case QImage::Format_Indexed8: + mode = EColor256; + break; + case QImage::Format_RGB444: + mode = EColor4K; + break; + case QImage::Format_RGB16: + mode = EColor64K; + break; + case QImage::Format_RGB888: + mode = EColor16M; + break; + case QImage::Format_RGB32: + mode = EColor16MU; + break; + case QImage::Format_ARGB32: + mode = EColor16MA; + break; + case QImage::Format_ARGB32_Premultiplied: + mode = Q_SYMBIAN_ECOLOR16MAP; + break; + default: + mode = ENone; + break; + } + return mode; +} + +static CFbsBitmap *imageToBitmap(const QImage &image) +{ + if (image.isNull()) { + return 0; + } + CFbsBitmap *bitmap = new CFbsBitmap; + if (bitmap->Create(TSize(image.width(), image.height()), + format2TDisplayMode(image.format())) == KErrNone) { + bitmap->BeginDataAccess(); + uchar *dptr = reinterpret_cast(bitmap->DataAddress()); + int bmpLineLen = bitmap->DataStride(); + int imgLineLen = image.bytesPerLine(); + if (bmpLineLen == imgLineLen) { + qMemCopy(dptr, image.constBits(), image.byteCount()); + } else { + int len = qMin(bmpLineLen, imgLineLen); + const uchar *sptr = image.constBits(); + for (int y = 0; y < image.height(); ++y) { + qMemCopy(dptr, sptr, len); + dptr += bmpLineLen; + sptr += imgLineLen; + } + } + bitmap->EndDataAccess(); + } else { + qWarning("QVolatileImage: Failed to create source bitmap"); + delete bitmap; + bitmap = 0; + } + return bitmap; +} + +static CFbsBitmap *copyData(const QVolatileImageData &source) +{ + source.beginDataAccess(); + CFbsBitmap *bmp = imageToBitmap(source.image); + source.endDataAccess(); + return bmp; +} + +static CFbsBitmap *convertData(const QVolatileImageData &source, QImage::Format newFormat) +{ + source.beginDataAccess(); + QImage img = source.image.convertToFormat(newFormat); + CFbsBitmap *bmp = imageToBitmap(img); + source.endDataAccess(); + return bmp; +} + +static CFbsBitmap *duplicateBitmap(const CFbsBitmap &sourceBitmap) +{ + CFbsBitmap *bitmap = new CFbsBitmap; + if (bitmap->Duplicate(sourceBitmap.Handle()) != KErrNone) { + qWarning("QVolatileImage: Failed to duplicate source bitmap"); + delete bitmap; + bitmap = 0; + } + return bitmap; +} + +static CFbsBitmap *createBitmap(int w, int h, QImage::Format format) +{ + CFbsBitmap *bitmap = new CFbsBitmap; + if (bitmap->Create(TSize(w, h), format2TDisplayMode(format)) != KErrNone) { + qWarning("QVolatileImage: Failed to create source bitmap %d,%d (%d)", w, h, format); + delete bitmap; + bitmap = 0; + } + return bitmap; +} + +static inline bool bitmapNeedsCopy(CFbsBitmap *bitmap) +{ + bool needsCopy = bitmap->IsCompressedInRAM(); +#ifdef Q_SYMBIAN_HAS_EXTENDED_BITMAP_TYPE + needsCopy |= (bitmap->ExtendedBitmapType() != KNullUid); +#endif + return needsCopy; +} + +static bool cleanup_function_registered = false; +static QVolatileImageData *firstImageData = 0; + +static void cleanup() +{ + if (RFbsSession::GetSession()) { + QVolatileImageData *imageData = firstImageData; + while (imageData) { + imageData->release(); + imageData = imageData->next; + } + } +} + +static void ensureCleanup() +{ + // Destroy all underlying bitmaps in a post routine to prevent panics. + // This is a must because CFbsBitmap destructor needs the fbs session, + // that was used to create the bitmap, to be open still. + if (!cleanup_function_registered) { + qAddPostRoutine(cleanup); + cleanup_function_registered = true; + } +} + +static void registerImageData(QVolatileImageData *imageData) +{ + ensureCleanup(); + imageData->next = firstImageData; + if (firstImageData) { + firstImageData->prev = imageData; + } + firstImageData = imageData; +} + +static void unregisterImageData(QVolatileImageData *imageData) +{ + if (imageData->prev) { + imageData->prev->next = imageData->next; + } else { + firstImageData = imageData->next; + } + if (imageData->next) { + imageData->next->prev = imageData->prev; + } +} + +QVolatileImageData::QVolatileImageData() + : next(0), prev(0), bitmap(0), pengine(0) +{ + registerImageData(this); +} + +QVolatileImageData::QVolatileImageData(int w, int h, QImage::Format format) + : next(0), prev(0), bitmap(0), pengine(0) +{ + registerImageData(this); + bitmap = createBitmap(w, h, format); + updateImage(); +} + +QVolatileImageData::QVolatileImageData(const QImage &sourceImage) + : next(0), prev(0), bitmap(0), pengine(0) +{ + registerImageData(this); + image = sourceImage; + // The following is not mandatory, but we do it here to have a bitmap + // created always in order to reduce local heap usage. + ensureBitmap(); +} + +QVolatileImageData::QVolatileImageData(void *nativeImage, void *nativeMask) + : next(0), prev(0), bitmap(0), pengine(0) +{ + registerImageData(this); + if (nativeImage) { + CFbsBitmap *source = static_cast(nativeImage); + CFbsBitmap *mask = static_cast(nativeMask); + initWithBitmap(source); + if (mask) { + applyMask(mask); + } + } +} + +QVolatileImageData::QVolatileImageData(const QVolatileImageData &other) +{ + bitmap = 0; + pengine = 0; + next = prev = 0; + registerImageData(this); + if (!other.image.isNull()) { + bitmap = copyData(other); + updateImage(); + } +} + +QVolatileImageData::~QVolatileImageData() +{ + release(); + unregisterImageData(this); +} + +void QVolatileImageData::release() +{ + delete bitmap; + bitmap = 0; + delete pengine; + pengine = 0; +} + +void QVolatileImageData::beginDataAccess() const +{ + if (bitmap) { + bitmap->BeginDataAccess(); + } +} + +void QVolatileImageData::endDataAccess(bool readOnly) const +{ + if (bitmap) { + bitmap->EndDataAccess(readOnly); + } +} + +bool QVolatileImageData::ensureFormat(QImage::Format format) +{ + if (image.isNull()) { + return false; + } + if (image.format() != format) { + CFbsBitmap *newBitmap = convertData(*this, format); + if (newBitmap && newBitmap != bitmap) { + delete bitmap; + bitmap = newBitmap; + updateImage(); + } else { + return false; + } + } + return true; +} + +void *QVolatileImageData::duplicateNativeImage() const +{ + const_cast(this)->ensureBitmap(); + if (bitmap) { + if (bitmap->DisplayMode() == EColor16M) { + // slow path: needs rgb swapping + beginDataAccess(); + QImage tmp = image.rgbSwapped(); + endDataAccess(true); + return imageToBitmap(tmp); + } else if (bitmap->DisplayMode() == EGray2) { + // slow path: needs inverting pixels + beginDataAccess(); + QImage tmp = image.copy(); + endDataAccess(true); + tmp.invertPixels(); + return imageToBitmap(tmp); + } else { + // fast path: just duplicate the bitmap + return duplicateBitmap(*bitmap); + } + } + return 0; +} + +void QVolatileImageData::updateImage() +{ + if (bitmap) { + TSize size = bitmap->SizeInPixels(); + beginDataAccess(); + // Use existing buffer, no copy. The data address never changes so it + // is enough to do this whenever we have a new CFbsBitmap. N.B. never + // use const uchar* here, that would create a read-only image data which + // would make a copy in detach() even when refcount is 1. + image = QImage(reinterpret_cast(bitmap->DataAddress()), + size.iWidth, size.iHeight, bitmap->DataStride(), + qt_TDisplayMode2Format(bitmap->DisplayMode())); + endDataAccess(true); + } else { + image = QImage(); + } +} + +void QVolatileImageData::initWithBitmap(CFbsBitmap *source) +{ + bool needsCopy = bitmapNeedsCopy(source); + if (source->DisplayMode() == EColor16M) { + // EColor16M is BGR + CFbsBitmap *unswappedBmp = source; + if (needsCopy) { + unswappedBmp = rasterizeBitmap(source, source->DisplayMode()); + } + unswappedBmp->BeginDataAccess(); + TSize sourceSize = unswappedBmp->SizeInPixels(); + QImage img((uchar *) unswappedBmp->DataAddress(), + sourceSize.iWidth, sourceSize.iHeight, unswappedBmp->DataStride(), + qt_TDisplayMode2Format(unswappedBmp->DisplayMode())); + img = img.rgbSwapped(); + unswappedBmp->EndDataAccess(true); + bitmap = imageToBitmap(img); + if (needsCopy) { + delete unswappedBmp; + } + } else if (needsCopy) { + // Rasterize extended and compressed bitmaps. + bitmap = rasterizeBitmap(source, EColor16MAP); + } else if (source->DisplayMode() == EGray2) { + // The pixels will be inverted, must make a copy. + bitmap = rasterizeBitmap(source, source->DisplayMode()); + } else { + // Efficient path: no pixel data copying. Just duplicate. This of course + // means the original bitmap's data may get modified, but that's fine + // and is in accordance with the QPixmap::fromSymbianCFbsBitmap() docs. + bitmap = duplicateBitmap(*source); + } + updateImage(); + if (bitmap && bitmap->DisplayMode() == EGray2) { + // Symbian thinks set pixels are white/transparent, Qt thinks they are + // foreground/solid. Invert mono bitmaps so that masks work correctly. + beginDataAccess(); + image.invertPixels(); + endDataAccess(); + } +} + +void QVolatileImageData::applyMask(CFbsBitmap *mask) +{ + ensureFormat(QImage::Format_ARGB32_Premultiplied); + bool destroyMask = false; + if (bitmapNeedsCopy(mask)) { + mask = rasterizeBitmap(mask, EColor16MU); + if (!mask) { + return; + } + destroyMask = true; + } + mask->BeginDataAccess(); + TSize maskSize = mask->SizeInPixels(); + QImage maskImg((const uchar *) mask->DataAddress(), maskSize.iWidth, maskSize.iHeight, + mask->DataStride(), qt_TDisplayMode2Format(mask->DisplayMode())); + if (mask->DisplayMode() == EGray2) { + maskImg = maskImg.copy(); + maskImg.invertPixels(); + } + beginDataAccess(); + image.setAlphaChannel(maskImg); + endDataAccess(); + mask->EndDataAccess(true); + ensureImage(); + if (destroyMask) { + delete mask; + } +} + +void QVolatileImageData::ensureImage() +{ + if (bitmap && !image.isNull()) { + QImageData *imaged = image.data_ptr(); + if (imaged->ref != 1 || imaged->ro_data) { + // This is bad, the imagedata got shared somehow. Detach, in order to + // have the next check fail and thus have 'image' recreated. + beginDataAccess(); + image.detach(); + endDataAccess(true); + } + } + if (bitmap && image.constBits() != reinterpret_cast(bitmap->DataAddress())) { + // Should not ever get here. If we do it means that either 'image' has + // been replaced with a copy (e.g. because some QImage API assigned a + // new, regular QImage to *this) or the bitmap's data address changed + // unexpectedly. + qWarning("QVolatileImageData: Ptr mismatch"); + // Recover by recreating the image so that it uses the bitmap as its buffer. + updateImage(); + } +} + +void QVolatileImageData::ensureBitmap() +{ + if (!bitmap && !image.isNull()) { + bitmap = imageToBitmap(image); + updateImage(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qxbmhandler.cpp b/src/gui/image/qxbmhandler.cpp new file mode 100644 index 0000000000..ae9e2047e5 --- /dev/null +++ b/src/gui/image/qxbmhandler.cpp @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "private/qxbmhandler_p.h" + +#ifndef QT_NO_IMAGEFORMAT_XBM + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + X bitmap image read/write functions + *****************************************************************************/ + +static inline int hex2byte(register char *p) +{ + return ((isdigit((uchar) *p) ? *p - '0' : toupper((uchar) *p) - 'A' + 10) << 4) | + (isdigit((uchar) *(p+1)) ? *(p+1) - '0' : toupper((uchar) *(p+1)) - 'A' + 10); +} + +static bool read_xbm_header(QIODevice *device, int& w, int& h) +{ + const int buflen = 300; + const int maxlen = 4096; + char buf[buflen + 1]; + QRegExp r1(QLatin1String("^#define[ \t]+[a-zA-Z0-9._]+[ \t]+")); + QRegExp r2(QLatin1String("[0-9]+")); + + qint64 readBytes = 0; + qint64 totalReadBytes = 0; + + buf[0] = '\0'; + + // skip initial comment, if any + while (buf[0] != '#') { + readBytes = device->readLine(buf, buflen); + + // if readBytes >= buflen, it's very probably not a C file + if (readBytes <= 0 || readBytes >= buflen -1) + return false; + + // limit xbm headers to the first 4k in the file to prevent + // excessive reads on non-xbm files + totalReadBytes += readBytes; + if (totalReadBytes >= maxlen) + return false; + } + + buf[readBytes - 1] = '\0'; + QString sbuf; + sbuf = QString::fromLatin1(buf); + + // "#define .._width " + if (r1.indexIn(sbuf) == 0 && + r2.indexIn(sbuf, r1.matchedLength()) == r1.matchedLength()) + w = QByteArray(&buf[r1.matchedLength()]).trimmed().toInt(); + + // "#define .._height " + readBytes = device->readLine(buf, buflen); + if (readBytes <= 0) + return false; + buf[readBytes - 1] = '\0'; + + sbuf = QString::fromLatin1(buf); + + if (r1.indexIn(sbuf) == 0 && + r2.indexIn(sbuf, r1.matchedLength()) == r1.matchedLength()) + h = QByteArray(&buf[r1.matchedLength()]).trimmed().toInt(); + + // format error + if (w <= 0 || w > 32767 || h <= 0 || h > 32767) + return false; + + return true; +} + +static bool read_xbm_body(QIODevice *device, int w, int h, QImage *outImage) +{ + const int buflen = 300; + char buf[buflen + 1]; + + qint64 readBytes = 0; + + // scan for database + for (;;) { + if ((readBytes = device->readLine(buf, buflen)) <= 0) { + // end of file + return false; + } + + buf[readBytes] = '\0'; + if (QByteArray::fromRawData(buf, readBytes).contains("0x")) + break; + } + + if (outImage->size() != QSize(w, h) || outImage->format() != QImage::Format_MonoLSB) { + *outImage = QImage(w, h, QImage::Format_MonoLSB); + if (outImage->isNull()) + return false; + } + + outImage->setColorCount(2); + outImage->setColor(0, qRgb(255,255,255)); // white + outImage->setColor(1, qRgb(0,0,0)); // black + + int x = 0, y = 0; + uchar *b = outImage->scanLine(0); + char *p = buf + QByteArray::fromRawData(buf, readBytes).indexOf("0x"); + w = (w+7)/8; // byte width + + while (y < h) { // for all encoded bytes... + if (p) { // p = "0x.." + *b++ = hex2byte(p+2); + p += 2; + if (++x == w && ++y < h) { + b = outImage->scanLine(y); + x = 0; + } + p = strstr(p, "0x"); + } else { // read another line + if ((readBytes = device->readLine(buf,buflen)) <= 0) // EOF ==> truncated image + break; + p = buf + QByteArray::fromRawData(buf, readBytes).indexOf("0x"); + } + } + + return true; +} + +static bool read_xbm_image(QIODevice *device, QImage *outImage) +{ + int w = 0, h = 0; + if (!read_xbm_header(device, w, h)) + return false; + return read_xbm_body(device, w, h, outImage); +} + +static bool write_xbm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName) +{ + QImage image = sourceImage; + int w = image.width(); + int h = image.height(); + int i; + QString s = fileName; // get file base name + int msize = s.length() + 100; + char *buf = new char[msize]; + + qsnprintf(buf, msize, "#define %s_width %d\n", s.toAscii().data(), w); + device->write(buf, qstrlen(buf)); + qsnprintf(buf, msize, "#define %s_height %d\n", s.toAscii().data(), h); + device->write(buf, qstrlen(buf)); + qsnprintf(buf, msize, "static char %s_bits[] = {\n ", s.toAscii().data()); + device->write(buf, qstrlen(buf)); + + if (image.format() != QImage::Format_MonoLSB) + image = image.convertToFormat(QImage::Format_MonoLSB); + + bool invert = qGray(image.color(0)) < qGray(image.color(1)); + char hexrep[16]; + for (i=0; i<10; i++) + hexrep[i] = '0' + i; + for (i=10; i<16; i++) + hexrep[i] = 'a' -10 + i; + if (invert) { + char t; + for (i=0; i<8; i++) { + t = hexrep[15-i]; + hexrep[15-i] = hexrep[i]; + hexrep[i] = t; + } + } + int bcnt = 0; + register char *p = buf; + int bpl = (w+7)/8; + for (int y = 0; y < h; ++y) { + uchar *b = image.scanLine(y); + for (i = 0; i < bpl; ++i) { + *p++ = '0'; *p++ = 'x'; + *p++ = hexrep[*b >> 4]; + *p++ = hexrep[*b++ & 0xf]; + + if (i < bpl - 1 || y < h - 1) { + *p++ = ','; + if (++bcnt > 14) { + *p++ = '\n'; + *p++ = ' '; + *p = '\0'; + if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) { + delete [] buf; + return false; + } + p = buf; + bcnt = 0; + } + } + } + } +#if defined(_MSC_VER) && _MSC_VER >= 1400 + strcpy_s(p, sizeof(" };\n"), " };\n"); +#else + strcpy(p, " };\n"); +#endif + if ((int)qstrlen(buf) != device->write(buf, qstrlen(buf))) { + delete [] buf; + return false; + } + + delete [] buf; + return true; +} + +QXbmHandler::QXbmHandler() + : state(Ready) +{ +} + +bool QXbmHandler::readHeader() +{ + state = Error; + if (!read_xbm_header(device(), width, height)) + return false; + state = ReadHeader; + return true; +} + +bool QXbmHandler::canRead() const +{ + if (state == Ready && !canRead(device())) + return false; + + if (state != Error) { + setFormat("xbm"); + return true; + } + + return false; +} + +bool QXbmHandler::canRead(QIODevice *device) +{ + QImage image; + + // it's impossible to tell whether we can load an XBM or not when + // it's from a sequential device, as the only way to do it is to + // attempt to parse the whole image. + if (device->isSequential()) + return false; + + qint64 oldPos = device->pos(); + bool success = read_xbm_image(device, &image); + device->seek(oldPos); + + return success; +} + +bool QXbmHandler::read(QImage *image) +{ + if (state == Error) + return false; + + if (state == Ready && !readHeader()) { + state = Error; + return false; + } + + if (!read_xbm_body(device(), width, height, image)) { + state = Error; + return false; + } + + state = Ready; + return true; +} + +bool QXbmHandler::write(const QImage &image) +{ + return write_xbm_image(image, device(), fileName); +} + +bool QXbmHandler::supportsOption(ImageOption option) const +{ + return option == Name + || option == Size + || option == ImageFormat; +} + +QVariant QXbmHandler::option(ImageOption option) const +{ + if (option == Name) { + return fileName; + } else if (option == Size) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + return QSize(width, height); + } else if (option == ImageFormat) { + return QImage::Format_MonoLSB; + } + return QVariant(); +} + +void QXbmHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == Name) + fileName = value.toString(); +} + +QByteArray QXbmHandler::name() const +{ + return "xbm"; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_XBM diff --git a/src/gui/image/qxbmhandler_p.h b/src/gui/image/qxbmhandler_p.h new file mode 100644 index 0000000000..6fdd3d0816 --- /dev/null +++ b/src/gui/image/qxbmhandler_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QXBMHANDLER_P_H +#define QXBMHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qimageiohandler.h" + +#ifndef QT_NO_IMAGEFORMAT_XBM + +QT_BEGIN_NAMESPACE + +class QXbmHandler : public QImageIOHandler +{ +public: + QXbmHandler(); + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + bool readHeader(); + enum State { + Ready, + ReadHeader, + Error + }; + State state; + int width; + int height; + QString fileName; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_XBM + +#endif // QXBMHANDLER_P_H diff --git a/src/gui/image/qxpmhandler.cpp b/src/gui/image/qxpmhandler.cpp new file mode 100644 index 0000000000..075d5dadc7 --- /dev/null +++ b/src/gui/image/qxpmhandler.cpp @@ -0,0 +1,1295 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qxpmhandler_p.h" + +#ifndef QT_NO_IMAGEFORMAT_XPM + +#include +#include +#include +#include +#include + +#if defined(Q_CC_BOR) +// needed for qsort() because of a std namespace problem on Borland +#include "qplatformdefs.h" +#endif + +QT_BEGIN_NAMESPACE + +static quint64 xpmHash(const QString &str) +{ + unsigned int hashValue = 0; + for (int i = 0; i < str.size(); ++i) { + hashValue <<= 8; + hashValue += (unsigned int)str.at(i).unicode(); + } + return hashValue; +} +static quint64 xpmHash(char *str) +{ + unsigned int hashValue = 0; + while (*str != '\0') { + hashValue <<= 8; + hashValue += (unsigned int)*str; + ++str; + } + return hashValue; +} + +#ifdef QRGB +#undef QRGB +#endif +#define QRGB(r,g,b) (r*65536 + g*256 + b) + +static const int xpmRgbTblSize = 657; + +static const struct XPMRGBData { + uint value; + const char *name; +} xpmRgbTbl[] = { + { QRGB(240,248,255), "aliceblue" }, + { QRGB(250,235,215), "antiquewhite" }, + { QRGB(255,239,219), "antiquewhite1" }, + { QRGB(238,223,204), "antiquewhite2" }, + { QRGB(205,192,176), "antiquewhite3" }, + { QRGB(139,131,120), "antiquewhite4" }, + { QRGB(127,255,212), "aquamarine" }, + { QRGB(127,255,212), "aquamarine1" }, + { QRGB(118,238,198), "aquamarine2" }, + { QRGB(102,205,170), "aquamarine3" }, + { QRGB( 69,139,116), "aquamarine4" }, + { QRGB(240,255,255), "azure" }, + { QRGB(240,255,255), "azure1" }, + { QRGB(224,238,238), "azure2" }, + { QRGB(193,205,205), "azure3" }, + { QRGB(131,139,139), "azure4" }, + { QRGB(245,245,220), "beige" }, + { QRGB(255,228,196), "bisque" }, + { QRGB(255,228,196), "bisque1" }, + { QRGB(238,213,183), "bisque2" }, + { QRGB(205,183,158), "bisque3" }, + { QRGB(139,125,107), "bisque4" }, + { QRGB( 0, 0, 0), "black" }, + { QRGB(255,235,205), "blanchedalmond" }, + { QRGB( 0, 0,255), "blue" }, + { QRGB( 0, 0,255), "blue1" }, + { QRGB( 0, 0,238), "blue2" }, + { QRGB( 0, 0,205), "blue3" }, + { QRGB( 0, 0,139), "blue4" }, + { QRGB(138, 43,226), "blueviolet" }, + { QRGB(165, 42, 42), "brown" }, + { QRGB(255, 64, 64), "brown1" }, + { QRGB(238, 59, 59), "brown2" }, + { QRGB(205, 51, 51), "brown3" }, + { QRGB(139, 35, 35), "brown4" }, + { QRGB(222,184,135), "burlywood" }, + { QRGB(255,211,155), "burlywood1" }, + { QRGB(238,197,145), "burlywood2" }, + { QRGB(205,170,125), "burlywood3" }, + { QRGB(139,115, 85), "burlywood4" }, + { QRGB( 95,158,160), "cadetblue" }, + { QRGB(152,245,255), "cadetblue1" }, + { QRGB(142,229,238), "cadetblue2" }, + { QRGB(122,197,205), "cadetblue3" }, + { QRGB( 83,134,139), "cadetblue4" }, + { QRGB(127,255, 0), "chartreuse" }, + { QRGB(127,255, 0), "chartreuse1" }, + { QRGB(118,238, 0), "chartreuse2" }, + { QRGB(102,205, 0), "chartreuse3" }, + { QRGB( 69,139, 0), "chartreuse4" }, + { QRGB(210,105, 30), "chocolate" }, + { QRGB(255,127, 36), "chocolate1" }, + { QRGB(238,118, 33), "chocolate2" }, + { QRGB(205,102, 29), "chocolate3" }, + { QRGB(139, 69, 19), "chocolate4" }, + { QRGB(255,127, 80), "coral" }, + { QRGB(255,114, 86), "coral1" }, + { QRGB(238,106, 80), "coral2" }, + { QRGB(205, 91, 69), "coral3" }, + { QRGB(139, 62, 47), "coral4" }, + { QRGB(100,149,237), "cornflowerblue" }, + { QRGB(255,248,220), "cornsilk" }, + { QRGB(255,248,220), "cornsilk1" }, + { QRGB(238,232,205), "cornsilk2" }, + { QRGB(205,200,177), "cornsilk3" }, + { QRGB(139,136,120), "cornsilk4" }, + { QRGB( 0,255,255), "cyan" }, + { QRGB( 0,255,255), "cyan1" }, + { QRGB( 0,238,238), "cyan2" }, + { QRGB( 0,205,205), "cyan3" }, + { QRGB( 0,139,139), "cyan4" }, + { QRGB( 0, 0,139), "darkblue" }, + { QRGB( 0,139,139), "darkcyan" }, + { QRGB(184,134, 11), "darkgoldenrod" }, + { QRGB(255,185, 15), "darkgoldenrod1" }, + { QRGB(238,173, 14), "darkgoldenrod2" }, + { QRGB(205,149, 12), "darkgoldenrod3" }, + { QRGB(139,101, 8), "darkgoldenrod4" }, + { QRGB(169,169,169), "darkgray" }, + { QRGB( 0,100, 0), "darkgreen" }, + { QRGB(169,169,169), "darkgrey" }, + { QRGB(189,183,107), "darkkhaki" }, + { QRGB(139, 0,139), "darkmagenta" }, + { QRGB( 85,107, 47), "darkolivegreen" }, + { QRGB(202,255,112), "darkolivegreen1" }, + { QRGB(188,238,104), "darkolivegreen2" }, + { QRGB(162,205, 90), "darkolivegreen3" }, + { QRGB(110,139, 61), "darkolivegreen4" }, + { QRGB(255,140, 0), "darkorange" }, + { QRGB(255,127, 0), "darkorange1" }, + { QRGB(238,118, 0), "darkorange2" }, + { QRGB(205,102, 0), "darkorange3" }, + { QRGB(139, 69, 0), "darkorange4" }, + { QRGB(153, 50,204), "darkorchid" }, + { QRGB(191, 62,255), "darkorchid1" }, + { QRGB(178, 58,238), "darkorchid2" }, + { QRGB(154, 50,205), "darkorchid3" }, + { QRGB(104, 34,139), "darkorchid4" }, + { QRGB(139, 0, 0), "darkred" }, + { QRGB(233,150,122), "darksalmon" }, + { QRGB(143,188,143), "darkseagreen" }, + { QRGB(193,255,193), "darkseagreen1" }, + { QRGB(180,238,180), "darkseagreen2" }, + { QRGB(155,205,155), "darkseagreen3" }, + { QRGB(105,139,105), "darkseagreen4" }, + { QRGB( 72, 61,139), "darkslateblue" }, + { QRGB( 47, 79, 79), "darkslategray" }, + { QRGB(151,255,255), "darkslategray1" }, + { QRGB(141,238,238), "darkslategray2" }, + { QRGB(121,205,205), "darkslategray3" }, + { QRGB( 82,139,139), "darkslategray4" }, + { QRGB( 47, 79, 79), "darkslategrey" }, + { QRGB( 0,206,209), "darkturquoise" }, + { QRGB(148, 0,211), "darkviolet" }, + { QRGB(255, 20,147), "deeppink" }, + { QRGB(255, 20,147), "deeppink1" }, + { QRGB(238, 18,137), "deeppink2" }, + { QRGB(205, 16,118), "deeppink3" }, + { QRGB(139, 10, 80), "deeppink4" }, + { QRGB( 0,191,255), "deepskyblue" }, + { QRGB( 0,191,255), "deepskyblue1" }, + { QRGB( 0,178,238), "deepskyblue2" }, + { QRGB( 0,154,205), "deepskyblue3" }, + { QRGB( 0,104,139), "deepskyblue4" }, + { QRGB(105,105,105), "dimgray" }, + { QRGB(105,105,105), "dimgrey" }, + { QRGB( 30,144,255), "dodgerblue" }, + { QRGB( 30,144,255), "dodgerblue1" }, + { QRGB( 28,134,238), "dodgerblue2" }, + { QRGB( 24,116,205), "dodgerblue3" }, + { QRGB( 16, 78,139), "dodgerblue4" }, + { QRGB(178, 34, 34), "firebrick" }, + { QRGB(255, 48, 48), "firebrick1" }, + { QRGB(238, 44, 44), "firebrick2" }, + { QRGB(205, 38, 38), "firebrick3" }, + { QRGB(139, 26, 26), "firebrick4" }, + { QRGB(255,250,240), "floralwhite" }, + { QRGB( 34,139, 34), "forestgreen" }, + { QRGB(220,220,220), "gainsboro" }, + { QRGB(248,248,255), "ghostwhite" }, + { QRGB(255,215, 0), "gold" }, + { QRGB(255,215, 0), "gold1" }, + { QRGB(238,201, 0), "gold2" }, + { QRGB(205,173, 0), "gold3" }, + { QRGB(139,117, 0), "gold4" }, + { QRGB(218,165, 32), "goldenrod" }, + { QRGB(255,193, 37), "goldenrod1" }, + { QRGB(238,180, 34), "goldenrod2" }, + { QRGB(205,155, 29), "goldenrod3" }, + { QRGB(139,105, 20), "goldenrod4" }, + { QRGB(190,190,190), "gray" }, + { QRGB( 0, 0, 0), "gray0" }, + { QRGB( 3, 3, 3), "gray1" }, + { QRGB( 26, 26, 26), "gray10" }, + { QRGB(255,255,255), "gray100" }, + { QRGB( 28, 28, 28), "gray11" }, + { QRGB( 31, 31, 31), "gray12" }, + { QRGB( 33, 33, 33), "gray13" }, + { QRGB( 36, 36, 36), "gray14" }, + { QRGB( 38, 38, 38), "gray15" }, + { QRGB( 41, 41, 41), "gray16" }, + { QRGB( 43, 43, 43), "gray17" }, + { QRGB( 46, 46, 46), "gray18" }, + { QRGB( 48, 48, 48), "gray19" }, + { QRGB( 5, 5, 5), "gray2" }, + { QRGB( 51, 51, 51), "gray20" }, + { QRGB( 54, 54, 54), "gray21" }, + { QRGB( 56, 56, 56), "gray22" }, + { QRGB( 59, 59, 59), "gray23" }, + { QRGB( 61, 61, 61), "gray24" }, + { QRGB( 64, 64, 64), "gray25" }, + { QRGB( 66, 66, 66), "gray26" }, + { QRGB( 69, 69, 69), "gray27" }, + { QRGB( 71, 71, 71), "gray28" }, + { QRGB( 74, 74, 74), "gray29" }, + { QRGB( 8, 8, 8), "gray3" }, + { QRGB( 77, 77, 77), "gray30" }, + { QRGB( 79, 79, 79), "gray31" }, + { QRGB( 82, 82, 82), "gray32" }, + { QRGB( 84, 84, 84), "gray33" }, + { QRGB( 87, 87, 87), "gray34" }, + { QRGB( 89, 89, 89), "gray35" }, + { QRGB( 92, 92, 92), "gray36" }, + { QRGB( 94, 94, 94), "gray37" }, + { QRGB( 97, 97, 97), "gray38" }, + { QRGB( 99, 99, 99), "gray39" }, + { QRGB( 10, 10, 10), "gray4" }, + { QRGB(102,102,102), "gray40" }, + { QRGB(105,105,105), "gray41" }, + { QRGB(107,107,107), "gray42" }, + { QRGB(110,110,110), "gray43" }, + { QRGB(112,112,112), "gray44" }, + { QRGB(115,115,115), "gray45" }, + { QRGB(117,117,117), "gray46" }, + { QRGB(120,120,120), "gray47" }, + { QRGB(122,122,122), "gray48" }, + { QRGB(125,125,125), "gray49" }, + { QRGB( 13, 13, 13), "gray5" }, + { QRGB(127,127,127), "gray50" }, + { QRGB(130,130,130), "gray51" }, + { QRGB(133,133,133), "gray52" }, + { QRGB(135,135,135), "gray53" }, + { QRGB(138,138,138), "gray54" }, + { QRGB(140,140,140), "gray55" }, + { QRGB(143,143,143), "gray56" }, + { QRGB(145,145,145), "gray57" }, + { QRGB(148,148,148), "gray58" }, + { QRGB(150,150,150), "gray59" }, + { QRGB( 15, 15, 15), "gray6" }, + { QRGB(153,153,153), "gray60" }, + { QRGB(156,156,156), "gray61" }, + { QRGB(158,158,158), "gray62" }, + { QRGB(161,161,161), "gray63" }, + { QRGB(163,163,163), "gray64" }, + { QRGB(166,166,166), "gray65" }, + { QRGB(168,168,168), "gray66" }, + { QRGB(171,171,171), "gray67" }, + { QRGB(173,173,173), "gray68" }, + { QRGB(176,176,176), "gray69" }, + { QRGB( 18, 18, 18), "gray7" }, + { QRGB(179,179,179), "gray70" }, + { QRGB(181,181,181), "gray71" }, + { QRGB(184,184,184), "gray72" }, + { QRGB(186,186,186), "gray73" }, + { QRGB(189,189,189), "gray74" }, + { QRGB(191,191,191), "gray75" }, + { QRGB(194,194,194), "gray76" }, + { QRGB(196,196,196), "gray77" }, + { QRGB(199,199,199), "gray78" }, + { QRGB(201,201,201), "gray79" }, + { QRGB( 20, 20, 20), "gray8" }, + { QRGB(204,204,204), "gray80" }, + { QRGB(207,207,207), "gray81" }, + { QRGB(209,209,209), "gray82" }, + { QRGB(212,212,212), "gray83" }, + { QRGB(214,214,214), "gray84" }, + { QRGB(217,217,217), "gray85" }, + { QRGB(219,219,219), "gray86" }, + { QRGB(222,222,222), "gray87" }, + { QRGB(224,224,224), "gray88" }, + { QRGB(227,227,227), "gray89" }, + { QRGB( 23, 23, 23), "gray9" }, + { QRGB(229,229,229), "gray90" }, + { QRGB(232,232,232), "gray91" }, + { QRGB(235,235,235), "gray92" }, + { QRGB(237,237,237), "gray93" }, + { QRGB(240,240,240), "gray94" }, + { QRGB(242,242,242), "gray95" }, + { QRGB(245,245,245), "gray96" }, + { QRGB(247,247,247), "gray97" }, + { QRGB(250,250,250), "gray98" }, + { QRGB(252,252,252), "gray99" }, + { QRGB( 0,255, 0), "green" }, + { QRGB( 0,255, 0), "green1" }, + { QRGB( 0,238, 0), "green2" }, + { QRGB( 0,205, 0), "green3" }, + { QRGB( 0,139, 0), "green4" }, + { QRGB(173,255, 47), "greenyellow" }, + { QRGB(190,190,190), "grey" }, + { QRGB( 0, 0, 0), "grey0" }, + { QRGB( 3, 3, 3), "grey1" }, + { QRGB( 26, 26, 26), "grey10" }, + { QRGB(255,255,255), "grey100" }, + { QRGB( 28, 28, 28), "grey11" }, + { QRGB( 31, 31, 31), "grey12" }, + { QRGB( 33, 33, 33), "grey13" }, + { QRGB( 36, 36, 36), "grey14" }, + { QRGB( 38, 38, 38), "grey15" }, + { QRGB( 41, 41, 41), "grey16" }, + { QRGB( 43, 43, 43), "grey17" }, + { QRGB( 46, 46, 46), "grey18" }, + { QRGB( 48, 48, 48), "grey19" }, + { QRGB( 5, 5, 5), "grey2" }, + { QRGB( 51, 51, 51), "grey20" }, + { QRGB( 54, 54, 54), "grey21" }, + { QRGB( 56, 56, 56), "grey22" }, + { QRGB( 59, 59, 59), "grey23" }, + { QRGB( 61, 61, 61), "grey24" }, + { QRGB( 64, 64, 64), "grey25" }, + { QRGB( 66, 66, 66), "grey26" }, + { QRGB( 69, 69, 69), "grey27" }, + { QRGB( 71, 71, 71), "grey28" }, + { QRGB( 74, 74, 74), "grey29" }, + { QRGB( 8, 8, 8), "grey3" }, + { QRGB( 77, 77, 77), "grey30" }, + { QRGB( 79, 79, 79), "grey31" }, + { QRGB( 82, 82, 82), "grey32" }, + { QRGB( 84, 84, 84), "grey33" }, + { QRGB( 87, 87, 87), "grey34" }, + { QRGB( 89, 89, 89), "grey35" }, + { QRGB( 92, 92, 92), "grey36" }, + { QRGB( 94, 94, 94), "grey37" }, + { QRGB( 97, 97, 97), "grey38" }, + { QRGB( 99, 99, 99), "grey39" }, + { QRGB( 10, 10, 10), "grey4" }, + { QRGB(102,102,102), "grey40" }, + { QRGB(105,105,105), "grey41" }, + { QRGB(107,107,107), "grey42" }, + { QRGB(110,110,110), "grey43" }, + { QRGB(112,112,112), "grey44" }, + { QRGB(115,115,115), "grey45" }, + { QRGB(117,117,117), "grey46" }, + { QRGB(120,120,120), "grey47" }, + { QRGB(122,122,122), "grey48" }, + { QRGB(125,125,125), "grey49" }, + { QRGB( 13, 13, 13), "grey5" }, + { QRGB(127,127,127), "grey50" }, + { QRGB(130,130,130), "grey51" }, + { QRGB(133,133,133), "grey52" }, + { QRGB(135,135,135), "grey53" }, + { QRGB(138,138,138), "grey54" }, + { QRGB(140,140,140), "grey55" }, + { QRGB(143,143,143), "grey56" }, + { QRGB(145,145,145), "grey57" }, + { QRGB(148,148,148), "grey58" }, + { QRGB(150,150,150), "grey59" }, + { QRGB( 15, 15, 15), "grey6" }, + { QRGB(153,153,153), "grey60" }, + { QRGB(156,156,156), "grey61" }, + { QRGB(158,158,158), "grey62" }, + { QRGB(161,161,161), "grey63" }, + { QRGB(163,163,163), "grey64" }, + { QRGB(166,166,166), "grey65" }, + { QRGB(168,168,168), "grey66" }, + { QRGB(171,171,171), "grey67" }, + { QRGB(173,173,173), "grey68" }, + { QRGB(176,176,176), "grey69" }, + { QRGB( 18, 18, 18), "grey7" }, + { QRGB(179,179,179), "grey70" }, + { QRGB(181,181,181), "grey71" }, + { QRGB(184,184,184), "grey72" }, + { QRGB(186,186,186), "grey73" }, + { QRGB(189,189,189), "grey74" }, + { QRGB(191,191,191), "grey75" }, + { QRGB(194,194,194), "grey76" }, + { QRGB(196,196,196), "grey77" }, + { QRGB(199,199,199), "grey78" }, + { QRGB(201,201,201), "grey79" }, + { QRGB( 20, 20, 20), "grey8" }, + { QRGB(204,204,204), "grey80" }, + { QRGB(207,207,207), "grey81" }, + { QRGB(209,209,209), "grey82" }, + { QRGB(212,212,212), "grey83" }, + { QRGB(214,214,214), "grey84" }, + { QRGB(217,217,217), "grey85" }, + { QRGB(219,219,219), "grey86" }, + { QRGB(222,222,222), "grey87" }, + { QRGB(224,224,224), "grey88" }, + { QRGB(227,227,227), "grey89" }, + { QRGB( 23, 23, 23), "grey9" }, + { QRGB(229,229,229), "grey90" }, + { QRGB(232,232,232), "grey91" }, + { QRGB(235,235,235), "grey92" }, + { QRGB(237,237,237), "grey93" }, + { QRGB(240,240,240), "grey94" }, + { QRGB(242,242,242), "grey95" }, + { QRGB(245,245,245), "grey96" }, + { QRGB(247,247,247), "grey97" }, + { QRGB(250,250,250), "grey98" }, + { QRGB(252,252,252), "grey99" }, + { QRGB(240,255,240), "honeydew" }, + { QRGB(240,255,240), "honeydew1" }, + { QRGB(224,238,224), "honeydew2" }, + { QRGB(193,205,193), "honeydew3" }, + { QRGB(131,139,131), "honeydew4" }, + { QRGB(255,105,180), "hotpink" }, + { QRGB(255,110,180), "hotpink1" }, + { QRGB(238,106,167), "hotpink2" }, + { QRGB(205, 96,144), "hotpink3" }, + { QRGB(139, 58, 98), "hotpink4" }, + { QRGB(205, 92, 92), "indianred" }, + { QRGB(255,106,106), "indianred1" }, + { QRGB(238, 99, 99), "indianred2" }, + { QRGB(205, 85, 85), "indianred3" }, + { QRGB(139, 58, 58), "indianred4" }, + { QRGB(255,255,240), "ivory" }, + { QRGB(255,255,240), "ivory1" }, + { QRGB(238,238,224), "ivory2" }, + { QRGB(205,205,193), "ivory3" }, + { QRGB(139,139,131), "ivory4" }, + { QRGB(240,230,140), "khaki" }, + { QRGB(255,246,143), "khaki1" }, + { QRGB(238,230,133), "khaki2" }, + { QRGB(205,198,115), "khaki3" }, + { QRGB(139,134, 78), "khaki4" }, + { QRGB(230,230,250), "lavender" }, + { QRGB(255,240,245), "lavenderblush" }, + { QRGB(255,240,245), "lavenderblush1" }, + { QRGB(238,224,229), "lavenderblush2" }, + { QRGB(205,193,197), "lavenderblush3" }, + { QRGB(139,131,134), "lavenderblush4" }, + { QRGB(124,252, 0), "lawngreen" }, + { QRGB(255,250,205), "lemonchiffon" }, + { QRGB(255,250,205), "lemonchiffon1" }, + { QRGB(238,233,191), "lemonchiffon2" }, + { QRGB(205,201,165), "lemonchiffon3" }, + { QRGB(139,137,112), "lemonchiffon4" }, + { QRGB(173,216,230), "lightblue" }, + { QRGB(191,239,255), "lightblue1" }, + { QRGB(178,223,238), "lightblue2" }, + { QRGB(154,192,205), "lightblue3" }, + { QRGB(104,131,139), "lightblue4" }, + { QRGB(240,128,128), "lightcoral" }, + { QRGB(224,255,255), "lightcyan" }, + { QRGB(224,255,255), "lightcyan1" }, + { QRGB(209,238,238), "lightcyan2" }, + { QRGB(180,205,205), "lightcyan3" }, + { QRGB(122,139,139), "lightcyan4" }, + { QRGB(238,221,130), "lightgoldenrod" }, + { QRGB(255,236,139), "lightgoldenrod1" }, + { QRGB(238,220,130), "lightgoldenrod2" }, + { QRGB(205,190,112), "lightgoldenrod3" }, + { QRGB(139,129, 76), "lightgoldenrod4" }, + { QRGB(250,250,210), "lightgoldenrodyellow" }, + { QRGB(211,211,211), "lightgray" }, + { QRGB(144,238,144), "lightgreen" }, + { QRGB(211,211,211), "lightgrey" }, + { QRGB(255,182,193), "lightpink" }, + { QRGB(255,174,185), "lightpink1" }, + { QRGB(238,162,173), "lightpink2" }, + { QRGB(205,140,149), "lightpink3" }, + { QRGB(139, 95,101), "lightpink4" }, + { QRGB(255,160,122), "lightsalmon" }, + { QRGB(255,160,122), "lightsalmon1" }, + { QRGB(238,149,114), "lightsalmon2" }, + { QRGB(205,129, 98), "lightsalmon3" }, + { QRGB(139, 87, 66), "lightsalmon4" }, + { QRGB( 32,178,170), "lightseagreen" }, + { QRGB(135,206,250), "lightskyblue" }, + { QRGB(176,226,255), "lightskyblue1" }, + { QRGB(164,211,238), "lightskyblue2" }, + { QRGB(141,182,205), "lightskyblue3" }, + { QRGB( 96,123,139), "lightskyblue4" }, + { QRGB(132,112,255), "lightslateblue" }, + { QRGB(119,136,153), "lightslategray" }, + { QRGB(119,136,153), "lightslategrey" }, + { QRGB(176,196,222), "lightsteelblue" }, + { QRGB(202,225,255), "lightsteelblue1" }, + { QRGB(188,210,238), "lightsteelblue2" }, + { QRGB(162,181,205), "lightsteelblue3" }, + { QRGB(110,123,139), "lightsteelblue4" }, + { QRGB(255,255,224), "lightyellow" }, + { QRGB(255,255,224), "lightyellow1" }, + { QRGB(238,238,209), "lightyellow2" }, + { QRGB(205,205,180), "lightyellow3" }, + { QRGB(139,139,122), "lightyellow4" }, + { QRGB( 50,205, 50), "limegreen" }, + { QRGB(250,240,230), "linen" }, + { QRGB(255, 0,255), "magenta" }, + { QRGB(255, 0,255), "magenta1" }, + { QRGB(238, 0,238), "magenta2" }, + { QRGB(205, 0,205), "magenta3" }, + { QRGB(139, 0,139), "magenta4" }, + { QRGB(176, 48, 96), "maroon" }, + { QRGB(255, 52,179), "maroon1" }, + { QRGB(238, 48,167), "maroon2" }, + { QRGB(205, 41,144), "maroon3" }, + { QRGB(139, 28, 98), "maroon4" }, + { QRGB(102,205,170), "mediumaquamarine" }, + { QRGB( 0, 0,205), "mediumblue" }, + { QRGB(186, 85,211), "mediumorchid" }, + { QRGB(224,102,255), "mediumorchid1" }, + { QRGB(209, 95,238), "mediumorchid2" }, + { QRGB(180, 82,205), "mediumorchid3" }, + { QRGB(122, 55,139), "mediumorchid4" }, + { QRGB(147,112,219), "mediumpurple" }, + { QRGB(171,130,255), "mediumpurple1" }, + { QRGB(159,121,238), "mediumpurple2" }, + { QRGB(137,104,205), "mediumpurple3" }, + { QRGB( 93, 71,139), "mediumpurple4" }, + { QRGB( 60,179,113), "mediumseagreen" }, + { QRGB(123,104,238), "mediumslateblue" }, + { QRGB( 0,250,154), "mediumspringgreen" }, + { QRGB( 72,209,204), "mediumturquoise" }, + { QRGB(199, 21,133), "mediumvioletred" }, + { QRGB( 25, 25,112), "midnightblue" }, + { QRGB(245,255,250), "mintcream" }, + { QRGB(255,228,225), "mistyrose" }, + { QRGB(255,228,225), "mistyrose1" }, + { QRGB(238,213,210), "mistyrose2" }, + { QRGB(205,183,181), "mistyrose3" }, + { QRGB(139,125,123), "mistyrose4" }, + { QRGB(255,228,181), "moccasin" }, + { QRGB(255,222,173), "navajowhite" }, + { QRGB(255,222,173), "navajowhite1" }, + { QRGB(238,207,161), "navajowhite2" }, + { QRGB(205,179,139), "navajowhite3" }, + { QRGB(139,121, 94), "navajowhite4" }, + { QRGB( 0, 0,128), "navy" }, + { QRGB( 0, 0,128), "navyblue" }, + { QRGB(253,245,230), "oldlace" }, + { QRGB(107,142, 35), "olivedrab" }, + { QRGB(192,255, 62), "olivedrab1" }, + { QRGB(179,238, 58), "olivedrab2" }, + { QRGB(154,205, 50), "olivedrab3" }, + { QRGB(105,139, 34), "olivedrab4" }, + { QRGB(255,165, 0), "orange" }, + { QRGB(255,165, 0), "orange1" }, + { QRGB(238,154, 0), "orange2" }, + { QRGB(205,133, 0), "orange3" }, + { QRGB(139, 90, 0), "orange4" }, + { QRGB(255, 69, 0), "orangered" }, + { QRGB(255, 69, 0), "orangered1" }, + { QRGB(238, 64, 0), "orangered2" }, + { QRGB(205, 55, 0), "orangered3" }, + { QRGB(139, 37, 0), "orangered4" }, + { QRGB(218,112,214), "orchid" }, + { QRGB(255,131,250), "orchid1" }, + { QRGB(238,122,233), "orchid2" }, + { QRGB(205,105,201), "orchid3" }, + { QRGB(139, 71,137), "orchid4" }, + { QRGB(238,232,170), "palegoldenrod" }, + { QRGB(152,251,152), "palegreen" }, + { QRGB(154,255,154), "palegreen1" }, + { QRGB(144,238,144), "palegreen2" }, + { QRGB(124,205,124), "palegreen3" }, + { QRGB( 84,139, 84), "palegreen4" }, + { QRGB(175,238,238), "paleturquoise" }, + { QRGB(187,255,255), "paleturquoise1" }, + { QRGB(174,238,238), "paleturquoise2" }, + { QRGB(150,205,205), "paleturquoise3" }, + { QRGB(102,139,139), "paleturquoise4" }, + { QRGB(219,112,147), "palevioletred" }, + { QRGB(255,130,171), "palevioletred1" }, + { QRGB(238,121,159), "palevioletred2" }, + { QRGB(205,104,137), "palevioletred3" }, + { QRGB(139, 71, 93), "palevioletred4" }, + { QRGB(255,239,213), "papayawhip" }, + { QRGB(255,218,185), "peachpuff" }, + { QRGB(255,218,185), "peachpuff1" }, + { QRGB(238,203,173), "peachpuff2" }, + { QRGB(205,175,149), "peachpuff3" }, + { QRGB(139,119,101), "peachpuff4" }, + { QRGB(205,133, 63), "peru" }, + { QRGB(255,192,203), "pink" }, + { QRGB(255,181,197), "pink1" }, + { QRGB(238,169,184), "pink2" }, + { QRGB(205,145,158), "pink3" }, + { QRGB(139, 99,108), "pink4" }, + { QRGB(221,160,221), "plum" }, + { QRGB(255,187,255), "plum1" }, + { QRGB(238,174,238), "plum2" }, + { QRGB(205,150,205), "plum3" }, + { QRGB(139,102,139), "plum4" }, + { QRGB(176,224,230), "powderblue" }, + { QRGB(160, 32,240), "purple" }, + { QRGB(155, 48,255), "purple1" }, + { QRGB(145, 44,238), "purple2" }, + { QRGB(125, 38,205), "purple3" }, + { QRGB( 85, 26,139), "purple4" }, + { QRGB(255, 0, 0), "red" }, + { QRGB(255, 0, 0), "red1" }, + { QRGB(238, 0, 0), "red2" }, + { QRGB(205, 0, 0), "red3" }, + { QRGB(139, 0, 0), "red4" }, + { QRGB(188,143,143), "rosybrown" }, + { QRGB(255,193,193), "rosybrown1" }, + { QRGB(238,180,180), "rosybrown2" }, + { QRGB(205,155,155), "rosybrown3" }, + { QRGB(139,105,105), "rosybrown4" }, + { QRGB( 65,105,225), "royalblue" }, + { QRGB( 72,118,255), "royalblue1" }, + { QRGB( 67,110,238), "royalblue2" }, + { QRGB( 58, 95,205), "royalblue3" }, + { QRGB( 39, 64,139), "royalblue4" }, + { QRGB(139, 69, 19), "saddlebrown" }, + { QRGB(250,128,114), "salmon" }, + { QRGB(255,140,105), "salmon1" }, + { QRGB(238,130, 98), "salmon2" }, + { QRGB(205,112, 84), "salmon3" }, + { QRGB(139, 76, 57), "salmon4" }, + { QRGB(244,164, 96), "sandybrown" }, + { QRGB( 46,139, 87), "seagreen" }, + { QRGB( 84,255,159), "seagreen1" }, + { QRGB( 78,238,148), "seagreen2" }, + { QRGB( 67,205,128), "seagreen3" }, + { QRGB( 46,139, 87), "seagreen4" }, + { QRGB(255,245,238), "seashell" }, + { QRGB(255,245,238), "seashell1" }, + { QRGB(238,229,222), "seashell2" }, + { QRGB(205,197,191), "seashell3" }, + { QRGB(139,134,130), "seashell4" }, + { QRGB(160, 82, 45), "sienna" }, + { QRGB(255,130, 71), "sienna1" }, + { QRGB(238,121, 66), "sienna2" }, + { QRGB(205,104, 57), "sienna3" }, + { QRGB(139, 71, 38), "sienna4" }, + { QRGB(135,206,235), "skyblue" }, + { QRGB(135,206,255), "skyblue1" }, + { QRGB(126,192,238), "skyblue2" }, + { QRGB(108,166,205), "skyblue3" }, + { QRGB( 74,112,139), "skyblue4" }, + { QRGB(106, 90,205), "slateblue" }, + { QRGB(131,111,255), "slateblue1" }, + { QRGB(122,103,238), "slateblue2" }, + { QRGB(105, 89,205), "slateblue3" }, + { QRGB( 71, 60,139), "slateblue4" }, + { QRGB(112,128,144), "slategray" }, + { QRGB(198,226,255), "slategray1" }, + { QRGB(185,211,238), "slategray2" }, + { QRGB(159,182,205), "slategray3" }, + { QRGB(108,123,139), "slategray4" }, + { QRGB(112,128,144), "slategrey" }, + { QRGB(255,250,250), "snow" }, + { QRGB(255,250,250), "snow1" }, + { QRGB(238,233,233), "snow2" }, + { QRGB(205,201,201), "snow3" }, + { QRGB(139,137,137), "snow4" }, + { QRGB( 0,255,127), "springgreen" }, + { QRGB( 0,255,127), "springgreen1" }, + { QRGB( 0,238,118), "springgreen2" }, + { QRGB( 0,205,102), "springgreen3" }, + { QRGB( 0,139, 69), "springgreen4" }, + { QRGB( 70,130,180), "steelblue" }, + { QRGB( 99,184,255), "steelblue1" }, + { QRGB( 92,172,238), "steelblue2" }, + { QRGB( 79,148,205), "steelblue3" }, + { QRGB( 54,100,139), "steelblue4" }, + { QRGB(210,180,140), "tan" }, + { QRGB(255,165, 79), "tan1" }, + { QRGB(238,154, 73), "tan2" }, + { QRGB(205,133, 63), "tan3" }, + { QRGB(139, 90, 43), "tan4" }, + { QRGB(216,191,216), "thistle" }, + { QRGB(255,225,255), "thistle1" }, + { QRGB(238,210,238), "thistle2" }, + { QRGB(205,181,205), "thistle3" }, + { QRGB(139,123,139), "thistle4" }, + { QRGB(255, 99, 71), "tomato" }, + { QRGB(255, 99, 71), "tomato1" }, + { QRGB(238, 92, 66), "tomato2" }, + { QRGB(205, 79, 57), "tomato3" }, + { QRGB(139, 54, 38), "tomato4" }, + { QRGB( 64,224,208), "turquoise" }, + { QRGB( 0,245,255), "turquoise1" }, + { QRGB( 0,229,238), "turquoise2" }, + { QRGB( 0,197,205), "turquoise3" }, + { QRGB( 0,134,139), "turquoise4" }, + { QRGB(238,130,238), "violet" }, + { QRGB(208, 32,144), "violetred" }, + { QRGB(255, 62,150), "violetred1" }, + { QRGB(238, 58,140), "violetred2" }, + { QRGB(205, 50,120), "violetred3" }, + { QRGB(139, 34, 82), "violetred4" }, + { QRGB(245,222,179), "wheat" }, + { QRGB(255,231,186), "wheat1" }, + { QRGB(238,216,174), "wheat2" }, + { QRGB(205,186,150), "wheat3" }, + { QRGB(139,126,102), "wheat4" }, + { QRGB(255,255,255), "white" }, + { QRGB(245,245,245), "whitesmoke" }, + { QRGB(255,255, 0), "yellow" }, + { QRGB(255,255, 0), "yellow1" }, + { QRGB(238,238, 0), "yellow2" }, + { QRGB(205,205, 0), "yellow3" }, + { QRGB(139,139, 0), "yellow4" }, + { QRGB(154,205, 50), "yellowgreen" } }; + +inline bool operator<(const char *name, const XPMRGBData &data) +{ return qstrcmp(name, data.name) < 0; } +inline bool operator<(const XPMRGBData &data, const char *name) +{ return qstrcmp(data.name, name) < 0; } + +static inline bool qt_get_named_xpm_rgb(const char *name_no_space, QRgb *rgb) +{ + const XPMRGBData *r = qBinaryFind(xpmRgbTbl, xpmRgbTbl + xpmRgbTblSize, name_no_space); + if (r != xpmRgbTbl + xpmRgbTblSize) { + *rgb = r->value; + return true; + } else { + return false; + } +} + +/***************************************************************************** + Misc. utility functions + *****************************************************************************/ +static QString fbname(const QString &fileName) // get file basename (sort of) +{ + QString s = fileName; + if (!s.isEmpty()) { + int i; + if ((i = s.lastIndexOf(QLatin1Char('/'))) >= 0) + s = s.mid(i); + if ((i = s.lastIndexOf(QLatin1Char('\\'))) >= 0) + s = s.mid(i); + QRegExp r(QLatin1String("[a-zA-Z][a-zA-Z0-9_]*")); + int p = r.indexIn(s); + if (p == -1) + s.clear(); + else + s = s.mid(p, r.matchedLength()); + } + if (s.isEmpty()) + s = QString::fromLatin1("dummy"); + return s; +} + +// Skip until ", read until the next ", return the rest in *buf +// Returns false on error, true on success + +static bool read_xpm_string(QByteArray &buf, QIODevice *d, const char * const *source, int &index, + QByteArray &state) +{ + if (source) { + buf = source[index++]; + return true; + } + + buf = ""; + bool gotQuote = false; + int offset = 0; + forever { + if (offset == state.size() || state.isEmpty()) { + char buf[2048]; + qint64 bytesRead = d->read(buf, sizeof(buf)); + if (bytesRead <= 0) + return false; + state = QByteArray(buf, int(bytesRead)); + offset = 0; + } + + if (!gotQuote) { + if (state.at(offset++) == '"') + gotQuote = true; + } else { + char c = state.at(offset++); + if (c == '"') + break; + buf += c; + } + } + state.remove(0, offset); + return true; +} + +// Tests if the given prefix can be the start of an XPM color specification + +static bool is_xpm_color_spec_prefix(const QByteArray& prefix) +{ + return prefix == "c" || + prefix == "g" || + prefix == "g4" || + prefix == "m" || + prefix == "s"; +} + +// Reads XPM header. + +static bool read_xpm_header( + QIODevice *device, const char * const * source, int& index, QByteArray &state, + int *cpp, int *ncols, int *w, int *h) +{ + QByteArray buf(200, 0); + + if (!read_xpm_string(buf, device, source, index, state)) + return false; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(Q_OS_WINCE) + if (sscanf_s(buf, "%d %d %d %d", w, h, ncols, cpp) < 4) +#else + if (sscanf(buf, "%d %d %d %d", w, h, ncols, cpp) < 4) +#endif + return false; // < 4 numbers parsed + + return true; +} + +// Reads XPM body (color information & pixels). + +static bool read_xpm_body( + QIODevice *device, const char * const * source, int& index, QByteArray& state, + int cpp, int ncols, int w, int h, QImage& image) +{ + QByteArray buf(200, 0); + int i; + + if (cpp < 0 || cpp > 15) + return false; + + // For > 256 colors, we delay creation of the image until + // after we have read the color specifications, so that we can + // create it in correct format (Format_RGB32 vs Format_ARGB32, + // depending on absence or presence of "c none", respectively) + if (ncols <= 256) { + if (image.size() != QSize(w, h) || image.format() != QImage::Format_Indexed8) { + image = QImage(w, h, QImage::Format_Indexed8); + if (image.isNull()) + return false; + } + image.setColorCount(ncols); + } + + QMap colorMap; + int currentColor; + bool hasTransparency = false; + + for(currentColor=0; currentColor < ncols; ++currentColor) { + if (!read_xpm_string(buf, device, source, index, state)) { + qWarning("QImage: XPM color specification missing"); + return false; + } + QByteArray index; + index = buf.left(cpp); + buf = buf.mid(cpp).simplified().trimmed().toLower(); + QList tokens = buf.split(' '); + i = tokens.indexOf("c"); + if (i < 0) + i = tokens.indexOf("g"); + if (i < 0) + i = tokens.indexOf("g4"); + if (i < 0) + i = tokens.indexOf("m"); + if (i < 0) { + qWarning("QImage: XPM color specification is missing: %s", buf.constData()); + return false; // no c/g/g4/m specification at all + } + QByteArray color; + while ((++i < tokens.size()) && !is_xpm_color_spec_prefix(tokens.at(i))) { + color.append(tokens.at(i)); + } + if (color.isEmpty()) { + qWarning("QImage: XPM color value is missing from specification: %s", buf.constData()); + return false; // no color value + } + buf = color; + if (buf == "none") { + hasTransparency = true; + int transparentColor = currentColor; + if (ncols <= 256) { + image.setColor(transparentColor, 0); + colorMap.insert(xpmHash(QLatin1String(index.constData())), transparentColor); + } else { + colorMap.insert(xpmHash(QLatin1String(index.constData())), 0); + } + } else { + QRgb c_rgb; + if (((buf.length()-1) % 3) && (buf[0] == '#')) { + buf.truncate(((buf.length()-1) / 4 * 3) + 1); // remove alpha channel left by imagemagick + } + if (buf[0] == '#') { + qt_get_hex_rgb(buf, &c_rgb); + } else { + qt_get_named_xpm_rgb(buf, &c_rgb); + } + if (ncols <= 256) { + image.setColor(currentColor, 0xff000000 | c_rgb); + colorMap.insert(xpmHash(QLatin1String(index.constData())), currentColor); + } else { + colorMap.insert(xpmHash(QLatin1String(index.constData())), 0xff000000 | c_rgb); + } + } + } + + if (ncols > 256) { + // Now we can create 32-bit image of appropriate format + QImage::Format format = hasTransparency ? + QImage::Format_ARGB32 : QImage::Format_RGB32; + if (image.size() != QSize(w, h) || image.format() != format) { + image = QImage(w, h, format); + if (image.isNull()) + return false; + } + } + + // Read pixels + for(int y=0; y= 0; --i) + device->ungetChar(state[i]); + char c; + while (device->getChar(&c) && c != ';') {} + while (device->getChar(&c) && c != '\n') {} + } + return true; +} + +// +// INTERNAL +// +// Reads an .xpm from either the QImageIO or from the QString *. +// One of the two HAS to be 0, the other one is used. +// + +bool qt_read_xpm_image_or_array(QIODevice *device, const char * const * source, QImage &image) +{ + if (!source) + return true; + + QByteArray buf(200, 0); + QByteArray state; + + int cpp, ncols, w, h, index = 0; + + if (device) { + // "/* XPM */" + int readBytes; + if ((readBytes = device->readLine(buf.data(), buf.size())) < 0) + return false; + + if (buf.indexOf("/* XPM") != 0) { + while (readBytes > 0) { + device->ungetChar(buf.at(readBytes - 1)); + --readBytes; + } + return false; + }// bad magic + } + + if (!read_xpm_header(device, source, index, state, &cpp, &ncols, &w, &h)) + return false; + + return read_xpm_body(device, source, index, state, cpp, ncols, w, h, image); +} + +static const char* xpm_color_name(int cpp, int index) +{ + static char returnable[5]; + static const char code[] = ".#abcdefghijklmnopqrstuvwxyzABCD" + "EFGHIJKLMNOPQRSTUVWXYZ0123456789"; + // cpp is limited to 4 and index is limited to 64^cpp + if (cpp > 1) { + if (cpp > 2) { + if (cpp > 3) { + returnable[3] = code[index % 64]; + index /= 64; + } else + returnable[3] = '\0'; + returnable[2] = code[index % 64]; + index /= 64; + } else + returnable[2] = '\0'; + // the following 4 lines are a joke! + if (index == 0) + index = 64*44+21; + else if (index == 64*44+21) + index = 0; + returnable[1] = code[index % 64]; + index /= 64; + } else + returnable[1] = '\0'; + returnable[0] = code[index]; + + return returnable; +} + + +// write XPM image data +static bool write_xpm_image(const QImage &sourceImage, QIODevice *device, const QString &fileName) +{ + if (!device->isWritable()) + return false; + + QImage image; + if (sourceImage.depth() != 32) + image = sourceImage.convertToFormat(QImage::Format_RGB32); + else + image = sourceImage; + + QMap colorMap; + + int w = image.width(), h = image.height(), ncolors = 0; + int x, y; + + // build color table + for(y=0; y k; k *= 64) { + ++cpp; + // limit to 4 characters per pixel + // 64^4 colors is enough for a 4096x4096 image + if (cpp > 4) + break; + } + + QString line; + + // write header + QTextStream s(device); + s << "/* XPM */" << endl + << "static char *" << fbname(fileName) << "[]={" << endl + << '\"' << w << ' ' << h << ' ' << ncolors << ' ' << cpp << '\"'; + + // write palette + QMap::Iterator c = colorMap.begin(); + while (c != colorMap.end()) { + QRgb color = c.key(); + if (image.format() != QImage::Format_RGB32 && !qAlpha(color)) + line.sprintf("\"%s c None\"", + xpm_color_name(cpp, *c)); + else + line.sprintf("\"%s c #%02x%02x%02x\"", + xpm_color_name(cpp, *c), + qRed(color), + qGreen(color), + qBlue(color)); + ++c; + s << ',' << endl << line; + } + + // write pixels, limit to 4 characters per pixel + line.truncate(cpp*w); + for(y=0; y 1) { + line[cc++] = QLatin1Char(chars[1]); + if (cpp > 2) { + line[cc++] = QLatin1Char(chars[2]); + if (cpp > 3) { + line[cc++] = QLatin1Char(chars[3]); + } + } + } + } + s << ',' << endl << '\"' << line << '\"'; + } + s << "};" << endl; + return (s.status() == QTextStream::Ok); +} + +QXpmHandler::QXpmHandler() + : state(Ready), index(0) +{ +} + +bool QXpmHandler::readHeader() +{ + state = Error; + if (!read_xpm_header(device(), 0, index, buffer, &cpp, &ncols, &width, &height)) + return false; + state = ReadHeader; + return true; +} + +bool QXpmHandler::readImage(QImage *image) +{ + if (state == Error) + return false; + + if (state == Ready && !readHeader()) { + state = Error; + return false; + } + + if (!read_xpm_body(device(), 0, index, buffer, cpp, ncols, width, height, *image)) { + state = Error; + return false; + } + + state = Ready; + return true; +} + +bool QXpmHandler::canRead() const +{ + if (state == Ready && !canRead(device())) + return false; + + if (state != Error) { + setFormat("xpm"); + return true; + } + + return false; +} + +bool QXpmHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QXpmHandler::canRead() called with no device"); + return false; + } + + char head[6]; + if (device->peek(head, sizeof(head)) != sizeof(head)) + return false; + + return qstrncmp(head, "/* XPM", 6) == 0; +} + +bool QXpmHandler::read(QImage *image) +{ + if (!canRead()) + return false; + return readImage(image); +} + +bool QXpmHandler::write(const QImage &image) +{ + return write_xpm_image(image, device(), fileName); +} + +bool QXpmHandler::supportsOption(ImageOption option) const +{ + return option == Name + || option == Size + || option == ImageFormat; +} + +QVariant QXpmHandler::option(ImageOption option) const +{ + if (option == Name) { + return fileName; + } else if (option == Size) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + return QSize(width, height); + } else if (option == ImageFormat) { + if (state == Error) + return QVariant(); + if (state == Ready && !const_cast(this)->readHeader()) + return QVariant(); + // If we have more than 256 colors in the table, we need to + // figure out, if it contains transparency. That means reading + // the whole color table, which is too much work work pre-checking + // the image format + if (ncols <= 256) + return QImage::Format_Indexed8; + else + return QImage::Format_Invalid; + } + + return QVariant(); +} + +void QXpmHandler::setOption(ImageOption option, const QVariant &value) +{ + if (option == Name) + fileName = value.toString(); +} + +QByteArray QXpmHandler::name() const +{ + return "xpm"; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_XPM diff --git a/src/gui/image/qxpmhandler_p.h b/src/gui/image/qxpmhandler_p.h new file mode 100644 index 0000000000..fe2e9eab68 --- /dev/null +++ b/src/gui/image/qxpmhandler_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QXPMHANDLER_P_H +#define QXPMHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qimageiohandler.h" + +#ifndef QT_NO_IMAGEFORMAT_XPM + +QT_BEGIN_NAMESPACE + +class QXpmHandler : public QImageIOHandler +{ +public: + QXpmHandler(); + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + static bool canRead(QIODevice *device); + + QByteArray name() const; + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + bool readHeader(); + bool readImage(QImage *image); + enum State { + Ready, + ReadHeader, + Error + }; + State state; + int width; + int height; + int ncols; + int cpp; + QByteArray buffer; + int index; + QString fileName; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_IMAGEFORMAT_XPM + +#endif // QXPMHANDLER_P_H diff --git a/src/gui/inputmethod/inputmethod.pri b/src/gui/inputmethod/inputmethod.pri new file mode 100644 index 0000000000..d4394380dc --- /dev/null +++ b/src/gui/inputmethod/inputmethod.pri @@ -0,0 +1,31 @@ +# Qt inputmethod module + +HEADERS +=inputmethod/qinputcontextfactory.h \ + inputmethod/qinputcontextplugin.h \ + inputmethod/qinputcontext_p.h \ + inputmethod/qinputcontext.h +SOURCES +=inputmethod/qinputcontextfactory.cpp \ + inputmethod/qinputcontextplugin.cpp \ + inputmethod/qinputcontext.cpp +x11 { + HEADERS += inputmethod/qximinputcontext_p.h + SOURCES += inputmethod/qximinputcontext_x11.cpp +} +win32 { + HEADERS += inputmethod/qwininputcontext_p.h + SOURCES += inputmethod/qwininputcontext_win.cpp +} +embedded { + HEADERS += inputmethod/qwsinputcontext_p.h + SOURCES += inputmethod/qwsinputcontext_qws.cpp +} +mac:!embedded:!qpa { + HEADERS += inputmethod/qmacinputcontext_p.h + SOURCES += inputmethod/qmacinputcontext_mac.cpp +} +symbian:contains(QT_CONFIG, s60) { + HEADERS += inputmethod/qcoefepinputcontext_p.h + SOURCES += inputmethod/qcoefepinputcontext_s60.cpp + LIBS += -lfepbase -lakninputlanguage +} + diff --git a/src/gui/inputmethod/qcoefepinputcontext_p.h b/src/gui/inputmethod/qcoefepinputcontext_p.h new file mode 100644 index 0000000000..de3577f1a6 --- /dev/null +++ b/src/gui/inputmethod/qcoefepinputcontext_p.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOEFEPINPUTCONTEXT_P_H +#define QCOEFEPINPUTCONTEXT_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. +// + +#ifndef QT_NO_IM + +#include "qinputcontext.h" +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QCoeFepInputContext : public QInputContext, + public MCoeFepAwareTextEditor, + public MCoeFepAwareTextEditor_Extension1, + public MObjectProvider +{ + Q_OBJECT + +public: + QCoeFepInputContext(QObject *parent = 0); + ~QCoeFepInputContext(); + + QString identifierName() { return QLatin1String("coefep"); } + QString language(); + + void reset(); + void update(); + + bool filterEvent(const QEvent *event); + bool symbianFilterEvent(QWidget *keyWidget, const QSymbianEvent *event); + void mouseHandler( int x, QMouseEvent *event); + bool isComposing() const { return !m_preeditString.isEmpty(); } + + void setFocusWidget(QWidget * w); + void widgetDestroyed(QWidget *w); + + TCoeInputCapabilities inputCapabilities(); + + void resetSplitViewWidget(bool keepInputWidget = false); + void ensureFocusWidgetVisible(QWidget *widget); + +protected: + void timerEvent(QTimerEvent *timerEvent); + +private: + void commitCurrentString(bool cancelFepTransaction); + void updateHints(bool mustUpdateInputCapabilities); + void applyHints(Qt::InputMethodHints hints); + void applyFormat(QList *attributes); + void queueInputCapabilitiesChanged(); + bool needsInputPanel(); + void commitTemporaryPreeditString(); + bool isWidgetVisible(QWidget *widget, int offset = 0); + +private Q_SLOTS: + void ensureInputCapabilitiesChanged(); + void translateInputWidget(); + + // From MCoeFepAwareTextEditor +public: + void StartFepInlineEditL(const TDesC& aInitialInlineText, TInt aPositionOfInsertionPointInInlineText, + TBool aCursorVisibility, const MFormCustomDraw* aCustomDraw, + MFepInlineTextFormatRetriever& aInlineTextFormatRetriever, + MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit); + void UpdateFepInlineTextL(const TDesC& aNewInlineText, TInt aPositionOfInsertionPointInInlineText); + void SetInlineEditingCursorVisibilityL(TBool aCursorVisibility); + void CancelFepInlineEdit(); + TInt DocumentLengthForFep() const; + TInt DocumentMaximumLengthForFep() const; + void SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection); + void GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const; + void GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition, TInt aLengthToRetrieve) const; + void GetFormatForFep(TCharFormat& aFormat, TInt aDocumentPosition) const; + void GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight, TInt& aAscent, + TInt aDocumentPosition) const; +private: + void DoCommitFepInlineEditL(); + MCoeFepAwareTextEditor_Extension1* Extension1(TBool& aSetToTrue); + void ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType); + + // From MCoeFepAwareTextEditor_Extension1 +public: + void SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState, TUid aTypeSafetyUid); + MCoeFepAwareTextEditor_Extension1::CState* State(TUid aTypeSafetyUid); + + // From MObjectProvider +public: + TTypeUid::Ptr MopSupplyObject(TTypeUid id); + MObjectProvider *MopNext(); + +private: + QSymbianControl *m_parent; + CAknEdwinState *m_fepState; + QString m_preeditString; + Qt::InputMethodHints m_lastImHints; + TUint m_textCapabilities; + bool m_inDestruction; + bool m_pendingInputCapabilitiesChanged; + int m_cursorVisibility; + int m_inlinePosition; + MFepInlineTextFormatRetriever *m_formatRetriever; + MFepPointerEventHandlerDuringInlineEdit *m_pointerHandler; + QBasicTimer m_tempPreeditStringTimeout; + bool m_hasTempPreeditString; + + int m_splitViewResizeBy; + Qt::WindowStates m_splitViewPreviousWindowStates; + QRectF m_transformation; + + friend class tst_QInputContext; +}; + +Q_GUI_EXPORT void qt_s60_setPartialScreenInputMode(bool enable); + +QT_END_NAMESPACE + +#endif // QT_NO_IM + +#endif // QCOEFEPINPUTCONTEXT_P_H diff --git a/src/gui/inputmethod/qcoefepinputcontext_s60.cpp b/src/gui/inputmethod/qcoefepinputcontext_s60.cpp new file mode 100644 index 0000000000..06dc25c708 --- /dev/null +++ b/src/gui/inputmethod/qcoefepinputcontext_s60.cpp @@ -0,0 +1,1200 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_IM + +#include "qcoefepinputcontext_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +// You only find these enumerations on SDK 5 onwards, so we need to provide our own +// to remain compatible with older releases. They won't be called by pre-5.0 SDKs. + +// MAknEdStateObserver::EAknCursorPositionChanged +#define QT_EAknCursorPositionChanged MAknEdStateObserver::EAknEdwinStateEvent(6) +// MAknEdStateObserver::EAknActivatePenInputRequest +#define QT_EAknActivatePenInputRequest MAknEdStateObserver::EAknEdwinStateEvent(7) + +// EAknEditorFlagSelectionVisible is only valid from 3.2 onwards. +// Sym^3 AVKON FEP manager expects that this flag is used for FEP-aware editors +// that support text selection. +#define QT_EAknEditorFlagSelectionVisible 0x100000 + +// EAknEditorFlagEnablePartialScreen is only valid from Sym^3 onwards. +#define QT_EAknEditorFlagEnablePartialScreen 0x200000 + +QT_BEGIN_NAMESPACE + +Q_GUI_EXPORT void qt_s60_setPartialScreenInputMode(bool enable) +{ + S60->partial_keyboard = enable; + + QInputContext *ic = 0; + if (QApplication::focusWidget()) { + ic = QApplication::focusWidget()->inputContext(); + } else if (qApp && qApp->inputContext()) { + ic = qApp->inputContext(); + } + if (ic) + ic->update(); +} + +QCoeFepInputContext::QCoeFepInputContext(QObject *parent) + : QInputContext(parent), + m_fepState(q_check_ptr(new CAknEdwinState)), // CBase derived object needs check on new + m_lastImHints(Qt::ImhNone), + m_textCapabilities(TCoeInputCapabilities::EAllText), + m_inDestruction(false), + m_pendingInputCapabilitiesChanged(false), + m_cursorVisibility(1), + m_inlinePosition(0), + m_formatRetriever(0), + m_pointerHandler(0), + m_hasTempPreeditString(false), + m_splitViewResizeBy(0), + m_splitViewPreviousWindowStates(Qt::WindowNoState) +{ + m_fepState->SetObjectProvider(this); + int defaultFlags = EAknEditorFlagDefault; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + if (S60->partial_keyboard) { + defaultFlags |= QT_EAknEditorFlagEnablePartialScreen; + } + defaultFlags |= QT_EAknEditorFlagSelectionVisible; + } + m_fepState->SetFlags(defaultFlags); + m_fepState->SetDefaultInputMode( EAknEditorTextInputMode ); + m_fepState->SetPermittedInputModes( EAknEditorAllInputModes ); + m_fepState->SetDefaultCase( EAknEditorTextCase ); + m_fepState->SetPermittedCases( EAknEditorAllCaseModes ); + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + m_fepState->SetNumericKeymap(EAknEditorAlphanumericNumberModeKeymap); +} + +QCoeFepInputContext::~QCoeFepInputContext() +{ + m_inDestruction = true; + + // This is to make sure that the FEP manager "forgets" about us, + // otherwise we may get callbacks even after we're destroyed. + // The call below is essentially equivalent to InputCapabilitiesChanged(), + // but is synchronous, rather than asynchronous. + CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); + + if (m_fepState) + delete m_fepState; +} + +void QCoeFepInputContext::reset() +{ + commitCurrentString(true); +} + +void QCoeFepInputContext::ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType) +{ + QT_TRAP_THROWING(m_fepState->ReportAknEdStateEventL(aEventType)); +} + +void QCoeFepInputContext::update() +{ + updateHints(false); + + // For pre-5.0 SDKs, we don't do text updates on S60 side. + if (QSysInfo::s60Version() < QSysInfo::SV_S60_5_0) { + return; + } + + // Don't be fooled (as I was) by the name of this enumeration. + // What it really does is tell the virtual keyboard UI that the text has been + // updated and it should be reflected in the internal display of the VK. + ReportAknEdStateEvent(QT_EAknCursorPositionChanged); +} + +void QCoeFepInputContext::setFocusWidget(QWidget *w) +{ + commitCurrentString(true); + + QInputContext::setFocusWidget(w); + + updateHints(true); +} + +void QCoeFepInputContext::widgetDestroyed(QWidget *w) +{ + // Make sure that the input capabilities of whatever new widget got focused are queried. + CCoeControl *ctrl = w->effectiveWinId(); + if (ctrl->IsFocused()) { + queueInputCapabilitiesChanged(); + } +} + +QString QCoeFepInputContext::language() +{ + TLanguage lang = m_fepState->LocalLanguage(); + const QByteArray localeName = qt_symbianLocaleName(lang); + if (!localeName.isEmpty()) { + return QString::fromLatin1(localeName); + } else { + return QString::fromLatin1("C"); + } +} + +bool QCoeFepInputContext::needsInputPanel() +{ + switch (QSysInfo::s60Version()) { + case QSysInfo::SV_S60_3_1: + case QSysInfo::SV_S60_3_2: + // There are no touch phones for pre-5.0 SDKs. + return false; +#ifdef Q_CC_NOKIAX86 + default: + // For emulator we assume that we need an input panel, since we can't + // separate between phone types. + return true; +#else + case QSysInfo::SV_S60_5_0: { + // For SDK == 5.0, we need phone specific detection, since the HAL API + // is no good on most phones. However, all phones at the time of writing use the + // input panel, except N97 in landscape mode, but in this mode it refuses to bring + // up the panel anyway, so we don't have to care. + return true; + } + default: + // For unknown/newer types, we try to use the HAL API. + int keyboardEnabled; + int keyboardType; + int err[2]; + err[0] = HAL::Get(HAL::EKeyboard, keyboardType); + err[1] = HAL::Get(HAL::EKeyboardState, keyboardEnabled); + if (err[0] == KErrNone && err[1] == KErrNone + && keyboardType != 0 && keyboardEnabled) + // Means that we have some sort of keyboard. + return false; + + // Fall back to using the input panel. + return true; +#endif // !Q_CC_NOKIAX86 + } +} + +bool QCoeFepInputContext::filterEvent(const QEvent *event) +{ + // The CloseSoftwareInputPanel event is not handled here, because the VK will automatically + // close when it discovers that the underlying widget does not have input capabilities. + + if (!focusWidget()) + return false; + + switch (event->type()) { + case QEvent::MouseButtonPress: + // Alphanumeric keypad doesn't like it when we click and text is still getting displayed + // It ignores the mouse event, so we need to commit and send a selection event (which will get triggered + // after the commit) + if (!m_preeditString.isEmpty()) { + commitCurrentString(true); + + int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); + + QList selectAttributes; + selectAttributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, 0, QVariant()); + QInputMethodEvent selectEvent(QLatin1String(""), selectAttributes); + sendEvent(selectEvent); + } + break; + case QEvent::KeyPress: + commitTemporaryPreeditString(); + // fall through intended + case QEvent::KeyRelease: + const QKeyEvent *keyEvent = static_cast(event); + //If proxy exists, always use hints from proxy. + QWidget *proxy = focusWidget()->focusProxy(); + Qt::InputMethodHints currentHints = proxy ? proxy->inputMethodHints() : focusWidget()->inputMethodHints(); + + switch (keyEvent->key()) { + case Qt::Key_F20: + Q_ASSERT(m_lastImHints == currentHints); + if (m_lastImHints & Qt::ImhHiddenText) { + // Special case in Symbian. On editors with secret text, F20 is for some reason + // considered to be a backspace. + QKeyEvent modifiedEvent(keyEvent->type(), Qt::Key_Backspace, keyEvent->modifiers(), + keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); + QApplication::sendEvent(focusWidget(), &modifiedEvent); + return true; + } + break; + case Qt::Key_Select: + if (!m_preeditString.isEmpty()) { + commitCurrentString(true); + return true; + } + break; + default: + break; + } + + QString widgetText = focusWidget()->inputMethodQuery(Qt::ImSurroundingText).toString(); + bool validLength; + int maxLength = focusWidget()->inputMethodQuery(Qt::ImMaximumTextLength).toInt(&validLength); + if (!keyEvent->text().isEmpty() && validLength + && widgetText.size() + m_preeditString.size() >= maxLength) { + // Don't send key events with string content if the widget is "full". + return true; + } + + if (keyEvent->type() == QEvent::KeyPress + && currentHints & Qt::ImhHiddenText + && !keyEvent->text().isEmpty()) { + // Send some temporary preedit text in order to make text visible for a moment. + m_preeditString = keyEvent->text(); + QList attributes; + QInputMethodEvent imEvent(m_preeditString, attributes); + sendEvent(imEvent); + m_tempPreeditStringTimeout.start(1000, this); + m_hasTempPreeditString = true; + update(); + return true; + } + break; + } + + if (!needsInputPanel()) + return false; + + if (event->type() == QEvent::RequestSoftwareInputPanel) { + // Notify S60 that we want the virtual keyboard to show up. + QSymbianControl *sControl; + sControl = focusWidget()->effectiveWinId()->MopGetObject(sControl); + Q_ASSERT(sControl); + + // The FEP UI temporarily steals focus when it shows up the first time, causing + // all sorts of weird effects on the focused widgets. Since it will immediately give + // back focus to us, we temporarily disable focus handling until the job's done. + if (sControl) { + sControl->setIgnoreFocusChanged(true); + } + + ensureInputCapabilitiesChanged(); + m_fepState->ReportAknEdStateEventL(MAknEdStateObserver::QT_EAknActivatePenInputRequest); + + if (sControl) { + sControl->setIgnoreFocusChanged(false); + } + return true; + } + + return false; +} + +bool QCoeFepInputContext::symbianFilterEvent(QWidget *keyWidget, const QSymbianEvent *event) +{ + Q_UNUSED(keyWidget); + if (event->type() == QSymbianEvent::CommandEvent) + // A command basically means the same as a button being pushed. With Qt buttons + // that would normally result in a reset of the input method due to the focus change. + // This should also happen for commands. + reset(); + + if (event->type() == QSymbianEvent::WindowServerEvent + && event->windowServerEvent() + && event->windowServerEvent()->Type() == EEventWindowVisibilityChanged + && S60->splitViewLastWidget) { + + QGraphicsView *gv = qobject_cast(S60->splitViewLastWidget); + const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + + if (alwaysResize) { + TUint visibleFlags = event->windowServerEvent()->VisibilityChanged()->iFlags; + if (visibleFlags & TWsVisibilityChangedEvent::EPartiallyVisible) + ensureFocusWidgetVisible(S60->splitViewLastWidget); + if (visibleFlags & TWsVisibilityChangedEvent::ENotVisible) + resetSplitViewWidget(true); + } + } + + return false; +} + +void QCoeFepInputContext::timerEvent(QTimerEvent *timerEvent) +{ + if (timerEvent->timerId() == m_tempPreeditStringTimeout.timerId()) + commitTemporaryPreeditString(); +} + +void QCoeFepInputContext::commitTemporaryPreeditString() +{ + if (m_tempPreeditStringTimeout.isActive()) + m_tempPreeditStringTimeout.stop(); + + if (!m_hasTempPreeditString) + return; + + commitCurrentString(false); +} + +void QCoeFepInputContext::mouseHandler( int x, QMouseEvent *event) +{ + Q_ASSERT(focusWidget()); + + if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) { + commitCurrentString(true); + int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); + + QList attributes; + attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos + x, 0, QVariant()); + QInputMethodEvent event(QLatin1String(""), attributes); + sendEvent(event); + } +} + +TCoeInputCapabilities QCoeFepInputContext::inputCapabilities() +{ + if (m_inDestruction || !focusWidget()) { + return TCoeInputCapabilities(TCoeInputCapabilities::ENone, 0, 0); + } + + return TCoeInputCapabilities(m_textCapabilities, this, 0); +} + +void QCoeFepInputContext::resetSplitViewWidget(bool keepInputWidget) +{ + QGraphicsView *gv = qobject_cast(S60->splitViewLastWidget); + + if (!gv) { + return; + } + + QSymbianControl *symControl = static_cast(gv->effectiveWinId()); + symControl->CancelLongTapTimer(); + + const bool alwaysResize = (gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + QWidget *windowToMove = gv->window(); + + bool userResize = gv->testAttribute(Qt::WA_Resized); + + windowToMove->setUpdatesEnabled(false); + + if (!alwaysResize) { + if (gv->scene()) { + if (gv->scene()->focusItem()) { + // Check if the widget contains cursorPositionChanged signal and disconnect from it. + QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged())); + int index = gv->scene()->focusItem()->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1)); + if (index != -1) + disconnect(gv->scene()->focusItem()->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget())); + } + + QGraphicsItem *rootItem = 0; + foreach (QGraphicsItem *item, gv->scene()->items()) { + if (!item->parentItem()) { + rootItem = item; + break; + } + } + if (rootItem) + rootItem->resetTransform(); + } + } else { + if (m_splitViewResizeBy) + gv->resize(gv->rect().width(), m_splitViewResizeBy); + } + // Resizing might have led to widget losing its original windowstate. + // Restore previous window state. + + if (m_splitViewPreviousWindowStates != windowToMove->windowState()) + windowToMove->setWindowState(m_splitViewPreviousWindowStates); + + windowToMove->setUpdatesEnabled(true); + + gv->setAttribute(Qt::WA_Resized, userResize); //not a user resize + + m_splitViewResizeBy = 0; + if (!keepInputWidget) { + m_splitViewPreviousWindowStates = Qt::WindowNoState; + S60->splitViewLastWidget = 0; + } +} + +// Checks if a given widget is visible in the splitview rect. The offset +// parameter can be used to validate if moving widget upwards or downwards +// by the offset would make a difference for the visibility. + +bool QCoeFepInputContext::isWidgetVisible(QWidget *widget, int offset) +{ + bool visible = false; + if (widget) { + QRect splitViewRect = qt_TRect2QRect(static_cast(S60->appUi())->ClientRect()); + QWidget *window = QApplication::activeWindow(); + QGraphicsView *gv = qobject_cast(widget); + if (gv && window) { + if (QGraphicsScene *scene = gv->scene()) { + if (QGraphicsItem *focusItem = scene->focusItem()) { + QPoint cursorPos = window->mapToGlobal(focusItem->cursor().pos()); + cursorPos.setY(cursorPos.y() + offset); + if (splitViewRect.contains(cursorPos)) { + visible = true; + } + } + } + } + } + return visible; +} + +// Ensure that the input widget is visible in the splitview rect. + +void QCoeFepInputContext::ensureFocusWidgetVisible(QWidget *widget) +{ + // Native side opening and closing its virtual keyboard when it changes the keyboard layout, + // has an adverse impact on long tap timer. Cancel the timer when splitview opens to avoid this. + QSymbianControl *symControl = static_cast(widget->effectiveWinId()); + symControl->CancelLongTapTimer(); + + // Graphicsviews that have vertical scrollbars should always be resized to the splitview area. + // Graphicsviews without scrollbars should be translated. + + QGraphicsView *gv = qobject_cast(widget); + if (!gv) + return; + + const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff); + const bool moveWithinVisibleArea = (S60->splitViewLastWidget != 0); + + QWidget *windowToMove = gv ? gv : symControl->widget(); + if (!windowToMove->isWindow()) + windowToMove = windowToMove->window(); + if (!windowToMove) { + return; + } + + // When opening the keyboard (not moving within the splitview area), save the original + // window state. In some cases, ensuring input widget visibility might lead to window + // states getting changed. + + if (!moveWithinVisibleArea) { + // Check if the widget contains cursorPositionChanged signal and connect to it. + QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged())); + if (gv->scene() && gv->scene()->focusItem()) { + int index = gv->scene()->focusItem()->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1)); + if (index != -1) + connect(gv->scene()->focusItem()->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget())); + } + S60->splitViewLastWidget = widget; + m_splitViewPreviousWindowStates = windowToMove->windowState(); + } + + int windowTop = widget->window()->pos().y(); + + const bool userResize = widget->testAttribute(Qt::WA_Resized); + + QRect splitViewRect = qt_TRect2QRect(static_cast(S60->appUi())->ClientRect()); + + + // When resizing a window widget, it will lose its maximized window state. + // Native applications hide statuspane in splitview state, so lets move to + // fullscreen mode. This makes available area slightly bigger, which helps usability + // and greatly reduces event passing in orientation switch cases, + // as the statuspane size is not changing. + + if (alwaysResize) + windowToMove->setUpdatesEnabled(false); + + if (!(windowToMove->windowState() & Qt::WindowFullScreen)) { + windowToMove->setWindowState( + (windowToMove->windowState() & ~(Qt::WindowMinimized | Qt::WindowFullScreen)) | Qt::WindowFullScreen); + } + + if (alwaysResize) { + if (!moveWithinVisibleArea) { + m_splitViewResizeBy = widget->height(); + windowTop = widget->geometry().top(); + widget->resize(widget->width(), splitViewRect.height() - windowTop); + } + + if (gv->scene()) { + const QRectF microFocusRect = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF(); + gv->ensureVisible(microFocusRect); + } + } else { + translateInputWidget(); + } + + if (alwaysResize) + windowToMove->setUpdatesEnabled(true); + + widget->setAttribute(Qt::WA_Resized, userResize); //not a user resize +} + +static QTextCharFormat qt_TCharFormat2QTextCharFormat(const TCharFormat &cFormat, bool validStyleColor) +{ + QTextCharFormat qFormat; + + if (validStyleColor) { + QBrush foreground(QColor(cFormat.iFontPresentation.iTextColor.Internal())); + qFormat.setForeground(foreground); + } + + qFormat.setFontStrikeOut(cFormat.iFontPresentation.iStrikethrough == EStrikethroughOn); + qFormat.setFontUnderline(cFormat.iFontPresentation.iUnderline == EUnderlineOn); + + return qFormat; +} + +void QCoeFepInputContext::updateHints(bool mustUpdateInputCapabilities) +{ + QWidget *w = focusWidget(); + if (w) { + QWidget *proxy = w->focusProxy(); + Qt::InputMethodHints hints = proxy ? proxy->inputMethodHints() : w->inputMethodHints(); + + // Since splitview support works like an input method hint, yet it is private flag, + // we need to update its state separately. + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + TInt currentFlags = m_fepState->Flags(); + if (S60->partial_keyboard) + currentFlags |= QT_EAknEditorFlagEnablePartialScreen; + else + currentFlags &= ~QT_EAknEditorFlagEnablePartialScreen; + if (currentFlags != m_fepState->Flags()) + m_fepState->SetFlags(currentFlags); + } + + if (hints != m_lastImHints) { + m_lastImHints = hints; + applyHints(hints); + } else if (!mustUpdateInputCapabilities) { + // Optimization. Return immediately if there was no change. + return; + } + } + queueInputCapabilitiesChanged(); +} + +void QCoeFepInputContext::applyHints(Qt::InputMethodHints hints) +{ + using namespace Qt; + + commitTemporaryPreeditString(); + + const bool anynumbermodes = hints & (ImhDigitsOnly | ImhFormattedNumbersOnly | ImhDialableCharactersOnly); + const bool anytextmodes = hints & (ImhUppercaseOnly | ImhLowercaseOnly | ImhEmailCharactersOnly | ImhUrlCharactersOnly); + const bool numbersOnly = anynumbermodes && !anytextmodes; + const bool noOnlys = !(hints & ImhExclusiveInputMask); + // if alphanumeric input, or if multiple incompatible number modes are selected; + // then make all symbols available in numeric mode too. + const bool needsCharMap= !numbersOnly || ((hints & ImhFormattedNumbersOnly) && (hints & ImhDialableCharactersOnly)); + TInt flags; + Qt::InputMethodHints oldHints = hints; + + // Some sanity checking. Make sure that only one preference is set. + InputMethodHints prefs = ImhPreferNumbers | ImhPreferUppercase | ImhPreferLowercase; + prefs &= hints; + if (prefs != ImhPreferNumbers && prefs != ImhPreferUppercase && prefs != ImhPreferLowercase) { + hints &= ~prefs; + } + if (!noOnlys) { + // Make sure that the preference is within the permitted set. + if (hints & ImhPreferNumbers && !anynumbermodes) { + hints &= ~ImhPreferNumbers; + } else if (hints & ImhPreferUppercase && !(hints & ImhUppercaseOnly)) { + hints &= ~ImhPreferUppercase; + } else if (hints & ImhPreferLowercase && !(hints & ImhLowercaseOnly)) { + hints &= ~ImhPreferLowercase; + } + // If there is no preference, set it to something within the permitted set. + if (!(hints & ImhPreferNumbers || hints & ImhPreferUppercase || hints & ImhPreferLowercase)) { + if (hints & ImhLowercaseOnly) { + hints |= ImhPreferLowercase; + } else if (hints & ImhUppercaseOnly) { + hints |= ImhPreferUppercase; + } else if (numbersOnly) { + hints |= ImhPreferNumbers; + } + } + } + + if (hints & ImhPreferNumbers) { + m_fepState->SetDefaultInputMode(EAknEditorNumericInputMode); + m_fepState->SetCurrentInputMode(EAknEditorNumericInputMode); + } else { + m_fepState->SetDefaultInputMode(EAknEditorTextInputMode); + m_fepState->SetCurrentInputMode(EAknEditorTextInputMode); + } + flags = 0; + if (noOnlys || (anynumbermodes && anytextmodes)) { + flags = EAknEditorAllInputModes; + } + else if (anynumbermodes) { + flags |= EAknEditorNumericInputMode; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0 + && ((hints & ImhFormattedNumbersOnly) || (hints & ImhDialableCharactersOnly))) { + //workaround - the * key does not launch the symbols menu, making it impossible to use these modes unless text mode is enabled. + flags |= EAknEditorTextInputMode; + } + } + else if (anytextmodes) { + flags |= EAknEditorTextInputMode; + } + else { + flags = EAknEditorAllInputModes; + } + m_fepState->SetPermittedInputModes(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateInputModeUpdate); + + if (hints & ImhPreferLowercase) { + m_fepState->SetDefaultCase(EAknEditorLowerCase); + m_fepState->SetCurrentCase(EAknEditorLowerCase); + } else if (hints & ImhPreferUppercase) { + m_fepState->SetDefaultCase(EAknEditorUpperCase); + m_fepState->SetCurrentCase(EAknEditorUpperCase); + } else if (hints & ImhNoAutoUppercase) { + m_fepState->SetDefaultCase(EAknEditorLowerCase); + m_fepState->SetCurrentCase(EAknEditorLowerCase); + } else { + m_fepState->SetDefaultCase(EAknEditorTextCase); + m_fepState->SetCurrentCase(EAknEditorTextCase); + } + flags = 0; + if (hints & ImhUppercaseOnly) { + flags |= EAknEditorUpperCase; + } + if (hints & ImhLowercaseOnly) { + flags |= EAknEditorLowerCase; + } + if (flags == 0) { + flags = EAknEditorAllCaseModes; + if (hints & ImhNoAutoUppercase) { + flags &= ~EAknEditorTextCase; + } + } + m_fepState->SetPermittedCases(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateCaseModeUpdate); + + flags = 0; + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + if (S60->partial_keyboard) + flags |= QT_EAknEditorFlagEnablePartialScreen; + flags |= QT_EAknEditorFlagSelectionVisible; + } + if (hints & ImhUppercaseOnly && !(hints & ImhLowercaseOnly) + || hints & ImhLowercaseOnly && !(hints & ImhUppercaseOnly)) { + flags |= EAknEditorFlagFixedCase; + } + // Using T9 and hidden text together may actually crash the FEP, so check for hidden text too. + if (hints & ImhNoPredictiveText || hints & ImhHiddenText) { + flags |= EAknEditorFlagNoT9; + } + if (needsCharMap) + flags |= EAknEditorFlagUseSCTNumericCharmap; + m_fepState->SetFlags(flags); + ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateFlagsUpdate); + + if (hints & ImhDialableCharactersOnly) { + // This is first, because if (ImhDialableCharactersOnly | ImhFormattedNumbersOnly) + // is specified, this one is more natural (# key enters a #) + flags = EAknEditorStandardNumberModeKeymap; + } else if (hints & ImhFormattedNumbersOnly) { + // # key enters decimal point + flags = EAknEditorCalculatorNumberModeKeymap; + } else if (hints & ImhDigitsOnly) { + // This is last, because it is most restrictive (# key is inactive) + flags = EAknEditorPlainNumberModeKeymap; + } else { + flags = EAknEditorStandardNumberModeKeymap; + } + m_fepState->SetNumericKeymap(static_cast(flags)); + + if (hints & ImhUrlCharactersOnly) { + // URL characters is everything except space, so a superset of the other restrictions + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_URL_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if (hints & ImhEmailCharactersOnly) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_EMAIL_ADDR_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if (needsCharMap) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + } else if ((hints & ImhFormattedNumbersOnly) || (hints & ImhDialableCharactersOnly)) { + m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); + } else { + m_fepState->SetSpecialCharacterTableResourceId(0); + } + + if (hints & ImhHiddenText) { + m_textCapabilities = TCoeInputCapabilities::EAllText | TCoeInputCapabilities::ESecretText; + } else { + m_textCapabilities = TCoeInputCapabilities::EAllText; + } +} + +void QCoeFepInputContext::applyFormat(QList *attributes) +{ + TCharFormat cFormat; + QColor styleTextColor; + if (QWidget *focused = focusWidget()) { + QGraphicsView *gv = qobject_cast(focused); + if (!gv) // could be either the QGV or its viewport that has focus + gv = qobject_cast(focused->parentWidget()); + if (gv) { + if (QGraphicsScene *scene = gv->scene()) { + if (QGraphicsItem *focusItem = scene->focusItem()) { + if (focusItem->isWidget()) { + styleTextColor = static_cast(focusItem)->palette().text().color(); + } + } + } + } else { + styleTextColor = focused->palette().text().color(); + } + } else { + styleTextColor = QApplication::palette("QLineEdit").text().color(); + } + + if (styleTextColor.isValid()) { + const TLogicalRgb fontColor(TRgb(styleTextColor.red(), styleTextColor.green(), styleTextColor.blue(), styleTextColor.alpha())); + cFormat.iFontPresentation.iTextColor = fontColor; + } + + TInt numChars = 0; + TInt charPos = 0; + int oldSize = attributes->size(); + while (m_formatRetriever) { + m_formatRetriever->GetFormatOfFepInlineText(cFormat, numChars, charPos); + if (numChars <= 0) { + // This shouldn't happen according to S60 docs, but apparently does sometimes. + break; + } + attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + charPos, + numChars, + QVariant(qt_TCharFormat2QTextCharFormat(cFormat, styleTextColor.isValid())))); + charPos += numChars; + if (charPos >= m_preeditString.size()) { + break; + } + } + + if (attributes->size() == oldSize) { + // S60 didn't provide any format, so let's give our own instead. + attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, + m_preeditString.size(), + standardFormat(PreeditFormat))); + } +} + +void QCoeFepInputContext::queueInputCapabilitiesChanged() +{ + if (m_pendingInputCapabilitiesChanged) + return; + + // Call ensureInputCapabilitiesChanged asynchronously. This is done to improve performance + // by not updating input capabilities too often. The reason we don't call the Symbian + // asynchronous version of InputCapabilitiesChanged is because we need to ensure that it + // is synchronous in some specific cases. Those will call ensureInputCapabilitesChanged. + QMetaObject::invokeMethod(this, "ensureInputCapabilitiesChanged", Qt::QueuedConnection); + m_pendingInputCapabilitiesChanged = true; +} + +void QCoeFepInputContext::ensureInputCapabilitiesChanged() +{ + if (!m_pendingInputCapabilitiesChanged) + return; + + // The call below is essentially equivalent to InputCapabilitiesChanged(), + // but is synchronous, rather than asynchronous. + CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); + m_pendingInputCapabilitiesChanged = false; +} + +void QCoeFepInputContext::translateInputWidget() +{ + QGraphicsView *gv = qobject_cast(S60->splitViewLastWidget); + QRect splitViewRect = qt_TRect2QRect(static_cast(S60->appUi())->ClientRect()); + + QRectF cursor = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF(); + QPolygon cursorP = gv->mapFromScene(cursor); + QRectF vkbRect = QRectF(splitViewRect.bottomLeft(), qApp->desktop()->rect().bottomRight()); + if (cursor.isEmpty() || vkbRect.isEmpty()) + return; + + // Fetch root item (i.e. graphicsitem with no parent) + QGraphicsItem *rootItem = 0; + foreach (QGraphicsItem *item, gv->scene()->items()) { + if (!item->parentItem()) { + rootItem = item; + break; + } + } + if (!rootItem) + return; + + m_transformation = (rootItem->transform().isTranslating()) ? QRectF(0,0, gv->width(), rootItem->transform().dy()) : QRectF(); + + // Do nothing if the cursor is visible in the splitview area. + if (splitViewRect.contains(cursorP.boundingRect())) + return; + + // New Y position should be ideally at the center of the splitview area. + // If that would expose unpainted canvas, limit the tranformation to the visible scene bottom. + + const qreal maxY = gv->sceneRect().bottom() - splitViewRect.bottom() + m_transformation.height(); + qreal dy = -(qMin(maxY, (cursor.bottom() - vkbRect.top() / 2))); + + // Do not allow transform above screen top. + if (m_transformation.height() + dy > 0) + return; + + rootItem->setTransform(QTransform::fromTranslate(0, dy), true); +} + +void QCoeFepInputContext::StartFepInlineEditL(const TDesC& aInitialInlineText, + TInt aPositionOfInsertionPointInInlineText, TBool aCursorVisibility, const MFormCustomDraw* /*aCustomDraw*/, + MFepInlineTextFormatRetriever& aInlineTextFormatRetriever, + MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + QList attributes; + + m_cursorVisibility = aCursorVisibility ? 1 : 0; + m_inlinePosition = aPositionOfInsertionPointInInlineText; + m_preeditString = qt_TDesC2QString(aInitialInlineText); + + m_formatRetriever = &aInlineTextFormatRetriever; + m_pointerHandler = &aPointerEventHandlerDuringInlineEdit; + + // With T9 aInitialInlineText is typically empty when StartFepInlineEditL is called, + // but FEP requires that selected text is always removed at StartFepInlineEditL. + // Let's remove the selected text if aInitialInlineText is empty and there is selected text + if (m_preeditString.isEmpty()) { + int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt(); + int cursorPos = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); + int replacementLength = qAbs(cursorPos-anchor); + if (replacementLength > 0) { + int replacementStart = cursorPos < anchor ? 0 : -replacementLength; + QList clearSelectionAttributes; + QInputMethodEvent clearSelectionEvent(QLatin1String(""), clearSelectionAttributes); + clearSelectionEvent.setCommitString(QLatin1String(""), replacementStart, replacementLength); + sendEvent(clearSelectionEvent); + } + } + + applyFormat(&attributes); + + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::UpdateFepInlineTextL(const TDesC& aNewInlineText, + TInt aPositionOfInsertionPointInInlineText) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + m_inlinePosition = aPositionOfInsertionPointInInlineText; + + QList attributes; + applyFormat(&attributes); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QString newPreeditString = qt_TDesC2QString(aNewInlineText); + QInputMethodEvent event(newPreeditString, attributes); + if (newPreeditString.isEmpty() && m_preeditString.isEmpty()) { + // In Symbian world this means "erase last character". + event.setCommitString(QLatin1String(""), -1, 1); + } + m_preeditString = newPreeditString; + sendEvent(event); +} + +void QCoeFepInputContext::SetInlineEditingCursorVisibilityL(TBool aCursorVisibility) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + m_cursorVisibility = aCursorVisibility ? 1 : 0; + + QList attributes; + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, + m_inlinePosition, + m_cursorVisibility, + QVariant())); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::CancelFepInlineEdit() +{ + // We are not supposed to ever have a tempPreeditString and a real preedit string + // from S60 at the same time, so it should be safe to rely on this test to determine + // whether we should honor S60's request to clear the text or not. + if (m_hasTempPreeditString) + return; + + QList attributes; + QInputMethodEvent event(QLatin1String(""), attributes); + event.setCommitString(QLatin1String(""), 0, 0); + m_preeditString.clear(); + m_inlinePosition = 0; + sendEvent(event); +} + +TInt QCoeFepInputContext::DocumentLengthForFep() const +{ + QWidget *w = focusWidget(); + if (!w) + return 0; + + QVariant variant = w->inputMethodQuery(Qt::ImSurroundingText); + return variant.value().size() + m_preeditString.size(); +} + +TInt QCoeFepInputContext::DocumentMaximumLengthForFep() const +{ + QWidget *w = focusWidget(); + if (!w) + return 0; + + QVariant variant = w->inputMethodQuery(Qt::ImMaximumTextLength); + int size; + if (variant.isValid()) { + size = variant.toInt(); + } else { + size = INT_MAX; // Sensible default for S60. + } + return size; +} + +void QCoeFepInputContext::SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection) +{ + QWidget *w = focusWidget(); + if (!w) + return; + + commitTemporaryPreeditString(); + + int pos = aCursorSelection.iAnchorPos; + int length = aCursorSelection.iCursorPos - pos; + + QList attributes; + attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, length, QVariant()); + QInputMethodEvent event(m_preeditString, attributes); + sendEvent(event); +} + +void QCoeFepInputContext::GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const +{ + QWidget *w = focusWidget(); + if (!w) { + aCursorSelection.SetSelection(0,0); + return; + } + + int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt() + m_preeditString.size(); + int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt() + m_preeditString.size(); + QString text = w->inputMethodQuery(Qt::ImSurroundingText).value(); + int combinedSize = text.size() + m_preeditString.size(); + if (combinedSize < anchor || combinedSize < cursor) { + // ### TODO! FIXME! QTBUG-5050 + // This is a hack to prevent crashing in 4.6 with QLineEdits that use input masks. + // The root problem is that cursor position is relative to displayed text instead of the + // actual text we get. + // + // To properly fix this we would need to know the displayText of QLineEdits instead + // of just the text, which on itself should be a trivial change. The difficulties start + // when we need to commit the changes back to the QLineEdit, which would have to be somehow + // able to handle displayText, too. + // + // Until properly fixed, the cursor and anchor positions will not reflect correct positions + // for masked QLineEdits, unless all the masked positions are filled in order so that + // cursor position relative to the displayed text matches position relative to actual text. + aCursorSelection.iAnchorPos = combinedSize; + aCursorSelection.iCursorPos = combinedSize; + } else { + aCursorSelection.iAnchorPos = anchor; + aCursorSelection.iCursorPos = cursor; + } +} + +void QCoeFepInputContext::GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition, + TInt aLengthToRetrieve) const +{ + QWidget *w = focusWidget(); + if (!w) { + aEditorContent.FillZ(aLengthToRetrieve); + return; + } + + QString text = w->inputMethodQuery(Qt::ImSurroundingText).value(); + // FEP expects the preedit string to be part of the editor content, so let's mix it in. + int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); + text.insert(cursor, m_preeditString); + aEditorContent.Copy(qt_QString2TPtrC(text.mid(aDocumentPosition, aLengthToRetrieve))); +} + +void QCoeFepInputContext::GetFormatForFep(TCharFormat& aFormat, TInt /* aDocumentPosition */) const +{ + QWidget *w = focusWidget(); + if (!w) { + aFormat = TCharFormat(); + return; + } + + QFont font = w->inputMethodQuery(Qt::ImFont).value(); + QFontMetrics metrics(font); + //QString name = font.rawName(); + QString name = font.defaultFamily(); // TODO! FIXME! Should be the above. + QHBufC hBufC(name); + aFormat = TCharFormat(hBufC->Des(), metrics.height()); +} + +void QCoeFepInputContext::GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight, + TInt& aAscent, TInt /* aDocumentPosition */) const +{ + QWidget *w = focusWidget(); + if (!w) { + aLeftSideOfBaseLine = TPoint(0,0); + aHeight = 0; + aAscent = 0; + return; + } + + QRect rect = w->inputMethodQuery(Qt::ImMicroFocus).value(); + aLeftSideOfBaseLine.iX = rect.left(); + aLeftSideOfBaseLine.iY = rect.bottom(); + + QFont font = w->inputMethodQuery(Qt::ImFont).value(); + QFontMetrics metrics(font); + aHeight = metrics.height(); + aAscent = metrics.ascent(); +} + +void QCoeFepInputContext::DoCommitFepInlineEditL() +{ + commitCurrentString(false); + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) + ReportAknEdStateEvent(QT_EAknCursorPositionChanged); + +} + +void QCoeFepInputContext::commitCurrentString(bool cancelFepTransaction) +{ + QList attributes; + QInputMethodEvent event(QLatin1String(""), attributes); + event.setCommitString(m_preeditString, 0, 0); + m_preeditString.clear(); + m_inlinePosition = 0; + sendEvent(event); + + m_hasTempPreeditString = false; + + if (cancelFepTransaction) { + CCoeFep* fep = CCoeEnv::Static()->Fep(); + if (fep) + fep->CancelTransaction(); + } +} + +MCoeFepAwareTextEditor_Extension1* QCoeFepInputContext::Extension1(TBool& aSetToTrue) +{ + aSetToTrue = ETrue; + return this; +} + +void QCoeFepInputContext::SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState, + TUid /*aTypeSafetyUid*/) +{ + // Note: The S60 docs are wrong! See the State() function. + if (m_fepState) + delete m_fepState; + m_fepState = static_cast(aState); +} + +MCoeFepAwareTextEditor_Extension1::CState* QCoeFepInputContext::State(TUid /*aTypeSafetyUid*/) +{ + // Note: The S60 docs are horribly wrong when describing the + // SetStateTransferingOwnershipL function and this function. They say that the former + // sets a CState object identified by the TUid, and the latter retrieves it. + // In reality, the CState is expected to always be a CAknEdwinState (even if it was not + // previously set), and the TUid is ignored. All in all, there is a single CAknEdwinState + // per QCoeFepInputContext, which should be deleted if the SetStateTransferingOwnershipL + // function is used to set a new one. + return m_fepState; +} + +TTypeUid::Ptr QCoeFepInputContext::MopSupplyObject(TTypeUid /*id*/) +{ + return TTypeUid::Null(); +} + +MObjectProvider *QCoeFepInputContext::MopNext() +{ + QWidget *w = focusWidget(); + if (w) + return w->effectiveWinId(); + return 0; +} + +QT_END_NAMESPACE + +#endif // QT_NO_IM diff --git a/src/gui/inputmethod/qinputcontext.cpp b/src/gui/inputmethod/qinputcontext.cpp new file mode 100644 index 0000000000..f083e51981 --- /dev/null +++ b/src/gui/inputmethod/qinputcontext.cpp @@ -0,0 +1,500 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Implementation of QInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +//#define QT_NO_IM_PREEDIT_RELOCATION + +#include "qinputcontext.h" +#include "qinputcontext_p.h" + +#ifndef QT_NO_IM + +#include "qplatformdefs.h" + +#include "qapplication.h" +#include "qmenu.h" +#include "qtextformat.h" +#include "qpalette.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QInputContext + \brief The QInputContext class abstracts the input method dependent data and composing state. + + \ingroup i18n + + An input method is responsible for inputting complex text that cannot + be inputted via simple keymap. It converts a sequence of input + events (typically key events) into a text string through the input + method specific converting process. The class of the processes are + widely ranging from simple finite state machine to complex text + translator that pools a whole paragraph of a text with text + editing capability to perform grammar and semantic analysis. + + To abstract such different input method specific intermediate + information, Qt offers the QInputContext as base class. The + concept is well known as 'input context' in the input method + domain. An input context is created for a text widget in response + to a demand. It is ensured that an input context is prepared for + an input method before input to a text widget. + + Multiple input contexts that belong to a single input method + may concurrently coexist. Suppose multi-window text editor. Each + text widget of window A and B holds different QInputContext + instance which contains different state information such as + partially composed text. + + \section1 Groups of Functions + + \table + \header \o Context \o Functions + + \row \o Receiving information \o + x11FilterEvent(), + filterEvent(), + mouseHandler() + + \row \o Sending back composed text \o + sendEvent() + + \row \o State change notification \o + setFocusWidget(), + reset() + + \row \o Context information \o + identifierName(), + language(), + font(), + isComposing() + + \endtable + + \section1 Licensing Information + + \legalese + Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. + + This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own + license. You may use this file under your Qt license. Following + description is copied from their original file headers. Contact + immodule-qt@freedesktop.org if any conditions of this licensing are + not clear to you. + \endlegalese + + \sa QInputContextPlugin, QInputContextFactory, QApplication::setInputContext() +*/ + +/*! + Constructs an input context with the given \a parent. +*/ +QInputContext::QInputContext(QObject* parent) + : QObject(*new QInputContextPrivate, parent) +{ +} + + +/*! + Destroys the input context. +*/ +QInputContext::~QInputContext() +{ +} + +/*! + Returns the widget that has an input focus for this input + context. + + The return value may differ from holderWidget() if the input + context is shared between several text widgets. + + \warning To ensure platform independence and support flexible + configuration of widgets, ordinary input methods should not call + this function directly. + + \sa setFocusWidget() +*/ +QWidget *QInputContext::focusWidget() const +{ + Q_D(const QInputContext); + return d->focusWidget; +} + + +/*! + Sets the \a widget that has an input focus for this input context. + + \warning Ordinary input methods must not call this function + directly. + + \sa focusWidget() +*/ +void QInputContext::setFocusWidget(QWidget *widget) +{ + Q_ASSERT(!widget || widget->testAttribute(Qt::WA_InputMethodEnabled)); + Q_D(QInputContext); + d->focusWidget = widget; +} + +/*! + \fn bool QInputContext::isComposing() const + + This function indicates whether InputMethodStart event had been + sent to the current focus widget. It is ensured that an input + context can send InputMethodCompose or InputMethodEnd event safely + if this function returned true. + + The state is automatically being tracked through sendEvent(). + + \sa sendEvent() +*/ + +/*! + This function can be reimplemented in a subclass to filter input + events. + + Return true if the \a event has been consumed. Otherwise, the + unfiltered \a event will be forwarded to widgets as ordinary + way. Although the input events have accept() and ignore() + methods, leave it untouched. + + \a event is currently restricted to events of these types: + + \list + \i CloseSoftwareInputPanel + \i KeyPress + \i KeyRelease + \i MouseButtonDblClick + \i MouseButtonPress + \i MouseButtonRelease + \i MouseMove + \i RequestSoftwareInputPanel + \endlist + + But some input method related events such as QWheelEvent or + QTabletEvent may be added in future. + + The filtering opportunity is always given to the input context as + soon as possible. It has to be taken place before any other key + event consumers such as eventfilters and accelerators because some + input methods require quite various key combination and + sequences. It often conflicts with accelerators and so on, so we + must give the input context the filtering opportunity first to + ensure all input methods work properly regardless of application + design. + + Ordinary input methods require discrete key events to work + properly, so Qt's key compression is always disabled for any input + contexts. + + \sa QKeyEvent, x11FilterEvent() +*/ +bool QInputContext::filterEvent(const QEvent * /*event*/) +{ + return false; +} + +/*! + Sends an input method event specified by \a event to the current focus + widget. Implementations of QInputContext should call this method to + send the generated input method events and not + QApplication::sendEvent(), as the events might have to get dispatched + to a different application on some platforms. + + Some complex input methods route the handling to several child + contexts (e.g. to enable language switching). To account for this, + QInputContext will check if the parent object is a QInputContext. If + yes, it will call the parents sendEvent() implementation instead of + sending the event directly. + + \sa QInputMethodEvent +*/ +void QInputContext::sendEvent(const QInputMethodEvent &event) +{ + // route events over input context parents to make chaining possible. + QInputContext *p = qobject_cast(parent()); + if (p) { + p->sendEvent(event); + return; + } + + QWidget *focus = focusWidget(); + if (!focus) + return; + + QInputMethodEvent e(event); + QApplication::sendEvent(focus, &e); +} + + +/*! + This function can be reimplemented in a subclass to handle mouse + press, release, double-click, and move events within the preedit + text. You can use the function to implement mouse-oriented user + interface such as text selection or popup menu for candidate + selection. + + The \a x parameter is the offset within the string that was sent + with the InputMethodCompose event. The alteration boundary of \a + x is ensured as character boundary of preedit string accurately. + + The \a event parameter is the event that was sent to the editor + widget. The event type is QEvent::MouseButtonPress, + QEvent::MouseButtonRelease, QEvent::MouseButtonDblClick or + QEvent::MouseMove. The event's button and state indicate + the kind of operation that was performed. +*/ +void QInputContext::mouseHandler(int /*x*/, QMouseEvent *event) +{ + // Default behavior for simple ephemeral input contexts. Some + // complex input contexts should not be reset here. + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) + reset(); +} + + +/*! + Returns the font of the current input widget +*/ +QFont QInputContext::font() const +{ + Q_D(const QInputContext); + if (!d->focusWidget) + return QApplication::font(); + + return qvariant_cast(d->focusWidget->inputMethodQuery(Qt::ImFont)); +} + +/*! + This virtual function is called when a state in the focus widget + has changed. QInputContext can then use + QWidget::inputMethodQuery() to query the new state of the widget. +*/ +void QInputContext::update() +{ +} + +/*! + This virtual function is called when the specified \a widget is + destroyed. The \a widget is a widget on which this input context + is installed. +*/ +void QInputContext::widgetDestroyed(QWidget *widget) +{ + Q_D(QInputContext); + if (widget == d->focusWidget) + setFocusWidget(0); +} + +/*! + \fn void QInputContext::reset() + + This function can be reimplemented in a subclass to reset the + state of the input method. + + This function is called by several widgets to reset input + state. For example, a text widget call this function before + inserting a text to make widget ready to accept a text. + + Default implementation is sufficient for simple input method. You + can override this function to reset external input method engines + in complex input method. In the case, call QInputContext::reset() + to ensure proper termination of inputting. + + In a reimplementation of reset(), you must not send any + QInputMethodEvent containing preedit text. You can only commit + string and attributes; otherwise, you risk breaking input state + consistency. +*/ + + +/*! + \fn QString QInputContext::identifierName() + + This function must be implemented in any subclasses to return the + identifier name of the input method. + + Return value is the name to identify and specify input methods for + the input method switching mechanism and so on. The name has to be + consistent with QInputContextPlugin::keys(). The name has to + consist of ASCII characters only. + + There are two different names with different responsibility in the + input method domain. This function returns one of them. Another + name is called 'display name' that stands for the name for + endusers appeared in a menu and so on. + + \sa QInputContextPlugin::keys(), QInputContextPlugin::displayName() +*/ + + +/*! + \fn QString QInputContext::language() + + This function must be implemented in any subclasses to return a + language code (e.g. "zh_CN", "zh_TW", "zh_HK", "ja", "ko", ...) + of the input context. If the input context can handle multiple + languages, return the currently used one. The name has to be + consistent with QInputContextPlugin::language(). + + This information will be used by language tagging feature in + QInputMethodEvent. It is required to distinguish unified han characters + correctly. It enables proper font and character code + handling. Suppose CJK-awared multilingual web browser + (that automatically modifies fonts in CJK-mixed text) and XML editor + (that automatically inserts lang attr). +*/ + + +/*! + This is a preliminary interface for Qt 4. +*/ +QList QInputContext::actions() +{ + return QList(); +} + +/*! + \enum QInputContext::StandardFormat + + \value PreeditFormat The preedit text. + \value SelectionFormat The selection text. + + \sa standardFormat() +*/ + +/*! + Returns a QTextFormat object that specifies the format for + component \a s. +*/ +QTextFormat QInputContext::standardFormat(StandardFormat s) const +{ + QWidget *focus = focusWidget(); + const QPalette &pal = focus ? focus->palette() : QApplication::palette(); + + QTextCharFormat fmt; + QColor bg; + switch (s) { + case QInputContext::PreeditFormat: { + fmt.setUnderlineStyle(QTextCharFormat::DashUnderline); + break; + } + case QInputContext::SelectionFormat: { + bg = pal.text().color(); + fmt.setBackground(QBrush(bg)); + fmt.setForeground(pal.background()); + break; + } + } + return fmt; +} + +#ifdef Q_WS_X11 +/*! + This function may be overridden only if input method is depending + on X11 and you need raw XEvent. Otherwise, this function must not. + + This function is designed to filter raw key events for XIM, but + other input methods may use this to implement some special + features such as distinguishing Shift_L and Shift_R. + + Return true if the \a event has been consumed. Otherwise, the + unfiltered \a event will be translated into QEvent and forwarded + to filterEvent(). Filtering at both x11FilterEvent() and + filterEvent() in single input method is allowed. + + \a keywidget is a client widget into which a text is inputted. \a + event is inputted XEvent. + + \sa filterEvent() +*/ +bool QInputContext::x11FilterEvent(QWidget * /*keywidget*/, XEvent * /*event*/) +{ + return false; +} +#endif // Q_WS_X11 + +#ifdef Q_OS_SYMBIAN +/*! + \since 4.6 + + This function may be overridden only if input method is depending + on Symbian and you need raw Symbian events. Otherwise, this function must not. + + This function is designed to filter raw key events on Symbian, but + other input methods may use this to implement some special + features. + + Return true if the \a event has been consumed. Otherwise, the + unfiltered \a event will be translated into QEvent and forwarded + to filterEvent(). Filtering at both symbianFilterEvent() and + filterEvent() in single input method is allowed. + + \a keywidget is a client widget into which a text is inputted. \a + event is inputted QSymbianEvent. + + \sa filterEvent() +*/ +bool QInputContext::symbianFilterEvent(QWidget * /*keywidget*/, const QSymbianEvent * /*event*/) +{ + return false; +} +#endif // Q_OS_SYMBIAN + +QT_END_NAMESPACE + +#endif //Q_NO_IM diff --git a/src/gui/inputmethod/qinputcontext.h b/src/gui/inputmethod/qinputcontext.h new file mode 100644 index 0000000000..089e6cdb9a --- /dev/null +++ b/src/gui/inputmethod/qinputcontext.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Definition of QInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QINPUTCONTEXT_H +#define QINPUTCONTEXT_H + +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_IM + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWidget; +class QFont; +class QPopupMenu; +class QInputContextPrivate; +#ifdef Q_OS_SYMBIAN +class QSymbianEvent; +#endif + +class Q_GUI_EXPORT QInputContext : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QInputContext) +public: + explicit QInputContext(QObject* parent = 0); + virtual ~QInputContext(); + + virtual QString identifierName() = 0; + virtual QString language() = 0; + + virtual void reset() = 0; + virtual void update(); + + virtual void mouseHandler( int x, QMouseEvent *event); + virtual QFont font() const; + virtual bool isComposing() const = 0; + + QWidget *focusWidget() const; + virtual void setFocusWidget( QWidget *w ); + + virtual void widgetDestroyed(QWidget *w); + + virtual QList actions(); + +#if defined(Q_WS_X11) + virtual bool x11FilterEvent( QWidget *keywidget, XEvent *event ); +#endif // Q_WS_X11 +#if defined(Q_OS_SYMBIAN) + virtual bool symbianFilterEvent( QWidget *keywidget, const QSymbianEvent *event ); +#endif // Q_OS_SYMBIAN + virtual bool filterEvent( const QEvent *event ); + + void sendEvent(const QInputMethodEvent &event); + + enum StandardFormat { + PreeditFormat, + SelectionFormat + }; + QTextFormat standardFormat(StandardFormat s) const; +private: + friend class QWidget; + friend class QWidgetPrivate; + friend class QInputContextFactory; + friend class QApplication; +private: // Disabled copy constructor and operator= + QInputContext( const QInputContext & ); + QInputContext &operator=( const QInputContext & ); + +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //Q_NO_IM + +#endif // QINPUTCONTEXT_H diff --git a/src/gui/inputmethod/qinputcontext_p.h b/src/gui/inputmethod/qinputcontext_p.h new file mode 100644 index 0000000000..be4c141d05 --- /dev/null +++ b/src/gui/inputmethod/qinputcontext_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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. +// + +/**************************************************************************** +** +** Implementation of QInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QINPUTCONTEXT_P_H +#define QINPUTCONTEXT_P_H + +#include "private/qobject_p.h" +#include "qwidget.h" +#include "qinputcontext.h" + +#ifndef QT_NO_IM + +QT_BEGIN_NAMESPACE + +class QInputContextPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QInputContext) +public: + QInputContextPrivate() + : focusWidget(0) + {} + + QWidget *focusWidget; +}; + +QT_END_NAMESPACE + +#endif + +#endif + diff --git a/src/gui/inputmethod/qinputcontextfactory.cpp b/src/gui/inputmethod/qinputcontextfactory.cpp new file mode 100644 index 0000000000..636e9d5c52 --- /dev/null +++ b/src/gui/inputmethod/qinputcontextfactory.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Implementation of QInputContextFactory class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#include "qinputcontextfactory.h" + +#ifndef QT_NO_IM + +#include "qcoreapplication.h" +#include "qinputcontext.h" +#include "qinputcontextplugin.h" + +#ifdef Q_WS_X11 +#include "private/qt_x11_p.h" +#include "qximinputcontext_p.h" +#endif +#ifdef Q_WS_WIN +#include "qwininputcontext_p.h" +#endif +#ifdef Q_WS_MAC +#include "qmacinputcontext_p.h" +#endif +#ifdef Q_WS_S60 +#include "qcoefepinputcontext_p.h" +#include "AknInputLanguageInfo.h" +#endif + +#include "private/qfactoryloader_p.h" +#include "qmutex.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QInputContextFactoryInterface_iid, QLatin1String("/inputmethods"))) +#endif + +/*! + \class QInputContextFactory + \brief The QInputContextFactory class creates QInputContext objects. + + + The input context factory creates a QInputContext object for a + given key with QInputContextFactory::create(). + + The input contexts are either built-in or dynamically loaded from + an input context plugin (see QInputContextPlugin). + + keys() returns a list of valid keys. The + keys are the names used, for example, to identify and specify + input methods for the input method switching mechanism. The names + have to be consistent with QInputContext::identifierName(), and + may only contain ASCII characters. + + A key can be used to retrieve the associated input context's + supported languages using languages(). You + can retrieve the input context's description using + description() and finally you can get a user + friendly internationalized name of the QInputContext object + specified by the key using displayName(). + + \legalese + Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. + + This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own + license. You may use this file under your Qt license. Following + description is copied from their original file headers. Contact + immodule-qt@freedesktop.org if any conditions of this licensing are + not clear to you. + \endlegalese + + \sa QInputContext, QInputContextPlugin +*/ + +/*! + Creates and returns a QInputContext object for the input context + specified by \a key with the given \a parent. Keys are case + sensitive. + + \sa keys() +*/ +QInputContext *QInputContextFactory::create( const QString& key, QObject *parent ) +{ + QInputContext *result = 0; +#if defined(Q_WS_X11) && !defined(QT_NO_XIM) + if (key == QLatin1String("xim")) { + result = new QXIMInputContext; + } +#endif +#if defined(Q_WS_WIN) + if (key == QLatin1String("win")) { + result = new QWinInputContext; + } +#endif +#if defined(Q_WS_MAC) + if (key == QLatin1String("mac")) { + result = new QMacInputContext; + } +#endif +#if defined(Q_WS_S60) + if (key == QLatin1String("coefep")) { + result = new QCoeFepInputContext; + } +#endif +#ifdef QT_NO_LIBRARY + Q_UNUSED(key); +#else + if (QInputContextFactoryInterface *factory = + qobject_cast(loader()->instance(key))) { + result = factory->create(key); + } +#endif + if (result) + result->setParent(parent); + return result; +} + + +/*! + Returns the list of keys this factory can create input contexts + for. + + The keys are the names used, for example, to identify and specify + input methods for the input method switching mechanism. The names + have to be consistent with QInputContext::identifierName(), and + may only contain ASCII characters. + + \sa create(), displayName(), QInputContext::identifierName() +*/ +QStringList QInputContextFactory::keys() +{ + QStringList result; +#if defined(Q_WS_X11) && !defined(QT_NO_XIM) + result << QLatin1String("xim"); +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_XIM) + result << QLatin1String("win"); +#endif +#if defined(Q_WS_MAC) + result << QLatin1String("mac"); +#endif +#if defined(Q_WS_S60) + result << QLatin1String("coefep"); +#endif +#ifndef QT_NO_LIBRARY + result += loader()->keys(); +#endif // QT_NO_LIBRARY + return result; +} + +#if defined(Q_WS_S60) +/*! + \internal + + This function contains pure Symbian exception handling code for + getting S60 language list. + Returned object ownership is transferred to caller. +*/ +static CAknInputLanguageList* s60LangListL() +{ + CAknInputLanguageInfo *langInfo = AknInputLanguageInfoFactory::CreateInputLanguageInfoL(); + CleanupStack::PushL(langInfo); + // In rare phone there is more than 7 languages installed -> use 7 as an array granularity + CAknInputLanguageList *langList = new (ELeave) CAknInputLanguageList(7); + CleanupStack::PushL(langList); + langInfo->AppendAvailableLanguagesL(langList); + CleanupStack::Pop(langList); + CleanupStack::PopAndDestroy(langInfo); + return langList; +} + +/*! + \internal + + This function utility function return S60 language list. + Returned object ownership is transferred to caller. +*/ +static CAknInputLanguageList* s60LangList() +{ + CAknInputLanguageList *langList = NULL; + TRAP_IGNORE(langList = s60LangListL()); + q_check_ptr(langList); + return langList; +} +#endif + +/*! + Returns the languages supported by the QInputContext object + specified by \a key. + + The languages are expressed as language code (e.g. "zh_CN", + "zh_TW", "zh_HK", "ja", "ko", ...). An input context that supports + multiple languages can return all supported languages as a + QStringList. The name has to be consistent with + QInputContext::language(). + + This information may be used to optimize a user interface. + + \sa keys(), QInputContext::language(), QLocale +*/ +QStringList QInputContextFactory::languages( const QString &key ) +{ + QStringList result; +#if defined(Q_WS_X11) && !defined(QT_NO_XIM) + if (key == QLatin1String("xim")) + return QStringList(QString()); +#endif +#if defined(Q_WS_WIN) + if (key == QLatin1String("win")) + return QStringList(QString()); +#endif +#if defined(Q_WS_MAC) + if (key == QLatin1String("mac")) + return QStringList(QString()); +#endif +#if defined(Q_WS_S60) + if (key == QLatin1String("coefep")) + { + CAknInputLanguageList *langList = s60LangList(); + int count = langList->Count(); + for (int i = 0; i < count; ++i) + { + result.append(QString(qt_symbianLocaleName(langList->At(i)->LanguageCode()))); + } + delete langList; + } +#endif +#if defined(QT_NO_LIBRARY) || defined(QT_NO_SETTINGS) + Q_UNUSED(key); +#else + if (QInputContextFactoryInterface *factory = + qobject_cast(loader()->instance(key))) + result = factory->languages(key); +#endif // QT_NO_LIBRARY + return result; +} + +/*! + Returns a user friendly internationalized name of the + QInputContext object specified by \a key. You can, for example, + use this name in a menu. + + \sa keys(), QInputContext::identifierName() +*/ +QString QInputContextFactory::displayName( const QString &key ) +{ + QString result; +#if defined(Q_WS_X11) && !defined(QT_NO_XIM) + if (key == QLatin1String("xim")) + return QInputContext::tr( "XIM" ); +#endif +#ifdef Q_WS_S60 + if (key == QLatin1String("coefep")) + return QInputContext::tr( "FEP" ); +#endif +#if defined(QT_NO_LIBRARY) || defined(QT_NO_SETTINGS) + Q_UNUSED(key); +#else + if (QInputContextFactoryInterface *factory = + qobject_cast(loader()->instance(key))) + return factory->displayName(key); +#endif // QT_NO_LIBRARY + return QString(); +} + +/*! + Returns an internationalized brief description of the QInputContext + object specified by \a key. You can, for example, use this + description in a user interface. + + \sa keys(), displayName() +*/ +QString QInputContextFactory::description( const QString &key ) +{ +#if defined(Q_WS_X11) && !defined(QT_NO_XIM) + if (key == QLatin1String("xim")) + return QInputContext::tr( "XIM input method" ); +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_XIM) + if (key == QLatin1String("win")) + return QInputContext::tr( "Windows input method" ); +#endif +#if defined(Q_WS_MAC) + if (key == QLatin1String("mac")) + return QInputContext::tr( "Mac OS X input method" ); +#endif +#if defined(Q_WS_S60) + if (key == QLatin1String("coefep")) + return QInputContext::tr( "S60 FEP input method" ); +#endif +#if defined(QT_NO_LIBRARY) || defined(QT_NO_SETTINGS) + Q_UNUSED(key); +#else + if (QInputContextFactoryInterface *factory = + qobject_cast(loader()->instance(key))) + return factory->description(key); +#endif // QT_NO_LIBRARY + return QString(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_IM diff --git a/src/gui/inputmethod/qinputcontextfactory.h b/src/gui/inputmethod/qinputcontextfactory.h new file mode 100644 index 0000000000..2382857ae1 --- /dev/null +++ b/src/gui/inputmethod/qinputcontextfactory.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Definition of QInputContextFactory class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QINPUTCONTEXTFACTORY_H +#define QINPUTCONTEXTFACTORY_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_IM + +class QInputContext; +class QWidget; + +class Q_GUI_EXPORT QInputContextFactory +{ +public: + static QStringList keys(); + static QInputContext *create( const QString &key, QObject *parent ); // should be a toplevel widget + static QStringList languages( const QString &key ); + static QString displayName( const QString &key ); + static QString description( const QString &key ); +}; + +#endif // QT_NO_IM + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QINPUTCONTEXTFACTORY_H diff --git a/src/gui/inputmethod/qinputcontextplugin.cpp b/src/gui/inputmethod/qinputcontextplugin.cpp new file mode 100644 index 0000000000..a83359324a --- /dev/null +++ b/src/gui/inputmethod/qinputcontextplugin.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Implementation of QInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#include "qinputcontextplugin.h" + +#ifndef QT_NO_IM +#ifndef QT_NO_LIBRARY + +QT_BEGIN_NAMESPACE + +/*! + \class QInputContextPlugin + \brief The QInputContextPlugin class provides an abstract base for custom QInputContext plugins. + + \reentrant + \ingroup plugins + + The input context plugin is a simple plugin interface that makes it + easy to create custom input contexts that can be loaded dynamically + into applications. + + To create an input context plugin you subclass this base class, + reimplement the pure virtual functions keys(), create(), + languages(), displayName(), and description(), and export the + class with the Q_EXPORT_PLUGIN2() macro. + + \legalese + Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. + + This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own + license. You may use this file under your Qt license. Following + description is copied from their original file headers. Contact + immodule-qt@freedesktop.org if any conditions of this licensing are + not clear to you. + \endlegalese + + \sa QInputContext, {How to Create Qt Plugins} +*/ + +/*! + \fn QStringList QInputContextPlugin::keys() const + + Returns the list of QInputContext keys this plugin provides. + + These keys are usually the class names of the custom input context + that are implemented in the plugin. The names are used, for + example, to identify and specify input methods for the input + method switching mechanism. They have to be consistent with + QInputContext::identifierName(), and may only contain ASCII + characters. + + \sa create(), displayName(), QInputContext::identifierName() +*/ + +/*! + \fn QInputContext* QInputContextPlugin::create( const QString& key ) + + Creates and returns a QInputContext object for the input context + key \a key. The input context key is usually the class name of + the required input method. + + \sa keys() +*/ + +/*! + \fn QStringList QInputContextPlugin::languages(const QString &key) + + Returns the languages supported by the QInputContext object + specified by \a key. + + The languages are expressed as language code (e.g. "zh_CN", + "zh_TW", "zh_HK", "ja", "ko", ...). An input context that supports + multiple languages can return all supported languages as + QStringList. The name has to be consistent with + QInputContext::language(). + + This information may be used to optimize user interface. + + \sa keys(), QInputContext::language(), QLocale +*/ + +/*! + \fn QString QInputContextPlugin::displayName(const QString &key) + + Returns a user friendly internationalized name of the + QInputContext object specified by \a key. You can, for example, + use this name in a menu. + + \sa keys(), QInputContext::identifierName() +*/ + +/*! + \fn QString QInputContextPlugin::description(const QString &key) + + Returns an internationalized brief description of the QInputContext + object specified by \a key. You can, for example, use this + description in a user interface. + + \sa keys(), displayName() +*/ + + +/*! + Constructs a input context plugin with the given \a parent. This + is invoked automatically by the Q_EXPORT_PLUGIN2() macro. +*/ +QInputContextPlugin::QInputContextPlugin(QObject *parent) + :QObject(parent) +{ +} + +/*! + Destroys the input context plugin. + + You never have to call this explicitly. Qt destroys a plugin + automatically when it's no longer used. +*/ +QInputContextPlugin::~QInputContextPlugin() +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_LIBRARY + +#endif // QT_NO_IM diff --git a/src/gui/inputmethod/qinputcontextplugin.h b/src/gui/inputmethod/qinputcontextplugin.h new file mode 100644 index 0000000000..3038298792 --- /dev/null +++ b/src/gui/inputmethod/qinputcontextplugin.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Definition of QInputContextPlugin class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QINPUTCONTEXTPLUGIN_H +#define QINPUTCONTEXTPLUGIN_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_IM) + +class QInputContext; +class QInputContextPluginPrivate; + +struct Q_GUI_EXPORT QInputContextFactoryInterface : public QFactoryInterface +{ + virtual QInputContext *create( const QString &key ) = 0; + virtual QStringList languages( const QString &key ) = 0; + virtual QString displayName( const QString &key ) = 0; + virtual QString description( const QString &key ) = 0; +}; + +#define QInputContextFactoryInterface_iid "com.trolltech.Qt.QInputContextFactoryInterface" +Q_DECLARE_INTERFACE(QInputContextFactoryInterface, QInputContextFactoryInterface_iid) + +class Q_GUI_EXPORT QInputContextPlugin : public QObject, public QInputContextFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QInputContextFactoryInterface:QFactoryInterface) +public: + explicit QInputContextPlugin(QObject *parent = 0); + ~QInputContextPlugin(); + + virtual QStringList keys() const = 0; + virtual QInputContext *create( const QString &key ) = 0; + virtual QStringList languages( const QString &key ) = 0; + virtual QString displayName( const QString &key ) = 0; + virtual QString description( const QString &key ) = 0; +}; + +#endif // QT_NO_IM + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QINPUTCONTEXTPLUGIN_H diff --git a/src/gui/inputmethod/qmacinputcontext_mac.cpp b/src/gui/inputmethod/qmacinputcontext_mac.cpp new file mode 100644 index 0000000000..a1ba39c1d6 --- /dev/null +++ b/src/gui/inputmethod/qmacinputcontext_mac.cpp @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include "qtextformat.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); + +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) +# define typeRefCon typeSInt32 +# define typeByteCount typeSInt32 +#endif + +QMacInputContext::QMacInputContext(QObject *parent) + : QInputContext(parent), composing(false), recursionGuard(false), textDocument(0), + keydownEvent(0) +{ +// createTextDocument(); +} + +QMacInputContext::~QMacInputContext() +{ +#ifndef QT_MAC_USE_COCOA + if(textDocument) + DeleteTSMDocument(textDocument); +#endif +} + +void +QMacInputContext::createTextDocument() +{ +#ifndef QT_MAC_USE_COCOA + if(!textDocument) { + InterfaceTypeList itl = { kUnicodeDocument }; + NewTSMDocument(1, itl, &textDocument, SRefCon(this)); + } +#endif +} + + +QString QMacInputContext::language() +{ + return QString(); +} + + +void QMacInputContext::mouseHandler(int pos, QMouseEvent *e) +{ +#ifndef QT_MAC_USE_COCOA + if(e->type() != QEvent::MouseButtonPress) + return; + + if (!composing) + return; + if (pos < 0 || pos > currentText.length()) + reset(); + // ##### handle mouse position +#else + Q_UNUSED(pos); + Q_UNUSED(e); +#endif +} + +#if !defined QT_MAC_USE_COCOA + +static QTextFormat qt_mac_compose_format() +{ + QTextCharFormat ret; + ret.setFontUnderline(true); + return ret; +} + +void QMacInputContext::reset() +{ + if (recursionGuard) + return; + if (!currentText.isEmpty()){ + QInputMethodEvent e; + e.setCommitString(currentText); + qt_sendSpontaneousEvent(focusWidget(), &e); + currentText = QString(); + } + recursionGuard = true; + createTextDocument(); + composing = false; + ActivateTSMDocument(textDocument); + FixTSMDocument(textDocument); + recursionGuard = false; +} + +bool QMacInputContext::isComposing() const +{ + return composing; +} +#endif + +void QMacInputContext::setFocusWidget(QWidget *w) +{ + createTextDocument(); +#ifndef QT_MAC_USE_COCOA + if(w) + ActivateTSMDocument(textDocument); + else + DeactivateTSMDocument(textDocument); +#endif + QInputContext::setFocusWidget(w); +} + + +#ifndef QT_MAC_USE_COCOA +static EventTypeSpec input_events[] = { + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassTextInput, kEventTextInputOffsetToPos }, + { kEventClassTextInput, kEventTextInputUpdateActiveInputArea } +}; +static EventHandlerUPP input_proc_handlerUPP = 0; +static EventHandlerRef input_proc_handler = 0; +#endif + +void +QMacInputContext::initialize() +{ +#ifndef QT_MAC_USE_COCOA + if(!input_proc_handler) { + input_proc_handlerUPP = NewEventHandlerUPP(QMacInputContext::globalEventProcessor); + InstallEventHandler(GetApplicationEventTarget(), input_proc_handlerUPP, + GetEventTypeCount(input_events), input_events, + 0, &input_proc_handler); + } +#endif +} + +void +QMacInputContext::cleanup() +{ +#ifndef QT_MAC_USE_COCOA + if(input_proc_handler) { + RemoveEventHandler(input_proc_handler); + input_proc_handler = 0; + } + if(input_proc_handlerUPP) { + DisposeEventHandlerUPP(input_proc_handlerUPP); + input_proc_handlerUPP = 0; + } +#endif +} + +void QMacInputContext::setLastKeydownEvent(EventRef event) +{ + EventRef tmpEvent = keydownEvent; + keydownEvent = event; + if (keydownEvent) + RetainEvent(keydownEvent); + if (tmpEvent) + ReleaseEvent(tmpEvent); +} + +OSStatus +QMacInputContext::globalEventProcessor(EventHandlerCallRef, EventRef event, void *) +{ +#ifndef QT_MAC_USE_COCOA + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + + SRefCon refcon = 0; + GetEventParameter(event, kEventParamTextInputSendRefCon, typeRefCon, 0, + sizeof(refcon), 0, &refcon); + QMacInputContext *context = reinterpret_cast(refcon); + + bool handled_event=true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassTextInput: { + handled_event = false; + QWidget *widget = QApplicationPrivate::focus_widget; + bool canCompose = widget && (!context || widget->inputContext() == context) + && !(widget->inputMethodHints() & Qt::ImhDigitsOnly + || widget->inputMethodHints() & Qt::ImhFormattedNumbersOnly + || widget->inputMethodHints() & Qt::ImhHiddenText); + if(!canCompose) { + handled_event = false; + } else if(ekind == kEventTextInputOffsetToPos) { + if(!widget->testAttribute(Qt::WA_InputMethodEnabled)) { + handled_event = false; + break; + } + + QRect mr(widget->inputMethodQuery(Qt::ImMicroFocus).toRect()); + QPoint mp(widget->mapToGlobal(QPoint(mr.topLeft()))); + Point pt; + pt.h = mp.x(); + pt.v = mp.y() + mr.height(); + SetEventParameter(event, kEventParamTextInputReplyPoint, typeQDPoint, + sizeof(pt), &pt); + handled_event = true; + } else if(ekind == kEventTextInputUpdateActiveInputArea) { + if(!widget->testAttribute(Qt::WA_InputMethodEnabled)) { + handled_event = false; + break; + } + + if (context->recursionGuard) + break; + + ByteCount unilen = 0; + GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, + 0, 0, &unilen, 0); + UniChar *unicode = (UniChar*)NewPtr(unilen); + GetEventParameter(event, kEventParamTextInputSendText, typeUnicodeText, + 0, unilen, 0, unicode); + QString text((QChar*)unicode, unilen / sizeof(UniChar)); + DisposePtr((char*)unicode); + + ByteCount fixed_length = 0; + GetEventParameter(event, kEventParamTextInputSendFixLen, typeByteCount, 0, + sizeof(fixed_length), 0, &fixed_length); + if(fixed_length == ULONG_MAX || fixed_length == unilen) { + QInputMethodEvent e; + e.setCommitString(text); + context->currentText = QString(); + qt_sendSpontaneousEvent(context->focusWidget(), &e); + handled_event = true; + context->reset(); + } else { + ByteCount rngSize = 0; + OSStatus err = GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, 0, + 0, &rngSize, 0); + QVarLengthArray highlight(rngSize); + if (noErr == err) { + err = GetEventParameter(event, kEventParamTextInputSendHiliteRng, typeTextRangeArray, 0, + rngSize, &rngSize, highlight.data()); + } + context->composing = true; + if(fixed_length > 0) { + const int qFixedLength = fixed_length / sizeof(UniChar); + QList attrs; + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + qFixedLength, text.length()-qFixedLength, + qt_mac_compose_format()); + QInputMethodEvent e(text, attrs); + context->currentText = text; + e.setCommitString(text.left(qFixedLength), 0, qFixedLength); + qt_sendSpontaneousEvent(widget, &e); + handled_event = true; + } else { + /* Apple's enums that they have removed from Tiger :( + enum { + kCaretPosition = 1, + kRawText = 2, + kSelectedRawText = 3, + kConvertedText = 4, + kSelectedConvertedText = 5, + kBlockFillText = 6, + kOutlineText = 7, + kSelectedText = 8 + }; + */ +#ifndef kConvertedText +#define kConvertedText 4 +#endif +#ifndef kCaretPosition +#define kCaretPosition 1 +#endif + QList attrs; + if (!highlight.isEmpty()) { + TextRangeArray *data = highlight.data(); + for (int i = 0; i < data->fNumOfRanges; ++i) { + int start = data->fRange[i].fStart / sizeof(UniChar); + int len = (data->fRange[i].fEnd - data->fRange[i].fStart) / sizeof(UniChar); + if (data->fRange[i].fHiliteStyle == kCaretPosition) { + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, start, 0, QVariant()); + continue; + } + QTextCharFormat format; + format.setFontUnderline(true); + if (data->fRange[i].fHiliteStyle == kConvertedText) + format.setUnderlineColor(Qt::gray); + else + format.setUnderlineColor(Qt::black); + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, start, len, format); + } + } else { + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, text.length(), qt_mac_compose_format()); + } + context->currentText = text; + QInputMethodEvent e(text, attrs); + qt_sendSpontaneousEvent(widget, &e); + handled_event = true; + } + } +#if 0 + if(!context->composing) + handled_event = false; +#endif + + extern bool qt_mac_eat_unicode_key; //qapplication_mac.cpp + qt_mac_eat_unicode_key = handled_event; + } else if(ekind == kEventTextInputUnicodeForKeyEvent) { + EventRef key_ev = 0; + GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, 0, + sizeof(key_ev), 0, &key_ev); + QString text; + ByteCount unilen = 0; + if(GetEventParameter(key_ev, kEventParamKeyUnicodes, typeUnicodeText, 0, 0, &unilen, 0) == noErr) { + UniChar *unicode = (UniChar*)NewPtr(unilen); + GetEventParameter(key_ev, kEventParamKeyUnicodes, typeUnicodeText, 0, unilen, 0, unicode); + text = QString((QChar*)unicode, unilen / sizeof(UniChar)); + DisposePtr((char*)unicode); + } + unsigned char chr = 0; + GetEventParameter(key_ev, kEventParamKeyMacCharCodes, typeChar, 0, sizeof(chr), 0, &chr); + if(!chr || chr >= 128 || (text.length() > 0 && (text.length() > 1 || text.at(0) != QLatin1Char(chr)))) + handled_event = !widget->testAttribute(Qt::WA_InputMethodEnabled); + QMacInputContext *context = qobject_cast(qApp->inputContext()); + if (context && context->lastKeydownEvent()) { + qt_keymapper_private()->translateKeyEvent(widget, 0, context->lastKeydownEvent(), + 0, false); + context->setLastKeydownEvent(0); + } + } + break; } + default: + break; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; +#else + Q_UNUSED(event); +#endif + return noErr; //we eat the event +} + +QT_END_NAMESPACE diff --git a/src/gui/inputmethod/qmacinputcontext_p.h b/src/gui/inputmethod/qmacinputcontext_p.h new file mode 100644 index 0000000000..c4575a3c60 --- /dev/null +++ b/src/gui/inputmethod/qmacinputcontext_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACINPUTCONTEXT_P_H +#define QMACINPUTCONTEXT_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 "QtGui/qinputcontext.h" +#include "private/qt_mac_p.h" + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QMacInputContext : public QInputContext +{ + Q_OBJECT + //Q_DECLARE_PRIVATE(QMacInputContext) + void createTextDocument(); +public: + explicit QMacInputContext(QObject* parent = 0); + virtual ~QMacInputContext(); + + virtual void setFocusWidget(QWidget *w); + virtual QString identifierName() { return QLatin1String("mac"); } + virtual QString language(); + + virtual void reset(); + + virtual bool isComposing() const; + + static OSStatus globalEventProcessor(EventHandlerCallRef, EventRef, void *); + static void initialize(); + static void cleanup(); + + EventRef lastKeydownEvent() { return keydownEvent; } + void setLastKeydownEvent(EventRef); + +protected: + void mouseHandler(int pos, QMouseEvent *); +private: + bool composing; + bool recursionGuard; + TSMDocumentID textDocument; + QString currentText; + EventRef keydownEvent; +}; + +QT_END_NAMESPACE + +#endif // QMACINPUTCONTEXT_P_H diff --git a/src/gui/inputmethod/qwininputcontext_p.h b/src/gui/inputmethod/qwininputcontext_p.h new file mode 100644 index 0000000000..dc87000b6c --- /dev/null +++ b/src/gui/inputmethod/qwininputcontext_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWININPUTCONTEXT_P_H +#define QWININPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qinputcontext.cpp. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qinputcontext.h" +#include "QtCore/qt_windows.h" + +#if !defined(IMR_RECONVERTSTRING) +typedef struct tagRECONVERTSTRING { + DWORD dwSize; + DWORD dwVersion; + DWORD dwStrLen; + DWORD dwStrOffset; + DWORD dwCompStrLen; + DWORD dwCompStrOffset; + DWORD dwTargetStrLen; + DWORD dwTargetStrOffset; +} RECONVERTSTRING, *PRECONVERTSTRING; +#endif + +QT_BEGIN_NAMESPACE + +class QWinInputContext : public QInputContext +{ + Q_OBJECT +public: + explicit QWinInputContext(QObject* parent = 0); + virtual ~QWinInputContext(); + + virtual QString identifierName() { return QLatin1String("win"); } + virtual QString language(); + + virtual void reset(); + virtual void update(); + + virtual void mouseHandler(int x, QMouseEvent *event); + virtual bool isComposing() const; + + virtual void setFocusWidget(QWidget *w); + + bool startComposition(); + bool endComposition(); + bool composition(LPARAM lparam); + int reconvertString(RECONVERTSTRING *reconv); + + static void TranslateMessage(const MSG *msg); + static LRESULT DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + static void updateImeStatus(QWidget *w, bool hasFocus); + static void enablePopupChild(QWidget *w, bool e); + static void enable(QWidget *w, bool e); + +private: + void init(); + bool recursionGuard; +}; + +QT_END_NAMESPACE + +#endif // QWININPUTCONTEXT_P_H diff --git a/src/gui/inputmethod/qwininputcontext_win.cpp b/src/gui/inputmethod/qwininputcontext_win.cpp new file mode 100644 index 0000000000..4289cf4191 --- /dev/null +++ b/src/gui/inputmethod/qwininputcontext_win.cpp @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwininputcontext_p.h" +#include "qinputcontext_p.h" + +#include "qfont.h" +#include "qwidget.h" +#include "qapplication.h" +#include "qevent.h" +#include "qtextformat.h" +#include "qtextboundaryfinder.h" + +//#define Q_IME_DEBUG + +#ifdef Q_IME_DEBUG +#include "qdebug.h" +#endif + +#if defined(Q_WS_WINCE) +extern void qt_wince_show_SIP(bool show); // defined in qguifunctions_wince.cpp +#endif + +QT_BEGIN_NAMESPACE + +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); + + +DEFINE_GUID(IID_IActiveIMMApp, +0x08c0e040, 0x62d1, 0x11d1, 0x93, 0x26, 0x0, 0x60, 0xb0, 0x67, 0xb8, 0x6e); + + + +DEFINE_GUID(CLSID_CActiveIMM, +0x4955DD33, 0xB159, 0x11d0, 0x8F, 0xCF, 0x0, 0xAA, 0x00, 0x6B, 0xCC, 0x59); + + + +DEFINE_GUID(IID_IActiveIMMMessagePumpOwner, +0xb5cf2cfa, 0x8aeb, 0x11d1, 0x93, 0x64, 0x0, 0x60, 0xb0, 0x67, 0xb8, 0x6e); + + + +interface IEnumRegisterWordW; +interface IEnumInputContext; + + +bool qt_sendSpontaneousEvent(QObject*, QEvent*); + + +#define IFMETHOD HRESULT STDMETHODCALLTYPE + +interface IActiveIMMApp : public IUnknown +{ +public: + virtual IFMETHOD AssociateContext(HWND hWnd, HIMC hIME, HIMC __RPC_FAR *phPrev) = 0; + virtual IFMETHOD dummy_ConfigureIMEA() = 0; + virtual IFMETHOD ConfigureIMEW(HKL hKL, HWND hWnd, DWORD dwMode, REGISTERWORDW __RPC_FAR *pData) = 0; + virtual IFMETHOD CreateContext(HIMC __RPC_FAR *phIMC) = 0; + virtual IFMETHOD DestroyContext(HIMC hIME) = 0; + virtual IFMETHOD dummy_EnumRegisterWordA() = 0; + virtual IFMETHOD EnumRegisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szRegister, LPVOID pData, + IEnumRegisterWordW __RPC_FAR *__RPC_FAR *pEnum) = 0; + virtual IFMETHOD dummy_EscapeA() = 0; + virtual IFMETHOD EscapeW(HKL hKL, HIMC hIMC, UINT uEscape, LPVOID pData, LRESULT __RPC_FAR *plResult) = 0; + virtual IFMETHOD dummy_GetCandidateListA() = 0; + virtual IFMETHOD GetCandidateListW(HIMC hIMC, DWORD dwIndex, UINT uBufLen, CANDIDATELIST __RPC_FAR *pCandList, + UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD dummy_GetCandidateListCountA() = 0; + virtual IFMETHOD GetCandidateListCountW(HIMC hIMC, DWORD __RPC_FAR *pdwListSize, DWORD __RPC_FAR *pdwBufLen) = 0; + virtual IFMETHOD GetCandidateWindow(HIMC hIMC, DWORD dwIndex, CANDIDATEFORM __RPC_FAR *pCandidate) = 0; + virtual IFMETHOD dummy_GetCompositionFontA() = 0; + virtual IFMETHOD GetCompositionFontW(HIMC hIMC, LOGFONTW __RPC_FAR *plf) = 0; + virtual IFMETHOD dummy_GetCompositionStringA() = 0; + virtual IFMETHOD GetCompositionStringW(HIMC hIMC, DWORD dwIndex, DWORD dwBufLen, LONG __RPC_FAR *plCopied, LPVOID pBuf) = 0; + virtual IFMETHOD GetCompositionWindow(HIMC hIMC, COMPOSITIONFORM __RPC_FAR *pCompForm) = 0; + virtual IFMETHOD GetContext(HWND hWnd, HIMC __RPC_FAR *phIMC) = 0; + virtual IFMETHOD dummy_GetConversionListA() = 0; + virtual IFMETHOD GetConversionListW(HKL hKL, HIMC hIMC, LPWSTR pSrc, UINT uBufLen, UINT uFlag, + CANDIDATELIST __RPC_FAR *pDst, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetConversionStatus(HIMC hIMC, DWORD __RPC_FAR *pfdwConversion, DWORD __RPC_FAR *pfdwSentence) = 0; + virtual IFMETHOD GetDefaultIMEWnd(HWND hWnd, HWND __RPC_FAR *phDefWnd) = 0; + virtual IFMETHOD dummy_GetDescriptionA() = 0; + virtual IFMETHOD GetDescriptionW(HKL hKL, UINT uBufLen, LPWSTR szDescription, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD dummy_GetGuideLineA() = 0; + virtual IFMETHOD GetGuideLineW(HIMC hIMC, DWORD dwIndex, DWORD dwBufLen, LPWSTR pBuf, DWORD __RPC_FAR *pdwResult) = 0; + virtual IFMETHOD dummy_GetIMEFileNameA() = 0; + virtual IFMETHOD GetIMEFileNameW(HKL hKL, UINT uBufLen, LPWSTR szFileName, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetOpenStatus(HIMC hIMC) = 0; + virtual IFMETHOD GetProperty(HKL hKL, DWORD fdwIndex, DWORD __RPC_FAR *pdwProperty) = 0; + virtual IFMETHOD dummy_GetRegisterWordStyleA() = 0; + virtual IFMETHOD GetRegisterWordStyleW(HKL hKL, UINT nItem, STYLEBUFW __RPC_FAR *pStyleBuf, UINT __RPC_FAR *puCopied) = 0; + virtual IFMETHOD GetStatusWindowPos(HIMC hIMC, POINT __RPC_FAR *pptPos) = 0; + virtual IFMETHOD GetVirtualKey(HWND hWnd, UINT __RPC_FAR *puVirtualKey) = 0; + virtual IFMETHOD dummy_InstallIMEA() = 0; + virtual IFMETHOD InstallIMEW(LPWSTR szIMEFileName, LPWSTR szLayoutText, HKL __RPC_FAR *phKL) = 0; + virtual IFMETHOD IsIME(HKL hKL) = 0; + virtual IFMETHOD dummy_IsUIMessageA() = 0; + virtual IFMETHOD IsUIMessageW(HWND hWndIME, UINT msg, WPARAM wParam, LPARAM lParam) = 0; + virtual IFMETHOD NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) = 0; + virtual IFMETHOD dummy_RegisterWordA() = 0; + virtual IFMETHOD RegisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szRegister) = 0; + virtual IFMETHOD ReleaseContext(HWND hWnd, HIMC hIMC) = 0; + virtual IFMETHOD SetCandidateWindow(HIMC hIMC, CANDIDATEFORM __RPC_FAR *pCandidate) = 0; + virtual IFMETHOD SetCompositionFontA(HIMC hIMC, LOGFONTA __RPC_FAR *plf) = 0; + virtual IFMETHOD SetCompositionFontW(HIMC hIMC, LOGFONTW __RPC_FAR *plf) = 0; + virtual IFMETHOD dummy_SetCompositionStringA() = 0; + virtual IFMETHOD SetCompositionStringW(HIMC hIMC, DWORD dwIndex, LPVOID pComp, DWORD dwCompLen, + LPVOID pRead, DWORD dwReadLen) = 0; + virtual IFMETHOD SetCompositionWindow(HIMC hIMC, COMPOSITIONFORM __RPC_FAR *pCompForm) = 0; + virtual IFMETHOD SetConversionStatus(HIMC hIMC, DWORD fdwConversion, DWORD fdwSentence) = 0; + virtual IFMETHOD SetOpenStatus(HIMC hIMC, BOOL fOpen) = 0; + virtual IFMETHOD SetStatusWindowPos(HIMC hIMC, POINT __RPC_FAR *pptPos) = 0; + virtual IFMETHOD SimulateHotKey(HWND hWnd, DWORD dwHotKeyID) = 0; + virtual IFMETHOD dummy_UnregisterWordA() = 0; + virtual IFMETHOD UnregisterWordW(HKL hKL, LPWSTR szReading, DWORD dwStyle, LPWSTR szUnregister) = 0; + virtual IFMETHOD Activate(BOOL fRestoreLayout) = 0; + virtual IFMETHOD Deactivate(void) = 0; + virtual IFMETHOD OnDefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT __RPC_FAR *plResult) = 0; + virtual IFMETHOD FilterClientWindows(ATOM __RPC_FAR *aaClassList, UINT uSize) = 0; + virtual IFMETHOD dummy_GetCodePageA() = 0; + virtual IFMETHOD GetLangId(HKL hKL, LANGID __RPC_FAR *plid) = 0; + virtual IFMETHOD AssociateContextEx(HWND hWnd, HIMC hIMC, DWORD dwFlags) = 0; + virtual IFMETHOD DisableIME(DWORD idThread) = 0; + virtual IFMETHOD dummy_GetImeMenuItemsA() = 0; + virtual IFMETHOD GetImeMenuItemsW(HIMC hIMC, DWORD dwFlags, DWORD dwType, /*IMEMENUITEMINFOW*/ void __RPC_FAR *pImeParentMenu, + /*IMEMENUITEMINFOW*/ void __RPC_FAR *pImeMenu, DWORD dwSize, DWORD __RPC_FAR *pdwResult) = 0; + virtual IFMETHOD EnumInputContext(DWORD idThread, IEnumInputContext __RPC_FAR *__RPC_FAR *ppEnum) = 0; +}; + +interface IActiveIMMMessagePumpOwner : public IUnknown +{ +public: + virtual IFMETHOD Start(void) = 0; + virtual IFMETHOD End(void) = 0; + virtual IFMETHOD OnTranslateMessage(const MSG __RPC_FAR *pMsg) = 0; + virtual IFMETHOD Pause(DWORD __RPC_FAR *pdwCookie) = 0; + virtual IFMETHOD Resume(DWORD dwCookie) = 0; +}; + + +static IActiveIMMApp *aimm = 0; +static IActiveIMMMessagePumpOwner *aimmpump = 0; +static QString *imeComposition = 0; +static int imePosition = -1; +bool qt_use_rtl_extensions = false; +static bool haveCaret = false; + +#ifndef LGRPID_INSTALLED +#define LGRPID_INSTALLED 0x00000001 // installed language group ids +#define LGRPID_SUPPORTED 0x00000002 // supported language group ids +#endif + +#ifndef LGRPID_ARABIC +#define LGRPID_WESTERN_EUROPE 0x0001 // Western Europe & U.S. +#define LGRPID_CENTRAL_EUROPE 0x0002 // Central Europe +#define LGRPID_BALTIC 0x0003 // Baltic +#define LGRPID_GREEK 0x0004 // Greek +#define LGRPID_CYRILLIC 0x0005 // Cyrillic +#define LGRPID_TURKISH 0x0006 // Turkish +#define LGRPID_JAPANESE 0x0007 // Japanese +#define LGRPID_KOREAN 0x0008 // Korean +#define LGRPID_TRADITIONAL_CHINESE 0x0009 // Traditional Chinese +#define LGRPID_SIMPLIFIED_CHINESE 0x000a // Simplified Chinese +#define LGRPID_THAI 0x000b // Thai +#define LGRPID_HEBREW 0x000c // Hebrew +#define LGRPID_ARABIC 0x000d // Arabic +#define LGRPID_VIETNAMESE 0x000e // Vietnamese +#define LGRPID_INDIC 0x000f // Indic +#define LGRPID_GEORGIAN 0x0010 // Georgian +#define LGRPID_ARMENIAN 0x0011 // Armenian +#endif + +static DWORD WM_MSIME_MOUSE = 0; + +QWinInputContext::QWinInputContext(QObject *parent) + : QInputContext(parent), recursionGuard(false) +{ +#ifndef Q_WS_WINCE + QSysInfo::WinVersion ver = QSysInfo::windowsVersion(); + if (ver & QSysInfo::WV_NT_based && ver >= QSysInfo::WV_VISTA) { + // Since the IsValidLanguageGroup/IsValidLocale functions always return true on + // Vista, check the Keyboard Layouts for enabling RTL. + UINT nLayouts = GetKeyboardLayoutList(0, 0); + if (nLayouts) { + HKL *lpList = new HKL[nLayouts]; + GetKeyboardLayoutList(nLayouts, lpList); + for (int i = 0; i<(int)nLayouts; i++) { + WORD plangid = PRIMARYLANGID((quintptr)lpList[i]); + if (plangid == LANG_ARABIC + || plangid == LANG_HEBREW + || plangid == LANG_FARSI +#ifdef LANG_SYRIAC + || plangid == LANG_SYRIAC +#endif + ) { + qt_use_rtl_extensions = true; + break; + } + } + delete []lpList; + } + } else { + // figure out whether a RTL language is installed + qt_use_rtl_extensions = IsValidLanguageGroup(LGRPID_ARABIC, LGRPID_INSTALLED) + || IsValidLanguageGroup(LGRPID_HEBREW, LGRPID_INSTALLED) + || IsValidLocale(MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) + || IsValidLocale(MAKELCID(MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) +#ifdef LANG_SYRIAC + || IsValidLocale(MAKELCID(MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED) +#endif + || IsValidLocale(MAKELCID(MAKELANGID(LANG_FARSI, SUBLANG_DEFAULT), SORT_DEFAULT), LCID_INSTALLED); + } +#else + qt_use_rtl_extensions = false; +#endif + + WM_MSIME_MOUSE = RegisterWindowMessage(L"MSIMEMouseOperation"); +} + +QWinInputContext::~QWinInputContext() +{ + // release active input method if we have one + if (aimm) { + aimmpump->End(); + aimmpump->Release(); + aimm->Deactivate(); + aimm->Release(); + aimm = 0; + aimmpump = 0; + } + delete imeComposition; + imeComposition = 0; +} + +static HWND getDefaultIMEWnd(HWND wnd) +{ + HWND ime_wnd; + if(aimm) + aimm->GetDefaultIMEWnd(wnd, &ime_wnd); + else + ime_wnd = ImmGetDefaultIMEWnd(wnd); + return ime_wnd; +} + +static HIMC getContext(HWND wnd) +{ + HIMC imc; + if (aimm) + aimm->GetContext(wnd, &imc); + else + imc = ImmGetContext(wnd); + + return imc; +} + +static void releaseContext(HWND wnd, HIMC imc) +{ + if (aimm) + aimm->ReleaseContext(wnd, imc); + else + ImmReleaseContext(wnd, imc); +} + +static void notifyIME(HIMC imc, DWORD dwAction, DWORD dwIndex, DWORD dwValue) +{ + if (!imc) + return; + if (aimm) + aimm->NotifyIME(imc, dwAction, dwIndex, dwValue); + else + ImmNotifyIME(imc, dwAction, dwIndex, dwValue); +} + +static LONG getCompositionString(HIMC himc, DWORD dwIndex, LPVOID lpbuf, DWORD dBufLen) +{ + LONG len = 0; + if (aimm) + aimm->GetCompositionStringW(himc, dwIndex, dBufLen, &len, lpbuf); + else + len = ImmGetCompositionString(himc, dwIndex, lpbuf, dBufLen); + return len; +} + +static int getCursorPosition(HIMC himc) +{ + return getCompositionString(himc, GCS_CURSORPOS, 0, 0); +} + +static QString getString(HIMC himc, DWORD dwindex, int *selStart = 0, int *selLength = 0) +{ + const int bufferSize = 256; + wchar_t buffer[bufferSize]; + int len = getCompositionString(himc, dwindex, buffer, bufferSize * sizeof(wchar_t)); + + if (selStart) { + char attrbuffer[bufferSize]; + int attrlen = getCompositionString(himc, GCS_COMPATTR, attrbuffer, bufferSize); + *selStart = attrlen+1; + *selLength = -1; + for (int i = 0; i < attrlen; i++) { + if (attrbuffer[i] & ATTR_TARGET_CONVERTED) { + *selStart = qMin(*selStart, i); + *selLength = qMax(*selLength, i); + } + } + *selLength = qMax(0, *selLength - *selStart + 1); + } + + if (len <= 0) + return QString(); + + return QString((QChar*)buffer, len / sizeof(QChar)); +} + +void QWinInputContext::TranslateMessage(const MSG *msg) +{ + if (!aimmpump || aimmpump->OnTranslateMessage(msg) != S_OK) + ::TranslateMessage(msg); +} + +LRESULT QWinInputContext::DefWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT retval; + if (!aimm || aimm->OnDefWindowProc(hwnd, msg, wParam, lParam, &retval) != S_OK) + { + retval = ::DefWindowProc(hwnd, msg, wParam, lParam); + } + return retval; +} + + +void QWinInputContext::update() +{ + QWidget *w = focusWidget(); + if(!w) + return; + + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(w->effectiveWinId()); + + if (!imc) + return; + + QFont f = qvariant_cast(w->inputMethodQuery(Qt::ImFont)); + HFONT hf; + hf = f.handle(); + + LOGFONT lf; + if (GetObject(hf, sizeof(lf), &lf)) { + if (aimm) + aimm->SetCompositionFontW(imc, &lf); + else + ImmSetCompositionFont(imc, &lf); + } + + QRect r = w->inputMethodQuery(Qt::ImMicroFocus).toRect(); + + // The ime window positions are based on the WinId with active focus. + QWidget *imeWnd = QWidget::find(::GetFocus()); + if (imeWnd && !aimm) { + QPoint pt (r.topLeft()); + pt = w->mapToGlobal(pt); + pt = imeWnd->mapFromGlobal(pt); + r.moveTo(pt); + } + + COMPOSITIONFORM cf; + // ### need X-like inputStyle config settings + cf.dwStyle = CFS_FORCE_POSITION; + cf.ptCurrentPos.x = r.x(); + cf.ptCurrentPos.y = r.y(); + + CANDIDATEFORM candf; + candf.dwIndex = 0; + candf.dwStyle = CFS_EXCLUDE; + candf.ptCurrentPos.x = r.x(); + candf.ptCurrentPos.y = r.y() + r.height(); + candf.rcArea.left = r.x(); + candf.rcArea.top = r.y(); + candf.rcArea.right = r.x() + r.width(); + candf.rcArea.bottom = r.y() + r.height(); + + if(haveCaret) + SetCaretPos(r.x(), r.y()); + + if (aimm) { + aimm->SetCompositionWindow(imc, &cf); + aimm->SetCandidateWindow(imc, &candf); + } else { + ImmSetCompositionWindow(imc, &cf); + ImmSetCandidateWindow(imc, &candf); + } + + releaseContext(w->effectiveWinId(), imc); +} + + +bool QWinInputContext::endComposition() +{ + QWidget *fw = focusWidget(); +#ifdef Q_IME_DEBUG + qDebug("endComposition! fw = %s", fw ? fw->className() : "(null)"); +#endif + bool result = true; + if(imePosition == -1 || recursionGuard) + return result; + + // Googles Pinyin Input Method likes to call endComposition again + // when we call notifyIME with CPS_CANCEL, so protect ourselves + // against that. + recursionGuard = true; + + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + notifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + releaseContext(fw->effectiveWinId(), imc); + if(haveCaret) { + DestroyCaret(); + haveCaret = false; + } + } + + if (!fw) + fw = QApplication::focusWidget(); + + if (fw) { + QInputMethodEvent e; + result = qt_sendSpontaneousEvent(fw, &e); + } + + if (imeComposition) + imeComposition->clear(); + imePosition = -1; + + recursionGuard = false; + + return result; +} + +void QWinInputContext::reset() +{ + QWidget *fw = focusWidget(); + +#ifdef Q_IME_DEBUG + qDebug("sending accept to focus widget %s", fw ? fw->className() : "(null)"); +#endif + + if (fw && imePosition != -1) { + QInputMethodEvent e; + if (imeComposition) + e.setCommitString(*imeComposition); + imePosition = -1; + qt_sendSpontaneousEvent(fw, &e); + } + + if (imeComposition) + imeComposition->clear(); + imePosition = -1; + + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + notifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + releaseContext(fw->effectiveWinId(), imc); + } + +} + + +bool QWinInputContext::startComposition() +{ +#ifdef Q_IME_DEBUG + qDebug("startComposition"); +#endif + + if (!imeComposition) + imeComposition = new QString(); + + QWidget *fw = focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + imePosition = 0; + haveCaret = CreateCaret(fw->effectiveWinId(), 0, 1, 1); + HideCaret(fw->effectiveWinId()); + update(); + } + return fw != 0; +} + +enum StandardFormat { + PreeditFormat, + SelectionFormat +}; + +bool QWinInputContext::composition(LPARAM lParam) +{ +#ifdef Q_IME_DEBUG + QString str; + if (lParam & GCS_RESULTSTR) + str += "RESULTSTR "; + if (lParam & GCS_COMPSTR) + str += "COMPSTR "; + if (lParam & GCS_COMPATTR) + str += "COMPATTR "; + if (lParam & GCS_CURSORPOS) + str += "CURSORPOS "; + if (lParam & GCS_COMPCLAUSE) + str += "COMPCLAUSE "; + if (lParam & CS_INSERTCHAR) + str += "INSERTCHAR "; + if (lParam & CS_NOMOVECARET) + str += "NOMOVECARET "; + qDebug("composition, lParam=(%x) %s imePosition=%d", lParam, str.latin1(), imePosition); +#endif + + bool result = true; + + if(!lParam) + // bogus event + return true; + + QWidget *fw = QApplication::focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC imc = getContext(fw->effectiveWinId()); + QInputMethodEvent e; + if (lParam & (GCS_COMPSTR | GCS_COMPATTR | GCS_CURSORPOS)) { + if (imePosition == -1) + // need to send a start event + startComposition(); + + // some intermediate composition result + int selStart, selLength; + *imeComposition = getString(imc, GCS_COMPSTR, &selStart, &selLength); + imePosition = getCursorPosition(imc); + if (lParam & CS_INSERTCHAR && lParam & CS_NOMOVECARET) { + // make korean work correctly. Hope this is correct for all IMEs + selStart = 0; + selLength = imeComposition->length(); + } + if(selLength == 0) + selStart = 0; + + QList attrs; + if (selStart > 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selStart, + standardFormat(PreeditFormat)); + if (selLength) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart, selLength, + standardFormat(SelectionFormat)); + if (selStart + selLength < imeComposition->length()) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selStart + selLength, + imeComposition->length() - selStart - selLength, + standardFormat(PreeditFormat)); + if(imePosition >= 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, imePosition, selLength ? 0 : 1, QVariant()); + + e = QInputMethodEvent(*imeComposition, attrs); + } + if (lParam & GCS_RESULTSTR) { + if(imePosition == -1) + startComposition(); + // a fixed result, return the converted string + *imeComposition = getString(imc, GCS_RESULTSTR); + imePosition = -1; + e.setCommitString(*imeComposition); + imeComposition->clear(); + } + result = qt_sendSpontaneousEvent(fw, &e); + update(); + releaseContext(fw->effectiveWinId(), imc); + } +#ifdef Q_IME_DEBUG + qDebug("imecomposition: cursor pos at %d, str=%x", imePosition, str[0].unicode()); +#endif + return result; +} + +static HIMC defaultContext = 0; + +// checks whether widget is a popup +inline bool isPopup(QWidget *w) +{ + if (w && (w->windowFlags() & Qt::Popup) == Qt::Popup) + return true; + else + return false; +} +// checks whether widget is in a popup +inline bool isInPopup(QWidget *w) +{ + if (w && (isPopup(w) || isPopup(w->window()))) + return true; + else + return false; +} + +// find the parent widget, which is a non popup toplevel +// this is valid only if the widget is/in a popup +inline QWidget *findParentforPopup(QWidget *w) +{ + QWidget *e = QWidget::find(w->effectiveWinId()); + // check if this or its parent is a popup + while (isInPopup(e)) { + e = e->window()->parentWidget(); + if (!e) + break; + e = QWidget::find(e->effectiveWinId()); + } + if (e) + return e->window(); + else + return 0; +} + +// enables or disables the ime +inline void enableIme(QWidget *w, bool value) +{ + if (value) { + // enable ime + if (defaultContext) + ImmAssociateContext(w->effectiveWinId(), defaultContext); +#ifdef Q_WS_WINCE + if (qApp->autoSipEnabled()) + qt_wince_show_SIP(true); +#endif + } else { + // disable ime + HIMC oldimc = ImmAssociateContext(w->effectiveWinId(), 0); + if (!defaultContext) + defaultContext = oldimc; +#ifdef Q_WS_WINCE + if (qApp->autoSipEnabled()) + qt_wince_show_SIP(false); +#endif + } +} + + +void QWinInputContext::updateImeStatus(QWidget *w, bool hasFocus) +{ + if (!w) + return; + // It's always the proxy that carries the hints. + QWidget *focusProxyWidget = w->focusProxy(); + if (!focusProxyWidget) + focusProxyWidget = w; + bool e = w->testAttribute(Qt::WA_InputMethodEnabled) && w->isEnabled() + && !(focusProxyWidget->inputMethodHints() & (Qt::ImhExclusiveInputMask | Qt::ImhHiddenText)); + bool hasIme = e && hasFocus; +#ifdef Q_IME_DEBUG + qDebug("%s HasFocus = %d hasIme = %d e = %d ", w->className(), hasFocus, hasIme, e); +#endif + if (hasFocus || e) { + if (isInPopup(w)) + QWinInputContext::enablePopupChild(w, hasIme); + else + QWinInputContext::enable(w, hasIme); + } +} + +void QWinInputContext::enablePopupChild(QWidget *w, bool e) +{ + if (aimm) { + enable(w, e); + return; + } + + if (!w || !isInPopup(w)) + return; +#ifdef Q_IME_DEBUG + qDebug("enablePopupChild: w=%s, enable = %s", w ? w->className() : "(null)" , e ? "true" : "false"); +#endif + QWidget *parent = findParentforPopup(w); + if (parent) { + // update ime status of the normal toplevel parent of the popup + enableIme(parent, e); + } + QWidget *toplevel = w->window(); + if (toplevel) { + // update ime status of the toplevel popup + enableIme(toplevel, e); + } +} + +void QWinInputContext::enable(QWidget *w, bool e) +{ + if(w) { +#ifdef Q_IME_DEBUG + qDebug("enable: w=%s, enable = %s", w ? w->className() : "(null)" , e ? "true" : "false"); +#endif + if (!w->testAttribute(Qt::WA_WState_Created)) + return; + if(aimm) { + HIMC oldimc; + if (!e) { + aimm->AssociateContext(w->effectiveWinId(), 0, &oldimc); + if (!defaultContext) + defaultContext = oldimc; + } else if (defaultContext) { + aimm->AssociateContext(w->effectiveWinId(), defaultContext, &oldimc); + } + } else { + // update ime status on the widget + QWidget *p = QWidget::find(w->effectiveWinId()); + if (p) + enableIme(p, e); + } + } +} + +void QWinInputContext::setFocusWidget(QWidget *w) +{ + QWidget *oldFocus = focusWidget(); + if (oldFocus == w) + return; + if (w) { + QWinInputContext::updateImeStatus(w, true); + } else { + if (oldFocus) + QWinInputContext::updateImeStatus(oldFocus , false); + } + QInputContext::setFocusWidget(w); + update(); +} + +bool QWinInputContext::isComposing() const +{ + return imeComposition && !imeComposition->isEmpty(); +} + +void QWinInputContext::mouseHandler(int pos, QMouseEvent *e) +{ + if(e->type() != QEvent::MouseButtonPress) + return; + + if (pos < 0 || pos > imeComposition->length()) + reset(); + + // Probably should pass the correct button, but it seems to work fine like this. + DWORD button = MK_LBUTTON; + + QWidget *fw = focusWidget(); + if (fw) { + Q_ASSERT(fw->testAttribute(Qt::WA_WState_Created)); + HIMC himc = getContext(fw->effectiveWinId()); + HWND ime_wnd = getDefaultIMEWnd(fw->effectiveWinId()); + SendMessage(ime_wnd, WM_MSIME_MOUSE, MAKELONG(MAKEWORD(button, pos == 0 ? 2 : 1), pos), (LPARAM)himc); + releaseContext(fw->effectiveWinId(), himc); + } + //qDebug("mouseHandler: got value %d pos=%d", ret,pos); +} + +QString QWinInputContext::language() +{ + return QString(); +} + +int QWinInputContext::reconvertString(RECONVERTSTRING *reconv) +{ + QWidget *w = focusWidget(); + if(!w) + return -1; + + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + QString surroundingText = qvariant_cast(w->inputMethodQuery(Qt::ImSurroundingText)); + int memSize = sizeof(RECONVERTSTRING)+(surroundingText.length()+1)*sizeof(ushort); + // If memory is not allocated, return the required size. + if (!reconv) { + if (surroundingText.isEmpty()) + return -1; + else + return memSize; + } + int pos = qvariant_cast(w->inputMethodQuery(Qt::ImCursorPosition)); + // find the word in the surrounding text. + QTextBoundaryFinder bounds(QTextBoundaryFinder::Word, surroundingText); + bounds.setPosition(pos); + if (bounds.isAtBoundary()) { + if (QTextBoundaryFinder::EndWord == bounds.boundaryReasons()) + bounds.toPreviousBoundary(); + } else { + bounds.toPreviousBoundary(); + } + int startPos = bounds.position(); + bounds.toNextBoundary(); + int endPos = bounds.position(); + // select the text, this will be overwritten by following ime events. + QList attrs; + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, startPos, endPos-startPos, QVariant()); + QInputMethodEvent e(QString(), attrs); + qt_sendSpontaneousEvent(w, &e); + + reconv->dwSize = memSize; + reconv->dwVersion = 0; + + reconv->dwStrLen = surroundingText.length(); + reconv->dwStrOffset = sizeof(RECONVERTSTRING); + reconv->dwCompStrLen = endPos-startPos; + reconv->dwCompStrOffset = startPos*sizeof(ushort); + reconv->dwTargetStrLen = reconv->dwCompStrLen; + reconv->dwTargetStrOffset = reconv->dwCompStrOffset; + memcpy((char*)(reconv+1), surroundingText.utf16(), surroundingText.length()*sizeof(ushort)); + return memSize; +} + +QT_END_NAMESPACE diff --git a/src/gui/inputmethod/qwsinputcontext_p.h b/src/gui/inputmethod/qwsinputcontext_p.h new file mode 100644 index 0000000000..72b06a0b8f --- /dev/null +++ b/src/gui/inputmethod/qwsinputcontext_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSINPUTCONTEXT_P_H +#define QWSINPUTCONTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qinputcontext.h" + +#ifndef QT_NO_QWS_INPUTMETHODS + +QT_BEGIN_NAMESPACE + +class QWSIMEvent; +class QWSIMQueryEvent; +class QWSIMInitEvent; + +class QWSInputContext : public QInputContext +{ + Q_OBJECT +public: + explicit QWSInputContext(QObject* parent = 0); + ~QWSInputContext() {} + + + QString identifierName() { return QString(); } + QString language() { return QString(); } + + void reset(); + void update(); + void mouseHandler( int x, QMouseEvent *event); + + void setFocusWidget( QWidget *w ); + void widgetDestroyed(QWidget *w); + + bool isComposing() const; + + static QWidget *activeWidget(); + static bool translateIMEvent(QWidget *w, const QWSIMEvent *e); + static bool translateIMQueryEvent(QWidget *w, const QWSIMQueryEvent *e); + static bool translateIMInitEvent(const QWSIMInitEvent *e); + static void updateImeStatus(QWidget *w, bool hasFocus); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_INPUTMETHODS + +#endif // QWSINPUTCONTEXT_P_H diff --git a/src/gui/inputmethod/qwsinputcontext_qws.cpp b/src/gui/inputmethod/qwsinputcontext_qws.cpp new file mode 100644 index 0000000000..d8d64f28d4 --- /dev/null +++ b/src/gui/inputmethod/qwsinputcontext_qws.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwsinputcontext_p.h" +#include "qinputcontext_p.h" +#include "qwsdisplay_qws.h" +#include "qwsevent_qws.h" +#include "private/qwscommand_qws_p.h" +#include "qwindowsystem_qws.h" +#include "qevent.h" +#include "qtextformat.h" + +#include + +#include + +#ifndef QT_NO_QWS_INPUTMETHODS + +QT_BEGIN_NAMESPACE + +static QWidget* activeWidget = 0; + +//#define EXTRA_DEBUG + +QWSInputContext::QWSInputContext(QObject *parent) + :QInputContext(parent) +{ +} + +void QWSInputContext::reset() +{ + QPaintDevice::qwsDisplay()->resetIM(); +} + + +void QWSInputContext::setFocusWidget( QWidget *w ) +{ + QWidget *oldFocus = focusWidget(); + if (oldFocus == w) + return; + + if (w) { + QWSInputContext::updateImeStatus(w, true); + } else { + if (oldFocus) + QWSInputContext::updateImeStatus(oldFocus, false); + } + + if (oldFocus) { + QWidget *tlw = oldFocus->window(); + int winid = tlw->internalWinId(); + + int widgetid = oldFocus->internalWinId(); + QPaintDevice::qwsDisplay()->sendIMUpdate(QWSInputMethod::FocusOut, winid, widgetid); + } + + QInputContext::setFocusWidget(w); + + if (!w) + return; + + QWidget *tlw = w->window(); + int winid = tlw->winId(); + + int widgetid = w->winId(); + QPaintDevice::qwsDisplay()->sendIMUpdate(QWSInputMethod::FocusIn, winid, widgetid); + + //setfocus ??? + + update(); +} + + +void QWSInputContext::widgetDestroyed(QWidget *w) +{ + if (w == QT_PREPEND_NAMESPACE(activeWidget)) + QT_PREPEND_NAMESPACE(activeWidget) = 0; + QInputContext::widgetDestroyed(w); +} + +void QWSInputContext::update() +{ + QWidget *w = focusWidget(); + if (!w) + return; + + QWidget *tlw = w->window(); + int winid = tlw->winId(); + + int widgetid = w->winId(); + QPaintDevice::qwsDisplay()->sendIMUpdate(QWSInputMethod::Update, winid, widgetid); + +} + +void QWSInputContext::mouseHandler( int x, QMouseEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) + QPaintDevice::qwsDisplay()->sendIMMouseEvent( x, event->type() == QEvent::MouseButtonPress ); +} + +QWidget *QWSInputContext::activeWidget() +{ + return QT_PREPEND_NAMESPACE(activeWidget); +} + + +bool QWSInputContext::isComposing() const +{ + return QT_PREPEND_NAMESPACE(activeWidget) != 0; +} + +bool QWSInputContext::translateIMQueryEvent(QWidget *w, const QWSIMQueryEvent *e) +{ + Qt::InputMethodQuery type = static_cast(e->simpleData.property); + QVariant result = w->inputMethodQuery(type); + QWidget *tlw = w->window(); + int winId = tlw->winId(); + + if ( type == Qt::ImMicroFocus ) { + // translate to relative to tlw + QRect mf = result.toRect(); + mf.moveTopLeft(w->mapTo(tlw,mf.topLeft())); + result = mf; + } + + QPaintDevice::qwsDisplay()->sendIMResponse(winId, e->simpleData.property, result); + + return false; +} + +bool QWSInputContext::translateIMInitEvent(const QWSIMInitEvent *e) +{ + Q_UNUSED(e); + qDebug("### QWSInputContext::translateIMInitEvent not implemented ###"); + return false; +} + +bool QWSInputContext::translateIMEvent(QWidget *w, const QWSIMEvent *e) +{ + QDataStream stream(e->streamingData); + QString preedit; + QString commit; + + stream >> preedit; + stream >> commit; + + if (preedit.isEmpty() && QT_PREPEND_NAMESPACE(activeWidget)) + w = QT_PREPEND_NAMESPACE(activeWidget); + + QInputContext *qic = w->inputContext(); + if (!qic) + return false; + + QList attrs; + + + while (!stream.atEnd()) { + int type = -1; + int start = -1; + int length = -1; + QVariant data; + stream >> type >> start >> length >> data; + if (stream.status() != QDataStream::Ok) { + qWarning("corrupted QWSIMEvent"); + //qic->reset(); //??? + return false; + } + if (type == QInputMethodEvent::TextFormat) + data = qic->standardFormat(static_cast(data.toInt())); + attrs << QInputMethodEvent::Attribute(static_cast(type), start, length, data); + } +#ifdef EXTRA_DEBUG + qDebug() << "preedit" << preedit << "len" << preedit.length() <<"commit" << commit << "len" << commit.length() + << "n attr" << attrs.count(); +#endif + + if (preedit.isEmpty()) + QT_PREPEND_NAMESPACE(activeWidget) = 0; + else + QT_PREPEND_NAMESPACE(activeWidget) = w; + + + QInputMethodEvent ime(preedit, attrs); + if (!commit.isEmpty() || e->simpleData.replaceLength > 0) + ime.setCommitString(commit, e->simpleData.replaceFrom, e->simpleData.replaceLength); + + + extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); //qapplication_qws.cpp + qt_sendSpontaneousEvent(w, &ime); + + return true; +} + +Q_GUI_EXPORT void (*qt_qws_inputMethodStatusChanged)(QWidget*) = 0; + +void QWSInputContext::updateImeStatus(QWidget *w, bool hasFocus) +{ + Q_UNUSED(hasFocus); + + if (!w || !qt_qws_inputMethodStatusChanged) + return; + qt_qws_inputMethodStatusChanged(w); +} + + +QT_END_NAMESPACE + +#endif // QT_NO_QWS_INPUTMETHODS diff --git a/src/gui/inputmethod/qximinputcontext_p.h b/src/gui/inputmethod/qximinputcontext_p.h new file mode 100644 index 0000000000..13c389cce6 --- /dev/null +++ b/src/gui/inputmethod/qximinputcontext_p.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Definition of QXIMInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#ifndef QXIMINPUTCONTEXT_P_H +#define QXIMINPUTCONTEXT_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. +// + +#if !defined(Q_NO_IM) + +#include "QtCore/qglobal.h" +#include "QtGui/qinputcontext.h" +#include "QtGui/qfont.h" +#include "QtCore/qhash.h" +#ifdef Q_WS_X11 +#include "QtCore/qlist.h" +#include "QtCore/qbitarray.h" +#include "QtGui/qwindowdefs.h" +#include "private/qt_x11_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QWidget; +class QFont; +class QString; + +class QXIMInputContext : public QInputContext +{ + Q_OBJECT +public: + struct ICData { + XIC ic; + XFontSet fontset; + QWidget *widget; + QString text; + QBitArray selectedChars; + bool composing; + bool preeditEmpty; + void clear(); + }; + + QXIMInputContext(); + ~QXIMInputContext(); + + QString identifierName(); + QString language(); + + void reset(); + + void mouseHandler( int x, QMouseEvent *event); + bool isComposing() const; + + void setFocusWidget( QWidget *w ); + void widgetDestroyed(QWidget *w); + + void create_xim(); + void close_xim(); + + void update(); + + ICData *icData() const; +protected: + bool x11FilterEvent( QWidget *keywidget, XEvent *event ); + +private: + static XIMStyle xim_style; + + QString _language; + XIM xim; + QHash ximData; + + ICData *createICData(QWidget *w); +}; + +QT_END_NAMESPACE + +#endif // Q_NO_IM + +#endif // QXIMINPUTCONTEXT_P_H diff --git a/src/gui/inputmethod/qximinputcontext_x11.cpp b/src/gui/inputmethod/qximinputcontext_x11.cpp new file mode 100644 index 0000000000..155796828e --- /dev/null +++ b/src/gui/inputmethod/qximinputcontext_x11.cpp @@ -0,0 +1,885 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Implementation of QXIMInputContext class +** +** Copyright (C) 2003-2004 immodule for Qt Project. All rights reserved. +** +** This file is written to contribute to Nokia Corporation and/or its subsidiary(-ies) under their own +** license. You may use this file under your Qt license. Following +** description is copied from their original file headers. Contact +** immodule-qt@freedesktop.org if any conditions of this licensing are +** not clear to you. +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qdebug.h" +#include "qximinputcontext_p.h" + +#if !defined(QT_NO_IM) + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_XIM) + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qplatformdefs.h" + +#include "qapplication.h" +#include "qwidget.h" +#include "qstring.h" +#include "qlist.h" +#include "qtextcodec.h" +#include "qevent.h" +#include "qtextformat.h" + +#include "qx11info_x11.h" + +#include +#include +QT_END_INCLUDE_NAMESPACE + +// #define QT_XIM_DEBUG +#ifdef QT_XIM_DEBUG +#define XIM_DEBUG qDebug +#else +#define XIM_DEBUG if (0) qDebug +#endif + +// from qapplication_x11.cpp +// #### move to X11 struct +extern XIMStyle qt_xim_preferred_style; +extern char *qt_ximServer; +extern int qt_ximComposingKeycode; +extern QTextCodec * qt_input_mapper; + +XIMStyle QXIMInputContext::xim_style = 0; +// moved from qapplication_x11.cpp +static const XIMStyle xim_default_style = XIMPreeditCallbacks | XIMStatusNothing; + + +extern "C" { +#ifdef USE_X11R6_XIM + static void xim_create_callback(XIM /*im*/, + XPointer client_data, + XPointer /*call_data*/) + { + QXIMInputContext *qic = reinterpret_cast(client_data); + // qDebug("xim_create_callback"); + qic->create_xim(); + } + + static void xim_destroy_callback(XIM /*im*/, + XPointer client_data, + XPointer /*call_data*/) + { + QXIMInputContext *qic = reinterpret_cast(client_data); + // qDebug("xim_destroy_callback"); + qic->close_xim(); + XRegisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast(qic)); + } +#endif // USE_X11R6_XIM + + static int xic_start_callback(XIC, XPointer client_data, XPointer) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) { + XIM_DEBUG("xic_start_callback: no qic"); + return 0; + } + QXIMInputContext::ICData *data = qic->icData(); + if (!data) { + XIM_DEBUG("xic_start_callback: no ic data"); + return 0; + } + XIM_DEBUG("xic_start_callback"); + + data->clear(); + data->composing = true; + + return 0; + } + + static int xic_draw_callback(XIC, XPointer client_data, XPointer call_data) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) { + XIM_DEBUG("xic_draw_callback: no qic"); + return 0; + } + QXIMInputContext::ICData *data = qic->icData(); + if (!data) { + XIM_DEBUG("xic_draw_callback: no ic data"); + return 0; + } + XIM_DEBUG("xic_draw_callback"); + + + if(!data->composing) { + data->clear(); + data->composing = true; + } + + XIMPreeditDrawCallbackStruct *drawstruct = (XIMPreeditDrawCallbackStruct *) call_data; + XIMText *text = (XIMText *) drawstruct->text; + int cursor = drawstruct->caret, sellen = 0, selstart = 0; + + if (!drawstruct->caret && !drawstruct->chg_first && !drawstruct->chg_length && !text) { + if(data->text.isEmpty()) { + XIM_DEBUG("compose emptied"); + // if the composition string has been emptied, we need + // to send an InputMethodEnd event + QInputMethodEvent e; + qic->sendEvent(e); + data->clear(); + + // if the commit string has coming after here, InputMethodStart + // will be sent dynamically + } + return 0; + } + + if (text) { + char *str = 0; + if (text->encoding_is_wchar) { + int l = wcstombs(NULL, text->string.wide_char, text->length); + if (l != -1) { + str = new char[l + 1]; + wcstombs(str, text->string.wide_char, l); + str[l] = 0; + } + } else + str = text->string.multi_byte; + + if (!str) + return 0; + + QString s = QString::fromLocal8Bit(str); + + if (text->encoding_is_wchar) + delete [] str; + + if (drawstruct->chg_length < 0) + data->text.replace(drawstruct->chg_first, INT_MAX, s); + else + data->text.replace(drawstruct->chg_first, drawstruct->chg_length, s); + + if (data->selectedChars.size() < data->text.length()) { + // expand the selectedChars array if the compose string is longer + int from = data->selectedChars.size(); + data->selectedChars.resize(data->text.length()); + for (int x = from; x < data->selectedChars.size(); ++x) + data->selectedChars.clearBit(x); + } + + // determine if the changed chars are selected based on text->feedback + for (int x = 0; x < text->length; ++x) + data->selectedChars.setBit(x + drawstruct->chg_first, + (text->feedback ? (text->feedback[x] & XIMReverse) : 0)); + + // figure out where the selection starts, and how long it is + bool started = false; + for (int x = 0; x < qMin(data->selectedChars.size(), data->text.length()); ++x) { + if (started) { + if (data->selectedChars.testBit(x)) ++sellen; + else break; + } else { + if (data->selectedChars.testBit(x)) { + selstart = x; + started = true; + sellen = 1; + } + } + } + } else { + if (drawstruct->chg_length == 0) + drawstruct->chg_length = -1; + + data->text.remove(drawstruct->chg_first, drawstruct->chg_length); + bool qt_compose_emptied = data->text.isEmpty(); + if (qt_compose_emptied) { + XIM_DEBUG("compose emptied 2 text=%s", data->text.toUtf8().constData()); + // if the composition string has been emptied, we need + // to send an InputMethodEnd event + QInputMethodEvent e; + qic->sendEvent(e); + data->clear(); + // if the commit string has coming after here, InputMethodStart + // will be sent dynamically + return 0; + } + } + + XIM_DEBUG("sending compose: '%s', cursor=%d, sellen=%d", + data->text.toUtf8().constData(), cursor, sellen); + QList attrs; + if (selstart > 0) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, selstart, + qic->standardFormat(QInputContext::PreeditFormat)); + if (sellen) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, selstart, sellen, + qic->standardFormat(QInputContext::SelectionFormat)); + if (selstart + sellen < data->text.length()) + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + selstart + sellen, data->text.length() - selstart - sellen, + qic->standardFormat(QInputContext::PreeditFormat)); + attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, sellen ? 0 : 1, QVariant()); + QInputMethodEvent e(data->text, attrs); + data->preeditEmpty = data->text.isEmpty(); + qic->sendEvent(e); + + return 0; + } + + static int xic_done_callback(XIC, XPointer client_data, XPointer) { + QXIMInputContext *qic = (QXIMInputContext *) client_data; + if (!qic) + return 0; + + XIM_DEBUG("xic_done_callback"); + // Don't send InputMethodEnd here. QXIMInputContext::x11FilterEvent() + // handles InputMethodEnd with commit string. + return 0; + } +} + +void QXIMInputContext::ICData::clear() +{ + text = QString(); + selectedChars.clear(); + composing = false; + preeditEmpty = true; +} + +QXIMInputContext::ICData *QXIMInputContext::icData() const +{ + if (QWidget *w = focusWidget()) + return ximData.value(w->effectiveWinId()); + return 0; +} +/* The cache here is needed, as X11 leaks a few kb for every + XFreeFontSet call, so we avoid creating and deletion of fontsets as + much as possible +*/ +static XFontSet fontsetCache[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +static int fontsetRefCount = 0; + +static const char * const fontsetnames[] = { + "-*-fixed-medium-r-*-*-16-*,-*-*-medium-r-*-*-16-*", + "-*-fixed-medium-i-*-*-16-*,-*-*-medium-i-*-*-16-*", + "-*-fixed-bold-r-*-*-16-*,-*-*-bold-r-*-*-16-*", + "-*-fixed-bold-i-*-*-16-*,-*-*-bold-i-*-*-16-*", + "-*-fixed-medium-r-*-*-24-*,-*-*-medium-r-*-*-24-*", + "-*-fixed-medium-i-*-*-24-*,-*-*-medium-i-*-*-24-*", + "-*-fixed-bold-r-*-*-24-*,-*-*-bold-r-*-*-24-*", + "-*-fixed-bold-i-*-*-24-*,-*-*-bold-i-*-*-24-*" +}; + +static XFontSet getFontSet(const QFont &f) +{ + int i = 0; + if (f.italic()) + i |= 1; + if (f.bold()) + i |= 2; + + if (f.pointSize() > 20) + i += 4; + + if (!fontsetCache[i]) { + Display* dpy = X11->display; + int missCount; + char** missList; + fontsetCache[i] = XCreateFontSet(dpy, fontsetnames[i], &missList, &missCount, 0); + if(missCount > 0) + XFreeStringList(missList); + if (!fontsetCache[i]) { + fontsetCache[i] = XCreateFontSet(dpy, "-*-fixed-*-*-*-*-16-*", &missList, &missCount, 0); + if(missCount > 0) + XFreeStringList(missList); + if (!fontsetCache[i]) + fontsetCache[i] = (XFontSet)-1; + } + } + return (fontsetCache[i] == (XFontSet)-1) ? 0 : fontsetCache[i]; +} + +extern bool qt_use_rtl_extensions; // from qapplication_x11.cpp +#ifndef QT_NO_XKB +extern QLocale q_getKeyboardLocale(const QByteArray &layoutName, const QByteArray &variantName); +#endif + +QXIMInputContext::QXIMInputContext() +{ + if (!qt_xim_preferred_style) // no configured input style, use the default + qt_xim_preferred_style = xim_default_style; + + xim = 0; + QByteArray ximServerName(qt_ximServer); + if (qt_ximServer) + ximServerName.prepend("@im="); + else + ximServerName = ""; + + if (!XSupportsLocale()) +#ifndef QT_NO_DEBUG + qWarning("Qt: Locale not supported on X server") +#endif + ; +#ifdef USE_X11R6_XIM + else if (XSetLocaleModifiers (ximServerName.constData()) == 0) + qWarning("Qt: Cannot set locale modifiers: %s", ximServerName.constData()); + else + XRegisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast(this)); +#else // !USE_X11R6_XIM + else if (XSetLocaleModifiers ("") == 0) + qWarning("Qt: Cannot set locale modifiers"); + else + QXIMInputContext::create_xim(); +#endif // USE_X11R6_XIM + +#ifndef QT_NO_XKB + if (X11->use_xkb) { + QByteArray layoutName; + QByteArray variantName; + + Atom type = XNone; + int format = 0; + ulong nitems = 0; + ulong bytesAfter = 0; + uchar *data = 0; + if (XGetWindowProperty(X11->display, RootWindow(X11->display, 0), ATOM(_XKB_RULES_NAMES), 0, 1024, + false, XA_STRING, &type, &format, &nitems, &bytesAfter, &data) == Success + && type == XA_STRING && format == 8 && nitems > 2) { + + char *names[5] = { 0, 0, 0, 0, 0 }; + char *p = reinterpret_cast(data), *end = p + nitems; + int i = 0; + do { + names[i++] = p; + p += qstrlen(p) + 1; + } while (p < end); + + QList layoutNames = QByteArray::fromRawData(names[2], qstrlen(names[2])).split(','); + QList variantNames = QByteArray::fromRawData(names[3], qstrlen(names[3])).split(','); + for (int i = 0; i < qMin(layoutNames.count(), variantNames.count()); ++i ) { + QByteArray variantName = variantNames.at(i); + const int dashPos = variantName.indexOf("-"); + if (dashPos >= 0) + variantName.truncate(dashPos); + QLocale keyboardInputLocale = q_getKeyboardLocale(layoutNames.at(i), variantName); + if (keyboardInputLocale.textDirection() == Qt::RightToLeft) + qt_use_rtl_extensions = true; + } + } + + if (data) + XFree(data); + } +#endif // QT_NO_XKB + +} + + +/*!\internal + Creates the application input method. +*/ +void QXIMInputContext::create_xim() +{ + ++fontsetRefCount; +#ifndef QT_NO_XIM + xim = XOpenIM(X11->display, 0, 0, 0); + if (xim) { + +#ifdef USE_X11R6_XIM + XIMCallback destroy; + destroy.callback = (XIMProc) xim_destroy_callback; + destroy.client_data = XPointer(this); + if (XSetIMValues(xim, XNDestroyCallback, &destroy, (char *) 0) != 0) + qWarning("Xlib doesn't support destroy callback"); +#endif // USE_X11R6_XIM + + XIMStyles *styles = 0; + XGetIMValues(xim, XNQueryInputStyle, &styles, (char *) 0, (char *) 0); + if (styles) { + int i; + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == qt_xim_preferred_style) { + xim_style = qt_xim_preferred_style; + break; + } + } + // if the preferred input style couldn't be found, look for + // Nothing + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) { + xim_style = XIMPreeditNothing | XIMStatusNothing; + break; + } + } + // ... and failing that, None. + for (i = 0; !xim_style && i < styles->count_styles; i++) { + if (styles->supported_styles[i] == (XIMPreeditNone | + XIMStatusNone)) { + xim_style = XIMPreeditNone | XIMStatusNone; + break; + } + } + + // qDebug("QApplication: using im style %lx", xim_style); + XFree((char *)styles); + } + + if (xim_style) { + +#ifdef USE_X11R6_XIM + XUnregisterIMInstantiateCallback(X11->display, 0, 0, 0, + (XIMProc) xim_create_callback, reinterpret_cast(this)); +#endif // USE_X11R6_XIM + + if (QWidget *focusWidget = QApplication::focusWidget()) { + // reinitialize input context after the input method + // server (like SCIM) has been launched without + // requiring the user to manually switch focus. + if (focusWidget->testAttribute(Qt::WA_InputMethodEnabled) + && focusWidget->testAttribute(Qt::WA_WState_Created) + && focusWidget->isEnabled()) + setFocusWidget(focusWidget); + } + // following code fragment is not required for immodule + // version of XIM +#if 0 + QWidgetList list = qApp->topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + w->d->createTLSysExtra(); + } +#endif + } else { + // Give up + qWarning("No supported input style found." + " See InputMethod documentation."); + close_xim(); + } + } +#endif // QT_NO_XIM +} + +/*!\internal + Closes the application input method. +*/ +void QXIMInputContext::close_xim() +{ + for(QHash::const_iterator i = ximData.constBegin(), + e = ximData.constEnd(); i != e; ++i) { + ICData *data = i.value(); + if (data->ic) + XDestroyIC(data->ic); + delete data; + } + ximData.clear(); + + if ( --fontsetRefCount == 0 ) { + Display *dpy = X11->display; + for ( int i = 0; i < 8; i++ ) { + if ( fontsetCache[i] && fontsetCache[i] != (XFontSet)-1 ) { + XFreeFontSet(dpy, fontsetCache[i]); + fontsetCache[i] = 0; + } + } + } + + setFocusWidget(0); + xim = 0; +} + + + +QXIMInputContext::~QXIMInputContext() +{ + XIM old_xim = xim; // close_xim clears xim pointer. + close_xim(); + if (old_xim) + XCloseIM(old_xim); +} + + +QString QXIMInputContext::identifierName() +{ + // the name should be "xim" rather than "XIM" to be consistent + // with corresponding immodule of GTK+ + return QLatin1String("xim"); +} + + +QString QXIMInputContext::language() +{ + QString language; + if (xim) { + QByteArray locale(XLocaleOfIM(xim)); + + if (locale.startsWith("zh")) { + // Chinese language should be formed as "zh_CN", "zh_TW", "zh_HK" + language = QLatin1String(locale.left(5)); + } else { + // other languages should be two-letter ISO 639 language code + language = QLatin1String(locale.left(2)); + } + } + return language; +} + +void QXIMInputContext::reset() +{ + QWidget *w = focusWidget(); + if (!w) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return; + + if (data->ic) { + char *mb = XmbResetIC(data->ic); + QInputMethodEvent e; + if (mb) { + e.setCommitString(QString::fromLocal8Bit(mb)); + XFree(mb); + data->preeditEmpty = false; // force sending an event + } + if (!data->preeditEmpty) { + sendEvent(e); + update(); + } + } + data->clear(); +} + +void QXIMInputContext::widgetDestroyed(QWidget *w) +{ + QInputContext::widgetDestroyed(w); + ICData *data = ximData.take(w->effectiveWinId()); + if (!data) + return; + + data->clear(); + if (data->ic) + XDestroyIC(data->ic); + delete data; +} + +void QXIMInputContext::mouseHandler(int pos, QMouseEvent *e) +{ + if(e->type() != QEvent::MouseButtonPress) + return; + + XIM_DEBUG("QXIMInputContext::mouseHandler pos=%d", pos); + if (QWidget *w = focusWidget()) { + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return; + if (pos < 0 || pos > data->text.length()) + reset(); + // ##### handle mouse position + } +} + +bool QXIMInputContext::isComposing() const +{ + QWidget *w = focusWidget(); + if (!w) + return false; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return false; + return data->composing; +} + +void QXIMInputContext::setFocusWidget(QWidget *w) +{ + if (!xim) + return; + QWidget *oldFocus = focusWidget(); + if (oldFocus == w) + return; + + if (language() != QLatin1String("ja")) + reset(); + + if (oldFocus) { + ICData *data = ximData.value(oldFocus->effectiveWinId()); + if (data && data->ic) + XUnsetICFocus(data->ic); + } + + QInputContext::setFocusWidget(w); + + if (!w || w->inputMethodHints() & (Qt::ImhExclusiveInputMask | Qt::ImhHiddenText)) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + data = createICData(w); + + if (data->ic) + XSetICFocus(data->ic); + + update(); +} + + +bool QXIMInputContext::x11FilterEvent(QWidget *keywidget, XEvent *event) +{ + int xkey_keycode = event->xkey.keycode; + if (!keywidget->testAttribute(Qt::WA_WState_Created)) + return false; + if (XFilterEvent(event, keywidget->effectiveWinId())) { + qt_ximComposingKeycode = xkey_keycode; // ### not documented in xlib + + update(); + + return true; + } + if (event->type != XKeyPress || event->xkey.keycode != 0) + return false; + + QWidget *w = focusWidget(); + if (keywidget != w) + return false; + ICData *data = ximData.value(w->effectiveWinId()); + if (!data) + return false; + + // input method has sent us a commit string + QByteArray string; + string.resize(513); + KeySym key; // unused + Status status; // unused + QString text; + int count = XmbLookupString(data->ic, &event->xkey, string.data(), string.size(), + &key, &status); + + if (status == XBufferOverflow) { + string.resize(count + 1); + count = XmbLookupString(data->ic, &event->xkey, string.data(), string.size(), + &key, &status); + } + if (count > 0) { + // XmbLookupString() gave us some text, convert it to unicode + text = qt_input_mapper->toUnicode(string.constData() , count); + if (text.isEmpty()) { + // codec couldn't convert to unicode? this can happen when running in the + // C locale (or with no LANG set). try converting from latin-1 + text = QString::fromLatin1(string.constData(), count); + } + } + +#if 0 + if (!(xim_style & XIMPreeditCallbacks) || !isComposing()) { + // ############### send a regular key event here! + ; + } +#endif + + QInputMethodEvent e; + e.setCommitString(text); + sendEvent(e); + data->clear(); + + update(); + + return true; +} + + +QXIMInputContext::ICData *QXIMInputContext::createICData(QWidget *w) +{ + ICData *data = new ICData; + data->widget = w; + data->preeditEmpty = true; + + XVaNestedList preedit_attr = 0; + XIMCallback startcallback, drawcallback, donecallback; + + QFont font = w->font(); + data->fontset = getFontSet(font); + + if (xim_style & XIMPreeditArea) { + XRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = w->width(); + rect.height = w->height(); + + preedit_attr = XVaCreateNestedList(0, + XNArea, &rect, + XNFontSet, data->fontset, + (char *) 0); + } else if (xim_style & XIMPreeditPosition) { + XPoint spot; + spot.x = 1; + spot.y = 1; + + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNFontSet, data->fontset, + (char *) 0); + } else if (xim_style & XIMPreeditCallbacks) { + startcallback.client_data = (XPointer) this; + startcallback.callback = (XIMProc) xic_start_callback; + drawcallback.client_data = (XPointer) this; + drawcallback.callback = (XIMProc)xic_draw_callback; + donecallback.client_data = (XPointer) this; + donecallback.callback = (XIMProc) xic_done_callback; + + preedit_attr = XVaCreateNestedList(0, + XNPreeditStartCallback, &startcallback, + XNPreeditDrawCallback, &drawcallback, + XNPreeditDoneCallback, &donecallback, + (char *) 0); + } + + if (preedit_attr) { + data->ic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, w->effectiveWinId(), + XNPreeditAttributes, preedit_attr, + (char *) 0); + XFree(preedit_attr); + } else { + data->ic = XCreateIC(xim, + XNInputStyle, xim_style, + XNClientWindow, w->effectiveWinId(), + (char *) 0); + } + + if (data->ic) { + // when resetting the input context, preserve the input state + (void) XSetICValues(data->ic, XNResetState, XIMPreserveState, (char *) 0); + } else { + qWarning("Failed to create XIC"); + } + + ximData[w->effectiveWinId()] = data; + return data; +} + +void QXIMInputContext::update() +{ + QWidget *w = focusWidget(); + if (!w) + return; + + ICData *data = ximData.value(w->effectiveWinId()); + if (!data || !data->ic) + return; + + QRect r = w->inputMethodQuery(Qt::ImMicroFocus).toRect(); + QPoint p; + if (w->nativeParentWidget()) + p = w->mapTo(w->nativeParentWidget(), QPoint((r.left() + r.right() + 1)/2, r.bottom())); + else + p = QPoint((r.left() + r.right() + 1)/2, r.bottom()); + XPoint spot; + spot.x = p.x(); + spot.y = p.y(); + + r = w->rect(); + XRectangle area; + area.x = r.x(); + area.y = r.y(); + area.width = r.width(); + area.height = r.height(); + + XFontSet fontset = getFontSet(qvariant_cast(w->inputMethodQuery(Qt::ImFont))); + if (data->fontset == fontset) + fontset = 0; + else + data->fontset = fontset; + + XVaNestedList preedit_attr; + if (fontset) + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNArea, &area, + XNFontSet, fontset, + (char *) 0); + else + preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + XNArea, &area, + (char *) 0); + + XSetICValues(data->ic, XNPreeditAttributes, preedit_attr, (char *) 0); + XFree(preedit_attr); +} + + +#else +/* + When QT_NO_XIM is defined, we provide a dummy implementation for + this class. The reason for this is that the header file is moc'ed + regardless of QT_NO_XIM. The best would be to remove the file + completely from the pri file is QT_NO_XIM was defined, or for moc + to understand this preprocessor directive. Since the header does + not declare this class when QT_NO_XIM is defined, this is dead + code. +*/ +bool QXIMInputContext::isComposing() const { return false; } +QString QXIMInputContext::identifierName() { return QString(); } +void QXIMInputContext::mouseHandler(int, QMouseEvent *) {} +void QXIMInputContext::setFocusWidget(QWidget *) {} +void QXIMInputContext::reset() {} +void QXIMInputContext::update() {} +QXIMInputContext::~QXIMInputContext() {} +void QXIMInputContext::widgetDestroyed(QWidget *) {} +QString QXIMInputContext::language() { return QString(); } +bool QXIMInputContext::x11FilterEvent(QWidget *, XEvent *) { return true; } + +#endif //QT_NO_XIM + +QT_END_NAMESPACE + +#endif //QT_NO_IM diff --git a/src/gui/itemviews/itemviews.pri b/src/gui/itemviews/itemviews.pri new file mode 100644 index 0000000000..bbc1e983ba --- /dev/null +++ b/src/gui/itemviews/itemviews.pri @@ -0,0 +1,70 @@ +# Qt gui library, itemviews + +HEADERS += \ + itemviews/qabstractitemview.h \ + itemviews/qabstractitemview_p.h \ + itemviews/qheaderview.h \ + itemviews/qlistview.h \ + itemviews/qlistview_p.h \ + itemviews/qbsptree_p.h \ + itemviews/qtableview.h \ + itemviews/qtableview_p.h \ + itemviews/qtreeview.h \ + itemviews/qtreeview_p.h \ + itemviews/qabstractitemdelegate.h \ + itemviews/qitemdelegate.h \ + itemviews/qitemselectionmodel.h \ + itemviews/qitemselectionmodel_p.h \ + itemviews/qdirmodel.h \ + itemviews/qlistwidget.h \ + itemviews/qlistwidget_p.h \ + itemviews/qtablewidget.h \ + itemviews/qtablewidget_p.h \ + itemviews/qtreewidget.h \ + itemviews/qtreewidget_p.h \ + itemviews/qwidgetitemdata_p.h \ + itemviews/qproxymodel.h \ + itemviews/qproxymodel_p.h \ + itemviews/qabstractproxymodel.h \ + itemviews/qabstractproxymodel_p.h \ + itemviews/qsortfilterproxymodel.h \ + itemviews/qitemeditorfactory.h \ + itemviews/qitemeditorfactory_p.h \ + itemviews/qstandarditemmodel.h \ + itemviews/qstandarditemmodel_p.h \ + itemviews/qstringlistmodel.h \ + itemviews/qtreewidgetitemiterator.h \ + itemviews/qdatawidgetmapper.h \ + itemviews/qfileiconprovider.h \ + itemviews/qcolumnviewgrip_p.h \ + itemviews/qcolumnview.h \ + itemviews/qcolumnview_p.h \ + itemviews/qstyleditemdelegate.h + +SOURCES += \ + itemviews/qabstractitemview.cpp \ + itemviews/qheaderview.cpp \ + itemviews/qlistview.cpp \ + itemviews/qbsptree.cpp \ + itemviews/qtableview.cpp \ + itemviews/qtreeview.cpp \ + itemviews/qabstractitemdelegate.cpp \ + itemviews/qitemdelegate.cpp \ + itemviews/qitemselectionmodel.cpp \ + itemviews/qdirmodel.cpp \ + itemviews/qlistwidget.cpp \ + itemviews/qtablewidget.cpp \ + itemviews/qtreewidget.cpp \ + itemviews/qproxymodel.cpp \ + itemviews/qabstractproxymodel.cpp \ + itemviews/qsortfilterproxymodel.cpp \ + itemviews/qitemeditorfactory.cpp \ + itemviews/qstandarditemmodel.cpp \ + itemviews/qstringlistmodel.cpp \ + itemviews/qtreewidgetitemiterator.cpp \ + itemviews/qdatawidgetmapper.cpp \ + itemviews/qfileiconprovider.cpp \ + itemviews/qcolumnview.cpp \ + itemviews/qcolumnviewgrip.cpp \ + itemviews/qstyleditemdelegate.cpp + diff --git a/src/gui/itemviews/qabstractitemdelegate.cpp b/src/gui/itemviews/qabstractitemdelegate.cpp new file mode 100644 index 0000000000..478c4747ef --- /dev/null +++ b/src/gui/itemviews/qabstractitemdelegate.cpp @@ -0,0 +1,393 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractitemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractItemDelegate + + \brief The QAbstractItemDelegate class is used to display and edit + data items from a model. + + \ingroup model-view + + + A QAbstractItemDelegate provides the interface and common functionality + for delegates in the model/view architecture. Delegates display + individual items in views, and handle the editing of model data. + + The QAbstractItemDelegate class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + To render an item in a custom way, you must implement paint() and + sizeHint(). The QItemDelegate class provides default implementations for + these functions; if you do not need custom rendering, subclass that + class instead. + + We give an example of drawing a progress bar in items; in our case + for a package management program. + + \image widgetdelegate.png + + We create the \c WidgetDelegate class, which inherits from + QStyledItemDelegate. We do the drawing in the paint() function: + + \snippet doc/src/snippets/widgetdelegate.cpp 0 + + Notice that we use a QStyleOptionProgressBar and initialize its + members. We can then use the current QStyle to draw it. + + To provide custom editing, there are two approaches that can be + used. The first approach is to create an editor widget and display + it directly on top of the item. To do this you must reimplement + createEditor() to provide an editor widget, setEditorData() to populate + the editor with the data from the model, and setModelData() so that the + delegate can update the model with data from the editor. + + The second approach is to handle user events directly by reimplementing + editorEvent(). + + \sa {model-view-programming}{Model/View Programming}, QItemDelegate, + {Pixelator Example}, QStyledItemDelegate, QStyle +*/ + +/*! + \enum QAbstractItemDelegate::EndEditHint + + This enum describes the different hints that the delegate can give to the + model and view components to make editing data in a model a comfortable + experience for the user. + + \value NoHint There is no recommended action to be performed. + + These hints let the delegate influence the behavior of the view: + + \value EditNextItem The view should use the delegate to open an + editor on the next item in the view. + \value EditPreviousItem The view should use the delegate to open an + editor on the previous item in the view. + + Note that custom views may interpret the concepts of next and previous + differently. + + The following hints are most useful when models are used that cache + data, such as those that manipulate data locally in order to increase + performance or conserve network bandwidth. + + \value SubmitModelCache If the model caches data, it should write out + cached data to the underlying data store. + \value RevertModelCache If the model caches data, it should discard + cached data and replace it with data from the + underlying data store. + + Although models and views should respond to these hints in appropriate + ways, custom components may ignore any or all of them if they are not + relevant. +*/ + +/*! + \fn void QAbstractItemDelegate::commitData(QWidget *editor) + + This signal must be emitted when the \a editor widget has completed + editing the data, and wants to write it back into the model. +*/ + +/*! + \fn void QAbstractItemDelegate::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) + + This signal is emitted when the user has finished editing an item using + the specified \a editor. + + The \a hint provides a way for the delegate to influence how the model and + view behave after editing is completed. It indicates to these components + what action should be performed next to provide a comfortable editing + experience for the user. For example, if \c EditNextItem is specified, + the view should use a delegate to open an editor on the next item in the + model. + + \sa EndEditHint +*/ + +/*! + \fn void QAbstractItemDelegate::sizeHintChanged(const QModelIndex &index) + \since 4.4 + + This signal must be emitted when the sizeHint() of \a index changed. + + Views automatically connect to this signal and relayout items as necessary. +*/ + + +/*! + Creates a new abstract item delegate with the given \a parent. +*/ +QAbstractItemDelegate::QAbstractItemDelegate(QObject *parent) + : QObject(parent) +{ + +} + +/*! + \internal + + Creates a new abstract item delegate with the given \a parent. +*/ +QAbstractItemDelegate::QAbstractItemDelegate(QObjectPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + +} + +/*! + Destroys the abstract item delegate. +*/ +QAbstractItemDelegate::~QAbstractItemDelegate() +{ + +} + +/*! + \fn void QAbstractItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const = 0; + + This pure abstract function must be reimplemented if you want to + provide custom rendering. Use the \a painter and style \a option to + render the item specified by the item \a index. + + If you reimplement this you must also reimplement sizeHint(). +*/ + +/*! + \fn QSize QAbstractItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const = 0 + + This pure abstract function must be reimplemented if you want to + provide custom rendering. The options are specified by \a option + and the model item by \a index. + + If you reimplement this you must also reimplement paint(). +*/ + +/*! + Returns the editor to be used for editing the data item with the + given \a index. Note that the index contains information about the + model being used. The editor's parent widget is specified by \a parent, + and the item options by \a option. + + The base implementation returns 0. If you want custom editing you + will need to reimplement this function. + + The returned editor widget should have Qt::StrongFocus; + otherwise, \l{QMouseEvent}s received by the widget will propagate + to the view. The view's background will shine through unless the + editor paints its own background (e.g., with + \l{QWidget::}{setAutoFillBackground()}). + + \sa setModelData() setEditorData() +*/ +QWidget *QAbstractItemDelegate::createEditor(QWidget *, + const QStyleOptionViewItem &, + const QModelIndex &) const +{ + return 0; +} + +/*! + Sets the contents of the given \a editor to the data for the item + at the given \a index. Note that the index contains information + about the model being used. + + The base implementation does nothing. If you want custom editing + you will need to reimplement this function. + + \sa setModelData() +*/ +void QAbstractItemDelegate::setEditorData(QWidget *, + const QModelIndex &) const +{ + // do nothing +} + +/*! + Sets the data for the item at the given \a index in the \a model + to the contents of the given \a editor. + + The base implementation does nothing. If you want custom editing + you will need to reimplement this function. + + \sa setEditorData() +*/ +void QAbstractItemDelegate::setModelData(QWidget *, + QAbstractItemModel *, + const QModelIndex &) const +{ + // do nothing +} + +/*! + Updates the geometry of the \a editor for the item with the given + \a index, according to the rectangle specified in the \a option. + If the item has an internal layout, the editor will be laid out + accordingly. Note that the index contains information about the + model being used. + + The base implementation does nothing. If you want custom editing + you must reimplement this function. +*/ +void QAbstractItemDelegate::updateEditorGeometry(QWidget *, + const QStyleOptionViewItem &, + const QModelIndex &) const +{ + // do nothing +} + +/*! + When editing of an item starts, this function is called with the + \a event that triggered the editing, the \a model, the \a index of + the item, and the \a option used for rendering the item. + + Mouse events are sent to editorEvent() even if they don't start + editing of the item. This can, for instance, be useful if you wish + to open a context menu when the right mouse button is pressed on + an item. + + The base implementation returns false (indicating that it has not + handled the event). +*/ +bool QAbstractItemDelegate::editorEvent(QEvent *, + QAbstractItemModel *, + const QStyleOptionViewItem &, + const QModelIndex &) +{ + // do nothing + return false; +} + +/*! + \obsolete + + Use QFontMetrics::elidedText() instead. + + \oldcode + QFontMetrics fm = ... + QString str = QAbstractItemDelegate::elidedText(fm, width, mode, text); + \newcode + QFontMetrics fm = ... + QString str = fm.elidedText(text, mode, width); + \endcode +*/ + +QString QAbstractItemDelegate::elidedText(const QFontMetrics &fontMetrics, int width, + Qt::TextElideMode mode, const QString &text) +{ + return fontMetrics.elidedText(text, mode, width); +} + +/*! + \since 4.3 + Whenever a help event occurs, this function is called with the \a event + \a view \a option and the \a index that corresponds to the item where the + event occurs. + + Returns true if the delegate can handle the event; otherwise returns false. + A return value of true indicates that the data obtained using the index had + the required role. + + For QEvent::ToolTip and QEvent::WhatsThis events that were handled successfully, + the relevant popup may be shown depending on the user's system configuration. + + \sa QHelpEvent +*/ +// ### Qt 5: Make this a virtual non-slot function +bool QAbstractItemDelegate::helpEvent(QHelpEvent *event, + QAbstractItemView *view, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_UNUSED(option); + + if (!event || !view) + return false; + switch (event->type()) { +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *he = static_cast(event); + QVariant tooltip = index.data(Qt::ToolTipRole); + if (tooltip.canConvert()) { + QToolTip::showText(he->globalPos(), tooltip.toString(), view); + return true; + } + break;} +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: { + if (index.data(Qt::WhatsThisRole).isValid()) + return true; + break; } + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast(event); + QVariant whatsthis = index.data(Qt::WhatsThisRole); + if (whatsthis.canConvert()) { + QWhatsThis::showText(he->globalPos(), whatsthis.toString(), view); + return true; + } + break ; } +#endif + default: + break; + } + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qabstractitemdelegate.h b/src/gui/itemviews/qabstractitemdelegate.h new file mode 100644 index 0000000000..6789317137 --- /dev/null +++ b/src/gui/itemviews/qabstractitemdelegate.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMDELEGATE_H +#define QABSTRACTITEMDELEGATE_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QPainter; +class QModelIndex; +class QAbstractItemModel; +class QAbstractItemView; +class QHelpEvent; + +class Q_GUI_EXPORT QAbstractItemDelegate : public QObject +{ + Q_OBJECT + +public: + + enum EndEditHint { + NoHint, + EditNextItem, + EditPreviousItem, + SubmitModelCache, + RevertModelCache + }; + + explicit QAbstractItemDelegate(QObject *parent = 0); + virtual ~QAbstractItemDelegate(); + + // painting + virtual void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const = 0; + + virtual QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const = 0; + + // editing + virtual QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; + + virtual void setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const; + + virtual void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // for non-widget editors + virtual bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index); + + static QString elidedText(const QFontMetrics &fontMetrics, int width, + Qt::TextElideMode mode, const QString &text); + +public Q_SLOTS: + bool helpEvent(QHelpEvent *event, + QAbstractItemView *view, + const QStyleOptionViewItem &option, + const QModelIndex &index); + +Q_SIGNALS: + void commitData(QWidget *editor); + void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint = NoHint); + void sizeHintChanged(const QModelIndex &); + +protected: + QAbstractItemDelegate(QObjectPrivate &, QObject *parent = 0); +private: + Q_DISABLE_COPY(QAbstractItemDelegate) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTITEMDELEGATE_H diff --git a/src/gui/itemviews/qabstractitemview.cpp b/src/gui/itemviews/qabstractitemview.cpp new file mode 100644 index 0000000000..d6714968b5 --- /dev/null +++ b/src/gui/itemviews/qabstractitemview.cpp @@ -0,0 +1,4241 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractitemview.h" + +#ifndef QT_NO_ITEMVIEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif +#include +#ifndef QT_NO_GESTURE +# include +#endif + +QT_BEGIN_NAMESPACE + +QAbstractItemViewPrivate::QAbstractItemViewPrivate() + : model(QAbstractItemModelPrivate::staticEmptyModel()), + itemDelegate(0), + selectionModel(0), + ctrlDragSelectionFlag(QItemSelectionModel::NoUpdate), + noSelectionOnMousePress(false), + selectionMode(QAbstractItemView::ExtendedSelection), + selectionBehavior(QAbstractItemView::SelectItems), + currentlyCommittingEditor(0), + pressedModifiers(Qt::NoModifier), + pressedPosition(QPoint(-1, -1)), + pressedAlreadySelected(false), + viewportEnteredNeeded(false), + state(QAbstractItemView::NoState), + stateBeforeAnimation(QAbstractItemView::NoState), + editTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed), + lastTrigger(QAbstractItemView::NoEditTriggers), + tabKeyNavigation(false), +#ifndef QT_NO_DRAGANDDROP + showDropIndicator(true), + dragEnabled(false), + dragDropMode(QAbstractItemView::NoDragDrop), + overwrite(false), + dropIndicatorPosition(QAbstractItemView::OnItem), + defaultDropAction(Qt::IgnoreAction), +#endif +#ifdef QT_SOFTKEYS_ENABLED + doneSoftKey(0), +#endif + autoScroll(true), + autoScrollMargin(16), + autoScrollCount(0), + shouldScrollToCurrentOnShow(false), + shouldClearStatusTip(false), + alternatingColors(false), + textElideMode(Qt::ElideRight), + verticalScrollMode(QAbstractItemView::ScrollPerItem), + horizontalScrollMode(QAbstractItemView::ScrollPerItem), + currentIndexSet(false), + wrapItemText(false), + delayedPendingLayout(true), + moveCursorUpdatedView(false) +{ + keyboardInputTime.invalidate(); +} + +QAbstractItemViewPrivate::~QAbstractItemViewPrivate() +{ +} + +void QAbstractItemViewPrivate::init() +{ + Q_Q(QAbstractItemView); + q->setItemDelegate(new QStyledItemDelegate(q)); + + vbar->setRange(0, 0); + hbar->setRange(0, 0); + + QObject::connect(vbar, SIGNAL(actionTriggered(int)), + q, SLOT(verticalScrollbarAction(int))); + QObject::connect(hbar, SIGNAL(actionTriggered(int)), + q, SLOT(horizontalScrollbarAction(int))); + QObject::connect(vbar, SIGNAL(valueChanged(int)), + q, SLOT(verticalScrollbarValueChanged(int))); + QObject::connect(hbar, SIGNAL(valueChanged(int)), + q, SLOT(horizontalScrollbarValueChanged(int))); + + viewport->setBackgroundRole(QPalette::Base); + + q->setAttribute(Qt::WA_InputMethodEnabled); + +#ifdef QT_SOFTKEYS_ENABLED + doneSoftKey = QSoftKeyManager::createKeyedAction(QSoftKeyManager::DoneSoftKey, Qt::Key_Back, q); +#endif +} + +void QAbstractItemViewPrivate::setHoverIndex(const QPersistentModelIndex &index) +{ + Q_Q(QAbstractItemView); + if (hover == index) + return; + + if (selectionBehavior != QAbstractItemView::SelectRows) { + q->update(hover); //update the old one + q->update(index); //update the new one + } else { + QRect oldHoverRect = q->visualRect(hover); + QRect newHoverRect = q->visualRect(index); + viewport->update(QRect(0, newHoverRect.y(), viewport->width(), newHoverRect.height())); + viewport->update(QRect(0, oldHoverRect.y(), viewport->width(), oldHoverRect.height())); + } + hover = index; +} + +void QAbstractItemViewPrivate::checkMouseMove(const QPersistentModelIndex &index) +{ + //we take a persistent model index because the model might change by emitting signals + Q_Q(QAbstractItemView); + setHoverIndex(index); + if (viewportEnteredNeeded || enteredIndex != index) { + viewportEnteredNeeded = false; + + if (index.isValid()) { + emit q->entered(index); +#ifndef QT_NO_STATUSTIP + QString statustip = model->data(index, Qt::StatusTipRole).toString(); + if (parent && (shouldClearStatusTip || !statustip.isEmpty())) { + QStatusTipEvent tip(statustip); + QApplication::sendEvent(parent, &tip); + shouldClearStatusTip = !statustip.isEmpty(); + } +#endif + } else { +#ifndef QT_NO_STATUSTIP + if (parent && shouldClearStatusTip) { + QString emptyString; + QStatusTipEvent tip( emptyString ); + QApplication::sendEvent(parent, &tip); + } +#endif + emit q->viewportEntered(); + } + enteredIndex = index; + } +} + +#ifndef QT_NO_GESTURES + +// stores and restores the selection and current item when flicking +void QAbstractItemViewPrivate::_q_scrollerStateChanged() +{ + Q_Q(QAbstractItemView); + + if (QScroller *scroller = QScroller::scroller(viewport)) { + switch (scroller->state()) { + case QScroller::Pressed: + // store the current selection in case we start scrolling + if (q->selectionModel()) { + oldSelection = q->selectionModel()->selection(); + oldCurrent = q->selectionModel()->currentIndex(); + } + break; + + case QScroller::Dragging: + // restore the old selection if we really start scrolling + if (q->selectionModel()) { + q->selectionModel()->select(oldSelection, QItemSelectionModel::ClearAndSelect); + q->selectionModel()->setCurrentIndex(oldCurrent, QItemSelectionModel::NoUpdate); + } + // fall through + + default: + oldSelection = QItemSelection(); + oldCurrent = QModelIndex(); + break; + } + } +} + +#endif // QT_NO_GESTURES + +/*! + \class QAbstractItemView + + \brief The QAbstractItemView class provides the basic functionality for + item view classes. + + \ingroup model-view + + + QAbstractItemView class is the base class for every standard view + that uses a QAbstractItemModel. QAbstractItemView is an abstract + class and cannot itself be instantiated. It provides a standard + interface for interoperating with models through the signals and + slots mechanism, enabling subclasses to be kept up-to-date with + changes to their models. This class provides standard support for + keyboard and mouse navigation, viewport scrolling, item editing, + and selections. The keyboard navigation implements this + functionality: + + \table + \header + \o Keys + \o Functionality + \row + \o Arrow keys + \o Changes the current item and selects it. + \row + \o Ctrl+Arrow keys + \o Changes the current item but does not select it. + \row + \o Shift+Arrow keys + \o Changes the current item and selects it. The previously + selected item(s) is not deselected. + \row + \o Ctr+Space + \o Toggles selection of the current item. + \row + \o Tab/Backtab + \o Changes the current item to the next/previous item. + \row + \o Home/End + \o Selects the first/last item in the model. + \row + \o Page up/Page down + \o Scrolls the rows shown up/down by the number of + visible rows in the view. + \row + \o Ctrl+A + \o Selects all items in the model. + \endtable + + Note that the above table assumes that the + \l{selectionMode}{selection mode} allows the operations. For + instance, you cannot select items if the selection mode is + QAbstractItemView::NoSelection. + + The QAbstractItemView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The view classes that inherit QAbstractItemView only need + to implement their own view-specific functionality, such as + drawing items, returning the geometry of items, finding items, + etc. + + QAbstractItemView provides common slots such as edit() and + setCurrentIndex(). Many protected slots are also provided, including + dataChanged(), rowsInserted(), rowsAboutToBeRemoved(), selectionChanged(), + and currentChanged(). + + The root item is returned by rootIndex(), and the current item by + currentIndex(). To make sure that an item is visible use + scrollTo(). + + Some of QAbstractItemView's functions are concerned with + scrolling, for example setHorizontalScrollMode() and + setVerticalScrollMode(). To set the range of the scroll bars, you + can, for example, reimplement the view's resizeEvent() function: + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 0 + + Note that the range is not updated until the widget is shown. + + Several other functions are concerned with selection control; for + example setSelectionMode(), and setSelectionBehavior(). This class + provides a default selection model to work with + (selectionModel()), but this can be replaced by using + setSelectionModel() with an instance of QItemSelectionModel. + + For complete control over the display and editing of items you can + specify a delegate with setItemDelegate(). + + QAbstractItemView provides a lot of protected functions. Some are + concerned with editing, for example, edit(), and commitData(), + whilst others are keyboard and mouse event handlers. + + \note If you inherit QAbstractItemView and intend to update the contents + of the viewport, you should use viewport->update() instead of + \l{QWidget::update()}{update()} as all painting operations take place on the + viewport. + + \sa {View Classes}, {Model/View Programming}, QAbstractItemModel, {Chart Example} +*/ + +/*! + \enum QAbstractItemView::SelectionMode + + This enum indicates how the view responds to user selections: + + \value SingleSelection When the user selects an item, any already-selected + item becomes unselected, and the user cannot unselect the selected item by + clicking on it. + + \value ContiguousSelection When the user selects an item in the usual way, + the selection is cleared and the new item selected. However, if the user + presses the Shift key while clicking on an item, all items between the + current item and the clicked item are selected or unselected, depending on + the state of the clicked item. + + \value ExtendedSelection When the user selects an item in the usual way, + the selection is cleared and the new item selected. However, if the user + presses the Ctrl key when clicking on an item, the clicked item gets + toggled and all other items are left untouched. If the user presses the + Shift key while clicking on an item, all items between the current item + and the clicked item are selected or unselected, depending on the state of + the clicked item. Multiple items can be selected by dragging the mouse over + them. + + \value MultiSelection When the user selects an item in the usual way, the + selection status of that item is toggled and the other items are left + alone. Multiple items can be toggled by dragging the mouse over them. + + \value NoSelection Items cannot be selected. + + The most commonly used modes are SingleSelection and ExtendedSelection. +*/ + +/*! + \enum QAbstractItemView::SelectionBehavior + + \value SelectItems Selecting single items. + \value SelectRows Selecting only rows. + \value SelectColumns Selecting only columns. +*/ + +/*! + \enum QAbstractItemView::ScrollHint + + \value EnsureVisible Scroll to ensure that the item is visible. + \value PositionAtTop Scroll to position the item at the top of the + viewport. + \value PositionAtBottom Scroll to position the item at the bottom of the + viewport. + \value PositionAtCenter Scroll to position the item at the center of the + viewport. +*/ + + +/*! + \enum QAbstractItemView::EditTrigger + + This enum describes actions which will initiate item editing. + + \value NoEditTriggers No editing possible. + \value CurrentChanged Editing start whenever current item changes. + \value DoubleClicked Editing starts when an item is double clicked. + \value SelectedClicked Editing starts when clicking on an already selected + item. + \value EditKeyPressed Editing starts when the platform edit key has been + pressed over an item. + \value AnyKeyPressed Editing starts when any key is pressed over an item. + \value AllEditTriggers Editing starts for all above actions. +*/ + +/*! + \enum QAbstractItemView::CursorAction + + This enum describes the different ways to navigate between items, + \sa moveCursor() + + \value MoveUp Move to the item above the current item. + \value MoveDown Move to the item below the current item. + \value MoveLeft Move to the item left of the current item. + \value MoveRight Move to the item right of the current item. + \value MoveHome Move to the top-left corner item. + \value MoveEnd Move to the bottom-right corner item. + \value MovePageUp Move one page up above the current item. + \value MovePageDown Move one page down below the current item. + \value MoveNext Move to the item after the current item. + \value MovePrevious Move to the item before the current item. +*/ + +/*! + \enum QAbstractItemView::State + + Describes the different states the view can be in. This is usually + only interesting when reimplementing your own view. + + \value NoState The is the default state. + \value DraggingState The user is dragging items. + \value DragSelectingState The user is selecting items. + \value EditingState The user is editing an item in a widget editor. + \value ExpandingState The user is opening a branch of items. + \value CollapsingState The user is closing a branch of items. + \value AnimatingState The item view is performing an animation. +*/ + +/*! + \since 4.2 + \enum QAbstractItemView::ScrollMode + + \value ScrollPerItem The view will scroll the contents one item at a time. + \value ScrollPerPixel The view will scroll the contents one pixel at a time. +*/ + +/*! + \fn QRect QAbstractItemView::visualRect(const QModelIndex &index) const = 0 + Returns the rectangle on the viewport occupied by the item at \a index. + + If your item is displayed in several areas then visualRect should return + the primary area that contains index and not the complete area that index + might encompasses, touch or cause drawing. + + In the base class this is a pure virtual function. + + \sa indexAt(), visualRegionForSelection() +*/ + +/*! + \fn void QAbstractItemView::scrollTo(const QModelIndex &index, ScrollHint hint) = 0 + + Scrolls the view if necessary to ensure that the item at \a index + is visible. The view will try to position the item according to the given \a hint. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn QModelIndex QAbstractItemView::indexAt(const QPoint &point) const = 0 + + Returns the model index of the item at the viewport coordinates \a point. + + In the base class this is a pure virtual function. + + \sa visualRect() +*/ + +/*! + \fn void QAbstractItemView::activated(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is + activated by the user. How to activate items depends on the + platform; e.g., by single- or double-clicking the item, or by + pressing the Return or Enter key when the item is current. + + \sa clicked(), doubleClicked(), entered(), pressed() +*/ + +/*! + \fn void QAbstractItemView::entered(const QModelIndex &index) + + This signal is emitted when the mouse cursor enters the item + specified by \a index. + Mouse tracking needs to be enabled for this feature to work. + + \sa viewportEntered(), activated(), clicked(), doubleClicked(), pressed() +*/ + +/*! + \fn void QAbstractItemView::viewportEntered() + + This signal is emitted when the mouse cursor enters the viewport. + Mouse tracking needs to be enabled for this feature to work. + + \sa entered() +*/ + +/*! + \fn void QAbstractItemView::pressed(const QModelIndex &index) + + This signal is emitted when a mouse button is pressed. The item + the mouse was pressed on is specified by \a index. The signal is + only emitted when the index is valid. + + Use the QApplication::mouseButtons() function to get the state + of the mouse buttons. + + \sa activated(), clicked(), doubleClicked(), entered() +*/ + +/*! + \fn void QAbstractItemView::clicked(const QModelIndex &index) + + This signal is emitted when a mouse button is clicked. The item + the mouse was clicked on is specified by \a index. The signal is + only emitted when the index is valid. + + \sa activated(), doubleClicked(), entered(), pressed() +*/ + +/*! + \fn void QAbstractItemView::doubleClicked(const QModelIndex &index) + + This signal is emitted when a mouse button is double-clicked. The + item the mouse was double-clicked on is specified by \a index. + The signal is only emitted when the index is valid. + + \sa clicked(), activated() +*/ + +/*! + \fn QModelIndex QAbstractItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) = 0 + + Returns a QModelIndex object pointing to the next object in the view, + based on the given \a cursorAction and keyboard modifiers specified + by \a modifiers. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn int QAbstractItemView::horizontalOffset() const = 0 + + Returns the horizontal offset of the view. + + In the base class this is a pure virtual function. + + \sa verticalOffset() +*/ + +/*! + \fn int QAbstractItemView::verticalOffset() const = 0 + + Returns the vertical offset of the view. + + In the base class this is a pure virtual function. + + \sa horizontalOffset() +*/ + +/*! + \fn bool QAbstractItemView::isIndexHidden(const QModelIndex &index) const + + Returns true if the item referred to by the given \a index is hidden in the view, + otherwise returns false. + + Hiding is a view specific feature. For example in TableView a column can be marked + as hidden or a row in the TreeView. + + In the base class this is a pure virtual function. +*/ + +/*! + \fn void QAbstractItemView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) + + Applies the selection \a flags to the items in or touched by the + rectangle, \a rect. + + When implementing your own itemview setSelection should call + selectionModel()->select(selection, flags) where selection + is either an empty QModelIndex or a QItemSelection that contains + all items that are contained in \a rect. + + \sa selectionCommand(), selectedIndexes() +*/ + +/*! + \fn QRegion QAbstractItemView::visualRegionForSelection(const QItemSelection &selection) const = 0 + + Returns the region from the viewport of the items in the given + \a selection. + + In the base class this is a pure virtual function. + + \sa visualRect(), selectedIndexes() +*/ + +/*! + \fn void QAbstractItemView::update() + \internal +*/ + +/*! + Constructs an abstract item view with the given \a parent. +*/ +QAbstractItemView::QAbstractItemView(QWidget *parent) + : QAbstractScrollArea(*(new QAbstractItemViewPrivate), parent) +{ + d_func()->init(); +} + +/*! + \internal +*/ +QAbstractItemView::QAbstractItemView(QAbstractItemViewPrivate &dd, QWidget *parent) + : QAbstractScrollArea(dd, parent) +{ + d_func()->init(); +} + +/*! + Destroys the view. +*/ +QAbstractItemView::~QAbstractItemView() +{ + Q_D(QAbstractItemView); + // stop these timers here before ~QObject + d->delayedReset.stop(); + d->updateTimer.stop(); + d->delayedEditing.stop(); + d->delayedAutoScroll.stop(); + d->autoScrollTimer.stop(); + d->delayedLayout.stop(); + d->fetchMoreTimer.stop(); +} + +/*! + Sets the \a model for the view to present. + + This function will create and set a new selection model, replacing any + model that was previously set with setSelectionModel(). However, the old + selection model will not be deleted as it may be shared between several + views. We recommend that you delete the old selection model if it is no + longer required. This is done with the following code: + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 2 + + If both the old model and the old selection model do not have parents, or + if their parents are long-lived objects, it may be preferable to call their + deleteLater() functions to explicitly delete them. + + The view \e{does not} take ownership of the model unless it is the model's + parent object because the model may be shared between many different views. + + \sa selectionModel(), setSelectionModel() +*/ +void QAbstractItemView::setModel(QAbstractItemModel *model) +{ + Q_D(QAbstractItemView); + if (model == d->model) + return; + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + disconnect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_headerDataChanged())); + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(reset())); + disconnect(d->model, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); + } + d->model = (model ? model : QAbstractItemModelPrivate::staticEmptyModel()); + + // These asserts do basic sanity checking of the model + Q_ASSERT_X(d->model->index(0,0) == d->model->index(0,0), + "QAbstractItemView::setModel", + "A model should return the exact same index " + "(including its internal id/pointer) when asked for it twice in a row."); + Q_ASSERT_X(!d->model->index(0,0).parent().isValid(), + "QAbstractItemView::setModel", + "The parent of a top level index should be invalid"); + + if (d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + connect(d->model, SIGNAL(destroyed()), + this, SLOT(_q_modelDestroyed())); + connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(dataChanged(QModelIndex,QModelIndex))); + connect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_headerDataChanged())); + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_columnsRemoved(QModelIndex,int,int))); + connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_columnsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(modelReset()), this, SLOT(reset())); + connect(d->model, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); + } + + QItemSelectionModel *selection_model = new QItemSelectionModel(d->model, this); + connect(d->model, SIGNAL(destroyed()), selection_model, SLOT(deleteLater())); + setSelectionModel(selection_model); + + reset(); // kill editors, set new root and do layout +} + +/*! + Returns the model that this view is presenting. +*/ +QAbstractItemModel *QAbstractItemView::model() const +{ + Q_D(const QAbstractItemView); + return (d->model == QAbstractItemModelPrivate::staticEmptyModel() ? 0 : d->model); +} + +/*! + Sets the current selection model to the given \a selectionModel. + + Note that, if you call setModel() after this function, the given \a selectionModel + will be replaced by one created by the view. + + \note It is up to the application to delete the old selection model if it is no + longer needed; i.e., if it is not being used by other views. This will happen + automatically when its parent object is deleted. However, if it does not have a + parent, or if the parent is a long-lived object, it may be preferable to call its + deleteLater() function to explicitly delete it. + + \sa selectionModel(), setModel(), clearSelection() +*/ +void QAbstractItemView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + // ### if the given model is null, we should use the original selection model + Q_ASSERT(selectionModel); + Q_D(QAbstractItemView); + + if (selectionModel->model() != d->model) { + qWarning("QAbstractItemView::setSelectionModel() failed: " + "Trying to set a selection model, which works on " + "a different model than the view."); + return; + } + + if (d->selectionModel) { + disconnect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection))); + disconnect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex,QModelIndex))); + } + + d->selectionModel = selectionModel; + + if (d->selectionModel) { + connect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(selectionChanged(QItemSelection,QItemSelection))); + connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex,QModelIndex))); + } +} + +/*! + Returns the current selection model. + + \sa setSelectionModel(), selectedIndexes() +*/ +QItemSelectionModel* QAbstractItemView::selectionModel() const +{ + Q_D(const QAbstractItemView); + return d->selectionModel; +} + +/*! + Sets the item delegate for this view and its model to \a delegate. + This is useful if you want complete control over the editing and + display of items. + + Any existing delegate will be removed, but not deleted. QAbstractItemView + does not take ownership of \a delegate. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegate() +*/ +void QAbstractItemView::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (delegate == d->itemDelegate) + return; + + if (d->itemDelegate) { + if (d->delegateRefCount(d->itemDelegate) == 1) { + disconnect(d->itemDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(d->itemDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + disconnect(d->itemDelegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout())); + } + } + + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + qRegisterMetaType("QModelIndex"); + connect(delegate, SIGNAL(sizeHintChanged(QModelIndex)), this, SLOT(doItemsLayout()), Qt::QueuedConnection); + } + } + d->itemDelegate = delegate; + viewport()->update(); +} + +/*! + Returns the item delegate used by this view and model. This is + either one set with setItemDelegate(), or the default one. + + \sa setItemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegate() const +{ + return d_func()->itemDelegate; +} + +/*! + \reimp +*/ +QVariant QAbstractItemView::inputMethodQuery(Qt::InputMethodQuery query) const +{ + const QModelIndex current = currentIndex(); + if (!current.isValid() || query != Qt::ImMicroFocus) + return QAbstractScrollArea::inputMethodQuery(query); + return visualRect(current); +} + +/*! + \since 4.2 + + Sets the given item \a delegate used by this view and model for the given + \a row. All items on \a row will be drawn and managed by \a delegate + instead of using the default delegate (i.e., itemDelegate()). + + Any existing row delegate for \a row will be removed, but not + deleted. QAbstractItemView does not take ownership of \a delegate. + + \note If a delegate has been assigned to both a row and a column, the row + delegate (i.e., this delegate) will take precedence and manage the + intersecting cell index. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegateForRow(), setItemDelegateForColumn(), itemDelegate() +*/ +void QAbstractItemView::setItemDelegateForRow(int row, QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (QAbstractItemDelegate *rowDelegate = d->rowDelegates.value(row, 0)) { + if (d->delegateRefCount(rowDelegate) == 1) { + disconnect(rowDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(rowDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->rowDelegates.remove(row); + } + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->rowDelegates.insert(row, delegate); + } + viewport()->update(); +} + +/*! + \since 4.2 + + Returns the item delegate used by this view and model for the given \a row, + or 0 if no delegate has been assigned. You can call itemDelegate() to get a + pointer to the current delegate for a given index. + + \sa setItemDelegateForRow(), itemDelegateForColumn(), setItemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegateForRow(int row) const +{ + Q_D(const QAbstractItemView); + return d->rowDelegates.value(row, 0); +} + +/*! + \since 4.2 + + Sets the given item \a delegate used by this view and model for the given + \a column. All items on \a column will be drawn and managed by \a delegate + instead of using the default delegate (i.e., itemDelegate()). + + Any existing column delegate for \a column will be removed, but not + deleted. QAbstractItemView does not take ownership of \a delegate. + + \note If a delegate has been assigned to both a row and a column, the row + delegate will take precedence and manage the intersecting cell index. + + \warning You should not share the same instance of a delegate between views. + Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + + \sa itemDelegateForColumn(), setItemDelegateForRow(), itemDelegate() +*/ +void QAbstractItemView::setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate) +{ + Q_D(QAbstractItemView); + if (QAbstractItemDelegate *columnDelegate = d->columnDelegates.value(column, 0)) { + if (d->delegateRefCount(columnDelegate) == 1) { + disconnect(columnDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + disconnect(columnDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->columnDelegates.remove(column); + } + if (delegate) { + if (d->delegateRefCount(delegate) == 0) { + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + connect(delegate, SIGNAL(commitData(QWidget*)), this, SLOT(commitData(QWidget*))); + } + d->columnDelegates.insert(column, delegate); + } + viewport()->update(); +} + +/*! + \since 4.2 + + Returns the item delegate used by this view and model for the given \a + column. You can call itemDelegate() to get a pointer to the current delegate + for a given index. + + \sa setItemDelegateForColumn(), itemDelegateForRow(), itemDelegate() +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegateForColumn(int column) const +{ + Q_D(const QAbstractItemView); + return d->columnDelegates.value(column, 0); +} + +/*! + Returns the item delegate used by this view and model for + the given \a index. +*/ +QAbstractItemDelegate *QAbstractItemView::itemDelegate(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + return d->delegateForIndex(index); +} + +/*! + \property QAbstractItemView::selectionMode + \brief which selection mode the view operates in + + This property controls whether the user can select one or many items + and, in many-item selections, whether the selection must be a + continuous range of items. + + \sa SelectionMode SelectionBehavior +*/ +void QAbstractItemView::setSelectionMode(SelectionMode mode) +{ + Q_D(QAbstractItemView); + d->selectionMode = mode; +} + +QAbstractItemView::SelectionMode QAbstractItemView::selectionMode() const +{ + Q_D(const QAbstractItemView); + return d->selectionMode; +} + +/*! + \property QAbstractItemView::selectionBehavior + \brief which selection behavior the view uses + + This property holds whether selections are done + in terms of single items, rows or columns. + + \sa SelectionMode SelectionBehavior +*/ + +void QAbstractItemView::setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior) +{ + Q_D(QAbstractItemView); + d->selectionBehavior = behavior; +} + +QAbstractItemView::SelectionBehavior QAbstractItemView::selectionBehavior() const +{ + Q_D(const QAbstractItemView); + return d->selectionBehavior; +} + +/*! + Sets the current item to be the item at \a index. + + Unless the current selection mode is + \l{QAbstractItemView::}{NoSelection}, the item is also be selected. + Note that this function also updates the starting position for any + new selections the user performs. + + To set an item as the current item without selecting it, call + + \c{selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);} + + \sa currentIndex(), currentChanged(), selectionMode +*/ +void QAbstractItemView::setCurrentIndex(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (d->selectionModel && (!index.isValid() || d->isIndexEnabled(index))) { + QItemSelectionModel::SelectionFlags command = selectionCommand(index, 0); + d->selectionModel->setCurrentIndex(index, command); + d->currentIndexSet = true; + if ((command & QItemSelectionModel::Current) == 0) + d->pressedPosition = visualRect(currentIndex()).center() + d->offset(); + } +} + +/*! + Returns the model index of the current item. + + \sa setCurrentIndex() +*/ +QModelIndex QAbstractItemView::currentIndex() const +{ + Q_D(const QAbstractItemView); + return d->selectionModel ? d->selectionModel->currentIndex() : QModelIndex(); +} + + +/*! + Reset the internal state of the view. + + \warning This function will reset open editors, scroll bar positions, + selections, etc. Existing changes will not be committed. If you would like + to save your changes when resetting the view, you can reimplement this + function, commit your changes, and then call the superclass' + implementation. +*/ +void QAbstractItemView::reset() +{ + Q_D(QAbstractItemView); + d->delayedReset.stop(); //make sure we stop the timer + foreach (const QEditorInfo &info, d->indexEditorHash) { + if (info.widget) + d->releaseEditor(info.widget.data()); + } + d->editorIndexHash.clear(); + d->indexEditorHash.clear(); + d->persistent.clear(); + d->currentIndexSet = false; + setState(NoState); + setRootIndex(QModelIndex()); + if (d->selectionModel) + d->selectionModel->reset(); +} + +/*! + Sets the root item to the item at the given \a index. + + \sa rootIndex() +*/ +void QAbstractItemView::setRootIndex(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (index.isValid() && index.model() != d->model) { + qWarning("QAbstractItemView::setRootIndex failed : index must be from the currently set model"); + return; + } + d->root = index; + d->doDelayedItemsLayout(); +} + +/*! + Returns the model index of the model's root item. The root item is + the parent item to the view's toplevel items. The root can be invalid. + + \sa setRootIndex() +*/ +QModelIndex QAbstractItemView::rootIndex() const +{ + return QModelIndex(d_func()->root); +} + +/*! + Selects all items in the view. + This function will use the selection behavior + set on the view when selecting. + + \sa setSelection(), selectedIndexes(), clearSelection() +*/ +void QAbstractItemView::selectAll() +{ + Q_D(QAbstractItemView); + SelectionMode mode = d->selectionMode; + if (mode == MultiSelection || mode == ExtendedSelection) + d->selectAll(QItemSelectionModel::ClearAndSelect + |d->selectionBehaviorFlags()); + else if (mode != SingleSelection) + d->selectAll(selectionCommand(d->model->index(0, 0, d->root))); +} + +/*! + Starts editing the item corresponding to the given \a index if it is + editable. + + Note that this function does not change the current index. Since the current + index defines the next and previous items to edit, users may find that + keyboard navigation does not work as expected. To provide consistent navigation + behavior, call setCurrentIndex() before this function with the same model + index. + + \sa QModelIndex::flags() +*/ +void QAbstractItemView::edit(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (!d->isIndexValid(index)) + qWarning("edit: index was invalid"); + if (!edit(index, AllEditTriggers, 0)) + qWarning("edit: editing failed"); +} + +/*! + Deselects all selected items. The current index will not be changed. + + \sa setSelection(), selectAll() +*/ +void QAbstractItemView::clearSelection() +{ + Q_D(QAbstractItemView); + if (d->selectionModel) + d->selectionModel->clearSelection(); +} + +/*! + \internal + + This function is intended to lay out the items in the view. + The default implementation just calls updateGeometries() and updates the viewport. +*/ +void QAbstractItemView::doItemsLayout() +{ + Q_D(QAbstractItemView); + d->interruptDelayedItemsLayout(); + updateGeometries(); + d->viewport->update(); +} + +/*! + \property QAbstractItemView::editTriggers + \brief which actions will initiate item editing + + This property is a selection of flags defined by + \l{EditTrigger}, combined using the OR + operator. The view will only initiate the editing of an item if the + action performed is set in this property. +*/ +void QAbstractItemView::setEditTriggers(EditTriggers actions) +{ + Q_D(QAbstractItemView); + d->editTriggers = actions; +} + +QAbstractItemView::EditTriggers QAbstractItemView::editTriggers() const +{ + Q_D(const QAbstractItemView); + return d->editTriggers; +} + +/*! + \since 4.2 + \property QAbstractItemView::verticalScrollMode + \brief how the view scrolls its contents in the vertical direction + + This property controls how the view scroll its contents vertically. + Scrolling can be done either per pixel or per item. +*/ + +void QAbstractItemView::setVerticalScrollMode(ScrollMode mode) +{ + Q_D(QAbstractItemView); + if (mode == d->verticalScrollMode) + return; + QModelIndex topLeft = indexAt(QPoint(0, 0)); + d->verticalScrollMode = mode; + updateGeometries(); // update the scroll bars + scrollTo(topLeft, QAbstractItemView::PositionAtTop); +} + +QAbstractItemView::ScrollMode QAbstractItemView::verticalScrollMode() const +{ + Q_D(const QAbstractItemView); + return d->verticalScrollMode; +} + +/*! + \since 4.2 + \property QAbstractItemView::horizontalScrollMode + \brief how the view scrolls its contents in the horizontal direction + + This property controls how the view scroll its contents horizontally. + Scrolling can be done either per pixel or per item. +*/ + +void QAbstractItemView::setHorizontalScrollMode(ScrollMode mode) +{ + Q_D(QAbstractItemView); + d->horizontalScrollMode = mode; + updateGeometries(); // update the scroll bars +} + +QAbstractItemView::ScrollMode QAbstractItemView::horizontalScrollMode() const +{ + Q_D(const QAbstractItemView); + return d->horizontalScrollMode; +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \since 4.2 + \property QAbstractItemView::dragDropOverwriteMode + \brief the view's drag and drop behavior + + If its value is \c true, the selected data will overwrite the + existing item data when dropped, while moving the data will clear + the item. If its value is \c false, the selected data will be + inserted as a new item when the data is dropped. When the data is + moved, the item is removed as well. + + The default value is \c false, as in the QListView and QTreeView + subclasses. In the QTableView subclass, on the other hand, the + property has been set to \c true. + + Note: This is not intended to prevent overwriting of items. + The model's implementation of flags() should do that by not + returning Qt::ItemIsDropEnabled. + + \sa dragDropMode +*/ +void QAbstractItemView::setDragDropOverwriteMode(bool overwrite) +{ + Q_D(QAbstractItemView); + d->overwrite = overwrite; +} + +bool QAbstractItemView::dragDropOverwriteMode() const +{ + Q_D(const QAbstractItemView); + return d->overwrite; +} +#endif + +/*! + \property QAbstractItemView::autoScroll + \brief whether autoscrolling in drag move events is enabled + + If this property is set to true (the default), the + QAbstractItemView automatically scrolls the contents of the view + if the user drags within 16 pixels of the viewport edge. If the current + item changes, then the view will scroll automatically to ensure that the + current item is fully visible. + + This property only works if the viewport accepts drops. Autoscroll is + switched off by setting this property to false. +*/ + +void QAbstractItemView::setAutoScroll(bool enable) +{ + Q_D(QAbstractItemView); + d->autoScroll = enable; +} + +bool QAbstractItemView::hasAutoScroll() const +{ + Q_D(const QAbstractItemView); + return d->autoScroll; +} + +/*! + \since 4.4 + \property QAbstractItemView::autoScrollMargin + \brief the size of the area when auto scrolling is triggered + + This property controls the size of the area at the edge of the viewport that + triggers autoscrolling. The default value is 16 pixels. +*/ +void QAbstractItemView::setAutoScrollMargin(int margin) +{ + Q_D(QAbstractItemView); + d->autoScrollMargin = margin; +} + +int QAbstractItemView::autoScrollMargin() const +{ + Q_D(const QAbstractItemView); + return d->autoScrollMargin; +} + +/*! + \property QAbstractItemView::tabKeyNavigation + \brief whether item navigation with tab and backtab is enabled. +*/ + +void QAbstractItemView::setTabKeyNavigation(bool enable) +{ + Q_D(QAbstractItemView); + d->tabKeyNavigation = enable; +} + +bool QAbstractItemView::tabKeyNavigation() const +{ + Q_D(const QAbstractItemView); + return d->tabKeyNavigation; +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \property QAbstractItemView::showDropIndicator + \brief whether the drop indicator is shown when dragging items and dropping. + + \sa dragEnabled DragDropMode dragDropOverwriteMode acceptDrops +*/ + +void QAbstractItemView::setDropIndicatorShown(bool enable) +{ + Q_D(QAbstractItemView); + d->showDropIndicator = enable; +} + +bool QAbstractItemView::showDropIndicator() const +{ + Q_D(const QAbstractItemView); + return d->showDropIndicator; +} + +/*! + \property QAbstractItemView::dragEnabled + \brief whether the view supports dragging of its own items + + \sa showDropIndicator DragDropMode dragDropOverwriteMode acceptDrops +*/ + +void QAbstractItemView::setDragEnabled(bool enable) +{ + Q_D(QAbstractItemView); + d->dragEnabled = enable; +} + +bool QAbstractItemView::dragEnabled() const +{ + Q_D(const QAbstractItemView); + return d->dragEnabled; +} + +/*! + \since 4.2 + \enum QAbstractItemView::DragDropMode + + Describes the various drag and drop events the view can act upon. + By default the view does not support dragging or dropping (\c + NoDragDrop). + + \value NoDragDrop Does not support dragging or dropping. + \value DragOnly The view supports dragging of its own items + \value DropOnly The view accepts drops + \value DragDrop The view supports both dragging and dropping + \value InternalMove The view accepts move (\bold{not copy}) operations only + from itself. + + Note that the model used needs to provide support for drag and drop operations. + + \sa setDragDropMode() {Using drag and drop with item views} +*/ + +/*! + \property QAbstractItemView::dragDropMode + \brief the drag and drop event the view will act upon + + \since 4.2 + \sa showDropIndicator dragDropOverwriteMode +*/ +void QAbstractItemView::setDragDropMode(DragDropMode behavior) +{ + Q_D(QAbstractItemView); + d->dragDropMode = behavior; + setDragEnabled(behavior == DragOnly || behavior == DragDrop || behavior == InternalMove); + setAcceptDrops(behavior == DropOnly || behavior == DragDrop || behavior == InternalMove); +} + +QAbstractItemView::DragDropMode QAbstractItemView::dragDropMode() const +{ + Q_D(const QAbstractItemView); + DragDropMode setBehavior = d->dragDropMode; + if (!dragEnabled() && !acceptDrops()) + return NoDragDrop; + + if (dragEnabled() && !acceptDrops()) + return DragOnly; + + if (!dragEnabled() && acceptDrops()) + return DropOnly; + + if (dragEnabled() && acceptDrops()) { + if (setBehavior == InternalMove) + return setBehavior; + else + return DragDrop; + } + + return NoDragDrop; +} + +/*! + \property QAbstractItemView::defaultDropAction + \brief the drop action that will be used by default in QAbstractItemView::drag() + + If the property is not set, the drop action is CopyAction when the supported + actions support CopyAction. + + \since 4.6 + \sa showDropIndicator dragDropOverwriteMode +*/ +void QAbstractItemView::setDefaultDropAction(Qt::DropAction dropAction) +{ + Q_D(QAbstractItemView); + d->defaultDropAction = dropAction; +} + +Qt::DropAction QAbstractItemView::defaultDropAction() const +{ + Q_D(const QAbstractItemView); + return d->defaultDropAction; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \property QAbstractItemView::alternatingRowColors + \brief whether to draw the background using alternating colors + + If this property is true, the item background will be drawn using + QPalette::Base and QPalette::AlternateBase; otherwise the background + will be drawn using the QPalette::Base color. + + By default, this property is false. +*/ +void QAbstractItemView::setAlternatingRowColors(bool enable) +{ + Q_D(QAbstractItemView); + d->alternatingColors = enable; + if (isVisible()) + d->viewport->update(); +} + +bool QAbstractItemView::alternatingRowColors() const +{ + Q_D(const QAbstractItemView); + return d->alternatingColors; +} + +/*! + \property QAbstractItemView::iconSize + \brief the size of items' icons + + Setting this property when the view is visible will cause the + items to be laid out again. +*/ +void QAbstractItemView::setIconSize(const QSize &size) +{ + Q_D(QAbstractItemView); + if (size == d->iconSize) + return; + d->iconSize = size; + d->doDelayedItemsLayout(); +} + +QSize QAbstractItemView::iconSize() const +{ + Q_D(const QAbstractItemView); + return d->iconSize; +} + +/*! + \property QAbstractItemView::textElideMode + + \brief the position of the "..." in elided text. + + The default value for all item views is Qt::ElideRight. +*/ +void QAbstractItemView::setTextElideMode(Qt::TextElideMode mode) +{ + Q_D(QAbstractItemView); + d->textElideMode = mode; +} + +Qt::TextElideMode QAbstractItemView::textElideMode() const +{ + return d_func()->textElideMode; +} + +/*! + \reimp +*/ +bool QAbstractItemView::focusNextPrevChild(bool next) +{ + Q_D(QAbstractItemView); + if (d->tabKeyNavigation && isEnabled() && d->viewport->isEnabled()) { + QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); + keyPressEvent(&event); + if (event.isAccepted()) + return true; + } + return QAbstractScrollArea::focusNextPrevChild(next); +} + +/*! + \reimp +*/ +bool QAbstractItemView::event(QEvent *event) +{ + Q_D(QAbstractItemView); + switch (event->type()) { + case QEvent::Paint: + //we call this here because the scrollbars' visibility might be altered + //so this can't be done in the paintEvent method + d->executePostedLayout(); //make sure we set the layout properly + break; + case QEvent::Show: + d->executePostedLayout(); //make sure we set the layout properly + if (d->shouldScrollToCurrentOnShow) { + d->shouldScrollToCurrentOnShow = false; + const QModelIndex current = currentIndex(); + if (current.isValid() && (d->state == QAbstractItemView::EditingState || d->autoScroll)) + scrollTo(current); + } + break; + case QEvent::LocaleChange: + viewport()->update(); + break; + case QEvent::LayoutDirectionChange: + case QEvent::ApplicationLayoutDirectionChange: + updateGeometries(); + break; + case QEvent::StyleChange: + doItemsLayout(); + break; + case QEvent::FocusOut: + d->checkPersistentEditorFocus(); + break; + case QEvent::FontChange: + d->doDelayedItemsLayout(); // the size of the items will change + break; +#ifdef QT_SOFTKEYS_ENABLED + case QEvent::LanguageChange: + d->doneSoftKey->setText(QSoftKeyManager::standardSoftKeyText(QSoftKeyManager::DoneSoftKey)); + break; +#endif + default: + break; + } + return QAbstractScrollArea::event(event); +} + +/*! + \fn bool QAbstractItemView::viewportEvent(QEvent *event) + + This function is used to handle tool tips, and What's + This? mode, if the given \a event is a QEvent::ToolTip,or a + QEvent::WhatsThis. It passes all other + events on to its base class viewportEvent() handler. +*/ +bool QAbstractItemView::viewportEvent(QEvent *event) +{ + Q_D(QAbstractItemView); + switch (event->type()) { + case QEvent::HoverMove: + case QEvent::HoverEnter: + d->setHoverIndex(indexAt(static_cast(event)->pos())); + break; + case QEvent::HoverLeave: + d->setHoverIndex(QModelIndex()); + break; + case QEvent::Enter: + d->viewportEnteredNeeded = true; + break; + case QEvent::Leave: + #ifndef QT_NO_STATUSTIP + if (d->shouldClearStatusTip && d->parent) { + QString empty; + QStatusTipEvent tip(empty); + QApplication::sendEvent(d->parent, &tip); + d->shouldClearStatusTip = false; + } + #endif + d->enteredIndex = QModelIndex(); + break; + case QEvent::ToolTip: + case QEvent::QueryWhatsThis: + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast(event); + const QModelIndex index = indexAt(he->pos()); + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + option.rect = visualRect(index); + option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + bool retval = false; + // ### Qt 5: make this a normal function call to a virtual function + QMetaObject::invokeMethod(d->delegateForIndex(index), "helpEvent", + Q_RETURN_ARG(bool, retval), + Q_ARG(QHelpEvent *, he), + Q_ARG(QAbstractItemView *, this), + Q_ARG(QStyleOptionViewItem, option), + Q_ARG(QModelIndex, index)); + return retval; + } + case QEvent::FontChange: + d->doDelayedItemsLayout(); // the size of the items will change + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + d->viewport->update(); + break; + case QEvent::ScrollPrepare: + executeDelayedItemsLayout(); +#ifndef QT_NO_GESTURES + connect(QScroller::scroller(d->viewport), SIGNAL(stateChanged(QScroller::State)), this, SLOT(_q_scrollerStateChanged()), Qt::UniqueConnection); +#endif + break; + + default: + break; + } + return QAbstractScrollArea::viewportEvent(event); +} + +/*! + This function is called with the given \a event when a mouse button is pressed + while the cursor is inside the widget. If a valid item is pressed on it is made + into the current item. This function emits the pressed() signal. +*/ +void QAbstractItemView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + if (!d->selectionModel + || (d->state == EditingState && d->hasEditor(index))) + return; + + d->pressedAlreadySelected = d->selectionModel->isSelected(index); + d->pressedIndex = index; + d->pressedModifiers = event->modifiers(); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + d->noSelectionOnMousePress = command == QItemSelectionModel::NoUpdate || !index.isValid(); + QPoint offset = d->offset(); + if ((command & QItemSelectionModel::Current) == 0) + d->pressedPosition = pos + offset; + else if (!indexAt(d->pressedPosition - offset).isValid()) + d->pressedPosition = visualRect(currentIndex()).center() + offset; + + if (edit(index, NoEditTriggers, event)) + return; + + if (index.isValid() && d->isIndexEnabled(index)) { + // we disable scrollTo for mouse press so the item doesn't change position + // when the user is interacting with it (ie. clicking on it) + bool autoScroll = d->autoScroll; + d->autoScroll = false; + d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + d->autoScroll = autoScroll; + QRect rect(d->pressedPosition - offset, pos); + if (command.testFlag(QItemSelectionModel::Toggle)) { + command &= ~QItemSelectionModel::Toggle; + d->ctrlDragSelectionFlag = d->selectionModel->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; + command |= d->ctrlDragSelectionFlag; + } + setSelection(rect, command); + + // signal handlers may change the model + emit pressed(index); + if (d->autoScroll) { + //we delay the autoscrolling to filter out double click event + //100 is to be sure that there won't be a double-click misinterpreted as a 2 single clicks + d->delayedAutoScroll.start(QApplication::doubleClickInterval()+100, this); + } + + } else { + // Forces a finalize() even if mouse is pressed, but not on a item + d->selectionModel->select(QModelIndex(), QItemSelectionModel::Select); + } +} + +/*! + This function is called with the given \a event when a mouse move event is + sent to the widget. If a selection is in progress and new items are moved + over the selection is extended; if a drag is in progress it is continued. +*/ +void QAbstractItemView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + QPoint topLeft; + QPoint bottomRight = event->pos(); + + if (state() == ExpandingState || state() == CollapsingState) + return; + +#ifndef QT_NO_DRAGANDDROP + if (state() == DraggingState) { + topLeft = d->pressedPosition - d->offset(); + if ((topLeft - bottomRight).manhattanLength() > QApplication::startDragDistance()) { + d->pressedIndex = QModelIndex(); + startDrag(d->model->supportedDragActions()); + setState(NoState); // the startDrag will return when the dnd operation is done + stopAutoScroll(); + } + return; + } +#endif // QT_NO_DRAGANDDROP + + QPersistentModelIndex index = indexAt(bottomRight); + QModelIndex buddy = d->model->buddy(d->pressedIndex); + if ((state() == EditingState && d->hasEditor(buddy)) + || edit(index, NoEditTriggers, event)) + return; + + if (d->selectionMode != SingleSelection) + topLeft = d->pressedPosition - d->offset(); + else + topLeft = bottomRight; + + d->checkMouseMove(index); + +#ifndef QT_NO_DRAGANDDROP + if (d->pressedIndex.isValid() + && d->dragEnabled + && (state() != DragSelectingState) + && (event->buttons() != Qt::NoButton) + && !d->selectedDraggableIndexes().isEmpty()) { + setState(DraggingState); + return; + } +#endif + + if ((event->buttons() & Qt::LeftButton) && d->selectionAllowed(index) && d->selectionModel) { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + if (d->ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(QItemSelectionModel::Toggle)) { + command &= ~QItemSelectionModel::Toggle; + command |= d->ctrlDragSelectionFlag; + } + + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + setSelection(selectionRect, command); + + // set at the end because it might scroll the view + if (index.isValid() + && (index != d->selectionModel->currentIndex()) + && d->isIndexEnabled(index)) + d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } +} + +/*! + This function is called with the given \a event when a mouse button is released, + after a mouse press event on the widget. If a user presses the mouse inside your + widget and then drags the mouse to another location before releasing the mouse button, + your widget receives the release event. The function will emit the clicked() signal if an + item was being pressed. +*/ +void QAbstractItemView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + if (state() == EditingState) { + if (d->isIndexValid(index) + && d->isIndexEnabled(index) + && d->sendDelegateEvent(index, event)) + update(index); + return; + } + + bool click = (index == d->pressedIndex && index.isValid()); + bool selectedClicked = click && (event->button() & Qt::LeftButton) && d->pressedAlreadySelected; + EditTrigger trigger = (selectedClicked ? SelectedClicked : NoEditTriggers); + bool edited = edit(index, trigger, event); + + d->ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; + + if (d->selectionModel && d->noSelectionOnMousePress) { + d->noSelectionOnMousePress = false; + d->selectionModel->select(index, selectionCommand(index, event)); + } + + setState(NoState); + + if (click) { + emit clicked(index); + if (edited) + return; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + if (d->pressedAlreadySelected) + option.state |= QStyle::State_Selected; + if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + emit activated(index); + } +} + +/*! + This function is called with the given \a event when a mouse button is + double clicked inside the widget. If the double-click is on a valid item it + emits the doubleClicked() signal and calls edit() on the item. +*/ +void QAbstractItemView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QAbstractItemView); + + QModelIndex index = indexAt(event->pos()); + if (!index.isValid() + || !d->isIndexEnabled(index) + || (d->pressedIndex != index)) { + QMouseEvent me(QEvent::MouseButtonPress, + event->pos(), event->button(), + event->buttons(), event->modifiers()); + mousePressEvent(&me); + return; + } + // signal handlers may change the model + QPersistentModelIndex persistent = index; + emit doubleClicked(persistent); + if ((event->button() & Qt::LeftButton) && !edit(persistent, DoubleClicked, event) + && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) + emit activated(persistent); +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + This function is called with the given \a event when a drag and drop operation enters + the widget. If the drag is over a valid dropping place (e.g. over an item that + accepts drops), the event is accepted; otherwise it is ignored. + + \sa dropEvent() startDrag() +*/ +void QAbstractItemView::dragEnterEvent(QDragEnterEvent *event) +{ + if (dragDropMode() == InternalMove + && (event->source() != this|| !(event->possibleActions() & Qt::MoveAction))) + return; + + if (d_func()->canDecode(event)) { + event->accept(); + setState(DraggingState); + } else { + event->ignore(); + } +} + +/*! + This function is called continuously with the given \a event during a drag and + drop operation over the widget. It can cause the view to scroll if, for example, + the user drags a selection to view's right or bottom edge. In this case, the + event will be accepted; otherwise it will be ignored. + + \sa dropEvent() startDrag() +*/ +void QAbstractItemView::dragMoveEvent(QDragMoveEvent *event) +{ + Q_D(QAbstractItemView); + if (dragDropMode() == InternalMove + && (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) + return; + + // ignore by default + event->ignore(); + + QModelIndex index = indexAt(event->pos()); + d->hover = index; + if (!d->droppingOnItself(event, index) + && d->canDecode(event)) { + + if (index.isValid() && d->showDropIndicator) { + QRect rect = visualRect(index); + d->dropIndicatorPosition = d->position(event->pos(), rect, index); + switch (d->dropIndicatorPosition) { + case AboveItem: + if (d->isIndexDropEnabled(index.parent())) { + d->dropIndicatorRect = QRect(rect.left(), rect.top(), rect.width(), 0); + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case BelowItem: + if (d->isIndexDropEnabled(index.parent())) { + d->dropIndicatorRect = QRect(rect.left(), rect.bottom(), rect.width(), 0); + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case OnItem: + if (d->isIndexDropEnabled(index)) { + d->dropIndicatorRect = rect; + event->accept(); + } else { + d->dropIndicatorRect = QRect(); + } + break; + case OnViewport: + d->dropIndicatorRect = QRect(); + if (d->isIndexDropEnabled(rootIndex())) { + event->accept(); // allow dropping in empty areas + } + break; + } + } else { + d->dropIndicatorRect = QRect(); + d->dropIndicatorPosition = OnViewport; + if (d->isIndexDropEnabled(rootIndex())) { + event->accept(); // allow dropping in empty areas + } + } + d->viewport->update(); + } // can decode + + if (d->shouldAutoScroll(event->pos())) + startAutoScroll(); +} + +/*! + \internal + Return true if this is a move from ourself and \a index is a child of the selection that + is being moved. + */ +bool QAbstractItemViewPrivate::droppingOnItself(QDropEvent *event, const QModelIndex &index) +{ + Q_Q(QAbstractItemView); + Qt::DropAction dropAction = event->dropAction(); + if (q->dragDropMode() == QAbstractItemView::InternalMove) + dropAction = Qt::MoveAction; + if (event->source() == q + && event->possibleActions() & Qt::MoveAction + && dropAction == Qt::MoveAction) { + QModelIndexList selectedIndexes = q->selectedIndexes(); + QModelIndex child = index; + while (child.isValid() && child != root) { + if (selectedIndexes.contains(child)) + return true; + child = child.parent(); + } + } + return false; +} + +/*! + \fn void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *event) + + This function is called when the item being dragged leaves the view. + The \a event describes the state of the drag and drop operation. +*/ +void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *) +{ + Q_D(QAbstractItemView); + stopAutoScroll(); + setState(NoState); + d->hover = QModelIndex(); + d->viewport->update(); +} + +/*! + This function is called with the given \a event when a drop event occurs over + the widget. If the model accepts the even position the drop event is accepted; + otherwise it is ignored. + + \sa startDrag() +*/ +void QAbstractItemView::dropEvent(QDropEvent *event) +{ + Q_D(QAbstractItemView); + if (dragDropMode() == InternalMove) { + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + return; + } + + QModelIndex index; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &index)) { + if (d->model->dropMimeData(event->mimeData(), + dragDropMode() == InternalMove ? Qt::MoveAction : event->dropAction(), row, col, index)) { + if (dragDropMode() == InternalMove) + event->setDropAction(Qt::MoveAction); + event->accept(); + } + } + stopAutoScroll(); + setState(NoState); + d->viewport->update(); +} + +/*! + If the event hasn't already been accepted, determines the index to drop on. + + if (row == -1 && col == -1) + // append to this drop index + else + // place at row, col in drop index + + If it returns true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop. + \internal + */ +bool QAbstractItemViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) +{ + Q_Q(QAbstractItemView); + if (event->isAccepted()) + return false; + + QModelIndex index; + // rootIndex() (i.e. the viewport) might be a valid index + if (viewport->rect().contains(event->pos())) { + index = q->indexAt(event->pos()); + if (!index.isValid() || !q->visualRect(index).contains(event->pos())) + index = root; + } + + // If we are allowed to do the drop + if (model->supportedDropActions() & event->dropAction()) { + int row = -1; + int col = -1; + if (index != root) { + dropIndicatorPosition = position(event->pos(), q->visualRect(index), index); + switch (dropIndicatorPosition) { + case QAbstractItemView::AboveItem: + row = index.row(); + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::BelowItem: + row = index.row() + 1; + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::OnItem: + case QAbstractItemView::OnViewport: + break; + } + } else { + dropIndicatorPosition = QAbstractItemView::OnViewport; + } + *dropIndex = index; + *dropRow = row; + *dropCol = col; + if (!droppingOnItself(event, index)) + return true; + } + return false; +} + +QAbstractItemView::DropIndicatorPosition +QAbstractItemViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const +{ + QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; + if (!overwrite) { + const int margin = 2; + if (pos.y() - rect.top() < margin) { + r = QAbstractItemView::AboveItem; + } else if (rect.bottom() - pos.y() < margin) { + r = QAbstractItemView::BelowItem; + } else if (rect.contains(pos, true)) { + r = QAbstractItemView::OnItem; + } + } else { + QRect touchingRect = rect; + touchingRect.adjust(-1, -1, 1, 1); + if (touchingRect.contains(pos, false)) { + r = QAbstractItemView::OnItem; + } + } + + if (r == QAbstractItemView::OnItem && (!(model->flags(index) & Qt::ItemIsDropEnabled))) + r = pos.y() < rect.center().y() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; + + return r; +} + +#endif // QT_NO_DRAGANDDROP + +/*! + This function is called with the given \a event when the widget obtains the focus. + By default, the event is ignored. + + \sa setFocus(), focusOutEvent() +*/ +void QAbstractItemView::focusInEvent(QFocusEvent *event) +{ + Q_D(QAbstractItemView); + QAbstractScrollArea::focusInEvent(event); + + const QItemSelectionModel* model = selectionModel(); + const bool currentIndexValid = currentIndex().isValid(); + + if (model + && !d->currentIndexSet + && !currentIndexValid) { + bool autoScroll = d->autoScroll; + d->autoScroll = false; + QModelIndex index = moveCursor(MoveNext, Qt::NoModifier); // first visible index + if (index.isValid() && d->isIndexEnabled(index) && event->reason() != Qt::MouseFocusReason) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + d->autoScroll = autoScroll; + } + + if (model && currentIndexValid) { + if (currentIndex().flags() != Qt::ItemIsEditable) + setAttribute(Qt::WA_InputMethodEnabled, false); + else + setAttribute(Qt::WA_InputMethodEnabled); + } + + if (!currentIndexValid) + setAttribute(Qt::WA_InputMethodEnabled, false); + + d->viewport->update(); +} + +/*! + This function is called with the given \a event when the widget + looses the focus. By default, the event is ignored. + + \sa clearFocus(), focusInEvent() +*/ +void QAbstractItemView::focusOutEvent(QFocusEvent *event) +{ + Q_D(QAbstractItemView); + QAbstractScrollArea::focusOutEvent(event); + d->viewport->update(); + +#ifdef QT_SOFTKEYS_ENABLED + if(!hasEditFocus()) + removeAction(d->doneSoftKey); +#endif +} + +/*! + This function is called with the given \a event when a key event is sent to + the widget. The default implementation handles basic cursor movement, e.g. Up, + Down, Left, Right, Home, PageUp, and PageDown; the activated() signal is + emitted if the current index is valid and the activation key is pressed + (e.g. Enter or Return, depending on the platform). + This function is where editing is initiated by key press, e.g. if F2 is + pressed. + + \sa edit(), moveCursor(), keyboardSearch(), tabKeyNavigation +*/ +void QAbstractItemView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QAbstractItemView); + d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling + +#ifdef QT_KEYPAD_NAVIGATION + switch (event->key()) { + case Qt::Key_Select: + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus()) { + setEditFocus(true); +#ifdef QT_SOFTKEYS_ENABLED + // If we can't keypad navigate to any direction, there is no sense to add + // "Done" softkey, since it basically does nothing when there is + // only one widget in screen + if(QWidgetPrivate::canKeypadNavigate(Qt::Horizontal) + || QWidgetPrivate::canKeypadNavigate(Qt::Vertical)) + addAction(d->doneSoftKey); +#endif + return; + } + } + break; + case Qt::Key_Back: + if (QApplication::keypadNavigationEnabled() && hasEditFocus()) { +#ifdef QT_SOFTKEYS_ENABLED + removeAction(d->doneSoftKey); +#endif + setEditFocus(false); + } else { + event->ignore(); + } + return; + case Qt::Key_Down: + case Qt::Key_Up: + // Let's ignore vertical navigation events, only if there is no other widget + // what can take the focus in vertical direction. This means widget can handle navigation events + // even the widget don't have edit focus, and there is no other widget in requested direction. + if(QApplication::keypadNavigationEnabled() && !hasEditFocus() + && QWidgetPrivate::canKeypadNavigate(Qt::Vertical)) { + event->ignore(); + return; + } + break; + case Qt::Key_Left: + case Qt::Key_Right: + // Similar logic as in up and down events + if(QApplication::keypadNavigationEnabled() && !hasEditFocus() + && (QWidgetPrivate::canKeypadNavigate(Qt::Horizontal) || QWidgetPrivate::inTabWidget(this))) { + event->ignore(); + return; + } + break; + default: + if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) { + event->ignore(); + return; + } + } +#endif + +#if !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) + if (event == QKeySequence::Copy) { + QVariant variant; + if (d->model) + variant = d->model->data(currentIndex(), Qt::DisplayRole); + if (variant.type() == QVariant::String) + QApplication::clipboard()->setText(variant.toString()); + event->accept(); + } +#endif + + QPersistentModelIndex newCurrent; + d->moveCursorUpdatedView = false; + switch (event->key()) { + case Qt::Key_Down: + newCurrent = moveCursor(MoveDown, event->modifiers()); + break; + case Qt::Key_Up: + newCurrent = moveCursor(MoveUp, event->modifiers()); + break; + case Qt::Key_Left: + newCurrent = moveCursor(MoveLeft, event->modifiers()); + break; + case Qt::Key_Right: + newCurrent = moveCursor(MoveRight, event->modifiers()); + break; + case Qt::Key_Home: + newCurrent = moveCursor(MoveHome, event->modifiers()); + break; + case Qt::Key_End: + newCurrent = moveCursor(MoveEnd, event->modifiers()); + break; + case Qt::Key_PageUp: + newCurrent = moveCursor(MovePageUp, event->modifiers()); + break; + case Qt::Key_PageDown: + newCurrent = moveCursor(MovePageDown, event->modifiers()); + break; + case Qt::Key_Tab: + if (d->tabKeyNavigation) + newCurrent = moveCursor(MoveNext, event->modifiers()); + break; + case Qt::Key_Backtab: + if (d->tabKeyNavigation) + newCurrent = moveCursor(MovePrevious, event->modifiers()); + break; + } + + QPersistentModelIndex oldCurrent = currentIndex(); + if (newCurrent != oldCurrent && newCurrent.isValid() && d->isIndexEnabled(newCurrent)) { + if (!hasFocus() && QApplication::focusWidget() == indexWidget(oldCurrent)) + setFocus(); + QItemSelectionModel::SelectionFlags command = selectionCommand(newCurrent, event); + if (command != QItemSelectionModel::NoUpdate + || style()->styleHint(QStyle::SH_ItemView_MovementWithoutUpdatingSelection, 0, this)) { + // note that we don't check if the new current index is enabled because moveCursor() makes sure it is + if (command & QItemSelectionModel::Current) { + d->selectionModel->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + if (!indexAt(d->pressedPosition - d->offset()).isValid()) + d->pressedPosition = visualRect(oldCurrent).center() + d->offset(); + QRect rect(d->pressedPosition - d->offset(), visualRect(newCurrent).center()); + setSelection(rect, command); + } else { + d->selectionModel->setCurrentIndex(newCurrent, command); + d->pressedPosition = visualRect(newCurrent).center() + d->offset(); + if (newCurrent.isValid()) { + // We copy the same behaviour as for mousePressEvent(). + QRect rect(d->pressedPosition - d->offset(), QSize(1, 1)); + setSelection(rect, command); + } + } + event->accept(); + return; + } + } + + switch (event->key()) { + // ignored keys + case Qt::Key_Down: + case Qt::Key_Up: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && QWidgetPrivate::canKeypadNavigate(Qt::Vertical)) { + event->accept(); // don't change focus + break; + } +#endif + case Qt::Key_Left: + case Qt::Key_Right: +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional + && (QWidgetPrivate::canKeypadNavigate(Qt::Horizontal) + || (QWidgetPrivate::inTabWidget(this) && d->model->columnCount(d->root) > 1))) { + event->accept(); // don't change focus + break; + } +#endif // QT_KEYPAD_NAVIGATION + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Escape: + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Delete: + case Qt::Key_Backspace: + event->ignore(); + break; + case Qt::Key_Space: + case Qt::Key_Select: + if (!edit(currentIndex(), AnyKeyPressed, event) && d->selectionModel) + d->selectionModel->select(currentIndex(), selectionCommand(currentIndex(), event)); +#ifdef QT_KEYPAD_NAVIGATION + if ( event->key()==Qt::Key_Select ) { + // Also do Key_Enter action. + if (currentIndex().isValid()) { + if (state() != EditingState) + emit activated(currentIndex()); + } else { + event->ignore(); + } + } +#endif + break; +#ifdef Q_WS_MAC + case Qt::Key_Enter: + case Qt::Key_Return: + // Propagate the enter if you couldn't edit the item and there are no + // current editors (if there are editors, the event was most likely propagated from it). + if (!edit(currentIndex(), EditKeyPressed, event) && d->editorIndexHash.isEmpty()) + event->ignore(); + break; +#else + case Qt::Key_F2: + if (!edit(currentIndex(), EditKeyPressed, event)) + event->ignore(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + // ### we can't open the editor on enter, becuse + // some widgets will forward the enter event back + // to the viewport, starting an endless loop + if (state() != EditingState || hasFocus()) { + if (currentIndex().isValid()) + emit activated(currentIndex()); + event->ignore(); + } + break; +#endif + case Qt::Key_A: + if (event->modifiers() & Qt::ControlModifier) { + selectAll(); + break; + } + default: { +#ifdef Q_WS_MAC + if (event->key() == Qt::Key_O && event->modifiers() & Qt::ControlModifier && currentIndex().isValid()) { + emit activated(currentIndex()); + break; + } +#endif + bool modified = (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)); + if (!event->text().isEmpty() && !modified && !edit(currentIndex(), AnyKeyPressed, event)) { + keyboardSearch(event->text()); + event->accept(); + } else { + event->ignore(); + } + break; } + } + if (d->moveCursorUpdatedView) + event->accept(); +} + +/*! + This function is called with the given \a event when a resize event is sent to + the widget. + + \sa QWidget::resizeEvent() +*/ +void QAbstractItemView::resizeEvent(QResizeEvent *event) +{ + QAbstractScrollArea::resizeEvent(event); + updateGeometries(); +} + +/*! + This function is called with the given \a event when a timer event is sent + to the widget. + + \sa QObject::timerEvent() +*/ +void QAbstractItemView::timerEvent(QTimerEvent *event) +{ + Q_D(QAbstractItemView); + if (event->timerId() == d->fetchMoreTimer.timerId()) + d->fetchMore(); + else if (event->timerId() == d->delayedReset.timerId()) + reset(); + else if (event->timerId() == d->autoScrollTimer.timerId()) + doAutoScroll(); + else if (event->timerId() == d->updateTimer.timerId()) + d->updateDirtyRegion(); + else if (event->timerId() == d->delayedEditing.timerId()) { + d->delayedEditing.stop(); + edit(currentIndex()); + } else if (event->timerId() == d->delayedLayout.timerId()) { + d->delayedLayout.stop(); + if (isVisible()) { + d->interruptDelayedItemsLayout(); + doItemsLayout(); + const QModelIndex current = currentIndex(); + if (current.isValid() && d->state == QAbstractItemView::EditingState) + scrollTo(current); + } + } else if (event->timerId() == d->delayedAutoScroll.timerId()) { + d->delayedAutoScroll.stop(); + //end of the timer: if the current item is still the same as the one when the mouse press occurred + //we only get here if there was no double click + if (d->pressedIndex.isValid() && d->pressedIndex == currentIndex()) + scrollTo(d->pressedIndex); + } +} + +/*! + \reimp +*/ +void QAbstractItemView::inputMethodEvent(QInputMethodEvent *event) +{ + if (event->commitString().isEmpty() && event->preeditString().isEmpty()) { + event->ignore(); + return; + } + if (!edit(currentIndex(), AnyKeyPressed, event)) { + if (!event->commitString().isEmpty()) + keyboardSearch(event->commitString()); + event->ignore(); + } +} + +#ifndef QT_NO_DRAGANDDROP +/*! + \enum QAbstractItemView::DropIndicatorPosition + + This enum indicates the position of the drop indicator in + relation to the index at the current mouse position: + + \value OnItem The item will be dropped on the index. + + \value AboveItem The item will be dropped above the index. + + \value BelowItem The item will be dropped below the index. + + \value OnViewport The item will be dropped onto a region of the viewport with + no items. The way each view handles items dropped onto the viewport depends on + the behavior of the underlying model in use. +*/ + + +/*! + \since 4.1 + + Returns the position of the drop indicator in relation to the closest item. +*/ +QAbstractItemView::DropIndicatorPosition QAbstractItemView::dropIndicatorPosition() const +{ + Q_D(const QAbstractItemView); + return d->dropIndicatorPosition; +} +#endif + +/*! + This convenience function returns a list of all selected and + non-hidden item indexes in the view. The list contains no + duplicates, and is not sorted. + + \sa QItemSelectionModel::selectedIndexes() +*/ +QModelIndexList QAbstractItemView::selectedIndexes() const +{ + Q_D(const QAbstractItemView); + QModelIndexList indexes; + if (d->selectionModel) { + indexes = d->selectionModel->selectedIndexes(); + QList::iterator it = indexes.begin(); + while (it != indexes.end()) + if (isIndexHidden(*it)) + it = indexes.erase(it); + else + ++it; + } + return indexes; +} + +/*! + Starts editing the item at \a index, creating an editor if + necessary, and returns true if the view's \l{State} is now + EditingState; otherwise returns false. + + The action that caused the editing process is described by + \a trigger, and the associated event is specified by \a event. + + Editing can be forced by specifying the \a trigger to be + QAbstractItemView::AllEditTriggers. + + \sa closeEditor() +*/ +bool QAbstractItemView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) +{ + Q_D(QAbstractItemView); + + if (!d->isIndexValid(index)) + return false; + + if (QWidget *w = (d->persistent.isEmpty() ? static_cast(0) : d->editorForIndex(index).widget.data())) { + if (w->focusPolicy() == Qt::NoFocus) + return false; + w->setFocus(); + return true; + } + + if (trigger == DoubleClicked) { + d->delayedEditing.stop(); + d->delayedAutoScroll.stop(); + } else if (trigger == CurrentChanged) { + d->delayedEditing.stop(); + } + + if (d->sendDelegateEvent(index, event)) { + update(index); + return true; + } + + // save the previous trigger before updating + EditTriggers lastTrigger = d->lastTrigger; + d->lastTrigger = trigger; + + if (!d->shouldEdit(trigger, d->model->buddy(index))) + return false; + + if (d->delayedEditing.isActive()) + return false; + + // we will receive a mouseButtonReleaseEvent after a + // mouseDoubleClickEvent, so we need to check the previous trigger + if (lastTrigger == DoubleClicked && trigger == SelectedClicked) + return false; + + // we may get a double click event later + if (trigger == SelectedClicked) + d->delayedEditing.start(QApplication::doubleClickInterval(), this); + else + d->openEditor(index, d->shouldForwardEvent(trigger, event) ? event : 0); + + return true; +} + +/*! + \internal + Updates the data shown in the open editor widgets in the view. +*/ +void QAbstractItemView::updateEditorData() +{ + Q_D(QAbstractItemView); + d->updateEditorData(QModelIndex(), QModelIndex()); +} + +/*! + \internal + Updates the geometry of the open editor widgets in the view. +*/ +void QAbstractItemView::updateEditorGeometries() +{ + Q_D(QAbstractItemView); + if(d->editorIndexHash.isEmpty()) + return; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QEditorIndexHash::iterator it = d->editorIndexHash.begin(); + QWidgetList editorsToRelease; + QWidgetList editorsToHide; + while (it != d->editorIndexHash.end()) { + QModelIndex index = it.value(); + QWidget *editor = it.key(); + if (index.isValid() && editor) { + option.rect = visualRect(index); + if (option.rect.isValid()) { + editor->show(); + QAbstractItemDelegate *delegate = d->delegateForIndex(index); + if (delegate) + delegate->updateEditorGeometry(editor, option, index); + } else { + editorsToHide << editor; + } + ++it; + } else { + d->indexEditorHash.remove(it.value()); + it = d->editorIndexHash.erase(it); + editorsToRelease << editor; + } + } + + //we hide and release the editor outside of the loop because it might change the focus and try + //to change the editors hashes. + for (int i = 0; i < editorsToHide.count(); ++i) { + editorsToHide.at(i)->hide(); + } + for (int i = 0; i < editorsToRelease.count(); ++i) { + d->releaseEditor(editorsToRelease.at(i)); + } +} + +/*! + \since 4.4 + + Updates the geometry of the child widgets of the view. +*/ +void QAbstractItemView::updateGeometries() +{ + updateEditorGeometries(); + d_func()->fetchMoreTimer.start(0, this); //fetch more later +} + +/*! + \internal +*/ +void QAbstractItemView::verticalScrollbarValueChanged(int value) +{ + Q_D(QAbstractItemView); + if (verticalScrollBar()->maximum() == value && d->model->canFetchMore(d->root)) + d->model->fetchMore(d->root); + QPoint posInVp = viewport()->mapFromGlobal(QCursor::pos()); + if (viewport()->rect().contains(posInVp)) + d->checkMouseMove(posInVp); +} + +/*! + \internal +*/ +void QAbstractItemView::horizontalScrollbarValueChanged(int value) +{ + Q_D(QAbstractItemView); + if (horizontalScrollBar()->maximum() == value && d->model->canFetchMore(d->root)) + d->model->fetchMore(d->root); + QPoint posInVp = viewport()->mapFromGlobal(QCursor::pos()); + if (viewport()->rect().contains(posInVp)) + d->checkMouseMove(posInVp); +} + +/*! + \internal +*/ +void QAbstractItemView::verticalScrollbarAction(int) +{ + //do nothing +} + +/*! + \internal +*/ +void QAbstractItemView::horizontalScrollbarAction(int) +{ + //do nothing +} + +/*! + Closes the given \a editor, and releases it. The \a hint is + used to specify how the view should respond to the end of the editing + operation. For example, the hint may indicate that the next item in + the view should be opened for editing. + + \sa edit(), commitData() +*/ + +void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) +{ + Q_D(QAbstractItemView); + + // Close the editor + if (editor) { + bool isPersistent = d->persistent.contains(editor); + bool hadFocus = editor->hasFocus(); + QModelIndex index = d->indexForEditor(editor); + if (!index.isValid()) + return; // the editor was not registered + + if (!isPersistent) { + setState(NoState); + QModelIndex index = d->indexForEditor(editor); + editor->removeEventFilter(d->delegateForIndex(index)); + d->removeEditor(editor); + } + if (hadFocus) + setFocus(); // this will send a focusLost event to the editor + else + d->checkPersistentEditorFocus(); + + QPointer ed = editor; + QApplication::sendPostedEvents(editor, 0); + editor = ed; + + if (!isPersistent && editor) + d->releaseEditor(editor); + } + + // The EndEditHint part + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect + | d->selectionBehaviorFlags(); + switch (hint) { + case QAbstractItemDelegate::EditNextItem: { + QModelIndex index = moveCursor(MoveNext, Qt::NoModifier); + if (index.isValid()) { + QPersistentModelIndex persistent(index); + d->selectionModel->setCurrentIndex(persistent, flags); + // currentChanged signal would have already started editing + if (index.flags() & Qt::ItemIsEditable + && (!(editTriggers() & QAbstractItemView::CurrentChanged))) + edit(persistent); + } break; } + case QAbstractItemDelegate::EditPreviousItem: { + QModelIndex index = moveCursor(MovePrevious, Qt::NoModifier); + if (index.isValid()) { + QPersistentModelIndex persistent(index); + d->selectionModel->setCurrentIndex(persistent, flags); + // currentChanged signal would have already started editing + if (index.flags() & Qt::ItemIsEditable + && (!(editTriggers() & QAbstractItemView::CurrentChanged))) + edit(persistent); + } break; } + case QAbstractItemDelegate::SubmitModelCache: + d->model->submit(); + break; + case QAbstractItemDelegate::RevertModelCache: + d->model->revert(); + break; + default: + break; + } +} + +/*! + Commit the data in the \a editor to the model. + + \sa closeEditor() +*/ +void QAbstractItemView::commitData(QWidget *editor) +{ + Q_D(QAbstractItemView); + if (!editor || !d->itemDelegate || d->currentlyCommittingEditor) + return; + QModelIndex index = d->indexForEditor(editor); + if (!index.isValid()) + return; + d->currentlyCommittingEditor = editor; + QAbstractItemDelegate *delegate = d->delegateForIndex(index); + editor->removeEventFilter(delegate); + delegate->setModelData(editor, d->model, index); + editor->installEventFilter(delegate); + d->currentlyCommittingEditor = 0; +} + +/*! + This function is called when the given \a editor has been destroyed. + + \sa closeEditor() +*/ +void QAbstractItemView::editorDestroyed(QObject *editor) +{ + Q_D(QAbstractItemView); + QWidget *w = qobject_cast(editor); + d->removeEditor(w); + d->persistent.remove(w); + if (state() == EditingState) + setState(NoState); +} + +/*! + \obsolete + Sets the horizontal scroll bar's steps per item to \a steps. + + This is the number of steps used by the horizontal scroll bar to + represent the width of an item. + + Note that if the view has a horizontal header, the item steps + will be ignored and the header section size will be used instead. + + \sa horizontalStepsPerItem() setVerticalStepsPerItem() +*/ +void QAbstractItemView::setHorizontalStepsPerItem(int steps) +{ + Q_UNUSED(steps); + // do nothing +} + +/*! + \obsolete + Returns the horizontal scroll bar's steps per item. + + \sa setHorizontalStepsPerItem() verticalStepsPerItem() +*/ +int QAbstractItemView::horizontalStepsPerItem() const +{ + return 1; +} + +/*! + \obsolete + Sets the vertical scroll bar's steps per item to \a steps. + + This is the number of steps used by the vertical scroll bar to + represent the height of an item. + + Note that if the view has a vertical header, the item steps + will be ignored and the header section size will be used instead. + + \sa verticalStepsPerItem() setHorizontalStepsPerItem() +*/ +void QAbstractItemView::setVerticalStepsPerItem(int steps) +{ + Q_UNUSED(steps); + // do nothing +} + +/*! + \obsolete + Returns the vertical scroll bar's steps per item. + + \sa setVerticalStepsPerItem() horizontalStepsPerItem() +*/ +int QAbstractItemView::verticalStepsPerItem() const +{ + return 1; +} + +/*! + Moves to and selects the item best matching the string \a search. + If no item is found nothing happens. + + In the default implementation, the search is reset if \a search is empty, or + the time interval since the last search has exceeded + QApplication::keyboardInputInterval(). +*/ +void QAbstractItemView::keyboardSearch(const QString &search) +{ + Q_D(QAbstractItemView); + if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) + return; + + QModelIndex start = currentIndex().isValid() ? currentIndex() + : d->model->index(0, 0, d->root); + bool skipRow = false; + bool keyboardTimeWasValid = d->keyboardInputTime.isValid(); + qint64 keyboardInputTimeElapsed = d->keyboardInputTime.restart(); + if (search.isEmpty() || !keyboardTimeWasValid + || keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) { + d->keyboardInput = search; + skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0) + } else { + d->keyboardInput += search; + } + + // special case for searches with same key like 'aaaaa' + bool sameKey = false; + if (d->keyboardInput.length() > 1) { + int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); + sameKey = (c == d->keyboardInput.length()); + if (sameKey) + skipRow = true; + } + + // skip if we are searching for the same key or a new search started + if (skipRow) { + QModelIndex parent = start.parent(); + int newRow = (start.row() < d->model->rowCount(parent) - 1) ? start.row() + 1 : 0; + start = d->model->index(newRow, start.column(), parent); + } + + // search from start with wraparound + const QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; + QModelIndex current = start; + QModelIndexList match; + QModelIndex firstMatch; + QModelIndex startMatch; + QModelIndexList previous; + do { + match = d->model->match(current, Qt::DisplayRole, searchString); + if (match == previous) + break; + firstMatch = match.value(0); + previous = match; + if (firstMatch.isValid()) { + if (d->isIndexEnabled(firstMatch)) { + setCurrentIndex(firstMatch); + break; + } + int row = firstMatch.row() + 1; + if (row >= d->model->rowCount(firstMatch.parent())) + row = 0; + current = firstMatch.sibling(row, firstMatch.column()); + + //avoid infinite loop if all the matching items are disabled. + if (!startMatch.isValid()) + startMatch = firstMatch; + else if (startMatch == firstMatch) + break; + } + } while (current != start && firstMatch.isValid()); +} + +/*! + Returns the size hint for the item with the specified \a index or + an invalid size for invalid indexes. + + \sa sizeHintForRow(), sizeHintForColumn() +*/ +QSize QAbstractItemView::sizeHintForIndex(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + if (!d->isIndexValid(index) || !d->itemDelegate) + return QSize(); + return d->delegateForIndex(index)->sizeHint(d->viewOptionsV4(), index); +} + +/*! + Returns the height size hint for the specified \a row or -1 if + there is no model. + + The returned height is calculated using the size hints of the + given \a row's items, i.e. the returned value is the maximum + height among the items. Note that to control the height of a row, + you must reimplement the QAbstractItemDelegate::sizeHint() + function. + + This function is used in views with a vertical header to find the + size hint for a header section based on the contents of the given + \a row. + + \sa sizeHintForColumn() +*/ +int QAbstractItemView::sizeHintForRow(int row) const +{ + Q_D(const QAbstractItemView); + + if (row < 0 || row >= d->model->rowCount(d->root)) + return -1; + + ensurePolished(); + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + int height = 0; + int colCount = d->model->columnCount(d->root); + QModelIndex index; + for (int c = 0; c < colCount; ++c) { + index = d->model->index(row, c, d->root); + if (QWidget *editor = d->editorForIndex(index).widget.data()) + height = qMax(height, editor->height()); + int hint = d->delegateForIndex(index)->sizeHint(option, index).height(); + height = qMax(height, hint); + } + return height; +} + +/*! + Returns the width size hint for the specified \a column or -1 if there is no model. + + This function is used in views with a horizontal header to find the size hint for + a header section based on the contents of the given \a column. + + \sa sizeHintForRow() +*/ +int QAbstractItemView::sizeHintForColumn(int column) const +{ + Q_D(const QAbstractItemView); + + if (column < 0 || column >= d->model->columnCount(d->root)) + return -1; + + ensurePolished(); + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + int width = 0; + int rows = d->model->rowCount(d->root); + QModelIndex index; + for (int r = 0; r < rows; ++r) { + index = d->model->index(r, column, d->root); + if (QWidget *editor = d->editorForIndex(index).widget.data()) + width = qMax(width, editor->sizeHint().width()); + int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); + width = qMax(width, hint); + } + return width; +} + +/*! + Opens a persistent editor on the item at the given \a index. + If no editor exists, the delegate will create a new editor. + + \sa closePersistentEditor() +*/ +void QAbstractItemView::openPersistentEditor(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + QStyleOptionViewItemV4 options = d->viewOptionsV4(); + options.rect = visualRect(index); + options.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + + QWidget *editor = d->editor(index, options); + if (editor) { + editor->show(); + d->persistent.insert(editor); + } +} + +/*! + Closes the persistent editor for the item at the given \a index. + + \sa openPersistentEditor() +*/ +void QAbstractItemView::closePersistentEditor(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (QWidget *editor = d->editorForIndex(index).widget.data()) { + if (index == selectionModel()->currentIndex()) + closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + d->persistent.remove(editor); + d->removeEditor(editor); + d->releaseEditor(editor); + } +} + +/*! + \since 4.1 + + Sets the given \a widget on the item at the given \a index, passing the + ownership of the widget to the viewport. + + If \a index is invalid (e.g., if you pass the root index), this function + will do nothing. + + The given \a widget's \l{QWidget}{autoFillBackground} property must be set + to true, otherwise the widget's background will be transparent, showing + both the model data and the item at the given \a index. + + If index widget A is replaced with index widget B, index widget A will be + deleted. For example, in the code snippet below, the QLineEdit object will + be deleted. + + \snippet doc/src/snippets/code/src_gui_itemviews_qabstractitemview.cpp 1 + + This function should only be used to display static content within the + visible area corresponding to an item of data. If you want to display + custom dynamic content or implement a custom editor widget, subclass + QItemDelegate instead. + + \sa {Delegate Classes} +*/ +void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget) +{ + Q_D(QAbstractItemView); + if (!d->isIndexValid(index)) + return; + if (QWidget *oldWidget = indexWidget(index)) { + d->persistent.remove(oldWidget); + d->removeEditor(oldWidget); + oldWidget->deleteLater(); + } + if (widget) { + widget->setParent(viewport()); + d->persistent.insert(widget); + d->addEditor(index, widget, true); + widget->show(); + dataChanged(index, index); // update the geometry + if (!d->delayedPendingLayout) + widget->setGeometry(visualRect(index)); + } +} + +/*! + \since 4.1 + + Returns the widget for the item at the given \a index. +*/ +QWidget* QAbstractItemView::indexWidget(const QModelIndex &index) const +{ + Q_D(const QAbstractItemView); + if (d->isIndexValid(index)) + if (QWidget *editor = d->editorForIndex(index).widget.data()) + return editor; + + return 0; +} + +/*! + \since 4.1 + + Scrolls the view to the top. + + \sa scrollTo(), scrollToBottom() +*/ +void QAbstractItemView::scrollToTop() +{ + verticalScrollBar()->setValue(verticalScrollBar()->minimum()); +} + +/*! + \since 4.1 + + Scrolls the view to the bottom. + + \sa scrollTo(), scrollToTop() +*/ +void QAbstractItemView::scrollToBottom() +{ + Q_D(QAbstractItemView); + if (d->delayedPendingLayout) { + d->executePostedLayout(); + updateGeometries(); + } + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); +} + +/*! + \since 4.3 + + Updates the area occupied by the given \a index. + +*/ +void QAbstractItemView::update(const QModelIndex &index) +{ + Q_D(QAbstractItemView); + if (index.isValid()) { + const QRect rect = visualRect(index); + //this test is important for peformance reason + //For example in dataChanged we simply update all the cells without checking + //it can be a major bottleneck to update rects that aren't even part of the viewport + if (d->viewport->rect().intersects(rect)) + d->viewport->update(rect); + } +} + +/*! + This slot is called when items are changed in the model. The + changed items are those from \a topLeft to \a bottomRight + inclusive. If just one item is changed \a topLeft == \a + bottomRight. +*/ +void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + // Single item changed + Q_D(QAbstractItemView); + if (topLeft == bottomRight && topLeft.isValid()) { + const QEditorInfo &editorInfo = d->editorForIndex(topLeft); + //we don't update the edit data if it is static + if (!editorInfo.isStatic && editorInfo.widget) { + QAbstractItemDelegate *delegate = d->delegateForIndex(topLeft); + if (delegate) { + delegate->setEditorData(editorInfo.widget.data(), topLeft); + } + } + if (isVisible() && !d->delayedPendingLayout) { + // otherwise the items will be update later anyway + update(topLeft); + } + return; + } + d->updateEditorData(topLeft, bottomRight); + if (!isVisible() || d->delayedPendingLayout) + return; // no need to update + d->viewport->update(); +} + +/*! + This slot is called when rows are inserted. The new rows are those + under the given \a parent from \a start to \a end inclusive. The + base class implementation calls fetchMore() on the model to check + for more data. + + \sa rowsAboutToBeRemoved() +*/ +void QAbstractItemView::rowsInserted(const QModelIndex &, int, int) +{ + if (!isVisible()) + d_func()->fetchMoreTimer.start(0, this); //fetch more later + else + updateEditorGeometries(); +} + +/*! + This slot is called when rows are about to be removed. The deleted rows are + those under the given \a parent from \a start to \a end inclusive. + + \sa rowsInserted() +*/ +void QAbstractItemView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QAbstractItemView); + + setState(CollapsingState); + + // Ensure one selected item in single selection mode. + QModelIndex current = currentIndex(); + if (d->selectionMode == SingleSelection + && current.isValid() + && current.row() >= start + && current.row() <= end + && current.parent() == parent) { + int totalToRemove = end - start + 1; + if (d->model->rowCount(parent) <= totalToRemove) { // no more children + QModelIndex index = parent; + while (index != d->root && !d->isIndexEnabled(index)) + index = index.parent(); + if (index != d->root) + setCurrentIndex(index); + } else { + int row = end + 1; + QModelIndex next; + do { // find the next visible and enabled item + next = d->model->index(row++, current.column(), current.parent()); + } while (next.isValid() && (isIndexHidden(next) || !d->isIndexEnabled(next))); + if (row > d->model->rowCount(parent)) { + row = start - 1; + do { // find the previous visible and enabled item + next = d->model->index(row--, current.column(), current.parent()); + } while (next.isValid() && (isIndexHidden(next) || !d->isIndexEnabled(next))); + } + setCurrentIndex(next); + } + } + + // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes + QEditorIndexHash::iterator i = d->editorIndexHash.begin(); + while (i != d->editorIndexHash.end()) { + const QModelIndex index = i.value(); + if (index.row() >= start && index.row() <= end && d->model->parent(index) == parent) { + QWidget *editor = i.key(); + QEditorInfo info = d->indexEditorHash.take(index); + i = d->editorIndexHash.erase(i); + if (info.widget) + d->releaseEditor(editor); + } else { + ++i; + } + } +} + +/*! + \internal + + This slot is called when rows have been removed. The deleted + rows are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_rowsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); + q->setState(QAbstractItemView::NoState); +} + +/*! + \internal + + This slot is called when columns are about to be removed. The deleted + columns are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_Q(QAbstractItemView); + + q->setState(QAbstractItemView::CollapsingState); + + // Ensure one selected item in single selection mode. + QModelIndex current = q->currentIndex(); + if (current.isValid() + && selectionMode == QAbstractItemView::SingleSelection + && current.column() >= start + && current.column() <= end) { + int totalToRemove = end - start + 1; + if (model->columnCount(parent) < totalToRemove) { // no more columns + QModelIndex index = parent; + while (index.isValid() && !isIndexEnabled(index)) + index = index.parent(); + if (index.isValid()) + q->setCurrentIndex(index); + } else { + int column = end; + QModelIndex next; + do { // find the next visible and enabled item + next = model->index(current.row(), column++, current.parent()); + } while (next.isValid() && (q->isIndexHidden(next) || !isIndexEnabled(next))); + q->setCurrentIndex(next); + } + } + + // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes + QEditorIndexHash::iterator it = editorIndexHash.begin(); + while (it != editorIndexHash.end()) { + QModelIndex index = it.value(); + if (index.column() <= start && index.column() >= end && model->parent(index) == parent) { + QWidget *editor = it.key(); + QEditorInfo info = indexEditorHash.take(it.value()); + it = editorIndexHash.erase(it); + if (info.widget) + releaseEditor(editor); + } else { + ++it; + } + } + +} + +/*! + \internal + + This slot is called when columns have been removed. The deleted + rows are those under the given \a parent from \a start to \a end + inclusive. +*/ +void QAbstractItemViewPrivate::_q_columnsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); + q->setState(QAbstractItemView::NoState); +} + +/*! + \internal + + This slot is called when rows have been inserted. +*/ +void QAbstractItemViewPrivate::_q_columnsInserted(const QModelIndex &, int, int) +{ + Q_Q(QAbstractItemView); + if (q->isVisible()) + q->updateEditorGeometries(); +} + + + +/*! + \internal +*/ +void QAbstractItemViewPrivate::_q_modelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); + doDelayedReset(); +} + +/*! + \internal + + This slot is called when the layout is changed. +*/ +void QAbstractItemViewPrivate::_q_layoutChanged() +{ + doDelayedItemsLayout(); +} + +/*! + This slot is called when the selection is changed. The previous + selection (which may be empty), is specified by \a deselected, and the + new selection by \a selected. + + \sa setSelection() +*/ +void QAbstractItemView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ + Q_D(QAbstractItemView); + if (isVisible() && updatesEnabled()) { + d->viewport->update(visualRegionForSelection(deselected) | visualRegionForSelection(selected)); + } +} + +/*! + This slot is called when a new item becomes the current item. + The previous current item is specified by the \a previous index, and the new + item by the \a current index. + + If you want to know about changes to items see the + dataChanged() signal. +*/ +void QAbstractItemView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_D(QAbstractItemView); + Q_ASSERT(d->model); + + if (previous.isValid()) { + QModelIndex buddy = d->model->buddy(previous); + QWidget *editor = d->editorForIndex(buddy).widget.data(); + if (editor && !d->persistent.contains(editor)) { + commitData(editor); + if (current.row() != previous.row()) + closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + else + closeEditor(editor, QAbstractItemDelegate::NoHint); + } + if (isVisible()) { + update(previous); + } + } + + if (current.isValid() && !d->autoScrollTimer.isActive()) { + if (isVisible()) { + if (d->autoScroll) + scrollTo(current); + update(current); + edit(current, CurrentChanged, 0); + if (current.row() == (d->model->rowCount(d->root) - 1)) + d->fetchMore(); + } else { + d->shouldScrollToCurrentOnShow = d->autoScroll; + } + } +} + +#ifndef QT_NO_DRAGANDDROP +/*! + Starts a drag by calling drag->exec() using the given \a supportedActions. +*/ +void QAbstractItemView::startDrag(Qt::DropActions supportedActions) +{ + Q_D(QAbstractItemView); + QModelIndexList indexes = d->selectedDraggableIndexes(); + if (indexes.count() > 0) { + QMimeData *data = d->model->mimeData(indexes); + if (!data) + return; + QRect rect; + QPixmap pixmap = d->renderToPixmap(indexes, &rect); + rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + QDrag *drag = new QDrag(this); + drag->setPixmap(pixmap); + drag->setMimeData(data); + drag->setHotSpot(d->pressedPosition - rect.topLeft()); + Qt::DropAction defaultDropAction = Qt::IgnoreAction; + if (d->defaultDropAction != Qt::IgnoreAction && (supportedActions & d->defaultDropAction)) + defaultDropAction = d->defaultDropAction; + else if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove) + defaultDropAction = Qt::CopyAction; + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) + d->clearOrRemove(); + } +} +#endif // QT_NO_DRAGANDDROP + +/*! + Returns a QStyleOptionViewItem structure populated with the view's + palette, font, state, alignments etc. +*/ +QStyleOptionViewItem QAbstractItemView::viewOptions() const +{ + Q_D(const QAbstractItemView); + QStyleOptionViewItem option; + option.init(this); + option.state &= ~QStyle::State_MouseOver; + option.font = font(); + +#ifndef Q_WS_MAC + // On mac the focus appearance follows window activation + // not widget activation + if (!hasFocus()) + option.state &= ~QStyle::State_Active; +#endif + + option.state &= ~QStyle::State_HasFocus; + if (d->iconSize.isValid()) { + option.decorationSize = d->iconSize; + } else { + int pm = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this); + option.decorationSize = QSize(pm, pm); + } + option.decorationPosition = QStyleOptionViewItem::Left; + option.decorationAlignment = Qt::AlignCenter; + option.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; + option.textElideMode = d->textElideMode; + option.rect = QRect(); + option.showDecorationSelected = style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, this); + return option; +} + +QStyleOptionViewItemV4 QAbstractItemViewPrivate::viewOptionsV4() const +{ + Q_Q(const QAbstractItemView); + QStyleOptionViewItemV4 option = q->viewOptions(); + if (wrapItemText) + option.features = QStyleOptionViewItemV2::WrapText; + option.locale = q->locale(); + option.locale.setNumberOptions(QLocale::OmitGroupSeparator); + option.widget = q; + return option; +} + +/*! + Returns the item view's state. + + \sa setState() +*/ +QAbstractItemView::State QAbstractItemView::state() const +{ + Q_D(const QAbstractItemView); + return d->state; +} + +/*! + Sets the item view's state to the given \a state. + + \sa state() +*/ +void QAbstractItemView::setState(State state) +{ + Q_D(QAbstractItemView); + d->state = state; +} + +/*! + Schedules a layout of the items in the view to be executed when the + event processing starts. + + Even if scheduleDelayedItemsLayout() is called multiple times before + events are processed, the view will only do the layout once. + + \sa executeDelayedItemsLayout() +*/ +void QAbstractItemView::scheduleDelayedItemsLayout() +{ + Q_D(QAbstractItemView); + d->doDelayedItemsLayout(); +} + +/*! + Executes the scheduled layouts without waiting for the event processing + to begin. + + \sa scheduleDelayedItemsLayout() +*/ +void QAbstractItemView::executeDelayedItemsLayout() +{ + Q_D(QAbstractItemView); + d->executePostedLayout(); +} + +/*! + \since 4.1 + + Marks the given \a region as dirty and schedules it to be updated. + You only need to call this function if you are implementing + your own view subclass. + + \sa scrollDirtyRegion(), dirtyRegionOffset() +*/ + +void QAbstractItemView::setDirtyRegion(const QRegion ®ion) +{ + Q_D(QAbstractItemView); + d->setDirtyRegion(region); +} + +/*! + Prepares the view for scrolling by (\a{dx},\a{dy}) pixels by moving the dirty regions in the + opposite direction. You only need to call this function if you are implementing a scrolling + viewport in your view subclass. + + If you implement scrollContentsBy() in a subclass of QAbstractItemView, call this function + before you call QWidget::scroll() on the viewport. Alternatively, just call update(). + + \sa scrollContentsBy(), dirtyRegionOffset(), setDirtyRegion() +*/ +void QAbstractItemView::scrollDirtyRegion(int dx, int dy) +{ + Q_D(QAbstractItemView); + d->scrollDirtyRegion(dx, dy); +} + +/*! + Returns the offset of the dirty regions in the view. + + If you use scrollDirtyRegion() and implement a paintEvent() in a subclass of + QAbstractItemView, you should translate the area given by the paint event with + the offset returned from this function. + + \sa scrollDirtyRegion(), setDirtyRegion() +*/ +QPoint QAbstractItemView::dirtyRegionOffset() const +{ + Q_D(const QAbstractItemView); + return d->scrollDelayOffset; +} + +/*! + \internal +*/ +void QAbstractItemView::startAutoScroll() +{ + d_func()->startAutoScroll(); +} + +/*! + \internal +*/ +void QAbstractItemView::stopAutoScroll() +{ + d_func()->stopAutoScroll(); +} + +/*! + \internal +*/ +void QAbstractItemView::doAutoScroll() +{ + // find how much we should scroll with + Q_D(QAbstractItemView); + int verticalStep = verticalScrollBar()->pageStep(); + int horizontalStep = horizontalScrollBar()->pageStep(); + if (d->autoScrollCount < qMax(verticalStep, horizontalStep)) + ++d->autoScrollCount; + + int margin = d->autoScrollMargin; + int verticalValue = verticalScrollBar()->value(); + int horizontalValue = horizontalScrollBar()->value(); + + QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); + QRect area = static_cast(d->viewport)->d_func()->clipRect(); // access QWidget private by bending C++ rules + + // do the scrolling if we are in the scroll margins + if (pos.y() - area.top() < margin) + verticalScrollBar()->setValue(verticalValue - d->autoScrollCount); + else if (area.bottom() - pos.y() < margin) + verticalScrollBar()->setValue(verticalValue + d->autoScrollCount); + if (pos.x() - area.left() < margin) + horizontalScrollBar()->setValue(horizontalValue - d->autoScrollCount); + else if (area.right() - pos.x() < margin) + horizontalScrollBar()->setValue(horizontalValue + d->autoScrollCount); + // if nothing changed, stop scrolling + bool verticalUnchanged = (verticalValue == verticalScrollBar()->value()); + bool horizontalUnchanged = (horizontalValue == horizontalScrollBar()->value()); + if (verticalUnchanged && horizontalUnchanged) { + stopAutoScroll(); + } else { +#ifndef QT_NO_DRAGANDDROP + d->dropIndicatorRect = QRect(); + d->dropIndicatorPosition = QAbstractItemView::OnViewport; +#endif + d->viewport->update(); + } +} + +/*! + Returns the SelectionFlags to be used when updating a selection with + to include the \a index specified. The \a event is a user input event, + such as a mouse or keyboard event. + + Reimplement this function to define your own selection behavior. + + \sa setSelection() +*/ +QItemSelectionModel::SelectionFlags QAbstractItemView::selectionCommand(const QModelIndex &index, + const QEvent *event) const +{ + Q_D(const QAbstractItemView); + switch (d->selectionMode) { + case NoSelection: // Never update selection model + return QItemSelectionModel::NoUpdate; + case SingleSelection: // ClearAndSelect on valid index otherwise NoUpdate + if (event && event->type() == QEvent::MouseButtonRelease) + return QItemSelectionModel::NoUpdate; + return QItemSelectionModel::ClearAndSelect|d->selectionBehaviorFlags(); + case MultiSelection: + return d->multiSelectionCommand(index, event); + case ExtendedSelection: + return d->extendedSelectionCommand(index, event); + case ContiguousSelection: + return d->contiguousSelectionCommand(index, event); + } + return QItemSelectionModel::NoUpdate; +} + +QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::multiSelectionCommand( + const QModelIndex &index, const QEvent *event) const +{ + Q_UNUSED(index); + + if (event) { + switch (event->type()) { + case QEvent::KeyPress: + if (static_cast(event)->key() == Qt::Key_Space + || static_cast(event)->key() == Qt::Key_Select) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + break; + case QEvent::MouseButtonPress: + if (static_cast(event)->button() == Qt::LeftButton) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); // toggle + break; + case QEvent::MouseButtonRelease: + if (static_cast(event)->button() == Qt::LeftButton) + return QItemSelectionModel::NoUpdate|selectionBehaviorFlags(); // finalize + break; + case QEvent::MouseMove: + if (static_cast(event)->buttons() & Qt::LeftButton) + return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); // toggle drag select + default: + break; + } + return QItemSelectionModel::NoUpdate; + } + + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); +} + +QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::extendedSelectionCommand( + const QModelIndex &index, const QEvent *event) const +{ + Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); + if (event) { + switch (event->type()) { + case QEvent::MouseMove: { + // Toggle on MouseMove + modifiers = static_cast(event)->modifiers(); + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); + break; + } + case QEvent::MouseButtonPress: { + modifiers = static_cast(event)->modifiers(); + const Qt::MouseButton button = static_cast(event)->button(); + const bool rightButtonPressed = button & Qt::RightButton; + const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; + const bool controlKeyPressed = modifiers & Qt::ControlModifier; + const bool indexIsSelected = selectionModel->isSelected(index); + if ((shiftKeyPressed || controlKeyPressed) && rightButtonPressed) + return QItemSelectionModel::NoUpdate; + if (!shiftKeyPressed && !controlKeyPressed && indexIsSelected) + return QItemSelectionModel::NoUpdate; + if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) + return QItemSelectionModel::Clear; + if (!index.isValid()) + return QItemSelectionModel::NoUpdate; + break; + } + case QEvent::MouseButtonRelease: { + // ClearAndSelect on MouseButtonRelease if MouseButtonPress on selected item or empty area + modifiers = static_cast(event)->modifiers(); + const Qt::MouseButton button = static_cast(event)->button(); + const bool rightButtonPressed = button & Qt::RightButton; + const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; + const bool controlKeyPressed = modifiers & Qt::ControlModifier; + if (((index == pressedIndex && selectionModel->isSelected(index)) + || !index.isValid()) && state != QAbstractItemView::DragSelectingState + && !shiftKeyPressed && !controlKeyPressed && (!rightButtonPressed || !index.isValid())) + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); + return QItemSelectionModel::NoUpdate; + } + case QEvent::KeyPress: { + // NoUpdate on Key movement and Ctrl + modifiers = static_cast(event)->modifiers(); + switch (static_cast(event)->key()) { + case Qt::Key_Backtab: + modifiers = modifiers & ~Qt::ShiftModifier; // special case for backtab + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Tab: + if (modifiers & Qt::ControlModifier +#ifdef QT_KEYPAD_NAVIGATION + // Preserve historical tab order navigation behavior + || QApplication::navigationMode() == Qt::NavigationModeKeypadTabOrder +#endif + ) + return QItemSelectionModel::NoUpdate; + break; + case Qt::Key_Select: + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + case Qt::Key_Space:// Toggle on Ctrl-Qt::Key_Space, Select on Space + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + return QItemSelectionModel::Select|selectionBehaviorFlags(); + default: + break; + } + } + default: + break; + } + } + + if (modifiers & Qt::ShiftModifier) + return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + if (modifiers & Qt::ControlModifier) + return QItemSelectionModel::Toggle|selectionBehaviorFlags(); + if (state == QAbstractItemView::DragSelectingState) { + //when drag-selecting we need to clear any previous selection and select the current one + return QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + } + + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); +} + +QItemSelectionModel::SelectionFlags +QAbstractItemViewPrivate::contiguousSelectionCommand(const QModelIndex &index, + const QEvent *event) const +{ + QItemSelectionModel::SelectionFlags flags = extendedSelectionCommand(index, event); + const int Mask = QItemSelectionModel::Clear | QItemSelectionModel::Select + | QItemSelectionModel::Deselect | QItemSelectionModel::Toggle + | QItemSelectionModel::Current; + + switch (flags & Mask) { + case QItemSelectionModel::Clear: + case QItemSelectionModel::ClearAndSelect: + case QItemSelectionModel::SelectCurrent: + return flags; + case QItemSelectionModel::NoUpdate: + if (event && + (event->type() == QEvent::MouseButtonPress + || event->type() == QEvent::MouseButtonRelease)) + return flags; + return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); + default: + return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); + } +} + +void QAbstractItemViewPrivate::fetchMore() +{ + fetchMoreTimer.stop(); + if (!model->canFetchMore(root)) + return; + int last = model->rowCount(root) - 1; + if (last < 0) { + model->fetchMore(root); + return; + } + + QModelIndex index = model->index(last, 0, root); + QRect rect = q_func()->visualRect(index); + if (viewport->rect().intersects(rect)) + model->fetchMore(root); +} + +bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger, + const QModelIndex &index) const +{ + if (!index.isValid()) + return false; + Qt::ItemFlags flags = model->flags(index); + if (((flags & Qt::ItemIsEditable) == 0) || ((flags & Qt::ItemIsEnabled) == 0)) + return false; + if (state == QAbstractItemView::EditingState) + return false; + if (hasEditor(index)) + return false; + if (trigger == QAbstractItemView::AllEditTriggers) // force editing + return true; + if ((trigger & editTriggers) == QAbstractItemView::SelectedClicked + && !selectionModel->isSelected(index)) + return false; + return (trigger & editTriggers); +} + +bool QAbstractItemViewPrivate::shouldForwardEvent(QAbstractItemView::EditTrigger trigger, + const QEvent *event) const +{ + if (!event || (trigger & editTriggers) != QAbstractItemView::AnyKeyPressed) + return false; + + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + return true; + default: + break; + }; + + return false; +} + +bool QAbstractItemViewPrivate::shouldAutoScroll(const QPoint &pos) const +{ + if (!autoScroll) + return false; + QRect area = static_cast(viewport)->d_func()->clipRect(); // access QWidget private by bending C++ rules + return (pos.y() - area.top() < autoScrollMargin) + || (area.bottom() - pos.y() < autoScrollMargin) + || (pos.x() - area.left() < autoScrollMargin) + || (area.right() - pos.x() < autoScrollMargin); +} + +void QAbstractItemViewPrivate::doDelayedItemsLayout(int delay) +{ + if (!delayedPendingLayout) { + delayedPendingLayout = true; + delayedLayout.start(delay, q_func()); + } +} + +void QAbstractItemViewPrivate::interruptDelayedItemsLayout() const +{ + delayedLayout.stop(); + delayedPendingLayout = false; +} + + + +QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index, + const QStyleOptionViewItem &options) +{ + Q_Q(QAbstractItemView); + QWidget *w = editorForIndex(index).widget.data(); + if (!w) { + QAbstractItemDelegate *delegate = delegateForIndex(index); + if (!delegate) + return 0; + w = delegate->createEditor(viewport, options, index); + if (w) { + w->installEventFilter(delegate); + QObject::connect(w, SIGNAL(destroyed(QObject*)), q, SLOT(editorDestroyed(QObject*))); + delegate->updateEditorGeometry(w, options, index); + delegate->setEditorData(w, index); + addEditor(index, w, false); + if (w->parent() == viewport) + QWidget::setTabOrder(q, w); + + // Special cases for some editors containing QLineEdit + QWidget *focusWidget = w; + while (QWidget *fp = focusWidget->focusProxy()) + focusWidget = fp; +#ifndef QT_NO_LINEEDIT + if (QLineEdit *le = qobject_cast(focusWidget)) + le->selectAll(); +#endif +#ifndef QT_NO_SPINBOX + if (QSpinBox *sb = qobject_cast(focusWidget)) + sb->selectAll(); + else if (QDoubleSpinBox *dsb = qobject_cast(focusWidget)) + dsb->selectAll(); +#endif + } + } + + return w; +} + +void QAbstractItemViewPrivate::updateEditorData(const QModelIndex &tl, const QModelIndex &br) +{ + // we are counting on having relatively few editors + const bool checkIndexes = tl.isValid() && br.isValid(); + const QModelIndex parent = tl.parent(); + QIndexEditorHash::const_iterator it = indexEditorHash.constBegin(); + for (; it != indexEditorHash.constEnd(); ++it) { + QWidget *editor = it.value().widget.data(); + const QModelIndex index = it.key(); + if (it.value().isStatic || !editor || !index.isValid() || + (checkIndexes + && (index.row() < tl.row() || index.row() > br.row() + || index.column() < tl.column() || index.column() > br.column() + || index.parent() != parent))) + continue; + + QAbstractItemDelegate *delegate = delegateForIndex(index); + if (delegate) { + delegate->setEditorData(editor, index); + } + } +} + +/*! + \internal + + In DND if something has been moved then this is called. + Typically this means you should "remove" the selected item or row, + but the behavior is view dependant (table just clears the selected indexes for example). + + Either remove the selected rows or clear them +*/ +void QAbstractItemViewPrivate::clearOrRemove() +{ +#ifndef QT_NO_DRAGANDDROP + const QItemSelection selection = selectionModel->selection(); + QList::const_iterator it = selection.constBegin(); + + if (!overwrite) { + for (; it != selection.constEnd(); ++it) { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) + continue; + if ((*it).right() != (model->columnCount(parent) - 1)) + continue; + int count = (*it).bottom() - (*it).top() + 1; + model->removeRows((*it).top(), count, parent); + } + } else { + // we can't remove the rows so reset the items (i.e. the view is like a table) + QModelIndexList list = selection.indexes(); + for (int i=0; i < list.size(); ++i) { + QModelIndex index = list.at(i); + QMap roles = model->itemData(index); + for (QMap::Iterator it = roles.begin(); it != roles.end(); ++it) + it.value() = QVariant(); + model->setItemData(index, roles); + } + } +#endif +} + +/*! + \internal + + When persistent aeditor gets/loses focus, we need to check + and setcorrectly the current index. +*/ +void QAbstractItemViewPrivate::checkPersistentEditorFocus() +{ + Q_Q(QAbstractItemView); + if (QWidget *widget = QApplication::focusWidget()) { + if (persistent.contains(widget)) { + //a persistent editor has gained the focus + QModelIndex index = indexForEditor(widget); + if (selectionModel->currentIndex() != index) + q->setCurrentIndex(index); + } + } +} + + +const QEditorInfo & QAbstractItemViewPrivate::editorForIndex(const QModelIndex &index) const +{ + static QEditorInfo nullInfo; + + QIndexEditorHash::const_iterator it = indexEditorHash.find(index); + if (it == indexEditorHash.end()) + return nullInfo; + + return it.value(); +} + +QModelIndex QAbstractItemViewPrivate::indexForEditor(QWidget *editor) const +{ + QEditorIndexHash::const_iterator it = editorIndexHash.find(editor); + if (it == editorIndexHash.end()) + return QModelIndex(); + + return it.value(); +} + +void QAbstractItemViewPrivate::removeEditor(QWidget *editor) +{ + QEditorIndexHash::iterator it = editorIndexHash.find(editor); + if (it != editorIndexHash.end()) + { + indexEditorHash.remove(it.value()); + editorIndexHash.erase(it); + } +} + +void QAbstractItemViewPrivate::addEditor(const QModelIndex &index, QWidget *editor, bool isStatic) +{ + editorIndexHash.insert(editor, index); + indexEditorHash.insert(index, QEditorInfo(editor, isStatic)); +} + +bool QAbstractItemViewPrivate::sendDelegateEvent(const QModelIndex &index, QEvent *event) const +{ + Q_Q(const QAbstractItemView); + QModelIndex buddy = model->buddy(index); + QStyleOptionViewItemV4 options = viewOptionsV4(); + options.rect = q->visualRect(buddy); + options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + QAbstractItemDelegate *delegate = delegateForIndex(index); + return (event && delegate && delegate->editorEvent(event, model, options, buddy)); +} + +bool QAbstractItemViewPrivate::openEditor(const QModelIndex &index, QEvent *event) +{ + Q_Q(QAbstractItemView); + + QModelIndex buddy = model->buddy(index); + QStyleOptionViewItemV4 options = viewOptionsV4(); + options.rect = q->visualRect(buddy); + options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); + + QWidget *w = editor(buddy, options); + if (!w) + return false; + + q->setState(QAbstractItemView::EditingState); + w->show(); + w->setFocus(); + + if (event) + QApplication::sendEvent(w->focusProxy() ? w->focusProxy() : w, event); + + return true; +} + +/* + \internal + + returns the pair QRect/QModelIndex that should be painted on the viewports's rect +*/ + +QItemViewPaintPairs QAbstractItemViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const +{ + Q_ASSERT(r); + Q_Q(const QAbstractItemView); + QRect &rect = *r; + const QRect viewportRect = viewport->rect(); + QItemViewPaintPairs ret; + for (int i = 0; i < indexes.count(); ++i) { + const QModelIndex &index = indexes.at(i); + const QRect current = q->visualRect(index); + if (current.intersects(viewportRect)) { + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + +QPixmap QAbstractItemViewPrivate::renderToPixmap(const QModelIndexList &indexes, QRect *r) const +{ + Q_ASSERT(r); + QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); + if (paintPairs.isEmpty()) + return QPixmap(); + QPixmap pixmap(r->size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QStyleOptionViewItemV4 option = viewOptionsV4(); + option.state |= QStyle::State_Selected; + for (int j = 0; j < paintPairs.count(); ++j) { + option.rect = paintPairs.at(j).first.translated(-r->topLeft()); + const QModelIndex ¤t = paintPairs.at(j).second; + delegateForIndex(current)->paint(&painter, option, current); + } + return pixmap; +} + +void QAbstractItemViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) +{ + if (!selectionModel) + return; + + QItemSelection selection; + QModelIndex tl = model->index(0, 0, root); + QModelIndex br = model->index(model->rowCount(root) - 1, + model->columnCount(root) - 1, + root); + selection.append(QItemSelectionRange(tl, br)); + selectionModel->select(selection, command); +} + +QModelIndexList QAbstractItemViewPrivate::selectedDraggableIndexes() const +{ + Q_Q(const QAbstractItemView); + QModelIndexList indexes = q->selectedIndexes(); + for(int i = indexes.count() - 1 ; i >= 0; --i) { + if (!isIndexDragEnabled(indexes.at(i))) + indexes.removeAt(i); + } + return indexes; +} + + +QT_END_NAMESPACE + +#include "moc_qabstractitemview.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qabstractitemview.h b/src/gui/itemviews/qabstractitemview.h new file mode 100644 index 0000000000..f11f209010 --- /dev/null +++ b/src/gui/itemviews/qabstractitemview.h @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMVIEW_H +#define QABSTRACTITEMVIEW_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QMenu; +class QDrag; +class QEvent; +class QAbstractItemViewPrivate; + +class Q_GUI_EXPORT QAbstractItemView : public QAbstractScrollArea +{ + Q_OBJECT + Q_ENUMS(SelectionMode SelectionBehavior ScrollHint ScrollMode DragDropMode) + Q_FLAGS(EditTriggers) + Q_PROPERTY(bool autoScroll READ hasAutoScroll WRITE setAutoScroll) + Q_PROPERTY(int autoScrollMargin READ autoScrollMargin WRITE setAutoScrollMargin) + Q_PROPERTY(EditTriggers editTriggers READ editTriggers WRITE setEditTriggers) + Q_PROPERTY(bool tabKeyNavigation READ tabKeyNavigation WRITE setTabKeyNavigation) +#ifndef QT_NO_DRAGANDDROP + Q_PROPERTY(bool showDropIndicator READ showDropIndicator WRITE setDropIndicatorShown) + Q_PROPERTY(bool dragEnabled READ dragEnabled WRITE setDragEnabled) + Q_PROPERTY(bool dragDropOverwriteMode READ dragDropOverwriteMode WRITE setDragDropOverwriteMode) + Q_PROPERTY(DragDropMode dragDropMode READ dragDropMode WRITE setDragDropMode) + Q_PROPERTY(Qt::DropAction defaultDropAction READ defaultDropAction WRITE setDefaultDropAction) +#endif + Q_PROPERTY(bool alternatingRowColors READ alternatingRowColors WRITE setAlternatingRowColors) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode) + Q_PROPERTY(SelectionBehavior selectionBehavior READ selectionBehavior WRITE setSelectionBehavior) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + Q_PROPERTY(Qt::TextElideMode textElideMode READ textElideMode WRITE setTextElideMode) + Q_PROPERTY(ScrollMode verticalScrollMode READ verticalScrollMode WRITE setVerticalScrollMode) + Q_PROPERTY(ScrollMode horizontalScrollMode READ horizontalScrollMode WRITE setHorizontalScrollMode) + +public: + enum SelectionMode { + NoSelection, + SingleSelection, + MultiSelection, + ExtendedSelection, + ContiguousSelection + }; + + enum SelectionBehavior { + SelectItems, + SelectRows, + SelectColumns + }; + + enum ScrollHint { + EnsureVisible, + PositionAtTop, + PositionAtBottom, + PositionAtCenter + }; + + enum EditTrigger { + NoEditTriggers = 0, + CurrentChanged = 1, + DoubleClicked = 2, + SelectedClicked = 4, + EditKeyPressed = 8, + AnyKeyPressed = 16, + AllEditTriggers = 31 + }; + + Q_DECLARE_FLAGS(EditTriggers, EditTrigger) + + enum ScrollMode { + ScrollPerItem, + ScrollPerPixel + }; + + explicit QAbstractItemView(QWidget *parent = 0); + ~QAbstractItemView(); + + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + virtual void setSelectionModel(QItemSelectionModel *selectionModel); + QItemSelectionModel *selectionModel() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setSelectionMode(QAbstractItemView::SelectionMode mode); + QAbstractItemView::SelectionMode selectionMode() const; + + void setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior); + QAbstractItemView::SelectionBehavior selectionBehavior() const; + + QModelIndex currentIndex() const; + QModelIndex rootIndex() const; + + void setEditTriggers(EditTriggers triggers); + EditTriggers editTriggers() const; + + void setVerticalScrollMode(ScrollMode mode); + ScrollMode verticalScrollMode() const; + + void setHorizontalScrollMode(ScrollMode mode); + ScrollMode horizontalScrollMode() const; + + void setAutoScroll(bool enable); + bool hasAutoScroll() const; + + void setAutoScrollMargin(int margin); + int autoScrollMargin() const; + + void setTabKeyNavigation(bool enable); + bool tabKeyNavigation() const; + +#ifndef QT_NO_DRAGANDDROP + void setDropIndicatorShown(bool enable); + bool showDropIndicator() const; + + void setDragEnabled(bool enable); + bool dragEnabled() const; + + void setDragDropOverwriteMode(bool overwrite); + bool dragDropOverwriteMode() const; + + enum DragDropMode { + NoDragDrop, + DragOnly, + DropOnly, + DragDrop, + InternalMove + }; + + void setDragDropMode(DragDropMode behavior); + DragDropMode dragDropMode() const; + + void setDefaultDropAction(Qt::DropAction dropAction); + Qt::DropAction defaultDropAction() const; +#endif + + void setAlternatingRowColors(bool enable); + bool alternatingRowColors() const; + + void setIconSize(const QSize &size); + QSize iconSize() const; + + void setTextElideMode(Qt::TextElideMode mode); + Qt::TextElideMode textElideMode() const; + + virtual void keyboardSearch(const QString &search); + + virtual QRect visualRect(const QModelIndex &index) const = 0; + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) = 0; + virtual QModelIndex indexAt(const QPoint &point) const = 0; + + QSize sizeHintForIndex(const QModelIndex &index) const; + virtual int sizeHintForRow(int row) const; + virtual int sizeHintForColumn(int column) const; + + void openPersistentEditor(const QModelIndex &index); + void closePersistentEditor(const QModelIndex &index); + + void setIndexWidget(const QModelIndex &index, QWidget *widget); + QWidget *indexWidget(const QModelIndex &index) const; + + void setItemDelegateForRow(int row, QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegateForRow(int row) const; + + void setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegateForColumn(int column) const; + + QAbstractItemDelegate *itemDelegate(const QModelIndex &index) const; + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + +#ifdef Q_NO_USING_KEYWORD + inline void update() { QAbstractScrollArea::update(); } +#else + using QAbstractScrollArea::update; +#endif + +public Q_SLOTS: + virtual void reset(); + virtual void setRootIndex(const QModelIndex &index); + virtual void doItemsLayout(); + virtual void selectAll(); + void edit(const QModelIndex &index); + void clearSelection(); + void setCurrentIndex(const QModelIndex &index); + void scrollToTop(); + void scrollToBottom(); + void update(const QModelIndex &index); + +protected Q_SLOTS: + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + virtual void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + virtual void updateEditorData(); + virtual void updateEditorGeometries(); + virtual void updateGeometries(); + virtual void verticalScrollbarAction(int action); + virtual void horizontalScrollbarAction(int action); + virtual void verticalScrollbarValueChanged(int value); + virtual void horizontalScrollbarValueChanged(int value); + virtual void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); + virtual void commitData(QWidget *editor); + virtual void editorDestroyed(QObject *editor); + +Q_SIGNALS: + void pressed(const QModelIndex &index); + void clicked(const QModelIndex &index); + void doubleClicked(const QModelIndex &index); + + void activated(const QModelIndex &index); + void entered(const QModelIndex &index); + void viewportEntered(); + +protected: + QAbstractItemView(QAbstractItemViewPrivate &, QWidget *parent = 0); + + void setHorizontalStepsPerItem(int steps); + int horizontalStepsPerItem() const; + void setVerticalStepsPerItem(int steps); + int verticalStepsPerItem() const; + + enum CursorAction { MoveUp, MoveDown, MoveLeft, MoveRight, + MoveHome, MoveEnd, MovePageUp, MovePageDown, + MoveNext, MovePrevious }; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) = 0; + + virtual int horizontalOffset() const = 0; + virtual int verticalOffset() const = 0; + + virtual bool isIndexHidden(const QModelIndex &index) const = 0; + + virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) = 0; + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const = 0; + virtual QModelIndexList selectedIndexes() const; + + virtual bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); + + virtual QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, + const QEvent *event = 0) const; + +#ifndef QT_NO_DRAGANDDROP + virtual void startDrag(Qt::DropActions supportedActions); +#endif + + virtual QStyleOptionViewItem viewOptions() const; + + enum State { + NoState, + DraggingState, + DragSelectingState, + EditingState, + ExpandingState, + CollapsingState, + AnimatingState + }; + + State state() const; + void setState(State state); + + void scheduleDelayedItemsLayout(); + void executeDelayedItemsLayout(); + + void setDirtyRegion(const QRegion ®ion); + void scrollDirtyRegion(int dx, int dy); + QPoint dirtyRegionOffset() const; + + void startAutoScroll(); + void stopAutoScroll(); + void doAutoScroll(); + + bool focusNextPrevChild(bool next); + bool event(QEvent *event); + bool viewportEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); +#endif + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); + void resizeEvent(QResizeEvent *event); + void timerEvent(QTimerEvent *event); + void inputMethodEvent(QInputMethodEvent *event); + +#ifndef QT_NO_DRAGANDDROP + enum DropIndicatorPosition { OnItem, AboveItem, BelowItem, OnViewport }; + DropIndicatorPosition dropIndicatorPosition() const; +#endif + +private: + Q_DECLARE_PRIVATE(QAbstractItemView) + Q_DISABLE_COPY(QAbstractItemView) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_headerDataChanged()) +#ifndef QT_NO_GESTURES + Q_PRIVATE_SLOT(d_func(), void _q_scrollerStateChanged()) +#endif + + friend class QTreeViewPrivate; // needed to compile with MSVC + friend class QAccessibleItemRow; + friend class QListModeViewBase; + friend class QListViewPrivate; // needed to compile for Symbian emulator +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractItemView::EditTriggers) + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTITEMVIEW_H diff --git a/src/gui/itemviews/qabstractitemview_p.h b/src/gui/itemviews/qabstractitemview_p.h new file mode 100644 index 0000000000..6041e5e177 --- /dev/null +++ b/src/gui/itemviews/qabstractitemview_p.h @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTITEMVIEW_P_H +#define QABSTRACTITEMVIEW_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/qabstractscrollarea_p.h" +#include "private/qabstractitemmodel_p.h" +#include "QtGui/qapplication.h" +#include "QtGui/qevent.h" +#include "QtGui/qmime.h" +#include "QtGui/qpainter.h" +#include "QtCore/qpair.h" +#include "QtGui/qregion.h" +#include "QtCore/qdebug.h" +#include "QtGui/qpainter.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qelapsedtimer.h" + +#ifndef QT_NO_ITEMVIEWS + +QT_BEGIN_NAMESPACE + +struct QEditorInfo { + QEditorInfo(QWidget *e, bool s): widget(QWeakPointer(e)), isStatic(s) {} + QEditorInfo(): isStatic(false) {} + + QWeakPointer widget; + bool isStatic; +}; + +// Fast associativity between Persistent editors and indices. +typedef QHash QEditorIndexHash; +typedef QHash QIndexEditorHash; + +typedef QPair QItemViewPaintPair; +typedef QList QItemViewPaintPairs; + +class QEmptyModel : public QAbstractItemModel +{ +public: + explicit QEmptyModel(QObject *parent = 0) : QAbstractItemModel(parent) {} + QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + int rowCount(const QModelIndex &) const { return 0; } + int columnCount(const QModelIndex &) const { return 0; } + bool hasChildren(const QModelIndex &) const { return false; } + QVariant data(const QModelIndex &, int) const { return QVariant(); } +}; + +class Q_AUTOTEST_EXPORT QAbstractItemViewPrivate : public QAbstractScrollAreaPrivate +{ + Q_DECLARE_PUBLIC(QAbstractItemView) + +public: + QAbstractItemViewPrivate(); + virtual ~QAbstractItemViewPrivate(); + + void init(); + + virtual void _q_rowsRemoved(const QModelIndex &parent, int start, int end); + virtual void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void _q_columnsRemoved(const QModelIndex &parent, int start, int end); + virtual void _q_columnsInserted(const QModelIndex &parent, int start, int end); + virtual void _q_modelDestroyed(); + virtual void _q_layoutChanged(); + void _q_headerDataChanged() { doDelayedItemsLayout(); } + void _q_scrollerStateChanged(); + + void fetchMore(); + + bool shouldEdit(QAbstractItemView::EditTrigger trigger, const QModelIndex &index) const; + bool shouldForwardEvent(QAbstractItemView::EditTrigger trigger, const QEvent *event) const; + bool shouldAutoScroll(const QPoint &pos) const; + void doDelayedItemsLayout(int delay = 0); + void interruptDelayedItemsLayout() const; + + void startAutoScroll() + { // ### it would be nice to make this into a style hint one day + int scrollInterval = (verticalScrollMode == QAbstractItemView::ScrollPerItem) ? 150 : 50; + autoScrollTimer.start(scrollInterval, q_func()); + autoScrollCount = 0; + } + void stopAutoScroll() { autoScrollTimer.stop(); autoScrollCount = 0;} + +#ifndef QT_NO_DRAGANDDROP + virtual bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index); +#endif + bool droppingOnItself(QDropEvent *event, const QModelIndex &index); + + QWidget *editor(const QModelIndex &index, const QStyleOptionViewItem &options); + bool sendDelegateEvent(const QModelIndex &index, QEvent *event) const; + bool openEditor(const QModelIndex &index, QEvent *event); + void updateEditorData(const QModelIndex &topLeft, const QModelIndex &bottomRight); + + QItemSelectionModel::SelectionFlags multiSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + QItemSelectionModel::SelectionFlags extendedSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + QItemSelectionModel::SelectionFlags contiguousSelectionCommand(const QModelIndex &index, + const QEvent *event) const; + virtual void selectAll(QItemSelectionModel::SelectionFlags command); + + void setHoverIndex(const QPersistentModelIndex &index); + + void checkMouseMove(const QPersistentModelIndex &index); + inline void checkMouseMove(const QPoint &pos) { checkMouseMove(q_func()->indexAt(pos)); } + + inline QItemSelectionModel::SelectionFlags selectionBehaviorFlags() const + { + switch (selectionBehavior) { + case QAbstractItemView::SelectRows: return QItemSelectionModel::Rows; + case QAbstractItemView::SelectColumns: return QItemSelectionModel::Columns; + case QAbstractItemView::SelectItems: default: return QItemSelectionModel::NoUpdate; + } + } + +#ifndef QT_NO_DRAGANDDROP + virtual QAbstractItemView::DropIndicatorPosition position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const; + + inline bool canDecode(QDropEvent *e) const { + QStringList modelTypes = model->mimeTypes(); + const QMimeData *mime = e->mimeData(); + for (int i = 0; i < modelTypes.count(); ++i) + if (mime->hasFormat(modelTypes.at(i)) + && (e->dropAction() & model->supportedDropActions())) + return true; + return false; + } + + inline void paintDropIndicator(QPainter *painter) + { + if (showDropIndicator && state == QAbstractItemView::DraggingState +#ifndef QT_NO_CURSOR + && viewport->cursor().shape() != Qt::ForbiddenCursor +#endif + ) { + QStyleOption opt; + opt.init(q_func()); + opt.rect = dropIndicatorRect; + q_func()->style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, painter, q_func()); + } + } + +#endif + virtual QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; + + inline void releaseEditor(QWidget *editor) const { + if (editor) { + QObject::disconnect(editor, SIGNAL(destroyed(QObject*)), + q_func(), SLOT(editorDestroyed(QObject*))); + editor->removeEventFilter(itemDelegate); + editor->hide(); + editor->deleteLater(); + } + } + + inline void executePostedLayout() const { + if (delayedPendingLayout && state != QAbstractItemView::CollapsingState) { + interruptDelayedItemsLayout(); + const_cast(q_func())->doItemsLayout(); + } + } + + inline void setDirtyRegion(const QRegion &visualRegion) { + updateRegion += visualRegion; + if (!updateTimer.isActive()) + updateTimer.start(0, q_func()); + } + + inline void scrollDirtyRegion(int dx, int dy) { + scrollDelayOffset = QPoint(-dx, -dy); + updateDirtyRegion(); + scrollDelayOffset = QPoint(0, 0); + } + + inline void scrollContentsBy(int dx, int dy) { + scrollDirtyRegion(dx, dy); + viewport->scroll(dx, dy); + } + + void updateDirtyRegion() { + updateTimer.stop(); + viewport->update(updateRegion); + updateRegion = QRegion(); + } + + void clearOrRemove(); + void checkPersistentEditorFocus(); + + QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; + + inline QPoint offset() const { + const Q_Q(QAbstractItemView); + return QPoint(q->isRightToLeft() ? -q->horizontalOffset() + : q->horizontalOffset(), q->verticalOffset()); + } + + const QEditorInfo &editorForIndex(const QModelIndex &index) const; + inline bool hasEditor(const QModelIndex &index) const { + return indexEditorHash.find(index) != indexEditorHash.constEnd(); + } + + QModelIndex indexForEditor(QWidget *editor) const; + void addEditor(const QModelIndex &index, QWidget *editor, bool isStatic); + void removeEditor(QWidget *editor); + + inline bool isAnimating() const { + return state == QAbstractItemView::AnimatingState; + } + + inline QAbstractItemDelegate *delegateForIndex(const QModelIndex &index) const { + QAbstractItemDelegate *del; + if ((del = rowDelegates.value(index.row(), 0))) return del; + if ((del = columnDelegates.value(index.column(), 0))) return del; + return itemDelegate; + } + + inline bool isIndexValid(const QModelIndex &index) const { + return (index.row() >= 0) && (index.column() >= 0) && (index.model() == model); + } + inline bool isIndexSelectable(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsSelectable); + } + inline bool isIndexEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsEnabled); + } + inline bool isIndexDropEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsDropEnabled); + } + inline bool isIndexDragEnabled(const QModelIndex &index) const { + return (model->flags(index) & Qt::ItemIsDragEnabled); + } + + virtual bool selectionAllowed(const QModelIndex &index) const { + // in some views we want to go ahead with selections, even if the index is invalid + return isIndexValid(index) && isIndexSelectable(index); + } + + // reimplemented from QAbstractScrollAreaPrivate + virtual QPoint contentsOffset() const { + Q_Q(const QAbstractItemView); + return QPoint(q->horizontalOffset(), q->verticalOffset()); + } + + /** + * For now, assume that we have few editors, if we need a more efficient implementation + * we should add a QMap member. + */ + int delegateRefCount(const QAbstractItemDelegate *delegate) const + { + int ref = 0; + if (itemDelegate == delegate) + ++ref; + + for (int maps = 0; maps < 2; ++maps) { + const QMap > *delegates = maps ? &columnDelegates : &rowDelegates; + for (QMap >::const_iterator it = delegates->begin(); + it != delegates->end(); ++it) { + if (it.value() == delegate) { + ++ref; + // optimization, we are only interested in the ref count values 0, 1 or >=2 + if (ref >= 2) { + return ref; + } + } + } + } + return ref; + } + + /** + * return true if the index is registered as a QPersistentModelIndex + */ + inline bool isPersistent(const QModelIndex &index) const + { + return static_cast(model->d_ptr.data())->persistent.indexes.contains(index); + } + + QModelIndexList selectedDraggableIndexes() const; + + QStyleOptionViewItemV4 viewOptionsV4() const; + + void doDelayedReset() + { + //we delay the reset of the timer because some views (QTableView) + //with headers can't handle the fact that the model has been destroyed + //all _q_modelDestroyed slots must have been called + if (!delayedReset.isActive()) + delayedReset.start(0, q_func()); + } + + QAbstractItemModel *model; + QPointer itemDelegate; + QMap > rowDelegates; + QMap > columnDelegates; + QPointer selectionModel; + QItemSelectionModel::SelectionFlag ctrlDragSelectionFlag; + bool noSelectionOnMousePress; + + QAbstractItemView::SelectionMode selectionMode; + QAbstractItemView::SelectionBehavior selectionBehavior; + + QEditorIndexHash editorIndexHash; + QIndexEditorHash indexEditorHash; + QSet persistent; + QWidget *currentlyCommittingEditor; + + QPersistentModelIndex enteredIndex; + QPersistentModelIndex pressedIndex; + Qt::KeyboardModifiers pressedModifiers; + QPoint pressedPosition; + bool pressedAlreadySelected; + + //forces the next mouseMoveEvent to send the viewportEntered signal + //if the mouse is over the viewport and not over an item + bool viewportEnteredNeeded; + + QAbstractItemView::State state; + QAbstractItemView::State stateBeforeAnimation; + QAbstractItemView::EditTriggers editTriggers; + QAbstractItemView::EditTrigger lastTrigger; + + QPersistentModelIndex root; + QPersistentModelIndex hover; + + bool tabKeyNavigation; + +#ifndef QT_NO_DRAGANDDROP + bool showDropIndicator; + QRect dropIndicatorRect; + bool dragEnabled; + QAbstractItemView::DragDropMode dragDropMode; + bool overwrite; + QAbstractItemView::DropIndicatorPosition dropIndicatorPosition; + Qt::DropAction defaultDropAction; +#endif + +#ifdef QT_SOFTKEYS_ENABLED + QAction *doneSoftKey; +#endif + + QString keyboardInput; + QElapsedTimer keyboardInputTime; + + bool autoScroll; + QBasicTimer autoScrollTimer; + int autoScrollMargin; + int autoScrollCount; + bool shouldScrollToCurrentOnShow; //used to know if we should scroll to current on show event + bool shouldClearStatusTip; //if there is a statustip currently shown that need to be cleared when leaving. + + bool alternatingColors; + + QSize iconSize; + Qt::TextElideMode textElideMode; + + QRegion updateRegion; // used for the internal update system + QPoint scrollDelayOffset; + + QBasicTimer updateTimer; + QBasicTimer delayedEditing; + QBasicTimer delayedAutoScroll; //used when an item is clicked + QBasicTimer delayedReset; + + QAbstractItemView::ScrollMode verticalScrollMode; + QAbstractItemView::ScrollMode horizontalScrollMode; + +#ifndef QT_NO_GESTURES + // the selection before the last mouse down. In case we have to restore it for scrolling + QItemSelection oldSelection; + QModelIndex oldCurrent; +#endif + + bool currentIndexSet; + + bool wrapItemText; + mutable bool delayedPendingLayout; + bool moveCursorUpdatedView; + +private: + mutable QBasicTimer delayedLayout; + mutable QBasicTimer fetchMoreTimer; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +template +inline int qBinarySearch(const QVector &vec, const T &item, int start, int end) +{ + int i = (start + end + 1) >> 1; + while (end - start > 0) { + if (vec.at(i) > item) + end = i - 1; + else + start = i; + i = (start + end + 1) >> 1; + } + return i; +} + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS + +#endif // QABSTRACTITEMVIEW_P_H diff --git a/src/gui/itemviews/qabstractproxymodel.cpp b/src/gui/itemviews/qabstractproxymodel.cpp new file mode 100644 index 0000000000..34ca7dff50 --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel.cpp @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractproxymodel.h" + +#ifndef QT_NO_PROXYMODEL + +#include "qitemselectionmodel.h" +#include +#include +#include + + +QT_BEGIN_NAMESPACE + +/*! + \since 4.1 + \class QAbstractProxyModel + \brief The QAbstractProxyModel class provides a base class for proxy item + models that can do sorting, filtering or other data processing tasks. + \ingroup model-view + + This class defines the standard interface that proxy models must use to be + able to interoperate correctly with other model/view components. It is not + supposed to be instantiated directly. + + All standard proxy models are derived from the QAbstractProxyModel class. + If you need to create a new proxy model class, it is usually better to + subclass an existing class that provides the closest behavior to the one + you want to provide. + + Proxy models that filter or sort items of data from a source model should + be created by using or subclassing QSortFilterProxyModel. + + To subclass QAbstractProxyModel, you need to implement mapFromSource() and + mapToSource(). The mapSelectionFromSource() and mapSelectionToSource() + functions only need to be reimplemented if you need a behavior different + from the default behavior. + + \note If the source model is deleted or no source model is specified, the + proxy model operates on a empty placeholder model. + + \sa QSortFilterProxyModel, QAbstractItemModel, {Model/View Programming} +*/ + +//detects the deletion of the source model +void QAbstractProxyModelPrivate::_q_sourceModelDestroyed() +{ + model = QAbstractItemModelPrivate::staticEmptyModel(); +} + +/*! + Constructs a proxy model with the given \a parent. +*/ + +QAbstractProxyModel::QAbstractProxyModel(QObject *parent) + :QAbstractItemModel(*new QAbstractProxyModelPrivate, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + \internal +*/ + +QAbstractProxyModel::QAbstractProxyModel(QAbstractProxyModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + setSourceModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + Destroys the proxy model. +*/ +QAbstractProxyModel::~QAbstractProxyModel() +{ + +} + +/*! + Sets the given \a sourceModel to be processed by the proxy model. +*/ +void QAbstractProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QAbstractProxyModel); + if (d->model) { + disconnect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(resetInternalData())); + } + + if (sourceModel) { + d->model = sourceModel; + connect(d->model, SIGNAL(destroyed()), this, SLOT(_q_sourceModelDestroyed())); + connect(d->model, SIGNAL(modelReset()), this, SLOT(resetInternalData())); + } else { + d->model = QAbstractItemModelPrivate::staticEmptyModel(); + } + d->roleNames = d->model->roleNames(); +} + +/*! + Returns the model that contains the data that is available through the proxy model. +*/ +QAbstractItemModel *QAbstractProxyModel::sourceModel() const +{ + Q_D(const QAbstractProxyModel); + if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) + return 0; + return d->model; +} + +/*! + \reimp + */ +bool QAbstractProxyModel::submit() +{ + Q_D(QAbstractProxyModel); + return d->model->submit(); +} + +/*! + \reimp + */ +void QAbstractProxyModel::revert() +{ + Q_D(QAbstractProxyModel); + d->model->revert(); +} + + +/*! + \fn QModelIndex QAbstractProxyModel::mapToSource(const QModelIndex &proxyIndex) const + + Reimplement this function to return the model index in the source model that + corresponds to the \a proxyIndex in the proxy model. + + \sa mapFromSource() +*/ + +/*! + \fn QModelIndex QAbstractProxyModel::mapFromSource(const QModelIndex &sourceIndex) const + + Reimplement this function to return the model index in the proxy model that + corresponds to the \a sourceIndex from the source model. + + \sa mapToSource() +*/ + +/*! + Returns a source selection mapped from the specified \a proxySelection. + + Reimplement this method to map proxy selections to source selections. + */ +QItemSelection QAbstractProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + QModelIndexList proxyIndexes = proxySelection.indexes(); + QItemSelection sourceSelection; + for (int i = 0; i < proxyIndexes.size(); ++i) { + const QModelIndex proxyIdx = mapToSource(proxyIndexes.at(i)); + if (!proxyIdx.isValid()) + continue; + sourceSelection << QItemSelectionRange(proxyIdx); + } + return sourceSelection; +} + +/*! + Returns a proxy selection mapped from the specified \a sourceSelection. + + Reimplement this method to map source selections to proxy selections. +*/ +QItemSelection QAbstractProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + QModelIndexList sourceIndexes = sourceSelection.indexes(); + QItemSelection proxySelection; + for (int i = 0; i < sourceIndexes.size(); ++i) { + const QModelIndex srcIdx = mapFromSource(sourceIndexes.at(i)); + if (!srcIdx.isValid()) + continue; + proxySelection << QItemSelectionRange(srcIdx); + } + return proxySelection; +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::data(const QModelIndex &proxyIndex, int role) const +{ + Q_D(const QAbstractProxyModel); + return d->model->data(mapToSource(proxyIndex), role); +} + +/*! + \reimp + */ +QVariant QAbstractProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->headerData(sourceSection, orientation, role); +} + +/*! + \reimp + */ +QMap QAbstractProxyModel::itemData(const QModelIndex &proxyIndex) const +{ + Q_D(const QAbstractProxyModel); + return d->model->itemData(mapToSource(proxyIndex)); +} + +/*! + \reimp + */ +Qt::ItemFlags QAbstractProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return d->model->flags(mapToSource(index)); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + return d->model->setData(mapToSource(index), value, role); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setItemData(const QModelIndex &index, const QMap< int, QVariant >& roles) +{ + Q_D(QAbstractProxyModel); + return d->model->setItemData(mapToSource(index), roles); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + Q_D(QAbstractProxyModel); + int sourceSection; + if (orientation == Qt::Horizontal) { + const QModelIndex proxyIndex = index(0, section); + sourceSection = mapToSource(proxyIndex).column(); + } else { + const QModelIndex proxyIndex = index(section, 0); + sourceSection = mapToSource(proxyIndex).row(); + } + return d->model->setHeaderData(sourceSection, orientation, value, role); +} + +/*! + \reimp + */ +QModelIndex QAbstractProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return mapFromSource(d->model->buddy(mapToSource(index))); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QAbstractProxyModel); + return d->model->canFetchMore(mapToSource(parent)); +} + +/*! + \reimp + */ +void QAbstractProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QAbstractProxyModel); + d->model->fetchMore(mapToSource(parent)); +} + +/*! + \reimp + */ +void QAbstractProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QAbstractProxyModel); + d->model->sort(column, order); +} + +/*! + \reimp + */ +QSize QAbstractProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QAbstractProxyModel); + return d->model->span(mapToSource(index)); +} + +/*! + \reimp + */ +bool QAbstractProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QAbstractProxyModel); + return d->model->hasChildren(mapToSource(parent)); +} + +/*! + \reimp + */ +QMimeData* QAbstractProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QAbstractProxyModel); + QModelIndexList list; + foreach(const QModelIndex &index, indexes) + list << mapToSource(index); + return d->model->mimeData(list); +} + +/*! + \reimp + */ +QStringList QAbstractProxyModel::mimeTypes() const +{ + Q_D(const QAbstractProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp + */ +Qt::DropActions QAbstractProxyModel::supportedDropActions() const +{ + Q_D(const QAbstractProxyModel); + return d->model->supportedDropActions(); +} + +/* + \since 4.8 + + This slot is called just after the internal data of a model is cleared + while it is being reset. + + This slot is provided the convenience of subclasses of concrete proxy + models, such as subclasses of QSortFilterProxyModel which maintain extra + data. + + \snippet doc/src/snippets/code/src_corelib_kernel_qabstractitemmodel.cpp 10 + + \sa modelAboutToBeReset(), modelReset() +*/ +void QAbstractProxyModel::resetInternalData() +{ + +} + +QT_END_NAMESPACE + +#include "moc_qabstractproxymodel.cpp" + +#endif // QT_NO_PROXYMODEL diff --git a/src/gui/itemviews/qabstractproxymodel.h b/src/gui/itemviews/qabstractproxymodel.h new file mode 100644 index 0000000000..6e485aec12 --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_H +#define QABSTRACTPROXYMODEL_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROXYMODEL + +class QAbstractProxyModelPrivate; +class QItemSelection; + +class Q_GUI_EXPORT QAbstractProxyModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + QAbstractProxyModel(QObject *parent = 0); + ~QAbstractProxyModel(); + + virtual void setSourceModel(QAbstractItemModel *sourceModel); + QAbstractItemModel *sourceModel() const; + + virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const = 0; + virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const = 0; + + virtual QItemSelection mapSelectionToSource(const QItemSelection &selection) const; + virtual QItemSelection mapSelectionFromSource(const QItemSelection &selection) const; + + bool submit(); + void revert(); + + QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QMap itemData(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + bool setItemData(const QModelIndex& index, const QMap &roles); + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); + + QModelIndex buddy(const QModelIndex &index) const; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + QSize span(const QModelIndex &index) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QMimeData* mimeData(const QModelIndexList &indexes) const; + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; + +protected Q_SLOTS: + void resetInternalData(); + +protected: + QAbstractProxyModel(QAbstractProxyModelPrivate &, QObject *parent); + +private: + Q_DECLARE_PRIVATE(QAbstractProxyModel) + Q_DISABLE_COPY(QAbstractProxyModel) + Q_PRIVATE_SLOT(d_func(), void _q_sourceModelDestroyed()) +}; + +#endif // QT_NO_PROXYMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTPROXYMODEL_H diff --git a/src/gui/itemviews/qabstractproxymodel_p.h b/src/gui/itemviews/qabstractproxymodel_p.h new file mode 100644 index 0000000000..3e6e35a29f --- /dev/null +++ b/src/gui/itemviews/qabstractproxymodel_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTPROXYMODEL_P_H +#define QABSTRACTPROXYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_PROXYMODEL + +QT_BEGIN_NAMESPACE + +class QAbstractProxyModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QAbstractProxyModel) +public: + QAbstractProxyModelPrivate() : QAbstractItemModelPrivate(), model(0) {} + QAbstractItemModel *model; + virtual void _q_sourceModelDestroyed(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROXYMODEL + +#endif // QABSTRACTPROXYMODEL_P_H diff --git a/src/gui/itemviews/qbsptree.cpp b/src/gui/itemviews/qbsptree.cpp new file mode 100644 index 0000000000..ffe3ae8a10 --- /dev/null +++ b/src/gui/itemviews/qbsptree.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbsptree_p.h" + +QT_BEGIN_NAMESPACE + +QBspTree::QBspTree() : depth(6), visited(0) {} + +void QBspTree::create(int n, int d) +{ + // simple heuristics to find the best tree depth + if (d == -1) { + int c; + for (c = 0; n; ++c) + n = n / 10; + depth = c << 1; + } else { + depth = d; + } + depth = qMax(depth, uint(1)); + + nodes.resize((1 << depth) - 1); // resize to number of nodes + leaves.resize(1 << depth); // resize to number of leaves +} + +void QBspTree::destroy() +{ + leaves.clear(); + nodes.clear(); +} + +void QBspTree::climbTree(const QRect &rect, callback *function, QBspTreeData data) +{ + if (nodes.isEmpty()) + return; + ++visited; + climbTree(rect, function, data, 0); +} + +void QBspTree::climbTree(const QRect &area, callback *function, QBspTreeData data, int index) +{ + if (index >= nodes.count()) { // the index points to a leaf + Q_ASSERT(!nodes.isEmpty()); + function(leaf(index - nodes.count()), area, visited, data); + return; + } + + Node::Type t = (Node::Type) nodes.at(index).type; + + int pos = nodes.at(index).pos; + int idx = firstChildIndex(index); + if (t == Node::VerticalPlane) { + if (area.left() < pos) + climbTree(area, function, data, idx); // back + if (area.right() >= pos) + climbTree(area, function, data, idx + 1); // front + } else { + if (area.top() < pos) + climbTree(area, function, data, idx); // back + if (area.bottom() >= pos) + climbTree(area, function, data, idx + 1); // front + } +} + +void QBspTree::init(const QRect &area, int depth, NodeType type, int index) +{ + Node::Type t = Node::None; // t should never have this value + if (type == Node::Both) // if both planes are specified, use 2d bsp + t = (depth & 1) ? Node::HorizontalPlane : Node::VerticalPlane; + else + t = type; + QPoint center = area.center(); + nodes[index].pos = (t == Node::VerticalPlane ? center.x() : center.y()); + nodes[index].type = t; + + QRect front = area; + QRect back = area; + + if (t == Node::VerticalPlane) { + front.setLeft(center.x()); + back.setRight(center.x() - 1); // front includes the center + } else { // t == Node::HorizontalPlane + front.setTop(center.y()); + back.setBottom(center.y() - 1); + } + + int idx = firstChildIndex(index); + if (--depth) { + init(back, depth, type, idx); + init(front, depth, type, idx + 1); + } +} + +void QBspTree::insert(QVector &leaf, const QRect &, uint, QBspTreeData data) +{ + leaf.append(data.i); +} + +void QBspTree::remove(QVector &leaf, const QRect &, uint, QBspTreeData data) +{ + int i = leaf.indexOf(data.i); + if (i != -1) + leaf.remove(i); +} + +QT_END_NAMESPACE diff --git a/src/gui/itemviews/qbsptree_p.h b/src/gui/itemviews/qbsptree_p.h new file mode 100644 index 0000000000..f5b559a6d9 --- /dev/null +++ b/src/gui/itemviews/qbsptree_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBSPTREE_P_H +#define QBSPTREE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QBspTree +{ +public: + + struct Node + { + enum Type { None = 0, VerticalPlane = 1, HorizontalPlane = 2, Both = 3 }; + inline Node() : pos(0), type(None) {} + int pos; + Type type; + }; + typedef Node::Type NodeType; + + struct Data + { + Data(void *p) : ptr(p) {} + Data(int n) : i(n) {} + union { + void *ptr; + int i; + }; + }; + typedef QBspTree::Data QBspTreeData; + typedef void callback(QVector &leaf, const QRect &area, uint visited, QBspTreeData data); + + QBspTree(); + + void create(int n, int d = -1); + void destroy(); + + inline void init(const QRect &area, NodeType type) { init(area, depth, type, 0); } + + void climbTree(const QRect &rect, callback *function, QBspTreeData data); + + inline int leafCount() const { return leaves.count(); } + inline QVector &leaf(int i) { return leaves[i]; } + inline void insertLeaf(const QRect &r, int i) { climbTree(r, &insert, i, 0); } + inline void removeLeaf(const QRect &r, int i) { climbTree(r, &remove, i, 0); } + +protected: + void init(const QRect &area, int depth, NodeType type, int index); + void climbTree(const QRect &rect, callback *function, QBspTreeData data, int index); + + inline int parentIndex(int i) const { return (i & 1) ? ((i - 1) / 2) : ((i - 2) / 2); } + inline int firstChildIndex(int i) const { return ((i * 2) + 1); } + + static void insert(QVector &leaf, const QRect &area, uint visited, QBspTreeData data); + static void remove(QVector &leaf, const QRect &area, uint visited, QBspTreeData data); + +private: + uint depth; + mutable uint visited; + QVector nodes; + mutable QVector< QVector > leaves; // the leaves are just indices into the items +}; + +QT_END_NAMESPACE + +#endif // QBSPTREE_P_H diff --git a/src/gui/itemviews/qcolumnview.cpp b/src/gui/itemviews/qcolumnview.cpp new file mode 100644 index 0000000000..a44ecc7779 --- /dev/null +++ b/src/gui/itemviews/qcolumnview.cpp @@ -0,0 +1,1168 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QT_NO_COLUMNVIEW + +#include "qcolumnview.h" +#include "qcolumnview_p.h" +#include "qcolumnviewgrip_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#define ANIMATION_DURATION_MSEC 150 + +/*! + \since 4.3 + \class QColumnView + \brief The QColumnView class provides a model/view implementation of a column view. + \ingroup model-view + \ingroup advanced + + + QColumnView displays a model in a number of QListViews, one for each + hierarchy in the tree. This is sometimes referred to as a cascading list. + + The QColumnView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + QColumnView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + \image qcolumnview.png + + \sa \link model-view-programming.html Model/View Programming\endlink +*/ + +/*! + Constructs a column view with a \a parent to represent a model's + data. Use setModel() to set the model. + + \sa QAbstractItemModel +*/ +QColumnView::QColumnView(QWidget * parent) +: QAbstractItemView(*new QColumnViewPrivate, parent) +{ + Q_D(QColumnView); + d->initialize(); +} + +/*! + \internal +*/ +QColumnView::QColumnView(QColumnViewPrivate & dd, QWidget * parent) +: QAbstractItemView(dd, parent) +{ + Q_D(QColumnView); + d->initialize(); +} + +void QColumnViewPrivate::initialize() +{ + Q_Q(QColumnView); + q->setTextElideMode(Qt::ElideMiddle); +#ifndef QT_NO_ANIMATION + QObject::connect(¤tAnimation, SIGNAL(finished()), q, SLOT(_q_changeCurrentColumn())); + currentAnimation.setDuration(ANIMATION_DURATION_MSEC); + currentAnimation.setTargetObject(hbar); + currentAnimation.setPropertyName("value"); + currentAnimation.setEasingCurve(QEasingCurve::InOutQuad); +#endif //QT_NO_ANIMATION + delete itemDelegate; + q->setItemDelegate(new QColumnViewDelegate(q)); +} + +/*! + Destroys the column view. +*/ +QColumnView::~QColumnView() +{ +} + +/*! + \property QColumnView::resizeGripsVisible + \brief the way to specify if the list views gets resize grips or not + + By default, \c visible is set to true + + \sa setRootIndex() +*/ +void QColumnView::setResizeGripsVisible(bool visible) +{ + Q_D(QColumnView); + if (d->showResizeGrips == visible) + return; + d->showResizeGrips = visible; + for (int i = 0; i < d->columns.count(); ++i) { + QAbstractItemView *view = d->columns[i]; + if (visible) { + QColumnViewGrip *grip = new QColumnViewGrip(view); + view->setCornerWidget(grip); + connect(grip, SIGNAL(gripMoved(int)), this, SLOT(_q_gripMoved(int))); + } else { + QWidget *widget = view->cornerWidget(); + view->setCornerWidget(0); + widget->deleteLater(); + } + } +} + +bool QColumnView::resizeGripsVisible() const +{ + Q_D(const QColumnView); + return d->showResizeGrips; +} + +/*! + \reimp +*/ +void QColumnView::setModel(QAbstractItemModel *model) +{ + Q_D(QColumnView); + if (model == d->model) + return; + d->closeColumns(); + QAbstractItemView::setModel(model); +} + +/*! + \reimp +*/ +void QColumnView::setRootIndex(const QModelIndex &index) +{ + Q_D(QColumnView); + if (!model()) + return; + + d->closeColumns(); + Q_ASSERT(d->columns.count() == 0); + + QAbstractItemView *view = d->createColumn(index, true); + if (view->selectionModel()) + view->selectionModel()->deleteLater(); + if (view->model()) + view->setSelectionModel(selectionModel()); + + QAbstractItemView::setRootIndex(index); + d->updateScrollbars(); +} + +/*! + \reimp +*/ +bool QColumnView::isIndexHidden(const QModelIndex &index) const +{ + Q_UNUSED(index); + return false; +} + +/*! + \reimp +*/ +QModelIndex QColumnView::indexAt(const QPoint &point) const +{ + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + QPoint topLeft = d->columns.at(i)->frameGeometry().topLeft(); + QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y()); + QModelIndex index = d->columns.at(i)->indexAt(adjustedPoint); + if (index.isValid()) + return index; + } + return QModelIndex(); +} + +/*! + \reimp +*/ +QRect QColumnView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid()) + return QRect(); + + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + QRect rect = d->columns.at(i)->visualRect(index); + if (!rect.isNull()) { + rect.translate(d->columns.at(i)->frameGeometry().topLeft()); + return rect; + } + } + return QRect(); +} + +/*! + \reimp + */ +void QColumnView::scrollContentsBy(int dx, int dy) +{ + Q_D(QColumnView); + if (d->columns.isEmpty() || dx == 0) + return; + + dx = isRightToLeft() ? -dx : dx; + for (int i = 0; i < d->columns.count(); ++i) + d->columns.at(i)->move(d->columns.at(i)->x() + dx, 0); + d->offset += dx; + QAbstractItemView::scrollContentsBy(dx, dy); +} + +/*! + \reimp +*/ +void QColumnView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QColumnView); + Q_UNUSED(hint); + if (!index.isValid() || d->columns.isEmpty()) + return; + +#ifndef QT_NO_ANIMATION + if (d->currentAnimation.state() == QPropertyAnimation::Running) + return; + + d->currentAnimation.stop(); +#endif //QT_NO_ANIMATION + + // Fill up what is needed to get to index + d->closeColumns(index, true); + + QModelIndex indexParent = index.parent(); + // Find the left edge of the column that contains index + int currentColumn = 0; + int leftEdge = 0; + while (currentColumn < d->columns.size()) { + if (indexParent == d->columns.at(currentColumn)->rootIndex()) + break; + leftEdge += d->columns.at(currentColumn)->width(); + ++currentColumn; + } + + // Don't let us scroll above the root index + if (currentColumn == d->columns.size()) + return; + + int indexColumn = currentColumn; + // Find the width of what we want to show (i.e. the right edge) + int visibleWidth = d->columns.at(currentColumn)->width(); + // We want to always try to show two columns + if (currentColumn + 1 < d->columns.size()) { + ++currentColumn; + visibleWidth += d->columns.at(currentColumn)->width(); + } + + int rightEdge = leftEdge + visibleWidth; + if (isRightToLeft()) { + leftEdge = viewport()->width() - leftEdge; + rightEdge = leftEdge - visibleWidth; + qSwap(rightEdge, leftEdge); + } + + // If it is already visible don't animate + if (leftEdge > -horizontalOffset() + && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) { + d->columns.at(indexColumn)->scrollTo(index); + d->_q_changeCurrentColumn(); + return; + } + + int newScrollbarValue = 0; + if (isRightToLeft()) { + if (leftEdge < 0) { + // scroll to the right + newScrollbarValue = viewport()->size().width() - leftEdge; + } else { + // scroll to the left + newScrollbarValue = rightEdge + horizontalOffset(); + } + } else { + if (leftEdge > -horizontalOffset()) { + // scroll to the right + newScrollbarValue = rightEdge - viewport()->size().width(); + } else { + // scroll to the left + newScrollbarValue = leftEdge; + } + } + +#ifndef QT_NO_ANIMATION + d->currentAnimation.setEndValue(newScrollbarValue); + d->currentAnimation.start(); +#else + horizontalScrollBar()->setValue(newScrollbarValue); +#endif //QT_NO_ANIMATION +} + +/*! + \reimp + Move left should go to the parent index + Move right should go to the child index or down if there is no child +*/ +QModelIndex QColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + // the child views which have focus get to deal with this first and if + // they don't accept it then it comes up this view and we only grip left/right + Q_UNUSED(modifiers); + if (!model()) + return QModelIndex(); + + QModelIndex current = currentIndex(); + if (isRightToLeft()) { + if (cursorAction == MoveLeft) + cursorAction = MoveRight; + else if (cursorAction == MoveRight) + cursorAction = MoveLeft; + } + switch (cursorAction) { + case MoveLeft: + if (current.parent().isValid() && current.parent() != rootIndex()) + return (current.parent()); + else + return current; + break; + + case MoveRight: + if (model()->hasChildren(current)) + return model()->index(0, 0, current); + else + return current.sibling(current.row() + 1, current.column()); + break; + + default: + break; + } + + return QModelIndex(); +} + +/*! + \reimp +*/ +void QColumnView::resizeEvent(QResizeEvent *event) +{ + Q_D(QColumnView); + d->doLayout(); + d->updateScrollbars(); + if (!isRightToLeft()) { + int diff = event->oldSize().width() - event->size().width(); + if (diff < 0 && horizontalScrollBar()->isVisible() + && horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) { + horizontalScrollBar()->setMaximum(horizontalScrollBar()->maximum() + diff); + } + } + QAbstractItemView::resizeEvent(event); +} + +/*! + \internal +*/ +void QColumnViewPrivate::updateScrollbars() +{ + Q_Q(QColumnView); +#ifndef QT_NO_ANIMATION + if (currentAnimation.state() == QPropertyAnimation::Running) + return; +#endif //QT_NO_ANIMATION + + // find the total horizontal length of the laid out columns + int horizontalLength = 0; + if (!columns.isEmpty()) { + horizontalLength = (columns.last()->x() + columns.last()->width()) - columns.first()->x(); + if (horizontalLength <= 0) // reverse mode + horizontalLength = (columns.first()->x() + columns.first()->width()) - columns.last()->x(); + } + + QSize viewportSize = viewport->size(); + if (horizontalLength < viewportSize.width() && hbar->value() == 0) { + hbar->setRange(0, 0); + } else { + int visibleLength = qMin(horizontalLength + q->horizontalOffset(), viewportSize.width()); + int hiddenLength = horizontalLength - visibleLength; + if (hiddenLength != hbar->maximum()) + hbar->setRange(0, hiddenLength); + } + if (!columns.isEmpty()) { + int pageStepSize = columns.at(0)->width(); + if (pageStepSize != hbar->pageStep()) + hbar->setPageStep(pageStepSize); + } + bool visible = (hbar->maximum() > 0); + if (visible != hbar->isVisible()) + hbar->setVisible(visible); +} + +/*! + \reimp +*/ +int QColumnView::horizontalOffset() const +{ + Q_D(const QColumnView); + return d->offset; +} + +/*! + \reimp +*/ +int QColumnView::verticalOffset() const +{ + return 0; +} + +/*! + \reimp +*/ +QRegion QColumnView::visualRegionForSelection(const QItemSelection &selection) const +{ + int ranges = selection.count(); + + if (ranges == 0) + return QRect(); + + // Note that we use the top and bottom functions of the selection range + // since the data is stored in rows. + int firstRow = selection.at(0).top(); + int lastRow = selection.at(0).top(); + for (int i = 0; i < ranges; ++i) { + firstRow = qMin(firstRow, selection.at(i).top()); + lastRow = qMax(lastRow, selection.at(i).bottom()); + } + + QModelIndex firstIdx = model()->index(qMin(firstRow, lastRow), 0, rootIndex()); + QModelIndex lastIdx = model()->index(qMax(firstRow, lastRow), 0, rootIndex()); + + if (firstIdx == lastIdx) + return visualRect(firstIdx); + + QRegion firstRegion = visualRect(firstIdx); + QRegion lastRegion = visualRect(lastIdx); + return firstRegion.unite(lastRegion); +} + +/*! + \reimp +*/ +void QColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_UNUSED(rect); + Q_UNUSED(command); +} + +/*! + \reimp +*/ +void QColumnView::setSelectionModel(QItemSelectionModel *newSelectionModel) +{ + Q_D(const QColumnView); + for (int i = 0; i < d->columns.size(); ++i) { + if (d->columns.at(i)->selectionModel() == selectionModel()) { + d->columns.at(i)->setSelectionModel(newSelectionModel); + break; + } + } + QAbstractItemView::setSelectionModel(newSelectionModel); +} + +/*! + \reimp +*/ +QSize QColumnView::sizeHint() const +{ + Q_D(const QColumnView); + QSize sizeHint; + for (int i = 0; i < d->columns.size(); ++i) { + sizeHint += d->columns.at(i)->sizeHint(); + } + return sizeHint.expandedTo(QAbstractItemView::sizeHint()); +} + +/*! + \internal + Move all widgets from the corner grip and to the right + */ +void QColumnViewPrivate::_q_gripMoved(int offset) +{ + Q_Q(QColumnView); + + QObject *grip = q->sender(); + Q_ASSERT(grip); + + if (q->isRightToLeft()) + offset = -1 * offset; + + bool found = false; + for (int i = 0; i < columns.size(); ++i) { + if (!found && columns.at(i)->cornerWidget() == grip) { + found = true; + columnSizes[i] = columns.at(i)->width(); + if (q->isRightToLeft()) + columns.at(i)->move(columns.at(i)->x() + offset, 0); + continue; + } + if (!found) + continue; + + int currentX = columns.at(i)->x(); + columns.at(i)->move(currentX + offset, 0); + } + + updateScrollbars(); +} + +/*! + \internal + + Find where the current columns intersect parent's columns + + Delete any extra columns and insert any needed columns. + */ +void QColumnViewPrivate::closeColumns(const QModelIndex &parent, bool build) +{ + if (columns.isEmpty()) + return; + + bool clearAll = !parent.isValid(); + bool passThroughRoot = false; + + QList dirsToAppend; + + // Find the last column that matches the parent's tree + int currentColumn = -1; + QModelIndex parentIndex = parent; + while (currentColumn == -1 && parentIndex.isValid()) { + if (columns.isEmpty()) + break; + parentIndex = parentIndex.parent(); + if (root == parentIndex) + passThroughRoot = true; + if (!parentIndex.isValid()) + break; + for (int i = columns.size() - 1; i >= 0; --i) { + if (columns.at(i)->rootIndex() == parentIndex) { + currentColumn = i; + break; + } + } + if (currentColumn == -1) + dirsToAppend.append(parentIndex); + } + + // Someone wants to go to an index that can be reached without changing + // the root index, don't allow them + if (!clearAll && !passThroughRoot && currentColumn == -1) + return; + + if (currentColumn == -1 && parent.isValid()) + currentColumn = 0; + + // Optimization so we don't go deleting and then creating the same thing + bool alreadyExists = false; + if (build && columns.size() > currentColumn + 1) { + bool viewingParent = (columns.at(currentColumn + 1)->rootIndex() == parent); + bool viewingChild = (!model->hasChildren(parent) + && !columns.at(currentColumn + 1)->rootIndex().isValid()); + if (viewingParent || viewingChild) { + currentColumn++; + alreadyExists = true; + } + } + + // Delete columns that don't match our path + for (int i = columns.size() - 1; i > currentColumn; --i) { + QAbstractItemView* notShownAnymore = columns.at(i); + columns.removeAt(i); + notShownAnymore->setVisible(false); + if (notShownAnymore != previewColumn) + notShownAnymore->deleteLater(); + } + + if (columns.isEmpty()) { + offset = 0; + updateScrollbars(); + } + + // Now fill in missing columns + while (!dirsToAppend.isEmpty()) { + QAbstractItemView *newView = createColumn(dirsToAppend.takeLast(), true); + if (!dirsToAppend.isEmpty()) + newView->setCurrentIndex(dirsToAppend.last()); + } + + if (build && !alreadyExists) + createColumn(parent, false); +} + +void QColumnViewPrivate::_q_clicked(const QModelIndex &index) +{ + Q_Q(QColumnView); + QModelIndex parent = index.parent(); + QAbstractItemView *columnClicked = 0; + for (int column = 0; column < columns.count(); ++column) { + if (columns.at(column)->rootIndex() == parent) { + columnClicked = columns[column]; + break; + } + } + if (q->selectionModel() && columnClicked) { + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current; + if (columnClicked->selectionModel()->isSelected(index)) + flags |= QItemSelectionModel::Select; + q->selectionModel()->setCurrentIndex(index, flags); + } +} + +/*! + \internal + Create a new column for \a index. A grip is attached if requested and it is shown + if requested. + + Return the new view + + \sa createColumn() setPreviewWidget() + \sa doLayout() +*/ +QAbstractItemView *QColumnViewPrivate::createColumn(const QModelIndex &index, bool show) +{ + Q_Q(QColumnView); + QAbstractItemView *view = 0; + if (model->hasChildren(index)) { + view = q->createColumn(index); + q->connect(view, SIGNAL(clicked(QModelIndex)), + q, SLOT(_q_clicked(QModelIndex))); + } else { + if (!previewColumn) + setPreviewWidget(new QWidget(q)); + view = previewColumn; + view->setMinimumWidth(qMax(view->minimumWidth(), previewWidget->minimumWidth())); + } + + q->connect(view, SIGNAL(activated(QModelIndex)), + q, SIGNAL(activated(QModelIndex))); + q->connect(view, SIGNAL(clicked(QModelIndex)), + q, SIGNAL(clicked(QModelIndex))); + q->connect(view, SIGNAL(doubleClicked(QModelIndex)), + q, SIGNAL(doubleClicked(QModelIndex))); + q->connect(view, SIGNAL(entered(QModelIndex)), + q, SIGNAL(entered(QModelIndex))); + q->connect(view, SIGNAL(pressed(QModelIndex)), + q, SIGNAL(pressed(QModelIndex))); + + view->setFocusPolicy(Qt::NoFocus); + view->setParent(viewport); + Q_ASSERT(view); + + // Setup corner grip + if (showResizeGrips) { + QColumnViewGrip *grip = new QColumnViewGrip(view); + view->setCornerWidget(grip); + q->connect(grip, SIGNAL(gripMoved(int)), q, SLOT(_q_gripMoved(int))); + } + + if (columnSizes.count() > columns.count()) { + view->setGeometry(0, 0, columnSizes.at(columns.count()), viewport->height()); + } else { + int initialWidth = view->sizeHint().width(); + if (q->isRightToLeft()) + view->setGeometry(viewport->width() - initialWidth, 0, initialWidth, viewport->height()); + else + view->setGeometry(0, 0, initialWidth, viewport->height()); + columnSizes.resize(qMax(columnSizes.count(), columns.count() + 1)); + columnSizes[columns.count()] = initialWidth; + } + if (!columns.isEmpty() && columns.last()->isHidden()) + columns.last()->setVisible(true); + + columns.append(view); + doLayout(); + updateScrollbars(); + if (show && view->isHidden()) + view->setVisible(true); + return view; +} + +/*! + \fn void QColumnView::updatePreviewWidget(const QModelIndex &index) + + This signal is emitted when the preview widget should be updated to + provide rich information about \a index + + \sa previewWidget() + */ + +/*! + To use a custom widget for the final column when you select + an item overload this function and return a widget. + \a index is the root index that will be assigned to the view. + + Return the new view. QColumnView will automatically take ownership of the widget. + + \sa setPreviewWidget() + */ +QAbstractItemView *QColumnView::createColumn(const QModelIndex &index) +{ + QListView *view = new QListView(viewport()); + + initializeColumn(view); + + view->setRootIndex(index); + if (model()->canFetchMore(index)) + model()->fetchMore(index); + + return view; +} + +/*! + Copies the behavior and options of the column view and applies them to + the \a column such as the iconSize(), textElideMode() and + alternatingRowColors(). This can be useful when reimplementing + createColumn(). + + \since 4.4 + \sa createColumn() + */ +void QColumnView::initializeColumn(QAbstractItemView *column) const +{ + Q_D(const QColumnView); + + column->setFrameShape(QFrame::NoFrame); + column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + column->setMinimumWidth(100); + column->setAttribute(Qt::WA_MacShowFocusRect, false); + +#ifndef QT_NO_DRAGANDDROP + column->setDragDropMode(dragDropMode()); + column->setDragDropOverwriteMode(dragDropOverwriteMode()); + column->setDropIndicatorShown(showDropIndicator()); +#endif + column->setAlternatingRowColors(alternatingRowColors()); + column->setAutoScroll(hasAutoScroll()); + column->setEditTriggers(editTriggers()); + column->setHorizontalScrollMode(horizontalScrollMode()); + column->setIconSize(iconSize()); + column->setSelectionBehavior(selectionBehavior()); + column->setSelectionMode(selectionMode()); + column->setTabKeyNavigation(tabKeyNavigation()); + column->setTextElideMode(textElideMode()); + column->setVerticalScrollMode(verticalScrollMode()); + + column->setModel(model()); + + // Copy the custom delegate per row + QMapIterator > i(d->rowDelegates); + while (i.hasNext()) { + i.next(); + column->setItemDelegateForRow(i.key(), i.value()); + } + + // set the delegate to be the columnview delegate + QAbstractItemDelegate *delegate = column->itemDelegate(); + column->setItemDelegate(d->itemDelegate); + delete delegate; +} + +/*! + Returns the preview widget, or 0 if there is none. + + \sa setPreviewWidget(), updatePreviewWidget() +*/ +QWidget *QColumnView::previewWidget() const +{ + Q_D(const QColumnView); + return d->previewWidget; +} + +/*! + Sets the preview \a widget. + + The \a widget becomes a child of the column view, and will be + destroyed when the column area is deleted or when a new widget is + set. + + \sa previewWidget(), updatePreviewWidget() +*/ +void QColumnView::setPreviewWidget(QWidget *widget) +{ + Q_D(QColumnView); + d->setPreviewWidget(widget); +} + +/*! + \internal +*/ +void QColumnViewPrivate::setPreviewWidget(QWidget *widget) +{ + Q_Q(QColumnView); + if (previewColumn) { + if (!columns.isEmpty() && columns.last() == previewColumn) + columns.removeLast(); + previewColumn->deleteLater(); + } + QColumnViewPreviewColumn *column = new QColumnViewPreviewColumn(q); + column->setPreviewWidget(widget); + previewColumn = column; + previewColumn->hide(); + previewColumn->setFrameShape(QFrame::NoFrame); + previewColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + previewColumn->setSelectionMode(QAbstractItemView::NoSelection); + previewColumn->setMinimumWidth(qMax(previewColumn->verticalScrollBar()->width(), + previewColumn->minimumWidth())); + previewWidget = widget; + previewWidget->setParent(previewColumn->viewport()); +} + +/*! + Sets the column widths to the values given in the \a list. Extra values in the list are + kept and used when the columns are created. + + If list contains too few values, only width of the rest of the columns will not be modified. + + \sa columnWidths(), createColumn() +*/ +void QColumnView::setColumnWidths(const QList &list) +{ + Q_D(QColumnView); + int i = 0; + for (; (i < list.count() && i < d->columns.count()); ++i) { + d->columns.at(i)->resize(list.at(i), d->columns.at(i)->height()); + d->columnSizes[i] = list.at(i); + } + for (; i < list.count(); ++i) + d->columnSizes.append(list.at(i)); +} + +/*! + Returns a list of the width of all the columns in this view. + + \sa setColumnWidths() +*/ +QList QColumnView::columnWidths() const +{ + Q_D(const QColumnView); + QList list; + for (int i = 0; i < d->columns.count(); ++i) + list.append(d->columnSizes.at(i)); + return list; +} + +/*! + \reimp +*/ +void QColumnView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + QAbstractItemView::rowsInserted(parent, start, end); + d_func()->checkColumnCreation(parent); +} + +/*! + \reimp +*/ +void QColumnView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_D(QColumnView); + if (!current.isValid()) { + QAbstractItemView::currentChanged(current, previous); + return; + } + + QModelIndex currentParent = current.parent(); + // optimize for just moving up/down in a list where the child view doesn't change + if (currentParent == previous.parent() + && model()->hasChildren(current) && model()->hasChildren(previous)) { + for (int i = 0; i < d->columns.size(); ++i) { + if (currentParent == d->columns.at(i)->rootIndex()) { + if (d->columns.size() > i + 1) { + QAbstractItemView::currentChanged(current, previous); + return; + } + break; + } + } + } + + // Scrolling to the right we need to have an empty spot + bool found = false; + if (currentParent == previous) { + for (int i = 0; i < d->columns.size(); ++i) { + if (currentParent == d->columns.at(i)->rootIndex()) { + found = true; + if (d->columns.size() < i + 2) { + d->createColumn(current, false); + } + break; + } + } + } + if (!found) + d->closeColumns(current, true); + + if (!model()->hasChildren(current)) + emit updatePreviewWidget(current); + + QAbstractItemView::currentChanged(current, previous); +} + +/* + We have change the current column and need to update focus and selection models + on the new current column. +*/ +void QColumnViewPrivate::_q_changeCurrentColumn() +{ + Q_Q(QColumnView); + if (columns.isEmpty()) + return; + + QModelIndex current = q->currentIndex(); + if (!current.isValid()) + return; + + // We might have scrolled far to the left so we need to close all of the children + closeColumns(current, true); + + // Set up the "current" column with focus + int currentColumn = qMax(0, columns.size() - 2); + QAbstractItemView *parentColumn = columns.at(currentColumn); + if (q->hasFocus()) + parentColumn->setFocus(Qt::OtherFocusReason); + q->setFocusProxy(parentColumn); + + // find the column that is our current selection model and give it a new one. + for (int i = 0; i < columns.size(); ++i) { + if (columns.at(i)->selectionModel() == q->selectionModel()) { + QItemSelectionModel *replacementSelectionModel = + new QItemSelectionModel(parentColumn->model()); + replacementSelectionModel->setCurrentIndex( + q->selectionModel()->currentIndex(), QItemSelectionModel::Current); + replacementSelectionModel->select( + q->selectionModel()->selection(), QItemSelectionModel::Select); + QAbstractItemView *view = columns.at(i); + view->setSelectionModel(replacementSelectionModel); + view->setFocusPolicy(Qt::NoFocus); + if (columns.size() > i + 1) + view->setCurrentIndex(columns.at(i+1)->rootIndex()); + break; + } + } + parentColumn->selectionModel()->deleteLater(); + parentColumn->setFocusPolicy(Qt::StrongFocus); + parentColumn->setSelectionModel(q->selectionModel()); + // We want the parent selection to stay highlighted (but dimmed depending upon the color theme) + if (currentColumn > 0) { + parentColumn = columns.at(currentColumn - 1); + if (parentColumn->currentIndex() != current.parent()) + parentColumn->setCurrentIndex(current.parent()); + } + + if (columns.last()->isHidden()) { + columns.last()->setVisible(true); + } + if (columns.last()->selectionModel()) + columns.last()->selectionModel()->clear(); + updateScrollbars(); +} + +/*! + \reimp +*/ +void QColumnView::selectAll() +{ + if (!model() || !selectionModel()) + return; + + QModelIndexList indexList = selectionModel()->selectedIndexes(); + QModelIndex parent = rootIndex(); + QItemSelection selection; + if (indexList.count() >= 1) + parent = indexList.at(0).parent(); + if (indexList.count() == 1) { + parent = indexList.at(0); + if (!model()->hasChildren(parent)) + parent = parent.parent(); + else + selection.append(QItemSelectionRange(parent, parent)); + } + + QModelIndex tl = model()->index(0, 0, parent); + QModelIndex br = model()->index(model()->rowCount(parent) - 1, + model()->columnCount(parent) - 1, + parent); + selection.append(QItemSelectionRange(tl, br)); + selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); +} + +/* + * private object implementation + */ +QColumnViewPrivate::QColumnViewPrivate() +: QAbstractItemViewPrivate() +,showResizeGrips(true) +,offset(0) +,previewWidget(0) +,previewColumn(0) +{ +} + +QColumnViewPrivate::~QColumnViewPrivate() +{ +} + +/*! + \internal + + */ +void QColumnViewPrivate::_q_columnsInserted(const QModelIndex &parent, int start, int end) +{ + QAbstractItemViewPrivate::_q_columnsInserted(parent, start, end); + checkColumnCreation(parent); +} + +/*! + \internal + + Makes sure we create a corresponding column as a result of changing the model. + + */ +void QColumnViewPrivate::checkColumnCreation(const QModelIndex &parent) +{ + if (parent == q_func()->currentIndex() && model->hasChildren(parent)) { + //the parent has children and is the current + //let's try to find out if there is already a mapping that is good + for (int i = 0; i < columns.count(); ++i) { + QAbstractItemView *view = columns.at(i); + if (view->rootIndex() == parent) { + if (view == previewColumn) { + //let's recreate the parent + closeColumns(parent, false); + createColumn(parent, true /*show*/); + } + break; + } + } + } +} + +/*! + \internal + Place all of the columns where they belong inside of the viewport, resize as necessary. +*/ +void QColumnViewPrivate::doLayout() +{ + Q_Q(QColumnView); + if (!model || columns.isEmpty()) + return; + + int viewportHeight = viewport->height(); + int x = columns.at(0)->x(); + + if (q->isRightToLeft()) { + x = viewport->width() + q->horizontalOffset(); + for (int i = 0; i < columns.size(); ++i) { + QAbstractItemView *view = columns.at(i); + x -= view->width(); + if (x != view->x() || viewportHeight != view->height()) + view->setGeometry(x, 0, view->width(), viewportHeight); + } + } else { + for (int i = 0; i < columns.size(); ++i) { + QAbstractItemView *view = columns.at(i); + int currentColumnWidth = view->width(); + if (x != view->x() || viewportHeight != view->height()) + view->setGeometry(x, 0, currentColumnWidth, viewportHeight); + x += currentColumnWidth; + } + } +} + +/*! + \internal + + Draws a delegate with a > if an object has children. + + \sa {Model/View Programming}, QItemDelegate +*/ +void QColumnViewDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + drawBackground(painter, option, index ); + + bool reverse = (option.direction == Qt::RightToLeft); + int width = ((option.rect.height() * 2) / 3); + // Modify the options to give us room to add an arrow + QStyleOptionViewItemV4 opt = option; + if (reverse) + opt.rect.adjust(width,0,0,0); + else + opt.rect.adjust(0,0,-width,0); + + if (!(index.model()->flags(index) & Qt::ItemIsEnabled)) { + opt.showDecorationSelected = true; + opt.state |= QStyle::State_Selected; + } + + QItemDelegate::paint(painter, opt, index); + + if (reverse) + opt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height()); + else + opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(), + width, option.rect.height()); + + // Draw > + if (index.model()->hasChildren(index)) { + const QWidget *view = opt.widget; + QStyle *style = view ? view->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_IndicatorColumnViewArrow, &opt, painter, view); + } +} + +QT_END_NAMESPACE + +#include "moc_qcolumnview.cpp" + +#endif // QT_NO_COLUMNVIEW diff --git a/src/gui/itemviews/qcolumnview.h b/src/gui/itemviews/qcolumnview.h new file mode 100644 index 0000000000..a3f74b89d5 --- /dev/null +++ b/src/gui/itemviews/qcolumnview.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEW_H +#define QCOLUMNVIEW_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_COLUMNVIEW + +class QColumnViewPrivate; + +class Q_GUI_EXPORT QColumnView : public QAbstractItemView { + +Q_OBJECT + Q_PROPERTY(bool resizeGripsVisible READ resizeGripsVisible WRITE setResizeGripsVisible) + +Q_SIGNALS: + void updatePreviewWidget(const QModelIndex &index); + +public: + explicit QColumnView(QWidget *parent = 0); + ~QColumnView(); + + // QAbstractItemView overloads + QModelIndex indexAt(const QPoint &point) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QSize sizeHint() const; + QRect visualRect(const QModelIndex &index) const; + void setModel(QAbstractItemModel *model); + void setSelectionModel(QItemSelectionModel * selectionModel); + void setRootIndex(const QModelIndex &index); + void selectAll(); + + // QColumnView functions + void setResizeGripsVisible(bool visible); + bool resizeGripsVisible() const; + + QWidget *previewWidget() const; + void setPreviewWidget(QWidget *widget); + + void setColumnWidths(const QList &list); + QList columnWidths() const; + +protected: + QColumnView(QColumnViewPrivate &dd, QWidget *parent = 0); + + // QAbstractItemView overloads + bool isIndexHidden(const QModelIndex &index) const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + void resizeEvent(QResizeEvent *event); + void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + int horizontalOffset() const; + int verticalOffset() const; + void rowsInserted(const QModelIndex &parent, int start, int end); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + + // QColumnView functions + void scrollContentsBy(int dx, int dy); + virtual QAbstractItemView* createColumn(const QModelIndex &rootIndex); + void initializeColumn(QAbstractItemView *column) const; + +private: + Q_DECLARE_PRIVATE(QColumnView) + Q_DISABLE_COPY(QColumnView) + Q_PRIVATE_SLOT(d_func(), void _q_gripMoved(int)) + Q_PRIVATE_SLOT(d_func(), void _q_changeCurrentColumn()) + Q_PRIVATE_SLOT(d_func(), void _q_clicked(const QModelIndex &)) +}; + +#endif // QT_NO_COLUMNVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLUMNVIEW_H + diff --git a/src/gui/itemviews/qcolumnview_p.h b/src/gui/itemviews/qcolumnview_p.h new file mode 100644 index 0000000000..c42c236445 --- /dev/null +++ b/src/gui/itemviews/qcolumnview_p.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEW_P_H +#define QCOLUMNVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qcolumnview.h" + +#ifndef QT_NO_QCOLUMNVIEW + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QColumnViewPreviewColumn : public QAbstractItemView { + +public: + QColumnViewPreviewColumn(QWidget *parent) : QAbstractItemView(parent), previewWidget(0) { + } + + void setPreviewWidget(QWidget *widget) { + previewWidget = widget; + setMinimumWidth(previewWidget->minimumWidth()); + } + + void resizeEvent(QResizeEvent * event){ + if (!previewWidget) + return; + previewWidget->resize( + qMax(previewWidget->minimumWidth(), event->size().width()), + previewWidget->height()); + QSize p = viewport()->size(); + QSize v = previewWidget->size(); + horizontalScrollBar()->setRange(0, v.width() - p.width()); + horizontalScrollBar()->setPageStep(p.width()); + verticalScrollBar()->setRange(0, v.height() - p.height()); + verticalScrollBar()->setPageStep(p.height()); + + QAbstractScrollArea::resizeEvent(event); + } + + QRect visualRect(const QModelIndex &) const + { + return QRect(); + } + void scrollTo(const QModelIndex &, ScrollHint) + { + } + QModelIndex indexAt(const QPoint &) const + { + return QModelIndex(); + } + QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers) + { + return QModelIndex(); + } + int horizontalOffset () const { + return 0; + } + int verticalOffset () const { + return 0; + } + QRegion visualRegionForSelection(const QItemSelection &) const + { + return QRegion(); + } + bool isIndexHidden(const QModelIndex &) const + { + return false; + } + void setSelection(const QRect &, QItemSelectionModel::SelectionFlags) + { + } +private: + QWidget *previewWidget; +}; + +class Q_AUTOTEST_EXPORT QColumnViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QColumnView) + +public: + QColumnViewPrivate(); + ~QColumnViewPrivate(); + void initialize(); + + QAbstractItemView *createColumn(const QModelIndex &index, bool show); + + void updateScrollbars(); + void closeColumns(const QModelIndex &parent = QModelIndex(), bool build = false); + void doLayout(); + void setPreviewWidget(QWidget *widget); + void checkColumnCreation(const QModelIndex &parent); + + + void _q_gripMoved(int offset); + void _q_changeCurrentColumn(); + void _q_clicked(const QModelIndex &index); + void _q_columnsInserted(const QModelIndex &parent, int start, int end); + + QList columns; + QVector columnSizes; // used during init and corner moving + bool showResizeGrips; + int offset; +#ifndef QT_NO_ANIMATION + QPropertyAnimation currentAnimation; +#endif + QWidget *previewWidget; + QAbstractItemView *previewColumn; +}; + +/*! + * This is a delegate that will paint the triangle + */ +class QColumnViewDelegate : public QItemDelegate +{ + +public: + explicit QColumnViewDelegate(QObject *parent = 0) : QItemDelegate(parent) {} + ~QColumnViewDelegate() {} + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; +#endif // QT_NO_QCOLUMNVIEW + + +QT_END_NAMESPACE +#endif //QCOLUMNVIEW_P_H + diff --git a/src/gui/itemviews/qcolumnviewgrip.cpp b/src/gui/itemviews/qcolumnviewgrip.cpp new file mode 100644 index 0000000000..5cd448cc35 --- /dev/null +++ b/src/gui/itemviews/qcolumnviewgrip.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_QCOLUMNVIEW + +#include "qcolumnviewgrip_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + \internal + class QColumnViewGrip + + QColumnViewGrip is created to go inside QAbstractScrollArea's corner. + When the mouse it moved it will resize the scroll area and emit's a signal. + */ + +/* + \internal + \fn void QColumnViewGrip::gripMoved() + Signal that is emitted when the grip moves the parent widget. + */ + +/*! + Creates a new QColumnViewGrip with the given \a parent to view a model. + Use setModel() to set the model. +*/ +QColumnViewGrip::QColumnViewGrip(QWidget *parent) +: QWidget(*new QColumnViewGripPrivate, parent, 0) +{ +#ifndef QT_NO_CURSOR + setCursor(Qt::SplitHCursor); +#endif +} + +/*! + \internal +*/ +QColumnViewGrip::QColumnViewGrip(QColumnViewGripPrivate & dd, QWidget *parent, Qt::WFlags f) +: QWidget(dd, parent, f) +{ +} + +/*! + Destroys the view. +*/ +QColumnViewGrip::~QColumnViewGrip() +{ +} + +/*! + Attempt to resize the parent object by \a offset + returns the amount of offset that it was actually able to resized +*/ +int QColumnViewGrip::moveGrip(int offset) +{ + QWidget *parentWidget = (QWidget*)parent(); + + // first resize the parent + int oldWidth = parentWidget->width(); + int newWidth = oldWidth; + if (isRightToLeft()) + newWidth -= offset; + else + newWidth += offset; + newWidth = qMax(parentWidget->minimumWidth(), newWidth); + parentWidget->resize(newWidth, parentWidget->height()); + + // Then have the view move the widget + int realOffset = parentWidget->width() - oldWidth; + int oldX = parentWidget->x(); + if (realOffset != 0) + emit gripMoved(realOffset); + if (isRightToLeft()) + realOffset = -1 * (oldX - parentWidget->x()); + return realOffset; +} + +/*! + \reimp +*/ +void QColumnViewGrip::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + QStyleOption opt; + opt.initFrom(this); + style()->drawControl(QStyle::CE_ColumnViewGrip, &opt, &painter, this); + event->accept(); +} + +/*! + \reimp + Resize the parent window to the sizeHint +*/ +void QColumnViewGrip::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + QWidget *parentWidget = (QWidget*)parent(); + int offset = parentWidget->sizeHint().width() - parentWidget->width(); + if (isRightToLeft()) + offset *= -1; + moveGrip(offset); + event->accept(); +} + +/*! + \reimp + Begin watching for mouse movements +*/ +void QColumnViewGrip::mousePressEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + d->originalXLocation = event->globalX(); + event->accept(); +} + +/*! + \reimp + Calculate the movement of the grip and moveGrip() and emit gripMoved +*/ +void QColumnViewGrip::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + int offset = event->globalX() - d->originalXLocation; + d->originalXLocation = moveGrip(offset) + d->originalXLocation; + event->accept(); +} + +/*! + \reimp + Stop watching for mouse movements +*/ +void QColumnViewGrip::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QColumnViewGrip); + d->originalXLocation = -1; + event->accept(); +} + +/* + * private object implementation + */ +QColumnViewGripPrivate::QColumnViewGripPrivate() +: QWidgetPrivate(), +originalXLocation(-1) +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_QCOLUMNVIEW diff --git a/src/gui/itemviews/qcolumnviewgrip_p.h b/src/gui/itemviews/qcolumnviewgrip_p.h new file mode 100644 index 0000000000..a621f953ea --- /dev/null +++ b/src/gui/itemviews/qcolumnviewgrip_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLUMNVIEWGRIP_P_H +#define QCOLUMNVIEWGRIP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#ifndef QT_NO_QCOLUMNVIEW + +QT_BEGIN_NAMESPACE + +class QColumnViewGripPrivate; + +class Q_AUTOTEST_EXPORT QColumnViewGrip : public QWidget { + +Q_OBJECT + +Q_SIGNALS: + void gripMoved(int offset); + +public: + explicit QColumnViewGrip(QWidget *parent = 0); + ~QColumnViewGrip(); + int moveGrip(int offset); + +protected: + QColumnViewGrip(QColumnViewGripPrivate &, QWidget *parent = 0, Qt::WFlags f = 0); + void paintEvent(QPaintEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + +private: + Q_DECLARE_PRIVATE(QColumnViewGrip) + Q_DISABLE_COPY(QColumnViewGrip) +}; + +class QColumnViewGripPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QColumnViewGrip) + +public: + QColumnViewGripPrivate(); + ~QColumnViewGripPrivate() {} + + int originalXLocation; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_QCOLUMNVIEW + +#endif //QCOLUMNVIEWGRIP_P_H diff --git a/src/gui/itemviews/qdatawidgetmapper.cpp b/src/gui/itemviews/qdatawidgetmapper.cpp new file mode 100644 index 0000000000..745ef5af91 --- /dev/null +++ b/src/gui/itemviews/qdatawidgetmapper.cpp @@ -0,0 +1,849 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdatawidgetmapper.h" + +#ifndef QT_NO_DATAWIDGETMAPPER + +#include "qabstractitemmodel.h" +#include "qitemdelegate.h" +#include "qmetaobject.h" +#include "qwidget.h" +#include "private/qobject_p.h" +#include "private/qabstractitemmodel_p.h" + +QT_BEGIN_NAMESPACE + +class QDataWidgetMapperPrivate: public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QDataWidgetMapper) + + QDataWidgetMapperPrivate() + : model(QAbstractItemModelPrivate::staticEmptyModel()), delegate(0), + orientation(Qt::Horizontal), submitPolicy(QDataWidgetMapper::AutoSubmit) + { + } + + QAbstractItemModel *model; + QAbstractItemDelegate *delegate; + Qt::Orientation orientation; + QDataWidgetMapper::SubmitPolicy submitPolicy; + QPersistentModelIndex rootIndex; + QPersistentModelIndex currentTopLeft; + + inline int itemCount() + { + return orientation == Qt::Horizontal + ? model->rowCount(rootIndex) + : model->columnCount(rootIndex); + } + + inline int currentIdx() const + { + return orientation == Qt::Horizontal ? currentTopLeft.row() : currentTopLeft.column(); + } + + inline QModelIndex indexAt(int itemPos) + { + return orientation == Qt::Horizontal + ? model->index(currentIdx(), itemPos, rootIndex) + : model->index(itemPos, currentIdx(), rootIndex); + } + + inline void flipEventFilters(QAbstractItemDelegate *oldDelegate, + QAbstractItemDelegate *newDelegate) + { + for (int i = 0; i < widgetMap.count(); ++i) { + QWidget *w = widgetMap.at(i).widget; + if (!w) + continue; + w->removeEventFilter(oldDelegate); + w->installEventFilter(newDelegate); + } + } + + void populate(); + + // private slots + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_commitData(QWidget *); + void _q_closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint); + void _q_modelDestroyed(); + + struct WidgetMapper + { + inline WidgetMapper(QWidget *w = 0, int c = 0, const QModelIndex &i = QModelIndex()) + : widget(w), section(c), currentIndex(i) {} + inline WidgetMapper(QWidget *w, int c, const QModelIndex &i, const QByteArray &p) + : widget(w), section(c), currentIndex(i), property(p) {} + + QPointer widget; + int section; + QPersistentModelIndex currentIndex; + QByteArray property; + }; + + void populate(WidgetMapper &m); + int findWidget(QWidget *w) const; + + bool commit(const WidgetMapper &m); + + QList widgetMap; +}; + +int QDataWidgetMapperPrivate::findWidget(QWidget *w) const +{ + for (int i = 0; i < widgetMap.count(); ++i) { + if (widgetMap.at(i).widget == w) + return i; + } + return -1; +} + +bool QDataWidgetMapperPrivate::commit(const WidgetMapper &m) +{ + if (m.widget.isNull()) + return true; // just ignore + + if (!m.currentIndex.isValid()) + return false; + + // Create copy to avoid passing the widget mappers data + QModelIndex idx = m.currentIndex; + if (m.property.isEmpty()) + delegate->setModelData(m.widget, model, idx); + else + model->setData(idx, m.widget->property(m.property), Qt::EditRole); + + return true; +} + +void QDataWidgetMapperPrivate::populate(WidgetMapper &m) +{ + if (m.widget.isNull()) + return; + + m.currentIndex = indexAt(m.section); + if (m.property.isEmpty()) + delegate->setEditorData(m.widget, m.currentIndex); + else + m.widget->setProperty(m.property, m.currentIndex.data(Qt::EditRole)); +} + +void QDataWidgetMapperPrivate::populate() +{ + for (int i = 0; i < widgetMap.count(); ++i) + populate(widgetMap[i]); +} + +static bool qContainsIndex(const QModelIndex &idx, const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + return idx.row() >= topLeft.row() && idx.row() <= bottomRight.row() + && idx.column() >= topLeft.column() && idx.column() <= bottomRight.column(); +} + +void QDataWidgetMapperPrivate::_q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (topLeft.parent() != rootIndex) + return; // not in our hierarchy + + for (int i = 0; i < widgetMap.count(); ++i) { + WidgetMapper &m = widgetMap[i]; + if (qContainsIndex(m.currentIndex, topLeft, bottomRight)) + populate(m); + } +} + +void QDataWidgetMapperPrivate::_q_commitData(QWidget *w) +{ + if (submitPolicy == QDataWidgetMapper::ManualSubmit) + return; + + int idx = findWidget(w); + if (idx == -1) + return; // not our widget + + commit(widgetMap.at(idx)); +} + +class QFocusHelper: public QWidget +{ +public: + bool focusNextPrevChild(bool next) + { + return QWidget::focusNextPrevChild(next); + } + + static inline void focusNextPrevChild(QWidget *w, bool next) + { + static_cast(w)->focusNextPrevChild(next); + } +}; + +void QDataWidgetMapperPrivate::_q_closeEditor(QWidget *w, QAbstractItemDelegate::EndEditHint hint) +{ + int idx = findWidget(w); + if (idx == -1) + return; // not our widget + + switch (hint) { + case QAbstractItemDelegate::RevertModelCache: { + populate(widgetMap[idx]); + break; } + case QAbstractItemDelegate::EditNextItem: + QFocusHelper::focusNextPrevChild(w, true); + break; + case QAbstractItemDelegate::EditPreviousItem: + QFocusHelper::focusNextPrevChild(w, false); + break; + case QAbstractItemDelegate::SubmitModelCache: + case QAbstractItemDelegate::NoHint: + // nothing + break; + } +} + +void QDataWidgetMapperPrivate::_q_modelDestroyed() +{ + Q_Q(QDataWidgetMapper); + + model = 0; + q->setModel(QAbstractItemModelPrivate::staticEmptyModel()); +} + +/*! + \class QDataWidgetMapper + \brief The QDataWidgetMapper class provides mapping between a section + of a data model to widgets. + \since 4.2 + \ingroup model-view + \ingroup advanced + + QDataWidgetMapper can be used to create data-aware widgets by mapping + them to sections of an item model. A section is a column of a model + if the orientation is horizontal (the default), otherwise a row. + + Every time the current index changes, each widget is updated with data + from the model via the property specified when its mapping was made. + If the user edits the contents of a widget, the changes are read using + the same property and written back to the model. + By default, each widget's \l{Q_PROPERTY()}{user property} is used to + transfer data between the model and the widget. Since Qt 4.3, an + additional addMapping() function enables a named property to be used + instead of the default user property. + + It is possible to set an item delegate to support custom widgets. By default, + a QItemDelegate is used to synchronize the model with the widgets. + + Let us assume that we have an item model named \c{model} with the following contents: + + \table + \row \o 1 \o Qt Norway \o Oslo + \row \o 2 \o Qt Australia \o Brisbane + \row \o 3 \o Qt USA \o Palo Alto + \row \o 4 \o Qt China \o Beijing + \row \o 5 \o Qt Germany \o Berlin + \endtable + + The following code will map the columns of the model to widgets called \c mySpinBox, + \c myLineEdit and \c{myCountryChooser}: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 0 + + After the call to toFirst(), \c mySpinBox displays the value \c{1}, \c myLineEdit + displays \c {Nokia Corporation and/or its subsidiary(-ies)} and \c myCountryChooser displays \c{Oslo}. The + navigational functions toFirst(), toNext(), toPrevious(), toLast() and setCurrentIndex() + can be used to navigate in the model and update the widgets with contents from + the model. + + The setRootIndex() function enables a particular item in a model to be + specified as the root index - children of this item will be mapped to + the relevant widgets in the user interface. + + QDataWidgetMapper supports two submit policies, \c AutoSubmit and \c{ManualSubmit}. + \c AutoSubmit will update the model as soon as the current widget loses focus, + \c ManualSubmit will not update the model unless submit() is called. \c ManualSubmit + is useful when displaying a dialog that lets the user cancel all modifications. + Also, other views that display the model won't update until the user finishes + all their modifications and submits. + + Note that QDataWidgetMapper keeps track of external modifications. If the contents + of the model are updated in another module of the application, the widgets are + updated as well. + + \sa QAbstractItemModel, QAbstractItemDelegate + */ + +/*! \enum QDataWidgetMapper::SubmitPolicy + + This enum describes the possible submit policies a QDataWidgetMapper + supports. + + \value AutoSubmit Whenever a widget loses focus, the widget's current + value is set to the item model. + \value ManualSubmit The model is not updated until submit() is called. + */ + +/*! + \fn void QDataWidgetMapper::currentIndexChanged(int index) + + This signal is emitted after the current index has changed and + all widgets were populated with new data. \a index is the new + current index. + + \sa currentIndex(), setCurrentIndex() + */ + +/*! + Constructs a new QDataWidgetMapper with parent object \a parent. + By default, the orientation is horizontal and the submit policy + is \c{AutoSubmit}. + + \sa setOrientation(), setSubmitPolicy() + */ +QDataWidgetMapper::QDataWidgetMapper(QObject *parent) + : QObject(*new QDataWidgetMapperPrivate, parent) +{ + setItemDelegate(new QItemDelegate(this)); +} + +/*! + Destroys the object. + */ +QDataWidgetMapper::~QDataWidgetMapper() +{ +} + +/*! + Sets the current model to \a model. If another model was set, + all mappings to that old model are cleared. + + \sa model() + */ +void QDataWidgetMapper::setModel(QAbstractItemModel *model) +{ + Q_D(QDataWidgetMapper); + + if (d->model == model) + return; + + if (d->model) { + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, + SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + disconnect(d->model, SIGNAL(destroyed()), this, + SLOT(_q_modelDestroyed())); + } + clearMapping(); + d->rootIndex = QModelIndex(); + d->currentTopLeft = QModelIndex(); + + d->model = model; + + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(destroyed()), SLOT(_q_modelDestroyed())); +} + +/*! + Returns the current model. + + \sa setModel() + */ +QAbstractItemModel *QDataWidgetMapper::model() const +{ + Q_D(const QDataWidgetMapper); + return d->model == QAbstractItemModelPrivate::staticEmptyModel() + ? static_cast(0) + : d->model; +} + +/*! + Sets the item delegate to \a delegate. The delegate will be used to write + data from the model into the widget and from the widget to the model, + using QAbstractItemDelegate::setEditorData() and QAbstractItemDelegate::setModelData(). + + The delegate also decides when to apply data and when to change the editor, + using QAbstractItemDelegate::commitData() and QAbstractItemDelegate::closeEditor(). + + \warning You should not share the same instance of a delegate between widget mappers + or views. Doing so can cause incorrect or unintuitive editing behavior since each + view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} + signal, and attempt to access, modify or close an editor that has already been closed. + */ +void QDataWidgetMapper::setItemDelegate(QAbstractItemDelegate *delegate) +{ + Q_D(QDataWidgetMapper); + QAbstractItemDelegate *oldDelegate = d->delegate; + if (oldDelegate) { + disconnect(oldDelegate, SIGNAL(commitData(QWidget*)), this, SLOT(_q_commitData(QWidget*))); + disconnect(oldDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + this, SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + } + + d->delegate = delegate; + + if (delegate) { + connect(delegate, SIGNAL(commitData(QWidget*)), SLOT(_q_commitData(QWidget*))); + connect(delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), + SLOT(_q_closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint))); + } + + d->flipEventFilters(oldDelegate, delegate); +} + +/*! + Returns the current item delegate. + */ +QAbstractItemDelegate *QDataWidgetMapper::itemDelegate() const +{ + Q_D(const QDataWidgetMapper); + return d->delegate; +} + +/*! + Sets the root item to \a index. This can be used to display + a branch of a tree. Pass an invalid model index to display + the top-most branch. + + \sa rootIndex() + */ +void QDataWidgetMapper::setRootIndex(const QModelIndex &index) +{ + Q_D(QDataWidgetMapper); + d->rootIndex = index; +} + +/*! + Returns the current root index. + + \sa setRootIndex() +*/ +QModelIndex QDataWidgetMapper::rootIndex() const +{ + Q_D(const QDataWidgetMapper); + return QModelIndex(d->rootIndex); +} + +/*! + Adds a mapping between a \a widget and a \a section from the model. + The \a section is a column in the model if the orientation is + horizontal (the default), otherwise a row. + + For the following example, we assume a model \c myModel that + has two columns: the first one contains the names of people in a + group, and the second column contains their ages. The first column + is mapped to the QLineEdit \c nameLineEdit, and the second is + mapped to the QSpinBox \c{ageSpinBox}: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 1 + + \bold{Notes:} + \list + \o If the \a widget is already mapped to a section, the + old mapping will be replaced by the new one. + \o Only one-to-one mappings between sections and widgets are allowed. + It is not possible to map a single section to multiple widgets, or to + map a single widget to multiple sections. + \endlist + + \sa removeMapping(), mappedSection(), clearMapping() + */ +void QDataWidgetMapper::addMapping(QWidget *widget, int section) +{ + Q_D(QDataWidgetMapper); + + removeMapping(widget); + d->widgetMap.append(QDataWidgetMapperPrivate::WidgetMapper(widget, section, d->indexAt(section))); + widget->installEventFilter(d->delegate); +} + +/*! + \since 4.3 + + Essentially the same as addMapping(), but adds the possibility to specify + the property to use specifying \a propertyName. + + \sa addMapping() +*/ + +void QDataWidgetMapper::addMapping(QWidget *widget, int section, const QByteArray &propertyName) +{ + Q_D(QDataWidgetMapper); + + removeMapping(widget); + d->widgetMap.append(QDataWidgetMapperPrivate::WidgetMapper(widget, section, d->indexAt(section), propertyName)); + widget->installEventFilter(d->delegate); +} + +/*! + Removes the mapping for the given \a widget. + + \sa addMapping(), clearMapping() + */ +void QDataWidgetMapper::removeMapping(QWidget *widget) +{ + Q_D(QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return; + + d->widgetMap.removeAt(idx); + widget->removeEventFilter(d->delegate); +} + +/*! + Returns the section the \a widget is mapped to or -1 + if the widget is not mapped. + + \sa addMapping(), removeMapping() + */ +int QDataWidgetMapper::mappedSection(QWidget *widget) const +{ + Q_D(const QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return -1; + + return d->widgetMap.at(idx).section; +} + +/*! + \since 4.3 + Returns the name of the property that is used when mapping + data to the given \a widget. + + \sa mappedSection(), addMapping(), removeMapping() +*/ + +QByteArray QDataWidgetMapper::mappedPropertyName(QWidget *widget) const +{ + Q_D(const QDataWidgetMapper); + + int idx = d->findWidget(widget); + if (idx == -1) + return QByteArray(); + const QDataWidgetMapperPrivate::WidgetMapper &m = d->widgetMap.at(idx); + if (m.property.isEmpty()) + return m.widget->metaObject()->userProperty().name(); + else + return m.property; +} + +/*! + Returns the widget that is mapped at \a section, or + 0 if no widget is mapped at that section. + + \sa addMapping(), removeMapping() + */ +QWidget *QDataWidgetMapper::mappedWidgetAt(int section) const +{ + Q_D(const QDataWidgetMapper); + + for (int i = 0; i < d->widgetMap.count(); ++i) { + if (d->widgetMap.at(i).section == section) + return d->widgetMap.at(i).widget; + } + + return 0; +} + +/*! + Repopulates all widgets with the current data of the model. + All unsubmitted changes will be lost. + + \sa submit(), setSubmitPolicy() + */ +void QDataWidgetMapper::revert() +{ + Q_D(QDataWidgetMapper); + + d->populate(); +} + +/*! + Submits all changes from the mapped widgets to the model. + + For every mapped section, the item delegate reads the current + value from the widget and sets it in the model. Finally, the + model's \l {QAbstractItemModel::}{submit()} method is invoked. + + Returns true if all the values were submitted, otherwise false. + + Note: For database models, QSqlQueryModel::lastError() can be + used to retrieve the last error. + + \sa revert(), setSubmitPolicy() + */ +bool QDataWidgetMapper::submit() +{ + Q_D(QDataWidgetMapper); + + for (int i = 0; i < d->widgetMap.count(); ++i) { + const QDataWidgetMapperPrivate::WidgetMapper &m = d->widgetMap.at(i); + if (!d->commit(m)) + return false; + } + + return d->model->submit(); +} + +/*! + Populates the widgets with data from the first row of the model + if the orientation is horizontal (the default), otherwise + with data from the first column. + + This is equivalent to calling \c setCurrentIndex(0). + + \sa toLast(), setCurrentIndex() + */ +void QDataWidgetMapper::toFirst() +{ + setCurrentIndex(0); +} + +/*! + Populates the widgets with data from the last row of the model + if the orientation is horizontal (the default), otherwise + with data from the last column. + + Calls setCurrentIndex() internally. + + \sa toFirst(), setCurrentIndex() + */ +void QDataWidgetMapper::toLast() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->itemCount() - 1); +} + + +/*! + Populates the widgets with data from the next row of the model + if the orientation is horizontal (the default), otherwise + with data from the next column. + + Calls setCurrentIndex() internally. Does nothing if there is + no next row in the model. + + \sa toPrevious(), setCurrentIndex() + */ +void QDataWidgetMapper::toNext() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->currentIdx() + 1); +} + +/*! + Populates the widgets with data from the previous row of the model + if the orientation is horizontal (the default), otherwise + with data from the previous column. + + Calls setCurrentIndex() internally. Does nothing if there is + no previous row in the model. + + \sa toNext(), setCurrentIndex() + */ +void QDataWidgetMapper::toPrevious() +{ + Q_D(QDataWidgetMapper); + setCurrentIndex(d->currentIdx() - 1); +} + +/*! + \property QDataWidgetMapper::currentIndex + \brief the current row or column + + The widgets are populated with with data from the row at \a index + if the orientation is horizontal (the default), otherwise with + data from the column at \a index. + + \sa setCurrentModelIndex(), toFirst(), toNext(), toPrevious(), toLast() +*/ +void QDataWidgetMapper::setCurrentIndex(int index) +{ + Q_D(QDataWidgetMapper); + + if (index < 0 || index >= d->itemCount()) + return; + d->currentTopLeft = d->orientation == Qt::Horizontal + ? d->model->index(index, 0, d->rootIndex) + : d->model->index(0, index, d->rootIndex); + d->populate(); + + emit currentIndexChanged(index); +} + +int QDataWidgetMapper::currentIndex() const +{ + Q_D(const QDataWidgetMapper); + return d->currentIdx(); +} + +/*! + Sets the current index to the row of the \a index if the + orientation is horizontal (the default), otherwise to the + column of the \a index. + + Calls setCurrentIndex() internally. This convenience slot can be + connected to the signal \l + {QItemSelectionModel::}{currentRowChanged()} or \l + {QItemSelectionModel::}{currentColumnChanged()} of another view's + \l {QItemSelectionModel}{selection model}. + + The following example illustrates how to update all widgets + with new data whenever the selection of a QTableView named + \c myTableView changes: + + \snippet doc/src/snippets/code/src_gui_itemviews_qdatawidgetmapper.cpp 2 + + \sa currentIndex() +*/ +void QDataWidgetMapper::setCurrentModelIndex(const QModelIndex &index) +{ + Q_D(QDataWidgetMapper); + + if (!index.isValid() + || index.model() != d->model + || index.parent() != d->rootIndex) + return; + + setCurrentIndex(d->orientation == Qt::Horizontal ? index.row() : index.column()); +} + +/*! + Clears all mappings. + + \sa addMapping(), removeMapping() + */ +void QDataWidgetMapper::clearMapping() +{ + Q_D(QDataWidgetMapper); + + while (!d->widgetMap.isEmpty()) { + QWidget *w = d->widgetMap.takeLast().widget; + if (w) + w->removeEventFilter(d->delegate); + } +} + +/*! + \property QDataWidgetMapper::orientation + \brief the orientation of the model + + If the orientation is Qt::Horizontal (the default), a widget is + mapped to a column of a data model. The widget will be populated + with the model's data from its mapped column and the row that + currentIndex() points at. + + Use Qt::Horizontal for tabular data that looks like this: + + \table + \row \o 1 \o Qt Norway \o Oslo + \row \o 2 \o Qt Australia \o Brisbane + \row \o 3 \o Qt USA \o Silicon Valley + \row \o 4 \o Qt China \o Beijing + \row \o 5 \o Qt Germany \o Berlin + \endtable + + If the orientation is set to Qt::Vertical, a widget is mapped to + a row. Calling setCurrentIndex() will change the current column. + The widget will be populates with the model's data from its + mapped row and the column that currentIndex() points at. + + Use Qt::Vertical for tabular data that looks like this: + + \table + \row \o 1 \o 2 \o 3 \o 4 \o 5 + \row \o Qt Norway \o Qt Australia \o Qt USA \o Qt China \o Qt Germany + \row \o Oslo \o Brisbane \o Silicon Valley \o Beijing \i Berlin + \endtable + + Changing the orientation clears all existing mappings. +*/ +void QDataWidgetMapper::setOrientation(Qt::Orientation orientation) +{ + Q_D(QDataWidgetMapper); + + if (d->orientation == orientation) + return; + + clearMapping(); + d->orientation = orientation; +} + +Qt::Orientation QDataWidgetMapper::orientation() const +{ + Q_D(const QDataWidgetMapper); + return d->orientation; +} + +/*! + \property QDataWidgetMapper::submitPolicy + \brief the current submit policy + + Changing the current submit policy will revert all widgets + to the current data from the model. +*/ +void QDataWidgetMapper::setSubmitPolicy(SubmitPolicy policy) +{ + Q_D(QDataWidgetMapper); + if (policy == d->submitPolicy) + return; + + revert(); + d->submitPolicy = policy; +} + +QDataWidgetMapper::SubmitPolicy QDataWidgetMapper::submitPolicy() const +{ + Q_D(const QDataWidgetMapper); + return d->submitPolicy; +} + +QT_END_NAMESPACE + +#include "moc_qdatawidgetmapper.cpp" + +#endif // QT_NO_DATAWIDGETMAPPER diff --git a/src/gui/itemviews/qdatawidgetmapper.h b/src/gui/itemviews/qdatawidgetmapper.h new file mode 100644 index 0000000000..9485cb08a8 --- /dev/null +++ b/src/gui/itemviews/qdatawidgetmapper.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATAWIDGETMAPPER_H +#define QDATAWIDGETMAPPER_H + +#include "QtCore/qobject.h" + +#ifndef QT_NO_DATAWIDGETMAPPER + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractItemDelegate; +class QAbstractItemModel; +class QModelIndex; +class QDataWidgetMapperPrivate; + +class Q_GUI_EXPORT QDataWidgetMapper: public QObject +{ + Q_OBJECT + + Q_ENUMS(SubmitPolicy) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) + Q_PROPERTY(SubmitPolicy submitPolicy READ submitPolicy WRITE setSubmitPolicy) + +public: + QDataWidgetMapper(QObject *parent = 0); + ~QDataWidgetMapper(); + + void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + void setItemDelegate(QAbstractItemDelegate *delegate); + QAbstractItemDelegate *itemDelegate() const; + + void setRootIndex(const QModelIndex &index); + QModelIndex rootIndex() const; + + void setOrientation(Qt::Orientation aOrientation); + Qt::Orientation orientation() const; + + enum SubmitPolicy { AutoSubmit, ManualSubmit }; + void setSubmitPolicy(SubmitPolicy policy); + SubmitPolicy submitPolicy() const; + + void addMapping(QWidget *widget, int section); + void addMapping(QWidget *widget, int section, const QByteArray &propertyName); + void removeMapping(QWidget *widget); + int mappedSection(QWidget *widget) const; + QByteArray mappedPropertyName(QWidget *widget) const; + QWidget *mappedWidgetAt(int section) const; + void clearMapping(); + + int currentIndex() const; + +public Q_SLOTS: + void revert(); + bool submit(); + + void toFirst(); + void toLast(); + void toNext(); + void toPrevious(); + virtual void setCurrentIndex(int index); + void setCurrentModelIndex(const QModelIndex &index); + +Q_SIGNALS: + void currentIndexChanged(int index); + +private: + Q_DECLARE_PRIVATE(QDataWidgetMapper) + Q_DISABLE_COPY(QDataWidgetMapper) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &, const QModelIndex &)) + Q_PRIVATE_SLOT(d_func(), void _q_commitData(QWidget *)) + Q_PRIVATE_SLOT(d_func(), void _q_closeEditor(QWidget *, QAbstractItemDelegate::EndEditHint)) + Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_DATAWIDGETMAPPER +#endif + diff --git a/src/gui/itemviews/qdirmodel.cpp b/src/gui/itemviews/qdirmodel.cpp new file mode 100644 index 0000000000..203ac69c12 --- /dev/null +++ b/src/gui/itemviews/qdirmodel.cpp @@ -0,0 +1,1406 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdirmodel.h" + +#ifndef QT_NO_DIRMODEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \enum QDirModel::Roles + \value FileIconRole + \value FilePathRole + \value FileNameRole +*/ + +QT_BEGIN_NAMESPACE + +class QDirModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QDirModel) + +public: + struct QDirNode + { + QDirNode() : parent(0), populated(false), stat(false) {} + ~QDirNode() { children.clear(); } + QDirNode *parent; + QFileInfo info; + QIcon icon; // cache the icon + mutable QVector children; + mutable bool populated; // have we read the children + mutable bool stat; + }; + + QDirModelPrivate() + : resolveSymlinks(true), + readOnly(true), + lazyChildCount(false), + allowAppendChild(true), + iconProvider(&defaultProvider), + shouldStat(true) // ### This is set to false by QFileDialog + { } + + void init(); + QDirNode *node(int row, QDirNode *parent) const; + QVector children(QDirNode *parent, bool stat) const; + + void _q_refresh(); + + void savePersistentIndexes(); + void restorePersistentIndexes(); + + QFileInfoList entryInfoList(const QString &path) const; + QStringList entryList(const QString &path) const; + + QString name(const QModelIndex &index) const; + QString size(const QModelIndex &index) const; + QString type(const QModelIndex &index) const; + QString time(const QModelIndex &index) const; + + void appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const; + static QFileInfo resolvedInfo(QFileInfo info); + + inline QDirNode *node(const QModelIndex &index) const; + inline void populate(QDirNode *parent) const; + inline void clear(QDirNode *parent) const; + + void invalidate(); + + mutable QDirNode root; + bool resolveSymlinks; + bool readOnly; + bool lazyChildCount; + bool allowAppendChild; + + QDir::Filters filters; + QDir::SortFlags sort; + QStringList nameFilters; + + QFileIconProvider *iconProvider; + QFileIconProvider defaultProvider; + + struct SavedPersistent { + QString path; + int column; + QPersistentModelIndexData *data; + QPersistentModelIndex index; + }; + QList savedPersistent; + QPersistentModelIndex toBeRefreshed; + + bool shouldStat; // use the "carefull not to stat directories" mode +}; + +void qt_setDirModelShouldNotStat(QDirModelPrivate *modelPrivate) +{ + modelPrivate->shouldStat = false; +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(const QModelIndex &index) const +{ + QDirModelPrivate::QDirNode *n = + static_cast(index.internalPointer()); + Q_ASSERT(n); + return n; +} + +void QDirModelPrivate::populate(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children = children(parent, parent->stat); + parent->populated = true; +} + +void QDirModelPrivate::clear(QDirNode *parent) const +{ + Q_ASSERT(parent); + parent->children.clear(); + parent->populated = false; +} + +void QDirModelPrivate::invalidate() +{ + QStack nodes; + nodes.push(&root); + while (!nodes.empty()) { + const QDirNode *current = nodes.pop(); + current->stat = false; + const QVector children = current->children; + for (int i = 0; i < children.count(); ++i) + nodes.push(&children.at(i)); + } +} + +/*! + \class QDirModel + \obsolete + \brief The QDirModel class provides a data model for the local filesystem. + + \ingroup model-view + + The usage of QDirModel is not recommended anymore. The + QFileSystemModel class is a more performant alternative. + + This class provides access to the local filesystem, providing functions + for renaming and removing files and directories, and for creating new + directories. In the simplest case, it can be used with a suitable display + widget as part of a browser or filer. + + QDirModel keeps a cache with file information. The cache needs to be + updated with refresh(). + + QDirModel can be accessed using the standard interface provided by + QAbstractItemModel, but it also provides some convenience functions + that are specific to a directory model. The fileInfo() and isDir() + functions provide information about the underlying files and directories + related to items in the model. + + Directories can be created and removed using mkdir(), rmdir(), and the + model will be automatically updated to take the changes into account. + + \note QDirModel requires an instance of a GUI application. + + \sa nameFilters(), setFilter(), filter(), QListView, QTreeView, QFileSystemModel, + {Dir View Example}, {Model Classes} +*/ + +/*! + Constructs a new directory model with the given \a parent. + Only those files matching the \a nameFilters and the + \a filters are included in the model. The sort order is given by the + \a sort flags. +*/ + +QDirModel::QDirModel(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort, + QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + // we always start with QDir::drives() + d->nameFilters = nameFilters.isEmpty() ? QStringList(QLatin1String("*")) : nameFilters; + d->filters = filters; + d->sort = sort; + d->root.parent = 0; + d->root.info = QFileInfo(); + d->clear(&d->root); +} + +/*! + Constructs a directory model with the given \a parent. +*/ + +QDirModel::QDirModel(QObject *parent) + : QAbstractItemModel(*new QDirModelPrivate, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + \internal +*/ +QDirModel::QDirModel(QDirModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QDirModel); + d->init(); +} + +/*! + Destroys this directory model. +*/ + +QDirModel::~QDirModel() +{ + +} + +/*! + Returns the model item index for the item in the \a parent with the + given \a row and \a column. + +*/ + +QModelIndex QDirModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QDirModel); + // note that rowCount does lazy population + if (column < 0 || column >= columnCount(parent) || row < 0 || parent.column() > 0) + return QModelIndex(); + // make sure the list of children is up to date + QDirModelPrivate::QDirNode *p = (d->indexValid(parent) ? d->node(parent) : &d->root); + Q_ASSERT(p); + if (!p->populated) + d->populate(p); // populate without stat'ing + if (row >= p->children.count()) + return QModelIndex(); + // now get the internal pointer for the index + QDirModelPrivate::QDirNode *n = d->node(row, d->indexValid(parent) ? p : 0); + Q_ASSERT(n); + + return createIndex(row, column, n); +} + +/*! + Return the parent of the given \a child model item. +*/ + +QModelIndex QDirModel::parent(const QModelIndex &child) const +{ + Q_D(const QDirModel); + + if (!d->indexValid(child)) + return QModelIndex(); + QDirModelPrivate::QDirNode *node = d->node(child); + QDirModelPrivate::QDirNode *par = (node ? node->parent : 0); + if (par == 0) // parent is the root node + return QModelIndex(); + + // get the parent's row + const QVector children = + par->parent ? par->parent->children : d->root.children; + Q_ASSERT(children.count() > 0); + int row = (par - &(children.at(0))); + Q_ASSERT(row >= 0); + + return createIndex(row, 0, par); +} + +/*! + Returns the number of rows in the \a parent model item. + +*/ + +int QDirModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) { + if (!d->root.populated) // lazy population + d->populate(&d->root); + return d->root.children.count(); + } + if (parent.model() != this) + return 0; + QDirModelPrivate::QDirNode *p = d->node(parent); + if (p->info.isDir() && !p->populated) // lazy population + d->populate(p); + return p->children.count(); +} + +/*! + Returns the number of columns in the \a parent model item. + +*/ + +int QDirModel::columnCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + return 4; +} + +/*! + Returns the data for the model item \a index with the given \a role. +*/ +QVariant QDirModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (index.column()) { + case 0: return d->name(index); + case 1: return d->size(index); + case 2: return d->type(index); + case 3: return d->time(index); + default: + qWarning("data: invalid display value column %d", index.column()); + return QVariant(); + } + } + + if (index.column() == 0) { + if (role == FileIconRole) + return fileIcon(index); + if (role == FilePathRole) + return filePath(index); + if (role == FileNameRole) + return fileName(index); + } + + if (index.column() == 1 && Qt::TextAlignmentRole == role) { + return Qt::AlignRight; + } + return QVariant(); +} + +/*! + Sets the data for the model item \a index with the given \a role to + the data referenced by the \a value. Returns true if successful; + otherwise returns false. + + \sa Qt::ItemDataRole +*/ + +bool QDirModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || index.column() != 0 + || (flags(index) & Qt::ItemIsEditable) == 0 || role != Qt::EditRole) + return false; + + QDirModelPrivate::QDirNode *node = d->node(index); + QDir dir = node->info.dir(); + QString name = value.toString(); + if (dir.rename(node->info.fileName(), name)) { + node->info = QFileInfo(dir, name); + QModelIndex sibling = index.sibling(index.row(), 3); + emit dataChanged(index, sibling); + + d->toBeRefreshed = index.parent(); + QMetaObject::invokeMethod(this, "_q_refresh", Qt::QueuedConnection); + + return true; + } + + return false; +} + +/*! + Returns the data stored under the given \a role for the specified \a section + of the header with the given \a orientation. +*/ + +QVariant QDirModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) { + if (role != Qt::DisplayRole) + return QVariant(); + switch (section) { + case 0: return tr("Name"); + case 1: return tr("Size"); + case 2: return +#ifdef Q_OS_MAC + tr("Kind", "Match OS X Finder"); +#else + tr("Type", "All other platforms"); +#endif + // Windows - Type + // OS X - Kind + // Konqueror - File Type + // Nautilus - Type + case 3: return tr("Date Modified"); + default: return QVariant(); + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + Returns true if the \a parent model item has children; otherwise + returns false. +*/ + +bool QDirModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QDirModel); + if (parent.column() > 0) + return false; + + if (!parent.isValid()) // the invalid index is the "My Computer" item + return true; // the drives + QDirModelPrivate::QDirNode *p = d->node(parent); + Q_ASSERT(p); + + if (d->lazyChildCount) // optimization that only checks for children if the node has been populated + return p->info.isDir(); + return p->info.isDir() && rowCount(parent) > 0; +} + +/*! + Returns the item flags for the given \a index in the model. + + \sa Qt::ItemFlags +*/ +Qt::ItemFlags QDirModel::flags(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (!d->indexValid(index)) + return flags; + flags |= Qt::ItemIsDragEnabled; + if (d->readOnly) + return flags; + QDirModelPrivate::QDirNode *node = d->node(index); + if ((index.column() == 0) && node->info.isWritable()) { + flags |= Qt::ItemIsEditable; + if (fileInfo(index).isDir()) // is directory and is editable + flags |= Qt::ItemIsDropEnabled; + } + return flags; +} + +/*! + Sort the model items in the \a column using the \a order given. + The order is a value defined in \l Qt::SortOrder. +*/ + +void QDirModel::sort(int column, Qt::SortOrder order) +{ + QDir::SortFlags sort = QDir::DirsFirst | QDir::IgnoreCase; + if (order == Qt::DescendingOrder) + sort |= QDir::Reversed; + + switch (column) { + case 0: + sort |= QDir::Name; + break; + case 1: + sort |= QDir::Size; + break; + case 2: + sort |= QDir::Type; + break; + case 3: + sort |= QDir::Time; + break; + default: + break; + } + + setSorting(sort); +} + +/*! + Returns a list of MIME types that can be used to describe a list of items + in the model. +*/ + +QStringList QDirModel::mimeTypes() const +{ + return QStringList(QLatin1String("text/uri-list")); +} + +/*! + Returns an object that contains a serialized description of the specified + \a indexes. The format used to describe the items corresponding to the + indexes is obtained from the mimeTypes() function. + + If the list of indexes is empty, 0 is returned rather than a serialized + empty list. +*/ + +QMimeData *QDirModel::mimeData(const QModelIndexList &indexes) const +{ + QList urls; + QList::const_iterator it = indexes.begin(); + for (; it != indexes.end(); ++it) + if ((*it).column() == 0) + urls << QUrl::fromLocalFile(filePath(*it)); + QMimeData *data = new QMimeData(); + data->setUrls(urls); + return data; +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action over the row in the model specified by the \a row and + \a column and by the \a parent index. + + \sa supportedDropActions() +*/ + +bool QDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int /* row */, int /* column */, const QModelIndex &parent) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return false; + + bool success = true; + QString to = filePath(parent) + QDir::separator(); + QModelIndex _parent = parent; + + QList urls = data->urls(); + QList::const_iterator it = urls.constBegin(); + + switch (action) { + case Qt::CopyAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::copy(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::LinkAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + success = QFile::link(path, to + QFileInfo(path).fileName()) && success; + } + break; + case Qt::MoveAction: + for (; it != urls.constEnd(); ++it) { + QString path = (*it).toLocalFile(); + if (QFile::copy(path, to + QFileInfo(path).fileName()) + && QFile::remove(path)) { + QModelIndex idx=index(QFileInfo(path).path()); + if (idx.isValid()) { + refresh(idx); + //the previous call to refresh may invalidate the _parent. so recreate a new QModelIndex + _parent = index(to); + } + } else { + success = false; + } + } + break; + default: + return false; + } + + if (success) + refresh(_parent); + + return success; +} + +/*! + Returns the drop actions supported by this model. + + \sa Qt::DropActions +*/ + +Qt::DropActions QDirModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; // FIXME: LinkAction is not supported yet +} + +/*! + Sets the \a provider of file icons for the directory model. + +*/ + +void QDirModel::setIconProvider(QFileIconProvider *provider) +{ + Q_D(QDirModel); + d->iconProvider = provider; +} + +/*! + Returns the file icon provider for this directory model. +*/ + +QFileIconProvider *QDirModel::iconProvider() const +{ + Q_D(const QDirModel); + return d->iconProvider; +} + +/*! + Sets the name \a filters for the directory model. +*/ + +void QDirModel::setNameFilters(const QStringList &filters) +{ + Q_D(QDirModel); + d->nameFilters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns a list of filters applied to the names in the model. +*/ + +QStringList QDirModel::nameFilters() const +{ + Q_D(const QDirModel); + return d->nameFilters; +} + +/*! + Sets the directory model's filter to that specified by \a filters. + + Note that the filter you set should always include the QDir::AllDirs enum value, + otherwise QDirModel won't be able to read the directory structure. + + \sa QDir::Filters +*/ + +void QDirModel::setFilter(QDir::Filters filters) +{ + Q_D(QDirModel); + d->filters = filters; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the filter specification for the directory model. + + \sa QDir::Filters +*/ + +QDir::Filters QDirModel::filter() const +{ + Q_D(const QDirModel); + return d->filters; +} + +/*! + Sets the directory model's sorting order to that specified by \a sort. + + \sa QDir::SortFlags +*/ + +void QDirModel::setSorting(QDir::SortFlags sort) +{ + Q_D(QDirModel); + d->sort = sort; + emit layoutAboutToBeChanged(); + if (d->shouldStat) + refresh(QModelIndex()); + else + d->invalidate(); + emit layoutChanged(); +} + +/*! + Returns the sorting method used for the directory model. + + \sa QDir::SortFlags */ + +QDir::SortFlags QDirModel::sorting() const +{ + Q_D(const QDirModel); + return d->sort; +} + +/*! + \property QDirModel::resolveSymlinks + \brief Whether the directory model should resolve symbolic links + + This is only relevant on operating systems that support symbolic + links. +*/ +void QDirModel::setResolveSymlinks(bool enable) +{ + Q_D(QDirModel); + d->resolveSymlinks = enable; +} + +bool QDirModel::resolveSymlinks() const +{ + Q_D(const QDirModel); + return d->resolveSymlinks; +} + +/*! + \property QDirModel::readOnly + \brief Whether the directory model allows writing to the file system + + If this property is set to false, the directory model will allow renaming, copying + and deleting of files and directories. + + This property is true by default +*/ + +void QDirModel::setReadOnly(bool enable) +{ + Q_D(QDirModel); + d->readOnly = enable; +} + +bool QDirModel::isReadOnly() const +{ + Q_D(const QDirModel); + return d->readOnly; +} + +/*! + \property QDirModel::lazyChildCount + \brief Whether the directory model optimizes the hasChildren function + to only check if the item is a directory. + + If this property is set to false, the directory model will make sure that a directory + actually containes any files before reporting that it has children. + Otherwise the directory model will report that an item has children if the item + is a directory. + + This property is false by default +*/ + +void QDirModel::setLazyChildCount(bool enable) +{ + Q_D(QDirModel); + d->lazyChildCount = enable; +} + +bool QDirModel::lazyChildCount() const +{ + Q_D(const QDirModel); + return d->lazyChildCount; +} + +/*! + QDirModel caches file information. This function updates the + cache. The \a parent parameter is the directory from which the + model is updated; the default value will update the model from + root directory of the file system (the entire model). +*/ + +void QDirModel::refresh(const QModelIndex &parent) +{ + Q_D(QDirModel); + + QDirModelPrivate::QDirNode *n = d->indexValid(parent) ? d->node(parent) : &(d->root); + + int rows = n->children.count(); + if (rows == 0) { + emit layoutAboutToBeChanged(); + n->stat = true; // make sure that next time we read all the info + n->populated = false; + emit layoutChanged(); + return; + } + + emit layoutAboutToBeChanged(); + d->savePersistentIndexes(); + d->rowsAboutToBeRemoved(parent, 0, rows - 1); + n->stat = true; // make sure that next time we read all the info + d->clear(n); + d->rowsRemoved(parent, 0, rows - 1); + d->restorePersistentIndexes(); + emit layoutChanged(); +} + +/*! + \overload + + Returns the model item index for the given \a path. +*/ + +QModelIndex QDirModel::index(const QString &path, int column) const +{ + Q_D(const QDirModel); + + if (path.isEmpty() || path == QCoreApplication::translate("QFileDialog", "My Computer")) + return QModelIndex(); + + QString absolutePath = QDir(path).absolutePath(); +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + absolutePath = absolutePath.toLower(); +#endif +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + // On Windows, "filename......." and "filename" are equivalent + if (absolutePath.endsWith(QLatin1Char('.'))) { + int i; + for (i = absolutePath.count() - 1; i >= 0; --i) { + if (absolutePath.at(i) != QLatin1Char('.')) + break; + } + absolutePath = absolutePath.left(i+1); + } +#endif + + QStringList pathElements = absolutePath.split(QLatin1Char('/'), QString::SkipEmptyParts); + if ((pathElements.isEmpty() || !QFileInfo(path).exists()) +#if !defined(Q_OS_WIN) || defined(Q_OS_WINCE) + && path != QLatin1String("/") +#endif + ) + return QModelIndex(); + + QModelIndex idx; // start with "My Computer" + if (!d->root.populated) // make sure the root is populated + d->populate(&d->root); + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (absolutePath.startsWith(QLatin1String("//"))) { // UNC path + QString host = pathElements.first(); + int r = 0; + for (; r < d->root.children.count(); ++r) + if (d->root.children.at(r).info.fileName() == host) + break; + bool childAppended = false; + if (r >= d->root.children.count() && d->allowAppendChild) { + d->appendChild(&d->root, QLatin1String("//") + host); + childAppended = true; + } + idx = index(r, 0, QModelIndex()); + pathElements.pop_front(); + if (childAppended) + emit const_cast(this)->layoutChanged(); + } else +#endif +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + if (pathElements.at(0).endsWith(QLatin1Char(':'))) { + pathElements[0] += QLatin1Char('/'); + } +#else + // add the "/" item, since it is a valid path element on unix + pathElements.prepend(QLatin1String("/")); +#endif + + for (int i = 0; i < pathElements.count(); ++i) { + Q_ASSERT(!pathElements.at(i).isEmpty()); + QString element = pathElements.at(i); + QDirModelPrivate::QDirNode *parent = (idx.isValid() ? d->node(idx) : &d->root); + + Q_ASSERT(parent); + if (!parent->populated) + d->populate(parent); + + // search for the element in the child nodes first + int row = -1; + for (int j = parent->children.count() - 1; j >= 0; --j) { + const QFileInfo& fi = parent->children.at(j).info; + QString childFileName; + childFileName = idx.isValid() ? fi.fileName() : fi.absoluteFilePath(); +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + childFileName = childFileName.toLower(); +#endif + if (childFileName == element) { + if (i == pathElements.count() - 1) + parent->children[j].stat = true; + row = j; + break; + } + } + + // we couldn't find the path element, we create a new node since we _know_ that the path is valid + if (row == -1) { +#if defined(Q_OS_WINCE) + QString newPath; + if (parent->info.isRoot()) + newPath = parent->info.absoluteFilePath() + element; + else + newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#else + QString newPath = parent->info.absoluteFilePath() + QLatin1Char('/') + element; +#endif + if (!d->allowAppendChild || !QFileInfo(newPath).isDir()) + return QModelIndex(); + d->appendChild(parent, newPath); + row = parent->children.count() - 1; + if (i == pathElements.count() - 1) // always stat children of the last element + parent->children[row].stat = true; + emit const_cast(this)->layoutChanged(); + } + + Q_ASSERT(row >= 0); + idx = createIndex(row, 0, static_cast(&parent->children[row])); + Q_ASSERT(idx.isValid()); + } + + if (column != 0) + return idx.sibling(idx.row(), column); + return idx; +} + +/*! + Returns true if the model item \a index represents a directory; + otherwise returns false. +*/ + +bool QDirModel::isDir(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info.isDir(); +} + +/*! + Create a directory with the \a name in the \a parent model item. +*/ + +QModelIndex QDirModel::mkdir(const QModelIndex &parent, const QString &name) +{ + Q_D(QDirModel); + if (!d->indexValid(parent) || isReadOnly()) + return QModelIndex(); + + QDirModelPrivate::QDirNode *p = d->node(parent); + QString path = p->info.absoluteFilePath(); + // For the indexOf() method to work, the new directory has to be a direct child of + // the parent directory. + + QDir newDir(name); + QDir dir(path); + if (newDir.isRelative()) + newDir = QDir(path + QLatin1Char('/') + name); + QString childName = newDir.dirName(); // Get the singular name of the directory + newDir.cdUp(); + + if (newDir.absolutePath() != dir.absolutePath() || !dir.mkdir(name)) + return QModelIndex(); // nothing happened + + refresh(parent); + + QStringList entryList = d->entryList(path); + int r = entryList.indexOf(childName); + QModelIndex i = index(r, 0, parent); // return an invalid index + + return i; +} + +/*! + Removes the directory corresponding to the model item \a index in the + directory model and \bold{deletes the corresponding directory from the + file system}, returning true if successful. If the directory cannot be + removed, false is returned. + + \warning This function deletes directories from the file system; it does + \bold{not} move them to a location where they can be recovered. + + \sa remove() +*/ + +bool QDirModel::rmdir(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (!n->info.isDir()) { + qWarning("rmdir: the node is not a directory"); + return false; + } + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.rmdir(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Removes the model item \a index from the directory model and \bold{deletes the + corresponding file from the file system}, returning true if successful. If the + item cannot be removed, false is returned. + + \warning This function deletes files from the file system; it does \bold{not} + move them to a location where they can be recovered. + + \sa rmdir() +*/ + +bool QDirModel::remove(const QModelIndex &index) +{ + Q_D(QDirModel); + if (!d->indexValid(index) || isReadOnly()) + return false; + + QDirModelPrivate::QDirNode *n = d_func()->node(index); + if (n->info.isDir()) + return false; + + QModelIndex par = parent(index); + QDirModelPrivate::QDirNode *p = d_func()->node(par); + QDir dir = p->info.dir(); // parent dir + QString path = n->info.absoluteFilePath(); + if (!dir.remove(path)) + return false; + + refresh(par); + + return true; +} + +/*! + Returns the path of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::filePath(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (d->indexValid(index)) { + QFileInfo fi = fileInfo(index); + if (d->resolveSymlinks && fi.isSymLink()) + fi = d->resolvedInfo(fi); + return QDir::cleanPath(fi.absoluteFilePath()); + } + return QString(); // root path +} + +/*! + Returns the name of the item stored in the model under the + \a index given. + +*/ + +QString QDirModel::fileName(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return QString(); + QFileInfo info = fileInfo(index); + if (info.isRoot()) + return info.absoluteFilePath(); + if (d->resolveSymlinks && info.isSymLink()) + info = d->resolvedInfo(info); + return info.fileName(); +} + +/*! + Returns the icons for the item stored in the model under the given + \a index. +*/ + +QIcon QDirModel::fileIcon(const QModelIndex &index) const +{ + Q_D(const QDirModel); + if (!d->indexValid(index)) + return d->iconProvider->icon(QFileIconProvider::Computer); + QDirModelPrivate::QDirNode *node = d->node(index); + if (node->icon.isNull()) + node->icon = d->iconProvider->icon(node->info); + return node->icon; +} + +/*! + Returns the file information for the specified model \a index. + + \bold{Note:} If the model index represents a symbolic link in the + underlying filing system, the file information returned will contain + information about the symbolic link itself, regardless of whether + resolveSymlinks is enabled or not. + + \sa QFileInfo::symLinkTarget() +*/ + +QFileInfo QDirModel::fileInfo(const QModelIndex &index) const +{ + Q_D(const QDirModel); + Q_ASSERT(d->indexValid(index)); + + QDirModelPrivate::QDirNode *node = d->node(index); + return node->info; +} + +/*! + \fn QObject *QDirModel::parent() const + \internal +*/ + +/* + The root node is never seen outside the model. +*/ + +void QDirModelPrivate::init() +{ + Q_Q(QDirModel); + filters = QDir::AllEntries | QDir::NoDotAndDotDot; + sort = QDir::Name; + nameFilters << QLatin1String("*"); + root.parent = 0; + root.info = QFileInfo(); + clear(&root); + QHash roles = q->roleNames(); + roles.insertMulti(QDirModel::FileIconRole, "fileIcon"); // == Qt::decoration + roles.insert(QDirModel::FilePathRole, "filePath"); + roles.insert(QDirModel::FileNameRole, "fileName"); + q->setRoleNames(roles); +} + +QDirModelPrivate::QDirNode *QDirModelPrivate::node(int row, QDirNode *parent) const +{ + if (row < 0) + return 0; + + bool isDir = !parent || parent->info.isDir(); + QDirNode *p = (parent ? parent : &root); + if (isDir && !p->populated) + populate(p); // will also resolve symlinks + + if (row >= p->children.count()) { + qWarning("node: the row does not exist"); + return 0; + } + + return const_cast(&p->children.at(row)); +} + +QVector QDirModelPrivate::children(QDirNode *parent, bool stat) const +{ + Q_ASSERT(parent); + QFileInfoList infoList; + if (parent == &root) { + parent = 0; + infoList = QDir::drives(); + } else if (parent->info.isDir()) { + //resolve directory links only if requested. + if (parent->info.isSymLink() && resolveSymlinks) { + QString link = parent->info.symLinkTarget(); + if (link.size() > 1 && link.at(link.size() - 1) == QDir::separator()) + link.chop(1); + if (stat) + infoList = entryInfoList(link); + else + infoList = QDir(link).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } else { + if (stat) + infoList = entryInfoList(parent->info.absoluteFilePath()); + else + infoList = QDir(parent->info.absoluteFilePath()).entryInfoList(nameFilters, QDir::AllEntries | QDir::System); + } + } + + QVector nodes(infoList.count()); + for (int i = 0; i < infoList.count(); ++i) { + QDirNode &node = nodes[i]; + node.parent = parent; + node.info = infoList.at(i); + node.populated = false; + node.stat = shouldStat; + } + + return nodes; +} + +void QDirModelPrivate::_q_refresh() +{ + Q_Q(QDirModel); + q->refresh(toBeRefreshed); + toBeRefreshed = QModelIndex(); +} + +void QDirModelPrivate::savePersistentIndexes() +{ + Q_Q(QDirModel); + savedPersistent.clear(); + foreach (QPersistentModelIndexData *data, persistent.indexes) { + SavedPersistent saved; + QModelIndex index = data->index; + saved.path = q->filePath(index); + saved.column = index.column(); + saved.data = data; + saved.index = index; + savedPersistent.append(saved); + } +} + +void QDirModelPrivate::restorePersistentIndexes() +{ + Q_Q(QDirModel); + bool allow = allowAppendChild; + allowAppendChild = false; + for (int i = 0; i < savedPersistent.count(); ++i) { + QPersistentModelIndexData *data = savedPersistent.at(i).data; + QString path = savedPersistent.at(i).path; + int column = savedPersistent.at(i).column; + QModelIndex idx = q->index(path, column); + if (idx != data->index || data->model == 0) { + //data->model may be equal to 0 if the model is getting destroyed + persistent.indexes.remove(data->index); + data->index = idx; + data->model = q; + if (idx.isValid()) + persistent.indexes.insert(idx, data); + } + } + savedPersistent.clear(); + allowAppendChild = allow; +} + +QFileInfoList QDirModelPrivate::entryInfoList(const QString &path) const +{ + const QDir dir(path); + return dir.entryInfoList(nameFilters, filters, sort); +} + +QStringList QDirModelPrivate::entryList(const QString &path) const +{ + const QDir dir(path); + return dir.entryList(nameFilters, filters, sort); +} + +QString QDirModelPrivate::name(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + const QFileInfo info = n->info; + if (info.isRoot()) { + QString name = info.absoluteFilePath(); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (name.startsWith(QLatin1Char('/'))) // UNC host + return info.fileName(); +#endif +#if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) + if (name.endsWith(QLatin1Char('/'))) + name.chop(1); +#endif + return name; + } + return info.fileName(); +} + +QString QDirModelPrivate::size(const QModelIndex &index) const +{ + const QDirNode *n = node(index); + if (n->info.isDir()) { +#ifdef Q_OS_MAC + return QLatin1String("--"); +#else + return QLatin1String(""); +#endif + // Windows - "" + // OS X - "--" + // Konqueror - "4 KB" + // Nautilus - "9 items" (the number of children) + } + + // According to the Si standard KB is 1000 bytes, KiB is 1024 + // but on windows sizes are calulated by dividing by 1024 so we do what they do. + const quint64 kb = 1024; + const quint64 mb = 1024 * kb; + const quint64 gb = 1024 * mb; + const quint64 tb = 1024 * gb; + quint64 bytes = n->info.size(); + if (bytes >= tb) + return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3)); + if (bytes >= gb) + return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2)); + if (bytes >= mb) + return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1)); + if (bytes >= kb) + return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb)); + return QFileSystemModel::tr("%1 byte(s)").arg(QLocale().toString(bytes)); +} + +QString QDirModelPrivate::type(const QModelIndex &index) const +{ + return iconProvider->type(node(index)->info); +} + +QString QDirModelPrivate::time(const QModelIndex &index) const +{ +#ifndef QT_NO_DATESTRING + return node(index)->info.lastModified().toString(Qt::LocalDate); +#else + Q_UNUSED(index); + return QString(); +#endif +} + +void QDirModelPrivate::appendChild(QDirModelPrivate::QDirNode *parent, const QString &path) const +{ + QDirModelPrivate::QDirNode node; + node.populated = false; + node.stat = shouldStat; + node.parent = (parent == &root ? 0 : parent); + node.info = QFileInfo(path); + node.info.setCaching(true); + + // The following append(node) may reallocate the vector, thus + // we need to update the pointers to the childnodes parent. + QDirModelPrivate *that = const_cast(this); + that->savePersistentIndexes(); + parent->children.append(node); + for (int i = 0; i < parent->children.count(); ++i) { + QDirNode *childNode = &parent->children[i]; + for (int j = 0; j < childNode->children.count(); ++j) + childNode->children[j].parent = childNode; + } + that->restorePersistentIndexes(); +} + +QFileInfo QDirModelPrivate::resolvedInfo(QFileInfo info) +{ +#ifdef Q_OS_WIN + // On windows, we cannot create a shortcut to a shortcut. + return QFileInfo(info.symLinkTarget()); +#else + QStringList paths; + do { + QFileInfo link(info.symLinkTarget()); + if (link.isRelative()) + info.setFile(info.absolutePath(), link.filePath()); + else + info = link; + if (paths.contains(info.absoluteFilePath())) + return QFileInfo(); + paths.append(info.absoluteFilePath()); + } while (info.isSymLink()); + return info; +#endif +} + +QT_END_NAMESPACE + +#include "moc_qdirmodel.cpp" + +#endif // QT_NO_DIRMODEL diff --git a/src/gui/itemviews/qdirmodel.h b/src/gui/itemviews/qdirmodel.h new file mode 100644 index 0000000000..2b1cd9d81b --- /dev/null +++ b/src/gui/itemviews/qdirmodel.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDIRMODEL_H +#define QDIRMODEL_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DIRMODEL + +class QDirModelPrivate; + +class Q_GUI_EXPORT QDirModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(bool resolveSymlinks READ resolveSymlinks WRITE setResolveSymlinks) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool lazyChildCount READ lazyChildCount WRITE setLazyChildCount) + +public: + enum Roles { + FileIconRole = Qt::DecorationRole, + FilePathRole = Qt::UserRole + 1, + FileNameRole + }; + + QDirModel(const QStringList &nameFilters, QDir::Filters filters, + QDir::SortFlags sort, QObject *parent = 0); + explicit QDirModel(QObject *parent = 0); + ~QDirModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + bool hasChildren(const QModelIndex &index = QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + // QDirModel specific API + + void setIconProvider(QFileIconProvider *provider); + QFileIconProvider *iconProvider() const; + + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const; + + void setFilter(QDir::Filters filters); + QDir::Filters filter() const; + + void setSorting(QDir::SortFlags sort); + QDir::SortFlags sorting() const; + + void setResolveSymlinks(bool enable); + bool resolveSymlinks() const; + + void setReadOnly(bool enable); + bool isReadOnly() const; + + void setLazyChildCount(bool enable); + bool lazyChildCount() const; + + QModelIndex index(const QString &path, int column = 0) const; + + bool isDir(const QModelIndex &index) const; + QModelIndex mkdir(const QModelIndex &parent, const QString &name); + bool rmdir(const QModelIndex &index); + bool remove(const QModelIndex &index); + + QString filePath(const QModelIndex &index) const; + QString fileName(const QModelIndex &index) const; + QIcon fileIcon(const QModelIndex &index) const; + QFileInfo fileInfo(const QModelIndex &index) const; + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + +public Q_SLOTS: + void refresh(const QModelIndex &parent = QModelIndex()); + +protected: + QDirModel(QDirModelPrivate &, QObject *parent = 0); + friend class QFileDialogPrivate; + +private: + Q_DECLARE_PRIVATE(QDirModel) + Q_DISABLE_COPY(QDirModel) + Q_PRIVATE_SLOT(d_func(), void _q_refresh()) +}; + +#endif // QT_NO_DIRMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDIRMODEL_H diff --git a/src/gui/itemviews/qfileiconprovider.cpp b/src/gui/itemviews/qfileiconprovider.cpp new file mode 100644 index 0000000000..ae93054cf3 --- /dev/null +++ b/src/gui/itemviews/qfileiconprovider.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfileiconprovider.h" + +#ifndef QT_NO_FILEICONPROVIDER +#include +#include +#include +#include +#if defined(Q_WS_WIN) +# define _WIN32_IE 0x0500 +# include +# include +# include +#elif defined(Q_WS_MAC) +# include +#endif + +#include +#include + +#if defined(Q_WS_X11) && !defined(Q_NO_STYLE_GTK) +# include +# include +#endif + +#ifndef SHGFI_ADDOVERLAYS +# define SHGFI_ADDOVERLAYS 0x000000020 +# define SHGFI_OVERLAYINDEX 0x000000040 +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QFileIconProvider + + \brief The QFileIconProvider class provides file icons for the QDirModel and the QFileSystemModel classes. +*/ + +/*! + \enum QFileIconProvider::IconType + \value Computer + \value Desktop + \value Trashcan + \value Network + \value Drive + \value Folder + \value File +*/ + +class QFileIconProviderPrivate +{ + Q_DECLARE_PUBLIC(QFileIconProvider) + +public: + QFileIconProviderPrivate(); + QIcon getIcon(QStyle::StandardPixmap name) const; +#ifdef Q_WS_WIN + QIcon getWinIcon(const QFileInfo &fi) const; +#elif defined(Q_WS_MAC) + QIcon getMacIcon(const QFileInfo &fi) const; +#endif + QFileIconProvider *q_ptr; + const QString homePath; + +private: + mutable QIcon file; + mutable QIcon fileLink; + mutable QIcon directory; + mutable QIcon directoryLink; + mutable QIcon harddisk; + mutable QIcon floppy; + mutable QIcon cdrom; + mutable QIcon ram; + mutable QIcon network; + mutable QIcon computer; + mutable QIcon desktop; + mutable QIcon trashcan; + mutable QIcon generic; + mutable QIcon home; +}; + +QFileIconProviderPrivate::QFileIconProviderPrivate() : + homePath(QDir::home().absolutePath()) +{ +} + +QIcon QFileIconProviderPrivate::getIcon(QStyle::StandardPixmap name) const +{ + switch (name) { + case QStyle::SP_FileIcon: + if (file.isNull()) + file = QApplication::style()->standardIcon(name); + return file; + case QStyle::SP_FileLinkIcon: + if (fileLink.isNull()) + fileLink = QApplication::style()->standardIcon(name); + return fileLink; + case QStyle::SP_DirIcon: + if (directory.isNull()) + directory = QApplication::style()->standardIcon(name); + return directory; + case QStyle::SP_DirLinkIcon: + if (directoryLink.isNull()) + directoryLink = QApplication::style()->standardIcon(name); + return directoryLink; + case QStyle::SP_DriveHDIcon: + if (harddisk.isNull()) + harddisk = QApplication::style()->standardIcon(name); + return harddisk; + case QStyle::SP_DriveFDIcon: + if (floppy.isNull()) + floppy = QApplication::style()->standardIcon(name); + return floppy; + case QStyle::SP_DriveCDIcon: + if (cdrom.isNull()) + cdrom = QApplication::style()->standardIcon(name); + return cdrom; + case QStyle::SP_DriveNetIcon: + if (network.isNull()) + network = QApplication::style()->standardIcon(name); + return network; + case QStyle::SP_ComputerIcon: + if (computer.isNull()) + computer = QApplication::style()->standardIcon(name); + return computer; + case QStyle::SP_DesktopIcon: + if (desktop.isNull()) + desktop = QApplication::style()->standardIcon(name); + return desktop; + case QStyle::SP_TrashIcon: + if (trashcan.isNull()) + trashcan = QApplication::style()->standardIcon(name); + return trashcan; + case QStyle::SP_DirHomeIcon: + if (home.isNull()) + home = QApplication::style()->standardIcon(name); + return home; + default: + return QIcon(); + } + return QIcon(); +} + +/*! + Constructs a file icon provider. +*/ + +QFileIconProvider::QFileIconProvider() + : d_ptr(new QFileIconProviderPrivate) +{ +} + +/*! + Destroys the file icon provider. + +*/ + +QFileIconProvider::~QFileIconProvider() +{ +} + +/*! + Returns an icon set for the given \a type. +*/ + +QIcon QFileIconProvider::icon(IconType type) const +{ + Q_D(const QFileIconProvider); + switch (type) { + case Computer: + return d->getIcon(QStyle::SP_ComputerIcon); + case Desktop: + return d->getIcon(QStyle::SP_DesktopIcon); + case Trashcan: + return d->getIcon(QStyle::SP_TrashIcon); + case Network: + return d->getIcon(QStyle::SP_DriveNetIcon); + case Drive: + return d->getIcon(QStyle::SP_DriveHDIcon); + case Folder: + return d->getIcon(QStyle::SP_DirIcon); + case File: + return d->getIcon(QStyle::SP_FileIcon); + default: + break; + }; + return QIcon(); +} + +#ifdef Q_WS_WIN +QIcon QFileIconProviderPrivate::getWinIcon(const QFileInfo &fileInfo) const +{ + QIcon retIcon; + const QString fileExtension = QLatin1Char('.') + fileInfo.suffix().toUpper(); + + QString key; + if (fileInfo.isFile() && !fileInfo.isExecutable() && !fileInfo.isSymLink() && fileExtension != QLatin1String(".ICO")) + key = QLatin1String("qt_") + fileExtension; + + QPixmap pixmap; + if (!key.isEmpty()) { + QPixmapCache::find(key, pixmap); + } + + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(key + QLatin1Char('l'), pixmap)) + retIcon.addPixmap(pixmap); + return retIcon; + } + + /* We don't use the variable, but by storing it statically, we + * ensure CoInitialize is only called once. */ + static HRESULT comInit = CoInitialize(NULL); + Q_UNUSED(comInit); + + SHFILEINFO info; + unsigned long val = 0; + + //Get the small icon +#ifndef Q_OS_WINCE + val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX); +#else + val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_SMALLICON|SHGFI_SYSICONINDEX); +#endif + + // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases + if (val && info.hIcon) { + if (fileInfo.isDir() && !fileInfo.isRoot()) { + //using the unique icon index provided by windows save us from duplicate keys + key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon); + QPixmapCache::find(key, pixmap); + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(key + QLatin1Char('l'), pixmap)) + retIcon.addPixmap(pixmap); + DestroyIcon(info.hIcon); + return retIcon; + } + } + if (pixmap.isNull()) { +#ifndef Q_OS_WINCE + pixmap = QPixmap::fromWinHICON(info.hIcon); +#else + pixmap = QPixmap::fromWinHICON(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL)); +#endif + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (!key.isEmpty()) + QPixmapCache::insert(key, pixmap); + } + else { + qWarning("QFileIconProviderPrivate::getWinIcon() no small icon found"); + } + } + DestroyIcon(info.hIcon); + } + + //Get the big icon +#ifndef Q_OS_WINCE + val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_LARGEICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX); +#else + val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info, + sizeof(SHFILEINFO), SHGFI_LARGEICON|SHGFI_SYSICONINDEX); +#endif + if (val && info.hIcon) { + if (fileInfo.isDir() && !fileInfo.isRoot()) { + //using the unique icon index provided by windows save us from duplicate keys + key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon); + } +#ifndef Q_OS_WINCE + pixmap = QPixmap::fromWinHICON(info.hIcon); +#else + pixmap = QPixmap::fromWinHICON(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL)); +#endif + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (!key.isEmpty()) + QPixmapCache::insert(key + QLatin1Char('l'), pixmap); + } + else { + qWarning("QFileIconProviderPrivate::getWinIcon() no large icon found"); + } + DestroyIcon(info.hIcon); + } + return retIcon; +} + +#elif defined(Q_WS_MAC) +QIcon QFileIconProviderPrivate::getMacIcon(const QFileInfo &fi) const +{ + QIcon retIcon; + QString fileExtension = fi.suffix().toUpper(); + fileExtension.prepend(QLatin1String(".")); + + const QString keyBase = QLatin1String("qt_") + fileExtension; + + QPixmap pixmap; + if (fi.isFile() && !fi.isExecutable() && !fi.isSymLink()) { + QPixmapCache::find(keyBase + QLatin1String("16"), pixmap); + } + + if (!pixmap.isNull()) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(keyBase + QLatin1String("32"), pixmap)) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(keyBase + QLatin1String("64"), pixmap)) { + retIcon.addPixmap(pixmap); + if (QPixmapCache::find(keyBase + QLatin1String("128"), pixmap)) { + retIcon.addPixmap(pixmap); + return retIcon; + } + } + } + } + + + FSRef macRef; + OSStatus status = FSPathMakeRef(reinterpret_cast(fi.canonicalFilePath().toUtf8().constData()), + &macRef, 0); + if (status != noErr) + return retIcon; + FSCatalogInfo info; + HFSUniStr255 macName; + status = FSGetCatalogInfo(&macRef, kIconServicesCatalogInfoMask, &info, &macName, 0, 0); + if (status != noErr) + return retIcon; + IconRef iconRef; + SInt16 iconLabel; + status = GetIconRefFromFileInfo(&macRef, macName.length, macName.unicode, + kIconServicesCatalogInfoMask, &info, kIconServicesNormalUsageFlag, + &iconRef, &iconLabel); + if (status != noErr) + return retIcon; + qt_mac_constructQIconFromIconRef(iconRef, 0, &retIcon); + ReleaseIconRef(iconRef); + + pixmap = retIcon.pixmap(16); + QPixmapCache::insert(keyBase + QLatin1String("16"), pixmap); + pixmap = retIcon.pixmap(32); + QPixmapCache::insert(keyBase + QLatin1String("32"), pixmap); + pixmap = retIcon.pixmap(64); + QPixmapCache::insert(keyBase + QLatin1String("64"), pixmap); + pixmap = retIcon.pixmap(128); + QPixmapCache::insert(keyBase + QLatin1String("128"), pixmap); + + return retIcon; +} +#endif + + +/*! + Returns an icon for the file described by \a info. +*/ + +QIcon QFileIconProvider::icon(const QFileInfo &info) const +{ + Q_D(const QFileIconProvider); + + QIcon platformIcon = qt_guiPlatformPlugin()->fileSystemIcon(info); + if (!platformIcon.isNull()) + return platformIcon; + +#if defined(Q_WS_X11) && !defined(QT_NO_STYLE_GTK) + if (X11->desktopEnvironment == DE_GNOME) { + QIcon gtkIcon = QGtkStylePrivate::getFilesystemIcon(info); + if (!gtkIcon.isNull()) + return gtkIcon; + } +#endif + +#ifdef Q_WS_MAC + QIcon retIcon = d->getMacIcon(info); + if (!retIcon.isNull()) + return retIcon; +#elif defined Q_WS_WIN + QIcon icon = d->getWinIcon(info); + if (!icon.isNull()) + return icon; +#endif + if (info.isRoot()) +#if defined (Q_WS_WIN) && !defined(Q_WS_WINCE) + { + UINT type = GetDriveType((wchar_t *)info.absoluteFilePath().utf16()); + + switch (type) { + case DRIVE_REMOVABLE: + return d->getIcon(QStyle::SP_DriveFDIcon); + case DRIVE_FIXED: + return d->getIcon(QStyle::SP_DriveHDIcon); + case DRIVE_REMOTE: + return d->getIcon(QStyle::SP_DriveNetIcon); + case DRIVE_CDROM: + return d->getIcon(QStyle::SP_DriveCDIcon); + case DRIVE_RAMDISK: + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: + default: + return d->getIcon(QStyle::SP_DriveHDIcon); + } + } +#else + return d->getIcon(QStyle::SP_DriveHDIcon); +#endif + if (info.isFile()) { + if (info.isSymLink()) + return d->getIcon(QStyle::SP_FileLinkIcon); + else + return d->getIcon(QStyle::SP_FileIcon); + } + if (info.isDir()) { + if (info.isSymLink()) { + return d->getIcon(QStyle::SP_DirLinkIcon); + } else { + if (info.absoluteFilePath() == d->homePath) { + return d->getIcon(QStyle::SP_DirHomeIcon); + } else { + return d->getIcon(QStyle::SP_DirIcon); + } + } + } + return QIcon(); +} + +/*! + Returns the type of the file described by \a info. +*/ + +QString QFileIconProvider::type(const QFileInfo &info) const +{ + if (info.isRoot()) + return QApplication::translate("QFileDialog", "Drive"); + if (info.isFile()) { + if (!info.suffix().isEmpty()) + return info.suffix() + QLatin1Char(' ') + QApplication::translate("QFileDialog", "File"); + return QApplication::translate("QFileDialog", "File"); + } + + if (info.isDir()) +#ifdef Q_WS_WIN + return QApplication::translate("QFileDialog", "File Folder", "Match Windows Explorer"); +#else + return QApplication::translate("QFileDialog", "Folder", "All other platforms"); +#endif + // Windows - "File Folder" + // OS X - "Folder" + // Konqueror - "Folder" + // Nautilus - "folder" + + if (info.isSymLink()) +#ifdef Q_OS_MAC + return QApplication::translate("QFileDialog", "Alias", "Mac OS X Finder"); +#else + return QApplication::translate("QFileDialog", "Shortcut", "All other platforms"); +#endif + // OS X - "Alias" + // Windows - "Shortcut" + // Konqueror - "Folder" or "TXT File" i.e. what it is pointing to + // Nautilus - "link to folder" or "link to object file", same as Konqueror + + return QApplication::translate("QFileDialog", "Unknown"); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/itemviews/qfileiconprovider.h b/src/gui/itemviews/qfileiconprovider.h new file mode 100644 index 0000000000..5a70f80062 --- /dev/null +++ b/src/gui/itemviews/qfileiconprovider.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFILEICONPROVIDER_H +#define QFILEICONPROVIDER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_FILEICONPROVIDER + +class QFileIconProviderPrivate; + +class Q_GUI_EXPORT QFileIconProvider +{ +public: + QFileIconProvider(); + virtual ~QFileIconProvider(); + enum IconType { Computer, Desktop, Trashcan, Network, Drive, Folder, File }; + virtual QIcon icon(IconType type) const; + virtual QIcon icon(const QFileInfo &info) const; + virtual QString type(const QFileInfo &info) const; + +private: + Q_DECLARE_PRIVATE(QFileIconProvider) + QScopedPointer d_ptr; + Q_DISABLE_COPY(QFileIconProvider) +}; + +#endif // QT_NO_FILEICONPROVIDER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFILEICONPROVIDER_H + diff --git a/src/gui/itemviews/qheaderview.cpp b/src/gui/itemviews/qheaderview.cpp new file mode 100644 index 0000000000..471c199bc2 --- /dev/null +++ b/src/gui/itemviews/qheaderview.cpp @@ -0,0 +1,3615 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qheaderview.h" + +#ifndef QT_NO_ITEMVIEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_DATASTREAM +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &out, const QHeaderViewPrivate::SectionSpan &span) +{ + span.write(out); + return out; +} + +QDataStream &operator>>(QDataStream &in, QHeaderViewPrivate::SectionSpan &span) +{ + span.read(in); + return in; +} +#endif // QT_NO_DATASTREAM + + +/*! + \class QHeaderView + + \brief The QHeaderView class provides a header row or header column for + item views. + + \ingroup model-view + + + A QHeaderView displays the headers used in item views such as the + QTableView and QTreeView classes. It takes the place of Qt3's \c QHeader + class previously used for the same purpose, but uses the Qt's model/view + architecture for consistency with the item view classes. + + The QHeaderView class is one of the \l{Model/View Classes} and is part of + Qt's \l{Model/View Programming}{model/view framework}. + + The header gets the data for each section from the model using the + QAbstractItemModel::headerData() function. You can set the data by using + QAbstractItemModel::setHeaderData(). + + Each header has an orientation() and a number of sections, given by the + count() function. A section refers to a part of the header - either a row + or a column, depending on the orientation. + + Sections can be moved and resized using moveSection() and resizeSection(); + they can also be hidden and shown with hideSection() and showSection(). + + Each section of a header is described by a section ID, specified by its + section(), and can be located at a particular visualIndex() in the header. + A section can have a sort indicator set with setSortIndicator(); this + indicates whether the items in the associated item view will be sorted in + the order given by the section. + + For a horizontal header the section is equivalent to a column in the model, + and for a vertical header the section is equivalent to a row in the model. + + \section1 Moving Header Sections + + A header can be fixed in place, or made movable with setMovable(). It can + be made clickable with setClickable(), and has resizing behavior in + accordance with setResizeMode(). + + \note Double-clicking on a header to resize a section only applies for + visible rows. + + A header will emit sectionMoved() if the user moves a section, + sectionResized() if the user resizes a section, and sectionClicked() as + well as sectionHandleDoubleClicked() in response to mouse clicks. A header + will also emit sectionCountChanged() and sectionAutoResize(). + + You can identify a section using the logicalIndex() and logicalIndexAt() + functions, or by its index position, using the visualIndex() and + visualIndexAt() functions. The visual index will change if a section is + moved, but the logical index will not change. + + \section1 Appearance + + QTableWidget and QTableView create default headers. If you want + the headers to be visible, you can use \l{QFrame::}{setVisible()}. + + Not all \l{Qt::}{ItemDataRole}s will have an effect on a + QHeaderView. If you need to draw other roles, you can subclass + QHeaderView and reimplement \l{QHeaderView::}{paintEvent()}. + QHeaderView respects the following item data roles: + \l{Qt::}{TextAlignmentRole}, \l{Qt::}{DisplayRole}, + \l{Qt::}{FontRole}, \l{Qt::}{DecorationRole}, + \l{Qt::}{ForegroundRole}, and \l{Qt::}{BackgroundRole}. + + \note Each header renders the data for each section itself, and does not + rely on a delegate. As a result, calling a header's setItemDelegate() + function will have no effect. + + \sa {Model/View Programming}, QListView, QTableView, QTreeView +*/ + +/*! + \enum QHeaderView::ResizeMode + + The resize mode specifies the behavior of the header sections. It can be + set on the entire header view or on individual sections using + setResizeMode(). + + \value Interactive The user can resize the section. The section can also be + resized programmatically using resizeSection(). The section size + defaults to \l defaultSectionSize. (See also + \l cascadingSectionResizes.) + + \value Fixed The user cannot resize the section. The section can only be + resized programmatically using resizeSection(). The section size + defaults to \l defaultSectionSize. + + \value Stretch QHeaderView will automatically resize the section to fill + the available space. The size cannot be changed by the user or + programmatically. + + \value ResizeToContents QHeaderView will automatically resize the section + to its optimal size based on the contents of the entire column or + row. The size cannot be changed by the user or programmatically. + (This value was introduced in 4.2) + + The following values are obsolete: + \value Custom Use Fixed instead. + + \sa setResizeMode() stretchLastSection minimumSectionSize +*/ + +/*! + \fn void QHeaderView::sectionMoved(int logicalIndex, int oldVisualIndex, + int newVisualIndex) + + This signal is emitted when a section is moved. The section's logical index + is specified by \a logicalIndex, the old index by \a oldVisualIndex, and + the new index position by \a newVisualIndex. + + \sa moveSection() +*/ + +/*! + \fn void QHeaderView::sectionResized(int logicalIndex, int oldSize, + int newSize) + + This signal is emitted when a section is resized. The section's logical + number is specified by \a logicalIndex, the old size by \a oldSize, and the + new size by \a newSize. + + \sa resizeSection() +*/ + +/*! + \fn void QHeaderView::sectionPressed(int logicalIndex) + + This signal is emitted when a section is pressed. The section's logical + index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sectionClicked(int logicalIndex) + + This signal is emitted when a section is clicked. The section's logical + index is specified by \a logicalIndex. + + Note that the sectionPressed signal will also be emitted. + + \sa setClickable(), sectionPressed() +*/ + +/*! + \fn void QHeaderView::sectionEntered(int logicalIndex) + \since 4.3 + + This signal is emitted when the cursor moves over the section and the left + mouse button is pressed. The section's logical index is specified by + \a logicalIndex. + + \sa setClickable(), sectionPressed() +*/ + +/*! + \fn void QHeaderView::sectionDoubleClicked(int logicalIndex) + + This signal is emitted when a section is double-clicked. The section's + logical index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sectionCountChanged(int oldCount, int newCount) + + This signal is emitted when the number of sections changes, i.e., when + sections are added or deleted. The original count is specified by + \a oldCount, and the new count by \a newCount. + + \sa count(), length(), headerDataChanged() +*/ + +/*! + \fn void QHeaderView::sectionHandleDoubleClicked(int logicalIndex) + + This signal is emitted when a section is double-clicked. The section's + logical index is specified by \a logicalIndex. + + \sa setClickable() +*/ + +/*! + \fn void QHeaderView::sortIndicatorChanged(int logicalIndex, + Qt::SortOrder order) + \since 4.3 + + This signal is emitted when the section containing the sort indicator or + the order indicated is changed. The section's logical index is specified + by \a logicalIndex and the sort order is specified by \a order. + + \sa setSortIndicator() +*/ + +/*! + \fn void QHeaderView::sectionAutoResize(int logicalIndex, + QHeaderView::ResizeMode mode) + + This signal is emitted when a section is automatically resized. The + section's logical index is specified by \a logicalIndex, and the resize + mode by \a mode. + + \sa setResizeMode(), stretchLastSection() +*/ +// ### Qt 5: change to sectionAutoResized() + +/*! + \fn void QHeaderView::geometriesChanged() + \since 4.2 + + This signal is emitted when the header's geometries have changed. +*/ + +/*! + \property QHeaderView::highlightSections + \brief whether the sections containing selected items are highlighted + + By default, this property is false. +*/ + +/*! + Creates a new generic header with the given \a orientation and \a parent. +*/ +QHeaderView::QHeaderView(Qt::Orientation orientation, QWidget *parent) + : QAbstractItemView(*new QHeaderViewPrivate, parent) +{ + Q_D(QHeaderView); + d->setDefaultValues(orientation); + initialize(); +} + +/*! + \internal +*/ +QHeaderView::QHeaderView(QHeaderViewPrivate &dd, + Qt::Orientation orientation, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QHeaderView); + d->setDefaultValues(orientation); + initialize(); +} + +/*! + Destroys the header. +*/ + +QHeaderView::~QHeaderView() +{ +} + +/*! + \internal +*/ +void QHeaderView::initialize() +{ + Q_D(QHeaderView); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameStyle(NoFrame); + setFocusPolicy(Qt::NoFocus); + d->viewport->setMouseTracking(true); + d->viewport->setBackgroundRole(QPalette::Button); + d->textElideMode = Qt::ElideNone; + delete d->itemDelegate; +} + +/*! + \reimp +*/ +void QHeaderView::setModel(QAbstractItemModel *model) +{ + if (model == this->model()) + return; + Q_D(QHeaderView); + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + if (d->orientation == Qt::Horizontal) { + QObject::disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } else { + QObject::disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } + QObject::disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(headerDataChanged(Qt::Orientation,int,int))); + QObject::disconnect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + } + + if (model && model != QAbstractItemModelPrivate::staticEmptyModel()) { + if (d->orientation == Qt::Horizontal) { + QObject::connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } else { + QObject::connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(sectionsInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(sectionsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sectionsRemoved(QModelIndex,int,int))); + } + QObject::connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(headerDataChanged(Qt::Orientation,int,int))); + QObject::connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_layoutAboutToBeChanged())); + } + + d->state = QHeaderViewPrivate::NoClear; + QAbstractItemView::setModel(model); + d->state = QHeaderViewPrivate::NoState; + + // Users want to set sizes and modes before the widget is shown. + // Thus, we have to initialize when the model is set, + // and not lazily like we do in the other views. + initializeSections(); +} + +/*! + Returns the orientation of the header. + + \sa Qt::Orientation +*/ + +Qt::Orientation QHeaderView::orientation() const +{ + Q_D(const QHeaderView); + return d->orientation; +} + +/*! + Returns the offset of the header: this is the header's left-most (or + top-most for vertical headers) visible pixel. + + \sa setOffset() +*/ + +int QHeaderView::offset() const +{ + Q_D(const QHeaderView); + return d->offset; +} + +/*! + \fn void QHeaderView::setOffset(int offset) + + Sets the header's offset to \a offset. + + \sa offset(), length() +*/ + +void QHeaderView::setOffset(int newOffset) +{ + Q_D(QHeaderView); + if (d->offset == (int)newOffset) + return; + int ndelta = d->offset - newOffset; + d->offset = newOffset; + if (d->orientation == Qt::Horizontal) + d->viewport->scroll(isRightToLeft() ? -ndelta : ndelta, 0); + else + d->viewport->scroll(0, ndelta); + if (d->state == QHeaderViewPrivate::ResizeSection) { + QPoint cursorPos = QCursor::pos(); + if (d->orientation == Qt::Horizontal) + QCursor::setPos(cursorPos.x() + ndelta, cursorPos.y()); + else + QCursor::setPos(cursorPos.x(), cursorPos.y() + ndelta); + d->firstPos += ndelta; + d->lastPos += ndelta; + } +} + +/*! + \since 4.2 + Sets the offset to the start of the section at the given \a visualIndex. + + \sa setOffset(), sectionPosition() +*/ +void QHeaderView::setOffsetToSectionPosition(int visualIndex) +{ + Q_D(QHeaderView); + if (visualIndex > -1 && visualIndex < d->sectionCount) { + int position = d->headerSectionPosition(d->adjustedVisualIndex(visualIndex)); + setOffset(position); + } +} + +/*! + \since 4.2 + Sets the offset to make the last section visible. + + \sa setOffset(), sectionPosition(), setOffsetToSectionPosition() +*/ +void QHeaderView::setOffsetToLastSection() +{ + Q_D(const QHeaderView); + int size = (d->orientation == Qt::Horizontal ? viewport()->width() : viewport()->height()); + int position = length() - size; + setOffset(position); +} + +/*! + Returns the length along the orientation of the header. + + \sa sizeHint(), setResizeMode(), offset() +*/ + +int QHeaderView::length() const +{ + Q_D(const QHeaderView); + //Q_ASSERT(d->headerLength() == d->length); + return d->length; +} + +/*! + Returns a suitable size hint for this header. + + \sa sectionSizeHint() +*/ + +QSize QHeaderView::sizeHint() const +{ + Q_D(const QHeaderView); + if (d->cachedSizeHint.isValid()) + return d->cachedSizeHint; + d->cachedSizeHint = QSize(0, 0); //reinitialize the cached size hint + const int sectionCount = count(); + + // get size hint for the first n sections + int i = 0; + for (int checked = 0; checked < 100 && i < sectionCount; ++i) { + if (isSectionHidden(i)) + continue; + checked++; + QSize hint = sectionSizeFromContents(i); + d->cachedSizeHint = d->cachedSizeHint.expandedTo(hint); + } + // get size hint for the last n sections + i = qMax(i, sectionCount - 100 ); + for (int j = sectionCount - 1, checked = 0; j >= i && checked < 100; --j) { + if (isSectionHidden(j)) + continue; + checked++; + QSize hint = sectionSizeFromContents(j); + d->cachedSizeHint = d->cachedSizeHint.expandedTo(hint); + } + return d->cachedSizeHint; +} + +/*! + Returns a suitable size hint for the section specified by \a logicalIndex. + + \sa sizeHint(), defaultSectionSize(), minimumSectionSize(), + Qt::SizeHintRole +*/ + +int QHeaderView::sectionSizeHint(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (isSectionHidden(logicalIndex)) + return 0; + if (logicalIndex < 0 || logicalIndex >= count()) + return -1; + QSize size; + QVariant value = d->model->headerData(logicalIndex, d->orientation, Qt::SizeHintRole); + if (value.isValid()) + size = qvariant_cast(value); + else + size = sectionSizeFromContents(logicalIndex); + int hint = d->orientation == Qt::Horizontal ? size.width() : size.height(); + return qMax(minimumSectionSize(), hint); +} + +/*! + Returns the visual index of the section that covers the given \a position + in the viewport. + + \sa logicalIndexAt() +*/ + +int QHeaderView::visualIndexAt(int position) const +{ + Q_D(const QHeaderView); + int vposition = position; + d->executePostedLayout(); + d->executePostedResize(); + const int count = d->sectionCount; + if (count < 1) + return -1; + + if (d->reverse()) + vposition = d->viewport->width() - vposition; + vposition += d->offset; + + if (vposition > d->length) + return -1; + int visual = d->headerVisualIndexAt(vposition); + if (visual < 0) + return -1; + + while (d->isVisualIndexHidden(visual)){ + ++visual; + if (visual >= count) + return -1; + } + return visual; +} + +/*! + Returns the section that covers the given \a position in the viewport. + + \sa visualIndexAt(), isSectionHidden() +*/ + +int QHeaderView::logicalIndexAt(int position) const +{ + const int visual = visualIndexAt(position); + if (visual > -1) + return logicalIndex(visual); + return -1; +} + +/*! + Returns the width (or height for vertical headers) of the given + \a logicalIndex. + + \sa length(), setResizeMode(), defaultSectionSize() +*/ + +int QHeaderView::sectionSize(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (isSectionHidden(logicalIndex)) + return 0; + if (logicalIndex < 0 || logicalIndex >= count()) + return 0; + int visual = visualIndex(logicalIndex); + if (visual == -1) + return 0; + d->executePostedResize(); + return d->headerSectionSize(visual); +} + +/*! + + Returns the section position of the given \a logicalIndex, or -1 + if the section is hidden. The position is measured in pixels from + the first visible item's top-left corner to the top-left corner of + the item with \a logicalIndex. The measurement is along the x-axis + for horizontal headers and along the y-axis for vertical headers. + + \sa sectionViewportPosition() +*/ + +int QHeaderView::sectionPosition(int logicalIndex) const +{ + Q_D(const QHeaderView); + int visual = visualIndex(logicalIndex); + // in some cases users may change the selections + // before we have a chance to do the layout + if (visual == -1) + return -1; + d->executePostedResize(); + return d->headerSectionPosition(visual); +} + +/*! + Returns the section viewport position of the given \a logicalIndex. + + If the section is hidden, the return value is undefined. + + \sa sectionPosition(), isSectionHidden() +*/ + +int QHeaderView::sectionViewportPosition(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (logicalIndex >= count()) + return -1; + int position = sectionPosition(logicalIndex); + if (position < 0) + return position; // the section was hidden + int offsetPosition = position - d->offset; + if (d->reverse()) + return d->viewport->width() - (offsetPosition + sectionSize(logicalIndex)); + return offsetPosition; +} + +/*! + \fn int QHeaderView::logicalIndexAt(int x, int y) const + + Returns the logical index of the section at the given coordinate. If the + header is horizontal \a x will be used, otherwise \a y will be used to + find the logical index. +*/ + +/*! + \fn int QHeaderView::logicalIndexAt(const QPoint &pos) const + + Returns the logical index of the section at the position given in \a pos. + If the header is horizontal the x-coordinate will be used, otherwise the + y-coordinate will be used to find the logical index. + + \sa sectionPosition() +*/ + +/*! + Moves the section at visual index \a from to occupy visual index \a to. + + \sa sectionsMoved() +*/ + +void QHeaderView::moveSection(int from, int to) +{ + Q_D(QHeaderView); + + d->executePostedLayout(); + if (from < 0 || from >= d->sectionCount || to < 0 || to >= d->sectionCount) + return; + + if (from == to) { + int logical = logicalIndex(from); + Q_ASSERT(logical != -1); + updateSection(logical); + return; + } + + if (stretchLastSection() && to == d->lastVisibleVisualIndex()) + d->lastSectionSize = sectionSize(from); + + //int oldHeaderLength = length(); // ### for debugging; remove later + d->initializeIndexMapping(); + + QBitArray sectionHidden = d->sectionHidden; + int *visualIndices = d->visualIndices.data(); + int *logicalIndices = d->logicalIndices.data(); + int logical = logicalIndices[from]; + int visual = from; + + int affected_count = qAbs(to - from) + 1; + QVarLengthArray sizes(affected_count); + QVarLengthArray modes(affected_count); + + // move sections and indices + if (to > from) { + sizes[to - from] = d->headerSectionSize(from); + modes[to - from] = d->headerSectionResizeMode(from); + while (visual < to) { + sizes[visual - from] = d->headerSectionSize(visual + 1); + modes[visual - from] = d->headerSectionResizeMode(visual + 1); + if (!sectionHidden.isEmpty()) + sectionHidden.setBit(visual, sectionHidden.testBit(visual + 1)); + visualIndices[logicalIndices[visual + 1]] = visual; + logicalIndices[visual] = logicalIndices[visual + 1]; + ++visual; + } + } else { + sizes[0] = d->headerSectionSize(from); + modes[0] = d->headerSectionResizeMode(from); + while (visual > to) { + sizes[visual - to] = d->headerSectionSize(visual - 1); + modes[visual - to] = d->headerSectionResizeMode(visual - 1); + if (!sectionHidden.isEmpty()) + sectionHidden.setBit(visual, sectionHidden.testBit(visual - 1)); + visualIndices[logicalIndices[visual - 1]] = visual; + logicalIndices[visual] = logicalIndices[visual - 1]; + --visual; + } + } + if (!sectionHidden.isEmpty()) { + sectionHidden.setBit(to, d->sectionHidden.testBit(from)); + d->sectionHidden = sectionHidden; + } + visualIndices[logical] = to; + logicalIndices[to] = logical; + + //Q_ASSERT(oldHeaderLength == length()); + // move sizes + // ### check for spans of section sizes here + if (to > from) { + for (visual = from; visual <= to; ++visual) { + int size = sizes[visual - from]; + ResizeMode mode = modes[visual - from]; + d->createSectionSpan(visual, visual, size, mode); + } + } else { + for (visual = to; visual <= from; ++visual) { + int size = sizes[visual - to]; + ResizeMode mode = modes[visual - to]; + d->createSectionSpan(visual, visual, size, mode); + } + } + //Q_ASSERT(d->headerLength() == length()); + //Q_ASSERT(oldHeaderLength == length()); + //Q_ASSERT(d->logicalIndices.count() == d->sectionCount); + + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); + d->viewport->update(); + + emit sectionMoved(logical, from, to); +} + +/*! + \since 4.2 + Swaps the section at visual index \a first with the section at visual + index \a second. + + \sa moveSection() +*/ +void QHeaderView::swapSections(int first, int second) +{ + Q_D(QHeaderView); + + if (first == second) + return; + d->executePostedLayout(); + if (first < 0 || first >= d->sectionCount || second < 0 || second >= d->sectionCount) + return; + + int firstSize = d->headerSectionSize(first); + ResizeMode firstMode = d->headerSectionResizeMode(first); + int firstLogical = d->logicalIndex(first); + + int secondSize = d->headerSectionSize(second); + ResizeMode secondMode = d->headerSectionResizeMode(second); + int secondLogical = d->logicalIndex(second); + + d->createSectionSpan(second, second, firstSize, firstMode); + d->createSectionSpan(first, first, secondSize, secondMode); + + d->initializeIndexMapping(); + + d->visualIndices[firstLogical] = second; + d->logicalIndices[second] = firstLogical; + + d->visualIndices[secondLogical] = first; + d->logicalIndices[first] = secondLogical; + + if (!d->sectionHidden.isEmpty()) { + bool firstHidden = d->sectionHidden.testBit(first); + bool secondHidden = d->sectionHidden.testBit(second); + d->sectionHidden.setBit(first, secondHidden); + d->sectionHidden.setBit(second, firstHidden); + } + + d->viewport->update(); + emit sectionMoved(firstLogical, first, second); + emit sectionMoved(secondLogical, second, first); +} + +/*! + \fn void QHeaderView::resizeSection(int logicalIndex, int size) + + Resizes the section specified by \a logicalIndex to \a size measured in + pixels. + + \sa sectionResized(), resizeMode(), sectionSize() +*/ + +void QHeaderView::resizeSection(int logical, int size) +{ + Q_D(QHeaderView); + if (logical < 0 || logical >= count()) + return; + + if (isSectionHidden(logical)) { + d->hiddenSectionSize.insert(logical, size); + return; + } + + int visual = visualIndex(logical); + if (visual == -1) + return; + + int oldSize = d->headerSectionSize(visual); + if (oldSize == size) + return; + + d->executePostedLayout(); + d->invalidateCachedSizeHint(); + + if (stretchLastSection() && visual == d->lastVisibleVisualIndex()) + d->lastSectionSize = size; + + if (size != oldSize) + d->createSectionSpan(visual, visual, size, d->headerSectionResizeMode(visual)); + + int w = d->viewport->width(); + int h = d->viewport->height(); + int pos = sectionViewportPosition(logical); + QRect r; + if (d->orientation == Qt::Horizontal) + if (isRightToLeft()) + r.setRect(0, 0, pos + size, h); + else + r.setRect(pos, 0, w - pos, h); + else + r.setRect(0, pos, w, h - pos); + + if (d->hasAutoResizeSections()) { + d->doDelayedResizeSections(); + r = d->viewport->rect(); + } + d->viewport->update(r.normalized()); + emit sectionResized(logical, oldSize, size); +} + +/*! + Resizes the sections according to the given \a mode, ignoring the current + resize mode. + + \sa resizeMode(), sectionResized() +*/ + +void QHeaderView::resizeSections(QHeaderView::ResizeMode mode) +{ + Q_D(QHeaderView); + d->resizeSections(mode, true); +} + +/*! + \fn void QHeaderView::hideSection(int logicalIndex) + Hides the section specified by \a logicalIndex. + + \sa showSection(), isSectionHidden(), hiddenSectionCount(), + setSectionHidden() +*/ + +/*! + \fn void QHeaderView::showSection(int logicalIndex) + Shows the section specified by \a logicalIndex. + + \sa hideSection(), isSectionHidden(), hiddenSectionCount(), + setSectionHidden() +*/ + +/*! + Returns true if the section specified by \a logicalIndex is explicitly + hidden from the user; otherwise returns false. + + \sa hideSection(), showSection(), setSectionHidden(), hiddenSectionCount() +*/ + +bool QHeaderView::isSectionHidden(int logicalIndex) const +{ + Q_D(const QHeaderView); + d->executePostedLayout(); + if (logicalIndex >= d->sectionHidden.count() || logicalIndex < 0 || logicalIndex >= d->sectionCount) + return false; + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + return d->sectionHidden.testBit(visual); +} + +/*! + \since 4.1 + + Returns the number of sections in the header that has been hidden. + + \sa setSectionHidden(), isSectionHidden() +*/ +int QHeaderView::hiddenSectionCount() const +{ + Q_D(const QHeaderView); + return d->hiddenSectionSize.count(); +} + +/*! + If \a hide is true the section specified by \a logicalIndex is hidden; + otherwise the section is shown. + + \sa isSectionHidden(), hiddenSectionCount() +*/ + +void QHeaderView::setSectionHidden(int logicalIndex, bool hide) +{ + Q_D(QHeaderView); + if (logicalIndex < 0 || logicalIndex >= count()) + return; + + d->executePostedLayout(); + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + if (hide == d->isVisualIndexHidden(visual)) + return; + if (hide) { + int size = d->headerSectionSize(visual); + if (!d->hasAutoResizeSections()) + resizeSection(logicalIndex, 0); + d->hiddenSectionSize.insert(logicalIndex, size); + if (d->sectionHidden.count() < count()) + d->sectionHidden.resize(count()); + d->sectionHidden.setBit(visual, true); + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); + } else { + int size = d->hiddenSectionSize.value(logicalIndex, d->defaultSectionSize); + d->hiddenSectionSize.remove(logicalIndex); + if (d->hiddenSectionSize.isEmpty()) { + d->sectionHidden.clear(); + } else { + Q_ASSERT(visual <= d->sectionHidden.count()); + d->sectionHidden.setBit(visual, false); + } + resizeSection(logicalIndex, size); + } +} + +/*! + Returns the number of sections in the header. + + \sa sectionCountChanged(), length() +*/ + +int QHeaderView::count() const +{ + Q_D(const QHeaderView); + //Q_ASSERT(d->sectionCount == d->headerSectionCount()); + // ### this may affect the lazy layout + d->executePostedLayout(); + return d->sectionCount; +} + +/*! + Returns the visual index position of the section specified by the given + \a logicalIndex, or -1 otherwise. + + Hidden sections still have valid visual indexes. + + \sa logicalIndex() +*/ + +int QHeaderView::visualIndex(int logicalIndex) const +{ + Q_D(const QHeaderView); + if (logicalIndex < 0) + return -1; + d->executePostedLayout(); + if (d->visualIndices.isEmpty()) { // nothing has been moved, so we have no mapping + if (logicalIndex < d->sectionCount) + return logicalIndex; + } else if (logicalIndex < d->visualIndices.count()) { + int visual = d->visualIndices.at(logicalIndex); + Q_ASSERT(visual < d->sectionCount); + return visual; + } + return -1; +} + +/*! + Returns the logicalIndex for the section at the given \a visualIndex + position, or -1 if visualIndex < 0 or visualIndex >= QHeaderView::count(). + + Note that the visualIndex is not affected by hidden sections. + + \sa visualIndex(), sectionPosition() +*/ + +int QHeaderView::logicalIndex(int visualIndex) const +{ + Q_D(const QHeaderView); + if (visualIndex < 0 || visualIndex >= d->sectionCount) + return -1; + return d->logicalIndex(visualIndex); +} + +/*! + If \a movable is true, the header may be moved by the user; otherwise it + is fixed in place. + + \sa isMovable(), sectionMoved() +*/ + +// ### Qt 5: change to setSectionsMovable() +void QHeaderView::setMovable(bool movable) +{ + Q_D(QHeaderView); + d->movableSections = movable; +} + +/*! + Returns true if the header can be moved by the user; otherwise returns + false. + + \sa setMovable() +*/ + +// ### Qt 5: change to sectionsMovable() +bool QHeaderView::isMovable() const +{ + Q_D(const QHeaderView); + return d->movableSections; +} + +/*! + If \a clickable is true, the header will respond to single clicks. + + \sa isClickable(), sectionClicked(), sectionPressed(), + setSortIndicatorShown() +*/ + +// ### Qt 5: change to setSectionsClickable() +void QHeaderView::setClickable(bool clickable) +{ + Q_D(QHeaderView); + d->clickableSections = clickable; +} + +/*! + Returns true if the header is clickable; otherwise returns false. A + clickable header could be set up to allow the user to change the + representation of the data in the view related to the header. + + \sa setClickable() +*/ + +// ### Qt 5: change to sectionsClickable() +bool QHeaderView::isClickable() const +{ + Q_D(const QHeaderView); + return d->clickableSections; +} + +void QHeaderView::setHighlightSections(bool highlight) +{ + Q_D(QHeaderView); + d->highlightSelected = highlight; +} + +bool QHeaderView::highlightSections() const +{ + Q_D(const QHeaderView); + return d->highlightSelected; +} + +/*! + Sets the constraints on how the header can be resized to those described + by the given \a mode. + + \sa resizeMode(), length(), sectionResized(), sectionAutoResize() +*/ + +void QHeaderView::setResizeMode(ResizeMode mode) +{ + Q_D(QHeaderView); + initializeSections(); + d->stretchSections = (mode == Stretch ? count() : 0); + d->contentsSections = (mode == ResizeToContents ? count() : 0); + d->setGlobalHeaderResizeMode(mode); + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); // section sizes may change as a result of the new mode +} + +/*! + \overload + + Sets the constraints on how the section specified by \a logicalIndex in + the header can be resized to those described by the given \a mode. The logical + index should exist at the time this function is called. + + \note This setting will be ignored for the last section if the stretchLastSection + property is set to true. This is the default for the horizontal headers provided + by QTreeView. + + \sa setStretchLastSection() +*/ + +// ### Qt 5: change to setSectionResizeMode() +void QHeaderView::setResizeMode(int logicalIndex, ResizeMode mode) +{ + Q_D(QHeaderView); + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + + ResizeMode old = d->headerSectionResizeMode(visual); + d->setHeaderSectionResizeMode(visual, mode); + + if (mode == Stretch && old != Stretch) + ++d->stretchSections; + else if (mode == ResizeToContents && old != ResizeToContents) + ++d->contentsSections; + else if (mode != Stretch && old == Stretch) + --d->stretchSections; + else if (mode != ResizeToContents && old == ResizeToContents) + --d->contentsSections; + + if (d->hasAutoResizeSections() && d->state == QHeaderViewPrivate::NoState) + d->doDelayedResizeSections(); // section sizes may change as a result of the new mode +} + +/*! + Returns the resize mode that applies to the section specified by the given + \a logicalIndex. + + \sa setResizeMode() +*/ + +QHeaderView::ResizeMode QHeaderView::resizeMode(int logicalIndex) const +{ + Q_D(const QHeaderView); + int visual = visualIndex(logicalIndex); + if (visual == -1) + return Fixed; //the default value + return d->headerSectionResizeMode(visual); +} + +/*! + \since 4.1 + + Returns the number of sections that are set to resize mode stretch. In + views, this can be used to see if the headerview needs to resize the + sections when the view's geometry changes. + + \sa stretchLastSection, resizeMode() +*/ + +int QHeaderView::stretchSectionCount() const +{ + Q_D(const QHeaderView); + return d->stretchSections; +} + +/*! + \property QHeaderView::showSortIndicator + \brief whether the sort indicator is shown + + By default, this property is false. + + \sa setClickable() +*/ + +void QHeaderView::setSortIndicatorShown(bool show) +{ + Q_D(QHeaderView); + if (d->sortIndicatorShown == show) + return; + + d->sortIndicatorShown = show; + + if (sortIndicatorSection() < 0 || sortIndicatorSection() > count()) + return; + + if (d->headerSectionResizeMode(sortIndicatorSection()) == ResizeToContents) + resizeSections(); + + d->viewport->update(); +} + +bool QHeaderView::isSortIndicatorShown() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorShown; +} + +/*! + Sets the sort indicator for the section specified by the given + \a logicalIndex in the direction specified by \a order, and removes the + sort indicator from any other section that was showing it. + + \a logicalIndex may be -1, in which case no sort indicator will be shown + and the model will return to its natural, unsorted order. Note that not + all models support this and may even crash in this case. + + \sa sortIndicatorSection() sortIndicatorOrder() +*/ + +void QHeaderView::setSortIndicator(int logicalIndex, Qt::SortOrder order) +{ + Q_D(QHeaderView); + + // This is so that people can set the position of the sort indicator before the fill the model + int old = d->sortIndicatorSection; + d->sortIndicatorSection = logicalIndex; + d->sortIndicatorOrder = order; + + if (logicalIndex >= d->sectionCount) { + emit sortIndicatorChanged(logicalIndex, order); + return; // nothing to do + } + + if (old != logicalIndex + && ((logicalIndex >= 0 && resizeMode(logicalIndex) == ResizeToContents) + || old >= d->sectionCount || (old >= 0 && resizeMode(old) == ResizeToContents))) { + resizeSections(); + d->viewport->update(); + } else { + if (old >= 0 && old != logicalIndex) + updateSection(old); + if (logicalIndex >= 0) + updateSection(logicalIndex); + } + + emit sortIndicatorChanged(logicalIndex, order); +} + +/*! + Returns the logical index of the section that has a sort indicator. + By default this is section 0. + + \sa setSortIndicator() sortIndicatorOrder() setSortIndicatorShown() +*/ + +int QHeaderView::sortIndicatorSection() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorSection; +} + +/*! + Returns the order for the sort indicator. If no section has a sort + indicator the return value of this function is undefined. + + \sa setSortIndicator() sortIndicatorSection() +*/ + +Qt::SortOrder QHeaderView::sortIndicatorOrder() const +{ + Q_D(const QHeaderView); + return d->sortIndicatorOrder; +} + +/*! + \property QHeaderView::stretchLastSection + \brief whether the last visible section in the header takes up all the + available space + + The default value is false. + + \note The horizontal headers provided by QTreeView are configured with this + property set to true, ensuring that the view does not waste any of the + space assigned to it for its header. If this value is set to true, this + property will override the resize mode set on the last section in the + header. + + \sa setResizeMode() +*/ +bool QHeaderView::stretchLastSection() const +{ + Q_D(const QHeaderView); + return d->stretchLastSection; +} + +void QHeaderView::setStretchLastSection(bool stretch) +{ + Q_D(QHeaderView); + d->stretchLastSection = stretch; + if (d->state != QHeaderViewPrivate::NoState) + return; + if (stretch) + resizeSections(); + else if (count()) + resizeSection(count() - 1, d->defaultSectionSize); +} + +/*! + \since 4.2 + \property QHeaderView::cascadingSectionResizes + \brief whether interactive resizing will be cascaded to the following + sections once the section being resized by the user has reached its + minimum size + + This property only affects sections that have \l Interactive as their + resize mode. + + The default value is false. + + \sa setResizeMode() +*/ +bool QHeaderView::cascadingSectionResizes() const +{ + Q_D(const QHeaderView); + return d->cascadingResizing; +} + +void QHeaderView::setCascadingSectionResizes(bool enable) +{ + Q_D(QHeaderView); + d->cascadingResizing = enable; +} + +/*! + \property QHeaderView::defaultSectionSize + \brief the default size of the header sections before resizing. + + This property only affects sections that have \l Interactive or \l Fixed + as their resize mode. + + \sa setResizeMode() minimumSectionSize +*/ +int QHeaderView::defaultSectionSize() const +{ + Q_D(const QHeaderView); + return d->defaultSectionSize; +} + +void QHeaderView::setDefaultSectionSize(int size) +{ + Q_D(QHeaderView); + d->setDefaultSectionSize(size); +} + +/*! + \since 4.2 + \property QHeaderView::minimumSectionSize + \brief the minimum size of the header sections. + + The minimum section size is the smallest section size allowed. If the + minimum section size is set to -1, QHeaderView will use the maximum of + the \l{QApplication::globalStrut()}{global strut} or the + \l{fontMetrics()}{font metrics} size. + + This property is honored by all \l{ResizeMode}{resize modes}. + + \sa setResizeMode() defaultSectionSize +*/ +int QHeaderView::minimumSectionSize() const +{ + Q_D(const QHeaderView); + if (d->minimumSectionSize == -1) { + QSize strut = QApplication::globalStrut(); + int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, 0, this); + if (d->orientation == Qt::Horizontal) + return qMax(strut.width(), (fontMetrics().maxWidth() + margin)); + return qMax(strut.height(), (fontMetrics().height() + margin)); + } + return d->minimumSectionSize; +} + +void QHeaderView::setMinimumSectionSize(int size) +{ + Q_D(QHeaderView); + d->minimumSectionSize = size; +} + +/*! + \since 4.1 + \property QHeaderView::defaultAlignment + \brief the default alignment of the text in each header section +*/ + +Qt::Alignment QHeaderView::defaultAlignment() const +{ + Q_D(const QHeaderView); + return d->defaultAlignment; +} + +void QHeaderView::setDefaultAlignment(Qt::Alignment alignment) +{ + Q_D(QHeaderView); + if (d->defaultAlignment == alignment) + return; + + d->defaultAlignment = alignment; + d->viewport->update(); +} + +/*! + \internal +*/ +void QHeaderView::doItemsLayout() +{ + initializeSections(); + QAbstractItemView::doItemsLayout(); +} + +/*! + Returns true if sections in the header has been moved; otherwise returns + false; + + \sa moveSection() +*/ +bool QHeaderView::sectionsMoved() const +{ + Q_D(const QHeaderView); + return !d->visualIndices.isEmpty(); +} + +/*! + \since 4.1 + + Returns true if sections in the header has been hidden; otherwise returns + false; + + \sa setSectionHidden() +*/ +bool QHeaderView::sectionsHidden() const +{ + Q_D(const QHeaderView); + return !d->hiddenSectionSize.isEmpty(); +} + +#ifndef QT_NO_DATASTREAM +/*! + \since 4.3 + + Saves the current state of this header view. + + To restore the saved state, pass the return value to restoreState(). + + \sa restoreState() +*/ +QByteArray QHeaderView::saveState() const +{ + Q_D(const QHeaderView); + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << QHeaderViewPrivate::VersionMarker; + stream << 0; // current version is 0 + d->write(stream); + return data; +} + +/*! + \since 4.3 + Restores the \a state of this header view. + This function returns \c true if the state was restored; otherwise returns + false. + + \sa saveState() +*/ +bool QHeaderView::restoreState(const QByteArray &state) +{ + Q_D(QHeaderView); + if (state.isEmpty()) + return false; + QByteArray data = state; + QDataStream stream(&data, QIODevice::ReadOnly); + int marker; + int ver; + stream >> marker; + stream >> ver; + if (stream.status() != QDataStream::Ok + || marker != QHeaderViewPrivate::VersionMarker + || ver != 0) // current version is 0 + return false; + + if (d->read(stream)) { + emit sortIndicatorChanged(d->sortIndicatorSection, d->sortIndicatorOrder ); + d->viewport->update(); + return true; + } + return false; +} +#endif // QT_NO_DATASTREAM + +/*! + \reimp +*/ +void QHeaderView::reset() +{ + QAbstractItemView::reset(); + // it would be correct to call clear, but some apps rely + // on the header keeping the sections, even after calling reset + //d->clear(); + initializeSections(); +} + +/*! + Updates the changed header sections with the given \a orientation, from + \a logicalFirst to \a logicalLast inclusive. +*/ +void QHeaderView::headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast) +{ + Q_D(QHeaderView); + if (d->orientation != orientation) + return; + + if (logicalFirst < 0 || logicalLast < 0 || logicalFirst >= count() || logicalLast >= count()) + return; + + d->invalidateCachedSizeHint(); + + int firstVisualIndex = INT_MAX, lastVisualIndex = -1; + + for (int section = logicalFirst; section <= logicalLast; ++section) { + const int visual = visualIndex(section); + firstVisualIndex = qMin(firstVisualIndex, visual); + lastVisualIndex = qMax(lastVisualIndex, visual); + } + + d->executePostedResize(); + const int first = d->headerSectionPosition(firstVisualIndex), + last = d->headerSectionPosition(lastVisualIndex) + + d->headerSectionSize(lastVisualIndex); + + if (orientation == Qt::Horizontal) { + d->viewport->update(first, 0, last - first, d->viewport->height()); + } else { + d->viewport->update(0, first, d->viewport->width(), last - first); + } +} + +/*! + \internal + \since 4.2 + + Updates the section specified by the given \a logicalIndex. +*/ + +void QHeaderView::updateSection(int logicalIndex) +{ + Q_D(QHeaderView); + if (d->orientation == Qt::Horizontal) + d->viewport->update(QRect(sectionViewportPosition(logicalIndex), + 0, sectionSize(logicalIndex), d->viewport->height())); + else + d->viewport->update(QRect(0, sectionViewportPosition(logicalIndex), + d->viewport->width(), sectionSize(logicalIndex))); +} + +/*! + Resizes the sections according to their size hints. Normally, you do not + have to call this function. +*/ + +void QHeaderView::resizeSections() +{ + Q_D(QHeaderView); + if (d->hasAutoResizeSections()) + d->resizeSections(Interactive, false); // no global resize mode +} + +/*! + This slot is called when sections are inserted into the \a parent. + \a logicalFirst and \a logicalLast indices signify where the new sections + were inserted. + + If only one section is inserted, \a logicalFirst and \a logicalLast will + be the same. +*/ + +void QHeaderView::sectionsInserted(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_D(QHeaderView); + if (parent != d->root) + return; // we only handle changes in the top level + int oldCount = d->sectionCount; + + d->invalidateCachedSizeHint(); + + // add the new sections + int insertAt = 0; + for (int spanStart = 0; insertAt < d->sectionSpans.count() && spanStart < logicalFirst; ++insertAt) + spanStart += d->sectionSpans.at(insertAt).count; + + int insertCount = logicalLast - logicalFirst + 1; + d->sectionCount += insertCount; + + if (d->sectionSpans.isEmpty() || insertAt >= d->sectionSpans.count()) { + int insertLength = d->defaultSectionSize * insertCount; + d->length += insertLength; + QHeaderViewPrivate::SectionSpan span(insertLength, insertCount, d->globalResizeMode); + d->sectionSpans.append(span); + } else if ((d->sectionSpans.at(insertAt).sectionSize() == d->defaultSectionSize) + && d->sectionSpans.at(insertAt).resizeMode == d->globalResizeMode) { + // add the new sections to an existing span + int insertLength = d->sectionSpans.at(insertAt).sectionSize() * insertCount; + d->length += insertLength; + d->sectionSpans[insertAt].size += insertLength; + d->sectionSpans[insertAt].count += insertCount; + } else { + // separate them out into their own span + int insertLength = d->defaultSectionSize * insertCount; + d->length += insertLength; + QHeaderViewPrivate::SectionSpan span(insertLength, insertCount, d->globalResizeMode); + d->sectionSpans.insert(insertAt, span); + } + + // update sorting column + if (d->sortIndicatorSection >= logicalFirst) + d->sortIndicatorSection += insertCount; + + // update resize mode section counts + if (d->globalResizeMode == Stretch) + d->stretchSections = d->sectionCount; + else if (d->globalResizeMode == ResizeToContents) + d->contentsSections = d->sectionCount; + + // clear selection cache + d->sectionSelected.clear(); + + // update mapping + if (!d->visualIndices.isEmpty() && !d->logicalIndices.isEmpty()) { + Q_ASSERT(d->visualIndices.count() == d->logicalIndices.count()); + int mappingCount = d->visualIndices.count(); + for (int i = 0; i < mappingCount; ++i) { + if (d->visualIndices.at(i) >= logicalFirst) + d->visualIndices[i] += insertCount; + if (d->logicalIndices.at(i) >= logicalFirst) + d->logicalIndices[i] += insertCount; + } + for (int j = logicalFirst; j <= logicalLast; ++j) { + d->visualIndices.insert(j, j); + d->logicalIndices.insert(j, j); + } + } + + // insert sections into sectionsHidden + if (!d->sectionHidden.isEmpty()) { + QBitArray sectionHidden(d->sectionHidden); + sectionHidden.resize(sectionHidden.count() + insertCount); + sectionHidden.fill(false, logicalFirst, logicalLast + 1); + for (int j = logicalLast + 1; j < sectionHidden.count(); ++j) + //here we simply copy the old sectionHidden + sectionHidden.setBit(j, d->sectionHidden.testBit(j - insertCount)); + d->sectionHidden = sectionHidden; + } + + // insert sections into hiddenSectionSize + QHash newHiddenSectionSize; // from logical index to section size + for (int i = 0; i < logicalFirst; ++i) + if (isSectionHidden(i)) + newHiddenSectionSize[i] = d->hiddenSectionSize[i]; + for (int j = logicalLast + 1; j < d->sectionCount; ++j) + if (isSectionHidden(j)) + newHiddenSectionSize[j] = d->hiddenSectionSize[j - insertCount]; + d->hiddenSectionSize = newHiddenSectionSize; + + d->doDelayedResizeSections(); + emit sectionCountChanged(oldCount, count()); + + // if the new sections were not updated by resizing, we need to update now + if (!d->hasAutoResizeSections()) + d->viewport->update(); +} + +/*! + This slot is called when sections are removed from the \a parent. + \a logicalFirst and \a logicalLast signify where the sections were removed. + + If only one section is removed, \a logicalFirst and \a logicalLast will + be the same. +*/ + +void QHeaderView::sectionsAboutToBeRemoved(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_UNUSED(parent); + Q_UNUSED(logicalFirst); + Q_UNUSED(logicalLast); +} + +void QHeaderViewPrivate::updateHiddenSections(int logicalFirst, int logicalLast) +{ + Q_Q(QHeaderView); + const int changeCount = logicalLast - logicalFirst + 1; + + // remove sections from hiddenSectionSize + QHash newHiddenSectionSize; // from logical index to section size + for (int i = 0; i < logicalFirst; ++i) + if (q->isSectionHidden(i)) + newHiddenSectionSize[i] = hiddenSectionSize[i]; + for (int j = logicalLast + 1; j < sectionCount; ++j) + if (q->isSectionHidden(j)) + newHiddenSectionSize[j - changeCount] = hiddenSectionSize[j]; + hiddenSectionSize = newHiddenSectionSize; + + // remove sections from sectionsHidden + if (!sectionHidden.isEmpty()) { + const int newsize = qMin(sectionCount - changeCount, sectionHidden.size()); + QBitArray newSectionHidden(newsize); + for (int j = 0, k = 0; j < sectionHidden.size(); ++j) { + const int logical = logicalIndex(j); + if (logical < logicalFirst || logical > logicalLast) { + newSectionHidden[k++] = sectionHidden[j]; + } + } + sectionHidden = newSectionHidden; + } +} + +void QHeaderViewPrivate::_q_sectionsRemoved(const QModelIndex &parent, + int logicalFirst, int logicalLast) +{ + Q_Q(QHeaderView); + if (parent != root) + return; // we only handle changes in the top level + if (qMin(logicalFirst, logicalLast) < 0 + || qMax(logicalLast, logicalFirst) >= sectionCount) + return; + int oldCount = q->count(); + int changeCount = logicalLast - logicalFirst + 1; + + updateHiddenSections(logicalFirst, logicalLast); + + if (visualIndices.isEmpty() && logicalIndices.isEmpty()) { + //Q_ASSERT(headerSectionCount() == sectionCount); + removeSectionsFromSpans(logicalFirst, logicalLast); + } else { + for (int l = logicalLast; l >= logicalFirst; --l) { + int visual = visualIndices.at(l); + for (int v = 0; v < sectionCount; ++v) { + if (v >= logicalIndices.count()) + continue; // the section doesn't exist + if (v > visual) { + int logical = logicalIndices.at(v); + --(visualIndices[logical]); + } + if (logicalIndex(v) > l) // no need to move the positions before l + --(logicalIndices[v]); + } + logicalIndices.remove(visual); + visualIndices.remove(l); + //Q_ASSERT(headerSectionCount() == sectionCount); + removeSectionsFromSpans(visual, visual); + } + // ### handle sectionSelection, sectionHidden + } + sectionCount -= changeCount; + + // update sorting column + if (sortIndicatorSection >= logicalFirst) { + if (sortIndicatorSection <= logicalLast) + sortIndicatorSection = -1; + else + sortIndicatorSection -= changeCount; + } + + // if we only have the last section (the "end" position) left, the header is empty + if (sectionCount <= 0) + clear(); + invalidateCachedSizeHint(); + emit q->sectionCountChanged(oldCount, q->count()); + viewport->update(); +} + +void QHeaderViewPrivate::_q_layoutAboutToBeChanged() +{ + //if there is no row/column we can't have mapping for columns + //because no QModelIndex in the model would be valid + // ### this is far from being bullet-proof and we would need a real system to + // ### map columns or rows persistently + if ((orientation == Qt::Horizontal && model->rowCount(root) == 0) + || model->columnCount(root) == 0) + return; + + for (int i = 0; i < sectionHidden.count(); ++i) + if (sectionHidden.testBit(i)) // ### note that we are using column or row 0 + persistentHiddenSections.append(orientation == Qt::Horizontal + ? model->index(0, logicalIndex(i), root) + : model->index(logicalIndex(i), 0, root)); +} + +void QHeaderViewPrivate::_q_layoutChanged() +{ + Q_Q(QHeaderView); + viewport->update(); + if (persistentHiddenSections.isEmpty() || modelIsEmpty()) { + if (modelSectionCount() != sectionCount) + q->initializeSections(); + persistentHiddenSections.clear(); + return; + } + + QBitArray oldSectionHidden = sectionHidden; + bool sectionCountChanged = false; + + for (int i = 0; i < persistentHiddenSections.count(); ++i) { + QModelIndex index = persistentHiddenSections.at(i); + if (index.isValid()) { + const int logical = (orientation == Qt::Horizontal + ? index.column() + : index.row()); + q->setSectionHidden(logical, true); + oldSectionHidden.setBit(logical, false); + } else if (!sectionCountChanged && (modelSectionCount() != sectionCount)) { + sectionCountChanged = true; + break; + } + } + persistentHiddenSections.clear(); + + for (int i = 0; i < oldSectionHidden.count(); ++i) { + if (oldSectionHidden.testBit(i)) + q->setSectionHidden(i, false); + } + + // the number of sections changed; we need to reread the state of the model + if (sectionCountChanged) + q->initializeSections(); +} + +/*! + \internal +*/ + +void QHeaderView::initializeSections() +{ + Q_D(QHeaderView); + const int oldCount = d->sectionCount; + const int newCount = d->modelSectionCount(); + if (newCount <= 0) { + d->clear(); + emit sectionCountChanged(oldCount, 0); + } else if (newCount != oldCount) { + const int min = qBound(0, oldCount, newCount - 1); + initializeSections(min, newCount - 1); + if (stretchLastSection()) // we've already gotten the size hint + d->lastSectionSize = sectionSize(logicalIndex(d->sectionCount - 1)); + + //make sure we update the hidden sections + if (newCount < oldCount) + d->updateHiddenSections(0, newCount-1); + } +} + +/*! + \internal +*/ + +void QHeaderView::initializeSections(int start, int end) +{ + Q_D(QHeaderView); + + Q_ASSERT(start >= 0); + Q_ASSERT(end >= 0); + + d->invalidateCachedSizeHint(); + + if (end + 1 < d->sectionCount) { + int newCount = end + 1; + d->removeSectionsFromSpans(newCount, d->sectionCount); + if (!d->hiddenSectionSize.isEmpty()) { + if (d->sectionCount - newCount > d->hiddenSectionSize.count()) { + for (int i = end + 1; i < d->sectionCount; ++i) + d->hiddenSectionSize.remove(i); + } else { + QHash::iterator it = d->hiddenSectionSize.begin(); + while (it != d->hiddenSectionSize.end()) { + if (it.key() > end) + it = d->hiddenSectionSize.erase(it); + else + ++it; + } + } + } + } + + int oldCount = d->sectionCount; + d->sectionCount = end + 1; + + if (!d->logicalIndices.isEmpty()) { + if (oldCount <= d->sectionCount) { + d->logicalIndices.resize(d->sectionCount); + d->visualIndices.resize(d->sectionCount); + for (int i = oldCount; i < d->sectionCount; ++i) { + d->logicalIndices[i] = i; + d->visualIndices[i] = i; + } + } else { + int j = 0; + for (int i = 0; i < oldCount; ++i) { + int v = d->logicalIndices.at(i); + if (v < d->sectionCount) { + d->logicalIndices[j] = v; + d->visualIndices[v] = j; + j++; + } + } + d->logicalIndices.resize(d->sectionCount); + d->visualIndices.resize(d->sectionCount); + } + } + + if (d->globalResizeMode == Stretch) + d->stretchSections = d->sectionCount; + else if (d->globalResizeMode == ResizeToContents) + d->contentsSections = d->sectionCount; + if (!d->sectionHidden.isEmpty()) + d->sectionHidden.resize(d->sectionCount); + + if (d->sectionCount > oldCount) + d->createSectionSpan(start, end, (end - start + 1) * d->defaultSectionSize, d->globalResizeMode); + //Q_ASSERT(d->headerLength() == d->length); + + if (d->sectionCount != oldCount) + emit sectionCountChanged(oldCount, d->sectionCount); + d->viewport->update(); +} + +/*! + \reimp +*/ + +void QHeaderView::currentChanged(const QModelIndex ¤t, const QModelIndex &old) +{ + Q_D(QHeaderView); + + if (d->orientation == Qt::Horizontal && current.column() != old.column()) { + if (old.isValid() && old.parent() == d->root) + d->viewport->update(QRect(sectionViewportPosition(old.column()), 0, + sectionSize(old.column()), d->viewport->height())); + if (current.isValid() && current.parent() == d->root) + d->viewport->update(QRect(sectionViewportPosition(current.column()), 0, + sectionSize(current.column()), d->viewport->height())); + } else if (d->orientation == Qt::Vertical && current.row() != old.row()) { + if (old.isValid() && old.parent() == d->root) + d->viewport->update(QRect(0, sectionViewportPosition(old.row()), + d->viewport->width(), sectionSize(old.row()))); + if (current.isValid() && current.parent() == d->root) + d->viewport->update(QRect(0, sectionViewportPosition(current.row()), + d->viewport->width(), sectionSize(current.row()))); + } +} + + +/*! + \reimp +*/ + +bool QHeaderView::event(QEvent *e) +{ + Q_D(QHeaderView); + switch (e->type()) { + case QEvent::HoverEnter: { + QHoverEvent *he = static_cast(e); + d->hover = logicalIndexAt(he->pos()); + if (d->hover != -1) + updateSection(d->hover); + break; } + case QEvent::Leave: + case QEvent::HoverLeave: { + if (d->hover != -1) + updateSection(d->hover); + d->hover = -1; + break; } + case QEvent::HoverMove: { + QHoverEvent *he = static_cast(e); + int oldHover = d->hover; + d->hover = logicalIndexAt(he->pos()); + if (d->hover != oldHover) { + if (oldHover != -1) + updateSection(oldHover); + if (d->hover != -1) + updateSection(d->hover); + } + break; } + case QEvent::Timer: { + QTimerEvent *te = static_cast(e); + if (te->timerId() == d->delayedResize.timerId()) { + d->delayedResize.stop(); + resizeSections(); + } + break; } + default: + break; + } + return QAbstractItemView::event(e); +} + +/*! + \reimp +*/ + +void QHeaderView::paintEvent(QPaintEvent *e) +{ + Q_D(QHeaderView); + + if (count() == 0) + return; + + QPainter painter(d->viewport); + const QPoint offset = d->scrollDelayOffset; + QRect translatedEventRect = e->rect(); + translatedEventRect.translate(offset); + + int start = -1; + int end = -1; + if (d->orientation == Qt::Horizontal) { + start = visualIndexAt(translatedEventRect.left()); + end = visualIndexAt(translatedEventRect.right()); + } else { + start = visualIndexAt(translatedEventRect.top()); + end = visualIndexAt(translatedEventRect.bottom()); + } + + if (d->reverse()) { + start = (start == -1 ? count() - 1 : start); + end = (end == -1 ? 0 : end); + } else { + start = (start == -1 ? 0 : start); + end = (end == -1 ? count() - 1 : end); + } + + int tmp = start; + start = qMin(start, end); + end = qMax(tmp, end); + + d->prepareSectionSelected(); // clear and resize the bit array + + QRect currentSectionRect; + int logical; + const int width = d->viewport->width(); + const int height = d->viewport->height(); + for (int i = start; i <= end; ++i) { + if (d->isVisualIndexHidden(i)) + continue; + painter.save(); + logical = logicalIndex(i); + if (d->orientation == Qt::Horizontal) { + currentSectionRect.setRect(sectionViewportPosition(logical), 0, sectionSize(logical), height); + } else { + currentSectionRect.setRect(0, sectionViewportPosition(logical), width, sectionSize(logical)); + } + currentSectionRect.translate(offset); + + QVariant variant = d->model->headerData(logical, d->orientation, + Qt::FontRole); + if (variant.isValid() && variant.canConvert()) { + QFont sectionFont = qvariant_cast(variant); + painter.setFont(sectionFont); + } + paintSection(&painter, currentSectionRect, logical); + painter.restore(); + } + + QStyleOption opt; + opt.init(this); + // Paint the area beyond where there are indexes + if (d->reverse()) { + opt.state |= QStyle::State_Horizontal; + if (currentSectionRect.left() > translatedEventRect.left()) { + opt.rect = QRect(translatedEventRect.left(), 0, + currentSectionRect.left() - translatedEventRect.left(), height); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } + } else if (currentSectionRect.right() < translatedEventRect.right()) { + // paint to the right + opt.state |= QStyle::State_Horizontal; + opt.rect = QRect(currentSectionRect.right() + 1, 0, + translatedEventRect.right() - currentSectionRect.right(), height); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } else if (currentSectionRect.bottom() < translatedEventRect.bottom()) { + // paint the bottom section + opt.state &= ~QStyle::State_Horizontal; + opt.rect = QRect(0, currentSectionRect.bottom() + 1, + width, height - currentSectionRect.bottom() - 1); + style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this); + } + +#if 0 + // ### visualize section spans + for (int a = 0, i = 0; i < d->sectionSpans.count(); ++i) { + QColor color((i & 4 ? 255 : 0), (i & 2 ? 255 : 0), (i & 1 ? 255 : 0)); + if (d->orientation == Qt::Horizontal) + painter.fillRect(a - d->offset, 0, d->sectionSpans.at(i).size, 4, color); + else + painter.fillRect(0, a - d->offset, 4, d->sectionSpans.at(i).size, color); + a += d->sectionSpans.at(i).size; + } + +#endif +} + +/*! + \reimp +*/ + +void QHeaderView::mousePressEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + if (d->state != QHeaderViewPrivate::NoState || e->button() != Qt::LeftButton) + return; + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + int handle = d->sectionHandleAt(pos); + d->originalSize = -1; // clear the stored original size + if (handle == -1) { + d->pressed = logicalIndexAt(pos); + if (d->clickableSections) + emit sectionPressed(d->pressed); + if (d->movableSections) { + d->section = d->target = d->pressed; + if (d->section == -1) + return; + d->state = QHeaderViewPrivate::MoveSection; + d->setupSectionIndicator(d->section, pos); + } else if (d->clickableSections && d->pressed != -1) { + updateSection(d->pressed); + d->state = QHeaderViewPrivate::SelectSections; + } + } else if (resizeMode(handle) == Interactive) { + d->originalSize = sectionSize(handle); + d->state = QHeaderViewPrivate::ResizeSection; + d->section = handle; + } + + d->firstPos = pos; + d->lastPos = pos; + + d->clearCascadingSections(); +} + +/*! + \reimp +*/ + +void QHeaderView::mouseMoveEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + if (pos < 0) + return; + if (e->buttons() == Qt::NoButton) { + d->state = QHeaderViewPrivate::NoState; + d->pressed = -1; + } + switch (d->state) { + case QHeaderViewPrivate::ResizeSection: { + Q_ASSERT(d->originalSize != -1); + if (d->cascadingResizing) { + int delta = d->reverse() ? d->lastPos - pos : pos - d->lastPos; + int visual = visualIndex(d->section); + d->cascadingResize(visual, d->headerSectionSize(visual) + delta); + } else { + int delta = d->reverse() ? d->firstPos - pos : pos - d->firstPos; + resizeSection(d->section, qMax(d->originalSize + delta, minimumSectionSize())); + } + d->lastPos = pos; + return; + } + case QHeaderViewPrivate::MoveSection: { + if (qAbs(pos - d->firstPos) >= QApplication::startDragDistance() + || !d->sectionIndicator->isHidden()) { + int visual = visualIndexAt(pos); + if (visual == -1) + return; + int posThreshold = d->headerSectionPosition(visual) + d->headerSectionSize(visual) / 2; + int moving = visualIndex(d->section); + if (visual < moving) { + if (pos < posThreshold) + d->target = d->logicalIndex(visual); + else + d->target = d->logicalIndex(visual + 1); + } else if (visual > moving) { + if (pos > posThreshold) + d->target = d->logicalIndex(visual); + else + d->target = d->logicalIndex(visual - 1); + } else { + d->target = d->section; + } + d->updateSectionIndicator(d->section, pos); + } + return; + } + case QHeaderViewPrivate::SelectSections: { + int logical = logicalIndexAt(pos); + if (logical == d->pressed) + return; // nothing to do + else if (d->pressed != -1) + updateSection(d->pressed); + d->pressed = logical; + if (d->clickableSections && logical != -1) { + emit sectionEntered(d->pressed); + updateSection(d->pressed); + } + return; + } + case QHeaderViewPrivate::NoState: { +#ifndef QT_NO_CURSOR + int handle = d->sectionHandleAt(pos); + bool hasCursor = testAttribute(Qt::WA_SetCursor); + if (handle != -1 && (resizeMode(handle) == Interactive)) { + if (!hasCursor) + setCursor(d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); + } else if (hasCursor) { + unsetCursor(); + } +#endif + return; + } + default: + break; + } +} + +/*! + \reimp +*/ + +void QHeaderView::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + switch (d->state) { + case QHeaderViewPrivate::MoveSection: + if (!d->sectionIndicator->isHidden()) { // moving + int from = visualIndex(d->section); + Q_ASSERT(from != -1); + int to = visualIndex(d->target); + Q_ASSERT(to != -1); + moveSection(from, to); + d->section = d->target = -1; + d->updateSectionIndicator(d->section, pos); + break; + } // not moving + case QHeaderViewPrivate::SelectSections: + if (!d->clickableSections) { + int section = logicalIndexAt(pos); + updateSection(section); + } + // fall through + case QHeaderViewPrivate::NoState: + if (d->clickableSections) { + int section = logicalIndexAt(pos); + if (section != -1 && section == d->pressed) { + d->flipSortIndicator(section); + emit sectionClicked(section); + } + if (d->pressed != -1) + updateSection(d->pressed); + } + break; + case QHeaderViewPrivate::ResizeSection: + d->originalSize = -1; + d->clearCascadingSections(); + break; + default: + break; + } + d->state = QHeaderViewPrivate::NoState; + d->pressed = -1; +} + +/*! + \reimp +*/ + +void QHeaderView::mouseDoubleClickEvent(QMouseEvent *e) +{ + Q_D(QHeaderView); + int pos = d->orientation == Qt::Horizontal ? e->x() : e->y(); + int handle = d->sectionHandleAt(pos); + if (handle > -1 && resizeMode(handle) == Interactive) { + emit sectionHandleDoubleClicked(handle); +#ifndef QT_NO_CURSOR + Qt::CursorShape splitCursor = (d->orientation == Qt::Horizontal) + ? Qt::SplitHCursor : Qt::SplitVCursor; + if (cursor().shape() == splitCursor) { + // signal handlers may have changed the section size + handle = d->sectionHandleAt(pos); + if (!(handle > -1 && resizeMode(handle) == Interactive)) + setCursor(Qt::ArrowCursor); + } +#endif + } else { + emit sectionDoubleClicked(logicalIndexAt(e->pos())); + } +} + +/*! + \reimp +*/ + +bool QHeaderView::viewportEvent(QEvent *e) +{ + Q_D(QHeaderView); + switch (e->type()) { +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *he = static_cast(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QVariant variant = d->model->headerData(logical, d->orientation, Qt::ToolTipRole); + if (variant.isValid()) { + QToolTip::showText(he->globalPos(), variant.toString(), this); + return true; + } + } + break; } +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::QueryWhatsThis: { + QHelpEvent *he = static_cast(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1 + && d->model->headerData(logical, d->orientation, Qt::WhatsThisRole).isValid()) + return true; + break; } + case QEvent::WhatsThis: { + QHelpEvent *he = static_cast(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QVariant whatsthis = d->model->headerData(logical, d->orientation, + Qt::WhatsThisRole); + if (whatsthis.isValid()) { + QWhatsThis::showText(he->globalPos(), whatsthis.toString(), this); + return true; + } + } + break; } +#endif // QT_NO_WHATSTHIS +#ifndef QT_NO_STATUSTIP + case QEvent::StatusTip: { + QHelpEvent *he = static_cast(e); + int logical = logicalIndexAt(he->pos()); + if (logical != -1) { + QString statustip = d->model->headerData(logical, d->orientation, + Qt::StatusTipRole).toString(); + if (!statustip.isEmpty()) + setStatusTip(statustip); + } + return true; } +#endif // QT_NO_STATUSTIP + case QEvent::Hide: + case QEvent::Show: + case QEvent::FontChange: + case QEvent::StyleChange: + d->invalidateCachedSizeHint(); + resizeSections(); + emit geometriesChanged(); + break; + case QEvent::ContextMenu: { + d->state = QHeaderViewPrivate::NoState; + d->pressed = d->section = d->target = -1; + d->updateSectionIndicator(d->section, -1); + break; } + case QEvent::Wheel: { + QAbstractScrollArea *asa = qobject_cast(parentWidget()); + if (asa) + return QApplication::sendEvent(asa->viewport(), e); + break; } + default: + break; + } + return QAbstractItemView::viewportEvent(e); +} + +/*! + Paints the section specified by the given \a logicalIndex, using the given + \a painter and \a rect. + + Normally, you do not have to call this function. +*/ + +void QHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const +{ + Q_D(const QHeaderView); + if (!rect.isValid()) + return; + // get the state of the section + QStyleOptionHeader opt; + initStyleOption(&opt); + QStyle::State state = QStyle::State_None; + if (isEnabled()) + state |= QStyle::State_Enabled; + if (window()->isActiveWindow()) + state |= QStyle::State_Active; + if (d->clickableSections) { + if (logicalIndex == d->hover) + state |= QStyle::State_MouseOver; + if (logicalIndex == d->pressed) + state |= QStyle::State_Sunken; + else if (d->highlightSelected) { + if (d->sectionIntersectsSelection(logicalIndex)) + state |= QStyle::State_On; + if (d->isSectionSelected(logicalIndex)) + state |= QStyle::State_Sunken; + } + + } + if (isSortIndicatorShown() && sortIndicatorSection() == logicalIndex) + opt.sortIndicator = (sortIndicatorOrder() == Qt::AscendingOrder) + ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp; + + // setup the style options structure + QVariant textAlignment = d->model->headerData(logicalIndex, d->orientation, + Qt::TextAlignmentRole); + opt.rect = rect; + opt.section = logicalIndex; + opt.state |= state; + opt.textAlignment = Qt::Alignment(textAlignment.isValid() + ? Qt::Alignment(textAlignment.toInt()) + : d->defaultAlignment); + + opt.iconAlignment = Qt::AlignVCenter; + opt.text = d->model->headerData(logicalIndex, d->orientation, + Qt::DisplayRole).toString(); + if (d->textElideMode != Qt::ElideNone) + opt.text = opt.fontMetrics.elidedText(opt.text, d->textElideMode , rect.width() - 4); + + QVariant variant = d->model->headerData(logicalIndex, d->orientation, + Qt::DecorationRole); + opt.icon = qvariant_cast(variant); + if (opt.icon.isNull()) + opt.icon = qvariant_cast(variant); + QVariant foregroundBrush = d->model->headerData(logicalIndex, d->orientation, + Qt::ForegroundRole); + if (foregroundBrush.canConvert()) + opt.palette.setBrush(QPalette::ButtonText, qvariant_cast(foregroundBrush)); + + QPointF oldBO = painter->brushOrigin(); + QVariant backgroundBrush = d->model->headerData(logicalIndex, d->orientation, + Qt::BackgroundRole); + if (backgroundBrush.canConvert()) { + opt.palette.setBrush(QPalette::Button, qvariant_cast(backgroundBrush)); + opt.palette.setBrush(QPalette::Window, qvariant_cast(backgroundBrush)); + painter->setBrushOrigin(opt.rect.topLeft()); + } + + // the section position + int visual = visualIndex(logicalIndex); + Q_ASSERT(visual != -1); + if (count() == 1) + opt.position = QStyleOptionHeader::OnlyOneSection; + else if (visual == 0) + opt.position = QStyleOptionHeader::Beginning; + else if (visual == count() - 1) + opt.position = QStyleOptionHeader::End; + else + opt.position = QStyleOptionHeader::Middle; + opt.orientation = d->orientation; + // the selected position + bool previousSelected = d->isSectionSelected(this->logicalIndex(visual - 1)); + bool nextSelected = d->isSectionSelected(this->logicalIndex(visual + 1)); + if (previousSelected && nextSelected) + opt.selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected; + else if (previousSelected) + opt.selectedPosition = QStyleOptionHeader::PreviousIsSelected; + else if (nextSelected) + opt.selectedPosition = QStyleOptionHeader::NextIsSelected; + else + opt.selectedPosition = QStyleOptionHeader::NotAdjacent; + // draw the section + style()->drawControl(QStyle::CE_Header, &opt, painter, this); + + painter->setBrushOrigin(oldBO); +} + +/*! + Returns the size of the contents of the section specified by the given + \a logicalIndex. + + \sa defaultSectionSize() +*/ + +QSize QHeaderView::sectionSizeFromContents(int logicalIndex) const +{ + Q_D(const QHeaderView); + Q_ASSERT(logicalIndex >= 0); + + ensurePolished(); + + // use SizeHintRole + QVariant variant = d->model->headerData(logicalIndex, d->orientation, Qt::SizeHintRole); + if (variant.isValid()) + return qvariant_cast(variant); + + // otherwise use the contents + QStyleOptionHeader opt; + initStyleOption(&opt); + opt.section = logicalIndex; + QVariant var = d->model->headerData(logicalIndex, d->orientation, + Qt::FontRole); + QFont fnt; + if (var.isValid() && var.canConvert()) + fnt = qvariant_cast(var); + else + fnt = font(); + fnt.setBold(true); + opt.fontMetrics = QFontMetrics(fnt); + opt.text = d->model->headerData(logicalIndex, d->orientation, + Qt::DisplayRole).toString(); + variant = d->model->headerData(logicalIndex, d->orientation, Qt::DecorationRole); + opt.icon = qvariant_cast(variant); + if (opt.icon.isNull()) + opt.icon = qvariant_cast(variant); + QSize size = style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), this); + if (isSortIndicatorShown()) { + int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, &opt, this); + if (d->orientation == Qt::Horizontal) + size.rwidth() += size.height() + margin; + else + size.rheight() += size.width() + margin; + } + return size; +} + +/*! + Returns the horizontal offset of the header. This is 0 for vertical + headers. + + \sa offset() +*/ + +int QHeaderView::horizontalOffset() const +{ + Q_D(const QHeaderView); + if (d->orientation == Qt::Horizontal) + return d->offset; + return 0; +} + +/*! + Returns the vertical offset of the header. This is 0 for horizontal + headers. + + \sa offset() +*/ + +int QHeaderView::verticalOffset() const +{ + Q_D(const QHeaderView); + if (d->orientation == Qt::Vertical) + return d->offset; + return 0; +} + +/*! + \reimp + \internal +*/ + +void QHeaderView::updateGeometries() +{ + Q_D(QHeaderView); + d->layoutChildren(); + if (d->hasAutoResizeSections()) + d->doDelayedResizeSections(); +} + +/*! + \reimp + \internal +*/ + +void QHeaderView::scrollContentsBy(int dx, int dy) +{ + Q_D(QHeaderView); + d->scrollDirtyRegion(dx, dy); +} + +/*! + \reimp + \internal +*/ +void QHeaderView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(QHeaderView); + d->invalidateCachedSizeHint(); + if (d->hasAutoResizeSections()) { + bool resizeRequired = d->globalResizeMode == ResizeToContents; + int first = orientation() == Qt::Horizontal ? topLeft.column() : topLeft.row(); + int last = orientation() == Qt::Horizontal ? bottomRight.column() : bottomRight.row(); + for (int i = first; i <= last && !resizeRequired; ++i) + resizeRequired = (resizeMode(i) == ResizeToContents); + if (resizeRequired) + d->doDelayedResizeSections(); + } +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ +void QHeaderView::rowsInserted(const QModelIndex &, int, int) +{ + // do nothing +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QRect QHeaderView::visualRect(const QModelIndex &) const +{ + return QRect(); +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +void QHeaderView::scrollTo(const QModelIndex &, ScrollHint) +{ + // do nothing - the header only displays sections +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QModelIndex QHeaderView::indexAt(const QPoint &) const +{ + return QModelIndex(); +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +bool QHeaderView::isIndexHidden(const QModelIndex &) const +{ + return true; // the header view has no items, just sections +} + +/*! + \reimp + \internal + + Empty implementation because the header doesn't show QModelIndex items. +*/ + +QModelIndex QHeaderView::moveCursor(CursorAction, Qt::KeyboardModifiers) +{ + return QModelIndex(); +} + +/*! + \reimp + + Selects the items in the given \a rect according to the specified + \a flags. + + The base class implementation does nothing. +*/ + +void QHeaderView::setSelection(const QRect&, QItemSelectionModel::SelectionFlags) +{ + // do nothing +} + +/*! + \internal +*/ + +QRegion QHeaderView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QHeaderView); + const int max = d->modelSectionCount(); + if (d->orientation == Qt::Horizontal) { + int left = max; + int right = 0; + int rangeLeft, rangeRight; + + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange r = selection.at(i); + if (r.parent().isValid() || !r.isValid()) + continue; // we only know about toplevel items and we don't want invalid ranges + // FIXME an item inside the range may be the leftmost or rightmost + rangeLeft = visualIndex(r.left()); + if (rangeLeft == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + rangeRight = visualIndex(r.right()); + if (rangeRight == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + if (rangeLeft < left) + left = rangeLeft; + if (rangeRight > right) + right = rangeRight; + } + + int logicalLeft = logicalIndex(left); + int logicalRight = logicalIndex(right); + + if (logicalLeft < 0 || logicalLeft >= count() || + logicalRight < 0 || logicalRight >= count()) + return QRegion(); + + int leftPos = sectionViewportPosition(logicalLeft); + int rightPos = sectionViewportPosition(logicalRight); + rightPos += sectionSize(logicalRight); + return QRect(leftPos, 0, rightPos - leftPos, height()); + } + // orientation() == Qt::Vertical + int top = max; + int bottom = 0; + int rangeTop, rangeBottom; + + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange r = selection.at(i); + if (r.parent().isValid() || !r.isValid()) + continue; // we only know about toplevel items + // FIXME an item inside the range may be the leftmost or rightmost + rangeTop = visualIndex(r.top()); + if (rangeTop == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + rangeBottom = visualIndex(r.bottom()); + if (rangeBottom == -1) // in some cases users may change the selections + continue; // before we have a chance to do the layout + if (rangeTop < top) + top = rangeTop; + if (rangeBottom > bottom) + bottom = rangeBottom; + } + + int logicalTop = logicalIndex(top); + int logicalBottom = logicalIndex(bottom); + + if (logicalTop == -1 || logicalBottom == -1) + return QRect(); + + int topPos = sectionViewportPosition(logicalTop); + int bottomPos = sectionViewportPosition(logicalBottom) + sectionSize(logicalBottom); + + return QRect(0, topPos, width(), bottomPos - topPos); +} + + +// private implementation + +int QHeaderViewPrivate::sectionHandleAt(int position) +{ + Q_Q(QHeaderView); + int visual = q->visualIndexAt(position); + if (visual == -1) + return -1; + int log = logicalIndex(visual); + int pos = q->sectionViewportPosition(log); + int grip = q->style()->pixelMetric(QStyle::PM_HeaderGripMargin, 0, q); + + bool atLeft = position < pos + grip; + bool atRight = (position > pos + q->sectionSize(log) - grip); + if (reverse()) + qSwap(atLeft, atRight); + + if (atLeft) { + //grip at the beginning of the section + while(visual > -1) { + int logical = q->logicalIndex(--visual); + if (!q->isSectionHidden(logical)) + return logical; + } + } else if (atRight) { + //grip at the end of the section + return log; + } + return -1; +} + +void QHeaderViewPrivate::setupSectionIndicator(int section, int position) +{ + Q_Q(QHeaderView); + if (!sectionIndicator) { + sectionIndicator = new QLabel(viewport); + } + + int w, h; + int p = q->sectionViewportPosition(section); + if (orientation == Qt::Horizontal) { + w = q->sectionSize(section); + h = viewport->height(); + } else { + w = viewport->width(); + h = q->sectionSize(section); + } + sectionIndicator->resize(w, h); + + QPixmap pm(w, h); + pm.fill(QColor(0, 0, 0, 45)); + QRect rect(0, 0, w, h); + + QPainter painter(&pm); + painter.setOpacity(0.75); + q->paintSection(&painter, rect, section); + painter.end(); + + sectionIndicator->setPixmap(pm); + sectionIndicatorOffset = position - qMax(p, 0); +} + +void QHeaderViewPrivate::updateSectionIndicator(int section, int position) +{ + if (!sectionIndicator) + return; + + if (section == -1 || target == -1) { + sectionIndicator->hide(); + return; + } + + if (orientation == Qt::Horizontal) + sectionIndicator->move(position - sectionIndicatorOffset, 0); + else + sectionIndicator->move(0, position - sectionIndicatorOffset); + + sectionIndicator->show(); +} + +/*! + Initialize \a option with the values from this QHeaderView. This method is + useful for subclasses when they need a QStyleOptionHeader, but do not want + to fill in all the information themselves. + + \sa QStyleOption::initFrom() +*/ +void QHeaderView::initStyleOption(QStyleOptionHeader *option) const +{ + Q_D(const QHeaderView); + option->initFrom(this); + option->state = QStyle::State_None | QStyle::State_Raised; + option->orientation = d->orientation; + if (d->orientation == Qt::Horizontal) + option->state |= QStyle::State_Horizontal; + if (isEnabled()) + option->state |= QStyle::State_Enabled; + option->section = 0; +} + +bool QHeaderViewPrivate::isSectionSelected(int section) const +{ + int i = section * 2; + if (i < 0 || i >= sectionSelected.count()) + return false; + if (sectionSelected.testBit(i)) // if the value was cached + return sectionSelected.testBit(i + 1); + bool s = false; + if (orientation == Qt::Horizontal) + s = isColumnSelected(section); + else + s = isRowSelected(section); + sectionSelected.setBit(i + 1, s); // selection state + sectionSelected.setBit(i, true); // cache state + return s; +} + +/*! + \internal + Returns the last visible (ie. not hidden) visual index +*/ +int QHeaderViewPrivate::lastVisibleVisualIndex() const +{ + Q_Q(const QHeaderView); + for (int visual = q->count()-1; visual >= 0; --visual) { + if (!q->isSectionHidden(q->logicalIndex(visual))) + return visual; + } + + //default value if no section is actually visible + return -1; +} + +/*! + \internal + Go through and resize all of the sections applying stretchLastSection, + manualy stretches, sizes, and useGlobalMode. + + The different resize modes are: + Interactive - the user decides the size + Stretch - take up whatever space is left + Fixed - the size is set programmatically outside the header + ResizeToContentes - the size is set based on the contents of the row or column in the parent view + + The resize mode will not affect the last section if stretchLastSection is true. +*/ +void QHeaderViewPrivate::resizeSections(QHeaderView::ResizeMode globalMode, bool useGlobalMode) +{ + Q_Q(QHeaderView); + //stop the timer in case it is delayed + delayedResize.stop(); + + executePostedLayout(); + if (sectionCount == 0) + return; + + if (resizeRecursionBlock) + return; + resizeRecursionBlock = true; + + invalidateCachedSizeHint(); + + const int lastVisibleSection = lastVisibleVisualIndex(); + + // find stretchLastSection if we have it + int stretchSection = -1; + if (stretchLastSection && !useGlobalMode) + stretchSection = lastVisibleVisualIndex(); + + // count up the number of strected sections and how much space left for them + int lengthToStrech = (orientation == Qt::Horizontal ? viewport->width() : viewport->height()); + int numberOfStretchedSections = 0; + QList section_sizes; + for (int i = 0; i < sectionCount; ++i) { + if (isVisualIndexHidden(i)) + continue; + + QHeaderView::ResizeMode resizeMode; + if (useGlobalMode && (i != stretchSection)) + resizeMode = globalMode; + else + resizeMode = (i == stretchSection ? QHeaderView::Stretch : headerSectionResizeMode(i)); + + if (resizeMode == QHeaderView::Stretch) { + ++numberOfStretchedSections; + section_sizes.append(headerSectionSize(i)); + continue; + } + + // because it isn't stretch, determine its width and remove that from lengthToStrech + int sectionSize = 0; + if (resizeMode == QHeaderView::Interactive || resizeMode == QHeaderView::Fixed) { + sectionSize = headerSectionSize(i); + } else { // resizeMode == QHeaderView::ResizeToContents + int logicalIndex = q->logicalIndex(i); + sectionSize = qMax(viewSectionSizeHint(logicalIndex), + q->sectionSizeHint(logicalIndex)); + } + section_sizes.append(sectionSize); + lengthToStrech -= sectionSize; + } + + // calculate the new length for all of the stretched sections + int stretchSectionLength = -1; + int pixelReminder = 0; + if (numberOfStretchedSections > 0 && lengthToStrech > 0) { // we have room to stretch in + int hintLengthForEveryStretchedSection = lengthToStrech / numberOfStretchedSections; + stretchSectionLength = qMax(hintLengthForEveryStretchedSection, q->minimumSectionSize()); + pixelReminder = lengthToStrech % numberOfStretchedSections; + } + + int spanStartSection = 0; + int previousSectionLength = 0; + + QHeaderView::ResizeMode previousSectionResizeMode = QHeaderView::Interactive; + + // resize each section along the total length + for (int i = 0; i < sectionCount; ++i) { + int oldSectionLength = headerSectionSize(i); + int newSectionLength = -1; + QHeaderView::ResizeMode newSectionResizeMode = headerSectionResizeMode(i); + + if (isVisualIndexHidden(i)) { + newSectionLength = 0; + } else { + QHeaderView::ResizeMode resizeMode; + if (useGlobalMode) + resizeMode = globalMode; + else + resizeMode = (i == stretchSection + ? QHeaderView::Stretch + : newSectionResizeMode); + if (resizeMode == QHeaderView::Stretch && stretchSectionLength != -1) { + if (i == lastVisibleSection) + newSectionLength = qMax(stretchSectionLength, lastSectionSize); + else + newSectionLength = stretchSectionLength; + if (pixelReminder > 0) { + newSectionLength += 1; + --pixelReminder; + } + section_sizes.removeFirst(); + } else { + newSectionLength = section_sizes.front(); + section_sizes.removeFirst(); + } + } + + //Q_ASSERT(newSectionLength > 0); + if ((previousSectionResizeMode != newSectionResizeMode + || previousSectionLength != newSectionLength) && i > 0) { + int spanLength = (i - spanStartSection) * previousSectionLength; + createSectionSpan(spanStartSection, i - 1, spanLength, previousSectionResizeMode); + //Q_ASSERT(headerLength() == length); + spanStartSection = i; + } + + if (newSectionLength != oldSectionLength) + emit q->sectionResized(logicalIndex(i), oldSectionLength, newSectionLength); + + previousSectionLength = newSectionLength; + previousSectionResizeMode = newSectionResizeMode; + } + + createSectionSpan(spanStartSection, sectionCount - 1, + (sectionCount - spanStartSection) * previousSectionLength, + previousSectionResizeMode); + //Q_ASSERT(headerLength() == length); + resizeRecursionBlock = false; + viewport->update(); +} + +void QHeaderViewPrivate::createSectionSpan(int start, int end, int size, QHeaderView::ResizeMode mode) +{ + // ### the code for merging spans does not merge at all opertuneties + // ### what if the number of sections is reduced ? + + SectionSpan span(size, (end - start) + 1, mode); + int start_section = 0; +#ifndef QT_NO_DEBUG + int initial_section_count = headerSectionCount(); // ### debug code +#endif + + QList spansToRemove; + for (int i = 0; i < sectionSpans.count(); ++i) { + int end_section = start_section + sectionSpans.at(i).count - 1; + int section_count = sectionSpans.at(i).count; + if (start <= start_section && end > end_section) { + // the existing span is entirely coveded by the new span + spansToRemove.append(i); + } else if (start < start_section && end >= end_section) { + // the existing span is entirely coveded by the new span + spansToRemove.append(i); + } else if (start == start_section && end == end_section) { + // the new span is covered by an existin span + length -= sectionSpans.at(i).size; + length += size; + sectionSpans[i].size = size; + sectionSpans[i].resizeMode = mode; + // ### check if we can merge the section with any of its neighbours + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } else if (start > start_section && end < end_section) { + if (sectionSpans.at(i).sectionSize() == span.sectionSize() + && sectionSpans.at(i).resizeMode == span.resizeMode) { + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + // the new span is in the middle of the old span, so we have to split it + length -= sectionSpans.at(i).size; + int section_size = sectionSpans.at(i).sectionSize(); +#ifndef QT_NO_DEBUG + int span_count = sectionSpans.at(i).count; +#endif + QHeaderView::ResizeMode span_mode = sectionSpans.at(i).resizeMode; + // first span + int first_span_count = start - start_section; + int first_span_size = section_size * first_span_count; + sectionSpans[i].count = first_span_count; + sectionSpans[i].size = first_span_size; + sectionSpans[i].resizeMode = span_mode; + length += first_span_size; + // middle span (the new span) +#ifndef QT_NO_DEBUG + int mid_span_count = span.count; +#endif + int mid_span_size = span.size; + sectionSpans.insert(i + 1, span); + length += mid_span_size; + // last span + int last_span_count = end_section - end; + int last_span_size = section_size * last_span_count; + sectionSpans.insert(i + 2, SectionSpan(last_span_size, last_span_count, span_mode)); + length += last_span_size; + Q_ASSERT(span_count == first_span_count + mid_span_count + last_span_count); + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } else if (start > start_section && start <= end_section && end >= end_section) { + // the new span covers the last part of the existing span + length -= sectionSpans.at(i).size; + int removed_count = (end_section - start + 1); + int span_count = sectionSpans.at(i).count - removed_count; + int section_size = sectionSpans.at(i).sectionSize(); + int span_size = section_size * span_count; + sectionSpans[i].count = span_count; + sectionSpans[i].size = span_size; + length += span_size; + if (end == end_section) { + sectionSpans.insert(i + 1, span); // insert after + length += span.size; + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + } else if (end < end_section && end >= start_section && start <= start_section) { + // the new span covers the first part of the existing span + length -= sectionSpans.at(i).size; + int removed_count = (end - start_section + 1); + int section_size = sectionSpans.at(i).sectionSize(); + int span_count = sectionSpans.at(i).count - removed_count; + int span_size = section_size * span_count; + sectionSpans[i].count = span_count; + sectionSpans[i].size = span_size; + length += span_size; + sectionSpans.insert(i, span); // insert before + length += span.size; + removeSpans(spansToRemove); + Q_ASSERT(initial_section_count == headerSectionCount()); + return; + } + start_section += section_count; + } + + // ### adding and removing _ sections_ in addition to spans + // ### add some more checks here + + if (spansToRemove.isEmpty()) { + if (!sectionSpans.isEmpty() + && sectionSpans.last().sectionSize() == span.sectionSize() + && sectionSpans.last().resizeMode == span.resizeMode) { + length += span.size; + int last = sectionSpans.count() - 1; + sectionSpans[last].count += span.count; + sectionSpans[last].size += span.size; + sectionSpans[last].resizeMode = span.resizeMode; + } else { + length += span.size; + sectionSpans.append(span); + } + } else { + removeSpans(spansToRemove); + length += span.size; + sectionSpans.insert(spansToRemove.first(), span); + //Q_ASSERT(initial_section_count == headerSectionCount()); + } +} + +void QHeaderViewPrivate::removeSectionsFromSpans(int start, int end) +{ + // remove sections + int start_section = 0; + QList spansToRemove; + for (int i = 0; i < sectionSpans.count(); ++i) { + int end_section = start_section + sectionSpans.at(i).count - 1; + int section_size = sectionSpans.at(i).sectionSize(); + int section_count = sectionSpans.at(i).count; + if (start <= start_section && end >= end_section) { + // the change covers the entire span + spansToRemove.append(i); + if (end == end_section) + break; + } else if (start > start_section && end < end_section) { + // all the removed sections are inside the span + int change = (end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + length -= (change * section_size); + break; + } else if (start >= start_section && start <= end_section) { + // the some of the removed sections are inside the span,at the end + int change = qMin(end_section - start + 1, end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + start += change; + length -= (change * section_size); + // the change affects several spans + } else if (end >= start_section && end <= end_section) { + // the some of the removed sections are inside the span, at the beginning + int change = qMin((end - start_section + 1), end - start + 1); + sectionSpans[i].count -= change; + sectionSpans[i].size = section_size * sectionSpans.at(i).count; + length -= (change * section_size); + break; + } + start_section += section_count; + } + + for (int i = spansToRemove.count() - 1; i >= 0; --i) { + int s = spansToRemove.at(i); + length -= sectionSpans.at(s).size; + sectionSpans.remove(s); + // ### merge remaining spans + } +} + +void QHeaderViewPrivate::clear() +{ + if (state != NoClear) { + length = 0; + sectionCount = 0; + visualIndices.clear(); + logicalIndices.clear(); + sectionSelected.clear(); + sectionHidden.clear(); + hiddenSectionSize.clear(); + sectionSpans.clear(); + } +} + +void QHeaderViewPrivate::flipSortIndicator(int section) +{ + Q_Q(QHeaderView); + Qt::SortOrder sortOrder; + if (sortIndicatorSection == section) { + sortOrder = (sortIndicatorOrder == Qt::DescendingOrder) ? Qt::AscendingOrder : Qt::DescendingOrder; + } else { + const QVariant value = model->headerData(section, orientation, Qt::InitialSortOrderRole); + if (value.canConvert(QVariant::Int)) + sortOrder = static_cast(value.toInt()); + else + sortOrder = Qt::AscendingOrder; + } + q->setSortIndicator(section, sortOrder); +} + +void QHeaderViewPrivate::cascadingResize(int visual, int newSize) +{ + Q_Q(QHeaderView); + const int minimumSize = q->minimumSectionSize(); + const int oldSize = headerSectionSize(visual); + int delta = newSize - oldSize; + + if (delta > 0) { // larger + bool sectionResized = false; + + // restore old section sizes + for (int i = firstCascadingSection; i < visual; ++i) { + if (cascadingSectionSize.contains(i)) { + int currentSectionSize = headerSectionSize(i); + int originalSectionSize = cascadingSectionSize.value(i); + if (currentSectionSize < originalSectionSize) { + int newSectionSize = currentSectionSize + delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + if (newSectionSize >= originalSectionSize && false) + cascadingSectionSize.remove(i); // the section is now restored + sectionResized = true; + break; + } + } + + } + + // resize the section + if (!sectionResized) { + newSize = qMax(newSize, minimumSize); + if (oldSize != newSize) + resizeSectionSpan(visual, oldSize, newSize); + } + + // cascade the section size change + for (int i = visual + 1; i < sectionCount; ++i) { + if (!sectionIsCascadable(i)) + continue; + int currentSectionSize = headerSectionSize(i); + if (currentSectionSize <= minimumSize) + continue; + int newSectionSize = qMax(currentSectionSize - delta, minimumSize); + //qDebug() << "### cascading to" << i << newSectionSize - currentSectionSize << delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + saveCascadingSectionSize(i, currentSectionSize); + delta = delta - (currentSectionSize - newSectionSize); + //qDebug() << "new delta" << delta; + //if (newSectionSize != minimumSize) + if (delta <= 0) + break; + } + } else { // smaller + bool sectionResized = false; + + // restore old section sizes + for (int i = lastCascadingSection; i > visual; --i) { + if (!cascadingSectionSize.contains(i)) + continue; + int currentSectionSize = headerSectionSize(i); + int originalSectionSize = cascadingSectionSize.value(i); + if (currentSectionSize >= originalSectionSize) + continue; + int newSectionSize = currentSectionSize - delta; + resizeSectionSpan(i, currentSectionSize, newSectionSize); + if (newSectionSize >= originalSectionSize && false) { + //qDebug() << "section" << i << "restored to" << originalSectionSize; + cascadingSectionSize.remove(i); // the section is now restored + } + sectionResized = true; + break; + } + + // resize the section + resizeSectionSpan(visual, oldSize, qMax(newSize, minimumSize)); + + // cascade the section size change + if (delta < 0 && newSize < minimumSize) { + for (int i = visual - 1; i >= 0; --i) { + if (!sectionIsCascadable(i)) + continue; + int sectionSize = headerSectionSize(i); + if (sectionSize <= minimumSize) + continue; + resizeSectionSpan(i, sectionSize, qMax(sectionSize + delta, minimumSize)); + saveCascadingSectionSize(i, sectionSize); + break; + } + } + + // let the next section get the space from the resized section + if (!sectionResized) { + for (int i = visual + 1; i < sectionCount; ++i) { + if (!sectionIsCascadable(i)) + continue; + int currentSectionSize = headerSectionSize(i); + int newSectionSize = qMax(currentSectionSize - delta, minimumSize); + resizeSectionSpan(i, currentSectionSize, newSectionSize); + break; + } + } + } + + if (hasAutoResizeSections()) + doDelayedResizeSections(); + + viewport->update(); +} + +void QHeaderViewPrivate::setDefaultSectionSize(int size) +{ + Q_Q(QHeaderView); + defaultSectionSize = size; + int currentVisualIndex = 0; + for (int i = 0; i < sectionSpans.count(); ++i) { + QHeaderViewPrivate::SectionSpan &span = sectionSpans[i]; + if (span.size > 0) { + //we resize it if it is not hidden (ie size > 0) + const int newSize = span.count * size; + if (newSize != span.size) { + length += newSize - span.size; //the whole length is changed + const int oldSectionSize = span.sectionSize(); + span.size = span.count * size; + for (int i = currentVisualIndex; i < currentVisualIndex + span.count; ++i) { + emit q->sectionResized(logicalIndex(i), oldSectionSize, size); + } + } + } + currentVisualIndex += span.count; + } +} + +void QHeaderViewPrivate::resizeSectionSpan(int visualIndex, int oldSize, int newSize) +{ + Q_Q(QHeaderView); + QHeaderView::ResizeMode mode = headerSectionResizeMode(visualIndex); + createSectionSpan(visualIndex, visualIndex, newSize, mode); + emit q->sectionResized(logicalIndex(visualIndex), oldSize, newSize); +} + +int QHeaderViewPrivate::headerSectionSize(int visual) const +{ + // ### stupid iteration + int section_start = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int section_end = section_start + currentSection.count - 1; + if (visual >= section_start && visual <= section_end) + return currentSection.sectionSize(); + section_start = section_end + 1; + } + return -1; +} + +int QHeaderViewPrivate::headerSectionPosition(int visual) const +{ + // ### stupid iteration + int section_start = 0; + int span_position = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int section_end = section_start + currentSection.count - 1; + if (visual >= section_start && visual <= section_end) + return span_position + (visual - section_start) * currentSection.sectionSize(); + section_start = section_end + 1; + span_position += currentSection.size; + } + return -1; +} + +int QHeaderViewPrivate::headerVisualIndexAt(int position) const +{ + // ### stupid iteration + int span_start_section = 0; + int span_position = 0; + const int sectionSpansCount = sectionSpans.count(); + for (int i = 0; i < sectionSpansCount; ++i) { + const QHeaderViewPrivate::SectionSpan ¤tSection = sectionSpans.at(i); + int next_span_start_section = span_start_section + currentSection.count; + int next_span_position = span_position + currentSection.size; + if (position == span_position) + return span_start_section; // spans with no size + if (position > span_position && position < next_span_position) { + int position_in_span = position - span_position; + return span_start_section + (position_in_span / currentSection.sectionSize()); + } + span_start_section = next_span_start_section; + span_position = next_span_position; + } + return -1; +} + +void QHeaderViewPrivate::setHeaderSectionResizeMode(int visual, QHeaderView::ResizeMode mode) +{ + int size = headerSectionSize(visual); + createSectionSpan(visual, visual, size, mode); +} + +QHeaderView::ResizeMode QHeaderViewPrivate::headerSectionResizeMode(int visual) const +{ + int span = sectionSpanIndex(visual); + if (span == -1) + return globalResizeMode; + return sectionSpans.at(span).resizeMode; +} + +void QHeaderViewPrivate::setGlobalHeaderResizeMode(QHeaderView::ResizeMode mode) +{ + globalResizeMode = mode; + for (int i = 0; i < sectionSpans.count(); ++i) + sectionSpans[i].resizeMode = mode; +} + +int QHeaderViewPrivate::viewSectionSizeHint(int logical) const +{ + if (QAbstractItemView *view = qobject_cast(parent)) { + return (orientation == Qt::Horizontal + ? view->sizeHintForColumn(logical) + : view->sizeHintForRow(logical)); + } + return 0; +} + +int QHeaderViewPrivate::adjustedVisualIndex(int visualIndex) const +{ + if (hiddenSectionSize.count() > 0) { + int adjustedVisualIndex = visualIndex; + int currentVisualIndex = 0; + for (int i = 0; i < sectionSpans.count(); ++i) { + if (sectionSpans.at(i).size == 0) + adjustedVisualIndex += sectionSpans.at(i).count; + else + currentVisualIndex += sectionSpans.at(i).count; + if (currentVisualIndex >= visualIndex) + break; + } + visualIndex = adjustedVisualIndex; + } + return visualIndex; +} + +#ifndef QT_NO_DATASTREAM +void QHeaderViewPrivate::write(QDataStream &out) const +{ + out << int(orientation); + out << int(sortIndicatorOrder); + out << sortIndicatorSection; + out << sortIndicatorShown; + + out << visualIndices; + out << logicalIndices; + + out << sectionHidden; + out << hiddenSectionSize; + + out << length; + out << sectionCount; + out << movableSections; + out << clickableSections; + out << highlightSelected; + out << stretchLastSection; + out << cascadingResizing; + out << stretchSections; + out << contentsSections; + out << defaultSectionSize; + out << minimumSectionSize; + + out << int(defaultAlignment); + out << int(globalResizeMode); + + out << sectionSpans; +} + +bool QHeaderViewPrivate::read(QDataStream &in) +{ + int orient, order, align, global; + in >> orient; + orientation = (Qt::Orientation)orient; + + in >> order; + sortIndicatorOrder = (Qt::SortOrder)order; + + in >> sortIndicatorSection; + in >> sortIndicatorShown; + + in >> visualIndices; + in >> logicalIndices; + + in >> sectionHidden; + in >> hiddenSectionSize; + + in >> length; + in >> sectionCount; + in >> movableSections; + in >> clickableSections; + in >> highlightSelected; + in >> stretchLastSection; + in >> cascadingResizing; + in >> stretchSections; + in >> contentsSections; + in >> defaultSectionSize; + in >> minimumSectionSize; + + in >> align; + defaultAlignment = Qt::Alignment(align); + + in >> global; + globalResizeMode = (QHeaderView::ResizeMode)global; + + in >> sectionSpans; + + return true; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS + +#include "moc_qheaderview.cpp" diff --git a/src/gui/itemviews/qheaderview.h b/src/gui/itemviews/qheaderview.h new file mode 100644 index 0000000000..bd0b7666b0 --- /dev/null +++ b/src/gui/itemviews/qheaderview.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHEADERVIEW_H +#define QHEADERVIEW_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QHeaderViewPrivate; +class QStyleOptionHeader; + +class Q_GUI_EXPORT QHeaderView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(bool showSortIndicator READ isSortIndicatorShown WRITE setSortIndicatorShown) + Q_PROPERTY(bool highlightSections READ highlightSections WRITE setHighlightSections) + Q_PROPERTY(bool stretchLastSection READ stretchLastSection WRITE setStretchLastSection) + Q_PROPERTY(bool cascadingSectionResizes READ cascadingSectionResizes WRITE setCascadingSectionResizes) + Q_PROPERTY(int defaultSectionSize READ defaultSectionSize WRITE setDefaultSectionSize) + Q_PROPERTY(int minimumSectionSize READ minimumSectionSize WRITE setMinimumSectionSize) + Q_PROPERTY(Qt::Alignment defaultAlignment READ defaultAlignment WRITE setDefaultAlignment) + Q_ENUMS(ResizeMode) + +public: + + enum ResizeMode + { + Interactive, + Stretch, + Fixed, + ResizeToContents, + Custom = Fixed + }; + + explicit QHeaderView(Qt::Orientation orientation, QWidget *parent = 0); + virtual ~QHeaderView(); + + void setModel(QAbstractItemModel *model); + + Qt::Orientation orientation() const; + int offset() const; + int length() const; + QSize sizeHint() const; + int sectionSizeHint(int logicalIndex) const; + + int visualIndexAt(int position) const; + int logicalIndexAt(int position) const; + + inline int logicalIndexAt(int x, int y) const; + inline int logicalIndexAt(const QPoint &pos) const; + + int sectionSize(int logicalIndex) const; + int sectionPosition(int logicalIndex) const; + int sectionViewportPosition(int logicalIndex) const; + + void moveSection(int from, int to); + void swapSections(int first, int second); + void resizeSection(int logicalIndex, int size); + void resizeSections(QHeaderView::ResizeMode mode); + + bool isSectionHidden(int logicalIndex) const; + void setSectionHidden(int logicalIndex, bool hide); + int hiddenSectionCount() const; + + inline void hideSection(int logicalIndex); + inline void showSection(int logicalIndex); + + int count() const; + int visualIndex(int logicalIndex) const; + int logicalIndex(int visualIndex) const; + + void setMovable(bool movable); + bool isMovable() const; + + void setClickable(bool clickable); + bool isClickable() const; + + void setHighlightSections(bool highlight); + bool highlightSections() const; + + void setResizeMode(ResizeMode mode); + void setResizeMode(int logicalIndex, ResizeMode mode); + ResizeMode resizeMode(int logicalIndex) const; + int stretchSectionCount() const; + + void setSortIndicatorShown(bool show); + bool isSortIndicatorShown() const; + + void setSortIndicator(int logicalIndex, Qt::SortOrder order); + int sortIndicatorSection() const; + Qt::SortOrder sortIndicatorOrder() const; + + bool stretchLastSection() const; + void setStretchLastSection(bool stretch); + + bool cascadingSectionResizes() const; + void setCascadingSectionResizes(bool enable); + + int defaultSectionSize() const; + void setDefaultSectionSize(int size); + + int minimumSectionSize() const; + void setMinimumSectionSize(int size); + + Qt::Alignment defaultAlignment() const; + void setDefaultAlignment(Qt::Alignment alignment); + + void doItemsLayout(); + bool sectionsMoved() const; + bool sectionsHidden() const; + +#ifndef QT_NO_DATASTREAM + QByteArray saveState() const; + bool restoreState(const QByteArray &state); +#endif + + void reset(); + +public Q_SLOTS: + void setOffset(int offset); + void setOffsetToSectionPosition(int visualIndex); + void setOffsetToLastSection(); + void headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast); + +Q_SIGNALS: + void sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void sectionResized(int logicalIndex, int oldSize, int newSize); + void sectionPressed(int logicalIndex); + void sectionClicked(int logicalIndex); + void sectionEntered(int logicalIndex); + void sectionDoubleClicked(int logicalIndex); + void sectionCountChanged(int oldCount, int newCount); + void sectionHandleDoubleClicked(int logicalIndex); + void sectionAutoResize(int logicalIndex, QHeaderView::ResizeMode mode); + void geometriesChanged(); + void sortIndicatorChanged(int logicalIndex, Qt::SortOrder order); + +protected Q_SLOTS: + void updateSection(int logicalIndex); + void resizeSections(); + void sectionsInserted(const QModelIndex &parent, int logicalFirst, int logicalLast); + void sectionsAboutToBeRemoved(const QModelIndex &parent, int logicalFirst, int logicalLast); + +protected: + QHeaderView(QHeaderViewPrivate &dd, Qt::Orientation orientation, QWidget *parent = 0); + void initialize(); + + void initializeSections(); + void initializeSections(int start, int end); + void currentChanged(const QModelIndex ¤t, const QModelIndex &old); + + bool event(QEvent *e); + void paintEvent(QPaintEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseDoubleClickEvent(QMouseEvent *e); + bool viewportEvent(QEvent *e); + + virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; + virtual QSize sectionSizeFromContents(int logicalIndex) const; + + int horizontalOffset() const; + int verticalOffset() const; + void updateGeometries(); + void scrollContentsBy(int dx, int dy); + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint); + + QModelIndex indexAt(const QPoint &p) const; + bool isIndexHidden(const QModelIndex &index) const; + + QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers); + void setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + void initStyleOption(QStyleOptionHeader *option) const; + +private: + Q_PRIVATE_SLOT(d_func(), void _q_sectionsRemoved(const QModelIndex &parent, int logicalFirst, int logicalLast)) + Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged()) + Q_DECLARE_PRIVATE(QHeaderView) + Q_DISABLE_COPY(QHeaderView) +}; + +inline int QHeaderView::logicalIndexAt(int ax, int ay) const +{ return orientation() == Qt::Horizontal ? logicalIndexAt(ax) : logicalIndexAt(ay); } +inline int QHeaderView::logicalIndexAt(const QPoint &apos) const +{ return logicalIndexAt(apos.x(), apos.y()); } +inline void QHeaderView::hideSection(int alogicalIndex) +{ setSectionHidden(alogicalIndex, true); } +inline void QHeaderView::showSection(int alogicalIndex) +{ setSectionHidden(alogicalIndex, false); } + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QHEADERVIEW_H diff --git a/src/gui/itemviews/qheaderview_p.h b/src/gui/itemviews/qheaderview_p.h new file mode 100644 index 0000000000..f4b7ff7df0 --- /dev/null +++ b/src/gui/itemviews/qheaderview_p.h @@ -0,0 +1,366 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHEADERVIEW_P_H +#define QHEADERVIEW_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/qabstractitemview_p.h" + +#ifndef QT_NO_ITEMVIEWS + +#include "QtCore/qbitarray.h" +#include "QtGui/qapplication.h" +#include "QtGui/qlabel.h" + +QT_BEGIN_NAMESPACE + +class QHeaderViewPrivate: public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QHeaderView) + +public: + enum StateVersion { VersionMarker = 0xff }; + + QHeaderViewPrivate() + : state(NoState), + offset(0), + sortIndicatorOrder(Qt::DescendingOrder), + sortIndicatorSection(0), + sortIndicatorShown(false), + lastPos(-1), + firstPos(-1), + originalSize(-1), + section(-1), + target(-1), + pressed(-1), + hover(-1), + length(0), + sectionCount(0), + movableSections(false), + clickableSections(false), + highlightSelected(false), + stretchLastSection(false), + cascadingResizing(false), + resizeRecursionBlock(false), + stretchSections(0), + contentsSections(0), + minimumSectionSize(-1), + lastSectionSize(0), + sectionIndicatorOffset(0), + sectionIndicator(0), + globalResizeMode(QHeaderView::Interactive) + {} + + + int lastVisibleVisualIndex() const; + int sectionHandleAt(int position); + void setupSectionIndicator(int section, int position); + void updateSectionIndicator(int section, int position); + void updateHiddenSections(int logicalFirst, int logicalLast); + void resizeSections(QHeaderView::ResizeMode globalMode, bool useGlobalMode = false); + void _q_sectionsRemoved(const QModelIndex &,int,int); + void _q_layoutAboutToBeChanged(); + void _q_layoutChanged(); + + bool isSectionSelected(int section) const; + + inline bool rowIntersectsSelection(int row) const { + return (selectionModel ? selectionModel->rowIntersectsSelection(row, root) : false); + } + + inline bool columnIntersectsSelection(int column) const { + return (selectionModel ? selectionModel->columnIntersectsSelection(column, root) : false); + } + + inline bool sectionIntersectsSelection(int logical) const { + return (orientation == Qt::Horizontal ? columnIntersectsSelection(logical) : rowIntersectsSelection(logical)); + } + + inline bool isRowSelected(int row) const { + return (selectionModel ? selectionModel->isRowSelected(row, root) : false); + } + + inline bool isColumnSelected(int column) const { + return (selectionModel ? selectionModel->isColumnSelected(column, root) : false); + } + + inline void prepareSectionSelected() { + if (!selectionModel || !selectionModel->hasSelection()) + sectionSelected.clear(); + else if (sectionSelected.count() != sectionCount * 2) + sectionSelected.fill(false, sectionCount * 2); + else sectionSelected.fill(false); + } + + inline bool reverse() const { + return orientation == Qt::Horizontal && q_func()->isRightToLeft(); + } + + inline int logicalIndex(int visualIndex) const { + return logicalIndices.isEmpty() ? visualIndex : logicalIndices.at(visualIndex); + } + + inline int visualIndex(int logicalIndex) const { + return visualIndices.isEmpty() ? logicalIndex : visualIndices.at(logicalIndex); + } + + inline void setDefaultValues(Qt::Orientation o) { + orientation = o; + defaultSectionSize = (o == Qt::Horizontal ? 100 + : qMax(q_func()->minimumSectionSize(), 30)); + defaultAlignment = (o == Qt::Horizontal + ? Qt::Alignment(Qt::AlignCenter) + : Qt::AlignLeft|Qt::AlignVCenter); + } + + inline bool isVisualIndexHidden(int visual) const { + return !sectionHidden.isEmpty() && sectionHidden.at(visual); + } + + inline void setVisualIndexHidden(int visual, bool hidden) { + if (!sectionHidden.isEmpty()) sectionHidden.setBit(visual, hidden); + } + + inline bool hasAutoResizeSections() const { + return stretchSections || stretchLastSection || contentsSections; + } + + QStyleOptionHeader getStyleOption() const; + + inline void invalidateCachedSizeHint() const { + cachedSizeHint = QSize(); + } + + inline void initializeIndexMapping() const { + if (visualIndices.count() != sectionCount + || logicalIndices.count() != sectionCount) { + visualIndices.resize(sectionCount); + logicalIndices.resize(sectionCount); + for (int s = 0; s < sectionCount; ++s) { + visualIndices[s] = s; + logicalIndices[s] = s; + } + } + } + + inline void clearCascadingSections() { + firstCascadingSection = sectionCount; + lastCascadingSection = 0; + cascadingSectionSize.clear(); + } + + inline void saveCascadingSectionSize(int visual, int size) { + if (!cascadingSectionSize.contains(visual)) { + cascadingSectionSize.insert(visual, size); + firstCascadingSection = qMin(firstCascadingSection, visual); + lastCascadingSection = qMax(lastCascadingSection, visual); + } + } + + inline bool sectionIsCascadable(int visual) const { + return headerSectionResizeMode(visual) == QHeaderView::Interactive; + } + + inline int modelSectionCount() const { + return (orientation == Qt::Horizontal + ? model->columnCount(root) + : model->rowCount(root)); + } + + inline bool modelIsEmpty() const { + return (model->rowCount(root) == 0 || model->columnCount(root) == 0); + } + + inline void doDelayedResizeSections() { + if (!delayedResize.isActive()) + delayedResize.start(0, q_func()); + } + + inline void executePostedResize() const { + if (delayedResize.isActive() && state == NoState) { + const_cast(q_func())->resizeSections(); + } + } + + void clear(); + void flipSortIndicator(int section); + void cascadingResize(int visual, int newSize); + + enum State { NoState, ResizeSection, MoveSection, SelectSections, NoClear } state; + + int offset; + Qt::Orientation orientation; + Qt::SortOrder sortIndicatorOrder; + int sortIndicatorSection; + bool sortIndicatorShown; + + mutable QVector visualIndices; // visualIndex = visualIndices.at(logicalIndex) + mutable QVector logicalIndices; // logicalIndex = row or column in the model + mutable QBitArray sectionSelected; // from logical index to bit + mutable QBitArray sectionHidden; // from visual index to bit + mutable QHash hiddenSectionSize; // from logical index to section size + mutable QHash cascadingSectionSize; // from visual index to section size + mutable QSize cachedSizeHint; + mutable QBasicTimer delayedResize; + + int firstCascadingSection; + int lastCascadingSection; + + int lastPos; + int firstPos; + int originalSize; + int section; // used for resizing and moving sections + int target; + int pressed; + int hover; + + int length; + int sectionCount; + bool movableSections; + bool clickableSections; + bool highlightSelected; + bool stretchLastSection; + bool cascadingResizing; + bool resizeRecursionBlock; + int stretchSections; + int contentsSections; + int defaultSectionSize; + int minimumSectionSize; + int lastSectionSize; // $$$ + int sectionIndicatorOffset; + Qt::Alignment defaultAlignment; + QLabel *sectionIndicator; + QHeaderView::ResizeMode globalResizeMode; + QList persistentHiddenSections; + + // header section spans + + struct SectionSpan { + int size; + int count; + QHeaderView::ResizeMode resizeMode; + inline SectionSpan() : size(0), count(0), resizeMode(QHeaderView::Interactive) {} + inline SectionSpan(int length, int sections, QHeaderView::ResizeMode mode) + : size(length), count(sections), resizeMode(mode) {} + inline int sectionSize() const { return (count > 0 ? size / count : 0); } +#ifndef QT_NO_DATASTREAM + inline void write(QDataStream &out) const + { out << size; out << count; out << (int)resizeMode; } + inline void read(QDataStream &in) + { in >> size; in >> count; int m; in >> m; resizeMode = (QHeaderView::ResizeMode)m; } +#endif + }; + + QVector sectionSpans; + + void createSectionSpan(int start, int end, int size, QHeaderView::ResizeMode mode); + void removeSectionsFromSpans(int start, int end); + void resizeSectionSpan(int visualIndex, int oldSize, int newSize); + void setDefaultSectionSize(int size); + + inline int headerSectionCount() const { // for debugging + int count = 0; + for (int i = 0; i < sectionSpans.count(); ++i) + count += sectionSpans.at(i).count; + return count; + } + + inline int headerLength() const { // for debugging + int len = 0; + for (int i = 0; i < sectionSpans.count(); ++i) + len += sectionSpans.at(i).size; + return len; + } + + inline void removeSpans(const QList &spans) { + for (int i = spans.count() - 1; i >= 0; --i) { + length -= sectionSpans.at(spans.at(i)).size; + sectionSpans.remove(spans.at(i)); + } + } + + inline int sectionSpanIndex(int visual) const { + int section_start = 0; + for (int i = 0; i < sectionSpans.count(); ++i) { + int section_end = section_start + sectionSpans.at(i).count - 1; + if (visual >= section_start && visual <= section_end) + return i; + section_start = section_end + 1; + } + return -1; + } + + int headerSectionSize(int visual) const; + int headerSectionPosition(int visual) const; + int headerVisualIndexAt(int position) const; + + // resize mode + void setHeaderSectionResizeMode(int visual, QHeaderView::ResizeMode mode); + QHeaderView::ResizeMode headerSectionResizeMode(int visual) const; + void setGlobalHeaderResizeMode(QHeaderView::ResizeMode mode); + + // other + int viewSectionSizeHint(int logical) const; + int adjustedVisualIndex(int visualIndex) const; + +#ifndef QT_NO_DATASTREAM + void write(QDataStream &out) const; + bool read(QDataStream &in); +#endif + +}; + +QT_END_NAMESPACE + +#endif // QT_NO_ITEMVIEWS + +#endif // QHEADERVIEW_P_H diff --git a/src/gui/itemviews/qitemdelegate.cpp b/src/gui/itemviews/qitemdelegate.cpp new file mode 100644 index 0000000000..f211438dcd --- /dev/null +++ b/src/gui/itemviews/qitemdelegate.cpp @@ -0,0 +1,1341 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qitemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef DBL_DIG +# define DBL_DIG 10 +#endif + +QT_BEGIN_NAMESPACE + +class QItemDelegatePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QItemDelegate) + +public: + QItemDelegatePrivate() : f(0), clipPainting(true) {} + + inline const QItemEditorFactory *editorFactory() const + { return f ? f : QItemEditorFactory::defaultFactory(); } + + inline QIcon::Mode iconMode(QStyle::State state) const + { + if (!(state & QStyle::State_Enabled)) return QIcon::Disabled; + if (state & QStyle::State_Selected) return QIcon::Selected; + return QIcon::Normal; + } + + inline QIcon::State iconState(QStyle::State state) const + { return state & QStyle::State_Open ? QIcon::On : QIcon::Off; } + + inline static QString replaceNewLine(QString text) + { + const QChar nl = QLatin1Char('\n'); + for (int i = 0; i < text.count(); ++i) + if (text.at(i) == nl) + text[i] = QChar::LineSeparator; + return text; + } + + static QString valueToText(const QVariant &value, const QStyleOptionViewItemV4 &option); + + void _q_commitDataAndCloseEditor(QWidget *editor); + + QItemEditorFactory *f; + bool clipPainting; + + QRect textLayoutBounds(const QStyleOptionViewItemV2 &options) const; + QSizeF doTextLayout(int lineWidth) const; + mutable QTextLayout textLayout; + mutable QTextOption textOption; + + const QWidget *widget(const QStyleOptionViewItem &option) const + { + if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast(&option)) + return v3->widget; + + return 0; + } + + // ### temporary hack until we have QStandardItemDelegate + mutable struct Icon { + QIcon icon; + QIcon::Mode mode; + QIcon::State state; + } tmp; +}; + +void QItemDelegatePrivate::_q_commitDataAndCloseEditor(QWidget *editor) +{ + Q_Q(QItemDelegate); + emit q->commitData(editor); + emit q->closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); +} + +QRect QItemDelegatePrivate::textLayoutBounds(const QStyleOptionViewItemV2 &option) const +{ + QRect rect = option.rect; + const bool wrapText = option.features & QStyleOptionViewItemV2::WrapText; + switch (option.decorationPosition) { + case QStyleOptionViewItem::Left: + case QStyleOptionViewItem::Right: + rect.setWidth(wrapText && rect.isValid() ? rect.width() : (QFIXED_MAX)); + break; + case QStyleOptionViewItem::Top: + case QStyleOptionViewItem::Bottom: + rect.setWidth(wrapText ? option.decorationSize.width() : (QFIXED_MAX)); + break; + } + + return rect; +} + +QSizeF QItemDelegatePrivate::doTextLayout(int lineWidth) const +{ + qreal height = 0; + qreal widthUsed = 0; + textLayout.beginLayout(); + while (true) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return QSizeF(widthUsed, height); +} + +/*! + \class QItemDelegate + + \brief The QItemDelegate class provides display and editing facilities for + data items from a model. + + \ingroup model-view + + + QItemDelegate can be used to provide custom display features and editor + widgets for item views based on QAbstractItemView subclasses. Using a + delegate for this purpose allows the display and editing mechanisms to be + customized and developed independently from the model and view. + + The QItemDelegate class is one of the \l{Model/View Classes} and + is part of Qt's \l{Model/View Programming}{model/view framework}. + Note that QStyledItemDelegate has taken over the job of drawing + Qt's item views. We recommend the use of QStyledItemDelegate when + creating new delegates. + + When displaying items from a custom model in a standard view, it is + often sufficient to simply ensure that the model returns appropriate + data for each of the \l{Qt::ItemDataRole}{roles} that determine the + appearance of items in views. The default delegate used by Qt's + standard views uses this role information to display items in most + of the common forms expected by users. However, it is sometimes + necessary to have even more control over the appearance of items than + the default delegate can provide. + + This class provides default implementations of the functions for + painting item data in a view and editing data from item models. + Default implementations of the paint() and sizeHint() virtual + functions, defined in QAbstractItemDelegate, are provided to + ensure that the delegate implements the correct basic behavior + expected by views. You can reimplement these functions in + subclasses to customize the appearance of items. + + When editing data in an item view, QItemDelegate provides an + editor widget, which is a widget that is placed on top of the view + while editing takes place. Editors are created with a + QItemEditorFactory; a default static instance provided by + QItemEditorFactory is installed on all item delegates. You can set + a custom factory using setItemEditorFactory() or set a new default + factory with QItemEditorFactory::setDefaultFactory(). It is the + data stored in the item model with the Qt::EditRole that is edited. + + Only the standard editing functions for widget-based delegates are + reimplemented here: + + \list + \o createEditor() returns the widget used to change data from the model + and can be reimplemented to customize editing behavior. + \o setEditorData() provides the widget with data to manipulate. + \o updateEditorGeometry() ensures that the editor is displayed correctly + with respect to the item view. + \o setModelData() returns updated data to the model. + \endlist + + The closeEditor() signal indicates that the user has completed editing the data, + and that the editor widget can be destroyed. + + \section1 Standard Roles and Data Types + + The default delegate used by the standard views supplied with Qt + associates each standard role (defined by Qt::ItemDataRole) with certain + data types. Models that return data in these types can influence the + appearance of the delegate as described in the following table. + + \table + \header \o Role \o Accepted Types + \omit + \row \o \l Qt::AccessibleDescriptionRole \o QString + \row \o \l Qt::AccessibleTextRole \o QString + \endomit + \row \o \l Qt::BackgroundRole \o QBrush + \row \o \l Qt::BackgroundColorRole \o QColor (obsolete; use Qt::BackgroundRole instead) + \row \o \l Qt::CheckStateRole \o Qt::CheckState + \row \o \l Qt::DecorationRole \o QIcon, QPixmap and QColor + \row \o \l Qt::DisplayRole \o QString and types with a string representation + \row \o \l Qt::EditRole \o See QItemEditorFactory for details + \row \o \l Qt::FontRole \o QFont + \row \o \l Qt::SizeHintRole \o QSize + \omit + \row \o \l Qt::StatusTipRole \o + \endomit + \row \o \l Qt::TextAlignmentRole \o Qt::Alignment + \row \o \l Qt::ForegroundRole \o QBrush + \row \o \l Qt::TextColorRole \o QColor (obsolete; use Qt::ForegroundRole instead) + \omit + \row \o \l Qt::ToolTipRole + \row \o \l Qt::WhatsThisRole + \endomit + \endtable + + If the default delegate does not allow the level of customization that + you need, either for display purposes or for editing data, it is possible to + subclass QItemDelegate to implement the desired behavior. + + \section1 Subclassing + + When subclassing QItemDelegate to create a delegate that displays items + using a custom renderer, it is important to ensure that the delegate can + render items suitably for all the required states; e.g. selected, + disabled, checked. The documentation for the paint() function contains + some hints to show how this can be achieved. + + You can provide custom editors by using a QItemEditorFactory. The + \l{Color Editor Factory Example} shows how a custom editor can be + made available to delegates with the default item editor + factory. This way, there is no need to subclass QItemDelegate. An + alternative is to reimplement createEditor(), setEditorData(), + setModelData(), and updateEditorGeometry(). This process is + described in the \l{Spin Box Delegate Example}. + + \section1 QStyledItemDelegate vs. QItemDelegate + + Since Qt 4.4, there are two delegate classes: QItemDelegate and + QStyledItemDelegate. However, the default delegate is QStyledItemDelegate. + These two classes are independent alternatives to painting and providing + editors for items in views. The difference between them is that + QStyledItemDelegate uses the current style to paint its items. We therefore + recommend using QStyledItemDelegate as the base class when implementing + custom delegates or when working with Qt style sheets. The code required + for either class should be equal unless the custom delegate needs to use + the style for drawing. + + \sa {Delegate Classes}, QStyledItemDelegate, QAbstractItemDelegate, + {Spin Box Delegate Example}, {Settings Editor Example}, + {Icons Example} +*/ + +/*! + Constructs an item delegate with the given \a parent. +*/ + +QItemDelegate::QItemDelegate(QObject *parent) + : QAbstractItemDelegate(*new QItemDelegatePrivate(), parent) +{ + +} + +/*! + Destroys the item delegate. +*/ + +QItemDelegate::~QItemDelegate() +{ +} + +/*! + \property QItemDelegate::clipping + \brief if the delegate should clip the paint events + \since 4.2 + + This property will set the paint clip to the size of the item. + The default value is on. It is useful for cases such + as when images are larger than the size of the item. +*/ + +bool QItemDelegate::hasClipping() const +{ + Q_D(const QItemDelegate); + return d->clipPainting; +} + +void QItemDelegate::setClipping(bool clip) +{ + Q_D(QItemDelegate); + d->clipPainting = clip; +} + +QString QItemDelegatePrivate::valueToText(const QVariant &value, const QStyleOptionViewItemV4 &option) +{ + QString text; + switch (value.userType()) { + case QMetaType::Float: + text = option.locale.toString(value.toFloat(), 'g'); + break; + case QVariant::Double: + text = option.locale.toString(value.toDouble(), 'g', DBL_DIG); + break; + case QVariant::Int: + case QVariant::LongLong: + text = option.locale.toString(value.toLongLong()); + break; + case QVariant::UInt: + case QVariant::ULongLong: + text = option.locale.toString(value.toULongLong()); + break; + case QVariant::Date: + text = option.locale.toString(value.toDate(), QLocale::ShortFormat); + break; + case QVariant::Time: + text = option.locale.toString(value.toTime(), QLocale::ShortFormat); + break; + case QVariant::DateTime: + text = option.locale.toString(value.toDateTime().date(), QLocale::ShortFormat); + text += QLatin1Char(' '); + text += option.locale.toString(value.toDateTime().time(), QLocale::ShortFormat); + break; + default: + text = replaceNewLine(value.toString()); + break; + } + return text; +} + +/*! + Renders the delegate using the given \a painter and style \a option for + the item specified by \a index. + + When reimplementing this function in a subclass, you should update the area + held by the option's \l{QStyleOption::rect}{rect} variable, using the + option's \l{QStyleOption::state}{state} variable to determine the state of + the item to be displayed, and adjust the way it is painted accordingly. + + For example, a selected item may need to be displayed differently to + unselected items, as shown in the following code: + + \snippet examples/itemviews/pixelator/pixeldelegate.cpp 2 + \dots + + After painting, you should ensure that the painter is returned to its + the state it was supplied in when this function was called. For example, + it may be useful to call QPainter::save() before painting and + QPainter::restore() afterwards. + + \sa QStyle::State +*/ +void QItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_D(const QItemDelegate); + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = setOptions(index, option); + + const QStyleOptionViewItemV2 *v2 = qstyleoption_cast(&option); + opt.features = v2 ? v2->features + : QStyleOptionViewItemV2::ViewItemFeatures(QStyleOptionViewItemV2::None); + const QStyleOptionViewItemV3 *v3 = qstyleoption_cast(&option); + opt.locale = v3 ? v3->locale : QLocale(); + opt.widget = v3 ? v3->widget : 0; + + // prepare + painter->save(); + if (d->clipPainting) + painter->setClipRect(opt.rect); + + // get the data and the rectangles + + QVariant value; + + QPixmap pixmap; + QRect decorationRect; + value = index.data(Qt::DecorationRole); + if (value.isValid()) { + // ### we need the pixmap to call the virtual function + pixmap = decoration(opt, value); + if (value.type() == QVariant::Icon) { + d->tmp.icon = qvariant_cast(value); + d->tmp.mode = d->iconMode(option.state); + d->tmp.state = d->iconState(option.state); + const QSize size = d->tmp.icon.actualSize(option.decorationSize, + d->tmp.mode, d->tmp.state); + decorationRect = QRect(QPoint(0, 0), size); + } else { + d->tmp.icon = QIcon(); + decorationRect = QRect(QPoint(0, 0), pixmap.size()); + } + } else { + d->tmp.icon = QIcon(); + decorationRect = QRect(); + } + + QString text; + QRect displayRect; + value = index.data(Qt::DisplayRole); + if (value.isValid() && !value.isNull()) { + text = QItemDelegatePrivate::valueToText(value, opt); + displayRect = textRectangle(painter, d->textLayoutBounds(opt), opt.font, text); + } + + QRect checkRect; + Qt::CheckState checkState = Qt::Unchecked; + value = index.data(Qt::CheckStateRole); + if (value.isValid()) { + checkState = static_cast(value.toInt()); + checkRect = check(opt, opt.rect, value); + } + + // do the layout + + doLayout(opt, &checkRect, &decorationRect, &displayRect, false); + + // draw the item + + drawBackground(painter, opt, index); + drawCheck(painter, opt, checkRect, checkState); + drawDecoration(painter, opt, decorationRect, pixmap); + drawDisplay(painter, opt, displayRect, text); + drawFocus(painter, opt, displayRect); + + // done + painter->restore(); +} + +/*! + Returns the size needed by the delegate to display the item + specified by \a index, taking into account the style information + provided by \a option. + + When reimplementing this function, note that in case of text + items, QItemDelegate adds a margin (i.e. 2 * + QStyle::PM_FocusFrameHMargin) to the length of the text. +*/ + +QSize QItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::SizeHintRole); + if (value.isValid()) + return qvariant_cast(value); + QRect decorationRect = rect(option, index, Qt::DecorationRole); + QRect displayRect = rect(option, index, Qt::DisplayRole); + QRect checkRect = rect(option, index, Qt::CheckStateRole); + + doLayout(option, &checkRect, &decorationRect, &displayRect, true); + + return (decorationRect|displayRect|checkRect).size(); +} + +/*! + Returns the widget used to edit the item specified by \a index + for editing. The \a parent widget and style \a option are used to + control how the editor widget appears. + + \sa QAbstractItemDelegate::createEditor() +*/ + +QWidget *QItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + Q_D(const QItemDelegate); + if (!index.isValid()) + return 0; + QVariant::Type t = static_cast(index.data(Qt::EditRole).userType()); + const QItemEditorFactory *factory = d->f; + if (factory == 0) + factory = QItemEditorFactory::defaultFactory(); + return factory->createEditor(t, parent); +} + +/*! + Sets the data to be displayed and edited by the \a editor from the + data model item specified by the model \a index. + + The default implementation stores the data in the \a editor + widget's \l {Qt's Property System} {user property}. + + \sa QMetaProperty::isUser() +*/ + +void QItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QItemDelegate); + QVariant v = index.data(Qt::EditRole); + QByteArray n = editor->metaObject()->userProperty().name(); + + // ### Qt 5: remove + // A work-around for missing "USER true" in qdatetimeedit.h for + // QTimeEdit's time property and QDateEdit's date property. + // It only triggers if the default user property "dateTime" is + // reported for QTimeEdit and QDateEdit. + if (n == "dateTime") { + if (editor->inherits("QTimeEdit")) + n = "time"; + else if (editor->inherits("QDateEdit")) + n = "date"; + } + + // ### Qt 5: give QComboBox a USER property + if (n.isEmpty() && editor->inherits("QComboBox")) + n = d->editorFactory()->valuePropertyName(static_cast(v.userType())); + if (!n.isEmpty()) { + if (!v.isValid()) + v = QVariant(editor->property(n).userType(), (const void *)0); + editor->setProperty(n, v); + } +#endif +} + +/*! + Gets data from the \a editor widget and stores it in the specified + \a model at the item \a index. + + The default implementation gets the value to be stored in the data + model from the \a editor widget's \l {Qt's Property System} {user + property}. + + \sa QMetaProperty::isUser() +*/ + +void QItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(model); + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QItemDelegate); + Q_ASSERT(model); + Q_ASSERT(editor); + QByteArray n = editor->metaObject()->userProperty().name(); + if (n.isEmpty()) + n = d->editorFactory()->valuePropertyName( + static_cast(model->data(index, Qt::EditRole).userType())); + if (!n.isEmpty()) + model->setData(index, editor->property(n), Qt::EditRole); +#endif +} + +/*! + Updates the \a editor for the item specified by \a index + according to the style \a option given. +*/ + +void QItemDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!editor) + return; + Q_ASSERT(index.isValid()); + QPixmap pixmap = decoration(option, index.data(Qt::DecorationRole)); + QString text = QItemDelegatePrivate::replaceNewLine(index.data(Qt::DisplayRole).toString()); + QRect pixmapRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); + QRect textRect = textRectangle(0, option.rect, option.font, text); + QRect checkRect = check(option, textRect, index.data(Qt::CheckStateRole)); + QStyleOptionViewItem opt = option; + opt.showDecorationSelected = true; // let the editor take up all available space + doLayout(opt, &checkRect, &pixmapRect, &textRect, false); + editor->setGeometry(textRect); +} + +/*! + Returns the editor factory used by the item delegate. + If no editor factory is set, the function will return null. + + \sa setItemEditorFactory() +*/ +QItemEditorFactory *QItemDelegate::itemEditorFactory() const +{ + Q_D(const QItemDelegate); + return d->f; +} + +/*! + Sets the editor factory to be used by the item delegate to be the \a factory + specified. If no editor factory is set, the item delegate will use the + default editor factory. + + \sa itemEditorFactory() +*/ +void QItemDelegate::setItemEditorFactory(QItemEditorFactory *factory) +{ + Q_D(QItemDelegate); + d->f = factory; +} + +/*! + Renders the item view \a text within the rectangle specified by \a rect + using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QString &text) const +{ + Q_D(const QItemDelegate); + + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option.state & QStyle::State_Selected) { + painter->fillRect(rect, option.palette.brush(cg, QPalette::Highlight)); + painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(cg, QPalette::Text)); + } + + if (text.isEmpty()) + return; + + if (option.state & QStyle::State_Editing) { + painter->save(); + painter->setPen(option.palette.color(cg, QPalette::Text)); + painter->drawRect(rect.adjusted(0, 0, -1, -1)); + painter->restore(); + } + + const QStyleOptionViewItemV4 opt = option; + + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; + QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding + const bool wrapText = opt.features & QStyleOptionViewItemV2::WrapText; + d->textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap); + d->textOption.setTextDirection(option.direction); + d->textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment)); + d->textLayout.setTextOption(d->textOption); + d->textLayout.setFont(option.font); + d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text)); + + QSizeF textLayoutSize = d->doTextLayout(textRect.width()); + + if (textRect.width() < textLayoutSize.width() + || textRect.height() < textLayoutSize.height()) { + QString elided; + int start = 0; + int end = text.indexOf(QChar::LineSeparator, start); + if (end == -1) { + elided += option.fontMetrics.elidedText(text, option.textElideMode, textRect.width()); + } else { + while (end != -1) { + elided += option.fontMetrics.elidedText(text.mid(start, end - start), + option.textElideMode, textRect.width()); + elided += QChar::LineSeparator; + start = end + 1; + end = text.indexOf(QChar::LineSeparator, start); + } + //let's add the last line (after the last QChar::LineSeparator) + elided += option.fontMetrics.elidedText(text.mid(start), + option.textElideMode, textRect.width()); + } + d->textLayout.setText(elided); + textLayoutSize = d->doTextLayout(textRect.width()); + } + + const QSize layoutSize(textRect.width(), int(textLayoutSize.height())); + const QRect layoutRect = QStyle::alignedRect(option.direction, option.displayAlignment, + layoutSize, textRect); + // if we still overflow even after eliding the text, enable clipping + if (!hasClipping() && (textRect.width() < textLayoutSize.width() + || textRect.height() < textLayoutSize.height())) { + painter->save(); + painter->setClipRect(layoutRect); + d->textLayout.draw(painter, layoutRect.topLeft(), QVector(), layoutRect); + painter->restore(); + } else { + d->textLayout.draw(painter, layoutRect.topLeft(), QVector(), layoutRect); + } +} + +/*! + Renders the decoration \a pixmap within the rectangle specified by + \a rect using the given \a painter and style \a option. +*/ +void QItemDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QPixmap &pixmap) const +{ + Q_D(const QItemDelegate); + // if we have an icon, we ignore the pixmap + if (!d->tmp.icon.isNull()) { + d->tmp.icon.paint(painter, rect, option.decorationAlignment, + d->tmp.mode, d->tmp.state); + return; + } + + if (pixmap.isNull() || !rect.isValid()) + return; + QPoint p = QStyle::alignedRect(option.direction, option.decorationAlignment, + pixmap.size(), rect).topLeft(); + if (option.state & QStyle::State_Selected) { + QPixmap *pm = selected(pixmap, option.palette, option.state & QStyle::State_Enabled); + painter->drawPixmap(p, *pm); + } else { + painter->drawPixmap(p, pixmap); + } +} + +/*! + Renders the region within the rectangle specified by \a rect, indicating + that it has the focus, using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawFocus(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect) const +{ + Q_D(const QItemDelegate); + if ((option.state & QStyle::State_HasFocus) == 0 || !rect.isValid()) + return; + QStyleOptionFocusRect o; + o.QStyleOption::operator=(option); + o.rect = rect; + o.state |= QStyle::State_KeyboardFocusChange; + o.state |= QStyle::State_Item; + QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) + ? QPalette::Highlight : QPalette::Window); + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget); +} + +/*! + Renders a check indicator within the rectangle specified by \a + rect, using the given \a painter and style \a option, using the + given \a state. +*/ + +void QItemDelegate::drawCheck(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect, Qt::CheckState state) const +{ + Q_D(const QItemDelegate); + if (!rect.isValid()) + return; + + QStyleOptionViewItem opt(option); + opt.rect = rect; + opt.state = opt.state & ~QStyle::State_HasFocus; + + switch (state) { + case Qt::Unchecked: + opt.state |= QStyle::State_Off; + break; + case Qt::PartiallyChecked: + opt.state |= QStyle::State_NoChange; + break; + case Qt::Checked: + opt.state |= QStyle::State_On; + break; + } + + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &opt, painter, widget); +} + +/*! + \since 4.2 + + Renders the item background for the given \a index, + using the given \a painter and style \a option. +*/ + +void QItemDelegate::drawBackground(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + + painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight)); + } else { + QVariant value = index.data(Qt::BackgroundRole); + if (value.canConvert()) { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(option.rect.topLeft()); + painter->fillRect(option.rect, qvariant_cast(value)); + painter->setBrushOrigin(oldBO); + } + } +} + + +/*! + \internal + + Code duplicated in QCommonStylePrivate::viewItemLayout +*/ + +void QItemDelegate::doLayout(const QStyleOptionViewItem &option, + QRect *checkRect, QRect *pixmapRect, QRect *textRect, + bool hint) const +{ + Q_ASSERT(checkRect && pixmapRect && textRect); + Q_D(const QItemDelegate); + const QWidget *widget = d->widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + const bool hasCheck = checkRect->isValid(); + const bool hasPixmap = pixmapRect->isValid(); + const bool hasText = textRect->isValid(); + const int textMargin = hasText ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + const int pixmapMargin = hasPixmap ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + const int checkMargin = hasCheck ? style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1 : 0; + int x = option.rect.left(); + int y = option.rect.top(); + int w, h; + + textRect->adjust(-textMargin, 0, textMargin, 0); // add width padding + if (textRect->height() == 0 && (!hasPixmap || !hint)) { + //if there is no text, we still want to have a decent height for the item sizeHint and the editor size + textRect->setHeight(option.fontMetrics.height()); + } + + QSize pm(0, 0); + if (hasPixmap) { + pm = pixmapRect->size(); + pm.rwidth() += 2 * pixmapMargin; + } + if (hint) { + h = qMax(checkRect->height(), qMax(textRect->height(), pm.height())); + if (option.decorationPosition == QStyleOptionViewItem::Left + || option.decorationPosition == QStyleOptionViewItem::Right) { + w = textRect->width() + pm.width(); + } else { + w = qMax(textRect->width(), pm.width()); + } + } else { + w = option.rect.width(); + h = option.rect.height(); + } + + int cw = 0; + QRect check; + if (hasCheck) { + cw = checkRect->width() + 2 * checkMargin; + if (hint) w += cw; + if (option.direction == Qt::RightToLeft) { + check.setRect(x + w - cw, y, cw, h); + } else { + check.setRect(x + checkMargin, y, cw, h); + } + } + + // at this point w should be the *total* width + + QRect display; + QRect decoration; + switch (option.decorationPosition) { + case QStyleOptionViewItem::Top: { + if (hasPixmap) + pm.setHeight(pm.height() + pixmapMargin); // add space + h = hint ? textRect->height() : h - pm.height(); + + if (option.direction == Qt::RightToLeft) { + decoration.setRect(x, y, w - cw, pm.height()); + display.setRect(x, y + pm.height(), w - cw, h); + } else { + decoration.setRect(x + cw, y, w - cw, pm.height()); + display.setRect(x + cw, y + pm.height(), w - cw, h); + } + break; } + case QStyleOptionViewItem::Bottom: { + if (hasText) + textRect->setHeight(textRect->height() + textMargin); // add space + h = hint ? textRect->height() + pm.height() : h; + + if (option.direction == Qt::RightToLeft) { + display.setRect(x, y, w - cw, textRect->height()); + decoration.setRect(x, y + textRect->height(), w - cw, h - textRect->height()); + } else { + display.setRect(x + cw, y, w - cw, textRect->height()); + decoration.setRect(x + cw, y + textRect->height(), w - cw, h - textRect->height()); + } + break; } + case QStyleOptionViewItem::Left: { + if (option.direction == Qt::LeftToRight) { + decoration.setRect(x + cw, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } else { + display.setRect(x, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } + break; } + case QStyleOptionViewItem::Right: { + if (option.direction == Qt::LeftToRight) { + display.setRect(x + cw, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } else { + decoration.setRect(x, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } + break; } + default: + qWarning("doLayout: decoration position is invalid"); + decoration = *pixmapRect; + break; + } + + if (!hint) { // we only need to do the internal layout if we are going to paint + *checkRect = QStyle::alignedRect(option.direction, Qt::AlignCenter, + checkRect->size(), check); + *pixmapRect = QStyle::alignedRect(option.direction, option.decorationAlignment, + pixmapRect->size(), decoration); + // the text takes up all available space, unless the decoration is not shown as selected + if (option.showDecorationSelected) + *textRect = display; + else + *textRect = QStyle::alignedRect(option.direction, option.displayAlignment, + textRect->size().boundedTo(display.size()), display); + } else { + *checkRect = check; + *pixmapRect = decoration; + *textRect = display; + } +} + +/*! + \internal + + Returns the pixmap used to decorate the root of the item view. + The style \a option controls the appearance of the root; the \a variant + refers to the data associated with an item. +*/ + +QPixmap QItemDelegate::decoration(const QStyleOptionViewItem &option, const QVariant &variant) const +{ + Q_D(const QItemDelegate); + switch (variant.type()) { + case QVariant::Icon: { + QIcon::Mode mode = d->iconMode(option.state); + QIcon::State state = d->iconState(option.state); + return qvariant_cast(variant).pixmap(option.decorationSize, mode, state); } + case QVariant::Color: { + static QPixmap pixmap(option.decorationSize); + pixmap.fill(qvariant_cast(variant)); + return pixmap; } + default: + break; + } + + return qvariant_cast(variant); +} + +// hacky but faster version of "QString::sprintf("%d-%d", i, enabled)" +static QString qPixmapSerial(quint64 i, bool enabled) +{ + ushort arr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', ushort('0' + enabled) }; + ushort *ptr = &arr[16]; + + while (i > 0) { + // hey - it's our internal representation, so use the ascii character after '9' + // instead of 'a' for hex + *(--ptr) = '0' + i % 16; + i >>= 4; + } + + return QString((const QChar *)ptr, int(&arr[sizeof(arr) / sizeof(ushort)] - ptr)); +} + +/*! + \internal + Returns the selected version of the given \a pixmap using the given \a palette. + The \a enabled argument decides whether the normal or disabled highlight color of + the palette is used. +*/ +QPixmap *QItemDelegate::selected(const QPixmap &pixmap, const QPalette &palette, bool enabled) const +{ + QString key = qPixmapSerial(qt_pixmap_id(pixmap), enabled); + QPixmap *pm = QPixmapCache::find(key); + if (!pm) { + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + + QColor color = palette.color(enabled ? QPalette::Normal : QPalette::Disabled, + QPalette::Highlight); + color.setAlphaF((qreal)0.3); + + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter.fillRect(0, 0, img.width(), img.height(), color); + painter.end(); + + QPixmap selected = QPixmap(QPixmap::fromImage(img)); + int n = (img.byteCount() >> 10) + 1; + if (QPixmapCache::cacheLimit() < n) + QPixmapCache::setCacheLimit(n); + + QPixmapCache::insert(key, selected); + pm = QPixmapCache::find(key); + } + return pm; +} + +/*! + \internal +*/ + +QRect QItemDelegate::rect(const QStyleOptionViewItem &option, + const QModelIndex &index, int role) const +{ + Q_D(const QItemDelegate); + QVariant value = index.data(role); + if (role == Qt::CheckStateRole) + return check(option, option.rect, value); + if (value.isValid() && !value.isNull()) { + switch (value.type()) { + case QVariant::Invalid: + break; + case QVariant::Pixmap: + return QRect(QPoint(0, 0), qvariant_cast(value).size()); + case QVariant::Image: + return QRect(QPoint(0, 0), qvariant_cast(value).size()); + case QVariant::Icon: { + QIcon::Mode mode = d->iconMode(option.state); + QIcon::State state = d->iconState(option.state); + QIcon icon = qvariant_cast(value); + QSize size = icon.actualSize(option.decorationSize, mode, state); + return QRect(QPoint(0, 0), size); } + case QVariant::Color: + return QRect(QPoint(0, 0), option.decorationSize); + case QVariant::String: + default: { + QString text = QItemDelegatePrivate::valueToText(value, option); + value = index.data(Qt::FontRole); + QFont fnt = qvariant_cast(value).resolve(option.font); + return textRectangle(0, d->textLayoutBounds(option), fnt, text); } + } + } + return QRect(); +} + +/*! + \internal + + Note that on Mac, if /usr/include/AssertMacros.h is included prior + to QItemDelegate, and the application is building in debug mode, the + check(assertion) will conflict with QItemDelegate::check. + + To avoid this problem, add + + #ifdef check + #undef check + #endif + + after including AssertMacros.h +*/ +QRect QItemDelegate::check(const QStyleOptionViewItem &option, + const QRect &bounding, const QVariant &value) const +{ + if (value.isValid()) { + Q_D(const QItemDelegate); + QStyleOptionButton opt; + opt.QStyleOption::operator=(option); + opt.rect = bounding; + const QWidget *widget = d->widget(option); // cast + QStyle *style = widget ? widget->style() : QApplication::style(); + return style->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, widget); + } + return QRect(); +} + +/*! + \internal +*/ +QRect QItemDelegate::textRectangle(QPainter * /*painter*/, const QRect &rect, + const QFont &font, const QString &text) const +{ + Q_D(const QItemDelegate); + d->textOption.setWrapMode(QTextOption::WordWrap); + d->textLayout.setTextOption(d->textOption); + d->textLayout.setFont(font); + d->textLayout.setText(QItemDelegatePrivate::replaceNewLine(text)); + QSizeF fpSize = d->doTextLayout(rect.width()); + const QSize size = QSize(qCeil(fpSize.width()), qCeil(fpSize.height())); + // ###: textRectangle should take style option as argument + const int textMargin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + return QRect(0, 0, size.width() + 2 * textMargin, size.height()); +} + +/*! + \fn bool QItemDelegate::eventFilter(QObject *editor, QEvent *event) + + Returns true if the given \a editor is a valid QWidget and the + given \a event is handled; otherwise returns false. The following + key press events are handled by default: + + \list + \o \gui Tab + \o \gui Backtab + \o \gui Enter + \o \gui Return + \o \gui Esc + \endlist + + In the case of \gui Tab, \gui Backtab, \gui Enter and \gui Return + key press events, the \a editor's data is comitted to the model + and the editor is closed. If the \a event is a \gui Tab key press + the view will open an editor on the next item in the + view. Likewise, if the \a event is a \gui Backtab key press the + view will open an editor on the \e previous item in the view. + + If the event is a \gui Esc key press event, the \a editor is + closed \e without committing its data. + + \sa commitData(), closeEditor() +*/ + +bool QItemDelegate::eventFilter(QObject *object, QEvent *event) +{ + QWidget *editor = qobject_cast(object); + if (!editor) + return false; + if (event->type() == QEvent::KeyPress) { + switch (static_cast(event)->key()) { + case Qt::Key_Tab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); + return true; + case Qt::Key_Backtab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: +#ifndef QT_NO_TEXTEDIT + if (qobject_cast(editor) || qobject_cast(editor)) + return false; // don't filter enter key events for QTextEdit + // We want the editor to be able to process the key press + // before committing the data (e.g. so it can do + // validation/fixup of the input). +#endif // QT_NO_TEXTEDIT +#ifndef QT_NO_LINEEDIT + if (QLineEdit *e = qobject_cast(editor)) + if (!e->hasAcceptableInput()) + return false; +#endif // QT_NO_LINEEDIT + QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor", + Qt::QueuedConnection, Q_ARG(QWidget*, editor)); + return false; + case Qt::Key_Escape: + // don't commit data + emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + break; + default: + return false; + } + if (editor->parentWidget()) + editor->parentWidget()->setFocus(); + return true; + } else if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) { + //the Hide event will take care of he editors that are in fact complete dialogs + if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) { + QWidget *w = QApplication::focusWidget(); + while (w) { // don't worry about focus changes internally in the editor + if (w == editor) + return false; + w = w->parentWidget(); + } +#ifndef QT_NO_DRAGANDDROP + // The window may lose focus during an drag operation. + // i.e when dragging involves the taskbar on Windows. + if (QDragManager::self() && QDragManager::self()->object != 0) + return false; +#endif + + emit commitData(editor); + emit closeEditor(editor, NoHint); + } + } else if (event->type() == QEvent::ShortcutOverride) { + if (static_cast(event)->key() == Qt::Key_Escape) { + event->accept(); + return true; + } + } + return false; +} + +/*! + \reimp +*/ + +bool QItemDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_ASSERT(event); + Q_ASSERT(model); + + // make sure that the item is checkable + Qt::ItemFlags flags = model->flags(index); + if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled) + || !(flags & Qt::ItemIsEnabled)) + return false; + + // make sure that we have a check state + QVariant value = index.data(Qt::CheckStateRole); + if (!value.isValid()) + return false; + + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick) + || (event->type() == QEvent::MouseButtonPress)) { + QRect checkRect = check(option, option.rect, Qt::Checked); + QRect emptyRect; + doLayout(option, &checkRect, &emptyRect, &emptyRect, false); + QMouseEvent *me = static_cast(event); + if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos())) + return false; + + // eat the double click events inside the check rect + if ((event->type() == QEvent::MouseButtonPress) + || (event->type() == QEvent::MouseButtonDblClick)) + return true; + + } else if (event->type() == QEvent::KeyPress) { + if (static_cast(event)->key() != Qt::Key_Space + && static_cast(event)->key() != Qt::Key_Select) + return false; + } else { + return false; + } + + Qt::CheckState state = (static_cast(value.toInt()) == Qt::Checked + ? Qt::Unchecked : Qt::Checked); + return model->setData(index, state, Qt::CheckStateRole); +} + +/*! + \internal +*/ + +QStyleOptionViewItem QItemDelegate::setOptions(const QModelIndex &index, + const QStyleOptionViewItem &option) const +{ + QStyleOptionViewItem opt = option; + + // set font + QVariant value = index.data(Qt::FontRole); + if (value.isValid()){ + opt.font = qvariant_cast(value).resolve(opt.font); + opt.fontMetrics = QFontMetrics(opt.font); + } + + // set text alignment + value = index.data(Qt::TextAlignmentRole); + if (value.isValid()) + opt.displayAlignment = Qt::Alignment(value.toInt()); + + // set foreground brush + value = index.data(Qt::ForegroundRole); + if (value.canConvert()) + opt.palette.setBrush(QPalette::Text, qvariant_cast(value)); + + return opt; +} + +QT_END_NAMESPACE + +#include "moc_qitemdelegate.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemdelegate.h b/src/gui/itemviews/qitemdelegate.h new file mode 100644 index 0000000000..2df3d1c1c1 --- /dev/null +++ b/src/gui/itemviews/qitemdelegate.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMDELEGATE_H +#define QITEMDELEGATE_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QItemDelegatePrivate; +class QItemEditorFactory; + +class Q_GUI_EXPORT QItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT + Q_PROPERTY(bool clipping READ hasClipping WRITE setClipping) + +public: + explicit QItemDelegate(QObject *parent = 0); + ~QItemDelegate(); + + bool hasClipping() const; + void setClipping(bool clip); + + // painting + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editing + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editor factory + QItemEditorFactory *itemEditorFactory() const; + void setItemEditorFactory(QItemEditorFactory *factory); + +protected: + virtual void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QString &text) const; + virtual void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QPixmap &pixmap) const; + virtual void drawFocus(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect) const; + virtual void drawCheck(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, Qt::CheckState state) const; + void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void doLayout(const QStyleOptionViewItem &option, + QRect *checkRect, QRect *iconRect, QRect *textRect, bool hint) const; + + QRect rect(const QStyleOptionViewItem &option, const QModelIndex &index, int role) const; + + bool eventFilter(QObject *object, QEvent *event); + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + + QStyleOptionViewItem setOptions(const QModelIndex &index, + const QStyleOptionViewItem &option) const; + + QPixmap decoration(const QStyleOptionViewItem &option, const QVariant &variant) const; + QPixmap *selected(const QPixmap &pixmap, const QPalette &palette, bool enabled) const; + + QRect check(const QStyleOptionViewItem &option, const QRect &bounding, + const QVariant &variant) const; + QRect textRectangle(QPainter *painter, const QRect &rect, + const QFont &font, const QString &text) const; + +private: + Q_DECLARE_PRIVATE(QItemDelegate) + Q_DISABLE_COPY(QItemDelegate) + + Q_PRIVATE_SLOT(d_func(), void _q_commitDataAndCloseEditor(QWidget*)) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMDELEGATE_H diff --git a/src/gui/itemviews/qitemeditorfactory.cpp b/src/gui/itemviews/qitemeditorfactory.cpp new file mode 100644 index 0000000000..92c78cb6f1 --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory.cpp @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qitemeditorfactory.h" +#include "qitemeditorfactory_p.h" + +#ifndef QT_NO_ITEMVIEWS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + + +#ifndef QT_NO_COMBOBOX + +class QBooleanComboBox : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(bool value READ value WRITE setValue USER true) + +public: + QBooleanComboBox(QWidget *parent); + void setValue(bool); + bool value() const; +}; + +#endif // QT_NO_COMBOBOX + +/*! + \class QItemEditorFactory + \brief The QItemEditorFactory class provides widgets for editing item data + in views and delegates. + \since 4.2 + \ingroup model-view + + When editing data in an item view, editors are created and + displayed by a delegate. QItemDelegate, which is the delegate by + default installed on Qt's item views, uses a QItemEditorFactory to + create editors for it. A default unique instance provided by + QItemEditorFactory is used by all item delegates. If you set a + new default factory with setDefaultFactory(), the new factory will + be used by existing and new delegates. + + A factory keeps a collection of QItemEditorCreatorBase + instances, which are specialized editors that produce editors + for one particular QVariant data type (All Qt models store + their data in \l{QVariant}s). + + \section1 Standard Editing Widgets + + The standard factory implementation provides editors for a variety of data + types. These are created whenever a delegate needs to provide an editor for + data supplied by a model. The following table shows the relationship between + types and the standard editors provided. + + \table + \header \o Type \o Editor Widget + \row \o bool \o QComboBox + \row \o double \o QDoubleSpinBox + \row \o int \o{1,2} QSpinBox + \row \o unsigned int + \row \o QDate \o QDateEdit + \row \o QDateTime \o QDateTimeEdit + \row \o QPixmap \o QLabel + \row \o QString \o QLineEdit + \row \o QTime \o QTimeEdit + \endtable + + Additional editors can be registered with the registerEditor() function. + + \sa QItemDelegate, {Model/View Programming}, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorFactory::QItemEditorFactory() + + Constructs a new item editor factory. +*/ + +/*! + Creates an editor widget with the given \a parent for the specified \a type of data, + and returns it as a QWidget. + + \sa registerEditor() +*/ +QWidget *QItemEditorFactory::createEditor(QVariant::Type type, QWidget *parent) const +{ + QItemEditorCreatorBase *creator = creatorMap.value(type, 0); + if (!creator) { + const QItemEditorFactory *dfactory = defaultFactory(); + return dfactory == this ? 0 : dfactory->createEditor(type, parent); + } + return creator->createWidget(parent); +} + +/*! + Returns the property name used to access data for the given \a type of data. +*/ +QByteArray QItemEditorFactory::valuePropertyName(QVariant::Type type) const +{ + QItemEditorCreatorBase *creator = creatorMap.value(type, 0); + if (!creator) { + const QItemEditorFactory *dfactory = defaultFactory(); + return dfactory == this ? QByteArray() : dfactory->valuePropertyName(type); + } + return creator->valuePropertyName(); +} + +/*! + Destroys the item editor factory. +*/ +QItemEditorFactory::~QItemEditorFactory() +{ + //we make sure we delete all the QItemEditorCreatorBase + //this has to be done only once, hence the QSet + QSet set = creatorMap.values().toSet(); + qDeleteAll(set); +} + +/*! + Registers an item editor creator specified by \a creator for the given \a type of data. + + \bold{Note:} The factory takes ownership of the item editor creator and will destroy + it if a new creator for the same type is registered later. + + \sa createEditor() +*/ +void QItemEditorFactory::registerEditor(QVariant::Type type, QItemEditorCreatorBase *creator) +{ + QHash::iterator it = creatorMap.find(type); + if (it != creatorMap.end()) { + QItemEditorCreatorBase *oldCreator = it.value(); + Q_ASSERT(oldCreator); + creatorMap.erase(it); + if (!creatorMap.values().contains(oldCreator)) + delete oldCreator; // if it is no more in use we can delete it + } + + creatorMap[type] = creator; +} + +class QDefaultItemEditorFactory : public QItemEditorFactory +{ +public: + inline QDefaultItemEditorFactory() {} + QWidget *createEditor(QVariant::Type type, QWidget *parent) const; + QByteArray valuePropertyName(QVariant::Type) const; +}; + +QWidget *QDefaultItemEditorFactory::createEditor(QVariant::Type type, QWidget *parent) const +{ + switch (type) { +#ifndef QT_NO_COMBOBOX + case QVariant::Bool: { + QBooleanComboBox *cb = new QBooleanComboBox(parent); + cb->setFrame(false); + return cb; } +#endif +#ifndef QT_NO_SPINBOX + case QVariant::UInt: { + QSpinBox *sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMaximum(INT_MAX); + return sb; } + case QVariant::Int: { + QSpinBox *sb = new QSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(INT_MIN); + sb->setMaximum(INT_MAX); + return sb; } +#endif +#ifndef QT_NO_DATETIMEEDIT + case QVariant::Date: { + QDateTimeEdit *ed = new QDateEdit(parent); + ed->setFrame(false); + return ed; } + case QVariant::Time: { + QDateTimeEdit *ed = new QTimeEdit(parent); + ed->setFrame(false); + return ed; } + case QVariant::DateTime: { + QDateTimeEdit *ed = new QDateTimeEdit(parent); + ed->setFrame(false); + return ed; } +#endif + case QVariant::Pixmap: + return new QLabel(parent); +#ifndef QT_NO_SPINBOX + case QVariant::Double: { + QDoubleSpinBox *sb = new QDoubleSpinBox(parent); + sb->setFrame(false); + sb->setMinimum(-DBL_MAX); + sb->setMaximum(DBL_MAX); + return sb; } +#endif +#ifndef QT_NO_LINEEDIT + case QVariant::String: + default: { + // the default editor is a lineedit + QExpandingLineEdit *le = new QExpandingLineEdit(parent); + le->setFrame(le->style()->styleHint(QStyle::SH_ItemView_DrawDelegateFrame, 0, le)); + if (!le->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, le)) + le->setWidgetOwnsGeometry(true); + return le; } +#else + default: + break; +#endif + } + return 0; +} + +QByteArray QDefaultItemEditorFactory::valuePropertyName(QVariant::Type type) const +{ + switch (type) { +#ifndef QT_NO_COMBOBOX + case QVariant::Bool: + return "currentIndex"; +#endif +#ifndef QT_NO_SPINBOX + case QVariant::UInt: + case QVariant::Int: + case QVariant::Double: + return "value"; +#endif +#ifndef QT_NO_DATETIMEEDIT + case QVariant::Date: + return "date"; + case QVariant::Time: + return "time"; + case QVariant::DateTime: + return "dateTime"; +#endif + case QVariant::String: + default: + // the default editor is a lineedit + return "text"; + } +} + +static QItemEditorFactory *q_default_factory = 0; +struct QDefaultFactoryCleaner +{ + inline QDefaultFactoryCleaner() {} + ~QDefaultFactoryCleaner() { delete q_default_factory; q_default_factory = 0; } +}; + +/*! + Returns the default item editor factory. + + \sa setDefaultFactory() +*/ +const QItemEditorFactory *QItemEditorFactory::defaultFactory() +{ + static const QDefaultItemEditorFactory factory; + if (q_default_factory) + return q_default_factory; + return &factory; +} + +/*! + Sets the default item editor factory to the given \a factory. + Both new and existing delegates will use the new factory. + + \sa defaultFactory() +*/ +void QItemEditorFactory::setDefaultFactory(QItemEditorFactory *factory) +{ + static const QDefaultFactoryCleaner cleaner; + delete q_default_factory; + q_default_factory = factory; +} + +/*! + \class QItemEditorCreatorBase + \brief The QItemEditorCreatorBase class provides an abstract base class that + must be subclassed when implementing new item editor creators. + \since 4.2 + \ingroup model-view + + QItemEditorCreatorBase objects are specialized widget factories that + provide editor widgets for one particular QVariant data type. They + are used by QItemEditorFactory to create editors for + \l{QItemDelegate}s. Creator bases must be registered with + QItemEditorFactory::registerEditor(). + + An editor should provide a user property for the data it edits. + QItemDelagates can then access the property using Qt's + \l{Meta-Object System}{meta-object system} to set and retrieve the + editing data. A property is set as the user property with the USER + keyword: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 0 + + If the editor does not provide a user property, it must return the + name of the property from valuePropertyName(); delegates will then + use the name to access the property. If a user property exists, + item delegates will not call valuePropertyName(). + + QStandardItemEditorCreator is a convenience template class that can be used + to register widgets without the need to subclass QItemEditorCreatorBase. + + \sa QStandardItemEditorCreator, QItemEditorFactory, + {Model/View Programming}, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorCreatorBase::~QItemEditorCreatorBase() + + Destroys the editor creator object. +*/ + +/*! + \fn QWidget *QItemEditorCreatorBase::createWidget(QWidget *parent) const + + Returns an editor widget with the given \a parent. + + When implementing this function in subclasses of this class, you must + construct and return new editor widgets with the parent widget specified. +*/ + +/*! + \fn QByteArray QItemEditorCreatorBase::valuePropertyName() const + + Returns the name of the property used to get and set values in the creator's + editor widgets. + + When implementing this function in subclasses, you must ensure that the + editor widget's property specified by this function can accept the type + the creator is registered for. For example, a creator which constructs + QCheckBox widgets to edit boolean values would return the + \l{QCheckBox::checkable}{checkable} property name from this function, + and must be registered in the item editor factory for the QVariant::Bool + type. + + Note: Since Qt 4.2 the item delegates query the user property of widgets, + and only call this function if the widget has no user property. You can + override this behavior by reimplementing QAbstractItemDelegate::setModelData() + and QAbstractItemDelegate::setEditorData(). + + \sa QMetaObject::userProperty(), QItemEditorFactory::registerEditor() +*/ + +/*! + \class QItemEditorCreator + \brief The QItemEditorCreator class makes it possible to create + item editor creator bases without subclassing + QItemEditorCreatorBase. + + \since 4.2 + \ingroup model-view + + QItemEditorCreator is a convenience template class. It uses + the template class to create editors for QItemEditorFactory. + This way, it is not necessary to subclass + QItemEditorCreatorBase. + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 1 + + The constructor takes the name of the property that contains the + editing data. QItemDelegate can then access the property by name + when it sets and retrieves editing data. Only use this class if + your editor does not define a user property (using the USER + keyword in the Q_PROPERTY macro). If the widget has a user + property, you should use QStandardItemEditorCreator instead. + + \sa QItemEditorCreatorBase, QStandardItemEditorCreator, + QItemEditorFactory, {Color Editor Factory Example} +*/ + +/*! + \fn QItemEditorCreator::QItemEditorCreator(const QByteArray &valuePropertyName) + + Constructs an editor creator object using \a valuePropertyName + as the name of the property to be used for editing. The + property name is used by QItemDelegate when setting and + getting editor data. + + Note that the \a valuePropertyName is only used if the editor + widget does not have a user property defined. +*/ + +/*! + \fn QWidget *QItemEditorCreator::createWidget(QWidget *parent) const + \reimp +*/ + +/*! + \fn QByteArray QItemEditorCreator::valuePropertyName() const + \reimp +*/ + +/*! + \class QStandardItemEditorCreator + + \brief The QStandardItemEditorCreator class provides the + possibility to register widgets without having to subclass + QItemEditorCreatorBase. + + \since 4.2 + \ingroup model-view + + This convenience template class makes it possible to register widgets without + having to subclass QItemEditorCreatorBase. + + Example: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 2 + + Setting the \c editorFactory created above in an item delegate via + QItemDelegate::setItemEditorFactory() makes sure that all values of type + QVariant::DateTime will be edited in \c{MyFancyDateTimeEdit}. + + The editor must provide a user property that will contain the + editing data. The property is used by \l{QItemDelegate}s to set + and retrieve the data (using Qt's \l{Meta-Object + System}{meta-object system}). You set the user property with + the USER keyword: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemeditorfactory.cpp 3 + + \sa QItemEditorCreatorBase, QItemEditorCreator, + QItemEditorFactory, QItemDelegate, {Color Editor Factory Example} +*/ + +/*! + \fn QStandardItemEditorCreator::QStandardItemEditorCreator() + + Constructs an editor creator object. +*/ + +/*! + \fn QWidget *QStandardItemEditorCreator::createWidget(QWidget *parent) const + \reimp +*/ + +/*! + \fn QByteArray QStandardItemEditorCreator::valuePropertyName() const + \reimp +*/ + +#ifndef QT_NO_LINEEDIT + +QExpandingLineEdit::QExpandingLineEdit(QWidget *parent) + : QLineEdit(parent), originalWidth(-1), widgetOwnsGeometry(false) +{ + connect(this, SIGNAL(textChanged(QString)), this, SLOT(resizeToContents())); + updateMinimumWidth(); +} + +void QExpandingLineEdit::changeEvent(QEvent *e) +{ + switch (e->type()) + { + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::ContentsRectChange: + updateMinimumWidth(); + break; + default: + break; + } + + QLineEdit::changeEvent(e); +} + +void QExpandingLineEdit::updateMinimumWidth() +{ + int left, right; + getTextMargins(&left, 0, &right, 0); + int width = left + right + 4 /*horizontalMargin in qlineedit.cpp*/; + getContentsMargins(&left, 0, &right, 0); + width += left + right; + + QStyleOptionFrameV2 opt; + initStyleOption(&opt); + + int minWidth = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(width, 0). + expandedTo(QApplication::globalStrut()), this).width(); + setMinimumWidth(minWidth); +} + +void QExpandingLineEdit::resizeToContents() +{ + int oldWidth = width(); + if (originalWidth == -1) + originalWidth = oldWidth; + if (QWidget *parent = parentWidget()) { + QPoint position = pos(); + int hintWidth = minimumWidth() + fontMetrics().width(displayText()); + int parentWidth = parent->width(); + int maxWidth = isRightToLeft() ? position.x() + oldWidth : parentWidth - position.x(); + int newWidth = qBound(originalWidth, hintWidth, maxWidth); + if (widgetOwnsGeometry) + setMaximumWidth(newWidth); + if (isRightToLeft()) + move(position.x() - newWidth + oldWidth, position.y()); + resize(newWidth, height()); + } +} + +#endif // QT_NO_LINEEDIT + +#ifndef QT_NO_COMBOBOX + +QBooleanComboBox::QBooleanComboBox(QWidget *parent) + : QComboBox(parent) +{ + addItem(QComboBox::tr("False")); + addItem(QComboBox::tr("True")); +} + +void QBooleanComboBox::setValue(bool value) +{ + setCurrentIndex(value ? 1 : 0); +} + +bool QBooleanComboBox::value() const +{ + return (currentIndex() == 1); +} + +#endif // QT_NO_COMBOBOX + +QT_END_NAMESPACE + +#if !defined(QT_NO_LINEEDIT) || !defined(QT_NO_COMBOBOX) +#include "qitemeditorfactory.moc" +#endif + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemeditorfactory.h b/src/gui/itemviews/qitemeditorfactory.h new file mode 100644 index 0000000000..28e145460f --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMEDITORFACTORY_H +#define QITEMEDITORFACTORY_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QWidget; + +class Q_GUI_EXPORT QItemEditorCreatorBase +{ +public: + virtual ~QItemEditorCreatorBase() {} + + virtual QWidget *createWidget(QWidget *parent) const = 0; + virtual QByteArray valuePropertyName() const = 0; +}; + +template +class QItemEditorCreator : public QItemEditorCreatorBase +{ +public: + inline QItemEditorCreator(const QByteArray &valuePropertyName); + inline QWidget *createWidget(QWidget *parent) const { return new T(parent); } + inline QByteArray valuePropertyName() const { return propertyName; } + +private: + QByteArray propertyName; +}; + +template +class QStandardItemEditorCreator: public QItemEditorCreatorBase +{ +public: + inline QStandardItemEditorCreator() + : propertyName(T::staticMetaObject.userProperty().name()) + {} + inline QWidget *createWidget(QWidget *parent) const { return new T(parent); } + inline QByteArray valuePropertyName() const { return propertyName; } + +private: + QByteArray propertyName; +}; + + +template +Q_INLINE_TEMPLATE QItemEditorCreator::QItemEditorCreator(const QByteArray &avaluePropertyName) + : propertyName(avaluePropertyName) {} + +class Q_GUI_EXPORT QItemEditorFactory +{ +public: + inline QItemEditorFactory() {} + virtual ~QItemEditorFactory(); + + virtual QWidget *createEditor(QVariant::Type type, QWidget *parent) const; + virtual QByteArray valuePropertyName(QVariant::Type type) const; + + void registerEditor(QVariant::Type type, QItemEditorCreatorBase *creator); + + static const QItemEditorFactory *defaultFactory(); + static void setDefaultFactory(QItemEditorFactory *factory); + +private: + QHash creatorMap; +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMEDITORFACTORY_H diff --git a/src/gui/itemviews/qitemeditorfactory_p.h b/src/gui/itemviews/qitemeditorfactory_p.h new file mode 100644 index 0000000000..87b39c7a12 --- /dev/null +++ b/src/gui/itemviews/qitemeditorfactory_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMEDITORFACTORY_P_H +#define QITEMEDITORFACTORY_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 + +#ifndef QT_NO_ITEMVIEWS + +#ifndef QT_NO_LINEEDIT + +QT_BEGIN_NAMESPACE + + +class QExpandingLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + QExpandingLineEdit(QWidget *parent); + + void setWidgetOwnsGeometry(bool value) + { + widgetOwnsGeometry = value; + } + +protected: + void changeEvent(QEvent *e); + +public Q_SLOTS: + void resizeToContents(); + +private: + void updateMinimumWidth(); + + int originalWidth; + bool widgetOwnsGeometry; +}; + + +QT_END_NAMESPACE + +#endif // QT_NO_LINEEDIT + +#endif //QT_NO_ITEMVIEWS + +#endif //QITEMEDITORFACTORY_P_H diff --git a/src/gui/itemviews/qitemselectionmodel.cpp b/src/gui/itemviews/qitemselectionmodel.cpp new file mode 100644 index 0000000000..27a4a402e2 --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel.cpp @@ -0,0 +1,1625 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qitemselectionmodel.h" +#include +#include + +#ifndef QT_NO_ITEMVIEWS + +QT_BEGIN_NAMESPACE + +/*! + \class QItemSelectionRange + + \brief The QItemSelectionRange class manages information about a + range of selected items in a model. + + \ingroup model-view + + A QItemSelectionRange contains information about a range of + selected items in a model. A range of items is a contiguous array + of model items, extending to cover a number of adjacent rows and + columns with a common parent item; this can be visualized as a + two-dimensional block of cells in a table. A selection range has a + top(), left() a bottom(), right() and a parent(). + + The QItemSelectionRange class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The model items contained in the selection range can be obtained + using the indexes() function. Use QItemSelectionModel::selectedIndexes() + to get a list of all selected items for a view. + + You can determine whether a given model item lies within a + particular range by using the contains() function. Ranges can also + be compared using the overloaded operators for equality and + inequality, and the intersects() function allows you to determine + whether two ranges overlap. + + \sa {Model/View Programming}, QAbstractItemModel, QItemSelection, + QItemSelectionModel +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange() + + Constructs an empty selection range. +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QItemSelectionRange &other) + + Copy constructor. Constructs a new selection range with the same contents + as the \a other range given. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight) + + Constructs a new selection range containing only the index specified + by the \a topLeft and the index \a bottomRight. + +*/ + +/*! + \fn QItemSelectionRange::QItemSelectionRange(const QModelIndex &index) + + Constructs a new selection range containing only the model item specified + by the model index \a index. +*/ + +/*! + \fn int QItemSelectionRange::top() const + + Returns the row index corresponding to the uppermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::left() const + + Returns the column index corresponding to the leftmost selected column in the + selection range. +*/ + +/*! + \fn int QItemSelectionRange::bottom() const + + Returns the row index corresponding to the lowermost selected row in the + selection range. + +*/ + +/*! + \fn int QItemSelectionRange::right() const + + Returns the column index corresponding to the rightmost selected column in + the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::width() const + + Returns the number of selected columns in the selection range. + +*/ + +/*! + \fn int QItemSelectionRange::height() const + + Returns the number of selected rows in the selection range. + +*/ + +/*! + \fn const QAbstractItemModel *QItemSelectionRange::model() const + + Returns the model that the items in the selection range belong to. +*/ + +/*! + \fn QModelIndex QItemSelectionRange::topLeft() const + + Returns the index for the item located at the top-left corner of + the selection range. + + \sa top(), left(), bottomRight() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::bottomRight() const + + Returns the index for the item located at the bottom-right corner + of the selection range. + + \sa bottom(), right(), topLeft() +*/ + +/*! + \fn QModelIndex QItemSelectionRange::parent() const + + Returns the parent model item index of the items in the selection range. + +*/ + +/*! + \fn bool QItemSelectionRange::contains(const QModelIndex &index) const + + Returns true if the model item specified by the \a index lies within the + range of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::contains(int row, int column, + const QModelIndex &parentIndex) const + \overload + + Returns true if the model item specified by (\a row, \a column) + and with \a parentIndex as the parent item lies within the range + of selected items; otherwise returns false. +*/ + +/*! + \fn bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const + + Returns true if this selection range intersects (overlaps with) the \a other + range given; otherwise returns false. + +*/ +bool QItemSelectionRange::intersects(const QItemSelectionRange &other) const +{ + return (isValid() && other.isValid() + && parent() == other.parent() + && model() == other.model() + && ((top() <= other.top() && bottom() >= other.top()) + || (top() >= other.top() && top() <= other.bottom())) + && ((left() <= other.left() && right() >= other.left()) + || (left() >= other.left() && left() <= other.right()))); +} + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const + \obsolete + + Use intersected(\a other) instead. +*/ + +/*! + \fn QItemSelectionRange QItemSelectionRange::intersected(const QItemSelectionRange &other) const + \since 4.2 + + Returns a new selection range containing only the items that are found in + both the selection range and the \a other selection range. +*/ + +QItemSelectionRange QItemSelectionRange::intersect(const QItemSelectionRange &other) const +{ + if (model() == other.model() && parent() == other.parent()) { + QModelIndex topLeft = model()->index(qMax(top(), other.top()), + qMax(left(), other.left()), + other.parent()); + QModelIndex bottomRight = model()->index(qMin(bottom(), other.bottom()), + qMin(right(), other.right()), + other.parent()); + return QItemSelectionRange(topLeft, bottomRight); + } + return QItemSelectionRange(); +} + +/*! + \fn bool QItemSelectionRange::operator==(const QItemSelectionRange &other) const + + Returns true if the selection range is exactly the same as the \a other + range given; otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::operator!=(const QItemSelectionRange &other) const + + Returns true if the selection range differs from the \a other range given; + otherwise returns false. + +*/ + +/*! + \fn bool QItemSelectionRange::isValid() const + + Returns true if the selection range is valid; otherwise returns false. + +*/ + +/* + \internal + + utility function for getting the indexes from a range + it avoid concatenating list and works on one + */ + +static void indexesFromRange(const QItemSelectionRange &range, QModelIndexList &result) +{ + if (range.isValid() && range.model()) { + for (int column = range.left(); column <= range.right(); ++column) { + for (int row = range.top(); row <= range.bottom(); ++row) { + QModelIndex index = range.model()->index(row, column, range.parent()); + Qt::ItemFlags flags = range.model()->flags(index); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + result.append(index); + } + } + } +} + +/*! + Returns true if the selection range contains no selectable item + \since 4.7 +*/ + +bool QItemSelectionRange::isEmpty() const +{ + if (!isValid() || !model()) + return true; + + for (int column = left(); column <= right(); ++column) { + for (int row = top(); row <= bottom(); ++row) { + QModelIndex index = model()->index(row, column, parent()); + Qt::ItemFlags flags = model()->flags(index); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return false; + } + } + return true; +} + +/*! + Returns the list of model index items stored in the selection. +*/ + +QModelIndexList QItemSelectionRange::indexes() const +{ + QModelIndexList result; + indexesFromRange(*this, result); + return result; +} + +/*! + \class QItemSelection + + \brief The QItemSelection class manages information about selected items in a model. + + \ingroup model-view + + A QItemSelection describes the items in a model that have been + selected by the user. A QItemSelection is basically a list of + selection ranges, see QItemSelectionRange. It provides functions for + creating and manipulating selections, and selecting a range of items + from a model. + + The QItemSelection class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + An item selection can be constructed and initialized to contain a + range of items from an existing model. The following example constructs + a selection that contains a range of items from the given \c model, + beginning at the \c topLeft, and ending at the \c bottomRight. + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 0 + + An empty item selection can be constructed, and later populated as + required. So, if the model is going to be unavailable when we construct + the item selection, we can rewrite the above code in the following way: + + \snippet doc/src/snippets/code/src_gui_itemviews_qitemselectionmodel.cpp 1 + + QItemSelection saves memory, and avoids unnecessary work, by working with + selection ranges rather than recording the model item index for each + item in the selection. Generally, an instance of this class will contain + a list of non-overlapping selection ranges. + + Use merge() to merge one item selection into another without making + overlapping ranges. Use split() to split one selection range into + smaller ranges based on a another selection range. + + \sa {Model/View Programming}, QItemSelectionModel +*/ + +/*! + \fn QItemSelection::QItemSelection() + + Constructs an empty selection. +*/ + +/*! + Constructs an item selection that extends from the top-left model item, + specified by the \a topLeft index, to the bottom-right item, specified + by \a bottomRight. +*/ +QItemSelection::QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + select(topLeft, bottomRight); +} + +/*! + Adds the items in the range that extends from the top-left model + item, specified by the \a topLeft index, to the bottom-right item, + specified by \a bottomRight to the list. + + \note \a topLeft and \a bottomRight must have the same parent. +*/ +void QItemSelection::select(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (!topLeft.isValid() || !bottomRight.isValid()) + return; + + if ((topLeft.model() != bottomRight.model()) + || topLeft.parent() != bottomRight.parent()) { + qWarning("Can't select indexes from different model or with different parents"); + return; + } + if (topLeft.row() > bottomRight.row() || topLeft.column() > bottomRight.column()) { + int top = qMin(topLeft.row(), bottomRight.row()); + int bottom = qMax(topLeft.row(), bottomRight.row()); + int left = qMin(topLeft.column(), bottomRight.column()); + int right = qMax(topLeft.column(), bottomRight.column()); + QModelIndex tl = topLeft.sibling(top, left); + QModelIndex br = bottomRight.sibling(bottom, right); + append(QItemSelectionRange(tl, br)); + return; + } + append(QItemSelectionRange(topLeft, bottomRight)); +} + +/*! + Returns true if the selection contains the given \a index; otherwise + returns false. +*/ + +bool QItemSelection::contains(const QModelIndex &index) const +{ + if (index.flags() & Qt::ItemIsSelectable) { + QList::const_iterator it = begin(); + for (; it != end(); ++it) + if ((*it).contains(index)) + return true; + } + return false; +} + +/*! + Returns a list of model indexes that correspond to the selected items. +*/ + +QModelIndexList QItemSelection::indexes() const +{ + QModelIndexList result; + QList::const_iterator it = begin(); + for (; it != end(); ++it) + indexesFromRange(*it, result); + return result; +} + +/*! + Merges the \a other selection with this QItemSelection using the + \a command given. This method guarantees that no ranges are overlapping. + + Note that only QItemSelectionModel::Select, + QItemSelectionModel::Deselect, and QItemSelectionModel::Toggle are + supported. + + \sa split() +*/ +void QItemSelection::merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command) +{ + if (other.isEmpty() || + !(command & QItemSelectionModel::Select || + command & QItemSelectionModel::Deselect || + command & QItemSelectionModel::Toggle)) + return; + + QItemSelection newSelection = other; + // Collect intersections + QItemSelection intersections; + QItemSelection::iterator it = newSelection.begin(); + while (it != newSelection.end()) { + if (!(*it).isValid()) { + it = newSelection.erase(it); + continue; + } + for (int t = 0; t < count(); ++t) { + if ((*it).intersects(at(t))) + intersections.append(at(t).intersected(*it)); + } + ++it; + } + + // Split the old (and new) ranges using the intersections + for (int i = 0; i < intersections.count(); ++i) { // for each intersection + for (int t = 0; t < count();) { // splitt each old range + if (at(t).intersects(intersections.at(i))) { + split(at(t), intersections.at(i), this); + removeAt(t); + } else { + ++t; + } + } + // only split newSelection if Toggle is specified + for (int n = 0; (command & QItemSelectionModel::Toggle) && n < newSelection.count();) { + if (newSelection.at(n).intersects(intersections.at(i))) { + split(newSelection.at(n), intersections.at(i), &newSelection); + newSelection.removeAt(n); + } else { + ++n; + } + } + } + // do not add newSelection for Deselect + if (!(command & QItemSelectionModel::Deselect)) + operator+=(newSelection); +} + +/*! + Splits the selection \a range using the selection \a other range. + Removes all items in \a other from \a range and puts the result in \a result. + This can be compared with the semantics of the \e subtract operation of a set. + \sa merge() +*/ + +void QItemSelection::split(const QItemSelectionRange &range, + const QItemSelectionRange &other, QItemSelection *result) +{ + if (range.parent() != other.parent() || range.model() != other.model()) + return; + + QModelIndex parent = other.parent(); + int top = range.top(); + int left = range.left(); + int bottom = range.bottom(); + int right = range.right(); + int other_top = other.top(); + int other_left = other.left(); + int other_bottom = other.bottom(); + int other_right = other.right(); + const QAbstractItemModel *model = range.model(); + Q_ASSERT(model); + if (other_top > top) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(other_top - 1, right, parent); + result->append(QItemSelectionRange(tl, br)); + top = other_top; + } + if (other_bottom < bottom) { + QModelIndex tl = model->index(other_bottom + 1, left, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + bottom = other_bottom; + } + if (other_left > left) { + QModelIndex tl = model->index(top, left, parent); + QModelIndex br = model->index(bottom, other_left - 1, parent); + result->append(QItemSelectionRange(tl, br)); + left = other_left; + } + if (other_right < right) { + QModelIndex tl = model->index(top, other_right + 1, parent); + QModelIndex br = model->index(bottom, right, parent); + result->append(QItemSelectionRange(tl, br)); + right = other_right; + } +} + + +void QItemSelectionModelPrivate::initModel(QAbstractItemModel *model) +{ + this->model = model; + if (model) { + Q_Q(QItemSelectionModel); + QObject::connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(_q_rowsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + q, SLOT(_q_columnsAboutToBeRemoved(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + q, SLOT(_q_rowsAboutToBeInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + q, SLOT(_q_columnsAboutToBeInserted(QModelIndex,int,int))); + QObject::connect(model, SIGNAL(layoutAboutToBeChanged()), + q, SLOT(_q_layoutAboutToBeChanged())); + QObject::connect(model, SIGNAL(layoutChanged()), + q, SLOT(_q_layoutChanged())); + } +} + +/*! + \internal + + returns a QItemSelection where all ranges have been expanded to: + Rows: left: 0 and right: columnCount()-1 + Columns: top: 0 and bottom: rowCount()-1 +*/ + +QItemSelection QItemSelectionModelPrivate::expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const +{ + if (selection.isEmpty() && !((command & QItemSelectionModel::Rows) || + (command & QItemSelectionModel::Columns))) + return selection; + + QItemSelection expanded; + if (command & QItemSelectionModel::Rows) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int colCount = model->columnCount(parent); + QModelIndex tl = model->index(selection.at(i).top(), 0, parent); + QModelIndex br = model->index(selection.at(i).bottom(), colCount - 1, parent); + //we need to merge because the same row could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + if (command & QItemSelectionModel::Columns) { + for (int i = 0; i < selection.count(); ++i) { + QModelIndex parent = selection.at(i).parent(); + int rowCount = model->rowCount(parent); + QModelIndex tl = model->index(0, selection.at(i).left(), parent); + QModelIndex br = model->index(rowCount - 1, selection.at(i).right(), parent); + //we need to merge because the same column could have already been inserted + expanded.merge(QItemSelection(tl, br), QItemSelectionModel::Select); + } + } + return expanded; +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + finalize(); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.row() >= start && currentIndex.row() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are rows left above the change + currentIndex = model->index(start - 1, old.column(), parent); + else if (model && end < model->rowCount(parent) - 1) // there are rows left below the change + currentIndex = model->index(end + 1, old.column(), parent); + else // there are no rows left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + emit q->currentRowChanged(currentIndex, old); + if (currentIndex.column() != old.column()) + emit q->currentColumnChanged(currentIndex, old); + } + + QItemSelection deselected; + QItemSelection newParts; + QItemSelection::iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->topLeft().parent() != parent) { // Check parents until reaching root or contained in range + QModelIndex itParent = it->topLeft().parent(); + while (itParent.isValid() && itParent.parent() != parent) + itParent = itParent.parent(); + + if (itParent.isValid() && start <= itParent.row() && itParent.row() <= end) { + deselected.append(*it); + it = ranges.erase(it); + } else { + ++it; + } + } else if (start <= it->bottom() && it->bottom() <= end // Full inclusion + && start <= it->top() && it->top() <= end) { + deselected.append(*it); + it = ranges.erase(it); + } else if (start <= it->top() && it->top() <= end) { // Top intersection + deselected.append(QItemSelectionRange(it->topLeft(), model->index(end, it->left(), it->parent()))); + *it = QItemSelectionRange(model->index(end + 1, it->left(), it->parent()), it->bottomRight()); + ++it; + } else if (start <= it->bottom() && it->bottom() <= end) { // Bottom intersection + deselected.append(QItemSelectionRange(model->index(start, it->right(), it->parent()), it->bottomRight())); + *it = QItemSelectionRange(it->topLeft(), model->index(start - 1, it->right(), it->parent())); + ++it; + } else if (it->top() < start && end < it->bottom()) { // Middle intersection + // If the parent contains (1, 2, 3, 4, 5, 6, 7, 8) and [3, 4, 5, 6] is selected, + // and [4, 5] is removed, we need to split [3, 4, 5, 6] into [3], [4, 5] and [6]. + // [4, 5] is appended to deselected, and [3] and [6] remain part of the selection + // in ranges. + const QItemSelectionRange removedRange(model->index(start, it->right(), it->parent()), + model->index(end, it->left(), it->parent())); + deselected.append(removedRange); + QItemSelection::split(*it, removedRange, &newParts); + it = ranges.erase(it); + } else + ++it; + } + ranges.append(newParts); + + if (!deselected.isEmpty()) + emit q->selectionChanged(QItemSelection(), deselected); +} + +/*! + \internal +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + Q_Q(QItemSelectionModel); + + // update current index + if (currentIndex.isValid() && parent == currentIndex.parent() + && currentIndex.column() >= start && currentIndex.column() <= end) { + QModelIndex old = currentIndex; + if (start > 0) // there are columns to the left of the change + currentIndex = model->index(old.row(), start - 1, parent); + else if (model && end < model->columnCount() - 1) // there are columns to the right of the change + currentIndex = model->index(old.row(), end + 1, parent); + else // there are no columns left in the table + currentIndex = QModelIndex(); + emit q->currentChanged(currentIndex, old); + if (currentIndex.row() != old.row()) + emit q->currentRowChanged(currentIndex, old); + emit q->currentColumnChanged(currentIndex, old); + } + + // update selections + QModelIndex tl = model->index(0, start, parent); + QModelIndex br = model->index(model->rowCount(parent) - 1, end, parent); + q->select(QItemSelection(tl, br), QItemSelectionModel::Deselect); + finalize(); +} + +/*! + \internal + + Split selection ranges if columns are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_columnsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList split; + QList::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).left() < start && (*it).right() >= start) { + QModelIndex bottomMiddle = model->index((*it).bottom(), start - 1, (*it).parent()); + QItemSelectionRange left((*it).topLeft(), bottomMiddle); + QModelIndex topMiddle = model->index((*it).top(), start, (*it).parent()); + QItemSelectionRange right(topMiddle, (*it).bottomRight()); + it = ranges.erase(it); + split.append(left); + split.append(right); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection ranges if rows are about to be inserted in the middle. +*/ +void QItemSelectionModelPrivate::_q_rowsAboutToBeInserted(const QModelIndex &parent, + int start, int end) +{ + Q_UNUSED(end); + finalize(); + QList split; + QList::iterator it = ranges.begin(); + for (; it != ranges.end(); ) { + if ((*it).isValid() && (*it).parent() == parent + && (*it).top() < start && (*it).bottom() >= start) { + QModelIndex middleRight = model->index(start - 1, (*it).right(), (*it).parent()); + QItemSelectionRange top((*it).topLeft(), middleRight); + QModelIndex middleLeft = model->index(start, (*it).left(), (*it).parent()); + QItemSelectionRange bottom(middleLeft, (*it).bottomRight()); + it = ranges.erase(it); + split.append(top); + split.append(bottom); + } else { + ++it; + } + } + ranges += split; +} + +/*! + \internal + + Split selection into individual (persistent) indexes. This is done in + preparation for the layoutChanged() signal, where the indexes can be + merged again. +*/ +void QItemSelectionModelPrivate::_q_layoutAboutToBeChanged() +{ + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); + + // optimization for when all indexes are selected + // (only if there is lots of items (1000) because this is not entirely correct) + if (ranges.isEmpty() && currentSelection.count() == 1) { + QItemSelectionRange range = currentSelection.first(); + QModelIndex parent = range.parent(); + tableRowCount = model->rowCount(parent); + tableColCount = model->columnCount(parent); + if (tableRowCount * tableColCount > 1000 + && range.top() == 0 + && range.left() == 0 + && range.bottom() == tableRowCount - 1 + && range.right() == tableColCount - 1) { + tableSelected = true; + tableParent = parent; + return; + } + } + tableSelected = false; + + QModelIndexList indexes = ranges.indexes(); + QModelIndexList::const_iterator it; + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentIndexes.append(QPersistentModelIndex(*it)); + indexes = currentSelection.indexes(); + for (it = indexes.constBegin(); it != indexes.constEnd(); ++it) + savedPersistentCurrentIndexes.append(QPersistentModelIndex(*it)); +} + +/*! + \internal + + Merges \a indexes into an item selection made up of ranges. + Assumes that the indexes are sorted. +*/ +static QItemSelection mergeIndexes(const QList &indexes) +{ + QItemSelection colSpans; + // merge columns + int i = 0; + while (i < indexes.count()) { + QModelIndex tl = indexes.at(i); + QModelIndex br = tl; + while (++i < indexes.count()) { + QModelIndex next = indexes.at(i); + if ((next.parent() == br.parent()) + && (next.row() == br.row()) + && (next.column() == br.column() + 1)) + br = next; + else + break; + } + colSpans.append(QItemSelectionRange(tl, br)); + } + // merge rows + QItemSelection rowSpans; + i = 0; + while (i < colSpans.count()) { + QModelIndex tl = colSpans.at(i).topLeft(); + QModelIndex br = colSpans.at(i).bottomRight(); + QModelIndex prevTl = tl; + while (++i < colSpans.count()) { + QModelIndex nextTl = colSpans.at(i).topLeft(); + QModelIndex nextBr = colSpans.at(i).bottomRight(); + + if (nextTl.parent() != tl.parent()) + break; // we can't merge selection ranges from different parents + + if ((nextTl.column() == prevTl.column()) && (nextBr.column() == br.column()) + && (nextTl.row() == prevTl.row() + 1) && (nextBr.row() == br.row() + 1)) { + br = nextBr; + prevTl = nextTl; + } else { + break; + } + } + rowSpans.append(QItemSelectionRange(tl, br)); + } + return rowSpans; +} + +/*! + \internal + + Merge the selected indexes into selection ranges again. +*/ +void QItemSelectionModelPrivate::_q_layoutChanged() +{ + // special case for when all indexes are selected + if (tableSelected && tableColCount == model->columnCount(tableParent) + && tableRowCount == model->rowCount(tableParent)) { + ranges.clear(); + currentSelection.clear(); + int bottom = tableRowCount - 1; + int right = tableColCount - 1; + QModelIndex tl = model->index(0, 0, tableParent); + QModelIndex br = model->index(bottom, right, tableParent); + currentSelection << QItemSelectionRange(tl, br); + tableParent = QModelIndex(); + tableSelected = false; + return; + } + + if (savedPersistentCurrentIndexes.isEmpty() && savedPersistentIndexes.isEmpty()) { + // either the selection was actually empty, or we + // didn't get the layoutAboutToBeChanged() signal + return; + } + // clear the "old" selection + ranges.clear(); + currentSelection.clear(); + + // sort the "new" selection, as preparation for merging + qStableSort(savedPersistentIndexes.begin(), savedPersistentIndexes.end()); + qStableSort(savedPersistentCurrentIndexes.begin(), savedPersistentCurrentIndexes.end()); + + // update the selection by merging the individual indexes + ranges = mergeIndexes(savedPersistentIndexes); + currentSelection = mergeIndexes(savedPersistentCurrentIndexes); + + // release the persistent indexes + savedPersistentIndexes.clear(); + savedPersistentCurrentIndexes.clear(); +} + +/*! + \class QItemSelectionModel + + \brief The QItemSelectionModel class keeps track of a view's selected items. + + \ingroup model-view + + A QItemSelectionModel keeps track of the selected items in a view, or + in several views onto the same model. It also keeps track of the + currently selected item in a view. + + The QItemSelectionModel class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + The selected items are stored using ranges. Whenever you want to + modify the selected items use select() and provide either a + QItemSelection, or a QModelIndex and a QItemSelectionModel::SelectionFlag. + + The QItemSelectionModel takes a two layer approach to selection + management, dealing with both selected items that have been committed + and items that are part of the current selection. The current + selected items are part of the current interactive selection (for + example with rubber-band selection or keyboard-shift selections). + + To update the currently selected items, use the bitwise OR of + QItemSelectionModel::Current and any of the other SelectionFlags. + If you omit the QItemSelectionModel::Current command, a new current + selection will be created, and the previous one added to the whole + selection. All functions operate on both layers; for example, + selectedItems() will return items from both layers. + + \sa {Model/View Programming}, QAbstractItemModel, {Chart Example} +*/ + +/*! + Constructs a selection model that operates on the specified item \a model. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model) + : QObject(*new QItemSelectionModelPrivate, model) +{ + d_func()->initModel(model); +} + +/*! + Constructs a selection model that operates on the specified item \a model with \a parent. +*/ +QItemSelectionModel::QItemSelectionModel(QAbstractItemModel *model, QObject *parent) + : QObject(*new QItemSelectionModelPrivate, parent) +{ + d_func()->initModel(model); +} + +/*! + \internal +*/ +QItemSelectionModel::QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model) + : QObject(dd, model) +{ + dd.initModel(model); +} + +/*! + Destroys the selection model. +*/ +QItemSelectionModel::~QItemSelectionModel() +{ +} + +/*! + Selects the model item \a index using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlags +*/ +void QItemSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + QItemSelection selection(index, index); + select(selection, command); +} + +/*! + \fn void QItemSelectionModel::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted whenever the current item changes. The \a previous + model item index is replaced by the \a current index as the selection's + current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentIndex() setCurrentIndex() selectionChanged() +*/ + +/*! + \fn void QItemSelectionModel::currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its column is + different to the column of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentRowChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) + + This signal is emitted if the \a current item changes and its row is + different to the row of the \a previous current item. + + Note that this signal will not be emitted when the item model is reset. + + \sa currentChanged() currentColumnChanged() currentIndex() setCurrentIndex() +*/ + +/*! + \fn void QItemSelectionModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) + + This signal is emitted whenever the selection changes. The change in the + selection is represented as an item selection of \a deselected items and + an item selection of \a selected items. + + Note the that the current index changes independently from the selection. + Also note that this signal will not be emitted when the item model is reset. + + \sa select() currentChanged() +*/ + +/*! + \enum QItemSelectionModel::SelectionFlag + + This enum describes the way the selection model will be updated. + + \value NoUpdate No selection will be made. + \value Clear The complete selection will be cleared. + \value Select All specified indexes will be selected. + \value Deselect All specified indexes will be deselected. + \value Toggle All specified indexes will be selected or + deselected depending on their current state. + \value Current The current selection will be updated. + \value Rows All indexes will be expanded to span rows. + \value Columns All indexes will be expanded to span columns. + \value SelectCurrent A combination of Select and Current, provided for + convenience. + \value ToggleCurrent A combination of Toggle and Current, provided for + convenience. + \value ClearAndSelect A combination of Clear and Select, provided for + convenience. +*/ + +/*! + Selects the item \a selection using the specified \a command, and emits + selectionChanged(). + + \sa QItemSelectionModel::SelectionFlag +*/ +void QItemSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (command == NoUpdate) + return; + + // store old selection + QItemSelection sel = selection; + // If d->ranges is non-empty when the source model is reset the persistent indexes + // it contains will be invalid. We can't clear them in a modelReset slot because that might already + // be too late if another model observer is connected to the same modelReset slot and is invoked first + // it might call select() on this selection model before any such QItemSelectionModelPrivate::_q_modelReset() slot + // is invoked, so it would not be cleared yet. We clear it invalid ranges in it here. + QItemSelection::iterator it = d->ranges.begin(); + while (it != d->ranges.end()) { + if (!it->isValid()) + it = d->ranges.erase(it); + else + ++it; + } + + QItemSelection old = d->ranges; + old.merge(d->currentSelection, d->currentCommand); + + // expand selection according to SelectionBehavior + if (command & Rows || command & Columns) + sel = d->expandSelection(sel, command); + + // clear ranges and currentSelection + if (command & Clear) { + d->ranges.clear(); + d->currentSelection.clear(); + } + + // merge and clear currentSelection if Current was not set (ie. start new currentSelection) + if (!(command & Current)) + d->finalize(); + + // update currentSelection + if (command & Toggle || command & Select || command & Deselect) { + d->currentCommand = command; + d->currentSelection = sel; + } + + // generate new selection, compare with old and emit selectionChanged() + QItemSelection newSelection = d->ranges; + newSelection.merge(d->currentSelection, d->currentCommand); + emitSelectionChanged(newSelection, old); +} + +/*! + Clears the selection model. Emits selectionChanged() and currentChanged(). +*/ +void QItemSelectionModel::clear() +{ + Q_D(QItemSelectionModel); + clearSelection(); + QModelIndex previous = d->currentIndex; + d->currentIndex = QModelIndex(); + if (previous.isValid()) { + emit currentChanged(d->currentIndex, previous); + emit currentRowChanged(d->currentIndex, previous); + emit currentColumnChanged(d->currentIndex, previous); + } +} + +/*! + Clears the selection model. Does not emit any signals. +*/ +void QItemSelectionModel::reset() +{ + bool block = blockSignals(true); + clear(); + blockSignals(block); +} + +/*! + \since 4.2 + Clears the selection in the selection model. Emits selectionChanged(). +*/ +void QItemSelectionModel::clearSelection() +{ + Q_D(QItemSelectionModel); + if (d->ranges.count() == 0 && d->currentSelection.count() == 0) + return; + QItemSelection selection = d->ranges; + selection.merge(d->currentSelection, d->currentCommand); + d->ranges.clear(); + d->currentSelection.clear(); + emit selectionChanged(QItemSelection(), selection); +} + + +/*! + Sets the model item \a index to be the current item, and emits + currentChanged(). The current item is used for keyboard navigation and + focus indication; it is independent of any selected items, although a + selected item can also be the current item. + + Depending on the specified \a command, the \a index can also become part + of the current selection. + \sa select() +*/ +void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QItemSelectionModel); + if (index == d->currentIndex) { + if (command != NoUpdate) + select(index, command); // select item + return; + } + QPersistentModelIndex previous = d->currentIndex; + d->currentIndex = index; // set current before emitting selection changed below + if (command != NoUpdate) + select(d->currentIndex, command); // select item + emit currentChanged(d->currentIndex, previous); + if (d->currentIndex.row() != previous.row() || + d->currentIndex.parent() != previous.parent()) + emit currentRowChanged(d->currentIndex, previous); + if (d->currentIndex.column() != previous.column() || + d->currentIndex.parent() != previous.parent()) + emit currentColumnChanged(d->currentIndex, previous); +} + +/*! + Returns the model item index for the current item, or an invalid index + if there is no current item. +*/ +QModelIndex QItemSelectionModel::currentIndex() const +{ + return static_cast(d_func()->currentIndex); +} + +/*! + Returns true if the given model item \a index is selected. +*/ +bool QItemSelectionModel::isSelected(const QModelIndex &index) const +{ + Q_D(const QItemSelectionModel); + if (d->model != index.model() || !index.isValid()) + return false; + + bool selected = false; + // search model ranges + QList::const_iterator it = d->ranges.begin(); + for (; it != d->ranges.end(); ++it) { + if ((*it).isValid() && (*it).contains(index)) { + selected = true; + break; + } + } + + // check currentSelection + if (d->currentSelection.count()) { + if ((d->currentCommand & Deselect) && selected) + selected = !d->currentSelection.contains(index); + else if (d->currentCommand & Toggle) + selected ^= d->currentSelection.contains(index); + else if ((d->currentCommand & Select) && !selected) + selected = d->currentSelection.contains(index); + } + + if (selected) { + Qt::ItemFlags flags = d->model->flags(index); + return (flags & Qt::ItemIsSelectable); + } + + return false; +} + +/*! + Returns true if all items are selected in the \a row with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same row and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isRowSelected(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if row exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i=0; icurrentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + row >= d->currentSelection.at(i).top() && + row <= d->currentSelection.at(i).bottom()) + return false; + } + } + // return false if ranges in both currentSelection and ranges + // intersect and have the same row contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i=0; icurrentSelection.count(); ++i) + if (d->currentSelection.at(i).top() <= row && + d->currentSelection.at(i).bottom() >= row) + for (int j=0; jranges.count(); ++j) + if (d->ranges.at(j).top() <= row && d->ranges.at(j).bottom() >= row + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) + return false; + } + // add ranges and currentSelection and check through them all + QList::const_iterator it; + QList joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int colCount = d->model->columnCount(parent); + for (int column = 0; column < colCount; ++column) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + bool selectable = false; + for (int i = column; !selectable && i <= (*it).right(); ++i) { + Qt::ItemFlags flags = d->model->index(row, i, parent).flags(); + selectable = flags & Qt::ItemIsSelectable; + } + if (selectable){ + column = qMax(column, (*it).right()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return colCount > 0; // no columns means no selected items +} + +/*! + Returns true if all items are selected in the \a column with the given + \a parent. + + Note that this function is usually faster than calling isSelected() + on all items in the same column and that unselectable items are + ignored. +*/ +bool QItemSelectionModel::isColumnSelected(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + // return false if column exist in currentSelection (Deselect) + if (d->currentCommand & Deselect && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).parent() == parent && + column >= d->currentSelection.at(i).left() && + column <= d->currentSelection.at(i).right()) + return false; + } + } + // return false if ranges in both currentSelection and the selection model + // intersect and have the same column contained + if (d->currentCommand & Toggle && d->currentSelection.count()) { + for (int i = 0; i < d->currentSelection.count(); ++i) { + if (d->currentSelection.at(i).left() <= column && + d->currentSelection.at(i).right() >= column) { + for (int j = 0; j < d->ranges.count(); ++j) { + if (d->ranges.at(j).left() <= column && d->ranges.at(j).right() >= column + && d->currentSelection.at(i).intersected(d->ranges.at(j)).isValid()) { + return false; + } + } + } + } + } + // add ranges and currentSelection and check through them all + QList::const_iterator it; + QList joined = d->ranges; + if (d->currentSelection.count()) + joined += d->currentSelection; + int rowCount = d->model->rowCount(parent); + for (int row = 0; row < rowCount; ++row) { + for (it = joined.constBegin(); it != joined.constEnd(); ++it) { + if ((*it).contains(row, column, parent)) { + Qt::ItemFlags flags = d->model->index(row, column, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) { + row = qMax(row, (*it).bottom()); + break; + } + } + } + if (it == joined.constEnd()) + return false; + } + return rowCount > 0; // no rows means no selected items +} + +/*! + Returns true if there are any items selected in the \a row with the given + \a parent. +*/ +bool QItemSelectionModel::rowIntersectsSelection(int row, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + int left = sel.at(i).left(); + int right = sel.at(i).right(); + if (top <= row && bottom >= row) { + for (int j = left; j <= right; j++) { + const Qt::ItemFlags flags = d->model->index(row, j, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return true; + } + } + } + + return false; +} + +/*! + Returns true if there are any items selected in the \a column with the given + \a parent. +*/ +bool QItemSelectionModel::columnIntersectsSelection(int column, const QModelIndex &parent) const +{ + Q_D(const QItemSelectionModel); + if (parent.isValid() && d->model != parent.model()) + return false; + + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + for (int i = 0; i < sel.count(); ++i) { + int left = sel.at(i).left(); + int right = sel.at(i).right(); + int top = sel.at(i).top(); + int bottom = sel.at(i).bottom(); + if (left <= column && right >= column) { + for (int j = top; j <= bottom; j++) { + const Qt::ItemFlags flags = d->model->index(j, column, parent).flags(); + if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) + return true; + } + } + } + + return false; +} + +/*! + \since 4.2 + + Returns true if the selection model contains any selection ranges; + otherwise returns false. +*/ +bool QItemSelectionModel::hasSelection() const +{ + Q_D(const QItemSelectionModel); + if (d->currentCommand & (Toggle | Deselect)) { + QItemSelection sel = d->ranges; + sel.merge(d->currentSelection, d->currentCommand); + return !sel.isEmpty(); + } else { + return !(d->ranges.isEmpty() && d->currentSelection.isEmpty()); + } +} + +/*! + Returns a list of all selected model item indexes. The list contains no + duplicates, and is not sorted. +*/ +QModelIndexList QItemSelectionModel::selectedIndexes() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + return selected.indexes(); +} + +/*! + \since 4.2 + Returns the indexes in the given \a column for the rows where all columns are selected. + + \sa selectedIndexes(), selectedColumns() +*/ + +QModelIndexList QItemSelectionModel::selectedRows(int column) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and row number + QSet< QPair > rowsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int row = range.top(); row <= range.bottom(); row++) { + QPair rowDef = qMakePair(parent, row); + if (!rowsSeen.contains(rowDef)) { + rowsSeen << rowDef; + if (isRowSelected(row, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + \since 4.2 + Returns the indexes in the given \a row for columns where all rows are selected. + + \sa selectedIndexes(), selectedRows() +*/ + +QModelIndexList QItemSelectionModel::selectedColumns(int row) const +{ + QModelIndexList indexes; + //the QSet contains pairs of parent modelIndex + //and column number + QSet< QPair > columnsSeen; + + const QItemSelection ranges = selection(); + for (int i = 0; i < ranges.count(); ++i) { + const QItemSelectionRange &range = ranges.at(i); + QModelIndex parent = range.parent(); + for (int column = range.left(); column <= range.right(); column++) { + QPair columnDef = qMakePair(parent, column); + if (!columnsSeen.contains(columnDef)) { + columnsSeen << columnDef; + if (isColumnSelected(column, parent)) { + indexes.append(model()->index(row, column, parent)); + } + } + } + } + + return indexes; +} + +/*! + Returns the selection ranges stored in the selection model. +*/ +const QItemSelection QItemSelectionModel::selection() const +{ + Q_D(const QItemSelectionModel); + QItemSelection selected = d->ranges; + selected.merge(d->currentSelection, d->currentCommand); + int i = 0; + // make sure we have no invalid ranges + // ### should probably be handled more generic somewhere else + while (imodel; +} + +/*! + Compares the two selections \a newSelection and \a oldSelection + and emits selectionChanged() with the deselected and selected items. +*/ +void QItemSelectionModel::emitSelectionChanged(const QItemSelection &newSelection, + const QItemSelection &oldSelection) +{ + // if both selections are empty or equal we return + if ((oldSelection.isEmpty() && newSelection.isEmpty()) || + oldSelection == newSelection) + return; + + // if either selection is empty we do not need to compare + if (oldSelection.isEmpty() || newSelection.isEmpty()) { + emit selectionChanged(newSelection, oldSelection); + return; + } + + QItemSelection deselected = oldSelection; + QItemSelection selected = newSelection; + + // remove equal ranges + bool advance; + for (int o = 0; o < deselected.count(); ++o) { + advance = true; + for (int s = 0; s < selected.count() && o < deselected.count();) { + if (deselected.at(o) == selected.at(s)) { + deselected.removeAt(o); + selected.removeAt(s); + advance = false; + } else { + ++s; + } + } + if (advance) + ++o; + } + + // find intersections + QItemSelection intersections; + for (int o = 0; o < deselected.count(); ++o) { + for (int s = 0; s < selected.count(); ++s) { + if (deselected.at(o).intersects(selected.at(s))) + intersections.append(deselected.at(o).intersected(selected.at(s))); + } + } + + // compare remaining ranges with intersections and split them to find deselected and selected + for (int i = 0; i < intersections.count(); ++i) { + // split deselected + for (int o = 0; o < deselected.count();) { + if (deselected.at(o).intersects(intersections.at(i))) { + QItemSelection::split(deselected.at(o), intersections.at(i), &deselected); + deselected.removeAt(o); + } else { + ++o; + } + } + // split selected + for (int s = 0; s < selected.count();) { + if (selected.at(s).intersects(intersections.at(i))) { + QItemSelection::split(selected.at(s), intersections.at(i), &selected); + selected.removeAt(s); + } else { + ++s; + } + } + } + + if (!selected.isEmpty() || !deselected.isEmpty()) + emit selectionChanged(selected, deselected); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QItemSelectionRange &range) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QItemSelectionRange(" << range.topLeft() + << ',' << range.bottomRight() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QItemSelectionRange to QDebug"); + return dbg; + Q_UNUSED(range); +#endif +} +#endif + +QT_END_NAMESPACE + +#include "moc_qitemselectionmodel.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qitemselectionmodel.h b/src/gui/itemviews/qitemselectionmodel.h new file mode 100644 index 0000000000..12b875b8ab --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_H +#define QITEMSELECTIONMODEL_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class Q_GUI_EXPORT QItemSelectionRange +{ + +public: + inline QItemSelectionRange() {} + inline QItemSelectionRange(const QItemSelectionRange &other) + : tl(other.tl), br(other.br) {} + inline QItemSelectionRange(const QModelIndex &topLeft, const QModelIndex &bottomRight); + explicit inline QItemSelectionRange(const QModelIndex &index) + { tl = index; br = tl; } + + inline int top() const { return tl.row(); } + inline int left() const { return tl.column(); } + inline int bottom() const { return br.row(); } + inline int right() const { return br.column(); } + inline int width() const { return br.column() - tl.column() + 1; } + inline int height() const { return br.row() - tl.row() + 1; } + + inline QModelIndex topLeft() const { return QModelIndex(tl); } + inline QModelIndex bottomRight() const { return QModelIndex(br); } + inline QModelIndex parent() const { return tl.parent(); } + inline const QAbstractItemModel *model() const { return tl.model(); } + + inline bool contains(const QModelIndex &index) const + { + return (parent() == index.parent() + && tl.row() <= index.row() && tl.column() <= index.column() + && br.row() >= index.row() && br.column() >= index.column()); + } + + inline bool contains(int row, int column, const QModelIndex &parentIndex) const + { + return (parent() == parentIndex + && tl.row() <= row && tl.column() <= column + && br.row() >= row && br.column() >= column); + } + + bool intersects(const QItemSelectionRange &other) const; + QItemSelectionRange intersect(const QItemSelectionRange &other) const; // ### Qt 5: make QT4_SUPPORT + inline QItemSelectionRange intersected(const QItemSelectionRange &other) const + { return intersect(other); } + + inline bool operator==(const QItemSelectionRange &other) const + { return (tl == other.tl && br == other.br); } + inline bool operator!=(const QItemSelectionRange &other) const + { return !operator==(other); } + inline bool operator<(const QItemSelectionRange &other) const + { + // Comparing parents will compare the models, but if two equivalent ranges + // in two different models have invalid parents, they would appear the same + if (other.tl.model() == tl.model()) { + // parent has to be calculated, so we only do so once. + const QModelIndex topLeftParent = tl.parent(); + const QModelIndex otherTopLeftParent = other.tl.parent(); + if (topLeftParent == otherTopLeftParent) { + if (other.tl.row() == tl.row()) { + if (other.tl.column() == tl.column()) { + if (other.br.row() == br.row()) { + return br.column() < other.br.column(); + } + return br.row() < other.br.row(); + } + return tl.column() < other.tl.column(); + } + return tl.row() < other.tl.row(); + } + return topLeftParent < otherTopLeftParent; + } + return tl.model() < other.tl.model(); + } + + inline bool isValid() const + { + return (tl.isValid() && br.isValid() && tl.parent() == br.parent() + && top() <= bottom() && left() <= right()); + } + + bool isEmpty() const; + + QModelIndexList indexes() const; + +private: + QPersistentModelIndex tl, br; +}; +Q_DECLARE_TYPEINFO(QItemSelectionRange, Q_MOVABLE_TYPE); + +inline QItemSelectionRange::QItemSelectionRange(const QModelIndex &atopLeft, + const QModelIndex &abottomRight) +{ tl = atopLeft; br = abottomRight; } + +class QItemSelection; +class QItemSelectionModelPrivate; + +class Q_GUI_EXPORT QItemSelectionModel : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QItemSelectionModel) + Q_FLAGS(SelectionFlags) + +public: + + enum SelectionFlag { + NoUpdate = 0x0000, + Clear = 0x0001, + Select = 0x0002, + Deselect = 0x0004, + Toggle = 0x0008, + Current = 0x0010, + Rows = 0x0020, + Columns = 0x0040, + SelectCurrent = Select | Current, + ToggleCurrent = Toggle | Current, + ClearAndSelect = Clear | Select + }; + + Q_DECLARE_FLAGS(SelectionFlags, SelectionFlag) + + explicit QItemSelectionModel(QAbstractItemModel *model); + explicit QItemSelectionModel(QAbstractItemModel *model, QObject *parent); + virtual ~QItemSelectionModel(); + + QModelIndex currentIndex() const; + + bool isSelected(const QModelIndex &index) const; + bool isRowSelected(int row, const QModelIndex &parent) const; + bool isColumnSelected(int column, const QModelIndex &parent) const; + + bool rowIntersectsSelection(int row, const QModelIndex &parent) const; + bool columnIntersectsSelection(int column, const QModelIndex &parent) const; + + bool hasSelection() const; + + QModelIndexList selectedIndexes() const; + QModelIndexList selectedRows(int column = 0) const; + QModelIndexList selectedColumns(int row = 0) const; + const QItemSelection selection() const; + + const QAbstractItemModel *model() const; + +public Q_SLOTS: + void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command); + virtual void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command); + virtual void clear(); + virtual void reset(); + + void clearSelection(); + +Q_SIGNALS: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentColumnChanged(const QModelIndex ¤t, const QModelIndex &previous); + +protected: + QItemSelectionModel(QItemSelectionModelPrivate &dd, QAbstractItemModel *model); + void emitSelectionChanged(const QItemSelection &newSelection, const QItemSelection &oldSelection); + +private: + Q_DISABLE_COPY(QItemSelectionModel) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeRemoved(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_columnsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_rowsAboutToBeInserted(const QModelIndex&, int, int)) + Q_PRIVATE_SLOT(d_func(), void _q_layoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QItemSelectionModel::SelectionFlags) + +// dummy implentation of qHash() necessary for instantiating QList::toSet() with MSVC +inline uint qHash(const QItemSelectionRange &) { return 0; } + +class Q_GUI_EXPORT QItemSelection : public QList +{ +public: + QItemSelection() {} + QItemSelection(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void select(const QModelIndex &topLeft, const QModelIndex &bottomRight); + bool contains(const QModelIndex &index) const; + QModelIndexList indexes() const; + void merge(const QItemSelection &other, QItemSelectionModel::SelectionFlags command); + static void split(const QItemSelectionRange &range, + const QItemSelectionRange &other, + QItemSelection *result); +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QItemSelectionRange &); +#endif + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QITEMSELECTIONMODEL_H diff --git a/src/gui/itemviews/qitemselectionmodel_p.h b/src/gui/itemviews/qitemselectionmodel_p.h new file mode 100644 index 0000000000..1574864477 --- /dev/null +++ b/src/gui/itemviews/qitemselectionmodel_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QITEMSELECTIONMODEL_P_H +#define QITEMSELECTIONMODEL_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/qobject_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_ITEMVIEWS +class QItemSelectionModelPrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QItemSelectionModel) +public: + QItemSelectionModelPrivate() + : model(0), + currentCommand(QItemSelectionModel::NoUpdate), + tableSelected(false), tableColCount(0), tableRowCount(0) {} + + QItemSelection expandSelection(const QItemSelection &selection, + QItemSelectionModel::SelectionFlags command) const; + + void initModel(QAbstractItemModel *model); + + void _q_rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void _q_rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_columnsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void _q_layoutAboutToBeChanged(); + void _q_layoutChanged(); + + inline void remove(QList &r) + { + QList::const_iterator it = r.constBegin(); + for (; it != r.constEnd(); ++it) + ranges.removeAll(*it); + } + + inline void finalize() + { + ranges.merge(currentSelection, currentCommand); + if (!currentSelection.isEmpty()) // ### perhaps this should be in QList + currentSelection.clear(); + } + + QPointer model; + QItemSelection ranges; + QItemSelection currentSelection; + QPersistentModelIndex currentIndex; + QItemSelectionModel::SelectionFlags currentCommand; + QList savedPersistentIndexes; + QList savedPersistentCurrentIndexes; + // optimization when all indexes are selected + bool tableSelected; + QPersistentModelIndex tableParent; + int tableColCount, tableRowCount; +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +#endif // QITEMSELECTIONMODEL_P_H diff --git a/src/gui/itemviews/qlistview.cpp b/src/gui/itemviews/qlistview.cpp new file mode 100644 index 0000000000..44b45d835d --- /dev/null +++ b/src/gui/itemviews/qlistview.cpp @@ -0,0 +1,3212 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlistview.h" + +#ifndef QT_NO_LISTVIEW +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QListView + + \brief The QListView class provides a list or icon view onto a model. + + \ingroup model-view + \ingroup advanced + + + A QListView presents items stored in a model, either as a simple + non-hierarchical list, or as a collection of icons. This class is used + to provide lists and icon views that were previously provided by the + \c QListBox and \c QIconView classes, but using the more flexible + approach provided by Qt's model/view architecture. + + The QListView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + This view does not display horizontal or vertical headers; to display + a list of items with a horizontal header, use QTreeView instead. + + QListView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + Items in a list view can be displayed using one of two view modes: + In \l ListMode, the items are displayed in the form of a simple list; + in \l IconMode, the list view takes the form of an \e{icon view} in + which the items are displayed with icons like files in a file manager. + By default, the list view is in \l ListMode. To change the view mode, + use the setViewMode() function, and to determine the current view mode, + use viewMode(). + + Items in these views are laid out in the direction specified by the + flow() of the list view. The items may be fixed in place, or allowed + to move, depending on the view's movement() state. + + If the items in the model cannot be completely laid out in the + direction of flow, they can be wrapped at the boundary of the view + widget; this depends on isWrapping(). This property is useful when the + items are being represented by an icon view. + + The resizeMode() and layoutMode() govern how and when the items are + laid out. Items are spaced according to their spacing(), and can exist + within a notional grid of size specified by gridSize(). The items can + be rendered as large or small icons depending on their iconSize(). + + \table 100% + \row \o \inlineimage windowsxp-listview.png Screenshot of a Windows XP style list view + \o \inlineimage macintosh-listview.png Screenshot of a Macintosh style table view + \o \inlineimage plastique-listview.png Screenshot of a Plastique style table view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} list view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} list view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} list view. + \endtable + + \section1 Improving Performance + + It is possible to give the view hints about the data it is handling in order + to improve its performance when displaying large numbers of items. One approach + that can be taken for views that are intended to display items with equal sizes + is to set the \l uniformItemSizes property to true. + + \sa {View Classes}, QTreeView, QTableView, QListWidget +*/ + +/*! + \enum QListView::ViewMode + + \value ListMode The items are laid out using TopToBottom flow, with Small size and Static movement + \value IconMode The items are laid out using LeftToRight flow, with Large size and Free movement +*/ + +/*! + \enum QListView::Movement + + \value Static The items cannot be moved by the user. + \value Free The items can be moved freely by the user. + \value Snap The items snap to the specified grid when moved; see + setGridSize(). +*/ + +/*! + \enum QListView::Flow + + \value LeftToRight The items are laid out in the view from the left + to the right. + \value TopToBottom The items are laid out in the view from the top + to the bottom. +*/ + +/*! + \enum QListView::ResizeMode + + \value Fixed The items will only be laid out the first time the view is shown. + \value Adjust The items will be laid out every time the view is resized. +*/ + +/*! + \enum QListView::LayoutMode + + \value SinglePass The items are laid out all at once. + \value Batched The items are laid out in batches of \l batchSize items. + \sa batchSize +*/ + +/*! + \since 4.2 + \fn void QListView::indexesMoved(const QModelIndexList &indexes) + + This signal is emitted when the specified \a indexes are moved in the view. +*/ + +/*! + Creates a new QListView with the given \a parent to view a model. + Use setModel() to set the model. +*/ +QListView::QListView(QWidget *parent) + : QAbstractItemView(*new QListViewPrivate, parent) +{ + setViewMode(ListMode); + setSelectionMode(SingleSelection); + setAttribute(Qt::WA_MacShowFocusRect); + Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change + d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed +} + +/*! + \internal +*/ +QListView::QListView(QListViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + setViewMode(ListMode); + setSelectionMode(SingleSelection); + setAttribute(Qt::WA_MacShowFocusRect); + Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change + d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed +} + +/*! + Destroys the view. +*/ +QListView::~QListView() +{ +} + +/*! + \property QListView::movement + \brief whether the items can be moved freely, are snapped to a + grid, or cannot be moved at all. + + This property determines how the user can move the items in the + view. \l Static means that the items can't be moved the user. \l + Free means that the user can drag and drop the items to any + position in the view. \l Snap means that the user can drag and + drop the items, but only to the positions in a notional grid + signified by the gridSize property. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is set to \l Static. + + \sa gridSize, resizeMode, viewMode +*/ +void QListView::setMovement(Movement movement) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Movement); + d->movement = movement; + +#ifndef QT_NO_DRAGANDDROP + bool movable = (movement != Static); + setDragEnabled(movable); + d->viewport->setAcceptDrops(movable); +#endif + d->doDelayedItemsLayout(); +} + +QListView::Movement QListView::movement() const +{ + Q_D(const QListView); + return d->movement; +} + +/*! + \property QListView::flow + \brief which direction the items layout should flow. + + If this property is \l LeftToRight, the items will be laid out left + to right. If the \l isWrapping property is true, the layout will wrap + when it reaches the right side of the visible area. If this + property is \l TopToBottom, the items will be laid out from the top + of the visible area, wrapping when it reaches the bottom. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is set to \l TopToBottom. + + \sa viewMode +*/ +void QListView::setFlow(Flow flow) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Flow); + d->flow = flow; + d->doDelayedItemsLayout(); +} + +QListView::Flow QListView::flow() const +{ + Q_D(const QListView); + return d->flow; +} + +/*! + \property QListView::isWrapping + \brief whether the items layout should wrap. + + This property holds whether the layout should wrap when there is + no more space in the visible area. The point at which the layout wraps + depends on the \l flow property. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property is false. + + \sa viewMode +*/ +void QListView::setWrapping(bool enable) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Wrap); + d->setWrapping(enable); + d->doDelayedItemsLayout(); +} + +bool QListView::isWrapping() const +{ + Q_D(const QListView); + return d->isWrapping(); +} + +/*! + \property QListView::resizeMode + \brief whether the items are laid out again when the view is resized. + + If this property is \l Adjust, the items will be laid out again + when the view is resized. If the value is \l Fixed, the items will + not be laid out when the view is resized. + + By default, this property is set to \l Fixed. + + \sa movement, gridSize, viewMode +*/ +void QListView::setResizeMode(ResizeMode mode) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::ResizeMode); + d->resizeMode = mode; +} + +QListView::ResizeMode QListView::resizeMode() const +{ + Q_D(const QListView); + return d->resizeMode; +} + +/*! + \property QListView::layoutMode + \brief determines whether the layout of items should happen immediately or be delayed. + + This property holds the layout mode for the items. When the mode + is \l SinglePass (the default), the items are laid out all in one go. + When the mode is \l Batched, the items are laid out in batches of \l batchSize + items, while processing events. This makes it possible to + instantly view and interact with the visible items while the rest + are being laid out. + + \sa viewMode +*/ +void QListView::setLayoutMode(LayoutMode mode) +{ + Q_D(QListView); + d->layoutMode = mode; +} + +QListView::LayoutMode QListView::layoutMode() const +{ + Q_D(const QListView); + return d->layoutMode; +} + +/*! + \property QListView::spacing + \brief the space around the items in the layout + + This property is the size of the empty space that is padded around + an item in the layout. + + Setting this property when the view is visible will cause the + items to be laid out again. + + By default, this property contains a value of 0. + + \sa viewMode +*/ +// ### Qt5: Use same semantic as layouts (spacing is the size of space +// *between* items) +void QListView::setSpacing(int space) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::Spacing); + d->setSpacing(space); + d->doDelayedItemsLayout(); +} + +int QListView::spacing() const +{ + Q_D(const QListView); + return d->spacing(); +} + +/*! + \property QListView::batchSize + \brief the number of items laid out in each batch if \l layoutMode is + set to \l Batched + + The default value is 100. + + \since 4.2 +*/ + +void QListView::setBatchSize(int batchSize) +{ + Q_D(QListView); + if (batchSize <= 0) { + qWarning("Invalid batchSize (%d)", batchSize); + return; + } + d->batchSize = batchSize; +} + +int QListView::batchSize() const +{ + Q_D(const QListView); + return d->batchSize; +} + +/*! + \property QListView::gridSize + \brief the size of the layout grid + + This property is the size of the grid in which the items are laid + out. The default is an empty size which means that there is no + grid and the layout is not done in a grid. Setting this property + to a non-empty size switches on the grid layout. (When a grid + layout is in force the \l spacing property is ignored.) + + Setting this property when the view is visible will cause the + items to be laid out again. + + \sa viewMode +*/ +void QListView::setGridSize(const QSize &size) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::GridSize); + d->setGridSize(size); + d->doDelayedItemsLayout(); +} + +QSize QListView::gridSize() const +{ + Q_D(const QListView); + return d->gridSize(); +} + +/*! + \property QListView::viewMode + \brief the view mode of the QListView. + + This property will change the other unset properties to conform + with the set view mode. QListView-specific properties that have already been set + will not be changed, unless clearPropertyFlags() has been called. + + Setting the view mode will enable or disable drag and drop based on the + selected movement. For ListMode, the default movement is \l Static + (drag and drop disabled); for IconMode, the default movement is + \l Free (drag and drop enabled). + + \sa isWrapping, spacing, gridSize, flow, movement, resizeMode +*/ +void QListView::setViewMode(ViewMode mode) +{ + Q_D(QListView); + if (d->commonListView && d->viewMode == mode) + return; + d->viewMode = mode; + + delete d->commonListView; + if (mode == ListMode) { + d->commonListView = new QListModeViewBase(this, d); + if (!(d->modeProperties & QListViewPrivate::Wrap)) + d->setWrapping(false); + if (!(d->modeProperties & QListViewPrivate::Spacing)) + d->setSpacing(0); + if (!(d->modeProperties & QListViewPrivate::GridSize)) + d->setGridSize(QSize()); + if (!(d->modeProperties & QListViewPrivate::Flow)) + d->flow = TopToBottom; + if (!(d->modeProperties & QListViewPrivate::Movement)) + d->movement = Static; + if (!(d->modeProperties & QListViewPrivate::ResizeMode)) + d->resizeMode = Fixed; + if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) + d->showElasticBand = false; + } else { + d->commonListView = new QIconModeViewBase(this, d); + if (!(d->modeProperties & QListViewPrivate::Wrap)) + d->setWrapping(true); + if (!(d->modeProperties & QListViewPrivate::Spacing)) + d->setSpacing(0); + if (!(d->modeProperties & QListViewPrivate::GridSize)) + d->setGridSize(QSize()); + if (!(d->modeProperties & QListViewPrivate::Flow)) + d->flow = LeftToRight; + if (!(d->modeProperties & QListViewPrivate::Movement)) + d->movement = Free; + if (!(d->modeProperties & QListViewPrivate::ResizeMode)) + d->resizeMode = Fixed; + if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) + d->showElasticBand = true; + } + +#ifndef QT_NO_DRAGANDDROP + bool movable = (d->movement != Static); + setDragEnabled(movable); + setAcceptDrops(movable); +#endif + d->clear(); + d->doDelayedItemsLayout(); +} + +QListView::ViewMode QListView::viewMode() const +{ + Q_D(const QListView); + return d->viewMode; +} + +/*! + Clears the QListView-specific property flags. See \l{viewMode}. + + Properties inherited from QAbstractItemView are not covered by the + property flags. Specifically, \l{QAbstractItemView::dragEnabled} + {dragEnabled} and \l{QAbstractItemView::acceptDrops} + {acceptsDrops} are computed by QListView when calling + setMovement() or setViewMode(). +*/ +void QListView::clearPropertyFlags() +{ + Q_D(QListView); + d->modeProperties = 0; +} + +/*! + Returns true if the \a row is hidden; otherwise returns false. +*/ +bool QListView::isRowHidden(int row) const +{ + Q_D(const QListView); + return d->isHidden(row); +} + +/*! + If \a hide is true, the given \a row will be hidden; otherwise + the \a row will be shown. +*/ +void QListView::setRowHidden(int row, bool hide) +{ + Q_D(QListView); + const bool hidden = d->isHidden(row); + if (hide && !hidden) + d->commonListView->appendHiddenRow(row); + else if (!hide && hidden) + d->commonListView->removeHiddenRow(row); + d->doDelayedItemsLayout(); + d->viewport->update(); +} + +/*! + \reimp +*/ +QRect QListView::visualRect(const QModelIndex &index) const +{ + Q_D(const QListView); + return d->mapToViewport(rectForIndex(index)); +} + +/*! + \reimp +*/ +void QListView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QListView); + + if (index.parent() != d->root || index.column() != d->column) + return; + + const QRect rect = visualRect(index); + if (hint == EnsureVisible && d->viewport->rect().contains(rect)) { + d->viewport->update(rect); + return; + } + + if (d->flow == QListView::TopToBottom || d->isWrapping()) // vertical + verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint)); + + if (d->flow == QListView::LeftToRight || d->isWrapping()) // horizontal + horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint)); +} + +int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const +{ + Q_Q(const QListView); + const QRect area = viewport->rect(); + const bool leftOf = q->isRightToLeft() + ? (rect.left() < area.left()) && (rect.right() < area.right()) + : rect.left() < area.left(); + const bool rightOf = q->isRightToLeft() + ? rect.right() > area.right() + : (rect.right() > area.right()) && (rect.left() > area.left()); + return commonListView->horizontalScrollToValue(q->visualIndex(index), hint, leftOf, rightOf, area, rect); +} + +int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const +{ + Q_Q(const QListView); + const QRect area = viewport->rect(); + const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); + const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); + return commonListView->verticalScrollToValue(q->visualIndex(index), hint, above, below, area, rect); +} + +void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) +{ + if (!selectionModel) + return; + + QItemSelection selection; + QModelIndex topLeft; + int row = 0; + const int colCount = model->columnCount(root); + for(; row < model->rowCount(root); ++row) { + if (isHidden(row)) { + //it might be the end of a selection range + if (topLeft.isValid()) { + QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + topLeft = QModelIndex(); + } + continue; + } + + if (!topLeft.isValid()) //start of a new selection range + topLeft = model->index(row, 0, root); + } + + if (topLeft.isValid()) { + //last selected range + QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + + if (!selection.isEmpty()) + selectionModel->select(selection, command); +} + +/*! + \reimp + + We have a QListView way of knowing what elements are on the viewport + through the intersectingSet function +*/ +QItemViewPaintPairs QListViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const +{ + Q_ASSERT(r); + Q_Q(const QListView); + QRect &rect = *r; + const QRect viewportRect = viewport->rect(); + QItemViewPaintPairs ret; + const QSet visibleIndexes = intersectingSet(viewportRect).toList().toSet(); + for (int i = 0; i < indexes.count(); ++i) { + const QModelIndex &index = indexes.at(i); + if (visibleIndexes.contains(index)) { + const QRect current = q->visualRect(index); + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + +/*! + \internal +*/ +void QListView::reset() +{ + Q_D(QListView); + d->clear(); + d->hiddenRows.clear(); + QAbstractItemView::reset(); +} + +/*! + \internal +*/ +void QListView::setRootIndex(const QModelIndex &index) +{ + Q_D(QListView); + d->column = qBound(0, d->column, d->model->columnCount(index) - 1); + QAbstractItemView::setRootIndex(index); + // sometimes we get an update before reset() is called + d->clear(); + d->hiddenRows.clear(); +} + +/*! + \internal + + Scroll the view contents by \a dx and \a dy. +*/ + +void QListView::scrollContentsBy(int dx, int dy) +{ + Q_D(QListView); + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + d->commonListView->scrollContentsBy(dx, dy, d->state == QListView::DragSelectingState); +} + +/*! + \internal + + Resize the internal contents to \a width and \a height and set the + scroll bar ranges accordingly. +*/ +void QListView::resizeContents(int width, int height) +{ + Q_D(QListView); + d->setContentsSize(width, height); +} + +/*! + \internal +*/ +QSize QListView::contentsSize() const +{ + Q_D(const QListView); + return d->contentsSize(); +} + +/*! + \reimp +*/ +void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + d_func()->commonListView->dataChanged(topLeft, bottomRight); + QAbstractItemView::dataChanged(topLeft, bottomRight); +} + +/*! + \reimp +*/ +void QListView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_D(QListView); + // ### be smarter about inserted items + d->clear(); + d->doDelayedItemsLayout(); + QAbstractItemView::rowsInserted(parent, start, end); +} + +/*! + \reimp +*/ +void QListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QListView); + // if the parent is above d->root in the tree, nothing will happen + QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); + if (parent == d->root) { + QSet::iterator it = d->hiddenRows.begin(); + while (it != d->hiddenRows.end()) { + int hiddenRow = it->row(); + if (hiddenRow >= start && hiddenRow <= end) { + it = d->hiddenRows.erase(it); + } else { + ++it; + } + } + } + d->clear(); + d->doDelayedItemsLayout(); +} + +/*! + \reimp +*/ +void QListView::mouseMoveEvent(QMouseEvent *e) +{ + if (!isVisible()) + return; + Q_D(QListView); + QAbstractItemView::mouseMoveEvent(e); + if (state() == DragSelectingState + && d->showElasticBand + && d->selectionMode != SingleSelection + && d->selectionMode != NoSelection) { + QRect rect(d->pressedPosition, e->pos() + QPoint(horizontalOffset(), verticalOffset())); + rect = rect.normalized(); + d->viewport->update(d->mapToViewport(rect.united(d->elasticBand))); + d->elasticBand = rect; + } +} + +/*! + \reimp +*/ +void QListView::mouseReleaseEvent(QMouseEvent *e) +{ + Q_D(QListView); + QAbstractItemView::mouseReleaseEvent(e); + // #### move this implementation into a dynamic class + if (d->showElasticBand && d->elasticBand.isValid()) { + d->viewport->update(d->mapToViewport(d->elasticBand)); + d->elasticBand = QRect(); + } +} + +/*! + \reimp +*/ +void QListView::timerEvent(QTimerEvent *e) +{ + Q_D(QListView); + if (e->timerId() == d->batchLayoutTimer.timerId()) { + if (d->doItemsLayout(d->batchSize)) { // layout is done + d->batchLayoutTimer.stop(); + updateGeometries(); + d->viewport->update(); + } + } + QAbstractItemView::timerEvent(e); +} + +/*! + \reimp +*/ +void QListView::resizeEvent(QResizeEvent *e) +{ + Q_D(QListView); + if (d->delayedPendingLayout) + return; + + QSize delta = e->size() - e->oldSize(); + + if (delta.isNull()) + return; + + bool listWrap = (d->viewMode == ListMode) && d->wrapItemText; + bool flowDimensionChanged = (d->flow == LeftToRight && delta.width() != 0) + || (d->flow == TopToBottom && delta.height() != 0); + + // We post a delayed relayout in the following cases : + // - we're wrapping + // - the state is NoState, we're adjusting and the size has changed in the flowing direction + if (listWrap + || (state() == NoState && d->resizeMode == Adjust && flowDimensionChanged)) { + d->doDelayedItemsLayout(100); // wait 1/10 sec before starting the layout + } else { + QAbstractItemView::resizeEvent(e); + } +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + \reimp +*/ +void QListView::dragMoveEvent(QDragMoveEvent *e) +{ + Q_D(QListView); + if (!d->commonListView->filterDragMoveEvent(e)) { + if (viewMode() == QListView::ListMode && flow() == QListView::LeftToRight) + static_cast(d->commonListView)->dragMoveEvent(e); + else + QAbstractItemView::dragMoveEvent(e); + } +} + + +/*! + \reimp +*/ +void QListView::dragLeaveEvent(QDragLeaveEvent *e) +{ + if (!d_func()->commonListView->filterDragLeaveEvent(e)) + QAbstractItemView::dragLeaveEvent(e); +} + +/*! + \reimp +*/ +void QListView::dropEvent(QDropEvent *e) +{ + if (!d_func()->commonListView->filterDropEvent(e)) + QAbstractItemView::dropEvent(e); +} + +/*! + \reimp +*/ +void QListView::startDrag(Qt::DropActions supportedActions) +{ + if (!d_func()->commonListView->filterStartDrag(supportedActions)) + QAbstractItemView::startDrag(supportedActions); +} + +/*! + \internal + + Called whenever items from the view is dropped on the viewport. + The \a event provides additional information. +*/ +void QListView::internalDrop(QDropEvent *event) +{ + // ### Qt5: remove that function + Q_UNUSED(event); +} + +/*! + \internal + + Called whenever the user starts dragging items and the items are movable, + enabling internal dragging and dropping of items. +*/ +void QListView::internalDrag(Qt::DropActions supportedActions) +{ + // ### Qt5: remove that function + Q_UNUSED(supportedActions); +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \reimp +*/ +QStyleOptionViewItem QListView::viewOptions() const +{ + Q_D(const QListView); + QStyleOptionViewItem option = QAbstractItemView::viewOptions(); + if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview + int pm = (d->viewMode == ListMode + ? style()->pixelMetric(QStyle::PM_ListViewIconSize, 0, this) + : style()->pixelMetric(QStyle::PM_IconViewIconSize, 0, this)); + option.decorationSize = QSize(pm, pm); + } + if (d->viewMode == IconMode) { + option.showDecorationSelected = false; + option.decorationPosition = QStyleOptionViewItem::Top; + option.displayAlignment = Qt::AlignCenter; + } else { + option.decorationPosition = QStyleOptionViewItem::Left; + } + return option; +} + + +/*! + \reimp +*/ +void QListView::paintEvent(QPaintEvent *e) +{ + Q_D(QListView); + if (!d->itemDelegate) + return; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QPainter painter(d->viewport); + + const QVector toBeRendered = d->intersectingSet(e->rect().translated(horizontalOffset(), verticalOffset()), false); + + const QModelIndex current = currentIndex(); + const QModelIndex hover = d->hover; + const QAbstractItemModel *itemModel = d->model; + const QItemSelectionModel *selections = d->selectionModel; + const bool focus = (hasFocus() || d->viewport->hasFocus()) && current.isValid(); + const bool alternate = d->alternatingColors; + const QStyle::State state = option.state; + const QAbstractItemView::State viewState = this->state(); + const bool enabled = (state & QStyle::State_Enabled) != 0; + + bool alternateBase = false; + int previousRow = -2; // trigger the alternateBase adjustment on first pass + + int maxSize = (flow() == TopToBottom) + ? qMax(viewport()->size().width(), d->contentsSize().width()) - 2 * d->spacing() + : qMax(viewport()->size().height(), d->contentsSize().height()) - 2 * d->spacing(); + + QVector::const_iterator end = toBeRendered.constEnd(); + for (QVector::const_iterator it = toBeRendered.constBegin(); it != end; ++it) { + Q_ASSERT((*it).isValid()); + option.rect = visualRect(*it); + + if (flow() == TopToBottom) + option.rect.setWidth(qMin(maxSize, option.rect.width())); + else + option.rect.setHeight(qMin(maxSize, option.rect.height())); + + option.state = state; + if (selections && selections->isSelected(*it)) + option.state |= QStyle::State_Selected; + if (enabled) { + QPalette::ColorGroup cg; + if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) { + option.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else { + cg = QPalette::Normal; + } + option.palette.setCurrentColorGroup(cg); + } + if (focus && current == *it) { + option.state |= QStyle::State_HasFocus; + if (viewState == EditingState) + option.state |= QStyle::State_Editing; + } + if (*it == hover) + option.state |= QStyle::State_MouseOver; + else + option.state &= ~QStyle::State_MouseOver; + + if (alternate) { + int row = (*it).row(); + if (row != previousRow + 1) { + // adjust alternateBase according to rows in the "gap" + if (!d->hiddenRows.isEmpty()) { + for (int r = qMax(previousRow + 1, 0); r < row; ++r) { + if (!d->isHidden(r)) + alternateBase = !alternateBase; + } + } else { + alternateBase = (row & 1) != 0; + } + } + if (alternateBase) { + option.features |= QStyleOptionViewItemV2::Alternate; + } else { + option.features &= ~QStyleOptionViewItemV2::Alternate; + } + + // draw background of the item (only alternate row). rest of the background + // is provided by the delegate + QStyle::State oldState = option.state; + option.state &= ~QStyle::State_Selected; + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, &painter, this); + option.state = oldState; + + alternateBase = !alternateBase; + previousRow = row; + } + + d->delegateForIndex(*it)->paint(&painter, option, *it); + } + +#ifndef QT_NO_DRAGANDDROP + d->commonListView->paintDragDrop(&painter); +#endif + +#ifndef QT_NO_RUBBERBAND + // #### move this implementation into a dynamic class + if (d->showElasticBand && d->elasticBand.isValid()) { + QStyleOptionRubberBand opt; + opt.initFrom(this); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = d->mapToViewport(d->elasticBand, false).intersected( + d->viewport->rect().adjusted(-16, -16, 16, 16)); + painter.save(); + style()->drawControl(QStyle::CE_RubberBand, &opt, &painter); + painter.restore(); + } +#endif +} + +/*! + \reimp +*/ +QModelIndex QListView::indexAt(const QPoint &p) const +{ + Q_D(const QListView); + QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); + const QVector intersectVector = d->intersectingSet(rect); + QModelIndex index = intersectVector.count() > 0 + ? intersectVector.last() : QModelIndex(); + if (index.isValid() && visualRect(index).contains(p)) + return index; + return QModelIndex(); +} + +/*! + \reimp +*/ +int QListView::horizontalOffset() const +{ + return d_func()->commonListView->horizontalOffset(); +} + +/*! + \reimp +*/ +int QListView::verticalOffset() const +{ + return d_func()->commonListView->verticalOffset(); +} + +/*! + \reimp +*/ +QModelIndex QListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QListView); + Q_UNUSED(modifiers); + + QModelIndex current = currentIndex(); + if (!current.isValid()) { + int rowCount = d->model->rowCount(d->root); + if (!rowCount) + return QModelIndex(); + int row = 0; + while (row < rowCount && d->isHiddenOrDisabled(row)) + ++row; + if (row >= rowCount) + return QModelIndex(); + return d->model->index(row, d->column, d->root); + } + + const QRect initialRect = rectForIndex(current); + QRect rect = initialRect; + if (rect.isEmpty()) { + return d->model->index(0, d->column, d->root); + } + if (d->gridSize().isValid()) rect.setSize(d->gridSize()); + + QSize contents = d->contentsSize(); + QVector intersectVector; + + switch (cursorAction) { + case MoveLeft: + while (intersectVector.isEmpty()) { + rect.translate(-rect.width(), 0); + if (rect.right() <= 0) + return current; + if (rect.left() < 0) + rect.setLeft(0); + intersectVector = d->intersectingSet(rect); + d->removeCurrentAndDisabled(&intersectVector, current); + } + return d->closestIndex(initialRect, intersectVector); + case MoveRight: + while (intersectVector.isEmpty()) { + rect.translate(rect.width(), 0); + if (rect.left() >= contents.width()) + return current; + if (rect.right() > contents.width()) + rect.setRight(contents.width()); + intersectVector = d->intersectingSet(rect); + d->removeCurrentAndDisabled(&intersectVector, current); + } + return d->closestIndex(initialRect, intersectVector); + case MovePageUp: + // move current by (visibileRowCount - 1) items. + // rect.translate(0, -rect.height()); will happen in the switch fallthrough for MoveUp. + rect.moveTop(rect.top() - d->viewport->height() + 2 * rect.height()); + if (rect.top() < rect.height()) + rect.moveTop(rect.height()); + case MovePrevious: + case MoveUp: + while (intersectVector.isEmpty()) { + rect.translate(0, -rect.height()); + if (rect.bottom() <= 0) { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + int row = d->batchStartRow() - 1; + while (row >= 0 && d->isHiddenOrDisabled(row)) + --row; + if (row >= 0) + return d->model->index(row, d->column, d->root); + } +#endif + return current; + } + if (rect.top() < 0) + rect.setTop(0); + intersectVector = d->intersectingSet(rect); + d->removeCurrentAndDisabled(&intersectVector, current); + } + return d->closestIndex(initialRect, intersectVector); + case MovePageDown: + // move current by (visibileRowCount - 1) items. + // rect.translate(0, rect.height()); will happen in the switch fallthrough for MoveDown. + rect.moveTop(rect.top() + d->viewport->height() - 2 * rect.height()); + if (rect.bottom() > contents.height() - rect.height()) + rect.moveBottom(contents.height() - rect.height()); + case MoveNext: + case MoveDown: + while (intersectVector.isEmpty()) { + rect.translate(0, rect.height()); + if (rect.top() >= contents.height()) { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + int rowCount = d->model->rowCount(d->root); + int row = 0; + while (row < rowCount && d->isHiddenOrDisabled(row)) + ++row; + if (row < rowCount) + return d->model->index(row, d->column, d->root); + } +#endif + return current; + } + if (rect.bottom() > contents.height()) + rect.setBottom(contents.height()); + intersectVector = d->intersectingSet(rect); + d->removeCurrentAndDisabled(&intersectVector, current); + } + return d->closestIndex(initialRect, intersectVector); + case MoveHome: + return d->model->index(0, d->column, d->root); + case MoveEnd: + return d->model->index(d->batchStartRow() - 1, d->column, d->root);} + + return current; +} + +/*! + Returns the rectangle of the item at position \a index in the + model. The rectangle is in contents coordinates. + + \sa visualRect() +*/ +QRect QListView::rectForIndex(const QModelIndex &index) const +{ + return d_func()->rectForIndex(index); +} + +/*! + \since 4.1 + + Sets the contents position of the item at \a index in the model to the given + \a position. + If the list view's movement mode is Static or its view mode is ListView, + this function will have no effect. +*/ +void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index) +{ + Q_D(QListView); + if (d->movement == Static + || !d->isIndexValid(index) + || index.parent() != d->root + || index.column() != d->column) + return; + + d->executePostedLayout(); + d->commonListView->setPositionForIndex(position, index); +} + +/*! + \reimp +*/ +void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QListView); + if (!d->selectionModel) + return; + + // if we are wrapping, we can only selecte inside the contents rectangle + int w = qMax(d->contentsSize().width(), d->viewport->width()); + int h = qMax(d->contentsSize().height(), d->viewport->height()); + if (d->wrap && !QRect(0, 0, w, h).intersects(rect)) + return; + + QItemSelection selection; + + if (rect.width() == 1 && rect.height() == 1) { + const QVector intersectVector = d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset())); + QModelIndex tl; + if (!intersectVector.isEmpty()) + tl = intersectVector.last(); // special case for mouse press; only select the top item + if (tl.isValid() && d->isIndexEnabled(tl)) + selection.select(tl, tl); + } else { + if (state() == DragSelectingState) { // visual selection mode (rubberband selection) + selection = d->selection(rect.translated(horizontalOffset(), verticalOffset())); + } else { // logical selection mode (key and mouse click selection) + QModelIndex tl, br; + // get the first item + const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1); + QVector intersectVector = d->intersectingSet(topLeft); + if (!intersectVector.isEmpty()) + tl = intersectVector.last(); + // get the last item + const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1); + intersectVector = d->intersectingSet(bottomRight); + if (!intersectVector.isEmpty()) + br = intersectVector.last(); + + // get the ranges + if (tl.isValid() && br.isValid() + && d->isIndexEnabled(tl) + && d->isIndexEnabled(br)) { + QRect first = rectForIndex(tl); + QRect last = rectForIndex(br); + QRect middle; + if (d->flow == LeftToRight) { + QRect &top = first; + QRect &bottom = last; + // if bottom is above top, swap them + if (top.center().y() > bottom.center().y()) { + QRect tmp = top; + top = bottom; + bottom = tmp; + } + // if the rect are on differnet lines, expand + if (top.top() != bottom.top()) { + // top rectangle + if (isRightToLeft()) + top.setLeft(0); + else + top.setRight(contentsSize().width()); + // bottom rectangle + if (isRightToLeft()) + bottom.setRight(contentsSize().width()); + else + bottom.setLeft(0); + } else if (top.left() > bottom.right()) { + if (isRightToLeft()) + bottom.setLeft(top.right()); + else + bottom.setRight(top.left()); + } else { + if (isRightToLeft()) + top.setLeft(bottom.right()); + else + top.setRight(bottom.left()); + } + // middle rectangle + if (top.bottom() < bottom.top()) { + if (gridSize().isValid() && !gridSize().isNull()) + middle.setTop(top.top() + gridSize().height()); + else + middle.setTop(top.bottom() + 1); + middle.setLeft(qMin(top.left(), bottom.left())); + middle.setBottom(bottom.top() - 1); + middle.setRight(qMax(top.right(), bottom.right())); + } + } else { // TopToBottom + QRect &left = first; + QRect &right = last; + if (left.center().x() > right.center().x()) + qSwap(left, right); + + int ch = contentsSize().height(); + if (left.left() != right.left()) { + // left rectangle + if (isRightToLeft()) + left.setTop(0); + else + left.setBottom(ch); + + // top rectangle + if (isRightToLeft()) + right.setBottom(ch); + else + right.setTop(0); + // only set middle if the + middle.setTop(0); + middle.setBottom(ch); + if (gridSize().isValid() && !gridSize().isNull()) + middle.setLeft(left.left() + gridSize().width()); + else + middle.setLeft(left.right() + 1); + middle.setRight(right.left() - 1); + } else if (left.bottom() < right.top()) { + left.setBottom(right.top() - 1); + } else { + right.setBottom(left.top() - 1); + } + } + + // do the selections + QItemSelection topSelection = d->selection(first); + QItemSelection middleSelection = d->selection(middle); + QItemSelection bottomSelection = d->selection(last); + // merge + selection.merge(topSelection, QItemSelectionModel::Select); + selection.merge(middleSelection, QItemSelectionModel::Select); + selection.merge(bottomSelection, QItemSelectionModel::Select); + } + } + } + + d->selectionModel->select(selection, command); +} + +/*! + \reimp + + Since 4.7, the returned region only contains rectangles intersecting + (or included in) the viewport. +*/ +QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QListView); + // ### NOTE: this is a potential bottleneck in non-static mode + int c = d->column; + QRegion selectionRegion; + const QRect &viewportRect = d->viewport->rect(); + for (int i = 0; i < selection.count(); ++i) { + if (!selection.at(i).isValid()) + continue; + QModelIndex parent = selection.at(i).topLeft().parent(); + //we only display the children of the root in a listview + //we're not interested in the other model indexes + if (parent != d->root) + continue; + int t = selection.at(i).topLeft().row(); + int b = selection.at(i).bottomRight().row(); + if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items + for (int r = t; r <= b; ++r) { + const QRect &rect = visualRect(d->model->index(r, c, parent)); + if (viewportRect.intersects(rect)) + selectionRegion += rect; + } + } else { // in static mode, we can optimize a bit + while (t <= b && d->isHidden(t)) ++t; + while (b >= t && d->isHidden(b)) --b; + const QModelIndex top = d->model->index(t, c, parent); + const QModelIndex bottom = d->model->index(b, c, parent); + QRect rect(visualRect(top).topLeft(), + visualRect(bottom).bottomRight()); + if (viewportRect.intersects(rect)) + selectionRegion += rect; + } + } + + return selectionRegion; +} + +/*! + \reimp +*/ +QModelIndexList QListView::selectedIndexes() const +{ + Q_D(const QListView); + if (!d->selectionModel) + return QModelIndexList(); + + QModelIndexList viewSelected = d->selectionModel->selectedIndexes(); + for (int i = 0; i < viewSelected.count(); ++i) { + const QModelIndex &index = viewSelected.at(i); + if (!isIndexHidden(index) && index.parent() == d->root && index.column() == d->column) + ++i; + else + viewSelected.removeAt(i); + } + return viewSelected; +} + +/*! + \internal + + Layout the items according to the flow and wrapping properties. +*/ +void QListView::doItemsLayout() +{ + Q_D(QListView); + // showing the scroll bars will trigger a resize event, + // so we set the state to expanding to avoid + // triggering another layout + QAbstractItemView::State oldState = state(); + setState(ExpandingState); + if (d->model->columnCount(d->root) > 0) { // no columns means no contents + d->resetBatchStartRow(); + if (layoutMode() == SinglePass) + d->doItemsLayout(d->model->rowCount(d->root)); // layout everything + else if (!d->batchLayoutTimer.isActive()) { + if (!d->doItemsLayout(d->batchSize)) // layout is done + d->batchLayoutTimer.start(0, this); // do a new batch as fast as possible + } + } + QAbstractItemView::doItemsLayout(); + setState(oldState); // restoring the oldState +} + +/*! + \reimp +*/ +void QListView::updateGeometries() +{ + Q_D(QListView); + if (d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) { + horizontalScrollBar()->setRange(0, 0); + verticalScrollBar()->setRange(0, 0); + } else { + QModelIndex index = d->model->index(0, d->column, d->root); + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + QSize step = d->itemSize(option, index); + d->commonListView->updateHorizontalScrollBar(step); + d->commonListView->updateVerticalScrollBar(step); + } + + QAbstractItemView::updateGeometries(); + + // if the scroll bars are turned off, we resize the contents to the viewport + if (d->movement == Static && !d->isWrapping()) { + const QSize maxSize = maximumViewportSize(); + if (d->flow == TopToBottom) { + if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { + d->setContentsSize(maxSize.width(), contentsSize().height()); + horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway + } + } else { // LeftToRight + if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { + d->setContentsSize(contentsSize().width(), maxSize.height()); + verticalScrollBar()->setRange(0, 0); // we see all the contents anyway + } + } + } + +} + +/*! + \reimp +*/ +bool QListView::isIndexHidden(const QModelIndex &index) const +{ + Q_D(const QListView); + return (d->isHidden(index.row()) + && (index.parent() == d->root) + && index.column() == d->column); +} + +/*! + \property QListView::modelColumn + \brief the column in the model that is visible + + By default, this property contains 0, indicating that the first + column in the model will be shown. +*/ +void QListView::setModelColumn(int column) +{ + Q_D(QListView); + if (column < 0 || column >= d->model->columnCount(d->root)) + return; + d->column = column; + d->doDelayedItemsLayout(); +} + +int QListView::modelColumn() const +{ + Q_D(const QListView); + return d->column; +} + +/*! + \property QListView::uniformItemSizes + \brief whether all items in the listview have the same size + \since 4.1 + + This property should only be set to true if it is guaranteed that all items + in the view have the same size. This enables the view to do some + optimizations for performance purposes. + + By default, this property is false. +*/ +void QListView::setUniformItemSizes(bool enable) +{ + Q_D(QListView); + d->uniformItemSizes = enable; +} + +bool QListView::uniformItemSizes() const +{ + Q_D(const QListView); + return d->uniformItemSizes; +} + +/*! + \property QListView::wordWrap + \brief the item text word-wrapping policy + \since 4.2 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is false by default. + + Please note that even if wrapping is enabled, the cell will not be + expanded to make room for the text. It will print ellipsis for + text that cannot be shown, according to the view's + \l{QAbstractItemView::}{textElideMode}. +*/ +void QListView::setWordWrap(bool on) +{ + Q_D(QListView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + d->doDelayedItemsLayout(); +} + +bool QListView::wordWrap() const +{ + Q_D(const QListView); + return d->wrapItemText; +} + +/*! + \property QListView::selectionRectVisible + \brief if the selection rectangle should be visible + \since 4.3 + + If this property is true then the selection rectangle is visible; + otherwise it will be hidden. + + \note The selection rectangle will only be visible if the selection mode + is in a mode where more than one item can be selected; i.e., it will not + draw a selection rectangle if the selection mode is + QAbstractItemView::SingleSelection. + + By default, this property is false. +*/ +void QListView::setSelectionRectVisible(bool show) +{ + Q_D(QListView); + d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible); + d->setSelectionRectVisible(show); +} + +bool QListView::isSelectionRectVisible() const +{ + Q_D(const QListView); + return d->isSelectionRectVisible(); +} + +/*! + \reimp +*/ +bool QListView::event(QEvent *e) +{ + return QAbstractItemView::event(e); +} + +/* + * private object implementation + */ + +QListViewPrivate::QListViewPrivate() + : QAbstractItemViewPrivate(), + commonListView(0), + wrap(false), + space(0), + flow(QListView::TopToBottom), + movement(QListView::Static), + resizeMode(QListView::Fixed), + layoutMode(QListView::SinglePass), + viewMode(QListView::ListMode), + modeProperties(0), + column(0), + uniformItemSizes(false), + batchSize(100), + showElasticBand(false) +{ +} + +QListViewPrivate::~QListViewPrivate() +{ + delete commonListView; +} + +void QListViewPrivate::clear() +{ + // initialization of data structs + cachedItemSize = QSize(); + commonListView->clear(); +} + +void QListViewPrivate::prepareItemsLayout() +{ + Q_Q(QListView); + clear(); + + //take the size as if there were scrollbar in order to prevent scrollbar to blink + layoutBounds = QRect(QPoint(), q->maximumViewportSize()); + + int frameAroundContents = 0; + if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) + frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; + + // maximumViewportSize() already takes scrollbar into account if policy is + // Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy + // is Qt::ScrollBarAsNeeded + int verticalMargin = vbarpolicy==Qt::ScrollBarAsNeeded + ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, vbar) + frameAroundContents + : 0; + int horizontalMargin = hbarpolicy==Qt::ScrollBarAsNeeded + ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, hbar) + frameAroundContents + : 0; + + layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin); + + int rowCount = model->columnCount(root) <= 0 ? 0 : model->rowCount(root); + commonListView->setRowCount(rowCount); +} + +/*! + \internal +*/ +bool QListViewPrivate::doItemsLayout(int delta) +{ + int max = model->rowCount(root) - 1; + int first = batchStartRow(); + int last = qMin(first + delta - 1, max); + + if (first == 0) { + layoutChildren(); // make sure the viewport has the right size + prepareItemsLayout(); + } + + if (max < 0 || last < first) { + return true; // nothing to do + } + + QListViewLayoutInfo info; + info.bounds = layoutBounds; + info.grid = gridSize(); + info.spacing = (info.grid.isValid() ? 0 : spacing()); + info.first = first; + info.last = last; + info.wrap = isWrapping(); + info.flow = flow; + info.max = max; + + return commonListView->doBatchedItemLayout(info, max); +} + +QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const +{ + if (!index.isValid() || isHidden(index.row())) + return QListViewItem(); + + return commonListView->indexToListViewItem(index); +} + +QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const +{ + Q_Q(const QListView); + if (!rect.isValid()) + return rect; + + QRect result = extend ? commonListView->mapToViewport(rect) : rect; + int dx = -q->horizontalOffset(); + int dy = -q->verticalOffset(); + return result.adjusted(dx, dy, dx, dy); +} + +QModelIndex QListViewPrivate::closestIndex(const QRect &target, + const QVector &candidates) const +{ + int distance = 0; + int shortest = INT_MAX; + QModelIndex closest; + QVector::const_iterator it = candidates.begin(); + + for (; it != candidates.end(); ++it) { + if (!(*it).isValid()) + continue; + + const QRect indexRect = indexToListViewItem(*it).rect(); + + //if the center x (or y) position of an item is included in the rect of the other item, + //we define the distance between them as the difference in x (or y) of their respective center. + // Otherwise, we use the nahattan length between the 2 items + if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right()) + || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) { + //one item's center is at the vertical of the other + distance = qAbs(indexRect.center().y() - target.center().y()); + } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom()) + || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) { + //one item's center is at the vertical of the other + distance = qAbs(indexRect.center().x() - target.center().x()); + } else { + distance = (indexRect.center() - target.center()).manhattanLength(); + } + if (distance < shortest) { + shortest = distance; + closest = *it; + } + } + return closest; +} + +QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (!uniformItemSizes) { + const QAbstractItemDelegate *delegate = delegateForIndex(index); + return delegate ? delegate->sizeHint(option, index) : QSize(); + } + if (!cachedItemSize.isValid()) { // the last item is probaly the largest, so we use its size + int row = model->rowCount(root) - 1; + QModelIndex sample = model->index(row, column, root); + const QAbstractItemDelegate *delegate = delegateForIndex(sample); + cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize(); + } + return cachedItemSize; +} + +QItemSelection QListViewPrivate::selection(const QRect &rect) const +{ + QItemSelection selection; + QModelIndex tl, br; + const QVector intersectVector = intersectingSet(rect); + QVector::const_iterator it = intersectVector.begin(); + for (; it != intersectVector.end(); ++it) { + if (!tl.isValid() && !br.isValid()) { + tl = br = *it; + } else if ((*it).row() == (tl.row() - 1)) { + tl = *it; // expand current range + } else if ((*it).row() == (br.row() + 1)) { + br = (*it); // expand current range + } else { + selection.select(tl, br); // select current range + tl = br = *it; // start new range + } + } + + if (tl.isValid() && br.isValid()) + selection.select(tl, br); + else if (tl.isValid()) + selection.select(tl, tl); + else if (br.isValid()) + selection.select(br, br); + + return selection; +} + +#ifndef QT_NO_DRAGANDDROP +QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const +{ + if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) + return static_cast(commonListView)->position(pos, rect, idx); + else + return QAbstractItemViewPrivate::position(pos, rect, idx); +} + +bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) +{ + if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) + return static_cast(commonListView)->dropOn(event, dropRow, dropCol, dropIndex); + else + return QAbstractItemViewPrivate::dropOn(event, dropRow, dropCol, dropIndex); +} +#endif + +/* + * Common ListView Implementation +*/ + +void QCommonListViewBase::appendHiddenRow(int row) +{ + dd->hiddenRows.insert(dd->model->index(row, 0, qq->rootIndex())); +} + +void QCommonListViewBase::removeHiddenRow(int row) +{ + dd->hiddenRows.remove(dd->model->index(row, 0, qq->rootIndex())); +} + +void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step) +{ + horizontalScrollBar()->setSingleStep(step.width() + spacing()); + horizontalScrollBar()->setPageStep(viewport()->width()); + horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width()); +} + +void QCommonListViewBase::updateVerticalScrollBar(const QSize &step) +{ + verticalScrollBar()->setSingleStep(step.height() + spacing()); + verticalScrollBar()->setPageStep(viewport()->height()); + verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height()); +} + +void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/) +{ + dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy); +} + +int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint, + bool above, bool below, const QRect &area, const QRect &rect) const +{ + int verticalValue = verticalScrollBar()->value(); + QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); + if (hint == QListView::PositionAtTop || above) + verticalValue += adjusted.top(); + else if (hint == QListView::PositionAtBottom || below) + verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); + else if (hint == QListView::PositionAtCenter) + verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); + return verticalValue; +} + +int QCommonListViewBase::horizontalOffset() const +{ + return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value()); +} + +int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint, + bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const +{ + int horizontalValue = horizontalScrollBar()->value(); + if (isRightToLeft()) { + if (hint == QListView::PositionAtCenter) { + horizontalValue += ((area.width() - rect.width()) / 2) - rect.left(); + } else { + if (leftOf) + horizontalValue -= rect.left(); + else if (rightOf) + horizontalValue += qMin(rect.left(), area.width() - rect.right()); + } + } else { + if (hint == QListView::PositionAtCenter) { + horizontalValue += rect.left() - ((area.width()- rect.width()) / 2); + } else { + if (leftOf) + horizontalValue += rect.left(); + else if (rightOf) + horizontalValue += qMin(rect.left(), rect.right() - area.width()); + } + } + return horizontalValue; +} + +/* + * ListMode ListView Implementation +*/ + +#ifndef QT_NO_DRAGANDDROP +void QListModeViewBase::paintDragDrop(QPainter *painter) +{ + // FIXME: Until the we can provide a proper drop indicator + // in IconMode, it makes no sense to show it + dd->paintDropIndicator(painter); +} + +QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const +{ + QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; + if (!dd->overwrite) { + const int margin = 2; + if (pos.x() - rect.left() < margin) { + r = QAbstractItemView::AboveItem; // Visually, on the left + } else if (rect.right() - pos.x() < margin) { + r = QAbstractItemView::BelowItem; // Visually, on the right + } else if (rect.contains(pos, true)) { + r = QAbstractItemView::OnItem; + } + } else { + QRect touchingRect = rect; + touchingRect.adjust(-1, -1, 1, 1); + if (touchingRect.contains(pos, false)) { + r = QAbstractItemView::OnItem; + } + } + + if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled))) + r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; + + return r; +} + +void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event) +{ + if (qq->dragDropMode() == QAbstractItemView::InternalMove + && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction))) + return; + + // ignore by default + event->ignore(); + + // can't use indexAt, doesn't account for spacing. + QPoint p = event->pos(); + QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); + rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing()); + const QVector intersectVector = dd->intersectingSet(rect); + QModelIndex index = intersectVector.count() > 0 + ? intersectVector.last() : QModelIndex(); + dd->hover = index; + if (!dd->droppingOnItself(event, index) + && dd->canDecode(event)) { + + if (index.isValid() && dd->showDropIndicator) { + QRect rect = qq->visualRect(index); + dd->dropIndicatorPosition = position(event->pos(), rect, index); + // if spacing, should try to draw between items, not just next to item. + switch (dd->dropIndicatorPosition) { + case QAbstractItemView::AboveItem: + if (dd->isIndexDropEnabled(index.parent())) { + dd->dropIndicatorRect = QRect(rect.left()-dd->spacing(), rect.top(), 0, rect.height()); + event->accept(); + } else { + dd->dropIndicatorRect = QRect(); + } + break; + case QAbstractItemView::BelowItem: + if (dd->isIndexDropEnabled(index.parent())) { + dd->dropIndicatorRect = QRect(rect.right()+dd->spacing(), rect.top(), 0, rect.height()); + event->accept(); + } else { + dd->dropIndicatorRect = QRect(); + } + break; + case QAbstractItemView::OnItem: + if (dd->isIndexDropEnabled(index)) { + dd->dropIndicatorRect = rect; + event->accept(); + } else { + dd->dropIndicatorRect = QRect(); + } + break; + case QAbstractItemView::OnViewport: + dd->dropIndicatorRect = QRect(); + if (dd->isIndexDropEnabled(qq->rootIndex())) { + event->accept(); // allow dropping in empty areas + } + break; + } + } else { + dd->dropIndicatorRect = QRect(); + dd->dropIndicatorPosition = QAbstractItemView::OnViewport; + if (dd->isIndexDropEnabled(qq->rootIndex())) { + event->accept(); // allow dropping in empty areas + } + } + dd->viewport->update(); + } // can decode + + if (dd->shouldAutoScroll(event->pos())) + qq->startAutoScroll(); +} + +/*! + If the event hasn't already been accepted, determines the index to drop on. + + if (row == -1 && col == -1) + // append to this drop index + else + // place at row, col in drop index + + If it returns true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop. + \internal + */ +bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) +{ + if (event->isAccepted()) + return false; + + QModelIndex index; + if (dd->viewport->rect().contains(event->pos())) { + // can't use indexAt, doesn't account for spacing. + QPoint p = event->pos(); + QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); + rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing()); + const QVector intersectVector = dd->intersectingSet(rect); + index = intersectVector.count() > 0 + ? intersectVector.last() : QModelIndex(); + if (!index.isValid()) + index = dd->root; + } + + // If we are allowed to do the drop + if (dd->model->supportedDropActions() & event->dropAction()) { + int row = -1; + int col = -1; + if (index != dd->root) { + dd->dropIndicatorPosition = position(event->pos(), qq->visualRect(index), index); + switch (dd->dropIndicatorPosition) { + case QAbstractItemView::AboveItem: + row = index.row(); + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::BelowItem: + row = index.row() + 1; + col = index.column(); + index = index.parent(); + break; + case QAbstractItemView::OnItem: + case QAbstractItemView::OnViewport: + break; + } + } else { + dd->dropIndicatorPosition = QAbstractItemView::OnViewport; + } + *dropIndex = index; + *dropRow = row; + *dropCol = col; + if (!dd->droppingOnItself(event, index)) + return true; + } + return false; +} + +#endif //QT_NO_DRAGANDDROP + +void QListModeViewBase::updateVerticalScrollBar(const QSize &step) +{ + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem + && ((flow() == QListView::TopToBottom && !isWrapping()) + || (flow() == QListView::LeftToRight && isWrapping()))) { + const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).count() - 1; + if (steps > 0) { + const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping()); + verticalScrollBar()->setSingleStep(1); + verticalScrollBar()->setPageStep(pageSteps); + verticalScrollBar()->setRange(0, steps - pageSteps); + } else { + verticalScrollBar()->setRange(0, 0); + } + // } else if (vertical && d->isWrapping() && d->movement == Static) { + // ### wrapped scrolling in flow direction + } else { + QCommonListViewBase::updateVerticalScrollBar(step); + } +} + +void QListModeViewBase::updateHorizontalScrollBar(const QSize &step) +{ + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem + && ((flow() == QListView::TopToBottom && isWrapping()) + || (flow() == QListView::LeftToRight && !isWrapping()))) { + int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).count() - 1; + if (steps > 0) { + const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping()); + horizontalScrollBar()->setSingleStep(1); + horizontalScrollBar()->setPageStep(pageSteps); + horizontalScrollBar()->setRange(0, steps - pageSteps); + } else { + horizontalScrollBar()->setRange(0, 0); + } + } else { + QCommonListViewBase::updateHorizontalScrollBar(step); + } +} + +int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint, + bool above, bool below, const QRect &area, const QRect &rect) const +{ + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int value; + if (scrollValueMap.isEmpty()) + value = 0; + else + value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()), flowPositions.count() - 1); + if (above) + hint = QListView::PositionAtTop; + else if (below) + hint = QListView::PositionAtBottom; + if (hint == QListView::EnsureVisible) + return value; + + return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height()); + } + + return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect); +} + +int QListModeViewBase::horizontalOffset() const +{ + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + if (isWrapping()) { + if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) { + const int max = segmentPositions.count() - 1; + int currentValue = qBound(0, horizontalScrollBar()->value(), max); + int position = segmentPositions.at(currentValue); + int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max); + int maximum = segmentPositions.at(maximumValue); + return (isRightToLeft() ? maximum - position : position); + } + } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) { + int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value())); + int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum())); + return (isRightToLeft() ? maximum - position : position); + } + } + return QCommonListViewBase::horizontalOffset(); +} + +int QListModeViewBase::verticalOffset() const +{ + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + if (isWrapping()) { + if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) { + int value = verticalScrollBar()->value(); + if (value >= segmentPositions.count()) + return 0; + return segmentPositions.at(value) - spacing(); + } + } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) { + int value = verticalScrollBar()->value(); + if (value > scrollValueMap.count()) + return 0; + return flowPositions.at(scrollValueMap.at(value)) - spacing(); + } + } + return QCommonListViewBase::verticalOffset(); +} + +int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint, + bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const +{ + if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem) + return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect); + + int value; + if (scrollValueMap.isEmpty()) + value = 0; + else + value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.count() - 1); + if (leftOf) + hint = QListView::PositionAtTop; + else if (rightOf) + hint = QListView::PositionAtBottom; + if (hint == QListView::EnsureVisible) + return value; + + return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width()); +} + +void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) +{ + // ### reorder this logic + const int verticalValue = verticalScrollBar()->value(); + const int horizontalValue = horizontalScrollBar()->value(); + const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem); + const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem); + + if (isWrapping()) { + if (segmentPositions.isEmpty()) + return; + const int max = segmentPositions.count() - 1; + if (horizontal && flow() == QListView::TopToBottom && dx != 0) { + int currentValue = qBound(0, horizontalValue, max); + int previousValue = qBound(0, currentValue + dx, max); + int currentCoordinate = segmentPositions.at(currentValue) - spacing(); + int previousCoordinate = segmentPositions.at(previousValue) - spacing(); + dx = previousCoordinate - currentCoordinate; + } else if (vertical && flow() == QListView::LeftToRight && dy != 0) { + int currentValue = qBound(0, verticalValue, max); + int previousValue = qBound(0, currentValue + dy, max); + int currentCoordinate = segmentPositions.at(currentValue) - spacing(); + int previousCoordinate = segmentPositions.at(previousValue) - spacing(); + dy = previousCoordinate - currentCoordinate; + } + } else { + if (flowPositions.isEmpty()) + return; + const int max = scrollValueMap.count() - 1; + if (vertical && flow() == QListView::TopToBottom && dy != 0) { + int currentValue = qBound(0, verticalValue, max); + int previousValue = qBound(0, currentValue + dy, max); + int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); + int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); + dy = previousCoordinate - currentCoordinate; + } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) { + int currentValue = qBound(0, horizontalValue, max); + int previousValue = qBound(0, currentValue + dx, max); + int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); + int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); + dx = previousCoordinate - currentCoordinate; + } + } + QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); +} + +bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) +{ + doStaticLayout(info); + if (batchStartRow > max) { // stop items layout + flowPositions.resize(flowPositions.count()); + segmentPositions.resize(segmentPositions.count()); + segmentStartRows.resize(segmentStartRows.count()); + return true; // done + } + return false; // not done +} + +QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const +{ + if (flowPositions.isEmpty() + || segmentPositions.isEmpty() + || index.row() >= flowPositions.count()) + return QListViewItem(); + + const int segment = qBinarySearch(segmentStartRows, index.row(), + 0, segmentStartRows.count() - 1); + + + QStyleOptionViewItemV4 options = viewOptions(); + options.rect.setSize(contentsSize); + QSize size = (uniformItemSizes() && cachedItemSize().isValid()) + ? cachedItemSize() : itemSize(options, index); + + QPoint pos; + if (flow() == QListView::LeftToRight) { + pos.setX(flowPositions.at(index.row())); + pos.setY(segmentPositions.at(segment)); + } else { // TopToBottom + pos.setY(flowPositions.at(index.row())); + pos.setX(segmentPositions.at(segment)); + if (isWrapping()) { // make the items as wide as the segment + int right = (segment + 1 >= segmentPositions.count() + ? contentsSize.width() + : segmentPositions.at(segment + 1)); + size.setWidth(right - pos.x()); + } else { // make the items as wide as the viewport + size.setWidth(qMax(size.width(), viewport()->width())); + } + } + + return QListViewItem(QRect(pos, size), index.row()); +} + +QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info) +{ + int x, y; + if (info.first == 0) { + flowPositions.clear(); + segmentPositions.clear(); + segmentStartRows.clear(); + segmentExtents.clear(); + scrollValueMap.clear(); + x = info.bounds.left() + info.spacing; + y = info.bounds.top() + info.spacing; + segmentPositions.append(info.flow == QListView::LeftToRight ? y : x); + segmentStartRows.append(0); + } else if (info.wrap) { + if (info.flow == QListView::LeftToRight) { + x = batchSavedPosition; + y = segmentPositions.last(); + } else { // flow == QListView::TopToBottom + x = segmentPositions.last(); + y = batchSavedPosition; + } + } else { // not first and not wrap + if (info.flow == QListView::LeftToRight) { + x = batchSavedPosition; + y = info.bounds.top() + info.spacing; + } else { // flow == QListView::TopToBottom + x = info.bounds.left() + info.spacing; + y = batchSavedPosition; + } + } + return QPoint(x, y); +} + +/*! + \internal +*/ +void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info) +{ + const bool useItemSize = !info.grid.isValid(); + const QPoint topLeft = initStaticLayout(info); + QStyleOptionViewItemV4 option = viewOptions(); + option.rect = info.bounds; + option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing); + + // The static layout data structures are as follows: + // One vector contains the coordinate in the direction of layout flow. + // Another vector contains the coordinates of the segments. + // A third vector contains the index (model row) of the first item + // of each segment. + + int segStartPosition; + int segEndPosition; + int deltaFlowPosition; + int deltaSegPosition; + int deltaSegHint; + int flowPosition; + int segPosition; + + if (info.flow == QListView::LeftToRight) { + segStartPosition = info.bounds.left(); + segEndPosition = info.bounds.width(); + flowPosition = topLeft.x(); + segPosition = topLeft.y(); + deltaFlowPosition = info.grid.width(); // dx + deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy + deltaSegHint = info.grid.height(); + } else { // flow == QListView::TopToBottom + segStartPosition = info.bounds.top(); + segEndPosition = info.bounds.height(); + flowPosition = topLeft.y(); + segPosition = topLeft.x(); + deltaFlowPosition = info.grid.height(); // dy + deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx + deltaSegHint = info.grid.width(); + } + + for (int row = info.first; row <= info.last; ++row) { + if (isHidden(row)) { // ### + flowPositions.append(flowPosition); + } else { + // if we are not using a grid, we need to find the deltas + if (useItemSize) { + QSize hint = itemSize(option, modelIndex(row)); + if (info.flow == QListView::LeftToRight) { + deltaFlowPosition = hint.width() + info.spacing; + deltaSegHint = hint.height() + info.spacing; + } else { // TopToBottom + deltaFlowPosition = hint.height() + info.spacing; + deltaSegHint = hint.width() + info.spacing; + } + } + // create new segment + if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) { + segmentExtents.append(flowPosition); + flowPosition = info.spacing + segStartPosition; + segPosition += deltaSegPosition; + if (info.wrap) + segPosition += info.spacing; + segmentPositions.append(segPosition); + segmentStartRows.append(row); + deltaSegPosition = 0; + } + // save the flow position of this item + scrollValueMap.append(flowPositions.count()); + flowPositions.append(flowPosition); + // prepare for the next item + deltaSegPosition = qMax(deltaSegHint, deltaSegPosition); + flowPosition += info.spacing + deltaFlowPosition; + } + } + // used when laying out next batch + batchSavedPosition = flowPosition; + batchSavedDeltaSeg = deltaSegPosition; + batchStartRow = info.last + 1; + if (info.last == info.max) + flowPosition -= info.spacing; // remove extra spacing + // set the contents size + QRect rect = info.bounds; + if (info.flow == QListView::LeftToRight) { + rect.setRight(segmentPositions.count() == 1 ? flowPosition : info.bounds.right()); + rect.setBottom(segPosition + deltaSegPosition); + } else { // TopToBottom + rect.setRight(segPosition + deltaSegPosition); + rect.setBottom(segmentPositions.count() == 1 ? flowPosition : info.bounds.bottom()); + } + contentsSize = QSize(rect.right(), rect.bottom()); + // if it is the last batch, save the end of the segments + if (info.last == info.max) { + segmentExtents.append(flowPosition); + scrollValueMap.append(flowPositions.count()); + flowPositions.append(flowPosition); + segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX); + } + // if the new items are visble, update the viewport + QRect changedRect(topLeft, rect.bottomRight()); + if (clipRect().intersects(changedRect)) + viewport()->update(); +} + +/*! + \internal + Finds the set of items intersecting with \a area. + In this function, itemsize is counted from topleft to the start of the next item. +*/ +QVector QListModeViewBase::intersectingSet(const QRect &area) const +{ + QVector ret; + int segStartPosition; + int segEndPosition; + int flowStartPosition; + int flowEndPosition; + if (flow() == QListView::LeftToRight) { + segStartPosition = area.top(); + segEndPosition = area.bottom(); + flowStartPosition = area.left(); + flowEndPosition = area.right(); + } else { + segStartPosition = area.left(); + segEndPosition = area.right(); + flowStartPosition = area.top(); + flowEndPosition = area.bottom(); + } + if (segmentPositions.count() < 2 || flowPositions.isEmpty()) + return ret; + // the last segment position is actually the edge of the last segment + const int segLast = segmentPositions.count() - 2; + int seg = qBinarySearch(segmentPositions, segStartPosition, 0, segLast + 1); + for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) { + int first = segmentStartRows.at(seg); + int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1; + if (segmentExtents.at(seg) < flowStartPosition) + continue; + int row = qBinarySearch(flowPositions, flowStartPosition, first, last); + for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) { + if (isHidden(row)) + continue; + QModelIndex index = modelIndex(row); + if (index.isValid()) + ret += index; +#if 0 // for debugging + else + qWarning("intersectingSet: row %d was invalid", row); +#endif + } + } + return ret; +} + +void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &) +{ + dd->doDelayedItemsLayout(); +} + + +QRect QListModeViewBase::mapToViewport(const QRect &rect) const +{ + if (isWrapping()) + return rect; + // If the listview is in "listbox-mode", the items are as wide as the view. + // But we don't shrink the items. + QRect result = rect; + if (flow() == QListView::TopToBottom) { + result.setLeft(spacing()); + result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing())); + } else { // LeftToRight + result.setTop(spacing()); + result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing())); + } + return result; +} + +int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const +{ + QVector positions; + if (wrap) + positions = segmentPositions; + else if (!flowPositions.isEmpty()) { + positions.reserve(scrollValueMap.size()); + foreach (int itemShown, scrollValueMap) + positions.append(flowPositions.at(itemShown)); + } + if (positions.isEmpty() || bounds <= length) + return positions.count(); + if (uniformItemSizes()) { + for (int i = 1; i < positions.count(); ++i) + if (positions.at(i) > 0) + return length / positions.at(i); + return 0; // all items had height 0 + } + int pageSteps = 0; + int steps = positions.count() - 1; + int max = qMax(length, bounds); + int min = qMin(length, bounds); + int pos = min - (max - positions.last()); + + while (pos >= 0 && steps > 0) { + pos -= (positions.at(steps) - positions.at(steps - 1)); + if (pos >= 0) //this item should be visible + ++pageSteps; + --steps; + } + + // at this point we know that positions has at least one entry + return qMax(pageSteps, 1); +} + +int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, + QAbstractItemView::ScrollHint hint, + Qt::Orientation orientation, bool wrap, int itemExtent) const +{ + if (index < 0) + return scrollValue; + if (!wrap) { + int topIndex = index; + const int bottomIndex = topIndex; + const int bottomCoordinate = flowPositions.at(index); + + while (topIndex > 0 && + (bottomCoordinate - flowPositions.at(topIndex-1) + itemExtent) <= (viewportSize)) { + topIndex--; + } + + const int itemCount = bottomIndex - topIndex + 1; + switch (hint) { + case QAbstractItemView::PositionAtTop: + return index; + case QAbstractItemView::PositionAtBottom: + return index - itemCount + 1; + case QAbstractItemView::PositionAtCenter: + return index - (itemCount / 2); + default: + break; + } + } else { // wrapping + Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight + ? Qt::Horizontal : Qt::Vertical); + if (flowOrientation == orientation) { // scrolling in the "flow" direction + // ### wrapped scrolling in the flow direction + return flowPositions.at(index); // ### always pixel based for now + } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction + int segment = qBinarySearch(segmentStartRows, index, 0, segmentStartRows.count() - 1); + int leftSegment = segment; + const int rightSegment = leftSegment; + const int bottomCoordinate = segmentPositions.at(segment); + + while (leftSegment > scrollValue && + (bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) { + leftSegment--; + } + + const int segmentCount = rightSegment - leftSegment + 1; + switch (hint) { + case QAbstractItemView::PositionAtTop: + return segment; + case QAbstractItemView::PositionAtBottom: + return segment - segmentCount + 1; + case QAbstractItemView::PositionAtCenter: + return segment - (segmentCount / 2); + default: + break; + } + } + } + return scrollValue; +} + +void QListModeViewBase::clear() +{ + flowPositions.clear(); + segmentPositions.clear(); + segmentStartRows.clear(); + segmentExtents.clear(); + batchSavedPosition = 0; + batchStartRow = 0; + batchSavedDeltaSeg = 0; +} + +/* + * IconMode ListView Implementation +*/ + +void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index) +{ + if (index.row() >= items.count()) + return; + const QSize oldContents = contentsSize; + qq->update(index); // update old position + moveItem(index.row(), position); + qq->update(index); // update new position + + if (contentsSize != oldContents) + dd->viewUpdateGeometries(); // update the scroll bars +} + +void QIconModeViewBase::appendHiddenRow(int row) +{ + if (row >= 0 && row < items.count()) //remove item + tree.removeLeaf(items.at(row).rect(), row); + QCommonListViewBase::appendHiddenRow(row); +} + +void QIconModeViewBase::removeHiddenRow(int row) +{ + QCommonListViewBase::removeHiddenRow(row); + if (row >= 0 && row < items.count()) //insert item + tree.insertLeaf(items.at(row).rect(), row); +} + +#ifndef QT_NO_DRAGANDDROP +void QIconModeViewBase::paintDragDrop(QPainter *painter) +{ + if (!draggedItems.isEmpty() && viewport()->rect().contains(draggedItemsPos)) { + //we need to draw the items that arre dragged + painter->translate(draggedItemsDelta()); + QStyleOptionViewItemV4 option = viewOptions(); + option.state &= ~QStyle::State_MouseOver; + QVector::const_iterator it = draggedItems.begin(); + QListViewItem item = indexToListViewItem(*it); + for (; it != draggedItems.end(); ++it) { + item = indexToListViewItem(*it); + option.rect = viewItemRect(item); + delegate(*it)->paint(painter, option, *it); + } + } +} + +bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions) +{ + // This function does the same thing as in QAbstractItemView::startDrag(), + // plus adding viewitems to the draggedItems list. + // We need these items to draw the drag items + QModelIndexList indexes = dd->selectionModel->selectedIndexes(); + if (indexes.count() > 0 ) { + if (viewport()->acceptDrops()) { + QModelIndexList::ConstIterator it = indexes.constBegin(); + for (; it != indexes.constEnd(); ++it) + if (dd->model->flags(*it) & Qt::ItemIsDragEnabled + && (*it).column() == dd->column) + draggedItems.push_back(*it); + } + QDrag *drag = new QDrag(qq); + drag->setMimeData(dd->model->mimeData(indexes)); + Qt::DropAction action = drag->exec(supportedActions, Qt::CopyAction); + draggedItems.clear(); + if (action == Qt::MoveAction) + dd->clearOrRemove(); + } + return true; +} + +bool QIconModeViewBase::filterDropEvent(QDropEvent *e) +{ + if (e->source() != qq) + return false; + + const QSize contents = contentsSize; + QPoint offset(horizontalOffset(), verticalOffset()); + QPoint end = e->pos() + offset; + if (qq->acceptDrops()) { + const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; + const QVector &dropIndices = intersectingSet(QRect(end, QSize(1, 1))); + foreach (const QModelIndex &index, dropIndices) + if ((index.flags() & dropableFlags) == dropableFlags) + return false; + } + QPoint start = dd->pressedPosition; + QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start); + QList indexes = dd->selectionModel->selectedIndexes(); + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + QRect rect = dd->rectForIndex(index); + viewport()->update(dd->mapToViewport(rect, false)); + QPoint dest = rect.topLeft() + delta; + if (qq->isRightToLeft()) + dest.setX(dd->flipX(dest.x()) - rect.width()); + moveItem(index.row(), dest); + qq->update(index); + } + dd->stopAutoScroll(); + draggedItems.clear(); + dd->emitIndexesMoved(indexes); + e->accept(); // we have handled the event + // if the size has not grown, we need to check if it has shrinked + if (contentsSize != contents) { + if ((contentsSize.width() <= contents.width() + || contentsSize.height() <= contents.height())) { + updateContentsSize(); + } + dd->viewUpdateGeometries(); + } + return true; +} + +bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e) +{ + viewport()->update(draggedItemsRect()); // erase the area + draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items + return QCommonListViewBase::filterDragLeaveEvent(e); +} + +bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e) +{ + if (e->source() != qq || !dd->canDecode(e)) + return false; + + // ignore by default + e->ignore(); + // get old dragged items rect + QRect itemsRect = this->itemsRect(draggedItems); + viewport()->update(itemsRect.translated(draggedItemsDelta())); + // update position + draggedItemsPos = e->pos(); + // get new items rect + viewport()->update(itemsRect.translated(draggedItemsDelta())); + // set the item under the cursor to current + QModelIndex index; + if (movement() == QListView::Snap) { + QRect rect(snapToGrid(e->pos() + offset()), gridSize()); + const QVector intersectVector = intersectingSet(rect); + index = intersectVector.count() > 0 ? intersectVector.last() : QModelIndex(); + } else { + index = qq->indexAt(e->pos()); + } + // check if we allow drops here + if (draggedItems.contains(index)) + e->accept(); // allow changing item position + else if (dd->model->flags(index) & Qt::ItemIsDropEnabled) + e->accept(); // allow dropping on dropenabled items + else if (!index.isValid()) + e->accept(); // allow dropping in empty areas + + // the event was treated. do autoscrolling + if (dd->shouldAutoScroll(e->pos())) + dd->startAutoScroll(); + return true; +} +#endif // QT_NO_DRAGANDDROP + +void QIconModeViewBase::setRowCount(int rowCount) +{ + tree.create(qMax(rowCount - hiddenCount(), 0)); +} + +void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) +{ + if (scrollElasticBand) + dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy); + + QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); + if (!draggedItems.isEmpty()) + viewport()->update(draggedItemsRect().translated(dx, dy)); +} + +void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + if (column() >= topLeft.column() && column() <= bottomRight.column()) { + QStyleOptionViewItemV4 option = viewOptions(); + int bottom = qMin(items.count(), bottomRight.row() + 1); + for (int row = topLeft.row(); row < bottom; ++row) + items[row].resize(itemSize(option, modelIndex(row))); + } +} + +bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) +{ + if (info.last >= items.count()) { + //first we create the items + QStyleOptionViewItemV4 option = viewOptions(); + for (int row = items.count(); row <= info.last; ++row) { + QSize size = itemSize(option, modelIndex(row)); + QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos + items.append(item); + } + doDynamicLayout(info); + } + return (batchStartRow > max); // done +} + +QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const +{ + if (index.isValid() && index.row() < items.count()) + return items.at(index.row()); + return QListViewItem(); +} + +void QIconModeViewBase::initBspTree(const QSize &contents) +{ + // remove all items from the tree + int leafCount = tree.leafCount(); + for (int l = 0; l < leafCount; ++l) + tree.leaf(l).clear(); + // we have to get the bounding rect of the items before we can initialize the tree + QBspTree::Node::Type type = QBspTree::Node::Both; // 2D + // simple heuristics to get better bsp + if (contents.height() / contents.width() >= 3) + type = QBspTree::Node::HorizontalPlane; + else if (contents.width() / contents.height() >= 3) + type = QBspTree::Node::VerticalPlane; + // build tree for the bounding rect (not just the contents rect) + tree.init(QRect(0, 0, contents.width(), contents.height()), type); +} + +QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info) +{ + int x, y; + if (info.first == 0) { + x = info.bounds.x() + info.spacing; + y = info.bounds.y() + info.spacing; + items.reserve(rowCount() - hiddenCount()); + } else { + int idx = info.first - 1; + while (idx > 0 && !items.at(idx).isValid()) + --idx; + const QListViewItem &item = items.at(idx); + x = item.x; + y = item.y; + if (info.flow == QListView::LeftToRight) + x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing; + else + y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing; + } + return QPoint(x, y); +} + +/*! + \internal +*/ +void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info) +{ + const bool useItemSize = !info.grid.isValid(); + const QPoint topLeft = initDynamicLayout(info); + + int segStartPosition; + int segEndPosition; + int deltaFlowPosition; + int deltaSegPosition; + int deltaSegHint; + int flowPosition; + int segPosition; + + if (info.flow == QListView::LeftToRight) { + segStartPosition = info.bounds.left() + info.spacing; + segEndPosition = info.bounds.right(); + deltaFlowPosition = info.grid.width(); // dx + deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy + deltaSegHint = info.grid.height(); + flowPosition = topLeft.x(); + segPosition = topLeft.y(); + } else { // flow == QListView::TopToBottom + segStartPosition = info.bounds.top() + info.spacing; + segEndPosition = info.bounds.bottom(); + deltaFlowPosition = info.grid.height(); // dy + deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx + deltaSegHint = info.grid.width(); + flowPosition = topLeft.y(); + segPosition = topLeft.x(); + } + + if (moved.count() != items.count()) + moved.resize(items.count()); + + QRect rect(QPoint(), topLeft); + QListViewItem *item = 0; + for (int row = info.first; row <= info.last; ++row) { + item = &items[row]; + if (isHidden(row)) { + item->invalidate(); + } else { + // if we are not using a grid, we need to find the deltas + if (useItemSize) { + if (info.flow == QListView::LeftToRight) + deltaFlowPosition = item->w + info.spacing; + else + deltaFlowPosition = item->h + info.spacing; + } else { + item->w = qMin(info.grid.width(), item->w); + item->h = qMin(info.grid.height(), item->h); + } + + // create new segment + if (info.wrap + && flowPosition + deltaFlowPosition > segEndPosition + && flowPosition > segStartPosition) { + flowPosition = segStartPosition; + segPosition += deltaSegPosition; + if (useItemSize) + deltaSegPosition = 0; + } + // We must delay calculation of the seg adjustment, as this item + // may have caused a wrap to occur + if (useItemSize) { + if (info.flow == QListView::LeftToRight) + deltaSegHint = item->h + info.spacing; + else + deltaSegHint = item->w + info.spacing; + deltaSegPosition = qMax(deltaSegPosition, deltaSegHint); + } + + // set the position of the item + // ### idealy we should have some sort of alignment hint for the item + // ### (normally that would be a point between the icon and the text) + if (!moved.testBit(row)) { + if (info.flow == QListView::LeftToRight) { + if (useItemSize) { + item->x = flowPosition; + item->y = segPosition; + } else { // use grid + item->x = flowPosition + ((deltaFlowPosition - item->w) / 2); + item->y = segPosition; + } + } else { // TopToBottom + if (useItemSize) { + item->y = flowPosition; + item->x = segPosition; + } else { // use grid + item->y = flowPosition + ((deltaFlowPosition - item->h) / 2); + item->x = segPosition; + } + } + } + + // let the contents contain the new item + if (useItemSize) + rect |= item->rect(); + else if (info.flow == QListView::LeftToRight) + rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition); + else // flow == TopToBottom + rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition); + + // prepare for next item + flowPosition += deltaFlowPosition; // current position + item width + gap + } + } + batchSavedDeltaSeg = deltaSegPosition; + batchStartRow = info.last + 1; + bool done = (info.last >= rowCount() - 1); + // resize the content area + if (done || !info.bounds.contains(item->rect())) { + contentsSize = rect.size(); + if (info.flow == QListView::LeftToRight) + contentsSize.rheight() += info.spacing; + else + contentsSize.rwidth() += info.spacing; + } + if (rect.size().isEmpty()) + return; + // resize tree + int insertFrom = info.first; + if (done || info.first == 0) { + initBspTree(rect.size()); + insertFrom = 0; + } + // insert items in tree + for (int row = insertFrom; row <= info.last; ++row) + tree.insertLeaf(items.at(row).rect(), row); + // if the new items are visble, update the viewport + QRect changedRect(topLeft, rect.bottomRight()); + if (clipRect().intersects(changedRect)) + viewport()->update(); +} + +QVector QIconModeViewBase::intersectingSet(const QRect &area) const +{ + QIconModeViewBase *that = const_cast(this); + QBspTree::Data data(static_cast(that)); + QVector res; + that->interSectingVector = &res; + that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data); + that->interSectingVector = 0; + return res; +} + +QRect QIconModeViewBase::itemsRect(const QVector &indexes) const +{ + QVector::const_iterator it = indexes.begin(); + QListViewItem item = indexToListViewItem(*it); + QRect rect(item.x, item.y, item.w, item.h); + for (; it != indexes.end(); ++it) { + item = indexToListViewItem(*it); + rect |= viewItemRect(item); + } + return rect; +} + +int QIconModeViewBase::itemIndex(const QListViewItem &item) const +{ + if (!item.isValid()) + return -1; + int i = item.indexHint; + if (i < items.count()) { + if (items.at(i) == item) + return i; + } else { + i = items.count() - 1; + } + + int j = i; + int c = items.count(); + bool a = true; + bool b = true; + + while (a || b) { + if (a) { + if (items.at(i) == item) { + items.at(i).indexHint = i; + return i; + } + a = ++i < c; + } + if (b) { + if (items.at(j) == item) { + items.at(j).indexHint = j; + return j; + } + b = --j > -1; + } + } + return -1; +} + +void QIconModeViewBase::addLeaf(QVector &leaf, const QRect &area, + uint visited, QBspTree::Data data) +{ + QListViewItem *vi; + QIconModeViewBase *_this = static_cast(data.ptr); + for (int i = 0; i < leaf.count(); ++i) { + int idx = leaf.at(i); + if (idx < 0 || idx >= _this->items.count()) + continue; + vi = &_this->items[idx]; + Q_ASSERT(vi); + if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) { + QModelIndex index = _this->dd->listViewItemToIndex(*vi); + Q_ASSERT(index.isValid()); + _this->interSectingVector->append(index); + vi->visited = visited; + } + } +} + +void QIconModeViewBase::moveItem(int index, const QPoint &dest) +{ + // does not impact on the bintree itself or the contents rect + QListViewItem *item = &items[index]; + QRect rect = item->rect(); + + // move the item without removing it from the tree + tree.removeLeaf(rect, index); + item->move(dest); + tree.insertLeaf(QRect(dest, rect.size()), index); + + // resize the contents area + contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size(); + + // mark the item as moved + if (moved.count() != items.count()) + moved.resize(items.count()); + moved.setBit(index, true); +} + +QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const +{ + int x = pos.x() - (pos.x() % gridSize().width()); + int y = pos.y() - (pos.y() % gridSize().height()); + return QPoint(x, y); +} + +QPoint QIconModeViewBase::draggedItemsDelta() const +{ + if (movement() == QListView::Snap) { + QPoint snapdelta = QPoint((offset().x() % gridSize().width()), + (offset().y() % gridSize().height())); + return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta; + } + return draggedItemsPos - pressedPosition(); +} + +QRect QIconModeViewBase::draggedItemsRect() const +{ + QRect rect = itemsRect(draggedItems); + rect.translate(draggedItemsDelta()); + return rect; +} + +void QListViewPrivate::scrollElasticBandBy(int dx, int dy) +{ + if (dx > 0) // right + elasticBand.moveRight(elasticBand.right() + dx); + else if (dx < 0) // left + elasticBand.moveLeft(elasticBand.left() - dx); + if (dy > 0) // down + elasticBand.moveBottom(elasticBand.bottom() + dy); + else if (dy < 0) // up + elasticBand.moveTop(elasticBand.top() - dy); +} + +void QIconModeViewBase::clear() +{ + tree.destroy(); + items.clear(); + moved.clear(); + batchStartRow = 0; + batchSavedDeltaSeg = 0; +} + +void QIconModeViewBase::updateContentsSize() +{ + QRect bounding; + for (int i = 0; i < items.count(); ++i) + bounding |= items.at(i).rect(); + contentsSize = bounding.size(); +} + +/*! + \reimp +*/ +void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + if (current.isValid()) { + int entry = visualIndex(current) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } + } +#endif + QAbstractItemView::currentChanged(current, previous); +} + +/*! + \reimp +*/ +void QListView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(desel) + 1; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QListView::visualIndex(const QModelIndex &index) const +{ + Q_D(const QListView); + d->executePostedLayout(); + QListViewItem itm = d->indexToListViewItem(index); + return d->commonListView->itemIndex(itm); +} + +QT_END_NAMESPACE + +#endif // QT_NO_LISTVIEW diff --git a/src/gui/itemviews/qlistview.h b/src/gui/itemviews/qlistview.h new file mode 100644 index 0000000000..3d6dd421f7 --- /dev/null +++ b/src/gui/itemviews/qlistview.h @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTVIEW_H +#define QLISTVIEW_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LISTVIEW + +class QListViewPrivate; + +class Q_GUI_EXPORT QListView : public QAbstractItemView +{ + Q_OBJECT + Q_ENUMS(Movement Flow ResizeMode LayoutMode ViewMode) + Q_PROPERTY(Movement movement READ movement WRITE setMovement) + Q_PROPERTY(Flow flow READ flow WRITE setFlow) + Q_PROPERTY(bool isWrapping READ isWrapping WRITE setWrapping) + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(LayoutMode layoutMode READ layoutMode WRITE setLayoutMode) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + Q_PROPERTY(QSize gridSize READ gridSize WRITE setGridSize) + Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode) + Q_PROPERTY(int modelColumn READ modelColumn WRITE setModelColumn) + Q_PROPERTY(bool uniformItemSizes READ uniformItemSizes WRITE setUniformItemSizes) + Q_PROPERTY(int batchSize READ batchSize WRITE setBatchSize) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool selectionRectVisible READ isSelectionRectVisible WRITE setSelectionRectVisible) + +public: + enum Movement { Static, Free, Snap }; + enum Flow { LeftToRight, TopToBottom }; + enum ResizeMode { Fixed, Adjust }; + enum LayoutMode { SinglePass, Batched }; + enum ViewMode { ListMode, IconMode }; + + explicit QListView(QWidget *parent = 0); + ~QListView(); + + void setMovement(Movement movement); + Movement movement() const; + + void setFlow(Flow flow); + Flow flow() const; + + void setWrapping(bool enable); + bool isWrapping() const; + + void setResizeMode(ResizeMode mode); + ResizeMode resizeMode() const; + + void setLayoutMode(LayoutMode mode); + LayoutMode layoutMode() const; + + void setSpacing(int space); + int spacing() const; + + void setBatchSize(int batchSize); + int batchSize() const; + + void setGridSize(const QSize &size); + QSize gridSize() const; + + void setViewMode(ViewMode mode); + ViewMode viewMode() const; + + void clearPropertyFlags(); + + bool isRowHidden(int row) const; + void setRowHidden(int row, bool hide); + + void setModelColumn(int column); + int modelColumn() const; + + void setUniformItemSizes(bool enable); + bool uniformItemSizes() const; + + void setWordWrap(bool on); + bool wordWrap() const; + + void setSelectionRectVisible(bool show); + bool isSelectionRectVisible() const; + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + + void doItemsLayout(); + void reset(); + void setRootIndex(const QModelIndex &index); + +Q_SIGNALS: + void indexesMoved(const QModelIndexList &indexes); + +protected: + QListView(QListViewPrivate &, QWidget *parent = 0); + + bool event(QEvent *e); + + void scrollContentsBy(int dx, int dy); + + void resizeContents(int width, int height); + QSize contentsSize() const; + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + + void timerEvent(QTimerEvent *e); + void resizeEvent(QResizeEvent *e); +#ifndef QT_NO_DRAGANDDROP + void dragMoveEvent(QDragMoveEvent *e); + void dragLeaveEvent(QDragLeaveEvent *e); + void dropEvent(QDropEvent *e); + void startDrag(Qt::DropActions supportedActions); + + void internalDrop(QDropEvent *e); + void internalDrag(Qt::DropActions supportedActions); +#endif // QT_NO_DRAGANDDROP + + QStyleOptionViewItem viewOptions() const; + void paintEvent(QPaintEvent *e); + + int horizontalOffset() const; + int verticalOffset() const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + QRect rectForIndex(const QModelIndex &index) const; + void setPositionForIndex(const QPoint &position, const QModelIndex &index); + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void updateGeometries(); + + bool isIndexHidden(const QModelIndex &index) const; + + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QListView) + Q_DISABLE_COPY(QListView) +}; + +#endif // QT_NO_LISTVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLISTVIEW_H diff --git a/src/gui/itemviews/qlistview_p.h b/src/gui/itemviews/qlistview_p.h new file mode 100644 index 0000000000..84aaaca2de --- /dev/null +++ b/src/gui/itemviews/qlistview_p.h @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTVIEW_P_H +#define QLISTVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemview_p.h" +#include "qrubberband.h" +#include "qbitarray.h" +#include "qbsptree_p.h" +#include +#include + +#ifndef QT_NO_LISTVIEW + +QT_BEGIN_NAMESPACE + +class QListViewItem +{ + friend class QListViewPrivate; + friend class QListModeViewBase; + friend class QIconModeViewBase; +public: + inline QListViewItem() + : x(-1), y(-1), w(0), h(0), indexHint(-1), visited(0xffff) {} + inline QListViewItem(const QListViewItem &other) + : x(other.x), y(other.y), w(other.w), h(other.h), + indexHint(other.indexHint), visited(other.visited) {} + inline QListViewItem(QRect r, int i) + : x(r.x()), y(r.y()), w(qMin(r.width(), SHRT_MAX)), h(qMin(r.height(), SHRT_MAX)), + indexHint(i), visited(0xffff) {} + inline bool operator==(const QListViewItem &other) const { + return (x == other.x && y == other.y && w == other.w && h == other.h && + indexHint == other.indexHint); } + inline bool operator!=(const QListViewItem &other) const + { return !(*this == other); } + inline bool isValid() const + { return rect().isValid() && (indexHint > -1); } + inline void invalidate() + { x = -1; y = -1; w = 0; h = 0; } + inline void resize(const QSize &size) + { w = qMin(size.width(), SHRT_MAX); h = qMin(size.height(), SHRT_MAX); } + inline void move(const QPoint &position) + { x = position.x(); y = position.y(); } + inline int width() const { return w; } + inline int height() const { return h; } +private: + inline QRect rect() const + { return QRect(x, y, w, h); } + int x, y; + short w, h; + mutable int indexHint; + uint visited; +}; + +struct QListViewLayoutInfo +{ + QRect bounds; + QSize grid; + int spacing; + int first; + int last; + bool wrap; + QListView::Flow flow; + int max; +}; + +class QListView; +class QListViewPrivate; + +class QCommonListViewBase +{ +public: + inline QCommonListViewBase(QListView *q, QListViewPrivate *d) : dd(d), qq(q), batchStartRow(0), batchSavedDeltaSeg(0) {} + virtual ~QCommonListViewBase() {} + + //common interface + virtual int itemIndex(const QListViewItem &item) const = 0; + virtual QListViewItem indexToListViewItem(const QModelIndex &index) const = 0; + virtual bool doBatchedItemLayout(const QListViewLayoutInfo &info, int max) = 0; + virtual void clear() = 0; + virtual void setRowCount(int) = 0; + virtual QVector intersectingSet(const QRect &area) const = 0; + virtual void dataChanged(const QModelIndex &, const QModelIndex &) = 0; + + virtual int horizontalScrollToValue(int index, QListView::ScrollHint hint, + bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const; + virtual int verticalScrollToValue(int index, QListView::ScrollHint hint, + bool above, bool below, const QRect &area, const QRect &rect) const; + virtual void scrollContentsBy(int dx, int dy, bool scrollElasticBand); + virtual QRect mapToViewport(const QRect &rect) const {return rect;} + virtual int horizontalOffset() const; + virtual int verticalOffset() const { return verticalScrollBar()->value(); } + virtual void updateHorizontalScrollBar(const QSize &step); + virtual void updateVerticalScrollBar(const QSize &step); + virtual void appendHiddenRow(int row); + virtual void removeHiddenRow(int row); + virtual void setPositionForIndex(const QPoint &, const QModelIndex &) { } + +#ifndef QT_NO_DRAGANDDROP + virtual void paintDragDrop(QPainter *painter) = 0; + virtual bool filterDragMoveEvent(QDragMoveEvent *) { return false; } + virtual bool filterDragLeaveEvent(QDragLeaveEvent *) { return false; } + virtual bool filterDropEvent(QDropEvent *) { return false; } + virtual bool filterStartDrag(Qt::DropActions) { return false; } +#endif + + + //other inline members + inline int spacing() const; + inline bool isWrapping() const; + inline QSize gridSize() const; + inline QListView::Flow flow() const; + inline QListView::Movement movement() const; + + inline QPoint offset() const; + inline QPoint pressedPosition() const; + inline bool uniformItemSizes() const; + inline int column() const; + + inline QScrollBar *verticalScrollBar() const; + inline QScrollBar *horizontalScrollBar() const; + inline QListView::ScrollMode verticalScrollMode() const; + inline QListView::ScrollMode horizontalScrollMode() const; + + inline QModelIndex modelIndex(int row) const; + inline int rowCount() const; + + inline QStyleOptionViewItemV4 viewOptions() const; + inline QWidget *viewport() const; + inline QRect clipRect() const; + + inline QSize cachedItemSize() const; + inline QRect viewItemRect(const QListViewItem &item) const; + inline QSize itemSize(const QStyleOptionViewItemV2 &opt, const QModelIndex &idx) const; + inline QAbstractItemDelegate *delegate(const QModelIndex &idx) const; + + inline bool isHidden(int row) const; + inline int hiddenCount() const; + + inline bool isRightToLeft() const; + + QListViewPrivate *dd; + QListView *qq; + QSize contentsSize; + int batchStartRow; + int batchSavedDeltaSeg; +}; + +class QListModeViewBase : public QCommonListViewBase +{ +public: + QListModeViewBase(QListView *q, QListViewPrivate *d) : QCommonListViewBase(q, d) {} + + QVector flowPositions; + QVector segmentPositions; + QVector segmentStartRows; + QVector segmentExtents; + QVector scrollValueMap; + + // used when laying out in batches + int batchSavedPosition; + + //reimplementations + int itemIndex(const QListViewItem &item) const { return item.indexHint; } + QListViewItem indexToListViewItem(const QModelIndex &index) const; + bool doBatchedItemLayout(const QListViewLayoutInfo &info, int max); + void clear(); + void setRowCount(int rowCount) { flowPositions.resize(rowCount); } + QVector intersectingSet(const QRect &area) const; + void dataChanged(const QModelIndex &, const QModelIndex &); + + int horizontalScrollToValue(int index, QListView::ScrollHint hint, + bool leftOf, bool rightOf,const QRect &area, const QRect &rect) const; + int verticalScrollToValue(int index, QListView::ScrollHint hint, + bool above, bool below, const QRect &area, const QRect &rect) const; + void scrollContentsBy(int dx, int dy, bool scrollElasticBand); + QRect mapToViewport(const QRect &rect) const; + int horizontalOffset() const; + int verticalOffset() const; + void updateHorizontalScrollBar(const QSize &step); + void updateVerticalScrollBar(const QSize &step); + +#ifndef QT_NO_DRAGANDDROP + void paintDragDrop(QPainter *painter); + + // The next two methods are to be used on LefToRight flow only. + // WARNING: Plenty of duplicated code from QAbstractItemView{,Private}. + QAbstractItemView::DropIndicatorPosition position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const; + void dragMoveEvent(QDragMoveEvent *e); + bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index); +#endif + +private: + QPoint initStaticLayout(const QListViewLayoutInfo &info); + void doStaticLayout(const QListViewLayoutInfo &info); + int perItemScrollToValue(int index, int value, int height, + QAbstractItemView::ScrollHint hint, + Qt::Orientation orientation, bool wrap, int extent) const; + int perItemScrollingPageSteps(int length, int bounds, bool wrap) const; +}; + +class QIconModeViewBase : public QCommonListViewBase +{ +public: + QIconModeViewBase(QListView *q, QListViewPrivate *d) : QCommonListViewBase(q, d), interSectingVector(0) {} + + QBspTree tree; + QVector items; + QBitArray moved; + + QVector draggedItems; // indices to the tree.itemVector + mutable QPoint draggedItemsPos; + + // used when laying out in batches + QVector *interSectingVector; //used from within intersectingSet + + //reimplementations + int itemIndex(const QListViewItem &item) const; + QListViewItem indexToListViewItem(const QModelIndex &index) const; + bool doBatchedItemLayout(const QListViewLayoutInfo &info, int max); + void clear(); + void setRowCount(int rowCount); + QVector intersectingSet(const QRect &area) const; + + void scrollContentsBy(int dx, int dy, bool scrollElasticBand); + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void appendHiddenRow(int row); + void removeHiddenRow(int row); + void setPositionForIndex(const QPoint &position, const QModelIndex &index); + +#ifndef QT_NO_DRAGANDDROP + void paintDragDrop(QPainter *painter); + bool filterDragMoveEvent(QDragMoveEvent *); + bool filterDragLeaveEvent(QDragLeaveEvent *); + bool filterDropEvent(QDropEvent *e); + bool filterStartDrag(Qt::DropActions); +#endif + +private: + void initBspTree(const QSize &contents); + QPoint initDynamicLayout(const QListViewLayoutInfo &info); + void doDynamicLayout(const QListViewLayoutInfo &info); + static void addLeaf(QVector &leaf, const QRect &area, + uint visited, QBspTree::Data data); + QRect itemsRect(const QVector &indexes) const; + QRect draggedItemsRect() const; + QPoint snapToGrid(const QPoint &pos) const; + void updateContentsSize(); + QPoint draggedItemsDelta() const; + void drawItems(QPainter *painter, const QVector &indexes) const; + void moveItem(int index, const QPoint &dest); + +}; + +class QListViewPrivate: public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QListView) +public: + QListViewPrivate(); + ~QListViewPrivate(); + + void clear(); + void prepareItemsLayout(); + + bool doItemsLayout(int num); + + inline QVector intersectingSet(const QRect &area, bool doLayout = true) const { + if (doLayout) executePostedLayout(); + QRect a = (q_func()->isRightToLeft() ? flipX(area.normalized()) : area.normalized()); + return commonListView->intersectingSet(a); + } + + inline void resetBatchStartRow() { commonListView->batchStartRow = 0; } + inline int batchStartRow() const { return commonListView->batchStartRow; } + inline QSize contentsSize() const { return commonListView->contentsSize; } + inline void setContentsSize(int w, int h) { commonListView->contentsSize = QSize(w, h); } + + inline int flipX(int x) const + { return qMax(viewport->width(), contentsSize().width()) - x; } + inline QPoint flipX(const QPoint &p) const + { return QPoint(flipX(p.x()), p.y()); } + inline QRect flipX(const QRect &r) const + { return QRect(flipX(r.x()) - r.width(), r.y(), r.width(), r.height()); } + inline QRect viewItemRect(const QListViewItem &item) const + { if (q_func()->isRightToLeft()) return flipX(item.rect()); return item.rect(); } + + QListViewItem indexToListViewItem(const QModelIndex &index) const; + inline QModelIndex listViewItemToIndex(const QListViewItem &item) const + { return model->index(commonListView->itemIndex(item), column, root); } + + QRect rectForIndex(const QModelIndex &index) const + { + if (!isIndexValid(index) || index.parent() != root || index.column() != column || isHidden(index.row())) + return QRect(); + executePostedLayout(); + return viewItemRect(indexToListViewItem(index)); + } + + void viewUpdateGeometries() { q_func()->updateGeometries(); } + + + QRect mapToViewport(const QRect &rect, bool extend = true) const; + + QModelIndex closestIndex(const QRect &target, const QVector &candidates) const; + QSize itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + bool selectionAllowed(const QModelIndex &index) const + { if (viewMode == QListView::ListMode && !showElasticBand) return index.isValid(); return true; } + + int horizontalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const; + int verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const; + + QItemSelection selection(const QRect &rect) const; + void selectAll(QItemSelectionModel::SelectionFlags command); + +#ifndef QT_NO_DRAGANDDROP + virtual QAbstractItemView::DropIndicatorPosition position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const; + bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index); +#endif + + inline void setGridSize(const QSize &size) { grid = size; } + inline QSize gridSize() const { return grid; } + inline void setWrapping(bool b) { wrap = b; } + inline bool isWrapping() const { return wrap; } + inline void setSpacing(int s) { space = s; } + inline int spacing() const { return space; } + inline void setSelectionRectVisible(bool visible) { showElasticBand = visible; } + inline bool isSelectionRectVisible() const { return showElasticBand; } + + inline QModelIndex modelIndex(int row) const { return model->index(row, column, root); } + inline bool isHidden(int row) const { + QModelIndex idx = model->index(row, 0, root); + return isPersistent(idx) && hiddenRows.contains(idx); + } + inline bool isHiddenOrDisabled(int row) const { return isHidden(row) || !isIndexEnabled(modelIndex(row)); } + + inline void removeCurrentAndDisabled(QVector *indexes, const QModelIndex ¤t) const { + QVector::iterator it = indexes->begin(); + while (it != indexes->end()) { + if (!isIndexEnabled(*it) || (*it) == current) + indexes->erase(it); + else + ++it; + } + } + + void scrollElasticBandBy(int dx, int dy); + + QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; + + void emitIndexesMoved(const QModelIndexList &indexes) { emit q_func()->indexesMoved(indexes); } + + + QCommonListViewBase *commonListView; + + // ### FIXME: see if we can move the members into the dynamic/static classes + + bool wrap; + int space; + QSize grid; + + QListView::Flow flow; + QListView::Movement movement; + QListView::ResizeMode resizeMode; + QListView::LayoutMode layoutMode; + QListView::ViewMode viewMode; + + // the properties controlling the + // icon- or list-view modes + enum ModeProperties { + Wrap = 1, + Spacing = 2, + GridSize = 4, + Flow = 8, + Movement = 16, + ResizeMode = 32, + SelectionRectVisible = 64 + }; + + uint modeProperties : 8; + + QRect layoutBounds; + + // timers + QBasicTimer batchLayoutTimer; + + // used for hidden items + QSet hiddenRows; + + int column; + bool uniformItemSizes; + mutable QSize cachedItemSize; + int batchSize; + + QRect elasticBand; + bool showElasticBand; +}; + +// inline implementations + +inline int QCommonListViewBase::spacing() const { return dd->spacing(); } +inline bool QCommonListViewBase::isWrapping() const { return dd->isWrapping(); } +inline QSize QCommonListViewBase::gridSize() const { return dd->gridSize(); } +inline QListView::Flow QCommonListViewBase::flow() const { return dd->flow; } +inline QListView::Movement QCommonListViewBase::movement() const { return dd->movement; } + +inline QPoint QCommonListViewBase::offset() const { return dd->offset(); } +inline QPoint QCommonListViewBase::pressedPosition() const { return dd->pressedPosition; } +inline bool QCommonListViewBase::uniformItemSizes() const { return dd->uniformItemSizes; } +inline int QCommonListViewBase::column() const { return dd->column; } + +inline QScrollBar *QCommonListViewBase::verticalScrollBar() const { return qq->verticalScrollBar(); } +inline QScrollBar *QCommonListViewBase::horizontalScrollBar() const { return qq->horizontalScrollBar(); } +inline QListView::ScrollMode QCommonListViewBase::verticalScrollMode() const { return qq->verticalScrollMode(); } +inline QListView::ScrollMode QCommonListViewBase::horizontalScrollMode() const { return qq->horizontalScrollMode(); } + +inline QModelIndex QCommonListViewBase::modelIndex(int row) const + { return dd->model->index(row, dd->column, dd->root); } +inline int QCommonListViewBase::rowCount() const { return dd->model->rowCount(dd->root); } + +inline QStyleOptionViewItemV4 QCommonListViewBase::viewOptions() const { return dd->viewOptionsV4(); } +inline QWidget *QCommonListViewBase::viewport() const { return dd->viewport; } +inline QRect QCommonListViewBase::clipRect() const { return dd->clipRect(); } + +inline QSize QCommonListViewBase::cachedItemSize() const { return dd->cachedItemSize; } +inline QRect QCommonListViewBase::viewItemRect(const QListViewItem &item) const { return dd->viewItemRect(item); } +inline QSize QCommonListViewBase::itemSize(const QStyleOptionViewItemV2 &opt, const QModelIndex &idx) const + { return dd->itemSize(opt, idx); } + +inline QAbstractItemDelegate *QCommonListViewBase::delegate(const QModelIndex &idx) const + { return dd->delegateForIndex(idx); } + +inline bool QCommonListViewBase::isHidden(int row) const { return dd->isHidden(row); } +inline int QCommonListViewBase::hiddenCount() const { return dd->hiddenRows.count(); } + +inline bool QCommonListViewBase::isRightToLeft() const { return qq->isRightToLeft(); } + +QT_END_NAMESPACE + +#endif // QT_NO_LISTVIEW + +#endif // QLISTVIEW_P_H diff --git a/src/gui/itemviews/qlistwidget.cpp b/src/gui/itemviews/qlistwidget.cpp new file mode 100644 index 0000000000..61a935fdc9 --- /dev/null +++ b/src/gui/itemviews/qlistwidget.cpp @@ -0,0 +1,1914 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlistwidget.h" + +#ifndef QT_NO_LISTWIDGET +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug (?) +typedef bool(*LessThan)(const QPair&,const QPair&); + +class QListWidgetMimeData : public QMimeData +{ + Q_OBJECT +public: + QList items; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qlistwidget.moc" +QT_END_INCLUDE_NAMESPACE + +QListModel::QListModel(QListWidget *parent) + : QAbstractListModel(parent) +{ +} + +QListModel::~QListModel() +{ + clear(); +} + +void QListModel::clear() +{ + for (int i = 0; i < items.count(); ++i) { + if (items.at(i)) { + items.at(i)->d->theid = -1; + items.at(i)->view = 0; + delete items.at(i); + } + } + items.clear(); + reset(); +} + +QListWidgetItem *QListModel::at(int row) const +{ + return items.value(row); +} + +void QListModel::remove(QListWidgetItem *item) +{ + if (!item) + return; + int row = items.indexOf(item); // ### use index(item) - it's faster + Q_ASSERT(row != -1); + beginRemoveRows(QModelIndex(), row, row); + items.at(row)->d->theid = -1; + items.at(row)->view = 0; + items.removeAt(row); + endRemoveRows(); +} + +void QListModel::insert(int row, QListWidgetItem *item) +{ + if (!item) + return; + + item->view = qobject_cast(QObject::parent()); + if (item->view && item->view->isSortingEnabled()) { + // sorted insertion + QList::iterator it; + it = sortedInsertionIterator(items.begin(), items.end(), + item->view->sortOrder(), item); + row = qMax(it - items.begin(), 0); + } else { + if (row < 0) + row = 0; + else if (row > items.count()) + row = items.count(); + } + beginInsertRows(QModelIndex(), row, row); + items.insert(row, item); + item->d->theid = row; + endInsertRows(); +} + +void QListModel::insert(int row, const QStringList &labels) +{ + const int count = labels.count(); + if (count <= 0) + return; + QListWidget *view = qobject_cast(QObject::parent()); + if (view && view->isSortingEnabled()) { + // sorted insertion + for (int i = 0; i < count; ++i) { + QListWidgetItem *item = new QListWidgetItem(labels.at(i)); + insert(row, item); + } + } else { + if (row < 0) + row = 0; + else if (row > items.count()) + row = items.count(); + beginInsertRows(QModelIndex(), row, row + count - 1); + for (int i = 0; i < count; ++i) { + QListWidgetItem *item = new QListWidgetItem(labels.at(i)); + item->d->theid = row; + item->view = qobject_cast(QObject::parent()); + items.insert(row++, item); + } + endInsertRows(); + } +} + +QListWidgetItem *QListModel::take(int row) +{ + if (row < 0 || row >= items.count()) + return 0; + + beginRemoveRows(QModelIndex(), row, row); + items.at(row)->d->theid = -1; + items.at(row)->view = 0; + QListWidgetItem *item = items.takeAt(row); + endRemoveRows(); + return item; +} + +void QListModel::move(int srcRow, int dstRow) +{ + if (srcRow == dstRow + || srcRow < 0 || srcRow >= items.count() + || dstRow < 0 || dstRow > items.count()) + return; + + if (!beginMoveRows(QModelIndex(), srcRow, srcRow, QModelIndex(), dstRow)) + return; + if (srcRow < dstRow) + --dstRow; + items.move(srcRow, dstRow); + endMoveRows(); +} + +int QListModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : items.count(); +} + +QModelIndex QListModel::index(QListWidgetItem *item) const +{ + if (!item || !item->view || static_cast(item->view->model()) != this + || items.isEmpty()) + return QModelIndex(); + int row; + const int theid = item->d->theid; + if (theid >= 0 && theid < items.count() && items.at(theid) == item) { + row = theid; + } else { // we need to search for the item + row = items.lastIndexOf(item); // lastIndexOf is an optimization in favor of indexOf + if (row == -1) // not found + return QModelIndex(); + item->d->theid = row; + } + return createIndex(row, 0, item); +} + +QModelIndex QListModel::index(int row, int column, const QModelIndex &parent) const +{ + if (hasIndex(row, column, parent)) + return createIndex(row, column, items.at(row)); + return QModelIndex(); +} + +QVariant QListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= items.count()) + return QVariant(); + return items.at(index.row())->data(role); +} + +bool QListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() >= items.count()) + return false; + items.at(index.row())->setData(role, value); + return true; +} + +QMap QListModel::itemData(const QModelIndex &index) const +{ + QMap roles; + if (!index.isValid() || index.row() >= items.count()) + return roles; + QListWidgetItem *itm = items.at(index.row()); + for (int i = 0; i < itm->d->values.count(); ++i) { + roles.insert(itm->d->values.at(i).role, + itm->d->values.at(i).value); + } + return roles; +} + +bool QListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || row > rowCount() || parent.isValid()) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + QListWidget *view = qobject_cast(QObject::parent()); + QListWidgetItem *itm = 0; + + for (int r = row; r < row + count; ++r) { + itm = new QListWidgetItem; + itm->view = view; + itm->d->theid = r; + items.insert(r, itm); + } + + endInsertRows(); + return true; +} + +bool QListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || (row + count) > rowCount() || parent.isValid()) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + QListWidgetItem *itm = 0; + for (int r = row; r < row + count; ++r) { + itm = items.takeAt(row); + itm->view = 0; + itm->d->theid = -1; + delete itm; + } + endRemoveRows(); + return true; +} + +Qt::ItemFlags QListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid() || index.row() >= items.count() || index.model() != this) + return Qt::ItemIsDropEnabled; // we allow drops outside the items + return items.at(index.row())->flags(); +} + +void QListModel::sort(int column, Qt::SortOrder order) +{ + if (column != 0) + return; + + emit layoutAboutToBeChanged(); + + QVector < QPair > sorting(items.count()); + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + sorting[i].first = item; + sorting[i].second = i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qSort(sorting.begin(), sorting.end(), compare); + QModelIndexList fromIndexes; + QModelIndexList toIndexes; + for (int r = 0; r < sorting.count(); ++r) { + QListWidgetItem *item = sorting.at(r).first; + toIndexes.append(createIndex(r, 0, item)); + fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first)); + items[r] = sorting.at(r).first; + } + changePersistentIndexList(fromIndexes, toIndexes); + + emit layoutChanged(); +} + +/** + * This function assumes that all items in the model except the items that are between + * (inclusive) start and end are sorted. + * With these assumptions, this function can ensure that the model is sorted in a + * much more efficient way than doing a naive 'sort everything'. + * (provided that the range is relatively small compared to the total number of items) + */ +void QListModel::ensureSorted(int column, Qt::SortOrder order, int start, int end) +{ + if (column != 0) + return; + + int count = end - start + 1; + QVector < QPair > sorting(count); + for (int i = 0; i < count; ++i) { + sorting[i].first = items.at(start + i); + sorting[i].second = start + i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + QList tmp = items; + QList::iterator lit = tmp.begin(); + bool changed = false; + for (int i = 0; i < count; ++i) { + int oldRow = sorting.at(i).second; + QListWidgetItem *item = tmp.takeAt(oldRow); + lit = sortedInsertionIterator(lit, tmp.end(), order, item); + int newRow = qMax(lit - tmp.begin(), 0); + lit = tmp.insert(lit, item); + if (newRow != oldRow) { + changed = true; + for (int j = i + 1; j < count; ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + for (int k = 0; k < newPersistentIndexes.count(); ++k) { + QModelIndex pi = newPersistentIndexes.at(k); + int oldPersistentRow = pi.row(); + int newPersistentRow = oldPersistentRow; + if (oldPersistentRow == oldRow) + newPersistentRow = newRow; + else if (oldRow < oldPersistentRow && newRow >= oldPersistentRow) + newPersistentRow = oldPersistentRow - 1; + else if (oldRow > oldPersistentRow && newRow <= oldPersistentRow) + newPersistentRow = oldPersistentRow + 1; + if (newPersistentRow != oldPersistentRow) + newPersistentIndexes[k] = createIndex(newPersistentRow, + pi.column(), pi.internalPointer()); + } + } + } + + if (changed) { + emit layoutAboutToBeChanged(); + items = tmp; + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + emit layoutChanged(); + } +} + +bool QListModel::itemLessThan(const QPair &left, + const QPair &right) +{ + return (*left.first) < (*right.first); +} + +bool QListModel::itemGreaterThan(const QPair &left, + const QPair &right) +{ + return (*right.first) < (*left.first); +} + +QList::iterator QListModel::sortedInsertionIterator( + const QList::iterator &begin, + const QList::iterator &end, + Qt::SortOrder order, QListWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QListModelLessThan()); + return qLowerBound(begin, end, item, QListModelGreaterThan()); +} + +void QListModel::itemChanged(QListWidgetItem *item) +{ + QModelIndex idx = index(item); + emit dataChanged(idx, idx); +} + +QStringList QListModel::mimeTypes() const +{ + const QListWidget *view = qobject_cast(QObject::parent()); + return view->mimeTypes(); +} + +QMimeData *QListModel::internalMimeData() const +{ + return QAbstractItemModel::mimeData(cachedIndexes); +} + +QMimeData *QListModel::mimeData(const QModelIndexList &indexes) const +{ + QList itemlist; + for (int i = 0; i < indexes.count(); ++i) + itemlist << at(indexes.at(i).row()); + const QListWidget *view = qobject_cast(QObject::parent()); + + cachedIndexes = indexes; + QMimeData *mimeData = view->mimeData(itemlist); + cachedIndexes.clear(); + return mimeData; +} + +#ifndef QT_NO_DRAGANDDROP +bool QListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &index) +{ + Q_UNUSED(column); + QListWidget *view = qobject_cast(QObject::parent()); + if (index.isValid()) + row = index.row(); + else if (row == -1) + row = items.count(); + + return view->dropMimeData(row, data, action); +} + +Qt::DropActions QListModel::supportedDropActions() const +{ + const QListWidget *view = qobject_cast(QObject::parent()); + return view->supportedDropActions(); +} +#endif // QT_NO_DRAGANDDROP + +/*! + \class QListWidgetItem + \brief The QListWidgetItem class provides an item for use with the + QListWidget item view class. + + \ingroup model-view + + A QListWidgetItem represents a single item in a QListWidget. Each item can + hold several pieces of information, and will display them appropriately. + + The item view convenience classes use a classic item-based interface rather + than a pure model/view approach. For a more flexible list view widget, + consider using the QListView class with a standard model. + + List items can be inserted automatically into a list, when they are + constructed, by specifying the list widget: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 2 + + Alternatively, list items can also be created without a parent widget, and + later inserted into a list using QListWidget::insertItem(). + + List items are typically used to display text() and an icon(). These are + set with the setText() and setIcon() functions. The appearance of the text + can be customized with setFont(), setForeground(), and setBackground(). + Text in list items can be aligned using the setTextAlignment() function. + Tooltips, status tips and "What's This?" help can be added to list items + with setToolTip(), setStatusTip(), and setWhatsThis(). + + By default, items are enabled, selectable, checkable, and can be the source + of drag and drop operations. + + Each item's flags can be changed by calling setFlags() with the appropriate + value (see Qt::ItemFlags). Checkable items can be checked, unchecked and + partially checked with the setCheckState() function. The corresponding + checkState() function indicates the item's current check state. + + The isHidden() function can be used to determine whether the item is + hidden. To hide an item, use setHidden(). + + + \section1 Subclassing + + When subclassing QListWidgetItem to provide custom items, it is possible to + define new types for them enabling them to be distinguished from standard + items. For subclasses that require this feature, ensure that you call the + base class constructor with a new type value equal to or greater than + \l UserType, within \e your constructor. + + \sa QListWidget, {Model/View Programming}, QTreeWidgetItem, QTableWidgetItem +*/ + +/*! + \enum QListWidgetItem::ItemType + + This enum describes the types that are used to describe list widget items. + + \value Type The default type for list widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QListWidgetItem subclasses to ensure that + custom items are treated specially. + + \sa type() +*/ + +/*! + \fn int QListWidgetItem::type() const + + Returns the type passed to the QListWidgetItem constructor. +*/ + +/*! + \fn QListWidget *QListWidgetItem::listWidget() const + + Returns the list widget containing the item. +*/ + +/*! + \fn void QListWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() +*/ + +/*! + \fn bool QListWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected; otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn void QListWidgetItem::setHidden(bool hide) + \since 4.2 + + Hides the item if \a hide is true; otherwise shows the item. + + \sa isHidden() +*/ + +/*! + \fn bool QListWidgetItem::isHidden() const + \since 4.2 + + Returns true if the item is hidden; otherwise returns false. + + \sa setHidden() +*/ + +/*! + \fn QListWidgetItem::QListWidgetItem(QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a parent. If \a parent is not specified, the item will need to be + inserted into a list widget with QListWidget::insertItem(). + + This constructor inserts the item into the model of the parent that is + passed to the constructor. If the model is sorted then the behavior of the + insert is undetermined since the model will call the \c '<' operator method + on the item which, at this point, is not yet constructed. To avoid the + undetermined behavior, we recommend not to specify the parent and use + QListWidget::insertItem() instead. + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(QListWidget *view, int type) + : rtti(type), view(view), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + \fn QListWidgetItem::QListWidgetItem(const QString &text, QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a text and \a parent. If the parent is not specified, the item will + need to be inserted into a list widget with QListWidget::insertItem(). + + This constructor inserts the item into the model of the parent that is + passed to the constructor. If the model is sorted then the behavior of the + insert is undetermined since the model will call the \c '<' operator method + on the item which, at this point, is not yet constructed. To avoid the + undetermined behavior, we recommend not to specify the parent and use + QListWidget::insertItem() instead. + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(const QString &text, QListWidget *view, int type) + : rtti(type), view(0), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + setData(Qt::DisplayRole, text); + this->view = view; + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + \fn QListWidgetItem::QListWidgetItem(const QIcon &icon, const QString &text, QListWidget *parent, int type) + + Constructs an empty list widget item of the specified \a type with the + given \a icon, \a text and \a parent. If the parent is not specified, the + item will need to be inserted into a list widget with + QListWidget::insertItem(). + + This constructor inserts the item into the model of the parent that is + passed to the constructor. If the model is sorted then the behavior of the + insert is undetermined since the model will call the \c '<' operator method + on the item which, at this point, is not yet constructed. To avoid the + undetermined behavior, we recommend not to specify the parent and use + QListWidget::insertItem() instead. + + \sa type() +*/ +QListWidgetItem::QListWidgetItem(const QIcon &icon,const QString &text, + QListWidget *view, int type) + : rtti(type), view(0), d(new QListWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled) +{ + setData(Qt::DisplayRole, text); + setData(Qt::DecorationRole, icon); + this->view = view; + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->insert(model->rowCount(), this); +} + +/*! + Destroys the list item. +*/ +QListWidgetItem::~QListWidgetItem() +{ + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->remove(this); + delete d; +} + +/*! + Creates an exact copy of the item. +*/ +QListWidgetItem *QListWidgetItem::clone() const +{ + return new QListWidgetItem(*this); +} + +/*! + Sets the data for a given \a role to the given \a value. Reimplement this + function if you need extra roles or special behavior for certain roles. + + \sa Qt::ItemDataRole, data() +*/ +void QListWidgetItem::setData(int role, const QVariant &value) +{ + bool found = false; + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < d->values.count(); ++i) { + if (d->values.at(i).role == role) { + if (d->values.at(i).value == value) + return; + d->values[i].value = value; + found = true; + break; + } + } + if (!found) + d->values.append(QWidgetItemData(role, value)); + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + Returns the item's data for a given \a role. Reimplement this function if + you need extra roles or special behavior for certain roles. + + \sa Qt::ItemDataRole, setData() +*/ +QVariant QListWidgetItem::data(int role) const +{ + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < d->values.count(); ++i) + if (d->values.at(i).role == role) + return d->values.at(i).value; + return QVariant(); +} + +/*! + Returns true if this item's text is less then \a other item's text; + otherwise returns false. +*/ +bool QListWidgetItem::operator<(const QListWidgetItem &other) const +{ + const QVariant v1 = data(Qt::DisplayRole), v2 = other.data(Qt::DisplayRole); + return QAbstractItemModelPrivate::variantLessThan(v1, v2); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. + + \sa write() +*/ +void QListWidgetItem::read(QDataStream &in) +{ + in >> d->values; +} + +/*! + Writes the item to stream \a out. + + \sa read() +*/ +void QListWidgetItem::write(QDataStream &out) const +{ + out << d->values; +} +#endif // QT_NO_DATASTREAM + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and listWidget() are not + copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QListWidgetItem::QListWidgetItem(const QListWidgetItem &other) + : rtti(Type), view(0), + d(new QListWidgetItemPrivate(this)), + itemFlags(other.itemFlags) +{ + d->values = other.d->values; +} + +/*! + Assigns \a other's data and flags to this item. Note that type() and + listWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QListWidgetItem &QListWidgetItem::operator=(const QListWidgetItem &other) +{ + d->values = other.d->values; + itemFlags = other.itemFlags; + return *this; +} + +#ifndef QT_NO_DATASTREAM + +/*! + \relates QListWidgetItem + + Writes the list widget item \a item to stream \a out. + + This operator uses QListWidgetItem::write(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QListWidgetItem &item) +{ + item.write(out); + return out; +} + +/*! + \relates QListWidgetItem + + Reads a list widget item from stream \a in into \a item. + + This operator uses QListWidgetItem::read(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &in, QListWidgetItem &item) +{ + item.read(in); + return in; +} + +#endif // QT_NO_DATASTREAM + +/*! + \fn Qt::ItemFlags QListWidgetItem::flags() const + + Returns the item flags for this item (see \l{Qt::ItemFlags}). +*/ + +/*! + \fn QString QListWidgetItem::text() const + + Returns the list item's text. + + \sa setText() +*/ + +/*! + \fn QIcon QListWidgetItem::icon() const + + Returns the list item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QListWidgetItem::statusTip() const + + Returns the list item's status tip. + + \sa setStatusTip() +*/ + +/*! + \fn QString QListWidgetItem::toolTip() const + + Returns the list item's tooltip. + + \sa setToolTip() statusTip() whatsThis() +*/ + +/*! + \fn QString QListWidgetItem::whatsThis() const + + Returns the list item's "What's This?" help text. + + \sa setWhatsThis() statusTip() toolTip() +*/ + +/*! + \fn QFont QListWidgetItem::font() const + + Returns the font used to display this list item's text. +*/ + +/*! + \fn int QListWidgetItem::textAlignment() const + + Returns the text alignment for the list item. + + \sa Qt::AlignmentFlag +*/ + +/*! + \fn QColor QListWidgetItem::backgroundColor() const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn QBrush QListWidgetItem::background() const + \since 4.2 + + Returns the brush used to display the list item's background. + + \sa setBackground() foreground() +*/ + +/*! + \fn QColor QListWidgetItem::textColor() const + \obsolete + + Returns the color used to display the list item's text. + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn QBrush QListWidgetItem::foreground() const + \since 4.2 + + Returns the brush used to display the list item's foreground (e.g. text). + + \sa setForeground() background() +*/ + +/*! + \fn Qt::CheckState QListWidgetItem::checkState() const + + Returns the checked state of the list item (see \l{Qt::CheckState}). + + \sa flags() +*/ + +/*! + \fn QSize QListWidgetItem::sizeHint() const + \since 4.1 + + Returns the size hint set for the list item. +*/ + +/*! + \fn void QListWidgetItem::setSizeHint(const QSize &size) + \since 4.1 + + Sets the size hint for the list item to be \a size. If no size hint is set, + the item delegate will compute the size hint based on the item data. +*/ + +/*! + \fn void QListWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the item flags for the list item to \a flags. + + \sa Qt::ItemFlags +*/ +void QListWidgetItem::setFlags(Qt::ItemFlags aflags) { + itemFlags = aflags; + if (QListModel *model = (view ? qobject_cast(view->model()) : 0)) + model->itemChanged(this); +} + + +/*! + \fn void QListWidgetItem::setText(const QString &text) + + Sets the text for the list widget item's to the given \a text. + + \sa text() +*/ + +/*! + \fn void QListWidgetItem::setIcon(const QIcon &icon) + + Sets the icon for the list item to the given \a icon. + + \sa icon(), text(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QListWidgetItem::setStatusTip(const QString &statusTip) + + Sets the status tip for the list item to the text specified by + \a statusTip. QListWidget mouseTracking needs to be enabled for this + feature to work. + + \sa statusTip(), setToolTip(), setWhatsThis(), QWidget::setMouseTracking() +*/ + +/*! + \fn void QListWidgetItem::setToolTip(const QString &toolTip) + + Sets the tooltip for the list item to the text specified by \a toolTip. + + \sa toolTip(), setStatusTip(), setWhatsThis() +*/ + +/*! + \fn void QListWidgetItem::setWhatsThis(const QString &whatsThis) + + Sets the "What's This?" help for the list item to the text specified by + \a whatsThis. + + \sa whatsThis(), setStatusTip(), setToolTip() +*/ + +/*! + \fn void QListWidgetItem::setFont(const QFont &font) + + Sets the font used when painting the item to the given \a font. +*/ + +/*! + \fn void QListWidgetItem::setTextAlignment(int alignment) + + Sets the list item's text alignment to \a alignment. + + \sa Qt::AlignmentFlag +*/ + +/*! + \fn void QListWidgetItem::setBackgroundColor(const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn void QListWidgetItem::setBackground(const QBrush &brush) + \since 4.2 + + Sets the background brush of the list item to the given \a brush. + + \sa background() setForeground() +*/ + +/*! + \fn void QListWidgetItem::setTextColor(const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn void QListWidgetItem::setForeground(const QBrush &brush) + \since 4.2 + + Sets the foreground brush of the list item to the given \a brush. + + \sa foreground() setBackground() +*/ + +/*! + \fn void QListWidgetItem::setCheckState(Qt::CheckState state) + + Sets the check state of the list item to \a state. + + \sa checkState() +*/ + +void QListWidgetPrivate::setup() +{ + Q_Q(QListWidget); + q->QListView::setModel(new QListModel(q)); + // view signals + QObject::connect(q, SIGNAL(pressed(QModelIndex)), q, SLOT(_q_emitItemPressed(QModelIndex))); + QObject::connect(q, SIGNAL(clicked(QModelIndex)), q, SLOT(_q_emitItemClicked(QModelIndex))); + QObject::connect(q, SIGNAL(doubleClicked(QModelIndex)), + q, SLOT(_q_emitItemDoubleClicked(QModelIndex))); + QObject::connect(q, SIGNAL(activated(QModelIndex)), + q, SLOT(_q_emitItemActivated(QModelIndex))); + QObject::connect(q, SIGNAL(entered(QModelIndex)), q, SLOT(_q_emitItemEntered(QModelIndex))); + QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SIGNAL(itemSelectionChanged())); + QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), q, SLOT(_q_sort())); +} + +void QListWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemPressed(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemClicked(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemDoubleClicked(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemActivated(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemEntered(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QListWidget); + emit q->itemChanged(listModel()->at(index.row())); +} + +void QListWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QListWidget); + QPersistentModelIndex persistentCurrent = current; + QListWidgetItem *currentItem = listModel()->at(persistentCurrent.row()); + emit q->currentItemChanged(currentItem, listModel()->at(previous.row())); + + //persistentCurrent is invalid if something changed the model in response + //to the currentItemChanged signal emission and the item was removed + if (!persistentCurrent.isValid()) { + currentItem = 0; + } + + emit q->currentTextChanged(currentItem ? currentItem->text() : QString()); + emit q->currentRowChanged(persistentCurrent.row()); +} + +void QListWidgetPrivate::_q_sort() +{ + if (sortingEnabled) + model->sort(0, sortOrder); +} + +void QListWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid()) + listModel()->ensureSorted(topLeft.column(), sortOrder, + topLeft.row(), bottomRight.row()); +} + +/*! + \class QListWidget + \brief The QListWidget class provides an item-based list widget. + + \ingroup model-view + + + QListWidget is a convenience class that provides a list view similar to the + one supplied by QListView, but with a classic item-based interface for + adding and removing items. QListWidget uses an internal model to manage + each QListWidgetItem in the list. + + For a more flexible list view widget, use the QListView class with a + standard model. + + List widgets are constructed in the same way as other widgets: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 0 + + The selectionMode() of a list widget determines how many of the items in + the list can be selected at the same time, and whether complex selections + of items can be created. This can be set with the setSelectionMode() + function. + + There are two ways to add items to the list: they can be constructed with + the list widget as their parent widget, or they can be constructed with no + parent widget and added to the list later. If a list widget already exists + when the items are constructed, the first method is easier to use: + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 1 + + If you need to insert a new item into the list at a particular position, it + is more required to construct the item without a parent widget and use the + insertItem() function to place it within the list. The list widget will + take ownership of the item. + + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 6 + \snippet doc/src/snippets/qlistwidget-using/mainwindow.cpp 7 + + For multiple items, insertItems() can be used instead. The number of items + in the list is found with the count() function. To remove items from the + list, use takeItem(). + + The current item in the list can be found with currentItem(), and changed + with setCurrentItem(). The user can also change the current item by + navigating with the keyboard or clicking on a different item. When the + current item changes, the currentItemChanged() signal is emitted with the + new current item and the item that was previously current. + + \table 100% + \row \o \inlineimage windowsxp-listview.png Screenshot of a Windows XP style list widget + \o \inlineimage macintosh-listview.png Screenshot of a Macintosh style table widget + \o \inlineimage plastique-listview.png Screenshot of a Plastique style table widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} list widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} list widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} list widget. + \endtable + + \sa QListWidgetItem, QListView, QTreeView, {Model/View Programming}, + {Config Dialog Example} +*/ + +/*! + \fn void QListWidget::addItem(QListWidgetItem *item) + + Inserts the \a item at the end of the list widget. + + \warning A QListWidgetItem can only be added to a QListWidget once. Adding + the same QListWidgetItem multiple times to a QListWidget will result in + undefined behavior. + + \sa insertItem() +*/ + +/*! + \fn void QListWidget::addItem(const QString &label) + + Inserts an item with the text \a label at the end of the list widget. +*/ + +/*! + \fn void QListWidget::addItems(const QStringList &labels) + + Inserts items with the text \a labels at the end of the list widget. + + \sa insertItems() +*/ + +/*! + \fn void QListWidget::itemPressed(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is + pressed on an item in the widget. + + \sa itemClicked(), itemDoubleClicked() +*/ + +/*! + \fn void QListWidget::itemClicked(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is + clicked on an item in the widget. + + \sa itemPressed(), itemDoubleClicked() +*/ + +/*! + \fn void QListWidget::itemDoubleClicked(QListWidgetItem *item) + + This signal is emitted with the specified \a item when a mouse button is + double clicked on an item in the widget. + + \sa itemClicked(), itemPressed() +*/ + +/*! + \fn void QListWidget::itemActivated(QListWidgetItem *item) + + This signal is emitted when the \a item is activated. The \a item is + activated when the user clicks or double clicks on it, depending on the + system configuration. It is also activated when the user presses the + activation key (on Windows and X11 this is the \gui Return key, on Mac OS + X it is \key{Ctrl+0}). +*/ + +/*! + \fn void QListWidget::itemEntered(QListWidgetItem *item) + + This signal is emitted when the mouse cursor enters an item. The \a item is + the item entered. This signal is only emitted when mouseTracking is turned + on, or when a mouse button is pressed while moving into an item. + + \sa QWidget::setMouseTracking() +*/ + +/*! + \fn void QListWidget::itemChanged(QListWidgetItem *item) + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + \fn void QListWidget::currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) + + This signal is emitted whenever the current item changes. + + \a previous is the item that previously had the focus; \a current is the + new current item. +*/ + +/*! + \fn void QListWidget::currentTextChanged(const QString ¤tText) + + This signal is emitted whenever the current item changes. + + \a currentText is the text data in the current item. If there is no current + item, the \a currentText is invalid. +*/ + +/*! + \fn void QListWidget::currentRowChanged(int currentRow) + + This signal is emitted whenever the current item changes. + + \a currentRow is the row of the current item. If there is no current item, + the \a currentRow is -1. +*/ + +/*! + \fn void QListWidget::itemSelectionChanged() + + This signal is emitted whenever the selection changes. + + \sa selectedItems(), QListWidgetItem::isSelected(), currentItemChanged() +*/ + +/*! + \since 4.3 + + \fn void QListWidget::removeItemWidget(QListWidgetItem *item) + + Removes the widget set on the given \a item. +*/ + +/*! + Constructs an empty QListWidget with the given \a parent. +*/ + +QListWidget::QListWidget(QWidget *parent) + : QListView(*new QListWidgetPrivate(), parent) +{ + Q_D(QListWidget); + d->setup(); +} + +/*! + Destroys the list widget and all its items. +*/ + +QListWidget::~QListWidget() +{ +} + +/*! + Returns the item that occupies the given \a row in the list if one has been + set; otherwise returns 0. + + \sa row() +*/ + +QListWidgetItem *QListWidget::item(int row) const +{ + Q_D(const QListWidget); + if (row < 0 || row >= d->model->rowCount()) + return 0; + return d->listModel()->at(row); +} + +/*! + Returns the row containing the given \a item. + + \sa item() +*/ + +int QListWidget::row(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + return d->listModel()->index(const_cast(item)).row(); +} + + +/*! + Inserts the \a item at the position in the list given by \a row. + + \sa addItem() +*/ + +void QListWidget::insertItem(int row, QListWidgetItem *item) +{ + Q_D(QListWidget); + if (item && !item->view) + d->listModel()->insert(row, item); +} + +/*! + Inserts an item with the text \a label in the list widget at the position + given by \a row. + + \sa addItem() +*/ + +void QListWidget::insertItem(int row, const QString &label) +{ + Q_D(QListWidget); + d->listModel()->insert(row, new QListWidgetItem(label)); +} + +/*! + Inserts items from the list of \a labels into the list, starting at the + given \a row. + + \sa insertItem(), addItem() +*/ + +void QListWidget::insertItems(int row, const QStringList &labels) +{ + Q_D(QListWidget); + d->listModel()->insert(row, labels); +} + +/*! + Removes and returns the item from the given \a row in the list widget; + otherwise returns 0. + + Items removed from a list widget will not be managed by Qt, and will need + to be deleted manually. + + \sa insertItem(), addItem() +*/ + +QListWidgetItem *QListWidget::takeItem(int row) +{ + Q_D(QListWidget); + if (row < 0 || row >= d->model->rowCount()) + return 0; + return d->listModel()->take(row); +} + +/*! + \property QListWidget::count + \brief the number of items in the list including any hidden items. +*/ + +int QListWidget::count() const +{ + Q_D(const QListWidget); + return d->model->rowCount(); +} + +/*! + Returns the current item. +*/ +QListWidgetItem *QListWidget::currentItem() const +{ + Q_D(const QListWidget); + return d->listModel()->at(currentIndex().row()); +} + + +/*! + Sets the current item to \a item. + + Unless the selection mode is \l{QAbstractItemView::}{NoSelection}, + the item is also be selected. +*/ +void QListWidget::setCurrentItem(QListWidgetItem *item) +{ + setCurrentRow(row(item)); +} + +/*! + \since 4.4 + Set the current item to \a item, using the given \a command. +*/ +void QListWidget::setCurrentItem(QListWidgetItem *item, QItemSelectionModel::SelectionFlags command) +{ + setCurrentRow(row(item), command); +} + +/*! + \property QListWidget::currentRow + \brief the row of the current item. + + Depending on the current selection mode, the row may also be selected. +*/ + +int QListWidget::currentRow() const +{ + return currentIndex().row(); +} + +void QListWidget::setCurrentRow(int row) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(row); + if (d->selectionMode == SingleSelection) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + else if (d->selectionMode == NoSelection) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + else + selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent); +} + +/*! + \since 4.4 + + Sets the current row to be the given \a row, using the given \a command, +*/ +void QListWidget::setCurrentRow(int row, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QListWidget); + d->selectionModel->setCurrentIndex(d->listModel()->index(row), command); +} + +/*! + Returns a pointer to the item at the coordinates \a p. The coordinates + are relative to the list widget's \l{QAbstractScrollArea::}{viewport()}. + +*/ +QListWidgetItem *QListWidget::itemAt(const QPoint &p) const +{ + Q_D(const QListWidget); + return d->listModel()->at(indexAt(p).row()); + +} + +/*! + \fn QListWidgetItem *QListWidget::itemAt(int x, int y) const + \overload + + Returns a pointer to the item at the coordinates (\a x, \a y). + The coordinates are relative to the list widget's + \l{QAbstractScrollArea::}{viewport()}. + +*/ + + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. +*/ +QRect QListWidget::visualItemRect(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->listModel()->index(const_cast(item)); + return visualRect(index); +} + +/*! + Sorts all the items in the list widget according to the specified \a order. +*/ +void QListWidget::sortItems(Qt::SortOrder order) +{ + Q_D(QListWidget); + d->sortOrder = order; + d->listModel()->sort(0, order); +} + +/*! + \since 4.2 + \property QListWidget::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the list; if the property + is false, sorting is not enabled. + + The default value is false. +*/ +void QListWidget::setSortingEnabled(bool enable) +{ + Q_D(QListWidget); + d->sortingEnabled = enable; +} + +bool QListWidget::isSortingEnabled() const +{ + Q_D(const QListWidget); + return d->sortingEnabled; +} + +/*! + \internal +*/ +Qt::SortOrder QListWidget::sortOrder() const +{ + Q_D(const QListWidget); + return d->sortOrder; +} + +/*! + Starts editing the \a item if it is editable. +*/ + +void QListWidget::editItem(QListWidgetItem *item) +{ + Q_D(QListWidget); + edit(d->listModel()->index(item)); +} + +/*! + Opens an editor for the given \a item. The editor remains open after + editing. + + \sa closePersistentEditor() +*/ +void QListWidget::openPersistentEditor(QListWidgetItem *item) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(item); + QAbstractItemView::openPersistentEditor(index); +} + +/*! + Closes the persistent editor for the given \a item. + + \sa openPersistentEditor() +*/ +void QListWidget::closePersistentEditor(QListWidgetItem *item) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(item); + QAbstractItemView::closePersistentEditor(index); +} + +/*! + \since 4.1 + + Returns the widget displayed in the given \a item. +*/ +QWidget *QListWidget::itemWidget(QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->listModel()->index(item); + return QAbstractItemView::indexWidget(index); +} + +/*! + \since 4.1 + + Sets the \a widget to be displayed in the give \a item. + + This function should only be used to display static content in the place of + a list widget item. If you want to display custom dynamic content or + implement a custom editor widget, use QListView and subclass QItemDelegate + instead. + + \sa {Delegate Classes} +*/ +void QListWidget::setItemWidget(QListWidgetItem *item, QWidget *widget) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(item); + QAbstractItemView::setIndexWidget(index, widget); +} + +/*! + Returns true if \a item is selected; otherwise returns false. + + \obsolete + + This function is deprecated. Use QListWidgetItem::isSelected() instead. +*/ +bool QListWidget::isItemSelected(const QListWidgetItem *item) const +{ + Q_D(const QListWidget); + QModelIndex index = d->listModel()->index(const_cast(item)); + return selectionModel()->isSelected(index); +} + +/*! + Selects or deselects the given \a item depending on whether \a select is + true of false. + + \obsolete + + This function is deprecated. Use QListWidgetItem::setSelected() instead. +*/ +void QListWidget::setItemSelected(const QListWidgetItem *item, bool select) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(const_cast(item)); + + if (d->selectionMode == SingleSelection) { + selectionModel()->select(index, select + ? QItemSelectionModel::ClearAndSelect + : QItemSelectionModel::Deselect); + } else if (d->selectionMode != NoSelection) { + selectionModel()->select(index, select + ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect); + } + +} + +/*! + Returns a list of all selected items in the list widget. +*/ + +QList QListWidget::selectedItems() const +{ + Q_D(const QListWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList items; + for (int i = 0; i < indexes.count(); ++i) + items.append(d->listModel()->at(indexes.at(i).row())); + return items; +} + +/*! + Finds items with the text that matches the string \a text using the given + \a flags. +*/ + +QList QListWidget::findItems(const QString &text, Qt::MatchFlags flags) const +{ + Q_D(const QListWidget); + QModelIndexList indexes = d->listModel()->match(model()->index(0, 0, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->listModel()->at(indexes.at(i).row())); + return items; +} + +/*! + Returns true if the \a item is explicitly hidden; otherwise returns false. + + \obsolete + + This function is deprecated. Use QListWidgetItem::isHidden() instead. +*/ +bool QListWidget::isItemHidden(const QListWidgetItem *item) const +{ + return isRowHidden(row(item)); +} + +/*! + If \a hide is true, the \a item will be hidden; otherwise it will be shown. + + \obsolete + + This function is deprecated. Use QListWidgetItem::setHidden() instead. +*/ +void QListWidget::setItemHidden(const QListWidgetItem *item, bool hide) +{ + setRowHidden(row(item), hide); +} + +/*! + Scrolls the view if necessary to ensure that the \a item is visible. + + \a hint specifies where the \a item should be located after the operation. +*/ + +void QListWidget::scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QListWidget); + QModelIndex index = d->listModel()->index(const_cast(item)); + QListView::scrollTo(index, hint); +} + +/*! + Removes all items and selections in the view. + + \warning All items will be permanently deleted. +*/ +void QListWidget::clear() +{ + Q_D(QListWidget); + selectionModel()->clear(); + d->listModel()->clear(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + listwidget items. + + \sa mimeData() +*/ +QStringList QListWidget::mimeTypes() const +{ + return d_func()->listModel()->QAbstractListModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned instead of a serialized empty + list. +*/ +QMimeData *QListWidget::mimeData(const QList) const +{ + return d_func()->listModel()->internalMimeData(); +} + +#ifndef QT_NO_DRAGANDDROP +/*! + Handles \a data supplied by an external drag and drop operation that ended + with the given \a action in the given \a index. Returns true if \a data and + \a action can be handled by the model; otherwise returns false. + + \sa supportedDropActions() +*/ +bool QListWidget::dropMimeData(int index, const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; + int row = index; + int column = 0; + if (dropIndicatorPosition() == QAbstractItemView::OnItem) { + // QAbstractListModel::dropMimeData will overwrite on the index if row == -1 and column == -1 + idx = model()->index(row, column); + row = -1; + column = -1; + } + return d_func()->listModel()->QAbstractListModel::dropMimeData(data, action , row, column, idx); +} + +/*! \reimp */ +void QListWidget::dropEvent(QDropEvent *event) { + Q_D(QListWidget); + if (event->source() == this && d->movement != Static) { + QListView::dropEvent(event); + return; + } + + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QList selIndexes = selectedIndexes(); + QList persIndexes; + for (int i = 0; i < selIndexes.count(); i++) + persIndexes.append(selIndexes.at(i)); + + if (persIndexes.contains(topIndex)) + return; + qSort(persIndexes); // The dropped items will remain in the same visual order. + + QPersistentModelIndex dropRow = model()->index(row, col, topIndex); + + int r = row == -1 ? count() : (dropRow.row() >= 0 ? dropRow.row() : row); + for (int i = 0; i < persIndexes.count(); ++i) { + const QPersistentModelIndex &pIndex = persIndexes.at(i); + d->listModel()->move(pIndex.row(), r); + r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order. + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QListView::dropEvent(event); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QListWidget::supportedDropActions() const +{ + Q_D(const QListWidget); + return d->listModel()->QAbstractListModel::supportedDropActions() | Qt::MoveAction; +} +#endif // QT_NO_DRAGANDDROP + +/*! + Returns a list of pointers to the items contained in the \a data object. If + the object was not created by a QListWidget in the same process, the list + is empty. +*/ +QList QListWidget::items(const QMimeData *data) const +{ + const QListWidgetMimeData *lwd = qobject_cast(data); + if (lwd) + return lwd->items; + return QList(); +} + +/*! + Returns the QModelIndex assocated with the given \a item. +*/ + +QModelIndex QListWidget::indexFromItem(QListWidgetItem *item) const +{ + Q_D(const QListWidget); + return d->listModel()->index(item); +} + +/*! + Returns a pointer to the QListWidgetItem assocated with the given \a index. +*/ + +QListWidgetItem *QListWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QListWidget); + if (d->isIndexValid(index)) + return d->listModel()->at(index.row()); + return 0; +} + +/*! + \internal +*/ +void QListWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QListWidget::setModel() - Changing the model of the QListWidget is not allowed."); +} + +/*! + \reimp +*/ +bool QListWidget::event(QEvent *e) +{ + return QListView::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qlistwidget.cpp" + +#endif // QT_NO_LISTWIDGET diff --git a/src/gui/itemviews/qlistwidget.h b/src/gui/itemviews/qlistwidget.h new file mode 100644 index 0000000000..ff6616dfac --- /dev/null +++ b/src/gui/itemviews/qlistwidget.h @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTWIDGET_H +#define QLISTWIDGET_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LISTWIDGET + +class QListWidget; +class QListModel; +class QWidgetItemData; +class QListWidgetItemPrivate; + +class Q_GUI_EXPORT QListWidgetItem +{ + friend class QListModel; + friend class QListWidget; +public: + enum ItemType { Type = 0, UserType = 1000 }; + explicit QListWidgetItem(QListWidget *view = 0, int type = Type); + explicit QListWidgetItem(const QString &text, QListWidget *view = 0, int type = Type); + explicit QListWidgetItem(const QIcon &icon, const QString &text, + QListWidget *view = 0, int type = Type); + QListWidgetItem(const QListWidgetItem &other); + virtual ~QListWidgetItem(); + + virtual QListWidgetItem *clone() const; + + inline QListWidget *listWidget() const { return view; } + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline void setHidden(bool hide); + inline bool isHidden() const; + + inline Qt::ItemFlags flags() const { return itemFlags; } + void setFlags(Qt::ItemFlags flags); + + inline QString text() const + { return data(Qt::DisplayRole).toString(); } + inline void setText(const QString &text); + + inline QIcon icon() const + { return qvariant_cast(data(Qt::DecorationRole)); } + inline void setIcon(const QIcon &icon); + + inline QString statusTip() const + { return data(Qt::StatusTipRole).toString(); } + inline void setStatusTip(const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const + { return data(Qt::ToolTipRole).toString(); } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const + { return data(Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QFont font() const + { return qvariant_cast(data(Qt::FontRole)); } + inline void setFont(const QFont &font); + + inline int textAlignment() const + { return data(Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int alignment) + { setData(Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor() const + { return qvariant_cast(data(Qt::BackgroundColorRole)); } + virtual void setBackgroundColor(const QColor &color) + { setData(Qt::BackgroundColorRole, color); } + + inline QBrush background() const + { return qvariant_cast(data(Qt::BackgroundRole)); } + inline void setBackground(const QBrush &brush) + { setData(Qt::BackgroundRole, brush); } + + inline QColor textColor() const + { return qvariant_cast(data(Qt::TextColorRole)); } + inline void setTextColor(const QColor &color) + { setData(Qt::TextColorRole, color); } + + inline QBrush foreground() const + { return qvariant_cast(data(Qt::ForegroundRole)); } + inline void setForeground(const QBrush &brush) + { setData(Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState() const + { return static_cast(data(Qt::CheckStateRole).toInt()); } + inline void setCheckState(Qt::CheckState state) + { setData(Qt::CheckStateRole, static_cast(state)); } + + inline QSize sizeHint() const + { return qvariant_cast(data(Qt::SizeHintRole)); } + inline void setSizeHint(const QSize &size) + { setData(Qt::SizeHintRole, size); } + + virtual QVariant data(int role) const; + virtual void setData(int role, const QVariant &value); + + virtual bool operator<(const QListWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QListWidgetItem &operator=(const QListWidgetItem &other); + + inline int type() const { return rtti; } + +private: + int rtti; + QVector dummy; + QListWidget *view; + QListWidgetItemPrivate *d; + Qt::ItemFlags itemFlags; +}; + +inline void QListWidgetItem::setText(const QString &atext) +{ setData(Qt::DisplayRole, atext); } + +inline void QListWidgetItem::setIcon(const QIcon &aicon) +{ setData(Qt::DecorationRole, aicon); } + +inline void QListWidgetItem::setStatusTip(const QString &astatusTip) +{ setData(Qt::StatusTipRole, astatusTip); } + +#ifndef QT_NO_TOOLTIP +inline void QListWidgetItem::setToolTip(const QString &atoolTip) +{ setData(Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QListWidgetItem::setWhatsThis(const QString &awhatsThis) +{ setData(Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QListWidgetItem::setFont(const QFont &afont) +{ setData(Qt::FontRole, afont); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QListWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QListWidgetItem &item); +#endif + +class QListWidgetPrivate; + +class Q_GUI_EXPORT QListWidget : public QListView +{ + Q_OBJECT + Q_PROPERTY(int count READ count) + Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged USER true) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + + friend class QListWidgetItem; + friend class QListModel; +public: + explicit QListWidget(QWidget *parent = 0); + ~QListWidget(); + + QListWidgetItem *item(int row) const; + int row(const QListWidgetItem *item) const; + void insertItem(int row, QListWidgetItem *item); + void insertItem(int row, const QString &label); + void insertItems(int row, const QStringList &labels); + inline void addItem(const QString &label) { insertItem(count(), label); } + inline void addItem(QListWidgetItem *item); + inline void addItems(const QStringList &labels) { insertItems(count(), labels); } + QListWidgetItem *takeItem(int row); + int count() const; + + QListWidgetItem *currentItem() const; + void setCurrentItem(QListWidgetItem *item); + void setCurrentItem(QListWidgetItem *item, QItemSelectionModel::SelectionFlags command); + + int currentRow() const; + void setCurrentRow(int row); + void setCurrentRow(int row, QItemSelectionModel::SelectionFlags command); + + QListWidgetItem *itemAt(const QPoint &p) const; + inline QListWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QListWidgetItem *item) const; + + void sortItems(Qt::SortOrder order = Qt::AscendingOrder); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QListWidgetItem *item); + void openPersistentEditor(QListWidgetItem *item); + void closePersistentEditor(QListWidgetItem *item); + + QWidget *itemWidget(QListWidgetItem *item) const; + void setItemWidget(QListWidgetItem *item, QWidget *widget); + inline void removeItemWidget(QListWidgetItem *item); + + bool isItemSelected(const QListWidgetItem *item) const; + void setItemSelected(const QListWidgetItem *item, bool select); + QList selectedItems() const; + QList findItems(const QString &text, Qt::MatchFlags flags) const; + + bool isItemHidden(const QListWidgetItem *item) const; + void setItemHidden(const QListWidgetItem *item, bool hide); + void dropEvent(QDropEvent *event); + +public Q_SLOTS: + void scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible); + void clear(); + +Q_SIGNALS: + void itemPressed(QListWidgetItem *item); + void itemClicked(QListWidgetItem *item); + void itemDoubleClicked(QListWidgetItem *item); + void itemActivated(QListWidgetItem *item); + void itemEntered(QListWidgetItem *item); + void itemChanged(QListWidgetItem *item); + + void currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void currentTextChanged(const QString ¤tText); + void currentRowChanged(int currentRow); + + void itemSelectionChanged(); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList items) const; +#ifndef QT_NO_DRAGANDDROP + virtual bool dropMimeData(int index, const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; +#endif + QList items(const QMimeData *data) const; + + QModelIndex indexFromItem(QListWidgetItem *item) const; + QListWidgetItem *itemFromIndex(const QModelIndex &index) const; + +private: + void setModel(QAbstractItemModel *model); + Qt::SortOrder sortOrder() const; + + Q_DECLARE_PRIVATE(QListWidget) + Q_DISABLE_COPY(QListWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) +}; + +inline void QListWidget::removeItemWidget(QListWidgetItem *aItem) +{ setItemWidget(aItem, 0); } + +inline void QListWidget::addItem(QListWidgetItem *aitem) +{ insertItem(count(), aitem); } + +inline QListWidgetItem *QListWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline void QListWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QListWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +inline void QListWidgetItem::setHidden(bool ahide) +{ if (view) view->setItemHidden(this, ahide); } + +inline bool QListWidgetItem::isHidden() const +{ return (view ? view->isItemHidden(this) : false); } + +#endif // QT_NO_LISTWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLISTWIDGET_H diff --git a/src/gui/itemviews/qlistwidget_p.h b/src/gui/itemviews/qlistwidget_p.h new file mode 100644 index 0000000000..0614b66799 --- /dev/null +++ b/src/gui/itemviews/qlistwidget_p.h @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLISTWIDGET_P_H +#define QLISTWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_LISTWIDGET + +QT_BEGIN_NAMESPACE + +class QListModelLessThan +{ +public: + inline bool operator()(QListWidgetItem *i1, QListWidgetItem *i2) const + { return *i1 < *i2; } +}; + +class QListModelGreaterThan +{ +public: + inline bool operator()(QListWidgetItem *i1, QListWidgetItem *i2) const + { return *i2 < *i1; } +}; + +class Q_AUTOTEST_EXPORT QListModel : public QAbstractListModel +{ + Q_OBJECT +public: + QListModel(QListWidget *parent); + ~QListModel(); + + void clear(); + QListWidgetItem *at(int row) const; + void insert(int row, QListWidgetItem *item); + void insert(int row, const QStringList &items); + void remove(QListWidgetItem *item); + QListWidgetItem *take(int row); + void move(int srcRow, int dstRow); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QModelIndex index(QListWidgetItem *item) const; + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + QMap itemData(const QModelIndex &index) const; + + bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + void ensureSorted(int column, Qt::SortOrder order, int start, int end); + static bool itemLessThan(const QPair &left, + const QPair &right); + static bool itemGreaterThan(const QPair &left, + const QPair &right); + static QList::iterator sortedInsertionIterator( + const QList::iterator &begin, + const QList::iterator &end, + Qt::SortOrder order, QListWidgetItem *item); + + void itemChanged(QListWidgetItem *item); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; +#ifndef QT_NO_DRAGANDDROP + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; +#endif + + QMimeData *internalMimeData() const; +private: + QList items; + + // A cache must be mutable if get-functions should have const modifiers + mutable QModelIndexList cachedIndexes; +}; + + + +class QListWidgetPrivate : public QListViewPrivate +{ + Q_DECLARE_PUBLIC(QListWidget) +public: + QListWidgetPrivate() : QListViewPrivate(), sortOrder(Qt::AscendingOrder), sortingEnabled(false) {} + inline QListModel *listModel() const { return qobject_cast(model); } + void setup(); + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + void _q_emitItemChanged(const QModelIndex &index); + void _q_emitCurrentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + Qt::SortOrder sortOrder; + bool sortingEnabled; +}; + +class QListWidgetItemPrivate +{ +public: + QListWidgetItemPrivate(QListWidgetItem *item) : q(item), theid(-1) {} + QListWidgetItem *q; + QVector values; + int theid; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LISTWIDGET + +#endif // QLISTWIDGET_P_H diff --git a/src/gui/itemviews/qproxymodel.cpp b/src/gui/itemviews/qproxymodel.cpp new file mode 100644 index 0000000000..9a232a6a74 --- /dev/null +++ b/src/gui/itemviews/qproxymodel.cpp @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qproxymodel.h" + +#ifndef QT_NO_PROXYMODEL +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QProxyModel + \obsolete + \brief The QProxyModel class provides support for processing data + passed between another model and a view. + + \ingroup model-view + + If you want to do filtering and sorting, see QSortFilterProxyModel. + + Proxy models provide a standard model interface that can be used to + manipulate the data retrieved through an underlying model. They can be used to + perform operations such as sorting and filtering on the data obtained without + changing the contents of the model. + + Just as with subclasses of QAbstractItemView, QProxyModel provides the setModel() + function that is used to specify the model to be acted on by the proxy. + Views can be connected to either the underlying model or the proxy model with + \l QAbstractItemView::setModel(). + + Since views rely on the information provided in model indexes to identify + items of data from models, and to position these items in some visual + representation, proxy models must create their own model indexes instead of + supplying model indexes from their underlying models. + + \sa \link model-view-programming.html Model/View Programming\endlink QAbstractItemModel + +*/ + +/*! + Constructs a proxy model with the given \a parent. +*/ + +QProxyModel::QProxyModel(QObject *parent) + : QAbstractItemModel(*new QProxyModelPrivate, parent) +{ + Q_D(QProxyModel); + setModel(&d->empty); +} + +/*! + \internal +*/ +QProxyModel::QProxyModel(QProxyModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QProxyModel); + setModel(&d->empty); +} + +/*! + Destroys the proxy model. +*/ +QProxyModel::~QProxyModel() +{ +} + +/*! + Sets the given \a model to be processed by the proxy model. +*/ +void QProxyModel::setModel(QAbstractItemModel *model) +{ + Q_D(QProxyModel); + if (d->model && d->model != &d->empty) + disconnectFromModel(d->model); + if (model) { + d->model = model; + connectToModel(model); + } else { + d->model = &d->empty; + } +} + +/*! + Returns the model that contains the data that is available through the + proxy model. +*/ +QAbstractItemModel *QProxyModel::model() const +{ + Q_D(const QProxyModel); + return d->model; +} + +/*! + Returns the model index with the given \a row, \a column, and \a parent. + + \sa QAbstractItemModel::index() +*/ +QModelIndex QProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return setProxyModel(d->model->index(row, column, setSourceModel(parent))); +} + +/*! + Returns the model index that corresponds to the parent of the given \a child + index. +*/ +QModelIndex QProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QProxyModel); + return setProxyModel(d->model->parent(setSourceModel(child))); +} + +/*! + Returns the number of rows for the given \a parent. + + \sa QAbstractItemModel::rowCount() +*/ +int QProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->rowCount(setSourceModel(parent)); +} + +/*! + Returns the number of columns for the given \a parent. + + \sa QAbstractItemModel::columnCount() +*/ +int QProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->columnCount(setSourceModel(parent)); +} + +/*! + Returns true if the item corresponding to the \a parent index has child + items; otherwise returns false. + + \sa QAbstractItemModel::hasChildren() +*/ +bool QProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QProxyModel); + return d->model->hasChildren(setSourceModel(parent)); +} + +/*! + Returns the data stored in the item with the given \a index under the + specified \a role. +*/ +QVariant QProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QProxyModel); + return d->model->data(setSourceModel(index), role); +} + +/*! + Sets the \a role data for the item at \a index to \a value. + Returns true if successful; otherwise returns false. + + The base class implementation returns false. This function and + data() must be reimplemented for editable models. + + \sa data() itemData() QAbstractItemModel::setData() +*/ +bool QProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(const QProxyModel); + return d->model->setData(setSourceModel(index), value, role); +} + +/*! + Returns the data stored in the \a section of the header with specified + \a orientation under the given \a role. +*/ +QVariant QProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QProxyModel); + return d->model->headerData(section, orientation, role); +} + +/*! + Sets the \a role data in the \a section of the header with the specified + \a orientation to the \a value given. + + \sa QAbstractItemModel::setHeaderData() +*/ +bool QProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(const QProxyModel); + return d->model->setHeaderData(section, orientation, value, role); +} + +/*! + Returns a list of MIME types that are supported by the model. +*/ +QStringList QProxyModel::mimeTypes() const +{ + Q_D(const QProxyModel); + return d->model->mimeTypes(); +} + +/*! + Returns MIME data for the specified \a indexes in the model. +*/ +QMimeData *QProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QProxyModel); + QModelIndexList lst; + for (int i = 0; i < indexes.count(); ++i) + lst.append(setSourceModel(indexes.at(i))); + return d->model->mimeData(lst); +} + +/*! + Returns true if the model accepts the \a data dropped onto an attached + view for the specified \a action; otherwise returns false. + + The \a parent, \a row, and \a column details can be used to control + which MIME types are acceptable to different parts of a model when + received via the drag and drop system. +*/ +bool QProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->dropMimeData(data, action, row, column, setSourceModel(parent)); +} + +/*! + Returns the drop actions that are supported by the model; this is + a combination of the individual actions defined in \l Qt::DropActions. + + The selection of drop actions provided by the model will influence the + behavior of the component that started the drag and drop operation. + + \sa \link dnd.html Drag and Drop\endlink +*/ +Qt::DropActions QProxyModel::supportedDropActions() const +{ + Q_D(const QProxyModel); + return d->model->supportedDropActions(); +} + +/*! + Inserts \a count rows into the model, creating new items as children of + the given \a parent. The new rows are inserted before the \a row + specified. If the \a parent item has no children, a single column is + created to contain the required number of rows. + + Returns true if the rows were successfully inserted; otherwise + returns false. + + \sa QAbstractItemModel::insertRows()*/ +bool QProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->insertRows(row, count, setSourceModel(parent)); +} + +/*! + Inserts \a count columns into the model, creating new items as children of + the given \a parent. The new columns are inserted before the \a column + specified. If the \a parent item has no children, a single row is created + to contain the required number of columns. + + Returns true if the columns were successfully inserted; otherwise + returns false. + + \sa QAbstractItemModel::insertColumns() +*/ +bool QProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(const QProxyModel); + return d->model->insertColumns(column, count, setSourceModel(parent)); +} + +/*! + Fetches more child items of the given \a parent. This function is used by views + to tell the model that they can display more data than the model has provided. + + \sa QAbstractItemModel::fetchMore() +*/ +void QProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(const QProxyModel); + d->model->fetchMore(parent); +} + +/*! + Returns the item flags for the given \a index. +*/ +Qt::ItemFlags QProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QProxyModel); + return d->model->flags(setSourceModel(index)); +} + +/*! + Sorts the child items in the specified \a column according to the sort + order defined by \a order. + + \sa QAbstractItemModel::sort() +*/ +void QProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QProxyModel); + d->model->sort(column, order); +} + +/*! + Returns a list of model indexes that each contain the given \a value for + the \a role specified. The search begins at the \a start index and is + performed according to the specified \a flags. The search continues until + the number of matching data items equals \a hits, the last row is reached, + or the search reaches \a start again, depending on whether \c MatchWrap is + specified in \a flags. + + \sa QAbstractItemModel::match() +*/ +QModelIndexList QProxyModel::match(const QModelIndex &start, + int role, const QVariant &value, + int hits, Qt::MatchFlags flags) const +{ + Q_D(const QProxyModel); + return d->model->match(start, role, value, hits, flags); +} + +/*! + Returns the size of the item that corresponds to the specified \a index. +*/ +QSize QProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QProxyModel); + return d->model->span(setSourceModel(index)); +} + +/*! + */ +bool QProxyModel::submit() +{ + Q_D(QProxyModel); + return d->model->submit(); +} + +/*! + */ +void QProxyModel::revert() +{ + Q_D(QProxyModel); + d->model->revert(); +} + +/*! + \internal + Change the model pointer in the given \a source_index to point to the proxy model. + */ +QModelIndex QProxyModel::setProxyModel(const QModelIndex &source_index) const +{ + QModelIndex proxy_index = source_index; + if (proxy_index.isValid()) + proxy_index.m = this; + return proxy_index; +} + +/*! + \internal + Change the model pointer in the given \a proxy_index to point to the source model. + */ +QModelIndex QProxyModel::setSourceModel(const QModelIndex &proxy_index) const +{ + Q_D(const QProxyModel); + QModelIndex source_index = proxy_index; + source_index.m = d->model; + return source_index; +} + +/*! + \internal + Connect to all the signals emitted by given \a model. +*/ +void QProxyModel::connectToModel(const QAbstractItemModel *model) const +{ + connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SIGNAL(headerDataChanged(Qt::Orientation,int,int))); // signal to signal + connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + connect(model, SIGNAL(modelReset()), this, SIGNAL(modelReset())); // signal to signal + connect(model, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); // signal to signal + connect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); // signal to signal +} + +/*! + \internal + Disconnect from all the signals emitted by the given \a model. + */ +void QProxyModel::disconnectFromModel(const QAbstractItemModel *model) const +{ + disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + disconnect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SIGNAL(headerDataChanged(Qt::Orientation,int,int))); // signal to signal + disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(rowsInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + disconnect(model, SIGNAL(modelReset()), this, SIGNAL(modelReset())); // signal to signal + disconnect(model, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); // signal to signal + disconnect(model, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); // signal to signal +} + +/*! + \fn QObject *QProxyModel::parent() const + \internal +*/ + +void QProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &tl,const QModelIndex &br) +{ + Q_Q(QProxyModel); + emit q->dataChanged(q->setProxyModel(tl), q->setProxyModel(br)); +} + +void QProxyModelPrivate::_q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int first ,int last) +{ + Q_Q(QProxyModel); + q->beginInsertRows(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceRowsInserted(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endInsertRows(); +} + +void QProxyModelPrivate::_q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginRemoveRows(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceRowsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endRemoveRows(); +} + +void QProxyModelPrivate::_q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginInsertColumns(q->setProxyModel(parent), first, last); +} + +void QProxyModelPrivate::_q_sourceColumnsInserted(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endInsertColumns(); +} + +void QProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + Q_Q(QProxyModel); + q->beginRemoveColumns(q->setProxyModel(parent), first, last); +} + + +void QProxyModelPrivate::_q_sourceColumnsRemoved(const QModelIndex &, int, int) +{ + Q_Q(QProxyModel); + q->endRemoveColumns(); +} + +QT_END_NAMESPACE + +#include "moc_qproxymodel.cpp" + +#endif // QT_NO_PROXYMODEL diff --git a/src/gui/itemviews/qproxymodel.h b/src/gui/itemviews/qproxymodel.h new file mode 100644 index 0000000000..eeca4b266b --- /dev/null +++ b/src/gui/itemviews/qproxymodel.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYMODEL_H +#define QPROXYMODEL_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PROXYMODEL + +class QProxyModelPrivate; + +class Q_GUI_EXPORT QProxyModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit QProxyModel(QObject *parent = 0); + ~QProxyModel(); + + virtual void setModel(QAbstractItemModel *model); + QAbstractItemModel *model() const; + + // implementing model interface + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void fetchMore(const QModelIndex &parent); + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, + int hits = 1, Qt::MatchFlags flags = + Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + + QSize span(const QModelIndex &index) const; + + bool submit(); + void revert(); + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + +protected: + QProxyModel(QProxyModelPrivate &, QObject *parent = 0); + + QModelIndex setProxyModel(const QModelIndex &source_index) const; + QModelIndex setSourceModel(const QModelIndex &proxy_index) const; + + void connectToModel(const QAbstractItemModel *model) const; + void disconnectFromModel(const QAbstractItemModel *model) const; + +private: + Q_DECLARE_PRIVATE(QProxyModel) + Q_DISABLE_COPY(QProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(const QModelIndex&,const QModelIndex&)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex&,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex&,int,int)) +}; + +#endif // QT_NO_PROXYMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROXYMODEL_H diff --git a/src/gui/itemviews/qproxymodel_p.h b/src/gui/itemviews/qproxymodel_p.h new file mode 100644 index 0000000000..ef4bcbada2 --- /dev/null +++ b/src/gui/itemviews/qproxymodel_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYMODEL_P_H +#define QPROXYMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QAbstractItemModel*. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// +// + +#include "QtCore/qabstractitemmodel.h" +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_PROXYMODEL + +QT_BEGIN_NAMESPACE + +class QEmptyModel : public QAbstractItemModel +{ +public: + explicit QEmptyModel(QObject *parent = 0) : QAbstractItemModel(parent) {} + QModelIndex index(int, int, const QModelIndex &) const { return QModelIndex(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + int rowCount(const QModelIndex &) const { return 0; } + int columnCount(const QModelIndex &) const { return 0; } + bool hasChildren(const QModelIndex &) const { return false; } + QVariant data(const QModelIndex &, int) const { return QVariant(); } +}; + +class QProxyModelPrivate : private QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QProxyModel) + +public: + void _q_sourceDataChanged(const QModelIndex &tl,const QModelIndex &br); + void _q_sourceRowsAboutToBeInserted(const QModelIndex &parent, int first ,int last); + void _q_sourceRowsInserted(const QModelIndex &parent, int first ,int last); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceRowsRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsInserted(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void _q_sourceColumnsRemoved(const QModelIndex &parent, int first, int last); + + QProxyModelPrivate() : QAbstractItemModelPrivate(), model(0) {} + QAbstractItemModel *model; + QEmptyModel empty; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PROXYMODEL + +#endif // QPROXYMODEL_P_H diff --git a/src/gui/itemviews/qsortfilterproxymodel.cpp b/src/gui/itemviews/qsortfilterproxymodel.cpp new file mode 100644 index 0000000000..eb56f56ed4 --- /dev/null +++ b/src/gui/itemviews/qsortfilterproxymodel.cpp @@ -0,0 +1,2542 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsortfilterproxymodel.h" + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include "qitemselectionmodel.h" +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +typedef QList > QModelIndexPairList; + +static inline QSet qVectorToSet(const QVector &vector) +{ + QSet set; + set.reserve(vector.size()); + for(int i=0; i < vector.size(); ++i) + set << vector.at(i); + return set; +} + +class QSortFilterProxyModelLessThan +{ +public: + inline QSortFilterProxyModelLessThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i1, i2); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + +class QSortFilterProxyModelGreaterThan +{ +public: + inline QSortFilterProxyModelGreaterThan(int column, const QModelIndex &parent, + const QAbstractItemModel *source, + const QSortFilterProxyModel *proxy) + : sort_column(column), source_parent(parent), + source_model(source), proxy_model(proxy) {} + + inline bool operator()(int r1, int r2) const + { + QModelIndex i1 = source_model->index(r1, sort_column, source_parent); + QModelIndex i2 = source_model->index(r2, sort_column, source_parent); + return proxy_model->lessThan(i2, i1); + } + +private: + int sort_column; + QModelIndex source_parent; + const QAbstractItemModel *source_model; + const QSortFilterProxyModel *proxy_model; +}; + + +//this struct is used to store what are the rows that are removed +//between a call to rowsAboutToBeRemoved and rowsRemoved +//it avoids readding rows to the mapping that are currently being removed +struct QRowsRemoval +{ + QRowsRemoval(const QModelIndex &parent_source, int start, int end) : parent_source(parent_source), start(start), end(end) + { + } + + QRowsRemoval() : start(-1), end(-1) + { + } + + bool contains(QModelIndex parent, int row) + { + do { + if (parent == parent_source) + return row >= start && row <= end; + row = parent.row(); + parent = parent.parent(); + } while (row >= 0); + return false; + } +private: + QModelIndex parent_source; + int start; + int end; +}; + +class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate +{ + Q_DECLARE_PUBLIC(QSortFilterProxyModel) + +public: + struct Mapping { + QVector source_rows; + QVector source_columns; + QVector proxy_rows; + QVector proxy_columns; + QVector mapped_children; + QHash::const_iterator map_iter; + }; + + mutable QHash source_index_mapping; + + int source_sort_column; + int proxy_sort_column; + Qt::SortOrder sort_order; + Qt::CaseSensitivity sort_casesensitivity; + int sort_role; + bool sort_localeaware; + + int filter_column; + QRegExp filter_regexp; + int filter_role; + + bool dynamic_sortfilter; + QRowsRemoval itemsBeingRemoved; + + QModelIndexPairList saved_persistent_indexes; + + QHash::const_iterator create_mapping( + const QModelIndex &source_parent) const; + QModelIndex proxy_to_source(const QModelIndex &proxyIndex) const; + QModelIndex source_to_proxy(const QModelIndex &sourceIndex) const; + bool can_create_mapping(const QModelIndex &source_parent) const; + + void remove_from_mapping(const QModelIndex &source_parent); + + inline QHash::const_iterator index_to_iterator( + const QModelIndex &proxy_index) const + { + Q_ASSERT(proxy_index.isValid()); + Q_ASSERT(proxy_index.model() == q_func()); + const void *p = proxy_index.internalPointer(); + Q_ASSERT(p); + QHash::const_iterator it = + static_cast(p)->map_iter; + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + return it; + } + + inline QModelIndex create_index(int row, int column, + QHash::const_iterator it) const + { + return q_func()->createIndex(row, column, *it); + } + + void _q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right); + void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end); + + void _q_sourceAboutToBeReset(); + void _q_sourceReset(); + + void _q_sourceLayoutAboutToBeChanged(); + void _q_sourceLayoutChanged(); + + void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceRowsRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsInserted(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, + int start, int end); + void _q_sourceColumnsRemoved(const QModelIndex &source_parent, + int start, int end); + + void _q_clearMapping(); + + void sort(); + bool update_source_sort_column(); + void sort_source_rows(QVector &source_rows, + const QModelIndex &source_parent) const; + QVector > > proxy_intervals_for_source_items_to_add( + const QVector &proxy_to_source, const QVector &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const; + QVector > proxy_intervals_for_source_items( + const QVector &source_to_proxy, const QVector &source_items) const; + void insert_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal = true); + void remove_proxy_interval( + QVector &source_to_proxy, QVector &proxy_to_source, + int proxy_start, int proxy_end, const QModelIndex &proxy_parent, + Qt::Orientation orient, bool emit_signal = true); + void build_source_to_proxy_mapping( + const QVector &proxy_to_source, QVector &source_to_proxy) const; + void source_items_inserted(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_about_to_be_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void source_items_removed(const QModelIndex &source_parent, + int start, int end, Qt::Orientation orient); + void proxy_item_range( + const QVector &source_to_proxy, const QVector &source_items, + int &proxy_low, int &proxy_high) const; + + QModelIndexPairList store_persistent_indexes(); + void update_persistent_indexes(const QModelIndexPairList &source_indexes); + + void filter_changed(const QModelIndex &source_parent = QModelIndex()); + QSet handle_filter_changed( + QVector &source_to_proxy, QVector &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient); + + void updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove); + + virtual void _q_sourceModelDestroyed(); +}; + +typedef QHash IndexMap; + +void QSortFilterProxyModelPrivate::_q_sourceModelDestroyed() +{ + QAbstractProxyModelPrivate::_q_sourceModelDestroyed(); + _q_clearMapping(); +} + +void QSortFilterProxyModelPrivate::remove_from_mapping(const QModelIndex &source_parent) +{ + if (Mapping *m = source_index_mapping.take(source_parent)) { + for (int i = 0; i < m->mapped_children.size(); ++i) + remove_from_mapping(m->mapped_children.at(i)); + delete m; + } +} + +void QSortFilterProxyModelPrivate::_q_clearMapping() +{ + // store the persistent indexes + QModelIndexPairList source_indexes = store_persistent_indexes(); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + // update the persistent indexes + update_persistent_indexes(source_indexes); +} + +IndexMap::const_iterator QSortFilterProxyModelPrivate::create_mapping( + const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it != source_index_mapping.constEnd()) // was mapped already + return it; + + Mapping *m = new Mapping; + + int source_rows = model->rowCount(source_parent); + m->source_rows.reserve(source_rows); + for (int i = 0; i < source_rows; ++i) { + if (q->filterAcceptsRow(i, source_parent)) + m->source_rows.append(i); + } + int source_cols = model->columnCount(source_parent); + m->source_columns.reserve(source_cols); + for (int i = 0; i < source_cols; ++i) { + if (q->filterAcceptsColumn(i, source_parent)) + m->source_columns.append(i); + } + + sort_source_rows(m->source_rows, source_parent); + m->proxy_rows.resize(source_rows); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + m->proxy_columns.resize(source_cols); + build_source_to_proxy_mapping(m->source_columns, m->proxy_columns); + + it = IndexMap::const_iterator(source_index_mapping.insert(source_parent, m)); + m->map_iter = it; + + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it2 = create_mapping(source_grand_parent); + Q_ASSERT(it2 != source_index_mapping.constEnd()); + it2.value()->mapped_children.append(source_parent); + } + + Q_ASSERT(it != source_index_mapping.constEnd()); + Q_ASSERT(it.value()); + + return it; +} + +QModelIndex QSortFilterProxyModelPrivate::proxy_to_source(const QModelIndex &proxy_index) const +{ + if (!proxy_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (proxy_index.model() != q_func()) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapToSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapToSource"); + return QModelIndex(); + } + IndexMap::const_iterator it = index_to_iterator(proxy_index); + Mapping *m = it.value(); + if ((proxy_index.row() >= m->source_rows.size()) || (proxy_index.column() >= m->source_columns.size())) + return QModelIndex(); + int source_row = m->source_rows.at(proxy_index.row()); + int source_col = m->source_columns.at(proxy_index.column()); + return model->index(source_row, source_col, it.key()); +} + +QModelIndex QSortFilterProxyModelPrivate::source_to_proxy(const QModelIndex &source_index) const +{ + if (!source_index.isValid()) + return QModelIndex(); // for now; we may want to be able to set a root index later + if (source_index.model() != model) { + qWarning() << "QSortFilterProxyModel: index from wrong model passed to mapFromSource"; + Q_ASSERT(!"QSortFilterProxyModel: index from wrong model passed to mapFromSource"); + return QModelIndex(); + } + QModelIndex source_parent = source_index.parent(); + IndexMap::const_iterator it = create_mapping(source_parent); + Mapping *m = it.value(); + if ((source_index.row() >= m->proxy_rows.size()) || (source_index.column() >= m->proxy_columns.size())) + return QModelIndex(); + int proxy_row = m->proxy_rows.at(source_index.row()); + int proxy_column = m->proxy_columns.at(source_index.column()); + if (proxy_row == -1 || proxy_column == -1) + return QModelIndex(); + return create_index(proxy_row, proxy_column, it); +} + +bool QSortFilterProxyModelPrivate::can_create_mapping(const QModelIndex &source_parent) const +{ + if (source_parent.isValid()) { + QModelIndex source_grand_parent = source_parent.parent(); + IndexMap::const_iterator it = source_index_mapping.constFind(source_grand_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for the grand parent + return false; + } + Mapping *gm = it.value(); + if (gm->proxy_rows.at(source_parent.row()) == -1 || + gm->proxy_columns.at(source_parent.column()) == -1) { + // Don't care, since parent is filtered + return false; + } + } + return true; +} + +/*! + \internal + + Sorts the existing mappings. +*/ +void QSortFilterProxyModelPrivate::sort() +{ + Q_Q(QSortFilterProxyModel); + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + IndexMap::const_iterator it = source_index_mapping.constBegin(); + for (; it != source_index_mapping.constEnd(); ++it) { + QModelIndex source_parent = it.key(); + Mapping *m = it.value(); + sort_source_rows(m->source_rows, source_parent); + build_source_to_proxy_mapping(m->source_rows, m->proxy_rows); + } + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); +} + +/*! + \internal + + update the source_sort_column according to the proxy_sort_column + return true if the column was changed +*/ +bool QSortFilterProxyModelPrivate::update_source_sort_column() +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_index = q->index(0, proxy_sort_column, QModelIndex()); + int old_source_sort_colum = source_sort_column; + source_sort_column = q->mapToSource(proxy_index).column(); + return old_source_sort_colum != source_sort_column; +} + + +/*! + \internal + + Sorts the given \a source_rows according to current sort column and order. +*/ +void QSortFilterProxyModelPrivate::sort_source_rows( + QVector &source_rows, const QModelIndex &source_parent) const +{ + Q_Q(const QSortFilterProxyModel); + if (source_sort_column >= 0) { + if (sort_order == Qt::AscendingOrder) { + QSortFilterProxyModelLessThan lt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), lt); + } else { + QSortFilterProxyModelGreaterThan gt(source_sort_column, source_parent, model, q); + qStableSort(source_rows.begin(), source_rows.end(), gt); + } + } else { // restore the source model order + qStableSort(source_rows.begin(), source_rows.end()); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and the set of + source items \a source_items (which are part of that mapping), + determines the corresponding proxy item intervals that should + be removed from the proxy model. + + The result is a vector of pairs, where each pair represents a + (start, end) tuple, sorted in ascending order. +*/ +QVector > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items( + const QVector &source_to_proxy, const QVector &source_items) const +{ + QVector > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int source_items_index = 0; + while (source_items_index < source_items.size()) { + int first_proxy_item = source_to_proxy.at(source_items.at(source_items_index)); + Q_ASSERT(first_proxy_item != -1); + int last_proxy_item = first_proxy_item; + ++source_items_index; + // Find end of interval + while ((source_items_index < source_items.size()) + && (source_to_proxy.at(source_items.at(source_items_index)) == last_proxy_item + 1)) { + ++last_proxy_item; + ++source_items_index; + } + // Add interval to result + proxy_intervals.append(QPair(first_proxy_item, last_proxy_item)); + } + qStableSort(proxy_intervals.begin(), proxy_intervals.end()); + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a src_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes \a source_items from this proxy model. + The corresponding proxy items are removed in intervals, so that the proper + rows/columnsRemoved(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::remove_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (already removed) + + QVector > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items(source_to_proxy, source_items); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair interval = proxy_intervals.at(i); + int proxy_start = interval.first; + int proxy_end = interval.second; + remove_proxy_interval(source_to_proxy, proxy_to_source, proxy_start, proxy_end, + proxy_parent, orient, emit_signal); + } +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, removes items from \a proxy_start to \a proxy_end + (inclusive) from this proxy model. +*/ +void QSortFilterProxyModelPrivate::remove_proxy_interval( + QVector &source_to_proxy, QVector &proxy_to_source, int proxy_start, int proxy_end, + const QModelIndex &proxy_parent, Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginRemoveRows(proxy_parent, proxy_start, proxy_end); + else + q->beginRemoveColumns(proxy_parent, proxy_start, proxy_end); + } + + // Remove items from proxy-to-source mapping + proxy_to_source.remove(proxy_start, proxy_end - proxy_start + 1); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endRemoveRows(); + else + q->endRemoveColumns(); + } +} + +/*! + \internal + + Given proxy-to-source mapping \a proxy_to_source and a set of + unmapped source items \a source_items, determines the proxy item + intervals at which the subsets of source items should be inserted + (but does not actually add them to the mapping). + + The result is a vector of pairs, each pair representing a tuple (start, + items), where items is a vector containing the (sorted) source items that + should be inserted at that proxy model location. +*/ +QVector > > QSortFilterProxyModelPrivate::proxy_intervals_for_source_items_to_add( + const QVector &proxy_to_source, const QVector &source_items, + const QModelIndex &source_parent, Qt::Orientation orient) const +{ + Q_Q(const QSortFilterProxyModel); + QVector > > proxy_intervals; + if (source_items.isEmpty()) + return proxy_intervals; + + int proxy_low = 0; + int proxy_item = 0; + int source_items_index = 0; + QVector source_items_in_interval; + bool compare = (orient == Qt::Vertical && source_sort_column >= 0 && dynamic_sortfilter); + while (source_items_index < source_items.size()) { + source_items_in_interval.clear(); + int first_new_source_item = source_items.at(source_items_index); + source_items_in_interval.append(first_new_source_item); + ++source_items_index; + + // Find proxy item at which insertion should be started + int proxy_high = proxy_to_source.size() - 1; + QModelIndex i1 = compare ? model->index(first_new_source_item, source_sort_column, source_parent) : QModelIndex(); + while (proxy_low <= proxy_high) { + proxy_item = (proxy_low + proxy_high) / 2; + if (compare) { + QModelIndex i2 = model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } else { + if (first_new_source_item < proxy_to_source.at(proxy_item)) + proxy_high = proxy_item - 1; + else + proxy_low = proxy_item + 1; + } + } + proxy_item = proxy_low; + + // Find the sequence of new source items that should be inserted here + if (proxy_item >= proxy_to_source.size()) { + for ( ; source_items_index < source_items.size(); ++source_items_index) + source_items_in_interval.append(source_items.at(source_items_index)); + } else { + i1 = compare ? model->index(proxy_to_source.at(proxy_item), source_sort_column, source_parent) : QModelIndex(); + for ( ; source_items_index < source_items.size(); ++source_items_index) { + int new_source_item = source_items.at(source_items_index); + if (compare) { + QModelIndex i2 = model->index(new_source_item, source_sort_column, source_parent); + if ((sort_order == Qt::AscendingOrder) ? q->lessThan(i1, i2) : q->lessThan(i2, i1)) + break; + } else { + if (proxy_to_source.at(proxy_item) < new_source_item) + break; + } + source_items_in_interval.append(new_source_item); + } + } + + // Add interval to result + proxy_intervals.append(QPair >(proxy_item, source_items_in_interval)); + } + return proxy_intervals; +} + +/*! + \internal + + Given source-to-proxy mapping \a source_to_proxy and proxy-to-source mapping + \a proxy_to_source, inserts the given \a source_items into this proxy model. + The source items are inserted in intervals (based on some sorted order), so + that the proper rows/columnsInserted(start, end) signals will be generated. +*/ +void QSortFilterProxyModelPrivate::insert_source_items( + QVector &source_to_proxy, QVector &proxy_to_source, + const QVector &source_items, const QModelIndex &source_parent, + Qt::Orientation orient, bool emit_signal) +{ + Q_Q(QSortFilterProxyModel); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (!proxy_parent.isValid() && source_parent.isValid()) + return; // nothing to do (source_parent is not mapped) + + QVector > > proxy_intervals; + proxy_intervals = proxy_intervals_for_source_items_to_add( + proxy_to_source, source_items, source_parent, orient); + + for (int i = proxy_intervals.size()-1; i >= 0; --i) { + QPair > interval = proxy_intervals.at(i); + int proxy_start = interval.first; + QVector source_items = interval.second; + int proxy_end = proxy_start + source_items.size() - 1; + + if (emit_signal) { + if (orient == Qt::Vertical) + q->beginInsertRows(proxy_parent, proxy_start, proxy_end); + else + q->beginInsertColumns(proxy_parent, proxy_start, proxy_end); + } + + for (int i = 0; i < source_items.size(); ++i) + proxy_to_source.insert(proxy_start + i, source_items.at(i)); + + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + if (emit_signal) { + if (orient == Qt::Vertical) + q->endInsertRows(); + else + q->endInsertColumns(); + } + } +} + +/*! + \internal + + Handles source model items insertion (columnsInserted(), rowsInserted()). + Determines + 1) which of the inserted items to also insert into proxy model (filtering), + 2) where to insert the items into the proxy model (sorting), + then inserts those items. + The items are inserted into the proxy model in intervals (based on + sorted order), so that the proper rows/columnsInserted(start, end) + signals will be generated. +*/ +void QSortFilterProxyModelPrivate::source_items_inserted( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + if (!can_create_mapping(source_parent)) + return; + it = create_mapping(source_parent); + Mapping *m = it.value(); + QModelIndex proxy_parent = q->mapFromSource(source_parent); + if (m->source_rows.count() > 0) { + q->beginInsertRows(proxy_parent, 0, m->source_rows.count() - 1); + q->endInsertRows(); + } + if (m->source_columns.count() > 0) { + q->beginInsertColumns(proxy_parent, 0, m->source_columns.count() - 1); + q->endInsertColumns(); + } + return; + } + + Mapping *m = it.value(); + QVector &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + int delta_item_count = end - start + 1; + int old_item_count = source_to_proxy.size(); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, false); + + // Expand source-to-proxy mapping to account for new items + if (start < 0 || start > source_to_proxy.size()) { + qWarning("QSortFilterProxyModel: invalid inserted rows reported by source model"); + remove_from_mapping(source_parent); + return; + } + source_to_proxy.insert(start, delta_item_count, -1); + + if (start < old_item_count) { + // Adjust existing "stale" indexes in proxy-to-source mapping + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) + proxy_to_source.replace(proxy_item, source_item + delta_item_count); + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + } + + // Figure out which items to add to mapping based on filter + QVector source_items; + for (int i = start; i <= end; ++i) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(i, source_parent) + : q->filterAcceptsColumn(i, source_parent)) { + source_items.append(i); + } + } + + if (model->rowCount(source_parent) == delta_item_count) { + // Items were inserted where there were none before. + // If it was new rows make sure to create mappings for columns so that a + // valid mapping can be retrieved later and vice-versa. + + QVector &orthogonal_proxy_to_source = (orient == Qt::Horizontal) ? m->source_rows : m->source_columns; + QVector &orthogonal_source_to_proxy = (orient == Qt::Horizontal) ? m->proxy_rows : m->proxy_columns; + + if (orthogonal_source_to_proxy.isEmpty()) { + const int ortho_end = (orient == Qt::Horizontal) ? model->rowCount(source_parent) : model->columnCount(source_parent); + + orthogonal_source_to_proxy.resize(ortho_end); + + for (int ortho_item = 0; ortho_item < ortho_end; ++ortho_item) { + if ((orient == Qt::Horizontal) ? q->filterAcceptsRow(ortho_item, source_parent) + : q->filterAcceptsColumn(ortho_item, source_parent)) { + orthogonal_proxy_to_source.append(ortho_item); + } + } + if (orient == Qt::Horizontal) { + // We're reacting to columnsInserted, but we've just inserted new rows. Sort them. + sort_source_rows(orthogonal_proxy_to_source, source_parent); + } + build_source_to_proxy_mapping(orthogonal_proxy_to_source, orthogonal_source_to_proxy); + } + } + + // Sort and insert the items + if (orient == Qt::Vertical) // Only sort rows + sort_source_rows(source_items, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, source_items, source_parent, orient); +} + +/*! + \internal + + Handles source model items removal + (columnsAboutToBeRemoved(), rowsAboutToBeRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_about_to_be_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + // figure out which items to remove + QVector source_items_to_remove; + int proxy_count = proxy_to_source.size(); + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if ((source_item >= start) && (source_item <= end)) + source_items_to_remove.append(source_item); + } + + remove_source_items(source_to_proxy, proxy_to_source, source_items_to_remove, + source_parent, orient); +} + +/*! + \internal + + Handles source model items removal (columnsRemoved(), rowsRemoved()). +*/ +void QSortFilterProxyModelPrivate::source_items_removed( + const QModelIndex &source_parent, int start, int end, Qt::Orientation orient) +{ + if ((start < 0) || (end < 0)) + return; + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + + Mapping *m = it.value(); + QVector &source_to_proxy = (orient == Qt::Vertical) ? m->proxy_rows : m->proxy_columns; + QVector &proxy_to_source = (orient == Qt::Vertical) ? m->source_rows : m->source_columns; + + if (end >= source_to_proxy.size()) + end = source_to_proxy.size() - 1; + + // Shrink the source-to-proxy mapping to reflect the new item count + int delta_item_count = end - start + 1; + source_to_proxy.remove(start, delta_item_count); + + int proxy_count = proxy_to_source.size(); + if (proxy_count > source_to_proxy.size()) { + // mapping is in an inconsistent state -- redo the whole mapping + qWarning("QSortFilterProxyModel: inconsistent changes reported by source model"); + remove_from_mapping(source_parent); + Q_Q(QSortFilterProxyModel); + q->reset(); + return; + } + + // Adjust "stale" indexes in proxy-to-source mapping + for (int proxy_item = 0; proxy_item < proxy_count; ++proxy_item) { + int source_item = proxy_to_source.at(proxy_item); + if (source_item >= start) { + Q_ASSERT(source_item - delta_item_count >= 0); + proxy_to_source.replace(proxy_item, source_item - delta_item_count); + } + } + build_source_to_proxy_mapping(proxy_to_source, source_to_proxy); + + updateChildrenMapping(source_parent, m, orient, start, end, delta_item_count, true); + +} + + +/*! + \internal + updates the mapping of the children when inserting or removing items +*/ +void QSortFilterProxyModelPrivate::updateChildrenMapping(const QModelIndex &source_parent, Mapping *parent_mapping, + Qt::Orientation orient, int start, int end, int delta_item_count, bool remove) +{ + // see if any mapped children should be (re)moved + QVector > moved_source_index_mappings; + QVector::iterator it2 = parent_mapping->mapped_children.begin(); + for ( ; it2 != parent_mapping->mapped_children.end();) { + const QModelIndex source_child_index = *it2; + const int pos = (orient == Qt::Vertical) + ? source_child_index.row() + : source_child_index.column(); + if (pos < start) { + // not affected + ++it2; + } else if (remove && pos <= end) { + // in the removed interval + it2 = parent_mapping->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + // below the removed items -- recompute the index + QModelIndex new_index; + const int newpos = remove ? pos - delta_item_count : pos + delta_item_count; + if (orient == Qt::Vertical) { + new_index = model->index(newpos, + source_child_index.column(), + source_parent); + } else { + new_index = model->index(source_child_index.row(), + newpos, + source_parent); + } + *it2 = new_index; + ++it2; + + // update mapping + Mapping *cm = source_index_mapping.take(source_child_index); + Q_ASSERT(cm); + // we do not reinsert right away, because the new index might be identical with another, old index + moved_source_index_mappings.append(QPair(new_index, cm)); + } + } + + // reinsert moved, mapped indexes + QVector >::iterator it = moved_source_index_mappings.begin(); + for (; it != moved_source_index_mappings.end(); ++it) { +#ifdef QT_STRICT_ITERATORS + source_index_mapping.insert((*it).first, (*it).second); + (*it).second->map_iter = source_index_mapping.constFind((*it).first); +#else + (*it).second->map_iter = source_index_mapping.insert((*it).first, (*it).second); +#endif + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::proxy_item_range( + const QVector &source_to_proxy, const QVector &source_items, + int &proxy_low, int &proxy_high) const +{ + proxy_low = INT_MAX; + proxy_high = INT_MIN; + for (int i = 0; i < source_items.count(); ++i) { + int proxy_item = source_to_proxy.at(source_items.at(i)); + Q_ASSERT(proxy_item != -1); + if (proxy_item < proxy_low) + proxy_low = proxy_item; + if (proxy_item > proxy_high) + proxy_high = proxy_item; + } +} + +/*! + \internal +*/ +void QSortFilterProxyModelPrivate::build_source_to_proxy_mapping( + const QVector &proxy_to_source, QVector &source_to_proxy) const +{ + source_to_proxy.fill(-1); + int proxy_count = proxy_to_source.size(); + for (int i = 0; i < proxy_count; ++i) + source_to_proxy[proxy_to_source.at(i)] = i; +} + +/*! + \internal + + Maps the persistent proxy indexes to source indexes and + returns the list of source indexes. +*/ +QModelIndexPairList QSortFilterProxyModelPrivate::store_persistent_indexes() +{ + Q_Q(QSortFilterProxyModel); + QModelIndexPairList source_indexes; + foreach (QPersistentModelIndexData *data, persistent.indexes) { + QModelIndex proxy_index = data->index; + QModelIndex source_index = q->mapToSource(proxy_index); + source_indexes.append(qMakePair(proxy_index, QPersistentModelIndex(source_index))); + } + return source_indexes; +} + +/*! + \internal + + Maps \a source_indexes to proxy indexes and stores those + as persistent indexes. +*/ +void QSortFilterProxyModelPrivate::update_persistent_indexes( + const QModelIndexPairList &source_indexes) +{ + Q_Q(QSortFilterProxyModel); + QModelIndexList from, to; + for (int i = 0; i < source_indexes.count(); ++i) { + QModelIndex source_index = source_indexes.at(i).second; + QModelIndex old_proxy_index = source_indexes.at(i).first; + create_mapping(source_index.parent()); + QModelIndex proxy_index = q->mapFromSource(source_index); + from << old_proxy_index; + to << proxy_index; + } + q->changePersistentIndexList(from, to); +} + + +/*! + \internal + + Updates the proxy model (adds/removes rows) based on the + new filter. +*/ +void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_parent) +{ + IndexMap::const_iterator it = source_index_mapping.constFind(source_parent); + if (it == source_index_mapping.constEnd()) + return; + Mapping *m = it.value(); + QSet rows_removed = handle_filter_changed(m->proxy_rows, m->source_rows, source_parent, Qt::Vertical); + QSet columns_removed = handle_filter_changed(m->proxy_columns, m->source_columns, source_parent, Qt::Horizontal); + QVector::iterator it2 = m->mapped_children.end(); + while (it2 != m->mapped_children.begin()) { + --it2; + const QModelIndex source_child_index = *it2; + if (rows_removed.contains(source_child_index.row()) || columns_removed.contains(source_child_index.column())) { + it2 = m->mapped_children.erase(it2); + remove_from_mapping(source_child_index); + } else { + filter_changed(source_child_index); + } + } +} + +/*! + \internal + returns the removed items indexes +*/ +QSet QSortFilterProxyModelPrivate::handle_filter_changed( + QVector &source_to_proxy, QVector &proxy_to_source, + const QModelIndex &source_parent, Qt::Orientation orient) +{ + Q_Q(QSortFilterProxyModel); + // Figure out which mapped items to remove + QVector source_items_remove; + for (int i = 0; i < proxy_to_source.count(); ++i) { + const int source_item = proxy_to_source.at(i); + if ((orient == Qt::Vertical) + ? !q->filterAcceptsRow(source_item, source_parent) + : !q->filterAcceptsColumn(source_item, source_parent)) { + // This source item does not satisfy the filter, so it must be removed + source_items_remove.append(source_item); + } + } + // Figure out which non-mapped items to insert + QVector source_items_insert; + int source_count = source_to_proxy.size(); + for (int source_item = 0; source_item < source_count; ++source_item) { + if (source_to_proxy.at(source_item) == -1) { + if ((orient == Qt::Vertical) + ? q->filterAcceptsRow(source_item, source_parent) + : q->filterAcceptsColumn(source_item, source_parent)) { + // This source item satisfies the filter, so it must be added + source_items_insert.append(source_item); + } + } + } + if (!source_items_remove.isEmpty() || !source_items_insert.isEmpty()) { + // Do item removal and insertion + remove_source_items(source_to_proxy, proxy_to_source, + source_items_remove, source_parent, orient); + if (orient == Qt::Vertical) + sort_source_rows(source_items_insert, source_parent); + insert_source_items(source_to_proxy, proxy_to_source, + source_items_insert, source_parent, orient); + } + return qVectorToSet(source_items_remove); +} + +void QSortFilterProxyModelPrivate::_q_sourceDataChanged(const QModelIndex &source_top_left, + const QModelIndex &source_bottom_right) +{ + Q_Q(QSortFilterProxyModel); + if (!source_top_left.isValid() || !source_bottom_right.isValid()) + return; + QModelIndex source_parent = source_top_left.parent(); + IndexMap::const_iterator it = source_index_mapping.find(source_parent); + if (it == source_index_mapping.constEnd()) { + // Don't care, since we don't have mapping for this index + return; + } + Mapping *m = it.value(); + + // Figure out how the source changes affect us + QVector source_rows_remove; + QVector source_rows_insert; + QVector source_rows_change; + QVector source_rows_resort; + int end = qMin(source_bottom_right.row(), m->proxy_rows.count() - 1); + for (int source_row = source_top_left.row(); source_row <= end; ++source_row) { + if (dynamic_sortfilter) { + if (m->proxy_rows.at(source_row) != -1) { + if (!q->filterAcceptsRow(source_row, source_parent)) { + // This source row no longer satisfies the filter, so it must be removed + source_rows_remove.append(source_row); + } else if (source_sort_column >= source_top_left.column() && source_sort_column <= source_bottom_right.column()) { + // This source row has changed in a way that may affect sorted order + source_rows_resort.append(source_row); + } else { + // This row has simply changed, without affecting filtering nor sorting + source_rows_change.append(source_row); + } + } else { + if (!itemsBeingRemoved.contains(source_parent, source_row) && q->filterAcceptsRow(source_row, source_parent)) { + // This source row now satisfies the filter, so it must be added + source_rows_insert.append(source_row); + } + } + } else { + if (m->proxy_rows.at(source_row) != -1) + source_rows_change.append(source_row); + } + } + + if (!source_rows_remove.isEmpty()) { + remove_source_items(m->proxy_rows, m->source_rows, + source_rows_remove, source_parent, Qt::Vertical); + QSet source_rows_remove_set = qVectorToSet(source_rows_remove); + QVector::iterator it = m->mapped_children.end(); + while (it != m->mapped_children.begin()) { + --it; + const QModelIndex source_child_index = *it; + if (source_rows_remove_set.contains(source_child_index.row())) { + it = m->mapped_children.erase(it); + remove_from_mapping(source_child_index); + } + } + } + + if (!source_rows_resort.isEmpty()) { + // Re-sort the rows + emit q->layoutAboutToBeChanged(); + QModelIndexPairList source_indexes = store_persistent_indexes(); + remove_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + sort_source_rows(source_rows_resort, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, source_rows_resort, + source_parent, Qt::Vertical, false); + update_persistent_indexes(source_indexes); + emit q->layoutChanged(); + // Make sure we also emit dataChanged for the rows + source_rows_change += source_rows_resort; + } + + if (!source_rows_change.isEmpty()) { + // Find the proxy row range + int proxy_start_row; + int proxy_end_row; + proxy_item_range(m->proxy_rows, source_rows_change, + proxy_start_row, proxy_end_row); + // ### Find the proxy column range also + if (proxy_end_row >= 0) { + // the row was accepted, but some columns might still be filtered out + int source_left_column = source_top_left.column(); + while (source_left_column < source_bottom_right.column() + && m->proxy_columns.at(source_left_column) == -1) + ++source_left_column; + const QModelIndex proxy_top_left = create_index( + proxy_start_row, m->proxy_columns.at(source_left_column), it); + int source_right_column = source_bottom_right.column(); + while (source_right_column > source_top_left.column() + && m->proxy_columns.at(source_right_column) == -1) + --source_right_column; + const QModelIndex proxy_bottom_right = create_index( + proxy_end_row, m->proxy_columns.at(source_right_column), it); + emit q->dataChanged(proxy_top_left, proxy_bottom_right); + } + } + + if (!source_rows_insert.isEmpty()) { + sort_source_rows(source_rows_insert, source_parent); + insert_source_items(m->proxy_rows, m->source_rows, + source_rows_insert, source_parent, Qt::Vertical); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceHeaderDataChanged(Qt::Orientation orientation, + int start, int end) +{ + Q_Q(QSortFilterProxyModel); + Mapping *m = create_mapping(QModelIndex()).value(); + int proxy_start = (orientation == Qt::Vertical + ? m->proxy_rows.at(start) + : m->proxy_columns.at(start)); + int proxy_end = (orientation == Qt::Vertical + ? m->proxy_rows.at(end) + : m->proxy_columns.at(end)); + emit q->headerDataChanged(orientation, proxy_start, proxy_end); +} + +void QSortFilterProxyModelPrivate::_q_sourceAboutToBeReset() +{ + Q_Q(QSortFilterProxyModel); + q->beginResetModel(); +} + +void QSortFilterProxyModelPrivate::_q_sourceReset() +{ + Q_Q(QSortFilterProxyModel); + invalidatePersistentIndexes(); + _q_clearMapping(); + // All internal structures are deleted in clear() + q->endResetModel(); + update_source_sort_column(); + if (dynamic_sortfilter) + sort(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged() +{ + Q_Q(QSortFilterProxyModel); + saved_persistent_indexes.clear(); + emit q->layoutAboutToBeChanged(); + if (persistent.indexes.isEmpty()) + return; + + saved_persistent_indexes = store_persistent_indexes(); +} + +void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged() +{ + Q_Q(QSortFilterProxyModel); + + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + + update_persistent_indexes(saved_persistent_indexes); + saved_persistent_indexes.clear(); + + if (dynamic_sortfilter && update_source_sort_column()) { + //update_source_sort_column might have created wrong mapping so we have to clear it again + qDeleteAll(source_index_mapping); + source_index_mapping.clear(); + } + + emit q->layoutChanged(); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits rowsAboutToBeInserted in insert_source_items + if (can_create_mapping(source_parent)) + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsInserted( + const QModelIndex &source_parent, int start, int end) +{ + source_items_inserted(source_parent, start, end, Qt::Vertical); + if (update_source_sort_column() && dynamic_sortfilter) //previous call to update_source_sort_column may fail if the model has no column. + sort(); // now it should succeed so we need to make sure to sort again +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + itemsBeingRemoved = QRowsRemoval(source_parent, start, end); + source_items_about_to_be_removed(source_parent, start, end, + Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceRowsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + itemsBeingRemoved = QRowsRemoval(); + source_items_removed(source_parent, start, end, Qt::Vertical); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_UNUSED(start); + Q_UNUSED(end); + //Force the creation of a mapping now, even if its empty. + //We need it because the proxy can be acessed at the moment it emits columnsAboutToBeInserted in insert_source_items + if (can_create_mapping(source_parent)) + create_mapping(source_parent); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsInserted( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_inserted(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (source_sort_column == -1) { + //we update the source_sort_column depending on the proxy_sort_column + if (update_source_sort_column() && dynamic_sortfilter) + sort(); + } else { + if (start <= source_sort_column) + source_sort_column += end - start + 1; + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); + } +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsAboutToBeRemoved( + const QModelIndex &source_parent, int start, int end) +{ + source_items_about_to_be_removed(source_parent, start, end, + Qt::Horizontal); +} + +void QSortFilterProxyModelPrivate::_q_sourceColumnsRemoved( + const QModelIndex &source_parent, int start, int end) +{ + Q_Q(const QSortFilterProxyModel); + source_items_removed(source_parent, start, end, Qt::Horizontal); + + if (source_parent.isValid()) + return; //we sort according to the root column only + if (start <= source_sort_column) { + if (end < source_sort_column) + source_sort_column -= end - start + 1; + else + source_sort_column = -1; + } + + proxy_sort_column = q->mapFromSource(model->index(0,source_sort_column, source_parent)).column(); +} + +/*! + \since 4.1 + \class QSortFilterProxyModel + \brief The QSortFilterProxyModel class provides support for sorting and + filtering data passed between another model and a view. + + \ingroup model-view + + QSortFilterProxyModel can be used for sorting items, filtering out items, + or both. The model transforms the structure of a source model by mapping + the model indexes it supplies to new indexes, corresponding to different + locations, for views to use. This approach allows a given source model to + be restructured as far as views are concerned without requiring any + transformations on the underlying data, and without duplicating the data in + memory. + + Let's assume that we want to sort and filter the items provided by a custom + model. The code to set up the model and the view, \e without sorting and + filtering, would look like this: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 1 + + To add sorting and filtering support to \c MyItemModel, we need to create + a QSortFilterProxyModel, call setSourceModel() with the \c MyItemModel as + argument, and install the QSortFilterProxyModel on the view: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 0 + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 2 + + At this point, neither sorting nor filtering is enabled; the original data + is displayed in the view. Any changes made through the + QSortFilterProxyModel are applied to the original model. + + The QSortFilterProxyModel acts as a wrapper for the original model. If you + need to convert source \l{QModelIndex}es to sorted/filtered model indexes + or vice versa, use mapToSource(), mapFromSource(), mapSelectionToSource(), + and mapSelectionFromSource(). + + \note By default, the model does not dynamically re-sort and re-filter data + whenever the original model changes. This behavior can be changed by + setting the \l{QSortFilterProxyModel::dynamicSortFilter}{dynamicSortFilter} + property. + + The \l{itemviews/basicsortfiltermodel}{Basic Sort/Filter Model} and + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} examples + illustrate how to use QSortFilterProxyModel to perform basic sorting and + filtering and how to subclass it to implement custom behavior. + + \section1 Sorting + + QTableView and QTreeView have a + \l{QTreeView::sortingEnabled}{sortingEnabled} property that controls + whether the user can sort the view by clicking the view's horizontal + header. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 3 + + When this feature is on (the default is off), clicking on a header section + sorts the items according to that column. By clicking repeatedly, the user + can alternate between ascending and descending order. + + \image qsortfilterproxymodel-sorting.png A sorted QTreeView + + Behind the scene, the view calls the sort() virtual function on the model + to reorder the data in the model. To make your data sortable, you can + either implement sort() in your model, or use a QSortFilterProxyModel to + wrap your model -- QSortFilterProxyModel provides a generic sort() + reimplementation that operates on the sortRole() (Qt::DisplayRole by + default) of the items and that understands several data types, including + \c int, QString, and QDateTime. For hierarchical models, sorting is applied + recursively to all child items. String comparisons are case sensitive by + default; this can be changed by setting the \l{QSortFilterProxyModel::} + {sortCaseSensitivity} property. + + Custom sorting behavior is achieved by subclassing + QSortFilterProxyModel and reimplementing lessThan(), which is + used to compare items. For example: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 5 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + An alternative approach to sorting is to disable sorting on the view and to + impose a certain order to the user. This is done by explicitly calling + sort() with the desired column and order as arguments on the + QSortFilterProxyModel (or on the original model if it implements sort()). + For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 4 + + QSortFilterProxyModel can be sorted by column -1, in which case it returns + to the sort order of the underlying source model. + + \section1 Filtering + + In addition to sorting, QSortFilterProxyModel can be used to hide items + that do not match a certain filter. The filter is specified using a QRegExp + object and is applied to the filterRole() (Qt::DisplayRole by default) of + each item, for a given column. The QRegExp object can be used to match a + regular expression, a wildcard pattern, or a fixed string. For example: + + \snippet doc/src/snippets/qsortfilterproxymodel-details/main.cpp 5 + + For hierarchical models, the filter is applied recursively to all children. + If a parent item doesn't match the filter, none of its children will be + shown. + + A common use case is to let the user specify the filter regexp, wildcard + pattern, or fixed string in a QLineEdit and to connect the + \l{QLineEdit::textChanged()}{textChanged()} signal to setFilterRegExp(), + setFilterWildcard(), or setFilterFixedString() to reapply the filter. + + Custom filtering behavior can be achieved by reimplementing the + filterAcceptsRow() and filterAcceptsColumn() functions. For + example (from the \l{itemviews/customsortfiltermodel} + {Custom Sort/Filter Model} example), the following implementation ignores + the \l{QSortFilterProxyModel::filterKeyColumn}{filterKeyColumn} property + and performs filtering on columns 0, 1, and 2: + + \snippet examples/itemviews/customsortfiltermodel/mysortfilterproxymodel.cpp 3 + + (This code snippet comes from the + \l{itemviews/customsortfiltermodel}{Custom Sort/Filter Model} + example.) + + If you are working with large amounts of filtering and have to invoke + invalidateFilter() repeatedly, using reset() may be more efficient, + depending on the implementation of your model. However, reset() returns the + proxy model to its original state, losing selection information, and will + cause the proxy model to be repopulated. + + \section1 Subclassing + + Since QAbstractProxyModel and its subclasses are derived from + QAbstractItemModel, much of the same advice about subclassing normal models + also applies to proxy models. In addition, it is worth noting that many of + the default implementations of functions in this class are written so that + they call the equivalent functions in the relevant source model. This + simple proxying mechanism may need to be overridden for source models with + more complex behavior; for example, if the source model provides a custom + hasChildren() implementation, you should also provide one in the proxy + model. + + \note Some general guidelines for subclassing models are available in the + \l{Model Subclassing Reference}. + + \sa QAbstractProxyModel, QAbstractItemModel, {Model/View Programming}, + {Basic Sort/Filter Model Example}, {Custom Sort/Filter Model Example} +*/ + +/*! + Constructs a sorting filter model with the given \a parent. +*/ + +QSortFilterProxyModel::QSortFilterProxyModel(QObject *parent) + : QAbstractProxyModel(*new QSortFilterProxyModelPrivate, parent) +{ + Q_D(QSortFilterProxyModel); + d->proxy_sort_column = d->source_sort_column = -1; + d->sort_order = Qt::AscendingOrder; + d->sort_casesensitivity = Qt::CaseSensitive; + d->sort_role = Qt::DisplayRole; + d->sort_localeaware = false; + d->filter_column = 0; + d->filter_role = Qt::DisplayRole; + d->dynamic_sortfilter = false; + connect(this, SIGNAL(modelReset()), this, SLOT(_q_clearMapping())); +} + +/*! + Destroys this sorting filter model. +*/ +QSortFilterProxyModel::~QSortFilterProxyModel() +{ + Q_D(QSortFilterProxyModel); + qDeleteAll(d->source_index_mapping); + d->source_index_mapping.clear(); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + Q_D(QSortFilterProxyModel); + + beginResetModel(); + + disconnect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + disconnect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + disconnect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + QAbstractProxyModel::setSourceModel(sourceModel); + + connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); + + connect(d->model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)), + this, SLOT(_q_sourceHeaderDataChanged(Qt::Orientation,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsInserted(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsAboutToBeRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sourceColumnsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(_q_sourceLayoutAboutToBeChanged())); + + connect(d->model, SIGNAL(layoutChanged()), + this, SLOT(_q_sourceLayoutChanged())); + + connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_sourceAboutToBeReset())); + connect(d->model, SIGNAL(modelReset()), this, SLOT(_q_sourceReset())); + + d->_q_clearMapping(); + endResetModel(); + if (d->update_source_sort_column() && d->dynamic_sortfilter) + d->sort(); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + if (row < 0 || column < 0) + return QModelIndex(); + + QModelIndex source_parent = mapToSource(parent); // parent is already mapped at this point + IndexMap::const_iterator it = d->create_mapping(source_parent); // but make sure that the children are mapped + if (it.value()->source_rows.count() <= row || it.value()->source_columns.count() <= column) + return QModelIndex(); + + return d->create_index(row, column, it); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::parent(const QModelIndex &child) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(child)) + return QModelIndex(); + IndexMap::const_iterator it = d->index_to_iterator(child); + Q_ASSERT(it != d->source_index_mapping.constEnd()); + QModelIndex source_parent = it.key(); + QModelIndex proxy_parent = mapFromSource(source_parent); + return proxy_parent; +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_rows.count(); +} + +/*! + \reimp +*/ +int QSortFilterProxyModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return 0; + IndexMap::const_iterator it = d->create_mapping(source_parent); + return it.value()->source_columns.count(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + if (!d->model->hasChildren(source_parent)) + return false; + + if (d->model->canFetchMore(source_parent)) + return true; //we assume we might have children that can be fetched + + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + return m->source_rows.count() != 0 && m->source_columns.count() != 0; +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QVariant(); + return d->model->data(source_index, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return false; + return d->model->setData(source_index, value, role); +} + +/*! + \reimp +*/ +QVariant QSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::headerData(section, orientation, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return QVariant(); + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return QVariant(); + source_section = it.value()->source_columns.at(section); + } + return d->model->headerData(source_section, orientation, role); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + Q_D(QSortFilterProxyModel); + IndexMap::const_iterator it = d->create_mapping(QModelIndex()); + if (it.value()->source_rows.count() * it.value()->source_columns.count() > 0) + return QAbstractProxyModel::setHeaderData(section, orientation, value, role); + int source_section; + if (orientation == Qt::Vertical) { + if (section < 0 || section >= it.value()->source_rows.count()) + return false; + source_section = it.value()->source_rows.at(section); + } else { + if (section < 0 || section >= it.value()->source_columns.count()) + return false; + source_section = it.value()->source_columns.at(section); + } + return d->model->setHeaderData(source_section, orientation, value, role); +} + +/*! + \reimp +*/ +QMimeData *QSortFilterProxyModel::mimeData(const QModelIndexList &indexes) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndexList source_indexes; + for (int i = 0; i < indexes.count(); ++i) + source_indexes << mapToSource(indexes.at(i)); + return d->model->mimeData(source_indexes); +} + +/*! + \reimp +*/ +QStringList QSortFilterProxyModel::mimeTypes() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->mimeTypes(); +} + +/*! + \reimp +*/ +Qt::DropActions QSortFilterProxyModel::supportedDropActions() const +{ + Q_D(const QSortFilterProxyModel); + return d->model->supportedDropActions(); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if ((row == -1) && (column == -1)) + return d->model->dropMimeData(data, action, -1, -1, mapToSource(parent)); + int source_destination_row = -1; + int source_destination_column = -1; + QModelIndex source_parent; + if (row == rowCount(parent)) { + source_parent = mapToSource(parent); + source_destination_row = d->model->rowCount(source_parent); + } else { + QModelIndex proxy_index = index(row, column, parent); + QModelIndex source_index = mapToSource(proxy_index); + source_destination_row = source_index.row(); + source_destination_column = source_index.column(); + source_parent = source_index.parent(); + } + return d->model->dropMimeData(data, action, source_destination_row, + source_destination_column, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row > m->source_rows.count()) + return false; + int source_row = (row >= m->source_rows.count() + ? m->source_rows.count() + : m->source_rows.at(row)); + return d->model->insertRows(source_row, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0|| count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column > m->source_columns.count()) + return false; + int source_column = (column >= m->source_columns.count() + ? m->source_columns.count() + : m->source_columns.at(column)); + return d->model->insertColumns(source_column, count, source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (row < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (row + count > m->source_rows.count()) + return false; + if ((count == 1) + || ((d->source_sort_column < 0) && (m->proxy_rows.count() == m->source_rows.count()))) { + int source_row = m->source_rows.at(row); + return d->model->removeRows(source_row, count, source_parent); + } + // remove corresponding source intervals + // ### if this proves to be slow, we can switch to single-row removal + QVector rows; + for (int i = row; i < row + count; ++i) + rows.append(m->source_rows.at(i)); + qSort(rows.begin(), rows.end()); + + int pos = rows.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = rows.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (rows.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeRows(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + if (column < 0 || count <= 0) + return false; + QModelIndex source_parent = mapToSource(parent); + if (parent.isValid() && !source_parent.isValid()) + return false; + QSortFilterProxyModelPrivate::Mapping *m = d->create_mapping(source_parent).value(); + if (column + count > m->source_columns.count()) + return false; + if ((count == 1) || (m->proxy_columns.count() == m->source_columns.count())) { + int source_column = m->source_columns.at(column); + return d->model->removeColumns(source_column, count, source_parent); + } + // remove corresponding source intervals + QVector columns; + for (int i = column; i < column + count; ++i) + columns.append(m->source_columns.at(i)); + + int pos = columns.count() - 1; + bool ok = true; + while (pos >= 0) { + const int source_end = columns.at(pos--); + int source_start = source_end; + while ((pos >= 0) && (columns.at(pos) == (source_start - 1))) { + --source_start; + --pos; + } + ok = ok && d->model->removeColumns(source_start, source_end - source_start + 1, + source_parent); + } + return ok; +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::fetchMore(const QModelIndex &parent) +{ + Q_D(QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + d->model->fetchMore(source_parent); +} + +/*! + \reimp +*/ +bool QSortFilterProxyModel::canFetchMore(const QModelIndex &parent) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_parent; + if (d->indexValid(parent)) + source_parent = mapToSource(parent); + return d->model->canFetchMore(source_parent); +} + +/*! + \reimp +*/ +Qt::ItemFlags QSortFilterProxyModel::flags(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index; + if (d->indexValid(index)) + source_index = mapToSource(index); + return d->model->flags(source_index); +} + +/*! + \reimp +*/ +QModelIndex QSortFilterProxyModel::buddy(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + if (!d->indexValid(index)) + return QModelIndex(); + QModelIndex source_index = mapToSource(index); + QModelIndex source_buddy = d->model->buddy(source_index); + if (source_index == source_buddy) + return index; + return mapFromSource(source_buddy); +} + +/*! + \reimp +*/ +QModelIndexList QSortFilterProxyModel::match(const QModelIndex &start, int role, + const QVariant &value, int hits, + Qt::MatchFlags flags) const +{ + return QAbstractProxyModel::match(start, role, value, hits, flags); +} + +/*! + \reimp +*/ +QSize QSortFilterProxyModel::span(const QModelIndex &index) const +{ + Q_D(const QSortFilterProxyModel); + QModelIndex source_index = mapToSource(index); + if (index.isValid() && !source_index.isValid()) + return QSize(); + return d->model->span(source_index); +} + +/*! + \reimp +*/ +void QSortFilterProxyModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QSortFilterProxyModel); + if (d->dynamic_sortfilter && d->proxy_sort_column == column && d->sort_order == order) + return; + d->sort_order = order; + d->proxy_sort_column = column; + d->update_source_sort_column(); + d->sort(); +} + +/*! + \since 4.5 + \brief the column currently used for sorting + + This returns the most recently used sort column. +*/ +int QSortFilterProxyModel::sortColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_sort_column; +} + +/*! + \since 4.5 + \brief the order currently used for sorting + + This returns the most recently used sort order. +*/ +Qt::SortOrder QSortFilterProxyModel::sortOrder() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_order; +} + +/*! + \property QSortFilterProxyModel::filterRegExp + \brief the QRegExp used to filter the contents of the source model + + Setting this property overwrites the current + \l{QSortFilterProxyModel::filterCaseSensitivity}{filterCaseSensitivity}. + By default, the QRegExp is an empty string matching all contents. + + If no QRegExp or an empty string is set, everything in the source model + will be accepted. + + \sa filterCaseSensitivity, setFilterWildcard(), setFilterFixedString() +*/ +QRegExp QSortFilterProxyModel::filterRegExp() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp; +} + +void QSortFilterProxyModel::setFilterRegExp(const QRegExp ®Exp) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp = regExp; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterKeyColumn + \brief the column where the key used to filter the contents of the + source model is read from. + + The default value is 0. If the value is -1, the keys will be read + from all columns. +*/ +int QSortFilterProxyModel::filterKeyColumn() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_column; +} + +void QSortFilterProxyModel::setFilterKeyColumn(int column) +{ + Q_D(QSortFilterProxyModel); + d->filter_column = column; + d->filter_changed(); +} + +/*! + \property QSortFilterProxyModel::filterCaseSensitivity + + \brief the case sensitivity of the QRegExp pattern used to filter the + contents of the source model + + By default, the filter is case sensitive. + + \sa filterRegExp, sortCaseSensitivity +*/ +Qt::CaseSensitivity QSortFilterProxyModel::filterCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_regexp.caseSensitivity(); +} + +void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (cs == d->filter_regexp.caseSensitivity()) + return; + d->filter_regexp.setCaseSensitivity(cs); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortCaseSensitivity + \brief the case sensitivity setting used for comparing strings when sorting + + By default, sorting is case sensitive. + + \sa filterCaseSensitivity, lessThan() +*/ +Qt::CaseSensitivity QSortFilterProxyModel::sortCaseSensitivity() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_casesensitivity; +} + +void QSortFilterProxyModel::setSortCaseSensitivity(Qt::CaseSensitivity cs) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_casesensitivity == cs) + return; + + d->sort_casesensitivity = cs; + d->sort(); +} + +/*! + \since 4.3 + \property QSortFilterProxyModel::isSortLocaleAware + \brief the local aware setting used for comparing strings when sorting + + By default, sorting is not local aware. + + \sa sortCaseSensitivity, lessThan() +*/ +bool QSortFilterProxyModel::isSortLocaleAware() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_localeaware; +} + +void QSortFilterProxyModel::setSortLocaleAware(bool on) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_localeaware == on) + return; + + d->sort_localeaware = on; + d->sort(); +} + +/*! + \overload + + Sets the regular expression used to filter the contents + of the source model to \a pattern. + + \sa setFilterCaseSensitivity(), setFilterWildcard(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterRegExp(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::RegExp); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the wildcard expression used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterFixedString(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterWildcard(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::Wildcard); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + Sets the fixed string used to filter the contents + of the source model to the given \a pattern. + + \sa setFilterCaseSensitivity(), setFilterRegExp(), setFilterWildcard(), filterRegExp() +*/ +void QSortFilterProxyModel::setFilterFixedString(const QString &pattern) +{ + Q_D(QSortFilterProxyModel); + d->filter_regexp.setPatternSyntax(QRegExp::FixedString); + d->filter_regexp.setPattern(pattern); + d->filter_changed(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::dynamicSortFilter + \brief whether the proxy model is dynamically sorted and filtered + whenever the contents of the source model change + + Note that you should not update the source model through the proxy + model when dynamicSortFilter is true. For instance, if you set the + proxy model on a QComboBox, then using functions that update the + model, e.g., \l{QComboBox::}{addItem()}, will not work as + expected. An alternative is to set dynamicSortFilter to false and + call \l{QSortFilterProxyModel::}{sort()} after adding items to the + QComboBox. + + The default value is false. +*/ +bool QSortFilterProxyModel::dynamicSortFilter() const +{ + Q_D(const QSortFilterProxyModel); + return d->dynamic_sortfilter; +} + +void QSortFilterProxyModel::setDynamicSortFilter(bool enable) +{ + Q_D(QSortFilterProxyModel); + d->dynamic_sortfilter = enable; + if (enable) + d->sort(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::sortRole + \brief the item role that is used to query the source model's data when sorting items + + The default value is Qt::DisplayRole. + + \sa lessThan() +*/ +int QSortFilterProxyModel::sortRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->sort_role; +} + +void QSortFilterProxyModel::setSortRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->sort_role == role) + return; + d->sort_role = role; + d->sort(); +} + +/*! + \since 4.2 + \property QSortFilterProxyModel::filterRole + \brief the item role that is used to query the source model's data when filtering items + + The default value is Qt::DisplayRole. + + \sa filterAcceptsRow() +*/ +int QSortFilterProxyModel::filterRole() const +{ + Q_D(const QSortFilterProxyModel); + return d->filter_role; +} + +void QSortFilterProxyModel::setFilterRole(int role) +{ + Q_D(QSortFilterProxyModel); + if (d->filter_role == role) + return; + d->filter_role = role; + d->filter_changed(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidate() instead. +*/ +void QSortFilterProxyModel::clear() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->_q_clearMapping(); + emit layoutChanged(); +} + +/*! + \since 4.3 + + Invalidates the current sorting and filtering. + + \sa invalidateFilter() +*/ +void QSortFilterProxyModel::invalidate() +{ + Q_D(QSortFilterProxyModel); + emit layoutAboutToBeChanged(); + d->_q_clearMapping(); + emit layoutChanged(); +} + +/*! + \obsolete + + This function is obsolete. Use invalidateFilter() instead. +*/ +void QSortFilterProxyModel::filterChanged() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + \since 4.3 + + Invalidates the current filtering. + + This function should be called if you are implementing custom filtering + (e.g. filterAcceptsRow()), and your filter parameters have changed. + + \sa invalidate() +*/ +void QSortFilterProxyModel::invalidateFilter() +{ + Q_D(QSortFilterProxyModel); + d->filter_changed(); +} + +/*! + Returns true if the value of the item referred to by the given + index \a left is less than the value of the item referred to by + the given index \a right, otherwise returns false. + + This function is used as the < operator when sorting, and handles + the following QVariant types: + + \list + \o QVariant::Int + \o QVariant::UInt + \o QVariant::LongLong + \o QVariant::ULongLong + \o QVariant::Double + \o QVariant::Char + \o QVariant::Date + \o QVariant::Time + \o QVariant::DateTime + \o QVariant::String + \endlist + + Any other type will be converted to a QString using + QVariant::toString(). + + Comparison of \l{QString}s is case sensitive by default; this can + be changed using the \l {QSortFilterProxyModel::sortCaseSensitivity} + {sortCaseSensitivity} property. + + By default, the Qt::DisplayRole associated with the + \l{QModelIndex}es is used for comparisons. This can be changed by + setting the \l {QSortFilterProxyModel::sortRole} {sortRole} property. + + \note The indices passed in correspond to the source model. + + \sa sortRole, sortCaseSensitivity, dynamicSortFilter +*/ +bool QSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + Q_D(const QSortFilterProxyModel); + QVariant l = (left.model() ? left.model()->data(left, d->sort_role) : QVariant()); + QVariant r = (right.model() ? right.model()->data(right, d->sort_role) : QVariant()); + switch (l.userType()) { + case QVariant::Invalid: + return (r.type() != QVariant::Invalid); + case QVariant::Int: + return l.toInt() < r.toInt(); + case QVariant::UInt: + return l.toUInt() < r.toUInt(); + case QVariant::LongLong: + return l.toLongLong() < r.toLongLong(); + case QVariant::ULongLong: + return l.toULongLong() < r.toULongLong(); + case QMetaType::Float: + return l.toFloat() < r.toFloat(); + case QVariant::Double: + return l.toDouble() < r.toDouble(); + case QVariant::Char: + return l.toChar() < r.toChar(); + case QVariant::Date: + return l.toDate() < r.toDate(); + case QVariant::Time: + return l.toTime() < r.toTime(); + case QVariant::DateTime: + return l.toDateTime() < r.toDateTime(); + case QVariant::String: + default: + if (d->sort_localeaware) + return l.toString().localeAwareCompare(r.toString()) < 0; + else + return l.toString().compare(r.toString(), d->sort_casesensitivity) < 0; + } + return false; +} + +/*! + Returns true if the item in the row indicated by the given \a source_row + and \a source_parent should be included in the model; otherwise returns + false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the + \l{QSortFilterProxyModel::filterRole}{filterRole} property. + + \sa filterAcceptsColumn(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_D(const QSortFilterProxyModel); + if (d->filter_regexp.isEmpty()) + return true; + if (d->filter_column == -1) { + int column_count = d->model->columnCount(source_parent); + for (int column = 0; column < column_count; ++column) { + QModelIndex source_index = d->model->index(source_row, column, source_parent); + QString key = d->model->data(source_index, d->filter_role).toString(); + if (key.contains(d->filter_regexp)) + return true; + } + return false; + } + QModelIndex source_index = d->model->index(source_row, d->filter_column, source_parent); + if (!source_index.isValid()) // the column may not exist + return true; + QString key = d->model->data(source_index, d->filter_role).toString(); + return key.contains(d->filter_regexp); +} + +/*! + Returns true if the item in the column indicated by the given \a source_column + and \a source_parent should be included in the model; otherwise returns false. + + The default implementation returns true if the value held by the relevant item + matches the filter string, wildcard string or regular expression. + + \note By default, the Qt::DisplayRole is used to determine if the row + should be accepted or not. This can be changed by setting the \l + filterRole property. + + \sa filterAcceptsRow(), setFilterFixedString(), setFilterRegExp(), setFilterWildcard() +*/ +bool QSortFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_column); + Q_UNUSED(source_parent); + return true; +} + +/*! + Returns the source model index corresponding to the given \a + proxyIndex from the sorting filter model. + + \sa mapFromSource() +*/ +QModelIndex QSortFilterProxyModel::mapToSource(const QModelIndex &proxyIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->proxy_to_source(proxyIndex); +} + +/*! + Returns the model index in the QSortFilterProxyModel given the \a + sourceIndex from the source model. + + \sa mapToSource() +*/ +QModelIndex QSortFilterProxyModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + Q_D(const QSortFilterProxyModel); + return d->source_to_proxy(sourceIndex); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionToSource(const QItemSelection &proxySelection) const +{ + return QAbstractProxyModel::mapSelectionToSource(proxySelection); +} + +/*! + \reimp +*/ +QItemSelection QSortFilterProxyModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const +{ + return QAbstractProxyModel::mapSelectionFromSource(sourceSelection); +} + +/*! + \fn QObject *QSortFilterProxyModel::parent() const + \internal +*/ + +QT_END_NAMESPACE + +#include "moc_qsortfilterproxymodel.cpp" + +#endif // QT_NO_SORTFILTERPROXYMODEL diff --git a/src/gui/itemviews/qsortfilterproxymodel.h b/src/gui/itemviews/qsortfilterproxymodel.h new file mode 100644 index 0000000000..afeaa6936d --- /dev/null +++ b/src/gui/itemviews/qsortfilterproxymodel.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSORTFILTERPROXYMODEL_H +#define QSORTFILTERPROXYMODEL_H + +#include + +#ifndef QT_NO_SORTFILTERPROXYMODEL + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QSortFilterProxyModelPrivate; +class QSortFilterProxyModelLessThan; +class QSortFilterProxyModelGreaterThan; + +class Q_GUI_EXPORT QSortFilterProxyModel : public QAbstractProxyModel +{ + friend class QSortFilterProxyModelLessThan; + friend class QSortFilterProxyModelGreaterThan; + + Q_OBJECT + Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp) + Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn) + Q_PROPERTY(bool dynamicSortFilter READ dynamicSortFilter WRITE setDynamicSortFilter) + Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity) + Q_PROPERTY(Qt::CaseSensitivity sortCaseSensitivity READ sortCaseSensitivity WRITE setSortCaseSensitivity) + Q_PROPERTY(bool isSortLocaleAware READ isSortLocaleAware WRITE setSortLocaleAware) + Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole) + Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole) + +public: + QSortFilterProxyModel(QObject *parent = 0); + ~QSortFilterProxyModel(); + + void setSourceModel(QAbstractItemModel *sourceModel); + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; + + QItemSelection mapSelectionToSource(const QItemSelection &proxySelection) const; + QItemSelection mapSelectionFromSource(const QItemSelection &sourceSelection) const; + + QRegExp filterRegExp() const; + void setFilterRegExp(const QRegExp ®Exp); + + int filterKeyColumn() const; + void setFilterKeyColumn(int column); + + Qt::CaseSensitivity filterCaseSensitivity() const; + void setFilterCaseSensitivity(Qt::CaseSensitivity cs); + + Qt::CaseSensitivity sortCaseSensitivity() const; + void setSortCaseSensitivity(Qt::CaseSensitivity cs); + + bool isSortLocaleAware() const; + void setSortLocaleAware(bool on); + + int sortColumn() const; + Qt::SortOrder sortOrder() const; + + bool dynamicSortFilter() const; + void setDynamicSortFilter(bool enable); + + int sortRole() const; + void setSortRole(int role); + + int filterRole() const; + void setFilterRole(int role); + +public Q_SLOTS: + void setFilterRegExp(const QString &pattern); + void setFilterWildcard(const QString &pattern); + void setFilterFixedString(const QString &pattern); + void clear(); + void invalidate(); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + + void filterChanged(); + void invalidateFilter(); + +public: +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role = Qt::EditRole); + + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + void fetchMore(const QModelIndex &parent); + bool canFetchMore(const QModelIndex &parent) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + QModelIndex buddy(const QModelIndex &index) const; + QModelIndexList match(const QModelIndex &start, int role, + const QVariant &value, int hits = 1, + Qt::MatchFlags flags = + Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const; + QSize span(const QModelIndex &index) const; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList mimeTypes() const; + Qt::DropActions supportedDropActions() const; +private: + Q_DECLARE_PRIVATE(QSortFilterProxyModel) + Q_DISABLE_COPY(QSortFilterProxyModel) + + Q_PRIVATE_SLOT(d_func(), void _q_sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceHeaderDataChanged(Qt::Orientation orientation, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutAboutToBeChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLayoutChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsInserted(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_sourceColumnsRemoved(const QModelIndex &source_parent, int start, int end)) + Q_PRIVATE_SLOT(d_func(), void _q_clearMapping()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SORTFILTERPROXYMODEL + +#endif // QSORTFILTERPROXYMODEL_H diff --git a/src/gui/itemviews/qstandarditemmodel.cpp b/src/gui/itemviews/qstandarditemmodel.cpp new file mode 100644 index 0000000000..711f5f8a40 --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel.cpp @@ -0,0 +1,3115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstandarditemmodel.h" + +#ifndef QT_NO_STANDARDITEMMODEL + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QStandardItemModelLessThan +{ +public: + inline QStandardItemModelLessThan() + { } + + inline bool operator()(const QPair &l, + const QPair &r) const + { + return *(l.first) < *(r.first); + } +}; + +class QStandardItemModelGreaterThan +{ +public: + inline QStandardItemModelGreaterThan() + { } + + inline bool operator()(const QPair &l, + const QPair &r) const + { + return *(r.first) < *(l.first); + } +}; + +/*! + \internal +*/ +QStandardItemPrivate::~QStandardItemPrivate() +{ + QVector::const_iterator it; + for (it = children.constBegin(); it != children.constEnd(); ++it) { + QStandardItem *child = *it; + if (child) + child->d_func()->setModel(0); + delete child; + } + children.clear(); + if (parent && model) + parent->d_func()->childDeleted(q_func()); +} + +/*! + \internal +*/ +QPair QStandardItemPrivate::position() const +{ + if (QStandardItem *par = parent) { + int idx = par->d_func()->childIndex(q_func()); + if (idx == -1) + return QPair(-1, -1); + return QPair(idx / par->columnCount(), idx % par->columnCount()); + } + // ### support header items? + return QPair(-1, -1); +} + +/*! + \internal +*/ +void QStandardItemPrivate::setChild(int row, int column, QStandardItem *item, + bool emitChanged) +{ + Q_Q(QStandardItem); + if (item == q) { + qWarning("QStandardItem::setChild: Can't make an item a child of itself %p", + item); + return; + } + if ((row < 0) || (column < 0)) + return; + if (rows <= row) + q->setRowCount(row + 1); + if (columns <= column) + q->setColumnCount(column + 1); + int index = childIndex(row, column); + Q_ASSERT(index != -1); + QStandardItem *oldItem = children.at(index); + if (item == oldItem) + return; + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::setChild: Ignoring duplicate insertion of item %p", + item); + return; + } + } + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + children.replace(index, item); + if (emitChanged && model) + model->d_func()->itemChanged(item); +} + + +/*! + \internal +*/ +void QStandardItemPrivate::changeFlags(bool enable, Qt::ItemFlags f) +{ + Q_Q(QStandardItem); + Qt::ItemFlags flags = q->flags(); + if (enable) + flags |= f; + else + flags &= ~f; + q->setFlags(flags); +} + +/*! + \internal +*/ +void QStandardItemPrivate::childDeleted(QStandardItem *child) +{ + int index = childIndex(child); + Q_ASSERT(index != -1); + children.replace(index, 0); +} + +/*! + \internal +*/ +void QStandardItemPrivate::setItemData(const QMap &roles) +{ + Q_Q(QStandardItem); + + //let's build the vector of new values + QVector newValues; + QMap::const_iterator it; + for (it = roles.begin(); it != roles.end(); ++it) { + QVariant value = it.value(); + if (value.isValid()) { + int role = it.key(); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QWidgetItemData wid(role,it.value()); + newValues.append(wid); + } + } + + if (values!=newValues) { + values=newValues; + if (model) + model->d_func()->itemChanged(q); + } +} + +/*! + \internal +*/ +const QMap QStandardItemPrivate::itemData() const +{ + QMap result; + QVector::const_iterator it; + for (it = values.begin(); it != values.end(); ++it) + result.insert((*it).role, (*it).value); + return result; +} + +/*! + \internal +*/ +void QStandardItemPrivate::sortChildren(int column, Qt::SortOrder order) +{ + Q_Q(QStandardItem); + if (column >= columnCount()) + return; + + QVector > sortable; + QVector unsortable; + + sortable.reserve(rowCount()); + unsortable.reserve(rowCount()); + + for (int row = 0; row < rowCount(); ++row) { + QStandardItem *itm = q->child(row, column); + if (itm) + sortable.append(QPair(itm, row)); + else + unsortable.append(row); + } + + if (order == Qt::AscendingOrder) { + QStandardItemModelLessThan lt; + qStableSort(sortable.begin(), sortable.end(), lt); + } else { + QStandardItemModelGreaterThan gt; + qStableSort(sortable.begin(), sortable.end(), gt); + } + + QModelIndexList changedPersistentIndexesFrom, changedPersistentIndexesTo; + QVector sorted_children(children.count()); + for (int i = 0; i < rowCount(); ++i) { + int r = (i < sortable.count() + ? sortable.at(i).second + : unsortable.at(i - sortable.count())); + for (int c = 0; c < columnCount(); ++c) { + QStandardItem *itm = q->child(r, c); + sorted_children[childIndex(i, c)] = itm; + if (model) { + QModelIndex from = model->createIndex(r, c, q); + if (model->d_func()->persistent.indexes.contains(from)) { + QModelIndex to = model->createIndex(i, c, q); + changedPersistentIndexesFrom.append(from); + changedPersistentIndexesTo.append(to); + } + } + } + } + + children = sorted_children; + + if (model) { + model->changePersistentIndexList(changedPersistentIndexesFrom, changedPersistentIndexesTo); + } + + QVector::iterator it; + for (it = children.begin(); it != children.end(); ++it) { + if (*it) + (*it)->d_func()->sortChildren(column, order); + } +} + +/*! + \internal + set the model of this item and all its children + */ +void QStandardItemPrivate::setModel(QStandardItemModel *mod) +{ + if (children.isEmpty()) { + if (model) + model->d_func()->invalidatePersistentIndex(model->indexFromItem(q_ptr)); + model = mod; + } else { + QStack stack; + stack.push(q_ptr); + while (!stack.isEmpty()) { + QStandardItem *itm = stack.pop(); + if (itm->d_func()->model) { + itm->d_func()->model->d_func()->invalidatePersistentIndex(itm->d_func()->model->indexFromItem(itm)); + } + itm->d_func()->model = mod; + const QVector &childList = itm->d_func()->children; + for (int i = 0; i < childList.count(); ++i) { + QStandardItem *chi = childList.at(i); + if (chi) + stack.push(chi); + } + } + } +} + +/*! + \internal +*/ +QStandardItemModelPrivate::QStandardItemModelPrivate() + : root(new QStandardItem), + itemPrototype(0), + sortRole(Qt::DisplayRole) +{ + root->setFlags(Qt::ItemIsDropEnabled); +} + +/*! + \internal +*/ +QStandardItemModelPrivate::~QStandardItemModelPrivate() +{ + delete itemPrototype; + qDeleteAll(columnHeaderItems); + qDeleteAll(rowHeaderItems); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::init() +{ + Q_Q(QStandardItemModel); + QObject::connect(q, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex,QModelIndex))); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::_q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + Q_Q(QStandardItemModel); + QModelIndex parent = topLeft.parent(); + for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { + for (int column = topLeft.column(); column <= bottomRight.column(); ++column) { + QModelIndex index = q->index(row, column, parent); + if (QStandardItem *item = itemFromIndex(index)) + emit q->itemChanged(item); + } + } +} + +/*! + \internal +*/ +bool QStandardItemPrivate::insertRows(int row, const QList &items) +{ + Q_Q(QStandardItem); + if ((row < 0) || (row > rowCount())) + return false; + int count = items.count(); + if (model) + model->d_func()->rowsAboutToBeInserted(q, row, row + count - 1); + if (rowCount() == 0) { + if (columnCount() == 0) + q->setColumnCount(1); + children.resize(columnCount() * count); + rows = count; + } else { + rows += count; + int index = childIndex(row, 0); + if (index != -1) + children.insert(index, columnCount() * count, 0); + } + for (int i = 0; i < items.count(); ++i) { + QStandardItem *item = items.at(i); + item->d_func()->model = model; + item->d_func()->parent = q; + int index = childIndex(i + row, 0); + children.replace(index, item); + } + if (model) + model->d_func()->rowsInserted(q, row, count); + return true; +} + +bool QStandardItemPrivate::insertRows(int row, int count, const QList &items) +{ + Q_Q(QStandardItem); + if ((count < 1) || (row < 0) || (row > rowCount())) + return false; + if (model) + model->d_func()->rowsAboutToBeInserted(q, row, row + count - 1); + if (rowCount() == 0) { + children.resize(columnCount() * count); + rows = count; + } else { + rows += count; + int index = childIndex(row, 0); + if (index != -1) + children.insert(index, columnCount() * count, 0); + } + if (!items.isEmpty()) { + int index = childIndex(row, 0); + int limit = qMin(items.count(), columnCount() * count); + for (int i = 0; i < limit; ++i) { + QStandardItem *item = items.at(i); + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::insertRows: Ignoring duplicate insertion of item %p", + item); + item = 0; + } + } + children.replace(index, item); + ++index; + } + } + if (model) + model->d_func()->rowsInserted(q, row, count); + return true; +} + +/*! + \internal +*/ +bool QStandardItemPrivate::insertColumns(int column, int count, const QList &items) +{ + Q_Q(QStandardItem); + if ((count < 1) || (column < 0) || (column > columnCount())) + return false; + if (model) + model->d_func()->columnsAboutToBeInserted(q, column, column + count - 1); + if (columnCount() == 0) { + children.resize(rowCount() * count); + columns = count; + } else { + columns += count; + int index = childIndex(0, column); + for (int row = 0; row < rowCount(); ++row) { + children.insert(index, count, 0); + index += columnCount(); + } + } + if (!items.isEmpty()) { + int limit = qMin(items.count(), rowCount() * count); + for (int i = 0; i < limit; ++i) { + QStandardItem *item = items.at(i); + if (item) { + if (item->d_func()->parent == 0) { + item->d_func()->setParentAndModel(q, model); + } else { + qWarning("QStandardItem::insertColumns: Ignoring duplicate insertion of item %p", + item); + item = 0; + } + } + int r = i / count; + int c = column + (i % count); + int index = childIndex(r, c); + children.replace(index, item); + } + } + if (model) + model->d_func()->columnsInserted(q, column, count); + return true; +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::itemChanged(QStandardItem *item) +{ + Q_Q(QStandardItemModel); + if (item->d_func()->parent == 0) { + // Header item + int idx = columnHeaderItems.indexOf(item); + if (idx != -1) { + emit q->headerDataChanged(Qt::Horizontal, idx, idx); + } else { + idx = rowHeaderItems.indexOf(item); + if (idx != -1) + emit q->headerDataChanged(Qt::Vertical, idx, idx); + } + } else { + // Normal item + QModelIndex index = q->indexFromItem(item); + emit q->dataChanged(index, index); + } +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsAboutToBeInserted(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginInsertRows(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsAboutToBeInserted(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginInsertColumns(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsAboutToBeRemoved(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginRemoveRows(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsAboutToBeRemoved(QStandardItem *parent, + int start, int end) +{ + Q_Q(QStandardItemModel); + QModelIndex index = q->indexFromItem(parent); + q->beginRemoveColumns(index, start, end); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsInserted(QStandardItem *parent, + int row, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root.data()) + rowHeaderItems.insert(row, count, 0); + q->endInsertRows(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsInserted(QStandardItem *parent, + int column, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root.data()) + columnHeaderItems.insert(column, count, 0); + q->endInsertColumns(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::rowsRemoved(QStandardItem *parent, + int row, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root.data()) { + for (int i = row; i < row + count; ++i) { + QStandardItem *oldItem = rowHeaderItems.at(i); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + rowHeaderItems.remove(row, count); + } + q->endRemoveRows(); +} + +/*! + \internal +*/ +void QStandardItemModelPrivate::columnsRemoved(QStandardItem *parent, + int column, int count) +{ + Q_Q(QStandardItemModel); + if (parent == root.data()) { + for (int i = column; i < column + count; ++i) { + QStandardItem *oldItem = columnHeaderItems.at(i); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + columnHeaderItems.remove(column, count); + } + q->endRemoveColumns(); +} + +/*! + \class QStandardItem + \brief The QStandardItem class provides an item for use with the + QStandardItemModel class. + \since 4.2 + \ingroup model-view + + Items usually contain text, icons, or checkboxes. + + Each item can have its own background brush which is set with the + setBackground() function. The current background brush can be found with + background(). The text label for each item can be rendered with its own + font and brush. These are specified with the setFont() and setForeground() + functions, and read with font() and foreground(). + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + Each item's flags can be changed by calling setFlags(). Checkable items + can be checked and unchecked with the setCheckState() function. The + corresponding checkState() function indicates whether the item is + currently checked. + + You can store application-specific data in an item by calling setData(). + + Each item can have a two-dimensional table of child items. This makes it + possible to build hierarchies of items. The typical hierarchy is the tree, + in which case the child table is a table with a single column (a list). + + The dimensions of the child table can be set with setRowCount() and + setColumnCount(). Items can be positioned in the child table with + setChild(). Get a pointer to a child item with child(). New rows and + columns of children can also be inserted with insertRow() and + insertColumn(), or appended with appendRow() and appendColumn(). When + using the append and insert functions, the dimensions of the child table + will grow as needed. + + An existing row of children can be removed with removeRow() or takeRow(); + correspondingly, a column can be removed with removeColumn() or + takeColumn(). + + An item's children can be sorted by calling sortChildren(). + + \section1 Subclassing + + When subclassing QStandardItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from the base + class. The type() function should be reimplemented to return a new type + value equal to or greater than \l UserType. + + Reimplement data() and setData() if you want to perform custom handling of + data queries and/or control how an item's data is represented. + + Reimplement clone() if you want QStandardItemModel to be able to create + instances of your custom item class on demand (see + QStandardItemModel::setItemPrototype()). + + Reimplement read() and write() if you want to control how items are + represented in their serialized form. + + Reimplement \l{operator<()} if you want to control the semantics of item + comparison. \l{operator<()} determines the sorted order when sorting items + with sortChildren() or with QStandardItemModel::sort(). + + \sa QStandardItemModel, {Item View Convenience Classes}, {Model/View Programming} +*/ + +/*! + \enum QStandardItem::ItemType + + This enum describes the types that are used to describe standard items. + + \value Type The default type for standard items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QStandardItem subclasses to ensure that + custom items are treated specially; for example, when items are sorted. + + \sa type() +*/ + +/*! + Constructs an item. +*/ +QStandardItem::QStandardItem() + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; +} + +/*! + Constructs an item with the given \a text. +*/ +QStandardItem::QStandardItem(const QString &text) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setText(text); +} + +/*! + Constructs an item with the given \a icon and \a text. +*/ +QStandardItem::QStandardItem(const QIcon &icon, const QString &text) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setIcon(icon); + setText(text); +} + +/*! + Constructs an item with \a rows rows and \a columns columns of child items. +*/ +QStandardItem::QStandardItem(int rows, int columns) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + setRowCount(rows); + setColumnCount(columns); +} + +/*! + \internal +*/ +QStandardItem::QStandardItem(QStandardItemPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QStandardItem); + d->q_ptr = this; +} + +/*! + Constructs a copy of \a other. Note that model() is + not copied. + + This function is useful when reimplementing clone(). +*/ +QStandardItem::QStandardItem(const QStandardItem &other) + : d_ptr(new QStandardItemPrivate) +{ + Q_D(QStandardItem); + d->q_ptr = this; + operator=(other); +} + +/*! + Assigns \a other's data and flags to this item. Note that + type() and model() are not copied. + + This function is useful when reimplementing clone(). +*/ +QStandardItem &QStandardItem::operator=(const QStandardItem &other) +{ + Q_D(QStandardItem); + d->values = other.d_func()->values; + return *this; +} + +/*! + Destructs the item. + This causes the item's children to be destructed as well. +*/ +QStandardItem::~QStandardItem() +{ +} + +/*! + Returns the item's parent item, or 0 if the item has no parent. + + \sa child() +*/ +QStandardItem *QStandardItem::parent() const +{ + Q_D(const QStandardItem); + if (!d->model || (d->model->d_func()->root.data() != d->parent)) + return d->parent; + return 0; +} + +/*! + Sets the item's data for the given \a role to the specified \a value. + + If you subclass QStandardItem and reimplement this function, your + reimplementation should call emitDataChanged() if you do not call + the base implementation of setData(). This will ensure that e.g. + views using the model are notified of the changes. + + \note The default implementation treats Qt::EditRole and Qt::DisplayRole + as referring to the same data. + + \sa Qt::ItemDataRole, data(), setFlags() +*/ +void QStandardItem::setData(const QVariant &value, int role) +{ + Q_D(QStandardItem); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QVector::iterator it; + for (it = d->values.begin(); it != d->values.end(); ++it) { + if ((*it).role == role) { + if (value.isValid()) { + if ((*it).value.type() == value.type() && (*it).value == value) + return; + (*it).value = value; + } else { + d->values.erase(it); + } + if (d->model) + d->model->d_func()->itemChanged(this); + return; + } + } + d->values.append(QWidgetItemData(role, value)); + if (d->model) + d->model->d_func()->itemChanged(this); +} + +/*! + Returns the item's data for the given \a role, or an invalid + QVariant if there is no data for the role. + + \note The default implementation treats Qt::EditRole and Qt::DisplayRole + as referring to the same data. +*/ +QVariant QStandardItem::data(int role) const +{ + Q_D(const QStandardItem); + role = (role == Qt::EditRole) ? Qt::DisplayRole : role; + QVector::const_iterator it; + for (it = d->values.begin(); it != d->values.end(); ++it) { + if ((*it).role == role) + return (*it).value; + } + return QVariant(); +} + +/*! + \since 4.4 + + Causes the model associated with this item to emit a + \l{QAbstractItemModel::dataChanged()}{dataChanged}() signal for this + item. + + You normally only need to call this function if you have subclassed + QStandardItem and reimplemented data() and/or setData(). + + \sa setData() +*/ +void QStandardItem::emitDataChanged() +{ + Q_D(QStandardItem); + if (d->model) + d->model->d_func()->itemChanged(this); +} + +/*! + Sets the item flags for the item to \a flags. + + The item flags determine how the user can interact with the item. + This is often used to disable an item. + + \sa flags(), setData() +*/ +void QStandardItem::setFlags(Qt::ItemFlags flags) +{ + setData((int)flags, Qt::UserRole - 1); +} + +/*! + Returns the item flags for the item. + + The item flags determine how the user can interact with the item. + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + + \sa setFlags() +*/ +Qt::ItemFlags QStandardItem::flags() const +{ + QVariant v = data(Qt::UserRole - 1); + if (!v.isValid()) + return (Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable + |Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled); + return Qt::ItemFlags(v.toInt()); +} + +/*! + \fn QString QStandardItem::text() const + + Returns the item's text. This is the text that's presented to the user + in a view. + + \sa setText() +*/ + +/*! + \fn void QStandardItem::setText(const QString &text) + + Sets the item's text to the \a text specified. + + \sa text(), setFont(), setForeground() +*/ + +/*! + \fn QIcon QStandardItem::icon() const + + Returns the item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QStandardItem::setIcon(const QIcon &icon) + + Sets the item's icon to the \a icon specified. +*/ + +/*! + \fn QString QStandardItem::statusTip() const + + Returns the item's status tip. + + \sa setStatusTip(), toolTip(), whatsThis() +*/ + +/*! + \fn void QStandardItem::setStatusTip(const QString &statusTip) + + Sets the item's status tip to the string specified by \a statusTip. + + \sa statusTip(), setToolTip(), setWhatsThis() +*/ + +/*! + \fn QString QStandardItem::toolTip() const + + Returns the item's tooltip. + + \sa setToolTip(), statusTip(), whatsThis() +*/ + +/*! + \fn void QStandardItem::setToolTip(const QString &toolTip) + + Sets the item's tooltip to the string specified by \a toolTip. + + \sa toolTip(), setStatusTip(), setWhatsThis() +*/ + +/*! + \fn QString QStandardItem::whatsThis() const + + Returns the item's "What's This?" help. + + \sa setWhatsThis(), toolTip(), statusTip() +*/ + +/*! + \fn void QStandardItem::setWhatsThis(const QString &whatsThis) + + Sets the item's "What's This?" help to the string specified by \a whatsThis. + + \sa whatsThis(), setStatusTip(), setToolTip() +*/ + +/*! + \fn QFont QStandardItem::font() const + + Returns the font used to render the item's text. + + \sa setFont() +*/ + +/*! + \fn void QStandardItem::setFont(const QFont &font) + + Sets the font used to display the item's text to the given \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QBrush QStandardItem::background() const + + Returns the brush used to render the item's background. + + \sa foreground() setBackground() +*/ + +/*! + \fn void QStandardItem::setBackground(const QBrush &brush) + + Sets the item's background brush to the specified \a brush. + + \sa background() setForeground() +*/ + +/*! + \fn QBrush QStandardItem::foreground() const + + Returns the brush used to render the item's foreground (e.g. text). + + \sa setForeground() background() +*/ + +/*! + \fn void QStandardItem::setForeground(const QBrush &brush) + + Sets the brush used to display the item's foreground (e.g. text) to the + given \a brush. + + \sa foreground() setBackground() setFont() +*/ + +/*! + \fn int QStandardItem::textAlignment() const + + Returns the text alignment for the item's text. +*/ + +/*! + \fn void QStandardItem::setTextAlignment(Qt::Alignment alignment) + + Sets the text alignment for the item's text to the \a alignment + specified. + + \sa textAlignment() +*/ + +/*! + \fn QSize QStandardItem::sizeHint() const + + Returns the size hint set for the item, or an invalid QSize if no + size hint has been set. + + If no size hint has been set, the item delegate will compute the + size hint based on the item data. + + \sa setSizeHint() +*/ + +/*! + \fn void QStandardItem::setSizeHint(const QSize &size) + + Sets the size hint for the item to be \a size. + If no size hint is set, the item delegate will compute the + size hint based on the item data. + + \sa sizeHint() +*/ + +/*! + \fn Qt::CheckState QStandardItem::checkState() const + + Returns the checked state of the item. + + \sa setCheckState(), isCheckable() +*/ + +/*! + \fn void QStandardItem::setCheckState(Qt::CheckState state) + + Sets the check state of the item to be \a state. + + \sa checkState(), setCheckable() +*/ + +/*! + \fn QString QStandardItem::accessibleText() const + + Returns the item's accessible text. + + The accessible text is used by assistive technologies (i.e. for users who + cannot use conventional means of interaction). + + \sa setAccessibleText(), accessibleDescription() +*/ + +/*! + \fn void QStandardItem::setAccessibleText(const QString &accessibleText) + + Sets the item's accessible text to the string specified by \a accessibleText. + + The accessible text is used by assistive technologies (i.e. for users who + cannot use conventional means of interaction). + + \sa accessibleText(), setAccessibleDescription() +*/ + +/*! + \fn QString QStandardItem::accessibleDescription() const + + Returns the item's accessible description. + + The accessible description is used by assistive technologies (i.e. for + users who cannot use conventional means of interaction). + + \sa setAccessibleDescription(), accessibleText() +*/ + +/*! + \fn void QStandardItem::setAccessibleDescription(const QString &accessibleDescription) + + Sets the item's accessible description to the string specified by \a + accessibleDescription. + + The accessible description is used by assistive technologies (i.e. for + users who cannot use conventional means of interaction). + + \sa accessibleDescription(), setAccessibleText() +*/ + +/*! + Sets whether the item is enabled. If \a enabled is true, the item is enabled, + meaning that the user can interact with the item; if \a enabled is false, the + user cannot interact with the item. + + This flag takes precedence over the other item flags; e.g. if an item is not + enabled, it cannot be selected by the user, even if the Qt::ItemIsSelectable + flag has been set. + + \sa isEnabled(), Qt::ItemIsEnabled, setFlags() +*/ +void QStandardItem::setEnabled(bool enabled) +{ + Q_D(QStandardItem); + d->changeFlags(enabled, Qt::ItemIsEnabled); +} + +/*! + \fn bool QStandardItem::isEnabled() const + + Returns whether the item is enabled. + + When an item is enabled, the user can interact with it. The possible + types of interaction are specified by the other item flags, such as + isEditable() and isSelectable(). + + The default value is true. + + \sa setEnabled(), flags() +*/ + +/*! + Sets whether the item is editable. If \a editable is true, the item can be + edited by the user; otherwise, the user cannot edit the item. + + How the user can edit items in a view is determined by the view's edit + triggers; see QAbstractItemView::editTriggers. + + \sa isEditable(), setFlags() +*/ +void QStandardItem::setEditable(bool editable) +{ + Q_D(QStandardItem); + d->changeFlags(editable, Qt::ItemIsEditable); +} + +/*! + \fn bool QStandardItem::isEditable() const + + Returns whether the item can be edited by the user. + + When an item is editable (and enabled), the user can edit the item by + invoking one of the view's edit triggers; see + QAbstractItemView::editTriggers. + + The default value is true. + + \sa setEditable(), flags() +*/ + +/*! + Sets whether the item is selectable. If \a selectable is true, the item + can be selected by the user; otherwise, the user cannot select the item. + + You can control the selection behavior and mode by manipulating their + view properties; see QAbstractItemView::selectionMode and + QAbstractItemView::selectionBehavior. + + \sa isSelectable(), setFlags() +*/ +void QStandardItem::setSelectable(bool selectable) +{ + Q_D(QStandardItem); + d->changeFlags(selectable, Qt::ItemIsSelectable); +} + +/*! + \fn bool QStandardItem::isSelectable() const + + Returns whether the item is selectable by the user. + + The default value is true. + + \sa setSelectable(), flags() +*/ + +/*! + Sets whether the item is user-checkable. If \a checkable is true, the + item can be checked by the user; otherwise, the user cannot check + the item. + + The item delegate will render a checkable item with a check box next to the + item's text. + + \sa isCheckable(), setCheckState(), setTristate() +*/ +void QStandardItem::setCheckable(bool checkable) +{ + Q_D(QStandardItem); + if (checkable && !isCheckable()) { + // make sure there's data for the checkstate role + if (!data(Qt::CheckStateRole).isValid()) + setData(Qt::Unchecked, Qt::CheckStateRole); + } + d->changeFlags(checkable, Qt::ItemIsUserCheckable); +} + +/*! + \fn bool QStandardItem::isCheckable() const + + Returns whether the item is user-checkable. + + The default value is false. + + \sa setCheckable(), checkState(), isTristate() +*/ + +/*! + Sets whether the item is tristate. If \a tristate is true, the + item is checkable with three separate states; otherwise, the item + is checkable with two states. (Note that this also requires that + the item is checkable; see isCheckable().) + + \sa isTristate(), setCheckable(), setCheckState() +*/ +void QStandardItem::setTristate(bool tristate) +{ + Q_D(QStandardItem); + d->changeFlags(tristate, Qt::ItemIsTristate); +} + +/*! + \fn bool QStandardItem::isTristate() const + + Returns whether the item is tristate; that is, if it's checkable with three + separate states. + + The default value is false. + + \sa setTristate(), isCheckable(), checkState() +*/ + +#ifndef QT_NO_DRAGANDDROP + +/*! + Sets whether the item is drag enabled. If \a dragEnabled is true, the item + can be dragged by the user; otherwise, the user cannot drag the item. + + Note that you also need to ensure that item dragging is enabled in the view; + see QAbstractItemView::dragEnabled. + + \sa isDragEnabled(), setDropEnabled(), setFlags() +*/ +void QStandardItem::setDragEnabled(bool dragEnabled) +{ + Q_D(QStandardItem); + d->changeFlags(dragEnabled, Qt::ItemIsDragEnabled); +} + +/*! + \fn bool QStandardItem::isDragEnabled() const + + Returns whether the item is drag enabled. An item that is drag enabled can + be dragged by the user. + + The default value is true. + + Note that item dragging must be enabled in the view for dragging to work; + see QAbstractItemView::dragEnabled. + + \sa setDragEnabled(), isDropEnabled(), flags() +*/ + +/*! + Sets whether the item is drop enabled. If \a dropEnabled is true, the item + can be used as a drop target; otherwise, it cannot. + + Note that you also need to ensure that drops are enabled in the view; see + QWidget::acceptDrops(); and that the model supports the desired drop actions; + see QAbstractItemModel::supportedDropActions(). + + \sa isDropEnabled(), setDragEnabled(), setFlags() +*/ +void QStandardItem::setDropEnabled(bool dropEnabled) +{ + Q_D(QStandardItem); + d->changeFlags(dropEnabled, Qt::ItemIsDropEnabled); +} + +/*! + \fn bool QStandardItem::isDropEnabled() const + + Returns whether the item is drop enabled. When an item is drop enabled, it + can be used as a drop target. + + The default value is true. + + \sa setDropEnabled(), isDragEnabled(), flags() +*/ + +#endif // QT_NO_DRAGANDDROP + +/*! + Returns the row where the item is located in its parent's child table, or + -1 if the item has no parent. + + \sa column(), parent() +*/ +int QStandardItem::row() const +{ + Q_D(const QStandardItem); + QPair pos = d->position(); + return pos.first; +} + +/*! + Returns the column where the item is located in its parent's child table, + or -1 if the item has no parent. + + \sa row(), parent() +*/ +int QStandardItem::column() const +{ + Q_D(const QStandardItem); + QPair pos = d->position(); + return pos.second; +} + +/*! + Returns the QModelIndex associated with this item. + + When you need to invoke item functionality in a QModelIndex-based API (e.g. + QAbstractItemView), you can call this function to obtain an index that + corresponds to the item's location in the model. + + If the item is not associated with a model, an invalid QModelIndex is + returned. + + \sa model(), QStandardItemModel::itemFromIndex() +*/ +QModelIndex QStandardItem::index() const +{ + Q_D(const QStandardItem); + return d->model ? d->model->indexFromItem(this) : QModelIndex(); +} + +/*! + Returns the QStandardItemModel that this item belongs to. + + If the item is not a child of another item that belongs to the model, this + function returns 0. + + \sa index() +*/ +QStandardItemModel *QStandardItem::model() const +{ + Q_D(const QStandardItem); + return d->model; +} + +/*! + Sets the number of child item rows to \a rows. If this is less than + rowCount(), the data in the unwanted rows is discarded. + + \sa rowCount(), setColumnCount() +*/ +void QStandardItem::setRowCount(int rows) +{ + int rc = rowCount(); + if (rc == rows) + return; + if (rc < rows) + insertRows(qMax(rc, 0), rows - rc); + else + removeRows(qMax(rows, 0), rc - rows); +} + +/*! + Returns the number of child item rows that the item has. + + \sa setRowCount(), columnCount() +*/ +int QStandardItem::rowCount() const +{ + Q_D(const QStandardItem); + return d->rowCount(); +} + +/*! + Sets the number of child item columns to \a columns. If this is less than + columnCount(), the data in the unwanted columns is discarded. + + \sa columnCount(), setRowCount() +*/ +void QStandardItem::setColumnCount(int columns) +{ + int cc = columnCount(); + if (cc == columns) + return; + if (cc < columns) + insertColumns(qMax(cc, 0), columns - cc); + else + removeColumns(qMax(columns, 0), cc - columns); +} + +/*! + Returns the number of child item columns that the item has. + + \sa setColumnCount(), rowCount() +*/ +int QStandardItem::columnCount() const +{ + Q_D(const QStandardItem); + return d->columnCount(); +} + +/*! + Inserts a row at \a row containing \a items. If necessary, the column + count is increased to the size of \a items. + + \sa insertRows(), insertColumn() +*/ +void QStandardItem::insertRow(int row, const QList &items) +{ + Q_D(QStandardItem); + if (row < 0) + return; + if (columnCount() < items.count()) + setColumnCount(items.count()); + d->insertRows(row, 1, items); +} + +/*! + Inserts \a items at \a row. The column count wont be changed. + + \sa insertRow(), insertColumn() +*/ +void QStandardItem::insertRows(int row, const QList &items) +{ + Q_D(QStandardItem); + if (row < 0) + return; + d->insertRows(row, items); +} + +/*! + Inserts a column at \a column containing \a items. If necessary, + the row count is increased to the size of \a items. + + \sa insertColumns(), insertRow() +*/ +void QStandardItem::insertColumn(int column, const QList &items) +{ + Q_D(QStandardItem); + if (column < 0) + return; + if (rowCount() < items.count()) + setRowCount(items.count()); + d->insertColumns(column, 1, items); +} + +/*! + Inserts \a count rows of child items at row \a row. + + \sa insertRow(), insertColumns() +*/ +void QStandardItem::insertRows(int row, int count) +{ + Q_D(QStandardItem); + if (rowCount() < row) { + count += row - rowCount(); + row = rowCount(); + } + d->insertRows(row, count, QList()); +} + +/*! + Inserts \a count columns of child items at column \a column. + + \sa insertColumn(), insertRows() +*/ +void QStandardItem::insertColumns(int column, int count) +{ + Q_D(QStandardItem); + if (columnCount() < column) { + count += column - columnCount(); + column = columnCount(); + } + d->insertColumns(column, count, QList()); +} + +/*! + \fn void QStandardItem::appendRow(const QList &items) + + Appends a row containing \a items. If necessary, the column count is + increased to the size of \a items. + + \sa insertRow() +*/ + +/*! + \fn void QStandardItem::appendRows(const QList &items) + + Appends rows containing \a items. The column count will not change. + + \sa insertRow() +*/ + +/*! + \fn void QStandardItem::appendColumn(const QList &items) + + Appends a column containing \a items. If necessary, the row count is + increased to the size of \a items. + + \sa insertColumn() +*/ + +/*! + \fn bool QStandardItemModel::insertRow(int row, const QModelIndex &parent) + + Inserts a single row before the given \a row in the child items of the + \a parent specified. Returns true if the row is inserted; otherwise + returns false. + + \sa insertRows(), insertColumn(), removeRow() +*/ + +/*! + \fn bool QStandardItemModel::insertColumn(int column, const QModelIndex &parent) + + Inserts a single column before the given \a column in the child items of + the \a parent specified. Returns true if the column is inserted; otherwise + returns false. + + \sa insertColumns(), insertRow(), removeColumn() +*/ + +/*! + \fn QStandardItem::insertRow(int row, QStandardItem *item) + \overload + + Inserts a row at \a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to insert a single new item. +*/ + +/*! + \fn QStandardItem::appendRow(QStandardItem *item) + \overload + + Appends a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new item. +*/ + +/*! + Removes the given \a row. The items that were in the row are deleted. + + \sa takeRow(), removeRows(), removeColumn() +*/ +void QStandardItem::removeRow(int row) +{ + removeRows(row, 1); +} + +/*! + Removes the given \a column. The items that were in the + column are deleted. + + \sa takeColumn(), removeColumns(), removeRow() +*/ +void QStandardItem::removeColumn(int column) +{ + removeColumns(column, 1); +} + +/*! + Removes \a count rows at row \a row. The items that were in those rows are + deleted. + + \sa removeRow(), removeColumn() +*/ +void QStandardItem::removeRows(int row, int count) +{ + Q_D(QStandardItem); + if ((count < 1) || (row < 0) || ((row + count) > rowCount())) + return; + if (d->model) + d->model->d_func()->rowsAboutToBeRemoved(this, row, row + count - 1); + int i = d->childIndex(row, 0); + int n = count * d->columnCount(); + for (int j = i; j < n+i; ++j) { + QStandardItem *oldItem = d->children.at(j); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + d->children.remove(qMax(i, 0), n); + d->rows -= count; + if (d->model) + d->model->d_func()->rowsRemoved(this, row, count); +} + +/*! + Removes \a count columns at column \a column. The items that were in those + columns are deleted. + + \sa removeColumn(), removeRows() +*/ +void QStandardItem::removeColumns(int column, int count) +{ + Q_D(QStandardItem); + if ((count < 1) || (column < 0) || ((column + count) > columnCount())) + return; + if (d->model) + d->model->d_func()->columnsAboutToBeRemoved(this, column, column + count - 1); + for (int row = d->rowCount() - 1; row >= 0; --row) { + int i = d->childIndex(row, column); + for (int j=i; jchildren.at(j); + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + } + d->children.remove(i, count); + } + d->columns -= count; + if (d->model) + d->model->d_func()->columnsRemoved(this, column, count); +} + +/*! + Returns true if this item has any children; otherwise returns false. + + \sa rowCount(), columnCount(), child() +*/ +bool QStandardItem::hasChildren() const +{ + return (rowCount() > 0) && (columnCount() > 0); +} + +/*! + Sets the child item at (\a row, \a column) to \a item. This item (the parent + item) takes ownership of \a item. If necessary, the row count and column + count are increased to fit the item. + + \sa child() +*/ +void QStandardItem::setChild(int row, int column, QStandardItem *item) +{ + Q_D(QStandardItem); + d->setChild(row, column, item, true); +} + +/*! + \fn QStandardItem::setChild(int row, QStandardItem *item) + \overload + + Sets the child at \a row to \a item. +*/ + +/*! + Returns the child item at (\a row, \a column) if one has been set; otherwise + returns 0. + + \sa setChild(), takeChild(), parent() +*/ +QStandardItem *QStandardItem::child(int row, int column) const +{ + Q_D(const QStandardItem); + int index = d->childIndex(row, column); + if (index == -1) + return 0; + return d->children.at(index); +} + +/*! + Removes the child item at (\a row, \a column) without deleting it, and returns + a pointer to the item. If there was no child at the given location, then + this function returns 0. + + Note that this function, unlike takeRow() and takeColumn(), does not affect + the dimensions of the child table. + + \sa child(), takeRow(), takeColumn() +*/ +QStandardItem *QStandardItem::takeChild(int row, int column) +{ + Q_D(QStandardItem); + QStandardItem *item = 0; + int index = d->childIndex(row, column); + if (index != -1) { + item = d->children.at(index); + if (item) + item->d_func()->setParentAndModel(0, 0); + d->children.replace(index, 0); + } + return item; +} + +/*! + Removes \a row without deleting the row items, and returns a list of + pointers to the removed items. For items in the row that have not been + set, the corresponding pointers in the list will be 0. + + \sa removeRow(), insertRow(), takeColumn() +*/ +QList QStandardItem::takeRow(int row) +{ + Q_D(QStandardItem); + if ((row < 0) || (row >= rowCount())) + return QList(); + if (d->model) + d->model->d_func()->rowsAboutToBeRemoved(this, row, row); + QList items; + int index = d->childIndex(row, 0); // Will return -1 if there are no columns + if (index != -1) { + int col_count = d->columnCount(); + for (int column = 0; column < col_count; ++column) { + QStandardItem *ch = d->children.at(index + column); + if (ch) + ch->d_func()->setParentAndModel(0, 0); + items.append(ch); + } + d->children.remove(index, col_count); + } + d->rows--; + if (d->model) + d->model->d_func()->rowsRemoved(this, row, 1); + return items; +} + +/*! + Removes \a column without deleting the column items, and returns a list of + pointers to the removed items. For items in the column that have not been + set, the corresponding pointers in the list will be 0. + + \sa removeColumn(), insertColumn(), takeRow() +*/ +QList QStandardItem::takeColumn(int column) +{ + Q_D(QStandardItem); + if ((column < 0) || (column >= columnCount())) + return QList(); + if (d->model) + d->model->d_func()->columnsAboutToBeRemoved(this, column, column); + QList items; + + for (int row = d->rowCount() - 1; row >= 0; --row) { + int index = d->childIndex(row, column); + QStandardItem *ch = d->children.at(index); + if (ch) + ch->d_func()->setParentAndModel(0, 0); + d->children.remove(index); + items.prepend(ch); + } + d->columns--; + if (d->model) + d->model->d_func()->columnsRemoved(this, column, 1); + return items; +} + +/*! + Returns true if this item is less than \a other; otherwise returns false. + + The default implementation uses the data for the item's sort role (see + QStandardItemModel::sortRole) to perform the comparison if the item + belongs to a model; otherwise, the data for the item's Qt::DisplayRole + (text()) is used to perform the comparison. + + sortChildren() and QStandardItemModel::sort() use this function when + sorting items. If you want custom sorting, you can subclass QStandardItem + and reimplement this function. +*/ +bool QStandardItem::operator<(const QStandardItem &other) const +{ + const int role = model() ? model()->sortRole() : Qt::DisplayRole; + const QVariant l = data(role), r = other.data(role); + // this code is copied from QSortFilterProxyModel::lessThan() + switch (l.userType()) { + case QVariant::Invalid: + return (r.type() == QVariant::Invalid); + case QVariant::Int: + return l.toInt() < r.toInt(); + case QVariant::UInt: + return l.toUInt() < r.toUInt(); + case QVariant::LongLong: + return l.toLongLong() < r.toLongLong(); + case QVariant::ULongLong: + return l.toULongLong() < r.toULongLong(); + case QMetaType::Float: + return l.toFloat() < r.toFloat(); + case QVariant::Double: + return l.toDouble() < r.toDouble(); + case QVariant::Char: + return l.toChar() < r.toChar(); + case QVariant::Date: + return l.toDate() < r.toDate(); + case QVariant::Time: + return l.toTime() < r.toTime(); + case QVariant::DateTime: + return l.toDateTime() < r.toDateTime(); + case QVariant::String: + default: + return l.toString().compare(r.toString()) < 0; + } +} + +/*! + Sorts the children of the item using the given \a order, by the values in + the given \a column. + + \note This function is recursive, therefore it sorts the children of the + item, its grandchildren, etc. + + \sa {operator<()} +*/ +void QStandardItem::sortChildren(int column, Qt::SortOrder order) +{ + Q_D(QStandardItem); + if ((column < 0) || (rowCount() == 0)) + return; + if (d->model) + emit d->model->layoutAboutToBeChanged(); + d->sortChildren(column, order); + if (d->model) + emit d->model->layoutChanged(); +} + +/*! + Returns a copy of this item. The item's children are not copied. + + When subclassing QStandardItem, you can reimplement this function + to provide QStandardItemModel with a factory that it can use to + create new items on demand. + + \sa QStandardItemModel::setItemPrototype(), operator=() +*/ +QStandardItem *QStandardItem::clone() const +{ + return new QStandardItem(*this); +} + +/*! + Returns the type of this item. The type is used to distinguish custom + items from the base class. When subclassing QStandardItem, you should + reimplement this function and return a new value greater than or equal + to \l UserType. + + \sa QStandardItem::Type +*/ +int QStandardItem::type() const +{ + return Type; +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. Only the data and flags of the item are + read, not the child items. + + \sa write() +*/ +void QStandardItem::read(QDataStream &in) +{ + Q_D(QStandardItem); + in >> d->values; + qint32 flags; + in >> flags; + setFlags(Qt::ItemFlags(flags)); +} + +/*! + Writes the item to stream \a out. Only the data and flags of the item + are written, not the child items. + + \sa read() +*/ +void QStandardItem::write(QDataStream &out) const +{ + Q_D(const QStandardItem); + out << d->values; + out << flags(); +} + +/*! + \relates QStandardItem + \since 4.2 + + Reads a QStandardItem from stream \a in into \a item. + + This operator uses QStandardItem::read(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &in, QStandardItem &item) +{ + item.read(in); + return in; +} + +/*! + \relates QStandardItem + \since 4.2 + + Writes the QStandardItem \a item to stream \a out. + + This operator uses QStandardItem::write(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QStandardItem &item) +{ + item.write(out); + return out; +} + +#endif // QT_NO_DATASTREAM + +/*! + \class QStandardItemModel + \brief The QStandardItemModel class provides a generic model for storing custom data. + \ingroup model-view + + QStandardItemModel can be used as a repository for standard Qt + data types. It is one of the \l {Model/View Classes} and is part + of Qt's \l {Model/View Programming}{model/view} framework. + + QStandardItemModel provides a classic item-based approach to working with + the model. The items in a QStandardItemModel are provided by + QStandardItem. + + QStandardItemModel implements the QAbstractItemModel interface, which + means that the model can be used to provide data in any view that supports + that interface (such as QListView, QTableView and QTreeView, and your own + custom views). For performance and flexibility, you may want to subclass + QAbstractItemModel to provide support for different kinds of data + repositories. For example, the QDirModel provides a model interface to the + underlying file system. + + When you want a list or tree, you typically create an empty + QStandardItemModel and use appendRow() to add items to the model, and + item() to access an item. If your model represents a table, you typically + pass the dimensions of the table to the QStandardItemModel constructor and + use setItem() to position items into the table. You can also use setRowCount() + and setColumnCount() to alter the dimensions of the model. To insert items, + use insertRow() or insertColumn(), and to remove items, use removeRow() or + removeColumn(). + + You can set the header labels of your model with setHorizontalHeaderLabels() + and setVerticalHeaderLabels(). + + You can search for items in the model with findItems(), and sort the model by + calling sort(). + + Call clear() to remove all items from the model. + + An example usage of QStandardItemModel to create a table: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 0 + + An example usage of QStandardItemModel to create a tree: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 1 + + After setting the model on a view, you typically want to react to user + actions, such as an item being clicked. Since a QAbstractItemView provides + QModelIndex-based signals and functions, you need a way to obtain the + QStandardItem that corresponds to a given QModelIndex, and vice + versa. itemFromIndex() and indexFromItem() provide this mapping. Typical + usage of itemFromIndex() includes obtaining the item at the current index + in a view, and obtaining the item that corresponds to an index carried by + a QAbstractItemView signal, such as QAbstractItemView::clicked(). First + you connect the view's signal to a slot in your class: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 2 + + When you receive the signal, you call itemFromIndex() on the given model + index to get a pointer to the item: + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 3 + + Conversely, you must obtain the QModelIndex of an item when you want to + invoke a model/view function that takes an index as argument. You can + obtain the index either by using the model's indexFromItem() function, or, + equivalently, by calling QStandardItem::index(): + + \snippet doc/src/snippets/code/src_gui_itemviews_qstandarditemmodel.cpp 4 + + You are, of course, not required to use the item-based approach; you could + instead rely entirely on the QAbstractItemModel interface when working with + the model, or use a combination of the two as appropriate. + + \sa QStandardItem, {Model/View Programming}, QAbstractItemModel, + {itemviews/simpletreemodel}{Simple Tree Model example}, + {Item View Convenience Classes} +*/ + +/*! + \fn void QStandardItemModel::itemChanged(QStandardItem *item) + \since 4.2 + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + Constructs a new item model with the given \a parent. +*/ +QStandardItemModel::QStandardItemModel(QObject *parent) + : QAbstractItemModel(*new QStandardItemModelPrivate, parent) +{ + Q_D(QStandardItemModel); + d->init(); + d->root->d_func()->setModel(this); +} + +/*! + Constructs a new item model that initially has \a rows rows and \a columns + columns, and that has the given \a parent. +*/ +QStandardItemModel::QStandardItemModel(int rows, int columns, QObject *parent) + : QAbstractItemModel(*new QStandardItemModelPrivate, parent) +{ + Q_D(QStandardItemModel); + d->init(); + d->root->insertColumns(0, columns); + d->columnHeaderItems.insert(0, columns, 0); + d->root->insertRows(0, rows); + d->rowHeaderItems.insert(0, rows, 0); + d->root->d_func()->setModel(this); +} + +/*! + \internal +*/ +QStandardItemModel::QStandardItemModel(QStandardItemModelPrivate &dd, QObject *parent) + : QAbstractItemModel(dd, parent) +{ + Q_D(QStandardItemModel); + d->init(); +} + +/*! + Destructs the model. The model destroys all its items. +*/ +QStandardItemModel::~QStandardItemModel() +{ +} + +/*! + Removes all items (including header items) from the model and sets the + number of rows and columns to zero. + + \sa removeColumns(), removeRows() +*/ +void QStandardItemModel::clear() +{ + Q_D(QStandardItemModel); + d->root.reset(new QStandardItem); + d->root->d_func()->setModel(this); + qDeleteAll(d->columnHeaderItems); + d->columnHeaderItems.clear(); + qDeleteAll(d->rowHeaderItems); + d->rowHeaderItems.clear(); + reset(); +} + +/*! + \since 4.2 + + Returns a pointer to the QStandardItem associated with the given \a index. + + Calling this function is typically the initial step when processing + QModelIndex-based signals from a view, such as + QAbstractItemView::activated(). In your slot, you call itemFromIndex(), + with the QModelIndex carried by the signal as argument, to obtain a + pointer to the corresponding QStandardItem. + + Note that this function will lazily create an item for the index (using + itemPrototype()), and set it in the parent item's child table, if no item + already exists at that index. + + If \a index is an invalid index, this function returns 0. + + \sa indexFromItem() +*/ +QStandardItem *QStandardItemModel::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + if ((index.row() < 0) || (index.column() < 0) || (index.model() != this)) + return 0; + QStandardItem *parent = static_cast(index.internalPointer()); + if (parent == 0) + return 0; + QStandardItem *item = parent->child(index.row(), index.column()); + // lazy part + if (item == 0) { + item = d->createItem(); + parent->d_func()->setChild(index.row(), index.column(), item); + } + return item; +} + +/*! + \since 4.2 + + Returns the QModelIndex associated with the given \a item. + + Use this function when you want to perform an operation that requires the + QModelIndex of the item, such as + QAbstractItemView::scrollTo(). QStandardItem::index() is provided as + convenience; it is equivalent to calling this function. + + \sa itemFromIndex(), QStandardItem::index() +*/ +QModelIndex QStandardItemModel::indexFromItem(const QStandardItem *item) const +{ + if (item && item->d_func()->parent) { + QPair pos = item->d_func()->position(); + return createIndex(pos.first, pos.second, item->d_func()->parent); + } + return QModelIndex(); +} + +/*! + \since 4.2 + + Sets the number of rows in this model to \a rows. If + this is less than rowCount(), the data in the unwanted rows + is discarded. + + \sa setColumnCount() +*/ +void QStandardItemModel::setRowCount(int rows) +{ + Q_D(QStandardItemModel); + d->root->setRowCount(rows); +} + +/*! + \since 4.2 + + Sets the number of columns in this model to \a columns. If + this is less than columnCount(), the data in the unwanted columns + is discarded. + + \sa setRowCount() +*/ +void QStandardItemModel::setColumnCount(int columns) +{ + Q_D(QStandardItemModel); + d->root->setColumnCount(columns); +} + +/*! + \since 4.2 + + Sets the item for the given \a row and \a column to \a item. The model + takes ownership of the item. If necessary, the row count and column count + are increased to fit the item. The previous item at the given location (if + there was one) is deleted. + + \sa item() +*/ +void QStandardItemModel::setItem(int row, int column, QStandardItem *item) +{ + Q_D(QStandardItemModel); + d->root->d_func()->setChild(row, column, item, true); +} + +/*! + \fn QStandardItemModel::setItem(int row, QStandardItem *item) + \overload +*/ + +/*! + \since 4.2 + + Returns the item for the given \a row and \a column if one has been set; + otherwise returns 0. + + \sa setItem(), takeItem(), itemFromIndex() +*/ +QStandardItem *QStandardItemModel::item(int row, int column) const +{ + Q_D(const QStandardItemModel); + return d->root->child(row, column); +} + +/*! + \since 4.2 + + Returns the model's invisible root item. + + The invisible root item provides access to the model's top-level items + through the QStandardItem API, making it possible to write functions that + can treat top-level items and their children in a uniform way; for + example, recursive functions involving a tree model. + + \note Calling \l{QAbstractItemModel::index()}{index()} on the QStandardItem object + retrieved from this function is not valid. +*/ +QStandardItem *QStandardItemModel::invisibleRootItem() const +{ + Q_D(const QStandardItemModel); + return d->root.data(); +} + +/*! + \since 4.2 + + Sets the horizontal header item for \a column to \a item. The model takes + ownership of the item. If necessary, the column count is increased to fit + the item. The previous header item (if there was one) is deleted. + + \sa horizontalHeaderItem(), setHorizontalHeaderLabels(), + setVerticalHeaderItem() +*/ +void QStandardItemModel::setHorizontalHeaderItem(int column, QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (column < 0) + return; + if (columnCount() <= column) + setColumnCount(column + 1); + + QStandardItem *oldItem = d->columnHeaderItems.at(column); + if (item == oldItem) + return; + + if (item) { + if (item->model() == 0) { + item->d_func()->setModel(this); + } else { + qWarning("QStandardItem::setHorizontalHeaderItem: Ignoring duplicate insertion of item %p", + item); + return; + } + } + + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + + d->columnHeaderItems.replace(column, item); + emit headerDataChanged(Qt::Horizontal, column, column); +} + +/*! + \since 4.2 + + Returns the horizontal header item for \a column if one has been set; + otherwise returns 0. + + \sa setHorizontalHeaderItem(), verticalHeaderItem() +*/ +QStandardItem *QStandardItemModel::horizontalHeaderItem(int column) const +{ + Q_D(const QStandardItemModel); + if ((column < 0) || (column >= columnCount())) + return 0; + return d->columnHeaderItems.at(column); +} + +/*! + \since 4.2 + + Sets the vertical header item for \a row to \a item. The model takes + ownership of the item. If necessary, the row count is increased to fit the + item. The previous header item (if there was one) is deleted. + + \sa verticalHeaderItem(), setVerticalHeaderLabels(), + setHorizontalHeaderItem() +*/ +void QStandardItemModel::setVerticalHeaderItem(int row, QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (row < 0) + return; + if (rowCount() <= row) + setRowCount(row + 1); + + QStandardItem *oldItem = d->rowHeaderItems.at(row); + if (item == oldItem) + return; + + if (item) { + if (item->model() == 0) { + item->d_func()->setModel(this); + } else { + qWarning("QStandardItem::setVerticalHeaderItem: Ignoring duplicate insertion of item %p", + item); + return; + } + } + + if (oldItem) + oldItem->d_func()->setModel(0); + delete oldItem; + + d->rowHeaderItems.replace(row, item); + emit headerDataChanged(Qt::Vertical, row, row); +} + +/*! + \since 4.2 + + Returns the vertical header item for row \a row if one has been set; + otherwise returns 0. + + \sa setVerticalHeaderItem(), horizontalHeaderItem() +*/ +QStandardItem *QStandardItemModel::verticalHeaderItem(int row) const +{ + Q_D(const QStandardItemModel); + if ((row < 0) || (row >= rowCount())) + return 0; + return d->rowHeaderItems.at(row); +} + +/*! + \since 4.2 + + Sets the horizontal header labels using \a labels. If necessary, the + column count is increased to the size of \a labels. + + \sa setHorizontalHeaderItem() +*/ +void QStandardItemModel::setHorizontalHeaderLabels(const QStringList &labels) +{ + Q_D(QStandardItemModel); + if (columnCount() < labels.count()) + setColumnCount(labels.count()); + for (int i = 0; i < labels.count(); ++i) { + QStandardItem *item = horizontalHeaderItem(i); + if (!item) { + item = d->createItem(); + setHorizontalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + \since 4.2 + + Sets the vertical header labels using \a labels. If necessary, the row + count is increased to the size of \a labels. + + \sa setVerticalHeaderItem() +*/ +void QStandardItemModel::setVerticalHeaderLabels(const QStringList &labels) +{ + Q_D(QStandardItemModel); + if (rowCount() < labels.count()) + setRowCount(labels.count()); + for (int i = 0; i < labels.count(); ++i) { + QStandardItem *item = verticalHeaderItem(i); + if (!item) { + item = d->createItem(); + setVerticalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + \since 4.2 + + Sets the item prototype for the model to the specified \a item. The model + takes ownership of the prototype. + + The item prototype acts as a QStandardItem factory, by relying on the + QStandardItem::clone() function. To provide your own prototype, subclass + QStandardItem, reimplement QStandardItem::clone() and set the prototype to + be an instance of your custom class. Whenever QStandardItemModel needs to + create an item on demand (for instance, when a view or item delegate calls + setData())), the new items will be instances of your custom class. + + \sa itemPrototype(), QStandardItem::clone() +*/ +void QStandardItemModel::setItemPrototype(const QStandardItem *item) +{ + Q_D(QStandardItemModel); + if (d->itemPrototype != item) { + delete d->itemPrototype; + d->itemPrototype = item; + } +} + +/*! + \since 4.2 + + Returns the item prototype used by the model. The model uses the item + prototype as an item factory when it needs to construct new items on + demand (for instance, when a view or item delegate calls setData()). + + \sa setItemPrototype() +*/ +const QStandardItem *QStandardItemModel::itemPrototype() const +{ + Q_D(const QStandardItemModel); + return d->itemPrototype; +} + +/*! + \since 4.2 + + Returns a list of items that match the given \a text, using the given \a + flags, in the given \a column. +*/ +QList QStandardItemModel::findItems(const QString &text, + Qt::MatchFlags flags, int column) const +{ + QModelIndexList indexes = match(index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList items; + for (int i = 0; i < indexes.size(); ++i) + items.append(itemFromIndex(indexes.at(i))); + return items; +} + +/*! + \since 4.2 + + Appends a row containing \a items. If necessary, the column count is + increased to the size of \a items. + + \sa insertRow(), appendColumn() +*/ +void QStandardItemModel::appendRow(const QList &items) +{ + invisibleRootItem()->appendRow(items); +} + +/*! + \since 4.2 + + Appends a column containing \a items. If necessary, the row count is + increased to the size of \a items. + + \sa insertColumn(), appendRow() +*/ +void QStandardItemModel::appendColumn(const QList &items) +{ + invisibleRootItem()->appendColumn(items); +} + +/*! + \since 4.2 + \fn QStandardItemModel::appendRow(QStandardItem *item) + \overload + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new \a item. +*/ + +/*! + \since 4.2 + + Inserts a row at \a row containing \a items. If necessary, the column + count is increased to the size of \a items. + + \sa takeRow(), appendRow(), insertColumn() +*/ +void QStandardItemModel::insertRow(int row, const QList &items) +{ + invisibleRootItem()->insertRow(row, items); +} + +/*! + \since 4.2 + + \fn void QStandardItemModel::insertRow(int row, QStandardItem *item) + \overload + + Inserts a row at \a row containing \a item. + + When building a list or a tree that has only one column, this function + provides a convenient way to append a single new item. +*/ + +/*! + \since 4.2 + + Inserts a column at \a column containing \a items. If necessary, the row + count is increased to the size of \a items. + + \sa takeColumn(), appendColumn(), insertRow() +*/ +void QStandardItemModel::insertColumn(int column, const QList &items) +{ + invisibleRootItem()->insertColumn(column, items); +} + +/*! + \since 4.2 + + Removes the item at (\a row, \a column) without deleting it. The model + releases ownership of the item. + + \sa item(), takeRow(), takeColumn() +*/ +QStandardItem *QStandardItemModel::takeItem(int row, int column) +{ + Q_D(QStandardItemModel); + return d->root->takeChild(row, column); +} + +/*! + \since 4.2 + + Removes the given \a row without deleting the row items, and returns a + list of pointers to the removed items. The model releases ownership of the + items. For items in the row that have not been set, the corresponding + pointers in the list will be 0. + + \sa takeColumn() +*/ +QList QStandardItemModel::takeRow(int row) +{ + Q_D(QStandardItemModel); + return d->root->takeRow(row); +} + +/*! + \since 4.2 + + Removes the given \a column without deleting the column items, and returns + a list of pointers to the removed items. The model releases ownership of + the items. For items in the column that have not been set, the + corresponding pointers in the list will be 0. + + \sa takeRow() +*/ +QList QStandardItemModel::takeColumn(int column) +{ + Q_D(QStandardItemModel); + return d->root->takeColumn(column); +} + +/*! + \since 4.2 + + Removes the horizontal header item at \a column from the header without + deleting it, and returns a pointer to the item. The model releases + ownership of the item. + + \sa horizontalHeaderItem(), takeVerticalHeaderItem() +*/ +QStandardItem *QStandardItemModel::takeHorizontalHeaderItem(int column) +{ + Q_D(QStandardItemModel); + if ((column < 0) || (column >= columnCount())) + return 0; + QStandardItem *headerItem = d->columnHeaderItems.at(column); + if (headerItem) { + headerItem->d_func()->setParentAndModel(0, 0); + d->columnHeaderItems.replace(column, 0); + } + return headerItem; +} + +/*! + \since 4.2 + + Removes the vertical header item at \a row from the header without + deleting it, and returns a pointer to the item. The model releases + ownership of the item. + + \sa verticalHeaderItem(), takeHorizontalHeaderItem() +*/ +QStandardItem *QStandardItemModel::takeVerticalHeaderItem(int row) +{ + Q_D(QStandardItemModel); + if ((row < 0) || (row >= rowCount())) + return 0; + QStandardItem *headerItem = d->rowHeaderItems.at(row); + if (headerItem) { + headerItem->d_func()->setParentAndModel(0, 0); + d->rowHeaderItems.replace(row, 0); + } + return headerItem; +} + +/*! + \since 4.2 + \property QStandardItemModel::sortRole + \brief the item role that is used to query the model's data when sorting items + + The default value is Qt::DisplayRole. + + \sa sort(), QStandardItem::sortChildren() +*/ +int QStandardItemModel::sortRole() const +{ + Q_D(const QStandardItemModel); + return d->sortRole; +} + +void QStandardItemModel::setSortRole(int role) +{ + Q_D(QStandardItemModel); + d->sortRole = role; +} + +/*! + \reimp +*/ +int QStandardItemModel::columnCount(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->columnCount() : 0; +} + +/*! + \reimp +*/ +QVariant QStandardItemModel::data(const QModelIndex &index, int role) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(index); + return item ? item->data(role) : QVariant(); +} + +/*! + \reimp +*/ +Qt::ItemFlags QStandardItemModel::flags(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + if (!d->indexValid(index)) + return d->root->flags(); + QStandardItem *item = d->itemFromIndex(index); + if (item) + return item->flags(); + return Qt::ItemIsSelectable + |Qt::ItemIsEnabled + |Qt::ItemIsEditable + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled; +} + +/*! + \reimp +*/ +bool QStandardItemModel::hasChildren(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->hasChildren() : false; +} + +/*! + \reimp +*/ +QVariant QStandardItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_D(const QStandardItemModel); + if ((section < 0) + || ((orientation == Qt::Horizontal) && (section >= columnCount())) + || ((orientation == Qt::Vertical) && (section >= rowCount()))) { + return QVariant(); + } + QStandardItem *headerItem = 0; + if (orientation == Qt::Horizontal) + headerItem = d->columnHeaderItems.at(section); + else if (orientation == Qt::Vertical) + headerItem = d->rowHeaderItems.at(section); + return headerItem ? headerItem->data(role) + : QAbstractItemModel::headerData(section, orientation, role); +} + +/*! + \reimp + + QStandardItemModel supports both copy and move. +*/ +Qt::DropActions QStandardItemModel::supportedDropActions () const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +/*! + \reimp +*/ +QModelIndex QStandardItemModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *parentItem = d->itemFromIndex(parent); + if ((parentItem == 0) + || (row < 0) + || (column < 0) + || (row >= parentItem->rowCount()) + || (column >= parentItem->columnCount())) { + return QModelIndex(); + } + return createIndex(row, column, parentItem); +} + +/*! + \reimp +*/ +bool QStandardItemModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = parent.isValid() ? itemFromIndex(parent) : d->root.data(); + if (item == 0) + return false; + return item->d_func()->insertColumns(column, count, QList()); +} + +/*! + \reimp +*/ +bool QStandardItemModel::insertRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = parent.isValid() ? itemFromIndex(parent) : d->root.data(); + if (item == 0) + return false; + return item->d_func()->insertRows(row, count, QList()); +} + +/*! + \reimp +*/ +QMap QStandardItemModel::itemData(const QModelIndex &index) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(index); + return item ? item->d_func()->itemData() : QMap(); +} + +/*! + \reimp +*/ +QModelIndex QStandardItemModel::parent(const QModelIndex &child) const +{ + Q_D(const QStandardItemModel); + if (!d->indexValid(child)) + return QModelIndex(); + QStandardItem *parentItem = static_cast(child.internalPointer()); + return indexFromItem(parentItem); +} + +/*! + \reimp +*/ +bool QStandardItemModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + if ((item == 0) || (count < 1) || (column < 0) || ((column + count) > item->columnCount())) + return false; + item->removeColumns(column, count); + return true; +} + +/*! + \reimp +*/ +bool QStandardItemModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + if ((item == 0) || (count < 1) || (row < 0) || ((row + count) > item->rowCount())) + return false; + item->removeRows(row, count); + return true; +} + +/*! + \reimp +*/ +int QStandardItemModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const QStandardItemModel); + QStandardItem *item = d->itemFromIndex(parent); + return item ? item->rowCount() : 0; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + QStandardItem *item = itemFromIndex(index); + if (item == 0) + return false; + item->setData(value, role); + return true; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + Q_D(QStandardItemModel); + if ((section < 0) + || ((orientation == Qt::Horizontal) && (section >= columnCount())) + || ((orientation == Qt::Vertical) && (section >= rowCount()))) { + return false; + } + QStandardItem *headerItem = 0; + if (orientation == Qt::Horizontal) { + headerItem = d->columnHeaderItems.at(section); + if (headerItem == 0) { + headerItem = d->createItem(); + headerItem->d_func()->setModel(this); + d->columnHeaderItems.replace(section, headerItem); + } + } else if (orientation == Qt::Vertical) { + headerItem = d->rowHeaderItems.at(section); + if (headerItem == 0) { + headerItem = d->createItem(); + headerItem->d_func()->setModel(this); + d->rowHeaderItems.replace(section, headerItem); + } + } + if (headerItem) { + headerItem->setData(value, role); + return true; + } + return false; +} + +/*! + \reimp +*/ +bool QStandardItemModel::setItemData(const QModelIndex &index, const QMap &roles) +{ + QStandardItem *item = itemFromIndex(index); + if (item == 0) + return false; + item->d_func()->setItemData(roles); + return true; +} + +/*! + \reimp +*/ +void QStandardItemModel::sort(int column, Qt::SortOrder order) +{ + Q_D(QStandardItemModel); + d->root->sortChildren(column, order); +} + +/*! + \fn QObject *QStandardItemModel::parent() const + \internal +*/ + + +/*! + \reimp +*/ +QStringList QStandardItemModel::mimeTypes() const +{ + return QAbstractItemModel::mimeTypes() << QLatin1String("application/x-qstandarditemmodeldatalist"); +} + +/*! + \reimp +*/ +QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *data = QAbstractItemModel::mimeData(indexes); + if(!data) + return 0; + + QString format = QLatin1String("application/x-qstandarditemmodeldatalist"); + if (!mimeTypes().contains(format)) + return data; + QByteArray encoded; + QDataStream stream(&encoded, QIODevice::WriteOnly); + + QSet itemsSet; + QStack stack; + itemsSet.reserve(indexes.count()); + stack.reserve(indexes.count()); + for (int i = 0; i < indexes.count(); ++i) { + QStandardItem *item = itemFromIndex(indexes.at(i)); + itemsSet << item; + stack.push(item); + } + + //remove duplicates childrens + { + QSet seen; + while (!stack.isEmpty()) { + QStandardItem *itm = stack.pop(); + if (seen.contains(itm)) + continue; + seen.insert(itm); + + const QVector &childList = itm->d_func()->children; + for (int i = 0; i < childList.count(); ++i) { + QStandardItem *chi = childList.at(i); + if (chi) { + QSet::iterator it = itemsSet.find(chi); + if (it != itemsSet.end()) { + itemsSet.erase(it); + } + stack.push(chi); + } + } + } + } + + stack.reserve(itemsSet.count()); + foreach (QStandardItem *item, itemsSet) { + stack.push(item); + } + + //stream everything recursively + while (!stack.isEmpty()) { + QStandardItem *item = stack.pop(); + if(itemsSet.contains(item)) { //if the item is selection 'top-level', strem its position + stream << item->row() << item->column(); + } + if(item) { + stream << *item << item->columnCount() << item->d_ptr->children.count(); + stack += item->d_ptr->children; + } else { + QStandardItem dummy; + stream << dummy << 0 << 0; + } + } + + data->setData(format, encoded); + return data; +} + + +/* \internal + Used by QStandardItemModel::dropMimeData + stream out an item and his children + */ +void QStandardItemModelPrivate::decodeDataRecursive(QDataStream &stream, QStandardItem *item) +{ + int colCount, childCount; + stream >> *item; + stream >> colCount >> childCount; + item->setColumnCount(colCount); + + int childPos = childCount; + + while(childPos > 0) { + childPos--; + QStandardItem *child = createItem(); + decodeDataRecursive(stream, child); + item->setChild( childPos / colCount, childPos % colCount, child); + } +} + + +/*! + \reimp +*/ +bool QStandardItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + Q_D(QStandardItemModel); + // check if the action is supported + if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) + return false; + // check if the format is supported + QString format = QLatin1String("application/x-qstandarditemmodeldatalist"); + if (!data->hasFormat(format)) + return QAbstractItemModel::dropMimeData(data, action, row, column, parent); + + if (row > rowCount(parent)) + row = rowCount(parent); + if (row == -1) + row = rowCount(parent); + if (column == -1) + column = 0; + + // decode and insert + QByteArray encoded = data->data(format); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + + //code based on QAbstractItemModel::decodeData + // adapted to work with QStandardItem + int top = INT_MAX; + int left = INT_MAX; + int bottom = 0; + int right = 0; + QVector rows, columns; + QVector items; + + while (!stream.atEnd()) { + int r, c; + QStandardItem *item = d->createItem(); + stream >> r >> c; + d->decodeDataRecursive(stream, item); + + rows.append(r); + columns.append(c); + items.append(item); + top = qMin(r, top); + left = qMin(c, left); + bottom = qMax(r, bottom); + right = qMax(c, right); + } + + // insert the dragged items into the table, use a bit array to avoid overwriting items, + // since items from different tables can have the same row and column + int dragRowCount = 0; + int dragColumnCount = right - left + 1; + + // Compute the number of continuous rows upon insertion and modify the rows to match + QVector rowsToInsert(bottom + 1); + for (int i = 0; i < rows.count(); ++i) + rowsToInsert[rows.at(i)] = 1; + for (int i = 0; i < rowsToInsert.count(); ++i) { + if (rowsToInsert[i] == 1){ + rowsToInsert[i] = dragRowCount; + ++dragRowCount; + } + } + for (int i = 0; i < rows.count(); ++i) + rows[i] = top + rowsToInsert[rows[i]]; + + QBitArray isWrittenTo(dragRowCount * dragColumnCount); + + // make space in the table for the dropped data + int colCount = columnCount(parent); + if (colCount < dragColumnCount + column) { + insertColumns(colCount, dragColumnCount + column - colCount, parent); + colCount = columnCount(parent); + } + insertRows(row, dragRowCount, parent); + + row = qMax(0, row); + column = qMax(0, column); + + QStandardItem *parentItem = itemFromIndex (parent); + if (!parentItem) + parentItem = invisibleRootItem(); + + QVector newIndexes(items.size()); + // set the data in the table + for (int j = 0; j < items.size(); ++j) { + int relativeRow = rows.at(j) - top; + int relativeColumn = columns.at(j) - left; + int destinationRow = relativeRow + row; + int destinationColumn = relativeColumn + column; + int flat = (relativeRow * dragColumnCount) + relativeColumn; + // if the item was already written to, or we just can't fit it in the table, create a new row + if (destinationColumn >= colCount || isWrittenTo.testBit(flat)) { + destinationColumn = qBound(column, destinationColumn, colCount - 1); + destinationRow = row + dragRowCount; + insertRows(row + dragRowCount, 1, parent); + flat = (dragRowCount * dragColumnCount) + relativeColumn; + isWrittenTo.resize(++dragRowCount * dragColumnCount); + } + if (!isWrittenTo.testBit(flat)) { + newIndexes[j] = index(destinationRow, destinationColumn, parentItem->index()); + isWrittenTo.setBit(flat); + } + } + + for(int k = 0; k < newIndexes.size(); k++) { + if (newIndexes.at(k).isValid()) { + parentItem->setChild(newIndexes.at(k).row(), newIndexes.at(k).column(), items.at(k)); + } else { + delete items.at(k); + } + } + + return true; +} + +QT_END_NAMESPACE + +#include "moc_qstandarditemmodel.cpp" + +#endif // QT_NO_STANDARDITEMMODEL diff --git a/src/gui/itemviews/qstandarditemmodel.h b/src/gui/itemviews/qstandarditemmodel.h new file mode 100644 index 0000000000..6cc0270dad --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel.h @@ -0,0 +1,456 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTANDARDITEMMODEL_H +#define QSTANDARDITEMMODEL_H + +#include +#include +#include +#include +#ifndef QT_NO_DATASTREAM +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STANDARDITEMMODEL + +template class QList; + +class QStandardItemModel; + +class QStandardItemPrivate; +class Q_GUI_EXPORT QStandardItem +{ +public: + QStandardItem(); + QStandardItem(const QString &text); + QStandardItem(const QIcon &icon, const QString &text); + explicit QStandardItem(int rows, int columns = 1); + virtual ~QStandardItem(); + + virtual QVariant data(int role = Qt::UserRole + 1) const; + virtual void setData(const QVariant &value, int role = Qt::UserRole + 1); + + inline QString text() const { + return qvariant_cast(data(Qt::DisplayRole)); + } + inline void setText(const QString &text); + + inline QIcon icon() const { + return qvariant_cast(data(Qt::DecorationRole)); + } + inline void setIcon(const QIcon &icon); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const { + return qvariant_cast(data(Qt::ToolTipRole)); + } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_STATUSTIP + inline QString statusTip() const { + return qvariant_cast(data(Qt::StatusTipRole)); + } + inline void setStatusTip(const QString &statusTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const { + return qvariant_cast(data(Qt::WhatsThisRole)); + } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QSize sizeHint() const { + return qvariant_cast(data(Qt::SizeHintRole)); + } + inline void setSizeHint(const QSize &sizeHint); + + inline QFont font() const { + return qvariant_cast(data(Qt::FontRole)); + } + inline void setFont(const QFont &font); + + inline Qt::Alignment textAlignment() const { + return Qt::Alignment(qvariant_cast(data(Qt::TextAlignmentRole))); + } + inline void setTextAlignment(Qt::Alignment textAlignment); + + inline QBrush background() const { + return qvariant_cast(data(Qt::BackgroundRole)); + } + inline void setBackground(const QBrush &brush); + + inline QBrush foreground() const { + return qvariant_cast(data(Qt::ForegroundRole)); + } + inline void setForeground(const QBrush &brush); + + inline Qt::CheckState checkState() const { + return Qt::CheckState(qvariant_cast(data(Qt::CheckStateRole))); + } + inline void setCheckState(Qt::CheckState checkState); + + inline QString accessibleText() const { + return qvariant_cast(data(Qt::AccessibleTextRole)); + } + inline void setAccessibleText(const QString &accessibleText); + + inline QString accessibleDescription() const { + return qvariant_cast(data(Qt::AccessibleDescriptionRole)); + } + inline void setAccessibleDescription(const QString &accessibleDescription); + + Qt::ItemFlags flags() const; + void setFlags(Qt::ItemFlags flags); + + inline bool isEnabled() const { + return (flags() & Qt::ItemIsEnabled) != 0; + } + void setEnabled(bool enabled); + + inline bool isEditable() const { + return (flags() & Qt::ItemIsEditable) != 0; + } + void setEditable(bool editable); + + inline bool isSelectable() const { + return (flags() & Qt::ItemIsSelectable) != 0; + } + void setSelectable(bool selectable); + + inline bool isCheckable() const { + return (flags() & Qt::ItemIsUserCheckable) != 0; + } + void setCheckable(bool checkable); + + inline bool isTristate() const { + return (flags() & Qt::ItemIsTristate) != 0; + } + void setTristate(bool tristate); + +#ifndef QT_NO_DRAGANDDROP + inline bool isDragEnabled() const { + return (flags() & Qt::ItemIsDragEnabled) != 0; + } + void setDragEnabled(bool dragEnabled); + + inline bool isDropEnabled() const { + return (flags() & Qt::ItemIsDropEnabled) != 0; + } + void setDropEnabled(bool dropEnabled); +#endif // QT_NO_DRAGANDDROP + + QStandardItem *parent() const; + int row() const; + int column() const; + QModelIndex index() const; + QStandardItemModel *model() const; + + int rowCount() const; + void setRowCount(int rows); + int columnCount() const; + void setColumnCount(int columns); + + bool hasChildren() const; + QStandardItem *child(int row, int column = 0) const; + void setChild(int row, int column, QStandardItem *item); + inline void setChild(int row, QStandardItem *item); + + void insertRow(int row, const QList &items); + void insertColumn(int column, const QList &items); + void insertRows(int row, const QList &items); + void insertRows(int row, int count); + void insertColumns(int column, int count); + + void removeRow(int row); + void removeColumn(int column); + void removeRows(int row, int count); + void removeColumns(int column, int count); + + inline void appendRow(const QList &items); + inline void appendRows(const QList &items); + inline void appendColumn(const QList &items); + inline void insertRow(int row, QStandardItem *item); + inline void appendRow(QStandardItem *item); + + QStandardItem *takeChild(int row, int column = 0); + QList takeRow(int row); + QList takeColumn(int column); + + void sortChildren(int column, Qt::SortOrder order = Qt::AscendingOrder); + + virtual QStandardItem *clone() const; + + enum ItemType { Type = 0, UserType = 1000 }; + virtual int type() const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + virtual bool operator<(const QStandardItem &other) const; + +protected: + QStandardItem(const QStandardItem &other); + QStandardItem(QStandardItemPrivate &dd); + QStandardItem &operator=(const QStandardItem &other); + QScopedPointer d_ptr; + + void emitDataChanged(); + +private: + Q_DECLARE_PRIVATE(QStandardItem) + friend class QStandardItemModelPrivate; + friend class QStandardItemModel; +}; + +inline void QStandardItem::setText(const QString &atext) +{ setData(atext, Qt::DisplayRole); } + +inline void QStandardItem::setIcon(const QIcon &aicon) +{ setData(aicon, Qt::DecorationRole); } + +#ifndef QT_NO_TOOLTIP +inline void QStandardItem::setToolTip(const QString &atoolTip) +{ setData(atoolTip, Qt::ToolTipRole); } +#endif + +#ifndef QT_NO_STATUSTIP +inline void QStandardItem::setStatusTip(const QString &astatusTip) +{ setData(astatusTip, Qt::StatusTipRole); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QStandardItem::setWhatsThis(const QString &awhatsThis) +{ setData(awhatsThis, Qt::WhatsThisRole); } +#endif + +inline void QStandardItem::setSizeHint(const QSize &asizeHint) +{ setData(asizeHint, Qt::SizeHintRole); } + +inline void QStandardItem::setFont(const QFont &afont) +{ setData(afont, Qt::FontRole); } + +inline void QStandardItem::setTextAlignment(Qt::Alignment atextAlignment) +{ setData(int(atextAlignment), Qt::TextAlignmentRole); } + +inline void QStandardItem::setBackground(const QBrush &abrush) +{ setData(abrush, Qt::BackgroundRole); } + +inline void QStandardItem::setForeground(const QBrush &abrush) +{ setData(abrush, Qt::ForegroundRole); } + +inline void QStandardItem::setCheckState(Qt::CheckState acheckState) +{ setData(acheckState, Qt::CheckStateRole); } + +inline void QStandardItem::setAccessibleText(const QString &aaccessibleText) +{ setData(aaccessibleText, Qt::AccessibleTextRole); } + +inline void QStandardItem::setAccessibleDescription(const QString &aaccessibleDescription) +{ setData(aaccessibleDescription, Qt::AccessibleDescriptionRole); } + +inline void QStandardItem::setChild(int arow, QStandardItem *aitem) +{ setChild(arow, 0, aitem); } + +inline void QStandardItem::appendRow(const QList &aitems) +{ insertRow(rowCount(), aitems); } + +inline void QStandardItem::appendRows(const QList &aitems) +{ insertRows(rowCount(), aitems); } + +inline void QStandardItem::appendColumn(const QList &aitems) +{ insertColumn(columnCount(), aitems); } + +inline void QStandardItem::insertRow(int arow, QStandardItem *aitem) +{ insertRow(arow, QList() << aitem); } + +inline void QStandardItem::appendRow(QStandardItem *aitem) +{ insertRow(rowCount(), aitem); } + +class QStandardItemModelPrivate; + +class Q_GUI_EXPORT QStandardItemModel : public QAbstractItemModel +{ + Q_OBJECT + Q_PROPERTY(int sortRole READ sortRole WRITE setSortRole) + +public: + explicit QStandardItemModel(QObject *parent = 0); + QStandardItemModel(int rows, int columns, QObject *parent = 0); + ~QStandardItemModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()); + + Qt::ItemFlags flags(const QModelIndex &index) const; + Qt::DropActions supportedDropActions() const; + + QMap itemData(const QModelIndex &index) const; + bool setItemData(const QModelIndex &index, const QMap &roles); + + void clear(); + +#ifdef Q_NO_USING_KEYWORD + inline QObject *parent() const { return QObject::parent(); } +#else + using QObject::parent; +#endif + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStandardItem *itemFromIndex(const QModelIndex &index) const; + QModelIndex indexFromItem(const QStandardItem *item) const; + + QStandardItem *item(int row, int column = 0) const; + void setItem(int row, int column, QStandardItem *item); + inline void setItem(int row, QStandardItem *item); + QStandardItem *invisibleRootItem() const; + + QStandardItem *horizontalHeaderItem(int column) const; + void setHorizontalHeaderItem(int column, QStandardItem *item); + QStandardItem *verticalHeaderItem(int row) const; + void setVerticalHeaderItem(int row, QStandardItem *item); + + void setHorizontalHeaderLabels(const QStringList &labels); + void setVerticalHeaderLabels(const QStringList &labels); + + void setRowCount(int rows); + void setColumnCount(int columns); + + void appendRow(const QList &items); + void appendColumn(const QList &items); + inline void appendRow(QStandardItem *item); + + void insertRow(int row, const QList &items); + void insertColumn(int column, const QList &items); + inline void insertRow(int row, QStandardItem *item); + + inline bool insertRow(int row, const QModelIndex &parent = QModelIndex()); + inline bool insertColumn(int column, const QModelIndex &parent = QModelIndex()); + + QStandardItem *takeItem(int row, int column = 0); + QList takeRow(int row); + QList takeColumn(int column); + + QStandardItem *takeHorizontalHeaderItem(int column); + QStandardItem *takeVerticalHeaderItem(int row); + + const QStandardItem *itemPrototype() const; + void setItemPrototype(const QStandardItem *item); + + QList findItems(const QString &text, + Qt::MatchFlags flags = Qt::MatchExactly, + int column = 0) const; + + int sortRole() const; + void setSortRole(int role); + + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData (const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + +Q_SIGNALS: + void itemChanged(QStandardItem *item); + +protected: + QStandardItemModel(QStandardItemModelPrivate &dd, QObject *parent = 0); + +private: + friend class QStandardItemPrivate; + friend class QStandardItem; + Q_DISABLE_COPY(QStandardItemModel) + Q_DECLARE_PRIVATE(QStandardItemModel) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight)) +}; + +inline void QStandardItemModel::setItem(int arow, QStandardItem *aitem) +{ setItem(arow, 0, aitem); } + +inline void QStandardItemModel::appendRow(QStandardItem *aitem) +{ appendRow(QList() << aitem); } + +inline void QStandardItemModel::insertRow(int arow, QStandardItem *aitem) +{ insertRow(arow, QList() << aitem); } + +inline bool QStandardItemModel::insertRow(int arow, const QModelIndex &aparent) +{ return QAbstractItemModel::insertRow(arow, aparent); } +inline bool QStandardItemModel::insertColumn(int acolumn, const QModelIndex &aparent) +{ return QAbstractItemModel::insertColumn(acolumn, aparent); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QStandardItem &item); +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QStandardItem &item); +#endif + +#endif // QT_NO_STANDARDITEMMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QSTANDARDITEMMODEL_H diff --git a/src/gui/itemviews/qstandarditemmodel_p.h b/src/gui/itemviews/qstandarditemmodel_p.h new file mode 100644 index 0000000000..bc053398a3 --- /dev/null +++ b/src/gui/itemviews/qstandarditemmodel_p.h @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTANDARDITEMMODEL_P_H +#define QSTANDARDITEMMODEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractitemmodel_p.h" + +#ifndef QT_NO_STANDARDITEMMODEL + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QStandardItemPrivate +{ + Q_DECLARE_PUBLIC(QStandardItem) +public: + inline QStandardItemPrivate() + : model(0), + parent(0), + rows(0), + columns(0), + q_ptr(0), + lastIndexOf(2) + { } + virtual ~QStandardItemPrivate(); + + inline int childIndex(int row, int column) const { + if ((row < 0) || (column < 0) + || (row >= rowCount()) || (column >= columnCount())) { + return -1; + } + return (row * columnCount()) + column; + } + inline int childIndex(const QStandardItem *child) { + int start = qMax(0, lastIndexOf -2); + lastIndexOf = children.indexOf(const_cast(child), start); + if (lastIndexOf == -1 && start != 0) + lastIndexOf = children.lastIndexOf(const_cast(child), start); + return lastIndexOf; + } + QPair position() const; + void setChild(int row, int column, QStandardItem *item, + bool emitChanged = false); + inline int rowCount() const { + return rows; + } + inline int columnCount() const { + return columns; + } + void childDeleted(QStandardItem *child); + + void setModel(QStandardItemModel *mod); + + inline void setParentAndModel( + QStandardItem *par, + QStandardItemModel *mod) { + setModel(mod); + parent = par; + } + + void changeFlags(bool enable, Qt::ItemFlags f); + void setItemData(const QMap &roles); + const QMap itemData() const; + + bool insertRows(int row, int count, const QList &items); + bool insertRows(int row, const QList &items); + bool insertColumns(int column, int count, const QList &items); + + void sortChildren(int column, Qt::SortOrder order); + + QStandardItemModel *model; + QStandardItem *parent; + QVector values; + QVector children; + int rows; + int columns; + + QStandardItem *q_ptr; + + int lastIndexOf; +}; + +class QStandardItemModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QStandardItemModel) + +public: + QStandardItemModelPrivate(); + virtual ~QStandardItemModelPrivate(); + + void init(); + + inline QStandardItem *createItem() const { + return itemPrototype ? itemPrototype->clone() : new QStandardItem; + } + + inline QStandardItem *itemFromIndex(const QModelIndex &index) const { + Q_Q(const QStandardItemModel); + if (!index.isValid()) + return root.data(); + if (index.model() != q) + return 0; + QStandardItem *parent = static_cast(index.internalPointer()); + if (parent == 0) + return 0; + return parent->child(index.row(), index.column()); + } + + void sort(QStandardItem *parent, int column, Qt::SortOrder order); + void itemChanged(QStandardItem *item); + void rowsAboutToBeInserted(QStandardItem *parent, int start, int end); + void columnsAboutToBeInserted(QStandardItem *parent, int start, int end); + void rowsAboutToBeRemoved(QStandardItem *parent, int start, int end); + void columnsAboutToBeRemoved(QStandardItem *parent, int start, int end); + void rowsInserted(QStandardItem *parent, int row, int count); + void columnsInserted(QStandardItem *parent, int column, int count); + void rowsRemoved(QStandardItem *parent, int row, int count); + void columnsRemoved(QStandardItem *parent, int column, int count); + + void _q_emitItemChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight); + + void decodeDataRecursive(QDataStream &stream, QStandardItem *item); + + QVector columnHeaderItems; + QVector rowHeaderItems; + QScopedPointer root; + const QStandardItem *itemPrototype; + int sortRole; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STANDARDITEMMODEL + +#endif // QSTANDARDITEMMODEL_P_H diff --git a/src/gui/itemviews/qstringlistmodel.cpp b/src/gui/itemviews/qstringlistmodel.cpp new file mode 100644 index 0000000000..d2e4c2204e --- /dev/null +++ b/src/gui/itemviews/qstringlistmodel.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + A simple model that uses a QStringList as its data source. +*/ + +#include "qstringlistmodel.h" + +#ifndef QT_NO_STRINGLISTMODEL + +QT_BEGIN_NAMESPACE + +/*! + \class QStringListModel + \brief The QStringListModel class provides a model that supplies strings to views. + + \ingroup model-view + + + QStringListModel is an editable model that can be used for simple + cases where you need to display a number of strings in a view + widget, such as a QListView or a QComboBox. + + The model provides all the standard functions of an editable + model, representing the data in the string list as a model with + one column and a number of rows equal to the number of items in + the list. + + Model indexes corresponding to items are obtained with the + \l{QAbstractListModel::index()}{index()} function, and item flags + are obtained with flags(). Item data is read with the data() + function and written with setData(). The number of rows (and + number of items in the string list) can be found with the + rowCount() function. + + The model can be constructed with an existing string list, or + strings can be set later with the setStringList() convenience + function. Strings can also be inserted in the usual way with the + insertRows() function, and removed with removeRows(). The contents + of the string list can be retrieved with the stringList() + convenience function. + + An example usage of QStringListModel: + + \snippet doc/src/snippets/qstringlistmodel/main.cpp 0 + + \sa QAbstractListModel, QAbstractItemModel, {Model Classes} +*/ + +/*! + Constructs a string list model with the given \a parent. +*/ + +QStringListModel::QStringListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +/*! + Constructs a string list model containing the specified \a strings + with the given \a parent. +*/ + +QStringListModel::QStringListModel(const QStringList &strings, QObject *parent) + : QAbstractListModel(parent), lst(strings) +{ +} + +/*! + Returns the number of rows in the model. This value corresponds to the + number of items in the model's internal string list. + + The optional \a parent argument is in most models used to specify + the parent of the rows to be counted. Because this is a list if a + valid parent is specified, the result will always be 0. + + \sa insertRows(), removeRows(), QAbstractItemModel::rowCount() +*/ + +int QStringListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return lst.count(); +} + +/*! + Returns data for the specified \a role, from the item with the + given \a index. + + If the view requests an invalid index, an invalid variant is returned. + + \sa setData() +*/ + +QVariant QStringListModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= lst.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole) + return lst.at(index.row()); + + return QVariant(); +} + +/*! + Returns the flags for the item with the given \a index. + + Valid items are enabled, selectable, editable, drag enabled and drop enabled. + + \sa QAbstractItemModel::flags() +*/ + +Qt::ItemFlags QStringListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +/*! + Sets the data for the specified \a role in the item with the given + \a index in the model, to the provided \a value. + + The dataChanged() signal is emitted if the item is changed. + + \sa Qt::ItemDataRole, data() +*/ + +bool QStringListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() >= 0 && index.row() < lst.size() + && (role == Qt::EditRole || role == Qt::DisplayRole)) { + lst.replace(index.row(), value.toString()); + emit dataChanged(index, index); + return true; + } + return false; +} + +/*! + Inserts \a count rows into the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are inserted in the top level of + the model. + + \sa QAbstractItemModel::insertRows() +*/ + +bool QStringListModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (count < 1 || row < 0 || row > rowCount(parent)) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.insert(row, QString()); + + endInsertRows(); + + return true; +} + +/*! + Removes \a count rows from the model, beginning at the given \a row. + + The \a parent index of the rows is optional and is only used for + consistency with QAbstractItemModel. By default, a null index is + specified, indicating that the rows are removed in the top level of + the model. + + \sa QAbstractItemModel::removeRows() +*/ + +bool QStringListModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count <= 0 || row < 0 || (row + count) > rowCount(parent)) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + + for (int r = 0; r < count; ++r) + lst.removeAt(row); + + endRemoveRows(); + + return true; +} + +static bool ascendingLessThan(const QPair &s1, const QPair &s2) +{ + return s1.first < s2.first; +} + +static bool decendingLessThan(const QPair &s1, const QPair &s2) +{ + return s1.first > s2.first; +} + +/*! + \reimp +*/ +void QStringListModel::sort(int, Qt::SortOrder order) +{ + emit layoutAboutToBeChanged(); + + QList > list; + for (int i = 0; i < lst.count(); ++i) + list.append(QPair(lst.at(i), i)); + + if (order == Qt::AscendingOrder) + qSort(list.begin(), list.end(), ascendingLessThan); + else + qSort(list.begin(), list.end(), decendingLessThan); + + lst.clear(); + QVector forwarding(list.count()); + for (int i = 0; i < list.count(); ++i) { + lst.append(list.at(i).first); + forwarding[list.at(i).second] = i; + } + + QModelIndexList oldList = persistentIndexList(); + QModelIndexList newList; + for (int i = 0; i < oldList.count(); ++i) + newList.append(index(forwarding.at(oldList.at(i).row()), 0)); + changePersistentIndexList(oldList, newList); + + emit layoutChanged(); +} + +/*! + Returns the string list used by the model to store data. +*/ +QStringList QStringListModel::stringList() const +{ + return lst; +} + +/*! + Sets the model's internal string list to \a strings. The model will + notify any attached views that its underlying data has changed. + + \sa dataChanged() +*/ +void QStringListModel::setStringList(const QStringList &strings) +{ + emit beginResetModel(); + lst = strings; + emit endResetModel(); +} + +/*! + \reimp +*/ +Qt::DropActions QStringListModel::supportedDropActions() const +{ + return QAbstractItemModel::supportedDropActions() | Qt::MoveAction; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STRINGLISTMODEL diff --git a/src/gui/itemviews/qstringlistmodel.h b/src/gui/itemviews/qstringlistmodel.h new file mode 100644 index 0000000000..b9b284cfc5 --- /dev/null +++ b/src/gui/itemviews/qstringlistmodel.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTRINGLISTMODEL_H +#define QSTRINGLISTMODEL_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STRINGLISTMODEL + +class Q_GUI_EXPORT QStringListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit QStringListModel(QObject *parent = 0); + QStringListModel(const QStringList &strings, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + QStringList stringList() const; + void setStringList(const QStringList &strings); + + Qt::DropActions supportedDropActions() const; + +private: + Q_DISABLE_COPY(QStringListModel) + QStringList lst; +}; + +#endif // QT_NO_STRINGLISTMODEL + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTRINGLISTMODEL_H diff --git a/src/gui/itemviews/qstyleditemdelegate.cpp b/src/gui/itemviews/qstyleditemdelegate.cpp new file mode 100644 index 0000000000..4989c05c69 --- /dev/null +++ b/src/gui/itemviews/qstyleditemdelegate.cpp @@ -0,0 +1,765 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstyleditemdelegate.h" + +#ifndef QT_NO_ITEMVIEWS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QStyledItemDelegatePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QStyledItemDelegate) + +public: + QStyledItemDelegatePrivate() : factory(0) { } + + static const QWidget *widget(const QStyleOptionViewItem &option) + { + if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast(&option)) + return v3->widget; + return 0; + } + + const QItemEditorFactory *editorFactory() const + { + return factory ? factory : QItemEditorFactory::defaultFactory(); + } + + void _q_commitDataAndCloseEditor(QWidget *editor) + { + Q_Q(QStyledItemDelegate); + emit q->commitData(editor); + emit q->closeEditor(editor, QAbstractItemDelegate::SubmitModelCache); + } + QItemEditorFactory *factory; +}; + +/*! + \class QStyledItemDelegate + + \brief The QStyledItemDelegate class provides display and editing facilities for + data items from a model. + + \ingroup model-view + + \since 4.4 + + When displaying data from models in Qt item views, e.g., a + QTableView, the individual items are drawn by a delegate. Also, + when an item is edited, it provides an editor widget, which is + placed on top of the item view while editing takes place. + QStyledItemDelegate is the default delegate for all Qt item + views, and is installed upon them when they are created. + + The QStyledItemDelegate class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view + framework}. The delegate allows the display and editing of items + to be developed independently from the model and view. + + The data of items in models are assigned an + \l{Qt::}{ItemDataRole}; each item can store a QVariant for each + role. QStyledItemDelegate implements display and editing for the + most common datatypes expected by users, including booleans, + integers, and strings. + + The data will be drawn differently depending on which role they + have in the model. The following table describes the roles and the + data types the delegate can handle for each of them. It is often + sufficient to ensure that the model returns appropriate data for + each of the roles to determine the appearance of items in views. + + \table + \header \o Role \o Accepted Types + \omit + \row \o \l Qt::AccessibleDescriptionRole \o QString + \row \o \l Qt::AccessibleTextRole \o QString + \endomit + \row \o \l Qt::BackgroundRole \o QBrush + \row \o \l Qt::BackgroundColorRole \o QColor (obsolete; use Qt::BackgroundRole instead) + \row \o \l Qt::CheckStateRole \o Qt::CheckState + \row \o \l Qt::DecorationRole \o QIcon, QPixmap, QImage and QColor + \row \o \l Qt::DisplayRole \o QString and types with a string representation + \row \o \l Qt::EditRole \o See QItemEditorFactory for details + \row \o \l Qt::FontRole \o QFont + \row \o \l Qt::SizeHintRole \o QSize + \omit + \row \o \l Qt::StatusTipRole \o + \endomit + \row \o \l Qt::TextAlignmentRole \o Qt::Alignment + \row \o \l Qt::ForegroundRole \o QBrush + \row \o \l Qt::TextColorRole \o QColor (obsolete; use Qt::ForegroundRole instead) + \omit + \row \o \l Qt::ToolTipRole + \row \o \l Qt::WhatsThisRole + \endomit + \endtable + + Editors are created with a QItemEditorFactory; a default static + instance provided by QItemEditorFactory is installed on all item + delegates. You can set a custom factory using + setItemEditorFactory() or set a new default factory with + QItemEditorFactory::setDefaultFactory(). It is the data stored in + the item model with the \l{Qt::}{EditRole} that is edited. See the + QItemEditorFactory class for a more high-level introduction to + item editor factories. The \l{Color Editor Factory Example}{Color + Editor Factory} example shows how to create custom editors with a + factory. + + \section1 Subclassing QStyledItemDelegate + + If the delegate does not support painting of the data types you + need or you want to customize the drawing of items, you need to + subclass QStyledItemDelegate, and reimplement paint() and possibly + sizeHint(). The paint() function is called individually for each + item, and with sizeHint(), you can specify the hint for each + of them. + + When reimplementing paint(), one would typically handle the + datatypes one would like to draw and use the superclass + implementation for other types. + + The painting of check box indicators are performed by the current + style. The style also specifies the size and the bounding + rectangles in which to draw the data for the different data roles. + The bounding rectangle of the item itself is also calculated by + the style. When drawing already supported datatypes, it is + therefore a good idea to ask the style for these bounding + rectangles. The QStyle class description describes this in + more detail. + + If you wish to change any of the bounding rectangles calculated by + the style or the painting of check box indicators, you can + subclass QStyle. Note, however, that the size of the items can + also be affected by reimplementing sizeHint(). + + It is possible for a custom delegate to provide editors + without the use of an editor item factory. In this case, the + following virtual functions must be reimplemented: + + \list + \o createEditor() returns the widget used to change data from the model + and can be reimplemented to customize editing behavior. + \o setEditorData() provides the widget with data to manipulate. + \o updateEditorGeometry() ensures that the editor is displayed correctly + with respect to the item view. + \o setModelData() returns updated data to the model. + \endlist + + The \l{Star Delegate Example}{Star Delegate} example creates + editors by reimplementing these methods. + + \section1 QStyledItemDelegate vs. QItemDelegate + + Since Qt 4.4, there are two delegate classes: QItemDelegate and + QStyledItemDelegate. However, the default delegate is QStyledItemDelegate. + These two classes are independent alternatives to painting and providing + editors for items in views. The difference between them is that + QStyledItemDelegate uses the current style to paint its items. We therefore + recommend using QStyledItemDelegate as the base class when implementing + custom delegates or when working with Qt style sheets. The code required + for either class should be equal unless the custom delegate needs to use + the style for drawing. + + If you wish to customize the painting of item views, you should + implement a custom style. Please see the QStyle class + documentation for details. + + \sa {Delegate Classes}, QItemDelegate, QAbstractItemDelegate, QStyle, + {Spin Box Delegate Example}, {Star Delegate Example}, {Color + Editor Factory Example} +*/ + + +/*! + Constructs an item delegate with the given \a parent. +*/ +QStyledItemDelegate::QStyledItemDelegate(QObject *parent) + : QAbstractItemDelegate(*new QStyledItemDelegatePrivate(), parent) +{ +} + +/*! + Destroys the item delegate. +*/ +QStyledItemDelegate::~QStyledItemDelegate() +{ +} + +/*! + This function returns the string that the delegate will use to display the + Qt::DisplayRole of the model in \a locale. \a value is the value of the Qt::DisplayRole + provided by the model. + + The default implementation uses the QLocale::toString to convert \a value into + a QString. + + This function is not called for empty model indices, i.e., indices for which + the model returns an invalid QVariant. + + \sa QAbstractItemModel::data() +*/ +QString QStyledItemDelegate::displayText(const QVariant &value, const QLocale& locale) const +{ + QString text; + switch (value.userType()) { + case QMetaType::Float: + case QVariant::Double: + text = locale.toString(value.toReal()); + break; + case QVariant::Int: + case QVariant::LongLong: + text = locale.toString(value.toLongLong()); + break; + case QVariant::UInt: + case QVariant::ULongLong: + text = locale.toString(value.toULongLong()); + break; + case QVariant::Date: + text = locale.toString(value.toDate(), QLocale::ShortFormat); + break; + case QVariant::Time: + text = locale.toString(value.toTime(), QLocale::ShortFormat); + break; + case QVariant::DateTime: + text = locale.toString(value.toDateTime().date(), QLocale::ShortFormat); + text += QLatin1Char(' '); + text += locale.toString(value.toDateTime().time(), QLocale::ShortFormat); + break; + default: + // convert new lines into line separators + text = value.toString(); + for (int i = 0; i < text.count(); ++i) { + if (text.at(i) == QLatin1Char('\n')) + text[i] = QChar::LineSeparator; + } + break; + } + return text; +} + +/*! + Initialize \a option with the values using the index \a index. This method + is useful for subclasses when they need a QStyleOptionViewItem, but don't want + to fill in all the information themselves. This function will check the version + of the QStyleOptionViewItem and fill in the additional values for a + QStyleOptionViewItemV2, QStyleOptionViewItemV3 and QStyleOptionViewItemV4. + + \sa QStyleOption::initFrom() +*/ +void QStyledItemDelegate::initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::FontRole); + if (value.isValid() && !value.isNull()) { + option->font = qvariant_cast(value).resolve(option->font); + option->fontMetrics = QFontMetrics(option->font); + } + + value = index.data(Qt::TextAlignmentRole); + if (value.isValid() && !value.isNull()) + option->displayAlignment = Qt::Alignment(value.toInt()); + + value = index.data(Qt::ForegroundRole); + if (value.canConvert()) + option->palette.setBrush(QPalette::Text, qvariant_cast(value)); + + if (QStyleOptionViewItemV4 *v4 = qstyleoption_cast(option)) { + v4->index = index; + QVariant value = index.data(Qt::CheckStateRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasCheckIndicator; + v4->checkState = static_cast(value.toInt()); + } + + value = index.data(Qt::DecorationRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasDecoration; + switch (value.type()) { + case QVariant::Icon: { + v4->icon = qvariant_cast(value); + QIcon::Mode mode; + if (!(option->state & QStyle::State_Enabled)) + mode = QIcon::Disabled; + else if (option->state & QStyle::State_Selected) + mode = QIcon::Selected; + else + mode = QIcon::Normal; + QIcon::State state = option->state & QStyle::State_Open ? QIcon::On : QIcon::Off; + v4->decorationSize = v4->icon.actualSize(option->decorationSize, mode, state); + break; + } + case QVariant::Color: { + QPixmap pixmap(option->decorationSize); + pixmap.fill(qvariant_cast(value)); + v4->icon = QIcon(pixmap); + break; + } + case QVariant::Image: { + QImage image = qvariant_cast(value); + v4->icon = QIcon(QPixmap::fromImage(image)); + v4->decorationSize = image.size(); + break; + } + case QVariant::Pixmap: { + QPixmap pixmap = qvariant_cast(value); + v4->icon = QIcon(pixmap); + v4->decorationSize = pixmap.size(); + break; + } + default: + break; + } + } + + value = index.data(Qt::DisplayRole); + if (value.isValid() && !value.isNull()) { + v4->features |= QStyleOptionViewItemV2::HasDisplay; + v4->text = displayText(value, v4->locale); + } + + v4->backgroundBrush = qvariant_cast(index.data(Qt::BackgroundRole)); + } +} + +/*! + Renders the delegate using the given \a painter and style \a option for + the item specified by \a index. + + This function paints the item using the view's QStyle. + + When reimplementing paint in a subclass. Use the initStyleOption() + to set up the \a option in the same way as the + QStyledItemDelegate; the option will always be an instance of + QStyleOptionViewItemV4. Please see its class description for + information on its contents. + + Whenever possible, use the \a option while painting. + Especially its \l{QStyleOption::}{rect} variable to decide + where to draw and its \l{QStyleOption::}{state} to determine + if it is enabled or selected. + + After painting, you should ensure that the painter is returned to + its the state it was supplied in when this function was called. + For example, it may be useful to call QPainter::save() before + painting and QPainter::restore() afterwards. + + \sa QItemDelegate::paint(), QStyle::drawControl(), QStyle::CE_ItemViewItem +*/ +void QStyledItemDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); +} + +/*! + Returns the size needed by the delegate to display the item + specified by \a index, taking into account the style information + provided by \a option. + + This function uses the view's QStyle to determine the size of the + item. + + \sa QStyle::sizeFromContents(), QStyle::CT_ItemViewItem +*/ +QSize QStyledItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QVariant value = index.data(Qt::SizeHintRole); + if (value.isValid()) + return qvariant_cast(value); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + return style->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), widget); +} + +/*! + Returns the widget used to edit the item specified by \a index + for editing. The \a parent widget and style \a option are used to + control how the editor widget appears. + + \sa QAbstractItemDelegate::createEditor() +*/ +QWidget *QStyledItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + Q_D(const QStyledItemDelegate); + if (!index.isValid()) + return 0; + QVariant::Type t = static_cast(index.data(Qt::EditRole).userType()); + return d->editorFactory()->createEditor(t, parent); +} + +/*! + Sets the data to be displayed and edited by the \a editor from the + data model item specified by the model \a index. + + The default implementation stores the data in the \a editor + widget's \l {Qt's Property System} {user property}. + + \sa QMetaProperty::isUser() +*/ +void QStyledItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QStyledItemDelegate); + QVariant v = index.data(Qt::EditRole); + QByteArray n = editor->metaObject()->userProperty().name(); + + // ### Qt 5: remove + // A work-around for missing "USER true" in qdatetimeedit.h for + // QTimeEdit's time property and QDateEdit's date property. + // It only triggers if the default user property "dateTime" is + // reported for QTimeEdit and QDateEdit. + if (n == "dateTime") { + if (editor->inherits("QTimeEdit")) + n = "time"; + else if (editor->inherits("QDateEdit")) + n = "date"; + } + + // ### Qt 5: give QComboBox a USER property + if (n.isEmpty() && editor->inherits("QComboBox")) + n = d->editorFactory()->valuePropertyName(static_cast(v.userType())); + if (!n.isEmpty()) { + if (!v.isValid()) + v = QVariant(editor->property(n).userType(), (const void *)0); + editor->setProperty(n, v); + } +#endif +} + +/*! + Gets data from the \a editor widget and stores it in the specified + \a model at the item \a index. + + The default implementation gets the value to be stored in the data + model from the \a editor widget's \l {Qt's Property System} {user + property}. + + \sa QMetaProperty::isUser() +*/ +void QStyledItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ +#ifdef QT_NO_PROPERTIES + Q_UNUSED(model); + Q_UNUSED(editor); + Q_UNUSED(index); +#else + Q_D(const QStyledItemDelegate); + Q_ASSERT(model); + Q_ASSERT(editor); + QByteArray n = editor->metaObject()->userProperty().name(); + if (n.isEmpty()) + n = d->editorFactory()->valuePropertyName( + static_cast(model->data(index, Qt::EditRole).userType())); + if (!n.isEmpty()) + model->setData(index, editor->property(n), Qt::EditRole); +#endif +} + +/*! + Updates the \a editor for the item specified by \a index + according to the style \a option given. +*/ +void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (!editor) + return; + Q_ASSERT(index.isValid()); + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // let the editor take up all available space + //if the editor is not a QLineEdit + //or it is in a QTableView +#if !defined(QT_NO_TABLEVIEW) && !defined(QT_NO_LINEEDIT) + if (qobject_cast(editor) && !qobject_cast(widget)) + opt.showDecorationSelected = editor->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, 0, editor); + else +#endif + opt.showDecorationSelected = true; + + QStyle *style = widget ? widget->style() : QApplication::style(); + QRect geom = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); + if ( editor->layoutDirection() == Qt::RightToLeft) { + const int delta = qSmartMinSize(editor).width() - geom.width(); + if (delta > 0) { + //we need to widen the geometry + geom.adjust(-delta, 0, 0, 0); + } + } + + editor->setGeometry(geom); +} + +/*! + Returns the editor factory used by the item delegate. + If no editor factory is set, the function will return null. + + \sa setItemEditorFactory() +*/ +QItemEditorFactory *QStyledItemDelegate::itemEditorFactory() const +{ + Q_D(const QStyledItemDelegate); + return d->factory; +} + +/*! + Sets the editor factory to be used by the item delegate to be the \a factory + specified. If no editor factory is set, the item delegate will use the + default editor factory. + + \sa itemEditorFactory() +*/ +void QStyledItemDelegate::setItemEditorFactory(QItemEditorFactory *factory) +{ + Q_D(QStyledItemDelegate); + d->factory = factory; +} + + +/*! + \fn bool QStyledItemDelegate::eventFilter(QObject *editor, QEvent *event) + + Returns true if the given \a editor is a valid QWidget and the + given \a event is handled; otherwise returns false. The following + key press events are handled by default: + + \list + \o \gui Tab + \o \gui Backtab + \o \gui Enter + \o \gui Return + \o \gui Esc + \endlist + + In the case of \gui Tab, \gui Backtab, \gui Enter and \gui Return + key press events, the \a editor's data is comitted to the model + and the editor is closed. If the \a event is a \gui Tab key press + the view will open an editor on the next item in the + view. Likewise, if the \a event is a \gui Backtab key press the + view will open an editor on the \e previous item in the view. + + If the event is a \gui Esc key press event, the \a editor is + closed \e without committing its data. + + \sa commitData(), closeEditor() +*/ +bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event) +{ + QWidget *editor = qobject_cast(object); + if (!editor) + return false; + if (event->type() == QEvent::KeyPress) { + switch (static_cast(event)->key()) { + case Qt::Key_Tab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditNextItem); + return true; + case Qt::Key_Backtab: + emit commitData(editor); + emit closeEditor(editor, QAbstractItemDelegate::EditPreviousItem); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: +#ifndef QT_NO_TEXTEDIT + if (qobject_cast(editor) || qobject_cast(editor)) + return false; // don't filter enter key events for QTextEdit + // We want the editor to be able to process the key press + // before committing the data (e.g. so it can do + // validation/fixup of the input). +#endif // QT_NO_TEXTEDIT +#ifndef QT_NO_LINEEDIT + if (QLineEdit *e = qobject_cast(editor)) + if (!e->hasAcceptableInput()) + return false; +#endif // QT_NO_LINEEDIT + QMetaObject::invokeMethod(this, "_q_commitDataAndCloseEditor", + Qt::QueuedConnection, Q_ARG(QWidget*, editor)); + return false; + case Qt::Key_Escape: + // don't commit data + emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache); + break; + default: + return false; + } + if (editor->parentWidget()) + editor->parentWidget()->setFocus(); + return true; + } else if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) { + //the Hide event will take care of he editors that are in fact complete dialogs + if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) { + QWidget *w = QApplication::focusWidget(); + while (w) { // don't worry about focus changes internally in the editor + if (w == editor) + return false; + w = w->parentWidget(); + } +#ifndef QT_NO_DRAGANDDROP + // The window may lose focus during an drag operation. + // i.e when dragging involves the taskbar on Windows. + if (QDragManager::self() && QDragManager::self()->object != 0) + return false; +#endif + + emit commitData(editor); + emit closeEditor(editor, NoHint); + } + } else if (event->type() == QEvent::ShortcutOverride) { + if (static_cast(event)->key() == Qt::Key_Escape) { + event->accept(); + return true; + } + } + return false; +} + +/*! + \reimp +*/ +bool QStyledItemDelegate::editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + Q_ASSERT(event); + Q_ASSERT(model); + + // make sure that the item is checkable + Qt::ItemFlags flags = model->flags(index); + if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled) + || !(flags & Qt::ItemIsEnabled)) + return false; + + // make sure that we have a check state + QVariant value = index.data(Qt::CheckStateRole); + if (!value.isValid()) + return false; + + const QWidget *widget = QStyledItemDelegatePrivate::widget(option); + QStyle *style = widget ? widget->style() : QApplication::style(); + + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick) + || (event->type() == QEvent::MouseButtonPress)) { + QStyleOptionViewItemV4 viewOpt(option); + initStyleOption(&viewOpt, index); + QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &viewOpt, widget); + QMouseEvent *me = static_cast(event); + if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos())) + return false; + + if ((event->type() == QEvent::MouseButtonPress) + || (event->type() == QEvent::MouseButtonDblClick)) + return true; + + } else if (event->type() == QEvent::KeyPress) { + if (static_cast(event)->key() != Qt::Key_Space + && static_cast(event)->key() != Qt::Key_Select) + return false; + } else { + return false; + } + + Qt::CheckState state = (static_cast(value.toInt()) == Qt::Checked + ? Qt::Unchecked : Qt::Checked); + return model->setData(index, state, Qt::CheckStateRole); +} + +QT_END_NAMESPACE + +#include "moc_qstyleditemdelegate.cpp" + +#endif // QT_NO_ITEMVIEWS diff --git a/src/gui/itemviews/qstyleditemdelegate.h b/src/gui/itemviews/qstyleditemdelegate.h new file mode 100644 index 0000000000..1c8299b07c --- /dev/null +++ b/src/gui/itemviews/qstyleditemdelegate.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEDITEMDELEGATE_H +#define QSTYLEDITEMDELEGATE_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ITEMVIEWS + +class QStyledItemDelegatePrivate; +class QItemEditorFactory; + +class Q_GUI_EXPORT QStyledItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT + +public: + explicit QStyledItemDelegate(QObject *parent = 0); + ~QStyledItemDelegate(); + + // painting + void paint(QPainter *painter, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editing + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + // editor factory + QItemEditorFactory *itemEditorFactory() const; + void setItemEditorFactory(QItemEditorFactory *factory); + + virtual QString displayText(const QVariant &value, const QLocale &locale) const; + +protected: + virtual void initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const; + + bool eventFilter(QObject *object, QEvent *event); + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + +private: + Q_DECLARE_PRIVATE(QStyledItemDelegate) + Q_DISABLE_COPY(QStyledItemDelegate) + + Q_PRIVATE_SLOT(d_func(), void _q_commitDataAndCloseEditor(QWidget*)) +}; + +#endif // QT_NO_ITEMVIEWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEDITEMDELEGATE_H diff --git a/src/gui/itemviews/qtableview.cpp b/src/gui/itemviews/qtableview.cpp new file mode 100644 index 0000000000..e494ee5564 --- /dev/null +++ b/src/gui/itemviews/qtableview.cpp @@ -0,0 +1,3198 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtableview.h" + +#ifndef QT_NO_TABLEVIEW +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif + +QT_BEGIN_NAMESPACE + +/** \internal + Add a span to the collection. the collection takes the ownership. + */ +void QSpanCollection::addSpan(QSpanCollection::Span *span) +{ + spans.append(span); + Index::iterator it_y = index.lowerBound(-span->top()); + if (it_y == index.end() || it_y.key() != -span->top()) { + //there is no spans that starts with the row in the index, so create a sublist for it. + SubIndex sub_index; + if (it_y != index.end()) { + //the previouslist is the list of spans that sarts _before_ the row of the span. + // and which may intersect this row. + const SubIndex previousList = it_y.value(); + foreach(Span *s, previousList) { + //If a subspans intersect the row, we need to split it into subspans + if(s->bottom() >= span->top()) + sub_index.insert(-s->left(), s); + } + } + it_y = index.insert(-span->top(), sub_index); + //we will insert span to *it_y in the later loop + } + + //insert the span as supspan in all the lists that intesects the span + while(-it_y.key() <= span->bottom()) { + (*it_y).insert(-span->left(), span); + if(it_y == index.begin()) + break; + --it_y; + } +} + + +/** \internal +* Has to be called after the height and width of a span is changed. +* +* old_height is the height before the change +* +* if the size of the span is now 0x0 the span will be deleted. +*/ +void QSpanCollection::updateSpan(QSpanCollection::Span *span, int old_height) +{ + if (old_height < span->height()) { + //add the span as subspan in all the lists that intersect the new covered columns + Index::iterator it_y = index.lowerBound(-(span->top() + old_height - 1)); + Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list + while (-it_y.key() <= span->bottom()) { + (*it_y).insert(-span->left(), span); + if(it_y == index.begin()) + break; + --it_y; + } + } else if (old_height > span->height()) { + //remove the span from all the subspans lists that intersect the columns not covered anymore + Index::iterator it_y = index.lowerBound(-qMax(span->bottom(), span->top())); //qMax useful if height is 0 + Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list + while (-it_y.key() <= span->top() + old_height -1) { + if (-it_y.key() > span->bottom()) { + int removed = (*it_y).remove(-span->left()); + Q_ASSERT(removed == 1); Q_UNUSED(removed); + if (it_y->isEmpty()) { + it_y = index.erase(it_y); + } + } + if(it_y == index.begin()) + break; + --it_y; + } + } + + if (span->width() == 0 && span->height() == 0) { + spans.removeOne(span); + delete span; + } +} + +/** \internal + * \return a spans that spans over cell x,y (column,row) or 0 if there is none. + */ +QSpanCollection::Span *QSpanCollection::spanAt(int x, int y) const +{ + Index::const_iterator it_y = index.lowerBound(-y); + if (it_y == index.end()) + return 0; + SubIndex::const_iterator it_x = (*it_y).lowerBound(-x); + if (it_x == (*it_y).end()) + return 0; + Span *span = *it_x; + if (span->right() >= x && span->bottom() >= y) + return span; + return 0; +} + + +/** \internal +* remove and deletes all spans inside the collection +*/ +void QSpanCollection::clear() +{ + qDeleteAll(spans); + index.clear(); + spans.clear(); +} + +/** \internal + * return a list to all the spans that spans over cells in the given rectangle + */ +QList QSpanCollection::spansInRect(int x, int y, int w, int h) const +{ + QSet list; + Index::const_iterator it_y = index.lowerBound(-y); + if(it_y == index.end()) + --it_y; + while(-it_y.key() <= y + h) { + SubIndex::const_iterator it_x = (*it_y).lowerBound(-x); + if (it_x == (*it_y).end()) + --it_x; + while(-it_x.key() <= x + w) { + Span *s = *it_x; + if (s->bottom() >= y && s->right() >= x) + list << s; + if (it_x == (*it_y).begin()) + break; + --it_x; + } + if(it_y == index.begin()) + break; + --it_y; + } + return list.toList(); +} + +#undef DEBUG_SPAN_UPDATE + +#ifdef DEBUG_SPAN_UPDATE +QDebug operator<<(QDebug str, const QSpanCollection::Span &span) +{ + str << "(" << span.top() << "," << span.left() << "," << span.bottom() << "," << span.right() << ")"; + return str; +} +#endif + +/** \internal +* Updates the span collection after row insertion. +*/ +void QSpanCollection::updateInsertedRows(int start, int end) +{ +#ifdef DEBUG_SPAN_UPDATE + qDebug() << Q_FUNC_INFO; + qDebug() << start << end; + qDebug() << index; +#endif + if (spans.isEmpty()) + return; + + int delta = end - start + 1; +#ifdef DEBUG_SPAN_UPDATE + qDebug("Before"); +#endif + for (SpanList::iterator it = spans.begin(); it != spans.end(); ++it) { + Span *span = *it; +#ifdef DEBUG_SPAN_UPDATE + qDebug() << span << *span; +#endif + if (span->m_bottom < start) + continue; + if (span->m_top >= start) + span->m_top += delta; + span->m_bottom += delta; + } + +#ifdef DEBUG_SPAN_UPDATE + qDebug("After"); + foreach (QSpanCollection::Span *span, spans) + qDebug() << span << *span; +#endif + + for (Index::iterator it_y = index.begin(); it_y != index.end(); ) { + int y = -it_y.key(); + if (y < start) { + ++it_y; + continue; + } + + index.insert(-y - delta, it_y.value()); + it_y = index.erase(it_y); + } +#ifdef DEBUG_SPAN_UPDATE + qDebug() << index; +#endif +} + +/** \internal +* Updates the span collection after column insertion. +*/ +void QSpanCollection::updateInsertedColumns(int start, int end) +{ +#ifdef DEBUG_SPAN_UPDATE + qDebug() << Q_FUNC_INFO; + qDebug() << start << end; + qDebug() << index; +#endif + if (spans.isEmpty()) + return; + + int delta = end - start + 1; +#ifdef DEBUG_SPAN_UPDATE + qDebug("Before"); +#endif + for (SpanList::iterator it = spans.begin(); it != spans.end(); ++it) { + Span *span = *it; +#ifdef DEBUG_SPAN_UPDATE + qDebug() << span << *span; +#endif + if (span->m_right < start) + continue; + if (span->m_left >= start) + span->m_left += delta; + span->m_right += delta; + } + +#ifdef DEBUG_SPAN_UPDATE + qDebug("After"); + foreach (QSpanCollection::Span *span, spans) + qDebug() << span << *span; +#endif + + for (Index::iterator it_y = index.begin(); it_y != index.end(); ++it_y) { + SubIndex &subindex = it_y.value(); + for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) { + int x = -it.key(); + if (x < start) { + ++it; + continue; + } + subindex.insert(-x - delta, it.value()); + it = subindex.erase(it); + } + } +#ifdef DEBUG_SPAN_UPDATE + qDebug() << index; +#endif +} + +/** \internal +* Cleans a subindex from to be deleted spans. The update argument is used +* to move the spans inside the subindex, in case their anchor changed. +* \return true if no span in this subindex starts at y, and should thus be deleted. +*/ +bool QSpanCollection::cleanSpanSubIndex(QSpanCollection::SubIndex &subindex, int y, bool update) +{ + if (subindex.isEmpty()) + return true; + + bool should_be_deleted = true; + SubIndex::iterator it = subindex.end(); + do { + --it; + int x = -it.key(); + Span *span = it.value(); + if (span->will_be_deleted) { + it = subindex.erase(it); + continue; + } + if (update && span->m_left != x) { + subindex.insert(-span->m_left, span); + it = subindex.erase(it); + } + if (should_be_deleted && span->m_top == y) + should_be_deleted = false; + } while (it != subindex.begin()); + + return should_be_deleted; +} + +/** \internal +* Updates the span collection after row removal. +*/ +void QSpanCollection::updateRemovedRows(int start, int end) +{ +#ifdef DEBUG_SPAN_UPDATE + qDebug() << Q_FUNC_INFO; + qDebug() << start << end; + qDebug() << index; +#endif + if (spans.isEmpty()) + return; + + SpanList spansToBeDeleted; + int delta = end - start + 1; +#ifdef DEBUG_SPAN_UPDATE + qDebug("Before"); +#endif + for (SpanList::iterator it = spans.begin(); it != spans.end(); ) { + Span *span = *it; +#ifdef DEBUG_SPAN_UPDATE + qDebug() << span << *span; +#endif + if (span->m_bottom < start) { + ++it; + continue; + } + if (span->m_top < start) { + if (span->m_bottom <= end) + span->m_bottom = start - 1; + else + span->m_bottom -= delta; + } else { + if (span->m_bottom > end) { + if (span->m_top <= end) + span->m_top = start; + else + span->m_top -= delta; + span->m_bottom -= delta; + } else { + span->will_be_deleted = true; + } + } + if (span->m_top == span->m_bottom && span->m_left == span->m_right) + span->will_be_deleted = true; + if (span->will_be_deleted) { + spansToBeDeleted.append(span); + it = spans.erase(it); + } else { + ++it; + } + } + +#ifdef DEBUG_SPAN_UPDATE + qDebug("After"); + foreach (QSpanCollection::Span *span, spans) + qDebug() << span << *span; +#endif + if (spans.isEmpty()) { + qDeleteAll(spansToBeDeleted); + index.clear(); + return; + } + + Index::iterator it_y = index.end(); + do { + --it_y; + int y = -it_y.key(); + SubIndex &subindex = it_y.value(); + if (y < start) { + if (cleanSpanSubIndex(subindex, y)) + it_y = index.erase(it_y); + } else if (y >= start && y <= end) { + bool span_at_start = false; + SubIndex spansToBeMoved; + for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ++it) { + Span *span = it.value(); + if (span->will_be_deleted) + continue; + if (!span_at_start && span->m_top == start) + span_at_start = true; + spansToBeMoved.insert(it.key(), span); + } + + if (y == start && span_at_start) + subindex.clear(); + else + it_y = index.erase(it_y); + + if (span_at_start) { + Index::iterator it_start; + if (y == start) + it_start = it_y; + else { + it_start = index.find(-start); + if (it_start == index.end()) + it_start = index.insert(-start, SubIndex()); + } + SubIndex &start_subindex = it_start.value(); + for (SubIndex::iterator it = spansToBeMoved.begin(); it != spansToBeMoved.end(); ++it) + start_subindex.insert(it.key(), it.value()); + } + } else { + if (y == end + 1) { + Index::iterator it_top = index.find(-y + delta); + if (it_top == index.end()) + it_top = index.insert(-y + delta, SubIndex()); + for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) { + Span *span = it.value(); + if (!span->will_be_deleted) + it_top.value().insert(it.key(), span); + ++it; + } + } else { + index.insert(-y + delta, subindex); + } + it_y = index.erase(it_y); + } + } while (it_y != index.begin()); + +#ifdef DEBUG_SPAN_UPDATE + qDebug() << index; + qDebug("Deleted"); + foreach (QSpanCollection::Span *span, spansToBeDeleted) + qDebug() << span << *span; +#endif + qDeleteAll(spansToBeDeleted); +} + +/** \internal +* Updates the span collection after column removal. +*/ +void QSpanCollection::updateRemovedColumns(int start, int end) +{ +#ifdef DEBUG_SPAN_UPDATE + qDebug() << Q_FUNC_INFO; + qDebug() << start << end; + qDebug() << index; +#endif + if (spans.isEmpty()) + return; + + SpanList toBeDeleted; + int delta = end - start + 1; +#ifdef DEBUG_SPAN_UPDATE + qDebug("Before"); +#endif + for (SpanList::iterator it = spans.begin(); it != spans.end(); ) { + Span *span = *it; +#ifdef DEBUG_SPAN_UPDATE + qDebug() << span << *span; +#endif + if (span->m_right < start) { + ++it; + continue; + } + if (span->m_left < start) { + if (span->m_right <= end) + span->m_right = start - 1; + else + span->m_right -= delta; + } else { + if (span->m_right > end) { + if (span->m_left <= end) + span->m_left = start; + else + span->m_left -= delta; + span->m_right -= delta; + } else { + span->will_be_deleted = true; + } + } + if (span->m_top == span->m_bottom && span->m_left == span->m_right) + span->will_be_deleted = true; + if (span->will_be_deleted) { + toBeDeleted.append(span); + it = spans.erase(it); + } else { + ++it; + } + } + +#ifdef DEBUG_SPAN_UPDATE + qDebug("After"); + foreach (QSpanCollection::Span *span, spans) + qDebug() << span << *span; +#endif + if (spans.isEmpty()) { + qDeleteAll(toBeDeleted); + index.clear(); + return; + } + + for (Index::iterator it_y = index.begin(); it_y != index.end(); ) { + int y = -it_y.key(); + if (cleanSpanSubIndex(it_y.value(), y, true)) + it_y = index.erase(it_y); + else + ++it_y; + } + +#ifdef DEBUG_SPAN_UPDATE + qDebug() << index; + qDebug("Deleted"); + foreach (QSpanCollection::Span *span, toBeDeleted) + qDebug() << span << *span; +#endif + qDeleteAll(toBeDeleted); +} + +#ifdef QT_BUILD_INTERNAL +/*! + \internal + Checks whether the span index structure is self-consistent, and consistent with the spans list. +*/ +bool QSpanCollection::checkConsistency() const +{ + for (Index::const_iterator it_y = index.begin(); it_y != index.end(); ++it_y) { + int y = -it_y.key(); + const SubIndex &subIndex = it_y.value(); + for (SubIndex::const_iterator it = subIndex.begin(); it != subIndex.end(); ++it) { + int x = -it.key(); + Span *span = it.value(); + if (!spans.contains(span) || span->left() != x + || y < span->top() || y > span->bottom()) + return false; + } + } + + foreach (const Span *span, spans) { + if (span->width() < 1 || span->height() < 1 + || (span->width() == 1 && span->height() == 1)) + return false; + for (int y = span->top(); y <= span->bottom(); ++y) { + Index::const_iterator it_y = index.find(-y); + if (it_y == index.end()) { + if (y == span->top()) + return false; + else + continue; + } + const SubIndex &subIndex = it_y.value(); + SubIndex::const_iterator it = subIndex.find(-span->left()); + if (it == subIndex.end() || it.value() != span) + return false; + } + } + return true; +} +#endif + +class QTableCornerButton : public QAbstractButton +{ + Q_OBJECT +public: + QTableCornerButton(QWidget *parent) : QAbstractButton(parent) {} + void paintEvent(QPaintEvent*) { + QStyleOptionHeader opt; + opt.init(this); + QStyle::State state = QStyle::State_None; + if (isEnabled()) + state |= QStyle::State_Enabled; + if (isActiveWindow()) + state |= QStyle::State_Active; + if (isDown()) + state |= QStyle::State_Sunken; + opt.state = state; + opt.rect = rect(); + opt.position = QStyleOptionHeader::OnlyOneSection; + QPainter painter(this); + style()->drawControl(QStyle::CE_Header, &opt, &painter, this); + } +}; + +void QTableViewPrivate::init() +{ + Q_Q(QTableView); + + q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed); + + QHeaderView *vertical = new QHeaderView(Qt::Vertical, q); + vertical->setClickable(true); + vertical->setHighlightSections(true); + q->setVerticalHeader(vertical); + + QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q); + horizontal->setClickable(true); + horizontal->setHighlightSections(true); + q->setHorizontalHeader(horizontal); + + tabKeyNavigation = true; + + cornerWidget = new QTableCornerButton(q); + cornerWidget->setFocusPolicy(Qt::NoFocus); + QObject::connect(cornerWidget, SIGNAL(clicked()), q, SLOT(selectAll())); +} + +/*! + \internal + Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections. +*/ +void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const +{ + Q_ASSERT(range && range->isValid()); + + int top = range->top(); + int left = range->left(); + int bottom = range->bottom(); + int right = range->right(); + + while (bottom >= top && verticalHeader->isSectionHidden(bottom)) + --bottom; + while (right >= left && horizontalHeader->isSectionHidden(right)) + --right; + + if (top > bottom || left > right) { // everything is hidden + *range = QItemSelectionRange(); + return; + } + + while (verticalHeader->isSectionHidden(top) && top <= bottom) + ++top; + while (horizontalHeader->isSectionHidden(left) && left <= right) + ++left; + + if (top > bottom || left > right) { // everything is hidden + *range = QItemSelectionRange(); + return; + } + + QModelIndex bottomRight = model->index(bottom, right, range->parent()); + QModelIndex topLeft = model->index(top, left, range->parent()); + *range = QItemSelectionRange(topLeft, bottomRight); +} + +/*! + \internal + Sets the span for the cell at (\a row, \a column). +*/ +void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan) +{ + if (row < 0 || column < 0 || rowSpan <= 0 || columnSpan <= 0) { + qWarning() << "QTableView::setSpan: invalid span given: (" << row << ',' << column << ',' << rowSpan << ',' << columnSpan << ')'; + return; + } + QSpanCollection::Span *sp = spans.spanAt(column, row); + if (sp) { + if (sp->top() != row || sp->left() != column) { + qWarning() << "QTableView::setSpan: span cannot overlap"; + return; + } + if (rowSpan == 1 && columnSpan == 1) { + rowSpan = columnSpan = 0; + } + const int old_height = sp->height(); + sp->m_bottom = row + rowSpan - 1; + sp->m_right = column + columnSpan - 1; + spans.updateSpan(sp, old_height); + return; + } else if (rowSpan == 1 && columnSpan == 1) { + qWarning() << "QTableView::setSpan: single cell span won't be added"; + return; + } + sp = new QSpanCollection::Span(row, column, rowSpan, columnSpan); + spans.addSpan(sp); +} + +/*! + \internal + Gets the span information for the cell at (\a row, \a column). +*/ +QSpanCollection::Span QTableViewPrivate::span(int row, int column) const +{ + QSpanCollection::Span *sp = spans.spanAt(column, row); + if (sp) + return *sp; + + return QSpanCollection::Span(row, column, 1, 1); +} + +/*! + \internal + Returns the logical index of the last section that's part of the span. +*/ +int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const +{ + int visual = header->visualIndex(logical); + for (int i = 1; i < span; ) { + if (++visual >= header->count()) + break; + logical = header->logicalIndex(visual); + ++i; + } + return logical; +} + +/*! + \internal + Returns the size of the span starting at logical index \a logical + and spanning \a span sections. +*/ +int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const +{ + int endLogical = sectionSpanEndLogical(header, logical, span); + return header->sectionPosition(endLogical) + - header->sectionPosition(logical) + + header->sectionSize(endLogical); +} + +/*! + \internal + Returns true if the section at logical index \a logical is part of the span + starting at logical index \a spanLogical and spanning \a span sections; + otherwise, returns false. +*/ +bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const +{ + if (logical == spanLogical) + return true; // it's the start of the span + int visual = header->visualIndex(spanLogical); + for (int i = 1; i < span; ) { + if (++visual >= header->count()) + break; + spanLogical = header->logicalIndex(visual); + if (logical == spanLogical) + return true; + ++i; + } + return false; +} + +/*! + \internal + Returns the visual rect for the given \a span. +*/ +QRect QTableViewPrivate::visualSpanRect(const QSpanCollection::Span &span) const +{ + Q_Q(const QTableView); + // vertical + int row = span.top(); + int rowp = verticalHeader->sectionViewportPosition(row); + int rowh = rowSpanHeight(row, span.height()); + // horizontal + int column = span.left(); + int colw = columnSpanWidth(column, span.width()); + if (q->isRightToLeft()) + column = span.right(); + int colp = horizontalHeader->sectionViewportPosition(column); + + const int i = showGrid ? 1 : 0; + if (q->isRightToLeft()) + return QRect(colp + i, rowp, colw - i, rowh - i); + return QRect(colp, rowp, colw - i, rowh - i); +} + +/*! + \internal + Draws the spanning cells within rect \a area, and clips them off as + preparation for the main drawing loop. + \a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn +*/ +void QTableViewPrivate::drawAndClipSpans(const QRegion &area, QPainter *painter, + const QStyleOptionViewItemV4 &option, QBitArray *drawn, + int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn) +{ + bool alternateBase = false; + QRegion region = viewport->rect(); + + QList visibleSpans; + bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved(); + + if (!sectionMoved) { + visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow), + lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1); + } else { + QSet set; + for(int x = firstVisualColumn; x <= lastVisualColumn; x++) + for(int y = firstVisualRow; y <= lastVisualRow; y++) + set.insert(spans.spanAt(x,y)); + set.remove(0); + visibleSpans = set.toList(); + } + + foreach (QSpanCollection::Span *span, visibleSpans) { + int row = span->top(); + int col = span->left(); + QModelIndex index = model->index(row, col, root); + if (!index.isValid()) + continue; + QRect rect = visualSpanRect(*span); + rect.translate(scrollDelayOffset); + if (!area.intersects(rect)) + continue; + QStyleOptionViewItemV4 opt = option; + opt.rect = rect; + alternateBase = alternatingColors && (span->top() & 1); + if (alternateBase) + opt.features |= QStyleOptionViewItemV2::Alternate; + else + opt.features &= ~QStyleOptionViewItemV2::Alternate; + drawCell(painter, opt, index); + region -= rect; + for (int r = span->top(); r <= span->bottom(); ++r) { + const int vr = visualRow(r); + if (vr < firstVisualRow || vr > lastVisualRow) + continue; + for (int c = span->left(); c <= span->right(); ++c) { + const int vc = visualColumn(c); + if (vc < firstVisualColumn || vc > lastVisualColumn) + continue; + drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1) + + vc - firstVisualColumn); + } + } + + } + painter->setClipRegion(region); +} + +/*! + \internal + Updates spans after row insertion. +*/ +void QTableViewPrivate::_q_updateSpanInsertedRows(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + spans.updateInsertedRows(start, end); +} + +/*! + \internal + Updates spans after column insertion. +*/ +void QTableViewPrivate::_q_updateSpanInsertedColumns(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + spans.updateInsertedColumns(start, end); +} + +/*! + \internal + Updates spans after row removal. +*/ +void QTableViewPrivate::_q_updateSpanRemovedRows(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + spans.updateRemovedRows(start, end); +} + +/*! + \internal + Updates spans after column removal. +*/ +void QTableViewPrivate::_q_updateSpanRemovedColumns(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + spans.updateRemovedColumns(start, end); +} + +/*! + \internal + Draws a table cell. +*/ +void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index) +{ + Q_Q(QTableView); + QStyleOptionViewItemV4 opt = option; + + if (selectionModel && selectionModel->isSelected(index)) + opt.state |= QStyle::State_Selected; + if (index == hover) + opt.state |= QStyle::State_MouseOver; + if (option.state & QStyle::State_Enabled) { + QPalette::ColorGroup cg; + if ((model->flags(index) & Qt::ItemIsEnabled) == 0) { + opt.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else { + cg = QPalette::Normal; + } + opt.palette.setCurrentColorGroup(cg); + } + + if (index == q->currentIndex()) { + const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid(); + if (focus) + opt.state |= QStyle::State_HasFocus; + } + + q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q); + + q->itemDelegate(index)->paint(painter, opt, index); +} + +/*! + \class QTableView + + \brief The QTableView class provides a default model/view + implementation of a table view. + + \ingroup model-view + \ingroup advanced + + + A QTableView implements a table view that displays items from a + model. This class is used to provide standard tables that were + previously provided by the QTable class, but using the more + flexible approach provided by Qt's model/view architecture. + + The QTableView class is one of the \l{Model/View Classes} + and is part of Qt's \l{Model/View Programming}{model/view framework}. + + QTableView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + \section1 Navigation + + You can navigate the cells in the table by clicking on a cell with the + mouse, or by using the arrow keys. Because QTableView enables + \l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you + can also hit Tab and Backtab to move from cell to cell. + + \section1 Visual Appearance + + The table has a vertical header that can be obtained using the + verticalHeader() function, and a horizontal header that is available + through the horizontalHeader() function. The height of each row in the + table can be found by using rowHeight(); similarly, the width of + columns can be found using columnWidth(). Since both of these are plain + widgets, you can hide either of them using their hide() functions. + + Rows and columns can be hidden and shown with hideRow(), hideColumn(), + showRow(), and showColumn(). They can be selected with selectRow() + and selectColumn(). The table will show a grid depending on the + \l showGrid property. + + The items shown in a table view, like those in the other item views, are + rendered and edited using standard \l{QItemDelegate}{delegates}. However, + for some tasks it is sometimes useful to be able to insert widgets in a + table instead. Widgets are set for particular indexes with the + \l{QAbstractItemView::}{setIndexWidget()} function, and + later retrieved with \l{QAbstractItemView::}{indexWidget()}. + + \table + \row \o \inlineimage qtableview-resized.png + \o By default, the cells in a table do not expand to fill the available space. + + You can make the cells fill the available space by stretching the last + header section. Access the relevant header using horizontalHeader() + or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection} + property. + + To distribute the available space according to the space requirement of + each column or row, call the view's resizeColumnsToContents() or + resizeRowsToContents() functions. + \endtable + + \section1 Coordinate Systems + + For some specialized forms of tables it is useful to be able to + convert between row and column indexes and widget coordinates. + The rowAt() function provides the y-coordinate within the view of the + specified row; the row index can be used to obtain a corresponding + y-coordinate with rowViewportPosition(). The columnAt() and + columnViewportPosition() functions provide the equivalent conversion + operations between x-coordinates and column indexes. + + \section1 Styles + + QTableView is styled appropriately for each platform. The following images show + how it looks on three different platforms. Go to the \l{Qt Widget Gallery} to see + its appearance in other styles. + + \table 100% + \row \o \inlineimage windowsxp-tableview.png Screenshot of a Windows XP style table view + \o \inlineimage macintosh-tableview.png Screenshot of a Macintosh style table view + \o \inlineimage plastique-tableview.png Screenshot of a Plastique style table view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} table view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} table view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} table view. + \endtable + + \sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, + {Chart Example}, {Pixelator Example}, {Table Model Example} +*/ + +/*! + Constructs a table view with a \a parent to represent the data. + + \sa QAbstractItemModel +*/ + +QTableView::QTableView(QWidget *parent) + : QAbstractItemView(*new QTableViewPrivate, parent) +{ + Q_D(QTableView); + d->init(); +} + +/*! + \internal +*/ +QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QTableView); + d->init(); +} + +/*! + Destroys the table view. +*/ +QTableView::~QTableView() +{ +} + +/*! + \reimp +*/ +void QTableView::setModel(QAbstractItemModel *model) +{ + Q_D(QTableView); + if (model == d->model) + return; + //let's disconnect from the old model + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int))); + } + if (model) { //and connect to the new one + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int))); + connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)), + this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int))); + connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int))); + connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int))); + } + d->verticalHeader->setModel(model); + d->horizontalHeader->setModel(model); + QAbstractItemView::setModel(model); +} + +/*! + \reimp +*/ +void QTableView::setRootIndex(const QModelIndex &index) +{ + Q_D(QTableView); + if (index == d->root) { + viewport()->update(); + return; + } + d->verticalHeader->setRootIndex(index); + d->horizontalHeader->setRootIndex(index); + QAbstractItemView::setRootIndex(index); +} + +/*! + \reimp +*/ +void QTableView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTableView); + Q_ASSERT(selectionModel); + d->verticalHeader->setSelectionModel(selectionModel); + d->horizontalHeader->setSelectionModel(selectionModel); + QAbstractItemView::setSelectionModel(selectionModel); +} + +/*! + Returns the table view's horizontal header. + + \sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData() +*/ +QHeaderView *QTableView::horizontalHeader() const +{ + Q_D(const QTableView); + return d->horizontalHeader; +} + +/*! + Returns the table view's vertical header. + + \sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData() +*/ +QHeaderView *QTableView::verticalHeader() const +{ + Q_D(const QTableView); + return d->verticalHeader; +} + +/*! + Sets the widget to use for the horizontal header to \a header. + + \sa horizontalHeader() setVerticalHeader() +*/ +void QTableView::setHorizontalHeader(QHeaderView *header) +{ + Q_D(QTableView); + + if (!header || header == d->horizontalHeader) + return; + if (d->horizontalHeader && d->horizontalHeader->parent() == this) + delete d->horizontalHeader; + d->horizontalHeader = header; + d->horizontalHeader->setParent(this); + if (!d->horizontalHeader->model()) { + d->horizontalHeader->setModel(d->model); + if (d->selectionModel) + d->horizontalHeader->setSelectionModel(d->selectionModel); + } + + connect(d->horizontalHeader,SIGNAL(sectionResized(int,int,int)), + this, SLOT(columnResized(int,int,int))); + connect(d->horizontalHeader, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(columnMoved(int,int,int))); + connect(d->horizontalHeader, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(columnCountChanged(int,int))); + connect(d->horizontalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectColumn(int))); + connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectColumn(int))); + connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeColumnToContents(int))); + connect(d->horizontalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries())); + + //update the sorting enabled states on the new header + setSortingEnabled(d->sortingEnabled); +} + +/*! + Sets the widget to use for the vertical header to \a header. + + \sa verticalHeader() setHorizontalHeader() +*/ +void QTableView::setVerticalHeader(QHeaderView *header) +{ + Q_D(QTableView); + + if (!header || header == d->verticalHeader) + return; + if (d->verticalHeader && d->verticalHeader->parent() == this) + delete d->verticalHeader; + d->verticalHeader = header; + d->verticalHeader->setParent(this); + if (!d->verticalHeader->model()) { + d->verticalHeader->setModel(d->model); + if (d->selectionModel) + d->verticalHeader->setSelectionModel(d->selectionModel); + } + + connect(d->verticalHeader, SIGNAL(sectionResized(int,int,int)), + this, SLOT(rowResized(int,int,int))); + connect(d->verticalHeader, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(rowMoved(int,int,int))); + connect(d->verticalHeader, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(rowCountChanged(int,int))); + connect(d->verticalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectRow(int))); + connect(d->verticalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectRow(int))); + connect(d->verticalHeader, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeRowToContents(int))); + connect(d->verticalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries())); +} + +/*! + \internal + + Scroll the contents of the table view by (\a dx, \a dy). +*/ +void QTableView::scrollContentsBy(int dx, int dy) +{ + Q_D(QTableView); + + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + + dx = isRightToLeft() ? -dx : dx; + if (dx) { + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->horizontalHeader->offset(); + if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) + d->horizontalHeader->setOffsetToLastSection(); + else + d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value()); + int newOffset = d->horizontalHeader->offset(); + dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; + } else { + d->horizontalHeader->setOffset(horizontalScrollBar()->value()); + } + } + if (dy) { + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->verticalHeader->offset(); + if (verticalScrollBar()->value() == verticalScrollBar()->maximum()) + d->verticalHeader->setOffsetToLastSection(); + else + d->verticalHeader->setOffsetToSectionPosition(verticalScrollBar()->value()); + int newOffset = d->verticalHeader->offset(); + dy = oldOffset - newOffset; + } else { + d->verticalHeader->setOffset(verticalScrollBar()->value()); + } + } + d->scrollContentsBy(dx, dy); + + if (d->showGrid) { + //we need to update the first line of the previous top item in the view + //because it has the grid drawn if the header is invisible. + //It is strictly related to what's done at then end of the paintEvent + if (dy > 0 && d->horizontalHeader->isHidden() && d->verticalScrollMode == ScrollPerItem) { + d->viewport->update(0, dy, d->viewport->width(), dy); + } + if (dx > 0 && d->verticalHeader->isHidden() && d->horizontalScrollMode == ScrollPerItem) { + d->viewport->update(dx, 0, dx, d->viewport->height()); + } + } +} + +/*! + \reimp +*/ +QStyleOptionViewItem QTableView::viewOptions() const +{ + QStyleOptionViewItem option = QAbstractItemView::viewOptions(); + option.showDecorationSelected = true; + return option; +} + +/*! + Paints the table on receipt of the given paint event \a event. +*/ +void QTableView::paintEvent(QPaintEvent *event) +{ + Q_D(QTableView); + // setup temp variables for the painting + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QPoint offset = d->scrollDelayOffset; + const bool showGrid = d->showGrid; + const int gridSize = showGrid ? 1 : 0; + const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this); + const QColor gridColor = static_cast(gridHint); + const QPen gridPen = QPen(gridColor, 0, d->gridStyle); + const QHeaderView *verticalHeader = d->verticalHeader; + const QHeaderView *horizontalHeader = d->horizontalHeader; + const QStyle::State state = option.state; + const bool alternate = d->alternatingColors; + const bool rightToLeft = isRightToLeft(); + + QPainter painter(d->viewport); + + // if there's nothing to do, clear the area and return + if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate) + return; + + uint x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1); + uint y = verticalHeader->length() - verticalHeader->offset() - 1; + + const QRegion region = event->region().translated(offset); + const QVector rects = region.rects(); + + //firstVisualRow is the visual index of the first visible row. lastVisualRow is the visual index of the last visible Row. + //same goes for ...VisualColumn + int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0); + int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->viewport()->height()); + if (lastVisualRow == -1) + lastVisualRow = d->model->rowCount(d->root) - 1; + + int firstVisualColumn = horizontalHeader->visualIndexAt(0); + int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->viewport()->width()); + if (rightToLeft) + qSwap(firstVisualColumn, lastVisualColumn); + if (firstVisualColumn == -1) + firstVisualColumn = 0; + if (lastVisualColumn == -1) + lastVisualColumn = horizontalHeader->count() - 1; + + QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1)); + + if (d->hasSpans()) { + d->drawAndClipSpans(region, &painter, option, &drawn, + firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn); + } + + for (int i = 0; i < rects.size(); ++i) { + QRect dirtyArea = rects.at(i); + dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y))); + if (rightToLeft) { + dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x))); + } else { + dirtyArea.setRight(qMin(dirtyArea.right(), int(x))); + } + + // get the horizontal start and end visual sections + int left = horizontalHeader->visualIndexAt(dirtyArea.left()); + int right = horizontalHeader->visualIndexAt(dirtyArea.right()); + if (rightToLeft) + qSwap(left, right); + if (left == -1) left = 0; + if (right == -1) right = horizontalHeader->count() - 1; + + // get the vertical start and end visual sections and if alternate color + int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom()); + if (bottom == -1) bottom = verticalHeader->count() - 1; + int top = 0; + bool alternateBase = false; + if (alternate && verticalHeader->sectionsHidden()) { + uint verticalOffset = verticalHeader->offset(); + int row = verticalHeader->logicalIndex(top); + for (int y = 0; + ((uint)(y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom); + ++top) { + row = verticalHeader->logicalIndex(top); + if (alternate && !verticalHeader->isSectionHidden(row)) + alternateBase = !alternateBase; + } + } else { + top = verticalHeader->visualIndexAt(dirtyArea.top()); + alternateBase = (top & 1) && alternate; + } + if (top == -1 || top > bottom) + continue; + + // Paint each row item + for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) { + int row = verticalHeader->logicalIndex(visualRowIndex); + if (verticalHeader->isSectionHidden(row)) + continue; + int rowY = rowViewportPosition(row); + rowY += offset.y(); + int rowh = rowHeight(row) - gridSize; + + // Paint each column item + for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) { + int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1) + + visualColumnIndex - firstVisualColumn; + + if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit)) + continue; + drawn.setBit(currentBit); + + int col = horizontalHeader->logicalIndex(visualColumnIndex); + if (horizontalHeader->isSectionHidden(col)) + continue; + int colp = columnViewportPosition(col); + colp += offset.x(); + int colw = columnWidth(col) - gridSize; + + const QModelIndex index = d->model->index(row, col, d->root); + if (index.isValid()) { + option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh); + if (alternate) { + if (alternateBase) + option.features |= QStyleOptionViewItemV2::Alternate; + else + option.features &= ~QStyleOptionViewItemV2::Alternate; + } + d->drawCell(&painter, option, index); + } + } + alternateBase = !alternateBase && alternate; + } + + if (showGrid) { + // Find the bottom right (the last rows/columns might be hidden) + while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom; + QPen old = painter.pen(); + painter.setPen(gridPen); + // Paint each row + for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) { + int row = verticalHeader->logicalIndex(visualIndex); + if (verticalHeader->isSectionHidden(row)) + continue; + int rowY = rowViewportPosition(row); + rowY += offset.y(); + int rowh = rowHeight(row) - gridSize; + painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh); + } + + // Paint each column + for (int h = left; h <= right; ++h) { + int col = horizontalHeader->logicalIndex(h); + if (horizontalHeader->isSectionHidden(col)) + continue; + int colp = columnViewportPosition(col); + colp += offset.x(); + if (!rightToLeft) + colp += columnWidth(col) - gridSize; + painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom()); + } + + //draw the top & left grid lines if the headers are not visible. + //We do update this line when subsequent scroll happen (see scrollContentsBy) + if (horizontalHeader->isHidden() && verticalScrollMode() == ScrollPerItem) + painter.drawLine(dirtyArea.left(), 0, dirtyArea.right(), 0); + if (verticalHeader->isHidden() && horizontalScrollMode() == ScrollPerItem) + painter.drawLine(0, dirtyArea.top(), 0, dirtyArea.bottom()); + painter.setPen(old); + } + } + +#ifndef QT_NO_DRAGANDDROP + // Paint the dropIndicator + d->paintDropIndicator(&painter); +#endif +} + +/*! + Returns the index position of the model item corresponding to the + table item at position \a pos in contents coordinates. +*/ +QModelIndex QTableView::indexAt(const QPoint &pos) const +{ + Q_D(const QTableView); + d->executePostedLayout(); + int r = rowAt(pos.y()); + int c = columnAt(pos.x()); + if (r >= 0 && c >= 0) { + if (d->hasSpans()) { + QSpanCollection::Span span = d->span(r, c); + r = span.top(); + c = span.left(); + } + return d->model->index(r, c, d->root); + } + return QModelIndex(); +} + +/*! + Returns the horizontal offset of the items in the table view. + + Note that the table view uses the horizontal header section + positions to determine the positions of columns in the view. + + \sa verticalOffset() +*/ +int QTableView::horizontalOffset() const +{ + Q_D(const QTableView); + return d->horizontalHeader->offset(); +} + +/*! + Returns the vertical offset of the items in the table view. + + Note that the table view uses the vertical header section + positions to determine the positions of rows in the view. + + \sa horizontalOffset() +*/ +int QTableView::verticalOffset() const +{ + Q_D(const QTableView); + return d->verticalHeader->offset(); +} + +/*! + \fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) + + Moves the cursor in accordance with the given \a cursorAction, using the + information provided by the \a modifiers. + + \sa QAbstractItemView::CursorAction +*/ +QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QTableView); + Q_UNUSED(modifiers); + + int bottom = d->model->rowCount(d->root) - 1; + // make sure that bottom is the bottommost *visible* row + while (bottom >= 0 && isRowHidden(d->logicalRow(bottom))) + --bottom; + + int right = d->model->columnCount(d->root) - 1; + + while (right >= 0 && isColumnHidden(d->logicalColumn(right))) + --right; + + if (bottom == -1 || right == -1) + return QModelIndex(); // model is empty + + QModelIndex current = currentIndex(); + + if (!current.isValid()) { + int row = 0; + int column = 0; + while (column < right && isColumnHidden(d->logicalColumn(column))) + ++column; + while (isRowHidden(d->logicalRow(row)) && row < bottom) + ++row; + d->visualCursor = QPoint(column, row); + return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root); + } + + // Update visual cursor if current index has changed. + QPoint visualCurrent(d->visualColumn(current.column()), d->visualRow(current.row())); + if (visualCurrent != d->visualCursor) { + if (d->hasSpans()) { + QSpanCollection::Span span = d->span(current.row(), current.column()); + if (span.top() > d->visualCursor.y() || d->visualCursor.y() > span.bottom() + || span.left() > d->visualCursor.x() || d->visualCursor.x() > span.right()) + d->visualCursor = visualCurrent; + } else { + d->visualCursor = visualCurrent; + } + } + + int visualRow = d->visualCursor.y(); + if (visualRow > bottom) + visualRow = bottom; + Q_ASSERT(visualRow != -1); + int visualColumn = d->visualCursor.x(); + if (visualColumn > right) + visualColumn = right; + Q_ASSERT(visualColumn != -1); + + if (isRightToLeft()) { + if (cursorAction == MoveLeft) + cursorAction = MoveRight; + else if (cursorAction == MoveRight) + cursorAction = MoveLeft; + } + + switch (cursorAction) { + case MoveUp: { + int originalRow = visualRow; +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && visualRow == 0) + visualRow = d->visualRow(model()->rowCount() - 1) + 1; + // FIXME? visualRow = bottom + 1; +#endif + int r = d->logicalRow(visualRow); + int c = d->logicalColumn(visualColumn); + if (r != -1 && d->hasSpans()) { + QSpanCollection::Span span = d->span(r, c); + if (span.width() > 1 || span.height() > 1) + visualRow = d->visualRow(span.top()); + } + while (visualRow >= 0) { + --visualRow; + r = d->logicalRow(visualRow); + c = d->logicalColumn(visualColumn); + if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c))) + break; + } + if (visualRow < 0) + visualRow = originalRow; + break; + } + case MoveDown: { + int originalRow = visualRow; + if (d->hasSpans()) { + QSpanCollection::Span span = d->span(current.row(), current.column()); + visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height())); + } +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled() && visualRow >= bottom) + visualRow = -1; +#endif + int r = d->logicalRow(visualRow); + int c = d->logicalColumn(visualColumn); + if (r != -1 && d->hasSpans()) { + QSpanCollection::Span span = d->span(r, c); + if (span.width() > 1 || span.height() > 1) + visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height())); + } + while (visualRow <= bottom) { + ++visualRow; + r = d->logicalRow(visualRow); + c = d->logicalColumn(visualColumn); + if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c))) + break; + } + if (visualRow > bottom) + visualRow = originalRow; + break; + } + case MovePrevious: + case MoveLeft: { + int originalRow = visualRow; + int originalColumn = visualColumn; + bool firstTime = true; + bool looped = false; + bool wrapped = false; + do { + int r = d->logicalRow(visualRow); + int c = d->logicalColumn(visualColumn); + if (firstTime && c != -1 && d->hasSpans()) { + firstTime = false; + QSpanCollection::Span span = d->span(r, c); + if (span.width() > 1 || span.height() > 1) + visualColumn = d->visualColumn(span.left()); + } + while (visualColumn >= 0) { + --visualColumn; + r = d->logicalRow(visualRow); + c = d->logicalColumn(visualColumn); + if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c))) + break; + if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) { + looped = true; + break; + } + } + if (cursorAction == MoveLeft || visualColumn >= 0) + break; + visualColumn = right + 1; + if (visualRow == 0) { + wrapped = true; + visualRow = bottom; + } else { + --visualRow; + } + } while (!looped); + if (visualColumn < 0) + visualColumn = originalColumn; + break; + } + case MoveNext: + case MoveRight: { + int originalRow = visualRow; + int originalColumn = visualColumn; + bool firstTime = true; + bool looped = false; + bool wrapped = false; + do { + int r = d->logicalRow(visualRow); + int c = d->logicalColumn(visualColumn); + if (firstTime && c != -1 && d->hasSpans()) { + firstTime = false; + QSpanCollection::Span span = d->span(r, c); + if (span.width() > 1 || span.height() > 1) + visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width())); + } + while (visualColumn <= right) { + ++visualColumn; + r = d->logicalRow(visualRow); + c = d->logicalColumn(visualColumn); + if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c))) + break; + if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) { + looped = true; + break; + } + } + if (cursorAction == MoveRight || visualColumn <= right) + break; + visualColumn = -1; + if (visualRow == bottom) { + wrapped = true; + visualRow = 0; + } else { + ++visualRow; + } + } while (!looped); + if (visualColumn > right) + visualColumn = originalColumn; + break; + } + case MoveHome: + visualColumn = 0; + while (visualColumn < right && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn)) + ++visualColumn; + if (modifiers & Qt::ControlModifier) { + visualRow = 0; + while (visualRow < bottom && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn)) + ++visualRow; + } + break; + case MoveEnd: + visualColumn = right; + if (modifiers & Qt::ControlModifier) + visualRow = bottom; + break; + case MovePageUp: { + int newRow = rowAt(visualRect(current).top() - d->viewport->height()); + if (newRow == -1) + newRow = d->logicalRow(0); + return d->model->index(newRow, current.column(), d->root); + } + case MovePageDown: { + int newRow = rowAt(visualRect(current).bottom() + d->viewport->height()); + if (newRow == -1) + newRow = d->logicalRow(bottom); + return d->model->index(newRow, current.column(), d->root); + }} + + d->visualCursor = QPoint(visualColumn, visualRow); + int logicalRow = d->logicalRow(visualRow); + int logicalColumn = d->logicalColumn(visualColumn); + if (!d->model->hasIndex(logicalRow, logicalColumn, d->root)) + return QModelIndex(); + + QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root); + if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result)) + return result; + + return QModelIndex(); +} + +/*! + \fn void QTableView::setSelection(const QRect &rect, + QItemSelectionModel::SelectionFlags flags) + + Selects the items within the given \a rect and in accordance with + the specified selection \a flags. +*/ +void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableView); + QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right()) + : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom()))); + QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) : + qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom()))); + if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br)) + return; + + bool verticalMoved = verticalHeader()->sectionsMoved(); + bool horizontalMoved = horizontalHeader()->sectionsMoved(); + + QItemSelection selection; + + if (d->hasSpans()) { + bool expanded; + int top = qMin(d->visualRow(tl.row()), d->visualRow(br.row())); + int left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column())); + int bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row())); + int right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column())); + do { + expanded = false; + foreach (QSpanCollection::Span *it, d->spans.spans) { + const QSpanCollection::Span &span = *it; + int t = d->visualRow(span.top()); + int l = d->visualColumn(span.left()); + int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height())); + int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width())); + if ((t > bottom) || (l > right) || (top > b) || (left > r)) + continue; // no intersect + if (t < top) { + top = t; + expanded = true; + } + if (l < left) { + left = l; + expanded = true; + } + if (b > bottom) { + bottom = b; + expanded = true; + } + if (r > right) { + right = r; + expanded = true; + } + if (expanded) + break; + } + } while (expanded); + for (int horizontal = left; horizontal <= right; ++horizontal) { + int column = d->logicalColumn(horizontal); + for (int vertical = top; vertical <= bottom; ++vertical) { + int row = d->logicalRow(vertical); + QModelIndex index = d->model->index(row, column, d->root); + selection.append(QItemSelectionRange(index)); + } + } + } else if (verticalMoved && horizontalMoved) { + int top = d->visualRow(tl.row()); + int left = d->visualColumn(tl.column()); + int bottom = d->visualRow(br.row()); + int right = d->visualColumn(br.column()); + for (int horizontal = left; horizontal <= right; ++horizontal) { + int column = d->logicalColumn(horizontal); + for (int vertical = top; vertical <= bottom; ++vertical) { + int row = d->logicalRow(vertical); + QModelIndex index = d->model->index(row, column, d->root); + selection.append(QItemSelectionRange(index)); + } + } + } else if (horizontalMoved) { + int left = d->visualColumn(tl.column()); + int right = d->visualColumn(br.column()); + for (int visual = left; visual <= right; ++visual) { + int column = d->logicalColumn(visual); + QModelIndex topLeft = d->model->index(tl.row(), column, d->root); + QModelIndex bottomRight = d->model->index(br.row(), column, d->root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + } else if (verticalMoved) { + int top = d->visualRow(tl.row()); + int bottom = d->visualRow(br.row()); + for (int visual = top; visual <= bottom; ++visual) { + int row = d->logicalRow(visual); + QModelIndex topLeft = d->model->index(row, tl.column(), d->root); + QModelIndex bottomRight = d->model->index(row, br.column(), d->root); + selection.append(QItemSelectionRange(topLeft, bottomRight)); + } + } else { // nothing moved + QItemSelectionRange range(tl, br); + if (!range.isEmpty()) + selection.append(range); + } + + d->selectionModel->select(selection, command); +} + +/*! + \internal + + Returns the rectangle from the viewport of the items in the given + \a selection. + + Since 4.7, the returned region only contains rectangles intersecting + (or included in) the viewport. +*/ +QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QTableView); + + if (selection.isEmpty()) + return QRegion(); + + QRegion selectionRegion; + const QRect &viewportRect = d->viewport->rect(); + bool verticalMoved = verticalHeader()->sectionsMoved(); + bool horizontalMoved = horizontalHeader()->sectionsMoved(); + + if ((verticalMoved && horizontalMoved) || (d->hasSpans() && (verticalMoved || horizontalMoved))) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + for (int r = range.top(); r <= range.bottom(); ++r) + for (int c = range.left(); c <= range.right(); ++c) { + const QRect &rangeRect = visualRect(d->model->index(r, c, d->root)); + if (viewportRect.intersects(rangeRect)) + selectionRegion += rangeRect; + } + } + } else if (horizontalMoved) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + int top = rowViewportPosition(range.top()); + int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom()); + if (top > bottom) + qSwap(top, bottom); + int height = bottom - top; + for (int c = range.left(); c <= range.right(); ++c) { + const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height); + if (viewportRect.intersects(rangeRect)) + selectionRegion += rangeRect; + } + } + } else if (verticalMoved) { + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + int left = columnViewportPosition(range.left()); + int right = columnViewportPosition(range.right()) + columnWidth(range.right()); + if (left > right) + qSwap(left, right); + int width = right - left; + for (int r = range.top(); r <= range.bottom(); ++r) { + const QRect rangeRect(left, rowViewportPosition(r), width, rowHeight(r)); + if (viewportRect.intersects(rangeRect)) + selectionRegion += rangeRect; + } + } + } else { // nothing moved + const int gridAdjust = showGrid() ? 1 : 0; + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (range.parent() != d->root || !range.isValid()) + continue; + d->trimHiddenSelections(&range); + + const int rtop = rowViewportPosition(range.top()); + const int rbottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom()); + int rleft; + int rright; + if (isLeftToRight()) { + rleft = columnViewportPosition(range.left()); + rright = columnViewportPosition(range.right()) + columnWidth(range.right()); + } else { + rleft = columnViewportPosition(range.right()); + rright = columnViewportPosition(range.left()) + columnWidth(range.left()); + } + const QRect rangeRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust)); + if (viewportRect.intersects(rangeRect)) + selectionRegion += rangeRect; + if (d->hasSpans()) { + foreach (QSpanCollection::Span *s, + d->spans.spansInRect(range.left(), range.top(), range.width(), range.height())) { + if (range.contains(s->top(), s->left(), range.parent())) { + const QRect &visualSpanRect = d->visualSpanRect(*s); + if (viewportRect.intersects(visualSpanRect)) + selectionRegion += visualSpanRect; + } + } + } + } + } + + return selectionRegion; +} + + +/*! + \reimp +*/ +QModelIndexList QTableView::selectedIndexes() const +{ + Q_D(const QTableView); + QModelIndexList viewSelected; + QModelIndexList modelSelected; + if (d->selectionModel) + modelSelected = d->selectionModel->selectedIndexes(); + for (int i = 0; i < modelSelected.count(); ++i) { + QModelIndex index = modelSelected.at(i); + if (!isIndexHidden(index) && index.parent() == d->root) + viewSelected.append(index); + } + return viewSelected; +} + + +/*! + This slot is called whenever rows are added or deleted. The + previous number of rows is specified by \a oldCount, and the new + number of rows is specified by \a newCount. +*/ +void QTableView::rowCountChanged(int /*oldCount*/, int /*newCount*/ ) +{ + Q_D(QTableView); + d->doDelayedItemsLayout(); +} + +/*! + This slot is called whenever columns are added or deleted. The + previous number of columns is specified by \a oldCount, and the new + number of columns is specified by \a newCount. +*/ +void QTableView::columnCountChanged(int, int) +{ + Q_D(QTableView); + updateGeometries(); + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) + d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value()); + else + d->horizontalHeader->setOffset(horizontalScrollBar()->value()); + d->viewport->update(); +} + +/*! + \reimp +*/ +void QTableView::updateGeometries() +{ + Q_D(QTableView); + if (d->geometryRecursionBlock) + return; + d->geometryRecursionBlock = true; + + int width = 0; + if (!d->verticalHeader->isHidden()) { + width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width()); + width = qMin(width, d->verticalHeader->maximumWidth()); + } + int height = 0; + if (!d->horizontalHeader->isHidden()) { + height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height()); + height = qMin(height, d->horizontalHeader->maximumHeight()); + } + bool reverse = isRightToLeft(); + if (reverse) + setViewportMargins(0, height, width, 0); + else + setViewportMargins(width, height, 0, 0); + + // update headers + + QRect vg = d->viewport->geometry(); + + int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width); + d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height()); + if (d->verticalHeader->isHidden()) + QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries"); + + int horizontalTop = vg.top() - height; + d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height); + if (d->horizontalHeader->isHidden()) + QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries"); + + // update cornerWidget + if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) { + d->cornerWidget->setHidden(true); + } else { + d->cornerWidget->setHidden(false); + d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height); + } + + // update scroll bars + + // ### move this block into the if + QSize vsize = d->viewport->size(); + QSize max = maximumViewportSize(); + uint horizontalLength = d->horizontalHeader->length(); + uint verticalLength = d->verticalHeader->length(); + if ((uint)max.width() >= horizontalLength && (uint)max.height() >= verticalLength) + vsize = max; + + // horizontal scroll bar + const int columnCount = d->horizontalHeader->count(); + const int viewportWidth = vsize.width(); + int columnsInViewport = 0; + for (int width = 0, column = columnCount - 1; column >= 0; --column) { + int logical = d->horizontalHeader->logicalIndex(column); + if (!d->horizontalHeader->isSectionHidden(logical)) { + width += d->horizontalHeader->sectionSize(logical); + if (width > viewportWidth) + break; + ++columnsInViewport; + } + } + columnsInViewport = qMax(columnsInViewport, 1); //there must be always at least 1 column + + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount(); + horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport); + horizontalScrollBar()->setPageStep(columnsInViewport); + if (columnsInViewport >= visibleColumns) + d->horizontalHeader->setOffset(0); + horizontalScrollBar()->setSingleStep(1); + } else { // ScrollPerPixel + horizontalScrollBar()->setPageStep(vsize.width()); + horizontalScrollBar()->setRange(0, horizontalLength - vsize.width()); + horizontalScrollBar()->setSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2)); + } + + // vertical scroll bar + const int rowCount = d->verticalHeader->count(); + const int viewportHeight = vsize.height(); + int rowsInViewport = 0; + for (int height = 0, row = rowCount - 1; row >= 0; --row) { + int logical = d->verticalHeader->logicalIndex(row); + if (!d->verticalHeader->isSectionHidden(logical)) { + height += d->verticalHeader->sectionSize(logical); + if (height > viewportHeight) + break; + ++rowsInViewport; + } + } + rowsInViewport = qMax(rowsInViewport, 1); //there must be always at least 1 row + + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount(); + verticalScrollBar()->setRange(0, visibleRows - rowsInViewport); + verticalScrollBar()->setPageStep(rowsInViewport); + if (rowsInViewport >= visibleRows) + d->verticalHeader->setOffset(0); + verticalScrollBar()->setSingleStep(1); + } else { // ScrollPerPixel + verticalScrollBar()->setPageStep(vsize.height()); + verticalScrollBar()->setRange(0, verticalLength - vsize.height()); + verticalScrollBar()->setSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2)); + } + + d->geometryRecursionBlock = false; + QAbstractItemView::updateGeometries(); +} + +/*! + Returns the size hint for the given \a row's height or -1 if there + is no model. + + If you need to set the height of a given row to a fixed value, call + QHeaderView::resizeSection() on the table's vertical header. + + If you reimplement this function in a subclass, note that the value you + return is only used when resizeRowToContents() is called. In that case, + if a larger row height is required by either the vertical header or + the item delegate, that width will be used instead. + + \sa QWidget::sizeHint, verticalHeader() +*/ +int QTableView::sizeHintForRow(int row) const +{ + Q_D(const QTableView); + + if (!model()) + return -1; + + ensurePolished(); + + int left = qMax(0, d->horizontalHeader->visualIndexAt(0)); + int right = d->horizontalHeader->visualIndexAt(d->viewport->width()); + if (right == -1) // the table don't have enough columns to fill the viewport + right = d->model->columnCount(d->root) - 1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + + int hint = 0; + QModelIndex index; + for (int column = left; column <= right; ++column) { + int logicalColumn = d->horizontalHeader->logicalIndex(column); + if (d->horizontalHeader->isSectionHidden(logicalColumn)) + continue; + index = d->model->index(row, logicalColumn, d->root); + if (d->wrapItemText) {// for wrapping boundaries + option.rect.setY(rowViewportPosition(index.row())); + option.rect.setHeight(rowHeight(index.row())); + option.rect.setX(columnViewportPosition(index.column())); + option.rect.setWidth(columnWidth(index.column())); + } + + QWidget *editor = d->editorForIndex(index).widget.data(); + if (editor && d->persistent.contains(editor)) { + hint = qMax(hint, editor->sizeHint().height()); + int min = editor->minimumSize().height(); + int max = editor->maximumSize().height(); + hint = qBound(min, hint, max); + } + + hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).height()); + } + + return d->showGrid ? hint + 1 : hint; +} + +/*! + Returns the size hint for the given \a column's width or -1 if + there is no model. + + If you need to set the width of a given column to a fixed value, call + QHeaderView::resizeSection() on the table's horizontal header. + + If you reimplement this function in a subclass, note that the value you + return will be used when resizeColumnToContents() or + QHeaderView::resizeSections() is called. If a larger column width is + required by either the horizontal header or the item delegate, the larger + width will be used instead. + + \sa QWidget::sizeHint, horizontalHeader() +*/ +int QTableView::sizeHintForColumn(int column) const +{ + Q_D(const QTableView); + + if (!model()) + return -1; + + ensurePolished(); + + int top = qMax(0, d->verticalHeader->visualIndexAt(0)); + int bottom = d->verticalHeader->visualIndexAt(d->viewport->height()); + if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport + bottom = d->model->rowCount(d->root) - 1; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + + int hint = 0; + QModelIndex index; + for (int row = top; row <= bottom; ++row) { + int logicalRow = d->verticalHeader->logicalIndex(row); + if (d->verticalHeader->isSectionHidden(logicalRow)) + continue; + index = d->model->index(logicalRow, column, d->root); + + QWidget *editor = d->editorForIndex(index).widget.data(); + if (editor && d->persistent.contains(editor)) { + hint = qMax(hint, editor->sizeHint().width()); + int min = editor->minimumSize().width(); + int max = editor->maximumSize().width(); + hint = qBound(min, hint, max); + } + + hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).width()); + } + + return d->showGrid ? hint + 1 : hint; +} + +/*! + Returns the y-coordinate in contents coordinates of the given \a + row. +*/ +int QTableView::rowViewportPosition(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->sectionViewportPosition(row); +} + +/*! + Returns the row in which the given y-coordinate, \a y, in contents + coordinates is located. + + \note This function returns -1 if the given coordinate is not valid + (has no row). + + \sa columnAt() +*/ +int QTableView::rowAt(int y) const +{ + Q_D(const QTableView); + return d->verticalHeader->logicalIndexAt(y); +} + +/*! + \since 4.1 + + Sets the height of the given \a row to be \a height. +*/ +void QTableView::setRowHeight(int row, int height) +{ + Q_D(const QTableView); + d->verticalHeader->resizeSection(row, height); +} + +/*! + Returns the height of the given \a row. + + \sa resizeRowToContents(), columnWidth() +*/ +int QTableView::rowHeight(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->sectionSize(row); +} + +/*! + Returns the x-coordinate in contents coordinates of the given \a + column. +*/ +int QTableView::columnViewportPosition(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->sectionViewportPosition(column); +} + +/*! + Returns the column in which the given x-coordinate, \a x, in contents + coordinates is located. + + \note This function returns -1 if the given coordinate is not valid + (has no column). + + \sa rowAt() +*/ +int QTableView::columnAt(int x) const +{ + Q_D(const QTableView); + return d->horizontalHeader->logicalIndexAt(x); +} + +/*! + \since 4.1 + + Sets the width of the given \a column to be \a width. +*/ +void QTableView::setColumnWidth(int column, int width) +{ + Q_D(const QTableView); + d->horizontalHeader->resizeSection(column, width); +} + +/*! + Returns the width of the given \a column. + + \sa resizeColumnToContents(), rowHeight() +*/ +int QTableView::columnWidth(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->sectionSize(column); +} + +/*! + Returns true if the given \a row is hidden; otherwise returns false. + + \sa isColumnHidden() +*/ +bool QTableView::isRowHidden(int row) const +{ + Q_D(const QTableView); + return d->verticalHeader->isSectionHidden(row); +} + +/*! + If \a hide is true \a row will be hidden, otherwise it will be shown. + + \sa setColumnHidden() +*/ +void QTableView::setRowHidden(int row, bool hide) +{ + Q_D(QTableView); + if (row < 0 || row >= d->verticalHeader->count()) + return; + d->verticalHeader->setSectionHidden(row, hide); +} + +/*! + Returns true if the given \a column is hidden; otherwise returns false. + + \sa isRowHidden() +*/ +bool QTableView::isColumnHidden(int column) const +{ + Q_D(const QTableView); + return d->horizontalHeader->isSectionHidden(column); +} + +/*! + If \a hide is true the given \a column will be hidden; otherwise it + will be shown. + + \sa setRowHidden() +*/ +void QTableView::setColumnHidden(int column, bool hide) +{ + Q_D(QTableView); + if (column < 0 || column >= d->horizontalHeader->count()) + return; + d->horizontalHeader->setSectionHidden(column, hide); +} + +/*! + \since 4.2 + \property QTableView::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the table. If + this property is false, sorting is not enabled. The default value + is false. + + \note. Setting the property to true with setSortingEnabled() + immediately triggers a call to sortByColumn() with the current + sort section and order. + + \sa sortByColumn() +*/ + +/*! + If \a enabled true enables sorting for the table and immediately + trigger a call to sortByColumn() with the current sort section and + order + */ +void QTableView::setSortingEnabled(bool enable) +{ + Q_D(QTableView); + d->sortingEnabled = enable; + horizontalHeader()->setSortIndicatorShown(enable); + if (enable) { + disconnect(d->horizontalHeader, SIGNAL(sectionEntered(int)), + this, SLOT(_q_selectColumn(int))); + disconnect(horizontalHeader(), SIGNAL(sectionPressed(int)), + this, SLOT(selectColumn(int))); + connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(sortByColumn(int)), Qt::UniqueConnection); + sortByColumn(horizontalHeader()->sortIndicatorSection(), + horizontalHeader()->sortIndicatorOrder()); + } else { + connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), + this, SLOT(_q_selectColumn(int)), Qt::UniqueConnection); + connect(horizontalHeader(), SIGNAL(sectionPressed(int)), + this, SLOT(selectColumn(int)), Qt::UniqueConnection); + disconnect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(sortByColumn(int))); + } +} + +bool QTableView::isSortingEnabled() const +{ + Q_D(const QTableView); + return d->sortingEnabled; +} + +/*! + \property QTableView::showGrid + \brief whether the grid is shown + + If this property is true a grid is drawn for the table; if the + property is false, no grid is drawn. The default value is true. +*/ +bool QTableView::showGrid() const +{ + Q_D(const QTableView); + return d->showGrid; +} + +void QTableView::setShowGrid(bool show) +{ + Q_D(QTableView); + if (d->showGrid != show) { + d->showGrid = show; + d->viewport->update(); + } +} + +/*! + \property QTableView::gridStyle + \brief the pen style used to draw the grid. + + This property holds the style used when drawing the grid (see \l{showGrid}). +*/ +Qt::PenStyle QTableView::gridStyle() const +{ + Q_D(const QTableView); + return d->gridStyle; +} + +void QTableView::setGridStyle(Qt::PenStyle style) +{ + Q_D(QTableView); + if (d->gridStyle != style) { + d->gridStyle = style; + d->viewport->update(); + } +} + +/*! + \property QTableView::wordWrap + \brief the item text word-wrapping policy + \since 4.3 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is true by default. + + Note that even of wrapping is enabled, the cell will not be + expanded to fit all text. Ellipsis will be inserted according to + the current \l{QAbstractItemView::}{textElideMode}. + +*/ +void QTableView::setWordWrap(bool on) +{ + Q_D(QTableView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + QMetaObject::invokeMethod(d->verticalHeader, "resizeSections"); + QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections"); +} + +bool QTableView::wordWrap() const +{ + Q_D(const QTableView); + return d->wrapItemText; +} + +/*! + \property QTableView::cornerButtonEnabled + \brief whether the button in the top-left corner is enabled + \since 4.3 + + If this property is true then button in the top-left corner + of the table view is enabled. Clicking on this button will + select all the cells in the table view. + + This property is true by default. +*/ +void QTableView::setCornerButtonEnabled(bool enable) +{ + Q_D(QTableView); + d->cornerWidget->setEnabled(enable); +} + +bool QTableView::isCornerButtonEnabled() const +{ + Q_D(const QTableView); + return d->cornerWidget->isEnabled(); +} + +/*! + \internal + + Returns the rectangle on the viewport occupied by the given \a + index. + If the index is hidden in the view it will return a null QRect. +*/ +QRect QTableView::visualRect(const QModelIndex &index) const +{ + Q_D(const QTableView); + if (!d->isIndexValid(index) || index.parent() != d->root + || (!d->hasSpans() && isIndexHidden(index))) + return QRect(); + + d->executePostedLayout(); + + if (d->hasSpans()) { + QSpanCollection::Span span = d->span(index.row(), index.column()); + return d->visualSpanRect(span); + } + + int rowp = rowViewportPosition(index.row()); + int rowh = rowHeight(index.row()); + int colp = columnViewportPosition(index.column()); + int colw = columnWidth(index.column()); + + const int i = showGrid() ? 1 : 0; + return QRect(colp, rowp, colw - i, rowh - i); +} + +/*! + \internal + + Makes sure that the given \a item is visible in the table view, + scrolling if necessary. +*/ +void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QTableView); + + // check if we really need to do anything + if (!d->isIndexValid(index) + || (d->model->parent(index) != d->root) + || isRowHidden(index.row()) || isColumnHidden(index.column())) + return; + + QSpanCollection::Span span; + if (d->hasSpans()) + span = d->span(index.row(), index.column()); + + // Adjust horizontal position + + int viewportWidth = d->viewport->width(); + int horizontalOffset = d->horizontalHeader->offset(); + int horizontalPosition = d->horizontalHeader->sectionPosition(index.column()); + int horizontalIndex = d->horizontalHeader->visualIndex(index.column()); + int cellWidth = d->hasSpans() + ? d->columnSpanWidth(index.column(), span.width()) + : d->horizontalHeader->sectionSize(index.column()); + + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + + bool positionAtLeft = (horizontalPosition - horizontalOffset < 0); + bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth); + + if (hint == PositionAtCenter || positionAtRight) { + int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth); + int x = cellWidth; + while (horizontalIndex > 0) { + x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1)); + if (x > w) + break; + --horizontalIndex; + } + } + + if (positionAtRight || hint == PositionAtCenter || positionAtLeft) { + int hiddenSections = 0; + if (d->horizontalHeader->sectionsHidden()) { + for (int s = horizontalIndex - 1; s >= 0; --s) { + int column = d->horizontalHeader->logicalIndex(s); + if (d->horizontalHeader->isSectionHidden(column)) + ++hiddenSections; + } + } + horizontalScrollBar()->setValue(horizontalIndex - hiddenSections); + } + + } else { // ScrollPerPixel + if (hint == PositionAtCenter) { + horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); + } else { + if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition); + else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); + } + } + + // Adjust vertical position + + int viewportHeight = d->viewport->height(); + int verticalOffset = d->verticalHeader->offset(); + int verticalPosition = d->verticalHeader->sectionPosition(index.row()); + int verticalIndex = d->verticalHeader->visualIndex(index.row()); + int cellHeight = d->hasSpans() + ? d->rowSpanHeight(index.row(), span.height()) + : d->verticalHeader->sectionSize(index.row()); + + if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) { + if (hint == EnsureVisible) + hint = PositionAtTop; + } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) { + if (hint == EnsureVisible) + hint = PositionAtBottom; + } + + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + + if (hint == PositionAtBottom || hint == PositionAtCenter) { + int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight); + int y = cellHeight; + while (verticalIndex > 0) { + int row = d->verticalHeader->logicalIndex(verticalIndex - 1); + y += d->verticalHeader->sectionSize(row); + if (y > h) + break; + --verticalIndex; + } + } + + if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) { + int hiddenSections = 0; + if (d->verticalHeader->sectionsHidden()) { + for (int s = verticalIndex - 1; s >= 0; --s) { + int row = d->verticalHeader->logicalIndex(s); + if (d->verticalHeader->isSectionHidden(row)) + ++hiddenSections; + } + } + verticalScrollBar()->setValue(verticalIndex - hiddenSections); + } + + } else { // ScrollPerPixel + if (hint == PositionAtTop) { + verticalScrollBar()->setValue(verticalPosition); + } else if (hint == PositionAtBottom) { + verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight); + } else if (hint == PositionAtCenter) { + verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2)); + } + } + + update(index); +} + +/*! + This slot is called to change the height of the given \a row. The + old height is specified by \a oldHeight, and the new height by \a + newHeight. + + \sa columnResized() +*/ +void QTableView::rowResized(int row, int, int) +{ + Q_D(QTableView); + d->rowsToUpdate.append(row); + if (d->rowResizeTimerID == 0) + d->rowResizeTimerID = startTimer(0); +} + +/*! + This slot is called to change the width of the given \a column. + The old width is specified by \a oldWidth, and the new width by \a + newWidth. + + \sa rowResized() +*/ +void QTableView::columnResized(int column, int, int) +{ + Q_D(QTableView); + d->columnsToUpdate.append(column); + if (d->columnResizeTimerID == 0) + d->columnResizeTimerID = startTimer(0); +} + +/*! + \reimp + */ +void QTableView::timerEvent(QTimerEvent *event) +{ + Q_D(QTableView); + + if (event->timerId() == d->columnResizeTimerID) { + updateGeometries(); + killTimer(d->columnResizeTimerID); + d->columnResizeTimerID = 0; + + QRect rect; + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + if (d->hasSpans()) { + rect = QRect(0, 0, viewportWidth, viewportHeight); + } else { + for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) { + int column = d->columnsToUpdate.at(i); + int x = columnViewportPosition(column); + if (isRightToLeft()) + rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); + else + rect |= QRect(x, 0, viewportWidth - x, viewportHeight); + } + } + + d->viewport->update(rect.normalized()); + d->columnsToUpdate.clear(); + } + + if (event->timerId() == d->rowResizeTimerID) { + updateGeometries(); + killTimer(d->rowResizeTimerID); + d->rowResizeTimerID = 0; + + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + int top; + if (d->hasSpans()) { + top = 0; + } else { + top = viewportHeight; + for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) { + int y = rowViewportPosition(d->rowsToUpdate.at(i)); + top = qMin(top, y); + } + } + + d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top)); + d->rowsToUpdate.clear(); + } + + QAbstractItemView::timerEvent(event); +} + +/*! + This slot is called to change the index of the given \a row in the + table view. The old index is specified by \a oldIndex, and the new + index by \a newIndex. + + \sa columnMoved() +*/ +void QTableView::rowMoved(int, int oldIndex, int newIndex) +{ + Q_D(QTableView); + + updateGeometries(); + int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex); + int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex); + if (d->hasSpans()) { + d->viewport->update(); + } else { + int oldTop = rowViewportPosition(logicalOldIndex); + int newTop = rowViewportPosition(logicalNewIndex); + int oldBottom = oldTop + rowHeight(logicalOldIndex); + int newBottom = newTop + rowHeight(logicalNewIndex); + int top = qMin(oldTop, newTop); + int bottom = qMax(oldBottom, newBottom); + int height = bottom - top; + d->viewport->update(0, top, d->viewport->width(), height); + } +} + +/*! + This slot is called to change the index of the given \a column in + the table view. The old index is specified by \a oldIndex, and + the new index by \a newIndex. + + \sa rowMoved() +*/ +void QTableView::columnMoved(int, int oldIndex, int newIndex) +{ + Q_D(QTableView); + + updateGeometries(); + int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex); + int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex); + if (d->hasSpans()) { + d->viewport->update(); + } else { + int oldLeft = columnViewportPosition(logicalOldIndex); + int newLeft = columnViewportPosition(logicalNewIndex); + int oldRight = oldLeft + columnWidth(logicalOldIndex); + int newRight = newLeft + columnWidth(logicalNewIndex); + int left = qMin(oldLeft, newLeft); + int right = qMax(oldRight, newRight); + int width = right - left; + d->viewport->update(left, 0, width, d->viewport->height()); + } +} + +/*! + Selects the given \a row in the table view if the current + SelectionMode and SelectionBehavior allows rows to be selected. + + \sa selectColumn() +*/ +void QTableView::selectRow(int row) +{ + Q_D(QTableView); + d->selectRow(row, true); +} + +/*! + Selects the given \a column in the table view if the current + SelectionMode and SelectionBehavior allows columns to be selected. + + \sa selectRow() +*/ +void QTableView::selectColumn(int column) +{ + Q_D(QTableView); + d->selectColumn(column, true); +} + +/*! + Hide the given \a row. + + \sa showRow() hideColumn() +*/ +void QTableView::hideRow(int row) +{ + Q_D(QTableView); + d->verticalHeader->hideSection(row); +} + +/*! + Hide the given \a column. + + \sa showColumn() hideRow() +*/ +void QTableView::hideColumn(int column) +{ + Q_D(QTableView); + d->horizontalHeader->hideSection(column); +} + +/*! + Show the given \a row. + + \sa hideRow() showColumn() +*/ +void QTableView::showRow(int row) +{ + Q_D(QTableView); + d->verticalHeader->showSection(row); +} + +/*! + Show the given \a column. + + \sa hideColumn() showRow() +*/ +void QTableView::showColumn(int column) +{ + Q_D(QTableView); + d->horizontalHeader->showSection(column); +} + +/*! + Resizes the given \a row based on the size hints of the delegate + used to render each item in the row. +*/ +void QTableView::resizeRowToContents(int row) +{ + Q_D(QTableView); + int content = sizeHintForRow(row); + int header = d->verticalHeader->sectionSizeHint(row); + d->verticalHeader->resizeSection(row, qMax(content, header)); +} + +/*! + Resizes all rows based on the size hints of the delegate + used to render each item in the rows. +*/ +void QTableView::resizeRowsToContents() +{ + Q_D(QTableView); + d->verticalHeader->resizeSections(QHeaderView::ResizeToContents); +} + +/*! + Resizes the given \a column based on the size hints of the delegate + used to render each item in the column. + + \note Only visible columns will be resized. Reimplement sizeHintForColumn() + to resize hidden columns as well. +*/ +void QTableView::resizeColumnToContents(int column) +{ + Q_D(QTableView); + int content = sizeHintForColumn(column); + int header = d->horizontalHeader->sectionSizeHint(column); + d->horizontalHeader->resizeSection(column, qMax(content, header)); +} + +/*! + Resizes all columns based on the size hints of the delegate + used to render each item in the columns. +*/ +void QTableView::resizeColumnsToContents() +{ + Q_D(QTableView); + d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents); +} + +/*! + \obsolete + \overload + + Sorts the model by the values in the given \a column. +*/ +void QTableView::sortByColumn(int column) +{ + Q_D(QTableView); + if (column == -1) + return; + d->model->sort(column, d->horizontalHeader->sortIndicatorOrder()); +} + +/*! + \since 4.2 + + Sorts the model by the values in the given \a column in the given \a order. + + \sa sortingEnabled + */ +void QTableView::sortByColumn(int column, Qt::SortOrder order) +{ + Q_D(QTableView); + d->horizontalHeader->setSortIndicator(column, order); + sortByColumn(column); +} + +/*! + \internal +*/ +void QTableView::verticalScrollbarAction(int action) +{ + QAbstractItemView::verticalScrollbarAction(action); +} + +/*! + \internal +*/ +void QTableView::horizontalScrollbarAction(int action) +{ + QAbstractItemView::horizontalScrollbarAction(action); +} + +/*! + \reimp +*/ +bool QTableView::isIndexHidden(const QModelIndex &index) const +{ + Q_D(const QTableView); + Q_ASSERT(d->isIndexValid(index)); + if (isRowHidden(index.row()) || isColumnHidden(index.column())) + return true; + if (d->hasSpans()) { + QSpanCollection::Span span = d->span(index.row(), index.column()); + return !((span.top() == index.row()) && (span.left() == index.column())); + } + return false; +} + +/*! + \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount) + \since 4.2 + + Sets the span of the table element at (\a row, \a column) to the number of + rows and columns specified by (\a rowSpanCount, \a columnSpanCount). + + \sa rowSpan(), columnSpan() +*/ +void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan) +{ + Q_D(QTableView); + if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0) + return; + d->setSpan(row, column, rowSpan, columnSpan); + d->viewport->update(); +} + +/*! + \since 4.2 + + Returns the row span of the table element at (\a row, \a column). + The default is 1. + + \sa setSpan(), columnSpan() +*/ +int QTableView::rowSpan(int row, int column) const +{ + Q_D(const QTableView); + return d->rowSpan(row, column); +} + +/*! + \since 4.2 + + Returns the column span of the table element at (\a row, \a + column). The default is 1. + + \sa setSpan(), rowSpan() +*/ +int QTableView::columnSpan(int row, int column) const +{ + Q_D(const QTableView); + return d->columnSpan(row, column); +} + +/*! + \since 4.4 + + Removes all row and column spans in the table view. + + \sa setSpan() +*/ + +void QTableView::clearSpans() +{ + Q_D(QTableView); + d->spans.clear(); + d->viewport->update(); +} + +void QTableViewPrivate::_q_selectRow(int row) +{ + selectRow(row, false); +} + +void QTableViewPrivate::_q_selectColumn(int column) +{ + selectColumn(column, false); +} + +void QTableViewPrivate::selectRow(int row, bool anchor) +{ + Q_Q(QTableView); + + if (q->selectionBehavior() == QTableView::SelectColumns + || (q->selectionMode() == QTableView::SingleSelection + && q->selectionBehavior() == QTableView::SelectItems)) + return; + + if (row >= 0 && row < model->rowCount(root)) { + int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0); + QModelIndex index = model->index(row, column, root); + QItemSelectionModel::SelectionFlags command = q->selectionCommand(index); + selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + if ((anchor && !(command & QItemSelectionModel::Current)) + || (q->selectionMode() == QTableView::SingleSelection)) + rowSectionAnchor = row; + + if (q->selectionMode() != QTableView::SingleSelection + && command.testFlag(QItemSelectionModel::Toggle)) { + if (anchor) + ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows().contains(index) + ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; + command &= ~QItemSelectionModel::Toggle; + command |= ctrlDragSelectionFlag; + if (!anchor) + command |= QItemSelectionModel::Current; + } + + QModelIndex tl = model->index(qMin(rowSectionAnchor, row), 0, root); + QModelIndex br = model->index(qMax(rowSectionAnchor, row), model->columnCount(root) - 1, root); + if (verticalHeader->sectionsMoved() && tl.row() != br.row()) + q->setSelection(q->visualRect(tl)|q->visualRect(br), command); + else + selectionModel->select(QItemSelection(tl, br), command); + } +} + +void QTableViewPrivate::selectColumn(int column, bool anchor) +{ + Q_Q(QTableView); + + if (q->selectionBehavior() == QTableView::SelectRows + || (q->selectionMode() == QTableView::SingleSelection + && q->selectionBehavior() == QTableView::SelectItems)) + return; + + if (column >= 0 && column < model->columnCount(root)) { + int row = verticalHeader->logicalIndexAt(0); + QModelIndex index = model->index(row, column, root); + QItemSelectionModel::SelectionFlags command = q->selectionCommand(index); + selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + if ((anchor && !(command & QItemSelectionModel::Current)) + || (q->selectionMode() == QTableView::SingleSelection)) + columnSectionAnchor = column; + + if (q->selectionMode() != QTableView::SingleSelection + && command.testFlag(QItemSelectionModel::Toggle)) { + if (anchor) + ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns().contains(index) + ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; + command &= ~QItemSelectionModel::Toggle; + command |= ctrlDragSelectionFlag; + if (!anchor) + command |= QItemSelectionModel::Current; + } + + QModelIndex tl = model->index(0, qMin(columnSectionAnchor, column), root); + QModelIndex br = model->index(model->rowCount(root) - 1, + qMax(columnSectionAnchor, column), root); + if (horizontalHeader->sectionsMoved() && tl.column() != br.column()) + q->setSelection(q->visualRect(tl)|q->visualRect(br), command); + else + selectionModel->select(QItemSelection(tl, br), command); + } +} + +/*! + \reimp + */ +void QTableView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + if (current.isValid()) { + int entry = visualIndex(current) + 1; + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } + } +#endif + QAbstractItemView::currentChanged(current, previous); +} + +/*! + \reimp + */ +void QTableView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel); + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(sel); + if (horizontalHeader()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QTableView::visualIndex(const QModelIndex &index) const +{ + return index.row(); +} + +QT_END_NAMESPACE + +#include "qtableview.moc" + +#include "moc_qtableview.cpp" + +#endif // QT_NO_TABLEVIEW diff --git a/src/gui/itemviews/qtableview.h b/src/gui/itemviews/qtableview.h new file mode 100644 index 0000000000..d4be0868c0 --- /dev/null +++ b/src/gui/itemviews/qtableview.h @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEVIEW_H +#define QTABLEVIEW_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABLEVIEW + +class QHeaderView; +class QTableViewPrivate; + +class Q_GUI_EXPORT QTableView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid) + Q_PROPERTY(Qt::PenStyle gridStyle READ gridStyle WRITE setGridStyle) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool cornerButtonEnabled READ isCornerButtonEnabled WRITE setCornerButtonEnabled) + +public: + explicit QTableView(QWidget *parent = 0); + ~QTableView(); + + void setModel(QAbstractItemModel *model); + void setRootIndex(const QModelIndex &index); + void setSelectionModel(QItemSelectionModel *selectionModel); + + QHeaderView *horizontalHeader() const; + QHeaderView *verticalHeader() const; + void setHorizontalHeader(QHeaderView *header); + void setVerticalHeader(QHeaderView *header); + + int rowViewportPosition(int row) const; + int rowAt(int y) const; + + void setRowHeight(int row, int height); + int rowHeight(int row) const; + + int columnViewportPosition(int column) const; + int columnAt(int x) const; + + void setColumnWidth(int column, int width); + int columnWidth(int column) const; + + bool isRowHidden(int row) const; + void setRowHidden(int row, bool hide); + + bool isColumnHidden(int column) const; + void setColumnHidden(int column, bool hide); + + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + bool showGrid() const; + + Qt::PenStyle gridStyle() const; + void setGridStyle(Qt::PenStyle style); + + void setWordWrap(bool on); + bool wordWrap() const; + + void setCornerButtonEnabled(bool enable); + bool isCornerButtonEnabled() const; + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + + void setSpan(int row, int column, int rowSpan, int columnSpan); + int rowSpan(int row, int column) const; + int columnSpan(int row, int column) const; + void clearSpans(); + + void sortByColumn(int column, Qt::SortOrder order); + +public Q_SLOTS: + void selectRow(int row); + void selectColumn(int column); + void hideRow(int row); + void hideColumn(int column); + void showRow(int row); + void showColumn(int column); + void resizeRowToContents(int row); + void resizeRowsToContents(); + void resizeColumnToContents(int column); + void resizeColumnsToContents(); + void sortByColumn(int column); + void setShowGrid(bool show); + +protected Q_SLOTS: + void rowMoved(int row, int oldIndex, int newIndex); + void columnMoved(int column, int oldIndex, int newIndex); + void rowResized(int row, int oldHeight, int newHeight); + void columnResized(int column, int oldWidth, int newWidth); + void rowCountChanged(int oldCount, int newCount); + void columnCountChanged(int oldCount, int newCount); + +protected: + QTableView(QTableViewPrivate &, QWidget *parent); + void scrollContentsBy(int dx, int dy); + + QStyleOptionViewItem viewOptions() const; + void paintEvent(QPaintEvent *e); + + void timerEvent(QTimerEvent *event); + + int horizontalOffset() const; + int verticalOffset() const; + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void updateGeometries(); + + int sizeHintForRow(int row) const; + int sizeHintForColumn(int column) const; + + void verticalScrollbarAction(int action); + void horizontalScrollbarAction(int action); + + bool isIndexHidden(const QModelIndex &index) const; + + void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, + const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QTableView) + Q_DISABLE_COPY(QTableView) + Q_PRIVATE_SLOT(d_func(), void _q_selectRow(int)) + Q_PRIVATE_SLOT(d_func(), void _q_selectColumn(int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSpanInsertedRows(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSpanInsertedColumns(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSpanRemovedRows(QModelIndex,int,int)) + Q_PRIVATE_SLOT(d_func(), void _q_updateSpanRemovedColumns(QModelIndex,int,int)) +}; + +#endif // QT_NO_TABLEVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABLEVIEW_H diff --git a/src/gui/itemviews/qtableview_p.h b/src/gui/itemviews/qtableview_p.h new file mode 100644 index 0000000000..e3d2a8f4a6 --- /dev/null +++ b/src/gui/itemviews/qtableview_p.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEVIEW_P_H +#define QTABLEVIEW_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 +#include +#include +#include +#include +#include "private/qabstractitemview_p.h" + +#ifndef QT_NO_TABLEVIEW + +QT_BEGIN_NAMESPACE + +/** \internal +* +* This is a list of span with a binary index to look up quickly a span at a certain index. +* +* The index is a map of map. +* spans are mentaly divided into sub spans so that the start of any subspans doesn't overlap +* with any other subspans. There is no real representation of the subspans. +* The key of the first map is the row where the subspan starts, the value of the first map is +* a list (map) of all subspans that starts at the same row. It is indexed with its row +*/ +class Q_AUTOTEST_EXPORT QSpanCollection +{ +public: + struct Span + { + int m_top; + int m_left; + int m_bottom; + int m_right; + bool will_be_deleted; + Span() + : m_top(-1), m_left(-1), m_bottom(-1), m_right(-1), will_be_deleted(false) { } + Span(int row, int column, int rowCount, int columnCount) + : m_top(row), m_left(column), m_bottom(row+rowCount-1), m_right(column+columnCount-1), will_be_deleted(false) { } + inline int top() const { return m_top; } + inline int left() const { return m_left; } + inline int bottom() const { return m_bottom; } + inline int right() const { return m_right; } + inline int height() const { return m_bottom - m_top + 1; } + inline int width() const { return m_right - m_left + 1; } + }; + + ~QSpanCollection() + { + qDeleteAll(spans); + } + + void addSpan(Span *span); + void updateSpan(Span *span, int old_height); + Span *spanAt(int x, int y) const; + void clear(); + QList spansInRect(int x, int y, int w, int h) const; + + void updateInsertedRows(int start, int end); + void updateInsertedColumns(int start, int end); + void updateRemovedRows(int start, int end); + void updateRemovedColumns(int start, int end); + +#ifdef QT_BUILD_INTERNAL + bool checkConsistency() const; +#endif + + typedef QLinkedList SpanList; + SpanList spans; //lists of all spans +private: + //the indexes are negative so the QMap::lowerBound do what i need. + typedef QMap SubIndex; + typedef QMap Index; + Index index; + + bool cleanSpanSubIndex(SubIndex &subindex, int end, bool update = false); +}; + +Q_DECLARE_TYPEINFO ( QSpanCollection::Span, Q_MOVABLE_TYPE); + + +class QTableViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QTableView) +public: + QTableViewPrivate() + : showGrid(true), gridStyle(Qt::SolidLine), + rowSectionAnchor(-1), columnSectionAnchor(-1), + columnResizeTimerID(0), rowResizeTimerID(0), + horizontalHeader(0), verticalHeader(0), + sortingEnabled(false), geometryRecursionBlock(false), + visualCursor(QPoint()) + { + wrapItemText = true; +#ifndef QT_NO_DRAGANDDROP + overwrite = true; +#endif + } + void init(); + void trimHiddenSelections(QItemSelectionRange *range) const; + + inline bool isHidden(int row, int col) const { + return verticalHeader->isSectionHidden(row) + || horizontalHeader->isSectionHidden(col); + } + inline int visualRow(int logicalRow) const { + return verticalHeader->visualIndex(logicalRow); + } + inline int visualColumn(int logicalCol) const { + return horizontalHeader->visualIndex(logicalCol); + } + inline int logicalRow(int visualRow) const { + return verticalHeader->logicalIndex(visualRow); + } + inline int logicalColumn(int visualCol) const { + return horizontalHeader->logicalIndex(visualCol); + } + + int sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const; + int sectionSpanSize(const QHeaderView *header, int logical, int span) const; + bool spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const; + void drawAndClipSpans(const QRegion &area, QPainter *painter, + const QStyleOptionViewItemV4 &option, QBitArray *drawn, + int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn); + void drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index); + + bool showGrid; + Qt::PenStyle gridStyle; + int rowSectionAnchor; + int columnSectionAnchor; + int columnResizeTimerID; + int rowResizeTimerID; + QList columnsToUpdate; + QList rowsToUpdate; + QHeaderView *horizontalHeader; + QHeaderView *verticalHeader; + QWidget *cornerWidget; + bool sortingEnabled; + bool geometryRecursionBlock; + QPoint visualCursor; // (Row,column) cell coordinates to track through span navigation. + + QSpanCollection spans; + + void setSpan(int row, int column, int rowSpan, int columnSpan); + QSpanCollection::Span span(int row, int column) const; + inline int rowSpan(int row, int column) const { + return span(row, column).height(); + } + inline int columnSpan(int row, int column) const { + return span(row, column).width(); + } + inline bool hasSpans() const { + return !spans.spans.isEmpty(); + } + inline int rowSpanHeight(int row, int span) const { + return sectionSpanSize(verticalHeader, row, span); + } + inline int columnSpanWidth(int column, int span) const { + return sectionSpanSize(horizontalHeader, column, span); + } + inline int rowSpanEndLogical(int row, int span) const { + return sectionSpanEndLogical(verticalHeader, row, span); + } + inline int columnSpanEndLogical(int column, int span) const { + return sectionSpanEndLogical(horizontalHeader, column, span); + } + + inline bool isRowHidden(int row) const { + return verticalHeader->isSectionHidden(row); + } + inline bool isColumnHidden(int column) const { + return horizontalHeader->isSectionHidden(column); + } + inline bool isCellEnabled(int row, int column) const { + return isIndexEnabled(model->index(row, column, root)); + } + inline bool isVisualRowHiddenOrDisabled(int row, int column) const { + int r = logicalRow(row); + int c = logicalColumn(column); + return isRowHidden(r) || !isCellEnabled(r, c); + } + inline bool isVisualColumnHiddenOrDisabled(int row, int column) const { + int r = logicalRow(row); + int c = logicalColumn(column); + return isColumnHidden(c) || !isCellEnabled(r, c); + } + + QRect visualSpanRect(const QSpanCollection::Span &span) const; + + void _q_selectRow(int row); + void _q_selectColumn(int column); + + void selectRow(int row, bool anchor); + void selectColumn(int column, bool anchor); + + void _q_updateSpanInsertedRows(const QModelIndex &parent, int start, int end); + void _q_updateSpanInsertedColumns(const QModelIndex &parent, int start, int end); + void _q_updateSpanRemovedRows(const QModelIndex &parent, int start, int end); + void _q_updateSpanRemovedColumns(const QModelIndex &parent, int start, int end); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TABLEVIEW + +#endif // QTABLEVIEW_P_H diff --git a/src/gui/itemviews/qtablewidget.cpp b/src/gui/itemviews/qtablewidget.cpp new file mode 100644 index 0000000000..3c5d22379a --- /dev/null +++ b/src/gui/itemviews/qtablewidget.cpp @@ -0,0 +1,2685 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtablewidget.h" + +#ifndef QT_NO_TABLEWIDGET +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QTableModel::QTableModel(int rows, int columns, QTableWidget *parent) + : QAbstractTableModel(parent), + prototype(0), + tableItems(rows * columns, 0), + verticalHeaderItems(rows, 0), + horizontalHeaderItems(columns, 0) +{} + +QTableModel::~QTableModel() +{ + clear(); + delete prototype; +} + +bool QTableModel::insertRows(int row, int count, const QModelIndex &) +{ + if (count < 1 || row < 0 || row > verticalHeaderItems.count()) + return false; + + beginInsertRows(QModelIndex(), row, row + count - 1); + int rc = verticalHeaderItems.count(); + int cc = horizontalHeaderItems.count(); + verticalHeaderItems.insert(row, count, 0); + if (rc == 0) + tableItems.resize(cc * count); + else + tableItems.insert(tableIndex(row, 0), cc * count, 0); + endInsertRows(); + return true; +} + +bool QTableModel::insertColumns(int column, int count, const QModelIndex &) +{ + if (count < 1 || column < 0 || column > horizontalHeaderItems.count()) + return false; + + beginInsertColumns(QModelIndex(), column, column + count - 1); + int rc = verticalHeaderItems.count(); + int cc = horizontalHeaderItems.count(); + horizontalHeaderItems.insert(column, count, 0); + if (cc == 0) + tableItems.resize(rc * count); + else + for (int row = 0; row < rc; ++row) + tableItems.insert(tableIndex(row, column), count, 0); + endInsertColumns(); + return true; +} + +bool QTableModel::removeRows(int row, int count, const QModelIndex &) +{ + if (count < 1 || row < 0 || row + count > verticalHeaderItems.count()) + return false; + + beginRemoveRows(QModelIndex(), row, row + count - 1); + int i = tableIndex(row, 0); + int n = count * columnCount(); + QTableWidgetItem *oldItem = 0; + for (int j = i; j < n + i; ++j) { + oldItem = tableItems.at(j); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + tableItems.remove(qMax(i, 0), n); + for (int v = row; v < row + count; ++v) { + oldItem = verticalHeaderItems.at(v); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + verticalHeaderItems.remove(row, count); + endRemoveRows(); + return true; +} + +bool QTableModel::removeColumns(int column, int count, const QModelIndex &) +{ + if (count < 1 || column < 0 || column + count > horizontalHeaderItems.count()) + return false; + + beginRemoveColumns(QModelIndex(), column, column + count - 1); + QTableWidgetItem *oldItem = 0; + for (int row = rowCount() - 1; row >= 0; --row) { + int i = tableIndex(row, column); + for (int j = i; j < i + count; ++j) { + oldItem = tableItems.at(j); + if (oldItem) + oldItem->view = 0; + delete oldItem; + } + tableItems.remove(i, count); + } + for (int h=column; hview = 0; + delete oldItem; + } + horizontalHeaderItems.remove(column, count); + endRemoveColumns(); + return true; +} + +void QTableModel::setItem(int row, int column, QTableWidgetItem *item) +{ + int i = tableIndex(row, column); + if (i < 0 || i >= tableItems.count()) + return; + QTableWidgetItem *oldItem = tableItems.at(i); + if (item == oldItem) + return; + + // remove old + if (oldItem) + oldItem->view = 0; + delete tableItems.at(i); + + QTableWidget *view = qobject_cast(QObject::parent()); + + // set new + if (item) + item->d->id = i; + tableItems[i] = item; + + if (view && view->isSortingEnabled() + && view->horizontalHeader()->sortIndicatorSection() == column) { + // sorted insertion + Qt::SortOrder order = view->horizontalHeader()->sortIndicatorOrder(); + QVector colItems = columnItems(column); + if (row < colItems.count()) + colItems.remove(row); + int sortedRow; + if (item == 0) { + // move to after all non-0 (sortable) items + sortedRow = colItems.count(); + } else { + QVector::iterator it; + it = sortedInsertionIterator(colItems.begin(), colItems.end(), order, item); + sortedRow = qMax((int)(it - colItems.begin()), 0); + } + if (sortedRow != row) { + emit layoutAboutToBeChanged(); + // move the items @ row to sortedRow + int cc = columnCount(); + QVector rowItems(cc); + for (int j = 0; j < cc; ++j) + rowItems[j] = tableItems.at(tableIndex(row, j)); + tableItems.remove(tableIndex(row, 0), cc); + tableItems.insert(tableIndex(sortedRow, 0), cc, 0); + for (int j = 0; j < cc; ++j) + tableItems[tableIndex(sortedRow, j)] = rowItems.at(j); + QTableWidgetItem *header = verticalHeaderItems.at(row); + verticalHeaderItems.remove(row); + verticalHeaderItems.insert(sortedRow, header); + // update persistent indexes + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + updateRowIndexes(newPersistentIndexes, row, sortedRow); + changePersistentIndexList(oldPersistentIndexes, + newPersistentIndexes); + + emit layoutChanged(); + return; + } + } + QModelIndex idx = QAbstractTableModel::index(row, column); + emit dataChanged(idx, idx); +} + +QTableWidgetItem *QTableModel::takeItem(int row, int column) +{ + long i = tableIndex(row, column); + QTableWidgetItem *itm = tableItems.value(i); + if (itm) { + itm->view = 0; + itm->d->id = -1; + tableItems[i] = 0; + QModelIndex ind = index(itm); + emit dataChanged(ind, ind); + } + return itm; +} + +QTableWidgetItem *QTableModel::item(int row, int column) const +{ + return tableItems.value(tableIndex(row, column)); +} + +QTableWidgetItem *QTableModel::item(const QModelIndex &index) const +{ + if (!isValid(index)) + return 0; + return tableItems.at(tableIndex(index.row(), index.column())); +} + +void QTableModel::removeItem(QTableWidgetItem *item) +{ + int i = tableItems.indexOf(item); + if (i != -1) { + tableItems[i] = 0; + QModelIndex idx = index(item); + emit dataChanged(idx, idx); + return; + } + + i = verticalHeaderItems.indexOf(item); + + if (i != -1) { + verticalHeaderItems[i] = 0; + emit headerDataChanged(Qt::Vertical, i, i); + return; + } + i = horizontalHeaderItems.indexOf(item); + if (i != -1) { + horizontalHeaderItems[i] = 0; + emit headerDataChanged(Qt::Horizontal, i, i); + return; + } +} + +void QTableModel::setHorizontalHeaderItem(int section, QTableWidgetItem *item) +{ + if (section < 0 || section >= horizontalHeaderItems.count()) + return; + QTableWidgetItem *oldItem = horizontalHeaderItems.at(section); + if (item == oldItem) + return; + + if (oldItem) + oldItem->view = 0; + delete oldItem; + + QTableWidget *view = qobject_cast(QObject::parent()); + + if (item) { + item->view = view; + item->itemFlags = Qt::ItemFlags(int(item->itemFlags)|ItemIsHeaderItem); + } + horizontalHeaderItems[section] = item; + emit headerDataChanged(Qt::Horizontal, section, section); +} + +void QTableModel::setVerticalHeaderItem(int section, QTableWidgetItem *item) +{ + if (section < 0 || section >= verticalHeaderItems.count()) + return; + QTableWidgetItem *oldItem = verticalHeaderItems.at(section); + if (item == oldItem) + return; + + if (oldItem) + oldItem->view = 0; + delete oldItem; + + QTableWidget *view = qobject_cast(QObject::parent()); + + if (item) { + item->view = view; + item->itemFlags = Qt::ItemFlags(int(item->itemFlags)|ItemIsHeaderItem); + } + verticalHeaderItems[section] = item; + emit headerDataChanged(Qt::Vertical, section, section); +} + +QTableWidgetItem *QTableModel::takeHorizontalHeaderItem(int section) +{ + if (section < 0 || section >= horizontalHeaderItems.count()) + return 0; + QTableWidgetItem *itm = horizontalHeaderItems.at(section); + if (itm) { + itm->view = 0; + itm->itemFlags &= ~ItemIsHeaderItem; + horizontalHeaderItems[section] = 0; + } + return itm; +} + +QTableWidgetItem *QTableModel::takeVerticalHeaderItem(int section) +{ + if (section < 0 || section >= verticalHeaderItems.count()) + return 0; + QTableWidgetItem *itm = verticalHeaderItems.at(section); + if (itm) { + itm->view = 0; + itm->itemFlags &= ~ItemIsHeaderItem; + verticalHeaderItems[section] = 0; + } + return itm; +} + +QTableWidgetItem *QTableModel::horizontalHeaderItem(int section) +{ + return horizontalHeaderItems.value(section); +} + +QTableWidgetItem *QTableModel::verticalHeaderItem(int section) +{ + return verticalHeaderItems.value(section); +} + +QModelIndex QTableModel::index(const QTableWidgetItem *item) const +{ + if (!item) + return QModelIndex(); + int i = -1; + const int id = item->d->id; + if (id >= 0 && id < tableItems.count() && tableItems.at(id) == item) { + i = id; + } else { // we need to search for the item + i = tableItems.indexOf(const_cast(item)); + if (i == -1) // not found + return QModelIndex(); + } + int row = i / columnCount(); + int col = i % columnCount(); + return QAbstractTableModel::index(row, col); +} + +void QTableModel::setRowCount(int rows) +{ + int rc = verticalHeaderItems.count(); + if (rows < 0 || rc == rows) + return; + if (rc < rows) + insertRows(qMax(rc, 0), rows - rc); + else + removeRows(qMax(rows, 0), rc - rows); +} + +void QTableModel::setColumnCount(int columns) +{ + int cc = horizontalHeaderItems.count(); + if (columns < 0 || cc == columns) + return; + if (cc < columns) + insertColumns(qMax(cc, 0), columns - cc); + else + removeColumns(qMax(columns, 0), cc - columns); +} + +int QTableModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : verticalHeaderItems.count(); +} + +int QTableModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : horizontalHeaderItems.count(); +} + +QVariant QTableModel::data(const QModelIndex &index, int role) const +{ + QTableWidgetItem *itm = item(index); + if (itm) + return itm->data(role); + return QVariant(); +} + +bool QTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + QTableWidgetItem *itm = item(index); + if (itm) { + itm->setData(role, value); + return true; + } + + // don't create dummy table items for empty values + if (!value.isValid()) + return false; + + QTableWidget *view = qobject_cast(QObject::parent()); + if (!view) + return false; + + itm = createItem(); + itm->setData(role, value); + view->setItem(index.row(), index.column(), itm); + return true; +} + +QMap QTableModel::itemData(const QModelIndex &index) const +{ + QMap roles; + QTableWidgetItem *itm = item(index); + if (itm) { + for (int i = 0; i < itm->values.count(); ++i) { + roles.insert(itm->values.at(i).role, + itm->values.at(i).value); + } + } + return roles; +} + +// reimplemented to ensure that only one dataChanged() signal is emitted +bool QTableModel::setItemData(const QModelIndex &index, const QMap &roles) +{ + if (!index.isValid()) + return false; + + QTableWidget *view = qobject_cast(QObject::parent()); + QTableWidgetItem *itm = item(index); + if (itm) { + itm->view = 0; // prohibits item from calling itemChanged() + bool changed = false; + for (QMap::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) { + if (itm->data(it.key()) != it.value()) { + itm->setData(it.key(), it.value()); + changed = true; + } + } + itm->view = view; + if (changed) + itemChanged(itm); + return true; + } + + if (!view) + return false; + + itm = createItem(); + for (QMap::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) + itm->setData(it.key(), it.value()); + view->setItem(index.row(), index.column(), itm); + return true; +} + +Qt::ItemFlags QTableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + if (QTableWidgetItem *itm = item(index)) + return itm->flags(); + return (Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled); +} + +void QTableModel::sort(int column, Qt::SortOrder order) +{ + QVector > sortable; + QVector unsortable; + + sortable.reserve(rowCount()); + unsortable.reserve(rowCount()); + + for (int row = 0; row < rowCount(); ++row) { + if (QTableWidgetItem *itm = item(row, column)) + sortable.append(QPair(itm, row)); + else + unsortable.append(row); + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sortable.begin(), sortable.end(), compare); + + QVector sorted_table(tableItems.count()); + QModelIndexList from; + QModelIndexList to; + for (int i = 0; i < rowCount(); ++i) { + int r = (i < sortable.count() + ? sortable.at(i).second + : unsortable.at(i - sortable.count())); + for (int c = 0; c < columnCount(); ++c) { + sorted_table[tableIndex(i, c)] = item(r, c); + from.append(createIndex(r, c, 0)); + to.append(createIndex(i, c, 0)); + } + } + + emit layoutAboutToBeChanged(); + + tableItems = sorted_table; + changePersistentIndexList(from, to); // ### slow + + emit layoutChanged(); +} + +/* + \internal + + Ensures that rows in the interval [start, end] are + sorted according to the contents of column \a column + and the given sort \a order. +*/ +void QTableModel::ensureSorted(int column, Qt::SortOrder order, + int start, int end) +{ + int count = end - start + 1; + QVector < QPair > sorting; + sorting.reserve(count); + for (int row = start; row <= end; ++row) { + QTableWidgetItem *itm = item(row, column); + if (itm == 0) { + // no more sortable items (all 0-items are + // at the end of the table when it is sorted) + break; + } + sorting.append(QPair(itm, row)); + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes = persistentIndexList(); + QModelIndexList newPersistentIndexes = oldPersistentIndexes; + QVector newTable = tableItems; + QVector newVertical = verticalHeaderItems; + QVector colItems = columnItems(column); + QVector::iterator vit = colItems.begin(); + bool changed = false; + for (int i = 0; i < sorting.count(); ++i) { + int oldRow = sorting.at(i).second; + QTableWidgetItem *item = colItems.at(oldRow); + colItems.remove(oldRow); + vit = sortedInsertionIterator(vit, colItems.end(), order, item); + int newRow = qMax((int)(vit - colItems.begin()), 0); + if ((newRow < oldRow) && !(*item < *colItems.at(oldRow - 1)) && !(*colItems.at(oldRow - 1) < *item)) + newRow = oldRow; + vit = colItems.insert(vit, item); + if (newRow != oldRow) { + changed = true; + // move the items @ oldRow to newRow + int cc = columnCount(); + QVector rowItems(cc); + for (int j = 0; j < cc; ++j) + rowItems[j] = newTable.at(tableIndex(oldRow, j)); + newTable.remove(tableIndex(oldRow, 0), cc); + newTable.insert(tableIndex(newRow, 0), cc, 0); + for (int j = 0; j < cc; ++j) + newTable[tableIndex(newRow, j)] = rowItems.at(j); + QTableWidgetItem *header = newVertical.at(oldRow); + newVertical.remove(oldRow); + newVertical.insert(newRow, header); + // update persistent indexes + updateRowIndexes(newPersistentIndexes, oldRow, newRow); + // the index of the remaining rows may have changed + for (int j = i + 1; j < sorting.count(); ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + } + } + + if (changed) { + emit layoutAboutToBeChanged(); + tableItems = newTable; + verticalHeaderItems = newVertical; + changePersistentIndexList(oldPersistentIndexes, + newPersistentIndexes); + emit layoutChanged(); + } +} + +/* + \internal + + Returns the non-0 items in column \a column. +*/ +QVector QTableModel::columnItems(int column) const +{ + QVector items; + int rc = rowCount(); + items.reserve(rc); + for (int row = 0; row < rc; ++row) { + QTableWidgetItem *itm = item(row, column); + if (itm == 0) { + // no more sortable items (all 0-items are + // at the end of the table when it is sorted) + break; + } + items.append(itm); + } + return items; +} + +/* + \internal + + Adjusts the row of each index in \a indexes if necessary, given + that a row of items has been moved from row \a movedFrom to row + \a movedTo. +*/ +void QTableModel::updateRowIndexes(QModelIndexList &indexes, + int movedFromRow, int movedToRow) +{ + QModelIndexList::iterator it; + for (it = indexes.begin(); it != indexes.end(); ++it) { + int oldRow = (*it).row(); + int newRow = oldRow; + if (oldRow == movedFromRow) + newRow = movedToRow; + else if (movedFromRow < oldRow && movedToRow >= oldRow) + newRow = oldRow - 1; + else if (movedFromRow > oldRow && movedToRow <= oldRow) + newRow = oldRow + 1; + if (newRow != oldRow) + *it = index(newRow, (*it).column(), (*it).parent()); + } +} + +/* + \internal + + Returns an iterator to the item where \a item should be + inserted in the interval (\a begin, \a end) according to + the given sort \a order. +*/ +QVector::iterator QTableModel::sortedInsertionIterator( + const QVector::iterator &begin, + const QVector::iterator &end, + Qt::SortOrder order, QTableWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QTableModelLessThan()); + return qLowerBound(begin, end, item, QTableModelGreaterThan()); +} + +bool QTableModel::itemLessThan(const QPair &left, + const QPair &right) +{ + return *(left.first) < *(right.first); +} + +bool QTableModel::itemGreaterThan(const QPair &left, + const QPair &right) +{ + return (*(right.first) < *(left .first)); +} + +QVariant QTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section < 0) + return QVariant(); + + QTableWidgetItem *itm = 0; + if (orientation == Qt::Horizontal && section < horizontalHeaderItems.count()) + itm = horizontalHeaderItems.at(section); + else if (orientation == Qt::Vertical && section < verticalHeaderItems.count()) + itm = verticalHeaderItems.at(section); + else + return QVariant(); // section is out of bounds + + if (itm) + return itm->data(role); + if (role == Qt::DisplayRole) + return section + 1; + return QVariant(); +} + +bool QTableModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + if (section < 0 || + (orientation == Qt::Horizontal && horizontalHeaderItems.size() <= section) || + (orientation == Qt::Vertical && verticalHeaderItems.size() <= section)) + return false; + + QTableWidgetItem *itm = 0; + if (orientation == Qt::Horizontal) + itm = horizontalHeaderItems.at(section); + else + itm = verticalHeaderItems.at(section); + if (itm) { + itm->setData(role, value); + return true; + } + return false; +} + +bool QTableModel::isValid(const QModelIndex &index) const +{ + return (index.isValid() + && index.row() < verticalHeaderItems.count() + && index.column() < horizontalHeaderItems.count()); +} + +void QTableModel::clear() +{ + for (int j = 0; j < verticalHeaderItems.count(); ++j) { + if (verticalHeaderItems.at(j)) { + verticalHeaderItems.at(j)->view = 0; + delete verticalHeaderItems.at(j); + verticalHeaderItems[j] = 0; + } + } + for (int k = 0; k < horizontalHeaderItems.count(); ++k) { + if (horizontalHeaderItems.at(k)) { + horizontalHeaderItems.at(k)->view = 0; + delete horizontalHeaderItems.at(k); + horizontalHeaderItems[k] = 0; + } + } + clearContents(); +} + +void QTableModel::clearContents() +{ + for (int i = 0; i < tableItems.count(); ++i) { + if (tableItems.at(i)) { + tableItems.at(i)->view = 0; + delete tableItems.at(i); + tableItems[i] = 0; + } + } + reset(); +} + +void QTableModel::itemChanged(QTableWidgetItem *item) +{ + if (!item) + return; + if (item->flags() & ItemIsHeaderItem) { + int row = verticalHeaderItems.indexOf(item); + if (row >= 0) { + emit headerDataChanged(Qt::Vertical, row, row); + } else { + int column = horizontalHeaderItems.indexOf(item); + if (column >= 0) + emit headerDataChanged(Qt::Horizontal, column, column); + } + } else { + QModelIndex idx = index(item); + if (idx.isValid()) + emit dataChanged(idx, idx); + } +} + +QTableWidgetItem* QTableModel::createItem() const +{ + return prototype ? prototype->clone() : new QTableWidgetItem; +} + +const QTableWidgetItem *QTableModel::itemPrototype() const +{ + return prototype; +} + +void QTableModel::setItemPrototype(const QTableWidgetItem *item) +{ + if (prototype != item) { + delete prototype; + prototype = item; + } +} + +QStringList QTableModel::mimeTypes() const +{ + const QTableWidget *view = qobject_cast(QObject::parent()); + return (view ? view->mimeTypes() : QStringList()); +} + +QMimeData *QTableModel::internalMimeData() const +{ + return QAbstractTableModel::mimeData(cachedIndexes); +} + +QMimeData *QTableModel::mimeData(const QModelIndexList &indexes) const +{ + QList items; + for (int i = 0; i < indexes.count(); ++i) + items << item(indexes.at(i)); + const QTableWidget *view = qobject_cast(QObject::parent()); + + // cachedIndexes is a little hack to avoid copying from QModelIndexList to + // QList and back again in the view + cachedIndexes = indexes; + QMimeData *mimeData = (view ? view->mimeData(items) : 0); + cachedIndexes.clear(); + return mimeData; +} + +bool QTableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row , int column, const QModelIndex &index) +{ + if (index.isValid()) { + row = index.row(); + column = index.column(); + }else if (row == -1 || column == -1) { // The user dropped outside the table. + row = rowCount(); + column = 0; + } + + QTableWidget *view = qobject_cast(QObject::parent()); + return (view ? view->dropMimeData(row, column, data, action) : false); +} + +Qt::DropActions QTableModel::supportedDropActions() const +{ + const QTableWidget *view = qobject_cast(QObject::parent()); + return (view ? view->supportedDropActions() : Qt::DropActions(Qt::IgnoreAction)); +} + +/*! + \class QTableWidgetSelectionRange + + \brief The QTableWidgetSelectionRange class provides a way to interact with + selection in a model without using model indexes and a selection model. + + \ingroup model-view + + The QTableWidgetSelectionRange class stores the top left and bottom + right rows and columns of a selection range in a table. The + selections in the table may consist of several selection ranges. + + \note If the item within the selection range is marked as not selectable, + e.g., \c{itemFlags() & Qt::ItemIsSelectable == 0} then it will not appear + in the selection range. + + \sa QTableWidget +*/ + +/*! + Constructs an table selection range, i.e. a range + whose rowCount() and columnCount() are 0. +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange() + : top(-1), left(-1), bottom(-2), right(-2) +{ +} + +/*! + Constructs the table selection range from the given \a top, \a + left, \a bottom and \a right table rows and columns. + + \sa topRow(), leftColumn(), bottomRow(), rightColumn() +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange(int top, int left, int bottom, int right) + : top(top), left(left), bottom(bottom), right(right) +{ +} + +/*! + Constructs a the table selection range by copying the given \a + other table selection range. +*/ +QTableWidgetSelectionRange::QTableWidgetSelectionRange(const QTableWidgetSelectionRange &other) + : top(other.top), left(other.left), bottom(other.bottom), right(other.right) +{ +} + +/*! + Destroys the table selection range. +*/ +QTableWidgetSelectionRange::~QTableWidgetSelectionRange() +{ +} + +/*! + \fn int QTableWidgetSelectionRange::topRow() const + + Returns the top row of the range. + + \sa bottomRow(), leftColumn(), rowCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::bottomRow() const + + Returns the bottom row of the range. + + \sa topRow(), rightColumn(), rowCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::leftColumn() const + + Returns the left column of the range. + + \sa rightColumn(), topRow(), columnCount() +*/ + +/*! + \fn int QTableWidgetSelectionRange::rightColumn() const + + Returns the right column of the range. + + \sa leftColumn(), bottomRow(), columnCount() +*/ + +/*! + \since 4.1 + \fn int QTableWidgetSelectionRange::rowCount() const + + Returns the number of rows in the range. + + This is equivalent to bottomRow() - topRow() + 1. + + \sa columnCount(), topRow(), bottomRow() +*/ + +/*! + \since 4.1 + \fn int QTableWidgetSelectionRange::columnCount() const + + Returns the number of columns in the range. + + This is equivalent to rightColumn() - leftColumn() + 1. + + \sa rowCount(), leftColumn(), rightColumn() +*/ + +/*! + \class QTableWidgetItem + \brief The QTableWidgetItem class provides an item for use with the + QTableWidget class. + + \ingroup model-view + + Table items are used to hold pieces of information for table widgets. + Items usually contain text, icons, or checkboxes + + The QTableWidgetItem class is a convenience class that replaces the + \c QTableItem class in Qt 3. It provides an item for use with + the QTableWidget class. + + Top-level items are constructed without a parent then inserted at the + position specified by a pair of row and column numbers: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 3 + + Each item can have its own background brush which is set with + the setBackground() function. The current background brush can be + found with background(). + The text label for each item can be rendered with its own font and brush. + These are specified with the setFont() and setForeground() functions, + and read with font() and foreground(). + + By default, items are enabled, editable, selectable, checkable, and can be + used both as the source of a drag and drop operation and as a drop target. + Each item's flags can be changed by calling setFlags() with the appropriate + value (see \l{Qt::ItemFlags}). Checkable items can be checked and unchecked + with the setCheckState() function. The corresponding checkState() function + indicates whether the item is currently checked. + + \section1 Subclassing + + When subclassing QTableWidgetItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from standard + items. The constructors for subclasses that require this feature need to + call the base class constructor with a new type value equal to or greater + than \l UserType. + + \sa QTableWidget, {Model/View Programming}, QListWidgetItem, QTreeWidgetItem +*/ + +/*! + \fn int QTableWidgetItem::row() const + \since 4.2 + + Returns the row of the item in the table. + If the item is not in a table, this function will return -1. + + \sa column() +*/ + +/*! + \fn int QTableWidgetItem::column() const + \since 4.2 + + Returns the column of the item in the table. + If the item is not in a table, this function will return -1. + + \sa row() +*/ + +/*! + \fn void QTableWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() +*/ + +/*! + \fn bool QTableWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected, otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn QSize QTableWidgetItem::sizeHint() const + \since 4.1 + + Returns the size hint set for the table item. +*/ + +/*! + \fn void QTableWidgetItem::setSizeHint(const QSize &size) + \since 4.1 + + Sets the size hint for the table item to be \a size. + If no size hint is set, the item delegate will compute the + size hint based on the item data. +*/ + +/*! + \fn Qt::CheckState QTableWidgetItem::checkState() const + + Returns the checked state of the table item. + + \sa flags() +*/ + +/*! + \fn void QTableWidgetItem::setCheckState(Qt::CheckState state) + + Sets the check state of the table item to be \a state. +*/ + +/*! + \fn QTableWidget *QTableWidgetItem::tableWidget() const + + Returns the table widget that contains the item. +*/ + +/*! + \fn Qt::ItemFlags QTableWidgetItem::flags() const + + Returns the flags used to describe the item. These determine whether + the item can be checked, edited, and selected. + + \sa setFlags() +*/ + +/*! + \fn void QTableWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the flags for the item to the given \a flags. These determine whether + the item can be selected or modified. + + \sa flags() +*/ +void QTableWidgetItem::setFlags(Qt::ItemFlags aflags) +{ + itemFlags = aflags; + if (QTableModel *model = (view ? qobject_cast(view->model()) : 0)) + model->itemChanged(this); +} + + +/*! + \fn QString QTableWidgetItem::text() const + + Returns the item's text. + + \sa setText() +*/ + +/*! + \fn void QTableWidgetItem::setText(const QString &text) + + Sets the item's text to the \a text specified. + + \sa text() setFont() setForeground() +*/ + +/*! + \fn QIcon QTableWidgetItem::icon() const + + Returns the item's icon. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QTableWidgetItem::setIcon(const QIcon &icon) + + Sets the item's icon to the \a icon specified. + + \sa icon(), setText(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QTableWidgetItem::statusTip() const + + Returns the item's status tip. + + \sa setStatusTip() +*/ + +/*! + \fn void QTableWidgetItem::setStatusTip(const QString &statusTip) + + Sets the status tip for the table item to the text specified by + \a statusTip. QTableWidget mouse tracking needs to be enabled for this + feature to work. + + \sa statusTip() setToolTip() setWhatsThis() +*/ + +/*! + \fn QString QTableWidgetItem::toolTip() const + + Returns the item's tooltip. + + \sa setToolTip() +*/ + +/*! + \fn void QTableWidgetItem::setToolTip(const QString &toolTip) + + Sets the item's tooltip to the string specified by \a toolTip. + + \sa toolTip() setStatusTip() setWhatsThis() +*/ + +/*! + \fn QString QTableWidgetItem::whatsThis() const + + Returns the item's "What's This?" help. + + \sa setWhatsThis() +*/ + +/*! + \fn void QTableWidgetItem::setWhatsThis(const QString &whatsThis) + + Sets the item's "What's This?" help to the string specified by \a whatsThis. + + \sa whatsThis() setStatusTip() setToolTip() +*/ + +/*! + \fn QFont QTableWidgetItem::font() const + + Returns the font used to render the item's text. + + \sa setFont() +*/ + +/*! + \fn void QTableWidgetItem::setFont(const QFont &font) + + Sets the font used to display the item's text to the given \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QColor QTableWidgetItem::backgroundColor() const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn void QTableWidgetItem::setBackgroundColor(const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn QBrush QTableWidgetItem::background() const + \since 4.2 + + Returns the brush used to render the item's background. + + \sa foreground() +*/ + +/*! + \fn void QTableWidgetItem::setBackground(const QBrush &brush) + \since 4.2 + + Sets the item's background brush to the specified \a brush. + + \sa setForeground() +*/ + +/*! + \fn QColor QTableWidgetItem::textColor() const + \obsolete + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn void QTableWidgetItem::setTextColor(const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn QBrush QTableWidgetItem::foreground() const + \since 4.2 + + Returns the brush used to render the item's foreground (e.g. text). + + \sa background() +*/ + +/*! + \fn void QTableWidgetItem::setForeground(const QBrush &brush) + \since 4.2 + + Sets the item's foreground brush to the specified \a brush. + + \sa setBackground() +*/ + +/*! + \fn int QTableWidgetItem::textAlignment() const + + Returns the text alignment for the item's text. + + \sa Qt::Alignment +*/ + +/*! + \fn void QTableWidgetItem::setTextAlignment(int alignment) + + Sets the text alignment for the item's text to the \a alignment + specified. + + \sa Qt::Alignment +*/ + +/*! + Constructs a table item of the specified \a type that does not belong + to any table. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ +} + +/*! + Constructs a table item with the given \a text. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(const QString &text, int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + setData(Qt::DisplayRole, text); +} + +/*! + Constructs a table item with the given \a icon and \a text. + + \sa type() +*/ +QTableWidgetItem::QTableWidgetItem(const QIcon &icon, const QString &text, int type) + : rtti(type), view(0), d(new QTableWidgetItemPrivate(this)), + itemFlags(Qt::ItemIsEditable + |Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + setData(Qt::DecorationRole, icon); + setData(Qt::DisplayRole, text); +} + +/*! + Destroys the table item. +*/ +QTableWidgetItem::~QTableWidgetItem() +{ + if (QTableModel *model = (view ? qobject_cast(view->model()) : 0)) + model->removeItem(this); + view = 0; + delete d; +} + +/*! + Creates a copy of the item. +*/ +QTableWidgetItem *QTableWidgetItem::clone() const +{ + return new QTableWidgetItem(*this); +} + +/*! + Sets the item's data for the given \a role to the specified \a value. + + \sa Qt::ItemDataRole, data() +*/ +void QTableWidgetItem::setData(int role, const QVariant &value) +{ + bool found = false; + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < values.count(); ++i) { + if (values.at(i).role == role) { + if (values[i].value == value) + return; + + values[i].value = value; + found = true; + break; + } + } + if (!found) + values.append(QWidgetItemData(role, value)); + if (QTableModel *model = (view ? qobject_cast(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + Returns the item's data for the given \a role. +*/ +QVariant QTableWidgetItem::data(int role) const +{ + role = (role == Qt::EditRole ? Qt::DisplayRole : role); + for (int i = 0; i < values.count(); ++i) + if (values.at(i).role == role) + return values.at(i).value; + return QVariant(); +} + +/*! + Returns true if the item is less than the \a other item; otherwise returns + false. +*/ +bool QTableWidgetItem::operator<(const QTableWidgetItem &other) const +{ + const QVariant v1 = data(Qt::DisplayRole), v2 = other.data(Qt::DisplayRole); + return QAbstractItemModelPrivate::variantLessThan(v1, v2); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. + + \sa write() +*/ +void QTableWidgetItem::read(QDataStream &in) +{ + in >> values; +} + +/*! + Writes the item to stream \a out. + + \sa read() +*/ +void QTableWidgetItem::write(QDataStream &out) const +{ + out << values; +} + +/*! + \relates QTableWidgetItem + + Reads a table widget item from stream \a in into \a item. + + This operator uses QTableWidgetItem::read(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &in, QTableWidgetItem &item) +{ + item.read(in); + return in; +} + +/*! + \relates QTableWidgetItem + + Writes the table widget item \a item to stream \a out. + + This operator uses QTableWidgetItem::write(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QTableWidgetItem &item) +{ + item.write(out); + return out; +} + +#endif // QT_NO_DATASTREAM + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and tableWidget() + are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTableWidgetItem::QTableWidgetItem(const QTableWidgetItem &other) + : rtti(Type), values(other.values), view(0), + d(new QTableWidgetItemPrivate(this)), + itemFlags(other.itemFlags) +{ +} + +/*! + Assigns \a other's data and flags to this item. Note that type() + and tableWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTableWidgetItem &QTableWidgetItem::operator=(const QTableWidgetItem &other) +{ + values = other.values; + itemFlags = other.itemFlags; + return *this; +} + +/*! + \class QTableWidget + \brief The QTableWidget class provides an item-based table view with a default model. + + \ingroup model-view + + + Table widgets provide standard table display facilities for applications. + The items in a QTableWidget are provided by QTableWidgetItem. + + If you want a table that uses your own data model you should + use QTableView rather than this class. + + Table widgets can be constructed with the required numbers of rows and + columns: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 0 + + Alternatively, tables can be constructed without a given size and resized + later: + + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 0 + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 1 + + Items are created ouside the table (with no parent widget) and inserted + into the table with setItem(): + + \snippet doc/src/snippets/qtablewidget-resizing/mainwindow.cpp 2 + + If you want to enable sorting in your table widget, do so after you + have populated it with items, otherwise sorting may interfere with + the insertion order (see setItem() for details). + + Tables can be given both horizontal and vertical headers. The simplest way + to create the headers is to supply a list of strings to the + setHorizontalHeaderLabels() and setVerticalHeaderLabels() functions. These + will provide simple textual headers for the table's columns and rows. + More sophisticated headers can be created from existing table items + that are usually constructed outside the table. For example, we can + construct a table item with an icon and aligned text, and use it as the + header for a particular column: + + \snippet doc/src/snippets/qtablewidget-using/mainwindow.cpp 2 + + The number of rows in the table can be found with rowCount(), and the + number of columns with columnCount(). The table can be cleared with the + clear() function. + + \table 100% + \row \o \inlineimage windowsxp-tableview.png Screenshot of a Windows XP style table widget + \o \inlineimage macintosh-tableview.png Screenshot of a Macintosh style table widget + \o \inlineimage plastique-tableview.png Screenshot of a Plastique style table widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} table widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} table widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} table widget. + \endtable + + \sa QTableWidgetItem, QTableView, {Model/View Programming} +*/ + +/*! + \property QTableWidget::rowCount + \brief the number of rows in the table + + By default, for a table constructed without row and column counts, + this property contains a value of 0. +*/ + +/*! + \property QTableWidget::columnCount + \brief the number of columns in the table + + By default, for a table constructed without row and column counts, + this property contains a value of 0. +*/ + +void QTableWidgetPrivate::setup() +{ + Q_Q(QTableWidget); + // view signals + QObject::connect(q, SIGNAL(pressed(QModelIndex)), q, SLOT(_q_emitItemPressed(QModelIndex))); + QObject::connect(q, SIGNAL(clicked(QModelIndex)), q, SLOT(_q_emitItemClicked(QModelIndex))); + QObject::connect(q, SIGNAL(doubleClicked(QModelIndex)), + q, SLOT(_q_emitItemDoubleClicked(QModelIndex))); + QObject::connect(q, SIGNAL(activated(QModelIndex)), q, SLOT(_q_emitItemActivated(QModelIndex))); + QObject::connect(q, SIGNAL(entered(QModelIndex)), q, SLOT(_q_emitItemEntered(QModelIndex))); + // model signals + QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitItemChanged(QModelIndex))); + // selection signals + QObject::connect(q->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + q, SIGNAL(itemSelectionChanged())); + // sorting + QObject::connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + q, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)), q, SLOT(_q_sort())); +} + +void QTableWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemPressed(item); + emit q->cellPressed(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemClicked(item); + emit q->cellClicked(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemDoubleClicked(item); + emit q->cellDoubleClicked(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemActivated(item); + emit q->cellActivated(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemEntered(item); + emit q->cellEntered(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QTableWidget); + if (QTableWidgetItem *item = tableModel()->item(index)) + emit q->itemChanged(item); + emit q->cellChanged(index.row(), index.column()); +} + +void QTableWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QTableWidget); + QTableWidgetItem *currentItem = tableModel()->item(current); + QTableWidgetItem *previousItem = tableModel()->item(previous); + if (currentItem || previousItem) + emit q->currentItemChanged(currentItem, previousItem); + emit q->currentCellChanged(current.row(), current.column(), previous.row(), previous.column()); +} + +void QTableWidgetPrivate::_q_sort() +{ + if (sortingEnabled) { + int column = horizontalHeader->sortIndicatorSection(); + Qt::SortOrder order = horizontalHeader->sortIndicatorOrder(); + model->sort(column, order); + } +} + +void QTableWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid()) { + int column = horizontalHeader->sortIndicatorSection(); + if (column >= topLeft.column() && column <= bottomRight.column()) { + Qt::SortOrder order = horizontalHeader->sortIndicatorOrder(); + tableModel()->ensureSorted(column, order, topLeft.row(), bottomRight.row()); + } + } +} + +/*! + \fn void QTableWidget::itemPressed(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is pressed. + The \a item specified is the item that was pressed. +*/ + +/*! + \fn void QTableWidget::itemClicked(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is clicked. + The \a item specified is the item that was clicked. +*/ + +/*! + \fn void QTableWidget::itemDoubleClicked(QTableWidgetItem *item) + + This signal is emitted whenever an item in the table is double + clicked. The \a item specified is the item that was double clicked. +*/ + +/*! + \fn void QTableWidget::itemActivated(QTableWidgetItem *item) + + This signal is emitted when the specified \a item has been activated +*/ + +/*! + \fn void QTableWidget::itemEntered(QTableWidgetItem *item) + + This signal is emitted when the mouse cursor enters an item. The + \a item is the item entered. + + This signal is only emitted when mouseTracking is turned on, or when a + mouse button is pressed while moving into an item. +*/ + +/*! + \fn void QTableWidget::itemChanged(QTableWidgetItem *item) + + This signal is emitted whenever the data of \a item has changed. +*/ + +/*! + \fn void QTableWidget::currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) + + This signal is emitted whenever the current item changes. The \a + previous item is the item that previously had the focus, \a + current is the new current item. +*/ + +/*! + \fn void QTableWidget::itemSelectionChanged() + + This signal is emitted whenever the selection changes. + + \sa selectedItems() QTableWidgetItem::isSelected() +*/ + + +/*! + \since 4.1 + \fn void QTableWidget::cellPressed(int row, int column) + + This signal is emitted whenever a cell in the table is pressed. + The \a row and \a column specified is the cell that was pressed. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellClicked(int row, int column) + + This signal is emitted whenever a cell in the table is clicked. + The \a row and \a column specified is the cell that was clicked. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellDoubleClicked(int row, int column) + + This signal is emitted whenever a cell in the table is double + clicked. The \a row and \a column specified is the cell that was + double clicked. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellActivated(int row, int column) + + This signal is emitted when the cell specified by \a row and \a column + has been activated +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellEntered(int row, int column) + + This signal is emitted when the mouse cursor enters a cell. The + cell is specified by \a row and \a column. + + This signal is only emitted when mouseTracking is turned on, or when a + mouse button is pressed while moving into an item. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::cellChanged(int row, int column) + + This signal is emitted whenever the data of the item in the cell + specified by \a row and \a column has changed. +*/ + +/*! + \since 4.1 + \fn void QTableWidget::currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) + + This signal is emitted whenever the current cell changes. The cell + specified by \a previousRow and \a previousColumn is the cell that + previously had the focus, the cell specified by \a currentRow and \a + currentColumn is the new current cell. +*/ + +/*! + \since 4.3 + \fn void QTableWidget::removeCellWidget(int row, int column) + + Removes the widget set on the cell indicated by \a row and \a column. +*/ + +/*! + \fn QTableWidgetItem *QTableWidget::itemAt(int ax, int ay) const + + Returns the item at the position equivalent to QPoint(\a{ax}, \a{ay}) in + the table widget's coordinate system, or returns 0 if the specified point + is not covered by an item in the table widget. + + \sa item() +*/ + +/*! + \enum QTableWidgetItem::ItemType + + This enum describes the types that are used to describe table widget items. + + \value Type The default type for table widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QTableWidgetItem subclasses to ensure that + custom items are treated specially. + + \sa type() +*/ + +/*! + \fn int QTableWidgetItem::type() const + + Returns the type passed to the QTableWidgetItem constructor. +*/ + +/*! + Creates a new table view with the given \a parent. +*/ +QTableWidget::QTableWidget(QWidget *parent) + : QTableView(*new QTableWidgetPrivate, parent) +{ + Q_D(QTableWidget); + QTableView::setModel(new QTableModel(0, 0, this)); + d->setup(); +} + +/*! + Creates a new table view with the given \a rows and \a columns, and with the given \a parent. +*/ +QTableWidget::QTableWidget(int rows, int columns, QWidget *parent) + : QTableView(*new QTableWidgetPrivate, parent) +{ + Q_D(QTableWidget); + QTableView::setModel(new QTableModel(rows, columns, this)); + d->setup(); +} + +/*! + Destroys this QTableWidget. +*/ +QTableWidget::~QTableWidget() +{ +} + +/*! + Sets the number of rows in this table's model to \a rows. If + this is less than rowCount(), the data in the unwanted rows + is discarded. + + \sa setColumnCount() +*/ +void QTableWidget::setRowCount(int rows) +{ + Q_D(QTableWidget); + d->tableModel()->setRowCount(rows); +} + +/*! + Returns the number of rows. +*/ + +int QTableWidget::rowCount() const +{ + Q_D(const QTableWidget); + return d->model->rowCount(); +} + +/*! + Sets the number of columns in this table's model to \a columns. If + this is less than columnCount(), the data in the unwanted columns + is discarded. + + \sa setRowCount() +*/ +void QTableWidget::setColumnCount(int columns) +{ + Q_D(QTableWidget); + d->tableModel()->setColumnCount(columns); +} + +/*! + Returns the number of columns. +*/ + +int QTableWidget::columnCount() const +{ + Q_D(const QTableWidget); + return d->model->columnCount(); +} + +/*! + Returns the row for the \a item. +*/ +int QTableWidget::row(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->tableModel()->index(item).row(); +} + +/*! + Returns the column for the \a item. +*/ +int QTableWidget::column(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->tableModel()->index(item).column(); +} + + +/*! + Returns the item for the given \a row and \a column if one has been set; otherwise + returns 0. + + \sa setItem() +*/ +QTableWidgetItem *QTableWidget::item(int row, int column) const +{ + Q_D(const QTableWidget); + return d->tableModel()->item(row, column); +} + +/*! + Sets the item for the given \a row and \a column to \a item. + + The table takes ownership of the item. + + Note that if sorting is enabled (see + \l{QTableView::sortingEnabled} {sortingEnabled}) and \a column is + the current sort column, the \a row will be moved to the sorted + position determined by \a item. + + If you want to set several items of a particular row (say, by + calling setItem() in a loop), you may want to turn off sorting + before doing so, and turn it back on afterwards; this will allow + you to use the same \a row argument for all items in the same row + (i.e. setItem() will not move the row). + + \sa item() takeItem() +*/ +void QTableWidget::setItem(int row, int column, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + if (item->view != 0) { + qWarning("QTableWidget: cannot insert an item that is already owned by another QTableWidget"); + } else { + item->view = this; + d->tableModel()->setItem(row, column, item); + } + } else { + delete takeItem(row, column); + } +} + +/*! + Removes the item at \a row and \a column from the table without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeItem(int row, int column) +{ + Q_D(QTableWidget); + QTableWidgetItem *item = d->tableModel()->takeItem(row, column); + if (item) + item->view = 0; + return item; +} + +/*! + Returns the vertical header item for row \a row. +*/ +QTableWidgetItem *QTableWidget::verticalHeaderItem(int row) const +{ + Q_D(const QTableWidget); + return d->tableModel()->verticalHeaderItem(row); +} + +/*! + Sets the vertical header item for row \a row to \a item. +*/ +void QTableWidget::setVerticalHeaderItem(int row, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + item->view = this; + d->tableModel()->setVerticalHeaderItem(row, item); + } else { + delete takeVerticalHeaderItem(row); + } +} + +/*! + \since 4.1 + Removes the vertical header item at \a row from the header without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeVerticalHeaderItem(int row) +{ + Q_D(QTableWidget); + QTableWidgetItem *itm = d->tableModel()->takeVerticalHeaderItem(row); + if (itm) + itm->view = 0; + return itm; +} + +/*! + Returns the horizontal header item for column, \a column, if one has been + set; otherwise returns 0. +*/ +QTableWidgetItem *QTableWidget::horizontalHeaderItem(int column) const +{ + Q_D(const QTableWidget); + return d->tableModel()->horizontalHeaderItem(column); +} + +/*! + Sets the horizontal header item for column \a column to \a item. +*/ +void QTableWidget::setHorizontalHeaderItem(int column, QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (item) { + item->view = this; + d->tableModel()->setHorizontalHeaderItem(column, item); + } else { + delete takeHorizontalHeaderItem(column); + } +} + +/*! + \since 4.1 + Removes the horizontal header item at \a column from the header without deleting it. +*/ +QTableWidgetItem *QTableWidget::takeHorizontalHeaderItem(int column) +{ + Q_D(QTableWidget); + QTableWidgetItem *itm = d->tableModel()->takeHorizontalHeaderItem(column); + if (itm) + itm->view = 0; + return itm; +} + +/*! + Sets the vertical header labels using \a labels. +*/ +void QTableWidget::setVerticalHeaderLabels(const QStringList &labels) +{ + Q_D(QTableWidget); + QTableModel *model = d->tableModel(); + QTableWidgetItem *item = 0; + for (int i = 0; i < model->rowCount() && i < labels.count(); ++i) { + item = model->verticalHeaderItem(i); + if (!item) { + item = model->createItem(); + setVerticalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + Sets the horizontal header labels using \a labels. +*/ +void QTableWidget::setHorizontalHeaderLabels(const QStringList &labels) +{ + Q_D(QTableWidget); + QTableModel *model = d->tableModel(); + QTableWidgetItem *item = 0; + for (int i = 0; i < model->columnCount() && i < labels.count(); ++i) { + item = model->horizontalHeaderItem(i); + if (!item) { + item = model->createItem(); + setHorizontalHeaderItem(i, item); + } + item->setText(labels.at(i)); + } +} + +/*! + Returns the row of the current item. + + \sa currentColumn(), setCurrentCell() +*/ +int QTableWidget::currentRow() const +{ + return currentIndex().row(); +} + +/*! + Returns the column of the current item. + + \sa currentRow(), setCurrentCell() +*/ +int QTableWidget::currentColumn() const +{ + return currentIndex().column(); +} + +/*! + Returns the current item. + + \sa setCurrentItem() +*/ +QTableWidgetItem *QTableWidget::currentItem() const +{ + Q_D(const QTableWidget); + return d->tableModel()->item(currentIndex()); +} + +/*! + Sets the current item to \a item. + + Unless the selection mode is \l{QAbstractItemView::}{NoSelection}, + the item is also be selected. + + \sa currentItem(), setCurrentCell() +*/ +void QTableWidget::setCurrentItem(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + setCurrentIndex(d->tableModel()->index(item)); +} + +/*! + \since 4.4 + + Sets the current item to be \a item, using the given \a command. + + \sa currentItem(), setCurrentCell() +*/ +void QTableWidget::setCurrentItem(QTableWidgetItem *item, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableWidget); + d->selectionModel->setCurrentIndex(d->tableModel()->index(item), command); +} + +/*! + \since 4.1 + + Sets the current cell to be the cell at position (\a row, \a + column). + + Depending on the current \l{QAbstractItemView::SelectionMode}{selection mode}, + the cell may also be selected. + + \sa setCurrentItem(), currentRow(), currentColumn() +*/ +void QTableWidget::setCurrentCell(int row, int column) +{ + setCurrentIndex(model()->index(row, column, QModelIndex())); +} + +/*! + \since 4.4 + + Sets the current cell to be the cell at position (\a row, \a + column), using the given \a command. + + \sa setCurrentItem(), currentRow(), currentColumn() +*/ +void QTableWidget::setCurrentCell(int row, int column, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTableWidget); + d->selectionModel->setCurrentIndex(model()->index(row, column, QModelIndex()), command); +} + +/*! + Sorts all the rows in the table widget based on \a column and \a order. +*/ +void QTableWidget::sortItems(int column, Qt::SortOrder order) +{ + Q_D(QTableWidget); + d->model->sort(column, order); + horizontalHeader()->setSortIndicator(column, order); +} + +/*! + \internal +*/ +void QTableWidget::setSortingEnabled(bool enable) +{ + QTableView::setSortingEnabled(enable); +} + +/*! + \internal +*/ +bool QTableWidget::isSortingEnabled() const +{ + return QTableView::isSortingEnabled(); +} + +/*! + Starts editing the \a item if it is editable. +*/ + +void QTableWidget::editItem(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + edit(d->tableModel()->index(item)); +} + +/*! + Opens an editor for the give \a item. The editor remains open after editing. + + \sa closePersistentEditor() +*/ +void QTableWidget::openPersistentEditor(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->tableModel()->index(item); + QAbstractItemView::openPersistentEditor(index); +} + +/*! + Closes the persistent editor for \a item. + + \sa openPersistentEditor() +*/ +void QTableWidget::closePersistentEditor(QTableWidgetItem *item) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->tableModel()->index(item); + QAbstractItemView::closePersistentEditor(index); +} + +/*! + \since 4.1 + + Returns the widget displayed in the cell in the given \a row and \a column. + + \note The table takes ownership of the widget. + + \sa setCellWidget() +*/ +QWidget *QTableWidget::cellWidget(int row, int column) const +{ + QModelIndex index = model()->index(row, column, QModelIndex()); + return QAbstractItemView::indexWidget(index); +} + +/*! + \since 4.1 + + Sets the given \a widget to be displayed in the cell in the given \a row + and \a column, passing the ownership of the widget to the table. + + If cell widget A is replaced with cell widget B, cell widget A will be + deleted. For example, in the code snippet below, the QLineEdit object will + be deleted. + + \snippet doc/src/snippets/code/src_gui_itemviews_qtablewidget.cpp 0 + + \sa cellWidget() +*/ +void QTableWidget::setCellWidget(int row, int column, QWidget *widget) +{ + QModelIndex index = model()->index(row, column, QModelIndex()); + QAbstractItemView::setIndexWidget(index, widget); +} + +/*! + Returns true if the \a item is selected, otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QTableWidgetItem::isSelected()} instead. +*/ + +bool QTableWidget::isItemSelected(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + QModelIndex index = d->tableModel()->index(item); + return selectionModel()->isSelected(index); +} + +/*! + Selects or deselects \a item depending on \a select. + + \obsolete + + This function is deprecated. Use \l{QTableWidgetItem::setSelected()} instead. +*/ +void QTableWidget::setItemSelected(const QTableWidgetItem *item, bool select) +{ + Q_D(QTableWidget); + QModelIndex index = d->tableModel()->index(item); + selectionModel()->select(index, select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); +} + +/*! + Selects or deselects the \a range depending on \a select. +*/ +void QTableWidget::setRangeSelected(const QTableWidgetSelectionRange &range, bool select) +{ + if (!model()->hasIndex(range.topRow(), range.leftColumn(), rootIndex()) || + !model()->hasIndex(range.bottomRow(), range.rightColumn(), rootIndex())) + return; + + QModelIndex topLeft = model()->index(range.topRow(), range.leftColumn(), rootIndex()); + QModelIndex bottomRight = model()->index(range.bottomRow(), range.rightColumn(), rootIndex()); + + selectionModel()->select(QItemSelection(topLeft, bottomRight), + select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); +} + +/*! + Returns a list of all selected ranges. + + \sa QTableWidgetSelectionRange +*/ + +QList QTableWidget::selectedRanges() const +{ + const QList ranges = selectionModel()->selection(); + QList result; + for (int i = 0; i < ranges.count(); ++i) + result.append(QTableWidgetSelectionRange(ranges.at(i).top(), + ranges.at(i).left(), + ranges.at(i).bottom(), + ranges.at(i).right())); + return result; +} + +/*! + Returns a list of all selected items. + + This function returns a list of pointers to the contents of the + selected cells. Use the selectedIndexes() function to retrieve the + complete selection \e including empty cells. + + \sa selectedIndexes() +*/ + +QList QTableWidget::selectedItems() +{ + Q_D(QTableWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList items; + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + if (isIndexHidden(index)) + continue; + QTableWidgetItem *item = d->tableModel()->item(index); + if (item) + items.append(item); + } + return items; +} + +/*! + Finds items that matches the \a text using the given \a flags. +*/ + +QList QTableWidget::findItems(const QString &text, Qt::MatchFlags flags) const +{ + Q_D(const QTableWidget); + QModelIndexList indexes; + for (int column = 0; column < columnCount(); ++column) + indexes += d->model->match(model()->index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->tableModel()->item(indexes.at(i))); + return items; +} + +/*! + Returns the visual row of the given \a logicalRow. +*/ + +int QTableWidget::visualRow(int logicalRow) const +{ + return verticalHeader()->visualIndex(logicalRow); +} + +/*! + Returns the visual column of the given \a logicalColumn. +*/ + +int QTableWidget::visualColumn(int logicalColumn) const +{ + return horizontalHeader()->visualIndex(logicalColumn); +} + +/*! + \fn QTableWidgetItem *QTableWidget::itemAt(const QPoint &point) const + + Returns a pointer to the item at the given \a point, or returns 0 if + \a point is not covered by an item in the table widget. + + \sa item() +*/ + +QTableWidgetItem *QTableWidget::itemAt(const QPoint &p) const +{ + Q_D(const QTableWidget); + return d->tableModel()->item(indexAt(p)); +} + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. +*/ +QRect QTableWidget::visualItemRect(const QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + if (!item) + return QRect(); + QModelIndex index = d->tableModel()->index(const_cast(item)); + Q_ASSERT(index.isValid()); + return visualRect(index); +} + +/*! + Scrolls the view if necessary to ensure that the \a item is visible. + The \a hint parameter specifies more precisely where the + \a item should be located after the operation. +*/ + +void QTableWidget::scrollToItem(const QTableWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QTableWidget); + if (!item) + return; + QModelIndex index = d->tableModel()->index(const_cast(item)); + Q_ASSERT(index.isValid()); + QTableView::scrollTo(index, hint); +} + +/*! + Returns the item prototype used by the table. + + \sa setItemPrototype() +*/ +const QTableWidgetItem *QTableWidget::itemPrototype() const +{ + Q_D(const QTableWidget); + return d->tableModel()->itemPrototype(); +} + +/*! + Sets the item prototype for the table to the specified \a item. + + The table widget will use the item prototype clone function when it needs + to create a new table item. For example when the user is editing + in an empty cell. This is useful when you have a QTableWidgetItem + subclass and want to make sure that QTableWidget creates instances of + your subclass. + + The table takes ownership of the prototype. + + \sa itemPrototype() +*/ +void QTableWidget::setItemPrototype(const QTableWidgetItem *item) +{ + Q_D(QTableWidget); + d->tableModel()->setItemPrototype(item); +} + +/*! + Inserts an empty row into the table at \a row. +*/ +void QTableWidget::insertRow(int row) +{ + Q_D(QTableWidget); + d->tableModel()->insertRows(row); +} + +/*! + Inserts an empty column into the table at \a column. +*/ +void QTableWidget::insertColumn(int column) +{ + Q_D(QTableWidget); + d->tableModel()->insertColumns(column); +} + +/*! + Removes the row \a row and all its items from the table. +*/ +void QTableWidget::removeRow(int row) +{ + Q_D(QTableWidget); + d->tableModel()->removeRows(row); +} + +/*! + Removes the column \a column and all its items from the table. +*/ +void QTableWidget::removeColumn(int column) +{ + Q_D(QTableWidget); + d->tableModel()->removeColumns(column); +} + +/*! + Removes all items in the view. + This will also remove all selections. + The table dimensions stay the same. +*/ + +void QTableWidget::clear() +{ + Q_D(QTableWidget); + selectionModel()->clear(); + d->tableModel()->clear(); +} + +/*! + \since 4.2 + + Removes all items not in the headers from the view. + This will also remove all selections. + The table dimensions stay the same. +*/ +void QTableWidget::clearContents() +{ + Q_D(QTableWidget); + selectionModel()->clear(); + d->tableModel()->clearContents(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + tablewidget items. + + \sa mimeData() +*/ +QStringList QTableWidget::mimeTypes() const +{ + return d_func()->tableModel()->QAbstractTableModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QTableWidget::mimeData(const QList) const +{ + return d_func()->tableModel()->internalMimeData(); +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action in the given \a row and \a column. + Returns true if the data and action can be handled by the model; + otherwise returns false. + + \sa supportedDropActions() +*/ +bool QTableWidget::dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; +#ifndef QT_NO_DRAGANDDROP + if (dropIndicatorPosition() == QAbstractItemView::OnItem) { + // QAbstractTableModel::dropMimeData will overwrite on the index if row == -1 and column == -1 + idx = model()->index(row, column); + row = -1; + column = -1; + } +#endif + return d_func()->tableModel()->QAbstractTableModel::dropMimeData(data, action , row, column, idx); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QTableWidget::supportedDropActions() const +{ + return d_func()->tableModel()->QAbstractTableModel::supportedDropActions() | Qt::MoveAction; +} + +/*! + Returns a list of pointers to the items contained in the \a data object. + If the object was not created by a QTreeWidget in the same process, the list + is empty. + +*/ +QList QTableWidget::items(const QMimeData *data) const +{ + const QTableWidgetMimeData *twd = qobject_cast(data); + if (twd) + return twd->items; + return QList(); +} + +/*! + Returns the QModelIndex assocated with the given \a item. +*/ + +QModelIndex QTableWidget::indexFromItem(QTableWidgetItem *item) const +{ + Q_D(const QTableWidget); + return d->tableModel()->index(item); +} + +/*! + Returns a pointer to the QTableWidgetItem assocated with the given \a index. +*/ + +QTableWidgetItem *QTableWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QTableWidget); + return d->tableModel()->item(index); +} + +/*! + \internal +*/ +void QTableWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QTableWidget::setModel() - Changing the model of the QTableWidget is not allowed."); +} + +/*! \reimp */ +bool QTableWidget::event(QEvent *e) +{ + return QTableView::event(e); +} + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp */ +void QTableWidget::dropEvent(QDropEvent *event) { + Q_D(QTableWidget); + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QModelIndexList indexes = selectedIndexes(); + int top = INT_MAX; + int left = INT_MAX; + for (int i = 0; i < indexes.count(); ++i) { + top = qMin(indexes.at(i).row(), top); + left = qMin(indexes.at(i).column(), left); + } + + QList taken; + for (int i = 0; i < indexes.count(); ++i) + taken.append(takeItem(indexes.at(i).row(), indexes.at(i).column())); + + for (int i = 0; i < indexes.count(); ++i) { + QModelIndex index = indexes.at(i); + int r = index.row() - top + topIndex.row(); + int c = index.column() - left + topIndex.column(); + setItem(r, c, taken.takeFirst()); + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QTableView::dropEvent(event); +} +#endif + +QT_END_NAMESPACE + +#include "moc_qtablewidget.cpp" + +#endif // QT_NO_TABLEWIDGET diff --git a/src/gui/itemviews/qtablewidget.h b/src/gui/itemviews/qtablewidget.h new file mode 100644 index 0000000000..35e3b29628 --- /dev/null +++ b/src/gui/itemviews/qtablewidget.h @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEWIDGET_H +#define QTABLEWIDGET_H + +#include +#include +#include +//#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TABLEWIDGET + +class Q_GUI_EXPORT QTableWidgetSelectionRange +{ +public: + QTableWidgetSelectionRange(); + QTableWidgetSelectionRange(int top, int left, int bottom, int right); + QTableWidgetSelectionRange(const QTableWidgetSelectionRange &other); + ~QTableWidgetSelectionRange(); + + inline int topRow() const { return top; } + inline int bottomRow() const { return bottom; } + inline int leftColumn() const { return left; } + inline int rightColumn() const { return right; } + inline int rowCount() const { return bottom - top + 1; } + inline int columnCount() const { return right - left + 1; } + +private: + int top, left, bottom, right; +}; + +class QTableWidget; +class QTableModel; +class QWidgetItemData; +class QTableWidgetItemPrivate; + +class Q_GUI_EXPORT QTableWidgetItem +{ + friend class QTableWidget; + friend class QTableModel; +public: + enum ItemType { Type = 0, UserType = 1000 }; + QTableWidgetItem(int type = Type); + explicit QTableWidgetItem(const QString &text, int type = Type); + explicit QTableWidgetItem(const QIcon &icon, const QString &text, int type = Type); + QTableWidgetItem(const QTableWidgetItem &other); + virtual ~QTableWidgetItem(); + + virtual QTableWidgetItem *clone() const; + + inline QTableWidget *tableWidget() const { return view; } + + inline int row() const; + inline int column() const; + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline Qt::ItemFlags flags() const { return itemFlags; } + void setFlags(Qt::ItemFlags flags); + + inline QString text() const + { return data(Qt::DisplayRole).toString(); } + inline void setText(const QString &text); + + inline QIcon icon() const + { return qvariant_cast(data(Qt::DecorationRole)); } + inline void setIcon(const QIcon &icon); + + inline QString statusTip() const + { return data(Qt::StatusTipRole).toString(); } + inline void setStatusTip(const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip() const + { return data(Qt::ToolTipRole).toString(); } + inline void setToolTip(const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis() const + { return data(Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(const QString &whatsThis); +#endif + + inline QFont font() const + { return qvariant_cast(data(Qt::FontRole)); } + inline void setFont(const QFont &font); + + inline int textAlignment() const + { return data(Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int alignment) + { setData(Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor() const + { return qvariant_cast(data(Qt::BackgroundColorRole)); } + inline void setBackgroundColor(const QColor &color) + { setData(Qt::BackgroundColorRole, color); } + + inline QBrush background() const + { return qvariant_cast(data(Qt::BackgroundRole)); } + inline void setBackground(const QBrush &brush) + { setData(Qt::BackgroundRole, brush); } + + inline QColor textColor() const + { return qvariant_cast(data(Qt::TextColorRole)); } + inline void setTextColor(const QColor &color) + { setData(Qt::TextColorRole, color); } + + inline QBrush foreground() const + { return qvariant_cast(data(Qt::ForegroundRole)); } + inline void setForeground(const QBrush &brush) + { setData(Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState() const + { return static_cast(data(Qt::CheckStateRole).toInt()); } + inline void setCheckState(Qt::CheckState state) + { setData(Qt::CheckStateRole, state); } + + inline QSize sizeHint() const + { return qvariant_cast(data(Qt::SizeHintRole)); } + inline void setSizeHint(const QSize &size) + { setData(Qt::SizeHintRole, size); } + + virtual QVariant data(int role) const; + virtual void setData(int role, const QVariant &value); + + virtual bool operator<(const QTableWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QTableWidgetItem &operator=(const QTableWidgetItem &other); + + inline int type() const { return rtti; } + +private: + int rtti; + QVector values; + QTableWidget *view; + QTableWidgetItemPrivate *d; + Qt::ItemFlags itemFlags; +}; + +inline void QTableWidgetItem::setText(const QString &atext) +{ setData(Qt::DisplayRole, atext); } + +inline void QTableWidgetItem::setIcon(const QIcon &aicon) +{ setData(Qt::DecorationRole, aicon); } + +inline void QTableWidgetItem::setStatusTip(const QString &astatusTip) +{ setData(Qt::StatusTipRole, astatusTip); } + +#ifndef QT_NO_TOOLTIP +inline void QTableWidgetItem::setToolTip(const QString &atoolTip) +{ setData(Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QTableWidgetItem::setWhatsThis(const QString &awhatsThis) +{ setData(Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QTableWidgetItem::setFont(const QFont &afont) +{ setData(Qt::FontRole, afont); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QTableWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QTableWidgetItem &item); +#endif + +class QTableWidgetPrivate; + +class Q_GUI_EXPORT QTableWidget : public QTableView +{ + Q_OBJECT + Q_PROPERTY(int rowCount READ rowCount WRITE setRowCount) + Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount) + + friend class QTableModel; +public: + explicit QTableWidget(QWidget *parent = 0); + QTableWidget(int rows, int columns, QWidget *parent = 0); + ~QTableWidget(); + + void setRowCount(int rows); + int rowCount() const; + + void setColumnCount(int columns); + int columnCount() const; + + int row(const QTableWidgetItem *item) const; + int column(const QTableWidgetItem *item) const; + + QTableWidgetItem *item(int row, int column) const; + void setItem(int row, int column, QTableWidgetItem *item); + QTableWidgetItem *takeItem(int row, int column); + + QTableWidgetItem *verticalHeaderItem(int row) const; + void setVerticalHeaderItem(int row, QTableWidgetItem *item); + QTableWidgetItem *takeVerticalHeaderItem(int row); + + QTableWidgetItem *horizontalHeaderItem(int column) const; + void setHorizontalHeaderItem(int column, QTableWidgetItem *item); + QTableWidgetItem *takeHorizontalHeaderItem(int column); + void setVerticalHeaderLabels(const QStringList &labels); + void setHorizontalHeaderLabels(const QStringList &labels); + + int currentRow() const; + int currentColumn() const; + QTableWidgetItem *currentItem() const; + void setCurrentItem(QTableWidgetItem *item); + void setCurrentItem(QTableWidgetItem *item, QItemSelectionModel::SelectionFlags command); + void setCurrentCell(int row, int column); + void setCurrentCell(int row, int column, QItemSelectionModel::SelectionFlags command); + + void sortItems(int column, Qt::SortOrder order = Qt::AscendingOrder); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QTableWidgetItem *item); + void openPersistentEditor(QTableWidgetItem *item); + void closePersistentEditor(QTableWidgetItem *item); + + QWidget *cellWidget(int row, int column) const; + void setCellWidget(int row, int column, QWidget *widget); + inline void removeCellWidget(int row, int column); + + bool isItemSelected(const QTableWidgetItem *item) const; + void setItemSelected(const QTableWidgetItem *item, bool select); + void setRangeSelected(const QTableWidgetSelectionRange &range, bool select); + + QList selectedRanges() const; + QList selectedItems(); + QList findItems(const QString &text, Qt::MatchFlags flags) const; + + int visualRow(int logicalRow) const; + int visualColumn(int logicalColumn) const; + + QTableWidgetItem *itemAt(const QPoint &p) const; + inline QTableWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QTableWidgetItem *item) const; + + const QTableWidgetItem *itemPrototype() const; + void setItemPrototype(const QTableWidgetItem *item); + +public Q_SLOTS: + void scrollToItem(const QTableWidgetItem *item, QAbstractItemView::ScrollHint hint = EnsureVisible); + void insertRow(int row); + void insertColumn(int column); + void removeRow(int row); + void removeColumn(int column); + void clear(); + void clearContents(); + +Q_SIGNALS: + void itemPressed(QTableWidgetItem *item); + void itemClicked(QTableWidgetItem *item); + void itemDoubleClicked(QTableWidgetItem *item); + + void itemActivated(QTableWidgetItem *item); + void itemEntered(QTableWidgetItem *item); + void itemChanged(QTableWidgetItem *item); + + void currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous); + void itemSelectionChanged(); + + void cellPressed(int row, int column); + void cellClicked(int row, int column); + void cellDoubleClicked(int row, int column); + + void cellActivated(int row, int column); + void cellEntered(int row, int column); + void cellChanged(int row, int column); + + void currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList items) const; + virtual bool dropMimeData(int row, int column, const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; + QList items(const QMimeData *data) const; + + QModelIndex indexFromItem(QTableWidgetItem *item) const; + QTableWidgetItem *itemFromIndex(const QModelIndex &index) const; + void dropEvent(QDropEvent *event); + +private: + void setModel(QAbstractItemModel *model); + + Q_DECLARE_PRIVATE(QTableWidget) + Q_DISABLE_COPY(QTableWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) +}; + +inline void QTableWidget::removeCellWidget(int arow, int acolumn) +{ setCellWidget(arow, acolumn, 0); } + +inline QTableWidgetItem *QTableWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline int QTableWidgetItem::row() const +{ return (view ? view->row(this) : -1); } + +inline int QTableWidgetItem::column() const +{ return (view ? view->column(this) : -1); } + +inline void QTableWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QTableWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +#endif // QT_NO_TABLEWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTABLEWIDGET_H diff --git a/src/gui/itemviews/qtablewidget_p.h b/src/gui/itemviews/qtablewidget_p.h new file mode 100644 index 0000000000..e0b06cb89c --- /dev/null +++ b/src/gui/itemviews/qtablewidget_p.h @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTABLEWIDGET_P_H +#define QTABLEWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_TABLEWIDGET + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug +typedef bool(*LessThan)(const QPair&,const QPair&); + +class QTableWidgetMimeData : public QMimeData +{ + Q_OBJECT +public: + QList items; +}; + +class QTableModelLessThan +{ +public: + inline bool operator()(QTableWidgetItem *i1, QTableWidgetItem *i2) const + { return (*i1 < *i2); } +}; + +class QTableModelGreaterThan +{ +public: + inline bool operator()(QTableWidgetItem *i1, QTableWidgetItem *i2) const + { return (*i2 < *i1); } +}; + +class QTableModel : public QAbstractTableModel +{ + Q_OBJECT +public: + enum ItemFlagsExtension { + ItemIsHeaderItem = 128 + }; // we need this to separate header items from other items + + QTableModel(int rows, int columns, QTableWidget *parent); + ~QTableModel(); + + bool insertRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool insertColumns(int column, int count = 1, const QModelIndex &parent = QModelIndex()); + + bool removeRows(int row, int count = 1, const QModelIndex &parent = QModelIndex()); + bool removeColumns(int column, int count = 1, const QModelIndex &parent = QModelIndex()); + + void setItem(int row, int column, QTableWidgetItem *item); + QTableWidgetItem *takeItem(int row, int column); + QTableWidgetItem *item(int row, int column) const; + QTableWidgetItem *item(const QModelIndex &index) const; + void removeItem(QTableWidgetItem *item); + + void setHorizontalHeaderItem(int section, QTableWidgetItem *item); + void setVerticalHeaderItem(int section, QTableWidgetItem *item); + QTableWidgetItem *takeHorizontalHeaderItem(int section); + QTableWidgetItem *takeVerticalHeaderItem(int section); + QTableWidgetItem *horizontalHeaderItem(int section); + QTableWidgetItem *verticalHeaderItem(int section); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { return QAbstractTableModel::index(row, column, parent); } + + QModelIndex index(const QTableWidgetItem *item) const; + + void setRowCount(int rows); + void setColumnCount(int columns); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + bool setItemData(const QModelIndex &index, const QMap &roles); + + QMap itemData(const QModelIndex &index) const; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + static bool itemLessThan(const QPair &left, + const QPair &right); + static bool itemGreaterThan(const QPair &left, + const QPair &right); + + void ensureSorted(int column, Qt::SortOrder order, int start, int end); + QVector columnItems(int column) const; + void updateRowIndexes(QModelIndexList &indexes, int movedFromRow, int movedToRow); + static QVector::iterator sortedInsertionIterator( + const QVector::iterator &begin, + const QVector::iterator &end, + Qt::SortOrder order, QTableWidgetItem *item); + + bool isValid(const QModelIndex &index) const; + inline long tableIndex(int row, int column) const + { return (row * horizontalHeaderItems.count()) + column; } + + void clear(); + void clearContents(); + void itemChanged(QTableWidgetItem *item); + + QTableWidgetItem *createItem() const; + const QTableWidgetItem *itemPrototype() const; + void setItemPrototype(const QTableWidgetItem *item); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + QMimeData *internalMimeData() const; + +private: + const QTableWidgetItem *prototype; + QVector tableItems; + QVector verticalHeaderItems; + QVector horizontalHeaderItems; + + // A cache must be mutable if get-functions should have const modifiers + mutable QModelIndexList cachedIndexes; +}; + +class QTableWidgetPrivate : public QTableViewPrivate +{ + Q_DECLARE_PUBLIC(QTableWidget) +public: + QTableWidgetPrivate() : QTableViewPrivate() {} + inline QTableModel *tableModel() const { return qobject_cast(model); } + void setup(); + + // view signals + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + // model signals + void _q_emitItemChanged(const QModelIndex &index); + // selection signals + void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t); + // sorting + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); +}; + +class QTableWidgetItemPrivate +{ +public: + QTableWidgetItemPrivate(QTableWidgetItem *item) : q(item), id(-1) {} + QTableWidgetItem *q; + int id; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TABLEWIDGET + +#endif // QTABLEWIDGET_P_H diff --git a/src/gui/itemviews/qtreeview.cpp b/src/gui/itemviews/qtreeview.cpp new file mode 100644 index 0000000000..21c9d3b31a --- /dev/null +++ b/src/gui/itemviews/qtreeview.cpp @@ -0,0 +1,3755 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qtreeview.h" + +#ifndef QT_NO_TREEVIEW +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_ACCESSIBILITY +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QTreeView + \brief The QTreeView class provides a default model/view implementation of a tree view. + + \ingroup model-view + \ingroup advanced + + + A QTreeView implements a tree representation of items from a + model. This class is used to provide standard hierarchical lists that + were previously provided by the \c QListView class, but using the more + flexible approach provided by Qt's model/view architecture. + + The QTreeView class is one of the \l{Model/View Classes} and is part of + Qt's \l{Model/View Programming}{model/view framework}. + + QTreeView implements the interfaces defined by the + QAbstractItemView class to allow it to display data provided by + models derived from the QAbstractItemModel class. + + It is simple to construct a tree view displaying data from a + model. In the following example, the contents of a directory are + supplied by a QDirModel and displayed as a tree: + + \snippet doc/src/snippets/shareddirmodel/main.cpp 3 + \snippet doc/src/snippets/shareddirmodel/main.cpp 6 + + The model/view architecture ensures that the contents of the tree view + are updated as the model changes. + + Items that have children can be in an expanded (children are + visible) or collapsed (children are hidden) state. When this state + changes a collapsed() or expanded() signal is emitted with the + model index of the relevant item. + + The amount of indentation used to indicate levels of hierarchy is + controlled by the \l indentation property. + + Headers in tree views are constructed using the QHeaderView class and can + be hidden using \c{header()->hide()}. Note that each header is configured + with its \l{QHeaderView::}{stretchLastSection} property set to true, + ensuring that the view does not waste any of the space assigned to it for + its header. If this value is set to true, this property will override the + resize mode set on the last section in the header. + + + \section1 Key Bindings + + QTreeView supports a set of key bindings that enable the user to + navigate in the view and interact with the contents of items: + + \table + \header \o Key \o Action + \row \o Up \o Moves the cursor to the item in the same column on + the previous row. If the parent of the current item has no more rows to + navigate to, the cursor moves to the relevant item in the last row + of the sibling that precedes the parent. + \row \o Down \o Moves the cursor to the item in the same column on + the next row. If the parent of the current item has no more rows to + navigate to, the cursor moves to the relevant item in the first row + of the sibling that follows the parent. + \row \o Left \o Hides the children of the current item (if present) + by collapsing a branch. + \row \o Minus \o Same as LeftArrow. + \row \o Right \o Reveals the children of the current item (if present) + by expanding a branch. + \row \o Plus \o Same as RightArrow. + \row \o Asterisk \o Expands all children of the current item (if present). + \row \o PageUp \o Moves the cursor up one page. + \row \o PageDown \o Moves the cursor down one page. + \row \o Home \o Moves the cursor to an item in the same column of the first + row of the first top-level item in the model. + \row \o End \o Moves the cursor to an item in the same column of the last + row of the last top-level item in the model. + \row \o F2 \o In editable models, this opens the current item for editing. + The Escape key can be used to cancel the editing process and revert + any changes to the data displayed. + \endtable + + \omit + Describe the expanding/collapsing concept if not covered elsewhere. + \endomit + + \table 100% + \row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree view + \o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree view + \o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree view + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree view. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree view. + \o A \l{Plastique Style Widget Gallery}{Plastique style} tree view. + \endtable + + \section1 Improving Performance + + It is possible to give the view hints about the data it is handling in order + to improve its performance when displaying large numbers of items. One approach + that can be taken for views that are intended to display items with equal heights + is to set the \l uniformRowHeights property to true. + + \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, + {Dir View Example} +*/ + + +/*! + \fn void QTreeView::expanded(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is expanded. +*/ + + +/*! + \fn void QTreeView::collapsed(const QModelIndex &index) + + This signal is emitted when the item specified by \a index is collapsed. +*/ + +/*! + Constructs a tree view with a \a parent to represent a model's + data. Use setModel() to set the model. + + \sa QAbstractItemModel +*/ +QTreeView::QTreeView(QWidget *parent) + : QAbstractItemView(*new QTreeViewPrivate, parent) +{ + Q_D(QTreeView); + d->initialize(); +} + +/*! + \internal +*/ +QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent) + : QAbstractItemView(dd, parent) +{ + Q_D(QTreeView); + d->initialize(); +} + +/*! + Destroys the tree view. +*/ +QTreeView::~QTreeView() +{ +} + +/*! + \reimp +*/ +void QTreeView::setModel(QAbstractItemModel *model) +{ + Q_D(QTreeView); + if (model == d->model) + return; + if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); + + disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); + } + + if (d->selectionModel) { // support row editing + disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); + disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); + } + d->viewItems.clear(); + d->expandedIndexes.clear(); + d->hiddenIndexes.clear(); + d->header->setModel(model); + QAbstractItemView::setModel(model); + + // QAbstractItemView connects to a private slot + disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + // do header layout after the tree + disconnect(d->model, SIGNAL(layoutChanged()), + d->header, SLOT(_q_layoutChanged())); + // QTreeView has a public slot for this + connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(rowsRemoved(QModelIndex,int,int))); + + connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset())); + + if (d->sortingEnabled) + d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); +} + +/*! + \reimp +*/ +void QTreeView::setRootIndex(const QModelIndex &index) +{ + Q_D(QTreeView); + d->header->setRootIndex(index); + QAbstractItemView::setRootIndex(index); +} + +/*! + \reimp +*/ +void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTreeView); + Q_ASSERT(selectionModel); + if (d->selectionModel) { + // support row editing + disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + } + + d->header->setSelectionModel(selectionModel); + QAbstractItemView::setSelectionModel(selectionModel); + + if (d->selectionModel) { + // support row editing + connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + d->model, SLOT(submit())); + } +} + +/*! + Returns the header for the tree view. + + \sa QAbstractItemModel::headerData() +*/ +QHeaderView *QTreeView::header() const +{ + Q_D(const QTreeView); + return d->header; +} + +/*! + Sets the header for the tree view, to the given \a header. + + The view takes ownership over the given \a header and deletes it + when a new header is set. + + \sa QAbstractItemModel::headerData() +*/ +void QTreeView::setHeader(QHeaderView *header) +{ + Q_D(QTreeView); + if (header == d->header || !header) + return; + if (d->header && d->header->parent() == this) + delete d->header; + d->header = header; + d->header->setParent(this); + + if (!d->header->model()) { + d->header->setModel(d->model); + if (d->selectionModel) + d->header->setSelectionModel(d->selectionModel); + } + + connect(d->header, SIGNAL(sectionResized(int,int,int)), + this, SLOT(columnResized(int,int,int))); + connect(d->header, SIGNAL(sectionMoved(int,int,int)), + this, SLOT(columnMoved())); + connect(d->header, SIGNAL(sectionCountChanged(int,int)), + this, SLOT(columnCountChanged(int,int))); + connect(d->header, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(resizeColumnToContents(int))); + connect(d->header, SIGNAL(geometriesChanged()), + this, SLOT(updateGeometries())); + + setSortingEnabled(d->sortingEnabled); +} + +/*! + \property QTreeView::autoExpandDelay + \brief The delay time before items in a tree are opened during a drag and drop operation. + \since 4.3 + + This property holds the amount of time in milliseconds that the user must wait over + a node before that node will automatically open or close. If the time is + set to less then 0 then it will not be activated. + + By default, this property has a value of -1, meaning that auto-expansion is disabled. +*/ +int QTreeView::autoExpandDelay() const +{ + Q_D(const QTreeView); + return d->autoExpandDelay; +} + +void QTreeView::setAutoExpandDelay(int delay) +{ + Q_D(QTreeView); + d->autoExpandDelay = delay; +} + +/*! + \property QTreeView::indentation + \brief indentation of the items in the tree view. + + This property holds the indentation measured in pixels of the items for each + level in the tree view. For top-level items, the indentation specifies the + horizontal distance from the viewport edge to the items in the first column; + for child items, it specifies their indentation from their parent items. + + By default, this property has a value of 20. +*/ +int QTreeView::indentation() const +{ + Q_D(const QTreeView); + return d->indent; +} + +void QTreeView::setIndentation(int i) +{ + Q_D(QTreeView); + if (i != d->indent) { + d->indent = i; + d->viewport->update(); + } +} + +/*! + \property QTreeView::rootIsDecorated + \brief whether to show controls for expanding and collapsing top-level items + + Items with children are typically shown with controls to expand and collapse + them, allowing their children to be shown or hidden. If this property is + false, these controls are not shown for top-level items. This can be used to + make a single level tree structure appear like a simple list of items. + + By default, this property is true. +*/ +bool QTreeView::rootIsDecorated() const +{ + Q_D(const QTreeView); + return d->rootDecoration; +} + +void QTreeView::setRootIsDecorated(bool show) +{ + Q_D(QTreeView); + if (show != d->rootDecoration) { + d->rootDecoration = show; + d->viewport->update(); + } +} + +/*! + \property QTreeView::uniformRowHeights + \brief whether all items in the treeview have the same height + + This property should only be set to true if it is guaranteed that all items + in the view has the same height. This enables the view to do some + optimizations. + + The height is obtained from the first item in the view. It is updated + when the data changes on that item. + + By default, this property is false. +*/ +bool QTreeView::uniformRowHeights() const +{ + Q_D(const QTreeView); + return d->uniformRowHeights; +} + +void QTreeView::setUniformRowHeights(bool uniform) +{ + Q_D(QTreeView); + d->uniformRowHeights = uniform; +} + +/*! + \property QTreeView::itemsExpandable + \brief whether the items are expandable by the user. + + This property holds whether the user can expand and collapse items + interactively. + + By default, this property is true. + +*/ +bool QTreeView::itemsExpandable() const +{ + Q_D(const QTreeView); + return d->itemsExpandable; +} + +void QTreeView::setItemsExpandable(bool enable) +{ + Q_D(QTreeView); + d->itemsExpandable = enable; +} + +/*! + \property QTreeView::expandsOnDoubleClick + \since 4.4 + \brief whether the items can be expanded by double-clicking. + + This property holds whether the user can expand and collapse items + by double-clicking. The default value is true. + + \sa itemsExpandable +*/ +bool QTreeView::expandsOnDoubleClick() const +{ + Q_D(const QTreeView); + return d->expandsOnDoubleClick; +} + +void QTreeView::setExpandsOnDoubleClick(bool enable) +{ + Q_D(QTreeView); + d->expandsOnDoubleClick = enable; +} + +/*! + Returns the horizontal position of the \a column in the viewport. +*/ +int QTreeView::columnViewportPosition(int column) const +{ + Q_D(const QTreeView); + return d->header->sectionViewportPosition(column); +} + +/*! + Returns the width of the \a column. + + \sa resizeColumnToContents(), setColumnWidth() +*/ +int QTreeView::columnWidth(int column) const +{ + Q_D(const QTreeView); + return d->header->sectionSize(column); +} + +/*! + \since 4.2 + + Sets the width of the given \a column to the \a width specified. + + \sa columnWidth(), resizeColumnToContents() +*/ +void QTreeView::setColumnWidth(int column, int width) +{ + Q_D(QTreeView); + d->header->resizeSection(column, width); +} + +/*! + Returns the column in the tree view whose header covers the \a x + coordinate given. +*/ +int QTreeView::columnAt(int x) const +{ + Q_D(const QTreeView); + return d->header->logicalIndexAt(x); +} + +/*! + Returns true if the \a column is hidden; otherwise returns false. + + \sa hideColumn(), isRowHidden() +*/ +bool QTreeView::isColumnHidden(int column) const +{ + Q_D(const QTreeView); + return d->header->isSectionHidden(column); +} + +/*! + If \a hide is true the \a column is hidden, otherwise the \a column is shown. + + \sa hideColumn(), setRowHidden() +*/ +void QTreeView::setColumnHidden(int column, bool hide) +{ + Q_D(QTreeView); + if (column < 0 || column >= d->header->count()) + return; + d->header->setSectionHidden(column, hide); +} + +/*! + \property QTreeView::headerHidden + \brief whether the header is shown or not. + \since 4.4 + + If this property is true, the header is not shown otherwise it is. + The default value is false. + + \sa header() +*/ +bool QTreeView::isHeaderHidden() const +{ + Q_D(const QTreeView); + return d->header->isHidden(); +} + +void QTreeView::setHeaderHidden(bool hide) +{ + Q_D(QTreeView); + d->header->setHidden(hide); +} + +/*! + Returns true if the item in the given \a row of the \a parent is hidden; + otherwise returns false. + + \sa setRowHidden(), isColumnHidden() +*/ +bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const +{ + Q_D(const QTreeView); + if (!d->model) + return false; + return d->isRowHidden(d->model->index(row, 0, parent)); +} + +/*! + If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown. + + \sa isRowHidden(), setColumnHidden() +*/ +void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide) +{ + Q_D(QTreeView); + if (!d->model) + return; + QModelIndex index = d->model->index(row, 0, parent); + if (!index.isValid()) + return; + + if (hide) { + d->hiddenIndexes.insert(index); + } else if(d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set + d->hiddenIndexes.remove(index); + } + + d->doDelayedItemsLayout(); +} + +/*! + \since 4.3 + + Returns true if the item in first column in the given \a row + of the \a parent is spanning all the columns; otherwise returns false. + + \sa setFirstColumnSpanned() +*/ +bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const +{ + Q_D(const QTreeView); + if (d->spanningIndexes.isEmpty() || !d->model) + return false; + QModelIndex index = d->model->index(row, 0, parent); + for (int i = 0; i < d->spanningIndexes.count(); ++i) + if (d->spanningIndexes.at(i) == index) + return true; + return false; +} + +/*! + \since 4.3 + + If \a span is true the item in the first column in the \a row + with the given \a parent is set to span all columns, otherwise all items + on the \a row are shown. + + \sa isFirstColumnSpanned() +*/ +void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span) +{ + Q_D(QTreeView); + if (!d->model) + return; + QModelIndex index = d->model->index(row, 0, parent); + if (!index.isValid()) + return; + + if (span) { + QPersistentModelIndex persistent(index); + if (!d->spanningIndexes.contains(persistent)) + d->spanningIndexes.append(persistent); + } else { + QPersistentModelIndex persistent(index); + int i = d->spanningIndexes.indexOf(persistent); + if (i >= 0) + d->spanningIndexes.remove(i); + } + + d->executePostedLayout(); + int i = d->viewIndex(index); + if (i >= 0) + d->viewItems[i].spanning = span; + + d->viewport->update(); +} + +/*! + \reimp +*/ +void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(QTreeView); + + // if we are going to do a complete relayout anyway, there is no need to update + if (d->delayedPendingLayout) + return; + + // refresh the height cache here; we don't really lose anything by getting the size hint, + // since QAbstractItemView::dataChanged() will get the visualRect for the items anyway + + bool sizeChanged = false; + int topViewIndex = d->viewIndex(topLeft); + if (topViewIndex == 0) { + int newDefaultItemHeight = indexRowSizeHint(topLeft); + sizeChanged = d->defaultItemHeight != newDefaultItemHeight; + d->defaultItemHeight = newDefaultItemHeight; + } + + if (topViewIndex != -1) { + if (topLeft.row() == bottomRight.row()) { + int oldHeight = d->itemHeight(topViewIndex); + d->invalidateHeightCache(topViewIndex); + sizeChanged |= (oldHeight != d->itemHeight(topViewIndex)); + if (topLeft.column() == 0) + d->viewItems[topViewIndex].hasChildren = d->hasVisibleChildren(topLeft); + } else { + int bottomViewIndex = d->viewIndex(bottomRight); + for (int i = topViewIndex; i <= bottomViewIndex; ++i) { + int oldHeight = d->itemHeight(i); + d->invalidateHeightCache(i); + sizeChanged |= (oldHeight != d->itemHeight(i)); + if (topLeft.column() == 0) + d->viewItems[i].hasChildren = d->hasVisibleChildren(d->viewItems.at(i).index); + } + } + } + + if (sizeChanged) { + d->updateScrollBars(); + d->viewport->update(); + } + QAbstractItemView::dataChanged(topLeft, bottomRight); +} + +/*! + Hides the \a column given. + + \note This function should only be called after the model has been + initialized, as the view needs to know the number of columns in order to + hide \a column. + + \sa showColumn(), setColumnHidden() +*/ +void QTreeView::hideColumn(int column) +{ + Q_D(QTreeView); + d->header->hideSection(column); +} + +/*! + Shows the given \a column in the tree view. + + \sa hideColumn(), setColumnHidden() +*/ +void QTreeView::showColumn(int column) +{ + Q_D(QTreeView); + d->header->showSection(column); +} + +/*! + \fn void QTreeView::expand(const QModelIndex &index) + + Expands the model item specified by the \a index. + + \sa expanded() +*/ +void QTreeView::expand(const QModelIndex &index) +{ + Q_D(QTreeView); + if (!d->isIndexValid(index)) + return; + if (d->delayedPendingLayout) { + //A complete relayout is going to be performed, just store the expanded index, no need to layout. + if (d->storeExpanded(index)) + emit expanded(index); + return; + } + + int i = d->viewIndex(index); + if (i != -1) { // is visible + d->expand(i, true); + if (!d->isAnimating()) { + updateGeometries(); + d->viewport->update(); + } + } else if (d->storeExpanded(index)) { + emit expanded(index); + } +} + +/*! + \fn void QTreeView::collapse(const QModelIndex &index) + + Collapses the model item specified by the \a index. + + \sa collapsed() +*/ +void QTreeView::collapse(const QModelIndex &index) +{ + Q_D(QTreeView); + if (!d->isIndexValid(index)) + return; + //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll + d->delayedAutoScroll.stop(); + + if (d->delayedPendingLayout) { + //A complete relayout is going to be performed, just un-store the expanded index, no need to layout. + if (d->isPersistent(index) && d->expandedIndexes.remove(index)) + emit collapsed(index); + return; + } + int i = d->viewIndex(index); + if (i != -1) { // is visible + d->collapse(i, true); + if (!d->isAnimating()) { + updateGeometries(); + viewport()->update(); + } + } else { + if (d->isPersistent(index) && d->expandedIndexes.remove(index)) + emit collapsed(index); + } +} + +/*! + \fn bool QTreeView::isExpanded(const QModelIndex &index) const + + Returns true if the model item \a index is expanded; otherwise returns + false. + + \sa expand(), expanded(), setExpanded() +*/ +bool QTreeView::isExpanded(const QModelIndex &index) const +{ + Q_D(const QTreeView); + return d->isIndexExpanded(index); +} + +/*! + Sets the item referred to by \a index to either collapse or expanded, + depending on the value of \a expanded. + + \sa expanded(), expand(), isExpanded() +*/ +void QTreeView::setExpanded(const QModelIndex &index, bool expanded) +{ + if (expanded) + this->expand(index); + else + this->collapse(index); +} + +/*! + \since 4.2 + \property QTreeView::sortingEnabled + \brief whether sorting is enabled + + If this property is true, sorting is enabled for the tree; if the property + is false, sorting is not enabled. The default value is false. + + \note In order to avoid performance issues, it is recommended that + sorting is enabled \e after inserting the items into the tree. + Alternatively, you could also insert the items into a list before inserting + the items into the tree. + + \sa sortByColumn() +*/ + +void QTreeView::setSortingEnabled(bool enable) +{ + Q_D(QTreeView); + header()->setSortIndicatorShown(enable); + header()->setClickable(enable); + if (enable) { + //sortByColumn has to be called before we connect or set the sortingEnabled flag + // because otherwise it will not call sort on the model. + sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); + connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder)), Qt::UniqueConnection); + } else { + disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(_q_sortIndicatorChanged(int,Qt::SortOrder))); + } + d->sortingEnabled = enable; +} + +bool QTreeView::isSortingEnabled() const +{ + Q_D(const QTreeView); + return d->sortingEnabled; +} + +/*! + \since 4.2 + \property QTreeView::animated + \brief whether animations are enabled + + If this property is true the treeview will animate expandsion + and collasping of branches. If this property is false, the treeview + will expand or collapse branches immediately without showing + the animation. + + By default, this property is false. +*/ + +void QTreeView::setAnimated(bool animate) +{ + Q_D(QTreeView); + d->animationsEnabled = animate; +} + +bool QTreeView::isAnimated() const +{ + Q_D(const QTreeView); + return d->animationsEnabled; +} + +/*! + \since 4.2 + \property QTreeView::allColumnsShowFocus + \brief whether items should show keyboard focus using all columns + + If this property is true all columns will show focus, otherwise only + one column will show focus. + + The default is false. +*/ + +void QTreeView::setAllColumnsShowFocus(bool enable) +{ + Q_D(QTreeView); + if (d->allColumnsShowFocus == enable) + return; + d->allColumnsShowFocus = enable; + d->viewport->update(); +} + +bool QTreeView::allColumnsShowFocus() const +{ + Q_D(const QTreeView); + return d->allColumnsShowFocus; +} + +/*! + \property QTreeView::wordWrap + \brief the item text word-wrapping policy + \since 4.3 + + If this property is true then the item text is wrapped where + necessary at word-breaks; otherwise it is not wrapped at all. + This property is false by default. + + Note that even if wrapping is enabled, the cell will not be + expanded to fit all text. Ellipsis will be inserted according to + the current \l{QAbstractItemView::}{textElideMode}. +*/ +void QTreeView::setWordWrap(bool on) +{ + Q_D(QTreeView); + if (d->wrapItemText == on) + return; + d->wrapItemText = on; + d->doDelayedItemsLayout(); +} + +bool QTreeView::wordWrap() const +{ + Q_D(const QTreeView); + return d->wrapItemText; +} + + +/*! + \reimp + */ +void QTreeView::keyboardSearch(const QString &search) +{ + Q_D(QTreeView); + if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) + return; + + QModelIndex start; + if (currentIndex().isValid()) + start = currentIndex(); + else + start = d->model->index(0, 0, d->root); + + bool skipRow = false; + bool keyboardTimeWasValid = d->keyboardInputTime.isValid(); + qint64 keyboardInputTimeElapsed = d->keyboardInputTime.restart(); + if (search.isEmpty() || !keyboardTimeWasValid + || keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) { + d->keyboardInput = search; + skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0) + } else { + d->keyboardInput += search; + } + + // special case for searches with same key like 'aaaaa' + bool sameKey = false; + if (d->keyboardInput.length() > 1) { + int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); + sameKey = (c == d->keyboardInput.length()); + if (sameKey) + skipRow = true; + } + + // skip if we are searching for the same key or a new search started + if (skipRow) { + if (indexBelow(start).isValid()) + start = indexBelow(start); + else + start = d->model->index(0, start.column(), d->root); + } + + d->executePostedLayout(); + int startIndex = d->viewIndex(start); + if (startIndex <= -1) + return; + + int previousLevel = -1; + int bestAbove = -1; + int bestBelow = -1; + QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; + for (int i = 0; i < d->viewItems.count(); ++i) { + if ((int)d->viewItems.at(i).level > previousLevel) { + QModelIndex searchFrom = d->viewItems.at(i).index; + if (searchFrom.parent() == start.parent()) + searchFrom = start; + QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString); + if (match.count()) { + int hitIndex = d->viewIndex(match.at(0)); + if (hitIndex >= 0 && hitIndex < startIndex) + bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove); + else if (hitIndex >= startIndex) + bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow); + } + } + previousLevel = d->viewItems.at(i).level; + } + + QModelIndex index; + if (bestBelow > -1) + index = d->viewItems.at(bestBelow).index; + else if (bestAbove > -1) + index = d->viewItems.at(bestAbove).index; + + if (index.isValid()) { + QItemSelectionModel::SelectionFlags flags = (d->selectionMode == SingleSelection + ? QItemSelectionModel::SelectionFlags( + QItemSelectionModel::ClearAndSelect + |d->selectionBehaviorFlags()) + : QItemSelectionModel::SelectionFlags( + QItemSelectionModel::NoUpdate)); + selectionModel()->setCurrentIndex(index, flags); + } +} + +/*! + Returns the rectangle on the viewport occupied by the item at \a index. + If the index is not visible or explicitly hidden, the returned rectangle is invalid. +*/ +QRect QTreeView::visualRect(const QModelIndex &index) const +{ + Q_D(const QTreeView); + + if (!d->isIndexValid(index) || isIndexHidden(index)) + return QRect(); + + d->executePostedLayout(); + + int vi = d->viewIndex(index); + if (vi < 0) + return QRect(); + + bool spanning = d->viewItems.at(vi).spanning; + + // if we have a spanning item, make the selection stretch from left to right + int x = (spanning ? 0 : columnViewportPosition(index.column())); + int w = (spanning ? d->header->length() : columnWidth(index.column())); + // handle indentation + if (index.column() == 0) { + int i = d->indentationForItem(vi); + w -= i; + if (!isRightToLeft()) + x += i; + } + + int y = d->coordinateForItem(vi); + int h = d->itemHeight(vi); + + return QRect(x, y, w, h); +} + +/*! + Scroll the contents of the tree view until the given model item + \a index is visible. The \a hint parameter specifies more + precisely where the item should be located after the + operation. + If any of the parents of the model item are collapsed, they will + be expanded to ensure that the model item is visible. +*/ +void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(QTreeView); + + if (!d->isIndexValid(index)) + return; + + d->executePostedLayout(); + d->updateScrollBars(); + + // Expand all parents if the parent(s) of the node are not expanded. + QModelIndex parent = index.parent(); + while (parent.isValid() && state() == NoState && d->itemsExpandable) { + if (!isExpanded(parent)) + expand(parent); + parent = d->model->parent(parent); + } + + int item = d->viewIndex(index); + if (item < 0) + return; + + QRect area = d->viewport->rect(); + + // vertical + if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int top = verticalScrollBar()->value(); + int bottom = top + verticalScrollBar()->pageStep(); + if (hint == EnsureVisible && item >= top && item < bottom) { + // nothing to do + } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) { + verticalScrollBar()->setValue(item); + } else { // PositionAtBottom or PositionAtCenter + const int currentItemHeight = d->itemHeight(item); + int y = (hint == PositionAtCenter + //we center on the current item with a preference to the top item (ie. -1) + ? area.height() / 2 + currentItemHeight - 1 + //otherwise we simply take the whole space + : area.height()); + if (y > currentItemHeight) { + while (item >= 0) { + y -= d->itemHeight(item); + if (y < 0) { //there is no more space left + item++; + break; + } + item--; + } + } + verticalScrollBar()->setValue(item); + } + } else { // ScrollPerPixel + QRect rect(columnViewportPosition(index.column()), + d->coordinateForItem(item), // ### slow for items outside the view + columnWidth(index.column()), + d->itemHeight(item)); + + if (rect.isEmpty()) { + // nothing to do + } else if (hint == EnsureVisible && area.contains(rect)) { + d->viewport->update(rect); + // nothing to do + } else { + bool above = (hint == EnsureVisible + && (rect.top() < area.top() + || area.height() < rect.height())); + bool below = (hint == EnsureVisible + && rect.bottom() > area.bottom() + && rect.height() < area.height()); + + int verticalValue = verticalScrollBar()->value(); + if (hint == PositionAtTop || above) + verticalValue += rect.top(); + else if (hint == PositionAtBottom || below) + verticalValue += rect.bottom() - area.height(); + else if (hint == PositionAtCenter) + verticalValue += rect.top() - ((area.height() - rect.height()) / 2); + verticalScrollBar()->setValue(verticalValue); + } + } + // horizontal + int viewportWidth = d->viewport->width(); + int horizontalOffset = d->header->offset(); + int horizontalPosition = d->header->sectionPosition(index.column()); + int cellWidth = d->header->sectionSize(index.column()); + + if (hint == PositionAtCenter) { + horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); + } else { + if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition); + else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) + horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); + } +} + +/*! + \reimp +*/ +void QTreeView::timerEvent(QTimerEvent *event) +{ + Q_D(QTreeView); + if (event->timerId() == d->columnResizeTimerID) { + updateGeometries(); + killTimer(d->columnResizeTimerID); + d->columnResizeTimerID = 0; + QRect rect; + int viewportHeight = d->viewport->height(); + int viewportWidth = d->viewport->width(); + for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) { + int column = d->columnsToUpdate.at(i); + int x = columnViewportPosition(column); + if (isRightToLeft()) + rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); + else + rect |= QRect(x, 0, viewportWidth - x, viewportHeight); + } + d->viewport->update(rect.normalized()); + d->columnsToUpdate.clear(); + } else if (event->timerId() == d->openTimer.timerId()) { + QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); + if (state() == QAbstractItemView::DraggingState + && d->viewport->rect().contains(pos)) { + QModelIndex index = indexAt(pos); + setExpanded(index, !isExpanded(index)); + } + d->openTimer.stop(); + } + + QAbstractItemView::timerEvent(event); +} + +/*! + \reimp +*/ +#ifndef QT_NO_DRAGANDDROP +void QTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + Q_D(QTreeView); + if (d->autoExpandDelay >= 0) + d->openTimer.start(d->autoExpandDelay, this); + QAbstractItemView::dragMoveEvent(event); +} +#endif + +/*! + \reimp +*/ +bool QTreeView::viewportEvent(QEvent *event) +{ + Q_D(QTreeView); + switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: { + QHoverEvent *he = static_cast(event); + int oldBranch = d->hoverBranch; + d->hoverBranch = d->itemDecorationAt(he->pos()); + if (oldBranch != d->hoverBranch) { + //we need to paint the whole items (including the decoration) so that when the user + //moves the mouse over those elements they are updated + if (oldBranch >= 0) { + int y = d->coordinateForItem(oldBranch); + int h = d->itemHeight(oldBranch); + viewport()->update(QRect(0, y, viewport()->width(), h)); + } + if (d->hoverBranch >= 0) { + int y = d->coordinateForItem(d->hoverBranch); + int h = d->itemHeight(d->hoverBranch); + viewport()->update(QRect(0, y, viewport()->width(), h)); + } + } + break; } + default: + break; + } + return QAbstractItemView::viewportEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::paintEvent(QPaintEvent *event) +{ + Q_D(QTreeView); + d->executePostedLayout(); + QPainter painter(viewport()); +#ifndef QT_NO_ANIMATION + if (d->isAnimating()) { + drawTree(&painter, event->region() - d->animatedOperation.rect()); + d->drawAnimatedOperation(&painter); + } else +#endif //QT_NO_ANIMATION + { + drawTree(&painter, event->region()); +#ifndef QT_NO_DRAGANDDROP + d->paintDropIndicator(&painter); +#endif + } +} + +void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const +{ + Q_Q(const QTreeView); + if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q)) + return; + int rowHeight = defaultItemHeight; + if (rowHeight <= 0) { + rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height(); + if (rowHeight <= 0) + return; + } + while (y <= bottom) { + option->rect.setRect(0, y, viewport->width(), rowHeight); + if (current & 1) { + option->features |= QStyleOptionViewItemV2::Alternate; + } else { + option->features &= ~QStyleOptionViewItemV2::Alternate; + } + ++current; + q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q); + y += rowHeight; + } +} + +bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos) +{ + Q_Q(QTreeView); + // we want to handle mousePress in EditingState (persistent editors) + if ((state != QAbstractItemView::NoState + && state != QAbstractItemView::EditingState) + || !viewport->rect().contains(pos)) + return true; + + int i = itemDecorationAt(pos); + if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) { + if (viewItems.at(i).expanded) + collapse(i, true); + else + expand(i, true); + if (!isAnimating()) { + q->updateGeometries(); + viewport->update(); + } + return true; + } + return false; +} + +void QTreeViewPrivate::_q_modelDestroyed() +{ + //we need to clear that list because it contais QModelIndex to + //the model currently being destroyed + viewItems.clear(); + QAbstractItemViewPrivate::_q_modelDestroyed(); +} + +/*! + \reimp + + We have a QTreeView way of knowing what elements are on the viewport +*/ +QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const +{ + Q_ASSERT(r); + return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r); + Q_Q(const QTreeView); + QRect &rect = *r; + const QRect viewportRect = viewport->rect(); + int itemOffset = 0; + int row = firstVisibleItem(&itemOffset); + QPair startEnd = startAndEndColumns(viewportRect); + QVector columns; + for (int i = startEnd.first; i <= startEnd.second; ++i) { + int logical = header->logicalIndex(i); + if (!header->isSectionHidden(logical)) + columns += logical; + } + QSet visibleIndexes; + for (; itemOffset < viewportRect.bottom() && row < viewItems.count(); ++row) { + const QModelIndex &index = viewItems.at(row).index; + for (int colIndex = 0; colIndex < columns.count(); ++colIndex) + visibleIndexes += index.sibling(index.row(), columns.at(colIndex)); + itemOffset += itemHeight(row); + } + + //now that we have the visible indexes, we can try to find those which are selected + QItemViewPaintPairs ret; + for (int i = 0; i < indexes.count(); ++i) { + const QModelIndex &index = indexes.at(i); + if (visibleIndexes.contains(index)) { + const QRect current = q->visualRect(index); + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + + +/*! + \since 4.2 + Draws the part of the tree intersecting the given \a region using the specified + \a painter. + + \sa paintEvent() +*/ +void QTreeView::drawTree(QPainter *painter, const QRegion ®ion) const +{ + Q_D(const QTreeView); + const QVector viewItems = d->viewItems; + + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QStyle::State state = option.state; + d->current = 0; + + if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) { + d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); + return; + } + + int firstVisibleItemOffset = 0; + const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset); + if (firstVisibleItem < 0) { + d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); + return; + } + + const int viewportWidth = d->viewport->width(); + + QVector rects = region.rects(); + QVector drawn; + bool multipleRects = (rects.size() > 1); + for (int a = 0; a < rects.size(); ++a) { + const QRect area = (multipleRects + ? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height()) + : rects.at(a)); + d->leftAndRight = d->startAndEndColumns(area); + + int i = firstVisibleItem; // the first item at the top of the viewport + int y = firstVisibleItemOffset; // we may only see part of the first item + + // start at the top of the viewport and iterate down to the update area + for (; i < viewItems.count(); ++i) { + const int itemHeight = d->itemHeight(i); + if (y + itemHeight > area.top()) + break; + y += itemHeight; + } + + // paint the visible rows + for (; i < viewItems.count() && y <= area.bottom(); ++i) { + const int itemHeight = d->itemHeight(i); + option.rect.setRect(0, y, viewportWidth, itemHeight); + option.state = state | (viewItems.at(i).expanded ? QStyle::State_Open : QStyle::State_None) + | (viewItems.at(i).hasChildren ? QStyle::State_Children : QStyle::State_None) + | (viewItems.at(i).hasMoreSiblings ? QStyle::State_Sibling : QStyle::State_None); + d->current = i; + d->spanning = viewItems.at(i).spanning; + if (!multipleRects || !drawn.contains(i)) { + drawRow(painter, option, viewItems.at(i).index); + if (multipleRects) // even if the rect only intersects the item, + drawn.append(i); // the entire item will be painted + } + y += itemHeight; + } + + if (y <= area.bottom()) { + d->current = i; + d->paintAlternatingRowColors(painter, &option, y, area.bottom()); + } + } +} + +/// ### move to QObject :) +static inline bool ancestorOf(QObject *widget, QObject *other) +{ + for (QObject *parent = other; parent != 0; parent = parent->parent()) { + if (parent == widget) + return true; + } + return false; +} + +/*! + Draws the row in the tree view that contains the model item \a index, + using the \a painter given. The \a option control how the item is + displayed. + + \sa setAlternatingRowColors() +*/ +void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + Q_D(const QTreeView); + QStyleOptionViewItemV4 opt = option; + const QPoint offset = d->scrollDelayOffset; + const int y = option.rect.y() + offset.y(); + const QModelIndex parent = index.parent(); + const QHeaderView *header = d->header; + const QModelIndex current = currentIndex(); + const QModelIndex hover = d->hover; + const bool reverse = isRightToLeft(); + const QStyle::State state = opt.state; + const bool spanning = d->spanning; + const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first); + const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second); + const bool alternate = d->alternatingColors; + const bool enabled = (state & QStyle::State_Enabled) != 0; + const bool allColumnsShowFocus = d->allColumnsShowFocus; + + + // when the row contains an index widget which has focus, + // we want to paint the entire row as active + bool indexWidgetHasFocus = false; + if ((current.row() == index.row()) && !d->editorIndexHash.isEmpty()) { + const int r = index.row(); + QWidget *fw = QApplication::focusWidget(); + for (int c = 0; c < header->count(); ++c) { + QModelIndex idx = d->model->index(r, c, parent); + if (QWidget *editor = indexWidget(idx)) { + if (ancestorOf(editor, fw)) { + indexWidgetHasFocus = true; + break; + } + } + } + } + + const bool widgetHasFocus = hasFocus(); + bool currentRowHasFocus = false; + if (allColumnsShowFocus && widgetHasFocus && current.isValid()) { + // check if the focus index is before or after the visible columns + const int r = index.row(); + for (int c = 0; c < left && !currentRowHasFocus; ++c) { + QModelIndex idx = d->model->index(r, c, parent); + currentRowHasFocus = (idx == current); + } + QModelIndex parent = d->model->parent(index); + for (int c = right; c < header->count() && !currentRowHasFocus; ++c) { + currentRowHasFocus = (d->model->index(r, c, parent) == current); + } + } + + // ### special case: treeviews with multiple columns draw + // the selections differently than with only one column + opt.showDecorationSelected = (d->selectionBehavior & SelectRows) + || option.showDecorationSelected; + + int width, height = option.rect.height(); + int position; + QModelIndex modelIndex; + int columnCount = header->count(); + const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows + && index.parent() == hover.parent() + && index.row() == hover.row(); + + /* 'left' and 'right' are the left-most and right-most visible visual indices. + Compute the first visible logical indices before and after the left and right. + We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */ + int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1; + for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) { + int logicalIndex = header->logicalIndex(visualIndex); + if (!header->isSectionHidden(logicalIndex)) { + logicalIndexBeforeLeft = logicalIndex; + break; + } + } + QVector logicalIndices; // vector of currently visibly logical indices + for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) { + int logicalIndex = header->logicalIndex(visualIndex); + if (!header->isSectionHidden(logicalIndex)) { + if (visualIndex > right) { + logicalIndexAfterRight = logicalIndex; + break; + } + logicalIndices.append(logicalIndex); + } + } + + for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) { + int headerSection = logicalIndices.at(currentLogicalSection); + position = columnViewportPosition(headerSection) + offset.x(); + width = header->sectionSize(headerSection); + + if (spanning) { + int lastSection = header->logicalIndex(header->count() - 1); + if (!reverse) { + width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position; + } else { + width += position - columnViewportPosition(lastSection); + position = columnViewportPosition(lastSection); + } + } + + modelIndex = d->model->index(index.row(), headerSection, parent); + if (!modelIndex.isValid()) + continue; + opt.state = state; + + // determine the viewItemPosition depending on the position of column 0 + int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices.count() + ? logicalIndexAfterRight + : logicalIndices.at(currentLogicalSection + 1); + int prevLogicalSection = currentLogicalSection - 1 < 0 + ? logicalIndexBeforeLeft + : logicalIndices.at(currentLogicalSection - 1); + if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1) + || (headerSection == 0 && nextLogicalSection == -1) || spanning) + opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1)) + opt.viewItemPosition = QStyleOptionViewItemV4::Beginning; + else if (nextLogicalSection == 0 || nextLogicalSection == -1) + opt.viewItemPosition = QStyleOptionViewItemV4::End; + else + opt.viewItemPosition = QStyleOptionViewItemV4::Middle; + + // fake activeness when row editor has focus + if (indexWidgetHasFocus) + opt.state |= QStyle::State_Active; + + if (d->selectionModel->isSelected(modelIndex)) + opt.state |= QStyle::State_Selected; + if (widgetHasFocus && (current == modelIndex)) { + if (allColumnsShowFocus) + currentRowHasFocus = true; + else + opt.state |= QStyle::State_HasFocus; + } + if ((hoverRow || modelIndex == hover) + && (option.showDecorationSelected || (d->hoverBranch == -1))) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + + if (enabled) { + QPalette::ColorGroup cg; + if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) { + opt.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } else if (opt.state & QStyle::State_Active) { + cg = QPalette::Active; + } else { + cg = QPalette::Inactive; + } + opt.palette.setCurrentColorGroup(cg); + } + + if (alternate) { + if (d->current & 1) { + opt.features |= QStyleOptionViewItemV2::Alternate; + } else { + opt.features &= ~QStyleOptionViewItemV2::Alternate; + } + } + + /* Prior to Qt 4.3, the background of the branch (in selected state and + alternate row color was provided by the view. For backward compatibility, + this is now delegated to the style using PE_PanelViewItemRow which + does the appropriate fill */ + if (headerSection == 0) { + const int i = d->indentationForItem(d->current); + QRect branches(reverse ? position + width - i : position, y, i, height); + const bool setClipRect = branches.width() > width; + if (setClipRect) { + painter->save(); + painter->setClipRect(QRect(position, y, width, height)); + } + // draw background for the branch (selection + alternate row) + opt.rect = branches; + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + + // draw background of the item (only alternate row). rest of the background + // is provided by the delegate + QStyle::State oldState = opt.state; + opt.state &= ~QStyle::State_Selected; + opt.rect.setRect(reverse ? position : i + position, y, width - i, height); + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + opt.state = oldState; + + drawBranches(painter, branches, index); + if (setClipRect) + painter->restore(); + } else { + QStyle::State oldState = opt.state; + opt.state &= ~QStyle::State_Selected; + opt.rect.setRect(position, y, width, height); + style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); + opt.state = oldState; + } + + d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); + } + + if (currentRowHasFocus) { + QStyleOptionFocusRect o; + o.QStyleOption::operator=(option); + o.state |= QStyle::State_KeyboardFocusChange; + QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index) + ? QPalette::Highlight : QPalette::Background); + int x = 0; + if (!option.showDecorationSelected) + x = header->sectionPosition(0) + d->indentationForItem(d->current); + QRect focusRect(x - header->offset(), y, header->length() - x, height); + o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); + // if we show focus on all columns and the first section is moved, + // we have to split the focus rect into two rects + if (allColumnsShowFocus && !option.showDecorationSelected + && header->sectionsMoved() && (header->visualIndex(0) != 0)) { + QRect sectionRect(0, y, header->sectionPosition(0), height); + o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); + } + } +} + +/*! + Draws the branches in the tree view on the same row as the model item + \a index, using the \a painter given. The branches are drawn in the + rectangle specified by \a rect. +*/ +void QTreeView::drawBranches(QPainter *painter, const QRect &rect, + const QModelIndex &index) const +{ + Q_D(const QTreeView); + const bool reverse = isRightToLeft(); + const int indent = d->indent; + const int outer = d->rootDecoration ? 0 : 1; + const int item = d->current; + const QTreeViewItem &viewItem = d->viewItems.at(item); + int level = viewItem.level; + QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height()); + + QModelIndex parent = index.parent(); + QModelIndex current = parent; + QModelIndex ancestor = current.parent(); + + QStyleOptionViewItemV2 opt = viewOptions(); + QStyle::State extraFlags = QStyle::State_None; + if (isEnabled()) + extraFlags |= QStyle::State_Enabled; + if (window()->isActiveWindow()) + extraFlags |= QStyle::State_Active; + QPoint oldBO = painter->brushOrigin(); + if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) + painter->setBrushOrigin(QPoint(0, verticalOffset())); + + if (d->alternatingColors) { + if (d->current & 1) { + opt.features |= QStyleOptionViewItemV2::Alternate; + } else { + opt.features &= ~QStyleOptionViewItemV2::Alternate; + } + } + + // When hovering over a row, pass State_Hover for painting the branch + // indicators if it has the decoration (aka branch) selected. + bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows + && opt.showDecorationSelected + && index.parent() == d->hover.parent() + && index.row() == d->hover.row(); + + if (d->selectionModel->isSelected(index)) + extraFlags |= QStyle::State_Selected; + + if (level >= outer) { + // start with the innermost branch + primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent); + opt.rect = primitive; + + const bool expanded = viewItem.expanded; + const bool children = viewItem.hasChildren; + bool moreSiblings = viewItem.hasMoreSiblings; + + opt.state = QStyle::State_Item | extraFlags + | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None) + | (children ? QStyle::State_Children : QStyle::State_None) + | (expanded ? QStyle::State_Open : QStyle::State_None); + if (hoverRow || item == d->hoverBranch) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); + } + // then go out level by level + for (--level; level >= outer; --level) { // we have already drawn the innermost branch + primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent); + opt.rect = primitive; + opt.state = extraFlags; + bool moreSiblings = false; + if (d->hiddenIndexes.isEmpty()) { + moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row()); + } else { + int successor = item + viewItem.total + 1; + while (successor < d->viewItems.size() + && d->viewItems.at(successor).level >= uint(level)) { + const QTreeViewItem &successorItem = d->viewItems.at(successor); + if (successorItem.level == uint(level)) { + moreSiblings = true; + break; + } + successor += successorItem.total + 1; + } + } + if (moreSiblings) + opt.state |= QStyle::State_Sibling; + if (hoverRow || item == d->hoverBranch) + opt.state |= QStyle::State_MouseOver; + else + opt.state &= ~QStyle::State_MouseOver; + style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); + current = ancestor; + ancestor = current.parent(); + } + painter->setBrushOrigin(oldBO); +} + +/*! + \reimp +*/ +void QTreeView::mousePressEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + bool handled = false; + if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress) + handled = d->expandOrCollapseItemAtPos(event->pos()); + if (!handled && d->itemDecorationAt(event->pos()) == -1) + QAbstractItemView::mousePressEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (d->itemDecorationAt(event->pos()) == -1) { + QAbstractItemView::mouseReleaseEvent(event); + } else { + if (state() == QAbstractItemView::DragSelectingState) + setState(QAbstractItemView::NoState); + if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease) + d->expandOrCollapseItemAtPos(event->pos()); + } +} + +/*! + \reimp +*/ +void QTreeView::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (state() != NoState || !d->viewport->rect().contains(event->pos())) + return; + + int i = d->itemDecorationAt(event->pos()); + if (i == -1) { + i = d->itemAtCoordinate(event->y()); + if (i == -1) + return; // user clicked outside the items + + const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index; + const QPersistentModelIndex persistent = indexAt(event->pos()); + + if (d->pressedIndex != persistent) { + mousePressEvent(event); + return; + } + + // signal handlers may change the model + emit doubleClicked(persistent); + + if (!persistent.isValid()) + return; + + if (edit(persistent, DoubleClicked, event) || state() != NoState) + return; // the double click triggered editing + + if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) + emit activated(persistent); + + d->executePostedLayout(); // we need to make sure viewItems is updated + if (d->itemsExpandable + && d->expandsOnDoubleClick + && d->hasVisibleChildren(persistent)) { + if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) { + // find the new index of the item + for (i = 0; i < d->viewItems.count(); ++i) { + if (d->viewItems.at(i).index == firstColumnIndex) + break; + } + if (i == d->viewItems.count()) + return; + } + if (d->viewItems.at(i).expanded) + d->collapse(i, true); + else + d->expand(i, true); + updateGeometries(); + viewport()->update(); + } + } +} + +/*! + \reimp +*/ +void QTreeView::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QTreeView); + if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ? + QAbstractItemView::mouseMoveEvent(event); +} + +/*! + \reimp +*/ +void QTreeView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QTreeView); + QModelIndex current = currentIndex(); + //this is the management of the expansion + if (d->isIndexValid(current) && d->model && d->itemsExpandable) { + switch (event->key()) { + case Qt::Key_Asterisk: { + QStack parents; + parents.push(current); + while (!parents.isEmpty()) { + QModelIndex parent = parents.pop(); + for (int row = 0; row < d->model->rowCount(parent); ++row) { + QModelIndex child = d->model->index(row, 0, parent); + if (!d->isIndexValid(child)) + break; + parents.push(child); + expand(child); + } + } + expand(current); + break; } + case Qt::Key_Plus: + expand(current); + break; + case Qt::Key_Minus: + collapse(current); + break; + } + } + + QAbstractItemView::keyPressEvent(event); +} + +/*! + \reimp +*/ +QModelIndex QTreeView::indexAt(const QPoint &point) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + + int visualIndex = d->itemAtCoordinate(point.y()); + QModelIndex idx = d->modelIndex(visualIndex); + if (!idx.isValid()) + return QModelIndex(); + + if (d->viewItems.at(visualIndex).spanning) + return idx; + + int column = d->columnAt(point.x()); + if (column == idx.column()) + return idx; + if (column < 0) + return QModelIndex(); + return idx.sibling(idx.row(), column); +} + +/*! + Returns the model index of the item above \a index. +*/ +QModelIndex QTreeView::indexAbove(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index)) + return QModelIndex(); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (--i < 0) + return QModelIndex(); + return d->viewItems.at(i).index; +} + +/*! + Returns the model index of the item below \a index. +*/ +QModelIndex QTreeView::indexBelow(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index)) + return QModelIndex(); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (++i >= d->viewItems.count()) + return QModelIndex(); + return d->viewItems.at(i).index; +} + +/*! + \internal + + Lays out the items in the tree view. +*/ +void QTreeView::doItemsLayout() +{ + Q_D(QTreeView); + if (d->hasRemovedItems) { + //clean the QSet that may contains old (and this invalid) indexes + d->hasRemovedItems = false; + QSet::iterator it = d->expandedIndexes.begin(); + while (it != d->expandedIndexes.constEnd()) { + if (!it->isValid()) + it = d->expandedIndexes.erase(it); + else + ++it; + } + it = d->hiddenIndexes.begin(); + while (it != d->hiddenIndexes.constEnd()) { + if (!it->isValid()) + it = d->hiddenIndexes.erase(it); + else + ++it; + } + } + d->viewItems.clear(); // prepare for new layout + QModelIndex parent = d->root; + if (d->model->hasChildren(parent)) { + d->layout(-1); + } + QAbstractItemView::doItemsLayout(); + d->header->doItemsLayout(); +} + +/*! + \reimp +*/ +void QTreeView::reset() +{ + Q_D(QTreeView); + d->expandedIndexes.clear(); + d->hiddenIndexes.clear(); + d->spanningIndexes.clear(); + d->viewItems.clear(); + QAbstractItemView::reset(); +} + +/*! + Returns the horizontal offset of the items in the treeview. + + Note that the tree view uses the horizontal header section + positions to determine the positions of columns in the view. + + \sa verticalOffset() +*/ +int QTreeView::horizontalOffset() const +{ + Q_D(const QTreeView); + return d->header->offset(); +} + +/*! + Returns the vertical offset of the items in the tree view. + + \sa horizontalOffset() +*/ +int QTreeView::verticalOffset() const +{ + Q_D(const QTreeView); + if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (d->uniformRowHeights) + return verticalScrollBar()->value() * d->defaultItemHeight; + // If we are scrolling per item and have non-uniform row heights, + // finding the vertical offset in pixels is going to be relatively slow. + // ### find a faster way to do this + d->executePostedLayout(); + int offset = 0; + for (int i = 0; i < d->viewItems.count(); ++i) { + if (i == verticalScrollBar()->value()) + return offset; + offset += d->itemHeight(i); + } + return 0; + } + // scroll per pixel + return verticalScrollBar()->value(); +} + +/*! + Move the cursor in the way described by \a cursorAction, using the + information provided by the button \a modifiers. +*/ +QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + Q_D(QTreeView); + Q_UNUSED(modifiers); + + d->executePostedLayout(); + + QModelIndex current = currentIndex(); + if (!current.isValid()) { + int i = d->below(-1); + int c = 0; + while (c < d->header->count() && d->header->isSectionHidden(c)) + ++c; + if (i < d->viewItems.count() && c < d->header->count()) { + return d->modelIndex(i, c); + } + return QModelIndex(); + } + int vi = -1; +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + // Selection behavior is slightly different on the Mac. + if (d->selectionMode == QAbstractItemView::ExtendedSelection + && d->selectionModel + && d->selectionModel->hasSelection()) { + + const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown); + const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious); + const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier); + + // Use the outermost index in the selection as the current index + if (!contiguousSelection && (moveUpDown || moveNextPrev)) { + + // Find outermost index. + const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious); + int index = useTopIndex ? INT_MAX : INT_MIN; + const QItemSelection selection = d->selectionModel->selection(); + for (int i = 0; i < selection.count(); ++i) { + const QItemSelectionRange &range = selection.at(i); + int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight()); + if (candidate >= 0) + index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate); + } + + if (index >= 0 && index < INT_MAX) + vi = index; + } + } +#endif + if (vi < 0) + vi = qMax(0, d->viewIndex(current)); + + if (isRightToLeft()) { + if (cursorAction == MoveRight) + cursorAction = MoveLeft; + else if (cursorAction == MoveLeft) + cursorAction = MoveRight; + } + switch (cursorAction) { + case MoveNext: + case MoveDown: +#ifdef QT_KEYPAD_NAVIGATION + if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled()) + return d->model->index(0, current.column(), d->root); +#endif + return d->modelIndex(d->below(vi), current.column()); + case MovePrevious: + case MoveUp: +#ifdef QT_KEYPAD_NAVIGATION + if (vi == 0 && QApplication::keypadNavigationEnabled()) + return d->modelIndex(d->viewItems.count() - 1, current.column()); +#endif + return d->modelIndex(d->above(vi), current.column()); + case MoveLeft: { + QScrollBar *sb = horizontalScrollBar(); + if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) { + d->collapse(vi, true); + d->moveCursorUpdatedView = true; + } else { + bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); + if (descend) { + QModelIndex par = current.parent(); + if (par.isValid() && par != rootIndex()) + return par; + else + descend = false; + } + if (!descend) { + if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { + int visualColumn = d->header->visualIndex(current.column()) - 1; + while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn))) + visualColumn--; + int newColumn = d->header->logicalIndex(visualColumn); + QModelIndex next = current.sibling(current.row(), newColumn); + if (next.isValid()) + return next; + } + + int oldValue = sb->value(); + sb->setValue(sb->value() - sb->singleStep()); + if (oldValue != sb->value()) + d->moveCursorUpdatedView = true; + } + + } + updateGeometries(); + viewport()->update(); + break; + } + case MoveRight: + if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable + && d->hasVisibleChildren(d->viewItems.at(vi).index)) { + d->expand(vi, true); + d->moveCursorUpdatedView = true; + } else { + bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); + if (descend) { + QModelIndex idx = d->modelIndex(d->below(vi)); + if (idx.parent() == current) + return idx; + else + descend = false; + } + if (!descend) { + if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { + int visualColumn = d->header->visualIndex(current.column()) + 1; + while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn))) + visualColumn++; + + QModelIndex next = current.sibling(current.row(), visualColumn); + if (next.isValid()) + return next; + } + + //last restort: we change the scrollbar value + QScrollBar *sb = horizontalScrollBar(); + int oldValue = sb->value(); + sb->setValue(sb->value() + sb->singleStep()); + if (oldValue != sb->value()) + d->moveCursorUpdatedView = true; + } + } + updateGeometries(); + viewport()->update(); + break; + case MovePageUp: + return d->modelIndex(d->pageUp(vi), current.column()); + case MovePageDown: + return d->modelIndex(d->pageDown(vi), current.column()); + case MoveHome: + return d->model->index(0, current.column(), d->root); + case MoveEnd: + return d->modelIndex(d->viewItems.count() - 1, current.column()); + } + return current; +} + +/*! + Applies the selection \a command to the items in or touched by the + rectangle, \a rect. + + \sa selectionCommand() +*/ +void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTreeView); + if (!selectionModel() || rect.isNull()) + return; + + d->executePostedLayout(); + QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right()) + : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())); + QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) : + qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())); + QModelIndex topLeft = indexAt(tl); + QModelIndex bottomRight = indexAt(br); + if (!topLeft.isValid() && !bottomRight.isValid()) { + if (command & QItemSelectionModel::Clear) + selectionModel()->clear(); + return; + } + if (!topLeft.isValid() && !d->viewItems.isEmpty()) + topLeft = d->viewItems.first().index; + if (!bottomRight.isValid() && !d->viewItems.isEmpty()) { + const int column = d->header->logicalIndex(d->header->count() - 1); + const QModelIndex index = d->viewItems.last().index; + bottomRight = index.sibling(index.row(), column); + } + + if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight)) + return; + + d->select(topLeft, bottomRight, command); +} + +/*! + Returns the rectangle from the viewport of the items in the given + \a selection. + + Since 4.7, the returned region only contains rectangles intersecting + (or included in) the viewport. +*/ +QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const +{ + Q_D(const QTreeView); + if (selection.isEmpty()) + return QRegion(); + + QRegion selectionRegion; + const QRect &viewportRect = d->viewport->rect(); + for (int i = 0; i < selection.count(); ++i) { + QItemSelectionRange range = selection.at(i); + if (!range.isValid()) + continue; + QModelIndex parent = range.parent(); + QModelIndex leftIndex = range.topLeft(); + int columnCount = d->model->columnCount(parent); + while (leftIndex.isValid() && isIndexHidden(leftIndex)) { + if (leftIndex.column() + 1 < columnCount) + leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent); + else + leftIndex = QModelIndex(); + } + if (!leftIndex.isValid()) + continue; + const QRect leftRect = visualRect(leftIndex); + int top = leftRect.top(); + QModelIndex rightIndex = range.bottomRight(); + while (rightIndex.isValid() && isIndexHidden(rightIndex)) { + if (rightIndex.column() - 1 >= 0) + rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent); + else + rightIndex = QModelIndex(); + } + if (!rightIndex.isValid()) + continue; + const QRect rightRect = visualRect(rightIndex); + int bottom = rightRect.bottom(); + if (top > bottom) + qSwap(top, bottom); + int height = bottom - top + 1; + if (d->header->sectionsMoved()) { + for (int c = range.left(); c <= range.right(); ++c) { + const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height); + if (viewportRect.intersects(rangeRect)) + selectionRegion += rangeRect; + } + } else { + QRect combined = leftRect|rightRect; + combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left())); + if (viewportRect.intersects(combined)) + selectionRegion += combined; + } + } + return selectionRegion; +} + +/*! + \reimp +*/ +QModelIndexList QTreeView::selectedIndexes() const +{ + QModelIndexList viewSelected; + QModelIndexList modelSelected; + if (selectionModel()) + modelSelected = selectionModel()->selectedIndexes(); + for (int i = 0; i < modelSelected.count(); ++i) { + // check that neither the parents nor the index is hidden before we add + QModelIndex index = modelSelected.at(i); + while (index.isValid() && !isIndexHidden(index)) + index = index.parent(); + if (index.isValid()) + continue; + viewSelected.append(modelSelected.at(i)); + } + return viewSelected; +} + +/*! + Scrolls the contents of the tree view by (\a dx, \a dy). +*/ +void QTreeView::scrollContentsBy(int dx, int dy) +{ + Q_D(QTreeView); + + d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling + + dx = isRightToLeft() ? -dx : dx; + if (dx) { + if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { + int oldOffset = d->header->offset(); + if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) + d->header->setOffsetToLastSection(); + else + d->header->setOffsetToSectionPosition(horizontalScrollBar()->value()); + int newOffset = d->header->offset(); + dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; + } else { + d->header->setOffset(horizontalScrollBar()->value()); + } + } + + const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight; + if (d->viewItems.isEmpty() || itemHeight == 0) + return; + + // guestimate the number of items in the viewport + int viewCount = d->viewport->height() / itemHeight; + int maxDeltaY = qMin(d->viewItems.count(), viewCount); + // no need to do a lot of work if we are going to redraw the whole thing anyway + if (qAbs(dy) > qAbs(maxDeltaY) && d->editorIndexHash.isEmpty()) { + verticalScrollBar()->update(); + d->viewport->update(); + return; + } + + if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) { + int currentScrollbarValue = verticalScrollBar()->value(); + int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy) + int currentViewIndex = currentScrollbarValue; // the first visible item + int previousViewIndex = previousScrollbarValue; + const QVector viewItems = d->viewItems; + dy = 0; + if (previousViewIndex < currentViewIndex) { // scrolling down + for (int i = previousViewIndex; i < currentViewIndex; ++i) { + if (i < d->viewItems.count()) + dy -= d->itemHeight(i); + } + } else if (previousViewIndex > currentViewIndex) { // scrolling up + for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) { + if (i < d->viewItems.count()) + dy += d->itemHeight(i); + } + } + } + + d->scrollContentsBy(dx, dy); +} + +/*! + This slot is called whenever a column has been moved. +*/ +void QTreeView::columnMoved() +{ + Q_D(QTreeView); + updateEditorGeometries(); + d->viewport->update(); +} + +/*! + \internal +*/ +void QTreeView::reexpand() +{ + // do nothing +} + +/*! + Informs the view that the rows from the \a start row to the \a end row + inclusive have been inserted into the \a parent model item. +*/ +void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + // if we are going to do a complete relayout anyway, there is no need to update + if (d->delayedPendingLayout) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + //don't add a hierarchy on a column != 0 + if (parent.column() != 0 && parent.isValid()) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + const int parentRowCount = d->model->rowCount(parent); + const int delta = end - start + 1; + if (parent != d->root && !d->isIndexExpanded(parent) && parentRowCount > delta) { + QAbstractItemView::rowsInserted(parent, start, end); + return; + } + + const int parentItem = d->viewIndex(parent); + if (((parentItem != -1) && d->viewItems.at(parentItem).expanded) + || (parent == d->root)) { + d->doDelayedItemsLayout(); + } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) { + // the parent just went from 0 children to more. update to re-paint the decoration + d->viewItems[parentItem].hasChildren = true; + viewport()->update(); + } + QAbstractItemView::rowsInserted(parent, start, end); +} + +/*! + Informs the view that the rows from the \a start row to the \a end row + inclusive are about to removed from the given \a parent model item. +*/ +void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); + d->viewItems.clear(); +} + +/*! + \since 4.1 + + Informs the view that the rows from the \a start row to the \a end row + inclusive have been removed from the given \a parent model item. +*/ +void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(QTreeView); + d->viewItems.clear(); + d->doDelayedItemsLayout(); + d->hasRemovedItems = true; + d->_q_rowsRemoved(parent, start, end); +} + +/*! + Informs the tree view that the number of columns in the tree view has + changed from \a oldCount to \a newCount. +*/ +void QTreeView::columnCountChanged(int oldCount, int newCount) +{ + Q_D(QTreeView); + if (oldCount == 0 && newCount > 0) { + //if the first column has just been added we need to relayout. + d->doDelayedItemsLayout(); + } + + if (isVisible()) + updateGeometries(); + viewport()->update(); +} + +/*! + Resizes the \a column given to the size of its contents. + + \sa columnWidth(), setColumnWidth() +*/ +void QTreeView::resizeColumnToContents(int column) +{ + Q_D(QTreeView); + d->executePostedLayout(); + if (column < 0 || column >= d->header->count()) + return; + int contents = sizeHintForColumn(column); + int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column); + d->header->resizeSection(column, qMax(contents, header)); +} + +/*! + \obsolete + \overload + + Sorts the model by the values in the given \a column. +*/ +void QTreeView::sortByColumn(int column) +{ + Q_D(QTreeView); + sortByColumn(column, d->header->sortIndicatorOrder()); +} + +/*! + \since 4.2 + + Sets the model up for sorting by the values in the given \a column and \a order. + + \a column may be -1, in which case no sort indicator will be shown + and the model will return to its natural, unsorted order. Note that not + all models support this and may even crash in this case. + + \sa sortingEnabled +*/ +void QTreeView::sortByColumn(int column, Qt::SortOrder order) +{ + Q_D(QTreeView); + + //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts + d->header->setSortIndicator(column, order); + //If sorting is not enabled, force to sort now. + if (!d->sortingEnabled) + d->model->sort(column, order); +} + +/*! + \reimp +*/ +void QTreeView::selectAll() +{ + Q_D(QTreeView); + if (!selectionModel()) + return; + SelectionMode mode = d->selectionMode; + d->executePostedLayout(); //make sure we lay out the items + if (mode != SingleSelection && !d->viewItems.isEmpty()) { + const QModelIndex &idx = d->viewItems.last().index; + QModelIndex lastItemIndex = idx.sibling(idx.row(), d->model->columnCount(idx.parent()) - 1); + d->select(d->viewItems.first().index, lastItemIndex, + QItemSelectionModel::ClearAndSelect + |QItemSelectionModel::Rows); + } +} + +/*! + \since 4.2 + Expands all expandable items. + + Warning: if the model contains a large number of items, + this function will take some time to execute. + + \sa collapseAll() expand() collapse() setExpanded() +*/ +void QTreeView::expandAll() +{ + Q_D(QTreeView); + d->viewItems.clear(); + d->interruptDelayedItemsLayout(); + d->layout(-1, true); + updateGeometries(); + d->viewport->update(); +} + +/*! + \since 4.2 + + Collapses all expanded items. + + \sa expandAll() expand() collapse() setExpanded() +*/ +void QTreeView::collapseAll() +{ + Q_D(QTreeView); + d->expandedIndexes.clear(); + doItemsLayout(); +} + +/*! + \since 4.3 + Expands all expandable items to the given \a depth. + + \sa expandAll() collapseAll() expand() collapse() setExpanded() +*/ +void QTreeView::expandToDepth(int depth) +{ + Q_D(QTreeView); + d->viewItems.clear(); + d->expandedIndexes.clear(); + d->interruptDelayedItemsLayout(); + d->layout(-1); + for (int i = 0; i < d->viewItems.count(); ++i) { + if (d->viewItems.at(i).level <= (uint)depth) { + d->viewItems[i].expanded = true; + d->layout(i); + d->storeExpanded(d->viewItems.at(i).index); + } + } + updateGeometries(); + d->viewport->update(); +} + +/*! + This function is called whenever \a{column}'s size is changed in + the header. \a oldSize and \a newSize give the previous size and + the new size in pixels. + + \sa setColumnWidth() +*/ +void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */) +{ + Q_D(QTreeView); + d->columnsToUpdate.append(column); + if (d->columnResizeTimerID == 0) + d->columnResizeTimerID = startTimer(0); +} + +/*! + \reimp +*/ +void QTreeView::updateGeometries() +{ + Q_D(QTreeView); + if (d->header) { + if (d->geometryRecursionBlock) + return; + d->geometryRecursionBlock = true; + QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint(); + setViewportMargins(0, hint.height(), 0, 0); + QRect vg = d->viewport->geometry(); + QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height()); + d->header->setGeometry(geometryRect); + //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ??? + QMetaObject::invokeMethod(d->header, "updateGeometries"); + d->updateScrollBars(); + d->geometryRecursionBlock = false; + } + QAbstractItemView::updateGeometries(); +} + +/*! + Returns the size hint for the \a column's width or -1 if there is no + model. + + If you need to set the width of a given column to a fixed value, call + QHeaderView::resizeSection() on the view's header. + + If you reimplement this function in a subclass, note that the value you + return is only used when resizeColumnToContents() is called. In that case, + if a larger column width is required by either the view's header or + the item delegate, that width will be used instead. + + \sa QWidget::sizeHint, header() +*/ +int QTreeView::sizeHintForColumn(int column) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + if (d->viewItems.isEmpty()) + return -1; + ensurePolished(); + int w = 0; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + const QVector viewItems = d->viewItems; + + int start = 0; + int end = viewItems.count(); + if(end > 1000) { //if we have too many item this function would be too slow. + //we get a good approximation by only iterate over 1000 items. + start = qMax(0, d->firstVisibleItem() - 100); + end = qMin(end, start + 900); + } + + for (int i = start; i < end; ++i) { + if (viewItems.at(i).spanning) + continue; // we have no good size hint + QModelIndex index = viewItems.at(i).index; + index = index.sibling(index.row(), column); + QWidget *editor = d->editorForIndex(index).widget.data(); + if (editor && d->persistent.contains(editor)) { + w = qMax(w, editor->sizeHint().width()); + int min = editor->minimumSize().width(); + int max = editor->maximumSize().width(); + w = qBound(min, w, max); + } + int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); + w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0)); + } + return w; +} + +/*! + Returns the size hint for the row indicated by \a index. + + \sa sizeHintForColumn(), uniformRowHeights() +*/ +int QTreeView::indexRowSizeHint(const QModelIndex &index) const +{ + Q_D(const QTreeView); + if (!d->isIndexValid(index) || !d->itemDelegate) + return 0; + + int start = -1; + int end = -1; + int indexRow = index.row(); + int count = d->header->count(); + bool emptyHeader = (count == 0); + QModelIndex parent = index.parent(); + + if (count && isVisible()) { + // If the sections have moved, we end up checking too many or too few + start = d->header->visualIndexAt(0); + } else { + // If the header has not been laid out yet, we use the model directly + count = d->model->columnCount(parent); + } + + if (isRightToLeft()) { + start = (start == -1 ? count - 1 : start); + end = 0; + } else { + start = (start == -1 ? 0 : start); + end = count - 1; + } + + if (end < start) + qSwap(end, start); + + int height = -1; + QStyleOptionViewItemV4 option = d->viewOptionsV4(); + // ### If we want word wrapping in the items, + // ### we need to go through all the columns + // ### and set the width of the column + + // Hack to speed up the function + option.rect.setWidth(-1); + + for (int column = start; column <= end; ++column) { + int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column); + if (d->header->isSectionHidden(logicalColumn)) + continue; + QModelIndex idx = d->model->index(indexRow, logicalColumn, parent); + if (idx.isValid()) { + QWidget *editor = d->editorForIndex(idx).widget.data(); + if (editor && d->persistent.contains(editor)) { + height = qMax(height, editor->sizeHint().height()); + int min = editor->minimumSize().height(); + int max = editor->maximumSize().height(); + height = qBound(min, height, max); + } + int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height(); + height = qMax(height, hint); + } + } + + return height; +} + +/*! + \since 4.3 + Returns the height of the row indicated by the given \a index. + \sa indexRowSizeHint() +*/ +int QTreeView::rowHeight(const QModelIndex &index) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + int i = d->viewIndex(index); + if (i == -1) + return 0; + return d->itemHeight(i); +} + +/*! + \internal +*/ +void QTreeView::horizontalScrollbarAction(int action) +{ + QAbstractItemView::horizontalScrollbarAction(action); +} + +/*! + \reimp +*/ +bool QTreeView::isIndexHidden(const QModelIndex &index) const +{ + return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent())); +} + +/* + private implementation +*/ +void QTreeViewPrivate::initialize() +{ + Q_Q(QTreeView); + updateStyledFrameWidths(); + q->setSelectionBehavior(QAbstractItemView::SelectRows); + q->setSelectionMode(QAbstractItemView::SingleSelection); + q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + q->setAttribute(Qt::WA_MacShowFocusRect); + + QHeaderView *header = new QHeaderView(Qt::Horizontal, q); + header->setMovable(true); + header->setStretchLastSection(true); + header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter); + q->setHeader(header); +#ifndef QT_NO_ANIMATION + QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation())); +#endif //QT_NO_ANIMATION +} + +void QTreeViewPrivate::expand(int item, bool emitSignal) +{ + Q_Q(QTreeView); + + if (item == -1 || viewItems.at(item).expanded) + return; + +#ifndef QT_NO_ANIMATION + if (emitSignal && animationsEnabled) + prepareAnimatedOperation(item, QVariantAnimation::Forward); +#endif //QT_NO_ANIMATION + stateBeforeAnimation = state; + q->setState(QAbstractItemView::ExpandingState); + const QModelIndex index = viewItems.at(item).index; + storeExpanded(index); + viewItems[item].expanded = true; + layout(item); + q->setState(stateBeforeAnimation); + + if (model->canFetchMore(index)) + model->fetchMore(index); + if (emitSignal) { + emit q->expanded(index); +#ifndef QT_NO_ANIMATION + if (animationsEnabled) + beginAnimatedOperation(); +#endif //QT_NO_ANIMATION + } +} + +void QTreeViewPrivate::insertViewItems(int pos, int count, const QTreeViewItem &viewItem) +{ + viewItems.insert(pos, count, viewItem); + QTreeViewItem *items = viewItems.data(); + for (int i = pos + count; i < viewItems.count(); i++) + if (items[i].parentItem >= pos) + items[i].parentItem += count; +} + +void QTreeViewPrivate::removeViewItems(int pos, int count) +{ + viewItems.remove(pos, count); + QTreeViewItem *items = viewItems.data(); + for (int i = pos; i < viewItems.count(); i++) + if (items[i].parentItem >= pos) + items[i].parentItem -= count; +} + +#if 0 +bool QTreeViewPrivate::checkViewItems() const +{ + for (int i = 0; i < viewItems.count(); ++i) { + const QTreeViewItem &vi = viewItems.at(i); + if (vi.parentItem == -1) { + Q_ASSERT(!vi.index.parent().isValid() || vi.index.parent() == root); + } else { + Q_ASSERT(vi.index.parent() == viewItems.at(vi.parentItem).index); + } + } + return true; +} +#endif + +void QTreeViewPrivate::collapse(int item, bool emitSignal) +{ + Q_Q(QTreeView); + + if (item == -1 || expandedIndexes.isEmpty()) + return; + + //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll + delayedAutoScroll.stop(); + + int total = viewItems.at(item).total; + const QModelIndex &modelIndex = viewItems.at(item).index; + if (!isPersistent(modelIndex)) + return; // if the index is not persistent, no chances it is expanded + QSet::iterator it = expandedIndexes.find(modelIndex); + if (it == expandedIndexes.end() || viewItems.at(item).expanded == false) + return; // nothing to do + +#ifndef QT_NO_ANIMATION + if (emitSignal && animationsEnabled) + prepareAnimatedOperation(item, QVariantAnimation::Backward); +#endif //QT_NO_ANIMATION + + stateBeforeAnimation = state; + q->setState(QAbstractItemView::CollapsingState); + expandedIndexes.erase(it); + viewItems[item].expanded = false; + int index = item; + while (index > -1) { + viewItems[index].total -= total; + index = viewItems[index].parentItem; + } + removeViewItems(item + 1, total); // collapse + q->setState(stateBeforeAnimation); + + if (emitSignal) { + emit q->collapsed(modelIndex); +#ifndef QT_NO_ANIMATION + if (animationsEnabled) + beginAnimatedOperation(); +#endif //QT_NO_ANIMATION + } +} + +#ifndef QT_NO_ANIMATION +void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction) +{ + animatedOperation.item = item; + animatedOperation.viewport = viewport; + animatedOperation.setDirection(direction); + + int top = coordinateForItem(item) + itemHeight(item); + QRect rect = viewport->rect(); + rect.setTop(top); + if (direction == QVariantAnimation::Backward) { + const int limit = rect.height() * 2; + int h = 0; + int c = item + viewItems.at(item).total + 1; + for (int i = item + 1; i < c && h < limit; ++i) + h += itemHeight(i); + rect.setHeight(h); + animatedOperation.setEndValue(top + h); + } + animatedOperation.setStartValue(top); + animatedOperation.before = renderTreeToPixmapForAnimation(rect); +} + +void QTreeViewPrivate::beginAnimatedOperation() +{ + Q_Q(QTreeView); + + QRect rect = viewport->rect(); + rect.setTop(animatedOperation.top()); + if (animatedOperation.direction() == QVariantAnimation::Forward) { + const int limit = rect.height() * 2; + int h = 0; + int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1; + for (int i = animatedOperation.item + 1; i < c && h < limit; ++i) + h += itemHeight(i); + rect.setHeight(h); + animatedOperation.setEndValue(animatedOperation.top() + h); + } + + if (!rect.isEmpty()) { + animatedOperation.after = renderTreeToPixmapForAnimation(rect); + + q->setState(QAbstractItemView::AnimatingState); + animatedOperation.start(); //let's start the animation + } +} + +void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const +{ + const int start = animatedOperation.startValue().toInt(), + end = animatedOperation.endValue().toInt(), + current = animatedOperation.currentValue().toInt(); + bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward; + const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after; + painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height()); + const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before; + painter->drawPixmap(0, current, bottom); +} + +QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const +{ + Q_Q(const QTreeView); + QPixmap pixmap(rect.size()); + if (rect.size().isEmpty()) + return pixmap; + pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels. + QPainter painter(&pixmap); + painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base()); + painter.translate(0, -rect.top()); + q->drawTree(&painter, QRegion(rect)); + painter.end(); + + //and now let's render the editors the editors + QStyleOptionViewItemV4 option = viewOptionsV4(); + for (QEditorIndexHash::const_iterator it = editorIndexHash.constBegin(); it != editorIndexHash.constEnd(); ++it) { + QWidget *editor = it.key(); + const QModelIndex &index = it.value(); + option.rect = q->visualRect(index); + if (option.rect.isValid()) { + + if (QAbstractItemDelegate *delegate = delegateForIndex(index)) + delegate->updateEditorGeometry(editor, option, index); + + const QPoint pos = editor->pos(); + if (rect.contains(pos)) { + editor->render(&pixmap, pos - rect.topLeft()); + //the animation uses pixmap to display the treeview's content + //the editor is rendered on this pixmap and thus can (should) be hidden + editor->hide(); + } + } + } + + + return pixmap; +} + +void QTreeViewPrivate::_q_endAnimatedOperation() +{ + Q_Q(QTreeView); + q->setState(stateBeforeAnimation); + q->updateGeometries(); + viewport->update(); +} +#endif //QT_NO_ANIMATION + +void QTreeViewPrivate::_q_modelAboutToBeReset() +{ + viewItems.clear(); +} + +void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + if (start <= 0 && 0 <= end) + viewItems.clear(); + QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end); +} + +void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end) +{ + if (start <= 0 && 0 <= end) + doDelayedItemsLayout(); + QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end); +} + +/** \internal + creates and initialize the viewItem structure of the children of the element \i + + set \a recursiveExpanding if the function has to expand all the children (called from expandAll) + \a afterIsUninitialized is when we recurse from layout(-1), it means all the items after 'i' are + not yet initialized and need not to be moved + */ +void QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized) +{ + Q_Q(QTreeView); + QModelIndex current; + QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i); + + if (i>=0 && !parent.isValid()) { + //modelIndex() should never return something invalid for the real items. + //This can happen if columncount has been set to 0. + //To avoid infinite loop we stop here. + return; + } + + int count = 0; + if (model->hasChildren(parent)) { + if (model->canFetchMore(parent)) + model->fetchMore(parent); + count = model->rowCount(parent); + } + + bool expanding = true; + if (i == -1) { + if (uniformRowHeights) { + QModelIndex index = model->index(0, 0, parent); + defaultItemHeight = q->indexRowSizeHint(index); + } + viewItems.resize(count); + afterIsUninitialized = true; + } else if (viewItems[i].total != (uint)count) { + if (!afterIsUninitialized) + insertViewItems(i + 1, count, QTreeViewItem()); // expand + else if (count > 0) + viewItems.resize(viewItems.count() + count); + } else { + expanding = false; + } + + int first = i + 1; + int level = (i >= 0 ? viewItems.at(i).level + 1 : 0); + int hidden = 0; + int last = 0; + int children = 0; + QTreeViewItem *item = 0; + for (int j = first; j < first + count; ++j) { + current = model->index(j - first, 0, parent); + if (isRowHidden(current)) { + ++hidden; + last = j - hidden + children; + } else { + last = j - hidden + children; + if (item) + item->hasMoreSiblings = true; + item = &viewItems[last]; + item->index = current; + item->parentItem = i; + item->level = level; + item->height = 0; + item->spanning = q->isFirstColumnSpanned(current.row(), parent); + item->expanded = false; + item->total = 0; + item->hasMoreSiblings = false; + if (recursiveExpanding || isIndexExpanded(current)) { + if (recursiveExpanding) + expandedIndexes.insert(current); + item->expanded = true; + layout(last, recursiveExpanding, afterIsUninitialized); + item = &viewItems[last]; + children += item->total; + item->hasChildren = item->total > 0; + last = j - hidden + children; + } else { + item->hasChildren = hasVisibleChildren(current); + } + } + } + + // remove hidden items + if (hidden > 0) { + if (!afterIsUninitialized) + removeViewItems(last + 1, hidden); + else + viewItems.resize(viewItems.size() - hidden); + } + + if (!expanding) + return; // nothing changed + + while (i > -1) { + viewItems[i].total += count - hidden; + i = viewItems[i].parentItem; + } +} + +int QTreeViewPrivate::pageUp(int i) const +{ + int index = itemAtCoordinate(coordinateForItem(i) - viewport->height()); + while (isItemHiddenOrDisabled(index)) + index--; + return index == -1 ? 0 : index; +} + +int QTreeViewPrivate::pageDown(int i) const +{ + int index = itemAtCoordinate(coordinateForItem(i) + viewport->height()); + while (isItemHiddenOrDisabled(index)) + index++; + return index == -1 ? viewItems.count() - 1 : index; +} + +int QTreeViewPrivate::indentationForItem(int item) const +{ + if (item < 0 || item >= viewItems.count()) + return 0; + int level = viewItems.at(item).level; + if (rootDecoration) + ++level; + return level * indent; +} + +int QTreeViewPrivate::itemHeight(int item) const +{ + if (uniformRowHeights) + return defaultItemHeight; + if (viewItems.isEmpty()) + return 0; + const QModelIndex &index = viewItems.at(item).index; + if (!index.isValid()) + return 0; + int height = viewItems.at(item).height; + if (height <= 0) { + height = q_func()->indexRowSizeHint(index); + viewItems[item].height = height; + } + return qMax(height, 0); +} + + +/*! + \internal + Returns the viewport y coordinate for \a item. +*/ +int QTreeViewPrivate::coordinateForItem(int item) const +{ + if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { + if (uniformRowHeights) + return (item * defaultItemHeight) - vbar->value(); + // ### optimize (spans or caching) + int y = 0; + for (int i = 0; i < viewItems.count(); ++i) { + if (i == item) + return y - vbar->value(); + y += itemHeight(i); + } + } else { // ScrollPerItem + int topViewItemIndex = vbar->value(); + if (uniformRowHeights) + return defaultItemHeight * (item - topViewItemIndex); + if (item >= topViewItemIndex) { + // search in the visible area first and continue down + // ### slow if the item is not visible + int viewItemCoordinate = 0; + int viewItemIndex = topViewItemIndex; + while (viewItemIndex < viewItems.count()) { + if (viewItemIndex == item) + return viewItemCoordinate; + viewItemCoordinate += itemHeight(viewItemIndex); + ++viewItemIndex; + } + // below the last item in the view + Q_ASSERT(false); + return viewItemCoordinate; + } else { + // search the area above the viewport (used for editor widgets) + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) { + if (viewItemIndex == item) + return viewItemCoordinate; + viewItemCoordinate -= itemHeight(viewItemIndex - 1); + } + return viewItemCoordinate; + } + } + return 0; +} + +/*! + \internal + Returns the index of the view item at the + given viewport \a coordinate. + + \sa modelIndex() +*/ +int QTreeViewPrivate::itemAtCoordinate(int coordinate) const +{ + const int itemCount = viewItems.count(); + if (itemCount == 0) + return -1; + if (uniformRowHeights && defaultItemHeight <= 0) + return -1; + if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { + if (uniformRowHeights) { + const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight; + return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); + } + // ### optimize + int viewItemCoordinate = 0; + const int contentsCoordinate = coordinate + vbar->value(); + for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) { + viewItemCoordinate += itemHeight(viewItemIndex); + if (viewItemCoordinate >= contentsCoordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + } + } else { // ScrollPerItem + int topViewItemIndex = vbar->value(); + if (uniformRowHeights) { + if (coordinate < 0) + coordinate -= defaultItemHeight - 1; + const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight); + return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); + } + if (coordinate >= 0) { + // the coordinate is in or below the viewport + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) { + viewItemCoordinate += itemHeight(viewItemIndex); + if (viewItemCoordinate > coordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + } + } else { + // the coordinate is above the viewport + int viewItemCoordinate = 0; + for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) { + if (viewItemCoordinate <= coordinate) + return (viewItemIndex >= itemCount ? -1 : viewItemIndex); + viewItemCoordinate -= itemHeight(viewItemIndex); + } + } + } + return -1; +} + +int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const +{ + if (!_index.isValid() || viewItems.isEmpty()) + return -1; + + const int totalCount = viewItems.count(); + const QModelIndex index = _index.sibling(_index.row(), 0); + const int row = index.row(); + const qint64 internalId = index.internalId(); + + // We start nearest to the lastViewedItem + int localCount = qMin(lastViewedItem - 1, totalCount - lastViewedItem); + for (int i = 0; i < localCount; ++i) { + const QModelIndex &idx1 = viewItems.at(lastViewedItem + i).index; + if (idx1.row() == row && idx1.internalId() == internalId) { + lastViewedItem = lastViewedItem + i; + return lastViewedItem; + } + const QModelIndex &idx2 = viewItems.at(lastViewedItem - i - 1).index; + if (idx2.row() == row && idx2.internalId() == internalId) { + lastViewedItem = lastViewedItem - i - 1; + return lastViewedItem; + } + } + + for (int j = qMax(0, lastViewedItem + localCount); j < totalCount; ++j) { + const QModelIndex &idx = viewItems.at(j).index; + if (idx.row() == row && idx.internalId() == internalId) { + lastViewedItem = j; + return j; + } + } + for (int j = qMin(totalCount, lastViewedItem - localCount) - 1; j >= 0; --j) { + const QModelIndex &idx = viewItems.at(j).index; + if (idx.row() == row && idx.internalId() == internalId) { + lastViewedItem = j; + return j; + } + } + + // nothing found + return -1; +} + +QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const +{ + if (i < 0 || i >= viewItems.count()) + return QModelIndex(); + + QModelIndex ret = viewItems.at(i).index; + if (column) + ret = ret.sibling(ret.row(), column); + return ret; +} + +int QTreeViewPrivate::firstVisibleItem(int *offset) const +{ + const int value = vbar->value(); + if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (offset) + *offset = 0; + return (value < 0 || value >= viewItems.count()) ? -1 : value; + } + // ScrollMode == ScrollPerPixel + if (uniformRowHeights) { + if (!defaultItemHeight) + return -1; + + if (offset) + *offset = -(value % defaultItemHeight); + return value / defaultItemHeight; + } + int y = 0; // ### optimize (use spans ?) + for (int i = 0; i < viewItems.count(); ++i) { + y += itemHeight(i); // the height value is cached + if (y > value) { + if (offset) + *offset = y - value - itemHeight(i); + return i; + } + } + return -1; +} + +int QTreeViewPrivate::columnAt(int x) const +{ + return header->logicalIndexAt(x); +} + +void QTreeViewPrivate::updateScrollBars() +{ + Q_Q(QTreeView); + QSize viewportSize = viewport->size(); + if (!viewportSize.isValid()) + viewportSize = QSize(0, 0); + + if (viewItems.isEmpty()) { + q->doItemsLayout(); + } + + int itemsInViewport = 0; + if (uniformRowHeights) { + if (defaultItemHeight <= 0) + itemsInViewport = viewItems.count(); + else + itemsInViewport = viewportSize.height() / defaultItemHeight; + } else { + const int itemsCount = viewItems.count(); + const int viewportHeight = viewportSize.height(); + for (int height = 0, item = itemsCount - 1; item >= 0; --item) { + height += itemHeight(item); + if (height > viewportHeight) + break; + ++itemsInViewport; + } + } + if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { + if (!viewItems.isEmpty()) + itemsInViewport = qMax(1, itemsInViewport); + vbar->setRange(0, viewItems.count() - itemsInViewport); + vbar->setPageStep(itemsInViewport); + vbar->setSingleStep(1); + } else { // scroll per pixel + int contentsHeight = 0; + if (uniformRowHeights) { + contentsHeight = defaultItemHeight * viewItems.count(); + } else { // ### optimize (spans or caching) + for (int i = 0; i < viewItems.count(); ++i) + contentsHeight += itemHeight(i); + } + vbar->setRange(0, contentsHeight - viewportSize.height()); + vbar->setPageStep(viewportSize.height()); + vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2)); + } + + const int columnCount = header->count(); + const int viewportWidth = viewportSize.width(); + int columnsInViewport = 0; + for (int width = 0, column = columnCount - 1; column >= 0; --column) { + int logical = header->logicalIndex(column); + width += header->sectionSize(logical); + if (width > viewportWidth) + break; + ++columnsInViewport; + } + if (columnCount > 0) + columnsInViewport = qMax(1, columnsInViewport); + if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) { + hbar->setRange(0, columnCount - columnsInViewport); + hbar->setPageStep(columnsInViewport); + hbar->setSingleStep(1); + } else { // scroll per pixel + const int horizontalLength = header->length(); + const QSize maxSize = q->maximumViewportSize(); + if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0) + viewportSize = maxSize; + hbar->setPageStep(viewportSize.width()); + hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0)); + hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2)); + } +} + +int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const +{ + executePostedLayout(); + int x = pos.x(); + int column = header->logicalIndexAt(x); + if (column != 0) + return -1; // no logical index at x + + int viewItemIndex = itemAtCoordinate(pos.y()); + QRect returning = itemDecorationRect(modelIndex(viewItemIndex)); + if (!returning.contains(pos)) + return -1; + + return viewItemIndex; +} + +QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const +{ + Q_Q(const QTreeView); + if (!rootDecoration && index.parent() == root) + return QRect(); // no decoration at root + + int viewItemIndex = viewIndex(index); + if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index)) + return QRect(); + + int itemIndentation = indentationForItem(viewItemIndex); + int position = header->sectionViewportPosition(0); + int size = header->sectionSize(0); + + QRect rect; + if (q->isRightToLeft()) + rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex), + indent, itemHeight(viewItemIndex)); + else + rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex), + indent, itemHeight(viewItemIndex)); + QStyleOption opt; + opt.initFrom(q); + opt.rect = rect; + return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q); +} + +QList > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex, + const QModelIndex &bottomIndex) const +{ + const int topVisual = header->visualIndex(topIndex.column()), + bottomVisual = header->visualIndex(bottomIndex.column()); + + const int start = qMin(topVisual, bottomVisual); + const int end = qMax(topVisual, bottomVisual); + + QList logicalIndexes; + + //we iterate over the visual indexes to get the logical indexes + for (int c = start; c <= end; c++) { + const int logical = header->logicalIndex(c); + if (!header->isSectionHidden(logical)) { + logicalIndexes << logical; + } + } + //let's sort the list + qSort(logicalIndexes.begin(), logicalIndexes.end()); + + QList > ret; + QPair current; + current.first = -2; // -1 is not enough because -1+1 = 0 + current.second = -2; + for(int i = 0; i < logicalIndexes.count(); ++i) { + const int logicalColumn = logicalIndexes.at(i); + if (current.second + 1 != logicalColumn) { + if (current.first != -2) { + //let's save the current one + ret += current; + } + //let's start a new one + current.first = current.second = logicalColumn; + } else { + current.second++; + } + } + + //let's get the last range + if (current.first != -2) { + ret += current; + } + + return ret; +} + +void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex, + QItemSelectionModel::SelectionFlags command) +{ + Q_Q(QTreeView); + QItemSelection selection; + const int top = viewIndex(topIndex), + bottom = viewIndex(bottomIndex); + + const QList< QPair > colRanges = columnRanges(topIndex, bottomIndex); + QList< QPair >::const_iterator it; + for (it = colRanges.begin(); it != colRanges.end(); ++it) { + const int left = (*it).first, + right = (*it).second; + + QModelIndex previous; + QItemSelectionRange currentRange; + QStack rangeStack; + for (int i = top; i <= bottom; ++i) { + QModelIndex index = modelIndex(i); + QModelIndex parent = index.parent(); + QModelIndex previousParent = previous.parent(); + if (previous.isValid() && parent == previousParent) { + // same parent + if (qAbs(previous.row() - index.row()) > 1) { + //a hole (hidden index inside a range) has been detected + if (currentRange.isValid()) { + selection.append(currentRange); + } + //let's start a new range + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + QModelIndex tl = model->index(currentRange.top(), currentRange.left(), + currentRange.parent()); + currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right)); + } + } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) { + // item is child of previous + rangeStack.push(currentRange); + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + if (currentRange.isValid()) + selection.append(currentRange); + if (rangeStack.isEmpty()) { + currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); + } else { + currentRange = rangeStack.pop(); + index = currentRange.bottomRight(); //let's resume the range + --i; //we process again the current item + } + } + previous = index; + } + if (currentRange.isValid()) + selection.append(currentRange); + for (int i = 0; i < rangeStack.count(); ++i) + selection.append(rangeStack.at(i)); + } + q->selectionModel()->select(selection, command); +} + +QPair QTreeViewPrivate::startAndEndColumns(const QRect &rect) const +{ + Q_Q(const QTreeView); + int start = header->visualIndexAt(rect.left()); + int end = header->visualIndexAt(rect.right()); + if (q->isRightToLeft()) { + start = (start == -1 ? header->count() - 1 : start); + end = (end == -1 ? 0 : end); + } else { + start = (start == -1 ? 0 : start); + end = (end == -1 ? header->count() - 1 : end); + } + return qMakePair(qMin(start, end), qMax(start, end)); +} + +bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const +{ + Q_Q(const QTreeView); + if (model->hasChildren(parent)) { + if (hiddenIndexes.isEmpty()) + return true; + if (q->isIndexHidden(parent)) + return false; + int rowCount = model->rowCount(parent); + for (int i = 0; i < rowCount; ++i) { + if (!q->isRowHidden(i, parent)) + return true; + } + if (rowCount == 0) + return true; + } + return false; +} + +void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order) +{ + model->sort(column, order); +} + +/*! + \reimp + */ +void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + int entry = visualIndex(current) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); + } +#endif + QAbstractItemView::currentChanged(current, previous); + + if (allColumnsShowFocus()) { + if (previous.isValid()) { + QRect previousRect = visualRect(previous); + previousRect.setX(0); + previousRect.setWidth(viewport()->width()); + viewport()->update(previousRect); + } + if (current.isValid()) { + QRect currentRect = visualRect(current); + currentRect.setX(0); + currentRect.setWidth(viewport()->width()); + viewport()->update(currentRect); + } + } +} + +/*! + \reimp + */ +void QTreeView::selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected) +{ +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive()) { + // ### does not work properly for selection ranges. + QModelIndex sel = selected.indexes().value(0); + if (sel.isValid()) { + int entry = visualIndex(sel) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); + } + QModelIndex desel = deselected.indexes().value(0); + if (desel.isValid()) { + int entry = visualIndex(desel) + 1; + if (header()) + ++entry; + QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); + } + } +#endif + QAbstractItemView::selectionChanged(selected, deselected); +} + +int QTreeView::visualIndex(const QModelIndex &index) const +{ + Q_D(const QTreeView); + d->executePostedLayout(); + return d->viewIndex(index); +} + +QT_END_NAMESPACE + +#include "moc_qtreeview.cpp" + +#endif // QT_NO_TREEVIEW diff --git a/src/gui/itemviews/qtreeview.h b/src/gui/itemviews/qtreeview.h new file mode 100644 index 0000000000..49b8419389 --- /dev/null +++ b/src/gui/itemviews/qtreeview.h @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEVIEW_H +#define QTREEVIEW_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEVIEW + +class QTreeViewPrivate; +class QHeaderView; + +class Q_GUI_EXPORT QTreeView : public QAbstractItemView +{ + Q_OBJECT + Q_PROPERTY(int autoExpandDelay READ autoExpandDelay WRITE setAutoExpandDelay) + Q_PROPERTY(int indentation READ indentation WRITE setIndentation) + Q_PROPERTY(bool rootIsDecorated READ rootIsDecorated WRITE setRootIsDecorated) + Q_PROPERTY(bool uniformRowHeights READ uniformRowHeights WRITE setUniformRowHeights) + Q_PROPERTY(bool itemsExpandable READ itemsExpandable WRITE setItemsExpandable) + Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) + Q_PROPERTY(bool animated READ isAnimated WRITE setAnimated) + Q_PROPERTY(bool allColumnsShowFocus READ allColumnsShowFocus WRITE setAllColumnsShowFocus) + Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(bool headerHidden READ isHeaderHidden WRITE setHeaderHidden) + Q_PROPERTY(bool expandsOnDoubleClick READ expandsOnDoubleClick WRITE setExpandsOnDoubleClick) + +public: + explicit QTreeView(QWidget *parent = 0); + ~QTreeView(); + + void setModel(QAbstractItemModel *model); + void setRootIndex(const QModelIndex &index); + void setSelectionModel(QItemSelectionModel *selectionModel); + + QHeaderView *header() const; + void setHeader(QHeaderView *header); + + int autoExpandDelay() const; + void setAutoExpandDelay(int delay); + + int indentation() const; + void setIndentation(int i); + + bool rootIsDecorated() const; + void setRootIsDecorated(bool show); + + bool uniformRowHeights() const; + void setUniformRowHeights(bool uniform); + + bool itemsExpandable() const; + void setItemsExpandable(bool enable); + + bool expandsOnDoubleClick() const; + void setExpandsOnDoubleClick(bool enable); + + int columnViewportPosition(int column) const; + int columnWidth(int column) const; + void setColumnWidth(int column, int width); + int columnAt(int x) const; + + bool isColumnHidden(int column) const; + void setColumnHidden(int column, bool hide); + + bool isHeaderHidden() const; + void setHeaderHidden(bool hide); + + bool isRowHidden(int row, const QModelIndex &parent) const; + void setRowHidden(int row, const QModelIndex &parent, bool hide); + + bool isFirstColumnSpanned(int row, const QModelIndex &parent) const; + void setFirstColumnSpanned(int row, const QModelIndex &parent, bool span); + + bool isExpanded(const QModelIndex &index) const; + void setExpanded(const QModelIndex &index, bool expand); + + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void setAnimated(bool enable); + bool isAnimated() const; + + void setAllColumnsShowFocus(bool enable); + bool allColumnsShowFocus() const; + + void setWordWrap(bool on); + bool wordWrap() const; + + void keyboardSearch(const QString &search); + + QRect visualRect(const QModelIndex &index) const; + void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible); + QModelIndex indexAt(const QPoint &p) const; + QModelIndex indexAbove(const QModelIndex &index) const; + QModelIndex indexBelow(const QModelIndex &index) const; + + void doItemsLayout(); + void reset(); + + void sortByColumn(int column, Qt::SortOrder order); + + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void selectAll(); + +Q_SIGNALS: + void expanded(const QModelIndex &index); + void collapsed(const QModelIndex &index); + +public Q_SLOTS: + void hideColumn(int column); + void showColumn(int column); + void expand(const QModelIndex &index); + void collapse(const QModelIndex &index); + void resizeColumnToContents(int column); + void sortByColumn(int column); + void expandAll(); + void collapseAll(); + void expandToDepth(int depth); + +protected Q_SLOTS: + void columnResized(int column, int oldSize, int newSize); + void columnCountChanged(int oldCount, int newCount); + void columnMoved(); + void reexpand(); + void rowsRemoved(const QModelIndex &parent, int first, int last); + +protected: + QTreeView(QTreeViewPrivate &dd, QWidget *parent = 0); + void scrollContentsBy(int dx, int dy); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + int horizontalOffset() const; + int verticalOffset() const; + + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command); + QRegion visualRegionForSelection(const QItemSelection &selection) const; + QModelIndexList selectedIndexes() const; + + void timerEvent(QTimerEvent *event); + void paintEvent(QPaintEvent *event); + + void drawTree(QPainter *painter, const QRegion ®ion) const; + virtual void drawRow(QPainter *painter, + const QStyleOptionViewItem &options, + const QModelIndex &index) const; + virtual void drawBranches(QPainter *painter, + const QRect &rect, + const QModelIndex &index) const; + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); +#ifndef QT_NO_DRAGANDDROP + void dragMoveEvent(QDragMoveEvent *event); +#endif + bool viewportEvent(QEvent *event); + + void updateGeometries(); + + int sizeHintForColumn(int column) const; + int indexRowSizeHint(const QModelIndex &index) const; + int rowHeight(const QModelIndex &index) const; + + void horizontalScrollbarAction(int action); + + bool isIndexHidden(const QModelIndex &index) const; + void selectionChanged(const QItemSelection &selected, + const QItemSelection &deselected); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + friend class QAccessibleItemView; + int visualIndex(const QModelIndex &index) const; + + Q_DECLARE_PRIVATE(QTreeView) + Q_DISABLE_COPY(QTreeView) +#ifndef QT_NO_ANIMATION + Q_PRIVATE_SLOT(d_func(), void _q_endAnimatedOperation()) +#endif //QT_NO_ANIMATION + Q_PRIVATE_SLOT(d_func(), void _q_modelAboutToBeReset()) + Q_PRIVATE_SLOT(d_func(), void _q_sortIndicatorChanged(int column, Qt::SortOrder order)) +}; + +#endif // QT_NO_TREEVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTREEVIEW_H diff --git a/src/gui/itemviews/qtreeview_p.h b/src/gui/itemviews/qtreeview_p.h new file mode 100644 index 0000000000..9a923c5972 --- /dev/null +++ b/src/gui/itemviews/qtreeview_p.h @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEVIEW_P_H +#define QTREEVIEW_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/qabstractitemview_p.h" +#include +#include + +#ifndef QT_NO_TREEVIEW + +QT_BEGIN_NAMESPACE + +struct QTreeViewItem +{ + QTreeViewItem() : parentItem(-1), expanded(false), spanning(false), hasChildren(false), + hasMoreSiblings(false), total(0), level(0), height(0) {} + QModelIndex index; // we remove items whenever the indexes are invalidated + int parentItem; // parent item index in viewItems + uint expanded : 1; + uint spanning : 1; + uint hasChildren : 1; // if the item has visible children (even if collapsed) + uint hasMoreSiblings : 1; + uint total : 28; // total number of children visible + uint level : 16; // indentation + int height : 16; // row height +}; + +Q_DECLARE_TYPEINFO(QTreeViewItem, Q_MOVABLE_TYPE); + +class QTreeViewPrivate : public QAbstractItemViewPrivate +{ + Q_DECLARE_PUBLIC(QTreeView) +public: + + QTreeViewPrivate() + : QAbstractItemViewPrivate(), + header(0), indent(20), lastViewedItem(0), defaultItemHeight(-1), + uniformRowHeights(false), rootDecoration(true), + itemsExpandable(true), sortingEnabled(false), + expandsOnDoubleClick(true), + allColumnsShowFocus(false), current(0), spanning(false), + animationsEnabled(false), columnResizeTimerID(0), + autoExpandDelay(-1), hoverBranch(-1), geometryRecursionBlock(false), hasRemovedItems(false) {} + + ~QTreeViewPrivate() {} + void initialize(); + + QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; + +#ifndef QT_NO_ANIMATION + struct AnimatedOperation : public QVariantAnimation + { + int item; + QPixmap before; + QPixmap after; + QWidget *viewport; + AnimatedOperation() : item(0) { setEasingCurve(QEasingCurve::InOutQuad); } + int top() const { return startValue().toInt(); } + QRect rect() const { QRect rect = viewport->rect(); rect.moveTop(top()); return rect; } + void updateCurrentValue(const QVariant &) { viewport->update(rect()); } + void updateState(State state, State) { if (state == Stopped) before = after = QPixmap(); } + } animatedOperation; + void prepareAnimatedOperation(int item, QVariantAnimation::Direction d); + void beginAnimatedOperation(); + void drawAnimatedOperation(QPainter *painter) const; + QPixmap renderTreeToPixmapForAnimation(const QRect &rect) const; + void _q_endAnimatedOperation(); +#endif //QT_NO_ANIMATION + + void expand(int item, bool emitSignal); + void collapse(int item, bool emitSignal); + + void _q_columnsAboutToBeRemoved(const QModelIndex &, int, int); + void _q_columnsRemoved(const QModelIndex &, int, int); + void _q_modelAboutToBeReset(); + void _q_sortIndicatorChanged(int column, Qt::SortOrder order); + void _q_modelDestroyed(); + + void layout(int item, bool recusiveExpanding = false, bool afterIsUninitialized = false); + + int pageUp(int item) const; + int pageDown(int item) const; + + int itemHeight(int item) const; + int indentationForItem(int item) const; + int coordinateForItem(int item) const; + int itemAtCoordinate(int coordinate) const; + + int viewIndex(const QModelIndex &index) const; + QModelIndex modelIndex(int i, int column = 0) const; + + void insertViewItems(int pos, int count, const QTreeViewItem &viewItem); + void removeViewItems(int pos, int count); +#if 0 + bool checkViewItems() const; +#endif + + int firstVisibleItem(int *offset = 0) const; + int columnAt(int x) const; + bool hasVisibleChildren( const QModelIndex& parent) const; + + bool expandOrCollapseItemAtPos(const QPoint &pos); + + void updateScrollBars(); + + int itemDecorationAt(const QPoint &pos) const; + QRect itemDecorationRect(const QModelIndex &index) const; + + + QList > columnRanges(const QModelIndex &topIndex, const QModelIndex &bottomIndex) const; + void select(const QModelIndex &start, const QModelIndex &stop, QItemSelectionModel::SelectionFlags command); + + QPair startAndEndColumns(const QRect &rect) const; + + void updateChildCount(const int parentItem, const int delta); + + void paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const; + + QHeaderView *header; + int indent; + + mutable QVector viewItems; + mutable int lastViewedItem; + int defaultItemHeight; // this is just a number; contentsHeight() / numItems + bool uniformRowHeights; // used when all rows have the same height + bool rootDecoration; + bool itemsExpandable; + bool sortingEnabled; + bool expandsOnDoubleClick; + bool allColumnsShowFocus; + + // used for drawing + mutable QPair leftAndRight; + mutable int current; + mutable bool spanning; + + // used when expanding and collapsing items + QSet expandedIndexes; + bool animationsEnabled; + + inline bool storeExpanded(const QPersistentModelIndex &idx) { + if (expandedIndexes.contains(idx)) + return false; + expandedIndexes.insert(idx); + return true; + } + + inline bool isIndexExpanded(const QModelIndex &idx) const { + //We first check if the idx is a QPersistentModelIndex, because creating QPersistentModelIndex is slow + return isPersistent(idx) && expandedIndexes.contains(idx); + } + + // used when hiding and showing items + QSet hiddenIndexes; + + inline bool isRowHidden(const QModelIndex &idx) const { + //We first check if the idx is a QPersistentModelIndex, because creating QPersistentModelIndex is slow + return isPersistent(idx) && hiddenIndexes.contains(idx); + } + + inline bool isItemHiddenOrDisabled(int i) const { + if (i < 0 || i >= viewItems.count()) + return false; + const QModelIndex index = viewItems.at(i).index; + return isRowHidden(index) || !isIndexEnabled(index); + } + + inline int above(int item) const + { int i = item; while (isItemHiddenOrDisabled(--item)){} return item < 0 ? i : item; } + inline int below(int item) const + { int i = item; while (isItemHiddenOrDisabled(++item)){} return item >= viewItems.count() ? i : item; } + inline void invalidateHeightCache(int item) const + { viewItems[item].height = 0; } + + // used for spanning rows + QVector spanningIndexes; + + // used for updating resized columns + int columnResizeTimerID; + QList columnsToUpdate; + + // used for the automatic opening of nodes during DND + int autoExpandDelay; + QBasicTimer openTimer; + + // used for drawing hilighted expand/collapse indicators + int hoverBranch; + + // used for blocking recursion when calling setViewportMargins from updateGeometries + bool geometryRecursionBlock; + + // If we should clean the set + bool hasRemovedItems; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEVIEW + +#endif // QTREEVIEW_P_H diff --git a/src/gui/itemviews/qtreewidget.cpp b/src/gui/itemviews/qtreewidget.cpp new file mode 100644 index 0000000000..2ea9a4316b --- /dev/null +++ b/src/gui/itemviews/qtreewidget.cpp @@ -0,0 +1,3460 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtreewidget.h" + +#ifndef QT_NO_TREEWIDGET +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// workaround for VC++ 6.0 linker bug (?) +typedef bool(*LessThan)(const QPair&,const QPair&); + +class QTreeModelLessThan +{ +public: + inline bool operator()(QTreeWidgetItem *i1, QTreeWidgetItem *i2) const + { return *i1 < *i2; } +}; + +class QTreeModelGreaterThan +{ +public: + inline bool operator()(QTreeWidgetItem *i1, QTreeWidgetItem *i2) const + { return *i2 < *i1; } +}; + +/* + \class QTreeModel + \brief The QTreeModel class manages the items stored in a tree view. + + \ingroup model-view + +*/ + +/*! + \enum QTreeWidgetItem::ChildIndicatorPolicy + \since 4.3 + + \value ShowIndicator The controls for expanding and collapsing will be shown for this item even if there are no children. + \value DontShowIndicator The controls for expanding and collapsing will never be shown even if there are children. If the node is forced open the user will not be able to expand or collapse the item. + \value DontShowIndicatorWhenChildless The controls for expanding and collapsing will be shown if the item contains children. +*/ + +/*! + \fn void QTreeWidgetItem::setDisabled(bool disabled) + \since 4.3 + + Disables the item if \a disabled is true; otherwise enables the item. + + \sa setFlags() +*/ + +/*! + \fn bool QTreeWidgetItem::isDisabled() const + \since 4.3 + + Returns true if the item is disabled; otherwise returns false. + + \sa setFlags() +*/ + +/*! + \internal + + Constructs a tree model with a \a parent object and the given + number of \a columns. +*/ + +QTreeModel::QTreeModel(int columns, QTreeWidget *parent) + : QAbstractItemModel(parent), rootItem(new QTreeWidgetItem), + headerItem(new QTreeWidgetItem), skipPendingSort(false) +{ + rootItem->view = parent; + rootItem->itemFlags = Qt::ItemIsDropEnabled; + headerItem->view = parent; + setColumnCount(columns); +} + +/*! + \internal + +*/ + +QTreeModel::QTreeModel(QTreeModelPrivate &dd, QTreeWidget *parent) + : QAbstractItemModel(dd, parent), rootItem(new QTreeWidgetItem), + headerItem(new QTreeWidgetItem), skipPendingSort(false) +{ + rootItem->view = parent; + rootItem->itemFlags = Qt::ItemIsDropEnabled; + headerItem->view = parent; +} + +/*! + \internal + + Destroys this tree model. +*/ + +QTreeModel::~QTreeModel() +{ + clear(); + delete headerItem; + rootItem->view = 0; + delete rootItem; +} + +/*! + \internal + + Removes all items in the model. +*/ + +void QTreeModel::clear() +{ + SkipSorting skipSorting(this); + for (int i = 0; i < rootItem->childCount(); ++i) { + QTreeWidgetItem *item = rootItem->children.at(i); + item->par = 0; + item->view = 0; + delete item; + } + rootItem->children.clear(); + sortPendingTimer.stop(); + reset(); +} + +/*! + \internal + + Sets the number of \a columns in the tree model. +*/ + +void QTreeModel::setColumnCount(int columns) +{ + SkipSorting skipSorting(this); + if (columns < 0) + return; + if (!headerItem) { + headerItem = new QTreeWidgetItem(); + headerItem->view = view(); + } + int count = columnCount(); + if (count == columns) + return; + + if (columns < count) { + beginRemoveColumns(QModelIndex(), columns, count - 1); + headerItem->values.resize(columns); + endRemoveColumns(); + } else { + beginInsertColumns(QModelIndex(), count, columns - 1); + headerItem->values.resize(columns); + for (int i = count; i < columns; ++i) {// insert data without emitting the dataChanged signal + headerItem->values[i].append(QWidgetItemData(Qt::DisplayRole, QString::number(i + 1))); + headerItem->d->display.append(QString::number(i + 1)); + } + endInsertColumns(); + } +} + +/*! + \internal + + Returns the tree view item corresponding to the \a index given. + + \sa QModelIndex +*/ + +QTreeWidgetItem *QTreeModel::item(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + return static_cast(index.internalPointer()); +} + +/*! + \internal + + Returns the model index that refers to the + tree view \a item and \a column. +*/ + +QModelIndex QTreeModel::index(const QTreeWidgetItem *item, int column) const +{ + executePendingSort(); + + if (!item || (item == rootItem)) + return QModelIndex(); + const QTreeWidgetItem *par = item->parent(); + QTreeWidgetItem *itm = const_cast(item); + if (!par) + par = rootItem; + int row; + int guess = item->d->rowGuess; + if (guess >= 0 + && par->children.count() > guess + && par->children.at(guess) == itm) { + row = guess; + } else { + row = par->children.lastIndexOf(itm); + itm->d->rowGuess = row; + } + return createIndex(row, column, itm); +} + +/*! + \internal + \reimp + + Returns the model index with the given \a row, + \a column and \a parent. +*/ + +QModelIndex QTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + executePendingSort(); + + int c = columnCount(parent); + if (row < 0 || column < 0 || column >= c) + return QModelIndex(); + + QTreeWidgetItem *parentItem = parent.isValid() ? item(parent) : rootItem; + if (parentItem && row < parentItem->childCount()) { + QTreeWidgetItem *itm = parentItem->child(row); + if (itm) + return createIndex(row, column, itm); + return QModelIndex(); + } + + return QModelIndex(); +} + +/*! + \internal + \reimp + + Returns the parent model index of the index given as + the \a child. +*/ + +QModelIndex QTreeModel::parent(const QModelIndex &child) const +{ + SkipSorting skipSorting(this); //The reason we don't sort here is that this might be called from a valid QPersistentModelIndex + //We don't want it to become suddenly invalid + + if (!child.isValid()) + return QModelIndex(); + QTreeWidgetItem *itm = static_cast(child.internalPointer()); + if (!itm || itm == rootItem) + return QModelIndex(); + QTreeWidgetItem *parent = itm->parent(); + return index(parent, 0); +} + +/*! + \internal + \reimp + + Returns the number of rows in the \a parent model index. +*/ + +int QTreeModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return rootItem->childCount(); + + QTreeWidgetItem *parentItem = item(parent); + if (parentItem) + return parentItem->childCount(); + return 0; +} + +/*! + \internal + \reimp + + Returns the number of columns in the item referred to by + the given \a index. +*/ + +int QTreeModel::columnCount(const QModelIndex &index) const +{ + Q_UNUSED(index); + if (!headerItem) + return 0; + return headerItem->columnCount(); +} + +bool QTreeModel::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return (rootItem->childCount() > 0); + + QTreeWidgetItem *itm = item(parent); + if (!itm) + return false; + switch (itm->d->policy) { + case QTreeWidgetItem::ShowIndicator: + return true; + case QTreeWidgetItem::DontShowIndicator: + return false; + case QTreeWidgetItem::DontShowIndicatorWhenChildless: + return (itm->childCount() > 0); + } + return false; +} + +/*! + \internal + \reimp + + Returns the data corresponding to the given model \a index + and \a role. +*/ + +QVariant QTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + QTreeWidgetItem *itm = item(index); + if (itm) + return itm->data(index.column(), role); + return QVariant(); +} + +/*! + \internal + \reimp + + Sets the data for the item specified by the \a index and \a role + to that referred to by the \a value. + + Returns true if successful; otherwise returns false. +*/ + +bool QTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + QTreeWidgetItem *itm = item(index); + if (itm) { + itm->setData(index.column(), role, value); + return true; + } + return false; +} + +QMap QTreeModel::itemData(const QModelIndex &index) const +{ + QMap roles; + QTreeWidgetItem *itm = item(index); + if (itm) { + int column = index.column(); + if (column < itm->values.count()) { + for (int i = 0; i < itm->values.at(column).count(); ++i) { + roles.insert(itm->values.at(column).at(i).role, + itm->values.at(column).at(i).value); + } + } + + // the two special cases + QVariant displayValue = itm->data(column, Qt::DisplayRole); + if (displayValue.isValid()) + roles.insert(Qt::DisplayRole, displayValue); + + QVariant checkValue = itm->data(column, Qt::CheckStateRole); + if (checkValue.isValid()) + roles.insert(Qt::CheckStateRole, checkValue); + } + return roles; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::insertRows(int row, int count, const QModelIndex &parent) +{ + SkipSorting skipSorting(this); + if (count < 1 || row < 0 || row > rowCount(parent) || parent.column() > 0) + return false; + + beginInsertRows(parent, row, row + count - 1); + QTreeWidgetItem *par = item(parent); + while (count > 0) { + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->view = view(); + item->par = par; + if (par) + par->children.insert(row++, item); + else + rootItem->children.insert(row++, item); + --count; + } + endInsertRows(); + return true; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + SkipSorting skipSorting(this); + if (count < 1 || column < 0 || column > columnCount(parent) || parent.column() > 0 || !headerItem) + return false; + + beginInsertColumns(parent, column, column + count - 1); + + int oldCount = columnCount(parent); + column = qBound(0, column, oldCount); + headerItem->values.resize(oldCount + count); + for (int i = oldCount; i < oldCount + count; ++i) { + headerItem->values[i].append(QWidgetItemData(Qt::DisplayRole, QString::number(i + 1))); + headerItem->d->display.append(QString::number(i + 1)); + } + + QStack itemstack; + itemstack.push(0); + while (!itemstack.isEmpty()) { + QTreeWidgetItem *par = itemstack.pop(); + QList children = par ? par->children : rootItem->children; + for (int row = 0; row < children.count(); ++row) { + QTreeWidgetItem *child = children.at(row); + if (child->children.count()) + itemstack.push(child); + child->values.insert(column, count, QVector()); + } + } + + endInsertColumns(); + return true; +} + +/*! + \internal + \reimp +*/ +bool QTreeModel::removeRows(int row, int count, const QModelIndex &parent) { + if (count < 1 || row < 0 || (row + count) > rowCount(parent)) + return false; + + beginRemoveRows(parent, row, row + count - 1); + + bool blockSignal = signalsBlocked(); + blockSignals(true); + + QTreeWidgetItem *itm = item(parent); + for (int i = row + count - 1; i >= row; --i) { + QTreeWidgetItem *child = itm ? itm->takeChild(i) : rootItem->children.takeAt(i); + Q_ASSERT(child); + child->view = 0; + delete child; + child = 0; + } + blockSignals(blockSignal); + + endRemoveRows(); + return true; +} + +/*! + \internal + \reimp + + Returns the header data corresponding to the given header \a section, + \a orientation and data \a role. +*/ + +QVariant QTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) + return QVariant(); + + if (headerItem) + return headerItem->data(section, role); + if (role == Qt::DisplayRole) + return QString::number(section + 1); + return QVariant(); +} + +/*! + \internal + \reimp + + Sets the header data for the item specified by the header \a section, + \a orientation and data \a role to the given \a value. + + Returns true if successful; otherwise returns false. +*/ + +bool QTreeModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) +{ + if (section < 0 || orientation != Qt::Horizontal || !headerItem || section >= columnCount()) + return false; + + headerItem->setData(section, role, value); + return true; +} + +/*! + \reimp + + Returns the flags for the item referred to the given \a index. + +*/ + +Qt::ItemFlags QTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return rootItem->flags(); + QTreeWidgetItem *itm = item(index); + Q_ASSERT(itm); + return itm->flags(); +} + +/*! + \internal + + Sorts the entire tree in the model in the given \a order, + by the values in the given \a column. +*/ + +void QTreeModel::sort(int column, Qt::SortOrder order) +{ + SkipSorting skipSorting(this); + sortPendingTimer.stop(); + + if (column < 0 || column >= columnCount()) + return; + + //layoutAboutToBeChanged and layoutChanged will be called by sortChildren + rootItem->sortChildren(column, order, true); +} + +/*! + \internal +*/ +void QTreeModel::ensureSorted(int column, Qt::SortOrder order, + int start, int end, const QModelIndex &parent) +{ + if (isChanging()) + return; + + sortPendingTimer.stop(); + + if (column < 0 || column >= columnCount()) + return; + + SkipSorting skipSorting(this); + + QTreeWidgetItem *itm = item(parent); + if (!itm) + itm = rootItem; + QList lst = itm->children; + + int count = end - start + 1; + QVector < QPair > sorting(count); + for (int i = 0; i < count; ++i) { + sorting[i].first = lst.at(start + i); + sorting[i].second = start + i; + } + + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList oldPersistentIndexes; + QModelIndexList newPersistentIndexes; + QList::iterator lit = lst.begin(); + bool changed = false; + + for (int i = 0; i < count; ++i) { + int oldRow = sorting.at(i).second; + QTreeWidgetItem *item = lst.takeAt(oldRow); + lit = sortedInsertionIterator(lit, lst.end(), order, item); + int newRow = qMax(lit - lst.begin(), 0); + + if ((newRow < oldRow) && !(*item < *lst.at(oldRow - 1)) && !(*lst.at(oldRow - 1) < *item )) + newRow = oldRow; + + lit = lst.insert(lit, item); + if (newRow != oldRow) { + // we are going to change the persistent indexes, so we need to prepare + if (!changed) { // this will only happen once + changed = true; + emit layoutAboutToBeChanged(); // the selection model needs to know + oldPersistentIndexes = persistentIndexList(); + newPersistentIndexes = oldPersistentIndexes; + } + for (int j = i + 1; j < count; ++j) { + int otherRow = sorting.at(j).second; + if (oldRow < otherRow && newRow >= otherRow) + --sorting[j].second; + else if (oldRow > otherRow && newRow <= otherRow) + ++sorting[j].second; + } + for (int k = 0; k < newPersistentIndexes.count(); ++k) { + QModelIndex pi = newPersistentIndexes.at(k); + if (pi.parent() != parent) + continue; + int oldPersistentRow = pi.row(); + int newPersistentRow = oldPersistentRow; + if (oldPersistentRow == oldRow) + newPersistentRow = newRow; + else if (oldRow < oldPersistentRow && newRow >= oldPersistentRow) + newPersistentRow = oldPersistentRow - 1; + else if (oldRow > oldPersistentRow && newRow <= oldPersistentRow) + newPersistentRow = oldPersistentRow + 1; + if (newPersistentRow != oldPersistentRow) + newPersistentIndexes[k] = createIndex(newPersistentRow, + pi.column(), pi.internalPointer()); + } + } + } + + if (changed) { + itm->children = lst; + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + emit layoutChanged(); + } +} + +/*! + \internal + + Returns true if the value of the \a left item is + less than the value of the \a right item. + + Used by the sorting functions. +*/ + +bool QTreeModel::itemLessThan(const QPair &left, + const QPair &right) +{ + return *(left.first) < *(right.first); +} + +/*! + \internal + + Returns true if the value of the \a left item is + greater than the value of the \a right item. + + Used by the sorting functions. +*/ + +bool QTreeModel::itemGreaterThan(const QPair &left, + const QPair &right) +{ + return *(right.first) < *(left.first); +} + +/*! + \internal +*/ +QList::iterator QTreeModel::sortedInsertionIterator( + const QList::iterator &begin, + const QList::iterator &end, + Qt::SortOrder order, QTreeWidgetItem *item) +{ + if (order == Qt::AscendingOrder) + return qLowerBound(begin, end, item, QTreeModelLessThan()); + return qLowerBound(begin, end, item, QTreeModelGreaterThan()); +} + +QStringList QTreeModel::mimeTypes() const +{ + return view()->mimeTypes(); +} + +QMimeData *QTreeModel::internalMimeData() const +{ + return QAbstractItemModel::mimeData(cachedIndexes); +} + +QMimeData *QTreeModel::mimeData(const QModelIndexList &indexes) const +{ + QList items; + for (int i = 0; i < indexes.count(); ++i) { + if (indexes.at(i).column() == 0) // only one item per row + items << item(indexes.at(i)); + } + + // cachedIndexes is a little hack to avoid copying from QModelIndexList to + // QList and back again in the view + cachedIndexes = indexes; + QMimeData *mimeData = view()->mimeData(items); + cachedIndexes.clear(); + return mimeData; +} + +bool QTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (row == -1 && column == -1) + row = rowCount(parent); // append + return view()->dropMimeData(item(parent), row, data, action); +} + +Qt::DropActions QTreeModel::supportedDropActions() const +{ + return view()->supportedDropActions(); +} + +void QTreeModel::itemChanged(QTreeWidgetItem *item) +{ + SkipSorting skipSorting(this); //this is kind of wrong, but not doing this would kill performence + QModelIndex left = index(item, 0); + QModelIndex right = index(item, item->columnCount() - 1); + emit dataChanged(left, right); +} + +bool QTreeModel::isChanging() const +{ + Q_D(const QTreeModel); + return !d->changes.isEmpty(); +} + +/*! + \internal + Emits the dataChanged() signal for the given \a item. + if column is -1 then all columns have changed +*/ + +void QTreeModel::emitDataChanged(QTreeWidgetItem *item, int column) +{ + if (signalsBlocked()) + return; + + if (headerItem == item && column < item->columnCount()) { + if (column == -1) + emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); + else + emit headerDataChanged(Qt::Horizontal, column, column); + return; + } + + SkipSorting skipSorting(this); //This is a little bit wrong, but not doing it would kill performence + + QModelIndex bottomRight, topLeft; + if (column == -1) { + topLeft = index(item, 0); + bottomRight = createIndex(topLeft.row(), columnCount() - 1, item); + } else { + topLeft = index(item, column); + bottomRight = topLeft; + } + emit dataChanged(topLeft, bottomRight); +} + +void QTreeModel::beginInsertItems(QTreeWidgetItem *parent, int row, int count) +{ + QModelIndex par = index(parent, 0); + beginInsertRows(par, row, row + count - 1); +} + +void QTreeModel::endInsertItems() +{ + endInsertRows(); +} + +void QTreeModel::beginRemoveItems(QTreeWidgetItem *parent, int row, int count) +{ + Q_ASSERT(row >= 0); + Q_ASSERT(count > 0); + beginRemoveRows(index(parent, 0), row, row + count - 1); + if (!parent) + parent = rootItem; + // now update the iterators + for (int i = 0; i < iterators.count(); ++i) { + for (int j = 0; j < count; j++) { + QTreeWidgetItem *c = parent->child(row + j); + iterators[i]->d_func()->ensureValidIterator(c); + } + } +} + +void QTreeModel::endRemoveItems() +{ + endRemoveRows(); +} + +void QTreeModel::sortItems(QList *items, int column, Qt::SortOrder order) +{ + // see QTreeViewItem::operator< + Q_UNUSED(column); + if (isChanging()) + return; + + // store the original order of indexes + QVector< QPair > sorting(items->count()); + for (int i = 0; i < sorting.count(); ++i) { + sorting[i].first = items->at(i); + sorting[i].second = i; + } + + // do the sorting + LessThan compare = (order == Qt::AscendingOrder ? &itemLessThan : &itemGreaterThan); + qStableSort(sorting.begin(), sorting.end(), compare); + + QModelIndexList fromList; + QModelIndexList toList; + int colCount = columnCount(); + for (int r = 0; r < sorting.count(); ++r) { + int oldRow = sorting.at(r).second; + if (oldRow == r) + continue; + QTreeWidgetItem *item = sorting.at(r).first; + items->replace(r, item); + for (int c = 0; c < colCount; ++c) { + QModelIndex from = createIndex(oldRow, c, item); + if (static_cast(d_ptr.data())->persistent.indexes.contains(from)) { + QModelIndex to = createIndex(r, c, item); + fromList << from; + toList << to; + } + } + } + changePersistentIndexList(fromList, toList); +} + +void QTreeModel::timerEvent(QTimerEvent *ev) +{ + if (ev->timerId() == sortPendingTimer.timerId()) { + executePendingSort(); + } else { + QAbstractItemModel::timerEvent(ev); + } +} + +/*! + \class QTreeWidgetItem + + \brief The QTreeWidgetItem class provides an item for use with the + QTreeWidget convenience class. + + \ingroup model-view + + Tree widget items are used to hold rows of information for tree widgets. + Rows usually contain several columns of data, each of which can contain + a text label and an icon. + + The QTreeWidgetItem class is a convenience class that replaces the + QListViewItem class in Qt 3. It provides an item for use with + the QTreeWidget class. + + Items are usually constructed with a parent that is either a QTreeWidget + (for top-level items) or a QTreeWidgetItem (for items on lower levels of + the tree). For example, the following code constructs a top-level item + to represent cities of the world, and adds a entry for Oslo as a child + item: + + \snippet doc/src/snippets/qtreewidget-using/mainwindow.cpp 3 + + Items can be added in a particular order by specifying the item they + follow when they are constructed: + + \snippet doc/src/snippets/qtreewidget-using/mainwindow.cpp 5 + + Each column in an item can have its own background brush which is set with + the setBackground() function. The current background brush can be + found with background(). + The text label for each column can be rendered with its own font and brush. + These are specified with the setFont() and setForeground() functions, + and read with font() and foreground(). + + The main difference between top-level items and those in lower levels of + the tree is that a top-level item has no parent(). This information + can be used to tell the difference between items, and is useful to know + when inserting and removing items from the tree. + Children of an item can be removed with takeChild() and inserted at a + given index in the list of children with the insertChild() function. + + By default, items are enabled, selectable, checkable, and can be the source + of a drag and drop operation. + Each item's flags can be changed by calling setFlags() with the appropriate + value (see \l{Qt::ItemFlags}). Checkable items can be checked and unchecked + with the setCheckState() function. The corresponding checkState() function + indicates whether the item is currently checked. + + \section1 Subclassing + + When subclassing QTreeWidgetItem to provide custom items, it is possible to + define new types for them so that they can be distinguished from standard + items. The constructors for subclasses that require this feature need to + call the base class constructor with a new type value equal to or greater + than \l UserType. + + \sa QTreeWidget, QTreeWidgetItemIterator, {Model/View Programming}, + QListWidgetItem, QTableWidgetItem +*/ + +/*! + \enum QTreeWidgetItem::ItemType + + This enum describes the types that are used to describe tree widget items. + + \value Type The default type for tree widget items. + \value UserType The minimum value for custom types. Values below UserType are + reserved by Qt. + + You can define new user types in QTreeWidgetItem subclasses to ensure that + custom items are treated specially; for example, when items are sorted. + + \sa type() +*/ + +/*! + \fn int QTreeWidgetItem::type() const + + Returns the type passed to the QTreeWidgetItem constructor. +*/ + +/*! + \fn void QTreeWidgetItem::sortChildren(int column, Qt::SortOrder order) + \since 4.2 + + Sorts the children of the item using the given \a order, + by the values in the given \a column. + + \note This function does nothing if the item is not associated with a + QTreeWidget. +*/ + +/*! + \fn QTreeWidget *QTreeWidgetItem::treeWidget() const + + Returns the tree widget that contains the item. +*/ + +/*! + \fn void QTreeWidgetItem::setSelected(bool select) + \since 4.2 + + Sets the selected state of the item to \a select. + + \sa isSelected() + +*/ + +/*! + \fn bool QTreeWidgetItem::isSelected() const + \since 4.2 + + Returns true if the item is selected, otherwise returns false. + + \sa setSelected() +*/ + +/*! + \fn void QTreeWidgetItem::setHidden(bool hide) + \since 4.2 + + Hides the item if \a hide is true, otherwise shows the item. + + \sa isHidden() +*/ + +/*! + \fn bool QTreeWidgetItem::isHidden() const + \since 4.2 + + Returns true if the item is hidden, otherwise returns false. + + \sa setHidden() +*/ + +/*! + \fn void QTreeWidgetItem::setExpanded(bool expand) + \since 4.2 + + Expands the item if \a expand is true, otherwise collapses the item. + \warning The QTreeWidgetItem must be added to the QTreeWidget before calling this function. + + \sa isExpanded() +*/ + +/*! + \fn bool QTreeWidgetItem::isExpanded() const + \since 4.2 + + Returns true if the item is expanded, otherwise returns false. + + \sa setExpanded() +*/ + +/*! + \fn void QTreeWidgetItem::setFirstColumnSpanned(bool span) + \since 4.3 + + Sets the first section to span all columns if \a span is true; + otherwise all item sections are shown. + + \sa isFirstColumnSpanned() +*/ + +/*! + \fn bool QTreeWidgetItem::isFirstColumnSpanned() const + \since 4.3 + + Returns true if the item is spanning all the columns in a row; otherwise returns false. + + \sa setFirstColumnSpanned() +*/ + +/*! + \fn QString QTreeWidgetItem::text(int column) const + + Returns the text in the specified \a column. + + \sa setText() +*/ + +/*! + \fn void QTreeWidgetItem::setText(int column, const QString &text) + + Sets the text to be displayed in the given \a column to the given \a text. + + \sa text() setFont() setForeground() +*/ + +/*! + \fn QIcon QTreeWidgetItem::icon(int column) const + + Returns the icon that is displayed in the specified \a column. + + \sa setIcon(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn void QTreeWidgetItem::setIcon(int column, const QIcon &icon) + + Sets the icon to be displayed in the given \a column to \a icon. + + \sa icon(), setText(), {QAbstractItemView::iconSize}{iconSize} +*/ + +/*! + \fn QString QTreeWidgetItem::statusTip(int column) const + + Returns the status tip for the contents of the given \a column. + + \sa setStatusTip() +*/ + +/*! + \fn void QTreeWidgetItem::setStatusTip(int column, const QString &statusTip) + + Sets the status tip for the given \a column to the given \a statusTip. + QTreeWidget mouse tracking needs to be enabled for this feature to work. + + \sa statusTip() setToolTip() setWhatsThis() +*/ + +/*! + \fn QString QTreeWidgetItem::toolTip(int column) const + + Returns the tool tip for the given \a column. + + \sa setToolTip() +*/ + +/*! + \fn void QTreeWidgetItem::setToolTip(int column, const QString &toolTip) + + Sets the tooltip for the given \a column to \a toolTip. + + \sa toolTip() setStatusTip() setWhatsThis() +*/ + +/*! + \fn QString QTreeWidgetItem::whatsThis(int column) const + + Returns the "What's This?" help for the contents of the given \a column. + + \sa setWhatsThis() +*/ + +/*! + \fn void QTreeWidgetItem::setWhatsThis(int column, const QString &whatsThis) + + Sets the "What's This?" help for the given \a column to \a whatsThis. + + \sa whatsThis() setStatusTip() setToolTip() +*/ + +/*! + \fn QFont QTreeWidgetItem::font(int column) const + + Returns the font used to render the text in the specified \a column. + + \sa setFont() +*/ + +/*! + \fn void QTreeWidgetItem::setFont(int column, const QFont &font) + + Sets the font used to display the text in the given \a column to the given + \a font. + + \sa font() setText() setForeground() +*/ + +/*! + \fn QColor QTreeWidgetItem::backgroundColor(int column) const + \obsolete + + This function is deprecated. Use background() instead. +*/ + +/*! + \fn void QTreeWidgetItem::setBackgroundColor(int column, const QColor &color) + \obsolete + + This function is deprecated. Use setBackground() instead. +*/ + +/*! + \fn QBrush QTreeWidgetItem::background(int column) const + \since 4.2 + + Returns the brush used to render the background of the specified \a column. + + \sa foreground() +*/ + +/*! + \fn void QTreeWidgetItem::setBackground(int column, const QBrush &brush) + \since 4.2 + + Sets the background brush of the label in the given \a column to the + specified \a brush. + + \sa setForeground() +*/ + +/*! + \fn QColor QTreeWidgetItem::textColor(int column) const + \obsolete + + This function is deprecated. Use foreground() instead. +*/ + +/*! + \fn void QTreeWidgetItem::setTextColor(int column, const QColor &color) + \obsolete + + This function is deprecated. Use setForeground() instead. +*/ + +/*! + \fn QBrush QTreeWidgetItem::foreground(int column) const + \since 4.2 + + Returns the brush used to render the foreground (e.g. text) of the + specified \a column. + + \sa background() +*/ + +/*! + \fn void QTreeWidgetItem::setForeground(int column, const QBrush &brush) + \since 4.2 + + Sets the foreground brush of the label in the given \a column to the + specified \a brush. + + \sa setBackground() +*/ + +/*! + \fn Qt::CheckState QTreeWidgetItem::checkState(int column) const + + Returns the check state of the label in the given \a column. + + \sa Qt::CheckState +*/ + +/*! + \fn void QTreeWidgetItem::setCheckState(int column, Qt::CheckState state) + + Sets the item in the given \a column check state to be \a state. + + \sa checkState() +*/ + +/*! + \fn QSize QTreeWidgetItem::sizeHint(int column) const + \since 4.1 + + Returns the size hint set for the tree item in the given + \a column (see \l{QSize}). +*/ + +/*! + \fn void QTreeWidgetItem::setSizeHint(int column, const QSize &size) + \since 4.1 + + Sets the size hint for the tree item in the given \a column to be \a size. + If no size hint is set, the item delegate will compute the size hint based + on the item data. +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItem::parent() const + + Returns the item's parent. + + \sa child() +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItem::child(int index) const + + Returns the item at the given \a index in the list of the item's children. + + \sa parent() +*/ + +/*! + \fn int QTreeWidgetItem::childCount() const + + Returns the number of child items. +*/ + +/*! + \fn int QTreeWidgetItem::columnCount() const + + Returns the number of columns in the item. +*/ + +/*! + \fn int QTreeWidgetItem::textAlignment(int column) const + + Returns the text alignment for the label in the given \a column + (see \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn void QTreeWidgetItem::setTextAlignment(int column, int alignment) + + Sets the text alignment for the label in the given \a column to + the \a alignment specified (see \l{Qt::AlignmentFlag}). +*/ + +/*! + \fn int QTreeWidgetItem::indexOfChild(QTreeWidgetItem *child) const + + Returns the index of the given \a child in the item's list of children. +*/ + +/*! + Constructs a tree widget item of the specified \a type. The item + must be inserted into a tree widget. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ +} + + +/*! + Constructs a tree widget item of the specified \a type. The item + must be inserted into a tree widget. + The given list of \a strings will be set as the item text for each + column in the item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, int type) + + Constructs a tree widget item of the specified \a type and appends it + to the items in the given \a parent. + + \sa type() +*/ + +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (view && view->model()) { + QTreeModel *model = qobject_cast(view->model()); + model->rootItem->addChild(this); + values.reserve(model->headerItem->columnCount()); + } +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, const QStringList &strings, int type) + + Constructs a tree widget item of the specified \a type and appends it + to the items in the given \a parent. The given list of \a strings will be set as + the item text for each column in the item. + + \sa type() +*/ + +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); + if (view && view->model()) { + QTreeModel *model = qobject_cast(view->model()); + model->rootItem->addChild(this); + values.reserve(model->headerItem->columnCount()); + } +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *parent, QTreeWidgetItem *preceding, int type) + + Constructs a tree widget item of the specified \a type and inserts it into + the given \a parent after the \a preceding item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidget *view, QTreeWidgetItem *after, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (view) { + QTreeModel *model = qobject_cast(view->model()); + if (model) { + int i = model->rootItem->children.indexOf(after) + 1; + model->rootItem->insertChild(i, this); + values.reserve(model->headerItem->columnCount()); + } + } +} + +/*! + Constructs a tree widget item and append it to the given \a parent. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (parent) + parent->addChild(this); +} + +/*! + Constructs a tree widget item and append it to the given \a parent. + The given list of \a strings will be set as the item text for each column in the item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + for (int i = 0; i < strings.count(); ++i) + setText(i, strings.at(i)); + if (parent) + parent->addChild(this); +} + +/*! + \fn QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding, int type) + + Constructs a tree widget item of the specified \a type that is inserted + into the \a parent after the \a preceding child item. + + \sa type() +*/ +QTreeWidgetItem::QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, int type) + : rtti(type), view(0), d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(Qt::ItemIsSelectable + |Qt::ItemIsUserCheckable + |Qt::ItemIsEnabled + |Qt::ItemIsDragEnabled + |Qt::ItemIsDropEnabled) +{ + if (parent) { + int i = parent->children.indexOf(after) + 1; + parent->insertChild(i, this); + } +} + +/*! + Destroys this tree widget item. + + The item will be removed from \l{QTreeWidget}s to which it has + been added. This makes it safe to delete an item at any time. + +*/ + +QTreeWidgetItem::~QTreeWidgetItem() +{ + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + bool wasSkipSort = false; + if (model) { + wasSkipSort = model->skipPendingSort; + model->skipPendingSort = true; + } + if (par) { + int i = par->children.indexOf(this); + if (i >= 0) { + if (model) model->beginRemoveItems(par, i, 1); + // users _could_ do changes when connected to rowsAboutToBeRemoved, + // so we check again to make sure 'i' is valid + if (!par->children.isEmpty() && par->children.at(i) == this) + par->children.takeAt(i); + if (model) model->endRemoveItems(); + } + } else if (model) { + if (this == model->headerItem) { + model->headerItem = 0; + } else { + int i = model->rootItem->children.indexOf(this); + if (i >= 0) { + model->beginRemoveItems(0, i, 1); + // users _could_ do changes when connected to rowsAboutToBeRemoved, + // so we check again to make sure 'i' is valid + if (!model->rootItem->children.isEmpty() && model->rootItem->children.at(i) == this) + model->rootItem->children.takeAt(i); + model->endRemoveItems(); + } + } + } + // at this point the persistent indexes for the children should also be invalidated + // since we invalidated the parent + for (int i = 0; i < children.count(); ++i) { + QTreeWidgetItem *child = children.at(i); + // make sure the child does not try to remove itself from our children list + child->par = 0; + // make sure the child does not try to remove itself from the top level list + child->view = 0; + delete child; + } + + children.clear(); + delete d; + if (model) { + model->skipPendingSort = wasSkipSort; + } +} + +/*! + Creates a deep copy of the item and of its children. +*/ +QTreeWidgetItem *QTreeWidgetItem::clone() const +{ + QTreeWidgetItem *copy = 0; + + QStack stack; + QStack parentStack; + stack.push(this); + parentStack.push(0); + + QTreeWidgetItem *root = 0; + const QTreeWidgetItem *item = 0; + QTreeWidgetItem *parent = 0; + while (!stack.isEmpty()) { + // get current item, and copied parent + item = stack.pop(); + parent = parentStack.pop(); + + // copy item + copy = new QTreeWidgetItem(*item); + if (!root) + root = copy; + + // set parent and add to parents children list + if (parent) { + copy->par = parent; + parent->children.insert(0, copy); + } + + for (int i = 0; i < item->childCount(); ++i) { + stack.push(item->child(i)); + parentStack.push(copy); + } + } + return root; +} + +/*! + Sets the item indicator \a policy. This policy decides when the + tree branch expand/collapse indicator is shown. + The default value is ShowForChildren. + + \sa childIndicatorPolicy() +*/ +void QTreeWidgetItem::setChildIndicatorPolicy(QTreeWidgetItem::ChildIndicatorPolicy policy) +{ + if (d->policy == policy) + return; + d->policy = policy; + + if (!view) + return; + + view->scheduleDelayedItemsLayout(); +} + +/*! + Returns the item indicator policy. This policy decides when the + tree branch expand/collapse indicator is shown. + + \sa setChildIndicatorPolicy() +*/ +QTreeWidgetItem::ChildIndicatorPolicy QTreeWidgetItem::childIndicatorPolicy() const +{ + return d->policy; +} + +/*! + \fn void QTreeWidgetItem::setFlags(Qt::ItemFlags flags) + + Sets the flags for the item to the given \a flags. These determine whether + the item can be selected or modified. This is often used to disable an item. + + \sa flags() +*/ +void QTreeWidgetItem::setFlags(Qt::ItemFlags flags) +{ + const bool enable = (flags & Qt::ItemIsEnabled); + const bool changedState = bool(itemFlags & Qt::ItemIsEnabled) != enable; + const bool changedExplicit = d->disabled != !enable; + + d->disabled = !enable; + + if (enable && par && !(par->itemFlags & Qt::ItemIsEnabled)) // inherit from parent + itemFlags = flags & ~Qt::ItemIsEnabled; + else // this item is explicitly disabled or has no parent + itemFlags = flags; + + if (changedState && changedExplicit) { // if the propagate the change to the children + QStack parents; + parents.push(this); + while (!parents.isEmpty()) { + QTreeWidgetItem *parent = parents.pop(); + for (int i = 0; i < parent->children.count(); ++i) { + QTreeWidgetItem *child = parent->children.at(i); + if (!child->d->disabled) { // if not explicitly disabled + parents.push(child); + if (enable) + child->itemFlags = child->itemFlags | Qt::ItemIsEnabled; + else + child->itemFlags = child->itemFlags & ~Qt::ItemIsEnabled; + child->itemChanged(); // ### we may want to optimize this + } + } + } + } + itemChanged(); +} + +void QTreeWidgetItemPrivate::propagateDisabled(QTreeWidgetItem *item) +{ + Q_ASSERT(item); + const bool enable = item->par ? (item->par->itemFlags.testFlag(Qt::ItemIsEnabled)) : true; + + QStack parents; + parents.push(item); + while (!parents.isEmpty()) { + QTreeWidgetItem *parent = parents.pop(); + if (!parent->d->disabled) { // if not explicitly disabled + Qt::ItemFlags oldFlags = parent->itemFlags; + if (enable) + parent->itemFlags = parent->itemFlags | Qt::ItemIsEnabled; + else + parent->itemFlags = parent->itemFlags & ~Qt::ItemIsEnabled; + if (parent->itemFlags != oldFlags) + parent->itemChanged(); + } + + for (int i = 0; i < parent->children.count(); ++i) { + QTreeWidgetItem *child = parent->children.at(i); + parents.push(child); + } + } +} +/*! + \fn Qt::ItemFlags QTreeWidgetItem::flags() const + + Returns the flags used to describe the item. These determine whether + the item can be checked, edited, and selected. + + The default value for flags is + Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled. + If the item was constructed with a parent, flags will in addition contain Qt::ItemIsDropEnabled. + + \sa setFlags() +*/ +Qt::ItemFlags QTreeWidgetItem::flags() const +{ + return itemFlags; +} + +/*! + Sets the value for the item's \a column and \a role to the given + \a value. + + The \a role describes the type of data specified by \a value, and is defined by + the Qt::ItemDataRole enum. +*/ +void QTreeWidgetItem::setData(int column, int role, const QVariant &value) +{ + if (column < 0) + return; + + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: { + if (values.count() <= column) { + if (model && this == model->headerItem) + model->setColumnCount(column + 1); + else + values.resize(column + 1); + } + if (d->display.count() <= column) { + for (int i = d->display.count() - 1; i < column - 1; ++i) + d->display.append(QVariant()); + d->display.append(value); + } else if (d->display[column] != value) { + d->display[column] = value; + } else { + return; // value is unchanged + } + } break; + case Qt::CheckStateRole: + if (itemFlags & Qt::ItemIsTristate) { + for (int i = 0; i < children.count(); ++i) { + QTreeWidgetItem *child = children.at(i); + if (child->data(column, role).isValid()) {// has a CheckState + Qt::ItemFlags f = itemFlags; // a little hack to avoid multiple dataChanged signals + itemFlags &= ~Qt::ItemIsTristate; + child->setData(column, role, value); + itemFlags = f; + } + } + } + // Don't break, but fall through + default: + if (column < values.count()) { + bool found = false; + QVector column_values = values.at(column); + for (int i = 0; i < column_values.count(); ++i) { + if (column_values.at(i).role == role) { + if (column_values.at(i).value == value) + return; // value is unchanged + values[column][i].value = value; + found = true; + break; + } + } + if (!found) + values[column].append(QWidgetItemData(role, value)); + } else { + if (model && this == model->headerItem) + model->setColumnCount(column + 1); + else + values.resize(column + 1); + values[column].append(QWidgetItemData(role, value)); + } + } + + if (model) { + model->emitDataChanged(this, column); + if (role == Qt::CheckStateRole) { + QTreeWidgetItem *p; + for (p = par; p && (p->itemFlags & Qt::ItemIsTristate); p = p->par) + model->emitDataChanged(p, column); + } + } +} + +/*! + Returns the value for the item's \a column and \a role. +*/ +QVariant QTreeWidgetItem::data(int column, int role) const +{ + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + if (column >= 0 && column < d->display.count()) + return d->display.at(column); + break; + case Qt::CheckStateRole: + // special case for check state in tristate + if (children.count() && (itemFlags & Qt::ItemIsTristate)) + return childrenCheckState(column); + // fallthrough intended + default: + if (column >= 0 && column < values.size()) { + const QVector &column_values = values.at(column); + for (int i = 0; i < column_values.count(); ++i) + if (column_values.at(i).role == role) + return column_values.at(i).value; + } + } + return QVariant(); +} + +/*! + Returns true if the text in the item is less than the text in the + \a other item, otherwise returns false. +*/ + +bool QTreeWidgetItem::operator<(const QTreeWidgetItem &other) const +{ + int column = view ? view->sortColumn() : 0; + const QVariant v1 = data(column, Qt::DisplayRole); + const QVariant v2 = other.data(column, Qt::DisplayRole); + return QAbstractItemModelPrivate::variantLessThan(v1, v2); +} + +#ifndef QT_NO_DATASTREAM + +/*! + Reads the item from stream \a in. This only reads data into a single item. + + \sa write() +*/ +void QTreeWidgetItem::read(QDataStream &in) +{ + // convert from streams written before we introduced display (4.2.0) + if (in.version() < QDataStream::Qt_4_2) { + d->display.clear(); + in >> values; + // move the display value over to the display string list + for (int column = 0; column < values.count(); ++column) { + d->display << QVariant(); + for (int i = 0; i < values.at(column).count(); ++i) { + if (values.at(column).at(i).role == Qt::DisplayRole) { + d->display[column] = values.at(column).at(i).value; + values[column].remove(i--); + } + } + } + } else { + in >> values >> d->display; + } +} + +/*! + Writes the item to stream \a out. This only writes data from one single item. + + \sa read() +*/ +void QTreeWidgetItem::write(QDataStream &out) const +{ + out << values << d->display; +} +#endif // QT_NO_DATASTREAM + +/*! + \since 4.1 + + Constructs a copy of \a other. Note that type() and treeWidget() + are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTreeWidgetItem::QTreeWidgetItem(const QTreeWidgetItem &other) + : rtti(Type), values(other.values), view(0), + d(new QTreeWidgetItemPrivate(this)), par(0), + itemFlags(other.itemFlags) +{ + d->display = other.d->display; +} + +/*! + Assigns \a other's data and flags to this item. Note that type() + and treeWidget() are not copied. + + This function is useful when reimplementing clone(). + + \sa data(), flags() +*/ +QTreeWidgetItem &QTreeWidgetItem::operator=(const QTreeWidgetItem &other) +{ + values = other.values; + d->display = other.d->display; + d->policy = other.d->policy; + itemFlags = other.itemFlags; + return *this; +} + +/*! + Appends the \a child item to the list of children. + + \sa insertChild() takeChild() +*/ +void QTreeWidgetItem::addChild(QTreeWidgetItem *child) +{ + if (child) { + insertChild(children.count(), child); + child->d->rowGuess = children.count() - 1; + } +} + +/*! + Inserts the \a child item at \a index in the list of children. + + If the child has already been inserted somewhere else it wont be inserted again. +*/ +void QTreeWidgetItem::insertChild(int index, QTreeWidgetItem *child) +{ + if (index < 0 || index > children.count() || child == 0 || child->view != 0 || child->par != 0) + return; + + if (QTreeModel *model = (view ? qobject_cast(view->model()) : 0)) { + const bool wasSkipSort = model->skipPendingSort; + model->skipPendingSort = true; + if (model->rootItem == this) + child->par = 0; + else + child->par = this; + if (view->isSortingEnabled()) { + // do a delayed sort instead + if (!model->sortPendingTimer.isActive()) + model->sortPendingTimer.start(0, model); + } + model->beginInsertItems(this, index, 1); + int cols = model->columnCount(); + QStack stack; + stack.push(child); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = view; + i->values.reserve(cols); + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + children.insert(index, child); + model->endInsertItems(); + model->skipPendingSort = wasSkipSort; + } else { + child->par = this; + children.insert(index, child); + } + if (child->par) + d->propagateDisabled(child); +} + +/*! + Removes the given item indicated by \a child. + The removed item will not be deleted. +*/ +void QTreeWidgetItem::removeChild(QTreeWidgetItem *child) +{ + (void)takeChild(children.indexOf(child)); +} + +/*! + Removes the item at \a index and returns it, otherwise return 0. +*/ +QTreeWidgetItem *QTreeWidgetItem::takeChild(int index) +{ + // we move this outside the check of the index to allow executing + // pending sorts from inline functions, using this function (hack) + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + if (model) { + // This will trigger a layoutChanged signal, thus we might want to optimize + // this function by not emitting the rowsRemoved signal etc to the view. + // On the other hand we also need to make sure that the selectionmodel + // is updated in case we take an item that is selected. + model->skipPendingSort = false; + model->executePendingSort(); + } + if (index >= 0 && index < children.count()) { + if (model) model->beginRemoveItems(this, index, 1); + QTreeWidgetItem *item = children.takeAt(index); + item->par = 0; + QStack stack; + stack.push(item); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = 0; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + d->propagateDisabled(item); + if (model) model->endRemoveRows(); + return item; + } + return 0; +} + +/*! + \since 4.1 + + Appends the given list of \a children to the item. + + \sa insertChildren() takeChildren() +*/ +void QTreeWidgetItem::addChildren(const QList &children) +{ + insertChildren(this->children.count(), children); +} + +/*! + \since 4.1 + + Inserts the given list of \a children into the list of the item children at \a index . + + Children that have already been inserted somewhere else wont be inserted. +*/ +void QTreeWidgetItem::insertChildren(int index, const QList &children) +{ + if (view && view->isSortingEnabled()) { + for (int n = 0; n < children.count(); ++n) + insertChild(index, children.at(n)); + return; + } + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + QStack stack; + QList itemsToInsert; + for (int n = 0; n < children.count(); ++n) { + QTreeWidgetItem *child = children.at(n); + if (child->view || child->par) + continue; + itemsToInsert.append(child); + if (view && model) { + if (child->childCount() == 0) + child->view = view; + else + stack.push(child); + } + if (model && (model->rootItem == this)) + child->par = 0; + else + child->par = this; + } + if (!itemsToInsert.isEmpty()) { + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = view; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + if (model) model->beginInsertItems(this, index, itemsToInsert.count()); + for (int n = 0; n < itemsToInsert.count(); ++n) { + QTreeWidgetItem *child = itemsToInsert.at(n); + this->children.insert(index + n, child); + if (child->par) + d->propagateDisabled(child); + } + if (model) model->endInsertItems(); + } +} + +/*! + \since 4.1 + + Removes the list of children and returns it, otherwise returns an empty list. +*/ +QList QTreeWidgetItem::takeChildren() +{ + QList removed; + if (children.count() > 0) { + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + if (model) { + // This will trigger a layoutChanged signal, thus we might want to optimize + // this function by not emitting the rowsRemoved signal etc to the view. + // On the other hand we also need to make sure that the selectionmodel + // is updated in case we take an item that is selected. + model->executePendingSort(); + } + if (model) model->beginRemoveItems(this, 0, children.count()); + for (int n = 0; n < children.count(); ++n) { + QTreeWidgetItem *item = children.at(n); + item->par = 0; + QStack stack; + stack.push(item); + while (!stack.isEmpty()) { + QTreeWidgetItem *i = stack.pop(); + i->view = 0; + for (int c = 0; c < i->children.count(); ++c) + stack.push(i->children.at(c)); + } + d->propagateDisabled(item); + } + removed = children; + children.clear(); // detach + if (model) model->endRemoveItems(); + } + return removed; +} + + +void QTreeWidgetItemPrivate::sortChildren(int column, Qt::SortOrder order, bool climb) +{ + QTreeModel *model = (q->view ? qobject_cast(q->view->model()) : 0); + if (!model) + return; + model->sortItems(&q->children, column, order); + if (climb) { + QList::iterator it = q->children.begin(); + for (; it != q->children.end(); ++it) { + //here we call the private object's method to avoid emitting + //the layoutAboutToBeChanged and layoutChanged signals + (*it)->d->sortChildren(column, order, climb); + } + } +} + +/*! + \internal + + Sorts the children by the value in the given \a column, in the \a order + specified. If \a climb is true, the items below each of the children will + also be sorted. +*/ +void QTreeWidgetItem::sortChildren(int column, Qt::SortOrder order, bool climb) +{ + QTreeModel *model = (view ? qobject_cast(view->model()) : 0); + if (!model) + return; + if (model->isChanging()) + return; + int oldSortColumn = view->d_func()->explicitSortColumn; + view->d_func()->explicitSortColumn = column; + emit model->layoutAboutToBeChanged(); + d->sortChildren(column, order, climb); + emit model->layoutChanged(); + view->d_func()->explicitSortColumn = oldSortColumn; +} + +/*! + \internal + + Calculates the checked state of the item based on the checked state + of its children. E.g. if all children checked => this item is also + checked; if some children checked => this item is partially checked; + if no children checked => this item is unchecked. +*/ +QVariant QTreeWidgetItem::childrenCheckState(int column) const +{ + if (column < 0) + return QVariant(); + bool checkedChildren = false; + bool uncheckedChildren = false; + for (int i = 0; i < children.count(); ++i) { + QVariant value = children.at(i)->data(column, Qt::CheckStateRole); + if (!value.isValid()) + return QVariant(); + + switch (static_cast(value.toInt())) + { + case Qt::Unchecked: + uncheckedChildren = true; + break; + case Qt::Checked: + checkedChildren = true; + break; + case Qt::PartiallyChecked: + default: + return Qt::PartiallyChecked; + } + } + + if (uncheckedChildren && checkedChildren) + return Qt::PartiallyChecked; + if (uncheckedChildren) + return Qt::Unchecked; + else if (checkedChildren) + return Qt::Checked; + else + return QVariant(); // value was not defined +} + +/*! + \since 4.5 + + Causes the model associated with this item to emit a + \l{QAbstractItemModel::dataChanged()}{dataChanged}() signal for this + item. + + You normally only need to call this function if you have subclassed + QTreeWidgetItem and reimplemented data() and/or setData(). + + \sa setData() +*/ +void QTreeWidgetItem::emitDataChanged() +{ + itemChanged(); +} + +/*! + \internal +*/ +void QTreeWidgetItem::itemChanged() +{ + if (QTreeModel *model = (view ? qobject_cast(view->model()) : 0)) + model->itemChanged(this); +} + +/*! + \internal +*/ +void QTreeWidgetItem::executePendingSort() const +{ + if (QTreeModel *model = (view ? qobject_cast(view->model()) : 0)) + model->executePendingSort(); +} + + +#ifndef QT_NO_DATASTREAM +/*! + \relates QTreeWidgetItem + + Writes the tree widget item \a item to stream \a out. + + This operator uses QTreeWidgetItem::write(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &out, const QTreeWidgetItem &item) +{ + item.write(out); + return out; +} + +/*! + \relates QTreeWidgetItem + + Reads a tree widget item from stream \a in into \a item. + + This operator uses QTreeWidgetItem::read(). + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &in, QTreeWidgetItem &item) +{ + item.read(in); + return in; +} +#endif // QT_NO_DATASTREAM + + +void QTreeWidgetPrivate::_q_emitItemPressed(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemPressed(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemClicked(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemClicked(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemDoubleClicked(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemDoubleClicked(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemActivated(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemActivated(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemEntered(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemEntered(item(index), index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemChanged(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + QTreeWidgetItem *indexItem = item(index); + if (indexItem) + emit q->itemChanged(indexItem, index.column()); +} + +void QTreeWidgetPrivate::_q_emitItemExpanded(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemExpanded(item(index)); +} + +void QTreeWidgetPrivate::_q_emitItemCollapsed(const QModelIndex &index) +{ + Q_Q(QTreeWidget); + emit q->itemCollapsed(item(index)); +} + +void QTreeWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + Q_Q(QTreeWidget); + QTreeWidgetItem *currentItem = item(current); + QTreeWidgetItem *previousItem = item(previous); + emit q->currentItemChanged(currentItem, previousItem); +} + +void QTreeWidgetPrivate::_q_sort() +{ + if (sortingEnabled) { + int column = header->sortIndicatorSection(); + Qt::SortOrder order = header->sortIndicatorOrder(); + treeModel()->sort(column, order); + } +} + +void QTreeWidgetPrivate::_q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + Q_Q(QTreeWidget); + QModelIndexList indices = selected.indexes(); + int i; + QTreeModel *m = treeModel(); + for (i = 0; i < indices.count(); ++i) { + QTreeWidgetItem *item = m->item(indices.at(i)); + item->d->selected = true; + } + + indices = deselected.indexes(); + for (i = 0; i < indices.count(); ++i) { + QTreeWidgetItem *item = m->item(indices.at(i)); + item->d->selected = false; + } + + emit q->itemSelectionChanged(); +} + +void QTreeWidgetPrivate::_q_dataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + if (sortingEnabled && topLeft.isValid() && bottomRight.isValid() + && !treeModel()->sortPendingTimer.isActive()) { + int column = header->sortIndicatorSection(); + if (column >= topLeft.column() && column <= bottomRight.column()) { + Qt::SortOrder order = header->sortIndicatorOrder(); + treeModel()->ensureSorted(column, order, topLeft.row(), + bottomRight.row(), topLeft.parent()); + } + } +} + +/*! + \class QTreeWidget + + \brief The QTreeWidget class provides a tree view that uses a predefined + tree model. + + \ingroup model-view + + + The QTreeWidget class is a convenience class that provides a standard + tree widget with a classic item-based interface similar to that used by + the QListView class in Qt 3. + This class is based on Qt's Model/View architecture and uses a default + model to hold items, each of which is a QTreeWidgetItem. + + Developers who do not need the flexibility of the Model/View framework + can use this class to create simple hierarchical lists very easily. A more + flexible approach involves combining a QTreeView with a standard item model. + This allows the storage of data to be separated from its representation. + + In its simplest form, a tree widget can be constructed in the following way: + + \snippet doc/src/snippets/code/src_gui_itemviews_qtreewidget.cpp 0 + + Before items can be added to the tree widget, the number of columns must + be set with setColumnCount(). This allows each item to have one or more + labels or other decorations. The number of columns in use can be found + with the columnCount() function. + + The tree can have a header that contains a section for each column in + the widget. It is easiest to set up the labels for each section by + supplying a list of strings with setHeaderLabels(), but a custom header + can be constructed with a QTreeWidgetItem and inserted into the tree + with the setHeaderItem() function. + + The items in the tree can be sorted by column according to a predefined + sort order. If sorting is enabled, the user can sort the items by clicking + on a column header. Sorting can be enabled or disabled by calling + \l{QTreeView::setSortingEnabled()}{setSortingEnabled()}. The + \l{QTreeView::isSortingEnabled()}{isSortingEnabled()} function indicates + whether sorting is enabled. + + \table 100% + \row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree widget + \o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree widget + \o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree widget + \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree widget. + \o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree widget. + \o A \l{Plastique Style Widget Gallery}{Plastique style} tree widget. + \endtable + + \sa QTreeWidgetItem, QTreeWidgetItemIterator, QTreeView, + {Model/View Programming}, {Settings Editor Example} +*/ + +/*! + \property QTreeWidget::columnCount + \brief the number of columns displayed in the tree widget + + By default, this property has a value of 1. +*/ + +/*! + \fn void QTreeWidget::itemActivated(QTreeWidgetItem *item, int column) + + This signal is emitted when the user activates an item by single- + or double-clicking (depending on the platform, i.e. on the + QStyle::SH_ItemView_ActivateItemOnSingleClick style hint) or + pressing a special key (e.g., \key Enter). + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked, or -1 if no item was clicked. +*/ + +/*! + \fn void QTreeWidget::itemPressed(QTreeWidgetItem *item, int column) + + This signal is emitted when the user presses a mouse button inside + the widget. + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked, or -1 if no item was clicked. +*/ + +/*! + \fn void QTreeWidget::itemClicked(QTreeWidgetItem *item, int column) + + This signal is emitted when the user clicks inside the widget. + + The specified \a item is the item that was clicked. The \a column is the + item's column that was clicked. If no item was clicked, no signal will be + emitted. +*/ + +/*! + \fn void QTreeWidget::itemDoubleClicked(QTreeWidgetItem *item, int column) + + This signal is emitted when the user double clicks inside the + widget. + + The specified \a item is the item that was clicked, or 0 if no + item was clicked. The \a column is the item's column that was + clicked. If no item was double clicked, no signal will be emitted. +*/ + +/*! + \fn void QTreeWidget::itemExpanded(QTreeWidgetItem *item) + + This signal is emitted when the specified \a item is expanded so that + all of its children are displayed. + + \note This signal will not be emitted if an item changes its state when + expandAll() is invoked. + + \sa QTreeWidgetItem::isExpanded(), itemCollapsed(), expandItem() +*/ + +/*! + \fn void QTreeWidget::itemCollapsed(QTreeWidgetItem *item) + + This signal is emitted when the specified \a item is collapsed so that + none of its children are displayed. + + \note This signal will not be emitted if an item changes its state when + collapseAll() is invoked. + + \sa QTreeWidgetItem::isExpanded(), itemExpanded(), collapseItem() +*/ + +/*! + \fn void QTreeWidget::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) + + This signal is emitted when the current item changes. The current + item is specified by \a current, and this replaces the \a previous + current item. + + \sa setCurrentItem() +*/ + +/*! + \fn void QTreeWidget::itemSelectionChanged() + + This signal is emitted when the selection changes in the tree widget. + The current selection can be found with selectedItems(). +*/ + +/*! + \fn void QTreeWidget::itemEntered(QTreeWidgetItem *item, int column) + + This signal is emitted when the mouse cursor enters an \a item over the + specified \a column. + QTreeWidget mouse tracking needs to be enabled for this feature to work. +*/ + +/*! + \fn void QTreeWidget::itemChanged(QTreeWidgetItem *item, int column) + + This signal is emitted when the contents of the \a column in the specified + \a item changes. +*/ + +/*! + \since 4.3 + + \fn void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column) + + Removes the widget set in the given \a item in the given \a column. +*/ + +/*! + Constructs a tree widget with the given \a parent. +*/ +QTreeWidget::QTreeWidget(QWidget *parent) + : QTreeView(*new QTreeWidgetPrivate(), parent) +{ + QTreeView::setModel(new QTreeModel(1, this)); + connect(this, SIGNAL(pressed(QModelIndex)), + SLOT(_q_emitItemPressed(QModelIndex))); + connect(this, SIGNAL(clicked(QModelIndex)), + SLOT(_q_emitItemClicked(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), + SLOT(_q_emitItemDoubleClicked(QModelIndex))); + connect(this, SIGNAL(activated(QModelIndex)), + SLOT(_q_emitItemActivated(QModelIndex))); + connect(this, SIGNAL(entered(QModelIndex)), + SLOT(_q_emitItemEntered(QModelIndex))); + connect(this, SIGNAL(expanded(QModelIndex)), + SLOT(_q_emitItemExpanded(QModelIndex))); + connect(this, SIGNAL(collapsed(QModelIndex)), + SLOT(_q_emitItemCollapsed(QModelIndex))); + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex))); + connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_emitItemChanged(QModelIndex))); + connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + connect(model(), SIGNAL(columnsRemoved(QModelIndex,int,int)), + this, SLOT(_q_sort())); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(_q_selectionChanged(QItemSelection,QItemSelection))); + header()->setClickable(false); +} + +/*! + Destroys the tree widget and all its items. +*/ + +QTreeWidget::~QTreeWidget() +{ +} + +/* + Retuns the number of header columns in the view. + + \sa sortColumn(), currentColumn(), topLevelItemCount() +*/ + +int QTreeWidget::columnCount() const +{ + Q_D(const QTreeWidget); + return d->model->columnCount(); +} + +/* + Sets the number of header \a columns in the tree widget. +*/ + +void QTreeWidget::setColumnCount(int columns) +{ + Q_D(QTreeWidget); + if (columns < 0) + return; + d->treeModel()->setColumnCount(columns); +} + +/*! + \since 4.2 + + Returns the tree widget's invisible root item. + + The invisible root item provides access to the tree widget's top-level items + through the QTreeWidgetItem API, making it possible to write functions that + can treat top-level items and their children in a uniform way; for example, + recursive functions. +*/ + +QTreeWidgetItem *QTreeWidget::invisibleRootItem() const +{ + Q_D(const QTreeWidget); + return d->treeModel()->rootItem; +} + +/*! + Returns the top level item at the given \a index, or 0 if the item does + not exist. + + \sa topLevelItemCount(), insertTopLevelItem() +*/ + +QTreeWidgetItem *QTreeWidget::topLevelItem(int index) const +{ + Q_D(const QTreeWidget); + return d->treeModel()->rootItem->child(index); +} + +/*! + \property QTreeWidget::topLevelItemCount + \brief the number of top-level items + + By default, this property has a value of 0. + + \sa columnCount(), currentItem() +*/ + +int QTreeWidget::topLevelItemCount() const +{ + Q_D(const QTreeWidget); + return d->treeModel()->rootItem->childCount(); +} + +/*! + Inserts the \a item at \a index in the top level in the view. + + If the item has already been inserted somewhere else it wont be inserted. + + \sa addTopLevelItem(), columnCount() +*/ + +void QTreeWidget::insertTopLevelItem(int index, QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + d->treeModel()->rootItem->insertChild(index, item); +} + +/*! + \since 4.1 + + Appends the \a item as a top-level item in the widget. + + \sa insertTopLevelItem() +*/ +void QTreeWidget::addTopLevelItem(QTreeWidgetItem *item) +{ + insertTopLevelItem(topLevelItemCount(), item); +} + +/*! + Removes the top-level item at the given \a index in the tree and + returns it, otherwise returns 0; + + \sa insertTopLevelItem(), topLevelItem(), topLevelItemCount() +*/ + +QTreeWidgetItem *QTreeWidget::takeTopLevelItem(int index) +{ + Q_D(QTreeWidget); + return d->treeModel()->rootItem->takeChild(index); +} + +/*! + \internal +*/ +int QTreeWidget::indexOfTopLevelItem(QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + d->treeModel()->executePendingSort(); + return d->treeModel()->rootItem->children.indexOf(item); +} + +/*! + Returns the index of the given top-level \a item, or -1 if the item + cannot be found. + + \sa sortItems(), topLevelItemCount() + */ +int QTreeWidget::indexOfTopLevelItem(QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + d->treeModel()->executePendingSort(); + return d->treeModel()->rootItem->children.indexOf(item); +} + +/*! + \since 4.1 + + Inserts the list of \a items at \a index in the top level in the view. + + Items that have already been inserted somewhere else wont be inserted. + + \sa addTopLevelItems() +*/ +void QTreeWidget::insertTopLevelItems(int index, const QList &items) +{ + Q_D(QTreeWidget); + d->treeModel()->rootItem->insertChildren(index, items); +} + +/*! + Appends the list of \a items as a top-level items in the widget. + + \sa insertTopLevelItems() +*/ +void QTreeWidget::addTopLevelItems(const QList &items) +{ + insertTopLevelItems(topLevelItemCount(), items); +} + +/*! + Returns the item used for the tree widget's header. + + \sa setHeaderItem() +*/ + +QTreeWidgetItem *QTreeWidget::headerItem() const +{ + Q_D(const QTreeWidget); + return d->treeModel()->headerItem; +} + +/*! + Sets the header \a item for the tree widget. The label for each column in + the header is supplied by the corresponding label in the item. + + The tree widget takes ownership of the item. + + \sa headerItem(), setHeaderLabels() +*/ + +void QTreeWidget::setHeaderItem(QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + if (!item) + return; + item->view = this; + + int oldCount = columnCount(); + if (oldCount < item->columnCount()) + d->treeModel()->beginInsertColumns(QModelIndex(), oldCount, item->columnCount()); + else + d->treeModel()->beginRemoveColumns(QModelIndex(), item->columnCount(), oldCount); + delete d->treeModel()->headerItem; + d->treeModel()->headerItem = item; + if (oldCount < item->columnCount()) + d->treeModel()->endInsertColumns(); + else + d->treeModel()->endRemoveColumns(); + d->treeModel()->headerDataChanged(Qt::Horizontal, 0, oldCount); +} + + +/*! + Adds a column in the header for each item in the \a labels list, and sets + the label for each column. + + Note that setHeaderLabels() won't remove existing columns. + + \sa setHeaderItem(), setHeaderLabel() +*/ +void QTreeWidget::setHeaderLabels(const QStringList &labels) +{ + Q_D(QTreeWidget); + if (columnCount() < labels.count()) + setColumnCount(labels.count()); + QTreeWidgetItem *item = d->treeModel()->headerItem; + for (int i = 0; i < labels.count(); ++i) + item->setText(i, labels.at(i)); +} + +/*! + \fn void QTreeWidget::setHeaderLabel(const QString &label) + \since 4.2 + + Same as setHeaderLabels(QStringList(\a label)). +*/ + +/*! + Returns the current item in the tree widget. + + \sa setCurrentItem(), currentItemChanged() +*/ +QTreeWidgetItem *QTreeWidget::currentItem() const +{ + Q_D(const QTreeWidget); + return d->item(currentIndex()); +} + +/*! + \since 4.1 + Returns the current column in the tree widget. + + \sa setCurrentItem(), columnCount() +*/ +int QTreeWidget::currentColumn() const +{ + return currentIndex().column(); +} + +/*! + Sets the current \a item in the tree widget. + + Unless the selection mode is \l{QAbstractItemView::}{NoSelection}, + the item is also be selected. + + \sa currentItem(), currentItemChanged() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item) +{ + setCurrentItem(item, 0); +} + +/*! + \since 4.1 + Sets the current \a item in the tree widget and the current column to \a column. + + \sa currentItem() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + setCurrentIndex(d->index(item, column)); +} + +/*! + \since 4.4 + Sets the current \a item in the tree widget and the current column to \a column, + using the given \a command. + + \sa currentItem() +*/ +void QTreeWidget::setCurrentItem(QTreeWidgetItem *item, int column, + QItemSelectionModel::SelectionFlags command) +{ + Q_D(QTreeWidget); + d->selectionModel->setCurrentIndex(d->index(item, column), command); +} + + +/*! + Returns a pointer to the item at the coordinates \a p. The coordinates + are relative to the tree widget's \l{QAbstractScrollArea::}{viewport()}. + + \sa visualItemRect() +*/ +QTreeWidgetItem *QTreeWidget::itemAt(const QPoint &p) const +{ + Q_D(const QTreeWidget); + return d->item(indexAt(p)); +} + +/*! + \fn QTreeWidgetItem *QTreeWidget::itemAt(int x, int y) const + \overload + + Returns a pointer to the item at the coordinates (\a x, \a y). The coordinates + are relative to the tree widget's \l{QAbstractScrollArea::}{viewport()}. +*/ + +/*! + Returns the rectangle on the viewport occupied by the item at \a item. + + \sa itemAt() +*/ +QRect QTreeWidget::visualItemRect(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + //the visual rect for an item is across all columns. So we need to determine + //what is the first and last column and get their visual index rects + QModelIndex base = d->index(item); + const int firstVisiblesection = header()->logicalIndexAt(- header()->offset()); + const int lastVisibleSection = header()->logicalIndexAt(header()->length() - header()->offset() - 1); + QModelIndex first = base.sibling(base.row(), header()->logicalIndex(firstVisiblesection)); + QModelIndex last = base.sibling(base.row(), header()->logicalIndex(lastVisibleSection)); + return visualRect(first) | visualRect(last); +} + +/*! + \since 4.1 + + Returns the column used to sort the contents of the widget. + + \sa sortItems() +*/ +int QTreeWidget::sortColumn() const +{ + Q_D(const QTreeWidget); + return (d->explicitSortColumn != -1 + ? d->explicitSortColumn + : header()->sortIndicatorSection()); +} + +/*! + Sorts the items in the widget in the specified \a order by the values in + the given \a column. + + \sa sortColumn() +*/ + +void QTreeWidget::sortItems(int column, Qt::SortOrder order) +{ + Q_D(QTreeWidget); + header()->setSortIndicator(column, order); + d->model->sort(column, order); +} + +/*! + \internal + + ### Qt 5: remove +*/ +void QTreeWidget::setSortingEnabled(bool enable) +{ + QTreeView::setSortingEnabled(enable); +} + +/*! + \internal + + ### Qt 5: remove +*/ +bool QTreeWidget::isSortingEnabled() const +{ + return QTreeView::isSortingEnabled(); +} + +/*! + Starts editing the \a item in the given \a column if it is editable. +*/ + +void QTreeWidget::editItem(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + edit(d->index(item, column)); +} + +/*! + Opens a persistent editor for the \a item in the given \a column. + + \sa closePersistentEditor() +*/ + +void QTreeWidget::openPersistentEditor(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + QAbstractItemView::openPersistentEditor(d->index(item, column)); +} + +/*! + Closes the persistent editor for the \a item in the given \a column. + + This function has no effect if no persistent editor is open for this + combination of item and column. + + \sa openPersistentEditor() +*/ + +void QTreeWidget::closePersistentEditor(QTreeWidgetItem *item, int column) +{ + Q_D(QTreeWidget); + QAbstractItemView::closePersistentEditor(d->index(item, column)); +} + +/*! + \since 4.1 + + Returns the widget displayed in the cell specified by \a item and the given \a column. + + \note The tree takes ownership of the widget. + +*/ +QWidget *QTreeWidget::itemWidget(QTreeWidgetItem *item, int column) const +{ + Q_D(const QTreeWidget); + return QAbstractItemView::indexWidget(d->index(item, column)); +} + +/*! + \since 4.1 + + Sets the given \a widget to be displayed in the cell specified by the given + \a item and \a column. + + The given \a widget's \l {QWidget::}{autoFillBackground} property must be + set to true, otherwise the widget's background will be transparent, showing + both the model data and the tree widget item. + + This function should only be used to display static content in the place of + a tree widget item. If you want to display custom dynamic content or + implement a custom editor widget, use QTreeView and subclass QItemDelegate + instead. + + This function cannot be called before the item hierarchy has been set up, + i.e., the QTreeWidgetItem that will hold \a widget must have been added to + the view before \a widget is set. + + \note The tree takes ownership of the widget. + + \sa {Delegate Classes} +*/ +void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget) +{ + Q_D(QTreeWidget); + QAbstractItemView::setIndexWidget(d->index(item, column), widget); +} + +/*! + Returns true if the \a item is selected; otherwise returns false. + + \sa itemSelectionChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isSelected()} instead. +*/ +bool QTreeWidget::isItemSelected(const QTreeWidgetItem *item) const +{ + if (!item) + return false; + return item->d->selected; +} + +/*! + If \a select is true, the given \a item is selected; otherwise it is + deselected. + + \sa itemSelectionChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setSelected()} instead. +*/ +void QTreeWidget::setItemSelected(const QTreeWidgetItem *item, bool select) +{ + Q_D(QTreeWidget); + + if (!item) + return; + + selectionModel()->select(d->index(item), (select ? QItemSelectionModel::Select + : QItemSelectionModel::Deselect) + |QItemSelectionModel::Rows); + item->d->selected = select; +} + +/*! + Returns a list of all selected non-hidden items. + + \sa itemSelectionChanged() +*/ +QList QTreeWidget::selectedItems() const +{ + Q_D(const QTreeWidget); + QModelIndexList indexes = selectionModel()->selectedIndexes(); + QList items; + items.reserve(indexes.count()); + QSet seen; + seen.reserve(indexes.count()); + for (int i = 0; i < indexes.count(); ++i) { + QTreeWidgetItem *item = d->item(indexes.at(i)); + if (isItemHidden(item) || seen.contains(item)) + continue; + seen.insert(item); + items.append(item); + } + return items; +} + +/*! + Returns a list of items that match the given \a text, using the given \a flags, in the given \a column. +*/ +QList QTreeWidget::findItems(const QString &text, Qt::MatchFlags flags, int column) const +{ + Q_D(const QTreeWidget); + QModelIndexList indexes = d->model->match(model()->index(0, column, QModelIndex()), + Qt::DisplayRole, text, -1, flags); + QList items; + for (int i = 0; i < indexes.size(); ++i) + items.append(d->item(indexes.at(i))); + return items; +} + +/*! + Returns true if the \a item is explicitly hidden, otherwise returns false. + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isHidden()} instead. +*/ +bool QTreeWidget::isItemHidden(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->treeModel()->headerItem) + return header()->isHidden(); + if (d->hiddenIndexes.isEmpty()) + return false; + QTreeModel::SkipSorting skipSorting(d->treeModel()); + return d->isRowHidden(d->index(item)); +} + +/*! + Hides the given \a item if \a hide is true; otherwise shows the item. + + \sa itemChanged() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setHidden()} instead. +*/ +void QTreeWidget::setItemHidden(const QTreeWidgetItem *item, bool hide) +{ + Q_D(QTreeWidget); + if (item == d->treeModel()->headerItem) { + header()->setHidden(hide); + } else { + const QModelIndex index = d->index(item); + setRowHidden(index.row(), index.parent(), hide); + } +} + +/*! + Returns true if the given \a item is open; otherwise returns false. + + \sa itemExpanded() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::isExpanded()} instead. +*/ +bool QTreeWidget::isItemExpanded(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->treeModel()); + return isExpanded(d->index(item)); +} + +/*! + Sets the item referred to by \a item to either closed or opened, + depending on the value of \a expand. + + \sa expandItem(), collapseItem(), itemExpanded() + + \obsolete + + This function is deprecated. Use \l{QTreeWidgetItem::setExpanded()} instead. +*/ +void QTreeWidget::setItemExpanded(const QTreeWidgetItem *item, bool expand) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->treeModel()); + setExpanded(d->index(item), expand); +} + +/*! + \since 4.3 + + Returns true if the given \a item is set to show only one section over all columns; + otherwise returns false. + + \sa setFirstItemColumnSpanned() +*/ +bool QTreeWidget::isFirstItemColumnSpanned(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->treeModel()->headerItem) + return false; // We can't set the header items to spanning + const QModelIndex index = d->index(item); + return isFirstColumnSpanned(index.row(), index.parent()); +} + +/*! + \since 4.3 + + Sets the given \a item to only show one section for all columns if \a span is true; + otherwise the item will show one section per column. + + \sa isFirstItemColumnSpanned() +*/ +void QTreeWidget::setFirstItemColumnSpanned(const QTreeWidgetItem *item, bool span) +{ + Q_D(QTreeWidget); + if (item == d->treeModel()->headerItem) + return; // We can't set header items to spanning + const QModelIndex index = d->index(item); + setFirstColumnSpanned(index.row(), index.parent(), span); +} + +/*! + \since 4.3 + + Returns the item above the given \a item. +*/ +QTreeWidgetItem *QTreeWidget::itemAbove(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->treeModel()->headerItem) + return 0; + const QModelIndex index = d->index(item); + const QModelIndex above = indexAbove(index); + return d->item(above); +} + +/*! + \since 4.3 + + Returns the item visually below the given \a item. +*/ +QTreeWidgetItem *QTreeWidget::itemBelow(const QTreeWidgetItem *item) const +{ + Q_D(const QTreeWidget); + if (item == d->treeModel()->headerItem) + return 0; + const QModelIndex index = d->index(item); + const QModelIndex below = indexBelow(index); + return d->item(below); +} + +/*! + \reimp + */ +void QTreeWidget::setSelectionModel(QItemSelectionModel *selectionModel) +{ + Q_D(QTreeWidget); + QTreeView::setSelectionModel(selectionModel); + QItemSelection newSelection = selectionModel->selection(); + if (!newSelection.isEmpty()) + d->_q_selectionChanged(newSelection, QItemSelection()); +} + +/*! + Ensures that the \a item is visible, scrolling the view if necessary using + the specified \a hint. + + \sa currentItem(), itemAt(), topLevelItem() +*/ +void QTreeWidget::scrollToItem(const QTreeWidgetItem *item, QAbstractItemView::ScrollHint hint) +{ + Q_D(QTreeWidget); + QTreeView::scrollTo(d->index(item), hint); +} + +/*! + Expands the \a item. This causes the tree containing the item's children + to be expanded. + + \sa collapseItem(), currentItem(), itemAt(), topLevelItem(), itemExpanded() +*/ +void QTreeWidget::expandItem(const QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->treeModel()); + expand(d->index(item)); +} + +/*! + Closes the \a item. This causes the tree containing the item's children + to be collapsed. + + \sa expandItem(), currentItem(), itemAt(), topLevelItem() +*/ +void QTreeWidget::collapseItem(const QTreeWidgetItem *item) +{ + Q_D(QTreeWidget); + QTreeModel::SkipSorting skipSorting(d->treeModel()); + collapse(d->index(item)); +} + +/*! + Clears the tree widget by removing all of its items and selections. + + \bold{Note:} Since each item is removed from the tree widget before being + deleted, the return value of QTreeWidgetItem::treeWidget() will be invalid + when called from an item's destructor. + + \sa takeTopLevelItem(), topLevelItemCount(), columnCount() +*/ +void QTreeWidget::clear() +{ + Q_D(QTreeWidget); + selectionModel()->clear(); + d->treeModel()->clear(); +} + +/*! + Returns a list of MIME types that can be used to describe a list of + treewidget items. + + \sa mimeData() +*/ +QStringList QTreeWidget::mimeTypes() const +{ + return model()->QAbstractItemModel::mimeTypes(); +} + +/*! + Returns an object that contains a serialized description of the specified + \a items. The format used to describe the items is obtained from the + mimeTypes() function. + + If the list of items is empty, 0 is returned rather than a serialized + empty list. +*/ +QMimeData *QTreeWidget::mimeData(const QList items) const +{ + Q_D(const QTreeWidget); + if (d->treeModel()->cachedIndexes.isEmpty()) { + QList indexes; + for (int i = 0; i < items.count(); ++i) { + QTreeWidgetItem *item = items.at(i); + for (int c = 0; c < item->values.count(); ++c) { + indexes << indexFromItem(item, c); + } + } + return d->model->QAbstractItemModel::mimeData(indexes); + } + return d->treeModel()->internalMimeData(); +} + +/*! + Handles the \a data supplied by a drag and drop operation that ended with + the given \a action in the \a index in the given \a parent item. + + The default implementation returns true if the drop was + successfully handled by decoding the mime data and inserting it + into the model; otherwise it returns false. + + \sa supportedDropActions() +*/ +bool QTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, + const QMimeData *data, Qt::DropAction action) +{ + QModelIndex idx; + if (parent) idx = indexFromItem(parent); + return model()->QAbstractItemModel::dropMimeData(data, action , index, 0, idx); +} + +/*! + Returns the drop actions supported by this view. + + \sa Qt::DropActions +*/ +Qt::DropActions QTreeWidget::supportedDropActions() const +{ + return model()->QAbstractItemModel::supportedDropActions() | Qt::MoveAction; +} + +/*! + \obsolete + Returns an empty list + + \sa mimeData() +*/ +QList QTreeWidget::items(const QMimeData *data) const +{ + Q_UNUSED(data); + return QList(); +} + +/*! + Returns the QModelIndex assocated with the given \a item in the given \a column. + + \sa itemFromIndex(), topLevelItem() +*/ +QModelIndex QTreeWidget::indexFromItem(QTreeWidgetItem *item, int column) const +{ + Q_D(const QTreeWidget); + return d->index(item, column); +} + +/*! + Returns a pointer to the QTreeWidgetItem assocated with the given \a index. + + \sa indexFromItem() +*/ +QTreeWidgetItem *QTreeWidget::itemFromIndex(const QModelIndex &index) const +{ + Q_D(const QTreeWidget); + return d->item(index); +} + +#ifndef QT_NO_DRAGANDDROP +/*! \reimp */ +void QTreeWidget::dropEvent(QDropEvent *event) { + Q_D(QTreeWidget); + if (event->source() == this && (event->dropAction() == Qt::MoveAction || + dragDropMode() == QAbstractItemView::InternalMove)) { + QModelIndex topIndex; + int col = -1; + int row = -1; + if (d->dropOn(event, &row, &col, &topIndex)) { + QList idxs = selectedIndexes(); + QList indexes; + for (int i = 0; i < idxs.count(); i++) + indexes.append(idxs.at(i)); + + if (indexes.contains(topIndex)) + return; + + // When removing items the drop location could shift + QPersistentModelIndex dropRow = model()->index(row, col, topIndex); + + // Remove the items + QList taken; + for (int i = indexes.count() - 1; i >= 0; --i) { + QTreeWidgetItem *parent = itemFromIndex(indexes.at(i)); + if (!parent || !parent->parent()) { + taken.append(takeTopLevelItem(indexes.at(i).row())); + } else { + taken.append(parent->parent()->takeChild(indexes.at(i).row())); + } + } + + // insert them back in at their new positions + for (int i = 0; i < indexes.count(); ++i) { + // Either at a specific point or appended + if (row == -1) { + if (topIndex.isValid()) { + QTreeWidgetItem *parent = itemFromIndex(topIndex); + parent->insertChild(parent->childCount(), taken.takeFirst()); + } else { + insertTopLevelItem(topLevelItemCount(), taken.takeFirst()); + } + } else { + int r = dropRow.row() >= 0 ? dropRow.row() : row; + if (topIndex.isValid()) { + QTreeWidgetItem *parent = itemFromIndex(topIndex); + parent->insertChild(qMin(r, parent->childCount()), taken.takeFirst()); + } else { + insertTopLevelItem(qMin(r, topLevelItemCount()), taken.takeFirst()); + } + } + } + + event->accept(); + // Don't want QAbstractItemView to delete it because it was "moved" we already did it + event->setDropAction(Qt::CopyAction); + } + } + + QTreeView::dropEvent(event); +} +#endif + +/*! + \reimp +*/ + +void QTreeWidget::setModel(QAbstractItemModel * /*model*/) +{ + Q_ASSERT(!"QTreeWidget::setModel() - Changing the model of the QTreeWidget is not allowed."); +} + +/*! + \reimp +*/ +bool QTreeWidget::event(QEvent *e) +{ + Q_D(QTreeWidget); + if (e->type() == QEvent::Polish) + d->treeModel()->executePendingSort(); + return QTreeView::event(e); +} + +QT_END_NAMESPACE + +#include "moc_qtreewidget.cpp" + +#endif // QT_NO_TREEWIDGET diff --git a/src/gui/itemviews/qtreewidget.h b/src/gui/itemviews/qtreewidget.h new file mode 100644 index 0000000000..59532dd594 --- /dev/null +++ b/src/gui/itemviews/qtreewidget.h @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGET_H +#define QTREEWIDGET_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEWIDGET + +class QTreeWidget; +class QTreeModel; +class QWidgetItemData; +class QTreeWidgetItemPrivate; + +class Q_GUI_EXPORT QTreeWidgetItem +{ + friend class QTreeModel; + friend class QTreeWidget; + friend class QTreeWidgetPrivate; + friend class QTreeWidgetItemIterator; + friend class QTreeWidgetItemPrivate; +public: + enum ItemType { Type = 0, UserType = 1000 }; + explicit QTreeWidgetItem(int type = Type); + QTreeWidgetItem(const QStringList &strings, int type = Type); + explicit QTreeWidgetItem(QTreeWidget *view, int type = Type); + QTreeWidgetItem(QTreeWidget *view, const QStringList &strings, int type = Type); + QTreeWidgetItem(QTreeWidget *view, QTreeWidgetItem *after, int type = Type); + explicit QTreeWidgetItem(QTreeWidgetItem *parent, int type = Type); + QTreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings, int type = Type); + QTreeWidgetItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, int type = Type); + QTreeWidgetItem(const QTreeWidgetItem &other); + virtual ~QTreeWidgetItem(); + + virtual QTreeWidgetItem *clone() const; + + inline QTreeWidget *treeWidget() const { return view; } + + inline void setSelected(bool select); + inline bool isSelected() const; + + inline void setHidden(bool hide); + inline bool isHidden() const; + + inline void setExpanded(bool expand); + inline bool isExpanded() const; + + inline void setFirstColumnSpanned(bool span); + inline bool isFirstColumnSpanned() const; + + inline void setDisabled(bool disabled); + inline bool isDisabled() const; + + enum ChildIndicatorPolicy { ShowIndicator, DontShowIndicator, DontShowIndicatorWhenChildless }; + void setChildIndicatorPolicy(QTreeWidgetItem::ChildIndicatorPolicy policy); + QTreeWidgetItem::ChildIndicatorPolicy childIndicatorPolicy() const; + + Qt::ItemFlags flags() const; + void setFlags(Qt::ItemFlags flags); + + inline QString text(int column) const + { return data(column, Qt::DisplayRole).toString(); } + inline void setText(int column, const QString &text); + + inline QIcon icon(int column) const + { return qvariant_cast(data(column, Qt::DecorationRole)); } + inline void setIcon(int column, const QIcon &icon); + + inline QString statusTip(int column) const + { return data(column, Qt::StatusTipRole).toString(); } + inline void setStatusTip(int column, const QString &statusTip); + +#ifndef QT_NO_TOOLTIP + inline QString toolTip(int column) const + { return data(column, Qt::ToolTipRole).toString(); } + inline void setToolTip(int column, const QString &toolTip); +#endif + +#ifndef QT_NO_WHATSTHIS + inline QString whatsThis(int column) const + { return data(column, Qt::WhatsThisRole).toString(); } + inline void setWhatsThis(int column, const QString &whatsThis); +#endif + + inline QFont font(int column) const + { return qvariant_cast(data(column, Qt::FontRole)); } + inline void setFont(int column, const QFont &font); + + inline int textAlignment(int column) const + { return data(column, Qt::TextAlignmentRole).toInt(); } + inline void setTextAlignment(int column, int alignment) + { setData(column, Qt::TextAlignmentRole, alignment); } + + inline QColor backgroundColor(int column) const + { return qvariant_cast(data(column, Qt::BackgroundColorRole)); } + inline void setBackgroundColor(int column, const QColor &color) + { setData(column, Qt::BackgroundColorRole, color); } + + inline QBrush background(int column) const + { return qvariant_cast(data(column, Qt::BackgroundRole)); } + inline void setBackground(int column, const QBrush &brush) + { setData(column, Qt::BackgroundRole, brush); } + + inline QColor textColor(int column) const + { return qvariant_cast(data(column, Qt::TextColorRole)); } + inline void setTextColor(int column, const QColor &color) + { setData(column, Qt::TextColorRole, color); } + + inline QBrush foreground(int column) const + { return qvariant_cast(data(column, Qt::ForegroundRole)); } + inline void setForeground(int column, const QBrush &brush) + { setData(column, Qt::ForegroundRole, brush); } + + inline Qt::CheckState checkState(int column) const + { return static_cast(data(column, Qt::CheckStateRole).toInt()); } + inline void setCheckState(int column, Qt::CheckState state) + { setData(column, Qt::CheckStateRole, state); } + + inline QSize sizeHint(int column) const + { return qvariant_cast(data(column, Qt::SizeHintRole)); } + inline void setSizeHint(int column, const QSize &size) + { setData(column, Qt::SizeHintRole, size); } + + virtual QVariant data(int column, int role) const; + virtual void setData(int column, int role, const QVariant &value); + + virtual bool operator<(const QTreeWidgetItem &other) const; + +#ifndef QT_NO_DATASTREAM + virtual void read(QDataStream &in); + virtual void write(QDataStream &out) const; +#endif + QTreeWidgetItem &operator=(const QTreeWidgetItem &other); + + inline QTreeWidgetItem *parent() const { return par; } + inline QTreeWidgetItem *child(int index) const { + if (index < 0 || index >= children.size()) + return 0; + executePendingSort(); + return children.at(index); + } + inline int childCount() const { return children.count(); } + inline int columnCount() const { return values.count(); } + inline int indexOfChild(QTreeWidgetItem *child) const; + + void addChild(QTreeWidgetItem *child); + void insertChild(int index, QTreeWidgetItem *child); + void removeChild(QTreeWidgetItem *child); + QTreeWidgetItem *takeChild(int index); + + void addChildren(const QList &children); + void insertChildren(int index, const QList &children); + QList takeChildren(); + + inline int type() const { return rtti; } + inline void sortChildren(int column, Qt::SortOrder order) + { sortChildren(column, order, false); } + +protected: + void emitDataChanged(); + +private: + void sortChildren(int column, Qt::SortOrder order, bool climb); + QVariant childrenCheckState(int column) const; + void itemChanged(); + void executePendingSort() const; + + int rtti; + // One item has a vector of column entries. Each column has a vector of (role, value) pairs. + QVector< QVector > values; + QTreeWidget *view; + QTreeWidgetItemPrivate *d; + QTreeWidgetItem *par; + QList children; + Qt::ItemFlags itemFlags; +}; + +inline void QTreeWidgetItem::setText(int column, const QString &atext) +{ setData(column, Qt::DisplayRole, atext); } + +inline void QTreeWidgetItem::setIcon(int column, const QIcon &aicon) +{ setData(column, Qt::DecorationRole, aicon); } + +#ifndef QT_NO_STATUSTIP +inline void QTreeWidgetItem::setStatusTip(int column, const QString &astatusTip) +{ setData(column, Qt::StatusTipRole, astatusTip); } +#endif + +#ifndef QT_NO_TOOLTIP +inline void QTreeWidgetItem::setToolTip(int column, const QString &atoolTip) +{ setData(column, Qt::ToolTipRole, atoolTip); } +#endif + +#ifndef QT_NO_WHATSTHIS +inline void QTreeWidgetItem::setWhatsThis(int column, const QString &awhatsThis) +{ setData(column, Qt::WhatsThisRole, awhatsThis); } +#endif + +inline void QTreeWidgetItem::setFont(int column, const QFont &afont) +{ setData(column, Qt::FontRole, afont); } + +inline int QTreeWidgetItem::indexOfChild(QTreeWidgetItem *achild) const +{ executePendingSort(); return children.indexOf(achild); } + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &out, const QTreeWidgetItem &item); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QTreeWidgetItem &item); +#endif + +class QTreeWidgetPrivate; + +class Q_GUI_EXPORT QTreeWidget : public QTreeView +{ + Q_OBJECT + Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount) + Q_PROPERTY(int topLevelItemCount READ topLevelItemCount) + + friend class QTreeModel; + friend class QTreeWidgetItem; +public: + explicit QTreeWidget(QWidget *parent = 0); + ~QTreeWidget(); + + int columnCount() const; + void setColumnCount(int columns); + + QTreeWidgetItem *invisibleRootItem() const; + QTreeWidgetItem *topLevelItem(int index) const; + int topLevelItemCount() const; + void insertTopLevelItem(int index, QTreeWidgetItem *item); + void addTopLevelItem(QTreeWidgetItem *item); + QTreeWidgetItem *takeTopLevelItem(int index); + int indexOfTopLevelItem(QTreeWidgetItem *item); // ### Qt 5: remove me + int indexOfTopLevelItem(QTreeWidgetItem *item) const; + + void insertTopLevelItems(int index, const QList &items); + void addTopLevelItems(const QList &items); + + QTreeWidgetItem *headerItem() const; + void setHeaderItem(QTreeWidgetItem *item); + void setHeaderLabels(const QStringList &labels); + inline void setHeaderLabel(const QString &label); + + QTreeWidgetItem *currentItem() const; + int currentColumn() const; + void setCurrentItem(QTreeWidgetItem *item); + void setCurrentItem(QTreeWidgetItem *item, int column); + void setCurrentItem(QTreeWidgetItem *item, int column, QItemSelectionModel::SelectionFlags command); + + QTreeWidgetItem *itemAt(const QPoint &p) const; + inline QTreeWidgetItem *itemAt(int x, int y) const; + QRect visualItemRect(const QTreeWidgetItem *item) const; + + int sortColumn() const; + void sortItems(int column, Qt::SortOrder order); + void setSortingEnabled(bool enable); + bool isSortingEnabled() const; + + void editItem(QTreeWidgetItem *item, int column = 0); + void openPersistentEditor(QTreeWidgetItem *item, int column = 0); + void closePersistentEditor(QTreeWidgetItem *item, int column = 0); + + QWidget *itemWidget(QTreeWidgetItem *item, int column) const; + void setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget); + inline void removeItemWidget(QTreeWidgetItem *item, int column); + + bool isItemSelected(const QTreeWidgetItem *item) const; + void setItemSelected(const QTreeWidgetItem *item, bool select); + QList selectedItems() const; + QList findItems(const QString &text, Qt::MatchFlags flags, + int column = 0) const; + + bool isItemHidden(const QTreeWidgetItem *item) const; + void setItemHidden(const QTreeWidgetItem *item, bool hide); + + bool isItemExpanded(const QTreeWidgetItem *item) const; + void setItemExpanded(const QTreeWidgetItem *item, bool expand); + + bool isFirstItemColumnSpanned(const QTreeWidgetItem *item) const; + void setFirstItemColumnSpanned(const QTreeWidgetItem *item, bool span); + + QTreeWidgetItem *itemAbove(const QTreeWidgetItem *item) const; + QTreeWidgetItem *itemBelow(const QTreeWidgetItem *item) const; + + void setSelectionModel(QItemSelectionModel *selectionModel); + +public Q_SLOTS: + void scrollToItem(const QTreeWidgetItem *item, + QAbstractItemView::ScrollHint hint = EnsureVisible); + void expandItem(const QTreeWidgetItem *item); + void collapseItem(const QTreeWidgetItem *item); + void clear(); + +Q_SIGNALS: + void itemPressed(QTreeWidgetItem *item, int column); + void itemClicked(QTreeWidgetItem *item, int column); + void itemDoubleClicked(QTreeWidgetItem *item, int column); + void itemActivated(QTreeWidgetItem *item, int column); + void itemEntered(QTreeWidgetItem *item, int column); + void itemChanged(QTreeWidgetItem *item, int column); + void itemExpanded(QTreeWidgetItem *item); + void itemCollapsed(QTreeWidgetItem *item); + void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); + void itemSelectionChanged(); + +protected: + bool event(QEvent *e); + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QList items) const; + virtual bool dropMimeData(QTreeWidgetItem *parent, int index, + const QMimeData *data, Qt::DropAction action); + virtual Qt::DropActions supportedDropActions() const; + QList items(const QMimeData *data) const; + + QModelIndex indexFromItem(QTreeWidgetItem *item, int column = 0) const; + QTreeWidgetItem *itemFromIndex(const QModelIndex &index) const; + void dropEvent(QDropEvent *event); + +private: + void setModel(QAbstractItemModel *model); + + Q_DECLARE_PRIVATE(QTreeWidget) + Q_DISABLE_COPY(QTreeWidget) + + Q_PRIVATE_SLOT(d_func(), void _q_emitItemPressed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemDoubleClicked(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemActivated(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemEntered(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemChanged(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemExpanded(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitItemCollapsed(const QModelIndex &index)) + Q_PRIVATE_SLOT(d_func(), void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex ¤t)) + Q_PRIVATE_SLOT(d_func(), void _q_sort()) + Q_PRIVATE_SLOT(d_func(), void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)) + Q_PRIVATE_SLOT(d_func(), void _q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)) +}; + +inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column) +{ setItemWidget(item, column, 0); } + +inline QTreeWidgetItem *QTreeWidget::itemAt(int ax, int ay) const +{ return itemAt(QPoint(ax, ay)); } + +inline void QTreeWidget::setHeaderLabel(const QString &alabel) +{ setHeaderLabels(QStringList(alabel)); } + +inline void QTreeWidgetItem::setSelected(bool aselect) +{ if (view) view->setItemSelected(this, aselect); } + +inline bool QTreeWidgetItem::isSelected() const +{ return (view ? view->isItemSelected(this) : false); } + +inline void QTreeWidgetItem::setHidden(bool ahide) +{ if (view) view->setItemHidden(this, ahide); } + +inline bool QTreeWidgetItem::isHidden() const +{ return (view ? view->isItemHidden(this) : false); } + +inline void QTreeWidgetItem::setExpanded(bool aexpand) +{ if (view) view->setItemExpanded(this, aexpand); } + +inline bool QTreeWidgetItem::isExpanded() const +{ return (view ? view->isItemExpanded(this) : false); } + +inline void QTreeWidgetItem::setFirstColumnSpanned(bool aspan) +{ if (view) view->setFirstItemColumnSpanned(this, aspan); } + +inline bool QTreeWidgetItem::isFirstColumnSpanned() const +{ return (view ? view->isFirstItemColumnSpanned(this) : false); } + +inline void QTreeWidgetItem::setDisabled(bool disabled) +{ setFlags(disabled ? (flags() & ~Qt::ItemIsEnabled) : flags() | Qt::ItemIsEnabled); } + +inline bool QTreeWidgetItem::isDisabled() const +{ return !(flags() & Qt::ItemIsEnabled); } + +#endif // QT_NO_TREEWIDGET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTREEWIDGET_H diff --git a/src/gui/itemviews/qtreewidget_p.h b/src/gui/itemviews/qtreewidget_p.h new file mode 100644 index 0000000000..7395e6a5db --- /dev/null +++ b/src/gui/itemviews/qtreewidget_p.h @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGET_P_H +#define QTREEWIDGET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_TREEWIDGET + +QT_BEGIN_NAMESPACE + +class QTreeWidgetItem; +class QTreeWidgetItemIterator; +class QTreeModelPrivate; + +class QTreeModel : public QAbstractItemModel +{ + Q_OBJECT + friend class QTreeWidget; + friend class QTreeWidgetPrivate; + friend class QTreeWidgetItem; + friend class QTreeWidgetItemPrivate; + friend class QTreeWidgetItemIterator; + friend class QTreeWidgetItemIteratorPrivate; + +public: + explicit QTreeModel(int columns = 0, QTreeWidget *parent = 0); + ~QTreeModel(); + + inline QTreeWidget *view() const + { return qobject_cast(QObject::parent()); } + + void clear(); + void setColumnCount(int columns); + + QTreeWidgetItem *item(const QModelIndex &index) const; + void itemChanged(QTreeWidgetItem *item); + + QModelIndex index(const QTreeWidgetItem *item, int column) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + bool hasChildren(const QModelIndex &parent) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + QMap itemData(const QModelIndex &index) const; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void sort(int column, Qt::SortOrder order); + void ensureSorted(int column, Qt::SortOrder order, + int start, int end, const QModelIndex &parent); + static bool itemLessThan(const QPair &left, + const QPair &right); + static bool itemGreaterThan(const QPair &left, + const QPair &right); + static QList::iterator sortedInsertionIterator( + const QList::iterator &begin, + const QList::iterator &end, + Qt::SortOrder order, QTreeWidgetItem *item); + + bool insertRows(int row, int count, const QModelIndex &); + bool insertColumns(int column, int count, const QModelIndex &); + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + // dnd + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + Qt::DropActions supportedDropActions() const; + + QMimeData *internalMimeData() const; + + inline QModelIndex createIndexFromItem(int row, int col, QTreeWidgetItem *item) const + { return createIndex(row, col, item); } + +protected: + QTreeModel(QTreeModelPrivate &, QTreeWidget *parent = 0); + void emitDataChanged(QTreeWidgetItem *item, int column); + void beginInsertItems(QTreeWidgetItem *parent, int row, int count); + void endInsertItems(); + void beginRemoveItems(QTreeWidgetItem *parent, int row, int count); + void endRemoveItems(); + void sortItems(QList *items, int column, Qt::SortOrder order); + void timerEvent(QTimerEvent *); + +private: + QTreeWidgetItem *rootItem; + QTreeWidgetItem *headerItem; + + mutable QModelIndexList cachedIndexes; + QList iterators; + + mutable QBasicTimer sortPendingTimer; + mutable bool skipPendingSort; //while doing internal operation we don't care about sorting + bool inline executePendingSort() const; + + bool isChanging() const; + +private: + Q_DECLARE_PRIVATE(QTreeModel) +public: + struct SkipSorting + { + const QTreeModel * const model; + const bool previous; + SkipSorting(const QTreeModel *m) : model(m), previous(model->skipPendingSort) + { model->skipPendingSort = true; } + ~SkipSorting() { model->skipPendingSort = previous; } + }; + friend struct SkipSorting; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "private/qabstractitemmodel_p.h" +QT_END_INCLUDE_NAMESPACE + +class QTreeModelPrivate : public QAbstractItemModelPrivate +{ + Q_DECLARE_PUBLIC(QTreeModel) +}; + +class QTreeWidgetItemPrivate +{ +public: + QTreeWidgetItemPrivate(QTreeWidgetItem *item) + : q(item), disabled(false), selected(false), rowGuess(-1), policy(QTreeWidgetItem::DontShowIndicatorWhenChildless) {} + void propagateDisabled(QTreeWidgetItem *item); + void sortChildren(int column, Qt::SortOrder order, bool climb); + QTreeWidgetItem *q; + QVariantList display; + uint disabled : 1; + uint selected : 1; + int rowGuess; + QTreeWidgetItem::ChildIndicatorPolicy policy; +}; + + +inline bool QTreeModel::executePendingSort() const +{ + if (!skipPendingSort && sortPendingTimer.isActive() && !isChanging()) { + sortPendingTimer.stop(); + int column = view()->header()->sortIndicatorSection(); + Qt::SortOrder order = view()->header()->sortIndicatorOrder(); + QTreeModel *that = const_cast(this); + that->sort(column, order); + return true; + } + return false; +} + +class QTreeWidgetPrivate : public QTreeViewPrivate +{ + friend class QTreeModel; + Q_DECLARE_PUBLIC(QTreeWidget) +public: + QTreeWidgetPrivate() : QTreeViewPrivate(), explicitSortColumn(-1) {} + inline QTreeModel *treeModel() const { return qobject_cast(model); } + inline QModelIndex index(const QTreeWidgetItem *item, int column = 0) const + { return treeModel()->index(item, column); } + inline QTreeWidgetItem *item(const QModelIndex &index) const + { return treeModel()->item(index); } + void _q_emitItemPressed(const QModelIndex &index); + void _q_emitItemClicked(const QModelIndex &index); + void _q_emitItemDoubleClicked(const QModelIndex &index); + void _q_emitItemActivated(const QModelIndex &index); + void _q_emitItemEntered(const QModelIndex &index); + void _q_emitItemChanged(const QModelIndex &index); + void _q_emitItemExpanded(const QModelIndex &index); + void _q_emitItemCollapsed(const QModelIndex &index); + void _q_emitCurrentItemChanged(const QModelIndex &previous, const QModelIndex &index); + void _q_sort(); + void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void _q_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + + // used by QTreeWidgetItem::sortChildren to make sure the column argument is used + int explicitSortColumn; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET + +#endif // QTREEWIDGET_P_H diff --git a/src/gui/itemviews/qtreewidgetitemiterator.cpp b/src/gui/itemviews/qtreewidgetitemiterator.cpp new file mode 100644 index 0000000000..fa0115ed27 --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator.cpp @@ -0,0 +1,458 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qtreewidget.h" +#include "qtreewidget_p.h" +#include "qwidgetitemdata_p.h" + +#ifndef QT_NO_TREEWIDGET + +QT_BEGIN_NAMESPACE + +/*! + \class QTreeWidgetItemIterator + \ingroup model-view + \brief The QTreeWidgetItemIterator class provides a way to iterate over the + items in a QTreeWidget instance. + + The iterator will walk the items in a pre-order traversal order, thus visiting the + parent node \e before it continues to the child nodes. + + For example, the following code examples each item in a tree, checking the + text in the first column against a user-specified search string: + + \snippet doc/src/snippets/qtreewidgetitemiterator-using/mainwindow.cpp 0 + + It is also possible to filter out certain types of node by passing certain + \l{IteratorFlag}{flags} to the constructor of QTreeWidgetItemIterator. + + \sa QTreeWidget, {Model/View Programming}, QTreeWidgetItem +*/ + +/*! + Constructs an iterator for the same QTreeWidget as \a it. The + current iterator item is set to point on the current item of \a it. +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(const QTreeWidgetItemIterator &it) + : d_ptr(new QTreeWidgetItemIteratorPrivate(*(it.d_ptr))), + current(it.current), flags(it.flags) +{ + Q_D(QTreeWidgetItemIterator); + Q_ASSERT(d->m_model); + d->m_model->iterators.append(this); +} + +/*! + Constructs an iterator for the given \a widget that uses the specified \a flags + to determine which items are found during iteration. + The iterator is set to point to the first top-level item contained in the widget, + or the next matching item if the top-level item doesn't match the flags. + + \sa QTreeWidgetItemIterator::IteratorFlag +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(QTreeWidget *widget, IteratorFlags flags) +: current(0), flags(flags) +{ + Q_ASSERT(widget); + QTreeModel *model = qobject_cast(widget->model()); + Q_ASSERT(model); + d_ptr.reset(new QTreeWidgetItemIteratorPrivate(this, model)); + model->iterators.append(this); + if (!model->rootItem->children.isEmpty()) current = model->rootItem->children.first(); + if (current && !matchesFlags(current)) + ++(*this); +} + +/*! + Constructs an iterator for the given \a item that uses the specified \a flags + to determine which items are found during iteration. + The iterator is set to point to \a item, or the next matching item if \a item + doesn't match the flags. + + \sa QTreeWidgetItemIterator::IteratorFlag +*/ + +QTreeWidgetItemIterator::QTreeWidgetItemIterator(QTreeWidgetItem *item, IteratorFlags flags) + : d_ptr(new QTreeWidgetItemIteratorPrivate( + this, qobject_cast(item->view->model()))), + current(item), flags(flags) +{ + Q_D(QTreeWidgetItemIterator); + Q_ASSERT(item); + QTreeModel *model = qobject_cast(item->view->model()); + Q_ASSERT(model); + model->iterators.append(this); + + // Initialize m_currentIndex and m_parentIndex as it would be if we had traversed from + // the beginning. + QTreeWidgetItem *parent = item; + parent = parent->parent(); + QList children = parent ? parent->children : d->m_model->rootItem->children; + d->m_currentIndex = children.indexOf(item); + + while (parent) { + QTreeWidgetItem *itm = parent; + parent = parent->parent(); + QList children = parent ? parent->children : d->m_model->rootItem->children; + int index = children.indexOf(itm); + d->m_parentIndex.prepend(index); + } + + if (current && !matchesFlags(current)) + ++(*this); +} + +/*! + Destroys the iterator. +*/ + +QTreeWidgetItemIterator::~QTreeWidgetItemIterator() +{ + d_func()->m_model->iterators.removeAll(this); +} + +/*! + Assignment. Makes a copy of \a it and returns a reference to its + iterator. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator=(const QTreeWidgetItemIterator &it) +{ + Q_D(QTreeWidgetItemIterator); + if (d_func()->m_model != it.d_func()->m_model) { + d_func()->m_model->iterators.removeAll(this); + it.d_func()->m_model->iterators.append(this); + } + current = it.current; + flags = it.flags; + d->operator=(*it.d_func()); + return *this; +} + +/*! + The prefix ++ operator (++it) advances the iterator to the next matching item + and returns a reference to the resulting iterator. + Sets the current pointer to 0 if the current item is the last matching item. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator++() +{ + if (current) + do { + current = d_func()->next(current); + } while (current && !matchesFlags(current)); + return *this; +} + +/*! + The prefix -- operator (--it) advances the iterator to the previous matching item + and returns a reference to the resulting iterator. + Sets the current pointer to 0 if the current item is the first matching item. +*/ + +QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator--() +{ + if (current) + do { + current = d_func()->previous(current); + } while (current && !matchesFlags(current)); + return *this; +} + +/*! + \internal +*/ +bool QTreeWidgetItemIterator::matchesFlags(const QTreeWidgetItem *item) const +{ + if (!item) + return false; + + if (flags == All) + return true; + + { + Qt::ItemFlags itemFlags = item->flags(); + if ((flags & Selectable) && !(itemFlags & Qt::ItemIsSelectable)) + return false; + if ((flags & NotSelectable) && (itemFlags & Qt::ItemIsSelectable)) + return false; + if ((flags & DragEnabled) && !(itemFlags & Qt::ItemIsDragEnabled)) + return false; + if ((flags & DragDisabled) && (itemFlags & Qt::ItemIsDragEnabled)) + return false; + if ((flags & DropEnabled) && !(itemFlags & Qt::ItemIsDropEnabled)) + return false; + if ((flags & DropDisabled) && (itemFlags & Qt::ItemIsDropEnabled)) + return false; + if ((flags & Enabled) && !(itemFlags & Qt::ItemIsEnabled)) + return false; + if ((flags & Disabled) && (itemFlags & Qt::ItemIsEnabled)) + return false; + if ((flags & Editable) && !(itemFlags & Qt::ItemIsEditable)) + return false; + if ((flags & NotEditable) && (itemFlags & Qt::ItemIsEditable)) + return false; + } + + if (flags & (Checked|NotChecked)) { + // ### We only test the check state for column 0 + Qt::CheckState check = item->checkState(0); + // PartiallyChecked matches as Checked. + if ((flags & Checked) && (check == Qt::Unchecked)) + return false; + if ((flags & NotChecked) && (check != Qt::Unchecked)) + return false; + } + + if ((flags & HasChildren) && !item->childCount()) + return false; + if ((flags & NoChildren) && item->childCount()) + return false; + + if ((flags & Hidden) && !item->isHidden()) + return false; + if ((flags & NotHidden) && item->isHidden()) + return false; + + if ((flags & Selected) && !item->isSelected()) + return false; + if ((flags & Unselected) && item->isSelected()) + return false; + + return true; +} + +/* + * Implementation of QTreeWidgetItemIteratorPrivate + */ +QTreeWidgetItem* QTreeWidgetItemIteratorPrivate::nextSibling(const QTreeWidgetItem* item) const +{ + Q_ASSERT(item); + QTreeWidgetItem *next = 0; + if (QTreeWidgetItem *par = item->parent()) { + int i = par->indexOfChild(const_cast(item)); + next = par->child(i + 1); + } else { + QTreeWidget *tw = item->treeWidget(); + int i = tw->indexOfTopLevelItem(const_cast(item)); + next = tw->topLevelItem(i + 1); + } + return next; +} + +QTreeWidgetItem *QTreeWidgetItemIteratorPrivate::next(const QTreeWidgetItem *current) +{ + if (!current) return 0; + + QTreeWidgetItem *next = 0; + if (current->childCount()) { + // walk the child + m_parentIndex.push(m_currentIndex); + m_currentIndex = 0; + next = current->child(0); + } else { + // walk the sibling + QTreeWidgetItem *parent = current->parent(); + next = parent ? parent->child(m_currentIndex + 1) + : m_model->rootItem->child(m_currentIndex + 1); + while (!next && parent) { + // if we had no sibling walk up the parent and try the sibling of that + parent = parent->parent(); + m_currentIndex = m_parentIndex.pop(); + next = parent ? parent->child(m_currentIndex + 1) + : m_model->rootItem->child(m_currentIndex + 1); + } + if (next) ++(m_currentIndex); + } + return next; +} + +QTreeWidgetItem *QTreeWidgetItemIteratorPrivate::previous(const QTreeWidgetItem *current) +{ + if (!current) return 0; + + QTreeWidgetItem *prev = 0; + // walk the previous sibling + QTreeWidgetItem *parent = current->parent(); + prev = parent ? parent->child(m_currentIndex - 1) + : m_model->rootItem->child(m_currentIndex - 1); + if (prev) { + // Yes, we had a previous sibling but we need go down to the last leafnode. + --m_currentIndex; + while (prev && prev->childCount()) { + m_parentIndex.push(m_currentIndex); + m_currentIndex = prev->childCount() - 1; + prev = prev->child(m_currentIndex); + } + } else if (parent) { + m_currentIndex = m_parentIndex.pop(); + prev = parent; + } + return prev; +} + +void QTreeWidgetItemIteratorPrivate::ensureValidIterator(const QTreeWidgetItem *itemToBeRemoved) +{ + Q_Q(QTreeWidgetItemIterator); + Q_ASSERT(itemToBeRemoved); + + if (!q->current) return; + QTreeWidgetItem *nextItem = q->current; + + // Do not walk to the ancestor to find the other item if they have the same parent. + if (nextItem->parent() != itemToBeRemoved->parent()) { + while (nextItem->parent() && nextItem != itemToBeRemoved) { + nextItem = nextItem->parent(); + } + } + // If the item to be removed is an ancestor of the current iterator item, + // we need to adjust the iterator. + if (nextItem == itemToBeRemoved) { + QTreeWidgetItem *parent = nextItem; + nextItem = 0; + while (parent && !nextItem) { + nextItem = nextSibling(parent); + parent = parent->parent(); + } + if (nextItem) { + // Ooooh... Set the iterator to the next valid item + *q = QTreeWidgetItemIterator(nextItem, q->flags); + if (!(q->matchesFlags(nextItem))) ++(*q); + } else { + // set it to null. + q->current = 0; + m_parentIndex.clear(); + return; + } + } + if (nextItem->parent() == itemToBeRemoved->parent()) { + // They have the same parent, i.e. we have to adjust the m_currentIndex member of the iterator + // if the deleted item is to the left of the nextItem. + + QTreeWidgetItem *par = itemToBeRemoved->parent(); // We know they both have the same parent. + QTreeWidget *tw = itemToBeRemoved->treeWidget(); // ..and widget + int indexOfItemToBeRemoved = par ? par->indexOfChild(const_cast(itemToBeRemoved)) + : tw->indexOfTopLevelItem(const_cast(itemToBeRemoved)); + int indexOfNextItem = par ? par->indexOfChild(nextItem) : tw->indexOfTopLevelItem(nextItem); + + if (indexOfItemToBeRemoved <= indexOfNextItem) { + // A sibling to the left of us was deleted, adjust the m_currentIndex member of the iterator. + // Note that the m_currentIndex will be wrong until the item is actually removed! + m_currentIndex--; + } + } +} + +/*! + \fn const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator++(int) + + The postfix ++ operator (it++) advances the iterator to the next matching item + and returns an iterator to the previously current item. +*/ + +/*! + \fn QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator+=(int n) + + Makes the iterator go forward by \a n matching items. (If n is negative, the + iterator goes backward.) + + If the current item is beyond the last item, the current item pointer is + set to 0. Returns the resulting iterator. +*/ + +/*! + \fn const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator--(int) + + The postfix -- operator (it--) makes the preceding matching item current and returns an iterator to the previously current item. +*/ + +/*! + \fn QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator-=(int n) + + Makes the iterator go backward by \a n matching items. (If n is negative, the + iterator goes forward.) + + If the current item is ahead of the last item, the current item pointer is + set to 0. Returns the resulting iterator. +*/ + +/*! + \fn QTreeWidgetItem *QTreeWidgetItemIterator::operator*() const + + Dereference operator. Returns a pointer to the current item. +*/ + + +/*! + \enum QTreeWidgetItemIterator::IteratorFlag + + These flags can be passed to a QTreeWidgetItemIterator constructor + (OR-ed together if more than one is used), so that the iterator + will only iterate over items that match the given flags. + + \value All + \value Hidden + \value NotHidden + \value Selected + \value Unselected + \value Selectable + \value NotSelectable + \value DragEnabled + \value DragDisabled + \value DropEnabled + \value DropDisabled + \value HasChildren + \value NoChildren + \value Checked + \value NotChecked + \value Enabled + \value Disabled + \value Editable + \value NotEditable + \value UserFlag +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET diff --git a/src/gui/itemviews/qtreewidgetitemiterator.h b/src/gui/itemviews/qtreewidgetitemiterator.h new file mode 100644 index 0000000000..680bac4f51 --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGETITEMITERATOR_H +#define QTREEWIDGETITEMITERATOR_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TREEWIDGET + +class QTreeWidget; +class QTreeWidgetItem; +class QTreeModel; + +class QTreeWidgetItemIteratorPrivate; +class Q_GUI_EXPORT QTreeWidgetItemIterator +{ + friend class QTreeModel; + +public: + enum IteratorFlag { + All = 0x00000000, + Hidden = 0x00000001, + NotHidden = 0x00000002, + Selected = 0x00000004, + Unselected = 0x00000008, + Selectable = 0x00000010, + NotSelectable = 0x00000020, + DragEnabled = 0x00000040, + DragDisabled = 0x00000080, + DropEnabled = 0x00000100, + DropDisabled = 0x00000200, + HasChildren = 0x00000400, + NoChildren = 0x00000800, + Checked = 0x00001000, + NotChecked = 0x00002000, + Enabled = 0x00004000, + Disabled = 0x00008000, + Editable = 0x00010000, + NotEditable = 0x00020000, + UserFlag = 0x01000000 // The first flag that can be used by the user. + }; + Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag) + + QTreeWidgetItemIterator(const QTreeWidgetItemIterator &it); + explicit QTreeWidgetItemIterator(QTreeWidget *widget, IteratorFlags flags = All); + explicit QTreeWidgetItemIterator(QTreeWidgetItem *item, IteratorFlags flags = All); + ~QTreeWidgetItemIterator(); + + QTreeWidgetItemIterator &operator=(const QTreeWidgetItemIterator &it); + + QTreeWidgetItemIterator &operator++(); + inline const QTreeWidgetItemIterator operator++(int); + inline QTreeWidgetItemIterator &operator+=(int n); + + QTreeWidgetItemIterator &operator--(); + inline const QTreeWidgetItemIterator operator--(int); + inline QTreeWidgetItemIterator &operator-=(int n); + + inline QTreeWidgetItem *operator*() const; + +private: + bool matchesFlags(const QTreeWidgetItem *item) const; + QScopedPointer d_ptr; + QTreeWidgetItem *current; + IteratorFlags flags; + Q_DECLARE_PRIVATE(QTreeWidgetItemIterator) +}; + +inline const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator++(int) +{ + QTreeWidgetItemIterator it = *this; + ++(*this); + return it; +} + +inline const QTreeWidgetItemIterator QTreeWidgetItemIterator::operator--(int) +{ + QTreeWidgetItemIterator it = *this; + --(*this); + return it; +} + +inline QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator+=(int n) +{ + if (n < 0) + return (*this) -= (-n); + while (current && n--) + ++(*this); + return *this; +} + +inline QTreeWidgetItemIterator &QTreeWidgetItemIterator::operator-=(int n) +{ + if (n < 0) + return (*this) += (-n); + while (current && n--) + --(*this); + return *this; +} + +inline QTreeWidgetItem *QTreeWidgetItemIterator::operator*() const +{ + return current; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTreeWidgetItemIterator::IteratorFlags) + + +QT_END_NAMESPACE +#endif // QT_NO_TREEWIDGET +QT_END_HEADER + +#endif // QTREEWIDGETITEMITERATOR_H diff --git a/src/gui/itemviews/qtreewidgetitemiterator_p.h b/src/gui/itemviews/qtreewidgetitemiterator_p.h new file mode 100644 index 0000000000..69876eb646 --- /dev/null +++ b/src/gui/itemviews/qtreewidgetitemiterator_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTREEWIDGETITEMITERATOR_P_H +#define QTREEWIDGETITEMITERATOR_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 + +#ifndef QT_NO_TREEWIDGET +#include "qtreewidgetitemiterator.h" + +QT_BEGIN_NAMESPACE + +class QTreeModel; +class QTreeWidgetItem; + +class QTreeWidgetItemIteratorPrivate { + Q_DECLARE_PUBLIC(QTreeWidgetItemIterator) +public: + QTreeWidgetItemIteratorPrivate(QTreeWidgetItemIterator *q, QTreeModel *model) + : m_currentIndex(0), m_model(model), q_ptr(q) + { + + } + + QTreeWidgetItemIteratorPrivate(const QTreeWidgetItemIteratorPrivate& other) + : m_currentIndex(other.m_currentIndex), m_model(other.m_model), + m_parentIndex(other.m_parentIndex), q_ptr(other.q_ptr) + { + + } + + QTreeWidgetItemIteratorPrivate &operator=(const QTreeWidgetItemIteratorPrivate& other) + { + m_currentIndex = other.m_currentIndex; + m_parentIndex = other.m_parentIndex; + m_model = other.m_model; + return (*this); + } + + ~QTreeWidgetItemIteratorPrivate() + { + } + + QTreeWidgetItem* nextSibling(const QTreeWidgetItem* item) const; + void ensureValidIterator(const QTreeWidgetItem *itemToBeRemoved); + + QTreeWidgetItem *next(const QTreeWidgetItem *current); + QTreeWidgetItem *previous(const QTreeWidgetItem *current); +private: + int m_currentIndex; + QTreeModel *m_model; // This iterator class should not have ownership of the model. + QStack m_parentIndex; + QTreeWidgetItemIterator *q_ptr; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_TREEWIDGET + +#endif //QTREEWIDGETITEMITERATOR_P_H diff --git a/src/gui/itemviews/qwidgetitemdata_p.h b/src/gui/itemviews/qwidgetitemdata_p.h new file mode 100644 index 0000000000..a7068b7559 --- /dev/null +++ b/src/gui/itemviews/qwidgetitemdata_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGETITEMDATA_P_H +#define QWIDGETITEMDATA_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 + +class QWidgetItemData +{ +public: + inline QWidgetItemData() : role(-1) {} + inline QWidgetItemData(int r, QVariant v) : role(r), value(v) {} + int role; + QVariant value; + inline bool operator==(const QWidgetItemData &other) { return role == other.role && value == other.value; } +}; + +#ifndef QT_NO_DATASTREAM + +inline QDataStream &operator>>(QDataStream &in, QWidgetItemData &data) +{ + in >> data.role; + in >> data.value; + return in; +} + +inline QDataStream &operator<<(QDataStream &out, const QWidgetItemData &data) +{ + out << data.role; + out << data.value; + return out; +} + +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +#endif // QWIDGETITEMDATA_P_H diff --git a/src/gui/kernel/kernel.pri b/src/gui/kernel/kernel.pri new file mode 100644 index 0000000000..3c57368bab --- /dev/null +++ b/src/gui/kernel/kernel.pri @@ -0,0 +1,329 @@ +# Qt kernel module + +# Only used on platforms with CONFIG += precompile_header +PRECOMPILED_HEADER = kernel/qt_gui_pch.h + + +KERNEL_P= kernel +HEADERS += \ + kernel/qaction.h \ + kernel/qaction_p.h \ + kernel/qactiongroup.h \ + kernel/qapplication.h \ + kernel/qapplication_p.h \ + kernel/qboxlayout.h \ + kernel/qclipboard.h \ + kernel/qcursor.h \ + kernel/qdesktopwidget.h \ + kernel/qdrag.h \ + kernel/qdnd_p.h \ + kernel/qevent.h \ + kernel/qevent_p.h \ + kernel/qformlayout.h \ + kernel/qgridlayout.h \ + kernel/qkeysequence.h \ + kernel/qlayout.h \ + kernel/qlayout_p.h \ + kernel/qlayoutengine_p.h \ + kernel/qlayoutitem.h \ + kernel/qmime.h \ + kernel/qsessionmanager.h \ + kernel/qshortcut.h \ + kernel/qshortcutmap_p.h \ + kernel/qsizepolicy.h \ + kernel/qpalette.h \ + kernel/qstackedlayout.h \ + kernel/qtooltip.h \ + kernel/qwhatsthis.h \ + kernel/qwidget.h \ + kernel/qwidget_p.h \ + kernel/qwidgetaction.h \ + kernel/qwidgetaction_p.h \ + kernel/qwindowdefs.h \ + kernel/qkeymapper_p.h \ + kernel/qgesture.h \ + kernel/qgesture_p.h \ + kernel/qstandardgestures_p.h \ + kernel/qgesturerecognizer.h \ + kernel/qgesturemanager_p.h \ + kernel/qsoftkeymanager_p.h \ + kernel/qsoftkeymanager_common_p.h \ + kernel/qguiplatformplugin_p.h \ + +SOURCES += \ + kernel/qaction.cpp \ + kernel/qactiongroup.cpp \ + kernel/qapplication.cpp \ + kernel/qboxlayout.cpp \ + kernel/qclipboard.cpp \ + kernel/qcursor.cpp \ + kernel/qdrag.cpp \ + kernel/qdnd.cpp \ + kernel/qevent.cpp \ + kernel/qformlayout.cpp \ + kernel/qgridlayout.cpp \ + kernel/qkeysequence.cpp \ + kernel/qlayout.cpp \ + kernel/qlayoutengine.cpp \ + kernel/qlayoutitem.cpp \ + kernel/qmime.cpp \ + kernel/qpalette.cpp \ + kernel/qshortcut.cpp \ + kernel/qshortcutmap.cpp \ + kernel/qstackedlayout.cpp \ + kernel/qtooltip.cpp \ + kernel/qguivariant.cpp \ + kernel/qwhatsthis.cpp \ + kernel/qwidget.cpp \ + kernel/qwidgetaction.cpp \ + kernel/qkeymapper.cpp \ + kernel/qgesture.cpp \ + kernel/qstandardgestures.cpp \ + kernel/qgesturerecognizer.cpp \ + kernel/qgesturemanager.cpp \ + kernel/qsoftkeymanager.cpp \ + kernel/qdesktopwidget.cpp \ + kernel/qguiplatformplugin.cpp + +win32 { + DEFINES += QT_NO_DIRECTDRAW + + HEADERS += \ + kernel/qwinnativepangesturerecognizer_win_p.h + + SOURCES += \ + kernel/qapplication_win.cpp \ + kernel/qclipboard_win.cpp \ + kernel/qcursor_win.cpp \ + kernel/qdesktopwidget_win.cpp \ + kernel/qdnd_win.cpp \ + kernel/qmime_win.cpp \ + kernel/qsound_win.cpp \ + kernel/qwidget_win.cpp \ + kernel/qole_win.cpp \ + kernel/qkeymapper_win.cpp \ + kernel/qwinnativepangesturerecognizer_win.cpp + + !contains(DEFINES, QT_NO_DIRECTDRAW):LIBS += ddraw.lib +} + +symbian { + exists($${EPOCROOT}epoc32/include/platform/mw/akntranseffect.h): DEFINES += QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H + + SOURCES += \ + kernel/qapplication_s60.cpp \ + kernel/qeventdispatcher_s60.cpp \ + kernel/qwidget_s60.cpp \ + kernel/qcursor_s60.cpp \ + kernel/qdesktopwidget_s60.cpp \ + kernel/qkeymapper_s60.cpp\ + kernel/qclipboard_s60.cpp\ + kernel/qdnd_s60.cpp \ + kernel/qsound_s60.cpp + + HEADERS += \ + kernel/qt_s60_p.h \ + kernel/qeventdispatcher_s60_p.h + + LIBS += -lbafl -lestor + + INCLUDEPATH += $$MW_LAYER_SYSTEMINCLUDE + INCLUDEPATH += ../3rdparty/s60 + + contains(QT_CONFIG, s60) { + SOURCES += kernel/qsoftkeymanager_s60.cpp + HEADERS += kernel/qsoftkeymanager_s60_p.h + } +} + + +unix:x11 { + INCLUDEPATH += ../3rdparty/xorg + HEADERS += \ + kernel/qx11embed_x11.h \ + kernel/qx11info_x11.h \ + kernel/qkde_p.h + + SOURCES += \ + kernel/qapplication_x11.cpp \ + kernel/qclipboard_x11.cpp \ + kernel/qcursor_x11.cpp \ + kernel/qdnd_x11.cpp \ + kernel/qdesktopwidget_x11.cpp \ + kernel/qmotifdnd_x11.cpp \ + kernel/qsound_x11.cpp \ + kernel/qwidget_x11.cpp \ + kernel/qwidgetcreate_x11.cpp \ + kernel/qx11embed_x11.cpp \ + kernel/qx11info_x11.cpp \ + kernel/qkeymapper_x11.cpp \ + kernel/qkde.cpp + + contains(QT_CONFIG, glib) { + SOURCES += \ + kernel/qguieventdispatcher_glib.cpp + HEADERS += \ + kernel/qguieventdispatcher_glib_p.h + QMAKE_CXXFLAGS += $$QT_CFLAGS_GLIB + LIBS_PRIVATE +=$$QT_LIBS_GLIB + } + SOURCES += \ + kernel/qeventdispatcher_x11.cpp + HEADERS += \ + kernel/qeventdispatcher_x11_p.h +} + +embedded { + HEADERS += \ + kernel/qeventdispatcher_qws_p.h + + SOURCES += \ + kernel/qapplication_qws.cpp \ + kernel/qclipboard_qws.cpp \ + kernel/qcursor_qws.cpp \ + kernel/qdesktopwidget_qws.cpp \ + kernel/qdnd_qws.cpp \ + kernel/qeventdispatcher_qws.cpp \ + kernel/qsound_qws.cpp \ + kernel/qwidget_qws.cpp \ + kernel/qkeymapper_qws.cpp \ + kernel/qsessionmanager_qws.cpp + + contains(QT_CONFIG, glib) { + SOURCES += \ + kernel/qeventdispatcher_glib_qws.cpp + HEADERS += \ + kernel/qeventdispatcher_glib_qws_p.h + QMAKE_CXXFLAGS += $$QT_CFLAGS_GLIB + LIBS_PRIVATE +=$$QT_LIBS_GLIB + } +} + +!qpa { + HEADERS += \ + kernel/qsound.h \ + kernel/qsound_p.h + + SOURCES += \ + kernel/qsound.cpp +} + +qpa { + HEADERS += \ + kernel/qgenericpluginfactory_qpa.h \ + kernel/qgenericplugin_qpa.h \ + kernel/qeventdispatcher_qpa_p.h \ + kernel/qwindowsysteminterface_qpa.h \ + kernel/qwindowsysteminterface_qpa_p.h \ + kernel/qplatformintegration_qpa.h \ + kernel/qplatformscreen_qpa.h \ + kernel/qplatformintegrationfactory_qpa_p.h \ + kernel/qplatformintegrationplugin_qpa.h \ + kernel/qplatformwindow_qpa.h \ + kernel/qplatformwindowformat_qpa.h \ + kernel/qplatformglcontext_qpa.h \ + kernel/qdesktopwidget_qpa_p.h \ + kernel/qplatformeventloopintegration_qpa.h \ + kernel/qplatformcursor_qpa.h \ + kernel/qplatformclipboard_qpa.h \ + kernel/qplatformnativeinterface_qpa.h + + SOURCES += \ + kernel/qapplication_qpa.cpp \ + kernel/qclipboard_qpa.cpp \ + kernel/qcursor_qpa.cpp \ + kernel/qdnd_qws.cpp \ + kernel/qdesktopwidget_qpa.cpp \ + kernel/qgenericpluginfactory_qpa.cpp \ + kernel/qgenericplugin_qpa.cpp \ + kernel/qkeymapper_qws.cpp \ + kernel/qwidget_qpa.cpp \ + kernel/qeventdispatcher_qpa.cpp \ + kernel/qwindowsysteminterface_qpa.cpp \ + kernel/qplatformintegration_qpa.cpp \ + kernel/qplatformscreen_qpa.cpp \ + kernel/qplatformintegrationfactory_qpa.cpp \ + kernel/qplatformintegrationplugin_qpa.cpp \ + kernel/qplatformwindow_qpa.cpp \ + kernel/qplatformwindowformat_qpa.cpp \ + kernel/qplatformeventloopintegration_qpa.cpp \ + kernel/qplatformglcontext_qpa.cpp \ + kernel/qplatformcursor_qpa.cpp \ + kernel/qplatformclipboard_qpa.cpp \ + kernel/qplatformnativeinterface_qpa.cpp \ + kernel/qsessionmanager_qpa.cpp + + contains(QT_CONFIG, glib) { + SOURCES += \ + kernel/qeventdispatcher_glib_qpa.cpp + HEADERS += \ + kernel/qeventdispatcher_glib_qpa_p.h + QMAKE_CXXFLAGS += $$QT_CFLAGS_GLIB + LIBS_PRIVATE +=$$QT_LIBS_GLIB + } +} + +!embedded:!qpa:!x11:mac { + SOURCES += \ + kernel/qclipboard_mac.cpp \ + kernel/qmime_mac.cpp \ + kernel/qt_mac.cpp \ + kernel/qkeymapper_mac.cpp + + OBJECTIVE_HEADERS += \ + qcocoawindow_mac_p.h \ + qcocoapanel_mac_p.h \ + qcocoawindowdelegate_mac_p.h \ + qcocoaview_mac_p.h \ + qcocoaapplication_mac_p.h \ + qcocoaapplicationdelegate_mac_p.h \ + qmacgesturerecognizer_mac_p.h \ + qmultitouch_mac_p.h \ + qcocoasharedwindowmethods_mac_p.h \ + qcocoaintrospection_p.h + + OBJECTIVE_SOURCES += \ + kernel/qcursor_mac.mm \ + kernel/qdnd_mac.mm \ + kernel/qsound_mac.mm \ + kernel/qapplication_mac.mm \ + kernel/qwidget_mac.mm \ + kernel/qcocoapanel_mac.mm \ + kernel/qcocoaview_mac.mm \ + kernel/qcocoawindow_mac.mm \ + kernel/qcocoawindowdelegate_mac.mm \ + kernel/qcocoamenuloader_mac.mm \ + kernel/qcocoaapplication_mac.mm \ + kernel/qcocoaapplicationdelegate_mac.mm \ + kernel/qt_cocoa_helpers_mac.mm \ + kernel/qdesktopwidget_mac.mm \ + kernel/qeventdispatcher_mac.mm \ + kernel/qcocoawindowcustomthemeframe_mac.mm \ + kernel/qmacgesturerecognizer_mac.mm \ + kernel/qmultitouch_mac.mm \ + kernel/qcocoaintrospection_mac.mm + + HEADERS += \ + kernel/qt_cocoa_helpers_mac_p.h \ + kernel/qcocoaapplication_mac_p.h \ + kernel/qcocoaapplicationdelegate_mac_p.h \ + kernel/qeventdispatcher_mac_p.h + + MENU_NIB.files = mac/qt_menu.nib + MENU_NIB.path = Resources + MENU_NIB.version = Versions + QMAKE_BUNDLE_DATA += MENU_NIB + RESOURCES += mac/macresources.qrc + + LIBS_PRIVATE += -framework AppKit +} + +wince*: { + HEADERS += \ + ../corelib/kernel/qfunctions_wince.h \ + kernel/qguifunctions_wince.h + + SOURCES += \ + ../corelib/kernel/qfunctions_wince.cpp \ + kernel/qguifunctions_wince.cpp +} diff --git a/src/gui/kernel/mac.pri b/src/gui/kernel/mac.pri new file mode 100644 index 0000000000..21acd06e65 --- /dev/null +++ b/src/gui/kernel/mac.pri @@ -0,0 +1,4 @@ +!x11:!embedded:!qpa:mac { + LIBS_PRIVATE += -framework Carbon -lz + *-mwerks:INCLUDEPATH += compat +} diff --git a/src/gui/kernel/qaction.cpp b/src/gui/kernel/qaction.cpp new file mode 100644 index 0000000000..69e9889e08 --- /dev/null +++ b/src/gui/kernel/qaction.cpp @@ -0,0 +1,1520 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaction.h" +#include "qactiongroup.h" + +#ifndef QT_NO_ACTION +#include "qaction_p.h" +#include "qapplication.h" +#include "qevent.h" +#include "qlist.h" +#include "qdebug.h" +#include +#include +#include + +#define QAPP_CHECK(functionName) \ + if (!qApp) { \ + qWarning("QAction: Initialize QApplication before calling '" functionName "'."); \ + return; \ + } + +QT_BEGIN_NAMESPACE + +/* + internal: guesses a descriptive text from a text suited for a menu entry + */ +static QString qt_strippedText(QString s) +{ + s.remove( QString::fromLatin1("...") ); + int i = 0; + while (i < s.size()) { + ++i; + if (s.at(i-1) != QLatin1Char('&')) + continue; + if (i < s.size() && s.at(i) == QLatin1Char('&')) + ++i; + s.remove(i-1,1); + } + return s.trimmed(); +} + + +QActionPrivate::QActionPrivate() : group(0), enabled(1), forceDisabled(0), + visible(1), forceInvisible(0), checkable(0), checked(0), separator(0), fontSet(false), + forceEnabledInSoftkeys(false), menuActionSoftkeys(false), + iconVisibleInMenu(-1), + menuRole(QAction::TextHeuristicRole), softKeyRole(QAction::NoSoftKey), + priority(QAction::NormalPriority) +{ +#ifdef QT3_SUPPORT + static int qt_static_action_id = -1; + param = id = --qt_static_action_id; + act_signal = 0; +#endif +#ifndef QT_NO_SHORTCUT + shortcutId = 0; + shortcutContext = Qt::WindowShortcut; + autorepeat = true; +#endif +} + +QActionPrivate::~QActionPrivate() +{ +} + +bool QActionPrivate::showStatusText(QWidget *widget, const QString &str) +{ +#ifdef QT_NO_STATUSTIP + Q_UNUSED(widget); + Q_UNUSED(str); +#else + if(QObject *object = widget ? widget : parent) { + QStatusTipEvent tip(str); + QApplication::sendEvent(object, &tip); + return true; + } +#endif + return false; +} + +void QActionPrivate::sendDataChanged() +{ + Q_Q(QAction); + QActionEvent e(QEvent::ActionChanged, q); + for (int i = 0; i < widgets.size(); ++i) { + QWidget *w = widgets.at(i); + QApplication::sendEvent(w, &e); + } +#ifndef QT_NO_GRAPHICSVIEW + for (int i = 0; i < graphicsWidgets.size(); ++i) { + QGraphicsWidget *w = graphicsWidgets.at(i); + QApplication::sendEvent(w, &e); + } +#endif + QApplication::sendEvent(q, &e); + + emit q->changed(); +} + +#ifndef QT_NO_SHORTCUT +void QActionPrivate::redoGrab(QShortcutMap &map) +{ + Q_Q(QAction); + if (shortcutId) + map.removeShortcut(shortcutId, q); + if (shortcut.isEmpty()) + return; + shortcutId = map.addShortcut(q, shortcut, shortcutContext); + if (!enabled) + map.setShortcutEnabled(false, shortcutId, q); + if (!autorepeat) + map.setShortcutAutoRepeat(false, shortcutId, q); +} + +void QActionPrivate::redoGrabAlternate(QShortcutMap &map) +{ + Q_Q(QAction); + for(int i = 0; i < alternateShortcutIds.count(); ++i) { + if (const int id = alternateShortcutIds.at(i)) + map.removeShortcut(id, q); + } + alternateShortcutIds.clear(); + if (alternateShortcuts.isEmpty()) + return; + for(int i = 0; i < alternateShortcuts.count(); ++i) { + const QKeySequence& alternate = alternateShortcuts.at(i); + if (!alternate.isEmpty()) + alternateShortcutIds.append(map.addShortcut(q, alternate, shortcutContext)); + else + alternateShortcutIds.append(0); + } + if (!enabled) { + for(int i = 0; i < alternateShortcutIds.count(); ++i) { + const int id = alternateShortcutIds.at(i); + map.setShortcutEnabled(false, id, q); + } + } + if (!autorepeat) { + for(int i = 0; i < alternateShortcutIds.count(); ++i) { + const int id = alternateShortcutIds.at(i); + map.setShortcutAutoRepeat(false, id, q); + } + } +} + +void QActionPrivate::setShortcutEnabled(bool enable, QShortcutMap &map) +{ + Q_Q(QAction); + if (shortcutId) + map.setShortcutEnabled(enable, shortcutId, q); + for(int i = 0; i < alternateShortcutIds.count(); ++i) { + if (const int id = alternateShortcutIds.at(i)) + map.setShortcutEnabled(enable, id, q); + } +} +#endif // QT_NO_SHORTCUT + + +/*! + \class QAction + \brief The QAction class provides an abstract user interface + action that can be inserted into widgets. + + \ingroup mainwindow-classes + + + \omit + * parent and widget are different + * parent does not define context + \endomit + + In applications many common commands can be invoked via menus, + toolbar buttons, and keyboard shortcuts. Since the user expects + each command to be performed in the same way, regardless of the + user interface used, it is useful to represent each command as + an \e action. + + Actions can be added to menus and toolbars, and will + automatically keep them in sync. For example, in a word processor, + if the user presses a Bold toolbar button, the Bold menu item + will automatically be checked. + + Actions can be created as independent objects, but they may + also be created during the construction of menus; the QMenu class + contains convenience functions for creating actions suitable for + use as menu items. + + A QAction may contain an icon, menu text, a shortcut, status text, + "What's This?" text, and a tooltip. Most of these can be set in + the constructor. They can also be set independently with + setIcon(), setText(), setIconText(), setShortcut(), + setStatusTip(), setWhatsThis(), and setToolTip(). For menu items, + it is possible to set an individual font with setFont(). + + Actions are added to widgets using QWidget::addAction() or + QGraphicsWidget::addAction(). Note that an action must be added to a + widget before it can be used; this is also true when the shortcut should + be global (i.e., Qt::ApplicationShortcut as Qt::ShortcutContext). + + Once a QAction has been created it should be added to the relevant + menu and toolbar, then connected to the slot which will perform + the action. For example: + + \snippet examples/mainwindows/application/mainwindow.cpp 19 + \codeline + \snippet examples/mainwindows/application/mainwindow.cpp 28 + \snippet examples/mainwindows/application/mainwindow.cpp 31 + + We recommend that actions are created as children of the window + they are used in. In most cases actions will be children of + the application's main window. + + \sa QMenu, QToolBar, {Application Example} +*/ + +/*! + \fn void QAction::trigger() + + This is a convenience slot that calls activate(Trigger). +*/ + +/*! + \fn void QAction::hover() + + This is a convenience slot that calls activate(Hover). +*/ + +/*! + \enum QAction::MenuRole + + This enum describes how an action should be moved into the application menu on Mac OS X. + + \value NoRole This action should not be put into the application menu + \value TextHeuristicRole This action should be put in the application menu based on the action's text + as described in the QMenuBar documentation. + \value ApplicationSpecificRole This action should be put in the application menu with an application specific role + \value AboutQtRole This action matches handles the "About Qt" menu item. + \value AboutRole This action should be placed where the "About" menu item is in the application menu. The text of + the menu item will be set to "About ". The application name is fetched from the + \c{Info.plist} file in the application's bundle (See \l{Deploying an Application on Mac OS X}). + \value PreferencesRole This action should be placed where the "Preferences..." menu item is in the application menu. + \value QuitRole This action should be placed where the Quit menu item is in the application menu. + + Setting this value only has effect on items that are in the immediate menus + of the menubar, not the submenus of those menus. For example, if you have + File menu in your menubar and the File menu has a submenu, setting the + MenuRole for the actions in that submenu have no effect. They will never be moved. +*/ + +/*! \since 4.6 + + \enum QAction::SoftKeyRole + + This enum describes how an action should be placed in the softkey bar. Currently this enum only + has an effect on the Symbian platform. + + \value NoSoftKey This action should not be used as a softkey + \value PositiveSoftKey This action is used to describe a softkey with a positive or non-destructive + role such as Ok, Select, or Options. + \value NegativeSoftKey This action is used to describe a soft ey with a negative or destructive role + role such as Cancel, Discard, or Close. + \value SelectSoftKey This action is used to describe a role that selects a particular item or widget + in the application. + + Actions with a softkey role defined are only visible in the softkey bar when the widget containing + the action has focus. If no widget currently has focus, the softkey framework will traverse up the + widget parent hierarchy looking for a widget containing softkey actions. + */ + +/*! + Constructs an action with \a parent. If \a parent is an action + group the action will be automatically inserted into the group. +*/ +QAction::QAction(QObject* parent) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + + +/*! + Constructs an action with some \a text and \a parent. If \a + parent is an action group the action will be automatically + inserted into the group. + + The action uses a stripped version of \a text (e.g. "\&Menu + Option..." becomes "Menu Option") as descriptive text for + tool buttons. You can override this by setting a specific + description with setText(). The same text will be used for + tooltips unless you specify a different text using + setToolTip(). + +*/ +QAction::QAction(const QString &text, QObject* parent) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + d->text = text; + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + +/*! + Constructs an action with an \a icon and some \a text and \a + parent. If \a parent is an action group the action will be + automatically inserted into the group. + + The action uses a stripped version of \a text (e.g. "\&Menu + Option..." becomes "Menu Option") as descriptive text for + tool buttons. You can override this by setting a specific + description with setText(). The same text will be used for + tooltips unless you specify a different text using + setToolTip(). +*/ +QAction::QAction(const QIcon &icon, const QString &text, QObject* parent) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + d->icon = icon; + d->text = text; + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + +/*! + \internal +*/ +QAction::QAction(QActionPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QAction); + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + +/*! + Returns the parent widget. +*/ +QWidget *QAction::parentWidget() const +{ + QObject *ret = parent(); + while (ret && !ret->isWidgetType()) + ret = ret->parent(); + return (QWidget*)ret; +} + +/*! + \since 4.2 + Returns a list of widgets this action has been added to. + + \sa QWidget::addAction(), associatedGraphicsWidgets() +*/ +QList QAction::associatedWidgets() const +{ + Q_D(const QAction); + return d->widgets; +} + +#ifndef QT_NO_GRAPHICSVIEW +/*! + \since 4.5 + Returns a list of widgets this action has been added to. + + \sa QWidget::addAction(), associatedWidgets() +*/ +QList QAction::associatedGraphicsWidgets() const +{ + Q_D(const QAction); + return d->graphicsWidgets; +} +#endif + +#ifndef QT_NO_SHORTCUT +/*! + \property QAction::shortcut + \brief the action's primary shortcut key + + Valid keycodes for this property can be found in \l Qt::Key and + \l Qt::Modifier. There is no default shortcut key. +*/ +void QAction::setShortcut(const QKeySequence &shortcut) +{ + QAPP_CHECK("setShortcut"); + + Q_D(QAction); + if (d->shortcut == shortcut) + return; + + d->shortcut = shortcut; + d->redoGrab(qApp->d_func()->shortcutMap); + d->sendDataChanged(); +} + +/*! + \since 4.2 + + Sets \a shortcuts as the list of shortcuts that trigger the + action. The first element of the list is the primary shortcut. + + \sa shortcut +*/ +void QAction::setShortcuts(const QList &shortcuts) +{ + Q_D(QAction); + + QList listCopy = shortcuts; + + QKeySequence primary; + if (!listCopy.isEmpty()) + primary = listCopy.takeFirst(); + + if (d->shortcut == primary && d->alternateShortcuts == listCopy) + return; + + QAPP_CHECK("setShortcuts"); + + d->shortcut = primary; + d->alternateShortcuts = listCopy; + d->redoGrab(qApp->d_func()->shortcutMap); + d->redoGrabAlternate(qApp->d_func()->shortcutMap); + d->sendDataChanged(); +} + +/*! + \since 4.2 + + Sets a platform dependent list of shortcuts based on the \a key. + The result of calling this function will depend on the currently running platform. + Note that more than one shortcut can assigned by this action. + If only the primary shortcut is required, use setShortcut instead. + + \sa QKeySequence::keyBindings() +*/ +void QAction::setShortcuts(QKeySequence::StandardKey key) +{ + QList list = QKeySequence::keyBindings(key); + setShortcuts(list); +} + +/*! + Returns the primary shortcut. + + \sa setShortcuts() +*/ +QKeySequence QAction::shortcut() const +{ + Q_D(const QAction); + return d->shortcut; +} + +/*! + \since 4.2 + + Returns the list of shortcuts, with the primary shortcut as + the first element of the list. + + \sa setShortcuts() +*/ +QList QAction::shortcuts() const +{ + Q_D(const QAction); + QList shortcuts; + if (!d->shortcut.isEmpty()) + shortcuts << d->shortcut; + if (!d->alternateShortcuts.isEmpty()) + shortcuts << d->alternateShortcuts; + return shortcuts; +} + +/*! + \property QAction::shortcutContext + \brief the context for the action's shortcut + + Valid values for this property can be found in \l Qt::ShortcutContext. + The default value is Qt::WindowShortcut. +*/ +void QAction::setShortcutContext(Qt::ShortcutContext context) +{ + Q_D(QAction); + if (d->shortcutContext == context) + return; + QAPP_CHECK("setShortcutContext"); + d->shortcutContext = context; + d->redoGrab(qApp->d_func()->shortcutMap); + d->redoGrabAlternate(qApp->d_func()->shortcutMap); + d->sendDataChanged(); +} + +Qt::ShortcutContext QAction::shortcutContext() const +{ + Q_D(const QAction); + return d->shortcutContext; +} + +/*! + \property QAction::autoRepeat + \brief whether the action can auto repeat + \since 4.2 + + If true, the action will auto repeat when the keyboard shortcut + combination is held down, provided that keyboard auto repeat is + enabled on the system. + The default value is true. +*/ +void QAction::setAutoRepeat(bool on) +{ + Q_D(QAction); + if (d->autorepeat == on) + return; + QAPP_CHECK("setAutoRepeat"); + d->autorepeat = on; + d->redoGrab(qApp->d_func()->shortcutMap); + d->redoGrabAlternate(qApp->d_func()->shortcutMap); + d->sendDataChanged(); +} + +bool QAction::autoRepeat() const +{ + Q_D(const QAction); + return d->autorepeat; +} +#endif // QT_NO_SHORTCUT + +/*! + \property QAction::font + \brief the action's font + + The font property is used to render the text set on the + QAction. The font will can be considered a hint as it will not be + consulted in all cases based upon application and style. + + By default, this property contains the application's default font. + + \sa QAction::setText() QStyle +*/ +void QAction::setFont(const QFont &font) +{ + Q_D(QAction); + if (d->font == font) + return; + + d->fontSet = true; + d->font = font; + d->sendDataChanged(); +} + +QFont QAction::font() const +{ + Q_D(const QAction); + return d->font; +} + +#ifdef QT3_SUPPORT +/*! + Use one of the QAction constructors that doesn't take a \a name + argument and call setObjectName() instead. +*/ +QAction::QAction(QObject* parent, const char* name) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + setObjectName(QString::fromAscii(name)); + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + + +/*! + Use one of the QAction constructors that doesn't take a \a name + argument and call setObjectName() instead. +*/ +QAction::QAction(const QString &text, const QKeySequence &shortcut, QObject* parent, const char* name) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + setObjectName(QString::fromAscii(name)); + d->text = text; + setShortcut(shortcut); + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} + +/*! + Use one of the QAction constructors that doesn't take a \a name + argument and call setObjectName() instead. +*/ +QAction::QAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, + QObject* parent, const char* name) + : QObject(*(new QActionPrivate), parent) +{ + Q_D(QAction); + setObjectName(QString::fromAscii(name)); + d->text = text; + setShortcut(shortcut); + d->icon = icon; + d->group = qobject_cast(parent); + if (d->group) + d->group->addAction(this); +} +#endif + +/*! + Destroys the object and frees allocated resources. +*/ +QAction::~QAction() +{ + Q_D(QAction); + for (int i = d->widgets.size()-1; i >= 0; --i) { + QWidget *w = d->widgets.at(i); + w->removeAction(this); + } +#ifndef QT_NO_GRAPHICSVIEW + for (int i = d->graphicsWidgets.size()-1; i >= 0; --i) { + QGraphicsWidget *w = d->graphicsWidgets.at(i); + w->removeAction(this); + } +#endif + if (d->group) + d->group->removeAction(this); +#ifndef QT_NO_SHORTCUT + if (d->shortcutId && qApp) { + qApp->d_func()->shortcutMap.removeShortcut(d->shortcutId, this); + for(int i = 0; i < d->alternateShortcutIds.count(); ++i) { + const int id = d->alternateShortcutIds.at(i); + qApp->d_func()->shortcutMap.removeShortcut(id, this); + } + } +#endif +} + +/*! + Sets this action group to \a group. The action will be automatically + added to the group's list of actions. + + Actions within the group will be mutually exclusive. + + \sa QActionGroup, QAction::actionGroup() +*/ +void QAction::setActionGroup(QActionGroup *group) +{ + Q_D(QAction); + if(group == d->group) + return; + + if(d->group) + d->group->removeAction(this); + d->group = group; + if(group) + group->addAction(this); +} + +/*! + Returns the action group for this action. If no action group manages + this action then 0 will be returned. + + \sa QActionGroup, QAction::setActionGroup() +*/ +QActionGroup *QAction::actionGroup() const +{ + Q_D(const QAction); + return d->group; +} + + +/*! + \property QAction::icon + \brief the action's icon + + In toolbars, the icon is used as the tool button icon; in menus, + it is displayed to the left of the menu text. There is no default + icon. + + On Symbian the icons which are passed to softkeys, i.e. to actions with + softkey role, need to have pixmap alpha channel correctly set otherwise + drawing artifacts will appear when softkey is pressed down. + + If a null icon (QIcon::isNull() is passed into this function, + the icon of the action is cleared. +*/ +void QAction::setIcon(const QIcon &icon) +{ + Q_D(QAction); + d->icon = icon; + d->sendDataChanged(); +} + +QIcon QAction::icon() const +{ + Q_D(const QAction); + return d->icon; +} + +#ifndef QT_NO_MENU +/*! + Returns the menu contained by this action. Actions that contain + menus can be used to create menu items with submenus, or inserted + into toolbars to create buttons with popup menus. + + \sa QMenu::addAction() +*/ +QMenu *QAction::menu() const +{ + Q_D(const QAction); + return d->menu; +} + +/*! + Sets the menu contained by this action to the specified \a menu. +*/ +void QAction::setMenu(QMenu *menu) +{ + Q_D(QAction); + if (d->menu) + d->menu->d_func()->setOverrideMenuAction(0); //we reset the default action of any previous menu + d->menu = menu; + if (menu) + menu->d_func()->setOverrideMenuAction(this); + d->sendDataChanged(); +} +#endif // QT_NO_MENU + +/*! + If \a b is true then this action will be considered a separator. + + How a separator is represented depends on the widget it is inserted + into. Under most circumstances the text, submenu, and icon will be + ignored for separator actions. + + \sa QAction::isSeparator() +*/ +void QAction::setSeparator(bool b) +{ + Q_D(QAction); + if (d->separator == b) + return; + + d->separator = b; + d->sendDataChanged(); +} + +/*! + Returns true if this action is a separator action; otherwise it + returns false. + + \sa QAction::setSeparator() +*/ +bool QAction::isSeparator() const +{ + Q_D(const QAction); + return d->separator; +} + +/*! + \property QAction::text + \brief the action's descriptive text + + If the action is added to a menu, the menu option will consist of + the icon (if there is one), the text, and the shortcut (if there + is one). If the text is not explicitly set in the constructor, or + by using setText(), the action's description icon text will be + used as text. There is no default text. + + \sa iconText +*/ +void QAction::setText(const QString &text) +{ + Q_D(QAction); + if (d->text == text) + return; + + d->text = text; + d->sendDataChanged(); +} + +QString QAction::text() const +{ + Q_D(const QAction); + QString s = d->text; + if(s.isEmpty()) { + s = d->iconText; + s.replace(QLatin1Char('&'), QLatin1String("&&")); + } + return s; +} + + + + + +/*! + \property QAction::iconText + \brief the action's descriptive icon text + + If QToolBar::toolButtonStyle is set to a value that permits text to + be displayed, the text defined held in this property appears as a + label in the relevant tool button. + + It also serves as the default text in menus and tooltips if the action + has not been defined with setText() or setToolTip(), and will + also be used in toolbar buttons if no icon has been defined using setIcon(). + + If the icon text is not explicitly set, the action's normal text will be + used for the icon text. + + By default, this property contains an empty string. + + \sa setToolTip(), setStatusTip() +*/ +void QAction::setIconText(const QString &text) +{ + Q_D(QAction); + if (d->iconText == text) + return; + + d->iconText = text; + d->sendDataChanged(); +} + +QString QAction::iconText() const +{ + Q_D(const QAction); + if (d->iconText.isEmpty()) + return qt_strippedText(d->text); + return d->iconText; +} + +/*! + \property QAction::toolTip + \brief the action's tooltip + + This text is used for the tooltip. If no tooltip is specified, + the action's text is used. + + By default, this property contains the action's text. + + \sa setStatusTip() setShortcut() +*/ +void QAction::setToolTip(const QString &tooltip) +{ + Q_D(QAction); + if (d->tooltip == tooltip) + return; + + d->tooltip = tooltip; + d->sendDataChanged(); +} + +QString QAction::toolTip() const +{ + Q_D(const QAction); + if (d->tooltip.isEmpty()) { + if (!d->text.isEmpty()) + return qt_strippedText(d->text); + return qt_strippedText(d->iconText); + } + return d->tooltip; +} + +/*! + \property QAction::statusTip + \brief the action's status tip + + The status tip is displayed on all status bars provided by the + action's top-level parent widget. + + By default, this property contains an empty string. + + \sa setToolTip() showStatusText() +*/ +void QAction::setStatusTip(const QString &statustip) +{ + Q_D(QAction); + if (d->statustip == statustip) + return; + + d->statustip = statustip; + d->sendDataChanged(); +} + +QString QAction::statusTip() const +{ + Q_D(const QAction); + return d->statustip; +} + +/*! + \property QAction::whatsThis + \brief the action's "What's This?" help text + + The "What's This?" text is used to provide a brief description of + the action. The text may contain rich text. There is no default + "What's This?" text. + + \sa QWhatsThis Q3StyleSheet +*/ +void QAction::setWhatsThis(const QString &whatsthis) +{ + Q_D(QAction); + if (d->whatsthis == whatsthis) + return; + + d->whatsthis = whatsthis; + d->sendDataChanged(); +} + +QString QAction::whatsThis() const +{ + Q_D(const QAction); + return d->whatsthis; +} + +/*! + \enum QAction::Priority + \since 4.6 + + This enum defines priorities for actions in user interface. + + \value LowPriority The action should not be prioritized in + the user interface. + + \value NormalPriority + + \value HighPriority The action should be prioritized in + the user interface. + + \sa priority +*/ + + +/*! + \property QAction::priority + \since 4.6 + + \brief the actions's priority in the user interface. + + This property can be set to indicate how the action should be prioritized + in the user interface. + + For instance, when toolbars have the Qt::ToolButtonTextBesideIcon + mode set, then actions with LowPriority will not show the text + labels. +*/ +void QAction::setPriority(Priority priority) +{ + Q_D(QAction); + if (d->priority == priority) + return; + + d->priority = priority; + d->sendDataChanged(); +} + +QAction::Priority QAction::priority() const +{ + Q_D(const QAction); + return d->priority; +} + +/*! + \property QAction::checkable + \brief whether the action is a checkable action + + A checkable action is one which has an on/off state. For example, + in a word processor, a Bold toolbar button may be either on or + off. An action which is not a toggle action is a command action; + a command action is simply executed, e.g. file save. + By default, this property is false. + + In some situations, the state of one toggle action should depend + on the state of others. For example, "Left Align", "Center" and + "Right Align" toggle actions are mutually exclusive. To achieve + exclusive toggling, add the relevant toggle actions to a + QActionGroup with the QActionGroup::exclusive property set to + true. + + \sa QAction::setChecked() +*/ +void QAction::setCheckable(bool b) +{ + Q_D(QAction); + if (d->checkable == b) + return; + + d->checkable = b; + d->checked = false; + d->sendDataChanged(); +} + +bool QAction::isCheckable() const +{ + Q_D(const QAction); + return d->checkable; +} + +/*! + \fn void QAction::toggle() + + This is a convenience function for the \l checked property. + Connect to it to change the checked state to its opposite state. +*/ +void QAction::toggle() +{ + Q_D(QAction); + setChecked(!d->checked); +} + +/*! + \property QAction::checked + \brief whether the action is checked. + + Only checkable actions can be checked. By default, this is false + (the action is unchecked). + + \sa checkable +*/ +void QAction::setChecked(bool b) +{ + Q_D(QAction); + if (!d->checkable || d->checked == b) + return; + + QPointer guard(this); + d->checked = b; + d->sendDataChanged(); + if (guard) + emit toggled(b); +} + +bool QAction::isChecked() const +{ + Q_D(const QAction); + return d->checked; +} + +/*! + \fn void QAction::setDisabled(bool b) + + This is a convenience function for the \l enabled property, that + is useful for signals--slots connections. If \a b is true the + action is disabled; otherwise it is enabled. +*/ + +/*! + \property QAction::enabled + \brief whether the action is enabled + + Disabled actions cannot be chosen by the user. They do not + disappear from menus or toolbars, but they are displayed in a way + which indicates that they are unavailable. For example, they might + be displayed using only shades of gray. + + \gui{What's This?} help on disabled actions is still available, provided + that the QAction::whatsThis property is set. + + An action will be disabled when all widgets to which it is added + (with QWidget::addAction()) are disabled or not visible. When an + action is disabled, it is not possible to trigger it through its + shortcut. + + By default, this property is true (actions are enabled). + + \sa text +*/ +void QAction::setEnabled(bool b) +{ + Q_D(QAction); + if (b == d->enabled && b != d->forceDisabled) + return; + d->forceDisabled = !b; + if (b && (!d->visible || (d->group && !d->group->isEnabled()))) + return; + QAPP_CHECK("setEnabled"); + d->enabled = b; +#ifndef QT_NO_SHORTCUT + d->setShortcutEnabled(b, qApp->d_func()->shortcutMap); +#endif + d->sendDataChanged(); +} + +bool QAction::isEnabled() const +{ + Q_D(const QAction); + return d->enabled; +} + +/*! + \property QAction::visible + \brief whether the action can be seen (e.g. in menus and toolbars) + + If \e visible is true the action can be seen (e.g. in menus and + toolbars) and chosen by the user; if \e visible is false the + action cannot be seen or chosen by the user. + + Actions which are not visible are \e not grayed out; they do not + appear at all. + + By default, this property is true (actions are visible). +*/ +void QAction::setVisible(bool b) +{ + Q_D(QAction); + if (b == d->visible && b != d->forceInvisible) + return; + QAPP_CHECK("setVisible"); + d->forceInvisible = !b; + d->visible = b; + d->enabled = b && !d->forceDisabled && (!d->group || d->group->isEnabled()) ; +#ifndef QT_NO_SHORTCUT + d->setShortcutEnabled(d->enabled, qApp->d_func()->shortcutMap); +#endif + d->sendDataChanged(); +} + + +bool QAction::isVisible() const +{ + Q_D(const QAction); + return d->visible; +} + +/*! + \reimp +*/ +bool +QAction::event(QEvent *e) +{ +#ifndef QT_NO_SHORTCUT + if (e->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast(e); + Q_ASSERT_X(se->key() == d_func()->shortcut || d_func()->alternateShortcuts.contains(se->key()), + "QAction::event", + "Received shortcut event from incorrect shortcut"); + if (se->isAmbiguous()) + qWarning("QAction::eventFilter: Ambiguous shortcut overload: %s", QString(se->key()).toLatin1().constData()); + else + activate(Trigger); + return true; + } +#endif + return QObject::event(e); +} + +/*! + Returns the user data as set in QAction::setData. + + \sa setData() +*/ +QVariant +QAction::data() const +{ + Q_D(const QAction); + return d->userData; +} + +/*! + \fn void QAction::setData(const QVariant &userData) + + Sets the action's internal data to the given \a userData. + + \sa data() +*/ +void +QAction::setData(const QVariant &data) +{ + Q_D(QAction); + d->userData = data; + d->sendDataChanged(); +} + + +/*! + Updates the relevant status bar for the \a widget specified by sending a + QStatusTipEvent to its parent widget. Returns true if an event was sent; + otherwise returns false. + + If a null widget is specified, the event is sent to the action's parent. + + \sa statusTip +*/ +bool +QAction::showStatusText(QWidget *widget) +{ + return d_func()->showStatusText(widget, statusTip()); +} + +/*! + Sends the relevant signals for ActionEvent \a event. + + Action based widgets use this API to cause the QAction + to emit signals as well as emitting their own. +*/ +void QAction::activate(ActionEvent event) +{ + Q_D(QAction); + if(event == Trigger) { + QObject *guard = this; + QMetaObject::addGuard(&guard); + if(d->checkable) { + // the checked action of an exclusive group cannot be unchecked + if (d->checked && (d->group && d->group->isExclusive() + && d->group->checkedAction() == this)) { + if (guard) + emit triggered(true); + QMetaObject::removeGuard(&guard); + return; + } + setChecked(!d->checked); + } + if (guard) + emit triggered(d->checked); +#ifdef QT3_SUPPORT + if (guard) + emit activated(d->param); +#endif + QMetaObject::removeGuard(&guard); + } else if(event == Hover) { + emit hovered(); + } +} + +/*! + \fn void QAction::triggered(bool checked) + + This signal is emitted when an action is activated by the user; + for example, when the user clicks a menu option, toolbar button, + or presses an action's shortcut key combination, or when trigger() + was called. Notably, it is \e not emitted when setChecked() or + toggle() is called. + + If the action is checkable, \a checked is true if the action is + checked, or false if the action is unchecked. + + \sa QAction::activate(), QAction::toggled(), checked +*/ + +/*! + \fn void QAction::toggled(bool checked) + + This signal is emitted whenever a checkable action changes its + isChecked() status. This can be the result of a user interaction, + or because setChecked() was called. + + \a checked is true if the action is checked, or false if the + action is unchecked. + + \sa QAction::activate(), QAction::triggered(), checked +*/ + +/*! + \fn void QAction::hovered() + + This signal is emitted when an action is highlighted by the user; + for example, when the user pauses with the cursor over a menu option, + toolbar button, or presses an action's shortcut key combination. + + \sa QAction::activate() +*/ + +/*! + \fn void QAction::changed() + + This signal is emitted when an action has changed. If you + are only interested in actions in a given widget, you can + watch for QWidget::actionEvent() sent with an + QEvent::ActionChanged. + + \sa QWidget::actionEvent() +*/ + +/*! + \enum QAction::ActionEvent + + This enum type is used when calling QAction::activate() + + \value Trigger this will cause the QAction::triggered() signal to be emitted. + + \value Hover this will cause the QAction::hovered() signal to be emitted. +*/ + +/*! + \fn void QAction::setMenuText(const QString &text) + + Use setText() instead. +*/ + +/*! + \fn QString QAction::menuText() const + + Use text() instead. +*/ + +/*! + \fn bool QAction::isOn() const + + Use isChecked() instead. +*/ + +/*! + \fn void QAction::setOn(bool b) + + Use setChecked() instead. +*/ + +/*! + \fn bool QAction::isToggleAction() const + + Use isCheckable() instead. +*/ + +/*! + \fn void QAction::setToggleAction(bool b) + + Use setCheckable() instead. +*/ + +/*! + \fn void QAction::setIconSet(const QIcon &i) + + Use setIcon() instead. +*/ + +/*! + \fn bool QAction::addTo(QWidget *w) + + Use QWidget::addAction() instead. + + \oldcode + action->addTo(widget); + \newcode + widget->addAction(action); + \endcode +*/ + +/*! + \fn bool QAction::removeFrom(QWidget *w) + + Use QWidget::removeAction() instead. + + \oldcode + action->removeFrom(widget); + \newcode + widget->removeAction(action); + \endcode +*/ + +/*! + \fn void QAction::setAccel(const QKeySequence &shortcut) + + Use setShortcut() instead. +*/ + +/*! + \fn QIcon QAction::iconSet() const + + Use icon() instead. +*/ + +/*! + \fn QKeySequence QAction::accel() const + + Use shortcut() instead. +*/ + +/*! + \fn void QAction::activated(int i); + + Use triggered() instead. +*/ + + +/*! + \property QAction::menuRole + \brief the action's menu role + \since 4.2 + + This indicates what role the action serves in the application menu on Mac + OS X. By default all action have the TextHeuristicRole, which means that + the action is added based on its text (see QMenuBar for more information). + + The menu role can only be changed before the actions are put into the menu + bar in Mac OS X (usually just before the first application window is + shown). +*/ +void QAction::setMenuRole(MenuRole menuRole) +{ + Q_D(QAction); + if (d->menuRole == menuRole) + return; + + d->menuRole = menuRole; + d->sendDataChanged(); +} + +QAction::MenuRole QAction::menuRole() const +{ + Q_D(const QAction); + return d->menuRole; +} + +/*! + \property QAction::softKeyRole + \brief the action's softkey role + \since 4.6 + + This indicates what type of role this action describes in the softkey framework + on platforms where such a framework is supported. Currently this is only + supported on the Symbian platform. + + The softkey role can be changed any time. +*/ +void QAction::setSoftKeyRole(SoftKeyRole softKeyRole) +{ + Q_D(QAction); + if (d->softKeyRole == softKeyRole) + return; + + d->softKeyRole = softKeyRole; + d->sendDataChanged(); +} + +QAction::SoftKeyRole QAction::softKeyRole() const +{ + Q_D(const QAction); + return d->softKeyRole; +} + +/*! + \property QAction::iconVisibleInMenu + \brief Whether or not an action should show an icon in a menu + \since 4.4 + + In some applications, it may make sense to have actions with icons in the + toolbar, but not in menus. If true, the icon (if valid) is shown in the menu, when it + is false, it is not shown. + + The default is to follow whether the Qt::AA_DontShowIconsInMenus attribute + is set for the application. Explicitly settings this property overrides + the presence (or abscence) of the attribute. + + For example: + \snippet doc/src/snippets/code/src_gui_kernel_qaction.cpp 0 + + \sa QAction::icon QApplication::setAttribute() +*/ +void QAction::setIconVisibleInMenu(bool visible) +{ + Q_D(QAction); + if (d->iconVisibleInMenu == -1 || visible != bool(d->iconVisibleInMenu)) { + int oldValue = d->iconVisibleInMenu; + d->iconVisibleInMenu = visible; + // Only send data changed if we really need to. + if (oldValue != -1 + || (oldValue == -1 + && visible == !QApplication::instance()->testAttribute(Qt::AA_DontShowIconsInMenus))) { + d->sendDataChanged(); + } + } +} + +bool QAction::isIconVisibleInMenu() const +{ + Q_D(const QAction); + if (d->iconVisibleInMenu == -1) { + return !QApplication::instance()->testAttribute(Qt::AA_DontShowIconsInMenus); + } + return d->iconVisibleInMenu; +} + +QT_END_NAMESPACE + +#include "moc_qaction.cpp" + +#endif // QT_NO_ACTION diff --git a/src/gui/kernel/qaction.h b/src/gui/kernel/qaction.h new file mode 100644 index 0000000000..856fd92f10 --- /dev/null +++ b/src/gui/kernel/qaction.h @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACTION_H +#define QACTION_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACTION + +class QMenu; +class QActionGroup; +class QActionPrivate; +class QGraphicsWidget; + +class Q_GUI_EXPORT QAction : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QAction) + + Q_ENUMS(MenuRole) + Q_ENUMS(SoftKeyRole) + Q_ENUMS(Priority) + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY changed) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked DESIGNABLE isCheckable NOTIFY toggled) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY changed) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY changed) + Q_PROPERTY(QString text READ text WRITE setText NOTIFY changed) + Q_PROPERTY(QString iconText READ iconText WRITE setIconText NOTIFY changed) + Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY changed) + Q_PROPERTY(QString statusTip READ statusTip WRITE setStatusTip NOTIFY changed) + Q_PROPERTY(QString whatsThis READ whatsThis WRITE setWhatsThis NOTIFY changed) + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY changed) +#ifndef QT_NO_SHORTCUT + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut NOTIFY changed) + Q_PROPERTY(Qt::ShortcutContext shortcutContext READ shortcutContext WRITE setShortcutContext NOTIFY changed) + Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat NOTIFY changed) +#endif + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY changed) + Q_PROPERTY(MenuRole menuRole READ menuRole WRITE setMenuRole NOTIFY changed) + Q_PROPERTY(SoftKeyRole softKeyRole READ softKeyRole WRITE setSoftKeyRole NOTIFY changed) + Q_PROPERTY(bool iconVisibleInMenu READ isIconVisibleInMenu WRITE setIconVisibleInMenu NOTIFY changed) + Q_PROPERTY(Priority priority READ priority WRITE setPriority) + +public: + enum MenuRole { NoRole, TextHeuristicRole, ApplicationSpecificRole, AboutQtRole, + AboutRole, PreferencesRole, QuitRole }; + enum SoftKeyRole { + NoSoftKey, PositiveSoftKey, NegativeSoftKey, SelectSoftKey }; + enum Priority { LowPriority = 0, + NormalPriority = 128, + HighPriority = 256}; + explicit QAction(QObject* parent); + QAction(const QString &text, QObject* parent); + QAction(const QIcon &icon, const QString &text, QObject* parent); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QAction(QObject* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QAction(const QString &text, const QKeySequence &shortcut, + QObject* parent, const char* name); + QT3_SUPPORT_CONSTRUCTOR QAction(const QIcon &icon, const QString &text, + const QKeySequence &shortcut, + QObject* parent, const char* name); +#endif + ~QAction(); + + void setActionGroup(QActionGroup *group); + QActionGroup *actionGroup() const; + void setIcon(const QIcon &icon); + QIcon icon() const; + + void setText(const QString &text); + QString text() const; + + void setIconText(const QString &text); + QString iconText() const; + + void setToolTip(const QString &tip); + QString toolTip() const; + + void setStatusTip(const QString &statusTip); + QString statusTip() const; + + void setWhatsThis(const QString &what); + QString whatsThis() const; + + void setPriority(Priority priority); + Priority priority() const; + +#ifndef QT_NO_MENU + QMenu *menu() const; + void setMenu(QMenu *menu); +#endif + + void setSeparator(bool b); + bool isSeparator() const; + +#ifndef QT_NO_SHORTCUT + void setShortcut(const QKeySequence &shortcut); + QKeySequence shortcut() const; + + void setShortcuts(const QList &shortcuts); + void setShortcuts(QKeySequence::StandardKey); + QList shortcuts() const; + + void setShortcutContext(Qt::ShortcutContext context); + Qt::ShortcutContext shortcutContext() const; + + void setAutoRepeat(bool); + bool autoRepeat() const; +#endif + + void setFont(const QFont &font); + QFont font() const; + + void setCheckable(bool); + bool isCheckable() const; + + QVariant data() const; + void setData(const QVariant &var); + + bool isChecked() const; + + bool isEnabled() const; + + bool isVisible() const; + + enum ActionEvent { Trigger, Hover }; + void activate(ActionEvent event); + bool showStatusText(QWidget *widget=0); + + void setMenuRole(MenuRole menuRole); + MenuRole menuRole() const; + + void setSoftKeyRole(SoftKeyRole softKeyRole); + SoftKeyRole softKeyRole() const; + + void setIconVisibleInMenu(bool visible); + bool isIconVisibleInMenu() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void setMenuText(const QString &text) { setText(text); } + inline QT3_SUPPORT QString menuText() const { return text(); } + inline QT3_SUPPORT bool isOn() const { return isChecked(); } + inline QT3_SUPPORT bool isToggleAction() const { return isCheckable(); } + inline QT3_SUPPORT void setToggleAction(bool b) { setCheckable(b); } + inline QT3_SUPPORT void setIconSet(const QIcon &i) { setIcon(i); } + inline QT3_SUPPORT QIcon iconSet() const { return icon(); } + inline QT3_SUPPORT bool addTo(QWidget *w) { w->addAction(this); return true; } + inline QT3_SUPPORT bool removeFrom(QWidget *w) { w->removeAction(this); return true; } + inline QT3_SUPPORT void setAccel(const QKeySequence &shortcut) { setShortcut(shortcut); } + inline QT3_SUPPORT QKeySequence accel() const { return shortcut(); } +#endif + + QWidget *parentWidget() const; + + QList associatedWidgets() const; +#ifndef QT_NO_GRAPHICSVIEW + QList associatedGraphicsWidgets() const; // ### suboptimal +#endif + +protected: + bool event(QEvent *); + QAction(QActionPrivate &dd, QObject *parent); + +public Q_SLOTS: +#ifdef QT3_SUPPORT + inline QT_MOC_COMPAT void setOn(bool b) { setChecked(b); } +#endif + void trigger() { activate(Trigger); } + void hover() { activate(Hover); } + void setChecked(bool); + void toggle(); + void setEnabled(bool); + inline void setDisabled(bool b) { setEnabled(!b); } + void setVisible(bool); + +Q_SIGNALS: + void changed(); + void triggered(bool checked = false); + void hovered(); + void toggled(bool); +#ifdef QT3_SUPPORT + QT_MOC_COMPAT void activated(int = 0); +#endif + +private: + Q_DISABLE_COPY(QAction) + +#ifdef QT3_SUPPORT + friend class QMenuItem; +#endif + friend class QGraphicsWidget; + friend class QWidget; + friend class QActionGroup; + friend class QMenu; + friend class QMenuPrivate; + friend class QMenuBar; + friend class QShortcutMap; + friend class QToolButton; +#ifdef Q_WS_MAC + friend void qt_mac_clear_status_text(QAction *action); +#endif +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +#endif // QT_NO_ACTION + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACTION_H diff --git a/src/gui/kernel/qaction_p.h b/src/gui/kernel/qaction_p.h new file mode 100644 index 0000000000..f3154f90c1 --- /dev/null +++ b/src/gui/kernel/qaction_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACTION_P_H +#define QACTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qaction.h" +#include "QtGui/qmenu.h" +#include "private/qgraphicswidget_p.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_ACTION + +#ifdef QT3_SUPPORT +class QMenuItemEmitter; +#endif + +class QShortcutMap; + +class Q_AUTOTEST_EXPORT QActionPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAction) +public: + QActionPrivate(); + ~QActionPrivate(); + + static QActionPrivate *get(QAction *q) + { + return q->d_func(); + } + + bool showStatusText(QWidget *w, const QString &str); + + QPointer group; + QString text; + QString iconText; + QIcon icon; + QString tooltip; + QString statustip; + QString whatsthis; +#ifndef QT_NO_SHORTCUT + QKeySequence shortcut; + QList alternateShortcuts; +#endif + QVariant userData; +#ifndef QT_NO_SHORTCUT + int shortcutId; + QList alternateShortcutIds; + Qt::ShortcutContext shortcutContext; + uint autorepeat : 1; +#endif + QFont font; + QPointer menu; + uint enabled : 1, forceDisabled : 1; + uint visible : 1, forceInvisible : 1; + uint checkable : 1; + uint checked : 1; + uint separator : 1; + uint fontSet : 1; + + //for soft keys management + uint forceEnabledInSoftkeys : 1; + uint menuActionSoftkeys : 1; + int iconVisibleInMenu : 3; // Only has values -1, 0, and 1 + + QAction::MenuRole menuRole; + QAction::SoftKeyRole softKeyRole; + QAction::Priority priority; + + QList widgets; +#ifndef QT_NO_GRAPHICSVIEW + QList graphicsWidgets; +#endif +#ifndef QT_NO_SHORTCUT + void redoGrab(QShortcutMap &map); + void redoGrabAlternate(QShortcutMap &map); + void setShortcutEnabled(bool enable, QShortcutMap &map); + + static QShortcutMap *globalMap; +#endif // QT_NO_SHORTCUT + +#ifdef QT3_SUPPORT //for menubar/menu compat + QMenuItemEmitter *act_signal; + int id, param; +#endif + void sendDataChanged(); +}; + +#endif // QT_NO_ACTION + +QT_END_NAMESPACE + +#endif // QACTION_P_H diff --git a/src/gui/kernel/qactiongroup.cpp b/src/gui/kernel/qactiongroup.cpp new file mode 100644 index 0000000000..95ea8afab7 --- /dev/null +++ b/src/gui/kernel/qactiongroup.cpp @@ -0,0 +1,422 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qactiongroup.h" + +#ifndef QT_NO_ACTION + +#include "qaction_p.h" +#include "qapplication.h" +#include "qevent.h" +#include "qlist.h" + +QT_BEGIN_NAMESPACE + +class QActionGroupPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QActionGroup) +public: + QActionGroupPrivate() : exclusive(1), enabled(1), visible(1) { } + QList actions; + QPointer current; + uint exclusive : 1; + uint enabled : 1; + uint visible : 1; + +private: + void _q_actionTriggered(); //private slot + void _q_actionChanged(); //private slot + void _q_actionHovered(); //private slot +}; + +void QActionGroupPrivate::_q_actionChanged() +{ + Q_Q(QActionGroup); + QAction *action = qobject_cast(q->sender()); + Q_ASSERT_X(action != 0, "QWidgetGroup::_q_actionChanged", "internal error"); + if(exclusive) { + if (action->isChecked()) { + if (action != current) { + if(current) + current->setChecked(false); + current = action; + } + } else if (action == current) { + current = 0; + } + } +} + +void QActionGroupPrivate::_q_actionTriggered() +{ + Q_Q(QActionGroup); + QAction *action = qobject_cast(q->sender()); + Q_ASSERT_X(action != 0, "QWidgetGroup::_q_actionTriggered", "internal error"); + emit q->triggered(action); + emit q->selected(action); +} + +void QActionGroupPrivate::_q_actionHovered() +{ + Q_Q(QActionGroup); + QAction *action = qobject_cast(q->sender()); + Q_ASSERT_X(action != 0, "QWidgetGroup::_q_actionHovered", "internal error"); + emit q->hovered(action); +} + +/*! + \class QActionGroup + \brief The QActionGroup class groups actions together. + + \ingroup mainwindow-classes + + In some situations it is useful to group actions together. For + example, if you have a \gui{Left Align} action, a \gui{Right + Align} action, a \gui{Justify} action, and a \gui{Center} action, + only one of these actions should be active at any one time. One + simple way of achieving this is to group the actions together in + an action group. + + Here's a example (from the \l{mainwindows/menus}{Menus} example): + + \snippet examples/mainwindows/menus/mainwindow.cpp 6 + + Here we create a new action group. Since the action group is + exclusive by default, only one of the actions in the group is + checked at any one time. + + \img qactiongroup-align.png Alignment options in a QMenu + + A QActionGroup emits an triggered() signal when one of its + actions is chosen. Each action in an action group emits its + triggered() signal as usual. + + As stated above, an action group is \l exclusive by default; it + ensures that only one checkable action is active at any one time. + If you want to group checkable actions without making them + exclusive, you can turn of exclusiveness by calling + setExclusive(false). + + Actions can be added to an action group using addAction(), but it + is usually more convenient to specify a group when creating + actions; this ensures that actions are automatically created with + a parent. Actions can be visually separated from each other by + adding a separator action to the group; create an action and use + QAction's \l {QAction::}{setSeparator()} function to make it + considered a separator. Action groups are added to widgets with + the QWidget::addActions() function. + + \sa QAction +*/ + +/*! + Constructs an action group for the \a parent object. + + The action group is exclusive by default. Call setExclusive(false) + to make the action group non-exclusive. +*/ +QActionGroup::QActionGroup(QObject* parent) : QObject(*new QActionGroupPrivate, parent) +{ +} + +/*! + Destroys the action group. +*/ +QActionGroup::~QActionGroup() +{ +} + +/*! + \fn QAction *QActionGroup::addAction(QAction *action) + + Adds the \a action to this group, and returns it. + + Normally an action is added to a group by creating it with the + group as its parent, so this function is not usually used. + + \sa QAction::setActionGroup() +*/ +QAction *QActionGroup::addAction(QAction* a) +{ + Q_D(QActionGroup); + if(!d->actions.contains(a)) { + d->actions.append(a); + QObject::connect(a, SIGNAL(triggered()), this, SLOT(_q_actionTriggered())); + QObject::connect(a, SIGNAL(changed()), this, SLOT(_q_actionChanged())); + QObject::connect(a, SIGNAL(hovered()), this, SLOT(_q_actionHovered())); + } + if(!a->d_func()->forceDisabled) { + a->setEnabled(d->enabled); + a->d_func()->forceDisabled = false; + } + if(!a->d_func()->forceInvisible) { + a->setVisible(d->visible); + a->d_func()->forceInvisible = false; + } + if(a->isChecked()) + d->current = a; + QActionGroup *oldGroup = a->d_func()->group; + if(oldGroup != this) { + if (oldGroup) + oldGroup->removeAction(a); + a->d_func()->group = this; + } + return a; +} + +/*! + Creates and returns an action with \a text. The newly created + action is a child of this action group. + + Normally an action is added to a group by creating it with the + group as parent, so this function is not usually used. + + \sa QAction::setActionGroup() +*/ +QAction *QActionGroup::addAction(const QString &text) +{ + return new QAction(text, this); +} + +/*! + Creates and returns an action with \a text and an \a icon. The + newly created action is a child of this action group. + + Normally an action is added to a group by creating it with the + group as its parent, so this function is not usually used. + + \sa QAction::setActionGroup() +*/ +QAction *QActionGroup::addAction(const QIcon &icon, const QString &text) +{ + return new QAction(icon, text, this); +} + +/*! + Removes the \a action from this group. The action will have no + parent as a result. + + \sa QAction::setActionGroup() +*/ +void QActionGroup::removeAction(QAction *action) +{ + Q_D(QActionGroup); + if (d->actions.removeAll(action)) { + if (action == d->current) + d->current = 0; + QObject::disconnect(action, SIGNAL(triggered()), this, SLOT(_q_actionTriggered())); + QObject::disconnect(action, SIGNAL(changed()), this, SLOT(_q_actionChanged())); + QObject::disconnect(action, SIGNAL(hovered()), this, SLOT(_q_actionHovered())); + action->d_func()->group = 0; + } +} + +/*! + Returns the list of this groups's actions. This may be empty. +*/ +QList QActionGroup::actions() const +{ + Q_D(const QActionGroup); + return d->actions; +} + +/*! + \property QActionGroup::exclusive + \brief whether the action group does exclusive checking + + If exclusive is true, only one checkable action in the action group + can ever be active at any time. If the user chooses another + checkable action in the group, the one they chose becomes active and + the one that was active becomes inactive. + + \sa QAction::checkable +*/ +void QActionGroup::setExclusive(bool b) +{ + Q_D(QActionGroup); + d->exclusive = b; +} + +bool QActionGroup::isExclusive() const +{ + Q_D(const QActionGroup); + return d->exclusive; +} + +/*! + \fn void QActionGroup::setDisabled(bool b) + + This is a convenience function for the \l enabled property, that + is useful for signals--slots connections. If \a b is true the + action group is disabled; otherwise it is enabled. +*/ + +/*! + \property QActionGroup::enabled + \brief whether the action group is enabled + + Each action in the group will be enabled or disabled unless it + has been explicitly disabled. + + \sa QAction::setEnabled() +*/ +void QActionGroup::setEnabled(bool b) +{ + Q_D(QActionGroup); + d->enabled = b; + for(QList::const_iterator it = d->actions.constBegin(); it != d->actions.constEnd(); ++it) { + if(!(*it)->d_func()->forceDisabled) { + (*it)->setEnabled(b); + (*it)->d_func()->forceDisabled = false; + } + } +} + +bool QActionGroup::isEnabled() const +{ + Q_D(const QActionGroup); + return d->enabled; +} + +/*! + Returns the currently checked action in the group, or 0 if none + are checked. +*/ +QAction *QActionGroup::checkedAction() const +{ + Q_D(const QActionGroup); + return d->current; +} + +/*! + \property QActionGroup::visible + \brief whether the action group is visible + + Each action in the action group will match the visible state of + this group unless it has been explicitly hidden. + + \sa QAction::setEnabled() +*/ +void QActionGroup::setVisible(bool b) +{ + Q_D(QActionGroup); + d->visible = b; + for(QList::Iterator it = d->actions.begin(); it != d->actions.end(); ++it) { + if(!(*it)->d_func()->forceInvisible) { + (*it)->setVisible(b); + (*it)->d_func()->forceInvisible = false; + } + } +} + +bool QActionGroup::isVisible() const +{ + Q_D(const QActionGroup); + return d->visible; +} + +/*! + \fn void QActionGroup::triggered(QAction *action) + + This signal is emitted when the given \a action in the action + group is activated by the user; for example, when the user clicks + a menu option, toolbar button, or presses an action's shortcut key + combination. + + Connect to this signal for command actions. + + \sa QAction::activate() +*/ + +/*! + \fn void QActionGroup::hovered(QAction *action) + + This signal is emitted when the given \a action in the action + group is highlighted by the user; for example, when the user + pauses with the cursor over a menu option, toolbar button, or + presses an action's shortcut key combination. + + \sa QAction::activate() +*/ + +/*! + \fn void QActionGroup::add(QAction* a) + + Use addAction() instead. +*/ + +/*! + \fn void QActionGroup::addSeparator() + + Normally you add a separator to the menus or widgets to which + actions are added, so this function is very rarely needed. + + \oldcode + actionGroup->addSeparator(); + \newcode + QAction *separator = new QAction(this); + separator->setSeparator(true); + actionGroup->addAction(separator); + \endcode +*/ + +/*! + \fn bool QActionGroup::addTo(QWidget *widget) + + \oldcode + actionGroup->addTo(widget); + \newcode + widget->addActions(actionGroup->actions()); + \endcode +*/ + +/*! + \fn void QActionGroup::selected(QAction *action); + + Use triggered() instead. + +*/ + +QT_END_NAMESPACE + +#include "moc_qactiongroup.cpp" + +#endif // QT_NO_ACTION diff --git a/src/gui/kernel/qactiongroup.h b/src/gui/kernel/qactiongroup.h new file mode 100644 index 0000000000..88f4fe8eac --- /dev/null +++ b/src/gui/kernel/qactiongroup.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QACTIONGROUP_H +#define QACTIONGROUP_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACTION + +class QActionGroupPrivate; + +class Q_GUI_EXPORT QActionGroup : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QActionGroup) + + Q_PROPERTY(bool exclusive READ isExclusive WRITE setExclusive) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible) + +public: + explicit QActionGroup(QObject* parent); + ~QActionGroup(); + + QAction *addAction(QAction* a); + QAction *addAction(const QString &text); + QAction *addAction(const QIcon &icon, const QString &text); + void removeAction(QAction *a); + QList actions() const; + + QAction *checkedAction() const; + bool isExclusive() const; + bool isEnabled() const; + bool isVisible() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void add(QAction* a) { addAction(a); } + inline QT3_SUPPORT void addSeparator() + { QAction *act = new QAction(this); act->setSeparator(true); addAction(act); } + inline QT3_SUPPORT bool addTo(QWidget *w) { w->addActions(actions()); return true; } +#endif + +public Q_SLOTS: + void setEnabled(bool); + inline void setDisabled(bool b) { setEnabled(!b); } + void setVisible(bool); + void setExclusive(bool); + +Q_SIGNALS: + void triggered(QAction *); + QT_MOC_COMPAT void selected(QAction *); + void hovered(QAction *); + +private: + Q_DISABLE_COPY(QActionGroup) + Q_PRIVATE_SLOT(d_func(), void _q_actionTriggered()) + Q_PRIVATE_SLOT(d_func(), void _q_actionChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_actionHovered()) +}; + +#endif // QT_NO_ACTION + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QACTIONGROUP_H diff --git a/src/gui/kernel/qapplication.cpp b/src/gui/kernel/qapplication.cpp new file mode 100644 index 0000000000..4096bf378b --- /dev/null +++ b/src/gui/kernel/qapplication.cpp @@ -0,0 +1,6139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qabstracteventdispatcher.h" +#include "qaccessible.h" +#include "qapplication.h" +#include "qclipboard.h" +#include "qcursor.h" +#include "qdesktopwidget.h" +#include "qdir.h" +#include "qevent.h" +#include "qfile.h" +#include "qfileinfo.h" +#include "qgraphicsscene.h" +#include "qhash.h" +#include "qset.h" +#include "qlayout.h" +#include "qsessionmanager.h" +#include "qstyle.h" +#include "qstylefactory.h" +#include "qtextcodec.h" +#include "qtranslator.h" +#include "qvariant.h" +#include "qwidget.h" +#include "qdnd_p.h" +#include "qcolormap.h" +#include "qdebug.h" +#include "private/qgraphicssystemfactory_p.h" +#include "private/qgraphicssystem_p.h" +#include "private/qstylesheetstyle_p.h" +#include "private/qstyle_p.h" +#include "qmessagebox.h" +#include + +#ifdef QT_GRAPHICSSYSTEM_RUNTIME +#include "private/qgraphicssystem_runtime_p.h" +#endif + +#include "qinputcontext.h" +#include "qkeymapper_p.h" + +#ifdef Q_WS_X11 +#include +#endif + +#if defined(Q_WS_X11) || defined(Q_OS_SYMBIAN) +#include "qinputcontextfactory.h" +#endif + +#include "qguiplatformplugin_p.h" + +#include +#include + +#include + +#include + +#if defined(Q_WS_X11) && !defined(QT_NO_EGL) +#include +#endif + +#include "qapplication_p.h" +#include "qevent_p.h" +#include "qwidget_p.h" + +#include "qapplication.h" + +#include "qgesture.h" +#include "private/qgesturemanager_p.h" + +#ifndef QT_NO_LIBRARY +#include "qlibrary.h" +#endif + +#ifdef Q_WS_WINCE +#include "qdatetime.h" +#include "qguifunctions_wince.h" +extern bool qt_wince_is_smartphone(); //qguifunctions_wince.cpp +extern bool qt_wince_is_mobile(); //qguifunctions_wince.cpp +extern bool qt_wince_is_pocket_pc(); //qguifunctions_wince.cpp +#endif + +#include "qdatetime.h" + +#ifdef QT_MAC_USE_COCOA +#include +#endif + +//#define ALIEN_DEBUG + +#if defined(Q_OS_SYMBIAN) +#include "qt_s60_p.h" +#endif + +static void initResources() +{ +#if defined(Q_WS_WINCE) + Q_INIT_RESOURCE_EXTERN(qstyle_wince) + Q_INIT_RESOURCE(qstyle_wince); +#elif defined(Q_OS_SYMBIAN) + Q_INIT_RESOURCE_EXTERN(qstyle_s60) + Q_INIT_RESOURCE(qstyle_s60); +#else + Q_INIT_RESOURCE_EXTERN(qstyle) + Q_INIT_RESOURCE(qstyle); +#endif + Q_INIT_RESOURCE_EXTERN(qmessagebox) + Q_INIT_RESOURCE(qmessagebox); +#if !defined(QT_NO_PRINTDIALOG) + Q_INIT_RESOURCE_EXTERN(qprintdialog) + Q_INIT_RESOURCE(qprintdialog); +#endif + +} + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT void qt_call_post_routines(); + +QApplication::Type qt_appType=QApplication::Tty; +QApplicationPrivate *QApplicationPrivate::self = 0; + +QInputContext *QApplicationPrivate::inputContext = 0; + +bool QApplicationPrivate::quitOnLastWindowClosed = true; + +#ifdef Q_WS_WINCE +int QApplicationPrivate::autoMaximizeThreshold = -1; +bool QApplicationPrivate::autoSipEnabled = false; +#else +bool QApplicationPrivate::autoSipEnabled = true; +#endif + +QApplicationPrivate::QApplicationPrivate(int &argc, char **argv, QApplication::Type type, int flags) + : QCoreApplicationPrivate(argc, argv, flags) +{ + application_type = type; + qt_appType = type; + +#ifndef QT_NO_SESSIONMANAGER + is_session_restored = false; +#endif + + quitOnLastWindowClosed = true; + +#ifdef QT3_SUPPORT + qt_compat_used = 0; + qt_compat_resolved = 0; + qt_tryAccelEvent = 0; + qt_tryComposeUnicode = 0; + qt_dispatchAccelEvent = 0; +#endif +#if defined(Q_WS_QWS) && !defined(QT_NO_DIRECTPAINTER) + directPainters = 0; +#endif + +#ifndef QT_NO_GESTURES + gestureManager = 0; + gestureWidget = 0; +#endif // QT_NO_GESTURES + +#if defined(Q_WS_X11) || defined(Q_WS_WIN) + move_cursor = 0; + copy_cursor = 0; + link_cursor = 0; +#endif +#if defined(Q_WS_WIN) + ignore_cursor = 0; +#endif + + if (!self) + self = this; +} + +QApplicationPrivate::~QApplicationPrivate() +{ + if (self == this) + self = 0; +} + +/*! + \class QApplication + \brief The QApplication class manages the GUI application's control + flow and main settings. + + QApplication contains the main event loop, where all events from the window + system and other sources are processed and dispatched. It also handles the + application's initialization, finalization, and provides session + management. In addition, QApplication handles most of the system-wide and + application-wide settings. + + For any GUI application using Qt, there is precisely \bold one QApplication + object, no matter whether the application has 0, 1, 2 or more windows at + any given time. For non-GUI Qt applications, use QCoreApplication instead, + as it does not depend on the \l QtGui library. + + The QApplication object is accessible through the instance() function that + returns a pointer equivalent to the global qApp pointer. + + QApplication's main areas of responsibility are: + \list + \o It initializes the application with the user's desktop settings + such as palette(), font() and doubleClickInterval(). It keeps + track of these properties in case the user changes the desktop + globally, for example through some kind of control panel. + + \o It performs event handling, meaning that it receives events + from the underlying window system and dispatches them to the + relevant widgets. By using sendEvent() and postEvent() you can + send your own events to widgets. + + \o It parses common command line arguments and sets its internal + state accordingly. See the \l{QApplication::QApplication()} + {constructor documentation} below for more details. + + \o It defines the application's look and feel, which is + encapsulated in a QStyle object. This can be changed at runtime + with setStyle(). + + \o It specifies how the application is to allocate colors. See + setColorSpec() for details. + + \o It provides localization of strings that are visible to the + user via translate(). + + \o It provides some magical objects like the desktop() and the + clipboard(). + + \o It knows about the application's windows. You can ask which + widget is at a certain position using widgetAt(), get a list of + topLevelWidgets() and closeAllWindows(), etc. + + \o It manages the application's mouse cursor handling, see + setOverrideCursor() + + \o On the X window system, it provides functions to flush and sync + the communication stream, see flushX() and syncX(). + + \o It provides support for sophisticated \l{Session Management} + {session management}. This makes it possible for applications + to terminate gracefully when the user logs out, to cancel a + shutdown process if termination isn't possible and even to + preserve the entire application's state for a future session. + See isSessionRestored(), sessionId() and commitData() and + saveState() for details. + \endlist + + Since the QApplication object does so much initialization, it \e{must} be + created before any other objects related to the user interface are created. + QApplication also deals with common command line arguments. Hence, it is + usually a good idea to create it \e before any interpretation or + modification of \c argv is done in the application itself. + + \table + \header + \o{2,1} Groups of functions + + \row + \o System settings + \o desktopSettingsAware(), + setDesktopSettingsAware(), + cursorFlashTime(), + setCursorFlashTime(), + doubleClickInterval(), + setDoubleClickInterval(), + setKeyboardInputInterval(), + wheelScrollLines(), + setWheelScrollLines(), + palette(), + setPalette(), + font(), + setFont(), + fontMetrics(). + + \row + \o Event handling + \o exec(), + processEvents(), + exit(), + quit(). + sendEvent(), + postEvent(), + sendPostedEvents(), + removePostedEvents(), + hasPendingEvents(), + notify(), + macEventFilter(), + qwsEventFilter(), + x11EventFilter(), + x11ProcessEvent(), + winEventFilter(). + + \row + \o GUI Styles + \o style(), + setStyle(). + + \row + \o Color usage + \o colorSpec(), + setColorSpec(), + qwsSetCustomColors(). + + \row + \o Text handling + \o installTranslator(), + removeTranslator() + translate(). + + \row + \o Widgets + \o allWidgets(), + topLevelWidgets(), + desktop(), + activePopupWidget(), + activeModalWidget(), + clipboard(), + focusWidget(), + activeWindow(), + widgetAt(). + + \row + \o Advanced cursor handling + \o overrideCursor(), + setOverrideCursor(), + restoreOverrideCursor(). + + \row + \o X Window System synchronization + \o flushX(), + syncX(). + + \row + \o Session management + \o isSessionRestored(), + sessionId(), + commitData(), + saveState(). + + \row + \o Miscellaneous + \o closeAllWindows(), + startingUp(), + closingDown(), + type(). + \endtable + + \sa QCoreApplication, QAbstractEventDispatcher, QEventLoop, QSettings +*/ + +/*! + \enum QApplication::Type + + \value Tty a console application + \value GuiClient a GUI client application + \value GuiServer a GUI server application (for Qt for Embedded Linux) +*/ + +/*! + \enum QApplication::ColorSpec + + \value NormalColor the default color allocation policy + \value CustomColor the same as NormalColor for X11; allocates colors + to a palette on demand under Windows + \value ManyColor the right choice for applications that use thousands of + colors + + See setColorSpec() for full details. +*/ + +/*! + \fn QWidget *QApplication::topLevelAt(const QPoint &point) + + Returns the top-level widget at the given \a point; returns 0 if + there is no such widget. +*/ + +/*! + \fn QWidget *QApplication::topLevelAt(int x, int y) + + \overload + + Returns the top-level widget at the point (\a{x}, \a{y}); returns + 0 if there is no such widget. +*/ + + +/* + The qt_init() and qt_cleanup() functions are implemented in the + qapplication_xyz.cpp file. +*/ + +void qt_init(QApplicationPrivate *priv, int type +#ifdef Q_WS_X11 + , Display *display = 0, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0 +#endif + ); +void qt_cleanup(); + +Qt::MouseButtons QApplicationPrivate::mouse_buttons = Qt::NoButton; +Qt::KeyboardModifiers QApplicationPrivate::modifier_buttons = Qt::NoModifier; + +QStyle *QApplicationPrivate::app_style = 0; // default application style +QString QApplicationPrivate::styleOverride; // style override + +#ifndef QT_NO_STYLE_STYLESHEET +QString QApplicationPrivate::styleSheet; // default application stylesheet +#endif +QPointer QApplicationPrivate::leaveAfterRelease = 0; + +int QApplicationPrivate::app_cspec = QApplication::NormalColor; +QPalette *QApplicationPrivate::app_pal = 0; // default application palette +QPalette *QApplicationPrivate::sys_pal = 0; // default system palette +QPalette *QApplicationPrivate::set_pal = 0; // default palette set by programmer + +QGraphicsSystem *QApplicationPrivate::graphics_system = 0; // default graphics system +#if defined(Q_WS_QPA) +QPlatformIntegration *QApplicationPrivate::platform_integration = 0; +#endif +QString QApplicationPrivate::graphics_system_name; // graphics system id - for delayed initialization +bool QApplicationPrivate::runtime_graphics_system = false; + +Q_GLOBAL_STATIC(QMutex, applicationFontMutex) +QFont *QApplicationPrivate::app_font = 0; // default application font +QFont *QApplicationPrivate::sys_font = 0; // default system font +QFont *QApplicationPrivate::set_font = 0; // default font set by programmer + +QIcon *QApplicationPrivate::app_icon = 0; +QWidget *QApplicationPrivate::main_widget = 0; // main application widget +QWidget *QApplicationPrivate::focus_widget = 0; // has keyboard input focus +QWidget *QApplicationPrivate::hidden_focus_widget = 0; // will get keyboard input focus after show() +QWidget *QApplicationPrivate::active_window = 0; // toplevel with keyboard focus +bool QApplicationPrivate::obey_desktop_settings = true; // use winsys resources +int QApplicationPrivate::cursor_flash_time = 1000; // text caret flash time +int QApplicationPrivate::mouse_double_click_time = 400; // mouse dbl click limit +int QApplicationPrivate::keyboard_input_time = 400; // keyboard input interval +#ifndef QT_NO_WHEELEVENT +int QApplicationPrivate::wheel_scroll_lines; // number of lines to scroll +#endif +bool qt_is_gui_used; +bool Q_GUI_EXPORT qt_tab_all_widgets = true; +bool qt_in_tab_key_event = false; +int qt_antialiasing_threshold = -1; +static int drag_time = 500; +#ifndef QT_GUI_DRAG_DISTANCE +#define QT_GUI_DRAG_DISTANCE 4 +#endif +#ifdef Q_OS_SYMBIAN +// The screens are a bit too small to for your thumb when using only 4 pixels drag distance. +static int drag_distance = 12; //XXX move to qplatformdefs.h +#else +static int drag_distance = QT_GUI_DRAG_DISTANCE; +#endif +static Qt::LayoutDirection layout_direction = Qt::LeftToRight; +QSize QApplicationPrivate::app_strut = QSize(0,0); // no default application strut +bool QApplicationPrivate::animate_ui = true; +bool QApplicationPrivate::animate_menu = false; +bool QApplicationPrivate::fade_menu = false; +bool QApplicationPrivate::animate_combo = false; +bool QApplicationPrivate::animate_tooltip = false; +bool QApplicationPrivate::fade_tooltip = false; +bool QApplicationPrivate::animate_toolbox = false; +bool QApplicationPrivate::widgetCount = false; +bool QApplicationPrivate::load_testability = false; +QString QApplicationPrivate::qmljs_debug_arguments; +#ifdef QT_KEYPAD_NAVIGATION +# ifdef Q_OS_SYMBIAN +Qt::NavigationMode QApplicationPrivate::navigationMode = Qt::NavigationModeKeypadDirectional; +# else +Qt::NavigationMode QApplicationPrivate::navigationMode = Qt::NavigationModeKeypadTabOrder; +# endif +QWidget *QApplicationPrivate::oldEditFocus = 0; +#endif + +bool qt_tabletChokeMouse = false; +static bool force_reverse = false; + +inline bool QApplicationPrivate::isAlien(QWidget *widget) +{ + if (!widget) + return false; +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + return !widget->isWindow() +# ifdef Q_BACKINGSTORE_SUBSURFACES + && !(widget->d_func()->maybeTopData() && widget->d_func()->maybeTopData()->windowSurface) +# endif + ; +#else + return !widget->internalWinId(); +#endif +} + +// ######## move to QApplicationPrivate +// Default application palettes and fonts (per widget type) +Q_GLOBAL_STATIC(PaletteHash, app_palettes) +PaletteHash *qt_app_palettes_hash() +{ + return app_palettes(); +} + +Q_GLOBAL_STATIC(FontHash, app_fonts) +FontHash *qt_app_fonts_hash() +{ + return app_fonts(); +} + +QWidgetList *QApplicationPrivate::popupWidgets = 0; // has keyboard input focus + +QDesktopWidget *qt_desktopWidget = 0; // root window widgets +#ifndef QT_NO_CLIPBOARD +QClipboard *qt_clipboard = 0; // global clipboard object +#endif +QWidgetList * qt_modal_stack=0; // stack of modal widgets + +/*! + \internal +*/ +void QApplicationPrivate::process_cmdline() +{ + // process platform-indep command line + if (!qt_is_gui_used || !argc) + return; + + int i, j; + + j = 1; + for (i=1; i= 0) { + session_key = session_id.mid(p +1); + session_id = session_id.left(p); + } + is_session_restored = true; + } +#endif +#ifndef QT_NO_STYLE_STYLESHEET + } else if (arg == "-stylesheet" && i < argc -1) { + styleSheet = QLatin1String("file:///"); + styleSheet.append(QString::fromLocal8Bit(argv[++i])); + } else if (arg.indexOf("-stylesheet=") != -1) { + styleSheet = QLatin1String("file:///"); + styleSheet.append(QString::fromLocal8Bit(arg.right(arg.length() - 12))); +#endif + } else if (qstrcmp(arg, "-reverse") == 0) { + force_reverse = true; + QApplication::setLayoutDirection(Qt::RightToLeft); + } else if (qstrcmp(arg, "-widgetcount") == 0) { + widgetCount = true; + } else if (qstrcmp(arg, "-testability") == 0) { + load_testability = true; + } else if (arg == "-graphicssystem" && i < argc-1) { + graphics_system_name = QString::fromLocal8Bit(argv[++i]); + } else { + argv[j++] = argv[i]; + } + if (!s.isEmpty()) { + if (app_style) { + delete app_style; + app_style = 0; + } + styleOverride = s; + } + } + + if(j < argc) { + argv[j] = 0; + argc = j; + } +} + +/*! + Initializes the window system and constructs an application object with + \a argc command line arguments in \a argv. + + \warning The data referred to by \a argc and \a argv must stay valid for + the entire lifetime of the QApplication object. In addition, \a argc must + be greater than zero and \a argv must contain at least one valid character + string. + + The global \c qApp pointer refers to this application object. Only one + application object should be created. + + This application object must be constructed before any \l{QPaintDevice} + {paint devices} (including widgets, pixmaps, bitmaps etc.). + + \note \a argc and \a argv might be changed as Qt removes command line + arguments that it recognizes. + + Qt debugging options (not available if Qt was compiled without the QT_DEBUG + flag defined): + \list + \o -nograb, tells Qt that it must never grab the mouse or the + keyboard. + \o -dograb (only under X11), running under a debugger can cause an + implicit -nograb, use -dograb to override. + \o -sync (only under X11), switches to synchronous mode for + debugging. + \endlist + + See \l{Debugging Techniques} for a more detailed explanation. + + All Qt programs automatically support the following command line options: + \list + \o -style= \e style, sets the application GUI style. Possible values + are \c motif, \c windows, and \c platinum. If you compiled Qt with + additional styles or have additional styles as plugins these will + be available to the \c -style command line option. + \o -style \e style, is the same as listed above. + \o -stylesheet= \e stylesheet, sets the application \l styleSheet. The + value must be a path to a file that contains the Style Sheet. + \note Relative URLs in the Style Sheet file are relative to the + Style Sheet file's path. + \o -stylesheet \e stylesheet, is the same as listed above. + \o -session= \e session, restores the application from an earlier + \l{Session Management}{session}. + \o -session \e session, is the same as listed above. + \o -widgetcount, prints debug message at the end about number of + widgets left undestroyed and maximum number of widgets existed at + the same time + \o -reverse, sets the application's layout direction to + Qt::RightToLeft + \o -graphicssystem, sets the backend to be used for on-screen widgets + and QPixmaps. Available options are \c{raster} and \c{opengl}. + \o -qmljsdebugger=, activates the QML/JS debugger with a specified port. + The value must be of format port:1234[,block], where block is optional + and will make the application wait until a debugger connects to it. + \endlist + + The X11 version of Qt supports some traditional X11 command line options: + \list + \o -display \e display, sets the X display (default is $DISPLAY). + \o -geometry \e geometry, sets the client geometry of the first window + that is shown. + \o -fn or \c -font \e font, defines the application font. The font + should be specified using an X logical font description. Note that + this option is ignored when Qt is built with fontconfig support enabled. + \o -bg or \c -background \e color, sets the default background color + and an application palette (light and dark shades are calculated). + \o -fg or \c -foreground \e color, sets the default foreground color. + \o -btn or \c -button \e color, sets the default button color. + \o -name \e name, sets the application name. + \o -title \e title, sets the application title. + \o -visual \c TrueColor, forces the application to use a TrueColor + visual on an 8-bit display. + \o -ncols \e count, limits the number of colors allocated in the color + cube on an 8-bit display, if the application is using the + QApplication::ManyColor color specification. If \e count is 216 + then a 6x6x6 color cube is used (i.e. 6 levels of red, 6 of green, + and 6 of blue); for other values, a cube approximately proportional + to a 2x3x1 cube is used. + \o -cmap, causes the application to install a private color map on an + 8-bit display. + \o -im, sets the input method server (equivalent to setting the + XMODIFIERS environment variable) + \o -inputstyle, defines how the input is inserted into the given + widget, e.g., \c onTheSpot makes the input appear directly in the + widget, while \c overTheSpot makes the input appear in a box + floating over the widget and is not inserted until the editing is + done. + \endlist + + \section1 X11 Notes + + If QApplication fails to open the X11 display, it will terminate + the process. This behavior is consistent with most X11 + applications. + + \sa arguments() +*/ + +QApplication::QApplication(int &argc, char **argv) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, 0x040000)) +{ Q_D(QApplication); d->construct(); } + +QApplication::QApplication(int &argc, char **argv, int _internal) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, _internal)) +{ Q_D(QApplication); d->construct(); } + + +/*! + Constructs an application object with \a argc command line arguments in + \a argv. If \a GUIenabled is true, a GUI application is constructed, + otherwise a non-GUI (console) application is created. + + \warning The data referred to by \a argc and \a argv must stay valid for + the entire lifetime of the QApplication object. In addition, \a argc must + be greater than zero and \a argv must contain at least one valid character + string. + + Set \a GUIenabled to false for programs without a graphical user interface + that should be able to run without a window system. + + On X11, the window system is initialized if \a GUIenabled is true. If + \a GUIenabled is false, the application does not connect to the X server. + On Windows and Mac OS, currently the window system is always initialized, + regardless of the value of GUIenabled. This may change in future versions + of Qt. + + The following example shows how to create an application that uses a + graphical interface when available. + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 0 +*/ + +QApplication::QApplication(int &argc, char **argv, bool GUIenabled ) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GUIenabled ? GuiClient : Tty, 0x040000)) +{ Q_D(QApplication); d->construct(); } + +QApplication::QApplication(int &argc, char **argv, bool GUIenabled , int _internal) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GUIenabled ? GuiClient : Tty, _internal)) +{ Q_D(QApplication); d->construct();} + + + +/*! + Constructs an application object with \a argc command line arguments in + \a argv. + + \warning The data referred to by \a argc and \a argv must stay valid for + the entire lifetime of the QApplication object. In addition, \a argc must + be greater than zero and \a argv must contain at least one valid character + string. + + With Qt for Embedded Linux, passing QApplication::GuiServer for \a type + makes this application the server (equivalent to running with the + \c -qws option). +*/ +QApplication::QApplication(int &argc, char **argv, Type type) + : QCoreApplication(*new QApplicationPrivate(argc, argv, type, 0x040000)) +{ Q_D(QApplication); d->construct(); } + +QApplication::QApplication(int &argc, char **argv, Type type , int _internal) + : QCoreApplication(*new QApplicationPrivate(argc, argv, type, _internal)) +{ Q_D(QApplication); d->construct(); } + +#if defined(Q_WS_X11) && !defined(QT_NO_EGL) +static int qt_matchLibraryName(dl_phdr_info *info, size_t, void *data) +{ + const char *name = static_cast(data); + return strstr(info->dlpi_name, name) != 0; +} +#endif + +/*! + \internal +*/ +void QApplicationPrivate::construct( +#ifdef Q_WS_X11 + Display *dpy, Qt::HANDLE visual, Qt::HANDLE cmap +#endif + ) +{ + initResources(); + + qt_is_gui_used = (qt_appType != QApplication::Tty); + process_cmdline(); + // the environment variable has the lowest precedence of runtime graphicssystem switches + if (graphics_system_name.isEmpty()) + graphics_system_name = QString::fromLocal8Bit(qgetenv("QT_GRAPHICSSYSTEM")); + +#if defined(Q_WS_X11) && !defined(QT_NO_EGL) + if (graphics_system_name.isEmpty()) { + bool linksWithMeeGoTouch = dl_iterate_phdr(qt_matchLibraryName, const_cast("libmeegotouchcore")); + bool linksWithMeeGoGraphicsSystemHelper = dl_iterate_phdr(qt_matchLibraryName, const_cast("libQtMeeGoGraphicsSystemHelper")); + + if (linksWithMeeGoTouch && !linksWithMeeGoGraphicsSystemHelper) { + qWarning("Running non-meego graphics system enabled MeeGo touch, forcing native graphicssystem\n"); + graphics_system_name = QLatin1String("native"); + } + } +#endif + + // Must be called before initialize() + qt_init(this, qt_appType +#ifdef Q_WS_X11 + , dpy, visual, cmap +#endif + ); + initialize(); + eventDispatcher->startingUp(); + +#ifdef QT_EVAL + extern void qt_gui_eval_init(uint); + qt_gui_eval_init(application_type); +#endif + +#if defined(Q_OS_SYMBIAN) && !defined(QT_NO_SYSTEMLOCALE) + symbianInit(); +#endif + +#ifndef QT_NO_LIBRARY + if(load_testability) { + QLibrary testLib(QLatin1String("qttestability")); + if (testLib.load()) { + typedef void (*TasInitialize)(void); + TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init"); +#ifdef Q_OS_SYMBIAN + // resolving method by name does not work on Symbian OS so need to use ordinal + if(!initFunction) { + initFunction = (TasInitialize)testLib.resolve("1"); + } +#endif + if (initFunction) { + initFunction(); + } else { + qCritical("Library qttestability resolve failed!"); + } + } else { + qCritical("Library qttestability load failed!"); + } + } + + //make sure the plugin is loaded + if (qt_is_gui_used) + qt_guiPlatformPlugin(); +#endif +} + +#if defined(Q_WS_X11) +// ### a string literal is a cont char* +// ### using it as a char* is wrong and could lead to segfaults +// ### if aargv is modified someday +// ########## make it work with argc == argv == 0 +static int aargc = 1; +static char *aargv[] = { (char*)"unknown", 0 }; + +/*! + \fn QApplication::QApplication(Display* display, Qt::HANDLE visual, Qt::HANDLE colormap) + + Creates an application, given an already open display \a display. If + \a visual and \a colormap are non-zero, the application will use those + values as the default Visual and Colormap contexts. + + \warning Qt only supports TrueColor visuals at depths higher than 8 + bits-per-pixel. + + This function is only available on X11. +*/ +QApplication::QApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE colormap) + : QCoreApplication(*new QApplicationPrivate(aargc, aargv, GuiClient, 0x040000)) +{ + if (! dpy) + qWarning("QApplication: Invalid Display* argument"); + Q_D(QApplication); + d->construct(dpy, visual, colormap); +} + +QApplication::QApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE colormap, int _internal) + : QCoreApplication(*new QApplicationPrivate(aargc, aargv, GuiClient, _internal)) +{ + if (! dpy) + qWarning("QApplication: Invalid Display* argument"); + Q_D(QApplication); + d->construct(dpy, visual, colormap); + QApplicationPrivate::app_compile_version = _internal; +} + +/*! + \fn QApplication::QApplication(Display *display, int &argc, char **argv, + Qt::HANDLE visual, Qt::HANDLE colormap) + + Creates an application, given an already open \a display and using \a argc + command line arguments in \a argv. If \a visual and \a colormap are + non-zero, the application will use those values as the default Visual + and Colormap contexts. + + \warning Qt only supports TrueColor visuals at depths higher than 8 + bits-per-pixel. + + This function is only available on X11. +*/ +QApplication::QApplication(Display *dpy, int &argc, char **argv, + Qt::HANDLE visual, Qt::HANDLE colormap) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, 0x040000)) +{ + if (! dpy) + qWarning("QApplication: Invalid Display* argument"); + Q_D(QApplication); + d->construct(dpy, visual, colormap); +} + +QApplication::QApplication(Display *dpy, int &argc, char **argv, + Qt::HANDLE visual, Qt::HANDLE colormap, int _internal) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, _internal)) +{ + if (! dpy) + qWarning("QApplication: Invalid Display* argument"); + Q_D(QApplication); + d->construct(dpy, visual, colormap); + QApplicationPrivate::app_compile_version = _internal; +} + +#endif // Q_WS_X11 + +extern void qInitDrawhelperAsm(); +extern void qInitImageConversions(); +extern int qRegisterGuiVariant(); +extern int qUnregisterGuiVariant(); +#ifndef QT_NO_STATEMACHINE +extern int qRegisterGuiStateMachine(); +extern int qUnregisterGuiStateMachine(); +#endif + +/*! + \fn void QApplicationPrivate::initialize() + + Initializes the QApplication object, called from the constructors. +*/ +void QApplicationPrivate::initialize() +{ + QWidgetPrivate::mapper = new QWidgetMapper; + QWidgetPrivate::allWidgets = new QWidgetSet; + +#if !defined(Q_WS_X11) && !defined(Q_WS_QWS) && !defined(Q_WS_QPA) + // initialize the graphics system - on X11 this is initialized inside + // qt_init() in qapplication_x11.cpp because of several reasons. + // On QWS, the graphics system is set by the QScreen plugin. + // We don't use graphics systems in Qt QPA + graphics_system = QGraphicsSystemFactory::create(graphics_system_name); +#endif + + if (qt_appType != QApplication::Tty) + (void) QApplication::style(); // trigger creation of application style + // trigger registering of QVariant's GUI types + qRegisterGuiVariant(); +#ifndef QT_NO_STATEMACHINE + // trigger registering of QStateMachine's GUI types + qRegisterGuiStateMachine(); +#endif + + is_app_running = true; // no longer starting up + + Q_Q(QApplication); +#ifndef QT_NO_SESSIONMANAGER + // connect to the session manager + session_manager = new QSessionManager(q, session_id, session_key); +#endif + + if (qgetenv("QT_USE_NATIVE_WINDOWS").toInt() > 0) + q->setAttribute(Qt::AA_NativeWindows); + +#ifdef Q_WS_WINCE +#ifdef QT_AUTO_MAXIMIZE_THRESHOLD + autoMaximizeThreshold = QT_AUTO_MAXIMIZE_THRESHOLD; +#else + if (qt_wince_is_mobile()) + autoMaximizeThreshold = 50; + else + autoMaximizeThreshold = -1; +#endif //QT_AUTO_MAXIMIZE_THRESHOLD +#endif //Q_WS_WINCE + + // Set up which span functions should be used in raster engine... + qInitDrawhelperAsm(); + // and QImage conversion functions + qInitImageConversions(); + +#ifndef QT_NO_WHEELEVENT + QApplicationPrivate::wheel_scroll_lines = 3; +#endif + + if (qt_is_gui_used) + initializeMultitouch(); +} + +/*! + Returns the type of application (\l Tty, GuiClient, or + GuiServer). The type is set when constructing the QApplication + object. +*/ +QApplication::Type QApplication::type() +{ + return qt_appType; +} + +/***************************************************************************** + Functions returning the active popup and modal widgets. + *****************************************************************************/ + +/*! + Returns the active popup widget. + + A popup widget is a special top-level widget that sets the \c + Qt::WType_Popup widget flag, e.g. the QMenu widget. When the application + opens a popup widget, all events are sent to the popup. Normal widgets and + modal widgets cannot be accessed before the popup widget is closed. + + Only other popup widgets may be opened when a popup widget is shown. The + popup widgets are organized in a stack. This function returns the active + popup widget at the top of the stack. + + \sa activeModalWidget(), topLevelWidgets() +*/ + +QWidget *QApplication::activePopupWidget() +{ + return QApplicationPrivate::popupWidgets && !QApplicationPrivate::popupWidgets->isEmpty() ? + QApplicationPrivate::popupWidgets->last() : 0; +} + + +/*! + Returns the active modal widget. + + A modal widget is a special top-level widget which is a subclass of QDialog + that specifies the modal parameter of the constructor as true. A modal + widget must be closed before the user can continue with other parts of the + program. + + Modal widgets are organized in a stack. This function returns the active + modal widget at the top of the stack. + + \sa activePopupWidget(), topLevelWidgets() +*/ + +QWidget *QApplication::activeModalWidget() +{ + return qt_modal_stack && !qt_modal_stack->isEmpty() ? qt_modal_stack->first() : 0; +} + +/*! + Cleans up any window system resources that were allocated by this + application. Sets the global variable \c qApp to 0. +*/ + +QApplication::~QApplication() +{ + Q_D(QApplication); + +#ifndef QT_NO_CLIPBOARD + // flush clipboard contents + if (qt_clipboard) { + QEvent event(QEvent::Clipboard); + QApplication::sendEvent(qt_clipboard, &event); + } +#endif + + //### this should probable be done even later + qt_call_post_routines(); + + // kill timers before closing down the dispatcher + d->toolTipWakeUp.stop(); + d->toolTipFallAsleep.stop(); + + d->eventDispatcher->closingDown(); + d->eventDispatcher = 0; + QApplicationPrivate::is_app_closing = true; + QApplicationPrivate::is_app_running = false; + + delete QWidgetPrivate::mapper; + QWidgetPrivate::mapper = 0; + + // delete all widgets + if (QWidgetPrivate::allWidgets) { + QWidgetSet *mySet = QWidgetPrivate::allWidgets; + QWidgetPrivate::allWidgets = 0; + for (QWidgetSet::ConstIterator it = mySet->constBegin(); it != mySet->constEnd(); ++it) { + register QWidget *w = *it; + if (!w->parent()) // window + w->destroy(true, true); + } + delete mySet; + } + + delete qt_desktopWidget; + qt_desktopWidget = 0; + +#ifndef QT_NO_CLIPBOARD + delete qt_clipboard; + qt_clipboard = 0; +#endif + +#if defined(Q_WS_X11) || defined(Q_WS_WIN) + delete d->move_cursor; d->move_cursor = 0; + delete d->copy_cursor; d->copy_cursor = 0; + delete d->link_cursor; d->link_cursor = 0; +#endif +#if defined(Q_WS_WIN) + delete d->ignore_cursor; d->ignore_cursor = 0; +#endif + + delete QApplicationPrivate::app_pal; + QApplicationPrivate::app_pal = 0; + delete QApplicationPrivate::sys_pal; + QApplicationPrivate::sys_pal = 0; + delete QApplicationPrivate::set_pal; + QApplicationPrivate::set_pal = 0; + app_palettes()->clear(); + + { + QMutexLocker locker(applicationFontMutex()); + delete QApplicationPrivate::app_font; + QApplicationPrivate::app_font = 0; + } + delete QApplicationPrivate::sys_font; + QApplicationPrivate::sys_font = 0; + delete QApplicationPrivate::set_font; + QApplicationPrivate::set_font = 0; + app_fonts()->clear(); + + delete QApplicationPrivate::app_style; + QApplicationPrivate::app_style = 0; + delete QApplicationPrivate::app_icon; + QApplicationPrivate::app_icon = 0; + delete QApplicationPrivate::graphics_system; + QApplicationPrivate::graphics_system = 0; +#ifndef QT_NO_CURSOR + d->cursor_list.clear(); +#endif + +#ifndef QT_NO_DRAGANDDROP + if (qt_is_gui_used) + delete QDragManager::self(); +#endif + + d->cleanupMultitouch(); + + qt_cleanup(); + + if (QApplicationPrivate::widgetCount) + qDebug("Widgets left: %i Max widgets: %i \n", QWidgetPrivate::instanceCounter, QWidgetPrivate::maxInstances); +#ifndef QT_NO_SESSIONMANAGER + delete d->session_manager; + d->session_manager = 0; +#endif //QT_NO_SESSIONMANAGER + + QApplicationPrivate::obey_desktop_settings = true; + QApplicationPrivate::cursor_flash_time = 1000; + QApplicationPrivate::mouse_double_click_time = 400; + QApplicationPrivate::keyboard_input_time = 400; + + drag_time = 500; + drag_distance = 4; + layout_direction = Qt::LeftToRight; + QApplicationPrivate::app_strut = QSize(0, 0); + QApplicationPrivate::animate_ui = true; + QApplicationPrivate::animate_menu = false; + QApplicationPrivate::fade_menu = false; + QApplicationPrivate::animate_combo = false; + QApplicationPrivate::animate_tooltip = false; + QApplicationPrivate::fade_tooltip = false; + QApplicationPrivate::widgetCount = false; + +#ifndef QT_NO_STATEMACHINE + // trigger unregistering of QStateMachine's GUI types + qUnregisterGuiStateMachine(); +#endif + // trigger unregistering of QVariant's GUI types + qUnregisterGuiVariant(); +} + + +/*! + \fn QWidget *QApplication::widgetAt(const QPoint &point) + + Returns the widget at global screen position \a point, or 0 if there is no + Qt widget there. + + This function can be slow. + + \sa QCursor::pos(), QWidget::grabMouse(), QWidget::grabKeyboard() +*/ +QWidget *QApplication::widgetAt(const QPoint &p) +{ + QWidget *window = QApplication::topLevelAt(p); + if (!window) + return 0; + + QWidget *child = 0; + + if (!window->testAttribute(Qt::WA_TransparentForMouseEvents)) + child = window->childAt(window->mapFromGlobal(p)); + + if (child) + return child; + + if (window->testAttribute(Qt::WA_TransparentForMouseEvents)) { + //shoot a hole in the widget and try once again, + //suboptimal on Qt for Embedded Linux where we do + //know the stacking order of the toplevels. + int x = p.x(); + int y = p.y(); + QRegion oldmask = window->mask(); + QPoint wpoint = window->mapFromGlobal(QPoint(x, y)); + QRegion newmask = (oldmask.isEmpty() ? QRegion(window->rect()) : oldmask) + - QRegion(wpoint.x(), wpoint.y(), 1, 1); + window->setMask(newmask); + QWidget *recurse = 0; + if (QApplication::topLevelAt(p) != window) // verify recursion will terminate + recurse = widgetAt(x, y); + if (oldmask.isEmpty()) + window->clearMask(); + else + window->setMask(oldmask); + return recurse; + } + return window; +} + +/*! + \fn QWidget *QApplication::widgetAt(int x, int y) + + \overload + + Returns the widget at global screen position (\a x, \a y), or 0 if there is + no Qt widget there. +*/ + +/*! + \fn void QApplication::setArgs(int argc, char **argv) + \internal +*/ + + + +/*! + \internal +*/ +bool QApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventList *postedEvents) +{ + if ((event->type() == QEvent::UpdateRequest +#ifdef QT3_SUPPORT + || event->type() == QEvent::LayoutHint +#endif + || event->type() == QEvent::LayoutRequest + || event->type() == QEvent::Resize + || event->type() == QEvent::Move + || event->type() == QEvent::LanguageChange + || event->type() == QEvent::UpdateSoftKeys + || event->type() == QEvent::InputMethod)) { + for (int i = 0; i < postedEvents->size(); ++i) { + const QPostEvent &cur = postedEvents->at(i); + if (cur.receiver != receiver || cur.event == 0 || cur.event->type() != event->type()) + continue; + if (cur.event->type() == QEvent::LayoutRequest +#ifdef QT3_SUPPORT + || cur.event->type() == QEvent::LayoutHint +#endif + || cur.event->type() == QEvent::UpdateRequest) { + ; + } else if (cur.event->type() == QEvent::Resize) { + ((QResizeEvent *)(cur.event))->s = ((QResizeEvent *)event)->s; + } else if (cur.event->type() == QEvent::Move) { + ((QMoveEvent *)(cur.event))->p = ((QMoveEvent *)event)->p; + } else if (cur.event->type() == QEvent::LanguageChange) { + ; + } else if (cur.event->type() == QEvent::UpdateSoftKeys) { + ; + } else if ( cur.event->type() == QEvent::InputMethod ) { + *(QInputMethodEvent *)(cur.event) = *(QInputMethodEvent *)event; + } else { + continue; + } + delete event; + return true; + } + return false; + } + return QCoreApplication::compressEvent(event, receiver, postedEvents); +} + +/*! + \property QApplication::styleSheet + \brief the application style sheet + \since 4.2 + + By default, this property returns an empty string unless the user specifies + the \c{-stylesheet} option on the command line when running the application. + + \sa QWidget::setStyle(), {Qt Style Sheets} +*/ + +/*! + \property QApplication::autoMaximizeThreshold + \since 4.4 + \brief defines a threshold for auto maximizing widgets + + \bold{The auto maximize threshold is only available as part of Qt for + Windows CE.} + + This property defines a threshold for the size of a window as a percentage + of the screen size. If the minimum size hint of a window exceeds the + threshold, calling show() will cause the window to be maximized + automatically. + + Setting the threshold to 100 or greater means that the widget will always + be maximized. Alternatively, setting the threshold to 50 means that the + widget will be maximized only if the vertical minimum size hint is at least + 50% of the vertical screen size. + + Setting the threshold to -1 disables the feature. + + On Windows CE the default is -1 (i.e., it is disabled). + On Windows Mobile the default is 40. +*/ + +/*! + \property QApplication::autoSipEnabled + \since 4.5 + \brief toggles automatic SIP (software input panel) visibility + + Set this property to \c true to automatically display the SIP when entering + widgets that accept keyboard input. This property only affects widgets with + the WA_InputMethodEnabled attribute set, and is typically used to launch + a virtual keyboard on devices which have very few or no keys. + + \bold{ The property only has an effect on platforms which use software input + panels, such as Windows CE and Symbian.} + + The default is platform dependent. +*/ + +#ifdef Q_WS_WINCE +void QApplication::setAutoMaximizeThreshold(const int threshold) +{ + QApplicationPrivate::autoMaximizeThreshold = threshold; +} + +int QApplication::autoMaximizeThreshold() const +{ + return QApplicationPrivate::autoMaximizeThreshold; +} +#endif + +void QApplication::setAutoSipEnabled(const bool enabled) +{ + QApplicationPrivate::autoSipEnabled = enabled; +} + +bool QApplication::autoSipEnabled() const +{ + return QApplicationPrivate::autoSipEnabled; +} + +#ifndef QT_NO_STYLE_STYLESHEET + +QString QApplication::styleSheet() const +{ + return QApplicationPrivate::styleSheet; +} + +void QApplication::setStyleSheet(const QString& styleSheet) +{ + QApplicationPrivate::styleSheet = styleSheet; + QStyleSheetStyle *proxy = qobject_cast(QApplicationPrivate::app_style); + if (styleSheet.isEmpty()) { // application style sheet removed + if (!proxy) + return; // there was no stylesheet before + setStyle(proxy->base); + } else if (proxy) { // style sheet update, just repolish + proxy->repolish(qApp); + } else { // stylesheet set the first time + QStyleSheetStyle *newProxy = new QStyleSheetStyle(QApplicationPrivate::app_style); + QApplicationPrivate::app_style->setParent(newProxy); + setStyle(newProxy); + } +} + +#endif // QT_NO_STYLE_STYLESHEET + +/*! + Returns the application's style object. + + \sa setStyle(), QStyle +*/ +QStyle *QApplication::style() +{ + if (QApplicationPrivate::app_style) + return QApplicationPrivate::app_style; + if (!qt_is_gui_used) { + Q_ASSERT(!"No style available in non-gui applications!"); + return 0; + } + + if (!QApplicationPrivate::app_style) { + // Compile-time search for default style + // + QString style; +#ifdef QT_BUILD_INTERNAL + QString envStyle = QString::fromLocal8Bit(qgetenv("QT_STYLE_OVERRIDE")); +#else + QString envStyle; +#endif + if (!QApplicationPrivate::styleOverride.isEmpty()) { + style = QApplicationPrivate::styleOverride; + } else if (!envStyle.isEmpty()) { + style = envStyle; + } else { + style = QApplicationPrivate::desktopStyleKey(); + } + + QStyle *&app_style = QApplicationPrivate::app_style; + app_style = QStyleFactory::create(style); + if (!app_style) { + QStringList styles = QStyleFactory::keys(); + for (int i = 0; i < styles.size(); ++i) { + if ((app_style = QStyleFactory::create(styles.at(i)))) + break; + } + } + if (!app_style) { + Q_ASSERT(!"No styles available!"); + return 0; + } + } + // take ownership of the style + QApplicationPrivate::app_style->setParent(qApp); + + if (!QApplicationPrivate::sys_pal) + QApplicationPrivate::setSystemPalette(QApplicationPrivate::app_style->standardPalette()); + if (QApplicationPrivate::set_pal) // repolish set palette with the new style + QApplication::setPalette(*QApplicationPrivate::set_pal); + +#ifndef QT_NO_STYLE_STYLESHEET + if (!QApplicationPrivate::styleSheet.isEmpty()) { + qApp->setStyleSheet(QApplicationPrivate::styleSheet); + } else +#endif + QApplicationPrivate::app_style->polish(qApp); + + return QApplicationPrivate::app_style; +} + +/*! + Sets the application's GUI style to \a style. Ownership of the style object + is transferred to QApplication, so QApplication will delete the style + object on application exit or when a new style is set and the old style is + still the parent of the application object. + + Example usage: + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 1 + + When switching application styles, the color palette is set back to the + initial colors or the system defaults. This is necessary since certain + styles have to adapt the color palette to be fully style-guide compliant. + + Setting the style before a palette has been se, i.e., before creating + QApplication, will cause the application to use QStyle::standardPalette() + for the palette. + + \warning Qt style sheets are currently not supported for custom QStyle + subclasses. We plan to address this in some future release. + + \sa style(), QStyle, setPalette(), desktopSettingsAware() +*/ +void QApplication::setStyle(QStyle *style) +{ + if (!style || style == QApplicationPrivate::app_style) + return; + + QWidgetList all = allWidgets(); + + // clean up the old style + if (QApplicationPrivate::app_style) { + if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) { + for (QWidgetList::ConstIterator it = all.constBegin(); it != all.constEnd(); ++it) { + register QWidget *w = *it; + if (!(w->windowType() == Qt::Desktop) && // except desktop + w->testAttribute(Qt::WA_WState_Polished)) { // has been polished + QApplicationPrivate::app_style->unpolish(w); + } + } + } + QApplicationPrivate::app_style->unpolish(qApp); + } + + QStyle *old = QApplicationPrivate::app_style; // save + +#ifndef QT_NO_STYLE_STYLESHEET + if (!QApplicationPrivate::styleSheet.isEmpty() && !qobject_cast(style)) { + // we have a stylesheet already and a new style is being set + QStyleSheetStyle *newProxy = new QStyleSheetStyle(style); + style->setParent(newProxy); + QApplicationPrivate::app_style = newProxy; + } else +#endif // QT_NO_STYLE_STYLESHEET + QApplicationPrivate::app_style = style; + QApplicationPrivate::app_style->setParent(qApp); // take ownership + + // take care of possible palette requirements of certain gui + // styles. Do it before polishing the application since the style + // might call QApplication::setPalette() itself + if (QApplicationPrivate::set_pal) { + QApplication::setPalette(*QApplicationPrivate::set_pal); + } else if (QApplicationPrivate::sys_pal) { + QApplicationPrivate::initializeWidgetPaletteHash(); + QApplicationPrivate::setPalette_helper(*QApplicationPrivate::sys_pal, /*className=*/0, /*clearWidgetPaletteHash=*/false); + } else if (!QApplicationPrivate::sys_pal) { + // Initialize the sys_pal if it hasn't happened yet... + QApplicationPrivate::setSystemPalette(QApplicationPrivate::app_style->standardPalette()); + } + + // initialize the application with the new style + QApplicationPrivate::app_style->polish(qApp); + + // re-polish existing widgets if necessary + if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) { + for (QWidgetList::ConstIterator it1 = all.constBegin(); it1 != all.constEnd(); ++it1) { + register QWidget *w = *it1; + if (w->windowType() != Qt::Desktop && w->testAttribute(Qt::WA_WState_Polished)) { + if (w->style() == QApplicationPrivate::app_style) + QApplicationPrivate::app_style->polish(w); // repolish +#ifndef QT_NO_STYLE_STYLESHEET + else + w->setStyleSheet(w->styleSheet()); // touch +#endif + } + } + + for (QWidgetList::ConstIterator it2 = all.constBegin(); it2 != all.constEnd(); ++it2) { + register QWidget *w = *it2; + if (w->windowType() != Qt::Desktop && !w->testAttribute(Qt::WA_SetStyle)) { + QEvent e(QEvent::StyleChange); + QApplication::sendEvent(w, &e); +#ifdef QT3_SUPPORT + if (old) + w->styleChange(*old); +#endif + w->update(); + } + } + } + +#ifndef QT_NO_STYLE_STYLESHEET + if (QStyleSheetStyle *oldProxy = qobject_cast(old)) { + oldProxy->deref(); + } else +#endif + if (old && old->parent() == qApp) { + delete old; + } + + if (QApplicationPrivate::focus_widget) { + QFocusEvent in(QEvent::FocusIn, Qt::OtherFocusReason); + QApplication::sendEvent(QApplicationPrivate::focus_widget->style(), &in); + QApplicationPrivate::focus_widget->update(); + } +} + +/*! + \overload + + Requests a QStyle object for \a style from the QStyleFactory. + + The string must be one of the QStyleFactory::keys(), typically one of + "windows", "motif", "cde", "plastique", "windowsxp", or "macintosh". Style + names are case insensitive. + + Returns 0 if an unknown \a style is passed, otherwise the QStyle object + returned is set as the application's GUI style. + + \warning To ensure that the application's style is set correctly, it is + best to call this function before the QApplication constructor, if + possible. +*/ +QStyle* QApplication::setStyle(const QString& style) +{ + QStyle *s = QStyleFactory::create(style); + if (!s) + return 0; + + setStyle(s); + return s; +} + +/*! + \since 4.5 + + Sets the default graphics backend to \a system, which will be used for + on-screen widgets and QPixmaps. The available systems are \c{"native"}, + \c{"raster"} and \c{"opengl"}. + + There are several ways to set the graphics backend, in order of decreasing + precedence: + \list + \o the application commandline \c{-graphicssystem} switch + \o QApplication::setGraphicsSystem() + \o the QT_GRAPHICSSYSTEM environment variable + \o the Qt configure \c{-graphicssystem} switch + \endlist + If the highest precedence switch sets an invalid name, the error will be + ignored and the default backend will be used. + + \warning This function is only effective before the QApplication constructor + is called. + + \note The \c{"opengl"} option is currently experimental. +*/ + +void QApplication::setGraphicsSystem(const QString &system) +{ +#ifdef Q_WS_QPA + Q_UNUSED(system); +#else +# ifdef QT_GRAPHICSSYSTEM_RUNTIME + if (QApplicationPrivate::graphics_system_name == QLatin1String("runtime")) { + QRuntimeGraphicsSystem *r = + static_cast(QApplicationPrivate::graphics_system); + r->setGraphicsSystem(system); + } else +# endif + QApplicationPrivate::graphics_system_name = system; +#endif +} + +/*! + Returns the color specification. + + \sa QApplication::setColorSpec() +*/ + +int QApplication::colorSpec() +{ + return QApplicationPrivate::app_cspec; +} + +/*! + Sets the color specification for the application to \a spec. + + The color specification controls how the application allocates colors when + run on a display with a limited amount of colors, e.g. 8 bit / 256 color + displays. + + The color specification must be set before you create the QApplication + object. + + The options are: + \list + \o QApplication::NormalColor. This is the default color allocation + strategy. Use this option if your application uses buttons, menus, + texts and pixmaps with few colors. With this option, the + application uses system global colors. This works fine for most + applications under X11, but on the Windows platform, it may cause + dithering of non-standard colors. + \o QApplication::CustomColor. Use this option if your application + needs a small number of custom colors. On X11, this option is the + same as NormalColor. On Windows, Qt creates a Windows palette, and + allocates colors to it on demand. + \o QApplication::ManyColor. Use this option if your application is + very color hungry, e.g., it requires thousands of colors. \br + Under X11 the effect is: + \list + \o For 256-color displays which have at best a 256 color true + color visual, the default visual is used, and colors are + allocated from a color cube. The color cube is the 6x6x6 + (216 color) "Web palette" (the red, green, and blue + components always have one of the following values: 0x00, + 0x33, 0x66, 0x99, 0xCC, or 0xFF), but the number of colors + can be changed by the \e -ncols option. The user can force + the application to use the true color visual with the + \l{QApplication::QApplication()}{-visual} option. + \o For 256-color displays which have a true color visual with + more than 256 colors, use that visual. Silicon Graphics X + servers this feature, for example. They provide an 8 bit + visual by default but can deliver true color when asked. + \endlist + On Windows, Qt creates a Windows palette, and fills it with a color + cube. + \endlist + + Be aware that the CustomColor and ManyColor choices may lead to colormap + flashing: The foreground application gets (most) of the available colors, + while the background windows will look less attractive. + + Example: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 2 + + \sa colorSpec() +*/ + +void QApplication::setColorSpec(int spec) +{ + if (qApp) + qWarning("QApplication::setColorSpec: This function must be " + "called before the QApplication object is created"); + QApplicationPrivate::app_cspec = spec; +} + +/*! + \property QApplication::globalStrut + \brief the minimum size that any GUI element that the user can interact + with should have + + For example, no button should be resized to be smaller than the global + strut size. The strut size should be considered when reimplementing GUI + controls that may be used on touch-screens or similar I/O devices. + + Example: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 3 + + By default, this property contains a QSize object with zero width and height. +*/ +QSize QApplication::globalStrut() +{ + return QApplicationPrivate::app_strut; +} + +void QApplication::setGlobalStrut(const QSize& strut) +{ + QApplicationPrivate::app_strut = strut; +} + +/*! + Returns the application palette. + + \sa setPalette(), QWidget::palette() +*/ +QPalette QApplication::palette() +{ + if (!QApplicationPrivate::app_pal) + QApplicationPrivate::app_pal = new QPalette(Qt::black); + return *QApplicationPrivate::app_pal; +} + +/*! + \fn QPalette QApplication::palette(const QWidget* widget) + \overload + + If a \a widget is passed, the default palette for the widget's class is + returned. This may or may not be the application palette. In most cases + there is no special palette for certain types of widgets, but one notable + exception is the popup menu under Windows, if the user has defined a + special background color for menus in the display settings. + + \sa setPalette(), QWidget::palette() +*/ +QPalette QApplication::palette(const QWidget* w) +{ + PaletteHash *hash = app_palettes(); + if (w && hash && hash->size()) { + QHash::ConstIterator it = hash->constFind(w->metaObject()->className()); + if (it != hash->constEnd()) + return *it; + for (it = hash->constBegin(); it != hash->constEnd(); ++it) { + if (w->inherits(it.key())) + return it.value(); + } + } + return palette(); +} + +/*! + \overload + + Returns the palette for widgets of the given \a className. + + \sa setPalette(), QWidget::palette() +*/ +QPalette QApplication::palette(const char *className) +{ + if (!QApplicationPrivate::app_pal) + palette(); + PaletteHash *hash = app_palettes(); + if (className && hash && hash->size()) { + QHash::ConstIterator it = hash->constFind(className); + if (it != hash->constEnd()) + return *it; + } + return *QApplicationPrivate::app_pal; +} + +void QApplicationPrivate::setPalette_helper(const QPalette &palette, const char* className, bool clearWidgetPaletteHash) +{ + QPalette pal = palette; + + if (QApplicationPrivate::app_style) + QApplicationPrivate::app_style->polish(pal); // NB: non-const reference + + bool all = false; + PaletteHash *hash = app_palettes(); + if (!className) { + if (QApplicationPrivate::app_pal && pal.isCopyOf(*QApplicationPrivate::app_pal)) + return; + if (!QApplicationPrivate::app_pal) + QApplicationPrivate::app_pal = new QPalette(pal); + else + *QApplicationPrivate::app_pal = pal; + if (hash && hash->size()) { + all = true; + if (clearWidgetPaletteHash) + hash->clear(); + } + } else if (hash) { + hash->insert(className, pal); + } + + if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) { + // Send ApplicationPaletteChange to qApp itself, and to the widgets. + QEvent e(QEvent::ApplicationPaletteChange); + QApplication::sendEvent(QApplication::instance(), &e); + + QWidgetList wids = QApplication::allWidgets(); + for (QWidgetList::ConstIterator it = wids.constBegin(); it != wids.constEnd(); ++it) { + register QWidget *w = *it; + if (all || (!className && w->isWindow()) || w->inherits(className)) // matching class + QApplication::sendEvent(w, &e); + } + + // Send to all scenes as well. +#ifndef QT_NO_GRAPHICSVIEW + QList &scenes = qApp->d_func()->scene_list; + for (QList::ConstIterator it = scenes.constBegin(); + it != scenes.constEnd(); ++it) { + QApplication::sendEvent(*it, &e); + } +#endif //QT_NO_GRAPHICSVIEW + } + if (!className && (!QApplicationPrivate::sys_pal || !palette.isCopyOf(*QApplicationPrivate::sys_pal))) { + if (!QApplicationPrivate::set_pal) + QApplicationPrivate::set_pal = new QPalette(palette); + else + *QApplicationPrivate::set_pal = palette; + } +} + +/*! + Changes the default application palette to \a palette. + + If \a className is passed, the change applies only to widgets that inherit + \a className (as reported by QObject::inherits()). If \a className is left + 0, the change affects all widgets, thus overriding any previously set class + specific palettes. + + The palette may be changed according to the current GUI style in + QStyle::polish(). + + \warning Do not use this function in conjunction with \l{Qt Style Sheets}. + When using style sheets, the palette of a widget can be customized using + the "color", "background-color", "selection-color", + "selection-background-color" and "alternate-background-color". + + \note Some styles do not use the palette for all drawing, for instance, if + they make use of native theme engines. This is the case for the Windows XP, + Windows Vista, and Mac OS X styles. + + \sa QWidget::setPalette(), palette(), QStyle::polish() +*/ + +void QApplication::setPalette(const QPalette &palette, const char* className) +{ + QApplicationPrivate::setPalette_helper(palette, className, /*clearWidgetPaletteHash=*/ true); +} + + + +void QApplicationPrivate::setSystemPalette(const QPalette &pal) +{ + QPalette adjusted; + +#if 0 + // adjust the system palette to avoid dithering + QColormap cmap = QColormap::instance(); + if (cmap.depths() > 4 && cmap.depths() < 24) { + for (int g = 0; g < QPalette::NColorGroups; g++) + for (int i = 0; i < QPalette::NColorRoles; i++) { + QColor color = pal.color((QPalette::ColorGroup)g, (QPalette::ColorRole)i); + color = cmap.colorAt(cmap.pixel(color)); + adjusted.setColor((QPalette::ColorGroup)g, (QPalette::ColorRole) i, color); + } + } +#else + adjusted = pal; +#endif + + if (!sys_pal) + sys_pal = new QPalette(adjusted); + else + *sys_pal = adjusted; + + + if (!QApplicationPrivate::set_pal) + QApplication::setPalette(*sys_pal); +} + +/*! + Returns the default application font. + + \sa fontMetrics(), QWidget::font() +*/ +QFont QApplication::font() +{ + QMutexLocker locker(applicationFontMutex()); + if (!QApplicationPrivate::app_font) + QApplicationPrivate::app_font = new QFont(QLatin1String("Helvetica")); + return *QApplicationPrivate::app_font; +} + +/*! + \overload + + Returns the default font for the \a widget. + + \sa fontMetrics(), QWidget::setFont() +*/ + +QFont QApplication::font(const QWidget *widget) +{ + FontHash *hash = app_fonts(); + +#ifdef Q_WS_MAC + // short circuit for small and mini controls + if (widget->testAttribute(Qt::WA_MacSmallSize)) { + return hash->value("QSmallFont"); + } else if (widget->testAttribute(Qt::WA_MacMiniSize)) { + return hash->value("QMiniFont"); + } +#endif + if (widget && hash && hash->size()) { + QHash::ConstIterator it = + hash->constFind(widget->metaObject()->className()); + if (it != hash->constEnd()) + return it.value(); + for (it = hash->constBegin(); it != hash->constEnd(); ++it) { + if (widget->inherits(it.key())) + return it.value(); + } + } + return font(); +} + +/*! + \overload + + Returns the font for widgets of the given \a className. + + \sa setFont(), QWidget::font() +*/ +QFont QApplication::font(const char *className) +{ + FontHash *hash = app_fonts(); + if (className && hash && hash->size()) { + QHash::ConstIterator it = hash->constFind(className); + if (it != hash->constEnd()) + return *it; + } + return font(); +} + + +/*! + Changes the default application font to \a font. If \a className is passed, + the change applies only to classes that inherit \a className (as reported + by QObject::inherits()). + + On application start-up, the default font depends on the window system. It + can vary depending on both the window system version and the locale. This + function lets you override the default font; but overriding may be a bad + idea because, for example, some locales need extra large fonts to support + their special characters. + + \warning Do not use this function in conjunction with \l{Qt Style Sheets}. + The font of an application can be customized using the "font" style sheet + property. To set a bold font for all QPushButtons, set the application + styleSheet() as "QPushButton { font: bold }" + + \sa font(), fontMetrics(), QWidget::setFont() +*/ + +void QApplication::setFont(const QFont &font, const char *className) +{ + bool all = false; + FontHash *hash = app_fonts(); + if (!className) { + QMutexLocker locker(applicationFontMutex()); + if (!QApplicationPrivate::app_font) + QApplicationPrivate::app_font = new QFont(font); + else + *QApplicationPrivate::app_font = font; + if (hash && hash->size()) { + all = true; + hash->clear(); + } + } else if (hash) { + hash->insert(className, font); + } + if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) { + // Send ApplicationFontChange to qApp itself, and to the widgets. + QEvent e(QEvent::ApplicationFontChange); + QApplication::sendEvent(QApplication::instance(), &e); + + QWidgetList wids = QApplication::allWidgets(); + for (QWidgetList::ConstIterator it = wids.constBegin(); it != wids.constEnd(); ++it) { + register QWidget *w = *it; + if (all || (!className && w->isWindow()) || w->inherits(className)) // matching class + sendEvent(w, &e); + } + +#ifndef QT_NO_GRAPHICSVIEW + // Send to all scenes as well. + QList &scenes = qApp->d_func()->scene_list; + for (QList::ConstIterator it = scenes.constBegin(); + it != scenes.constEnd(); ++it) { + QApplication::sendEvent(*it, &e); + } +#endif //QT_NO_GRAPHICSVIEW + } + if (!className && (!QApplicationPrivate::sys_font || !font.isCopyOf(*QApplicationPrivate::sys_font))) { + if (!QApplicationPrivate::set_font) + QApplicationPrivate::set_font = new QFont(font); + else + *QApplicationPrivate::set_font = font; + } +} + +/*! \internal +*/ +void QApplicationPrivate::setSystemFont(const QFont &font) +{ + if (!sys_font) + sys_font = new QFont(font); + else + *sys_font = font; + + if (!QApplicationPrivate::set_font) + QApplication::setFont(*sys_font); +} + +/*! \internal +*/ +QString QApplicationPrivate::desktopStyleKey() +{ + return qt_guiPlatformPlugin()->styleName(); +} + +/*! + \property QApplication::windowIcon + \brief the default window icon + + \sa QWidget::setWindowIcon(), {Setting the Application Icon} +*/ +QIcon QApplication::windowIcon() +{ + return QApplicationPrivate::app_icon ? *QApplicationPrivate::app_icon : QIcon(); +} + +void QApplication::setWindowIcon(const QIcon &icon) +{ + if (!QApplicationPrivate::app_icon) + QApplicationPrivate::app_icon = new QIcon(); + *QApplicationPrivate::app_icon = icon; + if (QApplicationPrivate::is_app_running && !QApplicationPrivate::is_app_closing) { +#ifdef Q_WS_MAC + void qt_mac_set_app_icon(const QPixmap &); //qapplication_mac.cpp + QSize size = QApplicationPrivate::app_icon->actualSize(QSize(128, 128)); + qt_mac_set_app_icon(QApplicationPrivate::app_icon->pixmap(size)); +#endif + QEvent e(QEvent::ApplicationWindowIconChange); + QWidgetList all = QApplication::allWidgets(); + for (QWidgetList::ConstIterator it = all.constBegin(); it != all.constEnd(); ++it) { + register QWidget *w = *it; + if (w->isWindow()) + sendEvent(w, &e); + } + } +} + +/*! + Returns a list of the top-level widgets (windows) in the application. + + \note Some of the top-level widgets may be hidden, for example a tooltip if + no tooltip is currently shown. + + Example: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 4 + + \sa allWidgets(), QWidget::isWindow(), QWidget::isHidden() +*/ +QWidgetList QApplication::topLevelWidgets() +{ + QWidgetList list; + QWidgetList all = allWidgets(); + + for (QWidgetList::ConstIterator it = all.constBegin(); it != all.constEnd(); ++it) { + QWidget *w = *it; + if (w->isWindow() && w->windowType() != Qt::Desktop) + list.append(w); + } + return list; +} + +/*! + Returns a list of all the widgets in the application. + + The list is empty (QList::isEmpty()) if there are no widgets. + + \note Some of the widgets may be hidden. + + Example: + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 5 + + \sa topLevelWidgets(), QWidget::isVisible() +*/ + +QWidgetList QApplication::allWidgets() +{ + if (QWidgetPrivate::allWidgets) + return QWidgetPrivate::allWidgets->toList(); + return QWidgetList(); +} + +/*! + Returns the application widget that has the keyboard input focus, or 0 if + no widget in this application has the focus. + + \sa QWidget::setFocus(), QWidget::hasFocus(), activeWindow(), focusChanged() +*/ + +QWidget *QApplication::focusWidget() +{ + return QApplicationPrivate::focus_widget; +} + +void QApplicationPrivate::setFocusWidget(QWidget *focus, Qt::FocusReason reason) +{ +#ifndef QT_NO_GRAPHICSVIEW + if (focus && focus->window()->graphicsProxyWidget()) + return; +#endif + + hidden_focus_widget = 0; + + if (focus != focus_widget) { + if (focus && focus->isHidden()) { + hidden_focus_widget = focus; + return; + } + + if (focus && (reason == Qt::BacktabFocusReason || reason == Qt::TabFocusReason) + && qt_in_tab_key_event) + focus->window()->setAttribute(Qt::WA_KeyboardFocusChange); + else if (focus && reason == Qt::ShortcutFocusReason) { + focus->window()->setAttribute(Qt::WA_KeyboardFocusChange); + } + QWidget *prev = focus_widget; + focus_widget = focus; +#ifndef QT_NO_IM + if (prev && ((reason != Qt::PopupFocusReason && reason != Qt::MenuBarFocusReason + && prev->testAttribute(Qt::WA_InputMethodEnabled)) + // Do reset the input context, in case the new focus widget won't accept keyboard input + // or it is not created fully yet. + || (focus_widget && (!focus_widget->testAttribute(Qt::WA_InputMethodEnabled) + || !focus_widget->testAttribute(Qt::WA_WState_Created))))) { + QInputContext *qic = prev->inputContext(); + if(qic) { + qic->reset(); + qic->setFocusWidget(0); + } + } +#endif //QT_NO_IM + + if(focus_widget) + focus_widget->d_func()->setFocus_sys(); + + if (reason != Qt::NoFocusReason) { + + //send events + if (prev) { +#ifdef QT_KEYPAD_NAVIGATION + if (QApplication::keypadNavigationEnabled()) { + if (prev->hasEditFocus() && reason != Qt::PopupFocusReason +#ifdef Q_OS_SYMBIAN + && reason != Qt::ActiveWindowFocusReason +#endif + ) + prev->setEditFocus(false); + } +#endif +#ifndef QT_NO_IM + if (focus) { + QInputContext *prevIc; + prevIc = prev->inputContext(); + if (prevIc && prevIc != focus->inputContext()) { + QEvent closeSIPEvent(QEvent::CloseSoftwareInputPanel); + QApplication::sendEvent(prev, &closeSIPEvent); + } + } +#endif + QFocusEvent out(QEvent::FocusOut, reason); + QPointer that = prev; + QApplication::sendEvent(prev, &out); + if (that) + QApplication::sendEvent(that->style(), &out); + } + if(focus && QApplicationPrivate::focus_widget == focus) { +#ifndef QT_NO_IM + if (focus->testAttribute(Qt::WA_InputMethodEnabled)) { + QInputContext *qic = focus->inputContext(); + if (qic && focus->testAttribute(Qt::WA_WState_Created) + && focus->isEnabled()) + qic->setFocusWidget(focus); + } +#endif //QT_NO_IM + QFocusEvent in(QEvent::FocusIn, reason); + QPointer that = focus; + QApplication::sendEvent(focus, &in); + if (that) + QApplication::sendEvent(that->style(), &in); + } + emit qApp->focusChanged(prev, focus_widget); + } + } +} + + +/*! + Returns the application top-level window that has the keyboard input focus, + or 0 if no application window has the focus. There might be an + activeWindow() even if there is no focusWidget(), for example if no widget + in that window accepts key events. + + \sa QWidget::setFocus(), QWidget::hasFocus(), focusWidget() +*/ + +QWidget *QApplication::activeWindow() +{ + return QApplicationPrivate::active_window; +} + +/*! + Returns display (screen) font metrics for the application font. + + \sa font(), setFont(), QWidget::fontMetrics(), QPainter::fontMetrics() +*/ + +QFontMetrics QApplication::fontMetrics() +{ + return desktop()->fontMetrics(); +} + + +/*! + Closes all top-level windows. + + This function is particularly useful for applications with many top-level + windows. It could, for example, be connected to a \gui{Exit} entry in the + \gui{File} menu: + + \snippet examples/mainwindows/mdi/mainwindow.cpp 0 + + The windows are closed in random order, until one window does not accept + the close event. The application quits when the last window was + successfully closed; this can be turned off by setting + \l quitOnLastWindowClosed to false. + + \sa quitOnLastWindowClosed, lastWindowClosed(), QWidget::close(), + QWidget::closeEvent(), lastWindowClosed(), quit(), topLevelWidgets(), + QWidget::isWindow() +*/ +void QApplication::closeAllWindows() +{ + bool did_close = true; + QWidget *w; + while ((w = activeModalWidget()) && did_close) { + if (!w->isVisible() || w->data->is_closing) + break; + did_close = w->close(); + } + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; did_close && i < list.size(); ++i) { + w = list.at(i); + if (w->isVisible() + && w->windowType() != Qt::Desktop + && !w->data->is_closing) { + did_close = w->close(); + list = QApplication::topLevelWidgets(); + i = -1; + } + } +} + +/*! + Displays a simple message box about Qt. The message includes the version + number of Qt being used by the application. + + This is useful for inclusion in the \gui Help menu of an application, as + shown in the \l{mainwindows/menus}{Menus} example. + + This function is a convenience slot for QMessageBox::aboutQt(). +*/ +void QApplication::aboutQt() +{ +#ifndef QT_NO_MESSAGEBOX + QMessageBox::aboutQt( +#ifdef Q_WS_MAC + 0 +#else + activeWindow() +#endif // Q_WS_MAC + ); +#endif // QT_NO_MESSAGEBOX +} + + +/*! + \fn void QApplication::lastWindowClosed() + + This signal is emitted from QApplication::exec() when the last visible + primary window (i.e. window with no parent) with the Qt::WA_QuitOnClose + attribute set is closed. + + By default, + + \list + \o this attribute is set for all widgets except transient windows such + as splash screens, tool windows, and popup menus + + \o QApplication implicitly quits when this signal is emitted. + \endlist + + This feature can be turned off by setting \l quitOnLastWindowClosed to + false. + + \sa QWidget::close() +*/ + +/*! + \since 4.1 + \fn void QApplication::focusChanged(QWidget *old, QWidget *now) + + This signal is emitted when the widget that has keyboard focus changed from + \a old to \a now, i.e., because the user pressed the tab-key, clicked into + a widget or changed the active window. Both \a old and \a now can be the + null-pointer. + + The signal is emitted after both widget have been notified about the change + through QFocusEvent. + + \sa QWidget::setFocus(), QWidget::clearFocus(), Qt::FocusReason +*/ + +/*! + \since 4.5 + \fn void QApplication::fontDatabaseChanged() + + This signal is emitted when application fonts are loaded or removed. + + \sa QFontDatabase::addApplicationFont(), + QFontDatabase::addApplicationFontFromData(), + QFontDatabase::removeAllApplicationFonts(), + QFontDatabase::removeApplicationFont() +*/ + +#ifndef QT_NO_TRANSLATION +static bool qt_detectRTLLanguage() +{ + return force_reverse ^ + (QApplication::tr("QT_LAYOUT_DIRECTION", + "Translate this string to the string 'LTR' in left-to-right" + " languages or to 'RTL' in right-to-left languages (such as Hebrew" + " and Arabic) to get proper widget layout.") == QLatin1String("RTL")); +} +#if defined(Q_WS_MAC) +static const char *application_menu_strings[] = { + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Services"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide %1"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide Others"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Show All"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1") + }; +QString qt_mac_applicationmenu_string(int type) +{ + QString menuString = QString::fromLatin1(application_menu_strings[type]); + QString translated = qApp->translate("QMenuBar", application_menu_strings[type]); + if (translated != menuString) + return translated; + else + return qApp->translate("MAC_APPLICATION_MENU", + application_menu_strings[type]); +} +#endif +#endif + +/*!\reimp + +*/ +bool QApplication::event(QEvent *e) +{ + Q_D(QApplication); + if(e->type() == QEvent::Close) { +#if defined(Q_OS_SYMBIAN) + // In order to have proper application-exit effects on Symbian, certain + // native APIs have to be called _before_ closing/destroying the widgets. + bool effectStarted = qt_beginFullScreenEffect(); +#endif + QCloseEvent *ce = static_cast(e); + ce->accept(); + closeAllWindows(); + + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (w->isVisible() && !(w->windowType() == Qt::Desktop) && !(w->windowType() == Qt::Popup) && + (!(w->windowType() == Qt::Dialog) || !w->parentWidget())) { + ce->ignore(); + break; + } + } + if (ce->isAccepted()) { + return true; + } else { +#if defined(Q_OS_SYMBIAN) + if (effectStarted) + qt_abortFullScreenEffect(); +#endif + } + } else if(e->type() == QEvent::LanguageChange) { +#ifndef QT_NO_TRANSLATION + setLayoutDirection(qt_detectRTLLanguage()?Qt::RightToLeft:Qt::LeftToRight); +#endif +#if defined(QT_MAC_USE_COCOA) + qt_mac_post_retranslateAppMenu(); +#endif + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (!(w->windowType() == Qt::Desktop)) + postEvent(w, new QEvent(QEvent::LanguageChange)); + } +#ifndef Q_OS_WIN + } else if (e->type() == QEvent::LocaleChange) { + // on Windows the event propagation is taken care by the + // WM_SETTINGCHANGE event handler. + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (!(w->windowType() == Qt::Desktop)) { + if (!w->testAttribute(Qt::WA_SetLocale)) + w->d_func()->setLocale_helper(QLocale(), true); + } + } +#endif + } else if (e->type() == QEvent::Timer) { + QTimerEvent *te = static_cast(e); + Q_ASSERT(te != 0); + if (te->timerId() == d->toolTipWakeUp.timerId()) { + d->toolTipWakeUp.stop(); + if (d->toolTipWidget) { + QWidget *w = d->toolTipWidget->window(); + // show tooltip if WA_AlwaysShowToolTips is set, or if + // any ancestor of d->toolTipWidget is the active + // window + bool showToolTip = w->testAttribute(Qt::WA_AlwaysShowToolTips); + while (w && !showToolTip) { + showToolTip = w->isActiveWindow(); + w = w->parentWidget(); + w = w ? w->window() : 0; + } + if (showToolTip) { + QHelpEvent e(QEvent::ToolTip, d->toolTipPos, d->toolTipGlobalPos); + QApplication::sendEvent(d->toolTipWidget, &e); + if (e.isAccepted()) + d->toolTipFallAsleep.start(2000, this); + } + } + } else if (te->timerId() == d->toolTipFallAsleep.timerId()) { + d->toolTipFallAsleep.stop(); + } + } + return QCoreApplication::event(e); +} +#if !defined(Q_WS_X11) + +// The doc and X implementation of this function is in qapplication_x11.cpp + +void QApplication::syncX() {} // do nothing + +#endif + +/*! + \fn Qt::WindowsVersion QApplication::winVersion() + + Use \l QSysInfo::WindowsVersion instead. +*/ + +/*! + \fn void QApplication::setActiveWindow(QWidget* active) + + Sets the active window to the \a active widget in response to a system + event. The function is called from the platform specific event handlers. + + \warning This function does \e not set the keyboard focus to the active + widget. Call QWidget::activateWindow() instead. + + It sets the activeWindow() and focusWidget() attributes and sends proper + \l{QEvent::WindowActivate}{WindowActivate}/\l{QEvent::WindowDeactivate} + {WindowDeactivate} and \l{QEvent::FocusIn}{FocusIn}/\l{QEvent::FocusOut} + {FocusOut} events to all appropriate widgets. The window will then be + painted in active state (e.g. cursors in line edits will blink), and it + will have tool tips enabled. + + \sa activeWindow(), QWidget::activateWindow() +*/ +void QApplication::setActiveWindow(QWidget* act) +{ + QWidget* window = act?act->window():0; + + if (QApplicationPrivate::active_window == window) + return; + +#ifndef QT_NO_GRAPHICSVIEW + if (window && window->graphicsProxyWidget()) { + // Activate the proxy's view->viewport() ? + return; + } +#endif + + QWidgetList toBeActivated; + QWidgetList toBeDeactivated; + + if (QApplicationPrivate::active_window) { + if (style()->styleHint(QStyle::SH_Widget_ShareActivation, 0, QApplicationPrivate::active_window)) { + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (w->isVisible() && w->isActiveWindow()) + toBeDeactivated.append(w); + } + } else { + toBeDeactivated.append(QApplicationPrivate::active_window); + } + } + +#if !defined(Q_WS_MAC) + QWidget *previousActiveWindow = QApplicationPrivate::active_window; +#endif + QApplicationPrivate::active_window = window; + + if (QApplicationPrivate::active_window) { + if (style()->styleHint(QStyle::SH_Widget_ShareActivation, 0, QApplicationPrivate::active_window)) { + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (w->isVisible() && w->isActiveWindow()) + toBeActivated.append(w); + } + } else { + toBeActivated.append(QApplicationPrivate::active_window); + } + + } + + // first the activation/deactivation events + QEvent activationChange(QEvent::ActivationChange); + QEvent windowActivate(QEvent::WindowActivate); + QEvent windowDeactivate(QEvent::WindowDeactivate); + +#if !defined(Q_WS_MAC) + if (!previousActiveWindow) { + QEvent appActivate(QEvent::ApplicationActivate); + sendSpontaneousEvent(qApp, &appActivate); + } +#endif + + for (int i = 0; i < toBeActivated.size(); ++i) { + QWidget *w = toBeActivated.at(i); + sendSpontaneousEvent(w, &windowActivate); + sendSpontaneousEvent(w, &activationChange); + } + +#ifdef QT_MAC_USE_COCOA + // In case the user clicked on a child window, we need to + // reestablish the stacking order of the window so + // it pops in front of other child windows in cocoa: + qt_cocoaStackChildWindowOnTopOfOtherChildren(window); +#endif + + for(int i = 0; i < toBeDeactivated.size(); ++i) { + QWidget *w = toBeDeactivated.at(i); + sendSpontaneousEvent(w, &windowDeactivate); + sendSpontaneousEvent(w, &activationChange); + } + +#if !defined(Q_WS_MAC) + if (!QApplicationPrivate::active_window) { + QEvent appDeactivate(QEvent::ApplicationDeactivate); + sendSpontaneousEvent(qApp, &appDeactivate); + } +#endif + + if (QApplicationPrivate::popupWidgets == 0) { // !inPopupMode() + // then focus events + if (!QApplicationPrivate::active_window && QApplicationPrivate::focus_widget) { + QApplicationPrivate::setFocusWidget(0, Qt::ActiveWindowFocusReason); + } else if (QApplicationPrivate::active_window) { + QWidget *w = QApplicationPrivate::active_window->focusWidget(); + if (w && w->isVisible() /*&& w->focusPolicy() != QWidget::NoFocus*/) + w->setFocus(Qt::ActiveWindowFocusReason); + else { + w = QApplicationPrivate::focusNextPrevChild_helper(QApplicationPrivate::active_window, true); + if (w) { + w->setFocus(Qt::ActiveWindowFocusReason); + } else { + // If the focus widget is not in the activate_window, clear the focus + w = QApplicationPrivate::focus_widget; + if (!w && QApplicationPrivate::active_window->focusPolicy() != Qt::NoFocus) + QApplicationPrivate::setFocusWidget(QApplicationPrivate::active_window, Qt::ActiveWindowFocusReason); + else if (!QApplicationPrivate::active_window->isAncestorOf(w)) + QApplicationPrivate::setFocusWidget(0, Qt::ActiveWindowFocusReason); + } + } + } + } +} + +/*!internal + * Helper function that returns the new focus widget, but does not set the focus reason. + * Returns 0 if a new focus widget could not be found. + * Shared with QGraphicsProxyWidgetPrivate::findFocusChild() +*/ +QWidget *QApplicationPrivate::focusNextPrevChild_helper(QWidget *toplevel, bool next) +{ + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + + QWidget *f = toplevel->focusWidget(); + if (!f) + f = toplevel; + + QWidget *w = f; + QWidget *test = f->d_func()->focus_next; + while (test && test != f) { + if ((test->focusPolicy() & focus_flag) == focus_flag + && !(test->d_func()->extra && test->d_func()->extra->focus_proxy) + && test->isVisibleTo(toplevel) && test->isEnabled() + && !(w->windowType() == Qt::SubWindow && !w->isAncestorOf(test)) + && (toplevel->windowType() != Qt::SubWindow || toplevel->isAncestorOf(test))) { + w = test; + if (next) + break; + } + test = test->d_func()->focus_next; + } + if (w == f) { + if (qt_in_tab_key_event) { + w->window()->setAttribute(Qt::WA_KeyboardFocusChange); + w->update(); + } + return 0; + } + return w; +} + +/*! + \fn void QApplicationPrivate::dispatchEnterLeave(QWidget* enter, QWidget* leave) + \internal + + Creates the proper Enter/Leave event when widget \a enter is entered and + widget \a leave is left. + */ +void QApplicationPrivate::dispatchEnterLeave(QWidget* enter, QWidget* leave) { +#if 0 + if (leave) { + QEvent e(QEvent::Leave); + QApplication::sendEvent(leave, & e); + } + if (enter) { + QEvent e(QEvent::Enter); + QApplication::sendEvent(enter, & e); + } + return; +#endif + + QWidget* w ; + if ((!enter && !leave) || (enter == leave)) + return; +#ifdef ALIEN_DEBUG + qDebug() << "QApplicationPrivate::dispatchEnterLeave, ENTER:" << enter << "LEAVE:" << leave; +#endif + QWidgetList leaveList; + QWidgetList enterList; + + bool sameWindow = leave && enter && leave->window() == enter->window(); + if (leave && !sameWindow) { + w = leave; + do { + leaveList.append(w); + } while (!w->isWindow() && (w = w->parentWidget())); + } + if (enter && !sameWindow) { + w = enter; + do { + enterList.prepend(w); + } while (!w->isWindow() && (w = w->parentWidget())); + } + if (sameWindow) { + int enterDepth = 0; + int leaveDepth = 0; + w = enter; + while (!w->isWindow() && (w = w->parentWidget())) + enterDepth++; + w = leave; + while (!w->isWindow() && (w = w->parentWidget())) + leaveDepth++; + QWidget* wenter = enter; + QWidget* wleave = leave; + while (enterDepth > leaveDepth) { + wenter = wenter->parentWidget(); + enterDepth--; + } + while (leaveDepth > enterDepth) { + wleave = wleave->parentWidget(); + leaveDepth--; + } + while (!wenter->isWindow() && wenter != wleave) { + wenter = wenter->parentWidget(); + wleave = wleave->parentWidget(); + } + + w = leave; + while (w != wleave) { + leaveList.append(w); + w = w->parentWidget(); + } + w = enter; + while (w != wenter) { + enterList.prepend(w); + w = w->parentWidget(); + } + } + + QEvent leaveEvent(QEvent::Leave); + for (int i = 0; i < leaveList.size(); ++i) { + w = leaveList.at(i); + if (!QApplication::activeModalWidget() || QApplicationPrivate::tryModalHelper(w, 0)) { +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_MAC) + if (leaveAfterRelease == w) + leaveAfterRelease = 0; +#endif + QApplication::sendEvent(w, &leaveEvent); + if (w->testAttribute(Qt::WA_Hover) && + (!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) { + Q_ASSERT(instance()); + QHoverEvent he(QEvent::HoverLeave, QPoint(-1, -1), w->mapFromGlobal(QApplicationPrivate::instance()->hoverGlobalPos)); + qApp->d_func()->notify_helper(w, &he); + } + } + } + QPoint posEnter = QCursor::pos(); + QEvent enterEvent(QEvent::Enter); + for (int i = 0; i < enterList.size(); ++i) { + w = enterList.at(i); + if (!QApplication::activeModalWidget() || QApplicationPrivate::tryModalHelper(w, 0)) { + QApplication::sendEvent(w, &enterEvent); + if (w->testAttribute(Qt::WA_Hover) && + (!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) { + QHoverEvent he(QEvent::HoverEnter, w->mapFromGlobal(posEnter), QPoint(-1, -1)); + qApp->d_func()->notify_helper(w, &he); + } + } + } + +#ifndef QT_NO_CURSOR + // Update cursor for alien/graphics widgets. + + const bool enterOnAlien = (enter && (isAlien(enter) || enter->testAttribute(Qt::WA_DontShowOnScreen))); +#if defined(Q_WS_X11) || defined(Q_WS_QPA) + //Whenever we leave an alien widget on X11, we need to reset its nativeParentWidget()'s cursor. + // This is not required on Windows as the cursor is reset on every single mouse move. + QWidget *parentOfLeavingCursor = 0; + for (int i = 0; i < leaveList.size(); ++i) { + w = leaveList.at(i); + if (!isAlien(w)) + break; + if (w->testAttribute(Qt::WA_SetCursor)) { + QWidget *parent = w->parentWidget(); + while (parent && parent->d_func()->data.in_destructor) + parent = parent->parentWidget(); + parentOfLeavingCursor = parent; + //continue looping, we need to find the downest alien widget with a cursor. + // (downest on the screen) + } + } + //check that we will not call qt_x11_enforce_cursor twice with the same native widget + if (parentOfLeavingCursor && (!enterOnAlien + || parentOfLeavingCursor->effectiveWinId() != enter->effectiveWinId())) { +#ifndef QT_NO_GRAPHICSVIEW + if (!parentOfLeavingCursor->window()->graphicsProxyWidget()) +#endif + { +#if defined(Q_WS_X11) + qt_x11_enforce_cursor(parentOfLeavingCursor,true); +#elif defined(Q_WS_QPA) + if (enter == QApplication::desktop()) { + qt_qpa_set_cursor(enter, true); + } else { + qt_qpa_set_cursor(parentOfLeavingCursor, true); + } +#endif + } + } +#endif + if (enterOnAlien) { + QWidget *cursorWidget = enter; + while (!cursorWidget->isWindow() && !cursorWidget->isEnabled()) + cursorWidget = cursorWidget->parentWidget(); + + if (!cursorWidget) + return; + +#ifndef QT_NO_GRAPHICSVIEW + if (cursorWidget->window()->graphicsProxyWidget()) { + QWidgetPrivate::nearestGraphicsProxyWidget(cursorWidget)->setCursor(cursorWidget->cursor()); + } else +#endif + { +#if defined(Q_WS_WIN) + qt_win_set_cursor(cursorWidget, true); +#elif defined(Q_WS_X11) + qt_x11_enforce_cursor(cursorWidget, true); +#elif defined(Q_OS_SYMBIAN) + qt_symbian_set_cursor(cursorWidget, true); +#elif defined(Q_WS_QPA) + qt_qpa_set_cursor(cursorWidget, true); +#endif + } + } +#endif +} + +/* exported for the benefit of testing tools */ +Q_GUI_EXPORT bool qt_tryModalHelper(QWidget *widget, QWidget **rettop) +{ + return QApplicationPrivate::tryModalHelper(widget, rettop); +} + +/*! \internal + Returns true if \a widget is blocked by a modal window. + */ +bool QApplicationPrivate::isBlockedByModal(QWidget *widget) +{ + widget = widget->window(); + if (!modalState()) + return false; + if (QApplication::activePopupWidget() == widget) + return false; + + for (int i = 0; i < qt_modal_stack->size(); ++i) { + QWidget *modalWidget = qt_modal_stack->at(i); + + { + // check if the active modal widget is our widget or a parent of our widget + QWidget *w = widget; + while (w) { + if (w == modalWidget) + return false; + w = w->parentWidget(); + } +#ifdef Q_WS_WIN + if ((widget->testAttribute(Qt::WA_WState_Created) || widget->data->winid) + && (modalWidget->testAttribute(Qt::WA_WState_Created) || modalWidget->data->winid) + && IsChild(modalWidget->data->winid, widget->data->winid)) + return false; +#endif + } + + Qt::WindowModality windowModality = modalWidget->windowModality(); + if (windowModality == Qt::NonModal) { + // determine the modality type if it hasn't been set on the + // modalWidget, this normally happens when waiting for a + // native dialog. use WindowModal if we are the child of a + // group leader; otherwise use ApplicationModal. + QWidget *m = modalWidget; + while (m && !m->testAttribute(Qt::WA_GroupLeader)) { + m = m->parentWidget(); + if (m) + m = m->window(); + } + windowModality = (m && m->testAttribute(Qt::WA_GroupLeader)) + ? Qt::WindowModal + : Qt::ApplicationModal; + } + + switch (windowModality) { + case Qt::ApplicationModal: + { + QWidget *groupLeaderForWidget = widget; + while (groupLeaderForWidget && !groupLeaderForWidget->testAttribute(Qt::WA_GroupLeader)) + groupLeaderForWidget = groupLeaderForWidget->parentWidget(); + + if (groupLeaderForWidget) { + // if \a widget has WA_GroupLeader, it can only be blocked by ApplicationModal children + QWidget *m = modalWidget; + while (m && m != groupLeaderForWidget && !m->testAttribute(Qt::WA_GroupLeader)) + m = m->parentWidget(); + if (m == groupLeaderForWidget) + return true; + } else if (modalWidget != widget) { + return true; + } + break; + } + case Qt::WindowModal: + { + QWidget *w = widget; + do { + QWidget *m = modalWidget; + do { + if (m == w) + return true; + m = m->parentWidget(); + if (m) + m = m->window(); + } while (m); + w = w->parentWidget(); + if (w) + w = w->window(); + } while (w); + break; + } + default: + Q_ASSERT_X(false, "QApplication", "internal error, a modal widget cannot be modeless"); + break; + } + } + return false; +} + +/*!\internal + */ +void QApplicationPrivate::enterModal(QWidget *widget) +{ + QSet blocked; + QList windows = QApplication::topLevelWidgets(); + for (int i = 0; i < windows.count(); ++i) { + QWidget *window = windows.at(i); + if (window->windowType() != Qt::Tool && isBlockedByModal(window)) + blocked.insert(window); + } + + enterModal_sys(widget); + + windows = QApplication::topLevelWidgets(); + QEvent e(QEvent::WindowBlocked); + for (int i = 0; i < windows.count(); ++i) { + QWidget *window = windows.at(i); + if (!blocked.contains(window) && window->windowType() != Qt::Tool && isBlockedByModal(window)) + QApplication::sendEvent(window, &e); + } +} + +/*!\internal + */ +void QApplicationPrivate::leaveModal(QWidget *widget) +{ + QSet blocked; + QList windows = QApplication::topLevelWidgets(); + for (int i = 0; i < windows.count(); ++i) { + QWidget *window = windows.at(i); + if (window->windowType() != Qt::Tool && isBlockedByModal(window)) + blocked.insert(window); + } + + leaveModal_sys(widget); + + windows = QApplication::topLevelWidgets(); + QEvent e(QEvent::WindowUnblocked); + for (int i = 0; i < windows.count(); ++i) { + QWidget *window = windows.at(i); + if(blocked.contains(window) && window->windowType() != Qt::Tool && !isBlockedByModal(window)) + QApplication::sendEvent(window, &e); + } +} + + + +/*!\internal + + Called from qapplication_\e{platform}.cpp, returns true + if the widget should accept the event. + */ +bool QApplicationPrivate::tryModalHelper(QWidget *widget, QWidget **rettop) +{ + QWidget *top = QApplication::activeModalWidget(); + if (rettop) + *rettop = top; + + // the active popup widget always gets the input event + if (QApplication::activePopupWidget()) + return true; + +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + top = QApplicationPrivate::tryModalHelper_sys(top); + if (rettop) + *rettop = top; +#endif + + return !isBlockedByModal(widget->window()); +} + +/* + \internal +*/ +QWidget *QApplicationPrivate::pickMouseReceiver(QWidget *candidate, const QPoint &globalPos, + QPoint &pos, QEvent::Type type, + Qt::MouseButtons buttons, QWidget *buttonDown, + QWidget *alienWidget) +{ + Q_ASSERT(candidate); + + QWidget *mouseGrabber = QWidget::mouseGrabber(); + if (((type == QEvent::MouseMove && buttons) || (type == QEvent::MouseButtonRelease)) + && !buttonDown && !mouseGrabber) { + return 0; + } + + if (alienWidget && alienWidget->internalWinId()) + alienWidget = 0; + + QWidget *receiver = candidate; + + if (!mouseGrabber) + mouseGrabber = (buttonDown && !isBlockedByModal(buttonDown)) ? buttonDown : alienWidget; + + if (mouseGrabber && mouseGrabber != candidate) { + receiver = mouseGrabber; + pos = receiver->mapFromGlobal(globalPos); +#ifdef ALIEN_DEBUG + qDebug() << " ** receiver adjusted to:" << receiver << "pos:" << pos; +#endif + } + + return receiver; + +} + +/* + \internal +*/ +bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event, + QWidget *alienWidget, QWidget *nativeWidget, + QWidget **buttonDown, QPointer &lastMouseReceiver, + bool spontaneous) +{ + Q_ASSERT(receiver); + Q_ASSERT(event); + Q_ASSERT(nativeWidget); + Q_ASSERT(buttonDown); + + if (alienWidget && !isAlien(alienWidget)) + alienWidget = 0; + + QPointer receiverGuard = receiver; + QPointer nativeGuard = nativeWidget; + QPointer alienGuard = alienWidget; + QPointer activePopupWidget = QApplication::activePopupWidget(); + + const bool graphicsWidget = nativeWidget->testAttribute(Qt::WA_DontShowOnScreen); + + if (*buttonDown) { + if (!graphicsWidget) { + // Register the widget that shall receive a leave event + // after the last button is released. + if ((alienWidget || !receiver->internalWinId()) && !leaveAfterRelease && !QWidget::mouseGrabber()) + leaveAfterRelease = *buttonDown; + if (event->type() == QEvent::MouseButtonRelease && !event->buttons()) + *buttonDown = 0; + } + } else if (lastMouseReceiver) { + // Dispatch enter/leave if we move: + // 1) from an alien widget to another alien widget or + // from a native widget to an alien widget (first OR case) + // 2) from an alien widget to a native widget (second OR case) + if ((alienWidget && alienWidget != lastMouseReceiver) + || (isAlien(lastMouseReceiver) && !alienWidget)) { + if (activePopupWidget) { + if (!QWidget::mouseGrabber()) + dispatchEnterLeave(alienWidget ? alienWidget : nativeWidget, lastMouseReceiver); + } else { + dispatchEnterLeave(receiver, lastMouseReceiver); + } + + } + } + +#ifdef ALIEN_DEBUG + qDebug() << "QApplicationPrivate::sendMouseEvent: receiver:" << receiver + << "pos:" << event->pos() << "alien" << alienWidget << "button down" + << *buttonDown << "last" << lastMouseReceiver << "leave after release" + << leaveAfterRelease; +#endif + + // We need this quard in case someone opens a modal dialog / popup. If that's the case + // leaveAfterRelease is set to null, but we shall not update lastMouseReceiver. + const bool wasLeaveAfterRelease = leaveAfterRelease != 0; + bool result; + if (spontaneous) + result = QApplication::sendSpontaneousEvent(receiver, event); + else + result = QApplication::sendEvent(receiver, event); + + if (!graphicsWidget && leaveAfterRelease && event->type() == QEvent::MouseButtonRelease + && !event->buttons() && QWidget::mouseGrabber() != leaveAfterRelease) { + // Dispatch enter/leave if: + // 1) the mouse grabber is an alien widget + // 2) the button is released on an alien widget + QWidget *enter = 0; + if (nativeGuard) + enter = alienGuard ? alienWidget : nativeWidget; + else // The receiver is typically deleted on mouse release with drag'n'drop. + enter = QApplication::widgetAt(event->globalPos()); + dispatchEnterLeave(enter, leaveAfterRelease); + leaveAfterRelease = 0; + lastMouseReceiver = enter; + } else if (!wasLeaveAfterRelease) { + if (activePopupWidget) { + if (!QWidget::mouseGrabber()) + lastMouseReceiver = alienGuard ? alienWidget : (nativeGuard ? nativeWidget : 0); + } else { + lastMouseReceiver = receiverGuard ? receiver : QApplication::widgetAt(event->globalPos()); + } + } + + return result; +} + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_MAC) || defined(Q_WS_QPA) +/* + This function should only be called when the widget changes visibility, i.e. + when the \a widget is shown, hidden or deleted. This function does nothing + if the widget is a top-level or native, i.e. not an alien widget. In that + case enter/leave events are genereated by the underlying windowing system. +*/ +extern QPointer qt_last_mouse_receiver; +extern QWidget *qt_button_down; +void QApplicationPrivate::sendSyntheticEnterLeave(QWidget *widget) +{ +#ifndef QT_NO_CURSOR +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + if (!widget || widget->isWindow()) + return; +#else + if (!widget || widget->internalWinId() || widget->isWindow()) + return; +#endif + const bool widgetInShow = widget->isVisible() && !widget->data->in_destructor; + if (!widgetInShow && widget != qt_last_mouse_receiver) + return; // Widget was not under the cursor when it was hidden/deleted. + + if (widgetInShow && widget->parentWidget()->data->in_show) + return; // Ingore recursive show. + + QWidget *mouseGrabber = QWidget::mouseGrabber(); + if (mouseGrabber && mouseGrabber != widget) + return; // Someone else has the grab; enter/leave should not occur. + + QWidget *tlw = widget->window(); + if (tlw->data->in_destructor || tlw->data->is_closing) + return; // Closing down the business. + + if (widgetInShow && (!qt_last_mouse_receiver || qt_last_mouse_receiver->window() != tlw)) + return; // Mouse cursor not inside the widget's top-level. + + const QPoint globalPos(QCursor::pos()); + QPoint pos = tlw->mapFromGlobal(globalPos); + + // Find the current widget under the mouse. If this function was called from + // the widget's destructor, we have to make sure childAt() doesn't take into + // account widgets that are about to be destructed. + QWidget *widgetUnderCursor = tlw->d_func()->childAt_helper(pos, widget->data->in_destructor); + if (!widgetUnderCursor) + widgetUnderCursor = tlw; + else + pos = widgetUnderCursor->mapFrom(tlw, pos); + + if (widgetInShow && widgetUnderCursor != widget && !widget->isAncestorOf(widgetUnderCursor)) + return; // Mouse cursor not inside the widget or any of its children. + + if (widget->data->in_destructor && qt_button_down == widget) + qt_button_down = 0; + + // Send enter/leave events followed by a mouse move on the entered widget. + QMouseEvent e(QEvent::MouseMove, pos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier); + sendMouseEvent(widgetUnderCursor, &e, widgetUnderCursor, tlw, &qt_button_down, qt_last_mouse_receiver); +#endif // QT_NO_CURSOR +} +#endif // Q_WS_WIN || Q_WS_X11 || Q_WS_MAC + +/*! + Returns the desktop widget (also called the root window). + + The desktop may be composed of multiple screens, so it would be incorrect, + for example, to attempt to \e center some widget in the desktop's geometry. + QDesktopWidget has various functions for obtaining useful geometries upon + the desktop, such as QDesktopWidget::screenGeometry() and + QDesktopWidget::availableGeometry(). + + On X11, it is also possible to draw on the desktop. +*/ +QDesktopWidget *QApplication::desktop() +{ + if (!qt_desktopWidget || // not created yet + !(qt_desktopWidget->windowType() == Qt::Desktop)) { // reparented away + qt_desktopWidget = new QDesktopWidget(); + } + return qt_desktopWidget; +} + +#ifndef QT_NO_CLIPBOARD +/*! + Returns a pointer to the application global clipboard. + + \note The QApplication object should already be constructed before + accessing the clipboard. +*/ +QClipboard *QApplication::clipboard() +{ + if (qt_clipboard == 0) { + if (!qApp) { + qWarning("QApplication: Must construct a QApplication before accessing a QClipboard"); + return 0; + } + qt_clipboard = new QClipboard(0); + } + return qt_clipboard; +} +#endif // QT_NO_CLIPBOARD + +/*! + Sets whether Qt should use the system's standard colors, fonts, etc., to + \a on. By default, this is true. + + This function must be called before creating the QApplication object, like + this: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 6 + + \sa desktopSettingsAware() +*/ +void QApplication::setDesktopSettingsAware(bool on) +{ + QApplicationPrivate::obey_desktop_settings = on; +} + +/*! + Returns true if Qt is set to use the system's standard colors, fonts, etc.; + otherwise returns false. The default is true. + + \sa setDesktopSettingsAware() +*/ +bool QApplication::desktopSettingsAware() +{ + return QApplicationPrivate::obey_desktop_settings; +} + +/*! + Returns the current state of the modifier keys on the keyboard. The current + state is updated sychronously as the event queue is emptied of events that + will spontaneously change the keyboard state (QEvent::KeyPress and + QEvent::KeyRelease events). + + It should be noted this may not reflect the actual keys held on the input + device at the time of calling but rather the modifiers as last reported in + one of the above events. If no keys are being held Qt::NoModifier is + returned. + + \sa mouseButtons() +*/ + +Qt::KeyboardModifiers QApplication::keyboardModifiers() +{ + return QApplicationPrivate::modifier_buttons; +} + +/*! + Returns the current state of the buttons on the mouse. The current state is + updated syncronously as the event queue is emptied of events that will + spontaneously change the mouse state (QEvent::MouseButtonPress and + QEvent::MouseButtonRelease events). + + It should be noted this may not reflect the actual buttons held on the + input device at the time of calling but rather the mouse buttons as last + reported in one of the above events. If no mouse buttons are being held + Qt::NoButton is returned. + + \sa keyboardModifiers() +*/ + +Qt::MouseButtons QApplication::mouseButtons() +{ + return QApplicationPrivate::mouse_buttons; +} + +/*! + \fn bool QApplication::isSessionRestored() const + + Returns true if the application has been restored from an earlier + \l{Session Management}{session}; otherwise returns false. + + \sa sessionId(), commitData(), saveState() +*/ + + +/*! + \fn QString QApplication::sessionId() const + + Returns the current \l{Session Management}{session's} identifier. + + If the application has been restored from an earlier session, this + identifier is the same as it was in that previous session. The session + identifier is guaranteed to be unique both for different applications + and for different instances of the same application. + + \sa isSessionRestored(), sessionKey(), commitData(), saveState() +*/ + +/*! + \fn QString QApplication::sessionKey() const + + Returns the session key in the current \l{Session Management}{session}. + + If the application has been restored from an earlier session, this key is + the same as it was when the previous session ended. + + The session key changes with every call of commitData() or saveState(). + + \sa isSessionRestored(), sessionId(), commitData(), saveState() +*/ +#ifndef QT_NO_SESSIONMANAGER +bool QApplication::isSessionRestored() const +{ + Q_D(const QApplication); + return d->is_session_restored; +} + +QString QApplication::sessionId() const +{ + Q_D(const QApplication); + return d->session_id; +} + +QString QApplication::sessionKey() const +{ + Q_D(const QApplication); + return d->session_key; +} +#endif + + + +/*! + \since 4.2 + \fn void QApplication::commitDataRequest(QSessionManager &manager) + + This signal deals with \l{Session Management}{session management}. It is + emitted when the QSessionManager wants the application to commit all its + data. + + Usually this means saving all open files, after getting permission from + the user. Furthermore you may want to provide a means by which the user + can cancel the shutdown. + + You should not exit the application within this signal. Instead, + the session manager may or may not do this afterwards, depending on the + context. + + \warning Within this signal, no user interaction is possible, \e + unless you ask the \a manager for explicit permission. See + QSessionManager::allowsInteraction() and + QSessionManager::allowsErrorInteraction() for details and example + usage. + + \note You should use Qt::DirectConnection when connecting to this signal. + + \sa isSessionRestored(), sessionId(), saveState(), {Session Management} +*/ + +/*! + This function deals with \l{Session Management}{session management}. It is + invoked when the QSessionManager wants the application to commit all its + data. + + Usually this means saving all open files, after getting permission from the + user. Furthermore you may want to provide a means by which the user can + cancel the shutdown. + + You should not exit the application within this function. Instead, the + session manager may or may not do this afterwards, depending on the + context. + + \warning Within this function, no user interaction is possible, \e + unless you ask the \a manager for explicit permission. See + QSessionManager::allowsInteraction() and + QSessionManager::allowsErrorInteraction() for details and example + usage. + + The default implementation requests interaction and sends a close event to + all visible top-level widgets. If any event was rejected, the shutdown is + canceled. + + \sa isSessionRestored(), sessionId(), saveState(), {Session Management} +*/ +#ifndef QT_NO_SESSIONMANAGER +void QApplication::commitData(QSessionManager& manager ) +{ + emit commitDataRequest(manager); + if (manager.allowsInteraction()) { + QWidgetList done; + QWidgetList list = QApplication::topLevelWidgets(); + bool cancelled = false; + for (int i = 0; !cancelled && i < list.size(); ++i) { + QWidget* w = list.at(i); + if (w->isVisible() && !done.contains(w)) { + cancelled = !w->close(); + if (!cancelled) + done.append(w); + list = QApplication::topLevelWidgets(); + i = -1; + } + } + if (cancelled) + manager.cancel(); + } +} + +/*! + \since 4.2 + \fn void QApplication::saveStateRequest(QSessionManager &manager) + + This signal deals with \l{Session Management}{session management}. It is + invoked when the \l{QSessionManager}{session manager} wants the application + to preserve its state for a future session. + + For example, a text editor would create a temporary file that includes the + current contents of its edit buffers, the location of the cursor and other + aspects of the current editing session. + + You should never exit the application within this signal. Instead, the + session manager may or may not do this afterwards, depending on the + context. Futhermore, most session managers will very likely request a saved + state immediately after the application has been started. This permits the + session manager to learn about the application's restart policy. + + \warning Within this function, no user interaction is possible, \e + unless you ask the \a manager for explicit permission. See + QSessionManager::allowsInteraction() and + QSessionManager::allowsErrorInteraction() for details. + + \note You should use Qt::DirectConnection when connecting to this signal. + + \sa isSessionRestored(), sessionId(), commitData(), {Session Management} +*/ + +/*! + This function deals with \l{Session Management}{session management}. It is + invoked when the \l{QSessionManager}{session manager} wants the application + to preserve its state for a future session. + + For example, a text editor would create a temporary file that includes the + current contents of its edit buffers, the location of the cursor and other + aspects of the current editing session. + + You should never exit the application within this function. Instead, the + session manager may or may not do this afterwards, depending on the + context. Futhermore, most session managers will very likely request a saved + state immediately after the application has been started. This permits the + session manager to learn about the application's restart policy. + + \warning Within this function, no user interaction is possible, \e + unless you ask the \a manager for explicit permission. See + QSessionManager::allowsInteraction() and + QSessionManager::allowsErrorInteraction() for details. + + \sa isSessionRestored(), sessionId(), commitData(), {Session Management} +*/ + +void QApplication::saveState(QSessionManager &manager) +{ + emit saveStateRequest(manager); +} +#endif //QT_NO_SESSIONMANAGER +/* + Sets the time after which a drag should start to \a ms ms. + + \sa startDragTime() +*/ + +void QApplication::setStartDragTime(int ms) +{ + drag_time = ms; +} + +/*! + \property QApplication::startDragTime + \brief the time in milliseconds that a mouse button must be held down + before a drag and drop operation will begin + + If you support drag and drop in your application, and want to start a drag + and drop operation after the user has held down a mouse button for a + certain amount of time, you should use this property's value as the delay. + + Qt also uses this delay internally, e.g. in QTextEdit and QLineEdit, for + starting a drag. + + The default value is 500 ms. + + \sa startDragDistance(), {Drag and Drop} +*/ + +int QApplication::startDragTime() +{ + return drag_time; +} + +/* + Sets the distance after which a drag should start to \a l pixels. + + \sa startDragDistance() +*/ + +void QApplication::setStartDragDistance(int l) +{ + drag_distance = l; +} + +/*! + \property QApplication::startDragDistance + + If you support drag and drop in your application, and want to start a drag + and drop operation after the user has moved the cursor a certain distance + with a button held down, you should use this property's value as the + minimum distance required. + + For example, if the mouse position of the click is stored in \c startPos + and the current position (e.g. in the mouse move event) is \c currentPos, + you can find out if a drag should be started with code like this: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 7 + + Qt uses this value internally, e.g. in QFileDialog. + + The default value is 4 pixels. + + \sa startDragTime() QPoint::manhattanLength() {Drag and Drop} +*/ + +int QApplication::startDragDistance() +{ + return drag_distance; +} + +/*! + \fn void QApplication::setReverseLayout(bool reverse) + + Use setLayoutDirection() instead. +*/ + +/*! + \fn void QApplication::reverseLayout() + + Use layoutDirection() instead. +*/ + +/*! + \fn bool QApplication::isRightToLeft() + + Returns true if the application's layout direction is + Qt::RightToLeft; otherwise returns false. + + \sa layoutDirection(), isLeftToRight() +*/ + +/*! + \fn bool QApplication::isLeftToRight() + + Returns true if the application's layout direction is + Qt::LeftToRight; otherwise returns false. + + \sa layoutDirection(), isRightToLeft() +*/ + +/*! + \property QApplication::layoutDirection + \brief the default layout direction for this application + + On system start-up, the default layout direction depends on the + application's language. + + \sa QWidget::layoutDirection, isLeftToRight(), isRightToLeft() + */ + +void QApplication::setLayoutDirection(Qt::LayoutDirection direction) +{ + if (layout_direction == direction || direction == Qt::LayoutDirectionAuto) + return; + + layout_direction = direction; + + QWidgetList list = topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + QEvent ev(QEvent::ApplicationLayoutDirectionChange); + sendEvent(w, &ev); + } +} + +Qt::LayoutDirection QApplication::layoutDirection() +{ + return layout_direction; +} + + +/*! + \obsolete + + Strips out vertical alignment flags and transforms an alignment \a align + of Qt::AlignLeft into Qt::AlignLeft or Qt::AlignRight according to the + language used. +*/ + +#ifdef QT3_SUPPORT +Qt::Alignment QApplication::horizontalAlignment(Qt::Alignment align) +{ + return QStyle::visualAlignment(layoutDirection(), align); +} +#endif + + +/*! + \fn QCursor *QApplication::overrideCursor() + + Returns the active application override cursor. + + This function returns 0 if no application cursor has been defined (i.e. the + internal cursor stack is empty). + + \sa setOverrideCursor(), restoreOverrideCursor() +*/ +#ifndef QT_NO_CURSOR +QCursor *QApplication::overrideCursor() +{ + return qApp->d_func()->cursor_list.isEmpty() ? 0 : &qApp->d_func()->cursor_list.first(); +} + +/*! + Changes the currently active application override cursor to \a cursor. + + This function has no effect if setOverrideCursor() was not called. + + \sa setOverrideCursor(), overrideCursor(), restoreOverrideCursor(), + QWidget::setCursor() + */ +void QApplication::changeOverrideCursor(const QCursor &cursor) +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + setOverrideCursor(cursor); +} +#endif + +/*! + \fn void QApplication::setOverrideCursor(const QCursor &cursor, bool replace) + + Use changeOverrideCursor(\a cursor) (if \a replace is true) or + setOverrideCursor(\a cursor) (if \a replace is false). +*/ + +/*! + Enters the main event loop and waits until exit() is called, then returns + the value that was set to exit() (which is 0 if exit() is called via + quit()). + + It is necessary to call this function to start event handling. The main + event loop receives events from the window system and dispatches these to + the application widgets. + + Generally, no user interaction can take place before calling exec(). As a + special case, modal widgets like QMessageBox can be used before calling + exec(), because modal widgets call exec() to start a local event loop. + + To make your application perform idle processing, i.e., executing a special + function whenever there are no pending events, use a QTimer with 0 timeout. + More advanced idle processing schemes can be achieved using processEvents(). + + We recommend that you connect clean-up code to the + \l{QCoreApplication::}{aboutToQuit()} signal, instead of putting it in your + application's \c{main()} function. This is because, on some platforms the + QApplication::exec() call may not return. For example, on the Windows + platform, when the user logs off, the system terminates the process after Qt + closes all top-level windows. Hence, there is \e{no guarantee} that the + application will have time to exit its event loop and execute code at the + end of the \c{main()} function, after the QApplication::exec() call. + + \sa quitOnLastWindowClosed, quit(), exit(), processEvents(), + QCoreApplication::exec() +*/ +int QApplication::exec() +{ +#ifndef QT_NO_ACCESSIBILITY + QAccessible::setRootObject(qApp); +#endif + return QCoreApplication::exec(); +} + +/*! \reimp + */ +bool QApplication::notify(QObject *receiver, QEvent *e) +{ + Q_D(QApplication); + // no events are delivered after ~QCoreApplication() has started + if (QApplicationPrivate::is_app_closing) + return true; + + if (receiver == 0) { // serious error + qWarning("QApplication::notify: Unexpected null receiver"); + return true; + } + +#ifndef QT_NO_DEBUG + d->checkReceiverThread(receiver); +#endif + + // capture the current mouse/keyboard state + if(e->spontaneous()) { + if (e->type() == QEvent::KeyPress + || e->type() == QEvent::KeyRelease) { + QKeyEvent *ke = static_cast(e); + QApplicationPrivate::modifier_buttons = ke->modifiers(); + } else if(e->type() == QEvent::MouseButtonPress + || e->type() == QEvent::MouseButtonRelease) { + QMouseEvent *me = static_cast(e); + QApplicationPrivate::modifier_buttons = me->modifiers(); + if(me->type() == QEvent::MouseButtonPress) + QApplicationPrivate::mouse_buttons |= me->button(); + else + QApplicationPrivate::mouse_buttons &= ~me->button(); + } +#if !defined(QT_NO_WHEELEVENT) || !defined(QT_NO_TABLETEVENT) + else if (false +# ifndef QT_NO_WHEELEVENT + || e->type() == QEvent::Wheel +# endif +# ifndef QT_NO_TABLETEVENT + || e->type() == QEvent::TabletMove + || e->type() == QEvent::TabletPress + || e->type() == QEvent::TabletRelease +# endif + ) { + QInputEvent *ie = static_cast(e); + QApplicationPrivate::modifier_buttons = ie->modifiers(); + } +#endif // !QT_NO_WHEELEVENT || !QT_NO_TABLETEVENT + } + +#ifndef QT_NO_GESTURES + // walk through parents and check for gestures + if (d->gestureManager) { + switch (e->type()) { + case QEvent::Paint: + case QEvent::MetaCall: + case QEvent::DeferredDelete: + case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: + case QEvent::Drop: case QEvent::DragResponse: + case QEvent::ChildAdded: case QEvent::ChildPolished: +#ifdef QT3_SUPPORT + case QEvent::ChildInsertedRequest: + case QEvent::ChildInserted: + case QEvent::LayoutHint: +#endif + case QEvent::ChildRemoved: + case QEvent::UpdateRequest: + case QEvent::UpdateLater: + case QEvent::AccessibilityPrepare: + case QEvent::LocaleChange: + case QEvent::Style: + case QEvent::IconDrag: + case QEvent::StyleChange: + case QEvent::AccessibilityHelp: + case QEvent::AccessibilityDescription: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDrop: + case QEvent::DynamicPropertyChange: + case QEvent::NetworkReplyUpdated: + break; + default: + if (receiver->isWidgetType()) { + if (d->gestureManager->filterEvent(static_cast(receiver), e)) + return true; + } else { + // a special case for events that go to QGesture objects. + // We pass the object to the gesture manager and it'll figure + // out if it's QGesture or not. + if (d->gestureManager->filterEvent(receiver, e)) + return true; + } + } + } +#endif // QT_NO_GESTURES + + // User input and window activation makes tooltips sleep + switch (e->type()) { + case QEvent::Wheel: + case QEvent::ActivationChange: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::FocusOut: + case QEvent::FocusIn: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + d->toolTipFallAsleep.stop(); + // fall-through + case QEvent::Leave: + d->toolTipWakeUp.stop(); + default: + break; + } + + bool res = false; + if (!receiver->isWidgetType()) { + res = d->notify_helper(receiver, e); + } else switch (e->type()) { +#if defined QT3_SUPPORT && !defined(QT_NO_SHORTCUT) + case QEvent::Accel: + { + if (d->use_compat()) { + QKeyEvent* key = static_cast(e); + res = d->notify_helper(receiver, e); + + if (!res && !key->isAccepted()) + res = d->qt_dispatchAccelEvent(static_cast(receiver), key); + + // next lines are for compatibility with Qt <= 3.0.x: old + // QAccel was listening on toplevel widgets + if (!res && !key->isAccepted() && !static_cast(receiver)->isWindow()) + res = d->notify_helper(static_cast(receiver)->window(), e); + } + break; + } +#endif //QT3_SUPPORT && !QT_NO_SHORTCUT + case QEvent::ShortcutOverride: + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + bool isWidget = receiver->isWidgetType(); + bool isGraphicsWidget = false; +#ifndef QT_NO_GRAPHICSVIEW + isGraphicsWidget = !isWidget && qobject_cast(receiver); +#endif + if (!isWidget && !isGraphicsWidget) { + res = d->notify_helper(receiver, e); + break; + } + + QKeyEvent* key = static_cast(e); +#if defined QT3_SUPPORT && !defined(QT_NO_SHORTCUT) + if (d->use_compat() && d->qt_tryComposeUnicode(static_cast(receiver), key)) + break; +#endif + if (key->type()==QEvent::KeyPress) { +#ifndef QT_NO_SHORTCUT + // Try looking for a Shortcut before sending key events + if ((res = qApp->d_func()->shortcutMap.tryShortcutEvent(receiver, key))) + return res; +#endif + qt_in_tab_key_event = (key->key() == Qt::Key_Backtab + || key->key() == Qt::Key_Tab + || key->key() == Qt::Key_Left + || key->key() == Qt::Key_Up + || key->key() == Qt::Key_Right + || key->key() == Qt::Key_Down); + } + bool def = key->isAccepted(); + QPointer pr = receiver; + while (receiver) { + if (def) + key->accept(); + else + key->ignore(); + res = d->notify_helper(receiver, e); + QWidget *w = isWidget ? static_cast(receiver) : 0; +#ifndef QT_NO_GRAPHICSVIEW + QGraphicsWidget *gw = isGraphicsWidget ? static_cast(receiver) : 0; +#endif + + if ((res && key->isAccepted()) + /* + QLineEdit will emit a signal on Key_Return, but + ignore the event, and sometimes the connected + slot deletes the QLineEdit (common in itemview + delegates), so we have to check if the widget + was destroyed even if the event was ignored (to + prevent a crash) + + note that we don't have to reset pw while + propagating (because the original receiver will + be destroyed if one of its ancestors is) + */ + || !pr + || (isWidget && (w->isWindow() || !w->parentWidget())) +#ifndef QT_NO_GRAPHICSVIEW + || (isGraphicsWidget && (gw->isWindow() || !gw->parentWidget())) +#endif + ) { + break; + } + +#ifndef QT_NO_GRAPHICSVIEW + receiver = w ? (QObject *)w->parentWidget() : (QObject *)gw->parentWidget(); +#else + receiver = w->parentWidget(); +#endif + } + qt_in_tab_key_event = false; + } + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + { + QWidget* w = static_cast(receiver); + + QMouseEvent* mouse = static_cast(e); + QPoint relpos = mouse->pos(); + + if (e->spontaneous()) { +#ifndef QT_NO_IM + QInputContext *ic = w->inputContext(); + if (ic + && w->testAttribute(Qt::WA_InputMethodEnabled) + && ic->filterEvent(mouse)) + return true; +#endif + + if (e->type() == QEvent::MouseButtonPress) { + QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, + Qt::ClickFocus, + Qt::MouseFocusReason); + } + + // ### Qt 5 These dynamic tool tips should be an OPT-IN feature. Some platforms + // like Mac OS X (probably others too), can optimize their views by not + // dispatching mouse move events. We have attributes to control hover, + // and mouse tracking, but as long as we are deciding to implement this + // feature without choice of opting-in or out, you ALWAYS have to have + // tracking enabled. Therefore, the other properties give a false sense of + // performance enhancement. + if (e->type() == QEvent::MouseMove && mouse->buttons() == 0) { + d->toolTipWidget = w; + d->toolTipPos = relpos; + d->toolTipGlobalPos = mouse->globalPos(); + d->toolTipWakeUp.start(d->toolTipFallAsleep.isActive()?20:700, this); + } + } + + bool eventAccepted = mouse->isAccepted(); + + QPointer pw = w; + while (w) { + QMouseEvent me(mouse->type(), relpos, mouse->globalPos(), mouse->button(), mouse->buttons(), + mouse->modifiers()); + me.spont = mouse->spontaneous(); + // throw away any mouse-tracking-only mouse events + if (!w->hasMouseTracking() + && mouse->type() == QEvent::MouseMove && mouse->buttons() == 0) { + // but still send them through all application event filters (normally done by notify_helper) + for (int i = 0; i < d->eventFilters.size(); ++i) { + register QObject *obj = d->eventFilters.at(i); + if (!obj) + continue; + if (obj->d_func()->threadData != w->d_func()->threadData) { + qWarning("QApplication: Object event filter cannot be in a different thread."); + continue; + } + if (obj->eventFilter(w, w == receiver ? mouse : &me)) + break; + } + res = true; + } else { + w->setAttribute(Qt::WA_NoMouseReplay, false); + res = d->notify_helper(w, w == receiver ? mouse : &me); + e->spont = false; + } + eventAccepted = (w == receiver ? mouse : &me)->isAccepted(); + if (res && eventAccepted) + break; + if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation)) + break; + relpos += w->pos(); + w = w->parentWidget(); + } + + mouse->setAccepted(eventAccepted); + + if (e->type() == QEvent::MouseMove) { + if (!pw) + break; + + w = static_cast(receiver); + relpos = mouse->pos(); + QPoint diff = relpos - w->mapFromGlobal(d->hoverGlobalPos); + while (w) { + if (w->testAttribute(Qt::WA_Hover) && + (!QApplication::activePopupWidget() || QApplication::activePopupWidget() == w->window())) { + QHoverEvent he(QEvent::HoverMove, relpos, relpos - diff); + d->notify_helper(w, &he); + } + if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation)) + break; + relpos += w->pos(); + w = w->parentWidget(); + } + } + + d->hoverGlobalPos = mouse->globalPos(); + } + break; +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: + { + QWidget* w = static_cast(receiver); + QWheelEvent* wheel = static_cast(e); + QPoint relpos = wheel->pos(); + bool eventAccepted = wheel->isAccepted(); + + if (e->spontaneous()) { + QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, + Qt::WheelFocus, + Qt::MouseFocusReason); + } + + while (w) { + QWheelEvent we(relpos, wheel->globalPos(), wheel->delta(), wheel->buttons(), + wheel->modifiers(), wheel->orientation()); + we.spont = wheel->spontaneous(); + res = d->notify_helper(w, w == receiver ? wheel : &we); + eventAccepted = ((w == receiver) ? wheel : &we)->isAccepted(); + e->spont = false; + if ((res && eventAccepted) + || w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation)) + break; + + relpos += w->pos(); + w = w->parentWidget(); + } + wheel->setAccepted(eventAccepted); + } + break; +#endif +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: + { + QWidget* w = static_cast(receiver); + QContextMenuEvent *context = static_cast(e); + QPoint relpos = context->pos(); + bool eventAccepted = context->isAccepted(); + while (w) { + QContextMenuEvent ce(context->reason(), relpos, context->globalPos(), context->modifiers()); + ce.spont = e->spontaneous(); + res = d->notify_helper(w, w == receiver ? context : &ce); + eventAccepted = ((w == receiver) ? context : &ce)->isAccepted(); + e->spont = false; + + if ((res && eventAccepted) + || w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation)) + break; + + relpos += w->pos(); + w = w->parentWidget(); + } + context->setAccepted(eventAccepted); + } + break; +#endif // QT_NO_CONTEXTMENU +#ifndef QT_NO_TABLETEVENT + case QEvent::TabletMove: + case QEvent::TabletPress: + case QEvent::TabletRelease: + { + QWidget *w = static_cast(receiver); + QTabletEvent *tablet = static_cast(e); + QPoint relpos = tablet->pos(); + bool eventAccepted = tablet->isAccepted(); + while (w) { + QTabletEvent te(tablet->type(), relpos, tablet->globalPos(), + tablet->hiResGlobalPos(), tablet->device(), tablet->pointerType(), + tablet->pressure(), tablet->xTilt(), tablet->yTilt(), + tablet->tangentialPressure(), tablet->rotation(), tablet->z(), + tablet->modifiers(), tablet->uniqueId()); + te.spont = e->spontaneous(); + res = d->notify_helper(w, w == receiver ? tablet : &te); + eventAccepted = ((w == receiver) ? tablet : &te)->isAccepted(); + e->spont = false; + if ((res && eventAccepted) + || w->isWindow() + || w->testAttribute(Qt::WA_NoMousePropagation)) + break; + + relpos += w->pos(); + w = w->parentWidget(); + } + tablet->setAccepted(eventAccepted); + qt_tabletChokeMouse = tablet->isAccepted(); + } + break; +#endif // QT_NO_TABLETEVENT + +#if !defined(QT_NO_TOOLTIP) || !defined(QT_NO_WHATSTHIS) + case QEvent::ToolTip: + case QEvent::WhatsThis: + case QEvent::QueryWhatsThis: + { + QWidget* w = static_cast(receiver); + QHelpEvent *help = static_cast(e); + QPoint relpos = help->pos(); + bool eventAccepted = help->isAccepted(); + while (w) { + QHelpEvent he(help->type(), relpos, help->globalPos()); + he.spont = e->spontaneous(); + res = d->notify_helper(w, w == receiver ? help : &he); + e->spont = false; + eventAccepted = (w == receiver ? help : &he)->isAccepted(); + if ((res && eventAccepted) || w->isWindow()) + break; + + relpos += w->pos(); + w = w->parentWidget(); + } + help->setAccepted(eventAccepted); + } + break; +#endif +#if !defined(QT_NO_STATUSTIP) || !defined(QT_NO_WHATSTHIS) + case QEvent::StatusTip: + case QEvent::WhatsThisClicked: + { + QWidget *w = static_cast(receiver); + while (w) { + res = d->notify_helper(w, e); + if ((res && e->isAccepted()) || w->isWindow()) + break; + w = w->parentWidget(); + } + } + break; +#endif + +#ifndef QT_NO_DRAGANDDROP + case QEvent::DragEnter: { + QWidget* w = static_cast(receiver); + QDragEnterEvent *dragEvent = static_cast(e); +#ifdef Q_WS_MAC + // HIView has a slight difference in how it delivers events to children and parents + // It will not give a leave to a child's parent when it enters a child. + QWidget *currentTarget = QDragManager::self()->currentTarget(); + if (currentTarget) { + // Assume currentTarget did not get a leave + QDragLeaveEvent event; + QApplication::sendEvent(currentTarget, &event); + } +#endif +#ifndef QT_NO_GRAPHICSVIEW + // QGraphicsProxyWidget handles its own propagation, + // and we must not change QDragManagers currentTarget. + QWExtra *extra = w->window()->d_func()->extra; + if (extra && extra->proxyWidget) { + res = d->notify_helper(w, dragEvent); + break; + } +#endif + while (w) { + if (w->isEnabled() && w->acceptDrops()) { + res = d->notify_helper(w, dragEvent); + if (res && dragEvent->isAccepted()) { + QDragManager::self()->setCurrentTarget(w); + break; + } + } + if (w->isWindow()) + break; + dragEvent->p = w->mapToParent(dragEvent->p); + w = w->parentWidget(); + } + } + break; + case QEvent::DragMove: + case QEvent::Drop: + case QEvent::DragLeave: { + QWidget* w = static_cast(receiver); +#ifndef QT_NO_GRAPHICSVIEW + // QGraphicsProxyWidget handles its own propagation, + // and we must not change QDragManagers currentTarget. + QWExtra *extra = w->window()->d_func()->extra; + bool isProxyWidget = extra && extra->proxyWidget; + if (!isProxyWidget) +#endif + w = QDragManager::self()->currentTarget(); + + if (!w) { +#ifdef Q_WS_MAC + // HIView has a slight difference in how it delivers events to children and parents + // It will not give an enter to a child's parent when it leaves the child. + if (e->type() == QEvent::DragLeave) + break; + // Assume that w did not get an enter. + QDropEvent *dropEvent = static_cast(e); + QDragEnterEvent dragEnterEvent(dropEvent->pos(), dropEvent->possibleActions(), + dropEvent->mimeData(), dropEvent->mouseButtons(), + dropEvent->keyboardModifiers()); + QApplication::sendEvent(receiver, &dragEnterEvent); + w = QDragManager::self()->currentTarget(); + if (!w) +#endif + break; + } + if (e->type() == QEvent::DragMove || e->type() == QEvent::Drop) { + QDropEvent *dragEvent = static_cast(e); + QWidget *origReciver = static_cast(receiver); + while (origReciver && w != origReciver) { + dragEvent->p = origReciver->mapToParent(dragEvent->p); + origReciver = origReciver->parentWidget(); + } + } + res = d->notify_helper(w, e); + if (e->type() != QEvent::DragMove +#ifndef QT_NO_GRAPHICSVIEW + && !isProxyWidget +#endif + ) + QDragManager::self()->setCurrentTarget(0, e->type() == QEvent::Drop); + } + break; +#endif + case QEvent::TouchBegin: + // Note: TouchUpdate and TouchEnd events are never propagated + { + QWidget *widget = static_cast(receiver); + QTouchEvent *touchEvent = static_cast(e); + bool eventAccepted = touchEvent->isAccepted(); + if (widget->testAttribute(Qt::WA_AcceptTouchEvents) && e->spontaneous()) { + // give the widget focus if the focus policy allows it + QApplicationPrivate::giveFocusAccordingToFocusPolicy(widget, + Qt::ClickFocus, + Qt::MouseFocusReason); + } + + while (widget) { + // first, try to deliver the touch event + bool acceptTouchEvents = widget->testAttribute(Qt::WA_AcceptTouchEvents); + touchEvent->setWidget(widget); + touchEvent->setAccepted(acceptTouchEvents); + QWeakPointer p = widget; + res = acceptTouchEvents && d->notify_helper(widget, touchEvent); + eventAccepted = touchEvent->isAccepted(); + if (p.isNull()) { + // widget was deleted + widget = 0; + } else { + widget->setAttribute(Qt::WA_WState_AcceptedTouchBeginEvent, res && eventAccepted); + } + touchEvent->spont = false; + if (res && eventAccepted) { + // the first widget to accept the TouchBegin gets an implicit grab. + for (int i = 0; i < touchEvent->touchPoints().count(); ++i) { + const QTouchEvent::TouchPoint &touchPoint = touchEvent->touchPoints().at(i); + d->widgetForTouchPointId[touchPoint.id()] = widget; + } + break; + } else if (p.isNull() || widget->isWindow() || widget->testAttribute(Qt::WA_NoMousePropagation)) { + break; + } + QPoint offset = widget->pos(); + widget = widget->parentWidget(); + touchEvent->setWidget(widget); + for (int i = 0; i < touchEvent->_touchPoints.size(); ++i) { + QTouchEvent::TouchPoint &pt = touchEvent->_touchPoints[i]; + QRectF rect = pt.rect(); + rect.moveCenter(offset); + pt.d->rect = rect; + pt.d->startPos = pt.startPos() + offset; + pt.d->lastPos = pt.lastPos() + offset; + } + } + + touchEvent->setAccepted(eventAccepted); + break; + } + case QEvent::RequestSoftwareInputPanel: + case QEvent::CloseSoftwareInputPanel: +#ifndef QT_NO_IM + if (receiver->isWidgetType()) { + QWidget *w = static_cast(receiver); + QInputContext *ic = w->inputContext(); + if (ic && ic->filterEvent(e)) { + break; + } + } +#endif + res = d->notify_helper(receiver, e); + break; + +#ifndef QT_NO_GESTURES + case QEvent::NativeGesture: + { + // only propagate the first gesture event (after the GID_BEGIN) + QWidget *w = static_cast(receiver); + while (w) { + e->ignore(); + res = d->notify_helper(w, e); + if ((res && e->isAccepted()) || w->isWindow()) + break; + w = w->parentWidget(); + } + break; + } + case QEvent::Gesture: + case QEvent::GestureOverride: + { + if (receiver->isWidgetType()) { + QWidget *w = static_cast(receiver); + QGestureEvent *gestureEvent = static_cast(e); + QList allGestures = gestureEvent->gestures(); + + bool eventAccepted = gestureEvent->isAccepted(); + bool wasAccepted = eventAccepted; + while (w) { + // send only gestures the widget expects + QList gestures; + QWidgetPrivate *wd = w->d_func(); + for (int i = 0; i < allGestures.size();) { + QGesture *g = allGestures.at(i); + Qt::GestureType type = g->gestureType(); + QMap::iterator contextit = + wd->gestureContext.find(type); + bool deliver = contextit != wd->gestureContext.end() && + (g->state() == Qt::GestureStarted || w == receiver || + (contextit.value() & Qt::ReceivePartialGestures)); + if (deliver) { + allGestures.removeAt(i); + gestures.append(g); + } else { + ++i; + } + } + if (!gestures.isEmpty()) { // we have gestures for this w + QGestureEvent ge(gestures); + ge.t = gestureEvent->t; + ge.spont = gestureEvent->spont; + ge.m_accept = wasAccepted; + ge.d_func()->accepted = gestureEvent->d_func()->accepted; + res = d->notify_helper(w, &ge); + gestureEvent->spont = false; + eventAccepted = ge.isAccepted(); + for (int i = 0; i < gestures.size(); ++i) { + QGesture *g = gestures.at(i); + // Ignore res [event return value] because handling of multiple gestures + // packed into a single QEvent depends on not consuming the event + if (eventAccepted || ge.isAccepted(g)) { + // if the gesture was accepted, mark the target widget for it + gestureEvent->d_func()->targetWidgets[g->gestureType()] = w; + gestureEvent->setAccepted(g, true); + } else { + // if the gesture was explicitly ignored by the application, + // put it back so a parent can get it + allGestures.append(g); + } + } + } + if (allGestures.isEmpty()) // everything delivered + break; + if (w->isWindow()) + break; + w = w->parentWidget(); + } + foreach (QGesture *g, allGestures) + gestureEvent->setAccepted(g, false); + gestureEvent->m_accept = false; // to make sure we check individual gestures + } else { + res = d->notify_helper(receiver, e); + } + break; + } +#endif // QT_NO_GESTURES +#ifdef QT_MAC_USE_COCOA + case QEvent::Enter: + if (receiver->isWidgetType()) { + QWidget *w = static_cast(receiver); + if (w->testAttribute(Qt::WA_AcceptTouchEvents)) + qt_widget_private(w)->registerTouchWindow(true); + } + res = d->notify_helper(receiver, e); + break; + case QEvent::Leave: + if (receiver->isWidgetType()) { + QWidget *w = static_cast(receiver); + if (w->testAttribute(Qt::WA_AcceptTouchEvents)) + qt_widget_private(w)->registerTouchWindow(false); + } + res = d->notify_helper(receiver, e); + break; +#endif + default: + res = d->notify_helper(receiver, e); + break; + } + + return res; +} + +bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) +{ + // send to all application event filters + if (sendThroughApplicationEventFilters(receiver, e)) + return true; + + if (receiver->isWidgetType()) { + QWidget *widget = static_cast(receiver); + +#if !defined(Q_WS_WINCE) || (defined(GWES_ICONCURS) && !defined(QT_NO_CURSOR)) + // toggle HasMouse widget state on enter and leave + if ((e->type() == QEvent::Enter || e->type() == QEvent::DragEnter) && + (!QApplication::activePopupWidget() || QApplication::activePopupWidget() == widget->window())) + widget->setAttribute(Qt::WA_UnderMouse, true); + else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) + widget->setAttribute(Qt::WA_UnderMouse, false); +#endif + + if (QLayout *layout=widget->d_func()->layout) { + layout->widgetEvent(e); + } + } + + // send to all receiver event filters + if (sendThroughObjectEventFilters(receiver, e)) + return true; + + // deliver the event + bool consumed = receiver->event(e); + e->spont = false; + return consumed; +} + + +/*! + \class QSessionManager + \brief The QSessionManager class provides access to the session manager. + + A session manager in a desktop environment (in which Qt GUI applications + live) keeps track of a session, which is a group of running applications, + each of which has a particular state. The state of an application contains + (most notably) the documents the application has open and the position and + size of its windows. + + The session manager is used to save the session, e.g., when the machine is + shut down, and to restore a session, e.g., when the machine is started up. + We recommend that you use QSettings to save an application's settings, + for example, window positions, recently used files, etc. When the + application is restarted by the session manager, you can restore the + settings. + + QSessionManager provides an interface between the application and the + session manager so that the program can work well with the session manager. + In Qt, session management requests for action are handled by the two + virtual functions QApplication::commitData() and QApplication::saveState(). + Both provide a reference to a session manager object as argument, to allow + the application to communicate with the session manager. The session + manager can only be accessed through these functions. + + No user interaction is possible \e unless the application gets explicit + permission from the session manager. You ask for permission by calling + allowsInteraction() or, if it is really urgent, allowsErrorInteraction(). + Qt does not enforce this, but the session manager may. + + You can try to abort the shutdown process by calling cancel(). The default + commitData() function does this if some top-level window rejected its + closeEvent(). + + For sophisticated session managers provided on Unix/X11, QSessionManager + offers further possibilities to fine-tune an application's session + management behavior: setRestartCommand(), setDiscardCommand(), + setRestartHint(), setProperty(), requestPhase2(). See the respective + function descriptions for further details. + + \sa QApplication, {Session Management} +*/ + +/*! \enum QSessionManager::RestartHint + + This enum type defines the circumstances under which this application wants + to be restarted by the session manager. The current values are: + + \value RestartIfRunning If the application is still running when the + session is shut down, it wants to be restarted + at the start of the next session. + + \value RestartAnyway The application wants to be started at the + start of the next session, no matter what. + (This is useful for utilities that run just + after startup and then quit.) + + \value RestartImmediately The application wants to be started immediately + whenever it is not running. + + \value RestartNever The application does not want to be restarted + automatically. + + The default hint is \c RestartIfRunning. +*/ + + +/*! + \fn QString QSessionManager::sessionId() const + + Returns the identifier of the current session. + + If the application has been restored from an earlier session, this + identifier is the same as it was in the earlier session. + + \sa sessionKey(), QApplication::sessionId() +*/ + +/*! + \fn QString QSessionManager::sessionKey() const + + Returns the session key in the current session. + + If the application has been restored from an earlier session, this key is + the same as it was when the previous session ended. + + The session key changes with every call of commitData() or saveState(). + + \sa sessionId(), QApplication::sessionKey() +*/ + +/*! + \fn void* QSessionManager::handle() const + + \internal +*/ + +/*! + \fn bool QSessionManager::allowsInteraction() + + Asks the session manager for permission to interact with the user. Returns + true if interaction is permitted; otherwise returns false. + + The rationale behind this mechanism is to make it possible to synchronize + user interaction during a shutdown. Advanced session managers may ask all + applications simultaneously to commit their data, resulting in a much + faster shutdown. + + When the interaction is completed we strongly recommend releasing the user + interaction semaphore with a call to release(). This way, other + applications may get the chance to interact with the user while your + application is still busy saving data. (The semaphore is implicitly + released when the application exits.) + + If the user decides to cancel the shutdown process during the interaction + phase, you must tell the session manager that this has happened by calling + cancel(). + + Here's an example of how an application's QApplication::commitData() might + be implemented: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 8 + + If an error occurred within the application while saving its data, you may + want to try allowsErrorInteraction() instead. + + \sa QApplication::commitData(), release(), cancel() +*/ + + +/*! + \fn bool QSessionManager::allowsErrorInteraction() + + Returns true if error interaction is permitted; otherwise returns false. + + This is similar to allowsInteraction(), but also enables the application to + tell the user about any errors that occur. Session managers may give error + interaction requests higher priority, which means that it is more likely + that an error interaction is permitted. However, you are still not + guaranteed that the session manager will allow interaction. + + \sa allowsInteraction(), release(), cancel() +*/ + +/*! + \fn void QSessionManager::release() + + Releases the session manager's interaction semaphore after an interaction + phase. + + \sa allowsInteraction(), allowsErrorInteraction() +*/ + +/*! + \fn void QSessionManager::cancel() + + Tells the session manager to cancel the shutdown process. Applications + should not call this function without asking the user first. + + \sa allowsInteraction(), allowsErrorInteraction() +*/ + +/*! + \fn void QSessionManager::setRestartHint(RestartHint hint) + + Sets the application's restart hint to \a hint. On application startup, the + hint is set to \c RestartIfRunning. + + \note These flags are only hints, a session manager may or may not respect + them. + + We recommend setting the restart hint in QApplication::saveState() because + most session managers perform a checkpoint shortly after an application's + startup. + + \sa restartHint() +*/ + +/*! + \fn QSessionManager::RestartHint QSessionManager::restartHint() const + + Returns the application's current restart hint. The default is + \c RestartIfRunning. + + \sa setRestartHint() +*/ + +/*! + \fn void QSessionManager::setRestartCommand(const QStringList& command) + + If the session manager is capable of restoring sessions it will execute + \a command in order to restore the application. The command defaults to + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 9 + + The \c -session option is mandatory; otherwise QApplication cannot tell + whether it has been restored or what the current session identifier is. + See QApplication::isSessionRestored() and QApplication::sessionId() for + details. + + If your application is very simple, it may be possible to store the entire + application state in additional command line options. This is usually a + very bad idea because command lines are often limited to a few hundred + bytes. Instead, use QSettings, temporary files, or a database for this + purpose. By marking the data with the unique sessionId(), you will be able + to restore the application in a future session. + + \sa restartCommand(), setDiscardCommand(), setRestartHint() +*/ + +/*! + \fn QStringList QSessionManager::restartCommand() const + + Returns the currently set restart command. + + To iterate over the list, you can use the \l foreach pseudo-keyword: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 10 + + \sa setRestartCommand(), restartHint() +*/ + +/*! + \fn void QSessionManager::setDiscardCommand(const QStringList& list) + + Sets the discard command to the given \a list. + + \sa discardCommand(), setRestartCommand() +*/ + + +/*! + \fn QStringList QSessionManager::discardCommand() const + + Returns the currently set discard command. + + To iterate over the list, you can use the \l foreach pseudo-keyword: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 11 + + \sa setDiscardCommand(), restartCommand(), setRestartCommand() +*/ + +/*! + \fn void QSessionManager::setManagerProperty(const QString &name, const QString &value) + \overload + + Low-level write access to the application's identification and state + records are kept in the session manager. + + The property called \a name has its value set to the string \a value. +*/ + +/*! + \fn void QSessionManager::setManagerProperty(const QString& name, + const QStringList& value) + + Low-level write access to the application's identification and state record + are kept in the session manager. + + The property called \a name has its value set to the string list \a value. +*/ + +/*! + \fn bool QSessionManager::isPhase2() const + + Returns true if the session manager is currently performing a second + session management phase; otherwise returns false. + + \sa requestPhase2() +*/ + +/*! + \fn void QSessionManager::requestPhase2() + + Requests a second session management phase for the application. The + application may then return immediately from the QApplication::commitData() + or QApplication::saveState() function, and they will be called again once + most or all other applications have finished their session management. + + The two phases are useful for applications such as the X11 window manager + that need to store information about another application's windows and + therefore have to wait until these applications have completed their + respective session management tasks. + + \note If another application has requested a second phase it may get called + before, simultaneously with, or after your application's second phase. + + \sa isPhase2() +*/ + +/***************************************************************************** + Stubbed session management support + *****************************************************************************/ +#ifndef QT_NO_SESSIONMANAGER +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) || defined(Q_WS_QWS) + +#if defined(Q_OS_WINCE) +HRESULT qt_CoCreateGuid(GUID* guid) +{ + // We will use the following information to create the GUID + // 1. absolute path to application + wchar_t tempFilename[MAX_PATH]; + if (!GetModuleFileName(0, tempFilename, MAX_PATH)) + return S_FALSE; + unsigned int hash = qHash(QString::fromWCharArray(tempFilename)); + guid->Data1 = hash; + // 2. creation time of file + QFileInfo info(QString::fromWCharArray(tempFilename)); + guid->Data2 = qHash(info.created().toTime_t()); + // 3. current system time + guid->Data3 = qHash(QDateTime::currentDateTime().toTime_t()); + return S_OK; +} +#if !defined(OLE32_MCOMGUID) || defined(QT_WINCE_FORCE_CREATE_GUID) +#define CoCreateGuid qt_CoCreateGuid +#endif + +#endif + +class QSessionManagerPrivate : public QObjectPrivate +{ +public: + QStringList restartCommand; + QStringList discardCommand; + QString sessionId; + QString sessionKey; + QSessionManager::RestartHint restartHint; +}; + +QSessionManager* qt_session_manager_self = 0; +QSessionManager::QSessionManager(QApplication * app, QString &id, QString &key) + : QObject(*new QSessionManagerPrivate, app) +{ + Q_D(QSessionManager); + setObjectName(QLatin1String("qt_sessionmanager")); + qt_session_manager_self = this; +#if defined(Q_WS_WIN) + wchar_t guidstr[40]; + GUID guid; + CoCreateGuid(&guid); + StringFromGUID2(guid, guidstr, 40); + id = QString::fromWCharArray(guidstr); + CoCreateGuid(&guid); + StringFromGUID2(guid, guidstr, 40); + key = QString::fromWCharArray(guidstr); +#endif + d->sessionId = id; + d->sessionKey = key; + d->restartHint = RestartIfRunning; +} + +QSessionManager::~QSessionManager() +{ + qt_session_manager_self = 0; +} + +QString QSessionManager::sessionId() const +{ + Q_D(const QSessionManager); + return d->sessionId; +} + +QString QSessionManager::sessionKey() const +{ + Q_D(const QSessionManager); + return d->sessionKey; +} + + +#if defined(Q_WS_X11) || defined(Q_WS_MAC) +void* QSessionManager::handle() const +{ + return 0; +} +#endif + +#if !defined(Q_WS_WIN) +bool QSessionManager::allowsInteraction() +{ + return true; +} + +bool QSessionManager::allowsErrorInteraction() +{ + return true; +} +void QSessionManager::release() +{ +} + +void QSessionManager::cancel() +{ +} +#endif + + +void QSessionManager::setRestartHint(QSessionManager::RestartHint hint) +{ + Q_D(QSessionManager); + d->restartHint = hint; +} + +QSessionManager::RestartHint QSessionManager::restartHint() const +{ + Q_D(const QSessionManager); + return d->restartHint; +} + +void QSessionManager::setRestartCommand(const QStringList& command) +{ + Q_D(QSessionManager); + d->restartCommand = command; +} + +QStringList QSessionManager::restartCommand() const +{ + Q_D(const QSessionManager); + return d->restartCommand; +} + +void QSessionManager::setDiscardCommand(const QStringList& command) +{ + Q_D(QSessionManager); + d->discardCommand = command; +} + +QStringList QSessionManager::discardCommand() const +{ + Q_D(const QSessionManager); + return d->discardCommand; +} + +void QSessionManager::setManagerProperty(const QString&, const QString&) +{ +} + +void QSessionManager::setManagerProperty(const QString&, const QStringList&) +{ +} + +bool QSessionManager::isPhase2() const +{ + return false; +} + +void QSessionManager::requestPhase2() +{ +} + +#endif +#endif // QT_NO_SESSIONMANAGER + +/*! + \typedef QApplication::ColorMode + \compat + + Use ColorSpec instead. +*/ + +/*! + \fn Qt::MacintoshVersion QApplication::macVersion() + + Use QSysInfo::MacintoshVersion instead. +*/ + +/*! + \fn QApplication::ColorMode QApplication::colorMode() + + Use colorSpec() instead, and use ColorSpec as the enum type. +*/ + +/*! + \fn void QApplication::setColorMode(ColorMode mode) + + Use setColorSpec() instead, and pass a ColorSpec value instead. +*/ + +/*! + \fn bool QApplication::hasGlobalMouseTracking() + + This feature does not exist anymore. This function always returns true + in Qt 4. +*/ + +/*! + \fn void QApplication::setGlobalMouseTracking(bool dummy) + + This function does nothing in Qt 4. The \a dummy parameter is ignored. +*/ + +/*! + \fn void QApplication::flushX() + + Use flush() instead. +*/ + +/*! + \fn void QApplication::setWinStyleHighlightColor(const QColor &c) + + Use the palette instead. + + \oldcode + app.setWinStyleHighlightColor(color); + \newcode + QPalette palette(QApplication::palette()); + palette.setColor(QPalette::Highlight, color); + QApplication::setPalette(palette); + \endcode +*/ + +/*! + \fn void QApplication::setPalette(const QPalette &pal, bool b, const char* className = 0) + + Use the two-argument overload instead. +*/ + +/*! + \fn void QApplication::setFont(const QFont &font, bool b, const char* className = 0) + + Use the two-argument overload instead. +*/ + +/*! + \fn const QColor &QApplication::winStyleHighlightColor() + + Use QApplication::palette().color(QPalette::Active, QPalette::Highlight) instead. +*/ + +/*! + \fn QWidget *QApplication::widgetAt(int x, int y, bool child) + + Use the two-argument widgetAt() overload to get the child widget. To get + the top-level widget do this: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 12 +*/ + +/*! + \fn QWidget *QApplication::widgetAt(const QPoint &point, bool child) + + Use the single-argument widgetAt() overload to get the child widget. To get + the top-level widget do this: + + \snippet doc/src/snippets/code/src_gui_kernel_qapplication.cpp 13 +*/ + +#ifdef QT3_SUPPORT +QWidget *QApplication::mainWidget() +{ + return QApplicationPrivate::main_widget; +} +#endif +bool QApplicationPrivate::inPopupMode() const +{ + return QApplicationPrivate::popupWidgets != 0; +} + +/*! + \property QApplication::quitOnLastWindowClosed + + \brief whether the application implicitly quits when the last window is + closed. + + The default is true. + + If this property is true, the applications quits when the last visible + primary window (i.e. window with no parent) with the Qt::WA_QuitOnClose + attribute set is closed. By default this attribute is set for all widgets + except for sub-windows. Refer to \l{Qt::WindowType} for a detailed list of + Qt::Window objects. + + \sa quit(), QWidget::close() + */ + +void QApplication::setQuitOnLastWindowClosed(bool quit) +{ + QApplicationPrivate::quitOnLastWindowClosed = quit; +} + +bool QApplication::quitOnLastWindowClosed() +{ + return QApplicationPrivate::quitOnLastWindowClosed; +} + +void QApplicationPrivate::emitLastWindowClosed() +{ + if (qApp && qApp->d_func()->in_exec) { + if (QApplicationPrivate::quitOnLastWindowClosed) { + // get ready to quit, this event might be removed if the + // event loop is re-entered, however + QApplication::postEvent(qApp, new QEvent(QEvent::Quit)); + } + emit qApp->lastWindowClosed(); + } +} + +/*! \variable QApplication::NormalColors + \compat + + Use \l NormalColor instead. +*/ + +/*! \variable QApplication::CustomColors + \compat + + Use \l CustomColor instead. +*/ + +#ifdef QT_KEYPAD_NAVIGATION +/*! + Sets the kind of focus navigation Qt should use to \a mode. + + This feature is available in Qt for Embedded Linux, Symbian and Windows CE + only. + + \note On Windows CE this feature is disabled by default for touch device + mkspecs. To enable keypad navigation, build Qt with + QT_KEYPAD_NAVIGATION defined. + + \note On Symbian, setting the mode to Qt::NavigationModeCursorAuto will enable a + virtual mouse cursor on non touchscreen devices, which is controlled + by the cursor keys if there is no analog pointer device. + On other platforms and on touchscreen devices, it has the same + meaning as Qt::NavigationModeNone. + + \since 4.6 + + \sa keypadNavigationEnabled() +*/ +void QApplication::setNavigationMode(Qt::NavigationMode mode) +{ +#ifdef Q_OS_SYMBIAN + QApplicationPrivate::setNavigationMode(mode); +#else + QApplicationPrivate::navigationMode = mode; +#endif +} + +/*! + Returns what kind of focus navigation Qt is using. + + This feature is available in Qt for Embedded Linux, Symbian and Windows CE + only. + + \note On Windows CE this feature is disabled by default for touch device + mkspecs. To enable keypad navigation, build Qt with + QT_KEYPAD_NAVIGATION defined. + + \note On Symbian, the default mode is Qt::NavigationModeNone for touch + devices, and Qt::NavigationModeKeypadDirectional. + + \since 4.6 + + \sa keypadNavigationEnabled() +*/ +Qt::NavigationMode QApplication::navigationMode() +{ + return QApplicationPrivate::navigationMode; +} + +/*! + Sets whether Qt should use focus navigation suitable for use with a + minimal keypad. + + This feature is available in Qt for Embedded Linux, Symbian and Windows CE + only. + + \note On Windows CE this feature is disabled by default for touch device + mkspecs. To enable keypad navigation, build Qt with + QT_KEYPAD_NAVIGATION defined. + + \deprecated + + \sa setNavigationMode() +*/ +void QApplication::setKeypadNavigationEnabled(bool enable) +{ + if (enable) { +#ifdef Q_OS_SYMBIAN + QApplication::setNavigationMode(Qt::NavigationModeKeypadDirectional); +#else + QApplication::setNavigationMode(Qt::NavigationModeKeypadTabOrder); +#endif + } + else { + QApplication::setNavigationMode(Qt::NavigationModeNone); + } +} + +/*! + Returns true if Qt is set to use keypad navigation; otherwise returns + false. The default value is true on Symbian, but false on other platforms. + + This feature is available in Qt for Embedded Linux, Symbian and Windows CE + only. + + \note On Windows CE this feature is disabled by default for touch device + mkspecs. To enable keypad navigation, build Qt with + QT_KEYPAD_NAVIGATION defined. + + \deprecated + + \sa navigationMode() +*/ +bool QApplication::keypadNavigationEnabled() +{ + return QApplicationPrivate::navigationMode == Qt::NavigationModeKeypadTabOrder || + QApplicationPrivate::navigationMode == Qt::NavigationModeKeypadDirectional; +} +#endif + +/*! + \fn void QApplication::alert(QWidget *widget, int msec) + \since 4.3 + + Causes an alert to be shown for \a widget if the window is not the active + window. The alert is shown for \a msec miliseconds. If \a msec is zero (the + default), then the alert is shown indefinitely until the window becomes + active again. + + Currently this function does nothing on Qt for Embedded Linux. + + On Mac OS X, this works more at the application level and will cause the + application icon to bounce in the dock. + + On Windows, this causes the window's taskbar entry to flash for a time. If + \a msec is zero, the flashing will stop and the taskbar entry will turn a + different color (currently orange). + + On X11, this will cause the window to be marked as "demands attention", the + window must not be hidden (i.e. not have hide() called on it, but be + visible in some sort of way) in order for this to work. +*/ + +/*! + \property QApplication::cursorFlashTime + \brief the text cursor's flash (blink) time in milliseconds + + The flash time is the time required to display, invert and restore the + caret display. Usually the text cursor is displayed for half the cursor + flash time, then hidden for the same amount of time, but this may vary. + + The default value on X11 is 1000 milliseconds. On Windows, the + \gui{Control Panel} value is used and setting this property sets the cursor + flash time for all applications. + + We recommend that widgets do not cache this value as it may change at any + time if the user changes the global desktop settings. +*/ + +/*! + \property QApplication::doubleClickInterval + \brief the time limit in milliseconds that distinguishes a double click + from two consecutive mouse clicks + + The default value on X11 is 400 milliseconds. On Windows and Mac OS, the + operating system's value is used. However, on Windows and Symbian OS, + calling this function sets the double click interval for all applications. +*/ + +/*! + \property QApplication::keyboardInputInterval + \brief the time limit in milliseconds that distinguishes a key press + from two consecutive key presses + \since 4.2 + + The default value on X11 is 400 milliseconds. On Windows and Mac OS, the + operating system's value is used. +*/ + +/*! + \property QApplication::wheelScrollLines + \brief the number of lines to scroll a widget, when the + mouse wheel is rotated. + + If the value exceeds the widget's number of visible lines, the widget + should interpret the scroll operation as a single \e{page up} or + \e{page down}. If the widget is an \l{QAbstractItemView}{item view class}, + then the result of scrolling one \e line depends on the setting of the + widget's \l{QAbstractItemView::verticalScrollMode()}{scroll mode}. Scroll + one \e line can mean \l{QAbstractItemView::ScrollPerItem}{scroll one item} + or \l{QAbstractItemView::ScrollPerPixel}{scroll one pixel}. + + By default, this property has a value of 3. +*/ + +/*! + \fn void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) + + Enables the UI effect \a effect if \a enable is true, otherwise the effect + will not be used. + + \note All effects are disabled on screens running at less than 16-bit color + depth. + + \sa isEffectEnabled(), Qt::UIEffect, setDesktopSettingsAware() +*/ + +/*! + \fn bool QApplication::isEffectEnabled(Qt::UIEffect effect) + + Returns true if \a effect is enabled; otherwise returns false. + + By default, Qt will try to use the desktop settings. To prevent this, call + setDesktopSettingsAware(false). + + \note All effects are disabled on screens running at less than 16-bit color + depth. + + \sa setEffectEnabled(), Qt::UIEffect +*/ + +/*! + \fn QWidget *QApplication::mainWidget() + + Returns the main application widget, or 0 if there is no main widget. +*/ + +/*! + \fn void QApplication::setMainWidget(QWidget *mainWidget) + + Sets the application's main widget to \a mainWidget. + + In most respects the main widget is like any other widget, except that if + it is closed, the application exits. QApplication does \e not take + ownership of the \a mainWidget, so if you create your main widget on the + heap you must delete it yourself. + + You need not have a main widget; connecting lastWindowClosed() to quit() + is an alternative. + + On X11, this function also resizes and moves the main widget according + to the \e -geometry command-line option, so you should set the default + geometry (using \l QWidget::setGeometry()) before calling setMainWidget(). + + \sa mainWidget(), exec(), quit() +*/ + +/*! + \fn void QApplication::beep() + + Sounds the bell, using the default volume and sound. The function is \e not + available in Qt for Embedded Linux. +*/ + +/*! + \fn void QApplication::setOverrideCursor(const QCursor &cursor) + + Sets the application override cursor to \a cursor. + + Application override cursors are intended for showing the user that the + application is in a special state, for example during an operation that + might take some time. + + This cursor will be displayed in all the application's widgets until + restoreOverrideCursor() or another setOverrideCursor() is called. + + Application cursors are stored on an internal stack. setOverrideCursor() + pushes the cursor onto the stack, and restoreOverrideCursor() pops the + active cursor off the stack. changeOverrideCursor() changes the curently + active application override cursor. + + Every setOverrideCursor() must eventually be followed by a corresponding + restoreOverrideCursor(), otherwise the stack will never be emptied. + + Example: + \snippet doc/src/snippets/code/src_gui_kernel_qapplication_x11.cpp 0 + + \sa overrideCursor(), restoreOverrideCursor(), changeOverrideCursor(), + QWidget::setCursor() +*/ + +/*! + \fn void QApplication::restoreOverrideCursor() + + Undoes the last setOverrideCursor(). + + If setOverrideCursor() has been called twice, calling + restoreOverrideCursor() will activate the first cursor set. Calling this + function a second time restores the original widgets' cursors. + + \sa setOverrideCursor(), overrideCursor() +*/ + +/*! + \macro qApp + \relates QApplication + + A global pointer referring to the unique application object. It is + equivalent to the pointer returned by the QCoreApplication::instance() + function except that, in GUI applications, it is a pointer to a + QApplication instance. + + Only one application object can be created. + + \sa QCoreApplication::instance() +*/ + +#ifndef QT_NO_IM +// ************************************************************************ +// Input Method support +// ************************************************************************ + +/*! + This function replaces the QInputContext instance used by the application + with \a inputContext. + + Qt takes ownership of the given \a inputContext. + + \sa inputContext() +*/ +void QApplication::setInputContext(QInputContext *inputContext) +{ + if (inputContext == QApplicationPrivate::inputContext) + return; + if (!inputContext) { + qWarning("QApplication::setInputContext: called with 0 input context"); + return; + } + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = inputContext; + QApplicationPrivate::inputContext->setParent(this); +} + +/*! + Returns the QInputContext instance used by the application. + + \sa setInputContext() +*/ +QInputContext *QApplication::inputContext() const +{ + Q_D(const QApplication); + Q_UNUSED(d);// only static members being used. + if (QApplicationPrivate::is_app_closing) + return d->inputContext; +#ifdef Q_WS_X11 + if (!X11) + return 0; + if (!d->inputContext) { + QApplication *that = const_cast(this); + QInputContext *qic = QInputContextFactory::create(X11->default_im, that); + // fallback to default X Input Method. + if (!qic) + qic = QInputContextFactory::create(QLatin1String("xim"), that); + that->d_func()->inputContext = qic; + } +#elif defined(Q_OS_SYMBIAN) + if (!d->inputContext) { + QApplication *that = const_cast(this); + const QStringList keys = QInputContextFactory::keys(); + // Try hbim and coefep first, then try others. + if (keys.contains(QLatin1String("hbim"))) { + that->d_func()->inputContext = QInputContextFactory::create(QLatin1String("hbim"), that); + } else if (keys.contains(QLatin1String("coefep"))) { + that->d_func()->inputContext = QInputContextFactory::create(QLatin1String("coefep"), that); + } else { + for (int c = 0; c < keys.size() && !d->inputContext; ++c) { + that->d_func()->inputContext = QInputContextFactory::create(keys[c], that); + } + } + } +#endif + return d->inputContext; +} +#endif // QT_NO_IM + +//Returns the current platform used by keyBindings +uint QApplicationPrivate::currentPlatform(){ + uint platform = KB_Win; +#ifdef Q_WS_MAC + platform = KB_Mac; +#elif defined Q_WS_X11 + platform = KB_X11; + if (X11->desktopEnvironment == DE_KDE) + platform |= KB_KDE; + if (X11->desktopEnvironment == DE_GNOME) + platform |= KB_Gnome; + if (X11->desktopEnvironment == DE_CDE) + platform |= KB_CDE; +#elif defined(Q_OS_SYMBIAN) + platform = KB_S60; +#endif + return platform; +} + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event) +{ + return QCoreApplication::sendSpontaneousEvent(receiver, event); +} + + +/*! + \since 4.2 + + Returns the current keyboard input locale. +*/ +QLocale QApplication::keyboardInputLocale() +{ + if (!QApplicationPrivate::checkInstance("keyboardInputLocale")) + return QLocale::c(); + return qt_keymapper_private()->keyboardInputLocale; +} + +/*! + \since 4.2 + + Returns the current keyboard input direction. +*/ +Qt::LayoutDirection QApplication::keyboardInputDirection() +{ + if (!QApplicationPrivate::checkInstance("keyboardInputDirection")) + return Qt::LeftToRight; + return qt_keymapper_private()->keyboardInputDirection; +} + +void QApplicationPrivate::giveFocusAccordingToFocusPolicy(QWidget *widget, + Qt::FocusPolicy focusPolicy, + Qt::FocusReason focusReason) +{ + QWidget *focusWidget = widget; + while (focusWidget) { + if (focusWidget->isEnabled() + && QApplicationPrivate::shouldSetFocus(focusWidget, focusPolicy)) { + focusWidget->setFocus(focusReason); + break; + } + if (focusWidget->isWindow()) + break; + focusWidget = focusWidget->parentWidget(); + } +} + +bool QApplicationPrivate::shouldSetFocus(QWidget *w, Qt::FocusPolicy policy) +{ + QWidget *f = w; + while (f->d_func()->extra && f->d_func()->extra->focus_proxy) + f = f->d_func()->extra->focus_proxy; + + if ((w->focusPolicy() & policy) != policy) + return false; + if (w != f && (f->focusPolicy() & policy) != policy) + return false; + return true; +} + +/*! \fn QDecoration &QApplication::qwsDecoration() + Return the QWSDecoration used for decorating windows. + + \warning This method is non-portable. It is only available in + Qt for Embedded Linux. + + \sa QDecoration +*/ + +/*! + \fn void QApplication::qwsSetDecoration(QDecoration *decoration) + + Sets the QDecoration derived class to use for decorating the + windows used by Qt for Embedded Linux to the \a decoration + specified. + + This method is non-portable. It is only available in Qt for Embedded Linux. + + \sa QDecoration +*/ + +/*! \fn QDecoration* QApplication::qwsSetDecoration(const QString &decoration) + \overload + + Requests a QDecoration object for \a decoration from the + QDecorationFactory. + + The string must be one of the QDecorationFactory::keys(). Keys are case + insensitive. + + A later call to the QApplication constructor will override the requested + style when a "-style" option is passed in as a commandline parameter. + + Returns 0 if an unknown \a decoration is passed, otherwise the QStyle object + returned is set as the application's GUI style. +*/ + +/*! + \fn bool QApplication::qwsEventFilter(QWSEvent *event) + + This virtual function is only implemented under Qt for Embedded Linux. + + If you create an application that inherits QApplication and + reimplement this function, you get direct access to all QWS (Q + Window System) events that the are received from the QWS master + process. The events are passed in the \a event parameter. + + Return true if you want to stop the event from being processed. + Return false for normal event dispatching. The default + implementation returns false. +*/ + +/*! \fn void QApplication::qwsSetCustomColors(QRgb *colorTable, int start, int numColors) + Set Qt for Embedded Linux custom color table. + + Qt for Embedded Linux on 8-bpp displays allocates a standard 216 color cube. + The remaining 40 colors may be used by setting a custom color + table in the QWS master process before any clients connect. + + \a colorTable is an array of up to 40 custom colors. \a start is + the starting index (0-39) and \a numColors is the number of colors + to be set (1-40). + + This method is non-portable. It is available \e only in + Qt for Embedded Linux. + + \note The custom colors will not be used by the default screen + driver. To make use of the new colors, implement a custom screen + driver, or use QDirectPainter. +*/ + +/*! \fn int QApplication::qwsProcessEvent(QWSEvent* event) + \internal +*/ + +/*! \fn int QApplication::x11ClientMessage(QWidget* w, XEvent* event, bool passive_only) + \internal +*/ + +/*! \fn int QApplication::x11ProcessEvent(XEvent* event) + This function does the core processing of individual X + \a{event}s, normally by dispatching Qt events to the right + destination. + + It returns 1 if the event was consumed by special handling, 0 if + the \a event was consumed by normal handling, and -1 if the \a + event was for an unrecognized widget. + + \sa x11EventFilter() +*/ + +/*! + \fn bool QApplication::x11EventFilter(XEvent *event) + + \warning This virtual function is only implemented under X11. + + If you create an application that inherits QApplication and + reimplement this function, you get direct access to all X events + that the are received from the X server. The events are passed in + the \a event parameter. + + Return true if you want to stop the event from being processed. + Return false for normal event dispatching. The default + implementation returns false. + + It is only the directly addressed messages that are filtered. + You must install an event filter directly on the event + dispatcher, which is returned by + QAbstractEventDispatcher::instance(), to handle system wide + messages. + + \sa x11ProcessEvent() +*/ + +/*! \fn void QApplication::winFocus(QWidget *widget, bool gotFocus) + \internal + \since 4.1 + + If \a gotFocus is true, \a widget will become the active window. + Otherwise the active window is reset to 0. +*/ + +/*! \fn void QApplication::winMouseButtonUp() + \internal + */ + +/*! \fn void QApplication::syncX() + Synchronizes with the X server in the X11 implementation. + This normally takes some time. Does nothing on other platforms. +*/ + +void QApplicationPrivate::updateTouchPointsForWidget(QWidget *widget, QTouchEvent *touchEvent) +{ + for (int i = 0; i < touchEvent->touchPoints().count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchEvent->_touchPoints[i]; + + // preserve the sub-pixel resolution + QRectF rect = touchPoint.screenRect(); + const QPointF screenPos = rect.center(); + const QPointF delta = screenPos - screenPos.toPoint(); + + rect.moveCenter(widget->mapFromGlobal(screenPos.toPoint()) + delta); + touchPoint.d->rect = rect; + if (touchPoint.state() == Qt::TouchPointPressed) { + touchPoint.d->startPos = widget->mapFromGlobal(touchPoint.startScreenPos().toPoint()) + delta; + touchPoint.d->lastPos = widget->mapFromGlobal(touchPoint.lastScreenPos().toPoint()) + delta; + } + } +} + +void QApplicationPrivate::initializeMultitouch() +{ + widgetForTouchPointId.clear(); + appCurrentTouchPoints.clear(); + + initializeMultitouch_sys(); +} + +void QApplicationPrivate::cleanupMultitouch() +{ + cleanupMultitouch_sys(); + + widgetForTouchPointId.clear(); + appCurrentTouchPoints.clear(); +} + +int QApplicationPrivate::findClosestTouchPointId(const QPointF &screenPos) +{ + int closestTouchPointId = -1; + qreal closestDistance = qreal(0.); + foreach (const QTouchEvent::TouchPoint &touchPoint, appCurrentTouchPoints) { + qreal distance = QLineF(screenPos, touchPoint.screenPos()).length(); + if (closestTouchPointId == -1 || distance < closestDistance) { + closestTouchPointId = touchPoint.id(); + closestDistance = distance; + } + } + return closestTouchPointId; +} + +void QApplicationPrivate::translateRawTouchEvent(QWidget *window, + QTouchEvent::DeviceType deviceType, + const QList &touchPoints) +{ + QApplicationPrivate *d = self; + typedef QPair > StatesAndTouchPoints; + QHash widgetsNeedingEvents; + + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint touchPoint = touchPoints.at(i); + // explicitly detach from the original touch point that we got, so even + // if the touchpoint structs are reused, we will make a copy that we'll + // deliver to the user (which might want to store the struct for later use). + touchPoint.d = touchPoint.d->detach(); + + // update state + QWeakPointer widget; + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + { + if (deviceType == QTouchEvent::TouchPad) { + // on touch-pads, send all touch points to the same widget + widget = d->widgetForTouchPointId.isEmpty() + ? QWeakPointer() + : d->widgetForTouchPointId.constBegin().value(); + } + + if (!widget) { + // determine which widget this event will go to + if (!window) + window = QApplication::topLevelAt(touchPoint.screenPos().toPoint()); + if (!window) + continue; + widget = window->childAt(window->mapFromGlobal(touchPoint.screenPos().toPoint())); + if (!widget) + widget = window; + } + + if (deviceType == QTouchEvent::TouchScreen) { + int closestTouchPointId = d->findClosestTouchPointId(touchPoint.screenPos()); + QWidget *closestWidget = d->widgetForTouchPointId.value(closestTouchPointId).data(); + if (closestWidget + && (widget.data()->isAncestorOf(closestWidget) || closestWidget->isAncestorOf(widget.data()))) { + widget = closestWidget; + } + } + + d->widgetForTouchPointId[touchPoint.id()] = widget; + touchPoint.d->startScreenPos = touchPoint.screenPos(); + touchPoint.d->lastScreenPos = touchPoint.screenPos(); + touchPoint.d->startNormalizedPos = touchPoint.normalizedPos(); + touchPoint.d->lastNormalizedPos = touchPoint.normalizedPos(); + if (touchPoint.pressure() < qreal(0.)) + touchPoint.d->pressure = qreal(1.); + + d->appCurrentTouchPoints.insert(touchPoint.id(), touchPoint); + break; + } + case Qt::TouchPointReleased: + { + widget = d->widgetForTouchPointId.take(touchPoint.id()); + if (!widget) + continue; + + QTouchEvent::TouchPoint previousTouchPoint = d->appCurrentTouchPoints.take(touchPoint.id()); + touchPoint.d->startScreenPos = previousTouchPoint.startScreenPos(); + touchPoint.d->lastScreenPos = previousTouchPoint.screenPos(); + touchPoint.d->startPos = previousTouchPoint.startPos(); + touchPoint.d->lastPos = previousTouchPoint.pos(); + touchPoint.d->startNormalizedPos = previousTouchPoint.startNormalizedPos(); + touchPoint.d->lastNormalizedPos = previousTouchPoint.normalizedPos(); + if (touchPoint.pressure() < qreal(0.)) + touchPoint.d->pressure = qreal(0.); + break; + } + default: + widget = d->widgetForTouchPointId.value(touchPoint.id()); + if (!widget) + continue; + + Q_ASSERT(d->appCurrentTouchPoints.contains(touchPoint.id())); + QTouchEvent::TouchPoint previousTouchPoint = d->appCurrentTouchPoints.value(touchPoint.id()); + touchPoint.d->startScreenPos = previousTouchPoint.startScreenPos(); + touchPoint.d->lastScreenPos = previousTouchPoint.screenPos(); + touchPoint.d->startPos = previousTouchPoint.startPos(); + touchPoint.d->lastPos = previousTouchPoint.pos(); + touchPoint.d->startNormalizedPos = previousTouchPoint.startNormalizedPos(); + touchPoint.d->lastNormalizedPos = previousTouchPoint.normalizedPos(); + if (touchPoint.pressure() < qreal(0.)) + touchPoint.d->pressure = qreal(1.); + d->appCurrentTouchPoints[touchPoint.id()] = touchPoint; + break; + } + Q_ASSERT(widget.data() != 0); + + // make the *scene* functions return the same as the *screen* functions + touchPoint.d->sceneRect = touchPoint.screenRect(); + touchPoint.d->startScenePos = touchPoint.startScreenPos(); + touchPoint.d->lastScenePos = touchPoint.lastScreenPos(); + + StatesAndTouchPoints &maskAndPoints = widgetsNeedingEvents[widget.data()]; + maskAndPoints.first |= touchPoint.state(); + if (touchPoint.isPrimary()) + maskAndPoints.first |= Qt::TouchPointPrimary; + maskAndPoints.second.append(touchPoint); + } + + if (widgetsNeedingEvents.isEmpty()) + return; + + QHash::ConstIterator it = widgetsNeedingEvents.constBegin(); + const QHash::ConstIterator end = widgetsNeedingEvents.constEnd(); + for (; it != end; ++it) { + QWidget *widget = it.key(); + if (!QApplicationPrivate::tryModalHelper(widget, 0)) + continue; + + QEvent::Type eventType; + switch (it.value().first & Qt::TouchPointStateMask) { + case Qt::TouchPointPressed: + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + eventType = QEvent::TouchEnd; + break; + case Qt::TouchPointStationary: + // don't send the event if nothing changed + continue; + default: + eventType = QEvent::TouchUpdate; + break; + } + + QTouchEvent touchEvent(eventType, + deviceType, + QApplication::keyboardModifiers(), + it.value().first, + it.value().second); + updateTouchPointsForWidget(widget, &touchEvent); + + switch (touchEvent.type()) { + case QEvent::TouchBegin: + { + // if the TouchBegin handler recurses, we assume that means the event + // has been implicitly accepted and continue to send touch events + widget->setAttribute(Qt::WA_WState_AcceptedTouchBeginEvent); + (void ) QApplication::sendSpontaneousEvent(widget, &touchEvent); + break; + } + default: + if (widget->testAttribute(Qt::WA_WState_AcceptedTouchBeginEvent)) { + if (touchEvent.type() == QEvent::TouchEnd) + widget->setAttribute(Qt::WA_WState_AcceptedTouchBeginEvent, false); + (void) QApplication::sendSpontaneousEvent(widget, &touchEvent); + } + break; + } + } +} + +Q_GUI_EXPORT void qt_translateRawTouchEvent(QWidget *window, + QTouchEvent::DeviceType deviceType, + const QList &touchPoints) +{ + QApplicationPrivate::translateRawTouchEvent(window, deviceType, touchPoints); +} + +#ifndef QT_NO_GESTURES +QGestureManager* QGestureManager::instance() +{ + QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); + if (!qAppPriv) + return 0; + if (!qAppPriv->gestureManager) + qAppPriv->gestureManager = new QGestureManager(qApp); + return qAppPriv->gestureManager; +} +#endif // QT_NO_GESTURES + +// These pixmaps approximate the images in the Windows User Interface Guidelines. + +// XPM + +static const char * const move_xpm[] = { +"11 20 3 1", +". c None", +#if defined(Q_WS_WIN) +"a c #000000", +"X c #FFFFFF", // Windows cursor is traditionally white +#else +"a c #FFFFFF", +"X c #000000", // X11 cursor is traditionally black +#endif +"aa.........", +"aXa........", +"aXXa.......", +"aXXXa......", +"aXXXXa.....", +"aXXXXXa....", +"aXXXXXXa...", +"aXXXXXXXa..", +"aXXXXXXXXa.", +"aXXXXXXXXXa", +"aXXXXXXaaaa", +"aXXXaXXa...", +"aXXaaXXa...", +"aXa..aXXa..", +"aa...aXXa..", +"a.....aXXa.", +"......aXXa.", +".......aXXa", +".......aXXa", +"........aa."}; + +#ifdef Q_WS_WIN +/* XPM */ +static const char * const ignore_xpm[] = { +"24 30 3 1", +". c None", +"a c #000000", +"X c #FFFFFF", +"aa......................", +"aXa.....................", +"aXXa....................", +"aXXXa...................", +"aXXXXa..................", +"aXXXXXa.................", +"aXXXXXXa................", +"aXXXXXXXa...............", +"aXXXXXXXXa..............", +"aXXXXXXXXXa.............", +"aXXXXXXaaaa.............", +"aXXXaXXa................", +"aXXaaXXa................", +"aXa..aXXa...............", +"aa...aXXa...............", +"a.....aXXa..............", +"......aXXa.....XXXX.....", +".......aXXa..XXaaaaXX...", +".......aXXa.XaaaaaaaaX..", +"........aa.XaaaXXXXaaaX.", +"...........XaaaaX..XaaX.", +"..........XaaXaaaX..XaaX", +"..........XaaXXaaaX.XaaX", +"..........XaaX.XaaaXXaaX", +"..........XaaX..XaaaXaaX", +"...........XaaX..XaaaaX.", +"...........XaaaXXXXaaaX.", +"............XaaaaaaaaX..", +".............XXaaaaXX...", +"...............XXXX....."}; +#endif + +/* XPM */ +static const char * const copy_xpm[] = { +"24 30 3 1", +". c None", +"a c #000000", +"X c #FFFFFF", +#if defined(Q_WS_WIN) // Windows cursor is traditionally white +"aa......................", +"aXa.....................", +"aXXa....................", +"aXXXa...................", +"aXXXXa..................", +"aXXXXXa.................", +"aXXXXXXa................", +"aXXXXXXXa...............", +"aXXXXXXXXa..............", +"aXXXXXXXXXa.............", +"aXXXXXXaaaa.............", +"aXXXaXXa................", +"aXXaaXXa................", +"aXa..aXXa...............", +"aa...aXXa...............", +"a.....aXXa..............", +"......aXXa..............", +".......aXXa.............", +".......aXXa.............", +"........aa...aaaaaaaaaaa", +#else +"XX......................", +"XaX.....................", +"XaaX....................", +"XaaaX...................", +"XaaaaX..................", +"XaaaaaX.................", +"XaaaaaaX................", +"XaaaaaaaX...............", +"XaaaaaaaaX..............", +"XaaaaaaaaaX.............", +"XaaaaaaXXXX.............", +"XaaaXaaX................", +"XaaXXaaX................", +"XaX..XaaX...............", +"XX...XaaX...............", +"X.....XaaX..............", +"......XaaX..............", +".......XaaX.............", +".......XaaX.............", +"........XX...aaaaaaaaaaa", +#endif +".............aXXXXXXXXXa", +".............aXXXXXXXXXa", +".............aXXXXaXXXXa", +".............aXXXXaXXXXa", +".............aXXaaaaaXXa", +".............aXXXXaXXXXa", +".............aXXXXaXXXXa", +".............aXXXXXXXXXa", +".............aXXXXXXXXXa", +".............aaaaaaaaaaa"}; + +/* XPM */ +static const char * const link_xpm[] = { +"24 30 3 1", +". c None", +"a c #000000", +"X c #FFFFFF", +#if defined(Q_WS_WIN) // Windows cursor is traditionally white +"aa......................", +"aXa.....................", +"aXXa....................", +"aXXXa...................", +"aXXXXa..................", +"aXXXXXa.................", +"aXXXXXXa................", +"aXXXXXXXa...............", +"aXXXXXXXXa..............", +"aXXXXXXXXXa.............", +"aXXXXXXaaaa.............", +"aXXXaXXa................", +"aXXaaXXa................", +"aXa..aXXa...............", +"aa...aXXa...............", +"a.....aXXa..............", +"......aXXa..............", +".......aXXa.............", +".......aXXa.............", +"........aa...aaaaaaaaaaa", +#else +"XX......................", +"XaX.....................", +"XaaX....................", +"XaaaX...................", +"XaaaaX..................", +"XaaaaaX.................", +"XaaaaaaX................", +"XaaaaaaaX...............", +"XaaaaaaaaX..............", +"XaaaaaaaaaX.............", +"XaaaaaaXXXX.............", +"XaaaXaaX................", +"XaaXXaaX................", +"XaX..XaaX...............", +"XX...XaaX...............", +"X.....XaaX..............", +"......XaaX..............", +".......XaaX.............", +".......XaaX.............", +"........XX...aaaaaaaaaaa", +#endif +".............aXXXXXXXXXa", +".............aXXXaaaaXXa", +".............aXXXXaaaXXa", +".............aXXXaaaaXXa", +".............aXXaaaXaXXa", +".............aXXaaXXXXXa", +".............aXXaXXXXXXa", +".............aXXXaXXXXXa", +".............aXXXXXXXXXa", +".............aaaaaaaaaaa"}; + +QPixmap QApplicationPrivate::getPixmapCursor(Qt::CursorShape cshape) +{ +#if defined(Q_WS_X11) || defined(Q_WS_WIN) + if (!move_cursor) { + move_cursor = new QPixmap((const char **)move_xpm); + copy_cursor = new QPixmap((const char **)copy_xpm); + link_cursor = new QPixmap((const char **)link_xpm); +#ifdef Q_WS_WIN + ignore_cursor = new QPixmap((const char **)ignore_xpm); +#endif + } + + switch (cshape) { + case Qt::DragMoveCursor: + return *move_cursor; + case Qt::DragCopyCursor: + return *copy_cursor; + case Qt::DragLinkCursor: + return *link_cursor; +#ifdef Q_WS_WIN + case Qt::ForbiddenCursor: + return *ignore_cursor; +#endif + default: + break; + } +#else + Q_UNUSED(cshape); +#endif + return QPixmap(); +} + +QString QApplicationPrivate::qmljsDebugArgumentsString() +{ + return qmljs_debug_arguments; +} + +QT_END_NAMESPACE + +#include "moc_qapplication.cpp" diff --git a/src/gui/kernel/qapplication.h b/src/gui/kernel/qapplication.h new file mode 100644 index 0000000000..fb61d36307 --- /dev/null +++ b/src/gui/kernel/qapplication.h @@ -0,0 +1,430 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QAPPLICATION_H +#define QAPPLICATION_H + +#include +#include +#include +#include +#include +#ifdef QT_INCLUDE_COMPAT +# include +#endif +#ifdef QT3_SUPPORT +# include +# include +#endif +#ifdef Q_WS_QWS +# include +# include +#endif + +QT_BEGIN_HEADER + +#if defined(Q_OS_SYMBIAN) +class CApaApplication; +#endif + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QSessionManager; +class QDesktopWidget; +class QStyle; +class QEventLoop; +class QIcon; +class QInputContext; +template class QList; +class QLocale; +#if defined(Q_WS_QWS) +class QDecoration; +#elif defined(Q_WS_QPA) +class QPlatformNativeInterface; +#endif +#if defined(Q_OS_SYMBIAN) +class QSymbianEvent; +#endif + +class QApplication; +class QApplicationPrivate; +#if defined(qApp) +#undef qApp +#endif +#define qApp (static_cast(QCoreApplication::instance())) + + +class Q_GUI_EXPORT QApplication : public QCoreApplication +{ + Q_OBJECT + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection) + Q_PROPERTY(QIcon windowIcon READ windowIcon WRITE setWindowIcon) + Q_PROPERTY(int cursorFlashTime READ cursorFlashTime WRITE setCursorFlashTime) + Q_PROPERTY(int doubleClickInterval READ doubleClickInterval WRITE setDoubleClickInterval) + Q_PROPERTY(int keyboardInputInterval READ keyboardInputInterval WRITE setKeyboardInputInterval) +#ifndef QT_NO_WHEELEVENT + Q_PROPERTY(int wheelScrollLines READ wheelScrollLines WRITE setWheelScrollLines) +#endif + Q_PROPERTY(QSize globalStrut READ globalStrut WRITE setGlobalStrut) + Q_PROPERTY(int startDragTime READ startDragTime WRITE setStartDragTime) + Q_PROPERTY(int startDragDistance READ startDragDistance WRITE setStartDragDistance) + Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed) +#ifndef QT_NO_STYLE_STYLESHEET + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) +#endif +#ifdef Q_WS_WINCE + Q_PROPERTY(int autoMaximizeThreshold READ autoMaximizeThreshold WRITE setAutoMaximizeThreshold) +#endif + Q_PROPERTY(bool autoSipEnabled READ autoSipEnabled WRITE setAutoSipEnabled) + +public: + enum Type { Tty, GuiClient, GuiServer }; + +#ifdef Q_OS_SYMBIAN + typedef CApaApplication * (*QS60MainApplicationFactory)(); +#endif + +#ifndef qdoc + QApplication(int &argc, char **argv, int = ApplicationFlags); + QApplication(int &argc, char **argv, bool GUIenabled, int = ApplicationFlags); + QApplication(int &argc, char **argv, Type, int = ApplicationFlags); +#if defined(Q_WS_X11) + QApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0, int = ApplicationFlags); + QApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0, int = ApplicationFlags); +#endif +#if defined(Q_OS_SYMBIAN) + QApplication(QApplication::QS60MainApplicationFactory factory, int &argc, char **argv, int = ApplicationFlags); +#endif +#endif + virtual ~QApplication(); + + static Type type(); + + static QStyle *style(); + static void setStyle(QStyle*); + static QStyle *setStyle(const QString&); + enum ColorSpec { NormalColor=0, CustomColor=1, ManyColor=2 }; + static int colorSpec(); + static void setColorSpec(int); + static void setGraphicsSystem(const QString &); + +#ifndef QT_NO_CURSOR + static QCursor *overrideCursor(); + static void setOverrideCursor(const QCursor &); + static void changeOverrideCursor(const QCursor &); + static void restoreOverrideCursor(); +#endif + static QPalette palette(); + static QPalette palette(const QWidget *); + static QPalette palette(const char *className); + static void setPalette(const QPalette &, const char* className = 0); + static QFont font(); + static QFont font(const QWidget*); + static QFont font(const char *className); + static void setFont(const QFont &, const char* className = 0); + static QFontMetrics fontMetrics(); + + static void setWindowIcon(const QIcon &icon); + static QIcon windowIcon(); + + +#ifdef QT3_SUPPORT + static QT3_SUPPORT QWidget *mainWidget(); + static QT3_SUPPORT void setMainWidget(QWidget *); +#endif + + static QWidgetList allWidgets(); + static QWidgetList topLevelWidgets(); + + static QDesktopWidget *desktop(); + + static QWidget *activePopupWidget(); + static QWidget *activeModalWidget(); +#ifndef QT_NO_CLIPBOARD + static QClipboard *clipboard(); +#endif + static QWidget *focusWidget(); + + static QWidget *activeWindow(); + static void setActiveWindow(QWidget* act); + + static QWidget *widgetAt(const QPoint &p); + static inline QWidget *widgetAt(int x, int y) { return widgetAt(QPoint(x, y)); } + static QWidget *topLevelAt(const QPoint &p); + static inline QWidget *topLevelAt(int x, int y) { return topLevelAt(QPoint(x, y)); } + + static void syncX(); + static void beep(); + static void alert(QWidget *widget, int duration = 0); + + static Qt::KeyboardModifiers keyboardModifiers(); + static Qt::MouseButtons mouseButtons(); + + static void setDesktopSettingsAware(bool); + static bool desktopSettingsAware(); + + static void setCursorFlashTime(int); + static int cursorFlashTime(); + + static void setDoubleClickInterval(int); + static int doubleClickInterval(); + + static void setKeyboardInputInterval(int); + static int keyboardInputInterval(); + +#ifndef QT_NO_WHEELEVENT + static void setWheelScrollLines(int); + static int wheelScrollLines(); +#endif + static void setGlobalStrut(const QSize &); + static QSize globalStrut(); + + static void setStartDragTime(int ms); + static int startDragTime(); + static void setStartDragDistance(int l); + static int startDragDistance(); + + static void setLayoutDirection(Qt::LayoutDirection direction); + static Qt::LayoutDirection layoutDirection(); + + static inline bool isRightToLeft() { return layoutDirection() == Qt::RightToLeft; } + static inline bool isLeftToRight() { return layoutDirection() == Qt::LeftToRight; } + + static bool isEffectEnabled(Qt::UIEffect); + static void setEffectEnabled(Qt::UIEffect, bool enable = true); + +#if defined(Q_WS_MAC) + virtual bool macEventFilter(EventHandlerCallRef, EventRef); +#endif +#if defined(Q_WS_X11) + virtual bool x11EventFilter(XEvent *); + virtual int x11ClientMessage(QWidget*, XEvent*, bool passive_only); + int x11ProcessEvent(XEvent*); +#endif +#if defined(Q_OS_SYMBIAN) + int symbianProcessEvent(const QSymbianEvent *event); + virtual bool symbianEventFilter(const QSymbianEvent *event); +#endif +#if defined(Q_WS_QWS) + virtual bool qwsEventFilter(QWSEvent *); + int qwsProcessEvent(QWSEvent*); + void qwsSetCustomColors(QRgb *colortable, int start, int numColors); +#ifndef QT_NO_QWS_MANAGER + static QDecoration &qwsDecoration(); + static void qwsSetDecoration(QDecoration *); + static QDecoration *qwsSetDecoration(const QString &decoration); +#endif +#endif + +#if defined(Q_WS_QPA) + static QPlatformNativeInterface *platformNativeInterface(); +#endif + + +#if defined(Q_WS_WIN) + void winFocus(QWidget *, bool); + static void winMouseButtonUp(); +#endif +#ifndef QT_NO_SESSIONMANAGER + // session management + bool isSessionRestored() const; + QString sessionId() const; + QString sessionKey() const; + virtual void commitData(QSessionManager& sm); + virtual void saveState(QSessionManager& sm); +#endif + +#ifndef QT_NO_IM + void setInputContext(QInputContext *); + QInputContext *inputContext() const; +#endif + + static QLocale keyboardInputLocale(); + static Qt::LayoutDirection keyboardInputDirection(); + + static int exec(); + bool notify(QObject *, QEvent *); + + + static void setQuitOnLastWindowClosed(bool quit); + static bool quitOnLastWindowClosed(); + +#ifdef QT_KEYPAD_NAVIGATION + static Q_DECL_DEPRECATED void setKeypadNavigationEnabled(bool); + static bool keypadNavigationEnabled(); + static void setNavigationMode(Qt::NavigationMode mode); + static Qt::NavigationMode navigationMode(); +#endif + +Q_SIGNALS: + void lastWindowClosed(); + void focusChanged(QWidget *old, QWidget *now); + void fontDatabaseChanged(); +#ifndef QT_NO_SESSIONMANAGER + void commitDataRequest(QSessionManager &sessionManager); + void saveStateRequest(QSessionManager &sessionManager); +#endif + +public: + QString styleSheet() const; +public Q_SLOTS: +#ifndef QT_NO_STYLE_STYLESHEET + void setStyleSheet(const QString& sheet); +#endif +#ifdef Q_WS_WINCE + void setAutoMaximizeThreshold(const int threshold); + int autoMaximizeThreshold() const; +#endif + void setAutoSipEnabled(const bool enabled); + bool autoSipEnabled() const; + static void closeAllWindows(); + static void aboutQt(); + +protected: +#if defined(Q_WS_QWS) + void setArgs(int, char **); +#endif + bool event(QEvent *); + bool compressEvent(QEvent *, QObject *receiver, QPostEventList *); + +#ifdef QT3_SUPPORT +public: + static inline QT3_SUPPORT void setReverseLayout(bool b) { setLayoutDirection(b?Qt::RightToLeft:Qt::LeftToRight); } + static inline bool QT3_SUPPORT reverseLayout() { return layoutDirection() == Qt::RightToLeft; } + static QT3_SUPPORT Qt::Alignment horizontalAlignment(Qt::Alignment align); + typedef int ColorMode; + enum { NormalColors = NormalColor, CustomColors = CustomColor }; + static inline QT3_SUPPORT ColorMode colorMode() { return static_cast(colorSpec()); } + static inline QT3_SUPPORT void setColorMode(ColorMode mode) { setColorSpec(int(mode)); } +#if defined(Q_OS_WIN32) || defined(Q_OS_CYGWIN) + static QT3_SUPPORT Qt::WindowsVersion winVersion() { return (Qt::WindowsVersion)QSysInfo::WindowsVersion; } +#endif +#if defined(Q_OS_MAC) + static QT3_SUPPORT Qt::MacintoshVersion macVersion() { return (Qt::MacintoshVersion)QSysInfo::MacintoshVersion; } +#endif +# ifndef QT_NO_CURSOR + inline static QT3_SUPPORT void setOverrideCursor(const QCursor &cursor, bool replace) + { if (replace) changeOverrideCursor(cursor); else setOverrideCursor(cursor); } +# endif + inline static QT3_SUPPORT bool hasGlobalMouseTracking() {return true;} + inline static QT3_SUPPORT void setGlobalMouseTracking(bool) {} + inline static QT3_SUPPORT void flushX() { flush(); } + static inline QT3_SUPPORT void setWinStyleHighlightColor(const QColor &c) { + QPalette p(palette()); + p.setColor(QPalette::Highlight, c); + setPalette(p); + } + static inline QT3_SUPPORT const QColor &winStyleHighlightColor() + { return palette().color(QPalette::Active, QPalette::Highlight); } + static inline QT3_SUPPORT void setPalette(const QPalette &pal, bool, const char* className = 0) + { setPalette(pal, className); } + static inline QT3_SUPPORT void setFont(const QFont &font, bool, const char* className = 0) + { setFont(font, className); } + + static inline QT3_SUPPORT QWidget *widgetAt(int x, int y, bool child) + { QWidget *w = widgetAt(x, y); return child ? w : (w ? w->window() : 0); } + static inline QT3_SUPPORT QWidget *widgetAt(const QPoint &p, bool child) + { QWidget *w = widgetAt(p); return child ? w : (w ? w->window() : 0); } +#endif // QT3_SUPPORT + +#if defined(Q_INTERNAL_QAPP_SRC) || defined(qdoc) + QApplication(int &argc, char **argv); + QApplication(int &argc, char **argv, bool GUIenabled); + QApplication(int &argc, char **argv, Type); +#if defined(Q_WS_X11) + QApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0); + QApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); +#endif +#if defined(Q_OS_SYMBIAN) || defined(qdoc) + QApplication(QApplication::QS60MainApplicationFactory factory, int &argc, char **argv); +#endif +#endif + +private: + Q_DISABLE_COPY(QApplication) + Q_DECLARE_PRIVATE(QApplication) + + friend class QGraphicsWidget; + friend class QGraphicsItem; + friend class QGraphicsScene; + friend class QGraphicsScenePrivate; + friend class QWidget; + friend class QWidgetPrivate; + friend class QETWidget; + friend class Q3AccelManager; + friend class QTranslator; + friend class QWidgetAnimator; +#ifndef QT_NO_SHORTCUT + friend class QShortcut; + friend class QLineEdit; + friend class QTextControl; +#endif + friend class QAction; + friend class QFontDatabasePrivate; + +#if defined(Q_WS_QWS) + friend class QInputContext; + friend class QWSDirectPainterSurface; + friend class QDirectPainter; + friend class QDirectPainterPrivate; +#endif +#ifndef QT_NO_GESTURES + friend class QGestureManager; +#endif + +#if defined(Q_WS_MAC) || defined(Q_WS_X11) + Q_PRIVATE_SLOT(d_func(), void _q_alertTimeOut()) +#endif +#if defined(QT_RX71_MULTITOUCH) + Q_PRIVATE_SLOT(d_func(), void _q_readRX71MultiTouchEvents()) +#endif +#if defined(Q_OS_SYMBIAN) + Q_PRIVATE_SLOT(d_func(), void _q_aboutToQuit()) +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QAPPLICATION_H diff --git a/src/gui/kernel/qapplication_mac.mm b/src/gui/kernel/qapplication_mac.mm new file mode 100644 index 0000000000..f607a72e92 --- /dev/null +++ b/src/gui/kernel/qapplication_mac.mm @@ -0,0 +1,3134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include + +#include "qapplication.h" +#include "qbitarray.h" +#include "qclipboard.h" +#include "qcursor.h" +#include "qdatastream.h" +#include "qdatetime.h" +#include "qdesktopwidget.h" +#include "qdockwidget.h" +#include "qevent.h" +#include "qhash.h" +#include "qlayout.h" +#include "qmenubar.h" +#include "qmessagebox.h" +#include "qmime.h" +#include "qpixmapcache.h" +#include "qpointer.h" +#include "qsessionmanager.h" +#include "qsettings.h" +#include "qsocketnotifier.h" +#include "qstyle.h" +#include "qstylefactory.h" +#include "qtextcodec.h" +#include "qtoolbar.h" +#include "qvariant.h" +#include "qwidget.h" +#include "qcolormap.h" +#include "qdir.h" +#include "qdebug.h" +#include "qtimer.h" +#include "qurl.h" +#include "private/qmacinputcontext_p.h" +#include "private/qpaintengine_mac_p.h" +#include "private/qcursor_p.h" +#include "private/qapplication_p.h" +#include "private/qcolor_p.h" +#include "private/qwidget_p.h" +#include "private/qkeymapper_p.h" +#include "private/qeventdispatcher_mac_p.h" +#include "private/qeventdispatcher_unix_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#ifndef QT_NO_THREAD +# include "qmutex.h" +#endif + +#include +#include +#include +#include + +/***************************************************************************** + QApplication debug facilities + *****************************************************************************/ +//#define DEBUG_EVENTS //like EventDebug but more specific to Qt +//#define DEBUG_DROPPED_EVENTS +//#define DEBUG_MOUSE_MAPS +//#define DEBUG_MODAL_EVENTS +//#define DEBUG_PLATFORM_SETTINGS + +#define QMAC_SPEAK_TO_ME +#ifdef QMAC_SPEAK_TO_ME +#include "qregexp.h" +#endif + +#ifndef kThemeBrushAlternatePrimaryHighlightColor +#define kThemeBrushAlternatePrimaryHighlightColor -5 +#endif + +#define kCMDeviceUnregisteredNotification CFSTR("CMDeviceUnregisteredNotification") +#define kCMDefaultDeviceNotification CFSTR("CMDefaultDeviceNotification") +#define kCMDeviceProfilesNotification CFSTR("CMDeviceProfilesNotification") +#define kCMDefaultDeviceProfileNotification CFSTR("CMDefaultDeviceProfileNotification") + +QT_BEGIN_NAMESPACE + +//for qt_mac.h +QPaintDevice *qt_mac_safe_pdev = 0; +QList *QMacWindowChangeEvent::change_events = 0; +QPointer topLevelAt_cache = 0; + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ +static struct { + bool use_qt_time_limit; + QPointer last_widget; + int last_x, last_y; + int last_modifiers, last_button; + EventTime last_time; +} qt_mac_dblclick = { false, 0, -1, -1, 0, 0, -2 }; + +static bool app_do_modal = false; // modal mode +extern QWidgetList *qt_modal_stack; // stack of modal widgets +extern bool qt_tab_all_widgets; // from qapplication.cpp +bool qt_mac_app_fullscreen = false; +bool qt_scrollbar_jump_to_pos = false; +static bool qt_mac_collapse_on_dblclick = true; +extern int qt_antialiasing_threshold; // from qapplication.cpp +QWidget * qt_button_down; // widget got last button-down +QPointer qt_last_mouse_receiver; +#ifndef QT_MAC_USE_COCOA +static bool qt_button_down_in_content; // whether the button_down was in the content area. +static bool qt_mac_previous_press_in_popup_mode = false; +static bool qt_mac_no_click_through_mode = false; +static int tablet_button_state = 0; +#endif +#if defined(QT_DEBUG) +static bool appNoGrab = false; // mouse/keyboard grabbing +#endif +#ifndef QT_MAC_USE_COCOA +static EventHandlerRef app_proc_handler = 0; +static EventHandlerUPP app_proc_handlerUPP = 0; +#endif +static AEEventHandlerUPP app_proc_ae_handlerUPP = NULL; +static EventHandlerRef tablet_proximity_handler = 0; +static EventHandlerUPP tablet_proximity_UPP = 0; +bool QApplicationPrivate::native_modal_dialog_active; + +Q_GUI_EXPORT bool qt_applefontsmoothing_enabled; + +/***************************************************************************** + External functions + *****************************************************************************/ +extern void qt_mac_beep(); //qsound_mac.mm +extern Qt::KeyboardModifiers qt_mac_get_modifiers(int keys); //qkeymapper_mac.cpp +extern bool qt_mac_can_clickThrough(const QWidget *); //qwidget_mac.cpp +extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp +extern OSWindowRef qt_mac_window_for(const QWidget*); //qwidget_mac.cpp +extern QWidget *qt_mac_find_window(OSWindowRef); //qwidget_mac.cpp +extern void qt_mac_set_cursor(const QCursor *); //qcursor_mac.cpp +extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp +extern QString qt_mac_from_pascal_string(const Str255); //qglobal.cpp +extern void qt_mac_command_set_enabled(MenuRef, UInt32, bool); //qmenu_mac.cpp +extern bool qt_sendSpontaneousEvent(QObject *obj, QEvent *event); // qapplication.cpp +extern void qt_mac_update_cursor(); // qcursor_mac.mm + +// Forward Decls +void onApplicationWindowChangedActivation( QWidget*widget, bool activated ); +void onApplicationChangedActivation( bool activated ); + +static void qt_mac_read_fontsmoothing_settings() +{ + qt_applefontsmoothing_enabled = true; + int w = 10, h = 10; + QImage image(w, h, QImage::Format_RGB32); + image.fill(0xffffffff); + QPainter p(&image); + p.drawText(0, h, "X\\"); + p.end(); + + const int *bits = (const int *) ((const QImage &) image).bits(); + int bpl = image.bytesPerLine() / 4; + for (int y=0; y= MAC_OS_X_VERSION_10_5) + const bool resized = flags & kCGDisplayDesktopShapeChangedFlag; +#else + Q_UNUSED(flags); + const bool resized = true; +#endif + if (resized && qApp) { + if (QDesktopWidget *dw = qApp->desktop()) { + QResizeEvent *re = new QResizeEvent(dw->size(), dw->size()); + QApplication::postEvent(dw, re); + QCoreGraphicsPaintEngine::cleanUpMacColorSpaces(); + } + } +} + +#ifdef DEBUG_PLATFORM_SETTINGS +static void qt_mac_debug_palette(const QPalette &pal, const QPalette &pal2, const QString &where) +{ + const char *const groups[] = {"Active", "Disabled", "Inactive" }; + const char *const roles[] = { "WindowText", "Button", "Light", "Midlight", "Dark", "Mid", + "Text", "BrightText", "ButtonText", "Base", "Window", "Shadow", + "Highlight", "HighlightedText", "Link", "LinkVisited" }; + if (!where.isNull()) + qDebug("qt-internal: %s", where.toLatin1().constData()); + for(int grp = 0; grp < QPalette::NColorGroups; grp++) { + for(int role = 0; role < QPalette::NColorRoles; role++) { + QBrush b = pal.brush((QPalette::ColorGroup)grp, (QPalette::ColorRole)role); + QPixmap pm = b.texture(); + qDebug(" %s::%s %d::%d::%d [%p]%s", groups[grp], roles[role], b.color().red(), + b.color().green(), b.color().blue(), pm.isNull() ? 0 : &pm, + pal2.brush((QPalette::ColorGroup)grp, (QPalette::ColorRole)role) != b ? " (*)" : ""); + } + } + +} +#else +#define qt_mac_debug_palette(x, y, z) +#endif + +//raise a notification +#ifndef QT_MAC_USE_COCOA +static NMRec qt_mac_notification = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif +void qt_mac_send_notification() +{ +#ifndef QT_MAC_USE_COCOA + //send it + qt_mac_notification.nmMark = 1; //non-zero magic number + qt_mac_notification.qType = nmType; + NMInstall(&qt_mac_notification); +#else + QMacCocoaAutoReleasePool pool; + [[NSApplication sharedApplication] requestUserAttention:NSInformationalRequest]; +#endif +} + +void qt_mac_cancel_notification() +{ +#ifndef QT_MAC_USE_COCOA + NMRemove(&qt_mac_notification); +#else + QMacCocoaAutoReleasePool pool; + [[NSApplication sharedApplication] cancelUserAttentionRequest:NSInformationalRequest]; +#endif +} + +#ifndef QT_MAC_USE_COCOA +//find widget (and part) at a given point +static short qt_mac_window_at(int x, int y, QWidget **w=0) +{ + Point p; + p.h = x; + p.v = y; + OSWindowRef wp; + WindowPartCode wpc; + OSStatus err = FindWindowOfClass(&p, kAllWindowClasses, &wp, &wpc); + if(err != noErr) { + if(w) + (*w) = 0; + return wpc; + } + if(w) { + if(wp) { + *w = qt_mac_find_window(wp); +#if 0 + if(!*w) + qWarning("QApplication: qt_mac_window_at: Couldn't find %d",(int)wp); +#endif + } else { + *w = 0; + } + } + return wpc; +} + +#endif + +void qt_mac_set_app_icon(const QPixmap &pixmap) +{ +#ifndef QT_MAC_USE_COCOA + if(pixmap.isNull()) { + RestoreApplicationDockTileImage(); + } else { + CGImageRef img = (CGImageRef)pixmap.macCGHandle(); + SetApplicationDockTileImage(img); + CGImageRelease(img); + } +#else + QMacCocoaAutoReleasePool pool; + NSImage *image = NULL; + if (pixmap.isNull()) { + // Get Application icon from bundle + image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; // released below + } else { + image = static_cast(qt_mac_create_nsimage(pixmap)); + } + + [NSApp setApplicationIconImage:image]; + [image release]; +#endif +} + +Q_GUI_EXPORT void qt_mac_set_press_and_hold_context(bool b) +{ + Q_UNUSED(b); + qWarning("qt_mac_set_press_and_hold_context: This functionality is no longer available"); +} + +bool qt_nograb() // application no-grab option +{ +#if defined(QT_DEBUG) + return appNoGrab; +#else + return false; +#endif +} + +void qt_mac_update_os_settings() +{ + if (!qApp) + return; + if (!QApplication::startingUp()) { + static bool needToPolish = true; + if (needToPolish) { + QApplication::style()->polish(qApp); + needToPolish = false; + } + } + //focus mode + /* First worked as of 10.2.3 */ + QSettings appleSettings(QLatin1String("apple.com")); + QVariant appleValue = appleSettings.value(QLatin1String("AppleKeyboardUIMode"), 0); + qt_tab_all_widgets = (appleValue.toInt() & 0x2); + //paging mode + /* First worked as of 10.2.3 */ + appleValue = appleSettings.value(QLatin1String("AppleScrollerPagingBehavior"), false); + qt_scrollbar_jump_to_pos = appleValue.toBool(); + //collapse + /* First worked as of 10.3.3 */ + appleValue = appleSettings.value(QLatin1String("AppleMiniaturizeOnDoubleClick"), true); + qt_mac_collapse_on_dblclick = appleValue.toBool(); + + // Anti-aliasing threshold + appleValue = appleSettings.value(QLatin1String("AppleAntiAliasingThreshold")); + if (appleValue.isValid()) + qt_antialiasing_threshold = appleValue.toInt(); + +#ifdef DEBUG_PLATFORM_SETTINGS + qDebug("qt_mac_update_os_settings *********************************************************************"); +#endif + { // setup the global palette + QColor qc; + (void) QApplication::style(); // trigger creation of application style and system palettes + QPalette pal = *QApplicationPrivate::sys_pal; + + pal.setBrush( QPalette::Active, QPalette::Highlight, qcolorForTheme(kThemeBrushPrimaryHighlightColor) ); + pal.setBrush( QPalette::Inactive, QPalette::Highlight, qcolorForTheme(kThemeBrushSecondaryHighlightColor) ); + + pal.setBrush( QPalette::Disabled, QPalette::Highlight, qcolorForTheme(kThemeBrushSecondaryHighlightColor) ); + pal.setBrush( QPalette::Active, QPalette::Shadow, qcolorForTheme(kThemeBrushButtonActiveDarkShadow) ); + + pal.setBrush( QPalette::Inactive, QPalette::Shadow, qcolorForTheme(kThemeBrushButtonInactiveDarkShadow) ); + pal.setBrush( QPalette::Disabled, QPalette::Shadow, qcolorForTheme(kThemeBrushButtonInactiveDarkShadow) ); + + qc = qcolorForThemeTextColor(kThemeTextColorDialogActive); + pal.setColor(QPalette::Active, QPalette::Text, qc); + pal.setColor(QPalette::Active, QPalette::WindowText, qc); + pal.setColor(QPalette::Active, QPalette::HighlightedText, qc); + + qc = qcolorForThemeTextColor(kThemeTextColorDialogInactive); + pal.setColor(QPalette::Inactive, QPalette::Text, qc); + pal.setColor(QPalette::Inactive, QPalette::WindowText, qc); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc); + pal.setColor(QPalette::Disabled, QPalette::Text, qc); + pal.setColor(QPalette::Disabled, QPalette::WindowText, qc); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, qc); + pal.setBrush(QPalette::ToolTipBase, QColor(255, 255, 199)); + + if (!QApplicationPrivate::sys_pal || *QApplicationPrivate::sys_pal != pal) { + QApplicationPrivate::setSystemPalette(pal); + QApplication::setPalette(pal); + } +#ifdef DEBUG_PLATFORM_SETTINGS + qt_mac_debug_palette(pal, QApplication::palette(), "Global Palette"); +#endif + } + + QFont fnt = qfontForThemeFont(kThemeApplicationFont); +#ifdef DEBUG_PLATFORM_SETTINGS + qDebug("qt-internal: Font for Application [%s::%d::%d::%d]", + fnt.family().toLatin1().constData(), fnt.pointSize(), fnt.bold(), fnt.italic()); +#endif + if (!QApplicationPrivate::sys_font || *QApplicationPrivate::sys_font != fnt) + QApplicationPrivate::setSystemFont(fnt); + + { //setup the fonts + struct FontMap { + FontMap(const char *qc, short fk) : qt_class(qc), font_key(fk) { } + const char *const qt_class; + short font_key; + } mac_widget_fonts[] = { + FontMap("QPushButton", kThemePushButtonFont), + FontMap("QListView", kThemeViewsFont), + FontMap("QListBox", kThemeViewsFont), + FontMap("QTitleBar", kThemeWindowTitleFont), + FontMap("QMenuBar", kThemeMenuTitleFont), + FontMap("QMenu", kThemeMenuItemFont), + FontMap("QComboMenuItem", kThemeSystemFont), + FontMap("QHeaderView", kThemeSmallSystemFont), + FontMap("Q3Header", kThemeSmallSystemFont), + FontMap("QTipLabel", kThemeSmallSystemFont), + FontMap("QLabel", kThemeSystemFont), + FontMap("QToolButton", kThemeSmallSystemFont), + FontMap("QMenuItem", kThemeMenuItemFont), // It doesn't exist, but its unique. + FontMap("QComboLineEdit", kThemeViewsFont), // It doesn't exist, but its unique. + FontMap("QSmallFont", kThemeSmallSystemFont), // It doesn't exist, but its unique. + FontMap("QMiniFont", kThemeMiniSystemFont), // It doesn't exist, but its unique. + FontMap(0, 0) }; + for(int i = 0; mac_widget_fonts[i].qt_class; i++) { + QFont fnt = qfontForThemeFont(mac_widget_fonts[i].font_key); + bool set_font = true; + FontHash *hash = qt_app_fonts_hash(); + if (!hash->isEmpty()) { + FontHash::const_iterator it + = hash->constFind(mac_widget_fonts[i].qt_class); + if (it != hash->constEnd()) + set_font = (fnt != *it); + } + if (set_font) { + QApplication::setFont(fnt, mac_widget_fonts[i].qt_class); +#ifdef DEBUG_PLATFORM_SETTINGS + qDebug("qt-internal: Font for %s [%s::%d::%d::%d]", mac_widget_fonts[i].qt_class, + fnt.family().toLatin1().constData(), fnt.pointSize(), fnt.bold(), fnt.italic()); +#endif + } + } + } + QApplicationPrivate::initializeWidgetPaletteHash(); +#ifdef DEBUG_PLATFORM_SETTINGS + qDebug("qt_mac_update_os_settings END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); +#endif +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ + { //setup the palette + struct PaletteMap { + inline PaletteMap(const char *qc, ThemeBrush a, ThemeBrush i) : + qt_class(qc), active(a), inactive(i) { } + const char *const qt_class; + ThemeBrush active, inactive; + } mac_widget_colors[] = { + PaletteMap("QToolButton", kThemeTextColorBevelButtonActive, kThemeTextColorBevelButtonInactive), + PaletteMap("QAbstractButton", kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), + PaletteMap("QHeaderView", kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), + PaletteMap("Q3Header", kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), + PaletteMap("QComboBox", kThemeTextColorPopupButtonActive, kThemeTextColorPopupButtonInactive), + PaletteMap("QAbstractItemView", kThemeTextColorListView, kThemeTextColorDialogInactive), + PaletteMap("QMessageBoxLabel", kThemeTextColorAlertActive, kThemeTextColorAlertInactive), + PaletteMap("QTabBar", kThemeTextColorTabFrontActive, kThemeTextColorTabFrontInactive), + PaletteMap("QLabel", kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), + PaletteMap("QGroupBox", kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), + PaletteMap("QMenu", kThemeTextColorPopupLabelActive, kThemeTextColorPopupLabelInactive), + PaletteMap("QTextEdit", 0, 0), + PaletteMap("QTextControl", 0, 0), + PaletteMap("QLineEdit", 0, 0), + PaletteMap(0, 0, 0) }; + QColor qc; + for(int i = 0; mac_widget_colors[i].qt_class; i++) { + QPalette pal; + if (mac_widget_colors[i].active != 0) { + qc = qcolorForThemeTextColor(mac_widget_colors[i].active); + pal.setColor(QPalette::Active, QPalette::Text, qc); + pal.setColor(QPalette::Active, QPalette::WindowText, qc); + pal.setColor(QPalette::Active, QPalette::HighlightedText, qc); + qc = qcolorForThemeTextColor(mac_widget_colors[i].inactive); + pal.setColor(QPalette::Inactive, QPalette::Text, qc); + pal.setColor(QPalette::Disabled, QPalette::Text, qc); + pal.setColor(QPalette::Inactive, QPalette::WindowText, qc); + pal.setColor(QPalette::Disabled, QPalette::WindowText, qc); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, qc); + } + if (!strcmp(mac_widget_colors[i].qt_class, "QMenu")) { + qc = qcolorForThemeTextColor(kThemeTextColorMenuItemActive); + pal.setBrush(QPalette::ButtonText, qc); + qc = qcolorForThemeTextColor(kThemeTextColorMenuItemSelected); + pal.setBrush(QPalette::HighlightedText, qc); + qc = qcolorForThemeTextColor(kThemeTextColorMenuItemDisabled); + pal.setBrush(QPalette::Disabled, QPalette::Text, qc); + } else if (!strcmp(mac_widget_colors[i].qt_class, "QAbstractButton") + || !strcmp(mac_widget_colors[i].qt_class, "QHeaderView") + || !strcmp(mac_widget_colors[i].qt_class, "Q3Header")) { //special + pal.setColor(QPalette::Disabled, QPalette::ButtonText, + pal.color(QPalette::Disabled, QPalette::Text)); + pal.setColor(QPalette::Inactive, QPalette::ButtonText, + pal.color(QPalette::Inactive, QPalette::Text)); + pal.setColor(QPalette::Active, QPalette::ButtonText, + pal.color(QPalette::Active, QPalette::Text)); + } else if (!strcmp(mac_widget_colors[i].qt_class, "QAbstractItemView")) { + pal.setBrush(QPalette::Active, QPalette::Highlight, + qcolorForTheme(kThemeBrushAlternatePrimaryHighlightColor)); + qc = qcolorForThemeTextColor(kThemeTextColorMenuItemSelected); + pal.setBrush(QPalette::Active, QPalette::HighlightedText, qc); +#if 1 + pal.setBrush(QPalette::Inactive, QPalette::Text, + pal.brush(QPalette::Active, QPalette::Text)); + pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, + pal.brush(QPalette::Active, QPalette::Text)); +#endif + } else if (!strcmp(mac_widget_colors[i].qt_class, "QTextEdit") + || !strcmp(mac_widget_colors[i].qt_class, "QTextControl")) { + pal.setBrush(QPalette::Inactive, QPalette::Text, + pal.brush(QPalette::Active, QPalette::Text)); + pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, + pal.brush(QPalette::Active, QPalette::Text)); + } else if (!strcmp(mac_widget_colors[i].qt_class, "QLineEdit")) { + pal.setBrush(QPalette::Disabled, QPalette::Base, + pal.brush(QPalette::Active, QPalette::Base)); + } + + bool set_palette = true; + PaletteHash *phash = qt_app_palettes_hash(); + if (!phash->isEmpty()) { + PaletteHash::const_iterator it + = phash->constFind(mac_widget_colors[i].qt_class); + if (it != phash->constEnd()) + set_palette = (pal != *it); + } + if (set_palette) { + QApplication::setPalette(pal, mac_widget_colors[i].qt_class); +#ifdef DEBUG_PLATFORM_SETTINGS + qt_mac_debug_palette(pal, QApplication::palette(), QLatin1String("Palette for ") + QString::fromLatin1(mac_widget_colors[i].qt_class)); +#endif + } + } + } +} + +static void qt_mac_event_release(EventRef &event) +{ + ReleaseEvent(event); + event = 0; +} +#ifndef QT_MAC_USE_COCOA +static void qt_mac_event_release(QWidget *w, EventRef &event) +{ + if (event) { + QWidget *widget = 0; + if (GetEventParameter(event, kEventParamQWidget, typeQWidget, 0, sizeof(widget), 0, &widget) == noErr + && w == widget) { + if (IsEventInQueue(GetMainEventQueue(), event)) + RemoveEventFromQueue(GetMainEventQueue(), event); + qt_mac_event_release(event); + } + } +} + +static bool qt_mac_event_remove(EventRef &event) +{ + if (event) { + if (IsEventInQueue(GetMainEventQueue(), event)) + RemoveEventFromQueue(GetMainEventQueue(), event); + qt_mac_event_release(event); + return true; + } + return false; +} +#endif + +/* sheets */ +#ifndef QT_MAC_USE_COCOA +static EventRef request_showsheet_pending = 0; +#endif +void qt_event_request_showsheet(QWidget *w) +{ + Q_ASSERT(qt_mac_is_macsheet(w)); +#ifdef QT_MAC_USE_COCOA + [NSApp beginSheet:qt_mac_window_for(w) modalForWindow:qt_mac_window_for(w->parentWidget()) + modalDelegate:nil didEndSelector:nil contextInfo:0]; +#else + qt_mac_event_remove(request_showsheet_pending); + CreateEvent(0, kEventClassQt, kEventQtRequestShowSheet, GetCurrentEventTime(), + kEventAttributeUserEvent, &request_showsheet_pending); + SetEventParameter(request_showsheet_pending, kEventParamQWidget, typeQWidget, sizeof(w), &w); + PostEventToQueue(GetMainEventQueue(), request_showsheet_pending, kEventPriorityStandard); +#endif +} + +static void qt_post_window_change_event(QWidget *widget) +{ + qt_widget_private(widget)->needWindowChange = true; + QEvent *glWindowChangeEvent = new QEvent(QEvent::MacGLWindowChange); + QApplication::postEvent(widget, glWindowChangeEvent); +} + +/* + Posts updates to all child and grandchild OpenGL widgets for the given widget. +*/ +static void qt_mac_update_child_gl_widgets(QWidget *widget) +{ + // Update all OpenGL child widgets for the given widget. + QList &glWidgets = qt_widget_private(widget)->glWidgets; + QList::iterator end = glWidgets.end(); + QList::iterator it = glWidgets.begin(); + + for (;it != end; ++it) { + qt_post_window_change_event(it->widget); + } +} + +/* + Sends updates to all child and grandchild gl widgets that have updates pending. +*/ +void qt_mac_send_posted_gl_updates(QWidget *widget) +{ + QList &glWidgets = qt_widget_private(widget)->glWidgets; + QList::iterator end = glWidgets.end(); + QList::iterator it = glWidgets.begin(); + + for (;it != end; ++it) { + QWidget *glWidget = it->widget; + if (qt_widget_private(glWidget)->needWindowChange) { + QEvent glChangeEvent(QEvent::MacGLWindowChange); + QApplication::sendEvent(glWidget, &glChangeEvent); + } + } +} + +/* + Posts updates to all OpenGL widgets within the window that the given widget intersects. +*/ +static void qt_mac_update_intersected_gl_widgets(QWidget *widget) +{ +#ifndef QT_MAC_USE_COCOA + QList &glWidgets = qt_widget_private(widget->window())->glWidgets; + if (glWidgets.isEmpty()) + return; + + // Exit if the window has not been created yet (mapToGlobal/size will force create it) + if (widget->testAttribute(Qt::WA_WState_Created) == false || HIViewGetWindow(qt_mac_nativeview_for(widget)) == 0) + return; + + const QRect globalWidgetRect = QRect(widget->mapToGlobal(QPoint(0, 0)), widget->size()); + + QList::iterator end = glWidgets.end(); + QList::iterator it = glWidgets.begin(); + + for (;it != end; ++it){ + QWidget *glWidget = it->widget; + const QRect globalGlWidgetRect = QRect(glWidget->mapToGlobal(QPoint(0, 0)), glWidget->size()); + if (globalWidgetRect.intersects(globalGlWidgetRect)) { + qt_post_window_change_event(glWidget); + it->lastUpdateWidget = widget; + } else if (it->lastUpdateWidget == widget) { + // Update the gl wigets that the widget intersected the last time around, + // and that we are not intersecting now. This prevents paint errors when the + // intersecting widget leaves a gl widget. + qt_post_window_change_event(glWidget); + it->lastUpdateWidget = 0; + } + } +#else + Q_UNUSED(widget); +#endif +} + +/* + Posts a kEventQtRequestWindowChange event to the main Carbon event queue. +*/ +static EventRef request_window_change_pending = 0; +Q_GUI_EXPORT void qt_event_request_window_change() +{ + if(request_window_change_pending) + return; + + CreateEvent(0, kEventClassQt, kEventQtRequestWindowChange, GetCurrentEventTime(), + kEventAttributeUserEvent, &request_window_change_pending); + PostEventToQueue(GetMainEventQueue(), request_window_change_pending, kEventPriorityHigh); +} + +/* window changing. This is a hack around Apple's missing functionality, pending the toolbox + team fix. --Sam */ +Q_GUI_EXPORT void qt_event_request_window_change(QWidget *widget) +{ + if (!widget) + return; + + // Post a kEventQtRequestWindowChange event. This event is semi-public, + // don't remove this line! + qt_event_request_window_change(); + + // Post update request on gl widgets unconditionally. + if (qt_widget_private(widget)->isGLWidget == true) { + qt_post_window_change_event(widget); + return; + } + + qt_mac_update_child_gl_widgets(widget); + qt_mac_update_intersected_gl_widgets(widget); +} + +/* activation */ +static struct { + QPointer widget; + EventRef event; + EventLoopTimerRef timer; + EventLoopTimerUPP timerUPP; +} request_activate_pending = { 0, 0, 0, 0 }; +bool qt_event_remove_activate() +{ + if (request_activate_pending.timer) { + RemoveEventLoopTimer(request_activate_pending.timer); + request_activate_pending.timer = 0; + } + if (request_activate_pending.event) + qt_mac_event_release(request_activate_pending.event); + return true; +} + +void qt_event_activate_timer_callbk(EventLoopTimerRef r, void *) +{ + EventLoopTimerRef otc = request_activate_pending.timer; + qt_event_remove_activate(); + if (r == otc && !request_activate_pending.widget.isNull()) { + const QWidget *tlw = request_activate_pending.widget->window(); + Qt::WindowType wt = tlw->windowType(); + if (tlw->isVisible() + && ((wt != Qt::Desktop && wt != Qt::Popup && wt != Qt::Tool) || tlw->isModal())) { + CreateEvent(0, kEventClassQt, kEventQtRequestActivate, GetCurrentEventTime(), + kEventAttributeUserEvent, &request_activate_pending.event); + PostEventToQueue(GetMainEventQueue(), request_activate_pending.event, kEventPriorityHigh); + } + } +} + +void qt_event_request_activate(QWidget *w) +{ + if (w == request_activate_pending.widget) + return; + + /* We put these into a timer because due to order of events being sent we need to be sure this + comes from inside of the event loop */ + qt_event_remove_activate(); + if (!request_activate_pending.timerUPP) + request_activate_pending.timerUPP = NewEventLoopTimerUPP(qt_event_activate_timer_callbk); + request_activate_pending.widget = w; + InstallEventLoopTimer(GetMainEventLoop(), 0, 0, request_activate_pending.timerUPP, 0, &request_activate_pending.timer); +} + + +/* menubars */ +#ifndef QT_MAC_USE_COCOA +static EventRef request_menubarupdate_pending = 0; +#endif +void qt_event_request_menubarupdate() +{ +#ifndef QT_MAC_USE_COCOA + if (request_menubarupdate_pending) { + if (IsEventInQueue(GetMainEventQueue(), request_menubarupdate_pending)) + return; +#ifdef DEBUG_DROPPED_EVENTS + qDebug("%s:%d Whoa, we dropped an event on the floor!", __FILE__, __LINE__); +#endif + } + + CreateEvent(0, kEventClassQt, kEventQtRequestMenubarUpdate, GetCurrentEventTime(), + kEventAttributeUserEvent, &request_menubarupdate_pending); + PostEventToQueue(GetMainEventQueue(), request_menubarupdate_pending, kEventPriorityHigh); +#else + // Just call this. The request has the benefit that we don't call this multiple times, but + // we can optimize this. + QMenuBar::macUpdateMenuBar(); +#endif +} + +#ifndef QT_MAC_USE_COCOA +//context menu +static EventRef request_context_pending = 0; +static void qt_event_request_context(QWidget *w=0, EventRef *where=0) +{ + if (!where) + where = &request_context_pending; + if (*where) + return; + CreateEvent(0, kEventClassQt, kEventQtRequestContext, GetCurrentEventTime(), + kEventAttributeUserEvent, where); + if (w) + SetEventParameter(*where, kEventParamQWidget, typeQWidget, sizeof(w), &w); + PostEventToQueue(GetMainEventQueue(), *where, kEventPriorityStandard); +} +#endif + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); + if (q->type() != QApplication::Tty) + eventDispatcher = new QEventDispatcherMac(q); + else + eventDispatcher = new QEventDispatcherUNIX(q); +} + +/* clipboard */ +void qt_event_send_clipboard_changed() +{ +#ifndef QT_MAC_USE_COCOA + AppleEvent ae; + if (AECreateAppleEvent(kEventClassQt, typeAEClipboardChanged, 0, kAutoGenerateReturnID, kAnyTransactionID, &ae) != noErr) + qDebug("Can't happen!!"); + AppleEvent reply; + AESend(&ae, &reply, kAENoReply, kAENormalPriority, kAEDefaultTimeout, 0, 0); +#endif +} + +/* app menu */ +static QMenu *qt_mac_dock_menu = 0; +Q_GUI_EXPORT void qt_mac_set_dock_menu(QMenu *menu) +{ + qt_mac_dock_menu = menu; +#ifdef QT_MAC_USE_COCOA + [NSApp setDockMenu:menu->macMenu()]; +#else + SetApplicationDockTileMenu(menu->macMenu()); +#endif +} + +/* events that hold pointers to widgets, must be cleaned up like this */ +void qt_mac_event_release(QWidget *w) +{ + if (w) { +#ifndef QT_MAC_USE_COCOA + qt_mac_event_release(w, request_showsheet_pending); + qt_mac_event_release(w, request_context_pending); +#endif + if (w == qt_mac_dock_menu) { + qt_mac_dock_menu = 0; +#ifndef QT_MAC_USE_COCOA + SetApplicationDockTileMenu(0); +#else + [NSApp setDockMenu:0]; +#endif + } + } +} + +struct QMacAppleEventTypeSpec { + AEEventClass mac_class; + AEEventID mac_id; +} app_apple_events[] = { + { kCoreEventClass, kAEQuitApplication }, + { kCoreEventClass, kAEOpenDocuments }, + { kInternetEventClass, kAEGetURL }, +}; + +#ifndef QT_MAC_USE_COCOA + +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) +enum +{ + kEventMouseScroll = 11, + kEventParamMouseWheelSmoothVerticalDelta = 'saxy', + kEventParamMouseWheelSmoothHorizontalDelta = 'saxx', +}; +#endif + +/* watched events */ +static EventTypeSpec app_events[] = { + { kEventClassQt, kEventQtRequestWindowChange }, + { kEventClassQt, kEventQtRequestShowSheet }, + { kEventClassQt, kEventQtRequestContext }, + { kEventClassQt, kEventQtRequestActivate }, + { kEventClassQt, kEventQtRequestMenubarUpdate }, + + { kEventClassWindow, kEventWindowActivated }, + { kEventClassWindow, kEventWindowDeactivated }, + + { kEventClassMouse, kEventMouseScroll }, + { kEventClassMouse, kEventMouseWheelMoved }, + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged }, + { kEventClassMouse, kEventMouseMoved }, + + { kEventClassTablet, kEventTabletProximity }, + + { kEventClassApplication, kEventAppActivated }, + { kEventClassApplication, kEventAppDeactivated }, + { kEventClassApplication, kEventAppAvailableWindowBoundsChanged }, + + // { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyDown }, + + { kEventClassCommand, kEventCommandProcess }, + + { kEventClassAppleEvent, kEventAppleEvent }, + + { kAppearanceEventClass, kAEAppearanceChanged } +}; + +void qt_init_app_proc_handler() +{ + InstallEventHandler(GetApplicationEventTarget(), app_proc_handlerUPP, + GetEventTypeCount(app_events), app_events, (void *)qApp, + &app_proc_handler); +} +#endif // QT_MAC_USE_COCOA + +static void qt_init_tablet_proximity_handler() +{ + EventTypeSpec tabletProximityEvent = { kEventClassTablet, kEventTabletProximity }; + InstallEventHandler(GetEventMonitorTarget(), tablet_proximity_UPP, + 1, &tabletProximityEvent, qApp, &tablet_proximity_handler); +} + +static void qt_release_tablet_proximity_handler() +{ + RemoveEventHandler(tablet_proximity_handler); +} + +QString QApplicationPrivate::appName() const +{ + static QString applName; + if (applName.isEmpty()) { + applName = QCoreApplicationPrivate::macMenuBarName(); + ProcessSerialNumber psn; + if (applName.isEmpty() && qt_is_gui_used && GetCurrentProcess(&psn) == noErr) { + QCFString cfstr; + CopyProcessName(&psn, &cfstr); + applName = cfstr; + } + } + return applName; +} + +void qt_release_app_proc_handler() +{ +#ifndef QT_MAC_USE_COCOA + if (app_proc_handler) { + RemoveEventHandler(app_proc_handler); + app_proc_handler = 0; + } +#endif +} + +void qt_color_profile_changed(CFNotificationCenterRef, void *, CFStringRef, const void *, + CFDictionaryRef) +{ + QCoreGraphicsPaintEngine::cleanUpMacColorSpaces(); +} +/* platform specific implementations */ +void qt_init(QApplicationPrivate *priv, int) +{ + if (qt_is_gui_used) { + CGDisplayRegisterReconfigurationCallback(qt_mac_display_change_callbk, 0); + CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter(); + CFNotificationCenterAddObserver(center, qApp, qt_color_profile_changed, + kCMDeviceUnregisteredNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(center, qApp, qt_color_profile_changed, + kCMDefaultDeviceNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(center, qApp, qt_color_profile_changed, + kCMDeviceProfilesNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(center, qApp, qt_color_profile_changed, + kCMDefaultDeviceProfileNotification, 0, + CFNotificationSuspensionBehaviorDeliverImmediately); + ProcessSerialNumber psn; + if (GetCurrentProcess(&psn) == noErr) { + // Jambi needs to transform itself since most people aren't "used" + // to putting things in bundles, but other people may actually not + // want to tranform the process (running as a helper or something) + // so don't do that for them. This means checking both LSUIElement + // and LSBackgroundOnly. If you set them both... well, you + // shouldn't do that. + + bool forceTransform = true; + CFTypeRef value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSUIElement")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + // Officially it's supposed to be a string, a boolean makes sense, so we'll check. + // A number less so, but OK. + if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast(value)).toInt()); + else if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast(value)); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + + if (forceTransform) { + value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSBackgroundOnly")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast(value)); + else if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast(value)).toInt()); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + } + + + if (forceTransform) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } + } + } + + char **argv = priv->argv; + + // Get command line params + if (int argc = priv->argc) { + int i, j = 1; + QString passed_psn; + for(i=1; i < argc; i++) { + if (argv[i] && *argv[i] != '-') { + argv[j++] = argv[i]; + continue; + } + QByteArray arg(argv[i]); +#if defined(QT_DEBUG) + if (arg == "-nograb") + appNoGrab = !appNoGrab; + else +#endif // QT_DEBUG + if (arg.left(5) == "-psn_") { + passed_psn = QString::fromLatin1(arg.mid(6)); + } else { + argv[j++] = argv[i]; + } + } + if (j < priv->argc) { + priv->argv[j] = 0; + priv->argc = j; + } + + //special hack to change working directory (for an app bundle) when running from finder + if (!passed_psn.isNull() && QDir::currentPath() == QLatin1String("/")) { + QCFType bundleURL(CFBundleCopyBundleURL(CFBundleGetMainBundle())); + QString qbundlePath = QCFString(CFURLCopyFileSystemPath(bundleURL, + kCFURLPOSIXPathStyle)); + if (qbundlePath.endsWith(QLatin1String(".app"))) + QDir::setCurrent(qbundlePath.section(QLatin1Char('/'), 0, -2)); + } + } + + QMacPasteboardMime::initialize(); + + qApp->setObjectName(priv->appName()); + if (qt_is_gui_used) { + QColormap::initialize(); + QFont::initialize(); + QCursorData::initialize(); + QCoreGraphicsPaintEngine::initialize(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::initialize(); +#endif + QMacInputContext::initialize(); + QApplicationPrivate::inputContext = new QMacInputContext; + + if (QApplication::desktopSettingsAware()) + qt_mac_update_os_settings(); +#ifndef QT_MAC_USE_COCOA + if (!app_proc_handler) { + app_proc_handlerUPP = NewEventHandlerUPP(QApplicationPrivate::globalEventProcessor); + qt_init_app_proc_handler(); + } + +#endif + if (!app_proc_ae_handlerUPP && !QApplication::testAttribute(Qt::AA_MacPluginApplication)) { + app_proc_ae_handlerUPP = AEEventHandlerUPP(QApplicationPrivate::globalAppleEventProcessor); + for(uint i = 0; i < sizeof(app_apple_events) / sizeof(QMacAppleEventTypeSpec); ++i) { + // Install apple event handler, but avoid overwriting an already + // existing handler (it means a 3rd party application has installed one): + SRefCon refCon = 0; + AEEventHandlerUPP current_handler = NULL; + AEGetEventHandler(app_apple_events[i].mac_class, app_apple_events[i].mac_id, ¤t_handler, &refCon, false); + if (!current_handler) + AEInstallEventHandler(app_apple_events[i].mac_class, app_apple_events[i].mac_id, + app_proc_ae_handlerUPP, SRefCon(qApp), false); + } + } + + if (QApplicationPrivate::app_style) { + QEvent ev(QEvent::Style); + qt_sendSpontaneousEvent(QApplicationPrivate::app_style, &ev); + } + } + if (QApplication::desktopSettingsAware()) + QApplicationPrivate::qt_mac_apply_settings(); + + // Cocoa application delegate +#ifdef QT_MAC_USE_COCOA + NSApplication *cocoaApp = [QNSApplication sharedApplication]; + qt_redirectNSApplicationSendEvent(); + + QMacCocoaAutoReleasePool pool; + id oldDelegate = [cocoaApp delegate]; + QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *newDelegate = [QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate]; + Q_ASSERT(newDelegate); + [newDelegate setQtPrivate:priv]; + // Only do things that make sense to do once, otherwise we crash. + if (oldDelegate != newDelegate && !QApplication::testAttribute(Qt::AA_MacPluginApplication)) { + [newDelegate setReflectionDelegate:oldDelegate]; + [cocoaApp setDelegate:newDelegate]; + + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader = [[QT_MANGLE_NAMESPACE(QCocoaMenuLoader) alloc] init]; + if ([NSBundle loadNibNamed:@"qt_menu" owner:qtMenuLoader] == false) { + qFatal("Qt internal error: qt_menu.nib could not be loaded. The .nib file" + " should be placed in QtGui.framework/Versions/Current/Resources/ " + " or in the resources directory of your application bundle."); + } + + [cocoaApp setMenu:[qtMenuLoader menu]]; + [newDelegate setMenuLoader:qtMenuLoader]; + [qtMenuLoader release]; + } +#endif + // Register for Carbon tablet proximity events on the event monitor target. + // This means that we should receive proximity events even when we aren't the active application. + if (!tablet_proximity_handler) { + tablet_proximity_UPP = NewEventHandlerUPP(QApplicationPrivate::tabletProximityCallback); + qt_init_tablet_proximity_handler(); + } + priv->native_modal_dialog_active = false; + + qt_mac_read_fontsmoothing_settings(); +} + +void qt_release_apple_event_handler() +{ + if(app_proc_ae_handlerUPP) { + for(uint i = 0; i < sizeof(app_apple_events) / sizeof(QMacAppleEventTypeSpec); ++i) + AERemoveEventHandler(app_apple_events[i].mac_class, app_apple_events[i].mac_id, + app_proc_ae_handlerUPP, true); + DisposeAEEventHandlerUPP(app_proc_ae_handlerUPP); + app_proc_ae_handlerUPP = 0; + } +} + +/***************************************************************************** + qt_cleanup() - cleans up when the application is finished + *****************************************************************************/ + +void qt_cleanup() +{ + CGDisplayRemoveReconfigurationCallback(qt_mac_display_change_callbk, 0); + CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter(); + CFNotificationCenterRemoveObserver(center, qApp, kCMDeviceUnregisteredNotification, 0); + CFNotificationCenterRemoveObserver(center, qApp, kCMDefaultDeviceNotification, 0); + CFNotificationCenterRemoveObserver(center, qApp, kCMDeviceProfilesNotification, 0); + CFNotificationCenterRemoveObserver(center, qApp, kCMDefaultDeviceProfileNotification, 0); + +#ifndef QT_MAC_USE_COCOA + qt_release_app_proc_handler(); + if (app_proc_handlerUPP) { + DisposeEventHandlerUPP(app_proc_handlerUPP); + app_proc_handlerUPP = 0; + } +#endif + qt_release_apple_event_handler(); + qt_release_tablet_proximity_handler(); + if (tablet_proximity_UPP) + DisposeEventHandlerUPP(tablet_proximity_UPP); + + QPixmapCache::clear(); + if (qt_is_gui_used) { +#ifndef QT_NO_ACCESSIBILITY + QAccessible::cleanup(); +#endif + QMacInputContext::cleanup(); + QCursorData::cleanup(); + QFont::cleanup(); + QColormap::cleanup(); + if (qt_mac_safe_pdev) { + delete qt_mac_safe_pdev; + qt_mac_safe_pdev = 0; + } + extern void qt_mac_unregister_widget(); // qapplication_mac.cpp + qt_mac_unregister_widget(); + } +} + +/***************************************************************************** + Platform specific global and internal functions + *****************************************************************************/ +void qt_updated_rootinfo() +{ +} + +bool qt_wstate_iconified(WId) +{ + return false; +} + +/***************************************************************************** + Platform specific QApplication members + *****************************************************************************/ +extern QWidget * mac_mouse_grabber; +extern QWidget * mac_keyboard_grabber; + +#ifdef QT3_SUPPORT +void QApplication::setMainWidget(QWidget *mainWidget) +{ + QApplicationPrivate::main_widget = mainWidget; + if (QApplicationPrivate::main_widget && windowIcon().isNull() + && QApplicationPrivate::main_widget->testAttribute(Qt::WA_SetWindowIcon)) + setWindowIcon(QApplicationPrivate::main_widget->windowIcon()); +} +#endif +#ifndef QT_NO_CURSOR + +/***************************************************************************** + QApplication cursor stack + *****************************************************************************/ + +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + +#ifdef QT_MAC_USE_COCOA + qt_mac_update_cursor(); +#else + if (qApp && qApp->activeWindow()) + qt_mac_set_cursor(&qApp->d_func()->cursor_list.first()); +#endif +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + +#ifdef QT_MAC_USE_COCOA + qt_mac_update_cursor(); +#else + if (qApp && qApp->activeWindow()) { + const QCursor def(Qt::ArrowCursor); + qt_mac_set_cursor(qApp->d_func()->cursor_list.isEmpty() ? &def : &qApp->d_func()->cursor_list.first()); + } +#endif +} +#endif // QT_NO_CURSOR + +QWidget *QApplication::topLevelAt(const QPoint &p) +{ +#ifndef QT_MAC_USE_COCOA + QWidget *widget; + qt_mac_window_at(p.x(), p.y(), &widget); + return widget; +#else + // Use a cache to avoid iterate through the whole list of windows for all + // calls to to topLevelAt. We e.g. do this for each and every mouse + // move since we need to find the widget under mouse: + if (topLevelAt_cache && topLevelAt_cache->frameGeometry().contains(p)) + return topLevelAt_cache; + + // INVARIANT: Cache miss. Go through the list if windows instead: + QMacCocoaAutoReleasePool pool; + NSPoint cocoaPoint = flipPoint(p); + NSInteger windowCount; + NSCountWindows(&windowCount); + if (windowCount <= 0) + return 0; // There's no window to find! + + QVarLengthArray windowList(windowCount); + NSWindowList(windowCount, windowList.data()); + int firstQtWindowFound = -1; + for (int i = 0; i < windowCount; ++i) { + NSWindow *window = [NSApp windowWithWindowNumber:windowList[i]]; + if (window) { + QWidget *candidateWindow = [window QT_MANGLE_NAMESPACE(qt_qwidget)]; + if (candidateWindow && firstQtWindowFound == -1) + firstQtWindowFound = i; + + if (NSPointInRect(cocoaPoint, [window frame])) { + // Check to see if there's a hole in the window where the mask is. + // If there is, we should just continue to see if there is a window below. + if (candidateWindow && !candidateWindow->mask().isEmpty()) { + QPoint localPoint = candidateWindow->mapFromGlobal(p); + if (!candidateWindow->mask().contains(localPoint)) + continue; + else + return candidateWindow; + } else { + if (i == firstQtWindowFound) { + // The cache will only work when the window under mouse is + // top most (that is, not partially obscured by other windows. + // And we only set it if no mask is present to optimize for the common case: + topLevelAt_cache = candidateWindow; + } + return candidateWindow; + } + } + } + } + + topLevelAt_cache = 0; + return 0; +#endif +} + +/***************************************************************************** + Main event loop + *****************************************************************************/ + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +#ifdef QT_MAC_USE_COCOA +#endif + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ +#ifdef DEBUG_MODAL_EVENTS + Q_ASSERT(widget); + qDebug("Entering modal state with %s::%s::%p (%d)", widget->metaObject()->className(), widget->objectName().toLocal8Bit().constData(), + widget, qt_modal_stack ? (int)qt_modal_stack->count() : -1); +#endif + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + + dispatchEnterLeave(0, qt_last_mouse_receiver); + qt_last_mouse_receiver = 0; + + qt_modal_stack->insert(0, widget); + if (!app_do_modal) + qt_event_request_menubarupdate(); + app_do_modal = true; + qt_button_down = 0; + +#ifdef QT_MAC_USE_COCOA + if (!qt_mac_is_macsheet(widget)) + QEventDispatcherMacPrivate::beginModalSession(widget); +#endif +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget) +{ + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { +#ifdef DEBUG_MODAL_EVENTS + qDebug("Leaving modal state with %s::%s::%p (%d)", widget->metaObject()->className(), widget->objectName().toLocal8Bit().constData(), + widget, qt_modal_stack->count()); +#endif + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + QPoint p(QCursor::pos()); + app_do_modal = false; + QWidget* w = 0; + if (QWidget *grabber = QWidget::mouseGrabber()) + w = grabber; + else + w = QApplication::widgetAt(p.x(), p.y()); + dispatchEnterLeave(w, qt_last_mouse_receiver); // send synthetic enter event + qt_last_mouse_receiver = w; + } +#ifdef QT_MAC_USE_COCOA + if (!qt_mac_is_macsheet(widget)) + QEventDispatcherMacPrivate::endModalSession(widget); +#endif + } +#ifdef DEBUG_MODAL_EVENTS + else qDebug("Failure to remove %s::%s::%p -- %p", widget->metaObject()->className(), widget->objectName().toLocal8Bit().constData(), widget, qt_modal_stack); +#endif + app_do_modal = (qt_modal_stack != 0); + if (!app_do_modal) + qt_event_request_menubarupdate(); +} + +QWidget *QApplicationPrivate::tryModalHelper_sys(QWidget *top) +{ +#ifndef QT_MAC_USE_COCOA + if(top && qt_mac_is_macsheet(top) && !IsWindowVisible(qt_mac_window_for(top))) { + if(OSWindowRef wp = GetFrontWindowOfClass(kSheetWindowClass, true)) { + if(QWidget *sheet = qt_mac_find_window(wp)) + top = sheet; + } + } +#endif + return top; +} + +#ifndef QT_MAC_USE_COCOA +static bool qt_try_modal(QWidget *widget, EventRef event) +{ + QWidget * top = 0; + + if (QApplicationPrivate::tryModalHelper(widget, &top)) + return true; + + // INVARIANT: widget is modally shaddowed within its + // window, and should therefore not handle the event. + // However, if the window is not active, the event + // might suggest that we should bring it to front: + + bool block_event = false; + + if (event) { + switch (GetEventClass(event)) { + case kEventClassMouse: + case kEventClassKeyboard: + block_event = true; + break; + } + } + + QWidget *activeWidget = QApplication::activeWindow(); + if ((!activeWidget || QApplicationPrivate::isBlockedByModal(activeWidget)) && + top->isWindow() && block_event && !QApplicationPrivate::native_modal_dialog_active) + top->raise(); + +#ifdef DEBUG_MODAL_EVENTS + qDebug("%s:%d -- final decision! (%s)", __FILE__, __LINE__, block_event ? "false" : "true"); +#endif + return !block_event; +} +#endif + +OSStatus QApplicationPrivate::tabletProximityCallback(EventHandlerCallRef, EventRef carbonEvent, + void *) +{ + OSType eventClass = GetEventClass(carbonEvent); + UInt32 eventKind = GetEventKind(carbonEvent); + if (eventClass != kEventClassTablet || eventKind != kEventTabletProximity) + return eventNotHandledErr; + + // Get the current point of the device and its unique ID. + ::TabletProximityRec proxRec; + GetEventParameter(carbonEvent, kEventParamTabletProximityRec, typeTabletProximityRec, 0, + sizeof(proxRec), 0, &proxRec); + qt_dispatchTabletProximityEvent(proxRec); + return noErr; +} + +OSStatus +QApplicationPrivate::globalEventProcessor(EventHandlerCallRef er, EventRef event, void *data) +{ +#ifndef QT_MAC_USE_COCOA + QApplication *app = (QApplication *)data; + QScopedLoopLevelCounter loopLevelCounter(app->d_func()->threadData); + long result; + if (app->filterEvent(&event, &result)) + return result; + if(app->macEventFilter(er, event)) //someone else ate it + return noErr; + QPointer widget; + + /*We assume all events are handled and in + the code below we set it to false when we know we didn't handle it, this + will let rogue events through (shouldn't really happen, but better safe + than sorry) */ + bool handled_event=true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) + { + case kEventClassQt: + if(ekind == kEventQtRequestShowSheet) { + request_showsheet_pending = 0; + QWidget *widget = 0; + GetEventParameter(event, kEventParamQWidget, typeQWidget, 0, + sizeof(widget), 0, &widget); + if(widget) { + if (widget->macEvent(er, event)) + return noErr; + WindowPtr window = qt_mac_window_for(widget); + bool just_show = !qt_mac_is_macsheet(widget); + if(!just_show) { + OSStatus err = ShowSheetWindow(window, qt_mac_window_for(widget->parentWidget())); + if(err != noErr) + qWarning("Qt: QWidget: Unable to show as sheet %s::%s [%ld]", widget->metaObject()->className(), + widget->objectName().toLocal8Bit().constData(), long(err)); + just_show = true; + } + if(just_show) //at least the window will be visible, but the sheet flag doesn't work sadly (probalby too many sheets) + ShowHide(window, true); + } + } else if(ekind == kEventQtRequestWindowChange) { + qt_mac_event_release(request_window_change_pending); + } else if(ekind == kEventQtRequestMenubarUpdate) { + qt_mac_event_release(request_menubarupdate_pending); + QMenuBar::macUpdateMenuBar(); + } else if(ekind == kEventQtRequestActivate) { + qt_mac_event_release(request_activate_pending.event); + if(request_activate_pending.widget) { + QWidget *tlw = request_activate_pending.widget->window(); + if (tlw->macEvent(er, event)) + return noErr; + request_activate_pending.widget = 0; + tlw->activateWindow(); + SelectWindow(qt_mac_window_for(tlw)); + } + } else if(ekind == kEventQtRequestContext) { + bool send = false; + if ((send = (event == request_context_pending))) + qt_mac_event_release(request_context_pending); + if(send) { + //figure out which widget to send it to + QPoint where = QCursor::pos(); + QWidget *widget = 0; + GetEventParameter(event, kEventParamQWidget, typeQWidget, 0, + sizeof(widget), 0, &widget); + if(!widget) { + if(qt_button_down) + widget = qt_button_down; + else + widget = QApplication::widgetAt(where.x(), where.y()); + } + if(widget && !isBlockedByModal(widget)) { + if (widget->macEvent(er, event)) + return noErr; + QPoint plocal(widget->mapFromGlobal(where)); + const Qt::KeyboardModifiers keyboardModifiers = qt_mac_get_modifiers(GetCurrentEventKeyModifiers()); + QContextMenuEvent qme(QContextMenuEvent::Mouse, plocal, where, keyboardModifiers); + QApplication::sendEvent(widget, &qme); + if(qme.isAccepted()) { //once this happens the events before are pitched + qt_button_down = 0; + qt_mac_dblclick.last_widget = 0; + } + } else { + handled_event = false; + } + } + } else { + handled_event = false; + } + break; + case kEventClassTablet: + switch (ekind) { + case kEventTabletProximity: + // Get the current point of the device and its unique ID. + ::TabletProximityRec proxRec; + GetEventParameter(event, kEventParamTabletProximityRec, typeTabletProximityRec, 0, + sizeof(proxRec), 0, &proxRec); + qt_dispatchTabletProximityEvent(proxRec); + } + break; + case kEventClassMouse: + { + static const int kEventParamQAppSeenMouseEvent = 'QASM'; + // Check if we've seen the event, if we have we shouldn't process + // it again as it may lead to spurious "double events" + bool seenEvent; + if (GetEventParameter(event, kEventParamQAppSeenMouseEvent, + typeBoolean, 0, sizeof(bool), 0, &seenEvent) == noErr) { + if (seenEvent) + return eventNotHandledErr; + } + seenEvent = true; + SetEventParameter(event, kEventParamQAppSeenMouseEvent, typeBoolean, + sizeof(bool), &seenEvent); + + Point where; + bool inNonClientArea = false; + GetEventParameter(event, kEventParamMouseLocation, typeQDPoint, 0, + sizeof(where), 0, &where); +#if defined(DEBUG_MOUSE_MAPS) + const char *edesc = 0; + switch(ekind) { + case kEventMouseDown: edesc = "MouseButtonPress"; break; + case kEventMouseUp: edesc = "MouseButtonRelease"; break; + case kEventMouseDragged: case kEventMouseMoved: edesc = "MouseMove"; break; + case kEventMouseScroll: edesc = "MouseWheelScroll"; break; + case kEventMouseWheelMoved: edesc = "MouseWheelMove"; break; + } + if(ekind == kEventMouseDown || ekind == kEventMouseUp) + qDebug("Handling mouse: %s", edesc); +#endif + QEvent::Type etype = QEvent::None; + Qt::KeyboardModifiers modifiers; + { + UInt32 mac_modifiers = 0; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(mac_modifiers), 0, &mac_modifiers); + modifiers = qt_mac_get_modifiers(mac_modifiers); + } + Qt::MouseButtons buttons; + { + UInt32 mac_buttons = 0; + GetEventParameter(event, kEventParamMouseChord, typeUInt32, 0, + sizeof(mac_buttons), 0, &mac_buttons); + if (ekind != kEventMouseWheelMoved) + buttons = qt_mac_get_buttons(mac_buttons); + else + buttons = QApplication::mouseButtons(); + } + + int wheel_deltaX = 0; + int wheel_deltaY = 0; + static EventRef compatibilityEvent = 0; + + if (ekind == kEventMouseScroll) { + // kEventMouseScroll is the new way of dealing with mouse wheel + // events (kEventMouseWheelMoved was the old). kEventMouseScroll results + // in much smoother scrolling when using Mighty Mouse or TrackPad. For + // compatibility with older applications, carbon will also send us + // kEventMouseWheelMoved events if we dont eat this event + // (actually two events; one for horizontal and one for vertical). + // As a results of this, and to make sure we dont't receive duplicate events, + // we try to detect when this happend by checking the 'compatibilityEvent'. + // Since delta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; + SInt32 mdelt = 0; + GetEventParameter(event, kEventParamMouseWheelSmoothHorizontalDelta, typeSInt32, 0, + sizeof(mdelt), 0, &mdelt); + wheel_deltaX = mdelt * pixelsToDegrees; + mdelt = 0; + GetEventParameter(event, kEventParamMouseWheelSmoothVerticalDelta, typeSInt32, 0, + sizeof(mdelt), 0, &mdelt); + wheel_deltaY = mdelt * pixelsToDegrees; + GetEventParameter(event, kEventParamEventRef, typeEventRef, 0, + sizeof(compatibilityEvent), 0, &compatibilityEvent); + } else if (ekind == kEventMouseWheelMoved) { + if (event != compatibilityEvent) { + compatibilityEvent = 0; + int mdelt = 0; + GetEventParameter(event, kEventParamMouseWheelDelta, typeSInt32, 0, + sizeof(mdelt), 0, &mdelt); + EventMouseWheelAxis axis; + GetEventParameter(event, kEventParamMouseWheelAxis, typeMouseWheelAxis, 0, + sizeof(axis), 0, &axis); + + // Remove acceleration, and use either -120 or 120 as delta: + if (axis == kEventMouseWheelAxisX) + wheel_deltaX = qBound(-120, int(mdelt * 10000), 120); + else + wheel_deltaY = qBound(-120, int(mdelt * 10000), 120); + } + } + + Qt::MouseButton button = Qt::NoButton; + if(ekind == kEventMouseDown || ekind == kEventMouseUp) { + EventMouseButton mac_button = 0; + GetEventParameter(event, kEventParamMouseButton, typeMouseButton, 0, + sizeof(mac_button), 0, &mac_button); + button = qt_mac_get_button(mac_button); + } + + switch(ekind) { + case kEventMouseDown: + etype = QEvent::MouseButtonPress; + break; + case kEventMouseUp: + etype = QEvent::MouseButtonRelease; + break; + case kEventMouseDragged: + case kEventMouseMoved: + etype = QEvent::MouseMove; + break; + } + + const bool inPopupMode = app->d_func()->inPopupMode(); + + // A click outside a popup closes the popup. Make sure + // that no events are generated for the release part of that click. + // (The press goes to the popup and closes it.) + if (etype == QEvent::MouseButtonPress) { + qt_mac_previous_press_in_popup_mode = inPopupMode; + } else if (qt_mac_previous_press_in_popup_mode && !inPopupMode && etype == QEvent::MouseButtonRelease) { + qt_mac_previous_press_in_popup_mode = false; + handled_event = true; +#if defined(DEBUG_MOUSE_MAPS) + qDebug("Bail out early due to qt_mac_previous_press_in_popup_mode"); +#endif + break; // break from case kEventClassMouse + } + + //figure out which widget to send it to + if(inPopupMode) { + QWidget *popup = qApp->activePopupWidget(); + if (qt_button_down && qt_button_down->window() == popup) { + widget = qt_button_down; + } else { + QPoint pos = popup->mapFromGlobal(QPoint(where.h, where.v)); + widget = popup->childAt(pos); + } + if(!widget) + widget = popup; + } else { + if(mac_mouse_grabber) { + widget = mac_mouse_grabber; + } else if (qt_button_down) { + widget = qt_button_down; + } else { + { + WindowPtr window = 0; + if(GetEventParameter(event, kEventParamWindowRef, typeWindowRef, 0, + sizeof(window), 0, &window) != noErr) + FindWindowOfClass(&where, kAllWindowClasses, &window, 0); + if(window) { + HIViewRef hiview; + if(HIViewGetViewForMouseEvent(HIViewGetRoot(window), event, &hiview) == noErr) { + widget = QWidget::find((WId)hiview); + if (widget) { + // Make sure we didn't pass over a widget with a "fake hole" in it. + QWidget *otherWidget = QApplication::widgetAt(where.h, where.v); + if (otherWidget && otherWidget->testAttribute(Qt::WA_MouseNoMask)) + widget = otherWidget; + } + } + } + } + if(!widget) //fallback + widget = QApplication::widgetAt(where.h, where.v); + if(ekind == kEventMouseUp && widget) { + short part = qt_mac_window_at(where.h, where.v); + if(part == inDrag) { + UInt32 count = 0; + GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, + sizeof(count), NULL, &count); + if(count == 2 && qt_mac_collapse_on_dblclick) { + if (widget->macEvent(er, event)) + return noErr; + widget->setWindowState(widget->windowState() | Qt::WindowMinimized); + //we send a hide to be like X11/Windows + QEvent e(QEvent::Hide); + QApplication::sendSpontaneousEvent(widget, &e); + break; + } + } + } + } + } + if (widget && widget->macEvent(er, event)) + return noErr; + WindowPartCode wpc = qt_mac_window_at(where.h, where.v, 0); + if (wpc == inProxyIcon && modifiers == Qt::ControlModifier && buttons != Qt::NoButton) { + QIconDragEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + if (e.isAccepted()) { + return noErr; // IconDrag ate it. + } + } + if (inPopupMode == false + && (qt_button_down == 0 || qt_button_down_in_content == false) + && (wpc != inContent && wpc != inStructure)) { + inNonClientArea = true; + switch (etype) { + case QEvent::MouseButtonPress: { + UInt32 count = 0; + GetEventParameter(event, kEventParamClickCount, typeUInt32, 0, + sizeof(count), 0, &count); + if(count % 2 || count == 0) { + etype = QEvent::NonClientAreaMouseButtonPress; + } else { + etype = QEvent::NonClientAreaMouseButtonDblClick; + }} break; + case QEvent::MouseButtonRelease: + etype = QEvent::NonClientAreaMouseButtonRelease; + break; + case QEvent::MouseMove: + if (widget == 0 || widget->hasMouseTracking()) + etype = QEvent::NonClientAreaMouseMove; + break; + default: + break; + } + } + + if(qt_mac_find_window((FrontWindow()))) { //set the cursor up + QCursor cursor(Qt::ArrowCursor); + QWidget *cursor_widget = widget; + if(cursor_widget && cursor_widget == qt_button_down && ekind == kEventMouseUp) + cursor_widget = QApplication::widgetAt(where.h, where.v); + if(cursor_widget) { //only over the app, do we set a cursor.. + if(!qApp->d_func()->cursor_list.isEmpty()) { + cursor = qApp->d_func()->cursor_list.first(); + } else { + for(; cursor_widget; cursor_widget = cursor_widget->parentWidget()) { + QWExtra *extra = cursor_widget->d_func()->extraData(); + if(extra && extra->curs && cursor_widget->isEnabled()) { + cursor = *extra->curs; + break; + } + } + } + } + qt_mac_set_cursor(&cursor); + } + + //This mouse button state stuff looks like this on purpose + //although it looks hacky it is VERY intentional.. + if(widget && app_do_modal && !qt_try_modal(widget, event)) { + if(ekind == kEventMouseDown && qt_mac_is_macsheet(QApplication::activeModalWidget())) + QApplication::activeModalWidget()->parentWidget()->activateWindow(); //sheets have a parent + handled_event = false; +#if defined(DEBUG_MOUSE_MAPS) + qDebug("Bail out early due to qt_try_modal"); +#endif + break; + } + + UInt32 tabletEventType = 0; + GetEventParameter(event, kEventParamTabletEventType, typeUInt32, 0, + sizeof(tabletEventType), 0, &tabletEventType); + if (tabletEventType == kEventTabletPoint) { + TabletPointRec tabletPointRec; + GetEventParameter(event, kEventParamTabletPointRec, typeTabletPointRec, 0, + sizeof(tabletPointRec), 0, &tabletPointRec); + QEvent::Type t = QEvent::TabletMove; //default + int new_tablet_button_state = tabletPointRec.buttons ? 1 : 0; + if (new_tablet_button_state != tablet_button_state) + if (new_tablet_button_state) + t = QEvent::TabletPress; + else + t = QEvent::TabletRelease; + tablet_button_state = new_tablet_button_state; + + QMacTabletHash *tabletHash = qt_mac_tablet_hash(); + if (!tabletHash->contains(tabletPointRec.deviceID) && t != QEvent::TabletRelease) { + // Never discard TabletRelease events as they may be delivered *after* TabletLeaveProximity events + qWarning("handleTabletEvent: This tablet device is unknown" + " (received no proximity event for it). Discarding event."); + return false; + } + QTabletDeviceData &deviceData = tabletHash->operator[](tabletPointRec.deviceID); + if (t == QEvent::TabletPress) { + deviceData.widgetToGetPress = widget; + } else if (t == QEvent::TabletRelease && deviceData.widgetToGetPress) { + widget = deviceData.widgetToGetPress; + deviceData.widgetToGetPress = 0; + } + + if (widget) { + int tiltX = ((int)tabletPointRec.tiltX)/(32767/64); // 32K -> 60 + int tiltY = ((int)tabletPointRec.tiltY)/(-32767/64); // 32K -> 60 + HIPoint hiPoint; + GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, 0, sizeof(HIPoint), 0, &hiPoint); + QPointF hiRes(hiPoint.x, hiPoint.y); + QPoint global(where.h, where.v); + + + + QPoint local(widget->mapFromGlobal(global)); + int z = 0; + qreal rotation = 0.0; + qreal tp = 0.0; + // Again from the Wacom.h header + + if (deviceData.capabilityMask & 0x0200) // Z-axis + z = tabletPointRec.absZ; + + if (deviceData.capabilityMask & 0x0800) // Tangential pressure + tp = tabletPointRec.tangentialPressure / 32767.0; + + if (deviceData.capabilityMask & 0x2000) // Rotation + rotation = qreal(tabletPointRec.rotation) / 64.0; + + QTabletEvent e(t, local, global, hiRes, deviceData.tabletDeviceType, + deviceData.tabletPointerType, + qreal(tabletPointRec.pressure / qreal(0xffff)), tiltX, tiltY, + tp, rotation, z, modifiers, deviceData.tabletUniqueID); + QApplication::sendSpontaneousEvent(widget, &e); + if (e.isAccepted()) { + if (t == QEvent::TabletPress) { + qt_button_down = widget; + } else if (t == QEvent::TabletRelease) { + qt_button_down = 0; + } +#if defined(DEBUG_MOUSE_MAPS) + qDebug("Bail out early due to tablet acceptance"); +#endif + break; + } + } + } + + if(ekind == kEventMouseDown) { + qt_mac_no_click_through_mode = false; + const short windowPart = qt_mac_window_at(where.h, where.v, 0); + // Menubar almost always wins. + if (!inPopupMode && windowPart == inMenuBar) { + MenuSelect(where); //allow menu tracking + return noErr; + } + + if (widget && !(GetCurrentKeyModifiers() & cmdKey)) { + extern bool qt_isGenuineQWidget(const QWidget *); // qwidget_mac.cpp + QWidget *window = widget->window(); + bool genuineQtWidget = qt_isGenuineQWidget(widget); // the widget, not the window. + window->raise(); + + bool needActivate = (window->windowType() != Qt::Desktop) + && (window->windowType() != Qt::Popup) + && !qt_mac_is_macsheet(window); + if (needActivate && (!window->isModal() && qobject_cast(window))) + needActivate = false; + + if (genuineQtWidget && needActivate) + needActivate = !window->isActiveWindow() + || !IsWindowActive(qt_mac_window_for(window)); + + if (needActivate) { + window->activateWindow(); + if (!qt_mac_can_clickThrough(widget)) { + qt_mac_no_click_through_mode = true; + handled_event = false; +#if defined(DEBUG_MOUSE_MAPS) + qDebug("Bail out early due to qt_mac_canClickThrough %s::%s", widget->metaObject()->className(), + widget->objectName().toLocal8Bit().constData()); +#endif + break; + } + } + } + + if(qt_mac_dblclick.last_widget && + qt_mac_dblclick.last_x != -1 && qt_mac_dblclick.last_y != -1 && + QRect(qt_mac_dblclick.last_x-2, qt_mac_dblclick.last_y-2, 4, 4).contains(QPoint(where.h, where.v))) { + if(qt_mac_dblclick.use_qt_time_limit) { + EventTime now = GetEventTime(event); + if(qt_mac_dblclick.last_time != -2 && qt_mac_dblclick.last_widget == widget && + now - qt_mac_dblclick.last_time <= ((double)QApplicationPrivate::mouse_double_click_time)/1000 && + qt_mac_dblclick.last_button == button) + etype = QEvent::MouseButtonDblClick; + } else { + UInt32 count = 0; + GetEventParameter(event, kEventParamClickCount, typeUInt32, 0, + sizeof(count), 0, &count); + if(!(count % 2) && qt_mac_dblclick.last_modifiers == modifiers && + qt_mac_dblclick.last_widget == widget && qt_mac_dblclick.last_button == button) + etype = QEvent::MouseButtonDblClick; + } + if(etype == QEvent::MouseButtonDblClick) + qt_mac_dblclick.last_widget = 0; + } + if(etype != QEvent::MouseButtonDblClick) { + qt_mac_dblclick.last_x = where.h; + qt_mac_dblclick.last_y = where.v; + } else { + qt_mac_dblclick.last_x = qt_mac_dblclick.last_y = -1; + } + } else if(qt_mac_no_click_through_mode) { + if(ekind == kEventMouseUp) + qt_mac_no_click_through_mode = false; + handled_event = false; +#if defined(DEBUG_MOUSE_MAPS) + qDebug("Bail out early due to qt_mac_no_click_through_mode"); +#endif + break; + } + + QPointer leaveAfterRelease = 0; + switch(ekind) { + case kEventMouseUp: + if (!buttons) { + if (!inPopupMode && !QWidget::mouseGrabber()) + leaveAfterRelease = qt_button_down; + qt_button_down = 0; + } + break; + case kEventMouseDown: { + if (!qt_button_down) + qt_button_down = widget; + WindowPartCode wpc = qt_mac_window_at(where.h, where.v, 0); + qt_button_down_in_content = (wpc == inContent || wpc == inStructure); + break; } + } + + // Check if we should send enter/leave events: + switch(ekind) { + case kEventMouseDragged: + case kEventMouseMoved: + case kEventMouseUp: + case kEventMouseDown: { + // If we are in popup mode, widget will point to the current popup no matter + // where the mouse cursor is. In that case find out if the mouse cursor is + // really over the popup in order to send correct enter / leave envents. + QWidget * const enterLeaveWidget = (inPopupMode || ekind == kEventMouseUp) ? + QApplication::widgetAt(where.h, where.v) : static_cast(widget); + + if ((QWidget *) qt_last_mouse_receiver != enterLeaveWidget || inNonClientArea) { +#ifdef DEBUG_MOUSE_MAPS + qDebug("Entering: %p - %s (%s), Leaving %s (%s)", (QWidget*)enterLeaveWidget, + enterLeaveWidget ? enterLeaveWidget->metaObject()->className() : "none", + enterLeaveWidget ? enterLeaveWidget->objectName().toLocal8Bit().constData() : "", + qt_last_mouse_receiver ? qt_last_mouse_receiver->metaObject()->className() : "none", + qt_last_mouse_receiver ? qt_last_mouse_receiver->objectName().toLocal8Bit().constData() : ""); +#endif + + QWidget * const mouseGrabber = QWidget::mouseGrabber(); + + if (inPopupMode) { + QWidget *enter = enterLeaveWidget; + QWidget *leave = qt_last_mouse_receiver; + if (mouseGrabber) { + QWidget * const popupWidget = qApp->activePopupWidget(); + if (leave == popupWidget) + enter = mouseGrabber; + if (enter == popupWidget) + leave = mouseGrabber; + if ((enter == mouseGrabber && leave == popupWidget) + || (leave == mouseGrabber && enter == popupWidget)) { + QApplicationPrivate::dispatchEnterLeave(enter, leave); + qt_last_mouse_receiver = enter; + } + } else { + QApplicationPrivate::dispatchEnterLeave(enter, leave); + qt_last_mouse_receiver = enter; + } + } else if ((!qt_button_down || !qt_last_mouse_receiver) && !mouseGrabber && !leaveAfterRelease) { + QApplicationPrivate::dispatchEnterLeave(enterLeaveWidget, qt_last_mouse_receiver); + qt_last_mouse_receiver = enterLeaveWidget; + } + } + break; } + } + + if(widget) { + QPoint p(where.h, where.v); + QPoint plocal(widget->mapFromGlobal(p)); + if(etype == QEvent::MouseButtonPress) { + qt_mac_dblclick.last_widget = widget; + qt_mac_dblclick.last_modifiers = modifiers; + qt_mac_dblclick.last_button = button; + qt_mac_dblclick.last_time = GetEventTime(event); + } + + if (wheel_deltaX || wheel_deltaY) { +#ifndef QT_NO_WHEELEVENT + if (wheel_deltaX) { + QWheelEvent qwe(plocal, p, wheel_deltaX, buttons, modifiers, Qt::Horizontal); + QApplication::sendSpontaneousEvent(widget, &qwe); + if (!qwe.isAccepted() && QApplicationPrivate::focus_widget && QApplicationPrivate::focus_widget != widget) { + QWheelEvent qwe2(QApplicationPrivate::focus_widget->mapFromGlobal(p), p, + wheel_deltaX, buttons, modifiers, Qt::Horizontal); + QApplication::sendSpontaneousEvent(QApplicationPrivate::focus_widget, &qwe2); + if (!qwe2.isAccepted()) + handled_event = false; + } + } + if (wheel_deltaY) { + QWheelEvent qwe(plocal, p, wheel_deltaY, buttons, modifiers, Qt::Vertical); + QApplication::sendSpontaneousEvent(widget, &qwe); + if (!qwe.isAccepted() && QApplicationPrivate::focus_widget && QApplicationPrivate::focus_widget != widget) { + QWheelEvent qwe2(QApplicationPrivate::focus_widget->mapFromGlobal(p), p, + wheel_deltaY, buttons, modifiers, Qt::Vertical); + QApplication::sendSpontaneousEvent(QApplicationPrivate::focus_widget, &qwe2); + if (!qwe2.isAccepted()) + handled_event = false; + } + } +#endif // QT_NO_WHEELEVENT + } else { +#ifdef QMAC_SPEAK_TO_ME + const int speak_keys = Qt::AltModifier | Qt::ShiftModifier; + if(etype == QMouseEvent::MouseButtonDblClick && ((modifiers & speak_keys) == speak_keys)) { + QVariant v = widget->property("displayText"); + if(!v.isValid()) v = widget->property("text"); + if(!v.isValid()) v = widget->property("windowTitle"); + if(v.isValid()) { + QString s = v.toString(); + s.replace(QRegExp(QString::fromLatin1("(\\&|\\<[^\\>]*\\>)")), QLatin1String("")); + SpeechChannel ch; + NewSpeechChannel(0, &ch); + SpeakText(ch, s.toLatin1().constData(), s.length()); + DisposeSpeechChannel(ch); + } + } +#endif + Qt::MouseButton buttonToSend = button; + static bool lastButtonTranslated = false; + if(ekind == kEventMouseDown && + button == Qt::LeftButton && (modifiers & Qt::MetaModifier)) { + buttonToSend = Qt::RightButton; + lastButtonTranslated = true; + } else if(ekind == kEventMouseUp && lastButtonTranslated) { + buttonToSend = Qt::RightButton; + lastButtonTranslated = false; + } + QMouseEvent qme(etype, plocal, p, buttonToSend, buttons, modifiers); + QApplication::sendSpontaneousEvent(widget, &qme); + if(!qme.isAccepted() || inNonClientArea) + handled_event = false; + } + + if (leaveAfterRelease) { + QWidget *enter = QApplication::widgetAt(where.h, where.v); + QApplicationPrivate::dispatchEnterLeave(enter, leaveAfterRelease); + qt_last_mouse_receiver = enter; + leaveAfterRelease = 0; + } + + if(ekind == kEventMouseDown && + ((button == Qt::RightButton) || + (button == Qt::LeftButton && (modifiers & Qt::MetaModifier)))) + qt_event_request_context(); + +#ifdef DEBUG_MOUSE_MAPS + const char *event_desc = edesc; + if(etype == QEvent::MouseButtonDblClick) + event_desc = "Double Click"; + else if(etype == QEvent::NonClientAreaMouseButtonPress) + event_desc = "NonClientMousePress"; + else if(etype == QEvent::NonClientAreaMouseButtonRelease) + event_desc = "NonClientMouseRelease"; + else if(etype == QEvent::NonClientAreaMouseMove) + event_desc = "NonClientMouseMove"; + else if(etype == QEvent::NonClientAreaMouseButtonDblClick) + event_desc = "NonClientMouseDblClick"; + qDebug("%d %d (%d %d) - Would send (%s) event to %p %s %s (%d 0x%08x 0x%08x %d)", p.x(), p.y(), + plocal.x(), plocal.y(), event_desc, (QWidget*)widget, + widget ? widget->objectName().toLocal8Bit().constData() : "*Unknown*", + widget ? widget->metaObject()->className() : "*Unknown*", + button, (int)buttons, (int)modifiers, wheel_deltaX); +#endif + } else { + handled_event = false; + } + break; + } + case kEventClassTextInput: + case kEventClassKeyboard: { + EventRef key_event = event; + if(eclass == kEventClassTextInput) { + Q_ASSERT(ekind == kEventTextInputUnicodeForKeyEvent); + OSStatus err = GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, 0, + sizeof(key_event), 0, &key_event); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + } + const UInt32 key_ekind = GetEventKind(key_event); + Q_ASSERT(GetEventClass(key_event) == kEventClassKeyboard); + + if(key_ekind == kEventRawKeyDown) + qt_keymapper_private()->updateKeyMap(er, key_event, data); + if(mac_keyboard_grabber) + widget = mac_keyboard_grabber; + else if (app->activePopupWidget()) + widget = (app->activePopupWidget()->focusWidget() ? + app->activePopupWidget()->focusWidget() : app->activePopupWidget()); + else if(QApplication::focusWidget()) + widget = QApplication::focusWidget(); + else + widget = app->activeWindow(); + + if (widget) { + if (widget->macEvent(er, event)) + return noErr; + } else { + // Darn, I need to update tho modifier state, even though + // Qt itself isn't getting them, otherwise the keyboard state get inconsistent. + if (key_ekind == kEventRawKeyModifiersChanged) { + UInt32 modifiers = 0; + GetEventParameter(key_event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(modifiers), 0, &modifiers); + extern void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object); // qkeymapper_mac.cpp + // Just send it to the qApp for the time being. + qt_mac_send_modifiers_changed(modifiers, qApp); + } + handled_event = false; + break; + } + + if(app_do_modal && !qt_try_modal(widget, key_event)) + break; + if (eclass == kEventClassTextInput) { + handled_event = false; + } else { + handled_event = qt_keymapper_private()->translateKeyEvent(widget, er, key_event, data, + widget == mac_keyboard_grabber); + } + break; } + case kEventClassWindow: { + WindowRef wid = 0; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, + sizeof(WindowRef), 0, &wid); + widget = qt_mac_find_window(wid); + if (widget && widget->macEvent(er, event)) + return noErr; + if(ekind == kEventWindowActivated) { + if(QApplicationPrivate::app_style) { + QEvent ev(QEvent::Style); + QApplication::sendSpontaneousEvent(QApplicationPrivate::app_style, &ev); + } + + if(widget && app_do_modal && !qt_try_modal(widget, event)) + break; + + if(widget && widget->window()->isVisible()) { + QWidget *tlw = widget->window(); + if(tlw->isWindow() && !(tlw->windowType() == Qt::Popup) + && !qt_mac_is_macdrawer(tlw) + && (!tlw->parentWidget() || tlw->isModal() + || !(tlw->windowType() == Qt::Tool))) { + bool just_send_event = false; + { + WindowActivationScope scope; + if(GetWindowActivationScope((WindowRef)wid, &scope) == noErr && + scope == kWindowActivationScopeIndependent) { + if(GetFrontWindowOfClass(kAllWindowClasses, true) != wid) + just_send_event = true; + } + } + if(just_send_event) { + QEvent e(QEvent::WindowActivate); + QApplication::sendSpontaneousEvent(widget, &e); + } else { + app->setActiveWindow(tlw); + } + } + QMenuBar::macUpdateMenuBar(); + } + } else if(ekind == kEventWindowDeactivated) { + if(widget && QApplicationPrivate::active_window == widget) + app->setActiveWindow(0); + } else { + handled_event = false; + } + break; } + case kEventClassApplication: + if(ekind == kEventAppActivated) { + if(QApplication::desktopSettingsAware()) + qt_mac_update_os_settings(); + if(qt_clipboard) { //manufacture an event so the clipboard can see if it has changed + QEvent ev(QEvent::Clipboard); + QApplication::sendSpontaneousEvent(qt_clipboard, &ev); + } + if(app) { + QEvent ev(QEvent::ApplicationActivate); + QApplication::sendSpontaneousEvent(app, &ev); + } + if(!app->activeWindow()) { + WindowPtr wp = ActiveNonFloatingWindow(); + if(QWidget *tmp_w = qt_mac_find_window(wp)) + app->setActiveWindow(tmp_w); + } + QMenuBar::macUpdateMenuBar(); + } else if(ekind == kEventAppDeactivated) { + //qt_mac_no_click_through_mode = false; + while(app->d_func()->inPopupMode()) + app->activePopupWidget()->close(); + if(app) { + QEvent ev(QEvent::ApplicationDeactivate); + QApplication::sendSpontaneousEvent(app, &ev); + } + app->setActiveWindow(0); + } else if(ekind == kEventAppAvailableWindowBoundsChanged) { + QDesktopWidgetImplementation::instance()->onResize(); + } else { + handled_event = false; + } + break; + case kAppearanceEventClass: + if(ekind == kAEAppearanceChanged) { + if(QApplication::desktopSettingsAware()) + qt_mac_update_os_settings(); + if(QApplicationPrivate::app_style) { + QEvent ev(QEvent::Style); + QApplication::sendSpontaneousEvent(QApplicationPrivate::app_style, &ev); + } + } else { + handled_event = false; + } + break; + case kEventClassAppleEvent: + if(ekind == kEventAppleEvent) { + EventRecord erec; + if(!ConvertEventRefToEventRecord(event, &erec)) + qDebug("Qt: internal: WH0A, unexpected condition reached. %s:%d", __FILE__, __LINE__); + else if(AEProcessAppleEvent(&erec) != noErr) + handled_event = false; + } else { + handled_event = false; + } + break; + case kEventClassCommand: + if(ekind == kEventCommandProcess) { + HICommand cmd; + GetEventParameter(event, kEventParamDirectObject, typeHICommand, + 0, sizeof(cmd), 0, &cmd); + handled_event = false; + if(!cmd.menu.menuRef && GetApplicationDockTileMenu()) { + EventRef copy = CopyEvent(event); + HICommand copy_cmd; + GetEventParameter(event, kEventParamDirectObject, typeHICommand, + 0, sizeof(copy_cmd), 0, ©_cmd); + copy_cmd.menu.menuRef = GetApplicationDockTileMenu(); + SetEventParameter(copy, kEventParamDirectObject, typeHICommand, sizeof(copy_cmd), ©_cmd); + if(SendEventToMenu(copy, copy_cmd.menu.menuRef) == noErr) + handled_event = true; + } + if(!handled_event) { + if(cmd.commandID == kHICommandQuit) { + // Quitting the application is not Qt's responsibility if + // used in a plugin or just embedded into a native application. + // In that case, let the event pass down to the native apps event handler. + if (!QApplication::testAttribute(Qt::AA_MacPluginApplication)) { + handled_event = true; + HiliteMenu(0); + bool handle_quit = true; + if(QApplicationPrivate::modalState()) { + int visible = 0; + const QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); ++i) { + if(tlws.at(i)->isVisible()) + ++visible; + } + handle_quit = (visible <= 1); + } + if(handle_quit) { + QCloseEvent ev; + QApplication::sendSpontaneousEvent(app, &ev); + if(ev.isAccepted()) + app->quit(); + } else { + QApplication::beep(); + } + } + } else if(cmd.commandID == kHICommandSelectWindow) { + if((GetCurrentKeyModifiers() & cmdKey)) + handled_event = true; + } else if(cmd.commandID == kHICommandAbout) { + QMessageBox::aboutQt(0); + HiliteMenu(0); + handled_event = true; + } + } + } + break; + } + +#ifdef DEBUG_EVENTS + qDebug("%shandled event %c%c%c%c %d", handled_event ? "(*) " : "", + char(eclass >> 24), char((eclass >> 16) & 255), char((eclass >> 8) & 255), + char(eclass & 255), (int)ekind); +#endif + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +#else + Q_UNUSED(er); + Q_UNUSED(event); + Q_UNUSED(data); + return eventNotHandledErr; +#endif +} + +#ifdef QT_MAC_USE_COCOA +void QApplicationPrivate::qt_initAfterNSAppStarted() +{ + setupAppleEvents(); + qt_mac_update_cursor(); +} + +void QApplicationPrivate::setupAppleEvents() +{ + // This function is called from the event dispatcher when NSApplication has + // finished initialization, which appears to be just after [NSApplication run] has + // started to execute. By setting up our apple events handlers this late, we override + // the ones set up by NSApplication. + + // If Qt is used as a plugin, we let the 3rd party application handle events + // like quit and open file events. Otherwise, if we install our own handlers, we + // easily end up breaking functionallity the 3rd party application depend on: + if (QApplication::testAttribute(Qt::AA_MacPluginApplication)) + return; + + QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *newDelegate = [QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate]; + NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; + [eventManager setEventHandler:newDelegate andSelector:@selector(appleEventQuit:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; + [eventManager setEventHandler:newDelegate andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:kInternetEventClass andEventID:kAEGetURL]; +} +#endif + +// In Carbon this is your one stop for apple events. +// In Cocoa, it ISN'T. This is the catch-all Apple Event handler that exists +// for the time between instantiating the NSApplication, but before the +// NSApplication has installed it's OWN Apple Event handler. When Cocoa has +// that set up, we remove this. So, if you are debugging problems, you likely +// want to check out QCocoaApplicationDelegate instead. +OSStatus QApplicationPrivate::globalAppleEventProcessor(const AppleEvent *ae, AppleEvent *, long handlerRefcon) +{ + QApplication *app = (QApplication *)handlerRefcon; + bool handled_event=false; + OSType aeID=typeWildCard, aeClass=typeWildCard; + AEGetAttributePtr(ae, keyEventClassAttr, typeType, 0, &aeClass, sizeof(aeClass), 0); + AEGetAttributePtr(ae, keyEventIDAttr, typeType, 0, &aeID, sizeof(aeID), 0); + if(aeClass == kCoreEventClass) { + switch(aeID) { + case kAEQuitApplication: { + extern bool qt_mac_quit_menu_item_enabled; // qmenu_mac.cpp + if (qt_mac_quit_menu_item_enabled) { + QCloseEvent ev; + QApplication::sendSpontaneousEvent(app, &ev); + if(ev.isAccepted()) { + handled_event = true; + app->quit(); + } + } else { + QApplication::beep(); // Sorry, you can't quit right now. + } + break; } + case kAEOpenDocuments: { + AEDescList docs; + if(AEGetParamDesc(ae, keyDirectObject, typeAEList, &docs) == noErr) { + long cnt = 0; + AECountItems(&docs, &cnt); + UInt8 *str_buffer = NULL; + for(int i = 0; i < cnt; i++) { + FSRef ref; + if(AEGetNthPtr(&docs, i+1, typeFSRef, 0, 0, &ref, sizeof(ref), 0) != noErr) + continue; + if(!str_buffer) + str_buffer = (UInt8 *)malloc(1024); + FSRefMakePath(&ref, str_buffer, 1024); + QFileOpenEvent ev(QString::fromUtf8((const char *)str_buffer)); + QApplication::sendSpontaneousEvent(app, &ev); + } + if(str_buffer) + free(str_buffer); + } + break; } + default: + break; + } + } else if (aeClass == kInternetEventClass) { + switch (aeID) { + case kAEGetURL: { + char urlData[1024]; + Size actualSize; + if (AEGetParamPtr(ae, keyDirectObject, typeChar, 0, urlData, + sizeof(urlData) - 1, &actualSize) == noErr) { + urlData[actualSize] = 0; + QFileOpenEvent ev(QUrl(QString::fromUtf8(urlData))); + QApplication::sendSpontaneousEvent(app, &ev); + } + break; + } + default: + break; + } + } +#ifdef DEBUG_EVENTS + qDebug("Qt: internal: %shandled Apple event! %c%c%c%c %c%c%c%c", handled_event ? "(*)" : "", + char(aeID >> 24), char((aeID >> 16) & 255), char((aeID >> 8) & 255),char(aeID & 255), + char(aeClass >> 24), char((aeClass >> 16) & 255), char((aeClass >> 8) & 255),char(aeClass & 255)); +#else + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +#endif +} + +/*! + \fn bool QApplication::macEventFilter(EventHandlerCallRef caller, EventRef event) + + \warning This virtual function is only used under Mac OS X, and behaves different + depending on if Qt is based on Carbon or Cocoa. + + For the Carbon port, If you create an application that inherits QApplication and reimplement + this function, you get direct access to all Carbon Events that Qt registers + for from Mac OS X with this function being called with the \a caller and + the \a event. + + For the Cocoa port, If you create an application that inherits QApplication and reimplement + this function, you get direct access to all Cocoa Events that Qt receives + from Mac OS X with this function being called with the \a caller being 0 and + the \a event being an NSEvent pointer: + + NSEvent *e = reinterpret_cast(event); + + Return true if you want to stop the event from being processed. + Return false for normal event dispatching. The default + implementation returns false. + + \sa macEventFilter(void *nsevent) +*/ +bool QApplication::macEventFilter(EventHandlerCallRef, EventRef) +{ + return false; +} + +/*! + \internal +*/ +void QApplicationPrivate::openPopup(QWidget *popup) +{ + if (!QApplicationPrivate::popupWidgets) // create list + QApplicationPrivate::popupWidgets = new QWidgetList; + QApplicationPrivate::popupWidgets->append(popup); // add to end of list + + // popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + if (popup->focusWidget()) { + popup->focusWidget()->setFocus(Qt::PopupFocusReason); + } else if (QApplicationPrivate::popupWidgets->count() == 1) { // this was the first popup + popup->setFocus(Qt::PopupFocusReason); + } +} + +/*! + \internal +*/ +void QApplicationPrivate::closePopup(QWidget *popup) +{ + Q_Q(QApplication); + if (!QApplicationPrivate::popupWidgets) + return; + + QApplicationPrivate::popupWidgets->removeAll(popup); + if (popup == qt_button_down) + qt_button_down = 0; + if (QApplicationPrivate::popupWidgets->isEmpty()) { // this was the last popup + delete QApplicationPrivate::popupWidgets; + QApplicationPrivate::popupWidgets = 0; + + // Special case for Tool windows: since they are activated and deactived together + // with a normal window they never become the QApplicationPrivate::active_window. + QWidget *appFocusWidget = QApplication::focusWidget(); + if (appFocusWidget && appFocusWidget->window()->windowType() == Qt::Tool) { + appFocusWidget->setFocus(Qt::PopupFocusReason); + } else if (QApplicationPrivate::active_window) { + if (QWidget *fw = QApplicationPrivate::active_window->focusWidget()) { + if (fw != QApplication::focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + q->sendEvent(fw, &e); + } + } + } + } else { + // popups are not focus-handled by the window system (the + // first popup grabbed the keyboard), so we have to do that + // manually: A popup was closed, so the previous popup gets + // the focus. + QWidget* aw = QApplicationPrivate::popupWidgets->last(); + if (QWidget *fw = aw->focusWidget()) + fw->setFocus(Qt::PopupFocusReason); + } +} + +void QApplication::beep() +{ + qt_mac_beep(); +} + +void QApplication::alert(QWidget *widget, int duration) +{ + if (!QApplicationPrivate::checkInstance("alert")) + return; + + QWidgetList windowsToMark; + if (!widget) + windowsToMark += topLevelWidgets(); + else + windowsToMark.append(widget->window()); + + bool needNotification = false; + for (int i = 0; i < windowsToMark.size(); ++i) { + QWidget *window = windowsToMark.at(i); + if (!window->isActiveWindow() && window->isVisible()) { + needNotification = true; // yeah, we may set it multiple times, but that's OK. + if (duration != 0) { + QTimer *timer = new QTimer(qApp); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), qApp, SLOT(_q_alertTimeOut())); + if (QTimer *oldTimer = qApp->d_func()->alertTimerHash.value(widget)) { + qApp->d_func()->alertTimerHash.remove(widget); + delete oldTimer; + } + qApp->d_func()->alertTimerHash.insert(widget, timer); + timer->start(duration); + } + } + } + if (needNotification) + qt_mac_send_notification(); +} + +void QApplicationPrivate::_q_alertTimeOut() +{ + if (QTimer *timer = qobject_cast(q_func()->sender())) { + QHash::iterator it = alertTimerHash.begin(); + while (it != alertTimerHash.end()) { + if (it.value() == timer) { + alertTimerHash.erase(it); + timer->deleteLater(); + break; + } + ++it; + } + if (alertTimerHash.isEmpty()) { + qt_mac_cancel_notification(); + } + } +} + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setDoubleClickInterval(int ms) +{ + qt_mac_dblclick.use_qt_time_limit = true; + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + if (!qt_mac_dblclick.use_qt_time_limit) { //get it from the system + QSettings appleSettings(QLatin1String("apple.com")); + /* First worked as of 10.3.3 */ + double dci = appleSettings.value(QLatin1String("com/apple/mouse/doubleClickThreshold"), 0.5).toDouble(); + return int(dci * 1000); + } + return QApplicationPrivate::mouse_double_click_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + // FIXME: get from the system + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int n) +{ + QApplicationPrivate::wheel_scroll_lines = n; +} + +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} +#endif + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + switch (effect) { + case Qt::UI_FadeMenu: + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateMenu: + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeTooltip: + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateTooltip: + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + case Qt::UI_General: + QApplicationPrivate::fade_tooltip = true; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } + + if (enable) + QApplicationPrivate::animate_ui = true; +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16 || !QApplicationPrivate::animate_ui) + return false; + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + break; + } + return QApplicationPrivate::animate_ui; +} + +/*! + \internal +*/ +bool QApplicationPrivate::qt_mac_apply_settings() +{ + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + /* + Qt settings. This is how they are written into the datastream. + Palette/ * - QPalette + font - QFont + libraryPath - QStringList + style - QString + doubleClickInterval - int + cursorFlashTime - int + wheelScrollLines - int + colorSpec - QString + defaultCodec - QString + globalStrut/width - int + globalStrut/height - int + GUIEffects - QStringList + Font Substitutions/ * - QStringList + Font Substitutions/... - QStringList + */ + + // read library (ie. plugin) path list + QString libpathkey = + QString::fromLatin1("%1.%2/libraryPath") + .arg(QT_VERSION >> 16) + .arg((QT_VERSION & 0xff00) >> 8); + QStringList pathlist = settings.value(libpathkey).toString().split(QLatin1Char(':')); + if (!pathlist.isEmpty()) { + QStringList::ConstIterator it = pathlist.begin(); + while(it != pathlist.end()) + QApplication::addLibraryPath(*it++); + } + + QString defaultcodec = settings.value(QLatin1String("defaultCodec"), QVariant(QLatin1String("none"))).toString(); + if (defaultcodec != QLatin1String("none")) { + QTextCodec *codec = QTextCodec::codecForName(defaultcodec.toLatin1().constData()); + if (codec) + QTextCodec::setCodecForTr(codec); + } + + if (qt_is_gui_used) { + QString str; + QStringList strlist; + int num; + + // read new palette + int i; + QPalette pal(QApplication::palette()); + strlist = settings.value(QLatin1String("Palette/active")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Active, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/inactive")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Inactive, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/disabled")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Disabled, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + + if (pal != QApplication::palette()) + QApplication::setPalette(pal); + + // read new font + QFont font(QApplication::font()); + str = settings.value(QLatin1String("font")).toString(); + if (!str.isEmpty()) { + font.fromString(str); + if (font != QApplication::font()) + QApplication::setFont(font); + } + + // read new QStyle + QString stylename = settings.value(QLatin1String("style")).toString(); + if (! stylename.isNull() && ! stylename.isEmpty()) { + QStyle *style = QStyleFactory::create(stylename); + if (style) + QApplication::setStyle(style); + else + stylename = QLatin1String("default"); + } else { + stylename = QLatin1String("default"); + } + + num = settings.value(QLatin1String("doubleClickInterval"), + QApplication::doubleClickInterval()).toInt(); + QApplication::setDoubleClickInterval(num); + + num = settings.value(QLatin1String("cursorFlashTime"), + QApplication::cursorFlashTime()).toInt(); + QApplication::setCursorFlashTime(num); + +#ifndef QT_NO_WHEELEVENT + num = settings.value(QLatin1String("wheelScrollLines"), + QApplication::wheelScrollLines()).toInt(); + QApplication::setWheelScrollLines(num); +#endif + + QString colorspec = settings.value(QLatin1String("colorSpec"), + QVariant(QLatin1String("default"))).toString(); + if (colorspec == QLatin1String("normal")) + QApplication::setColorSpec(QApplication::NormalColor); + else if (colorspec == QLatin1String("custom")) + QApplication::setColorSpec(QApplication::CustomColor); + else if (colorspec == QLatin1String("many")) + QApplication::setColorSpec(QApplication::ManyColor); + else if (colorspec != QLatin1String("default")) + colorspec = QLatin1String("default"); + + int w = settings.value(QLatin1String("globalStrut/width")).toInt(); + int h = settings.value(QLatin1String("globalStrut/height")).toInt(); + QSize strut(w, h); + if (strut.isValid()) + QApplication::setGlobalStrut(strut); + + QStringList effects = settings.value(QLatin1String("GUIEffects")).toStringList(); + if (!effects.isEmpty()) { + if (effects.contains(QLatin1String("none"))) + QApplication::setEffectEnabled(Qt::UI_General, false); + if (effects.contains(QLatin1String("general"))) + QApplication::setEffectEnabled(Qt::UI_General, true); + if (effects.contains(QLatin1String("animatemenu"))) + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, true); + if (effects.contains(QLatin1String("fademenu"))) + QApplication::setEffectEnabled(Qt::UI_FadeMenu, true); + if (effects.contains(QLatin1String("animatecombo"))) + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, true); + if (effects.contains(QLatin1String("animatetooltip"))) + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, true); + if (effects.contains(QLatin1String("fadetooltip"))) + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, true); + if (effects.contains(QLatin1String("animatetoolbox"))) + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, true); + } else { + QApplication::setEffectEnabled(Qt::UI_General, true); + } + + settings.beginGroup(QLatin1String("Font Substitutions")); + QStringList fontsubs = settings.childKeys(); + if (!fontsubs.isEmpty()) { + QStringList::Iterator it = fontsubs.begin(); + for (; it != fontsubs.end(); ++it) { + QString fam = QString::fromLatin1((*it).toLatin1().constData()); + QStringList subs = settings.value(fam).toStringList(); + QFont::insertSubstitutions(fam, subs); + } + } + settings.endGroup(); + } + + settings.endGroup(); + return true; +} + +// DRSWAT + +bool QApplicationPrivate::canQuit() +{ +#ifndef QT_MAC_USE_COCOA + return true; +#else + Q_Q(QApplication); +#ifdef QT_MAC_USE_COCOA + [[NSApp mainMenu] cancelTracking]; +#else + HiliteMenu(0); +#endif + + bool handle_quit = true; + if (QApplicationPrivate::modalState() && [[[[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] + menuLoader] quitMenuItem] isEnabled]) { + int visible = 0; + const QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); ++i) { + if (tlws.at(i)->isVisible()) + ++visible; + } + handle_quit = (visible <= 1); + } + if (handle_quit) { + QCloseEvent ev; + QApplication::sendSpontaneousEvent(q, &ev); + if (ev.isAccepted()) { + return true; + } + } + return false; +#endif +} + +void onApplicationWindowChangedActivation(QWidget *widget, bool activated) +{ +#if QT_MAC_USE_COCOA + if (!widget) + return; + + if (activated) { + if (QApplicationPrivate::app_style) { + QEvent ev(QEvent::Style); + qt_sendSpontaneousEvent(QApplicationPrivate::app_style, &ev); + } + qApp->setActiveWindow(widget); + } else { // deactivated + if (QApplicationPrivate::active_window == widget) + qApp->setActiveWindow(0); + } + + QMenuBar::macUpdateMenuBar(); + qt_mac_update_cursor(); +#else + Q_UNUSED(widget); + Q_UNUSED(activated); +#endif +} + + +void onApplicationChangedActivation( bool activated ) +{ +#if QT_MAC_USE_COCOA + QApplication *app = qApp; + +//NSLog(@"App Changed Activation\n"); + + if ( activated ) { + if (QApplication::desktopSettingsAware()) + qt_mac_update_os_settings(); + + if (qt_clipboard) { //manufacture an event so the clipboard can see if it has changed + QEvent ev(QEvent::Clipboard); + qt_sendSpontaneousEvent(qt_clipboard, &ev); + } + + if (app) { + QEvent ev(QEvent::ApplicationActivate); + qt_sendSpontaneousEvent(app, &ev); + } + + if (!app->activeWindow()) { + OSWindowRef wp = [NSApp keyWindow]; + if (QWidget *tmp_w = qt_mac_find_window(wp)) + app->setActiveWindow(tmp_w); + } + QMenuBar::macUpdateMenuBar(); + qt_mac_update_cursor(); + } else { // de-activated + QApplicationPrivate *priv = [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] qAppPrivate]; + while (priv->inPopupMode()) + app->activePopupWidget()->close(); + if (app) { + QEvent ev(QEvent::ApplicationDeactivate); + qt_sendSpontaneousEvent(app, &ev); + } + app->setActiveWindow(0); + } +#else + Q_UNUSED(activated); +#endif +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ } +void QApplicationPrivate::cleanupMultitouch_sys() +{ } + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qapplication_p.h b/src/gui/kernel/qapplication_p.h new file mode 100644 index 0000000000..954c6deb90 --- /dev/null +++ b/src/gui/kernel/qapplication_p.h @@ -0,0 +1,683 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QAPPLICATION_P_H +#define QAPPLICATION_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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qapplication.h" +#include "QtGui/qevent.h" +#include "QtGui/qfont.h" +#include "QtGui/qcursor.h" +#include "QtGui/qregion.h" +#include "QtCore/qmutex.h" +#include "QtCore/qtranslator.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qhash.h" +#include "QtCore/qpointer.h" +#include "private/qcoreapplication_p.h" +#include "QtGui/private/qshortcutmap_p.h" +#include +#include "QtCore/qpoint.h" +#include +#ifdef Q_WS_QWS +#include "QtGui/qscreen_qws.h" +#include +#endif +#ifdef Q_OS_SYMBIAN +#include +#endif +#ifdef Q_WS_QPA +#include +#include "qwindowsysteminterface_qpa_p.h" +#include "QtGui/qplatformintegration_qpa.h" +#endif + +QT_BEGIN_NAMESPACE + +class QClipboard; +class QGraphicsScene; +class QGraphicsSystem; +class QInputContext; +class QObject; +class QWidget; +class QSocketNotifier; +#ifndef QT_NO_GESTURES +class QGestureManager; +#endif + +extern bool qt_is_gui_used; +#ifndef QT_NO_CLIPBOARD +extern QClipboard *qt_clipboard; +#endif + +#if defined (Q_OS_WIN32) || defined (Q_OS_CYGWIN) || defined(Q_OS_WINCE) +extern QSysInfo::WinVersion qt_winver; +enum { QT_TABLET_NPACKETQSIZE = 128 }; +# ifdef Q_OS_WINCE + extern DWORD qt_cever; +# endif +#elif defined (Q_OS_MAC) +extern QSysInfo::MacVersion qt_macver; +#endif +#if defined(Q_WS_QWS) +class QWSManager; +class QDirectPainter; +struct QWSServerCleaner { ~QWSServerCleaner(); }; +#endif + +#ifndef QT_NO_TABLET +struct QTabletDeviceData +{ +#ifndef Q_WS_MAC + int minPressure; + int maxPressure; + int minTanPressure; + int maxTanPressure; + int minX, maxX, minY, maxY, minZ, maxZ; + inline QPointF scaleCoord(int coordX, int coordY, int outOriginX, int outExtentX, + int outOriginY, int outExtentY) const; +#endif + +#if defined(Q_WS_X11) || (defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA)) + QPointer widgetToGetPress; +#endif + +#ifdef Q_WS_X11 + int deviceType; + enum { + TOTAL_XINPUT_EVENTS = 64 + }; + void *device; + int eventCount; + long unsigned int eventList[TOTAL_XINPUT_EVENTS]; // XEventClass is in fact a long unsigned int + + int xinput_motion; + int xinput_key_press; + int xinput_key_release; + int xinput_button_press; + int xinput_button_release; + int xinput_proximity_in; + int xinput_proximity_out; +#elif defined(Q_WS_WIN) + qint64 llId; + int currentDevice; + int currentPointerType; +#elif defined(Q_WS_MAC) + quint64 tabletUniqueID; + int tabletDeviceType; + int tabletPointerType; + int capabilityMask; +#endif +}; + +static inline int sign(int x) +{ + return x >= 0 ? 1 : -1; +} + +#ifndef Q_WS_MAC +inline QPointF QTabletDeviceData::scaleCoord(int coordX, int coordY, + int outOriginX, int outExtentX, + int outOriginY, int outExtentY) const +{ + QPointF ret; + + if (sign(outExtentX) == sign(maxX)) + ret.setX(((coordX - minX) * qAbs(outExtentX) / qAbs(qreal(maxX - minX))) + outOriginX); + else + ret.setX(((qAbs(maxX) - (coordX - minX)) * qAbs(outExtentX) / qAbs(qreal(maxX - minX))) + + outOriginX); + + if (sign(outExtentY) == sign(maxY)) + ret.setY(((coordY - minY) * qAbs(outExtentY) / qAbs(qreal(maxY - minY))) + outOriginY); + else + ret.setY(((qAbs(maxY) - (coordY - minY)) * qAbs(outExtentY) / qAbs(qreal(maxY - minY))) + + outOriginY); + + return ret; +} +#endif + +typedef QList QTabletDeviceDataList; +QTabletDeviceDataList *qt_tablet_devices(); +# if defined(Q_WS_MAC) +typedef QHash QMacTabletHash; +QMacTabletHash *qt_mac_tablet_hash(); +# endif +#endif + +#ifdef QT3_SUPPORT +extern "C" { + typedef bool (*Ptrqt_tryAccelEvent)(QWidget *w, QKeyEvent *e); + typedef bool (*Ptrqt_tryComposeUnicode)(QWidget *w, QKeyEvent *e); + typedef bool (*Ptrqt_dispatchAccelEvent)(QWidget *w, QKeyEvent *e); +} +#endif + +#if defined(Q_WS_WIN) +typedef BOOL (WINAPI *PtrRegisterTouchWindow)(HWND, ULONG); +typedef BOOL (WINAPI *PtrGetTouchInputInfo)(HANDLE, UINT, PVOID, int); +typedef BOOL (WINAPI *PtrCloseTouchInputHandle)(HANDLE); + +#ifndef QT_NO_GESTURES +typedef BOOL (WINAPI *PtrGetGestureInfo)(HANDLE, PVOID); +typedef BOOL (WINAPI *PtrGetGestureExtraArgs)(HANDLE, UINT, PBYTE); +typedef BOOL (WINAPI *PtrCloseGestureInfoHandle)(HANDLE); +typedef BOOL (WINAPI *PtrSetGestureConfig)(HWND, DWORD, UINT, PVOID, UINT); +typedef BOOL (WINAPI *PtrGetGestureConfig)(HWND, DWORD, DWORD, PUINT, PVOID, UINT); + +typedef BOOL (WINAPI *PtrBeginPanningFeedback)(HWND); +typedef BOOL (WINAPI *PtrUpdatePanningFeedback)(HWND, LONG, LONG, BOOL); +typedef BOOL (WINAPI *PtrEndPanningFeedback)(HWND, BOOL); + +#ifndef WM_GESTURE +# define WM_GESTURE 0x0119 + +# define GID_BEGIN 1 +# define GID_END 2 +# define GID_ZOOM 3 +# define GID_PAN 4 +# define GID_ROTATE 5 +# define GID_TWOFINGERTAP 6 +# define GID_ROLLOVER 7 + +typedef struct tagGESTUREINFO +{ + UINT cbSize; + DWORD dwFlags; + DWORD dwID; + HWND hwndTarget; + POINTS ptsLocation; + DWORD dwInstanceID; + DWORD dwSequenceID; + ULONGLONG ullArguments; + UINT cbExtraArgs; +} GESTUREINFO; + +# define GC_PAN 0x00000001 +# define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002 +# define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004 + +# define GC_ZOOM 0x00000001 +# define GC_ROTATE 0x00000001 + +typedef struct tagGESTURECONFIG +{ + DWORD dwID; + DWORD dwWant; + DWORD dwBlock; +} GESTURECONFIG; + +# define GID_ROTATE_ANGLE_FROM_ARGUMENT(arg) ((((double)(arg) / 65535.0) * 4.0 * 3.14159265) - 2.0*3.14159265) + +#endif // WM_GESTURE + +#if defined(Q_WS_WINCE_WM) && defined(QT_WINCE_GESTURES) +#undef GID_ZOOM +#define GID_ZOOM 0xf000 +#undef GID_ROTATE +#define GID_ROTATE 0xf001 +#undef GID_TWOFINGERTAP +#define GID_TWOFINGERTAP 0xf002 +#undef GID_ROLLOVER +#define GID_ROLLOVER 0xf003 +#endif + +#endif // QT_NO_GESTURES + +#endif // Q_WS_WIN + +class QScopedLoopLevelCounter +{ + QThreadData *threadData; +public: + QScopedLoopLevelCounter(QThreadData *threadData) + : threadData(threadData) + { ++threadData->loopLevel; } + ~QScopedLoopLevelCounter() + { --threadData->loopLevel; } +}; + +typedef QHash FontHash; +FontHash *qt_app_fonts_hash(); + +typedef QHash PaletteHash; +PaletteHash *qt_app_palettes_hash(); + +class Q_GUI_EXPORT QApplicationPrivate : public QCoreApplicationPrivate +{ + Q_DECLARE_PUBLIC(QApplication) +public: + QApplicationPrivate(int &argc, char **argv, QApplication::Type type, int flags); + ~QApplicationPrivate(); + +#if defined(Q_WS_X11) +#ifndef QT_NO_SETTINGS + static bool x11_apply_settings(); +#endif + static void reset_instance_pointer(); +#elif defined(Q_WS_QWS) + static bool qws_apply_settings(); + static QWidget *findWidget(const QObjectList&, const QPoint &, bool rec); +#endif + static bool quitOnLastWindowClosed; + static void emitLastWindowClosed(); +#ifdef Q_WS_WINCE + static int autoMaximizeThreshold; +#endif + static bool autoSipEnabled; + static QString desktopStyleKey(); + + static QGraphicsSystem *graphicsSystem() +#if defined(Q_WS_QWS) + { return QScreen::instance()->graphicsSystem(); } +#else + { return graphics_system; } +#endif + +#if defined(Q_WS_QPA) + static QPlatformIntegration *platformIntegration() + { return platform_integration; } + + static QAbstractEventDispatcher *qt_qpa_core_dispatcher() + { return QCoreApplication::instance()->d_func()->threadData->eventDispatcher; } +#endif + + void createEventDispatcher(); + QString appName() const; + static void dispatchEnterLeave(QWidget *enter, QWidget *leave); + + //modality + static void enterModal(QWidget*); + static void leaveModal(QWidget*); + static void enterModal_sys(QWidget*); + static void leaveModal_sys(QWidget*); + static bool isBlockedByModal(QWidget *widget); + static bool modalState(); + static bool tryModalHelper(QWidget *widget, QWidget **rettop = 0); +#ifdef Q_WS_MAC + static QWidget *tryModalHelper_sys(QWidget *top); + bool canQuit(); +#endif + + bool notify_helper(QObject *receiver, QEvent * e); + + void construct( +#ifdef Q_WS_X11 + Display *dpy = 0, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 +#endif + ); + void initialize(); + void process_cmdline(); + +#if defined(Q_WS_X11) + static void x11_initialize_style(); +#endif + + enum KeyPlatform { + KB_Win = 1, + KB_Mac = 2, + KB_X11 = 4, + KB_KDE = 8, + KB_Gnome = 16, + KB_CDE = 32, + KB_S60 = 64, + KB_All = 0xffff + }; + + static uint currentPlatform(); + bool inPopupMode() const; + void closePopup(QWidget *popup); + void openPopup(QWidget *popup); + static void setFocusWidget(QWidget *focus, Qt::FocusReason reason); + static QWidget *focusNextPrevChild_helper(QWidget *toplevel, bool next); + +#ifndef QT_NO_SESSIONMANAGER + QSessionManager *session_manager; + QString session_id; + QString session_key; + bool is_session_restored; +#endif + +#ifndef QT_NO_CURSOR + QList cursor_list; +#endif +#ifndef QT_NO_GRAPHICSVIEW + // Maintain a list of all scenes to ensure font and palette propagation to + // all scenes. + QList scene_list; +#endif + + QBasicTimer toolTipWakeUp, toolTipFallAsleep; + QPoint toolTipPos, toolTipGlobalPos, hoverGlobalPos; + QPointer toolTipWidget; +#ifndef QT_NO_SHORTCUT + QShortcutMap shortcutMap; +#endif + +#ifdef QT3_SUPPORT + bool qt_compat_used; + bool qt_compat_resolved; + Ptrqt_tryAccelEvent qt_tryAccelEvent; + Ptrqt_tryComposeUnicode qt_tryComposeUnicode; + Ptrqt_dispatchAccelEvent qt_dispatchAccelEvent; + + bool use_compat() { + return qt_tryAccelEvent + && qt_tryComposeUnicode + && qt_dispatchAccelEvent; + } +#endif + static QInputContext *inputContext; + + static Qt::MouseButtons mouse_buttons; + static Qt::KeyboardModifiers modifier_buttons; + + static QSize app_strut; + static QWidgetList *popupWidgets; + static QStyle *app_style; + static int app_cspec; + static QPalette *app_pal; + static QPalette *sys_pal; + static QPalette *set_pal; + static QGraphicsSystem *graphics_system; + static QString graphics_system_name; + static bool runtime_graphics_system; +#ifdef Q_WS_QPA + static QPlatformIntegration *platform_integration; +#endif + +private: + static QFont *app_font; // private for a reason! Always use QApplication::font() instead! +public: + static QFont *sys_font; + static QFont *set_font; + static QWidget *main_widget; + static QWidget *focus_widget; + static QWidget *hidden_focus_widget; + static QWidget *active_window; + static QIcon *app_icon; + static bool obey_desktop_settings; + static int cursor_flash_time; + static int mouse_double_click_time; + static int keyboard_input_time; +#ifndef QT_NO_WHEELEVENT + static int wheel_scroll_lines; +#endif + + static bool animate_ui; + static bool animate_menu; + static bool animate_tooltip; + static bool animate_combo; + static bool fade_menu; + static bool fade_tooltip; + static bool animate_toolbox; + static bool widgetCount; // Coupled with -widgetcount switch + static bool load_testability; // Coupled with -testability switch + static QString qmljs_debug_arguments; // a string containing arguments for js/qml debugging. + static QString qmljsDebugArgumentsString(); // access string from other libraries + +#ifdef Q_WS_MAC + static bool native_modal_dialog_active; +#endif + + static void setSystemPalette(const QPalette &pal); + static void setPalette_helper(const QPalette &palette, const char* className, bool clearWidgetPaletteHash); + static void initializeWidgetPaletteHash(); + static void setSystemFont(const QFont &font); + +#if defined(Q_WS_X11) + static void applyX11SpecificCommandLineArguments(QWidget *main_widget); +#elif defined(Q_WS_QWS) + static void applyQWSSpecificCommandLineArguments(QWidget *main_widget); +#endif + +#ifdef Q_WS_MAC + static OSStatus globalEventProcessor(EventHandlerCallRef, EventRef, void *); + static OSStatus globalAppleEventProcessor(const AppleEvent *, AppleEvent *, long); + static OSStatus tabletProximityCallback(EventHandlerCallRef, EventRef, void *); +#ifdef QT_MAC_USE_COCOA + static void qt_initAfterNSAppStarted(); + static void setupAppleEvents(); +#endif + static bool qt_mac_apply_settings(); +#endif + +#ifdef Q_WS_QPA + static void processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e); + static void processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent *e); + static void processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e); + static void processTouchEvent(QWindowSystemInterfacePrivate::TouchEvent *e); + + static void processCloseEvent(QWindowSystemInterfacePrivate::CloseEvent *e); + + static void processGeometryChangeEvent(QWindowSystemInterfacePrivate::GeometryChangeEvent *e); + + static void processEnterEvent(QWindowSystemInterfacePrivate::EnterEvent *e); + static void processLeaveEvent(QWindowSystemInterfacePrivate::LeaveEvent *e); + + static void processActivatedEvent(QWindowSystemInterfacePrivate::ActivatedWindowEvent *e); + + static void processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e); + +// static void reportScreenCount(int count); + static void reportScreenCount(QWindowSystemInterfacePrivate::ScreenCountEvent *e); +// static void reportGeometryChange(int screenIndex); + static void reportGeometryChange(QWindowSystemInterfacePrivate::ScreenGeometryEvent *e); +// static void reportAvailableGeometryChange(int screenIndex); + static void reportAvailableGeometryChange(QWindowSystemInterfacePrivate::ScreenAvailableGeometryEvent *e); + +#endif + +#ifdef Q_WS_QWS + QPointer last_manager; + QWSServerCleaner qwsServerCleaner; +# ifndef QT_NO_DIRECTPAINTER + QMap *directPainters; +# endif + QRect maxWindowRect(const QScreen *screen) const { return maxWindowRects[screen]; } + void setMaxWindowRect(const QScreen *screen, int screenNo, const QRect &rect); + void setScreenTransformation(QScreen *screen, int screenNo, int transformation); +#endif + + static QApplicationPrivate *instance() { return self; } + + static QString styleOverride; + +#ifdef QT_KEYPAD_NAVIGATION + static QWidget *oldEditFocus; + static Qt::NavigationMode navigationMode; +#endif + +#if defined(Q_WS_MAC) || defined(Q_WS_X11) + void _q_alertTimeOut(); + QHash alertTimerHash; +#endif +#ifndef QT_NO_STYLE_STYLESHEET + static QString styleSheet; +#endif + static QPointer leaveAfterRelease; + static QWidget *pickMouseReceiver(QWidget *candidate, const QPoint &globalPos, QPoint &pos, + QEvent::Type type, Qt::MouseButtons buttons, + QWidget *buttonDown, QWidget *alienWidget); + static bool sendMouseEvent(QWidget *receiver, QMouseEvent *event, QWidget *alienWidget, + QWidget *native, QWidget **buttonDown, QPointer &lastMouseReceiver, + bool spontaneous = true); +#ifdef Q_OS_SYMBIAN + static void setNavigationMode(Qt::NavigationMode mode); + static TUint resolveS60ScanCode(TInt scanCode, TUint keysym); + QSet nativeWindows; + + int symbianProcessWsEvent(const QSymbianEvent *symbianEvent); + int symbianHandleCommand(const QSymbianEvent *symbianEvent); + int symbianResourceChange(const QSymbianEvent *symbianEvent); + + void _q_aboutToQuit(); +#endif +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined (Q_WS_QWS) || defined(Q_WS_MAC) || defined(Q_WS_QPA) + void sendSyntheticEnterLeave(QWidget *widget); +#endif + +#ifndef QT_NO_GESTURES + QGestureManager *gestureManager; + QWidget *gestureWidget; +#endif +#if defined(Q_WS_X11) || defined(Q_WS_WIN) + QPixmap *move_cursor; + QPixmap *copy_cursor; + QPixmap *link_cursor; +#endif +#if defined(Q_WS_WIN) + QPixmap *ignore_cursor; +#endif + QPixmap getPixmapCursor(Qt::CursorShape cshape); + + QMap > widgetForTouchPointId; + QMap appCurrentTouchPoints; + static void updateTouchPointsForWidget(QWidget *widget, QTouchEvent *touchEvent); + void initializeMultitouch(); + void initializeMultitouch_sys(); + void cleanupMultitouch(); + void cleanupMultitouch_sys(); + int findClosestTouchPointId(const QPointF &screenPos); + void appendTouchPoint(const QTouchEvent::TouchPoint &touchPoint); + void removeTouchPoint(int touchPointId); + static void translateRawTouchEvent(QWidget *widget, + QTouchEvent::DeviceType deviceType, + const QList &touchPoints); + +#if defined(Q_WS_WIN) + static bool HasTouchSupport; + static PtrRegisterTouchWindow RegisterTouchWindow; + static PtrGetTouchInputInfo GetTouchInputInfo; + static PtrCloseTouchInputHandle CloseTouchInputHandle; + + QHash touchInputIDToTouchPointID; + bool translateTouchEvent(const MSG &msg); + +#ifndef QT_NO_GESTURES + PtrGetGestureInfo GetGestureInfo; + PtrGetGestureExtraArgs GetGestureExtraArgs; + PtrCloseGestureInfoHandle CloseGestureInfoHandle; + PtrSetGestureConfig SetGestureConfig; + PtrGetGestureConfig GetGestureConfig; + PtrBeginPanningFeedback BeginPanningFeedback; + PtrUpdatePanningFeedback UpdatePanningFeedback; + PtrEndPanningFeedback EndPanningFeedback; +#endif // QT_NO_GESTURES +#endif + +#ifdef QT_RX71_MULTITOUCH + bool hasRX71MultiTouch; + + struct RX71TouchPointState { + QSocketNotifier *socketNotifier; + QTouchEvent::TouchPoint touchPoint; + + int minX, maxX, scaleX; + int minY, maxY, scaleY; + int minZ, maxZ; + }; + QList allRX71TouchPoints; + + bool readRX71MultiTouchEvents(int deviceNumber); + void fakeMouseEventFromRX71TouchEvent(); + void _q_readRX71MultiTouchEvents(); +#endif + +#if defined(Q_OS_SYMBIAN) + int pressureSupported; + int maxTouchPressure; + QList appAllTouchPoints; + + bool useTranslucentEGLSurfaces; +#endif + +private: +#ifdef Q_WS_QWS + QMap maxWindowRects; +#endif + +#ifdef Q_OS_SYMBIAN + QHash scanCodeCache; +#endif + + static QApplicationPrivate *self; + + static void giveFocusAccordingToFocusPolicy(QWidget *w, + Qt::FocusPolicy focusPolicy, + Qt::FocusReason focusReason); + static bool shouldSetFocus(QWidget *w, Qt::FocusPolicy policy); + + + static bool isAlien(QWidget *); +}; + +Q_GUI_EXPORT void qt_translateRawTouchEvent(QWidget *window, + QTouchEvent::DeviceType deviceType, + const QList &touchPoints); + +#if defined(Q_WS_WIN) + extern void qt_win_set_cursor(QWidget *, bool); +#elif defined(Q_WS_X11) + extern void qt_x11_enforce_cursor(QWidget *, bool); + extern void qt_x11_enforce_cursor(QWidget *); +#elif defined(Q_OS_SYMBIAN) + extern void qt_symbian_set_cursor(QWidget *, bool); +#elif defined (Q_WS_QPA) + extern void qt_qpa_set_cursor(QWidget *, bool); +#endif + +QT_END_NAMESPACE + +#endif // QAPPLICATION_P_H diff --git a/src/gui/kernel/qapplication_qpa.cpp b/src/gui/kernel/qapplication_qpa.cpp new file mode 100644 index 0000000000..b754cf7de8 --- /dev/null +++ b/src/gui/kernel/qapplication_qpa.cpp @@ -0,0 +1,965 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication_p.h" +#include "qcolormap.h" +#include "qpixmapcache.h" +#if !defined(QT_NO_GLIB) +#include "qeventdispatcher_glib_qpa_p.h" +#endif +#include "qeventdispatcher_qpa_p.h" +#ifndef QT_NO_CURSOR +#include "private/qcursor_p.h" +#endif + +#include "private/qwidget_p.h" +#include "private/qevent_p.h" + +#include "qgenericpluginfactory_qpa.h" +#include "qplatformintegrationfactory_qpa_p.h" +#include + +#include +#include +#include +#include +#include "qwindowsysteminterface_qpa_p.h" +#include + +#include "qdesktopwidget_qpa_p.h" + +QT_BEGIN_NAMESPACE + +static QString appName; +static QString appFont; + +QWidget *qt_button_down = 0; // widget got last button-down + +static bool app_do_modal = false; +extern QWidgetList *qt_modal_stack; // stack of modal widgets + +int qt_last_x = 0; +int qt_last_y = 0; +QPointer qt_last_mouse_receiver = 0; + +static Qt::MouseButtons buttons = Qt::NoButton; +static ulong mousePressTime; +static Qt::MouseButton mousePressButton = Qt::NoButton; +static int mousePressX; +static int mousePressY; +static int mouse_double_click_distance = 5; + +void QApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e) +{ + switch(e->type) { + case QWindowSystemInterfacePrivate::Mouse: + QApplicationPrivate::processMouseEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Wheel: + QApplicationPrivate::processWheelEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Key: + QApplicationPrivate::processKeyEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Touch: + QApplicationPrivate::processTouchEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::GeometryChange: + QApplicationPrivate::processGeometryChangeEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Enter: + QApplicationPrivate::processEnterEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Leave: + QApplicationPrivate::processLeaveEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::ActivatedWindow: + QApplicationPrivate::processActivatedEvent(static_cast(e)); + break; + case QWindowSystemInterfacePrivate::Close: + QApplicationPrivate::processCloseEvent( + static_cast(e)); + break; + case QWindowSystemInterfacePrivate::ScreenCountChange: + QApplicationPrivate::reportScreenCount( + static_cast(e)); + break; + case QWindowSystemInterfacePrivate::ScreenGeometry: + QApplicationPrivate::reportGeometryChange( + static_cast(e)); + break; + case QWindowSystemInterfacePrivate::ScreenAvailableGeometry: + QApplicationPrivate::reportAvailableGeometryChange( + static_cast(e)); + break; + default: + qWarning() << "Unknown user input event type:" << e->type; + break; + } +} + +QString QApplicationPrivate::appName() const +{ + return QT_PREPEND_NAMESPACE(appName); +} + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); +#if !defined(QT_NO_GLIB) + if (qgetenv("QT_NO_GLIB").isEmpty() && QEventDispatcherGlib::versionSupported()) + eventDispatcher = new QPAEventDispatcherGlib(q); + else +#endif + eventDispatcher = new QEventDispatcherQPA(q); +} + +static bool qt_try_modal(QWidget *widget, QEvent::Type type) +{ + QWidget * top = 0; + + if (QApplicationPrivate::tryModalHelper(widget, &top)) + return true; + + bool block_event = false; + bool paint_event = false; + + switch (type) { +#if 0 + case QEvent::Focus: + if (!static_cast(event)->simpleData.get_focus) + break; + // drop through +#endif + case QEvent::MouseButtonPress: // disallow mouse/key events + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + case QEvent::KeyPress: + case QEvent::KeyRelease: + block_event = true; + break; + default: + break; + } + + if ((block_event || paint_event) && top->parentWidget() == 0) + top->raise(); + + return !block_event; +} + + + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + qt_modal_stack->insert(0, widget); + app_do_modal = true; +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget ) +{ + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + } + } + app_do_modal = qt_modal_stack != 0; +} + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +void QApplicationPrivate::closePopup(QWidget *popup) +{ + Q_Q(QApplication); + if (!popupWidgets) + return; + popupWidgets->removeAll(popup); + +//### +// if (popup == qt_popup_down) { +// qt_button_down = 0; +// qt_popup_down = 0; +// } + + if (QApplicationPrivate::popupWidgets->count() == 0) { // this was the last popup + delete QApplicationPrivate::popupWidgets; + QApplicationPrivate::popupWidgets = 0; + + //### replay mouse event? + + //### transfer/release mouse grab + + //### transfer/release keyboard grab + + //give back focus + + if (active_window) { + if (QWidget *fw = active_window->focusWidget()) { + if (fw != QApplication::focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + q->sendEvent(fw, &e); + } + } + } + + } else { + // A popup was closed, so the previous popup gets the focus. + + QWidget* aw = QApplicationPrivate::popupWidgets->last(); + if (QWidget *fw = aw->focusWidget()) + fw->setFocus(Qt::PopupFocusReason); + + //### regrab the keyboard and mouse in case 'popup' lost the grab + + + } + +} + +static int openPopupCount = 0; +void QApplicationPrivate::openPopup(QWidget *popup) +{ + openPopupCount++; + if (!popupWidgets) { // create list + popupWidgets = new QWidgetList; + + /* only grab if you are the first/parent popup */ + //#### ->grabMouse(popup,true); + //#### ->grabKeyboard(popup,true); + //### popupGrabOk = true; + } + popupWidgets->append(popup); // add to end of list + + // popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + if (popup->focusWidget()) { + popup->focusWidget()->setFocus(Qt::PopupFocusReason); + } else if (popupWidgets->count() == 1) { // this was the first popup + if (QWidget *fw = QApplication::focusWidget()) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + QApplication::sendEvent(fw, &e); + } + } +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ +} + +void QApplicationPrivate::cleanupMultitouch_sys() +{ +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ +} + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setDoubleClickInterval(int ms) +{ + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + return QApplicationPrivate::mouse_double_click_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int lines) +{ + QApplicationPrivate::wheel_scroll_lines = lines; +} + +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} +#endif + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + switch (effect) { + case Qt::UI_AnimateMenu: + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeMenu: + if (enable) + QApplicationPrivate::animate_menu = true; + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateTooltip: + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_FadeTooltip: + if (enable) + QApplicationPrivate::animate_tooltip = true; + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16 || !QApplicationPrivate::animate_ui) + return false; + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + return QApplicationPrivate::animate_ui; + } +} + +#ifndef QT_NO_CURSOR +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + qt_qpa_set_cursor(0, false); +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + qt_qpa_set_cursor(0, false); +} + +#endif// QT_NO_CURSOR + +QWidget *QApplication::topLevelAt(const QPoint &pos) +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + + QList screens = pi->screens(); + QList::const_iterator screen = screens.constBegin(); + QList::const_iterator end = screens.constEnd(); + + // The first screen in a virtual environment should know about all top levels + if (pi->isVirtualDesktop()) { + QWidget *w = (*screen)->topLevelAt(pos); + return w; + } + + while (screen != end) { + if ((*screen)->geometry().contains(pos)) + return (*screen)->topLevelAt(pos); + ++screen; + } + return 0; +} + +void QApplication::beep() +{ +} + +void QApplication::alert(QWidget *, int) +{ +} + +QPlatformNativeInterface *QApplication::platformNativeInterface() +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + return pi->nativeInterface(); +} + +static void init_platform(const QString &name, const QString &platformPluginPath) +{ + QApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, platformPluginPath); + if (!QApplicationPrivate::platform_integration) { + QStringList keys = QPlatformIntegrationFactory::keys(platformPluginPath); + QString fatalMessage = + QString::fromLatin1("Failed to load platform plugin \"%1\". Available platforms are: \n").arg(name); + foreach(QString key, keys) { + fatalMessage.append(key + QString::fromLatin1("\n")); + } + qFatal("%s", fatalMessage.toLocal8Bit().constData()); + + } + +} + + +static void cleanup_platform() +{ + delete QApplicationPrivate::platform_integration; + QApplicationPrivate::platform_integration = 0; +} + +static void init_plugins(const QList pluginList) +{ + for (int i = 0; i < pluginList.count(); ++i) { + QByteArray pluginSpec = pluginList.at(i); + qDebug() << "init_plugins" << i << pluginSpec; + int colonPos = pluginSpec.indexOf(':'); + QObject *plugin; + if (colonPos < 0) + plugin = QGenericPluginFactory::create(QLatin1String(pluginSpec), QString()); + else + plugin = QGenericPluginFactory::create(QLatin1String(pluginSpec.mid(0, colonPos)), + QLatin1String(pluginSpec.mid(colonPos+1))); + qDebug() << " created" << plugin; + } +} + +#ifndef QT_NO_QWS_INPUTMETHODS +class QDummyInputContext : public QInputContext +{ +public: + explicit QDummyInputContext(QObject* parent = 0) : QInputContext(parent) {} + ~QDummyInputContext() {} + QString identifierName() { return QString(); } + QString language() { return QString(); } + + void reset() {} + bool isComposing() const { return false; } + +}; +#endif // QT_NO_QWS_INPUTMETHODS + +void qt_init(QApplicationPrivate *priv, int type) +{ + Q_UNUSED(type); + + qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + char *p; + char **argv = priv->argv; + int argc = priv->argc; + + if (argv && *argv) { //apparently, we allow people to pass 0 on the other platforms + p = strrchr(argv[0], '/'); + appName = QString::fromLocal8Bit(p ? p + 1 : argv[0]); + } + + QList pluginList; + QString platformPluginPath = QLatin1String(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH")); + QByteArray platformName; +#ifdef QT_QPA_DEFAULT_PLATFORM_NAME + platformName = QT_QPA_DEFAULT_PLATFORM_NAME; +#endif + QByteArray platformNameEnv = qgetenv("QT_QPA_PLATFORM"); + if (!platformNameEnv.isEmpty()) { + platformName = platformNameEnv; + } + + // Get command line params + + int j = argc ? 1 : 0; + for (int i=1; iargc) { + priv->argv[j] = 0; + priv->argc = j; + } + +#if 0 + QByteArray pluginEnv = qgetenv("QT_QPA_PLUGINS"); + if (!pluginEnv.isEmpty()) { + pluginList.append(pluginEnv.split(';')); + } +#endif + + init_platform(QLatin1String(platformName), platformPluginPath); + init_plugins(pluginList); + + QColormap::initialize(); + QFont::initialize(); +#ifndef QT_NO_CURSOR +// QCursorData::initialize(); +#endif + + qApp->setObjectName(appName); + +#ifndef QT_NO_QWS_INPUTMETHODS + qApp->setInputContext(new QDummyInputContext(qApp)); +#endif +} + +void qt_cleanup() +{ + cleanup_platform(); + + QPixmapCache::clear(); +#ifndef QT_NO_CURSOR + QCursorData::cleanup(); +#endif + QFont::cleanup(); + QColormap::cleanup(); + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = 0; + + QApplicationPrivate::active_window = 0; //### this should not be necessary +} + + +#ifdef QT3_SUPPORT +void QApplication::setMainWidget(QWidget *mainWidget) +{ + QApplicationPrivate::main_widget = mainWidget; + if (QApplicationPrivate::main_widget && windowIcon().isNull() + && QApplicationPrivate::main_widget->testAttribute(Qt::WA_SetWindowIcon)) + setWindowIcon(QApplicationPrivate::main_widget->windowIcon()); +} +#endif + +void QApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e) +{ + // qDebug() << "handleMouseEvent" << tlw << ev.pos() << ev.globalPos() << hex << ev.buttons(); + static QWeakPointer implicit_mouse_grabber; + + QEvent::Type type; + // move first + Qt::MouseButtons stateChange = e->buttons ^ buttons; + if (e->globalPos != QPoint(qt_last_x, qt_last_y) && (stateChange != Qt::NoButton)) { + QWindowSystemInterfacePrivate::MouseEvent * newMouseEvent = + new QWindowSystemInterfacePrivate::MouseEvent(e->widget.data(), e->timestamp, e->localPos, e->globalPos, e->buttons); + QWindowSystemInterfacePrivate::windowSystemEventQueue.prepend(newMouseEvent); // just in case the move triggers a new event loop + stateChange = Qt::NoButton; + } + + QWidget * tlw = e->widget.data(); + + QPoint localPoint = e->localPos; + QPoint globalPoint = e->globalPos; + QWidget *mouseWindow = tlw; + + Qt::MouseButton button = Qt::NoButton; + + + if (qt_last_x != globalPoint.x() || qt_last_y != globalPoint.y()) { + type = QEvent::MouseMove; + qt_last_x = globalPoint.x(); + qt_last_y = globalPoint.y(); + if (qAbs(globalPoint.x() - mousePressX) > mouse_double_click_distance|| + qAbs(globalPoint.y() - mousePressY) > mouse_double_click_distance) + mousePressButton = Qt::NoButton; + } + else { // check to see if a new button has been pressed/released + for (int check = Qt::LeftButton; + check <= Qt::XButton2; + check = check << 1) { + if (check & stateChange) { + button = Qt::MouseButton(check); + break; + } + } + if (button == Qt::NoButton) { + // Ignore mouse events that don't change the current state + return; + } + buttons = e->buttons; + if (button & e->buttons) { + if ((e->timestamp - mousePressTime) < static_cast(QApplication::doubleClickInterval()) && button == mousePressButton) { + type = QEvent::MouseButtonDblClick; + mousePressButton = Qt::NoButton; + } + else { + type = QEvent::MouseButtonPress; + mousePressTime = e->timestamp; + mousePressButton = button; + mousePressX = qt_last_x; + mousePressY = qt_last_y; + } + } + else + type = QEvent::MouseButtonRelease; + } + + if (self->inPopupMode()) { + //popup mouse handling is magical... + mouseWindow = qApp->activePopupWidget(); + + implicit_mouse_grabber.clear(); + //### how should popup mode and implicit mouse grab interact? + + } else if (tlw && app_do_modal && !qt_try_modal(tlw, QEvent::MouseButtonRelease) ) { + //even if we're blocked by modality, we should deliver the mouse release event.. + //### this code is not completely correct: multiple buttons can be pressed simultaneously + if (!(implicit_mouse_grabber && buttons == Qt::NoButton)) { + //qDebug() << "modal blocked mouse event to" << tlw; + return; + } + } + + // find the tlw if we didn't get it from the plugin + if (!mouseWindow) { + mouseWindow = QApplication::topLevelAt(globalPoint); + } + + if (!mouseWindow && !implicit_mouse_grabber) + mouseWindow = QApplication::desktop(); + + if (mouseWindow && mouseWindow != tlw) { + //we did not get a sensible localPoint from the window system, so let's calculate it + localPoint = mouseWindow->mapFromGlobal(globalPoint); + } + + // which child should have it? + QWidget *mouseWidget = mouseWindow; + if (mouseWindow) { + QWidget *w = mouseWindow->childAt(localPoint); + if (w) { + mouseWidget = w; + } + } + + //handle implicit mouse grab + if (type == QEvent::MouseButtonPress && !implicit_mouse_grabber) { + implicit_mouse_grabber = mouseWidget; + + Q_ASSERT(mouseWindow); + mouseWindow->activateWindow(); //focus + } else if (implicit_mouse_grabber) { + mouseWidget = implicit_mouse_grabber.data(); + mouseWindow = mouseWidget->window(); + if (mouseWindow != tlw) + localPoint = mouseWindow->mapFromGlobal(globalPoint); + } + + Q_ASSERT(mouseWidget); + + //localPoint is local to mouseWindow, but it needs to be local to mouseWidget + localPoint = mouseWidget->mapFrom(mouseWindow, localPoint); + + if (buttons == Qt::NoButton) { + //qDebug() << "resetting mouse grabber"; + implicit_mouse_grabber.clear(); + } + + if (mouseWidget != qt_last_mouse_receiver) { + dispatchEnterLeave(mouseWidget, qt_last_mouse_receiver); + qt_last_mouse_receiver = mouseWidget; + } + + // Remember, we might enter a modal event loop when sending the event, + // so think carefully before adding code below this point. + + // qDebug() << "sending mouse ev." << ev.type() << localPoint << globalPoint << ev.button() << ev.buttons() << mouseWidget << "mouse grabber" << implicit_mouse_grabber; + + QMouseEvent ev(type, localPoint, globalPoint, button, buttons, QApplication::keyboardModifiers()); + + QList > cursors = QPlatformCursorPrivate::getInstances(); + foreach (QWeakPointer cursor, cursors) { + if (cursor) + cursor.data()->pointerEvent(ev); + } + + int oldOpenPopupCount = openPopupCount; + QApplication::sendSpontaneousEvent(mouseWidget, &ev); + +#ifndef QT_NO_CONTEXTMENU + if (type == QEvent::MouseButtonPress && button == Qt::RightButton && (openPopupCount == oldOpenPopupCount)) { + QContextMenuEvent e(QContextMenuEvent::Mouse, localPoint, globalPoint, QApplication::keyboardModifiers()); + QApplication::sendSpontaneousEvent(mouseWidget, &e); + } +#endif // QT_NO_CONTEXTMENU +} + + +//### there's a lot of duplicated logic here -- refactoring required! + +void QApplicationPrivate::processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e) +{ +// QPoint localPoint = ev.pos(); + QPoint globalPoint = e->globalPos; +// bool trustLocalPoint = !!tlw; //is there something the local point can be local to? + QWidget *mouseWidget; + + qt_last_x = globalPoint.x(); + qt_last_y = globalPoint.y(); + + QWidget *mouseWindow = e->widget.data(); + + // find the tlw if we didn't get it from the plugin + if (!mouseWindow) { + mouseWindow = QApplication::topLevelAt(globalPoint); + } + + if (!mouseWindow) + return; + + mouseWidget = mouseWindow; + + if (app_do_modal && !qt_try_modal(mouseWindow, QEvent::Wheel) ) { + qDebug() << "modal blocked wheel event" << mouseWindow; + return; + } + QPoint p = mouseWindow->mapFromGlobal(globalPoint); + QWidget *w = mouseWindow->childAt(p); + if (w) { + mouseWidget = w; + p = mouseWidget->mapFromGlobal(globalPoint); + } + + QWheelEvent ev(p, globalPoint, e->delta, buttons, QApplication::keyboardModifiers(), + e->orient); + QApplication::sendSpontaneousEvent(mouseWidget, &ev); +} + + + +// Remember, Qt convention is: keyboard state is state *before* + +void QApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent *e) +{ + QWidget *focusW = 0; + if (self->inPopupMode()) { + QWidget *popupW = qApp->activePopupWidget(); + focusW = popupW->focusWidget() ? popupW->focusWidget() : popupW; + } + if (!focusW) + focusW = QApplication::focusWidget(); + if (!focusW) { + focusW = e->widget.data(); + } + if (!focusW) + focusW = QApplication::activeWindow(); + + //qDebug() << "handleKeyEvent" << hex << e->key() << e->modifiers() << e->text() << "widget" << focusW; + + if (!focusW) + return; + if (app_do_modal && !qt_try_modal(focusW, e->keyType)) + return; + + if (e->nativeScanCode || e->nativeVirtualKey || e->nativeModifiers) { + QKeyEventEx ev(e->keyType, e->key, e->modifiers, e->unicode, e->repeat, e->repeatCount, + e->nativeScanCode, e->nativeVirtualKey, e->nativeModifiers); + QApplication::sendSpontaneousEvent(focusW, &ev); + } else { + QKeyEvent ev(e->keyType, e->key, e->modifiers, e->unicode, e->repeat, e->repeatCount); + QApplication::sendSpontaneousEvent(focusW, &ev); + } +} + +void QApplicationPrivate::processEnterEvent(QWindowSystemInterfacePrivate::EnterEvent *e) +{ + QApplicationPrivate::dispatchEnterLeave(e->enter.data(),0); + qt_last_mouse_receiver = e->enter.data(); +} + +void QApplicationPrivate::processLeaveEvent(QWindowSystemInterfacePrivate::LeaveEvent *e) +{ + QApplicationPrivate::dispatchEnterLeave(0,qt_last_mouse_receiver); + + if (e->leave.data() && !e->leave.data()->isAncestorOf(qt_last_mouse_receiver)) //(???) this should not happen + QApplicationPrivate::dispatchEnterLeave(0, e->leave.data()); + qt_last_mouse_receiver = 0; + +} + +void QApplicationPrivate::processActivatedEvent(QWindowSystemInterfacePrivate::ActivatedWindowEvent *e) +{ + QApplication::setActiveWindow(e->activated.data()); +} + +void QApplicationPrivate::processGeometryChangeEvent(QWindowSystemInterfacePrivate::GeometryChangeEvent *e) +{ + if (e->tlw.isNull()) + return; + QWidget *tlw = e->tlw.data(); + if (!tlw->isWindow()) + return; //geo of native child widgets is controlled by lighthouse + //so we already have sent the events; besides this new rect + //is not mapped to parent + + QRect newRect = e->newGeometry; + QRect cr(tlw->geometry()); + bool isResize = cr.size() != newRect.size(); + bool isMove = cr.topLeft() != newRect.topLeft(); + tlw->data->crect = newRect; + if (isResize) { + QResizeEvent e(tlw->data->crect.size(), cr.size()); + QApplication::sendSpontaneousEvent(tlw, &e); + tlw->update(); + } + + if (isMove) { + //### frame geometry + QMoveEvent e(tlw->data->crect.topLeft(), cr.topLeft()); + QApplication::sendSpontaneousEvent(tlw, &e); + } +} + +void QApplicationPrivate::processCloseEvent(QWindowSystemInterfacePrivate::CloseEvent *e) +{ + if (e->topLevel.isNull()) { + //qDebug() << "QApplicationPrivate::processCloseEvent NULL"; + return; + } + e->topLevel.data()->d_func()->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); +} + +void QApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::TouchEvent *e) +{ + translateRawTouchEvent(e->widget.data(), e->devType, e->points); +} + +void QApplicationPrivate::reportScreenCount(QWindowSystemInterfacePrivate::ScreenCountEvent *e) +{ + // This operation only makes sense after the QApplication constructor runs + if (QCoreApplication::startingUp()) + return; + + QApplication::desktop()->d_func()->updateScreenList(); + // signal anything listening for creation or deletion of screens + QDesktopWidget *desktop = QApplication::desktop(); + emit desktop->screenCountChanged(e->count); +} + +void QApplicationPrivate::reportGeometryChange(QWindowSystemInterfacePrivate::ScreenGeometryEvent *e) +{ + // This operation only makes sense after the QApplication constructor runs + if (QCoreApplication::startingUp()) + return; + + QApplication::desktop()->d_func()->updateScreenList(); + + // signal anything listening for screen geometry changes + QDesktopWidget *desktop = QApplication::desktop(); + emit desktop->resized(e->index); + + // make sure maximized and fullscreen windows are updated + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = list.size() - 1; i >= 0; --i) { + QWidget *w = list.at(i); + if (w->isFullScreen()) + w->d_func()->setFullScreenSize_helper(); + else if (w->isMaximized()) + w->d_func()->setMaxWindowState_helper(); + } +} + +void QApplicationPrivate::reportAvailableGeometryChange( + QWindowSystemInterfacePrivate::ScreenAvailableGeometryEvent *e) +{ + // This operation only makes sense after the QApplication constructor runs + if (QCoreApplication::startingUp()) + return; + + QApplication::desktop()->d_func()->updateScreenList(); + + // signal anything listening for screen geometry changes + QDesktopWidget *desktop = QApplication::desktop(); + emit desktop->workAreaResized(e->index); + + // make sure maximized and fullscreen windows are updated + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = list.size() - 1; i >= 0; --i) { + QWidget *w = list.at(i); + if (w->isFullScreen()) + w->d_func()->setFullScreenSize_helper(); + else if (w->isMaximized()) + w->d_func()->setMaxWindowState_helper(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qapplication_qws.cpp b/src/gui/kernel/qapplication_qws.cpp new file mode 100644 index 0000000000..642d3e6afc --- /dev/null +++ b/src/gui/kernel/qapplication_qws.cpp @@ -0,0 +1,3797 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" +#include "qlibrary.h" +#include "qcursor.h" +#include "qapplication.h" +#include "private/qapplication_p.h" +#include "qwidget.h" +#include "qbitarray.h" +#include "qpainter.h" +#include "qpixmapcache.h" +#include "qdatetime.h" +#include "qtextcodec.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qsocketnotifier.h" +#include "qsessionmanager.h" +#include "qclipboard.h" +#include "qbitmap.h" +#include "qwssocket_qws.h" +#include "qtransportauth_qws.h" +#include "private/qtransportauth_qws_p.h" +#include "qwsevent_qws.h" +#include "private/qwscommand_qws_p.h" +#include "qwsproperty_qws.h" +#include "qscreen_qws.h" +#include "qscreenproxy_qws.h" +#include "qcopchannel_qws.h" +#include "private/qlock_p.h" +#include "private/qwslock_p.h" +//#include "qmemorymanager_qws.h" +#include "qwsmanager_qws.h" +//#include "qwsregionmanager_qws.h" +#include "qwindowsystem_qws.h" +#include "private/qwindowsystem_p.h" +#include "qdecorationfactory_qws.h" + +#include "qwsdisplay_qws.h" +#include "private/qwsdisplay_qws_p.h" +#include "private/qwsinputcontext_p.h" +#include "qfile.h" +#include "qhash.h" +#include "qdesktopwidget.h" +#include "qcolormap.h" +#include "private/qcursor_p.h" +#include "qsettings.h" +#include "qdebug.h" +#include "qeventdispatcher_qws_p.h" +#if !defined(QT_NO_GLIB) +# include "qeventdispatcher_glib_qws_p.h" +#endif + + +#include "private/qwidget_p.h" +#include "private/qbackingstore_p.h" +#include "private/qwindowsurface_qws_p.h" +#include "private/qfont_p.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_OS_VXWORKS +# include +#else +# include +#endif +#include +#include + +#include + +#ifndef QT_NO_QWS_MULTIPROCESS +#ifdef QT_NO_QSHM +#include +#include +#ifndef Q_OS_DARWIN +# include +#endif +#include +#else +#include "private/qwssharedmemory_p.h" +#endif +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_DIRECTPAINTER +class QDirectPainter; +extern void qt_directpainter_region(QDirectPainter *dp, const QRegion &alloc, int type); +#ifndef QT_NO_QWSEMBEDWIDGET +extern void qt_directpainter_embedevent(QDirectPainter *dp, + const QWSEmbedEvent *e); +#endif +#endif // QT_NO_DIRECTPAINTER + +const int qwsSharedRamSize = 1 * 1024; // misc data, written by server, read by clients + +extern QApplication::Type qt_appType; +extern QDesktopWidget *qt_desktopWidget; + +//these used to be environment variables, they are initialized from +//environment variables in + +bool qws_savefonts = false; +bool qws_screen_is_interlaced=false; //### should be detected +bool qws_shared_memory = false; +bool qws_sw_cursor = true; +bool qws_accel = true; // ### never set +QByteArray qws_display_spec(":0"); +Q_GUI_EXPORT int qws_display_id = 0; +Q_GUI_EXPORT int qws_client_id = 0; +QWidget *qt_pressGrab = 0; +QWidget *qt_mouseGrb = 0; +int *qt_last_x = 0; +int *qt_last_y = 0; + +static int mouse_x_root = -1; +static int mouse_y_root = -1; +static int mouse_state = 0; +static int mouse_double_click_distance = 5; + +int qt_servershmid = -1; + +bool qws_overrideCursor = false; +#ifndef QT_NO_QWS_MANAGER + +extern Q_GUI_EXPORT QWSServer *qwsServer; + +static QDecoration *qws_decoration = 0; +#endif + +#if defined(QT_DEBUG) +/* +extern "C" void dumpmem(const char* m) +{ + static int init=0; + static int prev=0; + FILE* f = fopen("/proc/meminfo","r"); + // char line[100]; + int total=0,used=0,free=0,shared=0,buffers=0,cached=0; + fscanf(f,"%*[^M]Mem: %d %d %d %d %d %d",&total,&used,&free,&shared,&buffers,&cached); + used -= buffers + cached; + if (!init) { + init=used; + } else { + printf("%40s: %+8d = %8d\n",m,used-init-prev,used-init); + prev = used-init; + } + fclose(f); +} +*/ +#endif + +// Get the name of the directory where Qt for Embedded Linux temporary data should +// live. +QString qws_dataDir() +{ + static QString result; + if (!result.isEmpty()) + return result; + result = QT_VFB_DATADIR(qws_display_id); + QByteArray dataDir = result.toLocal8Bit(); + +#if defined(Q_OS_INTEGRITY) + /* ensure filesystem is ready before starting requests */ + WaitForFileSystemInitialization(); +#endif + + if (QT_MKDIR(dataDir, 0700)) { + if (errno != EEXIST) { + qFatal("Cannot create Qt for Embedded Linux data directory: %s", dataDir.constData()); + } + } + + QT_STATBUF buf; + if (QT_LSTAT(dataDir, &buf)) + qFatal("stat failed for Qt for Embedded Linux data directory: %s", dataDir.constData()); + + if (!S_ISDIR(buf.st_mode)) + qFatal("%s is not a directory", dataDir.constData()); + +#if !defined(Q_OS_INTEGRITY) && !defined(Q_OS_VXWORKS) + if (buf.st_uid != getuid()) + qFatal("Qt for Embedded Linux data directory is not owned by user %d", getuid()); + + if ((buf.st_mode & 0677) != 0600) + qFatal("Qt for Embedded Linux data directory has incorrect permissions: %s", dataDir.constData()); +#endif + + result.append("/"); + return result; +} + +// Get the filename of the pipe Qt for Embedded Linux uses for server/client comms +Q_GUI_EXPORT QString qws_qtePipeFilename() +{ + qws_dataDir(); + return QTE_PIPE(qws_display_id); +} + +static void setMaxWindowRect(const QRect &rect) +{ + const QList subScreens = qt_screen->subScreens(); + QScreen *screen = qt_screen; + int screenNo = 0; + for (int i = 0; i < subScreens.size(); ++i) { + if (subScreens.at(i)->region().contains(rect)) { + screen = subScreens.at(i); + screenNo = i; + break; + } + } + + QApplicationPrivate *ap = QApplicationPrivate::instance(); + ap->setMaxWindowRect(screen, screenNo, rect); +} + +void QApplicationPrivate::setMaxWindowRect(const QScreen *screen, int screenNo, + const QRect &rect) +{ + if (maxWindowRects.value(screen) == rect) + return; + + maxWindowRects[screen] = rect; + + // Re-resize any maximized windows + QWidgetList l = QApplication::topLevelWidgets(); + for (int i = 0; i < l.size(); ++i) { + QWidget *w = l.at(i); + QScreen *s = w->d_func()->getScreen(); + if (w->isMaximized() && s == screen) + w->d_func()->setMaxWindowState_helper(); + } + + if ( qt_desktopWidget ) // XXX workaround crash + emit QApplication::desktop()->workAreaResized(screenNo); +} + +#ifndef QT_NO_QWS_DYNAMICSCREENTRANSFORMATION + +typedef void (*TransformFunc)(QScreen *, int); +#ifndef QT_NO_QWS_TRANSFORMED +extern "C" void qws_setScreenTransformation(QScreen *, int); +#endif +static TransformFunc getTransformationFunction() +{ + static TransformFunc func = 0; + + if (!func) { +#ifdef QT_NO_QWS_TRANSFORMED +# ifndef QT_NO_LIBRARY + // symbol is not built into the library, search for the plugin + const QStringList paths = QApplication::libraryPaths(); + foreach (const QString &path, paths) { + const QString file = path + QLatin1String("/gfxdrivers/libqgfxtransformed"); + func = (TransformFunc)QLibrary::resolve(file, + "qws_setScreenTransformation"); + if (func) + break; + } +# endif +#else + func = qws_setScreenTransformation; +#endif + if (!func) + func = (TransformFunc)-1; + } + + if (func == (TransformFunc)-1) + return 0; + + return func; +} + +static void setScreenTransformation(int screenNo, int transformation) +{ + QScreen *screen = QScreen::instance(); + const QList subScreens = screen->subScreens(); + + if (screenNo == -1) + screenNo = 0; + + if (screenNo == -1 && !subScreens.isEmpty()) + screenNo = 0; + + if (subScreens.isEmpty() && screenNo == 0) { + // nothing + } else if (screenNo < 0 || screenNo >= subScreens.size()) { + qWarning("setScreenTransformation: invalid screen %i", screenNo); + return; + } + + if (screenNo < subScreens.size()) + screen = subScreens.at(screenNo); + + QApplicationPrivate *ap = QApplicationPrivate::instance(); + ap->setScreenTransformation(screen, screenNo, transformation); +} + +void QApplicationPrivate::setScreenTransformation(QScreen *screen, + int screenNo, + int transformation) +{ + QScreen *transformed = screen; + + while (transformed->classId() == QScreen::ProxyClass) + transformed = static_cast(transformed)->screen(); + + if (transformed->classId() != QScreen::TransformedClass) + return; + + TransformFunc setScreenTransformation = getTransformationFunction(); + if (!setScreenTransformation) + return; + + setScreenTransformation(transformed, transformation); + + // need to re-configure() proxies bottom-up + if (screen->classId() == QScreen::ProxyClass) { + QList proxies; + QScreen *s = screen; + + do { + QProxyScreen *proxy = static_cast(s); + proxies.append(proxy); + s = proxy->screen(); + } while (s->classId() == QScreen::ProxyClass); + + do { + QProxyScreen *proxy = proxies.takeLast(); + proxy->setScreen(proxy->screen()); // triggers configure() + } while (!proxies.isEmpty()); + } + + if (qt_desktopWidget) { // XXX workaround crash for early screen transform events + QDesktopWidget *desktop = QApplication::desktop(); + + emit desktop->resized(screenNo); + if (maxWindowRect(screen).isEmpty()) // not explicitly set + emit desktop->workAreaResized(screenNo); + } + + QWSServer *server = QWSServer::instance(); + if (server) { + server->updateWindowRegions(); + QRegion r = screen->region(); + server->refresh(r); + } + + // make sure maximized and fullscreen windows are updated + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = list.size() - 1; i >= 0; --i) { + QWidget *w = list.at(i); + if (w->isFullScreen()) + w->d_func()->setFullScreenSize_helper(); + else if (w->isMaximized()) + w->d_func()->setMaxWindowState_helper(); + } +} + +#endif // QT_NO_QWS_DYNAMICSCREENTRANSFORMATION + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ + + +static QString appName; // application name +static const char *appFont = 0; // application font +static const char *appBGCol = 0; // application bg color +static const char *appFGCol = 0; // application fg color +static const char *appBTNCol = 0; // application btn color +static const char *mwGeometry = 0; // main widget geometry +static const char *mwTitle = 0; // main widget title +//static bool mwIconic = false; // main widget iconified + +static bool app_do_modal = false; // modal mode +Q_GUI_EXPORT QWSDisplay *qt_fbdpy = 0; // QWS `display' +QLock *QWSDisplay::lock = 0; + +static int mouseButtonPressed = 0; // last mouse button pressed +static int mouseButtonPressTime = 0; // when was a button pressed +static short mouseXPos, mouseYPos; // mouse position in act window + +extern QWidgetList *qt_modal_stack; // stack of modal widgets + +static QWidget *popupButtonFocus = 0; +static QWidget *popupOfPopupButtonFocus = 0; +static bool popupCloseDownMode = false; +static bool popupGrabOk; +static QPointer *mouseInWidget = 0; +QPointer qt_last_mouse_receiver = 0; + +static bool sm_blockUserInput = false; // session management + +QWidget *qt_button_down = 0; // widget got last button-down +WId qt_last_cursor = 0xffffffff; // Was -1, but WIds are unsigned + +class QWSMouseEvent; +class QWSKeyEvent; + +class QETWidget : public QWidget // event translator widget +{ +public: + bool translateMouseEvent(const QWSMouseEvent *, int oldstate); + bool translateKeyEvent(const QWSKeyEvent *, bool grab); + bool translateRegionEvent(const QWSRegionEvent *); +#ifndef QT_NO_QWSEMBEDWIDGET + void translateEmbedEvent(const QWSEmbedEvent *event); +#endif + bool translateWheelEvent(const QWSMouseEvent *me); + void repaintDecoration(QRegion r, bool post); + void updateRegion(); + + bool raiseOnClick() + { + // With limited windowmanagement/taskbar/etc., raising big windows + // (eg. spreadsheet) over the top of everything else (eg. calculator) + // is just annoying. + return !isMaximized() && !isFullScreen(); + } +}; + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); +#if !defined(QT_NO_GLIB) + if (qgetenv("QT_NO_GLIB").isEmpty() && QEventDispatcherGlib::versionSupported()) + eventDispatcher = (q->type() != QApplication::Tty + ? new QWSEventDispatcherGlib(q) + : new QEventDispatcherGlib(q)); + else +#endif + eventDispatcher = (q->type() != QApplication::Tty + ? new QEventDispatcherQWS(q) + : new QEventDispatcherUNIX(q)); +} + +// Single-process stuff. This should maybe move into qwindowsystem_qws.cpp + +static bool qws_single_process; +static QList incoming; +static QList outgoing; + +void qt_client_enqueue(const QWSEvent *event) +{ + QWSEvent *copy = QWSEvent::factory(event->type); + copy->copyFrom(event); + incoming.append(copy); +} + +QList *qt_get_server_queue() +{ + return &outgoing; +} + +void qt_server_enqueue(const QWSCommand *command) +{ + QWSCommand *copy = QWSCommand::factory(command->type); + QT_TRY { + copy->copyFrom(command); + outgoing.append(copy); + } QT_CATCH(...) { + delete copy; + QT_RETHROW; + } +} + +QWSDisplay::Data::Data(QObject* parent, bool singleProcess) +{ +#ifdef QT_NO_QWS_MULTIPROCESS + Q_UNUSED(parent); + Q_UNUSED(singleProcess); +#else + if (singleProcess) + csocket = 0; + else { + csocket = new QWSSocket(parent); + QObject::connect(csocket, SIGNAL(disconnected()), + qApp, SLOT(quit())); + } + clientLock = 0; +#endif + init(); +} + +QWSDisplay::Data::~Data() +{ +// delete rgnMan; rgnMan = 0; +// delete memorymanager; memorymanager = 0; + qt_screen->disconnect(); + delete qt_screen; qt_screen = 0; +#ifndef QT_NO_QWS_CURSOR + delete qt_screencursor; qt_screencursor = 0; +#endif +#ifndef QT_NO_QWS_MULTIPROCESS + shm.detach(); + if (csocket) { + QWSCommand shutdownCmd(QWSCommand::Shutdown, 0, 0); + shutdownCmd.write(csocket); + csocket->flush(); // may be pending QCop message, eg. + delete csocket; + } + delete clientLock; + clientLock = 0; +#endif + delete connected_event; + delete mouse_event; + delete current_event; + qDeleteAll(queue); +#ifndef QT_NO_COP + delete qcop_response; +#endif +} + +#ifndef QT_NO_QWS_MULTIPROCESS +bool QWSDisplay::Data::lockClient(QWSLock::LockType type, int timeout) +{ + return !clientLock || clientLock->lock(type, timeout); +} + +void QWSDisplay::Data::unlockClient(QWSLock::LockType type) +{ + if (clientLock) clientLock->unlock(type); +} + +bool QWSDisplay::Data::waitClient(QWSLock::LockType type, int timeout) +{ + return !clientLock || clientLock->wait(type, timeout); +} + +QWSLock* QWSDisplay::Data::getClientLock() +{ + return clientLock; +} +#endif // QT_NO_QWS_MULTIPROCESS + +void QWSDisplay::Data::flush() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) { + csocket->waitForReadyRead(0); + csocket->flush(); + } +#endif +} + +#if 0 +void QWSDisplay::Data::debugQueue() { + for (int i = 0; i < queue.size(); ++i) { + QWSEvent *e = queue.at(i); + qDebug( " ev %d type %d sl %d rl %d", i, e->type, e->simpleLen, e->rawLen); + } +} +#endif + +bool QWSDisplay::Data::queueNotEmpty() +{ + return mouse_event/*||region_event*/||queue.count() > 0; +} +QWSEvent* QWSDisplay::Data::dequeue() +{ + QWSEvent *r=0; + if (queue.count()) { + r = queue.first(); + queue.removeFirst(); + if (r->type == QWSEvent::Region) + region_events_count--; + } else if (mouse_event) { + r = mouse_event; + mouse_event = 0; +#ifdef QAPPLICATION_EXTRA_DEBUG + mouse_event_count = 0; +#endif + } + return r; +} + +QWSEvent* QWSDisplay::Data::peek() +{ + return queue.first(); +} + +bool QWSDisplay::Data::directServerConnection() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + return csocket == 0; +#else + return true; +#endif +} + +void QWSDisplay::Data::create(int n) +{ + QWSCreateCommand cmd(n); + sendCommand(cmd); +} + +void QWSDisplay::Data::flushCommands() +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) + csocket->flush(); +#endif +} + +void QWSDisplay::Data::sendCommand(QWSCommand & cmd) +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) + cmd.write(csocket); + else +#endif + qt_server_enqueue(&cmd); +} + +void QWSDisplay::Data::sendSynchronousCommand(QWSCommand & cmd) +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) { + lockClient(QWSLock::Communication); + cmd.write(csocket); + bool ok = true; + while (csocket->bytesToWrite() > 0) { + if (!csocket->waitForBytesWritten(-1)) { + qCritical("QWSDisplay::Data::sendSynchronousCommand: %s", + qPrintable(csocket->errorString())); + ok = false; + break; + } + } + if (ok) + waitClient(QWSLock::Communication); + } else +#endif + qt_server_enqueue(&cmd); +} + +int QWSDisplay::Data::takeId() +{ + int unusedIdCount = unused_identifiers.count(); + if (unusedIdCount <= 10) + create(15); + if (unusedIdCount == 0) { + create(1); // Make sure we have an incoming id to wait for, just in case we're recursive + waitForCreation(); + } + + return unused_identifiers.takeFirst(); +} + +void QWSDisplay::Data::setMouseFilter(void (*filter)(QWSMouseEvent*)) +{ + mouseFilter = filter; +} + +#ifndef QT_NO_QWS_MULTIPROCESS + +QWSLock* QWSDisplay::Data::clientLock = 0; + +void Q_GUI_EXPORT qt_app_reinit( const QString& newAppName ) +{ + qt_fbdpy->d->reinit( newAppName ); +} + +#endif // QT_NO_QWS_MULTIPROCESS + +class QDesktopWidget; + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSDisplay::Data::reinit( const QString& newAppName ) +{ + Q_ASSERT(csocket); + + delete connected_event; + connected_event = 0; + region_events_count = 0; +// region_ack = 0; + delete mouse_event; + mouse_event = 0; +// region_event = 0; + region_offset_window = 0; +#ifndef QT_NO_COP + delete qcop_response; + qcop_response = 0; +#endif + delete current_event; + current_event = 0; +#ifdef QAPPLICATION_EXTRA_DEBUG + mouse_event_count = 0; +#endif + mouseFilter = 0; + + qt_desktopWidget = 0; + delete QWSDisplay::Data::clientLock; + QWSDisplay::Data::clientLock = 0; + + QString pipe = qws_qtePipeFilename(); + + // QWS client + // Cleanup all cached ids + unused_identifiers.clear(); + delete csocket; + + appName = newAppName; + qApp->setObjectName( appName ); + + csocket = new QWSSocket(); + QObject::connect(csocket, SIGNAL(disconnected()), + qApp, SLOT(quit())); + csocket->connectToLocalFile(pipe); + + QWSDisplay::Data::clientLock = new QWSLock(); + + QWSIdentifyCommand cmd; + cmd.setId(appName, QWSDisplay::Data::clientLock->id()); + +#ifndef QT_NO_SXE + QTransportAuth *a = QTransportAuth::getInstance(); + QTransportAuth::Data *d = a->connectTransport( + QTransportAuth::UnixStreamSock | + QTransportAuth::Trusted, + csocket->socketDescriptor()); + QAuthDevice *ad = a->authBuf( d, csocket ); + ad->setClient( csocket ); + + cmd.write(ad); +#else + cmd.write(csocket); +#endif + + // wait for connect confirmation + waitForConnection(); + + qws_client_id = connected_event->simpleData.clientId; + + if (!QWSDisplay::initLock(pipe, false)) + qFatal("Cannot get display lock"); + + if (shm.attach(connected_event->simpleData.servershmid)) { + sharedRam = static_cast(shm.address()); + QScreen *s = qt_get_screen(qws_display_id, qws_display_spec.constData()); + if (s) + sharedRamSize += s->memoryNeeded(QLatin1String(qws_display_spec.constData())); + } else { + perror("QWSDisplay::Data::init"); + qFatal("Client can't attach to main ram memory."); + } + + qApp->desktop(); + + // We wait for creation mainly so that we can process important + // initialization events such as MaxWindowRect that are sent + // before object id creation. Waiting here avoids later window + // resizing since we have the MWR before windows are displayed. + waitForCreation(); + + sharedRamSize -= sizeof(int); + qt_last_x = reinterpret_cast(sharedRam + sharedRamSize); + sharedRamSize -= sizeof(int); + qt_last_y = reinterpret_cast(sharedRam + sharedRamSize); + +#ifndef QT_NO_COP + QCopChannel::reregisterAll(); +#endif + csocket->flush(); +} +#endif + +void QWSDisplay::Data::init() +{ + connected_event = 0; + region_events_count = 0; +// region_ack = 0; + mouse_event = 0; + mouse_state = -1; + mouse_winid = 0; +// region_event = 0; + region_offset_window = 0; +#ifndef QT_NO_COP + qcop_response = 0; +#endif + current_event = 0; +#ifdef QAPPLICATION_EXTRA_DEBUG + mouse_event_count = 0; +#endif + mouseFilter = 0; + + QString pipe = qws_qtePipeFilename(); + + sharedRamSize = qwsSharedRamSize; + +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) { + // QWS client + + connectToPipe(); + + QWSDisplay::Data::clientLock = new QWSLock(); + + QWSIdentifyCommand cmd; + cmd.setId(appName, QWSDisplay::Data::clientLock->id()); +#ifndef QT_NO_SXE + QTransportAuth *a = QTransportAuth::getInstance(); + QTransportAuth::Data *d = a->connectTransport( + QTransportAuth::UnixStreamSock | + QTransportAuth::Trusted, + csocket->socketDescriptor()); + QAuthDevice *ad = a->authBuf( d, csocket ); + ad->setClient( csocket ); + cmd.write(ad); +#else + cmd.write(csocket); +#endif + + // create(30); // not necessary, server will send ids anyway + waitForConnection(); + + qws_client_id = connected_event->simpleData.clientId; + + // now we want to get the exact display spec to use if we haven't + // specified anything. + if (qws_display_spec.at(0) == ':') + qws_display_spec = connected_event->display; + + if (!QWSDisplay::initLock(pipe, false)) + qFatal("Cannot get display lock"); + + if (shm.attach(connected_event->simpleData.servershmid)) { + sharedRam = static_cast(shm.address()); + QScreen *s = qt_get_screen(qws_display_id, qws_display_spec.constData()); + if (s) + sharedRamSize += s->memoryNeeded(QLatin1String(qws_display_spec.constData())); + } else { + perror("QWSDisplay::Data::init"); + qFatal("Client can't attach to main ram memory."); + } + + // We wait for creation mainly so that we can process important + // initialization events such as MaxWindowRect that are sent + // before object id creation. Waiting here avoids later window + // resizing since we have the MWR before windows are displayed. + waitForCreation(); + } else +#endif + { + create(30); + + // QWS server + if (!QWSDisplay::initLock(pipe, true)) + qFatal("Cannot get display lock"); + + QScreen *s = qt_get_screen(qws_display_id, qws_display_spec.constData()); + if (s) + sharedRamSize += s->memoryNeeded(QLatin1String(qws_display_spec.constData())); + +#ifndef QT_NO_QWS_MULTIPROCESS + + if (!shm.create(sharedRamSize)) { + perror("Cannot create main ram shared memory\n"); + qFatal("Unable to allocate %d bytes of shared memory", sharedRamSize); + } + qt_servershmid = shm.id(); + sharedRam = static_cast(shm.address()); +#else + sharedRam=static_cast(malloc(sharedRamSize)); +#endif + // Need to zero index count at end of block, might as well zero + // the rest too + memset(sharedRam,0,sharedRamSize); + + QWSIdentifyCommand cmd; + cmd.setId(appName, -1); + qt_server_enqueue(&cmd); + } + + // Allow some memory for the graphics driver too + //### Note that sharedRamSize() has side effects; it must be called + //### once, and only once, and before initDevice() + sharedRamSize -= qt_screen->sharedRamSize(sharedRam+sharedRamSize); + +#ifndef QT_NO_QWS_MULTIPROCESS + if(!csocket) +#endif + { + //QWS server process + if (!qt_screen->initDevice()) + qFatal("Unable to initialize screen driver!"); + } + + sharedRamSize -= sizeof(int); + qt_last_x = reinterpret_cast(sharedRam + sharedRamSize); + sharedRamSize -= sizeof(int); + qt_last_y = reinterpret_cast(sharedRam + sharedRamSize); + + /* Initialise framebuffer memory manager */ + /* Add 4k for luck and to avoid clobbering hardware cursor */ +// int screensize=qt_screen->screenSize(); +// memorymanager=new QMemoryManager(qt_screen->base()+screensize+4096, +// qt_screen->totalSize()-(screensize+4096),0); + +// #ifndef QT_NO_QWS_MULTIPROCESS +// rgnMan = new QWSRegionManager(pipe, csocket); +// #else +// rgnMan = new QWSRegionManager(pipe, 0); //####### not necessary +// #endif +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) + csocket->flush(); +#endif +} + + +QWSEvent* QWSDisplay::Data::readMore() +{ +#ifdef QT_NO_QWS_MULTIPROCESS + return incoming.isEmpty() ? 0 : incoming.takeFirst(); +#else + if (!csocket) + return incoming.isEmpty() ? 0 : incoming.takeFirst(); + // read next event + if (!current_event) { + int event_type = qws_read_uint(csocket); + + if (event_type >= 0) { + current_event = QWSEvent::factory(event_type); + } + } + + if (current_event) { + if (current_event->read(csocket)) { + // Finished reading a whole event. + QWSEvent* result = current_event; + current_event = 0; + return result; + } + } + + // Not finished reading a whole event. + return 0; +#endif +} + +void QWSDisplay::Data::fillQueue() +{ + QWSServer::processEventQueue(); + QWSEvent *e = readMore(); +#ifndef QT_NO_QWS_MULTIPROCESS + int bytesAvailable = csocket ? csocket->bytesAvailable() : 0; + int bytesRead = 0; +#endif + while (e) { +#ifndef QT_NO_QWS_MULTIPROCESS + bytesRead += QWS_PROTOCOL_ITEM_SIZE((*e)); +#endif + if (e->type == QWSEvent::Connected) { + connected_event = static_cast(e); + return; + } else if (e->type == QWSEvent::Creation) { + QWSCreationEvent *ce = static_cast(e); + int id = ce->simpleData.objectid; + int count = ce->simpleData.count; + for (int i = 0; i < count; ++i) + unused_identifiers.append(id++); + delete e; + } else if (e->type == QWSEvent::Mouse) { + if (!qt_screen) { + delete e; + } else { + QWSMouseEvent *me = static_cast(e); + if (mouseFilter) + mouseFilter(me); +#ifdef QAPPLICATION_EXTRA_DEBUG + static const char *defaultAction= "INITIAL"; + const char * action = defaultAction; +#endif + delete mouse_event; + if (mouse_winid != me->window () + || mouse_state != me->simpleData.state) { + queue.append(me); + mouse_winid = me->window(); + mouse_state = me->simpleData.state; + mouse_event = 0; +#ifdef QAPPLICATION_EXTRA_DEBUG + mouse_event_count = 0; + action = "ENQUEUE"; +#endif + } else { +#ifdef QAPPLICATION_EXTRA_DEBUG + if (mouse_event) + action = "COMPRESS"; + mouse_event_count++; +#endif + mouse_event = me; + } +#ifdef QAPPLICATION_EXTRA_DEBUG + if (me->simpleData.state !=0 || action != defaultAction || mouse_event_count != 0) + qDebug("fillQueue %s (%d,%d), state %x win %d count %d", action, + me->simpleData.x_root, me->simpleData.y_root, me->simpleData.state, + me->window(), mouse_event_count); +#endif + } +#ifndef QT_NO_QWS_MULTIPROCESS + } else if (e->type == QWSEvent::Region && clientLock) { + // not really an unlock, decrements the semaphore + region_events_count++; + clientLock->unlock(QWSLock::RegionEvent); + queue.append(e); +#endif +#ifndef QT_NO_QWS_PROPERTIES + } else if (e->type == QWSEvent::PropertyReply) { + QWSPropertyReplyEvent *pe = static_cast(e); + int len = pe->simpleData.len; + char *data; + if (len <= 0) { + data = 0; + } else { + data = new char[len]; + memcpy(data, pe->data, len) ; + } + QPaintDevice::qwsDisplay()->getPropertyLen = len; + QPaintDevice::qwsDisplay()->getPropertyData = data; + delete e; +#endif // QT_NO_QWS_PROPERTIES + } else if (e->type==QWSEvent::MaxWindowRect && qt_screen) { + // Process this ASAP, in case new widgets are created (startup) + setMaxWindowRect((static_cast(e))->simpleData.rect); + delete e; +#ifndef QT_NO_QWS_DYNAMICSCREENTRANSFORMATION + } else if (e->type == QWSEvent::ScreenTransformation) { + QWSScreenTransformationEvent *pe = static_cast(e); + setScreenTransformation(pe->simpleData.screen, + pe->simpleData.transformation); + delete e; +#endif +#ifndef QT_NO_COP + } else if (e->type == QWSEvent::QCopMessage) { + QWSQCopMessageEvent *pe = static_cast(e); + if (pe->simpleData.is_response) { + qcop_response = pe; + } else { + queue.append(e); + } +#endif + } else { + queue.append(e); + } + //debugQueue(); +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket && bytesRead >= bytesAvailable) + break; +#endif + e = readMore(); + } +} + +#ifndef QT_NO_QWS_MULTIPROCESS + +static int qws_connection_timeout = 5; + +void QWSDisplay::Data::connectToPipe() +{ + Q_ASSERT(csocket); + + int timeout = qgetenv("QWS_CONNECTION_TIMEOUT").toInt(); + if (timeout) + qws_connection_timeout = timeout; + + const QString pipe = qws_qtePipeFilename(); + int i = 0; + while (!csocket->connectToLocalFile(pipe)) { + if (++i > qws_connection_timeout) { + qWarning("No Qt for Embedded Linux server appears to be running."); + qWarning("If you want to run this program as a server,"); + qWarning("add the \"-qws\" command-line option."); + exit(1); + } + sleep(1); + } +} + +void QWSDisplay::Data::waitForConnection() +{ + connected_event = 0; + + for (int i = 0; i < qws_connection_timeout; i++) { + fillQueue(); + if (connected_event) + return; + csocket->flush(); + csocket->waitForReadyRead(1000); + } + + csocket->flush(); + if (!connected_event) + qFatal("Did not receive a connection event from the qws server"); +} + +void QWSDisplay::Data::waitForRegionAck(int winId) +{ + QWSEvent *ack = 0; + + if (csocket) { // GuiClient + int i = 0; + while (!ack) { + fillQueue(); + + while (i < queue.size()) { + QWSEvent *e = queue.at(i); + if (e->type == QWSEvent::Region && e->window() == winId) { + ack = e; + queue.removeAt(i); + break; + } + ++i; + } + + if (!ack) { + csocket->flush(); + csocket->waitForReadyRead(1000); + } + } + } else { // GuiServer + fillQueue(); + for (int i = 0; i < queue.size(); /* nothing */) { + QWSEvent *e = queue.at(i); + if (e->type == QWSEvent::Region && e->window() == winId) { + ack = e; + queue.removeAt(i); + break; + } + ++i; + } + if (!ack) // already processed + return; + } + + Q_ASSERT(ack); + + qApp->qwsProcessEvent(ack); + delete ack; + region_events_count--; +} + +void QWSDisplay::Data::waitForRegionEvents(int winId, bool ungrabDisplay) +{ + if (!clientLock) + return; + + int removedEventsCount = 0; + + // fill queue with unreceived region events + if (!clientLock->hasLock(QWSLock::RegionEvent)) { + bool ungrabbed = false; + if (ungrabDisplay && QWSDisplay::grabbed()) { + QWSDisplay::ungrab(); + ungrabbed = true; + } + + for (;;) { + fillQueue(); + if (clientLock->hasLock(QWSLock::RegionEvent)) + break; + csocket->flush(); + csocket->waitForReadyRead(1000); + } + + if (ungrabbed) + QWSDisplay::grab(true); + } + + // check the queue for pending region events + QWSEvent *regionEvent = 0; + for (int i = 0; i < queue.size(); /* nothing */) { + QWSEvent *e = queue.at(i); + if (e->type == QWSEvent::Region && e->window() == winId) { + QWSRegionEvent *re = static_cast(e); + if (re->simpleData.type == QWSRegionEvent::Allocation) { + delete regionEvent; + regionEvent = re; + } + queue.removeAt(i); + removedEventsCount++; + } else { + ++i; + } + } + + if (regionEvent) { + qApp->qwsProcessEvent(regionEvent); + delete regionEvent; + } + region_events_count -= removedEventsCount; +} + +bool QWSDisplay::Data::hasPendingRegionEvents() const +{ + if (clientLock && !clientLock->hasLock(QWSLock::RegionEvent)) + return true; + + return region_events_count > 0; +} + +#endif // QT_NO_QWS_MULTIPROCESS + +void QWSDisplay::Data::waitForCreation() +{ + fillQueue(); +#ifndef QT_NO_QWS_MULTIPROCESS + while (unused_identifiers.count() == 0) { + if (csocket) { + csocket->flush(); + csocket->waitForReadyRead(1000); + } + fillQueue(); + } +#endif +} + + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSDisplay::Data::waitForPropertyReply() +{ + if (!csocket) + return; + fillQueue(); + while (qt_fbdpy->getPropertyLen == -2) { + csocket->flush(); + csocket->waitForReadyRead(1000); + fillQueue(); + } +} +#endif + +#ifndef QT_NO_COP +void QWSDisplay::Data::waitForQCopResponse() +{ + for (;;) { + fillQueue(); + if (qcop_response) + break; +#ifndef QT_NO_QWS_MULTIPROCESS + if (csocket) { + csocket->flush(); + csocket->waitForReadyRead(1000); + } +#endif + } + queue.prepend(qcop_response); + qcop_response = 0; +} +#endif + +/*! + \class QWSDisplay + \brief The QWSDisplay class provides a display for QWS; it is an internal class. + + \internal + + \ingroup qws +*/ + +QWSDisplay::QWSDisplay() +{ + d = new Data(0, qws_single_process); +} + +QWSDisplay::~QWSDisplay() +{ + delete d; + delete lock; + lock = 0; +} + +bool QWSDisplay::grabbed() +{ + return lock->locked(); +} + +void QWSDisplay::grab() +{ + lock->lock(QLock::Read); +} + +void QWSDisplay::grab(bool write) +{ + lock->lock(write ? QLock::Write : QLock::Read); + +} +void QWSDisplay::ungrab() +{ + lock->unlock(); +} + +#if 0 +QWSRegionManager *QWSDisplay::regionManager() const +{ + return d->rgnMan; +} +#endif + +bool QWSDisplay::eventPending() const +{ +#ifndef QT_NO_QWS_MULTIPROCESS + d->flush(); +#endif + d->fillQueue(); + return d->queueNotEmpty(); +} + + +/* + Caller must delete return value! + */ +QWSEvent *QWSDisplay::getEvent() +{ + d->fillQueue(); + Q_ASSERT(d->queueNotEmpty()); + QWSEvent* e = d->dequeue(); + + return e; +} + +uchar* QWSDisplay::frameBuffer() const { return qt_screen->base(); } +int QWSDisplay::width() const { return qt_screen->width(); } +int QWSDisplay::height() const { return qt_screen->height(); } +int QWSDisplay::depth() const { return qt_screen->depth(); } +int QWSDisplay::pixmapDepth() const { return qt_screen->pixmapDepth(); } +bool QWSDisplay::supportsDepth(int depth) const { return qt_screen->supportsDepth(depth); } +uchar *QWSDisplay::sharedRam() const { return d->sharedRam; } +int QWSDisplay::sharedRamSize() const { return d->sharedRamSize; } + +#ifndef QT_NO_QWS_PROPERTIES + +void QWSDisplay::addProperty(int winId, int property) +{ + QWSAddPropertyCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + d->sendCommand(cmd); +} + +void QWSDisplay::setProperty(int winId, int property, int mode, const QByteArray &data) +{ + QWSSetPropertyCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + cmd.simpleData.mode = mode; + cmd.setData(data.constData(), data.size()); + d->sendCommand(cmd); +} + +void QWSDisplay::setProperty(int winId, int property, int mode, + const char * data) +{ + QWSSetPropertyCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + cmd.simpleData.mode = mode; + cmd.setData(data, strlen(data)); + d->sendCommand(cmd); +} + +void QWSDisplay::removeProperty(int winId, int property) +{ + QWSRemovePropertyCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + d->sendCommand(cmd); +} + +/* + It is the caller's responsibility to delete[] \a data. + */ +bool QWSDisplay::getProperty(int winId, int property, char *&data, int &len) +{ + if (d->directServerConnection()) { + const char *propertyData; + bool retval = qwsServer->d_func()->get_property(winId, property, propertyData, len); + if (len <= 0) { + data = 0; + } else { + data = new char[len]; + memcpy(data, propertyData, len) ; + } + return retval; + } + QWSGetPropertyCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + d->sendCommand(cmd); + + getPropertyLen = -2; + getPropertyData = 0; + +#ifndef QT_NO_QWS_MULTIPROCESS + d->waitForPropertyReply(); +#endif + + len = getPropertyLen; + data = getPropertyData; + + getPropertyLen = -2; + getPropertyData = 0; + + return len != -1; +} + +#endif // QT_NO_QWS_PROPERTIES + +void QWSDisplay::setAltitude(int winId, int alt, bool fixed) +{ + QWSChangeAltitudeCommand cmd; +#ifdef QT_DEBUG + memset(cmd.simpleDataPtr, 0, sizeof(cmd.simpleData)); //shut up Valgrind +#endif + cmd.simpleData.windowid = winId; + cmd.simpleData.altitude = QWSChangeAltitudeCommand::Altitude(alt); + cmd.simpleData.fixed = fixed; + if (d->directServerConnection()) { + qwsServer->d_func()->set_altitude(&cmd); + } else { + d->sendSynchronousCommand(cmd); + } +} + +void QWSDisplay::setOpacity(int winId, int opacity) +{ + QWSSetOpacityCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.opacity = opacity; + if (d->directServerConnection()) { + qwsServer->d_func()->set_opacity(&cmd); + } else { + d->sendCommand(cmd); + } +} + + + +void QWSDisplay::requestFocus(int winId, bool get) +{ + QWSRequestFocusCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.flag = get; + if (d->directServerConnection()) + qwsServer->d_func()->request_focus(&cmd); + else + d->sendCommand(cmd); +} + +void QWSDisplay::setIdentity(const QString &appName) +{ + QWSIdentifyCommand cmd; +#ifdef QT_NO_QWS_MULTIPROCESS + const int id = -1; +#else + const int id = QWSDisplay::Data::clientLock ? QWSDisplay::Data::clientLock->id() : -1; +#endif + cmd.setId(appName, id); + if (d->directServerConnection()) + qwsServer->d_func()->set_identity(&cmd); + else + d->sendCommand(cmd); +} + +void QWSDisplay::nameRegion(int winId, const QString& n, const QString &c) +{ + QWSRegionNameCommand cmd; + cmd.simpleData.windowid = winId; + cmd.setName(n, c); + if (d->directServerConnection()) + qwsServer->d_func()->name_region(&cmd); + else + d->sendCommand(cmd); +} + +void QWSDisplay::requestRegion(int winId, const QString &surfaceKey, + const QByteArray &surfaceData, + const QRegion ®ion) +{ + if (d->directServerConnection()) { + qwsServer->d_func()->request_region(winId, surfaceKey, + surfaceData, region); + } else { + QWSRegionCommand cmd; + cmd.setData(winId, surfaceKey, surfaceData, region); + d->sendSynchronousCommand(cmd); + } +} + +void QWSDisplay::repaintRegion(int winId, int windowFlags, bool opaque, QRegion r) +{ + if (d->directServerConnection()) { + qwsServer->d_func()->repaint_region(winId, windowFlags, opaque, r); + } else { + QVector ra = r.rects(); + + /* + for (int i = 0; i < ra.size(); i++) { + QRect r(ra[i]); + qDebug("rect: %d %d %d %d", r.x(), r.y(), r.right(), r.bottom()); + } + */ + + QWSRepaintRegionCommand cmd; + /* XXX QWSRegionCommand is padded out in a compiler dependent way. + Zeroed out to avoid valgrind reporting uninitialized memory usage. + */ +#ifdef QT_DEBUG + memset(cmd.simpleDataPtr, 0, sizeof(cmd.simpleData)); //shut up Valgrind +#endif + cmd.simpleData.windowid = winId; + cmd.simpleData.windowFlags = windowFlags; + cmd.simpleData.opaque = opaque; + cmd.simpleData.nrectangles = ra.count(); + cmd.setData(reinterpret_cast(ra.constData()), + ra.count() * sizeof(QRect), false); + + d->sendSynchronousCommand(cmd); + } +} + + +void QWSDisplay::moveRegion(int winId, int dx, int dy) +{ + QWSRegionMoveCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.dx = dx; + cmd.simpleData.dy = dy; + + if (d->directServerConnection()) { + qwsServer->d_func()->move_region(&cmd); + } else { + d->sendSynchronousCommand(cmd); + } +// d->offsetPendingExpose(winId, QPoint(cmd.simpleData.dx, cmd.simpleData.dy)); +} + +void QWSDisplay::destroyRegion(int winId) +{ + QWSRegionDestroyCommand cmd; + cmd.simpleData.windowid = winId; + if (d->directServerConnection()) { + qwsServer->d_func()->destroy_region(&cmd); + } else { + d->sendCommand(cmd); + } +} + +#ifndef QT_NO_QWS_INPUTMETHODS + +void QWSDisplay::sendIMUpdate(int type, int winId, int widgetid) +{ + QWSIMUpdateCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.widgetid = widgetid; + + cmd.simpleData.type = type; + + if (d->directServerConnection()) { + qwsServer->d_func()->im_update(&cmd); + } else { + d->sendCommand(cmd); + } +} + +void QWSDisplay::sendIMResponse(int winId, int property, const QVariant &result) +{ + QWSIMResponseCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.property = property; + + cmd.setResult(result); + + if (d->directServerConnection()) { + qwsServer->d_func()->im_response(&cmd); + } else { + d->sendCommand(cmd); + } +} + +void QWSDisplay::resetIM() +{ + sendIMUpdate(QWSInputMethod::Reset, -1, -1); +} + +void QWSDisplay::sendIMMouseEvent(int index, bool isPress) +{ + QWSIMMouseCommand cmd; + cmd.simpleData.index = index; + cmd.simpleData.state = isPress ? QWSServer::MousePress : QWSServer::MouseRelease; + if (d->directServerConnection()) { + qwsServer->d_func()->send_im_mouse(&cmd); + } else { + d->sendCommand(cmd); + } +} + +#endif + +int QWSDisplay::takeId() +{ + return d->takeId(); +} + +bool QWSDisplay::initLock(const QString &filename, bool create) +{ + if (!lock) { + lock = new QLock(filename, 'd', create); + + if (!lock->isValid()) { + delete lock; + lock = 0; + return false; + } + } + + return true; +} + +void QWSDisplay::setSelectionOwner(int winId, const QTime &time) +{ + QWSSetSelectionOwnerCommand cmd; + cmd.simpleData.windowid = winId; + cmd.simpleData.hour = time.hour(); + cmd.simpleData.minute = time.minute(); + cmd.simpleData.sec = time.second(); + cmd.simpleData.ms = time.msec(); + d->sendCommand(cmd); +} + +void QWSDisplay::convertSelection(int winId, int selectionProperty, const QString &mimeTypes) +{ +#ifdef QT_NO_QWS_PROPERTIES + Q_UNUSED(mimeTypes); +#else + // ### we need the atom/property thingy like in X here + addProperty(winId, QT_QWS_PROPERTY_CONVERTSELECTION); + setProperty(winId, QT_QWS_PROPERTY_CONVERTSELECTION, + int(QWSPropertyManager::PropReplace), mimeTypes.toLatin1()); +#endif + QWSConvertSelectionCommand cmd; + cmd.simpleData.requestor = winId; + cmd.simpleData.selection = selectionProperty; + cmd.simpleData.mimeTypes = QT_QWS_PROPERTY_CONVERTSELECTION; + d->sendCommand(cmd); +} + +void QWSDisplay::defineCursor(int id, const QBitmap &curs, const QBitmap &mask, + int hotX, int hotY) +{ + const QImage cursImg = curs.toImage().convertToFormat(QImage::Format_MonoLSB); + const QImage maskImg = mask.toImage().convertToFormat(QImage::Format_MonoLSB); + + QWSDefineCursorCommand cmd; + cmd.simpleData.width = curs.width(); + cmd.simpleData.height = curs.height(); + cmd.simpleData.hotX = hotX; + cmd.simpleData.hotY = hotY; + cmd.simpleData.id = id; + + + // must copy each scanline since there might be gaps between them + const int height = curs.height(); + const int width = curs.width(); + const int dst_bpl = (width + 7) / 8; + + int dataLen = dst_bpl * height; + uchar *data = new uchar[dataLen*2]; + uchar *dst = data; + + int src_bpl = cursImg.bytesPerLine(); + const uchar *cursSrc = cursImg.bits(); + for (int i = 0; i < height; ++i) { + memcpy(dst, cursSrc + i*src_bpl, dst_bpl); + dst += dst_bpl; + } + + src_bpl = maskImg.bytesPerLine(); + const uchar *maskSrc = maskImg.bits(); + for (int i = 0; i < height; ++i) { + memcpy(dst, maskSrc + i*src_bpl, dst_bpl); + dst += dst_bpl; + } + + cmd.setData(reinterpret_cast(data), dataLen*2); + delete [] data; + d->sendCommand(cmd); +} + +void QWSDisplay::destroyCursor(int id) +{ + QWSDefineCursorCommand cmd; + cmd.simpleData.width = 0; + cmd.simpleData.height = 0; + cmd.simpleData.hotX = 0; + cmd.simpleData.hotY = 0; + cmd.simpleData.id = id; + cmd.setData(0, 0); + + d->sendCommand(cmd); +} + +#ifndef QT_NO_SOUND +void QWSDisplay::playSoundFile(const QString& f) +{ + QWSPlaySoundCommand cmd; + cmd.setFileName(f); + d->sendCommand(cmd); +} +#endif + +#ifndef QT_NO_COP +void QWSDisplay::registerChannel(const QString& channel) +{ + QWSQCopRegisterChannelCommand reg; + reg.setChannel(channel); + qt_fbdpy->d->sendCommand(reg); +} + +void QWSDisplay::sendMessage(const QString &channel, const QString &msg, + const QByteArray &data) +{ + QWSQCopSendCommand com; + com.setMessage(channel, msg, data); + qt_fbdpy->d->sendCommand(com); +} + +void QWSDisplay::flushCommands() +{ + qt_fbdpy->d->flushCommands(); +} + +/* + caller deletes result +*/ +QWSQCopMessageEvent* QWSDisplay::waitForQCopResponse() +{ + qt_fbdpy->d->waitForQCopResponse(); + QWSQCopMessageEvent *e = static_cast(qt_fbdpy->d->dequeue()); + Q_ASSERT(e->type == QWSEvent::QCopMessage); + return e; +} +#endif + +void QWSDisplay::sendFontCommand(int type, const QByteArray &fontName) +{ + QWSFontCommand cmd; + cmd.simpleData.type = type; + cmd.setFontName(fontName); + d->sendCommand(cmd); +} + +void QWSDisplay::setWindowCaption(QWidget *w, const QString &c) +{ + if (w->isWindow()) { + nameRegion(w->internalWinId(), w->objectName(), c); + static_cast(w)->repaintDecoration(qApp->desktop()->rect(), true); + } +} + +void QWSDisplay::selectCursor(QWidget *w, unsigned int cursId) +{ + if (cursId != qt_last_cursor) + { + QWidget *top = w->window(); + qt_last_cursor = cursId; + QWSSelectCursorCommand cmd; + cmd.simpleData.windowid = top->internalWinId(); + cmd.simpleData.id = cursId; + d->sendCommand(cmd); + d->flush(); + } +} + +void QWSDisplay::setCursorPosition(int x, int y) +{ + QWSPositionCursorCommand cmd; + cmd.simpleData.newX = x; + cmd.simpleData.newY = y; + d->sendCommand(cmd); + d->flush(); +} + +void QWSDisplay::grabMouse(QWidget *w, bool grab) +{ + QWidget *top = w->window(); + QWSGrabMouseCommand cmd; +#ifdef QT_DEBUG + memset(cmd.simpleDataPtr, 0, sizeof(cmd.simpleData)); //shut up Valgrind +#endif + cmd.simpleData.windowid = top->winId(); + cmd.simpleData.grab = grab; + d->sendCommand(cmd); + d->flush(); +} + +void QWSDisplay::grabKeyboard(QWidget *w, bool grab) +{ + QWidget *top = w->window(); + QWSGrabKeyboardCommand cmd; +#ifdef QT_DEBUG + memset(cmd.simpleDataPtr, 0, sizeof(cmd.simpleData)); //shut up Valgrind +#endif + cmd.simpleData.windowid = top->winId(); + cmd.simpleData.grab = grab; + d->sendCommand(cmd); + d->flush(); +} + +QList QWSDisplay::windowList() +{ + QList ret; + if(d->directServerConnection()) { + QList * qin=QWSServer::windowList(); + for (int i = 0; i < qin->count(); ++i) { + QWSInternalWindowInfo * qwi = qin->at(i); + QWSWindowInfo tmp; + tmp.winid = qwi->winid; + tmp.clientid = qwi->clientid; + tmp.name = QString(qwi->name); + ret.append(tmp); + } + qDeleteAll(*qin); + delete qin; + } + return ret; +} + +int QWSDisplay::windowAt(const QPoint &p) +{ + //### currently only implemented for the server process + int ret = 0; + if(d->directServerConnection()) { + QWSWindow *win = qwsServer->windowAt(p); + if (win) + return win->winId(); + } + return ret; +} + +void QWSDisplay::setRawMouseEventFilter(void (*filter)(QWSMouseEvent *)) +{ + if (qt_fbdpy) + qt_fbdpy->d->setMouseFilter(filter); +} + +/*! + \relates QScreen + + Here it is. \a transformation and \a screenNo + */ +void QWSDisplay::setTransformation(int transformation, int screenNo) +{ + QWSScreenTransformCommand cmd; + cmd.setTransformation(screenNo, transformation); + QWSDisplay::instance()->d->sendCommand(cmd); +} + +static bool qt_try_modal(QWidget *, QWSEvent *); + +/***************************************************************************** + qt_init() - initializes Qt/FB + *****************************************************************************/ + +static void qt_set_qws_resources() + +{ + if (QApplication::desktopSettingsAware()) + QApplicationPrivate::qws_apply_settings(); + + if (appFont) + QApplication::setFont(QFont(QString::fromLocal8Bit(appFont))); + + if (appBGCol || appBTNCol || appFGCol) { + (void) QApplication::style(); // trigger creation of application style and system palettes + QColor btn; + QColor bg; + QColor fg; + if (appBGCol) + bg = QColor(appBGCol); + else + bg = QApplicationPrivate::sys_pal->color(QPalette::Window); + if (appFGCol) + fg = QColor(appFGCol); + else + fg = QApplicationPrivate::sys_pal->color(QPalette::WindowText); + if (appBTNCol) + btn = QColor(appBTNCol); + else + btn = QApplicationPrivate::sys_pal->color(QPalette::Button); + + int h,s,v; + fg.getHsv(&h,&s,&v); + QColor base = Qt::white; + bool bright_mode = false; + if (v >= 255 - 50) { + base = btn.darker(150); + bright_mode = true; + } + + QPalette pal(fg, btn, btn.lighter(), btn.darker(), btn.darker(150), fg, Qt::white, base, bg); + if (bright_mode) { + pal.setColor(QPalette::HighlightedText, base); + pal.setColor(QPalette::Highlight, Qt::white); + } else { + pal.setColor(QPalette::HighlightedText, Qt::white); + pal.setColor(QPalette::Highlight, Qt::darkBlue); + } + QColor disabled((fg.red() + btn.red()) / 2, + (fg.green() + btn.green())/ 2, + (fg.blue() + btn.blue()) / 2); + pal.setColorGroup(QPalette::Disabled, disabled, btn, btn.lighter(125), + btn.darker(), btn.darker(150), disabled, Qt::white, Qt::white, bg); + if (bright_mode) { + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, base); + pal.setColor(QPalette::Disabled, QPalette::Highlight, Qt::white); + } else { + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, Qt::white); + pal.setColor(QPalette::Disabled, QPalette::Highlight, Qt::darkBlue); + } + QApplicationPrivate::setSystemPalette(pal); + + } +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ +} + +/*! \internal + apply the settings to the application +*/ +bool QApplicationPrivate::qws_apply_settings() +{ +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + QStringList strlist; + int i; + QPalette pal(Qt::black); + int groupCount = 0; + strlist = settings.value(QLatin1String("Palette/active")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + ++groupCount; + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Active, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/inactive")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + ++groupCount; + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Inactive, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/disabled")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + ++groupCount; + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Disabled, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + + + if (groupCount == QPalette::NColorGroups) + QApplicationPrivate::setSystemPalette(pal); + + QString str = settings.value(QLatin1String("font")).toString(); + if (!str.isEmpty()) { + QFont font(QApplication::font()); + font.fromString(str); + QApplicationPrivate::setSystemFont(font); + } + + // read library (ie. plugin) path list + QString libpathkey = + QString::fromLatin1("%1.%2/libraryPath") + .arg(QT_VERSION >> 16) + .arg((QT_VERSION & 0xff00) >> 8); + QStringList pathlist = settings.value(libpathkey).toString().split(QLatin1Char(':')); +#ifndef QT_NO_LIBRARY + if (! pathlist.isEmpty()) { + QStringList::ConstIterator it = pathlist.constBegin(); + while (it != pathlist.constEnd()) + QApplication::addLibraryPath(*it++); + } +#endif + + // read new QStyle + QString stylename = settings.value(QLatin1String("style")).toString(); + if (QCoreApplication::startingUp()) { + if (!stylename.isEmpty() && QApplicationPrivate::styleOverride.isNull()) + QApplicationPrivate::styleOverride = stylename; + } else { + QApplication::setStyle(stylename); + } + + int num = + settings.value(QLatin1String("doubleClickInterval"), + QApplication::doubleClickInterval()).toInt(); + QApplication::setDoubleClickInterval(num); + + num = + settings.value(QLatin1String("cursorFlashTime"), + QApplication::cursorFlashTime()).toInt(); + QApplication::setCursorFlashTime(num); + +#ifndef QT_NO_WHEELEVENT + num = + settings.value(QLatin1String("wheelScrollLines"), + QApplication::wheelScrollLines()).toInt(); + QApplication::setWheelScrollLines(num); +#endif + + QString colorspec = settings.value(QLatin1String("colorSpec"), + QVariant(QLatin1String("default"))).toString(); + if (colorspec == QLatin1String("normal")) + QApplication::setColorSpec(QApplication::NormalColor); + else if (colorspec == QLatin1String("custom")) + QApplication::setColorSpec(QApplication::CustomColor); + else if (colorspec == QLatin1String("many")) + QApplication::setColorSpec(QApplication::ManyColor); + else if (colorspec != QLatin1String("default")) + colorspec = QLatin1String("default"); + +#ifndef QT_NO_TEXTCODEC + QString defaultcodec = settings.value(QLatin1String("defaultCodec"), + QVariant(QLatin1String("none"))).toString(); + if (defaultcodec != QLatin1String("none")) { + QTextCodec *codec = QTextCodec::codecForName(defaultcodec.toLatin1()); + if (codec) + QTextCodec::setCodecForTr(codec); + } +#endif + + int w = settings.value(QLatin1String("globalStrut/width")).toInt(); + int h = settings.value(QLatin1String("globalStrut/height")).toInt(); + QSize strut(w, h); + if (strut.isValid()) + QApplication::setGlobalStrut(strut); + + QStringList effects = settings.value(QLatin1String("GUIEffects")).toStringList(); + QApplication::setEffectEnabled(Qt::UI_General, + effects.contains(QLatin1String("general"))); + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, + effects.contains(QLatin1String("animatemenu"))); + QApplication::setEffectEnabled(Qt::UI_FadeMenu, + effects.contains(QLatin1String("fademenu"))); + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, + effects.contains(QLatin1String("animatecombo"))); + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, + effects.contains(QLatin1String("animatetooltip"))); + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, + effects.contains(QLatin1String("fadetooltip"))); + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, + effects.contains(QLatin1String("animatetoolbox"))); + + settings.beginGroup(QLatin1String("Font Substitutions")); + QStringList fontsubs = settings.childKeys(); + if (!fontsubs.isEmpty()) { + QStringList::Iterator it = fontsubs.begin(); + for (; it != fontsubs.end(); ++it) { + QString fam = *it; + QStringList subs = settings.value(fam).toStringList(); + QFont::insertSubstitutions(fam, subs); + } + } + settings.endGroup(); + + settings.endGroup(); // Qt + + settings.beginGroup(QLatin1String("QWS Font Fallbacks")); + if (!settings.childKeys().isEmpty()) { + // from qfontdatabase_qws.cpp + extern void qt_applyFontDatabaseSettings(const QSettings &); + qt_applyFontDatabaseSettings(settings); + } + settings.endGroup(); + + return true; +#else + return false; +#endif // QT_NO_SETTINGS +} + + + +static void init_display() +{ + if (qt_fbdpy) return; // workaround server==client case + + // Connect to FB server + qt_fbdpy = new QWSDisplay(); + + // Get display parameters + // Set paintdevice parameters + // XXX initial info sent from server + // Misc. initialization + + QColormap::initialize(); + QFont::initialize(); +#ifndef QT_NO_CURSOR + QCursorData::initialize(); +#endif + + qApp->setObjectName(appName); + + if (!QApplicationPrivate::sys_font) { +#ifdef QT_NO_FREETYPE + QFont f = QFont(QLatin1String("helvetica"), 10); +#else + QFont f = QFont(QLatin1String("DejaVu Sans"), 12); +#endif + QApplicationPrivate::setSystemFont(f); + } + qt_set_qws_resources(); +} + +void qt_init_display() +{ + qt_is_gui_used = true; + qws_single_process = true; + init_display(); +} + +static bool read_bool_env_var(const char *var, bool defaultvalue) +{ + // returns true if env variable is set to non-zero + // returns false if env var is set to zero + // returns defaultvalue if env var not set + char *x = ::getenv(var); + return (x && *x) ? (strcmp(x,"0") != 0) : defaultvalue; +} + +static int read_int_env_var(const char *var, int defaultvalue) +{ + bool ok; + int r = qgetenv(var).toInt(&ok); + return ok ? r : defaultvalue; +} + +void qt_init(QApplicationPrivate *priv, int type) +{ +#ifdef QT_NO_QWS_MULTIPROCESS + if (type == QApplication::GuiClient) + type = QApplication::GuiServer; +#endif + if (type == QApplication::GuiServer) + qt_is_gui_used = false; //we'll turn it on in a second + qws_sw_cursor = read_bool_env_var("QWS_SW_CURSOR",qws_sw_cursor); + qws_screen_is_interlaced = read_bool_env_var("QWS_INTERLACE",false); + + const char *display = ::getenv("QWS_DISPLAY"); + if (display) + qws_display_spec = display; // since we setenv later! + + //qws_savefonts = qgetenv("QWS_SAVEFONTS") != 0; + //qws_shared_memory = qgetenv("QWS_NOSHARED") == 0; + + mouse_double_click_distance = read_int_env_var("QWS_DBLCLICK_DISTANCE", 5); + + priv->inputContext = 0; + + int flags = 0; + char *p; + int argc = priv->argc; + char **argv = priv->argv; + int j; + + // Set application name + + if (argv && *argv) { //apparently, we allow people to pass 0 on the other platforms + p = strrchr(argv[0], '/'); + appName = QString::fromLocal8Bit(p ? p + 1 : argv[0]); + } + + // Get command line params + + j = argc ? 1 : 0; + QString decoration; + for (int i=1; iargc) { + priv->argv[j] = 0; + priv->argc = j; + } + + mouseInWidget = new QPointer; + + const QString disp = QString::fromLatin1(qws_display_spec); + QRegExp regexp(QLatin1String(":(\\d+)$")); + if (regexp.lastIndexIn(disp) != -1) { + const QString capture = regexp.cap(1); + bool ok = false; + int id = capture.toInt(&ok); + if (ok) + qws_display_id = id; + } + + if (type == QApplication::GuiServer) { + qt_appType = QApplication::Type(type); + qws_single_process = true; + QWSServer::startup(flags); + if (!display) // if not already set + qputenv("QWS_DISPLAY", qws_display_spec); + } + + if(qt_is_gui_used) { + init_display(); +#ifndef QT_NO_QWS_MANAGER + if (decoration.isEmpty() && !qws_decoration) { + const QStringList keys = QDecorationFactory::keys(); + if (!keys.isEmpty()) + decoration = keys.first(); + } + if (!decoration.isEmpty()) + qws_decoration = QApplication::qwsSetDecoration(decoration); +#endif // QT_NO_QWS_MANAGER +#ifndef QT_NO_QWS_INPUTMETHODS + qApp->setInputContext(new QWSInputContext(qApp)); +#endif + } + +/*### convert interlace style + if (qws_screen_is_interlaced) + QApplication::setStyle(new QInterlaceStyle); +*/ +} + +/***************************************************************************** + qt_cleanup() - cleans up when the application is finished + *****************************************************************************/ + +void qt_cleanup() +{ + QPixmapCache::clear(); +#ifndef QT_NO_CURSOR + QCursorData::cleanup(); +#endif + QFont::cleanup(); + QColormap::cleanup(); + + if (qws_single_process) { + QWSServer::closedown(); + } + + qDeleteAll(outgoing); + outgoing.clear(); + qDeleteAll(incoming); + incoming.clear(); + + if (qt_is_gui_used) { + delete qt_fbdpy; + } + qt_fbdpy = 0; + +#ifndef QT_NO_QWS_MANAGER + delete qws_decoration; + qws_decoration = 0; +#endif + + delete mouseInWidget; + mouseInWidget = 0; + +#if !defined(QT_NO_IM) + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = 0; +#endif +} + + +/***************************************************************************** + Platform specific global and internal functions + *****************************************************************************/ + +QString QApplicationPrivate::appName() const // get application name +{ + return QT_PREPEND_NAMESPACE(appName); +} + +/***************************************************************************** + Platform specific QApplication members + *****************************************************************************/ + +#define NoValue 0x0000 +#define XValue 0x0001 +#define YValue 0x0002 +#define WidthValue 0x0004 +#define HeightValue 0x0008 +#define AllValues 0x000F +#define XNegative 0x0010 +#define YNegative 0x0020 + +/* Copyright notice for ReadInteger and parseGeometry + +Copyright (c) 1985, 1986, 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall +not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization +from the X Consortium. + +*/ +/* + * XParseGeometry parses strings of the form + * "=x{+-}{+-}", where + * width, height, xoffset, and yoffset are unsigned integers. + * Example: "=80x24+300-49" + * The equal sign is optional. + * It returns a bitmask that indicates which of the four values + * were actually found in the string. For each value found, + * the corresponding argument is updated; for each value + * not found, the corresponding argument is left unchanged. + */ + +static int +ReadInteger(char *string, char **NextString) +{ + register int Result = 0; + int Sign = 1; + + if (*string == '+') + string++; + else if (*string == '-') + { + string++; + Sign = -1; + } + for (; (*string >= '0') && (*string <= '9'); string++) + { + Result = (Result * 10) + (*string - '0'); + } + *NextString = string; + if (Sign >= 0) + return Result; + else + return -Result; +} + +static int parseGeometry(const char* string, + int* x, int* y, int* width, int* height) +{ + int mask = NoValue; + register char *strind; + unsigned int tempWidth=0, tempHeight=0; + int tempX=0, tempY=0; + char *nextCharacter; + + if (!string || (*string == '\0')) return mask; + if (*string == '=') + string++; /* ignore possible '=' at beg of geometry spec */ + + strind = const_cast(string); + if (*strind != '+' && *strind != '-' && *strind != 'x') { + tempWidth = ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + mask |= WidthValue; + } + + if (*strind == 'x' || *strind == 'X') { + strind++; + tempHeight = ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + mask |= HeightValue; + } + + if ((*strind == '+') || (*strind == '-')) { + if (*strind == '-') { + strind++; + tempX = -ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + mask |= XNegative; + + } + else + { strind++; + tempX = ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + } + mask |= XValue; + if ((*strind == '+') || (*strind == '-')) { + if (*strind == '-') { + strind++; + tempY = -ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + mask |= YNegative; + + } + else + { + strind++; + tempY = ReadInteger(strind, &nextCharacter); + if (strind == nextCharacter) + return 0; + strind = nextCharacter; + } + mask |= YValue; + } + } + + /* If strind isn't at the end of the string then it's an invalid + geometry specification. */ + + if (*strind != '\0') return 0; + + if (mask & XValue) + *x = tempX; + if (mask & YValue) + *y = tempY; + if (mask & WidthValue) + *width = tempWidth; + if (mask & HeightValue) + *height = tempHeight; + return mask; +} + +#ifdef QT3_SUPPORT +void QApplication::setMainWidget(QWidget *mainWidget) +{ + QApplicationPrivate::main_widget = mainWidget; + if (QApplicationPrivate::main_widget) // give WM command line + QApplicationPrivate::applyQWSSpecificCommandLineArguments(QApplicationPrivate::main_widget); +} +#endif + +void QApplicationPrivate::applyQWSSpecificCommandLineArguments(QWidget *main_widget) +{ + static bool beenHereDoneThat = false; + if (beenHereDoneThat) + return; + beenHereDoneThat = true; + if (qApp->windowIcon().isNull() && main_widget->testAttribute(Qt::WA_SetWindowIcon)) + qApp->setWindowIcon(main_widget->windowIcon()); + if (mwTitle) // && main_widget->windowTitle().isEmpty()) + main_widget->setWindowTitle(QString::fromLocal8Bit(mwTitle)); + if (mwGeometry) { // parse geometry + int x = 0; + int y = 0; + int w = 0; + int h = 0; + int m = parseGeometry(mwGeometry, &x, &y, &w, &h); + QSize minSize = main_widget->minimumSize(); + QSize maxSize = main_widget->maximumSize(); + if ((m & XValue) == 0) + x = main_widget->geometry().x(); + if ((m & YValue) == 0) + y = main_widget->geometry().y(); + if ((m & WidthValue) == 0) + w = main_widget->width(); + if ((m & HeightValue) == 0) + h = main_widget->height(); + w = qMin(w,maxSize.width()); + h = qMin(h,maxSize.height()); + w = qMax(w,minSize.width()); + h = qMax(h,minSize.height()); + if ((m & XNegative)) { + x = qApp->desktop()->width() + x - w; + x -= (main_widget->frameGeometry().width() - main_widget->width()) / 2; + } else { + x += (main_widget->geometry().x() - main_widget->x()); + } + if ((m & YNegative)) { + y = qApp->desktop()->height() + y - h; + } else { + y += (main_widget->geometry().y() - main_widget->y()); + } + + main_widget->setGeometry(x, y, w, h); + } +} + +/***************************************************************************** + QApplication cursor stack + *****************************************************************************/ +#ifndef QT_NO_CURSOR +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + + QWidget *w = QWidget::mouseGrabber(); + if (!w && qt_last_x) + w = topLevelAt(*qt_last_x, *qt_last_y); + if (!w) + w = desktop(); + QPaintDevice::qwsDisplay()->selectCursor(w, qApp->d_func()->cursor_list.first().handle()); +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + + QWidget *w = QWidget::mouseGrabber(); + if (!w && qt_last_x) + w = topLevelAt(*qt_last_x, *qt_last_y); + if (!w) + w = desktop(); + + int cursor_handle = Qt::ArrowCursor; + if (qApp->d_func()->cursor_list.isEmpty()) { + qws_overrideCursor = false; + QWidget *upw = QApplication::widgetAt(*qt_last_x, *qt_last_y); + if (upw) + cursor_handle = upw->cursor().handle(); + } else { + cursor_handle = qApp->d_func()->cursor_list.first().handle(); + } + QPaintDevice::qwsDisplay()->selectCursor(w, cursor_handle); +} +#endif// QT_NO_CURSOR + + + +/***************************************************************************** + Routines to find a Qt widget from a screen position + *****************************************************************************/ + +/*! + \internal +*/ +QWidget *QApplicationPrivate::findWidget(const QObjectList& list, + const QPoint &pos, bool rec) +{ + QWidget *w; + + for (int i = list.size()-1; i >= 0; --i) { + if (list.at(i)->isWidgetType()) { + w = static_cast(list.at(i)); + if (w->isVisible() && !w->testAttribute(Qt::WA_TransparentForMouseEvents) && w->geometry().contains(pos) + && (!w->d_func()->extra || w->d_func()->extra->mask.isEmpty() || w->d_func()->extra->mask.contains(pos - w->geometry().topLeft()) )) { + if (!rec) + return w; + QWidget *c = w->childAt(w->mapFromParent(pos)); + return c ? c : w; + } + } + } + return 0; +} + + +QWidget *QApplication::topLevelAt(const QPoint &pos) +{ + //### QWSDisplay::windowAt() is currently only implemented in the server process + int winId = QPaintDevice::qwsDisplay()->windowAt(pos); + if (winId !=0) + return QWidget::find(winId); + +#if 1 + // fallback implementation for client processes +//### This is slightly wrong: we have no guarantee that the list is in +//### stacking order, so if the topmost window is transparent, we may +//### return the wrong widget + + QWidgetList list = topLevelWidgets(); + for (int i = list.size()-1; i >= 0; --i) { + QWidget *w = list[i]; + if (w != QApplication::desktop() && + w->isVisible() && w->d_func()->localAllocatedRegion().contains(w->mapFromParent(pos)) + ) + return w; + } +#endif + return 0; +} + +void QApplication::beep() +{ +} + +void QApplication::alert(QWidget *, int) +{ +} + +int QApplication::qwsProcessEvent(QWSEvent* event) +{ + Q_D(QApplication); + QScopedLoopLevelCounter loopLevelCounter(d->threadData); + int oldstate = -1; + bool isMove = false; + if (event->type == QWSEvent::Mouse) { + QWSMouseEvent::SimpleData &mouse = event->asMouse()->simpleData; + isMove = mouse_x_root != mouse.x_root || mouse_y_root != mouse.y_root; + oldstate = mouse_state; + mouse_x_root = mouse.x_root; + mouse_y_root = mouse.y_root; + mouse_state = mouse.state; + } + + long unused; + if (filterEvent(event, &unused)) // send through app filter + return 1; + + if (qwsEventFilter(event)) // send through app filter + return 1; + + +#ifndef QT_NO_QWS_PROPERTIES + if (event->type == QWSEvent::PropertyNotify) { + QWSPropertyNotifyEvent *e = static_cast(event); + if (e->simpleData.property == 424242) { // Clipboard +#ifndef QT_NO_CLIPBOARD + if (qt_clipboard) { + QClipboardEvent e(reinterpret_cast(event)); + QApplication::sendEvent(qt_clipboard, &e); + } +#endif + } + } +#endif //QT_NO_QWS_PROPERTIES +#ifndef QT_NO_COP + else if (event->type == QWSEvent::QCopMessage) { + QWSQCopMessageEvent *e = static_cast(event); + QCopChannel::sendLocally(QLatin1String(e->channel), QLatin1String(e->message), e->data); + return 0; + } +#endif +#if !defined(QT_NO_QWS_QPF2) + else if (event->type == QWSEvent::Font) { + QWSFontEvent *e = static_cast(event); + if (e->simpleData.type == QWSFontEvent::FontRemoved) { + QFontCache::instance()->removeEngineForFont(e->fontName); + } + } +#endif + + QPointer widget = static_cast(QWidget::find(WId(event->window()))); +#ifdef Q_BACKINGSTORE_SUBSURFACES + if (!widget) { // XXX: hw: hack for accessing subsurfaces + extern QWSWindowSurface* qt_findWindowSurface(int); + QWSWindowSurface *s = qt_findWindowSurface(event->window()); + if (s) + widget = static_cast(s->window()); + } +#endif + +#ifndef QT_NO_DIRECTPAINTER + if (!widget && d->directPainters) { + QDirectPainter *dp = d->directPainters->value(WId(event->window())); + if (dp == 0) { + } else if (event->type == QWSEvent::Region) { + QWSRegionEvent *e = static_cast(event); + QRegion reg; + reg.setRects(e->rectangles, e->simpleData.nrectangles); + qt_directpainter_region(dp, reg, e->simpleData.type); + return 1; +#ifndef QT_NO_QWSEMBEDWIDGET + } else if (event->type == QWSEvent::Embed) { + QWSEmbedEvent *e = static_cast(event); + qt_directpainter_embedevent(dp, e); + return 1; + #endif // QT_NO_QWSEMBEDWIDGET + } + } +#endif // QT_NO_DIRECTPAINTER + +#ifndef QT_NO_QWS_MANAGER + if (d->last_manager && event->type == QWSEvent::Mouse) { + QPoint pos(event->asMouse()->simpleData.x_root, event->asMouse()->simpleData.y_root); + if (!d->last_manager->cachedRegion().contains(pos)) { + // MouseEvent not yet delivered, so QCursor::pos() is not yet updated, sending 2 x pos + QMouseEvent outside(QEvent::MouseMove, pos, pos, Qt::NoButton, 0, 0); + QApplication::sendSpontaneousEvent(d->last_manager, &outside); + d->last_manager = 0; + qt_last_cursor = 0xffffffff; //decoration is like another window; must redo cursor + } + } +#endif // QT_NO_QWS_MANAGER + + QETWidget *keywidget=0; + bool grabbed=false; + if (event->type==QWSEvent::Key || event->type == QWSEvent::IMEvent || event->type == QWSEvent::IMQuery) { + keywidget = static_cast(QWidget::keyboardGrabber()); + if (keywidget) { + grabbed = true; + } else { + if (QWidget *popup = QApplication::activePopupWidget()) { + if (popup->focusWidget()) + keywidget = static_cast(popup->focusWidget()); + else + keywidget = static_cast(popup); + } else if (QApplicationPrivate::focus_widget && QApplicationPrivate::focus_widget->isVisible()) + keywidget = static_cast(QApplicationPrivate::focus_widget); + else if (widget) + keywidget = static_cast(widget->window()); + } + } else if (event->type==QWSEvent::MaxWindowRect) { + QRect r = static_cast(event)->simpleData.rect; + setMaxWindowRect(r); + return 0; +#ifndef QT_NO_QWS_DYNAMICSCREENTRANSFORMATION + } else if (event->type == QWSEvent::ScreenTransformation) { + QWSScreenTransformationEvent *pe = static_cast(event); + setScreenTransformation(pe->simpleData.screen, + pe->simpleData.transformation); + return 0; +#endif + } else if (widget && event->type==QWSEvent::Mouse) { + // The mouse event is to one of my top-level widgets + // which one? + const int btnMask = Qt::LeftButton | Qt::RightButton | Qt::MidButton; + QPoint p(event->asMouse()->simpleData.x_root, + event->asMouse()->simpleData.y_root); + int mouseButtonState = event->asMouse()->simpleData.state & btnMask; + static int btnstate = 0; + + QETWidget *w = static_cast(QWidget::mouseGrabber()); + if (w && !mouseButtonState && qt_pressGrab == w) + qt_pressGrab = 0; +#ifndef QT_NO_QWS_MANAGER + if (!w) + w = static_cast(QWSManager::grabbedMouse()); +#endif + if (w) { + // Our mouse is grabbed - send it. + widget = w; + btnstate = mouseButtonState; + } else { + static QWidget *gw = 0; + // Three jobs to do here: + // 1. find the child widget this event belongs to. + // 2. make sure the cursor is correct. + // 3. handle implicit mouse grab due to button press. + w = widget; // w is the widget the cursor is in. + + //### ??? alloc_region + //#### why should we get events outside alloc_region ???? + if (1 /*widget->data->alloc_region.contains(dp) */) { + // Find the child widget that the cursor is in. + w = static_cast(widget->childAt(widget->mapFromParent(p))); + if (!w) + w = widget; +#ifndef QT_NO_CURSOR + // Update Cursor. + if (!gw || gw != w || qt_last_cursor == 0xffffffff) { + QCursor *curs = 0; + if (!qApp->d_func()->cursor_list.isEmpty()) + curs = &qApp->d_func()->cursor_list.first(); + else if (w->d_func()->extraData()) + curs = w->d_func()->extraData()->curs; + QWidget *pw = w; + // If this widget has no cursor set, try parent. + while (!curs) { + pw = pw->parentWidget(); + if (!pw) + break; + if (pw->d_func()->extraData()) + curs = pw->d_func()->extraData()->curs; + } + if (!qws_overrideCursor) { + if (curs) + QPaintDevice::qwsDisplay()->selectCursor(widget, curs->handle()); + else + QPaintDevice::qwsDisplay()->selectCursor(widget, Qt::ArrowCursor); + } + } +#endif + gw = w; + } else { + // This event is not for any of our widgets + gw = 0; + } + if (mouseButtonState && !btnstate) { + // The server has grabbed the mouse for us. + // Remember which of my widgets has it. + qt_pressGrab = w; + if (!widget->isActiveWindow() && + (!app_do_modal || QApplication::activeModalWidget() == widget) && + !((widget->windowFlags() & Qt::FramelessWindowHint) || (widget->windowType() == Qt::Tool))) { + widget->activateWindow(); + if (widget->raiseOnClick()) + widget->raise(); + } + } + btnstate = mouseButtonState; + widget = w; + } + } + + if (!widget) { // don't know this window + if (!QWidget::mouseGrabber() +#ifndef QT_NO_QWS_MANAGER + && !QWSManager::grabbedMouse() +#endif + ) { + qt_last_cursor = 0xffffffff; // cursor can be changed by another application + } + + QWidget* popup = QApplication::activePopupWidget(); + if (popup) { + + /* + That is more than suboptimal. The real solution should + do some keyevent and buttonevent translation, so that + the popup still continues to work as the user expects. + Unfortunately this translation is currently only + possible with a known widget. I'll change that soon + (Matthias). + */ + + // Danger - make sure we don't lock the server + switch (event->type) { + case QWSEvent::Mouse: + case QWSEvent::Key: + do { + popup->close(); + } while ((popup = qApp->activePopupWidget())); + return 1; + } + } + if (event->type == QWSEvent::Mouse && *mouseInWidget) { + QApplicationPrivate::dispatchEnterLeave(0, *mouseInWidget); + (*mouseInWidget) = 0; + } + return -1; + } + + if (app_do_modal) // modal event handling + if (!qt_try_modal(widget, event)) { + return 1; + } + + if (widget->qwsEvent(event)) // send through widget filter + return 1; + switch (event->type) { + + case QWSEvent::Mouse: { // mouse event + QWSMouseEvent *me = event->asMouse(); + QWSMouseEvent::SimpleData &mouse = me->simpleData; + + // Translate a QWS event into separate move + // and press/release events + // Beware of reentrancy: we can enter a modal state + // inside translateMouseEvent + + if (isMove) { + QWSMouseEvent move = *me; + move.simpleData.state = oldstate; + widget->translateMouseEvent(&move, oldstate); + } + if ((mouse.state&Qt::MouseButtonMask) != (oldstate&Qt::MouseButtonMask)) { + widget->translateMouseEvent(me, oldstate); + } + + if (mouse.delta != 0) + widget->translateWheelEvent(me); + + if (qt_button_down && (mouse_state & Qt::MouseButtonMask) == 0) + qt_button_down = 0; + + break; + } + case QWSEvent::Key: // keyboard event + if (keywidget) // should always exist + keywidget->translateKeyEvent(static_cast(event), grabbed); + break; + +#ifndef QT_NO_QWS_INPUTMETHODS + case QWSEvent::IMEvent: + if (keywidget) // should always exist + QWSInputContext::translateIMEvent(keywidget, static_cast(event)); + break; + + case QWSEvent::IMQuery: + if (keywidget) // should always exist + QWSInputContext::translateIMQueryEvent(keywidget, static_cast(event)); + break; + + case QWSEvent::IMInit: + QWSInputContext::translateIMInitEvent(static_cast(event)); + break; +#endif + case QWSEvent::Region: + widget->translateRegionEvent(static_cast(event)); + break; + case QWSEvent::Focus: + if ((static_cast(event))->simpleData.get_focus) { + if (widget == static_cast(desktop())) + return true; // not interesting + if (activeWindow() != widget) { + setActiveWindow(widget); + if (QApplicationPrivate::active_window) + static_cast(QApplicationPrivate::active_window)->repaintDecoration(desktop()->rect(), false); + if (widget && !d->inPopupMode()) { + QWidget *w = widget->focusWidget(); + while (w && w->focusProxy()) + w = w->focusProxy(); + if (w && (w->focusPolicy() != Qt::NoFocus)) + w->setFocus(); + else + widget->QWidget::focusNextPrevChild(true); + if (!QApplicationPrivate::focus_widget) { + if (widget->focusWidget()) + widget->focusWidget()->setFocus(); + else + widget->window()->setFocus(); + } + } + } + } else { // lost focus + if (widget == static_cast(desktop())) + return true; // not interesting + if (QApplicationPrivate::focus_widget) { + QETWidget *old = static_cast(QApplicationPrivate::active_window); + setActiveWindow(0); + qt_last_cursor = 0xffffffff; + //QApplicationPrivate::active_window = 0; + if (old) + old->repaintDecoration(desktop()->rect(), false); + /* activateWindow() sends focus events + QApplication::setFocusWidget(0); + */ + } + } + break; + + case QWSEvent::WindowOperation: + if (static_cast(widget) == desktop()) + return true; + switch ((static_cast(event))->simpleData.op) { + case QWSWindowOperationEvent::Show: + widget->show(); + break; + case QWSWindowOperationEvent::Hide: + widget->hide(); + break; + case QWSWindowOperationEvent::ShowMaximized: + widget->showMaximized(); + break; + case QWSWindowOperationEvent::ShowMinimized: + widget->showMinimized(); + break; + case QWSWindowOperationEvent::ShowNormal: + widget->showNormal(); + break; + case QWSWindowOperationEvent::Close: + widget->d_func()->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); + break; + } + break; +#ifndef QT_NO_QWSEMBEDWIDGET + case QWSEvent::Embed: + widget->translateEmbedEvent(static_cast(event)); + break; +#endif + default: + break; + } + + return 0; +} + +bool QApplication::qwsEventFilter(QWSEvent *) +{ + return false; +} + +void QApplication::qwsSetCustomColors(QRgb *colorTable, int start, int numColors) +{ + if (start < 0 || start > 39) { + qWarning("QApplication::qwsSetCustomColors: start < 0 || start > 39"); + return; + } + if (start + numColors > 40) { + numColors = 40 - start; + qWarning("QApplication::qwsSetCustomColors: Too many colors"); + } + start += 216; + for (int i = 0; i < numColors; i++) { + qt_screen->set(start + i, qRed(colorTable[i]), qGreen(colorTable[i]), + qBlue(colorTable[i])); + } +} + +#ifndef QT_NO_QWS_MANAGER +QDecoration &QApplication::qwsDecoration() +{ + return *qws_decoration; +} + +void QApplication::qwsSetDecoration(QDecoration *dec) +{ + if (dec) { + delete qws_decoration; + qws_decoration = dec; + QWidgetList widgets = topLevelWidgets(); + for (int i = 0; i < widgets.size(); ++i) { + QWidget *w = widgets[i]; + if (w->isVisible() && w != desktop()) { + static_cast(w)->updateRegion(); + static_cast(w)->repaintDecoration(desktop()->rect(), false); + if (w->isMaximized()) + w->showMaximized(); + } + } + } +} + +QDecoration* QApplication::qwsSetDecoration(const QString &decoration) +{ + QDecoration *decore = QDecorationFactory::create(decoration); + if (!decore) + return 0; + + qwsSetDecoration(decore); + return decore; +} + +#endif + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + qt_modal_stack->insert(0, widget); + app_do_modal = true; +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget) +{ + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + } + } + app_do_modal = qt_modal_stack != 0; +} + +static bool qt_try_modal(QWidget *widget, QWSEvent *event) +{ + QWidget * top = 0; + + if (QApplicationPrivate::tryModalHelper(widget, &top)) + return true; + + bool block_event = false; + bool paint_event = false; + + switch (event->type) { + case QWSEvent::Focus: + if (!static_cast(event)->simpleData.get_focus) + break; + // drop through + case QWSEvent::Mouse: // disallow mouse/key events + case QWSEvent::Key: + block_event = true; + break; + } + + if (top->parentWidget() == 0 && (block_event || paint_event)) + top->raise(); + + return !block_event; +} + +static int openPopupCount = 0; +void QApplicationPrivate::openPopup(QWidget *popup) +{ + openPopupCount++; + if (!popupWidgets) { // create list + popupWidgets = new QWidgetList; + + /* only grab if you are the first/parent popup */ + QPaintDevice::qwsDisplay()->grabMouse(popup,true); + QPaintDevice::qwsDisplay()->grabKeyboard(popup,true); + popupGrabOk = true; + } + popupWidgets->append(popup); // add to end of list + + // popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + if (popup->focusWidget()) { + popup->focusWidget()->setFocus(Qt::PopupFocusReason); + } else if (popupWidgets->count() == 1) { // this was the first popup + if (QWidget *fw = QApplication::focusWidget()) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + QApplication::sendEvent(fw, &e); + } + } +} + +void QApplicationPrivate::closePopup(QWidget *popup) +{ + if (!popupWidgets) + return; + + popupWidgets->removeAll(popup); + if (popup == popupOfPopupButtonFocus) { + popupButtonFocus = 0; + popupOfPopupButtonFocus = 0; + } + if (popupWidgets->count() == 0) { // this was the last popup + popupCloseDownMode = true; // control mouse events + delete popupWidgets; + popupWidgets = 0; + if (popupGrabOk) { // grabbing not disabled + QPaintDevice::qwsDisplay()->grabMouse(popup,false); + QPaintDevice::qwsDisplay()->grabKeyboard(popup,false); + popupGrabOk = false; + // XXX ungrab keyboard + } + if (active_window) { + if (QWidget *fw = active_window->focusWidget()) { + if (fw != QApplication::focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + QApplication::sendEvent(fw, &e); + } + } + } + } else { + // popups are not focus-handled by the window system (the + // first popup grabbed the keyboard), so we have to do that + // manually: A popup was closed, so the previous popup gets + // the focus. + QWidget* aw = popupWidgets->last(); + if (QWidget *fw = aw->focusWidget()) + fw->setFocus(Qt::PopupFocusReason); + } +} + +/***************************************************************************** + Event translation; translates FB events to Qt events + *****************************************************************************/ + +// +// Mouse event translation +// +// FB doesn't give mouse double click events, so we generate them by +// comparing window, time and position between two mouse press events. +// + + +// Needed for QCursor::pos + +static const int AnyButton = (Qt::LeftButton | Qt::MidButton | Qt::RightButton); + + + +// +// Wheel event translation +// +bool QETWidget::translateWheelEvent(const QWSMouseEvent *me) +{ +#ifdef QT_NO_WHEELEVENT + Q_UNUSED(me); + return false; +#else + const QWSMouseEvent::SimpleData &mouse = me->simpleData; + + // Figure out wheeling direction: + // Horizontal wheel w/o Alt + // OR Vertical wheel w/ Alt ==> Horizontal wheeling + // ..all other permutations ==> Vertical wheeling + int axis = mouse.delta / 120; // WHEEL_DELTA? + Qt::Orientation orient = ((axis == 2 || axis == -2) && ((mouse.state & Qt::AltModifier) == 0)) + ||((axis == 1 || axis == -1) && mouse.state & Qt::AltModifier) + ? Qt::Horizontal : Qt::Vertical; + + QPoint mousePoint = QPoint(mouse.x_root, mouse.y_root); + + // send the event to the widget or its ancestors + QWidget* popup = qApp->activePopupWidget(); + if (popup && window() != popup) + popup->close(); + QWheelEvent we(mapFromGlobal(mousePoint), mousePoint, mouse.delta, + Qt::MouseButtons(mouse.state & Qt::MouseButtonMask), + Qt::KeyboardModifiers(mouse.state & Qt::KeyboardModifierMask), orient); + if (QApplication::sendSpontaneousEvent(this, &we)) + return true; + + // send the event to the widget that has the focus or its ancestors, if different + QWidget *w = this; + if (w != qApp->focusWidget() && (w = qApp->focusWidget())) { + QWidget* popup = qApp->activePopupWidget(); + if (popup && w != popup) + popup->hide(); + if (QApplication::sendSpontaneousEvent(w, &we)) + return true; + } + return false; +#endif +} + +bool QETWidget::translateMouseEvent(const QWSMouseEvent *event, int prevstate) +{ + static bool manualGrab = false; + QPoint pos; + QPoint globalPos; + int button = 0; + + if (sm_blockUserInput) // block user interaction during session management + return true; + const QWSMouseEvent::SimpleData &mouse = event->simpleData; + pos = mapFromGlobal(QPoint(mouse.x_root, mouse.y_root)); +// if (qt_last_x) { +// *qt_last_x=mouse.x_root; +// *qt_last_y=mouse.y_root; +// } + globalPos.rx() = mouse.x_root; + globalPos.ry() = mouse.y_root; + + QEvent::Type type = QEvent::None; + + Qt::MouseButtons buttonstate = Qt::MouseButtons(mouse.state & Qt::MouseButtonMask); + Qt::KeyboardModifiers keystate = Qt::KeyboardModifiers(mouse.state & Qt::KeyboardModifierMask); + + if (mouse.state == prevstate) { + // mouse move + type = QEvent::MouseMove; + } else if ((mouse.state&AnyButton) != (prevstate&AnyButton)) { + Qt::MouseButtons current_buttons = Qt::MouseButtons(prevstate&Qt::MouseButtonMask); + for (button = Qt::LeftButton; !type && button <= Qt::MidButton; button<<=1) { + if ((mouse.state&button) != (current_buttons&button)) { + // button press or release + current_buttons = Qt::MouseButtons(current_buttons ^ button); + +#ifndef QT_NO_QWS_INPUTMETHODS + //############ We used to do a QInputContext::reset(oldFocus); + // when we changed the focus widget. See change 93389 for where the + // focus code went. The IM code was (after testing for ClickToFocus): + //if (mouse.state&button && w != QInputContext::microFocusWidget()) //button press + // QInputContext::reset(oldFocus); + +#endif + if (mouse.state&button) { //button press + qt_button_down = childAt(pos); + if (!qt_button_down) + qt_button_down = this; + if (/*XXX mouseActWindow == this &&*/ + mouseButtonPressed == button && + long(mouse.time) -long(mouseButtonPressTime) + < QApplication::doubleClickInterval() && + qAbs(mouse.x_root - mouseXPos) < mouse_double_click_distance && + qAbs(mouse.y_root - mouseYPos) < mouse_double_click_distance ) { + type = QEvent::MouseButtonDblClick; + mouseButtonPressTime -= 2000; // no double-click next time + } else { + type = QEvent::MouseButtonPress; + mouseButtonPressTime = mouse.time; + } + mouseButtonPressed = button; // save event params for + mouseXPos = globalPos.x(); // future double click tests + mouseYPos = globalPos.y(); + } else { // mouse button released + if (manualGrab) { // release manual grab + manualGrab = false; + // XXX XUngrabPointer(x11Display(), CurrentTime); + } + + type = QEvent::MouseButtonRelease; + } + } + } + button >>= 1; + } + //XXX mouseActWindow = winId(); // save some event params + + if (type == 0) { // event consumed + return false; //EXIT in the normal case + } + + if (qApp->d_func()->inPopupMode()) { // in popup mode + QWidget *popup = qApp->activePopupWidget(); + // in X11, this would be the window we are over. + // in QWS this is the top level popup. to allow mouse + // events to other widgets, need to go through qApp->QApplicationPrivate::popupWidgets. + QSize s(qt_screen->width(), qt_screen->height()); + for (int i = 0; i < QApplicationPrivate::popupWidgets->size(); ++i) { + QWidget *w = QApplicationPrivate::popupWidgets->at(i); + + if ((w->windowType() == Qt::Popup) && w->d_func()->localAllocatedRegion().contains(globalPos - w->geometry().topLeft())) + { + popup = w; + break; + } + } + pos = popup->mapFromGlobal(globalPos); + bool releaseAfter = false; + QWidget *popupChild = popup->childAt(pos); + QWidget *popupTarget = popupChild ? popupChild : popup; + + if (popup != popupOfPopupButtonFocus){ + popupButtonFocus = 0; + popupOfPopupButtonFocus = 0; + } + + if (!popupTarget->isEnabled()) { + return false; //EXIT special case + } + + switch (type) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + popupButtonFocus = popupChild; + popupOfPopupButtonFocus = popup; + break; + case QEvent::MouseButtonRelease: + releaseAfter = true; + break; + default: + break; // nothing for mouse move + } + + int oldOpenPopupCount = openPopupCount; + + if (popupButtonFocus) { + QMouseEvent e(type, popupButtonFocus->mapFromGlobal(globalPos), + globalPos, Qt::MouseButton(button), buttonstate, keystate); + QApplication::sendSpontaneousEvent(popupButtonFocus, & e); + if (releaseAfter) { + popupButtonFocus = 0; + popupOfPopupButtonFocus = 0; + } + } else if (popupChild) { + QMouseEvent e(type, popupChild->mapFromGlobal(globalPos), + globalPos, Qt::MouseButton(button), buttonstate, keystate); + QApplication::sendSpontaneousEvent(popupChild, & e); + } else { + QMouseEvent e(type, pos, globalPos, Qt::MouseButton(button), buttonstate, keystate); + QApplication::sendSpontaneousEvent(popupChild ? popupChild : popup, & e); + } +#ifndef QT_NO_CONTEXTMENU + if (type == QEvent::MouseButtonPress && button == Qt::RightButton && (openPopupCount == oldOpenPopupCount)) { + QWidget *popupEvent = popup; + if(popupButtonFocus) + popupEvent = popupButtonFocus; + else if(popupChild) + popupEvent = popupChild; + QContextMenuEvent e(QContextMenuEvent::Mouse, pos, globalPos, keystate); + QApplication::sendSpontaneousEvent(popupEvent, &e); + } +#endif // QT_NO_CONTEXTMENU + + if (releaseAfter) + qt_button_down = 0; + + } else { //qApp not in popup mode + QWidget *widget = this; + QWidget *w = QWidget::mouseGrabber(); + if (!w && qt_button_down) + w = qt_button_down; + if (w && w != this) { + widget = w; + pos = mapToGlobal(pos); + pos = w->mapFromGlobal(pos); + } + + if (popupCloseDownMode) { + popupCloseDownMode = false; + if ((windowType() == Qt::Popup)) // ignore replayed event + return true; //EXIT + } + + QPointer leaveAfterRelease = 0; + if (type == QEvent::MouseButtonRelease && + (mouse.state & (~button) & (Qt::LeftButton | + Qt::MidButton | + Qt::RightButton)) == 0) { + // Button released outside the widget -> leave the widget after the + // release event has been delivered. + if (widget == qt_button_down && (pos.x() < 0 || pos.y() < 0)) + leaveAfterRelease = qt_button_down; + qt_button_down = 0; + } + + int oldOpenPopupCount = openPopupCount; + + QMouseEvent e(type, pos, globalPos, Qt::MouseButton(button), buttonstate, keystate); +#ifndef QT_NO_QWS_MANAGER + if (widget->isWindow() && widget->d_func()->topData()->qwsManager + && (widget->d_func()->topData()->qwsManager->region().contains(globalPos) + || QWSManager::grabbedMouse() )) { + if ((*mouseInWidget)) { + QApplicationPrivate::dispatchEnterLeave(0, *mouseInWidget); + (*mouseInWidget) = 0; + } + QApplication::sendSpontaneousEvent(widget->d_func()->topData()->qwsManager, &e); + qApp->d_func()->last_manager = widget->d_func()->topData()->qwsManager; + } else +#endif + { + if (widget != (*mouseInWidget)) { + QApplicationPrivate::dispatchEnterLeave(widget, *mouseInWidget); + (*mouseInWidget) = widget; + qt_last_mouse_receiver = widget; + } + QApplication::sendSpontaneousEvent(widget, &e); + if (leaveAfterRelease && !QWidget::mouseGrabber()) { + *mouseInWidget = QApplication::widgetAt(globalPos); + qt_last_mouse_receiver = *mouseInWidget; + QApplicationPrivate::dispatchEnterLeave(*mouseInWidget, leaveAfterRelease); + leaveAfterRelease = 0; + } + } +#ifndef QT_NO_CONTEXTMENU + if (type == QEvent::MouseButtonPress && button == Qt::RightButton && (openPopupCount == oldOpenPopupCount)) { + QContextMenuEvent e(QContextMenuEvent::Mouse, pos, globalPos, keystate); + QApplication::sendSpontaneousEvent(widget, &e); + } +#endif // QT_NO_CONTEXTMENU + } + return true; +} + + +bool QETWidget::translateKeyEvent(const QWSKeyEvent *event, bool grab) /* grab is used in the #ifdef */ +{ + int code = -1; + //### Qt assumes keyboard state is state *before*, while QWS uses state after the event + static Qt::KeyboardModifiers oldstate; + Qt::KeyboardModifiers state = oldstate; + oldstate = event->simpleData.modifiers; + + if (sm_blockUserInput) // block user interaction during session management + return true; + + if (!isEnabled()) + return true; + + QEvent::Type type = event->simpleData.is_press ? + QEvent::KeyPress : QEvent::KeyRelease; + bool autor = event->simpleData.is_auto_repeat; + QString text; + char ascii = 0; + if (event->simpleData.unicode) { + QChar ch(event->simpleData.unicode); + if (ch.unicode() != 0xffff) + text += ch; + ascii = ch.toLatin1(); + } + code = event->simpleData.keycode; + +#if defined QT3_SUPPORT && !defined(QT_NO_SHORTCUT) + if (type == QEvent::KeyPress && !grab + && static_cast(qApp->d_ptr.data())->use_compat()) { + // send accel events if the keyboard is not grabbed + QKeyEvent a(type, code, state, text, autor, int(text.length())); + if (static_cast(qApp->d_ptr.data())->qt_tryAccelEvent(this, &a)) + return true; + } +#else + Q_UNUSED(grab); +#endif + if (!text.isEmpty() && testAttribute(Qt::WA_KeyCompression)) { + // the widget wants key compression so it gets it + + // XXX not implemented + } + + QKeyEvent e(type, code, state, text, autor, int(text.length())); + return QApplication::sendSpontaneousEvent(this, &e); +} + +bool QETWidget::translateRegionEvent(const QWSRegionEvent *event) +{ + QWSWindowSurface *surface = static_cast(windowSurface()); + Q_ASSERT(surface); + + QRegion region; + region.setRects(event->rectangles, event->simpleData.nrectangles); + + switch (event->simpleData.type) { + case QWSRegionEvent::Allocation: + region.translate(-mapToGlobal(QPoint())); + surface->setClipRegion(region); + break; +#ifdef QT_QWS_CLIENTBLIT + case QWSRegionEvent::DirectPaint: + surface->setDirectRegion(region, event->simpleData.id); + break; +#endif + default: + break; + } + + return true; +} + +#ifndef QT_NO_QWSEMBEDWIDGET +void QETWidget::translateEmbedEvent(const QWSEmbedEvent *event) +{ + if (event->simpleData.type | QWSEmbedEvent::Region) { + const QRegion region = event->region; + setGeometry(region.boundingRect()); + setVisible(!region.isEmpty()); + } +} +#endif // QT_NO_QWSEMBEDWIDGET + +void QETWidget::repaintDecoration(QRegion r, bool post) +{ + Q_UNUSED(post); +#ifdef QT_NO_QWS_MANAGER + Q_UNUSED(r); +#else + //please note that qwsManager is a QObject, not a QWidget. + //therefore, normal ways of painting do not work. + // However, it does listen to paint events. + + Q_D(QWidget); + if (isWindow() && d->topData()->qwsManager && isVisible()) { + QWSManager *manager = d->topData()->qwsManager; + r &= manager->region(); + if (!r.isEmpty()) + manager->repaintRegion(QDecoration::All, QDecoration::Normal); + } +#endif +} + +void QETWidget::updateRegion() +{ + Q_D(QWidget); + + QTLWExtra *topextra = d->maybeTopData(); + if (!topextra) + return; + + QRegion myregion = d->localRequestedRegion(); + myregion.translate(geometry().topLeft()); + +#ifndef QT_NO_QWS_MANAGER + QWSManager *manager = topextra->qwsManager; + if (manager) + myregion += manager->region(); +#endif + + QRect br(myregion.boundingRect()); + topextra->frameStrut.setCoords(d->data.crect.x() - br.x(), + d->data.crect.y() - br.y(), + br.right() - d->data.crect.right(), + br.bottom() - d->data.crect.bottom()); +} + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setDoubleClickInterval(int ms) +{ + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + return QApplicationPrivate::mouse_double_click_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int lines) +{ + QApplicationPrivate::wheel_scroll_lines = lines; +} + +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} +#endif + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + switch (effect) { + case Qt::UI_AnimateMenu: + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeMenu: + if (enable) + QApplicationPrivate::animate_menu = true; + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateTooltip: + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_FadeTooltip: + if (enable) + QApplicationPrivate::animate_tooltip = true; + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16 || !QApplicationPrivate::animate_ui) + return false; + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + return QApplicationPrivate::animate_ui; + } +} + +void QApplication::setArgs(int c, char **v) +{ + Q_D(QApplication); + d->argc = c; + d->argv = v; +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ } +void QApplicationPrivate::cleanupMultitouch_sys() +{ } + +/* \internal + This is used to clean up the qws server + in case the QApplication constructor threw an exception +*/ +QWSServerCleaner::~QWSServerCleaner() +{ + if (qwsServer && qws_single_process) + QWSServer::closedown(); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qapplication_s60.cpp b/src/gui/kernel/qapplication_s60.cpp new file mode 100644 index 0000000000..408c3b5883 --- /dev/null +++ b/src/gui/kernel/qapplication_s60.cpp @@ -0,0 +1,2712 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication_p.h" +#include "qsessionmanager.h" +#include "qevent.h" +#include "qsymbianevent.h" +#include "qeventdispatcher_s60_p.h" +#include "qwidget.h" +#include "qdesktopwidget.h" +#include "private/qbackingstore_p.h" +#include "qt_s60_p.h" +#include "private/qevent_p.h" +#include "qstring.h" +#include "qdebug.h" +#include "qimage.h" +#include "qcombobox.h" +#include "private/qkeymapper_p.h" +#include "private/qfont_p.h" +#ifndef QT_NO_STYLE_S60 +#include "private/qs60style_p.h" +#endif +#include "private/qwindowsurface_s60_p.h" +#include "qpaintengine.h" +#include "private/qmenubar_p.h" +#include "private/qsoftkeymanager_p.h" +#ifdef QT_GRAPHICSSYSTEM_RUNTIME +#include "private/qgraphicssystem_runtime_p.h" +#endif + +#include "apgwgnam.h" // For CApaWindowGroupName +#include // For CMdaAudioToneUtility + +#if defined(Q_OS_SYMBIAN) +# include +# include +# include "qs60mainappui.h" +# include "qinputcontext.h" +#endif + +#if defined(Q_WS_S60) +# if !defined(QT_NO_IM) +# include +# endif +#endif + +#include "private/qstylesheetstyle_p.h" + +#include +#include + +#ifdef Q_SYMBIAN_TRANSITION_EFFECTS +#include +#endif + +QT_BEGIN_NAMESPACE + +// Goom Events through Window Server +static const int KGoomMemoryLowEvent = 0x10282DBF; +static const int KGoomMemoryGoodEvent = 0x20026790; +// Split view open/close events from AVKON +static const int KSplitViewOpenEvent = 0x2001E2C0; +static const int KSplitViewCloseEvent = 0x2001E2C1; + +#if defined(QT_DEBUG) +static bool appNoGrab = false; // Grabbing enabled +#endif +static bool app_do_modal = false; // modal mode +Q_GLOBAL_STATIC(QS60Data, qt_s60Data); + +extern bool qt_sendSpontaneousEvent(QObject*,QEvent*); +extern QWidgetList *qt_modal_stack; // stack of modal widgets +extern QDesktopWidget *qt_desktopWidget; // qapplication.cpp + +QWidget *qt_button_down = 0; // widget got last button-down + +QSymbianControl *QSymbianControl::lastFocusedControl = 0; + +QS60Data* qGlobalS60Data() +{ + return qt_s60Data(); +} + +#ifdef Q_WS_S60 +void QS60Data::setStatusPaneAndButtonGroupVisibility(bool statusPaneVisible, bool buttonGroupVisible) +{ + bool buttonGroupVisibilityChanged = false; + if (CEikButtonGroupContainer *const b = buttonGroupContainer()) { + buttonGroupVisibilityChanged = (b->IsVisible() != buttonGroupVisible); + b->MakeVisible(buttonGroupVisible); + } + bool statusPaneVisibilityChanged = false; + if (CEikStatusPane *const s = statusPane()) { + statusPaneVisibilityChanged = (s->IsVisible() != statusPaneVisible); + s->MakeVisible(statusPaneVisible); + } + if (buttonGroupVisibilityChanged || statusPaneVisibilityChanged) { + const QSize size = qt_TRect2QRect(static_cast(S60->appUi())->ClientRect()).size(); + const QSize oldSize; // note that QDesktopWidget::resizeEvent ignores the QResizeEvent contents + QResizeEvent event(size, oldSize); + QApplication::instance()->sendEvent(QApplication::desktop(), &event); + } + if (buttonGroupVisibilityChanged && !statusPaneVisibilityChanged && QApplication::activeWindow()) + // Ensure that control rectangle is updated + static_cast(QApplication::activeWindow()->winId())->handleClientAreaChange(); +} + +bool QS60Data::setRecursiveDecorationsVisibility(QWidget *window, Qt::WindowStates newState) +{ + // Show statusbar: + // Topmost parent: Show unless fullscreen/minimized. + // Child windows: Follow topmost parent, unless fullscreen, in which case do not show statusbar + // Show CBA: + // Topmost parent: Show unless fullscreen/minimized. + // Exception: Show if fullscreen with Qt::WindowSoftkeysVisibleHint. + // Child windows: + // Minimized: Unclear if there is an use case for having focused minimized window at all. + // Always follow topmost parent just to be safe. + // Maximized and normal: follow topmost parent. + // Exception: If topmost parent is not showing CBA, show CBA if any softkey actions are + // defined. + // Fullscreen: Show only if Qt::WindowSoftkeysVisibleHint set. + + Qt::WindowStates comparisonState = newState; + QWidget *parentWindow = window->parentWidget(); + if (parentWindow) { + while (parentWindow->parentWidget()) + parentWindow = parentWindow->parentWidget(); + comparisonState = parentWindow->windowState(); + } else { + parentWindow = window; + } + + bool decorationsVisible = !(comparisonState & (Qt::WindowFullScreen | Qt::WindowMinimized)); + const bool parentIsFullscreen = comparisonState & Qt::WindowFullScreen; + const bool parentCbaVisibilityHint = parentWindow->windowFlags() & Qt::WindowSoftkeysVisibleHint; + bool buttonGroupVisibility = (decorationsVisible || (parentIsFullscreen && parentCbaVisibilityHint)); + + // Do extra checking for child windows + if (window->parentWidget()) { + if (newState & Qt::WindowFullScreen) { + decorationsVisible = false; + if (window->windowFlags() & Qt::WindowSoftkeysVisibleHint) + buttonGroupVisibility = true; + else + buttonGroupVisibility = false; + } else if (!(newState & Qt::WindowMinimized) && !buttonGroupVisibility) { + for (int i = 0; i < window->actions().size(); ++i) { + if (window->actions().at(i)->softKeyRole() != QAction::NoSoftKey) { + buttonGroupVisibility = true; + break; + } + } + } + } + + S60->setStatusPaneAndButtonGroupVisibility(decorationsVisible, buttonGroupVisibility); + + return decorationsVisible; +} +#endif + +void QS60Data::controlVisibilityChanged(CCoeControl *control, bool visible) +{ + if (QWidgetPrivate::mapper && QWidgetPrivate::mapper->contains(control)) { + QWidget *const widget = QWidgetPrivate::mapper->value(control); + QWidget *const window = widget->window(); + if (QTLWExtra *topData = qt_widget_private(window)->maybeTopData()) { + QWidgetBackingStoreTracker &backingStore = topData->backingStore; + if (visible) { + if (backingStore.data()) { + backingStore.registerWidget(widget); + } else { + backingStore.create(window); + backingStore.registerWidget(widget); + qt_widget_private(widget)->invalidateBuffer(widget->rect()); + widget->repaint(); + } + } else { + // In certain special scenarios we may get an ENotVisible event + // without a previous EPartiallyVisible. The backingstore must + // still be destroyed, hence the registerWidget() call below. + if (backingStore.data() && widget->internalWinId() + && qt_widget_private(widget)->maybeBackingStore() == backingStore.data()) + backingStore.registerWidget(widget); + backingStore.unregisterWidget(widget); + // In order to ensure that any resources used by the window surface + // are immediately freed, we flush the WSERV command buffer. + S60->wsSession().Flush(); + } + } + } +} + +bool qt_nograb() // application no-grab option +{ +#if defined(QT_DEBUG) + return appNoGrab; +#else + return false; +#endif +} + +// Modified from http://www3.symbian.com/faq.nsf/0/0F1464EE96E737E780256D5E00503DD1?OpenDocument +class QS60Beep : public CBase, public MMdaAudioToneObserver +{ +public: + static QS60Beep* NewL(TInt aFrequency, TTimeIntervalMicroSeconds iDuration); + void Play(); + ~QS60Beep(); +private: + void ConstructL(TInt aFrequency, TTimeIntervalMicroSeconds iDuration); + void MatoPrepareComplete(TInt aError); + void MatoPlayComplete(TInt aError); +private: + typedef enum + { + EBeepNotPrepared, + EBeepPrepared, + EBeepPlaying + } TBeepState; +private: + CMdaAudioToneUtility* iToneUtil; + TBeepState iState; + TInt iFrequency; + TTimeIntervalMicroSeconds iDuration; +}; + +static QS60Beep* qt_S60Beep = 0; + +QS60Beep::~QS60Beep() +{ + if (iToneUtil) { + switch (iState) { + case EBeepPlaying: + iToneUtil->CancelPlay(); + break; + case EBeepNotPrepared: + iToneUtil->CancelPrepare(); + break; + } + } + delete iToneUtil; +} + +QS60Beep* QS60Beep::NewL(TInt aFrequency, TTimeIntervalMicroSeconds aDuration) +{ + QS60Beep* self = new (ELeave) QS60Beep(); + CleanupStack::PushL(self); + self->ConstructL(aFrequency, aDuration); + CleanupStack::Pop(); + return self; +} + +void QS60Beep::ConstructL(TInt aFrequency, TTimeIntervalMicroSeconds aDuration) +{ + iToneUtil = CMdaAudioToneUtility::NewL(*this); + iState = EBeepNotPrepared; + iFrequency = aFrequency; + iDuration = aDuration; + iToneUtil->PrepareToPlayTone(iFrequency, iDuration); +} + +void QS60Beep::Play() +{ + if (iState == EBeepPlaying) { + iToneUtil->CancelPlay(); + iState = EBeepPrepared; + } + + iToneUtil->Play(); + iState = EBeepPlaying; +} + +void QS60Beep::MatoPrepareComplete(TInt aError) +{ + if (aError == KErrNone) { + iState = EBeepPrepared; + Play(); + } +} + +void QS60Beep::MatoPlayComplete(TInt aError) +{ + Q_UNUSED(aError); + iState = EBeepPrepared; +} + + +static Qt::KeyboardModifiers mapToQtModifiers(TUint s60Modifiers) +{ + Qt::KeyboardModifiers result = Qt::NoModifier; + + if (s60Modifiers & EModifierKeypad) + result |= Qt::KeypadModifier; + if (s60Modifiers & EModifierShift || s60Modifiers & EModifierLeftShift + || s60Modifiers & EModifierRightShift) + result |= Qt::ShiftModifier; + if (s60Modifiers & EModifierCtrl || s60Modifiers & EModifierLeftCtrl + || s60Modifiers & EModifierRightCtrl) + result |= Qt::ControlModifier; + if (s60Modifiers & EModifierAlt || s60Modifiers & EModifierLeftAlt + || s60Modifiers & EModifierRightAlt) + result |= Qt::AltModifier; + + return result; +} + +static void mapS60MouseEventTypeToQt(QEvent::Type *type, Qt::MouseButton *button, const TPointerEvent *pEvent) +{ + switch (pEvent->iType) { + case TPointerEvent::EButton1Down: + *type = QEvent::MouseButtonPress; + *button = Qt::LeftButton; + break; + case TPointerEvent::EButton1Up: + *type = QEvent::MouseButtonRelease; + *button = Qt::LeftButton; + break; + case TPointerEvent::EButton2Down: + *type = QEvent::MouseButtonPress; + *button = Qt::MidButton; + break; + case TPointerEvent::EButton2Up: + *type = QEvent::MouseButtonRelease; + *button = Qt::MidButton; + break; + case TPointerEvent::EButton3Down: + *type = QEvent::MouseButtonPress; + *button = Qt::RightButton; + break; + case TPointerEvent::EButton3Up: + *type = QEvent::MouseButtonRelease; + *button = Qt::RightButton; + break; + case TPointerEvent::EDrag: + *type = QEvent::MouseMove; + *button = Qt::NoButton; + break; + case TPointerEvent::EMove: + // Qt makes no distinction between move and drag + *type = QEvent::MouseMove; + *button = Qt::NoButton; + break; + default: + *type = QEvent::None; + *button = Qt::NoButton; + break; + } + if (pEvent->iModifiers & EModifierDoubleClick){ + *type = QEvent::MouseButtonDblClick; + } + + if (*type == QEvent::MouseButtonPress || *type == QEvent::MouseButtonDblClick) + QApplicationPrivate::mouse_buttons = QApplicationPrivate::mouse_buttons | (*button); + else if (*type == QEvent::MouseButtonRelease) + QApplicationPrivate::mouse_buttons = QApplicationPrivate::mouse_buttons &(~(*button)); + + QApplicationPrivate::mouse_buttons = QApplicationPrivate::mouse_buttons & Qt::MouseButtonMask; +} + +//### Can be replaced with CAknLongTapDetector if animation is required. +//NOTE: if CAknLongTapDetector is used make sure it gets variated out of 3.1 and 3.2,. +//also MLongTapObserver needs to be changed to MAknLongTapDetectorCallBack if CAknLongTapDetector is used. +class QLongTapTimer : public CTimer +{ +public: + static QLongTapTimer* NewL(QAbstractLongTapObserver *observer); + QLongTapTimer(QAbstractLongTapObserver *observer); + void ConstructL(); +public: + void PointerEventL(const TPointerEvent &event); + void RunL(); +protected: +private: + QAbstractLongTapObserver *m_observer; + TPointerEvent m_event; + QPoint m_pressedCoordinates; + int m_dragDistance; +}; + +QLongTapTimer* QLongTapTimer::NewL(QAbstractLongTapObserver *observer) +{ + QLongTapTimer* self = new QLongTapTimer(observer); + self->ConstructL(); + return self; +} +void QLongTapTimer::ConstructL() +{ + CTimer::ConstructL(); +} + +QLongTapTimer::QLongTapTimer(QAbstractLongTapObserver *observer):CTimer(CActive::EPriorityHigh) +{ + m_observer = observer; + m_dragDistance = qApp->startDragDistance(); + CActiveScheduler::Add(this); +} + +void QLongTapTimer::PointerEventL(const TPointerEvent& event) +{ + if ( event.iType == TPointerEvent::EDrag || event.iType == TPointerEvent::EButtonRepeat) + { + QPoint diff(QPoint(event.iPosition.iX,event.iPosition.iY) - m_pressedCoordinates); + if (diff.manhattanLength() < m_dragDistance) + return; + } + Cancel(); + m_event = event; + if (event.iType == TPointerEvent::EButton1Down) + { + m_pressedCoordinates = QPoint(event.iPosition.iX,event.iPosition.iY); + // must be same as KLongTapDelay in aknlongtapdetector.h + After(800000); + } +} +void QLongTapTimer::RunL() +{ + if (m_observer) + m_observer->HandleLongTapEventL(m_event.iPosition, m_event.iParentPosition); +} + +QSymbianControl::QSymbianControl(QWidget *w) + : CCoeControl() + , qwidget(w) + , m_longTapDetector(0) + , m_ignoreFocusChanged(0) + , m_symbianPopupIsOpen(0) + , m_inExternalScreenOverride(false) + , m_lastStatusPaneVisibility(0) +{ +} + +void QSymbianControl::ConstructL(bool isWindowOwning, bool desktop) +{ + if (!desktop) + { + if (isWindowOwning || !qwidget->parentWidget() + || qwidget->parentWidget()->windowType() == Qt::Desktop) { + RWindowGroup &wg(S60->windowGroup(qwidget)); + CreateWindowL(wg); + } else { + /** + * TODO: in order to avoid creating windows for all ancestors of + * this widget up to the root window, the parameter passed to + * CreateWindowL should be + * qwidget->parentWidget()->effectiveWinId(). However, if we do + * this, then we need to take care of re-parenting when a window + * is created for a widget between this one and the root window. + */ + CreateWindowL(qwidget->parentWidget()->winId()); + } + + // Necessary in order to be able to track the activation status of + // the control's window + qwidget->d_func()->createExtra(); + + SetFocusing(true); + m_longTapDetector = QLongTapTimer::NewL(this); + m_doubleClickTimer.invalidate(); + + DrawableWindow()->SetPointerGrab(ETrue); + } + +#ifdef Q_SYMBIAN_TRANSITION_EFFECTS + if (OwnsWindow()) { + TTfxWindowPurpose windowPurpose(ETfxPurposeNone); + switch (qwidget->windowType()) { + case Qt::Dialog: + windowPurpose = ETfxPurposeDialogWindow; + break; + case Qt::Popup: + windowPurpose = ETfxPurposePopupWindow; + break; + case Qt::Tool: + windowPurpose = ETfxPurposeToolWindow; + break; + case Qt::ToolTip: + windowPurpose = ETfxPurposeToolTipWindow; + break; + case Qt::SplashScreen: + windowPurpose = ETfxPurposeSplashScreenWindow; + break; + default: + windowPurpose = (isWindowOwning || !qwidget->parentWidget() || qwidget->parentWidget()->windowType() == Qt::Desktop) + ? ETfxPurposeWindow : ETfxPurposeChildWindow; + break; + } + Window().SetPurpose(windowPurpose); + } +#endif +} + +QSymbianControl::~QSymbianControl() +{ + // Ensure backing store is deleted before the top-level + // window is destroyed + qt_widget_private(qwidget)->topData()->backingStore.destroy(); + + if (S60->curWin == this) + S60->curWin = 0; + if (!QApplicationPrivate::is_app_closing) { + QT_TRY { + setFocusSafely(false); + } QT_CATCH(const std::exception&) { + // ignore exceptions, nothing can be done + } + } + S60->appUi()->RemoveFromStack(this); + delete m_longTapDetector; +} + +void QSymbianControl::setWidget(QWidget *w) +{ + qwidget = w; +} + +QPoint QSymbianControl::translatePointForFixedNativeOrientation(const TPoint &pointerEventPos) const +{ + QPoint pos(pointerEventPos.iX, pointerEventPos.iY); + if (qwidget->d_func()->fixNativeOrientationCalled) { + QSize wsize = qwidget->size(); + TSize size = Size(); + if (size.iWidth == wsize.height() && size.iHeight == wsize.width()) { + qreal x = pos.x(); + qreal y = pos.y(); + pos.setX(size.iHeight - y); + pos.setY(x); + } + } + return pos; +} + +TRect QSymbianControl::translateRectForFixedNativeOrientation(const TRect &controlRect) const +{ + TRect rect = controlRect; + if (qwidget->d_func()->fixNativeOrientationCalled) { + QPoint a = translatePointForFixedNativeOrientation(rect.iTl); + QPoint b = translatePointForFixedNativeOrientation(rect.iBr); + if (a.x() < b.x()) { + rect.iTl.iX = a.x(); + rect.iBr.iX = b.x(); + } else { + rect.iTl.iX = b.x(); + rect.iBr.iX = a.x(); + } + if (a.y() < b.y()) { + rect.iTl.iY = a.y(); + rect.iBr.iY = b.y(); + } else { + rect.iTl.iY = b.y(); + rect.iBr.iY = a.y(); + } + } + return rect; +} + +void QSymbianControl::HandleLongTapEventL( const TPoint& aPenEventLocation, const TPoint& aPenEventScreenLocation ) +{ + QWidget *alienWidget; + QPoint widgetPos = translatePointForFixedNativeOrientation(aPenEventLocation); + QPoint globalPos = translatePointForFixedNativeOrientation(aPenEventScreenLocation); + alienWidget = qwidget->childAt(widgetPos); + if (!alienWidget) + alienWidget = qwidget; + +#if !defined(QT_NO_CONTEXTMENU) + QContextMenuEvent contextMenuEvent(QContextMenuEvent::Mouse, widgetPos, globalPos, Qt::NoModifier); + qt_sendSpontaneousEvent(alienWidget, &contextMenuEvent); +#endif +} + +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER +void QSymbianControl::translateAdvancedPointerEvent(const TAdvancedPointerEvent *event) +{ + QApplicationPrivate *d = QApplicationPrivate::instance(); + QPointF screenPos = qwidget->mapToGlobal(translatePointForFixedNativeOrientation(event->iPosition)); + qreal pressure; + if(d->pressureSupported + && event->Pressure() > 0) //workaround for misconfigured HAL + pressure = event->Pressure() / qreal(d->maxTouchPressure); + else + pressure = qreal(1.0); + processTouchEvent(event->PointerNumber(), event->iType, screenPos, pressure); +} +#endif + +void QSymbianControl::processTouchEvent(int pointerNumber, TPointerEvent::TType type, QPointF screenPos, qreal pressure) +{ + QRect screenGeometry = qApp->desktop()->screenGeometry(qwidget); + + QApplicationPrivate *d = QApplicationPrivate::instance(); + + QList points = d->appAllTouchPoints; + while (points.count() <= pointerNumber) + points.append(QTouchEvent::TouchPoint(points.count())); + + Qt::TouchPointStates allStates = 0; + for (int i = 0; i < points.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = points[i]; + + if (touchPoint.id() == pointerNumber) { + Qt::TouchPointStates state; + switch (type) { + case TPointerEvent::EButton1Down: +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + case TPointerEvent::EEnterHighPressure: +#endif + state = Qt::TouchPointPressed; + break; + case TPointerEvent::EButton1Up: +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + case TPointerEvent::EExitCloseProximity: +#endif + state = Qt::TouchPointReleased; + break; + case TPointerEvent::EDrag: + state = Qt::TouchPointMoved; + break; + default: + // how likely is this to happen? + state = Qt::TouchPointStationary; + break; + } + if (pointerNumber == 0) + state |= Qt::TouchPointPrimary; + touchPoint.setState(state); + + touchPoint.setScreenPos(screenPos); + touchPoint.setNormalizedPos(QPointF(screenPos.x() / screenGeometry.width(), + screenPos.y() / screenGeometry.height())); + + touchPoint.setPressure(pressure); + } else if (touchPoint.state() != Qt::TouchPointReleased) { + // all other active touch points should be marked as stationary + touchPoint.setState(Qt::TouchPointStationary); + } + + allStates |= touchPoint.state(); + } + + if ((allStates & Qt::TouchPointStateMask) == Qt::TouchPointReleased) { + // all touch points released + d->appAllTouchPoints.clear(); + } else { + d->appAllTouchPoints = points; + } + + QApplicationPrivate::translateRawTouchEvent(qwidget, + QTouchEvent::TouchScreen, + points); +} + +void QSymbianControl::HandlePointerEventL(const TPointerEvent& pEvent) +{ +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + if (pEvent.IsAdvancedPointerEvent()) { + const TAdvancedPointerEvent *advancedPointerEvent = pEvent.AdvancedPointerEvent(); + translateAdvancedPointerEvent(advancedPointerEvent); + if (advancedPointerEvent->PointerNumber() != 0) { + // only send mouse events for the first touch point + return; + } + } +#endif + + m_longTapDetector->PointerEventL(pEvent); + QT_TRYCATCH_LEAVING(HandlePointerEvent(pEvent)); +} + +void QSymbianControl::HandlePointerEvent(const TPointerEvent& pEvent) +{ + QMouseEvent::Type type; + Qt::MouseButton button; + mapS60MouseEventTypeToQt(&type, &button, &pEvent); + Qt::KeyboardModifiers modifiers = mapToQtModifiers(pEvent.iModifiers); + + QPoint widgetPos = translatePointForFixedNativeOrientation(pEvent.iPosition); + TPoint controlScreenPos = PositionRelativeToScreen(); + QPoint globalPos = QPoint(controlScreenPos.iX, controlScreenPos.iY) + widgetPos; + S60->lastCursorPos = globalPos; + S60->lastPointerEventPos = widgetPos; + + QWidget *mouseGrabber = QWidget::mouseGrabber(); + + QWidget *popupWidget = qApp->activePopupWidget(); + QWidget *popupReceiver = 0; + if (popupWidget) { + QWidget *popupChild = popupWidget->childAt(popupWidget->mapFromGlobal(globalPos)); + popupReceiver = popupChild ? popupChild : popupWidget; + } + + if (mouseGrabber) { + if (popupReceiver) { + sendMouseEvent(popupReceiver, type, globalPos, button, modifiers); + } else { + sendMouseEvent(mouseGrabber, type, globalPos, button, modifiers); + } + // No Enter/Leave events in grabbing mode. + return; + } + + QWidget *widgetUnderPointer = qwidget->childAt(widgetPos); + if (!widgetUnderPointer) + widgetUnderPointer = qwidget; + + QApplicationPrivate::dispatchEnterLeave(widgetUnderPointer, S60->lastPointerEventTarget); + S60->lastPointerEventTarget = widgetUnderPointer; + + QWidget *receiver; + if (!popupReceiver && S60->mousePressTarget && type != QEvent::MouseButtonPress) { + receiver = S60->mousePressTarget; + if (type == QEvent::MouseButtonRelease) + S60->mousePressTarget = 0; + } else { + receiver = popupReceiver ? popupReceiver : widgetUnderPointer; + if (type == QEvent::MouseButtonPress) + S60->mousePressTarget = receiver; + } + +#if !defined(QT_NO_CURSOR) && !defined(Q_SYMBIAN_FIXED_POINTER_CURSORS) + if (S60->brokenPointerCursors) + qt_symbian_move_cursor_sprite(); +#endif + +//Generate single touch event for S60 5.0 (has touchscreen, does not have advanced pointers) +#ifndef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + if (S60->hasTouchscreen) { + processTouchEvent(0, pEvent.iType, QPointF(globalPos), 1.0); + } +#endif + + sendMouseEvent(receiver, type, globalPos, button, modifiers); +} + +#ifdef Q_WS_S60 +void QSymbianControl::HandleStatusPaneSizeChange() +{ + QS60MainAppUi *s60AppUi = static_cast(S60->appUi()); + s60AppUi->HandleStatusPaneSizeChange(); +} +#endif + +void QSymbianControl::sendMouseEvent( + QWidget *receiver, + QEvent::Type type, + const QPoint &globalPos, + Qt::MouseButton button, + Qt::KeyboardModifiers modifiers) +{ + Q_ASSERT(receiver); + QMouseEvent mEvent(type, receiver->mapFromGlobal(globalPos), globalPos, + button, QApplicationPrivate::mouse_buttons, modifiers); + QEventDispatcherS60 *dispatcher; + // It is theoretically possible for someone to install a different event dispatcher. + if ((dispatcher = qobject_cast(receiver->d_func()->threadData->eventDispatcher)) != 0) { + if (dispatcher->excludeUserInputEvents()) { + dispatcher->saveInputEvent(this, receiver, new QMouseEvent(mEvent)); + return; + } + } + + sendMouseEvent(receiver, &mEvent); +} + +bool QSymbianControl::sendMouseEvent(QWidget *widget, QMouseEvent *mEvent) +{ + return qt_sendSpontaneousEvent(widget, mEvent); +} + +TKeyResponse QSymbianControl::OfferKeyEventL(const TKeyEvent& keyEvent, TEventCode type) +{ + TKeyResponse r = EKeyWasNotConsumed; + QT_TRYCATCH_LEAVING(r = OfferKeyEvent(keyEvent, type)); + return r; +} + +TKeyResponse QSymbianControl::OfferKeyEvent(const TKeyEvent& keyEvent, TEventCode type) +{ + /* + S60 has a confusing way of delivering key events. There are three types of + events: EEventKey, EEventKeyDown and EEventKeyUp. When a key is pressed, + EEventKeyDown is first generated, followed by EEventKey. Then, when the key is + released, EEventKeyUp is generated. + However, it is possible that only the EEventKey is generated alone, typically + in relation to virtual keyboards. In that case we need to take care to + generate both press and release events in Qt, since applications expect that. + We do this by having three states for each used scan code, depending on the + events received. See the switch below for what happens in each state + transition. + */ + + if (type != EEventKeyDown) + if (handleVirtualMouse(keyEvent, type) == EKeyWasConsumed) + return EKeyWasConsumed; + + TKeyResponse ret = EKeyWasNotConsumed; +#define GET_RETURN(x) (ret = ((x) == EKeyWasConsumed) ? EKeyWasConsumed : ret) + + // This top level switch corresponds to the states, and the inner switches + // correspond to the transitions. + QS60Data::ScanCodeState &scanCodeState = S60->scanCodeStates[keyEvent.iScanCode]; + switch (scanCodeState) { + case QS60Data::Unpressed: + switch (type) { + case EEventKeyDown: + scanCodeState = QS60Data::KeyDown; + break; + case EEventKey: + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyPress)); + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyRelease)); + break; + case EEventKeyUp: + // No action. + break; + } + break; + case QS60Data::KeyDown: + switch (type) { + case EEventKeyDown: + // This should never happen, just stay in this state to be safe. + break; + case EEventKey: + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyPress)); + scanCodeState = QS60Data::KeyDownAndKey; + break; + case EEventKeyUp: + scanCodeState = QS60Data::Unpressed; + break; + } + break; + case QS60Data::KeyDownAndKey: + switch (type) { + case EEventKeyDown: + // This should never happen, just stay in this state to be safe. + break; + case EEventKey: + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyRelease)); + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyPress)); + break; + case EEventKeyUp: + GET_RETURN(sendSymbianKeyEvent(keyEvent, QEvent::KeyRelease)); + scanCodeState = QS60Data::Unpressed; + break; + } + break; + } + return ret; + +#undef GET_RETURN +} + +TKeyResponse QSymbianControl::sendSymbianKeyEvent(const TKeyEvent &keyEvent, QEvent::Type type) +{ + // Because S60 does not generate keysyms for EKeyEventDown and EKeyEventUp + // events, we need to cache the keysyms from the EKeyEvent events. This is what + // resolveS60ScanCode does. + TUint s60Keysym = QApplicationPrivate::resolveS60ScanCode(keyEvent.iScanCode, + keyEvent.iCode); + int keyCode; + if (s60Keysym == EKeyNull){ //some key events have 0 in iCode, for them iScanCode should be used + keyCode = qt_keymapper_private()->mapS60ScanCodesToQt(keyEvent.iScanCode); + } else if (s60Keysym >= 0x20 && s60Keysym < ENonCharacterKeyBase) { + // Normal characters keys. + keyCode = s60Keysym; + } else { + // Special S60 keys. + keyCode = qt_keymapper_private()->mapS60KeyToQt(s60Keysym); + } + + Qt::KeyboardModifiers mods = mapToQtModifiers(keyEvent.iModifiers); + QKeyEventEx qKeyEvent(type, keyCode, mods, qt_keymapper_private()->translateKeyEvent(keyCode, mods), + (keyEvent.iRepeats != 0), 1, keyEvent.iScanCode, s60Keysym, keyEvent.iModifiers); + QWidget *widget; + widget = QWidget::keyboardGrabber(); + if (!widget) { + if (QApplicationPrivate::popupWidgets != 0) { + widget = QApplication::activePopupWidget()->focusWidget(); + if (!widget) { + widget = QApplication::activePopupWidget(); + } + } else { + widget = QApplicationPrivate::focus_widget; + if (!widget) { + widget = qwidget; + } + } + } + + QEventDispatcherS60 *dispatcher; + // It is theoretically possible for someone to install a different event dispatcher. + if ((dispatcher = qobject_cast(widget->d_func()->threadData->eventDispatcher)) != 0) { + if (dispatcher->excludeUserInputEvents()) { + dispatcher->saveInputEvent(this, widget, new QKeyEventEx(qKeyEvent)); + return EKeyWasConsumed; + } + } + return sendKeyEvent(widget, &qKeyEvent); +} + +TKeyResponse QSymbianControl::handleVirtualMouse(const TKeyEvent& keyEvent,TEventCode type) +{ +#ifndef QT_NO_CURSOR + if (S60->mouseInteractionEnabled && S60->virtualMouseRequired) { + //translate keys to pointer + if ((keyEvent.iScanCode >= EStdKeyLeftArrow && keyEvent.iScanCode <= EStdKeyDownArrow) || + (keyEvent.iScanCode >= EStdKeyDevice10 && keyEvent.iScanCode <= EStdKeyDevice13) || + keyEvent.iScanCode == EStdKeyDevice3) { + QPoint pos = QCursor::pos(); + TPointerEvent fakeEvent; + fakeEvent.iType = (TPointerEvent::TType)(-1); + fakeEvent.iModifiers = keyEvent.iModifiers; + TInt x = pos.x(); + TInt y = pos.y(); + if (type == EEventKeyUp) { + S60->virtualMouseAccelTimeout.start(); + switch (keyEvent.iScanCode) { + case EStdKeyLeftArrow: + S60->virtualMousePressedKeys &= ~QS60Data::Left; + break; + case EStdKeyRightArrow: + S60->virtualMousePressedKeys &= ~QS60Data::Right; + break; + case EStdKeyUpArrow: + S60->virtualMousePressedKeys &= ~QS60Data::Up; + break; + case EStdKeyDownArrow: + S60->virtualMousePressedKeys &= ~QS60Data::Down; + break; + // diagonal keys (named aliases don't exist in 3.1 SDK) + case EStdKeyDevice10: + S60->virtualMousePressedKeys &= ~QS60Data::LeftUp; + break; + case EStdKeyDevice11: + S60->virtualMousePressedKeys &= ~QS60Data::RightUp; + break; + case EStdKeyDevice12: + S60->virtualMousePressedKeys &= ~QS60Data::RightDown; + break; + case EStdKeyDevice13: + S60->virtualMousePressedKeys &= ~QS60Data::LeftDown; + break; + case EStdKeyDevice3: //select + if (S60->virtualMousePressedKeys & QS60Data::Select) + fakeEvent.iType = TPointerEvent::EButton1Up; + S60->virtualMousePressedKeys &= ~QS60Data::Select; + break; + } + } + else if (type == EEventKey) { + int dx = 0; + int dy = 0; + if (keyEvent.iScanCode != EStdKeyDevice3) { + m_doubleClickTimer.invalidate(); + //reset mouse accelleration after a short time with no moves + const int maxTimeBetweenKeyEventsMs = 500; + if (S60->virtualMouseAccelTimeout.isValid() && + S60->virtualMouseAccelTimeout.hasExpired(maxTimeBetweenKeyEventsMs)) { + S60->virtualMouseAccelDX = 0; + S60->virtualMouseAccelDY = 0; + } + S60->virtualMouseAccelTimeout.invalidate(); + } + switch (keyEvent.iScanCode) { + case EStdKeyLeftArrow: + S60->virtualMousePressedKeys |= QS60Data::Left; + dx = -1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyRightArrow: + S60->virtualMousePressedKeys |= QS60Data::Right; + dx = 1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyUpArrow: + S60->virtualMousePressedKeys |= QS60Data::Up; + dy = -1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDownArrow: + S60->virtualMousePressedKeys |= QS60Data::Down; + dy = 1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDevice10: + S60->virtualMousePressedKeys |= QS60Data::LeftUp; + dx = -1; + dy = -1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDevice11: + S60->virtualMousePressedKeys |= QS60Data::RightUp; + dx = 1; + dy = -1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDevice12: + S60->virtualMousePressedKeys |= QS60Data::RightDown; + dx = 1; + dy = 1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDevice13: + S60->virtualMousePressedKeys |= QS60Data::LeftDown; + dx = -1; + dy = 1; + fakeEvent.iType = TPointerEvent::EMove; + break; + case EStdKeyDevice3: + // Platform bug. If you start pressing several keys simultaneously (for + // example for drag'n'drop), Symbian starts producing spurious up and + // down messages for some keys. Therefore, make sure we have a clean slate + // of pressed keys before starting a new button press. + if (S60->virtualMousePressedKeys & QS60Data::Select) { + return EKeyWasConsumed; + } else { + S60->virtualMousePressedKeys |= QS60Data::Select; + fakeEvent.iType = TPointerEvent::EButton1Down; + if (m_doubleClickTimer.isValid() + && !m_doubleClickTimer.hasExpired(QApplication::doubleClickInterval())) { + fakeEvent.iModifiers |= EModifierDoubleClick; + m_doubleClickTimer.invalidate(); + } else { + m_doubleClickTimer.start(); + } + } + break; + } + if (dx) { + int cdx = S60->virtualMouseAccelDX; + //reset accel on change of sign, else double accel + if (dx * cdx <= 0) + cdx = dx; + else + cdx *= 4; + //cap accelleration + if (dx * cdx > S60->virtualMouseMaxAccel) + cdx = dx * S60->virtualMouseMaxAccel; + //move mouse position + x += cdx; + S60->virtualMouseAccelDX = cdx; + } + + if (dy) { + int cdy = S60->virtualMouseAccelDY; + if (dy * cdy <= 0) + cdy = dy; + else + cdy *= 4; + if (dy * cdy > S60->virtualMouseMaxAccel) + cdy = dy * S60->virtualMouseMaxAccel; + y += cdy; + S60->virtualMouseAccelDY = cdy; + } + } + //clip to screen size (window server allows a sprite hotspot to be outside the screen) + int screenNumber = S60->screenNumberForWidget(qwidget); + if (x < 0) + x = 0; + else if (x >= S60->screenWidthInPixelsForScreen[screenNumber]) + x = S60->screenWidthInPixelsForScreen[screenNumber] - 1; + if (y < 0) + y = 0; + else if (y >= S60->screenHeightInPixelsForScreen[screenNumber]) + y = S60->screenHeightInPixelsForScreen[screenNumber] - 1; + TPoint epos(x, y); + TPoint cpos = epos - PositionRelativeToScreen(); + fakeEvent.iPosition = cpos; + fakeEvent.iParentPosition = epos; + if(fakeEvent.iType != -1) + HandlePointerEvent(fakeEvent); + return EKeyWasConsumed; + } + } +#endif + + return EKeyWasNotConsumed; +} + +void QSymbianControl::sendInputEvent(QWidget *widget, QInputEvent *inputEvent) +{ + switch (inputEvent->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + sendKeyEvent(widget, static_cast(inputEvent)); + break; + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + sendMouseEvent(widget, static_cast(inputEvent)); + break; + default: + // Shouldn't get here. + Q_ASSERT_X(0 == 1, "QSymbianControl::sendInputEvent()", "inputEvent->type() is unknown"); + break; + } +} + +TKeyResponse QSymbianControl::sendKeyEvent(QWidget *widget, QKeyEvent *keyEvent) +{ +#if !defined(QT_NO_IM) && defined(Q_WS_S60) + if (widget && widget->isEnabled() && widget->testAttribute(Qt::WA_InputMethodEnabled)) { + QInputContext *qic = widget->inputContext(); + if (qic && qic->filterEvent(keyEvent)) + return EKeyWasConsumed; + } +#endif // !defined(QT_NO_IM) && defined(Q_OS_SYMBIAN) + + if (widget && qt_sendSpontaneousEvent(widget, keyEvent)) + if (keyEvent->isAccepted()) + return EKeyWasConsumed; + + return EKeyWasNotConsumed; +} + +#if !defined(QT_NO_IM) && defined(Q_WS_S60) +TCoeInputCapabilities QSymbianControl::InputCapabilities() const +{ + QWidget *w = 0; + + if (qwidget->hasFocus()) + w = qwidget; + else + w = qwidget->focusWidget(); + + QCoeFepInputContext *ic; + if (w && w->isEnabled() && w->testAttribute(Qt::WA_InputMethodEnabled) + && (ic = qobject_cast(w->inputContext()))) { + return ic->inputCapabilities(); + } else { + return TCoeInputCapabilities(TCoeInputCapabilities::ENone, 0, 0); + } +} +#endif + +void QSymbianControl::Draw(const TRect& controlRect) const +{ + // Set flag to avoid calling DrawNow in window surface + QWidget *window = qwidget->window(); + Q_ASSERT(window); + QTLWExtra *topExtra = window->d_func()->maybeTopData(); + Q_ASSERT(topExtra); + + TRect wcontrolRect = translateRectForFixedNativeOrientation(controlRect); + + if (!topExtra->inExpose) { + topExtra->inExpose = true; + if (!qwidget->isWindow()) { + // If we get here, then it means we have a native child window + // Since no content should ever be painted to these windows, we + // erase them with a transparent brush when they get an expose. + CWindowGc &gc = SystemGc(); + gc.SetBrushColor(TRgb(0, 0, 0, 0)); + gc.Clear(controlRect); + } + QRect exposeRect = qt_TRect2QRect(wcontrolRect); + qwidget->d_func()->syncBackingStore(exposeRect); + topExtra->inExpose = false; + } + + QWindowSurface *surface = qwidget->windowSurface(); + QPaintEngine *engine = surface ? surface->paintDevice()->paintEngine() : NULL; + + if (!engine) + return; + + const bool sendNativePaintEvents = qwidget->d_func()->extraData()->receiveNativePaintEvents; + if (sendNativePaintEvents) { + const QRect r = qt_TRect2QRect(wcontrolRect); + QMetaObject::invokeMethod(qwidget, "beginNativePaintEvent", Qt::DirectConnection, Q_ARG(QRect, r)); + } + + // Map source rectangle into coordinates of the backing store. + const QPoint controlBase(controlRect.iTl.iX, controlRect.iTl.iY); + const QPoint backingStoreBase = qwidget->mapTo(qwidget->window(), controlBase); + const TRect backingStoreRect(TPoint(backingStoreBase.x(), backingStoreBase.y()), controlRect.Size()); + + if (engine->type() == QPaintEngine::Raster) { + QS60WindowSurface *s60Surface; +#ifdef QT_GRAPHICSSYSTEM_RUNTIME + if (QApplicationPrivate::runtime_graphics_system) { + QRuntimeWindowSurface *rtSurface = + static_cast(qwidget->windowSurface()); + s60Surface = static_cast(rtSurface->m_windowSurface.data()); + } else +#endif + s60Surface = static_cast(qwidget->windowSurface()); + + CFbsBitmap *bitmap = s60Surface->symbianBitmap(); + CWindowGc &gc = SystemGc(); + + QWExtra::NativePaintMode nativePaintMode = qwidget->d_func()->extraData()->nativePaintMode; + if(qwidget->d_func()->paintOnScreen()) + nativePaintMode = QWExtra::Disable; + + switch(nativePaintMode) { + case QWExtra::Disable: + // Do nothing + break; + case QWExtra::Blit: + case QWExtra::BlitWriteAlpha: + if (qwidget->d_func()->isOpaque || nativePaintMode == QWExtra::BlitWriteAlpha) + gc.SetDrawMode(CGraphicsContext::EDrawModeWriteAlpha); + gc.BitBlt(controlRect.iTl, bitmap, backingStoreRect); + break; + case QWExtra::ZeroFill: + if (Window().DisplayMode() == EColor16MA + || Window().DisplayMode() == Q_SYMBIAN_ECOLOR16MAP) { + gc.SetBrushStyle(CGraphicsContext::ESolidBrush); + gc.SetDrawMode(CGraphicsContext::EDrawModeWriteAlpha); + gc.SetBrushColor(TRgb::Color16MA(0)); + gc.Clear(controlRect); + } else { + gc.SetBrushColor(TRgb(0x000000)); + gc.Clear(controlRect); + }; + break; + default: + Q_ASSERT(false); + } + } + + if (sendNativePaintEvents) { + const QRect r = qt_TRect2QRect(wcontrolRect); + // The draw ops aren't actually sent to WSERV until the graphics + // context is deactivated, which happens in the function calling + // this one. We therefore delay the delivery of endNativePaintEvent, + // to ensure that drawing has completed by the time the widget + // receives the event. Note that, if the widget needs to ensure + // that the draw ops have actually been executed into the output + // framebuffer, a call to RWsSession::Flush is required in the + // endNativePaintEvent implementation. + QMetaObject::invokeMethod(qwidget, "endNativePaintEvent", Qt::QueuedConnection, Q_ARG(QRect, r)); + } +} + +void QSymbianControl::qwidgetResize_helper(const QSize &newSize) +{ + QRect cr = qwidget->geometry(); + QSize oldSize(cr.size()); + cr.setSize(newSize); + qwidget->data->crect = cr; + if (qwidget->isVisible()) { + QTLWExtra *tlwExtra = qwidget->d_func()->maybeTopData(); + bool slowResize = qgetenv("QT_SLOW_TOPLEVEL_RESIZE").toInt(); + if (!slowResize && tlwExtra) + tlwExtra->inTopLevelResize = true; + QResizeEvent e(newSize, oldSize); + qt_sendSpontaneousEvent(qwidget, &e); + if (!qwidget->testAttribute(Qt::WA_StaticContents)) + qwidget->d_func()->syncBackingStore(); + if (!slowResize && tlwExtra) + tlwExtra->inTopLevelResize = false; + } else { + if (!qwidget->testAttribute(Qt::WA_PendingResizeEvent)) { + QResizeEvent *e = new QResizeEvent(newSize, oldSize); + QApplication::postEvent(qwidget, e); + } + } +} + +void QSymbianControl::SizeChanged() +{ + CCoeControl::SizeChanged(); + + // When FixNativeOrientation had been called, the RWindow/CCoeControl size + // and the surface/QWidget size have nothing to do with each other. + if (qwidget->d_func()->fixNativeOrientationCalled) + return; + + QSize oldSize = qwidget->size(); + QSize newSize(Size().iWidth, Size().iHeight); + + if (oldSize != newSize) { + // Enforce the proper size for fullscreen widgets on the secondary screen. + const bool isFullscreen = qwidget->windowState() & Qt::WindowFullScreen; + const int screenNumber = S60->screenNumberForWidget(qwidget); + if (!m_inExternalScreenOverride && isFullscreen && screenNumber > 0) { + int screenWidth = S60->screenWidthInPixelsForScreen[screenNumber]; + int screenHeight = S60->screenHeightInPixelsForScreen[screenNumber]; + TSize screenSize(screenWidth, screenHeight); + if (screenWidth > 0 && screenHeight > 0 && screenSize != Size()) { + m_inExternalScreenOverride = true; + SetExtent(TPoint(0, 0), screenSize); + return; + } + } + + qwidgetResize_helper(newSize); + } + + m_inExternalScreenOverride = false; + + // CCoeControl::SetExtent calls SizeChanged, but does not call + // PositionChanged, so we call it here to ensure that the widget's + // position is updated. + PositionChanged(); +} + +void QSymbianControl::PositionChanged() +{ + CCoeControl::PositionChanged(); + + QPoint oldPos = qwidget->geometry().topLeft(); + QPoint newPos(Position().iX, Position().iY); + + if (oldPos != newPos) { + QRect cr = qwidget->geometry(); + cr.moveTopLeft(newPos); + qwidget->data->crect = cr; + QTLWExtra *top = qwidget->d_func()->maybeTopData(); + if (top && (qwidget->windowState() & (~Qt::WindowActive)) == Qt::WindowNoState) + top->normalGeometry.moveTopLeft(newPos); + if (qwidget->isVisible()) { + QMoveEvent e(newPos, oldPos); + qt_sendSpontaneousEvent(qwidget, &e); + } else { + QMoveEvent * e = new QMoveEvent(newPos, oldPos); + QApplication::postEvent(qwidget, e); + } + } +} + +void QSymbianControl::FocusChanged(TDrawNow /* aDrawNow */) +{ + if (m_ignoreFocusChanged || (qwidget->windowType() & Qt::WindowType_Mask) == Qt::Desktop) + return; + +#ifdef Q_WS_S60 + if (S60->splitViewLastWidget) + return; +#endif + + // Popups never get focused, but still receive the FocusChanged when they are hidden. + if (QApplicationPrivate::popupWidgets != 0 + || (qwidget->windowType() & Qt::Popup) == Qt::Popup) + return; + + if (IsFocused() && IsVisible()) { + if (m_symbianPopupIsOpen) { + QWidget *fw = QApplication::focusWidget(); + if (fw) { + QFocusEvent event(QEvent::FocusIn, Qt::PopupFocusReason); + QCoreApplication::sendEvent(fw, &event); + } + m_symbianPopupIsOpen = false; + } + + QApplication::setActiveWindow(qwidget->window()); + qwidget->d_func()->setWindowIcon_sys(true); + qwidget->d_func()->setWindowTitle_sys(qwidget->windowTitle()); +#ifdef Q_WS_S60 + if (qwidget->isWindow()) + S60->setRecursiveDecorationsVisibility(qwidget, qwidget->windowState()); +#endif + } else if (QApplication::activeWindow() == qwidget->window()) { + bool focusedControlFound = false; + WId winId = 0; + for (QWidget *w = qwidget->parentWidget(); w && (winId = w->internalWinId()); w = w->parentWidget()) { + if (winId->IsFocused() && winId->IsVisible()) { + focusedControlFound = true; + break; + } else if (w->isWindow()) + break; + } + if (!focusedControlFound) { + if (CCoeEnv::Static()->AppUi()->IsDisplayingMenuOrDialog() || S60->menuBeingConstructed) { + QWidget *fw = QApplication::focusWidget(); + if (fw) { + QFocusEvent event(QEvent::FocusOut, Qt::PopupFocusReason); + QCoreApplication::sendEvent(fw, &event); + } + m_symbianPopupIsOpen = true; + return; + } + + QApplication::setActiveWindow(0); + } + } + // else { We don't touch the active window unless we were explicitly activated or deactivated } +} + +void QSymbianControl::handleClientAreaChange() +{ + const bool cbaVisibilityHint = qwidget->windowFlags() & Qt::WindowSoftkeysVisibleHint; + if (qwidget->isFullScreen() && !cbaVisibilityHint) { + SetExtentToWholeScreen(); + } else if (qwidget->isMaximized() || (qwidget->isFullScreen() && cbaVisibilityHint)) { + TRect r = static_cast(S60->appUi())->ClientRect(); + SetExtent(r.iTl, r.Size()); + } else if (!qwidget->isMinimized()) { // Normal geometry + if (!qwidget->testAttribute(Qt::WA_Resized)) { + qwidget->adjustSize(); + qwidget->setAttribute(Qt::WA_Resized, false); //not a user resize + } + if (!qwidget->testAttribute(Qt::WA_Moved) && qwidget->windowType() != Qt::Dialog) { + TRect r = static_cast(S60->appUi())->ClientRect(); + SetPosition(r.iTl); + qwidget->setAttribute(Qt::WA_Moved, false); // not really an explicit position + } + } +} + +bool QSymbianControl::isSplitViewWidget(QWidget *widget) { + bool returnValue = true; + //Ignore events sent to non-active windows, not visible widgets and not parents of input widget. + if (!qwidget->isActiveWindow() + || !qwidget->isVisible() + || !qwidget->isAncestorOf(widget)) { + + returnValue = false; + } + return returnValue; +} + +void QSymbianControl::HandleResourceChange(int resourceType) +{ + switch (resourceType) { + case KSplitViewCloseEvent: //intentional fall-through + case KSplitViewOpenEvent: { +#if !defined(QT_NO_IM) && defined(Q_WS_S60) + + //Fetch widget getting the text input + QWidget *widget = QWidget::keyboardGrabber(); + if (!widget) { + if (QApplicationPrivate::popupWidgets) { + widget = QApplication::activePopupWidget()->focusWidget(); + if (!widget) { + widget = QApplication::activePopupWidget(); + } + } else { + widget = QApplicationPrivate::focus_widget; + if (!widget) { + widget = qwidget; + } + } + } + if (widget) { + QCoeFepInputContext *ic = qobject_cast(widget->inputContext()); + if (!ic) { + ic = qobject_cast(qApp->inputContext()); + } + if (ic && isSplitViewWidget(widget)) { + if (resourceType == KSplitViewCloseEvent) { + ic->resetSplitViewWidget(); + } else { + ic->ensureFocusWidgetVisible(widget); + } + } + } +#endif // !defined(QT_NO_IM) && defined(Q_WS_S60) + } + break; + case KInternalStatusPaneChange: + // When status pane is not visible, only handle client area change if status pane was + // previously visible, as size changes to hidden status pane should not affect + // client area. + if (S60->statusPane() && (S60->statusPane()->IsVisible() || m_lastStatusPaneVisibility)) { + m_lastStatusPaneVisibility = S60->statusPane()->IsVisible(); + handleClientAreaChange(); + } + if (IsFocused() && IsVisible()) { + qwidget->d_func()->setWindowIcon_sys(true); + qwidget->d_func()->setWindowTitle_sys(qwidget->windowTitle()); + } + break; + case KUidValueCoeFontChangeEvent: + // font change event + break; +#ifdef Q_WS_S60 + case KEikDynamicLayoutVariantSwitch: + { + handleClientAreaChange(); + // Send resize event to trigger desktopwidget workAreaResized signal + if (qt_desktopWidget) { + QResizeEvent e(qt_desktopWidget->size(), qt_desktopWidget->size()); + QApplication::sendEvent(qt_desktopWidget, &e); + } + break; + } +#endif + default: + break; + } + + CCoeControl::HandleResourceChange(resourceType); + +} +void QSymbianControl::CancelLongTapTimer() +{ + m_longTapDetector->Cancel(); +} + +TTypeUid::Ptr QSymbianControl::MopSupplyObject(TTypeUid id) +{ + if (id.iUid == ETypeId) + return id.MakePtr(this); + + return CCoeControl::MopSupplyObject(id); +} + +void QSymbianControl::setFocusSafely(bool focus) +{ + // The stack hack in here is very unfortunate, but it is the only way to ensure proper + // focus in Symbian. If this is not executed, the control which happens to be on + // the top of the stack may randomly be assigned focus by Symbian, for example + // when creating new windows (specifically in CCoeAppUi::HandleStackChanged()). + + // Close any popups. + CEikonEnv::Static()->EikAppUi()->StopDisplayingMenuBar(); + + if (focus) { + S60->appUi()->RemoveFromStack(this); + // Symbian doesn't automatically remove focus from the last focused control, so we need to + // remember it and clear focus ourselves. + if (lastFocusedControl && lastFocusedControl != this) + lastFocusedControl->SetFocus(false); + QT_TRAP_THROWING(S60->appUi()->AddToStackL(this, + ECoeStackPriorityDefault + 1, ECoeStackFlagStandard)); // Note the + 1 + lastFocusedControl = this; + this->SetFocus(true); + } else { + S60->appUi()->RemoveFromStack(this); + QT_TRAP_THROWING(S60->appUi()->AddToStackL(this, + ECoeStackPriorityDefault, ECoeStackFlagStandard)); + if(this == lastFocusedControl) + lastFocusedControl = 0; + this->SetFocus(false); + } +} + +bool QSymbianControl::isControlActive() +{ + return IsActivated() ? true : false; +} + +void QSymbianControl::ensureFixNativeOrientation() +{ +#if defined(Q_SYMBIAN_SUPPORTS_FIXNATIVEORIENTATION) + if (!qwidget->isWindow() || qwidget->windowType() == Qt::Desktop) + return; + if (S60->screenNumberForWidget(qwidget) > 0) + return; + const bool isFixed = qwidget->d_func()->fixNativeOrientationCalled; + const bool isFixEnabled = qwidget->testAttribute(Qt::WA_SymbianNoSystemRotation); + const bool isFullScreen = qwidget->windowState().testFlag(Qt::WindowFullScreen); + if (isFullScreen && isFixEnabled) { + const bool surfaceBasedGs = + QApplicationPrivate::graphics_system_name == QLatin1String("openvg") + || QApplicationPrivate::graphics_system_name == QLatin1String("opengl"); + if (!surfaceBasedGs) + qwidget->setAttribute(Qt::WA_SymbianNoSystemRotation, false); + if (!isFixed && surfaceBasedGs) { + if (Window().FixNativeOrientation() == KErrNone) { + qwidget->d_func()->fixNativeOrientationCalled = true; + // The EGL window surface is now fixed to the native orientation + // of the device, no matter what size we pass when creating it. + // Enforce the same size for the QWidget too. For the underlying + // CCoeControl and RWindow it is up to the system to resize them + // when the standard auto-rotation mechanism is in use, we must not + // change that behavior by forcing any size for those. In practice + // this means that the QWidget and the underlying native control + // dimensions will be out of sync when FixNativeOrientation was + // called and the device is turned to the non-native (typically + // landscape) orientation. The pointer event handling and certain + // functions like Draw() will need to compensate for this. + QSize newSize(S60->nativeScreenWidthInPixels, S60->nativeScreenHeightInPixels); + if (qwidget->size() != newSize) + qwidgetResize_helper(newSize); + } else { + qwidget->setAttribute(Qt::WA_SymbianNoSystemRotation, false); + } + } + } else if (isFixed) { + qwidget->setAttribute(Qt::WA_SymbianNoSystemRotation, false); + qwidget->d_func()->fixNativeOrientationCalled = false; + qwidget->hide(); + qwidget->d_func()->create_sys(0, false, true); + qwidget->show(); + } +#else + qwidget->setAttribute(Qt::WA_SymbianNoSystemRotation, false); +#endif +} + +/*! + \typedef QApplication::QS60MainApplicationFactory + \since 4.6 + + This is a typedef for a pointer to a function with the following + signature: + + \snippet doc/src/snippets/code/src_corelib_global_qglobal.cpp 47 + + \sa QApplication::QApplication() +*/ + +/*! + \since 4.6 + + Creates an application using the application factory given in + \a factory, and using \a argc command line arguments in \a argv. + \a factory can be leaving, but the error will be converted to a + standard exception. + + This function is only available on S60. +*/ +QApplication::QApplication(QApplication::QS60MainApplicationFactory factory, int &argc, char **argv) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, 0x040000)) +{ + Q_D(QApplication); + S60->s60ApplicationFactory = factory; + d->construct(); +} + +QApplication::QApplication(QApplication::QS60MainApplicationFactory factory, int &argc, char **argv, int _internal) + : QCoreApplication(*new QApplicationPrivate(argc, argv, GuiClient, _internal)) +{ + Q_D(QApplication); + S60->s60ApplicationFactory = factory; + d->construct(); + QApplicationPrivate::app_compile_version = _internal; +} + +void qt_init(QApplicationPrivate * /* priv */, int) +{ + if (!CCoeEnv::Static()) { + // The S60 framework creates a new trap handler which will render any existing traps + // invalid as long as it is active. This means that all code in main() that occurs after + // the QApplication construction needs to be surrounded by a new trap, despite having + // an outer one already. To avoid this, we save the original trap handler here, and set + // it back after the S60 framework is constructed. Then we restore it right before the S60 + // framework destruction. + TTrapHandler *origTrapHandler = User::TrapHandler(); + + // The S60 framework has not been initialized. We need to do it. + TApaApplicationFactory factory(S60->s60ApplicationFactory ? + S60->s60ApplicationFactory : newS60Application); + CApaCommandLine* commandLine = q_check_ptr(QCoreApplicationPrivate::symbianCommandLine()); + if (commandLine) { + // After this construction, CEikonEnv will be available from CEikonEnv::Static(). + // (much like our qApp). + QtEikonEnv* coe = new QtEikonEnv; + //not using QT_TRAP_THROWING, because coe owns the cleanupstack so it can't be pushed there. + TRAPD(err, coe->ConstructAppFromCommandLineL(factory, *commandLine)); + if(err != KErrNone) { + qWarning() << "qt_init: Eikon application construct failed (" + << err + << "), maybe missing resource file on S60 3.1?"; + delete coe; + qt_symbian_throwIfError(err); + } + } + + S60->s60InstalledTrapHandler = User::SetTrapHandler(origTrapHandler); + + S60->qtOwnsS60Environment = true; + } else { + S60->qtOwnsS60Environment = false; + } + +#ifdef QT_NO_DEBUG + if (!qgetenv("QT_S60_AUTO_FLUSH_WSERV").isEmpty()) +#endif + S60->wsSession().SetAutoFlush(ETrue); + +#ifdef Q_SYMBIAN_WINDOW_SIZE_CACHE + TRAP_IGNORE(S60->wsSession().EnableWindowSizeCacheL()); +#endif + + S60->updateScreenSize(); + + + TDisplayMode mode = S60->screenDevice()->DisplayMode(); + S60->screenDepth = TDisplayModeUtils::NumDisplayModeBitsPerPixel(mode); + + //NB: RWsSession::GetColorModeList tells you what window modes are supported, + //not what bitmap formats. + if(QSysInfo::symbianVersion() == QSysInfo::SV_9_2) + S60->supportsPremultipliedAlpha = 0; + else + S60->supportsPremultipliedAlpha = 1; + + RProcess me; + TSecureId securId = me.SecureId(); + S60->uid = securId.operator TUid(); + + // enable focus events - used to re-enable mouse after focus changed between mouse and non mouse app, + // and for dimming behind modal windows + S60->windowGroup().EnableFocusChangeEvents(); + + //Check if mouse interaction is supported (either EMouse=1 in the HAL, or EMachineUID is one of the phones known to support this) + const TInt KMachineUidSamsungI8510 = 0x2000C51E; + // HAL::Get(HALData::EPen, TInt& result) may set 'result' to 1 on some 3.1 systems (e.g. N95). + // But we know that S60 systems below 5.0 did not support touch. + static const bool touchIsUnsupportedOnSystem = + QSysInfo::s60Version() == QSysInfo::SV_S60_3_1 + || QSysInfo::s60Version() == QSysInfo::SV_S60_3_2; + TInt machineUID; + TInt mouse; + TInt touch; + TInt err; + err = HAL::Get(HALData::EMouse, mouse); + if (err != KErrNone) + mouse = 0; + err = HAL::Get(HALData::EMachineUid, machineUID); + if (err != KErrNone) + machineUID = 0; + err = HAL::Get(HALData::EPen, touch); + if (err != KErrNone || touchIsUnsupportedOnSystem) + touch = 0; +#ifdef __WINS__ + if(QSysInfo::symbianVersion() <= QSysInfo::SV_9_4) { + //for symbian SDK emulator, force values to match typical devices. + mouse = 0; + touch = touchIsUnsupportedOnSystem ? 0 : 1; + } +#endif + if (mouse || machineUID == KMachineUidSamsungI8510) { + S60->hasTouchscreen = false; + S60->virtualMouseRequired = false; + } + else if (!touch) { + S60->hasTouchscreen = false; + S60->virtualMouseRequired = true; + } + else { + S60->hasTouchscreen = true; + S60->virtualMouseRequired = false; + } + + S60->avkonComponentsSupportTransparency = false; + S60->menuBeingConstructed = false; + +#ifdef Q_WS_S60 + TUid KCRUidAvkon = { 0x101F876E }; + TUint32 KAknAvkonTransparencyEnabled = 0x0000000D; + + CRepository* repository = 0; + TRAP(err, repository = CRepository::NewL(KCRUidAvkon)); + + if(err == KErrNone) { + TInt value = 0; + err = repository->Get(KAknAvkonTransparencyEnabled, value); + if(err == KErrNone) { + S60->avkonComponentsSupportTransparency = (value==1) ? true : false; + } + } + delete repository; + repository = 0; +#endif + + qt_keymapper_private()->updateInputLanguage(); + +#ifdef QT_KEYPAD_NAVIGATION + if (touch) { + QApplicationPrivate::navigationMode = Qt::NavigationModeNone; + } else { + QApplicationPrivate::navigationMode = Qt::NavigationModeKeypadDirectional; + } +#endif + +#ifndef QT_NO_CURSOR + //Check if window server pointer cursors are supported or not +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + //In generic binary, use the HAL and OS version + //Any other known good phones should be added here. + if (machineUID == KMachineUidSamsungI8510 || (QSysInfo::symbianVersion() != QSysInfo::SV_9_4 + && QSysInfo::symbianVersion() != QSysInfo::SV_9_3 && QSysInfo::symbianVersion() + != QSysInfo::SV_9_2)) { + S60->brokenPointerCursors = false; + qt_symbian_setWindowGroupCursor(Qt::ArrowCursor, S60->windowGroup()); + } + else + S60->brokenPointerCursors = true; +#endif + + if (S60->mouseInteractionEnabled) { +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) { + qt_symbian_set_pointer_sprite(Qt::ArrowCursor); + qt_symbian_show_pointer_sprite(); + } + else +#endif + S60->wsSession().SetPointerCursorMode(EPointerCursorNormal); + } +#endif + + QFont systemFont; + systemFont.setFamily(systemFont.defaultFamily()); + QApplicationPrivate::setSystemFont(systemFont); + + QObject::connect(qApp, SIGNAL(aboutToQuit()), qApp, SLOT(_q_aboutToQuit())); + +#ifdef Q_SYMBIAN_SEMITRANSPARENT_BG_SURFACE + QApplicationPrivate::instance()->useTranslucentEGLSurfaces = true; + + const TUid KIvePropertyCat = {0x2726beef}; + enum TIvePropertyChipType { + EVCBCM2727B1 = 0x00000000, + EVCBCM2763A0 = 0x04000100, + EVCBCM2763B0 = 0x04000102, + EVCBCM2763C0 = 0x04000103, + EVCBCM2763C1 = 0x04000104, + EVCBCMUnknown = 0x7fffffff + }; + + TInt chipType = EVCBCMUnknown; + if (RProperty::Get(KIvePropertyCat, 0 /*chip type*/, chipType) == KErrNone) { + if (chipType == EVCBCM2727B1) { + // We have only 32MB GPU memory. Use raster surfaces + // for transparent TLWs. + QApplicationPrivate::instance()->useTranslucentEGLSurfaces = false; + } + } else { + QApplicationPrivate::instance()->useTranslucentEGLSurfaces = false; + } + if (QApplicationPrivate::graphics_system_name == QLatin1String("raster")) + QApplicationPrivate::instance()->useTranslucentEGLSurfaces = false; +#else + QApplicationPrivate::instance()->useTranslucentEGLSurfaces = false; +#endif +/* + ### Commented out for now as parameter handling not needed in SOS(yet). Code below will break testlib with -o flag + int argc = priv->argc; + char **argv = priv->argv; + + // Get command line params + int j = argc ? 1 : 0; + for (int i=1; i("WId"); +} + +#ifdef QT_NO_FREETYPE +extern void qt_cleanup_symbianFontDatabase(); // qfontdatabase_s60.cpp +#endif + +/***************************************************************************** + qt_cleanup() - cleans up when the application is finished + *****************************************************************************/ +void qt_cleanup() +{ +#ifdef Q_WS_S60 + S60->setButtonGroupContainer(0); +#endif + if(qt_S60Beep) { + delete qt_S60Beep; + qt_S60Beep = 0; + } + QFontCache::cleanup(); // Has to happen now, since QFontEngineS60 has FBS handles + QPixmapCache::clear(); // Has to happen now, since QS60PixmapData has FBS handles + +#ifdef QT_NO_FREETYPE + qt_cleanup_symbianFontDatabase(); +#endif +// S60 structure and window server session are freed in eventdispatcher destructor as they are needed there + + // It's important that this happens here, before the event dispatcher gets + // deleted, because the input context needs the event loop one last time before + // it dies. + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = 0; + + //Change mouse pointer back + S60->wsSession().SetPointerCursorMode(EPointerCursorNone); + +#ifdef Q_WS_S60 + // Clear CBA + CEikonEnv::Static()->AppUiFactory()->SwapButtonGroup(0); + delete S60->buttonGroupContainer(); + S60->setButtonGroupContainer(0); +#endif + + // Call EndFullScreen() to prevent confusing the system effect state machine. + qt_endFullScreenEffect(); + + if (S60->qtOwnsS60Environment) { + // Restore the S60 framework trap handler. See qt_init(). + User::SetTrapHandler(S60->s60InstalledTrapHandler); + + CEikonEnv* coe = CEikonEnv::Static(); + coe->PrepareToExit(); + // The CEikonEnv itself is destroyed in here. + coe->DestroyEnvironment(); + } +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ + // TODO: Implement QApplicationPrivate::initializeWidgetPaletteHash() + // Possibly a task fot the S60Style guys +} + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); + eventDispatcher = new QEventDispatcherS60(q); +} + +QString QApplicationPrivate::appName() const +{ + return QCoreApplicationPrivate::appName(); +} + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ +#ifdef Q_SYMBIAN_TRANSITION_EFFECTS + S60->wsSession().SendEffectCommand(ETfxCmdAppModalModeEnter); +#endif + if (widget) { + static_cast(widget->effectiveWinId())->FadeBehindPopup(ETrue); + // Modal partial screen dialogs (like queries) capture pointer events. + // ### FixMe: Add specialized behaviour for fullscreen modal dialogs + widget->effectiveWinId()->SetGloballyCapturing(ETrue); + widget->effectiveWinId()->SetPointerCapture(ETrue); + } + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + qt_modal_stack->insert(0, widget); + app_do_modal = true; +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget) +{ +#ifdef Q_SYMBIAN_TRANSITION_EFFECTS + S60->wsSession().SendEffectCommand(ETfxCmdAppModalModeExit); +#endif + if (widget) { + static_cast(widget->effectiveWinId())->FadeBehindPopup(EFalse); + // ### FixMe: Add specialized behaviour for fullscreen modal dialogs + widget->effectiveWinId()->SetGloballyCapturing(EFalse); + widget->effectiveWinId()->SetPointerCapture(EFalse); + } + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + } + } + app_do_modal = qt_modal_stack != 0; +} + +void QApplicationPrivate::openPopup(QWidget *popup) +{ + if (popup && qobject_cast(popup->parentWidget())) + static_cast(popup->effectiveWinId())->FadeBehindPopup(ETrue); + + if (!QApplicationPrivate::popupWidgets) + QApplicationPrivate::popupWidgets = new QWidgetList; + QApplicationPrivate::popupWidgets->append(popup); + + // Cancel focus widget pointer capture and long tap timer + if (QApplication::focusWidget()) { + static_cast(QApplication::focusWidget()->effectiveWinId())->CancelLongTapTimer(); + QApplication::focusWidget()->effectiveWinId()->SetPointerCapture(false); + } + + if (!qt_nograb()) { + // Cancel pointer capture and long tap timer for earlier popup + int popupCount = QApplicationPrivate::popupWidgets->count(); + if (popupCount > 1) { + QWidget* prevPopup = QApplicationPrivate::popupWidgets->at(popupCount-2); + static_cast(prevPopup->effectiveWinId())->CancelLongTapTimer(); + prevPopup->effectiveWinId()->SetPointerCapture(false); + } + + // Enable pointer capture for this (topmost) popup + Q_ASSERT(popup->testAttribute(Qt::WA_WState_Created)); + WId id = popup->effectiveWinId(); + id->SetPointerCapture(true); + } + + // popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + QWidget *fw = popup->focusWidget(); + if (fw) { + fw->setFocus(Qt::PopupFocusReason); + } else if (QApplicationPrivate::popupWidgets->count() == 1) { // this was the first popup + fw = QApplication::focusWidget(); + if (fw) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + q_func()->sendEvent(fw, &e); + } + } +} + +void QApplicationPrivate::closePopup(QWidget *popup) +{ + if (popup && qobject_cast(popup->parentWidget())) + static_cast(popup->effectiveWinId())->FadeBehindPopup(EFalse); + + if (!QApplicationPrivate::popupWidgets) + return; + QApplicationPrivate::popupWidgets->removeAll(popup); + + // Cancel pointer capture and long tap for this popup + WId id = popup->effectiveWinId(); + id->SetPointerCapture(false); + static_cast(id)->CancelLongTapTimer(); + + if (QApplicationPrivate::popupWidgets->isEmpty()) { // this was the last popup + delete QApplicationPrivate::popupWidgets; + QApplicationPrivate::popupWidgets = 0; + if (!qt_nograb()) { // grabbing not disabled + Q_ASSERT(popup->testAttribute(Qt::WA_WState_Created)); + if (QWidgetPrivate::mouseGrabber != 0) + QWidgetPrivate::mouseGrabber->grabMouse(); + + if (QWidgetPrivate::keyboardGrabber != 0) + QWidgetPrivate::keyboardGrabber->grabKeyboard(); + + QWidget *fw = QApplicationPrivate::active_window ? QApplicationPrivate::active_window->focusWidget() + : q_func()->focusWidget(); + if (fw) { + if(fw->window()->isModal()) // restore pointer capture for modal window + fw->effectiveWinId()->SetPointerCapture(true); + + if (fw != q_func()->focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + q_func()->sendEvent(fw, &e); + } + } + } + } else { + + // popups are not focus-handled by the window system (the + // first popup grabbed the keyboard), so we have to do that + // manually: A popup was closed, so the previous popup gets + // the focus. + QWidget* aw = QApplicationPrivate::popupWidgets->last(); + if (QWidget *fw = QApplication::focusWidget()) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + q_func()->sendEvent(fw, &e); + } + + // Enable pointer capture for previous popup + if (aw) { + aw->effectiveWinId()->SetPointerCapture(true); + } + } +} + +QWidget * QApplication::topLevelAt(QPoint const& point) +{ + QWidget *found = 0; + int lowestZ = INT_MAX; + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; i < list.count(); ++i) { + QWidget *widget = list.at(i); + if (widget->isVisible() && !(widget->windowType() == Qt::Desktop)) { + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + if (widget->geometry().adjusted(0,0,1,1).contains(point)) { + // At this point we know there is a Qt widget under the point. + // Now we need to make sure it is the top most in the z-order. + RDrawableWindow *const window = widget->effectiveWinId()->DrawableWindow(); + int z = window->OrdinalPosition(); + if (z < lowestZ) { + lowestZ = z; + found = widget; + } + } + } + } + return found; +} + +void QApplication::alert(QWidget * /* widget */, int /* duration */) +{ + // TODO: Implement QApplication::alert(QWidget *widget, int duration) +} + +int QApplication::doubleClickInterval() +{ + TTimeIntervalMicroSeconds32 us; + TInt distance; + S60->wsSession().GetDoubleClickSettings(us, distance); + return (us.Int() / 1000); +} + +void QApplication::setDoubleClickInterval(int ms) +{ + TTimeIntervalMicroSeconds32 newUs( ms * 1000); + TTimeIntervalMicroSeconds32 us; + TInt distance; + S60->wsSession().GetDoubleClickSettings(us, distance); + if (us != newUs) + S60->wsSession().SetDoubleClick(newUs, distance); +} + +int QApplication::keyboardInputInterval() +{ + return QApplicationPrivate::keyboard_input_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + +void QApplication::beep() +{ + if (!qt_S60Beep) { + TInt frequency = 880; + TTimeIntervalMicroSeconds duration(500000); + TRAP_IGNORE(qt_S60Beep=QS60Beep::NewL(frequency, duration)); + } + if (qt_S60Beep) + qt_S60Beep->Play(); +} + +static inline bool callSymbianEventFilters(const QSymbianEvent *event) +{ + long unused; + return qApp->filterEvent(const_cast(event), &unused); +} + +/*! + \warning This function is only available on Symbian. + \since 4.6 + + This function processes an individual Symbian event + \a event. It returns 1 if the event was handled, 0 if + the \a event was not handled, and -1 if the event was + not handled because the event is not known to Qt. + */ + +int QApplication::symbianProcessEvent(const QSymbianEvent *event) +{ + Q_D(QApplication); + + QScopedLoopLevelCounter counter(d->threadData); + + if (d->eventDispatcher->filterEvent(const_cast(event))) + return 1; + + QWidget *w = qApp ? qApp->focusWidget() : 0; + if (w) { + QInputContext *ic = w->inputContext(); + if (ic && ic->symbianFilterEvent(w, event)) + return 1; + } + + if (symbianEventFilter(event)) + return 1; + + switch (event->type()) { + case QSymbianEvent::WindowServerEvent: + return d->symbianProcessWsEvent(event); + case QSymbianEvent::CommandEvent: + return d->symbianHandleCommand(event); + case QSymbianEvent::ResourceChangeEvent: + return d->symbianResourceChange(event); + default: + return -1; + } +} + +int QApplicationPrivate::symbianProcessWsEvent(const QSymbianEvent *symbianEvent) +{ + // Qt event handling. Handle some events regardless of if the handle is in our + // widget map or not. + const TWsEvent *event = symbianEvent->windowServerEvent(); + CCoeControl* control = reinterpret_cast(event->Handle()); + const bool controlInMap = QWidgetPrivate::mapper && QWidgetPrivate::mapper->contains(control); + switch (event->Type()) { + case EEventPointerEnter: + if (controlInMap) { + callSymbianEventFilters(symbianEvent); + return 1; // Qt::Enter will be generated in HandlePointerL + } + break; + case EEventPointerExit: + if (controlInMap) { + if (callSymbianEventFilters(symbianEvent)) + return 1; + if (S60) { + // mouseEvent outside our window, send leave event to last focused widget + QMouseEvent mEvent(QEvent::Leave, S60->lastPointerEventPos, S60->lastCursorPos, + Qt::NoButton, QApplicationPrivate::mouse_buttons, Qt::NoModifier); + if (S60->lastPointerEventTarget) + qt_sendSpontaneousEvent(S60->lastPointerEventTarget,&mEvent); + S60->lastPointerEventTarget = 0; + } + return 1; + } + break; + case EEventScreenDeviceChanged: // fallthrough +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + case EEventDisplayChanged: +#endif + if (callSymbianEventFilters(symbianEvent)) + return 1; + if (S60) + S60->updateScreenSize(); + if (qt_desktopWidget) { + QSize oldSize = qt_desktopWidget->size(); + qt_desktopWidget->data->crect.setWidth(S60->screenWidthInPixels); + qt_desktopWidget->data->crect.setHeight(S60->screenHeightInPixels); + QResizeEvent e(qt_desktopWidget->size(), oldSize); + QApplication::sendEvent(qt_desktopWidget, &e); + } + return 0; // Propagate to CONE + case EEventWindowVisibilityChanged: + if (controlInMap) { + if (callSymbianEventFilters(symbianEvent)) + return 1; + const TWsVisibilityChangedEvent *visChangedEvent = event->VisibilityChanged(); + if (visChangedEvent->iFlags & TWsVisibilityChangedEvent::ENotVisible) + S60->controlVisibilityChanged(control, false); + else if (visChangedEvent->iFlags & TWsVisibilityChangedEvent::EPartiallyVisible) + S60->controlVisibilityChanged(control, true); + return 1; + } + break; + case EEventFocusGained: + if (callSymbianEventFilters(symbianEvent)) + return 1; +#ifndef QT_NO_CURSOR + //re-enable mouse interaction + if (S60->mouseInteractionEnabled) { +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) + qt_symbian_show_pointer_sprite(); + else +#endif + S60->wsSession().SetPointerCursorMode(EPointerCursorNormal); + } +#endif +#ifdef QT_SOFTKEYS_ENABLED + if (!CEikonEnv::Static()->EikAppUi()->IsDisplayingMenuOrDialog()) + QSoftKeyManager::updateSoftKeys(); +#endif + break; + case EEventFocusLost: + if (callSymbianEventFilters(symbianEvent)) + return 1; +#ifndef QT_NO_CURSOR + //disable mouse as may be moving to application that does not support it + if (S60->mouseInteractionEnabled) { +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) + qt_symbian_hide_pointer_sprite(); + else +#endif + S60->wsSession().SetPointerCursorMode(EPointerCursorNone); + } +#endif + break; + case KGoomMemoryLowEvent: +#ifdef QT_DEBUG + qDebug() << "QApplicationPrivate::symbianProcessWsEvent - KGoomMemoryLowEvent"; +#endif + if (callSymbianEventFilters(symbianEvent)) + return 1; +#ifdef QT_GRAPHICSSYSTEM_RUNTIME + if(QApplicationPrivate::runtime_graphics_system) { + bool switchToSwRendering(false); + + foreach (QWidget *w, QApplication::topLevelWidgets()) { + if(w->d_func()->topData()->backingStore) { + switchToSwRendering = true; + break; + } + } + + if (switchToSwRendering) { + QRuntimeGraphicsSystem *gs = + static_cast(QApplicationPrivate::graphics_system); + gs->setGraphicsSystem(QLatin1String("raster")); + } + } +#endif + break; + case KGoomMemoryGoodEvent: +#ifdef QT_DEBUG + qDebug() << "QApplicationPrivate::symbianProcessWsEvent - KGoomMemoryGoodEvent"; +#endif + if (callSymbianEventFilters(symbianEvent)) + return 1; +#ifdef QT_GRAPHICSSYSTEM_RUNTIME + if(QApplicationPrivate::runtime_graphics_system) { + QRuntimeGraphicsSystem *gs = + static_cast(QApplicationPrivate::graphics_system); + gs->setGraphicsSystem(QLatin1String("openvg")); + } +#endif + break; +#ifdef Q_SYMBIAN_SUPPORTS_SURFACES + case EEventUser: + { + // GOOM is looking for candidates to kill so indicate that we are + // capable of cleaning up by handling this event + TInt32 *data = reinterpret_cast(event->EventData()); + if (data[0] == EApaSystemEventShutdown && data[1] == KGoomMemoryLowEvent) + return 1; + } + break; +#endif + +#ifdef Q_WS_S60 + case KEikInputLanguageChange: + qt_keymapper_private()->updateInputLanguage(); + break; +#endif + + default: + break; + } + + if (!controlInMap) + return -1; + + return 0; +} + +/*! + \warning This virtual function is only available on Symbian. + \since 4.6 + + If you create an application that inherits QApplication and reimplement + this function, you get direct access to events that the are received + from Symbian. The events are passed in the \a event parameter. + + Return true if you want to stop the event from being processed. Return + false for normal event dispatching. The default implementation returns + false, and does nothing with \a event. + */ +bool QApplication::symbianEventFilter(const QSymbianEvent *event) +{ + Q_UNUSED(event); + return false; +} + +/*! + \warning This function is only available on Symbian. + \since 4.6 + + Handles \a{command}s which are typically handled by + CAknAppUi::HandleCommandL(). Qts Ui integration into Symbian is + partially achieved by deriving from CAknAppUi. Currently, exit, + menu and softkey commands are handled. + + \sa s60EventFilter(), s60ProcessEvent() +*/ +int QApplicationPrivate::symbianHandleCommand(const QSymbianEvent *symbianEvent) +{ + Q_Q(QApplication); + int ret = 0; + + if (callSymbianEventFilters(symbianEvent)) + return 1; + + int command = symbianEvent->command(); + + switch (command) { +#ifdef Q_WS_S60 + case EAknSoftkeyExit: { + QCloseEvent ev; + QApplication::sendSpontaneousEvent(q, &ev); + if (ev.isAccepted()) { + q->quit(); + ret = 1; + } + break; + } +#endif + case EEikCmdExit: + q->quit(); + ret = 1; + break; + default: +#ifdef Q_WS_S60 + bool handled = QSoftKeyManager::handleCommand(command); + if (handled) + ret = 1; + else + ret = QMenuBarPrivate::symbianCommands(command); +#endif + break; + } + + return ret; +} + +/*! + \warning This function is only available on Symbian. + \since 4.6 + + Handles the resource change specified by \a type. + + Currently, KEikDynamicLayoutVariantSwitch and + KAknsMessageSkinChange are handled. + */ +int QApplicationPrivate::symbianResourceChange(const QSymbianEvent *symbianEvent) +{ + int ret = 0; + + int type = symbianEvent->resourceChangeType(); + + switch (type) { +#ifdef Q_WS_S60 + case KEikDynamicLayoutVariantSwitch: + { + if (callSymbianEventFilters(symbianEvent)) + return 1; + if (S60) + S60->updateScreenSize(); + +#ifndef QT_NO_STYLE_S60 + QS60Style *s60Style = 0; + +#ifndef QT_NO_STYLE_STYLESHEET + QStyleSheetStyle *proxy = qobject_cast(QApplication::style()); + if (proxy) + s60Style = qobject_cast(proxy->baseStyle()); + else +#endif + s60Style = qobject_cast(QApplication::style()); + + if (s60Style) { + s60Style->d_func()->handleDynamicLayoutVariantSwitch(); + ret = 1; + } +#endif + } + break; + +#ifndef QT_NO_STYLE_S60 + case KAknsMessageSkinChange: + if (callSymbianEventFilters(symbianEvent)) + return 1; + if (QS60Style *s60Style = qobject_cast(QApplication::style())) { + s60Style->d_func()->handleSkinChange(); + ret = 1; + } + break; +#endif +#endif // Q_WS_S60 + default: + break; + } + + return ret; +} + +#ifndef QT_NO_WHEELEVENT +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} + +void QApplication::setWheelScrollLines(int n) +{ + QApplicationPrivate::wheel_scroll_lines = n; +} +#endif //QT_NO_WHEELEVENT + +bool QApplication::isEffectEnabled(Qt::UIEffect /* effect */) +{ + // TODO: Implement QApplication::isEffectEnabled(Qt::UIEffect effect) + return false; +} + +void QApplication::setEffectEnabled(Qt::UIEffect /* effect */, bool /* enable */) +{ + // TODO: Implement QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +} + +TUint QApplicationPrivate::resolveS60ScanCode(TInt scanCode, TUint keysym) +{ + if (!scanCode) + return keysym; + + QApplicationPrivate *d = QApplicationPrivate::instance(); + + if (keysym) { + // If keysym is specified, cache it. + d->scanCodeCache.insert(scanCode, keysym); + return keysym; + } else { + // If not, retrieve the cached version. + return d->scanCodeCache[scanCode]; + } +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + if (HAL::Get(HALData::EPointer3DPressureSupported, pressureSupported) != KErrNone) + pressureSupported = 0; + if (HAL::Get(HALData::EPointer3DMaxPressure, maxTouchPressure) != KErrNone) + maxTouchPressure = KMaxTInt; +#else + pressureSupported = 0; + maxTouchPressure = KMaxTInt; +#endif +} + +void QApplicationPrivate::cleanupMultitouch_sys() +{ } + +#ifndef QT_NO_SESSIONMANAGER +QSessionManager::QSessionManager(QApplication * /* app */, QString & /* id */, QString& /* key */) +{ + +} + +QSessionManager::~QSessionManager() +{ + +} + +bool QSessionManager::allowsInteraction() +{ + return false; +} + +void QSessionManager::cancel() +{ + +} +#endif //QT_NO_SESSIONMANAGER + +#ifdef QT_KEYPAD_NAVIGATION +/* + * Show/Hide the mouse cursor depending on phone type and chosen mode + */ +void QApplicationPrivate::setNavigationMode(Qt::NavigationMode mode) +{ +#ifndef QT_NO_CURSOR + const bool wasCursorOn = (QApplicationPrivate::navigationMode == Qt::NavigationModeCursorAuto + && !S60->hasTouchscreen) + || QApplicationPrivate::navigationMode == Qt::NavigationModeCursorForceVisible; + const bool isCursorOn = (mode == Qt::NavigationModeCursorAuto + && !S60->hasTouchscreen) + || mode == Qt::NavigationModeCursorForceVisible; + + if (!wasCursorOn && isCursorOn) { + //Show the cursor, when changing from another mode to cursor mode + qt_symbian_set_cursor_visible(true); + } + else if (wasCursorOn && !isCursorOn) { + //Hide the cursor, when leaving cursor mode + qt_symbian_set_cursor_visible(false); + } +#endif + QApplicationPrivate::navigationMode = mode; +} +#endif + +#ifndef QT_NO_CURSOR +/***************************************************************************** + QApplication cursor stack + *****************************************************************************/ + +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + qt_symbian_setGlobalCursor(cursor); +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + + if (!qApp->d_func()->cursor_list.isEmpty()) { + qt_symbian_setGlobalCursor(qApp->d_func()->cursor_list.first()); + } + else { + //determine which widget has focus + QWidget *w = QApplication::widgetAt(QCursor::pos()); +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) { + qt_symbian_set_pointer_sprite(w ? w->cursor() : Qt::ArrowCursor); + } + else +#endif + { + //because of the internals of window server, we need to force the cursor + //to be set in all child windows too, otherwise when the cursor is over + //the child window it may show a widget cursor or arrow cursor instead, + //depending on construction order. + QListIterator iter(QWidgetPrivate::mapper->uniqueKeys()); + while (iter.hasNext()) { + CCoeControl *ctrl = iter.next(); + if(ctrl->OwnsWindow()) { + ctrl->DrawableWindow()->ClearPointerCursor(); + } + } + if (w) + qt_symbian_setWindowCursor(w->cursor(), w->effectiveWinId()); + else + qt_symbian_setWindowGroupCursor(Qt::ArrowCursor, S60->windowGroup()); + } + } +} + +#endif // QT_NO_CURSOR + +void QApplicationPrivate::_q_aboutToQuit() +{ + qt_beginFullScreenEffect(); + +#ifdef Q_SYMBIAN_TRANSITION_EFFECTS + // Send the shutdown tfx command + S60->wsSession().SendEffectCommand(ETfxCmdAppShutDown); +#endif +} + +QS60ThreadLocalData::QS60ThreadLocalData() +{ + CCoeEnv *env = CCoeEnv::Static(); + if (env) { + //if this is the UI thread, share objects owned by CONE + usingCONEinstances = true; + wsSession = env->WsSession(); + screenDevice = env->ScreenDevice(); + } + else { + usingCONEinstances = false; + qt_symbian_throwIfError(wsSession.Connect(qt_s60GetRFs())); + screenDevice = new CWsScreenDevice(wsSession); + screenDevice->Construct(); + } +} + +QS60ThreadLocalData::~QS60ThreadLocalData() +{ + if (!usingCONEinstances) { + delete screenDevice; + wsSession.Close(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qapplication_win.cpp b/src/gui/kernel/qapplication_win.cpp new file mode 100644 index 0000000000..0a03397251 --- /dev/null +++ b/src/gui/kernel/qapplication_win.cpp @@ -0,0 +1,4232 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef Q_WS_WINCE +#include "qguifunctions_wince.h" +#include "qmenubar.h" +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_high_dpi(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_smartphone(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_pocket_pc(); //defined in qguifunctions_wince.cpp +extern void qt_wince_hide_taskbar(HWND hwnd); //defined in qguifunctions_wince.cpp +#endif +#ifdef Q_WS_WINCE_WM +#include +#include +#ifdef QT_WINCE_GESTURES +#ifndef QT_NO_GESTURES +#include +#endif +#endif +#endif + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "private/qeventdispatcher_win_p.h" +#include "qeventloop.h" +#include "qclipboard.h" +#include "qcursor.h" +#include "qdatetime.h" +#include "qpointer.h" +#include "qhash.h" +#include "qmetaobject.h" +#include "qmime.h" +#include "qpainter.h" +#include "qpixmapcache.h" +#include "qsessionmanager.h" +#include "qstyle.h" +#include "qwhatsthis.h" // ######## dependency +#include "qwidget.h" +#include "qcolormap.h" +#include "qlayout.h" +#include "qtooltip.h" +#include "qt_windows.h" +#include "qscrollbar.h" +#if defined(QT_NON_COMMERCIAL) +#include "qnc_win.h" +#endif +#include "private/qwininputcontext_p.h" +#include "private/qcursor_p.h" +#include "private/qmath_p.h" +#include "private/qapplication_p.h" +#include "private/qbackingstore_p.h" +#include "private/qwindowsurface_raster_p.h" +#include "qdebug.h" +#include +#include +#include +#include "qevent_p.h" + +//#define ALIEN_DEBUG + +#ifndef QT_NO_THREAD +#include "qmutex.h" +#endif + +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" + +#include +#ifndef WM_GETOBJECT +#define WM_GETOBJECT 0x003D +#endif +#endif // QT_NO_ACCESSIBILITY + +#if !defined(WINABLEAPI) +# if defined(Q_WS_WINCE) +# include +# endif +# if !defined(Q_WS_WINCE) +# include +# endif +#endif + +#ifndef QT_NO_GESTURES +# ifndef GID_ZOOM +# define GID_ZOOM 3 +# define GID_TWOFINGERTAP 6 +# define GID_PRESSANDTAP 7 +# define GID_ROLLOVER GID_PRESSANDTAP +# endif +#endif + +#ifndef WM_TOUCH +# define WM_TOUCH 0x0240 +#endif + +#ifndef TOUCHEVENTF_MOVE +# define TOUCHEVENTF_MOVE 0x0001 +# define TOUCHEVENTF_DOWN 0x0002 +# define TOUCHEVENTF_UP 0x0004 +# define TOUCHEVENTF_INRANGE 0x0008 +# define TOUCHEVENTF_PRIMARY 0x0010 +# define TOUCHEVENTF_NOCOALESCE 0x0020 +# define TOUCHEVENTF_PEN 0x0040 +# define TOUCHEVENTF_PALM 0x0080 + +# define TOUCHINPUTMASKF_TIMEFROMSYSTEM 0x0001 +# define TOUCHINPUTMASKF_EXTRAINFO 0x0002 +# define TOUCHINPUTMASKF_CONTACTAREA 0x0004 + +typedef struct tagTOUCHINPUT +{ + LONG x; + LONG y; + HANDLE hSource; + DWORD dwID; + DWORD dwFlags; + DWORD dwMask; + DWORD dwTime; + ULONG_PTR dwExtraInfo; + DWORD cxContact; + DWORD cyContact; +} TOUCHINPUT, *PTOUCHINPUT; + +#endif + +#include +#include +#include +#include +#include +#include + +#define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE \ + | PK_ORIENTATION | PK_CURSOR | PK_Z) +#define PACKETMODE 0 + +#include +#ifndef CSR_TYPE +#define CSR_TYPE 20 // Some old Wacom wintab.h may not provide this constant. +#endif +#include + +#if defined(__CYGWIN32__) +#define __INSIDE_CYGWIN32__ +#include +#endif + +#ifndef IMR_RECONVERTSTRING +#define IMR_RECONVERTSTRING 4 +#endif + +#ifndef IMR_CONFIRMRECONVERTSTRING +#define IMR_CONFIRMRECONVERTSTRING 0x0005 +#endif +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_WINCE +#ifndef SHRG_RETURNCMD +struct SHRGINFO { + DWORD cbSize; + HWND hwndClient; + POINT ptDown; + DWORD dwFlags; +}; +#define GN_CONTEXTMENU 1000 +#define SHRG_RETURNCMD 0x00000001 +#define SHRG_NOANIMATION 0x00000010 +#endif + +#ifndef SPI_SETSIPINFO +#define SPI_SETSIPINFO 224 +#endif + +#ifndef QT_NO_GESTURES +typedef DWORD (API *AygRecognizeGesture)(SHRGINFO*); +static AygRecognizeGesture ptrRecognizeGesture = 0; +static bool aygResolved = false; +static void resolveAygLibs() +{ + if (!aygResolved) { + aygResolved = true; + QSystemLibrary ayglib(QLatin1String("aygshell")); + ptrRecognizeGesture = (AygRecognizeGesture) ayglib.resolve("SHRecognizeGesture"); + } +} +#endif // QT_NO_GESTURES + +#endif + +#ifndef SPI_SETFONTSMOOTHINGTYPE +# define SPI_SETFONTSMOOTHINGTYPE 0x200B +#endif +#ifndef SPI_GETFONTSMOOTHINGTYPE +# define SPI_GETFONTSMOOTHINGTYPE 0x200A +#endif +#ifndef FE_FONTSMOOTHINGCLEARTYPE +# define FE_FONTSMOOTHINGCLEARTYPE 0x0002 +#endif + +Q_GUI_EXPORT bool qt_cleartype_enabled; +Q_GUI_EXPORT bool qt_win_owndc_required; // CS_OWNDC is required if we use the GL graphicssystem as default + +typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL); +typedef BOOL (API *PtrWTClose)(HCTX); +typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID); +typedef BOOL (API *PtrWTEnable)(HCTX, BOOL); +typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL); +typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID); +typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT); +typedef int (API *PtrWTQueueSizeGet)(HCTX); +typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int); + +static PtrWTInfo ptrWTInfo = 0; +static PtrWTEnable ptrWTEnable = 0; +static PtrWTOverlap ptrWTOverlap = 0; +static PtrWTPacketsGet ptrWTPacketsGet = 0; +static PtrWTGet ptrWTGet = 0; + +static PACKET localPacketBuf[QT_TABLET_NPACKETQSIZE]; // our own tablet packet queue. +HCTX qt_tablet_context; // the hardware context for the tablet (like a window handle) +bool qt_tablet_tilt_support; + +#ifndef QT_NO_TABLETEVENT +static void tabletInit(const quint64 uniqueId, const UINT csr_type, HCTX hTab); +static void tabletUpdateCursor(QTabletDeviceData &tdd, const UINT currentCursor); +static void initWinTabFunctions(); // resolve the WINTAB api functions +#endif // QT_NO_TABLETEVENT + + +#ifndef QT_NO_ACCESSIBILITY +extern IAccessible *qt_createWindowsAccessible(QAccessibleInterface *object); +#endif // QT_NO_ACCESSIBILITY + +extern bool qt_tabletChokeMouse; +extern QWidget* qt_get_tablet_widget(); +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); +extern QRegion qt_dirtyRegion(QWidget *); + +typedef QHash QTabletCursorInfo; +Q_GLOBAL_STATIC(QTabletCursorInfo, tCursorInfo) +QTabletDeviceData currentTabletPointer; + +// from qregion_win.cpp +extern HRGN qt_tryCreateRegion(QRegion::RegionType type, int left, int top, int right, int bottom); + +// support for on-the-fly changes of the XP theme engine +#ifndef WM_THEMECHANGED +#define WM_THEMECHANGED 0x031A +#endif +#ifndef COLOR_MENUHILIGHT +#define COLOR_MENUHILIGHT 29 +#define COLOR_MENUBAR 30 +#endif + +// support for xbuttons +#ifndef WM_XBUTTONDOWN +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#endif +#ifndef GET_KEYSTATE_WPARAM +#define GET_KEYSTATE_WPARAM(wParam) (LOWORD(wParam)) +#define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#ifndef MK_XBUTTON1 +#define MK_XBUTTON1 0x0020 +#define MK_XBUTTON2 0x0040 +#endif + +// support for multi-media-keys +#ifndef WM_APPCOMMAND +#define WM_APPCOMMAND 0x0319 +#endif + +#ifndef FAPPCOMMAND_MOUSE +#define FAPPCOMMAND_MOUSE 0x8000 +#define FAPPCOMMAND_KEY 0 +#define FAPPCOMMAND_OEM 0x1000 +#define FAPPCOMMAND_MASK 0xF000 +#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK)) +#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) & FAPPCOMMAND_MASK)) +#define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM +#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam)) +#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam) + +#define APPCOMMAND_BROWSER_BACKWARD 1 +#define APPCOMMAND_BROWSER_FORWARD 2 +#define APPCOMMAND_BROWSER_REFRESH 3 +#define APPCOMMAND_BROWSER_STOP 4 +#define APPCOMMAND_BROWSER_SEARCH 5 +#define APPCOMMAND_BROWSER_FAVORITES 6 +#define APPCOMMAND_BROWSER_HOME 7 +#define APPCOMMAND_VOLUME_MUTE 8 +#define APPCOMMAND_VOLUME_DOWN 9 +#define APPCOMMAND_VOLUME_UP 10 +#define APPCOMMAND_MEDIA_NEXTTRACK 11 +#define APPCOMMAND_MEDIA_PREVIOUSTRACK 12 +#define APPCOMMAND_MEDIA_STOP 13 +#define APPCOMMAND_MEDIA_PLAY_PAUSE 14 +#define APPCOMMAND_LAUNCH_MAIL 15 +#define APPCOMMAND_LAUNCH_MEDIA_SELECT 16 +#define APPCOMMAND_LAUNCH_APP1 17 +#define APPCOMMAND_LAUNCH_APP2 18 +#define APPCOMMAND_BASS_DOWN 19 +#define APPCOMMAND_BASS_BOOST 20 +#define APPCOMMAND_BASS_UP 21 +#define APPCOMMAND_TREBLE_DOWN 22 +#define APPCOMMAND_TREBLE_UP 23 +#endif // FAPPCOMMAND_MOUSE + +// New commands from Windows XP (some even Sp1) +#ifndef APPCOMMAND_MICROPHONE_VOLUME_MUTE +#define APPCOMMAND_MICROPHONE_VOLUME_MUTE 24 +#define APPCOMMAND_MICROPHONE_VOLUME_DOWN 25 +#define APPCOMMAND_MICROPHONE_VOLUME_UP 26 +#define APPCOMMAND_HELP 27 +#define APPCOMMAND_FIND 28 +#define APPCOMMAND_NEW 29 +#define APPCOMMAND_OPEN 30 +#define APPCOMMAND_CLOSE 31 +#define APPCOMMAND_SAVE 32 +#define APPCOMMAND_PRINT 33 +#define APPCOMMAND_UNDO 34 +#define APPCOMMAND_REDO 35 +#define APPCOMMAND_COPY 36 +#define APPCOMMAND_CUT 37 +#define APPCOMMAND_PASTE 38 +#define APPCOMMAND_REPLY_TO_MAIL 39 +#define APPCOMMAND_FORWARD_MAIL 40 +#define APPCOMMAND_SEND_MAIL 41 +#define APPCOMMAND_SPELL_CHECK 42 +#define APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE 43 +#define APPCOMMAND_MIC_ON_OFF_TOGGLE 44 +#define APPCOMMAND_CORRECTION_LIST 45 +#define APPCOMMAND_MEDIA_PLAY 46 +#define APPCOMMAND_MEDIA_PAUSE 47 +#define APPCOMMAND_MEDIA_RECORD 48 +#define APPCOMMAND_MEDIA_FAST_FORWARD 49 +#define APPCOMMAND_MEDIA_REWIND 50 +#define APPCOMMAND_MEDIA_CHANNEL_UP 51 +#define APPCOMMAND_MEDIA_CHANNEL_DOWN 52 +#endif // APPCOMMAND_MICROPHONE_VOLUME_MUTE + +#if (_WIN32_WINNT < 0x0400) +// This struct is defined in winuser.h if the _WIN32_WINNT >= 0x0400 -- in the +// other cases we have to define it on our own. +typedef struct tagTRACKMOUSEEVENT { + DWORD cbSize; + DWORD dwFlags; + HWND hwndTrack; + DWORD dwHoverTime; +} TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT; +#endif +#ifndef WM_MOUSELEAVE +#define WM_MOUSELEAVE 0x02A3 +#endif + +QT_BEGIN_INCLUDE_NAMESPACE +#include "private/qwidget_p.h" +QT_END_INCLUDE_NAMESPACE + +static int translateButtonState(int s, int type, int button); + +// ##### get rid of this! +QRgb qt_colorref2qrgb(COLORREF col) +{ + return qRgb(GetRValue(col),GetGValue(col),GetBValue(col)); +} + + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ + +static HWND curWin = 0; // current window +static HDC displayDC = 0; // display device context + +// Session management +static bool sm_blockUserInput = false; +static bool sm_smActive = false; +extern QSessionManager* qt_session_manager_self; +static bool sm_cancel; + +static bool replayPopupMouseEvent = false; // replay handling when popups close + +// ignore the next release event if return from a modal widget +Q_GUI_EXPORT bool qt_win_ignoreNextMouseReleaseEvent = false; + + +#if defined(QT_DEBUG) +static bool appNoGrab = false; // mouse/keyboard grabbing +#endif + +static bool app_do_modal = false; // modal mode +extern QWidgetList *qt_modal_stack; +extern QDesktopWidget *qt_desktopWidget; +static QPointer popupButtonFocus; +static bool qt_try_modal(QWidget *, MSG *, int& ret); + +QWidget *qt_button_down = 0; // widget got last button-down +QPointer qt_last_mouse_receiver = 0; + +static HWND autoCaptureWnd = 0; +static HWND imeParentWnd = 0; +static void setAutoCapture(HWND); // automatic capture +static void releaseAutoCapture(); + +static void unregWinClasses(); + +extern QCursor *qt_grab_cursor(); + +#if defined(Q_WS_WIN) +#define __export +#endif + +extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND, UINT, WPARAM, LPARAM); + +class QETWidget : public QWidget // event translator widget +{ +public: + QWExtra *xtra() { return d_func()->extraData(); } + QTLWExtra *topData() { return d_func()->topData(); } + QTLWExtra *maybeTopData() { return d_func()->maybeTopData(); } + void syncBackingStore(const QRegion &rgn) { d_func()->syncBackingStore(rgn); } + void syncBackingStore() { d_func()->syncBackingStore(); } + QWidgetData *dataPtr() { return data; } + QWidgetPrivate *dptr() { return d_func(); } + QRect frameStrut() const { return d_func()->frameStrut(); } + bool winEvent(MSG *m, long *r) { return QWidget::winEvent(m, r); } + void markFrameStrutDirty() { data->fstrut_dirty = 1; } + bool translateMouseEvent(const MSG &msg); + bool translateWheelEvent(const MSG &msg); + bool translatePaintEvent(const MSG &msg); + bool translateConfigEvent(const MSG &msg); + bool translateCloseEvent(const MSG &msg); + bool translateTabletEvent(const MSG &msg, PACKET *localPacketBuf, int numPackets); +#ifndef QT_NO_GESTURES + bool translateGestureEvent(const MSG &msg, const GESTUREINFO &gi); +#endif + void repolishStyle(QStyle &style); + inline void showChildren(bool spontaneous) { d_func()->showChildren(spontaneous); } + inline void hideChildren(bool spontaneous) { d_func()->hideChildren(spontaneous); } + inline uint testWindowState(uint teststate){ return dataPtr()->window_state & teststate; } + inline void setWindowTitle_helper(const QString &title) { d_func()->setWindowTitle_helper(title); } + inline void forceUpdate() { + QTLWExtra *tlwExtra = window()->d_func()->maybeTopData(); + if (tlwExtra && tlwExtra->backingStore) + tlwExtra->backingStore->markDirty(rect(), this, true, true); + } +}; + +// need to get default font? +extern bool qt_app_has_font; + +extern QFont qt_LOGFONTtoQFont(LOGFONT& lf,bool scale); + +static void qt_set_windows_color_resources() +{ + // Do the color settings + QPalette pal; + pal.setColor(QPalette::WindowText, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_WINDOWTEXT)))); + pal.setColor(QPalette::Button, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNFACE)))); + pal.setColor(QPalette::Light, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNHIGHLIGHT)))); + pal.setColor(QPalette::Dark, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNSHADOW)))); + pal.setColor(QPalette::Mid, pal.button().color().darker(150)); + pal.setColor(QPalette::Text, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_WINDOWTEXT)))); + pal.setColor(QPalette::BrightText, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNHIGHLIGHT)))); + pal.setColor(QPalette::Base, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_WINDOW)))); + pal.setColor(QPalette::Window, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNFACE)))); + pal.setColor(QPalette::ButtonText, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_BTNTEXT)))); + pal.setColor(QPalette::Midlight, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_3DLIGHT)))); + pal.setColor(QPalette::Shadow, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_3DDKSHADOW)))); + pal.setColor(QPalette::Highlight, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_HIGHLIGHT)))); + pal.setColor(QPalette::HighlightedText, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_HIGHLIGHTTEXT)))); + +#if defined(Q_WS_WINCE) + // ### hardcoded until I find out how to get it from the system settings. + pal.setColor(QPalette::LinkVisited, pal.highlight().color().dark(150)); + pal.setColor(QPalette::Link, pal.highlight().color().light(130)); + // Background == Base on Windows CE + if (qt_wince_is_smartphone() || qt_wince_is_pocket_pc()) + pal.setColor(QPalette::Background, pal.base().color()); +#else + pal.setColor(QPalette::Link, Qt::blue); + pal.setColor(QPalette::LinkVisited, Qt::magenta); +#endif + + + + pal.setColor(QPalette::Inactive, QPalette::Button, pal.button().color()); + pal.setColor(QPalette::Inactive, QPalette::Window, pal.background().color()); + pal.setColor(QPalette::Inactive, QPalette::Light, pal.light().color()); + pal.setColor(QPalette::Inactive, QPalette::Dark, pal.dark().color()); + + if (pal.midlight() == pal.button()) + pal.setColor(QPalette::Midlight, pal.button().color().lighter(110)); + if (pal.background() != pal.base()) { + pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Inactive, QPalette::Window)); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Inactive, QPalette::Text)); + } + + const QColor bg = pal.background().color(); + const QColor fg = pal.foreground().color(), btn = pal.button().color(); + QColor disabled((fg.red()+btn.red())/2,(fg.green()+btn.green())/2, + (fg.blue()+btn.blue())/2); + pal.setColorGroup(QPalette::Disabled, pal.foreground(), pal.button(), pal.light(), + pal.dark(), pal.mid(), pal.text(), pal.brightText(), pal.base(), pal.background() ); + pal.setColor(QPalette::Disabled, QPalette::WindowText, disabled); + pal.setColor(QPalette::Disabled, QPalette::Text, disabled); + pal.setColor(QPalette::Disabled, QPalette::ButtonText, disabled); + pal.setColor(QPalette::Disabled, QPalette::Highlight, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_HIGHLIGHT)))); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, + QColor(qt_colorref2qrgb(GetSysColor(COLOR_HIGHLIGHTTEXT)))); + pal.setColor(QPalette::Disabled, QPalette::Base, bg); + + QApplicationPrivate::setSystemPalette(pal); + + QApplicationPrivate::initializeWidgetPaletteHash(); + + QColor ttip(qt_colorref2qrgb(GetSysColor(COLOR_INFOBK))); + + QColor ttipText(qt_colorref2qrgb(GetSysColor(COLOR_INFOTEXT))); + { +#ifndef QT_NO_TOOLTIP + QPalette tiplabel(pal); + tiplabel.setColor(QPalette::All, QPalette::Button, ttip); + tiplabel.setColor(QPalette::All, QPalette::Window, ttip); + tiplabel.setColor(QPalette::All, QPalette::Text, ttipText); + tiplabel.setColor(QPalette::All, QPalette::WindowText, ttipText); + tiplabel.setColor(QPalette::All, QPalette::ButtonText, ttipText); + tiplabel.setColor(QPalette::All, QPalette::Button, ttip); + tiplabel.setColor(QPalette::All, QPalette::Window, ttip); + tiplabel.setColor(QPalette::All, QPalette::Text, ttipText); + tiplabel.setColor(QPalette::All, QPalette::WindowText, ttipText); + tiplabel.setColor(QPalette::All, QPalette::ButtonText, ttipText); + const QColor fg = tiplabel.foreground().color(), btn = tiplabel.button().color(); + QColor disabled((fg.red()+btn.red())/2,(fg.green()+btn.green())/2, + (fg.blue()+btn.blue())/2); + tiplabel.setColor(QPalette::Disabled, QPalette::WindowText, disabled); + tiplabel.setColor(QPalette::Disabled, QPalette::Text, disabled); + tiplabel.setColor(QPalette::Disabled, QPalette::Base, Qt::white); + tiplabel.setColor(QPalette::Disabled, QPalette::BrightText, Qt::white); + QToolTip::setPalette(tiplabel); +#endif //QT_NO_TOOLTIP + } +} + +static void qt_set_windows_font_resources() +{ +#ifndef Q_WS_WINCE + NONCLIENTMETRICS ncm; + ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0); + + QFont menuFont = qt_LOGFONTtoQFont(ncm.lfMenuFont, true); + QFont messageFont = qt_LOGFONTtoQFont(ncm.lfMessageFont, true); + QFont statusFont = qt_LOGFONTtoQFont(ncm.lfStatusFont, true); + QFont titleFont = qt_LOGFONTtoQFont(ncm.lfCaptionFont, true); + + LOGFONT lfIconTitleFont; + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0); + QFont iconTitleFont = qt_LOGFONTtoQFont(lfIconTitleFont, true); + + QApplication::setFont(menuFont, "QMenu"); + QApplication::setFont(menuFont, "QMenuBar"); + QApplication::setFont(messageFont, "QMessageBox"); + QApplication::setFont(statusFont, "QTipLabel"); + QApplication::setFont(statusFont, "QStatusBar"); + QApplication::setFont(titleFont, "Q3TitleBar"); + QApplication::setFont(titleFont, "QWorkspaceTitleBar"); + QApplication::setFont(iconTitleFont, "QAbstractItemView"); + QApplication::setFont(iconTitleFont, "QDockWidgetTitle"); + +#else + LOGFONT lf; + HGDIOBJ stockFont = GetStockObject(SYSTEM_FONT); + GetObject(stockFont, sizeof(lf), &lf); + QFont systemFont = qt_LOGFONTtoQFont(lf, true); + QApplicationPrivate::setSystemFont(systemFont); + QFont smallerFont = systemFont; + if (qt_wince_is_mobile()) { + smallerFont.setPointSize(systemFont.pointSize()-1); + QApplication::setFont(smallerFont, "QTabBar"); + smallerFont.setBold(true); + QApplication::setFont(smallerFont, "QAbstractButton"); + } +#endif// Q_WS_WINCE +} + +static void qt_win_read_cleartype_settings() +{ + UINT result = 0; +#ifdef Q_OS_WINCE + if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &result, 0)) + qt_cleartype_enabled = result; +#else + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &result, 0)) + qt_cleartype_enabled = (result == FE_FONTSMOOTHINGCLEARTYPE); +#endif +} + + +static void qt_set_windows_resources() +{ + if (QApplication::type() != QApplication::Tty) + (void) QApplication::style(); // trigger creation of application style + qt_set_windows_font_resources(); + qt_set_windows_color_resources(); +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ + QPalette pal = *QApplicationPrivate::sys_pal; + QColor menuCol(qt_colorref2qrgb(GetSysColor(COLOR_MENU))); + QColor menuText(qt_colorref2qrgb(GetSysColor(COLOR_MENUTEXT))); + BOOL isFlat = false; + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + SystemParametersInfo(SPI_GETFLATMENU, 0, &isFlat, 0); + QPalette menu(pal); + // we might need a special color group for the menu. + menu.setColor(QPalette::Active, QPalette::Button, menuCol); + menu.setColor(QPalette::Active, QPalette::Text, menuText); + menu.setColor(QPalette::Active, QPalette::WindowText, menuText); + menu.setColor(QPalette::Active, QPalette::ButtonText, menuText); + const QColor fg = menu.foreground().color(), btn = menu.button().color(); + QColor disabled(qt_colorref2qrgb(GetSysColor(COLOR_GRAYTEXT))); + menu.setColor(QPalette::Disabled, QPalette::WindowText, disabled); + menu.setColor(QPalette::Disabled, QPalette::Text, disabled); + menu.setColor(QPalette::Disabled, QPalette::Highlight, + QColor(qt_colorref2qrgb(GetSysColor( + (QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) + && isFlat ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT)))); + menu.setColor(QPalette::Disabled, QPalette::HighlightedText, disabled); + menu.setColor(QPalette::Disabled, QPalette::Button, + menu.color(QPalette::Active, QPalette::Button)); + menu.setColor(QPalette::Inactive, QPalette::Button, + menu.color(QPalette::Active, QPalette::Button)); + menu.setColor(QPalette::Inactive, QPalette::Text, + menu.color(QPalette::Active, QPalette::Text)); + menu.setColor(QPalette::Inactive, QPalette::WindowText, + menu.color(QPalette::Active, QPalette::WindowText)); + menu.setColor(QPalette::Inactive, QPalette::ButtonText, + menu.color(QPalette::Active, QPalette::ButtonText)); + menu.setColor(QPalette::Inactive, QPalette::Highlight, + menu.color(QPalette::Active, QPalette::Highlight)); + menu.setColor(QPalette::Inactive, QPalette::HighlightedText, + menu.color(QPalette::Active, QPalette::HighlightedText)); + menu.setColor(QPalette::Inactive, QPalette::ButtonText, + pal.color(QPalette::Inactive, QPalette::Dark)); + QApplication::setPalette(menu, "QMenu"); + + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based) && isFlat) { + QColor menubar(qt_colorref2qrgb(GetSysColor(COLOR_MENUBAR))); + menu.setColor(QPalette::Active, QPalette::Button, menubar); + menu.setColor(QPalette::Disabled, QPalette::Button, menubar); + menu.setColor(QPalette::Inactive, QPalette::Button, menubar); + } + QApplication::setPalette(menu, "QMenuBar"); +} + +static void qt_set_windows_updateScrollBar(QWidget *widget) +{ + QList children = widget->children(); + for (int i = 0; i < children.size(); ++i) { + QObject *o = children.at(i); + if(!o->isWidgetType()) + continue; + if (QWidget *w = static_cast(o)) + qt_set_windows_updateScrollBar(w); + } +#ifndef QT_NO_SCROLLBAR + if (qobject_cast(widget)) + widget->updateGeometry(); +#endif +} + + +/***************************************************************************** + qt_init() - initializes Qt for Windows + *****************************************************************************/ + +typedef BOOL (WINAPI *PtrSetProcessDPIAware) (VOID); +static PtrSetProcessDPIAware ptrSetProcessDPIAware = 0; +PtrUpdateLayeredWindow ptrUpdateLayeredWindow = 0; +PtrUpdateLayeredWindowIndirect ptrUpdateLayeredWindowIndirect = 0; +static BOOL WINAPI qt_updateLayeredWindowIndirect(HWND hwnd, const Q_UPDATELAYEREDWINDOWINFO *info) +{ + return (*ptrUpdateLayeredWindow)(hwnd, info->hdcDst, info->pptDst, info->psize, info->hdcSrc, + info->pptSrc, info->crKey, info->pblend, info->dwFlags); +} + +void qt_init(QApplicationPrivate *priv, int) +{ + + int argc = priv->argc; + char **argv = priv->argv; + int i, j; + + // Get command line params + + j = argc ? 1 : 0; + for (i=1; iargc) { + priv->argv[j] = 0; + priv->argc = j; + } + +#ifndef Q_WS_WINCE + // No message boxes but important ones + SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX); +#endif + +#ifndef Q_WS_WINCE + // Initialize OLE/COM + // S_OK means success and S_FALSE means that it has already + // been initialized + HRESULT r; + r = OleInitialize(0); + if (r != S_OK && r != S_FALSE) { + qWarning("Qt: Could not initialize OLE (error %x)", (unsigned int)r); + } +#endif + + // Misc. initialization +#if defined(QT_DEBUG) && !defined(Q_WS_WINCE) + GdiSetBatchLimit(1); +#endif + + // initialize key mapper + QKeyMapper::changeKeyboard(); + + QColormap::initialize(); + QFont::initialize(); +#ifndef QT_NO_CURSOR + QCursorData::initialize(); +#endif + qApp->setObjectName(priv->appName()); + + // default font +#ifndef Q_WS_WINCE + HGDIOBJ stockFont = GetStockObject(DEFAULT_GUI_FONT); +#else + HGDIOBJ stockFont = GetStockObject(SYSTEM_FONT); +#endif + + LOGFONT lf; + GetObject(stockFont, sizeof(lf), &lf); + QFont systemFont = qt_LOGFONTtoQFont(lf, true); + +#ifndef Q_WS_WINCE + if (systemFont.family() == QLatin1String("MS Shell Dlg")) { + systemFont.setFamily(QLatin1String("MS Shell Dlg 2")); + } +#endif + + QApplicationPrivate::setSystemFont(systemFont); + + // QFont::locale_init(); ### Uncomment when it does something on Windows + + if (QApplication::desktopSettingsAware()) + qt_set_windows_resources(); + +#ifndef QT_NO_TABLETEVENT + initWinTabFunctions(); +#endif // QT_NO_TABLETEVENT + QApplicationPrivate::inputContext = new QWinInputContext(0); + + // Read the initial cleartype settings... + qt_win_read_cleartype_settings(); + qt_win_owndc_required = false; + + extern void qt_win_initialize_directdraw(); + qt_win_initialize_directdraw(); + +#ifndef Q_OS_WINCE + ptrUpdateLayeredWindowIndirect = + (PtrUpdateLayeredWindowIndirect) QSystemLibrary::resolve(QLatin1String("user32"), + "UpdateLayeredWindowIndirect"); + ptrUpdateLayeredWindow = + (PtrUpdateLayeredWindow) QSystemLibrary::resolve(QLatin1String("user32"), + "UpdateLayeredWindow"); + + if (ptrUpdateLayeredWindow && !ptrUpdateLayeredWindowIndirect) + ptrUpdateLayeredWindowIndirect = qt_updateLayeredWindowIndirect; + + // Notify Vista and Windows 7 that we support highter DPI settings + ptrSetProcessDPIAware = (PtrSetProcessDPIAware) + QSystemLibrary::resolve(QLatin1String("user32"), "SetProcessDPIAware"); + if (ptrSetProcessDPIAware) + ptrSetProcessDPIAware(); +#endif + +#ifndef QT_NO_GESTURES + priv->GetGestureInfo = 0; + priv->GetGestureExtraArgs = 0; + priv->CloseGestureInfoHandle = 0; + priv->SetGestureConfig = 0; + priv->GetGestureConfig = 0; + priv->BeginPanningFeedback = 0; + priv->UpdatePanningFeedback = 0; + priv->EndPanningFeedback = 0; + +#if defined(Q_WS_WINCE_WM) && defined(QT_WINCE_GESTURES) + priv->GetGestureInfo = (PtrGetGestureInfo) &TKGetGestureInfo; + priv->GetGestureExtraArgs = (PtrGetGestureExtraArgs) &TKGetGestureExtraArguments; +#elif !defined(Q_WS_WINCE) + #if !defined(QT_NO_NATIVE_GESTURES) + priv->GetGestureInfo = + (PtrGetGestureInfo)QSystemLibrary::resolve(QLatin1String("user32"), + "GetGestureInfo"); + priv->GetGestureExtraArgs = + (PtrGetGestureExtraArgs)QSystemLibrary::resolve(QLatin1String("user32"), + "GetGestureExtraArgs"); + priv->CloseGestureInfoHandle = + (PtrCloseGestureInfoHandle)QSystemLibrary::resolve(QLatin1String("user32"), + "CloseGestureInfoHandle"); + priv->SetGestureConfig = + (PtrSetGestureConfig)QSystemLibrary::resolve(QLatin1String("user32"), + "SetGestureConfig"); + priv->GetGestureConfig = + (PtrGetGestureConfig)QSystemLibrary::resolve(QLatin1String("user32"), + "GetGestureConfig"); + #endif // QT_NO_NATIVE_GESTURES + QSystemLibrary libTheme(QLatin1String("uxtheme")); + priv->BeginPanningFeedback = + (PtrBeginPanningFeedback)libTheme.resolve("BeginPanningFeedback"); + priv->UpdatePanningFeedback = + (PtrUpdatePanningFeedback)libTheme.resolve("UpdatePanningFeedback"); + priv->EndPanningFeedback = + (PtrEndPanningFeedback)libTheme.resolve("EndPanningFeedback"); +#endif +#endif // QT_NO_GESTURES +} + +/***************************************************************************** + qt_cleanup() - cleans up when the application is finished + *****************************************************************************/ + +void qt_cleanup() +{ + unregWinClasses(); + QPixmapCache::clear(); + +#ifndef QT_NO_CURSOR + QCursorData::cleanup(); +#endif + QFont::cleanup(); + QColormap::cleanup(); + if (displayDC) { + ReleaseDC(0, displayDC); + displayDC = 0; + } + + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = 0; + +#ifndef Q_WS_WINCE + // Deinitialize OLE/COM + OleUninitialize(); +#endif +} + + +/***************************************************************************** + Platform specific global and internal functions + *****************************************************************************/ + +Q_GUI_EXPORT HDC qt_win_display_dc() // get display DC +{ + Q_ASSERT(qApp && qApp->thread() == QThread::currentThread()); + if (!displayDC) + displayDC = GetDC(0); + return displayDC; +} + +bool qt_nograb() // application no-grab option +{ +#if defined(QT_DEBUG) + return appNoGrab; +#else + return false; +#endif +} + +typedef QHash WinClassNameHash; +Q_GLOBAL_STATIC(WinClassNameHash, winclassNames) + +// +// If 0 is passed as the widget pointer, register a window class +// for QWidget as default. This is used in QGLTemporaryContext +// during GL initialization, where we don't want to use temporary +// QWidgets or QGLWidgets, neither do we want to have separate code +// to register window classes. +// +const QString qt_reg_winclass(QWidget *w) // register window class +{ + Qt::WindowFlags flags = w ? w->windowFlags() : (Qt::WindowFlags)0; + Qt::WindowFlags type = flags & Qt::WindowType_Mask; + + uint style; + bool icon; + QString cname; + if (w && qt_widget_private(w)->isGLWidget) { + cname = QLatin1String("QGLWidget"); + style = CS_DBLCLKS; +#ifndef Q_WS_WINCE + style |= CS_OWNDC; +#endif + icon = true; + } else if (w && (flags & Qt::MSWindowsOwnDC)) { + cname = QLatin1String("QWidgetOwnDC"); + style = CS_DBLCLKS; +#ifndef Q_WS_WINCE + style |= CS_OWNDC; +#endif + icon = true; + } else if (w && (type == Qt::Tool || type == Qt::ToolTip)) { + style = CS_DBLCLKS; + if (w->inherits("QTipLabel") || w->inherits("QAlphaWidget")) { + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) { + style |= CS_DROPSHADOW; + } + cname = QLatin1String("QToolTip"); + } else { + cname = QLatin1String("QTool"); + } +#ifndef Q_WS_WINCE + style |= CS_SAVEBITS; +#endif + icon = false; + } else if (w && (type == Qt::Popup)) { + cname = QLatin1String("QPopup"); + style = CS_DBLCLKS; +#ifndef Q_WS_WINCE + style |= CS_SAVEBITS; +#endif + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + style |= CS_DROPSHADOW; + icon = false; + } else { + cname = QLatin1String("QWidget"); + style = CS_DBLCLKS; + icon = true; + } + +#ifndef Q_WS_WINCE + // force CS_OWNDC when the GL graphics system is + // used as the default renderer + if (qt_win_owndc_required) + style |= CS_OWNDC; +#endif + +#ifdef Q_OS_WINCE + // We need to register the classes with the + // unique ID on WinCE to make sure we can + // move the windows to the front when starting + // a second instance. + wchar_t uniqueAppID[MAX_PATH]; + GetModuleFileName(0, uniqueAppID, MAX_PATH); + cname = QString::number(RegisterWindowMessage( + (const wchar_t *) QString::fromWCharArray(uniqueAppID).toLower().replace(QLatin1Char('\\'), + QLatin1Char('_')).utf16())); +#endif + + // since multiple Qt versions can be used in one process + // each one has to have window class names with a unique name + // The first instance gets the unmodified name; if the class + // has already been registered by another instance of Qt then + // add an instance-specific ID, the address of the window proc. + static int classExists = -1; + + if (classExists == -1) { + WNDCLASS wcinfo; + classExists = GetClassInfo((HINSTANCE)qWinAppInst(), (wchar_t*)cname.utf16(), &wcinfo); + classExists = classExists && wcinfo.lpfnWndProc != QtWndProc; + } + + if (classExists) + cname += QString::number((quintptr)QtWndProc); + + if (winclassNames()->contains(cname)) // already registered in our list + return cname; + +#ifndef Q_WS_WINCE + WNDCLASSEX wc; + wc.cbSize = sizeof(WNDCLASSEX); +#else + WNDCLASS wc; +#endif + wc.style = style; + wc.lpfnWndProc = (WNDPROC)QtWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = qWinAppInst(); + if (icon) { + wc.hIcon = (HICON)LoadImage(qWinAppInst(), L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); +#ifndef Q_WS_WINCE + if (wc.hIcon) { + int sw = GetSystemMetrics(SM_CXSMICON); + int sh = GetSystemMetrics(SM_CYSMICON); + wc.hIconSm = (HICON)LoadImage(qWinAppInst(), L"IDI_ICON1", IMAGE_ICON, sw, sh, 0); + } else { + wc.hIcon = (HICON)LoadImage(0, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + wc.hIconSm = 0; + } +#endif + } else { + wc.hIcon = 0; +#ifndef Q_WS_WINCE + wc.hIconSm = 0; +#endif + } + wc.hCursor = 0; +#ifndef Q_WS_WINCE + HBRUSH brush = 0; + if (w && !qt_widget_private(w)->isGLWidget) + brush = (HBRUSH)GetSysColorBrush(COLOR_WINDOW); + wc.hbrBackground = brush; +#else + wc.hbrBackground = 0; +#endif + wc.lpszMenuName = 0; + wc.lpszClassName = (wchar_t*)cname.utf16(); + +#ifndef Q_WS_WINCE + ATOM atom = RegisterClassEx(&wc); +#else + ATOM atom = RegisterClass(&wc); +#endif + +#ifndef QT_NO_DEBUG + if (!atom) + qErrnoWarning("QApplication::regClass: Registering window class failed."); +#else + Q_UNUSED(atom); +#endif + + winclassNames()->insert(cname, 1); + return cname; +} + +Q_GUI_EXPORT const QString qt_getRegisteredWndClass() +{ + return qt_reg_winclass(0); +} + +static void unregWinClasses() +{ + WinClassNameHash *hash = winclassNames(); + QHash::ConstIterator it = hash->constBegin(); + while (it != hash->constEnd()) { + UnregisterClass((wchar_t*)it.key().utf16(), qWinAppInst()); + ++it; + } + hash->clear(); +} + + +/***************************************************************************** + Safe configuration (move,resize,setGeometry) mechanism to avoid + recursion when processing messages. + *****************************************************************************/ + +struct QWinConfigRequest { + WId id; // widget to be configured + int req; // 0=move, 1=resize, 2=setGeo + int x, y, w, h; // request parameters +}; + +static QList *configRequests = 0; + +void qWinRequestConfig(WId id, int req, int x, int y, int w, int h) +{ + if (!configRequests) // create queue + configRequests = new QList; + QWinConfigRequest *r = new QWinConfigRequest; + r->id = id; // create new request + r->req = req; + r->x = x; + r->y = y; + r->w = w; + r->h = h; + configRequests->append(r); // store request in queue +} + +static void qWinProcessConfigRequests() // perform requests in queue +{ + if (!configRequests) + return; + QWinConfigRequest *r; + for (;;) { + if (configRequests->isEmpty()) + break; + r = configRequests->takeLast(); + QWidget *w = QWidget::find(r->id); + QRect rect(r->x, r->y, r->w, r->h); + int req = r->req; + delete r; + + if ( w ) { // widget exists + if (w->testAttribute(Qt::WA_WState_ConfigPending)) + return; // biting our tail + if (req == 0) + w->move(rect.topLeft()); + else if (req == 1) + w->resize(rect.size()); + else + w->setGeometry(rect); + } + } + delete configRequests; + configRequests = 0; +} + + +/***************************************************************************** + GUI event dispatcher + *****************************************************************************/ + +class QGuiEventDispatcherWin32 : public QEventDispatcherWin32 +{ + Q_DECLARE_PRIVATE(QEventDispatcherWin32) +public: + QGuiEventDispatcherWin32(QObject *parent = 0); + bool processEvents(QEventLoop::ProcessEventsFlags flags); +}; + +QGuiEventDispatcherWin32::QGuiEventDispatcherWin32(QObject *parent) + : QEventDispatcherWin32(parent) +{ + createInternalHwnd(); +} + +bool QGuiEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + if (!QEventDispatcherWin32::processEvents(flags)) + return false; + + if (configRequests) // any pending configs? + qWinProcessConfigRequests(); + + return true; +} + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); + if (q->type() != QApplication::Tty) + eventDispatcher = new QGuiEventDispatcherWin32(q); + else + eventDispatcher = new QEventDispatcherWin32(q); +} + +/***************************************************************************** + Platform specific QApplication members + *****************************************************************************/ + +#ifdef QT3_SUPPORT +void QApplication::setMainWidget(QWidget *mainWidget) +{ + QApplicationPrivate::main_widget = mainWidget; + if (QApplicationPrivate::main_widget && windowIcon().isNull() + && QApplicationPrivate::main_widget->testAttribute(Qt::WA_SetWindowIcon)) + setWindowIcon(QApplicationPrivate::main_widget->windowIcon()); +} +#endif + +#ifndef QT_NO_CURSOR + +/***************************************************************************** + QApplication cursor stack + *****************************************************************************/ + +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + SetCursor(qApp->d_func()->cursor_list.first().handle()); +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + + if (!qApp->d_func()->cursor_list.isEmpty()) { + SetCursor(qApp->d_func()->cursor_list.first().handle()); + } else { + QWidget *w = QWidget::find(curWin); + if (w) + SetCursor(w->cursor().handle()); + else + SetCursor(QCursor(Qt::ArrowCursor).handle()); + } +} + +#endif + +/* + Internal function called from QWidget::setCursor() + force is true if this function is called from dispatchEnterLeave, it means that the + mouse is actually directly under this widget. +*/ + +#ifndef QT_NO_CURSOR +void qt_win_set_cursor(QWidget *w, bool force) +{ + static QPointer lastUnderMouse = 0; + if (force) { + lastUnderMouse = w; + } else if (w->testAttribute(Qt::WA_WState_Created) && lastUnderMouse + && lastUnderMouse->effectiveWinId() == w->effectiveWinId()) { + w = lastUnderMouse; + } + + if (!curWin && w && w->internalWinId()) + return; + QWidget* cW = w && !w->internalWinId() ? w : QWidget::find(curWin); + if (!cW || cW->window() != w->window() || + !cW->isVisible() || !cW->underMouse() || QApplication::overrideCursor()) + return; + + SetCursor(cW->cursor().handle()); +} +#endif // QT_NO_CURSOR + +Qt::KeyboardModifiers qt_win_getKeyboardModifiers() +{ + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + if (GetKeyState(VK_SHIFT) < 0) + modifiers |= Qt::ShiftModifier; + if (GetKeyState(VK_CONTROL) < 0) + modifiers |= Qt::ControlModifier; + if (GetKeyState(VK_MENU) < 0) + modifiers |= Qt::AltModifier; + return modifiers; +} + +/***************************************************************************** + Routines to find a Qt widget from a screen position + *****************************************************************************/ + +QWidget *QApplication::topLevelAt(const QPoint &pos) +{ + POINT p; + HWND win; + QWidget *w; + p.x = pos.x(); + p.y = pos.y(); + win = WindowFromPoint(p); + if (!win) + return 0; + + w = QWidget::find(win); + while (!w && win) { + win = GetParent(win); + w = QWidget::find(win); + } + return w ? w->window() : 0; +} + +void QApplication::beep() +{ + MessageBeep(MB_OK); +} + +static void alert_widget(QWidget *widget, int duration) +{ +#ifdef Q_OS_WINCE + Q_UNUSED(widget); + Q_UNUSED(duration); +#else + bool stopFlash = duration < 0; + + if (widget && (!widget->isActiveWindow() || stopFlash)) { + DWORD timeOut = GetCaretBlinkTime(); + if (timeOut <= 0) + timeOut = 250; + + UINT flashCount; + if (duration == 0) + flashCount = 10; + else + flashCount = duration/timeOut; + + FLASHWINFO info; + info.cbSize = sizeof(info); + info.hwnd = widget->window()->winId(); + info.dwFlags = stopFlash ? FLASHW_STOP : FLASHW_TRAY; + info.dwTimeout = stopFlash ? 0 : timeOut; + info.uCount = stopFlash ? 0 : flashCount; + + FlashWindowEx(&info); + } +#endif +} + +void QApplication::alert(QWidget *widget, int duration) +{ + if (!QApplicationPrivate::checkInstance("alert")) + return; + + if (widget) { + alert_widget(widget, duration); + } else { + const QWidgetList toplevels(topLevelWidgets()); + for (int i = 0; i < toplevels.count(); ++i) { + QWidget *topLevel = toplevels.at(i); + alert_widget(topLevel, duration); + } + } +} + +QString QApplicationPrivate::appName() const +{ + return QCoreApplicationPrivate::appName(); +} + + +/***************************************************************************** + Main event loop + *****************************************************************************/ + +extern uint qGlobalPostedEventsCount(); + +void QApplication::winFocus(QWidget *widget, bool gotFocus) +{ + if (d_func()->inPopupMode()) // some delayed focus event to ignore + return; + if (gotFocus) { + setActiveWindow(widget); + if (QApplicationPrivate::active_window + && (QApplicationPrivate::active_window->windowType() == Qt::Dialog)) { + // raise the entire application, not just the dialog + QWidget* mw = QApplicationPrivate::active_window; +#ifndef Q_WS_WINCE + while(mw->parentWidget() && (mw->windowType() == Qt::Dialog)) + mw = mw->parentWidget()->window(); + if (mw->testAttribute(Qt::WA_WState_Created) && mw != QApplicationPrivate::active_window) + SetWindowPos(mw->internalWinId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); +#else + // On Desktop Windows, we set the first parent of the dialog on top + // Child windows will be automatically set above again. + // On Windows CE we pass no parent in CreateWindowEx as otherwise + // dialogs get embedded into the parent window. Thus we need to + // manually iterate and reactivate all windows from bottom up. + QList raiseList; + raiseList.push_back(mw); + while(mw->parentWidget() && (mw->windowType() == Qt::Dialog)) { + mw = mw->parentWidget()->window(); + raiseList.push_back(mw); + } + while(!raiseList.isEmpty()) { + mw = raiseList.takeLast(); + if (mw->testAttribute(Qt::WA_WState_Created)) { + HWND state = HWND_TOP; + if (mw->windowFlags() & Qt::WindowStaysOnBottomHint) + state = HWND_BOTTOM; + else if (mw->windowFlags() & Qt::WindowStaysOnTopHint) + state = HWND_TOPMOST; + SetWindowPos(mw->internalWinId(), state, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + } + } +#endif + } + } else { + setActiveWindow(0); + } +} + + +// +// QtWndProc() receives all messages from the main event loop +// + +static bool inLoop = false; +static int inputcharset = CP_ACP; + +#define RETURN(x) { inLoop=false;return x; } + +static bool qt_is_translatable_mouse_event(UINT message) +{ + return (((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) || + (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONDBLCLK)) + && message != WM_MOUSEWHEEL + && message != WM_MOUSEHWHEEL) + +#ifndef Q_WS_WINCE + || (message >= WM_NCMOUSEMOVE && message <= WM_NCMBUTTONDBLCLK) +#endif + ; +} + +extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + bool result = true; + QEvent::Type evt_type = QEvent::None; + QETWidget *widget = 0; + + // there is no need to process pakcets from tablet unless + // it is actually on the tablet, a flag to let us know... + int nPackets; // the number of packets we get from the queue + + long res = 0; + if (!qApp) // unstable app state + RETURN(QWinInputContext::DefWindowProc(hwnd,message,wParam,lParam)) + + QScopedLoopLevelCounter loopLevelCounter(QThreadData::get2(qApp->thread())); + +#if 0 + // make sure we update widgets also when the user resizes + if (inLoop && qApp->loopLevel()) + qApp->sendPostedEvents(0, QEvent::Paint); +#endif + + inLoop = true; + + MSG msg; + msg.hwnd = hwnd; // create MSG structure + msg.message = message; // time and pt fields ignored + msg.wParam = wParam; + msg.lParam = lParam; + msg.pt.x = GET_X_LPARAM(lParam); + msg.pt.y = GET_Y_LPARAM(lParam); + // If it's a non-client-area message the coords are screen coords, otherwise they are + // client coords. +#ifndef Q_WS_WINCE + if (message < WM_NCMOUSEMOVE || message > WM_NCMBUTTONDBLCLK) +#endif + ClientToScreen(msg.hwnd, &msg.pt); + + /* + // sometimes the autograb is not released, so the clickevent is sent + // to the wrong window. We ignore this for now, because it doesn't + // cause any problems. + if (msg.message == WM_LBUTTONDOWN || msg.message == WM_RBUTTONDOWN || msg.message == WM_MBUTTONDOWN) { + HWND handle = WindowFromPoint(msg.pt); + if (msg.hwnd != handle) { + msg.hwnd = handle; + hwnd = handle; + } + } + */ + +#if defined(QT_NON_COMMERCIAL) + QT_NC_WNDPROC +#endif + + // send through app filter + if (qApp->filterEvent(&msg, &res)) + return res; + + // close any opened ime candidate window (enabled only on a popup widget) + if (imeParentWnd && QApplication::activePopupWidget() + && (message == WM_MBUTTONDOWN || message == WM_XBUTTONDOWN + || message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN +#ifndef Q_WS_WINCE + || message == WM_NCMBUTTONDOWN || message == WM_NCLBUTTONDOWN + || message == WM_NCRBUTTONDOWN)) { +#else + )) { +#endif + ::SendMessage(imeParentWnd, WM_IME_ENDCOMPOSITION, 0, 0); + } + + switch (message) { +#ifndef Q_WS_WINCE +#ifndef QT_NO_SESSIONMANAGER + case WM_QUERYENDSESSION: { + if (sm_smActive) // bogus message from windows + RETURN(true); + + sm_smActive = true; + sm_blockUserInput = true; // prevent user-interaction outside interaction windows + sm_cancel = false; + if (qt_session_manager_self) + qApp->commitData(*qt_session_manager_self); + if (lParam & ENDSESSION_LOGOFF) { + _flushall(); + } + RETURN(!sm_cancel); + } + case WM_ENDSESSION: { + sm_smActive = false; + sm_blockUserInput = false; + bool endsession = (bool) wParam; + + // we receive the message for each toplevel window included internal hidden ones, + // but the aboutToQuit signal should be emitted only once. + QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); + if (endsession && !qAppPriv->aboutToQuitEmitted) { + qAppPriv->aboutToQuitEmitted = true; + int index = QApplication::staticMetaObject.indexOfSignal("aboutToQuit()"); + qApp->qt_metacall(QMetaObject::InvokeMetaMethod, index,0); + // since the process will be killed immediately quit() has no real effect + QApplication::quit(); + } + + RETURN(0); + } +#endif + case WM_DISPLAYCHANGE: + if (QApplication::type() == QApplication::Tty) + break; + if (qt_desktopWidget) { + qt_desktopWidget->move(GetSystemMetrics(76), GetSystemMetrics(77)); + QSize sz(GetSystemMetrics(78), GetSystemMetrics(79)); + if (sz == qt_desktopWidget->size()) { + // a screen resized without changing size of the virtual desktop + QResizeEvent rs(sz, qt_desktopWidget->size()); + QApplication::sendEvent(qt_desktopWidget, &rs); + } else { + qt_desktopWidget->resize(sz); + } + } + break; +#endif + + case WM_SETTINGCHANGE: +#ifdef Q_WS_WINCE + // CE SIP hide/show + if (qt_desktopWidget && wParam == SPI_SETSIPINFO) { + QResizeEvent re(QSize(0, 0), QSize(0, 0)); // Calculated by QDesktopWidget + QApplication::sendEvent(qt_desktopWidget, &re); + break; + } +#endif + // ignore spurious XP message when user logs in again after locking + if (QApplication::type() == QApplication::Tty) + break; + if (QApplication::desktopSettingsAware() && wParam != SPI_SETWORKAREA) { + widget = (QETWidget*)QWidget::find(hwnd); + if (widget) { + if (wParam == SPI_SETNONCLIENTMETRICS) + widget->markFrameStrutDirty(); + } + } + else if (qt_desktopWidget && wParam == SPI_SETWORKAREA) { + qt_desktopWidget->move(GetSystemMetrics(76), GetSystemMetrics(77)); + QSize sz(GetSystemMetrics(78), GetSystemMetrics(79)); + if (sz == qt_desktopWidget->size()) { + // a screen resized without changing size of the virtual desktop + QResizeEvent rs(sz, qt_desktopWidget->size()); + QApplication::sendEvent(qt_desktopWidget, &rs); + } else { + qt_desktopWidget->resize(sz); + } + } + + if (wParam == SPI_SETFONTSMOOTHINGTYPE) { + qt_win_read_cleartype_settings(); + foreach (QWidget *w, QApplication::topLevelWidgets()) { + if (!w->isVisible()) + continue; + ((QETWidget *) w)->forceUpdate(); + } + } + + break; + case WM_SYSCOLORCHANGE: + if (QApplication::type() == QApplication::Tty) + break; + if (QApplication::desktopSettingsAware()) { + widget = (QETWidget*)QWidget::find(hwnd); + if (widget && !widget->parentWidget()) + qt_set_windows_color_resources(); + } + break; + + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + if (qt_win_ignoreNextMouseReleaseEvent) + qt_win_ignoreNextMouseReleaseEvent = false; + break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + if (qt_win_ignoreNextMouseReleaseEvent) { + qt_win_ignoreNextMouseReleaseEvent = false; + if (qt_button_down && qt_button_down->internalWinId() == autoCaptureWnd) { + releaseAutoCapture(); + qt_button_down = 0; + } + + RETURN(0); + } + break; + + default: + break; + } + + if (!widget) + widget = (QETWidget*)QWidget::find(hwnd); + if (!widget) // don't know this widget + goto do_default; + + if (app_do_modal) { // modal event handling + int ret = 0; + if (!qt_try_modal(widget, &msg, ret)) + RETURN(ret); + } + + res = 0; + if (widget->winEvent(&msg, &res)) // send through widget filter + RETURN(res); + + if (qt_is_translatable_mouse_event(message)) { + if (QApplication::activePopupWidget() != 0) { // in popup mode + POINT curPos = msg.pt; + QWidget* w = QApplication::widgetAt(curPos.x, curPos.y); + if (w) + widget = (QETWidget*)w; + } + + if (!qt_tabletChokeMouse) { + result = widget->translateMouseEvent(msg); // mouse event +#if defined(Q_WS_WINCE) && !defined(QT_NO_CONTEXTMENU) + if (message == WM_LBUTTONDOWN && widget != QApplication::activePopupWidget()) { + QWidget* alienWidget = widget; + if ((alienWidget != QApplication::activePopupWidget()) && (alienWidget->contextMenuPolicy() != Qt::PreventContextMenu)) { + QPoint pos(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + QPoint globalPos(msg.pt.x, msg.pt.y); + // In case we are using Alien, then the widget to + // send the context menu event is a different one + if (!alienWidget->testAttribute(Qt::WA_NativeWindow) && !alienWidget->testAttribute(Qt::WA_PaintOnScreen)) { + alienWidget = QApplication::widgetAt(globalPos); + if (alienWidget) + pos = alienWidget->mapFromGlobal(globalPos); + } + if (alienWidget) { + SHRGINFO shrg; + shrg.cbSize = sizeof(shrg); + shrg.hwndClient = hwnd; + shrg.ptDown.x = GET_X_LPARAM(lParam); + shrg.ptDown.y = GET_Y_LPARAM(lParam); + shrg.dwFlags = SHRG_RETURNCMD | SHRG_NOANIMATION; + resolveAygLibs(); +#ifndef QT_NO_GESTURES + if (ptrRecognizeGesture && (ptrRecognizeGesture(&shrg) == GN_CONTEXTMENU)) { + if (QApplication::activePopupWidget()) + QApplication::activePopupWidget()->close(); + QContextMenuEvent e(QContextMenuEvent::Mouse, pos, globalPos); + result = qt_sendSpontaneousEvent(alienWidget, &e); + } +#endif // QT_NO_GESTURES + } + } + } +#endif + } else { + // Sometimes we only get a WM_MOUSEMOVE message + // and sometimes we get both a WM_MOUSEMOVE and + // a WM_LBUTTONDOWN/UP, this creates a spurious mouse + // press/release event, using the PeekMessage + // will help us fix this. This leaves us with a + // question: + // This effectively kills using the mouse AND the + // tablet simultaneously, well creates wacky input. + // Is this going to be a problem? (probably not) + bool next_is_button = false; + bool is_mouse_move = (message == WM_MOUSEMOVE); + if (is_mouse_move) { + MSG msg1; + if (PeekMessage(&msg1, msg.hwnd, WM_MOUSEFIRST, + WM_MOUSELAST, PM_NOREMOVE)) + next_is_button = (msg1.message == WM_LBUTTONUP + || msg1.message == WM_LBUTTONDOWN); + } + if (!is_mouse_move || (is_mouse_move && !next_is_button)) + qt_tabletChokeMouse = false; + } + } else { + switch (message) { + case WM_TOUCH: + result = QApplicationPrivate::instance()->translateTouchEvent(msg); + break; + case WM_KEYDOWN: // keyboard event + case WM_SYSKEYDOWN: + qt_keymapper_private()->updateKeyMap(msg); + // fall-through intended + case WM_KEYUP: + case WM_SYSKEYUP: +#if Q_OS_WINCE_WM + case WM_HOTKEY: + if(HIWORD(msg.lParam) == VK_TBACK) { + const bool hotKeyDown = !(LOWORD(msg.lParam) & MOD_KEYUP); + msg.lParam = 0x69 << 16; + msg.wParam = VK_BACK; + if (hotKeyDown) { + msg.message = WM_KEYDOWN; + qt_keymapper_private()->updateKeyMap(msg); + } else { + msg.message = WM_KEYUP; + } + } + // fall-through intended +#endif + case WM_IME_CHAR: + case WM_IME_KEYDOWN: + case WM_CHAR: { + MSG msg1; + bool anyMsg = PeekMessage(&msg1, msg.hwnd, 0, 0, PM_NOREMOVE); + if (anyMsg && msg1.message == WM_DEADCHAR) { + result = true; // consume event since there is a dead char next + break; + } + QWidget *g = QWidget::keyboardGrabber(); + if (g && qt_get_tablet_widget() && hwnd == qt_get_tablet_widget()->winId()) { + // if we get an event for the internal tablet widget, + // then don't send it to the keyboard grabber, but + // send it to the widget itself (we don't use it right + // now, just in case). + g = 0; + } + if (g) + widget = (QETWidget*)g; + else if (QApplication::activePopupWidget()) + widget = (QETWidget*)QApplication::activePopupWidget()->focusWidget() + ? (QETWidget*)QApplication::activePopupWidget()->focusWidget() + : (QETWidget*)QApplication::activePopupWidget(); + else if (QApplication::focusWidget()) + widget = (QETWidget*)QApplication::focusWidget(); + else if (!widget || widget->internalWinId() == GetFocus()) // We faked the message to go to exactly that widget. + widget = (QETWidget*)widget->window(); + if (widget->isEnabled()) + result = sm_blockUserInput + ? true + : qt_keymapper_private()->translateKeyEvent(widget, msg, g != 0); + break; + } + case WM_SYSCHAR: + result = true; // consume event + break; + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + result = widget->translateWheelEvent(msg); + break; + + case WM_APPCOMMAND: + { + uint cmd = GET_APPCOMMAND_LPARAM(lParam); + uint uDevice = GET_DEVICE_LPARAM(lParam); + uint dwKeys = GET_KEYSTATE_LPARAM(lParam); + + int state = translateButtonState(dwKeys, QEvent::KeyPress, 0); + + switch (uDevice) { + case FAPPCOMMAND_KEY: + { + int key = 0; + + switch(cmd) { + case APPCOMMAND_BASS_BOOST: + key = Qt::Key_BassBoost; + break; + case APPCOMMAND_BASS_DOWN: + key = Qt::Key_BassDown; + break; + case APPCOMMAND_BASS_UP: + key = Qt::Key_BassUp; + break; + case APPCOMMAND_TREBLE_DOWN: + key = Qt::Key_TrebleDown; + break; + case APPCOMMAND_TREBLE_UP: + key = Qt::Key_TrebleUp; + break; + case APPCOMMAND_HELP: + key = Qt::Key_Help; + break; + case APPCOMMAND_FIND: + key = Qt::Key_Search; + break; + default: + break; + } + if (key) { + bool res = false; + QWidget *g = QWidget::keyboardGrabber(); + if (g) + widget = (QETWidget*)g; + else if (QApplication::focusWidget()) + widget = (QETWidget*)QApplication::focusWidget(); + else + widget = (QETWidget*)widget->window(); + if (widget->isEnabled()) { + res = QKeyMapper::sendKeyEvent(widget, g != 0, QEvent::KeyPress, key, + Qt::KeyboardModifier(state), + QString(), false, 0, 0, 0, 0); + } + if (res) + return true; + } + } + break; + + default: + break; + } + + result = false; + } + break; + +#ifndef Q_WS_WINCE + case WM_NCHITTEST: + if (widget->isWindow()) { + QPoint pos = widget->mapFromGlobal(QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + // don't show resize-cursors for fixed-size widgets + QRect fs = widget->frameStrut(); + if (!widget->isMinimized()) { + if (widget->minimumHeight() == widget->maximumHeight()) { + if (pos.y() < -(fs.top() - fs.left())) + return HTCAPTION; + if (pos.y() >= widget->height()) + return HTBORDER; + } + if (widget->minimumWidth() == widget->maximumWidth() && (pos.x() < 0 || pos.x() >= widget->width())) + return HTBORDER; + } + } + + result = false; + break; +#endif + + case WM_SYSCOMMAND: { +#ifndef Q_WS_WINCE + bool window_state_change = false; + Qt::WindowStates oldstate = Qt::WindowStates(widget->dataPtr()->window_state); + // MSDN:In WM_SYSCOMMAND messages, the four low-order bits of the wParam parameter are + // used internally by the system. To obtain the correct result when testing the value of + // wParam, an application must combine the value 0xFFF0 with the wParam value by using + // the bitwise AND operator. + switch(0xfff0 & wParam) { + case SC_CONTEXTHELP: +#ifndef QT_NO_WHATSTHIS + QWhatsThis::enterWhatsThisMode(); +#endif + DefWindowProc(hwnd, WM_NCPAINT, 1, 0); + break; +#if defined(QT_NON_COMMERCIAL) + QT_NC_SYSCOMMAND +#endif + case SC_MINIMIZE: + window_state_change = true; + widget->dataPtr()->window_state |= Qt::WindowMinimized; + if (widget->isVisible()) { + QHideEvent e; + qt_sendSpontaneousEvent(widget, &e); + widget->hideChildren(true); + const QString title = widget->windowIconText(); + if (!title.isEmpty()) + widget->setWindowTitle_helper(title); + } + result = false; + break; + case SC_MAXIMIZE: + if(widget->isWindow()) + widget->topData()->normalGeometry = widget->geometry(); + case SC_RESTORE: + window_state_change = true; + if ((0xfff0 & wParam) == SC_MAXIMIZE) + widget->dataPtr()->window_state |= Qt::WindowMaximized; + else if (!widget->isMinimized()) + widget->dataPtr()->window_state &= ~Qt::WindowMaximized; + + if (widget->isMinimized()) { + widget->dataPtr()->window_state &= ~Qt::WindowMinimized; + widget->showChildren(true); + QShowEvent e; + qt_sendSpontaneousEvent(widget, &e); + const QString title = widget->windowTitle(); + if (!title.isEmpty()) + widget->setWindowTitle_helper(title); + } + result = false; + break; + default: + result = false; + break; + } + + if (window_state_change) { + QWindowStateChangeEvent e(oldstate); + qt_sendSpontaneousEvent(widget, &e); + } +#endif // #ifndef Q_OS_WINCE + + break; + } + + case WM_SETTINGCHANGE: + if ( QApplication::type() == QApplication::Tty ) + break; + + if (!msg.wParam) { +#ifdef Q_WS_WINCE + // On Windows CE, lParam parameter is a constant, not a char pointer. + if (msg.lParam == INI_INTL) { +#else + QString area = QString::fromWCharArray((wchar_t*)msg.lParam); + if (area == QLatin1String("intl")) { +#endif + QLocalePrivate::updateSystemPrivate(); + if (!widget->testAttribute(Qt::WA_SetLocale)) + widget->dptr()->setLocale_helper(QLocale(), true); + QEvent e(QEvent::LocaleChange); + QApplication::sendEvent(qApp, &e); + } + } + else if (msg.wParam == SPI_SETICONTITLELOGFONT) { + if (QApplication::desktopSettingsAware()) { + widget = (QETWidget*)QWidget::find(hwnd); + if (widget && !widget->parentWidget()) { + qt_set_windows_font_resources(); + } + } + } + else if (msg.wParam == SPI_SETNONCLIENTMETRICS) { + widget = (QETWidget*)QWidget::find(hwnd); + if (widget && !widget->parentWidget()) { + qt_set_windows_updateScrollBar(widget); + QEvent e(QEvent::LayoutRequest); + QApplication::sendEvent(widget, &e); + } + } + + break; + + case WM_PAINT: // paint event + case WM_ERASEBKGND: // erase window background + result = widget->translatePaintEvent(msg); + break; + +#ifndef Q_WS_WINCE + case WM_ENTERSIZEMOVE: + autoCaptureWnd = hwnd; + break; + case WM_EXITSIZEMOVE: + autoCaptureWnd = 0; + break; +#endif + case WM_MOVE: // move window + case WM_SIZE: // resize window + result = widget->translateConfigEvent(msg); + break; + + case WM_ACTIVATEAPP: + if (wParam == FALSE) { + QApplication::setActiveWindow(0); + // Another application was activated while our popups are open, + // then close all popups. In case some popup refuses to close, + // we give up after 1024 attempts (to avoid an infinite loop). + int maxiter = 1024; + QWidget *popup; + while ((popup=QApplication::activePopupWidget()) && maxiter--) + popup->close(); + } + break; + + case WM_ACTIVATE: + if ( QApplication::type() == QApplication::Tty ) + break; + + if (ptrWTOverlap && ptrWTEnable) { + // cooperate with other tablet applications, but when + // we get focus, I want to use the tablet... + if (qt_tablet_context && GET_WM_ACTIVATE_STATE(wParam, lParam)) { + if (ptrWTEnable(qt_tablet_context, true)) + ptrWTOverlap(qt_tablet_context, true); + } + } + if (QApplication::activePopupWidget() && LOWORD(wParam) == WA_INACTIVE && + QWidget::find((HWND)lParam) == 0) { + // Another application was activated while our popups are open, + // then close all popups. In case some popup refuses to close, + // we give up after 1024 attempts (to avoid an infinite loop). + int maxiter = 1024; + QWidget *popup; + while ((popup=QApplication::activePopupWidget()) && maxiter--) + popup->close(); + } + + if (LOWORD(wParam) != WA_INACTIVE) { + // WM_ACTIVATEAPP handles the "true" false case, as this is only when the application + // loses focus. Doing it here would result in the widget getting focus to not know + // where it got it from; it would simply get a 0 value as the old focus widget. +#ifdef Q_WS_WINCE + { +#ifdef Q_WS_WINCE_WM + // On Windows mobile we do not receive WM_SYSCOMMAND / SC_MINIMIZE messages. + // Thus we have to unset the minimized state explicitly. We must do this for all + // top-level widgets, because we get the HWND of a random widget here. + foreach (QWidget* tlw, QApplication::topLevelWidgets()) { + if (tlw->isMinimized()) + tlw->setWindowState(tlw->windowState() & ~Qt::WindowMinimized); + } +#else + // On Windows CE we do not receive WM_SYSCOMMAND / SC_MINIMIZE messages. + // Thus we have to unset the minimized state explicitly. + if (widget->windowState() & Qt::WindowMinimized) + widget->setWindowState(widget->windowState() & ~Qt::WindowMinimized); +#endif // Q_WS_WINCE_WM + +#else + if (!(widget->windowState() & Qt::WindowMinimized)) { +#endif + // Ignore the activate message send by WindowsXP to a minimized window +#ifdef Q_WS_WINCE_WM + if (widget->windowState() & Qt::WindowFullScreen) + qt_wince_hide_taskbar(widget->winId()); +#endif + qApp->winFocus(widget, true); + // reset any window alert flashes + alert_widget(widget, -1); + } + } + + // Windows tries to activate a modally blocked window. + // This happens when restoring an application after "Show Desktop" + if (app_do_modal && LOWORD(wParam) == WA_ACTIVE) { + QWidget *top = 0; + if (!QApplicationPrivate::tryModalHelper(widget, &top) && top && widget != top) { + if (top->isVisible()) { + top->activateWindow(); + } else { + // This is the case when native file dialogs are shown + QWidget *p = (top->parentWidget() ? top->parentWidget()->window() : 0); + if (p && p->isVisible()) + p->activateWindow(); + } + } + } + break; + +#ifndef Q_WS_WINCE + case WM_MOUSEACTIVATE: + if (widget->window()->windowType() == Qt::Tool) { + QWidget *w = widget; + if (!w->window()->focusWidget()) { + while (w && (w->focusPolicy() & Qt::ClickFocus) == 0) { + if (w->isWindow()) { + QWidget *fw = w; + while ((fw = fw->nextInFocusChain()) != w && fw->focusPolicy() == Qt::NoFocus) + ; + if (fw != w) + break; + QWidget *pw = w->parentWidget(); + while (pw) { + pw = pw->window(); + if (pw && pw->isVisible() && pw->focusWidget()) { + Q_ASSERT(pw->testAttribute(Qt::WA_WState_Created)); + SetWindowPos(pw->internalWinId(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + break; + } + pw = pw->parentWidget(); + } + RETURN(MA_NOACTIVATE); + } + w = w->parentWidget(); + } + } + } + RETURN(MA_ACTIVATE); + break; +#endif + case WM_SHOWWINDOW: + if (lParam == SW_PARENTOPENING) { + if (widget->testAttribute(Qt::WA_WState_Hidden)) + RETURN(0); + } + if (widget->isWindow() && widget->testAttribute(Qt::WA_WState_Visible) + && !widget->testWindowState(Qt::WindowMinimized)) { + if (lParam == SW_PARENTOPENING) { + QShowEvent e; + qt_sendSpontaneousEvent(widget, &e); + widget->showChildren(true); + } else if (lParam == SW_PARENTCLOSING) { + QHideEvent e; + qt_sendSpontaneousEvent(widget, &e); + widget->hideChildren(true); + } + } + if (!wParam && autoCaptureWnd == widget->internalWinId()) + releaseAutoCapture(); + result = false; + break; + + case WM_PALETTECHANGED: // our window changed palette + if (QColormap::hPal() && (WId)wParam == widget->internalWinId()) + RETURN(0); // otherwise: FALL THROUGH! + // FALL THROUGH + case WM_QUERYNEWPALETTE: // realize own palette + if (QColormap::hPal()) { + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + HDC hdc = GetDC(widget->internalWinId()); + HPALETTE hpalOld = SelectPalette(hdc, QColormap::hPal(), FALSE); + uint n = RealizePalette(hdc); + if (n) + InvalidateRect(widget->internalWinId(), 0, TRUE); + SelectPalette(hdc, hpalOld, TRUE); + RealizePalette(hdc); + ReleaseDC(widget->internalWinId(), hdc); + RETURN(n); + } + break; + case WM_CLOSE: // close window + widget->translateCloseEvent(msg); + RETURN(0); // always handled + + case WM_DESTROY: // destroy window + if (hwnd == curWin) { + QWidget *enter = QWidget::mouseGrabber(); + if (enter == widget) + enter = 0; + QApplicationPrivate::dispatchEnterLeave(enter, widget); + curWin = enter ? enter->effectiveWinId() : 0; + qt_last_mouse_receiver = enter; + } + if (widget == popupButtonFocus) + popupButtonFocus = 0; + result = false; + break; + +#ifndef Q_WS_WINCE + case WM_WINDOWPOSCHANGING: + { + result = false; + if (widget->isWindow()) { + WINDOWPOS *winPos = (WINDOWPOS *)lParam; + if (widget->layout() && widget->layout()->hasHeightForWidth() + && !(winPos->flags & (SWP_NOCOPYBITS | SWP_NOSIZE))) { + QRect fs = widget->frameStrut(); + QRect rect = widget->geometry(); + QRect newRect = QRect(winPos->x + fs.left(), + winPos->y + fs.top(), + winPos->cx - fs.left() - fs.right(), + winPos->cy - fs.top() - fs.bottom()); + + QSize newSize = QLayout::closestAcceptableSize(widget, newRect.size()); + + int dh = newSize.height() - newRect.height(); + int dw = newSize.width() - newRect.width(); + if (!dw && ! dh) + break; // Size OK + + if (rect.y() != newRect.y()) { + newRect.setTop(newRect.top() - dh); + } else { + newRect.setBottom(newRect.bottom() + dh); + } + + if (rect.x() != newRect.x()) { + newRect.setLeft(newRect.left() - dw); + } else { + newRect.setRight(newRect.right() + dw); + } + + winPos->x = newRect.x() - fs.left(); + winPos->y = newRect.y() - fs.top(); + winPos->cx = newRect.width() + fs.left() + fs.right(); + winPos->cy = newRect.height() + fs.top() + fs.bottom(); + + RETURN(0); + } + if (widget->windowFlags() & Qt::WindowStaysOnBottomHint) { + winPos->hwndInsertAfter = HWND_BOTTOM; + } + } + } + break; + + case WM_GETMINMAXINFO: + if (widget->xtra()) { + MINMAXINFO *mmi = (MINMAXINFO *)lParam; + QWExtra *x = widget->xtra(); + QRect fs = widget->frameStrut(); + if ( x->minw > 0 ) + mmi->ptMinTrackSize.x = x->minw + fs.right() + fs.left(); + if ( x->minh > 0 ) + mmi->ptMinTrackSize.y = x->minh + fs.top() + fs.bottom(); + qint32 maxw = (x->maxw >= x->minw) ? x->maxw : x->minw; + qint32 maxh = (x->maxh >= x->minh) ? x->maxh : x->minh; + if ( maxw < QWIDGETSIZE_MAX ) { + mmi->ptMaxTrackSize.x = maxw + fs.right() + fs.left(); + // windows with title bar have an implicit size limit of 112 pixels + if (widget->windowFlags() & Qt::WindowTitleHint) + mmi->ptMaxTrackSize.x = qMax(mmi->ptMaxTrackSize.x, 112); + } + if ( maxh < QWIDGETSIZE_MAX ) + mmi->ptMaxTrackSize.y = maxh + fs.top() + fs.bottom(); + RETURN(0); + } + break; + +#ifndef QT_NO_CONTEXTMENU + case WM_CONTEXTMENU: + { + // it's not VK_APPS or Shift+F10, but a click in the NC area + if (lParam != (int)0xffffffff) { + result = false; + break; + } + + QWidget *fw = QWidget::keyboardGrabber(); + if (!fw) { + if (QApplication::activePopupWidget()) + fw = (QApplication::activePopupWidget()->focusWidget() + ? QApplication::activePopupWidget()->focusWidget() + : QApplication::activePopupWidget()); + else if (QApplication::focusWidget()) + fw = QApplication::focusWidget(); + else if (widget) + fw = widget->window(); + } + if (fw && fw->isEnabled()) { + QPoint pos = fw->inputMethodQuery(Qt::ImMicroFocus).toRect().center(); + QContextMenuEvent e(QContextMenuEvent::Keyboard, pos, fw->mapToGlobal(pos), + qt_win_getKeyboardModifiers()); + result = qt_sendSpontaneousEvent(fw, &e); + } + } + break; +#endif +#endif + + case WM_IME_STARTCOMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_COMPOSITION: { + QWidget *fw = QApplication::focusWidget(); + QWinInputContext *im = fw ? qobject_cast(fw->inputContext()) : 0; + if (fw && im) { + if(message == WM_IME_STARTCOMPOSITION) + result = im->startComposition(); + else if (message == WM_IME_ENDCOMPOSITION) + result = im->endComposition(); + else if (message == WM_IME_COMPOSITION) + result = im->composition(lParam); + } + break; + } + case WM_IME_REQUEST: { + QWidget *fw = QApplication::focusWidget(); + QWinInputContext *im = fw ? qobject_cast(fw->inputContext()) : 0; + if (fw && im) { + if(wParam == IMR_RECONVERTSTRING) { + int ret = im->reconvertString((RECONVERTSTRING *)lParam); + if (ret == -1) { + result = false; + } else { + return ret; + } + } else if (wParam == IMR_CONFIRMRECONVERTSTRING) { + RETURN(TRUE); + } else { + // in all other cases, call DefWindowProc() + result = false; + } + } + break; + } +#ifndef Q_WS_WINCE + case WM_CHANGECBCHAIN: + case WM_DRAWCLIPBOARD: +#endif + case WM_RENDERFORMAT: + case WM_RENDERALLFORMATS: +#ifndef QT_NO_CLIPBOARD + case WM_DESTROYCLIPBOARD: + if (qt_clipboard) { + QClipboardEvent e(reinterpret_cast(&msg)); + qt_sendSpontaneousEvent(qt_clipboard, &e); + RETURN(0); + } + result = false; + break; +#endif //QT_NO_CLIPBOARD +#ifndef QT_NO_ACCESSIBILITY + case WM_GETOBJECT: + { + // Ignoring all requests while starting up + if (QApplication::startingUp() || QApplication::closingDown() || lParam != (LPARAM)OBJID_CLIENT) { + result = false; + break; + } + + typedef LRESULT (WINAPI *PtrLresultFromObject)(REFIID, WPARAM, LPUNKNOWN); + static PtrLresultFromObject ptrLresultFromObject = 0; + static bool oleaccChecked = false; + + if (!oleaccChecked) { + oleaccChecked = true; +#if !defined(Q_OS_WINCE) + ptrLresultFromObject = (PtrLresultFromObject)QSystemLibrary::resolve(QLatin1String("oleacc"), "LresultFromObject"); +#endif + } + if (ptrLresultFromObject) { + QAccessibleInterface *acc = QAccessible::queryAccessibleInterface(widget); + if (!acc) { + result = false; + break; + } + + // and get an instance of the IAccessibile implementation + IAccessible *iface = qt_createWindowsAccessible(acc); + res = ptrLresultFromObject(IID_IAccessible, wParam, iface); // ref == 2 + iface->Release(); // the client will release the object again, and then it will destroy itself + + if (res > 0) + RETURN(res); + } + } + result = false; + break; + case WM_GETTEXT: + if (!widget->isWindow()) { + int ret = 0; + QAccessibleInterface *acc = QAccessible::queryAccessibleInterface(widget); + if (acc) { + QString text = acc->text(QAccessible::Name, 0); + if (text.isEmpty()) + text = widget->objectName(); + ret = qMin(wParam - 1, text.size()); + text.resize(ret); + memcpy((void *)lParam, text.utf16(), (text.size() + 1) * sizeof(ushort)); + delete acc; + } + if (!ret) { + result = false; + break; + } + RETURN(ret); + } + result = false; + break; +#endif + case WT_PACKET: + if (ptrWTPacketsGet) { + if ((nPackets = ptrWTPacketsGet(qt_tablet_context, QT_TABLET_NPACKETQSIZE, &localPacketBuf))) { + result = widget->translateTabletEvent(msg, localPacketBuf, nPackets); + } + } + break; + case WT_PROXIMITY: + + #ifndef QT_NO_TABLETEVENT + if (ptrWTPacketsGet && ptrWTInfo) { + const bool enteredProximity = LOWORD(lParam) != 0; + PACKET proximityBuffer[1]; // we are only interested in the first packet in this case + const int totalPacks = ptrWTPacketsGet(qt_tablet_context, 1, proximityBuffer); + if (totalPacks > 0) { + const UINT currentCursor = proximityBuffer[0].pkCursor; + + UINT csr_physid; + ptrWTInfo(WTI_CURSORS + currentCursor, CSR_PHYSID, &csr_physid); + UINT csr_type; + ptrWTInfo(WTI_CURSORS + currentCursor, CSR_TYPE, &csr_type); + const UINT deviceIdMask = 0xFF6; // device type mask && device color mask + quint64 uniqueId = (csr_type & deviceIdMask); + uniqueId = (uniqueId << 32) | csr_physid; + + // initialising and updating the cursor should be done in response to + // WT_CSRCHANGE. We do it in WT_PROXIMITY because some wintab never send + // the event WT_CSRCHANGE even if asked with CXO_CSRMESSAGES + const QTabletCursorInfo *const globalCursorInfo = tCursorInfo(); + if (!globalCursorInfo->contains(uniqueId)) + tabletInit(uniqueId, csr_type, qt_tablet_context); + + currentTabletPointer = globalCursorInfo->value(uniqueId); + tabletUpdateCursor(currentTabletPointer, currentCursor); + } + qt_tabletChokeMouse = false; + + QTabletEvent tabletProximity(enteredProximity ? QEvent::TabletEnterProximity + : QEvent::TabletLeaveProximity, + QPoint(), QPoint(), QPointF(), currentTabletPointer.currentDevice, currentTabletPointer.currentPointerType, 0, 0, + 0, 0, 0, 0, 0, currentTabletPointer.llId); + QApplication::sendEvent(qApp, &tabletProximity); + } + #endif // QT_NO_TABLETEVENT + + break; +#ifdef Q_WS_WINCE_WM + case WM_SETFOCUS: { + HIMC hC; + hC = ImmGetContext(hwnd); + ImmSetOpenStatus(hC, TRUE); + ImmEscape(NULL, hC, IME_ESC_SET_MODE, (LPVOID)IM_SPELL); + result = false; + } + break; +#endif + case WM_KILLFOCUS: + if (!QWidget::find((HWND)wParam)) { // we don't get focus, so unset it now + if (!widget->hasFocus()) // work around Windows bug after minimizing/restoring + widget = (QETWidget*)QApplication::focusWidget(); + HWND focus = ::GetFocus(); + //if there is a current widget and the new widget belongs to the same toplevel window + //or if the current widget was embedded into non-qt window (i.e. we won't get WM_ACTIVATEAPP) + //then we clear the focus on the widget + //in case the new widget belongs to a different widget hierarchy, clearing the focus + //will be handled because the active window will change + const bool embedded = widget && ((QETWidget*)widget->window())->topData()->embedded; + if (widget && (embedded || ::IsChild(widget->window()->internalWinId(), focus))) { + widget->clearFocus(); + result = true; + } else { + result = false; + } + } else { + result = false; + } + break; + case WM_THEMECHANGED: + if ((widget->windowType() == Qt::Desktop) || !qApp || QApplication::closingDown() + || QApplication::type() == QApplication::Tty) + break; + + if (widget->testAttribute(Qt::WA_WState_Polished)) + QApplication::style()->unpolish(widget); + + if (widget->testAttribute(Qt::WA_WState_Polished)) + QApplication::style()->polish(widget); + widget->repolishStyle(*QApplication::style()); + if (widget->isVisible()) + widget->update(); + break; + +#ifndef Q_WS_WINCE + case WM_INPUTLANGCHANGE: { + wchar_t info[7]; + if (!GetLocaleInfo(MAKELCID(lParam, SORT_DEFAULT), LOCALE_IDEFAULTANSICODEPAGE, info, 6)) { + inputcharset = CP_ACP; + } else { + inputcharset = QString::fromWCharArray(info).toInt(); + } + QKeyMapper::changeKeyboard(); + break; + } +#else + case WM_COMMAND: { + bool OkCommand = (LOWORD(wParam) == 0x1); + bool CancelCommand = (LOWORD(wParam) == 0x2); + if (OkCommand) + QApplication::postEvent(widget, new QEvent(QEvent::OkRequest)); + if (CancelCommand) + widget->showMinimized(); + else +#ifndef QT_NO_MENUBAR + QMenuBar::wceCommands(LOWORD(wParam)); +#endif + result = true; + } + break; + case WM_HELP: + QApplication::postEvent(widget, new QEvent(QEvent::HelpRequest)); + result = true; + break; +#endif + + case WM_MOUSELEAVE: + // We receive a mouse leave for curWin, meaning + // the mouse was moved outside our widgets + if (widget->internalWinId() == curWin) { + bool dispatch = !widget->underMouse(); + // hasMouse is updated when dispatching enter/leave, + // so test if it is actually up-to-date + if (!dispatch) { + QRect geom = widget->geometry(); + if (widget->parentWidget() && !widget->isWindow()) { + QPoint gp = widget->parentWidget()->mapToGlobal(widget->pos()); + geom.setX(gp.x()); + geom.setY(gp.y()); + } + QPoint cpos = QCursor::pos(); + dispatch = !geom.contains(cpos); + if ( !dispatch && !QWidget::mouseGrabber()) { + QWidget *hittest = QApplication::widgetAt(cpos); + dispatch = !hittest || hittest->internalWinId() != curWin; + } + if (!dispatch) { + HRGN hrgn = qt_tryCreateRegion(QRegion::Rectangle, 0,0,0,0); + if (GetWindowRgn(curWin, hrgn) != ERROR) { + QPoint lcpos = widget->mapFromGlobal(cpos); + dispatch = !PtInRegion(hrgn, lcpos.x(), lcpos.y()); + } + DeleteObject(hrgn); + } + } + if (dispatch) { + if (qt_last_mouse_receiver && !qt_last_mouse_receiver->internalWinId()) + QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver); + else + QApplicationPrivate::dispatchEnterLeave(0, QWidget::find((WId)curWin)); + curWin = 0; + qt_last_mouse_receiver = 0; + } + } + break; + + case WM_CANCELMODE: + { + // this goes through QMenuBar's event filter + QEvent e(QEvent::ActivationChange); + QApplication::sendEvent(qApp, &e); + } + break; + + case WM_IME_NOTIFY: + // special handling for ime, only for widgets in a popup + if (wParam == IMN_OPENCANDIDATE) { + imeParentWnd = hwnd; + if (QApplication::activePopupWidget()) { + // temporarily disable the mouse grab to allow mouse input in + // the ime candidate window. The actual handle is untouched + if (autoCaptureWnd) + ReleaseCapture(); + } + } else if (wParam == IMN_CLOSECANDIDATE) { + imeParentWnd = 0; + if (QApplication::activePopupWidget()) { + // undo the action above, when candidate window is closed + if (autoCaptureWnd) + SetCapture(autoCaptureWnd); + } + } + result = false; + break; +#ifndef QT_NO_GESTURES +#if !defined(Q_WS_WINCE) || defined(QT_WINCE_GESTURES) + case WM_GESTURE: { + GESTUREINFO gi; + memset(&gi, 0, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); + BOOL bResult = false; + if (qAppPriv->GetGestureInfo) + bResult = qAppPriv->GetGestureInfo((HANDLE)msg.lParam, &gi); + if (bResult) { + if (gi.dwID == GID_BEGIN) { + // find the alien widget for the gesture position. + // This might not be accurate as the position is the center + // point of two fingers for multi-finger gestures. + QPoint pt(gi.ptsLocation.x, gi.ptsLocation.y); + QWidget *w = widget->childAt(widget->mapFromGlobal(pt)); + qAppPriv->gestureWidget = w ? w : widget; + } + if (qAppPriv->gestureWidget) + static_cast(qAppPriv->gestureWidget)->translateGestureEvent(msg, gi); + if (qAppPriv->CloseGestureInfoHandle) + qAppPriv->CloseGestureInfoHandle((HANDLE)msg.lParam); + if (gi.dwID == GID_END) + qAppPriv->gestureWidget = 0; + } else { + DWORD dwErr = GetLastError(); + if (dwErr > 0) + qWarning() << "translateGestureEvent: error = " << dwErr; + } + result = true; + break; + } +#endif // !defined(Q_WS_WINCE) || defined(QT_WINCE_GESTURES) +#endif // QT_NO_GESTURES +#ifndef QT_NO_CURSOR + case WM_SETCURSOR: { + QCursor *ovr = QApplication::overrideCursor(); + if (ovr) { + SetCursor(ovr->handle()); + RETURN(TRUE); + } + result = false; + break; + } +#endif + default: + result = false; // event was not processed + break; + } + } + + if (evt_type != QEvent::None) { // simple event + QEvent e(evt_type); + result = qt_sendSpontaneousEvent(widget, &e); + } + + if (result) + RETURN(false); + +do_default: + RETURN(QWinInputContext::DefWindowProc(hwnd,message,wParam,lParam)) +} + + +/***************************************************************************** + Modal widgets; We have implemented our own modal widget mechanism + to get total control. + A modal widget without a parent becomes application-modal. + A modal widget with a parent becomes modal to its parent and grandparents.. + + QApplicationPrivate::enterModal() + Enters modal state + Arguments: + QWidget *widget A modal widget + + QApplicationPrivate::leaveModal() + Leaves modal state for a widget + Arguments: + QWidget *widget A modal widget + *****************************************************************************/ + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + + releaseAutoCapture(); + ClipCursor(0); + QWidget *leave = qt_last_mouse_receiver; + if (!leave) + leave = QWidget::find((WId)curWin); + QApplicationPrivate::dispatchEnterLeave(0, leave); + qt_modal_stack->insert(0, widget); + app_do_modal = true; + curWin = 0; + qt_last_mouse_receiver = 0; + qt_win_ignoreNextMouseReleaseEvent = false; +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget) +{ + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + QPoint p(QCursor::pos()); + app_do_modal = false; // necessary, we may get recursively into qt_try_modal below + QWidget* w = QApplication::widgetAt(p.x(), p.y()); + QWidget *leave = qt_last_mouse_receiver; + if (!leave) + leave = QWidget::find((WId)curWin); + if (QWidget *grabber = QWidget::mouseGrabber()) { + w = grabber; + if (leave == w) + leave = 0; + } + QApplicationPrivate::dispatchEnterLeave(w, leave); // send synthetic enter event + curWin = w ? w->effectiveWinId() : 0; + qt_last_mouse_receiver = w; + } + qt_win_ignoreNextMouseReleaseEvent = true; + } + app_do_modal = qt_modal_stack != 0; +} + +bool qt_try_modal(QWidget *widget, MSG *msg, int& ret) +{ +#if defined(Q_OS_WINCE) + Q_UNUSED(ret); +#endif + QWidget * top = 0; + + if (QApplicationPrivate::tryModalHelper(widget, &top)) + return true; + + int type = msg->message; + + bool block_event = false; +#ifndef Q_WS_WINCE + if (type != WM_NCHITTEST) { +#endif + if ((type >= WM_MOUSEFIRST && type <= WM_MOUSELAST) || + type == WM_MOUSEWHEEL || type == WM_MOUSEHWHEEL || + type == WM_MOUSELEAVE || + (type >= WM_KEYFIRST && type <= WM_KEYLAST) +#ifndef Q_WS_WINCE + || type == WM_NCMOUSEMOVE +#endif + ) { + if (type == WM_MOUSEMOVE +#ifndef Q_WS_WINCE + || type == WM_NCMOUSEMOVE +#endif + ) { +#ifndef QT_NO_CURSOR + QCursor *c = qt_grab_cursor(); + if (!c) + c = QApplication::overrideCursor(); + if (c) // application cursor defined + SetCursor(c->handle()); + else + SetCursor(QCursor(Qt::ArrowCursor).handle()); +#endif // QT_NO_CURSOR + } + block_event = true; + } else if (type == WM_CLOSE) { + block_event = true; + } +#ifndef Q_WS_WINCE + else if (type == WM_MOUSEACTIVATE || type == WM_NCLBUTTONDOWN){ + if (!top->isActiveWindow()) { + top->activateWindow(); + } else { + QApplication::beep(); + } + block_event = true; + ret = MA_NOACTIVATEANDEAT; + } else if (type == WM_SYSCOMMAND) { + if (!(msg->wParam == SC_RESTORE && widget->isMinimized())) + block_event = true; + } + } +#endif + + return !block_event; +} + + +/***************************************************************************** + Popup widget mechanism + + openPopup() + Adds a widget to the list of popup widgets + Arguments: + QWidget *widget The popup widget to be added + + closePopup() + Removes a widget from the list of popup widgets + Arguments: + QWidget *widget The popup widget to be removed + *****************************************************************************/ + +void QApplicationPrivate::openPopup(QWidget *popup) +{ + if (!QApplicationPrivate::popupWidgets) + QApplicationPrivate::popupWidgets = new QWidgetList; + QApplicationPrivate::popupWidgets->append(popup); + if (!popup->isEnabled()) + return; + + // close any opened 'ime candidate window' + if (imeParentWnd) + ::SendMessage(imeParentWnd, WM_IME_ENDCOMPOSITION, 0, 0); + + if (QApplicationPrivate::popupWidgets->count() == 1 && !qt_nograb()) { + Q_ASSERT(popup->testAttribute(Qt::WA_WState_Created)); + setAutoCapture(popup->internalWinId()); // grab mouse/keyboard + } + // Popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + if (popup->focusWidget()) { + popup->focusWidget()->setFocus(Qt::PopupFocusReason); + } else if (QApplicationPrivate::popupWidgets->count() == 1) { // this was the first popup + if (QWidget *fw = QApplication::focusWidget()) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + QApplication::sendEvent(fw, &e); + } + } +} + +void QApplicationPrivate::closePopup(QWidget *popup) +{ + if (!QApplicationPrivate::popupWidgets) + return; + QApplicationPrivate::popupWidgets->removeAll(popup); + POINT curPos; + GetCursorPos(&curPos); + + // close any opened 'ime candidate window' + if (imeParentWnd) + ::SendMessage(imeParentWnd, WM_IME_ENDCOMPOSITION, 0, 0); + + if (QApplicationPrivate::popupWidgets->isEmpty()) { // this was the last popup + delete QApplicationPrivate::popupWidgets; + QApplicationPrivate::popupWidgets = 0; + replayPopupMouseEvent = (!popup->geometry().contains(QPoint(curPos.x, curPos.y)) + && !popup->testAttribute(Qt::WA_NoMouseReplay)); + if (!popup->isEnabled()) + return; + if (!qt_nograb()) // grabbing not disabled + releaseAutoCapture(); + QWidget *fw = QApplicationPrivate::active_window ? QApplicationPrivate::active_window->focusWidget() + : QApplication::focusWidget(); + if (fw) { + if (fw != QApplication::focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + QApplication::sendEvent(fw, &e); + } + } + } else { + // Popups are not focus-handled by the window system (the + // first popup grabbed the keyboard), so we have to do that + // manually: A popup was closed, so the previous popup gets + // the focus. + QWidget* aw = QApplicationPrivate::popupWidgets->last(); + if (QApplicationPrivate::popupWidgets->count() == 1) { + Q_ASSERT(aw->testAttribute(Qt::WA_WState_Created)); + setAutoCapture(aw->internalWinId()); + } + if (QWidget *fw = aw->focusWidget()) + fw->setFocus(Qt::PopupFocusReason); + } +} + + + + +/***************************************************************************** + Event translation; translates Windows events to Qt events + *****************************************************************************/ + +// +// Auto-capturing for mouse press and mouse release +// + +static void setAutoCapture(HWND h) +{ + if (autoCaptureWnd) + releaseAutoCapture(); + autoCaptureWnd = h; + SetCapture(h); +} + +static void releaseAutoCapture() +{ + if (autoCaptureWnd) { + ReleaseCapture(); + autoCaptureWnd = 0; + } +} + + +// +// Mouse event translation +// +// Non-client mouse messages are not translated +// + +static const ushort mouseTbl[] = { + WM_MOUSEMOVE, QEvent::MouseMove, 0, + WM_LBUTTONDOWN, QEvent::MouseButtonPress, Qt::LeftButton, + WM_LBUTTONUP, QEvent::MouseButtonRelease, Qt::LeftButton, + WM_LBUTTONDBLCLK, QEvent::MouseButtonDblClick, Qt::LeftButton, + WM_RBUTTONDOWN, QEvent::MouseButtonPress, Qt::RightButton, + WM_RBUTTONUP, QEvent::MouseButtonRelease, Qt::RightButton, + WM_RBUTTONDBLCLK, QEvent::MouseButtonDblClick, Qt::RightButton, + WM_MBUTTONDOWN, QEvent::MouseButtonPress, Qt::MidButton, + WM_MBUTTONUP, QEvent::MouseButtonRelease, Qt::MidButton, + WM_MBUTTONDBLCLK, QEvent::MouseButtonDblClick, Qt::MidButton, + // use XButton1 for now, the real X button is decided later + WM_XBUTTONDOWN, QEvent::MouseButtonPress, Qt::XButton1, + WM_XBUTTONUP, QEvent::MouseButtonRelease, Qt::XButton1, + WM_XBUTTONDBLCLK, QEvent::MouseButtonDblClick, Qt::XButton1, + +#ifndef Q_WS_WINCE + WM_NCMOUSEMOVE, QEvent::NonClientAreaMouseMove, 0, + WM_NCLBUTTONDOWN, QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton, + WM_NCLBUTTONUP, QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton, + WM_NCLBUTTONDBLCLK, QEvent::NonClientAreaMouseButtonDblClick, Qt::LeftButton, + WM_NCRBUTTONDOWN, QEvent::NonClientAreaMouseButtonPress, Qt::RightButton, + WM_NCRBUTTONUP, QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton, + WM_NCRBUTTONDBLCLK, QEvent::NonClientAreaMouseButtonDblClick, Qt::RightButton, + WM_NCMBUTTONDOWN, QEvent::NonClientAreaMouseButtonPress, Qt::MidButton, + WM_NCMBUTTONUP, QEvent::NonClientAreaMouseButtonRelease, Qt::MidButton, + WM_NCMBUTTONDBLCLK, QEvent::NonClientAreaMouseButtonDblClick, Qt::MidButton, +#endif + + 0, 0, 0 +}; + +static int translateButtonState(int s, int type, int button) +{ + Q_UNUSED(type); + Q_UNUSED(button); + int bst = 0; + if (s & MK_LBUTTON) + bst |= Qt::LeftButton; + if (s & MK_MBUTTON) + bst |= Qt::MidButton; + if (s & MK_RBUTTON) + bst |= Qt::RightButton; + if (s & MK_SHIFT) + bst |= Qt::ShiftModifier; + if (s & MK_CONTROL) + bst |= Qt::ControlModifier; + + if (s & MK_XBUTTON1) + bst |= Qt::XButton1; + if (s & MK_XBUTTON2) + bst |= Qt::XButton2; + + if (GetKeyState(VK_MENU) < 0) + bst |= Qt::AltModifier; + + if ((GetKeyState(VK_LWIN) < 0) || + (GetKeyState(VK_RWIN) < 0)) + bst |= Qt::MetaModifier; + + return bst; +} + +void qt_win_eatMouseMove() +{ + // after closing a windows dialog with a double click (i.e. open a file) + // the message queue still contains a dubious WM_MOUSEMOVE message where + // the left button is reported to be down (wParam != 0). + // remove all those messages (usually 1) and post the last one with a + // reset button state + + MSG msg = {0, 0, 0, 0, 0, {0, 0} }; + while (PeekMessage(&msg, 0, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) + ; + if (msg.message == WM_MOUSEMOVE) + PostMessage(msg.hwnd, msg.message, 0, msg.lParam); +} + +// In DnD, the mouse release event never appears, so the +// mouse button state machine must be manually reset +void QApplication::winMouseButtonUp() +{ + qt_button_down = 0; + releaseAutoCapture(); +} + +void QETWidget::repolishStyle(QStyle &) +{ + QEvent e(QEvent::StyleChange); + QApplication::sendEvent(this, &e); +} + +bool QETWidget::translateMouseEvent(const MSG &msg) +{ + if (!isWindow() && testAttribute(Qt::WA_NativeWindow)) + Q_ASSERT(internalWinId()); + + static QPoint pos; + static POINT gpos={-1,-1}; + QEvent::Type type; // event parameters + int button; + int state; + int i; + + if (sm_blockUserInput) //block user interaction during session management + return true; + + // Compress mouse move events + if (msg.message == WM_MOUSEMOVE) { + MSG mouseMsg; + while (PeekMessage(&mouseMsg, msg.hwnd, WM_MOUSEFIRST, + WM_MOUSELAST, PM_NOREMOVE)) { + if (mouseMsg.message == WM_MOUSEMOVE) { +#define PEEKMESSAGE_IS_BROKEN 1 +#ifdef PEEKMESSAGE_IS_BROKEN + // Since the Windows PeekMessage() function doesn't + // correctly return the wParam for WM_MOUSEMOVE events + // if there is a key release event in the queue + // _before_ the mouse event, we have to also consider + // key release events (kls 2003-05-13): + MSG keyMsg; + bool done = false; + while (PeekMessage(&keyMsg, 0, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE)) { + if (keyMsg.time < mouseMsg.time) { + if ((keyMsg.lParam & 0xC0000000) == 0x40000000) { + PeekMessage(&keyMsg, 0, keyMsg.message, + keyMsg.message, PM_REMOVE); + } else { + done = true; + break; + } + } else { + break; // no key event before the WM_MOUSEMOVE event + } + } + if (done) + break; +#else + // Actually the following 'if' should work instead of + // the above key event checking, but apparently + // PeekMessage() is broken :-( + if (mouseMsg.wParam != msg.wParam) + break; // leave the message in the queue because + // the key state has changed +#endif + MSG *msgPtr = (MSG *)(&msg); + // Update the passed in MSG structure with the + // most recent one. + msgPtr->lParam = mouseMsg.lParam; + msgPtr->wParam = mouseMsg.wParam; + // Extract the x,y coordinates from the lParam as we do in the WndProc + msgPtr->pt.x = GET_X_LPARAM(mouseMsg.lParam); + msgPtr->pt.y = GET_Y_LPARAM(mouseMsg.lParam); + ClientToScreen(msg.hwnd, &(msgPtr->pt)); + // Remove the mouse move message + PeekMessage(&mouseMsg, msg.hwnd, WM_MOUSEMOVE, + WM_MOUSEMOVE, PM_REMOVE); + } else { + break; // there was no more WM_MOUSEMOVE event + } + } + } + + for (i=0; (UINT)mouseTbl[i] != msg.message && mouseTbl[i]; i += 3) + ; + if (!mouseTbl[i]) + return false; + type = (QEvent::Type)mouseTbl[++i]; // event type + button = mouseTbl[++i]; // which button + if (button == Qt::XButton1) { + switch(GET_XBUTTON_WPARAM(msg.wParam)) { + case XBUTTON1: + button = Qt::XButton1; + break; + case XBUTTON2: + button = Qt::XButton2; + break; + } + } +#ifndef Q_OS_WINCE + static bool trackMouseEventLookup = false; + typedef BOOL (WINAPI *PtrTrackMouseEvent)(LPTRACKMOUSEEVENT); + static PtrTrackMouseEvent ptrTrackMouseEvent = 0; +#endif + state = translateButtonState(msg.wParam, type, button); // button state + const QPoint widgetPos = mapFromGlobal(QPoint(msg.pt.x, msg.pt.y)); + QWidget *alienWidget = !internalWinId() ? this : childAt(widgetPos); + if (alienWidget && alienWidget->internalWinId()) + alienWidget = 0; + + if (type == QEvent::MouseMove || type == QEvent::NonClientAreaMouseMove + || type == QEvent::TabletMove) { + + if (!(state & Qt::MouseButtonMask)) + qt_button_down = 0; +#ifndef QT_NO_CURSOR + QCursor *c = qt_grab_cursor(); + if (!c) + c = QApplication::overrideCursor(); + if (c) // application cursor defined + SetCursor(c->handle()); + else if (type != QEvent::NonClientAreaMouseMove && !qt_button_down) { + // use widget cursor if widget is enabled + QWidget *w = alienWidget ? alienWidget : this; + while (!w->isWindow() && !w->isEnabled()) + w = w->parentWidget(); + SetCursor(w->cursor().handle()); + } +#endif // QT_NO_CURSOR + + HWND id = effectiveWinId(); + QWidget *mouseGrabber = QWidget::mouseGrabber(); + QWidget *activePopupWidget = QApplication::activePopupWidget(); + if (mouseGrabber) { + if (!activePopupWidget || (activePopupWidget == this && !rect().contains(widgetPos))) + id = mouseGrabber->effectiveWinId(); + } else if (type == QEvent::NonClientAreaMouseMove) { + id = 0; + } + + if (curWin != id) { // new current window + if (id == 0) { + QWidget *leave = qt_last_mouse_receiver; + if (!leave) + leave = QWidget::find(curWin); + QApplicationPrivate::dispatchEnterLeave(0, leave); + qt_last_mouse_receiver = 0; + curWin = 0; + } else { + QWidget *leave = 0; + if (curWin && qt_last_mouse_receiver) + leave = qt_last_mouse_receiver; + else + leave = QWidget::find(curWin); + QWidget *enter = alienWidget ? alienWidget : this; + if (mouseGrabber && activePopupWidget) { + if (leave != mouseGrabber) + enter = mouseGrabber; + else + enter = activePopupWidget == this ? this : mouseGrabber; + } + QApplicationPrivate::dispatchEnterLeave(enter, leave); + qt_last_mouse_receiver = enter; + curWin = enter ? enter->effectiveWinId() : 0; + } +#ifndef Q_OS_WINCE + + if (curWin != 0) { + if (!trackMouseEventLookup) { + trackMouseEventLookup = true; + ptrTrackMouseEvent = (PtrTrackMouseEvent)QSystemLibrary::resolve(QLatin1String("comctl32"), "_TrackMouseEvent"); + } + if (ptrTrackMouseEvent && !qApp->d_func()->inPopupMode()) { + // We always have to set the tracking, since + // Windows detects more leaves than we do.. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = 0x00000002; // TME_LEAVE + tme.hwndTrack = curWin; // Track on window receiving msgs + tme.dwHoverTime = (DWORD)-1; // HOVER_DEFAULT + ptrTrackMouseEvent(&tme); + } + } +#endif // Q_OS_WINCE + } + + POINT curPos = msg.pt; + if (curPos.x == gpos.x && curPos.y == gpos.y) + return true; // same global position + gpos = curPos; + + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + ScreenToClient(internalWinId(), &curPos); + + pos.rx() = curPos.x; + pos.ry() = curPos.y; + pos = d_func()->mapFromWS(pos); + } else { + gpos = msg.pt; + pos = mapFromGlobal(QPoint(gpos.x, gpos.y)); + + // mouse button pressed + if (!qt_button_down && (type == QEvent::MouseButtonPress || type == QEvent::MouseButtonDblClick)) { + QWidget *tlw = window(); + if (QWidget *child = tlw->childAt(mapTo(tlw, pos))) + qt_button_down = child; + else + qt_button_down = this; + } + } + + bool res = false; + + bool nonClientAreaEvent = type >= QEvent::NonClientAreaMouseMove + && type <= QEvent::NonClientAreaMouseButtonDblClick; + + if (qApp->d_func()->inPopupMode()) { // in popup mode + + if (nonClientAreaEvent) + return false; + + replayPopupMouseEvent = false; + QWidget* activePopupWidget = QApplication::activePopupWidget(); + QWidget *target = activePopupWidget; + const QPoint globalPos(gpos.x, gpos.y); + + if (target != this) { + if ((windowType() == Qt::Popup) && rect().contains(pos) && 0) + target = this; + else // send to last popup + pos = target->mapFromGlobal(globalPos); + } + QWidget *popupChild = target->childAt(pos); + bool releaseAfter = false; + switch (type) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + popupButtonFocus = popupChild; + break; + case QEvent::MouseButtonRelease: + case QEvent::TabletRelease: + + releaseAfter = true; + break; + default: + break; // nothing for mouse move + } + + if (target->isEnabled()) { + if (popupButtonFocus) { + target = popupButtonFocus; + } else if (popupChild) { + target = popupChild; + } + + pos = target->mapFromGlobal(globalPos); + QMouseEvent e(type, pos, globalPos, + Qt::MouseButton(button), + Qt::MouseButtons(state & Qt::MouseButtonMask), + Qt::KeyboardModifiers(state & Qt::KeyboardModifierMask)); + res = QApplicationPrivate::sendMouseEvent(target, &e, alienWidget, this, &qt_button_down, + qt_last_mouse_receiver); + res = res && e.isAccepted(); + } else { + // close disabled popups when a mouse button is pressed or released + switch (type) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonRelease: + target->close(); + break; + default: + break; + } + } + + if (releaseAfter) { + popupButtonFocus = 0; + qt_button_down = 0; + } + +#ifndef Q_OS_WINCE + if (type == QEvent::MouseButtonPress + && QApplication::activePopupWidget() != activePopupWidget + && ptrTrackMouseEvent + && curWin) { + // Since curWin is already the window we clicked on, + // we have to setup the mouse tracking here. + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = 0x00000002; // TME_LEAVE + tme.hwndTrack = curWin; // Track on window receiving msgs + tme.dwHoverTime = (DWORD)-1; // HOVER_DEFAULT + ptrTrackMouseEvent(&tme); + } +#endif + if (type == QEvent::MouseButtonPress + && QApplication::activePopupWidget() != activePopupWidget + && replayPopupMouseEvent) { + // the popup disappeared. Replay the event + QWidget* w = QApplication::widgetAt(gpos.x, gpos.y); + if (w && !QApplicationPrivate::isBlockedByModal(w)) { + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + HWND hwndTarget = w->effectiveWinId(); + if (QWidget::mouseGrabber() == 0) + setAutoCapture(hwndTarget); + if (!w->isActiveWindow()) + w->activateWindow(); + POINT widgetpt = gpos; + ScreenToClient(hwndTarget, &widgetpt); + LPARAM lParam = MAKELPARAM(widgetpt.x, widgetpt.y); + PostMessage(hwndTarget, msg.message, msg.wParam, lParam); + } + } else if (type == QEvent::MouseButtonRelease && button == Qt::RightButton + && QApplication::activePopupWidget() == activePopupWidget) { + // popup still alive and received right-button-release +#if !defined(QT_NO_CONTEXTMENU) + QContextMenuEvent e2(QContextMenuEvent::Mouse, pos, globalPos, + qt_win_getKeyboardModifiers()); + bool res2 = QApplication::sendSpontaneousEvent( target, &e2 ); + if (!res) // RMB not accepted + res = res2 && e2.isAccepted(); +#endif + } + } else { // not popup mode + int bs = state & Qt::MouseButtonMask; + if ((type == QEvent::MouseButtonPress || + type == QEvent::MouseButtonDblClick) && bs == button) { + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + if (QWidget::mouseGrabber() == 0) + setAutoCapture(internalWinId()); + } else if (type == QEvent::MouseButtonRelease && bs == 0) { + if (QWidget::mouseGrabber() == 0) + releaseAutoCapture(); + } + + const QPoint globalPos(gpos.x,gpos.y); + QWidget *widget = QApplicationPrivate::pickMouseReceiver(this, globalPos, pos, type, + Qt::MouseButtons(bs), + qt_button_down, alienWidget); + if (!widget) + return false; // don't send event + + QMouseEvent e(type, pos, globalPos, Qt::MouseButton(button), + Qt::MouseButtons(state & Qt::MouseButtonMask), + Qt::KeyboardModifiers(state & Qt::KeyboardModifierMask)); + + res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, + qt_last_mouse_receiver); + + // non client area events are only informational, you cannot "handle" them + res = res && e.isAccepted() && !nonClientAreaEvent; +#if !defined(QT_NO_CONTEXTMENU) + if (type == QEvent::MouseButtonRelease && button == Qt::RightButton) { + QContextMenuEvent e2(QContextMenuEvent::Mouse, pos, globalPos, + qt_win_getKeyboardModifiers()); + bool res2 = QApplication::sendSpontaneousEvent(widget, &e2); + if (!res) + res = res2 && e2.isAccepted(); + } +#endif + + if (type != QEvent::MouseMove) + pos.rx() = pos.ry() = -9999; // init for move compression + } + return res; +} + +bool QETWidget::translateWheelEvent(const MSG &msg) +{ + int state = 0; + + if (sm_blockUserInput) // block user interaction during session management + return true; + + state = translateButtonState(GET_KEYSTATE_WPARAM(msg.wParam), 0, 0); + + int delta; + if (msg.message == WM_MOUSEWHEEL || msg.message == WM_MOUSEHWHEEL) + delta = (short) HIWORD (msg.wParam); + else + delta = (int) msg.wParam; + + Qt::Orientation orient = (msg.message == WM_MOUSEHWHEEL || state&Qt::AltModifier +#if 0 + // disabled for now - Trenton's one-wheel mouse makes trouble... + // "delta" for usual wheels is +-120. +-240 seems to indicate + // the second wheel see more recent MSDN for WM_MOUSEWHEEL + + ( // <- parantheses added to make update happy, remove if the + // #if 0 is removed + || delta == 240 || delta == -240)?Qt::Horizontal:Vertical; + if (delta == 240 || delta == -240) + delta /= 2; +#endif + ) ? Qt::Horizontal : Qt::Vertical; + + // according to the MSDN documentation on WM_MOUSEHWHEEL: + // a positive value indicates that the wheel was rotated to the right; + // a negative value indicates that the wheel was rotated to the left. + // Qt defines this value as the exact opposite, so we have to flip the value! + if (msg.message == WM_MOUSEHWHEEL) + delta = -delta; + + QPoint globalPos; + + globalPos.rx() = (short)LOWORD (msg.lParam); + globalPos.ry() = (short)HIWORD (msg.lParam); + + + // if there is a widget under the mouse and it is not shadowed + // by modality, we send the event to it first + int ret = 0; + QWidget* w = QApplication::widgetAt(globalPos); + if (!w || !qt_try_modal(w, (MSG*)&msg, ret)) { + //synaptics touchpad shows its own widget at this position + //so widgetAt() will fail with that HWND, try child of this widget + w = this->childAt(this->mapFromGlobal(globalPos)); + if (!w) + w = this; + } + + // send the event to the widget or its ancestors + { + QWidget* popup = QApplication::activePopupWidget(); + if (popup && w->window() != popup) + popup->close(); +#ifndef QT_NO_WHEELEVENT + QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, + Qt::MouseButtons(state & Qt::MouseButtonMask), + Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); + + if (QApplication::sendSpontaneousEvent(w, &e)) +#else + Q_UNUSED(orient); +#endif //QT_NO_WHEELEVENT + return true; + } + + // send the event to the widget that has the focus or its ancestors, if different + if (w != QApplication::focusWidget() && (w = QApplication::focusWidget())) { + QWidget* popup = QApplication::activePopupWidget(); + if (popup && w->window() != popup) + popup->close(); +#ifndef QT_NO_WHEELEVENT + QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, + Qt::MouseButtons(state & Qt::MouseButtonMask), + Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); + if (QApplication::sendSpontaneousEvent(w, &e)) +#endif //QT_NO_WHEELEVENT + return true; + } + return false; +} + + +// +// Windows Wintab to QTabletEvent translation +// + +// the following is adapted from the wintab syspress example (public domain) +/* -------------------------------------------------------------------------- */ +// Initialize the "static" information of a cursor device (pen, airbrush, etc). +// The QTabletDeviceData is initialized with the data that do not change in time +// (number of button, type of device, etc) but do not initialize the variable data +// (e.g.: pen or eraser) +#ifndef QT_NO_TABLETEVENT + +static void tabletInit(const quint64 uniqueId, const UINT csr_type, HCTX hTab) +{ + Q_ASSERT(ptrWTInfo); + Q_ASSERT(ptrWTGet); + + Q_ASSERT(!tCursorInfo()->contains(uniqueId)); + + /* browse WinTab's many info items to discover pressure handling. */ + AXIS np; + LOGCONTEXT lc; + + /* get the current context for its device variable. */ + ptrWTGet(hTab, &lc); + + /* get the size of the pressure axis. */ + QTabletDeviceData tdd; + tdd.llId = uniqueId; + + ptrWTInfo(WTI_DEVICES + lc.lcDevice, DVC_NPRESSURE, &np); + tdd.minPressure = int(np.axMin); + tdd.maxPressure = int(np.axMax); + + ptrWTInfo(WTI_DEVICES + lc.lcDevice, DVC_TPRESSURE, &np); + tdd.minTanPressure = int(np.axMin); + tdd.maxTanPressure = int(np.axMax); + + LOGCONTEXT lcMine; + + /* get default region */ + ptrWTInfo(WTI_DEFCONTEXT, 0, &lcMine); + + tdd.minX = 0; + tdd.maxX = int(lcMine.lcInExtX) - int(lcMine.lcInOrgX); + + tdd.minY = 0; + tdd.maxY = int(lcMine.lcInExtY) - int(lcMine.lcInOrgY); + + tdd.minZ = 0; + tdd.maxZ = int(lcMine.lcInExtZ) - int(lcMine.lcInOrgZ); + + const uint cursorTypeBitMask = 0x0F06; // bitmask to find the specific cursor type (see Wacom FAQ) + if (((csr_type & 0x0006) == 0x0002) && ((csr_type & cursorTypeBitMask) != 0x0902)) { + tdd.currentDevice = QTabletEvent::Stylus; + } else { + switch (csr_type & cursorTypeBitMask) { + case 0x0802: + tdd.currentDevice = QTabletEvent::Stylus; + break; + case 0x0902: + tdd.currentDevice = QTabletEvent::Airbrush; + break; + case 0x0004: + tdd.currentDevice = QTabletEvent::FourDMouse; + break; + case 0x0006: + tdd.currentDevice = QTabletEvent::Puck; + break; + case 0x0804: + tdd.currentDevice = QTabletEvent::RotationStylus; + break; + default: + tdd.currentDevice = QTabletEvent::NoDevice; + } + } + tCursorInfo()->insert(uniqueId, tdd); +} +#endif // QT_NO_TABLETEVENT + +// Update the "dynamic" information of a cursor device (pen, airbrush, etc). +// The dynamic information is the information of QTabletDeviceData that can change +// in time (eraser or pen if a device is turned around). +#ifndef QT_NO_TABLETEVENT + +static void tabletUpdateCursor(QTabletDeviceData &tdd, const UINT currentCursor) +{ + switch (currentCursor % 3) { // %3 for dual track + case 0: + tdd.currentPointerType = QTabletEvent::Cursor; + break; + case 1: + tdd.currentPointerType = QTabletEvent::Pen; + break; + case 2: + tdd.currentPointerType = QTabletEvent::Eraser; + break; + default: + tdd.currentPointerType = QTabletEvent::UnknownPointer; + } +} +#endif // QT_NO_TABLETEVENT + +bool QETWidget::translateTabletEvent(const MSG &msg, PACKET *localPacketBuf, + int numPackets) +{ + Q_UNUSED(msg); + POINT ptNew; + static DWORD btnNew, btnOld, btnChange; + qreal prsNew; + ORIENTATION ort; + static bool button_pressed = false; + int i, + tiltX, + tiltY; + bool sendEvent = false; + QEvent::Type t; + int z = 0; + qreal rotation = 0.0; + qreal tangentialPressure; + + // the most common event that we get... + t = QEvent::TabletMove; + for (i = 0; i < numPackets; i++) { + // get the unique ID of the device... + btnOld = btnNew; + btnNew = localPacketBuf[i].pkButtons; + btnChange = btnOld ^ btnNew; + + if (btnNew & btnChange) { + button_pressed = true; + t = QEvent::TabletPress; + } + ptNew.x = UINT(localPacketBuf[i].pkX); + ptNew.y = UINT(localPacketBuf[i].pkY); +#ifndef QT_NO_TABLETEVENT + z = (currentTabletPointer.currentDevice == QTabletEvent::FourDMouse) ? UINT(localPacketBuf[i].pkZ) : 0; +#else + Q_UNUSED(z); +#endif // QT_NO_TABLETEVENT + prsNew = 0.0; + QRect desktopArea = QApplication::desktop()->geometry(); + QPointF hiResGlobal = currentTabletPointer.scaleCoord(ptNew.x, ptNew.y, desktopArea.left(), + desktopArea.width(), desktopArea.top(), + desktopArea.height()); + + if (btnNew) { +#ifndef QT_NO_TABLETEVENT + if (currentTabletPointer.currentPointerType == QTabletEvent::Pen || currentTabletPointer.currentPointerType == QTabletEvent::Eraser) + prsNew = localPacketBuf[i].pkNormalPressure + / qreal(currentTabletPointer.maxPressure + - currentTabletPointer.minPressure); + else +#endif // QT_NO_TABLETEVENT + prsNew = 0; + } else if (button_pressed) { + // One button press, should only give one button release + t = QEvent::TabletRelease; + button_pressed = false; + } + QPoint globalPos(qRound(hiResGlobal.x()), qRound(hiResGlobal.y())); + + if (t == QEvent::TabletPress) + { + qt_button_down = QApplication::widgetAt(globalPos); + } + + // make sure the tablet event get's sent to the proper widget... + QWidget *w = 0; + + if (qt_button_down) + w = qt_button_down; // Pass it to the thing that's grabbed it. + else + w = QApplication::widgetAt(globalPos); + + if (!w) + w = this; + + if (t == QEvent::TabletRelease) + { + if (qt_win_ignoreNextMouseReleaseEvent) { + qt_win_ignoreNextMouseReleaseEvent = false; + if (qt_button_down && qt_button_down->internalWinId() == autoCaptureWnd) { + releaseAutoCapture(); + qt_button_down = 0; + } + } + + } + + QPoint localPos = w->mapFromGlobal(globalPos); +#ifndef QT_NO_TABLETEVENT + if (currentTabletPointer.currentDevice == QTabletEvent::Airbrush) { + tangentialPressure = localPacketBuf[i].pkTangentPressure + / qreal(currentTabletPointer.maxTanPressure + - currentTabletPointer.minTanPressure); + } else { + tangentialPressure = 0.0; + } +#else + tangentialPressure = 0.0; +#endif // QT_NO_TABLETEVENT + + if (!qt_tablet_tilt_support) { + tiltX = tiltY = 0; + rotation = 0.0; + } else { + ort = localPacketBuf[i].pkOrientation; + // convert from azimuth and altitude to x tilt and y tilt + // what follows is the optimized version. Here are the equations + // I used to get to this point (in case things change :) + // X = sin(azimuth) * cos(altitude) + // Y = cos(azimuth) * cos(altitude) + // Z = sin(altitude) + // X Tilt = arctan(X / Z) + // Y Tilt = arctan(Y / Z) + double radAzim = (ort.orAzimuth / 10) * (Q_PI / 180); + //double radAlt = abs(ort.orAltitude / 10) * (Q_PI / 180); + double tanAlt = tan((abs(ort.orAltitude / 10)) * (Q_PI / 180)); + + double degX = atan(sin(radAzim) / tanAlt); + double degY = atan(cos(radAzim) / tanAlt); + tiltX = int(degX * (180 / Q_PI)); + tiltY = int(-degY * (180 / Q_PI)); + rotation = ort.orTwist; + } +#ifndef QT_NO_TABLETEVENT + QTabletEvent e(t, localPos, globalPos, hiResGlobal, currentTabletPointer.currentDevice, + currentTabletPointer.currentPointerType, prsNew, tiltX, tiltY, + tangentialPressure, rotation, z, QApplication::keyboardModifiers(), currentTabletPointer.llId); + sendEvent = QApplication::sendSpontaneousEvent(w, &e); +#endif // QT_NO_TABLETEVENT + } + return sendEvent; +} + +extern bool qt_is_gui_used; + + +#ifndef QT_NO_TABLETEVENT + +static void initWinTabFunctions() +{ +#if defined(Q_OS_WINCE) + return; +#else + if (!qt_is_gui_used) + return; + + QSystemLibrary library(QLatin1String("wintab32")); + ptrWTInfo = (PtrWTInfo)library.resolve("WTInfoW"); + ptrWTGet = (PtrWTGet)library.resolve("WTGetW"); + ptrWTEnable = (PtrWTEnable)library.resolve("WTEnable"); + ptrWTOverlap = (PtrWTEnable)library.resolve("WTOverlap"); + ptrWTPacketsGet = (PtrWTPacketsGet)library.resolve("WTPacketsGet"); +#endif // Q_OS_WINCE +} +#endif // QT_NO_TABLETEVENT + + +// +// Paint event translation +// +bool QETWidget::translatePaintEvent(const MSG &msg) +{ + if (!isWindow() && testAttribute(Qt::WA_NativeWindow)) + Q_ASSERT(internalWinId()); + + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + if (!GetUpdateRect(internalWinId(), 0, FALSE)) { // The update bounding rect is invalid + d_func()->hd = 0; + setAttribute(Qt::WA_PendingUpdate, false); + return false; + } + + if (msg.message == WM_ERASEBKGND) + return true; + + setAttribute(Qt::WA_PendingUpdate, false); + + if (d_func()->isGLWidget) { + if (d_func()->usesDoubleBufferedGLContext) + InvalidateRect(internalWinId(), 0, false); + } else { + const QRegion dirtyInBackingStore(qt_dirtyRegion(this)); + // Make sure the invalidated region contains the region we're about to repaint. + // BeginPaint will set the clip to the invalidated region and it is impossible + // to enlarge it afterwards (only shrink it). Using GetDCEx is not suffient + // as it may return an invalid context (especially on Windows Vista). + if (!dirtyInBackingStore.isEmpty()) + InvalidateRgn(internalWinId(), dirtyInBackingStore.handle(), false); + } + PAINTSTRUCT ps; + d_func()->hd = BeginPaint(internalWinId(), &ps); + + const QRect updateRect(QPoint(ps.rcPaint.left, ps.rcPaint.top), + QPoint(ps.rcPaint.right, ps.rcPaint.bottom)); + + // Mapping region from system to qt (32 bit) coordinate system. + d_func()->syncBackingStore(updateRect.translated(data->wrect.topLeft())); + + d_func()->hd = 0; + EndPaint(internalWinId(), &ps); + + return true; +} + +// +// Window move and resize (configure) events +// + +bool QETWidget::translateConfigEvent(const MSG &msg) +{ + if (!testAttribute(Qt::WA_WState_Created)) // in QWidget::create() + return true; + if (testAttribute(Qt::WA_WState_ConfigPending)) + return true; + if (testAttribute(Qt::WA_DontShowOnScreen)) + return true; + if (!isWindow()) + return true; + setAttribute(Qt::WA_WState_ConfigPending); // set config flag + QRect cr = geometry(); + if (msg.message == WM_SIZE) { // resize event + WORD a = LOWORD(msg.lParam); + WORD b = HIWORD(msg.lParam); + QSize oldSize = size(); + QSize newSize(a, b); +#ifdef Q_WS_WINCE_WM + if (isFullScreen() && (oldSize.width() == newSize.height()) && (oldSize.height() == newSize.width())) + qt_wince_hide_taskbar(internalWinId()); +#endif + cr.setSize(newSize); + if (msg.wParam != SIZE_MINIMIZED) + data->crect = cr; + if (isWindow()) { // update title/icon text + d_func()->createTLExtra(); + // Capture SIZE_MINIMIZED without preceding WM_SYSCOMMAND + // (like Windows+M) + if (msg.wParam == SIZE_MINIMIZED && !isMinimized()) { +#ifndef Q_WS_WINCE + const QString title = windowIconText(); + if (!title.isEmpty()) + d_func()->setWindowTitle_helper(title); +#endif + data->window_state |= Qt::WindowMinimized; + if (isVisible()) { + QHideEvent e; + QApplication::sendSpontaneousEvent(this, &e); + hideChildren(true); + } + } else if (msg.wParam != SIZE_MINIMIZED) { + bool window_state_changed = false; + Qt::WindowStates oldstate = Qt::WindowStates(dataPtr()->window_state); + if (isMinimized()) { +#ifndef Q_WS_WINCE + const QString title = windowTitle(); + if (!title.isEmpty()) + d_func()->setWindowTitle_helper(title); +#endif + data->window_state &= ~Qt::WindowMinimized; + showChildren(true); + QShowEvent e; + QApplication::sendSpontaneousEvent(this, &e); + // Capture SIZE_MAXIMIZED and SIZE_RESTORED without preceding WM_SYSCOMMAND + // (Aero Snap on Win7) + } else if (msg.wParam == SIZE_MAXIMIZED && !isMaximized()) { + data->window_state |= Qt::WindowMaximized; + window_state_changed = true; + } else if (msg.wParam == SIZE_RESTORED && isMaximized()) { + data->window_state &= ~(Qt::WindowMaximized); + window_state_changed = true; + } + if (window_state_changed) { + QWindowStateChangeEvent e(oldstate); + QApplication::sendSpontaneousEvent(this, &e); + } + } + } + if (msg.wParam != SIZE_MINIMIZED && oldSize != newSize) { + if (isVisible()) { + QTLWExtra *tlwExtra = maybeTopData(); + static bool slowResize = qgetenv("QT_SLOW_TOPLEVEL_RESIZE").toInt(); + const bool hasStaticContents = tlwExtra && tlwExtra->backingStore + && tlwExtra->backingStore->hasStaticContents(); + // If we have a backing store with static contents, we have to disable the top-level + // resize optimization in order to get invalidated regions for resized widgets. + // The optimization discards all invalidateBuffer() calls since we're going to + // repaint everything anyways, but that's not the case with static contents. + if (!slowResize && tlwExtra && !hasStaticContents) + tlwExtra->inTopLevelResize = true; + QResizeEvent e(newSize, oldSize); + QApplication::sendSpontaneousEvent(this, &e); + if (d_func()->paintOnScreen()) { + QRegion updateRegion(rect()); + if (testAttribute(Qt::WA_StaticContents)) + updateRegion -= QRect(0, 0, oldSize.width(), oldSize.height()); + d_func()->syncBackingStore(updateRegion); + } else { + d_func()->syncBackingStore(); + } + if (!slowResize && tlwExtra) + tlwExtra->inTopLevelResize = false; + } else { + QResizeEvent *e = new QResizeEvent(newSize, oldSize); + QApplication::postEvent(this, e); + } + } + } else if (msg.message == WM_MOVE) { // move event + int a = (int) (short) LOWORD(msg.lParam); + int b = (int) (short) HIWORD(msg.lParam); + QPoint oldPos = geometry().topLeft(); + QPoint newCPos(a, b); + // Ignore silly Windows move event to wild pos after iconify. +#if !defined(Q_WS_WINCE) + if (!IsIconic(internalWinId()) && newCPos != oldPos) { +#endif + cr.moveTopLeft(newCPos); + data->crect = cr; + if (isVisible()) { + QMoveEvent e(newCPos, oldPos); // cpos (client position) + QApplication::sendSpontaneousEvent(this, &e); + } else { + QMoveEvent * e = new QMoveEvent(newCPos, oldPos); + QApplication::postEvent(this, e); + } +#if !defined(Q_WS_WINCE) + } +#endif + } + setAttribute(Qt::WA_WState_ConfigPending, false); // clear config flag + return true; +} + + +// +// Close window event translation. +// +// This class is a friend of QApplication because it needs to emit the +// lastWindowClosed() signal when the last top level widget is closed. +// + +bool QETWidget::translateCloseEvent(const MSG &) +{ + return d_func()->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); +} + +#ifndef QT_NO_GESTURES +bool QETWidget::translateGestureEvent(const MSG &, const GESTUREINFO &gi) +{ + const QPoint widgetPos = QPoint(gi.ptsLocation.x, gi.ptsLocation.y); + QWidget *alienWidget = !internalWinId() ? this : childAt(widgetPos); + if (alienWidget && alienWidget->internalWinId()) + alienWidget = 0; + QWidget *widget = alienWidget ? alienWidget : this; + + QNativeGestureEvent event; + event.sequenceId = gi.dwSequenceID; + event.position = QPoint(gi.ptsLocation.x, gi.ptsLocation.y); + event.argument = gi.ullArguments; + + switch (gi.dwID) { + case GID_BEGIN: + event.gestureType = QNativeGestureEvent::GestureBegin; + break; + case GID_END: + event.gestureType = QNativeGestureEvent::GestureEnd; + break; + case GID_ZOOM: + event.gestureType = QNativeGestureEvent::Zoom; + break; + case GID_PAN: + event.gestureType = QNativeGestureEvent::Pan; + break; + case GID_ROTATE: + event.gestureType = QNativeGestureEvent::Rotate; + break; + case GID_TWOFINGERTAP: + case GID_ROLLOVER: + default: + break; + } + if (event.gestureType != QNativeGestureEvent::None) + qt_sendSpontaneousEvent(widget, &event); + return true; +} +#endif // QT_NO_GESTURES + +void QApplication::setCursorFlashTime(int msecs) +{ + SetCaretBlinkTime(msecs / 2); + QApplicationPrivate::cursor_flash_time = msecs; +} + + +int QApplication::cursorFlashTime() +{ + int blink = (int)GetCaretBlinkTime(); + if (!blink) + return QApplicationPrivate::cursor_flash_time; + if (blink > 0) + return 2*blink; + return 0; +} + + +void QApplication::setDoubleClickInterval(int ms) +{ +#ifndef Q_WS_WINCE + SetDoubleClickTime(ms); +#endif + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + int ms = GetDoubleClickTime(); + if (ms != 0) + return ms; + return QApplicationPrivate::mouse_double_click_time; +} + + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + // FIXME: get from the system + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int n) +{ +#ifdef SPI_SETWHEELSCROLLLINES + if (n < 0) + n = 0; + SystemParametersInfo(SPI_SETWHEELSCROLLLINES, (uint)n, 0, 0); +#else + QApplicationPrivate::wheel_scroll_lines = n; +#endif +} + +int QApplication::wheelScrollLines() +{ +#ifdef SPI_GETWHEELSCROLLLINES + uint i = 3; + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, sizeof(uint), &i, 0); + if (i > INT_MAX) + i = INT_MAX; + return i; +#else + return QApplicationPrivate::wheel_scroll_lines; +#endif +} +#endif //QT_NO_WHEELEVENT + +static bool effect_override = false; + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + effect_override = true; + switch (effect) { + case Qt::UI_AnimateMenu: + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeMenu: + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateTooltip: + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_FadeTooltip: + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16) + return false; + + if (!effect_override && desktopSettingsAware()) { + // we know that they can be used when we are here + BOOL enabled = false; + UINT api; + switch (effect) { + case Qt::UI_AnimateMenu: + api = SPI_GETMENUANIMATION; + break; + case Qt::UI_FadeMenu: + api = SPI_GETMENUFADE; + break; + case Qt::UI_AnimateCombo: + api = SPI_GETCOMBOBOXANIMATION; + break; + case Qt::UI_AnimateTooltip: + api = SPI_GETTOOLTIPANIMATION; + break; + case Qt::UI_FadeTooltip: + api = SPI_GETTOOLTIPFADE; + break; + default: + api = SPI_GETUIEFFECTS; + break; + } + SystemParametersInfo(api, 0, &enabled, 0); + return enabled; + } + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + return QApplicationPrivate::animate_ui; + } +} + +#ifndef QT_NO_SESSIONMANAGER + +bool QSessionManager::allowsInteraction() +{ + sm_blockUserInput = false; + return true; +} + +bool QSessionManager::allowsErrorInteraction() +{ + sm_blockUserInput = false; + return true; +} + +void QSessionManager::release() +{ + if (sm_smActive) + sm_blockUserInput = true; +} + +void QSessionManager::cancel() +{ + sm_cancel = true; +} + +#endif //QT_NO_SESSIONMANAGER + + +bool QApplicationPrivate::HasTouchSupport = false; +PtrRegisterTouchWindow QApplicationPrivate::RegisterTouchWindow = 0; +PtrGetTouchInputInfo QApplicationPrivate::GetTouchInputInfo = 0; +PtrCloseTouchInputHandle QApplicationPrivate::CloseTouchInputHandle = 0; + +void QApplicationPrivate::initializeMultitouch_sys() +{ + if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) { + static const int QT_SM_DIGITIZER = 94; + int value = GetSystemMetrics(QT_SM_DIGITIZER); + static const int QT_NID_INTEGRATED_TOUCH = 0x01; + static const int QT_NID_EXTERNAL_TOUCH = 0x02; + static const int QT_NID_MULTI_INPUT = 0x40; + QApplicationPrivate::HasTouchSupport = + value & (QT_NID_INTEGRATED_TOUCH | QT_NID_EXTERNAL_TOUCH | QT_NID_MULTI_INPUT); + } + + QSystemLibrary library(QLatin1String("user32")); + // MinGW (g++ 3.4.5) accepts only C casts. + RegisterTouchWindow = (PtrRegisterTouchWindow)(library.resolve("RegisterTouchWindow")); + GetTouchInputInfo = (PtrGetTouchInputInfo)(library.resolve("GetTouchInputInfo")); + CloseTouchInputHandle = (PtrCloseTouchInputHandle)(library.resolve("CloseTouchInputHandle")); + + touchInputIDToTouchPointID.clear(); +} + +void QApplicationPrivate::cleanupMultitouch_sys() +{ + touchInputIDToTouchPointID.clear(); +} + +bool QApplicationPrivate::translateTouchEvent(const MSG &msg) +{ + QWidget *widgetForHwnd = QWidget::find(msg.hwnd); + if (!widgetForHwnd) + return false; + + QRect screenGeometry = QApplication::desktop()->screenGeometry(widgetForHwnd); + + QList touchPoints; + + QVector winTouchInputs(msg.wParam); + memset(winTouchInputs.data(), 0, sizeof(TOUCHINPUT) * winTouchInputs.count()); + Qt::TouchPointStates allStates = 0; + QApplicationPrivate::GetTouchInputInfo((HANDLE) msg.lParam, msg.wParam, winTouchInputs.data(), sizeof(TOUCHINPUT)); + for (int i = 0; i < winTouchInputs.count(); ++i) { + const TOUCHINPUT &touchInput = winTouchInputs.at(i); + + int touchPointID = touchInputIDToTouchPointID.value(touchInput.dwID, -1); + if (touchPointID == -1) { + touchPointID = touchInputIDToTouchPointID.count(); + touchInputIDToTouchPointID.insert(touchInput.dwID, touchPointID); + } + + QTouchEvent::TouchPoint touchPoint(touchPointID); + + // update state + QPointF screenPos(qreal(touchInput.x) / qreal(100.), qreal(touchInput.y) / qreal(100.)); + QRectF screenRect; + if (touchInput.dwMask & TOUCHINPUTMASKF_CONTACTAREA) + screenRect.setSize(QSizeF(qreal(touchInput.cxContact) / qreal(100.), + qreal(touchInput.cyContact) / qreal(100.))); + screenRect.moveCenter(screenPos); + + Qt::TouchPointStates state; + if (touchInput.dwFlags & TOUCHEVENTF_DOWN) { + state = Qt::TouchPointPressed; + } else if (touchInput.dwFlags & TOUCHEVENTF_UP) { + state = Qt::TouchPointReleased; + } else { + state = (screenPos == touchPoint.screenPos() + ? Qt::TouchPointStationary + : Qt::TouchPointMoved); + } + if (touchInput.dwFlags & TOUCHEVENTF_PRIMARY) + state |= Qt::TouchPointPrimary; + touchPoint.setState(state); + touchPoint.setScreenRect(screenRect); + touchPoint.setNormalizedPos(QPointF(screenPos.x() / screenGeometry.width(), + screenPos.y() / screenGeometry.height())); + + allStates |= state; + + touchPoints.append(touchPoint); + } + QApplicationPrivate::CloseTouchInputHandle((HANDLE) msg.lParam); + + if ((allStates & Qt::TouchPointStateMask) == Qt::TouchPointReleased) { + // all touch points released, forget the ids we've seen, they may not be reused + touchInputIDToTouchPointID.clear(); + } + + translateRawTouchEvent(widgetForHwnd, QTouchEvent::TouchScreen, touchPoints); + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qapplication_x11.cpp b/src/gui/kernel/qapplication_x11.cpp new file mode 100644 index 0000000000..20542ea328 --- /dev/null +++ b/src/gui/kernel/qapplication_x11.cpp @@ -0,0 +1,6239 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// ### 4.0: examine Q_EXPORT's below. The respective symbols had all +// been in use (e.g. in the KDE wm) before the introduction of a version +// map. One might want to turn some of them into proper public API and +// provide a proper alternative for others. See also the exports in +// qapplication_win.cpp, which suggest a unification. + +#include "qplatformdefs.h" + +#include "qcolormap.h" +#include "qdesktopwidget.h" +#include "qapplication.h" +#include "qapplication_p.h" +#include "qcursor.h" +#include "qwidget.h" +#include "qbitarray.h" +#include "qpainter.h" +#include "qfile.h" +#include "qpixmapcache.h" +#include "qdatetime.h" +#include "qtextcodec.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qsocketnotifier.h" +#include "qsessionmanager.h" +#include "qclipboard.h" +#include "qwhatsthis.h" +#include "qsettings.h" +#include "qstylefactory.h" +#include "qfileinfo.h" +#include "qdir.h" +#include "qhash.h" +#include "qevent.h" +#include "qevent_p.h" +#include "qvarlengtharray.h" +#include "qdebug.h" +#include +#include +#include +#include +#include +#include "qstyle.h" +#include "qmetaobject.h" +#include "qtimer.h" +#include "qlibrary.h" +#include +#include "qguiplatformplugin_p.h" +#include "qkde_p.h" + +#if !defined (QT_NO_TABLET) +extern "C" { +# define class c_class //XIproto.h has a name member named 'class' which the c++ compiler doesn't like +# include +# undef class +} +#endif + +#ifndef QT_GUI_DOUBLE_CLICK_RADIUS +#define QT_GUI_DOUBLE_CLICK_RADIUS 5 +#endif + + +//#define ALIEN_DEBUG + +#if !defined(QT_NO_GLIB) +# include "qguieventdispatcher_glib_p.h" +#endif +#include "qeventdispatcher_x11_p.h" +#include + +#include + +// Input method stuff +#ifndef QT_NO_IM +#include "qinputcontext.h" +#include "qinputcontextfactory.h" +#endif // QT_NO_IM + +#ifndef QT_NO_XFIXES +#include +#endif // QT_NO_XFIXES + +#include "qt_x11_p.h" +#include "qx11info_x11.h" + +#define XK_MISCELLANY +#include +#if !defined(QT_NO_XINPUT) +#include +#endif + +#include +#include +#include +#include + +#include "qwidget_p.h" + +#include + +#ifdef QT_RX71_MULTITOUCH +# include +# include +# include +#endif + +#if _POSIX_VERSION+0 < 200112L && !defined(Q_OS_BSD4) +# define QT_NO_UNSETENV +#endif + +QT_BEGIN_NAMESPACE + +//#define X_NOT_BROKEN +#ifdef X_NOT_BROKEN +// Some X libraries are built with setlocale #defined to _Xsetlocale, +// even though library users are then built WITHOUT such a definition. +// This creates a problem - Qt might setlocale() one value, but then +// X looks and doesn't see the value Qt set. The solution here is to +// implement _Xsetlocale just in case X calls it - redirecting it to +// the real libC version. +// +# ifndef setlocale +extern "C" char *_Xsetlocale(int category, const char *locale); +char *_Xsetlocale(int category, const char *locale) +{ + //qDebug("_Xsetlocale(%d,%s),category,locale"); + return setlocale(category,locale); +} +# endif // setlocale +#endif // X_NOT_BROKEN + +/* Warning: if you modify this string, modify the list of atoms in qt_x11_p.h as well! */ +static const char * x11_atomnames = { + // window-manager <-> client protocols + "WM_PROTOCOLS\0" + "WM_DELETE_WINDOW\0" + "WM_TAKE_FOCUS\0" + "_NET_WM_PING\0" + "_NET_WM_CONTEXT_HELP\0" + "_NET_WM_SYNC_REQUEST\0" + "_NET_WM_SYNC_REQUEST_COUNTER\0" + + // ICCCM window state + "WM_STATE\0" + "WM_CHANGE_STATE\0" + + // Session management + "WM_CLIENT_LEADER\0" + "WM_WINDOW_ROLE\0" + "SM_CLIENT_ID\0" + + // Clipboard + "CLIPBOARD\0" + "INCR\0" + "TARGETS\0" + "MULTIPLE\0" + "TIMESTAMP\0" + "SAVE_TARGETS\0" + "CLIP_TEMPORARY\0" + "_QT_SELECTION\0" + "_QT_CLIPBOARD_SENTINEL\0" + "_QT_SELECTION_SENTINEL\0" + "CLIPBOARD_MANAGER\0" + + "RESOURCE_MANAGER\0" + + "_XSETROOT_ID\0" + + "_QT_SCROLL_DONE\0" + "_QT_INPUT_ENCODING\0" + + "_MOTIF_WM_HINTS\0" + + "DTWM_IS_RUNNING\0" + "ENLIGHTENMENT_DESKTOP\0" + "_DT_SAVE_MODE\0" + "_SGI_DESKS_MANAGER\0" + + // EWMH (aka NETWM) + "_NET_SUPPORTED\0" + "_NET_VIRTUAL_ROOTS\0" + "_NET_WORKAREA\0" + + "_NET_MOVERESIZE_WINDOW\0" + "_NET_WM_MOVERESIZE\0" + + "_NET_WM_NAME\0" + "_NET_WM_ICON_NAME\0" + "_NET_WM_ICON\0" + + "_NET_WM_PID\0" + + "_NET_WM_WINDOW_OPACITY\0" + + "_NET_WM_STATE\0" + "_NET_WM_STATE_ABOVE\0" + "_NET_WM_STATE_BELOW\0" + "_NET_WM_STATE_FULLSCREEN\0" + "_NET_WM_STATE_MAXIMIZED_HORZ\0" + "_NET_WM_STATE_MAXIMIZED_VERT\0" + "_NET_WM_STATE_MODAL\0" + "_NET_WM_STATE_STAYS_ON_TOP\0" + "_NET_WM_STATE_DEMANDS_ATTENTION\0" + + "_NET_WM_USER_TIME\0" + "_NET_WM_USER_TIME_WINDOW\0" + "_NET_WM_FULL_PLACEMENT\0" + + "_NET_WM_WINDOW_TYPE\0" + "_NET_WM_WINDOW_TYPE_DESKTOP\0" + "_NET_WM_WINDOW_TYPE_DOCK\0" + "_NET_WM_WINDOW_TYPE_TOOLBAR\0" + "_NET_WM_WINDOW_TYPE_MENU\0" + "_NET_WM_WINDOW_TYPE_UTILITY\0" + "_NET_WM_WINDOW_TYPE_SPLASH\0" + "_NET_WM_WINDOW_TYPE_DIALOG\0" + "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0" + "_NET_WM_WINDOW_TYPE_POPUP_MENU\0" + "_NET_WM_WINDOW_TYPE_TOOLTIP\0" + "_NET_WM_WINDOW_TYPE_NOTIFICATION\0" + "_NET_WM_WINDOW_TYPE_COMBO\0" + "_NET_WM_WINDOW_TYPE_DND\0" + "_NET_WM_WINDOW_TYPE_NORMAL\0" + "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0" + + "_KDE_NET_WM_FRAME_STRUT\0" + + "_NET_STARTUP_INFO\0" + "_NET_STARTUP_INFO_BEGIN\0" + + "_NET_SUPPORTING_WM_CHECK\0" + + "_NET_WM_CM_S0\0" + + "_NET_SYSTEM_TRAY_VISUAL\0" + + "_NET_ACTIVE_WINDOW\0" + + // Property formats + "COMPOUND_TEXT\0" + "TEXT\0" + "UTF8_STRING\0" + + // xdnd + "XdndEnter\0" + "XdndPosition\0" + "XdndStatus\0" + "XdndLeave\0" + "XdndDrop\0" + "XdndFinished\0" + "XdndTypeList\0" + "XdndActionList\0" + + "XdndSelection\0" + + "XdndAware\0" + "XdndProxy\0" + + "XdndActionCopy\0" + "XdndActionLink\0" + "XdndActionMove\0" + "XdndActionPrivate\0" + + // Motif DND + "_MOTIF_DRAG_AND_DROP_MESSAGE\0" + "_MOTIF_DRAG_INITIATOR_INFO\0" + "_MOTIF_DRAG_RECEIVER_INFO\0" + "_MOTIF_DRAG_WINDOW\0" + "_MOTIF_DRAG_TARGETS\0" + + "XmTRANSFER_SUCCESS\0" + "XmTRANSFER_FAILURE\0" + + // Xkb + "_XKB_RULES_NAMES\0" + + // XEMBED + "_XEMBED\0" + "_XEMBED_INFO\0" + + // Wacom old. (before version 0.10) + "Wacom Stylus\0" + "Wacom Cursor\0" + "Wacom Eraser\0" + + // Tablet + "STYLUS\0" + "ERASER\0" +}; + +Q_GUI_EXPORT QX11Data *qt_x11Data = 0; + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ +static const char *appName = 0; // application name +static const char *appClass = 0; // application class +static const char *appFont = 0; // application font +static const char *appBGCol = 0; // application bg color +static const char *appFGCol = 0; // application fg color +static const char *appBTNCol = 0; // application btn color +static const char *mwGeometry = 0; // main widget geometry +static const char *mwTitle = 0; // main widget title +char *qt_ximServer = 0; // XIM Server will connect to +static bool appSync = false; // X11 synchronization +#if defined(QT_DEBUG) +static bool appNoGrab = false; // X11 grabbing enabled +static bool appDoGrab = false; // X11 grabbing override (gdb) +#endif +static bool app_save_rootinfo = false; // save root info +static bool app_do_modal = false; // modal mode +static Window curWin = 0; // current window + + +// function to update the workarea of the screen - in qdesktopwidget_x11.cpp +extern void qt_desktopwidget_update_workarea(); + +// Function to change the window manager state (from qwidget_x11.cpp) +extern void qt_change_net_wm_state(const QWidget *w, bool set, Atom one, Atom two = 0); + +// modifier masks for alt, meta, super, hyper, and mode_switch - detected when the application starts +// and/or keyboard layout changes +uchar qt_alt_mask = 0; +uchar qt_meta_mask = 0; +uchar qt_super_mask = 0; +uchar qt_hyper_mask = 0; +uchar qt_mode_switch_mask = 0; + +// flags for extensions for special Languages, currently only for RTL languages +bool qt_use_rtl_extensions = false; + +static Window mouseActWindow = 0; // window where mouse is +static Qt::MouseButton mouseButtonPressed = Qt::NoButton; // last mouse button pressed +static Qt::MouseButtons mouseButtonState = Qt::NoButton; // mouse button state +static Time mouseButtonPressTime = 0; // when was a button pressed +static short mouseXPos, mouseYPos; // mouse pres position in act window +static short mouseGlobalXPos, mouseGlobalYPos; // global mouse press position + +extern QWidgetList *qt_modal_stack; // stack of modal widgets + +// window where mouse buttons have been pressed +static Window pressed_window = XNone; + +// popup control +static bool replayPopupMouseEvent = false; +static bool popupGrabOk; + +bool qt_sm_blockUserInput = false; // session management + +Q_GUI_EXPORT int qt_xfocusout_grab_counter = 0; + +#if !defined (QT_NO_TABLET) +Q_GLOBAL_STATIC(QTabletDeviceDataList, tablet_devices) +QTabletDeviceDataList *qt_tablet_devices() +{ + return tablet_devices(); +} + +extern bool qt_tabletChokeMouse; +#endif + +typedef bool(*QX11FilterFunction)(XEvent *event); + +Q_GLOBAL_STATIC(QList, x11Filters) + +Q_GUI_EXPORT void qt_installX11EventFilter(QX11FilterFunction func) +{ + Q_ASSERT(func); + + if (QList *list = x11Filters()) + list->append(func); +} + +Q_GUI_EXPORT void qt_removeX11EventFilter(QX11FilterFunction func) +{ + Q_ASSERT(func); + + if (QList *list = x11Filters()) + list->removeOne(func); +} + + +static bool qt_x11EventFilter(XEvent* ev) +{ + long unused; + if (qApp->filterEvent(ev, &unused)) + return true; + if (const QList *list = x11Filters()) { + for (QList::const_iterator it = list->constBegin(); it != list->constEnd(); ++it) { + if ((*it)(ev)) + return true; + } + } + + return qApp->x11EventFilter(ev); +} + +#if !defined(QT_NO_XIM) +XIMStyle qt_xim_preferred_style = 0; +#endif +int qt_ximComposingKeycode=0; +QTextCodec * qt_input_mapper = 0; + +extern bool qt_check_clipboard_sentinel(); //def in qclipboard_x11.cpp +extern bool qt_check_selection_sentinel(); //def in qclipboard_x11.cpp +extern bool qt_xfixes_clipboard_changed(Window clipboardOwner, Time timestamp); //def in qclipboard_x11.cpp +extern bool qt_xfixes_selection_changed(Window selectionOwner, Time timestamp); //def in qclipboard_x11.cpp + +static void qt_save_rootinfo(); +Q_GUI_EXPORT bool qt_try_modal(QWidget *, XEvent *); + +QWidget *qt_button_down = 0; // last widget to be pressed with the mouse +QPointer qt_last_mouse_receiver = 0; +static QWidget *qt_popup_down = 0; // popup that contains the pressed widget + +extern bool qt_xdnd_dragging; + +// gui or non-gui from qapplication.cpp +extern bool qt_is_gui_used; + +/*! + \internal + Try to resolve a \a symbol from \a library with the version specified + by \a vernum. + + Note that, in the case of the Xfixes library, \a vernum is not the same as + \c XFIXES_MAJOR - it is a part of soname and may differ from the Xfixes + version. +*/ +static void* qt_load_library_runtime(const char *library, int vernum, + int highestVernum, const char *symbol) +{ + QList versions; + // we try to load in the following order: + // explicit version -> the default one -> (from the highest (highestVernum) to the lowest (vernum) ) + if (vernum != -1) + versions << vernum; + versions << -1; + if (vernum != -1) { + for(int i = highestVernum; i > vernum; --i) + versions << i; + } + Q_FOREACH(int version, versions) { + QLatin1String libName(library); + QLibrary xfixesLib(libName, version); + void *ptr = xfixesLib.resolve(symbol); + if (ptr) + return ptr; + } + return 0; +} + +#ifndef QT_NO_XINPUT +# ifdef QT_RUNTIME_XINPUT +# define XINPUT_LOAD_RUNTIME(vernum, symbol, symbol_type) \ + (symbol_type)qt_load_library_runtime("libXi", vernum, 6, #symbol); +# define XINPUT_LOAD(symbol) \ + XINPUT_LOAD_RUNTIME(1, symbol, Ptr##symbol) +# else // not runtime XInput +# define XINPUT_LOAD(symbol) symbol +# endif // QT_RUNTIME_XINPUT +#else // not using Xinput at all +# define XINPUT_LOAD(symbol) 0 +#endif // QT_NO_XINPUT + +#ifndef QT_NO_XFIXES +# ifdef QT_RUNTIME_XFIXES +# define XFIXES_LOAD_RUNTIME(vernum, symbol, symbol_type) \ + (symbol_type)qt_load_library_runtime("libXfixes", vernum, 4, #symbol); +# define XFIXES_LOAD_V1(symbol) \ + XFIXES_LOAD_RUNTIME(1, symbol, Ptr##symbol) +# define XFIXES_LOAD_V2(symbol) \ + XFIXES_LOAD_RUNTIME(2, symbol, Ptr##symbol) + +# else // not runtime Xfixes + +# if XFIXES_MAJOR >= 2 +# define XFIXES_LOAD_V1(symbol) symbol +# define XFIXES_LOAD_V2(symbol) symbol +# elif XFIXES_MAJOR >= 1 +# define XFIXES_LOAD_V1(symbol) symbol +# define XFIXES_LOAD_V2(symbol) 0 +# else +# error Unsupported version of Xfixes +# endif +# endif // QT_RUNTIME_XFIXES +#else // not using Xfixes at all +# define XFIXES_LOAD_V1(symbol) 0 +# define XFIXES_LOAD_V2(symbol) 0 +#endif // QT_NO_XFIXES + +#ifndef QT_NO_XFIXES + +struct qt_xfixes_selection_event_data +{ + // which selection to filter out. + Atom selection; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static Bool qt_xfixes_scanner(Display*, XEvent *event, XPointer arg) +{ + qt_xfixes_selection_event_data *data = + reinterpret_cast(arg); + if (event->type == X11->xfixes_eventbase + XFixesSelectionNotify) { + XFixesSelectionNotifyEvent *xfixes_event = reinterpret_cast(event); + if (xfixes_event->selection == data->selection) + return true; + } + return false; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +#endif // QT_NO_XFIXES + +class QETWidget : public QWidget // event translator widget +{ +public: + QWidgetPrivate* d_func() { return QWidget::d_func(); } + bool translateMouseEvent(const XEvent *); + void translatePaintEvent(const XEvent *); + bool translateConfigEvent(const XEvent *); + bool translateCloseEvent(const XEvent *); + bool translateScrollDoneEvent(const XEvent *); + bool translateWheelEvent(int global_x, int global_y, int delta, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers, Qt::Orientation orient); +#if !defined (QT_NO_TABLET) + bool translateXinputEvent(const XEvent*, QTabletDeviceData *tablet); +#endif + bool translatePropertyEvent(const XEvent *); + + void doDeferredMap() + { + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + if (!testAttribute(Qt::WA_Resized)) { + adjustSize(); + setAttribute(Qt::WA_Resized, false); + } + + /* + workaround for WM's that throw away ConfigureRequests from the following: + + window->hide(); + window->move(x, y); // could also be resize(), move()+resize(), or setGeometry() + window->show(); + */ + QRect r = geometry(); + + XMoveResizeWindow(X11->display, + internalWinId(), + r.x(), + r.y(), + r.width(), + r.height()); + + // static gravity! + XSizeHints sh; + long unused; + XGetWMNormalHints(X11->display, internalWinId(), &sh, &unused); + sh.flags |= USPosition | PPosition | USSize | PSize | PWinGravity; + sh.x = r.x(); + sh.y = r.y(); + sh.width = r.width(); + sh.height = r.height(); + sh.win_gravity = StaticGravity; + XSetWMNormalHints(X11->display, internalWinId(), &sh); + + setAttribute(Qt::WA_Mapped); + if (testAttribute(Qt::WA_DontShowOnScreen)) + return; + d_func()->topData()->waitingForMapNotify = 1; + XMapWindow(X11->display, internalWinId()); + } +}; + + +void QApplicationPrivate::createEventDispatcher() +{ + Q_Q(QApplication); +#if !defined(QT_NO_GLIB) + if (qgetenv("QT_NO_GLIB").isEmpty() && QEventDispatcherGlib::versionSupported()) + eventDispatcher = (q->type() != QApplication::Tty + ? new QGuiEventDispatcherGlib(q) + : new QEventDispatcherGlib(q)); + else +#endif + eventDispatcher = (q->type() != QApplication::Tty + ? new QEventDispatcherX11(q) + : new QEventDispatcherUNIX(q)); +} + +/***************************************************************************** + Default X error handlers + *****************************************************************************/ + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static int (*original_x_errhandler)(Display *dpy, XErrorEvent *); +static int (*original_xio_errhandler)(Display *dpy); + +static int qt_x_errhandler(Display *dpy, XErrorEvent *err) +{ + if (X11->display != dpy) { + // only handle X errors for our display + return 0; + } + + switch (err->error_code) { + case BadAtom: + if (err->request_code == 20 /* X_GetProperty */ + && (err->resourceid == XA_RESOURCE_MANAGER + || err->resourceid == XA_RGB_DEFAULT_MAP + || err->resourceid == ATOM(_NET_SUPPORTED) + || err->resourceid == ATOM(_NET_SUPPORTING_WM_CHECK) + || err->resourceid == ATOM(XdndProxy) + || err->resourceid == ATOM(XdndAware))) { + // Perhaps we're running under SECURITY reduction? :/ + return 0; + } + break; + + case BadWindow: + if (err->request_code == 2 /* X_ChangeWindowAttributes */ + || err->request_code == 38 /* X_QueryPointer */) { + for (int i = 0; i < ScreenCount(dpy); ++i) { + if (err->resourceid == RootWindow(dpy, i)) { + // Perhaps we're running under SECURITY reduction? :/ + return 0; + } + } + } + X11->seen_badwindow = true; + if (err->request_code == 25 /* X_SendEvent */) { + for (int i = 0; i < ScreenCount(dpy); ++i) { + if (err->resourceid == RootWindow(dpy, i)) { + // Perhaps we're running under SECURITY reduction? :/ + return 0; + } + } + if (X11->xdndHandleBadwindow()) { + qDebug("xdndHandleBadwindow returned true"); + return 0; + } + } + if (X11->ignore_badwindow) + return 0; + break; + + default: +#if !defined(QT_NO_XINPUT) + if (err->request_code == X11->xinput_major + && err->error_code == (X11->xinput_errorbase + XI_BadDevice) + && err->minor_code == 3 /* X_OpenDevice */) { + return 0; + } +#endif + break; + } + + char errstr[256]; + XGetErrorText( dpy, err->error_code, errstr, 256 ); + char buffer[256]; + char request_str[256]; + qsnprintf(buffer, 256, "%d", err->request_code); + XGetErrorDatabaseText(dpy, "XRequest", buffer, "", request_str, 256); + if (err->request_code < 128) { + // X error for a normal protocol request + qWarning( "X Error: %s %d\n" + " Major opcode: %d (%s)\n" + " Resource id: 0x%lx", + errstr, err->error_code, + err->request_code, + request_str, + err->resourceid ); + } else { + // X error for an extension request + const char *extensionName = 0; + if (err->request_code == X11->xrender_major) + extensionName = "RENDER"; + else if (err->request_code == X11->xrandr_major) + extensionName = "RANDR"; + else if (err->request_code == X11->xinput_major) + extensionName = "XInputExtension"; + else if (err->request_code == X11->mitshm_major) + extensionName = "MIT-SHM"; +#ifndef QT_NO_XKB + else if(err->request_code == X11->xkb_major) + extensionName = "XKEYBOARD"; +#endif + + char minor_str[256]; + if (extensionName) { + qsnprintf(buffer, 256, "%s.%d", extensionName, err->minor_code); + XGetErrorDatabaseText(dpy, "XRequest", buffer, "", minor_str, 256); + } else { + extensionName = "Uknown extension"; + qsnprintf(minor_str, 256, "Unknown request"); + } + qWarning( "X Error: %s %d\n" + " Extension: %d (%s)\n" + " Minor opcode: %d (%s)\n" + " Resource id: 0x%lx", + errstr, err->error_code, + err->request_code, + extensionName, + err->minor_code, + minor_str, + err->resourceid ); + } + + // ### we really should distinguish between severe, non-severe and + // ### application specific errors + + return 0; +} + + +static int qt_xio_errhandler(Display *) +{ + qWarning("%s: Fatal IO error: client killed", appName); + QApplicationPrivate::reset_instance_pointer(); + exit(1); + //### give the application a chance for a proper shutdown instead, + //### exit(1) doesn't help. + return 0; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +#ifndef QT_NO_XSYNC +struct qt_sync_request_event_data +{ + WId window; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static Bool qt_sync_request_scanner(Display*, XEvent *event, XPointer arg) +{ + qt_sync_request_event_data *data = + reinterpret_cast(arg); + if (event->type == ClientMessage && + event->xany.window == data->window && + event->xclient.message_type == ATOM(WM_PROTOCOLS) && + (Atom)event->xclient.data.l[0] == ATOM(_NET_WM_SYNC_REQUEST)) { + QWidget *w = QWidget::find(event->xany.window); + if (QTLWExtra *tlw = ((QETWidget*)w)->d_func()->maybeTopData()) { + const ulong timestamp = (const ulong) event->xclient.data.l[1]; + if (timestamp > X11->time) + X11->time = timestamp; + if (timestamp == CurrentTime || timestamp > tlw->syncRequestTimestamp) { + tlw->syncRequestTimestamp = timestamp; + tlw->newCounterValueLo = event->xclient.data.l[2]; + tlw->newCounterValueHi = event->xclient.data.l[3]; + } + } + return true; + } + return false; +} + +#if defined(Q_C_CALLBACKS) +} +#endif +#endif // QT_NO_XSYNC + +static void qt_x11_create_intern_atoms() +{ + const char *names[QX11Data::NAtoms]; + const char *ptr = x11_atomnames; + + int i = 0; + while (*ptr) { + names[i++] = ptr; + while (*ptr) + ++ptr; + ++ptr; + } + + Q_ASSERT(i == QX11Data::NPredefinedAtoms); + + QByteArray settings_atom_name("_QT_SETTINGS_TIMESTAMP_"); + settings_atom_name += XDisplayName(X11->displayName); + names[i++] = settings_atom_name; + + Q_ASSERT(i == QX11Data::NAtoms); +#if defined(XlibSpecificationRelease) && (XlibSpecificationRelease >= 6) + XInternAtoms(X11->display, (char **)names, i, False, X11->atoms); +#else + for (i = 0; i < QX11Data::NAtoms; ++i) + X11->atoms[i] = XInternAtom(X11->display, (char *)names[i], False); +#endif +} + +Q_GUI_EXPORT void qt_x11_apply_settings_in_all_apps() +{ + QByteArray stamp; + QDataStream s(&stamp, QIODevice::WriteOnly); + s << QDateTime::currentDateTime(); + + XChangeProperty(QX11Info::display(), QX11Info::appRootWindow(0), + ATOM(_QT_SETTINGS_TIMESTAMP), ATOM(_QT_SETTINGS_TIMESTAMP), 8, + PropModeReplace, (unsigned char *)stamp.data(), stamp.size()); +} + +/*! \internal + apply the settings to the application +*/ +bool QApplicationPrivate::x11_apply_settings() +{ + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + + settings.beginGroup(QLatin1String("Qt")); + + /* + Qt settings. This is now they are written into the datastream. + + Palette / * - QPalette + font - QFont + libraryPath - QStringList + style - QString + doubleClickInterval - int + keyboardInputInterval - int + cursorFlashTime - int + wheelScrollLines - int + colorSpec - QString + defaultCodec - QString + globalStrut/width - int + globalStrut/height - int + GUIEffects - QStringList + Font Substitutions/ * - QStringList + Font Substitutions/... - QStringList + */ + + QStringList strlist; + int i; + QPalette pal(Qt::black); + int groupCount = 0; + strlist = settings.value(QLatin1String("Palette/active")).toStringList(); + if (!strlist.isEmpty()) { + ++groupCount; + for (i = 0; i < qMin(strlist.count(), int(QPalette::NColorRoles)); i++) + pal.setColor(QPalette::Active, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/inactive")).toStringList(); + if (!strlist.isEmpty()) { + ++groupCount; + for (i = 0; i < qMin(strlist.count(), int(QPalette::NColorRoles)); i++) + pal.setColor(QPalette::Inactive, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/disabled")).toStringList(); + if (!strlist.isEmpty()) { + ++groupCount; + for (i = 0; i < qMin(strlist.count(), int(QPalette::NColorRoles)); i++) + pal.setColor(QPalette::Disabled, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + + // ### Fix properly for 4.6 + bool usingGtkSettings = QApplicationPrivate::app_style && QApplicationPrivate::app_style->inherits("QGtkStyle"); + if (!usingGtkSettings) { + if (groupCount == QPalette::NColorGroups) + QApplicationPrivate::setSystemPalette(pal); + } + + if (!appFont) { + // ### Fix properly for 4.6 + if (!usingGtkSettings) { + QFont font(QApplication::font()); + QString fontDescription; + // Override Qt font if KDE4 settings can be used + if (X11->desktopVersion == 4) { + QSettings kdeSettings(QKde::kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat); + fontDescription = kdeSettings.value(QLatin1String("font")).toString(); + if (fontDescription.isEmpty()) { + // KDE stores fonts without quotes + fontDescription = kdeSettings.value(QLatin1String("font")).toStringList().join(QLatin1String(",")); + } + } + if (fontDescription.isEmpty()) + fontDescription = settings.value(QLatin1String("font")).toString(); + if (!fontDescription .isEmpty()) { + font.fromString(fontDescription ); + QApplicationPrivate::setSystemFont(font); + } + } + } + + // read library (ie. plugin) path list + QString libpathkey = + QString::fromLatin1("%1.%2/libraryPath") + .arg(QT_VERSION >> 16) + .arg((QT_VERSION & 0xff00) >> 8); + QStringList pathlist = settings.value(libpathkey).toString().split(QLatin1Char(':')); + if (! pathlist.isEmpty()) { + QStringList::ConstIterator it = pathlist.constBegin(); + while (it != pathlist.constEnd()) + QApplication::addLibraryPath(*it++); + } + + // read new QStyle + QString stylename = settings.value(QLatin1String("style")).toString(); + + if (stylename.isEmpty() && QApplicationPrivate::styleOverride.isNull() && X11->use_xrender) { + stylename = qt_guiPlatformPlugin()->styleName(); + } + + static QString currentStyleName = stylename; + if (QCoreApplication::startingUp()) { + if (!stylename.isEmpty() && QApplicationPrivate::styleOverride.isNull()) + QApplicationPrivate::styleOverride = stylename; + } else { + if (currentStyleName != stylename) { + currentStyleName = stylename; + QApplication::setStyle(stylename); + } + } + + int num = + settings.value(QLatin1String("doubleClickInterval"), + QApplication::doubleClickInterval()).toInt(); + QApplication::setDoubleClickInterval(num); + + num = + settings.value(QLatin1String("cursorFlashTime"), + QApplication::cursorFlashTime()).toInt(); + QApplication::setCursorFlashTime(num); + +#ifndef QT_NO_WHEELEVENT + num = + settings.value(QLatin1String("wheelScrollLines"), + QApplication::wheelScrollLines()).toInt(); + QApplication::setWheelScrollLines(num); +#endif + + QString colorspec = settings.value(QLatin1String("colorSpec"), + QVariant(QLatin1String("default"))).toString(); + if (colorspec == QLatin1String("normal")) + QApplication::setColorSpec(QApplication::NormalColor); + else if (colorspec == QLatin1String("custom")) + QApplication::setColorSpec(QApplication::CustomColor); + else if (colorspec == QLatin1String("many")) + QApplication::setColorSpec(QApplication::ManyColor); + else if (colorspec != QLatin1String("default")) + colorspec = QLatin1String("default"); + + QString defaultcodec = settings.value(QLatin1String("defaultCodec"), + QVariant(QLatin1String("none"))).toString(); + if (defaultcodec != QLatin1String("none")) { + QTextCodec *codec = QTextCodec::codecForName(defaultcodec.toLatin1()); + if (codec) + QTextCodec::setCodecForTr(codec); + } + + int w = settings.value(QLatin1String("globalStrut/width")).toInt(); + int h = settings.value(QLatin1String("globalStrut/height")).toInt(); + QSize strut(w, h); + if (strut.isValid()) + QApplication::setGlobalStrut(strut); + + QStringList effects = settings.value(QLatin1String("GUIEffects")).toStringList(); + QApplication::setEffectEnabled(Qt::UI_General, + effects.contains(QLatin1String("general"))); + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, + effects.contains(QLatin1String("animatemenu"))); + QApplication::setEffectEnabled(Qt::UI_FadeMenu, + effects.contains(QLatin1String("fademenu"))); + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, + effects.contains(QLatin1String("animatecombo"))); + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, + effects.contains(QLatin1String("animatetooltip"))); + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, + effects.contains(QLatin1String("fadetooltip"))); + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, + effects.contains(QLatin1String("animatetoolbox"))); + + if (!X11->has_fontconfig) { + settings.beginGroup(QLatin1String("Font Substitutions")); + QStringList fontsubs = settings.childKeys(); + if (!fontsubs.isEmpty()) { + QStringList::Iterator it = fontsubs.begin(); + for (; it != fontsubs.end(); ++it) { + QString fam = *it; + QStringList subs = settings.value(fam).toStringList(); + QFont::insertSubstitutions(fam, subs); + } + } + settings.endGroup(); + } + + qt_use_rtl_extensions = + settings.value(QLatin1String("useRtlExtensions"), false).toBool(); + +#ifndef QT_NO_XIM + if (qt_xim_preferred_style == 0) { + QString ximInputStyle = settings.value(QLatin1String("XIMInputStyle"), + QVariant(QLatin1String("on the spot"))).toString().toLower(); + if (ximInputStyle == QLatin1String("on the spot")) + qt_xim_preferred_style = XIMPreeditCallbacks | XIMStatusNothing; + else if (ximInputStyle == QLatin1String("over the spot")) + qt_xim_preferred_style = XIMPreeditPosition | XIMStatusNothing; + else if (ximInputStyle == QLatin1String("off the spot")) + qt_xim_preferred_style = XIMPreeditArea | XIMStatusArea; + else if (ximInputStyle == QLatin1String("root")) + qt_xim_preferred_style = XIMPreeditNothing | XIMStatusNothing; + } +#endif + QStringList inputMethods = QInputContextFactory::keys(); + if (inputMethods.size() > 2 && inputMethods.contains(QLatin1String("imsw-multi"))) { + X11->default_im = QLatin1String("imsw-multi"); + } else { + X11->default_im = settings.value(QLatin1String("DefaultInputMethod"), + QLatin1String("xim")).toString(); + } + + settings.endGroup(); // Qt + + return true; +} + + +/*! \internal + Resets the QApplication::instance() pointer to zero +*/ +void QApplicationPrivate::reset_instance_pointer() +{ QApplication::self = 0; } + + +// read the _QT_INPUT_ENCODING property and apply the settings to +// the application +static void qt_set_input_encoding() +{ + Atom type; + int format; + ulong nitems, after = 1; + unsigned char *data = 0; + + int e = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_QT_INPUT_ENCODING), 0, 1024, + False, XA_STRING, &type, &format, &nitems, + &after, &data); + if (e != Success || !nitems || type == XNone) { + // Always use the locale codec, since we have no examples of non-local + // XIMs, and since we cannot get a sensible answer about the encoding + // from the XIM. + qt_input_mapper = QTextCodec::codecForLocale(); + + } else { + if (!qstricmp((char *)data, "locale")) + qt_input_mapper = QTextCodec::codecForLocale(); + else + qt_input_mapper = QTextCodec::codecForName((char *)data); + // make sure we have an input codec + if(!qt_input_mapper) + qt_input_mapper = QTextCodec::codecForName("ISO 8859-1"); + } + if (qt_input_mapper && qt_input_mapper->mibEnum() == 11) // 8859-8 + qt_input_mapper = QTextCodec::codecForName("ISO 8859-8-I"); + if(data) + XFree((char *)data); +} + +// set font, foreground and background from x11 resources. The +// arguments may override the resource settings. +static void qt_set_x11_resources(const char* font = 0, const char* fg = 0, + const char* bg = 0, const char* button = 0) +{ + + QString resFont, resFG, resBG, resButton, resEF, sysFont, selectBackground, selectForeground; + + QApplication::setEffectEnabled(Qt::UI_General, false); + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, false); + QApplication::setEffectEnabled(Qt::UI_FadeMenu, false); + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, false); + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, false); + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, false); + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, false); + + bool paletteAlreadySet = false; + if (QApplication::desktopSettingsAware()) { + // first, read from settings + QApplicationPrivate::x11_apply_settings(); + // the call to QApplication::style() below creates the system + // palette, which breaks the logic after the RESOURCE_MANAGER + // loop... so I have to save this value to be able to use it later + paletteAlreadySet = (QApplicationPrivate::sys_pal != 0); + + // second, parse the RESOURCE_MANAGER property + int format; + ulong nitems, after = 1; + QString res; + long offset = 0; + Atom type = XNone; + + while (after > 0) { + uchar *data = 0; + if (XGetWindowProperty(X11->display, QX11Info::appRootWindow(0), + ATOM(RESOURCE_MANAGER), + offset, 8192, False, AnyPropertyType, + &type, &format, &nitems, &after, + &data) != Success) { + res = QString(); + break; + } + if (type == XA_STRING) + res += QString::fromLatin1((char*)data); + else + res += QString::fromLocal8Bit((char*)data); + offset += 2048; // offset is in 32bit quantities... 8192/4 == 2048 + if (data) + XFree((char *)data); + } + + QString key, value; + int l = 0, r; + QString apn = QString::fromLocal8Bit(appName); + QString apc = QString::fromLocal8Bit(appClass); + int apnl = apn.length(); + int apcl = apc.length(); + int resl = res.length(); + + while (l < resl) { + r = res.indexOf(QLatin1Char('\n'), l); + if (r < 0) + r = resl; + while (res.at(l).isSpace()) + l++; + bool mine = false; + QChar sc = res.at(l + 1); + if (res.at(l) == QLatin1Char('*') && + (sc == QLatin1Char('f') || sc == QLatin1Char('b') || sc == QLatin1Char('g') || + sc == QLatin1Char('F') || sc == QLatin1Char('B') || sc == QLatin1Char('G') || + sc == QLatin1Char('s') || sc == QLatin1Char('S') + // capital T only, since we're looking for "Text.selectSomething" + || sc == QLatin1Char('T'))) { + // OPTIMIZED, since we only want "*[fbgsT].." + QString item = res.mid(l, r - l).simplified(); + int i = item.indexOf(QLatin1Char(':')); + key = item.left(i).trimmed().mid(1).toLower(); + value = item.right(item.length() - i - 1).trimmed(); + mine = true; + } else if ((apnl && res.at(l) == apn.at(0)) || (appClass && apcl && res.at(l) == apc.at(0))) { + if (res.mid(l,apnl) == apn && (res.at(l+apnl) == QLatin1Char('.') + || res.at(l+apnl) == QLatin1Char('*'))) { + QString item = res.mid(l, r - l).simplified(); + int i = item.indexOf(QLatin1Char(':')); + key = item.left(i).trimmed().mid(apnl+1).toLower(); + value = item.right(item.length() - i - 1).trimmed(); + mine = true; + } else if (res.mid(l,apcl) == apc && (res.at(l+apcl) == QLatin1Char('.') + || res.at(l+apcl) == QLatin1Char('*'))) { + QString item = res.mid(l, r - l).simplified(); + int i = item.indexOf(QLatin1Char(':')); + key = item.left(i).trimmed().mid(apcl+1).toLower(); + value = item.right(item.length() - i - 1).trimmed(); + mine = true; + } + } + + if (mine) { + if (!font && key == QLatin1String("systemfont")) + sysFont = value.left(value.lastIndexOf(QLatin1Char(':'))); + if (!font && key == QLatin1String("font")) + resFont = value; + else if (!fg && !paletteAlreadySet) { + if (key == QLatin1String("foreground")) + resFG = value; + else if (!bg && key == QLatin1String("background")) + resBG = value; + else if (!bg && !button && key == QLatin1String("button.background")) + resButton = value; + else if (key == QLatin1String("text.selectbackground")) { + selectBackground = value; + } else if (key == QLatin1String("text.selectforeground")) { + selectForeground = value; + } + } else if (key == QLatin1String("guieffects")) + resEF = value; + // NOTE: if you add more, change the [fbg] stuff above + } + + l = r + 1; + } + } + if (!sysFont.isEmpty()) + resFont = sysFont; + if (resFont.isEmpty()) + resFont = QString::fromLocal8Bit(font); + if (resFG.isEmpty()) + resFG = QString::fromLocal8Bit(fg); + if (resBG.isEmpty()) + resBG = QString::fromLocal8Bit(bg); + if (resButton.isEmpty()) + resButton = QString::fromLocal8Bit(button); + if (!resFont.isEmpty() + && !X11->has_fontconfig + && !QApplicationPrivate::sys_font) { + // set application font + QFont fnt; + fnt.setRawName(resFont); + + // the font we get may actually be an alias for another font, + // so we reset the application font to the real font info. + if (! fnt.exactMatch()) { + QFontInfo fontinfo(fnt); + fnt.setFamily(fontinfo.family()); + fnt.setRawMode(fontinfo.rawMode()); + + if (! fnt.rawMode()) { + fnt.setItalic(fontinfo.italic()); + fnt.setWeight(fontinfo.weight()); + fnt.setUnderline(fontinfo.underline()); + fnt.setStrikeOut(fontinfo.strikeOut()); + fnt.setStyleHint(fontinfo.styleHint()); + + if (fnt.pointSize() <= 0 && fnt.pixelSize() <= 0) { + // size is all wrong... fix it + qreal pointSize = fontinfo.pixelSize() * 72. / (float) QX11Info::appDpiY(); + if (pointSize <= 0) + pointSize = 12; + fnt.setPointSize(qRound(pointSize)); + } + } + } + + QApplicationPrivate::setSystemFont(fnt); + } + // QGtkStyle sets it's own system palette + bool gtkStyle = QApplicationPrivate::app_style && QApplicationPrivate::app_style->inherits("QGtkStyle"); + bool kdeColors = (QApplication::desktopSettingsAware() && X11->desktopEnvironment == DE_KDE); + if (!gtkStyle && (kdeColors || (button || !resBG.isEmpty() || !resFG.isEmpty()))) {// set app colors + bool allowX11ColorNames = QColor::allowX11ColorNames(); + QColor::setAllowX11ColorNames(true); + + (void) QApplication::style(); // trigger creation of application style and system palettes + QColor btn; + QColor bg; + QColor fg; + QColor bfg; + QColor wfg; + if (!resBG.isEmpty()) + bg = QColor(resBG); + if (!bg.isValid()) + bg = QApplicationPrivate::sys_pal->color(QPalette::Active, QPalette::Window); + + if (!resFG.isEmpty()) + fg = QColor(resFG); + if (!fg.isValid()) + fg = QApplicationPrivate::sys_pal->color(QPalette::Active, QPalette::WindowText); + + if (!resButton.isEmpty()) + btn = QColor(resButton); + else if (!resBG.isEmpty()) + btn = bg; + if (!btn.isValid()) + btn = QApplicationPrivate::sys_pal->color(QPalette::Active, QPalette::Button); + + int h,s,v; + fg.getHsv(&h,&s,&v); + QColor base = Qt::white; + bool bright_mode = false; + if (v >= 255 - 50) { + base = btn.darker(150); + bright_mode = true; + } + + QPalette pal(fg, btn, btn.lighter(125), btn.darker(130), btn.darker(120), wfg.isValid() ? wfg : fg, Qt::white, base, bg); + QColor disabled((fg.red() + btn.red()) / 2, + (fg.green() + btn.green())/ 2, + (fg.blue() + btn.blue()) / 2); + pal.setColorGroup(QPalette::Disabled, disabled, btn, btn.lighter(125), + btn.darker(130), btn.darker(150), disabled, Qt::white, Qt::white, bg); + + QColor highlight, highlightText; + if (!selectBackground.isEmpty() && !selectForeground.isEmpty()) { + highlight = QColor(selectBackground); + highlightText = QColor(selectForeground); + } + + if (highlight.isValid() && highlightText.isValid()) { + pal.setColor(QPalette::Highlight, highlight); + pal.setColor(QPalette::HighlightedText, highlightText); + + // calculate disabled colors by removing saturation + highlight.setHsv(highlight.hue(), 0, highlight.value(), highlight.alpha()); + highlightText.setHsv(highlightText.hue(), 0, highlightText.value(), highlightText.alpha()); + pal.setColor(QPalette::Disabled, QPalette::Highlight, highlight); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, highlightText); + } else if (bright_mode) { + pal.setColor(QPalette::HighlightedText, base); + pal.setColor(QPalette::Highlight, Qt::white); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, base); + pal.setColor(QPalette::Disabled, QPalette::Highlight, Qt::white); + } else { + pal.setColor(QPalette::HighlightedText, Qt::white); + pal.setColor(QPalette::Highlight, Qt::darkBlue); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, Qt::white); + pal.setColor(QPalette::Disabled, QPalette::Highlight, Qt::darkBlue); + } + + pal = qt_guiPlatformPlugin()->palette().resolve(pal); + QApplicationPrivate::setSystemPalette(pal); + QColor::setAllowX11ColorNames(allowX11ColorNames); + } + + if (!resEF.isEmpty()) { + QStringList effects = resEF.split(QLatin1Char(' ')); + QApplication::setEffectEnabled(Qt::UI_General, effects.contains(QLatin1String("general"))); + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, + effects.contains(QLatin1String("animatemenu"))); + QApplication::setEffectEnabled(Qt::UI_FadeMenu, + effects.contains(QLatin1String("fademenu"))); + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, + effects.contains(QLatin1String("animatecombo"))); + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, + effects.contains(QLatin1String("animatetooltip"))); + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, + effects.contains(QLatin1String("fadetooltip"))); + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, + effects.contains(QLatin1String("animatetoolbox"))); + } + + QIconLoader::instance()->updateSystemTheme(); +} + + +// update the supported array +static void qt_get_net_supported() +{ + Atom type; + int format; + long offset = 0; + unsigned long nitems, after; + unsigned char *data = 0; + + int e = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_NET_SUPPORTED), 0, 0, + False, XA_ATOM, &type, &format, &nitems, &after, &data); + if (data) + XFree(data); + + if (X11->net_supported_list) + delete [] X11->net_supported_list; + X11->net_supported_list = 0; + + if (e == Success && type == XA_ATOM && format == 32) { + QBuffer ts; + ts.open(QIODevice::WriteOnly); + + while (after > 0) { + XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_NET_SUPPORTED), offset, 1024, + False, XA_ATOM, &type, &format, &nitems, &after, &data); + + if (type == XA_ATOM && format == 32) { + ts.write(reinterpret_cast(data), nitems * sizeof(long)); + offset += nitems; + } else + after = 0; + if (data) + XFree(data); + } + + // compute nitems + QByteArray buffer(ts.buffer()); + nitems = buffer.size() / sizeof(Atom); + X11->net_supported_list = new Atom[nitems + 1]; + Atom *a = (Atom *) buffer.data(); + uint i; + for (i = 0; i < nitems; i++) + X11->net_supported_list[i] = a[i]; + X11->net_supported_list[nitems] = 0; + } +} + + +bool QX11Data::isSupportedByWM(Atom atom) +{ + if (!X11->net_supported_list) + return false; + + bool supported = false; + int i = 0; + while (X11->net_supported_list[i] != 0) { + if (X11->net_supported_list[i++] == atom) { + supported = true; + break; + } + } + + return supported; +} + + +// update the virtual roots array +static void qt_get_net_virtual_roots() +{ + if (X11->net_virtual_root_list) + delete [] X11->net_virtual_root_list; + X11->net_virtual_root_list = 0; + + if (!X11->isSupportedByWM(ATOM(_NET_VIRTUAL_ROOTS))) + return; + + Atom type; + int format; + long offset = 0; + unsigned long nitems, after; + unsigned char *data; + + int e = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_NET_VIRTUAL_ROOTS), 0, 0, + False, XA_ATOM, &type, &format, &nitems, &after, &data); + if (data) + XFree(data); + + if (e == Success && type == XA_ATOM && format == 32) { + QBuffer ts; + ts.open(QIODevice::WriteOnly); + + while (after > 0) { + XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_NET_VIRTUAL_ROOTS), offset, 1024, + False, XA_ATOM, &type, &format, &nitems, &after, &data); + + if (type == XA_ATOM && format == 32) { + ts.write(reinterpret_cast(data), nitems * 4); + offset += nitems; + } else + after = 0; + if (data) + XFree(data); + } + + // compute nitems + QByteArray buffer(ts.buffer()); + nitems = buffer.size() / sizeof(Window); + X11->net_virtual_root_list = new Window[nitems + 1]; + Window *a = (Window *) buffer.data(); + uint i; + for (i = 0; i < nitems; i++) + X11->net_virtual_root_list[i] = a[i]; + X11->net_virtual_root_list[nitems] = 0; + } +} + +void qt_net_remove_user_time(QWidget *tlw) +{ + Q_ASSERT(tlw); + QTLWExtra *extra = tlw->d_func()->maybeTopData(); + if (extra && extra->userTimeWindow) { + Q_ASSERT(tlw->internalWinId()); + XDeleteProperty(X11->display, tlw->internalWinId(), ATOM(_NET_WM_USER_TIME_WINDOW)); + XDestroyWindow(X11->display, extra->userTimeWindow); + extra->userTimeWindow = 0; + } +} + +void qt_net_update_user_time(QWidget *tlw, unsigned long timestamp) +{ + Q_ASSERT(tlw); + Q_ASSERT(tlw->isWindow()); + Q_ASSERT(tlw->testAttribute(Qt::WA_WState_Created)); + QTLWExtra *extra = tlw->d_func()->topData(); + WId wid = tlw->internalWinId(); + const bool isSupportedByWM = X11->isSupportedByWM(ATOM(_NET_WM_USER_TIME_WINDOW)); + if (extra->userTimeWindow || isSupportedByWM) { + if (!extra->userTimeWindow) { + extra->userTimeWindow = XCreateSimpleWindow(X11->display, + tlw->internalWinId(), + -1, -1, 1, 1, 0, 0, 0); + wid = extra->userTimeWindow; + XChangeProperty(X11->display, tlw->internalWinId(), ATOM(_NET_WM_USER_TIME_WINDOW), + XA_WINDOW, 32, PropModeReplace, + (unsigned char *)&wid, 1); + XDeleteProperty(X11->display, tlw->internalWinId(), ATOM(_NET_WM_USER_TIME)); + } else if (!isSupportedByWM) { + // WM no longer supports it, then we should remove the + // _NET_WM_USER_TIME_WINDOW atom. + qt_net_remove_user_time(tlw); + } else { + wid = extra->userTimeWindow; + } + } + XChangeProperty(X11->display, wid, ATOM(_NET_WM_USER_TIME), + XA_CARDINAL, 32, PropModeReplace, (unsigned char *) ×tamp, 1); +} + +static void qt_check_focus_model() +{ + Window fw = XNone; + int unused; + XGetInputFocus(X11->display, &fw, &unused); + if (fw == PointerRoot) + X11->focus_model = QX11Data::FM_PointerRoot; + else + X11->focus_model = QX11Data::FM_Other; +} + +#ifndef QT_NO_TABLET + +#if !defined (Q_OS_IRIX) +// from include/Xwacom.h +# define XWACOM_PARAM_TOOLID 322 +# define XWACOM_PARAM_TOOLSERIAL 323 + +typedef WACOMCONFIG * (*PtrWacomConfigInit) (Display*, WACOMERRORFUNC); +typedef WACOMDEVICE * (*PtrWacomConfigOpenDevice) (WACOMCONFIG*, const char*); +typedef int *(*PtrWacomConfigGetRawParam) (WACOMDEVICE*, int, int*, int, unsigned*); +typedef int (*PtrWacomConfigCloseDevice) (WACOMDEVICE *); +typedef void (*PtrWacomConfigTerm) (WACOMCONFIG *); + +static PtrWacomConfigInit ptrWacomConfigInit = 0; +static PtrWacomConfigOpenDevice ptrWacomConfigOpenDevice = 0; +static PtrWacomConfigGetRawParam ptrWacomConfigGetRawParam = 0; +static PtrWacomConfigCloseDevice ptrWacomConfigCloseDevice = 0; +static PtrWacomConfigTerm ptrWacomConfigTerm = 0; +Q_GLOBAL_STATIC(QByteArray, wacomDeviceName) +#endif + +#endif + +/***************************************************************************** + qt_init() - initializes Qt for X11 + *****************************************************************************/ + +#if !defined(QT_NO_FONTCONFIG) +static void getXDefault(const char *group, const char *key, int *val) +{ + char *str = XGetDefault(X11->display, group, key); + if (str) { + char *end = 0; + int v = strtol(str, &end, 0); + if (str != end) + *val = v; + // otherwise use fontconfig to convert the string to integer + else + FcNameConstant((FcChar8 *) str, val); + } +} + +static void getXDefault(const char *group, const char *key, double *val) +{ + char *str = XGetDefault(X11->display, group, key); + if (str) { + bool ok; + double v = QByteArray(str).toDouble(&ok); + if (ok) + *val = v; + } +} + +static void getXDefault(const char *group, const char *key, bool *val) +{ + char *str = XGetDefault(X11->display, group, key); + if (str) { + char c = str[0]; + if (isupper((int)c)) + c = tolower(c); + if (c == 't' || c == 'y' || c == '1') + *val = true; + else if (c == 'f' || c == 'n' || c == '0') + *val = false; + if (c == 'o') { + c = str[1]; + if (isupper((int)c)) + c = tolower(c); + if (c == 'n') + *val = true; + if (c == 'f') + *val = false; + } + } +} +#endif + +// ### This should be static but it isn't because of the friend declaration +// ### in qpaintdevice.h which then should have a static too but can't have +// ### it because "storage class specifiers invalid in friend function +// ### declarations" :-) Ideas anyone? +void qt_init(QApplicationPrivate *priv, int, + Display *display, Qt::HANDLE visual, Qt::HANDLE colormap) +{ + X11 = new QX11Data; + X11->display = display; + X11->displayName = 0; + X11->foreignDisplay = (display != 0); + X11->focus_model = -1; + + // RANDR + X11->use_xrandr = false; + X11->xrandr_major = 0; + X11->xrandr_eventbase = 0; + X11->xrandr_errorbase = 0; + + // RENDER + X11->use_xrender = false; + X11->xrender_major = 0; + X11->xrender_version = 0; + + // XFIXES + X11->use_xfixes = false; + X11->xfixes_major = 0; + X11->xfixes_eventbase = 0; + X11->xfixes_errorbase = 0; + + // XInputExtension + X11->use_xinput = false; + X11->xinput_major = 0; + X11->xinput_eventbase = 0; + X11->xinput_errorbase = 0; + + X11->use_xkb = false; + X11->xkb_major = 0; + X11->xkb_eventbase = 0; + X11->xkb_errorbase = 0; + + // MIT-SHM + X11->use_mitshm = false; + X11->use_mitshm_pixmaps = false; + X11->mitshm_major = 0; + + X11->sip_serial = 0; + X11->net_supported_list = 0; + X11->net_virtual_root_list = 0; + X11->wm_client_leader = 0; + X11->screens = 0; + X11->argbVisuals = 0; + X11->argbColormaps = 0; + X11->screenCount = 0; + X11->time = CurrentTime; + X11->userTime = CurrentTime; + X11->ignore_badwindow = false; + X11->seen_badwindow = false; + + X11->motifdnd_active = false; + + X11->default_im = QLatin1String("imsw-multi"); + priv->inputContext = 0; + + // colormap control + X11->visual_class = -1; + X11->visual_id = -1; + X11->color_count = 0; + X11->custom_cmap = false; + + // outside visual/colormap + X11->visual = reinterpret_cast(visual); + X11->colormap = colormap; + + // Fontconfig + X11->has_fontconfig = false; +#if !defined(QT_NO_FONTCONFIG) + if (qgetenv("QT_X11_NO_FONTCONFIG").isNull()) + X11->has_fontconfig = FcInit(); + X11->fc_antialias = true; +#endif + +#ifndef QT_NO_XRENDER + memset(X11->solid_fills, 0, sizeof(X11->solid_fills)); + for (int i = 0; i < X11->solid_fill_count; ++i) + X11->solid_fills[i].screen = -1; + memset(X11->pattern_fills, 0, sizeof(X11->pattern_fills)); + for (int i = 0; i < X11->pattern_fill_count; ++i) + X11->pattern_fills[i].screen = -1; +#endif + + X11->startupId = 0; + + int argc = priv->argc; + char **argv = priv->argv; + + if (X11->display) { + // Qt part of other application + + // Set application name and class + appName = qstrdup("Qt-subapplication"); + char *app_class = 0; + if (argv) { + const char* p = strrchr(argv[0], '/'); + app_class = qstrdup(p ? p + 1 : argv[0]); + if (app_class[0]) + app_class[0] = toupper(app_class[0]); + } + appClass = app_class; + } else { + // Qt controls everything (default) + + if (QApplication::testAttribute(Qt::AA_X11InitThreads)) + XInitThreads(); + + // Set application name and class + char *app_class = 0; + if (argv && argv[0]) { + const char *p = strrchr(argv[0], '/'); + appName = p ? p + 1 : argv[0]; + app_class = qstrdup(appName); + if (app_class[0]) + app_class[0] = toupper(app_class[0]); + } + appClass = app_class; + } + + // Install default error handlers + original_x_errhandler = XSetErrorHandler(qt_x_errhandler); + original_xio_errhandler = XSetIOErrorHandler(qt_xio_errhandler); + + // Get command line params + int j = argc ? 1 : 0; + for (int i=1; idisplay) + X11->displayName = argv[i]; + } else if (arg == "-fn" || arg == "-font") { + if (++i < argc) + appFont = argv[i]; + } else if (arg == "-bg" || arg == "-background") { + if (++i < argc) + appBGCol = argv[i]; + } else if (arg == "-btn" || arg == "-button") { + if (++i < argc) + appBTNCol = argv[i]; + } else if (arg == "-fg" || arg == "-foreground") { + if (++i < argc) + appFGCol = argv[i]; + } else if (arg == "-name") { + if (++i < argc) + appName = argv[i]; + } else if (arg == "-title") { + if (++i < argc) + mwTitle = argv[i]; + } else if (arg == "-geometry") { + if (++i < argc) + mwGeometry = argv[i]; + } else if (arg == "-im") { + if (++i < argc) + qt_ximServer = argv[i]; + } else if (arg == "-ncols") { // xv and netscape use this name + if (++i < argc) + X11->color_count = qMax(0,atoi(argv[i])); + } else if (arg == "-visual") { // xv and netscape use this name + if (++i < argc && !X11->visual) { + QString s = QString::fromLocal8Bit(argv[i]).toLower(); + if (s == QLatin1String("staticgray")) + X11->visual_class = StaticGray; + else if (s == QLatin1String("grayscale")) + X11->visual_class = XGrayScale; + else if (s == QLatin1String("staticcolor")) + X11->visual_class = StaticColor; + else if (s == QLatin1String("pseudocolor")) + X11->visual_class = PseudoColor; + else if (s == QLatin1String("truecolor")) + X11->visual_class = TrueColor; + else if (s == QLatin1String("directcolor")) + X11->visual_class = DirectColor; + else + X11->visual_id = static_cast(strtol(argv[i], 0, 0)); + } +#ifndef QT_NO_XIM + } else if (arg == "-inputstyle") { + if (++i < argc) { + QString s = QString::fromLocal8Bit(argv[i]).toLower(); + if (s == QLatin1String("onthespot")) + qt_xim_preferred_style = XIMPreeditCallbacks | + XIMStatusNothing; + else if (s == QLatin1String("overthespot")) + qt_xim_preferred_style = XIMPreeditPosition | + XIMStatusNothing; + else if (s == QLatin1String("offthespot")) + qt_xim_preferred_style = XIMPreeditArea | + XIMStatusArea; + else if (s == QLatin1String("root")) + qt_xim_preferred_style = XIMPreeditNothing | + XIMStatusNothing; + } +#endif + } else if (arg == "-cmap") { // xv uses this name + if (!X11->colormap) + X11->custom_cmap = true; + } + else if (arg == "-sync") + appSync = !appSync; +#if defined(QT_DEBUG) + else if (arg == "-nograb") + appNoGrab = !appNoGrab; + else if (arg == "-dograb") + appDoGrab = !appDoGrab; +#endif + else + argv[j++] = argv[i]; + } + + priv->argc = j; + +#if defined(QT_DEBUG) && defined(Q_OS_LINUX) + if (!appNoGrab && !appDoGrab) { + QString s; + s.sprintf("/proc/%d/cmdline", getppid()); + QFile f(s); + if (f.open(QIODevice::ReadOnly)) { + s.clear(); + char c; + while (f.getChar(&c) && c) { + if (c == '/') + s.clear(); + else + s += QLatin1Char(c); + } + if (s == QLatin1String("gdb")) { + appNoGrab = true; + qDebug("Qt: gdb: -nograb added to command-line options.\n" + "\t Use the -dograb option to enforce grabbing."); + } + f.close(); + } + } +#endif + + // Connect to X server + if (qt_is_gui_used && !X11->display) { + if ((X11->display = XOpenDisplay(X11->displayName)) == 0) { + qWarning("%s: cannot connect to X server %s", appName, + XDisplayName(X11->displayName)); + QApplicationPrivate::reset_instance_pointer(); + exit(1); + } + + if (appSync) // if "-sync" argument + XSynchronize(X11->display, true); + } + + // Common code, regardless of whether display is foreign. + + // Get X parameters + + if (qt_is_gui_used) { + X11->defaultScreen = DefaultScreen(X11->display); + X11->screenCount = ScreenCount(X11->display); + + X11->screens = new QX11InfoData[X11->screenCount]; + X11->argbVisuals = new Visual *[X11->screenCount]; + X11->argbColormaps = new Colormap[X11->screenCount]; + + for (int s = 0; s < X11->screenCount; s++) { + QX11InfoData *screen = X11->screens + s; + screen->ref = 1; // ensures it doesn't get deleted + screen->screen = s; + + int widthMM = DisplayWidthMM(X11->display, s); + if (widthMM != 0) { + screen->dpiX = (DisplayWidth(X11->display, s) * 254 + widthMM * 5) / (widthMM * 10); + } else { + screen->dpiX = 72; + } + + int heightMM = DisplayHeightMM(X11->display, s); + if (heightMM != 0) { + screen->dpiY = (DisplayHeight(X11->display, s) * 254 + heightMM * 5) / (heightMM * 10); + } else { + screen->dpiY = 72; + } + + X11->argbVisuals[s] = 0; + X11->argbColormaps[s] = 0; + } + + +#ifndef QT_NO_XRENDER + int xrender_eventbase, xrender_errorbase; + // See if XRender is supported on the connected display + if (XQueryExtension(X11->display, "RENDER", &X11->xrender_major, + &xrender_eventbase, &xrender_errorbase) + && XRenderQueryExtension(X11->display, &xrender_eventbase, + &xrender_errorbase)) { + // Check the version as well - we need v0.4 or higher + int major = 0; + int minor = 0; + XRenderQueryVersion(X11->display, &major, &minor); + if (qgetenv("QT_X11_NO_XRENDER").isNull()) { + X11->use_xrender = (major >= 0 && minor >= 5); + X11->xrender_version = major*100+minor; + // workaround for broken XServer on Ubuntu Breezy (6.8 compiled with 7.0 + // protocol headers) + if (X11->xrender_version == 10 + && VendorRelease(X11->display) < 60900000 + && QByteArray(ServerVendor(X11->display)).contains("X.Org")) + X11->xrender_version = 9; + } + } +#endif // QT_NO_XRENDER + +#ifndef QT_NO_MITSHM + int mitshm_minor; + int mitshm_major; + int mitshm_eventbase; + int mitshm_errorbase; + int mitshm_pixmaps; + if (XQueryExtension(X11->display, "MIT-SHM", &X11->mitshm_major, + &mitshm_eventbase, &mitshm_errorbase) + && XShmQueryVersion(X11->display, &mitshm_major, &mitshm_minor, + &mitshm_pixmaps)) + { + QString displayName = QLatin1String(XDisplayName(NULL)); + + // MITSHM only works for local displays, so do a quick check here + // to determine whether the display is local or not (not 100 % accurate). + // BGR server layouts are not supported either, since it requires the raster + // engine to work on a QImage with BGR layout. + bool local = displayName.isEmpty() || displayName.lastIndexOf(QLatin1Char(':')) == 0; + if (local && (qgetenv("QT_X11_NO_MITSHM").toInt() == 0)) { + Visual *defaultVisual = DefaultVisual(X11->display, DefaultScreen(X11->display)); + X11->use_mitshm = ((defaultVisual->red_mask == 0xff0000 + || defaultVisual->red_mask == 0xf800) + && (defaultVisual->green_mask == 0xff00 + || defaultVisual->green_mask == 0x7e0) + && (defaultVisual->blue_mask == 0xff + || defaultVisual->blue_mask == 0x1f)); + X11->use_mitshm_pixmaps = X11->use_mitshm && mitshm_pixmaps; + } + } +#endif // QT_NO_MITSHM + + // initialize the graphics system - order is imporant here - it must be done before + // the QColormap::initialize() call + QApplicationPrivate::graphics_system = QGraphicsSystemFactory::create(QApplicationPrivate::graphics_system_name); + QColormap::initialize(); + + // Support protocols + X11->xdndSetup(); + + // Finally create all atoms + qt_x11_create_intern_atoms(); + + // initialize NET lists + qt_get_net_supported(); + qt_get_net_virtual_roots(); + +#ifndef QT_NO_XRANDR + // See if XRandR is supported on the connected display + if (XQueryExtension(X11->display, "RANDR", &X11->xrandr_major, + &X11->xrandr_eventbase, &X11->xrandr_errorbase)) { + +# ifdef QT_RUNTIME_XRANDR + X11->ptrXRRSelectInput = 0; + X11->ptrXRRUpdateConfiguration = 0; + X11->ptrXRRRootToScreen = 0; + X11->ptrXRRQueryExtension = 0; + QLibrary xrandrLib(QLatin1String("Xrandr"), 2); + if (!xrandrLib.load()) { // try without the version number + xrandrLib.setFileName(QLatin1String("Xrandr")); + xrandrLib.load(); + } + if (xrandrLib.isLoaded()) { + X11->ptrXRRSelectInput = + (PtrXRRSelectInput) xrandrLib.resolve("XRRSelectInput"); + X11->ptrXRRUpdateConfiguration = + (PtrXRRUpdateConfiguration) xrandrLib.resolve("XRRUpdateConfiguration"); + X11->ptrXRRRootToScreen = + (PtrXRRRootToScreen) xrandrLib.resolve("XRRRootToScreen"); + X11->ptrXRRQueryExtension = + (PtrXRRQueryExtension) xrandrLib.resolve("XRRQueryExtension"); + X11->ptrXRRSizes = + (PtrXRRSizes) xrandrLib.resolve("XRRSizes"); + } +# else + X11->ptrXRRSelectInput = XRRSelectInput; + X11->ptrXRRUpdateConfiguration = XRRUpdateConfiguration; + X11->ptrXRRRootToScreen = XRRRootToScreen; + X11->ptrXRRQueryExtension = XRRQueryExtension; + X11->ptrXRRSizes = XRRSizes; +# endif + + if (X11->ptrXRRQueryExtension + && X11->ptrXRRQueryExtension(X11->display, &X11->xrandr_eventbase, &X11->xrandr_errorbase)) { + // XRandR is supported + X11->use_xrandr = true; + } + } +#endif // QT_NO_XRANDR + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + // XRender is supported, let's see if we have a PictFormat for the + // default visual + XRenderPictFormat *format = + XRenderFindVisualFormat(X11->display, + (Visual *) QX11Info::appVisual(X11->defaultScreen)); + + if (!format) { + X11->use_xrender = false; + } + } +#endif // QT_NO_XRENDER + +#ifndef QT_NO_XFIXES + // See if Xfixes is supported on the connected display + if (XQueryExtension(X11->display, "XFIXES", &X11->xfixes_major, + &X11->xfixes_eventbase, &X11->xfixes_errorbase)) { + X11->ptrXFixesQueryExtension = XFIXES_LOAD_V1(XFixesQueryExtension); + X11->ptrXFixesQueryVersion = XFIXES_LOAD_V1(XFixesQueryVersion); + X11->ptrXFixesSetCursorName = XFIXES_LOAD_V2(XFixesSetCursorName); + X11->ptrXFixesSelectSelectionInput = XFIXES_LOAD_V2(XFixesSelectSelectionInput); + + if(X11->ptrXFixesQueryExtension && X11->ptrXFixesQueryVersion + && X11->ptrXFixesQueryExtension(X11->display, &X11->xfixes_eventbase, + &X11->xfixes_errorbase)) { + // Xfixes is supported. + // Note: the XFixes protocol version is negotiated using QueryVersion. + // We supply the highest version we support, the X server replies with + // the highest version it supports, but no higher than the version we + // asked for. The version sent back is the protocol version the X server + // will use to talk us. If this call is removed, the behavior of the + // X server when it receives an XFixes request is undefined. + int major = 3; + int minor = 0; + X11->ptrXFixesQueryVersion(X11->display, &major, &minor); + X11->use_xfixes = (major >= 1); + X11->xfixes_major = major; + } + } +#endif // QT_NO_XFIXES + +#ifndef QT_NO_XCURSOR +#ifdef QT_RUNTIME_XCURSOR + X11->ptrXcursorLibraryLoadCursor = 0; + QLibrary xcursorLib(QLatin1String("Xcursor"), 1); + bool xcursorFound = xcursorLib.load(); + if (!xcursorFound) { //try without the version number + xcursorLib.setFileName(QLatin1String("Xcursor")); + xcursorFound = xcursorLib.load(); + } + if (xcursorFound) { + X11->ptrXcursorLibraryLoadCursor = + (PtrXcursorLibraryLoadCursor) xcursorLib.resolve("XcursorLibraryLoadCursor"); + } +#else + X11->ptrXcursorLibraryLoadCursor = XcursorLibraryLoadCursor; +#endif // QT_RUNTIME_XCURSOR +#endif // QT_NO_XCURSOR + +#ifndef QT_NO_XSYNC + int xsync_evbase, xsync_errbase; + int major, minor; + if (XSyncQueryExtension(X11->display, &xsync_evbase, &xsync_errbase)) + XSyncInitialize(X11->display, &major, &minor); +#endif // QT_NO_XSYNC + +#ifndef QT_NO_XINERAMA +#ifdef QT_RUNTIME_XINERAMA + X11->ptrXineramaQueryExtension = 0; + X11->ptrXineramaIsActive = 0; + X11->ptrXineramaQueryScreens = 0; + QLibrary xineramaLib(QLatin1String("Xinerama"), 1); + bool xineramaFound = xineramaLib.load(); + if (!xineramaFound) { //try without the version number + xineramaLib.setFileName(QLatin1String("Xinerama")); + xineramaFound = xineramaLib.load(); + } + if (xineramaFound) { + X11->ptrXineramaQueryExtension = + (PtrXineramaQueryExtension) xineramaLib.resolve("XineramaQueryExtension"); + X11->ptrXineramaIsActive = + (PtrXineramaIsActive) xineramaLib.resolve("XineramaIsActive"); + X11->ptrXineramaQueryScreens = + (PtrXineramaQueryScreens) xineramaLib.resolve("XineramaQueryScreens"); + } +#else + X11->ptrXineramaQueryScreens = XineramaQueryScreens; + X11->ptrXineramaIsActive = XineramaIsActive; + X11->ptrXineramaQueryExtension = XineramaQueryExtension; +#endif // QT_RUNTIME_XINERAMA +#endif // QT_NO_XINERAMA + +#ifndef QT_NO_XINPUT + // See if Xinput is supported on the connected display + X11->ptrXCloseDevice = 0; + X11->ptrXListInputDevices = 0; + X11->ptrXOpenDevice = 0; + X11->ptrXFreeDeviceList = 0; + X11->ptrXSelectExtensionEvent = 0; + X11->use_xinput = XQueryExtension(X11->display, "XInputExtension", &X11->xinput_major, + &X11->xinput_eventbase, &X11->xinput_errorbase); + if (X11->use_xinput) { + X11->ptrXCloseDevice = XINPUT_LOAD(XCloseDevice); + X11->ptrXListInputDevices = XINPUT_LOAD(XListInputDevices); + X11->ptrXOpenDevice = XINPUT_LOAD(XOpenDevice); + X11->ptrXFreeDeviceList = XINPUT_LOAD(XFreeDeviceList); + X11->ptrXSelectExtensionEvent = XINPUT_LOAD(XSelectExtensionEvent); + } +#endif // QT_NO_XINPUT + +#ifndef QT_NO_XKB + int xkblibMajor = XkbMajorVersion; + int xkblibMinor = XkbMinorVersion; + X11->use_xkb = XkbQueryExtension(X11->display, + &X11->xkb_major, + &X11->xkb_eventbase, + &X11->xkb_errorbase, + &xkblibMajor, + &xkblibMinor); + if (X11->use_xkb) { + // If XKB is detected, set the GrabsUseXKBState option so input method + // compositions continue to work (ie. deadkeys) + unsigned int state = XkbPCF_GrabsUseXKBStateMask; + (void) XkbSetPerClientControls(X11->display, state, &state); + + // select for group change events + XkbSelectEventDetails(X11->display, + XkbUseCoreKbd, + XkbStateNotify, + XkbAllStateComponentsMask, + XkbGroupStateMask); + + // current group state is queried when creating the keymapper, no need to do it here + } +#endif + + +#if !defined(QT_NO_FONTCONFIG) + int dpi = 0; + getXDefault("Xft", FC_DPI, &dpi); + if (dpi) { + for (int s = 0; s < ScreenCount(X11->display); ++s) { + QX11Info::setAppDpiX(s, dpi); + QX11Info::setAppDpiY(s, dpi); + } + } + double fc_scale = 1.; + getXDefault("Xft", FC_SCALE, &fc_scale); + X11->fc_scale = fc_scale; + for (int s = 0; s < ScreenCount(X11->display); ++s) { + int subpixel = FC_RGBA_UNKNOWN; +#if !defined(QT_NO_XRENDER) && (RENDER_MAJOR > 0 || RENDER_MINOR >= 6) + if (X11->use_xrender) { + int rsp = XRenderQuerySubpixelOrder(X11->display, s); + switch (rsp) { + default: + case SubPixelUnknown: + subpixel = FC_RGBA_UNKNOWN; + break; + case SubPixelHorizontalRGB: + subpixel = FC_RGBA_RGB; + break; + case SubPixelHorizontalBGR: + subpixel = FC_RGBA_BGR; + break; + case SubPixelVerticalRGB: + subpixel = FC_RGBA_VRGB; + break; + case SubPixelVerticalBGR: + subpixel = FC_RGBA_VBGR; + break; + case SubPixelNone: + subpixel = FC_RGBA_NONE; + break; + } + } +#endif + + char *rgba = XGetDefault(X11->display, "Xft", FC_RGBA); + if (rgba) { + char *end = 0; + int v = strtol(rgba, &end, 0); + if (rgba != end) { + subpixel = v; + } else if (qstrncmp(rgba, "unknown", 7) == 0) { + subpixel = FC_RGBA_UNKNOWN; + } else if (qstrncmp(rgba, "rgb", 3) == 0) { + subpixel = FC_RGBA_RGB; + } else if (qstrncmp(rgba, "bgr", 3) == 0) { + subpixel = FC_RGBA_BGR; + } else if (qstrncmp(rgba, "vrgb", 4) == 0) { + subpixel = FC_RGBA_VRGB; + } else if (qstrncmp(rgba, "vbgr", 4) == 0) { + subpixel = FC_RGBA_VBGR; + } else if (qstrncmp(rgba, "none", 4) == 0) { + subpixel = FC_RGBA_NONE; + } + } + X11->screens[s].subpixel = subpixel; + } + getXDefault("Xft", FC_ANTIALIAS, &X11->fc_antialias); +#ifdef FC_HINT_STYLE + X11->fc_hint_style = -1; + getXDefault("Xft", FC_HINT_STYLE, &X11->fc_hint_style); +#endif +#if 0 + // ###### these are implemented by Xft, not sure we need them + getXDefault("Xft", FC_AUTOHINT, &X11->fc_autohint); + getXDefault("Xft", FC_HINTING, &X11->fc_autohint); + getXDefault("Xft", FC_MINSPACE, &X11->fc_autohint); +#endif +#endif // QT_NO_XRENDER + + // initialize key mapper + QKeyMapper::changeKeyboard(); + + // Misc. initialization +#if 0 //disabled for now.. + QSegfaultHandler::initialize(priv->argv, priv->argc); +#endif + QCursorData::initialize(); + } + QFont::initialize(); + + if(qt_is_gui_used) { + qApp->setObjectName(QString::fromLocal8Bit(appName)); + + int screen; + for (screen = 0; screen < X11->screenCount; ++screen) { + XSelectInput(X11->display, QX11Info::appRootWindow(screen), + KeymapStateMask | EnterWindowMask | LeaveWindowMask | PropertyChangeMask); + +#ifndef QT_NO_XRANDR + if (X11->use_xrandr) + X11->ptrXRRSelectInput(X11->display, QX11Info::appRootWindow(screen), True); +#endif // QT_NO_XRANDR + } + } + + if (qt_is_gui_used) { + // Attempt to determine the current running X11 Desktop Enviornment + // Use dbus if/when we can, but fall back to using windowManagerName() for now + +#ifndef QT_NO_XFIXES + if (X11->ptrXFixesSelectSelectionInput) + X11->ptrXFixesSelectSelectionInput(X11->display, QX11Info::appRootWindow(), ATOM(_NET_WM_CM_S0), + XFixesSetSelectionOwnerNotifyMask + | XFixesSelectionWindowDestroyNotifyMask + | XFixesSelectionClientCloseNotifyMask); +#endif // QT_NO_XFIXES + X11->compositingManagerRunning = XGetSelectionOwner(X11->display, + ATOM(_NET_WM_CM_S0)); + X11->desktopEnvironment = DE_UNKNOWN; + X11->desktopVersion = 0; + + Atom type; + int format; + unsigned long length, after; + uchar *data = 0; + int rc; + + do { + if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { + X11->desktopEnvironment = DE_KDE; + X11->desktopVersion = qgetenv("KDE_SESSION_VERSION").toInt(); + break; + } + + if (qgetenv("DESKTOP_SESSION") == "gnome") { + X11->desktopEnvironment = DE_GNOME; + break; + } + + // GNOME_DESKTOP_SESSION_ID is deprecated for some reason, but still check it + if (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) { + X11->desktopEnvironment = DE_GNOME; + break; + } + + rc = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), ATOM(_DT_SAVE_MODE), + 0, 2, False, XA_STRING, &type, &format, &length, + &after, &data); + if (rc == Success && length) { + if (!strcmp(reinterpret_cast(data), "xfce4")) { + // Pretend that xfce4 is gnome, as it uses the same libraries. + // The detection above is stolen from xdg-open. + X11->desktopEnvironment = DE_GNOME; + break; + } + + // We got the property but it wasn't xfce4. Free data before it gets overwritten. + XFree(data); + data = 0; + } + + rc = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), ATOM(DTWM_IS_RUNNING), + 0, 1, False, AnyPropertyType, &type, &format, &length, + &after, &data); + if (rc == Success && length) { + // DTWM is running, meaning most likely CDE is running... + X11->desktopEnvironment = DE_CDE; + break; + } + + rc = XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_SGI_DESKS_MANAGER), 0, 1, False, XA_WINDOW, + &type, &format, &length, &after, &data); + if (rc == Success && length) { + X11->desktopEnvironment = DE_4DWM; + break; + } + + if (XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_NET_SUPPORTING_WM_CHECK), + 0, 1024, False, XA_WINDOW, &type, + &format, &length, &after, &data) == Success) { + if (type == XA_WINDOW && format == 32) { + Window windowManagerWindow = *((Window*) data); + XFree(data); + data = 0; + + if (windowManagerWindow != XNone) { + Atom utf8atom = ATOM(UTF8_STRING); + if (XGetWindowProperty(QX11Info::display(), windowManagerWindow, ATOM(_NET_WM_NAME), + 0, 1024, False, utf8atom, &type, + &format, &length, &after, &data) == Success) { + if (type == utf8atom && format == 8) { + if (qstrcmp((const char *)data, "MCompositor") == 0) + X11->desktopEnvironment = DE_MEEGO_COMPOSITOR; + } + } + } + } + } + + } while(0); + + if (data) + XFree((char *)data); + +#if !defined(QT_NO_STYLE_GTK) + if (X11->desktopEnvironment == DE_GNOME) { + static bool menusHaveIcons = QGtkStyle::getGConfBool(QLatin1String("/desktop/gnome/interface/menus_have_icons"), true); + QApplication::setAttribute(Qt::AA_DontShowIconsInMenus, !menusHaveIcons); + } +#endif + qt_set_input_encoding(); + + qt_set_x11_resources(appFont, appFGCol, appBGCol, appBTNCol); + + // be smart about the size of the default font. most X servers have helvetica + // 12 point available at 2 resolutions: + // 75dpi (12 pixels) and 100dpi (17 pixels). + // At 95 DPI, a 12 point font should be 16 pixels tall - in which case a 17 + // pixel font is a closer match than a 12 pixel font + int ptsz = (X11->use_xrender + ? 9 + : (int) (((QX11Info::appDpiY() >= 95 ? 17. : 12.) * + 72. / (float) QX11Info::appDpiY()) + 0.5)); + + if (!QApplicationPrivate::sys_font) { + // no font from settings or RESOURCE_MANAGER, provide a fallback + QFont f(X11->has_fontconfig ? QLatin1String("Sans Serif") : QLatin1String("Helvetica"), + ptsz); + QApplicationPrivate::setSystemFont(f); + } + +#if !defined (QT_NO_TABLET) + if (X11->use_xinput) { + int ndev, + i, + j; + bool gotStylus, + gotEraser; + XDeviceInfo *devices = 0, *devs; + XInputClassInfo *ip; + XAnyClassPtr any; + XValuatorInfoPtr v; + XAxisInfoPtr a; + XDevice *dev = 0; + + if (X11->ptrXListInputDevices) { + devices = X11->ptrXListInputDevices(X11->display, &ndev); + if (!devices) + qWarning("QApplication: Failed to get list of tablet devices"); + } + if (!devices) + ndev = -1; + QTabletEvent::TabletDevice deviceType; + for (devs = devices, i = 0; i < ndev && devs; i++, devs++) { + dev = 0; + deviceType = QTabletEvent::NoDevice; + gotStylus = false; + gotEraser = false; + +#if defined(Q_OS_IRIX) + QString devName = QString::fromLocal8Bit(devs->name).toLower(); + if (devName == QLatin1String(WACOM_NAME)) { + deviceType = QTabletEvent::Stylus; + gotStylus = true; + } +#else + if (devs->type == ATOM(XWacomStylus) || devs->type == ATOM(XTabletStylus)) { + deviceType = QTabletEvent::Stylus; + if (wacomDeviceName()->isEmpty()) + wacomDeviceName()->append(devs->name); + gotStylus = true; + } else if (devs->type == ATOM(XWacomEraser) || devs->type == ATOM(XTabletEraser)) { + deviceType = QTabletEvent::XFreeEraser; + gotEraser = true; + } +#endif + if (deviceType == QTabletEvent::NoDevice) + continue; + + if (gotStylus || gotEraser) { + if (X11->ptrXOpenDevice) + dev = X11->ptrXOpenDevice(X11->display, devs->id); + + if (!dev) + continue; + + QTabletDeviceData device_data; + device_data.deviceType = deviceType; + device_data.eventCount = 0; + device_data.device = dev; + device_data.xinput_motion = -1; + device_data.xinput_key_press = -1; + device_data.xinput_key_release = -1; + device_data.xinput_button_press = -1; + device_data.xinput_button_release = -1; + device_data.xinput_proximity_in = -1; + device_data.xinput_proximity_out = -1; + device_data.widgetToGetPress = 0; + + if (dev->num_classes > 0) { + for (ip = dev->classes, j = 0; j < dev->num_classes; + ip++, j++) { + switch (ip->input_class) { + case KeyClass: + DeviceKeyPress(dev, device_data.xinput_key_press, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + DeviceKeyRelease(dev, device_data.xinput_key_release, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + break; + case ButtonClass: + DeviceButtonPress(dev, device_data.xinput_button_press, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + DeviceButtonRelease(dev, device_data.xinput_button_release, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + break; + case ValuatorClass: + // I'm only going to be interested in motion when the + // stylus is already down anyway! + DeviceMotionNotify(dev, device_data.xinput_motion, + device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + ProximityIn(dev, device_data.xinput_proximity_in, device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + ProximityOut(dev, device_data.xinput_proximity_out, device_data.eventList[device_data.eventCount]); + if (device_data.eventList[device_data.eventCount]) + ++device_data.eventCount; + default: + break; + } + } + } + + // get the min/max value for pressure! + any = (XAnyClassPtr) (devs->inputclassinfo); + for (j = 0; j < devs->num_classes; j++) { + if (any->c_class == ValuatorClass) { + v = (XValuatorInfoPtr) any; + a = (XAxisInfoPtr) ((char *) v + + sizeof (XValuatorInfo)); +#if defined (Q_OS_IRIX) + // I'm not exaclty wild about this, but the + // dimensions of the tablet are more relevant here + // than the min and max values from the axis + // (actually it seems to be 2/3 or what is in the + // axis. So we'll try to parse it from this + // string. --tws + char returnString[SGIDeviceRtrnLen]; + int tmp; + if (XSGIMiscQueryExtension(X11->display, &tmp, &tmp) + && XSGIDeviceQuery(X11->display, devs->id, + "dimensions", returnString)) { + QString str = QLatin1String(returnString); + int comma = str.indexOf(','); + device_data.minX = 0; + device_data.minY = 0; + device_data.maxX = str.left(comma).toInt(); + device_data.maxY = str.mid(comma + 1).toInt(); + } else { + device_data.minX = a[WAC_XCOORD_I].min_value; + device_data.maxX = a[WAC_XCOORD_I].max_value; + device_data.minY = a[WAC_YCOORD_I].min_value; + device_data.maxY = a[WAC_YCOORD_I].max_value; + } + device_data.minPressure = a[WAC_PRESSURE_I].min_value; + device_data.maxPressure = a[WAC_PRESSURE_I].max_value; + device_data.minTanPressure = a[WAC_TAN_PRESSURE_I].min_value; + device_data.maxTanPressure = a[WAC_TAN_PRESSURE_I].max_value; + device_data.minZ = a[WAC_ZCOORD_I].min_value; + device_data.maxZ = a[WAC_ZCOORD_I].max_value; +#else + device_data.minX = a[0].min_value; + device_data.maxX = a[0].max_value; + device_data.minY = a[1].min_value; + device_data.maxY = a[1].max_value; + device_data.minPressure = a[2].min_value; + device_data.maxPressure = a[2].max_value; + device_data.minTanPressure = 0; + device_data.maxTanPressure = 0; + device_data.minZ = 0; + device_data.maxZ = 0; +#endif + + // got the max pressure no need to go further... + break; + } + any = (XAnyClassPtr) ((char *) any + any->length); + } // end of for loop + + tablet_devices()->append(device_data); + } // if (gotStylus || gotEraser) + } + if (X11->ptrXFreeDeviceList) + X11->ptrXFreeDeviceList(devices); + } +#endif // QT_NO_TABLET + + X11->startupId = getenv("DESKTOP_STARTUP_ID"); + if (X11->startupId) { +#ifndef QT_NO_UNSETENV + unsetenv("DESKTOP_STARTUP_ID"); +#else + // it's a small memory leak, however we won't crash if Qt is + // unloaded and someones tries to use the envoriment. + putenv(strdup("DESKTOP_STARTUP_ID=")); +#endif + } + } else { + // read some non-GUI settings when not using the X server... + + if (QApplication::desktopSettingsAware()) { + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + // read library (ie. plugin) path list + QString libpathkey = QString::fromLatin1("%1.%2/libraryPath") + .arg(QT_VERSION >> 16) + .arg((QT_VERSION & 0xff00) >> 8); + QStringList pathlist = + settings.value(libpathkey).toString().split(QLatin1Char(':')); + if (! pathlist.isEmpty()) { + QStringList::ConstIterator it = pathlist.constBegin(); + while (it != pathlist.constEnd()) + QApplication::addLibraryPath(*it++); + } + + QString defaultcodec = settings.value(QLatin1String("defaultCodec"), + QVariant(QLatin1String("none"))).toString(); + if (defaultcodec != QLatin1String("none")) { + QTextCodec *codec = QTextCodec::codecForName(defaultcodec.toLatin1()); + if (codec) + QTextCodec::setCodecForTr(codec); + } + + settings.endGroup(); // Qt + } + } + +#if !defined (Q_OS_IRIX) && !defined (QT_NO_TABLET) + QLibrary wacom(QString::fromLatin1("wacomcfg"), 0); // version 0 is the latest release at time of writing this. + // NOTE: C casts instead of reinterpret_cast for GCC 3.3.x + ptrWacomConfigInit = (PtrWacomConfigInit)wacom.resolve("WacomConfigInit"); + ptrWacomConfigOpenDevice = (PtrWacomConfigOpenDevice)wacom.resolve("WacomConfigOpenDevice"); + ptrWacomConfigGetRawParam = (PtrWacomConfigGetRawParam)wacom.resolve("WacomConfigGetRawParam"); + ptrWacomConfigCloseDevice = (PtrWacomConfigCloseDevice)wacom.resolve("WacomConfigCloseDevice"); + ptrWacomConfigTerm = (PtrWacomConfigTerm)wacom.resolve("WacomConfigTerm"); + + if (ptrWacomConfigInit == 0 || ptrWacomConfigOpenDevice == 0 || ptrWacomConfigGetRawParam == 0 + || ptrWacomConfigCloseDevice == 0 || ptrWacomConfigTerm == 0) { // either we have all, or we have none. + ptrWacomConfigInit = 0; + ptrWacomConfigOpenDevice = 0; + ptrWacomConfigGetRawParam = 0; + ptrWacomConfigCloseDevice = 0; + ptrWacomConfigTerm = 0; + } +#endif +} + +void QApplicationPrivate::initializeWidgetPaletteHash() +{ +} + +/***************************************************************************** + qt_cleanup() - cleans up when the application is finished + *****************************************************************************/ + +void qt_cleanup() +{ + if (app_save_rootinfo) // root window must keep state + qt_save_rootinfo(); + + if (qt_is_gui_used) { + QPixmapCache::clear(); + QCursorData::cleanup(); + QFont::cleanup(); + QColormap::cleanup(); + +#if !defined (QT_NO_TABLET) + QTabletDeviceDataList *devices = qt_tablet_devices(); + if (X11->ptrXCloseDevice) + for (int i = 0; i < devices->size(); ++i) + X11->ptrXCloseDevice(X11->display, (XDevice*)devices->at(i).device); + devices->clear(); +#endif + } + +#ifndef QT_NO_XRENDER + for (int i = 0; i < X11->solid_fill_count; ++i) { + if (X11->solid_fills[i].picture) + XRenderFreePicture(X11->display, X11->solid_fills[i].picture); + } + for (int i = 0; i < X11->pattern_fill_count; ++i) { + if (X11->pattern_fills[i].picture) + XRenderFreePicture(X11->display, X11->pattern_fills[i].picture); + } +#endif + +#if !defined(QT_NO_IM) + delete QApplicationPrivate::inputContext; + QApplicationPrivate::inputContext = 0; +#endif + + // Reset the error handlers + if (qt_is_gui_used) + XSync(X11->display, False); // sync first to process all possible errors + XSetErrorHandler(original_x_errhandler); + XSetIOErrorHandler(original_xio_errhandler); + + if (X11->argbColormaps) { + for (int s = 0; s < X11->screenCount; s++) { + if (X11->argbColormaps[s]) + XFreeColormap(X11->display, X11->argbColormaps[s]); + } + } + + if (qt_is_gui_used && !X11->foreignDisplay) + XCloseDisplay(X11->display); // close X display + X11->display = 0; + + delete [] X11->screens; + delete [] X11->argbVisuals; + delete [] X11->argbColormaps; + + if (X11->foreignDisplay) { + delete [] (char *)appName; + appName = 0; + } + + delete [] (char *)appClass; + appClass = 0; + + if (X11->net_supported_list) + delete [] X11->net_supported_list; + X11->net_supported_list = 0; + + if (X11->net_virtual_root_list) + delete [] X11->net_virtual_root_list; + X11->net_virtual_root_list = 0; + + delete X11; + X11 = 0; +} + + +/***************************************************************************** + Platform specific global and internal functions + *****************************************************************************/ + +void qt_save_rootinfo() // save new root info +{ + Atom type; + int format; + unsigned long length, after; + uchar *data = 0; + + if (ATOM(_XSETROOT_ID)) { // kill old pixmap + if (XGetWindowProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_XSETROOT_ID), 0, 1, + True, AnyPropertyType, &type, &format, + &length, &after, &data) == Success) { + if (type == XA_PIXMAP && format == 32 && length == 1 && + after == 0 && data) { + XKillClient(X11->display, *((Pixmap*)data)); + } + Pixmap dummy = XCreatePixmap(X11->display, QX11Info::appRootWindow(), + 1, 1, 1); + XChangeProperty(X11->display, QX11Info::appRootWindow(), + ATOM(_XSETROOT_ID), XA_PIXMAP, 32, + PropModeReplace, (uchar *)&dummy, 1); + XSetCloseDownMode(X11->display, RetainPermanent); + } + } + if (data) + XFree((char *)data); +} + +void qt_updated_rootinfo() +{ + app_save_rootinfo = true; +} + +// ### Cleanup, this function is not in use! +bool qt_wstate_iconified(WId winid) +{ + Atom type; + int format; + unsigned long length, after; + uchar *data = 0; + int r = XGetWindowProperty(X11->display, winid, ATOM(WM_STATE), 0, 2, + False, AnyPropertyType, &type, &format, + &length, &after, &data); + bool iconic = false; + if (r == Success && data && format == 32) { + // quint32 *wstate = (quint32*)data; + unsigned long *wstate = (unsigned long *) data; + iconic = (*wstate == IconicState); + XFree((char *)data); + } + return iconic; +} + +QString QApplicationPrivate::appName() const +{ + return QString::fromLocal8Bit(QT_PREPEND_NAMESPACE(appName)); +} + +const char *QX11Info::appClass() // get application class +{ + return QT_PREPEND_NAMESPACE(appClass); +} + +bool qt_nograb() // application no-grab option +{ +#if defined(QT_DEBUG) + return appNoGrab; +#else + return false; +#endif +} + + +/***************************************************************************** + Platform specific QApplication members + *****************************************************************************/ + +#ifdef QT3_SUPPORT +void QApplication::setMainWidget(QWidget *mainWidget) +{ +#ifndef QT_NO_DEBUG + if (mainWidget && mainWidget->parentWidget() && mainWidget->isWindow()) + qWarning("QApplication::setMainWidget: New main widget (%s/%s) " + "has a parent", + mainWidget->metaObject()->className(), mainWidget->objectName().toLocal8Bit().constData()); +#endif + if (mainWidget) + mainWidget->d_func()->createWinId(); + QApplicationPrivate::main_widget = mainWidget; + if (QApplicationPrivate::main_widget) // give WM command line + QApplicationPrivate::applyX11SpecificCommandLineArguments(QApplicationPrivate::main_widget); +} +#endif + +void QApplicationPrivate::applyX11SpecificCommandLineArguments(QWidget *main_widget) +{ + static bool beenHereDoneThat = false; + if (beenHereDoneThat) + return; + beenHereDoneThat = true; + Q_ASSERT(main_widget->testAttribute(Qt::WA_WState_Created)); + if (mwTitle) { + XStoreName(X11->display, main_widget->effectiveWinId(), (char*)mwTitle); + QByteArray net_wm_name = QString::fromLocal8Bit(mwTitle).toUtf8(); + XChangeProperty(X11->display, main_widget->effectiveWinId(), ATOM(_NET_WM_NAME), ATOM(UTF8_STRING), 8, + PropModeReplace, (unsigned char *)net_wm_name.data(), net_wm_name.size()); + } + if (mwGeometry) { // parse geometry + int x, y; + int w, h; + int m = XParseGeometry((char*)mwGeometry, &x, &y, (uint*)&w, (uint*)&h); + QSize minSize = main_widget->minimumSize(); + QSize maxSize = main_widget->maximumSize(); + if ((m & XValue) == 0) + x = main_widget->geometry().x(); + if ((m & YValue) == 0) + y = main_widget->geometry().y(); + if ((m & WidthValue) == 0) + w = main_widget->width(); + if ((m & HeightValue) == 0) + h = main_widget->height(); + w = qMin(w,maxSize.width()); + h = qMin(h,maxSize.height()); + w = qMax(w,minSize.width()); + h = qMax(h,minSize.height()); + if ((m & XNegative)) { + x = QApplication::desktop()->width() + x - w; + } + if ((m & YNegative)) { + y = QApplication::desktop()->height() + y - h; + } + main_widget->setGeometry(x, y, w, h); + } +} + +#ifndef QT_NO_CURSOR + +/***************************************************************************** + QApplication cursor stack + *****************************************************************************/ + +void QApplication::setOverrideCursor(const QCursor &cursor) +{ + qApp->d_func()->cursor_list.prepend(cursor); + + QWidgetList all = allWidgets(); + for (QWidgetList::ConstIterator it = all.constBegin(); it != all.constEnd(); ++it) { + register QWidget *w = *it; + if ((w->testAttribute(Qt::WA_SetCursor) || w->isWindow()) && (w->windowType() != Qt::Desktop)) + qt_x11_enforce_cursor(w); + } + XFlush(X11->display); // make X execute it NOW +} + +void QApplication::restoreOverrideCursor() +{ + if (qApp->d_func()->cursor_list.isEmpty()) + return; + qApp->d_func()->cursor_list.removeFirst(); + + if (QWidgetPrivate::mapper != 0 && !closingDown()) { + QWidgetList all = allWidgets(); + for (QWidgetList::ConstIterator it = all.constBegin(); it != all.constEnd(); ++it) { + register QWidget *w = *it; + if ((w->testAttribute(Qt::WA_SetCursor) || w->isWindow()) && (w->windowType() != Qt::Desktop)) + qt_x11_enforce_cursor(w); + } + XFlush(X11->display); + } +} + +#endif + + +/***************************************************************************** + Routines to find a Qt widget from a screen position + *****************************************************************************/ + +Window QX11Data::findClientWindow(Window win, Atom property, bool leaf) +{ + Atom type = XNone; + int format, i; + ulong nitems, after; + uchar *data = 0; + Window root, parent, target=0, *children=0; + uint nchildren; + if (XGetWindowProperty(X11->display, win, property, 0, 0, false, AnyPropertyType, + &type, &format, &nitems, &after, &data) == Success) { + if (data) + XFree((char *)data); + if (type) + return win; + } + if (!XQueryTree(X11->display,win,&root,&parent,&children,&nchildren)) { + if (children) + XFree((char *)children); + return 0; + } + for (i=nchildren-1; !target && i >= 0; i--) + target = X11->findClientWindow(children[i], property, leaf); + if (children) + XFree((char *)children); + return target; +} + +QWidget *QApplication::topLevelAt(const QPoint &p) +{ +#ifdef QT_NO_CURSOR + Q_UNUSED(p); + return 0; +#else + int screen = QCursor::x11Screen(); + int unused; + + int x = p.x(); + int y = p.y(); + Window target; + if (!XTranslateCoordinates(X11->display, + QX11Info::appRootWindow(screen), + QX11Info::appRootWindow(screen), + x, y, &unused, &unused, &target)) { + return 0; + } + if (!target || target == QX11Info::appRootWindow(screen)) + return 0; + QWidget *w; + w = QWidget::find((WId)target); + + if (!w) { + X11->ignoreBadwindow(); + target = X11->findClientWindow(target, ATOM(WM_STATE), true); + if (X11->badwindow()) + return 0; + w = QWidget::find((WId)target); + if (!w) { + // Perhaps the widget at (x,y) is inside a foreign application? + // Search all toplevel widgets to see if one is within target + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; i < list.count(); ++i) { + QWidget *widget = list.at(i); + Window ctarget = target; + if (widget->isVisible() && !(widget->windowType() == Qt::Desktop)) { + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + Window wid = widget->internalWinId(); + while (ctarget && !w) { + X11->ignoreBadwindow(); + if (!XTranslateCoordinates(X11->display, + QX11Info::appRootWindow(screen), + ctarget, x, y, &unused, &unused, &ctarget) + || X11->badwindow()) + break; + if (ctarget == wid) { + // Found! + w = widget; + break; + } + } + } + if (w) + break; + } + } + } + return w ? w->window() : 0; +#endif +} + +void QApplication::syncX() +{ + if (X11->display) + XSync(X11->display, False); // don't discard events +} + + +void QApplication::beep() +{ + if (X11->display) + XBell(X11->display, 0); + else + printf("\7"); +} + +void QApplication::alert(QWidget *widget, int msec) +{ + if (!QApplicationPrivate::checkInstance("alert")) + return; + + QWidgetList windowsToMark; + if (!widget) { + windowsToMark += topLevelWidgets(); + } else { + windowsToMark.append(widget->window()); + } + + for (int i = 0; i < windowsToMark.size(); ++i) { + QWidget *window = windowsToMark.at(i); + if (!window->isActiveWindow()) { + qt_change_net_wm_state(window, true, ATOM(_NET_WM_STATE_DEMANDS_ATTENTION)); + if (msec != 0) { + QTimer *timer = new QTimer(qApp); + timer->setSingleShot(true); + connect(timer, SIGNAL(timeout()), qApp, SLOT(_q_alertTimeOut())); + if (QTimer *oldTimer = qApp->d_func()->alertTimerHash.value(window)) { + qApp->d_func()->alertTimerHash.remove(window); + delete oldTimer; + } + qApp->d_func()->alertTimerHash.insert(window, timer); + timer->start(msec); + } + } + } +} + +void QApplicationPrivate::_q_alertTimeOut() +{ + if (QTimer *timer = qobject_cast(q_func()->sender())) { + QHash::iterator it = alertTimerHash.begin(); + while (it != alertTimerHash.end()) { + if (it.value() == timer) { + QWidget *window = it.key(); + qt_change_net_wm_state(window, false, ATOM(_NET_WM_STATE_DEMANDS_ATTENTION)); + alertTimerHash.erase(it); + timer->deleteLater(); + break; + } + ++it; + } + } +} + +/***************************************************************************** + Special lookup functions for windows that have been reparented recently + *****************************************************************************/ + +static QWidgetMapper *wPRmapper = 0; // alternative widget mapper + +void qPRCreate(const QWidget *widget, Window oldwin) +{ // QWidget::reparent mechanism + if (!wPRmapper) + wPRmapper = new QWidgetMapper; + + QETWidget *w = static_cast(const_cast(widget)); + wPRmapper->insert((int)oldwin, w); // add old window to mapper + w->setAttribute(Qt::WA_WState_Reparented); // set reparented flag +} + +void qPRCleanup(QWidget *widget) +{ + QETWidget *etw = static_cast(const_cast(widget)); + if (!(wPRmapper && widget->testAttribute(Qt::WA_WState_Reparented))) + return; // not a reparented widget + QWidgetMapper::Iterator it = wPRmapper->begin(); + while (it != wPRmapper->constEnd()) { + QWidget *w = *it; + if (w == etw) { // found widget + etw->setAttribute(Qt::WA_WState_Reparented, false); // clear flag + it = wPRmapper->erase(it);// old window no longer needed + } else { + ++it; + } + } + if (wPRmapper->size() == 0) { // became empty + delete wPRmapper; // then reset alt mapper + wPRmapper = 0; + } +} + +static QETWidget *qPRFindWidget(Window oldwin) +{ + return wPRmapper ? (QETWidget*)wPRmapper->value((int)oldwin, 0) : 0; +} + +int QApplication::x11ClientMessage(QWidget* w, XEvent* event, bool passive_only) +{ + if (w && !w->internalWinId()) + return 0; + QETWidget *widget = (QETWidget*)w; + if (event->xclient.format == 32 && event->xclient.message_type) { + if (event->xclient.message_type == ATOM(WM_PROTOCOLS)) { + Atom a = event->xclient.data.l[0]; + if (a == ATOM(WM_DELETE_WINDOW)) { + if (passive_only) return 0; + widget->translateCloseEvent(event); + } + else if (a == ATOM(WM_TAKE_FOCUS)) { + if ((ulong) event->xclient.data.l[1] > X11->time) + X11->time = event->xclient.data.l[1]; + QWidget *amw = activeModalWidget(); + if (amw && amw->testAttribute(Qt::WA_X11DoNotAcceptFocus)) + amw = 0; + if (amw && !QApplicationPrivate::tryModalHelper(widget, 0)) { + QWidget *p = amw->parentWidget(); + while (p && p != widget) + p = p->parentWidget(); + if (!p || !X11->net_supported_list) + amw->raise(); // help broken window managers + amw->activateWindow(); + } +#ifndef QT_NO_WHATSTHIS + } else if (a == ATOM(_NET_WM_CONTEXT_HELP)) { + QWhatsThis::enterWhatsThisMode(); +#endif // QT_NO_WHATSTHIS + } else if (a == ATOM(_NET_WM_PING)) { + // avoid send/reply loops + Window root = RootWindow(X11->display, w->x11Info().screen()); + if (event->xclient.window != root) { + event->xclient.window = root; + XSendEvent(event->xclient.display, event->xclient.window, + False, SubstructureNotifyMask|SubstructureRedirectMask, event); + } +#ifndef QT_NO_XSYNC + } else if (a == ATOM(_NET_WM_SYNC_REQUEST)) { + const ulong timestamp = (const ulong) event->xclient.data.l[1]; + if (timestamp > X11->time) + X11->time = timestamp; + if (QTLWExtra *tlw = w->d_func()->maybeTopData()) { + if (timestamp == CurrentTime || timestamp > tlw->syncRequestTimestamp) { + tlw->syncRequestTimestamp = timestamp; + tlw->newCounterValueLo = event->xclient.data.l[2]; + tlw->newCounterValueHi = event->xclient.data.l[3]; + } + } +#endif + } + } else if (event->xclient.message_type == ATOM(_QT_SCROLL_DONE)) { + widget->translateScrollDoneEvent(event); + } else if (event->xclient.message_type == ATOM(XdndPosition)) { + X11->xdndHandlePosition(widget, event, passive_only); + } else if (event->xclient.message_type == ATOM(XdndEnter)) { + X11->xdndHandleEnter(widget, event, passive_only); + } else if (event->xclient.message_type == ATOM(XdndStatus)) { + X11->xdndHandleStatus(widget, event, passive_only); + } else if (event->xclient.message_type == ATOM(XdndLeave)) { + X11->xdndHandleLeave(widget, event, passive_only); + } else if (event->xclient.message_type == ATOM(XdndDrop)) { + X11->xdndHandleDrop(widget, event, passive_only); + } else if (event->xclient.message_type == ATOM(XdndFinished)) { + X11->xdndHandleFinished(widget, event, passive_only); + } else { + if (passive_only) return 0; + // All other are interactions + } + } else { + X11->motifdndHandle(widget, event, passive_only); + } + + return 0; +} + +int QApplication::x11ProcessEvent(XEvent* event) +{ + Q_D(QApplication); + QScopedLoopLevelCounter loopLevelCounter(d->threadData); + +#ifdef ALIEN_DEBUG + //qDebug() << "QApplication::x11ProcessEvent:" << event->type; +#endif + switch (event->type) { + case ButtonPress: + pressed_window = event->xbutton.window; + X11->userTime = event->xbutton.time; + // fallthrough intended + case ButtonRelease: + X11->time = event->xbutton.time; + break; + case MotionNotify: + X11->time = event->xmotion.time; + break; + case XKeyPress: + X11->userTime = event->xkey.time; + // fallthrough intended + case XKeyRelease: + X11->time = event->xkey.time; + break; + case PropertyNotify: + X11->time = event->xproperty.time; + break; + case EnterNotify: + case LeaveNotify: + X11->time = event->xcrossing.time; + break; + case SelectionClear: + X11->time = event->xselectionclear.time; + break; + default: + break; + } +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) { + XFixesSelectionNotifyEvent *req = + reinterpret_cast(event); + X11->time = req->selection_timestamp; + if (req->selection == ATOM(_NET_WM_CM_S0)) + X11->compositingManagerRunning = req->owner; + } +#endif + + QETWidget *widget = (QETWidget*)QWidget::find((WId)event->xany.window); + + if (wPRmapper) { // just did a widget reparent? + if (widget == 0) { // not in std widget mapper + switch (event->type) { // only for mouse/key events + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case XKeyPress: + case XKeyRelease: + widget = qPRFindWidget(event->xany.window); + break; + } + } + else if (widget->testAttribute(Qt::WA_WState_Reparented)) + qPRCleanup(widget); // remove from alt mapper + } + + QETWidget *keywidget=0; + bool grabbed=false; + if (event->type==XKeyPress || event->type==XKeyRelease) { + keywidget = (QETWidget*)QWidget::keyboardGrabber(); + if (keywidget) { + grabbed = true; + } else if (!keywidget) { + if (d->inPopupMode()) // no focus widget, see if we have a popup + keywidget = (QETWidget*) (activePopupWidget()->focusWidget() ? activePopupWidget()->focusWidget() : activePopupWidget()); + else if (QApplicationPrivate::focus_widget) + keywidget = (QETWidget*)QApplicationPrivate::focus_widget; + else if (widget) + keywidget = (QETWidget*)widget->window(); + } + } + +#ifndef QT_NO_IM + // Filtering input events by the input context. It has to be taken + // place before any other key event consumers such as eventfilters + // and accelerators because some input methods require quite + // various key combination and sequences. It often conflicts with + // accelerators and so on, so we must give the input context the + // filtering opportunity first to ensure all input methods work + // properly regardless of application design. + + if(keywidget && keywidget->isEnabled() && keywidget->testAttribute(Qt::WA_InputMethodEnabled)) { + // block user interaction during session management + if((event->type==XKeyPress || event->type==XKeyRelease) && qt_sm_blockUserInput) + return true; + + // for XIM handling + QInputContext *qic = keywidget->inputContext(); + if(qic && qic->x11FilterEvent(keywidget, event)) + return true; + + // filterEvent() accepts QEvent *event rather than preexpanded + // key event attribute values. This is intended to pass other + // QInputEvent in future. Other non IM-related events should + // not be forwarded to input contexts to prevent weird event + // handling. + if ((event->type == XKeyPress || event->type == XKeyRelease)) { + int code = -1; + int count = 0; + Qt::KeyboardModifiers modifiers; + QEvent::Type type; + QString text; + KeySym keySym; + + qt_keymapper_private()->translateKeyEventInternal(keywidget, event, keySym, count, + text, modifiers, code, type, false); + + // both key press/release is required for some complex + // input methods. don't eliminate anything. + QKeyEventEx keyevent(type, code, modifiers, text, false, qMax(qMax(count, 1), text.length()), + event->xkey.keycode, keySym, event->xkey.state); + if(qic && qic->filterEvent(&keyevent)) + return true; + } + } else +#endif // QT_NO_IM + { + if (XFilterEvent(event, XNone)) + return true; + } + + if (qt_x11EventFilter(event)) // send through app filter + return 1; + + if (event->type == MappingNotify) { + // keyboard mapping changed + XRefreshKeyboardMapping(&event->xmapping); + + QKeyMapper::changeKeyboard(); + return 0; + } +#ifndef QT_NO_XKB + else if (X11->use_xkb && event->type == X11->xkb_eventbase) { + XkbAnyEvent *xkbevent = (XkbAnyEvent *) event; + switch (xkbevent->xkb_type) { + case XkbStateNotify: + { + XkbStateNotifyEvent *xkbstateevent = (XkbStateNotifyEvent *) xkbevent; + if ((xkbstateevent->changed & XkbGroupStateMask) != 0) { + qt_keymapper_private()->xkb_currentGroup = xkbstateevent->group; + QKeyMapper::changeKeyboard(); + } + break; + } + default: + break; + } + } +#endif + + if (!widget) { // don't know this windows + QWidget* popup = QApplication::activePopupWidget(); + if (popup) { + + /* + That is more than suboptimal. The real solution should + do some keyevent and buttonevent translation, so that + the popup still continues to work as the user expects. + Unfortunately this translation is currently only + possible with a known widget. I'll change that soon + (Matthias). + */ + + // Danger - make sure we don't lock the server + switch (event->type) { + case ButtonPress: + case ButtonRelease: + case XKeyPress: + case XKeyRelease: + do { + popup->close(); + } while ((popup = qApp->activePopupWidget())); + return 1; + } + } + return -1; + } + + if (event->type == XKeyPress || event->type == XKeyRelease) + widget = keywidget; // send XKeyEvents through keywidget->x11Event() + + if (app_do_modal) // modal event handling + if (!qt_try_modal(widget, event)) { + if (event->type == ClientMessage && !widget->x11Event(event)) + x11ClientMessage(widget, event, true); + return 1; + } + + + if (widget->x11Event(event)) // send through widget filter + return 1; +#if !defined (QT_NO_TABLET) + if (!qt_xdnd_dragging) { + QTabletDeviceDataList *tablets = qt_tablet_devices(); + for (int i = 0; i < tablets->size(); ++i) { + QTabletDeviceData &tab = tablets->operator [](i); + if (event->type == tab.xinput_motion + || event->type == tab.xinput_button_release + || event->type == tab.xinput_button_press + || event->type == tab.xinput_proximity_in + || event->type == tab.xinput_proximity_out) { + widget->translateXinputEvent(event, &tab); + return 0; + } + } + } +#endif + +#ifndef QT_NO_XRANDR + if (X11->use_xrandr && event->type == (X11->xrandr_eventbase + RRScreenChangeNotify)) { + // update Xlib internals with the latest screen configuration + X11->ptrXRRUpdateConfiguration(event); + + // update the size for desktop widget + int scr = X11->ptrXRRRootToScreen(X11->display, event->xany.window); + QDesktopWidget *desktop = QApplication::desktop(); + QWidget *w = desktop->screen(scr); + QSize oldSize(w->size()); + w->data->crect.setWidth(DisplayWidth(X11->display, scr)); + w->data->crect.setHeight(DisplayHeight(X11->display, scr)); + QResizeEvent e(w->size(), oldSize); + QApplication::sendEvent(w, &e); + if (w != desktop) + QApplication::sendEvent(desktop, &e); + } +#endif // QT_NO_XRANDR + +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) { + XFixesSelectionNotifyEvent *req = reinterpret_cast(event); + + // compress all XFixes events related to this selection + // we don't want to handle old SelectionNotify events. + qt_xfixes_selection_event_data xfixes_event; + xfixes_event.selection = req->selection; + for (XEvent ev;;) { + if (!XCheckIfEvent(X11->display, &ev, &qt_xfixes_scanner, (XPointer)&xfixes_event)) + break; + } + + if (req->selection == ATOM(CLIPBOARD)) { + if (qt_xfixes_clipboard_changed(req->owner, req->selection_timestamp)) { + emit clipboard()->changed(QClipboard::Clipboard); + emit clipboard()->dataChanged(); + } + } else if (req->selection == XA_PRIMARY) { + if (qt_xfixes_selection_changed(req->owner, req->selection_timestamp)) { + emit clipboard()->changed(QClipboard::Selection); + emit clipboard()->selectionChanged(); + } + } + } +#endif // QT_NO_XFIXES + + switch (event->type) { + + case ButtonRelease: // mouse event + if (!d->inPopupMode() && !QWidget::mouseGrabber() && pressed_window != widget->internalWinId() + && (widget = (QETWidget*) QWidget::find((WId)pressed_window)) == 0) + break; + // fall through intended + case ButtonPress: + if (event->xbutton.root != RootWindow(X11->display, widget->x11Info().screen()) + && ! qt_xdnd_dragging) { + while (activePopupWidget()) + activePopupWidget()->close(); + return 1; + } + if (event->type == ButtonPress) + qt_net_update_user_time(widget->window(), X11->userTime); + // fall through intended + case MotionNotify: +#if !defined(QT_NO_TABLET) + if (!qt_tabletChokeMouse) { +#endif + if (widget->testAttribute(Qt::WA_TransparentForMouseEvents)) { + QPoint pos(event->xbutton.x, event->xbutton.y); + pos = widget->d_func()->mapFromWS(pos); + QWidget *window = widget->window(); + pos = widget->mapTo(window, pos); + if (QWidget *child = window->childAt(pos)) { + widget = static_cast(child); + pos = child->mapFrom(window, pos); + event->xbutton.x = pos.x(); + event->xbutton.y = pos.y(); + } + } + widget->translateMouseEvent(event); +#if !defined(QT_NO_TABLET) + } else { + qt_tabletChokeMouse = false; + } +#endif + break; + + case XKeyPress: // keyboard event + qt_net_update_user_time(widget->window(), X11->userTime); + // fallthrough intended + case XKeyRelease: + { + if (keywidget && keywidget->isEnabled()) { // should always exist + // qDebug("sending key event"); + qt_keymapper_private()->translateKeyEvent(keywidget, event, grabbed); + } + break; + } + + case GraphicsExpose: + case Expose: // paint event + widget->translatePaintEvent(event); + break; + + case ConfigureNotify: // window move/resize event + if (event->xconfigure.event == event->xconfigure.window) + widget->translateConfigEvent(event); + break; + + case XFocusIn: { // got focus + if ((widget->windowType() == Qt::Desktop)) + break; + if (d->inPopupMode()) // some delayed focus event to ignore + break; + if (!widget->isWindow()) + break; + if (event->xfocus.detail != NotifyAncestor && + event->xfocus.detail != NotifyInferior && + event->xfocus.detail != NotifyNonlinear) + break; + setActiveWindow(widget); + if (X11->focus_model == QX11Data::FM_PointerRoot) { + // We got real input focus from somewhere, but we were in PointerRoot + // mode, so we don't trust this event. Check the focus model to make + // sure we know what focus mode we are using... + qt_check_focus_model(); + } + } + break; + + case XFocusOut: // lost focus + if ((widget->windowType() == Qt::Desktop)) + break; + if (!widget->isWindow()) + break; + if (event->xfocus.mode == NotifyGrab) { + qt_xfocusout_grab_counter++; + break; + } + if (event->xfocus.detail != NotifyAncestor && + event->xfocus.detail != NotifyNonlinearVirtual && + event->xfocus.detail != NotifyNonlinear) + break; + if (!d->inPopupMode() && widget == QApplicationPrivate::active_window) { + XEvent ev; + bool focus_will_change = false; + if (XCheckTypedEvent(X11->display, XFocusIn, &ev)) { + // we're about to get an XFocusIn, if we know we will + // get a new active window, we don't want to set the + // active window to 0 now + QWidget *w2 = QWidget::find(ev.xany.window); + if (w2 + && w2->windowType() != Qt::Desktop + && !d->inPopupMode() // some delayed focus event to ignore + && w2->isWindow() + && (ev.xfocus.detail == NotifyAncestor + || ev.xfocus.detail == NotifyInferior + || ev.xfocus.detail == NotifyNonlinear)) + focus_will_change = true; + + XPutBackEvent(X11->display, &ev); + } + if (!focus_will_change) + setActiveWindow(0); + } + break; + + case EnterNotify: { // enter window + if (QWidget::mouseGrabber() && (!d->inPopupMode() || widget->window() != activePopupWidget())) + break; + if ((event->xcrossing.mode != NotifyNormal + && event->xcrossing.mode != NotifyUngrab) + || event->xcrossing.detail == NotifyVirtual + || event->xcrossing.detail == NotifyNonlinearVirtual) + break; + if (event->xcrossing.focus && + !(widget->windowType() == Qt::Desktop) && !widget->isActiveWindow()) { + if (X11->focus_model == QX11Data::FM_Unknown) // check focus model + qt_check_focus_model(); + if (X11->focus_model == QX11Data::FM_PointerRoot) // PointerRoot mode + setActiveWindow(widget); + } + + if (qt_button_down && !d->inPopupMode()) + break; + + QWidget *alien = widget->childAt(widget->d_func()->mapFromWS(QPoint(event->xcrossing.x, + event->xcrossing.y))); + QWidget *enter = alien ? alien : widget; + QWidget *leave = 0; + if (qt_last_mouse_receiver && !qt_last_mouse_receiver->internalWinId()) + leave = qt_last_mouse_receiver; + else + leave = QWidget::find(curWin); + + // ### Alien: enter/leave might be wrong here with overlapping siblings + // if the enter widget is native and stacked under a non-native widget. + QApplicationPrivate::dispatchEnterLeave(enter, leave); + curWin = widget->internalWinId(); + qt_last_mouse_receiver = enter; + if (!d->inPopupMode() || widget->window() == activePopupWidget()) + widget->translateMouseEvent(event); //we don't get MotionNotify, emulate it + } + break; + case LeaveNotify: { // leave window + QWidget *mouseGrabber = QWidget::mouseGrabber(); + if (mouseGrabber && !d->inPopupMode()) + break; + if (curWin && widget->internalWinId() != curWin) + break; + if ((event->xcrossing.mode != NotifyNormal + && event->xcrossing.mode != NotifyUngrab) + || event->xcrossing.detail == NotifyInferior) + break; + if (!(widget->windowType() == Qt::Desktop)) + widget->translateMouseEvent(event); //we don't get MotionNotify, emulate it + + QWidget* enter = 0; + QPoint enterPoint; + XEvent ev; + while (XCheckMaskEvent(X11->display, EnterWindowMask | LeaveWindowMask , &ev) + && !qt_x11EventFilter(&ev)) { + QWidget* event_widget = QWidget::find(ev.xcrossing.window); + if(event_widget && event_widget->x11Event(&ev)) + break; + if (ev.type == LeaveNotify + || (ev.xcrossing.mode != NotifyNormal + && ev.xcrossing.mode != NotifyUngrab) + || ev.xcrossing.detail == NotifyVirtual + || ev.xcrossing.detail == NotifyNonlinearVirtual) + continue; + enter = event_widget; + if (enter) + enterPoint = enter->d_func()->mapFromWS(QPoint(ev.xcrossing.x, ev.xcrossing.y)); + if (ev.xcrossing.focus && + enter && !(enter->windowType() == Qt::Desktop) && !enter->isActiveWindow()) { + if (X11->focus_model == QX11Data::FM_Unknown) // check focus model + qt_check_focus_model(); + if (X11->focus_model == QX11Data::FM_PointerRoot) // PointerRoot mode + setActiveWindow(enter); + } + break; + } + + if ((! enter || (enter->windowType() == Qt::Desktop)) && + event->xcrossing.focus && widget == QApplicationPrivate::active_window && + X11->focus_model == QX11Data::FM_PointerRoot // PointerRoot mode + ) { + setActiveWindow(0); + } + + if (qt_button_down && !d->inPopupMode()) + break; + + if (!curWin) + QApplicationPrivate::dispatchEnterLeave(widget, 0); + + if (enter) { + QWidget *alienEnter = enter->childAt(enterPoint); + if (alienEnter) + enter = alienEnter; + } + + QWidget *leave = qt_last_mouse_receiver ? qt_last_mouse_receiver : widget; + QWidget *activePopupWidget = qApp->activePopupWidget(); + + if (mouseGrabber && activePopupWidget && leave == activePopupWidget) + enter = mouseGrabber; + else if (enter != widget && mouseGrabber) { + if (!widget->rect().contains(widget->d_func()->mapFromWS(QPoint(event->xcrossing.x, + event->xcrossing.y)))) + break; + } + + QApplicationPrivate::dispatchEnterLeave(enter, leave); + qt_last_mouse_receiver = enter; + + if (enter && QApplicationPrivate::tryModalHelper(enter, 0)) { + QWidget *nativeEnter = enter->internalWinId() ? enter : enter->nativeParentWidget(); + curWin = nativeEnter->internalWinId(); + static_cast(nativeEnter)->translateMouseEvent(&ev); //we don't get MotionNotify, emulate it + } else { + curWin = 0; + qt_last_mouse_receiver = 0; + } + } + break; + + case UnmapNotify: // window hidden + if (widget->isWindow()) { + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + widget->d_func()->topData()->waitingForMapNotify = 0; + + if (widget->windowType() != Qt::Popup && !widget->testAttribute(Qt::WA_DontShowOnScreen)) { + widget->setAttribute(Qt::WA_Mapped, false); + if (widget->isVisible()) { + widget->d_func()->topData()->spont_unmapped = 1; + QHideEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + widget->d_func()->hideChildren(true); + } + } + + if (!widget->d_func()->topData()->validWMState && X11->deferred_map.removeAll(widget)) + widget->doDeferredMap(); + } + break; + + case MapNotify: // window shown + if (widget->isWindow()) { + // if we got a MapNotify when we were not waiting for it, it most + // likely means the user has already asked to hide the window before + // it ever being shown, so we try to withdraw a window after sending + // the QShowEvent. + bool pendingHide = widget->testAttribute(Qt::WA_WState_ExplicitShowHide) && widget->testAttribute(Qt::WA_WState_Hidden); + widget->d_func()->topData()->waitingForMapNotify = 0; + + if (widget->windowType() != Qt::Popup) { + widget->setAttribute(Qt::WA_Mapped); + if (widget->d_func()->topData()->spont_unmapped) { + widget->d_func()->topData()->spont_unmapped = 0; + widget->d_func()->showChildren(true); + QShowEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + + // show() must have been called on this widget in + // order to reach this point, but we could have + // cleared these 2 attributes in case something + // previously forced us into WithdrawnState + // (e.g. kdocker) + widget->setAttribute(Qt::WA_WState_ExplicitShowHide, true); + widget->setAttribute(Qt::WA_WState_Visible, true); + } + } + if (pendingHide) // hide the window + XWithdrawWindow(X11->display, widget->internalWinId(), widget->x11Info().screen()); + } + break; + + case ClientMessage: // client message + return x11ClientMessage(widget,event,False); + + case ReparentNotify: { // window manager reparents + // compress old reparent events to self + XEvent ev; + while (XCheckTypedWindowEvent(X11->display, + widget->effectiveWinId(), + ReparentNotify, + &ev)) { + if (ev.xreparent.window != ev.xreparent.event) { + XPutBackEvent(X11->display, &ev); + break; + } + } + if (widget->isWindow()) { + QTLWExtra *topData = widget->d_func()->topData(); + + // store the parent. Useful for many things, embedding for instance. + topData->parentWinId = event->xreparent.parent; + + // the widget frame strut should also be invalidated + widget->data->fstrut_dirty = 1; + + // work around broken window managers... if we get a + // ReparentNotify before the MapNotify, we assume that + // we're being managed by a reparenting window + // manager. + // + // however, the WM_STATE property may not have been set + // yet, but we are going to assume that it will + // be... otherwise we could try to map again after getting + // an UnmapNotify... which could then, in turn, trigger a + // race in the window manager which causes the window to + // disappear when it really should be hidden. + if (topData->waitingForMapNotify && !topData->validWMState) { + topData->waitingForMapNotify = 0; + topData->validWMState = 1; + } + + if (X11->focus_model != QX11Data::FM_Unknown) { + // toplevel reparented... + QWidget *newparent = QWidget::find(event->xreparent.parent); + if (! newparent || (newparent->windowType() == Qt::Desktop)) { + // we don't know about the new parent (or we've been + // reparented to root), perhaps a window manager + // has been (re)started? reset the focus model to unknown + X11->focus_model = QX11Data::FM_Unknown; + } + } + } + break; + } + case SelectionRequest: { + XSelectionRequestEvent *req = &event->xselectionrequest; + if (! req) + break; + + if (ATOM(XdndSelection) && req->selection == ATOM(XdndSelection)) { + X11->xdndHandleSelectionRequest(req); + + } else if (qt_clipboard) { + QClipboardEvent e(reinterpret_cast(event)); + QApplication::sendSpontaneousEvent(qt_clipboard, &e); + } + break; + } + case SelectionClear: { + XSelectionClearEvent *req = &event->xselectionclear; + // don't deliver dnd events to the clipboard, it gets confused + if (! req || (ATOM(XdndSelection) && req->selection == ATOM(XdndSelection))) + break; + + if (qt_clipboard && !X11->use_xfixes) { + QClipboardEvent e(reinterpret_cast(event)); + QApplication::sendSpontaneousEvent(qt_clipboard, &e); + } + break; + } + + case SelectionNotify: { + XSelectionEvent *req = &event->xselection; + // don't deliver dnd events to the clipboard, it gets confused + if (! req || (ATOM(XdndSelection) && req->selection == ATOM(XdndSelection))) + break; + + if (qt_clipboard) { + QClipboardEvent e(reinterpret_cast(event)); + QApplication::sendSpontaneousEvent(qt_clipboard, &e); + } + break; + } + case PropertyNotify: + // some properties changed + if (event->xproperty.window == QX11Info::appRootWindow(0)) { + // root properties for the first screen + if (!X11->use_xfixes && event->xproperty.atom == ATOM(_QT_CLIPBOARD_SENTINEL)) { + if (qt_check_clipboard_sentinel()) { + emit clipboard()->changed(QClipboard::Clipboard); + emit clipboard()->dataChanged(); + } + } else if (!X11->use_xfixes && event->xproperty.atom == ATOM(_QT_SELECTION_SENTINEL)) { + if (qt_check_selection_sentinel()) { + emit clipboard()->changed(QClipboard::Selection); + emit clipboard()->selectionChanged(); + } + } else if (QApplicationPrivate::obey_desktop_settings) { + if (event->xproperty.atom == ATOM(RESOURCE_MANAGER)) + qt_set_x11_resources(); + else if (event->xproperty.atom == ATOM(_QT_SETTINGS_TIMESTAMP)) + qt_set_x11_resources(); + } + } + if (event->xproperty.window == QX11Info::appRootWindow()) { + // root properties for the default screen + if (event->xproperty.atom == ATOM(_QT_INPUT_ENCODING)) { + qt_set_input_encoding(); + } else if (event->xproperty.atom == ATOM(_NET_SUPPORTED)) { + qt_get_net_supported(); + } else if (event->xproperty.atom == ATOM(_NET_VIRTUAL_ROOTS)) { + qt_get_net_virtual_roots(); + } else if (event->xproperty.atom == ATOM(_NET_WORKAREA)) { + qt_desktopwidget_update_workarea(); + + // emit the workAreaResized() signal + QDesktopWidget *desktop = QApplication::desktop(); + int numScreens = desktop->numScreens(); + for (int i = 0; i < numScreens; ++i) + emit desktop->workAreaResized(i); + } + } else if (widget) { + widget->translatePropertyEvent(event); + } else { + return -1; // don't know this window + } + break; + + default: + break; + } + + return 0; +} + +bool QApplication::x11EventFilter(XEvent *) +{ + return false; +} + + + +/***************************************************************************** + Modal widgets; Since Xlib has little support for this we roll our own + modal widget mechanism. + A modal widget without a parent becomes application-modal. + A modal widget with a parent becomes modal to its parent and grandparents.. + + QApplicationPrivate::enterModal() + Enters modal state + Arguments: + QWidget *widget A modal widget + + QApplicationPrivate::leaveModal() + Leaves modal state for a widget + Arguments: + QWidget *widget A modal widget + *****************************************************************************/ + +bool QApplicationPrivate::modalState() +{ + return app_do_modal; +} + +void QApplicationPrivate::enterModal_sys(QWidget *widget) +{ + if (!qt_modal_stack) + qt_modal_stack = new QWidgetList; + + QWidget *leave = qt_last_mouse_receiver; + if (!leave) + leave = QWidget::find((WId)curWin); + QApplicationPrivate::dispatchEnterLeave(0, leave); + qt_modal_stack->insert(0, widget); + app_do_modal = true; + curWin = 0; + qt_last_mouse_receiver = 0; +} + +void QApplicationPrivate::leaveModal_sys(QWidget *widget) +{ + if (qt_modal_stack && qt_modal_stack->removeAll(widget)) { + if (qt_modal_stack->isEmpty()) { + delete qt_modal_stack; + qt_modal_stack = 0; + QPoint p(QCursor::pos()); + QWidget* w = QApplication::widgetAt(p.x(), p.y()); + QWidget *leave = qt_last_mouse_receiver; + if (!leave) + leave = QWidget::find((WId)curWin); + if (QWidget *grabber = QWidget::mouseGrabber()) { + w = grabber; + if (leave == w) + leave = 0; + } + QApplicationPrivate::dispatchEnterLeave(w, leave); // send synthetic enter event + curWin = w ? w->effectiveWinId() : 0; + qt_last_mouse_receiver = w; + } + } + app_do_modal = qt_modal_stack != 0; +} + +bool qt_try_modal(QWidget *widget, XEvent *event) +{ + if (qt_xdnd_dragging) { + // allow mouse events while DnD is active + switch (event->type) { + case ButtonPress: + case ButtonRelease: + case MotionNotify: + return true; + default: + break; + } + } + + // allow mouse release events to be sent to widgets that have been pressed + if (event->type == ButtonRelease) { + QWidget *alienWidget = widget->childAt(widget->mapFromGlobal(QPoint(event->xbutton.x_root, + event->xbutton.y_root))); + if (widget == qt_button_down || (alienWidget && alienWidget == qt_button_down)) + return true; + } + + if (QApplicationPrivate::tryModalHelper(widget)) + return true; + + // disallow mouse/key events + switch (event->type) { + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case XKeyPress: + case XKeyRelease: + case EnterNotify: + case LeaveNotify: + case ClientMessage: + return false; + default: + break; + } + + return true; +} + + +/***************************************************************************** + Popup widget mechanism + + openPopup() + Adds a widget to the list of popup widgets + Arguments: + QWidget *widget The popup widget to be added + + closePopup() + Removes a widget from the list of popup widgets + Arguments: + QWidget *widget The popup widget to be removed + *****************************************************************************/ + + +static int openPopupCount = 0; +void QApplicationPrivate::openPopup(QWidget *popup) +{ + Q_Q(QApplication); + openPopupCount++; + if (!QApplicationPrivate::popupWidgets) { // create list + QApplicationPrivate::popupWidgets = new QWidgetList; + } + QApplicationPrivate::popupWidgets->append(popup); // add to end of list + Display *dpy = X11->display; + if (QApplicationPrivate::popupWidgets->count() == 1 && !qt_nograb()){ // grab mouse/keyboard + Q_ASSERT(popup->testAttribute(Qt::WA_WState_Created)); + int r = XGrabKeyboard(dpy, popup->effectiveWinId(), false, + GrabModeAsync, GrabModeAsync, X11->time); + if ((popupGrabOk = (r == GrabSuccess))) { + r = XGrabPointer(dpy, popup->effectiveWinId(), true, + (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | EnterWindowMask | LeaveWindowMask | PointerMotionMask), + GrabModeAsync, GrabModeAsync, XNone, XNone, X11->time); + if (!(popupGrabOk = (r == GrabSuccess))) { + // transfer grab back to the keyboard grabber if any + if (QWidgetPrivate::keyboardGrabber != 0) + QWidgetPrivate::keyboardGrabber->grabKeyboard(); + else + XUngrabKeyboard(dpy, X11->time); + } + } + } + + // popups are not focus-handled by the window system (the first + // popup grabbed the keyboard), so we have to do that manually: A + // new popup gets the focus + if (popup->focusWidget()) { + popup->focusWidget()->setFocus(Qt::PopupFocusReason); + } else if (QApplicationPrivate::popupWidgets->count() == 1) { // this was the first popup + if (QWidget *fw = QApplication::focusWidget()) { + QFocusEvent e(QEvent::FocusOut, Qt::PopupFocusReason); + q->sendEvent(fw, &e); + } + } +} + +void QApplicationPrivate::closePopup(QWidget *popup) +{ + Q_Q(QApplication); + if (!QApplicationPrivate::popupWidgets) + return; + QApplicationPrivate::popupWidgets->removeAll(popup); + if (popup == qt_popup_down) { + qt_button_down = 0; + qt_popup_down = 0; + } + if (QApplicationPrivate::popupWidgets->count() == 0) { // this was the last popup + delete QApplicationPrivate::popupWidgets; + QApplicationPrivate::popupWidgets = 0; + if (!qt_nograb() && popupGrabOk) { // grabbing not disabled + Display *dpy = X11->display; + if (popup->geometry().contains(QPoint(mouseGlobalXPos, mouseGlobalYPos)) + || popup->testAttribute(Qt::WA_NoMouseReplay)) { + // mouse release event or inside + replayPopupMouseEvent = false; + } else { // mouse press event + mouseButtonPressTime -= 10000; // avoid double click + replayPopupMouseEvent = true; + } + // transfer grab back to mouse grabber if any, otherwise release the grab + if (QWidgetPrivate::mouseGrabber != 0) + QWidgetPrivate::mouseGrabber->grabMouse(); + else + XUngrabPointer(dpy, X11->time); + + // transfer grab back to keyboard grabber if any, otherwise release the grab + if (QWidgetPrivate::keyboardGrabber != 0) + QWidgetPrivate::keyboardGrabber->grabKeyboard(); + else + XUngrabKeyboard(dpy, X11->time); + + XFlush(dpy); + } + if (QApplicationPrivate::active_window) { + if (QWidget *fw = QApplicationPrivate::active_window->focusWidget()) { + if (fw != QApplication::focusWidget()) { + fw->setFocus(Qt::PopupFocusReason); + } else { + QFocusEvent e(QEvent::FocusIn, Qt::PopupFocusReason); + q->sendEvent(fw, &e); + } + } + } + } else { + // popups are not focus-handled by the window system (the + // first popup grabbed the keyboard), so we have to do that + // manually: A popup was closed, so the previous popup gets + // the focus. + QWidget* aw = QApplicationPrivate::popupWidgets->last(); + if (QWidget *fw = aw->focusWidget()) + fw->setFocus(Qt::PopupFocusReason); + + // regrab the keyboard and mouse in case 'popup' lost the grab + if (QApplicationPrivate::popupWidgets->count() == 1 && !qt_nograb()){ // grab mouse/keyboard + Display *dpy = X11->display; + Q_ASSERT(aw->testAttribute(Qt::WA_WState_Created)); + int r = XGrabKeyboard(dpy, aw->effectiveWinId(), false, + GrabModeAsync, GrabModeAsync, X11->time); + if ((popupGrabOk = (r == GrabSuccess))) { + r = XGrabPointer(dpy, aw->effectiveWinId(), true, + (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | EnterWindowMask | LeaveWindowMask | PointerMotionMask), + GrabModeAsync, GrabModeAsync, XNone, XNone, X11->time); + if (!(popupGrabOk = (r == GrabSuccess))) { + // transfer grab back to keyboard grabber + if (QWidgetPrivate::keyboardGrabber != 0) + QWidgetPrivate::keyboardGrabber->grabKeyboard(); + else + XUngrabKeyboard(dpy, X11->time); + } + } + } + } +} + +/***************************************************************************** + Event translation; translates X11 events to Qt events + *****************************************************************************/ + +// +// Mouse event translation +// +// Xlib doesn't give mouse double click events, so we generate them by +// comparing window, time and position between two mouse press events. +// + +static Qt::MouseButtons translateMouseButtons(int s) +{ + Qt::MouseButtons ret = 0; + if (s & Button1Mask) + ret |= Qt::LeftButton; + if (s & Button2Mask) + ret |= Qt::MidButton; + if (s & Button3Mask) + ret |= Qt::RightButton; + return ret; +} + +Qt::KeyboardModifiers QX11Data::translateModifiers(int s) +{ + Qt::KeyboardModifiers ret = 0; + if (s & ShiftMask) + ret |= Qt::ShiftModifier; + if (s & ControlMask) + ret |= Qt::ControlModifier; + if (s & qt_alt_mask) + ret |= Qt::AltModifier; + if (s & qt_meta_mask) + ret |= Qt::MetaModifier; + if (s & qt_mode_switch_mask) + ret |= Qt::GroupSwitchModifier; + return ret; +} + +bool QETWidget::translateMouseEvent(const XEvent *event) +{ + if (!isWindow() && testAttribute(Qt::WA_NativeWindow)) + Q_ASSERT(internalWinId()); + + Q_D(QWidget); + QEvent::Type type; // event parameters + QPoint pos; + QPoint globalPos; + Qt::MouseButton button = Qt::NoButton; + Qt::MouseButtons buttons; + Qt::KeyboardModifiers modifiers; + XEvent nextEvent; + + if (qt_sm_blockUserInput) // block user interaction during session management + return true; + + if (event->type == MotionNotify) { // mouse move + if (event->xmotion.root != RootWindow(X11->display, x11Info().screen()) && + ! qt_xdnd_dragging) + return false; + + XMotionEvent lastMotion = event->xmotion; + while(XPending(X11->display)) { // compress mouse moves + XNextEvent(X11->display, &nextEvent); + if (nextEvent.type == ConfigureNotify + || nextEvent.type == PropertyNotify + || nextEvent.type == Expose + || nextEvent.type == GraphicsExpose + || nextEvent.type == NoExpose + || nextEvent.type == KeymapNotify + || ((nextEvent.type == EnterNotify || nextEvent.type == LeaveNotify) + && qt_button_down == this) + || (nextEvent.type == ClientMessage + && (nextEvent.xclient.message_type == ATOM(_QT_SCROLL_DONE) || + (nextEvent.xclient.message_type == ATOM(WM_PROTOCOLS) && + (Atom)nextEvent.xclient.data.l[0] == ATOM(_NET_WM_SYNC_REQUEST))))) { + qApp->x11ProcessEvent(&nextEvent); + continue; + } else if (nextEvent.type != MotionNotify || + nextEvent.xmotion.window != event->xmotion.window || + nextEvent.xmotion.state != event->xmotion.state) { + XPutBackEvent(X11->display, &nextEvent); + break; + } + if (!qt_x11EventFilter(&nextEvent) + && !x11Event(&nextEvent)) // send event through filter + lastMotion = nextEvent.xmotion; + else + break; + } + type = QEvent::MouseMove; + pos.rx() = lastMotion.x; + pos.ry() = lastMotion.y; + pos = d->mapFromWS(pos); + globalPos.rx() = lastMotion.x_root; + globalPos.ry() = lastMotion.y_root; + buttons = translateMouseButtons(lastMotion.state); + modifiers = X11->translateModifiers(lastMotion.state); + if (qt_button_down && !buttons) + qt_button_down = 0; + } else if (event->type == EnterNotify || event->type == LeaveNotify) { + XEvent *xevent = (XEvent *)event; + //unsigned int xstate = event->xcrossing.state; + type = QEvent::MouseMove; + pos.rx() = xevent->xcrossing.x; + pos.ry() = xevent->xcrossing.y; + pos = d->mapFromWS(pos); + globalPos.rx() = xevent->xcrossing.x_root; + globalPos.ry() = xevent->xcrossing.y_root; + buttons = translateMouseButtons(xevent->xcrossing.state); + modifiers = X11->translateModifiers(xevent->xcrossing.state); + if (qt_button_down && !buttons) + qt_button_down = 0; + if (qt_button_down) + return true; + } else { // button press or release + pos.rx() = event->xbutton.x; + pos.ry() = event->xbutton.y; + pos = d->mapFromWS(pos); + globalPos.rx() = event->xbutton.x_root; + globalPos.ry() = event->xbutton.y_root; + buttons = translateMouseButtons(event->xbutton.state); + modifiers = X11->translateModifiers(event->xbutton.state); + switch (event->xbutton.button) { + case Button1: button = Qt::LeftButton; break; + case Button2: button = Qt::MidButton; break; + case Button3: button = Qt::RightButton; break; + case Button4: + case Button5: + case 6: + case 7: + // the fancy mouse wheel. + + // We are only interested in ButtonPress. + if (event->type == ButtonPress){ + // compress wheel events (the X Server will simply + // send a button press for each single notch, + // regardless whether the application can catch up + // or not) + int delta = 1; + XEvent xevent; + while (XCheckTypedWindowEvent(X11->display, effectiveWinId(), ButtonPress, &xevent)){ + if (xevent.xbutton.button != event->xbutton.button){ + XPutBackEvent(X11->display, &xevent); + break; + } + delta++; + } + + // the delta is defined as multiples of + // WHEEL_DELTA, which is set to 120. Future wheels + // may offer a finer-resolution. A positive delta + // indicates forward rotation, a negative one + // backward rotation respectively. + int btn = event->xbutton.button; + delta *= 120 * ((btn == Button4 || btn == 6) ? 1 : -1); + bool hor = (((btn == Button4 || btn == Button5) && (modifiers & Qt::AltModifier)) || + (btn == 6 || btn == 7)); + translateWheelEvent(globalPos.x(), globalPos.y(), delta, buttons, + modifiers, (hor) ? Qt::Horizontal: Qt::Vertical); + } + return true; + case 8: button = Qt::XButton1; break; + case 9: button = Qt::XButton2; break; + } + if (event->type == ButtonPress) { // mouse button pressed + buttons |= button; +#if defined(Q_OS_IRIX) && !defined(QT_NO_TABLET) + QTabletDeviceDataList *tablets = qt_tablet_devices(); + for (int i = 0; i < tablets->size(); ++i) { + QTabletDeviceData &tab = tablets->operator[](i); + XEvent myEv; + if (XCheckTypedEvent(X11->display, tab.xinput_button_press, &myEv)) { + if (translateXinputEvent(&myEv, &tab)) { + //Spontaneous event sent. Check if we need to continue. + if (qt_tabletChokeMouse) { + qt_tabletChokeMouse = false; + return false; + } + } + } + } +#endif + if (!qt_button_down) { + qt_button_down = childAt(pos); //magic for masked widgets + if (!qt_button_down) + qt_button_down = this; + } + if (mouseActWindow == event->xbutton.window && + mouseButtonPressed == button && + (long)event->xbutton.time -(long)mouseButtonPressTime + < QApplication::doubleClickInterval() && + qAbs(event->xbutton.x - mouseXPos) < QT_GUI_DOUBLE_CLICK_RADIUS && + qAbs(event->xbutton.y - mouseYPos) < QT_GUI_DOUBLE_CLICK_RADIUS) { + type = QEvent::MouseButtonDblClick; + mouseButtonPressTime -= 2000; // no double-click next time + } else { + type = QEvent::MouseButtonPress; + mouseButtonPressTime = event->xbutton.time; + } + mouseButtonPressed = button; // save event params for + mouseXPos = event->xbutton.x; // future double click tests + mouseYPos = event->xbutton.y; + mouseGlobalXPos = globalPos.x(); + mouseGlobalYPos = globalPos.y(); + } else { // mouse button released + buttons &= ~button; +#if defined(Q_OS_IRIX) && !defined(QT_NO_TABLET) + QTabletDeviceDataList *tablets = qt_tablet_devices(); + for (int i = 0; i < tablets->size(); ++i) { + QTabletDeviceData &tab = tablets->operator[](i); + XEvent myEv; + if (XCheckTypedEvent(X11->display, tab.xinput_button_press, &myEv)) { + if (translateXinputEvent(&myEv, &tab)) { + //Spontaneous event sent. Check if we need to continue. + if (qt_tabletChokeMouse) { + qt_tabletChokeMouse = false; + return false; + } + } + } + } +#endif + type = QEvent::MouseButtonRelease; + } + } + mouseActWindow = effectiveWinId(); // save some event params + mouseButtonState = buttons; + if (type == 0) // don't send event + return false; + + if (qApp->d_func()->inPopupMode()) { // in popup mode + QWidget *activePopupWidget = qApp->activePopupWidget(); + QWidget *popup = qApp->activePopupWidget(); + if (popup != this) { + if (event->type == LeaveNotify) + return false; + if ((windowType() == Qt::Popup) && rect().contains(pos) && 0) + popup = this; + else // send to last popup + pos = popup->mapFromGlobal(globalPos); + } + bool releaseAfter = false; + QWidget *popupChild = popup->childAt(pos); + + if (popup != qt_popup_down){ + qt_button_down = 0; + qt_popup_down = 0; + } + + switch (type) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + qt_button_down = popupChild; + qt_popup_down = popup; + break; + case QEvent::MouseButtonRelease: + releaseAfter = true; + break; + default: + break; // nothing for mouse move + } + + int oldOpenPopupCount = openPopupCount; + + if (popup->isEnabled()) { + // deliver event + replayPopupMouseEvent = false; + QWidget *receiver = popup; + QPoint widgetPos = pos; + if (qt_button_down) + receiver = qt_button_down; + else if (popupChild) + receiver = popupChild; + if (receiver != popup) + widgetPos = receiver->mapFromGlobal(globalPos); + QWidget *alien = childAt(mapFromGlobal(globalPos)); + QMouseEvent e(type, widgetPos, globalPos, button, buttons, modifiers); + QApplicationPrivate::sendMouseEvent(receiver, &e, alien, this, &qt_button_down, qt_last_mouse_receiver); + } else { + // close disabled popups when a mouse button is pressed or released + switch (type) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonRelease: + popup->close(); + break; + default: + break; + } + } + + if (qApp->activePopupWidget() != activePopupWidget + && replayPopupMouseEvent) { + // the active popup was closed, replay the mouse event + if (!(windowType() == Qt::Popup)) { +#if 1 + qt_button_down = 0; +#else + if (buttons == button) + qt_button_down = this; + QMouseEvent e(type, mapFromGlobal(globalPos), globalPos, button, + buttons, modifiers); + QApplication::sendSpontaneousEvent(this, &e); + + if (type == QEvent::MouseButtonPress + && button == Qt::RightButton + && (openPopupCount == oldOpenPopupCount)) { + QContextMenuEvent e(QContextMenuEvent::Mouse, mapFromGlobal(globalPos), + globalPos, modifiers); + QApplication::sendSpontaneousEvent(this, &e); + } +#endif + } + replayPopupMouseEvent = false; + } else if (type == QEvent::MouseButtonPress + && button == Qt::RightButton + && (openPopupCount == oldOpenPopupCount)) { + QWidget *popupEvent = popup; + if (qt_button_down) + popupEvent = qt_button_down; + else if(popupChild) + popupEvent = popupChild; + QContextMenuEvent e(QContextMenuEvent::Mouse, pos, globalPos, modifiers); + QApplication::sendSpontaneousEvent(popupEvent, &e); + } + + if (releaseAfter) { + qt_button_down = 0; + qt_popup_down = 0; + } + } else { + QWidget *alienWidget = childAt(pos); + QWidget *widget = QApplicationPrivate::pickMouseReceiver(this, globalPos, pos, type, buttons, + qt_button_down, alienWidget); + if (!widget) { + if (type == QEvent::MouseButtonRelease) + QApplicationPrivate::mouse_buttons &= ~button; + return false; // don't send event + } + + int oldOpenPopupCount = openPopupCount; + QMouseEvent e(type, pos, globalPos, button, buttons, modifiers); + QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, + qt_last_mouse_receiver); + if (type == QEvent::MouseButtonPress + && button == Qt::RightButton + && (openPopupCount == oldOpenPopupCount)) { + QContextMenuEvent e(QContextMenuEvent::Mouse, pos, globalPos, modifiers); + QApplication::sendSpontaneousEvent(widget, &e); + } + } + return true; +} + + +// +// Wheel event translation +// +bool QETWidget::translateWheelEvent(int global_x, int global_y, int delta, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, + Qt::Orientation orient) +{ + const QPoint globalPos = QPoint(global_x, global_y); + QPoint pos = mapFromGlobal(globalPos); + QWidget *widget = childAt(pos); + if (!widget) + widget = this; + else if (!widget->internalWinId()) + pos = widget->mapFromGlobal(globalPos); + +#ifdef ALIEN_DEBUG + qDebug() << "QETWidget::translateWheelEvent: receiver:" << widget << "pos:" << pos; +#endif + + // send the event to the widget or its ancestors + { + QWidget* popup = qApp->activePopupWidget(); + if (popup && window() != popup) + popup->close(); +#ifndef QT_NO_WHEELEVENT + QWheelEvent e(pos, globalPos, delta, buttons, modifiers, orient); + if (QApplication::sendSpontaneousEvent(widget, &e)) +#endif + return true; + } + + // send the event to the widget that has the focus or its ancestors, if different + if (widget != qApp->focusWidget() && (widget = qApp->focusWidget())) { + if (widget && !widget->internalWinId()) + pos = widget->mapFromGlobal(globalPos); + QWidget* popup = qApp->activePopupWidget(); + if (popup && widget != popup) + popup->hide(); +#ifndef QT_NO_WHEELEVENT + QWheelEvent e(pos, globalPos, delta, buttons, modifiers, orient); + if (QApplication::sendSpontaneousEvent(widget, &e)) +#endif + return true; + } + return false; +} + + +// +// XInput Translation Event +// +#if !defined (QT_NO_TABLET) + +#if !defined (Q_OS_IRIX) +void fetchWacomToolId(int &deviceType, qint64 &serialId) +{ + if (ptrWacomConfigInit == 0) // we actually have the lib + return; + WACOMCONFIG *config = ptrWacomConfigInit(X11->display, 0); + if (config == 0) + return; + WACOMDEVICE *device = ptrWacomConfigOpenDevice (config, wacomDeviceName()->constData()); + if (device == 0) + return; + unsigned keys[1]; + int serialInt; + ptrWacomConfigGetRawParam (device, XWACOM_PARAM_TOOLSERIAL, &serialInt, 1, keys); + serialId = serialInt; + int toolId; + ptrWacomConfigGetRawParam (device, XWACOM_PARAM_TOOLID, &toolId, 1, keys); + switch(toolId) { + case 0x007: /* Mouse 4D and 2D */ + case 0x017: /* Intuos3 2D Mouse */ + case 0x094: + case 0x09c: + deviceType = QTabletEvent::FourDMouse; + break; + case 0x096: /* Lens cursor */ + case 0x097: /* Intuos3 Lens cursor */ + deviceType = QTabletEvent::Puck; + break; + case 0x0fa: + case 0x81b: /* Intuos3 Classic Pen Eraser */ + case 0x82a: /* Eraser */ + case 0x82b: /* Intuos3 Grip Pen Eraser */ + case 0x85a: + case 0x91a: + case 0x91b: /* Intuos3 Airbrush Eraser */ + case 0xd1a: + deviceType = QTabletEvent::XFreeEraser; + break; + case 0x112: + case 0x912: + case 0x913: /* Intuos3 Airbrush */ + case 0xd12: + deviceType = QTabletEvent::Airbrush; + break; + case 0x012: + case 0x022: + case 0x032: + case 0x801: /* Intuos3 Inking pen */ + case 0x812: /* Inking pen */ + case 0x813: /* Intuos3 Classic Pen */ + case 0x822: /* Pen */ + case 0x823: /* Intuos3 Grip Pen */ + case 0x832: /* Stroke pen */ + case 0x842: + case 0x852: + case 0x885: /* Intuos3 Marker Pen */ + default: /* Unknown tool */ + deviceType = QTabletEvent::Stylus; + } + + /* Close device and return */ + ptrWacomConfigCloseDevice (device); + ptrWacomConfigTerm(config); +} +#endif + +struct qt_tablet_motion_data +{ + bool filterByWidget; + const QWidget *widget; + const QWidget *etWidget; + int tabletMotionType; + bool error; // found a reason to stop searching +}; + +static Bool qt_mouseMotion_scanner(Display *, XEvent *event, XPointer arg) +{ + qt_tablet_motion_data *data = (qt_tablet_motion_data *) arg; + if (data->error) + return false; + + if (event->type == MotionNotify) + return true; + + data->error = event->type != data->tabletMotionType; // we stop compression when another event gets in between. + return false; +} + +static Bool qt_tabletMotion_scanner(Display *, XEvent *event, XPointer arg) +{ + qt_tablet_motion_data *data = (qt_tablet_motion_data *) arg; + if (data->error) + return false; + if (event->type == data->tabletMotionType) { + const XDeviceMotionEvent *const motion = reinterpret_cast(event); + if (data->filterByWidget) { + const QPoint curr(motion->x, motion->y); + const QWidget *w = data->etWidget; + const QWidget *const child = w->childAt(curr); + if (child) { + w = child; + } + if (w == data->widget) + return true; + } else { + return true; + } + } + + data->error = event->type != MotionNotify; // we stop compression when another event gets in between. + return false; +} + +bool QETWidget::translateXinputEvent(const XEvent *ev, QTabletDeviceData *tablet) +{ +#if defined (Q_OS_IRIX) + // Wacom has put defines in their wacom.h file so it would be quite wise + // to use them, need to think of a decent way of not using + // it when it doesn't exist... + XDeviceState *s; + XInputClass *iClass; + XValuatorState *vs; + int j; +#endif + + Q_ASSERT(tablet != 0); + + QWidget *w = this; + QPoint global, + curr; + QPointF hiRes; + qreal pressure = 0; + int xTilt = 0, + yTilt = 0, + z = 0; + qreal tangentialPressure = 0; + qreal rotation = 0; + int deviceType = QTabletEvent::NoDevice; + int pointerType = QTabletEvent::UnknownPointer; + const XDeviceMotionEvent *motion = 0; + XDeviceButtonEvent *button = 0; + const XProximityNotifyEvent *proximity = 0; + QEvent::Type t; + Qt::KeyboardModifiers modifiers = 0; +#if !defined (Q_OS_IRIX) + XID device_id; +#endif + + if (ev->type == tablet->xinput_motion) { + motion = reinterpret_cast(ev); + t = QEvent::TabletMove; + global = QPoint(motion->x_root, motion->y_root); + curr = QPoint(motion->x, motion->y); +#if !defined (Q_OS_IRIX) + device_id = motion->deviceid; +#endif + } else if (ev->type == tablet->xinput_button_press || ev->type == tablet->xinput_button_release) { + if (ev->type == tablet->xinput_button_press) { + t = QEvent::TabletPress; + } else { + t = QEvent::TabletRelease; + } + button = (XDeviceButtonEvent*)ev; + + global = QPoint(button->x_root, button->y_root); + curr = QPoint(button->x, button->y); +#if !defined (Q_OS_IRIX) + device_id = button->deviceid; +#endif + } else { // Proximity + if (ev->type == tablet->xinput_proximity_in) + t = QEvent::TabletEnterProximity; + else + t = QEvent::TabletLeaveProximity; + proximity = (const XProximityNotifyEvent*)ev; +#if !defined (Q_OS_IRIX) + device_id = proximity->deviceid; +#endif + } + + qint64 uid = 0; +#if defined (Q_OS_IRIX) + QRect screenArea = qApp->desktop()->screenGeometry(this); + s = XQueryDeviceState(X11->display, static_cast(tablet->device)); + if (!s) + return false; + iClass = s->data; + for (j = 0; j < s->num_classes; j++) { + if (iClass->c_class == ValuatorClass) { + vs = reinterpret_cast(iClass); + // figure out what device we have, based on bitmasking... + if (vs->valuators[WAC_TRANSDUCER_I] + & WAC_TRANSDUCER_PROX_MSK) { + switch (vs->valuators[WAC_TRANSDUCER_I] + & WAC_TRANSDUCER_MSK) { + case WAC_PUCK_ID: + pointerType = QTabletEvent::Puck; + break; + case WAC_STYLUS_ID: + pointerType = QTabletEvent::Pen; + break; + case WAC_ERASER_ID: + pointerType = QTabletEvent::Eraser; + break; + } + // Get a Unique Id for the device, Wacom gives us this ability + uid = vs->valuators[WAC_TRANSDUCER_I] & WAC_TRANSDUCER_ID_MSK; + uid = (uid << 24) | vs->valuators[WAC_SERIAL_NUM_I]; + switch (WAC_TRANSDUCER_I & 0x0F0600) { + case 0x080200: + deviceType = QTabletEvent::Stylus; + break; + case 0x090200: + deviceType = QTabletEvent::Airbrush; + break; + case 0x000400: + deviceType = QTabletEvent::FourDMouse; + break; + case 0x000600: + deviceType = QTabletEvent::Puck; + break; + case 0x080400: + deviceType = QTabletEvent::RotationStylus; + break; + } + } else { + pointerType = QTabletEvent::UnknownPointer; + deviceType = QTabletEvent::NoDevice; + uid = 0; + } + + if (!proximity) { + // apparently Wacom needs a cast for the +/- values to make sense + xTilt = short(vs->valuators[WAC_XTILT_I]); + yTilt = short(vs->valuators[WAC_YTILT_I]); + pressure = vs->valuators[WAC_PRESSURE_I]; + if (deviceType == QTabletEvent::FourDMouse + || deviceType == QTabletEvent::RotationStylus) { + rotation = vs->valuators[WAC_ROTATION_I] / 64.0; + if (deviceType == QTabletEvent::FourDMouse) + z = vs->valuators[WAC_ZCOORD_I]; + } else if (deviceType == QTabletEvent::Airbrush) { + tangentialPressure = vs->valuators[WAC_TAN_PRESSURE_I] + / qreal(tablet->maxTanPressure - tablet->minTanPressure); + } + + hiRes = tablet->scaleCoord(vs->valuators[WAC_XCOORD_I], vs->valuators[WAC_YCOORD_I], + screenArea.x(), screenArea.width(), + screenArea.y(), screenArea.height()); + } + break; + } + iClass = reinterpret_cast(reinterpret_cast(iClass) + iClass->length); + } + XFreeDeviceState(s); +#else + QTabletDeviceDataList *tablet_list = qt_tablet_devices(); + for (int i = 0; i < tablet_list->size(); ++i) { + const QTabletDeviceData &t = tablet_list->at(i); + if (device_id == static_cast(t.device)->device_id) { + deviceType = t.deviceType; + if (t.deviceType == QTabletEvent::XFreeEraser) { + deviceType = QTabletEvent::Stylus; + pointerType = QTabletEvent::Eraser; + } else if (t.deviceType == QTabletEvent::Stylus) { + pointerType = QTabletEvent::Pen; + } + break; + } + } + + fetchWacomToolId(deviceType, uid); + + QRect screenArea = qApp->desktop()->rect(); + if (motion) { + xTilt = (short) motion->axis_data[3]; + yTilt = (short) motion->axis_data[4]; + rotation = ((short) motion->axis_data[5]) / 64.0; + pressure = (short) motion->axis_data[2]; + modifiers = X11->translateModifiers(motion->state); + hiRes = tablet->scaleCoord(motion->axis_data[0], motion->axis_data[1], + screenArea.x(), screenArea.width(), + screenArea.y(), screenArea.height()); + } else if (button) { + xTilt = (short) button->axis_data[3]; + yTilt = (short) button->axis_data[4]; + rotation = ((short) button->axis_data[5]) / 64.0; + pressure = (short) button->axis_data[2]; + modifiers = X11->translateModifiers(button->state); + hiRes = tablet->scaleCoord(button->axis_data[0], button->axis_data[1], + screenArea.x(), screenArea.width(), + screenArea.y(), screenArea.height()); + } else if (proximity) { + pressure = 0; + modifiers = 0; + } + if (deviceType == QTabletEvent::Airbrush) { + tangentialPressure = rotation; + rotation = 0.; + } +#endif + + if (tablet->widgetToGetPress) { + w = tablet->widgetToGetPress; + } else { + QWidget *child = w->childAt(curr); + if (child) + w = child; + } + curr = w->mapFromGlobal(global); + + if (t == QEvent::TabletPress) { + tablet->widgetToGetPress = w; + } else if (t == QEvent::TabletRelease && tablet->widgetToGetPress) { + w = tablet->widgetToGetPress; + curr = w->mapFromGlobal(global); + tablet->widgetToGetPress = 0; + } + + QTabletEvent e(t, curr, global, hiRes, + deviceType, pointerType, + qreal(pressure / qreal(tablet->maxPressure - tablet->minPressure)), + xTilt, yTilt, tangentialPressure, rotation, z, modifiers, uid); + if (proximity) { + QApplication::sendSpontaneousEvent(qApp, &e); + } else { + QApplication::sendSpontaneousEvent(w, &e); + const bool accepted = e.isAccepted(); + if (!accepted && ev->type == tablet->xinput_motion) { + // If the widget does not accept tablet events, we drop the next ones from the event queue + // for this widget so it is not overloaded with the numerous tablet events. + qt_tablet_motion_data tabletMotionData; + tabletMotionData.tabletMotionType = tablet->xinput_motion; + tabletMotionData.widget = w; + tabletMotionData.etWidget = this; + // if nothing is pressed, the events are filtered by position + tabletMotionData.filterByWidget = (tablet->widgetToGetPress == 0); + + bool reinsertMouseEvent = false; + XEvent mouseMotionEvent; + while (true) { + // Find first mouse event since we expect them in pairs inside Qt + tabletMotionData.error =false; + if (XCheckIfEvent(X11->display, &mouseMotionEvent, &qt_mouseMotion_scanner, (XPointer) &tabletMotionData)) { + reinsertMouseEvent = true; + } else { + break; + } + + // Now discard any duplicate tablet events. + tabletMotionData.error = false; + XEvent dummy; + while (XCheckIfEvent(X11->display, &dummy, &qt_tabletMotion_scanner, (XPointer) &tabletMotionData)) { + // just discard the event + } + } + + if (reinsertMouseEvent) { + XPutBackEvent(X11->display, &mouseMotionEvent); + } + } + } + return true; +} +#endif + +bool QETWidget::translatePropertyEvent(const XEvent *event) +{ + Q_D(QWidget); + if (!isWindow()) return true; + + Atom ret; + int format, e; + unsigned char *data = 0; + unsigned long nitems, after; + + if (event->xproperty.atom == ATOM(_KDE_NET_WM_FRAME_STRUT)) { + this->data->fstrut_dirty = 1; + + if (event->xproperty.state == PropertyNewValue) { + e = XGetWindowProperty(X11->display, event->xproperty.window, ATOM(_KDE_NET_WM_FRAME_STRUT), + 0, 4, // struts are 4 longs + False, XA_CARDINAL, &ret, &format, &nitems, &after, &data); + + if (e == Success && ret == XA_CARDINAL && + format == 32 && nitems == 4) { + long *strut = (long *) data; + d->topData()->frameStrut.setCoords(strut[0], strut[2], strut[1], strut[3]); + this->data->fstrut_dirty = 0; + } + } + } else if (event->xproperty.atom == ATOM(_NET_WM_STATE)) { + bool max = false; + bool full = false; + Qt::WindowStates oldState = Qt::WindowStates(this->data->window_state); + + if (event->xproperty.state == PropertyNewValue) { + // using length of 1024 should be safe for all current and + // possible NET states... + e = XGetWindowProperty(X11->display, event->xproperty.window, ATOM(_NET_WM_STATE), 0, 1024, + False, XA_ATOM, &ret, &format, &nitems, &after, &data); + + if (e == Success && ret == XA_ATOM && format == 32 && nitems > 0) { + Atom *states = (Atom *) data; + + unsigned long i; + uint maximized = 0; + for (i = 0; i < nitems; i++) { + if (states[i] == ATOM(_NET_WM_STATE_MAXIMIZED_VERT)) + maximized |= 1; + else if (states[i] == ATOM(_NET_WM_STATE_MAXIMIZED_HORZ)) + maximized |= 2; + else if (states[i] == ATOM(_NET_WM_STATE_FULLSCREEN)) + full = true; + } + if (maximized == 3) { + // only set maximized if both horizontal and vertical properties are set + max = true; + } + } + } + + bool send_event = false; + + if (X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_VERT)) + && X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ))) { + if (max && !isMaximized()) { + this->data->window_state = this->data->window_state | Qt::WindowMaximized; + send_event = true; + } else if (!max && isMaximized()) { + this->data->window_state &= ~Qt::WindowMaximized; + send_event = true; + } + } + + if (X11->isSupportedByWM(ATOM(_NET_WM_STATE_FULLSCREEN))) { + if (full && !isFullScreen()) { + this->data->window_state = this->data->window_state | Qt::WindowFullScreen; + send_event = true; + } else if (!full && isFullScreen()) { + this->data->window_state &= ~Qt::WindowFullScreen; + send_event = true; + } + } + + if (send_event) { + QWindowStateChangeEvent e(oldState); + QApplication::sendSpontaneousEvent(this, &e); + } + } else if (event->xproperty.atom == ATOM(WM_STATE)) { + // the widget frame strut should also be invalidated + this->data->fstrut_dirty = 1; + + if (event->xproperty.state == PropertyDelete) { + // the window manager has removed the WM State property, + // so it is now in the withdrawn state (ICCCM 4.1.3.1) and + // we are free to reuse this window + d->topData()->parentWinId = 0; + d->topData()->validWMState = 0; + // map the window if we were waiting for a transition to + // withdrawn + if (X11->deferred_map.removeAll(this)) { + doDeferredMap(); + } else if (isVisible() + && !testAttribute(Qt::WA_Mapped) + && !testAttribute(Qt::WA_OutsideWSRange)) { + // so that show() will work again. As stated in the + // ICCCM section 4.1.4: "Only the client can effect a + // transition into or out of the Withdrawn state.", + // but apparently this particular window manager + // doesn't seem to care + setAttribute(Qt::WA_WState_ExplicitShowHide, false); + setAttribute(Qt::WA_WState_Visible, false); + } + } else { + // the window manager has changed the WM State property... + // we are wanting to see if we are withdrawn so that we + // can reuse this window... + e = XGetWindowProperty(X11->display, internalWinId(), ATOM(WM_STATE), 0, 2, False, + ATOM(WM_STATE), &ret, &format, &nitems, &after, &data); + + if (e == Success && ret == ATOM(WM_STATE) && format == 32 && nitems > 0) { + long *state = (long *) data; + switch (state[0]) { + case WithdrawnState: + // if we are in the withdrawn state, we are free + // to reuse this window provided we remove the + // WM_STATE property (ICCCM 4.1.3.1) + XDeleteProperty(X11->display, internalWinId(), ATOM(WM_STATE)); + + // set the parent id to zero, so that show() will + // work again + d->topData()->parentWinId = 0; + d->topData()->validWMState = 0; + // map the window if we were waiting for a + // transition to withdrawn + if (X11->deferred_map.removeAll(this)) { + doDeferredMap(); + } else if (isVisible() + && !testAttribute(Qt::WA_Mapped) + && !testAttribute(Qt::WA_OutsideWSRange)) { + // so that show() will work again. As stated + // in the ICCCM section 4.1.4: "Only the + // client can effect a transition into or out + // of the Withdrawn state.", but apparently + // this particular window manager doesn't seem + // to care + setAttribute(Qt::WA_WState_ExplicitShowHide, false); + setAttribute(Qt::WA_WState_Visible, false); + } + break; + + case IconicState: + d->topData()->validWMState = 1; + if (!isMinimized()) { + // window was minimized + this->data->window_state = this->data->window_state | Qt::WindowMinimized; + QWindowStateChangeEvent e(Qt::WindowStates(this->data->window_state & ~Qt::WindowMinimized)); + QApplication::sendSpontaneousEvent(this, &e); + } + break; + + default: + d->topData()->validWMState = 1; + if (isMinimized()) { + // window was un-minimized + this->data->window_state &= ~Qt::WindowMinimized; + QWindowStateChangeEvent e(Qt::WindowStates(this->data->window_state | Qt::WindowMinimized)); + QApplication::sendSpontaneousEvent(this, &e); + } + break; + } + } + } + } else if (event->xproperty.atom == ATOM(_NET_WM_WINDOW_OPACITY)) { + // the window opacity was changed + if (event->xproperty.state == PropertyNewValue) { + e = XGetWindowProperty(event->xclient.display, + event->xclient.window, + ATOM(_NET_WM_WINDOW_OPACITY), + 0, 1, False, XA_CARDINAL, + &ret, &format, &nitems, &after, &data); + + if (e == Success && ret == XA_CARDINAL && format == 32 && nitems == 1 + && after == 0 && data) { + ulong value = *(ulong*)(data); + d->topData()->opacity = uint(value >> 24); + } + } else + d->topData()->opacity = 255; + } + + if (data) + XFree(data); + + return true; +} + + +// +// Paint event translation +// +// When receiving many expose events, we compress them (union of all expose +// rectangles) into one event which is sent to the widget. + +struct PaintEventInfo { + Window window; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static Bool isPaintOrScrollDoneEvent(Display *, XEvent *ev, XPointer a) +{ + PaintEventInfo *info = (PaintEventInfo *)a; + if (ev->type == Expose || ev->type == GraphicsExpose + || (ev->type == ClientMessage && ev->xclient.message_type == ATOM(_QT_SCROLL_DONE))) + { + if (ev->xexpose.window == info->window) + return True; + } + return False; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + + + +static +bool translateBySips(QWidget* that, QRect& paintRect) +{ + int dx=0, dy=0; + int sips=0; + for (int i = 0; i < X11->sip_list.size(); ++i) { + const QX11Data::ScrollInProgress &sip = X11->sip_list.at(i); + if (sip.scrolled_widget == that) { + if (sips) { + dx += sip.dx; + dy += sip.dy; + } + sips++; + } + } + if (sips > 1) { + paintRect.translate(dx, dy); + return true; + } + return false; +} + +void QETWidget::translatePaintEvent(const XEvent *event) +{ + if (!isWindow() && testAttribute(Qt::WA_NativeWindow)) + Q_ASSERT(internalWinId()); + + Q_D(QWidget); + QRect paintRect(event->xexpose.x, event->xexpose.y, + event->xexpose.width, event->xexpose.height); + XEvent xevent; + PaintEventInfo info; + info.window = internalWinId(); + translateBySips(this, paintRect); + paintRect = d->mapFromWS(paintRect); + + QRegion paintRegion = paintRect; + + // WARNING: this is O(number_of_events * number_of_matching_events) + while (XCheckIfEvent(X11->display,&xevent,isPaintOrScrollDoneEvent, + (XPointer)&info) && + !qt_x11EventFilter(&xevent) && + !x11Event(&xevent)) // send event through filter + { + if (xevent.type == Expose || xevent.type == GraphicsExpose) { + QRect exposure(xevent.xexpose.x, + xevent.xexpose.y, + xevent.xexpose.width, + xevent.xexpose.height); + translateBySips(this, exposure); + exposure = d->mapFromWS(exposure); + paintRegion |= exposure; + } else { + translateScrollDoneEvent(&xevent); + } + } + + if (!paintRegion.isEmpty() && !testAttribute(Qt::WA_WState_ConfigPending)) + d->syncBackingStore(paintRegion); +} + +// +// Scroll-done event translation. +// + +bool QETWidget::translateScrollDoneEvent(const XEvent *event) +{ + long id = event->xclient.data.l[0]; + + // Remove any scroll-in-progress record for the given id. + for (int i = 0; i < X11->sip_list.size(); ++i) { + const QX11Data::ScrollInProgress &sip = X11->sip_list.at(i); + if (sip.id == id) { + X11->sip_list.removeAt(i); + return true; + } + } + + return false; +} + +// +// ConfigureNotify (window move and resize) event translation + +bool QETWidget::translateConfigEvent(const XEvent *event) +{ + Q_ASSERT((!isWindow() && !testAttribute(Qt::WA_NativeWindow)) ? internalWinId() : true); + + Q_D(QWidget); + bool wasResize = testAttribute(Qt::WA_WState_ConfigPending); // set in QWidget::setGeometry_sys() + setAttribute(Qt::WA_WState_ConfigPending, false); + + if (testAttribute(Qt::WA_OutsideWSRange)) { + // discard events for windows that have a geometry X can't handle + XEvent xevent; + while (XCheckTypedWindowEvent(X11->display,internalWinId(), ConfigureNotify,&xevent) && + !qt_x11EventFilter(&xevent) && + !x11Event(&xevent)) // send event through filter + ; + return true; + } + + const QSize oldSize = size(); + + if (isWindow()) { + QPoint newCPos(geometry().topLeft()); + QSize newSize(event->xconfigure.width, event->xconfigure.height); + + bool trust = isVisible() + && (d->topData()->parentWinId == XNone || + d->topData()->parentWinId == QX11Info::appRootWindow()); + bool isCPos = false; + + if (event->xconfigure.send_event || trust) { + // if a ConfigureNotify comes from a real sendevent request, we can + // trust its values. + newCPos.rx() = event->xconfigure.x + event->xconfigure.border_width; + newCPos.ry() = event->xconfigure.y + event->xconfigure.border_width; + isCPos = true; + } + if (isVisible()) + QApplication::syncX(); + + if (d->extra->compress_events) { + // ConfigureNotify compression for faster opaque resizing + XEvent otherEvent; + while (XCheckTypedWindowEvent(X11->display, internalWinId(), ConfigureNotify, + &otherEvent)) { + if (qt_x11EventFilter(&otherEvent)) + continue; + + if (x11Event(&otherEvent)) + continue; + + if (otherEvent.xconfigure.event != otherEvent.xconfigure.window) + continue; + + newSize.setWidth(otherEvent.xconfigure.width); + newSize.setHeight(otherEvent.xconfigure.height); + + if (otherEvent.xconfigure.send_event || trust) { + newCPos.rx() = otherEvent.xconfigure.x + + otherEvent.xconfigure.border_width; + newCPos.ry() = otherEvent.xconfigure.y + + otherEvent.xconfigure.border_width; + isCPos = true; + } + } +#ifndef QT_NO_XSYNC + qt_sync_request_event_data sync_event; + sync_event.window = internalWinId(); + for (XEvent ev;;) { + if (!XCheckIfEvent(X11->display, &ev, &qt_sync_request_scanner, (XPointer)&sync_event)) + break; + } +#endif // QT_NO_XSYNC + } + + if (!isCPos) { + // we didn't get an updated position of the toplevel. + // either we haven't moved or there is a bug in the window manager. + // anyway, let's query the position to be certain. + int x, y; + Window child; + XTranslateCoordinates(X11->display, internalWinId(), + QApplication::desktop()->screen(d->xinfo.screen())->internalWinId(), + 0, 0, &x, &y, &child); + newCPos.rx() = x; + newCPos.ry() = y; + } + + QRect cr (geometry()); + if (newCPos != cr.topLeft()) { // compare with cpos (exluding frame) + QPoint oldPos = geometry().topLeft(); + cr.moveTopLeft(newCPos); + data->crect = cr; + if (isVisible()) { + QMoveEvent e(newCPos, oldPos); // pos (including frame), not cpos + QApplication::sendSpontaneousEvent(this, &e); + } else { + setAttribute(Qt::WA_PendingMoveEvent, true); + } + } + if (newSize != cr.size()) { // size changed + cr.setSize(newSize); + data->crect = cr; + + uint old_state = data->window_state; + if (!X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_VERT)) + && !X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ))) + data->window_state &= ~Qt::WindowMaximized; + if (!X11->isSupportedByWM(ATOM(_NET_WM_STATE_FULLSCREEN))) + data->window_state &= ~Qt::WindowFullScreen; + + if (old_state != data->window_state) { + QWindowStateChangeEvent e((Qt::WindowStates) old_state); + QApplication::sendEvent(this, &e); + } + + if (!isVisible()) + setAttribute(Qt::WA_PendingResizeEvent, true); + wasResize = true; + } + + } else { + XEvent xevent; + while (XCheckTypedWindowEvent(X11->display,internalWinId(), ConfigureNotify,&xevent) && + !qt_x11EventFilter(&xevent) && + !x11Event(&xevent)) // send event through filter + ; + } + + if (wasResize) { + if (isVisible() && data->crect.size() != oldSize) { + Q_ASSERT(d->extra->topextra); + QWidgetBackingStore *bs = d->extra->topextra->backingStore.data(); + const bool hasStaticContents = bs && bs->hasStaticContents(); + // If we have a backing store with static contents, we have to disable the top-level + // resize optimization in order to get invalidated regions for resized widgets. + // The optimization discards all invalidateBuffer() calls since we're going to + // repaint everything anyways, but that's not the case with static contents. + if (!hasStaticContents) + d->extra->topextra->inTopLevelResize = true; + QResizeEvent e(data->crect.size(), oldSize); + QApplication::sendSpontaneousEvent(this, &e); + } + + const bool waitingForMapNotify = d->extra->topextra && d->extra->topextra->waitingForMapNotify; + if (!waitingForMapNotify) { + if (d->paintOnScreen()) { + QRegion updateRegion(rect()); + if (testAttribute(Qt::WA_StaticContents)) + updateRegion -= QRect(0, 0, oldSize.width(), oldSize.height()); + d->syncBackingStore(updateRegion); + } else { + d->syncBackingStore(); + } + } + + if (d->extra && d->extra->topextra) + d->extra->topextra->inTopLevelResize = false; + } +#ifndef QT_NO_XSYNC + if (QTLWExtra *tlwExtra = d->maybeTopData()) { + if (tlwExtra->newCounterValueLo != 0 || tlwExtra->newCounterValueHi != 0) { + XSyncValue value; + XSyncIntsToValue(&value, + tlwExtra->newCounterValueLo, + tlwExtra->newCounterValueHi); + + XSyncSetCounter(X11->display, tlwExtra->syncUpdateCounter, value); + tlwExtra->newCounterValueHi = 0; + tlwExtra->newCounterValueLo = 0; + } + } +#endif + return true; +} + +// +// Close window event translation. +// +bool QETWidget::translateCloseEvent(const XEvent *) +{ + Q_D(QWidget); + return d->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); +} + + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setDoubleClickInterval(int ms) +{ + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + return QApplicationPrivate::mouse_double_click_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int n) +{ + QApplicationPrivate::wheel_scroll_lines = n; +} + +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} +#endif + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + switch (effect) { + case Qt::UI_AnimateMenu: + if (enable) QApplicationPrivate::fade_menu = false; + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeMenu: + if (enable) + QApplicationPrivate::animate_menu = true; + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateTooltip: + if (enable) QApplicationPrivate::fade_tooltip = false; + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_FadeTooltip: + if (enable) + QApplicationPrivate::animate_tooltip = true; + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16 || !QApplicationPrivate::animate_ui) + return false; + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + return QApplicationPrivate::animate_ui; + } +} + +/***************************************************************************** + Session management support + *****************************************************************************/ + +#ifndef QT_NO_SESSIONMANAGER + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +class QSessionManagerPrivate : public QObjectPrivate +{ +public: + QSessionManagerPrivate(QSessionManager* mgr, QString& id, QString& key) + : QObjectPrivate(), sm(mgr), sessionId(id), sessionKey(key), + restartHint(QSessionManager::RestartIfRunning), eventLoop(0) {} + QSessionManager* sm; + QStringList restartCommand; + QStringList discardCommand; + QString& sessionId; + QString& sessionKey; + QSessionManager::RestartHint restartHint; + QEventLoop *eventLoop; +}; + +class QSmSocketReceiver : public QObject +{ + Q_OBJECT +public: + QSmSocketReceiver(int socket) + { + QSocketNotifier* sn = new QSocketNotifier(socket, QSocketNotifier::Read, this); + connect(sn, SIGNAL(activated(int)), this, SLOT(socketActivated(int))); + } + +public slots: + void socketActivated(int); +}; + + +static SmcConn smcConnection = 0; +static bool sm_interactionActive; +static bool sm_smActive; +static int sm_interactStyle; +static int sm_saveType; +static bool sm_cancel; +// static bool sm_waitingForPhase2; ### never used?!? +static bool sm_waitingForInteraction; +static bool sm_isshutdown; +// static bool sm_shouldbefast; ### never used?!? +static bool sm_phase2; +static bool sm_in_phase2; + +static QSmSocketReceiver* sm_receiver = 0; + +static void resetSmState(); +static void sm_setProperty(const char* name, const char* type, + int num_vals, SmPropValue* vals); +static void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData, + int saveType, Bool shutdown , int interactStyle, Bool fast); +static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData) ; +static void sm_dieCallback(SmcConn smcConn, SmPointer clientData) ; +static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData); +static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer clientData); +static void sm_interactCallback(SmcConn smcConn, SmPointer clientData); +static void sm_performSaveYourself(QSessionManagerPrivate*); + +static void resetSmState() +{ +// sm_waitingForPhase2 = false; ### never used?!? + sm_waitingForInteraction = false; + sm_interactionActive = false; + sm_interactStyle = SmInteractStyleNone; + sm_smActive = false; + qt_sm_blockUserInput = false; + sm_isshutdown = false; +// sm_shouldbefast = false; ### never used?!? + sm_phase2 = false; + sm_in_phase2 = false; +} + + +// theoretically it's possible to set several properties at once. For +// simplicity, however, we do just one property at a time +static void sm_setProperty(const char* name, const char* type, + int num_vals, SmPropValue* vals) +{ + if (num_vals) { + SmProp prop; + prop.name = (char*)name; + prop.type = (char*)type; + prop.num_vals = num_vals; + prop.vals = vals; + + SmProp* props[1]; + props[0] = ∝ + SmcSetProperties(smcConnection, 1, props); + } + else { + char* names[1]; + names[0] = (char*) name; + SmcDeleteProperties(smcConnection, 1, names); + } +} + +static void sm_setProperty(const QString& name, const QString& value) +{ + QByteArray v = value.toUtf8(); + SmPropValue prop; + prop.length = v.length(); + prop.value = (SmPointer) 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()]; + int count = 0; + QList vl; + for (QStringList::ConstIterator it = value.begin(); it != value.end(); ++it) { + prop[count].length = (*it).length(); + vl.append((*it).toUtf8()); + prop[count].value = (char*)vl.last().data(); + ++count; + } + sm_setProperty(name.toLatin1().data(), SmLISTofARRAY8, count, prop); + delete [] prop; +} + + +// workaround for broken libsm, see below +struct QT_smcConn { + unsigned int save_yourself_in_progress : 1; + unsigned int shutdown_in_progress : 1; +}; + +static void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData, + int saveType, Bool shutdown , int interactStyle, Bool /*fast*/) +{ + if (smcConn != smcConnection) + return; + sm_cancel = false; + sm_smActive = true; + sm_isshutdown = shutdown; + sm_saveType = saveType; + sm_interactStyle = interactStyle; +// sm_shouldbefast = fast; ### never used?!? + + // ugly workaround for broken libSM. libSM should do that _before_ + // actually invoking the callback in sm_process.c + ((QT_smcConn*)smcConn)->save_yourself_in_progress = true; + if (sm_isshutdown) + ((QT_smcConn*)smcConn)->shutdown_in_progress = true; + + sm_performSaveYourself((QSessionManagerPrivate*) clientData); + if (!sm_isshutdown) // we cannot expect a confirmation message in that case + resetSmState(); +} + +static void sm_performSaveYourself(QSessionManagerPrivate* smd) +{ + if (sm_isshutdown) + qt_sm_blockUserInput = true; + + QSessionManager* sm = smd->sm; + + // generate a new session key + timeval tv; + gettimeofday(&tv, 0); + smd->sessionKey = QString::number(qulonglong(tv.tv_sec)) + QLatin1Char('_') + QString::number(qulonglong(tv.tv_usec)); + + QStringList arguments = qApp->arguments(); + QString argument0 = arguments.isEmpty() ? qApp->applicationFilePath() : arguments.at(0); + + // tell the session manager about our program in best POSIX style + sm_setProperty(QString::fromLatin1(SmProgram), argument0); + // tell the session manager about our user as well. + struct passwd *entryPtr = 0; +#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0) + QVarLengthArray buf(qMax(sysconf(_SC_GETPW_R_SIZE_MAX), 1024L)); + struct passwd entry; + while (getpwuid_r(geteuid(), &entry, buf.data(), buf.size(), &entryPtr) == ERANGE) { + if (buf.size() >= 32768) { + // too big already, fail + static char badusername[] = ""; + entryPtr = &entry; + entry.pw_name = badusername; + break; + } + + // retry with a bigger buffer + buf.resize(buf.size() * 2); + } +#else + entryPtr = getpwuid(geteuid()); +#endif + if (entryPtr) + sm_setProperty(QString::fromLatin1(SmUserID), QString::fromLatin1(entryPtr->pw_name)); + + // generate a restart and discard command that makes sense + QStringList restart; + restart << argument0 << QLatin1String("-session") + << smd->sessionId + QLatin1Char('_') + smd->sessionKey; + if (qstricmp(appName, QX11Info::appClass()) != 0) + restart << QLatin1String("-name") << qAppName(); + sm->setRestartCommand(restart); + QStringList discard; + sm->setDiscardCommand(discard); + + switch (sm_saveType) { + case SmSaveBoth: + qApp->commitData(*sm); + if (sm_isshutdown && sm_cancel) + break; // we cancelled the shutdown, no need to save state + // fall through + case SmSaveLocal: + qApp->saveState(*sm); + break; + case SmSaveGlobal: + qApp->commitData(*sm); + break; + default: + break; + } + + if (sm_phase2 && !sm_in_phase2) { + SmcRequestSaveYourselfPhase2(smcConnection, sm_saveYourselfPhase2Callback, (SmPointer*) smd); + qt_sm_blockUserInput = false; + } + else { + // close eventual interaction monitors and cancel the + // shutdown, if required. Note that we can only cancel when + // performing a shutdown, it does not work for checkpoints + if (sm_interactionActive) { + SmcInteractDone(smcConnection, sm_isshutdown && sm_cancel); + sm_interactionActive = false; + } + else if (sm_cancel && sm_isshutdown) { + if (sm->allowsErrorInteraction()) { + SmcInteractDone(smcConnection, True); + sm_interactionActive = false; + } + } + + // set restart and discard command in session manager + sm_setProperty(QString::fromLatin1(SmRestartCommand), sm->restartCommand()); + sm_setProperty(QString::fromLatin1(SmDiscardCommand), sm->discardCommand()); + + // set the restart hint + SmPropValue prop; + prop.length = sizeof(int); + int value = sm->restartHint(); + prop.value = (SmPointer) &value; + sm_setProperty(SmRestartStyleHint, SmCARD8, 1, &prop); + + // we are done + SmcSaveYourselfDone(smcConnection, !sm_cancel); + } +} + +static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */) +{ + if (smcConn != smcConnection) + return; + resetSmState(); + QEvent quitEvent(QEvent::Quit); + QApplication::sendEvent(qApp, &quitEvent); +} + +static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData) +{ + if (smcConn != smcConnection) + return; + if (sm_waitingForInteraction) + ((QSessionManagerPrivate *) clientData)->eventLoop->exit(); + resetSmState(); +} + +static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer /*clientData */) +{ + if (smcConn != smcConnection) + return; + resetSmState(); +} + +static void sm_interactCallback(SmcConn smcConn, SmPointer clientData) +{ + if (smcConn != smcConnection) + return; + if (sm_waitingForInteraction) + ((QSessionManagerPrivate *) clientData)->eventLoop->exit(); +} + +static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData) +{ + if (smcConn != smcConnection) + return; + sm_in_phase2 = true; + sm_performSaveYourself((QSessionManagerPrivate*) clientData); +} + + +void QSmSocketReceiver::socketActivated(int) +{ + IceProcessMessages(SmcGetIceConnection(smcConnection), 0, 0); +} + + +#undef Bool +QT_BEGIN_INCLUDE_NAMESPACE +#include "qapplication_x11.moc" +QT_END_INCLUDE_NAMESPACE + +QSessionManager::QSessionManager(QApplication * app, QString &id, QString& key) + : QObject(*new QSessionManagerPrivate(this, id, key), app) +{ + Q_D(QSessionManager); + d->restartHint = RestartIfRunning; + + resetSmState(); + char cerror[256]; + char* myId = 0; + QByteArray b_id = id.toLatin1(); + char* prevId = b_id.data(); + + SmcCallbacks cb; + cb.save_yourself.callback = sm_saveYourselfCallback; + cb.save_yourself.client_data = (SmPointer) d; + cb.die.callback = sm_dieCallback; + cb.die.client_data = (SmPointer) d; + cb.save_complete.callback = sm_saveCompleteCallback; + cb.save_complete.client_data = (SmPointer) d; + cb.shutdown_cancelled.callback = sm_shutdownCancelledCallback; + cb.shutdown_cancelled.client_data = (SmPointer) d; + + // avoid showing a warning message below + if (qgetenv("SESSION_MANAGER").isEmpty()) + return; + + smcConnection = SmcOpenConnection(0, 0, 1, 0, + SmcSaveYourselfProcMask | + SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &cb, + prevId, + &myId, + 256, cerror); + + id = QString::fromLatin1(myId); + ::free(myId); // it was allocated by C + + QString error = QString::fromLocal8Bit(cerror); + if (!smcConnection) { + qWarning("Qt: Session management error: %s", qPrintable(error)); + } + else { + sm_receiver = new QSmSocketReceiver(IceConnectionNumber(SmcGetIceConnection(smcConnection))); + } +} + +QSessionManager::~QSessionManager() +{ + if (smcConnection) + SmcCloseConnection(smcConnection, 0, 0); + smcConnection = 0; + delete sm_receiver; +} + +QString QSessionManager::sessionId() const +{ + Q_D(const QSessionManager); + return d->sessionId; +} + +QString QSessionManager::sessionKey() const +{ + Q_D(const QSessionManager); + return d->sessionKey; +} + + +void* QSessionManager::handle() const +{ + return (void*) smcConnection; +} + + +bool QSessionManager::allowsInteraction() +{ + Q_D(QSessionManager); + if (sm_interactionActive) + return true; + + if (sm_waitingForInteraction) + return false; + + if (sm_interactStyle == SmInteractStyleAny) { + sm_waitingForInteraction = SmcInteractRequest(smcConnection, SmDialogNormal, + sm_interactCallback, (SmPointer*) d); + } + if (sm_waitingForInteraction) { + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + (void) eventLoop.exec(); + d->eventLoop = 0; + + sm_waitingForInteraction = false; + if (sm_smActive) { // not cancelled + sm_interactionActive = true; + qt_sm_blockUserInput = false; + return true; + } + } + return false; +} + +bool QSessionManager::allowsErrorInteraction() +{ + Q_D(QSessionManager); + if (sm_interactionActive) + return true; + + if (sm_waitingForInteraction) + return false; + + if (sm_interactStyle == SmInteractStyleAny || sm_interactStyle == SmInteractStyleErrors) { + sm_waitingForInteraction = SmcInteractRequest(smcConnection, SmDialogError, + sm_interactCallback, (SmPointer*) d); + } + if (sm_waitingForInteraction) { + QEventLoop eventLoop; + d->eventLoop = &eventLoop; + (void) eventLoop.exec(); + d->eventLoop = 0; + + sm_waitingForInteraction = false; + if (sm_smActive) { // not cancelled + sm_interactionActive = true; + qt_sm_blockUserInput = false; + return true; + } + } + return false; +} + +void QSessionManager::release() +{ + if (sm_interactionActive) { + SmcInteractDone(smcConnection, False); + sm_interactionActive = false; + if (sm_smActive && sm_isshutdown) + qt_sm_blockUserInput = true; + } +} + +void QSessionManager::cancel() +{ + sm_cancel = true; +} + +void QSessionManager::setRestartHint(QSessionManager::RestartHint hint) +{ + Q_D(QSessionManager); + d->restartHint = hint; +} + +QSessionManager::RestartHint QSessionManager::restartHint() const +{ + Q_D(const QSessionManager); + return d->restartHint; +} + +void QSessionManager::setRestartCommand(const QStringList& command) +{ + Q_D(QSessionManager); + d->restartCommand = command; +} + +QStringList QSessionManager::restartCommand() const +{ + Q_D(const QSessionManager); + return d->restartCommand; +} + +void QSessionManager::setDiscardCommand(const QStringList& command) +{ + Q_D(QSessionManager); + d->discardCommand = command; +} + +QStringList QSessionManager::discardCommand() const +{ + Q_D(const QSessionManager); + return d->discardCommand; +} + +void QSessionManager::setManagerProperty(const QString& name, const QString& value) +{ + sm_setProperty(name, value); +} + +void QSessionManager::setManagerProperty(const QString& name, const QStringList& value) +{ + sm_setProperty(name, value); +} + +bool QSessionManager::isPhase2() const +{ + return sm_in_phase2; +} + +void QSessionManager::requestPhase2() +{ + sm_phase2 = true; +} + +#endif // QT_NO_SESSIONMANAGER + +#if defined(QT_RX71_MULTITOUCH) + +static inline int testBit(const char *array, int bit) +{ + return (array[bit/8] & (1<<(bit%8))); +} + +static int openRX71Device(const QByteArray &deviceName) +{ + int fd = open(deviceName, O_RDONLY | O_NONBLOCK); + if (fd == -1) { + fd = -errno; + return fd; + } + + // fetch the event type mask and check that the device reports absolute coordinates + char eventTypeMask[(EV_MAX + sizeof(char) - 1) * sizeof(char) + 1]; + memset(eventTypeMask, 0, sizeof(eventTypeMask)); + if (ioctl(fd, EVIOCGBIT(0, sizeof(eventTypeMask)), eventTypeMask) < 0) { + close(fd); + return -1; + } + if (!testBit(eventTypeMask, EV_ABS)) { + close(fd); + return -1; + } + + // make sure that we can get the absolute X and Y positions from the device + char absMask[(ABS_MAX + sizeof(char) - 1) * sizeof(char) + 1]; + memset(absMask, 0, sizeof(absMask)); + if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absMask)), absMask) < 0) { + close(fd); + return -1; + } + if (!testBit(absMask, ABS_X) || !testBit(absMask, ABS_Y)) { + close(fd); + return -1; + } + + return fd; +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ + Q_Q(QApplication); + + QByteArray deviceName = QByteArray("/dev/input/event"); + int currentDeviceNumber = 0; + for (;;) { + int fd = openRX71Device(QByteArray(deviceName + QByteArray::number(currentDeviceNumber++))); + if (fd == -ENOENT) { + // no more devices + break; + } + if (fd < 0) { + // not a touch device + continue; + } + + struct input_absinfo abs_x, abs_y, abs_z; + ioctl(fd, EVIOCGABS(ABS_X), &abs_x); + ioctl(fd, EVIOCGABS(ABS_Y), &abs_y); + ioctl(fd, EVIOCGABS(ABS_Z), &abs_z); + + int deviceNumber = allRX71TouchPoints.count(); + + QSocketNotifier *socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, q); + QObject::connect(socketNotifier, SIGNAL(activated(int)), q, SLOT(_q_readRX71MultiTouchEvents())); + + RX71TouchPointState touchPointState = { + socketNotifier, + QTouchEvent::TouchPoint(deviceNumber), + + abs_x.minimum, abs_x.maximum, q->desktop()->screenGeometry().width(), + abs_y.minimum, abs_y.maximum, q->desktop()->screenGeometry().height(), + abs_z.minimum, abs_z.maximum + }; + allRX71TouchPoints.append(touchPointState); + } + + hasRX71MultiTouch = allRX71TouchPoints.count() > 1; + if (!hasRX71MultiTouch) { + for (int i = 0; i < allRX71TouchPoints.count(); ++i) { + QSocketNotifier *socketNotifier = allRX71TouchPoints.at(i).socketNotifier; + close(socketNotifier->socket()); + delete socketNotifier; + } + allRX71TouchPoints.clear(); + } +} + +void QApplicationPrivate::cleanupMultitouch_sys() +{ + hasRX71MultiTouch = false; + for (int i = 0; i < allRX71TouchPoints.count(); ++i) { + QSocketNotifier *socketNotifier = allRX71TouchPoints.at(i).socketNotifier; + close(socketNotifier->socket()); + delete socketNotifier; + } + allRX71TouchPoints.clear(); +} + +bool QApplicationPrivate::readRX71MultiTouchEvents(int deviceNumber) +{ + RX71TouchPointState &touchPointState = allRX71TouchPoints[deviceNumber]; + QSocketNotifier *socketNotifier = touchPointState.socketNotifier; + int fd = socketNotifier->socket(); + + QTouchEvent::TouchPoint &touchPoint = touchPointState.touchPoint; + + bool down = touchPoint.state() != Qt::TouchPointReleased; + if (down) + touchPoint.setState(Qt::TouchPointStationary); + + bool changed = false; + for (;;) { + struct input_event inputEvent; + int bytesRead = read(fd, &inputEvent, sizeof(inputEvent)); + if (bytesRead <= 0) + break; + if (bytesRead != sizeof(inputEvent)) { + qWarning("Qt: INTERNAL ERROR: short read in readRX71MultiTouchEvents()"); + return false; + } + + switch (inputEvent.type) { + case EV_SYN: + changed = true; + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + case Qt::TouchPointReleased: + // make sure we don't compress pressed and releases with any other events + return changed; + default: + break; + } + continue; + case EV_KEY: + case EV_ABS: + break; + default: + qWarning("Qt: WARNING: unknown event type %d on multitouch device", inputEvent.type); + continue; + } + + QPointF screenPos = touchPoint.screenPos(); + switch (inputEvent.code) { + case BTN_TOUCH: + if (!down && inputEvent.value != 0) + touchPoint.setState(Qt::TouchPointPressed); + else if (down && inputEvent.value == 0) + touchPoint.setState(Qt::TouchPointReleased); + break; + case ABS_TOOL_WIDTH: + case ABS_VOLUME: + case ABS_PRESSURE: + // ignore for now + break; + case ABS_X: + { + qreal newValue = ((qreal(inputEvent.value - touchPointState.minX) + / qreal(touchPointState.maxX - touchPointState.minX)) + * touchPointState.scaleX); + screenPos.rx() = newValue; + touchPoint.setScreenPos(screenPos); + break; + } + case ABS_Y: + { + qreal newValue = ((qreal(inputEvent.value - touchPointState.minY) + / qreal(touchPointState.maxY - touchPointState.minY)) + * touchPointState.scaleY); + screenPos.ry() = newValue; + touchPoint.setScreenPos(screenPos); + break; + } + case ABS_Z: + { + // map Z (signal strength) to pressure for now + qreal newValue = (qreal(inputEvent.value - touchPointState.minZ) + / qreal(touchPointState.maxZ - touchPointState.minZ)); + touchPoint.setPressure(newValue); + break; + } + default: + qWarning("Qt: WARNING: unknown event code %d on multitouch device", inputEvent.code); + continue; + } + } + + if (down && touchPoint.state() != Qt::TouchPointReleased) + touchPoint.setState(changed ? Qt::TouchPointMoved : Qt::TouchPointStationary); + + return changed; +} + +void QApplicationPrivate::_q_readRX71MultiTouchEvents() +{ + // read touch events from all devices + bool changed = false; + for (int i = 0; i < allRX71TouchPoints.count(); ++i) + changed = readRX71MultiTouchEvents(i) || changed; + if (!changed) + return; + + QList touchPoints; + for (int i = 0; i < allRX71TouchPoints.count(); ++i) + touchPoints.append(allRX71TouchPoints.at(i).touchPoint); + + translateRawTouchEvent(0, QTouchEvent::TouchScreen, touchPoints); +} + +#else // !QT_RX71_MULTITOUCH + +void QApplicationPrivate::initializeMultitouch_sys() +{ } +void QApplicationPrivate::cleanupMultitouch_sys() +{ } + +#endif // QT_RX71_MULTITOUCH + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qboxlayout.cpp b/src/gui/kernel/qboxlayout.cpp new file mode 100644 index 0000000000..6946f9bd27 --- /dev/null +++ b/src/gui/kernel/qboxlayout.cpp @@ -0,0 +1,1550 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qboxlayout.h" +#include "qapplication.h" +#include "qwidget.h" +#include "qlist.h" +#include "qsizepolicy.h" +#include "qvector.h" + +#include "qlayoutengine_p.h" +#include "qlayout_p.h" + +QT_BEGIN_NAMESPACE + +/* + Returns true if the \a widget can be added to the \a layout; + otherwise returns false. +*/ +static bool checkWidget(QLayout *layout, QWidget *widget) +{ + if (!widget) { + qWarning("QLayout: Cannot add null widget to %s/%s", layout->metaObject()->className(), + layout->objectName().toLocal8Bit().data()); + return false; + } + return true; +} + +struct QBoxLayoutItem +{ + QBoxLayoutItem(QLayoutItem *it, int stretch_ = 0) + : item(it), stretch(stretch_), magic(false) { } + ~QBoxLayoutItem() { delete item; } + + int hfw(int w) { + if (item->hasHeightForWidth()) { + return item->heightForWidth(w); + } else { + return item->sizeHint().height(); + } + } + int mhfw(int w) { + if (item->hasHeightForWidth()) { + return item->heightForWidth(w); + } else { + return item->minimumSize().height(); + } + } + int hStretch() { + if (stretch == 0 && item->widget()) { + return item->widget()->sizePolicy().horizontalStretch(); + } else { + return stretch; + } + } + int vStretch() { + if (stretch == 0 && item->widget()) { + return item->widget()->sizePolicy().verticalStretch(); + } else { + return stretch; + } + } + + QLayoutItem *item; + int stretch; + bool magic; +}; + +class QBoxLayoutPrivate : public QLayoutPrivate +{ + Q_DECLARE_PUBLIC(QBoxLayout) +public: + QBoxLayoutPrivate() : hfwWidth(-1), dirty(true), spacing(-1) { } + ~QBoxLayoutPrivate(); + + void setDirty() { + geomArray.clear(); + hfwWidth = -1; + hfwHeight = -1; + dirty = true; + } + + QList list; + QVector geomArray; + int hfwWidth; + int hfwHeight; + int hfwMinHeight; + QSize sizeHint; + QSize minSize; + QSize maxSize; + int leftMargin, topMargin, rightMargin, bottomMargin; + Qt::Orientations expanding; + uint hasHfw : 1; + uint dirty : 1; + QBoxLayout::Direction dir; + int spacing; + + inline void deleteAll() { while (!list.isEmpty()) delete list.takeFirst(); } + + void setupGeom(); + void calcHfw(int); + + void effectiveMargins(int *left, int *top, int *right, int *bottom) const; +}; + +QBoxLayoutPrivate::~QBoxLayoutPrivate() +{ +} + +static inline bool horz(QBoxLayout::Direction dir) +{ + return dir == QBoxLayout::RightToLeft || dir == QBoxLayout::LeftToRight; +} + +/** + * The purpose of this function is to make sure that widgets are not laid out outside its layout. + * E.g. the layoutItemRect margins are only meant to take of the surrounding margins/spacings. + * However, if the margin is 0, it can easily cover the area of a widget above it. + */ +void QBoxLayoutPrivate::effectiveMargins(int *left, int *top, int *right, int *bottom) const +{ + int l = leftMargin; + int t = topMargin; + int r = rightMargin; + int b = bottomMargin; +#ifdef Q_WS_MAC + Q_Q(const QBoxLayout); + if (horz(dir)) { + QBoxLayoutItem *leftBox = 0; + QBoxLayoutItem *rightBox = 0; + + if (left || right) { + leftBox = list.value(0); + rightBox = list.value(list.count() - 1); + if (dir == QBoxLayout::RightToLeft) + qSwap(leftBox, rightBox); + + int leftDelta = 0; + int rightDelta = 0; + if (leftBox) { + QLayoutItem *itm = leftBox->item; + if (QWidget *w = itm->widget()) + leftDelta = itm->geometry().left() - w->geometry().left(); + } + if (rightBox) { + QLayoutItem *itm = rightBox->item; + if (QWidget *w = itm->widget()) + rightDelta = w->geometry().right() - itm->geometry().right(); + } + QWidget *w = q->parentWidget(); + Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : QApplication::layoutDirection(); + if (layoutDirection == Qt::RightToLeft) + qSwap(leftDelta, rightDelta); + + l = qMax(l, leftDelta); + r = qMax(r, rightDelta); + } + + int count = top || bottom ? list.count() : 0; + for (int i = 0; i < count; ++i) { + QBoxLayoutItem *box = list.at(i); + QLayoutItem *itm = box->item; + QWidget *w = itm->widget(); + if (w) { + QRect lir = itm->geometry(); + QRect wr = w->geometry(); + if (top) + t = qMax(t, lir.top() - wr.top()); + if (bottom) + b = qMax(b, wr.bottom() - lir.bottom()); + } + } + } else { // vertical layout + QBoxLayoutItem *topBox = 0; + QBoxLayoutItem *bottomBox = 0; + + if (top || bottom) { + topBox = list.value(0); + bottomBox = list.value(list.count() - 1); + if (dir == QBoxLayout::BottomToTop) { + qSwap(topBox, bottomBox); + } + + if (top && topBox) { + QLayoutItem *itm = topBox->item; + QWidget *w = itm->widget(); + if (w) + t = qMax(t, itm->geometry().top() - w->geometry().top()); + } + + if (bottom && bottomBox) { + QLayoutItem *itm = bottomBox->item; + QWidget *w = itm->widget(); + if (w) + b = qMax(b, w->geometry().bottom() - itm->geometry().bottom()); + } + } + + int count = left || right ? list.count() : 0; + for (int i = 0; i < count; ++i) { + QBoxLayoutItem *box = list.at(i); + QLayoutItem *itm = box->item; + QWidget *w = itm->widget(); + if (w) { + QRect lir = itm->geometry(); + QRect wr = w->geometry(); + if (left) + l = qMax(l, lir.left() - wr.left()); + if (right) + r = qMax(r, wr.right() - lir.right()); + } + } + } +#endif + if (left) + *left = l; + if (top) + *top = t; + if (right) + *right = r; + if (bottom) + *bottom = b; +} + + +/* + Initializes the data structure needed by qGeomCalc and + recalculates max/min and size hint. +*/ +void QBoxLayoutPrivate::setupGeom() +{ + if (!dirty) + return; + + Q_Q(QBoxLayout); + int maxw = horz(dir) ? 0 : QLAYOUTSIZE_MAX; + int maxh = horz(dir) ? QLAYOUTSIZE_MAX : 0; + int minw = 0; + int minh = 0; + int hintw = 0; + int hinth = 0; + + bool horexp = false; + bool verexp = false; + + hasHfw = false; + + int n = list.count(); + geomArray.clear(); + QVector a(n); + + QSizePolicy::ControlTypes controlTypes1; + QSizePolicy::ControlTypes controlTypes2; + int fixedSpacing = q->spacing(); + int previousNonEmptyIndex = -1; + + QStyle *style = 0; + if (fixedSpacing < 0) { + if (QWidget *parentWidget = q->parentWidget()) + style = parentWidget->style(); + } + + for (int i = 0; i < n; i++) { + QBoxLayoutItem *box = list.at(i); + QSize max = box->item->maximumSize(); + QSize min = box->item->minimumSize(); + QSize hint = box->item->sizeHint(); + Qt::Orientations exp = box->item->expandingDirections(); + bool empty = box->item->isEmpty(); + int spacing = 0; + + if (!empty) { + if (fixedSpacing >= 0) { + spacing = (previousNonEmptyIndex >= 0) ? fixedSpacing : 0; +#ifdef Q_WS_MAC + if (!horz(dir) && previousNonEmptyIndex >= 0) { + QBoxLayoutItem *sibling = (dir == QBoxLayout::TopToBottom ? box : list.at(previousNonEmptyIndex)); + if (sibling) { + QWidget *wid = sibling->item->widget(); + if (wid) + spacing = qMax(spacing, sibling->item->geometry().top() - wid->geometry().top()); + } + } +#endif + } else { + controlTypes1 = controlTypes2; + controlTypes2 = box->item->controlTypes(); + if (previousNonEmptyIndex >= 0) { + QSizePolicy::ControlTypes actual1 = controlTypes1; + QSizePolicy::ControlTypes actual2 = controlTypes2; + if (dir == QBoxLayout::RightToLeft || dir == QBoxLayout::BottomToTop) + qSwap(actual1, actual2); + + if (style) { + spacing = style->combinedLayoutSpacing(actual1, actual2, + horz(dir) ? Qt::Horizontal : Qt::Vertical, + 0, q->parentWidget()); + if (spacing < 0) + spacing = 0; + } + } + } + + if (previousNonEmptyIndex >= 0) + a[previousNonEmptyIndex].spacing = spacing; + previousNonEmptyIndex = i; + } + + bool ignore = empty && box->item->widget(); // ignore hidden widgets + bool dummy = true; + if (horz(dir)) { + bool expand = (exp & Qt::Horizontal || box->stretch > 0); + horexp = horexp || expand; + maxw += spacing + max.width(); + minw += spacing + min.width(); + hintw += spacing + hint.width(); + if (!ignore) + qMaxExpCalc(maxh, verexp, dummy, + max.height(), exp & Qt::Vertical, box->item->isEmpty()); + minh = qMax(minh, min.height()); + hinth = qMax(hinth, hint.height()); + + a[i].sizeHint = hint.width(); + a[i].maximumSize = max.width(); + a[i].minimumSize = min.width(); + a[i].expansive = expand; + a[i].stretch = box->stretch ? box->stretch : box->hStretch(); + } else { + bool expand = (exp & Qt::Vertical || box->stretch > 0); + verexp = verexp || expand; + maxh += spacing + max.height(); + minh += spacing + min.height(); + hinth += spacing + hint.height(); + if (!ignore) + qMaxExpCalc(maxw, horexp, dummy, + max.width(), exp & Qt::Horizontal, box->item->isEmpty()); + minw = qMax(minw, min.width()); + hintw = qMax(hintw, hint.width()); + + a[i].sizeHint = hint.height(); + a[i].maximumSize = max.height(); + a[i].minimumSize = min.height(); + a[i].expansive = expand; + a[i].stretch = box->stretch ? box->stretch : box->vStretch(); + } + + a[i].empty = empty; + a[i].spacing = 0; // might be be initialized with a non-zero value in a later iteration + hasHfw = hasHfw || box->item->hasHeightForWidth(); + } + + geomArray = a; + + expanding = (Qt::Orientations) + ((horexp ? Qt::Horizontal : 0) + | (verexp ? Qt::Vertical : 0)); + + minSize = QSize(minw, minh); + maxSize = QSize(maxw, maxh).expandedTo(minSize); + sizeHint = QSize(hintw, hinth).expandedTo(minSize).boundedTo(maxSize); + + q->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); + int left, top, right, bottom; + effectiveMargins(&left, &top, &right, &bottom); + QSize extra(left + right, top + bottom); + + minSize += extra; + maxSize += extra; + sizeHint += extra; + + dirty = false; +} + +/* + Calculates and stores the preferred height given the width \a w. +*/ +void QBoxLayoutPrivate::calcHfw(int w) +{ + QVector &a = geomArray; + int n = a.count(); + int h = 0; + int mh = 0; + + Q_ASSERT(n == list.size()); + + if (horz(dir)) { + qGeomCalc(a, 0, n, 0, w); + for (int i = 0; i < n; i++) { + QBoxLayoutItem *box = list.at(i); + h = qMax(h, box->hfw(a.at(i).size)); + mh = qMax(mh, box->mhfw(a.at(i).size)); + } + } else { + for (int i = 0; i < n; ++i) { + QBoxLayoutItem *box = list.at(i); + int spacing = a.at(i).spacing; + h += box->hfw(w); + mh += box->mhfw(w); + h += spacing; + mh += spacing; + } + } + hfwWidth = w; + hfwHeight = h; + hfwMinHeight = mh; +} + + +/*! + \class QBoxLayout + + \brief The QBoxLayout class lines up child widgets horizontally or + vertically. + + \ingroup geomanagement + + QBoxLayout takes the space it gets (from its parent layout or from + the parentWidget()), divides it up into a row of boxes, and makes + each managed widget fill one box. + + \image qhboxlayout-with-5-children.png Horizontal box layout with five child widgets + + If the QBoxLayout's orientation is Qt::Horizontal the boxes are + placed in a row, with suitable sizes. Each widget (or other box) + will get at least its minimum size and at most its maximum size. + Any excess space is shared according to the stretch factors (more + about that below). + + \image qvboxlayout-with-5-children.png Vertical box layout with five child widgets + + If the QBoxLayout's orientation is Qt::Vertical, the boxes are + placed in a column, again with suitable sizes. + + The easiest way to create a QBoxLayout is to use one of the + convenience classes, e.g. QHBoxLayout (for Qt::Horizontal boxes) + or QVBoxLayout (for Qt::Vertical boxes). You can also use the + QBoxLayout constructor directly, specifying its direction as + LeftToRight, RightToLeft, TopToBottom, or BottomToTop. + + If the QBoxLayout is not the top-level layout (i.e. it is not + managing all of the widget's area and children), you must add it + to its parent layout before you can do anything with it. The + normal way to add a layout is by calling + parentLayout-\>addLayout(). + + Once you have done this, you can add boxes to the QBoxLayout using + one of four functions: + + \list + \o addWidget() to add a widget to the QBoxLayout and set the + widget's stretch factor. (The stretch factor is along the row of + boxes.) + + \o addSpacing() to create an empty box; this is one of the + functions you use to create nice and spacious dialogs. See below + for ways to set margins. + + \o addStretch() to create an empty, stretchable box. + + \o addLayout() to add a box containing another QLayout to the row + and set that layout's stretch factor. + \endlist + + Use insertWidget(), insertSpacing(), insertStretch() or + insertLayout() to insert a box at a specified position in the + layout. + + QBoxLayout also includes two margin widths: + + \list + \o setContentsMargins() sets the width of the outer border on + each side of the widget. This is the width of the reserved space + along each of the QBoxLayout's four sides. + \o setSpacing() sets the width between neighboring boxes. (You + can use addSpacing() to get more space at a particular spot.) + \endlist + + The margin default is provided by the style. The default margin + most Qt styles specify is 9 for child widgets and 11 for windows. + The spacing defaults to the same as the margin width for a + top-level layout, or to the same as the parent layout. + + To remove a widget from a layout, call removeWidget(). Calling + QWidget::hide() on a widget also effectively removes the widget + from the layout until QWidget::show() is called. + + You will almost always want to use QVBoxLayout and QHBoxLayout + rather than QBoxLayout because of their convenient constructors. + + \sa QGridLayout, QStackedLayout, {Layout Management} +*/ + +/*! + \enum QBoxLayout::Direction + + This type is used to determine the direction of a box layout. + + \value LeftToRight Horizontal from left to right. + \value RightToLeft Horizontal from right to left. + \value TopToBottom Vertical from top to bottom. + \value BottomToTop Vertical from bottom to top. + + \omitvalue Down + \omitvalue Up +*/ + +/*! + Constructs a new QBoxLayout with direction \a dir and parent widget \a + parent. + + \sa direction() +*/ +QBoxLayout::QBoxLayout(Direction dir, QWidget *parent) + : QLayout(*new QBoxLayoutPrivate, 0, parent) +{ + Q_D(QBoxLayout); + d->dir = dir; +} + +#ifdef QT3_SUPPORT +/*! + Constructs a new QBoxLayout with direction \a dir and main widget \a + parent. \a parent may not be 0. + + The \a margin is the number of pixels between the edge of the + widget and its managed children. The \a spacing is the default + number of pixels between neighboring children. If \a spacing is -1 + the value of \a margin is used for \a spacing. + + \a name is the internal object name. + + \sa direction() +*/ +QBoxLayout::QBoxLayout(QWidget *parent, Direction dir, + int margin, int spacing, const char *name) + : QLayout(*new QBoxLayoutPrivate, 0, parent) +{ + Q_D(QBoxLayout); + d->dir = dir; + setMargin(margin); + setObjectName(QString::fromAscii(name)); + setSpacing(spacing<0 ? margin : spacing); +} + +/*! + Constructs a new QBoxLayout called \a name, with direction \a dir, + and inserts it into \a parentLayout. + + The \a spacing is the default number of pixels between neighboring + children. If \a spacing is -1, the layout will inherit its + parent's spacing(). +*/ +QBoxLayout::QBoxLayout(QLayout *parentLayout, Direction dir, int spacing, + const char *name) + : QLayout(*new QBoxLayoutPrivate, parentLayout, 0) +{ + Q_D(QBoxLayout); + d->dir = dir; + setObjectName(QString::fromAscii(name)); + setSpacing(spacing); +} + +/*! + Constructs a new QBoxLayout called \a name, with direction \a dir. + + If \a spacing is -1, the layout will inherit its parent's + spacing(); otherwise \a spacing is used. + + You must insert this box into another layout. +*/ +QBoxLayout::QBoxLayout(Direction dir, int spacing, const char *name) + : QLayout(*new QBoxLayoutPrivate,0, 0) +{ + Q_D(QBoxLayout); + d->dir = dir; + setObjectName(QString::fromAscii(name)); + setSpacing(spacing); +} +#endif // QT3_SUPPORT + + +/*! + Destroys this box layout. + + The layout's widgets aren't destroyed. +*/ +QBoxLayout::~QBoxLayout() +{ + Q_D(QBoxLayout); + d->deleteAll(); // must do it before QObject deletes children, so can't be in ~QBoxLayoutPrivate +} + +/*! + Reimplements QLayout::spacing(). If the spacing property is + valid, that value is returned. Otherwise, a value for the spacing + property is computed and returned. Since layout spacing in a widget + is style dependent, if the parent is a widget, it queries the style + for the (horizontal or vertical) spacing of the layout. Otherwise, + the parent is a layout, and it queries the parent layout for the + spacing(). + + \sa QLayout::spacing(), setSpacing() + */ +int QBoxLayout::spacing() const +{ + Q_D(const QBoxLayout); + if (d->spacing >=0) { + return d->spacing; + } else { + return qSmartSpacing(this, d->dir == LeftToRight || d->dir == RightToLeft + ? QStyle::PM_LayoutHorizontalSpacing + : QStyle::PM_LayoutVerticalSpacing); + } +} + +/*! + Reimplements QLayout::setSpacing(). Sets the spacing + property to \a spacing. + + \sa QLayout::setSpacing(), spacing() + */ +void QBoxLayout::setSpacing(int spacing) +{ + Q_D(QBoxLayout); + d->spacing = spacing; + invalidate(); +} + +/*! + \reimp +*/ +QSize QBoxLayout::sizeHint() const +{ + Q_D(const QBoxLayout); + if (d->dirty) + const_cast(this)->d_func()->setupGeom(); + return d->sizeHint; +} + +/*! + \reimp +*/ +QSize QBoxLayout::minimumSize() const +{ + Q_D(const QBoxLayout); + if (d->dirty) + const_cast(this)->d_func()->setupGeom(); + return d->minSize; +} + +/*! + \reimp +*/ +QSize QBoxLayout::maximumSize() const +{ + Q_D(const QBoxLayout); + if (d->dirty) + const_cast(this)->d_func()->setupGeom(); + + QSize s = d->maxSize.boundedTo(QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX)); + + if (alignment() & Qt::AlignHorizontal_Mask) + s.setWidth(QLAYOUTSIZE_MAX); + if (alignment() & Qt::AlignVertical_Mask) + s.setHeight(QLAYOUTSIZE_MAX); + return s; +} + +/*! + \reimp +*/ +bool QBoxLayout::hasHeightForWidth() const +{ + Q_D(const QBoxLayout); + if (d->dirty) + const_cast(this)->d_func()->setupGeom(); + return d->hasHfw; +} + +/*! + \reimp +*/ +int QBoxLayout::heightForWidth(int w) const +{ + Q_D(const QBoxLayout); + if (!hasHeightForWidth()) + return -1; + + int left, top, right, bottom; + d->effectiveMargins(&left, &top, &right, &bottom); + + w -= left + right; + if (w != d->hfwWidth) + const_cast(this)->d_func()->calcHfw(w); + + return d->hfwHeight + top + bottom; +} + +/*! + \reimp +*/ +int QBoxLayout::minimumHeightForWidth(int w) const +{ + Q_D(const QBoxLayout); + (void) heightForWidth(w); + int top, bottom; + d->effectiveMargins(0, &top, 0, &bottom); + return d->hasHfw ? (d->hfwMinHeight + top + bottom) : -1; +} + +/*! + Resets cached information. +*/ +void QBoxLayout::invalidate() +{ + Q_D(QBoxLayout); + d->setDirty(); + QLayout::invalidate(); +} + +/*! + \reimp +*/ +int QBoxLayout::count() const +{ + Q_D(const QBoxLayout); + return d->list.count(); +} + +/*! + \reimp +*/ +QLayoutItem *QBoxLayout::itemAt(int index) const +{ + Q_D(const QBoxLayout); + return index >= 0 && index < d->list.count() ? d->list.at(index)->item : 0; +} + +/*! + \reimp +*/ +QLayoutItem *QBoxLayout::takeAt(int index) +{ + Q_D(QBoxLayout); + if (index < 0 || index >= d->list.count()) + return 0; + QBoxLayoutItem *b = d->list.takeAt(index); + QLayoutItem *item = b->item; + b->item = 0; + delete b; + + invalidate(); + return item; +} + + +/*! + \reimp +*/ +Qt::Orientations QBoxLayout::expandingDirections() const +{ + Q_D(const QBoxLayout); + if (d->dirty) + const_cast(this)->d_func()->setupGeom(); + return d->expanding; +} + +/*! + \reimp +*/ +void QBoxLayout::setGeometry(const QRect &r) +{ + Q_D(QBoxLayout); + if (d->dirty || r != geometry()) { + QRect oldRect = geometry(); + QLayout::setGeometry(r); + if (d->dirty) + d->setupGeom(); + QRect cr = alignment() ? alignmentRect(r) : r; + + int left, top, right, bottom; + d->effectiveMargins(&left, &top, &right, &bottom); + QRect s(cr.x() + left, cr.y() + top, + cr.width() - (left + right), + cr.height() - (top + bottom)); + + QVector a = d->geomArray; + int pos = horz(d->dir) ? s.x() : s.y(); + int space = horz(d->dir) ? s.width() : s.height(); + int n = a.count(); + if (d->hasHfw && !horz(d->dir)) { + for (int i = 0; i < n; i++) { + QBoxLayoutItem *box = d->list.at(i); + if (box->item->hasHeightForWidth()) { + int width = qBound(box->item->minimumSize().width(), s.width(), box->item->maximumSize().width()); + a[i].sizeHint = a[i].minimumSize = + box->item->heightForWidth(width); + } + } + } + + Direction visualDir = d->dir; + QWidget *parent = parentWidget(); + if (parent && parent->isRightToLeft()) { + if (d->dir == LeftToRight) + visualDir = RightToLeft; + else if (d->dir == RightToLeft) + visualDir = LeftToRight; + } + + qGeomCalc(a, 0, n, pos, space); + + bool reverse = (horz(visualDir) + ? ((r.right() > oldRect.right()) != (visualDir == RightToLeft)) + : r.bottom() > oldRect.bottom()); + for (int j = 0; j < n; j++) { + int i = reverse ? n-j-1 : j; + QBoxLayoutItem *box = d->list.at(i); + + switch (visualDir) { + case LeftToRight: + box->item->setGeometry(QRect(a.at(i).pos, s.y(), a.at(i).size, s.height())); + break; + case RightToLeft: + box->item->setGeometry(QRect(s.left() + s.right() - a.at(i).pos - a.at(i).size + 1, + s.y(), a.at(i).size, s.height())); + break; + case TopToBottom: + box->item->setGeometry(QRect(s.x(), a.at(i).pos, s.width(), a.at(i).size)); + break; + case BottomToTop: + box->item->setGeometry(QRect(s.x(), + s.top() + s.bottom() - a.at(i).pos - a.at(i).size + 1, + s.width(), a.at(i).size)); + } + } + } +} + +/*! + \reimp +*/ +void QBoxLayout::addItem(QLayoutItem *item) +{ + Q_D(QBoxLayout); + QBoxLayoutItem *it = new QBoxLayoutItem(item); + d->list.append(it); + invalidate(); +} + +/*! + Inserts \a item into this box layout at position \a index. If \a + index is negative, the item is added at the end. + + \sa addItem(), insertWidget(), insertLayout(), insertStretch(), + insertSpacing() +*/ +void QBoxLayout::insertItem(int index, QLayoutItem *item) +{ + Q_D(QBoxLayout); + if (index < 0) // append + index = d->list.count(); + + QBoxLayoutItem *it = new QBoxLayoutItem(item); + d->list.insert(index, it); + invalidate(); +} + +/*! + Inserts a non-stretchable space (a QSpacerItem) at position \a index, with + size \a size. If \a index is negative the space is added at the end. + + The box layout has default margin and spacing. This function adds + additional space. + + \sa addSpacing(), insertItem(), QSpacerItem +*/ +void QBoxLayout::insertSpacing(int index, int size) +{ + Q_D(QBoxLayout); + if (index < 0) // append + index = d->list.count(); + + QLayoutItem *b; + if (horz(d->dir)) + b = QLayoutPrivate::createSpacerItem(this, size, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); + else + b = QLayoutPrivate::createSpacerItem(this, 0, size, QSizePolicy::Minimum, QSizePolicy::Fixed); + + QT_TRY { + QBoxLayoutItem *it = new QBoxLayoutItem(b); + it->magic = true; + d->list.insert(index, it); + + } QT_CATCH(...) { + delete b; + QT_RETHROW; + } + invalidate(); +} + +/*! + Inserts a stretchable space (a QSpacerItem) at position \a + index, with zero minimum size and stretch factor \a stretch. If \a + index is negative the space is added at the end. + + \sa addStretch(), insertItem(), QSpacerItem +*/ +void QBoxLayout::insertStretch(int index, int stretch) +{ + Q_D(QBoxLayout); + if (index < 0) // append + index = d->list.count(); + + QLayoutItem *b; + if (horz(d->dir)) + b = QLayoutPrivate::createSpacerItem(this, 0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); + else + b = QLayoutPrivate::createSpacerItem(this, 0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + + QBoxLayoutItem *it = new QBoxLayoutItem(b, stretch); + it->magic = true; + d->list.insert(index, it); + invalidate(); +} + +/*! + \since 4.4 + + Inserts \a spacerItem at position \a index, with zero minimum + size and stretch factor. If \a index is negative the + space is added at the end. + + \sa addSpacerItem(), insertStretch(), insertSpacing() +*/ +void QBoxLayout::insertSpacerItem(int index, QSpacerItem *spacerItem) +{ + Q_D(QBoxLayout); + if (index < 0) // append + index = d->list.count(); + + QBoxLayoutItem *it = new QBoxLayoutItem(spacerItem); + it->magic = true; + d->list.insert(index, it); + invalidate(); +} + +/*! + Inserts \a layout at position \a index, with stretch factor \a + stretch. If \a index is negative, the layout is added at the end. + + \a layout becomes a child of the box layout. + + \sa addLayout(), insertItem() +*/ +void QBoxLayout::insertLayout(int index, QLayout *layout, int stretch) +{ + Q_D(QBoxLayout); + addChildLayout(layout); + if (index < 0) // append + index = d->list.count(); + QBoxLayoutItem *it = new QBoxLayoutItem(layout, stretch); + d->list.insert(index, it); + invalidate(); +} + +/*! + Inserts \a widget at position \a index, with stretch factor \a + stretch and alignment \a alignment. If \a index is negative, the + widget is added at the end. + + The stretch factor applies only in the \l{direction()}{direction} + of the QBoxLayout, and is relative to the other boxes and widgets + in this QBoxLayout. Widgets and boxes with higher stretch factors + grow more. + + If the stretch factor is 0 and nothing else in the QBoxLayout has + a stretch factor greater than zero, the space is distributed + according to the QWidget:sizePolicy() of each widget that's + involved. + + The alignment is specified by \a alignment. The default alignment + is 0, which means that the widget fills the entire cell. + + \sa addWidget(), insertItem() +*/ +void QBoxLayout::insertWidget(int index, QWidget *widget, int stretch, + Qt::Alignment alignment) +{ + Q_D(QBoxLayout); + if (!checkWidget(this, widget)) + return; + addChildWidget(widget); + if (index < 0) // append + index = d->list.count(); + QWidgetItem *b = QLayoutPrivate::createWidgetItem(this, widget); + b->setAlignment(alignment); + + QBoxLayoutItem *it; + QT_TRY{ + it = new QBoxLayoutItem(b, stretch); + } QT_CATCH(...) { + delete b; + QT_RETHROW; + } + + QT_TRY{ + d->list.insert(index, it); + } QT_CATCH(...) { + delete it; + QT_RETHROW; + } + invalidate(); +} + +/*! + Adds a non-stretchable space (a QSpacerItem) with size \a size + to the end of this box layout. QBoxLayout provides default margin + and spacing. This function adds additional space. + + \sa insertSpacing(), addItem(), QSpacerItem +*/ +void QBoxLayout::addSpacing(int size) +{ + insertSpacing(-1, size); +} + +/*! + Adds a stretchable space (a QSpacerItem) with zero minimum + size and stretch factor \a stretch to the end of this box layout. + + \sa insertStretch(), addItem(), QSpacerItem +*/ +void QBoxLayout::addStretch(int stretch) +{ + insertStretch(-1, stretch); +} + +/*! + \since 4.4 + + Adds \a spacerItem to the end of this box layout. + + \sa addSpacing(), addStretch() +*/ +void QBoxLayout::addSpacerItem(QSpacerItem *spacerItem) +{ + insertSpacerItem(-1, spacerItem); +} + +/*! + Adds \a widget to the end of this box layout, with a stretch + factor of \a stretch and alignment \a alignment. + + The stretch factor applies only in the \l{direction()}{direction} + of the QBoxLayout, and is relative to the other boxes and widgets + in this QBoxLayout. Widgets and boxes with higher stretch factors + grow more. + + If the stretch factor is 0 and nothing else in the QBoxLayout has + a stretch factor greater than zero, the space is distributed + according to the QWidget:sizePolicy() of each widget that's + involved. + + The alignment is specified by \a alignment. The default + alignment is 0, which means that the widget fills the entire cell. + + \sa insertWidget(), addItem(), addLayout(), addStretch(), + addSpacing(), addStrut() +*/ +void QBoxLayout::addWidget(QWidget *widget, int stretch, Qt::Alignment alignment) +{ + insertWidget(-1, widget, stretch, alignment); +} + +/*! + Adds \a layout to the end of the box, with serial stretch factor + \a stretch. + + \sa insertLayout(), addItem(), addWidget() +*/ +void QBoxLayout::addLayout(QLayout *layout, int stretch) +{ + insertLayout(-1, layout, stretch); +} + +/*! + Limits the perpendicular dimension of the box (e.g. height if the + box is \l LeftToRight) to a minimum of \a size. Other constraints + may increase the limit. + + \sa addItem() +*/ +void QBoxLayout::addStrut(int size) +{ + Q_D(QBoxLayout); + QLayoutItem *b; + if (horz(d->dir)) + b = QLayoutPrivate::createSpacerItem(this, 0, size, QSizePolicy::Fixed, QSizePolicy::Minimum); + else + b = QLayoutPrivate::createSpacerItem(this, size, 0, QSizePolicy::Minimum, QSizePolicy::Fixed); + + QBoxLayoutItem *it = new QBoxLayoutItem(b); + it->magic = true; + d->list.append(it); + invalidate(); +} + +/*! + \fn int QBoxLayout::findWidget(QWidget *widget) + + Use indexOf(\a widget) instead. +*/ + +/*! + Sets the stretch factor for \a widget to \a stretch and returns + true if \a widget is found in this layout (not including child + layouts); otherwise returns false. + + \sa setAlignment() +*/ +bool QBoxLayout::setStretchFactor(QWidget *widget, int stretch) +{ + Q_D(QBoxLayout); + if (!widget) + return false; + for (int i = 0; i < d->list.size(); ++i) { + QBoxLayoutItem *box = d->list.at(i); + if (box->item->widget() == widget) { + box->stretch = stretch; + invalidate(); + return true; + } + } + return false; +} + +/*! + \overload + + Sets the stretch factor for the layout \a layout to \a stretch and + returns true if \a layout is found in this layout (not including + child layouts); otherwise returns false. +*/ +bool QBoxLayout::setStretchFactor(QLayout *layout, int stretch) +{ + Q_D(QBoxLayout); + for (int i = 0; i < d->list.size(); ++i) { + QBoxLayoutItem *box = d->list.at(i); + if (box->item->layout() == layout) { + if (box->stretch != stretch) { + box->stretch = stretch; + invalidate(); + } + return true; + } + } + return false; +} + +/*! + Sets the stretch factor at position \a index. to \a stretch. + + \since 4.5 +*/ + +void QBoxLayout::setStretch(int index, int stretch) +{ + Q_D(QBoxLayout); + if (index >= 0 && index < d->list.size()) { + QBoxLayoutItem *box = d->list.at(index); + if (box->stretch != stretch) { + box->stretch = stretch; + invalidate(); + } + } +} + +/*! + Returns the stretch factor at position \a index. + + \since 4.5 +*/ + +int QBoxLayout::stretch(int index) const +{ + Q_D(const QBoxLayout); + if (index >= 0 && index < d->list.size()) + return d->list.at(index)->stretch; + return -1; +} + +/*! + Sets the direction of this layout to \a direction. +*/ +void QBoxLayout::setDirection(Direction direction) +{ + Q_D(QBoxLayout); + if (d->dir == direction) + return; + if (horz(d->dir) != horz(direction)) { + //swap around the spacers (the "magic" bits) + //#### a bit yucky, knows too much. + //#### probably best to add access functions to spacerItem + //#### or even a QSpacerItem::flip() + for (int i = 0; i < d->list.size(); ++i) { + QBoxLayoutItem *box = d->list.at(i); + if (box->magic) { + QSpacerItem *sp = box->item->spacerItem(); + if (sp) { + if (sp->expandingDirections() == Qt::Orientations(0) /*No Direction*/) { + //spacing or strut + QSize s = sp->sizeHint(); + sp->changeSize(s.height(), s.width(), + horz(direction) ? QSizePolicy::Fixed:QSizePolicy::Minimum, + horz(direction) ? QSizePolicy::Minimum:QSizePolicy::Fixed); + + } else { + //stretch + if (horz(direction)) + sp->changeSize(0, 0, QSizePolicy::Expanding, + QSizePolicy::Minimum); + else + sp->changeSize(0, 0, QSizePolicy::Minimum, + QSizePolicy::Expanding); + } + } + } + } + } + d->dir = direction; + invalidate(); +} + +/*! + \fn QBoxLayout::Direction QBoxLayout::direction() const + + Returns the direction of the box. addWidget() and addSpacing() + work in this direction; the stretch stretches in this direction. + + \sa QBoxLayout::Direction addWidget() addSpacing() +*/ + +QBoxLayout::Direction QBoxLayout::direction() const +{ + Q_D(const QBoxLayout); + return d->dir; +} + +/*! + \class QHBoxLayout + \brief The QHBoxLayout class lines up widgets horizontally. + + \ingroup geomanagement + + This class is used to construct horizontal box layout objects. See + QBoxLayout for details. + + The simplest use of the class is like this: + + \snippet doc/src/snippets/layouts/layouts.cpp 0 + \snippet doc/src/snippets/layouts/layouts.cpp 1 + \snippet doc/src/snippets/layouts/layouts.cpp 2 + \codeline + \snippet doc/src/snippets/layouts/layouts.cpp 3 + \snippet doc/src/snippets/layouts/layouts.cpp 4 + \snippet doc/src/snippets/layouts/layouts.cpp 5 + + First, we create the widgets we want in the layout. Then, we + create the QHBoxLayout object and add the widgets into the + layout. Finally, we call QWidget::setLayout() to install the + QHBoxLayout object onto the widget. At that point, the widgets in + the layout are reparented to have \c window as their parent. + + \image qhboxlayout-with-5-children.png Horizontal box layout with five child widgets + + \sa QVBoxLayout, QGridLayout, QStackedLayout, {Layout Management}, {Basic Layouts Example} +*/ + + +/*! + Constructs a new top-level horizontal box with + parent \a parent. +*/ +QHBoxLayout::QHBoxLayout(QWidget *parent) + : QBoxLayout(LeftToRight, parent) +{ +} + +/*! + Constructs a new horizontal box. You must add + it to another layout. +*/ +QHBoxLayout::QHBoxLayout() + : QBoxLayout(LeftToRight) +{ +} + + + +#ifdef QT3_SUPPORT +/*! + Constructs a new top-level horizontal box called \a name, with + parent \a parent. + + The \a margin is the number of pixels between the edge of the + widget and its managed children. The \a spacing is the default + number of pixels between neighboring children. If \a spacing is -1 + the value of \a margin is used for \a spacing. +*/ +QHBoxLayout::QHBoxLayout(QWidget *parent, int margin, + int spacing, const char *name) + : QBoxLayout(LeftToRight, parent) +{ + setMargin(margin); + setSpacing(spacing<0 ? margin : spacing); + setObjectName(QString::fromAscii(name)); +} + +/*! + Constructs a new horizontal box called name \a name and adds it to + \a parentLayout. + + The \a spacing is the default number of pixels between neighboring + children. If \a spacing is -1, this QHBoxLayout will inherit its + parent's spacing(). +*/ +QHBoxLayout::QHBoxLayout(QLayout *parentLayout, int spacing, + const char *name) + : QBoxLayout(LeftToRight) +{ + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); + if (parentLayout) { + setParent(parentLayout); + parentLayout->addItem(this); + } +} + +/*! + Constructs a new horizontal box called name \a name. You must add + it to another layout. + + The \a spacing is the default number of pixels between neighboring + children. If \a spacing is -1, this QHBoxLayout will inherit its + parent's spacing(). +*/ +QHBoxLayout::QHBoxLayout(int spacing, const char *name) + : QBoxLayout(LeftToRight) +{ + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); +} +#endif + + +/*! + Destroys this box layout. + + The layout's widgets aren't destroyed. +*/ +QHBoxLayout::~QHBoxLayout() +{ +} + +/*! + \class QVBoxLayout + \brief The QVBoxLayout class lines up widgets vertically. + + \ingroup geomanagement + + This class is used to construct vertical box layout objects. See + QBoxLayout for details. + + The simplest use of the class is like this: + + \snippet doc/src/snippets/layouts/layouts.cpp 6 + \snippet doc/src/snippets/layouts/layouts.cpp 7 + \snippet doc/src/snippets/layouts/layouts.cpp 8 + \codeline + \snippet doc/src/snippets/layouts/layouts.cpp 9 + \snippet doc/src/snippets/layouts/layouts.cpp 10 + \snippet doc/src/snippets/layouts/layouts.cpp 11 + + First, we create the widgets we want in the layout. Then, we + create the QVBoxLayout object and add the widgets into the + layout. Finally, we call QWidget::setLayout() to install the + QVBoxLayout object onto the widget. At that point, the widgets in + the layout are reparented to have \c window as their parent. + + \image qvboxlayout-with-5-children.png Horizontal box layout with five child widgets + + \sa QHBoxLayout, QGridLayout, QStackedLayout, {Layout Management}, {Basic Layouts Example} +*/ + +/*! + Constructs a new top-level vertical box with + parent \a parent. +*/ +QVBoxLayout::QVBoxLayout(QWidget *parent) + : QBoxLayout(TopToBottom, parent) +{ +} + +/*! + Constructs a new vertical box. You must add + it to another layout. + +*/ +QVBoxLayout::QVBoxLayout() + : QBoxLayout(TopToBottom) +{ +} + +#ifdef QT3_SUPPORT +/*! + Constructs a new top-level vertical box called \a name, with + parent \a parent. + + The \a margin is the number of pixels between the edge of the + widget and its managed children. The \a spacing is the default + number of pixels between neighboring children. If \a spacing is -1 + the value of \a margin is used for \a spacing. +*/ +QVBoxLayout::QVBoxLayout(QWidget *parent, int margin, int spacing, + const char *name) + : QBoxLayout(TopToBottom, parent) +{ + setMargin(margin); + setSpacing(spacing<0 ? margin : spacing); + setObjectName(QString::fromAscii(name)); +} + +/*! + Constructs a new vertical box called name \a name and adds it to + \a parentLayout. + + The \a spacing is the default number of pixels between neighboring + children. If \a spacing is -1, this QVBoxLayout will inherit its + parent's spacing(). +*/ +QVBoxLayout::QVBoxLayout(QLayout *parentLayout, int spacing, + const char *name) + : QBoxLayout(TopToBottom) +{ + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); + if (parentLayout) { + setParent(parentLayout); + parentLayout->addItem(this); + } +} + +/*! + Constructs a new vertical box called name \a name. You must add + it to another layout. + + The \a spacing is the default number of pixels between neighboring + children. If \a spacing is -1, this QVBoxLayout will inherit its + parent's spacing(). +*/ +QVBoxLayout::QVBoxLayout(int spacing, const char *name) + : QBoxLayout(TopToBottom) +{ + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); +} + + +#endif + +/*! + Destroys this box layout. + + The layout's widgets aren't destroyed. +*/ +QVBoxLayout::~QVBoxLayout() +{ +} + +/*! + \fn QWidget *QLayout::mainWidget() const + + Use parentWidget() instead. +*/ + +/*! + \fn void QLayout::remove(QWidget *widget) + + Use removeWidget(\a widget) instead. +*/ + +/*! + \fn void QLayout::add(QWidget *widget) + + Use addWidget(\a widget) instead. +*/ + +/*! + \fn QLayoutIterator QLayout::iterator() + + Use a QLayoutIterator() constructor instead. +*/ + +/*! + \fn int QLayout::defaultBorder() const + + Use spacing() instead. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qboxlayout.h b/src/gui/kernel/qboxlayout.h new file mode 100644 index 0000000000..66ce23a9f5 --- /dev/null +++ b/src/gui/kernel/qboxlayout.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBOXLAYOUT_H +#define QBOXLAYOUT_H + +#include +#ifdef QT_INCLUDE_COMPAT +#include +#endif + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QBoxLayoutPrivate; + +class Q_GUI_EXPORT QBoxLayout : public QLayout +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QBoxLayout) +public: + enum Direction { LeftToRight, RightToLeft, TopToBottom, BottomToTop, + Down = TopToBottom, Up = BottomToTop }; + + explicit QBoxLayout(Direction, QWidget *parent = 0); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QBoxLayout(QWidget *parent, Direction, int border = 0, int spacing = -1, + const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QBoxLayout(QLayout *parentLayout, Direction, int spacing = -1, + const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QBoxLayout(Direction, int spacing, const char *name = 0); +#endif + ~QBoxLayout(); + + Direction direction() const; + void setDirection(Direction); + + void addSpacing(int size); + void addStretch(int stretch = 0); + void addSpacerItem(QSpacerItem *spacerItem); + void addWidget(QWidget *, int stretch = 0, Qt::Alignment alignment = 0); + void addLayout(QLayout *layout, int stretch = 0); + void addStrut(int); + void addItem(QLayoutItem *); + + void insertSpacing(int index, int size); + void insertStretch(int index, int stretch = 0); + void insertSpacerItem(int index, QSpacerItem *spacerItem); + void insertWidget(int index, QWidget *widget, int stretch = 0, Qt::Alignment alignment = 0); + void insertLayout(int index, QLayout *layout, int stretch = 0); + + int spacing() const; + void setSpacing(int spacing); + + bool setStretchFactor(QWidget *w, int stretch); + bool setStretchFactor(QLayout *l, int stretch); + void setStretch(int index, int stretch); + int stretch(int index) const; + + QSize sizeHint() const; + QSize minimumSize() const; + QSize maximumSize() const; + + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int minimumHeightForWidth(int) const; + + Qt::Orientations expandingDirections() const; + void invalidate(); + QLayoutItem *itemAt(int) const; + QLayoutItem *takeAt(int); + int count() const; + void setGeometry(const QRect&); +#ifdef QT3_SUPPORT + inline QT3_SUPPORT int findWidget(QWidget* w) {return indexOf(w);} +#endif +protected: + // ### Qt 5: make public + void insertItem(int index, QLayoutItem *); + +private: + Q_DISABLE_COPY(QBoxLayout) +}; + +class Q_GUI_EXPORT QHBoxLayout : public QBoxLayout +{ + Q_OBJECT +public: + QHBoxLayout(); + explicit QHBoxLayout(QWidget *parent); + ~QHBoxLayout(); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QHBoxLayout(QWidget *parent, int border, + int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QHBoxLayout(QLayout *parentLayout, + int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QHBoxLayout(int spacing, const char *name = 0); +#endif + +private: + Q_DISABLE_COPY(QHBoxLayout) +}; + +class Q_GUI_EXPORT QVBoxLayout : public QBoxLayout +{ + Q_OBJECT +public: + QVBoxLayout(); + explicit QVBoxLayout(QWidget *parent); + ~QVBoxLayout(); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QVBoxLayout(QWidget *parent, int border, + int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QVBoxLayout(QLayout *parentLayout, + int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QVBoxLayout(int spacing, const char *name = 0); +#endif + +private: + Q_DISABLE_COPY(QVBoxLayout) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QBOXLAYOUT_H diff --git a/src/gui/kernel/qclipboard.cpp b/src/gui/kernel/qclipboard.cpp new file mode 100644 index 0000000000..ba7ad0caac --- /dev/null +++ b/src/gui/kernel/qclipboard.cpp @@ -0,0 +1,667 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qpixmap.h" +#include "qclipboard_p.h" +#include "qvariant.h" +#include "qbuffer.h" +#include "qimage.h" +#include "qtextcodec.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QClipboard + \brief The QClipboard class provides access to the window system clipboard. + + The clipboard offers a simple mechanism to copy and paste data + between applications. + + QClipboard supports the same data types that QDrag does, and uses + similar mechanisms. For advanced clipboard usage read \l{Drag and + Drop}. + + There is a single QClipboard object in an application, accessible + as QApplication::clipboard(). + + Example: + \snippet doc/src/snippets/code/src_gui_kernel_qclipboard.cpp 0 + + QClipboard features some convenience functions to access common + data types: setText() allows the exchange of Unicode text and + setPixmap() and setImage() allows the exchange of QPixmaps and + QImages between applications. The setMimeData() function is the + ultimate in flexibility: it allows you to add any QMimeData into + the clipboard. There are corresponding getters for each of these, + e.g. text(), image() and pixmap(). You can clear the clipboard by + calling clear(). + + A typical example of the use of these functions follows: + + \snippet doc/src/snippets/droparea.cpp 0 + + \section1 Notes for X11 Users + + \list + + \i The X11 Window System has the concept of a separate selection + and clipboard. When text is selected, it is immediately available + as the global mouse selection. The global mouse selection may + later be copied to the clipboard. By convention, the middle mouse + button is used to paste the global mouse selection. + + \i X11 also has the concept of ownership; if you change the + selection within a window, X11 will only notify the owner and the + previous owner of the change, i.e. it will not notify all + applications that the selection or clipboard data changed. + + \i Lastly, the X11 clipboard is event driven, i.e. the clipboard + will not function properly if the event loop is not running. + Similarly, it is recommended that the contents of the clipboard + are stored or retrieved in direct response to user-input events, + e.g. mouse button or key presses and releases. You should not + store or retrieve the clipboard contents in response to timer or + non-user-input events. + + \i Since there is no standard way to copy and paste files between + applications on X11, various MIME types and conventions are currently + in use. For instance, Nautilus expects files to be supplied with a + \c{x-special/gnome-copied-files} MIME type with data beginning with + the cut/copy action, a newline character, and the URL of the file. + + \endlist + + \section1 Notes for Mac OS X Users + + Mac OS X supports a separate find buffer that holds the current + search string in Find operations. This find clipboard can be accessed + by specifying the FindBuffer mode. + + \section1 Notes for Windows and Mac OS X Users + + \list + + \i Windows and Mac OS X do not support the global mouse + selection; they only supports the global clipboard, i.e. they + only add text to the clipboard when an explicit copy or cut is + made. + + \i Windows and Mac OS X does not have the concept of ownership; + the clipboard is a fully global resource so all applications are + notified of changes. + + \endlist + + \sa QApplication +*/ + +#ifndef Q_WS_X11 +// for X11 there is a separate implementation of a constructor. +/*! + \internal + + Constructs a clipboard object. + + Do not call this function. + + Call QApplication::clipboard() instead to get a pointer to the + application's global clipboard object. + + There is only one clipboard in the window system, and creating + more than one object to represent it is almost certainly an error. +*/ + +QClipboard::QClipboard(QObject *parent) + : QObject(*new QClipboardPrivate, parent) +{ + // nothing +} +#endif + +#ifndef Q_WS_WIN32 +/*! + \internal + + Destroys the clipboard. + + You should never delete the clipboard. QApplication will do this + when the application terminates. +*/ +QClipboard::~QClipboard() +{ +} +#endif + +/*! + \fn void QClipboard::changed(QClipboard::Mode mode) + \since 4.2 + + This signal is emitted when the data for the given clipboard \a + mode is changed. + + \sa dataChanged(), selectionChanged(), findBufferChanged() +*/ + +/*! + \fn void QClipboard::dataChanged() + + This signal is emitted when the clipboard data is changed. + + On Mac OS X and with Qt version 4.3 or higher, clipboard + changes made by other applications will only be detected + when the application is activated. + + \sa findBufferChanged(), selectionChanged(), changed() +*/ + +/*! + \fn void QClipboard::selectionChanged() + + This signal is emitted when the selection is changed. This only + applies to windowing systems that support selections, e.g. X11. + Windows and Mac OS X don't support selections. + + \sa dataChanged(), findBufferChanged(), changed() +*/ + +/*! + \fn void QClipboard::findBufferChanged() + \since 4.2 + + This signal is emitted when the find buffer is changed. This only + applies to Mac OS X. + + With Qt version 4.3 or higher, clipboard changes made by other + applications will only be detected when the application is activated. + + \sa dataChanged(), selectionChanged(), changed() +*/ + + +/*! \enum QClipboard::Mode + \keyword clipboard mode + + This enum type is used to control which part of the system clipboard is + used by QClipboard::mimeData(), QClipboard::setMimeData() and related functions. + + \value Clipboard indicates that data should be stored and retrieved from + the global clipboard. + + \value Selection indicates that data should be stored and retrieved from + the global mouse selection. Support for \c Selection is provided only on + systems with a global mouse selection (e.g. X11). + + \value FindBuffer indicates that data should be stored and retrieved from + the Find buffer. This mode is used for holding search strings on Mac OS X. + + \omitvalue LastMode + + \sa QClipboard::supportsSelection() +*/ + + +/***************************************************************************** + QApplication member functions related to QClipboard. + *****************************************************************************/ + +// text handling is done directly in qclipboard_qws, for now + +/*! + \fn bool QClipboard::event(QEvent *e) + \reimp +*/ + +/*! + \overload + + Returns the clipboard text in subtype \a subtype, or an empty string + if the clipboard does not contain any text. If \a subtype is null, + any subtype is acceptable, and \a subtype is set to the chosen + subtype. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + text is retrieved from the global clipboard. If \a mode is + QClipboard::Selection, the text is retrieved from the global + mouse selection. + + Common values for \a subtype are "plain" and "html". + + Note that calling this function repeatedly, for instance from a + key event handler, may be slow. In such cases, you should use the + \c dataChanged() signal instead. + + \sa setText(), mimeData() +*/ +QString QClipboard::text(QString &subtype, Mode mode) const +{ + const QMimeData *const data = mimeData(mode); + if (!data) + return QString(); + + const QStringList formats = data->formats(); + if (subtype.isEmpty()) { + if (formats.contains(QLatin1String("text/plain"))) + subtype = QLatin1String("plain"); + else { + for (int i = 0; i < formats.size(); ++i) + if (formats.at(i).startsWith(QLatin1String("text/"))) { + subtype = formats.at(i).mid(5); + break; + } + if (subtype.isEmpty()) + return QString(); + } + } else if (!formats.contains(QLatin1String("text/") + subtype)) { + return QString(); + } + + const QByteArray rawData = data->data(QLatin1String("text/") + subtype); + +#ifndef QT_NO_TEXTCODEC + QTextCodec* codec = QTextCodec::codecForMib(106); // utf-8 is default + if (subtype == QLatin1String("html")) + codec = QTextCodec::codecForHtml(rawData, codec); + else + codec = QTextCodec::codecForUtfText(rawData, codec); + return codec->toUnicode(rawData); +#else //QT_NO_TEXTCODEC + return rawData; +#endif //QT_NO_TEXTCODEC +} + +/*! + Returns the clipboard text as plain text, or an empty string if the + clipboard does not contain any text. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + text is retrieved from the global clipboard. If \a mode is + QClipboard::Selection, the text is retrieved from the global + mouse selection. If \a mode is QClipboard::FindBuffer, the + text is retrieved from the search string buffer. + + \sa setText(), mimeData() +*/ +QString QClipboard::text(Mode mode) const +{ + const QMimeData *data = mimeData(mode); + return data ? data->text() : QString(); +} + +/*! + Copies \a text into the clipboard as plain text. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + text is stored in the global clipboard. If \a mode is + QClipboard::Selection, the text is stored in the global + mouse selection. If \a mode is QClipboard::FindBuffer, the + text is stored in the search string buffer. + + \sa text(), setMimeData() +*/ +void QClipboard::setText(const QString &text, Mode mode) +{ + QMimeData *data = new QMimeData; + data->setText(text); + setMimeData(data, mode); +} + +/*! + Returns the clipboard image, or returns a null image if the + clipboard does not contain an image or if it contains an image in + an unsupported image format. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + image is retrieved from the global clipboard. If \a mode is + QClipboard::Selection, the image is retrieved from the global + mouse selection. + + \sa setImage() pixmap() mimeData(), QImage::isNull() +*/ +QImage QClipboard::image(Mode mode) const +{ + const QMimeData *data = mimeData(mode); + if (!data) + return QImage(); + return qvariant_cast(data->imageData()); +} + +/*! + Copies the \a image into the clipboard. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + image is stored in the global clipboard. If \a mode is + QClipboard::Selection, the data is stored in the global + mouse selection. + + This is shorthand for: + + \snippet doc/src/snippets/code/src_gui_kernel_qclipboard.cpp 1 + + \sa image(), setPixmap() setMimeData() +*/ +void QClipboard::setImage(const QImage &image, Mode mode) +{ + QMimeData *data = new QMimeData; + data->setImageData(image); + setMimeData(data, mode); +} + +/*! + Returns the clipboard pixmap, or null if the clipboard does not + contain a pixmap. Note that this can lose information. For + example, if the image is 24-bit and the display is 8-bit, the + result is converted to 8 bits, and if the image has an alpha + channel, the result just has a mask. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + pixmap is retrieved from the global clipboard. If \a mode is + QClipboard::Selection, the pixmap is retrieved from the global + mouse selection. + + \sa setPixmap() image() mimeData() QPixmap::convertFromImage() +*/ +QPixmap QClipboard::pixmap(Mode mode) const +{ + const QMimeData *data = mimeData(mode); + return data ? qvariant_cast(data->imageData()) : QPixmap(); +} + +/*! + Copies \a pixmap into the clipboard. Note that this is slower + than setImage() because it needs to convert the QPixmap to a + QImage first. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + pixmap is stored in the global clipboard. If \a mode is + QClipboard::Selection, the pixmap is stored in the global + mouse selection. + + \sa pixmap() setImage() setMimeData() +*/ +void QClipboard::setPixmap(const QPixmap &pixmap, Mode mode) +{ + QMimeData *data = new QMimeData; + data->setImageData(pixmap); + setMimeData(data, mode); +} + + +/*! + \fn QMimeData *QClipboard::mimeData(Mode mode) const + + Returns a reference to a QMimeData representation of the current + clipboard data. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + data is retrieved from the global clipboard. If \a mode is + QClipboard::Selection, the data is retrieved from the global + mouse selection. If \a mode is QClipboard::FindBuffer, the + data is retrieved from the search string buffer. + + The text(), image(), and pixmap() functions are simpler + wrappers for retrieving text, image, and pixmap data. + + \sa setMimeData() +*/ + +/*! + \fn void QClipboard::setMimeData(QMimeData *src, Mode mode) + + Sets the clipboard data to \a src. Ownership of the data is + transferred to the clipboard. If you want to remove the data + either call clear() or call setMimeData() again with new data. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, the + data is stored in the global clipboard. If \a mode is + QClipboard::Selection, the data is stored in the global + mouse selection. If \a mode is QClipboard::FindBuffer, the + data is stored in the search string buffer. + + The setText(), setImage() and setPixmap() functions are simpler + wrappers for setting text, image and pixmap data respectively. + + \sa mimeData() +*/ + +/*! + \fn void QClipboard::clear(Mode mode) + Clear the clipboard contents. + + The \a mode argument is used to control which part of the system + clipboard is used. If \a mode is QClipboard::Clipboard, this + function clears the global clipboard contents. If \a mode is + QClipboard::Selection, this function clears the global mouse + selection contents. If \a mode is QClipboard::FindBuffer, this + function clears the search string buffer. + + \sa QClipboard::Mode, supportsSelection() +*/ + +#ifdef QT3_SUPPORT +/*! + \fn QMimeSource *QClipboard::data(Mode mode) const + \compat + + Use mimeData() instead. +*/ +QMimeSource *QClipboard::data(Mode mode) const +{ + Q_D(const QClipboard); + + if (supportsMode(mode) == false) + return 0; + + if (d->compat_data[mode]) + return d->compat_data[mode]; + + d->wrapper[mode]->data = mimeData(mode); + return d->wrapper[mode]; +} + + +/*! + \fn void QClipboard::setData(QMimeSource *src, Mode mode) + \compat + + Use setMimeData() instead. +*/ +void QClipboard::setData(QMimeSource *source, Mode mode) +{ + Q_D(QClipboard); + + if (supportsMode(mode) == false) + return; + + d->compat_data[mode] = source; + setMimeData(new QMimeSourceWrapper(d, mode), mode); +} +#endif // QT3_SUPPORT + +/*! + Returns true if the clipboard supports mouse selection; otherwise + returns false. +*/ +bool QClipboard::supportsSelection() const +{ + return supportsMode(Selection); +} + +/*! + Returns true if the clipboard supports a separate search buffer; otherwise + returns false. +*/ +bool QClipboard::supportsFindBuffer() const +{ + return supportsMode(FindBuffer); +} + +/*! + Returns true if this clipboard object owns the clipboard data; + otherwise returns false. +*/ +bool QClipboard::ownsClipboard() const +{ + return ownsMode(Clipboard); +} + +/*! + Returns true if this clipboard object owns the mouse selection + data; otherwise returns false. +*/ +bool QClipboard::ownsSelection() const +{ + return ownsMode(Selection); +} + +/*! + \since 4.2 + + Returns true if this clipboard object owns the find buffer data; + otherwise returns false. +*/ +bool QClipboard::ownsFindBuffer() const +{ + return ownsMode(FindBuffer); +} + +/*! + \internal + \fn bool QClipboard::supportsMode(Mode mode) const; + Returns true if the clipboard supports the clipboard mode speacified by \a mode; + otherwise returns false. +*/ + +/*! + \internal + \fn bool QClipboard::ownsMode(Mode mode) const; + Returns true if the clipboard supports the clipboard data speacified by \a mode; + otherwise returns false. +*/ + +/*! + \internal + Emits the appropriate changed signal for \a mode. +*/ +void QClipboard::emitChanged(Mode mode) +{ + switch (mode) { + case Clipboard: + emit dataChanged(); + break; + case Selection: + emit selectionChanged(); + break; + case FindBuffer: + emit findBufferChanged(); + break; + default: + break; + } + emit changed(mode); +} + +const char* QMimeDataWrapper::format(int n) const +{ + if (formats.isEmpty()) { + QStringList fmts = data->formats(); + for (int i = 0; i < fmts.size(); ++i) + formats.append(fmts.at(i).toLatin1()); + } + if (n < 0 || n >= formats.size()) + return 0; + return formats.at(n).data(); +} + +QByteArray QMimeDataWrapper::encodedData(const char *format) const +{ + if (QLatin1String(format) != QLatin1String("application/x-qt-image")){ + return data->data(QLatin1String(format)); + } else{ + QVariant variant = data->imageData(); + QImage img = qvariant_cast(variant); + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + return ba; + } +} + +QVariant QMimeSourceWrapper::retrieveData(const QString &mimetype, QVariant::Type) const +{ + return source->encodedData(mimetype.toLatin1()); +} + +bool QMimeSourceWrapper::hasFormat(const QString &mimetype) const +{ + return source->provides(mimetype.toLatin1()); +} + +QStringList QMimeSourceWrapper::formats() const +{ + QStringList fmts; + int i = 0; + const char *fmt; + while ((fmt = source->format(i))) { + fmts.append(QLatin1String(fmt)); + ++i; + } + return fmts; +} + +#endif // QT_NO_CLIPBOARD + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qclipboard.h b/src/gui/kernel/qclipboard.h new file mode 100644 index 0000000000..b55bdc684b --- /dev/null +++ b/src/gui/kernel/qclipboard.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCLIPBOARD_H +#define QCLIPBOARD_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_CLIPBOARD + +class QMimeSource; +class QMimeData; +class QImage; +class QPixmap; + +class QClipboardPrivate; + +class Q_GUI_EXPORT QClipboard : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QClipboard) +private: + QClipboard(QObject *parent); + ~QClipboard(); + +public: + enum Mode { Clipboard, Selection, FindBuffer, LastMode = FindBuffer }; + + void clear(Mode mode = Clipboard); + + bool supportsSelection() const; + bool supportsFindBuffer() const; + + bool ownsSelection() const; + bool ownsClipboard() const; + bool ownsFindBuffer() const; + + QString text(Mode mode = Clipboard) const; + QString text(QString& subtype, Mode mode = Clipboard) const; + void setText(const QString &, Mode mode = Clipboard); + +#ifdef QT3_SUPPORT + QT3_SUPPORT QMimeSource *data(Mode mode = Clipboard) const; + QT3_SUPPORT void setData(QMimeSource*, Mode mode = Clipboard); +#endif + const QMimeData *mimeData(Mode mode = Clipboard ) const; + void setMimeData(QMimeData *data, Mode mode = Clipboard); + + QImage image(Mode mode = Clipboard) const; + QPixmap pixmap(Mode mode = Clipboard) const; + void setImage(const QImage &, Mode mode = Clipboard); + void setPixmap(const QPixmap &, Mode mode = Clipboard); + +Q_SIGNALS: + void changed(QClipboard::Mode mode); + void selectionChanged(); + void findBufferChanged(); + void dataChanged(); +private Q_SLOTS: + void ownerDestroyed(); + +protected: + void connectNotify(const char *); + bool event(QEvent *); + + friend class QApplication; + friend class QApplicationPrivate; + friend class QBaseApplication; + friend class QDragManager; + friend class QMimeSource; + +private: + Q_DISABLE_COPY(QClipboard) + + bool supportsMode(Mode mode) const; + bool ownsMode(Mode mode) const; + void emitChanged(Mode mode); +}; + +#endif // QT_NO_CLIPBOARD + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCLIPBOARD_H diff --git a/src/gui/kernel/qclipboard_mac.cpp b/src/gui/kernel/qclipboard_mac.cpp new file mode 100644 index 0000000000..4a8bc56e41 --- /dev/null +++ b/src/gui/kernel/qclipboard_mac.cpp @@ -0,0 +1,634 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" +#include "qapplication.h" +#include "qbitmap.h" +#include "qdatetime.h" +#include "qdebug.h" +#include "qapplication_p.h" +#include +#include "qevent.h" +#include "qurl.h" +#include +#include +#include "qt_cocoa_helpers_mac_p.h" + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + QClipboard debug facilities + *****************************************************************************/ +//#define DEBUG_PASTEBOARD + +#ifndef QT_NO_CLIPBOARD + +/***************************************************************************** + QClipboard member functions for mac. + *****************************************************************************/ + +static QMacPasteboard *qt_mac_pasteboards[2] = {0, 0}; + +static inline QMacPasteboard *qt_mac_pasteboard(QClipboard::Mode mode) +{ + Q_ASSERT(mode == QClipboard::Clipboard || mode == QClipboard::FindBuffer); + if (mode == QClipboard::Clipboard) + return qt_mac_pasteboards[0]; + else + return qt_mac_pasteboards[1]; +} + +static void qt_mac_cleanupPasteboard() { + delete qt_mac_pasteboards[0]; + delete qt_mac_pasteboards[1]; + qt_mac_pasteboards[0] = 0; + qt_mac_pasteboards[1] = 0; +} + +static bool qt_mac_updateScrap(QClipboard::Mode mode) +{ + if(!qt_mac_pasteboards[0]) { + qt_mac_pasteboards[0] = new QMacPasteboard(kPasteboardClipboard, QMacPasteboardMime::MIME_CLIP); + qt_mac_pasteboards[1] = new QMacPasteboard(kPasteboardFind, QMacPasteboardMime::MIME_CLIP); + qAddPostRoutine(qt_mac_cleanupPasteboard); + return true; + } + return qt_mac_pasteboard(mode)->sync(); +} + +void QClipboard::clear(Mode mode) +{ + if (!supportsMode(mode)) + return; + qt_mac_updateScrap(mode); + qt_mac_pasteboard(mode)->clear(); + setMimeData(0, mode); +} + +void QClipboard::ownerDestroyed() +{ +} + + +void QClipboard::connectNotify(const char *signal) +{ + Q_UNUSED(signal); +} + +bool QClipboard::event(QEvent *e) +{ + if(e->type() != QEvent::Clipboard) + return QObject::event(e); + + if (qt_mac_updateScrap(QClipboard::Clipboard)) { + emitChanged(QClipboard::Clipboard); + } + + if (qt_mac_updateScrap(QClipboard::FindBuffer)) { + emitChanged(QClipboard::FindBuffer); + } + + return QObject::event(e); +} + +const QMimeData *QClipboard::mimeData(Mode mode) const +{ + if (!supportsMode(mode)) + return 0; + qt_mac_updateScrap(mode); + return qt_mac_pasteboard(mode)->mimeData(); +} + +void QClipboard::setMimeData(QMimeData *src, Mode mode) +{ + if (!supportsMode(mode)) + return; + qt_mac_updateScrap(mode); + qt_mac_pasteboard(mode)->setMimeData(src); + emitChanged(mode); +} + +bool QClipboard::supportsMode(Mode mode) const +{ + return (mode == Clipboard || mode == FindBuffer); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + Q_UNUSED(mode); + return false; +} + +#endif // QT_NO_CLIPBOARD + +/***************************************************************************** + QMacPasteboard code +*****************************************************************************/ + +QMacPasteboard::QMacPasteboard(PasteboardRef p, uchar mt) +{ + mac_mime_source = false; + mime_type = mt ? mt : uchar(QMacPasteboardMime::MIME_ALL); + paste = p; + CFRetain(paste); +} + +QMacPasteboard::QMacPasteboard(uchar mt) +{ + mac_mime_source = false; + mime_type = mt ? mt : uchar(QMacPasteboardMime::MIME_ALL); + paste = 0; + OSStatus err = PasteboardCreate(0, &paste); + if(err == noErr) { + PasteboardSetPromiseKeeper(paste, promiseKeeper, this); + } else { + qDebug("PasteBoard: Error creating pasteboard: [%d]", (int)err); + } +} + +QMacPasteboard::QMacPasteboard(CFStringRef name, uchar mt) +{ + mac_mime_source = false; + mime_type = mt ? mt : uchar(QMacPasteboardMime::MIME_ALL); + paste = 0; + OSStatus err = PasteboardCreate(name, &paste); + if(err == noErr) { + PasteboardSetPromiseKeeper(paste, promiseKeeper, this); + } else { + qDebug("PasteBoard: Error creating pasteboard: %s [%d]", QCFString::toQString(name).toLatin1().constData(), (int)err); + } +} + +QMacPasteboard::~QMacPasteboard() +{ + // commit all promises for paste after exit close + for (int i = 0; i < promises.count(); ++i) { + const Promise &promise = promises.at(i); + QCFString flavor = QCFString(promise.convertor->flavorFor(promise.mime)); + promiseKeeper(paste, (PasteboardItemID)promise.itemId, flavor, this); + } + + if(paste) + CFRelease(paste); +} + +PasteboardRef +QMacPasteboard::pasteBoard() const +{ + return paste; +} + +OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, CFStringRef flavor, void *_qpaste) +{ + QMacPasteboard *qpaste = (QMacPasteboard*)_qpaste; + const long promise_id = (long)id; + + // Find the kept promise + const QString flavorAsQString = QCFString::toQString(flavor); + QMacPasteboard::Promise promise; + for (int i = 0; i < qpaste->promises.size(); i++){ + QMacPasteboard::Promise tmp = qpaste->promises[i]; + if (tmp.itemId == promise_id && tmp.convertor->canConvert(tmp.mime, flavorAsQString)){ + promise = tmp; + break; + } + } + + if (!promise.itemId && flavorAsQString == QLatin1String("com.trolltech.qt.MimeTypeName")) { + // we have promised this data, but wont 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 data = CFDataCreate(0, (UInt8*)ba.constData(), ba.size()); + PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); + return noErr; + } + + if (!promise.itemId) { + // There was no promise that could deliver data for the + // given id and flavor. This should not happend. + qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(flavorAsQString)); + return cantGetFlavorErr; + } + +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: Calling in promise for %s[%ld] [%s] (%s) [%d]", qPrintable(promise.mime), promise_id, + qPrintable(flavorAsQString), qPrintable(promise.convertor->convertorName()), promise.offset); +#endif + + QList md = promise.convertor->convertFromMime(promise.mime, promise.data, flavorAsQString); + if (md.size() <= promise.offset) + return cantGetFlavorErr; + const QByteArray &ba = md[promise.offset]; + QCFType data = CFDataCreate(0, (UInt8*)ba.constData(), ba.size()); + PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); + return noErr; +} + +bool +QMacPasteboard::hasOSType(int c_flavor) const +{ + if (!paste) + return false; + + sync(); + + ItemCount cnt = 0; + if(PasteboardGetItemCount(paste, &cnt) || !cnt) + return false; + +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: hasOSType [%c%c%c%c]", (c_flavor>>24)&0xFF, (c_flavor>>16)&0xFF, + (c_flavor>>8)&0xFF, (c_flavor>>0)&0xFF); +#endif + for(uint index = 1; index <= cnt; ++index) { + + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + return false; + + QCFType 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); + const int os_flavor = UTGetOSTypeFromString(UTTypeCopyPreferredTagWithClass(flavor, kUTTagClassOSType)); + if(os_flavor == c_flavor) { +#ifdef DEBUG_PASTEBOARD + qDebug(" - Found!"); +#endif + return true; + } + } + } +#ifdef DEBUG_PASTEBOARD + qDebug(" - NotFound!"); +#endif + return false; +} + +bool +QMacPasteboard::hasFlavor(QString c_flavor) const +{ + if (!paste) + return false; + + sync(); + + ItemCount cnt = 0; + if(PasteboardGetItemCount(paste, &cnt) || !cnt) + return false; + +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: hasFlavor [%s]", qPrintable(c_flavor)); +#endif + for(uint index = 1; index <= cnt; ++index) { + + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + return false; + + PasteboardFlavorFlags flags; + if(PasteboardGetItemFlavorFlags(paste, id, QCFString(c_flavor), &flags) == noErr) { +#ifdef DEBUG_PASTEBOARD + qDebug(" - Found!"); +#endif + return true; + } + } +#ifdef DEBUG_PASTEBOARD + qDebug(" - NotFound!"); +#endif + return false; +} + +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, QVariant::Type type) const { return paste->retrieveData(format, type); } +}; + +QMimeData +*QMacPasteboard::mimeData() const +{ + if(!mime) { + mac_mime_source = true; + mime = new QMacPasteboardMimeSource(this); + + } + return mime; +} + +class QMacMimeData : public QMimeData +{ +public: + QVariant variantData(const QString &mime) { return retrieveData(mime, QVariant::Invalid); } +private: + QMacMimeData(); +}; + +void +QMacPasteboard::setMimeData(QMimeData *mime_src) +{ + if (!paste) + return; + + if (mime == mime_src || (!mime_src && mime && mac_mime_source)) + return; + mac_mime_source = false; + delete mime; + mime = mime_src; + + QList availableConverters = QMacPasteboardMime::all(mime_type); + if (mime != 0) { + clear_helper(); + QStringList formats = mime_src->formats(); + +#ifdef QT_MAC_USE_COCOA + // QMimeData sub classes reimplementing the formats() might not expose the + // temporary "application/x-qt-mime-type-name" mimetype. So check the existence + // of this mime type while doing drag and drop. + QString dummyMimeType(QLatin1String("application/x-qt-mime-type-name")); + if (!formats.contains(dummyMimeType)) { + QByteArray dummyType = mime_src->data(dummyMimeType); + if (!dummyType.isEmpty()) { + formats.append(dummyMimeType); + } + } +#endif + for(int f = 0; f < formats.size(); ++f) { + QString mimeType = formats.at(f); + for (QList::Iterator it = availableConverters.begin(); it != availableConverters.end(); ++it) { + QMacPasteboardMime *c = (*it); + QString flavor(c->flavorFor(mimeType)); + if(!flavor.isEmpty()) { + QVariant mimeData = static_cast(mime_src)->variantData(mimeType); +#if 0 + //### Grrr, why didn't I put in a virtual int QMacPasteboardMime::count()? --Sam + const int numItems = c->convertFromMime(mimeType, mimeData, flavor).size(); +#else + int numItems = 1; //this is a hack but it is much faster than allowing conversion above + if(c->convertorName() == QLatin1String("FileURL")) + numItems = mime_src->urls().count(); +#endif + for(int item = 0; item < numItems; ++item) { + const int itemID = item+1; //id starts at 1 + promises.append(QMacPasteboard::Promise(itemID, c, mimeType, mimeData, item)); + PasteboardPutItemFlavor(paste, (PasteboardItemID)itemID, QCFString(flavor), 0, kPasteboardFlavorNoFlags); +#ifdef DEBUG_PASTEBOARD + qDebug(" - adding %d %s [%s] <%s> [%d]", + itemID, qPrintable(mimeType), qPrintable(flavor), qPrintable(c->convertorName()), item); +#endif + } + } + } + } + } +} + +QStringList +QMacPasteboard::formats() const +{ + if (!paste) + return QStringList(); + + sync(); + + QStringList ret; + ItemCount cnt = 0; + if(PasteboardGetItemCount(paste, &cnt) || !cnt) + return ret; + +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: Formats [%d]", (int)cnt); +#endif + for(uint index = 1; index <= cnt; ++index) { + + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + continue; + + QCFType types; + if(PasteboardCopyItemFlavors(paste, id, &types ) != noErr) + continue; + + const int type_count = CFArrayGetCount(types); + for(int i = 0; i < type_count; ++i) { + const QString flavor = QCFString::toQString((CFStringRef)CFArrayGetValueAtIndex(types, i)); +#ifdef DEBUG_PASTEBOARD + qDebug(" -%s", qPrintable(QString(flavor))); +#endif + QString mimeType = QMacPasteboardMime::flavorToMime(mime_type, flavor); + if(!mimeType.isEmpty() && !ret.contains(mimeType)) { +#ifdef DEBUG_PASTEBOARD + qDebug(" -<%d> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(flavor))); +#endif + ret << mimeType; + } + } + } + return ret; +} + +bool +QMacPasteboard::hasFormat(const QString &format) const +{ + if (!paste) + return false; + + sync(); + + ItemCount cnt = 0; + if(PasteboardGetItemCount(paste, &cnt) || !cnt) + return false; + +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: hasFormat [%s]", qPrintable(format)); +#endif + for(uint index = 1; index <= cnt; ++index) { + + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + continue; + + QCFType types; + if(PasteboardCopyItemFlavors(paste, id, &types ) != noErr) + continue; + + const int type_count = CFArrayGetCount(types); + for(int i = 0; i < type_count; ++i) { + const QString flavor = QCFString::toQString((CFStringRef)CFArrayGetValueAtIndex(types, i)); +#ifdef DEBUG_PASTEBOARD + qDebug(" -%s [0x%x]", qPrintable(QString(flavor)), mime_type); +#endif + QString mimeType = QMacPasteboardMime::flavorToMime(mime_type, flavor); +#ifdef DEBUG_PASTEBOARD + if(!mimeType.isEmpty()) + qDebug(" - %s", qPrintable(mimeType)); +#endif + if(mimeType == format) + return true; + } + } + return false; +} + +QVariant +QMacPasteboard::retrieveData(const QString &format, QVariant::Type) const +{ + if (!paste) + return QVariant(); + + sync(); + + ItemCount cnt = 0; + if(PasteboardGetItemCount(paste, &cnt) || !cnt) + return QByteArray(); + +#ifdef DEBUG_PASTEBOARD + qDebug("Pasteboard: retrieveData [%s]", qPrintable(format)); +#endif + const QList mimes = QMacPasteboardMime::all(mime_type); + for(int mime = 0; mime < mimes.size(); ++mime) { + QMacPasteboardMime *c = mimes.at(mime); + QString c_flavor = c->flavorFor(format); + if(!c_flavor.isEmpty()) { + // Handle text/plain a little differently. Try handling Unicode first. + bool checkForUtf16 = (c_flavor == QLatin1String("com.apple.traditional-mac-plain-text") + || c_flavor == QLatin1String("public.utf8-plain-text")); + if (checkForUtf16 || c_flavor == QLatin1String("public.utf16-plain-text")) { + // Try to get the NSStringPboardType from NSPasteboard, newlines are mapped + // correctly (as '\n') in this data. The 'public.utf16-plain-text' type + // usually maps newlines to '\r' instead. + QString str = qt_mac_get_pasteboardString(paste); + if (!str.isEmpty()) + return str; + } + if (checkForUtf16 && hasFlavor(QLatin1String("public.utf16-plain-text"))) + c_flavor = QLatin1String("public.utf16-plain-text"); + + QVariant ret; + QList retList; + for(uint index = 1; index <= cnt; ++index) { + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + continue; + + QCFType types; + if(PasteboardCopyItemFlavors(paste, id, &types ) != noErr) + continue; + + const int type_count = CFArrayGetCount(types); + for(int i = 0; i < type_count; ++i) { + CFStringRef flavor = static_cast(CFArrayGetValueAtIndex(types, i)); + if(c_flavor == QCFString::toQString(flavor)) { + QCFType macBuffer; + if(PasteboardCopyItemFlavorData(paste, id, flavor, &macBuffer) == noErr) { + QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer), CFDataGetLength(macBuffer)); + if(!buffer.isEmpty()) { +#ifdef DEBUG_PASTEBOARD + qDebug(" - %s [%s] (%s)", qPrintable(format), qPrintable(QCFString::toQString(flavor)), qPrintable(c->convertorName())); +#endif + buffer.detach(); //detach since we release the macBuffer + retList.append(buffer); + break; //skip to next element + } + } + } else { +#ifdef DEBUG_PASTEBOARD + qDebug(" - NoMatch %s [%s] (%s)", qPrintable(c_flavor), qPrintable(QCFString::toQString(flavor)), qPrintable(c->convertorName())); +#endif + } + } + } + + if (!retList.isEmpty()) { + ret = c->convertToMime(format, retList, c_flavor); + return ret; + } + } + } + return QVariant(); +} + +void QMacPasteboard::clear_helper() +{ + if (paste) + PasteboardClear(paste); + promises.clear(); +} + +void +QMacPasteboard::clear() +{ +#ifdef DEBUG_PASTEBOARD + qDebug("PasteBoard: clear!"); +#endif + clear_helper(); +} + +bool +QMacPasteboard::sync() const +{ + if (!paste) + return false; + const bool fromGlobal = PasteboardSynchronize(paste) & kPasteboardModified; + + if (fromGlobal) + const_cast(this)->setMimeData(0); + +#ifdef DEBUG_PASTEBOARD + if(fromGlobal) + qDebug("Pasteboard: Synchronize!"); +#endif + return fromGlobal; +} + + + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qclipboard_p.h b/src/gui/kernel/qclipboard_p.h new file mode 100644 index 0000000000..c82694cfb8 --- /dev/null +++ b/src/gui/kernel/qclipboard_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCLIPBOARD_P_H +#define QCLIPBOARD_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/qobject_p.h" +#include "QtGui/qmime.h" +#include "QtGui/qclipboard.h" + +QT_BEGIN_NAMESPACE + +class QClipboardPrivate; + +class QMimeDataWrapper : public QMimeSource +{ +public: + QMimeDataWrapper() {} + + const char* format(int n) const; + QByteArray encodedData(const char*) const; + + mutable QList formats; + const QMimeData *data; +}; + +class QMimeSourceWrapper : public QMimeData +{ +public: + QMimeSourceWrapper(QClipboardPrivate *priv, QClipboard::Mode m); + ~QMimeSourceWrapper(); + + bool hasFormat(const QString &mimetype) const; + QStringList formats() const; + +protected: + QVariant retrieveData(const QString &mimetype, QVariant::Type) const; +private: + QClipboardPrivate *d; + QClipboard::Mode mode; + QMimeSource *source; +}; + + +class QClipboardPrivate : public QObjectPrivate +{ +public: + QClipboardPrivate() : QObjectPrivate() { + for (int i = 0; i <= QClipboard::LastMode; ++i) { + compat_data[i] = 0; + wrapper[i] = new QMimeDataWrapper(); + } + } + ~QClipboardPrivate() { + for (int i = 0; i <= QClipboard::LastMode; ++i) { + delete wrapper[i]; + delete compat_data[i]; + } + } + + mutable QMimeDataWrapper *wrapper[QClipboard::LastMode + 1]; + mutable QMimeSource *compat_data[QClipboard::LastMode + 1]; +}; + +inline QMimeSourceWrapper::QMimeSourceWrapper(QClipboardPrivate *priv, QClipboard::Mode m) + : QMimeData() +{ + d = priv; + mode = m; + source = d->compat_data[mode]; +} + +inline QMimeSourceWrapper::~QMimeSourceWrapper() +{ + if (d->compat_data[mode] == source) + d->compat_data[mode] = 0; + delete source; +} + +QT_END_NAMESPACE + +#endif // QCLIPBOARD_P_H diff --git a/src/gui/kernel/qclipboard_qpa.cpp b/src/gui/kernel/qclipboard_qpa.cpp new file mode 100644 index 0000000000..b8ce60e00d --- /dev/null +++ b/src/gui/kernel/qclipboard_qpa.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qmimedata.h" +#include "private/qapplication_p.h" +#include "qplatformclipboard_qpa.h" + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +void QClipboard::clear(Mode mode) +{ + setMimeData(0,mode); +} + + +bool QClipboard::event(QEvent *e) +{ + return QObject::event(e); +} + +const QMimeData* QClipboard::mimeData(Mode mode) const +{ + QPlatformClipboard *clipboard = QApplicationPrivate::platformIntegration()->clipboard(); + if (!clipboard->supportsMode(mode)) return 0; + return clipboard->mimeData(mode); +} + +void QClipboard::setMimeData(QMimeData* src, Mode mode) +{ + QPlatformClipboard *clipboard = QApplicationPrivate::platformIntegration()->clipboard(); + if (!clipboard->supportsMode(mode)) return; + + clipboard->setMimeData(src,mode); + + emitChanged(mode); +} + +bool QClipboard::supportsMode(Mode mode) const +{ + QPlatformClipboard *clipboard = QApplicationPrivate::platformIntegration()->clipboard(); + return clipboard->supportsMode(mode); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + if (mode == Clipboard) + qWarning("QClipboard::ownsClipboard: UNIMPLEMENTED!"); + return false; +} + +void QClipboard::connectNotify( const char * ) +{ +} + +void QClipboard::ownerDestroyed() +{ +} + +#endif // QT_NO_CLIPBOARD + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qclipboard_qws.cpp b/src/gui/kernel/qclipboard_qws.cpp new file mode 100644 index 0000000000..d50b412215 --- /dev/null +++ b/src/gui/kernel/qclipboard_qws.cpp @@ -0,0 +1,304 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qapplication.h" +#include "qbitmap.h" +#include "qdatetime.h" +#include "qbuffer.h" +#include "qwidget.h" +#include "qevent.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + + +/***************************************************************************** + Internal QClipboard functions for Qt for Embedded Linux + *****************************************************************************/ + +static const int TextClipboard=424242; +static bool init = false; + +static inline void qwsInitClipboard() +{ + //### this should go into QWSServer; it only needs to happen once. + if( !init ) { + QPaintDevice::qwsDisplay()->addProperty(0, TextClipboard); + init = true; + } +} + +static QString qwsClipboardText() +{ + char * data; + int len; + qwsInitClipboard(); + if( !QPaintDevice::qwsDisplay()->getProperty(0, TextClipboard, data, len) ) { +// qDebug("Property received: %d bytes", len); + } + + QString s((const QChar*)data, len/sizeof(QChar)); + // qDebug("Property received: '%s'", s.toAscii().constData()); + delete[] data; + return s; +} + + +static void qwsSetClipboardText(const QString& s) +{ + qwsInitClipboard(); + // qDebug("qwsSetClipboardText( %s )", s.toAscii().data()); + int len = s.length()*sizeof(QChar); + QByteArray ba((const char*)s.unicode(), len); + QPaintDevice::qwsDisplay()-> + setProperty(0, TextClipboard, QWSPropertyManager::PropReplace, ba); + +} + +class QClipboardData +{ +public: + QClipboardData(); + ~QClipboardData(); + + void setSource(QMimeData* s) + { + if (s == src) + return; + delete src; + src = s; + } + QMimeData* source() + { return src; } +#if 0 + void addTransferredPixmap(QPixmap pm) + { /* TODO: queue them */ + transferred[tindex] = pm; + tindex=(tindex+1)%2; + } + void clearTransfers() + { + transferred[0] = QPixmap(); + transferred[1] = QPixmap(); + } +#endif + + void clear(); + +private: + QMimeData* src; + +#if 0 + QPixmap transferred[2]; + int tindex; +#endif +}; + +QClipboardData::QClipboardData() +{ + src = 0; +#if 0 + tindex=0; +#endif +} + +QClipboardData::~QClipboardData() +{ + delete src; +} + +void QClipboardData::clear() +{ + delete src; + src = 0; +} + + +static QClipboardData *internalCbData = 0; + +static void cleanupClipboardData() +{ + delete internalCbData; + internalCbData = 0; +} + +static QClipboardData *clipboardData() +{ + if (internalCbData == 0) { + internalCbData = new QClipboardData; + qAddPostRoutine(cleanupClipboardData); + } + return internalCbData; +} + + +/***************************************************************************** + QClipboard member functions for FB. + *****************************************************************************/ + +#if 0 + +QString QClipboard::text() const +{ + return qwsClipboardText(); +} + +void QClipboard::setText(const QString &text) +{ + qwsSetClipboardText(text); +} + +QString QClipboard::text(QString& subtype) const +{ + QString r; + if (subtype == "plain") + r = text(); + return r; +} + +#endif + +void QClipboard::clear(Mode mode) +{ + setText(QString(), mode); +} + + +bool QClipboard::event(QEvent *e) +{ + static bool recursionWatch = false; + if (e->type() != QEvent::Clipboard || recursionWatch) + return QObject::event(e); + + recursionWatch = true; + QWSPropertyNotifyEvent *event = (QWSPropertyNotifyEvent *)(((QClipboardEvent *)e)->data()); + if (event && event->simpleData.state == QWSPropertyNotifyEvent::PropertyNewValue) { + QClipboardData *d = clipboardData(); + QString t = qwsClipboardText(); + if( (d->source() == 0 && !t.isEmpty()) || (d->source() != 0 && d->source()->text() != t) ) { + if( !d->source() ) + d->setSource(new QMimeData); + d->source()->setText( t ); + emitChanged(QClipboard::Clipboard); + } + } + + recursionWatch = false; + return true; +} + +const QMimeData* QClipboard::mimeData(Mode mode) const +{ + if (mode != Clipboard) return 0; + + QClipboardData *d = clipboardData(); + // Try and get data from QWSProperty if no mime data has been set on us. + if( !d->source() ) { + QString t = qwsClipboardText(); + if( !t.isEmpty() ) { + QMimeData* nd = new QMimeData; + nd->setText( t ); + d->setSource( nd ); + } + } + return d->source(); +} + +void QClipboard::setMimeData(QMimeData* src, Mode mode) +{ + if (mode != Clipboard) return; + + QClipboardData *d = clipboardData(); + + /* Propagate text data to other QWSClients */ + + QString newText; + if( src != 0 ) + newText = src->text(); + QString oldText; + if( d->source() != 0 ) + oldText = d->source()->text(); + + d->setSource(src); + + if( oldText != newText ) { + if( d->source() == 0 ) { + qwsSetClipboardText( QString() ); + } else { + qwsSetClipboardText( d->source()->text() ); + } + } + + emitChanged(QClipboard::Clipboard); +} + +bool QClipboard::supportsMode(Mode mode) const +{ + return (mode == Clipboard); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + if (mode == Clipboard) + qWarning("QClipboard::ownsClipboard: UNIMPLEMENTED!"); + return false; +} + +void QClipboard::connectNotify( const char * ) +{ +} + +void QClipboard::ownerDestroyed() +{ +} + +#endif // QT_NO_CLIPBOARD + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qclipboard_s60.cpp b/src/gui/kernel/qclipboard_s60.cpp new file mode 100644 index 0000000000..0dafae0996 --- /dev/null +++ b/src/gui/kernel/qclipboard_s60.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qapplication.h" +#include "qbitmap.h" +#include "qdatetime.h" +#include "qbuffer.h" +#include "qwidget.h" +#include "qevent.h" +#include "private/qcore_symbian_p.h" +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include "txtclipboard.h" +#endif +#include "txtetext.h" +#include + +// Symbian's clipboard +#include +QT_BEGIN_NAMESPACE + +const TUid KQtCbDataStream = {0x2001B2DD}; +const TInt KPlainTextBegin = 0; + +class QClipboardData +{ +public: + QClipboardData(); + ~QClipboardData(); + + void setSource(QMimeData* s) + { + if (s == src) + return; + delete src; + src = s; + } + QMimeData* source() + { return src; } + bool connected() + { return connection; } + void clear(); + +private: + QMimeData* src; + bool connection; +}; + +QClipboardData::QClipboardData():src(0),connection(true) +{ + clear(); +} + +QClipboardData::~QClipboardData() +{ + connection = false; + delete src; +} + +void QClipboardData::clear() +{ + QMimeData* newSrc = new QMimeData; + delete src; + src = newSrc; +} + +static QClipboardData *internalCbData = 0; + +static void cleanupClipboardData() +{ + delete internalCbData; + internalCbData = 0; +} + +static QClipboardData *clipboardData() +{ + if (internalCbData == 0) { + internalCbData = new QClipboardData; + if (internalCbData) + { + if (!internalCbData->connected()) + { + delete internalCbData; + internalCbData = 0; + } + else + { + qAddPostRoutine(cleanupClipboardData); + } + } + } + return internalCbData; +} + +void writeToStreamLX(const QMimeData* aData, RWriteStream& aStream) +{ + // This function both leaves and throws exceptions. There must be no destructor + // dependencies between cleanup styles, and no cleanup stack dependencies on stacked objects. + QStringList headers = aData->formats(); + aStream << TCardinality(headers.count()); + for (QStringList::const_iterator iter= headers.constBegin();iter != headers.constEnd();iter++) + { + HBufC* stringData = TPtrC(reinterpret_cast((*iter).utf16())).AllocLC(); + QByteArray ba = aData->data((*iter)); + // mime type + aStream << TCardinality(stringData->Size()); + aStream << *(stringData); + // mime data + aStream << TCardinality(ba.size()); + aStream.WriteL(reinterpret_cast(ba.constData()),ba.size()); + CleanupStack::PopAndDestroy(stringData); + } +} + +void writeToSymbianStoreLX(const QMimeData* aData, CClipboard* clipboard) +{ + // This function both leaves and throws exceptions. There must be no destructor + // dependencies between cleanup styles, and no cleanup stack dependencies on stacked objects. + if (aData->hasText()) { + CPlainText* text = CPlainText::NewL(); + CleanupStack::PushL(text); + + TPtrC textPtr(qt_QString2TPtrC(aData->text())); + text->InsertL(KPlainTextBegin, textPtr); + text->CopyToStoreL(clipboard->Store(), clipboard->StreamDictionary(), + KPlainTextBegin, textPtr.Length()); + CleanupStack::PopAndDestroy(text); + } +} + +void readSymbianStoreLX(QMimeData* aData, CClipboard* clipboard) +{ + // This function both leaves and throws exceptions. There must be no destructor + // dependencies between cleanup styles, and no cleanup stack dependencies on stacked objects. + CPlainText* text = CPlainText::NewL(); + CleanupStack::PushL(text); + TInt dataLength = text->PasteFromStoreL(clipboard->Store(), clipboard->StreamDictionary(), + KPlainTextBegin); + if (dataLength == 0) { + User::Leave(KErrNotFound); + } + HBufC* hBuf = HBufC::NewL(dataLength); + TPtr buf = hBuf->Des(); + text->Extract(buf, KPlainTextBegin, dataLength); + + QString string = qt_TDesC2QString(buf); + CleanupStack::PopAndDestroy(text); + + aData->setText(string); +} + +void readFromStreamLX(QMimeData* aData,RReadStream& aStream) +{ + // This function both leaves and throws exceptions. There must be no destructor + // dependencies between cleanup styles, and no cleanup stack dependencies on stacked objects. + TCardinality mimeTypeCount; + aStream >> mimeTypeCount; + for (int i = 0; i< mimeTypeCount;i++) + { + // mime type + TCardinality mimeTypeSize; + aStream >> mimeTypeSize; + HBufC* mimeTypeBuf = HBufC::NewLC(aStream,mimeTypeSize); + QString mimeType = QString(reinterpret_cast(mimeTypeBuf->Des().Ptr()), + mimeTypeBuf->Length()); + CleanupStack::PopAndDestroy(mimeTypeBuf); + // mime data + TCardinality dataSize; + aStream >> dataSize; + QByteArray ba; + ba.reserve(dataSize); + aStream.ReadL(reinterpret_cast(ba.data_ptr()->data),dataSize); + ba.data_ptr()->size = dataSize; + aData->setData(mimeType,ba); + } +} + + +/***************************************************************************** + QClipboard member functions + *****************************************************************************/ + +void QClipboard::clear(Mode mode) +{ + setText(QString(), mode); +} +const QMimeData* QClipboard::mimeData(Mode mode) const +{ + if (mode != Clipboard) return 0; + QClipboardData *d = clipboardData(); + bool dataExists(false); + if (d) + { + TRAPD(err,{ + RFs fs = qt_s60GetRFs(); + CClipboard* cb = CClipboard::NewForReadingLC(fs); + Q_ASSERT(cb); + //stream for qt + RStoreReadStream stream; + TStreamId stid = (cb->StreamDictionary()).At(KQtCbDataStream); + if (stid != 0) { + stream.OpenLC(cb->Store(),stid); + QT_TRYCATCH_LEAVING(readFromStreamLX(d->source(),stream)); + CleanupStack::PopAndDestroy(&stream); + dataExists = true; + } + else { + //symbian clipboard + RStoreReadStream symbianStream; + TStreamId symbianStId = (cb->StreamDictionary()).At(KClipboardUidTypePlainText); + if (symbianStId != 0) { + symbianStream.OpenLC(cb->Store(), symbianStId); + QT_TRYCATCH_LEAVING(readSymbianStoreLX(d->source(), cb)); + CleanupStack::PopAndDestroy(&symbianStream); + dataExists = true; + } + } + CleanupStack::PopAndDestroy(cb); + }); + if (err != KErrNone){ + qDebug()<< "clipboard is empty/err: " << err; + } + + if (dataExists) { + return d->source(); + } + } + return 0; +} + + +void QClipboard::setMimeData(QMimeData* src, Mode mode) +{ + if (mode != Clipboard) return; + QClipboardData *d = clipboardData(); + if (d) + { + TRAPD(err,{ + RFs fs = qt_s60GetRFs(); + CClipboard* cb = CClipboard::NewForWritingLC(fs); + //stream for qt + RStoreWriteStream stream; + TStreamId stid = stream.CreateLC(cb->Store()); + QT_TRYCATCH_LEAVING(writeToStreamLX(src,stream)); + d->setSource(src); + stream.CommitL(); + (cb->StreamDictionary()).AssignL(KQtCbDataStream,stid); + cb->CommitL(); + + //stream for symbian + RStoreWriteStream symbianStream; + TStreamId symbianStId = symbianStream.CreateLC(cb->Store()); + QT_TRYCATCH_LEAVING(writeToSymbianStoreLX(src, cb)); + (cb->StreamDictionary()).AssignL(KClipboardUidTypePlainText, symbianStId); + cb->CommitL(); + CleanupStack::PopAndDestroy(3,cb); + }); + if (err != KErrNone){ + qDebug()<< "clipboard write err :" << err; + } + } + emitChanged(QClipboard::Clipboard); +} + +bool QClipboard::supportsMode(Mode mode) const +{ + return (mode == Clipboard); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + if (mode == Clipboard) + qWarning("QClipboard::ownsClipboard: UNIMPLEMENTED!"); + return false; +} + +bool QClipboard::event(QEvent * /* e */) +{ + return true; +} + +void QClipboard::connectNotify( const char * ) +{ +} + +void QClipboard::ownerDestroyed() +{ +} +QT_END_NAMESPACE +#endif // QT_NO_CLIPBOARD diff --git a/src/gui/kernel/qclipboard_win.cpp b/src/gui/kernel/qclipboard_win.cpp new file mode 100644 index 0000000000..ea41165b9c --- /dev/null +++ b/src/gui/kernel/qclipboard_win.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qclipboard.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qeventloop.h" +#include "qwidget.h" +#include "qevent.h" +#include "qmime.h" +#include "qt_windows.h" +#include "qdnd_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_WINCE) +QT_BEGIN_INCLUDE_NAMESPACE +#include "qguifunctions_wince.h" +QT_END_INCLUDE_NAMESPACE + +HRESULT QtCeGetClipboard(IDataObject** obj); +HRESULT QtCeSetClipboard(IDataObject* obj); +void QtCeFlushClipboard(); + +#define OleGetClipboard QtCeGetClipboard +#define OleSetClipboard QtCeSetClipboard +#define OleFlushClipboard QtCeFlushClipboard + +#endif + +typedef BOOL (WINAPI *PtrIsHungAppWindow)(HWND); + +static PtrIsHungAppWindow ptrIsHungAppWindow = 0; + +class QClipboardWatcher : public QInternalMimeData { +public: + QClipboardWatcher() + : QInternalMimeData() + { + } + + bool hasFormat_sys(const QString &mimetype) const; + QStringList formats_sys() const; + QVariant retrieveData_sys(const QString &mimetype, QVariant::Type preferredType) const; +}; + + +bool QClipboardWatcher::hasFormat_sys(const QString &mime) const +{ + IDataObject * pDataObj = 0; + + if (OleGetClipboard(&pDataObj) != S_OK && !pDataObj) // Sanity + return false; + + bool has = QWindowsMime::converterToMime(mime, pDataObj) != 0; + + pDataObj->Release(); + + return has; +} + +QStringList QClipboardWatcher::formats_sys() const +{ + QStringList fmts; + IDataObject * pDataObj = 0; + + if (OleGetClipboard(&pDataObj) != S_OK && !pDataObj) // Sanity + return QStringList(); + + fmts = QWindowsMime::allMimesForFormats(pDataObj); + + pDataObj->Release(); + + return fmts; +} + +QVariant QClipboardWatcher::retrieveData_sys(const QString &mimeType, QVariant::Type type) const +{ + QVariant result; + IDataObject * pDataObj = 0; + + if (OleGetClipboard(&pDataObj) != S_OK && !pDataObj) // Sanity + return result; + + QWindowsMime *converter = QWindowsMime::converterToMime(mimeType, pDataObj); + + if (converter) + result = converter->convertToMime(mimeType, pDataObj, type); + + pDataObj->Release(); + + return result; +} + +class QClipboardData +{ +public: + QClipboardData() + : iData(0) + , nextClipboardViewer(0) + { + clipBoardViewer = new QWidget(); + clipBoardViewer->createWinId(); + clipBoardViewer->setObjectName(QLatin1String("internal clipboard owner")); + // We don't need this internal widget to appear in QApplication::topLevelWidgets() + if (QWidgetPrivate::allWidgets) + QWidgetPrivate::allWidgets->remove(clipBoardViewer); + } + + ~QClipboardData() + { + Q_ASSERT(clipBoardViewer->testAttribute(Qt::WA_WState_Created)); + ChangeClipboardChain(clipBoardViewer->internalWinId(), nextClipboardViewer); + delete clipBoardViewer; + releaseIData(); + } + + void releaseIData() + { + if (iData) { + delete iData->mimeData(); + iData->releaseQt(); + iData->Release(); + iData = 0; + } + } + + QOleDataObject * iData; + QWidget *clipBoardViewer; + HWND nextClipboardViewer; + QClipboardWatcher watcher; +}; + +static QClipboardData *ptrClipboardData = 0; + +static QClipboardData *clipboardData() +{ + if (ptrClipboardData == 0) { + ptrClipboardData = new QClipboardData; + // this needs to be done here to avoid recursion + Q_ASSERT(ptrClipboardData->clipBoardViewer->testAttribute(Qt::WA_WState_Created)); + ptrClipboardData->nextClipboardViewer = SetClipboardViewer(ptrClipboardData->clipBoardViewer->internalWinId()); + } + return ptrClipboardData; +} + +static void cleanupClipboardData() +{ + delete ptrClipboardData; + ptrClipboardData = 0; +} + +#if defined(Q_OS_WINCE) +HRESULT QtCeGetClipboard(IDataObject** obj) +{ + HWND owner = ptrClipboardData->clipBoardViewer->internalWinId(); + if (!OpenClipboard(owner)) + return !S_OK; + + if (!IsClipboardFormatAvailable(CF_TEXT) && !IsClipboardFormatAvailable(CF_UNICODETEXT)) + return !S_OK; + + HANDLE clipData = GetClipboardData(CF_TEXT); + QString clipText; + if (clipData == 0) { + clipData = GetClipboardData(CF_UNICODETEXT); + if (clipData != 0) + clipText = QString::fromWCharArray((wchar_t *)clipData); + } else { + clipText = QString::fromLatin1((const char*)clipData); + } + + QMimeData *mimeData = new QMimeData(); + mimeData->setText(clipText); + QOleDataObject* data = new QOleDataObject(mimeData); + *obj = data; + CloseClipboard(); + return S_OK; +} + +HRESULT QtCeSetClipboard(IDataObject* obj) +{ + HWND owner = ptrClipboardData->clipBoardViewer->internalWinId(); + if (!OpenClipboard(owner)) + return !S_OK; + + bool result = false; + if (obj == 0) { + result = true; + EmptyClipboard(); + CloseClipboard(); + } else { + QOleDataObject* qobj = static_cast(obj); + + const QMimeData* data = qobj->mimeData(); + if (data->hasText()) { + EmptyClipboard(); + result = SetClipboardData(CF_UNICODETEXT, wcsdup(reinterpret_cast (data->text().utf16()))) != NULL; + CloseClipboard(); + result = true; + } + } + return result ? S_OK : !S_OK; +} + +void QtCeFlushClipboard() { } +#endif + + + +QClipboard::~QClipboard() +{ + cleanupClipboardData(); +} + +void QClipboard::setMimeData(QMimeData *src, Mode mode) +{ + if (mode != Clipboard) + return; + QClipboardData *d = clipboardData(); + + if (!(d->iData && d->iData->mimeData() == src)) { + d->releaseIData(); + d->iData = new QOleDataObject(src); + } + + if (OleSetClipboard(d->iData) != S_OK) { + d->releaseIData(); + qErrnoWarning("QClipboard::setMimeData: Failed to set data on clipboard"); + return; + } +#if defined(Q_OS_WINCE) + // As WinCE does not support notifications we send the signal here + // We will get no event when the clipboard changes outside... + emit dataChanged(); + emit changed(Clipboard); +#endif +} + +void QClipboard::clear(Mode mode) +{ + if (mode != Clipboard) return; + + QClipboardData *d = clipboardData(); + + d->releaseIData(); + + if (OleSetClipboard(0) != S_OK) { + qErrnoWarning("QClipboard::clear: Failed to clear data on clipboard"); + return; + } +#if defined(Q_OS_WINCE) + // As WinCE does not support notifications we send the signal here + // We will get no event when the clipboard changes outside... + emit dataChanged(); + emit changed(Clipboard); +#endif +} + +bool QClipboard::event(QEvent *e) +{ + if (e->type() != QEvent::Clipboard) + return QObject::event(e); + + QClipboardData *d = clipboardData(); + + MSG *m = (MSG *)((QClipboardEvent*)e)->data(); + if (!m) { + // this is sent to render all formats at app shut down + if (ownsClipboard()) { + OleFlushClipboard(); + d->releaseIData(); + } + return true; + } + + bool propagate = false; + + if (m->message == WM_CHANGECBCHAIN) { + if ((HWND)m->wParam == d->nextClipboardViewer) + d->nextClipboardViewer = (HWND)m->lParam; + else + propagate = true; + } else if (m->message == WM_DRAWCLIPBOARD) { + emitChanged(QClipboard::Clipboard); + if (!ownsClipboard() && d->iData) + // clean up the clipboard object if we no longer own the clipboard + d->releaseIData(); + propagate = true; + } + if (propagate && d->nextClipboardViewer) { + if (ptrIsHungAppWindow == 0) { + QSystemLibrary library(QLatin1String("User32")); + ptrIsHungAppWindow = (PtrIsHungAppWindow)library.resolve("IsHungAppWindow"); + } + if (ptrIsHungAppWindow && ptrIsHungAppWindow(d->nextClipboardViewer)) { + qWarning("%s: Cowardly refusing to send clipboard message to hung application...", Q_FUNC_INFO); + } else { + SendMessage(d->nextClipboardViewer, m->message, m->wParam, m->lParam); + } + } + + return true; +} + +void QClipboard::connectNotify(const char *signal) +{ + if (qstrcmp(signal,SIGNAL(dataChanged())) == 0) { + // ensure we are up and running but block signals so the dataChange signal + // is not emitted while being connected to. + bool blocked = blockSignals(true); + QClipboardData *d = clipboardData(); + blockSignals(blocked); + Q_UNUSED(d); + } +} + +const QMimeData *QClipboard::mimeData(Mode mode) const +{ + if (mode != Clipboard) + return 0; + + QClipboardData *data = clipboardData(); + // sort cut for local copy / paste + if (ownsClipboard() && data->iData->mimeData()) + return data->iData->mimeData(); + return &data->watcher; +} + +bool QClipboard::supportsMode(Mode mode) const +{ + return (mode == Clipboard); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + if (mode == Clipboard) { + QClipboardData *d = clipboardData(); +#if !defined(Q_OS_WINCE) + return d->iData && OleIsCurrentClipboard(d->iData) == S_OK; +#else + return d->iData && GetClipboardOwner() == d->clipBoardViewer->internalWinId(); +#endif + } else { + return false; + } +} + +void QClipboard::ownerDestroyed() +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_CLIPBOARD diff --git a/src/gui/kernel/qclipboard_x11.cpp b/src/gui/kernel/qclipboard_x11.cpp new file mode 100644 index 0000000000..d566c86e04 --- /dev/null +++ b/src/gui/kernel/qclipboard_x11.cpp @@ -0,0 +1,1539 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// #define QCLIPBOARD_DEBUG +// #define QCLIPBOARD_DEBUG_VERBOSE + +#ifdef QCLIPBOARD_DEBUG +# define DEBUG qDebug +#else +# define DEBUG if (false) qDebug +#endif + +#ifdef QCLIPBOARD_DEBUG_VERBOSE +# define VDEBUG qDebug +#else +# define VDEBUG if (false) qDebug +#endif + +#include "qplatformdefs.h" + +#include "qclipboard.h" +#include "qclipboard_p.h" + +#ifndef QT_NO_CLIPBOARD + +#include "qabstracteventdispatcher.h" +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qbitmap.h" +#include "qiodevice.h" +#include "qbuffer.h" +#include "qtextcodec.h" +#include "qlist.h" +#include "qmap.h" +#include "qapplication_p.h" +#include "qevent.h" +#include "qt_x11_p.h" +#include "qx11info_x11.h" +#include "qimagewriter.h" +#include "qelapsedtimer.h" +#include "qvariant.h" +#include "qdnd_p.h" +#include + +#ifndef QT_NO_XFIXES +#include +#endif // QT_NO_XFIXES + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Internal QClipboard functions for X11. + *****************************************************************************/ + +static int clipboard_timeout = 5000; // 5s timeout on clipboard operations + +static QWidget * owner = 0; +static QWidget *requestor = 0; +static bool timer_event_clear = false; +static int timer_id = 0; + +static int pending_timer_id = 0; +static bool pending_clipboard_changed = false; +static bool pending_selection_changed = false; + + +// event capture mechanism for qt_xclb_wait_for_event +static bool waiting_for_data = false; +static bool has_captured_event = false; +static Window capture_event_win = XNone; +static int capture_event_type = -1; +static XEvent captured_event; + +class QClipboardWatcher; // forward decl +static QClipboardWatcher *selection_watcher = 0; +static QClipboardWatcher *clipboard_watcher = 0; + +static void cleanup() +{ + delete owner; + delete requestor; + owner = 0; + requestor = 0; +} + +static +void setupOwner() +{ + if (owner) + return; + owner = new QWidget(0); + owner->setObjectName(QLatin1String("internal clipboard owner")); + owner->createWinId(); + requestor = new QWidget(0); + requestor->createWinId(); + requestor->setObjectName(QLatin1String("internal clipboard requestor")); + // We don't need this internal widgets to appear in QApplication::topLevelWidgets() + if (QWidgetPrivate::allWidgets) { + QWidgetPrivate::allWidgets->remove(owner); + QWidgetPrivate::allWidgets->remove(requestor); + } + qAddPostRoutine(cleanup); +} + + +class QClipboardWatcher : public QInternalMimeData { +public: + QClipboardWatcher(QClipboard::Mode mode); + ~QClipboardWatcher(); + bool empty() const; + virtual bool hasFormat_sys(const QString &mimetype) const; + virtual QStringList formats_sys() const; + + QVariant retrieveData_sys(const QString &mimetype, QVariant::Type type) const; + QByteArray getDataInFormat(Atom fmtatom) const; + + Atom atom; + mutable QStringList formatList; + mutable QByteArray format_atoms; +}; + +class QClipboardData +{ +private: + QMimeData *&mimeDataRef() const + { + if(mode == QClipboard::Selection) + return selectionData; + return clipboardData; + } + +public: + QClipboardData(QClipboard::Mode mode); + ~QClipboardData(); + + void setSource(QMimeData* s) + { + if ((mode == QClipboard::Selection && selectionData == s) + || clipboardData == s) { + return; + } + + if (selectionData != clipboardData) { + delete mimeDataRef(); + } + + mimeDataRef() = s; + } + + QMimeData *source() const + { + return mimeDataRef(); + } + + void clear() + { + timestamp = CurrentTime; + if (selectionData == clipboardData) { + mimeDataRef() = 0; + } else { + QMimeData *&src = mimeDataRef(); + delete src; + src = 0; + } + } + + static QMimeData *selectionData; + static QMimeData *clipboardData; + Time timestamp; + QClipboard::Mode mode; +}; + +QMimeData *QClipboardData::selectionData = 0; +QMimeData *QClipboardData::clipboardData = 0; + +QClipboardData::QClipboardData(QClipboard::Mode clipboardMode) +{ + timestamp = CurrentTime; + mode = clipboardMode; +} + +QClipboardData::~QClipboardData() +{ clear(); } + + +static QClipboardData *internalCbData = 0; +static QClipboardData *internalSelData = 0; + +static void cleanupClipboardData() +{ + delete internalCbData; + internalCbData = 0; +} + +static QClipboardData *clipboardData() +{ + if (internalCbData == 0) { + internalCbData = new QClipboardData(QClipboard::Clipboard); + qAddPostRoutine(cleanupClipboardData); + } + return internalCbData; +} + +static void cleanupSelectionData() +{ + delete internalSelData; + internalSelData = 0; +} + +static QClipboardData *selectionData() +{ + if (internalSelData == 0) { + internalSelData = new QClipboardData(QClipboard::Selection); + qAddPostRoutine(cleanupSelectionData); + } + return internalSelData; +} + +class QClipboardINCRTransaction +{ +public: + QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, QByteArray d, unsigned int i); + ~QClipboardINCRTransaction(void); + + int x11Event(XEvent *event); + + Window window; + Atom property, target; + int format; + QByteArray data; + unsigned int increment; + unsigned int offset; +}; + +typedef QMap TransactionMap; +static TransactionMap *transactions = 0; +static QApplication::EventFilter prev_event_filter = 0; +static int incr_timer_id = 0; + +static bool qt_x11_incr_event_filter(void *message, long *result) +{ + XEvent *event = reinterpret_cast(message); + TransactionMap::Iterator it = transactions->find(event->xany.window); + if (it != transactions->end()) { + if ((*it)->x11Event(event) != 0) + return true; + } + if (prev_event_filter) + return prev_event_filter(event, result); + return false; +} + +/* + called when no INCR activity has happened for 'clipboard_timeout' + milliseconds... we assume that all unfinished transactions have + timed out and remove everything from the transaction map +*/ +static void qt_xclb_incr_timeout(void) +{ + qWarning("QClipboard: Timed out while sending data"); + + while (transactions) + delete *transactions->begin(); +} + +QClipboardINCRTransaction::QClipboardINCRTransaction(Window w, Atom p, Atom t, int f, + QByteArray d, unsigned int i) + : window(w), property(p), target(t), format(f), data(d), increment(i), offset(0u) +{ + DEBUG("QClipboard: sending %d bytes (INCR transaction %p)", d.size(), this); + + XSelectInput(X11->display, window, PropertyChangeMask); + + if (! transactions) { + VDEBUG("QClipboard: created INCR transaction map"); + transactions = new TransactionMap; + prev_event_filter = qApp->setEventFilter(qt_x11_incr_event_filter); + incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout); + } + transactions->insert(window, this); +} + +QClipboardINCRTransaction::~QClipboardINCRTransaction(void) +{ + VDEBUG("QClipboard: destroyed INCR transacton %p", this); + + XSelectInput(X11->display, window, NoEventMask); + + transactions->remove(window); + if (transactions->isEmpty()) { + VDEBUG("QClipboard: no more INCR transactions"); + delete transactions; + transactions = 0; + + (void)qApp->setEventFilter(prev_event_filter); + + if (incr_timer_id != 0) { + QApplication::clipboard()->killTimer(incr_timer_id); + incr_timer_id = 0; + } + } +} + +int QClipboardINCRTransaction::x11Event(XEvent *event) +{ + if (event->type != PropertyNotify + || (event->xproperty.state != PropertyDelete + || event->xproperty.atom != property)) + return 0; + + // restart the INCR timer + if (incr_timer_id) QApplication::clipboard()->killTimer(incr_timer_id); + incr_timer_id = QApplication::clipboard()->startTimer(clipboard_timeout); + + unsigned int bytes_left = data.size() - offset; + if (bytes_left > 0) { + unsigned int xfer = qMin(increment, bytes_left); + VDEBUG("QClipboard: sending %d bytes, %d remaining (INCR transaction %p)", + xfer, bytes_left - xfer, this); + + XChangeProperty(X11->display, window, property, target, format, + PropModeReplace, (uchar *) data.data() + offset, xfer); + offset += xfer; + } else { + // INCR transaction finished... + XChangeProperty(X11->display, window, property, target, format, + PropModeReplace, (uchar *) data.data(), 0); + delete this; + } + + return 1; +} + + +/***************************************************************************** + QClipboard member functions for X11. + *****************************************************************************/ + +struct qt_init_timestamp_data +{ + Time timestamp; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static Bool qt_init_timestamp_scanner(Display*, XEvent *event, XPointer arg) +{ + qt_init_timestamp_data *data = + reinterpret_cast(arg); + switch(event->type) + { + case ButtonPress: + case ButtonRelease: + data->timestamp = event->xbutton.time; + break; + case MotionNotify: + data->timestamp = event->xmotion.time; + break; + case XKeyPress: + case XKeyRelease: + data->timestamp = event->xkey.time; + break; + case PropertyNotify: + data->timestamp = event->xproperty.time; + break; + case EnterNotify: + case LeaveNotify: + data->timestamp = event->xcrossing.time; + break; + case SelectionClear: + data->timestamp = event->xselectionclear.time; + break; + default: + break; + } +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && event->type == (X11->xfixes_eventbase + XFixesSelectionNotify)) { + XFixesSelectionNotifyEvent *req = + reinterpret_cast(event); + data->timestamp = req->selection_timestamp; + } +#endif + return false; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +QClipboard::QClipboard(QObject *parent) + : QObject(*new QClipboardPrivate, parent) +{ + // create desktop widget since we need it to get PropertyNotify or + // XFixesSelectionNotify events when someone changes the + // clipboard. + (void)QApplication::desktop(); + +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && X11->ptrXFixesSelectSelectionInput) { + const unsigned long eventMask = + XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask; + for (int i = 0; i < X11->screenCount; ++i) { + X11->ptrXFixesSelectSelectionInput(X11->display, QX11Info::appRootWindow(i), + XA_PRIMARY, eventMask); + X11->ptrXFixesSelectSelectionInput(X11->display, QX11Info::appRootWindow(i), + ATOM(CLIPBOARD), eventMask); + } + } +#endif // QT_NO_XFIXES + + if (X11->time == CurrentTime) { + // send a dummy event to myself to get the timestamp from X11. + qt_init_timestamp_data data; + data.timestamp = CurrentTime; + XEvent ev; + XCheckIfEvent(X11->display, &ev, &qt_init_timestamp_scanner, (XPointer)&data); + if (data.timestamp == CurrentTime) { + setupOwner(); + // We need this value just for completeness, we don't use it. + long dummy = 0; + Window ownerId = owner->internalWinId(); + XChangeProperty(X11->display, ownerId, + ATOM(CLIP_TEMPORARY), XA_INTEGER, 32, + PropModeReplace, (uchar*)&dummy, 1); + XWindowEvent(X11->display, ownerId, PropertyChangeMask, &ev); + data.timestamp = ev.xproperty.time; + XDeleteProperty(X11->display, ownerId, ATOM(CLIP_TEMPORARY)); + } + X11->time = data.timestamp; + } +} + +void QClipboard::clear(Mode mode) +{ + setMimeData(0, mode); +} + + +bool QClipboard::supportsMode(Mode mode) const +{ + return (mode == Clipboard || mode == Selection); +} + +bool QClipboard::ownsMode(Mode mode) const +{ + if (mode == Clipboard) + return clipboardData()->timestamp != CurrentTime; + else if(mode == Selection) + return selectionData()->timestamp != CurrentTime; + else + return false; +} + + +// event filter function... captures interesting events while +// qt_xclb_wait_for_event is running the event loop +static bool qt_x11_clipboard_event_filter(void *message, long *) +{ + XEvent *event = reinterpret_cast(message); + if (event->xany.type == capture_event_type && + event->xany.window == capture_event_win) { + VDEBUG("QClipboard: event_filter(): caught event type %d", event->type); + has_captured_event = true; + captured_event = *event; + return true; + } + return false; +} + +static Bool checkForClipboardEvents(Display *, XEvent *e, XPointer) +{ + return ((e->type == SelectionRequest && (e->xselectionrequest.selection == XA_PRIMARY + || e->xselectionrequest.selection == ATOM(CLIPBOARD))) + || (e->type == SelectionClear && (e->xselectionclear.selection == XA_PRIMARY + || e->xselectionclear.selection == ATOM(CLIPBOARD)))); +} + +bool QX11Data::clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout) +{ + QElapsedTimer started; + started.start(); + QElapsedTimer now = started; + + if (QAbstractEventDispatcher::instance()->inherits("QtMotif") + || QApplication::clipboard()->property("useEventLoopWhenWaiting").toBool()) { + if (waiting_for_data) { + Q_ASSERT(!"QClipboard: internal error, qt_xclb_wait_for_event recursed"); + return false; + } + waiting_for_data = true; + + + has_captured_event = false; + capture_event_win = win; + capture_event_type = type; + + QApplication::EventFilter old_event_filter = + qApp->setEventFilter(qt_x11_clipboard_event_filter); + + do { + if (XCheckTypedWindowEvent(display, win, type, event)) { + waiting_for_data = false; + qApp->setEventFilter(old_event_filter); + return true; + } + + XSync(X11->display, false); + usleep(50000); + + now.start(); + + QEventLoop::ProcessEventsFlags flags(QEventLoop::ExcludeUserInputEvents + | QEventLoop::ExcludeSocketNotifiers + | QEventLoop::WaitForMoreEvents + | QEventLoop::X11ExcludeTimers); + QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance(); + eventDispatcher->processEvents(flags); + + if (has_captured_event) { + waiting_for_data = false; + *event = captured_event; + qApp->setEventFilter(old_event_filter); + return true; + } + } while (started.msecsTo(now) < timeout); + + waiting_for_data = false; + qApp->setEventFilter(old_event_filter); + } else { + do { + if (XCheckTypedWindowEvent(X11->display,win,type,event)) + return true; + + // process other clipboard events, since someone is probably requesting data from us + XEvent e; + if (XCheckIfEvent(X11->display, &e, checkForClipboardEvents, 0)) + qApp->x11ProcessEvent(&e); + + now.start(); + + XFlush(X11->display); + + // sleep 50 ms, so we don't use up CPU cycles all the time. + struct timeval usleep_tv; + usleep_tv.tv_sec = 0; + usleep_tv.tv_usec = 50000; + select(0, 0, 0, 0, &usleep_tv); + } while (started.msecsTo(now) < timeout); + } + return false; +} + + +static inline int maxSelectionIncr(Display *dpy) +{ return XMaxRequestSize(dpy) > 65536 ? 65536*4 : XMaxRequestSize(dpy)*4 - 100; } + +bool QX11Data::clipboardReadProperty(Window win, Atom property, bool deleteProperty, + QByteArray *buffer, int *size, Atom *type, int *format) +{ + int maxsize = maxSelectionIncr(display); + ulong bytes_left; // bytes_after + ulong length; // nitems + uchar *data; + Atom dummy_type; + int dummy_format; + int r; + + if (!type) // allow null args + type = &dummy_type; + if (!format) + format = &dummy_format; + + // Don't read anything, just get the size of the property data + r = XGetWindowProperty(display, win, property, 0, 0, False, + AnyPropertyType, type, format, + &length, &bytes_left, &data); + if (r != Success || (type && *type == XNone)) { + buffer->resize(0); + return false; + } + XFree((char*)data); + + int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left; + + VDEBUG("QClipboard: read_property(): initial property length: %d", proplen); + + switch (*format) { + case 8: + default: + format_inc = sizeof(char) / 1; + break; + + case 16: + format_inc = sizeof(short) / 2; + proplen *= sizeof(short) / 2; + break; + + case 32: + format_inc = sizeof(long) / 4; + proplen *= sizeof(long) / 4; + break; + } + + int newSize = proplen; + buffer->resize(newSize); + + bool ok = (buffer->size() == newSize); + VDEBUG("QClipboard: read_property(): buffer resized to %d", buffer->size()); + + if (ok && newSize) { + // could allocate buffer + + while (bytes_left) { + // more to read... + + r = XGetWindowProperty(display, win, property, offset, maxsize/4, + False, AnyPropertyType, type, format, + &length, &bytes_left, &data); + if (r != Success || (type && *type == XNone)) + break; + + offset += length / (32 / *format); + length *= format_inc * (*format) / 8; + + // Here we check if we get a buffer overflow and tries to + // recover -- this shouldn't normally happen, but it doesn't + // hurt to be defensive + if ((int)(buffer_offset + length) > buffer->size()) { + length = buffer->size() - buffer_offset; + + // escape loop + bytes_left = 0; + } + + memcpy(buffer->data() + buffer_offset, data, length); + buffer_offset += length; + + XFree((char*)data); + } + + if (*format == 8 && *type == ATOM(COMPOUND_TEXT)) { + // convert COMPOUND_TEXT to a multibyte string + XTextProperty textprop; + textprop.encoding = *type; + textprop.format = *format; + textprop.nitems = buffer_offset; + textprop.value = (unsigned char *) buffer->data(); + + char **list_ret = 0; + int count; + if (XmbTextPropertyToTextList(display, &textprop, &list_ret, + &count) == Success && count && list_ret) { + offset = buffer_offset = strlen(list_ret[0]); + buffer->resize(offset); + memcpy(buffer->data(), list_ret[0], offset); + } + if (list_ret) XFreeStringList(list_ret); + } + } + + // correct size, not 0-term. + if (size) + *size = buffer_offset; + + VDEBUG("QClipboard: read_property(): buffer size %d, buffer offset %d, offset %d", + buffer->size(), buffer_offset, offset); + + if (deleteProperty) + XDeleteProperty(display, win, property); + + XFlush(display); + + return ok; +} + +QByteArray QX11Data::clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm) +{ + XEvent event; + + QByteArray buf; + QByteArray tmp_buf; + bool alloc_error = false; + int length; + int offset = 0; + + if (nbytes > 0) { + // Reserve buffer + zero-terminator (for text data) + // We want to complete the INCR transfer even if we cannot + // allocate more memory + buf.resize(nbytes+1); + alloc_error = buf.size() != nbytes+1; + } + + for (;;) { + XFlush(display); + if (!clipboardWaitForEvent(win,PropertyNotify,&event,clipboard_timeout)) + break; + if (event.xproperty.atom != property || + event.xproperty.state != PropertyNewValue) + continue; + if (X11->clipboardReadProperty(win, property, true, &tmp_buf, &length, 0, 0)) { + if (length == 0) { // no more data, we're done + if (nullterm) { + buf.resize(offset+1); + buf[offset] = '\0'; + } else { + buf.resize(offset); + } + return buf; + } else if (!alloc_error) { + if (offset+length > (int)buf.size()) { + buf.resize(offset+length+65535); + if (buf.size() != offset+length+65535) { + alloc_error = true; + length = buf.size() - offset; + } + } + memcpy(buf.data()+offset, tmp_buf.constData(), length); + tmp_buf.resize(0); + offset += length; + } + } else { + break; + } + } + + // timed out ... create a new requestor window, otherwise the requestor + // could consider next request to be still part of this timed out request + delete requestor; + requestor = new QWidget(0); + requestor->setObjectName(QLatin1String("internal clipboard requestor")); + // We don't need this internal widget to appear in QApplication::topLevelWidgets() + if (QWidgetPrivate::allWidgets) + QWidgetPrivate::allWidgets->remove(requestor); + + return QByteArray(); +} + +static Atom send_targets_selection(QClipboardData *d, Window window, Atom property) +{ + QVector types; + QStringList formats = QInternalMimeData::formatsHelper(d->source()); + for (int i = 0; i < formats.size(); ++i) { + QList atoms = X11->xdndMimeAtomsForFormat(formats.at(i)); + for (int j = 0; j < atoms.size(); ++j) { + if (!types.contains(atoms.at(j))) + types.append(atoms.at(j)); + } + } + types.append(ATOM(TARGETS)); + types.append(ATOM(MULTIPLE)); + types.append(ATOM(TIMESTAMP)); + types.append(ATOM(SAVE_TARGETS)); + + XChangeProperty(X11->display, window, property, XA_ATOM, 32, + PropModeReplace, (uchar *) types.data(), types.size()); + return property; +} + +static Atom send_selection(QClipboardData *d, Atom target, Window window, Atom property) +{ + Atom atomFormat = target; + int dataFormat = 0; + QByteArray data; + + QByteArray fmt = X11->xdndAtomToString(target); + if (fmt.isEmpty()) { // Not a MIME type we have + DEBUG("QClipboard: send_selection(): converting to type '%s' is not supported", fmt.data()); + return XNone; + } + DEBUG("QClipboard: send_selection(): converting to type '%s'", fmt.data()); + + if (X11->xdndMimeDataForAtom(target, d->source(), &data, &atomFormat, &dataFormat)) { + + VDEBUG("QClipboard: send_selection():\n" + " property type %lx\n" + " property name '%s'\n" + " format %d\n" + " %d bytes\n", + target, X11->xdndMimeAtomToString(atomFormat).toLatin1().data(), dataFormat, data.size()); + + // don't allow INCR transfers when using MULTIPLE or to + // Motif clients (since Motif doesn't support INCR) + static Atom motif_clip_temporary = ATOM(CLIP_TEMPORARY); + bool allow_incr = property != motif_clip_temporary; + + // X_ChangeProperty protocol request is 24 bytes + const int increment = (XMaxRequestSize(X11->display) * 4) - 24; + if (data.size() > increment && allow_incr) { + long bytes = data.size(); + XChangeProperty(X11->display, window, property, + ATOM(INCR), 32, PropModeReplace, (uchar *) &bytes, 1); + + (void)new QClipboardINCRTransaction(window, property, atomFormat, dataFormat, data, increment); + return property; + } + + // make sure we can perform the XChangeProperty in a single request + if (data.size() > increment) + return XNone; // ### perhaps use several XChangeProperty calls w/ PropModeAppend? + int dataSize = data.size() / (dataFormat / 8); + // use a single request to transfer data + XChangeProperty(X11->display, window, property, atomFormat, + dataFormat, PropModeReplace, (uchar *) data.data(), + dataSize); + } + return property; +} + +/*! \internal + Internal cleanup for Windows. +*/ +void QClipboard::ownerDestroyed() +{ } + + +/*! \internal + Internal optimization for Windows. +*/ +void QClipboard::connectNotify(const char *) +{ } + + +bool QClipboard::event(QEvent *e) +{ + if (e->type() == QEvent::Timer) { + QTimerEvent *te = (QTimerEvent *) e; + + if (waiting_for_data) // should never happen + return false; + + if (te->timerId() == timer_id) { + killTimer(timer_id); + timer_id = 0; + + timer_event_clear = true; + if (selection_watcher) // clear selection + selectionData()->clear(); + if (clipboard_watcher) // clear clipboard + clipboardData()->clear(); + timer_event_clear = false; + + return true; + } else if (te->timerId() == pending_timer_id) { + // I hate klipper + killTimer(pending_timer_id); + pending_timer_id = 0; + + if (pending_clipboard_changed) { + pending_clipboard_changed = false; + clipboardData()->clear(); + emitChanged(QClipboard::Clipboard); + } + if (pending_selection_changed) { + pending_selection_changed = false; + selectionData()->clear(); + emitChanged(QClipboard::Selection); + } + + return true; + } else if (te->timerId() == incr_timer_id) { + killTimer(incr_timer_id); + incr_timer_id = 0; + + qt_xclb_incr_timeout(); + + return true; + } else { + return QObject::event(e); + } + } else if (e->type() != QEvent::Clipboard) { + return QObject::event(e); + } + + XEvent *xevent = (XEvent *)(((QClipboardEvent *)e)->data()); + Display *dpy = X11->display; + + if (!xevent) { + // That means application exits and we need to give clipboard + // content to the clipboard manager. + // First we check if there is a clipboard manager. + if (XGetSelectionOwner(X11->display, ATOM(CLIPBOARD_MANAGER)) == XNone + || !owner) + return true; + + Window ownerId = owner->internalWinId(); + Q_ASSERT(ownerId); + // we delete the property so the manager saves all TARGETS. + XDeleteProperty(X11->display, ownerId, ATOM(_QT_SELECTION)); + XConvertSelection(X11->display, ATOM(CLIPBOARD_MANAGER), ATOM(SAVE_TARGETS), + ATOM(_QT_SELECTION), ownerId, X11->time); + XSync(dpy, false); + + XEvent event; + // waiting until the clipboard manager fetches the content. + if (!X11->clipboardWaitForEvent(ownerId, SelectionNotify, &event, 10000)) { + qWarning("QClipboard: Unable to receive an event from the " + "clipboard manager in a reasonable time"); + } + + return true; + } + + switch (xevent->type) { + + case SelectionClear: + // new selection owner + if (xevent->xselectionclear.selection == XA_PRIMARY) { + QClipboardData *d = selectionData(); + + // ignore the event if it was generated before we gained selection ownership + if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp) + break; + + DEBUG("QClipboard: new selection owner 0x%lx at time %lx (ours %lx)", + XGetSelectionOwner(dpy, XA_PRIMARY), + xevent->xselectionclear.time, d->timestamp); + + if (! waiting_for_data) { + d->clear(); + emitChanged(QClipboard::Selection); + } else { + pending_selection_changed = true; + if (! pending_timer_id) + pending_timer_id = QApplication::clipboard()->startTimer(0); + } + } else if (xevent->xselectionclear.selection == ATOM(CLIPBOARD)) { + QClipboardData *d = clipboardData(); + + // ignore the event if it was generated before we gained selection ownership + if (d->timestamp != CurrentTime && xevent->xselectionclear.time <= d->timestamp) + break; + + DEBUG("QClipboard: new clipboard owner 0x%lx at time %lx (%lx)", + XGetSelectionOwner(dpy, ATOM(CLIPBOARD)), + xevent->xselectionclear.time, d->timestamp); + + if (! waiting_for_data) { + d->clear(); + emitChanged(QClipboard::Clipboard); + } else { + pending_clipboard_changed = true; + if (! pending_timer_id) + pending_timer_id = QApplication::clipboard()->startTimer(0); + } + } else { + qWarning("QClipboard: Unknown SelectionClear event received"); + return false; + } + break; + + case SelectionNotify: + /* + Something has delivered data to us, but this was not caught + by QClipboardWatcher::getDataInFormat() + + Just skip the event to prevent Bad Things (tm) from + happening later on... + */ + break; + + case SelectionRequest: + { + // someone wants our data + XSelectionRequestEvent *req = &xevent->xselectionrequest; + + if (requestor && req->requestor == requestor->internalWinId()) + break; + + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = req->display; + event.xselection.requestor = req->requestor; + event.xselection.selection = req->selection; + event.xselection.target = req->target; + event.xselection.property = XNone; + event.xselection.time = req->time; + + DEBUG("QClipboard: SelectionRequest from %lx\n" + " selection 0x%lx (%s) target 0x%lx (%s)", + req->requestor, + req->selection, + X11->xdndAtomToString(req->selection).data(), + req->target, + X11->xdndAtomToString(req->target).data()); + + QClipboardData *d; + if (req->selection == XA_PRIMARY) { + d = selectionData(); + } else if (req->selection == ATOM(CLIPBOARD)) { + d = clipboardData(); + } else { + qWarning("QClipboard: Unknown selection '%lx'", req->selection); + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + if (! d->source()) { + qWarning("QClipboard: Cannot transfer data, no data available"); + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + DEBUG("QClipboard: SelectionRequest at time %lx (ours %lx)", + req->time, d->timestamp); + + if (d->timestamp == CurrentTime // we don't own the selection anymore + || (req->time != CurrentTime && req->time < d->timestamp)) { + DEBUG("QClipboard: SelectionRequest too old"); + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + + Atom xa_targets = ATOM(TARGETS); + Atom xa_multiple = ATOM(MULTIPLE); + Atom xa_timestamp = ATOM(TIMESTAMP); + + struct AtomPair { Atom target; Atom property; } *multi = 0; + Atom multi_type = XNone; + int multi_format = 0; + int nmulti = 0; + int imulti = -1; + bool multi_writeback = false; + + if (req->target == xa_multiple) { + QByteArray multi_data; + if (req->property == XNone + || !X11->clipboardReadProperty(req->requestor, req->property, false, &multi_data, + 0, &multi_type, &multi_format) + || multi_format != 32) { + // MULTIPLE property not formatted correctly + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + break; + } + nmulti = multi_data.size()/sizeof(*multi); + multi = new AtomPair[nmulti]; + memcpy(multi,multi_data.data(),multi_data.size()); + imulti = 0; + } + + for (; imulti < nmulti; ++imulti) { + Atom target; + Atom property; + + if (multi) { + target = multi[imulti].target; + property = multi[imulti].property; + } else { + target = req->target; + property = req->property; + if (property == XNone) // obsolete client + property = target; + } + + Atom ret = XNone; + if (target == XNone || property == XNone) { + ; + } else if (target == xa_timestamp) { + if (d->timestamp != CurrentTime) { + XChangeProperty(dpy, req->requestor, property, XA_INTEGER, 32, + PropModeReplace, (uchar *) &d->timestamp, 1); + ret = property; + } else { + qWarning("QClipboard: Invalid data timestamp"); + } + } else if (target == xa_targets) { + ret = send_targets_selection(d, req->requestor, property); + } else { + ret = send_selection(d, target, req->requestor, property); + } + + if (nmulti > 0) { + if (ret == XNone) { + multi[imulti].property = XNone; + multi_writeback = true; + } + } else { + event.xselection.property = ret; + break; + } + } + + if (nmulti > 0) { + if (multi_writeback) { + // according to ICCCM 2.6.2 says to put None back + // into the original property on the requestor window + XChangeProperty(dpy, req->requestor, req->property, multi_type, 32, + PropModeReplace, (uchar *) multi, nmulti * 2); + } + + delete [] multi; + event.xselection.property = req->property; + } + + // send selection notify to requestor + XSendEvent(dpy, req->requestor, False, NoEventMask, &event); + + DEBUG("QClipboard: SelectionNotify to 0x%lx\n" + " property 0x%lx (%s)", + req->requestor, event.xselection.property, + X11->xdndAtomToString(event.xselection.property).data()); + } + break; + } + + return true; +} + + + + + + +QClipboardWatcher::QClipboardWatcher(QClipboard::Mode mode) + : QInternalMimeData() +{ + switch (mode) { + case QClipboard::Selection: + atom = XA_PRIMARY; + break; + + case QClipboard::Clipboard: + atom = ATOM(CLIPBOARD); + break; + + default: + qWarning("QClipboardWatcher: Internal error: Unsupported clipboard mode"); + break; + } + + setupOwner(); +} + +QClipboardWatcher::~QClipboardWatcher() +{ + if(selection_watcher == this) + selection_watcher = 0; + if(clipboard_watcher == this) + clipboard_watcher = 0; +} + +bool QClipboardWatcher::empty() const +{ + Display *dpy = X11->display; + Window win = XGetSelectionOwner(dpy, atom); + + if(win == requestor->internalWinId()) { + qWarning("QClipboardWatcher::empty: Internal error: Application owns the selection"); + return true; + } + + return win == XNone; +} + +QStringList QClipboardWatcher::formats_sys() const +{ + if (empty()) + return QStringList(); + + if (!formatList.count()) { + // 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... + + format_atoms = getDataInFormat(ATOM(TARGETS)); + + if (format_atoms.size() > 0) { + Atom *targets = (Atom *) format_atoms.data(); + int size = format_atoms.size() / sizeof(Atom); + + for (int i = 0; i < size; ++i) { + if (targets[i] == 0) + continue; + + QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(targets[i]); + for (int j = 0; j < formatsForAtom.size(); ++j) { + if (!formatList.contains(formatsForAtom.at(j))) + formatList.append(formatsForAtom.at(j)); + } + VDEBUG(" format: %s", X11->xdndAtomToString(targets[i]).data()); + VDEBUG(" data:\n%s\n", getDataInFormat(targets[i]).data()); + } + DEBUG("QClipboardWatcher::format: %d formats available", formatList.count()); + } + } + + return formatList; +} + +bool QClipboardWatcher::hasFormat_sys(const QString &format) const +{ + QStringList list = formats(); + return list.contains(format); +} + +QVariant QClipboardWatcher::retrieveData_sys(const QString &fmt, QVariant::Type requestedType) const +{ + if (fmt.isEmpty() || empty()) + return QByteArray(); + + (void)formats(); // trigger update of format list + DEBUG("QClipboardWatcher::data: fetching format '%s'", fmt.toLatin1().data()); + + QList atoms; + Atom *targets = (Atom *) format_atoms.data(); + int size = format_atoms.size() / sizeof(Atom); + for (int i = 0; i < size; ++i) + atoms.append(targets[i]); + + QByteArray encoding; + Atom fmtatom = X11->xdndMimeAtomForFormat(fmt, requestedType, atoms, &encoding); + + if (fmtatom == 0) + return QVariant(); + + return X11->xdndMimeConvertToFormat(fmtatom, getDataInFormat(fmtatom), fmt, requestedType, encoding); +} + +QByteArray QClipboardWatcher::getDataInFormat(Atom fmtatom) const +{ + QByteArray buf; + + Display *dpy = X11->display; + requestor->createWinId(); + Window win = requestor->internalWinId(); + Q_ASSERT(requestor->testAttribute(Qt::WA_WState_Created)); + + DEBUG("QClipboardWatcher::getDataInFormat: selection '%s' format '%s'", + X11->xdndAtomToString(atom).data(), X11->xdndAtomToString(fmtatom).data()); + + XSelectInput(dpy, win, NoEventMask); // don't listen for any events + + XDeleteProperty(dpy, win, ATOM(_QT_SELECTION)); + XConvertSelection(dpy, atom, fmtatom, ATOM(_QT_SELECTION), win, X11->time); + XSync(dpy, false); + + VDEBUG("QClipboardWatcher::getDataInFormat: waiting for SelectionNotify event"); + + XEvent xevent; + if (!X11->clipboardWaitForEvent(win,SelectionNotify,&xevent,clipboard_timeout) || + xevent.xselection.property == XNone) { + DEBUG("QClipboardWatcher::getDataInFormat: format not available"); + return buf; + } + + VDEBUG("QClipboardWatcher::getDataInFormat: fetching data..."); + + Atom type; + XSelectInput(dpy, win, PropertyChangeMask); + + if (X11->clipboardReadProperty(win, ATOM(_QT_SELECTION), true, &buf, 0, &type, 0)) { + if (type == ATOM(INCR)) { + int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0; + buf = X11->clipboardReadIncrementalProperty(win, ATOM(_QT_SELECTION), nbytes, false); + } + } + + XSelectInput(dpy, win, NoEventMask); + + DEBUG("QClipboardWatcher::getDataInFormat: %d bytes received", buf.size()); + + return buf; +} + + +const QMimeData* QClipboard::mimeData(Mode mode) const +{ + QClipboardData *d = 0; + switch (mode) { + case Selection: + d = selectionData(); + break; + case Clipboard: + d = clipboardData(); + break; + default: + qWarning("QClipboard::mimeData: unsupported mode '%d'", mode); + return 0; + } + + if (! d->source() && ! timer_event_clear) { + if (mode == Selection) { + if (! selection_watcher) + selection_watcher = new QClipboardWatcher(mode); + d->setSource(selection_watcher); + } else { + if (! clipboard_watcher) + clipboard_watcher = new QClipboardWatcher(mode); + d->setSource(clipboard_watcher); + } + + if (! timer_id) { + // start a zero timer - we will clear cached data when the timer + // times out, which will be the next time we hit the event loop... + // that way, the data is cached long enough for calls within a single + // loop/function, but the data doesn't linger around in case the selection + // changes + QClipboard *that = ((QClipboard *) this); + timer_id = that->startTimer(0); + } + } + + return d->source(); +} + + +void QClipboard::setMimeData(QMimeData* src, Mode mode) +{ + Atom atom, sentinel_atom; + QClipboardData *d; + switch (mode) { + case Selection: + atom = XA_PRIMARY; + sentinel_atom = ATOM(_QT_SELECTION_SENTINEL); + d = selectionData(); + break; + + case Clipboard: + atom = ATOM(CLIPBOARD); + sentinel_atom = ATOM(_QT_CLIPBOARD_SENTINEL); + d = clipboardData(); + break; + + default: + qWarning("QClipboard::setMimeData: unsupported mode '%d'", mode); + return; + } + + Display *dpy = X11->display; + Window newOwner; + + if (! src) { // no data, clear clipboard contents + newOwner = XNone; + d->clear(); + } else { + setupOwner(); + + newOwner = owner->internalWinId(); + + d->setSource(src); + d->timestamp = X11->time; + } + + Window prevOwner = XGetSelectionOwner(dpy, atom); + // use X11->time, since d->timestamp == CurrentTime when clearing + XSetSelectionOwner(dpy, atom, newOwner, X11->time); + + if (mode == Selection) + emitChanged(QClipboard::Selection); + else + emitChanged(QClipboard::Clipboard); + + if (XGetSelectionOwner(dpy, atom) != newOwner) { + qWarning("QClipboard::setData: Cannot set X11 selection owner for %s", + X11->xdndAtomToString(atom).data()); + d->clear(); + return; + } + + // Signal to other Qt processes that the selection has changed + Window owners[2]; + owners[0] = newOwner; + owners[1] = prevOwner; + XChangeProperty(dpy, QApplication::desktop()->screen(0)->internalWinId(), + sentinel_atom, XA_WINDOW, 32, PropModeReplace, + (unsigned char*)&owners, 2); +} + + +/* + Called by the main event loop in qapplication_x11.cpp when either + the _QT_SELECTION_SENTINEL property has been changed (i.e. when some + Qt process has performed QClipboard::setData()) or when Xfixes told + us that an other application changed the selection. If it returns + true, the QClipBoard dataChanged() signal should be emitted. +*/ + +bool qt_check_selection_sentinel() +{ + bool doIt = true; + if (owner && !X11->use_xfixes) { + /* + Since the X selection mechanism cannot give any signal when + the selection has changed, we emulate it (for Qt processes) here. + The notification should be ignored in case of either + a) This is the process that did setData (because setData() + then has already emitted dataChanged()) + b) This is the process that owned the selection when dataChanged() + was called (because we have then received a SelectionClear event, + and have already emitted dataChanged() as a result of that) + */ + + unsigned char *retval; + Atom actualType; + int actualFormat; + ulong nitems; + ulong bytesLeft; + + if (XGetWindowProperty(X11->display, + QApplication::desktop()->screen(0)->internalWinId(), + ATOM(_QT_SELECTION_SENTINEL), 0, 2, False, XA_WINDOW, + &actualType, &actualFormat, &nitems, + &bytesLeft, &retval) == Success) { + Window *owners = (Window *)retval; + if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) { + Window win = owner->internalWinId(); + if (owners[0] == win || owners[1] == win) + doIt = false; + } + + XFree(owners); + } + } + + if (doIt) { + if (waiting_for_data) { + pending_selection_changed = true; + if (! pending_timer_id) + pending_timer_id = QApplication::clipboard()->startTimer(0); + doIt = false; + } else { + selectionData()->clear(); + } + } + + return doIt; +} + + +bool qt_check_clipboard_sentinel() +{ + bool doIt = true; + if (owner && !X11->use_xfixes) { + unsigned char *retval; + Atom actualType; + int actualFormat; + unsigned long nitems, bytesLeft; + + if (XGetWindowProperty(X11->display, + QApplication::desktop()->screen(0)->internalWinId(), + ATOM(_QT_CLIPBOARD_SENTINEL), 0, 2, False, XA_WINDOW, + &actualType, &actualFormat, &nitems, &bytesLeft, + &retval) == Success) { + Window *owners = (Window *)retval; + if (actualType == XA_WINDOW && actualFormat == 32 && nitems == 2) { + Window win = owner->internalWinId(); + if (owners[0] == win || owners[1] == win) + doIt = false; + } + + XFree(owners); + } + } + + if (doIt) { + if (waiting_for_data) { + pending_clipboard_changed = true; + if (! pending_timer_id) + pending_timer_id = QApplication::clipboard()->startTimer(0); + doIt = false; + } else { + clipboardData()->clear(); + } + } + + return doIt; +} + +bool qt_xfixes_selection_changed(Window selectionOwner, Time timestamp) +{ + QClipboardData *d = selectionData(); +#ifdef QCLIPBOARD_DEBUG + DEBUG("qt_xfixes_selection_changed: owner = %u; selectionOwner = %u; internal timestamp = %u; external timestamp = %u", + (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)selectionOwner, + (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp); +#endif + if (!owner || (selectionOwner && selectionOwner != owner->internalWinId()) || + (!selectionOwner && (d->timestamp == CurrentTime || d->timestamp < timestamp))) + return qt_check_selection_sentinel(); + return false; +} + +bool qt_xfixes_clipboard_changed(Window clipboardOwner, Time timestamp) +{ + QClipboardData *d = clipboardData(); +#ifdef QCLIPBOARD_DEBUG + DEBUG("qt_xfixes_clipboard_changed: owner = %u; clipboardOwner = %u; internal timestamp = %u; external timestamp = %u", + (unsigned int)(owner ? (int)owner->internalWinId() : 0), (unsigned int)clipboardOwner, + (unsigned int)(d ? d->timestamp : 0), (unsigned int)timestamp); +#endif + if (!owner || (clipboardOwner && clipboardOwner != owner->internalWinId()) || + (!clipboardOwner && (d->timestamp == CurrentTime || d->timestamp < timestamp))) + return qt_check_clipboard_sentinel(); + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_CLIPBOARD diff --git a/src/gui/kernel/qcocoaapplication_mac.mm b/src/gui/kernel/qcocoaapplication_mac.mm new file mode 100644 index 0000000000..872f31dec7 --- /dev/null +++ b/src/gui/kernel/qcocoaapplication_mac.mm @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include +#ifdef QT_MAC_USE_COCOA +#include +#include +#include +#include + +QT_USE_NAMESPACE + +@implementation NSApplication (QT_MANGLE_NAMESPACE(QApplicationIntegration)) + +- (void)QT_MANGLE_NAMESPACE(qt_setDockMenu):(NSMenu *)newMenu +{ + [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] setDockMenu:newMenu]; +} + +- (QApplicationPrivate *)QT_MANGLE_NAMESPACE(qt_qappPrivate) +{ + return [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] qAppPrivate]; +} + +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader) +{ + return [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] menuLoader]; +} + +- (int)QT_MANGLE_NAMESPACE(qt_validModesForFontPanel):(NSFontPanel *)fontPanel +{ + Q_UNUSED(fontPanel); + // only display those things that QFont can handle + return NSFontPanelFaceModeMask + | NSFontPanelSizeModeMask + | NSFontPanelCollectionModeMask + | NSFontPanelUnderlineEffectModeMask + | NSFontPanelStrikethroughEffectModeMask; +} + +- (void)qt_sendPostedMessage:(NSEvent *)event +{ + // WARNING: data1 and data2 is truncated to from 64-bit to 32-bit on OS 10.5! + // That is why we need to split the address in two parts: + quint64 lower = [event data1]; + quint64 upper = [event data2]; + QCocoaPostMessageArgs *args = reinterpret_cast(lower | (upper << 32)); + // Special case for convenience: if the argument is an NSNumber, we unbox it directly. + // Use NSValue instead if this behaviour is unwanted. + id a1 = ([args->arg1 isKindOfClass:[NSNumber class]]) ? (id)[args->arg1 intValue] : args->arg1; + id a2 = ([args->arg2 isKindOfClass:[NSNumber class]]) ? (id)[args->arg2 intValue] : args->arg2; + switch (args->argCount) { + case 0: + [args->target performSelector:args->selector]; + break; + case 1: + [args->target performSelector:args->selector withObject:a1]; + break; + case 3: + [args->target performSelector:args->selector withObject:a1 withObject:a2]; + break; + } + + delete args; +} + +- (BOOL)qt_filterEvent:(NSEvent *)event +{ + if (qApp->macEventFilter(0, reinterpret_cast(event))) + return true; + + if ([event type] == NSApplicationDefined) { + switch ([event subtype]) { + case QtCocoaEventSubTypePostMessage: + [NSApp qt_sendPostedMessage:event]; + return true; + default: + break; + } + } + return false; +} + +@end + +@implementation QNSApplication + +- (void)qt_sendEvent_original:(NSEvent *)event +{ + Q_UNUSED(event); + // This method will only be used as a signature + // template for the method we add into NSApplication + // containing the original [NSApplication sendEvent:] implementation +} + +- (void)qt_sendEvent_replacement:(NSEvent *)event +{ + // This method (or its implementation to be precise) will + // be called instead of sendEvent if redirection occurs. + // 'self' will then be an instance of NSApplication + // (and not QNSApplication) + if (![NSApp qt_filterEvent:event]) + [self qt_sendEvent_original:event]; +} + +- (void)sendEvent:(NSEvent *)event +{ + // This method will be called if + // no redirection occurs + if (![NSApp qt_filterEvent:event]) + [super sendEvent:event]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + // Forward actions sendt from the menu bar (e.g. quit) to the menu loader. + // Having this method here means that we are the last stop in the responder + // chain, and that we are able to handle menu actions even when no window is + // visible on screen. Note: If Qt is used as a plugin, Qt will not use a + // native menu bar. Hence, we will also not need to do any redirection etc. as + // we do with sendEvent. + [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent() +{ + if ([NSApp isMemberOfClass:[QNSApplication class]]) { + // No need to change implementation since Qt + // already controls a subclass of NSApplication + return; + } + + // Change the implementation of [NSApplication sendEvent] to the + // implementation of qt_sendEvent_replacement found in QNSApplication. + // And keep the old implementation that gets overwritten inside a new + // method 'qt_sendEvent_original' that we add to NSApplication + qt_cocoa_change_implementation( + [NSApplication class], + @selector(sendEvent:), + [QNSApplication class], + @selector(qt_sendEvent_replacement:), + @selector(qt_sendEvent_original:)); + } + +QT_END_NAMESPACE +#endif diff --git a/src/gui/kernel/qcocoaapplication_mac_p.h b/src/gui/kernel/qcocoaapplication_mac_p.h new file mode 100644 index 0000000000..0c3f5e442d --- /dev/null +++ b/src/gui/kernel/qcocoaapplication_mac_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +/* + Cocoa Application Categories +*/ +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) +@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); + +@interface NSApplication (QT_MANGLE_NAMESPACE(QApplicationIntegration)) +- (void)QT_MANGLE_NAMESPACE(qt_setDockMenu):(NSMenu *)newMenu; +- (QApplicationPrivate *)QT_MANGLE_NAMESPACE(qt_qappPrivate); +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader); +- (int)QT_MANGLE_NAMESPACE(qt_validModesForFontPanel):(NSFontPanel *)fontPanel; + +- (void)qt_sendPostedMessage:(NSEvent *)event; +- (BOOL)qt_filterEvent:(NSEvent *)event; +@end + +@interface QNSApplication : NSApplication { +} +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent(); + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/kernel/qcocoaapplicationdelegate_mac.mm b/src/gui/kernel/qcocoaapplicationdelegate_mac.mm new file mode 100644 index 0000000000..77cd8902c3 --- /dev/null +++ b/src/gui/kernel/qcocoaapplicationdelegate_mac.mm @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** + ** + ** Copyright (c) 2007-2008, Apple, Inc. + ** + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are met: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions in binary form must reproduce the above copyright notice, + ** this list of conditions and the following disclaimer in the documentation + ** and/or other materials provided with the distribution. + ** + ** * Neither the name of Apple, Inc. nor the names of its contributors + ** may be used to endorse or promote products derived from this software + ** without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ****************************************************************************/ + +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA + +#import +#import +#import +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +extern void onApplicationChangedActivation(bool); // qapplication_mac.mm +extern void qt_release_apple_event_handler(); //qapplication_mac.mm +extern QPointer qt_last_mouse_receiver; // qapplication_mac.cpp +extern QPointer qt_last_native_mouse_receiver; // qt_cocoa_helpers_mac.mm +extern QPointer qt_button_down; // qapplication_mac.cpp + +QT_END_NAMESPACE + +QT_FORWARD_DECLARE_CLASS(QDesktopWidgetImplementation) +QT_USE_NAMESPACE + +static QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *sharedCocoaApplicationDelegate = nil; + +static void cleanupCocoaApplicationDelegate() +{ + [sharedCocoaApplicationDelegate release]; +} + +@implementation QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) + +- (id)init +{ + self = [super init]; + if (self) + inLaunch = true; + return self; +} + +- (void)dealloc +{ + sharedCocoaApplicationDelegate = nil; + [dockMenu release]; + [qtMenuLoader release]; + if (reflectionDelegate) { + [NSApp setDelegate:reflectionDelegate]; + [reflectionDelegate release]; + } + [super dealloc]; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + if (sharedCocoaApplicationDelegate == nil) { + sharedCocoaApplicationDelegate = [super allocWithZone:zone]; + return sharedCocoaApplicationDelegate; + qAddPostRoutine(cleanupCocoaApplicationDelegate); + } + } + return nil; +} + ++ (QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate)*)sharedDelegate +{ + @synchronized(self) { + if (sharedCocoaApplicationDelegate == nil) + [[self alloc] init]; + } + return [[sharedCocoaApplicationDelegate retain] autorelease]; +} + +- (void)setDockMenu:(NSMenu*)newMenu +{ + [newMenu retain]; + [dockMenu release]; + dockMenu = newMenu; +} + +- (NSMenu *)applicationDockMenu +{ + return [[dockMenu retain] autorelease]; +} + +- (QApplicationPrivate *)qAppPrivate +{ + return qtPrivate; +} + +- (void)setQtPrivate:(QApplicationPrivate *)value +{ + qtPrivate = value; +} + +- (void)setMenuLoader:(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader +{ + [menuLoader retain]; + [qtMenuLoader release]; + qtMenuLoader = menuLoader; +} + +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader +{ + return [[qtMenuLoader retain] autorelease]; +} + +// This function will only be called when NSApp is actually running. Before +// that, the kAEQuitApplication Apple event will be sent to +// QApplicationPrivate::globalAppleEventProcessor in qapplication_mac.mm +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + Q_UNUSED(sender); + // The reflection delegate gets precedence + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationShouldTerminate:)]) { + return [reflectionDelegate applicationShouldTerminate:sender]; + } + + if (qtPrivate->canQuit()) { + if (!startedQuit) { + startedQuit = true; + qAppInstance()->quit(); + startedQuit = false; + } + } + + if (qtPrivate->threadData->eventLoops.size() == 0) { + // INVARIANT: No event loop is executing. This probably + // means that Qt is used as a plugin, or as a part of a native + // Cocoa application. In any case it should be fine to + // terminate now: + return NSTerminateNow; + } + + return NSTerminateCancel; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + Q_UNUSED(aNotification); + inLaunch = false; + qt_release_apple_event_handler(); +} + +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames +{ + for (NSString *fileName in filenames) { + QString qtFileName = qt_mac_NSStringToQString(fileName); + if (inLaunch) { + // We need to be careful because Cocoa will be nice enough to take + // command line arguments and send them to us as events. Given the history + // of Qt Applications, this will result in behavior people don't want, as + // they might be doing the opening themselves with the command line parsing. + if (qApp->arguments().contains(qtFileName)) + continue; + } + QFileOpenEvent foe(qtFileName); + qt_sendSpontaneousEvent(qAppInstance(), &foe); + } + + if (reflectionDelegate && + [reflectionDelegate respondsToSelector:@selector(application:openFiles:)]) + [reflectionDelegate application:sender openFiles:filenames]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + // If we have a reflection delegate, that will get to call the shots. + if (reflectionDelegate + && [reflectionDelegate respondsToSelector: + @selector(applicationShouldTerminateAfterLastWindowClosed:)]) + return [reflectionDelegate applicationShouldTerminateAfterLastWindowClosed:sender]; + return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together. +} + + +- (void)applicationDidBecomeActive:(NSNotification *)notification +{ + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidBecomeActive:)]) + [reflectionDelegate applicationDidBecomeActive:notification]; + + onApplicationChangedActivation(true); + + if (!QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, 0); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification +{ + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidResignActive:)]) + [reflectionDelegate applicationDidResignActive:notification]; + + onApplicationChangedActivation(false); + + if (!QWidget::mouseGrabber()) + QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver); + qt_last_mouse_receiver = 0; + qt_last_native_mouse_receiver = 0; + qt_button_down = 0; +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification +{ + Q_UNUSED(notification); + QDesktopWidgetImplementation::instance()->onResize(); +} + +- (void)setReflectionDelegate:(NSObject *)oldDelegate +{ + [oldDelegate retain]; + [reflectionDelegate release]; + reflectionDelegate = oldDelegate; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + NSMethodSignature *result = [super methodSignatureForSelector:aSelector]; + if (!result && reflectionDelegate) { + result = [reflectionDelegate methodSignatureForSelector:aSelector]; + } + return result; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + BOOL result = [super respondsToSelector:aSelector]; + if (!result && reflectionDelegate) + result = [reflectionDelegate respondsToSelector:aSelector]; + return result; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + SEL invocationSelector = [invocation selector]; + if (reflectionDelegate && [reflectionDelegate respondsToSelector:invocationSelector]) + [invocation invokeWithTarget:reflectionDelegate]; + else + [self doesNotRecognizeSelector:invocationSelector]; +} + +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(replyEvent); + + NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + QUrl url(qt_mac_NSStringToQString(urlString)); + QFileOpenEvent qtEvent(url); + qt_sendSpontaneousEvent(qAppInstance(), &qtEvent); +} + +- (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(event); + Q_UNUSED(replyEvent); + [NSApp terminate:self]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end +#endif diff --git a/src/gui/kernel/qcocoaapplicationdelegate_mac_p.h b/src/gui/kernel/qcocoaapplicationdelegate_mac_p.h new file mode 100644 index 0000000000..714c046f48 --- /dev/null +++ b/src/gui/kernel/qcocoaapplicationdelegate_mac_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/**************************************************************************** + ** + ** Copyright (c) 2007-2008, Apple, Inc. + ** + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are met: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions in binary form must reproduce the above copyright notice, + ** this list of conditions and the following disclaimer in the documentation + ** and/or other materials provided with the distribution. + ** + ** * Neither the name of Apple, Inc. nor the names of its contributors + ** may be used to endorse or promote products derived from this software + ** without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + + +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import + +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate); + +@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + +@protocol NSApplicationDelegate +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames; +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender; +- (void)applicationDidBecomeActive:(NSNotification *)notification; +- (void)applicationDidResignActive:(NSNotification *)notification; +@end + +#endif + +@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject { + bool startedQuit; + QApplicationPrivate *qtPrivate; + NSMenu *dockMenu; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader; + NSObject *reflectionDelegate; + bool inLaunch; +} ++ (QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate)*)sharedDelegate; +- (void)setDockMenu:(NSMenu *)newMenu; +- (void)setQtPrivate:(QApplicationPrivate *)value; +- (QApplicationPrivate *)qAppPrivate; +- (void)setMenuLoader:(QT_MANGLE_NAMESPACE(QCocoaMenuLoader)*)menuLoader; +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader; +- (void)setReflectionDelegate:(NSObject *)oldDelegate; +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end +#endif diff --git a/src/gui/kernel/qcocoaintrospection_mac.mm b/src/gui/kernel/qcocoaintrospection_mac.mm new file mode 100644 index 0000000000..70c893aeec --- /dev/null +++ b/src/gui/kernel/qcocoaintrospection_mac.mm @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include + +QT_BEGIN_NAMESPACE + +void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel, SEL backupSel) +{ +#ifndef QT_MAC_USE_COCOA + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) +#endif + { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // The following code replaces the _implementation_ for the selector we want to hack + // (originalSel) with the implementation found in proxyClass. Then it creates + // a new 'backup' method inside baseClass containing the old, original, + // implementation (fakeSel). You can let the proxy implementation of originalSel + // call fakeSel if needed (similar approach to calling a super class implementation). + // fakeSel must also be implemented in proxyClass, as the signature is used + // as template for the method one we add into baseClass. + // NB: You will typically never create any instances of proxyClass; we use it + // only for stealing its contents and put it into baseClass. + if (!replacementSel) + replacementSel = originalSel; + + Method originalMethod = class_getInstanceMethod(baseClass, originalSel); + Method replacementMethod = class_getInstanceMethod(proxyClass, replacementSel); + IMP originalImp = method_setImplementation(originalMethod, method_getImplementation(replacementMethod)); + + if (backupSel) { + Method backupMethod = class_getInstanceMethod(proxyClass, backupSel); + class_addMethod(baseClass, backupSel, originalImp, method_getTypeEncoding(backupMethod)); + } +#endif + } +} + +void qt_cocoa_change_back_implementation(Class baseClass, SEL originalSel, SEL backupSel) +{ +#ifndef QT_MAC_USE_COCOA + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) +#endif + { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + Method originalMethod = class_getInstanceMethod(baseClass, originalSel); + Method backupMethodInBaseClass = class_getInstanceMethod(baseClass, backupSel); + method_setImplementation(originalMethod, method_getImplementation(backupMethodInBaseClass)); +#endif + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qcocoaintrospection_p.h b/src/gui/kernel/qcocoaintrospection_p.h new file mode 100644 index 0000000000..1c7d6ac13c --- /dev/null +++ b/src/gui/kernel/qcocoaintrospection_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include +#import + +QT_BEGIN_NAMESPACE + +void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel = 0, SEL backupSel = 0); +void qt_cocoa_change_back_implementation(Class baseClass, SEL originalSel, SEL backupSel); + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qcocoamenuloader_mac.mm b/src/gui/kernel/qcocoamenuloader_mac.mm new file mode 100644 index 0000000000..71ff011069 --- /dev/null +++ b/src/gui/kernel/qcocoamenuloader_mac.mm @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#include +#include +#include +#include +#include +#include +#include +#include + +QT_FORWARD_DECLARE_CLASS(QCFString) +QT_FORWARD_DECLARE_CLASS(QString) + +#ifndef QT_NO_TRANSLATION + QT_BEGIN_NAMESPACE + extern QString qt_mac_applicationmenu_string(int type); + QT_END_NAMESPACE +#endif + +QT_USE_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QCocoaMenuLoader) + +- (void)awakeFromNib +{ + servicesItem = [[appMenu itemWithTitle:@"Services"] retain]; + hideAllOthersItem = [[appMenu itemWithTitle:@"Hide Others"] retain]; + showAllItem = [[appMenu itemWithTitle:@"Show All"] retain]; + + // Get the names in the nib to match the app name set by Qt. + const NSString *appName = reinterpret_cast(QCFString::toCFStringRef(qAppName())); + [quitItem setTitle:[[quitItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast(appName)]]; + [hideItem setTitle:[[hideItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast(appName)]]; + [aboutItem setTitle:[[aboutItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast(appName)]]; + [appName release]; + // Disable the items that don't do anything. If someone associates a QAction with them + // They should get synced back in. + [preferencesItem setEnabled:NO]; + [preferencesItem setHidden:YES]; + [aboutItem setEnabled:NO]; + [aboutItem setHidden:YES]; +} + +- (void)ensureAppMenuInMenu:(NSMenu *)menu +{ + // The application menu is the menu in the menu bar that contains the + // 'Quit' item. When changing menu bar (e.g when switching between + // windows with different menu bars), we never recreate this menu, but + // instead pull it out the current menu bar and place into the new one: + NSMenu *mainMenu = [NSApp mainMenu]; + if ([NSApp mainMenu] == menu) + return; // nothing to do (menu is the current menu bar)! + +#ifndef QT_NAMESPACE + Q_ASSERT(mainMenu); +#endif + // Grab the app menu out of the current menu. + int numItems = [mainMenu numberOfItems]; + NSMenuItem *oldAppMenuItem = 0; + for (int i = 0; i < numItems; ++i) { + NSMenuItem *item = [mainMenu itemAtIndex:i]; + if ([item submenu] == appMenu) { + oldAppMenuItem = item; + [oldAppMenuItem retain]; + [mainMenu removeItemAtIndex:i]; + break; + } + } + + if (oldAppMenuItem) { + [oldAppMenuItem setSubmenu:nil]; + [oldAppMenuItem release]; + NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" + action:nil keyEquivalent:@""]; + [appMenuItem setSubmenu:appMenu]; + [menu insertItem:appMenuItem atIndex:0]; + } +} + +- (void)removeActionsFromAppMenu +{ + for (NSMenuItem *item in [appMenu itemArray]) + [item setTag:nil]; +} + +- (void)dealloc +{ + [servicesItem release]; + [hideAllOthersItem release]; + [showAllItem release]; + + [lastAppSpecificItem release]; + [theMenu release]; + [appMenu release]; + [super dealloc]; +} + +- (NSMenu *)menu +{ + return [[theMenu retain] autorelease]; +} + +- (NSMenu *)applicationMenu +{ + return [[appMenu retain] autorelease]; +} + +- (NSMenuItem *)quitMenuItem +{ + return [[quitItem retain] autorelease]; +} + +- (NSMenuItem *)preferencesMenuItem +{ + return [[preferencesItem retain] autorelease]; +} + +- (NSMenuItem *)aboutMenuItem +{ + return [[aboutItem retain] autorelease]; +} + +- (NSMenuItem *)aboutQtMenuItem +{ + return [[aboutQtItem retain] autorelease]; +} + +- (NSMenuItem *)hideMenuItem +{ + return [[hideItem retain] autorelease]; +} + +- (NSMenuItem *)appSpecificMenuItem +{ + // Create an App-Specific menu item, insert it into the menu and return + // it as an autorelease item. + NSMenuItem *item = [[NSMenuItem alloc] init]; + + NSInteger location; + if (lastAppSpecificItem == nil) { + location = [appMenu indexOfItem:aboutQtItem]; + } else { + location = [appMenu indexOfItem:lastAppSpecificItem]; + [lastAppSpecificItem release]; + } + lastAppSpecificItem = item; // Keep track of this for later (i.e., don't release it) + [appMenu insertItem:item atIndex:location + 1]; + + return [[item retain] autorelease]; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (void)terminate:(id)sender +{ + [NSApp terminate:sender]; +} + +- (void)orderFrontStandardAboutPanel:(id)sender +{ + [NSApp orderFrontStandardAboutPanel:sender]; +} + +- (void)hideOtherApplications:(id)sender +{ + [NSApp hideOtherApplications:sender]; +} + +- (void)unhideAllApplications:(id)sender +{ + [NSApp unhideAllApplications:sender]; +} + +- (void)hide:(id)sender +{ + [NSApp hide:sender]; +} + +- (void)qtUpdateMenubar +{ + QMenuBarPrivate::macUpdateMenuBarImmediatly(); +} + +- (void)qtTranslateApplicationMenu +{ +#ifndef QT_NO_TRANSLATION + [servicesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(0))]; + [hideItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(1).arg(qAppName()))]; + [hideAllOthersItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(2))]; + [showAllItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(3))]; + [preferencesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(4))]; + [quitItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(5).arg(qAppName()))]; + [aboutItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(6).arg(qAppName()))]; +#endif +} + +- (IBAction)qtDispatcherToQAction:(id)sender +{ + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + NSMenuItem *item = static_cast(sender); + if (QAction *action = reinterpret_cast([item tag])) { + action->trigger(); + } else if (item == quitItem) { + // We got here because someone was once the quitItem, but it has been + // abandoned (e.g., the menubar was deleted). In the meantime, just do + // normal QApplication::quit(). + qApp->quit(); + } +} + + - (void)orderFrontCharacterPalette:(id)sender + { + [NSApp orderFrontCharacterPalette:sender]; + } +@end +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/kernel/qcocoamenuloader_mac_p.h b/src/gui/kernel/qcocoamenuloader_mac_p.h new file mode 100644 index 0000000000..cfcc7e00c6 --- /dev/null +++ b/src/gui/kernel/qcocoamenuloader_mac_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOAMENULOADER_P_H +#define QCOCOAMENULOADER_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 "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import + +@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSResponder +{ + IBOutlet NSMenu *theMenu; + IBOutlet NSMenu *appMenu; + IBOutlet NSMenuItem *quitItem; + IBOutlet NSMenuItem *preferencesItem; + IBOutlet NSMenuItem *aboutItem; + IBOutlet NSMenuItem *aboutQtItem; + IBOutlet NSMenuItem *hideItem; + NSMenuItem *lastAppSpecificItem; + NSMenuItem *servicesItem; + NSMenuItem *hideAllOthersItem; + NSMenuItem *showAllItem; +} +- (void)ensureAppMenuInMenu:(NSMenu *)menu; +- (void)removeActionsFromAppMenu; +- (NSMenu *)applicationMenu; +- (NSMenu *)menu; +- (NSMenuItem *)quitMenuItem; +- (NSMenuItem *)preferencesMenuItem; +- (NSMenuItem *)aboutMenuItem; +- (NSMenuItem *)aboutQtMenuItem; +- (NSMenuItem *)hideMenuItem; +- (NSMenuItem *)appSpecificMenuItem; +- (IBAction)terminate:(id)sender; +- (IBAction)orderFrontStandardAboutPanel:(id)sender; +- (IBAction)hideOtherApplications:(id)sender; +- (IBAction)unhideAllApplications:(id)sender; +- (IBAction)hide:(id)sender; +- (IBAction)qtDispatcherToQAction:(id)sender; +- (void)qtUpdateMenubar; +- (void)orderFrontCharacterPalette:(id)sender; +@end + +#endif // QT_MAC_USE_COCOA +#endif // QCOCOAMENULOADER_P_H diff --git a/src/gui/kernel/qcocoapanel_mac.mm b/src/gui/kernel/qcocoapanel_mac.mm new file mode 100644 index 0000000000..67a12e25f8 --- /dev/null +++ b/src/gui/kernel/qcocoapanel_mac.mm @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import +#ifdef QT_MAC_USE_COCOA +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#include + +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_USE_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QCocoaPanel) + +/*********************************************************************** + Copy and Paste between QCocoaWindow and QCocoaPanel + This is a bit unfortunate, but thanks to the dynamic dispatch we + have to duplicate this code or resort to really silly forwarding methods +**************************************************************************/ +#include "qcocoasharedwindowmethods_mac_p.h" + +@end +#endif diff --git a/src/gui/kernel/qcocoapanel_mac_p.h b/src/gui/kernel/qcocoapanel_mac_p.h new file mode 100644 index 0000000000..542615903e --- /dev/null +++ b/src/gui/kernel/qcocoapanel_mac_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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. +// + +#ifndef QCOCOAPANEL_MAC_P +#define QCOCOAPANEL_MAC_P + +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import + +QT_FORWARD_DECLARE_CLASS(QStringList); +QT_FORWARD_DECLARE_CLASS(QCocoaDropData); + +@interface NSPanel (QtIntegration) +- (NSDragOperation)draggingEntered:(id )sender; +- (NSDragOperation)draggingUpdated:(id )sender; +- (void)draggingExited:(id )sender; +- (BOOL)performDragOperation:(id )sender; +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaPanel) : NSPanel { + QStringList *currentCustomDragTypes; + QCocoaDropData *dropData; + NSInteger dragEnterSequence; +} + ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask; +- (void)registerDragTypes; +- (void)drawRectOriginal:(NSRect)rect; + +@end +#endif + +#endif diff --git a/src/gui/kernel/qcocoasharedwindowmethods_mac_p.h b/src/gui/kernel/qcocoasharedwindowmethods_mac_p.h new file mode 100644 index 0000000000..ee1115bd4e --- /dev/null +++ b/src/gui/kernel/qcocoasharedwindowmethods_mac_p.h @@ -0,0 +1,610 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** + NB: This is not a header file, dispite the file name suffix. This file is + included directly into the source code of qcocoawindow_mac.mm and + qcocoapanel_mac.mm to avoid manually doing copy and paste of the exact + same code needed at both places. This solution makes it more difficult + to e.g fix a bug in qcocoawindow_mac.mm, but forget to do the same in + qcocoapanel_mac.mm. + The reason we need to do copy and paste in the first place, rather than + resolve to method overriding, is that QCocoaPanel needs to inherit from + NSPanel, while QCocoaWindow needs to inherit NSWindow rather than NSPanel). +****************************************************************************/ + +// WARNING: Don't include any header files from within this file. Put them +// directly into qcocoawindow_mac_p.h and qcocoapanel_mac_p.h + +QT_BEGIN_NAMESPACE +extern Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); // qcocoaview.mm +extern QPointer qt_button_down; //qapplication_mac.cpp +extern const QStringList& qEnabledDraggedTypes(); // qmime_mac.cpp +extern void qt_event_request_window_change(QWidget *); // qapplication_mac.mm +extern void qt_mac_send_posted_gl_updates(QWidget *widget); // qapplication_mac.mm + +Q_GLOBAL_STATIC(QPointer, currentDragTarget); +QT_END_NAMESPACE + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)deferCreation +{ + self = [super initWithContentRect:contentRect styleMask:windowStyle + backing:bufferingType defer:deferCreation]; + if (self) { + currentCustomDragTypes = 0; + } + return self; +} + +- (void)dealloc +{ + delete currentCustomDragTypes; + [super dealloc]; +} + +- (BOOL)canBecomeKeyWindow +{ + QWidget *widget = [self QT_MANGLE_NAMESPACE(qt_qwidget)]; + if (!widget) + return NO; // This should happen only for qt_root_win + if (QApplicationPrivate::isBlockedByModal(widget)) + return NO; + + bool isToolTip = (widget->windowType() == Qt::ToolTip); + bool isPopup = (widget->windowType() == Qt::Popup); + return !(isPopup || isToolTip); +} + +- (BOOL)canBecomeMainWindow +{ + QWidget *widget = [self QT_MANGLE_NAMESPACE(qt_qwidget)]; + if (!widget) + return NO; // This should happen only for qt_root_win + if ([self isSheet]) + return NO; + + bool isToolTip = (widget->windowType() == Qt::ToolTip); + bool isPopup = (widget->windowType() == Qt::Popup); + bool isTool = (widget->windowType() == Qt::Tool); + return !(isPopup || isToolTip || isTool); +} + +- (void)becomeMainWindow +{ + [super becomeMainWindow]; + // Cocoa sometimes tell a hidden window to become the + // main window (and as such, show it). This can e.g + // happend when the application gets activated. If + // this is the case, we tell it to hide again: + if (![self isVisible]) + [self orderOut:self]; +} + +- (void)toggleToolbarShown:(id)sender +{ + macSendToolbarChangeEvent([self QT_MANGLE_NAMESPACE(qt_qwidget)]); + [super toggleToolbarShown:sender]; +} + +- (void)flagsChanged:(NSEvent *)theEvent +{ + qt_dispatchModifiersChanged(theEvent, [self QT_MANGLE_NAMESPACE(qt_qwidget)]); + [super flagsChanged:theEvent]; +} + + +- (void)tabletProximity:(NSEvent *)tabletEvent +{ + qt_dispatchTabletProximityEvent(tabletEvent); +} + +- (void)terminate:(id)sender +{ + // This function is called from the quit item in the menubar when this window + // is in the first responder chain (see also qtDispatcherToQAction above) + [NSApp terminate:sender]; +} + +- (void)setLevel:(NSInteger)windowLevel +{ + // Cocoa will upon activating/deactivating applications level modal + // windows up and down, regardsless of any explicit set window level. + // To ensure that modal stays-on-top dialogs actually stays on top after + // the application is activated (and therefore stacks in front of + // other stays-on-top windows), we need to add this little special-case override: + QWidget *widget = [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] qt_qwidgetForWindow:self]; + if (widget && widget->isModal() && (widget->windowFlags() & Qt::WindowStaysOnTopHint)) + [super setLevel:NSPopUpMenuWindowLevel]; + else + [super setLevel:windowLevel]; +} + +- (void)sendEvent:(NSEvent *)event +{ + [self retain]; + + bool handled = false; + switch([event type]) { + case NSMouseMoved: + // Cocoa sends move events to a parent and all its children under the mouse, much + // like Qt handles hover events. But we only want to handle the move event once, so + // to optimize a bit (since we subscribe for move event for all views), we handle it + // here before this logic happends. Note: it might be tempting to do this shortcut for + // all mouse events. The problem is that Cocoa does more than just find the correct view + // when sending the event, like raising windows etc. So avoid it as much as possible: + handled = qt_mac_handleMouseEvent(event, QEvent::MouseMove, Qt::NoButton, 0); + break; + default: + break; + } + + if (!handled) { + [super sendEvent:event]; + qt_mac_handleNonClientAreaMouseEvent(self, event); + } + [self release]; +} + +- (void)setInitialFirstResponder:(NSView *)view +{ + // This method is called the first time the window is placed on screen and + // is the earliest point in time we can connect OpenGL contexts to NSViews. + QWidget *qwidget = [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] qt_qwidgetForWindow:self]; + if (qwidget) { + qt_event_request_window_change(qwidget); + qt_mac_send_posted_gl_updates(qwidget); + } + + [super setInitialFirstResponder:view]; +} + +- (BOOL)makeFirstResponder:(NSResponder *)responder +{ + // For some reason Cocoa wants to flip the first responder + // when Qt doesn't want to, sorry, but "No" :-) + if (responder == nil && qApp->focusWidget()) + return NO; + return [super makeFirstResponder:responder]; +} + ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask +{ + if (styleMask & QtMacCustomizeWindow) + return [QT_MANGLE_NAMESPACE(QCocoaWindowCustomThemeFrame) class]; + return [super frameViewClassForStyleMask:styleMask]; +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +- (void)touchesBeganWithEvent:(NSEvent *)event; +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetTouch = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, 0, &widgetToGetTouch); + if (!widgetToGetTouch) + return; + + bool all = widgetToGetTouch->testAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + qt_translateRawTouchEvent(widgetToGetTouch, QTouchEvent::TouchPad, QCocoaTouch::getCurrentTouchPointList(event, all)); +} + +- (void)touchesMovedWithEvent:(NSEvent *)event; +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetTouch = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, 0, &widgetToGetTouch); + if (!widgetToGetTouch) + return; + + bool all = widgetToGetTouch->testAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + qt_translateRawTouchEvent(widgetToGetTouch, QTouchEvent::TouchPad, QCocoaTouch::getCurrentTouchPointList(event, all)); +} + +- (void)touchesEndedWithEvent:(NSEvent *)event; +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetTouch = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, 0, &widgetToGetTouch); + if (!widgetToGetTouch) + return; + + bool all = widgetToGetTouch->testAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + qt_translateRawTouchEvent(widgetToGetTouch, QTouchEvent::TouchPad, QCocoaTouch::getCurrentTouchPointList(event, all)); +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event; +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetTouch = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, 0, &widgetToGetTouch); + if (!widgetToGetTouch) + return; + + bool all = widgetToGetTouch->testAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + qt_translateRawTouchEvent(widgetToGetTouch, QTouchEvent::TouchPad, QCocoaTouch::getCurrentTouchPointList(event, all)); +} +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +-(void)registerDragTypes +{ + // Calling registerForDraggedTypes below is slow, so only do + // it once for each window, or when the custom types change. + QMacCocoaAutoReleasePool pool; + const QStringList& customTypes = qEnabledDraggedTypes(); + if (currentCustomDragTypes == 0 || *currentCustomDragTypes != customTypes) { + if (currentCustomDragTypes == 0) + currentCustomDragTypes = new QStringList(); + *currentCustomDragTypes = customTypes; + const NSString* mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; + NSMutableArray *supportedTypes = [NSMutableArray arrayWithObjects:NSColorPboardType, + NSFilenamesPboardType, NSStringPboardType, + NSFilenamesPboardType, NSPostScriptPboardType, NSTIFFPboardType, + NSRTFPboardType, NSTabularTextPboardType, NSFontPboardType, + NSRulerPboardType, NSFileContentsPboardType, NSColorPboardType, + NSRTFDPboardType, NSHTMLPboardType, NSPICTPboardType, + NSURLPboardType, NSPDFPboardType, NSVCardPboardType, + NSFilesPromisePboardType, NSInkTextPboardType, + NSMultipleTextSelectionPboardType, mimeTypeGeneric, nil]; + // Add custom types supported by the application. + for (int i = 0; i < customTypes.size(); i++) { + [supportedTypes addObject:qt_mac_QStringToNSString(customTypes[i])]; + } + [self registerForDraggedTypes:supportedTypes]; + } +} + +- (void)removeDropData +{ + if (dropData) { + delete dropData; + dropData = 0; + } +} + +- (void)addDropData:(id )sender +{ + [self removeDropData]; + CFStringRef dropPasteboard = (CFStringRef) [[sender draggingPasteboard] name]; + dropData = new QCocoaDropData(dropPasteboard); +} + +- (void)changeDraggingCursor:(NSDragOperation)newOperation +{ + static SEL action = nil; + static bool operationSupported = false; + if (action == nil) { + action = NSSelectorFromString(@"operationNotAllowedCursor"); + if ([NSCursor respondsToSelector:action]) { + operationSupported = true; + } + } + if (operationSupported) { + NSCursor *notAllowedCursor = [NSCursor performSelector:action]; + bool isNotAllowedCursor = ([NSCursor currentCursor] == notAllowedCursor); + if (newOperation == NSDragOperationNone && !isNotAllowedCursor) { + [notAllowedCursor push]; + } else if (newOperation != NSDragOperationNone && isNotAllowedCursor) { + [notAllowedCursor pop]; + } + + } +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + // The user dragged something into the window. Send a draggingEntered message + // to the QWidget under the mouse. As the drag moves over the window, and over + // different widgets, we will handle enter and leave events from within + // draggingUpdated below. The reason why we handle this ourselves rather than + // subscribing for drag events directly in QCocoaView is that calling + // registerForDraggedTypes on the views will severly degrade initialization time + // for an application that uses a lot of drag subscribing widgets. + + NSPoint nswindowPoint = [sender draggingLocation]; + NSPoint nsglobalPoint = [[sender draggingDestinationWindow] convertBaseToScreen:nswindowPoint]; + QPoint globalPoint = flipPoint(nsglobalPoint).toPoint(); + + QWidget *qwidget = QApplication::widgetAt(globalPoint); + *currentDragTarget() = qwidget; + if (!qwidget) + return [super draggingEntered:sender]; + if (qwidget->testAttribute(Qt::WA_DropSiteRegistered) == false) + return NSDragOperationNone; + + [self addDropData:sender]; + + QMimeData *mimeData = dropData; + if (QDragManager::self()->source()) + mimeData = QDragManager::self()->dragPrivate()->data; + + NSDragOperation nsActions = [sender draggingSourceOperationMask]; + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(nsActions); + QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec.lastOperation) = nsActions; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + + if ([sender draggingSource] != nil) { + // modifier flags might have changed, update it here since we don't send any input events. + QApplicationPrivate::modifier_buttons = qt_cocoaModifiers2QtModifiers([[NSApp currentEvent] modifierFlags]); + modifiers = QApplication::keyboardModifiers(); + } else { + // when the source is from another application the above technique will not work. + modifiers = qt_cocoaDragOperation2QtModifiers(nsActions); + } + + // send the drag enter event to the widget. + QPoint localPoint(qwidget->mapFromGlobal(globalPoint)); + QDragEnterEvent qDEEvent(localPoint, qtAllowed, mimeData, QApplication::mouseButtons(), modifiers); + QApplication::sendEvent(qwidget, &qDEEvent); + + if (!qDEEvent.isAccepted()) { + // The enter event was not accepted. We mark this by removing + // the drop data so we don't send subsequent drag move events: + [self removeDropData]; + [self changeDraggingCursor:NSDragOperationNone]; + return NSDragOperationNone; + } else { + // Send a drag move event immediately after a drag enter event (as per documentation). + QDragMoveEvent qDMEvent(localPoint, qtAllowed, mimeData, QApplication::mouseButtons(), modifiers); + qDMEvent.setDropAction(qDEEvent.dropAction()); + qDMEvent.accept(); // accept by default, since enter event was accepted. + QApplication::sendEvent(qwidget, &qDMEvent); + + if (!qDMEvent.isAccepted() || qDMEvent.dropAction() == Qt::IgnoreAction) { + // Since we accepted the drag enter event, the widget expects + // future drage move events. + nsActions = NSDragOperationNone; + // Save as ignored in the answer rect. + qDMEvent.setDropAction(Qt::IgnoreAction); + } else { + nsActions = QT_PREPEND_NAMESPACE(qt_mac_mapDropAction)(qDMEvent.dropAction()); + } + + QT_PREPEND_NAMESPACE(qt_mac_copy_answer_rect)(qDMEvent); + [self changeDraggingCursor:nsActions]; + return nsActions; + } + } + +- (NSDragOperation)draggingUpdated:(id )sender +{ + NSPoint nswindowPoint = [sender draggingLocation]; + NSPoint nsglobalPoint = [[sender draggingDestinationWindow] convertBaseToScreen:nswindowPoint]; + QPoint globalPoint = flipPoint(nsglobalPoint).toPoint(); + + QWidget *qwidget = QApplication::widgetAt(globalPoint); + if (!qwidget) + return [super draggingEntered:sender]; + + // First, check if the widget under the mouse has changed since the + // last drag move events. If so, we need to change target, and dispatch + // syntetic drag enter/leave events: + if (qwidget != *currentDragTarget()) { + if (*currentDragTarget() && dropData) { + QDragLeaveEvent de; + QApplication::sendEvent(*currentDragTarget(), &de); + [self removeDropData]; + } + return [self draggingEntered:sender]; + } + + if (qwidget->testAttribute(Qt::WA_DropSiteRegistered) == false) + return NSDragOperationNone; + + // If we have no drop data (which will be assigned inside draggingEntered), it means + // that the current drag target did not accept the enter event. If so, we ignore + // subsequent move events as well: + if (dropData == 0) { + [self changeDraggingCursor:NSDragOperationNone]; + return NSDragOperationNone; + } + + // If the mouse is still within the accepted rect (provided by + // the application on a previous event), we follow the optimization + // and just return the answer given at that point: + NSDragOperation nsActions = [sender draggingSourceOperationMask]; + QPoint localPoint(qwidget->mapFromGlobal(globalPoint)); + if (qt_mac_mouse_inside_answer_rect(localPoint) + && QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec.lastOperation) == nsActions) { + NSDragOperation operation = QT_PREPEND_NAMESPACE(qt_mac_mapDropActions)(QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec.lastAction)); + [self changeDraggingCursor:operation]; + return operation; + } + + QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec.lastOperation) = nsActions; + Qt::DropActions qtAllowed = QT_PREPEND_NAMESPACE(qt_mac_mapNSDragOperations)(nsActions); + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + + // Update modifiers: + if ([sender draggingSource] != nil) { + QApplicationPrivate::modifier_buttons = qt_cocoaModifiers2QtModifiers([[NSApp currentEvent] modifierFlags]); + modifiers = QApplication::keyboardModifiers(); + } else { + modifiers = qt_cocoaDragOperation2QtModifiers(nsActions); + } + + QMimeData *mimeData = dropData; + if (QDragManager::self()->source()) + mimeData = QDragManager::self()->dragPrivate()->data; + + // Insert the same drop action on the event according to + // what the application told us it should be on the previous event: + QDragMoveEvent qDMEvent(localPoint, qtAllowed, mimeData, QApplication::mouseButtons(), modifiers); + if (QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec).lastAction != Qt::IgnoreAction + && QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec).buttons == qDMEvent.mouseButtons() + && QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec).modifiers == qDMEvent.keyboardModifiers()) + qDMEvent.setDropAction(QT_PREPEND_NAMESPACE(qt_mac_dnd_answer_rec).lastAction); + + // Now, end the drag move event to the widget: + qDMEvent.accept(); + QApplication::sendEvent(qwidget, &qDMEvent); + + NSDragOperation operation = qt_mac_mapDropAction(qDMEvent.dropAction()); + if (!qDMEvent.isAccepted() || qDMEvent.dropAction() == Qt::IgnoreAction) { + // Ignore this event (we will still receive further + // notifications), save as ignored in the answer rect: + operation = NSDragOperationNone; + qDMEvent.setDropAction(Qt::IgnoreAction); + } + + qt_mac_copy_answer_rect(qDMEvent); + [self changeDraggingCursor:operation]; + + return operation; +} + +- (void)draggingExited:(id )sender +{ + NSPoint nswindowPoint = [sender draggingLocation]; + NSPoint nsglobalPoint = [[sender draggingDestinationWindow] convertBaseToScreen:nswindowPoint]; + QPoint globalPoint = flipPoint(nsglobalPoint).toPoint(); + + QWidget *qwidget = *currentDragTarget(); + if (!qwidget) + return [super draggingExited:sender]; + + if (dropData) { + QDragLeaveEvent de; + QApplication::sendEvent(qwidget, &de); + [self removeDropData]; + } + + // Clean-up: + [self removeDropData]; + *currentDragTarget() = 0; + [self changeDraggingCursor:NSDragOperationEvery]; +} + +- (BOOL)performDragOperation:(id )sender +{ + QWidget *qwidget = *currentDragTarget(); + if (!qwidget) + return NO; + + *currentDragTarget() = 0; + NSPoint nswindowPoint = [sender draggingLocation]; + NSPoint nsglobalPoint = [[sender draggingDestinationWindow] convertBaseToScreen:nswindowPoint]; + QPoint globalPoint = flipPoint(nsglobalPoint).toPoint(); + + [self addDropData:sender]; + + NSDragOperation nsActions = [sender draggingSourceOperationMask]; + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(nsActions); + QMimeData *mimeData = dropData; + + if (QDragManager::self()->source()) + mimeData = QDragManager::self()->dragPrivate()->data; + if (QDragManager::self()->object) + QDragManager::self()->dragPrivate()->target = qwidget; + + QPoint localPoint(qwidget->mapFromGlobal(globalPoint)); + QDropEvent de(localPoint, qtAllowed, mimeData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(qwidget, &de); + + if (QDragManager::self()->object) + QDragManager::self()->dragPrivate()->executed_action = de.dropAction(); + + return de.isAccepted(); +} + +// This is a hack and it should be removed once we find the real cause for +// the painting problems. +// We have a static variable that signals if we have been called before or not. +static bool firstDrawingInvocation = true; + +// The method below exists only as a workaround to draw/not draw the baseline +// in the title bar. This is to support unifiedToolbar look. + +// This method is very special. To begin with, it is a +// method that will get called only if we enable documentMode. +// Furthermore, it won't get called as a normal method, we swap +// this method with the normal implementation of drawRect in +// _NSThemeFrame. When this method is active, its mission is to +// first call the original drawRect implementation so the widget +// gets proper painting. After that, it needs to detect if there +// is a toolbar or not, in order to decide how to handle the unified +// look. The distinction is important since the presence and +// visibility of a toolbar change the way we enter into unified mode. +// When there is a toolbar and that toolbar is visible, the problem +// is as simple as to tell the toolbar not to draw its baseline. +// However when there is not toolbar or the toolbar is not visible, +// we need to draw a line on top of the baseline, because the baseline +// in that case will belong to the title. For this case we need to draw +// a line on top of the baseline. +// As usual, there is a special case. When we first are called, we might +// need to repaint ourselves one more time. We only need that if we +// didn't get the activation, i.e. when we are launched via the command +// line. And this only if the toolbar is visible from the beginning, +// so we have a special flag that signals if we need to repaint or not. +- (void)drawRectSpecial:(NSRect)rect +{ + // Call the original drawing method. + [id(self) drawRectOriginal:rect]; + NSWindow *window = [id(self) window]; + NSToolbar *toolbar = [window toolbar]; + if(!toolbar) { + // There is no toolbar, we have to draw a line on top of the line drawn by Cocoa. + macDrawRectOnTop((void *)window); + } else { + if([toolbar isVisible]) { + // We tell Cocoa to avoid drawing the line at the end. + if(firstDrawingInvocation) { + firstDrawingInvocation = false; + macSyncDrawingOnFirstInvocation((void *)window); + } else + [toolbar setShowsBaselineSeparator:NO]; + } else { + // There is a toolbar but it is not visible so + // we have to draw a line on top of the line drawn by Cocoa. + macDrawRectOnTop((void *)window); + } + } +} + +- (void)drawRectOriginal:(NSRect)rect +{ + Q_UNUSED(rect) + // This method implementation is here to silenct the compiler. + // See drawRectSpecial for information. +} + diff --git a/src/gui/kernel/qcocoaview_mac.mm b/src/gui/kernel/qcocoaview_mac.mm new file mode 100644 index 0000000000..b5e5d186b8 --- /dev/null +++ b/src/gui/kernel/qcocoaview_mac.mm @@ -0,0 +1,1389 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import +#ifdef QT_MAC_USE_COCOA + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) + - (CGFloat)deviceDeltaX; + - (CGFloat)deviceDeltaY; + - (CGFloat)deviceDeltaZ; +@end + +@interface NSEvent (Qt_Compile_Leopard_Gestures) + - (CGFloat)magnification; +@end + +QT_BEGIN_NAMESPACE + +extern void qt_mac_update_cursor(); // qcursor_mac.mm +extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); // qapplication.cpp +extern QPointer qt_last_mouse_receiver; // qapplication_mac.cpp +extern QPointer qt_last_native_mouse_receiver; // qt_cocoa_helpers_mac.mm +extern OSViewRef qt_mac_nativeview_for(const QWidget *w); // qwidget_mac.mm +extern OSViewRef qt_mac_effectiveview_for(const QWidget *w); // qwidget_mac.mm +extern QPointer qt_button_down; //qapplication_mac.cpp +extern Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); +extern QWidget *mac_mouse_grabber; +extern bool qt_mac_clearDirtyOnWidgetInsideDrawWidget; // qwidget.cpp + +static QColor colorFrom(NSColor *color) +{ + QColor qtColor; + NSString *colorSpace = [color colorSpaceName]; + if (colorSpace == NSDeviceCMYKColorSpace) { + CGFloat cyan, magenta, yellow, black, alpha; + [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha]; + qtColor.setCmykF(cyan, magenta, yellow, black, alpha); + } else { + NSColor *tmpColor; + tmpColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + CGFloat red, green, blue, alpha; + [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; + qtColor.setRgbF(red, green, blue, alpha); + } + return qtColor; +} + +QT_END_NAMESPACE + +QT_FORWARD_DECLARE_CLASS(QMacCocoaAutoReleasePool) +QT_FORWARD_DECLARE_CLASS(QCFString) +QT_FORWARD_DECLARE_CLASS(QDragManager) +QT_FORWARD_DECLARE_CLASS(QMimeData) +QT_FORWARD_DECLARE_CLASS(QPoint) +QT_FORWARD_DECLARE_CLASS(QApplication) +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) +QT_FORWARD_DECLARE_CLASS(QDragEnterEvent) +QT_FORWARD_DECLARE_CLASS(QDragMoveEvent) +QT_FORWARD_DECLARE_CLASS(QStringList) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QRect) +QT_FORWARD_DECLARE_CLASS(QRegion) +QT_FORWARD_DECLARE_CLASS(QAbstractScrollArea) +QT_FORWARD_DECLARE_CLASS(QAbstractScrollAreaPrivate) +QT_FORWARD_DECLARE_CLASS(QPaintEvent) +QT_FORWARD_DECLARE_CLASS(QPainter) +QT_FORWARD_DECLARE_CLASS(QHoverEvent) +QT_FORWARD_DECLARE_CLASS(QCursor) +QT_USE_NAMESPACE +extern "C" { + extern NSString *NSTextInputReplacementRangeAttributeName; +} + +//#define ALIEN_DEBUG 1 +#ifdef ALIEN_DEBUG +static int qCocoaViewCount = 0; +#endif + +@implementation QT_MANGLE_NAMESPACE(QCocoaView) + +- (id)initWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate +{ + self = [super init]; + if (self) { + [self finishInitWithQWidget:widget widgetPrivate:widgetprivate]; + } + [self setFocusRingType:NSFocusRingTypeNone]; + composingText = new QString(); + +#ifdef ALIEN_DEBUG + ++qCocoaViewCount; + qDebug() << "Alien: create native view for" << widget << ". qCocoaViewCount is:" << qCocoaViewCount; +#endif + + composing = false; + sendKeyEvents = true; + fromKeyDownEvent = false; + alienTouchCount = 0; + + [self setHidden:YES]; + return self; +} + +- (void) finishInitWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate +{ + qwidget = widget; + qwidgetprivate = widgetprivate; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(frameDidChange:) + name:@"NSViewFrameDidChangeNotification" + object:self]; +} + +- (void)dealloc +{ + QMacCocoaAutoReleasePool pool; + delete composingText; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + +#ifdef ALIEN_DEBUG + --qCocoaViewCount; + qDebug() << "Alien: widget deallocated. qCocoaViewCount is:" << qCocoaViewCount; +#endif + + [super dealloc]; +} + +- (BOOL)isOpaque +{ + if (!qwidgetprivate) + return [super isOpaque]; + return qwidgetprivate->isOpaque; +} + +- (BOOL)isFlipped +{ + return YES; +} + +// We preserve the content of the view if WA_StaticContents is defined. +// +// More info in the Cocoa documentation: +// http://developer.apple.com/mac/library/documentation/cocoa/conceptual/CocoaViewsGuide/Optimizing/Optimizing.html +- (BOOL) preservesContentDuringLiveResize +{ + return qwidget->testAttribute(Qt::WA_StaticContents); +} + +- (void) setFrameSize:(NSSize)newSize +{ + [super setFrameSize:newSize]; + + // A change in size has required the view to be invalidated. + if ([self inLiveResize]) { + NSRect rects[4]; + NSInteger count; + [self getRectsExposedDuringLiveResize:rects count:&count]; + while (count-- > 0) + { + [self setNeedsDisplayInRect:rects[count]]; + } + } else { + [self setNeedsDisplay:YES]; + } + + // Make sure the opengl context is updated on resize. + if (qwidgetprivate && qwidgetprivate->isGLWidget && [self window]) { + qwidgetprivate->needWindowChange = true; + QEvent event(QEvent::MacGLWindowChange); + qApp->sendEvent(qwidget, &event); + } +} + +// We catch the 'setNeedsDisplay:' message in order to avoid a useless full repaint. +// During the resize, the top of the widget is repainted, probably because of the +// change of coordinate space (Quartz vs Qt). This is then followed by this message: +// -[NSView _setNeedsDisplayIfTopLeftChanged] +// which force a full repaint by sending the message 'setNeedsDisplay:'. +// That is what we are preventing here. +- (void)setNeedsDisplay:(BOOL)flag { + if (![self inLiveResize] || !(qwidget->testAttribute(Qt::WA_StaticContents))) { + [super setNeedsDisplay:flag]; + } +} + +- (void)drawRect:(NSRect)aRect +{ + if (!qwidget) + return; + + // Getting context. + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + qt_mac_retain_graphics_context(context); + + // We use a different graphics system. + // + // Widgets that are set to paint on screen, specifically QGLWidget, + // requires the native engine to execute in order to be drawn. + if (QApplicationPrivate::graphicsSystem() != 0 && !qwidget->testAttribute(Qt::WA_PaintOnScreen)) { + + // Raster engine. + if (QApplicationPrivate::graphics_system_name == QLatin1String("raster")) { + + if (!qwidgetprivate->isInUnifiedToolbar) { + + // Qt handles the painting occuring inside the window. + // Cocoa also keeps track of all widgets as NSView and therefore might + // ask for a repainting of a widget even if Qt is already taking care of it. + // + // The only valid reason for Cocoa to call drawRect: is for window manipulation + // (ie. resize, ...). + // + // Qt will then forward the update to the children. + if (!qwidget->isWindow()) { + qt_mac_release_graphics_context(context); + return; + } + + QRasterWindowSurface *winSurface = dynamic_cast(qwidget->windowSurface()); + if (!winSurface || !winSurface->needsFlush) { + qt_mac_release_graphics_context(context); + return; + } + + // Clip to region. + const QVector &rects = winSurface->regionToFlush.rects(); + for (int i = 0; i < rects.size(); ++i) { + const QRect &rect = rects.at(i); + CGContextAddRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height())); + } + CGContextClip(context); + + QRect r = winSurface->regionToFlush.boundingRect(); + const CGRect area = CGRectMake(r.x(), r.y(), r.width(), r.height()); + + qt_mac_draw_image(context, winSurface->imageContext(), area, area); + + winSurface->needsFlush = false; + winSurface->regionToFlush = QRegion(); + + } else { + + QUnifiedToolbarSurface *unifiedSurface = qwidgetprivate->unifiedSurface; + if (!unifiedSurface) { + qt_mac_release_graphics_context(context); + return; + } + + int areaX = qwidgetprivate->toolbar_offset.x(); + int areaY = qwidgetprivate->toolbar_offset.y(); + int areaWidth = qwidget->geometry().width(); + int areaHeight = qwidget->geometry().height(); + const CGRect area = CGRectMake(areaX, areaY, areaWidth, areaHeight); + const CGRect drawingArea = CGRectMake(0, 0, areaWidth, areaHeight); + + qt_mac_draw_image(context, unifiedSurface->imageContext(), area, drawingArea); + + qwidgetprivate->flushRequested = false; + + } + + CGContextFlush(context); + qt_mac_release_graphics_context(context); + return; + } + + // Qt handles the painting occuring inside the window. + // Cocoa also keeps track of all widgets as NSView and therefore might + // ask for a repainting of a widget even if Qt is already taking care of it. + // + // The only valid reason for Cocoa to call drawRect: is for window manipulation + // (ie. resize, ...). + // + // Qt will then forward the update to the children. + if (qwidget->isWindow()) { + qwidget->update(qwidget->rect()); + qwidgetprivate->syncBackingStore(qwidget->rect()); + } + } + + // Native engine. + qwidgetprivate->hd = context; + + if (qwidget->isVisible() && qwidget->updatesEnabled()) { //process the actual paint event. + if (qwidget->testAttribute(Qt::WA_WState_InPaintEvent)) + qWarning("QWidget::repaint: Recursive repaint detected"); + + const QRect qrect = QRect(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height); + QRegion qrgn; + + const NSRect *rects; + NSInteger count; + [self getRectsBeingDrawn:&rects count:&count]; + for (int i = 0; i < count; ++i) { + QRect tmpRect = QRect(rects[i].origin.x, rects[i].origin.y, rects[i].size.width, rects[i].size.height); + qrgn += tmpRect; + } + + if (!qwidget->isWindow() && !qobject_cast(qwidget->parent())) { + const QRegion &parentMask = qwidget->window()->mask(); + if (!parentMask.isEmpty()) { + const QPoint mappedPoint = qwidget->mapTo(qwidget->window(), qrect.topLeft()); + qrgn.translate(mappedPoint); + qrgn &= parentMask; + qrgn.translate(-mappedPoint.x(), -mappedPoint.y()); + } + } + + QPoint redirectionOffset(0, 0); + //setup the context + qwidget->setAttribute(Qt::WA_WState_InPaintEvent); + QPaintEngine *engine = qwidget->paintEngine(); + if (engine) + engine->setSystemClip(qrgn); + if (qwidgetprivate->extra && qwidgetprivate->extra->hasMask) { + CGRect widgetRect = CGRectMake(0, 0, qwidget->width(), qwidget->height()); + CGContextTranslateCTM (context, 0, widgetRect.size.height); + CGContextScaleCTM(context, 1, -1); + if (qwidget->isWindow()) + CGContextClearRect(context, widgetRect); + CGContextClipToMask(context, widgetRect, qwidgetprivate->extra->imageMask); + CGContextScaleCTM(context, 1, -1); + CGContextTranslateCTM (context, 0, -widgetRect.size.height); + } + + if (qwidget->isWindow() && !qwidgetprivate->isOpaque + && !qwidget->testAttribute(Qt::WA_MacBrushedMetal)) { + CGContextClearRect(context, NSRectToCGRect(aRect)); + } + + qwidget->setAttribute(Qt::WA_WState_InPaintEvent, false); + QWidgetPrivate *qwidgetPrivate = qt_widget_private(qwidget); + + // We specify that we want to draw the widget itself, and + // all its children recursive. But we skip native children, because + // they will receive drawRect calls by themselves as needed: + int flags = QWidgetPrivate::DrawPaintOnScreen + | QWidgetPrivate::DrawRecursive + | QWidgetPrivate::DontDrawNativeChildren; + + if (qwidget->isWindow()) + flags |= QWidgetPrivate::DrawAsRoot; + + // Start to draw: + qt_mac_clearDirtyOnWidgetInsideDrawWidget = true; + qwidgetPrivate->drawWidget(qwidget, qrgn, QPoint(), flags, 0); + qt_mac_clearDirtyOnWidgetInsideDrawWidget = false; + + if (!redirectionOffset.isNull()) + QPainter::restoreRedirected(qwidget); + if (engine) + engine->setSystemClip(QRegion()); + qwidget->setAttribute(Qt::WA_WState_InPaintEvent, false); + if(!qwidget->testAttribute(Qt::WA_PaintOutsidePaintEvent) && qwidget->paintingActive()) + qWarning("QWidget: It is dangerous to leave painters active on a" + " widget outside of the PaintEvent"); + } + qwidgetprivate->hd = 0; + qt_mac_release_graphics_context(context); +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + // Find the widget that should receive the event: + QPoint qlocal, qglobal; + QWidget *widgetToGetMouse = qt_mac_getTargetForMouseEvent(theEvent, QEvent::MouseButtonPress, qlocal, qglobal, qwidget, 0); + if (!widgetToGetMouse) + return NO; + + return !widgetToGetMouse->testAttribute(Qt::WA_MacNoClickThrough); +} + +- (NSView *)hitTest:(NSPoint)aPoint +{ + if (!qwidget) + return [super hitTest:aPoint]; + + if (qwidget->testAttribute(Qt::WA_TransparentForMouseEvents)) + return nil; // You cannot hit a transparent for mouse event widget. + return [super hitTest:aPoint]; +} + +- (void)updateTrackingAreas +{ + if (!qwidget) + return; + + // [NSView addTrackingArea] is slow, so bail out early if we can: + if (NSIsEmptyRect([self visibleRect])) + return; + + QMacCocoaAutoReleasePool pool; + if (NSArray *trackingArray = [self trackingAreas]) { + NSUInteger size = [trackingArray count]; + for (NSUInteger i = 0; i < size; ++i) { + NSTrackingArea *t = [trackingArray objectAtIndex:i]; + [self removeTrackingArea:t]; + } + } + + // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should + // only be turned on if mouseTracking, hover is on or a tool tip is set. + // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to + // turn it on in ALL case. That means EVERY QCocoaView gets to pay the cost of + // mouse moves delivered to it (Apple recommends keeping it OFF because there + // is a performance hit). So it goes. + NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp + | NSTrackingInVisibleRect | NSTrackingMouseMoved; + NSTrackingArea *ta = [[NSTrackingArea alloc] initWithRect:NSMakeRect(0, 0, + qwidget->width(), + qwidget->height()) + options:trackingOptions + owner:self + userInfo:nil]; + [self addTrackingArea:ta]; + [ta release]; +} + +- (void)mouseEntered:(NSEvent *)event +{ + // Cocoa will not send a move event on mouseEnter. But since + // Qt expect this, we fake one now. See also mouseExited below + // for info about enter/leave event handling + NSEvent *nsmoveEvent = [NSEvent + mouseEventWithType:NSMouseMoved + location:[[self window] mouseLocationOutsideOfEventStream] + modifierFlags: [event modifierFlags] + timestamp: [event timestamp] + windowNumber: [event windowNumber] + context: [event context] + eventNumber: [event eventNumber] + clickCount: 0 + pressure: 0]; + + // Important: Cocoa sends us mouseEnter on all views under the mouse + // and not just the one on top. Therefore, to we cannot use qwidget + // as native widget for this case. Instead, we let qt_mac_handleMouseEvent + // resolve it (last argument set to 0): + qt_mac_handleMouseEvent(nsmoveEvent, QEvent::MouseMove, Qt::NoButton, 0); +} + +- (void)mouseExited:(NSEvent *)event +{ + // Note: normal enter/leave handling is done from within mouseMove. This handler + // catches the case when the mouse moves out of the window (which mouseMove do not). + // Updating the mouse cursor follows the same logic as enter/leave. And we update + // neither if a grab exists (even if the grab points to this widget, it seems, ref X11) + Q_UNUSED(event); + if (self == [[self window] contentView] && !qt_button_down && !QWidget::mouseGrabber()) { + qt_mac_update_cursor(); + // If the mouse exits the content view, but qt_mac_getTargetForMouseEvent still + // reports a target, it means that either there is a grab involved, or the mouse + // hovered over another window in the application. In both cases, move events will + // cause qt_mac_handleMouseEvent to be called, which will handle enter/leave. + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Leave, qlocal, qglobal, qwidget, &widgetUnderMouse); + + if (widgetUnderMouse == 0) { + QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver); + qt_last_mouse_receiver = 0; + qt_last_native_mouse_receiver = 0; + } + } +} + +- (void)flagsChanged:(NSEvent *)theEvent +{ + QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget); + if (!widgetToGetKey) + return; + + qt_dispatchModifiersChanged(theEvent, widgetToGetKey); + [super flagsChanged:theEvent]; +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + // Important: this method will only be called when the view's window is _not_ inside + // QCocoaWindow/QCocoaPanel. Otherwise, [QCocoaWindow sendEvent] will handle the event + // before it ends up here. So, this method is added for supporting QMacNativeWidget. + // TODO: Cocoa send move events to all views under the mouse. So make sure we only + // handle the event for the widget on top when using QMacNativeWidget. + qt_mac_handleMouseEvent(theEvent, QEvent::MouseMove, Qt::NoButton, qwidget); +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonPress, Qt::LeftButton, qwidget); + // Don't call super here. This prevents us from getting the mouseUp event, + // which we need to send even if the mouseDown event was not accepted. + // (this is standard Qt behavior.) +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonRelease, Qt::LeftButton, qwidget); +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonPress, Qt::RightButton, qwidget); +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonRelease, Qt::RightButton, qwidget); +} + +- (void)otherMouseDown:(NSEvent *)theEvent +{ + Qt::MouseButton mouseButton = cocoaButton2QtButton([theEvent buttonNumber]); + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonPress, mouseButton, qwidget); +} + +- (void)otherMouseUp:(NSEvent *)theEvent +{ + Qt::MouseButton mouseButton = cocoaButton2QtButton([theEvent buttonNumber]); + qt_mac_handleMouseEvent(theEvent, QEvent::MouseButtonRelease, mouseButton, qwidget); +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseMove, Qt::NoButton, qwidget); +} + +- (void)rightMouseDragged:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseMove, Qt::NoButton, qwidget); +} + +- (void)otherMouseDragged:(NSEvent *)theEvent +{ + qt_mac_handleMouseEvent(theEvent, QEvent::MouseMove, Qt::NoButton, qwidget); +} + +- (void)scrollWheel:(NSEvent *)theEvent +{ + // Give the Input Manager a chance to process the wheel event. + NSInputManager *currentIManager = [NSInputManager currentInputManager]; + if (currentIManager && [currentIManager wantsToHandleMouseEvents]) { + [currentIManager handleMouseEvent:theEvent]; + } + + Qt::MouseButtons buttons = QApplication::mouseButtons(); + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([theEvent modifierFlags]); + + // Find the widget that should receive the event: + QPoint qlocal, qglobal; + QWidget *widgetToGetMouse = qt_mac_getTargetForMouseEvent(theEvent, QEvent::Wheel, qlocal, qglobal, qwidget, 0); + if (!widgetToGetMouse) + return; + + int deltaX = 0; + int deltaY = 0; + int deltaZ = 0; + + const EventRef carbonEvent = (EventRef)[theEvent eventRef]; + const UInt32 carbonEventKind = carbonEvent ? ::GetEventKind(carbonEvent) : 0; + const bool scrollEvent = carbonEventKind == kEventMouseScroll; + + if (scrollEvent) { + // The mouse device containts pixel scroll wheel support (Mighty Mouse, Trackpad). + // Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; // 8 * 1/4 + deltaX = [theEvent deviceDeltaX] * pixelsToDegrees; + deltaY = [theEvent deviceDeltaY] * pixelsToDegrees; + deltaZ = [theEvent deviceDeltaZ] * pixelsToDegrees; + } else { + // carbonEventKind == kEventMouseWheelMoved + // Remove acceleration, and use either -120 or 120 as delta: + deltaX = qBound(-120, int([theEvent deltaX] * 10000), 120); + deltaY = qBound(-120, int([theEvent deltaY] * 10000), 120); + deltaZ = qBound(-120, int([theEvent deltaZ] * 10000), 120); + } + +#ifndef QT_NO_WHEELEVENT + // ### Qt 5: Send one QWheelEvent with dx, dy and dz + + if (deltaX != 0 && deltaY != 0) + QMacScrollOptimization::initDelayedScroll(); + + if (deltaX != 0) { + QWheelEvent qwe(qlocal, qglobal, deltaX, buttons, keyMods, Qt::Horizontal); + qt_sendSpontaneousEvent(widgetToGetMouse, &qwe); + } + + if (deltaY != 0) { + QWheelEvent qwe(qlocal, qglobal, deltaY, buttons, keyMods, Qt::Vertical); + qt_sendSpontaneousEvent(widgetToGetMouse, &qwe); + } + + if (deltaZ != 0) { + // Qt doesn't explicitly support wheels with a Z component. In a misguided attempt to + // try to be ahead of the pack, I'm adding this extra value. + QWheelEvent qwe(qlocal, qglobal, deltaZ, buttons, keyMods, (Qt::Orientation)3); + qt_sendSpontaneousEvent(widgetToGetMouse, &qwe); + } + + if (deltaX != 0 && deltaY != 0) + QMacScrollOptimization::performDelayedScroll(); +#endif //QT_NO_WHEELEVENT +} + +- (void)tabletProximity:(NSEvent *)tabletEvent +{ + qt_dispatchTabletProximityEvent(tabletEvent); +} + +- (void)tabletPoint:(NSEvent *)tabletEvent +{ + if (!qt_mac_handleTabletEvent(self, tabletEvent)) + [super tabletPoint:tabletEvent]; +} + +- (void)magnifyWithEvent:(NSEvent *)event +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetGesture = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, qwidget, &widgetToGetGesture); + if (!widgetToGetGesture) + return; + if (!QApplicationPrivate::tryModalHelper(widgetToGetGesture, 0)) + return; + +#ifndef QT_NO_GESTURES + QNativeGestureEvent qNGEvent; + qNGEvent.gestureType = QNativeGestureEvent::Zoom; + NSPoint p = [[event window] convertBaseToScreen:[event locationInWindow]]; + qNGEvent.position = flipPoint(p).toPoint(); + qNGEvent.percentage = [event magnification]; + qt_sendSpontaneousEvent(widgetToGetGesture, &qNGEvent); +#endif // QT_NO_GESTURES +} + +- (void)rotateWithEvent:(NSEvent *)event +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetGesture = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, qwidget, &widgetToGetGesture); + if (!widgetToGetGesture) + return; + if (!QApplicationPrivate::tryModalHelper(widgetToGetGesture, 0)) + return; + +#ifndef QT_NO_GESTURES + QNativeGestureEvent qNGEvent; + qNGEvent.gestureType = QNativeGestureEvent::Rotate; + NSPoint p = [[event window] convertBaseToScreen:[event locationInWindow]]; + qNGEvent.position = flipPoint(p).toPoint(); + qNGEvent.percentage = -[event rotation]; + qt_sendSpontaneousEvent(widgetToGetGesture, &qNGEvent); +#endif // QT_NO_GESTURES +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetGesture = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, qwidget, &widgetToGetGesture); + if (!widgetToGetGesture) + return; + if (!QApplicationPrivate::tryModalHelper(widgetToGetGesture, 0)) + return; + +#ifndef QT_NO_GESTURES + QNativeGestureEvent qNGEvent; + qNGEvent.gestureType = QNativeGestureEvent::Swipe; + NSPoint p = [[event window] convertBaseToScreen:[event locationInWindow]]; + qNGEvent.position = flipPoint(p).toPoint(); + if ([event deltaX] == 1) + qNGEvent.angle = 180.0f; + else if ([event deltaX] == -1) + qNGEvent.angle = 0.0f; + else if ([event deltaY] == 1) + qNGEvent.angle = 90.0f; + else if ([event deltaY] == -1) + qNGEvent.angle = 270.0f; + qt_sendSpontaneousEvent(widgetToGetGesture, &qNGEvent); +#endif // QT_NO_GESTURES +} + +- (void)beginGestureWithEvent:(NSEvent *)event +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetGesture = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, qwidget, &widgetToGetGesture); + if (!widgetToGetGesture) + return; + if (!QApplicationPrivate::tryModalHelper(widgetToGetGesture, 0)) + return; + +#ifndef QT_NO_GESTURES + QNativeGestureEvent qNGEvent; + qNGEvent.gestureType = QNativeGestureEvent::GestureBegin; + NSPoint p = [[event window] convertBaseToScreen:[event locationInWindow]]; + qNGEvent.position = flipPoint(p).toPoint(); + qt_sendSpontaneousEvent(widgetToGetGesture, &qNGEvent); +#endif // QT_NO_GESTURES +} + +- (void)endGestureWithEvent:(NSEvent *)event +{ + QPoint qlocal, qglobal; + QWidget *widgetToGetGesture = 0; + qt_mac_getTargetForMouseEvent(event, QEvent::Gesture, qlocal, qglobal, qwidget, &widgetToGetGesture); + if (!widgetToGetGesture) + return; + if (!QApplicationPrivate::tryModalHelper(widgetToGetGesture, 0)) + return; + +#ifndef QT_NO_GESTURES + QNativeGestureEvent qNGEvent; + qNGEvent.gestureType = QNativeGestureEvent::GestureEnd; + NSPoint p = [[event window] convertBaseToScreen:[event locationInWindow]]; + qNGEvent.position = flipPoint(p).toPoint(); + qt_sendSpontaneousEvent(widgetToGetGesture, &qNGEvent); +} +#endif // QT_NO_GESTURES + +- (void)frameDidChange:(NSNotification *)note +{ + Q_UNUSED(note); + if (!qwidget) + return; + if (qwidget->isWindow()) + return; + NSRect newFrame = [self frame]; + QRect newGeo(newFrame.origin.x, newFrame.origin.y, newFrame.size.width, newFrame.size.height); + bool moved = qwidget->testAttribute(Qt::WA_Moved); + bool resized = qwidget->testAttribute(Qt::WA_Resized); + qwidget->setGeometry(newGeo); + qwidget->setAttribute(Qt::WA_Moved, moved); + qwidget->setAttribute(Qt::WA_Resized, resized); + qwidgetprivate->syncCocoaMask(); +} + +- (BOOL)isEnabled +{ + if (!qwidget) + return [super isEnabled]; + return [super isEnabled] && qwidget->isEnabled(); +} + +- (void)setEnabled:(BOOL)flag +{ + QMacCocoaAutoReleasePool pool; + [super setEnabled:flag]; + if (qwidget && qwidget->isEnabled() != flag) + qwidget->setEnabled(flag); +} + ++ (Class)cellClass +{ + return [NSActionCell class]; +} + +- (BOOL)acceptsFirstResponder +{ + if (!qwidget) + return NO; + + // Disabled widget shouldn't get focus even if it's a window. + // hence disabled windows will not get any key or mouse events. + if (!qwidget->isEnabled()) + return NO; + + if (qwidget->isWindow() && !qt_widget_private(qwidget)->topData()->embedded) { + QWidget *focusWidget = qApp->focusWidget(); + if (!focusWidget) { + // There is no focus widget, but we still want to receive key events + // for shortcut handling etc. So we accept first responer for the + // content view as a last resort: + return YES; + } + if (!focusWidget->internalWinId() && focusWidget->nativeParentWidget() == qwidget) { + // The current focus widget is alien, and hence, cannot get acceptsFirstResponder + // calls. Since the focus widget is a child of qwidget, we let this view say YES: + return YES; + } + if (focusWidget->window() != qwidget) { + // The current focus widget is in another window. Since cocoa + // suggest that this window should be key now, we accept: + return YES; + } + } + + return qwidget->focusPolicy() != Qt::NoFocus; +} + +- (BOOL)resignFirstResponder +{ + if (!qwidget) + return YES; + + // Seems like the following test only triggers if this + // view is inside a QMacNativeWidget: +// if (QWidget *fw = QApplication::focusWidget()) { +// if (qwidget == fw || qwidget == fw->nativeParentWidget()) +// fw->clearFocus(); +// } + return YES; +} + +- (BOOL)becomeFirstResponder +{ + // see the comment in the acceptsFirstResponder - if the window "stole" focus + // let it become the responder, but don't tell Qt + if (qwidget && qt_widget_private(qwidget->window())->topData()->embedded + && !QApplication::focusWidget() && qwidget->focusPolicy() != Qt::NoFocus) + qwidget->setFocus(Qt::OtherFocusReason); + return YES; +} + +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal +{ + Q_UNUSED(isLocal); + return supportedActions; +} + +- (void)setSupportedActions:(NSDragOperation)actions +{ + supportedActions = actions; +} + +- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation +{ + Q_UNUSED(anImage); + Q_UNUSED(aPoint); + macCurrentDnDParameters()->performedAction = operation; + if (QDragManager::self()->object + && QDragManager::self()->dragPrivate()->executed_action != Qt::ActionMask) { + macCurrentDnDParameters()->performedAction = + qt_mac_mapDropAction(QDragManager::self()->dragPrivate()->executed_action); + } +} + +- (QWidget *)qt_qwidget +{ + return qwidget; +} + +- (void) qt_clearQWidget +{ + qwidget = 0; + qwidgetprivate = 0; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + if (!qwidget) + return; + QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget); + if (!widgetToGetKey) + return; + + sendKeyEvents = true; + + if (widgetToGetKey->testAttribute(Qt::WA_InputMethodEnabled) + && !(widgetToGetKey->inputMethodHints() & Qt::ImhDigitsOnly + || widgetToGetKey->inputMethodHints() & Qt::ImhFormattedNumbersOnly + || widgetToGetKey->inputMethodHints() & Qt::ImhHiddenText)) { + fromKeyDownEvent = true; + [qt_mac_nativeview_for(qwidget) interpretKeyEvents:[NSArray arrayWithObject: theEvent]]; + fromKeyDownEvent = false; + } + + if (sendKeyEvents && !composing) { + bool keyEventEaten = qt_dispatchKeyEvent(theEvent, widgetToGetKey); + if (!keyEventEaten && qwidget) { + // The event is not yet eaten, and if Qt is embedded inside a native + // cocoa application, send it to first responder not owned by Qt. + // The exception is if widgetToGetKey was redirected to a popup. + QWidget *toplevel = qwidget->window(); + if (toplevel == widgetToGetKey->window()) { + if (qt_widget_private(toplevel)->topData()->embedded) { + if (NSResponder *w = [qt_mac_nativeview_for(toplevel) superview]) + [w keyDown:theEvent]; + } + } + } + } +} + + +- (void)keyUp:(NSEvent *)theEvent +{ + if (sendKeyEvents) { + QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget); + if (!widgetToGetKey) + return; + + bool keyEventEaten = qt_dispatchKeyEvent(theEvent, widgetToGetKey); + if (!keyEventEaten && qwidget) { + // The event is not yet eaten, and if Qt is embedded inside a native + // cocoa application, send it to first responder not owned by Qt. + // The exception is if widgetToGetKey was redirected to a popup. + QWidget *toplevel = qwidget->window(); + if (toplevel == widgetToGetKey->window()) { + if (qt_widget_private(toplevel)->topData()->embedded) { + if (NSResponder *w = [qt_mac_nativeview_for(toplevel) superview]) + [w keyUp:theEvent]; + } + } + } + } +} + +- (void)viewWillMoveToWindow:(NSWindow *)window +{ + if (qwidget == 0) + return; + + if (qwidget->windowFlags() & Qt::MSWindowsOwnDC + && (window != [self window])) { // OpenGL Widget + QEvent event(QEvent::MacGLClearDrawable); + qApp->sendEvent(qwidget, &event); + } +} + +- (void)viewDidMoveToWindow +{ + if (qwidget == 0) + return; + + if (qwidget->windowFlags() & Qt::MSWindowsOwnDC && [self window]) { + // call update paint event + qwidgetprivate->needWindowChange = true; + QEvent event(QEvent::MacGLWindowChange); + qApp->sendEvent(qwidget, &event); + } +} + + +// NSTextInput Protocol implementation + +- (void) insertText:(id)aString +{ + QString commitText; + if ([aString length]) { + if ([aString isKindOfClass:[NSAttributedString class]]) { + commitText = QCFString::toQString(reinterpret_cast([aString string])); + } else { + commitText = QCFString::toQString(reinterpret_cast(aString)); + }; + } + + // When entering characters through Character Viewer or Keyboard Viewer, the text is passed + // through this insertText method. Since we dont receive a keyDown Event in such cases, the + // composing flag will be false. + if (([aString length] && composing) || !fromKeyDownEvent) { + // Send the commit string to the widget. + composing = false; + sendKeyEvents = false; + QInputMethodEvent e; + e.setCommitString(commitText); + if (QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget)) + qt_sendSpontaneousEvent(widgetToGetKey, &e); + } else { + // The key sequence "`q" on a French Keyboard will generate two calls to insertText before + // it returns from interpretKeyEvents. The first call will turn off 'composing' and accept + // the "`" key. The last keyDown event needs to be processed by the widget to get the + // character "q". The string parameter is ignored for the second call. + sendKeyEvents = true; + } + + composingText->clear(); +} + +- (void) setMarkedText:(id)aString selectedRange:(NSRange)selRange +{ + // Generate the QInputMethodEvent with preedit string and the attributes + // for rendering it. The attributes handled here are 'underline', + // 'underline color' and 'cursor position'. + sendKeyEvents = false; + composing = true; + QString qtText; + // Cursor position is retrived from the range. + QList attrs; + attrs<([aString string])); + composingLength = qtText.length(); + int index = 0; + // Create attributes for individual sections of preedit text + while (index < composingLength) { + NSRange effectiveRange; + NSRange range = NSMakeRange(index, composingLength-index); + NSDictionary *attributes = [aString attributesAtIndex:index + longestEffectiveRange:&effectiveRange + inRange:range]; + NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; + if (underlineStyle) { + QColor clr (Qt::black); + NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; + if (color) { + clr = colorFrom(color); + } + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineColor(clr); + attrs<(aString)); + composingLength = qtText.length(); + } + // Make sure that we have at least one text format. + if (attrs.size() <= 1) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<clear(); + composing = false; +} + +- (BOOL) hasMarkedText +{ + return (composing ? YES: NO); +} + +- (void) doCommandBySelector:(SEL)aSelector +{ + Q_UNUSED(aSelector); +} + +- (BOOL)isComposing +{ + return composing; +} + +- (NSInteger) conversationIdentifier +{ + // Return a unique identifier fot this ime conversation + return (NSInteger)self; +} + +- (NSAttributedString *) attributedSubstringFromRange:(NSRange)theRange +{ + QString selectedText(qwidget->inputMethodQuery(Qt::ImCurrentSelection).toString()); + if (!selectedText.isEmpty()) { + QCFString string(selectedText.mid(theRange.location, theRange.length)); + const NSString *tmpString = reinterpret_cast((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast(tmpString)] autorelease]; + } else { + return nil; + } +} + +- (NSRange) markedRange +{ + NSRange range; + if (composing) { + range.location = 0; + range.length = composingLength; + } else { + range.location = NSNotFound; + range.length = 0; + } + return range; +} + +- (NSRange) selectedRange +{ + NSRange selRange; + QString selectedText(qwidget->inputMethodQuery(Qt::ImCurrentSelection).toString()); + if (!selectedText.isEmpty()) { + // Consider only the selected text. + selRange.location = 0; + selRange.length = selectedText.length(); + } else { + // No selected text. + selRange.location = NSNotFound; + selRange.length = 0; + } + return selRange; + +} + +- (NSRect) firstRectForCharacterRange:(NSRange)theRange +{ + Q_UNUSED(theRange); + // The returned rect is always based on the internal cursor. + QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget); + if (!widgetToGetKey) + return NSZeroRect; + + QRect mr(widgetToGetKey->inputMethodQuery(Qt::ImMicroFocus).toRect()); + QPoint mp(widgetToGetKey->mapToGlobal(QPoint(mr.bottomLeft()))); + NSRect rect ; + rect.origin.x = mp.x(); + rect.origin.y = flipYCoordinate(mp.y()); + rect.size.width = mr.width(); + rect.size.height = mr.height(); + return rect; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +{ + // We dont support cursor movements using mouse while composing. + Q_UNUSED(thePoint); + return NSNotFound; +} + +- (NSArray*) validAttributesForMarkedText +{ + QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget); + if (!widgetToGetKey) + return nil; + + if (!widgetToGetKey->testAttribute(Qt::WA_InputMethodEnabled)) + return nil; // Not sure if that's correct, but it's saves a malloc. + + // Support only underline color/style. + return [NSArray arrayWithObjects:NSUnderlineColorAttributeName, + NSUnderlineStyleAttributeName, nil]; +} +@end + +QT_BEGIN_NAMESPACE +void QMacInputContext::reset() +{ + QWidget *w = QInputContext::focusWidget(); + if (w) { + NSView *view = qt_mac_effectiveview_for(w); + if ([view isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaView) class]]) { + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaView) *qc = static_cast(view); + NSInputManager *currentIManager = [NSInputManager currentInputManager]; + if (currentIManager) { + [currentIManager markedTextAbandoned:view]; + [qc unmarkText]; + } + } + } +} + +bool QMacInputContext::isComposing() const +{ + QWidget *w = QInputContext::focusWidget(); + if (w) { + NSView *view = qt_mac_effectiveview_for(w); + if ([view isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaView) class]]) { + return [static_cast(view) isComposing]; + } + } + return false; +} + +extern bool qt_mac_in_drag; +void * /*NSImage */qt_mac_create_nsimage(const QPixmap &pm); +static const int default_pm_hotx = -2; +static const int default_pm_hoty = -16; +static const char* default_pm[] = { + "13 9 3 1", + ". c None", + " c #000000", + "X c #FFFFFF", + "X X X X X X X", + " X X X X X X ", + "X ......... X", + " X.........X ", + "X ......... X", + " X.........X ", + "X ......... X", + " X X X X X X ", + "X X X X X X X", +}; + +Qt::DropAction QDragManager::drag(QDrag *o) +{ + if(qt_mac_in_drag) { //just make sure.. + qWarning("Qt: Internal error: WH0A, unexpected condition reached"); + return Qt::IgnoreAction; + } + if(object == o) + return Qt::IgnoreAction; + /* At the moment it seems clear that Mac OS X does not want to drag with a non-left button + so we just bail early to prevent it */ + if(!(GetCurrentEventButtonState() & kEventMouseButtonPrimary)) + return Qt::IgnoreAction; + + if(object) { + dragPrivate()->source->removeEventFilter(this); + cancel(); + beingCancelled = false; + } + + object = o; + dragPrivate()->target = 0; + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart); +#endif + + // setup the data + QMacPasteboard dragBoard((CFStringRef) NSDragPboard, QMacPasteboardMime::MIME_DND); + dragPrivate()->data->setData(QLatin1String("application/x-qt-mime-type-name"), QByteArray("dummy")); + dragBoard.setMimeData(dragPrivate()->data); + + // create the image + QPoint hotspot; + QPixmap pix = dragPrivate()->pixmap; + if(pix.isNull()) { + if(dragPrivate()->data->hasText() || dragPrivate()->data->hasUrls()) { + // get the string + QString s = dragPrivate()->data->hasText() ? dragPrivate()->data->text() + : dragPrivate()->data->urls().first().toString(); + if(s.length() > 26) + s = s.left(23) + QChar(0x2026); + if(!s.isEmpty()) { + // draw it + QFont f(qApp->font()); + f.setPointSize(12); + QFontMetrics fm(f); + QPixmap tmp(fm.width(s), fm.height()); + if(!tmp.isNull()) { + QPainter p(&tmp); + p.fillRect(0, 0, tmp.width(), tmp.height(), Qt::color0); + p.setPen(Qt::color1); + p.setFont(f); + p.drawText(0, fm.ascent(), s); + // save it + pix = tmp; + hotspot = QPoint(tmp.width() / 2, tmp.height() / 2); + } + } + } else { + pix = QPixmap(default_pm); + hotspot = QPoint(default_pm_hotx, default_pm_hoty); + } + } else { + hotspot = dragPrivate()->hotspot; + } + + // Convert the image to NSImage: + NSImage *image = (NSImage *)qt_mac_create_nsimage(pix); + [image retain]; + + DnDParams *dndParams = macCurrentDnDParameters(); + QT_MANGLE_NAMESPACE(QCocoaView) *theView = static_cast(dndParams->view); + + // Save supported actions: + [theView setSupportedActions: qt_mac_mapDropActions(dragPrivate()->possible_actions)]; + QPoint pointInView = [theView qt_qwidget]->mapFromGlobal(dndParams->globalPoint); + NSPoint imageLoc = {pointInView.x() - hotspot.x(), pointInView.y() + pix.height() - hotspot.y()}; + NSSize mouseOffset = {0.0, 0.0}; + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + dragPrivate()->executed_action = Qt::ActionMask; + + // Execute the drag: + [theView retain]; + [theView dragImage:image + at:imageLoc + offset:mouseOffset + event:dndParams->theEvent + pasteboard:pboard + source:theView + slideBack:YES]; + + // Reset the implicit grab widget when drag ends because we will not + // receive the mouse release event when DND is active: + qt_button_down = 0; + [theView release]; + [image release]; + if (dragPrivate()) + dragPrivate()->executed_action = Qt::IgnoreAction; + object = 0; + Qt::DropAction performedAction(qt_mac_mapNSDragOperation(dndParams->performedAction)); + + // Do post drag processing, if required. + if (performedAction != Qt::IgnoreAction) { + // Check if the receiver points us to a file location. + // if so, we need to do the file copy/move ourselves. + QCFType pasteLocation = 0; + PasteboardCopyPasteLocation(dragBoard.pasteBoard(), &pasteLocation); + if (pasteLocation) { + QList urls = o->mimeData()->urls(); + for (int i = 0; i < urls.size(); ++i) { + QUrl fromUrl = urls.at(i); + QString filename = QFileInfo(fromUrl.path()).fileName(); + QUrl toUrl(QCFString::toQString(CFURLGetString(pasteLocation)) + filename); + if (performedAction == Qt::MoveAction) + QFile::rename(fromUrl.path(), toUrl.path()); + else if (performedAction == Qt::CopyAction) + QFile::copy(fromUrl.path(), toUrl.path()); + } + } + } + + // Clean-up: + o->setMimeData(0); + o->deleteLater(); + return performedAction; +} + +QT_END_NAMESPACE + +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/kernel/qcocoaview_mac_p.h b/src/gui/kernel/qcocoaview_mac_p.h new file mode 100644 index 0000000000..cc79b6705b --- /dev/null +++ b/src/gui/kernel/qcocoaview_mac_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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 +#ifdef QT_MAC_USE_COCOA +#import + +@class QT_MANGLE_NAMESPACE(QCocoaView); +QT_FORWARD_DECLARE_CLASS(QWidgetPrivate); +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_FORWARD_DECLARE_CLASS(QEvent); +QT_FORWARD_DECLARE_CLASS(QString); +QT_FORWARD_DECLARE_CLASS(QStringList); + +Q_GUI_EXPORT +@interface QT_MANGLE_NAMESPACE(QCocoaView) : NSControl { + QWidget *qwidget; + QWidgetPrivate *qwidgetprivate; + NSDragOperation supportedActions; + bool composing; + int composingLength; + bool sendKeyEvents; + bool fromKeyDownEvent; + QString *composingText; + @public int alienTouchCount; +} +- (id)initWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate; +- (void) finishInitWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate; +- (void)frameDidChange:(NSNotification *)note; +- (void)setSupportedActions:(NSDragOperation)actions; +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; +- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; +- (BOOL)isComposing; +- (QWidget *)qt_qwidget; +- (void) qt_clearQWidget; + +@end +#endif diff --git a/src/gui/kernel/qcocoawindow_mac.mm b/src/gui/kernel/qcocoawindow_mac.mm new file mode 100644 index 0000000000..6e5023aaca --- /dev/null +++ b/src/gui/kernel/qcocoawindow_mac.mm @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmacdefines_mac.h" +#ifdef QT_MAC_USE_COCOA +#import +#import +#import +#import +#import +#import +#import +#import + +#include + +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_USE_NAMESPACE + +@implementation NSWindow (QT_MANGLE_NAMESPACE(QWidgetIntegration)) + +- (id)QT_MANGLE_NAMESPACE(qt_initWithQWidget):(QWidget*)widget contentRect:(NSRect)rect styleMask:(NSUInteger)mask +{ + self = [self initWithContentRect:rect styleMask:mask backing:NSBackingStoreBuffered defer:YES]; + if (self) { + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] becomeDelegteForWindow:self widget:widget]; + [self setReleasedWhenClosed:NO]; + } + return self; +} + +- (QWidget *)QT_MANGLE_NAMESPACE(qt_qwidget) +{ + QWidget *widget = 0; + if ([self delegate] == [QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate]) + widget = [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] qt_qwidgetForWindow:self]; + return widget; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaWindow) + +/*********************************************************************** + Copy and Paste between QCocoaWindow and QCocoaPanel + This is a bit unfortunate, but thanks to the dynamic dispatch we + have to duplicate this code or resort to really silly forwarding methods +**************************************************************************/ +#include "qcocoasharedwindowmethods_mac_p.h" + +@end +#endif diff --git a/src/gui/kernel/qcocoawindow_mac_p.h b/src/gui/kernel/qcocoawindow_mac_p.h new file mode 100644 index 0000000000..d567cab244 --- /dev/null +++ b/src/gui/kernel/qcocoawindow_mac_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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. +// + +#ifndef QCOCOAWINDOW_MAC_P +#define QCOCOAWINDOW_MAC_P + +#ifdef QT_MAC_USE_COCOA +#include "qmacdefines_mac.h" +#import +#include +#include + +enum { QtMacCustomizeWindow = 1 << 21 }; // This will one day be run over by + +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_FORWARD_DECLARE_CLASS(QStringList); +QT_FORWARD_DECLARE_CLASS(QCocoaDropData); + +@interface NSWindow (QtCoverForHackWithCategory) ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask; +@end + +@interface NSWindow (QT_MANGLE_NAMESPACE(QWidgetIntegration)) +- (id)QT_MANGLE_NAMESPACE(qt_initWithQWidget):(QWidget *)widget contentRect:(NSRect)rect styleMask:(NSUInteger)mask; +- (QWidget *)QT_MANGLE_NAMESPACE(qt_qwidget); +@end + +@interface NSWindow (QtIntegration) +- (NSDragOperation)draggingEntered:(id )sender; +- (NSDragOperation)draggingUpdated:(id )sender; +- (void)draggingExited:(id )sender; +- (BOOL)performDragOperation:(id )sender; +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaWindow) : NSWindow { + QStringList *currentCustomDragTypes; + QCocoaDropData *dropData; + NSInteger dragEnterSequence; +} + ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask; +- (void)registerDragTypes; +- (void)drawRectOriginal:(NSRect)rect; + +@end +#endif + +#endif diff --git a/src/gui/kernel/qcocoawindowcustomthemeframe_mac.mm b/src/gui/kernel/qcocoawindowcustomthemeframe_mac.mm new file mode 100644 index 0000000000..b761934c01 --- /dev/null +++ b/src/gui/kernel/qcocoawindowcustomthemeframe_mac.mm @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmacdefines_mac.h" + +#ifdef QT_MAC_USE_COCOA + +#import "private/qcocoawindowcustomthemeframe_mac_p.h" +#import "private/qcocoawindow_mac_p.h" +#include "private/qt_cocoa_helpers_mac_p.h" +#include "qwidget.h" + +@implementation QT_MANGLE_NAMESPACE(QCocoaWindowCustomThemeFrame) + +- (void)_updateButtons +{ + [super _updateButtons]; + NSWindow *window = [self window]; + qt_syncCocoaTitleBarButtons(window, [window QT_MANGLE_NAMESPACE(qt_qwidget)]); +} + +@end + +#endif diff --git a/src/gui/kernel/qcocoawindowcustomthemeframe_mac_p.h b/src/gui/kernel/qcocoawindowcustomthemeframe_mac_p.h new file mode 100644 index 0000000000..09b40875f6 --- /dev/null +++ b/src/gui/kernel/qcocoawindowcustomthemeframe_mac_p.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// +#import +#include "qmacdefines_mac.h" +#import "qnsthemeframe_mac_p.h" + +@interface QT_MANGLE_NAMESPACE(QCocoaWindowCustomThemeFrame) : NSThemeFrame +{ +} + +@end diff --git a/src/gui/kernel/qcocoawindowdelegate_mac.mm b/src/gui/kernel/qcocoawindowdelegate_mac.mm new file mode 100644 index 0000000000..1faf068a12 --- /dev/null +++ b/src/gui/kernel/qcocoawindowdelegate_mac.mm @@ -0,0 +1,439 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import "private/qcocoawindowdelegate_mac_p.h" +#ifdef QT_MAC_USE_COCOA +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +extern QWidgetData *qt_qwidget_data(QWidget *); // qwidget.cpp +extern void onApplicationWindowChangedActivation(QWidget *, bool); //qapplication_mac.mm +extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); // qapplication.cpp +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +static QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) *sharedCocoaWindowDelegate = nil; + +// This is a singleton, but unlike most Cocoa singletons, it lives in a library and could be +// pontentially loaded and unloaded. This means we should at least attempt to do the +// memory management correctly. + +static void cleanupCocoaWindowDelegate() +{ + [sharedCocoaWindowDelegate release]; +} + +@implementation QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) + +- (id)init +{ + self = [super init]; + if (self != nil) { + m_windowHash = new QHash(); + m_drawerHash = new QHash(); + } + return self; +} + +- (void)dealloc +{ + sharedCocoaWindowDelegate = nil; + QHash::const_iterator windowIt = m_windowHash->constBegin(); + while (windowIt != m_windowHash->constEnd()) { + [windowIt.key() setDelegate:nil]; + ++windowIt; + } + delete m_windowHash; + QHash::const_iterator drawerIt = m_drawerHash->constBegin(); + while (drawerIt != m_drawerHash->constEnd()) { + [drawerIt.key() setDelegate:nil]; + ++drawerIt; + } + delete m_drawerHash; + [super dealloc]; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + if (sharedCocoaWindowDelegate == nil) { + sharedCocoaWindowDelegate = [super allocWithZone:zone]; + return sharedCocoaWindowDelegate; + qAddPostRoutine(cleanupCocoaWindowDelegate); + } + } + return nil; +} + ++ (QT_MANGLE_NAMESPACE(QCocoaWindowDelegate)*)sharedDelegate +{ + @synchronized(self) { + if (sharedCocoaWindowDelegate == nil) + [[self alloc] init]; + } + return [[sharedCocoaWindowDelegate retain] autorelease]; +} + +-(void)syncSizeForWidget:(QWidget *)qwidget toSize:(const QSize &)newSize fromSize:(const QSize &)oldSize +{ + qt_qwidget_data(qwidget)->crect.setSize(newSize); + // ### static contents optimization needs to go here + const OSViewRef view = qt_mac_nativeview_for(qwidget); + [view setFrameSize:NSMakeSize(newSize.width(), newSize.height())]; + if (!qwidget->isVisible()) { + qwidget->setAttribute(Qt::WA_PendingResizeEvent, true); + } else { + QResizeEvent qre(newSize, oldSize); + if (qwidget->testAttribute(Qt::WA_PendingResizeEvent)) { + qwidget->setAttribute(Qt::WA_PendingResizeEvent, false); + QApplication::sendEvent(qwidget, &qre); + } else { + qt_sendSpontaneousEvent(qwidget, &qre); + } + } +} + +- (void)dumpMaximizedStateforWidget:(QWidget*)qwidget window:(NSWindow *)window +{ + if (!window) + return; // Nothing to do. + QWidgetData *widgetData = qt_qwidget_data(qwidget); + if ((widgetData->window_state & Qt::WindowMaximized) && ![window isZoomed]) { + widgetData->window_state &= ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowState(widgetData->window_state | Qt::WindowMaximized)); + qt_sendSpontaneousEvent(qwidget, &e); + } +} + +- (NSSize)closestAcceptableSizeForWidget:(QWidget *)qwidget window:(NSWindow *)window + withNewSize:(NSSize)proposedSize +{ + [self dumpMaximizedStateforWidget:qwidget window:window]; + QSize newSize = QLayout::closestAcceptableSize(qwidget, + QSize(proposedSize.width, proposedSize.height)); + return [NSWindow frameRectForContentRect: + NSMakeRect(0., 0., newSize.width(), newSize.height()) + styleMask:[window styleMask]].size; +} + +- (NSSize)windowWillResize:(NSWindow *)windowToResize toSize:(NSSize)proposedFrameSize +{ + QWidget *qwidget = m_windowHash->value(windowToResize); + return [self closestAcceptableSizeForWidget:qwidget window:windowToResize + withNewSize:[NSWindow contentRectForFrameRect: + NSMakeRect(0, 0, + proposedFrameSize.width, + proposedFrameSize.height) + styleMask:[windowToResize styleMask]].size]; +} + +- (NSSize)drawerWillResizeContents:(NSDrawer *)sender toSize:(NSSize)contentSize +{ + QWidget *qwidget = m_drawerHash->value(sender); + return [self closestAcceptableSizeForWidget:qwidget window:nil withNewSize:contentSize]; +} + +-(void)windowDidMiniaturize:(NSNotification*)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + if (!qwidget->isMinimized()) { + QWidgetData *widgetData = qt_qwidget_data(qwidget); + widgetData->window_state = widgetData->window_state | Qt::WindowMinimized; + QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state & ~Qt::WindowMinimized)); + qt_sendSpontaneousEvent(qwidget, &e); + } + // Send hide to match Qt on X11 and Windows + QEvent e(QEvent::Hide); + qt_sendSpontaneousEvent(qwidget, &e); +} + +- (void)windowDidResize:(NSNotification *)notification +{ + NSWindow *window = [notification object]; + QWidget *qwidget = m_windowHash->value(window); + QWidgetData *widgetData = qt_qwidget_data(qwidget); + if (!(qwidget->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen)) && [window isZoomed]) { + widgetData->window_state = widgetData->window_state | Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state + & ~Qt::WindowMaximized)); + qt_sendSpontaneousEvent(qwidget, &e); + } else { + widgetData->window_state = widgetData->window_state & ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state + | Qt::WindowMaximized)); + qt_sendSpontaneousEvent(qwidget, &e); + } + NSRect rect = [[window contentView] frame]; + const QSize newSize(rect.size.width, rect.size.height); + const QSize &oldSize = widgetData->crect.size(); + if (newSize != oldSize) { + QWidgetPrivate::qt_mac_update_sizer(qwidget); + [self syncSizeForWidget:qwidget toSize:newSize fromSize:oldSize]; + } + + // We force the repaint to be synchronized with the resize of the window. + // Otherwise, the resize looks sluggish because we paint one event loop later. + if ([[window contentView] inLiveResize]) { + qwidget->repaint(); + + // We need to repaint the toolbar as well. + QMainWindow* mWindow = qobject_cast(qwidget->window()); + if (mWindow) { + QMainWindowLayout *mLayout = qobject_cast(mWindow->layout()); + QList toolbarList = mLayout->qtoolbarsInUnifiedToolbarList; + + for (int i = 0; i < toolbarList.size(); ++i) { + QToolBar* toolbar = toolbarList.at(i); + toolbar->repaint(); + } + } + } +} + +- (void)windowDidMove:(NSNotification *)notification +{ + // The code underneath needs to translate the window location + // from bottom left (which is the origin used by Cocoa) to + // upper left (which is the origin used by Qt): + NSWindow *window = [notification object]; + NSRect newRect = [window frame]; + QWidget *qwidget = m_windowHash->value(window); + QPoint qtPoint = flipPoint(NSMakePoint(newRect.origin.x, + newRect.origin.y + newRect.size.height)).toPoint(); + const QRect &oldRect = qwidget->frameGeometry(); + + if (qtPoint.x() != oldRect.x() || qtPoint.y() != oldRect.y()) { + QWidgetData *widgetData = qt_qwidget_data(qwidget); + QRect oldCRect = widgetData->crect; + QWidgetPrivate *widgetPrivate = qt_widget_private(qwidget); + const QRect &fStrut = widgetPrivate->frameStrut(); + widgetData->crect.moveTo(qtPoint.x() + fStrut.left(), qtPoint.y() + fStrut.top()); + if (!qwidget->isVisible()) { + qwidget->setAttribute(Qt::WA_PendingMoveEvent, true); + } else { + QMoveEvent qme(qtPoint, oldRect.topLeft()); + qt_sendSpontaneousEvent(qwidget, &qme); + } + } +} + +-(BOOL)windowShouldClose:(id)windowThatWantsToClose +{ + QWidget *qwidget = m_windowHash->value(windowThatWantsToClose); + QScopedLoopLevelCounter counter(qt_widget_private(qwidget)->threadData); + return qt_widget_private(qwidget)->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); +} + +-(void)windowDidDeminiaturize:(NSNotification *)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + QWidgetData *widgetData = qt_qwidget_data(qwidget); + Qt::WindowStates currState = Qt::WindowStates(widgetData->window_state); + Qt::WindowStates newState = currState; + if (currState & Qt::WindowMinimized) + newState &= ~Qt::WindowMinimized; + if (!(currState & Qt::WindowActive)) + newState |= Qt::WindowActive; + if (newState != currState) { + widgetData->window_state = newState; + QWindowStateChangeEvent e(currState); + qt_sendSpontaneousEvent(qwidget, &e); + } + QShowEvent qse; + qt_sendSpontaneousEvent(qwidget, &qse); +} + +-(void)windowDidBecomeMain:(NSNotification*)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + Q_ASSERT(qwidget); + onApplicationWindowChangedActivation(qwidget, true); +} + +-(void)windowDidResignMain:(NSNotification*)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + Q_ASSERT(qwidget); + onApplicationWindowChangedActivation(qwidget, false); +} + +// These are the same as main, but they are probably better to keep separate since there is a +// tiny difference between main and key windows. +-(void)windowDidBecomeKey:(NSNotification*)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + Q_ASSERT(qwidget); + onApplicationWindowChangedActivation(qwidget, true); +} + +-(void)windowDidResignKey:(NSNotification*)notification +{ + QWidget *qwidget = m_windowHash->value([notification object]); + Q_ASSERT(qwidget); + onApplicationWindowChangedActivation(qwidget, false); +} + +-(QWidget *)qt_qwidgetForWindow:(NSWindow *)window +{ + return m_windowHash->value(window); +} + +- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame +{ + Q_UNUSED(newFrame); + // saving the current window geometry before the window is maximized + QWidget *qwidget = m_windowHash->value(window); + QWidgetPrivate *widgetPrivate = qt_widget_private(qwidget); + if (qwidget->isWindow()) { + if(qwidget->windowState() & Qt::WindowMaximized) { + // Restoring + widgetPrivate->topData()->wasMaximized = false; + } else { + // Maximizing + widgetPrivate->topData()->normalGeometry = qwidget->geometry(); + // If the window was maximized we need to update the coordinates since now it will start at 0,0. + // We do this in a special field that is only used when not restoring but manually resizing the window. + // Since the coordinates are fixed we just set a boolean flag. + widgetPrivate->topData()->wasMaximized = true; + } + } + return YES; +} + +- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)defaultFrame +{ + NSRect frameToReturn = defaultFrame; + QWidget *qwidget = m_windowHash->value(window); + QSizeF size = qwidget->maximumSize(); + NSRect windowFrameRect = [window frame]; + NSRect viewFrameRect = [[window contentView] frame]; + // consider additional size required for titlebar & frame + frameToReturn.size.width = qMin(frameToReturn.size.width, + size.width()+(windowFrameRect.size.width - viewFrameRect.size.width)); + frameToReturn.size.height = qMin(frameToReturn.size.height, + size.height()+(windowFrameRect.size.height - viewFrameRect.size.height)); + return frameToReturn; +} + +- (void)becomeDelegteForWindow:(NSWindow *)window widget:(QWidget *)widget +{ + m_windowHash->insert(window, widget); + [window setDelegate:self]; +} + +- (void)resignDelegateForWindow:(NSWindow *)window +{ + [window setDelegate:nil]; + m_windowHash->remove(window); +} + +- (void)becomeDelegateForDrawer:(NSDrawer *)drawer widget:(QWidget *)widget +{ + m_drawerHash->insert(drawer, widget); + [drawer setDelegate:self]; + NSWindow *window = [[drawer contentView] window]; + [self becomeDelegteForWindow:window widget:widget]; +} + +- (void)resignDelegateForDrawer:(NSDrawer *)drawer +{ + QWidget *widget = m_drawerHash->value(drawer); + [drawer setDelegate:nil]; + if (widget) + [self resignDelegateForWindow:[[drawer contentView] window]]; + m_drawerHash->remove(drawer); +} + +- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu +{ + Q_UNUSED(menu); + QWidget *qwidget = m_windowHash->value(window); + if (qwidget && !qwidget->windowFilePath().isEmpty()) { + return YES; + } + return NO; +} + +- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event + from:(NSPoint)dragImageLocation + withPasteboard:(NSPasteboard *)pasteboard +{ + Q_UNUSED(event); + Q_UNUSED(dragImageLocation); + Q_UNUSED(pasteboard); + QWidget *qwidget = m_windowHash->value(window); + if (qwidget && !qwidget->windowFilePath().isEmpty()) { + return YES; + } + return NO; +} + +- (void)syncContentViewFrame: (NSNotification *)notification +{ + NSView *cView = [notification object]; + if (cView) { + NSWindow *window = [cView window]; + QWidget *qwidget = m_windowHash->value(window); + if (qwidget) { + QWidgetData *widgetData = qt_qwidget_data(qwidget); + NSRect rect = [cView frame]; + const QSize newSize(rect.size.width, rect.size.height); + const QSize &oldSize = widgetData->crect.size(); + if (newSize != oldSize) { + [self syncSizeForWidget:qwidget toSize:newSize fromSize:oldSize]; + } + } + + } +} + +@end +#endif// QT_MAC_USE_COCOA diff --git a/src/gui/kernel/qcocoawindowdelegate_mac_p.h b/src/gui/kernel/qcocoawindowdelegate_mac_p.h new file mode 100644 index 0000000000..638ce2df9a --- /dev/null +++ b/src/gui/kernel/qcocoawindowdelegate_mac_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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 "qmacdefines_mac.h" + +#ifdef QT_MAC_USE_COCOA +#import + +QT_BEGIN_NAMESPACE +template class QHash; +QT_END_NAMESPACE +using QT_PREPEND_NAMESPACE(QHash); +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QSize) +QT_FORWARD_DECLARE_CLASS(QWidgetData) + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSWindowDelegate +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +- (void)windowDidMiniaturize:(NSNotification*)notification; +- (void)windowDidResize:(NSNotification *)notification; +- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)defaultFrame; +- (void)windowDidMove:(NSNotification *)notification; +- (BOOL)windowShouldClose:(id)window; +- (void)windowDidDeminiaturize:(NSNotification *)notification; +- (void)windowDidBecomeMain:(NSNotification*)notification; +- (void)windowDidResignMain:(NSNotification*)notification; +- (void)windowDidBecomeKey:(NSNotification*)notification; +- (void)windowDidResignKey:(NSNotification*)notification; +- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu; +- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard; +- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame; +@end + +@protocol NSDrawerDelegate +- (NSSize)drawerWillResizeContents:(NSDrawer *)sender toSize:(NSSize)contentSize; +@end + +#endif + + + +@interface QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) : NSObject { + QHash *m_windowHash; + QHash *m_drawerHash; +} ++ (QT_MANGLE_NAMESPACE(QCocoaWindowDelegate)*)sharedDelegate; +- (void)becomeDelegteForWindow:(NSWindow *)window widget:(QWidget *)widget; +- (void)resignDelegateForWindow:(NSWindow *)window; +- (void)becomeDelegateForDrawer:(NSDrawer *)drawer widget:(QWidget *)widget; +- (void)resignDelegateForDrawer:(NSDrawer *)drawer; +- (void)dumpMaximizedStateforWidget:(QWidget*)qwidget window:(NSWindow *)window; +- (void)syncSizeForWidget:(QWidget *)qwidget + toSize:(const QSize &)newSize + fromSize:(const QSize &)oldSize; +- (NSSize)closestAcceptableSizeForWidget:(QWidget *)qwidget + window:(NSWindow *)window withNewSize:(NSSize)proposedSize; +- (QWidget *)qt_qwidgetForWindow:(NSWindow *)window; +- (void)syncContentViewFrame: (NSNotification *)notification; +@end +#endif diff --git a/src/gui/kernel/qcursor.cpp b/src/gui/kernel/qcursor.cpp new file mode 100644 index 0000000000..823f6f1e8c --- /dev/null +++ b/src/gui/kernel/qcursor.cpp @@ -0,0 +1,573 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcursor.h" + +#ifndef QT_NO_CURSOR + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QCursor + + \brief The QCursor class provides a mouse cursor with an arbitrary + shape. + + \ingroup appearance + \ingroup shared + + + This class is mainly used to create mouse cursors that are + associated with particular widgets and to get and set the position + of the mouse cursor. + + Qt has a number of standard cursor shapes, but you can also make + custom cursor shapes based on a QBitmap, a mask and a hotspot. + + To associate a cursor with a widget, use QWidget::setCursor(). To + associate a cursor with all widgets (normally for a short period + of time), use QApplication::setOverrideCursor(). + + To set a cursor shape use QCursor::setShape() or use the QCursor + constructor which takes the shape as argument, or you can use one + of the predefined cursors defined in the \l Qt::CursorShape enum. + + If you want to create a cursor with your own bitmap, either use + the QCursor constructor which takes a bitmap and a mask or the + constructor which takes a pixmap as arguments. + + To set or get the position of the mouse cursor use the static + methods QCursor::pos() and QCursor::setPos(). + + \bold{Note:} It is possible to create a QCursor before + QApplication, but it is not useful except as a place-holder for a + real QCursor created after QApplication. Attempting to use a + QCursor that was created before QApplication will result in a + crash. + + \section1 A Note for X11 Users + + On X11, Qt supports the \link + http://www.xfree86.org/4.3.0/Xcursor.3.html Xcursor\endlink + library, which allows for full color icon themes. The table below + shows the cursor name used for each Qt::CursorShape value. If a + cursor cannot be found using the name shown below, a standard X11 + cursor will be used instead. Note: X11 does not provide + appropriate cursors for all possible Qt::CursorShape values. It + is possible that some cursors will be taken from the Xcursor + theme, while others will use an internal bitmap cursor. + + \table + \header \o Shape \o Qt::CursorShape Value \o Cursor Name + \o Shape \o Qt::CursorShape Value \o Cursor Name + \row \o \inlineimage cursor-arrow.png + \o Qt::ArrowCursor \o \c left_ptr + \o \inlineimage cursor-sizev.png + \o Qt::SizeVerCursor \o \c size_ver + \row \o \inlineimage cursor-uparrow.png + \o Qt::UpArrowCursor \o \c up_arrow + \o \inlineimage cursor-sizeh.png + \o Qt::SizeHorCursor \o \c size_hor + \row \o \inlineimage cursor-cross.png + \o Qt::CrossCursor \o \c cross + \o \inlineimage cursor-sizeb.png + \o Qt::SizeBDiagCursor \o \c size_bdiag + \row \o \inlineimage cursor-ibeam.png + \o Qt::IBeamCursor \o \c ibeam + \o \inlineimage cursor-sizef.png + \o Qt::SizeFDiagCursor \o \c size_fdiag + \row \o \inlineimage cursor-wait.png + \o Qt::WaitCursor \o \c wait + \o \inlineimage cursor-sizeall.png + \o Qt::SizeAllCursor \o \c size_all + \row \o \inlineimage cursor-busy.png + \o Qt::BusyCursor \o \c left_ptr_watch + \o \inlineimage cursor-vsplit.png + \o Qt::SplitVCursor \o \c split_v + \row \o \inlineimage cursor-forbidden.png + \o Qt::ForbiddenCursor \o \c forbidden + \o \inlineimage cursor-hsplit.png + \o Qt::SplitHCursor \o \c split_h + \row \o \inlineimage cursor-hand.png + \o Qt::PointingHandCursor \o \c pointing_hand + \o \inlineimage cursor-openhand.png + \o Qt::OpenHandCursor \o \c openhand + \row \o \inlineimage cursor-whatsthis.png + \o Qt::WhatsThisCursor \o \c whats_this + \o \inlineimage cursor-closedhand.png + \o Qt::ClosedHandCursor \o \c closedhand + \row \o + \o Qt::DragMoveCursor \o \c dnd-move or \c move + \o + \o Qt::DragCopyCursor \o \c dnd-copy or \c copy + \row \o + \o Qt::DragLinkCursor \o \c dnd-link or \c link + \endtable + + \sa QWidget, {fowler}{GUI Design Handbook: Cursors} +*/ + +/*! + \fn HCURSOR_or_HANDLE QCursor::handle() const + + Returns a platform-specific cursor handle. The \c + HCURSOR_or_HANDLE type is \c HCURSOR on Windows and Qt::HANDLE on X11 + and Mac OS X. On \l{Qt for Embedded Linux} it is an integer. + + \warning Using the value returned by this function is not + portable. +*/ + +/*! + \fn QCursor::QCursor(HCURSOR cursor) + + Constructs a Qt cursor from the given Windows \a cursor. + + \warning This function is only available on Windows. + + \sa handle() +*/ + +/*! + \fn QCursor::QCursor(Qt::HANDLE handle) + + Constructs a Qt cursor from the given \a handle. + + \warning This function is only available on X11. + + \sa handle() +*/ + +/*! + \fn QPoint QCursor::pos() + + Returns the position of the cursor (hot spot) in global screen + coordinates. + + You can call QWidget::mapFromGlobal() to translate it to widget + coordinates. + + \sa setPos(), QWidget::mapFromGlobal(), QWidget::mapToGlobal() +*/ + +/*! + \fn void QCursor::setPos(int x, int y) + + Moves the cursor (hot spot) to the global screen position (\a x, + \a y). + + You can call QWidget::mapToGlobal() to translate widget + coordinates to global screen coordinates. + + \sa pos(), QWidget::mapFromGlobal(), QWidget::mapToGlobal() +*/ + +/*! + \fn void QCursor::setPos (const QPoint &p) + + \overload + + Moves the cursor (hot spot) to the global screen position at point + \a p. +*/ + +/***************************************************************************** + QCursor stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM + + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QCursor &cursor) + \relates QCursor + + Writes the \a cursor to the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QCursor &c) +{ + s << (qint16)c.shape(); // write shape id to stream + if (c.shape() == Qt::BitmapCursor) { // bitmap cursor + bool isPixmap = false; + if (s.version() >= 7) { + isPixmap = !c.pixmap().isNull(); + s << isPixmap; + } + if (isPixmap) + s << c.pixmap(); + else + s << *c.bitmap() << *c.mask(); + s << c.hotSpot(); + } + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QCursor &cursor) + \relates QCursor + + Reads the \a cursor from the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QCursor &c) +{ + qint16 shape; + s >> shape; // read shape id from stream + if (shape == Qt::BitmapCursor) { // read bitmap cursor + bool isPixmap = false; + if (s.version() >= 7) + s >> isPixmap; + if (isPixmap) { + QPixmap pm; + QPoint hot; + s >> pm >> hot; + c = QCursor(pm, hot.x(), hot.y()); + } else { + QBitmap bm, bmm; + QPoint hot; + s >> bm >> bmm >> hot; + c = QCursor(bm, bmm, hot.x(), hot.y()); + } + } else { + c.setShape((Qt::CursorShape)shape); // create cursor with shape + } + return s; +} +#endif // QT_NO_DATASTREAM + + +/*! + Constructs a custom pixmap cursor. + + \a pixmap is the image. It is usual to give it a mask (set using + QPixmap::setMask()). \a hotX and \a hotY define the cursor's hot + spot. + + If \a hotX is negative, it is set to the \c{pixmap().width()/2}. + If \a hotY is negative, it is set to the \c{pixmap().height()/2}. + + Valid cursor sizes depend on the display hardware (or the + underlying window system). We recommend using 32 x 32 cursors, + because this size is supported on all platforms. Some platforms + also support 16 x 16, 48 x 48, and 64 x 64 cursors. + + \note On Windows CE, the cursor size is fixed. If the pixmap + is bigger than the system size, it will be scaled. + + \sa QPixmap::QPixmap(), QPixmap::setMask() +*/ + +QCursor::QCursor(const QPixmap &pixmap, int hotX, int hotY) + : d(0) +{ + QImage img = pixmap.toImage().convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither|Qt::AvoidDither); + QBitmap bm = QBitmap::fromImage(img, Qt::ThresholdDither|Qt::AvoidDither); + QBitmap bmm = pixmap.mask(); + if (!bmm.isNull()) { + QBitmap nullBm; + bm.setMask(nullBm); + } + else if (!pixmap.mask().isNull()) { + QImage mimg = pixmap.mask().toImage().convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither|Qt::AvoidDither); + bmm = QBitmap::fromImage(mimg, Qt::ThresholdDither|Qt::AvoidDither); + } + else { + bmm = QBitmap(bm.size()); + bmm.fill(Qt::color1); + } + + d = QCursorData::setBitmap(bm, bmm, hotX, hotY); + d->pixmap = pixmap; +} + + + +/*! + Constructs a custom bitmap cursor. + + \a bitmap and + \a mask make up the bitmap. + \a hotX and + \a hotY define the cursor's hot spot. + + If \a hotX is negative, it is set to the \c{bitmap().width()/2}. + If \a hotY is negative, it is set to the \c{bitmap().height()/2}. + + The cursor \a bitmap (B) and \a mask (M) bits are combined like this: + \list + \o B=1 and M=1 gives black. + \o B=0 and M=1 gives white. + \o B=0 and M=0 gives transparent. + \o B=1 and M=0 gives an XOR'd result under Windows, undefined + results on all other platforms. + \endlist + + Use the global Qt color Qt::color0 to draw 0-pixels and Qt::color1 to + draw 1-pixels in the bitmaps. + + Valid cursor sizes depend on the display hardware (or the + underlying window system). We recommend using 32 x 32 cursors, + because this size is supported on all platforms. Some platforms + also support 16 x 16, 48 x 48, and 64 x 64 cursors. + + \note On Windows CE, the cursor size is fixed. If the pixmap + is bigger than the system size, it will be scaled. + + \sa QBitmap::QBitmap(), QBitmap::setMask() +*/ + +QCursor::QCursor(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) + : d(0) +{ + d = QCursorData::setBitmap(bitmap, mask, hotX, hotY); +} + +QCursorData *qt_cursorTable[Qt::LastCursor + 1]; +bool QCursorData::initialized = false; + +/*! \internal */ +void QCursorData::cleanup() +{ + if(!QCursorData::initialized) + return; + + for (int shape = 0; shape <= Qt::LastCursor; ++shape) { + // In case someone has a static QCursor defined with this shape + if (!qt_cursorTable[shape]->ref.deref()) + delete qt_cursorTable[shape]; + qt_cursorTable[shape] = 0; + } + QCursorData::initialized = false; +} + +/*! \internal */ +void QCursorData::initialize() +{ + if (QCursorData::initialized) + return; +#ifdef Q_WS_MAC + // DRSWAT - Not Needed Cocoa or Carbon + //InitCursor(); +#endif + for (int shape = 0; shape <= Qt::LastCursor; ++shape) + qt_cursorTable[shape] = new QCursorData((Qt::CursorShape)shape); + QCursorData::initialized = true; +} + +/*! + Constructs a cursor with the default arrow shape. +*/ +QCursor::QCursor() +{ + if (!QCursorData::initialized) { + if (QApplication::startingUp()) { + d = 0; + return; + } + QCursorData::initialize(); + } + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + d = c; +} + +/*! + Constructs a cursor with the specified \a shape. + + See \l Qt::CursorShape for a list of shapes. + + \sa setShape() +*/ +QCursor::QCursor(Qt::CursorShape shape) + : d(0) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + setShape(shape); +} + + +/*! + Returns the cursor shape identifier. The return value is one of + the \l Qt::CursorShape enum values (cast to an int). + + \sa setShape() +*/ +Qt::CursorShape QCursor::shape() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + return d->cshape; +} + +/*! + Sets the cursor to the shape identified by \a shape. + + See \l Qt::CursorShape for the list of cursor shapes. + + \sa shape() +*/ +void QCursor::setShape(Qt::CursorShape shape) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + QCursorData *c = uint(shape) <= Qt::LastCursor ? qt_cursorTable[shape] : 0; + if (!c) + c = qt_cursorTable[0]; + c->ref.ref(); + if (!d) { + d = c; + } else { + if (!d->ref.deref()) + delete d; + d = c; + } +} + +/*! + Returns the cursor bitmap, or 0 if it is one of the standard + cursors. +*/ +const QBitmap *QCursor::bitmap() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + return d->bm; +} + +/*! + Returns the cursor bitmap mask, or 0 if it is one of the standard + cursors. +*/ + +const QBitmap *QCursor::mask() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + return d->bmm; +} + +/*! + Returns the cursor pixmap. This is only valid if the cursor is a + pixmap cursor. +*/ + +QPixmap QCursor::pixmap() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + return d->pixmap; +} + +/*! + Returns the cursor hot spot, or (0, 0) if it is one of the + standard cursors. +*/ + +QPoint QCursor::hotSpot() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + return QPoint(d->hx, d->hy); +} + +/*! + Constructs a copy of the cursor \a c. +*/ + +QCursor::QCursor(const QCursor &c) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + d = c.d; + d->ref.ref(); +} + +/*! + Destroys the cursor. +*/ + +QCursor::~QCursor() +{ + if (d && !d->ref.deref()) + delete d; +} + + +/*! + Assigns \a c to this cursor and returns a reference to this + cursor. +*/ + +QCursor &QCursor::operator=(const QCursor &c) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (c.d) + c.d->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = c.d; + return *this; +} + +/*! + Returns the cursor as a QVariant. +*/ +QCursor::operator QVariant() const +{ + return QVariant(QVariant::Cursor, this); +} +QT_END_NAMESPACE +#endif // QT_NO_CURSOR + diff --git a/src/gui/kernel/qcursor.h b/src/gui/kernel/qcursor.h new file mode 100644 index 0000000000..c993763634 --- /dev/null +++ b/src/gui/kernel/qcursor.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCURSOR_H +#define QCURSOR_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVariant; + +/* + ### The fake cursor has to go first with old qdoc. +*/ +#ifdef QT_NO_CURSOR + +class Q_GUI_EXPORT QCursor +{ +public: + static QPoint pos(); + static void setPos(int x, int y); + inline static void setPos(const QPoint &p) { setPos(p.x(), p.y()); } +private: + QCursor(); +}; + +#endif // QT_NO_CURSOR + +#ifndef QT_NO_CURSOR + +class QCursorData; +class QBitmap; +class QPixmap; + +#if defined(Q_WS_MAC) +void qt_mac_set_cursor(const QCursor *c); +#endif +#if defined(Q_OS_SYMBIAN) +extern void qt_symbian_show_pointer_sprite(); +extern void qt_symbian_hide_pointer_sprite(); +extern void qt_symbian_set_pointer_sprite(const QCursor& cursor); +extern void qt_symbian_move_cursor_sprite(); +#endif + +class Q_GUI_EXPORT QCursor +{ +public: + QCursor(); + QCursor(Qt::CursorShape shape); + QCursor(const QBitmap &bitmap, const QBitmap &mask, int hotX=-1, int hotY=-1); + QCursor(const QPixmap &pixmap, int hotX=-1, int hotY=-1); + QCursor(const QCursor &cursor); + ~QCursor(); + QCursor &operator=(const QCursor &cursor); +#ifdef Q_COMPILER_RVALUE_REFS + inline QCursor &operator=(QCursor &&other) + { qSwap(d, other.d); return *this; } +#endif + operator QVariant() const; + + Qt::CursorShape shape() const; + void setShape(Qt::CursorShape newShape); + + const QBitmap *bitmap() const; + const QBitmap *mask() const; + QPixmap pixmap() const; + QPoint hotSpot() const; + + static QPoint pos(); + static void setPos(int x, int y); + inline static void setPos(const QPoint &p) { setPos(p.x(), p.y()); } + +#ifdef qdoc + HCURSOR_or_HANDLE handle() const; + QCursor(HCURSOR cursor); + QCursor(Qt::HANDLE cursor); +#endif + +#ifndef qdoc +#if defined(Q_WS_WIN) + HCURSOR handle() const; + QCursor(HCURSOR cursor); +#elif defined(Q_WS_X11) + Qt::HANDLE handle() const; + QCursor(Qt::HANDLE cursor); + static int x11Screen(); +#elif defined(Q_WS_MAC) + Qt::HANDLE handle() const; +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) + int handle() const; +#elif defined(Q_OS_SYMBIAN) + Qt::HANDLE handle() const; +#endif +#endif + +private: + QCursorData *d; +#if defined(Q_WS_MAC) + friend void *qt_mac_nsCursorForQCursor(const QCursor &c); + friend void qt_mac_set_cursor(const QCursor *c); + friend void qt_mac_updateCursorWithWidgetUnderMouse(QWidget *widgetUnderMouse); +#endif +#if defined(Q_OS_SYMBIAN) + friend void qt_symbian_show_pointer_sprite(); + friend void qt_symbian_hide_pointer_sprite(); + friend void qt_symbian_set_pointer_sprite(const QCursor& cursor); + friend void qt_symbian_move_cursor_sprite(); +#endif +}; + +#ifdef QT3_SUPPORT +// CursorShape is defined in X11/X.h +#ifdef CursorShape +#define X_CursorShape CursorShape +#undef CursorShape +#endif +typedef Qt::CursorShape QCursorShape; +#ifdef X_CursorShape +#define CursorShape X_CursorShape +#endif +#endif + +/***************************************************************************** + QCursor stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &outS, const QCursor &cursor); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &inS, QCursor &cursor); +#endif +#endif // QT_NO_CURSOR + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCURSOR_H diff --git a/src/gui/kernel/qcursor_mac.mm b/src/gui/kernel/qcursor_mac.mm new file mode 100644 index 0000000000..0afa3ee4f0 --- /dev/null +++ b/src/gui/kernel/qcursor_mac.mm @@ -0,0 +1,689 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Externals + *****************************************************************************/ +extern QCursorData *qt_cursorTable[Qt::LastCursor + 1]; +extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp +extern GrafPtr qt_mac_qd_context(const QPaintDevice *); //qpaintdevice_mac.cpp +extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); //qapplication_mac.cpp +extern QPointer qt_button_down; //qapplication_mac.cpp + +/***************************************************************************** + Internal QCursorData class + *****************************************************************************/ + +class QMacAnimateCursor : public QObject +{ + int timerId, step; + ThemeCursor curs; +public: + QMacAnimateCursor() : QObject(), timerId(-1) { } + void start(ThemeCursor c) { + step = 1; + if(timerId != -1) + killTimer(timerId); + timerId = startTimer(300); + curs = c; + } + void stop() { + if(timerId != -1) { + killTimer(timerId); + timerId = -1; + } + } +protected: + void timerEvent(QTimerEvent *e) { + if(e->timerId() == timerId) { + /* + if(SetAnimatedThemeCursor(curs, step++) == themeBadCursorIndexErr) + stop(); + */ + } + } +}; + +inline void *qt_mac_nsCursorForQCursor(const QCursor &c) +{ + c.d->update(); + return [[static_cast(c.d->curs.cp.nscursor) retain] autorelease]; +} + +static QCursorData *currentCursor = 0; //current cursor + +void qt_mac_set_cursor(const QCursor *c) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [static_cast(qt_mac_nsCursorForQCursor(*c)) set]; +#else + if (!c) { + currentCursor = 0; + return; + } + c->handle(); //force the cursor to get loaded, if it's not + + if(currentCursor && currentCursor->type == QCursorData::TYPE_ThemeCursor + && currentCursor->curs.tc.anim) + currentCursor->curs.tc.anim->stop(); + if(c->d->type == QCursorData::TYPE_ImageCursor) { + [static_cast(c->d->curs.cp.nscursor) set]; + } else if(c->d->type == QCursorData::TYPE_ThemeCursor) { + if(SetAnimatedThemeCursor(c->d->curs.tc.curs, 0) == themeBadCursorIndexErr) { + SetThemeCursor(c->d->curs.tc.curs); + } else { + if(!c->d->curs.tc.anim) + c->d->curs.tc.anim = new QMacAnimateCursor; + c->d->curs.tc.anim->start(c->d->curs.tc.curs); + } + } + + currentCursor = c->d; +#endif +} + +static QPointer lastWidgetUnderMouse = 0; +static QPointer lastMouseCursorWidget = 0; +static bool qt_button_down_on_prev_call = false; +static QCursor *grabCursor = 0; + +void qt_mac_updateCursorWithWidgetUnderMouse(QWidget *widgetUnderMouse) +{ + QCursor cursor(Qt::ArrowCursor); + if (qt_button_down) { + // The widget that is currently pressed + // grabs the mouse cursor: + widgetUnderMouse = qt_button_down; + qt_button_down_on_prev_call = true; + } else if (qt_button_down_on_prev_call) { + // Grab has been released, so do + // a full check: + qt_button_down_on_prev_call = false; + lastWidgetUnderMouse = 0; + lastMouseCursorWidget = 0; + } + + if (QApplication::overrideCursor()) { + cursor = *QApplication::overrideCursor(); + } else if (grabCursor) { + cursor = *grabCursor; + } else if (widgetUnderMouse) { + if (widgetUnderMouse == lastWidgetUnderMouse) { + // Optimization that should hit when the widget under + // the mouse does not change as the mouse moves: + if (lastMouseCursorWidget) + cursor = lastMouseCursorWidget->cursor(); + } else { + QWidget *w = widgetUnderMouse; + for (; w; w = w->parentWidget()) { + if (w->testAttribute(Qt::WA_SetCursor)) { + cursor = w->cursor(); + break; + } + if (w->isWindow()) + break; + } + // One final check in case we ran out of parents in the loop: + if (w && !w->testAttribute(Qt::WA_SetCursor)) + w = 0; + + lastWidgetUnderMouse = widgetUnderMouse; + lastMouseCursorWidget = w; + } + } + +#ifdef QT_MAC_USE_COCOA + cursor.d->update(); + NSCursor *nsCursor = static_cast(cursor.d->curs.cp.nscursor); + if ([NSCursor currentCursor] != nsCursor) { + QMacCocoaAutoReleasePool pool; + [nsCursor set]; + } +#else + qt_mac_set_cursor(&cursor); +#endif +} + +void qt_mac_update_cursor() +{ + // This function is similar to qt_mac_updateCursorWithWidgetUnderMouse + // except that is clears the optimization cache, and finds the widget + // under mouse itself. Clearing the cache is useful in cases where the + // application has been deactivated/activated etc. + // NB: since we dont have any true native widget, the call to + // qt_mac_getTargetForMouseEvent will fail when the mouse is over QMacNativeWidgets. +#ifdef QT_MAC_USE_COCOA + lastWidgetUnderMouse = 0; + lastMouseCursorWidget = 0; + QWidget *widgetUnderMouse = 0; + + if (qt_button_down) { + widgetUnderMouse = qt_button_down; + } else { + QPoint localPoint; + QPoint globalPoint; + qt_mac_getTargetForMouseEvent(0, QEvent::None, localPoint, globalPoint, 0, &widgetUnderMouse); + } + qt_mac_updateCursorWithWidgetUnderMouse(widgetUnderMouse); +#else + qt_mac_updateCursorWithWidgetUnderMouse(QApplication::widgetAt(QCursor::pos())); +#endif +} + +void qt_mac_setMouseGrabCursor(bool set, QCursor *const cursor = 0) +{ + if (grabCursor) { + delete grabCursor; + grabCursor = 0; + } + if (set) { + if (cursor) + grabCursor = new QCursor(*cursor); + else if (lastMouseCursorWidget) + grabCursor = new QCursor(lastMouseCursorWidget->cursor()); + else + grabCursor = new QCursor(Qt::ArrowCursor); + } + qt_mac_update_cursor(); +} + +#ifndef QT_MAC_USE_COCOA +void qt_mac_update_cursor_at_global_pos(const QPoint &globalPos) +{ + qt_mac_updateCursorWithWidgetUnderMouse(QApplication::widgetAt(globalPos)); +} +#endif + +static int nextCursorId = Qt::BitmapCursor; + +QCursorData::QCursorData(Qt::CursorShape s) + : cshape(s), bm(0), bmm(0), hx(-1), hy(-1), mId(s), type(TYPE_None) +{ + ref = 1; + memset(&curs, '\0', sizeof(curs)); +} + +QCursorData::~QCursorData() +{ + if (type == TYPE_ImageCursor) { + if (curs.cp.my_cursor) { + QMacCocoaAutoReleasePool pool; + [static_cast(curs.cp.nscursor) release]; + } + } else if(type == TYPE_ThemeCursor) { + delete curs.tc.anim; + } + type = TYPE_None; + + delete bm; + delete bmm; + if(currentCursor == this) + currentCursor = 0; +} + +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("Qt: QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + // This is silly, but this is apparently called outside the constructor, so we have + // to be ready for that case. + QCursorData *x = new QCursorData; + x->ref = 1; + x->mId = ++nextCursorId; + x->bm = new QBitmap(bitmap); + x->bmm = new QBitmap(mask); + x->cshape = Qt::BitmapCursor; + x->hx = hotX >= 0 ? hotX : bitmap.width() / 2; + x->hy = hotY >= 0 ? hotY : bitmap.height() / 2; + return x; +} + +Qt::HANDLE QCursor::handle() const +{ + if(!QCursorData::initialized) + QCursorData::initialize(); + if(d->type == QCursorData::TYPE_None) + d->update(); + return (Qt::HANDLE)d->mId; +} + +QPoint QCursor::pos() +{ + return flipPoint([NSEvent mouseLocation]).toPoint(); +} + +void QCursor::setPos(int x, int y) +{ +#ifdef QT_MAC_USE_COCOA + CGPoint pos; + pos.x = x; + pos.y = y; + + CGEventRef e = CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, 0); + CGEventPost(kCGHIDEventTap, e); + CFRelease(e); +#else + CGWarpMouseCursorPosition(CGPointMake(x, y)); + + /* I'm not too keen on doing this, but this makes it a lot easier, so I just + send the event back through the event system and let it get propagated correctly + ideally this would not really need to be faked --Sam + */ + QWidget *widget = 0; + if(QWidget *grb = QWidget::mouseGrabber()) + widget = grb; + else + widget = QApplication::widgetAt(QPoint(x, y)); + if(widget) { + QMouseEvent me(QMouseEvent::MouseMove, widget->mapFromGlobal(QPoint(x, y)), Qt::NoButton, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + qt_sendSpontaneousEvent(widget, &me); + } +#endif +} + +void QCursorData::initCursorFromBitmap() +{ + NSImage *nsimage; + QImage finalCursor(bm->size(), QImage::Format_ARGB32); + QImage bmi = bm->toImage().convertToFormat(QImage::Format_RGB32); + QImage bmmi = bmm->toImage().convertToFormat(QImage::Format_RGB32); + for (int row = 0; row < finalCursor.height(); ++row) { + QRgb *bmData = reinterpret_cast(bmi.scanLine(row)); + QRgb *bmmData = reinterpret_cast(bmmi.scanLine(row)); + QRgb *finalData = reinterpret_cast(finalCursor.scanLine(row)); + for (int col = 0; col < finalCursor.width(); ++col) { + if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) { + finalData[col] = 0xffffffff; + } else if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) { + finalData[col] = 0x7f000000; + } else if (bmmData[col] == 0xffffffff && bmData[col] == 0xffffffff) { + finalData[col] = 0x00000000; + } else { + finalData[col] = 0xff000000; + } + } + } + type = QCursorData::TYPE_ImageCursor; + curs.cp.my_cursor = true; + QPixmap bmCopy = QPixmap::fromImage(finalCursor); + NSPoint hotSpot = { hx, hy }; + nsimage = static_cast(qt_mac_create_nsimage(bmCopy)); + curs.cp.nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot: hotSpot]; + [nsimage release]; +} + +void QCursorData::initCursorFromPixmap() +{ + type = QCursorData::TYPE_ImageCursor; + curs.cp.my_cursor = true; + NSPoint hotSpot = { hx, hy }; + NSImage *nsimage; + nsimage = static_cast(qt_mac_create_nsimage(pixmap)); + curs.cp.nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot: hotSpot]; + [nsimage release]; +} + +void QCursorData::update() +{ + if(!QCursorData::initialized) + QCursorData::initialize(); + if(type != QCursorData::TYPE_None) + return; + + /* Note to self... *** + * mask x data + * 0xFF x 0x00 == fully opaque white + * 0x00 x 0xFF == xor'd black + * 0xFF x 0xFF == fully opaque black + * 0x00 x 0x00 == fully transparent + */ + + if (hx < 0) + hx = 0; + if (hy < 0) + hy = 0; + +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + static const uchar cur_ver_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, + 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0f, 0xf0, + 0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00 }; + static const uchar mcur_ver_bits[] = { + 0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x0f, 0xe0, 0x1f, 0xf0, 0x3f, 0xf8, + 0x7f, 0xfc, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x7f, 0xfc, 0x3f, 0xf8, + 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80 }; + + static const uchar cur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x18, 0x30, + 0x38, 0x38, 0x7f, 0xfc, 0x7f, 0xfc, 0x38, 0x38, 0x18, 0x30, 0x08, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x0c, 0x60, 0x1c, 0x70, 0x3c, 0x78, + 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3c, 0x78, + 0x1c, 0x70, 0x0c, 0x60, 0x04, 0x40, 0x00, 0x00 }; + + static const uchar cur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0x78, + 0x00, 0xf8, 0x01, 0xd8, 0x23, 0x88, 0x37, 0x00, 0x3e, 0x00, 0x3c, 0x00, + 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, 0xfc, + 0x41, 0xfc, 0x63, 0xfc, 0x77, 0xdc, 0x7f, 0x8c, 0x7f, 0x04, 0x7e, 0x00, + 0x7f, 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x00, 0x00 }; + + static const uchar cur_bdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, 0x00, + 0x37, 0x00, 0x23, 0x88, 0x01, 0xd8, 0x00, 0xf8, 0x00, 0x78, 0x00, 0xf8, + 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_bdiag_bits[] = { + 0x00, 0x00, 0x7f, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7e, 0x00, 0x7f, 0x04, + 0x7f, 0x8c, 0x77, 0xdc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, 0xfc, + 0x03, 0xfc, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x00 }; + + static const unsigned char cur_up_arrow_bits[] = { + 0x00, 0x80, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20, 0x04, 0x10, + 0x04, 0x10, 0x08, 0x08, 0x0f, 0x78, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, + 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0 }; + static const unsigned char mcur_up_arrow_bits[] = { + 0x00, 0x80, 0x01, 0xc0, 0x01, 0xc0, 0x03, 0xe0, 0x03, 0xe0, 0x07, 0xf0, + 0x07, 0xf0, 0x0f, 0xf8, 0x0f, 0xf8, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, + 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0 }; +#endif + const uchar *cursorData = 0; + const uchar *cursorMaskData = 0; +#ifdef QT_MAC_USE_COCOA + switch (cshape) { // map Q cursor to MAC cursor + case Qt::BitmapCursor: { + if (pixmap.isNull()) + initCursorFromBitmap(); + else + initCursorFromPixmap(); + break; } + case Qt::BlankCursor: { + pixmap = QPixmap(16, 16); + pixmap.fill(Qt::transparent); + initCursorFromPixmap(); + break; } + case Qt::ArrowCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor arrowCursor]; + break; } + case Qt::CrossCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor crosshairCursor]; + break; } + case Qt::WaitCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/spincursor.png")); + initCursorFromPixmap(); + break; } + case Qt::IBeamCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor IBeamCursor]; + break; } + case Qt::SizeAllCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/pluscursor.png")); + initCursorFromPixmap(); + break; } + case Qt::WhatsThisCursor: { //for now just use the pointing hand + case Qt::PointingHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor pointingHandCursor]; + break; } + case Qt::BusyCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/waitcursor.png")); + initCursorFromPixmap(); + break; } + case Qt::SplitVCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor resizeUpDownCursor]; + break; } + case Qt::SplitHCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor resizeLeftRightCursor]; + break; } + case Qt::ForbiddenCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/forbiddencursor.png")); + initCursorFromPixmap(); + break; } + case Qt::OpenHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor openHandCursor]; + break; + case Qt::ClosedHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor closedHandCursor]; + break; + case Qt::DragCopyCursor: + type = QCursorData::TYPE_ThemeCursor; + if ([NSCursor respondsToSelector:@selector(dragCopyCursor)]) + curs.cp.nscursor = [NSCursor performSelector:@selector(dragCopyCursor)]; + break; + case Qt::DragMoveCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor arrowCursor]; + break; + case Qt::DragLinkCursor: + type = QCursorData::TYPE_ThemeCursor; + if ([NSCursor respondsToSelector:@selector(dragLinkCursor)]) + curs.cp.nscursor = [NSCursor performSelector:@selector(dragLinkCursor)]; + break; +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + case Qt::SizeVerCursor: + cursorData = cur_ver_bits; + cursorMaskData = mcur_ver_bits; + hx = hy = 8; + break; + case Qt::SizeHorCursor: + cursorData = cur_hor_bits; + cursorMaskData = mcur_hor_bits; + hx = hy = 8; + break; + case Qt::SizeBDiagCursor: + cursorData = cur_fdiag_bits; + cursorMaskData = mcur_fdiag_bits; + hx = hy = 8; + break; + case Qt::SizeFDiagCursor: + cursorData = cur_bdiag_bits; + cursorMaskData = mcur_bdiag_bits; + hx = hy = 8; + break; + case Qt::UpArrowCursor: + cursorData = cur_up_arrow_bits; + cursorMaskData = mcur_up_arrow_bits; + hx = 8; + break; +#endif + default: + qWarning("Qt: QCursor::update: Invalid cursor shape %d", cshape); + return; + } +#else + // Carbon + switch (cshape) { // map Q cursor to MAC cursor + case Qt::BitmapCursor: { + if (pixmap.isNull()) + initCursorFromBitmap(); + else + initCursorFromPixmap(); + break; } + case Qt::BlankCursor: { + pixmap = QPixmap(16, 16); + pixmap.fill(Qt::transparent); + initCursorFromPixmap(); + break; } + case Qt::ArrowCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeArrowCursor; + break; } + case Qt::CrossCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeCrossCursor; + break; } + case Qt::WaitCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeWatchCursor; + break; } + case Qt::IBeamCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeIBeamCursor; + break; } + case Qt::SizeAllCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemePlusCursor; + break; } + case Qt::WhatsThisCursor: { //for now just use the pointing hand + case Qt::PointingHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemePointingHandCursor; + break; } + case Qt::BusyCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeSpinningCursor; + break; } + case Qt::SplitVCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeResizeUpDownCursor; + break; } + case Qt::SplitHCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeResizeLeftRightCursor; + break; } + case Qt::ForbiddenCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeNotAllowedCursor; + break; } + case Qt::OpenHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeOpenHandCursor; + break; + case Qt::ClosedHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeClosedHandCursor; + break; + case Qt::DragMoveCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeArrowCursor; + break; + case Qt::DragCopyCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeCopyArrowCursor; + break; + case Qt::DragLinkCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeAliasArrowCursor; + break; +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + case Qt::SizeVerCursor: + cursorData = cur_ver_bits; + cursorMaskData = mcur_ver_bits; + hx = hy = 8; + break; + case Qt::SizeHorCursor: + cursorData = cur_hor_bits; + cursorMaskData = mcur_hor_bits; + hx = hy = 8; + break; + case Qt::SizeBDiagCursor: + cursorData = cur_fdiag_bits; + cursorMaskData = mcur_fdiag_bits; + hx = hy = 8; + break; + case Qt::SizeFDiagCursor: + cursorData = cur_bdiag_bits; + cursorMaskData = mcur_bdiag_bits; + hx = hy = 8; + break; + case Qt::UpArrowCursor: + cursorData = cur_up_arrow_bits; + cursorMaskData = mcur_up_arrow_bits; + hx = 8; + break; +#endif + default: + qWarning("Qt: QCursor::update: Invalid cursor shape %d", cshape); + return; + } +#endif + + if (cursorData) { + bm = new QBitmap(QBitmap::fromData(QSize(16, 16), cursorData, + QImage::Format_Mono)); + bmm = new QBitmap(QBitmap::fromData(QSize(16, 16), cursorMaskData, + QImage::Format_Mono)); + initCursorFromBitmap(); + } + +#if 0 + if(type == QCursorData::TYPE_CursPtr && curs.cp.hcurs && curs.cp.my_cursor) { + curs.cp.hcurs->hotSpot.h = hx >= 0 ? hx : 8; + curs.cp.hcurs->hotSpot.v = hy >= 0 ? hy : 8; + } +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qcursor_p.h b/src/gui/kernel/qcursor_p.h new file mode 100644 index 0000000000..660a2a5e8b --- /dev/null +++ b/src/gui/kernel/qcursor_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCURSOR_P_H +#define QCURSOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qatomic.h" +#include "QtCore/qglobal.h" +#include "QtCore/qnamespace.h" +#include "QtGui/qpixmap.h" + +# if defined (Q_WS_MAC) +# include "private/qt_mac_p.h" +# elif defined(Q_WS_X11) +# include "private/qt_x11_p.h" +# elif defined(Q_WS_WIN) +# include "QtCore/qt_windows.h" +# elif defined(Q_OS_SYMBIAN) +# include "private/qt_s60_p.h" +#endif + +QT_BEGIN_NAMESPACE + +#if defined (Q_WS_MAC) +void *qt_mac_nsCursorForQCursor(const QCursor &c); +class QMacAnimateCursor; +#endif + +class QBitmap; +class QCursorData { +public: + QCursorData(Qt::CursorShape s = Qt::ArrowCursor); + ~QCursorData(); + + static void initialize(); + static void cleanup(); + + QAtomicInt ref; + Qt::CursorShape cshape; + QBitmap *bm, *bmm; + QPixmap pixmap; + short hx, hy; +#if defined (Q_WS_MAC) + int mId; +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) + int id; +#endif +#if defined (Q_WS_WIN) + HCURSOR hcurs; +#elif defined (Q_WS_X11) + XColor fg, bg; + Cursor hcurs; + Pixmap pm, pmm; +#elif defined (Q_WS_MAC) + enum { TYPE_None, TYPE_ImageCursor, TYPE_ThemeCursor } type; + union { + struct { + uint my_cursor:1; + void *nscursor; + } cp; + struct { + QMacAnimateCursor *anim; + ThemeCursor curs; + } tc; + } curs; + void initCursorFromBitmap(); + void initCursorFromPixmap(); +#elif defined Q_OS_SYMBIAN + void loadShapeFromResource(RWsSpriteBase& target, QString resource, int hx, int hy, int interval=0); + void constructShapeSprite(RWsSpriteBase& target); + void constructCursorSprite(RWsSpriteBase& target); + RWsPointerCursor pcurs; + RWsSprite scurs; + RPointerArray nativeSpriteMembers; +#endif + static bool initialized; + void update(); + static QCursorData *setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY); +}; + +extern QCursorData *qt_cursorTable[Qt::LastCursor + 1]; // qcursor.cpp + +QT_END_NAMESPACE + +#endif // QCURSOR_P_H diff --git a/src/gui/kernel/qcursor_qpa.cpp b/src/gui/kernel/qcursor_qpa.cpp new file mode 100644 index 0000000000..a6ae7d30f5 --- /dev/null +++ b/src/gui/kernel/qcursor_qpa.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +#ifndef QT_NO_CURSOR + +static int nextCursorId = Qt::BitmapCursor; + +/***************************************************************************** + Internal QCursorData class + *****************************************************************************/ + +QCursorData::QCursorData(Qt::CursorShape s) + : cshape(s), bm(0), bmm(0), hx(0), hy(0), id(s) +{ + ref = 1; +} + +QCursorData::~QCursorData() +{ + delete bm; + delete bmm; +} + + +/***************************************************************************** + Global cursors + *****************************************************************************/ + +extern QCursorData *qt_cursorTable[Qt::LastCursor + 1]; // qcursor.cpp + +int QCursor::handle() const +{ + return d->id; +} + + +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + QCursorData *d = new QCursorData; + d->bm = new QBitmap(bitmap); + d->bmm = new QBitmap(mask); + d->cshape = Qt::BitmapCursor; + d->id = ++nextCursorId; + d->hx = hotX >= 0 ? hotX : bitmap.width() / 2; + d->hy = hotY >= 0 ? hotY : bitmap.height() / 2; + + return d; +} + +void QCursorData::update() +{ +} + +#endif //QT_NO_CURSOR + +extern int qt_last_x,qt_last_y; + +QPoint QCursor::pos() +{ + return QPoint(qt_last_x, qt_last_y); +} + +void QCursor::setPos(int x, int y) +{ + // Need to check, since some X servers generate null mouse move + // events, causing looping in applications which call setPos() on + // every mouse move event. + // + if (pos() == QPoint(x, y)) + return; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qcursor_qws.cpp b/src/gui/kernel/qcursor_qws.cpp new file mode 100644 index 0000000000..60674c9507 --- /dev/null +++ b/src/gui/kernel/qcursor_qws.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +#ifndef QT_NO_CURSOR + +static int nextCursorId = Qt::BitmapCursor; + +/***************************************************************************** + Internal QCursorData class + *****************************************************************************/ + +QCursorData::QCursorData(Qt::CursorShape s) + : cshape(s), bm(0), bmm(0), hx(0), hy(0), id(s) +{ + ref = 1; +} + +QCursorData::~QCursorData() +{ + delete bm; + delete bmm; + QT_TRY { + QPaintDevice::qwsDisplay()->destroyCursor(id); + } QT_CATCH(const std::bad_alloc &) { + // do nothing. + } +} + + +/***************************************************************************** + Global cursors + *****************************************************************************/ + +int QCursor::handle() const +{ + return d->id; +} + + +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + QCursorData *d = new QCursorData; + d->bm = new QBitmap(bitmap); + d->bmm = new QBitmap(mask); + d->cshape = Qt::BitmapCursor; + d->id = ++nextCursorId; + d->hx = hotX >= 0 ? hotX : bitmap.width() / 2; + d->hy = hotY >= 0 ? hotY : bitmap.height() / 2; + + QPaintDevice::qwsDisplay()->defineCursor(d->id, *d->bm, *d->bmm, d->hx, d->hy); + return d; +} + +void QCursorData::update() +{ +} + +#endif //QT_NO_CURSOR + +extern int *qt_last_x,*qt_last_y; + +QPoint QCursor::pos() +{ + // This doesn't know about hotspots yet so we disable it + //qt_accel_update_cursor(); + if (qt_last_x) + return QPoint(*qt_last_x, *qt_last_y); + else + return QPoint(); +} + +void QCursor::setPos(int x, int y) +{ + // Need to check, since some X servers generate null mouse move + // events, causing looping in applications which call setPos() on + // every mouse move event. + // + if (pos() == QPoint(x, y)) + return; + QPaintDevice::qwsDisplay()->setCursorPosition(x, y); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qcursor_s60.cpp b/src/gui/kernel/qcursor_s60.cpp new file mode 100644 index 0000000000..8dfe87ef81 --- /dev/null +++ b/src/gui/kernel/qcursor_s60.cpp @@ -0,0 +1,533 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_CURSOR +static QCursor cursorSprite; +static int cursorSpriteVisible; +#endif + +//pos and setpos are required whether cursors are configured or not. +QPoint QCursor::pos() +{ + return S60->lastCursorPos; +} + +void QCursor::setPos(int x, int y) +{ + //clip to screen size (window server allows a sprite hotspot to be outside the screen) + if (x < 0) + x=0; + else if (x >= S60->screenWidthInPixels) + x = S60->screenWidthInPixels - 1; + if (y < 0) + y = 0; + else if (y >= S60->screenHeightInPixels) + y = S60->screenHeightInPixels - 1; + +#ifndef QT_NO_CURSOR +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors && cursorSpriteVisible) + cursorSprite.d->scurs.SetPosition(TPoint(x,y)); + else +#endif + S60->wsSession().SetPointerCursorPosition(TPoint(x, y)); +#endif + S60->lastCursorPos = QPoint(x, y); + //send a fake mouse move event, so that enter/leave events go to the widget hierarchy + QWidget *w = QApplication::topLevelAt(S60->lastCursorPos); + if (w) { + CCoeControl* ctrl = w->effectiveWinId(); + TPoint epos(x, y); + TPoint cpos = epos - ctrl->PositionRelativeToScreen(); + TPointerEvent fakeEvent; + fakeEvent.iType = TPointerEvent::EMove; + fakeEvent.iModifiers = 0U; + fakeEvent.iPosition = cpos; + fakeEvent.iParentPosition = epos; + ctrl->HandlePointerEventL(fakeEvent); + } +} + +#ifndef QT_NO_CURSOR +/* + * Request cursor to be turned on or off. + * Reference counted, so 2 on + 1 off = on, for example + */ +void qt_symbian_set_cursor_visible(bool visible) { + if (visible) + cursorSpriteVisible++; + else + cursorSpriteVisible--; + Q_ASSERT(cursorSpriteVisible >=0); + + if (cursorSpriteVisible && !S60->mouseInteractionEnabled) { +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) + qt_symbian_show_pointer_sprite(); + else +#endif + S60->wsSession().SetPointerCursorMode(EPointerCursorNormal); + } else if (!cursorSpriteVisible && S60->mouseInteractionEnabled) { +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) + qt_symbian_hide_pointer_sprite(); + else +#endif + S60->wsSession().SetPointerCursorMode(EPointerCursorNone); + } + S60->mouseInteractionEnabled = ((cursorSpriteVisible > 0) ? true : false); +} + +/* + * Check if the cursor is on or off + */ +bool qt_symbian_is_cursor_visible() { + return S60->mouseInteractionEnabled; +} + +QCursorData::QCursorData(Qt::CursorShape s) : + cshape(s), bm(0), bmm(0), hx(0), hy(0), pcurs() +{ + ref = 1; +} + +QCursorData::~QCursorData() +{ + for(int i=0;iiBitmap; + delete nativeSpriteMembers[i]->iMaskBitmap; + } + nativeSpriteMembers.ResetAndDestroy(); + pcurs.Close(); + delete bm; + delete bmm; +} + +/* Create a bitmap cursor, this is called by public constructors in the + * generic QCursor code. + */ +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + QCursorData *d = new QCursorData; + d->bm = new QBitmap(bitmap); + d->bmm = new QBitmap(mask); + d->cshape = Qt::BitmapCursor; + d->hx = hotX >= 0 ? hotX : bitmap.width() / 2; + d->hy = hotY >= 0 ? hotY : bitmap.height() / 2; + return d; +} + +/* + * returns an opaque native handle to a cursor. + * It happens to be the address of the native handle, as window server handles + * are not POD types. Note there is no QCursor(HANDLE) constructor on Symbian, + * Mac or QWS. + */ +Qt::HANDLE QCursor::handle() const +{ + if (d->pcurs.WsHandle()) + return reinterpret_cast (&(d->pcurs)); + +#ifdef Q_SYMBIAN_HAS_SYSTEM_CURSORS + // don't construct shape cursors, QApplication_s60 will use the system cursor instead + if (!(d->bm)) + return 0; +#endif + + d->pcurs = RWsPointerCursor(S60->wsSession()); + d->pcurs.Construct(0); + d->constructCursorSprite(d->pcurs); + d->pcurs.Activate(); + + return reinterpret_cast (&(d->pcurs)); +} + +#ifndef Q_SYMBIAN_HAS_SYSTEM_CURSORS +/* + * Loads a single cursor shape from resources and appends it to a native sprite. + * Animated cursors (e.g. the busy cursor) have multiple members. + */ +void QCursorData::loadShapeFromResource(RWsSpriteBase& target, QString resource, int hx, int hy, int interval) +{ + QPixmap pix; + CFbsBitmap* native; + QScopedPointer member(new TSpriteMember); + member->iInterval = interval; + member->iInvertMask = false; + member->iMaskBitmap = 0; // all shapes are RGBA + member->iDrawMode = CGraphicsContext::EDrawModePEN; + member->iOffset = TPoint(-hx, -hy); + QString res(QLatin1String(":/trolltech/symbian/cursors/images/%1.png")); + pix.load(res.arg(resource)); + native = pix.toSymbianCFbsBitmap(); + member->iBitmap = native; + qt_symbian_throwIfError(nativeSpriteMembers.Append(member.data())); + target.AppendMember(*(member.take())); +} + +//TODO: after 4.6, connect with style & skins? +/* + * Constructs the native cursor from resources compiled into QtGui + * This is needed only when the platform doesn't have system cursors. + * + * System cursors are higher performance, since they are constructed once + * and shared by all applications by specifying the shape number. + * Due to symbian platform security considerations, and the fact most + * existing phones have a broken RWsPointerCursor, system cursors are not + * being used. + */ +void QCursorData::constructShapeSprite(RWsSpriteBase& target) +{ + int i; + switch (cshape) { + default: + qWarning("QCursorData::constructShapeSprite unknown shape %d", cshape); + //fall through and give arrow cursor + case Qt::ArrowCursor: + loadShapeFromResource(target, QLatin1String("pointer"), 1, 1); + break; + case Qt::UpArrowCursor: + loadShapeFromResource(target, QLatin1String("uparrow"), 4, 0); + break; + case Qt::CrossCursor: + loadShapeFromResource(target, QLatin1String("cross"), 7, 7); + break; + case Qt::WaitCursor: + for (i = 1; i <= 12; i++) { + loadShapeFromResource(target, QString(QLatin1String("wait%1")).arg(i), 7, 7, 1000000); + } + break; + case Qt::IBeamCursor: + loadShapeFromResource(target, QLatin1String("ibeam"), 3, 10); + break; + case Qt::SizeVerCursor: + loadShapeFromResource(target, QLatin1String("sizever"), 4, 8); + break; + case Qt::SizeHorCursor: + loadShapeFromResource(target, QLatin1String("sizehor"), 8, 4); + break; + case Qt::SizeBDiagCursor: + loadShapeFromResource(target, QLatin1String("sizebdiag"), 8, 8); + break; + case Qt::SizeFDiagCursor: + loadShapeFromResource(target, QLatin1String("sizefdiag"), 8, 8); + break; + case Qt::SizeAllCursor: + loadShapeFromResource(target, QLatin1String("sizeall"), 7, 7); + break; + case Qt::BlankCursor: + loadShapeFromResource(target, QLatin1String("blank"), 0, 0); + break; + case Qt::SplitVCursor: + loadShapeFromResource(target, QLatin1String("splitv"), 7, 7); + break; + case Qt::SplitHCursor: + loadShapeFromResource(target, QLatin1String("splith"), 7, 7); + break; + case Qt::PointingHandCursor: + loadShapeFromResource(target, QLatin1String("handpoint"), 5, 0); + break; + case Qt::ForbiddenCursor: + loadShapeFromResource(target, QLatin1String("forbidden"), 7, 7); + break; + case Qt::WhatsThisCursor: + loadShapeFromResource(target, QLatin1String("whatsthis"), 1, 1); + break; + case Qt::BusyCursor: + loadShapeFromResource(target, QLatin1String("busy3"), 1, 1, 1000000); + loadShapeFromResource(target, QLatin1String("busy6"), 1, 1, 1000000); + loadShapeFromResource(target, QLatin1String("busy9"), 1, 1, 1000000); + loadShapeFromResource(target, QLatin1String("busy12"), 1, 1, 1000000); + break; + case Qt::OpenHandCursor: + loadShapeFromResource(target, QLatin1String("openhand"), 7, 7); + break; + case Qt::ClosedHandCursor: + loadShapeFromResource(target, QLatin1String("closehand"), 7, 7); + break; + } +} +#endif + +/* + * Common code between the sprite workaround and standard modes of operation. + * RWsSpriteBase is the base class for both RWsSprite and RWsPointerCursor. + * It is called from both handle() and qt_s60_show_pointer_sprite() + */ +void QCursorData::constructCursorSprite(RWsSpriteBase& target) +{ + int count = nativeSpriteMembers.Count(); + if (count) { + // already constructed + for (int i = 0; i < count; i++) + target.AppendMember(*(nativeSpriteMembers[i])); + + return; + } + if (pixmap.isNull() && !bm) { +#ifndef Q_SYMBIAN_HAS_SYSTEM_CURSORS + //shape cursor + constructShapeSprite(target); +#endif + return; + } + QScopedPointer member(new TSpriteMember); + if (pixmap.isNull()) { + //construct mono cursor + member->iBitmap = bm->toSymbianCFbsBitmap(); + member->iMaskBitmap = bmm->toSymbianCFbsBitmap(); + } + else { + //construct normal cursor + member->iBitmap = pixmap.toSymbianCFbsBitmap(); + if (pixmap.hasAlphaChannel()) { + member->iMaskBitmap = 0; //use alpha blending + } + else if (pixmap.hasAlpha()) { + member->iMaskBitmap = pixmap.mask().toSymbianCFbsBitmap(); + } + else { + member->iMaskBitmap = 0; //opaque rectangle cursor (due to EDrawModePEN) + } + } + + member->iDrawMode = CGraphicsContext::EDrawModePEN; + member->iInvertMask = EFalse; + member->iInterval = 0; + member->iOffset = TPoint(-(hx), -(hy)); //Symbian hotspot coordinates are negative + qt_symbian_throwIfError(nativeSpriteMembers.Append(member.data())); + target.AppendMember(*(member.take())); +} + +/* + * shows the pointer sprite by constructing a native handle, and registering + * it with the window server. + * Only used when the sprite workaround is in use. + */ +void qt_symbian_show_pointer_sprite() +{ + if (cursorSprite.d) { + if (cursorSprite.d->scurs.WsHandle()) + cursorSprite.d->scurs.Close(); + } else { + cursorSprite = QCursor(Qt::ArrowCursor); + } + + cursorSprite.d->scurs = RWsSprite(S60->wsSession()); + QPoint pos = QCursor::pos(); + cursorSprite.d->scurs.Construct(S60->windowGroup(), TPoint(pos.x(), pos.y()), ESpriteNoChildClip | ESpriteNoShadows); + + cursorSprite.d->constructCursorSprite(cursorSprite.d->scurs); + cursorSprite.d->scurs.Activate(); +} + +/* + * hides the pointer sprite by closing the native handle. + * Only used when the sprite workaround is in use. + */ +void qt_symbian_hide_pointer_sprite() +{ + if (cursorSprite.d) { + cursorSprite.d->scurs.Close(); + } +} + +/* + * Changes the cursor sprite to the cursor specified. + * Only used when the sprite workaround is in use. + */ +void qt_symbian_set_pointer_sprite(const QCursor& cursor) +{ + if (S60->mouseInteractionEnabled) + qt_symbian_hide_pointer_sprite(); + cursorSprite = cursor; + if (S60->mouseInteractionEnabled) + qt_symbian_show_pointer_sprite(); +} + +/* + * When using sprites as a workaround on phones that have a broken + * RWsPointerCursor, this function is called in response to pointer events + * and when QCursor::setPos() is called. + * Performance is worse than a real pointer cursor, due to extra context + * switches vs. the window server moving the cursor by itself. + */ +void qt_symbian_move_cursor_sprite() +{ + if (S60->mouseInteractionEnabled) { + cursorSprite.d->scurs.SetPosition(TPoint(S60->lastCursorPos.x(), S60->lastCursorPos.y())); + } +} + +/* + * Translate from Qt::CursorShape to OS system pointer cursor list index. + * Currently we control the implementation of the system pointer cursor list, + * so this function is trivial. That may not always be the case. + */ +TInt qt_symbian_translate_cursor_shape(Qt::CursorShape shape) +{ + return (TInt) shape; +} + +/* + Internal function called from QWidget::setCursor() + force is true if this function is called from dispatchEnterLeave, it means that the + mouse is actually directly under this widget. +*/ +void qt_symbian_set_cursor(QWidget *w, bool force) +{ + static QPointer lastUnderMouse = 0; + if (force) { + lastUnderMouse = w; + } + else if (w->testAttribute(Qt::WA_WState_Created) && lastUnderMouse + && lastUnderMouse->effectiveWinId() == w->effectiveWinId()) { + w = lastUnderMouse; + } + + if (!S60->curWin && w && w->internalWinId()) + return; + QWidget* cW = w && !w->internalWinId() ? w : QWidget::find(S60->curWin); + if (!cW || cW->window() != w->window() || !cW->isVisible() || !cW->underMouse() + || QApplication::overrideCursor()) + return; + +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) + qt_symbian_set_pointer_sprite(cW->cursor()); + else +#endif + qt_symbian_setWindowCursor(cW->cursor(), w->effectiveWinId()); +} + +/* + * Makes the specified cursor appear above a specific native window group + * Called from QSymbianControl and QApplication::restoreOverrideCursor + * + * Window server is needed for this, so there is no equivalent when using + * the sprite workaround. + */ +void qt_symbian_setWindowGroupCursor(const QCursor &cursor, RWindowTreeNode &node) +{ + Qt::HANDLE handle = cursor.handle(); + if (handle) { + RWsPointerCursor *pcurs = reinterpret_cast (handle); + node.SetCustomPointerCursor(*pcurs); + } else +#ifdef Q_SYMBIAN_HAS_SYSTEM_CURSORS + { + TInt shape = qt_symbian_translate_cursor_shape(cursor.shape()); + node.SetPointerCursor(shape); + } +#else + qWarning("qt_s60_setWindowGroupCursor - null handle"); +#endif +} + +/* + * Makes the specified cursor appear above a specific native window + * Called from QSymbianControl and QApplication::restoreOverrideCursor + * + * Window server is needed for this, so there is no equivalent when using + * the sprite workaround. + */ +void qt_symbian_setWindowCursor(const QCursor &cursor, const CCoeControl* wid) +{ + //find the window for this control + while (!wid->OwnsWindow()) { + wid = wid->Parent(); + if (!wid) + return; + } + RWindowTreeNode *node = wid->DrawableWindow(); + qt_symbian_setWindowGroupCursor(cursor, *node); +} + +/* + * Makes the specified cursor appear everywhere. + * Called from QApplication::setOverrideCursor + */ +void qt_symbian_setGlobalCursor(const QCursor &cursor) +{ +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + if (S60->brokenPointerCursors) { + qt_symbian_set_pointer_sprite(cursor); + } else +#endif + { + //because of the internals of window server, we need to force the cursor + //to be set in all child windows too, otherwise when the cursor is over + //the child window it may show a widget cursor or arrow cursor instead, + //depending on construction order. + QListIterator iter(QWidgetPrivate::mapper->uniqueKeys()); + while(iter.hasNext()) + { + CCoeControl *ctrl = iter.next(); + if(ctrl->OwnsWindow()) { + RWindowTreeNode *node = ctrl->DrawableWindow(); + qt_symbian_setWindowGroupCursor(cursor, *node); + } + } + } +} +QT_END_NAMESPACE +#endif // QT_NO_CURSOR diff --git a/src/gui/kernel/qcursor_win.cpp b/src/gui/kernel/qcursor_win.cpp new file mode 100644 index 0000000000..8a9362ebfc --- /dev/null +++ b/src/gui/kernel/qcursor_win.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#ifndef QT_NO_CURSOR + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Internal QCursorData class + *****************************************************************************/ + +QCursorData::QCursorData(Qt::CursorShape s) + : cshape(s), bm(0), bmm(0), hx(0), hy(0), hcurs(0) +{ + ref = 1; +} + +QCursorData::~QCursorData() +{ + delete bm; + delete bmm; +#if !defined(Q_WS_WINCE) || defined(GWES_ICONCURS) + if (hcurs) + DestroyCursor(hcurs); +#endif +} + + +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + QCursorData *d = new QCursorData; + d->bm = new QBitmap(bitmap); + d->bmm = new QBitmap(mask); + d->hcurs = 0; + d->cshape = Qt::BitmapCursor; + d->hx = hotX >= 0 ? hotX : bitmap.width()/2; + d->hy = hotY >= 0 ? hotY : bitmap.height()/2; + return d; +} + +HCURSOR QCursor::handle() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (!d->hcurs) + d->update(); + return d->hcurs; +} + +QCursor::QCursor(HCURSOR handle) +{ + d = new QCursorData(Qt::CustomCursor); + d->hcurs = handle; +} + +#endif //QT_NO_CURSOR + +QPoint QCursor::pos() +{ + POINT p; + GetCursorPos(&p); + return QPoint(p.x, p.y); +} + +void QCursor::setPos(int x, int y) +{ + SetCursorPos(x, y); +} + +#ifndef QT_NO_CURSOR + +extern HBITMAP qt_createIconMask(const QBitmap &bitmap); + +static HCURSOR create32BitCursor(const QPixmap &pixmap, int hx, int hy) +{ + HCURSOR cur = 0; +#if !defined(Q_WS_WINCE) + QBitmap mask = pixmap.mask(); + if (mask.isNull()) { + mask = QBitmap(pixmap.size()); + mask.fill(Qt::color1); + } + + HBITMAP ic = pixmap.toWinHBITMAP(QPixmap::Alpha); + HBITMAP im = qt_createIconMask(mask); + + ICONINFO ii; + ii.fIcon = 0; + ii.xHotspot = hx; + ii.yHotspot = hy; + ii.hbmMask = im; + ii.hbmColor = ic; + + cur = CreateIconIndirect(&ii); + + DeleteObject(ic); + DeleteObject(im); +#elif defined(GWES_ICONCURS) + QImage bbits, mbits; + bool invb, invm; + bbits = pixmap.toImage().convertToFormat(QImage::Format_Mono); + mbits = pixmap.toImage().convertToFormat(QImage::Format_Mono); + invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1)); + invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1)); + + int sysW = GetSystemMetrics(SM_CXCURSOR); + int sysH = GetSystemMetrics(SM_CYCURSOR); + int sysN = qMax(1, sysW / 8); + int n = qMax(1, bbits.width() / 8); + int h = bbits.height(); + + uchar* xBits = new uchar[sysH * sysN]; + uchar* xMask = new uchar[sysH * sysN]; + int x = 0; + for (int i = 0; i < sysH; ++i) { + if (i >= h) { + memset(&xBits[x] , 255, sysN); + memset(&xMask[x] , 0, sysN); + x += sysN; + } else { + int fillWidth = n > sysN ? sysN : n; + uchar *bits = bbits.scanLine(i); + uchar *mask = mbits.scanLine(i); + for (int j = 0; j < fillWidth; ++j) { + uchar b = bits[j]; + uchar m = mask[j]; + if (invb) + b ^= 0xFF; + if (invm) + m ^= 0xFF; + xBits[x] = ~m; + xMask[x] = b ^ m; + ++x; + } + for (int j = fillWidth; j < sysN; ++j ) { + xBits[x] = 255; + xMask[x] = 0; + ++x; + } + } + } + + cur = CreateCursor(qWinAppInst(), hx, hy, sysW, sysH, + xBits, xMask); +#else + Q_UNUSED(pixmap); + Q_UNUSED(hx); + Q_UNUSED(hy); +#endif + return cur; +} + +void QCursorData::update() +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (hcurs) + return; + + if (cshape == Qt::BitmapCursor && !pixmap.isNull()) { + hcurs = create32BitCursor(pixmap, hx, hy); + if (hcurs) + return; + } + + + // Non-standard Windows cursors are created from bitmaps + + static const uchar vsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar vsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar hsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03, + 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar hsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00, + 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07, + 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00, + 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar phand_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, + 0x80, 0x04, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, 0x80, 0x04, 0x00, 0x00, + 0x80, 0x1c, 0x00, 0x00, 0x80, 0xe4, 0x00, 0x00, 0x80, 0x24, 0x03, 0x00, + 0x80, 0x24, 0x05, 0x00, 0xb8, 0x24, 0x09, 0x00, 0xc8, 0x00, 0x09, 0x00, + 0x88, 0x00, 0x08, 0x00, 0x90, 0x00, 0x08, 0x00, 0xa0, 0x00, 0x08, 0x00, + 0x20, 0x00, 0x08, 0x00, 0x40, 0x00, 0x08, 0x00, 0x40, 0x00, 0x04, 0x00, + 0x80, 0x00, 0x04, 0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x01, 0x02, 0x00, + 0x00, 0x01, 0x02, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + static const uchar phandm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, + 0x80, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, + 0x80, 0x1f, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x80, 0xff, 0x03, 0x00, + 0x80, 0xff, 0x07, 0x00, 0xb8, 0xff, 0x0f, 0x00, 0xf8, 0xff, 0x0f, 0x00, + 0xf8, 0xff, 0x0f, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0xe0, 0xff, 0x0f, 0x00, + 0xe0, 0xff, 0x0f, 0x00, 0xc0, 0xff, 0x0f, 0x00, 0xc0, 0xff, 0x07, 0x00, + 0x80, 0xff, 0x07, 0x00, 0x80, 0xff, 0x07, 0x00, 0x00, 0xff, 0x03, 0x00, + 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + static const uchar openhand_bits[] = { + 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92, + 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20, + 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00}; + static const uchar openhandm_bits[] = { + 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff, + 0xf6,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f, + 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00}; + static const uchar closedhand_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50, + 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10, + 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}; + static const uchar closedhandm_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f, + 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f, + 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00}; + + static const uchar * const cursor_bits32[] = { + vsplit_bits, vsplitm_bits, hsplit_bits, hsplitm_bits, + phand_bits, phandm_bits + }; + + wchar_t *sh = 0; + switch (cshape) { // map to windows cursor + case Qt::ArrowCursor: + sh = IDC_ARROW; + break; + case Qt::UpArrowCursor: + sh = IDC_UPARROW; + break; + case Qt::CrossCursor: + sh = IDC_CROSS; + break; + case Qt::WaitCursor: + sh = IDC_WAIT; + break; + case Qt::IBeamCursor: + sh = IDC_IBEAM; + break; + case Qt::SizeVerCursor: + sh = IDC_SIZENS; + break; + case Qt::SizeHorCursor: + sh = IDC_SIZEWE; + break; + case Qt::SizeBDiagCursor: + sh = IDC_SIZENESW; + break; + case Qt::SizeFDiagCursor: + sh = IDC_SIZENWSE; + break; + case Qt::SizeAllCursor: + sh = IDC_SIZEALL; + break; + case Qt::ForbiddenCursor: + sh = IDC_NO; + break; + case Qt::WhatsThisCursor: + sh = IDC_HELP; + break; + case Qt::BusyCursor: + sh = IDC_APPSTARTING; + break; + case Qt::PointingHandCursor: + sh = IDC_HAND; + break; + case Qt::BlankCursor: + case Qt::SplitVCursor: + case Qt::SplitHCursor: + case Qt::OpenHandCursor: + case Qt::ClosedHandCursor: + case Qt::BitmapCursor: { + QImage bbits, mbits; + bool invb, invm; + if (cshape == Qt::BlankCursor) { + bbits = QImage(32, 32, QImage::Format_Mono); + bbits.fill(0); // ignore color table + mbits = bbits.copy(); + hx = hy = 16; + invb = invm = false; + } else if (cshape == Qt::OpenHandCursor || cshape == Qt::ClosedHandCursor) { + bool open = cshape == Qt::OpenHandCursor; + QBitmap cb = QBitmap::fromData(QSize(16, 16), open ? openhand_bits : closedhand_bits); + QBitmap cm = QBitmap::fromData(QSize(16, 16), open ? openhandm_bits : closedhandm_bits); + bbits = cb.toImage().convertToFormat(QImage::Format_Mono); + mbits = cm.toImage().convertToFormat(QImage::Format_Mono); + hx = hy = 8; + invb = invm = false; + } else if (cshape != Qt::BitmapCursor) { + int i = cshape - Qt::SplitVCursor; + QBitmap cb = QBitmap::fromData(QSize(32, 32), cursor_bits32[i * 2]); + QBitmap cm = QBitmap::fromData(QSize(32, 32), cursor_bits32[i * 2 + 1]); + bbits = cb.toImage().convertToFormat(QImage::Format_Mono); + mbits = cm.toImage().convertToFormat(QImage::Format_Mono); + if (cshape == Qt::PointingHandCursor) { + hx = 7; + hy = 0; + } else + hx = hy = 16; + invb = invm = false; + } else { + bbits = bm->toImage().convertToFormat(QImage::Format_Mono); + mbits = bmm->toImage().convertToFormat(QImage::Format_Mono); + invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1)); + invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1)); + } + int n = qMax(1, bbits.width() / 8); + int h = bbits.height(); +#if !defined(Q_WS_WINCE) + uchar* xBits = new uchar[h * n]; + uchar* xMask = new uchar[h * n]; + int x = 0; + for (int i = 0; i < h; ++i) { + uchar *bits = bbits.scanLine(i); + uchar *mask = mbits.scanLine(i); + for (int j = 0; j < n; ++j) { + uchar b = bits[j]; + uchar m = mask[j]; + if (invb) + b ^= 0xff; + if (invm) + m ^= 0xff; + xBits[x] = ~m; + xMask[x] = b ^ m; + ++x; + } + } + hcurs = CreateCursor(qWinAppInst(), hx, hy, bbits.width(), bbits.height(), + xBits, xMask); + delete [] xBits; + delete [] xMask; +#elif defined(GWES_ICONCURS) // Q_WS_WINCE + // Windows CE only supports fixed cursor size. + int sysW = GetSystemMetrics(SM_CXCURSOR); + int sysH = GetSystemMetrics(SM_CYCURSOR); + int sysN = qMax(1, sysW / 8); + uchar* xBits = new uchar[sysH * sysN]; + uchar* xMask = new uchar[sysH * sysN]; + int x = 0; + for (int i = 0; i < sysH; ++i) { + if (i >= h) { + memset(&xBits[x] , 255, sysN); + memset(&xMask[x] , 0, sysN); + x += sysN; + } else { + int fillWidth = n > sysN ? sysN : n; + uchar *bits = bbits.scanLine(i); + uchar *mask = mbits.scanLine(i); + for (int j = 0; j < fillWidth; ++j) { + uchar b = bits[j]; + uchar m = mask[j]; + if (invb) + b ^= 0xFF; + if (invm) + m ^= 0xFF; + xBits[x] = ~m; + xMask[x] = b ^ m; + ++x; + } + for (int j = fillWidth; j < sysN; ++j ) { + xBits[x] = 255; + xMask[x] = 0; + ++x; + } + } + } + + hcurs = CreateCursor(qWinAppInst(), hx, hy, sysW, sysH, + xBits, xMask); + delete [] xBits; + delete [] xMask; +#else + Q_UNUSED(n); + Q_UNUSED(h); +#endif + return; + } + case Qt::DragCopyCursor: + case Qt::DragMoveCursor: + case Qt::DragLinkCursor: { + QPixmap pixmap = QApplicationPrivate::instance()->getPixmapCursor(cshape); + hcurs = create32BitCursor(pixmap, hx, hy); + } + default: + qWarning("QCursor::update: Invalid cursor shape %d", cshape); + return; + } +#ifdef Q_WS_WINCE + hcurs = LoadCursor(0, sh); +#else + hcurs = (HCURSOR)LoadImage(0, sh, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); +#endif +} + +QT_END_NAMESPACE +#endif // QT_NO_CURSOR diff --git a/src/gui/kernel/qcursor_x11.cpp b/src/gui/kernel/qcursor_x11.cpp new file mode 100644 index 0000000000..d0ed98e1fe --- /dev/null +++ b/src/gui/kernel/qcursor_x11.cpp @@ -0,0 +1,637 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef QT_NO_XCURSOR +# include +#endif // QT_NO_XCURSOR + +#ifndef QT_NO_XFIXES +# include +#endif // QT_NO_XFIXES + +#include "qx11info_x11.h" +#include + +QT_BEGIN_NAMESPACE + +// Define QT_USE_APPROXIMATE_CURSORS when compiling if you REALLY want to +// use the ugly X11 cursors. + +/***************************************************************************** + Internal QCursorData class + *****************************************************************************/ + +QCursorData::QCursorData(Qt::CursorShape s) + : cshape(s), bm(0), bmm(0), hx(0), hy(0), hcurs(0), pm(0), pmm(0) +{ + ref = 1; +} + +QCursorData::~QCursorData() +{ + Display *dpy = X11 ? X11->display : (Display*)0; + + // Add in checking for the display too as on HP-UX + // we seem to get a core dump as the cursor data is + // deleted again from main() on exit... + if (hcurs && dpy) + XFreeCursor(dpy, hcurs); + if (pm && dpy) + XFreePixmap(dpy, pm); + if (pmm && dpy) + XFreePixmap(dpy, pmm); + delete bm; + delete bmm; +} + +#ifndef QT_NO_CURSOR +QCursor::QCursor(Qt::HANDLE cursor) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + d = new QCursorData(Qt::CustomCursor); + d->hcurs = cursor; +} + +#endif + +QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY) +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) { + qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)"); + QCursorData *c = qt_cursorTable[0]; + c->ref.ref(); + return c; + } + QCursorData *d = new QCursorData; + d->ref = 1; + + extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); // qpixmap_x11.cpp + d->bm = new QBitmap(qt_toX11Pixmap(bitmap)); + d->bmm = new QBitmap(qt_toX11Pixmap(mask)); + + d->hcurs = 0; + d->cshape = Qt::BitmapCursor; + d->hx = hotX >= 0 ? hotX : bitmap.width() / 2; + d->hy = hotY >= 0 ? hotY : bitmap.height() / 2; + d->fg.red = 0x0000; + d->fg.green = 0x0000; + d->fg.blue = 0x0000; + d->bg.red = 0xffff; + d->bg.green = 0xffff; + d->bg.blue = 0xffff; + return d; +} + + + +#ifndef QT_NO_CURSOR +Qt::HANDLE QCursor::handle() const +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (!d->hcurs) + d->update(); + return d->hcurs; +} +#endif + +QPoint QCursor::pos() +{ + Window root; + Window child; + int root_x, root_y, win_x, win_y; + uint buttons; + Display* dpy = X11->display; + for (int i = 0; i < ScreenCount(dpy); ++i) { + if (XQueryPointer(dpy, QX11Info::appRootWindow(i), &root, &child, &root_x, &root_y, + &win_x, &win_y, &buttons)) + + return QPoint(root_x, root_y); + } + return QPoint(); +} + +/*! \internal +*/ +#ifndef QT_NO_CURSOR +int QCursor::x11Screen() +{ + Window root; + Window child; + int root_x, root_y, win_x, win_y; + uint buttons; + Display* dpy = X11->display; + for (int i = 0; i < ScreenCount(dpy); ++i) { + if (XQueryPointer(dpy, QX11Info::appRootWindow(i), &root, &child, &root_x, &root_y, + &win_x, &win_y, &buttons)) + return i; + } + return -1; +} +#endif + +void QCursor::setPos(int x, int y) +{ + QPoint current, target(x, y); + + // this is copied from pos(), since we need the screen number for the correct + // root window in the XWarpPointer call + Window root; + Window child; + int root_x, root_y, win_x, win_y; + uint buttons; + Display* dpy = X11->display; + int screen; + for (screen = 0; screen < ScreenCount(dpy); ++screen) { + if (XQueryPointer(dpy, QX11Info::appRootWindow(screen), &root, &child, &root_x, &root_y, + &win_x, &win_y, &buttons)) { + current = QPoint(root_x, root_y); + break; + } + } + + if (screen >= ScreenCount(dpy)) + return; + + // Need to check, since some X servers generate null mouse move + // events, causing looping in applications which call setPos() on + // every mouse move event. + // + if (current == target) + return; + + XWarpPointer(X11->display, XNone, QX11Info::appRootWindow(screen), 0, 0, 0, 0, x, y); +} + + +/*! + \internal + + Creates the cursor. +*/ + +void QCursorData::update() +{ + if (!QCursorData::initialized) + QCursorData::initialize(); + if (hcurs) + return; + + Display *dpy = X11->display; + Window rootwin = QX11Info::appRootWindow(); + + if (cshape == Qt::BitmapCursor) { + extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); // qpixmap_x11.cpp +#ifndef QT_NO_XRENDER + if (!pixmap.isNull() && X11->use_xrender) { + pixmap = qt_toX11Pixmap(pixmap); + hcurs = XRenderCreateCursor (X11->display, pixmap.x11PictureHandle(), hx, hy); + } else +#endif + { + hcurs = XCreatePixmapCursor(dpy, bm->handle(), bmm->handle(), &fg, &bg, hx, hy); + } + return; + } + + static const char *cursorNames[] = { + "left_ptr", + "up_arrow", + "cross", + "wait", + "ibeam", + "size_ver", + "size_hor", + "size_bdiag", + "size_fdiag", + "size_all", + "blank", + "split_v", + "split_h", + "pointing_hand", + "forbidden", + "whats_this", + "left_ptr_watch", + "openhand", + "closedhand", + "copy", + "move", + "link" + }; + +#ifndef QT_NO_XCURSOR + if (X11->ptrXcursorLibraryLoadCursor) { + // special case for non-standard dnd-* cursors + switch (cshape) { + case Qt::DragCopyCursor: + hcurs = X11->ptrXcursorLibraryLoadCursor(dpy, "dnd-copy"); + break; + case Qt::DragMoveCursor: + hcurs = X11->ptrXcursorLibraryLoadCursor(dpy, "dnd-move"); + break; + case Qt::DragLinkCursor: + hcurs = X11->ptrXcursorLibraryLoadCursor(dpy, "dnd-link"); + break; + default: + break; + } + if (!hcurs) + hcurs = X11->ptrXcursorLibraryLoadCursor(dpy, cursorNames[cshape]); + } + if (hcurs) + return; +#endif // QT_NO_XCURSOR + + static const uchar cur_blank_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + // Non-standard X11 cursors are created from bitmaps + +#ifndef QT_USE_APPROXIMATE_CURSORS + static const uchar cur_ver_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0xc0, 0x03, 0xe0, 0x07, 0xf0, 0x0f, + 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xf0, 0x0f, + 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00 }; + static const uchar mcur_ver_bits[] = { + 0x00, 0x00, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, + 0xfc, 0x7f, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xfc, 0x7f, 0xf8, 0x3f, + 0xf0, 0x1f, 0xe0, 0x0f, 0xc0, 0x07, 0x80, 0x03 }; + static const uchar cur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0x30, 0x18, + 0x38, 0x38, 0xfc, 0x7f, 0xfc, 0x7f, 0x38, 0x38, 0x30, 0x18, 0x20, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0x60, 0x0c, 0x70, 0x1c, 0x78, 0x3c, + 0xfc, 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfc, 0x7f, 0x78, 0x3c, + 0x70, 0x1c, 0x60, 0x0c, 0x40, 0x04, 0x00, 0x00 }; + static const uchar cur_bdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, + 0x00, 0x37, 0x88, 0x23, 0xd8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0xf8, 0x00, + 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_bdiag_bits[] = { + 0x00, 0x00, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7f, 0x00, 0x7e, 0x04, 0x7f, + 0x8c, 0x7f, 0xdc, 0x77, 0xfc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, + 0xfc, 0x03, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00 }; + static const uchar cur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, + 0xf8, 0x00, 0xd8, 0x01, 0x88, 0x23, 0x00, 0x37, 0x00, 0x3e, 0x00, 0x3c, + 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, + 0xfc, 0x41, 0xfc, 0x63, 0xdc, 0x77, 0x8c, 0x7f, 0x04, 0x7f, 0x00, 0x7e, + 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x7f, 0x00, 0x00 }; + static const uchar *cursor_bits16[] = { + cur_ver_bits, mcur_ver_bits, cur_hor_bits, mcur_hor_bits, + cur_bdiag_bits, mcur_bdiag_bits, cur_fdiag_bits, mcur_fdiag_bits, + 0, 0, cur_blank_bits, cur_blank_bits }; + + static const uchar vsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar vsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar hsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03, + 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar hsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00, + 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07, + 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00, + 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar whatsthis_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0xf0, 0x07, 0x00, + 0x09, 0x18, 0x0e, 0x00, 0x11, 0x1c, 0x0e, 0x00, 0x21, 0x1c, 0x0e, 0x00, + 0x41, 0x1c, 0x0e, 0x00, 0x81, 0x1c, 0x0e, 0x00, 0x01, 0x01, 0x07, 0x00, + 0x01, 0x82, 0x03, 0x00, 0xc1, 0xc7, 0x01, 0x00, 0x49, 0xc0, 0x01, 0x00, + 0x95, 0xc0, 0x01, 0x00, 0x93, 0xc0, 0x01, 0x00, 0x21, 0x01, 0x00, 0x00, + 0x20, 0xc1, 0x01, 0x00, 0x40, 0xc2, 0x01, 0x00, 0x40, 0x02, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + static const uchar whatsthism_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0x00, 0x07, 0xf8, 0x0f, 0x00, + 0x0f, 0xfc, 0x1f, 0x00, 0x1f, 0x3e, 0x1f, 0x00, 0x3f, 0x3e, 0x1f, 0x00, + 0x7f, 0x3e, 0x1f, 0x00, 0xff, 0x3e, 0x1f, 0x00, 0xff, 0x9d, 0x0f, 0x00, + 0xff, 0xc3, 0x07, 0x00, 0xff, 0xe7, 0x03, 0x00, 0x7f, 0xe0, 0x03, 0x00, + 0xf7, 0xe0, 0x03, 0x00, 0xf3, 0xe0, 0x03, 0x00, 0xe1, 0xe1, 0x03, 0x00, + 0xe0, 0xe1, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + static const uchar busy_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x41, 0xe0, 0xff, 0x00, 0x81, 0x20, 0x80, 0x00, 0x01, 0xe1, 0xff, 0x00, + 0x01, 0x42, 0x40, 0x00, 0xc1, 0x47, 0x40, 0x00, 0x49, 0x40, 0x55, 0x00, + 0x95, 0x80, 0x2a, 0x00, 0x93, 0x00, 0x15, 0x00, 0x21, 0x01, 0x0a, 0x00, + 0x20, 0x01, 0x11, 0x00, 0x40, 0x82, 0x20, 0x00, 0x40, 0x42, 0x44, 0x00, + 0x80, 0x41, 0x4a, 0x00, 0x00, 0x40, 0x55, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0x20, 0x80, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static const uchar busym_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x7f, 0xe0, 0xff, 0x00, 0xff, 0xe0, 0xff, 0x00, 0xff, 0xe1, 0xff, 0x00, + 0xff, 0xc3, 0x7f, 0x00, 0xff, 0xc7, 0x7f, 0x00, 0x7f, 0xc0, 0x7f, 0x00, + 0xf7, 0x80, 0x3f, 0x00, 0xf3, 0x00, 0x1f, 0x00, 0xe1, 0x01, 0x0e, 0x00, + 0xe0, 0x01, 0x1f, 0x00, 0xc0, 0x83, 0x3f, 0x00, 0xc0, 0xc3, 0x7f, 0x00, + 0x80, 0xc1, 0x7f, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0xe0, 0xff, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + static const uchar * const cursor_bits32[] = { + vsplit_bits, vsplitm_bits, hsplit_bits, hsplitm_bits, + 0, 0, 0, 0, whatsthis_bits, whatsthism_bits, busy_bits, busym_bits + }; + + static const uchar forbidden_bits[] = { + 0x00,0x00,0x00,0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xf0,0x00,0x38,0xc0,0x01, + 0x7c,0x80,0x03,0xec,0x00,0x03,0xce,0x01,0x07,0x86,0x03,0x06,0x06,0x07,0x06, + 0x06,0x0e,0x06,0x06,0x1c,0x06,0x0e,0x38,0x07,0x0c,0x70,0x03,0x1c,0xe0,0x03, + 0x38,0xc0,0x01,0xf0,0xe0,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00,0x00,0x00,0x00 }; + + static const uchar forbiddenm_bits[] = { + 0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xff,0x00,0xf8,0xff,0x01,0xfc,0xf0,0x03, + 0xfe,0xc0,0x07,0xfe,0x81,0x07,0xff,0x83,0x0f,0xcf,0x07,0x0f,0x8f,0x0f,0x0f, + 0x0f,0x1f,0x0f,0x0f,0x3e,0x0f,0x1f,0xfc,0x0f,0x1e,0xf8,0x07,0x3e,0xf0,0x07, + 0xfc,0xe0,0x03,0xf8,0xff,0x01,0xf0,0xff,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00}; + + static const uchar openhand_bits[] = { + 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92, + 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20, + 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00}; + static const uchar openhandm_bits[] = { + 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff, + 0xf6,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f, + 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00}; + static const uchar closedhand_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50, + 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10, + 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}; + static const uchar closedhandm_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f, + 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f, + 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00}; + + static const uchar * const cursor_bits20[] = { + forbidden_bits, forbiddenm_bits + }; + + if ((cshape >= Qt::SizeVerCursor && cshape < Qt::SizeAllCursor) + || cshape == Qt::BlankCursor) { + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + int i = (cshape - Qt::SizeVerCursor) * 2; + pm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits16[i]), 16, 16); + pmm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits16[i + 1]), 16, 16); + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, 8, 8); + } else if ((cshape >= Qt::SplitVCursor && cshape <= Qt::SplitHCursor) + || cshape == Qt::WhatsThisCursor || cshape == Qt::BusyCursor) { + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + int i = (cshape - Qt::SplitVCursor) * 2; + pm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits32[i]), 32, 32); + pmm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits32[i + 1]), 32, 32); + int hs = (cshape == Qt::PointingHandCursor || cshape == Qt::WhatsThisCursor + || cshape == Qt::BusyCursor) ? 0 : 16; + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, hs, hs); + } else if (cshape == Qt::ForbiddenCursor) { + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + int i = (cshape - Qt::ForbiddenCursor) * 2; + pm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits20[i]), 20, 20); + pmm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(cursor_bits20[i + 1]), 20, 20); + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, 10, 10); + } else if (cshape == Qt::OpenHandCursor || cshape == Qt::ClosedHandCursor) { + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + bool open = cshape == Qt::OpenHandCursor; + pm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(open ? openhand_bits : closedhand_bits), 16, 16); + pmm = XCreateBitmapFromData(dpy, rootwin, reinterpret_cast(open ? openhandm_bits : closedhandm_bits), 16, 16); + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, 8, 8); + } else if (cshape == Qt::DragCopyCursor || cshape == Qt::DragMoveCursor + || cshape == Qt::DragLinkCursor) { + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + QImage image = QApplicationPrivate::instance()->getPixmapCursor(cshape).toImage(); + pm = QX11PixmapData::createBitmapFromImage(image); + pmm = QX11PixmapData::createBitmapFromImage(image.createAlphaMask().convertToFormat(QImage::Format_MonoLSB)); + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, 8, 8); + } + + if (hcurs) + { +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && X11->ptrXFixesSetCursorName) + X11->ptrXFixesSetCursorName(dpy, hcurs, cursorNames[cshape]); +#endif /* ! QT_NO_XFIXES */ + return; + } + +#endif /* ! QT_USE_APPROXIMATE_CURSORS */ + + uint sh; + switch (cshape) { // map Q cursor to X cursor + case Qt::ArrowCursor: + sh = XC_left_ptr; + break; + case Qt::UpArrowCursor: + sh = XC_center_ptr; + break; + case Qt::CrossCursor: + sh = XC_crosshair; + break; + case Qt::WaitCursor: + sh = XC_watch; + break; + case Qt::IBeamCursor: + sh = XC_xterm; + break; + case Qt::SizeAllCursor: + sh = XC_fleur; + break; + case Qt::PointingHandCursor: + sh = XC_hand2; + break; +#ifdef QT_USE_APPROXIMATE_CURSORS + case Qt::SizeBDiagCursor: + sh = XC_top_right_corner; + break; + case Qt::SizeFDiagCursor: + sh = XC_bottom_right_corner; + break; + case Qt::BlankCursor: + XColor bg, fg; + bg.red = 255 << 8; + bg.green = 255 << 8; + bg.blue = 255 << 8; + fg.red = 0; + fg.green = 0; + fg.blue = 0; + pm = XCreateBitmapFromData(dpy, rootwin, cur_blank_bits, 16, 16); + pmm = XCreateBitmapFromData(dpy, rootwin, cur_blank_bits, 16, 16); + hcurs = XCreatePixmapCursor(dpy, pm, pmm, &fg, &bg, 8, 8); + return; + break; + case Qt::SizeVerCursor: + case Qt::SplitVCursor: + sh = XC_sb_v_double_arrow; + break; + case Qt::SizeHorCursor: + case Qt::SplitHCursor: + sh = XC_sb_h_double_arrow; + break; + case Qt::WhatsThisCursor: + sh = XC_question_arrow; + break; + case Qt::ForbiddenCursor: + sh = XC_circle; + break; + case Qt::BusyCursor: + sh = XC_watch; + break; + case Qt::DragCopyCursor: + sh = XC_tcross; + break; + case Qt::DragLinkCursor: + sh = XC_center_ptr; + break; + case Qt::DragMoveCursor: + sh = XC_top_left_arrow; + break; +#endif /* QT_USE_APPROXIMATE_CURSORS */ + default: + qWarning("QCursor::update: Invalid cursor shape %d", cshape); + return; + } + hcurs = XCreateFontCursor(dpy, sh); + +#ifndef QT_NO_XFIXES + if (X11->use_xfixes && X11->ptrXFixesSetCursorName) + X11->ptrXFixesSetCursorName(dpy, hcurs, cursorNames[cshape]); +#endif /* ! QT_NO_XFIXES */ +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget.cpp b/src/gui/kernel/qdesktopwidget.cpp new file mode 100644 index 0000000000..6e1414dc5d --- /dev/null +++ b/src/gui/kernel/qdesktopwidget.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" +#include "qdesktopwidget.h" +#include "qwidget_p.h" + +QT_BEGIN_NAMESPACE + +const QRect QDesktopWidget::screenGeometry(const QWidget *widget) const +{ + if (!widget) { + qWarning("QDesktopWidget::screenGeometry(): Attempt " + "to get the screen geometry of a null widget"); + return QRect(); + } + QRect rect = QWidgetPrivate::screenGeometry(widget); + if (rect.isNull()) + return screenGeometry(screenNumber(widget)); + else return rect; +} + +const QRect QDesktopWidget::availableGeometry(const QWidget *widget) const +{ + if (!widget) { + qWarning("QDesktopWidget::availableGeometry(): Attempt " + "to get the available geometry of a null widget"); + return QRect(); + } + QRect rect = QWidgetPrivate::screenGeometry(widget); + if (rect.isNull()) + return availableGeometry(screenNumber(widget)); + else + return rect; +} + +QT_END_NAMESPACE + diff --git a/src/gui/kernel/qdesktopwidget.h b/src/gui/kernel/qdesktopwidget.h new file mode 100644 index 0000000000..deb896029a --- /dev/null +++ b/src/gui/kernel/qdesktopwidget.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDESKTOPWIDGET_H +#define QDESKTOPWIDGET_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QApplication; +class QDesktopWidgetPrivate; + +class Q_GUI_EXPORT QDesktopWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool virtualDesktop READ isVirtualDesktop) + Q_PROPERTY(int screenCount READ screenCount NOTIFY screenCountChanged) + Q_PROPERTY(int primaryScreen READ primaryScreen) +public: + QDesktopWidget(); + ~QDesktopWidget(); + + bool isVirtualDesktop() const; + + int numScreens() const; + int screenCount() const; + int primaryScreen() const; + + int screenNumber(const QWidget *widget = 0) const; + int screenNumber(const QPoint &) const; + + QWidget *screen(int screen = -1); + + const QRect screenGeometry(int screen = -1) const; + const QRect screenGeometry(const QWidget *widget) const; + const QRect screenGeometry(const QPoint &point) const + { return screenGeometry(screenNumber(point)); } + + const QRect availableGeometry(int screen = -1) const; + const QRect availableGeometry(const QWidget *widget) const; + const QRect availableGeometry(const QPoint &point) const + { return availableGeometry(screenNumber(point)); } + +Q_SIGNALS: + void resized(int); + void workAreaResized(int); + void screenCountChanged(int); + +protected: + void resizeEvent(QResizeEvent *e); + +private: + Q_DISABLE_COPY(QDesktopWidget) + Q_DECLARE_PRIVATE(QDesktopWidget) + + friend class QApplication; + friend class QApplicationPrivate; +}; + +inline int QDesktopWidget::screenCount() const +{ return numScreens(); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDESKTOPWIDGET_H diff --git a/src/gui/kernel/qdesktopwidget.qdoc b/src/gui/kernel/qdesktopwidget.qdoc new file mode 100644 index 0000000000..a79a098d74 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget.qdoc @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QDesktopWidget + \brief The QDesktopWidget class provides access to screen information on multi-head systems. + + \ingroup advanced + \ingroup desktop + + Systems with more than one graphics card and monitor can manage the + physical screen space available either as multiple desktops, or as a + large virtual desktop. + + This class provides information about the user's desktop, such as its + total size, number of screens, the geometry of each screen, and whether + they are configured as separate desktops or a single virtual desktop. + + Widgets provided by Qt use this class to place tooltips, menus and + dialog boxes on the correct screen for their parent or application + widgets. Applications can use this class to obtain information that + can be used to save window positions, or to place child widgets and + dialogs on one particular screen. + + \section1 Obtaining a Desktop Widget + + The QApplication::desktop() function is used to get an instance of + QDesktopWidget. + + The widget's screenGeometry() function provides information about the + geometry of the available screens with. The number of screens + available is returned by screenCount, and the screenCountChanged() + signal is emitted when screens are added or removed. + The screen number that a particular point or widget is located in + is returned by screenNumber(). + + \section1 Screen Geometry + + To obtain the dimensions of a particular screen, call the screenGeometry() + function. On some desktop environments, not all of the screen is + available for applications to use; for example, an application dock or + menu bar may take up some space. Use the availableGeometry() function + to obtain the available area for applications. + + QDesktopWidget also inherits the QWidget properties, width() and + height(), which specify the size of the desktop. However, for + desktops with multiple screens, the size of the desktop is the union + of all the screen sizes, so width() and height() should \e not be + used for computing the size of a widget to be placed on one of the + screens. + + On systems that are configured to use the available screens as a + single, large virtual desktop, the virtualDesktop property will be + set to true. In this case, the widget's size is usually the size of + the bounding rectangle of all the screens. + + \section1 Use of the Primary Screen + + For an application, the screen where the main widget resides is the + primary screen. This is stored in the primaryScreen property. + All windows opened in the context of the application should be + constrained to the boundaries of the primary screen; for example, + it would be inconvenient if a dialog box popped up on a different + screen, or split over two screens. + + \image qdesktopwidget.png Managing Multiple Screens + + In the illustration above, Application One's primary screen is + screen 0, and App Two's primary screen is screen 1. + + \sa QApplication, QApplication::desktop(), QX11Info::appRootWindow() +*/ + +/*! + \fn QDesktopWidget::QDesktopWidget() + + \internal + + Creates the desktop widget. + + If the system supports a virtual desktop, this widget will have + the size of the virtual desktop; otherwise this widget will have + the size of the primary screen. + + Instead of using QDesktopWidget directly, use QApplication::desktop(). +*/ + +/*! + \fn QDesktopWidget::~QDesktopWidget() + + \internal + + Destroys the desktop widget and frees any allocated resources. +*/ + +/*! + \fn int QDesktopWidget::numScreens() const + + Returns the number of available screens. + + \obsolete + + This function is deprecated. Use screenCount instead. + + \sa primaryScreen +*/ + +/*! + \fn QWidget *QDesktopWidget::screen(int screen) + + Returns a widget that represents the screen with index \a screen + (a value of -1 means the default screen). + + If the system uses a virtual desktop, the returned widget will + have the geometry of the entire virtual desktop; i.e., bounding + every \a screen. + + \sa primaryScreen, screenCount, virtualDesktop +*/ + +/*! + \fn const QRect QDesktopWidget::availableGeometry(int screen) const + + Returns the available geometry of the screen with index \a screen. What + is available will be subrect of screenGeometry() based on what the + platform decides is available (for example excludes the dock and menu bar + on Mac OS X, or the task bar on Windows). The default screen is used if + \a screen is -1. + + \sa screenNumber(), screenGeometry() +*/ + +/*! + \fn const QRect QDesktopWidget::availableGeometry(const QWidget *widget) const + \overload + + Returns the available geometry of the screen which contains \a widget. + + \sa screenGeometry() +*/ + +/*! + \fn const QRect QDesktopWidget::availableGeometry(const QPoint &p) const + \overload + + Returns the available geometry of the screen which contains \a p. + + \sa screenGeometry() +*/ + + +/*! + \fn const QRect QDesktopWidget::screenGeometry(int screen) const + + Returns the geometry of the screen with index \a screen. The default + screen is used if \a screen is -1. + + \sa screenNumber() +*/ + +/*! + \fn const QRect QDesktopWidget::screenGeometry(const QWidget *widget) const + \overload + + Returns the geometry of the screen which contains \a widget. +*/ + +/*! + \fn const QRect QDesktopWidget::screenGeometry(const QPoint &p) const + \overload + + Returns the geometry of the screen which contains \a p. +*/ + + +/*! + \fn int QDesktopWidget::screenNumber(const QWidget *widget) const + + Returns the index of the screen that contains the largest + part of \a widget, or -1 if the widget not on a screen. + + \sa primaryScreen +*/ + +/*! + \fn int QDesktopWidget::screenNumber(const QPoint &point) const + + \overload + Returns the index of the screen that contains the \a point, or the + screen which is the shortest distance from the \a point. + + \sa primaryScreen +*/ + +/*! + \fn void QDesktopWidget::resizeEvent(QResizeEvent *event) + \reimp +*/ + +/*! + \fn void QDesktopWidget::resized(int screen) + + This signal is emitted when the size of \a screen changes. +*/ + +/*! + \fn void QDesktopWidget::workAreaResized(int screen) + + This signal is emitted when the work area available on \a screen changes. +*/ + +/*! + \property QDesktopWidget::screenCount + \brief the number of screens currently available on the system. + + \since 4.6 + + \sa screenCountChanged() +*/ + +/*! + \property QDesktopWidget::primaryScreen + \brief the index of the screen that is configured to be the primary screen + on the system. +*/ + +/*! + \property QDesktopWidget::virtualDesktop + + \brief if the system manages the available screens in a virtual desktop. + + For virtual desktops, screen() will always return the same widget. + The size of the virtual desktop is the size of this desktop + widget. +*/ + +/*! + \fn void QDesktopWidget::screenCountChanged(int newCount) + + \since 4.6 + + This signal is emitted when the number of screens changes to \a newCount. + + \sa screenCount +*/ diff --git a/src/gui/kernel/qdesktopwidget_mac.mm b/src/gui/kernel/qdesktopwidget_mac.mm new file mode 100644 index 0000000000..0b529c9843 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_mac.mm @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#import + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include +#include "qwidget_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + Externals + *****************************************************************************/ + +/***************************************************************************** + QDesktopWidget member functions + *****************************************************************************/ + +Q_GLOBAL_STATIC(QDesktopWidgetImplementation, qdesktopWidgetImplementation) + +QDesktopWidgetImplementation::QDesktopWidgetImplementation() + : appScreen(0) +{ + onResize(); +} + +QDesktopWidgetImplementation::~QDesktopWidgetImplementation() +{ +} + +QDesktopWidgetImplementation *QDesktopWidgetImplementation::instance() +{ + return qdesktopWidgetImplementation(); +} + +QRect QDesktopWidgetImplementation::availableRect(int screenIndex) const +{ + if (screenIndex < 0 || screenIndex >= screenCount) + screenIndex = appScreen; + + return availableRects[screenIndex].toRect(); +} + +QRect QDesktopWidgetImplementation::screenRect(int screenIndex) const +{ + if (screenIndex < 0 || screenIndex >= screenCount) + screenIndex = appScreen; + + return screenRects[screenIndex].toRect(); +} + +void QDesktopWidgetImplementation::onResize() +{ + QMacCocoaAutoReleasePool pool; + NSArray *displays = [NSScreen screens]; + screenCount = [displays count]; + + screenRects.clear(); + availableRects.clear(); + NSRect primaryRect = [[displays objectAtIndex:0] frame]; + for (int i = 0; iappScreen; +} + +int QDesktopWidget::numScreens() const +{ + return qdesktopWidgetImplementation()->screenCount; +} + +QWidget *QDesktopWidget::screen(int) +{ + return this; +} + +const QRect QDesktopWidget::availableGeometry(int screen) const +{ + return qdesktopWidgetImplementation()->availableRect(screen); +} + +const QRect QDesktopWidget::screenGeometry(int screen) const +{ + return qdesktopWidgetImplementation()->screenRect(screen); +} + +int QDesktopWidget::screenNumber(const QWidget *widget) const +{ + QDesktopWidgetImplementation *d = qdesktopWidgetImplementation(); + if (!widget) + return d->appScreen; + QRect frame = widget->frameGeometry(); + if (!widget->isWindow()) + frame.moveTopLeft(widget->mapToGlobal(QPoint(0,0))); + int maxSize = -1, maxScreen = -1; + for (int i = 0; i < d->screenCount; ++i) { + QRect rr = d->screenRect(i); + QRect sect = rr.intersected(frame); + int size = sect.width() * sect.height(); + if (size > maxSize && sect.width() > 0 && sect.height() > 0) { + maxSize = size; + maxScreen = i; + } + } + return maxScreen; +} + +int QDesktopWidget::screenNumber(const QPoint &point) const +{ + QDesktopWidgetImplementation *d = qdesktopWidgetImplementation(); + int closestScreen = -1; + int shortestDistance = INT_MAX; + for (int i = 0; i < d->screenCount; ++i) { + QRect rr = d->screenRect(i); + int thisDistance = QWidgetPrivate::pointToRect(point, rr); + if (thisDistance < shortestDistance) { + shortestDistance = thisDistance; + closestScreen = i; + } + } + return closestScreen; +} + +void QDesktopWidget::resizeEvent(QResizeEvent *) +{ + QDesktopWidgetImplementation *d = qdesktopWidgetImplementation(); + + const int oldScreenCount = d->screenCount; + const QVector oldRects(d->screenRects); + const QVector oldWorks(d->availableRects); + + d->onResize(); + + for (int i = 0; i < qMin(oldScreenCount, d->screenCount); ++i) { + if (oldRects.at(i) != d->screenRects.at(i)) + emit resized(i); + } + for (int i = 0; i < qMin(oldScreenCount, d->screenCount); ++i) { + if (oldWorks.at(i) != d->availableRects.at(i)) + emit workAreaResized(i); + } + + if (oldScreenCount != d->screenCount) + emit screenCountChanged(d->screenCount); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_mac_p.h b/src/gui/kernel/qdesktopwidget_mac_p.h new file mode 100644 index 0000000000..ac638ea7d8 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_mac_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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 + +QT_BEGIN_NAMESPACE + +class QDesktopWidgetImplementation +{ +public: + QDesktopWidgetImplementation(); + ~QDesktopWidgetImplementation(); + static QDesktopWidgetImplementation *instance(); + + int appScreen; + int screenCount; + + QVector availableRects; + QVector screenRects; + + QRect availableRect(int screenIndex) const; + QRect screenRect(int screenIndex) const; + void onResize(); +}; + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_qpa.cpp b/src/gui/kernel/qdesktopwidget_qpa.cpp new file mode 100644 index 0000000000..cff05f5836 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_qpa.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdesktopwidget.h" +#include "private/qapplication_p.h" +#include "private/qgraphicssystem_p.h" +#include +#include "private/qwidget_p.h" +#include "private/qdesktopwidget_qpa_p.h" +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +void QDesktopWidgetPrivate::updateScreenList() +{ + QList screenList = QApplicationPrivate::platformIntegration()->screens(); + int targetLength = screenList.length(); + int currentLength = screens.length(); + + // Add or remove screen widgets as necessary + if(currentLength > targetLength) { + QDesktopScreenWidget *screen; + while (currentLength-- > targetLength) { + screen = screens.takeLast(); + delete screen; + } + } + else if (currentLength < targetLength) { + QDesktopScreenWidget *screen; + while (currentLength < targetLength) { + screen = new QDesktopScreenWidget(currentLength++); + screens.append(screen); + } + } + + QRegion virtualGeometry; + bool doVirtualGeometry = QApplicationPrivate::platformIntegration()->isVirtualDesktop(); + + // update the geometry of each screen widget + for (int i = 0; i < screens.length(); i++) { + QRect screenGeometry = screenList.at(i)->geometry(); + screens.at(i)->setGeometry(screenGeometry); + if (doVirtualGeometry) + virtualGeometry += screenGeometry; + } + + virtualScreen.setGeometry(virtualGeometry.boundingRect()); + Q_Q(QDesktopWidget); + q->setGeometry(virtualScreen.geometry()); +} + +QDesktopWidget::QDesktopWidget() + : QWidget(*new QDesktopWidgetPrivate, 0, Qt::Desktop) +{ + Q_D(QDesktopWidget); + setObjectName(QLatin1String("desktop")); + d->updateScreenList(); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + return QApplicationPrivate::platformIntegration()->isVirtualDesktop(); +} + +int QDesktopWidget::primaryScreen() const +{ + return 0; +} + +int QDesktopWidget::numScreens() const +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + return qMax(pi->screens().size(), 1); +} + +QWidget *QDesktopWidget::screen(int screen) +{ + Q_D(QDesktopWidget); + if (QApplicationPrivate::platformIntegration()->isVirtualDesktop()) + return &d->virtualScreen; + if (screen < 0 || screen >= d->screens.length()) + return d->screens.at(0); + return d->screens.at(screen); +} + +const QRect QDesktopWidget::availableGeometry(int screenNo) const +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + QList screens = pi->screens(); + if (screenNo == -1) + screenNo = 0; + if (screenNo < 0 || screenNo >= screens.size()) + return QRect(); + else + return screens[screenNo]->availableGeometry(); +} + +const QRect QDesktopWidget::screenGeometry(int screenNo) const +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + QList screens = pi->screens(); + if (screenNo == -1) + screenNo = 0; + if (screenNo < 0 || screenNo >= screens.size()) + return QRect(); + else + return screens[screenNo]->geometry(); +} + +int QDesktopWidget::screenNumber(const QWidget *w) const +{ + if (!w) + return 0; + + QRect frame = w->frameGeometry(); + if (!w->isWindow()) + frame.moveTopLeft(w->mapToGlobal(QPoint(0, 0))); + const QPoint midpoint = (frame.topLeft() + frame.bottomRight()) / 2; + return screenNumber(midpoint); +} + +int QDesktopWidget::screenNumber(const QPoint &p) const +{ + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + QList screens = pi->screens(); + + for (int i = 0; i < screens.size(); ++i) + if (screens[i]->geometry().contains(p)) + return i; + + return primaryScreen(); //even better would be closest screen +} + +void QDesktopWidget::resizeEvent(QResizeEvent *) +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_qpa_p.h b/src/gui/kernel/qdesktopwidget_qpa_p.h new file mode 100644 index 0000000000..abee8a101e --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_qpa_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESKTOPWIDGET_QPA_P_H +#define QDESKTOPWIDGET_QPA_P_H + +#include "QDesktopWidget" +#include "private/qwidget_p.h" + +class QDesktopScreenWidget : public QWidget { + Q_OBJECT +public: + QDesktopScreenWidget(int screenNumber = -1) + { + setWindowFlags(Qt::Desktop); + setVisible(false); + QTLWExtra *topData = d_func()->topData(); + topData->screenIndex = screenNumber; + } +}; + +class QDesktopWidgetPrivate : public QWidgetPrivate { + Q_DECLARE_PUBLIC(QDesktopWidget) + +public: + ~QDesktopWidgetPrivate() {foreach(QDesktopScreenWidget *s, screens) delete s; } + void updateScreenList(); + + QList screens; + QDesktopScreenWidget virtualScreen; +}; + +#endif // QDESKTOPWIDGET_QPA_P_H diff --git a/src/gui/kernel/qdesktopwidget_qws.cpp b/src/gui/kernel/qdesktopwidget_qws.cpp new file mode 100644 index 0000000000..1e21845df6 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_qws.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdesktopwidget.h" +#include "qscreen_qws.h" +#include "private/qapplication_p.h" + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +QDesktopWidget::QDesktopWidget() + : QWidget(0, Qt::Desktop) +{ + setObjectName(QLatin1String("desktop")); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + return true; +} + +int QDesktopWidget::primaryScreen() const +{ + return 0; +} + +int QDesktopWidget::numScreens() const +{ + QScreen *screen = QScreen::instance(); + if (!screen) + return 0; + + const QList subScreens = screen->subScreens(); + return qMax(subScreens.size(), 1); +} + +QWidget *QDesktopWidget::screen(int) +{ + return this; +} + +const QRect QDesktopWidget::availableGeometry(int screenNo) const +{ + const QScreen *screen = QScreen::instance(); + if (screenNo == -1) + screenNo = 0; + if (!screen || screenNo < 0) + return QRect(); + + const QList subScreens = screen->subScreens(); + if (!subScreens.isEmpty()) { + if (screenNo >= subScreens.size()) + return QRect(); + screen = subScreens.at(screenNo); + } + + QApplicationPrivate *ap = QApplicationPrivate::instance(); + const QRect r = ap->maxWindowRect(screen); + if (!r.isEmpty()) + return r; + + return screen->region().boundingRect(); +} + +const QRect QDesktopWidget::screenGeometry(int screenNo) const +{ + const QScreen *screen = QScreen::instance(); + if (screenNo == -1) + screenNo = 0; + if (!screen || screenNo < 0) + return QRect(); + + const QList subScreens = screen->subScreens(); + if (subScreens.size() == 0 && screenNo == 0) + return screen->region().boundingRect(); + + if (screenNo >= subScreens.size()) + return QRect(); + + return subScreens.at(screenNo)->region().boundingRect(); +} + +int QDesktopWidget::screenNumber(const QWidget *w) const +{ + if (!w) + return 0; + + QRect frame = w->frameGeometry(); + if (!w->isWindow()) + frame.moveTopLeft(w->mapToGlobal(QPoint(0, 0))); + const QPoint midpoint = (frame.topLeft() + frame.bottomRight()) / 2; + return screenNumber(midpoint); +} + +int QDesktopWidget::screenNumber(const QPoint &p) const +{ + const QScreen *screen = QScreen::instance(); + if (!screen || !screen->region().contains(p)) + return -1; + + const QList subScreens = screen->subScreens(); + if (subScreens.size() == 0) + return 0; + + for (int i = 0; i < subScreens.size(); ++i) + if (subScreens.at(i)->region().contains(p)) + return i; + + return -1; +} + +void QDesktopWidget::resizeEvent(QResizeEvent *) +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_s60.cpp b/src/gui/kernel/qdesktopwidget_s60.cpp new file mode 100644 index 0000000000..62a4d40eba --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_s60.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdesktopwidget.h" +#include "qapplication_p.h" +#include "qwidget_p.h" +#include "qt_s60_p.h" +#include +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) +#include +#endif + +QT_BEGIN_NAMESPACE + +extern int qt_symbian_create_desktop_on_screen; + +class QSingleDesktopWidget : public QWidget +{ +public: + QSingleDesktopWidget(); + ~QSingleDesktopWidget(); +}; + +QSingleDesktopWidget::QSingleDesktopWidget() + : QWidget(0, Qt::Desktop) +{ +} + +QSingleDesktopWidget::~QSingleDesktopWidget() +{ + const QObjectList &childList = children(); + for (int i = childList.size(); i > 0 ;) { + --i; + childList.at(i)->setParent(0); + } +} + +class QDesktopWidgetPrivate : public QWidgetPrivate +{ +public: + QDesktopWidgetPrivate(); + ~QDesktopWidgetPrivate(); + static void init(QDesktopWidget *that); + static void cleanup(); + static void init_sys(); + + static int screenCount; + static int primaryScreen; + + static QVector *rects; + static QVector *workrects; + static QVector *screens; + + static int refcount; + +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + static MDisplayControl *displayControl; +#endif +}; + +int QDesktopWidgetPrivate::screenCount = 1; +int QDesktopWidgetPrivate::primaryScreen = 0; +QVector *QDesktopWidgetPrivate::rects = 0; +QVector *QDesktopWidgetPrivate::workrects = 0; +QVector *QDesktopWidgetPrivate::screens = 0; +int QDesktopWidgetPrivate::refcount = 0; +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) +MDisplayControl *QDesktopWidgetPrivate::displayControl = 0; +#endif + +QDesktopWidgetPrivate::QDesktopWidgetPrivate() +{ + ++refcount; +} + +QDesktopWidgetPrivate::~QDesktopWidgetPrivate() +{ + if (!--refcount) + cleanup(); +} + +void QDesktopWidgetPrivate::init(QDesktopWidget *that) +{ + // Note that on S^3 devices the screen count retrieved via RWsSession + // will always be 2 but the width and height for screen number 1 will + // be 0 as long as TV-out is not connected. + // + // On the other hand a valid size for screen 1 will be reported even + // after the cable is disconnected. In order to overcome this, we use + // MDisplayControl::NumberOfResolutions() to check if the display is + // valid or not. + + screenCount = S60->screenCount(); +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + if (displayControl) { + if (displayControl->NumberOfResolutions() < 1) + screenCount = 1; + } +#endif + if (screenCount < 1) { + qWarning("No screen available"); + screenCount = 1; + } + + rects = new QVector(); + workrects = new QVector(); + screens = new QVector(); + + rects->resize(screenCount); + workrects->resize(screenCount); + screens->resize(screenCount); + + for (int i = 0; i < screenCount; ++i) { + // All screens will have a position of (0, 0) as there is no true virtual desktop + // or pointer event support for multiple screens on Symbian. + QRect r(0, 0, + S60->screenWidthInPixelsForScreen[i], S60->screenHeightInPixelsForScreen[i]); + // Stop here if empty and ignore this screen. + if (r.isEmpty()) { + screenCount = i; + break; + } + (*rects)[i] = r; + QRect wr; + if (i == 0) + wr = qt_TRect2QRect(static_cast(S60->appUi())->ClientRect()); + else + wr = rects->at(i); + (*workrects)[i].setRect(wr.x(), wr.y(), wr.width(), wr.height()); + (*screens)[i] = 0; + } + (*screens)[0] = that; +} + +void QDesktopWidgetPrivate::cleanup() +{ + delete rects; + rects = 0; + delete workrects; + workrects = 0; + if (screens) { + // First item is the QDesktopWidget so skip it. + for (int i = 1; i < screens->count(); ++i) + delete screens->at(i); + } + delete screens; + screens = 0; +} + +void QDesktopWidgetPrivate::init_sys() +{ +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + if (S60->screenCount() > 1) { + CWsScreenDevice *dev = S60->screenDevice(1); + if (dev) { + displayControl = static_cast( + dev->GetInterface(MDisplayControl::ETypeId)); + if (displayControl) { + displayControl->EnableDisplayChangeEvents(ETrue); + } + } + } +#endif +} + + +QDesktopWidget::QDesktopWidget() + : QWidget(*new QDesktopWidgetPrivate, 0, Qt::Desktop) +{ + setObjectName(QLatin1String("desktop")); + QDesktopWidgetPrivate::init_sys(); + QDesktopWidgetPrivate::init(this); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + return false; +} + +int QDesktopWidget::primaryScreen() const +{ + return QDesktopWidgetPrivate::primaryScreen; +} + +int QDesktopWidget::numScreens() const +{ + Q_D(const QDesktopWidget); + return QDesktopWidgetPrivate::screenCount; +} + +static inline QWidget *newSingleDesktopWidget(int screen) +{ + qt_symbian_create_desktop_on_screen = screen; + QWidget *w = new QSingleDesktopWidget; + qt_symbian_create_desktop_on_screen = -1; + return w; +} + +QWidget *QDesktopWidget::screen(int screen) +{ + Q_D(QDesktopWidget); + if (screen < 0 || screen >= d->screenCount) + screen = d->primaryScreen; + if (!d->screens->at(screen) + || d->screens->at(screen)->windowType() != Qt::Desktop) + (*d->screens)[screen] = newSingleDesktopWidget(screen); + return (*d->screens)[screen]; +} + +const QRect QDesktopWidget::availableGeometry(int screen) const +{ + Q_D(const QDesktopWidget); + if (screen < 0 || screen >= d->screenCount) + screen = d->primaryScreen; + + return d->workrects->at(screen); +} + +const QRect QDesktopWidget::screenGeometry(int screen) const +{ + Q_D(const QDesktopWidget); + if (screen < 0 || screen >= d->screenCount) + screen = d->primaryScreen; + + return d->rects->at(screen); +} + +int QDesktopWidget::screenNumber(const QWidget *widget) const +{ + Q_D(const QDesktopWidget); + return widget + ? S60->screenNumberForWidget(widget) + : d->primaryScreen; +} + +int QDesktopWidget::screenNumber(const QPoint &point) const +{ + Q_UNUSED(point); + Q_D(const QDesktopWidget); + return d->primaryScreen; +} + +void QDesktopWidget::resizeEvent(QResizeEvent *) +{ + Q_D(QDesktopWidget); + QVector oldrects; + oldrects = *d->rects; + QVector oldworkrects; + oldworkrects = *d->workrects; + int oldscreencount = d->screenCount; + + QDesktopWidgetPrivate::cleanup(); + QDesktopWidgetPrivate::init(this); + + for (int i = 0; i < qMin(oldscreencount, d->screenCount); ++i) { + QRect oldrect = oldrects[i]; + QRect newrect = d->rects->at(i); + if (oldrect != newrect) + emit resized(i); + } + + for (int j = 0; j < qMin(oldscreencount, d->screenCount); ++j) { + QRect oldrect = oldworkrects[j]; + QRect newrect = d->workrects->at(j); + if (oldrect != newrect) + emit workAreaResized(j); + } + + if (oldscreencount != d->screenCount) { + emit screenCountChanged(d->screenCount); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_win.cpp b/src/gui/kernel/qdesktopwidget_win.cpp new file mode 100644 index 0000000000..d57b355ef4 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_win.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdesktopwidget.h" +#include "qt_windows.h" +#include "qapplication_p.h" +#include +#include +#include +#ifdef Q_WS_WINCE +#include +#endif +#include "qwidget_p.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +class QDesktopWidgetPrivate : public QWidgetPrivate +{ +public: + QDesktopWidgetPrivate(); + ~QDesktopWidgetPrivate(); + + static void init(QDesktopWidget *that); + static void cleanup(); + static int screenCount; + static int primaryScreen; + + static QVector *rects; + static QVector *workrects; + + struct MONITORINFO + { + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; + }; + + typedef BOOL (WINAPI *InfoFunc)(HMONITOR, MONITORINFO*); + typedef BOOL (QT_WIN_CALLBACK *EnumProc)(HMONITOR, HDC, LPRECT, LPARAM); + typedef BOOL (WINAPI *EnumFunc)(HDC, LPCRECT, EnumProc, LPARAM); + + static EnumFunc enumDisplayMonitors; + static InfoFunc getMonitorInfo; + static int refcount; +}; + +int QDesktopWidgetPrivate::screenCount = 1; +int QDesktopWidgetPrivate::primaryScreen = 0; +QDesktopWidgetPrivate::EnumFunc QDesktopWidgetPrivate::enumDisplayMonitors = 0; +QDesktopWidgetPrivate::InfoFunc QDesktopWidgetPrivate::getMonitorInfo = 0; +QVector *QDesktopWidgetPrivate::rects = 0; +QVector *QDesktopWidgetPrivate::workrects = 0; +static int screen_number = 0; +int QDesktopWidgetPrivate::refcount = 0; +#ifdef Q_WS_WINCE_WM +// Use SIP information, if available +// SipGetInfo is not supported by SSDK (no definition!). +static inline void qt_get_sip_info(QRect &rect) +{ + SIPINFO sip; + memset(&sip, 0, sizeof(SIPINFO)); + sip.cbSize = sizeof(SIPINFO); + if (SipGetInfo(&sip)) + rect = QRect(QPoint(sip.rcVisibleDesktop.left, sip.rcVisibleDesktop.top), + QPoint(sip.rcVisibleDesktop.right - 1, sip.rcVisibleDesktop.bottom - 1)); +} +#endif + + +BOOL QT_WIN_CALLBACK enumCallback(HMONITOR hMonitor, HDC, LPRECT, LPARAM) +{ + QDesktopWidgetPrivate::screenCount++; + QDesktopWidgetPrivate::rects->resize(QDesktopWidgetPrivate::screenCount); + QDesktopWidgetPrivate::workrects->resize(QDesktopWidgetPrivate::screenCount); + // Get the MONITORINFO block + QDesktopWidgetPrivate::MONITORINFO info; + memset(&info, 0, sizeof(QDesktopWidgetPrivate::MONITORINFO)); + info.cbSize = sizeof(QDesktopWidgetPrivate::MONITORINFO); + BOOL res = QDesktopWidgetPrivate::getMonitorInfo(hMonitor, &info); + if (!res) { + (*QDesktopWidgetPrivate::rects)[screen_number] = QRect(); + (*QDesktopWidgetPrivate::workrects)[screen_number] = QRect(); + return true; + } + + // Fill list of rects + RECT r = info.rcMonitor; + QRect qr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + (*QDesktopWidgetPrivate::rects)[screen_number] = qr; + + r = info.rcWork; + qr = QRect(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + (*QDesktopWidgetPrivate::workrects)[screen_number] = qr; + + if (info.dwFlags & 0x00000001) //MONITORINFOF_PRIMARY + QDesktopWidgetPrivate::primaryScreen = screen_number; + + ++screen_number; + // Stop the enumeration if we have them all + return true; +} + +QDesktopWidgetPrivate::QDesktopWidgetPrivate() +{ + ++refcount; +} + +void QDesktopWidgetPrivate::init(QDesktopWidget *that) +{ + if (rects) + return; + + rects = new QVector(); + workrects = new QVector(); + screenCount = 0; + +#ifndef Q_OS_WINCE + QSystemLibrary user32Lib(QLatin1String("user32")); + enumDisplayMonitors = (EnumFunc)user32Lib.resolve("EnumDisplayMonitors"); + getMonitorInfo = (InfoFunc)user32Lib.resolve("GetMonitorInfoW"); + + if (!enumDisplayMonitors || !getMonitorInfo) { + screenCount = GetSystemMetrics(80); // SM_CMONITORS + rects->resize(screenCount); + for (int i = 0; i < screenCount; ++i) + rects->replace(i, that->rect()); + return; + } + // Calls enumCallback + enumDisplayMonitors(0, 0, enumCallback, 0); + enumDisplayMonitors = 0; + getMonitorInfo = 0; +#else + QSystemLibrary coreLib(QLatin1String("coredll")); + // CE >= 4.0 case + enumDisplayMonitors = (EnumFunc)coreLib.resolve("EnumDisplayMonitors"); + getMonitorInfo = (InfoFunc)coreLib.resolve("GetMonitorInfo"); + + if ((!enumDisplayMonitors || !getMonitorInfo)) { + screenCount = GetSystemMetrics(SM_CMONITORS); + return; + } + + if (!coreLib.isLoaded() || !enumDisplayMonitors || !getMonitorInfo) { + rects->resize(screenCount); + for (int i = 0; i < screenCount; ++i) + (*rects)[i] = that->rect(); + + RECT r; + SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0); + QRect qr = QRect(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + +#if defined(Q_WS_WINCE_WM) + qt_get_sip_info(qr); +#endif + + workrects->resize(screenCount); + for (int j = 0; j < screenCount; ++j) + (*workrects)[j] = qr; + return; + } + + // Calls enumCallback + enumDisplayMonitors(0, 0, enumCallback, 0); + enumDisplayMonitors = 0; + getMonitorInfo = 0; +#endif // Q_WS_WINCE +} + +QDesktopWidgetPrivate::~QDesktopWidgetPrivate() +{ + if (!--refcount) + cleanup(); +} + +void QDesktopWidgetPrivate::cleanup() +{ + screen_number = 0; + screenCount = 1; + primaryScreen = 0; + enumDisplayMonitors = 0; + getMonitorInfo = 0; + delete rects; + rects = 0; + delete workrects; + workrects = 0; +} + +/* + \omit + Function is commented out in header + \fn void *QDesktopWidget::handle(int screen) const + + Returns the window system handle of the display device with the + index \a screen, for low-level access. Using this function is not + portable. + + The return type varies with platform; see qwindowdefs.h for details. + + \sa x11Display(), QPaintDevice::handle() + \endomit +*/ + +QDesktopWidget::QDesktopWidget() + : QWidget(*new QDesktopWidgetPrivate, 0, Qt::Desktop) +{ + setObjectName(QLatin1String("desktop")); + QDesktopWidgetPrivate::init(this); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + return true; +} + +int QDesktopWidget::primaryScreen() const +{ + return d_func()->primaryScreen; +} + +int QDesktopWidget::numScreens() const +{ + return d_func()->screenCount; +} + +QWidget *QDesktopWidget::screen(int /* screen */) +{ + // It seems that a Qt::WType_Desktop cannot be moved? + return this; +} + +// +// MSVC 7.10 warns that d (the result of the expanded Q_D macro) as a local variable that is not referenced. +// Therefore, we ignore that warning with the following pragmas +// I've also tried to eliminate the macro, but to no use... +// We pop it further down +#ifdef Q_CC_MSVC +# pragma warning(push) +# pragma warning(disable : 4189) +#endif +const QRect QDesktopWidget::availableGeometry(int screen) const +{ + Q_D(const QDesktopWidget); +#ifdef Q_WS_WINCE_WM + for(int i=0; i < d->workrects->size(); ++i) + qt_get_sip_info((*d->workrects)[i]); +#endif + if (screen < 0 || screen >= d->screenCount) + screen = d->primaryScreen; + + return d->workrects->at(screen); +} + +const QRect QDesktopWidget::screenGeometry(int screen) const +{ + const QDesktopWidgetPrivate *d = d_func(); + if (screen < 0 || screen >= d->screenCount) + screen = d->primaryScreen; + + return d->rects->at(screen); +} + +int QDesktopWidget::screenNumber(const QWidget *widget) const +{ + Q_D(const QDesktopWidget); + if (!widget) + return d->primaryScreen; + + QRect frame = widget->frameGeometry(); + if (!widget->isWindow()) + frame.moveTopLeft(widget->mapToGlobal(QPoint(0,0))); + + int maxSize = -1; + int maxScreen = -1; + + for (int i = 0; i < d->screenCount; ++i) { + QRect sect = d->rects->at(i).intersected(frame); + int size = sect.width() * sect.height(); + if (size > maxSize && sect.width() > 0 && sect.height() > 0) { + maxSize = size; + maxScreen = i; + } + } + + return maxScreen; +} + +int QDesktopWidget::screenNumber(const QPoint &point) const +{ + Q_D(const QDesktopWidget); + + int closestScreen = -1; + int shortestDistance = INT_MAX; + + for (int i = 0; i < d->screenCount; ++i) { + int thisDistance = d->pointToRect(point, d->rects->at(i)); + if (thisDistance < shortestDistance) { + shortestDistance = thisDistance; + closestScreen = i; + } + } + + return closestScreen; +} + +void QDesktopWidget::resizeEvent(QResizeEvent *) +{ + Q_D(QDesktopWidget); + const QVector oldrects(*d->rects); + const QVector oldworkrects(*d->workrects); + int oldscreencount = d->screenCount; + + QDesktopWidgetPrivate::cleanup(); + QDesktopWidgetPrivate::init(this); +#ifdef Q_WS_WINCE_WM + for(int i=0; i < d->workrects->size(); ++i) + qt_get_sip_info((*d->workrects)[i]); +#endif + + for (int i = 0; i < qMin(oldscreencount, d->screenCount); ++i) { + const QRect oldrect = oldrects[i]; + const QRect newrect = d->rects->at(i); + if (oldrect != newrect) + emit resized(i); + } + + for (int j = 0; j < qMin(oldscreencount, d->screenCount); ++j) { + const QRect oldrect = oldworkrects[j]; + const QRect newrect = d->workrects->at(j); + if (oldrect != newrect) + emit workAreaResized(j); + } + + if (oldscreencount != d->screenCount) { + emit screenCountChanged(d->screenCount); + } +} + +#ifdef Q_CC_MSVC +# pragma warning(pop) +#endif + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdesktopwidget_x11.cpp b/src/gui/kernel/qdesktopwidget_x11.cpp new file mode 100644 index 0000000000..b0f12903a1 --- /dev/null +++ b/src/gui/kernel/qdesktopwidget_x11.cpp @@ -0,0 +1,406 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qlibrary.h" +#include "qt_x11_p.h" +#include "qvariant.h" +#include "qwidget_p.h" +#include "qx11info_x11.h" +#include + +QT_BEGIN_NAMESPACE + +// defined in qwidget_x11.cpp +extern int qt_x11_create_desktop_on_screen; + + +// function to update the workarea of the screen +static bool qt_desktopwidget_workarea_dirty = true; +void qt_desktopwidget_update_workarea() +{ + qt_desktopwidget_workarea_dirty = true; +} + + +class QSingleDesktopWidget : public QWidget +{ +public: + QSingleDesktopWidget(); + ~QSingleDesktopWidget(); +}; + +QSingleDesktopWidget::QSingleDesktopWidget() + : QWidget(0, Qt::Desktop) +{ +} + +QSingleDesktopWidget::~QSingleDesktopWidget() +{ + const QObjectList &childList = children(); + for (int i = childList.size(); i > 0 ;) { + --i; + childList.at(i)->setParent(0); + } +} + + +class QDesktopWidgetPrivate : public QWidgetPrivate +{ +public: + QDesktopWidgetPrivate(); + ~QDesktopWidgetPrivate(); + + void init(); + + bool use_xinerama; + int defaultScreen; + int screenCount; + + QWidget **screens; + QRect *rects; + QRect *workareas; +}; + +QDesktopWidgetPrivate::QDesktopWidgetPrivate() + : use_xinerama(false), defaultScreen(0), screenCount(1), + screens(0), rects(0), workareas(0) +{ +} + +QDesktopWidgetPrivate::~QDesktopWidgetPrivate() +{ + if (screens) { + for (int i = 0; i < screenCount; ++i) { + if (i == defaultScreen) continue; + delete screens[i]; + screens[i] = 0; + } + + free (screens); + } + + if (rects) delete [] rects; + if (workareas) delete [] workareas; +} + +void QDesktopWidgetPrivate::init() +{ + // get the screen count + int newScreenCount = ScreenCount(X11->display); +#ifndef QT_NO_XINERAMA + + XineramaScreenInfo *xinerama_screeninfo = 0; + + // we ignore the Xinerama extension when using the display is + // using traditional multi-screen (with multiple root windows) + if (newScreenCount == 1 + && X11->ptrXineramaQueryExtension + && X11->ptrXineramaIsActive + && X11->ptrXineramaQueryScreens) { + int unused; + use_xinerama = (X11->ptrXineramaQueryExtension(X11->display, &unused, &unused) + && X11->ptrXineramaIsActive(X11->display)); + } + + if (use_xinerama) { + xinerama_screeninfo = + X11->ptrXineramaQueryScreens(X11->display, &newScreenCount); + } + + if (xinerama_screeninfo) { + defaultScreen = 0; + } else +#endif // QT_NO_XINERAMA + { + defaultScreen = DefaultScreen(X11->display); + newScreenCount = ScreenCount(X11->display); + use_xinerama = false; + } + + delete [] rects; + rects = new QRect[newScreenCount]; + delete [] workareas; + workareas = new QRect[newScreenCount]; + + // get the geometry of each screen + int i, j, x, y, w, h; + for (i = 0, j = 0; i < newScreenCount; i++, j++) { + +#ifndef QT_NO_XINERAMA + if (use_xinerama) { + x = xinerama_screeninfo[i].x_org; + y = xinerama_screeninfo[i].y_org; + w = xinerama_screeninfo[i].width; + h = xinerama_screeninfo[i].height; + } else +#endif // QT_NO_XINERAMA + { + x = 0; + y = 0; + w = WidthOfScreen(ScreenOfDisplay(X11->display, i)); + h = HeightOfScreen(ScreenOfDisplay(X11->display, i)); + } + + rects[j].setRect(x, y, w, h); + + if (use_xinerama && j > 0 && rects[j-1].intersects(rects[j])) { + // merge a "cloned" screen with the previous, hiding all crtcs + // that are currently showing a sub-rect of the previous screen + if ((rects[j].width()*rects[j].height()) > + (rects[j-1].width()*rects[j-1].height())) + rects[j-1] = rects[j]; + j--; + } + + workareas[i] = QRect(); + } + + if (screens) { + // leaks QWidget* pointers on purpose, can't delete them as pointer escapes + screens = q_check_ptr((QWidget**) realloc(screens, j * sizeof(QWidget*))); + if (j > screenCount) + memset(&screens[screenCount], 0, (j-screenCount) * sizeof(QWidget*)); + } + + screenCount = j; + +#ifndef QT_NO_XINERAMA + if (use_xinerama && screenCount == 1) + use_xinerama = false; + + if (xinerama_screeninfo) + XFree(xinerama_screeninfo); +#endif // QT_NO_XINERAMA + +} + +// the QDesktopWidget itself will be created on the default screen +// as qt_x11_create_desktop_on_screen defaults to -1 +QDesktopWidget::QDesktopWidget() + : QWidget(*new QDesktopWidgetPrivate, 0, Qt::Desktop) +{ + Q_D(QDesktopWidget); + d->init(); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + Q_D(const QDesktopWidget); + return d->use_xinerama; +} + +int QDesktopWidget::primaryScreen() const +{ + Q_D(const QDesktopWidget); + return d->defaultScreen; +} + +int QDesktopWidget::numScreens() const +{ + Q_D(const QDesktopWidget); + return d->screenCount; +} + +QWidget *QDesktopWidget::screen(int screen) +{ + Q_D(QDesktopWidget); + if (d->use_xinerama) + return this; + + if (screen < 0 || screen >= d->screenCount) + screen = d->defaultScreen; + + if (! d->screens) { + d->screens = (QWidget**) calloc( d->screenCount, sizeof(QWidget*)); + d->screens[d->defaultScreen] = this; + } + + if (! d->screens[screen] || // not created yet + ! (d->screens[screen]->windowType() == Qt::Desktop)) { // reparented away + qt_x11_create_desktop_on_screen = screen; + d->screens[screen] = new QSingleDesktopWidget; + qt_x11_create_desktop_on_screen = -1; + } + + return d->screens[screen]; +} + +const QRect QDesktopWidget::availableGeometry(int screen) const +{ + Q_D(const QDesktopWidget); + if (qt_desktopwidget_workarea_dirty) { + // the workareas are dirty, invalidate them + for (int i = 0; i < d->screenCount; ++i) + d->workareas[i] = QRect(); + qt_desktopwidget_workarea_dirty = false; + } + + if (screen < 0 || screen >= d->screenCount) + screen = d->defaultScreen; + + if (d->workareas[screen].isValid()) + return d->workareas[screen]; + + if (X11->isSupportedByWM(ATOM(_NET_WORKAREA))) { + int x11Screen = isVirtualDesktop() ? DefaultScreen(X11->display) : screen; + + Atom ret; + int format, e; + unsigned char *data = 0; + unsigned long nitems, after; + + e = XGetWindowProperty(X11->display, + QX11Info::appRootWindow(x11Screen), + ATOM(_NET_WORKAREA), 0, 4, False, XA_CARDINAL, + &ret, &format, &nitems, &after, &data); + + QRect workArea; + if (e == Success && ret == XA_CARDINAL && + format == 32 && nitems == 4) { + long *workarea = (long *) data; + workArea = QRect(workarea[0], workarea[1], workarea[2], workarea[3]); + } else { + workArea = screenGeometry(screen); + } + + if (isVirtualDesktop()) { + // intersect the workarea (which spawns all Xinerama screens) with the rect for the + // requested screen + workArea &= screenGeometry(screen); + } + + d->workareas[screen] = workArea; + + if (data) + XFree(data); + } else { + d->workareas[screen] = screenGeometry(screen); + } + + return d->workareas[screen]; +} + +const QRect QDesktopWidget::screenGeometry(int screen) const +{ + Q_D(const QDesktopWidget); + if (screen < 0 || screen >= d->screenCount) + screen = d->defaultScreen; + + return d->rects[screen]; +} + +int QDesktopWidget::screenNumber(const QWidget *widget) const +{ + Q_D(const QDesktopWidget); + if (!widget) + return d->defaultScreen; + +#ifndef QT_NO_XINERAMA + if (d->use_xinerama) { + // this is how we do it for xinerama + QRect frame = widget->frameGeometry(); + if (!widget->isWindow()) + frame.moveTopLeft(widget->mapToGlobal(QPoint(0, 0))); + + int maxSize = -1; + int maxScreen = -1; + + for (int i = 0; i < d->screenCount; ++i) { + QRect sect = d->rects[i].intersected(frame); + int size = sect.width() * sect.height(); + if (size > maxSize && sect.width() > 0 && sect.height() > 0) { + maxSize = size; + maxScreen = i; + } + } + return maxScreen; + } +#endif // QT_NO_XINERAMA + + return widget->x11Info().screen(); +} + +int QDesktopWidget::screenNumber(const QPoint &point) const +{ + Q_D(const QDesktopWidget); + int closestScreen = -1; + int shortestDistance = INT_MAX; + for (int i = 0; i < d->screenCount; ++i) { + int thisDistance = d->pointToRect(point, d->rects[i]); + if (thisDistance < shortestDistance) { + shortestDistance = thisDistance; + closestScreen = i; + } + } + return closestScreen; +} + +void QDesktopWidget::resizeEvent(QResizeEvent *event) +{ + Q_D(QDesktopWidget); + int oldScreenCount = d->screenCount; + QVector oldRects(oldScreenCount); + for (int i = 0; i < oldScreenCount; ++i) { + oldRects[i] = d->rects[i]; + } + + d->init(); + + for (int i = 0; i < qMin(oldScreenCount, d->screenCount); ++i) { + if (oldRects.at(i) != d->rects[i]) + emit resized(i); + } + + if (oldScreenCount != d->screenCount) { + emit screenCountChanged(d->screenCount); + } + + qt_desktopwidget_workarea_dirty = true; + QWidget::resizeEvent(event); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdnd.cpp b/src/gui/kernel/qdnd.cpp new file mode 100644 index 0000000000..7063828610 --- /dev/null +++ b/src/gui/kernel/qdnd.cpp @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "qbitmap.h" +#include "qdrag.h" +#include "qpixmap.h" +#include "qevent.h" +#include "qfile.h" +#include "qtextcodec.h" +#include "qapplication.h" +#include "qpoint.h" +#include "qwidget.h" +#include "qbuffer.h" +#include "qimage.h" +#include "qregexp.h" +#include "qdir.h" +#include "qdnd_p.h" +#include "qimagereader.h" +#include "qimagewriter.h" +#include "qdebug.h" +#include + +#include + +#ifndef QT_NO_DRAGANDDROP + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_DRAGANDDROP + +//#define QDND_DEBUG + +#ifdef QDND_DEBUG +QString dragActionsToString(Qt::DropActions actions) +{ + QString str; + if (actions == Qt::IgnoreAction) { + if (!str.isEmpty()) + str += " | "; + str += "IgnoreAction"; + } + if (actions & Qt::LinkAction) { + if (!str.isEmpty()) + str += " | "; + str += "LinkAction"; + } + if (actions & Qt::CopyAction) { + if (!str.isEmpty()) + str += " | "; + str += "CopyAction"; + } + if (actions & Qt::MoveAction) { + if (!str.isEmpty()) + str += " | "; + str += "MoveAction"; + } + if ((actions & Qt::TargetMoveAction) == Qt::TargetMoveAction ) { + if (!str.isEmpty()) + str += " | "; + str += "TargetMoveAction"; + } + return str; +} + +QString KeyboardModifiersToString(Qt::KeyboardModifiers moderfies) +{ + QString str; + if (moderfies & Qt::ControlModifier) { + if (!str.isEmpty()) + str += " | "; + str += Qt::ControlModifier; + } + if (moderfies & Qt::AltModifier) { + if (!str.isEmpty()) + str += " | "; + str += Qt::AltModifier; + } + if (moderfies & Qt::ShiftModifier) { + if (!str.isEmpty()) + str += " | "; + str += Qt::ShiftModifier; + } + return str; +} +#endif + + +// the universe's only drag manager +QDragManager *QDragManager::instance = 0; + + +QDragManager::QDragManager() + : QObject(qApp) +{ + Q_ASSERT(!instance); + +#ifdef Q_WS_QWS + currentActionForOverrideCursor = Qt::IgnoreAction; +#endif + object = 0; + beingCancelled = false; + restoreCursor = false; + willDrop = false; + eventLoop = 0; + dropData = new QDropData(); + currentDropTarget = 0; +#ifdef Q_WS_X11 + xdndMimeTransferedPixmapIndex = 0; +#endif +} + + +QDragManager::~QDragManager() +{ +#ifndef QT_NO_CURSOR + if (restoreCursor) + QApplication::restoreOverrideCursor(); +#endif + instance = 0; + delete dropData; +} + +QDragManager *QDragManager::self() +{ + if (!instance && !QApplication::closingDown()) + instance = new QDragManager; + return instance; +} + +QPixmap QDragManager::dragCursor(Qt::DropAction action) const +{ + QDragPrivate * d = dragPrivate(); + if (d && d->customCursors.contains(action)) + return d->customCursors[action]; + else if (action == Qt::MoveAction) + return QApplicationPrivate::instance()->getPixmapCursor(Qt::DragMoveCursor); + else if (action == Qt::CopyAction) + return QApplicationPrivate::instance()->getPixmapCursor(Qt::DragCopyCursor); + else if (action == Qt::LinkAction) + return QApplicationPrivate::instance()->getPixmapCursor(Qt::DragLinkCursor); +#ifdef Q_WS_WIN + else if (action == Qt::IgnoreAction) + return QApplicationPrivate::instance()->getPixmapCursor(Qt::ForbiddenCursor); +#endif + return QPixmap(); +} + +bool QDragManager::hasCustomDragCursors() const +{ + QDragPrivate * d = dragPrivate(); + return d && !d->customCursors.isEmpty(); +} + +Qt::DropAction QDragManager::defaultAction(Qt::DropActions possibleActions, + Qt::KeyboardModifiers modifiers) const +{ +#ifdef QDND_DEBUG + qDebug("QDragManager::defaultAction(Qt::DropActions possibleActions)"); + qDebug("keyboard modifiers : %s", KeyboardModifiersToString(modifiers).latin1()); +#endif + + QDragPrivate *d = dragPrivate(); + Qt::DropAction defaultAction = d ? d->defaultDropAction : Qt::IgnoreAction; + + if (defaultAction == Qt::IgnoreAction) { + //This means that the drag was initiated by QDrag::start and we need to + //preserve the old behavior +#ifdef Q_WS_MAC + defaultAction = Qt::MoveAction; +#else + defaultAction = Qt::CopyAction; +#endif + } + +#ifdef Q_WS_MAC + if (modifiers & Qt::ControlModifier && modifiers & Qt::AltModifier) + defaultAction = Qt::LinkAction; + else if (modifiers & Qt::AltModifier) + defaultAction = Qt::CopyAction; + else if (modifiers & Qt::ControlModifier) + defaultAction = Qt::MoveAction; +#else + if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) + defaultAction = Qt::LinkAction; + else if (modifiers & Qt::ControlModifier) + defaultAction = Qt::CopyAction; + else if (modifiers & Qt::ShiftModifier) + defaultAction = Qt::MoveAction; + else if (modifiers & Qt::AltModifier) + defaultAction = Qt::LinkAction; +#endif + + // if the object is set take the list of possibles from it + if (object) + possibleActions = object->d_func()->possible_actions; + +#ifdef QDND_DEBUG + qDebug("possible actions : %s", dragActionsToString(possibleActions).latin1()); +#endif + + // Check if the action determined is allowed + if (!(possibleActions & defaultAction)) { + if (possibleActions & Qt::CopyAction) + defaultAction = Qt::CopyAction; + else if (possibleActions & Qt::MoveAction) + defaultAction = Qt::MoveAction; + else if (possibleActions & Qt::LinkAction) + defaultAction = Qt::LinkAction; + else + defaultAction = Qt::IgnoreAction; + } + +#ifdef QDND_DEBUG + qDebug("default action : %s", dragActionsToString(defaultAction).latin1()); +#endif + + return defaultAction; +} + +void QDragManager::setCurrentTarget(QWidget *target, bool dropped) +{ + if (currentDropTarget == target) + return; + + currentDropTarget = target; + if (!dropped && object) { + object->d_func()->target = target; + emit object->targetChanged(target); + } + +} + +QWidget *QDragManager::currentTarget() +{ + return currentDropTarget; +} + +#endif + +QDropData::QDropData() + : QInternalMimeData() +{ +} + +QDropData::~QDropData() +{ +} +#endif // QT_NO_DRAGANDDROP + +#if !(defined(QT_NO_DRAGANDDROP) && defined(QT_NO_CLIPBOARD)) + +static QStringList imageReadMimeFormats() +{ + QStringList formats; + QList imageFormats = QImageReader::supportedImageFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + QString format = QLatin1String("image/"); + format += QString::fromLatin1(imageFormats.at(i).toLower()); + formats.append(format); + } + + //put png at the front because it is best + int pngIndex = formats.indexOf(QLatin1String("image/png")); + if (pngIndex != -1 && pngIndex != 0) + formats.move(pngIndex, 0); + + return formats; +} + + +static QStringList imageWriteMimeFormats() +{ + QStringList formats; + QList imageFormats = QImageWriter::supportedImageFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + QString format = QLatin1String("image/"); + format += QString::fromLatin1(imageFormats.at(i).toLower()); + formats.append(format); + } + + //put png at the front because it is best + int pngIndex = formats.indexOf(QLatin1String("image/png")); + if (pngIndex != -1 && pngIndex != 0) + formats.move(pngIndex, 0); + + return formats; +} + +QInternalMimeData::QInternalMimeData() + : QMimeData() +{ +} + +QInternalMimeData::~QInternalMimeData() +{ +} + +bool QInternalMimeData::hasFormat(const QString &mimeType) const +{ + bool foundFormat = hasFormat_sys(mimeType); + if (!foundFormat && mimeType == QLatin1String("application/x-qt-image")) { + QStringList imageFormats = imageReadMimeFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + if ((foundFormat = hasFormat_sys(imageFormats.at(i)))) + break; + } + } + return foundFormat; +} + +QStringList QInternalMimeData::formats() const +{ + QStringList realFormats = formats_sys(); + if (!realFormats.contains(QLatin1String("application/x-qt-image"))) { + QStringList imageFormats = imageReadMimeFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + if (realFormats.contains(imageFormats.at(i))) { + realFormats += QLatin1String("application/x-qt-image"); + break; + } + } + } + return realFormats; +} + +QVariant QInternalMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const +{ + QVariant data = retrieveData_sys(mimeType, type); + if (mimeType == QLatin1String("application/x-qt-image")) { + if (data.isNull() || (data.type() == QVariant::ByteArray && data.toByteArray().isEmpty())) { + // try to find an image + QStringList imageFormats = imageReadMimeFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + data = retrieveData_sys(imageFormats.at(i), type); + if (data.isNull() || (data.type() == QVariant::ByteArray && data.toByteArray().isEmpty())) + continue; + break; + } + } + // we wanted some image type, but all we got was a byte array. Convert it to an image. + if (data.type() == QVariant::ByteArray + && (type == QVariant::Image || type == QVariant::Pixmap || type == QVariant::Bitmap)) + data = QImage::fromData(data.toByteArray()); + + } else if (mimeType == QLatin1String("application/x-color") && data.type() == QVariant::ByteArray) { + QColor c; + QByteArray ba = data.toByteArray(); + if (ba.size() == 8) { + ushort * colBuf = (ushort *)ba.data(); + c.setRgbF(qreal(colBuf[0]) / qreal(0xFFFF), + qreal(colBuf[1]) / qreal(0xFFFF), + qreal(colBuf[2]) / qreal(0xFFFF), + qreal(colBuf[3]) / qreal(0xFFFF)); + data = c; + } else { + qWarning("Qt: Invalid color format"); + } + } else if (data.type() != type && data.type() == QVariant::ByteArray) { + // try to use mime data's internal conversion stuf. + QInternalMimeData *that = const_cast(this); + that->setData(mimeType, data.toByteArray()); + data = QMimeData::retrieveData(mimeType, type); + that->clear(); + } + return data; +} + +bool QInternalMimeData::canReadData(const QString &mimeType) +{ + return imageReadMimeFormats().contains(mimeType); +} + +// helper functions for rendering mimedata to the system, this is needed because QMimeData is in core. +QStringList QInternalMimeData::formatsHelper(const QMimeData *data) +{ + QStringList realFormats = data->formats(); + if (realFormats.contains(QLatin1String("application/x-qt-image"))) { + // add all supported image formats + QStringList imageFormats = imageWriteMimeFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + if (!realFormats.contains(imageFormats.at(i))) + realFormats.append(imageFormats.at(i)); + } + } + return realFormats; +} + +bool QInternalMimeData::hasFormatHelper(const QString &mimeType, const QMimeData *data) +{ + + bool foundFormat = data->hasFormat(mimeType); + if (!foundFormat) { + if (mimeType == QLatin1String("application/x-qt-image")) { + // check all supported image formats + QStringList imageFormats = imageWriteMimeFormats(); + for (int i = 0; i < imageFormats.size(); ++i) { + if ((foundFormat = data->hasFormat(imageFormats.at(i)))) + break; + } + } else if (mimeType.startsWith(QLatin1String("image/"))) { + return data->hasImage() && imageWriteMimeFormats().contains(mimeType); + } + } + return foundFormat; +} + +QByteArray QInternalMimeData::renderDataHelper(const QString &mimeType, const QMimeData *data) +{ + QByteArray ba; + if (mimeType == QLatin1String("application/x-color")) { + /* QMimeData can only provide colors as QColor or the name + of a color as a QByteArray or a QString. So we need to do + the conversion to application/x-color here. + The application/x-color format is : + type: application/x-color + format: 16 + data[0]: red + data[1]: green + data[2]: blue + data[3]: opacity + */ + ba.resize(8); + ushort * colBuf = (ushort *)ba.data(); + QColor c = qvariant_cast(data->colorData()); + colBuf[0] = ushort(c.redF() * 0xFFFF); + colBuf[1] = ushort(c.greenF() * 0xFFFF); + colBuf[2] = ushort(c.blueF() * 0xFFFF); + colBuf[3] = ushort(c.alphaF() * 0xFFFF); + } else { + ba = data->data(mimeType); + if (ba.isEmpty()) { + if (mimeType == QLatin1String("application/x-qt-image") && data->hasImage()) { + QImage image = qvariant_cast(data->imageData()); + QBuffer buf(&ba); + buf.open(QBuffer::WriteOnly); + // would there not be PNG ?? + image.save(&buf, "PNG"); + } else if (mimeType.startsWith(QLatin1String("image/")) && data->hasImage()) { + QImage image = qvariant_cast(data->imageData()); + QBuffer buf(&ba); + buf.open(QBuffer::WriteOnly); + image.save(&buf, mimeType.mid(mimeType.indexOf(QLatin1Char('/')) + 1).toLatin1().toUpper()); + } + } + } + return ba; +} + +#endif // QT_NO_DRAGANDDROP && QT_NO_CLIPBOARD + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdnd_mac.mm b/src/gui/kernel/qdnd_mac.mm new file mode 100644 index 0000000000..3af2ba007c --- /dev/null +++ b/src/gui/kernel/qdnd_mac.mm @@ -0,0 +1,753 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#ifndef QT_NO_DRAGANDDROP +#include "qbitmap.h" +#include "qcursor.h" +#include "qevent.h" +#include "qpainter.h" +#include "qurl.h" +#include "qwidget.h" +#include "qfile.h" +#include "qfileinfo.h" +#include +#include +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +QMacDndAnswerRecord qt_mac_dnd_answer_rec; + +/***************************************************************************** + QDnD debug facilities + *****************************************************************************/ +//#define DEBUG_DRAG_EVENTS +//#define DEBUG_DRAG_PROMISES + +/***************************************************************************** + QDnD globals + *****************************************************************************/ +bool qt_mac_in_drag = false; +#ifndef QT_MAC_USE_COCOA +static DragReference qt_mac_current_dragRef = 0; +#endif + +/***************************************************************************** + Externals + *****************************************************************************/ +extern void qt_mac_send_modifiers_changed(quint32, QObject *); //qapplication_mac.cpp +extern uint qGlobalPostedEventsCount(); //qapplication.cpp +extern RgnHandle qt_mac_get_rgn(); // qregion_mac.cpp +extern void qt_mac_dispose_rgn(RgnHandle); // qregion_mac.cpp +/***************************************************************************** + QDnD utility functions + *****************************************************************************/ + +//default pixmap +static const int default_pm_hotx = -2; +static const int default_pm_hoty = -16; +#ifndef QT_MAC_USE_COCOA +static const char * const default_pm[] = { + "13 9 3 1", + ". c None", + " c #000000", + "X c #FFFFFF", + "X X X X X X X", + " X X X X X X ", + "X ......... X", + " X.........X ", + "X ......... X", + " X.........X ", + "X ......... X", + " X X X X X X ", + "X X X X X X X", +}; +#endif + +//action management +#ifdef DEBUG_DRAG_EVENTS +# define MAP_MAC_ENUM(x) x, #x +#else +# define MAP_MAC_ENUM(x) x +#endif +struct mac_enum_mapper +{ + int mac_code; + int qt_code; +#ifdef DEBUG_DRAG_EVENTS + char *qt_desc; +#endif +}; +#ifndef QT_MAC_USE_COCOA +static mac_enum_mapper dnd_action_symbols[] = { + { kDragActionAlias, MAP_MAC_ENUM(Qt::LinkAction) }, + { kDragActionMove, MAP_MAC_ENUM(Qt::MoveAction) }, + { kDragActionGeneric, MAP_MAC_ENUM(Qt::CopyAction) }, + { kDragActionCopy, MAP_MAC_ENUM(Qt::CopyAction) }, + { 0, MAP_MAC_ENUM(0) } +}; +static DragActions qt_mac_dnd_map_qt_actions(Qt::DropActions qActions) +{ + DragActions ret = kDragActionNothing; + for(int i = 0; dnd_action_symbols[i].qt_code; ++i) { + if(qActions & dnd_action_symbols[i].qt_code) + ret |= dnd_action_symbols[i].mac_code; + } + return ret; +} +static Qt::DropActions qt_mac_dnd_map_mac_actions(DragActions macActions) +{ +#ifdef DEBUG_DRAG_EVENTS + qDebug("Converting DND ActionList: 0x%lx", macActions); +#endif + Qt::DropActions ret = Qt::IgnoreAction; + for(int i = 0; dnd_action_symbols[i].qt_code; ++i) { +#ifdef DEBUG_DRAG_EVENTS + qDebug(" %d) [%s] : %s", i, dnd_action_symbols[i].qt_desc, + (macActions & dnd_action_symbols[i].mac_code) ? "true" : "false"); +#endif + if(macActions & dnd_action_symbols[i].mac_code) + ret |= Qt::DropAction(dnd_action_symbols[i].qt_code); + } + return ret; +} +static Qt::DropAction qt_mac_dnd_map_mac_default_action(DragActions macActions) +{ + static Qt::DropAction preferred_actions[] = { Qt::CopyAction, Qt::LinkAction, //in order + Qt::MoveAction, Qt::IgnoreAction }; + Qt::DropAction ret = Qt::IgnoreAction; + const Qt::DropActions qtActions = qt_mac_dnd_map_mac_actions(macActions); + for(int i = 0; preferred_actions[i] != Qt::IgnoreAction; ++i) { + if(qtActions & preferred_actions[i]) { + ret = preferred_actions[i]; + break; + } + } +#ifdef DEBUG_DRAG_EVENTS + for(int i = 0; dnd_action_symbols[i].qt_code; ++i) { + if(dnd_action_symbols[i].qt_code == ret) { + qDebug("Got default action: %s [0x%lx]", dnd_action_symbols[i].qt_desc, macActions); + break; + } + } +#endif + return ret; +} +static void qt_mac_dnd_update_action(DragReference dragRef) { + SInt16 modifiers; + GetDragModifiers(dragRef, &modifiers, 0, 0); + qt_mac_send_modifiers_changed(modifiers, qApp); + + Qt::DropAction qtAction = Qt::IgnoreAction; + { + DragActions macAllowed = kDragActionNothing; + GetDragDropAction(dragRef, &macAllowed); + Qt::DropActions qtAllowed = qt_mac_dnd_map_mac_actions(macAllowed); + qtAction = QDragManager::self()->defaultAction(qtAllowed, QApplication::keyboardModifiers()); +#if 1 + if(!(qtAllowed & qtAction)) + qtAction = qt_mac_dnd_map_mac_default_action(macAllowed); +#endif + } + DragActions macAction = qt_mac_dnd_map_qt_actions(qtAction); + + DragActions currentActions; + GetDragDropAction(dragRef, ¤tActions); + if(currentActions != macAction) { + SetDragDropAction(dragRef, macAction); + QDragManager::self()->emitActionChanged(qtAction); + } +} +#endif // !QT_MAC_USE_COCOA + +/***************************************************************************** + DnD functions + *****************************************************************************/ +bool QDropData::hasFormat_sys(const QString &mime) const +{ +#ifndef QT_MAC_USE_COCOA + OSPasteboardRef board; + if(GetDragPasteboard(qt_mac_current_dragRef, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return false; + } + return QMacPasteboard(board, QMacPasteboardMime::MIME_DND).hasFormat(mime); +#else + Q_UNUSED(mime); + return false; +#endif // !QT_MAC_USE_COCOA +} + +QVariant QDropData::retrieveData_sys(const QString &mime, QVariant::Type type) const +{ +#ifndef QT_MAC_USE_COCOA + OSPasteboardRef board; + if(GetDragPasteboard(qt_mac_current_dragRef, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return QVariant(); + } + return QMacPasteboard(board, QMacPasteboardMime::MIME_DND).retrieveData(mime, type); +#else + Q_UNUSED(mime); + Q_UNUSED(type); + return QVariant(); +#endif // !QT_MAC_USE_COCOA +} + +QStringList QDropData::formats_sys() const +{ +#ifndef QT_MAC_USE_COCOA + OSPasteboardRef board; + if(GetDragPasteboard(qt_mac_current_dragRef, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return QStringList(); + } + return QMacPasteboard(board, QMacPasteboardMime::MIME_DND).formats(); +#else + return QStringList(); +#endif +} + +void QDragManager::timerEvent(QTimerEvent*) +{ +} + +bool QDragManager::eventFilter(QObject *, QEvent *) +{ + return false; +} + +void QDragManager::updateCursor() +{ +} + +void QDragManager::cancel(bool) +{ + if(object) { + beingCancelled = true; + object = 0; + } +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd); +#endif +} + +void QDragManager::move(const QPoint &) +{ +} + +void QDragManager::drop() +{ +} + +/** + If a drop action is already set on the carbon event + (from e.g. an earlier enter event), we insert the same + action on the new Qt event that has yet to be sendt. +*/ +static inline bool qt_mac_set_existing_drop_action(const DragRef &dragRef, QDropEvent &event) +{ +#ifndef QT_MAC_USE_COCOA + DragActions currentAction = kDragActionNothing; + OSStatus err = GetDragDropAction(dragRef, ¤tAction); + if (err == noErr && currentAction != kDragActionNothing) { + // This looks a bit evil, but we only ever set one action, so it's OK. + event.setDropAction(Qt::DropAction(int(qt_mac_dnd_map_mac_actions(currentAction)))); + return true; + } +#else + Q_UNUSED(dragRef); + Q_UNUSED(event); +#endif + return false; +} + +/** + If an answer rect has been set on the event (after being sent + to the global event processor), we store that rect so we can + check if the mouse is in the same area upon next drag move event. +*/ +void qt_mac_copy_answer_rect(const QDragMoveEvent &event) +{ + if (!event.answerRect().isEmpty()) { + qt_mac_dnd_answer_rec.rect = event.answerRect(); + qt_mac_dnd_answer_rec.buttons = event.mouseButtons(); + qt_mac_dnd_answer_rec.modifiers = event.keyboardModifiers(); + qt_mac_dnd_answer_rec.lastAction = event.dropAction(); + } +} + +bool qt_mac_mouse_inside_answer_rect(QPoint mouse) +{ + if (!qt_mac_dnd_answer_rec.rect.isEmpty() + && qt_mac_dnd_answer_rec.rect.contains(mouse) + && QApplication::mouseButtons() == qt_mac_dnd_answer_rec.buttons + && QApplication::keyboardModifiers() == qt_mac_dnd_answer_rec.modifiers) + return true; + else + return false; +} + +bool QWidgetPrivate::qt_mac_dnd_event(uint kind, DragRef dragRef) +{ +#ifndef QT_MAC_USE_COCOA + Q_Q(QWidget); + qt_mac_current_dragRef = dragRef; + if (kind != kEventControlDragLeave) + qt_mac_dnd_update_action(dragRef); + + Point mouse; + GetDragMouse(dragRef, &mouse, 0L); + if(!mouse.h && !mouse.v) + GetGlobalMouse(&mouse); + + if(QApplicationPrivate::modalState()) { + for(QWidget *modal = q; modal; modal = modal->parentWidget()) { + if(modal->isWindow()) { + if(modal != QApplication::activeModalWidget()) + return noErr; + break; + } + } + } + + //lookup the possible actions + DragActions allowed = kDragActionNothing; + GetDragAllowableActions(dragRef, &allowed); + Qt::DropActions qtAllowed = qt_mac_dnd_map_mac_actions(allowed); + + //lookup the source dragAccepted + QMimeData *dropdata = QDragManager::self()->dropData; + if(QDragManager::self()->source()) + dropdata = QDragManager::self()->dragPrivate()->data; + + // 'interrestedInDrag' should end up being 'true' if a later drop + // will be accepted by the widget for the current mouse position + bool interrestedInDrag = true; + + //Dispatch events + if (kind == kEventControlDragWithin) { + if (qt_mac_mouse_inside_answer_rect(q->mapFromGlobal(QPoint(mouse.h, mouse.v)))) + return qt_mac_dnd_answer_rec.lastAction == Qt::IgnoreAction; + else + qt_mac_dnd_answer_rec.clear(); + + QDragMoveEvent qDMEvent(q->mapFromGlobal(QPoint(mouse.h, mouse.v)), qtAllowed, dropdata, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + + // Accept the event by default if a + // drag action exists on the carbon event + if (qt_mac_set_existing_drop_action(dragRef, qDMEvent)) + qDMEvent.accept(); + + QApplication::sendEvent(q, &qDMEvent); + if (!qDMEvent.isAccepted() || qDMEvent.dropAction() == Qt::IgnoreAction) + interrestedInDrag = false; + + qt_mac_copy_answer_rect(qDMEvent); + SetDragDropAction(dragRef, qt_mac_dnd_map_qt_actions(qDMEvent.dropAction())); + + } else if (kind == kEventControlDragEnter) { + qt_mac_dnd_answer_rec.clear(); + + QDragEnterEvent qDEEvent(q->mapFromGlobal(QPoint(mouse.h, mouse.v)), qtAllowed, dropdata, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + qt_mac_set_existing_drop_action(dragRef, qDEEvent); + QApplication::sendEvent(q, &qDEEvent); + SetDragDropAction(dragRef, qt_mac_dnd_map_qt_actions(qDEEvent.dropAction())); + + if (!qDEEvent.isAccepted()) + // The widget is simply not interested in this + // drag. So tell carbon this by returning 'false'. We will then + // not receive any further move, drop or leave events for this widget. + return false; + else { + // Documentation states that a drag move event is sent immediately after + // a drag enter event. So we do that. This will honor widgets overriding + // 'dragMoveEvent' only, and not 'dragEnterEvent' + QDragMoveEvent qDMEvent(q->mapFromGlobal(QPoint(mouse.h, mouse.v)), qtAllowed, dropdata, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + qDMEvent.accept(); // accept by default, since enter event was accepted. + qDMEvent.setDropAction(qDEEvent.dropAction()); + QApplication::sendEvent(q, &qDMEvent); + if (!qDMEvent.isAccepted() || qDMEvent.dropAction() == Qt::IgnoreAction) + interrestedInDrag = false; + + qt_mac_copy_answer_rect(qDMEvent); + SetDragDropAction(dragRef, qt_mac_dnd_map_qt_actions(qDMEvent.dropAction())); + } + + } else if(kind == kEventControlDragLeave) { + QDragLeaveEvent de; + QApplication::sendEvent(q, &de); + } else if(kind == kEventControlDragReceive) { + QDropEvent de(q->mapFromGlobal(QPoint(mouse.h, mouse.v)), qtAllowed, dropdata, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + if(QDragManager::self()->object) + QDragManager::self()->dragPrivate()->target = q; + QApplication::sendEvent(q, &de); + if(!de.isAccepted()) { + interrestedInDrag = false; + SetDragDropAction(dragRef, kDragActionNothing); + } else { + if(QDragManager::self()->object) + QDragManager::self()->dragPrivate()->executed_action = de.dropAction(); + SetDragDropAction(dragRef, qt_mac_dnd_map_qt_actions(de.dropAction())); + } + } + +#ifdef DEBUG_DRAG_EVENTS + { + const char *desc = 0; + switch(kind) { + case kEventControlDragWithin: desc = "DragMove"; break; + case kEventControlDragEnter: desc = "DragEnter"; break; + case kEventControlDragLeave: desc = "DragLeave"; break; + case kEventControlDragReceive: desc = "DragDrop"; break; + } + if(desc) { + QPoint pos(q->mapFromGlobal(QPoint(mouse.h, mouse.v))); + qDebug("Sending <%s>(%d, %d) event to %p(%s::%s) [%d] (%p)", + desc, pos.x(), pos.y(), q, q->metaObject()->className(), + q->objectName().toLocal8Bit().constData(), ret, dragRef); + } + } +#endif + + //set the cursor + bool found_cursor = false; + if(kind == kEventControlDragWithin || kind == kEventControlDragEnter) { + ThemeCursor cursor = kThemeNotAllowedCursor; + found_cursor = true; + if (interrestedInDrag) { + DragActions action = kDragActionNothing; + GetDragDropAction(dragRef, &action); + switch(qt_mac_dnd_map_mac_default_action(action)) { + case Qt::IgnoreAction: + cursor = kThemeNotAllowedCursor; + break; + case Qt::MoveAction: + cursor = kThemeArrowCursor; + break; + case Qt::CopyAction: + cursor = kThemeCopyArrowCursor; + break; + case Qt::LinkAction: + cursor = kThemeAliasArrowCursor; + break; + default: + cursor = kThemeNotAllowedCursor; + break; + } + } + SetThemeCursor(cursor); + } + if(found_cursor) { + qt_mac_set_cursor(0); //just use our's + } else { + QCursor cursor(Qt::ArrowCursor); + if(qApp && qApp->overrideCursor()) { + cursor = *qApp->overrideCursor(); + } else if(q) { + for(QWidget *p = q; p; p = p->parentWidget()) { + if(p->isEnabled() && p->testAttribute(Qt::WA_SetCursor)) { + cursor = p->cursor(); + break; + } + } + } + qt_mac_set_cursor(&cursor); + } + + //idle things + if(qGlobalPostedEventsCount()) { + QApplication::sendPostedEvents(); + QApplication::flush(); + } + + // If this was not a drop, tell carbon that we will be interresed in receiving more + // events for the current drag. We do that by returning true. + if (kind == kEventControlDragReceive) + return interrestedInDrag; + else + return true; +#else + Q_UNUSED(kind); + Q_UNUSED(dragRef); + return false; +#endif // !QT_MAC_USE_COCOA +} + +#ifndef QT_MAC_USE_COCOA +Qt::DropAction QDragManager::drag(QDrag *o) +{ + + if(qt_mac_in_drag) { //just make sure.. + qWarning("Qt: Internal error: WH0A, unexpected condition reached"); + return Qt::IgnoreAction; + } + if(object == o) + return Qt::IgnoreAction; + /* At the moment it seems clear that Mac OS X does not want to drag with a non-left button + so we just bail early to prevent it */ + if(!(GetCurrentEventButtonState() & kEventMouseButtonPrimary)) + return Qt::IgnoreAction; + + if(object) { + dragPrivate()->source->removeEventFilter(this); + cancel(); + beingCancelled = false; + } + + object = o; + dragPrivate()->target = 0; + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart); +#endif + + //setup the data + QMacPasteboard dragBoard(QMacPasteboardMime::MIME_DND); + dragBoard.setMimeData(dragPrivate()->data); + + //create the drag + OSErr result; + DragRef dragRef; + if((result = NewDragWithPasteboard(dragBoard.pasteBoard(), &dragRef))) + return Qt::IgnoreAction; + //setup the actions + DragActions possibleActions = qt_mac_dnd_map_qt_actions(dragPrivate()->possible_actions); + SetDragAllowableActions(dragRef, //local + possibleActions, + true); + SetDragAllowableActions(dragRef, //remote (same as local) + possibleActions, + false); + + + QPoint hotspot; + QPixmap pix = dragPrivate()->pixmap; + if(pix.isNull()) { + if(dragPrivate()->data->hasText() || dragPrivate()->data->hasUrls()) { + //get the string + QString s = dragPrivate()->data->hasText() ? dragPrivate()->data->text() + : dragPrivate()->data->urls().first().toString(); + if(s.length() > 26) + s = s.left(23) + QChar(0x2026); + if(!s.isEmpty()) { + //draw it + QFont f(qApp->font()); + f.setPointSize(12); + QFontMetrics fm(f); + const int width = fm.width(s); + const int height = fm.height(); + if(width > 0 && height > 0) { + QPixmap tmp(width, height); + QPainter p(&tmp); + p.fillRect(0, 0, tmp.width(), tmp.height(), Qt::color0); + p.setPen(Qt::color1); + p.setFont(f); + p.drawText(0, fm.ascent(), s); + p.end(); + //save it + pix = tmp; + hotspot = QPoint(tmp.width() / 2, tmp.height() / 2); + } + } + } else { + pix = QPixmap(default_pm); + hotspot = QPoint(default_pm_hotx, default_pm_hoty); + } + } else { + hotspot = dragPrivate()->hotspot; + } + + //so we must fake an event + EventRecord fakeEvent; + GetGlobalMouse(&(fakeEvent.where)); + fakeEvent.message = 0; + fakeEvent.what = mouseDown; + fakeEvent.when = EventTimeToTicks(GetCurrentEventTime()); + fakeEvent.modifiers = GetCurrentKeyModifiers(); + if(GetCurrentEventButtonState() & 2) + fakeEvent.modifiers |= controlKey; + + //find the hotspot in relation to the pixmap + Point boundsPoint; + boundsPoint.h = fakeEvent.where.h - hotspot.x(); + boundsPoint.v = fakeEvent.where.v - hotspot.y(); + Rect boundsRect; + SetRect(&boundsRect, boundsPoint.h, boundsPoint.v, boundsPoint.h + pix.width(), boundsPoint.v + pix.height()); + + //set the drag image + QRegion dragRegion(boundsPoint.h, boundsPoint.v, pix.width(), pix.height()), pixRegion; + if(!pix.isNull()) { + HIPoint hipoint = { -hotspot.x(), -hotspot.y() }; + CGImageRef img = (CGImageRef)pix.macCGHandle(); + SetDragImageWithCGImage(dragRef, img, &hipoint, 0); + CGImageRelease(img); + } + + SetDragItemBounds(dragRef, (ItemReference)1 , &boundsRect); + { //do the drag + qt_mac_in_drag = true; + qt_mac_dnd_update_action(dragRef); + result = TrackDrag(dragRef, &fakeEvent, QMacSmartQuickDrawRegion(dragRegion.toQDRgn())); + qt_mac_in_drag = false; + } + object = 0; + if(result == noErr) { + // Check if the receiver points us to + // a file location. If so, we need to do + // the file copy/move ourselves: + QCFType pasteLocation = 0; + PasteboardCopyPasteLocation(dragBoard.pasteBoard(), &pasteLocation); + if (pasteLocation){ + Qt::DropAction action = o->d_func()->defaultDropAction; + if (action == Qt::IgnoreAction){ + if (o->d_func()->possible_actions & Qt::MoveAction) + action = Qt::MoveAction; + else if (o->d_func()->possible_actions & Qt::CopyAction) + action = Qt::CopyAction; + + } + bool atleastOne = false; + QList urls = o->mimeData()->urls(); + for (int i = 0; i < urls.size(); ++i){ + QUrl fromUrl = urls.at(i); + QString filename = QFileInfo(fromUrl.path()).fileName(); + QUrl toUrl(QCFString::toQString(CFURLGetString(pasteLocation)) + filename); + if (action == Qt::MoveAction){ + if (QFile::rename(fromUrl.path(), toUrl.path())) + atleastOne = true; + } else if (action == Qt::CopyAction){ + if (QFile::copy(fromUrl.path(), toUrl.path())) + atleastOne = true; + } + } + if (atleastOne){ + DisposeDrag(dragRef); + o->setMimeData(0); + o->deleteLater(); + return action; + } + } + + DragActions ret = kDragActionNothing; + GetDragDropAction(dragRef, &ret); + DisposeDrag(dragRef); //cleanup + o->setMimeData(0); + o->deleteLater(); + return qt_mac_dnd_map_mac_default_action(ret); + } + DisposeDrag(dragRef); //cleanup + return Qt::IgnoreAction; +} +#endif + +void QDragManager::updatePixmap() +{ +} + +QCocoaDropData::QCocoaDropData(CFStringRef pasteboard) + : QInternalMimeData() +{ + NSString* pasteboardName = (NSString*)pasteboard; + [pasteboardName retain]; + dropPasteboard = pasteboard; +} + +QCocoaDropData::~QCocoaDropData() +{ + NSString* pasteboardName = (NSString*)dropPasteboard; + [pasteboardName release]; +} + +QStringList QCocoaDropData::formats_sys() const +{ + QStringList formats; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return formats; + } + formats = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).formats(); + return formats; +} + +QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const +{ + QVariant data; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return data; + } + data = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).retrieveData(mimeType, type); + CFRelease(board); + return data; +} + +bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const +{ + bool has = false; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return has; + } + has = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).hasFormat(mimeType); + CFRelease(board); + return has; +} + +#endif // QT_NO_DRAGANDDROP +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdnd_p.h b/src/gui/kernel/qdnd_p.h new file mode 100644 index 0000000000..754366637c --- /dev/null +++ b/src/gui/kernel/qdnd_p.h @@ -0,0 +1,336 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDND_P_H +#define QDND_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qobject.h" +#include "QtCore/qmap.h" +#include "QtGui/qmime.h" +#include "QtGui/qdrag.h" +#include "QtGui/qpixmap.h" +#include "QtGui/qcursor.h" +#include "QtCore/qpoint.h" +#include "private/qobject_p.h" +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +#endif + +#if defined(Q_WS_WIN) +# include +# include +#endif + +QT_BEGIN_NAMESPACE + +class QEventLoop; + +#if !(defined(QT_NO_DRAGANDDROP) && defined(QT_NO_CLIPBOARD)) + +class Q_GUI_EXPORT QInternalMimeData : public QMimeData +{ + Q_OBJECT +public: + QInternalMimeData(); + ~QInternalMimeData(); + + bool hasFormat(const QString &mimeType) const; + QStringList formats() const; + static bool canReadData(const QString &mimeType); + + + static QStringList formatsHelper(const QMimeData *data); + static bool hasFormatHelper(const QString &mimeType, const QMimeData *data); + static QByteArray renderDataHelper(const QString &mimeType, const QMimeData *data); + +protected: + QVariant retrieveData(const QString &mimeType, QVariant::Type type) const; + + virtual bool hasFormat_sys(const QString &mimeType) const = 0; + virtual QStringList formats_sys() const = 0; + virtual QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const = 0; +}; + +#ifdef Q_WS_WIN +class QOleDataObject : public IDataObject +{ +public: + explicit QOleDataObject(QMimeData *mimeData); + virtual ~QOleDataObject(); + + void releaseQt(); + const QMimeData *mimeData() const; + DWORD reportedPerformedEffect() const; + + // IUnknown methods + STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); + STDMETHOD_(ULONG,AddRef)(void); + STDMETHOD_(ULONG,Release)(void); + + // IDataObject methods + STDMETHOD(GetData)(LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium); + STDMETHOD(GetDataHere)(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium); + STDMETHOD(QueryGetData)(LPFORMATETC pformatetc); + STDMETHOD(GetCanonicalFormatEtc)(LPFORMATETC pformatetc, LPFORMATETC pformatetcOut); + STDMETHOD(SetData)(LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium, + BOOL fRelease); + STDMETHOD(EnumFormatEtc)(DWORD dwDirection, LPENUMFORMATETC FAR* ppenumFormatEtc); + STDMETHOD(DAdvise)(FORMATETC FAR* pFormatetc, DWORD advf, + LPADVISESINK pAdvSink, DWORD FAR* pdwConnection); + STDMETHOD(DUnadvise)(DWORD dwConnection); + STDMETHOD(EnumDAdvise)(LPENUMSTATDATA FAR* ppenumAdvise); + +private: + ULONG m_refs; + QPointer data; + int CF_PERFORMEDDROPEFFECT; + DWORD performedEffect; +}; + +class QOleEnumFmtEtc : public IEnumFORMATETC +{ +public: + explicit QOleEnumFmtEtc(const QVector &fmtetcs); + explicit QOleEnumFmtEtc(const QVector &lpfmtetcs); + virtual ~QOleEnumFmtEtc(); + + bool isNull() const; + + // IUnknown methods + STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); + STDMETHOD_(ULONG,AddRef)(void); + STDMETHOD_(ULONG,Release)(void); + + // IEnumFORMATETC methods + STDMETHOD(Next)(ULONG celt, LPFORMATETC rgelt, ULONG FAR* pceltFetched); + STDMETHOD(Skip)(ULONG celt); + STDMETHOD(Reset)(void); + STDMETHOD(Clone)(LPENUMFORMATETC FAR* newEnum); + +private: + bool copyFormatEtc(LPFORMATETC dest, LPFORMATETC src) const; + + ULONG m_dwRefs; + ULONG m_nIndex; + QVector m_lpfmtetcs; + bool m_isNull; +}; + +#endif + +#endif //QT_NO_DRAGANDDROP && QT_NO_CLIPBOARD + +#ifndef QT_NO_DRAGANDDROP + +class QDragPrivate : public QObjectPrivate +{ +public: + QWidget *source; + QWidget *target; + QMimeData *data; + QPixmap pixmap; + QPoint hotspot; + Qt::DropActions possible_actions; + Qt::DropAction executed_action; + QMap customCursors; + Qt::DropAction defaultDropAction; +}; + +class QDropData : public QInternalMimeData +{ + Q_OBJECT +public: + QDropData(); + ~QDropData(); + +protected: + bool hasFormat_sys(const QString &mimeType) const; + QStringList formats_sys() const; + QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const; + +#if defined(Q_WS_WIN) +public: + LPDATAOBJECT currentDataObject; +#endif +}; + +class QDragManager: public QObject { + Q_OBJECT + + QDragManager(); + ~QDragManager(); + // only friend classes can use QDragManager. + friend class QDrag; + friend class QDragMoveEvent; + friend class QDropEvent; + friend class QApplication; +#ifdef Q_WS_MAC + friend class QWidgetPrivate; //dnd is implemented here +#endif + + bool eventFilter(QObject *, QEvent *); + void timerEvent(QTimerEvent*); + +public: + Qt::DropAction drag(QDrag *); + + void cancel(bool deleteSource = true); + void move(const QPoint &); + void drop(); + void updatePixmap(); + QWidget *source() const { return object ? object->d_func()->source : 0; } + QDragPrivate *dragPrivate() const { return object ? object->d_func() : 0; } + static QDragPrivate *dragPrivate(QDrag *drag) { return drag ? drag->d_func() : 0; } + + static QDragManager *self(); + Qt::DropAction defaultAction(Qt::DropActions possibleActions, + Qt::KeyboardModifiers modifiers) const; + + QDrag *object; + + void updateCursor(); + + bool beingCancelled; + bool restoreCursor; + bool willDrop; + QEventLoop *eventLoop; + + QPixmap dragCursor(Qt::DropAction action) const; + + bool hasCustomDragCursors() const; + + QDropData *dropData; + + void emitActionChanged(Qt::DropAction newAction) { if (object) emit object->actionChanged(newAction); } + + void setCurrentTarget(QWidget *target, bool dropped = false); + QWidget *currentTarget(); + +#ifdef Q_WS_X11 + QPixmap xdndMimeTransferedPixmap[2]; + int xdndMimeTransferedPixmapIndex; +#endif + +private: +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + Qt::DropAction currentActionForOverrideCursor; +#endif +#ifdef Q_OS_SYMBIAN +#ifndef QT_NO_CURSOR + QCursor overrideCursor; +#endif +#endif + QWidget *currentDropTarget; + + static QDragManager *instance; + Q_DISABLE_COPY(QDragManager) +}; + + +#if defined(Q_WS_WIN) + +class QOleDropTarget : public IDropTarget +{ +public: + QOleDropTarget(QWidget* w); + virtual ~QOleDropTarget() {} + + void releaseQt(); + + // IUnknown methods + STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj); + STDMETHOD_(ULONG, AddRef)(void); + STDMETHOD_(ULONG, Release)(void); + + // IDropTarget methods + STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); + STDMETHOD(DragOver)(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); + STDMETHOD(DragLeave)(); + STDMETHOD(Drop)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); + +private: + ULONG m_refs; + QWidget* widget; + QPointer currentWidget; + QRect answerRect; + QPoint lastPoint; + DWORD chosenEffect; + DWORD lastKeyState; + + void sendDragEnterEvent(QWidget *to, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect); +}; + +#endif + +#if defined (Q_WS_MAC) +class QCocoaDropData : public QInternalMimeData +{ + Q_OBJECT +public: + QCocoaDropData(CFStringRef pasteboard); + ~QCocoaDropData(); + +protected: + bool hasFormat_sys(const QString &mimeType) const; + QStringList formats_sys() const; + QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const; +public: + CFStringRef dropPasteboard; +}; +#endif + +#endif // !QT_NO_DRAGANDDROP + + +QT_END_NAMESPACE + +#endif // QDND_P_H diff --git a/src/gui/kernel/qdnd_qws.cpp b/src/gui/kernel/qdnd_qws.cpp new file mode 100644 index 0000000000..b744c2f085 --- /dev/null +++ b/src/gui/kernel/qdnd_qws.cpp @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" + +#ifndef QT_NO_DRAGANDDROP + +#include "qwidget.h" +#include "qdatetime.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qevent.h" +#include "qpainter.h" +#include "qdnd_p.h" + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +static QPixmap *defaultPm = 0; +static const int default_pm_hotx = -2; +static const int default_pm_hoty = -16; +static const char *const default_pm[] = { +"13 9 3 1", +". c None", +" c #000000", +"X c #FFFFFF", +"X X X X X X X", +" X X X X X X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X X X X X X ", +"X X X X X X X", +}; + +// Shift/Ctrl handling, and final drop status +static Qt::DropAction global_accepted_action = Qt::CopyAction; +static Qt::DropActions possible_actions = Qt::IgnoreAction; + + +// static variables in place of a proper cross-process solution +static QDrag *drag_object; +static bool qt_qws_dnd_dragging = false; + + +static Qt::KeyboardModifiers oldstate; + +class QShapedPixmapWidget : public QWidget { + QPixmap pixmap; +public: + QShapedPixmapWidget() : + QWidget(0, Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint) + { + // ### Temporary workaround for 4.2-rc1!!! To prevent flickering when + // using drag'n drop in a client application. (task 126956) + // setAttribute() should be done unconditionally! + if (QApplication::type() == QApplication::GuiServer) + setAttribute(Qt::WA_TransparentForMouseEvents); + } + + void setPixmap(QPixmap pm) + { + pixmap = pm; + if (!pixmap.mask().isNull()) { + setMask(pixmap.mask()); + } else { + clearMask(); + } + resize(pm.width(),pm.height()); + } + + void paintEvent(QPaintEvent*) + { + QPainter p(this); + p.drawPixmap(0,0,pixmap); + } +}; + + +static QShapedPixmapWidget *qt_qws_dnd_deco = 0; + + +void QDragManager::updatePixmap() +{ + if (qt_qws_dnd_deco) { + QPixmap pm; + QPoint pm_hot(default_pm_hotx,default_pm_hoty); + if (drag_object) { + pm = drag_object->pixmap(); + if (!pm.isNull()) + pm_hot = drag_object->hotSpot(); + } + if (pm.isNull()) { + if (!defaultPm) + defaultPm = new QPixmap(default_pm); + pm = *defaultPm; + } + qt_qws_dnd_deco->setPixmap(pm); + qt_qws_dnd_deco->move(QCursor::pos()-pm_hot); + if (willDrop) { + qt_qws_dnd_deco->show(); + } else { + qt_qws_dnd_deco->hide(); + } + } +} + +void QDragManager::timerEvent(QTimerEvent *) { } + +void QDragManager::move(const QPoint &) { } + +void QDragManager::updateCursor() +{ +#ifndef QT_NO_CURSOR + if (willDrop) { + if (qt_qws_dnd_deco) + qt_qws_dnd_deco->show(); + if (currentActionForOverrideCursor != global_accepted_action) { + QApplication::changeOverrideCursor(QCursor(dragCursor(global_accepted_action), 0, 0)); + currentActionForOverrideCursor = global_accepted_action; + } + } else { + QCursor *overrideCursor = QApplication::overrideCursor(); + if (!overrideCursor || overrideCursor->shape() != Qt::ForbiddenCursor) { + QApplication::changeOverrideCursor(QCursor(Qt::ForbiddenCursor)); + currentActionForOverrideCursor = Qt::IgnoreAction; + } + if (qt_qws_dnd_deco) + qt_qws_dnd_deco->hide(); + } +#endif +} + + +bool QDragManager::eventFilter(QObject *o, QEvent *e) +{ + if (beingCancelled) { + if (e->type() == QEvent::KeyRelease && static_cast(e)->key() == Qt::Key_Escape) { + qApp->removeEventFilter(this); + Q_ASSERT(object == 0); + beingCancelled = false; + eventLoop->exit(); + return true; // block the key release + } + return false; + } + + + + if (!o->isWidgetType()) + return false; + + switch(e->type()) { + case QEvent::ShortcutOverride: + // prevent accelerators from firing while dragging + e->accept(); + return true; + + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + QKeyEvent *ke = ((QKeyEvent*)e); + if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + eventLoop->exit(); + } else { + updateCursor(); + } + return true; // Eat all key events + } + + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + { + if (!object) { //#### this should not happen + qWarning("QDragManager::eventFilter: No object"); + return true; + } + + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + if (manager->object) + possible_actions = manager->dragPrivate()->possible_actions; + else + possible_actions = Qt::IgnoreAction; + + QMouseEvent *me = (QMouseEvent *)e; + if (me->buttons()) { + Qt::DropAction prevAction = global_accepted_action; + QWidget *cw = QApplication::widgetAt(me->globalPos()); + + // Fix for when we move mouse on to the deco widget + if (qt_qws_dnd_deco && cw == qt_qws_dnd_deco) + cw = object->target(); + + while (cw && !cw->acceptDrops() && !cw->isWindow()) + cw = cw->parentWidget(); + + if (object->target() != cw) { + if (object->target()) { + QDragLeaveEvent dle; + QApplication::sendEvent(object->target(), &dle); + willDrop = false; + global_accepted_action = Qt::IgnoreAction; + updateCursor(); + restoreCursor = true; + object->d_func()->target = 0; + } + if (cw && cw->acceptDrops()) { + object->d_func()->target = cw; + QDragEnterEvent dee(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + QApplication::sendEvent(object->target(), &dee); + willDrop = dee.isAccepted() && dee.dropAction() != Qt::IgnoreAction; + global_accepted_action = willDrop ? dee.dropAction() : Qt::IgnoreAction; + updateCursor(); + restoreCursor = true; + } + } else if (cw) { + QDragMoveEvent dme(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + if (global_accepted_action != Qt::IgnoreAction) { + dme.setDropAction(global_accepted_action); + dme.accept(); + } + QApplication::sendEvent(cw, &dme); + willDrop = dme.isAccepted(); + global_accepted_action = willDrop ? dme.dropAction() : Qt::IgnoreAction; + updatePixmap(); + updateCursor(); + } + if (global_accepted_action != prevAction) + emitActionChanged(global_accepted_action); + } + return true; // Eat all mouse events + } + + case QEvent::MouseButtonRelease: + { + qApp->removeEventFilter(this); + if (restoreCursor) { + willDrop = false; +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif + restoreCursor = false; + } + if (object && object->target()) { + QMouseEvent *me = (QMouseEvent *)e; + + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + + QDropEvent de(object->target()->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + QApplication::sendEvent(object->target(), &de); + if (de.isAccepted()) + global_accepted_action = de.dropAction(); + else + global_accepted_action = Qt::IgnoreAction; + + if (object) + object->deleteLater(); + drag_object = object = 0; + } + eventLoop->exit(); + return true; // Eat all mouse events + } + + default: + break; + } + + return false; +} + +Qt::DropAction QDragManager::drag(QDrag *o) +{ + if (object == o || !o || !o->source()) + return Qt::IgnoreAction; + + if (object) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + } + + object = drag_object = o; + qt_qws_dnd_deco = new QShapedPixmapWidget(); + oldstate = Qt::NoModifier; // #### Should use state that caused the drag +// drag_mode = mode; + + willDrop = false; + updatePixmap(); + updateCursor(); + restoreCursor = true; + object->d_func()->target = 0; + qApp->installEventFilter(this); + + global_accepted_action = Qt::CopyAction; +#ifndef QT_NO_CURSOR + qApp->setOverrideCursor(Qt::ArrowCursor); + restoreCursor = true; + updateCursor(); +#endif + + qt_qws_dnd_dragging = true; + + eventLoop = new QEventLoop; + (void) eventLoop->exec(); + delete eventLoop; + eventLoop = 0; + + delete qt_qws_dnd_deco; + qt_qws_dnd_deco = 0; + qt_qws_dnd_dragging = false; + + + return global_accepted_action; +} + + +void QDragManager::cancel(bool deleteSource) +{ +// qDebug("QDragManager::cancel"); + beingCancelled = true; + + if (object->target()) { + QDragLeaveEvent dle; + QApplication::sendEvent(object->target(), &dle); + } + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + if (drag_object) { + if (deleteSource) + object->deleteLater(); + drag_object = object = 0; + } + + delete qt_qws_dnd_deco; + qt_qws_dnd_deco = 0; + + global_accepted_action = Qt::IgnoreAction; +} + + +void QDragManager::drop() +{ +} + +QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type type) const +{ + if (!drag_object) + return QVariant(); + QByteArray data = drag_object->mimeData()->data(mimetype); + if (type == QVariant::String) + return QString::fromUtf8(data); + return data; +} + +bool QDropData::hasFormat_sys(const QString &format) const +{ + return formats().contains(format); +} + +QStringList QDropData::formats_sys() const +{ + if (drag_object) + return drag_object->mimeData()->formats(); + return QStringList(); +} + + +#endif // QT_NO_DRAGANDDROP + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qdnd_s60.cpp b/src/gui/kernel/qdnd_s60.cpp new file mode 100644 index 0000000000..a9847a98f8 --- /dev/null +++ b/src/gui/kernel/qdnd_s60.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" + +#ifndef QT_NO_DRAGANDDROP + +#include "qwidget.h" +#include "qdatetime.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qevent.h" +#include "qpainter.h" +#include "qdnd_p.h" +#include "qt_s60_p.h" + +#include +// pointer cursor +#include +#include +#include + +QT_BEGIN_NAMESPACE +//### artistic impression of Symbians default DnD cursor ? + +static QPixmap *defaultPm = 0; +static const int default_pm_hotx = -50; +static const int default_pm_hoty = -50; +static const char *const default_pm[] = { +"13 9 3 1", +". c None", +" c #000000", +"X c #FFFFFF", +"X X X X X X X", +" X X X X X X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X X X X X X ", +"X X X X X X X", +}; +//### actions need to be redefined for S60 +// Shift/Ctrl handling, and final drop status +static Qt::DropAction global_accepted_action = Qt::MoveAction; +static Qt::DropActions possible_actions = Qt::IgnoreAction; + + +// static variables in place of a proper cross-process solution +static QDrag *drag_object; +static bool qt_symbian_dnd_dragging = false; + + +static Qt::KeyboardModifiers oldstate; + +void QDragManager::updatePixmap() +{ + QPixmap pm; + QPoint pm_hot(default_pm_hotx,default_pm_hoty); + if (drag_object) { + pm = drag_object->pixmap(); + if (!pm.isNull()) + pm_hot = drag_object->hotSpot(); + } + if (pm.isNull()) { + if (!defaultPm) + defaultPm = new QPixmap(default_pm); + pm = *defaultPm; + } +#ifndef QT_NO_CURSOR + QCursor cursor(pm, pm_hot.x(), pm_hot.y()); + overrideCursor = cursor; +#endif +} + +void QDragManager::timerEvent(QTimerEvent *) { } + +void QDragManager::move(const QPoint&) { +} + +void QDragManager::updateCursor() +{ +#ifndef QT_NO_CURSOR + QCursor cursor = willDrop ? overrideCursor : Qt::ForbiddenCursor; + if (!restoreCursor) { + QApplication::setOverrideCursor(cursor); + restoreCursor = true; + } + else { + QApplication::changeOverrideCursor(cursor); + } +#endif +} + + +bool QDragManager::eventFilter(QObject *o, QEvent *e) +{ + if (beingCancelled) { + return false; + } + if (!o->isWidgetType()) + return false; + + switch(e->type()) { + case QEvent::MouseButtonPress: + { + } + case QEvent::MouseMove: + { + if (!object) { //#### this should not happen + qWarning("QDragManager::eventFilter: No object"); + return true; + } + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + if (manager->object) + possible_actions = manager->dragPrivate()->possible_actions; + else + possible_actions = Qt::IgnoreAction; + + QMouseEvent *me = (QMouseEvent *)e; + + if (me->buttons()) { + Qt::DropAction prevAction = global_accepted_action; + QWidget *cw = QApplication::widgetAt(me->globalPos()); + // map the Coords relative to the window. + if (!cw) + return true; + + while (cw && !cw->acceptDrops() && !cw->isWindow()) + cw = cw->parentWidget(); + + bool oldWillDrop = willDrop; + if (object->target() != cw) { + if (object->target()) { + QDragLeaveEvent dle; + QApplication::sendEvent(object->target(), &dle); + willDrop = false; + global_accepted_action = Qt::IgnoreAction; + if (oldWillDrop != willDrop) + updateCursor(); + object->d_func()->target = 0; + } + if (cw && cw->acceptDrops()) { + object->d_func()->target = cw; + QDragEnterEvent dee(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + QApplication::sendEvent(object->target(), &dee); + willDrop = dee.isAccepted() && dee.dropAction() != Qt::IgnoreAction; + global_accepted_action = willDrop ? dee.dropAction() : Qt::IgnoreAction; + if (oldWillDrop != willDrop) + updateCursor(); + } + } else if (cw) { + QDragMoveEvent dme(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + if (global_accepted_action != Qt::IgnoreAction) { + dme.setDropAction(global_accepted_action); + dme.accept(); + } + QApplication::sendEvent(cw, &dme); + willDrop = dme.isAccepted(); + global_accepted_action = willDrop ? dme.dropAction() : Qt::IgnoreAction; + if (oldWillDrop != willDrop) { + updatePixmap(); + updateCursor(); + } + } + if (global_accepted_action != prevAction) + emitActionChanged(global_accepted_action); + } + return true; // Eat all mouse events + } + + case QEvent::MouseButtonRelease: + { + qApp->removeEventFilter(this); +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + willDrop = false; + restoreCursor = false; + } +#endif + if (object && object->target()) { + + QMouseEvent *me = (QMouseEvent *)e; + + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + + QDropEvent de(object->target()->mapFromGlobal(me->globalPos()), possible_actions, dropData, + me->buttons(), me->modifiers()); + QApplication::sendEvent(object->target(), &de); + if (de.isAccepted()) + global_accepted_action = de.dropAction(); + else + global_accepted_action = Qt::IgnoreAction; + + if (object) + object->deleteLater(); + drag_object = object = 0; + } + eventLoop->exit(); + return true; // Eat all mouse events + } + + default: + break; + } + return false; +} + +Qt::DropAction QDragManager::drag(QDrag *o) +{ + Q_ASSERT(!qt_symbian_dnd_dragging); + if (object == o || !o || !o->source()) + return Qt::IgnoreAction; + + if (object) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + } + + object = drag_object = o; + + oldstate = Qt::NoModifier; // #### Should use state that caused the drag + willDrop = false; + updatePixmap(); + updateCursor(); + +#ifndef QT_NO_CURSOR + qt_symbian_set_cursor_visible(true); //force cursor on even for touch phone +#endif + + object->d_func()->target = 0; + + qApp->installEventFilter(this); + + global_accepted_action = defaultAction(dragPrivate()->possible_actions, Qt::NoModifier); + qt_symbian_dnd_dragging = true; + + eventLoop = new QEventLoop; + // block + (void) eventLoop->exec(QEventLoop::AllEvents); + delete eventLoop; + eventLoop = 0; + +#ifndef QT_NO_CURSOR + qt_symbian_set_cursor_visible(false); + + overrideCursor = QCursor(); //deref the cursor data + qt_symbian_dnd_dragging = false; +#endif + + return global_accepted_action; +} + + +void QDragManager::cancel(bool deleteSource) +{ + beingCancelled = true; + + if (object->target()) { + QDragLeaveEvent dle; + QApplication::sendEvent(object->target(), &dle); + } + + if (drag_object) { + if (deleteSource) + object->deleteLater(); + drag_object = object = 0; + } + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + global_accepted_action = Qt::IgnoreAction; +} + + +void QDragManager::drop() +{ +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif +} + +QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type type) const +{ + if (!drag_object) + return QVariant(); + QByteArray data = drag_object->mimeData()->data(mimetype); + if (type == QVariant::String) + return QString::fromUtf8(data); + return data; +} + +bool QDropData::hasFormat_sys(const QString &format) const +{ + return formats().contains(format); +} + +QStringList QDropData::formats_sys() const +{ + if (drag_object) + return drag_object->mimeData()->formats(); + return QStringList(); +} + +QT_END_NAMESPACE +#endif // QT_NO_DRAGANDDROP diff --git a/src/gui/kernel/qdnd_win.cpp b/src/gui/kernel/qdnd_win.cpp new file mode 100644 index 0000000000..176e3cef7f --- /dev/null +++ b/src/gui/kernel/qdnd_win.cpp @@ -0,0 +1,1027 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" + +#include "qapplication_p.h" +#include "qevent.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbuffer.h" +#include "qdatastream.h" +#include "qcursor.h" +#include "qt_windows.h" +#include +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#include "qdnd_p.h" +#include "qdebug.h" + +#if defined(Q_OS_WINCE) +#include "qguifunctions_wince.h" +#endif + +// support for xbuttons +#ifndef MK_XBUTTON1 +#define MK_XBUTTON1 0x0020 +#define MK_XBUTTON2 0x0040 +#endif + +QT_BEGIN_NAMESPACE + +#if !(defined(QT_NO_DRAGANDDROP) && defined(QT_NO_CLIPBOARD)) + +//--------------------------------------------------------------------- +// QOleDataObject Constructor +//--------------------------------------------------------------------- + +QOleDataObject::QOleDataObject(QMimeData *mimeData) +{ + m_refs = 1; + data = mimeData; + CF_PERFORMEDDROPEFFECT = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT); + performedEffect = DROPEFFECT_NONE; +} + +QOleDataObject::~QOleDataObject() +{ +} + +void QOleDataObject::releaseQt() +{ + data = 0; +} + +const QMimeData *QOleDataObject::mimeData() const +{ + return data; +} + +DWORD QOleDataObject::reportedPerformedEffect() const +{ + return performedEffect; +} + +//--------------------------------------------------------------------- +// IUnknown Methods +//--------------------------------------------------------------------- + +STDMETHODIMP +QOleDataObject::QueryInterface(REFIID iid, void FAR* FAR* ppv) +{ + if (iid == IID_IUnknown || iid == IID_IDataObject) { + *ppv = this; + AddRef(); + return NOERROR; + } + *ppv = NULL; + return ResultFromScode(E_NOINTERFACE); +} + +STDMETHODIMP_(ULONG) +QOleDataObject::AddRef(void) +{ + return ++m_refs; +} + +STDMETHODIMP_(ULONG) +QOleDataObject::Release(void) +{ + if (--m_refs == 0) { + releaseQt(); + delete this; + return 0; + } + return m_refs; +} + +//--------------------------------------------------------------------- +// IDataObject Methods +// +// The following methods are NOT supported for data transfer using the +// clipboard or drag-drop: +// +// IDataObject::SetData -- return E_NOTIMPL +// IDataObject::DAdvise -- return OLE_E_ADVISENOTSUPPORTED +// ::DUnadvise +// ::EnumDAdvise +// IDataObject::GetCanonicalFormatEtc -- return E_NOTIMPL +// (NOTE: must set pformatetcOut->ptd = NULL) +//--------------------------------------------------------------------- + +STDMETHODIMP +QOleDataObject::GetData(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium) +{ +#ifdef QDND_DEBUG + qDebug("QOleDataObject::GetData(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium)"); +#ifndef Q_OS_WINCE + wchar_t buf[256] = {0}; + GetClipboardFormatName(pformatetc->cfFormat, buf, 255); + qDebug("CF = %d : %s", pformatetc->cfFormat, QString::fromWCharArray(buf)); +#endif +#endif + + if (!data) + return ResultFromScode(DATA_E_FORMATETC); + + QWindowsMime *converter = QWindowsMime::converterFromMime(*pformatetc, data); + + if (converter && converter->convertFromMime(*pformatetc, data, pmedium)) + return ResultFromScode(S_OK); + else + return ResultFromScode(DATA_E_FORMATETC); +} + +STDMETHODIMP +QOleDataObject::GetDataHere(LPFORMATETC, LPSTGMEDIUM) +{ + return ResultFromScode(DATA_E_FORMATETC); +} + +STDMETHODIMP +QOleDataObject::QueryGetData(LPFORMATETC pformatetc) +{ +#ifdef QDND_DEBUG + qDebug("QOleDataObject::QueryGetData(LPFORMATETC pformatetc)"); +#endif + + if (!data) + return ResultFromScode(DATA_E_FORMATETC); + + if (QWindowsMime::converterFromMime(*pformatetc, data)) + return ResultFromScode(S_OK); + return ResultFromScode(S_FALSE); +} + +STDMETHODIMP +QOleDataObject::GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC pformatetcOut) +{ + pformatetcOut->ptd = NULL; + return ResultFromScode(E_NOTIMPL); +} + +STDMETHODIMP +QOleDataObject::SetData(LPFORMATETC pFormatetc, STGMEDIUM *pMedium, BOOL fRelease) +{ + if (pFormatetc->cfFormat == CF_PERFORMEDDROPEFFECT && pMedium->tymed == TYMED_HGLOBAL) { + DWORD * val = (DWORD*)GlobalLock(pMedium->hGlobal); + performedEffect = *val; + GlobalUnlock(pMedium->hGlobal); + if (fRelease) + ReleaseStgMedium(pMedium); + return ResultFromScode(S_OK); + } + return ResultFromScode(E_NOTIMPL); +} + + +STDMETHODIMP +QOleDataObject::EnumFormatEtc(DWORD dwDirection, LPENUMFORMATETC FAR* ppenumFormatEtc) +{ +#ifdef QDND_DEBUG + qDebug("QOleDataObject::EnumFormatEtc(DWORD dwDirection, LPENUMFORMATETC FAR* ppenumFormatEtc)"); +#endif + + if (!data) + return ResultFromScode(DATA_E_FORMATETC); + + SCODE sc = S_OK; + + QVector fmtetcs; + if (dwDirection == DATADIR_GET) { + fmtetcs = QWindowsMime::allFormatsForMime(data); + } else { + FORMATETC formatetc; + formatetc.cfFormat = CF_PERFORMEDDROPEFFECT; + formatetc.dwAspect = DVASPECT_CONTENT; + formatetc.lindex = -1; + formatetc.ptd = NULL; + formatetc.tymed = TYMED_HGLOBAL; + fmtetcs.append(formatetc); + } + + QOleEnumFmtEtc *enumFmtEtc = new QOleEnumFmtEtc(fmtetcs); + *ppenumFormatEtc = enumFmtEtc; + if (enumFmtEtc->isNull()) { + delete enumFmtEtc; + *ppenumFormatEtc = NULL; + sc = E_OUTOFMEMORY; + } + + return ResultFromScode(sc); +} + +STDMETHODIMP +QOleDataObject::DAdvise(FORMATETC FAR*, DWORD, + LPADVISESINK, DWORD FAR*) +{ + return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); +} + + +STDMETHODIMP +QOleDataObject::DUnadvise(DWORD) +{ + return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); +} + +STDMETHODIMP +QOleDataObject::EnumDAdvise(LPENUMSTATDATA FAR*) +{ + return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); +} + +#endif // QT_NO_DRAGANDDROP && QT_NO_CLIPBOARD + +#ifndef QT_NO_DRAGANDDROP + +//#define QDND_DEBUG + +#ifdef QDND_DEBUG +extern QString dragActionsToString(Qt::DropActions actions); +#endif + +Qt::DropActions translateToQDragDropActions(DWORD pdwEffects) +{ + Qt::DropActions actions = Qt::IgnoreAction; + if (pdwEffects & DROPEFFECT_LINK) + actions |= Qt::LinkAction; + if (pdwEffects & DROPEFFECT_COPY) + actions |= Qt::CopyAction; + if (pdwEffects & DROPEFFECT_MOVE) + actions |= Qt::MoveAction; + return actions; +} + +Qt::DropAction translateToQDragDropAction(DWORD pdwEffect) +{ + if (pdwEffect & DROPEFFECT_LINK) + return Qt::LinkAction; + if (pdwEffect & DROPEFFECT_COPY) + return Qt::CopyAction; + if (pdwEffect & DROPEFFECT_MOVE) + return Qt::MoveAction; + return Qt::IgnoreAction; +} + +DWORD translateToWinDragEffects(Qt::DropActions action) +{ + DWORD effect = DROPEFFECT_NONE; + if (action & Qt::LinkAction) + effect |= DROPEFFECT_LINK; + if (action & Qt::CopyAction) + effect |= DROPEFFECT_COPY; + if (action & Qt::MoveAction) + effect |= DROPEFFECT_MOVE; + return effect; +} + +Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState) +{ + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + + if (keyState & MK_SHIFT) + modifiers |= Qt::ShiftModifier; + if (keyState & MK_CONTROL) + modifiers |= Qt::ControlModifier; + if (keyState & MK_ALT) + modifiers |= Qt::AltModifier; + + return modifiers; +} + +Qt::MouseButtons toQtMouseButtons(DWORD keyState) +{ + Qt::MouseButtons buttons = Qt::NoButton; + + if (keyState & MK_LBUTTON) + buttons |= Qt::LeftButton; + if (keyState & MK_RBUTTON) + buttons |= Qt::RightButton; + if (keyState & MK_MBUTTON) + buttons |= Qt::MidButton; + + return buttons; +} + +class QOleDropSource : public IDropSource +{ +public: + QOleDropSource(); + virtual ~QOleDropSource(); + + void createCursors(); + + // IUnknown methods + STDMETHOD(QueryInterface)(REFIID riid, void ** ppvObj); + STDMETHOD_(ULONG,AddRef)(void); + STDMETHOD_(ULONG,Release)(void); + + // IDropSource methods + STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState); + STDMETHOD(GiveFeedback)(DWORD dwEffect); + +private: + Qt::MouseButtons currentButtons; + Qt::DropAction currentAction; + QMap cursors; + + ULONG m_refs; +}; + + +QOleDropSource::QOleDropSource() +{ + currentButtons = Qt::NoButton; + m_refs = 1; + currentAction = Qt::IgnoreAction; +} + +QOleDropSource::~QOleDropSource() +{ +} + +void QOleDropSource::createCursors() +{ + QDragManager *manager = QDragManager::self(); + if (manager && manager->object + && (!manager->object->pixmap().isNull() + || manager->hasCustomDragCursors())) { + QPixmap pm = manager->object->pixmap(); + QList actions; + actions << Qt::MoveAction << Qt::CopyAction << Qt::LinkAction; + if (!manager->object->pixmap().isNull()) + actions << Qt::IgnoreAction; + QPoint hotSpot = manager->object->hotSpot(); + for (int cnum = 0; cnum < actions.size(); ++cnum) { + QPixmap cpm = manager->dragCursor(actions.at(cnum)); + int w = cpm.width(); + int h = cpm.height(); + + if (!pm.isNull()) { + int x1 = qMin(-hotSpot.x(), 0); + int x2 = qMax(pm.width() - hotSpot.x(), cpm.width()); + int y1 = qMin(-hotSpot.y(), 0); + int y2 = qMax(pm.height() - hotSpot.y(), cpm.height()); + + w = x2 - x1 + 1; + h = y2 - y1 + 1; + } + + QRect srcRect = pm.rect(); + QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y())); + QPoint newHotSpot = hotSpot; + +#if defined(Q_OS_WINCE) + // Limited cursor size + int reqw = GetSystemMetrics(SM_CXCURSOR); + int reqh = GetSystemMetrics(SM_CYCURSOR); + + QPoint hotspotInPM = newHotSpot - pmDest; + if (reqw < w) { + // Not wide enough - move objectpm right + qreal r = qreal(newHotSpot.x()) / w; + newHotSpot = QPoint(int(r * reqw), newHotSpot.y()); + if (newHotSpot.x() + cpm.width() > reqw) + newHotSpot.setX(reqw - cpm.width()); + + srcRect = QRect(QPoint(hotspotInPM.x() - newHotSpot.x(), srcRect.top()), QSize(reqw, srcRect.height())); + } + if (reqh < h) { + qreal r = qreal(newHotSpot.y()) / h; + newHotSpot = QPoint(newHotSpot.x(), int(r * reqh)); + if (newHotSpot.y() + cpm.height() > reqh) + newHotSpot.setY(reqh - cpm.height()); + + srcRect = QRect(QPoint(srcRect.left(), hotspotInPM.y() - newHotSpot.y()), QSize(srcRect.width(), reqh)); + } + // Always use system cursor size + w = reqw; + h = reqh; +#endif + + QPixmap newCursor(w, h); + if (!pm.isNull()) { + newCursor.fill(QColor(0, 0, 0, 0)); + QPainter p(&newCursor); + p.drawPixmap(pmDest, pm, srcRect); + p.drawPixmap(qMax(0,newHotSpot.x()),qMax(0,newHotSpot.y()),cpm); + } else { + newCursor = cpm; + } + +#ifndef QT_NO_CURSOR + cursors[actions.at(cnum)] = QCursor(newCursor, pm.isNull() ? 0 : qMax(0,newHotSpot.x()), + pm.isNull() ? 0 : qMax(0,newHotSpot.y())); +#endif + } + } +} + + + +//--------------------------------------------------------------------- +// IUnknown Methods +//--------------------------------------------------------------------- + + +STDMETHODIMP +QOleDropSource::QueryInterface(REFIID iid, void FAR* FAR* ppv) +{ + if(iid == IID_IUnknown || iid == IID_IDropSource) + { + *ppv = this; + ++m_refs; + return NOERROR; + } + *ppv = NULL; + return ResultFromScode(E_NOINTERFACE); +} + + +STDMETHODIMP_(ULONG) +QOleDropSource::AddRef(void) +{ + return ++m_refs; +} + + +STDMETHODIMP_(ULONG) +QOleDropSource::Release(void) +{ + if(--m_refs == 0) + { + delete this; + return 0; + } + return m_refs; +} + +static inline Qt::MouseButtons keystate_to_mousebutton(DWORD grfKeyState) +{ + Qt::MouseButtons result; + if (grfKeyState & MK_LBUTTON) + result |= Qt::LeftButton; + if (grfKeyState & MK_MBUTTON) + result |= Qt::MidButton; + if (grfKeyState & MK_RBUTTON) + result |= Qt::RightButton; + if (grfKeyState & MK_XBUTTON1) + result |= Qt::XButton1; + if (grfKeyState & MK_XBUTTON2) + result |= Qt::XButton2; + return result; +} + +//--------------------------------------------------------------------- +// IDropSource Methods +//--------------------------------------------------------------------- +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) +{ +#ifdef QDND_DEBUG + qDebug("QOleDropSource::QueryContinueDrag(fEscapePressed %d, grfKeyState %d)", fEscapePressed, grfKeyState); +#endif + + if (fEscapePressed) { + return ResultFromScode(DRAGDROP_S_CANCEL); + } else if ((GetAsyncKeyState(VK_LBUTTON) == 0) + && (GetAsyncKeyState(VK_MBUTTON) == 0) + && (GetAsyncKeyState(VK_RBUTTON) == 0)) { + // grfKeyState is broken on CE & some Windows XP versions, + // therefore we need to check the state manually + return ResultFromScode(DRAGDROP_S_DROP); + } else { +#if !defined(Q_OS_WINCE) + if (currentButtons == Qt::NoButton) { + currentButtons = keystate_to_mousebutton(grfKeyState); + } else { + Qt::MouseButtons buttons = keystate_to_mousebutton(grfKeyState); + if (!(currentButtons & buttons)) + return ResultFromScode(DRAGDROP_S_DROP); + } +#endif + QApplication::processEvents(); + return NOERROR; + } +} + +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropSource::GiveFeedback(DWORD dwEffect) +{ + Qt::DropAction action = translateToQDragDropAction(dwEffect); + +#ifdef QDND_DEBUG + qDebug("QOleDropSource::GiveFeedback(DWORD dwEffect)"); + qDebug("dwEffect = %s", dragActionsToString(action).toLatin1().data()); +#endif + + if (currentAction != action) { + currentAction = action; + QDragManager::self()->emitActionChanged(currentAction); + } + + if (cursors.contains(currentAction)) { +#ifndef QT_NO_CURSOR + SetCursor(cursors[currentAction].handle()); +#endif + return ResultFromScode(S_OK); + } + + return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS); +} + +//--------------------------------------------------------------------- +// QOleDropTarget +//--------------------------------------------------------------------- + +QOleDropTarget::QOleDropTarget(QWidget* w) +: widget(w) +{ + m_refs = 1; +} + +void QOleDropTarget::releaseQt() +{ + widget = 0; +} + +//--------------------------------------------------------------------- +// IUnknown Methods +//--------------------------------------------------------------------- + + +STDMETHODIMP +QOleDropTarget::QueryInterface(REFIID iid, void FAR* FAR* ppv) +{ + if(iid == IID_IUnknown || iid == IID_IDropTarget) + { + *ppv = this; + AddRef(); + return NOERROR; + } + *ppv = NULL; + return ResultFromScode(E_NOINTERFACE); +} + + +STDMETHODIMP_(ULONG) +QOleDropTarget::AddRef(void) +{ + return ++m_refs; +} + + +STDMETHODIMP_(ULONG) +QOleDropTarget::Release(void) +{ + if(--m_refs == 0) + { + delete this; + return 0; + } + return m_refs; +} + +//--------------------------------------------------------------------- +// IDropTarget Methods +//--------------------------------------------------------------------- + +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) +{ +#ifdef QDND_DEBUG + qDebug("QOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)"); +#endif + + if (!QApplicationPrivate::tryModalHelper(widget)) { + *pdwEffect = DROPEFFECT_NONE; + return NOERROR; + } + + QDragManager *manager = QDragManager::self(); + manager->dropData->currentDataObject = pDataObj; + manager->dropData->currentDataObject->AddRef(); + sendDragEnterEvent(widget, grfKeyState, pt, pdwEffect); + *pdwEffect = chosenEffect; + + return NOERROR; +} + +void QOleDropTarget::sendDragEnterEvent(QWidget *dragEnterWidget, DWORD grfKeyState, + POINTL pt, LPDWORD pdwEffect) +{ + Q_ASSERT(dragEnterWidget); + lastPoint = dragEnterWidget->mapFromGlobal(QPoint(pt.x,pt.y)); + lastKeyState = grfKeyState; + + chosenEffect = DROPEFFECT_NONE; + currentWidget = dragEnterWidget; + + QDragManager *manager = QDragManager::self(); + QMimeData * md = manager->source() ? manager->dragPrivate()->data : manager->dropData; + QDragEnterEvent enterEvent(lastPoint, translateToQDragDropActions(*pdwEffect), md, + toQtMouseButtons(grfKeyState), toQtKeyboardModifiers(grfKeyState)); + QApplication::sendEvent(dragEnterWidget, &enterEvent); + answerRect = enterEvent.answerRect(); + + if (enterEvent.isAccepted()) { + chosenEffect = translateToWinDragEffects(enterEvent.dropAction()); + } + + // Documentation states that a drag move event is sendt immidiatly after + // a drag enter event. This will honor widgets overriding dragMoveEvent only: + if (enterEvent.isAccepted()) { + QDragMoveEvent moveEvent(lastPoint, translateToQDragDropActions(*pdwEffect), md, + toQtMouseButtons(grfKeyState), toQtKeyboardModifiers(grfKeyState)); + answerRect = enterEvent.answerRect(); + moveEvent.setDropAction(enterEvent.dropAction()); + moveEvent.accept(); // accept by default, since enter event was accepted. + + QApplication::sendEvent(dragEnterWidget, &moveEvent); + if (moveEvent.isAccepted()) { + answerRect = moveEvent.answerRect(); + chosenEffect = translateToWinDragEffects(moveEvent.dropAction()); + } else { + chosenEffect = DROPEFFECT_NONE; + } + } + +} + +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) +{ +#ifdef QDND_DEBUG + qDebug("QOleDropTarget::DragOver(grfKeyState %d, pt (%d,%d), pdwEffect %d)", grfKeyState, pt.x, pt.y, pdwEffect); +#endif + + QWidget *dragOverWidget = widget->childAt(widget->mapFromGlobal(QPoint(pt.x, pt.y))); + if (!dragOverWidget) + dragOverWidget = widget; + + + if (!QApplicationPrivate::tryModalHelper(dragOverWidget) + || !dragOverWidget->testAttribute(Qt::WA_DropSiteRegistered)) { + *pdwEffect = DROPEFFECT_NONE; + return NOERROR; + } + + QPoint tmpPoint = dragOverWidget->mapFromGlobal(QPoint(pt.x, pt.y)); + // see if we should compress this event + if ((tmpPoint == lastPoint || answerRect.contains(tmpPoint)) && lastKeyState == grfKeyState) { + *pdwEffect = chosenEffect; + return NOERROR; + } + + if (!dragOverWidget->internalWinId() && dragOverWidget != currentWidget) { + QPointer dragOverWidgetGuard(dragOverWidget); + // Send drag leave event to the previous drag widget. + QDragLeaveEvent dragLeave; + if (currentWidget) + QApplication::sendEvent(currentWidget, &dragLeave); + if (!dragOverWidgetGuard) { + dragOverWidget = widget->childAt(widget->mapFromGlobal(QPoint(pt.x, pt.y))); + if (!dragOverWidget) + dragOverWidget = widget; + } + // Send drag enter event to the current drag widget. + sendDragEnterEvent(dragOverWidget, grfKeyState, pt, pdwEffect); + } + + QDragManager *manager = QDragManager::self(); + QMimeData *md = manager->source() ? manager->dragPrivate()->data : manager->dropData; + + QDragMoveEvent oldEvent(lastPoint, translateToQDragDropActions(*pdwEffect), md, + toQtMouseButtons(lastKeyState), toQtKeyboardModifiers(lastKeyState)); + + + lastPoint = tmpPoint; + lastKeyState = grfKeyState; + + QDragMoveEvent e(lastPoint, translateToQDragDropActions(*pdwEffect), md, + toQtMouseButtons(grfKeyState), toQtKeyboardModifiers(grfKeyState)); + if (chosenEffect != DROPEFFECT_NONE) { + if (oldEvent.dropAction() == e.dropAction() && + oldEvent.keyboardModifiers() == e.keyboardModifiers()) + e.setDropAction(translateToQDragDropAction(chosenEffect)); + e.accept(); + } + QApplication::sendEvent(dragOverWidget, &e); + + answerRect = e.answerRect(); + if (e.isAccepted()) + chosenEffect = translateToWinDragEffects(e.dropAction()); + else + chosenEffect = DROPEFFECT_NONE; + *pdwEffect = chosenEffect; + + return NOERROR; +} + +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropTarget::DragLeave() +{ +#ifdef QDND_DEBUG + qDebug("QOleDropTarget::DragLeave()"); +#endif + + if (!QApplicationPrivate::tryModalHelper(widget)) { + return NOERROR; + } + + currentWidget = 0; + QDragLeaveEvent e; + QApplication::sendEvent(widget, &e); + + QDragManager *manager = QDragManager::self(); + + if (manager->dropData->currentDataObject) { // Sanity + manager->dropData->currentDataObject->Release(); + manager->dropData->currentDataObject = 0; + } + + return NOERROR; +} + +#define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) + +QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP +QOleDropTarget::Drop(LPDATAOBJECT /*pDataObj*/, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect) +{ +#ifdef QDND_DEBUG + qDebug("QOleDropTarget::Drop(LPDATAOBJECT /*pDataObj*/, grfKeyState %d, POINTL pt, LPDWORD pdwEffect)", grfKeyState); +#endif + + QWidget *dropWidget = widget->childAt(widget->mapFromGlobal(QPoint(pt.x, pt.y))); + if (!dropWidget) + dropWidget = widget; + + if (!QApplicationPrivate::tryModalHelper(dropWidget) + || !dropWidget->testAttribute(Qt::WA_DropSiteRegistered)) { + *pdwEffect = DROPEFFECT_NONE; + return NOERROR; + } + + lastPoint = dropWidget->mapFromGlobal(QPoint(pt.x,pt.y)); + // grfKeyState does not all ways contain button state in the drop so if + // it doesn't then use the last known button state; + if ((grfKeyState & KEY_STATE_BUTTON_MASK) == 0) + grfKeyState |= lastKeyState & KEY_STATE_BUTTON_MASK; + lastKeyState = grfKeyState; + + QDragManager *manager = QDragManager::self(); + QMimeData *md = manager->source() ? manager->dragPrivate()->data : manager->dropData; + QDropEvent e(lastPoint, translateToQDragDropActions(*pdwEffect), md, + toQtMouseButtons(grfKeyState), toQtKeyboardModifiers(grfKeyState)); + if (chosenEffect != DROPEFFECT_NONE) { + e.setDropAction(translateToQDragDropAction(chosenEffect)); + } + QApplication::sendEvent(dropWidget, &e); + + if (chosenEffect != DROPEFFECT_NONE) { + e.accept(); + } + + + if (e.isAccepted()) { + if (e.dropAction() == Qt::MoveAction || e.dropAction() == Qt::TargetMoveAction) { + if (e.dropAction() == Qt::MoveAction) + chosenEffect = DROPEFFECT_MOVE; + else + chosenEffect = DROPEFFECT_COPY; + HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD)); + if (hData) { + DWORD *moveEffect = (DWORD *)GlobalLock(hData);; + *moveEffect = DROPEFFECT_MOVE; + GlobalUnlock(hData); + STGMEDIUM medium; + memset(&medium, 0, sizeof(STGMEDIUM)); + medium.tymed = TYMED_HGLOBAL; + medium.hGlobal = hData; + FORMATETC format; + format.cfFormat = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT); + format.tymed = TYMED_HGLOBAL; + format.ptd = 0; + format.dwAspect = 1; + format.lindex = -1; + manager->dropData->currentDataObject->SetData(&format, &medium, true); + } + } else { + chosenEffect = translateToWinDragEffects(e.dropAction()); + } + } else { + chosenEffect = DROPEFFECT_NONE; + } + *pdwEffect = chosenEffect; + + + if (manager->dropData->currentDataObject) { + manager->dropData->currentDataObject->Release(); + manager->dropData->currentDataObject = 0; + } + + return NOERROR; + + // We won't get any mouserelease-event, so manually adjust qApp state: +///### test this QApplication::winMouseButtonUp(); +} + +//--------------------------------------------------------------------- +// QDropData +//--------------------------------------------------------------------- + +bool QDropData::hasFormat_sys(const QString &mimeType) const +{ + if (!currentDataObject) // Sanity + return false; + + return QWindowsMime::converterToMime(mimeType, currentDataObject) != 0; +} + +QStringList QDropData::formats_sys() const +{ + QStringList fmts; + if (!currentDataObject) // Sanity + return fmts; + + fmts = QWindowsMime::allMimesForFormats(currentDataObject); + + return fmts; +} + +QVariant QDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const +{ + QVariant result; + + if (!currentDataObject) // Sanity + return result; + + QWindowsMime *converter = QWindowsMime::converterToMime(mimeType, currentDataObject); + + if (converter) + result = converter->convertToMime(mimeType, currentDataObject, type); + + return result; +} + +Qt::DropAction QDragManager::drag(QDrag *o) + +{ +#ifdef QDND_DEBUG + qDebug("QDragManager::drag(QDrag *drag)"); +#endif + + if (object == o || !o || !o->d_func()->source) + return Qt::IgnoreAction; + + if (object) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + } + + object = o; + +#ifdef QDND_DEBUG + qDebug("actions = %s", dragActionsToString(dragPrivate()->possible_actions).toLatin1().data()); +#endif + + dragPrivate()->target = 0; + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropStart); +#endif + + DWORD resultEffect; + QOleDropSource *src = new QOleDropSource(); + src->createCursors(); + QOleDataObject *obj = new QOleDataObject(o->mimeData()); + DWORD allowedEffects = translateToWinDragEffects(dragPrivate()->possible_actions); + +#if !defined(Q_OS_WINCE) || defined(GWES_ICONCURS) + HRESULT r = DoDragDrop(obj, src, allowedEffects, &resultEffect); +#else + HRESULT r = DRAGDROP_S_CANCEL; + resultEffect = DROPEFFECT_MOVE; +#endif + + Qt::DropAction ret = Qt::IgnoreAction; + if (r == DRAGDROP_S_DROP) { + if (obj->reportedPerformedEffect() == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) { + ret = Qt::TargetMoveAction; + resultEffect = DROPEFFECT_MOVE; + } else { + ret = translateToQDragDropAction(resultEffect); + } + // Force it to be a copy if an unsupported operation occurred. + // This indicates a bug in the drop target. + if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) + ret = Qt::CopyAction; + } else { + dragPrivate()->target = 0; + } + + // clean up + obj->releaseQt(); + obj->Release(); // Will delete obj if refcount becomes 0 + src->Release(); // Will delete src if refcount becomes 0 + object = 0; + o->setMimeData(0); + o->deleteLater(); + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd); +#endif + + return ret; +} + +void QDragManager::cancel(bool /* deleteSource */) +{ + if (object) { + beingCancelled = true; + object = 0; + } + +#ifndef QT_NO_CURSOR + // insert cancel code here ######## todo + + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::DragDropEnd); +#endif +} + +void QDragManager::updatePixmap() +{ + // not used in windows implementation +} + +bool QDragManager::eventFilter(QObject *, QEvent *) +{ + // not used in windows implementation + return false; +} + +void QDragManager::timerEvent(QTimerEvent*) +{ + // not used in windows implementation +} + +void QDragManager::move(const QPoint &) +{ + // not used in windows implementation +} + +void QDragManager::drop() +{ + // not used in windows implementation +} + +QT_END_NAMESPACE + +#endif // QT_NO_DRAGANDDROP diff --git a/src/gui/kernel/qdnd_x11.cpp b/src/gui/kernel/qdnd_x11.cpp new file mode 100644 index 0000000000..1c59d41a94 --- /dev/null +++ b/src/gui/kernel/qdnd_x11.cpp @@ -0,0 +1,2076 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "qapplication.h" + +#ifndef QT_NO_DRAGANDDROP + +#include "qwidget.h" +#include "qpainter.h" +#include "qpixmap.h" +#include "qbitmap.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qiodevice.h" +#include "qpointer.h" +#include "qcursor.h" +#include "qelapsedtimer.h" +#include "qvariant.h" +#include "qvector.h" +#include "qurl.h" +#include "qdebug.h" +#include "qimagewriter.h" +#include "qbuffer.h" +#include "qtextcodec.h" + +#include "qdnd_p.h" +#include "qapplication_p.h" +#include "qt_x11_p.h" +#include "qx11info_x11.h" + +#include "qwidget_p.h" +#include "qcursor_p.h" + +QT_BEGIN_NAMESPACE + +// #define DND_DEBUG +#ifdef DND_DEBUG +#define DEBUG qDebug +#else +#define DEBUG if(0) qDebug +#endif + +#ifdef DND_DEBUG +#define DNDDEBUG qDebug() +#else +#define DNDDEBUG if(0) qDebug() +#endif + +static int findXdndDropTransactionByWindow(Window window) +{ + int at = -1; + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.target == window || t.proxy_target == window) { + at = i; + break; + } + } + return at; +} + +static int findXdndDropTransactionByTime(Time timestamp) +{ + int at = -1; + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.timestamp == timestamp) { + at = i; + break; + } + } + return at; +} + +// timer used to discard old XdndDrop transactions +static int transaction_expiry_timer = -1; +enum { XdndDropTransactionTimeout = 5000 }; // 5 seconds + +static void restartXdndDropExpiryTimer() +{ + if (transaction_expiry_timer != -1) + QDragManager::self()->killTimer(transaction_expiry_timer); + transaction_expiry_timer = QDragManager::self()->startTimer(XdndDropTransactionTimeout); +} + + +// find an ancestor with XdndAware on it +static Window findXdndAwareParent(Window window) +{ + Window target = 0; + forever { + // check if window has XdndAware + Atom type = 0; + int f; + unsigned long n, a; + unsigned char *data = 0; + if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data) == Success) { + if (data) + XFree(data); + if (type) { + target = window; + break; + } + } + + // try window's parent + Window root; + Window parent; + Window *children; + uint unused; + if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused)) + break; + if (children) + XFree(children); + if (window == root) + break; + window = parent; + } + return target; +} + + + + +// and all this stuff is copied -into- qapp_x11.cpp + +static void handle_xdnd_position(QWidget *, const XEvent *, bool); +static void handle_xdnd_status(QWidget * w, const XEvent * xe, bool /*passive*/); + +const int xdnd_version = 5; + +static Qt::DropAction xdndaction_to_qtaction(Atom atom) +{ + if (atom == ATOM(XdndActionCopy) || atom == 0) + return Qt::CopyAction; + if (atom == ATOM(XdndActionLink)) + return Qt::LinkAction; + if (atom == ATOM(XdndActionMove)) + return Qt::MoveAction; + return Qt::CopyAction; +} + +static int qtaction_to_xdndaction(Qt::DropAction a) +{ + switch (a) { + case Qt::CopyAction: + return ATOM(XdndActionCopy); + case Qt::LinkAction: + return ATOM(XdndActionLink); + case Qt::MoveAction: + case Qt::TargetMoveAction: + return ATOM(XdndActionMove); + case Qt::IgnoreAction: + return XNone; + default: + return ATOM(XdndActionCopy); + } +} + +// clean up the stuff used. +static void qt_xdnd_cleanup(); + +static void qt_xdnd_send_leave(); + +// real variables: +// xid of current drag source +static Atom qt_xdnd_dragsource_xid = 0; + +// the types in this drop. 100 is no good, but at least it's big. +const int qt_xdnd_max_type = 100; +static Atom qt_xdnd_types[qt_xdnd_max_type + 1]; + +// timer used when target wants "continuous" move messages (eg. scroll) +static int heartbeat = -1; +// rectangle in which the answer will be the same +static QRect qt_xdnd_source_sameanswer; +// top-level window we sent position to last. +static Window qt_xdnd_current_target; +// window to send events to (always valid if qt_xdnd_current_target) +static Window qt_xdnd_current_proxy_target; +static Time qt_xdnd_source_current_time; + +// widget we forwarded position to last, and local position +static QPointer qt_xdnd_current_widget; +static QPoint qt_xdnd_current_position; +// timestamp from the XdndPosition and XdndDrop +static Time qt_xdnd_target_current_time; +// screen number containing the pointer... -1 means default +static int qt_xdnd_current_screen = -1; +// state of dragging... true if dragging, false if not +bool qt_xdnd_dragging = false; + +static bool waiting_for_status = false; + +// used to preset each new QDragMoveEvent +static Qt::DropAction last_target_accepted_action = Qt::IgnoreAction; + +// Shift/Ctrl handling, and final drop status +static Qt::DropAction global_accepted_action = Qt::CopyAction; +static Qt::DropActions possible_actions = Qt::IgnoreAction; + +// for embedding only +static QWidget* current_embedding_widget = 0; +static XEvent last_enter_event; + +// cursors +static QCursor *noDropCursor = 0; +static QCursor *moveCursor = 0; +static QCursor *copyCursor = 0; +static QCursor *linkCursor = 0; + +static QPixmap *defaultPm = 0; + +static const int default_pm_hotx = -2; +static const int default_pm_hoty = -16; +static const char* const default_pm[] = { +"13 9 3 1", +". c None", +" c #000000", +"X c #FFFFFF", +"X X X X X X X", +" X X X X X X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X.........X ", +"X ......... X", +" X X X X X X ", +"X X X X X X X" +}; + +class QShapedPixmapWidget : public QWidget +{ + Q_OBJECT +public: + QShapedPixmapWidget(QWidget* w) : + QWidget(w, + Qt::Tool | Qt::FramelessWindowHint + | Qt::X11BypassWindowManagerHint + | Qt::BypassGraphicsProxyWidget) + { + setAttribute(Qt::WA_X11NetWmWindowTypeDND); + } + + void setPixmap(const QPixmap &pm) + { + QBitmap mask = pm.mask(); + if (!mask.isNull()) { + setMask(mask); + } else { + clearMask(); + } + resize(pm.width(),pm.height()); + pixmap = pm; + update(); + } + QPoint pm_hot; + +protected: + QPixmap pixmap; + void paintEvent(QPaintEvent*) + { + QPainter p(this); + p.drawPixmap(0, 0, pixmap); + } +}; + +#include "qdnd_x11.moc" + +struct XdndData { + QShapedPixmapWidget *deco; + QWidget* desktop_proxy; +}; + +static XdndData xdnd_data = { 0, 0 }; + +class QExtraWidget : public QWidget +{ + Q_DECLARE_PRIVATE(QWidget) +public: + inline QWExtra* extraData(); + inline QTLWExtra* topData(); +}; + +inline QWExtra* QExtraWidget::extraData() { return d_func()->extraData(); } +inline QTLWExtra* QExtraWidget::topData() { return d_func()->topData(); } + + +static WId xdndProxy(WId w) +{ + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *retval = 0; + XGetWindowProperty(X11->display, w, ATOM(XdndProxy), 0, 1, False, + XA_WINDOW, &type, &f,&n,&a,&retval); + WId *proxy_id_ptr = (WId *)retval; + WId proxy_id = 0; + if (type == XA_WINDOW && proxy_id_ptr) { + proxy_id = *proxy_id_ptr; + XFree(proxy_id_ptr); + proxy_id_ptr = 0; + // Already exists. Real? + X11->ignoreBadwindow(); + XGetWindowProperty(X11->display, proxy_id, ATOM(XdndProxy), 0, 1, False, + XA_WINDOW, &type, &f,&n,&a,&retval); + proxy_id_ptr = (WId *)retval; + if (X11->badwindow() || type != XA_WINDOW || !proxy_id_ptr || *proxy_id_ptr != proxy_id) + // Bogus - we will overwrite. + proxy_id = 0; + } + if (proxy_id_ptr) + XFree(proxy_id_ptr); + return proxy_id; +} + +static bool xdndEnable(QWidget* w, bool on) +{ + DNDDEBUG << "xdndEnable" << w << on; + if (on) { + QWidget * xdnd_widget = 0; + if ((w->windowType() == Qt::Desktop)) { + if (xdnd_data.desktop_proxy) // *WE* already have one. + return false; + + // As per Xdnd4, use XdndProxy + XGrabServer(X11->display); + Q_ASSERT(w->testAttribute(Qt::WA_WState_Created)); + WId proxy_id = xdndProxy(w->effectiveWinId()); + + if (!proxy_id) { + xdnd_widget = xdnd_data.desktop_proxy = new QWidget; + proxy_id = xdnd_data.desktop_proxy->effectiveWinId(); + XChangeProperty (X11->display, w->effectiveWinId(), ATOM(XdndProxy), + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1); + XChangeProperty (X11->display, proxy_id, ATOM(XdndProxy), + XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1); + } + + XUngrabServer(X11->display); + } else { + xdnd_widget = w->window(); + } + if (xdnd_widget) { + DNDDEBUG << "setting XdndAware for" << xdnd_widget << xdnd_widget->effectiveWinId(); + Atom atm = (Atom)xdnd_version; + Q_ASSERT(xdnd_widget->testAttribute(Qt::WA_WState_Created)); + XChangeProperty(X11->display, xdnd_widget->effectiveWinId(), ATOM(XdndAware), + XA_ATOM, 32, PropModeReplace, (unsigned char *)&atm, 1); + return true; + } else { + return false; + } + } else { + if ((w->windowType() == Qt::Desktop)) { + XDeleteProperty(X11->display, w->internalWinId(), ATOM(XdndProxy)); + delete xdnd_data.desktop_proxy; + xdnd_data.desktop_proxy = 0; + } else { + DNDDEBUG << "not deleting XDndAware"; + } + return true; + } +} + +QByteArray QX11Data::xdndAtomToString(Atom a) +{ + if (!a) return 0; + + if (a == XA_STRING || a == ATOM(UTF8_STRING)) { + return "text/plain"; // some Xdnd clients are dumb + } + char *atom = XGetAtomName(display, a); + QByteArray result = atom; + XFree(atom); + return result; +} + +Atom QX11Data::xdndStringToAtom(const char *mimeType) +{ + if (!mimeType || !*mimeType) + return 0; + return XInternAtom(display, mimeType, False); +} + +//$$$ +QString QX11Data::xdndMimeAtomToString(Atom a) +{ + QString atomName; + if (a) { + char *atom = XGetAtomName(display, a); + atomName = QString::fromLatin1(atom); + XFree(atom); + } + return atomName; +} + +//$$$ +Atom QX11Data::xdndMimeStringToAtom(const QString &mimeType) +{ + if (mimeType.isEmpty()) + return 0; + return XInternAtom(display, mimeType.toLatin1().constData(), False); +} + +//$$$ replace ccxdndAtomToString() +QStringList QX11Data::xdndMimeFormatsForAtom(Atom a) +{ + QStringList formats; + if (a) { + QString atomName = xdndMimeAtomToString(a); + formats.append(atomName); + + // special cases for string type + if (a == ATOM(UTF8_STRING) || a == XA_STRING + || a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + formats.append(QLatin1String("text/plain")); + + // special cases for uris + if (atomName == QLatin1String("text/x-moz-url")) + formats.append(QLatin1String("text/uri-list")); + + // special case for images + if (a == XA_PIXMAP) + formats.append(QLatin1String("image/ppm")); + } + return formats; +} + +//$$$ +bool QX11Data::xdndMimeDataForAtom(Atom a, QMimeData *mimeData, QByteArray *data, Atom *atomFormat, int *dataFormat) +{ + bool ret = false; + *atomFormat = a; + *dataFormat = 8; + QString atomName = xdndMimeAtomToString(a); + if (QInternalMimeData::hasFormatHelper(atomName, mimeData)) { + *data = QInternalMimeData::renderDataHelper(atomName, mimeData); + if (atomName == QLatin1String("application/x-color")) + *dataFormat = 16; + ret = true; + } else { + if ((a == ATOM(UTF8_STRING) || a == XA_STRING + || a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + && QInternalMimeData::hasFormatHelper(QLatin1String("text/plain"), mimeData)) { + if (a == ATOM(UTF8_STRING)){ + *data = QInternalMimeData::renderDataHelper(QLatin1String("text/plain"), mimeData); + ret = true; + } else if (a == XA_STRING) { + *data = QString::fromUtf8(QInternalMimeData::renderDataHelper( + QLatin1String("text/plain"), mimeData)).toLocal8Bit(); + ret = true; + } else if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) { + // the ICCCM states that TEXT and COMPOUND_TEXT are in the + // encoding of choice, so we choose the encoding of the locale + QByteArray strData = QString::fromUtf8(QInternalMimeData::renderDataHelper( + QLatin1String("text/plain"), mimeData)).toLocal8Bit(); + char *list[] = { strData.data(), NULL }; + + XICCEncodingStyle style = (a == ATOM(COMPOUND_TEXT)) + ? XCompoundTextStyle : XStdICCTextStyle; + XTextProperty textprop; + if (list[0] != NULL + && XmbTextListToTextProperty(X11->display, list, 1, style, + &textprop) == Success) { + *atomFormat = textprop.encoding; + *dataFormat = textprop.format; + *data = QByteArray((const char *) textprop.value, textprop.nitems * textprop.format / 8); + ret = true; + + DEBUG(" textprop type %lx\n" + " textprop name '%s'\n" + " format %d\n" + " %ld items\n" + " %d bytes\n", + textprop.encoding, + X11->xdndMimeAtomToString(textprop.encoding).toLatin1().data(), + textprop.format, textprop.nitems, data->size()); + + XFree(textprop.value); + } + } + } else if (atomName == QLatin1String("text/x-moz-url") && + QInternalMimeData::hasFormatHelper(QLatin1String("text/uri-list"), mimeData)) { + QByteArray uri = QInternalMimeData::renderDataHelper( + QLatin1String("text/uri-list"), mimeData).split('\n').first(); + QString mozUri = QString::fromLatin1(uri, uri.size()); + mozUri += QLatin1Char('\n'); + *data = QByteArray(reinterpret_cast(mozUri.utf16()), mozUri.length() * 2); + ret = true; + } else if ((a == XA_PIXMAP || a == XA_BITMAP) && mimeData->hasImage()) { + QPixmap pm = qvariant_cast(mimeData->imageData()); + if (a == XA_BITMAP && pm.depth() != 1) { + QImage img = pm.toImage(); + img = img.convertToFormat(QImage::Format_MonoLSB); + pm = QPixmap::fromImage(img); + } + QDragManager *dm = QDragManager::self(); + if (dm) { + Pixmap handle = pm.handle(); + *data = QByteArray((const char *) &handle, sizeof(Pixmap)); + dm->xdndMimeTransferedPixmap[dm->xdndMimeTransferedPixmapIndex] = pm; + dm->xdndMimeTransferedPixmapIndex = + (dm->xdndMimeTransferedPixmapIndex + 1) % 2; + ret = true; + } + } else { + DEBUG("QClipboard: xdndMimeDataForAtom(): converting to type '%s' is not supported", qPrintable(atomName)); + } + } + return ret && data != 0; +} + +//$$$ +QList QX11Data::xdndMimeAtomsForFormat(const QString &format) +{ + QList atoms; + atoms.append(xdndMimeStringToAtom(format)); + + // special cases for strings + if (format == QLatin1String("text/plain")) { + atoms.append(ATOM(UTF8_STRING)); + atoms.append(XA_STRING); + atoms.append(ATOM(TEXT)); + atoms.append(ATOM(COMPOUND_TEXT)); + } + + // special cases for uris + if (format == QLatin1String("text/uri-list")) { + atoms.append(xdndMimeStringToAtom(QLatin1String("text/x-moz-url"))); + } + + //special cases for images + if (format == QLatin1String("image/ppm")) + atoms.append(XA_PIXMAP); + if (format == QLatin1String("image/pbm")) + atoms.append(XA_BITMAP); + + return atoms; +} + +//$$$ +QVariant QX11Data::xdndMimeConvertToFormat(Atom a, const QByteArray &data, const QString &format, QVariant::Type requestedType, const QByteArray &encoding) +{ + QString atomName = xdndMimeAtomToString(a); + if (atomName == format) + return data; + + if (!encoding.isEmpty() + && atomName == format + QLatin1String(";charset=") + QString::fromLatin1(encoding)) { + + if (requestedType == QVariant::String) { + QTextCodec *codec = QTextCodec::codecForName(encoding); + if (codec) + return codec->toUnicode(data); + } + + return data; + } + + // special cases for string types + if (format == QLatin1String("text/plain")) { + if (a == ATOM(UTF8_STRING)) + return QString::fromUtf8(data); + if (a == XA_STRING) + return QString::fromLatin1(data); + if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) + // #### might be wrong for COMPUND_TEXT + return QString::fromLocal8Bit(data, data.size()); + } + + // special case for uri types + if (format == QLatin1String("text/uri-list")) { + if (atomName == QLatin1String("text/x-moz-url")) { + // we expect this as utf16 + // the first part is a url that should only contain ascci char + // so it should be safe to check that the second char is 0 + // to verify that it is utf16 + if (data.size() > 1 && data.at(1) == 0) + return QString::fromRawData((const QChar *)data.constData(), + data.size() / 2).split(QLatin1Char('\n')).first().toLatin1(); + } + } + + // special cas for images + if (format == QLatin1String("image/ppm")) { + if (a == XA_PIXMAP && data.size() == sizeof(Pixmap)) { + Pixmap xpm = *((Pixmap*)data.data()); + if (!xpm) + return QByteArray(); + QPixmap qpm = QPixmap::fromX11Pixmap(xpm); + QImageWriter imageWriter; + imageWriter.setFormat("PPMRAW"); + QImage imageToWrite = qpm.toImage(); + QBuffer buf; + buf.open(QIODevice::WriteOnly); + imageWriter.setDevice(&buf); + imageWriter.write(imageToWrite); + return buf.buffer(); + } + } + return QVariant(); +} + +//$$$ middle of xdndObtainData +Atom QX11Data::xdndMimeAtomForFormat(const QString &format, QVariant::Type requestedType, const QList<Atom> &atoms, QByteArray *encoding) +{ + encoding->clear(); + + // find matches for string types + if (format == QLatin1String("text/plain")) { + if (atoms.contains(ATOM(UTF8_STRING))) + return ATOM(UTF8_STRING); + if (atoms.contains(ATOM(COMPOUND_TEXT))) + return ATOM(COMPOUND_TEXT); + if (atoms.contains(ATOM(TEXT))) + return ATOM(TEXT); + if (atoms.contains(XA_STRING)) + return XA_STRING; + } + + // find matches for uri types + if (format == QLatin1String("text/uri-list")) { + Atom a = xdndMimeStringToAtom(format); + if (a && atoms.contains(a)) + return a; + a = xdndMimeStringToAtom(QLatin1String("text/x-moz-url")); + if (a && atoms.contains(a)) + return a; + } + + // find match for image + if (format == QLatin1String("image/ppm")) { + if (atoms.contains(XA_PIXMAP)) + return XA_PIXMAP; + } + + // for string/text requests try to use a format with a well-defined charset + // first to avoid encoding problems + if (requestedType == QVariant::String + && format.startsWith(QLatin1String("text/")) + && !format.contains(QLatin1String("charset="))) { + + QString formatWithCharset = format; + formatWithCharset.append(QLatin1String(";charset=utf-8")); + + Atom a = xdndMimeStringToAtom(formatWithCharset); + if (a && atoms.contains(a)) { + *encoding = "utf-8"; + return a; + } + } + + Atom a = xdndMimeStringToAtom(format); + if (a && atoms.contains(a)) + return a; + + return 0; +} + +void QX11Data::xdndSetup() { + QCursorData::initialize(); + qAddPostRoutine(qt_xdnd_cleanup); +} + + +void qt_xdnd_cleanup() +{ + delete noDropCursor; + noDropCursor = 0; + delete copyCursor; + copyCursor = 0; + delete moveCursor; + moveCursor = 0; + delete linkCursor; + linkCursor = 0; + delete defaultPm; + defaultPm = 0; + delete xdnd_data.desktop_proxy; + xdnd_data.desktop_proxy = 0; + delete xdnd_data.deco; + xdnd_data.deco = 0; +} + + +static QWidget *find_child(QWidget *tlw, QPoint & p) +{ + QWidget *widget = tlw; + + p = widget->mapFromGlobal(p); + bool done = false; + while (!done) { + done = true; + if (((QExtraWidget*)widget)->extraData() && + ((QExtraWidget*)widget)->extraData()->xDndProxy != 0) + break; // stop searching for widgets under the mouse cursor if found widget is a proxy. + QObjectList children = widget->children(); + if (!children.isEmpty()) { + for(int i = children.size(); i > 0;) { + --i; + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (!w) + continue; + if (w->testAttribute(Qt::WA_TransparentForMouseEvents)) + continue; + if (w->isVisible() && + w->geometry().contains(p) && + !w->isWindow()) { + widget = w; + done = false; + p = widget->mapFromParent(p); + break; + } + } + } + } + return widget; +} + + +static bool checkEmbedded(QWidget* w, const XEvent* xe) +{ + if (!w) + return false; + + if (current_embedding_widget != 0 && current_embedding_widget != w) { + qt_xdnd_current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy; + qt_xdnd_current_proxy_target = qt_xdnd_current_target; + qt_xdnd_send_leave(); + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + current_embedding_widget = 0; + } + + QWExtra* extra = ((QExtraWidget*)w)->extraData(); + if (extra && extra->xDndProxy != 0) { + + if (current_embedding_widget != w) { + + last_enter_event.xany.window = extra->xDndProxy; + XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event); + current_embedding_widget = w; + } + + ((XEvent*)xe)->xany.window = extra->xDndProxy; + XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe); + if (qt_xdnd_current_widget != w) { + qt_xdnd_current_widget = w; + } + return true; + } + current_embedding_widget = 0; + return false; +} + +void QX11Data::xdndHandleEnter(QWidget *, const XEvent * xe, bool /*passive*/) +{ + motifdnd_active = false; + + last_enter_event.xclient = xe->xclient; + + const long *l = xe->xclient.data.l; + int version = (int)(((unsigned long)(l[1])) >> 24); + + if (version > xdnd_version) + return; + + qt_xdnd_dragsource_xid = l[0]; + + int j = 0; + if (l[1] & 1) { + // get the types from XdndTypeList + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *retval = 0; + XGetWindowProperty(X11->display, qt_xdnd_dragsource_xid, ATOM(XdndTypelist), 0, + qt_xdnd_max_type, False, XA_ATOM, &type, &f,&n,&a,&retval); + if (retval) { + Atom *data = (Atom *)retval; + for (; j<qt_xdnd_max_type && j < (int)n; j++) { + qt_xdnd_types[j] = data[j]; + } + XFree((uchar*)data); + } + } else { + // get the types from the message + int i; + for(i=2; i < 5; i++) { + qt_xdnd_types[j++] = l[i]; + } + } + qt_xdnd_types[j] = 0; +} + +static void handle_xdnd_position(QWidget *w, const XEvent * xe, bool passive) +{ + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff); + QWidget * c = find_child(w, p); // changes p to to c-local coordinates + + if (!passive && checkEmbedded(c, xe)) + return; + + if (!c || (!c->acceptDrops() && (c->windowType() == Qt::Desktop))) + return; + + if (l[0] != qt_xdnd_dragsource_xid) { + DEBUG("xdnd drag position from unexpected source (%08lx not %08lx)", l[0], qt_xdnd_dragsource_xid); + return; + } + + // timestamp from the source + if (l[3] != 0) { + // Some X server/client combination swallow the first 32 bit and + // interpret a set bit 31 as negative sign. + qt_xdnd_target_current_time = X11->userTime = + ((sizeof(Time) == 8 && xe->xclient.data.l[3] < 0) + ? uint(l[3]) + : l[3]); + } + + QDragManager *manager = QDragManager::self(); + QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; + + XClientMessageEvent response; + response.type = ClientMessage; + response.window = qt_xdnd_dragsource_xid; + response.format = 32; + response.message_type = ATOM(XdndStatus); + response.data.l[0] = w->effectiveWinId(); + response.data.l[1] = 0; // flags + response.data.l[2] = 0; // x, y + response.data.l[3] = 0; // w, h + response.data.l[4] = 0; // action + + if (!passive) { // otherwise just reject + while (c && !c->acceptDrops() && !c->isWindow()) { + p = c->mapToParent(p); + c = c->parentWidget(); + } + QWidget *target_widget = c && c->acceptDrops() ? c : 0; + + QRect answerRect(c->mapToGlobal(p), QSize(1,1)); + + if (manager->object) { + possible_actions = manager->dragPrivate()->possible_actions; + } else { + possible_actions = Qt::DropActions(xdndaction_to_qtaction(l[4])); +// possible_actions |= Qt::CopyAction; + } + QDragMoveEvent me(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers()); + + Qt::DropAction accepted_action = Qt::IgnoreAction; + + + if (target_widget != qt_xdnd_current_widget) { + if (qt_xdnd_current_widget) { + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + } + if (qt_xdnd_current_widget != target_widget) { + qt_xdnd_current_widget = target_widget; + } + if (target_widget) { + qt_xdnd_current_position = p; + + last_target_accepted_action = Qt::IgnoreAction; + QDragEnterEvent de(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(target_widget, &de); + if (de.isAccepted() && de.dropAction() != Qt::IgnoreAction) + last_target_accepted_action = de.dropAction(); + } + } + + DEBUG() << "qt_handle_xdnd_position action=" << X11->xdndAtomToString(l[4]); + if (!target_widget) { + answerRect = QRect(p, QSize(1, 1)); + } else { + qt_xdnd_current_widget = c; + qt_xdnd_current_position = p; + + if (last_target_accepted_action != Qt::IgnoreAction) { + me.setDropAction(last_target_accepted_action); + me.accept(); + } + QApplication::sendEvent(c, &me); + if (me.isAccepted()) { + response.data.l[1] = 1; // yes + accepted_action = me.dropAction(); + last_target_accepted_action = accepted_action; + } else { + response.data.l[0] = 0; + last_target_accepted_action = Qt::IgnoreAction; + } + answerRect = me.answerRect().intersected(c->rect()); + } + answerRect = QRect(c->mapToGlobal(answerRect.topLeft()), answerRect.size()); + + if (answerRect.left() < 0) + answerRect.setLeft(0); + if (answerRect.right() > 4096) + answerRect.setRight(4096); + if (answerRect.top() < 0) + answerRect.setTop(0); + if (answerRect.bottom() > 4096) + answerRect.setBottom(4096); + if (answerRect.width() < 0) + answerRect.setWidth(0); + if (answerRect.height() < 0) + answerRect.setHeight(0); + + response.data.l[2] = (answerRect.x() << 16) + answerRect.y(); + response.data.l[3] = (answerRect.width() << 16) + answerRect.height(); + response.data.l[4] = qtaction_to_xdndaction(accepted_action); + } + + // reset + qt_xdnd_target_current_time = CurrentTime; + + QWidget * source = QWidget::find(qt_xdnd_dragsource_xid); + if (source && (source->windowType() == Qt::Desktop) && !source->acceptDrops()) + source = 0; + + DEBUG() << "sending XdndStatus"; + if (source) + handle_xdnd_status(source, (const XEvent *)&response, passive); + else + XSendEvent(X11->display, qt_xdnd_dragsource_xid, False, NoEventMask, (XEvent*)&response); +} + +static Bool xdnd_position_scanner(Display *, XEvent *event, XPointer) +{ + if (event->type != ClientMessage) + return false; + XClientMessageEvent *ev = &event->xclient; + + if (ev->message_type == ATOM(XdndPosition)) + return true; + + return false; +} + +void QX11Data::xdndHandlePosition(QWidget * w, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandlePosition"); + while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_position_scanner, 0)) + ; + + handle_xdnd_position(w, xe, passive); +} + + +static void handle_xdnd_status(QWidget *, const XEvent * xe, bool) +{ + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + // ignore late status messages + if (l[0] && l[0] != qt_xdnd_current_proxy_target) + return; + Qt::DropAction newAction = (l[1] & 0x1) ? xdndaction_to_qtaction(l[4]) : Qt::IgnoreAction; + + if ((int)(l[1] & 2) == 0) { + QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff); + QSize s((l[3] & 0xffff0000) >> 16, l[3] & 0x0000ffff); + qt_xdnd_source_sameanswer = QRect(p, s); + } else { + qt_xdnd_source_sameanswer = QRect(); + } + QDragManager *manager = QDragManager::self(); + manager->willDrop = (l[1] & 0x1); + if (global_accepted_action != newAction) + manager->emitActionChanged(newAction); + global_accepted_action = newAction; + manager->updateCursor(); + waiting_for_status = false; +} + +static Bool xdnd_status_scanner(Display *, XEvent *event, XPointer) +{ + if (event->type != ClientMessage) + return false; + XClientMessageEvent *ev = &event->xclient; + + if (ev->message_type == ATOM(XdndStatus)) + return true; + + return false; +} + +void QX11Data::xdndHandleStatus(QWidget * w, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleStatus"); + while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_status_scanner, 0)) + ; + + handle_xdnd_status(w, xe, passive); + DEBUG("xdndHandleStatus end"); +} + +void QX11Data::xdndHandleLeave(QWidget *w, const XEvent * xe, bool /*passive*/) +{ + DEBUG("xdnd leave"); + if (!qt_xdnd_current_widget || + w->window() != qt_xdnd_current_widget->window()) { + return; // sanity + } + + if (checkEmbedded(current_embedding_widget, xe)) { + current_embedding_widget = 0; + qt_xdnd_current_widget = 0; + return; + } + + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + + if (l[0] != qt_xdnd_dragsource_xid) { + // This often happens - leave other-process window quickly + DEBUG("xdnd drag leave from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid); + qt_xdnd_current_widget = 0; + return; + } + + qt_xdnd_dragsource_xid = 0; + qt_xdnd_types[0] = 0; + qt_xdnd_current_widget = 0; +} + + +void qt_xdnd_send_leave() +{ + if (!qt_xdnd_current_target) + return; + + QDragManager *manager = QDragManager::self(); + + XClientMessageEvent leave; + leave.type = ClientMessage; + leave.window = qt_xdnd_current_target; + leave.format = 32; + leave.message_type = ATOM(XdndLeave); + leave.data.l[0] = manager->dragPrivate()->source->effectiveWinId(); + leave.data.l[1] = 0; // flags + leave.data.l[2] = 0; // x, y + leave.data.l[3] = 0; // w, h + leave.data.l[4] = 0; // just null + + QWidget * w = QWidget::find(qt_xdnd_current_proxy_target); + + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + + if (w) + X11->xdndHandleLeave(w, (const XEvent *)&leave, false); + else + XSendEvent(X11->display, qt_xdnd_current_proxy_target, False, + NoEventMask, (XEvent*)&leave); + + // reset the drag manager state + manager->willDrop = false; + if (global_accepted_action != Qt::IgnoreAction) + manager->emitActionChanged(Qt::IgnoreAction); + global_accepted_action = Qt::IgnoreAction; + manager->updateCursor(); + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + qt_xdnd_source_current_time = 0; + waiting_for_status = false; +} + +// TODO: remove and use QApplication::currentKeyboardModifiers() in Qt 4.8. +static Qt::KeyboardModifiers currentKeyboardModifiers() +{ + Window root; + Window child; + int root_x, root_y, win_x, win_y; + uint keybstate; + for (int i = 0; i < ScreenCount(X11->display); ++i) { + if (XQueryPointer(X11->display, QX11Info::appRootWindow(i), &root, &child, + &root_x, &root_y, &win_x, &win_y, &keybstate)) + return X11->translateModifiers(keybstate & 0x00ff); + } + return 0; +} + +void QX11Data::xdndHandleDrop(QWidget *, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleDrop"); + if (!qt_xdnd_current_widget) { + qt_xdnd_dragsource_xid = 0; + return; // sanity + } + + if (!passive && checkEmbedded(qt_xdnd_current_widget, xe)){ + current_embedding_widget = 0; + qt_xdnd_dragsource_xid = 0; + qt_xdnd_current_widget = 0; + return; + } + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + QDragManager *manager = QDragManager::self(); + DEBUG("xdnd drop"); + + if (l[0] != qt_xdnd_dragsource_xid) { + DEBUG("xdnd drop from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid); + return; + } + + // update the "user time" from the timestamp in the event. + if (l[2] != 0) { + // Some X server/client combination swallow the first 32 bit and + // interpret a set bit 31 as negative sign. + qt_xdnd_target_current_time = X11->userTime = + ((sizeof(Time) == 8 && xe->xclient.data.l[2] < 0) + ? uint(l[2]) + : l[2]); + } + + if (!passive) { + // 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. + QMimeData *dropData = 0; + int at = findXdndDropTransactionByTime(qt_xdnd_target_current_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 + if (!dropData) + dropData = (manager->object) ? manager->dragPrivate()->data : manager->dropData; + + // Drop coming from another app? Update keyboard modifiers. + if (!qt_xdnd_dragging) { + QApplicationPrivate::modifier_buttons = currentKeyboardModifiers(); + } + + QDropEvent de(qt_xdnd_current_position, possible_actions, dropData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(qt_xdnd_current_widget, &de); + if (!de.isAccepted()) { + // Ignore a failed drag + global_accepted_action = Qt::IgnoreAction; + } else { + global_accepted_action = de.dropAction(); + } + XClientMessageEvent finished; + finished.type = ClientMessage; + finished.window = qt_xdnd_dragsource_xid; + finished.format = 32; + finished.message_type = ATOM(XdndFinished); + DNDDEBUG << "xdndHandleDrop" + << "qt_xdnd_current_widget" << qt_xdnd_current_widget + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->effectiveWinId() : 0) + << "t_xdnd_current_widget->window()" + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->window() : 0) + << (qt_xdnd_current_widget ? qt_xdnd_current_widget->window()->internalWinId() : 0); + finished.data.l[0] = qt_xdnd_current_widget?qt_xdnd_current_widget->window()->internalWinId():0; + finished.data.l[1] = de.isAccepted() ? 1 : 0; // flags + finished.data.l[2] = qtaction_to_xdndaction(global_accepted_action); + XSendEvent(X11->display, qt_xdnd_dragsource_xid, False, + NoEventMask, (XEvent*)&finished); + } else { + QDragLeaveEvent e; + QApplication::sendEvent(qt_xdnd_current_widget, &e); + } + qt_xdnd_dragsource_xid = 0; + qt_xdnd_current_widget = 0; + waiting_for_status = false; + + // reset + qt_xdnd_target_current_time = CurrentTime; +} + + +void QX11Data::xdndHandleFinished(QWidget *, const XEvent * xe, bool passive) +{ + DEBUG("xdndHandleFinished"); + const unsigned long *l = (const unsigned long *)xe->xclient.data.l; + + DNDDEBUG << "xdndHandleFinished, l[0]" << l[0] + << "qt_xdnd_current_target" << qt_xdnd_current_target + << "qt_xdnd_current_proxy_targe" << qt_xdnd_current_proxy_target; + + if (l[0]) { + int at = findXdndDropTransactionByWindow(l[0]); + if (at != -1) { + restartXdndDropExpiryTimer(); + + QXdndDropTransaction t = X11->dndDropTransactions.takeAt(at); + QDragManager *manager = QDragManager::self(); + + Window target = qt_xdnd_current_target; + Window proxy_target = qt_xdnd_current_proxy_target; + QWidget *embedding_widget = current_embedding_widget; + QDrag *currentObject = manager->object; + + qt_xdnd_current_target = t.target; + qt_xdnd_current_proxy_target = t.proxy_target; + current_embedding_widget = t.embedding_widget; + manager->object = t.object; + + if (!passive) + (void) checkEmbedded(qt_xdnd_current_widget, xe); + + current_embedding_widget = 0; + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + + if (t.object) + t.object->deleteLater(); + + qt_xdnd_current_target = target; + qt_xdnd_current_proxy_target = proxy_target; + current_embedding_widget = embedding_widget; + manager->object = currentObject; + } + } + waiting_for_status = false; +} + + +void QDragManager::timerEvent(QTimerEvent* e) +{ + if (e->timerId() == heartbeat && qt_xdnd_source_sameanswer.isNull()) { + move(QCursor::pos()); + } else if (e->timerId() == transaction_expiry_timer) { + for (int i = 0; i < X11->dndDropTransactions.count(); ++i) { + const QXdndDropTransaction &t = X11->dndDropTransactions.at(i); + if (t.targetWidget) { + // dnd within the same process, don't delete these + continue; + } + t.object->deleteLater(); + X11->dndDropTransactions.removeAt(i--); + } + + killTimer(transaction_expiry_timer); + transaction_expiry_timer = -1; + } +} + +bool QDragManager::eventFilter(QObject * o, QEvent * e) +{ + if (beingCancelled) { + if (e->type() == QEvent::KeyRelease && ((QKeyEvent*)e)->key() == Qt::Key_Escape) { + qApp->removeEventFilter(this); + Q_ASSERT(object == 0); + beingCancelled = false; + eventLoop->exit(); + return true; // block the key release + } + return false; + } + + Q_ASSERT(object != 0); + + if (!o->isWidgetType()) + return false; + + if (e->type() == QEvent::MouseMove) { + QMouseEvent* me = (QMouseEvent *)e; + move(me->globalPos()); + return true; + } else if (e->type() == QEvent::MouseButtonRelease) { + DEBUG("pre drop"); + qApp->removeEventFilter(this); + if (willDrop) + drop(); + else + cancel(); + DEBUG("drop, resetting object"); + beingCancelled = false; + eventLoop->exit(); + return true; + } + + if (e->type() == QEvent::ShortcutOverride) { + // prevent accelerators from firing while dragging + e->accept(); + return true; + } + + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent *ke = ((QKeyEvent*)e); + if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + eventLoop->exit(); + } else { + qt_xdnd_source_sameanswer = QRect(); // force move + move(QCursor::pos()); + } + return true; // Eat all key events + } + + // ### We bind modality to widgets, so we have to do this + // ### "manually". + // DnD is modal - eat all other interactive events + switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::Wheel: + case QEvent::ShortcutOverride: +#ifdef QT3_SUPPORT + case QEvent::Accel: + case QEvent::AccelAvailable: +#endif + return true; + default: + return false; + } +} + +void QDragManager::updateCursor() +{ + if (!noDropCursor) { +#ifndef QT_NO_CURSOR + noDropCursor = new QCursor(Qt::ForbiddenCursor); + moveCursor = new QCursor(Qt::DragMoveCursor); + copyCursor = new QCursor(Qt::DragCopyCursor); + linkCursor = new QCursor(Qt::DragLinkCursor); +#endif + } + + QCursor *c; + if (willDrop) { + if (global_accepted_action == Qt::CopyAction) { + c = copyCursor; + } else if (global_accepted_action == Qt::LinkAction) { + c = linkCursor; + } else { + c = moveCursor; + } + if (xdnd_data.deco) { + xdnd_data.deco->show(); + xdnd_data.deco->raise(); + } + } else { + c = noDropCursor; + //if (qt_xdnd_deco) + // qt_xdnd_deco->hide(); + } +#ifndef QT_NO_CURSOR + if (c) + qApp->changeOverrideCursor(*c); +#endif +} + + +void QDragManager::cancel(bool deleteSource) +{ + DEBUG("QDragManager::cancel"); + Q_ASSERT(heartbeat != -1); + killTimer(heartbeat); + heartbeat = -1; + beingCancelled = true; + qt_xdnd_dragging = false; + + if (qt_xdnd_current_target) + qt_xdnd_send_leave(); + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + if (deleteSource && object) + object->deleteLater(); + object = 0; + qDeleteInEventHandler(xdnd_data.deco); + xdnd_data.deco = 0; + + global_accepted_action = Qt::IgnoreAction; +} + +static +Window findRealWindow(const QPoint & pos, Window w, int md) +{ + if (xdnd_data.deco && w == xdnd_data.deco->effectiveWinId()) + return 0; + + if (md) { + X11->ignoreBadwindow(); + XWindowAttributes attr; + XGetWindowAttributes(X11->display, w, &attr); + if (X11->badwindow()) + return 0; + + if (attr.map_state == IsViewable + && QRect(attr.x,attr.y,attr.width,attr.height).contains(pos)) { + { + Atom type = XNone; + int f; + unsigned long n, a; + unsigned char *data; + + XGetWindowProperty(X11->display, w, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data); + if (data) XFree(data); + if (type) + return w; + } + + Window r, p; + Window* c; + uint nc; + if (XQueryTree(X11->display, w, &r, &p, &c, &nc)) { + r=0; + for (uint i=nc; !r && i--;) { + r = findRealWindow(pos-QPoint(attr.x,attr.y), + c[i], md-1); + } + XFree(c); + if (r) + return r; + + // We didn't find a client window! Just use the + // innermost window. + } + + // No children! + return w; + } + } + return 0; +} + +void QDragManager::move(const QPoint & globalPos) +{ +#ifdef QT_NO_CURSOR + Q_UNUSED(globalPos); + return; +#else + DEBUG() << "QDragManager::move enter"; + if (!object) { + // perhaps the target crashed? + return; + } + + int screen = QCursor::x11Screen(); + if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) { + // recreate the pixmap on the new screen... + delete xdnd_data.deco; + QWidget* parent = object->source()->window()->x11Info().screen() == screen + ? object->source()->window() : QApplication::desktop()->screen(screen); + xdnd_data.deco = new QShapedPixmapWidget(parent); + if (!QWidget::mouseGrabber()) { + updatePixmap(); + xdnd_data.deco->grabMouse(); + } + } + xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot); + + if (qt_xdnd_source_sameanswer.contains(globalPos) && qt_xdnd_source_sameanswer.isValid()) + return; + + qt_xdnd_current_screen = screen; + Window rootwin = QX11Info::appRootWindow(qt_xdnd_current_screen); + Window target = 0; + int lx = 0, ly = 0; + if (!XTranslateCoordinates(X11->display, rootwin, rootwin, globalPos.x(), globalPos.y(), &lx, &ly, &target)) + // some weird error... + return; + + if (target == rootwin) { + // Ok. + } else if (target) { + //me + Window src = rootwin; + while (target != 0) { + DNDDEBUG << "checking target for XdndAware" << QWidget::find(target) << target; + int lx2, ly2; + Window t; + // translate coordinates + if (!XTranslateCoordinates(X11->display, src, target, lx, ly, &lx2, &ly2, &t)) { + target = 0; + break; + } + lx = lx2; + ly = ly2; + src = target; + + // check if it has XdndAware + Atom type = 0; + int f; + unsigned long n, a; + unsigned char *data = 0; + XGetWindowProperty(X11->display, target, ATOM(XdndAware), 0, 0, False, + AnyPropertyType, &type, &f,&n,&a,&data); + if (data) + XFree(data); + if (type) { + DNDDEBUG << "Found XdndAware on " << QWidget::find(target) << target; + break; + } + + // find child at the coordinates + if (!XTranslateCoordinates(X11->display, src, src, lx, ly, &lx2, &ly2, &target)) { + target = 0; + break; + } + } + if (xdnd_data.deco && (!target || target == xdnd_data.deco->effectiveWinId())) { + DNDDEBUG << "need to find real window"; + target = findRealWindow(globalPos, rootwin, 6); + DNDDEBUG << "real window found" << QWidget::find(target) << target; + } + } + + QWidget* w; + if (target) { + w = QWidget::find((WId)target); + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + } else { + w = 0; + target = rootwin; + } + + DNDDEBUG << "and the final target is " << QWidget::find(target) << target; + DNDDEBUG << "the widget w is" << w; + + WId proxy_target = xdndProxy(target); + if (!proxy_target) + proxy_target = target; + int target_version = 1; + + if (proxy_target) { + Atom type = XNone; + int r, f; + unsigned long n, a; + unsigned char *retval; + X11->ignoreBadwindow(); + r = XGetWindowProperty(X11->display, proxy_target, ATOM(XdndAware), 0, + 1, False, AnyPropertyType, &type, &f,&n,&a,&retval); + int *tv = (int *)retval; + if (r != Success || X11->badwindow()) { + target = 0; + } else { + target_version = qMin(xdnd_version,tv ? *tv : 1); + if (tv) + XFree(tv); +// if (!(!X11->badwindow() && type)) +// target = 0; + } + } + + if (target != qt_xdnd_current_target) { + if (qt_xdnd_current_target) + qt_xdnd_send_leave(); + + qt_xdnd_current_target = target; + qt_xdnd_current_proxy_target = proxy_target; + if (target) { + QVector<Atom> types; + int flags = target_version << 24; + QStringList fmts = QInternalMimeData::formatsHelper(dragPrivate()->data); + for (int i = 0; i < fmts.size(); ++i) { + QList<Atom> atoms = X11->xdndMimeAtomsForFormat(fmts.at(i)); + for (int j = 0; j < atoms.size(); ++j) { + if (!types.contains(atoms.at(j))) + types.append(atoms.at(j)); + } + } + if (types.size() > 3) { + XChangeProperty(X11->display, + dragPrivate()->source->effectiveWinId(), ATOM(XdndTypelist), + XA_ATOM, 32, PropModeReplace, + (unsigned char *)types.data(), + types.size()); + flags |= 0x0001; + } + XClientMessageEvent enter; + enter.type = ClientMessage; + enter.window = target; + enter.format = 32; + enter.message_type = ATOM(XdndEnter); + enter.data.l[0] = dragPrivate()->source->effectiveWinId(); + enter.data.l[1] = flags; + enter.data.l[2] = types.size()>0 ? types.at(0) : 0; + enter.data.l[3] = types.size()>1 ? types.at(1) : 0; + enter.data.l[4] = types.size()>2 ? types.at(2) : 0; + // provisionally set the rectangle to 5x5 pixels... + qt_xdnd_source_sameanswer = QRect(globalPos.x() - 2, + globalPos.y() -2 , 5, 5); + + DEBUG("sending Xdnd enter"); + if (w) + X11->xdndHandleEnter(w, (const XEvent *)&enter, false); + else if (target) + XSendEvent(X11->display, proxy_target, False, NoEventMask, (XEvent*)&enter); + waiting_for_status = false; + } + } + if (waiting_for_status) + return; + + if (target) { + waiting_for_status = true; + + XClientMessageEvent move; + move.type = ClientMessage; + move.window = target; + move.format = 32; + move.message_type = ATOM(XdndPosition); + move.window = target; + move.data.l[0] = dragPrivate()->source->effectiveWinId(); + move.data.l[1] = 0; // flags + move.data.l[2] = (globalPos.x() << 16) + globalPos.y(); + move.data.l[3] = X11->time; + move.data.l[4] = qtaction_to_xdndaction(defaultAction(dragPrivate()->possible_actions, QApplication::keyboardModifiers())); + DEBUG("sending Xdnd position"); + + qt_xdnd_source_current_time = X11->time; + + if (w) + handle_xdnd_position(w, (const XEvent *)&move, false); + else + XSendEvent(X11->display, proxy_target, False, NoEventMask, + (XEvent*)&move); + } else { + if (willDrop) { + willDrop = false; + updateCursor(); + } + } + DEBUG() << "QDragManager::move leave"; +#endif +} + + +void QDragManager::drop() +{ + Q_ASSERT(heartbeat != -1); + killTimer(heartbeat); + heartbeat = -1; + qt_xdnd_dragging = false; + + if (!qt_xdnd_current_target) + return; + + qDeleteInEventHandler(xdnd_data.deco); + xdnd_data.deco = 0; + + XClientMessageEvent drop; + drop.type = ClientMessage; + drop.window = qt_xdnd_current_target; + drop.format = 32; + drop.message_type = ATOM(XdndDrop); + drop.data.l[0] = dragPrivate()->source->effectiveWinId(); + drop.data.l[1] = 0; // flags + drop.data.l[2] = X11->time; + + drop.data.l[3] = 0; + drop.data.l[4] = 0; + + QWidget * w = QWidget::find(qt_xdnd_current_proxy_target); + + if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops()) + w = 0; + + QXdndDropTransaction t = { + X11->time, + qt_xdnd_current_target, + qt_xdnd_current_proxy_target, + w, + current_embedding_widget, + object + }; + X11->dndDropTransactions.append(t); + restartXdndDropExpiryTimer(); + + if (w) + X11->xdndHandleDrop(w, (const XEvent *)&drop, false); + else + XSendEvent(X11->display, qt_xdnd_current_proxy_target, False, + NoEventMask, (XEvent*)&drop); + + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + qt_xdnd_source_current_time = 0; + current_embedding_widget = 0; + object = 0; + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + QApplication::restoreOverrideCursor(); + restoreCursor = false; + } +#endif +} + + + +bool QX11Data::xdndHandleBadwindow() +{ + if (qt_xdnd_current_target) { + QDragManager *manager = QDragManager::self(); + if (manager->object) { + qt_xdnd_current_target = 0; + qt_xdnd_current_proxy_target = 0; + manager->object->deleteLater(); + manager->object = 0; + delete xdnd_data.deco; + xdnd_data.deco = 0; + return true; + } + } + if (qt_xdnd_dragsource_xid) { + qt_xdnd_dragsource_xid = 0; + if (qt_xdnd_current_widget) { + QApplication::postEvent(qt_xdnd_current_widget, new QDragLeaveEvent); + qt_xdnd_current_widget = 0; + } + return true; + } + return false; +} + +void QX11Data::xdndHandleSelectionRequest(const XSelectionRequestEvent * req) +{ + if (!req) + return; + XEvent evt; + evt.xselection.type = SelectionNotify; + evt.xselection.display = req->display; + evt.xselection.requestor = req->requestor; + evt.xselection.selection = req->selection; + evt.xselection.target = XNone; + evt.xselection.property = XNone; + evt.xselection.time = req->time; + + QDragManager *manager = QDragManager::self(); + QDrag *currentObject = manager->object; + + // which transaction do we use? (note: -2 means use current manager->object) + int at = -1; + + // figure out which data the requestor is really interested in + if (manager->object && req->time == qt_xdnd_source_current_time) { + // requestor wants the current drag data + at = -2; + } else { + // if someone has requested data in response to XdndDrop, find the corresponding transaction. the + // spec says to call XConvertSelection() using the timestamp from the XdndDrop + at = findXdndDropTransactionByTime(req->time); + if (at == -1) { + // no dice, perhaps the client was nice enough to use the same window id in XConvertSelection() + // that we sent the XdndDrop event to. + at = findXdndDropTransactionByWindow(req->requestor); + } + if (at == -1 && req->time == CurrentTime) { + // previous Qt versions always requested the data on a child of the target window + // using CurrentTime... but it could be asking for either drop data or the current drag's data + Window target = findXdndAwareParent(req->requestor); + if (target) { + if (qt_xdnd_current_target && qt_xdnd_current_target == target) + at = -2; + else + at = findXdndDropTransactionByWindow(target); + } + } + } + if (at >= 0) { + restartXdndDropExpiryTimer(); + + // use the drag object from an XdndDrop tansaction + manager->object = X11->dndDropTransactions.at(at).object; + } else if (at != -2) { + // no transaction found, we'll have to reject the request + manager->object = 0; + } + if (manager->object) { + Atom atomFormat = req->target; + int dataFormat = 0; + QByteArray data; + if (X11->xdndMimeDataForAtom(req->target, manager->dragPrivate()->data, + &data, &atomFormat, &dataFormat)) { + int dataSize = data.size() / (dataFormat / 8); + XChangeProperty (X11->display, req->requestor, req->property, + atomFormat, dataFormat, PropModeReplace, + (unsigned char *)data.data(), dataSize); + evt.xselection.property = req->property; + evt.xselection.target = atomFormat; + } + } + + // reset manager->object in case we modified it above + manager->object = currentObject; + + // ### this can die if req->requestor crashes at the wrong + // ### moment + XSendEvent(X11->display, req->requestor, False, 0, &evt); +} + +static QVariant xdndObtainData(const char *format, QVariant::Type requestedType) +{ + QByteArray result; + + QWidget* w; + QDragManager *manager = QDragManager::self(); + if (qt_xdnd_dragsource_xid && manager->object && + (w=QWidget::find(qt_xdnd_dragsource_xid)) + && (!(w->windowType() == Qt::Desktop) || w->acceptDrops())) + { + QDragPrivate * o = QDragManager::self()->dragPrivate(); + if (o->data->hasFormat(QLatin1String(format))) + result = o->data->data(QLatin1String(format)); + return result; + } + + QList<Atom> atoms; + int i = 0; + while ((qt_xdnd_types[i])) { + atoms.append(qt_xdnd_types[i]); + ++i; + } + QByteArray encoding; + Atom a = X11->xdndMimeAtomForFormat(QLatin1String(format), requestedType, atoms, &encoding); + if (!a) + return result; + + if (XGetSelectionOwner(X11->display, ATOM(XdndSelection)) == XNone) + return result; // should never happen? + + QWidget* tw = qt_xdnd_current_widget; + if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop)) + tw = new QWidget; + + XConvertSelection(X11->display, ATOM(XdndSelection), a, ATOM(XdndSelection), tw->effectiveWinId(), + qt_xdnd_target_current_time); + XFlush(X11->display); + + XEvent xevent; + bool got=X11->clipboardWaitForEvent(tw->effectiveWinId(), SelectionNotify, &xevent, 5000); + if (got) { + Atom type; + + if (X11->clipboardReadProperty(tw->effectiveWinId(), ATOM(XdndSelection), true, &result, 0, &type, 0)) { + if (type == ATOM(INCR)) { + int nbytes = result.size() >= 4 ? *((int*)result.data()) : 0; + result = X11->clipboardReadIncrementalProperty(tw->effectiveWinId(), ATOM(XdndSelection), nbytes, false); + } else if (type != a && type != XNone) { + DEBUG("Qt clipboard: unknown atom %ld", type); + } + } + } + if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop)) + delete tw; + + return X11->xdndMimeConvertToFormat(a, result, QLatin1String(format), requestedType, encoding); +} + + +/* + Enable drag and drop for widget w by installing the proper + properties on w's toplevel widget. +*/ +bool QX11Data::dndEnable(QWidget* w, bool on) +{ + w = w->window(); + + if (bool(((QExtraWidget*)w)->topData()->dnd) == on) + return true; // been there, done that + ((QExtraWidget*)w)->topData()->dnd = on ? 1 : 0; + + motifdndEnable(w, on); + return xdndEnable(w, on); +} + +Qt::DropAction QDragManager::drag(QDrag * o) +{ + if (object == o || !o || !o->d_func()->source) + return Qt::IgnoreAction; + + if (object) { + cancel(); + qApp->removeEventFilter(this); + beingCancelled = false; + } + + if (object) { + // the last drag and drop operation hasn't finished, so we are going to wait + // for one second to see if it does... if the finish message comes after this, + // then we could still have problems, but this is highly unlikely + QApplication::flush(); + + QElapsedTimer timer; + timer.start(); + do { + XEvent event; + if (XCheckTypedEvent(X11->display, ClientMessage, &event)) + qApp->x11ProcessEvent(&event); + + // sleep 50 ms, so we don't use up CPU cycles all the time. + struct timeval usleep_tv; + usleep_tv.tv_sec = 0; + usleep_tv.tv_usec = 50000; + select(0, 0, 0, 0, &usleep_tv); + } while (object && timer.hasExpired(1000)); + } + + object = o; + object->d_func()->target = 0; + xdnd_data.deco = new QShapedPixmapWidget(object->source()->window()); + + willDrop = false; + + updatePixmap(); + + qApp->installEventFilter(this); + XSetSelectionOwner(X11->display, ATOM(XdndSelection), dragPrivate()->source->window()->internalWinId(), X11->time); + global_accepted_action = Qt::CopyAction; + qt_xdnd_source_sameanswer = QRect(); +#ifndef QT_NO_CURSOR + // set the override cursor (must be done here, since it is updated + // in the call to move() below) + qApp->setOverrideCursor(Qt::ArrowCursor); + restoreCursor = true; +#endif + move(QCursor::pos()); + heartbeat = startTimer(200); + + qt_xdnd_dragging = true; + + if (!QWidget::mouseGrabber()) + xdnd_data.deco->grabMouse(); + + eventLoop = new QEventLoop; + (void) eventLoop->exec(); + delete eventLoop; + eventLoop = 0; + +#ifndef QT_NO_CURSOR + if (restoreCursor) { + qApp->restoreOverrideCursor(); + restoreCursor = false; + } +#endif + + // delete cursors as they may be different next drag. + delete noDropCursor; + noDropCursor = 0; + delete copyCursor; + copyCursor = 0; + delete moveCursor; + moveCursor = 0; + delete linkCursor; + linkCursor = 0; + + delete xdnd_data.deco; + xdnd_data.deco = 0; + if (heartbeat != -1) + killTimer(heartbeat); + heartbeat = -1; + qt_xdnd_current_screen = -1; + qt_xdnd_dragging = false; + + return global_accepted_action; + // object persists until we get an xdnd_finish message +} + +void QDragManager::updatePixmap() +{ + if (xdnd_data.deco) { + QPixmap pm; + QPoint pm_hot(default_pm_hotx,default_pm_hoty); + if (object) { + pm = dragPrivate()->pixmap; + if (!pm.isNull()) + pm_hot = dragPrivate()->hotspot; + } + if (pm.isNull()) { + if (!defaultPm) + defaultPm = new QPixmap(default_pm); + pm = *defaultPm; + } + xdnd_data.deco->pm_hot = pm_hot; + xdnd_data.deco->setPixmap(pm); + xdnd_data.deco->move(QCursor::pos()-pm_hot); + xdnd_data.deco->show(); + } +} + +QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const +{ + QByteArray mime = mimetype.toLatin1(); + QVariant data = X11->motifdnd_active + ? X11->motifdndObtainData(mime) + : xdndObtainData(mime, requestedType); + return data; +} + +bool QDropData::hasFormat_sys(const QString &format) const +{ + return formats().contains(format); +} + +QStringList QDropData::formats_sys() const +{ + QStringList formats; + if (X11->motifdnd_active) { + int i = 0; + QByteArray fmt; + while (!(fmt = X11->motifdndFormat(i)).isEmpty()) { + formats.append(QLatin1String(fmt)); + ++i; + } + } else { + int i = 0; + while ((qt_xdnd_types[i])) { + QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(qt_xdnd_types[i]); + for (int j = 0; j < formatsForAtom.size(); ++j) { + if (!formats.contains(formatsForAtom.at(j))) + formats.append(formatsForAtom.at(j)); + } + ++i; + } + } + return formats; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DRAGANDDROP diff --git a/src/gui/kernel/qdrag.cpp b/src/gui/kernel/qdrag.cpp new file mode 100644 index 0000000000..d8d14cb45c --- /dev/null +++ b/src/gui/kernel/qdrag.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qwidget.h> +#include <qdrag.h> +#include <qpixmap.h> +#include <qpoint.h> +#include "qdnd_p.h" + +#ifndef QT_NO_DRAGANDDROP + +QT_BEGIN_NAMESPACE + +/*! + \class QDrag + \brief The QDrag class provides support for MIME-based drag and drop data + transfer. + + Drag and drop is an intuitive way for users to copy or move data around in an + application, and is used in many desktop environments as a mechanism for copying + data between applications. Drag and drop support in Qt is centered around the + QDrag class that handles most of the details of a drag and drop operation. + + The data to be transferred by the drag and drop operation is contained in a + QMimeData object. This is specified with the setMimeData() function in the + following way: + + \snippet doc/src/snippets/dragging/mainwindow.cpp 1 + + Note that setMimeData() assigns ownership of the QMimeData object to the + QDrag object. The QDrag must be constructed on the heap with a parent QWidget + to ensure that Qt can clean up after the drag and drop operation has been + completed. + + A pixmap can be used to represent the data while the drag is in + progress, and will move with the cursor to the drop target. This + pixmap typically shows an icon that represents the MIME type of + the data being transferred, but any pixmap can be set with + setPixmap(). The cursor's hot spot can be given a position + relative to the top-left corner of the pixmap with the + setHotSpot() function. The following code positions the pixmap so + that the cursor's hot spot points to the center of its bottom + edge: + + \snippet doc/src/snippets/separations/finalwidget.cpp 2 + + \note On X11, the pixmap may not be able to keep up with the mouse + movements if the hot spot causes the pixmap to be displayed + directly under the cursor. + + The source and target widgets can be found with source() and target(). + These functions are often used to determine whether drag and drop operations + started and finished at the same widget, so that special behavior can be + implemented. + + QDrag only deals with the drag and drop operation itself. It is up to the + developer to decide when a drag operation begins, and how a QDrag object should + be constructed and used. For a given widget, it is often necessary to + reimplement \l{QWidget::mousePressEvent()}{mousePressEvent()} to determine + whether the user has pressed a mouse button, and reimplement + \l{QWidget::mouseMoveEvent()}{mouseMoveEvent()} to check whether a QDrag is + required. + + \sa {Drag and Drop}, QClipboard, QMimeData, QWindowsMime, QMacPasteboardMime, + {Draggable Icons Example}, {Draggable Text Example}, {Drop Site Example}, + {Fridge Magnets Example} +*/ + +/*! + Constructs a new drag object for the widget specified by \a dragSource. +*/ +QDrag::QDrag(QWidget *dragSource) + : QObject(*new QDragPrivate, dragSource) +{ + Q_D(QDrag); + d->source = dragSource; + d->target = 0; + d->data = 0; + d->hotspot = QPoint(-10, -10); + d->possible_actions = Qt::CopyAction; + d->executed_action = Qt::IgnoreAction; + d->defaultDropAction = Qt::IgnoreAction; +} + +/*! + Destroys the drag object. +*/ +QDrag::~QDrag() +{ + Q_D(QDrag); + delete d->data; + QDragManager *manager = QDragManager::self(); + if (manager && manager->object == this) + manager->cancel(false); +} + +/*! + Sets the data to be sent to the given MIME \a data. Ownership of the data is + transferred to the QDrag object. +*/ +void QDrag::setMimeData(QMimeData *data) +{ + Q_D(QDrag); + if (d->data == data) + return; + if (d->data != 0) + delete d->data; + d->data = data; +} + +/*! + Returns the MIME data that is encapsulated by the drag object. +*/ +QMimeData *QDrag::mimeData() const +{ + Q_D(const QDrag); + return d->data; +} + +/*! + Sets \a pixmap as the pixmap used to represent the data in a drag + and drop operation. You can only set a pixmap before the drag is + started. +*/ +void QDrag::setPixmap(const QPixmap &pixmap) +{ + Q_D(QDrag); + d->pixmap = pixmap; +} + +/*! + Returns the pixmap used to represent the data in a drag and drop operation. +*/ +QPixmap QDrag::pixmap() const +{ + Q_D(const QDrag); + return d->pixmap; +} + +/*! + Sets the position of the hot spot relative to the top-left corner of the + pixmap used to the point specified by \a hotspot. + + \bold{Note:} on X11, the pixmap may not be able to keep up with the mouse + movements if the hot spot causes the pixmap to be displayed + directly under the cursor. +*/ +void QDrag::setHotSpot(const QPoint& hotspot) +{ + Q_D(QDrag); + d->hotspot = hotspot; +} + +/*! + Returns the position of the hot spot relative to the top-left corner of the + cursor. +*/ +QPoint QDrag::hotSpot() const +{ + Q_D(const QDrag); + return d->hotspot; +} + +/*! + Returns the source of the drag object. This is the widget where the drag + and drop operation originated. +*/ +QWidget *QDrag::source() const +{ + Q_D(const QDrag); + return d->source; +} + +/*! + Returns the target of the drag and drop operation. This is the widget where + the drag object was dropped. +*/ +QWidget *QDrag::target() const +{ + Q_D(const QDrag); + return d->target; +} + +/*! + \since 4.3 + + Starts the drag and drop operation and returns a value indicating the requested + drop action when it is completed. The drop actions that the user can choose + from are specified in \a supportedActions. The default proposed action will be selected + among the allowed actions in the following order: Move, Copy and Link. + + \bold{Note:} On Linux and Mac OS X, the drag and drop operation + can take some time, but this function does not block the event + loop. Other events are still delivered to the application while + the operation is performed. On Windows, the Qt event loop is + blocked while during the operation. +*/ + +Qt::DropAction QDrag::exec(Qt::DropActions supportedActions) +{ + return exec(supportedActions, Qt::IgnoreAction); +} + +/*! + \since 4.3 + + Starts the drag and drop operation and returns a value indicating the requested + drop action when it is completed. The drop actions that the user can choose + from are specified in \a supportedActions. + + The \a defaultDropAction determines which action will be proposed when the user performs a + drag without using modifier keys. + + \bold{Note:} On Linux and Mac OS X, the drag and drop operation + can take some time, but this function does not block the event + loop. Other events are still delivered to the application while + the operation is performed. On Windows, the Qt event loop is + blocked during the operation. However, QDrag::exec() on + Windows causes processEvents() to be called frequently to keep the GUI responsive. + If any loops or operations are called while a drag operation is active, it will block the drag operation. +*/ + +Qt::DropAction QDrag::exec(Qt::DropActions supportedActions, Qt::DropAction defaultDropAction) +{ + Q_D(QDrag); + if (!d->data) { + qWarning("QDrag: No mimedata set before starting the drag"); + return d->executed_action; + } + QDragManager *manager = QDragManager::self(); + d->defaultDropAction = Qt::IgnoreAction; + d->possible_actions = supportedActions; + + if (manager) { + if (defaultDropAction == Qt::IgnoreAction) { + if (supportedActions & Qt::MoveAction) { + d->defaultDropAction = Qt::MoveAction; + } else if (supportedActions & Qt::CopyAction) { + d->defaultDropAction = Qt::CopyAction; + } else if (supportedActions & Qt::LinkAction) { + d->defaultDropAction = Qt::LinkAction; + } + } else { + d->defaultDropAction = defaultDropAction; + } + d->executed_action = manager->drag(this); + } + + return d->executed_action; +} + +/*! + \obsolete + + \bold{Note:} It is recommended to use exec() instead of this function. + + Starts the drag and drop operation and returns a value indicating the requested + drop action when it is completed. The drop actions that the user can choose + from are specified in \a request. Qt::CopyAction is always allowed. + + \bold{Note:} Although the drag and drop operation can take some time, this function + does not block the event loop. Other events are still delivered to the application + while the operation is performed. + + \sa exec() +*/ +Qt::DropAction QDrag::start(Qt::DropActions request) +{ + Q_D(QDrag); + if (!d->data) { + qWarning("QDrag: No mimedata set before starting the drag"); + return d->executed_action; + } + QDragManager *manager = QDragManager::self(); + d->defaultDropAction = Qt::IgnoreAction; + d->possible_actions = request | Qt::CopyAction; + if (manager) + d->executed_action = manager->drag(this); + return d->executed_action; +} + +/*! + Sets the drag \a cursor for the \a action. This allows you + to override the default native cursors. To revert to using the + native cursor for \a action pass in a null QPixmap as \a cursor. + + The \a action can only be CopyAction, MoveAction or LinkAction. + All other values of DropAction are ignored. +*/ +void QDrag::setDragCursor(const QPixmap &cursor, Qt::DropAction action) +{ + Q_D(QDrag); + if (action != Qt::CopyAction && action != Qt::MoveAction && action != Qt::LinkAction) + return; + if (cursor.isNull()) + d->customCursors.remove(action); + else + d->customCursors[action] = cursor; +} + +/*! + \fn void QDrag::actionChanged(Qt::DropAction action) + + This signal is emitted when the \a action associated with the + drag changes. + + \sa targetChanged() +*/ + +/*! + \fn void QDrag::targetChanged(QWidget *newTarget) + + This signal is emitted when the target of the drag and drop + operation changes, with \a newTarget the new target. + + \sa target(), actionChanged() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_DRAGANDDROP diff --git a/src/gui/kernel/qdrag.h b/src/gui/kernel/qdrag.h new file mode 100644 index 0000000000..da847898b2 --- /dev/null +++ b/src/gui/kernel/qdrag.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAG_H +#define QDRAG_H + +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_DRAGANDDROP +class QMimeData; +class QDragPrivate; +class QWidget; +class QPixmap; +class QPoint; +class QDragManager; + +class Q_GUI_EXPORT QDrag : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QDrag) +public: + explicit QDrag(QWidget *dragSource); + ~QDrag(); + + void setMimeData(QMimeData *data); + QMimeData *mimeData() const; + + void setPixmap(const QPixmap &); + QPixmap pixmap() const; + + void setHotSpot(const QPoint &hotspot); + QPoint hotSpot() const; + + QWidget *source() const; + QWidget *target() const; + + Qt::DropAction start(Qt::DropActions supportedActions = Qt::CopyAction); + Qt::DropAction exec(Qt::DropActions supportedActions = Qt::MoveAction); + Qt::DropAction exec(Qt::DropActions supportedActions, Qt::DropAction defaultAction); + + void setDragCursor(const QPixmap &cursor, Qt::DropAction action); + +Q_SIGNALS: + void actionChanged(Qt::DropAction action); + void targetChanged(QWidget *newTarget); + +private: +#ifdef Q_WS_MAC + friend class QWidgetPrivate; +#endif + friend class QDragManager; + Q_DISABLE_COPY(QDrag) +}; + +#endif // QT_NO_DRAGANDDROP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDRAG_H diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp new file mode 100644 index 0000000000..277a5e845d --- /dev/null +++ b/src/gui/kernel/qevent.cpp @@ -0,0 +1,4851 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qevent.h" +#include "qcursor.h" +#include "qapplication.h" +#include "private/qapplication_p.h" +#include "private/qevent_p.h" +#include "private/qkeysequence_p.h" +#include "qwidget.h" +#include "qgraphicsview.h" +#include "qdebug.h" +#include "qmime.h" +#include "qdnd_p.h" +#include "qevent_p.h" +#include "qgesture.h" +#include "qgesture_p.h" + +#ifdef Q_OS_SYMBIAN +#include "private/qcore_symbian_p.h" +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QInputEvent + \ingroup events + + \brief The QInputEvent class is the base class for events that + describe user input. +*/ + +/*! + \internal +*/ +QInputEvent::QInputEvent(Type type, Qt::KeyboardModifiers modifiers) + : QEvent(type), modState(modifiers) +{} + +/*! + \internal +*/ +QInputEvent::~QInputEvent() +{ +} + +/*! + \fn Qt::KeyboardModifiers QInputEvent::modifiers() const + + Returns the keyboard modifier flags that existed immediately + before the event occurred. + + \sa QApplication::keyboardModifiers() +*/ + +/*! \fn void QInputEvent::setModifiers(Qt::KeyboardModifiers modifiers) + + \internal + + Sets the keyboard modifiers flags for this event. +*/ + +/*! + \class QMouseEvent + \ingroup events + + \brief The QMouseEvent class contains parameters that describe a mouse event. + + Mouse events occur when a mouse button is pressed or released + inside a widget, or when the mouse cursor is moved. + + Mouse move events will occur only when a mouse button is pressed + down, unless mouse tracking has been enabled with + QWidget::setMouseTracking(). + + Qt automatically grabs the mouse when a mouse button is pressed + inside a widget; the widget will continue to receive mouse events + until the last mouse button is released. + + A mouse event contains a special accept flag that indicates + whether the receiver wants the event. You should call ignore() if + the mouse event is not handled by your widget. A mouse event is + propagated up the parent widget chain until a widget accepts it + with accept(), or an event filter consumes it. + + \note If a mouse event is propagated to a \l{QWidget}{widget} for + which Qt::WA_NoMousePropagation has been set, that mouse event + will not be propagated further up the parent widget chain. + + The state of the keyboard modifier keys can be found by calling the + \l{QInputEvent::modifiers()}{modifiers()} function, inherited from + QInputEvent. + + The functions pos(), x(), and y() give the cursor position + relative to the widget that receives the mouse event. If you + move the widget as a result of the mouse event, use the global + position returned by globalPos() to avoid a shaking motion. + + The QWidget::setEnabled() function can be used to enable or + disable mouse and keyboard events for a widget. + + Reimplement the QWidget event handlers, QWidget::mousePressEvent(), + QWidget::mouseReleaseEvent(), QWidget::mouseDoubleClickEvent(), + and QWidget::mouseMoveEvent() to receive mouse events in your own + widgets. + + \sa QWidget::setMouseTracking() QWidget::grabMouse() + QCursor::pos() +*/ + +/*! + Constructs a mouse event object. + + The \a type parameter must be one of QEvent::MouseButtonPress, + QEvent::MouseButtonRelease, QEvent::MouseButtonDblClick, + or QEvent::MouseMove. + + The \a position is the mouse cursor's position relative to the + receiving widget. + The \a button that caused the event is given as a value from + the Qt::MouseButton enum. If the event \a type is + \l MouseMove, the appropriate button for this event is Qt::NoButton. + The mouse and keyboard states at the time of the event are specified by + \a buttons and \a modifiers. + + The globalPos() is initialized to QCursor::pos(), which may not + be appropriate. Use the other constructor to specify the global + position explicitly. +*/ + +QMouseEvent::QMouseEvent(Type type, const QPoint &position, Qt::MouseButton button, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QInputEvent(type, modifiers), p(position), b(button), mouseState(buttons) +{ + g = QCursor::pos(); +} + +/*! + \internal +*/ +QMouseEvent::~QMouseEvent() +{ +} + +#ifdef QT3_SUPPORT +/*! + Use QMouseEvent(\a type, \a pos, \a button, \c buttons, \c + modifiers) instead, where \c buttons is \a state & + Qt::MouseButtonMask and \c modifiers is \a state & + Qt::KeyButtonMask. +*/ +QMouseEvent::QMouseEvent(Type type, const QPoint &pos, Qt::ButtonState button, int state) + : QInputEvent(type), p(pos), b((Qt::MouseButton)button) +{ + g = QCursor::pos(); + mouseState = Qt::MouseButtons((state ^ b) & Qt::MouseButtonMask); + modState = Qt::KeyboardModifiers(state & (int)Qt::KeyButtonMask); +} + +/*! + Use QMouseEvent(\a type, \a pos, \a globalPos, \a button, + \c buttons, \c modifiers) instead, where + \c buttons is \a state & Qt::MouseButtonMask and + \c modifiers is \a state & Qt::KeyButtonMask. +*/ +QMouseEvent::QMouseEvent(Type type, const QPoint &pos, const QPoint &globalPos, + Qt::ButtonState button, int state) + : QInputEvent(type), p(pos), g(globalPos), b((Qt::MouseButton)button) +{ + mouseState = Qt::MouseButtons((state ^ b) & Qt::MouseButtonMask); + modState = Qt::KeyboardModifiers(state & (int)Qt::KeyButtonMask); +} +#endif + + +/*! + Constructs a mouse event object. + + The \a type parameter must be QEvent::MouseButtonPress, + QEvent::MouseButtonRelease, QEvent::MouseButtonDblClick, + or QEvent::MouseMove. + + The \a pos is the mouse cursor's position relative to the + receiving widget. The cursor's position in global coordinates is + specified by \a globalPos. The \a button that caused the event is + given as a value from the \l Qt::MouseButton enum. If the event \a + type is \l MouseMove, the appropriate button for this event is + Qt::NoButton. \a buttons is the state of all buttons at the + time of the event, \a modifiers the state of all keyboard + modifiers. + +*/ +QMouseEvent::QMouseEvent(Type type, const QPoint &pos, const QPoint &globalPos, + Qt::MouseButton button, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers) + : QInputEvent(type, modifiers), p(pos), g(globalPos), b(button), mouseState(buttons) +{} + +/*! + \internal +*/ +QMouseEvent *QMouseEvent::createExtendedMouseEvent(Type type, const QPointF &pos, + const QPoint &globalPos, Qt::MouseButton button, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) +{ + return new QMouseEventEx(type, pos, globalPos, button, buttons, modifiers); +} + +/*! + \fn bool QMouseEvent::hasExtendedInfo() const + \internal +*/ + +/*! + \since 4.4 + + Returns the position of the mouse cursor as a QPointF, relative to the + widget that received the event. + + If you move the widget as a result of the mouse event, use the + global position returned by globalPos() to avoid a shaking + motion. + + \sa x() y() pos() globalPos() +*/ +QPointF QMouseEvent::posF() const +{ + return hasExtendedInfo() ? reinterpret_cast<const QMouseEventEx *>(this)->posF : QPointF(pos()); +} + +/*! + \internal +*/ +QMouseEventEx::QMouseEventEx(Type type, const QPointF &pos, const QPoint &globalPos, + Qt::MouseButton button, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers) + : QMouseEvent(type, pos.toPoint(), globalPos, button, buttons, modifiers), posF(pos) +{ + d = reinterpret_cast<QEventPrivate *>(this); +} + +/*! + \internal +*/ +QMouseEventEx::~QMouseEventEx() +{ +} + +/*! + \fn const QPoint &QMouseEvent::pos() const + + Returns the position of the mouse cursor, relative to the widget + that received the event. + + If you move the widget as a result of the mouse event, use the + global position returned by globalPos() to avoid a shaking + motion. + + \sa x() y() globalPos() +*/ + +/*! + \fn const QPoint &QMouseEvent::globalPos() const + + Returns the global position of the mouse cursor \e{at the time + of the event}. This is important on asynchronous window systems + like X11. Whenever you move your widgets around in response to + mouse events, globalPos() may differ a lot from the current + pointer position QCursor::pos(), and from + QWidget::mapToGlobal(pos()). + + \sa globalX() globalY() +*/ + +/*! + \fn int QMouseEvent::x() const + + Returns the x position of the mouse cursor, relative to the + widget that received the event. + + \sa y() pos() +*/ + +/*! + \fn int QMouseEvent::y() const + + Returns the y position of the mouse cursor, relative to the + widget that received the event. + + \sa x() pos() +*/ + +/*! + \fn int QMouseEvent::globalX() const + + Returns the global x position of the mouse cursor at the time of + the event. + + \sa globalY() globalPos() +*/ + +/*! + \fn int QMouseEvent::globalY() const + + Returns the global y position of the mouse cursor at the time of + the event. + + \sa globalX() globalPos() +*/ + +/*! + \fn Qt::MouseButton QMouseEvent::button() const + + Returns the button that caused the event. + + Note that the returned value is always Qt::NoButton for mouse + move events. + + \sa buttons() Qt::MouseButton +*/ + +/*! + \fn Qt::MouseButton QMouseEvent::buttons() const + + Returns the button state when the event was generated. The button + state is a combination of Qt::LeftButton, Qt::RightButton, + Qt::MidButton using the OR operator. For mouse move events, + this is all buttons that are pressed down. For mouse press and + double click events this includes the button that caused the + event. For mouse release events this excludes the button that + caused the event. + + \sa button() Qt::MouseButton +*/ + + +/*! + \fn Qt::ButtonState QMouseEvent::state() const + + Returns the button state immediately before the event was + generated. The button state is a combination of mouse buttons + (see Qt::ButtonState) and keyboard modifiers (Qt::MouseButtons). + + Use buttons() and/or modifiers() instead. Be aware that buttons() + return the state immediately \e after the event was generated. +*/ + +/*! + \fn Qt::ButtonState QMouseEvent::stateAfter() const + + Returns the button state immediately after the event was + generated. The button state is a combination of mouse buttons + (see Qt::ButtonState) and keyboard modifiers (Qt::MouseButtons). + + Use buttons() and/or modifiers() instead. +*/ + +/*! + \class QHoverEvent + \ingroup events + + \brief The QHoverEvent class contains parameters that describe a mouse event. + + Mouse events occur when a mouse cursor is moved into, out of, or within a + widget, and if the widget has the Qt::WA_Hover attribute. + + The function pos() gives the current cursor position, while oldPos() gives + the old mouse position. + + There are a few similarities between the events QEvent::HoverEnter + and QEvent::HoverLeave, and the events QEvent::Enter and QEvent::Leave. + However, they are slightly different because we do an update() in the event + handler of HoverEnter and HoverLeave. + + QEvent::HoverMove is also slightly different from QEvent::MouseMove. Let us + consider a top-level window A containing a child B which in turn contains a + child C (all with mouse tracking enabled): + + \image hoverevents.png + + Now, if you move the cursor from the top to the bottom in the middle of A, + you will get the following QEvent::MouseMove events: + + \list 1 + \o A::MouseMove + \o B::MouseMove + \o C::MouseMove + \endlist + + You will get the same events for QEvent::HoverMove, except that the event + always propagates to the top-level regardless whether the event is accepted + or not. It will only stop propagating with the Qt::WA_NoMousePropagation + attribute. + + In this case the events will occur in the following way: + + \list 1 + \o A::HoverMove + \o A::HoverMove, B::HoverMove + \o A::HoverMove, B::HoverMove, C::HoverMove + \endlist + +*/ + +/*! + \fn const QPoint &QHoverEvent::pos() const + + Returns the position of the mouse cursor, relative to the widget + that received the event. + + On QEvent::HoverLeave events, this position will always be + QPoint(-1, -1). + + \sa oldPos() +*/ + +/*! + \fn const QPoint &QHoverEvent::oldPos() const + + Returns the previous position of the mouse cursor, relative to the widget + that received the event. If there is no previous position, oldPos() will + return the same position as pos(). + + On QEvent::HoverEnter events, this position will always be + QPoint(-1, -1). + + \sa pos() +*/ + +/*! + Constructs a hover event object. + + The \a type parameter must be QEvent::HoverEnter, + QEvent::HoverLeave, or QEvent::HoverMove. + + The \a pos is the current mouse cursor's position relative to the + receiving widget, while \a oldPos is the previous mouse cursor's + position relative to the receiving widget. +*/ +QHoverEvent::QHoverEvent(Type type, const QPoint &pos, const QPoint &oldPos) + : QEvent(type), p(pos), op(oldPos) +{ +} + +/*! + \internal +*/ +QHoverEvent::~QHoverEvent() +{ +} + + +/*! + \class QWheelEvent + \brief The QWheelEvent class contains parameters that describe a wheel event. + + \ingroup events + + Wheel events are sent to the widget under the mouse cursor, but + if that widget does not handle the event they are sent to the + focus widget. The rotation distance is provided by delta(). + The functions pos() and globalPos() return the mouse cursor's + location at the time of the event. + + A wheel event contains a special accept flag that indicates + whether the receiver wants the event. You should call ignore() if + you do not handle the wheel event; this ensures that it will be + sent to the parent widget. + + The QWidget::setEnabled() function can be used to enable or + disable mouse and keyboard events for a widget. + + The event handler QWidget::wheelEvent() receives wheel events. + + \sa QMouseEvent QWidget::grabMouse() +*/ + +/*! + \fn Qt::MouseButtons QWheelEvent::buttons() const + + Returns the mouse state when the event occurred. +*/ + +/*! + \fn Qt::Orientation QWheelEvent::orientation() const + + Returns the wheel's orientation. +*/ + +/*! + Constructs a wheel event object. + + The position, \a pos, is the location of the mouse cursor within + the widget. The globalPos() is initialized to QCursor::pos() + which is usually, but not always, correct. + Use the other constructor if you need to specify the global + position explicitly. + + The \a buttons describe the state of the mouse buttons at the time + of the event, \a delta contains the rotation distance, + \a modifiers holds the keyboard modifier flags at the time of the + event, and \a orient holds the wheel's orientation. + + \sa pos() delta() state() +*/ +#ifndef QT_NO_WHEELEVENT +QWheelEvent::QWheelEvent(const QPoint &pos, int delta, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, + Qt::Orientation orient) + : QInputEvent(Wheel, modifiers), p(pos), d(delta), mouseState(buttons), o(orient) +{ + g = QCursor::pos(); +} + +/*! + \internal +*/ +QWheelEvent::~QWheelEvent() +{ +} + +#ifdef QT3_SUPPORT +/*! + Use one of the other constructors instead. +*/ +QWheelEvent::QWheelEvent(const QPoint &pos, int delta, int state, Qt::Orientation orient) + : QInputEvent(Wheel), p(pos), d(delta), o(orient) +{ + g = QCursor::pos(); + mouseState = Qt::MouseButtons(state & Qt::MouseButtonMask); + modState = Qt::KeyboardModifiers(state & (int)Qt::KeyButtonMask); +} +#endif + +/*! + Constructs a wheel event object. + + The \a pos provides the location of the mouse cursor + within the widget. The position in global coordinates is specified + by \a globalPos. \a delta contains the rotation distance, \a modifiers + holds the keyboard modifier flags at the time of the event, and + \a orient holds the wheel's orientation. + + \sa pos() globalPos() delta() state() +*/ +QWheelEvent::QWheelEvent(const QPoint &pos, const QPoint& globalPos, int delta, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, + Qt::Orientation orient) + : QInputEvent(Wheel, modifiers), p(pos), g(globalPos), d(delta), mouseState(buttons), o(orient) +{} + +#ifdef QT3_SUPPORT +/*! + Use one of the other constructors instead. +*/ +QWheelEvent::QWheelEvent(const QPoint &pos, const QPoint& globalPos, int delta, int state, + Qt::Orientation orient) + : QInputEvent(Wheel), p(pos), g(globalPos), d(delta), o(orient) +{ + mouseState = Qt::MouseButtons(state & Qt::MouseButtonMask); + modState = Qt::KeyboardModifiers(state & (int) Qt::KeyButtonMask); +} +#endif +#endif // QT_NO_WHEELEVENT + +/*! + \fn int QWheelEvent::delta() const + + Returns the distance that the wheel is rotated, in eighths of a + degree. A positive value indicates that the wheel was rotated + forwards away from the user; a negative value indicates that the + wheel was rotated backwards toward the user. + + Most mouse types work in steps of 15 degrees, in which case the + delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. + + However, some mice have finer-resolution wheels and send delta values + that are less than 120 units (less than 15 degrees). To support this + possibility, you can either cumulatively add the delta values from events + until the value of 120 is reached, then scroll the widget, or you can + partially scroll the widget in response to each wheel event. + + Example: + + \snippet doc/src/snippets/code/src_gui_kernel_qevent.cpp 0 +*/ + +/*! + \fn const QPoint &QWheelEvent::pos() const + + Returns the position of the mouse cursor relative to the widget + that received the event. + + If you move your widgets around in response to mouse events, + use globalPos() instead of this function. + + \sa x() y() globalPos() +*/ + +/*! + \fn int QWheelEvent::x() const + + Returns the x position of the mouse cursor, relative to the + widget that received the event. + + \sa y() pos() +*/ + +/*! + \fn int QWheelEvent::y() const + + Returns the y position of the mouse cursor, relative to the + widget that received the event. + + \sa x() pos() +*/ + + +/*! + \fn const QPoint &QWheelEvent::globalPos() const + + Returns the global position of the mouse pointer \e{at the time + of the event}. This is important on asynchronous window systems + such as X11; whenever you move your widgets around in response to + mouse events, globalPos() can differ a lot from the current + cursor position returned by QCursor::pos(). + + \sa globalX() globalY() +*/ + +/*! + \fn int QWheelEvent::globalX() const + + Returns the global x position of the mouse cursor at the time of + the event. + + \sa globalY() globalPos() +*/ + +/*! + \fn int QWheelEvent::globalY() const + + Returns the global y position of the mouse cursor at the time of + the event. + + \sa globalX() globalPos() +*/ + + +/*! \obsolete + \fn Qt::ButtonState QWheelEvent::state() const + + Returns the keyboard modifier flags at the time of the event. + + The returned value is a selection of the following values, + combined using the OR operator: Qt::ShiftButton, + Qt::ControlButton, and Qt::AltButton. +*/ + + +/*! + \class QKeyEvent + \brief The QKeyEvent class describes a key event. + + \ingroup events + + Key events are sent to the widget with keyboard input focus + when keys are pressed or released. + + A key event contains a special accept flag that indicates whether + the receiver will handle the key event. You should call ignore() + if the key press or release event is not handled by your widget. + A key event is propagated up the parent widget chain until a + widget accepts it with accept() or an event filter consumes it. + Key events for multimedia keys are ignored by default. You should + call accept() if your widget handles those events. + + The QWidget::setEnable() function can be used to enable or disable + mouse and keyboard events for a widget. + + The event handlers QWidget::keyPressEvent(), QWidget::keyReleaseEvent(), + QGraphicsItem::keyPressEvent() and QGraphicsItem::keyReleaseEvent() + receive key events. + + \sa QFocusEvent, QWidget::grabKeyboard() +*/ + +/*! + Constructs a key event object. + + The \a type parameter must be QEvent::KeyPress, QEvent::KeyRelease, + or QEvent::ShortcutOverride. + + Int \a key is the code for the Qt::Key that the event loop should listen + for. If \a key is 0, the event is not a result of a known key; for + example, it may be the result of a compose sequence or keyboard macro. + The \a modifiers holds the keyboard modifiers, and the given \a text + is the Unicode text that the key generated. If \a autorep is true, + isAutoRepeat() will be true. \a count is the number of keys involved + in the event. +*/ +QKeyEvent::QKeyEvent(Type type, int key, Qt::KeyboardModifiers modifiers, const QString& text, + bool autorep, ushort count) + : QInputEvent(type, modifiers), txt(text), k(key), c(count), autor(autorep) +{ +} + +/*! + \internal +*/ +QKeyEvent::~QKeyEvent() +{ +} + +/*! + \internal +*/ +QKeyEvent *QKeyEvent::createExtendedKeyEvent(Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text, bool autorep, ushort count) +{ + return new QKeyEventEx(type, key, modifiers, text, autorep, count, + nativeScanCode, nativeVirtualKey, nativeModifiers); +} + +/*! + \fn bool QKeyEvent::hasExtendedInfo() const + \internal +*/ + +/*! + \since 4.2 + + Returns the native scan code of the key event. If the key event + does not contain this data 0 is returned. + + Note: The native scan code may be 0, even if the key event contains + extended information. + + Note: On Mac OS/X, this function is not useful, because there is no + way to get the scan code from Carbon or Cocoa. The function always + returns 1 (or 0 in the case explained above). +*/ +quint32 QKeyEvent::nativeScanCode() const +{ + return (reinterpret_cast<const QKeyEvent*>(d) != this + ? 0 : reinterpret_cast<const QKeyEventEx*>(this)->nScanCode); +} + +/*! + \since 4.2 + + Returns the native virtual key, or key sym of the key event. + If the key event does not contain this data 0 is returned. + + Note: The native virtual key may be 0, even if the key event contains extended information. +*/ +quint32 QKeyEvent::nativeVirtualKey() const +{ + return (reinterpret_cast<const QKeyEvent*>(d) != this + ? 0 : reinterpret_cast<const QKeyEventEx*>(this)->nVirtualKey); +} + +/*! + \since 4.2 + + Returns the native modifiers of a key event. + If the key event does not contain this data 0 is returned. + + Note: The native modifiers may be 0, even if the key event contains extended information. +*/ +quint32 QKeyEvent::nativeModifiers() const +{ + return (reinterpret_cast<const QKeyEvent*>(d) != this + ? 0 : reinterpret_cast<const QKeyEventEx*>(this)->nModifiers); +} + +/*! + \internal + Creates an extended key event object, which in addition to the normal key event data, also + contains the native scan code, virtual key and modifiers. This extra data is used by the + shortcut system, to determine which shortcuts to trigger. +*/ +QKeyEventEx::QKeyEventEx(Type type, int key, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorep, ushort count, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers) + : QKeyEvent(type, key, modifiers, text, autorep, count), + nScanCode(nativeScanCode), nVirtualKey(nativeVirtualKey), nModifiers(nativeModifiers) +{ + d = reinterpret_cast<QEventPrivate*>(this); +} + +/*! + \internal + Creates a copy of an other extended key event. +*/ +QKeyEventEx::QKeyEventEx(const QKeyEventEx &other) + : QKeyEvent(QEvent::Type(other.t), other.k, other.modState, other.txt, other.autor, other.c), + nScanCode(other.nScanCode), nVirtualKey(other.nVirtualKey), nModifiers(other.nModifiers) +{ + d = reinterpret_cast<QEventPrivate*>(this); +} + +/*! + \internal +*/ +QKeyEventEx::~QKeyEventEx() +{ +} + +/*! + \fn int QKeyEvent::key() const + + Returns the code of the key that was pressed or released. + + See \l Qt::Key for the list of keyboard codes. These codes are + independent of the underlying window system. Note that this + function does not distinguish between capital and non-capital + letters, use the text() function (returning the Unicode text the + key generated) for this purpose. + + A value of either 0 or Qt::Key_unknown means that the event is not + the result of a known key; for example, it may be the result of + a compose sequence, a keyboard macro, or due to key event + compression. + + \sa Qt::WA_KeyCompression +*/ + +/*! + \fn QString QKeyEvent::text() const + + Returns the Unicode text that this key generated. The text + returned can be an empty string in cases + where modifier keys, such as Shift, Control, Alt, and Meta, + are being pressed or released. In such cases key() will contain + a valid value. + + \sa Qt::WA_KeyCompression +*/ + +/*! + Returns the keyboard modifier flags that existed immediately + after the event occurred. + + \warning This function cannot always be trusted. The user can + confuse it by pressing both \key{Shift} keys simultaneously and + releasing one of them, for example. + + \sa QApplication::keyboardModifiers() +*/ +//###### We must check with XGetModifierMapping +Qt::KeyboardModifiers QKeyEvent::modifiers() const +{ + if (key() == Qt::Key_Shift) + return Qt::KeyboardModifiers(QInputEvent::modifiers()^Qt::ShiftModifier); + if (key() == Qt::Key_Control) + return Qt::KeyboardModifiers(QInputEvent::modifiers()^Qt::ControlModifier); + if (key() == Qt::Key_Alt) + return Qt::KeyboardModifiers(QInputEvent::modifiers()^Qt::AltModifier); + if (key() == Qt::Key_Meta) + return Qt::KeyboardModifiers(QInputEvent::modifiers()^Qt::MetaModifier); + return QInputEvent::modifiers(); +} + +#ifndef QT_NO_SHORTCUT +/*! + \fn bool QKeyEvent::matches(QKeySequence::StandardKey key) const + \since 4.2 + + Returns true if the key event matches the given standard \a key; + otherwise returns false. +*/ +bool QKeyEvent::matches(QKeySequence::StandardKey matchKey) const +{ + uint searchkey = (modifiers() | key()) & ~(Qt::KeypadModifier); //The keypad modifier should not make a difference + uint platform = QApplicationPrivate::currentPlatform(); + +#ifdef Q_WS_MAC + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + uint oldSearchKey = searchkey; + searchkey &= ~(Qt::ControlModifier | Qt::MetaModifier); + if (oldSearchKey & Qt::ControlModifier) + searchkey |= Qt::MetaModifier; + if (oldSearchKey & Qt::MetaModifier) + searchkey |= Qt::ControlModifier; + } +#endif + + uint N = QKeySequencePrivate::numberOfKeyBindings; + int first = 0; + int last = N - 1; + + while (first <= last) { + int mid = (first + last) / 2; + QKeyBinding midVal = QKeySequencePrivate::keyBindings[mid]; + + if (searchkey > midVal.shortcut){ + first = mid + 1; // Search in top half + } + else if (searchkey < midVal.shortcut){ + last = mid - 1; // Search in bottom half + } + else { + //found correct shortcut value, now we must check for platform match + if ((midVal.platform & platform) && (midVal.standardKey == matchKey)) { + return true; + } else { //We may have several equal values for different platforms, so we must search in both directions + + //search forward + for ( unsigned int i = mid + 1 ; i < N - 1 ; ++i) { + QKeyBinding current = QKeySequencePrivate::keyBindings[i]; + if (current.shortcut != searchkey) + break; + else if (current.platform & platform && current.standardKey == matchKey) + return true; + } + + //search back + for ( int i = mid - 1 ; i >= 0 ; --i) { + QKeyBinding current = QKeySequencePrivate::keyBindings[i]; + if (current.shortcut != searchkey) + break; + else if (current.platform & platform && current.standardKey == matchKey) + return true; + } + return false; //we could not find it among the matching keySequences + } + } + } + return false; //we could not find matching keySequences at all +} +#endif // QT_NO_SHORTCUT + + +/*! + \fn bool QKeyEvent::isAutoRepeat() const + + Returns true if this event comes from an auto-repeating key; + returns false if it comes from an initial key press. + + Note that if the event is a multiple-key compressed event that is + partly due to auto-repeat, this function could return either true + or false indeterminately. +*/ + +/*! + \fn int QKeyEvent::count() const + + Returns the number of keys involved in this event. If text() + is not empty, this is simply the length of the string. + + \sa Qt::WA_KeyCompression +*/ + +#ifdef QT3_SUPPORT +/*! + \fn QKeyEvent::QKeyEvent(Type type, int key, int ascii, + int modifiers, const QString &text, + bool autorep, ushort count) + + Use one of the other constructors instead. +*/ + +/*! + \fn int QKeyEvent::ascii() const + + Use text() instead. +*/ + +/*! + \fn Qt::ButtonState QKeyEvent::state() const + + Use QInputEvent::modifiers() instead. +*/ + +/*! + \fn Qt::ButtonState QKeyEvent::stateAfter() const + + Use modifiers() instead. +*/ +#endif + +/*! + \class QFocusEvent + \brief The QFocusEvent class contains event parameters for widget focus + events. + + \ingroup events + + Focus events are sent to widgets when the keyboard input focus + changes. Focus events occur due to mouse actions, key presses + (such as \gui{Tab} or \gui{Backtab}), the window system, popup + menus, keyboard shortcuts, or other application-specific reasons. + The reason for a particular focus event is returned by reason() + in the appropriate event handler. + + The event handlers QWidget::focusInEvent(), + QWidget::focusOutEvent(), QGraphicsItem::focusInEvent and + QGraphicsItem::focusOutEvent() receive focus events. + + \sa QWidget::setFocus(), QWidget::setFocusPolicy(), {Keyboard Focus} +*/ + +/*! + Constructs a focus event object. + + The \a type parameter must be either QEvent::FocusIn or + QEvent::FocusOut. The \a reason describes the cause of the change + in focus. +*/ +QFocusEvent::QFocusEvent(Type type, Qt::FocusReason reason) + : QEvent(type), m_reason(reason) +{} + +/*! + \internal +*/ +QFocusEvent::~QFocusEvent() +{ +} + +// ### Qt 5: remove +/*! + \internal + */ +Qt::FocusReason QFocusEvent::reason() +{ + return m_reason; +} + +/*! + Returns the reason for this focus event. + */ +Qt::FocusReason QFocusEvent::reason() const +{ + return m_reason; +} + +/*! + \fn bool QFocusEvent::gotFocus() const + + Returns true if type() is QEvent::FocusIn; otherwise returns + false. +*/ + +/*! + \fn bool QFocusEvent::lostFocus() const + + Returns true if type() is QEvent::FocusOut; otherwise returns + false. +*/ + +#ifdef QT3_SUPPORT +/*! + \enum QFocusEvent::Reason + \compat + + Use Qt::FocusReason instead. + + \value Mouse Same as Qt::MouseFocusReason. + \value Tab Same as Qt::TabFocusReason. + \value Backtab Same as Qt::BacktabFocusReason. + \value MenuBar Same as Qt::MenuBarFocusReason. + \value ActiveWindow Same as Qt::ActiveWindowFocusReason + \value Other Same as Qt::OtherFocusReason + \value Popup Same as Qt::PopupFocusReason + \value Shortcut Same as Qt::ShortcutFocusReason +*/ +#endif + +/*! + \class QPaintEvent + \brief The QPaintEvent class contains event parameters for paint events. + + \ingroup events + + Paint events are sent to widgets that need to update themselves, + for instance when part of a widget is exposed because a covering + widget was moved. + + The event contains a region() that needs to be updated, and a + rect() that is the bounding rectangle of that region. Both are + provided because many widgets can't make much use of region(), + and rect() can be much faster than region().boundingRect(). + + \section1 Automatic Clipping + + Painting is clipped to region() during the processing of a paint + event. This clipping is performed by Qt's paint system and is + independent of any clipping that may be applied to a QPainter used to + draw on the paint device. + + As a result, the value returned by QPainter::clipRegion() on + a newly-constructed QPainter will not reflect the clip region that is + used by the paint system. + + \sa QPainter, QWidget::update(), QWidget::repaint(), + QWidget::paintEvent() +*/ + +/*! + \fn bool QPaintEvent::erased() const + \compat + + Returns true if the paint event region (or rectangle) has been + erased with the widget's background; otherwise returns false. + + Qt 4 \e always erases regions that require painting. The exception + to this rule is if the widget sets the Qt::WA_OpaquePaintEvent or + Qt::WA_NoSystemBackground attributes. If either one of those + attributes is set \e and the window system does not make use of + subwidget alpha composition (currently X11 and Windows, but this + may change), then the region is not erased. +*/ + +/*! + \fn void QPaintEvent::setErased(bool b) { m_erased = b; } + \internal +*/ + +/*! + Constructs a paint event object with the region that needs to + be updated. The region is specified by \a paintRegion. +*/ +QPaintEvent::QPaintEvent(const QRegion& paintRegion) + : QEvent(Paint), m_rect(paintRegion.boundingRect()), m_region(paintRegion), m_erased(false) +{} + +/*! + Constructs a paint event object with the rectangle that needs + to be updated. The region is specified by \a paintRect. +*/ +QPaintEvent::QPaintEvent(const QRect &paintRect) + : QEvent(Paint), m_rect(paintRect),m_region(paintRect), m_erased(false) +{} + + +#ifdef QT3_SUPPORT + /*! + Constructs a paint event object with both a \a paintRegion and a + \a paintRect, both of which represent the area of the widget that + needs to be updated. + +*/ +QPaintEvent::QPaintEvent(const QRegion &paintRegion, const QRect &paintRect) + : QEvent(Paint), m_rect(paintRect), m_region(paintRegion), m_erased(false) +{} +#endif + +/*! + \internal +*/ +QPaintEvent::~QPaintEvent() +{ +} + +/*! + \fn const QRect &QPaintEvent::rect() const + + Returns the rectangle that needs to be updated. + + \sa region() QPainter::setClipRect() +*/ + +/*! + \fn const QRegion &QPaintEvent::region() const + + Returns the region that needs to be updated. + + \sa rect() QPainter::setClipRegion() +*/ + + +QUpdateLaterEvent::QUpdateLaterEvent(const QRegion& paintRegion) + : QEvent(UpdateLater), m_region(paintRegion) +{ +} + +QUpdateLaterEvent::~QUpdateLaterEvent() +{ +} + +/*! + \class QMoveEvent + \brief The QMoveEvent class contains event parameters for move events. + + \ingroup events + + Move events are sent to widgets that have been moved to a new + position relative to their parent. + + The event handler QWidget::moveEvent() receives move events. + + \sa QWidget::move(), QWidget::setGeometry() +*/ + +/*! + Constructs a move event with the new and old widget positions, + \a pos and \a oldPos respectively. +*/ +QMoveEvent::QMoveEvent(const QPoint &pos, const QPoint &oldPos) + : QEvent(Move), p(pos), oldp(oldPos) +{} + +/*! + \internal +*/ +QMoveEvent::~QMoveEvent() +{ +} + +/*! + \fn const QPoint &QMoveEvent::pos() const + + Returns the new position of the widget. This excludes the window + frame for top level widgets. +*/ + +/*! + \fn const QPoint &QMoveEvent::oldPos() const + + Returns the old position of the widget. +*/ + + +/*! + \class QResizeEvent + \brief The QResizeEvent class contains event parameters for resize events. + + \ingroup events + + Resize events are sent to widgets that have been resized. + + The event handler QWidget::resizeEvent() receives resize events. + + \sa QWidget::resize() QWidget::setGeometry() +*/ + +/*! + Constructs a resize event with the new and old widget sizes, \a + size and \a oldSize respectively. +*/ +QResizeEvent::QResizeEvent(const QSize &size, const QSize &oldSize) + : QEvent(Resize), s(size), olds(oldSize) +{} + +/*! + \internal +*/ +QResizeEvent::~QResizeEvent() +{ +} + +/*! + \fn const QSize &QResizeEvent::size() const + + Returns the new size of the widget. This is the same as + QWidget::size(). +*/ + +/*! + \fn const QSize &QResizeEvent::oldSize() const + + Returns the old size of the widget. +*/ + + +/*! + \class QCloseEvent + \brief The QCloseEvent class contains parameters that describe a close event. + + \ingroup events + + Close events are sent to widgets that the user wants to close, + usually by choosing "Close" from the window menu, or by clicking + the \gui{X} title bar button. They are also sent when you call + QWidget::close() to close a widget programmatically. + + Close events contain a flag that indicates whether the receiver + wants the widget to be closed or not. When a widget accepts the + close event, it is hidden (and destroyed if it was created with + the Qt::WA_DeleteOnClose flag). If it refuses to accept the close + event nothing happens. (Under X11 it is possible that the window + manager will forcibly close the window; but at the time of writing + we are not aware of any window manager that does this.) + + The event handler QWidget::closeEvent() receives close events. The + default implementation of this event handler accepts the close + event. If you do not want your widget to be hidden, or want some + special handing, you should reimplement the event handler and + ignore() the event. + + The \l{mainwindows/application#close event handler}{closeEvent() in the + Application example} shows a close event handler that + asks whether to save a document before closing. + + If you want the widget to be deleted when it is closed, create it + with the Qt::WA_DeleteOnClose flag. This is very useful for + independent top-level windows in a multi-window application. + + \l{QObject}s emits the \l{QObject::destroyed()}{destroyed()} + signal when they are deleted. + + If the last top-level window is closed, the + QApplication::lastWindowClosed() signal is emitted. + + The isAccepted() function returns true if the event's receiver has + agreed to close the widget; call accept() to agree to close the + widget and call ignore() if the receiver of this event does not + want the widget to be closed. + + \sa QWidget::close(), QWidget::hide(), QObject::destroyed(), + QCoreApplication::exec(), QCoreApplication::quit(), + QApplication::lastWindowClosed() +*/ + +/*! + Constructs a close event object. + + \sa accept() +*/ +QCloseEvent::QCloseEvent() + : QEvent(Close) +{} + +/*! \internal +*/ +QCloseEvent::~QCloseEvent() +{ +} + +/*! + \class QIconDragEvent + \brief The QIconDragEvent class indicates that a main icon drag has begun. + + \ingroup events + + Icon drag events are sent to widgets when the main icon of a window + has been dragged away. On Mac OS X, this happens when the proxy + icon of a window is dragged off the title bar. + + It is normal to begin using drag and drop in response to this + event. + + \sa {Drag and Drop}, QMimeData, QDrag +*/ + +/*! + Constructs an icon drag event object with the accept flag set to + false. + + \sa accept() +*/ +QIconDragEvent::QIconDragEvent() + : QEvent(IconDrag) +{ ignore(); } + +/*! \internal */ +QIconDragEvent::~QIconDragEvent() +{ +} + +/*! + \class QContextMenuEvent + \brief The QContextMenuEvent class contains parameters that describe a context menu event. + + \ingroup events + + Context menu events are sent to widgets when a user performs + an action associated with opening a context menu. + The actions required to open context menus vary between platforms; + for example, on Windows, pressing the menu button or clicking the + right mouse button will cause this event to be sent. + + When this event occurs it is customary to show a QMenu with a + context menu, if this is relevant to the context. + + Context menu events contain a special accept flag that indicates + whether the receiver accepted the event. If the event handler does + not accept the event then, if possible, whatever triggered the event will be + handled as a regular input event. +*/ + +#ifndef QT_NO_CONTEXTMENU +/*! + Constructs a context menu event object with the accept parameter + flag set to false. + + The \a reason parameter must be QContextMenuEvent::Mouse or + QContextMenuEvent::Keyboard. + + The \a pos parameter specifies the mouse position relative to the + receiving widget. \a globalPos is the mouse position in absolute + coordinates. +*/ +QContextMenuEvent::QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos) + : QInputEvent(ContextMenu), p(pos), gp(globalPos), reas(reason) +{} + +/*! + Constructs a context menu event object with the accept parameter + flag set to false. + + The \a reason parameter must be QContextMenuEvent::Mouse or + QContextMenuEvent::Keyboard. + + The \a pos parameter specifies the mouse position relative to the + receiving widget. \a globalPos is the mouse position in absolute + coordinates. The \a modifiers holds the keyboard modifiers. +*/ +QContextMenuEvent::QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos, + Qt::KeyboardModifiers modifiers) + : QInputEvent(ContextMenu, modifiers), p(pos), gp(globalPos), reas(reason) +{} + +#ifdef QT3_SUPPORT +/*! + Constructs a context menu event with the given \a reason for the + position specified by \a pos in widget coordinates and \a globalPos + in global screen coordinates. \a dummy is ignored. +*/ +QContextMenuEvent::QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos, + int /* dummy */) + : QInputEvent(ContextMenu), p(pos), gp(globalPos), reas(reason) +{} +#endif + +/*! \internal */ +QContextMenuEvent::~QContextMenuEvent() +{ +} +/*! + Constructs a context menu event object with the accept parameter + flag set to false. + + The \a reason parameter must be QContextMenuEvent::Mouse or + QContextMenuEvent::Keyboard. + + The \a pos parameter specifies the mouse position relative to the + receiving widget. + + The globalPos() is initialized to QCursor::pos(), which may not be + appropriate. Use the other constructor to specify the global + position explicitly. +*/ +QContextMenuEvent::QContextMenuEvent(Reason reason, const QPoint &pos) + : QInputEvent(ContextMenu), p(pos), reas(reason) +{ + gp = QCursor::pos(); +} + +#ifdef QT3_SUPPORT +/*! + Constructs a context menu event with the given \a reason for the + position specified by \a pos in widget coordinates. \a dummy is + ignored. +*/ +QContextMenuEvent::QContextMenuEvent(Reason reason, const QPoint &pos, int /* dummy */) + : QInputEvent(ContextMenu), p(pos), reas(reason) +{ + gp = QCursor::pos(); +} + +Qt::ButtonState QContextMenuEvent::state() const +{ + return Qt::ButtonState(int(QApplication::keyboardModifiers())|QApplication::mouseButtons()); +} +#endif + +/*! + \fn const QPoint &QContextMenuEvent::pos() const + + Returns the position of the mouse pointer relative to the widget + that received the event. + + \sa x(), y(), globalPos() +*/ + +/*! + \fn int QContextMenuEvent::x() const + + Returns the x position of the mouse pointer, relative to the + widget that received the event. + + \sa y(), pos() +*/ + +/*! + \fn int QContextMenuEvent::y() const + + Returns the y position of the mouse pointer, relative to the + widget that received the event. + + \sa x(), pos() +*/ + +/*! + \fn const QPoint &QContextMenuEvent::globalPos() const + + Returns the global position of the mouse pointer at the time of + the event. + + \sa x(), y(), pos() +*/ + +/*! + \fn int QContextMenuEvent::globalX() const + + Returns the global x position of the mouse pointer at the time of + the event. + + \sa globalY(), globalPos() +*/ + +/*! + \fn int QContextMenuEvent::globalY() const + + Returns the global y position of the mouse pointer at the time of + the event. + + \sa globalX(), globalPos() +*/ +#endif // QT_NO_CONTEXTMENU + +/*! + \fn Qt::ButtonState QContextMenuEvent::state() const + + Returns the button state (a combination of mouse buttons + and keyboard modifiers) immediately before the event was + generated. + + The returned value is a selection of the following values, + combined with the OR operator: + Qt::LeftButton, Qt::RightButton, Qt::MidButton, + Qt::ShiftButton, Qt::ControlButton, and Qt::AltButton. +*/ + +/*! + \enum QContextMenuEvent::Reason + + This enum describes the reason why the event was sent. + + \value Mouse The mouse caused the event to be sent. Normally this + means the right mouse button was clicked, but this is platform + dependent. + + \value Keyboard The keyboard caused this event to be sent. On + Windows, this means the menu button was pressed. + + \value Other The event was sent by some other means (i.e. not by + the mouse or keyboard). +*/ + + +/*! + \fn QContextMenuEvent::Reason QContextMenuEvent::reason() const + + Returns the reason for this context event. +*/ + + +/*! + \class QInputMethodEvent + \brief The QInputMethodEvent class provides parameters for input method events. + + \ingroup events + + Input method events are sent to widgets when an input method is + used to enter text into a widget. Input methods are widely used + to enter text for languages with non-Latin alphabets. + + Note that when creating custom text editing widgets, the + Qt::WA_InputMethodEnabled window attribute must be set explicitly + (using the QWidget::setAttribute() function) in order to receive + input method events. + + The events are of interest to authors of keyboard entry widgets + who want to be able to correctly handle languages with complex + character input. Text input in such languages is usually a three + step process: + + \list 1 + \o \bold{Starting to Compose} + + When the user presses the first key on a keyboard, an input + context is created. This input context will contain a string + of the typed characters. + + \o \bold{Composing} + + With every new key pressed, the input method will try to create a + matching string for the text typed so far called preedit + string. While the input context is active, the user can only move + the cursor inside the string belonging to this input context. + + \o \bold{Completing} + + At some point, the user will activate a user interface component + (perhaps using a particular key) where they can choose from a + number of strings matching the text they have typed so far. The + user can either confirm their choice cancel the input; in either + case the input context will be closed. + \endlist + + QInputMethodEvent models these three stages, and transfers the + information needed to correctly render the intermediate result. A + QInputMethodEvent has two main parameters: preeditString() and + commitString(). The preeditString() parameter gives the currently + active preedit string. The commitString() parameter gives a text + that should get added to (or replace parts of) the text of the + editor widget. It usually is a result of the input operations and + has to be inserted to the widgets text directly before the preedit + string. + + If the commitString() should replace parts of the of the text in + the editor, replacementLength() will contain the number of + characters to be replaced. replacementStart() contains the position + at which characters are to be replaced relative from the start of + the preedit string. + + A number of attributes control the visual appearance of the + preedit string (the visual appearance of text outside the preedit + string is controlled by the widget only). The AttributeType enum + describes the different attributes that can be set. + + A class implementing QWidget::inputMethodEvent() or + QGraphicsItem::inputMethodEvent() should at least understand and + honor the \l TextFormat and \l Cursor attributes. + + Since input methods need to be able to query certain properties + from the widget or graphics item, subclasses must also implement + QWidget::inputMethodQuery() and QGraphicsItem::inputMethodQuery(), + respectively. + + When receiving an input method event, the text widget has to performs the + following steps: + + \list 1 + \o If the widget has selected text, the selected text should get + removed. + + \o Remove the text starting at replacementStart() with length + replacementLength() and replace it by the commitString(). If + replacementLength() is 0, replacementStart() gives the insertion + position for the commitString(). + + When doing replacement the area of the preedit + string is ignored, thus a replacement starting at -1 with a length + of 2 will remove the last character before the preedit string and + the first character afterwards, and insert the commit string + directly before the preedit string. + + If the widget implements undo/redo, this operation gets added to + the undo stack. + + \o If there is no current preedit string, insert the + preeditString() at the current cursor position; otherwise replace + the previous preeditString with the one received from this event. + + If the widget implements undo/redo, the preeditString() should not + influence the undo/redo stack in any way. + + The widget should examine the list of attributes to apply to the + preedit string. It has to understand at least the TextFormat and + Cursor attributes and render them as specified. + \endlist + + \sa QInputContext +*/ + +/*! + \enum QInputMethodEvent::AttributeType + + \value TextFormat + A QTextCharFormat for the part of the preedit string specified by + start and length. value contains a QVariant of type QTextFormat + specifying rendering of this part of the preedit string. There + should be at most one format for every part of the preedit + string. If several are specified for any character in the string the + behaviour is undefined. A conforming implementation has to at least + honor the backgroundColor, textColor and fontUnderline properties + of the format. + + \value Cursor If set, a cursor should be shown inside the preedit + string at position start. The length variable determines whether + the cursor is visible or not. If the length is 0 the cursor is + invisible. If value is a QVariant of type QColor this color will + be used for rendering the cursor, otherwise the color of the + surrounding text will be used. There should be at most one Cursor + attribute per event. If several are specified the behaviour is + undefined. + + \value Language + The variant contains a QLocale object specifying the language of a + certain part of the preedit string. There should be at most one + language set for every part of the preedit string. If several are + specified for any character in the string the behavior is undefined. + + \value Ruby + The ruby text for a part of the preedit string. There should be at + most one ruby text set for every part of the preedit string. If + several are specified for any character in the string the behaviour + is undefined. + + \value Selection + If set, the edit cursor should be moved to the specified position + in the editor text contents. In contrast with \c Cursor, this + attribute does not work on the preedit text, but on the surrounding + text. The cursor will be moved after the commit string has been + committed, and the preedit string will be located at the new edit + position. + The start position specifies the new position and the length + variable can be used to set a selection starting from that point. + The value is unused. + + \sa Attribute +*/ + +/*! + \class QInputMethodEvent::Attribute + \brief The QInputMethodEvent::Attribute class stores an input method attribute. +*/ + +/*! + \fn QInputMethodEvent::Attribute::Attribute(AttributeType type, int start, int length, QVariant value) + + Constructs an input method attribute. \a type specifies the type + of attribute, \a start and \a length the position of the + attribute, and \a value the value of the attribute. +*/ + +/*! + Constructs an event of type QEvent::InputMethod. The + attributes(), preeditString(), commitString(), replacementStart(), + and replacementLength() are initialized to default values. + + \sa setCommitString() +*/ +QInputMethodEvent::QInputMethodEvent() + : QEvent(QEvent::InputMethod), replace_from(0), replace_length(0) +{ +} + +/*! + Construcs an event of type QEvent::InputMethod. The + preedit text is set to \a preeditText, the attributes to + \a attributes. + + The commitString(), replacementStart(), and replacementLength() + values can be set using setCommitString(). + + \sa preeditString(), attributes() +*/ +QInputMethodEvent::QInputMethodEvent(const QString &preeditText, const QList<Attribute> &attributes) + : QEvent(QEvent::InputMethod), preedit(preeditText), attrs(attributes), + replace_from(0), replace_length(0) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QInputMethodEvent::QInputMethodEvent(const QInputMethodEvent &other) + : QEvent(QEvent::InputMethod), preedit(other.preedit), attrs(other.attrs), + commit(other.commit), replace_from(other.replace_from), replace_length(other.replace_length) +{ +} + +/*! + Sets the commit string to \a commitString. + + The commit string is the text that should get added to (or + replace parts of) the text of the editor widget. It usually is a + result of the input operations and has to be inserted to the + widgets text directly before the preedit string. + + If the commit string should replace parts of the of the text in + the editor, \a replaceLength specifies the number of + characters to be replaced. \a replaceFrom specifies the position + at which characters are to be replaced relative from the start of + the preedit string. + + \sa commitString(), replacementStart(), replacementLength() +*/ +void QInputMethodEvent::setCommitString(const QString &commitString, int replaceFrom, int replaceLength) +{ + commit = commitString; + replace_from = replaceFrom; + replace_length = replaceLength; +} + +/*! + \fn const QList<Attribute> &QInputMethodEvent::attributes() const + + Returns the list of attributes passed to the QInputMethodEvent + constructor. The attributes control the visual appearance of the + preedit string (the visual appearance of text outside the preedit + string is controlled by the widget only). + + \sa preeditString(), Attribute +*/ + +/*! + \fn const QString &QInputMethodEvent::preeditString() const + + Returns the preedit text, i.e. the text before the user started + editing it. + + \sa commitString(), attributes() +*/ + +/*! + \fn const QString &QInputMethodEvent::commitString() const + + Returns the text that should get added to (or replace parts of) + the text of the editor widget. It usually is a result of the + input operations and has to be inserted to the widgets text + directly before the preedit string. + + \sa setCommitString(), preeditString(), replacementStart(), replacementLength() +*/ + +/*! + \fn int QInputMethodEvent::replacementStart() const + + Returns the position at which characters are to be replaced relative + from the start of the preedit string. + + \sa replacementLength(), setCommitString() +*/ + +/*! + \fn int QInputMethodEvent::replacementLength() const + + Returns the number of characters to be replaced in the preedit + string. + + \sa replacementStart(), setCommitString() +*/ + +#ifndef QT_NO_TABLETEVENT + +/*! + \class QTabletEvent + \brief The QTabletEvent class contains parameters that describe a Tablet event. + + \ingroup events + + Tablet Events are generated from a Wacom tablet. Most of the time you will + want to deal with events from the tablet as if they were events from a + mouse; for example, you would retrieve the cursor position with x(), y(), + pos(), globalX(), globalY(), and globalPos(). In some situations you may + wish to retrieve the extra information provided by the tablet device + driver; for example, you might want to do subpixeling with higher + resolution coordinates or you may want to adjust color brightness based on + pressure. QTabletEvent allows you to read the pressure(), the xTilt(), and + yTilt(), as well as the type of device being used with device() (see + \l{TabletDevice}). It can also give you the minimum and maximum values for + each device's pressure and high resolution coordinates. + + A tablet event contains a special accept flag that indicates whether the + receiver wants the event. You should call QTabletEvent::accept() if you + handle the tablet event; otherwise it will be sent to the parent widget. + The exception are TabletEnterProximity and TabletLeaveProximity events, + these are only sent to QApplication and don't check whether or not they are + accepted. + + The QWidget::setEnabled() function can be used to enable or + disable mouse and keyboard events for a widget. + + The event handler QWidget::tabletEvent() receives all three types of + tablet events. Qt will first send a tabletEvent then, if it is not + accepted, it will send a mouse event. This allows applications that + don't utilize tablets to use a tablet like a mouse, while also + enabling those who want to use both tablets and mouses differently. + + \section1 Notes for X11 Users + + Qt uses the following hard-coded names to identify tablet + devices from the xorg.conf file on X11 (apart from IRIX): + 'stylus', 'pen', and 'eraser'. If the devices have other names, + they will not be picked up Qt. +*/ + +/*! + \enum QTabletEvent::TabletDevice + + This enum defines what type of device is generating the event. + + \value NoDevice No device, or an unknown device. + \value Puck A Puck (a device that is similar to a flat mouse with + a transparent circle with cross-hairs). + \value Stylus A Stylus. + \value Airbrush An airbrush + \value FourDMouse A 4D Mouse. + \value RotationStylus A special stylus that also knows about rotation + (a 6D stylus). \since 4.1 + \omitvalue XFreeEraser +*/ + +/*! + \enum QTabletEvent::PointerType + + This enum defines what type of point is generating the event. + + \value UnknownPointer An unknown device. + \value Pen Tip end of a stylus-like device (the narrow end of the pen). + \value Cursor Any puck-like device. + \value Eraser Eraser end of a stylus-like device (the broad end of the pen). + + \sa pointerType() +*/ + +/*! + Construct a tablet event of the given \a type. + + The \a pos parameter indicates where the event occurred in the + widget; \a globalPos is the corresponding position in absolute + coordinates. The \a hiResGlobalPos contains a high resolution + measurement of the position. + + \a pressure contains the pressure exerted on the \a device. + + \a pointerType describes the type of pen that is being used. + + \a xTilt and \a yTilt contain the device's degree of tilt from the + x and y axes respectively. + + \a keyState specifies which keyboard modifiers are pressed (e.g., + \key{Ctrl}). + + The \a uniqueID parameter contains the unique ID for the current device. + + The \a z parameter contains the coordinate of the device on the tablet, this + is usually given by a wheel on 4D mouse. If the device does not support a + Z-axis, pass zero here. + + The \a tangentialPressure parameter contins the tangential pressure of an air + brush. If the device does not support tangential pressure, pass 0 here. + + \a rotation contains the device's rotation in degrees. 4D mice support + rotation. If the device does not support rotation, pass 0 here. + + \sa pos() globalPos() device() pressure() xTilt() yTilt() uniqueId(), rotation(), tangentialPressure(), z() +*/ + +QTabletEvent::QTabletEvent(Type type, const QPoint &pos, const QPoint &globalPos, + const QPointF &hiResGlobalPos, int device, int pointerType, + qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, + qreal rotation, int z, Qt::KeyboardModifiers keyState, qint64 uniqueID) + : QInputEvent(type, keyState), + mPos(pos), + mGPos(globalPos), + mHiResGlobalPos(hiResGlobalPos), + mDev(device), + mPointerType(pointerType), + mXT(xTilt), + mYT(yTilt), + mZ(z), + mPress(pressure), + mTangential(tangentialPressure), + mRot(rotation), + mUnique(uniqueID), + mExtra(0) +{ +} + +/*! + \internal +*/ +QTabletEvent::~QTabletEvent() +{ +} + +/*! + \fn TabletDevices QTabletEvent::device() const + + Returns the type of device that generated the event. + + \sa TabletDevice +*/ + +/*! + \fn PointerType QTabletEvent::pointerType() const + + Returns the type of point that generated the event. +*/ + +/*! + \fn qreal QTabletEvent::tangentialPressure() const + + Returns the tangential pressure for the device. This is typically given by a finger + wheel on an airbrush tool. The range is from -1.0 to 1.0. 0.0 indicates a + neutral position. Current airbrushes can only move in the positive + direction from the neutrual position. If the device does not support + tangential pressure, this value is always 0.0. + + \sa pressure() +*/ + +/*! + \fn qreal QTabletEvent::rotation() const + + Returns the rotation of the current device in degress. This is usually + given by a 4D Mouse. If the device doesn't support rotation this value is + always 0.0. + +*/ + +/*! + \fn qreal QTabletEvent::pressure() const + + Returns the pressure for the device. 0.0 indicates that the stylus is not + on the tablet, 1.0 indicates the maximum amount of pressure for the stylus. + + \sa tangentialPressure() +*/ + +/*! + \fn int QTabletEvent::xTilt() const + + Returns the angle between the device (a pen, for example) and the + perpendicular in the direction of the x axis. + Positive values are towards the tablet's physical right. The angle + is in the range -60 to +60 degrees. + + \img qtabletevent-tilt.png + + \sa yTilt() +*/ + +/*! + \fn int QTabletEvent::yTilt() const + + Returns the angle between the device (a pen, for example) and the + perpendicular in the direction of the y axis. + Positive values are towards the bottom of the tablet. The angle is + within the range -60 to +60 degrees. + + \sa xTilt() +*/ + +/*! + \fn const QPoint &QTabletEvent::pos() const + + Returns the position of the device, relative to the widget that + received the event. + + If you move widgets around in response to mouse events, use + globalPos() instead of this function. + + \sa x() y() globalPos() +*/ + +/*! + \fn int QTabletEvent::x() const + + Returns the x position of the device, relative to the widget that + received the event. + + \sa y() pos() +*/ + +/*! + \fn int QTabletEvent::y() const + + Returns the y position of the device, relative to the widget that + received the event. + + \sa x() pos() +*/ + +/*! + \fn int QTabletEvent::z() const + + Returns the z position of the device. Typically this is represented by a + wheel on a 4D Mouse. If the device does not support a Z-axis, this value is + always zero. This is \bold not the same as pressure. + + \sa pressure() +*/ + +/*! + \fn const QPoint &QTabletEvent::globalPos() const + + Returns the global position of the device \e{at the time of the + event}. This is important on asynchronous windows systems like X11; + whenever you move your widgets around in response to mouse events, + globalPos() can differ significantly from the current position + QCursor::pos(). + + \sa globalX() globalY() hiResGlobalPos() +*/ + +/*! + \fn int QTabletEvent::globalX() const + + Returns the global x position of the mouse pointer at the time of + the event. + + \sa globalY() globalPos() hiResGlobalX() +*/ + +/*! + \fn int QTabletEvent::globalY() const + + Returns the global y position of the tablet device at the time of + the event. + + \sa globalX() globalPos() hiResGlobalY() +*/ + +/*! + \fn qint64 QTabletEvent::uniqueId() const + + Returns a unique ID for the current device, making it possible + to differentiate between multiple devices being used at the same + time on the tablet. + + Support of this feature is dependent on the tablet. + + Values for the same device may vary from OS to OS. + + Later versions of the Wacom driver for Linux will now report + the ID information. If you have a tablet that supports unique ID + and are not getting the information on Linux, consider upgrading + your driver. + + As of Qt 4.2, the unique ID is the same regardless of the orientation + of the pen. Earlier versions would report a different value when using + the eraser-end versus the pen-end of the stylus on some OS's. + + \sa pointerType() +*/ + +/*! + \fn const QPointF &QTabletEvent::hiResGlobalPos() const + + The high precision coordinates delivered from the tablet expressed. + Sub pixeling information is in the fractional part of the QPointF. + + \sa globalPos() hiResGlobalX() hiResGlobalY() +*/ + +/*! + \fn qreal &QTabletEvent::hiResGlobalX() const + + The high precision x position of the tablet device. +*/ + +/*! + \fn qreal &QTabletEvent::hiResGlobalY() const + + The high precision y position of the tablet device. +*/ + +#endif // QT_NO_TABLETEVENT + +#ifndef QT_NO_DRAGANDDROP +/*! + Creates a QDragMoveEvent of the required \a type indicating + that the mouse is at position \a pos given within a widget. + + The mouse and keyboard states are specified by \a buttons and + \a modifiers, and the \a actions describe the types of drag + and drop operation that are possible. + The drag data is passed as MIME-encoded information in \a data. + + \warning Do not attempt to create a QDragMoveEvent yourself. + These objects rely on Qt's internal state. +*/ +QDragMoveEvent::QDragMoveEvent(const QPoint& pos, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type) + : QDropEvent(pos, actions, data, buttons, modifiers, type) + , rect(pos, QSize(1, 1)) +{} + +/*! + Destroys the event. +*/ +QDragMoveEvent::~QDragMoveEvent() +{ +} + +/*! + \fn void QDragMoveEvent::accept(bool y) + + Calls setAccepted(\a y) instead. +*/ + +/*! + \fn void QDragMoveEvent::accept(const QRect &rectangle) + + The same as accept(), but also notifies that future moves will + also be acceptable if they remain within the \a rectangle + given on the widget. This can improve performance, but may + also be ignored by the underlying system. + + If the rectangle is empty, drag move events will be sent + continuously. This is useful if the source is scrolling in a + timer event. +*/ + +/*! + \fn void QDragMoveEvent::accept() + + \overload + + Calls QDropEvent::accept(). +*/ + +/*! + \fn void QDragMoveEvent::ignore() + + \overload + + Calls QDropEvent::ignore(). +*/ + +/*! + \fn void QDragMoveEvent::ignore(const QRect &rectangle) + + The opposite of the accept(const QRect&) function. + Moves within the \a rectangle are not acceptable, and will be + ignored. +*/ + +/*! + \fn QRect QDragMoveEvent::answerRect() const + + Returns the rectangle in the widget where the drop will occur if accepted. + You can use this information to restrict drops to certain places on the + widget. +*/ + + +/*! + \class QDropEvent + \ingroup events + \ingroup draganddrop + + \brief The QDropEvent class provides an event which is sent when a + drag and drop action is completed. + + When a widget \l{QWidget::setAcceptDrops()}{accepts drop events}, it will + receive this event if it has accepted the most recent QDragEnterEvent or + QDragMoveEvent sent to it. + + The drop event contains a proposed action, available from proposedAction(), for + the widget to either accept or ignore. If the action can be handled by the + widget, you should call the acceptProposedAction() function. Since the + proposed action can be a combination of \l Qt::DropAction values, it may be + useful to either select one of these values as a default action or ask + the user to select their preferred action. + + If the proposed drop action is not suitable, perhaps because your custom + widget does not support that action, you can replace it with any of the + \l{possibleActions()}{possible drop actions} by calling setDropAction() + with your preferred action. If you set a value that is not present in the + bitwise OR combination of values returned by possibleActions(), the default + copy action will be used. Once a replacement drop action has been set, call + accept() instead of acceptProposedAction() to complete the drop operation. + + The mimeData() function provides the data dropped on the widget in a QMimeData + object. This contains information about the MIME type of the data in addition to + the data itself. + + \sa QMimeData, QDrag, {Drag and Drop} +*/ + +/*! + \fn const QMimeData *QDropEvent::mimeData() const + + Returns the data that was dropped on the widget and its associated MIME + type information. +*/ + +/*! + Constructs a drop event of a certain \a type corresponding to a + drop at the point specified by \a pos in the destination widget's + coordinate system. + + The \a actions indicate which types of drag and drop operation can + be performed, and the drag data is stored as MIME-encoded data in \a data. + + The states of the mouse buttons and keyboard modifiers at the time of + the drop are specified by \a buttons and \a modifiers. +*/ // ### pos is in which coordinate system? +QDropEvent::QDropEvent(const QPoint& pos, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type) + : QEvent(type), p(pos), mouseState(buttons), + modState(modifiers), act(actions), + mdata(data) +{ + default_action = QDragManager::self()->defaultAction(act, modifiers); + drop_action = default_action; + ignore(); +} + +/*! \internal */ +QDropEvent::~QDropEvent() +{ +} + +/*! + \compat + Returns a byte array containing the drag's data, in \a format. + + data() normally needs to get the data from the drag source, which + is potentially very slow, so it's advisable to call this function + only if you're sure that you will need the data in that + particular \a format. + + The resulting data will have a size of 0 if the format was not + available. + + \sa format() QByteArray::size() +*/ + +QByteArray QDropEvent::encodedData(const char *format) const +{ + return mdata->data(QLatin1String(format)); +} + +/*! + \compat + Returns a string describing one of the available data types for + this drag. Common examples are "text/plain" and "image/gif". + If \a n is less than zero or greater than the number of available + data types, format() returns 0. + + This function is provided mainly for debugging. Most drop targets + will use provides(). + + \sa data() provides() +*/ + +const char* QDropEvent::format(int n) const +{ + if (fmts.isEmpty()) { + QStringList formats = mdata->formats(); + for (int i = 0; i < formats.size(); ++i) + fmts.append(formats.at(i).toLatin1()); + } + if (n < 0 || n >= fmts.size()) + return 0; + return fmts.at(n).constData(); +} + +/*! + \compat + Returns true if this event provides format \a mimeType; otherwise + returns false. + + \sa data() +*/ + +bool QDropEvent::provides(const char *mimeType) const +{ + return mdata->formats().contains(QLatin1String(mimeType)); +} + +/*! + If the source of the drag operation is a widget in this + application, this function returns that source; otherwise it + returns 0. The source of the operation is the first parameter to + the QDrag object used instantiate the drag. + + This is useful if your widget needs special behavior when dragging + to itself. + + \sa QDrag::QDrag() +*/ +QWidget* QDropEvent::source() const +{ + QDragManager *manager = QDragManager::self(); + return manager ? manager->source() : 0; +} + + +void QDropEvent::setDropAction(Qt::DropAction action) +{ + if (!(action & act) && action != Qt::IgnoreAction) + action = default_action; + drop_action = action; +} + +/*! + \fn const QPoint& QDropEvent::pos() const + + Returns the position where the drop was made. +*/ + +/*! + \fn Qt::MouseButtons QDropEvent::mouseButtons() const + + Returns the mouse buttons that are pressed.. +*/ + +/*! + \fn Qt::KeyboardModifiers QDropEvent::keyboardModifiers() const + + Returns the modifier keys that are pressed. +*/ + +/*! + \fn void QDropEvent::accept() + \internal +*/ + +/*! + \fn void QDropEvent::accept(bool accept) + + Call setAccepted(\a accept) instead. +*/ + +/*! + \fn void QDropEvent::acceptAction(bool accept = true) + + Call this to indicate that the action described by action() is + accepted (i.e. if \a accept is true, which is the default), not merely + the default copy action. If you call acceptAction(true), there is + no need to also call accept(true). +*/ + +/*! + \enum QDropEvent::Action + \compat + + When a drag and drop action is completed, the target is expected + to perform an action on the data provided by the source. This + will be one of the following: + + \value Copy The default action. The source simply uses the data + provided in the operation. + \value Link The source should somehow create a link to the + location specified by the data. + \value Move The source should somehow move the object from the + location specified by the data to a new location. + \value Private The target has special knowledge of the MIME type, + which the source should respond to in a similar way to + a Copy. + \value UserAction The source and target can co-operate using + special actions. This feature is not currently + supported. + + The Link and Move actions only makes sense if the data is a + reference, for example, text/uri-list file lists (see QUriDrag). +*/ + +/*! + \fn void QDropEvent::setDropAction(Qt::DropAction action) + + Sets the \a action to be performed on the data by the target. + Use this to override the \l{proposedAction()}{proposed action} + with one of the \l{possibleActions()}{possible actions}. + + If you set a drop action that is not one of the possible actions, the + drag and drop operation will default to a copy operation. + + Once you have supplied a replacement drop action, call accept() + instead of acceptProposedAction(). + + \sa dropAction() +*/ + +/*! + \fn Qt::DropAction QDropEvent::dropAction() const + + Returns the action to be performed on the data by the target. This may be + different from the action supplied in proposedAction() if you have called + setDropAction() to explicitly choose a drop action. + + \sa setDropAction() +*/ + +/*! + \fn Qt::DropActions QDropEvent::possibleActions() const + + Returns an OR-combination of possible drop actions. + + \sa dropAction() +*/ + +/*! + \fn Qt::DropAction QDropEvent::proposedAction() const + + Returns the proposed drop action. + + \sa dropAction() +*/ + +/*! + \fn void QDropEvent::acceptProposedAction() + + Sets the drop action to be the proposed action. + + \sa setDropAction(), proposedAction(), {QEvent::accept()}{accept()} +*/ + +#ifdef QT3_SUPPORT +/*! + Use dropAction() instead. + + The table below shows the correspondance between the return type + of action() and the return type of dropAction(). + + \table + \header \i Old enum value \i New enum value + \row \i QDropEvent::Copy \i Qt::CopyAction + \row \i QDropEvent::Move \i Qt::MoveAction + \row \i QDropEvent::Link \i Qt::LinkAction + \row \i other \i Qt::CopyAction + \endtable +*/ + +QT3_SUPPORT QDropEvent::Action QDropEvent::action() const +{ + switch(drop_action) { + case Qt::CopyAction: + return Copy; + case Qt::MoveAction: + return Move; + case Qt::LinkAction: + return Link; + default: + return Copy; + } +} +#endif + +/*! + \fn void QDropEvent::setPoint(const QPoint &point) + \compat + + Sets the drop to happen at the given \a point. You do not normally + need to use this as it will be set internally before your widget + receives the drop event. +*/ // ### here too - what coordinate system? + + +/*! + \class QDragEnterEvent + \brief The QDragEnterEvent class provides an event which is sent + to a widget when a drag and drop action enters it. + + \ingroup events + \ingroup draganddrop + + A widget must accept this event in order to receive the \l + {QDragMoveEvent}{drag move events} that are sent while the drag + and drop action is in progress. The drag enter event is always + immediately followed by a drag move event. + + QDragEnterEvent inherits most of its functionality from + QDragMoveEvent, which in turn inherits most of its functionality + from QDropEvent. + + \sa QDragLeaveEvent, QDragMoveEvent, QDropEvent +*/ + +/*! + Constructs a QDragEnterEvent that represents a drag entering a + widget at the given \a point with mouse and keyboard states specified by + \a buttons and \a modifiers. + + The drag data is passed as MIME-encoded information in \a data, and the + specified \a actions describe the possible types of drag and drop + operation that can be performed. + + \warning Do not create a QDragEnterEvent yourself since these + objects rely on Qt's internal state. +*/ +QDragEnterEvent::QDragEnterEvent(const QPoint& point, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) + : QDragMoveEvent(point, actions, data, buttons, modifiers, DragEnter) +{} + +/*! \internal +*/ +QDragEnterEvent::~QDragEnterEvent() +{ +} + +/*! + Constructs a drag response event containing the \a accepted value, + indicating whether the drag and drop operation was accepted by the + recipient. +*/ +QDragResponseEvent::QDragResponseEvent(bool accepted) + : QEvent(DragResponse), a(accepted) +{} + +/*! \internal +*/ +QDragResponseEvent::~QDragResponseEvent() +{ +} + +/*! + \class QDragMoveEvent + \brief The QDragMoveEvent class provides an event which is sent while a drag and drop action is in progress. + + \ingroup events + \ingroup draganddrop + + A widget will receive drag move events repeatedly while the drag + is within its boundaries, if it accepts + \l{QWidget::setAcceptDrops()}{drop events} and \l + {QWidget::dragEnterEvent()}{enter events}. The widget should + examine the event to see what kind of data it + \l{QDragMoveEvent::provides()}{provides}, and call the accept() + function to accept the drop if appropriate. + + The rectangle supplied by the answerRect() function can be used to restrict + drops to certain parts of the widget. For example, we can check whether the + rectangle intersects with the geometry of a certain child widget and only + call \l{QDropEvent::acceptProposedAction()}{acceptProposedAction()} if that + is the case. + + Note that this class inherits most of its functionality from + QDropEvent. + + \sa QDragEnterEvent, QDragLeaveEvent, QDropEvent +*/ + +/*! + \class QDragLeaveEvent + \brief The QDragLeaveEvent class provides an event that is sent to a widget when a drag and drop action leaves it. + + \ingroup events + \ingroup draganddrop + + This event is always preceded by a QDragEnterEvent and a series + of \l{QDragMoveEvent}s. It is not sent if a QDropEvent is sent + instead. + + \sa QDragEnterEvent, QDragMoveEvent, QDropEvent +*/ + +/*! + Constructs a QDragLeaveEvent. + + \warning Do not create a QDragLeaveEvent yourself since these + objects rely on Qt's internal state. +*/ +QDragLeaveEvent::QDragLeaveEvent() + : QEvent(DragLeave) +{} + +/*! \internal +*/ +QDragLeaveEvent::~QDragLeaveEvent() +{ +} +#endif // QT_NO_DRAGANDDROP + +/*! + \class QHelpEvent + \brief The QHelpEvent class provides an event that is used to request helpful information + about a particular point in a widget. + + \ingroup events + \ingroup helpsystem + + This event can be intercepted in applications to provide tooltips + or "What's This?" help for custom widgets. The type() can be + either QEvent::ToolTip or QEvent::WhatsThis. + + \sa QToolTip, QWhatsThis, QStatusTipEvent, QWhatsThisClickedEvent +*/ + +/*! + Constructs a help event with the given \a type corresponding to the + widget-relative position specified by \a pos and the global position + specified by \a globalPos. + + \a type must be either QEvent::ToolTip or QEvent::WhatsThis. + + \sa pos(), globalPos() +*/ +QHelpEvent::QHelpEvent(Type type, const QPoint &pos, const QPoint &globalPos) + : QEvent(type), p(pos), gp(globalPos) +{} + +/*! + \fn int QHelpEvent::x() const + + Same as pos().x(). + + \sa y(), pos(), globalPos() +*/ + +/*! + \fn int QHelpEvent::y() const + + Same as pos().y(). + + \sa x(), pos(), globalPos() +*/ + +/*! + \fn int QHelpEvent::globalX() const + + Same as globalPos().x(). + + \sa x(), globalY(), globalPos() +*/ + +/*! + \fn int QHelpEvent::globalY() const + + Same as globalPos().y(). + + \sa y(), globalX(), globalPos() +*/ + +/*! + \fn const QPoint &QHelpEvent::pos() const + + Returns the mouse cursor position when the event was generated, + relative to the widget to which the event is dispatched. + + \sa globalPos(), x(), y() +*/ + +/*! + \fn const QPoint &QHelpEvent::globalPos() const + + Returns the mouse cursor position when the event was generated + in global coordinates. + + \sa pos(), globalX(), globalY() +*/ + +/*! \internal +*/ +QHelpEvent::~QHelpEvent() +{ +} + +#ifndef QT_NO_STATUSTIP + +/*! + \class QStatusTipEvent + \brief The QStatusTipEvent class provides an event that is used to show messages in a status bar. + + \ingroup events + \ingroup helpsystem + + Status tips can be set on a widget using the + QWidget::setStatusTip() function. They are shown in the status + bar when the mouse cursor enters the widget. For example: + + \table 100% + \row + \o + \snippet doc/src/snippets/qstatustipevent/main.cpp 1 + \dots + \snippet doc/src/snippets/qstatustipevent/main.cpp 3 + \o + \image qstatustipevent-widget.png Widget with status tip. + \endtable + + Status tips can also be set on actions using the + QAction::setStatusTip() function: + + \table 100% + \row + \o + \snippet doc/src/snippets/qstatustipevent/main.cpp 0 + \snippet doc/src/snippets/qstatustipevent/main.cpp 2 + \dots + \snippet doc/src/snippets/qstatustipevent/main.cpp 3 + \o + \image qstatustipevent-action.png Action with status tip. + \endtable + + Finally, status tips are supported for the item view classes + through the Qt::StatusTipRole enum value. + + \sa QStatusBar, QHelpEvent, QWhatsThisClickedEvent +*/ + +/*! + Constructs a status tip event with the text specified by \a tip. + + \sa tip() +*/ +QStatusTipEvent::QStatusTipEvent(const QString &tip) + : QEvent(StatusTip), s(tip) +{} + +/*! \internal +*/ +QStatusTipEvent::~QStatusTipEvent() +{ +} + +/*! + \fn QString QStatusTipEvent::tip() const + + Returns the message to show in the status bar. + + \sa QStatusBar::showMessage() +*/ + +#endif // QT_NO_STATUSTIP + +#ifndef QT_NO_WHATSTHIS + +/*! + \class QWhatsThisClickedEvent + \brief The QWhatsThisClickedEvent class provides an event that + can be used to handle hyperlinks in a "What's This?" text. + + \ingroup events + \ingroup helpsystem + + \sa QWhatsThis, QHelpEvent, QStatusTipEvent +*/ + +/*! + Constructs an event containing a URL specified by \a href when a link + is clicked in a "What's This?" message. + + \sa href() +*/ +QWhatsThisClickedEvent::QWhatsThisClickedEvent(const QString &href) + : QEvent(WhatsThisClicked), s(href) +{} + +/*! \internal +*/ +QWhatsThisClickedEvent::~QWhatsThisClickedEvent() +{ +} + +/*! + \fn QString QWhatsThisClickedEvent::href() const + + Returns the URL that was clicked by the user in the "What's + This?" text. +*/ + +#endif // QT_NO_WHATSTHIS + +#ifndef QT_NO_ACTION + +/*! + \class QActionEvent + \brief The QActionEvent class provides an event that is generated + when a QAction is added, removed, or changed. + + \ingroup events + + Actions can be added to widgets using QWidget::addAction(). This + generates an \l ActionAdded event, which you can handle to provide + custom behavior. For example, QToolBar reimplements + QWidget::actionEvent() to create \l{QToolButton}s for the + actions. + + \sa QAction, QWidget::addAction(), QWidget::removeAction(), QWidget::actions() +*/ + +/*! + Constructs an action event. The \a type can be \l ActionChanged, + \l ActionAdded, or \l ActionRemoved. + + \a action is the action that is changed, added, or removed. If \a + type is ActionAdded, the action is to be inserted before the + action \a before. If \a before is 0, the action is appended. +*/ +QActionEvent::QActionEvent(int type, QAction *action, QAction *before) + : QEvent(static_cast<QEvent::Type>(type)), act(action), bef(before) +{} + +/*! \internal +*/ +QActionEvent::~QActionEvent() +{ +} + +/*! + \fn QAction *QActionEvent::action() const + + Returns the action that is changed, added, or removed. + + \sa before() +*/ + +/*! + \fn QAction *QActionEvent::before() const + + If type() is \l ActionAdded, returns the action that should + appear before action(). If this function returns 0, the action + should be appended to already existing actions on the same + widget. + + \sa action(), QWidget::actions() +*/ + +#endif // QT_NO_ACTION + +/*! + \class QHideEvent + \brief The QHideEvent class provides an event which is sent after a widget is hidden. + + \ingroup events + + This event is sent just before QWidget::hide() returns, and also + when a top-level window has been hidden (iconified) by the user. + + If spontaneous() is true, the event originated outside the + application. In this case, the user hid the window using the + window manager controls, either by iconifying the window or by + switching to another virtual desktop where the window isn't + visible. The window will become hidden but not withdrawn. If the + window was iconified, QWidget::isMinimized() returns true. + + \sa QShowEvent +*/ + +/*! + Constructs a QHideEvent. +*/ +QHideEvent::QHideEvent() + : QEvent(Hide) +{} + +/*! \internal +*/ +QHideEvent::~QHideEvent() +{ +} + +/*! + \class QShowEvent + \brief The QShowEvent class provides an event that is sent when a widget is shown. + + \ingroup events + + There are two kinds of show events: show events caused by the + window system (spontaneous), and internal show events. Spontaneous (QEvent::spontaneous()) + show events are sent just after the window system shows the + window; they are also sent when a top-level window is redisplayed + after being iconified. Internal show events are delivered just + before the widget becomes visible. + + \sa QHideEvent +*/ + +/*! + Constructs a QShowEvent. +*/ +QShowEvent::QShowEvent() + : QEvent(Show) +{} + +/*! \internal +*/ +QShowEvent::~QShowEvent() +{ +} + +/*! + \fn QByteArray QDropEvent::data(const char* f) const + + \obsolete + + The encoded data is in \a f. + Use QDropEvent::encodedData(). +*/ + +/*! + \class QFileOpenEvent + \brief The QFileOpenEvent class provides an event that will be + sent when there is a request to open a file or a URL. + + \ingroup events + + File open events will be sent to the QApplication::instance() + when the operating system requests that a file or URL should be opened. + This is a high-level event that can be caused by different user actions + depending on the user's desktop environment; for example, double + clicking on an file icon in the Finder on Mac OS X. + + This event is only used to notify the application of a request. + It may be safely ignored. + + \note This class is currently supported for Mac OS X and Symbian only. +*/ + +QFileOpenEventPrivate::~QFileOpenEventPrivate() +{ +#ifdef Q_OS_SYMBIAN + file.Close(); +#endif +} + +/*! + \internal + + Constructs a file open event for the given \a file. +*/ +QFileOpenEvent::QFileOpenEvent(const QString &file) + : QEvent(FileOpen), f(file) +{ + d = reinterpret_cast<QEventPrivate *>(new QFileOpenEventPrivate(QUrl::fromLocalFile(file))); +} + +/*! + \internal + + Constructs a file open event for the given \a url. +*/ +QFileOpenEvent::QFileOpenEvent(const QUrl &url) + : QEvent(FileOpen) +{ + d = reinterpret_cast<QEventPrivate *>(new QFileOpenEventPrivate(url)); + f = url.toLocalFile(); +} + +#ifdef Q_OS_SYMBIAN +/*! \internal +*/ +QFileOpenEvent::QFileOpenEvent(const RFile &fileHandle) + : QEvent(FileOpen) +{ + TFileName fullName; + fileHandle.FullName(fullName); + f = qt_TDesC2QString(fullName); + QScopedPointer<QFileOpenEventPrivate> priv(new QFileOpenEventPrivate(QUrl::fromLocalFile(f))); + // Duplicate here allows the file handle to be valid after S60 app construction is complete. + qt_symbian_throwIfError(priv->file.Duplicate(fileHandle)); + d = reinterpret_cast<QEventPrivate *>(priv.take()); +} +#endif + +/*! \internal +*/ +QFileOpenEvent::~QFileOpenEvent() +{ + delete reinterpret_cast<QFileOpenEventPrivate *>(d); +} + +/*! + \fn QString QFileOpenEvent::file() const + + Returns the file that is being opened. +*/ + +/*! + \fn QUrl QFileOpenEvent::url() const + + Returns the url that is being opened. + + \since 4.6 +*/ +QUrl QFileOpenEvent::url() const +{ + return reinterpret_cast<const QFileOpenEventPrivate *>(d)->url; +} + +/*! + \fn bool openFile(QFile &file, QIODevice::OpenMode flags) const + + Opens a QFile on the file referenced by this event. + Returns true if successful; otherwise returns false. + + This is necessary as some files cannot be opened by name, but require specific + information stored in this event. + For example, if this QFileOpenEvent contains a request to open a Symbian data caged file, + the QFile could only be opened from the Symbian RFile used in the construction of this event. + + \since 4.8 +*/ +bool QFileOpenEvent::openFile(QFile &file, QIODevice::OpenMode flags) const +{ + file.setFileName(f); +#ifdef Q_OS_SYMBIAN + const QFileOpenEventPrivate *priv = reinterpret_cast<const QFileOpenEventPrivate *>(d); + if (priv->file.SubSessionHandle()) { + RFile dup; + // Duplicate here means that the opened QFile will continue to be valid beyond the lifetime of this QFileOpenEvent. + // It also allows openFile to be used in threads other than the thread in which the QFileOpenEvent was created. + if (dup.Duplicate(priv->file) == KErrNone) { + QScopedPointer<RFile, QScopedPointerRCloser<RFile> > dupCloser(&dup); + bool open = file.open(dup, flags, QFile::AutoCloseHandle); + dupCloser.take(); + return open; + } + } +#endif + return file.open(flags); +} + +#ifndef QT_NO_TOOLBAR +/*! + \internal + \class QToolBarChangeEvent + \brief The QToolBarChangeEvent class provides an event that is + sent whenever a the toolbar button is clicked on Mac OS X. + + \ingroup events + + The QToolBarChangeEvent is sent when the toolbar button is clicked. On Mac + OS X, this is the long oblong button on the right side of the window + title bar. The default implementation is to toggle the appearance (hidden or + shown) of the associated toolbars for the window. +*/ + +/*! + \internal + + Construct a QToolBarChangeEvent given the current button state in \a state. +*/ +QToolBarChangeEvent::QToolBarChangeEvent(bool t) + : QEvent(ToolBarChange), tog(t) +{} + +/*! \internal +*/ +QToolBarChangeEvent::~QToolBarChangeEvent() +{ +} + +/*! + \fn bool QToolBarChangeEvent::toggle() const + \internal +*/ + +/* + \fn Qt::ButtonState QToolBarChangeEvent::state() const + + Returns the keyboard modifier flags at the time of the event. + + The returned value is a selection of the following values, + combined using the OR operator: + Qt::ShiftButton, Qt::ControlButton, Qt::MetaButton, and Qt::AltButton. +*/ + +#endif // QT_NO_TOOLBAR + +#ifndef QT_NO_SHORTCUT + +/*! + Constructs a shortcut event for the given \a key press, + associated with the QShortcut ID \a id. + + \a ambiguous specifies whether there is more than one QShortcut + for the same key sequence. +*/ +QShortcutEvent::QShortcutEvent(const QKeySequence &key, int id, bool ambiguous) + : QEvent(Shortcut), sequence(key), ambig(ambiguous), sid(id) +{ +} + +/*! + Destroys the event object. +*/ +QShortcutEvent::~QShortcutEvent() +{ +} + +#endif // QT_NO_SHORTCUT + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QEvent *e) { +#ifndef Q_BROKEN_DEBUG_STREAM + // More useful event output could be added here + if (!e) + return dbg << "QEvent(this = 0x0)"; + const char *n = 0; + switch (e->type()) { + case QEvent::Timer: + n = "Timer"; + break; + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + { + const QMouseEvent *me = static_cast<const QMouseEvent*>(e); + switch(me->type()) { + case QEvent::MouseButtonPress: + n = "MouseButtonPress"; + break; + case QEvent::MouseMove: + n = "MouseMove"; + break; + case QEvent::MouseButtonRelease: + n = "MouseButtonRelease"; + break; + case QEvent::MouseButtonDblClick: + default: + n = "MouseButtonDblClick"; + break; + } + dbg.nospace() << "QMouseEvent(" << n + << ", " << me->button() + << ", " << hex << (int)me->buttons() + << ", " << hex << (int)me->modifiers() + << ')'; + } + return dbg.space(); + +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: + n = "ToolTip"; + break; +#endif + case QEvent::WindowActivate: + n = "WindowActivate"; + break; + case QEvent::WindowDeactivate: + n = "WindowDeactivate"; + break; + case QEvent::ActivationChange: + n = "ActivationChange"; + break; +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: + dbg.nospace() << "QWheelEvent(" << static_cast<const QWheelEvent *>(e)->delta() + << ')'; + return dbg.space(); +#endif + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: + { + const QKeyEvent *ke = static_cast<const QKeyEvent*>(e); + switch(ke->type()) { + case QEvent::ShortcutOverride: + n = "ShortcutOverride"; + break; + case QEvent::KeyRelease: + n = "KeyRelease"; + break; + case QEvent::KeyPress: + default: + n = "KeyPress"; + break; + } + dbg.nospace() << "QKeyEvent(" << n + << ", " << hex << ke->key() + << ", " << hex << (int)ke->modifiers() + << ", \"" << ke->text() + << "\", " << ke->isAutoRepeat() + << ", " << ke->count() + << ')'; + } + return dbg.space(); + case QEvent::FocusIn: + n = "FocusIn"; + break; + case QEvent::FocusOut: + n = "FocusOut"; + break; + case QEvent::Enter: + n = "Enter"; + break; + case QEvent::Leave: + n = "Leave"; + break; + case QEvent::PaletteChange: + n = "PaletteChange"; + break; + case QEvent::PolishRequest: + n = "PolishRequest"; + break; + case QEvent::Polish: + n = "Polish"; + break; + case QEvent::UpdateRequest: + n = "UpdateRequest"; + break; + case QEvent::Paint: + n = "Paint"; + break; + case QEvent::Move: + n = "Move"; + break; + case QEvent::Resize: + n = "Resize"; + break; + case QEvent::Create: + n = "Create"; + break; + case QEvent::Destroy: + n = "Destroy"; + break; + case QEvent::Close: + n = "Close"; + break; + case QEvent::Quit: + n = "Quit"; + break; + case QEvent::FileOpen: + n = "FileOpen"; + break; + case QEvent::Show: + n = "Show"; + break; + case QEvent::ShowToParent: + n = "ShowToParent"; + break; + case QEvent::Hide: + n = "Hide"; + break; + case QEvent::HideToParent: + n = "HideToParent"; + break; + case QEvent::None: + n = "None"; + break; + case QEvent::ParentChange: + n = "ParentChange"; + break; + case QEvent::ParentAboutToChange: + n = "ParentAboutToChange"; + break; + case QEvent::HoverEnter: + n = "HoverEnter"; + break; + case QEvent::HoverMove: + n = "HoverMove"; + break; + case QEvent::HoverLeave: + n = "HoverLeave"; + break; + case QEvent::ZOrderChange: + n = "ZOrderChange"; + break; + case QEvent::StyleChange: + n = "StyleChange"; + break; + case QEvent::DragEnter: + n = "DragEnter"; + break; + case QEvent::DragMove: + n = "DragMove"; + break; + case QEvent::DragLeave: + n = "DragLeave"; + break; + case QEvent::Drop: + n = "Drop"; + break; + case QEvent::GraphicsSceneMouseMove: + n = "GraphicsSceneMouseMove"; + break; + case QEvent::GraphicsSceneMousePress: + n = "GraphicsSceneMousePress"; + break; + case QEvent::GraphicsSceneMouseRelease: + n = "GraphicsSceneMouseRelease"; + break; + case QEvent::GraphicsSceneMouseDoubleClick: + n = "GraphicsSceneMouseDoubleClick"; + break; + case QEvent::GraphicsSceneContextMenu: + n = "GraphicsSceneContextMenu"; + break; + case QEvent::GraphicsSceneHoverEnter: + n = "GraphicsSceneHoverEnter"; + break; + case QEvent::GraphicsSceneHoverMove: + n = "GraphicsSceneHoverMove"; + break; + case QEvent::GraphicsSceneHoverLeave: + n = "GraphicsSceneHoverLeave"; + break; + case QEvent::GraphicsSceneHelp: + n = "GraphicsSceneHelp"; + break; + case QEvent::GraphicsSceneDragEnter: + n = "GraphicsSceneDragEnter"; + break; + case QEvent::GraphicsSceneDragMove: + n = "GraphicsSceneDragMove"; + break; + case QEvent::GraphicsSceneDragLeave: + n = "GraphicsSceneDragLeave"; + break; + case QEvent::GraphicsSceneDrop: + n = "GraphicsSceneDrop"; + break; + case QEvent::GraphicsSceneWheel: + n = "GraphicsSceneWheel"; + break; + case QEvent::GraphicsSceneResize: + n = "GraphicsSceneResize"; + break; + case QEvent::GraphicsSceneMove: + n = "GraphicsSceneMove"; + break; + case QEvent::CursorChange: + n = "CursorChange"; + break; + case QEvent::ToolTipChange: + n = "ToolTipChange"; + break; + case QEvent::StatusTip: + n = "StatusTip"; + break; + case QEvent::WhatsThis: + n = "WhatsThis"; + break; + case QEvent::FontChange: + n = "FontChange"; + break; + case QEvent::Style: + n = "Style"; + break; + case QEvent::KeyboardLayoutChange: + n = "KeyboardLayoutChange"; + break; + case QEvent::DynamicPropertyChange: + n = "DynamicPropertyChange"; + break; + case QEvent::GrabMouse: + n = "GrabMouse"; + break; + case QEvent::UngrabMouse: + n = "UngrabMouse"; + break; + case QEvent::GrabKeyboard: + n = "GrabKeyboard"; + break; + case QEvent::UngrabKeyboard: + n = "UngrabKeyboard"; + break; +#ifdef QT3_SUPPORT + case QEvent::ChildInsertedRequest: + n = "ChildInsertedRequest"; + break; + case QEvent::ChildInserted: n = "ChildInserted"; +#endif + case QEvent::ChildAdded: n = n ? n : "ChildAdded"; + case QEvent::ChildPolished: n = n ? n : "ChildPolished"; + case QEvent::ChildRemoved: n = n ? n : "ChildRemoved"; + dbg.nospace() << "QChildEvent(" << n << ", " << (static_cast<const QChildEvent*>(e))->child(); + return dbg.space(); +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + n = "Gesture"; + break; +#endif + default: + dbg.nospace() << "QEvent(" << (const void *)e << ", type = " << e->type() << ')'; + return dbg.space(); + } + + dbg.nospace() << 'Q' << n << "Event(" << (const void *)e << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QEvent to QDebug"); + return dbg; + Q_UNUSED(e); +#endif +} +#endif + +#ifndef QT_NO_CLIPBOARD +/*! + \class QClipboardEvent + \ingroup events + \internal + + \brief The QClipboardEvent class provides the parameters used in a clipboard event. + + This class is for internal use only, and exists to aid the clipboard on various + platforms to get all the information it needs. Use QEvent::Clipboard instead. + + \sa QClipboard +*/ + +QClipboardEvent::QClipboardEvent(QEventPrivate *data) + : QEvent(QEvent::Clipboard) +{ + d = data; +} + +QClipboardEvent::~QClipboardEvent() +{ +} +#endif // QT_NO_CLIPBOARD + +/*! + \class QShortcutEvent + \brief The QShortcutEvent class provides an event which is generated when + the user presses a key combination. + + \ingroup events + + Normally you don't need to use this class directly; QShortcut + provides a higher-level interface to handle shortcut keys. + + \sa QShortcut +*/ + +/*! + \fn const QKeySequence &QShortcutEvent::key() const + + Returns the key sequence that triggered the event. +*/ + +// ### Qt 5: remove +/*! + \fn const QKeySequence &QShortcutEvent::key() + + \internal +*/ + +/*! + \fn int QShortcutEvent::shortcutId() const + + Returns the ID of the QShortcut object for which this event was + generated. + + \sa QShortcut::id() +*/ + +// ### Qt 5: remove +/*! + \fn int QShortcutEvent::shortcutId() + \overload + + \internal +*/ + +/*! + \fn bool QShortcutEvent::isAmbiguous() const + + Returns true if the key sequence that triggered the event is + ambiguous. + + \sa QShortcut::activatedAmbiguously() +*/ + +// ### Qt 5: remove +/*! + \fn bool QShortcutEvent::isAmbiguous() + + \internal +*/ + +/*! + \class QWindowStateChangeEvent + \ingroup events + + \brief The QWindowStateChangeEvent class provides the window state before a + window state change. +*/ + +/*! \fn Qt::WindowStates QWindowStateChangeEvent::oldState() const + + Returns the state of the window before the change. +*/ + +/*! \internal + */ +QWindowStateChangeEvent::QWindowStateChangeEvent(Qt::WindowStates s) + : QEvent(WindowStateChange), ostate(s) +{ +} + +/*! \internal + */ +QWindowStateChangeEvent::QWindowStateChangeEvent(Qt::WindowStates s, bool isOverride) + : QEvent(WindowStateChange), ostate(s) +{ + if (isOverride) + d = (QEventPrivate*)(this); +} + +/*! \internal + */ +bool QWindowStateChangeEvent::isOverride() const +{ + return (d != 0); +} + +/*! \internal +*/ +QWindowStateChangeEvent::~QWindowStateChangeEvent() +{ +} + +#ifdef QT3_SUPPORT + +/*! + \class QMenubarUpdatedEvent + \internal + Event sent by QMenuBar to tell Q3Workspace to update itself. +*/ + +/*! \internal + +*/ +QMenubarUpdatedEvent::QMenubarUpdatedEvent(QMenuBar * const menuBar) +:QEvent(QEvent::MenubarUpdated), m_menuBar(menuBar) {} + +/*! + \fn QMenuBar *QMenubarUpdatedEvent::menuBar() + \internal +*/ + +/*! + \fn bool operator==(QKeyEvent *e, QKeySequence::StandardKey key) + + \relates QKeyEvent + + Returns true if \a key is currently bound to the key combination + specified by \a e. + + Equivalent to \c {e->matches(key)}. +*/ + +/*! + \fn bool operator==(QKeySequence::StandardKey key, QKeyEvent *e) + + \relates QKeyEvent + + Returns true if \a key is currently bound to the key combination + specified by \a e. + + Equivalent to \c {e->matches(key)}. +*/ + +/*! + \internal + + \class QKeyEventEx + \ingroup events + + \brief The QKeyEventEx class provides more extended information about a keyevent. + + This class is for internal use only, and exists to aid the shortcut system on + various platforms to get all the information it needs. +*/ + +#endif + +/*! + \class QTouchEvent + \brief The QTouchEvent class contains parameters that describe a touch event. + \since 4.6 + \ingroup events + \ingroup touch + + \section1 Enabling Touch Events + + Touch events occur when pressing, releasing, or moving one or more touch points on a touch + device (such as a touch-screen or track-pad). To receive touch events, widgets have to have the + Qt::WA_AcceptTouchEvents attribute set and graphics items need to have the + \l{QGraphicsItem::setAcceptTouchEvents()}{acceptTouchEvents} attribute set to true. + + When using QAbstractScrollArea based widgets, you should enable the Qt::WA_AcceptTouchEvents + attribute on the scroll area's \l{QAbstractScrollArea::viewport()}{viewport}. + + Similarly to QMouseEvent, Qt automatically grabs each touch point on the first press inside a + widget, and the widget will receive all updates for the touch point until it is released. + Note that it is possible for a widget to receive events for numerous touch points, and that + multiple widgets may be receiving touch events at the same time. + + \section1 Event Handling + + All touch events are of type QEvent::TouchBegin, QEvent::TouchUpdate, or QEvent::TouchEnd. + Reimplement QWidget::event() or QAbstractScrollArea::viewportEvent() for widgets and + QGraphicsItem::sceneEvent() for items in a graphics view to receive touch events. + + The QEvent::TouchUpdate and QEvent::TouchEnd events are sent to the widget or item that + accepted the QEvent::TouchBegin event. If the QEvent::TouchBegin event is not accepted and not + filtered by an event filter, then no further touch events are sent until the next + QEvent::TouchBegin. + + The touchPoints() function returns a list of all touch points contained in the event. + Information about each touch point can be retrieved using the QTouchEvent::TouchPoint class. + The Qt::TouchPointState enum describes the different states that a touch point may have. + + \section1 Event Delivery and Propagation + + By default, QWidget::event() translates the first non-primary touch point in a QTouchEvent into + a QMouseEvent. This makes it possible to enable touch events on existing widgets that do not + normally handle QTouchEvent. See below for information on some special considerations needed + when doing this. + + QEvent::TouchBegin is the first touch event sent to a widget. The QEvent::TouchBegin event + contains a special accept flag that indicates whether the receiver wants the event. By default, + the event is accepted. You should call ignore() if the touch event is not handled by your + widget. The QEvent::TouchBegin event is propagated up the parent widget chain until a widget + accepts it with accept(), or an event filter consumes it. For QGraphicsItems, the + QEvent::TouchBegin event is propagated to items under the mouse (similar to mouse event + propagation for QGraphicsItems). + + \section1 Touch Point Grouping + + As mentioned above, it is possible that several widgets can be receiving QTouchEvents at the + same time. However, Qt makes sure to never send duplicate QEvent::TouchBegin events to the same + widget, which could theoretically happen during propagation if, for example, the user touched 2 + separate widgets in a QGroupBox and both widgets ignored the QEvent::TouchBegin event. + + To avoid this, Qt will group new touch points together using the following rules: + + \list + + \i When the first touch point is detected, the destination widget is determined firstly by the + location on screen and secondly by the propagation rules. + + \i When additional touch points are detected, Qt first looks to see if there are any active + touch points on any ancestor or descendent of the widget under the new touch point. If there + are, the new touch point is grouped with the first, and the new touch point will be sent in a + single QTouchEvent to the widget that handled the first touch point. (The widget under the new + touch point will not receive an event). + + \endlist + + This makes it possible for sibling widgets to handle touch events independently while making + sure that the sequence of QTouchEvents is always correct. + + \section1 Mouse Events and the Primary Touch Point + + QTouchEvent delivery is independent from that of QMouseEvent. On some windowing systems, mouse + events are also sent for the \l{QTouchEvent::TouchPoint::isPrimary()}{primary touch point}. + This means it is possible for your widget to receive both QTouchEvent and QMouseEvent for the + same user interaction point. You can use the QTouchEvent::TouchPoint::isPrimary() function to + identify the primary touch point. + + Note that on some systems, it is possible to receive touch events without a primary touch + point. All this means is that there will be no mouse event generated for the touch points in + the QTouchEvent. + + \section1 Caveats + + \list + + \i As mentioned above, enabling touch events means multiple widgets can be receiving touch + events simultaneously. Combined with the default QWidget::event() handling for QTouchEvents, + this gives you great flexibility in designing touch user interfaces. Be aware of the + implications. For example, it is possible that the user is moving a QSlider with one finger and + pressing a QPushButton with another. The signals emitted by these widgets will be + interleaved. + + \i Recursion into the event loop using one of the exec() methods (e.g., QDialog::exec() or + QMenu::exec()) in a QTouchEvent event handler is not supported. Since there are multiple event + recipients, recursion may cause problems, including but not limited to lost events + and unexpected infinite recursion. + + \i QTouchEvents are not affected by a \l{QWidget::grabMouse()}{mouse grab} or an + \l{QApplication::activePopupWidget()}{active pop-up widget}. The behavior of QTouchEvents is + undefined when opening a pop-up or grabbing the mouse while there are more than one active touch + points. + + \endlist + + \sa QTouchEvent::TouchPoint, Qt::TouchPointState, Qt::WA_AcceptTouchEvents, + QGraphicsItem::acceptTouchEvents() +*/ + +/*! \enum Qt::TouchPointState + \since 4.6 + + This enum represents the state of a touch point at the time the + QTouchEvent occurred. + + \value TouchPointPressed The touch point is now pressed. + \value TouchPointMoved The touch point moved. + \value TouchPointStationary The touch point did not move. + \value TouchPointReleased The touch point was released. + + \omitvalue TouchPointStateMask + \omitvalue TouchPointPrimary +*/ + +/*! \enum QTouchEvent::DeviceType + + This enum represents the type of device that generated a QTouchEvent. + + \value TouchScreen In this type of device, the touch surface and display are integrated. This + means the surface and display typically have the same size, such that there + is a direct relationship between the touch points' physical positions and the + coordinate reported by QTouchEvent::TouchPoint. As a result, Qt allows the + user to interact directly with multiple QWidgets and QGraphicsItems at the + same time. + + \value TouchPad In this type of device, the touch surface is separate from the display. There + is not a direct relationship between the physical touch location and the + on-screen coordinates. Instead, they are calculated relative to the current + mouse position, and the user must use the touch-pad to move this reference + point. Unlike touch-screens, Qt allows users to only interact with a single + QWidget or QGraphicsItem at a time. +*/ + +/*! + Constructs a QTouchEvent with the given \a eventType, \a deviceType, and \a touchPoints. + The \a touchPointStates and \a modifiers are the current touch point states and keyboard + modifiers at the time of the event. +*/ +QTouchEvent::QTouchEvent(QEvent::Type eventType, + QTouchEvent::DeviceType deviceType, + Qt::KeyboardModifiers modifiers, + Qt::TouchPointStates touchPointStates, + const QList<QTouchEvent::TouchPoint> &touchPoints) + : QInputEvent(eventType, modifiers), + _widget(0), + _deviceType(deviceType), + _touchPointStates(touchPointStates), + _touchPoints(touchPoints) +{ } + +/*! + Destroys the QTouchEvent. +*/ +QTouchEvent::~QTouchEvent() +{ } + +/*! \fn QWidget *QTouchEvent::widget() const + + Returns the widget on which the event occurred. +*/ + + +/*! \fn Qt::TouchPointStates QTouchEvent::touchPointStates() const + + Returns a bitwise OR of all the touch point states for this event. +*/ + +/*! \fn const QList<QTouchEvent::TouchPoint> &QTouchEvent::touchPoints() const + + Returns the list of touch points contained in the touch event. +*/ + +/*! \fn QTouchEvent::DeviceType QTouchEvent::deviceType() const + + Returns the touch device Type, which is of type \l {QTouchEvent::DeviceType} {DeviceType}. +*/ + +/*! \fn void QTouchEvent::setWidget(QWidget *widget) + + \internal + + Sets the widget for this event. +*/ + +/*! \fn void QTouchEvent::setTouchPointStates(Qt::TouchPointStates touchPointStates) + + \internal + + Sets a bitwise OR of all the touch point states for this event. +*/ + +/*! \fn void QTouchEvent::setTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints) + + \internal + + Sets the list of touch points for this event. +*/ + +/*! \fn void QTouchEvent::setDeviceType(DeviceType deviceType) + + \internal + + Sets the device type to \a deviceType, which is of type \l {QTouchEvent::DeviceType} + {DeviceType}. +*/ + +/*! \class QTouchEvent::TouchPoint + \brief The TouchPoint class provides information about a touch point in a QTouchEvent. + \since 4.6 +*/ + +/*! \internal + + Constructs a QTouchEvent::TouchPoint for use in a QTouchEvent. +*/ +QTouchEvent::TouchPoint::TouchPoint(int id) + : d(new QTouchEventTouchPointPrivate(id)) +{ } + +/*! \internal + + Constructs a copy of \a other. +*/ +QTouchEvent::TouchPoint::TouchPoint(const QTouchEvent::TouchPoint &other) + : d(other.d) +{ + d->ref.ref(); +} + +/*! \internal + + Destroys the QTouchEvent::TouchPoint. +*/ +QTouchEvent::TouchPoint::~TouchPoint() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + Returns the id number of this touch point. + + Id numbers are globally sequential, starting at zero, meaning the + first touch point in the application has id 0, the second has id 1, + and so on. +*/ +int QTouchEvent::TouchPoint::id() const +{ + return d->id; +} + +/*! + Returns the current state of this touch point. +*/ +Qt::TouchPointState QTouchEvent::TouchPoint::state() const +{ + return Qt::TouchPointState(int(d->state) & Qt::TouchPointStateMask); +} + +/*! + Returns true if this touch point is the primary touch point. The primary touch point is the + point for which the windowing system generates mouse events. +*/ +bool QTouchEvent::TouchPoint::isPrimary() const +{ + return (d->state & Qt::TouchPointPrimary) != 0; +} + +/*! + Returns the position of this touch point, relative to the widget + or QGraphicsItem that received the event. + + \sa startPos(), lastPos(), screenPos(), scenePos(), normalizedPos() +*/ +QPointF QTouchEvent::TouchPoint::pos() const +{ + return d->rect.center(); +} + +/*! + Returns the scene position of this touch point. + + The scene position is the position in QGraphicsScene coordinates + if the QTouchEvent is handled by a QGraphicsItem::touchEvent() + reimplementation, and identical to the screen position for + widgets. + + \sa startScenePos(), lastScenePos(), pos() +*/ +QPointF QTouchEvent::TouchPoint::scenePos() const +{ + return d->sceneRect.center(); +} + +/*! + Returns the screen position of this touch point. + + \sa startScreenPos(), lastScreenPos(), pos() +*/ +QPointF QTouchEvent::TouchPoint::screenPos() const +{ + return d->screenRect.center(); +} + +/*! + Returns the normalized position of this touch point. + + The coordinates are normalized to the size of the touch device, + i.e. (0,0) is the top-left corner and (1,1) is the bottom-right corner. + + \sa startNormalizedPos(), lastNormalizedPos(), pos() +*/ +QPointF QTouchEvent::TouchPoint::normalizedPos() const +{ + return d->normalizedPos; +} + +/*! + Returns the starting position of this touch point, relative to the + widget or QGraphicsItem that received the event. + + \sa pos(), lastPos() +*/ +QPointF QTouchEvent::TouchPoint::startPos() const +{ + return d->startPos; +} + +/*! + Returns the starting scene position of this touch point. + + The scene position is the position in QGraphicsScene coordinates + if the QTouchEvent is handled by a QGraphicsItem::touchEvent() + reimplementation, and identical to the screen position for + widgets. + + \sa scenePos(), lastScenePos() +*/ +QPointF QTouchEvent::TouchPoint::startScenePos() const +{ + return d->startScenePos; +} + +/*! + Returns the starting screen position of this touch point. + + \sa screenPos(), lastScreenPos() +*/ +QPointF QTouchEvent::TouchPoint::startScreenPos() const +{ + return d->startScreenPos; +} + +/*! + Returns the normalized starting position of this touch point. + + The coordinates are normalized to the size of the touch device, + i.e. (0,0) is the top-left corner and (1,1) is the bottom-right corner. + + \sa normalizedPos(), lastNormalizedPos() +*/ +QPointF QTouchEvent::TouchPoint::startNormalizedPos() const +{ + return d->startNormalizedPos; +} + +/*! + Returns the position of this touch point from the previous touch + event, relative to the widget or QGraphicsItem that received the event. + + \sa pos(), startPos() +*/ +QPointF QTouchEvent::TouchPoint::lastPos() const +{ + return d->lastPos; +} + +/*! + Returns the scene position of this touch point from the previous + touch event. + + The scene position is the position in QGraphicsScene coordinates + if the QTouchEvent is handled by a QGraphicsItem::touchEvent() + reimplementation, and identical to the screen position for + widgets. + + \sa scenePos(), startScenePos() +*/ +QPointF QTouchEvent::TouchPoint::lastScenePos() const +{ + return d->lastScenePos; +} + +/*! + Returns the screen position of this touch point from the previous + touch event. + + \sa screenPos(), startScreenPos() +*/ +QPointF QTouchEvent::TouchPoint::lastScreenPos() const +{ + return d->lastScreenPos; +} + +/*! + Returns the normalized position of this touch point from the + previous touch event. + + The coordinates are normalized to the size of the touch device, + i.e. (0,0) is the top-left corner and (1,1) is the bottom-right corner. + + \sa normalizedPos(), startNormalizedPos() +*/ +QPointF QTouchEvent::TouchPoint::lastNormalizedPos() const +{ + return d->lastNormalizedPos; +} + +/*! + Returns the rect for this touch point, relative to the widget + or QGraphicsItem that received the event. The rect is centered + around the point returned by pos(). + + \note This function returns an empty rect if the device does not report touch point sizes. +*/ +QRectF QTouchEvent::TouchPoint::rect() const +{ + return d->rect; +} + +/*! + Returns the rect for this touch point in scene coordinates. + + \note This function returns an empty rect if the device does not report touch point sizes. + + \sa scenePos(), rect() +*/ +QRectF QTouchEvent::TouchPoint::sceneRect() const +{ + return d->sceneRect; +} + +/*! + Returns the rect for this touch point in screen coordinates. + + \note This function returns an empty rect if the device does not report touch point sizes. + + \sa screenPos(), rect() +*/ +QRectF QTouchEvent::TouchPoint::screenRect() const +{ + return d->screenRect; +} + +/*! + Returns the pressure of this touch point. The return value is in + the range 0.0 to 1.0. +*/ +qreal QTouchEvent::TouchPoint::pressure() const +{ + return d->pressure; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setId(int id) +{ + if (d->ref != 1) + d = d->detach(); + d->id = id; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setState(Qt::TouchPointStates state) +{ + if (d->ref != 1) + d = d->detach(); + d->state = state; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setPos(const QPointF &pos) +{ + if (d->ref != 1) + d = d->detach(); + d->rect.moveCenter(pos); +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setScenePos(const QPointF &scenePos) +{ + if (d->ref != 1) + d = d->detach(); + d->sceneRect.moveCenter(scenePos); +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setScreenPos(const QPointF &screenPos) +{ + if (d->ref != 1) + d = d->detach(); + d->screenRect.moveCenter(screenPos); +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setNormalizedPos(const QPointF &normalizedPos) +{ + if (d->ref != 1) + d = d->detach(); + d->normalizedPos = normalizedPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setStartPos(const QPointF &startPos) +{ + if (d->ref != 1) + d = d->detach(); + d->startPos = startPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setStartScenePos(const QPointF &startScenePos) +{ + if (d->ref != 1) + d = d->detach(); + d->startScenePos = startScenePos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setStartScreenPos(const QPointF &startScreenPos) +{ + if (d->ref != 1) + d = d->detach(); + d->startScreenPos = startScreenPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setStartNormalizedPos(const QPointF &startNormalizedPos) +{ + if (d->ref != 1) + d = d->detach(); + d->startNormalizedPos = startNormalizedPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setLastPos(const QPointF &lastPos) +{ + if (d->ref != 1) + d = d->detach(); + d->lastPos = lastPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setLastScenePos(const QPointF &lastScenePos) +{ + if (d->ref != 1) + d = d->detach(); + d->lastScenePos = lastScenePos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setLastScreenPos(const QPointF &lastScreenPos) +{ + if (d->ref != 1) + d = d->detach(); + d->lastScreenPos = lastScreenPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setLastNormalizedPos(const QPointF &lastNormalizedPos) +{ + if (d->ref != 1) + d = d->detach(); + d->lastNormalizedPos = lastNormalizedPos; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setRect(const QRectF &rect) +{ + if (d->ref != 1) + d = d->detach(); + d->rect = rect; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setSceneRect(const QRectF &sceneRect) +{ + if (d->ref != 1) + d = d->detach(); + d->sceneRect = sceneRect; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setScreenRect(const QRectF &screenRect) +{ + if (d->ref != 1) + d = d->detach(); + d->screenRect = screenRect; +} + +/*! \internal */ +void QTouchEvent::TouchPoint::setPressure(qreal pressure) +{ + if (d->ref != 1) + d = d->detach(); + d->pressure = pressure; +} + +/*! \internal */ +QTouchEvent::TouchPoint &QTouchEvent::TouchPoint::operator=(const QTouchEvent::TouchPoint &other) +{ + other.d->ref.ref(); + if (!d->ref.deref()) + delete d; + d = other.d; + return *this; +} + +#ifndef QT_NO_GESTURES +/*! + \class QGestureEvent + \since 4.6 + \ingroup events + \ingroup gestures + + \brief The QGestureEvent class provides the description of triggered gestures. + + The QGestureEvent class contains a list of gestures, which can be obtained using the + gestures() function. + + The gestures are either active or canceled. A list of those that are currently being + executed can be obtained using the activeGestures() function. A list of those which + were previously active and have been canceled can be accessed using the + canceledGestures() function. A gesture might be canceled if the current window loses + focus, for example, or because of a timeout, or for other reasons. + + If the event handler does not accept the event by calling the generic + QEvent::accept() function, all individual QGesture object that were not + accepted and in the Qt::GestureStarted state will be propagated up the + parent widget chain until a widget accepts them individually, by calling + QGestureEvent::accept() for each of them, or an event filter consumes the + event. + + \section1 Further Reading + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \sa QGesture, QGestureRecognizer, + QWidget::grabGesture(), QGraphicsObject::grabGesture() +*/ + +/*! + Creates new QGestureEvent containing a list of \a gestures. +*/ +QGestureEvent::QGestureEvent(const QList<QGesture *> &gestures) + : QEvent(QEvent::Gesture) +{ + d = reinterpret_cast<QEventPrivate *>(new QGestureEventPrivate(gestures)); +} + +/*! + Destroys QGestureEvent. +*/ +QGestureEvent::~QGestureEvent() +{ + delete reinterpret_cast<QGestureEventPrivate *>(d); +} + +/*! + Returns all gestures that are delivered in the event. +*/ +QList<QGesture *> QGestureEvent::gestures() const +{ + return d_func()->gestures; +} + +/*! + Returns a gesture object by \a type. +*/ +QGesture *QGestureEvent::gesture(Qt::GestureType type) const +{ + const QGestureEventPrivate *d = d_func(); + for(int i = 0; i < d->gestures.size(); ++i) + if (d->gestures.at(i)->gestureType() == type) + return d->gestures.at(i); + return 0; +} + +/*! + Returns a list of active (not canceled) gestures. +*/ +QList<QGesture *> QGestureEvent::activeGestures() const +{ + QList<QGesture *> gestures; + foreach (QGesture *gesture, d_func()->gestures) { + if (gesture->state() != Qt::GestureCanceled) + gestures.append(gesture); + } + return gestures; +} + +/*! + Returns a list of canceled gestures. +*/ +QList<QGesture *> QGestureEvent::canceledGestures() const +{ + QList<QGesture *> gestures; + foreach (QGesture *gesture, d_func()->gestures) { + if (gesture->state() == Qt::GestureCanceled) + gestures.append(gesture); + } + return gestures; +} + +/*! + Sets the accept flag of the given \a gesture object to the specified \a value. + + Setting the accept flag indicates that the event receiver wants the \a gesture. + Unwanted gestures may be propagated to the parent widget. + + By default, gestures in events of type QEvent::Gesture are accepted, and + gestures in QEvent::GestureOverride events are ignored. + + For convenience, the accept flag can also be set with + \l{QGestureEvent::accept()}{accept(gesture)}, and cleared with + \l{QGestureEvent::ignore()}{ignore(gesture)}. +*/ +void QGestureEvent::setAccepted(QGesture *gesture, bool value) +{ + if (gesture) + setAccepted(gesture->gestureType(), value); +} + +/*! + Sets the accept flag of the given \a gesture object, the equivalent of calling + \l{QGestureEvent::setAccepted()}{setAccepted(gesture, true)}. + + Setting the accept flag indicates that the event receiver wants the + gesture. Unwanted gestures may be propagated to the parent widget. + + \sa QGestureEvent::ignore() +*/ +void QGestureEvent::accept(QGesture *gesture) +{ + if (gesture) + setAccepted(gesture->gestureType(), true); +} + +/*! + Clears the accept flag parameter of the given \a gesture object, the equivalent + of calling \l{QGestureEvent::setAccepted()}{setAccepted(gesture, false)}. + + Clearing the accept flag indicates that the event receiver does not + want the gesture. Unwanted gestures may be propagated to the parent widget. + + \sa QGestureEvent::accept() +*/ +void QGestureEvent::ignore(QGesture *gesture) +{ + if (gesture) + setAccepted(gesture->gestureType(), false); +} + +/*! + Returns true if the \a gesture is accepted; otherwise returns false. +*/ +bool QGestureEvent::isAccepted(QGesture *gesture) const +{ + return gesture ? isAccepted(gesture->gestureType()) : false; +} + +/*! + Sets the accept flag of the given \a gestureType object to the specified + \a value. + + Setting the accept flag indicates that the event receiver wants to receive + gestures of the specified type, \a gestureType. Unwanted gestures may be + propagated to the parent widget. + + By default, gestures in events of type QEvent::Gesture are accepted, and + gestures in QEvent::GestureOverride events are ignored. + + For convenience, the accept flag can also be set with + \l{QGestureEvent::accept()}{accept(gestureType)}, and cleared with + \l{QGestureEvent::ignore()}{ignore(gestureType)}. +*/ +void QGestureEvent::setAccepted(Qt::GestureType gestureType, bool value) +{ + setAccepted(false); + d_func()->accepted[gestureType] = value; +} + +/*! + Sets the accept flag of the given \a gestureType, the equivalent of calling + \l{QGestureEvent::setAccepted()}{setAccepted(gestureType, true)}. + + Setting the accept flag indicates that the event receiver wants the + gesture. Unwanted gestures may be propagated to the parent widget. + + \sa QGestureEvent::ignore() +*/ +void QGestureEvent::accept(Qt::GestureType gestureType) +{ + setAccepted(gestureType, true); +} + +/*! + Clears the accept flag parameter of the given \a gestureType, the equivalent + of calling \l{QGestureEvent::setAccepted()}{setAccepted(gesture, false)}. + + Clearing the accept flag indicates that the event receiver does not + want the gesture. Unwanted gestures may be propgated to the parent widget. + + \sa QGestureEvent::accept() +*/ +void QGestureEvent::ignore(Qt::GestureType gestureType) +{ + setAccepted(gestureType, false); +} + +/*! + Returns true if the gesture of type \a gestureType is accepted; otherwise + returns false. +*/ +bool QGestureEvent::isAccepted(Qt::GestureType gestureType) const +{ + return d_func()->accepted.value(gestureType, true); +} + +/*! + \internal + + Sets the widget for this event to the \a widget specified. +*/ +void QGestureEvent::setWidget(QWidget *widget) +{ + d_func()->widget = widget; +} + +/*! + Returns the widget on which the event occurred. +*/ +QWidget *QGestureEvent::widget() const +{ + return d_func()->widget; +} + +#ifndef QT_NO_GRAPHICSVIEW +/*! + Returns the scene-local coordinates if the \a gesturePoint is inside a + graphics view. + + This functional might be useful when the gesture event is delivered to a + QGraphicsObject to translate a point in screen coordinates to scene-local + coordinates. + + \sa QPointF::isNull(). +*/ +QPointF QGestureEvent::mapToGraphicsScene(const QPointF &gesturePoint) const +{ + QWidget *w = widget(); + if (w) // we get the viewport as widget, not the graphics view + w = w->parentWidget(); + QGraphicsView *view = qobject_cast<QGraphicsView*>(w); + if (view) { + return view->mapToScene(view->mapFromGlobal(gesturePoint.toPoint())); + } + return QPointF(); +} +#endif //QT_NO_GRAPHICSVIEW + +/*! + \internal +*/ +QGestureEventPrivate *QGestureEvent::d_func() +{ + return reinterpret_cast<QGestureEventPrivate *>(d); +} + +/*! + \internal +*/ +const QGestureEventPrivate *QGestureEvent::d_func() const +{ + return reinterpret_cast<const QGestureEventPrivate *>(d); +} + +#ifdef Q_NO_USING_KEYWORD +/*! + \fn void QGestureEvent::setAccepted(bool accepted) + + Sets or clears the event's internal flag that determines whether it should + be delivered to other objects. + + Calling this function with a value of true for \a accepted indicates that the + caller has accepted the event and that it should not be propagated further. + Calling this function with a value of false indicates that the caller has + ignored the event and that it should be delivered to other objects. + + For convenience, the accept flag can also be set with accept(), and cleared + with ignore(). + + \sa QEvent::accepted +*/ +/*! + \fn bool QGestureEvent::isAccepted() const + + Returns true is the event has been accepted; otherwise returns false. + + \sa QEvent::accepted +*/ +/*! + \fn void QGestureEvent::accept() + + Accepts the event, the equivalent of calling setAccepted(true). + + \sa QEvent::accept() +*/ +/*! + \fn void QGestureEvent::ignore() + + Ignores the event, the equivalent of calling setAccepted(false). + + \sa QEvent::ignore() +*/ +#endif + +#endif // QT_NO_GESTURES + +/*! + \class QScrollPrepareEvent + \since 4.8 + \ingroup events + + \brief The QScrollPrepareEvent class is send in preparation of a scrolling. + + The scroll prepare event is send before scrolling (usually by QScroller) is started. + The object receiving this event should set viewportSize, maxContentPos and contentPos. + It also should accept this event to indicate that scrolling should be started. + + It is not guaranteed that a QScrollEvent will be send after an acceepted + QScrollPrepareEvent, e.g. in a case where the maximum content position is (0,0). + + \sa QScrollEvent, QScroller +*/ + +/*! + Creates new QScrollPrepareEvent + The \a startPos is the position of a touch or mouse event that started the scrolling. +*/ +QScrollPrepareEvent::QScrollPrepareEvent(const QPointF &startPos) + : QEvent(QEvent::ScrollPrepare) +{ + d = reinterpret_cast<QEventPrivate *>(new QScrollPrepareEventPrivate()); + d_func()->startPos = startPos; +} + +/*! + Destroys QScrollEvent. +*/ +QScrollPrepareEvent::~QScrollPrepareEvent() +{ + delete reinterpret_cast<QScrollPrepareEventPrivate *>(d); +} + +/*! + Returns the position of the touch or mouse event that started the scrolling. +*/ +QPointF QScrollPrepareEvent::startPos() const +{ + return d_func()->startPos; +} + +/*! + Returns size of the area that is to be scrolled as set by setViewportSize + + \sa setViewportSize() +*/ +QSizeF QScrollPrepareEvent::viewportSize() const +{ + return d_func()->viewportSize; +} + +/*! + Returns the range of coordinates for the content as set by setContentPosRange(). +*/ +QRectF QScrollPrepareEvent::contentPosRange() const +{ + return d_func()->contentPosRange; +} + +/*! + Returns the current position of the content as set by setContentPos. +*/ +QPointF QScrollPrepareEvent::contentPos() const +{ + return d_func()->contentPos; +} + + +/*! + Sets the size of the area that is to be scrolled to \a size. + + \sa viewportSize() +*/ +void QScrollPrepareEvent::setViewportSize(const QSizeF &size) +{ + d_func()->viewportSize = size; +} + +/*! + Sets the range of content coordinates to \a rect. + + \sa contentPosRange() +*/ +void QScrollPrepareEvent::setContentPosRange(const QRectF &rect) +{ + d_func()->contentPosRange = rect; +} + +/*! + Sets the current content position to \a pos. + + \sa contentPos() +*/ +void QScrollPrepareEvent::setContentPos(const QPointF &pos) +{ + d_func()->contentPos = pos; +} + + +/*! + \internal +*/ +QScrollPrepareEventPrivate *QScrollPrepareEvent::d_func() +{ + return reinterpret_cast<QScrollPrepareEventPrivate *>(d); +} + +/*! + \internal +*/ +const QScrollPrepareEventPrivate *QScrollPrepareEvent::d_func() const +{ + return reinterpret_cast<const QScrollPrepareEventPrivate *>(d); +} + +/*! + \class QScrollEvent + \since 4.8 + \ingroup events + + \brief The QScrollEvent class is send when scrolling. + + The scroll event is send to indicate that the receiver should be scrolled. + Usually the receiver should be something visual like QWidget or QGraphicsObject. + + Some care should be taken that no conflicting QScrollEvents are sent from two + sources. Using QScroller::scrollTo is save however. + + \sa QScrollPrepareEvent, QScroller +*/ + +/*! + \enum QScrollEvent::ScrollState + + This enum describes the states a scroll event can have. + + \value ScrollStarted Set for the first scroll event of a scroll activity. + + \value ScrollUpdated Set for all but the first and the last scroll event of a scroll activity. + + \value ScrollFinished Set for the last scroll event of a scroll activity. + + \sa QScrollEvent::scrollState() +*/ + +/*! + Creates a new QScrollEvent + \a contentPos is the new content position, \a overshootDistance is the + new overshoot distance while \a scrollState indicates if this scroll + event is the first one, the last one or some event in between. +*/ +QScrollEvent::QScrollEvent(const QPointF &contentPos, const QPointF &overshootDistance, ScrollState scrollState) + : QEvent(QEvent::Scroll) +{ + d = reinterpret_cast<QEventPrivate *>(new QScrollEventPrivate()); + d_func()->contentPos = contentPos; + d_func()->overshoot= overshootDistance; + d_func()->state = scrollState; +} + +/*! + Destroys QScrollEvent. +*/ +QScrollEvent::~QScrollEvent() +{ + delete reinterpret_cast<QScrollEventPrivate *>(d); +} + +/*! + Returns the new scroll position. +*/ +QPointF QScrollEvent::contentPos() const +{ + return d_func()->contentPos; +} + +/*! + Returns the new overshoot distance. + See QScroller for an explanation of the term overshoot. + + \sa QScroller +*/ +QPointF QScrollEvent::overshootDistance() const +{ + return d_func()->overshoot; +} + +/*! + Returns the current scroll state as a combination of ScrollStateFlag values. + ScrollStarted (or ScrollFinished) will be set, if this scroll event is the first (or last) event in a scrolling activity. + Please note that both values can be set at the same time, if the activity consists of a single QScrollEvent. + All other scroll events in between will have their state set to ScrollUpdated. + + A widget could for example revert selections when scrolling is started and stopped. +*/ +QScrollEvent::ScrollState QScrollEvent::scrollState() const +{ + return d_func()->state; +} + +/*! + \internal +*/ +QScrollEventPrivate *QScrollEvent::d_func() +{ + return reinterpret_cast<QScrollEventPrivate *>(d); +} + +/*! + \internal +*/ +const QScrollEventPrivate *QScrollEvent::d_func() const +{ + return reinterpret_cast<const QScrollEventPrivate *>(d); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h new file mode 100644 index 0000000000..93c2bc53b6 --- /dev/null +++ b/src/gui/kernel/qevent.h @@ -0,0 +1,942 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENT_H +#define QEVENT_H + +#include <QtGui/qwindowdefs.h> +#include <QtCore/qobject.h> +#include <QtGui/qregion.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> +#include <QtGui/qkeysequence.h> +#include <QtCore/qcoreevent.h> +#include <QtGui/qmime.h> +#include <QtGui/qdrag.h> +#include <QtCore/qvariant.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> +#include <QtCore/qfile.h> + +#ifdef Q_OS_SYMBIAN +class RFile; +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAction; +#ifndef QT_NO_GESTURES +class QGesture; +#endif + +class Q_GUI_EXPORT QInputEvent : public QEvent +{ +public: + QInputEvent(Type type, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + ~QInputEvent(); + inline Qt::KeyboardModifiers modifiers() const { return modState; } + inline void setModifiers(Qt::KeyboardModifiers amodifiers) { modState = amodifiers; } +protected: + Qt::KeyboardModifiers modState; +}; + +class Q_GUI_EXPORT QMouseEvent : public QInputEvent +{ +public: + QMouseEvent(Type type, const QPoint &pos, Qt::MouseButton button, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); + QMouseEvent(Type type, const QPoint &pos, const QPoint &globalPos, + Qt::MouseButton button, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers); + ~QMouseEvent(); + + inline const QPoint &pos() const { return p; } + inline const QPoint &globalPos() const { return g; } + inline int x() const { return p.x(); } + inline int y() const { return p.y(); } + inline int globalX() const { return g.x(); } + inline int globalY() const { return g.y(); } + inline Qt::MouseButton button() const { return b; } + inline Qt::MouseButtons buttons() const { return mouseState; } + + static QMouseEvent *createExtendedMouseEvent(Type type, const QPointF &pos, + const QPoint &globalPos, Qt::MouseButton button, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); + inline bool hasExtendedInfo() const { return reinterpret_cast<const QMouseEvent *>(d) == this; } + QPointF posF() const; + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QMouseEvent(Type type, const QPoint &pos, Qt::ButtonState button, int state); + QT3_SUPPORT_CONSTRUCTOR QMouseEvent(Type type, const QPoint &pos, const QPoint &globalPos, + Qt::ButtonState button, int state); + inline QT3_SUPPORT Qt::ButtonState state() const + { return Qt::ButtonState((mouseState^b)|int(modifiers())); } + inline QT3_SUPPORT Qt::ButtonState stateAfter() const + { return Qt::ButtonState(int(mouseState)|int(modifiers())); } +#endif +protected: + QPoint p, g; + Qt::MouseButton b; + Qt::MouseButtons mouseState; +}; + +class Q_GUI_EXPORT QHoverEvent : public QEvent +{ +public: + QHoverEvent(Type type, const QPoint &pos, const QPoint &oldPos); + ~QHoverEvent(); + + inline const QPoint &pos() const { return p; } + inline const QPoint &oldPos() const { return op; } + +protected: + QPoint p, op; +}; + +#ifndef QT_NO_WHEELEVENT +class Q_GUI_EXPORT QWheelEvent : public QInputEvent +{ +public: + QWheelEvent(const QPoint &pos, int delta, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, + Qt::Orientation orient = Qt::Vertical); + QWheelEvent(const QPoint &pos, const QPoint& globalPos, int delta, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, + Qt::Orientation orient = Qt::Vertical); + ~QWheelEvent(); + + inline int delta() const { return d; } + inline const QPoint &pos() const { return p; } + inline const QPoint &globalPos() const { return g; } + inline int x() const { return p.x(); } + inline int y() const { return p.y(); } + inline int globalX() const { return g.x(); } + inline int globalY() const { return g.y(); } + + inline Qt::MouseButtons buttons() const { return mouseState; } + Qt::Orientation orientation() const { return o; } + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QWheelEvent(const QPoint &pos, int delta, int state, + Qt::Orientation orient = Qt::Vertical); + QT3_SUPPORT_CONSTRUCTOR QWheelEvent(const QPoint &pos, const QPoint& globalPos, int delta, int state, + Qt::Orientation orient = Qt::Vertical); + inline QT3_SUPPORT Qt::ButtonState state() const + { return static_cast<Qt::ButtonState>(int(buttons())|int(modifiers())); } +#endif +protected: + QPoint p; + QPoint g; + int d; + Qt::MouseButtons mouseState; + Qt::Orientation o; +}; +#endif + +#ifndef QT_NO_TABLETEVENT +class Q_GUI_EXPORT QTabletEvent : public QInputEvent +{ +public: + enum TabletDevice { NoDevice, Puck, Stylus, Airbrush, FourDMouse, + XFreeEraser /*internal*/, RotationStylus }; + enum PointerType { UnknownPointer, Pen, Cursor, Eraser }; + QTabletEvent(Type t, const QPoint &pos, const QPoint &globalPos, const QPointF &hiResGlobalPos, + int device, int pointerType, qreal pressure, int xTilt, int yTilt, + qreal tangentialPressure, qreal rotation, int z, + Qt::KeyboardModifiers keyState, qint64 uniqueID); + ~QTabletEvent(); + + inline const QPoint &pos() const { return mPos; } + inline const QPoint &globalPos() const { return mGPos; } + inline const QPointF &hiResGlobalPos() const { return mHiResGlobalPos; } + inline int x() const { return mPos.x(); } + inline int y() const { return mPos.y(); } + inline int globalX() const { return mGPos.x(); } + inline int globalY() const { return mGPos.y(); } + inline qreal hiResGlobalX() const { return mHiResGlobalPos.x(); } + inline qreal hiResGlobalY() const { return mHiResGlobalPos.y(); } + inline TabletDevice device() const { return TabletDevice(mDev); } + inline PointerType pointerType() const { return PointerType(mPointerType); } + inline qint64 uniqueId() const { return mUnique; } + inline qreal pressure() const { return mPress; } + inline int z() const { return mZ; } + inline qreal tangentialPressure() const { return mTangential; } + inline qreal rotation() const { return mRot; } + inline int xTilt() const { return mXT; } + inline int yTilt() const { return mYT; } + +protected: + QPoint mPos, mGPos; + QPointF mHiResGlobalPos; + int mDev, mPointerType, mXT, mYT, mZ; + qreal mPress, mTangential, mRot; + qint64 mUnique; + + // I don't know what the future holds for tablets but there could be some + // new devices coming along, and there seem to be "holes" in the + // OS-specific events for this. + void *mExtra; +}; +#endif // QT_NO_TABLETEVENT + +class Q_GUI_EXPORT QKeyEvent : public QInputEvent +{ +public: + QKeyEvent(Type type, int key, Qt::KeyboardModifiers modifiers, const QString& text = QString(), + bool autorep = false, ushort count = 1); + ~QKeyEvent(); + + int key() const { return k; } +#ifndef QT_NO_SHORTCUT + bool matches(QKeySequence::StandardKey key) const; +#endif + Qt::KeyboardModifiers modifiers() const; + inline QString text() const { return txt; } + inline bool isAutoRepeat() const { return autor; } + inline int count() const { return int(c); } + + // Functions for the extended key event information + static QKeyEvent *createExtendedKeyEvent(Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text = QString(), bool autorep = false, + ushort count = 1); + inline bool hasExtendedInfo() const { return reinterpret_cast<const QKeyEvent*>(d) == this; } + quint32 nativeScanCode() const; + quint32 nativeVirtualKey() const; + quint32 nativeModifiers() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT_CONSTRUCTOR QKeyEvent(Type type, int key, int /*ascii*/, + int modifiers, const QString& text = QString(), + bool autorep = false, ushort count = 1) + : QInputEvent(type, Qt::KeyboardModifiers(modifiers & (int)Qt::KeyButtonMask)), txt(text), k(key), + c(count), autor(autorep) + { + if (key >= Qt::Key_Back && key <= Qt::Key_MediaLast) + ignore(); + } + inline QT3_SUPPORT int ascii() const + { return (txt.length() ? txt.unicode()->toLatin1() : 0); } + inline QT3_SUPPORT Qt::ButtonState state() const { return Qt::ButtonState(QInputEvent::modifiers()); } + inline QT3_SUPPORT Qt::ButtonState stateAfter() const { return Qt::ButtonState(modifiers()); } +#endif + +protected: + QString txt; + int k; + ushort c; + uint autor:1; +}; + + +class Q_GUI_EXPORT QFocusEvent : public QEvent +{ +public: + QFocusEvent(Type type, Qt::FocusReason reason=Qt::OtherFocusReason); + ~QFocusEvent(); + + inline bool gotFocus() const { return type() == FocusIn; } + inline bool lostFocus() const { return type() == FocusOut; } + +#ifdef QT3_SUPPORT + enum Reason { Mouse=Qt::MouseFocusReason, Tab=Qt::TabFocusReason, + Backtab=Qt::BacktabFocusReason, MenuBar=Qt::MenuBarFocusReason, + ActiveWindow=Qt::ActiveWindowFocusReason, Other=Qt::OtherFocusReason, + Popup=Qt::PopupFocusReason, Shortcut=Qt::ShortcutFocusReason }; +#endif + Qt::FocusReason reason(); + Qt::FocusReason reason() const; + +private: + Qt::FocusReason m_reason; +}; + + +class Q_GUI_EXPORT QPaintEvent : public QEvent +{ +public: + QPaintEvent(const QRegion& paintRegion); + QPaintEvent(const QRect &paintRect); + ~QPaintEvent(); + + inline const QRect &rect() const { return m_rect; } + inline const QRegion ®ion() const { return m_region; } + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QPaintEvent(const QRegion &paintRegion, const QRect &paintRect); + inline QT3_SUPPORT bool erased() const { return m_erased; } + inline QT3_SUPPORT void setErased(bool b) { m_erased = b; } +#endif + +protected: + friend class QApplication; + friend class QCoreApplication; + QRect m_rect; + QRegion m_region; + bool m_erased; +}; + +class QUpdateLaterEvent : public QEvent +{ +public: + QUpdateLaterEvent(const QRegion& paintRegion); + ~QUpdateLaterEvent(); + + inline const QRegion ®ion() const { return m_region; } + +protected: + QRegion m_region; +}; + +class Q_GUI_EXPORT QMoveEvent : public QEvent +{ +public: + QMoveEvent(const QPoint &pos, const QPoint &oldPos); + ~QMoveEvent(); + + inline const QPoint &pos() const { return p; } + inline const QPoint &oldPos() const { return oldp;} +protected: + QPoint p, oldp; + friend class QApplication; + friend class QCoreApplication; +}; + + +class Q_GUI_EXPORT QResizeEvent : public QEvent +{ +public: + QResizeEvent(const QSize &size, const QSize &oldSize); + ~QResizeEvent(); + + inline const QSize &size() const { return s; } + inline const QSize &oldSize()const { return olds;} +protected: + QSize s, olds; + friend class QApplication; + friend class QCoreApplication; +}; + + +class Q_GUI_EXPORT QCloseEvent : public QEvent +{ +public: + QCloseEvent(); + ~QCloseEvent(); +}; + + +class Q_GUI_EXPORT QIconDragEvent : public QEvent +{ +public: + QIconDragEvent(); + ~QIconDragEvent(); +}; + + +class Q_GUI_EXPORT QShowEvent : public QEvent +{ +public: + QShowEvent(); + ~QShowEvent(); +}; + + +class Q_GUI_EXPORT QHideEvent : public QEvent +{ +public: + QHideEvent(); + ~QHideEvent(); +}; + +#ifndef QT_NO_CONTEXTMENU +class Q_GUI_EXPORT QContextMenuEvent : public QInputEvent +{ +public: + enum Reason { Mouse, Keyboard, Other }; + + QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos, + Qt::KeyboardModifiers modifiers); + QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos); + QContextMenuEvent(Reason reason, const QPoint &pos); + ~QContextMenuEvent(); + + inline int x() const { return p.x(); } + inline int y() const { return p.y(); } + inline int globalX() const { return gp.x(); } + inline int globalY() const { return gp.y(); } + + inline const QPoint& pos() const { return p; } + inline const QPoint& globalPos() const { return gp; } + + inline Reason reason() const { return Reason(reas); } + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QContextMenuEvent(Reason reason, const QPoint &pos, const QPoint &globalPos, int); + QT3_SUPPORT_CONSTRUCTOR QContextMenuEvent(Reason reason, const QPoint &pos, int); + + QT3_SUPPORT Qt::ButtonState state() const; +#endif +protected: + QPoint p; + QPoint gp; + uint reas : 8; +}; +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_INPUTMETHOD +class Q_GUI_EXPORT QInputMethodEvent : public QEvent +{ +public: + enum AttributeType { + TextFormat, + Cursor, + Language, + Ruby, + Selection + }; + class Attribute { + public: + Attribute(AttributeType t, int s, int l, QVariant val) : type(t), start(s), length(l), value(val) {} + AttributeType type; + + int start; + int length; + QVariant value; + }; + QInputMethodEvent(); + QInputMethodEvent(const QString &preeditText, const QList<Attribute> &attributes); + void setCommitString(const QString &commitString, int replaceFrom = 0, int replaceLength = 0); + + inline const QList<Attribute> &attributes() const { return attrs; } + inline const QString &preeditString() const { return preedit; } + + inline const QString &commitString() const { return commit; } + inline int replacementStart() const { return replace_from; } + inline int replacementLength() const { return replace_length; } + + QInputMethodEvent(const QInputMethodEvent &other); + +private: + QString preedit; + QList<Attribute> attrs; + QString commit; + int replace_from; + int replace_length; +}; +#endif // QT_NO_INPUTMETHOD + +#ifndef QT_NO_DRAGANDDROP + +class QMimeData; + +class Q_GUI_EXPORT QDropEvent : public QEvent +// QT3_SUPPORT + , public QMimeSource +// END QT3_SUPPORT +{ +public: + QDropEvent(const QPoint& pos, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type = Drop); + ~QDropEvent(); + + inline const QPoint &pos() const { return p; } + inline Qt::MouseButtons mouseButtons() const { return mouseState; } + inline Qt::KeyboardModifiers keyboardModifiers() const { return modState; } + + inline Qt::DropActions possibleActions() const { return act; } + inline Qt::DropAction proposedAction() const { return default_action; } + inline void acceptProposedAction() { drop_action = default_action; accept(); } + + inline Qt::DropAction dropAction() const { return drop_action; } + void setDropAction(Qt::DropAction action); + + QWidget* source() const; + inline const QMimeData *mimeData() const { return mdata; } + +// QT3_SUPPORT + const char* format(int n = 0) const; + QByteArray encodedData(const char*) const; + bool provides(const char*) const; +// END QT3_SUPPORT +#ifdef QT3_SUPPORT + inline void accept() { QEvent::accept(); } + inline QT3_SUPPORT void accept(bool y) { setAccepted(y); } + inline QT3_SUPPORT QByteArray data(const char* f) const { return encodedData(f); } + + enum Action { Copy, Link, Move, Private, UserAction = Private }; + QT3_SUPPORT Action action() const; + inline QT3_SUPPORT void acceptAction(bool y = true) { if (y) { drop_action = default_action; accept(); } } + inline QT3_SUPPORT void setPoint(const QPoint& np) { p = np; } +#endif + + +protected: + friend class QApplication; + QPoint p; + Qt::MouseButtons mouseState; + Qt::KeyboardModifiers modState; + Qt::DropActions act; + Qt::DropAction drop_action; + Qt::DropAction default_action; + const QMimeData *mdata; + mutable QList<QByteArray> fmts; // only used for QT3_SUPPORT +}; + + +class Q_GUI_EXPORT QDragMoveEvent : public QDropEvent +{ +public: + QDragMoveEvent(const QPoint &pos, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Type type = DragMove); + ~QDragMoveEvent(); + + inline QRect answerRect() const { return rect; } + + inline void accept() { QDropEvent::accept(); } + inline void ignore() { QDropEvent::ignore(); } + + inline void accept(const QRect & r) { accept(); rect = r; } + inline void ignore(const QRect & r) { ignore(); rect = r; } + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void accept(bool y) { setAccepted(y); } +#endif + +protected: + friend class QApplication; + QRect rect; +}; + + +class Q_GUI_EXPORT QDragEnterEvent : public QDragMoveEvent +{ +public: + QDragEnterEvent(const QPoint &pos, Qt::DropActions actions, const QMimeData *data, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers); + ~QDragEnterEvent(); +}; + + +/* An internal class */ +class Q_GUI_EXPORT QDragResponseEvent : public QEvent +{ +public: + QDragResponseEvent(bool accepted); + ~QDragResponseEvent(); + + inline bool dragAccepted() const { return a; } +protected: + bool a; +}; + + +class Q_GUI_EXPORT QDragLeaveEvent : public QEvent +{ +public: + QDragLeaveEvent(); + ~QDragLeaveEvent(); +}; +#endif // QT_NO_DRAGANDDROP + + +class Q_GUI_EXPORT QHelpEvent : public QEvent +{ +public: + QHelpEvent(Type type, const QPoint &pos, const QPoint &globalPos); + ~QHelpEvent(); + + inline int x() const { return p.x(); } + inline int y() const { return p.y(); } + inline int globalX() const { return gp.x(); } + inline int globalY() const { return gp.y(); } + + inline const QPoint& pos() const { return p; } + inline const QPoint& globalPos() const { return gp; } + +private: + QPoint p; + QPoint gp; +}; + +#ifndef QT_NO_STATUSTIP +class Q_GUI_EXPORT QStatusTipEvent : public QEvent +{ +public: + QStatusTipEvent(const QString &tip); + ~QStatusTipEvent(); + + inline QString tip() const { return s; } +private: + QString s; +}; +#endif + +#ifndef QT_NO_WHATSTHIS +class Q_GUI_EXPORT QWhatsThisClickedEvent : public QEvent +{ +public: + QWhatsThisClickedEvent(const QString &href); + ~QWhatsThisClickedEvent(); + + inline QString href() const { return s; } +private: + QString s; +}; +#endif + +#ifndef QT_NO_ACTION +class Q_GUI_EXPORT QActionEvent : public QEvent +{ + QAction *act, *bef; +public: + QActionEvent(int type, QAction *action, QAction *before = 0); + ~QActionEvent(); + + inline QAction *action() const { return act; } + inline QAction *before() const { return bef; } +}; +#endif + +class Q_GUI_EXPORT QFileOpenEvent : public QEvent +{ +public: + QFileOpenEvent(const QString &file); + QFileOpenEvent(const QUrl &url); +#ifdef Q_OS_SYMBIAN + QFileOpenEvent(const RFile &fileHandle); +#endif + ~QFileOpenEvent(); + + inline QString file() const { return f; } + QUrl url() const; + bool openFile(QFile &file, QIODevice::OpenMode flags) const; +private: + QString f; +}; + +#ifndef QT_NO_TOOLBAR +class Q_GUI_EXPORT QToolBarChangeEvent : public QEvent +{ +public: + QToolBarChangeEvent(bool t); + ~QToolBarChangeEvent(); + + inline bool toggle() const { return tog; } +private: + uint tog : 1; +}; +#endif + +#ifndef QT_NO_SHORTCUT +class Q_GUI_EXPORT QShortcutEvent : public QEvent +{ +public: + QShortcutEvent(const QKeySequence &key, int id, bool ambiguous = false); + ~QShortcutEvent(); + + inline const QKeySequence &key() { return sequence; } + inline const QKeySequence &key() const { return sequence; } + inline int shortcutId() { return sid; } + inline int shortcutId() const { return sid; } + inline bool isAmbiguous() { return ambig; } + inline bool isAmbiguous() const { return ambig; } +protected: + QKeySequence sequence; + bool ambig; + int sid; +}; +#endif + +#ifndef QT_NO_CLIPBOARD +class Q_GUI_EXPORT QClipboardEvent : public QEvent +{ +public: + QClipboardEvent(QEventPrivate *data); + ~QClipboardEvent(); + + QEventPrivate *data() { return d; } +}; +#endif + +class Q_GUI_EXPORT QWindowStateChangeEvent: public QEvent +{ +public: + QWindowStateChangeEvent(Qt::WindowStates aOldState); + QWindowStateChangeEvent(Qt::WindowStates aOldState, bool isOverride); + ~QWindowStateChangeEvent(); + + inline Qt::WindowStates oldState() const { return ostate; } + bool isOverride() const; + +private: + Qt::WindowStates ostate; +}; + +#ifdef QT3_SUPPORT +class QMenuBar; +class Q_GUI_EXPORT QMenubarUpdatedEvent: public QEvent +{ +public: + QMenubarUpdatedEvent(QMenuBar * const menBar); + inline QMenuBar *menuBar() { return m_menuBar; } +private: + QMenuBar *m_menuBar; +}; +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QEvent *); +#endif + +#ifndef QT_NO_SHORTCUT +inline bool operator==(QKeyEvent *e, QKeySequence::StandardKey key){return (e ? e->matches(key) : false);} +inline bool operator==(QKeySequence::StandardKey key, QKeyEvent *e){return (e ? e->matches(key) : false);} +#endif // QT_NO_SHORTCUT + +class QTouchEventTouchPointPrivate; +class Q_GUI_EXPORT QTouchEvent : public QInputEvent +{ +public: + class Q_GUI_EXPORT TouchPoint + { + public: + TouchPoint(int id = -1); + TouchPoint(const QTouchEvent::TouchPoint &other); + ~TouchPoint(); + + int id() const; + + Qt::TouchPointState state() const; + bool isPrimary() const; + + QPointF pos() const; + QPointF startPos() const; + QPointF lastPos() const; + + QPointF scenePos() const; + QPointF startScenePos() const; + QPointF lastScenePos() const; + + QPointF screenPos() const; + QPointF startScreenPos() const; + QPointF lastScreenPos() const; + + QPointF normalizedPos() const; + QPointF startNormalizedPos() const; + QPointF lastNormalizedPos() const; + + QRectF rect() const; + QRectF sceneRect() const; + QRectF screenRect() const; + + qreal pressure() const; + + // internal + void setId(int id); + void setState(Qt::TouchPointStates state); + void setPos(const QPointF &pos); + void setScenePos(const QPointF &scenePos); + void setScreenPos(const QPointF &screenPos); + void setNormalizedPos(const QPointF &normalizedPos); + void setStartPos(const QPointF &startPos); + void setStartScenePos(const QPointF &startScenePos); + void setStartScreenPos(const QPointF &startScreenPos); + void setStartNormalizedPos(const QPointF &startNormalizedPos); + void setLastPos(const QPointF &lastPos); + void setLastScenePos(const QPointF &lastScenePos); + void setLastScreenPos(const QPointF &lastScreenPos); + void setLastNormalizedPos(const QPointF &lastNormalizedPos); + void setRect(const QRectF &rect); + void setSceneRect(const QRectF &sceneRect); + void setScreenRect(const QRectF &screenRect); + void setPressure(qreal pressure); + QTouchEvent::TouchPoint &operator=(const QTouchEvent::TouchPoint &other); + + private: + QTouchEventTouchPointPrivate *d; + friend class QApplication; + friend class QApplicationPrivate; + }; + + enum DeviceType { + TouchScreen, + TouchPad + }; + + QTouchEvent(QEvent::Type eventType, + QTouchEvent::DeviceType deviceType = TouchScreen, + Qt::KeyboardModifiers modifiers = Qt::NoModifier, + Qt::TouchPointStates touchPointStates = 0, + const QList<QTouchEvent::TouchPoint> &touchPoints = QList<QTouchEvent::TouchPoint>()); + ~QTouchEvent(); + + inline QWidget *widget() const { return _widget; } + inline QTouchEvent::DeviceType deviceType() const { return _deviceType; } + inline Qt::TouchPointStates touchPointStates() const { return _touchPointStates; } + inline const QList<QTouchEvent::TouchPoint> &touchPoints() const { return _touchPoints; } + + // internal + inline void setWidget(QWidget *awidget) { _widget = awidget; } + inline void setDeviceType(DeviceType adeviceType) { _deviceType = adeviceType; } + inline void setTouchPointStates(Qt::TouchPointStates aTouchPointStates) { _touchPointStates = aTouchPointStates; } + inline void setTouchPoints(const QList<QTouchEvent::TouchPoint> &atouchPoints) { _touchPoints = atouchPoints; } + +protected: + QWidget *_widget; + QTouchEvent::DeviceType _deviceType; + Qt::TouchPointStates _touchPointStates; + QList<QTouchEvent::TouchPoint> _touchPoints; + + friend class QApplication; + friend class QApplicationPrivate; +}; + +#ifndef QT_NO_GESTURES +class QGesture; +class QGestureEventPrivate; +class Q_GUI_EXPORT QGestureEvent : public QEvent +{ +public: + QGestureEvent(const QList<QGesture *> &gestures); + ~QGestureEvent(); + + QList<QGesture *> gestures() const; + QGesture *gesture(Qt::GestureType type) const; + + QList<QGesture *> activeGestures() const; + QList<QGesture *> canceledGestures() const; + +#ifdef Q_NO_USING_KEYWORD + inline void setAccepted(bool accepted) { QEvent::setAccepted(accepted); } + inline bool isAccepted() const { return QEvent::isAccepted(); } + + inline void accept() { QEvent::accept(); } + inline void ignore() { QEvent::ignore(); } +#else + using QEvent::setAccepted; + using QEvent::isAccepted; + using QEvent::accept; + using QEvent::ignore; +#endif + + void setAccepted(QGesture *, bool); + void accept(QGesture *); + void ignore(QGesture *); + bool isAccepted(QGesture *) const; + + void setAccepted(Qt::GestureType, bool); + void accept(Qt::GestureType); + void ignore(Qt::GestureType); + bool isAccepted(Qt::GestureType) const; + + void setWidget(QWidget *widget); + QWidget *widget() const; + +#ifndef QT_NO_GRAPHICSVIEW + QPointF mapToGraphicsScene(const QPointF &gesturePoint) const; +#endif + +private: + QGestureEventPrivate *d_func(); + const QGestureEventPrivate *d_func() const; + + friend class QApplication; + friend class QGestureManager; +}; +#endif // QT_NO_GESTURES + +class QScrollPrepareEventPrivate; +class Q_GUI_EXPORT QScrollPrepareEvent : public QEvent +{ +public: + QScrollPrepareEvent(const QPointF &startPos); + ~QScrollPrepareEvent(); + + QPointF startPos() const; + + QSizeF viewportSize() const; + QRectF contentPosRange() const; + QPointF contentPos() const; + + void setViewportSize(const QSizeF &size); + void setContentPosRange(const QRectF &rect); + void setContentPos(const QPointF &pos); + +private: + QScrollPrepareEventPrivate *d_func(); + const QScrollPrepareEventPrivate *d_func() const; +}; + + +class QScrollEventPrivate; +class Q_GUI_EXPORT QScrollEvent : public QEvent +{ +public: + enum ScrollState + { + ScrollStarted, + ScrollUpdated, + ScrollFinished + }; + + QScrollEvent(const QPointF &contentPos, const QPointF &overshoot, ScrollState scrollState); + ~QScrollEvent(); + + QPointF contentPos() const; + QPointF overshootDistance() const; + ScrollState scrollState() const; + +private: + QScrollEventPrivate *d_func(); + const QScrollEventPrivate *d_func() const; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QEVENT_H diff --git a/src/gui/kernel/qevent_p.h b/src/gui/kernel/qevent_p.h new file mode 100644 index 0000000000..b79f372d8d --- /dev/null +++ b/src/gui/kernel/qevent_p.h @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENT_P_H +#define QEVENT_P_H + +#include <QtCore/qglobal.h> +#include <QtCore/qurl.h> +#include <QtGui/qevent.h> + +#ifdef Q_OS_SYMBIAN +#include <f32file.h> +#endif + +QT_BEGIN_NAMESPACE + +// +// 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 5: remove +class QKeyEventEx : public QKeyEvent +{ +public: + QKeyEventEx(Type type, int key, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorep, ushort count, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers); + QKeyEventEx(const QKeyEventEx &other); + + ~QKeyEventEx(); + +protected: + quint32 nScanCode; + quint32 nVirtualKey; + quint32 nModifiers; + friend class QKeyEvent; +}; + +// ### Qt 5: remove +class QMouseEventEx : public QMouseEvent +{ +public: + QMouseEventEx(Type type, const QPointF &pos, const QPoint &globalPos, + Qt::MouseButton button, Qt::MouseButtons buttons, + Qt::KeyboardModifiers modifiers); + ~QMouseEventEx(); + +protected: + QPointF posF; + friend class QMouseEvent; +}; + +class QTouchEventTouchPointPrivate +{ +public: + inline QTouchEventTouchPointPrivate(int id) + : ref(1), + id(id), + state(Qt::TouchPointReleased), + pressure(qreal(-1.)) + { } + + inline QTouchEventTouchPointPrivate *detach() + { + QTouchEventTouchPointPrivate *d = new QTouchEventTouchPointPrivate(*this); + d->ref = 1; + if (!this->ref.deref()) + delete this; + return d; + } + + QAtomicInt ref; + int id; + Qt::TouchPointStates state; + QRectF rect, sceneRect, screenRect; + QPointF normalizedPos, + startPos, startScenePos, startScreenPos, startNormalizedPos, + lastPos, lastScenePos, lastScreenPos, lastNormalizedPos; + qreal pressure; +}; + +#ifndef QT_NO_GESTURES +class QNativeGestureEvent : public QEvent +{ +public: + enum Type { + None, + GestureBegin, + GestureEnd, + Pan, + Zoom, + Rotate, + Swipe + }; + + QNativeGestureEvent() + : QEvent(QEvent::NativeGesture), gestureType(None), percentage(0) +#ifdef Q_WS_WIN + , sequenceId(0), argument(0) +#endif + { + } + + Type gestureType; + float percentage; + QPoint position; + float angle; +#ifdef Q_WS_WIN + ulong sequenceId; + quint64 argument; +#endif +}; + +class QGestureEventPrivate +{ +public: + inline QGestureEventPrivate(const QList<QGesture *> &list) + : gestures(list), widget(0) + { + } + + QList<QGesture *> gestures; + QWidget *widget; + QMap<Qt::GestureType, bool> accepted; + QMap<Qt::GestureType, QWidget *> targetWidgets; +}; +#endif // QT_NO_GESTURES + +class QFileOpenEventPrivate +{ +public: + inline QFileOpenEventPrivate(const QUrl &url) + : url(url) + { + } + ~QFileOpenEventPrivate(); + + QUrl url; +#ifdef Q_OS_SYMBIAN + RFile file; +#endif +}; + + +class QScrollPrepareEventPrivate +{ +public: + inline QScrollPrepareEventPrivate() + : target(0) + { + } + + QObject* target; + QPointF startPos; + QSizeF viewportSize; + QRectF contentPosRange; + QPointF contentPos; +}; + +class QScrollEventPrivate +{ +public: + inline QScrollEventPrivate() + { + } + + QPointF contentPos; + QPointF overshoot; + QScrollEvent::ScrollState state; +}; + +QT_END_NAMESPACE + +#endif // QEVENT_P_H diff --git a/src/gui/kernel/qeventdispatcher_glib_qpa.cpp b/src/gui/kernel/qeventdispatcher_glib_qpa.cpp new file mode 100644 index 0000000000..603aa2dbe7 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_glib_qpa.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qeventdispatcher_glib_qpa_p.h" + +#include "qapplication.h" + +#include "qplatformdefs.h" +#include "qapplication.h" + +#include <glib.h> +#include "qapplication_p.h" + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +struct GUserEventSource +{ + GSource source; + QPAEventDispatcherGlib *q; +}; + +static gboolean userEventSourcePrepare(GSource *s, gint *timeout) +{ + Q_UNUSED(s) + Q_UNUSED(timeout) + + return QWindowSystemInterfacePrivate::windowSystemEventsQueued() > 0; +} + +static gboolean userEventSourceCheck(GSource *source) +{ + return userEventSourcePrepare(source, 0); +} + +static gboolean userEventSourceDispatch(GSource *s, GSourceFunc, gpointer) +{ + GUserEventSource * source = reinterpret_cast<GUserEventSource *>(s); + + QWindowSystemInterfacePrivate::WindowSystemEvent * event; + while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) { + event = QWindowSystemInterfacePrivate::getWindowSystemEvent(); + if (!event) + break; + + // send through event filter + if (source->q->filterEvent(event)) { + delete event; + continue; + } + QApplicationPrivate::processWindowSystemEvent(event); + delete event; + } + + return true; +} + + +static GSourceFuncs userEventSourceFuncs = { + userEventSourcePrepare, + userEventSourceCheck, + userEventSourceDispatch, + NULL, + NULL, + NULL +}; + +QPAEventDispatcherGlibPrivate::QPAEventDispatcherGlibPrivate(GMainContext *context) + : QEventDispatcherGlibPrivate(context) +{ + userEventSource = reinterpret_cast<GUserEventSource *>(g_source_new(&userEventSourceFuncs, + sizeof(GUserEventSource))); + userEventSource->q = 0; + g_source_set_can_recurse(&userEventSource->source, true); + g_source_attach(&userEventSource->source, mainContext); +} + + +QPAEventDispatcherGlib::QPAEventDispatcherGlib(QObject *parent) + : QEventDispatcherGlib(*new QPAEventDispatcherGlibPrivate, parent) +{ + Q_D(QPAEventDispatcherGlib); + d->userEventSource->q = this; +} + +QPAEventDispatcherGlib::~QPAEventDispatcherGlib() +{ + Q_D(QPAEventDispatcherGlib); + + g_source_destroy(&d->userEventSource->source); + g_source_unref(&d->userEventSource->source); + d->userEventSource = 0; +} + +bool QPAEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + static bool init = false; + if (!init) { + if (QApplicationPrivate::platformIntegration()->createEventLoopIntegration()) { + qWarning("Eventloop integration is not supported by the glib event dispatcher"); + qWarning("Use the UNIX event dispatcher by defining environment variable QT_NO_GLIB=1"); + } + init = true; + } + return QEventDispatcherGlib::processEvents(flags); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_glib_qpa_p.h b/src/gui/kernel/qeventdispatcher_glib_qpa_p.h new file mode 100644 index 0000000000..701f6735c4 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_glib_qpa_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_GLIB_QPA_P_H +#define QEVENTDISPATCHER_GLIB_QPA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qeventdispatcher_glib_p.h> + +typedef struct _GMainContext GMainContext; + +QT_BEGIN_NAMESPACE +class QPAEventDispatcherGlibPrivate; + +class QPAEventDispatcherGlib : public QEventDispatcherGlib +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPAEventDispatcherGlib) + +public: + explicit QPAEventDispatcherGlib(QObject *parent = 0); + ~QPAEventDispatcherGlib(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); +}; + +struct GUserEventSource; + +class QPAEventDispatcherGlibPrivate : public QEventDispatcherGlibPrivate +{ + Q_DECLARE_PUBLIC(QPAEventDispatcherGlib) +public: + QPAEventDispatcherGlibPrivate(GMainContext *context = 0); + GUserEventSource *userEventSource; +}; + + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_GLIB_QPA_P_H diff --git a/src/gui/kernel/qeventdispatcher_glib_qws.cpp b/src/gui/kernel/qeventdispatcher_glib_qws.cpp new file mode 100644 index 0000000000..7a800cea07 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_glib_qws.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qeventdispatcher_glib_qws_p.h" + +#include "qapplication.h" + +#include "qplatformdefs.h" +#include "qapplication.h" +#include "private/qwscommand_qws_p.h" +#include "qwsdisplay_qws.h" +#include "qwsevent_qws.h" +#include "qwindowsystem_qws.h" + +#include <glib.h> + +QT_BEGIN_NAMESPACE + +// from qapplication_qws.cpp +extern QWSDisplay* qt_fbdpy; // QWS `display' + +//from qwindowsystem_qws.cpp +extern QList<QWSCommand*> *qt_get_server_queue(); + +struct GQWSEventSource +{ + GSource source; + QEventLoop::ProcessEventsFlags flags; + QWSEventDispatcherGlib *q; + QWSEventDispatcherGlibPrivate *d; +}; + +class QWSEventDispatcherGlibPrivate : public QEventDispatcherGlibPrivate +{ + Q_DECLARE_PUBLIC(QWSEventDispatcherGlib) + +public: + QWSEventDispatcherGlibPrivate(); + GQWSEventSource *qwsEventSource; + QList<QWSEvent*> queuedUserInputEvents; +}; + +static gboolean qwsEventSourcePrepare(GSource *s, gint *timeout) +{ + if (timeout) + *timeout = -1; + GQWSEventSource *source = reinterpret_cast<GQWSEventSource *>(s); + return qt_fbdpy->eventPending() || !source->d->queuedUserInputEvents.isEmpty() + || !qt_get_server_queue()->isEmpty() ; +} + +static gboolean qwsEventSourceCheck(GSource *s) +{ + GQWSEventSource *source = reinterpret_cast<GQWSEventSource *>(s); + return qt_fbdpy->eventPending() || !source->d->queuedUserInputEvents.isEmpty() + || !qt_get_server_queue()->isEmpty() ; +} + +static gboolean qwsEventSourceDispatch(GSource *s, GSourceFunc callback, gpointer user_data) +{ + GQWSEventSource *source = reinterpret_cast<GQWSEventSource *>(s); + + //??? ulong marker = XNextRequest(X11->display); + do { + QWSEvent *event; + if (!(source->flags & QEventLoop::ExcludeUserInputEvents) + && !source->d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = source->d->queuedUserInputEvents.takeFirst(); + } else if (qt_fbdpy->eventPending()) { + event = qt_fbdpy->getEvent(); + + if (source->flags & QEventLoop::ExcludeUserInputEvents) { + // queue user input events + + if (event->type == QWSEvent::Mouse || event->type == QWSEvent::Key) { + source->d->queuedUserInputEvents.append(event); + continue; + } + } + } else { + // no event to process + break; + } + + // send through event filter + if (source->q->filterEvent(event)) { + delete event; + continue; + } + + bool ret = qApp->qwsProcessEvent(event) == 1; + delete event; + if (ret) { + return true; + } + + } while (qt_fbdpy->eventPending()); + + if (callback) + callback(user_data); + return true; +} + +static GSourceFuncs qwsEventSourceFuncs = { + qwsEventSourcePrepare, + qwsEventSourceCheck, + qwsEventSourceDispatch, + NULL, + NULL, + NULL +}; + +QWSEventDispatcherGlibPrivate::QWSEventDispatcherGlibPrivate() +{ + qwsEventSource = reinterpret_cast<GQWSEventSource *>(g_source_new(&qwsEventSourceFuncs, + sizeof(GQWSEventSource))); + g_source_set_can_recurse(&qwsEventSource->source, true); + + qwsEventSource->flags = QEventLoop::AllEvents; + qwsEventSource->q = 0; + qwsEventSource->d = 0; + + g_source_attach(&qwsEventSource->source, mainContext); +} + +QWSEventDispatcherGlib::QWSEventDispatcherGlib(QObject *parent) + : QEventDispatcherGlib(*new QWSEventDispatcherGlibPrivate, parent) +{ +} + +QWSEventDispatcherGlib::~QWSEventDispatcherGlib() +{ + Q_D(QWSEventDispatcherGlib); + + g_source_destroy(&d->qwsEventSource->source); + d->qwsEventSource = 0; +} + +bool QWSEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QWSEventDispatcherGlib); + QEventLoop::ProcessEventsFlags saved_flags = d->qwsEventSource->flags; + d->qwsEventSource->flags = flags; + bool returnValue = QEventDispatcherGlib::processEvents(flags); + d->qwsEventSource->flags = saved_flags; + return returnValue; +} + +void QWSEventDispatcherGlib::startingUp() +{ + Q_D(QWSEventDispatcherGlib); + d->qwsEventSource->q = this; + d->qwsEventSource->d = d; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_glib_qws_p.h b/src/gui/kernel/qeventdispatcher_glib_qws_p.h new file mode 100644 index 0000000000..66cb2fc9dc --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_glib_qws_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWSEVENTDISPATCHER_GLIB_P_H +#define QWSEVENTDISPATCHER_GLIB_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qeventdispatcher_glib_p.h> + +QT_BEGIN_NAMESPACE + +class QWSEventDispatcherGlibPrivate; + +class QWSEventDispatcherGlib : public QEventDispatcherGlib +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWSEventDispatcherGlib) + +public: + explicit QWSEventDispatcherGlib(QObject *parent = 0); + ~QWSEventDispatcherGlib(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + + void startingUp(); +}; + +QT_END_NAMESPACE + +#endif // QWSEVENTDISPATCHER_GLIB_P_H diff --git a/src/gui/kernel/qeventdispatcher_mac.mm b/src/gui/kernel/qeventdispatcher_mac.mm new file mode 100644 index 0000000000..677a7368b4 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_mac.mm @@ -0,0 +1,1200 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "private/qt_mac_p.h" +#include "qeventdispatcher_mac_p.h" +#include "qapplication.h" +#include "qevent.h" +#include "qdialog.h" +#include "qhash.h" +#include "qsocketnotifier.h" +#include "private/qwidget_p.h" +#include "private/qthread_p.h" +#include "private/qapplication_p.h" + +#include <private/qcocoaapplication_mac_p.h> +#include "private/qt_cocoa_helpers_mac_p.h" + +#ifndef QT_NO_THREAD +# include "qmutex.h" +#endif + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + Externals + *****************************************************************************/ +extern void qt_event_request_timer(MacTimerInfo *); //qapplication_mac.cpp +extern MacTimerInfo *qt_event_get_timer(EventRef); //qapplication_mac.cpp +extern void qt_event_request_select(QEventDispatcherMac *); //qapplication_mac.cpp +extern void qt_event_request_updates(); //qapplication_mac.cpp +extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp +extern bool qt_is_gui_used; //qapplication.cpp +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp +extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp + +static inline CFRunLoopRef mainRunLoop() +{ +#ifndef QT_MAC_USE_COCOA + return reinterpret_cast<CFRunLoopRef>(const_cast<void *>(GetCFRunLoopFromEventLoop(GetMainEventLoop()))); +#else + return CFRunLoopGetMain(); +#endif +} + +/***************************************************************************** + Timers stuff + *****************************************************************************/ + +/* timer call back */ +void QEventDispatcherMacPrivate::activateTimer(CFRunLoopTimerRef, void *info) +{ + int timerID = +#ifdef Q_OS_MAC64 + qint64(info); +#else + int(info); +#endif + + MacTimerInfo *tmr; + tmr = macTimerHash.value(timerID); + if (tmr == 0 || tmr->pending == true) + return; // Can't send another timer event if it's pending. + + + if (blockSendPostedEvents) { + QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id)); + } else { + tmr->pending = true; + QTimerEvent e(tmr->id); + qt_sendSpontaneousEvent(tmr->obj, &e); + // Get the value again in case the timer gets unregistered during the sendEvent. + tmr = macTimerHash.value(timerID); + if (tmr != 0) + tmr->pending = false; + } + +} + +void QEventDispatcherMac::registerTimer(int timerId, int interval, QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (timerId < 1 || interval < 0 || !obj) { + qWarning("QEventDispatcherMac::registerTimer: invalid arguments"); + return; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::startTimer: timers cannot be started from another thread"); + return; + } +#endif + + MacTimerInfo *t = new MacTimerInfo(); + t->id = timerId; + t->interval = interval; + t->obj = obj; + t->runLoopTimer = 0; + t->pending = false; + + CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent(); + CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001); + fireDate += cfinterval; + QEventDispatcherMacPrivate::macTimerHash.insert(timerId, t); + CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 }; + t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0, + QEventDispatcherMacPrivate::activateTimer, &info); + if (t->runLoopTimer == 0) { + qFatal("QEventDispatcherMac::registerTimer: Cannot create timer"); + } + CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes); +} + +bool QEventDispatcherMac::unregisterTimer(int identifier) +{ +#ifndef QT_NO_DEBUG + if (identifier < 1) { + qWarning("QEventDispatcherMac::unregisterTimer: invalid argument"); + return false; + } else if (thread() != QThread::currentThread()) { + qWarning("QObject::killTimer: timers cannot be stopped from another thread"); + return false; + } +#endif + if (identifier <= 0) + return false; // not init'd or invalid timer + + MacTimerInfo *timerInfo = QEventDispatcherMacPrivate::macTimerHash.take(identifier); + if (timerInfo == 0) + return false; + + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(identifier); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + + return true; +} + +bool QEventDispatcherMac::unregisterTimers(QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (!obj) { + qWarning("QEventDispatcherMac::unregisterTimers: invalid argument"); + return false; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::killTimers: timers cannot be stopped from another thread"); + return false; + } +#endif + + MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.end()) { + MacTimerInfo *timerInfo = it.value(); + if (timerInfo->obj != obj) { + ++it; + } else { + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + it = QEventDispatcherMacPrivate::macTimerHash.erase(it); + } + } + return true; +} + +QList<QEventDispatcherMac::TimerInfo> +QEventDispatcherMac::registeredTimers(QObject *object) const +{ + if (!object) { + qWarning("QEventDispatcherMac:registeredTimers: invalid argument"); + return QList<TimerInfo>(); + } + + QList<TimerInfo> list; + + MacTimerHash::const_iterator it = QEventDispatcherMacPrivate::macTimerHash.constBegin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.constEnd()) { + MacTimerInfo *t = it.value(); + if (t->obj == object) + list << TimerInfo(t->id, t->interval); + ++it; + } + return list; +} + +/************************************************************************** + Socket Notifiers + *************************************************************************/ +void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef, + const void *, void *info) { + QEventDispatcherMacPrivate *const eventDispatcher + = static_cast<QEventDispatcherMacPrivate *>(info); + int nativeSocket = CFSocketGetNative(s); + MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket); + QEvent notifierEvent(QEvent::SockAct); + + // There is a race condition that happen where we disable the notifier and + // the kernel still has a notification to pass on. We then get this + // notification after we've successfully disabled the CFSocket, but our Qt + // notifier is now gone. The upshot is we have to check the notifier + // everytime. + if (callbackType == kCFSocketReadCallBack) { + if (socketInfo->readNotifier) + QApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent); + } else if (callbackType == kCFSocketWriteCallBack) { + if (socketInfo->writeNotifier) + QApplication::sendEvent(socketInfo->writeNotifier, ¬ifierEvent); + } +} + +/* + Adds a loop source for the given socket to the current run loop. +*/ +CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket) +{ + CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); + if (!loopSource) + return 0; + + CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes); + return loopSource; +} + +/* + Removes the loop source for the given socket from the current run loop. +*/ +void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop) +{ + Q_ASSERT(runloop); + CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes); + CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); + CFRunLoopSourceInvalidate(runloop); +} + +/* + Register a QSocketNotifier with the mac event system by creating a CFSocket with + with a read/write callback. + + Qt has separate socket notifiers for reading and writing, but on the mac there is + a limitation of one CFSocket object for each native socket. +*/ +void QEventDispatcherMac::registerSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() + || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); + return; + } +#endif + + Q_D(QEventDispatcherMac); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + + // Check if we have a CFSocket for the native socket, create one if not. + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + socketInfo = new MacSocketInfo(); + + // Create CFSocket, specify that we want both read and write callbacks (the callbacks + // are enabled/disabled later on). + const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack; + CFSocketContext context = {0, d, 0, 0, 0}; + socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context); + if (CFSocketIsValid(socketInfo->socket) == false) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket"); + return; + } + + CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket); + flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write + flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation + CFSocketSetSocketFlags(socketInfo->socket, flags); + + // Add CFSocket to runloop. + if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop"); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + return; + } + + // Disable both callback types by default. This must be done after + // we add the CFSocket to the runloop, or else these calls will have + // no effect. + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + + d->macSockets.insert(nativeSocket, socketInfo); + } + + // Increment read/write counters and select enable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(socketInfo->readNotifier == 0); + socketInfo->readNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(socketInfo->writeNotifier == 0); + socketInfo->writeNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } +} + +/* + Unregister QSocketNotifer. The CFSocket correspoding to this notifier is + removed from the runloop of this is the last notifier that users + that CFSocket. +*/ +void QEventDispatcherMac::unregisterSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); + return; + } +#endif + + Q_D(QEventDispatcherMac); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier"); + return; + } + + // Decrement read/write counters and disable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(notifier == socketInfo->readNotifier); + socketInfo->readNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(notifier == socketInfo->writeNotifier); + socketInfo->writeNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } + + // Remove CFSocket from runloop if this was the last QSocketNotifier. + if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) { + if (CFSocketIsValid(socketInfo->socket)) + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + delete socketInfo; + d->macSockets.remove(nativeSocket); + } +} + +bool QEventDispatcherMac::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); + return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); +} + + +static bool qt_mac_send_event(QEventLoop::ProcessEventsFlags, OSEventRef event, OSWindowRef pt) +{ +#ifndef QT_MAC_USE_COCOA + if(pt && SendEventToWindow(event, pt) != eventNotHandledErr) + return true; + return !SendEventToEventTarget(event, GetEventDispatcherTarget()); +#else // QT_MAC_USE_COCOA + if (pt) + [pt sendEvent:event]; + else + [NSApp sendEvent:event]; + return true; +#endif +} + +#ifdef QT_MAC_USE_COCOA +static bool IsMouseOrKeyEvent( NSEvent* event ) +{ + bool result = false; + + switch( [event type] ) + { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: // ?? + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSMouseEntered: + case NSMouseExited: + case NSKeyDown: + case NSKeyUp: + case NSFlagsChanged: // key modifiers changed? + case NSCursorUpdate: // ?? + case NSScrollWheel: + case NSTabletPoint: + case NSTabletProximity: + case NSOtherMouseDown: + case NSOtherMouseUp: + case NSOtherMouseDragged: +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + case NSEventTypeGesture: // touch events + case NSEventTypeMagnify: + case NSEventTypeSwipe: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: +#endif +#endif // QT_NO_GESTURES + result = true; + break; + + default: + break; + } + return result; +} +#endif + +static inline void qt_mac_waitForMoreEvents() +{ +#ifndef QT_MAC_USE_COCOA + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ; +#else + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +#endif +} + +#ifdef QT_MAC_USE_COCOA +static inline void qt_mac_waitForMoreModalSessionEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSModalPanelRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} +#endif + +bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QEventDispatcherMac); + d->interrupt = false; + +#ifdef QT_MAC_USE_COCOA + bool interruptLater = false; + QtMacInterruptDispatcherHelp::cancelInterruptLater(); +#endif + + // In case we end up recursing while we now process events, make sure + // that we send remaining posted Qt events before this call returns: + wakeUp(); + emit awake(); + + bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents; + bool retVal = false; + forever { + if (d->interrupt) + break; + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSEvent* event = 0; + + // First, send all previously excluded input events, if any: + if (!excludeUserEvents) { + while (!d->queuedUserInputEvents.isEmpty()) { + event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); + if (!filterEvent(event)) { + qt_mac_send_event(flags, event, 0); + retVal = true; + } + [event release]; + } + } + + // If Qt is used as a plugin, or as an extension in a native cocoa + // application, we should not run or stop NSApplication; This will be + // done from the application itself. And if processEvents is called + // manually (rather than from a QEventLoop), we cannot enter a tight + // loop and block this call, but instead we need to return after one flush. + // Finally, if we are to exclude user input events, we cannot call [NSApp run] + // as we then loose control over which events gets dispatched: + const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning]; + const bool canExec_Qt = !excludeUserEvents && + (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ; + + if (canExec_Qt && canExec_3rdParty) { + // We can use exec-mode, meaning that we can stay in a tight loop until + // interrupted. This is mostly an optimization, but it allow us to use + // [NSApp run], which is the normal code path for cocoa applications. + if (NSModalSession session = d->currentModalSession()) { + QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); + while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + qt_mac_waitForMoreModalSessionEvents(); + + if (!d->interrupt && session == d->currentModalSessionCached) { + // Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + } else { + d->nsAppRunCalledByQt = true; + QBoolBlocker execGuard(d->currentExecIsNSAppRun, true); + [NSApp run]; + } + retVal = true; + } else { + // We cannot block the thread (and run in a tight loop). + // Instead we will process all current pending events and return. + d->ensureNSAppInitialized(); + if (NSModalSession session = d->currentModalSession()) { + // INVARIANT: a modal window is executing. + if (!excludeUserEvents) { + // Since we can dispatch all kinds of events, we choose + // to use cocoa's native way of running modal sessions: + if (flags & QEventLoop::WaitForMoreEvents) + qt_mac_waitForMoreModalSessionEvents(); + NSInteger status = [NSApp runModalSession:session]; + if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) { + // INVARIANT: Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + retVal = true; + } else do { + // Dispatch all non-user events (but que non-user events up for later). In + // this case, we need more control over which events gets dispatched, and + // cannot use [NSApp runModalSession:session]: + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSModalPanelRunLoopMode + dequeue: YES]; + + if (event) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + if (!filterEvent(event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + } + } while (!d->interrupt && event != nil); + } else do { + // INVARIANT: No modal window is executing. + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + if (event) { + if (flags & QEventLoop::ExcludeUserInputEvents) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + } + if (!filterEvent(event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + } + } while (!d->interrupt && event != nil); + + // Be sure to flush the Qt posted events when not using exec mode + // (exec mode will always do this call from the event loop source): + if (!d->interrupt) + QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + + // Since the window that holds modality might have changed while processing + // events, we we need to interrupt when we return back the previous process + // event recursion to ensure that we spin the correct modal session. + // We do the interruptLater at the end of the function to ensure that we don't + // disturb the 'wait for more events' below (as deleteLater will post an event): + interruptLater = true; + } +#else + do { + EventRef event; + if (!(flags & QEventLoop::ExcludeUserInputEvents) + && !d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst()); + } else { + OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event); + if(err != noErr) + continue; + // else + if (flags & QEventLoop::ExcludeUserInputEvents) { + UInt32 ekind = GetEventKind(event), + eclass = GetEventClass(event); + switch(eclass) { + case kEventClassQt: + if(ekind != kEventQtRequestContext) + break; + // fall through + case kEventClassMouse: + case kEventClassKeyboard: + d->queuedUserInputEvents.append(event); + continue; + } + } + } + + if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + ReleaseEvent(event); + } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0); + +#endif + + bool canWait = (d->threadData->canWait + && !retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + // INVARIANT: We haven't processed any events yet. And we're told + // to stay inside this function until at least one event is processed. + qt_mac_waitForMoreEvents(); + flags &= ~QEventLoop::WaitForMoreEvents; + } else { + // Done with event processing for now. + // Leave the function: + break; + } + } + + // If we're interrupted, we need to interrupt the _current_ + // recursion as well to check if it is still supposed to be + // executing. This way we wind down the stack until we land + // on a recursion that again calls processEvents (typically + // from QEventLoop), and set interrupt to false: + if (d->interrupt) + interrupt(); + +#ifdef QT_MAC_USE_COCOA + if (interruptLater) + QtMacInterruptDispatcherHelp::interruptLater(); +#endif + + return retVal; +} + +void QEventDispatcherMac::wakeUp() +{ + Q_D(QEventDispatcherMac); + d->serialNumber.ref(); + CFRunLoopSourceSignal(d->postedEventsSource); + CFRunLoopWakeUp(mainRunLoop()); +} + +void QEventDispatcherMac::flush() +{ + if(qApp) { + QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); i++) { + QWidget *tlw = tlws.at(i); + if(tlw->isVisible()) + macWindowFlush(qt_mac_window_for(tlw)); + } + } +} + +/***************************************************************************** + QEventDispatcherMac Implementation + *****************************************************************************/ +MacTimerHash QEventDispatcherMacPrivate::macTimerHash; +bool QEventDispatcherMacPrivate::blockSendPostedEvents = false; +bool QEventDispatcherMacPrivate::interrupt = false; + +#ifdef QT_MAC_USE_COCOA +QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack; +bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false; +bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false; +bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false; +NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0; + +void QEventDispatcherMacPrivate::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 + // QApplication::exec is called, or the application spins the events loop + // manually rather than calling QApplication: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 QApplication::exec. + if (nsAppRunCalledByQt || [NSApp isRunning]) + return; + nsAppRunCalledByQt = true; + QBoolBlocker block1(interrupt, true); + QBoolBlocker block2(currentExecIsNSAppRun, true); + [NSApp run]; +} + +void QEventDispatcherMacPrivate::temporarilyStopAllModalSessions() +{ + // Flush, and Stop, all created modal session, and as + // such, make them pending again. The next call to + // currentModalSession will recreate them again. The + // reason to stop all session like this is that otherwise + // a call [NSApp stop] would not stop NSApp, but rather + // the current modal session. So if we need to stop NSApp + // we need to stop all the modal session first. To avoid changing + // the stacking order of the windows while doing so, we put + // up a block that is used in QCocoaWindow and QCocoaPanel: + int stackSize = cocoaModalSessionStack.size(); + for (int i=0; i<stackSize; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.session) { + [NSApp endModalSession:info.session]; + info.session = 0; + } + } + currentModalSessionCached = 0; +} + +NSModalSession QEventDispatcherMacPrivate::currentModalSession() +{ + // If we have one or more modal windows, this function will create + // a session for each of those, and return the one for the top. + if (currentModalSessionCached) + return currentModalSessionCached; + + if (cocoaModalSessionStack.isEmpty()) + return 0; + + int sessionCount = cocoaModalSessionStack.size(); + for (int i=0; i<sessionCount; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (!info.widget) + continue; + if (info.widget->testAttribute(Qt::WA_DontShowOnScreen)) + continue; + if (!info.session) { + QMacCocoaAutoReleasePool pool; + NSWindow *window = qt_mac_window_for(info.widget); + if (!window) + continue; + + ensureNSAppInitialized(); + QBoolBlocker block1(blockSendPostedEvents, true); + info.nswindow = window; + [(NSWindow*) info.nswindow retain]; + int levelBeforeEnterModal = [window level]; + info.session = [NSApp beginModalSessionForWindow:window]; + // Make sure we don't stack the window lower that it was before + // entering modal, in case it e.g. had the stays-on-top flag set: + if (levelBeforeEnterModal > [window level]) + [window setLevel:levelBeforeEnterModal]; + } + currentModalSessionCached = info.session; + cleanupModalSessionsNeeded = false; + } + return currentModalSessionCached; +} + +static void setChildrenWorksWhenModal(QWidget *widget, bool worksWhenModal) +{ + // For NSPanels (but not NSWindows, sadly), we can set the flag + // worksWhenModal, so that they are active even when they are not modal. + QList<QDialog *> dialogs = widget->findChildren<QDialog *>(); + for (int i=0; i<dialogs.size(); ++i){ + NSWindow *window = qt_mac_window_for(dialogs[i]); + if (window && [window isKindOfClass:[NSPanel class]]) { + [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal]; + if (worksWhenModal && [window isVisible]){ + [window orderFront:window]; + } + } + } +} + +void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal() +{ + // Make the dialog children of the widget + // active. And make the dialog children of + // the previous modal dialog unactive again: + QMacCocoaAutoReleasePool pool; + int size = cocoaModalSessionStack.size(); + if (size > 0){ + if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget) + setChildrenWorksWhenModal(prevModal, true); + if (size > 1){ + if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget) + setChildrenWorksWhenModal(prevModal, false); + } + } +} + +void QEventDispatcherMacPrivate::cleanupModalSessions() +{ + // Go through the list of modal sessions, and end those + // that no longer has a widget assosiated; no widget means + // the the session has logically ended. The reason we wait like + // this to actually end the sessions for real (rather than at the + // point they were marked as stopped), is that ending a session + // when no other session runs below it on the stack will make cocoa + // drop some events on the floor. + QMacCocoaAutoReleasePool pool; + int stackSize = cocoaModalSessionStack.size(); + + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.widget) { + // This session has a widget, and is therefore not marked + // as stopped. So just make it current. There might still be other + // stopped sessions on the stack, but those will be stopped on + // a later "cleanup" call. + currentModalSessionCached = info.session; + break; + } + cocoaModalSessionStack.remove(i); + currentModalSessionCached = 0; + if (info.session) { + [NSApp endModalSession:info.session]; + [(NSWindow *)info.nswindow release]; + } + } + + updateChildrenWorksWhenModal(); + cleanupModalSessionsNeeded = false; +} + +void QEventDispatcherMacPrivate::beginModalSession(QWidget *widget) +{ + // Add a new, empty (null), NSModalSession to the stack. + // It will become active the next time QEventDispatcher::processEvents is called. + // A QCocoaModalSessionInfo is considered pending to become active if the widget pointer + // is non-zero, and the session pointer is zero (it will become active upon a call to + // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if + // the widget pointer is zero, and the session pointer is non-zero (it will be fully + // stopped in cleanupModalSessions()). + QCocoaModalSessionInfo info = {widget, 0, 0}; + cocoaModalSessionStack.push(info); + updateChildrenWorksWhenModal(); + currentModalSessionCached = 0; +} + +void QEventDispatcherMacPrivate::endModalSession(QWidget *widget) +{ + // Mark all sessions attached to widget as pending to be stopped. We do this + // by setting the widget pointer to zero, but leave the session pointer. + // We don't tell cocoa to stop any sessions just yet, because cocoa only understands + // when we stop the _current_ modal session (which is the session on top of + // the stack, and might not belong to 'widget'). + int stackSize = cocoaModalSessionStack.size(); + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.widget == widget) { + info.widget = 0; + if (i == stackSize-1) { + // The top sessions ended. Interrupt the event dispatcher + // to start spinning the correct session immidiatly: + currentModalSessionCached = 0; + cleanupModalSessionsNeeded = true; + QEventDispatcherMac::instance()->interrupt(); + } + } + } +} + +#endif + +QEventDispatcherMacPrivate::QEventDispatcherMacPrivate() +{ +} + +QEventDispatcherMac::QEventDispatcherMac(QObject *parent) + : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent) +{ + Q_D(QEventDispatcherMac); + CFRunLoopSourceContext context; + bzero(&context, sizeof(CFRunLoopSourceContext)); + context.info = d; + context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback; + context.perform = QEventDispatcherMacPrivate::postedEventsSourcePerformCallback; + d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context); + Q_ASSERT(d->postedEventsSource); + CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + + CFRunLoopObserverContext observerContext; + bzero(&observerContext, sizeof(CFRunLoopObserverContext)); + observerContext.info = this; + d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, + true, 0, + QEventDispatcherMacPrivate::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, + QEventDispatcherMacPrivate::firstLoopEntry, + &firstTimeObserverContext); + CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes); +} + +void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef, + CFRunLoopActivity activity, void *info) +{ + if (activity == kCFRunLoopBeforeWaiting) + emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock(); + else + emit static_cast<QEventDispatcherMac*>(info)->awake(); +} + +Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2) +{ + return info1 == info2; +} + +inline static void processPostedEvents(QEventDispatcherMacPrivate *const d, const bool blockSendPostedEvents) +{ + if (blockSendPostedEvents) { + // We're told to not send posted events (because the event dispatcher + // is currently working on setting up the correct session to run). But + // we still need to make sure that we don't fall asleep until pending events + // are sendt, so we just signal this need, and return: + CFRunLoopSourceSignal(d->postedEventsSource); + return; + } + +#ifdef QT_MAC_USE_COCOA + if (d->cleanupModalSessionsNeeded) + d->cleanupModalSessions(); +#endif + + if (d->interrupt) { +#ifdef QT_MAC_USE_COCOA + if (d->currentExecIsNSAppRun) { + // The event dispatcher has been interrupted. But since + // [NSApplication run] is running the event loop, we + // delayed stopping it until now (to let cocoa process + // pending cocoa events first). + if (d->currentModalSessionCached) + d->temporarilyStopAllModalSessions(); + [NSApp stop:NSApp]; + d->cancelWaitForMoreEvents(); + } +#endif + return; + } + + if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) { + d->lastSerial = d->serialNumber; + QApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + } +} + +void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref, + CFRunLoopActivity activity, + void *info) +{ + Q_UNUSED(ref); + Q_UNUSED(activity); +#ifdef QT_MAC_USE_COCOA + QApplicationPrivate::qt_initAfterNSAppStarted(); +#endif + processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); +} + +void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info) +{ + processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); +} + +#ifdef QT_MAC_USE_COCOA +void QEventDispatcherMacPrivate::cancelWaitForMoreEvents() +{ + // In case the event dispatcher is waiting for more + // events somewhere, we post a dummy event to wake it up: + QMacCocoaAutoReleasePool pool; + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint + modifierFlags:0 timestamp:0. windowNumber:0 context:0 + subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; +} +#endif + +void QEventDispatcherMac::interrupt() +{ + Q_D(QEventDispatcherMac); + d->interrupt = true; + wakeUp(); + +#ifndef QT_MAC_USE_COCOA + CFRunLoopStop(mainRunLoop()); +#else + // We do nothing more here than setting d->interrupt = true, and + // poke the event loop if it is sleeping. Actually stopping + // NSApp, or the current modal session, is done inside the send + // posted events callback. We do this to ensure that all current pending + // cocoa events gets delivered before we stop. Otherwise, if we now stop + // the last event loop recursion, cocoa will just drop pending posted + // events on the floor before we get a chance to reestablish a new session. + d->cancelWaitForMoreEvents(); +#endif +} + +QEventDispatcherMac::~QEventDispatcherMac() +{ + Q_D(QEventDispatcherMac); + //timer cleanup + MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.end()) { + MacTimerInfo *t = it.value(); + if (t->runLoopTimer) { + CFRunLoopTimerInvalidate(t->runLoopTimer); + CFRelease(t->runLoopTimer); + } + delete t; + ++it; + } + QEventDispatcherMacPrivate::macTimerHash.clear(); + + // Remove CFSockets from the runloop. + for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) { + MacSocketInfo *socketInfo = (*it); + if (CFSocketIsValid(socketInfo->socket)) { + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + } + } + CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + CFRelease(d->postedEventsSource); + + CFRunLoopObserverInvalidate(d->waitingObserver); + CFRelease(d->waitingObserver); + + CFRunLoopObserverInvalidate(d->firstTimeObserver); + CFRelease(d->firstTimeObserver); +} + +#ifdef QT_MAC_USE_COCOA + +QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0; + +QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : cancelled(false) +{ + // The whole point of this class is that we enable a way to interrupt + // the event dispatcher when returning back to a lower recursion level + // than where interruptLater was called. This is needed to detect if + // [NSApp run] should still be running at the recursion level it is at. + // Since the interrupt is canceled if processEvents is called before + // this object gets deleted, we also avoid interrupting unnecessary. + deleteLater(); +} + +QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp() +{ + if (cancelled) + return; + instance = 0; + QEventDispatcherMac::instance()->interrupt(); +} + +void QtMacInterruptDispatcherHelp::cancelInterruptLater() +{ + if (!instance) + return; + instance->cancelled = true; + delete instance; + instance = 0; +} + +void QtMacInterruptDispatcherHelp::interruptLater() +{ + cancelInterruptLater(); + instance = new QtMacInterruptDispatcherHelp; +} + +#endif + +QT_END_NAMESPACE + diff --git a/src/gui/kernel/qeventdispatcher_mac_p.h b/src/gui/kernel/qeventdispatcher_mac_p.h new file mode 100644 index 0000000000..12fcafbb01 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_mac_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_MAC_P_H +#define QEVENTDISPATCHER_MAC_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 <QtGui/qwindowdefs.h> +#include <QtCore/qhash.h> +#include <QtCore/qstack.h> +#include "private/qabstracteventdispatcher_p.h" +#include "private/qt_mac_p.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA +typedef struct _NSModalSession *NSModalSession; +typedef struct _QCocoaModalSessionInfo { + QPointer<QWidget> widget; + NSModalSession session; + void *nswindow; +} QCocoaModalSessionInfo; +#endif + +class QEventDispatcherMacPrivate; + +class QEventDispatcherMac : public QAbstractEventDispatcher +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QEventDispatcherMac) + +public: + explicit QEventDispatcherMac(QObject *parent = 0); + ~QEventDispatcherMac(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void registerSocketNotifier(QSocketNotifier *notifier); + void unregisterSocketNotifier(QSocketNotifier *notifier); + + void registerTimer(int timerId, int interval, QObject *object); + bool unregisterTimer(int timerId); + bool unregisterTimers(QObject *object); + QList<TimerInfo> registeredTimers(QObject *object) const; + + void wakeUp(); + void flush(); + void interrupt(); + +private: + friend void qt_mac_select_timer_callbk(__EventLoopTimer*, void*); + friend class QApplicationPrivate; +}; + +struct MacTimerInfo { + int id; + int interval; + QObject *obj; + bool pending; + CFRunLoopTimerRef runLoopTimer; + bool operator==(const MacTimerInfo &other) + { + return (id == other.id); + } +}; +typedef QHash<int, MacTimerInfo *> MacTimerHash; + +struct MacSocketInfo { + MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {} + CFSocketRef socket; + CFRunLoopSourceRef runloop; + QObject *readNotifier; + QObject *writeNotifier; +}; +typedef QHash<int, MacSocketInfo *> MacSocketHash; + +class QEventDispatcherMacPrivate : public QAbstractEventDispatcherPrivate +{ + Q_DECLARE_PUBLIC(QEventDispatcherMac) + +public: + QEventDispatcherMacPrivate(); + + static MacTimerHash macTimerHash; + // Set 'blockSendPostedEvents' to true if you _really_ need + // to make sure that qt events are not posted while calling + // low-level cocoa functions (like beginModalForWindow). And + // use a QBoolBlocker to be safe: + static bool blockSendPostedEvents; +#ifdef QT_MAC_USE_COCOA + // The following variables help organizing modal sessions: + static QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; + static bool currentExecIsNSAppRun; + static bool nsAppRunCalledByQt; + static bool cleanupModalSessionsNeeded; + static NSModalSession currentModalSessionCached; + static NSModalSession currentModalSession(); + static void updateChildrenWorksWhenModal(); + static void temporarilyStopAllModalSessions(); + static void beginModalSession(QWidget *widget); + static void endModalSession(QWidget *widget); + static void cancelWaitForMoreEvents(); + static void cleanupModalSessions(); + static void ensureNSAppInitialized(); +#endif + + MacSocketHash macSockets; + QList<void *> queuedUserInputEvents; // List of EventRef in Carbon, and NSEvent * in Cocoa + CFRunLoopSourceRef postedEventsSource; + CFRunLoopObserverRef waitingObserver; + CFRunLoopObserverRef firstTimeObserver; + QAtomicInt serialNumber; + int lastSerial; + static bool interrupt; +private: + static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2); + static void postedEventsSourcePerformCallback(void *info); + static void activateTimer(CFRunLoopTimerRef, void *info); + static void waitingObserverCallback(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); + static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); +}; + +#ifdef QT_MAC_USE_COCOA +class QtMacInterruptDispatcherHelp : public QObject +{ + static QtMacInterruptDispatcherHelp *instance; + bool cancelled; + + QtMacInterruptDispatcherHelp(); + ~QtMacInterruptDispatcherHelp(); + + public: + static void interruptLater(); + static void cancelInterruptLater(); +}; +#endif + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_MAC_P_H diff --git a/src/gui/kernel/qeventdispatcher_qpa.cpp b/src/gui/kernel/qeventdispatcher_qpa.cpp new file mode 100644 index 0000000000..de53618fa8 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_qpa.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qapplication.h" +#include "qeventdispatcher_qpa_p.h" +#include "private/qeventdispatcher_unix_p.h" +#include "qapplication_p.h" +#include "qplatformeventloopintegration_qpa.h" + +#include <QWindowSystemInterface> +#include <QtCore/QElapsedTimer> +#include <QtCore/QAtomicInt> +#include <QtCore/QSemaphore> + +#include <QtCore/QDebug> + +#include <errno.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +class Rendezvous +{ +public: + void checkpoint() + { + if (state.testAndSetOrdered(0,1)) { + semaphore.acquire(); + } else if (state.testAndSetAcquire(1,0)) { + semaphore.release(); + } else { + qWarning("Barrier internal error"); + } + } +private: + QSemaphore semaphore; + QAtomicInt state; +}; + +class SelectWorker : public QThread +{ +public: + SelectWorker(QEventDispatcherQPAPrivate *eventDispatcherPrivate) + : QThread(), + m_edPrivate(eventDispatcherPrivate), + m_retVal(0) + { + } + + void setSelectValues(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds) + { + m_nfds = nfds; + m_readfds = readfds; + m_writefds = writefds; + m_exceptfds = exceptfds; + + + } + + int retVal() const { + return m_retVal; + } + +protected: + void run(); + +private: + QEventDispatcherQPAPrivate *m_edPrivate; + int m_retVal; + + int m_nfds; + fd_set *m_readfds, *m_writefds, *m_exceptfds; +}; + +class QEventDispatcherQPAPrivate : public QEventDispatcherUNIXPrivate +{ + Q_DECLARE_PUBLIC(QEventDispatcherQPA) +public: + QEventDispatcherQPAPrivate() + : eventLoopIntegration(0), + barrierBeforeBlocking(0), + barrierReturnValue(0), + selectReturnMutex(0), + selectWorkerNeedsSync(true), + selectWorkerHasResult(false), + m_integrationInitialised(false), + m_hasIntegration(false), + m_isEventLoopIntegrationRunning(false) + { + + } + + ~QEventDispatcherQPAPrivate() + { + delete selectWorker; + delete eventLoopIntegration; + delete barrierBeforeBlocking; + delete barrierReturnValue; + delete selectReturnMutex; + } + + bool hasIntegration() const + { + if (!m_integrationInitialised) { + QEventDispatcherQPAPrivate *that = const_cast<QEventDispatcherQPAPrivate *>(this); + if (qApp && (qApp->thread() == QThread::currentThread())) { // guiThread + if (QApplicationPrivate::platformIntegration()) { + that->eventLoopIntegration = QApplicationPrivate::platformIntegration()->createEventLoopIntegration(); + if (that->eventLoopIntegration) { + that->selectWorker = new SelectWorker(that); + that->barrierBeforeBlocking = new Rendezvous; + that->barrierReturnValue = new Rendezvous; + that->selectReturnMutex = new QMutex; + that->selectWorker->start(); + that->m_hasIntegration = true; + if (!QElapsedTimer::isMonotonic()) + qWarning("Having eventloop integration without monotonic timers can lead to undefined behaviour"); + } + } + } + that->m_integrationInitialised = true; + } + return m_hasIntegration; + } + + bool isEventLoopIntegrationRunning() const + { + return m_isEventLoopIntegrationRunning; + } + + void runEventLoopIntegration() + { + if (qApp && (qApp->thread() == QThread::currentThread())) { + m_isEventLoopIntegrationRunning = true; + eventLoopIntegration->startEventLoop(); + } + } + + QPlatformEventLoopIntegration *eventLoopIntegration; + Rendezvous *barrierBeforeBlocking; + Rendezvous *barrierReturnValue; + + QMutex *selectReturnMutex; + bool selectWorkerNeedsSync; + bool selectWorkerHasResult; + + SelectWorker *selectWorker; +private: + bool m_integrationInitialised; + bool m_hasIntegration; + bool m_isEventLoopIntegrationRunning; +}; + +QEventDispatcherQPA::QEventDispatcherQPA(QObject *parent) + : QEventDispatcherUNIX(*new QEventDispatcherQPAPrivate, parent) +{ } + +QEventDispatcherQPA::~QEventDispatcherQPA() +{ } + +bool QEventDispatcherQPA::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QEventDispatcherQPA); + + if (d->hasIntegration()) { + if (!d->isEventLoopIntegrationRunning()) { + d->runEventLoopIntegration(); + } + if (d->threadData->quitNow) { + d->eventLoopIntegration->quitEventLoop(); + return false; + } + } + + int nevents = 0; + + // handle gui and posted events + d->interrupt = false; + QApplication::sendPostedEvents(); + + while (!d->interrupt) { // also flushes output buffer ###can be optimized + QWindowSystemInterfacePrivate::WindowSystemEvent *event; + if (!(flags & QEventLoop::ExcludeUserInputEvents) + && QWindowSystemInterfacePrivate::windowSystemEventsQueued() > 0) { + // process a pending user input event + event = QWindowSystemInterfacePrivate::getWindowSystemEvent(); + if (!event) + break; + } else { + break; + } + + if (filterEvent(event)) { + delete event; + continue; + } + nevents++; + + QApplicationPrivate::processWindowSystemEvent(event); + delete event; + } + + if (!d->interrupt) { + if (QEventDispatcherUNIX::processEvents(flags)) { + QEventDispatcherUNIX::processEvents(flags); + return true; + } + } + return (nevents > 0); +} + +bool QEventDispatcherQPA::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); // from qapplication.cpp + return qGlobalPostedEventsCount() || QWindowSystemInterfacePrivate::windowSystemEventsQueued(); +} + +void QEventDispatcherQPA::registerSocketNotifier(QSocketNotifier *notifier) +{ + Q_D(QEventDispatcherQPA); + QEventDispatcherUNIX::registerSocketNotifier(notifier); + if (d->hasIntegration()) + wakeUp(); + +} + +void QEventDispatcherQPA::unregisterSocketNotifier(QSocketNotifier *notifier) +{ + Q_D(QEventDispatcherQPA); + QEventDispatcherUNIX::unregisterSocketNotifier(notifier); + if (d->hasIntegration()) + wakeUp(); +} + +void QEventDispatcherQPA::flush() +{ + if(qApp) + qApp->sendPostedEvents(); +} + +int QEventDispatcherQPA::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout) +{ + Q_D(QEventDispatcherQPA); + int retVal = 0; + if (d->hasIntegration()) { + qint64 timeoutmsec = 0; + if (timeout) + timeoutmsec = timeout->tv_sec * 1000 + (timeout->tv_usec/1000); + d->selectReturnMutex->lock(); + if (d->selectWorkerNeedsSync) { + if (d->selectWorkerHasResult) { + retVal = d->selectWorker->retVal(); + d->selectWorkerHasResult = false; + + d->selectReturnMutex->unlock(); + d->barrierReturnValue->checkpoint(); + d->eventLoopIntegration->setNextTimerEvent(0); + return retVal; + } else { + d->selectWorkerNeedsSync = false; + d->selectWorker->setSelectValues(nfds,readfds, writefds, exceptfds); + d->barrierBeforeBlocking->checkpoint(); + } + } + d->selectReturnMutex->unlock(); + d->eventLoopIntegration->setNextTimerEvent(timeoutmsec); + retVal = 0; //is 0 if select has not returned + } else { + retVal = QEventDispatcherUNIX::select(nfds, readfds, writefds, exceptfds, timeout); + } + return retVal; +} + + +void SelectWorker::run() +{ + + while(true) { + m_retVal = 0; + m_edPrivate->barrierBeforeBlocking->checkpoint(); // wait for mainthread + int tmpRet = qt_safe_select(m_nfds,m_readfds,m_writefds,m_exceptfds,0); + m_edPrivate->selectReturnMutex->lock(); + m_edPrivate->eventLoopIntegration->qtNeedsToProcessEvents(); + + m_edPrivate->selectWorkerNeedsSync = true; + m_edPrivate->selectWorkerHasResult = true; + m_retVal = tmpRet; + + m_edPrivate->selectReturnMutex->unlock(); + m_edPrivate->barrierReturnValue->checkpoint(); + } +} +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_qpa_p.h b/src/gui/kernel/qeventdispatcher_qpa_p.h new file mode 100644 index 0000000000..d4d2be1f38 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_qpa_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_QPA_P_H +#define QEVENTDISPATCHER_QPA_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/qeventdispatcher_unix_p.h" + +QT_BEGIN_NAMESPACE + +class QEventDispatcherQPAPrivate; + +class QEventDispatcherQPA : public QEventDispatcherUNIX +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QEventDispatcherQPA) + +public: + explicit QEventDispatcherQPA(QObject *parent = 0); + ~QEventDispatcherQPA(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void registerSocketNotifier(QSocketNotifier *notifier); + void unregisterSocketNotifier(QSocketNotifier *notifier); + + void flush(); + +protected: + int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout); +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_QPA_P_H diff --git a/src/gui/kernel/qeventdispatcher_qws.cpp b/src/gui/kernel/qeventdispatcher_qws.cpp new file mode 100644 index 0000000000..c5df07ea86 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_qws.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qapplication.h" +#include "private/qwscommand_qws_p.h" +#include "qwsdisplay_qws.h" +#include "qwsevent_qws.h" +#include "qwindowsystem_qws.h" +#include "qeventdispatcher_qws_p.h" +#include "private/qeventdispatcher_unix_p.h" +#ifndef QT_NO_THREAD +# include "qmutex.h" +#endif + +#include <errno.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +class QEventDispatcherQWSPrivate : public QEventDispatcherUNIXPrivate +{ + Q_DECLARE_PUBLIC(QEventDispatcherQWS) +public: + inline QEventDispatcherQWSPrivate() + { } + QList<QWSEvent*> queuedUserInputEvents; +}; + + +QEventDispatcherQWS::QEventDispatcherQWS(QObject *parent) + : QEventDispatcherUNIX(*new QEventDispatcherQWSPrivate, parent) +{ } + +QEventDispatcherQWS::~QEventDispatcherQWS() +{ } + + + +// from qapplication_qws.cpp +extern QWSDisplay* qt_fbdpy; // QWS `display' + +//#define ZERO_FOR_THE_MOMENT + +bool QEventDispatcherQWS::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QEventDispatcherQWS); + // process events from the QWS server + int nevents = 0; + + // handle gui and posted events + d->interrupt = false; + QApplication::sendPostedEvents(); + + while (!d->interrupt) { // also flushes output buffer ###can be optimized + QWSEvent *event; + if (!(flags & QEventLoop::ExcludeUserInputEvents) + && !d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = d->queuedUserInputEvents.takeFirst(); + } else if (qt_fbdpy->eventPending()) { + event = qt_fbdpy->getEvent(); // get next event + if (flags & QEventLoop::ExcludeUserInputEvents) { + // queue user input events + if (event->type == QWSEvent::Mouse || event->type == QWSEvent::Key) { + d->queuedUserInputEvents.append(event); + continue; + } + } + } else { + break; + } + + if (filterEvent(event)) { + delete event; + continue; + } + nevents++; + + bool ret = qApp->qwsProcessEvent(event) == 1; + delete event; + if (ret) { + return true; + } + } + + if (!d->interrupt) { + extern QList<QWSCommand*> *qt_get_server_queue(); + if (!qt_get_server_queue()->isEmpty()) { + QWSServer::processEventQueue(); + } + + if (QEventDispatcherUNIX::processEvents(flags)) + return true; + } + return (nevents > 0); +} + +bool QEventDispatcherQWS::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); // from qapplication.cpp + return qGlobalPostedEventsCount() || qt_fbdpy->eventPending(); +} + +void QEventDispatcherQWS::startingUp() +{ + +} + +void QEventDispatcherQWS::closingDown() +{ + +} + +void QEventDispatcherQWS::flush() +{ + if(qApp) + qApp->sendPostedEvents(); + (void)qt_fbdpy->eventPending(); // flush +} + + +int QEventDispatcherQWS::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout) +{ + return QEventDispatcherUNIX::select(nfds, readfds, writefds, exceptfds, timeout); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_qws_p.h b/src/gui/kernel/qeventdispatcher_qws_p.h new file mode 100644 index 0000000000..e73ed6ce5e --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_qws_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_QWS_P_H +#define QEVENTDISPATCHER_QWS_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/qeventdispatcher_unix_p.h" + +QT_BEGIN_NAMESPACE + +class QEventDispatcherQWSPrivate; + +class QEventDispatcherQWS : public QEventDispatcherUNIX +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QEventDispatcherQWS) + +public: + explicit QEventDispatcherQWS(QObject *parent = 0); + ~QEventDispatcherQWS(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void flush(); + + void startingUp(); + void closingDown(); + +protected: + int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout); +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_QWS_P_H diff --git a/src/gui/kernel/qeventdispatcher_s60.cpp b/src/gui/kernel/qeventdispatcher_s60.cpp new file mode 100644 index 0000000000..2d92c89c07 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_s60.cpp @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qwidget.h> + +#include "qeventdispatcher_s60_p.h" + +QT_BEGIN_NAMESPACE + +QtEikonEnv::QtEikonEnv() + : m_lastIterationCount(0) + , m_savedStatusCode(KRequestPending) + , m_hasAlreadyRun(false) +{ +} + +QtEikonEnv::~QtEikonEnv() +{ +} + +void QtEikonEnv::RunL() +{ + QEventDispatcherS60 *dispatcher = qobject_cast<QEventDispatcherS60 *>(QAbstractEventDispatcher::instance()); + if (!dispatcher) { + CEikonEnv::RunL(); + return; + } + + if (m_lastIterationCount != dispatcher->iterationCount()) { + m_hasAlreadyRun = false; + m_lastIterationCount = dispatcher->iterationCount(); + } + + if (m_hasAlreadyRun) { + // Fool the active scheduler into believing we are still waiting for events. + // The window server thinks we are not, however. + m_savedStatusCode = iStatus.Int(); + iStatus = KRequestPending; + SetActive(); + dispatcher->queueDeferredActiveObjectsCompletion(); + } else { + m_hasAlreadyRun = true; + CEikonEnv::RunL(); + } +} + +void QtEikonEnv::DoCancel() +{ + complete(); + + CEikonEnv::DoCancel(); +} + +void QtEikonEnv::complete() +{ + if (m_hasAlreadyRun) { + if (m_savedStatusCode != KRequestPending) { + TRequestStatus *status = &iStatus; + QEventDispatcherSymbian::RequestComplete(status, m_savedStatusCode); + m_savedStatusCode = KRequestPending; + } + m_hasAlreadyRun = false; + } +} + +QEventDispatcherS60::QEventDispatcherS60(QObject *parent) + : QEventDispatcherSymbian(parent), + m_noInputEvents(false) +{ +} + +QEventDispatcherS60::~QEventDispatcherS60() +{ + for (int c = 0; c < m_deferredInputEvents.size(); ++c) { + delete m_deferredInputEvents[c].event; + } +} + +bool QEventDispatcherS60::processEvents ( QEventLoop::ProcessEventsFlags flags ) +{ + bool ret = false; + + QT_TRY { + bool oldNoInputEventsValue = m_noInputEvents; + if (flags & QEventLoop::ExcludeUserInputEvents) { + m_noInputEvents = true; + } else { + m_noInputEvents = false; + ret = sendDeferredInputEvents() || ret; + } + + ret = QEventDispatcherSymbian::processEvents(flags) || ret; + + m_noInputEvents = oldNoInputEventsValue; + } QT_CATCH (const std::exception& ex) { +#ifndef QT_NO_EXCEPTIONS + CActiveScheduler::Current()->Error(qt_symbian_exception2Error(ex)); +#endif + } + + return ret; +} + +bool QEventDispatcherS60::hasPendingEvents() +{ + return !m_deferredInputEvents.isEmpty() || QEventDispatcherSymbian::hasPendingEvents(); +} + +void QEventDispatcherS60::saveInputEvent(QSymbianControl *control, QWidget *widget, QInputEvent *event) +{ + DeferredInputEvent inputEvent = {control, widget, event}; + m_deferredInputEvents.append(inputEvent); + connect(widget, SIGNAL(destroyed(QObject*)), SLOT(removeInputEventsForWidget(QObject*))); +} + +bool QEventDispatcherS60::sendDeferredInputEvents() +{ + bool eventsSent = false; + while (!m_deferredInputEvents.isEmpty()) { + DeferredInputEvent inputEvent = m_deferredInputEvents.takeFirst(); +#ifndef QT_NO_EXCEPTIONS + try { +#endif + inputEvent.control->sendInputEvent(inputEvent.widget, inputEvent.event); +#ifndef QT_NO_EXCEPTIONS + } catch (...) { + delete inputEvent.event; + throw; + } +#endif + delete inputEvent.event; + eventsSent = true; + } + + return eventsSent; +} + +void QEventDispatcherS60::removeInputEventsForWidget(QObject *object) +{ + for (int c = 0; c < m_deferredInputEvents.size(); ++c) { + if (m_deferredInputEvents[c].widget == object) { + delete m_deferredInputEvents[c].event; + m_deferredInputEvents.removeAt(c--); + } + } +} + +// reimpl +void QEventDispatcherS60::reactivateDeferredActiveObjects() +{ + if (S60->qtOwnsS60Environment) { + static_cast<QtEikonEnv *>(CCoeEnv::Static())->complete(); + } + + QEventDispatcherSymbian::reactivateDeferredActiveObjects(); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_s60_p.h b/src/gui/kernel/qeventdispatcher_s60_p.h new file mode 100644 index 0000000000..7c5a8d03d4 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_s60_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_S60_P_H +#define QEVENTDISPATCHER_S60_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/qeventdispatcher_symbian_p.h> +#include "qt_s60_p.h" + +#include <eikenv.h> + +QT_BEGIN_NAMESPACE + +class QEventDispatcherS60; + +class QtEikonEnv : public CEikonEnv +{ +public: + QtEikonEnv(); + ~QtEikonEnv(); + + // from CActive. + void RunL(); + void DoCancel(); + + void complete(); + +private: + // Workaround for a BC break from S60 3.2 -> 5.0, where the CEikonEnv override was removed. + // To avoid linking to that when we build against 3.2, define an empty body here. + // Reserved_*() have been verified to be empty in the S60 code. + void Reserved_1() {} + void Reserved_2() {} + +private: + int m_lastIterationCount; + TInt m_savedStatusCode; + bool m_hasAlreadyRun; +}; + +class Q_GUI_EXPORT QEventDispatcherS60 : public QEventDispatcherSymbian +{ + Q_OBJECT + +public: + QEventDispatcherS60(QObject *parent = 0); + ~QEventDispatcherS60(); + + bool processEvents ( QEventLoop::ProcessEventsFlags flags ); + bool hasPendingEvents(); + + bool excludeUserInputEvents() { return m_noInputEvents; } + + void saveInputEvent(QSymbianControl *control, QWidget *widget, QInputEvent *event); + + void reactivateDeferredActiveObjects(); + +private: + bool sendDeferredInputEvents(); + +private Q_SLOTS: + void removeInputEventsForWidget(QObject *object); + +private: + bool m_noInputEvents; + + struct DeferredInputEvent + { + QSymbianControl *control; + QWidget *widget; + QInputEvent *event; + }; + QList<DeferredInputEvent> m_deferredInputEvents; +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_S60_P_H diff --git a/src/gui/kernel/qeventdispatcher_x11.cpp b/src/gui/kernel/qeventdispatcher_x11.cpp new file mode 100644 index 0000000000..110786a378 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_x11.cpp @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qeventdispatcher_x11_p.h" + +#include "qapplication.h" +#include "qx11info_x11.h" + +#include "qt_x11_p.h" +#include <private/qeventdispatcher_unix_p.h> + +QT_BEGIN_NAMESPACE + +class QEventDispatcherX11Private : public QEventDispatcherUNIXPrivate +{ + Q_DECLARE_PUBLIC(QEventDispatcherX11) +public: + inline QEventDispatcherX11Private() + : xfd(-1) + { } + int xfd; + QList<XEvent> queuedUserInputEvents; +}; + +QEventDispatcherX11::QEventDispatcherX11(QObject *parent) + : QEventDispatcherUNIX(*new QEventDispatcherX11Private, parent) +{ } + +QEventDispatcherX11::~QEventDispatcherX11() +{ } + +bool QEventDispatcherX11::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QEventDispatcherX11); + + d->interrupt = false; + QApplication::sendPostedEvents(); + + ulong marker = XNextRequest(X11->display); + int nevents = 0; + do { + while (!d->interrupt) { + XEvent event; + if (!(flags & QEventLoop::ExcludeUserInputEvents) + && !d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = d->queuedUserInputEvents.takeFirst(); + } else if (XEventsQueued(X11->display, QueuedAlready)) { + // process events from the X server + XNextEvent(X11->display, &event); + + if (flags & QEventLoop::ExcludeUserInputEvents) { + // queue user input events + switch (event.type) { + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case XKeyPress: + case XKeyRelease: + case EnterNotify: + case LeaveNotify: + d->queuedUserInputEvents.append(event); + continue; + + case ClientMessage: + // only keep the wm_take_focus and + // _qt_scrolldone protocols, queue all other + // client messages + if (event.xclient.format == 32) { + if (event.xclient.message_type == ATOM(WM_PROTOCOLS) && + (Atom) event.xclient.data.l[0] == ATOM(WM_TAKE_FOCUS)) { + break; + } else if (event.xclient.message_type == ATOM(_QT_SCROLL_DONE)) { + break; + } + } + d->queuedUserInputEvents.append(event); + continue; + + default: + break; + } + } + } else { + // no event to process + break; + } + + // send through event filter + if (filterEvent(&event)) + continue; + + nevents++; + if (qApp->x11ProcessEvent(&event) == 1) + return true; + + if (event.xany.serial >= marker) { + if (XEventsQueued(X11->display, QueuedAfterFlush)) + flags &= ~QEventLoop::WaitForMoreEvents; + goto out; + } + } + } while (!d->interrupt && XEventsQueued(X11->display, QueuedAfterFlush)); + + out: + if (!d->interrupt) { + const uint exclude_all = + QEventLoop::ExcludeSocketNotifiers | QEventLoop::X11ExcludeTimers | QEventLoop::WaitForMoreEvents; + if (nevents > 0 && ((uint)flags & exclude_all) == exclude_all) { + QApplication::sendPostedEvents(); + return nevents > 0; + } + // return true if we handled events, false otherwise + return QEventDispatcherUNIX::processEvents(flags) || (nevents > 0); + } + return nevents > 0; +} + +bool QEventDispatcherX11::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); // from qapplication.cpp + return (qGlobalPostedEventsCount() || XPending(X11->display)); +} + +void QEventDispatcherX11::flush() +{ + XFlush(X11->display); +} + +void QEventDispatcherX11::startingUp() +{ + Q_D(QEventDispatcherX11); + d->xfd = XConnectionNumber(X11->display); +} + +void QEventDispatcherX11::closingDown() +{ + Q_D(QEventDispatcherX11); + d->xfd = -1; +} + +int QEventDispatcherX11::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout) +{ + Q_D(QEventDispatcherX11); + if (d->xfd > 0) { + nfds = qMax(nfds - 1, d->xfd) + 1; + FD_SET(d->xfd, readfds); + } + return QEventDispatcherUNIX::select(nfds, readfds, writefds, exceptfds, timeout); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qeventdispatcher_x11_p.h b/src/gui/kernel/qeventdispatcher_x11_p.h new file mode 100644 index 0000000000..cfdd2a5fa6 --- /dev/null +++ b/src/gui/kernel/qeventdispatcher_x11_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_X11_P_H +#define QEVENTDISPATCHER_X11_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/qeventdispatcher_unix_p.h" + +QT_BEGIN_NAMESPACE + +class QEventDispatcherX11Private; + +class QEventDispatcherX11 : public QEventDispatcherUNIX +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QEventDispatcherX11) + +public: + explicit QEventDispatcherX11(QObject *parent = 0); + ~QEventDispatcherX11(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void flush(); + + void startingUp(); + void closingDown(); + +protected: + int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + timeval *timeout); +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_X11_P_H diff --git a/src/gui/kernel/qformlayout.cpp b/src/gui/kernel/qformlayout.cpp new file mode 100644 index 0000000000..d098c01f0f --- /dev/null +++ b/src/gui/kernel/qformlayout.cpp @@ -0,0 +1,2079 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qdebug.h" +#include "qformlayout.h" +#include "qlabel.h" +#include "qlayout_p.h" +#include "qlayoutengine_p.h" +#include "qrect.h" +#include "qvector.h" +#include "qwidget.h" + +QT_BEGIN_NAMESPACE + +namespace { +// Fixed column matrix, stores items as [i11, i12, i21, i22...], +// with FORTRAN-style index operator(r, c). +template <class T, int NumColumns> +class FixedColumnMatrix { +public: + typedef QVector<T> Storage; + + FixedColumnMatrix() { } + + void clear() { m_storage.clear(); } + + const T &operator()(int r, int c) const { return m_storage[r * NumColumns + c]; } + T &operator()(int r, int c) { return m_storage[r * NumColumns + c]; } + + int rowCount() const { return m_storage.size() / NumColumns; } + void addRow(const T &value); + void insertRow(int r, const T &value); + void removeRow(int r); + + bool find(const T &value, int *rowPtr, int *colPtr) const ; + int count(const T &value) const { return m_storage.count(value); } + + // Hmmpf.. Some things are faster that way. + const Storage &storage() const { return m_storage; } + + static void storageIndexToPosition(int idx, int *rowPtr, int *colPtr); + +private: + Storage m_storage; +}; + +template <class T, int NumColumns> +void FixedColumnMatrix<T, NumColumns>::addRow(const T &value) +{ + for (int i = 0; i < NumColumns; ++i) + m_storage.append(value); +} + +template <class T, int NumColumns> +void FixedColumnMatrix<T, NumColumns>::insertRow(int r, const T &value) +{ + Q_TYPENAME Storage::iterator it = m_storage.begin(); + it += r * NumColumns; + m_storage.insert(it, NumColumns, value); +} + +template <class T, int NumColumns> +void FixedColumnMatrix<T, NumColumns>::removeRow(int r) +{ + m_storage.remove(r * NumColumns, NumColumns); +} + +template <class T, int NumColumns> +bool FixedColumnMatrix<T, NumColumns>::find(const T &value, int *rowPtr, int *colPtr) const +{ + const int idx = m_storage.indexOf(value); + if (idx == -1) + return false; + storageIndexToPosition(idx, rowPtr, colPtr); + return true; +} + +template <class T, int NumColumns> +void FixedColumnMatrix<T, NumColumns>::storageIndexToPosition(int idx, int *rowPtr, int *colPtr) +{ + *rowPtr = idx / NumColumns; + *colPtr = idx % NumColumns; +} +} // namespace + +// special values for unset fields; must not clash with values of FieldGrowthPolicy or +// RowWrapPolicy +const uint DefaultFieldGrowthPolicy = 255; +const uint DefaultRowWrapPolicy = 255; + +enum { ColumnCount = 2 }; + +// -- our data structure for our items +// This owns the QLayoutItem +struct QFormLayoutItem +{ + QFormLayoutItem(QLayoutItem* i) : item(i), fullRow(false), isHfw(false) { } + ~QFormLayoutItem() { delete item; } + + // Wrappers + QWidget *widget() const { return item->widget(); } + QLayout *layout() const { return item->layout(); } + + bool hasHeightForWidth() const { return item->hasHeightForWidth(); } + int heightForWidth(int width) const { return item->heightForWidth(width); } + int minimumHeightForWidth(int width) const { return item->minimumHeightForWidth(width); } + Qt::Orientations expandingDirections() const { return item->expandingDirections(); } + QSizePolicy::ControlTypes controlTypes() const { return item->controlTypes(); } + int vStretch() const { return widget() ? widget()->sizePolicy().verticalStretch() : 0; } + + void setGeometry(const QRect& r) { item->setGeometry(r); } + QRect geometry() const { return item->geometry(); } + + // For use with FixedColumnMatrix + bool operator==(const QFormLayoutItem& other) { return item == other.item; } + + QLayoutItem *item; + bool fullRow; + + // set by updateSizes + bool isHfw; + QSize minSize; + QSize sizeHint; + QSize maxSize; + + // also set by updateSizes + int sbsHSpace; // only used for side by side, for the field item only (not label) + int vSpace; // This is the spacing to the item in the row above + + // set by setupVerticalLayoutData + bool sideBySide; + int vLayoutIndex; + + // set by setupHorizontalLayoutData + int layoutPos; + int layoutWidth; +}; + +class QFormLayoutPrivate : public QLayoutPrivate +{ + Q_DECLARE_PUBLIC(QFormLayout) + +public: + typedef FixedColumnMatrix<QFormLayoutItem *, ColumnCount> ItemMatrix; + + QFormLayoutPrivate(); + ~QFormLayoutPrivate() { } + + int insertRow(int row); + void insertRows(int row, int count); + void setItem(int row, QFormLayout::ItemRole role, QLayoutItem *item); + void setLayout(int row, QFormLayout::ItemRole role, QLayout *layout); + void setWidget(int row, QFormLayout::ItemRole role, QWidget *widget); + + void arrangeWidgets(const QVector<QLayoutStruct>& layouts, QRect &rect); + + void updateSizes(); + + void setupVerticalLayoutData(int width); + void setupHorizontalLayoutData(int width); + + QStyle* getStyle() const; + + inline bool haveHfwCached(int width) const + { + return (hfw_width == width) || (width == sh_width && hfw_sh_height >= 0); + } + + void recalcHFW(int w); + void setupHfwLayoutData(); + + uint fieldGrowthPolicy : 8; + uint rowWrapPolicy : 8; + uint has_hfw : 2; + uint dirty : 2; // have we laid out yet? + uint sizesDirty : 2; // have we (not) gathered layout item sizes? + uint expandVertical : 1; // Do we expand vertically? + uint expandHorizontal : 1; // Do we expand horizonally? + Qt::Alignment labelAlignment; + Qt::Alignment formAlignment; + + ItemMatrix m_matrix; + QList<QFormLayoutItem *> m_things; + + int layoutWidth; // the last width that we called setupVerticalLayoutData on (for vLayouts) + + int hfw_width; // the last width we calculated HFW for + int hfw_height; // what that height was + int hfw_minheight; // what that minheight was + + int hfw_sh_height; // the hfw for sh_width + int hfw_sh_minheight; // the minhfw for sh_width + + int min_width; // the width that gets turned into minSize (from updateSizes) + int sh_width; // the width that gets turned into prefSize (from updateSizes) + int thresh_width; // the width that we start splitting label/field pairs at (from updateSizes) + QSize minSize; + QSize prefSize; + int formMaxWidth; + void calcSizeHints(); + + QVector<QLayoutStruct> vLayouts; // set by setupVerticalLayoutData; + int vLayoutCount; // Number of rows we calculated in setupVerticalLayoutData + int maxLabelWidth; // the label width we calculated in setupVerticalLayoutData + + QVector<QLayoutStruct> hfwLayouts; + + int hSpacing; + int vSpacing; +}; + +QFormLayoutPrivate::QFormLayoutPrivate() + : fieldGrowthPolicy(DefaultFieldGrowthPolicy), + rowWrapPolicy(DefaultRowWrapPolicy), has_hfw(false), dirty(true), sizesDirty(true), + expandVertical(0), expandHorizontal(0), labelAlignment(0), formAlignment(0), + layoutWidth(-1), hfw_width(-1), hfw_sh_height(-1), min_width(-1), + sh_width(-1), thresh_width(QLAYOUTSIZE_MAX), hSpacing(-1), vSpacing(-1) +{ +} + +static Qt::Alignment fixedAlignment(Qt::Alignment alignment, Qt::LayoutDirection layoutDirection) +{ + if (layoutDirection == Qt::RightToLeft && alignment & Qt::AlignAbsolute) { + // swap left and right, and eliminate absolute flag + return Qt::Alignment((alignment & ~(Qt::AlignLeft | Qt::AlignRight | Qt::AlignAbsolute)) + | ((alignment & Qt::AlignRight) ? Qt::AlignLeft : 0) + | ((alignment & Qt::AlignLeft) ? Qt::AlignRight : 0)); + } else { + return alignment & ~Qt::AlignAbsolute; + } +} + +static int storageIndexFromLayoutItem(const QFormLayoutPrivate::ItemMatrix &m, + QFormLayoutItem *item) +{ + if (item) { + return m.storage().indexOf(item); + } else { + return -1; + } +} + +static void updateFormLayoutItem(QFormLayoutItem *item, int userVSpacing, + QFormLayout::FieldGrowthPolicy fieldGrowthPolicy, + bool fullRow) +{ + item->minSize = item->item->minimumSize(); + item->sizeHint = item->item->sizeHint(); + item->maxSize = item->item->maximumSize(); + + if (!fullRow && (fieldGrowthPolicy == QFormLayout::FieldsStayAtSizeHint + || (fieldGrowthPolicy == QFormLayout::ExpandingFieldsGrow + && !(item->item->expandingDirections() & Qt::Horizontal)))) + item->maxSize.setWidth(item->sizeHint.width()); + + item->isHfw = item->item->hasHeightForWidth(); + item->vSpace = userVSpacing; +} + +/* + Iterate over all the controls and gather their size information + (min, sizeHint and max). Also work out what the spacing between + pairs of controls should be, and figure out the min and sizeHint + widths. +*/ +void QFormLayoutPrivate::updateSizes() +{ + Q_Q(QFormLayout); + + if (sizesDirty) { + QFormLayout::RowWrapPolicy wrapPolicy = q->rowWrapPolicy(); + bool wrapAllRows = (wrapPolicy == QFormLayout::WrapAllRows); + bool dontWrapRows = (wrapPolicy == QFormLayout::DontWrapRows); + int rr = m_matrix.rowCount(); + + has_hfw = false; + + // If any control can expand, so can this layout + // Wrapping doesn't affect expansion, though, just the minsize + bool expandH = false; + bool expandV = false; + + QFormLayoutItem *prevLbl = 0; + QFormLayoutItem *prevFld = 0; + + QWidget *parent = q->parentWidget(); + QStyle *style = parent ? parent->style() : 0; + + int userVSpacing = q->verticalSpacing(); + int userHSpacing = wrapAllRows ? 0 : q->horizontalSpacing(); + + int maxMinLblWidth = 0; + int maxMinFldWidth = 0; // field with label + int maxMinIfldWidth = 0; // independent field + + int maxShLblWidth = 0; + int maxShFldWidth = 0; + int maxShIfldWidth = 0; + + for (int i = 0; i < rr; ++i) { + QFormLayoutItem *label = m_matrix(i, 0); + QFormLayoutItem *field = m_matrix(i, 1); + + // Skip empty rows + if (!label && !field) + continue; + + if (label) { + updateFormLayoutItem(label, userVSpacing, q->fieldGrowthPolicy(), false); + if (label->isHfw) + has_hfw = true; + Qt::Orientations o = label->expandingDirections(); + + if (o & Qt::Vertical) + expandV = true; + if (o & Qt::Horizontal) + expandH = true; + } + if (field) { + updateFormLayoutItem(field, userVSpacing, q->fieldGrowthPolicy(), !label && field->fullRow); + field->sbsHSpace = (!label && field->fullRow) ? 0 : userHSpacing; + if (field->isHfw) + has_hfw = true; + + Qt::Orientations o = field->expandingDirections(); + + if (o & Qt::Vertical) + expandV = true; + if (o & Qt::Horizontal) + expandH = true; + } + + // See if we need to calculate default spacings + if ((userHSpacing < 0 || userVSpacing < 0) && style) { + QSizePolicy::ControlTypes lbltypes = + QSizePolicy::ControlTypes(label ? label->controlTypes() : QSizePolicy::DefaultType); + QSizePolicy::ControlTypes fldtypes = + QSizePolicy::ControlTypes(field ? field->controlTypes() : QSizePolicy::DefaultType); + + // VSpacing + if (userVSpacing < 0) { + if (wrapAllRows) { + // label spacing is to a previous item + QFormLayoutItem *lbltop = prevFld ? prevFld : prevLbl; + // field spacing is to the label (or a previous item) + QFormLayoutItem *fldtop = label ? label : lbltop; + QSizePolicy::ControlTypes lbltoptypes = + QSizePolicy::ControlTypes(lbltop ? lbltop->controlTypes() : QSizePolicy::DefaultType); + QSizePolicy::ControlTypes fldtoptypes = + QSizePolicy::ControlTypes(fldtop ? fldtop->controlTypes() : QSizePolicy::DefaultType); + if (label && lbltop) + label->vSpace = style->combinedLayoutSpacing(lbltoptypes, lbltypes, Qt::Vertical, 0, parent); + if (field && fldtop) + field->vSpace = style->combinedLayoutSpacing(fldtoptypes, fldtypes, Qt::Vertical, 0, parent); + } else { + // Side by side.. we have to also consider the spacings to empty cells, which can strangely be more than + // non empty cells.. + QFormLayoutItem *lbltop = prevLbl ? prevLbl : prevFld; + QFormLayoutItem *fldtop = prevFld; + QSizePolicy::ControlTypes lbltoptypes = + QSizePolicy::ControlTypes(lbltop ? lbltop->controlTypes() : QSizePolicy::DefaultType); + QSizePolicy::ControlTypes fldtoptypes = + QSizePolicy::ControlTypes(fldtop ? fldtop->controlTypes() : QSizePolicy::DefaultType); + + // To be compatible to QGridLayout, we have to compare solitary labels & fields with both predecessors + if (label) { + if (!field) { + int lblspacing = style->combinedLayoutSpacing(lbltoptypes, lbltypes, Qt::Vertical, 0, parent); + int fldspacing = style->combinedLayoutSpacing(fldtoptypes, lbltypes, Qt::Vertical, 0, parent); + label->vSpace = qMax(lblspacing, fldspacing); + } else + label->vSpace = style->combinedLayoutSpacing(lbltoptypes, lbltypes, Qt::Vertical, 0, parent); + } + + if (field) { + // check spacing against both the previous label and field + if (!label) { + int lblspacing = style->combinedLayoutSpacing(lbltoptypes, fldtypes, Qt::Vertical, 0, parent); + int fldspacing = style->combinedLayoutSpacing(fldtoptypes, fldtypes, Qt::Vertical, 0, parent); + field->vSpace = qMax(lblspacing, fldspacing); + } else + field->vSpace = style->combinedLayoutSpacing(fldtoptypes, fldtypes, Qt::Vertical, 0, parent); + } + } + } + + // HSpacing + // hard-coded the left and right control types so that all the rows have the same + // inter-column spacing (otherwise the right column isn't always left aligned) + if (userHSpacing < 0 && !wrapAllRows && (label || !field->fullRow) && field) + field->sbsHSpace = style->combinedLayoutSpacing(QSizePolicy::Label, QSizePolicy::LineEdit, Qt::Horizontal, 0, parent); + } + + // Now update our min/sizehint widths + // We choose to put the spacing in the field side in sbs, so + // the right edge of the labels will align, but fields may + // be a little ragged.. since different controls may have + // different appearances, a slight raggedness in the left + // edges of fields can be tolerated. + // (Note - field->sbsHSpace is 0 for WrapAllRows mode) + if (label) { + maxMinLblWidth = qMax(maxMinLblWidth, label->minSize.width()); + maxShLblWidth = qMax(maxShLblWidth, label->sizeHint.width()); + if (field) { + maxMinFldWidth = qMax(maxMinFldWidth, field->minSize.width() + field->sbsHSpace); + maxShFldWidth = qMax(maxShFldWidth, field->sizeHint.width() + field->sbsHSpace); + } + } else if (field) { + maxMinIfldWidth = qMax(maxMinIfldWidth, field->minSize.width()); + maxShIfldWidth = qMax(maxShIfldWidth, field->sizeHint.width()); + } + + prevLbl = label; + prevFld = field; + } + + // Now, finally update the min/sizeHint widths + if (wrapAllRows) { + sh_width = qMax(maxShLblWidth, qMax(maxShIfldWidth, maxShFldWidth)); + min_width = qMax(maxMinLblWidth, qMax(maxMinIfldWidth, maxMinFldWidth)); + // in two line, we don't care as much about the threshold width + thresh_width = 0; + } else if (dontWrapRows) { + // This is just the max widths glommed together + sh_width = qMax(maxShLblWidth + maxShFldWidth, maxShIfldWidth); + min_width = qMax(maxMinLblWidth + maxMinFldWidth, maxMinIfldWidth); + thresh_width = QWIDGETSIZE_MAX; + } else { + // This is just the max widths glommed together + sh_width = qMax(maxShLblWidth + maxShFldWidth, maxShIfldWidth); + // min width needs to be the min when everything is wrapped, + // otherwise we'll never get set with a width that causes wrapping + min_width = qMax(maxMinLblWidth, qMax(maxMinIfldWidth, maxMinFldWidth)); + // We split a pair at label sh + field min (### for now..) + thresh_width = maxShLblWidth + maxMinFldWidth; + } + + // Update the expansions + expandVertical = expandV; + expandHorizontal = expandH; + } + sizesDirty = false; +} + +void QFormLayoutPrivate::recalcHFW(int w) +{ + setupHfwLayoutData(); + + int h = 0; + int mh = 0; + + for (int r = 0; r < vLayoutCount; ++r) { + int spacing = hfwLayouts.at(r).spacing; + h += hfwLayouts.at(r).sizeHint + spacing; + mh += hfwLayouts.at(r).minimumSize + spacing; + } + + if (sh_width > 0 && sh_width == w) { + hfw_sh_height = qMin(QLAYOUTSIZE_MAX, h); + hfw_sh_minheight = qMin(QLAYOUTSIZE_MAX, mh); + } else { + hfw_width = w; + hfw_height = qMin(QLAYOUTSIZE_MAX, h); + hfw_minheight = qMin(QLAYOUTSIZE_MAX, mh); + } +} + +void QFormLayoutPrivate::setupHfwLayoutData() +{ + // setupVerticalLayoutData must be called before this + // setupHorizontalLayoutData must also be called before this + // copies non hfw data into hfw + // then updates size and min + + + // Note: QGridLayout doesn't call minimumHeightForWidth, + // but instead uses heightForWidth for both min and sizeHint. + // For the common case where minimumHeightForWidth just calls + // heightForWidth, we do the calculation twice, which can be + // very expensive for word wrapped QLabels/QTextEdits, for example. + // So we just use heightForWidth as well. + int i; + int rr = m_matrix.rowCount(); + + hfwLayouts.clear(); + hfwLayouts.resize(vLayoutCount); + for (i = 0; i < vLayoutCount; ++i) + hfwLayouts[i] = vLayouts.at(i); + + for (i = 0; i < rr; ++i) { + QFormLayoutItem *label = m_matrix(i, 0); + QFormLayoutItem *field = m_matrix(i, 1); + + if (label) { + if (label->isHfw) { + // We don't check sideBySide here, since a label is only + // ever side by side with its field + int hfw = label->heightForWidth(label->layoutWidth); + hfwLayouts[label->vLayoutIndex].minimumSize = hfw; + hfwLayouts[label->vLayoutIndex].sizeHint = hfw; + } else { + // Reset these here, so the field can do a qMax below (the previous value may have + // been the fields non-hfw values, which are often larger than hfw) + hfwLayouts[label->vLayoutIndex].sizeHint = label->sizeHint.height(); + hfwLayouts[label->vLayoutIndex].minimumSize = label->minSize.height(); + } + } + + if (field) { + int hfw = field->isHfw ? field->heightForWidth(field->layoutWidth) : 0; + int h = field->isHfw ? hfw : field->sizeHint.height(); + int mh = field->isHfw ? hfw : field->minSize.height(); + + if (field->sideBySide) { + int oh = hfwLayouts.at(field->vLayoutIndex).sizeHint; + int omh = hfwLayouts.at(field->vLayoutIndex).minimumSize; + + hfwLayouts[field->vLayoutIndex].sizeHint = qMax(h, oh); + hfwLayouts[field->vLayoutIndex].minimumSize = qMax(mh, omh); + } else { + hfwLayouts[field->vLayoutIndex].sizeHint = h; + hfwLayouts[field->vLayoutIndex].minimumSize = mh; + } + } + } +} + +/* + Given up to four items involved in a vertical spacing calculation + (two rows * two columns), return the max vertical spacing for the + row containing item1 (which may also include item2) + We assume parent and item1 are not null. + + If a particular row is split, then the spacings for that row and + the following row are affected, and this function should be + called with recalculate = true for both rows (note: only rows with both + a label and a field can be split). + + In particular: + + 1) the split label's row vspace needs to be changed to qMax(label/prevLabel, label/prevField) + [call with item1 = label, item2 = null, prevItem1 & prevItem2 as before] + 2) the split field's row vspace needs to be changed to the label/field spacing + [call with item1 = field, item2 = null, prevItem1 = label, prevItem2 = null] + + [if the next row has one item, 'item'] + 3a) the following row's vspace needs to be changed to item/field spacing (would + previously been the qMax(item/label, item/field) spacings) + [call with item1 = item, item2 = null, prevItem1 = field, prevItem2 = null] + + [if the next row has two items, 'label2' and 'field2'] + 3b) the following row's vspace needs to be changed to be qMax(field/label2, field/field2) spacing + [call with item1 = label2, item2 = field2, prevItem1 = field, prevItem2 = null] + + In the (common) non split case, we can just use the precalculated vspace (possibly qMaxed between + label and field). + + If recalculate is true, we expect: + - parent != null + - item1 != null + - item2 can be null + - prevItem1 can be null + - if item2 is not null, prevItem2 will be null (e.g. steps 1 or 3 above) + - if prevItem1 is null, prevItem2 will be null +*/ +static inline int spacingHelper(QWidget* parent, QStyle *style, int userVSpacing, bool recalculate, QFormLayoutItem* item1, QFormLayoutItem* item2, QFormLayoutItem* prevItem1, QFormLayoutItem *prevItem2) +{ + int spacing = userVSpacing; + if (spacing < 0) { + if (!recalculate) { + if (item1) + spacing = item1->vSpace; + if (item2) + spacing = qMax(spacing, item2->vSpace); + } else { + if (style && prevItem1) { + QSizePolicy::ControlTypes itemtypes = + QSizePolicy::ControlTypes(item1 ? item1->controlTypes() : QSizePolicy::DefaultType); + int spacing2 = 0; + + spacing = style->combinedLayoutSpacing(itemtypes, prevItem1->controlTypes(), Qt::Vertical, 0, parent); + + // At most of one of item2 and prevItem2 will be nonnull + if (item2) + spacing2 = style->combinedLayoutSpacing(item2->controlTypes(), prevItem1->controlTypes(), Qt::Vertical, 0, parent); + else if (prevItem2) + spacing2 = style->combinedLayoutSpacing(itemtypes, prevItem2->controlTypes(), Qt::Vertical, 0, parent); + + spacing = qMax(spacing, spacing2); + } + } + } else { + if (prevItem1) { + QWidget *wid = prevItem1->item->widget(); + if (wid) + spacing = qMax(spacing, prevItem1->geometry().top() - wid->geometry().top() ); + } + if (prevItem2) { + QWidget *wid = prevItem2->item->widget(); + if (wid) + spacing = qMax(spacing, prevItem2->geometry().top() - wid->geometry().top() ); + } + } + return spacing; +} + +static inline void initLayoutStruct(QLayoutStruct& sl, QFormLayoutItem* item) +{ + sl.init(item->vStretch(), item->minSize.height()); + sl.sizeHint = item->sizeHint.height(); + sl.maximumSize = item->maxSize.height(); + sl.expansive = (item->expandingDirections() & Qt::Vertical); + sl.empty = false; +} + +void QFormLayoutPrivate::setupVerticalLayoutData(int width) +{ + Q_Q(QFormLayout); + + // Early out if we have no changes that would cause a change in vertical layout + if ((width == layoutWidth || (width >= thresh_width && layoutWidth >= thresh_width)) && !dirty && !sizesDirty) + return; + + layoutWidth = width; + + int rr = m_matrix.rowCount(); + int vidx = 1; + QFormLayout::RowWrapPolicy rowWrapPolicy = q->rowWrapPolicy(); + bool wrapAllRows = (rowWrapPolicy == QFormLayout::WrapAllRows); + bool addTopBottomStretch = true; + + vLayouts.clear(); + vLayouts.resize((2 * rr) + 2); // a max, some may be unused + + QStyle *style = 0; + + int userVSpacing = q->verticalSpacing(); + + if (userVSpacing < 0) { + if (QWidget *widget = q->parentWidget()) + style = widget->style(); + } + + // make sure our sizes are up to date + updateSizes(); + + // Grab the widest label width here + // This might be different from the value computed during + // sizeHint/minSize, since we don't count label/field pairs that + // are split. + maxLabelWidth = 0; + if (!wrapAllRows) { + for (int i = 0; i < rr; ++i) { + const QFormLayoutItem *label = m_matrix(i, 0); + const QFormLayoutItem *field = m_matrix(i, 1); + if (label && (label->sizeHint.width() + (field ? field->minSize.width() : 0) <= width)) + maxLabelWidth = qMax(maxLabelWidth, label->sizeHint.width()); + } + } else { + maxLabelWidth = width; + } + + QFormLayoutItem *prevItem1 = 0; + QFormLayoutItem *prevItem2 = 0; + bool prevRowSplit = false; + + for (int i = 0; i < rr; ++i) { + QFormLayoutItem *label = m_matrix(i, 0); + QFormLayoutItem *field = m_matrix(i, 1); + + // Totally ignore empty rows... + if (!label && !field) + continue; + + QSize min1; + QSize min2; + QSize sh1; + QSize sh2; + if (label) { + min1 = label->minSize; + sh1 = label->sizeHint; + } + if (field) { + min2 = field->minSize; + sh2 = field->sizeHint; + } + + // In separate lines, we make a vLayout for everything that isn't null + // in side by side, we only separate label/field if we're going to wrap it + bool splitSideBySide = (rowWrapPolicy == QFormLayout::WrapLongRows) + && ((maxLabelWidth < sh1.width()) || (width < (maxLabelWidth + min2.width()))); + + if (wrapAllRows || splitSideBySide) { + if (label) { + initLayoutStruct(vLayouts[vidx], label); + + if (vidx > 1) + vLayouts[vidx - 1].spacing = spacingHelper(q->parentWidget(), style, userVSpacing, splitSideBySide || prevRowSplit, label, 0, prevItem1, prevItem2); + + label->vLayoutIndex = vidx; + label->sideBySide = false; + + prevItem1 = label; + prevItem2 = 0; + + if (vLayouts[vidx].stretch > 0) + addTopBottomStretch = false; + + ++vidx; + } + + if (field) { + initLayoutStruct(vLayouts[vidx], field); + + if (vidx > 1) + vLayouts[vidx - 1].spacing = spacingHelper(q->parentWidget(), style, userVSpacing, splitSideBySide || prevRowSplit, field, 0, prevItem1, prevItem2); + + field->vLayoutIndex = vidx; + field->sideBySide = false; + + prevItem1 = field; + prevItem2 = 0; + + if (vLayouts[vidx].stretch > 0) + addTopBottomStretch = false; + + ++vidx; + } + + prevRowSplit = splitSideBySide; + } else { + // we're in side by side mode, and we have enough space to do that + QSize max1(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + QSize max2(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + int stretch1 = 0; + int stretch2 = 0; + bool expanding = false; + + if (label) { + max1 = label->maxSize; + if (label->expandingDirections() & Qt::Vertical) + expanding = true; + + label->sideBySide = (field != 0); + label->vLayoutIndex = vidx; + stretch1 = label->vStretch(); + } + + if (field) { + max2 = field->maxSize; + if (field->expandingDirections() & Qt::Vertical) + expanding = true; + + field->sideBySide = (label || !field->fullRow); + field->vLayoutIndex = vidx; + stretch2 = field->vStretch(); + } + + vLayouts[vidx].init(qMax(stretch1, stretch2), qMax(min1.height(), min2.height())); + vLayouts[vidx].sizeHint = qMax(sh1.height(), sh2.height()); + vLayouts[vidx].maximumSize = qMin(max1.height(), max2.height()); + vLayouts[vidx].expansive = expanding || (vLayouts[vidx].stretch > 0); + vLayouts[vidx].empty = false; + + if (vLayouts[vidx].stretch > 0) + addTopBottomStretch = false; + + if (vidx > 1) + vLayouts[vidx - 1].spacing = spacingHelper(q->parentWidget(), style, userVSpacing, prevRowSplit, label, field, prevItem1, prevItem2); + + if (label) { + prevItem1 = label; + prevItem2 = field; + } else { + prevItem1 = field; + prevItem2 = 0; + } + + prevRowSplit = false; + ++vidx; + } + } + + if (addTopBottomStretch) { + Qt::Alignment formAlignment = q->formAlignment(); + + if (!(formAlignment & Qt::AlignBottom)) { + // AlignTop (default if unspecified) or AlignVCenter: We add a stretch at the bottom + vLayouts[vidx].init(1, 0); + vLayouts[vidx].expansive = true; + ++vidx; + } + + if (formAlignment & (Qt::AlignVCenter | Qt::AlignBottom)) { + // AlignVCenter or AlignBottom: We add a stretch at the top + vLayouts[0].init(1, 0); + vLayouts[0].expansive = true; + } else { + vLayouts[0].init(0, 0); + } + } else { + vLayouts[0].init(0, 0); + } + + vLayoutCount = vidx; + dirty = false; +} + +void QFormLayoutPrivate::setupHorizontalLayoutData(int width) +{ + Q_Q(QFormLayout); + + // requires setupVerticalLayoutData to be called first + + int fieldMaxWidth = 0; + + int rr = m_matrix.rowCount(); + bool wrapAllRows = (q->rowWrapPolicy() == QFormLayout::WrapAllRows); + + for (int i = 0; i < rr; ++i) { + QFormLayoutItem *label = m_matrix(i, 0); + QFormLayoutItem *field = m_matrix(i, 1); + + // Totally ignore empty rows... + if (!label && !field) + continue; + + if (label) { + // if there is a field, and we're side by side, we use maxLabelWidth + // otherwise we just use the sizehint + label->layoutWidth = (field && label->sideBySide) ? maxLabelWidth : label->sizeHint.width(); + label->layoutPos = 0; + } + + if (field) { + // This is the default amount allotted to fields in sbs + int fldwidth = width - maxLabelWidth - field->sbsHSpace; + + // If we've split a row, we still decide to align + // the field with all the other field if it will fit + // Fields in sbs mode get the remnants of the maxLabelWidth + if (!field->sideBySide) { + if (wrapAllRows || (!label && field->fullRow) || field->sizeHint.width() > fldwidth) { + field->layoutWidth = width; + field->layoutPos = 0; + } else { + field->layoutWidth = fldwidth; + field->layoutPos = width - fldwidth; + } + } else { + // We're sbs, so we should have a label + field->layoutWidth = fldwidth; + field->layoutPos = width - fldwidth; + } + + fieldMaxWidth = qMax(fieldMaxWidth, field->maxSize.width()); + } + } + + formMaxWidth = maxLabelWidth + fieldMaxWidth; +} + +void QFormLayoutPrivate::calcSizeHints() +{ + Q_Q(QFormLayout); + + int leftMargin, topMargin, rightMargin, bottomMargin; + q->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); + + updateSizes(); + setupVerticalLayoutData(QLAYOUTSIZE_MAX); + // Don't need to call setupHorizontal here + + int h = topMargin + bottomMargin; + int mh = topMargin + bottomMargin; + + // The following are set in updateSizes + int w = sh_width + leftMargin + rightMargin; + int mw = min_width + leftMargin + rightMargin; + + for (int i = 0; i < vLayoutCount; ++i) { + int spacing = vLayouts.at(i).spacing; + h += vLayouts.at(i).sizeHint + spacing; + mh += vLayouts.at(i).minimumSize + spacing; + } + + minSize.rwidth() = qMin(mw, QLAYOUTSIZE_MAX); + minSize.rheight() = qMin(mh, QLAYOUTSIZE_MAX); + prefSize.rwidth() = qMin(w, QLAYOUTSIZE_MAX); + prefSize.rheight() = qMin(h, QLAYOUTSIZE_MAX); +} + +int QFormLayoutPrivate::insertRow(int row) +{ + int rowCnt = m_matrix.rowCount(); + if (uint(row) > uint(rowCnt)) + row = rowCnt; + + insertRows(row, 1); + return row; +} + +void QFormLayoutPrivate::insertRows(int row, int count) +{ + while (count > 0) { + m_matrix.insertRow(row, 0); + --count; + } +} + +void QFormLayoutPrivate::setItem(int row, QFormLayout::ItemRole role, QLayoutItem *item) +{ + const bool fullRow = role == QFormLayout::SpanningRole; + const int column = role == QFormLayout::SpanningRole ? 1 : static_cast<int>(role); + if (uint(row) >= uint(m_matrix.rowCount()) || uint(column) > 1U) { + qWarning("QFormLayoutPrivate::setItem: Invalid cell (%d, %d)", row, column); + return; + } + + if (!item) + return; + + if (m_matrix(row, column)) { + qWarning("QFormLayoutPrivate::setItem: Cell (%d, %d) already occupied", row, column); + return; + } + + QFormLayoutItem *i = new QFormLayoutItem(item); + i->fullRow = fullRow; + m_matrix(row, column) = i; + + m_things.append(i); +} + +void QFormLayoutPrivate::setLayout(int row, QFormLayout::ItemRole role, QLayout *layout) +{ + if (layout) { + Q_Q(QFormLayout); + q->addChildLayout(layout); + setItem(row, role, layout); + } +} + +void QFormLayoutPrivate::setWidget(int row, QFormLayout::ItemRole role, QWidget *widget) +{ + if (widget) { + Q_Q(QFormLayout); + q->addChildWidget(widget); + setItem(row, role, QLayoutPrivate::createWidgetItem(q, widget)); + } +} + +QStyle* QFormLayoutPrivate::getStyle() const +{ + Q_Q(const QFormLayout); + + // ### cache + if (QWidget *parentWidget = q->parentWidget()) + return parentWidget->style(); + else + return QApplication::style(); +} + +/*! + \class QFormLayout + \since 4.4 + \brief The QFormLayout class manages forms of input widgets and their associated labels. + + \ingroup geomanagement + + + QFormLayout is a convenience layout class that lays out its + children in a two-column form. The left column consists of labels + and the right column consists of "field" widgets (line editors, + spin boxes, etc.). + + Traditionally, such two-column form layouts were achieved using + QGridLayout. QFormLayout is a higher-level alternative that + provides the following advantages: + + \list + \o \bold{Adherence to the different platform's look and feel guidelines.} + + For example, the + \l{Mac OS X Aqua} and KDE guidelines specify that the + labels should be right-aligned, whereas Windows and GNOME + applications normally use left-alignment. + + \o \bold{Support for wrapping long rows.} + + For devices with small displays, QFormLayout can be set to + \l{WrapLongRows}{wrap long rows}, or even to + \l{WrapAllRows}{wrap all rows}. + + \o \bold{Convenient API for creating label--field pairs.} + + The addRow() overload that takes a QString and a QWidget * + creates a QLabel behind the scenes and automatically set up + its buddy. We can then write code like this: + + \snippet doc/src/snippets/code/src_gui_kernel_qformlayout.cpp 0 + + Compare this with the following code, written using QGridLayout: + + \snippet doc/src/snippets/code/src_gui_kernel_qformlayout.cpp 1 + \endlist + + The table below shows the default appearance in different styles. + + \table + \header + \o QCommonStyle derived styles (except QPlastiqueStyle) + \o QMacStyle + \o QPlastiqueStyle + \o Qt Extended styles + \row + \o \inlineimage qformlayout-win.png + \o \inlineimage qformlayout-mac.png + \o \inlineimage qformlayout-kde.png + \o \inlineimage qformlayout-qpe.png + \row + \o Traditional style used for Windows, GNOME, and earlier + versions of KDE. Labels are left aligned, and expanding + fields grow to fill the available space. (This normally + corresponds to what we would get using a two-column + QGridLayout.) + \o Style based on the + \l{Mac OS X Aqua} guidelines. Labels are right-aligned, + the fields don't grow beyond their size hint, and the + form is horizontally centered. + \o Recommended style for + \l{KDE applications}. Similar to MacStyle, except that the form + is left-aligned and all fields grow to fill the available + space. + \o Default style for Qt Extended styles. Labels are right-aligned, + expanding fields grow to fill the available space, and row + wrapping is enabled for long lines. + \endtable + + The form styles can be also be overridden individually by calling + setLabelAlignment(), setFormAlignment(), setFieldGrowthPolicy(), + and setRowWrapPolicy(). For example, to simulate the form layout + appearance of QMacStyle on all platforms, but with left-aligned + labels, you could write: + + \snippet doc/src/snippets/code/src_gui_kernel_qformlayout.cpp 2 + + \sa QGridLayout, QBoxLayout, QStackedLayout +*/ + + +/*! + \enum QFormLayout::FieldGrowthPolicy + + This enum specifies the different policies that can be used to + control the way in which the form's fields grow. + + \value FieldsStayAtSizeHint + The fields never grow beyond their + \l{QWidgetItem::sizeHint()}{effective size hint}. This is + the default for QMacStyle. + + \value ExpandingFieldsGrow + Fields with an horizontal \l{QSizePolicy}{size policy} of + \l{QSizePolicy::}{Expanding} or + \l{QSizePolicy::}{MinimumExpanding} will grow to fill the + available space. The other fields will not grow beyond + their effective size hint. This is the default policy for + Plastique. + + \value AllNonFixedFieldsGrow + All fields with a size policy that allows them to grow + will grow to fill the available space. This is the default + policy for most styles. + + \sa fieldGrowthPolicy +*/ + +/*! + \enum QFormLayout::RowWrapPolicy + + This enum specifies the different policies that can be used to + control the way in which the form's rows wrap. + + \value DontWrapRows + Fields are always laid out next to their label. This is + the default policy for all styles except Qt Extended styles + and QS60Style. + + \value WrapLongRows + Labels are given enough horizontal space to fit the widest label, + and the rest of the space is given to the fields. If the minimum + size of a field pair is wider than the available space, the field + is wrapped to the next line. This is the default policy for + Qt Extended styles and and QS60Style. + + \value WrapAllRows + Fields are always laid out below their label. + + \sa rowWrapPolicy +*/ + +/*! + \enum QFormLayout::ItemRole + + This enum specifies the types of widgets (or other layout items) + that may appear in a row. + + \value LabelRole A label widget. + \value FieldRole A field widget. + \value SpanningRole A widget that spans label and field columns. + + \sa itemAt(), getItemPosition() +*/ + +/*! + Constructs a new form layout with the given \a parent widget. + + \sa QWidget::setLayout() +*/ +QFormLayout::QFormLayout(QWidget *parent) + : QLayout(*new QFormLayoutPrivate, 0, parent) +{ +} + +/*! + Destroys the form layout. +*/ +QFormLayout::~QFormLayout() +{ + Q_D(QFormLayout); + + /* + The clearing and destruction order here is important. We start by clearing + m_things so that QLayout and the rest of the world know that we don't babysit + the layout items anymore and don't care if they are destroyed. + */ + d->m_things.clear(); + qDeleteAll(d->m_matrix.storage()); + d->m_matrix.clear(); +} + +/*! + Adds a new row to the bottom of this form layout, with the given + \a label and \a field. + + \sa insertRow() +*/ +void QFormLayout::addRow(QWidget *label, QWidget *field) +{ + insertRow(-1, label, field); +} + +/*! + \overload +*/ +void QFormLayout::addRow(QWidget *label, QLayout *field) +{ + insertRow(-1, label, field); +} + +/*! + \overload + + This overload automatically creates a QLabel behind the scenes + with \a labelText as its text. The \a field is set as the new + QLabel's \l{QLabel::setBuddy()}{buddy}. +*/ +void QFormLayout::addRow(const QString &labelText, QWidget *field) +{ + insertRow(-1, labelText, field); +} + +/*! + \overload + + This overload automatically creates a QLabel behind the scenes + with \a labelText as its text. +*/ +void QFormLayout::addRow(const QString &labelText, QLayout *field) +{ + insertRow(-1, labelText, field); +} + +/*! + \overload + + Adds the specified \a widget at the end of this form layout. The + \a widget spans both columns. +*/ +void QFormLayout::addRow(QWidget *widget) +{ + insertRow(-1, widget); +} + +/*! + \overload + + Adds the specified \a layout at the end of this form layout. The + \a layout spans both columns. +*/ +void QFormLayout::addRow(QLayout *layout) +{ + insertRow(-1, layout); +} + +/*! + Inserts a new row at position \a row in this form layout, with + the given \a label and \a field. If \a row is out of bounds, the + new row is added at the end. + + \sa addRow() +*/ +void QFormLayout::insertRow(int row, QWidget *label, QWidget *field) +{ + Q_D(QFormLayout); + + row = d->insertRow(row); + if (label) + d->setWidget(row, LabelRole, label); + if (field) + d->setWidget(row, FieldRole, field); + invalidate(); +} + +/*! + \overload +*/ +void QFormLayout::insertRow(int row, QWidget *label, QLayout *field) +{ + Q_D(QFormLayout); + + row = d->insertRow(row); + if (label) + d->setWidget(row, LabelRole, label); + if (field) + d->setLayout(row, FieldRole, field); + invalidate(); +} + +/*! + \overload + + This overload automatically creates a QLabel behind the scenes + with \a labelText as its text. The \a field is set as the new + QLabel's \l{QLabel::setBuddy()}{buddy}. +*/ +void QFormLayout::insertRow(int row, const QString &labelText, QWidget *field) +{ + QLabel *label = 0; + if (!labelText.isEmpty()) { + label = new QLabel(labelText); +#ifndef QT_NO_SHORTCUT + label->setBuddy(field); +#endif + } + insertRow(row, label, field); +} + +/*! + \overload + + This overload automatically creates a QLabel behind the scenes + with \a labelText as its text. +*/ +void QFormLayout::insertRow(int row, const QString &labelText, QLayout *field) +{ + insertRow(row, labelText.isEmpty() ? 0 : new QLabel(labelText), field); +} + +/*! + \overload + + Inserts the specified \a widget at position \a row in this form + layout. The \a widget spans both columns. If \a row is out of + bounds, the widget is added at the end. +*/ +void QFormLayout::insertRow(int row, QWidget *widget) +{ + Q_D(QFormLayout); + + if (!widget) { + qWarning("QFormLayout: Cannot add null field to %s", qPrintable(objectName())); + return; + } + + row = d->insertRow(row); + d->setWidget(row, SpanningRole, widget); + invalidate(); +} + +/*! + \overload + + Inserts the specified \a layout at position \a row in this form + layout. The \a layout spans both columns. If \a row is out of + bounds, the widget is added at the end. +*/ +void QFormLayout::insertRow(int row, QLayout *layout) +{ + Q_D(QFormLayout); + + if (!layout) { + qWarning("QFormLayout: Cannot add null field to %s", qPrintable(objectName())); + return; + } + + row = d->insertRow(row); + d->setLayout(row, SpanningRole, layout); + invalidate(); +} + +/*! + \reimp +*/ +void QFormLayout::addItem(QLayoutItem *item) +{ + Q_D(QFormLayout); + + int row = d->insertRow(d->m_matrix.rowCount()); + d->setItem(row, FieldRole, item); + invalidate(); +} + +/*! + \reimp +*/ +int QFormLayout::count() const +{ + Q_D(const QFormLayout); + return d->m_things.count(); +} + +/*! + \reimp +*/ +QLayoutItem *QFormLayout::itemAt(int index) const +{ + Q_D(const QFormLayout); + if (QFormLayoutItem *formItem = d->m_things.value(index)) + return formItem->item; + return 0; +} + +/*! + \reimp +*/ +QLayoutItem *QFormLayout::takeAt(int index) +{ + Q_D(QFormLayout); + + const int storageIndex = storageIndexFromLayoutItem(d->m_matrix, d->m_things.value(index)); + if (storageIndex == -1) { + qWarning("QFormLayout::takeAt: Invalid index %d", index); + return 0; + } + + int row, col; + QFormLayoutPrivate::ItemMatrix::storageIndexToPosition(storageIndex, &row, &col); + Q_ASSERT(d->m_matrix(row, col)); + + QFormLayoutItem *item = d->m_matrix(row, col); + Q_ASSERT(item); + d->m_things.removeAt(index); + d->m_matrix(row, col) = 0; + + invalidate(); + + // grab ownership back from the QFormLayoutItem + QLayoutItem *i = item->item; + item->item = 0; + delete item; + return i; +} + +/*! + \reimp +*/ +Qt::Orientations QFormLayout::expandingDirections() const +{ + Q_D(const QFormLayout); + QFormLayoutPrivate *e = const_cast<QFormLayoutPrivate *>(d); + e->updateSizes(); + + Qt::Orientations o = 0; + if (e->expandHorizontal) + o = Qt::Horizontal; + if (e->expandVertical) + o |= Qt::Vertical; + return o; +} + +/*! + \reimp +*/ +bool QFormLayout::hasHeightForWidth() const +{ + Q_D(const QFormLayout); + QFormLayoutPrivate *e = const_cast<QFormLayoutPrivate *>(d); + e->updateSizes(); + return (d->has_hfw || rowWrapPolicy() == WrapLongRows); +} + +/*! + \reimp +*/ +int QFormLayout::heightForWidth(int width) const +{ + Q_D(const QFormLayout); + if (!hasHeightForWidth()) + return -1; + + int leftMargin, topMargin, rightMargin, bottomMargin; + getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); + + int targetWidth = width - leftMargin - rightMargin; + + if (!d->haveHfwCached(targetWidth)) { + QFormLayoutPrivate *dat = const_cast<QFormLayoutPrivate *>(d); + dat->setupVerticalLayoutData(targetWidth); + dat->setupHorizontalLayoutData(targetWidth); + dat->recalcHFW(targetWidth); + } + if (targetWidth == d->sh_width) + return d->hfw_sh_height + topMargin + bottomMargin; + else + return d->hfw_height + topMargin + bottomMargin; +} + +/*! + \reimp +*/ +void QFormLayout::setGeometry(const QRect &rect) +{ + Q_D(QFormLayout); + if (d->dirty || rect != geometry()) { + QRect cr = rect; + int leftMargin, topMargin, rightMargin, bottomMargin; + getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); + cr.adjust(+leftMargin, +topMargin, -rightMargin, -bottomMargin); + + bool hfw = hasHeightForWidth(); + d->setupVerticalLayoutData(cr.width()); + d->setupHorizontalLayoutData(cr.width()); + if (hfw && (!d->haveHfwCached(cr.width()) || d->hfwLayouts.size() != d->vLayoutCount)) + d->recalcHFW(cr.width()); + if (hfw) { + qGeomCalc(d->hfwLayouts, 0, d->vLayoutCount, cr.y(), cr.height()); + d->arrangeWidgets(d->hfwLayouts, cr); + } else { + qGeomCalc(d->vLayouts, 0, d->vLayoutCount, cr.y(), cr.height()); + d->arrangeWidgets(d->vLayouts, cr); + } + QLayout::setGeometry(rect); + } +} + +/*! + \reimp +*/ +QSize QFormLayout::sizeHint() const +{ + Q_D(const QFormLayout); + if (!d->prefSize.isValid()) { + QFormLayoutPrivate *dat = const_cast<QFormLayoutPrivate *>(d); + dat->calcSizeHints(); + } + return d->prefSize; +} + +/*! + \reimp +*/ +QSize QFormLayout::minimumSize() const +{ + // ### fix minimumSize if hfw + Q_D(const QFormLayout); + if (!d->minSize.isValid()) { + QFormLayoutPrivate *dat = const_cast<QFormLayoutPrivate *>(d); + dat->calcSizeHints(); + } + return d->minSize; +} + +/*! + \reimp +*/ +void QFormLayout::invalidate() +{ + Q_D(QFormLayout); + d->dirty = true; + d->sizesDirty = true; + d->minSize = QSize(); + d->prefSize = QSize(); + d->formMaxWidth = -1; + d->hfw_width = -1; + d->sh_width = -1; + d->layoutWidth = -1; + d->hfw_sh_height = -1; + QLayout::invalidate(); +} + +/*! + Returns the number of rows in the form. + + \sa QLayout::count() +*/ +int QFormLayout::rowCount() const +{ + Q_D(const QFormLayout); + return d->m_matrix.rowCount(); +} + +/*! + Returns the layout item in the given \a row with the specified \a + role (column). Returns 0 if there is no such item. + + \sa QLayout::itemAt(), setItem() +*/ +QLayoutItem *QFormLayout::itemAt(int row, ItemRole role) const +{ + Q_D(const QFormLayout); + if (uint(row) >= uint(d->m_matrix.rowCount())) + return 0; + switch (role) { + case SpanningRole: + if (QFormLayoutItem *item = d->m_matrix(row, 1)) + if (item->fullRow) + return item->item; + break; + case LabelRole: + case FieldRole: + if (QFormLayoutItem *item = d->m_matrix(row, (role == LabelRole) ? 0 : 1)) + return item->item; + break; + } + return 0; +} + +/*! + Retrieves the row and role (column) of the item at the specified + \a index. If \a index is out of bounds, *\a rowPtr is set to -1; + otherwise the row is stored in *\a rowPtr and the role is stored + in *\a rolePtr. + + \sa itemAt(), count(), getLayoutPosition(), getWidgetPosition() +*/ +void QFormLayout::getItemPosition(int index, int *rowPtr, ItemRole *rolePtr) const +{ + Q_D(const QFormLayout); + int col = -1; + int row = -1; + + const int storageIndex = storageIndexFromLayoutItem(d->m_matrix, d->m_things.value(index)); + if (storageIndex != -1) + QFormLayoutPrivate::ItemMatrix::storageIndexToPosition(storageIndex, &row, &col); + + if (rowPtr) + *rowPtr = row; + if (rolePtr && col != -1) { + const bool spanning = col == 1 && d->m_matrix(row, col)->fullRow; + if (spanning) { + *rolePtr = SpanningRole; + } else { + *rolePtr = ItemRole(col); + } + } +} + +/*! + Retrieves the row and role (column) of the specified child \a + layout. If \a layout is not in the form layout, *\a rowPtr is set + to -1; otherwise the row is stored in *\a rowPtr and the role is stored + in *\a rolePtr. +*/ +void QFormLayout::getLayoutPosition(QLayout *layout, int *rowPtr, ItemRole *rolePtr) const +{ + int n = count(); + int index = 0; + while (index < n) { + if (itemAt(index) == layout) + break; + ++index; + } + getItemPosition(index, rowPtr, rolePtr); +} + +/*! + Retrieves the row and role (column) of the specified \a widget in + the layout. If \a widget is not in the layout, *\a rowPtr is set + to -1; otherwise the row is stored in *\a rowPtr and the role is stored + in *\a rolePtr. + + \sa getItemPosition(), itemAt() +*/ +void QFormLayout::getWidgetPosition(QWidget *widget, int *rowPtr, ItemRole *rolePtr) const +{ + getItemPosition(indexOf(widget), rowPtr, rolePtr); +} + +// ### eliminate labelForField() + +/*! + Returns the label associated with the given \a field. + + \sa itemAt() +*/ +QWidget *QFormLayout::labelForField(QWidget *field) const +{ + Q_D(const QFormLayout); + + int row; + ItemRole role; + + getWidgetPosition(field, &row, &role); + + if (row != -1 && role == FieldRole) { + if (QFormLayoutItem *label = d->m_matrix(row, LabelRole)) + return label->widget(); + } + return 0; +} + +/*! + \overload +*/ +QWidget *QFormLayout::labelForField(QLayout *field) const +{ + Q_D(const QFormLayout); + + int row; + ItemRole role; + + getLayoutPosition(field, &row, &role); + + if (row != -1 && role == FieldRole) { + if (QFormLayoutItem *label = d->m_matrix(row, LabelRole)) + return label->widget(); + } + return 0; +} + +/*! + \property QFormLayout::fieldGrowthPolicy + \brief the way in which the form's fields grow + + The default value depends on the widget or application style. For + QMacStyle, the default is FieldsStayAtSizeHint; for QCommonStyle + derived styles (like Plastique and Windows), the default + is ExpandingFieldsGrow; for Qt Extended styles, the default is + AllNonFixedFieldsGrow. + + If none of the fields can grow and the form is resized, extra + space is distributed according to the current + \l{formAlignment}{form alignment}. + + \sa formAlignment, rowWrapPolicy +*/ + +void QFormLayout::setFieldGrowthPolicy(FieldGrowthPolicy policy) +{ + Q_D(QFormLayout); + if (FieldGrowthPolicy(d->fieldGrowthPolicy) != policy) { + d->fieldGrowthPolicy = policy; + invalidate(); + } +} + +QFormLayout::FieldGrowthPolicy QFormLayout::fieldGrowthPolicy() const +{ + Q_D(const QFormLayout); + if (d->fieldGrowthPolicy == DefaultFieldGrowthPolicy) { + return QFormLayout::FieldGrowthPolicy(d->getStyle()->styleHint(QStyle::SH_FormLayoutFieldGrowthPolicy)); + } else { + return QFormLayout::FieldGrowthPolicy(d->fieldGrowthPolicy); + } +} + +/*! + \property QFormLayout::rowWrapPolicy + \brief the way in which the form's rows wrap + + The default value depends on the widget or application style. For + Qt Extended styles and QS60Style, the default is WrapLongRows; + for the other styles, the default is DontWrapRows. + + If you want to display each label above its associated field + (instead of next to it), set this property to WrapAllRows. + + \sa fieldGrowthPolicy +*/ + +void QFormLayout::setRowWrapPolicy(RowWrapPolicy policy) +{ + Q_D(QFormLayout); + if (RowWrapPolicy(d->rowWrapPolicy) != policy) { + d->rowWrapPolicy = policy; + invalidate(); + } +} + +QFormLayout::RowWrapPolicy QFormLayout::rowWrapPolicy() const +{ + Q_D(const QFormLayout); + if (d->rowWrapPolicy == DefaultRowWrapPolicy) { + return QFormLayout::RowWrapPolicy(d->getStyle()->styleHint(QStyle::SH_FormLayoutWrapPolicy)); + } else { + return QFormLayout::RowWrapPolicy(d->rowWrapPolicy); + } +} + +/*! + \property QFormLayout::labelAlignment + \brief the horizontal alignment of the labels + + The default value depends on the widget or application style. For + QCommonStyle derived styles, except for QPlastiqueStyle, the + default is Qt::AlignLeft; for the other styles, the default is + Qt::AlignRight. + + \sa formAlignment +*/ + +void QFormLayout::setLabelAlignment(Qt::Alignment alignment) +{ + Q_D(QFormLayout); + if (d->labelAlignment != alignment) { + d->labelAlignment = alignment; + invalidate(); + } +} + +Qt::Alignment QFormLayout::labelAlignment() const +{ + Q_D(const QFormLayout); + if (!d->labelAlignment) { + return Qt::Alignment(d->getStyle()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); + } else { + return d->labelAlignment; + } +} + +/*! + \property QFormLayout::formAlignment + \brief the alignment of the form layout's contents within the layout's geometry + + The default value depends on the widget or application style. For + QMacStyle, the default is Qt::AlignHCenter | Qt::AlignTop; for the + other styles, the default is Qt::AlignLeft | Qt::AlignTop. + + \sa labelAlignment, rowWrapPolicy +*/ + +void QFormLayout::setFormAlignment(Qt::Alignment alignment) +{ + Q_D(QFormLayout); + if (d->formAlignment != alignment) { + d->formAlignment = alignment; + invalidate(); + } +} + +Qt::Alignment QFormLayout::formAlignment() const +{ + Q_D(const QFormLayout); + if (!d->formAlignment) { + return Qt::Alignment(d->getStyle()->styleHint(QStyle::SH_FormLayoutFormAlignment)); + } else { + return d->formAlignment; + } +} + +/*! + \property QFormLayout::horizontalSpacing + \brief the spacing between widgets that are laid out side by side + + By default, if no value is explicitly set, the layout's horizontal + spacing is inherited from the parent layout, or from the style settings + for the parent widget. + + \sa verticalSpacing, QStyle::pixelMetric(), {QStyle::}{PM_LayoutHorizontalSpacing} +*/ +void QFormLayout::setHorizontalSpacing(int spacing) +{ + Q_D(QFormLayout); + if (spacing != d->hSpacing) { + d->hSpacing = spacing; + invalidate(); + } +} + +int QFormLayout::horizontalSpacing() const +{ + Q_D(const QFormLayout); + if (d->hSpacing >= 0) { + return d->hSpacing; + } else { + return qSmartSpacing(this, QStyle::PM_LayoutHorizontalSpacing); + } +} + +/*! + \property QFormLayout::verticalSpacing + \brief the spacing between widgets that are laid out vertically + + By default, if no value is explicitly set, the layout's vertical spacing is + inherited from the parent layout, or from the style settings for the parent + widget. + + \sa horizontalSpacing, QStyle::pixelMetric(), {QStyle::}{PM_LayoutHorizontalSpacing} +*/ +void QFormLayout::setVerticalSpacing(int spacing) +{ + Q_D(QFormLayout); + if (spacing != d->vSpacing) { + d->vSpacing = spacing; + invalidate(); + } +} + +int QFormLayout::verticalSpacing() const +{ + Q_D(const QFormLayout); + if (d->vSpacing >= 0) { + return d->vSpacing; + } else { + return qSmartSpacing(this, QStyle::PM_LayoutVerticalSpacing); + } +} + +/*! + This function sets both the vertical and horizontal spacing to + \a spacing. + + \sa setVerticalSpacing(), setHorizontalSpacing() +*/ +void QFormLayout::setSpacing(int spacing) +{ + Q_D(QFormLayout); + d->vSpacing = d->hSpacing = spacing; + invalidate(); +} + +/*! + If the vertical spacing is equal to the horizontal spacing, + this function returns that value; otherwise it returns -1. + + \sa setSpacing(), verticalSpacing(), horizontalSpacing() +*/ +int QFormLayout::spacing() const +{ + int hSpacing = horizontalSpacing(); + if (hSpacing == verticalSpacing()) { + return hSpacing; + } else { + return -1; + } +} + +void QFormLayoutPrivate::arrangeWidgets(const QVector<QLayoutStruct>& layouts, QRect &rect) +{ + Q_Q(QFormLayout); + + int i; + const int rr = m_matrix.rowCount(); + QWidget *w = q->parentWidget(); + Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : QApplication::layoutDirection(); + + Qt::Alignment formAlignment = fixedAlignment(q->formAlignment(), layoutDirection); + int leftOffset = 0; + int delta = rect.width() - formMaxWidth; + if (formAlignment & (Qt::AlignHCenter | Qt::AlignRight) && delta > 0) { + leftOffset = delta; + if (formAlignment & Qt::AlignHCenter) + leftOffset >>= 1; + } + + for (i = 0; i < rr; ++i) { + QFormLayoutItem *label = m_matrix(i, 0); + QFormLayoutItem *field = m_matrix(i, 1); + + if (label) { + int height = layouts.at(label->vLayoutIndex).size; + if ((label->expandingDirections() & Qt::Vertical) == 0) { + /* + If the field on the right-hand side is tall, + we want the label to be top-aligned, but not too + much. So we introduce a 7 / 4 factor so that it + gets some extra pixels at the top. + */ + height = qMin(height, + qMin(label->sizeHint.height() * 7 / 4, + label->maxSize.height())); + } + + QSize sz(qMin(label->layoutWidth, label->sizeHint.width()), height); + int x = leftOffset + rect.x() + label->layoutPos; + if (fixedAlignment(q->labelAlignment(), layoutDirection) & Qt::AlignRight) + x += label->layoutWidth - sz.width(); + QPoint p(x, layouts.at(label->vLayoutIndex).pos); + // ### expansion & sizepolicy stuff + + label->setGeometry(QStyle::visualRect(layoutDirection, rect, QRect(p, sz))); + } + + if (field) { + QSize sz(field->layoutWidth, layouts.at(field->vLayoutIndex).size); + QPoint p(field->layoutPos + leftOffset + rect.x(), layouts.at(field->vLayoutIndex).pos); +/* + if ((field->widget() && field->widget()->sizePolicy().horizontalPolicy() & (QSizePolicy::GrowFlag | QSizePolicy::ExpandFlag | QSizePolicy::IgnoreFlag)) + || (field->layout() && sz.width() < field->maxSize.width())) { + sz.rwidth() = field->layoutWidth; + } +*/ + if (field->maxSize.isValid()) + sz = sz.boundedTo(field->maxSize); + + field->setGeometry(QStyle::visualRect(layoutDirection, rect, QRect(p, sz))); + } + } +} + +/*! + Sets the widget in the given \a row for the given \a role to \a widget, extending the + layout with empty rows if necessary. + + If the cell is already occupied, the \a widget is not inserted and an error message is + sent to the console. + + \bold{Note:} For most applications, addRow() or insertRow() should be used instead of setWidget(). + + \sa setLayout() +*/ +void QFormLayout::setWidget(int row, ItemRole role, QWidget *widget) +{ + Q_D(QFormLayout); + int rowCnt = rowCount(); + if (row >= rowCnt) + d->insertRows(rowCnt, row - rowCnt + 1); + d->setWidget(row, role, widget); +} + +/*! + Sets the sub-layout in the given \a row for the given \a role to \a layout, extending the + form layout with empty rows if necessary. + + If the cell is already occupied, the \a layout is not inserted and an error message is + sent to the console. + + \bold{Note:} For most applications, addRow() or insertRow() should be used instead of setLayout(). + + \sa setWidget() +*/ +void QFormLayout::setLayout(int row, ItemRole role, QLayout *layout) +{ + Q_D(QFormLayout); + int rowCnt = rowCount(); + if (row >= rowCnt) + d->insertRows(rowCnt, row - rowCnt + 1); + d->setLayout(row, role, layout); +} + +/*! + Sets the item in the given \a row for the given \a role to \a item, extending the + layout with empty rows if necessary. + + If the cell is already occupied, the \a item is not inserted and an error message is + sent to the console. + The \a item spans both columns. + + \warning Do not use this function to add child layouts or child + widget items. Use setLayout() or setWidget() instead. + + \sa setLayout() +*/ +void QFormLayout::setItem(int row, ItemRole role, QLayoutItem *item) +{ + Q_D(QFormLayout); + int rowCnt = rowCount(); + if (row >= rowCnt) + d->insertRows(rowCnt, row - rowCnt + 1); + d->setItem(row, role, item); +} + +/*! + \internal + */ + +void QFormLayout::resetFieldGrowthPolicy() +{ + Q_D(QFormLayout); + d->fieldGrowthPolicy = DefaultFieldGrowthPolicy; +} + +/*! + \internal + */ + +void QFormLayout::resetRowWrapPolicy() +{ + Q_D(QFormLayout); + d->rowWrapPolicy = DefaultRowWrapPolicy; +} + +/*! + \internal + */ + +void QFormLayout::resetFormAlignment() +{ + Q_D(QFormLayout); + d->formAlignment = 0; +} + +/*! + \internal + */ + +void QFormLayout::resetLabelAlignment() +{ + Q_D(QFormLayout); + d->labelAlignment = 0; +} + +#if 0 +void QFormLayout::dump() const +{ + Q_D(const QFormLayout); + for (int i = 0; i < rowCount(); ++i) { + for (int j = 0; j < 2; ++j) { + qDebug("m_matrix(%d, %d) = %p", i, j, d->m_matrix(i, j)); + } + } + for (int i = 0; i < d->m_things.count(); ++i) + qDebug("m_things[%d] = %p", i, d->m_things.at(i)); +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qformlayout.h b/src/gui/kernel/qformlayout.h new file mode 100644 index 0000000000..f229ac2d83 --- /dev/null +++ b/src/gui/kernel/qformlayout.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFORMLAYOUT_H +#define QFORMLAYOUT_H + +#include <QtGui/QLayout> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFormLayoutPrivate; + +class Q_GUI_EXPORT QFormLayout : public QLayout +{ + Q_OBJECT + Q_ENUMS(FormStyle FieldGrowthPolicy RowWrapPolicy ItemRole) + Q_DECLARE_PRIVATE(QFormLayout) + Q_PROPERTY(FieldGrowthPolicy fieldGrowthPolicy READ fieldGrowthPolicy WRITE setFieldGrowthPolicy RESET resetFieldGrowthPolicy) + Q_PROPERTY(RowWrapPolicy rowWrapPolicy READ rowWrapPolicy WRITE setRowWrapPolicy RESET resetRowWrapPolicy) + Q_PROPERTY(Qt::Alignment labelAlignment READ labelAlignment WRITE setLabelAlignment RESET resetLabelAlignment) + Q_PROPERTY(Qt::Alignment formAlignment READ formAlignment WRITE setFormAlignment RESET resetFormAlignment) + Q_PROPERTY(int horizontalSpacing READ horizontalSpacing WRITE setHorizontalSpacing) + Q_PROPERTY(int verticalSpacing READ verticalSpacing WRITE setVerticalSpacing) + +public: + enum FieldGrowthPolicy { + FieldsStayAtSizeHint, + ExpandingFieldsGrow, + AllNonFixedFieldsGrow + }; + + enum RowWrapPolicy { + DontWrapRows, + WrapLongRows, + WrapAllRows + }; + + enum ItemRole { + LabelRole = 0, + FieldRole = 1, + SpanningRole = 2 + }; + + explicit QFormLayout(QWidget *parent = 0); + ~QFormLayout(); + + void setFieldGrowthPolicy(FieldGrowthPolicy policy); + FieldGrowthPolicy fieldGrowthPolicy() const; + void setRowWrapPolicy(RowWrapPolicy policy); + RowWrapPolicy rowWrapPolicy() const; + void setLabelAlignment(Qt::Alignment alignment); + Qt::Alignment labelAlignment() const; + void setFormAlignment(Qt::Alignment alignment); + Qt::Alignment formAlignment() const; + + void setHorizontalSpacing(int spacing); + int horizontalSpacing() const; + void setVerticalSpacing(int spacing); + int verticalSpacing() const; + + int spacing() const; + void setSpacing(int); + + void addRow(QWidget *label, QWidget *field); + void addRow(QWidget *label, QLayout *field); + void addRow(const QString &labelText, QWidget *field); + void addRow(const QString &labelText, QLayout *field); + void addRow(QWidget *widget); + void addRow(QLayout *layout); + + void insertRow(int row, QWidget *label, QWidget *field); + void insertRow(int row, QWidget *label, QLayout *field); + void insertRow(int row, const QString &labelText, QWidget *field); + void insertRow(int row, const QString &labelText, QLayout *field); + void insertRow(int row, QWidget *widget); + void insertRow(int row, QLayout *layout); + + void setItem(int row, ItemRole role, QLayoutItem *item); + void setWidget(int row, ItemRole role, QWidget *widget); + void setLayout(int row, ItemRole role, QLayout *layout); + + QLayoutItem *itemAt(int row, ItemRole role) const; + void getItemPosition(int index, int *rowPtr, ItemRole *rolePtr) const; + void getWidgetPosition(QWidget *widget, int *rowPtr, ItemRole *rolePtr) const; + void getLayoutPosition(QLayout *layout, int *rowPtr, ItemRole *rolePtr) const; + QWidget *labelForField(QWidget *field) const; + QWidget *labelForField(QLayout *field) const; + + // reimplemented from QLayout + void addItem(QLayoutItem *item); + QLayoutItem *itemAt(int index) const; + QLayoutItem *takeAt(int index); + + void setGeometry(const QRect &rect); + QSize minimumSize() const; + QSize sizeHint() const; + void invalidate(); + + bool hasHeightForWidth() const; + int heightForWidth(int width) const; + Qt::Orientations expandingDirections() const; + int count() const; + + int rowCount() const; + +#if 0 + void dump() const; +#endif + +private: + void resetFieldGrowthPolicy(); + void resetRowWrapPolicy(); + void resetLabelAlignment(); + void resetFormAlignment(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/kernel/qgenericplugin_qpa.cpp b/src/gui/kernel/qgenericplugin_qpa.cpp new file mode 100644 index 0000000000..43d6525bb6 --- /dev/null +++ b/src/gui/kernel/qgenericplugin_qpa.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgenericplugin_qpa.h" + +#ifndef QT_NO_LIBRARY + +QT_BEGIN_NAMESPACE + +/*! + \class QGenericPlugin + \ingroup plugins + \ingroup qpa + + \brief The QGenericPlugin class is an abstract base class for + window-system related plugins in Qt QPA. + + Note that this class is only available in \l{Qt QPA}. + + A mouse plugin can be created by subclassing + QGenericPlugin and reimplementing the pure virtual keys() and + create() functions. By exporting the derived class using the + Q_EXPORT_PLUGIN2() macro, The default implementation of the + QGenericPluginFactory class will automatically detect the plugin and + load the driver into the server application at run-time. See \l + {How to Create Qt Plugins} for details. + + \sa QGenericPluginFactory +*/ + +/*! + \fn QStringList QGenericPlugin::keys() const + + Implement this function to return the list of valid keys, i.e. the + drivers supported by this plugin. + + \sa create() +*/ + +/*! + Constructs a plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QGenericPlugin::QGenericPlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QGenericPlugin::~QGenericPlugin() +{ +} + +/*! + \fn QObject* QGenericPlugin::create(const QString &key, const QString& specification) + + Implement this function to create a driver matching the type + specified by the given \a key and \a specification parameters. Note that + keys are case-insensitive. + + \sa keys() +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_LIBRARY diff --git a/src/gui/kernel/qgenericplugin_qpa.h b/src/gui/kernel/qgenericplugin_qpa.h new file mode 100644 index 0000000000..e1792cd417 --- /dev/null +++ b/src/gui/kernel/qgenericplugin_qpa.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGENERICPLUGIN_QPA_H +#define QGENERICPLUGIN_QPA_H + +#include <QtCore/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_LIBRARY + +//class QGenericObject; ????? + + struct Q_GUI_EXPORT QGenericPluginFactoryInterface : public QFactoryInterface +{ + virtual QObject* create(const QString &name, const QString &spec) = 0; +}; + +#define QGenericPluginFactoryInterface_iid "com.trolltech.Qt.QGenericPluginFactoryInterface" +Q_DECLARE_INTERFACE(QGenericPluginFactoryInterface, QGenericPluginFactoryInterface_iid) + +class Q_GUI_EXPORT QGenericPlugin : public QObject, public QGenericPluginFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QGenericPluginFactoryInterface:QFactoryInterface) +public: + explicit QGenericPlugin(QObject *parent = 0); + ~QGenericPlugin(); + + virtual QStringList keys() const = 0; + virtual QObject* create(const QString& name, const QString &spec) = 0; +}; + +#endif // QT_NO_LIBRARY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGENERICPLUGIN_QPA_H diff --git a/src/gui/kernel/qgenericpluginfactory_qpa.cpp b/src/gui/kernel/qgenericpluginfactory_qpa.cpp new file mode 100644 index 0000000000..abc575ae86 --- /dev/null +++ b/src/gui/kernel/qgenericpluginfactory_qpa.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgenericpluginfactory_qpa.h" + +#include "qapplication.h" +#include "private/qfactoryloader_p.h" +#include "qgenericplugin_qpa.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QGenericPluginFactoryInterface_iid, + QLatin1String("/generic"), Qt::CaseInsensitive)) + +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + +/*! + \class QGenericPluginFactory + \ingroup qpa + + \brief The QGenericPluginFactory class creates window-system + related plugin drivers in Qt QPA. + + Note that this class is only available in \l{Qt QPA}. + + + \sa QGenericPlugin +*/ + +/*! + Creates the driver specified by \a key, using the given \a specification. + + Note that the keys are case-insensitive. + + \sa keys() +*/ +QObject *QGenericPluginFactory::create(const QString& key, const QString &specification) +{ + QString driver = key.toLower(); + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + if (QGenericPluginFactoryInterface *factory = qobject_cast<QGenericPluginFactoryInterface*>(loader()->instance(driver))) + return factory->create(driver, specification); +#endif +#endif + return 0; +} + +/*! + Returns the list of valid keys, i.e. the available mouse drivers. + + \sa create() +*/ +QStringList QGenericPluginFactory::keys() +{ + QStringList list; + +#if !defined(Q_OS_WIN32) || defined(QT_MAKEDLL) +#ifndef QT_NO_LIBRARY + QStringList plugins = loader()->keys(); + for (int i = 0; i < plugins.size(); ++i) { + if (!list.contains(plugins.at(i))) + list += plugins.at(i); + } +#endif //QT_NO_LIBRARY +#endif //QT_MAKEDLL + return list; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qgenericpluginfactory_qpa.h b/src/gui/kernel/qgenericpluginfactory_qpa.h new file mode 100644 index 0000000000..59eac386ed --- /dev/null +++ b/src/gui/kernel/qgenericpluginfactory_qpa.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGENERICPLUGINFACTORY_QPA_H +#define QGENERICPLUGINFACTORY_QPA_H + +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QObject; + +class Q_GUI_EXPORT QGenericPluginFactory +{ +public: + static QStringList keys(); + static QObject *create(const QString&, const QString &); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGENERICPLUGINFACTORY_QPA_H diff --git a/src/gui/kernel/qgesture.cpp b/src/gui/kernel/qgesture.cpp new file mode 100644 index 0000000000..56daba2efc --- /dev/null +++ b/src/gui/kernel/qgesture.cpp @@ -0,0 +1,807 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgesture.h" +#include "private/qgesture_p.h" +#include "private/qstandardgestures_p.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + + /*! + \class QGesture + \since 4.6 + \ingroup gestures + + \brief The QGesture class represents a gesture, containing properties that + describe the corresponding user input. + + Gesture objects are not constructed directly by developers. They are created by + the QGestureRecognizer object that is registered with the application; see + QGestureRecognizer::registerRecognizer(). + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \section1 Gesture Properties + + The class has a list of properties that can be queried by the user to get + some gesture-specific arguments. For example, the pinch gesture has a scale + factor that is exposed as a property. + + Developers of custom gesture recognizers can add additional properties in + order to provide additional information about a gesture. This can be done + by adding new dynamic properties to a QGesture object, or by subclassing + the QGesture class (or one of its subclasses). + + \section1 Lifecycle of a Gesture Object + + A QGesture instance is implicitly created when needed and is owned by Qt. + Developers should never destroy them or store them for later use as Qt may + destroy particular instances of them and create new ones to replace them. + + The registered gesture recognizer monitors the input events for the target + object via its \l{QGestureRecognizer::}{recognize()} function, updating the + properties of the gesture object as required. + + The gesture object may be delivered to the target object in a QGestureEvent if + the corresponding gesture is active or has just been canceled. Each event that + is delivered contains a list of gesture objects, since support for more than + one gesture may be enabled for the target object. Due to the way events are + handled in Qt, gesture events may be filtered by other objects. + + \sa QGestureEvent, QGestureRecognizer +*/ + +/*! + Constructs a new gesture object with the given \a parent. + + QGesture objects are created by gesture recognizers in the + QGestureRecognizer::create() function. +*/ +QGesture::QGesture(QObject *parent) + : QObject(*new QGesturePrivate, parent) +{ + d_func()->gestureType = Qt::CustomGesture; +} + +/*! + \internal +*/ +QGesture::QGesture(QGesturePrivate &dd, QObject *parent) + : QObject(dd, parent) +{ +} + +/*! + Destroys the gesture object. +*/ +QGesture::~QGesture() +{ +} + +/*! + \property QGesture::state + \brief the current state of the gesture +*/ + +/*! + \property QGesture::gestureType + \brief the type of the gesture +*/ + +/*! + \property QGesture::hotSpot + + \brief The point that is used to find the receiver for the gesture event. + + The hot-spot is a point in the global coordinate system, use + QWidget::mapFromGlobal() or QGestureEvent::mapToGraphicsScene() to get a + local hot-spot. + + The hot-spot should be set by the gesture recognizer to allow gesture event + delivery to a QGraphicsObject. +*/ + +/*! + \property QGesture::hasHotSpot + \brief whether the gesture has a hot-spot +*/ + +Qt::GestureType QGesture::gestureType() const +{ + return d_func()->gestureType; +} + +Qt::GestureState QGesture::state() const +{ + return d_func()->state; +} + +QPointF QGesture::hotSpot() const +{ + return d_func()->hotSpot; +} + +void QGesture::setHotSpot(const QPointF &value) +{ + Q_D(QGesture); + d->hotSpot = value; + d->isHotSpotSet = true; +} + +bool QGesture::hasHotSpot() const +{ + return d_func()->isHotSpotSet; +} + +void QGesture::unsetHotSpot() +{ + d_func()->isHotSpotSet = false; +} + +/*! + \property QGesture::gestureCancelPolicy + \brief the policy for deciding what happens on accepting a gesture + + On accepting one gesture Qt can automatically cancel other gestures + that belong to other targets. The policy is normally set to not cancel + any other gestures and can be set to cancel all active gestures in the + context. For example for all child widgets. +*/ + +/*! + \enum QGesture::GestureCancelPolicy + + This enum describes how accepting a gesture can cancel other gestures + automatically. + + \value CancelNone On accepting this gesture no other gestures will be affected. + + \value CancelAllInContext On accepting this gesture all gestures that are + active in the context (respecting the Qt::GestureFlag that were specified + when subscribed to the gesture) will be cancelled. +*/ + +void QGesture::setGestureCancelPolicy(GestureCancelPolicy policy) +{ + Q_D(QGesture); + d->gestureCancelPolicy = static_cast<uint>(policy); +} + +QGesture::GestureCancelPolicy QGesture::gestureCancelPolicy() const +{ + Q_D(const QGesture); + return static_cast<GestureCancelPolicy>(d->gestureCancelPolicy); +} + +/*! + \class QPanGesture + \since 4.6 + \brief The QPanGesture class describes a panning gesture made by the user. + \ingroup gestures + + \image pangesture.png + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \sa QPinchGesture, QSwipeGesture +*/ + +/*! + \property QPanGesture::lastOffset + \brief the last offset recorded for this gesture + + The last offset contains the change in position of the user's input as + reported in the \l offset property when a previous gesture event was + delivered for this gesture. + + If no previous event was delivered with information about this gesture + (i.e., this gesture object contains information about the first movement + in the gesture) then this property contains a zero size. +*/ + +/*! + \property QPanGesture::offset + \brief the total offset from the first input position to the current input + position + + The offset measures the total change in position of the user's input + covered by the gesture on the input device. +*/ + +/*! + \property QPanGesture::delta + \brief the offset from the previous input position to the current input + + This is essentially the same as the difference between offset() and + lastOffset(). +*/ + +/*! + \property QPanGesture::acceleration + \brief the acceleration in the motion of the touch point for this gesture +*/ + +/*! + \property QPanGesture::horizontalVelocity + \brief the horizontal component of the motion of the touch point for this + gesture + \since 4.7.1 + \internal + + \sa verticalVelocity, acceleration +*/ + +/*! + \property QPanGesture::verticalVelocity + \brief the vertical component of the motion of the touch point for this + gesture + \since 4.7.1 + \internal + + \sa horizontalVelocity, acceleration +*/ + +/*! + \internal +*/ +QPanGesture::QPanGesture(QObject *parent) + : QGesture(*new QPanGesturePrivate, parent) +{ + d_func()->gestureType = Qt::PanGesture; +} + + +QPointF QPanGesture::lastOffset() const +{ + return d_func()->lastOffset; +} + +QPointF QPanGesture::offset() const +{ + return d_func()->offset; +} + +QPointF QPanGesture::delta() const +{ + Q_D(const QPanGesture); + return d->offset - d->lastOffset; +} + +qreal QPanGesture::acceleration() const +{ + return d_func()->acceleration; +} + +void QPanGesture::setLastOffset(const QPointF &value) +{ + d_func()->lastOffset = value; +} + +void QPanGesture::setOffset(const QPointF &value) +{ + d_func()->offset = value; +} + +void QPanGesture::setAcceleration(qreal value) +{ + d_func()->acceleration = value; +} + +/*! + \class QPinchGesture + \since 4.6 + \brief The QPinchGesture class describes a pinch gesture made by the user. + \ingroup touch + \ingroup gestures + + A pinch gesture is a form of touch user input in which the user typically + touches two points on the input device with a thumb and finger, before moving + them closer together or further apart to change the scale factor, zoom, or level + of detail of the user interface. + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \image pinchgesture.png + + Instead of repeatedly applying the same pinching gesture, the user may + continue to touch the input device in one place, and apply a second touch + to a new point, continuing the gesture. When this occurs, gesture events + will continue to be delivered to the target object, containing an instance + of QPinchGesture in the Qt::GestureUpdated state. + + \sa QPanGesture, QSwipeGesture +*/ + +/*! + \enum QPinchGesture::ChangeFlag + + This enum describes the changes that can occur to the properties of + the gesture object. + + \value ScaleFactorChanged The scale factor held by scaleFactor changed. + \value RotationAngleChanged The rotation angle held by rotationAngle changed. + \value CenterPointChanged The center point held by centerPoint changed. + + \sa changeFlags, totalChangeFlags +*/ + +/*! + \property QPinchGesture::totalChangeFlags + \brief the property of the gesture that has change + + This property indicates which of the other properties has changed since the + gesture has started. You can use this information to determine which aspect + of your user interface needs to be updated. + + \sa changeFlags, scaleFactor, rotationAngle, centerPoint +*/ + +/*! + \property QPinchGesture::changeFlags + \brief the property of the gesture that has changed in the current step + + This property indicates which of the other properties has changed since + the previous gesture event included information about this gesture. You + can use this information to determine which aspect of your user interface + needs to be updated. + + \sa totalChangeFlags, scaleFactor, rotationAngle, centerPoint +*/ + +/*! + \property QPinchGesture::totalScaleFactor + \brief the total scale factor + + The total scale factor measures the total change in scale factor from the + original value to the current scale factor. + + \sa scaleFactor, lastScaleFactor +*/ +/*! + \property QPinchGesture::lastScaleFactor + \brief the last scale factor recorded for this gesture + + The last scale factor contains the scale factor reported in the + \l scaleFactor property when a previous gesture event included + information about this gesture. + + If no previous event was delivered with information about this gesture + (i.e., this gesture object contains information about the first movement + in the gesture) then this property contains zero. + + \sa scaleFactor, totalScaleFactor +*/ +/*! + \property QPinchGesture::scaleFactor + \brief the current scale factor + + The scale factor measures the scale factor associated with the distance + between two of the user's inputs on a touch device. + + \sa totalScaleFactor, lastScaleFactor +*/ + +/*! + \property QPinchGesture::totalRotationAngle + \brief the total angle covered by the gesture + + This total angle measures the complete angle covered by the gesture. Usually, this + is equal to the value held by the \l rotationAngle property, except in the case where + the user performs multiple rotations by removing and repositioning one of the touch + points, as described above. In this case, the total angle will be the sum of the + rotation angles for the multiple stages of the gesture. + + \sa rotationAngle, lastRotationAngle +*/ +/*! + \property QPinchGesture::lastRotationAngle + \brief the last reported angle covered by the gesture motion + + The last rotation angle is the angle as reported in the \l rotationAngle property + when a previous gesture event was delivered for this gesture. + + \sa rotationAngle, totalRotationAngle +*/ +/*! + \property QPinchGesture::rotationAngle + \brief the angle covered by the gesture motion + + \sa totalRotationAngle, lastRotationAngle +*/ + +/*! + \property QPinchGesture::startCenterPoint + \brief the starting position of the center point + + \sa centerPoint, lastCenterPoint +*/ +/*! + \property QPinchGesture::lastCenterPoint + \brief the last position of the center point recorded for this gesture + + \sa centerPoint, startCenterPoint +*/ +/*! + \property QPinchGesture::centerPoint + \brief the current center point + + The center point is the midpoint between the two input points in the gesture. + + \sa startCenterPoint, lastCenterPoint +*/ + +/*! + \internal +*/ +QPinchGesture::QPinchGesture(QObject *parent) + : QGesture(*new QPinchGesturePrivate, parent) +{ + d_func()->gestureType = Qt::PinchGesture; +} + +QPinchGesture::ChangeFlags QPinchGesture::totalChangeFlags() const +{ + return d_func()->totalChangeFlags; +} + +void QPinchGesture::setTotalChangeFlags(QPinchGesture::ChangeFlags value) +{ + d_func()->totalChangeFlags = value; +} + +QPinchGesture::ChangeFlags QPinchGesture::changeFlags() const +{ + return d_func()->changeFlags; +} + +void QPinchGesture::setChangeFlags(QPinchGesture::ChangeFlags value) +{ + d_func()->changeFlags = value; +} + +QPointF QPinchGesture::startCenterPoint() const +{ + return d_func()->startCenterPoint; +} + +QPointF QPinchGesture::lastCenterPoint() const +{ + return d_func()->lastCenterPoint; +} + +QPointF QPinchGesture::centerPoint() const +{ + return d_func()->centerPoint; +} + +void QPinchGesture::setStartCenterPoint(const QPointF &value) +{ + d_func()->startCenterPoint = value; +} + +void QPinchGesture::setLastCenterPoint(const QPointF &value) +{ + d_func()->lastCenterPoint = value; +} + +void QPinchGesture::setCenterPoint(const QPointF &value) +{ + d_func()->centerPoint = value; +} + + +qreal QPinchGesture::totalScaleFactor() const +{ + return d_func()->totalScaleFactor; +} + +qreal QPinchGesture::lastScaleFactor() const +{ + return d_func()->lastScaleFactor; +} + +qreal QPinchGesture::scaleFactor() const +{ + return d_func()->scaleFactor; +} + +void QPinchGesture::setTotalScaleFactor(qreal value) +{ + d_func()->totalScaleFactor = value; +} + +void QPinchGesture::setLastScaleFactor(qreal value) +{ + d_func()->lastScaleFactor = value; +} + +void QPinchGesture::setScaleFactor(qreal value) +{ + d_func()->scaleFactor = value; +} + + +qreal QPinchGesture::totalRotationAngle() const +{ + return d_func()->totalRotationAngle; +} + +qreal QPinchGesture::lastRotationAngle() const +{ + return d_func()->lastRotationAngle; +} + +qreal QPinchGesture::rotationAngle() const +{ + return d_func()->rotationAngle; +} + +void QPinchGesture::setTotalRotationAngle(qreal value) +{ + d_func()->totalRotationAngle = value; +} + +void QPinchGesture::setLastRotationAngle(qreal value) +{ + d_func()->lastRotationAngle = value; +} + +void QPinchGesture::setRotationAngle(qreal value) +{ + d_func()->rotationAngle = value; +} + +/*! + \class QSwipeGesture + \since 4.6 + \brief The QSwipeGesture class describes a swipe gesture made by the user. + \ingroup gestures + + \image swipegesture.png + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \sa QPanGesture, QPinchGesture +*/ + +/*! + \enum QSwipeGesture::SwipeDirection + + This enum describes the possible directions for the gesture's motion + along the horizontal and vertical axes. + + \value NoDirection The gesture had no motion associated with it on a particular axis. + \value Left The gesture involved a horizontal motion to the left. + \value Right The gesture involved a horizontal motion to the right. + \value Up The gesture involved an upward vertical motion. + \value Down The gesture involved a downward vertical motion. +*/ + +/*! + \property QSwipeGesture::horizontalDirection + \brief the horizontal direction of the gesture + + If the gesture has a horizontal component, the horizontal direction + is either Left or Right; otherwise, it is NoDirection. + + \sa verticalDirection, swipeAngle +*/ + +/*! + \property QSwipeGesture::verticalDirection + \brief the vertical direction of the gesture + + If the gesture has a vertical component, the vertical direction + is either Up or Down; otherwise, it is NoDirection. + + \sa horizontalDirection, swipeAngle +*/ + +/*! + \property QSwipeGesture::swipeAngle + \brief the angle of the motion associated with the gesture + + If the gesture has either a horizontal or vertical component, the + swipe angle describes the angle between the direction of motion and the + x-axis as defined using the standard widget + \l{Coordinate System}{coordinate system}. + + \sa horizontalDirection, verticalDirection +*/ + +/*! + \property QSwipeGesture::velocity + \since 4.7.1 + \internal +*/ + +/*! + \internal +*/ +QSwipeGesture::QSwipeGesture(QObject *parent) + : QGesture(*new QSwipeGesturePrivate, parent) +{ + d_func()->gestureType = Qt::SwipeGesture; +} + +QSwipeGesture::SwipeDirection QSwipeGesture::horizontalDirection() const +{ + Q_D(const QSwipeGesture); + if (d->swipeAngle < 0 || d->swipeAngle == 90 || d->swipeAngle == 270) + return QSwipeGesture::NoDirection; + else if (d->swipeAngle < 90 || d->swipeAngle > 270) + return QSwipeGesture::Right; + else + return QSwipeGesture::Left; +} + +QSwipeGesture::SwipeDirection QSwipeGesture::verticalDirection() const +{ + Q_D(const QSwipeGesture); + if (d->swipeAngle <= 0 || d->swipeAngle == 180) + return QSwipeGesture::NoDirection; + else if (d->swipeAngle < 180) + return QSwipeGesture::Up; + else + return QSwipeGesture::Down; +} + +qreal QSwipeGesture::swipeAngle() const +{ + return d_func()->swipeAngle; +} + +void QSwipeGesture::setSwipeAngle(qreal value) +{ + d_func()->swipeAngle = value; +} + +/*! + \class QTapGesture + \since 4.6 + \brief The QTapGesture class describes a tap gesture made by the user. + \ingroup gestures + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \sa QPanGesture, QPinchGesture +*/ + +/*! + \property QTapGesture::position + \brief the position of the tap +*/ + +/*! + \internal +*/ +QTapGesture::QTapGesture(QObject *parent) + : QGesture(*new QTapGesturePrivate, parent) +{ + d_func()->gestureType = Qt::TapGesture; +} + +QPointF QTapGesture::position() const +{ + return d_func()->position; +} + +void QTapGesture::setPosition(const QPointF &value) +{ + d_func()->position = value; +} +/*! + \class QTapAndHoldGesture + \since 4.6 + \brief The QTapAndHoldGesture class describes a tap-and-hold (aka LongTap) + gesture made by the user. + \ingroup gestures + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \sa QPanGesture, QPinchGesture +*/ + +/*! + \property QTapAndHoldGesture::position + \brief the position of the tap +*/ + +/*! + \internal +*/ +QTapAndHoldGesture::QTapAndHoldGesture(QObject *parent) + : QGesture(*new QTapAndHoldGesturePrivate, parent) +{ + d_func()->gestureType = Qt::TapAndHoldGesture; +} + +QPointF QTapAndHoldGesture::position() const +{ + return d_func()->position; +} + +void QTapAndHoldGesture::setPosition(const QPointF &value) +{ + d_func()->position = value; +} + +/*! + Set the timeout, in milliseconds, before the gesture triggers. + + The recognizer will detect a touch down and and if \a msecs + later the touch is still down, it will trigger the QTapAndHoldGesture. + The default value is 700 milliseconds. +*/ +// static +void QTapAndHoldGesture::setTimeout(int msecs) +{ + QTapAndHoldGesturePrivate::Timeout = msecs; +} + +/*! + Gets the timeout, in milliseconds, before the gesture triggers. + + The recognizer will detect a touch down and and if timeout() + later the touch is still down, it will trigger the QTapAndHoldGesture. + The default value is 700 milliseconds. +*/ +// static +int QTapAndHoldGesture::timeout() +{ + return QTapAndHoldGesturePrivate::Timeout; +} + +int QTapAndHoldGesturePrivate::Timeout = 700; // in ms + +QT_END_NAMESPACE + +#include <moc_qgesture.cpp> + +#endif // QT_NO_GESTURES diff --git a/src/gui/kernel/qgesture.h b/src/gui/kernel/qgesture.h new file mode 100644 index 0000000000..6f9c994b1e --- /dev/null +++ b/src/gui/kernel/qgesture.h @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGESTURE_H +#define QGESTURE_H + +#include <QtCore/qobject.h> +#include <QtCore/qlist.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qpoint.h> +#include <QtCore/qrect.h> +#include <QtCore/qmetatype.h> + +#ifndef QT_NO_GESTURES + +QT_BEGIN_HEADER + +Q_DECLARE_METATYPE(Qt::GestureState) +Q_DECLARE_METATYPE(Qt::GestureType) + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGesturePrivate; +class Q_GUI_EXPORT QGesture : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGesture) + + Q_PROPERTY(Qt::GestureState state READ state) + Q_PROPERTY(Qt::GestureType gestureType READ gestureType) + Q_PROPERTY(QGesture::GestureCancelPolicy gestureCancelPolicy READ gestureCancelPolicy WRITE setGestureCancelPolicy) + Q_PROPERTY(QPointF hotSpot READ hotSpot WRITE setHotSpot RESET unsetHotSpot) + Q_PROPERTY(bool hasHotSpot READ hasHotSpot) + +public: + explicit QGesture(QObject *parent = 0); + ~QGesture(); + + Qt::GestureType gestureType() const; + + Qt::GestureState state() const; + + QPointF hotSpot() const; + void setHotSpot(const QPointF &value); + bool hasHotSpot() const; + void unsetHotSpot(); + + enum GestureCancelPolicy { + CancelNone = 0, + CancelAllInContext + }; + + void setGestureCancelPolicy(GestureCancelPolicy policy); + GestureCancelPolicy gestureCancelPolicy() const; + +protected: + QGesture(QGesturePrivate &dd, QObject *parent); + +private: + friend class QGestureEvent; + friend class QGestureRecognizer; + friend class QGestureManager; + friend class QGraphicsScenePrivate; +}; + +class QPanGesturePrivate; +class Q_GUI_EXPORT QPanGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPanGesture) + + Q_PROPERTY(QPointF lastOffset READ lastOffset WRITE setLastOffset) + Q_PROPERTY(QPointF offset READ offset WRITE setOffset) + Q_PROPERTY(QPointF delta READ delta STORED false) + Q_PROPERTY(qreal acceleration READ acceleration WRITE setAcceleration) + Q_PRIVATE_PROPERTY(QPanGesture::d_func(), qreal horizontalVelocity READ horizontalVelocity WRITE setHorizontalVelocity) + Q_PRIVATE_PROPERTY(QPanGesture::d_func(), qreal verticalVelocity READ verticalVelocity WRITE setVerticalVelocity) + +public: + QPanGesture(QObject *parent = 0); + + QPointF lastOffset() const; + QPointF offset() const; + QPointF delta() const; + qreal acceleration() const; + + void setLastOffset(const QPointF &value); + void setOffset(const QPointF &value); + void setAcceleration(qreal value); + + friend class QPanGestureRecognizer; + friend class QWinNativePanGestureRecognizer; +}; + +class QPinchGesturePrivate; +class Q_GUI_EXPORT QPinchGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPinchGesture) + Q_FLAGS(ChangeFlags ChangeFlag) + +public: + enum ChangeFlag { + ScaleFactorChanged = 0x1, + RotationAngleChanged = 0x2, + CenterPointChanged = 0x4 + }; + Q_DECLARE_FLAGS(ChangeFlags, ChangeFlag) + + Q_PROPERTY(ChangeFlags totalChangeFlags READ totalChangeFlags WRITE setTotalChangeFlags) + Q_PROPERTY(ChangeFlags changeFlags READ changeFlags WRITE setChangeFlags) + + Q_PROPERTY(qreal totalScaleFactor READ totalScaleFactor WRITE setTotalScaleFactor) + Q_PROPERTY(qreal lastScaleFactor READ lastScaleFactor WRITE setLastScaleFactor) + Q_PROPERTY(qreal scaleFactor READ scaleFactor WRITE setScaleFactor) + + Q_PROPERTY(qreal totalRotationAngle READ totalRotationAngle WRITE setTotalRotationAngle) + Q_PROPERTY(qreal lastRotationAngle READ lastRotationAngle WRITE setLastRotationAngle) + Q_PROPERTY(qreal rotationAngle READ rotationAngle WRITE setRotationAngle) + + Q_PROPERTY(QPointF startCenterPoint READ startCenterPoint WRITE setStartCenterPoint) + Q_PROPERTY(QPointF lastCenterPoint READ lastCenterPoint WRITE setLastCenterPoint) + Q_PROPERTY(QPointF centerPoint READ centerPoint WRITE setCenterPoint) + +public: + QPinchGesture(QObject *parent = 0); + + ChangeFlags totalChangeFlags() const; + void setTotalChangeFlags(ChangeFlags value); + + ChangeFlags changeFlags() const; + void setChangeFlags(ChangeFlags value); + + QPointF startCenterPoint() const; + QPointF lastCenterPoint() const; + QPointF centerPoint() const; + void setStartCenterPoint(const QPointF &value); + void setLastCenterPoint(const QPointF &value); + void setCenterPoint(const QPointF &value); + + qreal totalScaleFactor() const; + qreal lastScaleFactor() const; + qreal scaleFactor() const; + void setTotalScaleFactor(qreal value); + void setLastScaleFactor(qreal value); + void setScaleFactor(qreal value); + + qreal totalRotationAngle() const; + qreal lastRotationAngle() const; + qreal rotationAngle() const; + void setTotalRotationAngle(qreal value); + void setLastRotationAngle(qreal value); + void setRotationAngle(qreal value); + + friend class QPinchGestureRecognizer; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPinchGesture::ChangeFlags) + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QPinchGesture::ChangeFlags) + +QT_BEGIN_NAMESPACE + +class QSwipeGesturePrivate; +class Q_GUI_EXPORT QSwipeGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSwipeGesture) + Q_ENUMS(SwipeDirection) + + Q_PROPERTY(SwipeDirection horizontalDirection READ horizontalDirection STORED false) + Q_PROPERTY(SwipeDirection verticalDirection READ verticalDirection STORED false) + Q_PROPERTY(qreal swipeAngle READ swipeAngle WRITE setSwipeAngle) + Q_PRIVATE_PROPERTY(QSwipeGesture::d_func(), qreal velocity READ velocity WRITE setVelocity) + +public: + enum SwipeDirection { NoDirection, Left, Right, Up, Down }; + QSwipeGesture(QObject *parent = 0); + + SwipeDirection horizontalDirection() const; + SwipeDirection verticalDirection() const; + + qreal swipeAngle() const; + void setSwipeAngle(qreal value); + + friend class QSwipeGestureRecognizer; +}; + +class QTapGesturePrivate; +class Q_GUI_EXPORT QTapGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QTapGesture) + + Q_PROPERTY(QPointF position READ position WRITE setPosition) + +public: + QTapGesture(QObject *parent = 0); + + QPointF position() const; + void setPosition(const QPointF &pos); + + friend class QTapGestureRecognizer; +}; + +class QTapAndHoldGesturePrivate; +class Q_GUI_EXPORT QTapAndHoldGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QTapAndHoldGesture) + + Q_PROPERTY(QPointF position READ position WRITE setPosition) + +public: + QTapAndHoldGesture(QObject *parent = 0); + + QPointF position() const; + void setPosition(const QPointF &pos); + + static void setTimeout(int msecs); + static int timeout(); + + friend class QTapAndHoldGestureRecognizer; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGesture::GestureCancelPolicy) +QT_END_HEADER + +#endif // QT_NO_GESTURES + +#endif // QGESTURE_H diff --git a/src/gui/kernel/qgesture_p.h b/src/gui/kernel/qgesture_p.h new file mode 100644 index 0000000000..4fd4446cb6 --- /dev/null +++ b/src/gui/kernel/qgesture_p.h @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGESTURE_P_H +#define QGESTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qrect.h" +#include "qpoint.h" +#include "qgesture.h" +#include "qelapsedtimer.h" +#include "private/qobject_p.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QGesturePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QGesture) + +public: + QGesturePrivate() + : gestureType(Qt::CustomGesture), state(Qt::NoGesture), + isHotSpotSet(false), gestureCancelPolicy(0) + { + } + + Qt::GestureType gestureType; + Qt::GestureState state; + QPointF hotSpot; + QPointF sceneHotSpot; + uint isHotSpotSet : 1; + uint gestureCancelPolicy : 2; +}; + +class QPanGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QPanGesture) + +public: + QPanGesturePrivate() + : acceleration(0), xVelocity(0), yVelocity(0) + { + } + + qreal horizontalVelocity() const { return xVelocity; } + void setHorizontalVelocity(qreal value) { xVelocity = value; } + qreal verticalVelocity() const { return yVelocity; } + void setVerticalVelocity(qreal value) { yVelocity = value; } + + QPointF lastOffset; + QPointF offset; + QPoint startPosition; + qreal acceleration; + qreal xVelocity; + qreal yVelocity; +}; + +class QPinchGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QPinchGesture) + +public: + QPinchGesturePrivate() + : totalChangeFlags(0), changeFlags(0), + totalScaleFactor(1), lastScaleFactor(1), scaleFactor(1), + totalRotationAngle(0), lastRotationAngle(0), rotationAngle(0), + isNewSequence(true) + { + } + + QPinchGesture::ChangeFlags totalChangeFlags; + QPinchGesture::ChangeFlags changeFlags; + + QPointF startCenterPoint; + QPointF lastCenterPoint; + QPointF centerPoint; + + qreal totalScaleFactor; + qreal lastScaleFactor; + qreal scaleFactor; + + qreal totalRotationAngle; + qreal lastRotationAngle; + qreal rotationAngle; + + bool isNewSequence; + QPointF startPosition[2]; +}; + +class QSwipeGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QSwipeGesture) + +public: + QSwipeGesturePrivate() + : horizontalDirection(QSwipeGesture::NoDirection), + verticalDirection(QSwipeGesture::NoDirection), + swipeAngle(0), + started(false), velocityValue(0) + { + } + + qreal velocity() const { return velocityValue; } + void setVelocity(qreal value) { velocityValue = value; } + + QSwipeGesture::SwipeDirection horizontalDirection; + QSwipeGesture::SwipeDirection verticalDirection; + qreal swipeAngle; + + QPoint lastPositions[3]; + bool started; + qreal velocityValue; + QElapsedTimer time; +}; + +class QTapGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QTapGesture) + +public: + QTapGesturePrivate() + { + } + + QPointF position; +}; + +class QTapAndHoldGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QTapAndHoldGesture) + +public: + QTapAndHoldGesturePrivate() + : timerId(0) + { + } + + QPointF position; + int timerId; + static int Timeout; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QGESTURE_P_H diff --git a/src/gui/kernel/qgesturemanager.cpp b/src/gui/kernel/qgesturemanager.cpp new file mode 100644 index 0000000000..5359fb37e8 --- /dev/null +++ b/src/gui/kernel/qgesturemanager.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qgesturemanager_p.h" +#include "private/qstandardgestures_p.h" +#include "private/qwidget_p.h" +#include "private/qgesture_p.h" +#include "private/qgraphicsitem_p.h" +#include "private/qevent_p.h" +#include "private/qapplication_p.h" +#include "qgesture.h" +#include "qevent.h" +#include "qgraphicsitem.h" + +#ifdef Q_WS_MAC +#include "qmacgesturerecognizer_mac_p.h" +#endif +#if defined(Q_OS_WIN) && !defined(QT_NO_NATIVE_GESTURES) +#include "qwinnativepangesturerecognizer_win_p.h" +#endif + +#include "qdebug.h" + +// #define GESTURE_DEBUG +#ifndef GESTURE_DEBUG +# define DEBUG if (0) qDebug +#else +# define DEBUG qDebug +#endif + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +QGestureManager::QGestureManager(QObject *parent) + : QObject(parent), state(NotGesture), m_lastCustomGestureId(Qt::CustomGesture) +{ + qRegisterMetaType<Qt::GestureState>(); + +#if defined(Q_WS_MAC) + registerGestureRecognizer(new QMacSwipeGestureRecognizer); + registerGestureRecognizer(new QMacPinchGestureRecognizer); + #if defined(QT_MAC_USE_COCOA) + registerGestureRecognizer(new QMacPanGestureRecognizer); + #endif +#else + registerGestureRecognizer(new QPanGestureRecognizer); + registerGestureRecognizer(new QPinchGestureRecognizer); + registerGestureRecognizer(new QSwipeGestureRecognizer); + registerGestureRecognizer(new QTapGestureRecognizer); +#endif +#if defined(Q_OS_WIN) + #if !defined(QT_NO_NATIVE_GESTURES) + if (QApplicationPrivate::HasTouchSupport) + registerGestureRecognizer(new QWinNativePanGestureRecognizer); + #endif +#else + registerGestureRecognizer(new QTapAndHoldGestureRecognizer); +#endif +} + +QGestureManager::~QGestureManager() +{ + qDeleteAll(m_recognizers.values()); + foreach (QGestureRecognizer *recognizer, m_obsoleteGestures.keys()) { + qDeleteAll(m_obsoleteGestures.value(recognizer)); + delete recognizer; + } + m_obsoleteGestures.clear(); +} + +Qt::GestureType QGestureManager::registerGestureRecognizer(QGestureRecognizer *recognizer) +{ + QGesture *dummy = recognizer->create(0); + if (!dummy) { + qWarning("QGestureManager::registerGestureRecognizer: " + "the recognizer fails to create a gesture object, skipping registration."); + return Qt::GestureType(0); + } + Qt::GestureType type = dummy->gestureType(); + if (type == Qt::CustomGesture) { + // generate a new custom gesture id + ++m_lastCustomGestureId; + type = Qt::GestureType(m_lastCustomGestureId); + } + m_recognizers.insertMulti(type, recognizer); + delete dummy; + return type; +} + +void QGestureManager::unregisterGestureRecognizer(Qt::GestureType type) +{ + QList<QGestureRecognizer *> list = m_recognizers.values(type); + while (QGestureRecognizer *recognizer = m_recognizers.take(type)) { + if (!m_obsoleteGestures.contains(recognizer)) { + // inserting even an empty QSet will cause the recognizer to be deleted on destruction of the manager + m_obsoleteGestures.insert(recognizer, QSet<QGesture *>()); + } + } + foreach (QGesture *g, m_gestureToRecognizer.keys()) { + QGestureRecognizer *recognizer = m_gestureToRecognizer.value(g); + if (list.contains(recognizer)) { + m_deletedRecognizers.insert(g, recognizer); + } + } + + QMap<ObjectGesture, QList<QGesture *> >::const_iterator iter = m_objectGestures.begin(); + while (iter != m_objectGestures.end()) { + ObjectGesture objectGesture = iter.key(); + if (objectGesture.gesture == type) { + foreach (QGesture *g, iter.value()) { + if (QGestureRecognizer *recognizer = m_gestureToRecognizer.value(g)) { + m_gestureToRecognizer.remove(g); + m_obsoleteGestures[recognizer].insert(g); + } + } + } + ++iter; + } +} + +void QGestureManager::cleanupCachedGestures(QObject *target, Qt::GestureType type) +{ + QMap<ObjectGesture, QList<QGesture *> >::Iterator iter = m_objectGestures.begin(); + while (iter != m_objectGestures.end()) { + ObjectGesture objectGesture = iter.key(); + if (objectGesture.gesture == type && target == objectGesture.object) { + QSet<QGesture *> gestures = iter.value().toSet(); + for (QHash<QGestureRecognizer *, QSet<QGesture *> >::iterator + it = m_obsoleteGestures.begin(), e = m_obsoleteGestures.end(); it != e; ++it) { + it.value() -= gestures; + } + foreach (QGesture *g, gestures) { + m_deletedRecognizers.remove(g); + m_gestureToRecognizer.remove(g); + m_maybeGestures.remove(g); + m_activeGestures.remove(g); + m_gestureOwners.remove(g); + m_gestureTargets.remove(g); + m_gesturesToDelete.insert(g); + } + + iter = m_objectGestures.erase(iter); + } else { + ++iter; + } + } +} + +// get or create a QGesture object that will represent the state for a given object, used by the recognizer +QGesture *QGestureManager::getState(QObject *object, QGestureRecognizer *recognizer, Qt::GestureType type) +{ + // if the widget is being deleted we should be careful not to + // create a new state, as it will create QWeakPointer which doesn't work + // from the destructor. + if (object->isWidgetType()) { + if (static_cast<QWidget *>(object)->d_func()->data.in_destructor) + return 0; + } else if (QGesture *g = qobject_cast<QGesture *>(object)) { + return g; +#ifndef QT_NO_GRAPHICSVIEW + } else { + Q_ASSERT(qobject_cast<QGraphicsObject *>(object)); + QGraphicsObject *graphicsObject = static_cast<QGraphicsObject *>(object); + if (graphicsObject->QGraphicsItem::d_func()->inDestructor) + return 0; +#endif + } + + // check if the QGesture for this recognizer has already been created + foreach (QGesture *state, m_objectGestures.value(QGestureManager::ObjectGesture(object, type))) { + if (m_gestureToRecognizer.value(state) == recognizer) + return state; + } + + Q_ASSERT(recognizer); + QGesture *state = recognizer->create(object); + if (!state) + return 0; + state->setParent(this); + if (state->gestureType() == Qt::CustomGesture) { + // if the recognizer didn't fill in the gesture type, then this + // is a custom gesture with autogenerated id and we fill it. + state->d_func()->gestureType = type; +#if defined(GESTURE_DEBUG) + state->setObjectName(QString::number((int)type)); +#endif + } + m_objectGestures[QGestureManager::ObjectGesture(object, type)].append(state); + m_gestureToRecognizer[state] = recognizer; + m_gestureOwners[state] = object; + + return state; +} + +bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *, + Qt::GestureType> &contexts, + QEvent *event) +{ + QSet<QGesture *> triggeredGestures; + QSet<QGesture *> finishedGestures; + QSet<QGesture *> newMaybeGestures; + QSet<QGesture *> notGestures; + + // TODO: sort contexts by the gesture type and check if one of the contexts + // is already active. + + bool consumeEventHint = false; + + // filter the event through recognizers + typedef QMultiMap<QObject *, Qt::GestureType>::const_iterator ContextIterator; + ContextIterator contextEnd = contexts.end(); + for (ContextIterator context = contexts.begin(); context != contextEnd; ++context) { + Qt::GestureType gestureType = context.value(); + QMap<Qt::GestureType, QGestureRecognizer *>::const_iterator + typeToRecognizerIterator = m_recognizers.lowerBound(gestureType), + typeToRecognizerEnd = m_recognizers.upperBound(gestureType); + for (; typeToRecognizerIterator != typeToRecognizerEnd; ++typeToRecognizerIterator) { + QGestureRecognizer *recognizer = typeToRecognizerIterator.value(); + QObject *target = context.key(); + QGesture *state = getState(target, recognizer, gestureType); + if (!state) + continue; + QGestureRecognizer::Result recognizerResult = recognizer->recognize(state, target, event); + QGestureRecognizer::Result recognizerState = recognizerResult & QGestureRecognizer::ResultState_Mask; + QGestureRecognizer::Result resultHint = recognizerResult & QGestureRecognizer::ResultHint_Mask; + if (recognizerState == QGestureRecognizer::TriggerGesture) { + DEBUG() << "QGestureManager:Recognizer: gesture triggered: " << state; + triggeredGestures << state; + } else if (recognizerState == QGestureRecognizer::FinishGesture) { + DEBUG() << "QGestureManager:Recognizer: gesture finished: " << state; + finishedGestures << state; + } else if (recognizerState == QGestureRecognizer::MayBeGesture) { + DEBUG() << "QGestureManager:Recognizer: maybe gesture: " << state; + newMaybeGestures << state; + } else if (recognizerState == QGestureRecognizer::CancelGesture) { + DEBUG() << "QGestureManager:Recognizer: not gesture: " << state; + notGestures << state; + } else if (recognizerState == QGestureRecognizer::Ignore) { + DEBUG() << "QGestureManager:Recognizer: ignored the event: " << state; + } else { + DEBUG() << "QGestureManager:Recognizer: hm, lets assume the recognizer" + << "ignored the event: " << state; + } + if (resultHint & QGestureRecognizer::ConsumeEventHint) { + DEBUG() << "QGestureManager: we were asked to consume the event: " + << state; + consumeEventHint = true; + } + } + } + if (triggeredGestures.isEmpty() && finishedGestures.isEmpty() + && newMaybeGestures.isEmpty() && notGestures.isEmpty()) + return consumeEventHint; + + QSet<QGesture *> startedGestures = triggeredGestures - m_activeGestures; + triggeredGestures &= m_activeGestures; + + // check if a running gesture switched back to maybe state + QSet<QGesture *> activeToMaybeGestures = m_activeGestures & newMaybeGestures; + + // check if a maybe gesture switched to canceled - reset it but don't send an event + QSet<QGesture *> maybeToCanceledGestures = m_maybeGestures & notGestures; + + // check if a running gesture switched back to not gesture state, + // i.e. were canceled + QSet<QGesture *> canceledGestures = m_activeGestures & notGestures; + + // new gestures in maybe state + m_maybeGestures += newMaybeGestures; + + // gestures that were in maybe state + QSet<QGesture *> notMaybeGestures = (startedGestures | triggeredGestures + | finishedGestures | canceledGestures + | notGestures); + m_maybeGestures -= notMaybeGestures; + + Q_ASSERT((startedGestures & finishedGestures).isEmpty()); + Q_ASSERT((startedGestures & newMaybeGestures).isEmpty()); + Q_ASSERT((startedGestures & canceledGestures).isEmpty()); + Q_ASSERT((finishedGestures & newMaybeGestures).isEmpty()); + Q_ASSERT((finishedGestures & canceledGestures).isEmpty()); + Q_ASSERT((canceledGestures & newMaybeGestures).isEmpty()); + + QSet<QGesture *> notStarted = finishedGestures - m_activeGestures; + if (!notStarted.isEmpty()) { + // there are some gestures that claim to be finished, but never started. + // probably those are "singleshot" gestures so we'll fake the started state. + foreach (QGesture *gesture, notStarted) + gesture->d_func()->state = Qt::GestureStarted; + QSet<QGesture *> undeliveredGestures; + deliverEvents(notStarted, &undeliveredGestures); + finishedGestures -= undeliveredGestures; + } + + m_activeGestures += startedGestures; + // sanity check: all triggered gestures should already be in active gestures list + Q_ASSERT((m_activeGestures & triggeredGestures).size() == triggeredGestures.size()); + m_activeGestures -= finishedGestures; + m_activeGestures -= activeToMaybeGestures; + m_activeGestures -= canceledGestures; + + // set the proper gesture state on each gesture + foreach (QGesture *gesture, startedGestures) + gesture->d_func()->state = Qt::GestureStarted; + foreach (QGesture *gesture, triggeredGestures) + gesture->d_func()->state = Qt::GestureUpdated; + foreach (QGesture *gesture, finishedGestures) + gesture->d_func()->state = Qt::GestureFinished; + foreach (QGesture *gesture, canceledGestures) + gesture->d_func()->state = Qt::GestureCanceled; + foreach (QGesture *gesture, activeToMaybeGestures) + gesture->d_func()->state = Qt::GestureFinished; + + if (!m_activeGestures.isEmpty() || !m_maybeGestures.isEmpty() || + !startedGestures.isEmpty() || !triggeredGestures.isEmpty() || + !finishedGestures.isEmpty() || !canceledGestures.isEmpty()) { + DEBUG() << "QGestureManager::filterEventThroughContexts:" + << "\n\tactiveGestures:" << m_activeGestures + << "\n\tmaybeGestures:" << m_maybeGestures + << "\n\tstarted:" << startedGestures + << "\n\ttriggered:" << triggeredGestures + << "\n\tfinished:" << finishedGestures + << "\n\tcanceled:" << canceledGestures + << "\n\tmaybe-canceled:" << maybeToCanceledGestures; + } + + QSet<QGesture *> undeliveredGestures; + deliverEvents(startedGestures+triggeredGestures+finishedGestures+canceledGestures, + &undeliveredGestures); + + foreach (QGesture *g, startedGestures) { + if (undeliveredGestures.contains(g)) + continue; + if (g->gestureCancelPolicy() == QGesture::CancelAllInContext) { + DEBUG() << "lets try to cancel some"; + // find gestures in context in Qt::GestureStarted or Qt::GestureUpdated state and cancel them + cancelGesturesForChildren(g); + } + } + + m_activeGestures -= undeliveredGestures; + + // reset gestures that ended + QSet<QGesture *> endedGestures = + finishedGestures + canceledGestures + undeliveredGestures + maybeToCanceledGestures; + foreach (QGesture *gesture, endedGestures) { + recycle(gesture); + m_gestureTargets.remove(gesture); + } + + //Clean up the Gestures + qDeleteAll(m_gesturesToDelete); + m_gesturesToDelete.clear(); + + return consumeEventHint; +} + +// Cancel all gestures of children of the widget that original is associated with +void QGestureManager::cancelGesturesForChildren(QGesture *original) +{ + Q_ASSERT(original); + QWidget *originatingWidget = m_gestureTargets.value(original); + Q_ASSERT(originatingWidget); + + // iterate over all active gestures and all maybe gestures + // for each find the owner + // if the owner is part of our sub-hierarchy, cancel it. + + QSet<QGesture*> cancelledGestures; + QSet<QGesture*>::Iterator iter = m_activeGestures.begin(); + while (iter != m_activeGestures.end()) { + QWidget *widget = m_gestureTargets.value(*iter); + // note that we don't touch the gestures for our originatingWidget + if (widget != originatingWidget && originatingWidget->isAncestorOf(widget)) { + DEBUG() << " found a gesture to cancel" << (*iter); + (*iter)->d_func()->state = Qt::GestureCanceled; + cancelledGestures << *iter; + iter = m_activeGestures.erase(iter); + } else { + ++iter; + } + } + + // TODO handle 'maybe' gestures too + + // sort them per target widget by cherry picking from almostCanceledGestures and delivering + QSet<QGesture *> almostCanceledGestures = cancelledGestures; + while (!almostCanceledGestures.isEmpty()) { + QWidget *target = 0; + QSet<QGesture*> gestures; + iter = almostCanceledGestures.begin(); + // sort per target widget + while (iter != almostCanceledGestures.end()) { + QWidget *widget = m_gestureTargets.value(*iter); + if (target == 0) + target = widget; + if (target == widget) { + gestures << *iter; + iter = almostCanceledGestures.erase(iter); + } else { + ++iter; + } + } + Q_ASSERT(target); + + QSet<QGesture*> undeliveredGestures; + deliverEvents(gestures, &undeliveredGestures); + } + + for (iter = cancelledGestures.begin(); iter != cancelledGestures.end(); ++iter) + recycle(*iter); +} + +void QGestureManager::cleanupGesturesForRemovedRecognizer(QGesture *gesture) +{ + QGestureRecognizer *recognizer = m_deletedRecognizers.value(gesture); + if(!recognizer) //The Gesture is removed while in the even loop, so the recognizers for this gestures was removed + return; + m_deletedRecognizers.remove(gesture); + if (m_deletedRecognizers.keys(recognizer).isEmpty()) { + // no more active gestures, cleanup! + qDeleteAll(m_obsoleteGestures.value(recognizer)); + m_obsoleteGestures.remove(recognizer); + delete recognizer; + } +} + +// return true if accepted (consumed) +bool QGestureManager::filterEvent(QWidget *receiver, QEvent *event) +{ + QMap<Qt::GestureType, int> types; + QMultiMap<QObject *, Qt::GestureType> contexts; + QWidget *w = receiver; + typedef QMap<Qt::GestureType, Qt::GestureFlags>::const_iterator ContextIterator; + if (!w->d_func()->gestureContext.isEmpty()) { + for(ContextIterator it = w->d_func()->gestureContext.begin(), + e = w->d_func()->gestureContext.end(); it != e; ++it) { + types.insert(it.key(), 0); + contexts.insertMulti(w, it.key()); + } + } + // find all gesture contexts for the widget tree + w = w->isWindow() ? 0 : w->parentWidget(); + while (w) + { + for (ContextIterator it = w->d_func()->gestureContext.begin(), + e = w->d_func()->gestureContext.end(); it != e; ++it) { + if (!(it.value() & Qt::DontStartGestureOnChildren)) { + if (!types.contains(it.key())) { + types.insert(it.key(), 0); + contexts.insertMulti(w, it.key()); + } + } + } + if (w->isWindow()) + break; + w = w->parentWidget(); + } + return contexts.isEmpty() ? false : filterEventThroughContexts(contexts, event); +} + +#ifndef QT_NO_GRAPHICSVIEW +bool QGestureManager::filterEvent(QGraphicsObject *receiver, QEvent *event) +{ + QMap<Qt::GestureType, int> types; + QMultiMap<QObject *, Qt::GestureType> contexts; + QGraphicsObject *item = receiver; + if (!item->QGraphicsItem::d_func()->gestureContext.isEmpty()) { + typedef QMap<Qt::GestureType, Qt::GestureFlags>::const_iterator ContextIterator; + for(ContextIterator it = item->QGraphicsItem::d_func()->gestureContext.begin(), + e = item->QGraphicsItem::d_func()->gestureContext.end(); it != e; ++it) { + types.insert(it.key(), 0); + contexts.insertMulti(item, it.key()); + } + } + // find all gesture contexts for the graphics object tree + item = item->parentObject(); + while (item) + { + typedef QMap<Qt::GestureType, Qt::GestureFlags>::const_iterator ContextIterator; + for (ContextIterator it = item->QGraphicsItem::d_func()->gestureContext.begin(), + e = item->QGraphicsItem::d_func()->gestureContext.end(); it != e; ++it) { + if (!(it.value() & Qt::DontStartGestureOnChildren)) { + if (!types.contains(it.key())) { + types.insert(it.key(), 0); + contexts.insertMulti(item, it.key()); + } + } + } + item = item->parentObject(); + } + return contexts.isEmpty() ? false : filterEventThroughContexts(contexts, event); +} +#endif + +bool QGestureManager::filterEvent(QObject *receiver, QEvent *event) +{ + if (!m_gestureToRecognizer.contains(static_cast<QGesture *>(receiver))) + return false; + QGesture *state = static_cast<QGesture *>(receiver); + QMultiMap<QObject *, Qt::GestureType> contexts; + contexts.insert(state, state->gestureType()); + return filterEventThroughContexts(contexts, event); +} + +void QGestureManager::getGestureTargets(const QSet<QGesture*> &gestures, + QMap<QWidget *, QList<QGesture *> > *conflicts, + QMap<QWidget *, QList<QGesture *> > *normal) +{ + typedef QHash<Qt::GestureType, QHash<QWidget *, QGesture *> > GestureByTypes; + GestureByTypes gestureByTypes; + + // sort gestures by types + foreach (QGesture *gesture, gestures) { + QWidget *receiver = m_gestureTargets.value(gesture, 0); + Q_ASSERT(receiver); + gestureByTypes[gesture->gestureType()].insert(receiver, gesture); + } + + // for each gesture type + foreach (Qt::GestureType type, gestureByTypes.keys()) { + QHash<QWidget *, QGesture *> gestures = gestureByTypes.value(type); + foreach (QWidget *widget, gestures.keys()) { + QWidget *w = widget->parentWidget(); + while (w) { + QMap<Qt::GestureType, Qt::GestureFlags>::const_iterator it + = w->d_func()->gestureContext.find(type); + if (it != w->d_func()->gestureContext.end()) { + // i.e. 'w' listens to gesture 'type' + Qt::GestureFlags flags = it.value(); + if (!(it.value() & Qt::DontStartGestureOnChildren) && w != widget) { + // conflicting gesture! + (*conflicts)[widget].append(gestures[widget]); + break; + } + } + if (w->isWindow()) { + w = 0; + break; + } + w = w->parentWidget(); + } + if (!w) + (*normal)[widget].append(gestures[widget]); + } + } +} + +void QGestureManager::deliverEvents(const QSet<QGesture *> &gestures, + QSet<QGesture *> *undeliveredGestures) +{ + if (gestures.isEmpty()) + return; + + typedef QMap<QWidget *, QList<QGesture *> > GesturesPerWidget; + GesturesPerWidget conflictedGestures; + GesturesPerWidget normalStartedGestures; + + QSet<QGesture *> startedGestures; + // first figure out the initial receivers of gestures + for (QSet<QGesture *>::const_iterator it = gestures.begin(), + e = gestures.end(); it != e; ++it) { + QGesture *gesture = *it; + QWidget *target = m_gestureTargets.value(gesture, 0); + if (!target) { + // the gesture has just started and doesn't have a target yet. + Q_ASSERT(gesture->state() == Qt::GestureStarted); + if (gesture->hasHotSpot()) { + // guess the target widget using the hotspot of the gesture + QPoint pt = gesture->hotSpot().toPoint(); + if (QWidget *topLevel = qApp->topLevelAt(pt)) { + QWidget *child = topLevel->childAt(topLevel->mapFromGlobal(pt)); + target = child ? child : topLevel; + } + } else { + // or use the context of the gesture + QObject *context = m_gestureOwners.value(gesture, 0); + if (context->isWidgetType()) + target = static_cast<QWidget *>(context); + } + if (target) + m_gestureTargets.insert(gesture, target); + } + + Qt::GestureType gestureType = gesture->gestureType(); + Q_ASSERT(gestureType != Qt::CustomGesture); + Q_UNUSED(gestureType); + + if (target) { + if (gesture->state() == Qt::GestureStarted) { + startedGestures.insert(gesture); + } else { + normalStartedGestures[target].append(gesture); + } + } else { + DEBUG() << "QGestureManager::deliverEvent: could not find the target for gesture" + << gesture->gestureType(); + qWarning("QGestureManager::deliverEvent: could not find the target for gesture"); + undeliveredGestures->insert(gesture); + } + } + + getGestureTargets(startedGestures, &conflictedGestures, &normalStartedGestures); + DEBUG() << "QGestureManager::deliverEvents:" + << "\nstarted: " << startedGestures + << "\nconflicted: " << conflictedGestures + << "\nnormal: " << normalStartedGestures + << "\n"; + + // if there are conflicting gestures, send the GestureOverride event + for (GesturesPerWidget::const_iterator it = conflictedGestures.begin(), + e = conflictedGestures.end(); it != e; ++it) { + QWidget *receiver = it.key(); + QList<QGesture *> gestures = it.value(); + DEBUG() << "QGestureManager::deliverEvents: sending GestureOverride to" + << receiver + << "gestures:" << gestures; + QGestureEvent event(gestures); + event.t = QEvent::GestureOverride; + // mark event and individual gestures as ignored + event.ignore(); + foreach(QGesture *g, gestures) + event.setAccepted(g, false); + + QApplication::sendEvent(receiver, &event); + bool eventAccepted = event.isAccepted(); + foreach(QGesture *gesture, event.gestures()) { + if (eventAccepted || event.isAccepted(gesture)) { + QWidget *w = event.d_func()->targetWidgets.value(gesture->gestureType(), 0); + Q_ASSERT(w); + DEBUG() << "override event: gesture was accepted:" << gesture << w; + QList<QGesture *> &gestures = normalStartedGestures[w]; + gestures.append(gesture); + // override the target + m_gestureTargets[gesture] = w; + } else { + DEBUG() << "override event: gesture wasn't accepted. putting back:" << gesture; + QList<QGesture *> &gestures = normalStartedGestures[receiver]; + gestures.append(gesture); + } + } + } + + // delivering gestures that are not in conflicted state + for (GesturesPerWidget::const_iterator it = normalStartedGestures.begin(), + e = normalStartedGestures.end(); it != e; ++it) { + if (!it.value().isEmpty()) { + DEBUG() << "QGestureManager::deliverEvents: sending to" << it.key() + << "gestures:" << it.value(); + QGestureEvent event(it.value()); + QApplication::sendEvent(it.key(), &event); + bool eventAccepted = event.isAccepted(); + foreach (QGesture *gesture, event.gestures()) { + if (gesture->state() == Qt::GestureStarted && + (eventAccepted || event.isAccepted(gesture))) { + QWidget *w = event.d_func()->targetWidgets.value(gesture->gestureType(), 0); + Q_ASSERT(w); + DEBUG() << "started gesture was delivered and accepted by" << w; + m_gestureTargets[gesture] = w; + } + } + } + } +} + +void QGestureManager::recycle(QGesture *gesture) +{ + QGestureRecognizer *recognizer = m_gestureToRecognizer.value(gesture, 0); + if (recognizer) { + gesture->setGestureCancelPolicy(QGesture::CancelNone); + recognizer->reset(gesture); + m_activeGestures.remove(gesture); + } else { + cleanupGesturesForRemovedRecognizer(gesture); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#include "moc_qgesturemanager_p.cpp" diff --git a/src/gui/kernel/qgesturemanager_p.h b/src/gui/kernel/qgesturemanager_p.h new file mode 100644 index 0000000000..b4d9f61ec0 --- /dev/null +++ b/src/gui/kernel/qgesturemanager_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGESTUREMANAGER_P_H +#define QGESTUREMANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qobject.h" +#include "qbasictimer.h" +#include "private/qwidget_p.h" +#include "qgesturerecognizer.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QBasicTimer; +class QGraphicsObject; +class QGestureManager : public QObject +{ + Q_OBJECT +public: + QGestureManager(QObject *parent); + ~QGestureManager(); + + Qt::GestureType registerGestureRecognizer(QGestureRecognizer *recognizer); + void unregisterGestureRecognizer(Qt::GestureType type); + + bool filterEvent(QWidget *receiver, QEvent *event); + bool filterEvent(QObject *receiver, QEvent *event); +#ifndef QT_NO_GRAPHICSVIEW + bool filterEvent(QGraphicsObject *receiver, QEvent *event); +#endif //QT_NO_GRAPHICSVIEW + + static QGestureManager* instance(); // declared in qapplication.cpp + + void cleanupCachedGestures(QObject *target, Qt::GestureType type); + + void recycle(QGesture *gesture); + +protected: + bool filterEventThroughContexts(const QMultiMap<QObject *, Qt::GestureType> &contexts, + QEvent *event); + +private: + QMultiMap<Qt::GestureType, QGestureRecognizer *> m_recognizers; + + QSet<QGesture *> m_activeGestures; + QSet<QGesture *> m_maybeGestures; + + enum State { + Gesture, + NotGesture, + MaybeGesture // this means timers are up and waiting for some + // more events, and input events are handled by + // gesture recognizer explicitly + } state; + + struct ObjectGesture + { + QObject* object; + Qt::GestureType gesture; + + ObjectGesture(QObject *o, const Qt::GestureType &g) : object(o), gesture(g) { } + inline bool operator<(const ObjectGesture &rhs) const + { + if (object < rhs.object) + return true; + if (object == rhs.object) + return gesture < rhs.gesture; + return false; + } + }; + + QMap<ObjectGesture, QList<QGesture *> > m_objectGestures; + QHash<QGesture *, QGestureRecognizer *> m_gestureToRecognizer; + QHash<QGesture *, QObject *> m_gestureOwners; + + QHash<QGesture *, QWidget *> m_gestureTargets; + + int m_lastCustomGestureId; + + QHash<QGestureRecognizer *, QSet<QGesture *> > m_obsoleteGestures; + QHash<QGesture *, QGestureRecognizer *> m_deletedRecognizers; + QSet<QGesture *> m_gesturesToDelete; + void cleanupGesturesForRemovedRecognizer(QGesture *gesture); + + QGesture *getState(QObject *widget, QGestureRecognizer *recognizer, + Qt::GestureType gesture); + void deliverEvents(const QSet<QGesture *> &gestures, + QSet<QGesture *> *undeliveredGestures); + void getGestureTargets(const QSet<QGesture*> &gestures, + QMap<QWidget *, QList<QGesture *> > *conflicts, + QMap<QWidget *, QList<QGesture *> > *normal); + + void cancelGesturesForChildren(QGesture *originatingGesture); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QGESTUREMANAGER_P_H diff --git a/src/gui/kernel/qgesturerecognizer.cpp b/src/gui/kernel/qgesturerecognizer.cpp new file mode 100644 index 0000000000..f7a4a189b9 --- /dev/null +++ b/src/gui/kernel/qgesturerecognizer.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgesturerecognizer.h" + +#include "private/qgesture_p.h" +#include "private/qgesturemanager_p.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +/*! + \class QGestureRecognizer + \since 4.6 + \brief The QGestureRecognizer class provides the infrastructure for gesture recognition. + \ingroup gestures + + Gesture recognizers are responsible for creating and managing QGesture objects and + monitoring input events sent to QWidget and QGraphicsObject subclasses. + QGestureRecognizer is the base class for implementing custom gestures. + + Developers that only need to provide gesture recognition for standard gestures do not + need to use this class directly. Instances will be created behind the scenes by the + framework. + + For an overview of gesture handling in Qt and information on using gestures + in your applications, see the \l{Gestures Programming} document. + + \section1 Recognizing Gestures + + The process of recognizing gestures involves filtering input events sent to specific + objects, and modifying the associated QGesture objects to include relevant information + about the user's input. + + Gestures are created when the framework calls create() to handle user input + for a particular instance of a QWidget or QGraphicsObject subclass. A QGesture object + is created for each widget or item that is configured to use gestures. + + Once a QGesture has been created for a target object, the gesture recognizer will + receive events for it in its recognize() handler function. + + When a gesture is canceled, the reset() function is called, giving the recognizer the + chance to update the appropriate properties in the corresponding QGesture object. + + \section1 Supporting New Gestures + + To add support for new gestures, you need to derive from QGestureRecognizer to create + a custom recognizer class, construct an instance of this class, and register it with + the application by calling QGestureRecognizer::registerRecognizer(). You can also + subclass QGesture to create a custom gesture class, or rely on dynamic properties + to express specific details of the gesture you want to handle. + + Your custom QGestureRecognizer subclass needs to reimplement the recognize() + function to handle and filter the incoming input events for QWidget and + QGraphicsObject subclasses. Although the logic for gesture recognition is + implemented in this function, you can store persistent information about the + state of the recognition process in the QGesture object supplied. The + recognize() function must return a value of QGestureRecognizer::Result that + indicates the state of recognition for a given gesture and target object. + This determines whether or not a gesture event will be delivered to a target + object. + + If you choose to represent a gesture by a custom QGesture subclass, you will need to + reimplement the create() function to construct instances of your gesture class. + Similarly, you may need to reimplement the reset() function if your custom gesture + objects need to be specially handled when a gesture is canceled. + + \sa QGesture +*/ + +/*! + \enum QGestureRecognizer::ResultFlag + + This enum describes the result of the current event filtering step in + a gesture recognizer state machine. + + The result consists of a state value (one of Ignore, MayBeGesture, + TriggerGesture, FinishGesture, CancelGesture) and an optional hint + (ConsumeEventHint). + + \value Ignore The event does not change the state of the recognizer. + + \value MayBeGesture The event changed the internal state of the recognizer, + but it isn't clear yet if it is a gesture or not. The recognizer needs to + filter more events to decide. Gesture recognizers in the MayBeGesture state + may be reset automatically if they take too long to recognize gestures. + + \value TriggerGesture The gesture has been triggered and the appropriate + QGesture object will be delivered to the target as a part of a + QGestureEvent. + + \value FinishGesture The gesture has been finished successfully and the + appropriate QGesture object will be delivered to the target as a part of a + QGestureEvent. + + \value CancelGesture The event made it clear that it is not a gesture. If + the gesture recognizer was in GestureTriggered state before, then the + gesture is canceled and the appropriate QGesture object will be delivered + to the target as a part of a QGestureEvent. + + \value ConsumeEventHint This hint specifies that the gesture framework + should consume the filtered event and not deliver it to the receiver. + + \omitvalue ResultState_Mask + \omitvalue ResultHint_Mask + + \sa QGestureRecognizer::recognize() +*/ + +/*! + Constructs a new gesture recognizer object. +*/ +QGestureRecognizer::QGestureRecognizer() +{ +} + +/*! + Destroys the gesture recognizer. +*/ +QGestureRecognizer::~QGestureRecognizer() +{ +} + +/*! + This function is called by Qt to create a new QGesture object for the + given \a target (QWidget or QGraphicsObject). + + Reimplement this function to create a custom QGesture-derived gesture + object if necessary. + + The application takes ownership of the created gesture object. +*/ +QGesture *QGestureRecognizer::create(QObject *target) +{ + Q_UNUSED(target); + return new QGesture; +} + +/*! + This function is called by the framework to reset a given \a gesture. + + Reimplement this function to implement additional requirements for custom QGesture + objects. This may be necessary if you implement a custom QGesture whose properties + need special handling when the gesture is reset. +*/ +void QGestureRecognizer::reset(QGesture *gesture) +{ + if (gesture) { + QGesturePrivate *d = gesture->d_func(); + d->state = Qt::NoGesture; + d->hotSpot = QPointF(); + d->sceneHotSpot = QPointF(); + d->isHotSpotSet = false; + } +} + +/*! + \fn QGestureRecognizer::recognize(QGesture *gesture, QObject *watched, QEvent *event) + + Handles the given \a event for the \a watched object, updating the state of the \a gesture + object as required, and returns a suitable result for the current recognition step. + + This function is called by the framework to allow the recognizer to filter input events + dispatched to QWidget or QGraphicsObject instances that it is monitoring. + + The result reflects how much of the gesture has been recognized. The state of the + \a gesture object is set depending on the result. + + \sa QGestureRecognizer::Result +*/ + +/*! + Registers the given \a recognizer in the gesture framework and returns a gesture ID + for it. + + The application takes ownership of the \a recognizer and returns the gesture type + ID associated with it. For gesture recognizers which handle custom QGesture + objects (i.e., those which return Qt::CustomGesture in a QGesture::gestureType() + function) the return value is a generated gesture ID with the Qt::CustomGesture + flag set. + + \sa unregisterRecognizer(), QGestureRecognizer::create(), QGesture +*/ +Qt::GestureType QGestureRecognizer::registerRecognizer(QGestureRecognizer *recognizer) +{ + return QGestureManager::instance()->registerGestureRecognizer(recognizer); +} + +/*! + Unregisters all gesture recognizers of the specified \a type. + + \sa registerRecognizer() +*/ +void QGestureRecognizer::unregisterRecognizer(Qt::GestureType type) +{ + QGestureManager::instance()->unregisterGestureRecognizer(type); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/kernel/qgesturerecognizer.h b/src/gui/kernel/qgesturerecognizer.h new file mode 100644 index 0000000000..80d978d6bb --- /dev/null +++ b/src/gui/kernel/qgesturerecognizer.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGESTURERECOGNIZER_H +#define QGESTURERECOGNIZER_H + +#include <QtCore/qglobal.h> +#include <QtCore/qnamespace.h> + +#ifndef QT_NO_GESTURES + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QObject; +class QEvent; +class QGesture; +class Q_GUI_EXPORT QGestureRecognizer +{ +public: + enum ResultFlag + { + Ignore = 0x0001, + + MayBeGesture = 0x0002, + TriggerGesture = 0x0004, + FinishGesture = 0x0008, + CancelGesture = 0x0010, + + ResultState_Mask = 0x00ff, + + ConsumeEventHint = 0x0100, + // StoreEventHint = 0x0200, + // ReplayStoredEventsHint = 0x0400, + // DiscardStoredEventsHint = 0x0800, + + ResultHint_Mask = 0xff00 + }; + Q_DECLARE_FLAGS(Result, ResultFlag) + + QGestureRecognizer(); + virtual ~QGestureRecognizer(); + + virtual QGesture *create(QObject *target); + virtual Result recognize(QGesture *state, QObject *watched, + QEvent *event) = 0; + virtual void reset(QGesture *state); + + static Qt::GestureType registerRecognizer(QGestureRecognizer *recognizer); + static void unregisterRecognizer(Qt::GestureType type); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGestureRecognizer::Result) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_GESTURES + +#endif // QGESTURERECOGNIZER_H diff --git a/src/gui/kernel/qgridlayout.cpp b/src/gui/kernel/qgridlayout.cpp new file mode 100644 index 0000000000..19d101a0b0 --- /dev/null +++ b/src/gui/kernel/qgridlayout.cpp @@ -0,0 +1,1889 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgridlayout.h" +#include "qapplication.h" +#include "qwidget.h" +#include "qlist.h" +#include "qsizepolicy.h" +#include "qvector.h" +#include "qvarlengtharray.h" +#include "qlayoutengine_p.h" +#include "qlayout_p.h" + +QT_BEGIN_NAMESPACE + +struct QGridLayoutSizeTriple +{ + QSize minS; + QSize hint; + QSize maxS; +}; + +/* + Three internal classes related to QGridLayout: (1) QGridBox is a + QLayoutItem with (row, column) information and (torow, tocolumn) information; (3) QGridLayoutData is + the internal representation of a QGridLayout. +*/ + +class QGridBox +{ +public: + QGridBox(QLayoutItem *lit) { item_ = lit; } + + QGridBox(const QLayout *l, QWidget *wid) { item_ = QLayoutPrivate::createWidgetItem(l, wid); } + ~QGridBox() { delete item_; } + + QSize sizeHint() const { return item_->sizeHint(); } + QSize minimumSize() const { return item_->minimumSize(); } + QSize maximumSize() const { return item_->maximumSize(); } + Qt::Orientations expandingDirections() const { return item_->expandingDirections(); } + bool isEmpty() const { return item_->isEmpty(); } + + bool hasHeightForWidth() const { return item_->hasHeightForWidth(); } + int heightForWidth(int w) const { return item_->heightForWidth(w); } + + void setAlignment(Qt::Alignment a) { item_->setAlignment(a); } + void setGeometry(const QRect &r) { item_->setGeometry(r); } + Qt::Alignment alignment() const { return item_->alignment(); } + QLayoutItem *item() { return item_; } + QLayoutItem *takeItem() { QLayoutItem *i = item_; item_ = 0; return i; } + + int hStretch() { return item_->widget() ? + item_->widget()->sizePolicy().horizontalStretch() : 0; } + int vStretch() { return item_->widget() ? + item_->widget()->sizePolicy().verticalStretch() : 0; } + +private: + friend class QGridLayoutPrivate; + friend class QGridLayout; + + inline int toRow(int rr) const { return torow >= 0 ? torow : rr - 1; } + inline int toCol(int cc) const { return tocol >= 0 ? tocol : cc - 1; } + + QLayoutItem *item_; + int row, col; + int torow, tocol; +}; + +class QGridLayoutPrivate : public QLayoutPrivate +{ + Q_DECLARE_PUBLIC(QGridLayout) +public: + QGridLayoutPrivate(); + + void add(QGridBox*, int row, int col); + void add(QGridBox*, int row1, int row2, int col1, int col2); + QSize sizeHint(int hSpacing, int vSpacing) const; + QSize minimumSize(int hSpacing, int vSpacing) const; + QSize maximumSize(int hSpacing, int vSpacing) const; + + Qt::Orientations expandingDirections(int hSpacing, int vSpacing) const; + + void distribute(QRect rect, int hSpacing, int vSpacing); + inline int numRows() const { return rr; } + inline int numCols() const { return cc; } + inline void expand(int rows, int cols) + { setSize(qMax(rows, rr), qMax(cols, cc)); } + inline void setRowStretch(int r, int s) + { expand(r + 1, 0); rStretch[r] = s; setDirty(); } + inline void setColStretch(int c, int s) + { expand(0, c + 1); cStretch[c] = s; setDirty(); } + inline int rowStretch(int r) const { return rStretch.at(r); } + inline int colStretch(int c) const { return cStretch.at(c); } + inline void setRowMinimumHeight(int r, int s) + { expand(r + 1, 0); rMinHeights[r] = s; setDirty(); } + inline void setColumnMinimumWidth(int c, int s) + { expand(0, c + 1); cMinWidths[c] = s; setDirty(); } + inline int rowSpacing(int r) const { return rMinHeights.at(r); } + inline int colSpacing(int c) const { return cMinWidths.at(c); } + + inline void setReversed(bool r, bool c) { hReversed = c; vReversed = r; } + inline bool horReversed() const { return hReversed; } + inline bool verReversed() const { return vReversed; } + inline void setDirty() { needRecalc = true; hfw_width = -1; } + inline bool isDirty() const { return needRecalc; } + bool hasHeightForWidth(int hSpacing, int vSpacing); + int heightForWidth(int width, int hSpacing, int vSpacing); + int minimumHeightForWidth(int width, int hSpacing, int vSpacing); + + inline void getNextPos(int &row, int &col) { row = nextR; col = nextC; } + inline int count() const { return things.count(); } + QRect cellRect(int row, int col) const; + + inline QLayoutItem *itemAt(int index) const { + if (index < things.count()) + return things.at(index)->item(); + else + return 0; + } + inline QLayoutItem *takeAt(int index) { + QLayoutItem *item = 0; + if (index < things.count()) { + QGridBox *b = things.takeAt(index); + if (b) { + item = b->takeItem(); + delete b; + } + } + return item; + } + + void getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan) { + if (index < things.count()) { + QGridBox *b = things.at(index); + int toRow = b->toRow(rr); + int toCol = b->toCol(cc); + *row = b->row; + *column = b->col; + *rowSpan = toRow - *row + 1; + *columnSpan = toCol - *column +1; + } + } + void deleteAll(); + +private: + void setNextPosAfter(int r, int c); + void recalcHFW(int w); + void addHfwData(QGridBox *box, int width); + void init(); + QSize findSize(int QLayoutStruct::*, int hSpacing, int vSpacing) const; + void addData(QGridBox *b, const QGridLayoutSizeTriple &sizes, bool r, bool c); + void setSize(int rows, int cols); + void setupSpacings(QVector<QLayoutStruct> &chain, QGridBox *grid[], int fixedSpacing, + Qt::Orientation orientation); + void setupLayoutData(int hSpacing, int vSpacing); + void setupHfwLayoutData(); + void effectiveMargins(int *left, int *top, int *right, int *bottom) const; + + int rr; + int cc; + QVector<QLayoutStruct> rowData; + QVector<QLayoutStruct> colData; + QVector<QLayoutStruct> *hfwData; + QVector<int> rStretch; + QVector<int> cStretch; + QVector<int> rMinHeights; + QVector<int> cMinWidths; + QList<QGridBox *> things; + + int hfw_width; + int hfw_height; + int hfw_minheight; + int nextR; + int nextC; + + int horizontalSpacing; + int verticalSpacing; + int leftMargin; + int topMargin; + int rightMargin; + int bottomMargin; + + uint hReversed : 1; + uint vReversed : 1; + uint needRecalc : 1; + uint has_hfw : 1; + uint addVertical : 1; +}; + +void QGridLayoutPrivate::effectiveMargins(int *left, int *top, int *right, int *bottom) const +{ + int l = leftMargin; + int t = topMargin; + int r = rightMargin; + int b = bottomMargin; +#ifdef Q_WS_MAC + int leftMost = INT_MAX; + int topMost = INT_MAX; + int rightMost = 0; + int bottomMost = 0; + + QWidget *w = 0; + const int n = things.count(); + for (int i = 0; i < n; ++i) { + QGridBox *box = things.at(i); + QLayoutItem *itm = box->item(); + w = itm->widget(); + if (w) { + bool visualHReversed = hReversed != (w->layoutDirection() == Qt::RightToLeft); + QRect lir = itm->geometry(); + QRect wr = w->geometry(); + if (box->col <= leftMost) { + if (box->col < leftMost) { + // we found an item even closer to the margin, discard. + leftMost = box->col; + if (visualHReversed) + r = rightMargin; + else + l = leftMargin; + } + if (visualHReversed) { + r = qMax(r, wr.right() - lir.right()); + } else { + l = qMax(l, lir.left() - wr.left()); + } + } + if (box->row <= topMost) { + if (box->row < topMost) { + // we found an item even closer to the margin, discard. + topMost = box->row; + if (vReversed) + b = bottomMargin; + else + t = topMargin; + } + if (vReversed) + b = qMax(b, wr.bottom() - lir.bottom()); + else + t = qMax(t, lir.top() - wr.top()); + } + if (box->toCol(cc) >= rightMost) { + if (box->toCol(cc) > rightMost) { + // we found an item even closer to the margin, discard. + rightMost = box->toCol(cc); + if (visualHReversed) + l = leftMargin; + else + r = rightMargin; + } + if (visualHReversed) { + l = qMax(l, lir.left() - wr.left()); + } else { + r = qMax(r, wr.right() - lir.right()); + } + + } + if (box->toRow(rr) >= bottomMost) { + if (box->toRow(rr) > bottomMost) { + // we found an item even closer to the margin, discard. + bottomMost = box->toRow(rr); + if (vReversed) + t = topMargin; + else + b = bottomMargin; + } + if (vReversed) + t = qMax(t, lir.top() - wr.top()); + else + b = qMax(b, wr.bottom() - lir.bottom()); + } + } + } + +#endif + if (left) + *left = l; + if (top) + *top = t; + if (right) + *right = r; + if (bottom) + *bottom = b; +} + +QGridLayoutPrivate::QGridLayoutPrivate() +{ + addVertical = false; + setDirty(); + rr = cc = 0; + nextR = nextC = 0; + hfwData = 0; + hReversed = false; + vReversed = false; + horizontalSpacing = -1; + verticalSpacing = -1; +} + +#if 0 +QGridLayoutPrivate::QGridLayoutPrivate(int nRows, int nCols) + : rowData(0), colData(0) +{ + init(); + if (nRows < 0) { + nRows = 1; + addVertical = false; + } + if (nCols < 0) { + nCols = 1; + addVertical = true; + } + setSize(nRows, nCols); +} +#endif + +void QGridLayoutPrivate::deleteAll() +{ + while (!things.isEmpty()) + delete things.takeFirst(); + delete hfwData; +} + +bool QGridLayoutPrivate::hasHeightForWidth(int hSpacing, int vSpacing) +{ + setupLayoutData(hSpacing, vSpacing); + return has_hfw; +} + +/* + Assumes that setupLayoutData() has been called, and that + qGeomCalc() has filled in colData with appropriate values. +*/ +void QGridLayoutPrivate::recalcHFW(int w) +{ + /* + Go through all children, using colData and heightForWidth() + and put the results in hfwData. + */ + if (!hfwData) + hfwData = new QVector<QLayoutStruct>(rr); + setupHfwLayoutData(); + QVector<QLayoutStruct> &rData = *hfwData; + + int h = 0; + int mh = 0; + for (int r = 0; r < rr; r++) { + int spacing = rData.at(r).spacing; + h += rData.at(r).sizeHint + spacing; + mh += rData.at(r).minimumSize + spacing; + } + + hfw_width = w; + hfw_height = qMin(QLAYOUTSIZE_MAX, h); + hfw_minheight = qMin(QLAYOUTSIZE_MAX, mh); +} + +int QGridLayoutPrivate::heightForWidth(int w, int hSpacing, int vSpacing) +{ + setupLayoutData(hSpacing, vSpacing); + if (!has_hfw) + return -1; + int left, top, right, bottom; + effectiveMargins(&left, &top, &right, &bottom); + + int hMargins = left + right; + if (w - hMargins != hfw_width) { + qGeomCalc(colData, 0, cc, 0, w - hMargins); + recalcHFW(w - hMargins); + } + return hfw_height + top + bottom; +} + +int QGridLayoutPrivate::minimumHeightForWidth(int w, int hSpacing, int vSpacing) +{ + (void)heightForWidth(w, hSpacing, vSpacing); + if (!has_hfw) + return -1; + int top, bottom; + effectiveMargins(0, &top, 0, &bottom); + return hfw_minheight + top + bottom; +} + +QSize QGridLayoutPrivate::findSize(int QLayoutStruct::*size, int hSpacing, int vSpacing) const +{ + QGridLayoutPrivate *that = const_cast<QGridLayoutPrivate*>(this); + that->setupLayoutData(hSpacing, vSpacing); + + int w = 0; + int h = 0; + + for (int r = 0; r < rr; r++) + h += rowData.at(r).*size + rowData.at(r).spacing; + for (int c = 0; c < cc; c++) + w += colData.at(c).*size + colData.at(c).spacing; + + w = qMin(QLAYOUTSIZE_MAX, w); + h = qMin(QLAYOUTSIZE_MAX, h); + + return QSize(w, h); +} + +Qt::Orientations QGridLayoutPrivate::expandingDirections(int hSpacing, int vSpacing) const +{ + QGridLayoutPrivate *that = const_cast<QGridLayoutPrivate*>(this); + that->setupLayoutData(hSpacing, vSpacing); + Qt::Orientations ret; + + for (int r = 0; r < rr; r++) { + if (rowData.at(r).expansive) { + ret |= Qt::Vertical; + break; + } + } + for (int c = 0; c < cc; c++) { + if (colData.at(c).expansive) { + ret |= Qt::Horizontal; + break; + } + } + return ret; +} + +QSize QGridLayoutPrivate::sizeHint(int hSpacing, int vSpacing) const +{ + return findSize(&QLayoutStruct::sizeHint, hSpacing, vSpacing); +} + +QSize QGridLayoutPrivate::maximumSize(int hSpacing, int vSpacing) const +{ + return findSize(&QLayoutStruct::maximumSize, hSpacing, vSpacing); +} + +QSize QGridLayoutPrivate::minimumSize(int hSpacing, int vSpacing) const +{ + return findSize(&QLayoutStruct::minimumSize, hSpacing, vSpacing); +} + +void QGridLayoutPrivate::setSize(int r, int c) +{ + if ((int)rowData.size() < r) { + int newR = qMax(r, rr * 2); + rowData.resize(newR); + rStretch.resize(newR); + rMinHeights.resize(newR); + for (int i = rr; i < newR; i++) { + rowData[i].init(); + rowData[i].maximumSize = 0; + rowData[i].pos = 0; + rowData[i].size = 0; + rStretch[i] = 0; + rMinHeights[i] = 0; + } + } + if ((int)colData.size() < c) { + int newC = qMax(c, cc * 2); + colData.resize(newC); + cStretch.resize(newC); + cMinWidths.resize(newC); + for (int i = cc; i < newC; i++) { + colData[i].init(); + colData[i].maximumSize = 0; + colData[i].pos = 0; + colData[i].size = 0; + cStretch[i] = 0; + cMinWidths[i] = 0; + } + } + + if (hfwData && (int)hfwData->size() < r) { + delete hfwData; + hfwData = 0; + hfw_width = -1; + } + rr = r; + cc = c; +} + +void QGridLayoutPrivate::setNextPosAfter(int row, int col) +{ + if (addVertical) { + if (col > nextC || (col == nextC && row >= nextR)) { + nextR = row + 1; + nextC = col; + if (nextR >= rr) { + nextR = 0; + nextC++; + } + } + } else { + if (row > nextR || (row == nextR && col >= nextC)) { + nextR = row; + nextC = col + 1; + if (nextC >= cc) { + nextC = 0; + nextR++; + } + } + } +} + +void QGridLayoutPrivate::add(QGridBox *box, int row, int col) +{ + expand(row + 1, col + 1); + box->row = box->torow = row; + box->col = box->tocol = col; + things.append(box); + setDirty(); + setNextPosAfter(row, col); +} + +void QGridLayoutPrivate::add(QGridBox *box, int row1, int row2, int col1, int col2) +{ + if (row2 >= 0 && row2 < row1) + qWarning("QGridLayout: Multi-cell fromRow greater than toRow"); + if (col2 >= 0 && col2 < col1) + qWarning("QGridLayout: Multi-cell fromCol greater than toCol"); + if (row1 == row2 && col1 == col2) { + add(box, row1, col1); + return; + } + expand(row2 + 1, col2 + 1); + box->row = row1; + box->col = col1; + + box->torow = row2; + box->tocol = col2; + + things.append(box); + setDirty(); + if (col2 < 0) + col2 = cc - 1; + + setNextPosAfter(row2, col2); +} + +void QGridLayoutPrivate::addData(QGridBox *box, const QGridLayoutSizeTriple &sizes, bool r, bool c) +{ + const QWidget *widget = box->item()->widget(); + + if (box->isEmpty() && widget) + return; + + if (c) { + QLayoutStruct *data = &colData[box->col]; + if (!cStretch.at(box->col)) + data->stretch = qMax(data->stretch, box->hStretch()); + data->sizeHint = qMax(sizes.hint.width(), data->sizeHint); + data->minimumSize = qMax(sizes.minS.width(), data->minimumSize); + + qMaxExpCalc(data->maximumSize, data->expansive, data->empty, sizes.maxS.width(), + box->expandingDirections() & Qt::Horizontal, box->isEmpty()); + } + if (r) { + QLayoutStruct *data = &rowData[box->row]; + if (!rStretch.at(box->row)) + data->stretch = qMax(data->stretch, box->vStretch()); + data->sizeHint = qMax(sizes.hint.height(), data->sizeHint); + data->minimumSize = qMax(sizes.minS.height(), data->minimumSize); + + qMaxExpCalc(data->maximumSize, data->expansive, data->empty, sizes.maxS.height(), + box->expandingDirections() & Qt::Vertical, box->isEmpty()); + } +} + +static void initEmptyMultiBox(QVector<QLayoutStruct> &chain, int start, int end) +{ + for (int i = start; i <= end; i++) { + QLayoutStruct *data = &chain[i]; + if (data->empty && data->maximumSize == 0) // truly empty box + data->maximumSize = QWIDGETSIZE_MAX; + data->empty = false; + } +} + +static void distributeMultiBox(QVector<QLayoutStruct> &chain, int start, int end, int minSize, + int sizeHint, QVector<int> &stretchArray, int stretch) +{ + int i; + int w = 0; + int wh = 0; + int max = 0; + + for (i = start; i <= end; i++) { + QLayoutStruct *data = &chain[i]; + w += data->minimumSize; + wh += data->sizeHint; + max += data->maximumSize; + if (stretchArray.at(i) == 0) + data->stretch = qMax(data->stretch, stretch); + + if (i != end) { + int spacing = data->spacing; + w += spacing; + wh += spacing; + max += spacing; + } + } + + if (max < minSize) { // implies w < minSize + /* + We must increase the maximum size of at least one of the + items. qGeomCalc() will put the extra space in between the + items. We must recover that extra space and put it + somewhere. It does not really matter where, since the user + can always specify stretch factors and avoid this code. + */ + qGeomCalc(chain, start, end - start + 1, 0, minSize); + int pos = 0; + for (i = start; i <= end; i++) { + QLayoutStruct *data = &chain[i]; + int nextPos = (i == end) ? minSize : chain.at(i + 1).pos; + int realSize = nextPos - pos; + if (i != end) + realSize -= data->spacing; + if (data->minimumSize < realSize) + data->minimumSize = realSize; + if (data->maximumSize < data->minimumSize) + data->maximumSize = data->minimumSize; + pos = nextPos; + } + } else if (w < minSize) { + qGeomCalc(chain, start, end - start + 1, 0, minSize); + for (i = start; i <= end; i++) { + QLayoutStruct *data = &chain[i]; + if (data->minimumSize < data->size) + data->minimumSize = data->size; + } + } + + if (wh < sizeHint) { + qGeomCalc(chain, start, end - start + 1, 0, sizeHint); + for (i = start; i <= end; i++) { + QLayoutStruct *data = &chain[i]; + if (data->sizeHint < data->size) + data->sizeHint = data->size; + } + } +} + +static QGridBox *&gridAt(QGridBox *grid[], int r, int c, int cc, + Qt::Orientation orientation = Qt::Vertical) +{ + if (orientation == Qt::Horizontal) + qSwap(r, c); + return grid[(r * cc) + c]; +} + +void QGridLayoutPrivate::setupSpacings(QVector<QLayoutStruct> &chain, + QGridBox *grid[], int fixedSpacing, + Qt::Orientation orientation) +{ + Q_Q(QGridLayout); + int numRows = rr; // or columns if orientation is horizontal + int numColumns = cc; // or rows if orientation is horizontal + + if (orientation == Qt::Horizontal) { + qSwap(numRows, numColumns); + } + + QStyle *style = 0; + if (fixedSpacing < 0) { + if (QWidget *parentWidget = q->parentWidget()) + style = parentWidget->style(); + } + + for (int c = 0; c < numColumns; ++c) { + QGridBox *previousBox = 0; + int previousRow = -1; // previous *non-empty* row + + for (int r = 0; r < numRows; ++r) { + if (chain.at(r).empty) + continue; + + QGridBox *box = gridAt(grid, r, c, cc, orientation); + if (previousRow != -1 && (!box || previousBox != box)) { + int spacing = fixedSpacing; + if (spacing < 0) { + QSizePolicy::ControlTypes controlTypes1 = QSizePolicy::DefaultType; + QSizePolicy::ControlTypes controlTypes2 = QSizePolicy::DefaultType; + if (previousBox) + controlTypes1 = previousBox->item()->controlTypes(); + if (box) + controlTypes2 = box->item()->controlTypes(); + + if ((orientation == Qt::Horizontal && hReversed) + || (orientation == Qt::Vertical && vReversed)) + qSwap(controlTypes1, controlTypes2); + + if (style) + spacing = style->combinedLayoutSpacing(controlTypes1, controlTypes2, + orientation, 0, q->parentWidget()); + } else { + if (orientation == Qt::Vertical) { + QGridBox *sibling = vReversed ? previousBox : box; + if (sibling) { + QWidget *wid = sibling->item()->widget(); + if (wid) + spacing = qMax(spacing, sibling->item()->geometry().top() - wid->geometry().top() ); + } + } + } + + if (spacing > chain.at(previousRow).spacing) + chain[previousRow].spacing = spacing; + } + + previousBox = box; + previousRow = r; + } + } +} + +//#define QT_LAYOUT_DISABLE_CACHING + +void QGridLayoutPrivate::setupLayoutData(int hSpacing, int vSpacing) +{ + Q_Q(QGridLayout); + +#ifndef QT_LAYOUT_DISABLE_CACHING + if (!needRecalc) + return; +#endif + has_hfw = false; + int i; + + for (i = 0; i < rr; i++) { + rowData[i].init(rStretch.at(i), rMinHeights.at(i)); + rowData[i].maximumSize = rStretch.at(i) ? QLAYOUTSIZE_MAX : rMinHeights.at(i); + } + for (i = 0; i < cc; i++) { + colData[i].init(cStretch.at(i), cMinWidths.at(i)); + colData[i].maximumSize = cStretch.at(i) ? QLAYOUTSIZE_MAX : cMinWidths.at(i); + } + + int n = things.size(); + QVarLengthArray<QGridLayoutSizeTriple> sizes(n); + + bool has_multi = false; + + /* + Grid of items. We use it to determine which items are + adjacent to which and compute the spacings correctly. + */ + QVarLengthArray<QGridBox *> grid(rr * cc); + qMemSet(grid.data(), 0, rr * cc * sizeof(QGridBox *)); + + /* + Initialize 'sizes' and 'grid' data structures, and insert + non-spanning items to our row and column data structures. + */ + for (i = 0; i < n; ++i) { + QGridBox * const box = things.at(i); + sizes[i].minS = box->minimumSize(); + sizes[i].hint = box->sizeHint(); + sizes[i].maxS = box->maximumSize(); + + if (box->hasHeightForWidth()) + has_hfw = true; + + if (box->row == box->toRow(rr)) { + addData(box, sizes[i], true, false); + } else { + initEmptyMultiBox(rowData, box->row, box->toRow(rr)); + has_multi = true; + } + + if (box->col == box->toCol(cc)) { + addData(box, sizes[i], false, true); + } else { + initEmptyMultiBox(colData, box->col, box->toCol(cc)); + has_multi = true; + } + + for (int r = box->row; r <= box->toRow(rr); ++r) { + for (int c = box->col; c <= box->toCol(cc); ++c) { + gridAt(grid.data(), r, c, cc) = box; + } + } + } + + setupSpacings(colData, grid.data(), hSpacing, Qt::Horizontal); + setupSpacings(rowData, grid.data(), vSpacing, Qt::Vertical); + + /* + Insert multicell items to our row and column data structures. + This must be done after the non-spanning items to obtain a + better distribution in distributeMultiBox(). + */ + if (has_multi) { + for (i = 0; i < n; ++i) { + QGridBox * const box = things.at(i); + + if (box->row != box->toRow(rr)) + distributeMultiBox(rowData, box->row, box->toRow(rr), sizes[i].minS.height(), + sizes[i].hint.height(), rStretch, box->vStretch()); + if (box->col != box->toCol(cc)) + distributeMultiBox(colData, box->col, box->toCol(cc), sizes[i].minS.width(), + sizes[i].hint.width(), cStretch, box->hStretch()); + } + } + + for (i = 0; i < rr; i++) + rowData[i].expansive = rowData.at(i).expansive || rowData.at(i).stretch > 0; + for (i = 0; i < cc; i++) + colData[i].expansive = colData.at(i).expansive || colData.at(i).stretch > 0; + + q->getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); + + needRecalc = false; +} + +void QGridLayoutPrivate::addHfwData(QGridBox *box, int width) +{ + QVector<QLayoutStruct> &rData = *hfwData; + if (box->hasHeightForWidth()) { + int hint = box->heightForWidth(width); + rData[box->row].sizeHint = qMax(hint, rData.at(box->row).sizeHint); + rData[box->row].minimumSize = qMax(hint, rData.at(box->row).minimumSize); + } else { + QSize hint = box->sizeHint(); + QSize minS = box->minimumSize(); + rData[box->row].sizeHint = qMax(hint.height(), rData.at(box->row).sizeHint); + rData[box->row].minimumSize = qMax(minS.height(), rData.at(box->row).minimumSize); + } +} + +/* + Similar to setupLayoutData(), but uses heightForWidth(colData) + instead of sizeHint(). Assumes that setupLayoutData() and + qGeomCalc(colData) has been called. +*/ +void QGridLayoutPrivate::setupHfwLayoutData() +{ + QVector<QLayoutStruct> &rData = *hfwData; + for (int i = 0; i < rr; i++) { + rData[i] = rowData.at(i); + rData[i].minimumSize = rData[i].sizeHint = rMinHeights.at(i); + } + + for (int pass = 0; pass < 2; ++pass) { + for (int i = 0; i < things.size(); ++i) { + QGridBox *box = things.at(i); + int r1 = box->row; + int c1 = box->col; + int r2 = box->toRow(rr); + int c2 = box->toCol(cc); + int w = colData.at(c2).pos + colData.at(c2).size - colData.at(c1).pos; + + if (r1 == r2) { + if (pass == 0) + addHfwData(box, w); + } else { + if (pass == 0) { + initEmptyMultiBox(rData, r1, r2); + } else { + QSize hint = box->sizeHint(); + QSize min = box->minimumSize(); + if (box->hasHeightForWidth()) { + int hfwh = box->heightForWidth(w); + if (hfwh > hint.height()) + hint.setHeight(hfwh); + if (hfwh > min.height()) + min.setHeight(hfwh); + } + distributeMultiBox(rData, r1, r2, min.height(), hint.height(), + rStretch, box->vStretch()); + } + } + } + } + for (int i = 0; i < rr; i++) + rData[i].expansive = rData.at(i).expansive || rData.at(i).stretch > 0; +} + +void QGridLayoutPrivate::distribute(QRect r, int hSpacing, int vSpacing) +{ + Q_Q(QGridLayout); + bool visualHReversed = hReversed; + QWidget *parent = q->parentWidget(); + if (parent && parent->isRightToLeft()) + visualHReversed = !visualHReversed; + + setupLayoutData(hSpacing, vSpacing); + + int left, top, right, bottom; + effectiveMargins(&left, &top, &right, &bottom); + r.adjust(+left, +top, -right, -bottom); + + qGeomCalc(colData, 0, cc, r.x(), r.width()); + QVector<QLayoutStruct> *rDataPtr; + if (has_hfw) { + recalcHFW(r.width()); + qGeomCalc(*hfwData, 0, rr, r.y(), r.height()); + rDataPtr = hfwData; + } else { + qGeomCalc(rowData, 0, rr, r.y(), r.height()); + rDataPtr = &rowData; + } + QVector<QLayoutStruct> &rData = *rDataPtr; + int i; + + bool reverse = ((r.bottom() > rect.bottom()) || (r.bottom() == rect.bottom() + && ((r.right() > rect.right()) != visualHReversed))); + int n = things.size(); + for (i = 0; i < n; ++i) { + QGridBox *box = things.at(reverse ? n-i-1 : i); + int r2 = box->toRow(rr); + int c2 = box->toCol(cc); + + int x = colData.at(box->col).pos; + int y = rData.at(box->row).pos; + int x2p = colData.at(c2).pos + colData.at(c2).size; // x2+1 + int y2p = rData.at(r2).pos + rData.at(r2).size; // y2+1 + int w = x2p - x; + int h = y2p - y; + + if (visualHReversed) + x = r.left() + r.right() - x - w + 1; + if (vReversed) + y = r.top() + r.bottom() - y - h + 1; + + box->setGeometry(QRect(x, y, w, h)); + } +} + +QRect QGridLayoutPrivate::cellRect(int row, int col) const +{ + if (row < 0 || row >= rr || col < 0 || col >= cc) + return QRect(); + + const QVector<QLayoutStruct> *rDataPtr; + if (has_hfw && hfwData) + rDataPtr = hfwData; + else + rDataPtr = &rowData; + return QRect(colData.at(col).pos, rDataPtr->at(row).pos, + colData.at(col).size, rDataPtr->at(row).size); +} + +/*! + \class QGridLayout + + \brief The QGridLayout class lays out widgets in a grid. + + \ingroup geomanagement + + + QGridLayout takes the space made available to it (by its parent + layout or by the parentWidget()), divides it up into rows and + columns, and puts each widget it manages into the correct cell. + + Columns and rows behave identically; we will discuss columns, but + there are equivalent functions for rows. + + Each column has a minimum width and a stretch factor. The minimum + width is the greatest of that set using setColumnMinimumWidth() and the + minimum width of each widget in that column. The stretch factor is + set using setColumnStretch() and determines how much of the available + space the column will get over and above its necessary minimum. + + Normally, each managed widget or layout is put into a cell of its + own using addWidget(). It is also possible for a widget to occupy + multiple cells using the row and column spanning overloads of + addItem() and addWidget(). If you do this, QGridLayout will guess + how to distribute the size over the columns/rows (based on the + stretch factors). + + To remove a widget from a layout, call removeWidget(). Calling + QWidget::hide() on a widget also effectively removes the widget + from the layout until QWidget::show() is called. + + This illustration shows a fragment of a dialog with a five-column, + three-row grid (the grid is shown overlaid in magenta): + + \image gridlayout.png A grid layout + + Columns 0, 2 and 4 in this dialog fragment are made up of a + QLabel, a QLineEdit, and a QListBox. Columns 1 and 3 are + placeholders made with setColumnMinimumWidth(). Row 0 consists of three + QLabel objects, row 1 of three QLineEdit objects and row 2 of + three QListBox objects. We used placeholder columns (1 and 3) to + get the right amount of space between the columns. + + Note that the columns and rows are not equally wide or tall. If + you want two columns to have the same width, you must set their + minimum widths and stretch factors to be the same yourself. You do + this using setColumnMinimumWidth() and setColumnStretch(). + + If the QGridLayout is not the top-level layout (i.e. does not + manage all of the widget's area and children), you must add it to + its parent layout when you create it, but before you do anything + with it. The normal way to add a layout is by calling + addLayout() on the parent layout. + + Once you have added your layout you can start putting widgets and + other layouts into the cells of your grid layout using + addWidget(), addItem(), and addLayout(). + + QGridLayout also includes two margin widths: + the \l{getContentsMargins()}{contents margin} and the spacing(). + The contents margin is the width of the reserved space along each + of the QGridLayout's four sides. The spacing() is the width of the + automatically allocated spacing between neighboring boxes. + + The default contents margin values are provided by the + \l{QStyle::pixelMetric()}{style}. The default value Qt styles specify + is 9 for child widgets and 11 for windows. The spacing defaults to the same as + the margin width for a top-level layout, or to the same as the + parent layout. + + \sa QBoxLayout, QStackedLayout, {Layout Management}, {Basic Layouts Example} +*/ + + +/*! + Constructs a new QGridLayout with parent widget, \a parent. The + layout has one row and one column initially, and will expand when + new items are inserted. +*/ +QGridLayout::QGridLayout(QWidget *parent) + : QLayout(*new QGridLayoutPrivate, 0, parent) +{ + Q_D(QGridLayout); + d->expand(1, 1); +} + +/*! + Constructs a new grid layout. + + You must insert this grid into another layout. You can insert + widgets and layouts into this layout at any time, but laying out + will not be performed before this is inserted into another layout. +*/ +QGridLayout::QGridLayout() + : QLayout(*new QGridLayoutPrivate, 0, 0) +{ + Q_D(QGridLayout); + d->expand(1, 1); +} + + +#ifdef QT3_SUPPORT +/*! + \obsolete + Constructs a new QGridLayout with \a nRows rows, \a nCols columns + and parent widget, \a parent. \a parent may not be 0. The grid + layout is called \a name. + + \a margin is the number of pixels between the edge of the widget + and its managed children. \a space is the default number of pixels + between cells. If \a space is -1, the value of \a margin is used. +*/ +QGridLayout::QGridLayout(QWidget *parent, int nRows, int nCols, int margin, + int space, const char *name) + : QLayout(*new QGridLayoutPrivate, 0, parent) +{ + Q_D(QGridLayout); + d->expand(nRows, nCols); + setMargin(margin); + setSpacing(space < 0 ? margin : space); + setObjectName(QString::fromAscii(name)); +} + +/*! + \obsolete + + Constructs a new grid with \a nRows rows and \a nCols columns. If + \a spacing is -1, this QGridLayout inherits its parent's + spacing(); otherwise \a spacing is used. The grid layout is called + \a name. + + You must insert this grid into another layout. You can insert + widgets and layouts into this layout at any time, but laying out + will not be performed before this is inserted into another layout. +*/ +QGridLayout::QGridLayout(QLayout *parentLayout, int nRows, int nCols, + int spacing, const char *name) + : QLayout(*new QGridLayoutPrivate, parentLayout, 0) +{ + Q_D(QGridLayout); + d->expand(nRows, nCols); + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); +} + +/*! + \obsolete + + Constructs a new grid with \a nRows rows and \a nCols columns. If + \a spacing is -1, this QGridLayout inherits its parent's + spacing(); otherwise \a spacing is used. The grid layout is called + \a name. + + You must insert this grid into another layout. You can insert + widgets and layouts into this layout at any time, but laying out + will not be performed before this is inserted into another layout. +*/ +QGridLayout::QGridLayout(int nRows, int nCols, int spacing, const char *name) + : QLayout(*new QGridLayoutPrivate, 0, 0) +{ + Q_D(QGridLayout); + d->expand(nRows, nCols); + setSpacing(spacing); + setObjectName(QString::fromAscii(name)); +} +#endif + + +/*! +\internal (mostly) + +Sets the positioning mode used by addItem(). If \a orient is +Qt::Horizontal, this layout is expanded to \a n columns, and items +will be added columns-first. Otherwise it is expanded to \a n rows and +items will be added rows-first. +*/ + +void QGridLayout::setDefaultPositioning(int n, Qt::Orientation orient) +{ + Q_D(QGridLayout); + if (orient == Qt::Horizontal) { + d->expand(1, n); + d->addVertical = false; + } else { + d->expand(n,1); + d->addVertical = true; + } +} + + +/*! + Destroys the grid layout. Geometry management is terminated if + this is a top-level grid. + + The layout's widgets aren't destroyed. +*/ +QGridLayout::~QGridLayout() +{ + Q_D(QGridLayout); + d->deleteAll(); +} + +/*! + \property QGridLayout::horizontalSpacing + \brief the spacing between widgets that are laid out side by side + \since 4.3 + + If no value is explicitly set, the layout's horizontal spacing is + inherited from the parent layout, or from the style settings for + the parent widget. + + \sa verticalSpacing, QStyle::pixelMetric(), {QStyle::}{PM_LayoutHorizontalSpacing} +*/ +void QGridLayout::setHorizontalSpacing(int spacing) +{ + Q_D(QGridLayout); + d->horizontalSpacing = spacing; + invalidate(); +} + +int QGridLayout::horizontalSpacing() const +{ + Q_D(const QGridLayout); + if (d->horizontalSpacing >= 0) { + return d->horizontalSpacing; + } else { + return qSmartSpacing(this, QStyle::PM_LayoutHorizontalSpacing); + } +} + +/*! + \property QGridLayout::verticalSpacing + \brief the spacing between widgets that are laid out on top of each other + \since 4.3 + + If no value is explicitly set, the layout's vertical spacing is + inherited from the parent layout, or from the style settings for + the parent widget. + + \sa horizontalSpacing, QStyle::pixelMetric(), {QStyle::}{PM_LayoutHorizontalSpacing} +*/ +void QGridLayout::setVerticalSpacing(int spacing) +{ + Q_D(QGridLayout); + d->verticalSpacing = spacing; + invalidate(); +} + +int QGridLayout::verticalSpacing() const +{ + Q_D(const QGridLayout); + if (d->verticalSpacing >= 0) { + return d->verticalSpacing; + } else { + return qSmartSpacing(this, QStyle::PM_LayoutVerticalSpacing); + } +} + +/*! + This function sets both the vertical and horizontal spacing to + \a spacing. + + \sa setVerticalSpacing(), setHorizontalSpacing() +*/ +void QGridLayout::setSpacing(int spacing) +{ + Q_D(QGridLayout); + d->horizontalSpacing = d->verticalSpacing = spacing; + invalidate(); +} + +/*! + If the vertical spacing is equal to the horizontal spacing, + this function returns that value; otherwise it return -1. + + \sa setSpacing(), verticalSpacing(), horizontalSpacing() +*/ +int QGridLayout::spacing() const +{ + int hSpacing = horizontalSpacing(); + if (hSpacing == verticalSpacing()) { + return hSpacing; + } else { + return -1; + } +} + +/*! + Returns the number of rows in this grid. +*/ +int QGridLayout::rowCount() const +{ + Q_D(const QGridLayout); + return d->numRows(); +} + +/*! + Returns the number of columns in this grid. +*/ +int QGridLayout::columnCount() const +{ + Q_D(const QGridLayout); + return d->numCols(); +} + +/*! + \reimp +*/ +QSize QGridLayout::sizeHint() const +{ + Q_D(const QGridLayout); + QSize result(d->sizeHint(horizontalSpacing(), verticalSpacing())); + int left, top, right, bottom; + d->effectiveMargins(&left, &top, &right, &bottom); + result += QSize(left + right, top + bottom); + return result; +} + +/*! + \reimp +*/ +QSize QGridLayout::minimumSize() const +{ + Q_D(const QGridLayout); + QSize result(d->minimumSize(horizontalSpacing(), verticalSpacing())); + int left, top, right, bottom; + d->effectiveMargins(&left, &top, &right, &bottom); + result += QSize(left + right, top + bottom); + return result; +} + +/*! + \reimp +*/ +QSize QGridLayout::maximumSize() const +{ + Q_D(const QGridLayout); + + QSize s = d->maximumSize(horizontalSpacing(), verticalSpacing()); + int left, top, right, bottom; + d->effectiveMargins(&left, &top, &right, &bottom); + s += QSize(left + right, top + bottom); + s = s.boundedTo(QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX)); + if (alignment() & Qt::AlignHorizontal_Mask) + s.setWidth(QLAYOUTSIZE_MAX); + if (alignment() & Qt::AlignVertical_Mask) + s.setHeight(QLAYOUTSIZE_MAX); + return s; +} + +/*! + \reimp +*/ +bool QGridLayout::hasHeightForWidth() const +{ + return ((QGridLayout*)this)->d_func()->hasHeightForWidth(horizontalSpacing(), verticalSpacing()); +} + +/*! + \reimp +*/ +int QGridLayout::heightForWidth(int w) const +{ + Q_D(const QGridLayout); + QGridLayoutPrivate *dat = const_cast<QGridLayoutPrivate *>(d); + return dat->heightForWidth(w, horizontalSpacing(), verticalSpacing()); +} + +/*! + \reimp +*/ +int QGridLayout::minimumHeightForWidth(int w) const +{ + Q_D(const QGridLayout); + QGridLayoutPrivate *dat = const_cast<QGridLayoutPrivate *>(d); + return dat->minimumHeightForWidth(w, horizontalSpacing(), verticalSpacing()); +} + +#ifdef QT3_SUPPORT +/*! + \compat + + Searches for widget \a w in this layout (not including child + layouts). If \a w is found, it sets \c{*}\a{row} and + \c{*}\a{column} to the row and column that the widget + occupies and returns true; otherwise returns false. + + If the widget spans multiple rows/columns, the top-left cell + is returned. + + Use indexOf() and getItemPosition() instead. +*/ +bool QGridLayout::findWidget(QWidget* w, int *row, int *column) +{ + Q_D(QGridLayout); + int index = indexOf(w); + if (index < 0) + return false; + int dummy1, dummy2; + d->getItemPosition(index, row, column, &dummy1, &dummy2); + return true; +} +#endif +/*! + \reimp +*/ +int QGridLayout::count() const +{ + Q_D(const QGridLayout); + return d->count(); +} + + +/*! + \reimp +*/ +QLayoutItem *QGridLayout::itemAt(int index) const +{ + Q_D(const QGridLayout); + return d->itemAt(index); +} + +/*! + \since 4.4 + + Returns the layout item that occupies cell (\a row, \a column), or 0 if + the cell is empty. + + \sa getItemPosition(), indexOf() +*/ +QLayoutItem *QGridLayout::itemAtPosition(int row, int column) const +{ + Q_D(const QGridLayout); + int n = d->things.count(); + for (int i = 0; i < n; ++i) { + QGridBox *box = d->things.at(i); + if (row >= box->row && row <= box->toRow(d->rr) + && column >= box->col && column <= box->toCol(d->cc)) { + return box->item(); + } + } + return 0; +} + +/*! + \reimp +*/ +QLayoutItem *QGridLayout::takeAt(int index) +{ + Q_D(QGridLayout); + return d->takeAt(index); +} + +/*! + Returns the position information of the item with the given \a index. + + The variables passed as \a row and \a column are updated with the position of the + item in the layout, and the \a rowSpan and \a columnSpan variables are updated + with the vertical and horizontal spans of the item. + + \sa itemAtPosition(), itemAt() +*/ +void QGridLayout::getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan) +{ + Q_D(QGridLayout); + d->getItemPosition(index, row, column, rowSpan, columnSpan); +} + + +/*! + \reimp +*/ +void QGridLayout::setGeometry(const QRect &rect) +{ + Q_D(QGridLayout); + if (d->isDirty() || rect != geometry()) { + QRect cr = alignment() ? alignmentRect(rect) : rect; + d->distribute(cr, horizontalSpacing(), verticalSpacing()); + QLayout::setGeometry(rect); + } +} + +/*! + Returns the geometry of the cell with row \a row and column \a column + in the grid. Returns an invalid rectangle if \a row or \a column is + outside the grid. + + \warning in the current version of Qt this function does not + return valid results until setGeometry() has been called, i.e. + after the parentWidget() is visible. +*/ +QRect QGridLayout::cellRect(int row, int column) const +{ + Q_D(const QGridLayout); + return d->cellRect(row, column); +} +#ifdef QT3_SUPPORT +/*! + \obsolete + Expands this grid so that it will have \a nRows rows and \a nCols + columns. Will not shrink the grid. You should not need to call + this function because QGridLayout expands automatically as new + items are inserted. +*/ +void QGridLayout::expand(int nRows, int nCols) +{ + Q_D(QGridLayout); + d->expand(nRows, nCols); +} +#endif + +/*! + \reimp +*/ +void QGridLayout::addItem(QLayoutItem *item) +{ + Q_D(QGridLayout); + int r, c; + d->getNextPos(r, c); + addItem(item, r, c); +} + +/*! + Adds \a item at position \a row, \a column, spanning \a rowSpan + rows and \a columnSpan columns, and aligns it according to \a + alignment. If \a rowSpan and/or \a columnSpan is -1, then the item + will extend to the bottom and/or right edge, respectively. The + layout takes ownership of the \a item. + + \warning Do not use this function to add child layouts or child + widget items. Use addLayout() or addWidget() instead. +*/ +void QGridLayout::addItem(QLayoutItem *item, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + Q_D(QGridLayout); + QGridBox *b = new QGridBox(item); + b->setAlignment(alignment); + d->add(b, row, (rowSpan < 0) ? -1 : row + rowSpan - 1, column, (columnSpan < 0) ? -1 : column + columnSpan - 1); + invalidate(); +} + +/* + Returns true if the widget \a w can be added to the layout \a l; + otherwise returns false. +*/ +static bool checkWidget(QLayout *l, QWidget *w) +{ + if (!w) { + qWarning("QLayout: Cannot add null widget to %s/%s", l->metaObject()->className(), + l->objectName().toLocal8Bit().data()); + return false; + } + return true; +} + +/*! + Adds the given \a widget to the cell grid at \a row, \a column. The + top-left position is (0, 0) by default. + + The alignment is specified by \a alignment. The default + alignment is 0, which means that the widget fills the entire cell. + +*/ +void QGridLayout::addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment) +{ + if (!checkWidget(this, widget)) + return; + if (row < 0 || column < 0) { + qWarning("QGridLayout: Cannot add %s/%s to %s/%s at row %d column %d", + widget->metaObject()->className(), widget->objectName().toLocal8Bit().data(), + metaObject()->className(), objectName().toLocal8Bit().data(), row, column); + return; + } + addChildWidget(widget); + QWidgetItem *b = QLayoutPrivate::createWidgetItem(this, widget); + addItem(b, row, column, 1, 1, alignment); +} + +/*! + \overload + + This version adds the given \a widget to the cell grid, spanning + multiple rows/columns. The cell will start at \a fromRow, \a + fromColumn spanning \a rowSpan rows and \a columnSpan columns. The + \a widget will have the given \a alignment. + + If \a rowSpan and/or \a columnSpan is -1, then the widget will + extend to the bottom and/or right edge, respectively. + +*/ +void QGridLayout::addWidget(QWidget *widget, int fromRow, int fromColumn, + int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + Q_D(QGridLayout); + if (!checkWidget(this, widget)) + return; + int toRow = (rowSpan < 0) ? -1 : fromRow + rowSpan - 1; + int toColumn = (columnSpan < 0) ? -1 : fromColumn + columnSpan - 1; + addChildWidget(widget); + QGridBox *b = new QGridBox(this, widget); + b->setAlignment(alignment); + d->add(b, fromRow, toRow, fromColumn, toColumn); + invalidate(); +} + +/*! + \fn void QGridLayout::addWidget(QWidget *widget) + + \overload + \internal +*/ + +/*! + Places the \a layout at position (\a row, \a column) in the grid. The + top-left position is (0, 0). + + The alignment is specified by \a alignment. The default + alignment is 0, which means that the widget fills the entire cell. + + A non-zero alignment indicates that the layout should not grow to + fill the available space but should be sized according to + sizeHint(). + + + \a layout becomes a child of the grid layout. +*/ +void QGridLayout::addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment) +{ + Q_D(QGridLayout); + addChildLayout(layout); + QGridBox *b = new QGridBox(layout); + b->setAlignment(alignment); + d->add(b, row, column); +} + +/*! + \overload + This version adds the layout \a layout to the cell grid, spanning multiple + rows/columns. The cell will start at \a row, \a column spanning \a + rowSpan rows and \a columnSpan columns. + + If \a rowSpan and/or \a columnSpan is -1, then the layout will extend to the bottom + and/or right edge, respectively. +*/ +void QGridLayout::addLayout(QLayout *layout, int row, int column, + int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + Q_D(QGridLayout); + addChildLayout(layout); + QGridBox *b = new QGridBox(layout); + b->setAlignment(alignment); + d->add(b, row, (rowSpan < 0) ? -1 : row + rowSpan - 1, column, (columnSpan < 0) ? -1 : column + columnSpan - 1); +} + +/*! + Sets the stretch factor of row \a row to \a stretch. The first row + is number 0. + + The stretch factor is relative to the other rows in this grid. + Rows with a higher stretch factor take more of the available + space. + + The default stretch factor is 0. If the stretch factor is 0 and no + other row in this table can grow at all, the row may still grow. + + \sa rowStretch(), setRowMinimumHeight(), setColumnStretch() +*/ +void QGridLayout::setRowStretch(int row, int stretch) +{ + Q_D(QGridLayout); + d->setRowStretch(row, stretch); + invalidate(); +} + +/*! + Returns the stretch factor for row \a row. + + \sa setRowStretch() +*/ +int QGridLayout::rowStretch(int row) const +{ + Q_D(const QGridLayout); + return d->rowStretch(row); +} + +/*! + Returns the stretch factor for column \a column. + + \sa setColumnStretch() +*/ +int QGridLayout::columnStretch(int column) const +{ + Q_D(const QGridLayout); + return d->colStretch(column); +} + +/*! + Sets the stretch factor of column \a column to \a stretch. The first + column is number 0. + + The stretch factor is relative to the other columns in this grid. + Columns with a higher stretch factor take more of the available + space. + + The default stretch factor is 0. If the stretch factor is 0 and no + other column in this table can grow at all, the column may still + grow. + + An alternative approach is to add spacing using addItem() with a + QSpacerItem. + + \sa columnStretch(), setRowStretch() +*/ +void QGridLayout::setColumnStretch(int column, int stretch) +{ + Q_D(QGridLayout); + d->setColStretch(column, stretch); + invalidate(); +} + + + +/*! + Sets the minimum height of row \a row to \a minSize pixels. + + \sa rowMinimumHeight(), setColumnMinimumWidth() +*/ +void QGridLayout::setRowMinimumHeight(int row, int minSize) +{ + Q_D(QGridLayout); + d->setRowMinimumHeight(row, minSize); + invalidate(); +} + +/*! + Returns the minimum width set for row \a row. + + \sa setRowMinimumHeight() +*/ +int QGridLayout::rowMinimumHeight(int row) const +{ + Q_D(const QGridLayout); + return d->rowSpacing(row); +} + +/*! + Sets the minimum width of column \a column to \a minSize pixels. + + \sa columnMinimumWidth(), setRowMinimumHeight() +*/ +void QGridLayout::setColumnMinimumWidth(int column, int minSize) +{ + Q_D(QGridLayout); + d->setColumnMinimumWidth(column, minSize); + invalidate(); +} + +/*! + Returns the column spacing for column \a column. + + \sa setColumnMinimumWidth() +*/ +int QGridLayout::columnMinimumWidth(int column) const +{ + Q_D(const QGridLayout); + return d->colSpacing(column); +} + +/*! + \reimp +*/ +Qt::Orientations QGridLayout::expandingDirections() const +{ + Q_D(const QGridLayout); + return d->expandingDirections(horizontalSpacing(), verticalSpacing()); +} + +/*! + Sets the grid's origin corner, i.e. position (0, 0), to \a corner. +*/ +void QGridLayout::setOriginCorner(Qt::Corner corner) +{ + Q_D(QGridLayout); + d->setReversed(corner == Qt::BottomLeftCorner || corner == Qt::BottomRightCorner, + corner == Qt::TopRightCorner || corner == Qt::BottomRightCorner); +} + +/*! + Returns the corner that's used for the grid's origin, i.e. for + position (0, 0). +*/ +Qt::Corner QGridLayout::originCorner() const +{ + Q_D(const QGridLayout); + if (d->horReversed()) { + return d->verReversed() ? Qt::BottomRightCorner : Qt::TopRightCorner; + } else { + return d->verReversed() ? Qt::BottomLeftCorner : Qt::TopLeftCorner; + } +} + +/*! + \reimp +*/ +void QGridLayout::invalidate() +{ + Q_D(QGridLayout); + d->setDirty(); + QLayout::invalidate(); +} + +/*! + \fn void QGridLayout::addRowSpacing(int row, int minsize) + + Use addItem(new QSpacerItem(0, minsize), row, 0) instead. +*/ + +/*! + \fn void QGridLayout::addColSpacing(int col, int minsize) + + Use addItem(new QSpacerItem(minsize, 0), 0, col) instead. +*/ + +/*! + \fn void QGridLayout::addMultiCellWidget(QWidget *widget, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0) + + Use an addWidget() overload that allows you to specify row and + column spans instead. +*/ + +/*! + \fn void QGridLayout::addMultiCell(QLayoutItem *l, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0) + + Use an addItem() overload that allows you to specify row and + column spans instead. +*/ + +/*! + \fn void QGridLayout::addMultiCellLayout(QLayout *layout, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment align = 0) + + Use an addLayout() overload that allows you to specify row and + column spans instead. +*/ + +/*! + \fn int QGridLayout::numRows() const + + Use rowCount() instead. +*/ + +/*! + \fn int QGridLayout::numCols() const + + Use columnCount() instead. +*/ + +/*! + \fn void QGridLayout::setColStretch(int col, int stretch) + + Use setColumnStretch() instead. +*/ + +/*! + \fn int QGridLayout::colStretch(int col) const + + Use columnStretch() instead. +*/ + +/*! + \fn void QGridLayout::setColSpacing(int col, int minSize) + + Use setColumnMinimumWidth() instead. +*/ + +/*! + \fn int QGridLayout::colSpacing(int col) const + + Use columnMinimumWidth() instead. +*/ + +/*! + \fn void QGridLayout::setRowSpacing(int row, int minSize) + + Use setRowMinimumHeight(\a row, \a minSize) instead. +*/ + +/*! + \fn int QGridLayout::rowSpacing(int row) const + + Use rowMinimumHeight(\a row) instead. +*/ + +/*! + \fn QRect QGridLayout::cellGeometry(int row, int column) const + + Use cellRect(\a row, \a column) instead. +*/ + +/*! + \fn void QGridLayout::setOrigin(Qt::Corner corner) + + Use setOriginCorner(\a corner) instead. +*/ + +/*! + \fn Qt::Corner QGridLayout::origin() const + + Use originCorner() instead. +*/ + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qgridlayout.h b/src/gui/kernel/qgridlayout.h new file mode 100644 index 0000000000..0ac66e8f87 --- /dev/null +++ b/src/gui/kernel/qgridlayout.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRIDLAYOUT_H +#define QGRIDLAYOUT_H + +#include <QtGui/qlayout.h> +#ifdef QT_INCLUDE_COMPAT +#include <QtGui/qwidget.h> +#endif + +#include <limits.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGridLayoutPrivate; + +class Q_GUI_EXPORT QGridLayout : public QLayout +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGridLayout) + QDOC_PROPERTY(int horizontalSpacing READ horizontalSpacing WRITE setHorizontalSpacing) + QDOC_PROPERTY(int verticalSpacing READ verticalSpacing WRITE setVerticalSpacing) + +public: + explicit QGridLayout(QWidget *parent); + QGridLayout(); + +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QGridLayout(QWidget *parent, int nRows , int nCols = 1, int border = 0, + int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QGridLayout(int nRows , int nCols = 1, int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QGridLayout(QLayout *parentLayout, int nRows = 1, int nCols = 1, int spacing = -1, + const char *name = 0); +#endif + ~QGridLayout(); + + QSize sizeHint() const; + QSize minimumSize() const; + QSize maximumSize() const; + + void setHorizontalSpacing(int spacing); + int horizontalSpacing() const; + void setVerticalSpacing(int spacing); + int verticalSpacing() const; + void setSpacing(int spacing); + int spacing() const; + + void setRowStretch(int row, int stretch); + void setColumnStretch(int column, int stretch); + int rowStretch(int row) const; + int columnStretch(int column) const; + + void setRowMinimumHeight(int row, int minSize); + void setColumnMinimumWidth(int column, int minSize); + int rowMinimumHeight(int row) const; + int columnMinimumWidth(int column) const; + + int columnCount() const; + int rowCount() const; + + QRect cellRect(int row, int column) const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QRect cellGeometry(int row, int column) const {return cellRect(row, column);} +#endif + + bool hasHeightForWidth() const; + int heightForWidth(int) const; + int minimumHeightForWidth(int) const; + + Qt::Orientations expandingDirections() const; + void invalidate(); + + inline void addWidget(QWidget *w) { QLayout::addWidget(w); } + void addWidget(QWidget *, int row, int column, Qt::Alignment = 0); + void addWidget(QWidget *, int row, int column, int rowSpan, int columnSpan, Qt::Alignment = 0); + void addLayout(QLayout *, int row, int column, Qt::Alignment = 0); + void addLayout(QLayout *, int row, int column, int rowSpan, int columnSpan, Qt::Alignment = 0); + + void setOriginCorner(Qt::Corner); + Qt::Corner originCorner() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void setOrigin(Qt::Corner corner) { setOriginCorner(corner); } + inline QT3_SUPPORT Qt::Corner origin() const { return originCorner(); } +#endif + QLayoutItem *itemAt(int index) const; + QLayoutItem *itemAtPosition(int row, int column) const; + QLayoutItem *takeAt(int index); + int count() const; + void setGeometry(const QRect&); + + void addItem(QLayoutItem *item, int row, int column, int rowSpan = 1, int columnSpan = 1, Qt::Alignment = 0); + + void setDefaultPositioning(int n, Qt::Orientation orient); + void getItemPosition(int idx, int *row, int *column, int *rowSpan, int *columnSpan); + +protected: +#ifdef QT3_SUPPORT + QT3_SUPPORT bool findWidget(QWidget* w, int *r, int *c); +#endif + void addItem(QLayoutItem *); + +private: + Q_DISABLE_COPY(QGridLayout) + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT void expand(int rows, int cols); + inline QT3_SUPPORT void addRowSpacing(int row, int minsize) { addItem(new QSpacerItem(0,minsize), row, 0); } + inline QT3_SUPPORT void addColSpacing(int col, int minsize) { addItem(new QSpacerItem(minsize,0), 0, col); } + inline QT3_SUPPORT void addMultiCellWidget(QWidget *w, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment _align = 0) + { addWidget(w, fromRow, fromCol, (toRow < 0) ? -1 : toRow - fromRow + 1, (toCol < 0) ? -1 : toCol - fromCol + 1, _align); } + inline QT3_SUPPORT void addMultiCell(QLayoutItem *l, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment _align = 0) + { addItem(l, fromRow, fromCol, (toRow < 0) ? -1 : toRow - fromRow + 1, (toCol < 0) ? -1 : toCol - fromCol + 1, _align); } + inline QT3_SUPPORT void addMultiCellLayout(QLayout *layout, int fromRow, int toRow, int fromCol, int toCol, Qt::Alignment _align = 0) + { addLayout(layout, fromRow, fromCol, (toRow < 0) ? -1 : toRow - fromRow + 1, (toCol < 0) ? -1 : toCol - fromCol + 1, _align); } + + inline QT3_SUPPORT int numRows() const { return rowCount(); } + inline QT3_SUPPORT int numCols() const { return columnCount(); } + inline QT3_SUPPORT void setColStretch(int col, int stretch) {setColumnStretch(col, stretch); } + inline QT3_SUPPORT int colStretch(int col) const {return columnStretch(col); } + inline QT3_SUPPORT void setColSpacing(int col, int minSize) { setColumnMinimumWidth(col, minSize); } + inline QT3_SUPPORT int colSpacing(int col) const { return columnMinimumWidth(col); } + inline QT3_SUPPORT void setRowSpacing(int row, int minSize) {setRowMinimumHeight(row, minSize); } + inline QT3_SUPPORT int rowSpacing(int row) const {return rowMinimumHeight(row); } +#endif +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRIDLAYOUT_H diff --git a/src/gui/kernel/qguieventdispatcher_glib.cpp b/src/gui/kernel/qguieventdispatcher_glib.cpp new file mode 100644 index 0000000000..3fa10eb7e9 --- /dev/null +++ b/src/gui/kernel/qguieventdispatcher_glib.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qguieventdispatcher_glib_p.h" + +#include "qapplication.h" +#include "qx11info_x11.h" + +#include "qt_x11_p.h" + +#include <glib.h> + +QT_BEGIN_NAMESPACE + +struct GX11EventSource +{ + GSource source; + GPollFD pollfd; + QEventLoop::ProcessEventsFlags flags; + QGuiEventDispatcherGlib *q; + QGuiEventDispatcherGlibPrivate *d; +}; + +class QGuiEventDispatcherGlibPrivate : public QEventDispatcherGlibPrivate +{ + Q_DECLARE_PUBLIC(QGuiEventDispatcherGlib) + +public: + QGuiEventDispatcherGlibPrivate(); + GX11EventSource *x11EventSource; + QList<XEvent> queuedUserInputEvents; +}; + +static gboolean x11EventSourcePrepare(GSource *s, gint *timeout) +{ + if (timeout) + *timeout = -1; + GX11EventSource *source = reinterpret_cast<GX11EventSource *>(s); + return (XEventsQueued(X11->display, QueuedAfterFlush) + || (!(source->flags & QEventLoop::ExcludeUserInputEvents) + && !source->d->queuedUserInputEvents.isEmpty())); +} + +static gboolean x11EventSourceCheck(GSource *s) +{ + GX11EventSource *source = reinterpret_cast<GX11EventSource *>(s); + return (XEventsQueued(X11->display, QueuedAfterFlush) + || (!(source->flags & QEventLoop::ExcludeUserInputEvents) + && !source->d->queuedUserInputEvents.isEmpty())); +} + +static gboolean x11EventSourceDispatch(GSource *s, GSourceFunc callback, gpointer user_data) +{ + GX11EventSource *source = reinterpret_cast<GX11EventSource *>(s); + + ulong marker = XNextRequest(X11->display); + do { + XEvent event; + if (!(source->flags & QEventLoop::ExcludeUserInputEvents) + && !source->d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = source->d->queuedUserInputEvents.takeFirst(); + } else if (XEventsQueued(X11->display, QueuedAlready)) { + // process events from the X server + XNextEvent(X11->display, &event); + + if (source->flags & QEventLoop::ExcludeUserInputEvents) { + // queue user input events + switch (event.type) { + case ButtonPress: + case ButtonRelease: + case MotionNotify: + case XKeyPress: + case XKeyRelease: + case EnterNotify: + case LeaveNotify: + source->d->queuedUserInputEvents.append(event); + continue; + + case ClientMessage: + // only keep the wm_take_focus and + // _qt_scrolldone protocols, queue all other + // client messages + if (event.xclient.format == 32) { + if (event.xclient.message_type == ATOM(WM_PROTOCOLS) && + (Atom) event.xclient.data.l[0] == ATOM(WM_TAKE_FOCUS)) { + break; + } else if (event.xclient.message_type == ATOM(_QT_SCROLL_DONE)) { + break; + } + } + source->d->queuedUserInputEvents.append(event); + continue; + + default: + break; + } + } + } else { + // no event to process + break; + } + + // send through event filter + if (source->q->filterEvent(&event)) + continue; + + if (qApp->x11ProcessEvent(&event) == 1) + return true; + + if (event.xany.serial >= marker) + goto out; + } while (XEventsQueued(X11->display, QueuedAfterFlush)); + + out: + + source->d->runTimersOnceWithNormalPriority(); + + if (callback) + callback(user_data); + return true; +} + +static GSourceFuncs x11EventSourceFuncs = { + x11EventSourcePrepare, + x11EventSourceCheck, + x11EventSourceDispatch, + NULL, + NULL, + NULL +}; + +QGuiEventDispatcherGlibPrivate::QGuiEventDispatcherGlibPrivate() +{ + x11EventSource = reinterpret_cast<GX11EventSource *>(g_source_new(&x11EventSourceFuncs, + sizeof(GX11EventSource))); + g_source_set_can_recurse(&x11EventSource->source, true); + + memset(&x11EventSource->pollfd, 0, sizeof(GPollFD)); + x11EventSource->flags = QEventLoop::AllEvents; + x11EventSource->q = 0; + x11EventSource->d = 0; + + g_source_attach(&x11EventSource->source, mainContext); +} + +QGuiEventDispatcherGlib::QGuiEventDispatcherGlib(QObject *parent) + : QEventDispatcherGlib(*new QGuiEventDispatcherGlibPrivate, parent) +{ +} + +QGuiEventDispatcherGlib::~QGuiEventDispatcherGlib() +{ + Q_D(QGuiEventDispatcherGlib); + + g_source_remove_poll(&d->x11EventSource->source, &d->x11EventSource->pollfd); + g_source_destroy(&d->x11EventSource->source); + d->x11EventSource = 0; +} + +bool QGuiEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QGuiEventDispatcherGlib); + QEventLoop::ProcessEventsFlags saved_flags = d->x11EventSource->flags; + d->x11EventSource->flags = flags; + bool returnValue = QEventDispatcherGlib::processEvents(flags); + d->x11EventSource->flags = saved_flags; + return returnValue; +} + +void QGuiEventDispatcherGlib::startingUp() +{ + Q_D(QGuiEventDispatcherGlib); + d->x11EventSource->pollfd.fd = XConnectionNumber(X11->display); + d->x11EventSource->pollfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; + d->x11EventSource->q = this; + d->x11EventSource->d = d; + g_source_add_poll(&d->x11EventSource->source, &d->x11EventSource->pollfd); +} + +void QGuiEventDispatcherGlib::flush() +{ + XFlush(X11->display); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qguieventdispatcher_glib_p.h b/src/gui/kernel/qguieventdispatcher_glib_p.h new file mode 100644 index 0000000000..d37db93679 --- /dev/null +++ b/src/gui/kernel/qguieventdispatcher_glib_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGUIEVENTDISPATCHER_GLIB_P_H +#define QGUIEVENTDISPATCHER_GLIB_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qeventdispatcher_glib_p.h> + +QT_BEGIN_NAMESPACE + +class QGuiEventDispatcherGlibPrivate; + +class QGuiEventDispatcherGlib : public QEventDispatcherGlib +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGuiEventDispatcherGlib) + +public: + explicit QGuiEventDispatcherGlib(QObject *parent = 0); + ~QGuiEventDispatcherGlib(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + + void startingUp(); + void flush(); +}; + +QT_END_NAMESPACE + +#endif // QGUIEVENTDISPATCHER_GLIB_P_H diff --git a/src/gui/kernel/qguifunctions_wince.cpp b/src/gui/kernel/qguifunctions_wince.cpp new file mode 100644 index 0000000000..bb4ed11589 --- /dev/null +++ b/src/gui/kernel/qguifunctions_wince.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qguifunctions_wince.h" +#include <shellapi.h> +#include <QtCore/qlibrary.h> + +QT_USE_NAMESPACE + +struct AygSHINITDLGINFO +{ + DWORD dwMask; + HWND hDlg; + DWORD dwFlags; +}; + +struct AygSIPINFO +{ + DWORD cbSize; + DWORD fdwFlags; + RECT rcVisibleDesktop; + RECT rcSipRect; + DWORD dwImDataSize; + void *pvImData; +}; + +#ifndef SHIDIF_CANCELBUTTON +#define SHIDIF_CANCELBUTTON 0x0080 +#endif + +#ifndef SHIDIM_FLAGS +#define SHIDIM_FLAGS 0x0001 +#endif + +#ifndef SHIDIF_DONEBUTTON +#define SHIDIF_DONEBUTTON 0x0001 +#endif +#ifndef SHIDIF_SIZEDLGFULLSCREEN +#define SHIDIF_SIZEDLGFULLSCREEN 0x0004 +#endif + +#ifndef SHDB_HIDE +#define SHDB_HIDE 0x0002 +#endif + +#ifndef SHFS_SHOWTASKBAR +#define SHFS_SHOWTASKBAR 0x0001 +#endif +#ifndef SHFS_HIDETASKBAR +#define SHFS_HIDETASKBAR 0x0002 +#endif +#ifndef SHFS_SHOWSIPBUTTON +#define SHFS_SHOWSIPBUTTON 0x0004 +#endif +#ifndef SHFS_HIDESIPBUTTON +#define SHFS_HIDESIPBUTTON 0x0008 +#endif +#ifndef SHFS_SHOWSTARTICON +#define SHFS_SHOWSTARTICON 0x0010 +#endif +#ifndef SHFS_HIDESTARTICON +#define SHFS_HIDESTARTICON 0x0020 +#endif + +#ifndef SIPF_OFF +#define SIPF_OFF 0x00000000 +#endif +#ifndef SIPF_ON +#define SIPF_ON 0x00000001 +#endif + +#ifndef SPI_SETSIPINFO +#define SPI_SETSIPINFO 224 +#endif +#ifndef SPI_GETSIPINFO +#define SPI_GETSIPINFO 225 +#endif +#ifndef SPI_GETPLATFORMTYPE +#define SPI_GETPLATFORMTYPE 257 +#endif + +typedef BOOL (*AygInitDialog)(AygSHINITDLGINFO*); +typedef BOOL (*AygFullScreen)(HWND, DWORD); +typedef BOOL (*AygSHSipInfo)(UINT, UINT, PVOID, UINT); +typedef BOOL (*AygSHDoneButton)(HWND, DWORD); + +static AygInitDialog ptrAygInitDialog = 0; +static AygFullScreen ptrAygFullScreen = 0; +static AygSHSipInfo ptrAygSHSipInfo = 0; +static AygSHDoneButton ptrAygSHDoneButton = 0; +static bool aygResolved = false; + +static void resolveAygLibs() +{ + if (!aygResolved) { + aygResolved = true; + QLibrary ayglib(QLatin1String("aygshell")); + ptrAygInitDialog = (AygInitDialog) ayglib.resolve("SHInitDialog"); + ptrAygFullScreen = (AygFullScreen) ayglib.resolve("SHFullScreen"); + ptrAygSHSipInfo = (AygSHSipInfo) ayglib.resolve("SHSipInfo"); + ptrAygSHDoneButton = (AygSHDoneButton) ayglib.resolve("SHDoneButton"); + } +} + +int qt_wince_GetDIBits(HDC /*hdc*/ , HBITMAP hSourceBitmap, uint, uint, LPVOID lpvBits, LPBITMAPINFO, uint) +{ + if (!lpvBits) { + qWarning("::GetDIBits(), lpvBits NULL"); + return 0; + } + BITMAP bm; + GetObject(hSourceBitmap, sizeof(BITMAP), &bm); + bm.bmHeight = qAbs(bm.bmHeight); + + HBITMAP hTargetBitmap; + void *pixels; + + BITMAPINFO dibInfo; + memset(&dibInfo, 0, sizeof(dibInfo)); + dibInfo.bmiHeader.biBitCount = 32; + dibInfo.bmiHeader.biClrImportant = 0; + dibInfo.bmiHeader.biClrUsed = 0; + dibInfo.bmiHeader.biCompression = BI_RGB;; + dibInfo.bmiHeader.biHeight = -bm.bmHeight; + dibInfo.bmiHeader.biWidth = bm.bmWidth; + dibInfo.bmiHeader.biPlanes = 1; + dibInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + dibInfo.bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * 4; + + HDC displayDC = GetDC(NULL); + if (!displayDC) { + qWarning("::GetDIBits(), failed to GetDC"); + return 0; + } + + int ret = bm.bmHeight; + + hTargetBitmap = CreateDIBSection(displayDC, (const BITMAPINFO*) &dibInfo, DIB_RGB_COLORS, + (void**)&pixels, NULL, 0); + if (!hTargetBitmap) { + qWarning("::GetDIBits(), failed to CreateDIBSection"); + return 0; + } + + HDC hdcSrc = CreateCompatibleDC(displayDC); + HDC hdcDst = CreateCompatibleDC(displayDC); + + if (!(hdcDst && hdcSrc)) { + qWarning("::GetDIBits(), failed to CreateCompatibleDC"); + ret = 0; + } + + HBITMAP hOldBitmap1 = (HBITMAP) SelectObject(hdcSrc, hSourceBitmap); + HBITMAP hOldBitmap2 = (HBITMAP) SelectObject(hdcDst, hTargetBitmap); + + if (!(hOldBitmap1 && hOldBitmap2)) { + qWarning("::GetDIBits(), failed to SelectObject for bitmaps"); + ret = 0; + } + + if (!BitBlt(hdcDst, 0, 0, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY)) { + qWarning("::GetDIBits(), BitBlt failed"); + ret = 0; + } + + SelectObject(hdcSrc, hOldBitmap1); + SelectObject(hdcDst, hOldBitmap2); + + DeleteDC(hdcSrc); + DeleteDC(hdcDst); + + ReleaseDC(NULL, displayDC); + + memcpy(lpvBits, pixels, dibInfo.bmiHeader.biSizeImage); + + DeleteObject(hTargetBitmap); + return ret; +} + +HINSTANCE qt_wince_ShellExecute(HWND hwnd, LPCWSTR, LPCWSTR file, LPCWSTR params, LPCWSTR dir, int showCmd) +{ + SHELLEXECUTEINFO info; + info.hwnd = hwnd; + info.lpVerb = L"Open"; + info.lpFile = file; + info.lpParameters = params; + info.lpDirectory = dir; + info.nShow = showCmd; + info.cbSize = sizeof(info); + ShellExecuteEx(&info); + return info.hInstApp; +} + +// Clipboard -------------------------------------------------------- +BOOL qt_wince_ChangeClipboardChain( HWND /*hWndRemove*/, HWND /*hWndNewNext*/ ) +{ + return FALSE; +} + +HWND qt_wince_SetClipboardViewer( HWND /*hWndNewViewer*/ ) +{ + return NULL; +} + + +// Graphics --------------------------------------------------------- +COLORREF qt_wince_PALETTEINDEX( WORD /*wPaletteIndex*/) +{ + return 0; +} + +// Internal Qt ----------------------------------------------------- +bool qt_wince_is_platform(const QString &platformString) { + wchar_t tszPlatform[64]; + if (SystemParametersInfo(SPI_GETPLATFORMTYPE, sizeof(tszPlatform) / sizeof(wchar_t), tszPlatform, 0)) + if (0 == _tcsicmp(reinterpret_cast<const wchar_t *> (platformString.utf16()), tszPlatform)) + return true; + return false; +} + +int qt_wince_get_build() +{ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (GetVersionEx(&osvi)) { + return osvi.dwBuildNumber; + } + return 0; +} + +int qt_wince_get_version() +{ + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (GetVersionEx(&osvi)) { + return (osvi.dwMajorVersion * 10 + osvi.dwMinorVersion); + } + return 0; +} + +bool qt_wince_is_windows_mobile_65() +{ + const DWORD dwFirstWM65BuildNumber = 21139; + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (!GetVersionEx(&osvi)) + return false; + return osvi.dwMajorVersion > 5 + || (osvi.dwMajorVersion == 5 && (osvi.dwMinorVersion > 2 || + (osvi.dwMinorVersion == 2 && osvi.dwBuildNumber >= dwFirstWM65BuildNumber))); +} + +bool qt_wince_is_pocket_pc() { + return qt_wince_is_platform(QString::fromLatin1("PocketPC")); +} + +bool qt_wince_is_smartphone() { + return qt_wince_is_platform(QString::fromLatin1("Smartphone")); +} +bool qt_wince_is_mobile() { + return (qt_wince_is_smartphone() || qt_wince_is_pocket_pc()); +} + +bool qt_wince_is_high_dpi() { + if (!qt_wince_is_pocket_pc()) + return false; + HDC deviceContext = GetDC(0); + int dpi = GetDeviceCaps(deviceContext, LOGPIXELSX); + ReleaseDC(0, deviceContext); + if ((dpi < 1000) && (dpi > 0)) + return dpi > 96; + else + return false; +} + +void qt_wince_maximize(QWidget *widget) +{ + HWND hwnd = widget->winId(); + if (qt_wince_is_mobile()) { + AygSHINITDLGINFO shidi; + shidi.dwMask = SHIDIM_FLAGS; + shidi.hDlg = hwnd; + shidi.dwFlags = SHIDIF_SIZEDLGFULLSCREEN; + if (widget->windowFlags() & Qt::WindowCancelButtonHint) + shidi.dwFlags |= SHIDIF_CANCELBUTTON; + if (widget->windowFlags() & Qt::WindowOkButtonHint) + shidi.dwFlags |= SHIDIF_DONEBUTTON; + if (!(widget->windowFlags() & (Qt::WindowCancelButtonHint | Qt::WindowOkButtonHint))) + shidi.dwFlags |= SHIDIF_CANCELBUTTON; + resolveAygLibs(); + if (ptrAygInitDialog) + ptrAygInitDialog(&shidi); + } else { + RECT r; + SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0); + MoveWindow(hwnd, r.top, r.left, r.right - r.left, r.bottom - r.top, true); + SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) | WS_EX_NODRAG); + } +} + +void qt_wince_unmaximize(QWidget *widget) +{ + if (ptrAygSHDoneButton && qt_wince_is_mobile() + && !(widget->windowFlags() & (Qt::WindowCancelButtonHint | Qt::WindowOkButtonHint))) + { + // Hide the [X] button, we've added in qt_wince_maximize. + ptrAygSHDoneButton(widget->winId(), SHDB_HIDE); + } +} + +void qt_wince_minimize(HWND hwnd) +{ +#ifdef Q_OS_WINCE_WM + ShowWindow(hwnd, SW_HIDE); +#else + if (!IsWindowVisible(hwnd)) { + // Hack for an initial showMinimized. + // Without it, our widget doesn't appear in the task bar. + ShowWindow(hwnd, SW_SHOW); + } + ShowWindow(hwnd, SW_MINIMIZE); +#endif +} + +void qt_wince_hide_taskbar(HWND hwnd) { + if (ptrAygFullScreen) + ptrAygFullScreen(hwnd, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON | SHFS_HIDESTARTICON); +} + +void qt_wince_full_screen(HWND hwnd, bool fullScreen, UINT swpf) { + resolveAygLibs(); + if (fullScreen) { + QRect r = qApp->desktop()->screenGeometry(QWidget::find(hwnd)); + SetWindowPos(hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + if (ptrAygFullScreen) + ptrAygFullScreen(hwnd, SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON | SHFS_HIDESTARTICON); + if (!qt_wince_is_mobile()) { + HWND handle = FindWindow(L"HHTaskBar", L""); + if (handle) { + ShowWindow(handle, 0); + EnableWindow(handle, false); + } + } + } else { + if (ptrAygFullScreen) + ptrAygFullScreen(hwnd, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON | SHFS_SHOWSTARTICON); + SetWindowPos(hwnd, 0, 0, 0, 0, 0, swpf); + if (!qt_wince_is_mobile()) { + HWND handle = FindWindow(L"HHTaskBar", L""); + if (handle) { + ShowWindow(handle, 1); + EnableWindow(handle, true); + } + } + } +} + +void qt_wince_show_SIP(bool show) +{ + resolveAygLibs(); + if (!ptrAygSHSipInfo) + return; + + AygSIPINFO si; + memset(&si, 0, sizeof(si)); + si.cbSize = sizeof(si); + ptrAygSHSipInfo(SPI_GETSIPINFO, 0, &si, 0); + si.cbSize = sizeof(si); + si.fdwFlags = (show ? SIPF_ON : SIPF_OFF); + ptrAygSHSipInfo(SPI_SETSIPINFO, 0, &si, 0); +} diff --git a/src/gui/kernel/qguifunctions_wince.h b/src/gui/kernel/qguifunctions_wince.h new file mode 100644 index 0000000000..2e14de0693 --- /dev/null +++ b/src/gui/kernel/qguifunctions_wince.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGUIFUNCTIONS_WCE_H +#define QGUIFUNCTIONS_WCE_H +#ifdef Q_OS_WINCE +#include <QtCore/qfunctions_wince.h> +#define UNDER_NT +#include <wingdi.h> + +#ifdef QT_BUILD_GUI_LIB +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE +QT_MODULE(Gui) +QT_END_NAMESPACE +QT_END_HEADER +#endif + +// application defines +#define SPI_SETNONCLIENTMETRICS 72 +#define SPI_SETICONTITLELOGFONT 0x0022 +#define WM_ACTIVATEAPP 0x001c +#define SW_PARENTCLOSING 1 +#define SW_OTHERMAXIMIZED 2 +#define SW_PARENTOPENING 3 +#define SW_OTHERRESTORED 4 +#define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) + +// drag n drop +#ifndef CFSTR_PERFORMEDDROPEFFECT +#define CFSTR_PERFORMEDDROPEFFECT TEXT("Performed DropEffect") +#endif +int qt_wince_GetDIBits(HDC, HBITMAP, uint, uint, void*, LPBITMAPINFO, uint); +#define GetDIBits(a,b,c,d,e,f,g) qt_wince_GetDIBits(a,b,c,d,e,f,g) + +// QWidget +#define SW_SHOWMINIMIZED SW_MINIMIZE + +// QRegion +#define ALTERNATE 0 +#define WINDING 1 + +// QFontEngine +typedef struct _FIXED { + WORD fract; + short value; +} FIXED; + +typedef struct tagPOINTFX { + FIXED x; + FIXED y; +} POINTFX; + +typedef struct _MAT2 { + FIXED eM11; + FIXED eM12; + FIXED eM21; + FIXED eM22; +} MAT2; + +typedef struct _GLYPHMETRICS { + UINT gmBlackBoxX; + UINT gmBlackBoxY; + POINT gmptGlyphOrigin; + short gmCellIncX; + short gmCellIncY; +} GLYPHMETRICS; + +typedef struct tagTTPOLYGONHEADER +{ + DWORD cb; + DWORD dwType; + POINTFX pfxStart; +} TTPOLYGONHEADER; + +typedef struct tagTTPOLYCURVE +{ + WORD wType; + WORD cpfx; + POINTFX apfx[1]; +} TTPOLYCURVE; + +#define GGO_NATIVE 2 +#define GGO_GLYPH_INDEX 0x0080 +#define TT_PRIM_LINE 1 +#define TT_PRIM_QSPLINE 2 +#define TT_PRIM_CSPLINE 3 +#define ANSI_VAR_FONT 12 + +HINSTANCE qt_wince_ShellExecute(HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR params, LPCWSTR dir, int showCmd); +#define ShellExecute(a,b,c,d,e,f) qt_wince_ShellExecute(a,b,c,d,e,f) + + +// Clipboard -------------------------------------------------------- +#define WM_CHANGECBCHAIN 1 +#define WM_DRAWCLIPBOARD 2 + +BOOL qt_wince_ChangeClipboardChain( + HWND hWndRemove, // handle to window to remove + HWND hWndNewNext // handle to next window +); +#define ChangeClipboardChain(a,b) qt_wince_ChangeClipboardChain(a,b); + +HWND qt_wince_SetClipboardViewer( + HWND hWndNewViewer // handle to clipboard viewer window +); +#define SetClipboardViewer(a) qt_wince_SetClipboardViewer(a) + +// Graphics --------------------------------------------------------- +COLORREF qt_wince_PALETTEINDEX( WORD wPaletteIndex ); +#define PALETTEINDEX(a) qt_wince_PALETTEINDEX(a) + +#endif // Q_OS_WINCE +#endif // QGUIFUNCTIONS_WCE_H diff --git a/src/gui/kernel/qguiplatformplugin.cpp b/src/gui/kernel/qguiplatformplugin.cpp new file mode 100644 index 0000000000..011b816e77 --- /dev/null +++ b/src/gui/kernel/qguiplatformplugin.cpp @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qguiplatformplugin_p.h" +#include <qdebug.h> +#include <qfile.h> +#include <qdir.h> +#include <qsettings.h> +#include "private/qfactoryloader_p.h" +#include "qstylefactory.h" +#include "qapplication.h" +#include "qplatformdefs.h" +#include "qicon.h" + +#ifdef Q_WS_WINCE +#include "qguifunctions_wince.h" +extern bool qt_wince_is_smartphone(); //qguifunctions_wince.cpp +extern bool qt_wince_is_mobile(); //qguifunctions_wince.cpp +extern bool qt_wince_is_pocket_pc(); //qguifunctions_wince.cpp +#endif + + +#if defined(Q_WS_X11) +#include <private/qkde_p.h> +#include <private/qgtkstyle_p.h> +#include <private/qt_x11_p.h> +#endif + + +QT_BEGIN_NAMESPACE + + +/*! \internal + Return (an construct if necesseray) the Gui Platform plugin. + + The plugin key to be loaded is inside the QT_PLATFORM_PLUGIN environment variable. + If it is not set, it will be the DESKTOP_SESSION on X11. + + If no plugin can be loaded, the default one is returned. + */ +QGuiPlatformPlugin *qt_guiPlatformPlugin() +{ + static QGuiPlatformPlugin *plugin; + if (!plugin) + { +#ifndef QT_NO_LIBRARY + + QString key = QString::fromLocal8Bit(qgetenv("QT_PLATFORM_PLUGIN")); +#ifdef Q_WS_X11 + if (key.isEmpty()) { + switch(X11->desktopEnvironment) { + case DE_KDE: + key = QString::fromLatin1("kde"); + break; + default: + key = QString::fromLocal8Bit(qgetenv("DESKTOP_SESSION")); + break; + } + } +#endif + + if (!key.isEmpty() && QApplication::desktopSettingsAware()) { + QFactoryLoader loader(QGuiPlatformPluginInterface_iid, QLatin1String("/gui_platform")); + plugin = qobject_cast<QGuiPlatformPlugin *>(loader.instance(key)); + } +#endif // QT_NO_LIBRARY + + if(!plugin) { + static QGuiPlatformPlugin def; + plugin = &def; + } + } + return plugin; +} + + +/* \class QPlatformPlugin + QGuiPlatformPlugin can be used to integrate Qt applications in a platform built on top of Qt. + The application developer should not know or use the plugin, it is only used by Qt internaly. + + But full platform that are built on top of Qt may provide a plugin so 3rd party Qt application + running in the platform are integrated. + */ + +/* + The constructor can be used to install hooks in Qt + */ +QGuiPlatformPlugin::QGuiPlatformPlugin(QObject *parent) : QObject(parent) {} +QGuiPlatformPlugin::~QGuiPlatformPlugin() {} + + +/* return the string key to be used by default the application */ +QString QGuiPlatformPlugin::styleName() +{ +#if defined(Q_WS_WIN) && defined(Q_WS_WINCE) + if (qt_wince_is_smartphone() || qt_wince_is_pocket_pc()) + return QLatin1String("WindowsMobile"); + else + return QLatin1String("WindowsCE"); +#elif defined(Q_WS_WIN) + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + return QLatin1String("WindowsVista"); + else if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + return QLatin1String("WindowsXP"); + else + return QLatin1String("Windows"); // default styles for Windows +#elif defined(Q_WS_X11) && defined(Q_OS_SOLARIS) + return QLatin1String("CDE"); // default style for X11 on Solaris +#elif defined(Q_WS_S60) + return QLatin1String("S60"); // default style for Symbian with S60 +#elif defined(Q_OS_SYMBIAN) + return QLatin1String("Windows"); // default style for Symbian without S60 +#elif defined(Q_WS_X11) && defined(Q_OS_IRIX) + return QLatin1String("SGI"); // default style for X11 on IRIX +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) + return QLatin1String("Plastique"); // default style for X11 and small devices +#elif defined(Q_WS_MAC) + return QLatin1String("Macintosh"); // default style for all Mac's +#elif defined(Q_WS_X11) + QString stylename; + switch(X11->desktopEnvironment) { + case DE_KDE: + stylename = QKde::kdeStyle(); + break; + case DE_GNOME: { + QStringList availableStyles = QStyleFactory::keys(); + // Set QGtkStyle for GNOME if available + QString gtkStyleKey = QString::fromLatin1("GTK+"); + if (availableStyles.contains(gtkStyleKey)) { + stylename = gtkStyleKey; + break; + } + if (X11->use_xrender) + stylename = QLatin1String("cleanlooks"); + else + stylename = QLatin1String("windows"); + break; + } + case DE_CDE: + stylename = QLatin1String("cde"); + break; + default: + // Don't do anything + break; + } + return stylename; +#endif +} + +/* return an additional default palette (only work on X11) */ +QPalette QGuiPlatformPlugin::palette() +{ +#ifdef Q_WS_X11 + if (QApplication::desktopSettingsAware() && X11->desktopEnvironment == DE_KDE) + return QKde::kdePalette(); +#endif + + return QPalette(); +} + +/* the default icon theme name for QIcon::fromTheme. */ +QString QGuiPlatformPlugin::systemIconThemeName() +{ + QString result; +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_GNOME) { + result = QString::fromLatin1("gnome"); +#ifndef QT_NO_STYLE_GTK + result = QGtkStylePrivate::getGConfString(QLatin1String("/desktop/gnome/interface/icon_theme"), result); +#endif + } else if (X11->desktopEnvironment == DE_KDE) { + result = X11->desktopVersion >= 4 ? QString::fromLatin1("oxygen") : QString::fromLatin1("crystalsvg"); + QSettings settings(QKde::kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat); + settings.beginGroup(QLatin1String("Icons")); + result = settings.value(QLatin1String("Theme"), result).toString(); + } +#endif + return result; +} + + +QStringList QGuiPlatformPlugin::iconThemeSearchPaths() +{ + QStringList paths; +#if defined(Q_WS_X11) + QString xdgDirString = QFile::decodeName(getenv("XDG_DATA_DIRS")); + if (xdgDirString.isEmpty()) + xdgDirString = QLatin1String("/usr/local/share/:/usr/share/"); + + QStringList xdgDirs = xdgDirString.split(QLatin1Char(':')); + + for (int i = 0 ; i < xdgDirs.size() ; ++i) { + QDir dir(xdgDirs[i]); + if (dir.exists()) + paths.append(dir.path() + QLatin1String("/icons")); + } + if (X11->desktopEnvironment == DE_KDE) { + paths << QLatin1Char(':') + QKde::kdeHome() + QLatin1String("/share/icons"); + QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':')); + for (int i = 0 ; i< kdeDirs.count() ; ++i) { + QDir dir(QLatin1Char(':') + kdeDirs.at(i) + QLatin1String("/share/icons")); + if (dir.exists()) + paths.append(dir.path()); + } + } + + // Add home directory first in search path + QDir homeDir(QDir::homePath() + QLatin1String("/.icons")); + if (homeDir.exists()) + paths.prepend(homeDir.path()); +#endif + +#if defined(Q_WS_WIN) + paths.append(qApp->applicationDirPath() + QLatin1String("/icons")); +#elif defined(Q_WS_MAC) + paths.append(qApp->applicationDirPath() + QLatin1String("/../Resources/icons")); +#endif + return paths; +} + +/* backend for QFileIconProvider, null icon means default */ +QIcon QGuiPlatformPlugin::fileSystemIcon(const QFileInfo &) +{ + return QIcon(); +} + +/* Like QStyle::styleHint */ +int QGuiPlatformPlugin::platformHint(PlatformHint hint) +{ + int ret = 0; + switch(hint) + { + case PH_ToolButtonStyle: + ret = Qt::ToolButtonIconOnly; +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE && X11->desktopVersion >= 4 + && QApplication::desktopSettingsAware()) { + ret = QKde::kdeToolButtonStyle(); + } +#endif + break; + case PH_ToolBarIconSize: +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE && X11->desktopVersion >= 4 + && QApplication::desktopSettingsAware()) { + ret = QKde::kdeToolBarIconSize(); + } +#endif + //by default keep ret = 0 so QCommonStyle will use the style default + break; + default: + break; + } + return ret; +} + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qguiplatformplugin_p.h b/src/gui/kernel/qguiplatformplugin_p.h new file mode 100644 index 0000000000..49e2d9294a --- /dev/null +++ b/src/gui/kernel/qguiplatformplugin_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGUIPLATFORM_P_H +#define QGUIPLATFORM_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 <QtCore/qplugin.h> +#include <QtCore/qfactoryinterface.h> +#include <QtGui/qdialog.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyle; +class QPalette; +class QIcon; +class QFileDialog; +class QColorDialog; +class QFileInfo; + +struct Q_GUI_EXPORT QGuiPlatformPluginInterface : public QFactoryInterface +{ +}; + +#define QGuiPlatformPluginInterface_iid "com.nokia.qt.QGuiPlatformPluginInterface" + +Q_DECLARE_INTERFACE(QGuiPlatformPluginInterface, QGuiPlatformPluginInterface_iid) + +class Q_GUI_EXPORT QGuiPlatformPlugin : public QObject, public QGuiPlatformPluginInterface +{ + Q_OBJECT + Q_INTERFACES(QGuiPlatformPluginInterface:QFactoryInterface) + public: + explicit QGuiPlatformPlugin(QObject *parent = 0); + ~QGuiPlatformPlugin(); + + virtual QStringList keys() const { return QStringList() << QLatin1String("default"); }; + + virtual QString styleName(); + virtual QPalette palette(); + virtual QString systemIconThemeName(); + virtual QStringList iconThemeSearchPaths(); + virtual QIcon fileSystemIcon(const QFileInfo &); + + enum PlatformHint { PH_ToolButtonStyle, PH_ToolBarIconSize, PH_ItemView_ActivateItemOnSingleClick }; + virtual int platformHint(PlatformHint hint); + + + virtual void fileDialogDelete(QFileDialog *) {} + virtual bool fileDialogSetVisible(QFileDialog *, bool) { return false; } + virtual QDialog::DialogCode fileDialogResultCode(QFileDialog *) { return QDialog::Rejected; } + virtual void fileDialogSetDirectory(QFileDialog *, const QString &) {} + virtual QString fileDialogDirectory(const QFileDialog *) const { return QString(); } + virtual void fileDialogSelectFile(QFileDialog *, const QString &) {} + virtual QStringList fileDialogSelectedFiles(const QFileDialog *) const { return QStringList(); } + virtual void fileDialogSetFilter(QFileDialog *) {} + virtual void fileDialogSetNameFilters(QFileDialog *, const QStringList &) {} + virtual void fileDialogSelectNameFilter(QFileDialog *, const QString &) {} + virtual QString fileDialogSelectedNameFilter(const QFileDialog *) const { return QString(); } + + virtual void colorDialogDelete(QColorDialog *) {} + virtual bool colorDialogSetVisible(QColorDialog *, bool) { return false; } + virtual void colorDialogSetCurrentColor(QColorDialog *, const QColor &) {} +}; + +//internal +QGuiPlatformPlugin *qt_guiPlatformPlugin(); + +QT_END_NAMESPACE + +QT_END_HEADER + + +#endif // QGUIPLATFORMPLUGIN_H diff --git a/src/gui/kernel/qguivariant.cpp b/src/gui/kernel/qguivariant.cpp new file mode 100644 index 0000000000..badff80834 --- /dev/null +++ b/src/gui/kernel/qguivariant.cpp @@ -0,0 +1,828 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvariant.h" + +#include "qbitmap.h" +#include "qbrush.h" +#include "qcolor.h" +#include "qcursor.h" +#include "qdatastream.h" +#include "qdebug.h" +#include "qfont.h" +#include "qicon.h" +#include "qimage.h" +#include "qkeysequence.h" +#include "qtransform.h" +#include "qmatrix.h" +#include "qpalette.h" +#include "qpen.h" +#include "qpixmap.h" +#include "qpolygon.h" +#include "qregion.h" +#include "qsizepolicy.h" +#include "qtextformat.h" +#include "qmatrix4x4.h" +#include "qvector2d.h" +#include "qvector3d.h" +#include "qvector4d.h" +#include "qquaternion.h" + +#include "private/qvariant_p.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT3_SUPPORT +extern QDataStream &qt_stream_out_qcolorgroup(QDataStream &s, const QColorGroup &g); +extern QDataStream &qt_stream_in_qcolorgroup(QDataStream &s, QColorGroup &g); +#endif + +Q_CORE_EXPORT const QVariant::Handler *qcoreVariantHandler(); + +static void construct(QVariant::Private *x, const void *copy) +{ + switch (x->type) { + case QVariant::Bitmap: + v_construct<QBitmap>(x, copy); + break; + case QVariant::Region: + v_construct<QRegion>(x, copy); + break; + case QVariant::Polygon: + v_construct<QPolygon>(x, copy); + break; + case QVariant::Font: + v_construct<QFont>(x, copy); + break; + case QVariant::Pixmap: + v_construct<QPixmap>(x, copy); + break; + case QVariant::Image: + v_construct<QImage>(x, copy); + break; + case QVariant::Brush: + v_construct<QBrush>(x, copy); + break; + case QVariant::Color: + v_construct<QColor>(x, copy); + break; + case QVariant::Palette: + v_construct<QPalette>(x, copy); + break; +#ifdef QT3_SUPPORT + case QVariant::ColorGroup: + v_construct<QColorGroup>(x, copy); + break; +#endif +#ifndef QT_NO_ICON + case QVariant::Icon: + v_construct<QIcon>(x, copy); + break; +#endif + case QVariant::Matrix: + v_construct<QMatrix>(x, copy); + break; + case QVariant::Transform: + v_construct<QTransform>(x, copy); + break; + case QVariant::TextFormat: + v_construct<QTextFormat>(x, copy); + break; + case QVariant::TextLength: + v_construct<QTextLength>(x, copy); + break; +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: + v_construct<QKeySequence>(x, copy); + break; +#endif + case QVariant::Pen: + v_construct<QPen>(x, copy); + break; + case QVariant::SizePolicy: + v_construct<QSizePolicy>(x, copy); + break; +#ifndef QT_NO_CURSOR + case QVariant::Cursor: + v_construct<QCursor>(x, copy); + break; +#endif + case 62: { + // small 'trick' to let a QVariant(Qt::blue) create a variant + // of type QColor + x->type = QVariant::Color; + QColor color(*reinterpret_cast<const Qt::GlobalColor *>(copy)); + v_construct<QColor>(x, &color); + break; + } +#ifndef QT_NO_MATRIX4X4 + case QVariant::Matrix4x4: + v_construct<QMatrix4x4>(x, copy); + break; +#endif +#ifndef QT_NO_VECTOR2D + case QVariant::Vector2D: + v_construct<QVector2D>(x, copy); + break; +#endif +#ifndef QT_NO_VECTOR3D + case QVariant::Vector3D: + v_construct<QVector3D>(x, copy); + break; +#endif +#ifndef QT_NO_VECTOR4D + case QVariant::Vector4D: + v_construct<QVector4D>(x, copy); + break; +#endif +#ifndef QT_NO_QUATERNION + case QVariant::Quaternion: + v_construct<QQuaternion>(x, copy); + break; +#endif + default: + qcoreVariantHandler()->construct(x, copy); + return; + } + x->is_null = !copy; +} + +static void clear(QVariant::Private *d) +{ + switch (d->type) { + case QVariant::Bitmap: + v_clear<QBitmap>(d); + break; + case QVariant::Cursor: + v_clear<QCursor>(d); + break; + case QVariant::Region: + v_clear<QRegion>(d); + break; + case QVariant::Polygon: + v_clear<QPolygon>(d); + break; + case QVariant::Font: + v_clear<QFont>(d); + break; + case QVariant::Pixmap: + v_clear<QPixmap>(d); + break; + case QVariant::Image: + v_clear<QImage>(d); + break; + case QVariant::Brush: + v_clear<QBrush>(d); + break; + case QVariant::Color: + v_clear<QColor>(d); + break; + case QVariant::Palette: + v_clear<QPalette>(d); + break; +#ifdef QT3_SUPPORT + case QVariant::ColorGroup: + v_clear<QColorGroup>(d); + break; +#endif +#ifndef QT_NO_ICON + case QVariant::Icon: + v_clear<QIcon>(d); + break; +#endif + case QVariant::Matrix: + v_clear<QMatrix>(d); + break; + case QVariant::Transform: + v_clear<QTransform>(d); + break; + case QVariant::TextFormat: + v_clear<QTextFormat>(d); + break; + case QVariant::TextLength: + v_clear<QTextLength>(d); + break; + case QVariant::SizePolicy: + v_clear<QSizePolicy>(d); + break; +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: + v_clear<QKeySequence>(d); + break; +#endif + case QVariant::Pen: + v_clear<QPen>(d); + break; +#ifndef QT_NO_MATRIX4X4 + case QVariant::Matrix4x4: + v_clear<QMatrix4x4>(d); + break; +#endif +#ifndef QT_NO_VECTOR2D + case QVariant::Vector2D: + v_clear<QVector2D>(d); + break; +#endif +#ifndef QT_NO_VECTOR3D + case QVariant::Vector3D: + v_clear<QVector3D>(d); + break; +#endif +#ifndef QT_NO_VECTOR4D + case QVariant::Vector4D: + v_clear<QVector4D>(d); + break; +#endif +#ifndef QT_NO_QUATERNION + case QVariant::Quaternion: + v_clear<QVector4D>(d); + break; +#endif + default: + qcoreVariantHandler()->clear(d); + return; + } + + d->type = QVariant::Invalid; + d->is_null = true; + d->is_shared = false; +} + + +static bool isNull(const QVariant::Private *d) +{ + switch(d->type) { + case QVariant::Bitmap: + return v_cast<QBitmap>(d)->isNull(); + case QVariant::Region: + return v_cast<QRegion>(d)->isEmpty(); + case QVariant::Polygon: + return v_cast<QPolygon>(d)->isEmpty(); + case QVariant::Pixmap: + return v_cast<QPixmap>(d)->isNull(); + case QVariant::Image: + return v_cast<QImage>(d)->isNull(); +#ifndef QT_NO_ICON + case QVariant::Icon: + return v_cast<QIcon>(d)->isNull(); +#endif + case QVariant::Matrix: + case QVariant::TextFormat: + case QVariant::TextLength: + case QVariant::Cursor: + case QVariant::StringList: + case QVariant::Font: + case QVariant::Brush: + case QVariant::Color: + case QVariant::Palette: +#ifdef QT3_SUPPORT + case QVariant::ColorGroup: +#endif + case QVariant::SizePolicy: +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: +#endif + case QVariant::Pen: +#ifndef QT_NO_MATRIX4X4 + case QVariant::Matrix4x4: +#endif + break; +#ifndef QT_NO_VECTOR2D + case QVariant::Vector2D: + return v_cast<QVector2D>(d)->isNull(); +#endif +#ifndef QT_NO_VECTOR3D + case QVariant::Vector3D: + return v_cast<QVector3D>(d)->isNull(); +#endif +#ifndef QT_NO_VECTOR4D + case QVariant::Vector4D: + return v_cast<QVector4D>(d)->isNull(); +#endif +#ifndef QT_NO_QUATERNION + case QVariant::Quaternion: + return v_cast<QQuaternion>(d)->isNull(); +#endif + default: + return qcoreVariantHandler()->isNull(d); + } + return d->is_null; +} + +static bool compare(const QVariant::Private *a, const QVariant::Private *b) +{ + Q_ASSERT(a->type == b->type); + switch(a->type) { + case QVariant::Cursor: +#ifndef QT_NO_CURSOR + return v_cast<QCursor>(a)->shape() == v_cast<QCursor>(b)->shape(); +#endif + case QVariant::Bitmap: + return v_cast<QBitmap>(a)->cacheKey() + == v_cast<QBitmap>(b)->cacheKey(); + case QVariant::Polygon: + return *v_cast<QPolygon>(a) == *v_cast<QPolygon>(b); + case QVariant::Region: + return *v_cast<QRegion>(a) == *v_cast<QRegion>(b); + case QVariant::Font: + return *v_cast<QFont>(a) == *v_cast<QFont>(b); + case QVariant::Pixmap: + return v_cast<QPixmap>(a)->cacheKey() == v_cast<QPixmap>(b)->cacheKey(); + case QVariant::Image: + return *v_cast<QImage>(a) == *v_cast<QImage>(b); + case QVariant::Brush: + return *v_cast<QBrush>(a) == *v_cast<QBrush>(b); + case QVariant::Color: + return *v_cast<QColor>(a) == *v_cast<QColor>(b); + case QVariant::Palette: + return *v_cast<QPalette>(a) == *v_cast<QPalette>(b); +#ifdef QT3_SUPPORT + case QVariant::ColorGroup: + return *v_cast<QColorGroup>(a) == *v_cast<QColorGroup>(b); +#endif +#ifndef QT_NO_ICON + case QVariant::Icon: + /* QIcon::operator==() cannot be reasonably implemented for QIcon, + * so we always return false. */ + return false; +#endif + case QVariant::Matrix: + return *v_cast<QMatrix>(a) == *v_cast<QMatrix>(b); + case QVariant::Transform: + return *v_cast<QTransform>(a) == *v_cast<QTransform>(b); + case QVariant::TextFormat: + return *v_cast<QTextFormat>(a) == *v_cast<QTextFormat>(b); + case QVariant::TextLength: + return *v_cast<QTextLength>(a) == *v_cast<QTextLength>(b); + case QVariant::SizePolicy: + return *v_cast<QSizePolicy>(a) == *v_cast<QSizePolicy>(b); +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: + return *v_cast<QKeySequence>(a) == *v_cast<QKeySequence>(b); +#endif + case QVariant::Pen: + return *v_cast<QPen>(a) == *v_cast<QPen>(b); +#ifndef QT_NO_MATRIX4X4 + case QVariant::Matrix4x4: + return *v_cast<QMatrix4x4>(a) == *v_cast<QMatrix4x4>(b); +#endif +#ifndef QT_NO_VECTOR2D + case QVariant::Vector2D: + return *v_cast<QVector2D>(a) == *v_cast<QVector2D>(b); +#endif +#ifndef QT_NO_VECTOR3D + case QVariant::Vector3D: + return *v_cast<QVector3D>(a) == *v_cast<QVector3D>(b); +#endif +#ifndef QT_NO_VECTOR4D + case QVariant::Vector4D: + return *v_cast<QVector4D>(a) == *v_cast<QVector4D>(b); +#endif +#ifndef QT_NO_QUATERNION + case QVariant::Quaternion: + return *v_cast<QQuaternion>(a) == *v_cast<QQuaternion>(b); +#endif + default: + break; + } + return qcoreVariantHandler()->compare(a, b); +} + + + +static bool convert(const QVariant::Private *d, QVariant::Type t, + void *result, bool *ok) +{ + switch (t) { + case QVariant::ByteArray: + if (d->type == QVariant::Color) { + *static_cast<QByteArray *>(result) = v_cast<QColor>(d)->name().toLatin1(); + return true; + } + break; + case QVariant::String: { + QString *str = static_cast<QString *>(result); + switch (d->type) { +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: + *str = QString(*v_cast<QKeySequence>(d)); + return true; +#endif + case QVariant::Font: + *str = v_cast<QFont>(d)->toString(); + return true; + case QVariant::Color: + *str = v_cast<QColor>(d)->name(); + return true; + default: + break; + } + break; + } + case QVariant::Pixmap: + if (d->type == QVariant::Image) { + *static_cast<QPixmap *>(result) = QPixmap::fromImage(*v_cast<QImage>(d)); + return true; + } else if (d->type == QVariant::Bitmap) { + *static_cast<QPixmap *>(result) = *v_cast<QBitmap>(d); + return true; + } else if (d->type == QVariant::Brush) { + if (v_cast<QBrush>(d)->style() == Qt::TexturePattern) { + *static_cast<QPixmap *>(result) = v_cast<QBrush>(d)->texture(); + return true; + } + } + break; + case QVariant::Image: + if (d->type == QVariant::Pixmap) { + *static_cast<QImage *>(result) = v_cast<QPixmap>(d)->toImage(); + return true; + } else if (d->type == QVariant::Bitmap) { + *static_cast<QImage *>(result) = v_cast<QBitmap>(d)->toImage(); + return true; + } + break; + case QVariant::Bitmap: + if (d->type == QVariant::Pixmap) { + *static_cast<QBitmap *>(result) = *v_cast<QPixmap>(d); + return true; + } else if (d->type == QVariant::Image) { + *static_cast<QBitmap *>(result) = QBitmap::fromImage(*v_cast<QImage>(d)); + return true; + } + break; +#ifndef QT_NO_SHORTCUT + case QVariant::Int: + if (d->type == QVariant::KeySequence) { + *static_cast<int *>(result) = (int)(*(v_cast<QKeySequence>(d))); + return true; + } + break; +#endif + case QVariant::Font: + if (d->type == QVariant::String) { + QFont *f = static_cast<QFont *>(result); + f->fromString(*v_cast<QString>(d)); + return true; + } + break; + case QVariant::Color: + if (d->type == QVariant::String) { + static_cast<QColor *>(result)->setNamedColor(*v_cast<QString>(d)); + return static_cast<QColor *>(result)->isValid(); + } else if (d->type == QVariant::ByteArray) { + static_cast<QColor *>(result)->setNamedColor(QString::fromLatin1( + *v_cast<QByteArray>(d))); + return true; + } else if (d->type == QVariant::Brush) { + if (v_cast<QBrush>(d)->style() == Qt::SolidPattern) { + *static_cast<QColor *>(result) = v_cast<QBrush>(d)->color(); + return true; + } + } + break; + case QVariant::Brush: + if (d->type == QVariant::Color) { + *static_cast<QBrush *>(result) = QBrush(*v_cast<QColor>(d)); + return true; + } else if (d->type == QVariant::Pixmap) { + *static_cast<QBrush *>(result) = QBrush(*v_cast<QPixmap>(d)); + return true; + } + break; +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: { + QKeySequence *seq = static_cast<QKeySequence *>(result); + switch (d->type) { + case QVariant::String: + *seq = QKeySequence(*v_cast<QString>(d)); + return true; + case QVariant::Int: + *seq = QKeySequence(d->data.i); + return true; + default: + break; + } + } +#endif + default: + break; + } + return qcoreVariantHandler()->convert(d, t, result, ok); +} + +#if !defined(QT_NO_DEBUG_STREAM) && !defined(Q_BROKEN_DEBUG_STREAM) +static void streamDebug(QDebug dbg, const QVariant &v) +{ + switch(v.type()) { + case QVariant::Cursor: +#ifndef QT_NO_CURSOR +// dbg.nospace() << qvariant_cast<QCursor>(v); //FIXME +#endif + break; + case QVariant::Bitmap: +// dbg.nospace() << qvariant_cast<QBitmap>(v); //FIXME + break; + case QVariant::Polygon: + dbg.nospace() << qvariant_cast<QPolygon>(v); + break; + case QVariant::Region: + dbg.nospace() << qvariant_cast<QRegion>(v); + break; + case QVariant::Font: +// dbg.nospace() << qvariant_cast<QFont>(v); //FIXME + break; + case QVariant::Matrix: + dbg.nospace() << qvariant_cast<QMatrix>(v); + break; + case QVariant::Transform: + dbg.nospace() << qvariant_cast<QTransform>(v); + break; + case QVariant::Pixmap: +// dbg.nospace() << qvariant_cast<QPixmap>(v); //FIXME + break; + case QVariant::Image: +// dbg.nospace() << qvariant_cast<QImage>(v); //FIXME + break; + case QVariant::Brush: + dbg.nospace() << qvariant_cast<QBrush>(v); + break; + case QVariant::Color: + dbg.nospace() << qvariant_cast<QColor>(v); + break; + case QVariant::Palette: +// dbg.nospace() << qvariant_cast<QPalette>(v); //FIXME + break; +#ifndef QT_NO_ICON + case QVariant::Icon: +// dbg.nospace() << qvariant_cast<QIcon>(v); // FIXME + break; +#endif + case QVariant::SizePolicy: +// dbg.nospace() << qvariant_cast<QSizePolicy>(v); //FIXME + break; +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: + dbg.nospace() << qvariant_cast<QKeySequence>(v); + break; +#endif + case QVariant::Pen: + dbg.nospace() << qvariant_cast<QPen>(v); + break; +#ifndef QT_NO_MATRIX4X4 + case QVariant::Matrix4x4: + dbg.nospace() << qvariant_cast<QMatrix4x4>(v); + break; +#endif +#ifndef QT_NO_VECTOR2D + case QVariant::Vector2D: + dbg.nospace() << qvariant_cast<QVector2D>(v); + break; +#endif +#ifndef QT_NO_VECTOR3D + case QVariant::Vector3D: + dbg.nospace() << qvariant_cast<QVector3D>(v); + break; +#endif +#ifndef QT_NO_VECTOR4D + case QVariant::Vector4D: + dbg.nospace() << qvariant_cast<QVector4D>(v); + break; +#endif +#ifndef QT_NO_QUATERNION + case QVariant::Quaternion: + dbg.nospace() << qvariant_cast<QQuaternion>(v); + break; +#endif + default: + qcoreVariantHandler()->debugStream(dbg, v); + break; + } +} +#endif + +const QVariant::Handler qt_gui_variant_handler = { + construct, + clear, + isNull, +#ifndef QT_NO_DATASTREAM + 0, + 0, +#endif + compare, + convert, + 0, +#if !defined(QT_NO_DEBUG_STREAM) && !defined(Q_BROKEN_DEBUG_STREAM) + streamDebug +#else + 0 +#endif +}; + +struct QMetaTypeGuiHelper +{ + QMetaType::Constructor constr; + QMetaType::Destructor destr; +#ifndef QT_NO_DATASTREAM + QMetaType::SaveOperator saveOp; + QMetaType::LoadOperator loadOp; +#endif +}; + +extern Q_CORE_EXPORT const QMetaTypeGuiHelper *qMetaTypeGuiHelper; + + +#ifdef QT_NO_DATASTREAM +# define Q_DECL_METATYPE_HELPER(TYPE) \ + typedef void *(*QConstruct##TYPE)(const TYPE *); \ + static const QConstruct##TYPE qConstruct##TYPE = qMetaTypeConstructHelper<TYPE>; \ + typedef void (*QDestruct##TYPE)(TYPE *); \ + static const QDestruct##TYPE qDestruct##TYPE = qMetaTypeDeleteHelper<TYPE>; +#else +# define Q_DECL_METATYPE_HELPER(TYPE) \ + typedef void *(*QConstruct##TYPE)(const TYPE *); \ + static const QConstruct##TYPE qConstruct##TYPE = qMetaTypeConstructHelper<TYPE>; \ + typedef void (*QDestruct##TYPE)(TYPE *); \ + static const QDestruct##TYPE qDestruct##TYPE = qMetaTypeDeleteHelper<TYPE>; \ + typedef void (*QSave##TYPE)(QDataStream &, const TYPE *); \ + static const QSave##TYPE qSave##TYPE = qMetaTypeSaveHelper<TYPE>; \ + typedef void (*QLoad##TYPE)(QDataStream &, TYPE *); \ + static const QLoad##TYPE qLoad##TYPE = qMetaTypeLoadHelper<TYPE>; +#endif + +#ifdef QT3_SUPPORT +Q_DECL_METATYPE_HELPER(QColorGroup) +#endif +Q_DECL_METATYPE_HELPER(QFont) +Q_DECL_METATYPE_HELPER(QPixmap) +Q_DECL_METATYPE_HELPER(QBrush) +Q_DECL_METATYPE_HELPER(QColor) +Q_DECL_METATYPE_HELPER(QPalette) +#ifndef QT_NO_ICON +Q_DECL_METATYPE_HELPER(QIcon) +#endif +Q_DECL_METATYPE_HELPER(QImage) +Q_DECL_METATYPE_HELPER(QPolygon) +Q_DECL_METATYPE_HELPER(QRegion) +Q_DECL_METATYPE_HELPER(QBitmap) +#ifndef QT_NO_CURSOR +Q_DECL_METATYPE_HELPER(QCursor) +#endif +Q_DECL_METATYPE_HELPER(QSizePolicy) +#ifndef QT_NO_SHORTCUT +Q_DECL_METATYPE_HELPER(QKeySequence) +#endif +Q_DECL_METATYPE_HELPER(QPen) +Q_DECL_METATYPE_HELPER(QTextLength) +Q_DECL_METATYPE_HELPER(QTextFormat) +Q_DECL_METATYPE_HELPER(QMatrix) +Q_DECL_METATYPE_HELPER(QTransform) +#ifndef QT_NO_MATRIX4X4 +Q_DECL_METATYPE_HELPER(QMatrix4x4) +#endif +#ifndef QT_NO_VECTOR2D +Q_DECL_METATYPE_HELPER(QVector2D) +#endif +#ifndef QT_NO_VECTOR3D +Q_DECL_METATYPE_HELPER(QVector3D) +#endif +#ifndef QT_NO_VECTOR4D +Q_DECL_METATYPE_HELPER(QVector4D) +#endif +#ifndef QT_NO_QUATERNION +Q_DECL_METATYPE_HELPER(QQuaternion) +#endif + +#ifdef QT_NO_DATASTREAM +# define Q_IMPL_METATYPE_HELPER(TYPE) \ + { reinterpret_cast<QMetaType::Constructor>(qConstruct##TYPE), \ + reinterpret_cast<QMetaType::Destructor>(qDestruct##TYPE) } +#else +# define Q_IMPL_METATYPE_HELPER(TYPE) \ + { reinterpret_cast<QMetaType::Constructor>(qConstruct##TYPE), \ + reinterpret_cast<QMetaType::Destructor>(qDestruct##TYPE), \ + reinterpret_cast<QMetaType::SaveOperator>(qSave##TYPE), \ + reinterpret_cast<QMetaType::LoadOperator>(qLoad##TYPE) \ + } +#endif + +static const QMetaTypeGuiHelper qVariantGuiHelper[] = { +#ifdef QT3_SUPPORT + Q_IMPL_METATYPE_HELPER(QColorGroup), +#else + {0, 0, 0, 0}, +#endif + Q_IMPL_METATYPE_HELPER(QFont), + Q_IMPL_METATYPE_HELPER(QPixmap), + Q_IMPL_METATYPE_HELPER(QBrush), + Q_IMPL_METATYPE_HELPER(QColor), + Q_IMPL_METATYPE_HELPER(QPalette), +#ifdef QT_NO_ICON + {0, 0, 0, 0}, +#else + Q_IMPL_METATYPE_HELPER(QIcon), +#endif + Q_IMPL_METATYPE_HELPER(QImage), + Q_IMPL_METATYPE_HELPER(QPolygon), + Q_IMPL_METATYPE_HELPER(QRegion), + Q_IMPL_METATYPE_HELPER(QBitmap), +#ifdef QT_NO_CURSOR + {0, 0, 0, 0}, +#else + Q_IMPL_METATYPE_HELPER(QCursor), +#endif + Q_IMPL_METATYPE_HELPER(QSizePolicy), +#ifdef QT_NO_SHORTCUT + {0, 0, 0, 0}, +#else + Q_IMPL_METATYPE_HELPER(QKeySequence), +#endif + Q_IMPL_METATYPE_HELPER(QPen), + Q_IMPL_METATYPE_HELPER(QTextLength), + Q_IMPL_METATYPE_HELPER(QTextFormat), + Q_IMPL_METATYPE_HELPER(QMatrix), + Q_IMPL_METATYPE_HELPER(QTransform), +#ifndef QT_NO_MATRIX4X4 + Q_IMPL_METATYPE_HELPER(QMatrix4x4), +#else + {0, 0, 0, 0}, +#endif +#ifndef QT_NO_VECTOR2D + Q_IMPL_METATYPE_HELPER(QVector2D), +#else + {0, 0, 0, 0}, +#endif +#ifndef QT_NO_VECTOR3D + Q_IMPL_METATYPE_HELPER(QVector3D), +#else + {0, 0, 0, 0}, +#endif +#ifndef QT_NO_VECTOR4D + Q_IMPL_METATYPE_HELPER(QVector4D), +#else + {0, 0, 0, 0}, +#endif +#ifndef QT_NO_QUATERNION + Q_IMPL_METATYPE_HELPER(QQuaternion) +#else + {0, 0, 0, 0} +#endif +}; + +static const QVariant::Handler *qt_guivariant_last_handler = 0; +int qRegisterGuiVariant() +{ + qt_guivariant_last_handler = QVariant::handler; + QVariant::handler = &qt_gui_variant_handler; + qMetaTypeGuiHelper = qVariantGuiHelper; + return 1; +} +Q_CONSTRUCTOR_FUNCTION(qRegisterGuiVariant) + +int qUnregisterGuiVariant() +{ + QVariant::handler = qt_guivariant_last_handler; + qMetaTypeGuiHelper = 0; + return 1; +} +Q_DESTRUCTOR_FUNCTION(qUnregisterGuiVariant) + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkde.cpp b/src/gui/kernel/qkde.cpp new file mode 100644 index 0000000000..7d333feb9a --- /dev/null +++ b/src/gui/kernel/qkde.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qkde_p.h" +#include <QtCore/QLibrary> +#include <QtCore/QDir> +#include <QtCore/qdebug.h> +#include <QtCore/QSettings> +#include "QtGui/qstylefactory.h" +#include "qt_x11_p.h" + +#if defined(Q_WS_X11) + +QT_BEGIN_NAMESPACE + +/*! \internal +Gets the current KDE home path +like "/home/troll/.kde" +*/ +QString QKde::kdeHome() +{ + static QString kdeHomePath; + if (kdeHomePath.isEmpty()) { + kdeHomePath = QString::fromLocal8Bit(qgetenv("KDEHOME")); + if (kdeHomePath.isEmpty()) { + QDir homeDir(QDir::homePath()); + QString kdeConfDir(QLatin1String("/.kde")); + if (4 == X11->desktopVersion && homeDir.exists(QLatin1String(".kde4"))) + kdeConfDir = QLatin1String("/.kde4"); + kdeHomePath = QDir::homePath() + kdeConfDir; + } + } + return kdeHomePath; +} + +/*!\internal + Reads the color from the config, and store it in the palette with the given color role if found + */ +static bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QSettings &kdeSettings, const QString &kde4Key, const QString &kde3Key = QString()) +{ + QVariant variant = kdeSettings.value(kde4Key); + if (!variant.isValid()) + QVariant variant = kdeSettings.value(kde3Key); + if (variant.isValid()) { + QStringList values = variant.toStringList(); + if (values.size() == 3) { + int r = values[0].toInt(); + int g = values[1].toInt(); + int b = values[2].toInt(); + pal->setBrush(role, QColor(r, g, b)); + return true; + } + } + return false; +} + + +/*!\internal + Returns the KDE palette +*/ +QPalette QKde::kdePalette() +{ + const QSettings theKdeSettings(QKde::kdeHome() + + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat); + QPalette pal; + + // Setup KDE palette + kdeColor(&pal, QPalette::Button, theKdeSettings, QLatin1String("Colors:Button/BackgroundNormal"), QLatin1String("buttonBackground")); + kdeColor(&pal, QPalette::Window, theKdeSettings, QLatin1String("Colors:Window/BackgroundNormal"), QLatin1String("background")); + kdeColor(&pal, QPalette::Text, theKdeSettings, QLatin1String("Colors:View/ForegroundNormal"), QLatin1String("foreground")); + kdeColor(&pal, QPalette::WindowText, theKdeSettings, QLatin1String("Colors:Window/ForegroundNormal"), QLatin1String("windowForeground")); + kdeColor(&pal, QPalette::Base, theKdeSettings, QLatin1String("Colors:View/BackgroundNormal"), QLatin1String("windowBackground")); + kdeColor(&pal, QPalette::Highlight, theKdeSettings, QLatin1String("Colors:Selection/BackgroundNormal"), QLatin1String("selectBackground")); + kdeColor(&pal, QPalette::HighlightedText, theKdeSettings, QLatin1String("Colors:Selection/ForegroundNormal"), QLatin1String("selectForeground")); + kdeColor(&pal, QPalette::AlternateBase, theKdeSettings, QLatin1String("Colors:View/BackgroundAlternate"), QLatin1String("alternateBackground")); + kdeColor(&pal, QPalette::ButtonText, theKdeSettings, QLatin1String("Colors:Button/ForegroundNormal"), QLatin1String("buttonForeground")); + kdeColor(&pal, QPalette::Link, theKdeSettings, QLatin1String("Colors:View/ForegroundLink"), QLatin1String("linkColor")); + kdeColor(&pal, QPalette::LinkVisited, theKdeSettings, QLatin1String("Colors:View/ForegroundVisited"), QLatin1String("visitedLinkColor")); + //## TODO tooltip color + + return pal; +} + +/*!\internal + Returns the name of the QStyle to use. + (read from the kde config if needed) +*/ +QString QKde::kdeStyle() +{ + if (X11->desktopVersion >= 4) { + QSettings kdeSettings(QKde::kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat); + QString style = kdeSettings.value(QLatin1String("widgetStyle"), QLatin1String("Oxygen")).toString(); + + QStringList availableStyles = QStyleFactory::keys(); + if(availableStyles.contains(style, Qt::CaseInsensitive)) + return style; + } + + if (X11->use_xrender) + return QLatin1String("plastique"); + else + return QLatin1String("windows"); +} + + +int QKde::kdeToolButtonStyle() +{ + QSettings settings(QKde::kdeHome() + QLatin1String("/share/config/kdeglobals"), + QSettings::IniFormat); + settings.beginGroup(QLatin1String("Toolbar style")); + QString toolbarStyle = settings.value(QLatin1String("ToolButtonStyle"), QLatin1String("TextBesideIcon")).toString(); + if (toolbarStyle == QLatin1String("TextBesideIcon")) + return Qt::ToolButtonTextBesideIcon; + else if (toolbarStyle == QLatin1String("TextOnly")) + return Qt::ToolButtonTextOnly; + else if (toolbarStyle == QLatin1String("TextUnderIcon")) + return Qt::ToolButtonTextUnderIcon; + + return Qt::ToolButtonTextBesideIcon; +} + +int QKde::kdeToolBarIconSize() +{ + static int iconSize = -1; + if (iconSize == -1) { + QSettings settings(QKde::kdeHome() + QLatin1String("/share/config/kdeglobals"), + QSettings::IniFormat); + settings.beginGroup(QLatin1String("ToolbarIcons")); + iconSize = settings.value(QLatin1String("Size")).toInt(); + } + return iconSize; +} + +QT_END_NAMESPACE + +#endif //Q_WS_X11 + diff --git a/src/gui/kernel/qkde_p.h b/src/gui/kernel/qkde_p.h new file mode 100644 index 0000000000..4e108f6e9e --- /dev/null +++ b/src/gui/kernel/qkde_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKDE_H +#define QKDE_H + +#include <QtCore/qglobal.h> +#include <QtGui/QPalette> +#include <QtGui/QIcon> + +// +// 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. +// +#if defined(Q_WS_X11) + + +QT_BEGIN_NAMESPACE + +/*!\internal + This namespace contains helper function to help KDE integration + They are only used if we detect the use of KDE and the KDE platform plugin is not found (old KDE version) + Or if the detected KDE version is KDE3 +*/ +namespace QKde { + QString kdeHome(); + QString kdeStyle(); + QPalette kdePalette(); + int kdeToolButtonStyle(); + int kdeToolBarIconSize(); +} + + +QT_END_NAMESPACE + +#endif // Q_WS_X11 +#endif // QKDE_H diff --git a/src/gui/kernel/qkeymapper.cpp b/src/gui/kernel/qkeymapper.cpp new file mode 100644 index 0000000000..e6ba3ce142 --- /dev/null +++ b/src/gui/kernel/qkeymapper.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qapplication.h" + +#include <private/qobject_p.h> +#include "qkeymapper_p.h" +#include <qwidget.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QKeyMapper + \since 4.2 + \internal + + \sa QObject +*/ + +/*! + Constructs a new key mapper. +*/ +QKeyMapper::QKeyMapper() + : QObject(*new QKeyMapperPrivate, 0) +{ +} + +/*! + Destroys the key mapper. +*/ +QKeyMapper::~QKeyMapper() +{ +} + +QList<int> QKeyMapper::possibleKeys(QKeyEvent *e) +{ + QList<int> result; + + if (!e->nativeScanCode()) { + if (e->key() && (e->key() != Qt::Key_unknown)) + result << int(e->key() + e->modifiers()); + else if (!e->text().isEmpty()) + result << int(e->text().at(0).unicode() + e->modifiers()); + return result; + } + + return instance()->d_func()->possibleKeys(e); +} + +extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); // in qapplication_*.cpp +void QKeyMapper::changeKeyboard() +{ + instance()->d_func()->clearMappings(); + + // inform all toplevel widgets of the change + QEvent e(QEvent::KeyboardLayoutChange); + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + qt_sendSpontaneousEvent(w, &e); + } +} + +Q_GLOBAL_STATIC(QKeyMapper, keymapper) +/*! + Returns the pointer to the single instance of QKeyMapper in the application. + If none yet exists, the function ensures that one is created. +*/ +QKeyMapper *QKeyMapper::instance() +{ + return keymapper(); +} + +QKeyMapperPrivate *qt_keymapper_private() +{ + return QKeyMapper::instance()->d_func(); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_mac.cpp b/src/gui/kernel/qkeymapper_mac.cpp new file mode 100644 index 0000000000..d3bbf89711 --- /dev/null +++ b/src/gui/kernel/qkeymapper_mac.cpp @@ -0,0 +1,1023 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qt_mac_p.h> +#include <qdebug.h> +#include <qevent.h> +#include <private/qevent_p.h> +#include <qtextcodec.h> +#include <qapplication.h> +#include <qinputcontext.h> +#include <private/qkeymapper_p.h> +#include <private/qapplication_p.h> +#include <private/qmacinputcontext_p.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + QKeyMapper debug facilities + *****************************************************************************/ +//#define DEBUG_KEY_BINDINGS +//#define DEBUG_KEY_BINDINGS_MODIFIERS +//#define DEBUG_KEY_MAPS + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ +bool qt_mac_eat_unicode_key = false; +extern bool qt_sendSpontaneousEvent(QObject *obj, QEvent *event); //qapplication_mac.cpp + +Q_GUI_EXPORT void qt_mac_secure_keyboard(bool b) +{ + static bool secure = false; + if (b != secure){ + b ? EnableSecureEventInput() : DisableSecureEventInput(); + secure = b; + } +} + +/* + \internal + A Mac KeyboardLayoutItem has 8 possible states: + 1. Unmodified + 2. Shift + 3. Control + 4. Control + Shift + 5. Alt + 6. Alt + Shift + 7. Alt + Control + 8. Alt + Control + Shift + 9. Meta + 10. Meta + Shift + 11. Meta + Control + 12. Meta + Control + Shift + 13. Meta + Alt + 14. Meta + Alt + Shift + 15. Meta + Alt + Control + 16. Meta + Alt + Control + Shift +*/ +struct KeyboardLayoutItem { + bool dirty; + quint32 qtKey[16]; // Can by any Qt::Key_<foo>, or unicode character +}; + +// Possible modifier states. +// NOTE: The order of these states match the order in QKeyMapperPrivate::updatePossibleKeyCodes()! +static const Qt::KeyboardModifiers ModsTbl[] = { + Qt::NoModifier, // 0 + Qt::ShiftModifier, // 1 + Qt::ControlModifier, // 2 + Qt::ControlModifier | Qt::ShiftModifier, // 3 + Qt::AltModifier, // 4 + Qt::AltModifier | Qt::ShiftModifier, // 5 + Qt::AltModifier | Qt::ControlModifier, // 6 + Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 7 + Qt::MetaModifier, // 8 + Qt::MetaModifier | Qt::ShiftModifier, // 9 + Qt::MetaModifier | Qt::ControlModifier, // 10 + Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier,// 11 + Qt::MetaModifier | Qt::AltModifier, // 12 + Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier, // 13 + Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier, // 14 + Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 15 +}; + +/* key maps */ +struct qt_mac_enum_mapper +{ + int mac_code; + int qt_code; +#if defined(DEBUG_KEY_BINDINGS) +# define QT_MAC_MAP_ENUM(x) x, #x + const char *desc; +#else +# define QT_MAC_MAP_ENUM(x) x +#endif +}; + +//modifiers +static qt_mac_enum_mapper qt_mac_modifier_symbols[] = { + { shiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, + { rightShiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, + { controlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, + { rightControlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, + { cmdKey, QT_MAC_MAP_ENUM(Qt::ControlModifier) }, + { optionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, + { rightOptionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, + { kEventKeyModifierNumLockMask, QT_MAC_MAP_ENUM(Qt::KeypadModifier) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; +Qt::KeyboardModifiers qt_mac_get_modifiers(int keys) +{ +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", keys, keys); +#endif + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { + if (keys & qt_mac_modifier_symbols[i].mac_code) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); +#endif + ret |= Qt::KeyboardModifier(qt_mac_modifier_symbols[i].qt_code); + } + } + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + Qt::KeyboardModifiers oldModifiers = ret; + ret &= ~(Qt::MetaModifier | Qt::ControlModifier); + if (oldModifiers & Qt::ControlModifier) + ret |= Qt::MetaModifier; + if (oldModifiers & Qt::MetaModifier) + ret |= Qt::ControlModifier; + } + return ret; +} +static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys) +{ +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", (int)keys, (int)keys); +#endif + int ret = 0; + for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { + if (keys & qt_mac_modifier_symbols[i].qt_code) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); +#endif + ret |= qt_mac_modifier_symbols[i].mac_code; + } + } + + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + int oldModifiers = ret; + ret &= ~(controlKeyBit | cmdKeyBit); + if (oldModifiers & controlKeyBit) + ret |= cmdKeyBit; + if (oldModifiers & cmdKeyBit) + ret |= controlKeyBit; + } + return ret; +} +void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object) +{ + static quint32 cachedModifiers = 0; + quint32 lastModifiers = cachedModifiers, + changedModifiers = lastModifiers ^ modifiers; + cachedModifiers = modifiers; + + //check the bits + static qt_mac_enum_mapper modifier_key_symbols[] = { + { shiftKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Shift) }, + { rightShiftKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Shift) }, //??? + { controlKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Meta) }, + { rightControlKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Meta) }, //??? + { cmdKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Control) }, + { optionKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Alt) }, + { rightOptionKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Alt) }, //??? + { alphaLockBit, QT_MAC_MAP_ENUM(Qt::Key_CapsLock) }, + { kEventKeyModifierNumLockBit, QT_MAC_MAP_ENUM(Qt::Key_NumLock) }, + { 0, QT_MAC_MAP_ENUM(0) } }; + for (int i = 0; i <= 32; i++) { //just check each bit + if (!(changedModifiers & (1 << i))) + continue; + QEvent::Type etype = QEvent::KeyPress; + if (lastModifiers & (1 << i)) + etype = QEvent::KeyRelease; + int key = 0; + for (uint x = 0; modifier_key_symbols[x].mac_code; x++) { + if (modifier_key_symbols[x].mac_code == i) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("got modifier changed: %s", modifier_key_symbols[x].desc); +#endif + key = modifier_key_symbols[x].qt_code; + break; + } + } + if (!key) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("could not get modifier changed: %d", i); +#endif + continue; + } +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("KeyEvent (modif): Sending %s to %s::%s: %d - 0x%08x", + etype == QEvent::KeyRelease ? "KeyRelease" : "KeyPress", + object ? object->metaObject()->className() : "none", + object ? object->objectName().toLatin1().constData() : "", + key, (int)modifiers); +#endif + QKeyEvent ke(etype, key, qt_mac_get_modifiers(modifiers ^ (1 << i)), QLatin1String("")); + qt_sendSpontaneousEvent(object, &ke); + } +} + +//keyboard keys (non-modifiers) +static qt_mac_enum_mapper qt_mac_keyboard_symbols[] = { + { kHomeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Home) }, + { kEnterCharCode, QT_MAC_MAP_ENUM(Qt::Key_Enter) }, + { kEndCharCode, QT_MAC_MAP_ENUM(Qt::Key_End) }, + { kBackspaceCharCode, QT_MAC_MAP_ENUM(Qt::Key_Backspace) }, + { kTabCharCode, QT_MAC_MAP_ENUM(Qt::Key_Tab) }, + { kPageUpCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, + { kPageDownCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, + { kReturnCharCode, QT_MAC_MAP_ENUM(Qt::Key_Return) }, + { kEscapeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Escape) }, + { kLeftArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Left) }, + { kRightArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Right) }, + { kUpArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Up) }, + { kDownArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Down) }, + { kHelpCharCode, QT_MAC_MAP_ENUM(Qt::Key_Help) }, + { kDeleteCharCode, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, +//ascii maps, for debug + { ':', QT_MAC_MAP_ENUM(Qt::Key_Colon) }, + { ';', QT_MAC_MAP_ENUM(Qt::Key_Semicolon) }, + { '<', QT_MAC_MAP_ENUM(Qt::Key_Less) }, + { '=', QT_MAC_MAP_ENUM(Qt::Key_Equal) }, + { '>', QT_MAC_MAP_ENUM(Qt::Key_Greater) }, + { '?', QT_MAC_MAP_ENUM(Qt::Key_Question) }, + { '@', QT_MAC_MAP_ENUM(Qt::Key_At) }, + { ' ', QT_MAC_MAP_ENUM(Qt::Key_Space) }, + { '!', QT_MAC_MAP_ENUM(Qt::Key_Exclam) }, + { '"', QT_MAC_MAP_ENUM(Qt::Key_QuoteDbl) }, + { '#', QT_MAC_MAP_ENUM(Qt::Key_NumberSign) }, + { '$', QT_MAC_MAP_ENUM(Qt::Key_Dollar) }, + { '%', QT_MAC_MAP_ENUM(Qt::Key_Percent) }, + { '&', QT_MAC_MAP_ENUM(Qt::Key_Ampersand) }, + { '\'', QT_MAC_MAP_ENUM(Qt::Key_Apostrophe) }, + { '(', QT_MAC_MAP_ENUM(Qt::Key_ParenLeft) }, + { ')', QT_MAC_MAP_ENUM(Qt::Key_ParenRight) }, + { '*', QT_MAC_MAP_ENUM(Qt::Key_Asterisk) }, + { '+', QT_MAC_MAP_ENUM(Qt::Key_Plus) }, + { ',', QT_MAC_MAP_ENUM(Qt::Key_Comma) }, + { '-', QT_MAC_MAP_ENUM(Qt::Key_Minus) }, + { '.', QT_MAC_MAP_ENUM(Qt::Key_Period) }, + { '/', QT_MAC_MAP_ENUM(Qt::Key_Slash) }, + { '[', QT_MAC_MAP_ENUM(Qt::Key_BracketLeft) }, + { ']', QT_MAC_MAP_ENUM(Qt::Key_BracketRight) }, + { '\\', QT_MAC_MAP_ENUM(Qt::Key_Backslash) }, + { '_', QT_MAC_MAP_ENUM(Qt::Key_Underscore) }, + { '`', QT_MAC_MAP_ENUM(Qt::Key_QuoteLeft) }, + { '{', QT_MAC_MAP_ENUM(Qt::Key_BraceLeft) }, + { '}', QT_MAC_MAP_ENUM(Qt::Key_BraceRight) }, + { '|', QT_MAC_MAP_ENUM(Qt::Key_Bar) }, + { '~', QT_MAC_MAP_ENUM(Qt::Key_AsciiTilde) }, + { '^', QT_MAC_MAP_ENUM(Qt::Key_AsciiCircum) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static qt_mac_enum_mapper qt_mac_keyvkey_symbols[] = { //real scan codes + { 122, QT_MAC_MAP_ENUM(Qt::Key_F1) }, + { 120, QT_MAC_MAP_ENUM(Qt::Key_F2) }, + { 99, QT_MAC_MAP_ENUM(Qt::Key_F3) }, + { 118, QT_MAC_MAP_ENUM(Qt::Key_F4) }, + { 96, QT_MAC_MAP_ENUM(Qt::Key_F5) }, + { 97, QT_MAC_MAP_ENUM(Qt::Key_F6) }, + { 98, QT_MAC_MAP_ENUM(Qt::Key_F7) }, + { 100, QT_MAC_MAP_ENUM(Qt::Key_F8) }, + { 101, QT_MAC_MAP_ENUM(Qt::Key_F9) }, + { 109, QT_MAC_MAP_ENUM(Qt::Key_F10) }, + { 103, QT_MAC_MAP_ENUM(Qt::Key_F11) }, + { 111, QT_MAC_MAP_ENUM(Qt::Key_F12) }, + { 105, QT_MAC_MAP_ENUM(Qt::Key_F13) }, + { 107, QT_MAC_MAP_ENUM(Qt::Key_F14) }, + { 113, QT_MAC_MAP_ENUM(Qt::Key_F15) }, + { 106, QT_MAC_MAP_ENUM(Qt::Key_F16) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static qt_mac_enum_mapper qt_mac_private_unicode[] = { + { 0xF700, QT_MAC_MAP_ENUM(Qt::Key_Up) }, //NSUpArrowFunctionKey + { 0xF701, QT_MAC_MAP_ENUM(Qt::Key_Down) }, //NSDownArrowFunctionKey + { 0xF702, QT_MAC_MAP_ENUM(Qt::Key_Left) }, //NSLeftArrowFunctionKey + { 0xF703, QT_MAC_MAP_ENUM(Qt::Key_Right) }, //NSRightArrowFunctionKey + { 0xF727, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertFunctionKey + { 0xF728, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteFunctionKey + { 0xF729, QT_MAC_MAP_ENUM(Qt::Key_Home) }, //NSHomeFunctionKey + { 0xF72B, QT_MAC_MAP_ENUM(Qt::Key_End) }, //NSEndFunctionKey + { 0xF72C, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, //NSPageUpFunctionKey + { 0xF72D, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, //NSPageDownFunctionKey + { 0xF72F, QT_MAC_MAP_ENUM(Qt::Key_ScrollLock) }, //NSScrollLockFunctionKey + { 0xF730, QT_MAC_MAP_ENUM(Qt::Key_Pause) }, //NSPauseFunctionKey + { 0xF731, QT_MAC_MAP_ENUM(Qt::Key_SysReq) }, //NSSysReqFunctionKey + { 0xF735, QT_MAC_MAP_ENUM(Qt::Key_Menu) }, //NSMenuFunctionKey + { 0xF738, QT_MAC_MAP_ENUM(Qt::Key_Print) }, //NSPrintFunctionKey + { 0xF73A, QT_MAC_MAP_ENUM(Qt::Key_Clear) }, //NSClearDisplayFunctionKey + { 0xF73D, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertCharFunctionKey + { 0xF73E, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteCharFunctionKey + { 0xF741, QT_MAC_MAP_ENUM(Qt::Key_Select) }, //NSSelectFunctionKey + { 0xF742, QT_MAC_MAP_ENUM(Qt::Key_Execute) }, //NSExecuteFunctionKey + { 0xF746, QT_MAC_MAP_ENUM(Qt::Key_Help) }, //NSHelpFunctionKey + { 0xF747, QT_MAC_MAP_ENUM(Qt::Key_Mode_switch) }, //NSModeSwitchFunctionKey + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static int qt_mac_get_key(int modif, const QChar &key, int virtualKey) +{ +#ifdef DEBUG_KEY_BINDINGS + qDebug("**Mapping key: %d (0x%04x) - %d (0x%04x)", key.unicode(), key.unicode(), virtualKey, virtualKey); +#endif + + if (key == kClearCharCode && virtualKey == 0x47) + return Qt::Key_Clear; + + if (key.isDigit()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, key.digitValue()); +#endif + return key.digitValue() + Qt::Key_0; + } + + if (key.isLetter()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, (key.toUpper().unicode() - 'A')); +#endif + return (key.toUpper().unicode() - 'A') + Qt::Key_A; + } + if (key.isSymbol()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, (key.unicode())); +#endif + return key.unicode(); + } + + for (int i = 0; qt_mac_keyboard_symbols[i].qt_code; i++) { + if (qt_mac_keyboard_symbols[i].mac_code == key) { + /* To work like Qt for X11 we issue Backtab when Shift + Tab are pressed */ + if (qt_mac_keyboard_symbols[i].qt_code == Qt::Key_Tab && (modif & Qt::ShiftModifier)) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: Qt::Key_Backtab", __LINE__); +#endif + return Qt::Key_Backtab; + } + +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %s", __LINE__, qt_mac_keyboard_symbols[i].desc); +#endif + return qt_mac_keyboard_symbols[i].qt_code; + } + } + + //last ditch try to match the scan code + for (int i = 0; qt_mac_keyvkey_symbols[i].qt_code; i++) { + if (qt_mac_keyvkey_symbols[i].mac_code == virtualKey) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %s", __LINE__, qt_mac_keyvkey_symbols[i].desc); +#endif + return qt_mac_keyvkey_symbols[i].qt_code; + } + } + + // check if they belong to key codes in private unicode range + if (key >= 0xf700 && key <= 0xf747) { + if (key >= 0xf704 && key <= 0xf726) { + return Qt::Key_F1 + (key.unicode() - 0xf704) ; + } + for (int i = 0; qt_mac_private_unicode[i].qt_code; i++) { + if (qt_mac_private_unicode[i].mac_code == key) { + return qt_mac_private_unicode[i].qt_code; + } + } + + } + + //oh well +#ifdef DEBUG_KEY_BINDINGS + qDebug("Unknown case.. %s:%d %d[%d] %d", __FILE__, __LINE__, key.unicode(), key.toLatin1(), virtualKey); +#endif + return Qt::Key_unknown; +} + +static Boolean qt_KeyEventComparatorProc(EventRef inEvent, void *data) +{ + UInt32 ekind = GetEventKind(inEvent), + eclass = GetEventClass(inEvent); + return (eclass == kEventClassKeyboard && (void *)ekind == data); +} + +static bool translateKeyEventInternal(EventHandlerCallRef er, EventRef keyEvent, int *qtKey, + QChar *outChar, Qt::KeyboardModifiers *outModifiers, bool *outHandled) +{ +#if !defined(QT_MAC_USE_COCOA) || defined(Q_OS_MAC64) + Q_UNUSED(er); + Q_UNUSED(outHandled); +#endif + const UInt32 ekind = GetEventKind(keyEvent); + { + UInt32 mac_modifiers = 0; + GetEventParameter(keyEvent, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(mac_modifiers), 0, &mac_modifiers); +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("************ Mapping modifiers and key ***********"); +#endif + *outModifiers = qt_mac_get_modifiers(mac_modifiers); +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("------------ Mapping modifiers and key -----------"); +#endif + } + + //get keycode + UInt32 keyCode = 0; + GetEventParameter(keyEvent, kEventParamKeyCode, typeUInt32, 0, sizeof(keyCode), 0, &keyCode); + + //get mac mapping + static UInt32 tmp_unused_state = 0L; + const UCKeyboardLayout *uchrData = 0; +#if defined(Q_OS_MAC32) + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + OSStatus err; + if (keyLayoutRef != 0) { + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLuchrData, + (reinterpret_cast<const void **>(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType<TISInputSourceRef> inputSource = TISCopyCurrentKeyboardInputSource(); + Q_ASSERT(inputSource != 0); + CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(inputSource, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : 0; +#endif + *qtKey = Qt::Key_unknown; + if (uchrData) { + // The easy stuff; use the unicode stuff! + UniChar string[4]; + UniCharCount actualLength; + UInt32 currentModifiers = GetCurrentEventKeyModifiers(); + UInt32 currentModifiersWOAltOrControl = currentModifiers & ~(controlKey | optionKey); + int keyAction; + switch (ekind) { + default: + case kEventRawKeyDown: + keyAction = kUCKeyActionDown; + break; + case kEventRawKeyUp: + keyAction = kUCKeyActionUp; + break; + case kEventRawKeyRepeat: + keyAction = kUCKeyActionAutoKey; + break; + } + OSStatus err = UCKeyTranslate(uchrData, keyCode, keyAction, + ((currentModifiersWOAltOrControl >> 8) & 0xff), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysMask, &tmp_unused_state, 4, &actualLength, + string); + if (err == noErr) { + *outChar = QChar(string[0]); + *qtKey = qt_mac_get_key(*outModifiers, *outChar, keyCode); + if (currentModifiersWOAltOrControl != currentModifiers) { + // Now get the real char. + err = UCKeyTranslate(uchrData, keyCode, keyAction, + ((currentModifiers >> 8) & 0xff), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysMask, &tmp_unused_state, 4, &actualLength, + string); + if (err == noErr) + *outChar = QChar(string[0]); + } + } else { + qWarning("Qt::internal::UCKeyTranslate is returnining %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#ifdef Q_OS_MAC32 + else { + // The road less travelled; use KeyTranslate + const void *keyboard_layout; + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLKCHRData, + reinterpret_cast<const void **>(&keyboard_layout)); + + int translatedChar = KeyTranslate(keyboard_layout, (GetCurrentEventKeyModifiers() & + (kEventKeyModifierNumLockMask|shiftKey|cmdKey| + rightShiftKey|alphaLock)) | keyCode, + &tmp_unused_state); + if (!translatedChar) { +#ifdef QT_MAC_USE_COCOA + if (outHandled) { + qt_mac_eat_unicode_key = false; + if (er) + CallNextEventHandler(er, keyEvent); + *outHandled = qt_mac_eat_unicode_key; + } +#endif + return false; + } + + //map it into qt keys + *qtKey = qt_mac_get_key(*outModifiers, QChar(translatedChar), keyCode); + if (*outModifiers & (Qt::AltModifier | Qt::ControlModifier)) { + if (translatedChar & (1 << 7)) //high ascii + translatedChar = 0; + } else { //now get the real ascii value + UInt32 tmp_mod = 0L; + static UInt32 tmp_state = 0L; + if (*outModifiers & Qt::ShiftModifier) + tmp_mod |= shiftKey; + if (*outModifiers & Qt::MetaModifier) + tmp_mod |= controlKey; + if (*outModifiers & Qt::ControlModifier) + tmp_mod |= cmdKey; + if (GetCurrentEventKeyModifiers() & alphaLock) //no Qt mapper + tmp_mod |= alphaLock; + if (*outModifiers & Qt::AltModifier) + tmp_mod |= optionKey; + if (*outModifiers & Qt::KeypadModifier) + tmp_mod |= kEventKeyModifierNumLockMask; + translatedChar = KeyTranslate(keyboard_layout, tmp_mod | keyCode, &tmp_state); + } + { + ByteCount unilen = 0; + if (GetEventParameter(keyEvent, kEventParamKeyUnicodes, typeUnicodeText, 0, 0, &unilen, 0) + == noErr && unilen == 2) { + GetEventParameter(keyEvent, kEventParamKeyUnicodes, typeUnicodeText, 0, unilen, 0, outChar); + } else if (translatedChar) { + static QTextCodec *c = 0; + if (!c) + c = QTextCodec::codecForName("Apple Roman"); + char tmpChar = (char)translatedChar; // **sigh** + *outChar = c->toUnicode(&tmpChar, 1).at(0); + } else { + *qtKey = qt_mac_get_key(*outModifiers, QChar(translatedChar), keyCode); + } + } + } +#endif + if (*qtKey == Qt::Key_unknown) + *qtKey = qt_mac_get_key(*outModifiers, *outChar, keyCode); + return true; +} + +QKeyMapperPrivate::QKeyMapperPrivate() +{ + memset(keyLayout, 0, sizeof(keyLayout)); + keyboard_layout_format.unicode = 0; +#ifdef Q_OS_MAC32 + keyboard_mode = NullMode; +#else + currentInputSource = 0; +#endif +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ + deleteLayouts(); +} + +bool +QKeyMapperPrivate::updateKeyboard() +{ + const UCKeyboardLayout *uchrData = 0; +#ifdef Q_OS_MAC32 + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + + if (keyboard_mode != NullMode && currentKeyboardLayout == keyLayoutRef) + return false; + + OSStatus err; + if (keyLayoutRef != 0) { + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLuchrData, + const_cast<const void **>(reinterpret_cast<const void **>(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get unicode keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType<TISInputSourceRef> source = TISCopyCurrentKeyboardInputSource(); + if (keyboard_mode != NullMode && source == currentInputSource) { + return false; + } + Q_ASSERT(source != 0); + CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(source, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : 0; +#endif + + keyboard_kind = LMGetKbdType(); + if (uchrData) { + keyboard_layout_format.unicode = uchrData; + keyboard_mode = UnicodeMode; + } +#ifdef Q_OS_MAC32 + else { + void *happy; + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLKCHRData, + const_cast<const void **>(reinterpret_cast<void **>(&happy))); + if (err != noErr) { + qFatal("Qt::internal::unable to get non-unicode layout, cannot procede %ld %s:%d", + long(err), __FILE__, __LINE__); + } + keyboard_layout_format.other = happy; + keyboard_mode = OtherMode; + } + + currentKeyboardLayout = keyLayoutRef; +#else + currentInputSource = source; +#endif + keyboard_dead = 0; + CFStringRef iso639Code; +#ifdef Q_OS_MAC32 +# ifndef kKLLanguageCode +# define kKLLanguageCode 9 +# endif + KLGetKeyboardLayoutProperty(currentKeyboardLayout, kKLLanguageCode, + reinterpret_cast<const void **>(&iso639Code)); +#else + CFArrayRef array = static_cast<CFArrayRef>(TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceLanguages)); + iso639Code = static_cast<CFStringRef>(CFArrayGetValueAtIndex(array, 0)); // Actually a RFC3066bis, but it's close enough +#endif + if (iso639Code) { + keyboardInputLocale = QLocale(QCFString::toQString(iso639Code)); + keyboardInputDirection = keyboardInputLocale.textDirection(); + } else { + keyboardInputLocale = QLocale::c(); + keyboardInputDirection = Qt::LeftToRight; + } + return true; +} + +void +QKeyMapperPrivate::deleteLayouts() +{ + keyboard_mode = NullMode; + for (int i = 0; i < 255; ++i) { + if (keyLayout[i]) { + delete keyLayout[i]; + keyLayout[i] = 0; + } + } +} + +void +QKeyMapperPrivate::clearMappings() +{ + deleteLayouts(); + updateKeyboard(); +} + +QList<int> +QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList<int> ret; + + KeyboardLayoutItem *kbItem = keyLayout[e->nativeVirtualKey()]; + if (!kbItem) // Key is not in any keyboard layout (e.g. eisu-key on Japanese keyboard) + return ret; + + int baseKey = kbItem->qtKey[0]; + Qt::KeyboardModifiers keyMods = e->modifiers(); + ret << int(baseKey + keyMods); // The base key is _always_ valid, of course + + for (int i = 1; i < 8; ++i) { + Qt::KeyboardModifiers neededMods = ModsTbl[i]; + int key = kbItem->qtKey[i]; + if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) + ret << int(key + (keyMods & ~neededMods)); + } + + return ret; +} + +bool QKeyMapperPrivate::translateKeyEvent(QWidget *widget, EventHandlerCallRef er, EventRef event, + void *info, bool grab) +{ + Q_ASSERT(GetEventClass(event) == kEventClassKeyboard); + bool handled_event=true; + UInt32 ekind = GetEventKind(event); + + // unfortunately modifiers changed event looks quite different, so I have a separate + // code path + if (ekind == kEventRawKeyModifiersChanged) { + //figure out changed modifiers, wish Apple would just send a delta + UInt32 modifiers = 0; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(modifiers), 0, &modifiers); + qt_mac_send_modifiers_changed(modifiers, widget); + return true; + } + + QInputContext *currentContext = qApp->inputContext(); + if (currentContext && currentContext->isComposing()) { + if (ekind == kEventRawKeyDown) { + QMacInputContext *context = qobject_cast<QMacInputContext*>(currentContext); + if (context) + context->setLastKeydownEvent(event); + } + return false; + } + // Once we process the key down , we don't need to send the saved event again from + // kEventTextInputUnicodeForKeyEvent, so clear it. + if (currentContext && ekind == kEventRawKeyDown) { + QMacInputContext *context = qobject_cast<QMacInputContext*>(currentContext); + if (context) + context->setLastKeydownEvent(0); + } + + //get modifiers + Qt::KeyboardModifiers modifiers; + int qtKey; + QChar ourChar; + if (translateKeyEventInternal(er, event, &qtKey, &ourChar, &modifiers, + &handled_event) == false) + return handled_event; + QString text(ourChar); + /* This is actually wrong - but unfortunately it is the best that can be + done for now because of the Control/Meta mapping problems */ + if (modifiers & (Qt::ControlModifier | Qt::MetaModifier) + && !qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + text = QString(); + } + + + if (widget) { +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(info); + // Try not to call "other" event handlers if we have a popup, + // However, if the key has text + // then we should pass it along because otherwise then people + // can use input method stuff. + if (!qApp->activePopupWidget() + || (qApp->activePopupWidget() && !text.isEmpty())) { + //Find out if someone else wants the event, namely + //is it of use to text services? If so we won't bother + //with a QKeyEvent. + qt_mac_eat_unicode_key = false; + if (er) + CallNextEventHandler(er, event); + extern bool qt_mac_menubar_is_open(); + if (qt_mac_eat_unicode_key || qt_mac_menubar_is_open()) { + return true; + } + } +#endif + // Try to compress key events. + if (!text.isEmpty() && widget->testAttribute(Qt::WA_KeyCompression)) { + EventTime lastTime = GetEventTime(event); + for (;;) { + EventRef releaseEvent = FindSpecificEventInQueue(GetMainEventQueue(), + qt_KeyEventComparatorProc, + (void*)kEventRawKeyUp); + if (!releaseEvent) + break; + const EventTime releaseTime = GetEventTime(releaseEvent); + if (releaseTime < lastTime) + break; + lastTime = releaseTime; + + EventRef pressEvent = FindSpecificEventInQueue(GetMainEventQueue(), + qt_KeyEventComparatorProc, + (void*)kEventRawKeyDown); + if (!pressEvent) + break; + const EventTime pressTime = GetEventTime(pressEvent); + if (pressTime < lastTime) + break; + lastTime = pressTime; + + Qt::KeyboardModifiers compressMod; + int compressQtKey = 0; + QChar compressChar; + if (translateKeyEventInternal(er, pressEvent, + &compressQtKey, &compressChar, &compressMod, 0) + == false) { + break; + } + // Copied from qapplication_x11.cpp (change both). + + bool stopCompression = + // 1) misc keys + (compressQtKey >= Qt::Key_Escape && compressQtKey <= Qt::Key_SysReq) + // 2) cursor movement + || (compressQtKey >= Qt::Key_Home && compressQtKey <= Qt::Key_PageDown) + // 3) extra keys + || (compressQtKey >= Qt::Key_Super_L && compressQtKey <= Qt::Key_Direction_R) + // 4) something that a) doesn't translate to text or b) translates + // to newline text + || (compressQtKey == 0) + || (compressChar == QLatin1Char('\n')) + || (compressQtKey == Qt::Key_unknown); + + if (compressMod == modifiers && !compressChar.isNull() && !stopCompression) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("compressing away %c", compressChar.toLatin1()); +#endif + text += compressChar; + // Clean up + RemoveEventFromQueue(GetMainEventQueue(), releaseEvent); + RemoveEventFromQueue(GetMainEventQueue(), pressEvent); + } else { +#ifdef DEBUG_KEY_BINDINGS + qDebug("stoping compression.."); +#endif + break; + } + } + } + + // There is no way to get the scan code from carbon. But we cannot use the value 0, since + // it indicates that the event originates from somewhere else than the keyboard + UInt32 macScanCode = 1; + UInt32 macVirtualKey = 0; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, 0, sizeof(macVirtualKey), 0, &macVirtualKey); + UInt32 macModifiers = 0; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(macModifiers), 0, &macModifiers); +#ifdef QT_MAC_USE_COCOA + // The unicode characters in the range 0xF700-0xF747 are reserved + // by Mac OS X for transient use as keyboard function keys. We + // wont send 'text' for such key events. This is done to match + // behavior on other platforms. + unsigned int *unicodeKey = (unsigned int*)info; + if (*unicodeKey >= 0xf700 && *unicodeKey <= 0xf747) + text = QString(); + bool isAccepted; +#endif + handled_event = QKeyMapper::sendKeyEvent(widget, grab, + (ekind == kEventRawKeyUp) ? QEvent::KeyRelease : QEvent::KeyPress, + qtKey, modifiers, text, ekind == kEventRawKeyRepeat, 0, + macScanCode, macVirtualKey, macModifiers +#ifdef QT_MAC_USE_COCOA + ,&isAccepted +#endif + ); +#ifdef QT_MAC_USE_COCOA + *unicodeKey = (unsigned int)isAccepted; +#endif + } + return handled_event; +} + +void +QKeyMapperPrivate::updateKeyMap(EventHandlerCallRef, EventRef event, void * +#if defined(QT_MAC_USE_COCOA) + unicodeKey // unicode character from NSEvent (modifiers applied) +#endif + ) +{ + UInt32 macVirtualKey = 0; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, 0, sizeof(macVirtualKey), 0, &macVirtualKey); + if (updateKeyboard()) + QKeyMapper::changeKeyboard(); + else if (keyLayout[macVirtualKey]) + return; + + UniCharCount buffer_size = 10; + UniChar buffer[buffer_size]; + keyLayout[macVirtualKey] = new KeyboardLayoutItem; + for (int i = 0; i < 16; ++i) { + UniCharCount out_buffer_size = 0; + keyLayout[macVirtualKey]->qtKey[i] = 0; +#ifdef Q_WS_MAC32 + if (keyboard_mode == UnicodeMode) { +#endif + const UInt32 keyModifier = ((qt_mac_get_mac_modifiers(ModsTbl[i]) >> 8) & 0xFF); + OSStatus err = UCKeyTranslate(keyboard_layout_format.unicode, macVirtualKey, kUCKeyActionDown, keyModifier, + keyboard_kind, 0, &keyboard_dead, buffer_size, &out_buffer_size, buffer); + if (err == noErr && out_buffer_size) { + const QChar unicode(buffer[0]); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } +#ifndef Q_WS_MAC32 + else { + const QChar unicode(*((UniChar *)unicodeKey)); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } +#endif +#ifdef Q_WS_MAC32 + } else { + const UInt32 keyModifier = (qt_mac_get_mac_modifiers(ModsTbl[i])); + + uchar translatedChar = KeyTranslate(keyboard_layout_format.other, keyModifier | macVirtualKey, &keyboard_dead); + if (translatedChar) { + static QTextCodec *c = 0; + if (!c) + c = QTextCodec::codecForName("Apple Roman"); + const QChar unicode(c->toUnicode((const char *)&translatedChar, 1).at(0)); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } + } +#endif + } +#ifdef DEBUG_KEY_MAPS + qDebug("updateKeyMap for virtual key = 0x%02x!", (uint)macVirtualKey); + for (int i = 0; i < 16; ++i) { + qDebug(" [%d] (%d,0x%02x,'%c')", i, + keyLayout[macVirtualKey]->qtKey[i], + keyLayout[macVirtualKey]->qtKey[i], + keyLayout[macVirtualKey]->qtKey[i]); + } +#endif +} + +bool +QKeyMapper::sendKeyEvent(QWidget *widget, bool grab, + QEvent::Type type, int code, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorepeat, int count, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, bool *isAccepted) +{ + Q_UNUSED(count); + if (widget && widget->isEnabled()) { + bool key_event = true; +#if defined(QT3_SUPPORT) && !defined(QT_NO_SHORTCUT) + if (type == QEvent::KeyPress && !grab + && QApplicationPrivate::instance()->use_compat()) { + QKeyEventEx accel_ev(type, code, modifiers, + text, autorepeat, qMax(1, int(text.length())), + nativeScanCode, nativeVirtualKey, nativeModifiers); + if (QApplicationPrivate::instance()->qt_tryAccelEvent(widget, &accel_ev)) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: %s::%s consumed Accel: %s", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData()); +#endif + key_event = false; + } else { + if (accel_ev.isAccepted()) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: %s::%s overrode Accel: %s", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData()); +#endif + } + } + } +#else +Q_UNUSED(grab); +#endif // QT3_SUPPORT && !QT_NO_SHORTCUT + if (key_event) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: Sending %s to %s::%s: %s 0x%08x%s", + type == QEvent::KeyRelease ? "KeyRelease" : "KeyPress", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData(), int(modifiers), + autorepeat ? " Repeat" : ""); +#endif + QKeyEventEx ke(type, code, modifiers, text, autorepeat, qMax(1, text.length()), + nativeScanCode, nativeVirtualKey, nativeModifiers); + bool retMe = qt_sendSpontaneousEvent(widget,&ke); + if (isAccepted) + *isAccepted = ke.isAccepted(); + return retMe; + } + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_p.h b/src/gui/kernel/qkeymapper_p.h new file mode 100644 index 0000000000..ec2d8492fe --- /dev/null +++ b/src/gui/kernel/qkeymapper_p.h @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QKEYMAPPER_P_H +#define QKEYMAPPER_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 <qobject.h> +#include <private/qobject_p.h> +#include <qkeysequence.h> +#include <qlist.h> +#include <qlocale.h> +#include <qevent.h> +#include <qhash.h> + +#if defined (Q_WS_MAC64) +# include <private/qt_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class QKeyMapperPrivate; +class QKeyMapper : public QObject +{ + Q_OBJECT +public: + explicit QKeyMapper(); + ~QKeyMapper(); + + static QKeyMapper *instance(); + static void changeKeyboard(); + static bool sendKeyEvent(QWidget *widget, bool grab, + QEvent::Type type, int code, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorepeat, int count, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, + bool *unusedExceptForCocoa = 0); + static QList<int> possibleKeys(QKeyEvent *e); + +private: + friend QKeyMapperPrivate *qt_keymapper_private(); + Q_DECLARE_PRIVATE(QKeyMapper) + Q_DISABLE_COPY(QKeyMapper) +}; + + + +#if defined(Q_OS_WIN) +enum WindowsNativeModifiers { + ShiftLeft = 0x00000001, + ControlLeft = 0x00000002, + AltLeft = 0x00000004, + MetaLeft = 0x00000008, + ShiftRight = 0x00000010, + ControlRight = 0x00000020, + AltRight = 0x00000040, + MetaRight = 0x00000080, + CapsLock = 0x00000100, + NumLock = 0x00000200, + ScrollLock = 0x00000400, + ExtendedKey = 0x01000000, + + // Convenience mappings + ShiftAny = 0x00000011, + ControlAny = 0x00000022, + AltAny = 0x00000044, + MetaAny = 0x00000088, + LockAny = 0x00000700 +}; +# if !defined(tagMSG) + typedef struct tagMSG MSG; +# endif +#elif defined(Q_WS_MAC) +QT_BEGIN_INCLUDE_NAMESPACE +# include <private/qt_mac_p.h> +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_X11) + +QT_BEGIN_INCLUDE_NAMESPACE +typedef ulong XID; +typedef XID KeySym; +QT_END_INCLUDE_NAMESPACE + +struct QXCoreDesc { + int min_keycode; + int max_keycode; + int keysyms_per_keycode; + KeySym *keysyms; + uchar mode_switch; + uchar num_lock; + KeySym lock_meaning; +}; + +#endif + +struct KeyboardLayoutItem; +typedef struct __TISInputSource * TISInputSourceRef; +class QKeyEvent; +class QKeyMapperPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QKeyMapper) +public: + QKeyMapperPrivate(); + ~QKeyMapperPrivate(); + + void clearMappings(); + QList<int> possibleKeys(QKeyEvent *e); + + QLocale keyboardInputLocale; + Qt::LayoutDirection keyboardInputDirection; + +#if defined(Q_OS_WIN) + void clearRecordedKeys(); + void updateKeyMap(const MSG &msg); + bool translateKeyEvent(QWidget *receiver, const MSG &msg, bool grab); + void updatePossibleKeyCodes(unsigned char *kbdBuffer, quint32 scancode, quint32 vk_key); + bool isADeadKey(unsigned int vk_key, unsigned int modifiers); + void deleteLayouts(); + + KeyboardLayoutItem *keyLayout[256]; + +#elif defined(Q_WS_X11) + + QList<int> possibleKeysXKB(QKeyEvent *event); + QList<int> possibleKeysCore(QKeyEvent *event); + + bool translateKeyEventInternal(QWidget *keywidget, + const XEvent *, + KeySym &keysym, + int& count, + QString& text, + Qt::KeyboardModifiers& modifiers, + int &code, + QEvent::Type &type, + bool statefulTranslation = true); + bool translateKeyEvent(QWidget *keywidget, + const XEvent *, + bool grab); + + int xkb_currentGroup; + QXCoreDesc coreDesc; + +#elif defined(Q_WS_MAC) + bool updateKeyboard(); + void updateKeyMap(EventHandlerCallRef, EventRef, void *); + bool translateKeyEvent(QWidget *, EventHandlerCallRef, EventRef, void *, bool); + void deleteLayouts(); + + enum { NullMode, UnicodeMode, OtherMode } keyboard_mode; + union { + const UCKeyboardLayout *unicode; + void *other; + } keyboard_layout_format; +#ifdef Q_WS_MAC64 + QCFType<TISInputSourceRef> currentInputSource; +#else + KeyboardLayoutRef currentKeyboardLayout; +#endif + KeyboardLayoutKind keyboard_kind; + UInt32 keyboard_dead; + KeyboardLayoutItem *keyLayout[256]; +#elif defined(Q_WS_QWS) +#elif defined(Q_OS_SYMBIAN) +public: + QString translateKeyEvent(int keySym, Qt::KeyboardModifiers modifiers); + int mapS60KeyToQt(TUint s60key); + int mapS60ScanCodesToQt(TUint s60key); + int mapQtToS60Key(int qtKey); + int mapQtToS60ScanCodes(int qtKey); + void updateInputLanguage(); +#endif +}; + +QKeyMapperPrivate *qt_keymapper_private(); // from qkeymapper.cpp + +QT_END_NAMESPACE + +#endif // QKEYMAPPER_P_H diff --git a/src/gui/kernel/qkeymapper_qws.cpp b/src/gui/kernel/qkeymapper_qws.cpp new file mode 100644 index 0000000000..7e4114057f --- /dev/null +++ b/src/gui/kernel/qkeymapper_qws.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeymapper_p.h" +#include <qdebug.h> +#include <private/qevent_p.h> +#include <private/qlocale_p.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + + +QKeyMapperPrivate::QKeyMapperPrivate() +{ + keyboardInputLocale = QLocale::system(); + keyboardInputDirection = keyboardInputLocale.textDirection(); +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ + // clearMappings(); +} + +void QKeyMapperPrivate::clearMappings() +{ +} + +QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList<int> result; + if (e->key() && (e->key() != Qt::Key_unknown)) + result << int(e->key() + e->modifiers()); + else if (!e->text().isEmpty()) + result << int(e->text().at(0).unicode() + e->modifiers()); + return result; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_s60.cpp b/src/gui/kernel/qkeymapper_s60.cpp new file mode 100644 index 0000000000..08cfae0d2d --- /dev/null +++ b/src/gui/kernel/qkeymapper_s60.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qkeymapper_p.h" +#include <private/qcore_symbian_p.h> +#include <e32keys.h> +#include <e32cmn.h> +#include <centralrepository.h> +#include <biditext.h> + +QT_BEGIN_NAMESPACE + +QKeyMapperPrivate::QKeyMapperPrivate() +{ +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ +} + +QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent * /* e */) +{ + QList<int> result; + return result; +} + +void QKeyMapperPrivate::clearMappings() +{ + // stub +} + +QString QKeyMapperPrivate::translateKeyEvent(int keySym, Qt::KeyboardModifiers /* modifiers */) +{ + if (keySym >= Qt::Key_Escape) { + switch (keySym) { + case Qt::Key_Tab: + return QString(QChar('\t')); + case Qt::Key_Return: // fall through + case Qt::Key_Enter: + return QString(QChar('\r')); + default: + return QString(); + } + } + + // Symbian doesn't actually use modifiers, but gives us the character code directly. + + return QString(QChar(keySym)); +} + +#include <e32keys.h> +struct KeyMapping{ + TKeyCode s60KeyCode; + TStdScanCode s60ScanCode; + Qt::Key qtKey; +}; + +using namespace Qt; + +static const KeyMapping keyMapping[] = { + {EKeyBackspace, EStdKeyBackspace, Key_Backspace}, + {EKeyTab, EStdKeyTab, Key_Tab}, + {EKeyEnter, EStdKeyEnter, Key_Enter}, + {EKeyEscape, EStdKeyEscape, Key_Escape}, + {EKeySpace, EStdKeySpace, Key_Space}, + {EKeyDelete, EStdKeyDelete, Key_Delete}, + {EKeyPrintScreen, EStdKeyPrintScreen, Key_SysReq}, + {EKeyPause, EStdKeyPause, Key_Pause}, + {EKeyHome, EStdKeyHome, Key_Home}, + {EKeyEnd, EStdKeyEnd, Key_End}, + {EKeyPageUp, EStdKeyPageUp, Key_PageUp}, + {EKeyPageDown, EStdKeyPageDown, Key_PageDown}, + {EKeyInsert, EStdKeyInsert, Key_Insert}, + {EKeyLeftArrow, EStdKeyLeftArrow, Key_Left}, + {EKeyRightArrow, EStdKeyRightArrow, Key_Right}, + {EKeyUpArrow, EStdKeyUpArrow, Key_Up}, + {EKeyDownArrow, EStdKeyDownArrow, Key_Down}, + {EKeyLeftShift, EStdKeyLeftShift, Key_Shift}, + {EKeyRightShift, EStdKeyRightShift, Key_Shift}, + {EKeyLeftAlt, EStdKeyLeftAlt, Key_Alt}, + {EKeyRightAlt, EStdKeyRightAlt, Key_AltGr}, + {EKeyLeftCtrl, EStdKeyLeftCtrl, Key_Control}, + {EKeyRightCtrl, EStdKeyRightCtrl, Key_Control}, + {EKeyLeftFunc, EStdKeyLeftFunc, Key_Super_L}, + {EKeyRightFunc, EStdKeyRightFunc, Key_Super_R}, + {EKeyCapsLock, EStdKeyCapsLock, Key_CapsLock}, + {EKeyNumLock, EStdKeyNumLock, Key_NumLock}, + {EKeyScrollLock, EStdKeyScrollLock, Key_ScrollLock}, + {EKeyF1, EStdKeyF1, Key_F1}, + {EKeyF2, EStdKeyF2, Key_F2}, + {EKeyF3, EStdKeyF3, Key_F3}, + {EKeyF4, EStdKeyF4, Key_F4}, + {EKeyF5, EStdKeyF5, Key_F5}, + {EKeyF6, EStdKeyF6, Key_F6}, + {EKeyF7, EStdKeyF7, Key_F7}, + {EKeyF8, EStdKeyF8, Key_F8}, + {EKeyF9, EStdKeyF9, Key_F9}, + {EKeyF10, EStdKeyF10, Key_F10}, + {EKeyF11, EStdKeyF11, Key_F11}, + {EKeyF12, EStdKeyF12, Key_F12}, + {EKeyF13, EStdKeyF13, Key_F13}, + {EKeyF14, EStdKeyF14, Key_F14}, + {EKeyF15, EStdKeyF15, Key_F15}, + {EKeyF16, EStdKeyF16, Key_F16}, + {EKeyF17, EStdKeyF17, Key_F17}, + {EKeyF18, EStdKeyF18, Key_F18}, + {EKeyF19, EStdKeyF19, Key_F19}, + {EKeyF20, EStdKeyF20, Key_F20}, + {EKeyF21, EStdKeyF21, Key_F21}, + {EKeyF22, EStdKeyF22, Key_F22}, + {EKeyF23, EStdKeyF23, Key_F23}, + {EKeyF24, EStdKeyF24, Key_F24}, + {EKeyOff, EStdKeyOff, Key_PowerOff}, +// {EKeyMenu, EStdKeyMenu, Key_Menu}, // Menu is EKeyApplication0 + {EKeyHelp, EStdKeyHelp, Key_Help}, + {EKeyDial, EStdKeyDial, Key_Call}, + {EKeyIncVolume, EStdKeyIncVolume, Key_VolumeUp}, + {EKeyDecVolume, EStdKeyDecVolume, Key_VolumeDown}, + {EKeyDevice0, EStdKeyDevice0, Key_Context1}, // Found by manual testing. + {EKeyDevice1, EStdKeyDevice1, Key_Context2}, // Found by manual testing. + {EKeyDevice3, EStdKeyDevice3, Key_Select}, + {EKeyDevice7, EStdKeyDevice7, Key_Camera}, + {EKeyApplication0, EStdKeyApplication0, Key_Menu}, // Found by manual testing. + {EKeyApplication1, EStdKeyApplication1, Key_Launch1}, // Found by manual testing. + {EKeyApplication2, EStdKeyApplication2, Key_MediaPlay}, // Found by manual testing. + {EKeyApplication3, EStdKeyApplication3, Key_MediaStop}, // Found by manual testing. + {EKeyApplication4, EStdKeyApplication4, Key_MediaNext}, // Found by manual testing. + {EKeyApplication5, EStdKeyApplication5, Key_MediaPrevious}, // Found by manual testing. + {EKeyApplication6, EStdKeyApplication6, Key_Launch6}, + {EKeyApplication7, EStdKeyApplication7, Key_Launch7}, + {EKeyApplication8, EStdKeyApplication8, Key_Launch8}, + {EKeyApplication9, EStdKeyApplication9, Key_Launch9}, + {EKeyApplicationA, EStdKeyApplicationA, Key_LaunchA}, + {EKeyApplicationB, EStdKeyApplicationB, Key_LaunchB}, + {EKeyApplicationC, EStdKeyApplicationC, Key_LaunchC}, + {EKeyApplicationD, EStdKeyApplicationD, Key_LaunchD}, + {EKeyApplicationE, EStdKeyApplicationE, Key_LaunchE}, + {EKeyApplicationF, EStdKeyApplicationF, Key_LaunchF}, + {EKeyApplication19, EStdKeyApplication19, Key_CameraFocus}, + {EKeyYes, EStdKeyYes, Key_Yes}, + {EKeyNo, EStdKeyNo, Key_No}, + {TKeyCode(0), TStdScanCode(0), Qt::Key(0)} +}; + +int QKeyMapperPrivate::mapS60KeyToQt(TUint s60key) +{ + int res = Qt::Key_unknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].s60KeyCode == s60key) { + res = keyMapping[i].qtKey; + break; + } + } + return res; +} + +int QKeyMapperPrivate::mapS60ScanCodesToQt(TUint s60scanCode) +{ + int res = Qt::Key_unknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].s60ScanCode == s60scanCode) { + res = keyMapping[i].qtKey; + break; + } + } + return res; +} + +int QKeyMapperPrivate::mapQtToS60Key(int qtKey) +{ + int res = KErrUnknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].qtKey == qtKey) { + res = keyMapping[i].s60KeyCode; + break; + } + } + return res; +} + +int QKeyMapperPrivate::mapQtToS60ScanCodes(int qtKey) +{ + int res = KErrUnknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].qtKey == qtKey) { + res = keyMapping[i].s60ScanCode; + break; + } + } + return res; +} + +void QKeyMapperPrivate::updateInputLanguage() +{ +#ifdef Q_WS_S60 + TInt err; + CRepository *repo; + const TUid KCRUidAknFep = TUid::Uid(0x101F876D); + const TUint32 KAknFepInputTxtLang = 0x00000005; + TRAP(err, repo = CRepository::NewL(KCRUidAknFep)); + if (err != KErrNone) + return; + + TInt symbianLang; + err = repo->Get(KAknFepInputTxtLang, symbianLang); + delete repo; + if (err != KErrNone) + return; + + QString qtLang = QString::fromAscii(qt_symbianLocaleName(symbianLang)); + keyboardInputLocale = QLocale(qtLang); + keyboardInputDirection = (TBidiText::ScriptDirectionality(TLanguage(symbianLang)) == TBidiText::ERightToLeft) + ? Qt::RightToLeft : Qt::LeftToRight; +#else + keyboardInputLocale = QLocale(); + keyboardInputDirection = Qt::LeftToRight; +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_win.cpp b/src/gui/kernel/qkeymapper_win.cpp new file mode 100644 index 0000000000..92fa582617 --- /dev/null +++ b/src/gui/kernel/qkeymapper_win.cpp @@ -0,0 +1,1207 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeymapper_p.h" + +#include <qt_windows.h> +#include <qdebug.h> +#include <private/qevent_p.h> +#include <private/qlocale_p.h> +#include <private/qapplication_p.h> +#include <qwidget.h> +#include <qapplication.h> +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +// Uncommend, to show debugging information for the keymapper +//#define DEBUG_KEYMAPPER + +// Implemented elsewhere +extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND, UINT, WPARAM, LPARAM); + +extern Q_CORE_EXPORT QLocale qt_localeFromLCID(LCID id); +#ifndef LANG_PASHTO +#define LANG_PASHTO 0x63 +#endif +#ifndef LANG_SYRIAC +#define LANG_SYRIAC 0x5a +#endif +#ifndef LANG_DIVEHI +#define LANG_DIVEHI 0x65 +#endif +#ifndef VK_OEM_PLUS +#define VK_OEM_PLUS 0xBB +#endif +#ifndef VK_OEM_3 +#define VK_OEM_3 0xC0 +#endif + +#if defined(Q_OS_WINCE) +bool GetKeyboardState(unsigned char* kbuffer) +{ + for (int i=0; i< 256; ++i) + kbuffer[i] = GetAsyncKeyState(i); + return true; +} +#endif +// Key recorder ------------------------------------------------------------------------[ start ] -- +struct KeyRecord { + KeyRecord(int c, int a, int s, const QString &t) : code(c), ascii(a), state(s), text(t) {} + KeyRecord() {} + + int code; + int ascii; + int state; + QString text; +}; + +static const int QT_MAX_KEY_RECORDINGS = 64; // User has LOTS of fingers... +struct KeyRecorder +{ + KeyRecorder() : nrecs(0) {} + + inline KeyRecord *findKey(int code, bool remove); + inline void storeKey(int code, int ascii, int state, const QString& text); + inline void clearKeys(); + + int nrecs; + KeyRecord deleted_record; // A copy of last entry removed from records[] + KeyRecord records[QT_MAX_KEY_RECORDINGS]; +}; +static KeyRecorder key_recorder; + +KeyRecord *KeyRecorder::findKey(int code, bool remove) +{ + KeyRecord *result = 0; + for (int i = 0; i < nrecs; ++i) { + if (records[i].code == code) { + if (remove) { + deleted_record = records[i]; + // Move rest down, and decrease count + while (i + 1 < nrecs) { + records[i] = records[i + 1]; + ++i; + } + --nrecs; + result = &deleted_record; + } else { + result = &records[i]; + } + break; + } + } + return result; +} + +void KeyRecorder::storeKey(int code, int ascii, int state, const QString& text) +{ + Q_ASSERT_X(nrecs != QT_MAX_KEY_RECORDINGS, + "Internal KeyRecorder", + "Keyboard recorder buffer overflow, consider increasing QT_MAX_KEY_RECORDINGS"); + + if (nrecs == QT_MAX_KEY_RECORDINGS) { + qWarning("Qt: Internal keyboard buffer overflow"); + return; + } + records[nrecs++] = KeyRecord(code,ascii,state,text); +} + +void KeyRecorder::clearKeys() +{ + nrecs = 0; +} +// Key recorder --------------------------------------------------------------------------[ end ] -- + + +// Key translation ---------------------------------------------------------------------[ start ] -- +// Meaning of values: +// 0 = Character output key, needs keyboard driver mapping +// Key_unknown = Unknown Virtual Key, no translation possible, ignore +static const uint KeyTbl[] = { // Keyboard mapping table + // Dec | Hex | Windows Virtual key + Qt::Key_unknown, // 0 0x00 + Qt::Key_unknown, // 1 0x01 VK_LBUTTON | Left mouse button + Qt::Key_unknown, // 2 0x02 VK_RBUTTON | Right mouse button + Qt::Key_Cancel, // 3 0x03 VK_CANCEL | Control-Break processing + Qt::Key_unknown, // 4 0x04 VK_MBUTTON | Middle mouse button + Qt::Key_unknown, // 5 0x05 VK_XBUTTON1 | X1 mouse button + Qt::Key_unknown, // 6 0x06 VK_XBUTTON2 | X2 mouse button + Qt::Key_unknown, // 7 0x07 -- unassigned -- + Qt::Key_Backspace, // 8 0x08 VK_BACK | BackSpace key + Qt::Key_Tab, // 9 0x09 VK_TAB | Tab key + Qt::Key_unknown, // 10 0x0A -- reserved -- + Qt::Key_unknown, // 11 0x0B -- reserved -- + Qt::Key_Clear, // 12 0x0C VK_CLEAR | Clear key + Qt::Key_Return, // 13 0x0D VK_RETURN | Enter key + Qt::Key_unknown, // 14 0x0E -- unassigned -- + Qt::Key_unknown, // 15 0x0F -- unassigned -- + Qt::Key_Shift, // 16 0x10 VK_SHIFT | Shift key + Qt::Key_Control, // 17 0x11 VK_CONTROL | Ctrl key + Qt::Key_Alt, // 18 0x12 VK_MENU | Alt key + Qt::Key_Pause, // 19 0x13 VK_PAUSE | Pause key + Qt::Key_CapsLock, // 20 0x14 VK_CAPITAL | Caps-Lock + Qt::Key_unknown, // 21 0x15 VK_KANA / VK_HANGUL | IME Kana or Hangul mode + Qt::Key_unknown, // 22 0x16 -- unassigned -- + Qt::Key_unknown, // 23 0x17 VK_JUNJA | IME Junja mode + Qt::Key_unknown, // 24 0x18 VK_FINAL | IME final mode + Qt::Key_unknown, // 25 0x19 VK_HANJA / VK_KANJI | IME Hanja or Kanji mode + Qt::Key_unknown, // 26 0x1A -- unassigned -- + Qt::Key_Escape, // 27 0x1B VK_ESCAPE | Esc key + Qt::Key_unknown, // 28 0x1C VK_CONVERT | IME convert + Qt::Key_unknown, // 29 0x1D VK_NONCONVERT | IME non-convert + Qt::Key_unknown, // 30 0x1E VK_ACCEPT | IME accept + Qt::Key_Mode_switch,// 31 0x1F VK_MODECHANGE | IME mode change request + Qt::Key_Space, // 32 0x20 VK_SPACE | Spacebar + Qt::Key_PageUp, // 33 0x21 VK_PRIOR | Page Up key + Qt::Key_PageDown, // 34 0x22 VK_NEXT | Page Down key + Qt::Key_End, // 35 0x23 VK_END | End key + Qt::Key_Home, // 36 0x24 VK_HOME | Home key + Qt::Key_Left, // 37 0x25 VK_LEFT | Left arrow key + Qt::Key_Up, // 38 0x26 VK_UP | Up arrow key + Qt::Key_Right, // 39 0x27 VK_RIGHT | Right arrow key + Qt::Key_Down, // 40 0x28 VK_DOWN | Down arrow key + Qt::Key_Select, // 41 0x29 VK_SELECT | Select key + Qt::Key_Printer, // 42 0x2A VK_PRINT | Print key + Qt::Key_Execute, // 43 0x2B VK_EXECUTE | Execute key + Qt::Key_Print, // 44 0x2C VK_SNAPSHOT | Print Screen key + Qt::Key_Insert, // 45 0x2D VK_INSERT | Ins key + Qt::Key_Delete, // 46 0x2E VK_DELETE | Del key + Qt::Key_Help, // 47 0x2F VK_HELP | Help key + 0, // 48 0x30 (VK_0) | 0 key + 0, // 49 0x31 (VK_1) | 1 key + 0, // 50 0x32 (VK_2) | 2 key + 0, // 51 0x33 (VK_3) | 3 key + 0, // 52 0x34 (VK_4) | 4 key + 0, // 53 0x35 (VK_5) | 5 key + 0, // 54 0x36 (VK_6) | 6 key + 0, // 55 0x37 (VK_7) | 7 key + 0, // 56 0x38 (VK_8) | 8 key + 0, // 57 0x39 (VK_9) | 9 key + Qt::Key_unknown, // 58 0x3A -- unassigned -- + Qt::Key_unknown, // 59 0x3B -- unassigned -- + Qt::Key_unknown, // 60 0x3C -- unassigned -- + Qt::Key_unknown, // 61 0x3D -- unassigned -- + Qt::Key_unknown, // 62 0x3E -- unassigned -- + Qt::Key_unknown, // 63 0x3F -- unassigned -- + Qt::Key_unknown, // 64 0x40 -- unassigned -- + 0, // 65 0x41 (VK_A) | A key + 0, // 66 0x42 (VK_B) | B key + 0, // 67 0x43 (VK_C) | C key + 0, // 68 0x44 (VK_D) | D key + 0, // 69 0x45 (VK_E) | E key + 0, // 70 0x46 (VK_F) | F key + 0, // 71 0x47 (VK_G) | G key + 0, // 72 0x48 (VK_H) | H key + 0, // 73 0x49 (VK_I) | I key + 0, // 74 0x4A (VK_J) | J key + 0, // 75 0x4B (VK_K) | K key + 0, // 76 0x4C (VK_L) | L key + 0, // 77 0x4D (VK_M) | M key + 0, // 78 0x4E (VK_N) | N key + 0, // 79 0x4F (VK_O) | O key + 0, // 80 0x50 (VK_P) | P key + 0, // 81 0x51 (VK_Q) | Q key + 0, // 82 0x52 (VK_R) | R key + 0, // 83 0x53 (VK_S) | S key + 0, // 84 0x54 (VK_T) | T key + 0, // 85 0x55 (VK_U) | U key + 0, // 86 0x56 (VK_V) | V key + 0, // 87 0x57 (VK_W) | W key + 0, // 88 0x58 (VK_X) | X key + 0, // 89 0x59 (VK_Y) | Y key + 0, // 90 0x5A (VK_Z) | Z key + Qt::Key_Meta, // 91 0x5B VK_LWIN | Left Windows - MS Natural kbd + Qt::Key_Meta, // 92 0x5C VK_RWIN | Right Windows - MS Natural kbd + Qt::Key_Menu, // 93 0x5D VK_APPS | Application key-MS Natural kbd + Qt::Key_unknown, // 94 0x5E -- reserved -- + Qt::Key_Sleep, // 95 0x5F VK_SLEEP + Qt::Key_0, // 96 0x60 VK_NUMPAD0 | Numeric keypad 0 key + Qt::Key_1, // 97 0x61 VK_NUMPAD1 | Numeric keypad 1 key + Qt::Key_2, // 98 0x62 VK_NUMPAD2 | Numeric keypad 2 key + Qt::Key_3, // 99 0x63 VK_NUMPAD3 | Numeric keypad 3 key + Qt::Key_4, // 100 0x64 VK_NUMPAD4 | Numeric keypad 4 key + Qt::Key_5, // 101 0x65 VK_NUMPAD5 | Numeric keypad 5 key + Qt::Key_6, // 102 0x66 VK_NUMPAD6 | Numeric keypad 6 key + Qt::Key_7, // 103 0x67 VK_NUMPAD7 | Numeric keypad 7 key + Qt::Key_8, // 104 0x68 VK_NUMPAD8 | Numeric keypad 8 key + Qt::Key_9, // 105 0x69 VK_NUMPAD9 | Numeric keypad 9 key + Qt::Key_Asterisk, // 106 0x6A VK_MULTIPLY | Multiply key + Qt::Key_Plus, // 107 0x6B VK_ADD | Add key + Qt::Key_Comma, // 108 0x6C VK_SEPARATOR | Separator key + Qt::Key_Minus, // 109 0x6D VK_SUBTRACT | Subtract key + Qt::Key_Period, // 110 0x6E VK_DECIMAL | Decimal key + Qt::Key_Slash, // 111 0x6F VK_DIVIDE | Divide key + Qt::Key_F1, // 112 0x70 VK_F1 | F1 key + Qt::Key_F2, // 113 0x71 VK_F2 | F2 key + Qt::Key_F3, // 114 0x72 VK_F3 | F3 key + Qt::Key_F4, // 115 0x73 VK_F4 | F4 key + Qt::Key_F5, // 116 0x74 VK_F5 | F5 key + Qt::Key_F6, // 117 0x75 VK_F6 | F6 key + Qt::Key_F7, // 118 0x76 VK_F7 | F7 key + Qt::Key_F8, // 119 0x77 VK_F8 | F8 key + Qt::Key_F9, // 120 0x78 VK_F9 | F9 key + Qt::Key_F10, // 121 0x79 VK_F10 | F10 key + Qt::Key_F11, // 122 0x7A VK_F11 | F11 key + Qt::Key_F12, // 123 0x7B VK_F12 | F12 key + Qt::Key_F13, // 124 0x7C VK_F13 | F13 key + Qt::Key_F14, // 125 0x7D VK_F14 | F14 key + Qt::Key_F15, // 126 0x7E VK_F15 | F15 key + Qt::Key_F16, // 127 0x7F VK_F16 | F16 key + Qt::Key_F17, // 128 0x80 VK_F17 | F17 key + Qt::Key_F18, // 129 0x81 VK_F18 | F18 key + Qt::Key_F19, // 130 0x82 VK_F19 | F19 key + Qt::Key_F20, // 131 0x83 VK_F20 | F20 key + Qt::Key_F21, // 132 0x84 VK_F21 | F21 key + Qt::Key_F22, // 133 0x85 VK_F22 | F22 key + Qt::Key_F23, // 134 0x86 VK_F23 | F23 key + Qt::Key_F24, // 135 0x87 VK_F24 | F24 key + Qt::Key_unknown, // 136 0x88 -- unassigned -- + Qt::Key_unknown, // 137 0x89 -- unassigned -- + Qt::Key_unknown, // 138 0x8A -- unassigned -- + Qt::Key_unknown, // 139 0x8B -- unassigned -- + Qt::Key_unknown, // 140 0x8C -- unassigned -- + Qt::Key_unknown, // 141 0x8D -- unassigned -- + Qt::Key_unknown, // 142 0x8E -- unassigned -- + Qt::Key_unknown, // 143 0x8F -- unassigned -- + Qt::Key_NumLock, // 144 0x90 VK_NUMLOCK | Num Lock key + Qt::Key_ScrollLock, // 145 0x91 VK_SCROLL | Scroll Lock key + // Fujitsu/OASYS kbd -------------------- + 0, //Qt::Key_Jisho, // 146 0x92 VK_OEM_FJ_JISHO | 'Dictionary' key / + // VK_OEM_NEC_EQUAL = key on numpad on NEC PC-9800 kbd + Qt::Key_Massyo, // 147 0x93 VK_OEM_FJ_MASSHOU | 'Unregister word' key + Qt::Key_Touroku, // 148 0x94 VK_OEM_FJ_TOUROKU | 'Register word' key + 0, //Qt::Key_Oyayubi_Left,//149 0x95 VK_OEM_FJ_LOYA | 'Left OYAYUBI' key + 0, //Qt::Key_Oyayubi_Right,//150 0x96 VK_OEM_FJ_ROYA | 'Right OYAYUBI' key + Qt::Key_unknown, // 151 0x97 -- unassigned -- + Qt::Key_unknown, // 152 0x98 -- unassigned -- + Qt::Key_unknown, // 153 0x99 -- unassigned -- + Qt::Key_unknown, // 154 0x9A -- unassigned -- + Qt::Key_unknown, // 155 0x9B -- unassigned -- + Qt::Key_unknown, // 156 0x9C -- unassigned -- + Qt::Key_unknown, // 157 0x9D -- unassigned -- + Qt::Key_unknown, // 158 0x9E -- unassigned -- + Qt::Key_unknown, // 159 0x9F -- unassigned -- + Qt::Key_Shift, // 160 0xA0 VK_LSHIFT | Left Shift key + Qt::Key_Shift, // 161 0xA1 VK_RSHIFT | Right Shift key + Qt::Key_Control, // 162 0xA2 VK_LCONTROL | Left Ctrl key + Qt::Key_Control, // 163 0xA3 VK_RCONTROL | Right Ctrl key + Qt::Key_Alt, // 164 0xA4 VK_LMENU | Left Menu key + Qt::Key_Alt, // 165 0xA5 VK_RMENU | Right Menu key + Qt::Key_Back, // 166 0xA6 VK_BROWSER_BACK | Browser Back key + Qt::Key_Forward, // 167 0xA7 VK_BROWSER_FORWARD | Browser Forward key + Qt::Key_Refresh, // 168 0xA8 VK_BROWSER_REFRESH | Browser Refresh key + Qt::Key_Stop, // 169 0xA9 VK_BROWSER_STOP | Browser Stop key + Qt::Key_Search, // 170 0xAA VK_BROWSER_SEARCH | Browser Search key + Qt::Key_Favorites, // 171 0xAB VK_BROWSER_FAVORITES| Browser Favorites key + Qt::Key_HomePage, // 172 0xAC VK_BROWSER_HOME | Browser Start and Home key + Qt::Key_VolumeMute, // 173 0xAD VK_VOLUME_MUTE | Volume Mute key + Qt::Key_VolumeDown, // 174 0xAE VK_VOLUME_DOWN | Volume Down key + Qt::Key_VolumeUp, // 175 0xAF VK_VOLUME_UP | Volume Up key + Qt::Key_MediaNext, // 176 0xB0 VK_MEDIA_NEXT_TRACK | Next Track key + Qt::Key_MediaPrevious, //177 0xB1 VK_MEDIA_PREV_TRACK | Previous Track key + Qt::Key_MediaStop, // 178 0xB2 VK_MEDIA_STOP | Stop Media key + Qt::Key_MediaPlay, // 179 0xB3 VK_MEDIA_PLAY_PAUSE | Play/Pause Media key + Qt::Key_LaunchMail, // 180 0xB4 VK_LAUNCH_MAIL | Start Mail key + Qt::Key_LaunchMedia,// 181 0xB5 VK_LAUNCH_MEDIA_SELECT Select Media key + Qt::Key_Launch0, // 182 0xB6 VK_LAUNCH_APP1 | Start Application 1 key + Qt::Key_Launch1, // 183 0xB7 VK_LAUNCH_APP2 | Start Application 2 key + Qt::Key_unknown, // 184 0xB8 -- reserved -- + Qt::Key_unknown, // 185 0xB9 -- reserved -- + 0, // 186 0xBA VK_OEM_1 | ';:' for US + 0, // 187 0xBB VK_OEM_PLUS | '+' any country + 0, // 188 0xBC VK_OEM_COMMA | ',' any country + 0, // 189 0xBD VK_OEM_MINUS | '-' any country + 0, // 190 0xBE VK_OEM_PERIOD | '.' any country + 0, // 191 0xBF VK_OEM_2 | '/?' for US + 0, // 192 0xC0 VK_OEM_3 | '`~' for US + Qt::Key_unknown, // 193 0xC1 -- reserved -- + Qt::Key_unknown, // 194 0xC2 -- reserved -- + Qt::Key_unknown, // 195 0xC3 -- reserved -- + Qt::Key_unknown, // 196 0xC4 -- reserved -- + Qt::Key_unknown, // 197 0xC5 -- reserved -- + Qt::Key_unknown, // 198 0xC6 -- reserved -- + Qt::Key_unknown, // 199 0xC7 -- reserved -- + Qt::Key_unknown, // 200 0xC8 -- reserved -- + Qt::Key_unknown, // 201 0xC9 -- reserved -- + Qt::Key_unknown, // 202 0xCA -- reserved -- + Qt::Key_unknown, // 203 0xCB -- reserved -- + Qt::Key_unknown, // 204 0xCC -- reserved -- + Qt::Key_unknown, // 205 0xCD -- reserved -- + Qt::Key_unknown, // 206 0xCE -- reserved -- + Qt::Key_unknown, // 207 0xCF -- reserved -- + Qt::Key_unknown, // 208 0xD0 -- reserved -- + Qt::Key_unknown, // 209 0xD1 -- reserved -- + Qt::Key_unknown, // 210 0xD2 -- reserved -- + Qt::Key_unknown, // 211 0xD3 -- reserved -- + Qt::Key_unknown, // 212 0xD4 -- reserved -- + Qt::Key_unknown, // 213 0xD5 -- reserved -- + Qt::Key_unknown, // 214 0xD6 -- reserved -- + Qt::Key_unknown, // 215 0xD7 -- reserved -- + Qt::Key_unknown, // 216 0xD8 -- unassigned -- + Qt::Key_unknown, // 217 0xD9 -- unassigned -- + Qt::Key_unknown, // 218 0xDA -- unassigned -- + 0, // 219 0xDB VK_OEM_4 | '[{' for US + 0, // 220 0xDC VK_OEM_5 | '\|' for US + 0, // 221 0xDD VK_OEM_6 | ']}' for US + 0, // 222 0xDE VK_OEM_7 | ''"' for US + 0, // 223 0xDF VK_OEM_8 + Qt::Key_unknown, // 224 0xE0 -- reserved -- + Qt::Key_unknown, // 225 0xE1 VK_OEM_AX | 'AX' key on Japanese AX kbd + Qt::Key_unknown, // 226 0xE2 VK_OEM_102 | "<>" or "\|" on RT 102-key kbd + Qt::Key_unknown, // 227 0xE3 VK_ICO_HELP | Help key on ICO + Qt::Key_unknown, // 228 0xE4 VK_ICO_00 | 00 key on ICO + Qt::Key_unknown, // 229 0xE5 VK_PROCESSKEY | IME Process key + Qt::Key_unknown, // 230 0xE6 VK_ICO_CLEAR | + Qt::Key_unknown, // 231 0xE7 VK_PACKET | Unicode char as keystrokes + Qt::Key_unknown, // 232 0xE8 -- unassigned -- + // Nokia/Ericsson definitions --------------- + Qt::Key_unknown, // 233 0xE9 VK_OEM_RESET + Qt::Key_unknown, // 234 0xEA VK_OEM_JUMP + Qt::Key_unknown, // 235 0xEB VK_OEM_PA1 + Qt::Key_unknown, // 236 0xEC VK_OEM_PA2 + Qt::Key_unknown, // 237 0xED VK_OEM_PA3 + Qt::Key_unknown, // 238 0xEE VK_OEM_WSCTRL + Qt::Key_unknown, // 239 0xEF VK_OEM_CUSEL + Qt::Key_unknown, // 240 0xF0 VK_OEM_ATTN + Qt::Key_unknown, // 241 0xF1 VK_OEM_FINISH + Qt::Key_unknown, // 242 0xF2 VK_OEM_COPY + Qt::Key_unknown, // 243 0xF3 VK_OEM_AUTO + Qt::Key_unknown, // 244 0xF4 VK_OEM_ENLW + Qt::Key_unknown, // 245 0xF5 VK_OEM_BACKTAB + Qt::Key_unknown, // 246 0xF6 VK_ATTN | Attn key + Qt::Key_unknown, // 247 0xF7 VK_CRSEL | CrSel key + Qt::Key_unknown, // 248 0xF8 VK_EXSEL | ExSel key + Qt::Key_unknown, // 249 0xF9 VK_EREOF | Erase EOF key + Qt::Key_Play, // 250 0xFA VK_PLAY | Play key + Qt::Key_Zoom, // 251 0xFB VK_ZOOM | Zoom key + Qt::Key_unknown, // 252 0xFC VK_NONAME | Reserved + Qt::Key_unknown, // 253 0xFD VK_PA1 | PA1 key + Qt::Key_Clear, // 254 0xFE VK_OEM_CLEAR | Clear key + 0 +}; + +// Possible modifier states. +// NOTE: The order of these states match the order in QKeyMapperPrivate::updatePossibleKeyCodes()! +static const Qt::KeyboardModifiers ModsTbl[] = { + Qt::NoModifier, // 0 + Qt::ShiftModifier, // 1 + Qt::ControlModifier, // 2 + Qt::ControlModifier | Qt::ShiftModifier, // 3 + Qt::AltModifier, // 4 + Qt::AltModifier | Qt::ShiftModifier, // 5 + Qt::AltModifier | Qt::ControlModifier, // 6 + Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 7 + Qt::NoModifier, // Fall-back to raw Key_* +}; + +/** + Remap return or action key to select key for windows mobile. +*/ +inline int winceKeyBend(int keyCode) +{ +#if defined(Q_OS_WINCE_WM) && defined(QT_KEYPAD_NAVIGATION) + // remap return or action key to select key for windows mobile. + // will be changed to a table remapping function in the next version (4.6/7). + if (keyCode == VK_RETURN && QApplication::keypadNavigationEnabled()) + return Qt::Key_Select; + else + return KeyTbl[keyCode]; +#else + return KeyTbl[keyCode]; +#endif +} + +#if defined(Q_OS_WINCE) + // Use the KeyTbl to resolve a Qt::Key out of the virtual keys. + // In case it is not resolvable, continue using the virtual key itself. + +QT_BEGIN_INCLUDE_NAMESPACE + +int ToUnicode(UINT vk, int /*scancode*/, unsigned char* /*kbdBuffer*/, LPWSTR unicodeBuffer, int, int) +{ + QT_USE_NAMESPACE + QChar* buf = reinterpret_cast< QChar*>(unicodeBuffer); + if (KeyTbl[vk] == 0) { + buf[0] = vk; + return 1; + } + return 0; +} + +int ToAscii(UINT vk, int scancode, unsigned char *kbdBuffer, LPWORD unicodeBuffer, int flag) +{ + return ToUnicode(vk, scancode, kbdBuffer, (LPWSTR) unicodeBuffer, 0, flag); + +} +QT_END_INCLUDE_NAMESPACE + +#endif + +// Translate a VK into a Qt key code, or unicode character +static inline int toKeyOrUnicode(int vk, int scancode, unsigned char *kbdBuffer, bool *isDeadkey = 0) +{ + Q_ASSERT(vk > 0 && vk < 256); + int code = 0; + QChar unicodeBuffer[5]; + int res = ToUnicode(vk, scancode, kbdBuffer, reinterpret_cast<LPWSTR>(unicodeBuffer), 5, 0); + if (res) + code = unicodeBuffer[0].toUpper().unicode(); + + // Qt::Key_*'s are not encoded below 0x20, so try again, and DEL keys (0x7f) is encoded with a + // proper Qt::Key_ code + if (code < 0x20 || code == 0x7f) // Handles res==0 too + code = winceKeyBend(vk); + + if (isDeadkey) + *isDeadkey = (res == -1); + + return code == Qt::Key_unknown ? 0 : code; +} + +Q_GUI_EXPORT int qt_translateKeyCode(int vk) +{ + int code = winceKeyBend((vk < 0 || vk > 255) ? 0 : vk); + return code == Qt::Key_unknown ? 0 : code; +} + +static inline int asciiToKeycode(char a, int state) +{ + if (a >= 'a' && a <= 'z') + a = toupper(a); + if ((state & Qt::ControlModifier) != 0) { + if (a >= 0 && a <= 31) // Ctrl+@..Ctrl+A..CTRL+Z..Ctrl+_ + a += '@'; // to @..A..Z.._ + } + return a & 0xff; +} + +static inline bool isModifierKey(int code) +{ + return (code >= Qt::Key_Shift) && (code <= Qt::Key_ScrollLock); +} +// Key translation -----------------------------------------------------------------------[ end ]--- + + +static void qt_show_system_menu(QWidget* tlw) +{ + Q_ASSERT(tlw->testAttribute(Qt::WA_WState_Created)); + HMENU menu = GetSystemMenu(tlw->internalWinId(), FALSE); + if (!menu) + return; // no menu for this window + +#define enabled (MF_BYCOMMAND | MF_ENABLED) +#define disabled (MF_BYCOMMAND | MF_GRAYED) + +#ifndef Q_OS_WINCE + EnableMenuItem(menu, SC_MINIMIZE, (tlw->windowFlags() & Qt::WindowMinimizeButtonHint)?enabled:disabled); + bool maximized = IsZoomed(tlw->internalWinId()); + + EnableMenuItem(menu, SC_MAXIMIZE, ! (tlw->windowFlags() & Qt::WindowMaximizeButtonHint) || maximized?disabled:enabled); + EnableMenuItem(menu, SC_RESTORE, maximized?enabled:disabled); + + // 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, (tlw->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) || maximized?disabled:enabled); + EnableMenuItem(menu, SC_MOVE, maximized?disabled:enabled); + EnableMenuItem(menu, SC_CLOSE, enabled); + // 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); +#endif + +#undef enabled +#undef disabled + int ret = TrackPopupMenuEx(menu, + TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD, + tlw->geometry().x(), tlw->geometry().y(), + tlw->internalWinId(), + 0); + if (ret) + QtWndProc(tlw->internalWinId(), WM_SYSCOMMAND, ret, 0); +} + + +// QETWidget class is only for accessing the sendSpontaneousEvent function in QApplication +class QETWidget : public QWidget { +public: + static bool sendSpontaneousEvent(QObject *r, QEvent *e) + { return QApplication::sendSpontaneousEvent(r, e); } +}; + + +// Keyboard map private ----------------------------------------------------------------[ start ]--- + +/* + \internal + A Windows KeyboardLayoutItem has 8 possible states: + 1. Unmodified + 2. Shift + 3. Control + 4. Control + Shift + 5. Alt + 6. Alt + Shift + 7. Alt + Control + 8. Alt + Control + Shift +*/ +struct KeyboardLayoutItem { + bool dirty; + quint8 deadkeys; + quint32 qtKey[9]; // Can by any Qt::Key_<foo>, or unicode character +}; + +QKeyMapperPrivate::QKeyMapperPrivate() +{ + memset(keyLayout, 0, sizeof(keyLayout)); +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ + deleteLayouts(); +} + +void QKeyMapperPrivate::deleteLayouts() +{ + for (int i = 0; i < 255; ++i) { + if (keyLayout[i]) { + delete keyLayout[i]; + keyLayout[i] = 0; + } + } +} + +void QKeyMapperPrivate::clearMappings() +{ + deleteLayouts(); + + /* MAKELCID()'s first argument is a WORD, and GetKeyboardLayout() + * returns a DWORD. */ + + LCID newLCID = MAKELCID((quintptr)GetKeyboardLayout(0), SORT_DEFAULT); +// keyboardInputLocale = qt_localeFromLCID(newLCID); + + bool bidi = false; + wchar_t LCIDFontSig[16]; + if (GetLocaleInfo(newLCID, LOCALE_FONTSIGNATURE, LCIDFontSig, sizeof(LCIDFontSig) / sizeof(wchar_t)) + && (LCIDFontSig[7] & (wchar_t)0x0800)) + bidi = true; + + keyboardInputDirection = bidi ? Qt::RightToLeft : Qt::LeftToRight; +} + +void QKeyMapperPrivate::clearRecordedKeys() +{ + key_recorder.clearKeys(); +} + + +inline void setKbdState(unsigned char *kbd, bool shift, bool ctrl, bool alt) +{ + kbd[VK_LSHIFT ] = (shift ? 0x80 : 0); + kbd[VK_SHIFT ] = (shift ? 0x80 : 0); + kbd[VK_LCONTROL] = (ctrl ? 0x80 : 0); + kbd[VK_CONTROL ] = (ctrl ? 0x80 : 0); + kbd[VK_RMENU ] = (alt ? 0x80 : 0); + kbd[VK_MENU ] = (alt ? 0x80 : 0); +} + +void QKeyMapperPrivate::updateKeyMap(const MSG &msg) +{ + unsigned char kbdBuffer[256]; // Will hold the complete keyboard state + GetKeyboardState(kbdBuffer); + quint32 scancode = (msg.lParam >> 16) & 0xfff; + updatePossibleKeyCodes(kbdBuffer, scancode, msg.wParam); +} + +void QKeyMapperPrivate::updatePossibleKeyCodes(unsigned char *kbdBuffer, quint32 scancode, + quint32 vk_key) +{ + if (!vk_key || (keyLayout[vk_key] && !keyLayout[vk_key]->dirty)) + return; + + if (!keyLayout[vk_key]) + keyLayout[vk_key] = new KeyboardLayoutItem; + + // Copy keyboard state, so we can modify and query output for each possible permutation + unsigned char buffer[256]; + memcpy(buffer, kbdBuffer, sizeof(buffer)); + // Always 0, as Windows doesn't treat these as modifiers; + buffer[VK_LWIN ] = 0; + buffer[VK_RWIN ] = 0; + buffer[VK_CAPITAL ] = 0; + buffer[VK_NUMLOCK ] = 0; + buffer[VK_SCROLL ] = 0; + // Always 0, since we'll only change the other versions + buffer[VK_RSHIFT ] = 0; + buffer[VK_RCONTROL] = 0; + buffer[VK_LMENU ] = 0; // Use right Alt, since left Ctrl + right Alt is considered AltGraph + + bool isDeadKey = false; + keyLayout[vk_key]->deadkeys = 0; + keyLayout[vk_key]->dirty = false; + setKbdState(buffer, false, false, false); + keyLayout[vk_key]->qtKey[0] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x01 : 0; + setKbdState(buffer, true, false, false); + keyLayout[vk_key]->qtKey[1] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x02 : 0; + setKbdState(buffer, false, true, false); + keyLayout[vk_key]->qtKey[2] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x04 : 0; + setKbdState(buffer, true, true, false); + keyLayout[vk_key]->qtKey[3] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x08 : 0; + setKbdState(buffer, false, false, true); + keyLayout[vk_key]->qtKey[4] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x10 : 0; + setKbdState(buffer, true, false, true); + keyLayout[vk_key]->qtKey[5] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x20 : 0; + setKbdState(buffer, false, true, true); + keyLayout[vk_key]->qtKey[6] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x40 : 0; + setKbdState(buffer, true, true, true); + keyLayout[vk_key]->qtKey[7] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey); + keyLayout[vk_key]->deadkeys |= isDeadKey ? 0x80 : 0; + // Add a fall back key for layouts which don't do composition and show non-latin1 characters + int fallbackKey = winceKeyBend(vk_key); + if (!fallbackKey || fallbackKey == Qt::Key_unknown) { + fallbackKey = 0; + if (vk_key != keyLayout[vk_key]->qtKey[0] && vk_key < 0x5B && vk_key > 0x2F) + fallbackKey = vk_key; + } + keyLayout[vk_key]->qtKey[8] = fallbackKey; + + // If this vk_key a Dead Key + if (MapVirtualKey(vk_key, 2) & 0x80000000) { + // Push a Space, then the original key through the low-level ToAscii functions. + // We do this because these functions (ToAscii / ToUnicode) will alter the internal state of + // the keyboard driver By doing the following, we set the keyboard driver state back to what + // it was before we wrecked it with the code above. + // We need to push the space with an empty keystate map, since the driver checks the map for + // transitions in modifiers, so this helps us capture all possible deadkeys. + unsigned char emptyBuffer[256]; + memset(emptyBuffer, 0, sizeof(emptyBuffer)); + ::ToAscii(VK_SPACE, 0, emptyBuffer, reinterpret_cast<LPWORD>(&buffer), 0); + ::ToAscii(vk_key, scancode, kbdBuffer, reinterpret_cast<LPWORD>(&buffer), 0); + } + +#ifdef DEBUG_KEYMAPPER + qDebug("updatePossibleKeyCodes for virtual key = 0x%02x!", vk_key); + for (int i = 0; i < 9; ++i) { + qDebug(" [%d] (%d,0x%02x,'%c') %s", i, + keyLayout[vk_key]->qtKey[i], + keyLayout[vk_key]->qtKey[i], + keyLayout[vk_key]->qtKey[i] ? keyLayout[vk_key]->qtKey[i] : 0x03, + keyLayout[vk_key]->deadkeys & (1<<i) ? "deadkey" : ""); + } +#endif // DEBUG_KEYMAPPER +} + +bool QKeyMapperPrivate::isADeadKey(unsigned int vk_key, unsigned int modifiers) +{ + if (keyLayout && (vk_key < 256) && keyLayout[vk_key]) { + for(register int i = 0; i < 9; ++i) { + if (uint(ModsTbl[i]) == modifiers) + return bool(keyLayout[vk_key]->deadkeys & 1<<i); + } + } + return false; +} + +extern bool qt_use_rtl_extensions; + +QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList<int> result; + + KeyboardLayoutItem *kbItem = keyLayout[e->nativeVirtualKey()]; + if(!kbItem) + return result; + + quint32 baseKey = kbItem->qtKey[0]; + Qt::KeyboardModifiers keyMods = e->modifiers(); + if (baseKey == Qt::Key_Return && (e->nativeModifiers() & ExtendedKey)) { + result << int(Qt::Key_Enter + keyMods); + return result; + } + result << int(baseKey + keyMods); // The base key is _always_ valid, of course + + for(int i = 1; i < 9; ++i) { + Qt::KeyboardModifiers neededMods = ModsTbl[i]; + quint32 key = kbItem->qtKey[i]; + if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) + result << int(key + (keyMods & ~neededMods)); + } + + return result; +} + +bool QKeyMapperPrivate::translateKeyEvent(QWidget *widget, const MSG &msg, bool grab) +{ + Q_Q(QKeyMapper); + Q_UNUSED(q); // Strange, but the compiler complains on q not being referenced, even if it is.. + bool k0 = false; + bool k1 = false; + int msgType = msg.message; + + quint32 scancode = (msg.lParam >> 16) & 0xfff; + quint32 vk_key = MapVirtualKey(scancode, 1); + bool isNumpad = (msg.wParam >= VK_NUMPAD0 && msg.wParam <= VK_NUMPAD9); + quint32 nModifiers = 0; + +#if defined(Q_OS_WINCE) + nModifiers |= (GetKeyState(VK_SHIFT ) < 0 ? ShiftAny : 0); + nModifiers |= (GetKeyState(VK_CONTROL) < 0 ? ControlAny : 0); + nModifiers |= (GetKeyState(VK_MENU ) < 0 ? AltAny : 0); + nModifiers |= (GetKeyState(VK_LWIN ) < 0 ? MetaLeft : 0); + nModifiers |= (GetKeyState(VK_RWIN ) < 0 ? MetaRight : 0); +#else + // Map native modifiers to some bit representation + nModifiers |= (GetKeyState(VK_LSHIFT ) & 0x80 ? ShiftLeft : 0); + nModifiers |= (GetKeyState(VK_RSHIFT ) & 0x80 ? ShiftRight : 0); + nModifiers |= (GetKeyState(VK_LCONTROL) & 0x80 ? ControlLeft : 0); + nModifiers |= (GetKeyState(VK_RCONTROL) & 0x80 ? ControlRight : 0); + nModifiers |= (GetKeyState(VK_LMENU ) & 0x80 ? AltLeft : 0); + nModifiers |= (GetKeyState(VK_RMENU ) & 0x80 ? AltRight : 0); + nModifiers |= (GetKeyState(VK_LWIN ) & 0x80 ? MetaLeft : 0); + nModifiers |= (GetKeyState(VK_RWIN ) & 0x80 ? MetaRight : 0); + // Add Lock keys to the same bits + nModifiers |= (GetKeyState(VK_CAPITAL ) & 0x01 ? CapsLock : 0); + nModifiers |= (GetKeyState(VK_NUMLOCK ) & 0x01 ? NumLock : 0); + nModifiers |= (GetKeyState(VK_SCROLL ) & 0x01 ? ScrollLock : 0); +#endif // Q_OS_WINCE + + if (msg.lParam & ExtendedKey) + nModifiers |= msg.lParam & ExtendedKey; + + // Get the modifier states (may be altered later, depending on key code) + int state = 0; + state |= (nModifiers & ShiftAny ? Qt::ShiftModifier : 0); + state |= (nModifiers & ControlAny ? Qt::ControlModifier : 0); + state |= (nModifiers & AltAny ? Qt::AltModifier : 0); + state |= (nModifiers & MetaAny ? Qt::MetaModifier : 0); + + // Now we know enough to either have MapVirtualKey or our own keymap tell us if it's a deadkey + bool isDeadKey = isADeadKey(msg.wParam, state) + || MapVirtualKey(msg.wParam, 2) & 0x80000000; + + // A multi-character key not found by our look-ahead + if (msgType == WM_CHAR) { + QString s; + QChar ch = QChar((ushort)msg.wParam); + if (!ch.isNull()) + s += ch; + + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers); + k1 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers); + } + + // Input method characters not found by our look-ahead + else if (msgType == WM_IME_CHAR) { + QString s; + QChar ch = QChar((ushort)msg.wParam); + if (!ch.isNull()) + s += ch; + + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers); + k1 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers); + } + + else { + // handle Directionality changes (BiDi) with RTL extensions + if (qt_use_rtl_extensions) { + static int dirStatus = 0; + if (!dirStatus && state == Qt::ControlModifier + && msg.wParam == VK_CONTROL + && msgType == WM_KEYDOWN) { + if (GetKeyState(VK_LCONTROL) < 0) + dirStatus = VK_LCONTROL; + else if (GetKeyState(VK_RCONTROL) < 0) + dirStatus = VK_RCONTROL; + } else if (dirStatus) { + if (msgType == WM_KEYDOWN) { + if (msg.wParam == VK_SHIFT) { + if (dirStatus == VK_LCONTROL && GetKeyState(VK_LSHIFT) < 0) + dirStatus = VK_LSHIFT; + else if (dirStatus == VK_RCONTROL && GetKeyState(VK_RSHIFT) < 0) + dirStatus = VK_RSHIFT; + } else { + dirStatus = 0; + } + } else if (msgType == WM_KEYUP) { + if (dirStatus == VK_LSHIFT + && ((msg.wParam == VK_SHIFT && GetKeyState(VK_LCONTROL)) + || (msg.wParam == VK_CONTROL && GetKeyState(VK_LSHIFT)))) { + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, Qt::Key_Direction_L, 0, + QString(), false, 0, + scancode, msg.wParam, nModifiers); + k1 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, Qt::Key_Direction_L, 0, + QString(), false, 0, + scancode, msg.wParam, nModifiers); + dirStatus = 0; + } else if (dirStatus == VK_RSHIFT + && ( (msg.wParam == VK_SHIFT && GetKeyState(VK_RCONTROL)) + || (msg.wParam == VK_CONTROL && GetKeyState(VK_RSHIFT)))) { + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, Qt::Key_Direction_R, + 0, QString(), false, 0, + scancode, msg.wParam, nModifiers); + k1 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, Qt::Key_Direction_R, + 0, QString(), false, 0, + scancode, msg.wParam, nModifiers); + dirStatus = 0; + } else { + dirStatus = 0; + } + } else { + dirStatus = 0; + } + } + } + + // IME will process these keys, so simply return + if(msg.wParam == VK_PROCESSKEY) + return true; + + // Ignore invalid virtual keycodes (see bugs 127424, QTBUG-3630) + if (msg.wParam == 0 || msg.wParam == 0xFF) + return true; + + // Translate VK_* (native) -> Key_* (Qt) keys + // If it's a dead key, we cannot use the toKeyOrUnicode() function, since that will change + // the internal state of the keyboard driver, resulting in that dead keys no longer works. + // ..also if we're typing numbers on the keypad, while holding down the Alt modifier. + int code = 0; + if (isNumpad && (nModifiers & AltAny)) { + code = winceKeyBend(msg.wParam); + } else if (!isDeadKey) { + unsigned char kbdBuffer[256]; // Will hold the complete keyboard state + GetKeyboardState(kbdBuffer); + code = toKeyOrUnicode(msg.wParam, scancode, kbdBuffer); + } + + // Invert state logic: + // If the key actually pressed is a modifier key, then we remove its modifier key from the + // state, since a modifier-key can't have itself as a modifier + if (code == Qt::Key_Control) + state = state ^ Qt::ControlModifier; + else if (code == Qt::Key_Shift) + state = state ^ Qt::ShiftModifier; + else if (code == Qt::Key_Alt) + state = state ^ Qt::AltModifier; + + // If the bit 24 of lParm is set you received a enter, + // otherwise a Return. (This is the extended key bit) + if ((code == Qt::Key_Return) && (msg.lParam & 0x1000000)) + code = Qt::Key_Enter; + + // All cursor keys without extended bit + if (!(msg.lParam & 0x1000000)) { + switch (code) { + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Insert: + case Qt::Key_Delete: + case Qt::Key_Asterisk: + case Qt::Key_Plus: + case Qt::Key_Minus: + case Qt::Key_Period: + case Qt::Key_0: + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + state |= ((msg.wParam >= '0' && msg.wParam <= '9') + || (msg.wParam >= VK_OEM_PLUS && msg.wParam <= VK_OEM_3)) + ? 0 : Qt::KeypadModifier; + default: + if ((uint)msg.lParam == 0x004c0001 || (uint)msg.lParam == 0xc04c0001) + state |= Qt::KeypadModifier; + break; + } + } + // Other keys with with extended bit + else { + switch (code) { + case Qt::Key_Enter: + case Qt::Key_Slash: + case Qt::Key_NumLock: + state |= Qt::KeypadModifier; + default: + break; + } + } + + // KEYDOWN --------------------------------------------------------------------------------- + if (msgType == WM_KEYDOWN || msgType == WM_IME_KEYDOWN || msgType == WM_SYSKEYDOWN) { + // Get the last record of this key press, so we can validate the current state + // The record is not removed from the list + KeyRecord *rec = key_recorder.findKey(msg.wParam, false); + + // If rec's state doesn't match the current state, something has changed behind our back + // (Consumed by modal widget is one possibility) So, remove the record from the list + // This will stop the auto-repeat of the key, should a modifier change, for example + if (rec && rec->state != state) { + key_recorder.findKey(msg.wParam, true); + rec = 0; + } + + // Find unicode character from Windows Message Queue + MSG wm_char; + UINT charType = (msgType == WM_KEYDOWN + ? WM_CHAR + : msgType == WM_IME_KEYDOWN ? WM_IME_CHAR : WM_SYSCHAR); + + QChar uch; + if (PeekMessage(&wm_char, 0, charType, charType, PM_REMOVE)) { + // Found a ?_CHAR + uch = QChar((ushort)wm_char.wParam); + if (msgType == WM_SYSKEYDOWN && uch.isLetter() && (msg.lParam & KF_ALTDOWN)) + uch = uch.toLower(); // (See doc of WM_SYSCHAR) Alt-letter + if (!code && !uch.row()) + code = asciiToKeycode(uch.cell(), state); + } + + // Special handling for the WM_IME_KEYDOWN message. Microsoft IME (Korean) will not + // generate a WM_IME_CHAR message corresponding to this message. We might get wrong + // results, if we map this virtual key-code directly (for eg '?' US layouts). So try + // to find the correct key using the current message parameters & keyboard state. + if (uch.isNull() && msgType == WM_IME_KEYDOWN) { + BYTE keyState[256]; + wchar_t newKey[3] = {0}; + GetKeyboardState(keyState); + int val = ToUnicode(vk_key, scancode, keyState, newKey, 2, 0); + if (val == 1) { + uch = QChar(newKey[0]); + } else { + // If we are still not able to find a unicode key, pass the WM_IME_KEYDOWN + // message to DefWindowProc() for generating a proper WM_KEYDOWN. + return false; + } + } + + // If no ?_CHAR was found in the queue; deduct character from the ?_KEYDOWN parameters + if (uch.isNull()) { + if (msg.wParam == VK_DELETE) { + uch = QChar(QLatin1Char(0x7f)); // Windows doesn't know this one. + } else { + if (msgType != WM_SYSKEYDOWN || !code) { + UINT map = MapVirtualKey(msg.wParam, 2); + // If the high bit of the return value is set, it's a deadkey + if (!(map & 0x80000000)) + uch = QChar((ushort)map); + } + } + if (!code && !uch.row()) + code = asciiToKeycode(uch.cell(), state); + } + + // Special handling of global Windows hotkeys + if (state == Qt::AltModifier) { + switch (code) { + case Qt::Key_Escape: + case Qt::Key_Tab: + case Qt::Key_Enter: + case Qt::Key_F4: + return false; // Send the event on to Windows + case Qt::Key_Space: + // do not pass this key to windows, we will process it ourselves + qt_show_system_menu(widget->window()); + return true; + default: + break; + } + } + + // 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; + + // If we have a record, it means that the key is already pressed, the state is the same + // so, we have an auto-repeating key + if (rec) { + if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) { + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, code, + Qt::KeyboardModifier(state), rec->text, true, 0, + scancode, msg.wParam, nModifiers); + k1 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, code, + Qt::KeyboardModifier(state), rec->text, true, 0, + scancode, msg.wParam, nModifiers); + } + } + // No record of the key being previous pressed, so we now send a QEvent::KeyPress event, + // and store the key data into our records. + else { + QString text; + if (!uch.isNull()) + text += uch; + char a = uch.row() ? 0 : uch.cell(); + key_recorder.storeKey(msg.wParam, a, state, text); + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, code, Qt::KeyboardModifier(state), + text, false, 0, scancode, msg.wParam, nModifiers); + + bool store = true; + // Alt+<alphanumerical> go to the Win32 menu system if unhandled by Qt +#if !defined(Q_OS_WINCE) + if (msgType == WM_SYSKEYDOWN && !k0 && a) { + HWND parent = GetParent(widget->internalWinId()); + while (parent) { + if (GetMenu(parent)) { + SendMessage(parent, WM_SYSCOMMAND, SC_KEYMENU, a); + store = false; + k0 = true; + break; + } + parent = GetParent(parent); + } + } +#endif + if (!store) + key_recorder.findKey(msg.wParam, true); + } + } + + // KEYUP ----------------------------------------------------------------------------------- + else { + // Try to locate the key in our records, and remove it if it exists. + // The key may not be in our records if, for example, the down event was handled by + // win32 natively, or our window gets focus while a key is already press, but now gets + // the key release event. + KeyRecord* rec = key_recorder.findKey(msg.wParam, true); + if (!rec && !(code == Qt::Key_Shift + || code == Qt::Key_Control + || code == Qt::Key_Meta + || code == Qt::Key_Alt)) { + // Someone ate the key down event + } else { + if (!code) + code = asciiToKeycode(rec->ascii ? rec->ascii : msg.wParam, state); + + // 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; + + k0 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, code, Qt::KeyboardModifier(state), + (rec ? rec->text : QString()), false, 0, scancode, msg.wParam, nModifiers); + + // don't pass Alt to Windows unless we are embedded in a non-Qt window +#if !defined(Q_OS_WINCE) + if (code == Qt::Key_Alt) { + k0 = true; + HWND parent = GetParent(widget->internalWinId()); + while (parent) { + if (!QWidget::find(parent) && GetMenu(parent)) { + k0 = false; + break; + } + parent = GetParent(parent); + } + } +#endif + } + } + } + + // Return true, if a QKeyEvent was sent to a widget + return k0 || k1; +} + + +// QKeyMapper (Windows) implementation -------------------------------------------------[ start ]--- + +bool QKeyMapper::sendKeyEvent(QWidget *widget, bool grab, + QEvent::Type type, int code, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorepeat, int count, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, + bool *) +{ +#if defined(Q_OS_WINCE) + Q_UNUSED(grab); +#endif + Q_UNUSED(count); +#if defined QT3_SUPPORT && !defined(QT_NO_SHORTCUT) + if (type == QEvent::KeyPress + && !grab + && QApplicationPrivate::instance()->use_compat()) { + // send accel events if the keyboard is not grabbed + QKeyEventEx a(type, code, modifiers, + text, autorepeat, qMax(1, int(text.length())), + nativeScanCode, nativeVirtualKey, nativeModifiers); + if (QApplicationPrivate::instance()->qt_tryAccelEvent(widget, &a)) + return true; + } +#else + Q_UNUSED(grab); +#endif + if (!widget->isEnabled()) + return false; + + QKeyEventEx e(type, code, modifiers, + text, autorepeat, qMax(1, int(text.length())), + nativeScanCode, nativeVirtualKey, nativeModifiers); + QETWidget::sendSpontaneousEvent(widget, &e); + + if (!isModifierKey(code) + && modifiers == Qt::AltModifier + && ((code >= Qt::Key_A && code <= Qt::Key_Z) || (code >= Qt::Key_0 && code <= Qt::Key_9)) + && type == QEvent::KeyPress + && !e.isAccepted()) + QApplication::beep(); // Emulate windows behavior + + return e.isAccepted(); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_x11.cpp b/src/gui/kernel/qkeymapper_x11.cpp new file mode 100644 index 0000000000..5383bfd456 --- /dev/null +++ b/src/gui/kernel/qkeymapper_x11.cpp @@ -0,0 +1,1869 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeymapper_p.h" + +#include "qdebug.h" +#include "qtextcodec.h" +#include "qwidget.h" + +#include "qapplication_p.h" +#include "qevent_p.h" +#include "qt_x11_p.h" + +#ifndef QT_NO_XKB +# include <X11/XKBlib.h> +#endif + +#define XK_MISCELLANY +#define XK_LATIN1 +#define XK_KOREAN +#define XK_XKB_KEYS +#include <X11/keysymdef.h> + +#include <ctype.h> + +#ifdef QT_LINUXBASE +// LSB's IsKeypadKey define is wrong - see +// http://bugs.linuxbase.org/show_bug.cgi?id=2521 +#undef IsKeypadKey +#define IsKeypadKey(keysym) \ + (((KeySym)(keysym) >= XK_KP_Space) && ((KeySym)(keysym) <= XK_KP_Equal)) + +#undef IsPrivateKeypadKey +#define IsPrivateKeypadKey(keysym) \ + (((KeySym)(keysym) >= 0x11000000) && ((KeySym)(keysym) <= 0x1100FFFF)) +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_XKB + +// bring in the auto-generated xkbLayoutData +#include "qkeymapper_x11_p.cpp" + +QLocale q_getKeyboardLocale(const QByteArray &layoutName, const QByteArray &variantName) +{ + int i = 0; + while (xkbLayoutData[i].layout != 0) { + if (layoutName == xkbLayoutData[i].layout && variantName == xkbLayoutData[i].variant) + return QLocale(xkbLayoutData[i].language, xkbLayoutData[i].country); + ++i; + } + return QLocale::c(); +} +#endif // QT_NO_XKB + +// from qapplication_x11.cpp +extern uchar qt_alt_mask; +extern uchar qt_meta_mask; +extern uchar qt_super_mask; +extern uchar qt_hyper_mask; +extern uchar qt_mode_switch_mask; +uchar qt_num_lock_mask = 0; +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); + +// ### we should really resolve conflicts with other masks by +// ### decomposing the Qt::KeyboardModifers in possibleKeys() +#define SETMASK(sym, mask) \ + do { \ + if (qt_alt_mask == 0 \ + && qt_meta_mask != mask \ + && qt_super_mask != mask \ + && qt_hyper_mask != mask \ + && (sym == XK_Alt_L || sym == XK_Alt_R)) { \ + qt_alt_mask = mask; \ + } \ + if (qt_meta_mask == 0 \ + && qt_alt_mask != mask \ + && qt_super_mask != mask \ + && qt_hyper_mask != mask \ + && (sym == XK_Meta_L || sym == XK_Meta_R)) { \ + qt_meta_mask = mask; \ + } \ + if (qt_super_mask == 0 \ + && qt_alt_mask != mask \ + && qt_meta_mask != mask \ + && qt_hyper_mask != mask \ + && (sym == XK_Super_L || sym == XK_Super_R)) { \ + qt_super_mask = mask; \ + } \ + if (qt_hyper_mask == 0 \ + && qt_alt_mask != mask \ + && qt_meta_mask != mask \ + && qt_super_mask != mask \ + && (sym == XK_Hyper_L || sym == XK_Hyper_R)) { \ + qt_hyper_mask = mask; \ + } \ + if (qt_mode_switch_mask == 0 \ + && qt_alt_mask != mask \ + && qt_meta_mask != mask \ + && qt_super_mask != mask \ + && qt_hyper_mask != mask \ + && sym == XK_Mode_switch) { \ + qt_mode_switch_mask = mask; \ + } \ + if (qt_num_lock_mask == 0 \ + && sym == XK_Num_Lock) { \ + qt_num_lock_mask = mask; \ + } \ + } while(false) + +// qt_XTranslateKey() is based on _XTranslateKey() taken from: + +/* $Xorg: KeyBind.c,v 1.4 2001/02/09 02:03:34 xorgcvs Exp $ */ + +/* + +Copyright 1985, 1987, 1998 The Open Group + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from The Open Group. + +*/ +static int +qt_XTranslateKey(register QXCoreDesc *dpy, + KeyCode keycode, + register unsigned int modifiers, + unsigned int *modifiers_return, + KeySym *keysym_return) +{ + int per; + register KeySym *syms; + KeySym sym, lsym, usym; + + if (! dpy->keysyms) + return 0; + *modifiers_return = ((ShiftMask|LockMask) + | dpy->mode_switch | dpy->num_lock); + if (((int)keycode < dpy->min_keycode) || ((int)keycode > dpy->max_keycode)) + { + *keysym_return = NoSymbol; + return 1; + } + per = dpy->keysyms_per_keycode; + syms = &dpy->keysyms[(keycode - dpy->min_keycode) * per]; + while ((per > 2) && (syms[per - 1] == NoSymbol)) + per--; + if ((per > 2) && (modifiers & dpy->mode_switch)) { + syms += 2; + per -= 2; + } + if ((modifiers & dpy->num_lock) && + (per > 1 && (IsKeypadKey(syms[1]) || IsPrivateKeypadKey(syms[1])))) { + if ((modifiers & ShiftMask) || + ((modifiers & LockMask) && (dpy->lock_meaning == XK_Shift_Lock))) + *keysym_return = syms[0]; + else + *keysym_return = syms[1]; + } else if (!(modifiers & ShiftMask) && + (!(modifiers & LockMask) || (dpy->lock_meaning == NoSymbol))) { + if ((per == 1) || (syms[1] == NoSymbol)) + XConvertCase(syms[0], keysym_return, &usym); + else + *keysym_return = syms[0]; + } else if (!(modifiers & LockMask) || + (dpy->lock_meaning != XK_Caps_Lock)) { + if ((per == 1) || ((usym = syms[1]) == NoSymbol)) + XConvertCase(syms[0], &lsym, &usym); + *keysym_return = usym; + } else { + if ((per == 1) || ((sym = syms[1]) == NoSymbol)) + sym = syms[0]; + XConvertCase(sym, &lsym, &usym); + if (!(modifiers & ShiftMask) && (sym != syms[0]) && + ((sym != usym) || (lsym == usym))) + XConvertCase(syms[0], &lsym, &usym); + *keysym_return = usym; + } + if (*keysym_return == XK_VoidSymbol) + *keysym_return = NoSymbol; + return 1; +} + + + + +QKeyMapperPrivate::QKeyMapperPrivate() + : keyboardInputDirection(Qt::LeftToRight), xkb_currentGroup(0) +{ + memset(&coreDesc, 0, sizeof(coreDesc)); + +#ifndef QT_NO_XKB + if (X11->use_xkb) { + // get the current group + XkbStateRec xkbState; + if (XkbGetState(X11->display, XkbUseCoreKbd, &xkbState) == Success) + xkb_currentGroup = xkbState.group; + } +#endif +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ + if (coreDesc.keysyms) + XFree(coreDesc.keysyms); +} + +QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent *event) +{ +#ifndef QT_NO_XKB + if (X11->use_xkb) + return possibleKeysXKB(event); +#endif + return possibleKeysCore(event); +} + +enum { MaxBits = sizeof(uint) * 8 }; +static QString translateKeySym(KeySym keysym, uint xmodifiers, + int &code, Qt::KeyboardModifiers &modifiers, + QByteArray &chars, int &count); + +QList<int> QKeyMapperPrivate::possibleKeysXKB(QKeyEvent *event) +{ +#ifndef QT_NO_XKB + const int xkeycode = event->nativeScanCode(); + const uint xmodifiers = event->nativeModifiers(); + + // first, translate key only using lock modifiers (there are no Qt equivalents for these, so we must + // always use them when determining the baseKeySym) + KeySym baseKeySym; + uint consumedModifiers; + if (!XkbLookupKeySym(X11->display, xkeycode, (xmodifiers & (LockMask | qt_num_lock_mask)), + &consumedModifiers, &baseKeySym)) + return QList<int>(); + + QList<int> result; + + // translate sym -> code + Qt::KeyboardModifiers baseModifiers = 0; + int baseCode = -1; + QByteArray chars; + int count = 0; + QString text = translateKeySym(baseKeySym, xmodifiers, baseCode, baseModifiers, chars, count); + if (baseCode == -1) { + if (text.isEmpty()) + return QList<int>(); + baseCode = text.at(0).unicode(); + } + + if (baseCode && baseCode < 0xfffe) + baseCode = QChar(baseCode).toUpper().unicode(); + result += (baseCode | baseModifiers); + + int pos1Bits[MaxBits]; + int num1Bits = 0; + + for (int i = 0; i < MaxBits; ++i) { + if (consumedModifiers & (1 << i)) + pos1Bits[num1Bits++] = i; + } + + const int numPerms = (1 << num1Bits); + + // translate the key again using each permutation of consumedModifiers + for (int i = 1; i < numPerms; ++i) { + uint val = 0; + for (int j = 0; j < num1Bits; ++j) { + if (i & (1 << j)) + val |= (1 << pos1Bits[j]); + } + + if ((xmodifiers & val) != val) + continue; + + KeySym sym; + uint mods; + if (!XkbLookupKeySym(X11->display, xkeycode, val, &mods, &sym)) + continue; + + // translate sym -> code + Qt::KeyboardModifiers modifiers = 0; + int code = -1; + chars.clear(); + count = 0; + // mask out the modifiers needed to translate keycode + text = translateKeySym(sym, xmodifiers & ~val, code, modifiers, chars, count); + if (code == -1) { + if (text.isEmpty()) + continue; + code = text.at(0).unicode(); + } + + if (code && code < 0xfffe) + code = QChar(code).toUpper().unicode(); + + if (code == Qt::Key_Tab && (baseModifiers & Qt::ShiftModifier)) { + // map shift+tab to shift+backtab + code = Qt::Key_Backtab; + text = QString(); + } + + if (code == baseCode) + continue; + + result += (code | modifiers); + } + +#if 0 + qDebug() << "possibleKeysXKB()" << hex << result; +#endif + return result; +#else + Q_UNUSED(event); + return QList<int>(); +#endif // QT_NO_XKB +} + +QList<int> QKeyMapperPrivate::possibleKeysCore(QKeyEvent *event) +{ + const int xkeycode = event->nativeScanCode(); + const uint xmodifiers = event->nativeModifiers(); + + // first, translate key only using lock modifiers (there are no Qt equivalents for these, so we must + // always use them when determining the baseKeySym) + KeySym baseKeySym; + uint consumedModifiers; + if (!qt_XTranslateKey(&coreDesc, xkeycode, (xmodifiers & (LockMask | qt_num_lock_mask)), + &consumedModifiers, &baseKeySym)) + return QList<int>(); + + QList<int> result; + + // translate sym -> code + Qt::KeyboardModifiers baseModifiers = 0; + int baseCode = -1; + QByteArray chars; + int count = 0; + QString text = translateKeySym(baseKeySym, xmodifiers, baseCode, baseModifiers, chars, count); + if (baseCode == -1) { + if (text.isEmpty()) + return QList<int>(); + baseCode = text.at(0).unicode(); + } + + if (baseCode && baseCode < 0xfffe) + baseCode = QChar(baseCode).toUpper().unicode(); + result += (baseCode | baseModifiers); + + int pos1Bits[MaxBits]; + int num1Bits = 0; + + for (int i = 0; i < MaxBits; ++i) { + if (consumedModifiers & (1 << i)) + pos1Bits[num1Bits++] = i; + } + + const int numPerms = (1 << num1Bits); + + // translate the key again using each permutation of consumedModifiers + for (int i = 1; i < numPerms; ++i) { + uint val = 0; + for (int j = 0; j < num1Bits; ++j) { + if (i & (1 << j)) + val |= (1 << pos1Bits[j]); + } + + if ((xmodifiers & val) != val) + continue; + + KeySym sym; + uint mods; + if (!qt_XTranslateKey(&coreDesc, xkeycode, val, &mods, &sym)) + continue; + + // translate sym -> code + Qt::KeyboardModifiers modifiers = 0; + int code = -1; + chars.clear(); + count = 0; + // mask out the modifiers needed to translate keycode + text = translateKeySym(sym, xmodifiers & ~val, code, modifiers, chars, count); + if (code == -1) { + if (text.isEmpty()) + continue; + code = text.at(0).unicode(); + } + + if (code && code < 0xfffe) + code = QChar(code).toUpper().unicode(); + + if (code == Qt::Key_Tab && (baseModifiers & Qt::ShiftModifier)) { + // map shift+tab to shift+backtab + code = Qt::Key_Backtab; + text = QString(); + } + + if (code == baseCode) + continue; + + result += (code | modifiers); + } + +#if 0 + qDebug() << "possibleKeysCore()" << hex << result; +#endif + return result; +} + +// for parsing the _XKB_RULES_NAMES property +enum { + RulesFileIndex = 0, + ModelIndex = 1, + LayoutIndex = 2, + VariantIndex = 3, + OptionsIndex = 4 +}; + +void QKeyMapperPrivate::clearMappings() +{ +#ifndef QT_NO_XKB + if (X11->use_xkb) { + // try to determine the layout name and input direction by reading the _XKB_RULES_NAMES property off + // the root window + QByteArray layoutName; + QByteArray variantName; + + Atom type = XNone; + int format = 0; + ulong nitems = 0; + ulong bytesAfter = 0; + uchar *data = 0; + if (XGetWindowProperty(X11->display, RootWindow(X11->display, 0), ATOM(_XKB_RULES_NAMES), 0, 1024, + false, XA_STRING, &type, &format, &nitems, &bytesAfter, &data) == Success + && type == XA_STRING && format == 8 && nitems > 2) { + /* + index 0 == rules file name + index 1 == model name + index 2 == layout name + index 3 == variant name + index 4 == options + */ + char *names[5] = { 0, 0, 0, 0, 0 }; + char *p = reinterpret_cast<char *>(data), *end = p + nitems; + int i = 0; + do { + names[i++] = p; + p += qstrlen(p) + 1; + } while (p < end); + + // the layout names and variants are saved in the _XKB_RULES_NAMES property as a comma separated list + QList<QByteArray> layoutNames = QByteArray::fromRawData(names[2], qstrlen(names[2])).split(','); + if (uint(xkb_currentGroup) < uint(layoutNames.count())) + layoutName = layoutNames.at(xkb_currentGroup); + QList<QByteArray> variantNames = QByteArray::fromRawData(names[3], qstrlen(names[3])).split(','); + if (uint(xkb_currentGroup) < uint(variantNames.count())) + variantName = variantNames.at(xkb_currentGroup); + } + + // ### ??? + // if (keyboardLayoutName.isEmpty()) + // qWarning("Qt: unable to determine keyboard layout, please talk to qt-bugs@trolltech.com"); ? + + keyboardInputLocale = q_getKeyboardLocale(layoutName, variantName); + keyboardInputDirection = keyboardInputLocale.textDirection(); + +#if 0 + qDebug() << "keyboard input locale =" + << keyboardInputLocale.name() + << "direction =" + << keyboardInputDirection; +#endif + if (data) + XFree(data); + } else +#endif // QT_NO_XKB + { + if (coreDesc.keysyms) + XFree(coreDesc.keysyms); + + coreDesc.min_keycode = 8; + coreDesc.max_keycode = 255; + XDisplayKeycodes(X11->display, &coreDesc.min_keycode, &coreDesc.max_keycode); + + coreDesc.keysyms_per_keycode = 0; + coreDesc.keysyms = XGetKeyboardMapping(X11->display, + coreDesc.min_keycode, + coreDesc.max_keycode - coreDesc.min_keycode + 1, + &coreDesc.keysyms_per_keycode); + +#if 0 + qDebug() << "min_keycode =" << coreDesc.min_keycode; + qDebug() << "max_keycode =" << coreDesc.max_keycode; + qDebug() << "keysyms_per_keycode =" << coreDesc.keysyms_per_keycode; + qDebug() << "keysyms =" << coreDesc.keysyms; +#endif + + // ### cannot get/guess the locale with the core protocol + keyboardInputLocale = QLocale::c(); + // ### could examine group 0 for RTL keys + keyboardInputDirection = Qt::LeftToRight; + } + + qt_alt_mask = 0; + qt_meta_mask = 0; + qt_super_mask = 0; + qt_hyper_mask = 0; + qt_mode_switch_mask = 0; + + // look at the modifier mapping, and get the correct masks for alt, meta, super, hyper, and mode_switch +#ifndef QT_NO_XKB + if (X11->use_xkb) { + XkbDescPtr xkbDesc = XkbGetMap(X11->display, XkbAllClientInfoMask, XkbUseCoreKbd); + for (int i = xkbDesc->min_key_code; i < xkbDesc->max_key_code; ++i) { + const uint mask = xkbDesc->map->modmap ? xkbDesc->map->modmap[i] : 0; + if (mask == 0) { + // key is not bound to a modifier + continue; + } + + for (int j = 0; j < XkbKeyGroupsWidth(xkbDesc, i); ++j) { + KeySym keySym = XkbKeySym(xkbDesc, i, j); + if (keySym == NoSymbol) + continue; + SETMASK(keySym, mask); + } + } + XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, true); + } else +#endif // QT_NO_XKB + { + coreDesc.lock_meaning = NoSymbol; + + XModifierKeymap *map = XGetModifierMapping(X11->display); + + if (map) { + int i, maskIndex = 0, mapIndex = 0; + for (maskIndex = 0; maskIndex < 8; maskIndex++) { + for (i = 0; i < map->max_keypermod; i++) { + if (map->modifiermap[mapIndex]) { + KeySym sym; + int x = 0; + do { + sym = XKeycodeToKeysym(X11->display, map->modifiermap[mapIndex], x++); + } while (sym == NoSymbol && x < coreDesc.keysyms_per_keycode); + const uchar mask = 1 << maskIndex; + SETMASK(sym, mask); + } + mapIndex++; + } + } + + // determine the meaning of the Lock modifier + for (i = 0; i < map->max_keypermod; ++i) { + for (int x = 0; x < coreDesc.keysyms_per_keycode; ++x) { + KeySym sym = XKeycodeToKeysym(X11->display, map->modifiermap[LockMapIndex], x); + if (sym == XK_Caps_Lock || sym == XK_ISO_Lock) { + coreDesc.lock_meaning = XK_Caps_Lock; + break; + } else if (sym == XK_Shift_Lock) { + coreDesc.lock_meaning = XK_Shift_Lock; + } + } + } + + XFreeModifiermap(map); + } + + // for qt_XTranslateKey() + coreDesc.num_lock = qt_num_lock_mask; + coreDesc.mode_switch = qt_mode_switch_mask; + +#if 0 + qDebug() << "lock_meaning =" << coreDesc.lock_meaning; + qDebug() << "num_lock =" << coreDesc.num_lock; + qDebug() << "mode_switch =" << coreDesc.mode_switch; +#endif + } + + // set default modifier masks if needed + if( qt_alt_mask == 0 ) + qt_alt_mask = Mod1Mask; + if( qt_meta_mask == 0 ) + qt_meta_mask = Mod4Mask; + + // if we don't have a meta key (or it's hidden behind alt), use super or hyper to generate + // Qt::Key_Meta and Qt::MetaModifier, since most newer XFree86/Xorg installations map the Windows + // key to Super + if (qt_meta_mask == 0 || qt_meta_mask == qt_alt_mask) { + // no meta keys... s,meta,super, + qt_meta_mask = qt_super_mask; + if (qt_meta_mask == 0 || qt_meta_mask == qt_alt_mask) { + // no super keys either? guess we'll use hyper then + qt_meta_mask = qt_hyper_mask; + } + } + +#if 0 + qDebug() << "qt_alt_mask =" << hex << qt_alt_mask; + qDebug() << "qt_meta_mask =" << hex << qt_meta_mask; + qDebug() << "qt_super_mask =" << hex << qt_super_mask; + qDebug() << "qt_hyper_mask =" << hex << qt_hyper_mask; + qDebug() << "qt_mode_switch_mask =" << hex << qt_mode_switch_mask; + qDebug() << "qt_num_lock_mask =" << hex << qt_num_lock_mask; +#endif +} + +extern bool qt_sm_blockUserInput; + +// +// Keyboard event translation +// + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif + +#ifndef XK_dead_hook +#define XK_dead_hook 0xFE61 +#endif + +#ifndef XK_dead_horn +#define XK_dead_horn 0xFE62 +#endif + +#ifndef XK_Codeinput +#define XK_Codeinput 0xFF37 +#endif + +#ifndef XK_Kanji_Bangou +#define XK_Kanji_Bangou 0xFF37 /* same as codeinput */ +#endif + +// Fix old X libraries +#ifndef XK_KP_Home +#define XK_KP_Home 0xFF95 +#endif +#ifndef XK_KP_Left +#define XK_KP_Left 0xFF96 +#endif +#ifndef XK_KP_Up +#define XK_KP_Up 0xFF97 +#endif +#ifndef XK_KP_Right +#define XK_KP_Right 0xFF98 +#endif +#ifndef XK_KP_Down +#define XK_KP_Down 0xFF99 +#endif +#ifndef XK_KP_Prior +#define XK_KP_Prior 0xFF9A +#endif +#ifndef XK_KP_Next +#define XK_KP_Next 0xFF9B +#endif +#ifndef XK_KP_End +#define XK_KP_End 0xFF9C +#endif +#ifndef XK_KP_Insert +#define XK_KP_Insert 0xFF9E +#endif +#ifndef XK_KP_Delete +#define XK_KP_Delete 0xFF9F +#endif + +// the next lines are taken on 10/2009 from X.org (X11/XF86keysym.h), defining some special +// multimedia keys. They are included here as not every system has them. +#define XF86XK_MonBrightnessUp 0x1008FF02 +#define XF86XK_MonBrightnessDown 0x1008FF03 +#define XF86XK_KbdLightOnOff 0x1008FF04 +#define XF86XK_KbdBrightnessUp 0x1008FF05 +#define XF86XK_KbdBrightnessDown 0x1008FF06 +#define XF86XK_Standby 0x1008FF10 +#define XF86XK_AudioLowerVolume 0x1008FF11 +#define XF86XK_AudioMute 0x1008FF12 +#define XF86XK_AudioRaiseVolume 0x1008FF13 +#define XF86XK_AudioPlay 0x1008FF14 +#define XF86XK_AudioStop 0x1008FF15 +#define XF86XK_AudioPrev 0x1008FF16 +#define XF86XK_AudioNext 0x1008FF17 +#define XF86XK_HomePage 0x1008FF18 +#define XF86XK_Mail 0x1008FF19 +#define XF86XK_Start 0x1008FF1A +#define XF86XK_Search 0x1008FF1B +#define XF86XK_AudioRecord 0x1008FF1C +#define XF86XK_Calculator 0x1008FF1D +#define XF86XK_Memo 0x1008FF1E +#define XF86XK_ToDoList 0x1008FF1F +#define XF86XK_Calendar 0x1008FF20 +#define XF86XK_PowerDown 0x1008FF21 +#define XF86XK_ContrastAdjust 0x1008FF22 +#define XF86XK_Back 0x1008FF26 +#define XF86XK_Forward 0x1008FF27 +#define XF86XK_Stop 0x1008FF28 +#define XF86XK_Refresh 0x1008FF29 +#define XF86XK_PowerOff 0x1008FF2A +#define XF86XK_WakeUp 0x1008FF2B +#define XF86XK_Eject 0x1008FF2C +#define XF86XK_ScreenSaver 0x1008FF2D +#define XF86XK_WWW 0x1008FF2E +#define XF86XK_Sleep 0x1008FF2F +#define XF86XK_Favorites 0x1008FF30 +#define XF86XK_AudioPause 0x1008FF31 +#define XF86XK_AudioMedia 0x1008FF32 +#define XF86XK_MyComputer 0x1008FF33 +#define XF86XK_LightBulb 0x1008FF35 +#define XF86XK_Shop 0x1008FF36 +#define XF86XK_History 0x1008FF37 +#define XF86XK_OpenURL 0x1008FF38 +#define XF86XK_AddFavorite 0x1008FF39 +#define XF86XK_HotLinks 0x1008FF3A +#define XF86XK_BrightnessAdjust 0x1008FF3B +#define XF86XK_Finance 0x1008FF3C +#define XF86XK_Community 0x1008FF3D +#define XF86XK_AudioRewind 0x1008FF3E +#define XF86XK_BackForward 0x1008FF3F +#define XF86XK_Launch0 0x1008FF40 +#define XF86XK_Launch1 0x1008FF41 +#define XF86XK_Launch2 0x1008FF42 +#define XF86XK_Launch3 0x1008FF43 +#define XF86XK_Launch4 0x1008FF44 +#define XF86XK_Launch5 0x1008FF45 +#define XF86XK_Launch6 0x1008FF46 +#define XF86XK_Launch7 0x1008FF47 +#define XF86XK_Launch8 0x1008FF48 +#define XF86XK_Launch9 0x1008FF49 +#define XF86XK_LaunchA 0x1008FF4A +#define XF86XK_LaunchB 0x1008FF4B +#define XF86XK_LaunchC 0x1008FF4C +#define XF86XK_LaunchD 0x1008FF4D +#define XF86XK_LaunchE 0x1008FF4E +#define XF86XK_LaunchF 0x1008FF4F +#define XF86XK_ApplicationLeft 0x1008FF50 +#define XF86XK_ApplicationRight 0x1008FF51 +#define XF86XK_Book 0x1008FF52 +#define XF86XK_CD 0x1008FF53 +#define XF86XK_Calculater 0x1008FF54 +#define XF86XK_Clear 0x1008FF55 +#define XF86XK_ClearGrab 0x1008FE21 +#define XF86XK_Close 0x1008FF56 +#define XF86XK_Copy 0x1008FF57 +#define XF86XK_Cut 0x1008FF58 +#define XF86XK_Display 0x1008FF59 +#define XF86XK_DOS 0x1008FF5A +#define XF86XK_Documents 0x1008FF5B +#define XF86XK_Excel 0x1008FF5C +#define XF86XK_Explorer 0x1008FF5D +#define XF86XK_Game 0x1008FF5E +#define XF86XK_Go 0x1008FF5F +#define XF86XK_iTouch 0x1008FF60 +#define XF86XK_LogOff 0x1008FF61 +#define XF86XK_Market 0x1008FF62 +#define XF86XK_Meeting 0x1008FF63 +#define XF86XK_MenuKB 0x1008FF65 +#define XF86XK_MenuPB 0x1008FF66 +#define XF86XK_MySites 0x1008FF67 +#define XF86XK_News 0x1008FF69 +#define XF86XK_OfficeHome 0x1008FF6A +#define XF86XK_Option 0x1008FF6C +#define XF86XK_Paste 0x1008FF6D +#define XF86XK_Phone 0x1008FF6E +#define XF86XK_Reply 0x1008FF72 +#define XF86XK_Reload 0x1008FF73 +#define XF86XK_RotateWindows 0x1008FF74 +#define XF86XK_RotationPB 0x1008FF75 +#define XF86XK_RotationKB 0x1008FF76 +#define XF86XK_Save 0x1008FF77 +#define XF86XK_Send 0x1008FF7B +#define XF86XK_Spell 0x1008FF7C +#define XF86XK_SplitScreen 0x1008FF7D +#define XF86XK_Support 0x1008FF7E +#define XF86XK_TaskPane 0x1008FF7F +#define XF86XK_Terminal 0x1008FF80 +#define XF86XK_Tools 0x1008FF81 +#define XF86XK_Travel 0x1008FF82 +#define XF86XK_Video 0x1008FF87 +#define XF86XK_Word 0x1008FF89 +#define XF86XK_Xfer 0x1008FF8A +#define XF86XK_ZoomIn 0x1008FF8B +#define XF86XK_ZoomOut 0x1008FF8C +#define XF86XK_Away 0x1008FF8D +#define XF86XK_Messenger 0x1008FF8E +#define XF86XK_WebCam 0x1008FF8F +#define XF86XK_MailForward 0x1008FF90 +#define XF86XK_Pictures 0x1008FF91 +#define XF86XK_Music 0x1008FF92 +#define XF86XK_Battery 0x1008FF93 +#define XF86XK_Bluetooth 0x1008FF94 +#define XF86XK_WLAN 0x1008FF95 +#define XF86XK_UWB 0x1008FF96 +#define XF86XK_AudioForward 0x1008FF97 +#define XF86XK_AudioRepeat 0x1008FF98 +#define XF86XK_AudioRandomPlay 0x1008FF99 +#define XF86XK_Subtitle 0x1008FF9A +#define XF86XK_AudioCycleTrack 0x1008FF9B +#define XF86XK_Time 0x1008FF9F +#define XF86XK_Select 0x1008FFA0 +#define XF86XK_View 0x1008FFA1 +#define XF86XK_TopMenu 0x1008FFA2 +#define XF86XK_Suspend 0x1008FFA7 +#define XF86XK_Hibernate 0x1008FFA8 + + +// end of XF86keysyms.h + +// Special keys used by Qtopia, mapped into the X11 private keypad range. +#define QTOPIAXK_Select 0x11000601 +#define QTOPIAXK_Yes 0x11000602 +#define QTOPIAXK_No 0x11000603 +#define QTOPIAXK_Cancel 0x11000604 +#define QTOPIAXK_Printer 0x11000605 +#define QTOPIAXK_Execute 0x11000606 +#define QTOPIAXK_Sleep 0x11000607 +#define QTOPIAXK_Play 0x11000608 +#define QTOPIAXK_Zoom 0x11000609 +#define QTOPIAXK_Context1 0x1100060A +#define QTOPIAXK_Context2 0x1100060B +#define QTOPIAXK_Context3 0x1100060C +#define QTOPIAXK_Context4 0x1100060D +#define QTOPIAXK_Call 0x1100060E +#define QTOPIAXK_Hangup 0x1100060F +#define QTOPIAXK_Flip 0x11000610 + +// keyboard mapping table +static const unsigned int KeyTbl[] = { + + // misc keys + + XK_Escape, Qt::Key_Escape, + XK_Tab, Qt::Key_Tab, + XK_ISO_Left_Tab, Qt::Key_Backtab, + XK_BackSpace, Qt::Key_Backspace, + XK_Return, Qt::Key_Return, + XK_Insert, Qt::Key_Insert, + XK_Delete, Qt::Key_Delete, + XK_Clear, Qt::Key_Delete, + XK_Pause, Qt::Key_Pause, + XK_Print, Qt::Key_Print, + 0x1005FF60, Qt::Key_SysReq, // hardcoded Sun SysReq + 0x1007ff00, Qt::Key_SysReq, // hardcoded X386 SysReq + + // cursor movement + + XK_Home, Qt::Key_Home, + XK_End, Qt::Key_End, + XK_Left, Qt::Key_Left, + XK_Up, Qt::Key_Up, + XK_Right, Qt::Key_Right, + XK_Down, Qt::Key_Down, + XK_Prior, Qt::Key_PageUp, + XK_Next, Qt::Key_PageDown, + + // modifiers + + XK_Shift_L, Qt::Key_Shift, + XK_Shift_R, Qt::Key_Shift, + XK_Shift_Lock, Qt::Key_Shift, + XK_Control_L, Qt::Key_Control, + XK_Control_R, Qt::Key_Control, + XK_Meta_L, Qt::Key_Meta, + XK_Meta_R, Qt::Key_Meta, + XK_Alt_L, Qt::Key_Alt, + XK_Alt_R, Qt::Key_Alt, + XK_Caps_Lock, Qt::Key_CapsLock, + XK_Num_Lock, Qt::Key_NumLock, + XK_Scroll_Lock, Qt::Key_ScrollLock, + XK_Super_L, Qt::Key_Super_L, + XK_Super_R, Qt::Key_Super_R, + XK_Menu, Qt::Key_Menu, + XK_Hyper_L, Qt::Key_Hyper_L, + XK_Hyper_R, Qt::Key_Hyper_R, + XK_Help, Qt::Key_Help, + 0x1000FF74, Qt::Key_Backtab, // hardcoded HP backtab + 0x1005FF10, Qt::Key_F11, // hardcoded Sun F36 (labeled F11) + 0x1005FF11, Qt::Key_F12, // hardcoded Sun F37 (labeled F12) + + // numeric and function keypad keys + + XK_KP_Space, Qt::Key_Space, + XK_KP_Tab, Qt::Key_Tab, + XK_KP_Enter, Qt::Key_Enter, + //XK_KP_F1, Qt::Key_F1, + //XK_KP_F2, Qt::Key_F2, + //XK_KP_F3, Qt::Key_F3, + //XK_KP_F4, Qt::Key_F4, + XK_KP_Home, Qt::Key_Home, + XK_KP_Left, Qt::Key_Left, + XK_KP_Up, Qt::Key_Up, + XK_KP_Right, Qt::Key_Right, + XK_KP_Down, Qt::Key_Down, + XK_KP_Prior, Qt::Key_PageUp, + XK_KP_Next, Qt::Key_PageDown, + XK_KP_End, Qt::Key_End, + XK_KP_Begin, Qt::Key_Clear, + XK_KP_Insert, Qt::Key_Insert, + XK_KP_Delete, Qt::Key_Delete, + XK_KP_Equal, Qt::Key_Equal, + XK_KP_Multiply, Qt::Key_Asterisk, + XK_KP_Add, Qt::Key_Plus, + XK_KP_Separator, Qt::Key_Comma, + XK_KP_Subtract, Qt::Key_Minus, + XK_KP_Decimal, Qt::Key_Period, + XK_KP_Divide, Qt::Key_Slash, + + // International input method support keys + + // International & multi-key character composition + XK_ISO_Level3_Shift, Qt::Key_AltGr, + XK_Multi_key, Qt::Key_Multi_key, + XK_Codeinput, Qt::Key_Codeinput, + XK_SingleCandidate, Qt::Key_SingleCandidate, + XK_MultipleCandidate, Qt::Key_MultipleCandidate, + XK_PreviousCandidate, Qt::Key_PreviousCandidate, + + // Misc Functions + XK_Mode_switch, Qt::Key_Mode_switch, + XK_script_switch, Qt::Key_Mode_switch, + + // Japanese keyboard support + XK_Kanji, Qt::Key_Kanji, + XK_Muhenkan, Qt::Key_Muhenkan, + //XK_Henkan_Mode, Qt::Key_Henkan_Mode, + XK_Henkan_Mode, Qt::Key_Henkan, + XK_Henkan, Qt::Key_Henkan, + XK_Romaji, Qt::Key_Romaji, + XK_Hiragana, Qt::Key_Hiragana, + XK_Katakana, Qt::Key_Katakana, + XK_Hiragana_Katakana, Qt::Key_Hiragana_Katakana, + XK_Zenkaku, Qt::Key_Zenkaku, + XK_Hankaku, Qt::Key_Hankaku, + XK_Zenkaku_Hankaku, Qt::Key_Zenkaku_Hankaku, + XK_Touroku, Qt::Key_Touroku, + XK_Massyo, Qt::Key_Massyo, + XK_Kana_Lock, Qt::Key_Kana_Lock, + XK_Kana_Shift, Qt::Key_Kana_Shift, + XK_Eisu_Shift, Qt::Key_Eisu_Shift, + XK_Eisu_toggle, Qt::Key_Eisu_toggle, + //XK_Kanji_Bangou, Qt::Key_Kanji_Bangou, + //XK_Zen_Koho, Qt::Key_Zen_Koho, + //XK_Mae_Koho, Qt::Key_Mae_Koho, + XK_Kanji_Bangou, Qt::Key_Codeinput, + XK_Zen_Koho, Qt::Key_MultipleCandidate, + XK_Mae_Koho, Qt::Key_PreviousCandidate, + +#ifdef XK_KOREAN + // Korean keyboard support + XK_Hangul, Qt::Key_Hangul, + XK_Hangul_Start, Qt::Key_Hangul_Start, + XK_Hangul_End, Qt::Key_Hangul_End, + XK_Hangul_Hanja, Qt::Key_Hangul_Hanja, + XK_Hangul_Jamo, Qt::Key_Hangul_Jamo, + XK_Hangul_Romaja, Qt::Key_Hangul_Romaja, + //XK_Hangul_Codeinput, Qt::Key_Hangul_Codeinput, + XK_Hangul_Codeinput, Qt::Key_Codeinput, + XK_Hangul_Jeonja, Qt::Key_Hangul_Jeonja, + XK_Hangul_Banja, Qt::Key_Hangul_Banja, + XK_Hangul_PreHanja, Qt::Key_Hangul_PreHanja, + XK_Hangul_PostHanja, Qt::Key_Hangul_PostHanja, + //XK_Hangul_SingleCandidate,Qt::Key_Hangul_SingleCandidate, + //XK_Hangul_MultipleCandidate,Qt::Key_Hangul_MultipleCandidate, + //XK_Hangul_PreviousCandidate,Qt::Key_Hangul_PreviousCandidate, + XK_Hangul_SingleCandidate, Qt::Key_SingleCandidate, + XK_Hangul_MultipleCandidate,Qt::Key_MultipleCandidate, + XK_Hangul_PreviousCandidate,Qt::Key_PreviousCandidate, + XK_Hangul_Special, Qt::Key_Hangul_Special, + //XK_Hangul_switch, Qt::Key_Hangul_switch, + XK_Hangul_switch, Qt::Key_Mode_switch, +#endif // XK_KOREAN + + // dead keys + XK_dead_grave, Qt::Key_Dead_Grave, + XK_dead_acute, Qt::Key_Dead_Acute, + XK_dead_circumflex, Qt::Key_Dead_Circumflex, + XK_dead_tilde, Qt::Key_Dead_Tilde, + XK_dead_macron, Qt::Key_Dead_Macron, + XK_dead_breve, Qt::Key_Dead_Breve, + XK_dead_abovedot, Qt::Key_Dead_Abovedot, + XK_dead_diaeresis, Qt::Key_Dead_Diaeresis, + XK_dead_abovering, Qt::Key_Dead_Abovering, + XK_dead_doubleacute, Qt::Key_Dead_Doubleacute, + XK_dead_caron, Qt::Key_Dead_Caron, + XK_dead_cedilla, Qt::Key_Dead_Cedilla, + XK_dead_ogonek, Qt::Key_Dead_Ogonek, + XK_dead_iota, Qt::Key_Dead_Iota, + XK_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound, + XK_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound, + XK_dead_belowdot, Qt::Key_Dead_Belowdot, + XK_dead_hook, Qt::Key_Dead_Hook, + XK_dead_horn, Qt::Key_Dead_Horn, + + // Special keys from X.org - This include multimedia keys, + // wireless/bluetooth/uwb keys, special launcher keys, etc. + XF86XK_Back, Qt::Key_Back, + XF86XK_Forward, Qt::Key_Forward, + XF86XK_Stop, Qt::Key_Stop, + XF86XK_Refresh, Qt::Key_Refresh, + XF86XK_Favorites, Qt::Key_Favorites, + XF86XK_AudioMedia, Qt::Key_LaunchMedia, + XF86XK_OpenURL, Qt::Key_OpenUrl, + XF86XK_HomePage, Qt::Key_HomePage, + XF86XK_Search, Qt::Key_Search, + XF86XK_AudioLowerVolume, Qt::Key_VolumeDown, + XF86XK_AudioMute, Qt::Key_VolumeMute, + XF86XK_AudioRaiseVolume, Qt::Key_VolumeUp, + XF86XK_AudioPlay, Qt::Key_MediaPlay, + XF86XK_AudioStop, Qt::Key_MediaStop, + XF86XK_AudioPrev, Qt::Key_MediaPrevious, + XF86XK_AudioNext, Qt::Key_MediaNext, + XF86XK_AudioRecord, Qt::Key_MediaRecord, + XF86XK_Mail, Qt::Key_LaunchMail, + XF86XK_MyComputer, Qt::Key_Launch0, // ### Qt 5: remap properly + XF86XK_Calculator, Qt::Key_Launch1, + XF86XK_Memo, Qt::Key_Memo, + XF86XK_ToDoList, Qt::Key_ToDoList, + XF86XK_Calendar, Qt::Key_Calendar, + XF86XK_PowerDown, Qt::Key_PowerDown, + XF86XK_ContrastAdjust, Qt::Key_ContrastAdjust, + XF86XK_Standby, Qt::Key_Standby, + XF86XK_MonBrightnessUp, Qt::Key_MonBrightnessUp, + XF86XK_MonBrightnessDown, Qt::Key_MonBrightnessDown, + XF86XK_KbdLightOnOff, Qt::Key_KeyboardLightOnOff, + XF86XK_KbdBrightnessUp, Qt::Key_KeyboardBrightnessUp, + XF86XK_KbdBrightnessDown, Qt::Key_KeyboardBrightnessDown, + XF86XK_PowerOff, Qt::Key_PowerOff, + XF86XK_WakeUp, Qt::Key_WakeUp, + XF86XK_Eject, Qt::Key_Eject, + XF86XK_ScreenSaver, Qt::Key_ScreenSaver, + XF86XK_WWW, Qt::Key_WWW, + XF86XK_Sleep, Qt::Key_Sleep, + XF86XK_LightBulb, Qt::Key_LightBulb, + XF86XK_Shop, Qt::Key_Shop, + XF86XK_History, Qt::Key_History, + XF86XK_AddFavorite, Qt::Key_AddFavorite, + XF86XK_HotLinks, Qt::Key_HotLinks, + XF86XK_BrightnessAdjust, Qt::Key_BrightnessAdjust, + XF86XK_Finance, Qt::Key_Finance, + XF86XK_Community, Qt::Key_Community, + XF86XK_AudioRewind, Qt::Key_AudioRewind, + XF86XK_BackForward, Qt::Key_BackForward, + XF86XK_ApplicationLeft, Qt::Key_ApplicationLeft, + XF86XK_ApplicationRight, Qt::Key_ApplicationRight, + XF86XK_Book, Qt::Key_Book, + XF86XK_CD, Qt::Key_CD, + XF86XK_Calculater, Qt::Key_Calculator, + XF86XK_Clear, Qt::Key_Clear, + XF86XK_ClearGrab, Qt::Key_ClearGrab, + XF86XK_Close, Qt::Key_Close, + XF86XK_Copy, Qt::Key_Copy, + XF86XK_Cut, Qt::Key_Cut, + XF86XK_Display, Qt::Key_Display, + XF86XK_DOS, Qt::Key_DOS, + XF86XK_Documents, Qt::Key_Documents, + XF86XK_Excel, Qt::Key_Excel, + XF86XK_Explorer, Qt::Key_Explorer, + XF86XK_Game, Qt::Key_Game, + XF86XK_Go, Qt::Key_Go, + XF86XK_iTouch, Qt::Key_iTouch, + XF86XK_LogOff, Qt::Key_LogOff, + XF86XK_Market, Qt::Key_Market, + XF86XK_Meeting, Qt::Key_Meeting, + XF86XK_MenuKB, Qt::Key_MenuKB, + XF86XK_MenuPB, Qt::Key_MenuPB, + XF86XK_MySites, Qt::Key_MySites, + XF86XK_News, Qt::Key_News, + XF86XK_OfficeHome, Qt::Key_OfficeHome, + XF86XK_Option, Qt::Key_Option, + XF86XK_Paste, Qt::Key_Paste, + XF86XK_Phone, Qt::Key_Phone, + XF86XK_Reply, Qt::Key_Reply, + XF86XK_Reload, Qt::Key_Reload, + XF86XK_RotateWindows, Qt::Key_RotateWindows, + XF86XK_RotationPB, Qt::Key_RotationPB, + XF86XK_RotationKB, Qt::Key_RotationKB, + XF86XK_Save, Qt::Key_Save, + XF86XK_Send, Qt::Key_Send, + XF86XK_Spell, Qt::Key_Spell, + XF86XK_SplitScreen, Qt::Key_SplitScreen, + XF86XK_Support, Qt::Key_Support, + XF86XK_TaskPane, Qt::Key_TaskPane, + XF86XK_Terminal, Qt::Key_Terminal, + XF86XK_Tools, Qt::Key_Tools, + XF86XK_Travel, Qt::Key_Travel, + XF86XK_Video, Qt::Key_Video, + XF86XK_Word, Qt::Key_Word, + XF86XK_Xfer, Qt::Key_Xfer, + XF86XK_ZoomIn, Qt::Key_ZoomIn, + XF86XK_ZoomOut, Qt::Key_ZoomOut, + XF86XK_Away, Qt::Key_Away, + XF86XK_Messenger, Qt::Key_Messenger, + XF86XK_WebCam, Qt::Key_WebCam, + XF86XK_MailForward, Qt::Key_MailForward, + XF86XK_Pictures, Qt::Key_Pictures, + XF86XK_Music, Qt::Key_Music, + XF86XK_Battery, Qt::Key_Battery, + XF86XK_Bluetooth, Qt::Key_Bluetooth, + XF86XK_WLAN, Qt::Key_WLAN, + XF86XK_UWB, Qt::Key_UWB, + XF86XK_AudioForward, Qt::Key_AudioForward, + XF86XK_AudioRepeat, Qt::Key_AudioRepeat, + XF86XK_AudioRandomPlay, Qt::Key_AudioRandomPlay, + XF86XK_Subtitle, Qt::Key_Subtitle, + XF86XK_AudioCycleTrack, Qt::Key_AudioCycleTrack, + XF86XK_Time, Qt::Key_Time, + XF86XK_Select, Qt::Key_Select, + XF86XK_View, Qt::Key_View, + XF86XK_TopMenu, Qt::Key_TopMenu, + XF86XK_Bluetooth, Qt::Key_Bluetooth, + XF86XK_Suspend, Qt::Key_Suspend, + XF86XK_Hibernate, Qt::Key_Hibernate, + XF86XK_Launch0, Qt::Key_Launch2, // ### Qt 5: remap properly + XF86XK_Launch1, Qt::Key_Launch3, + XF86XK_Launch2, Qt::Key_Launch4, + XF86XK_Launch3, Qt::Key_Launch5, + XF86XK_Launch4, Qt::Key_Launch6, + XF86XK_Launch5, Qt::Key_Launch7, + XF86XK_Launch6, Qt::Key_Launch8, + XF86XK_Launch7, Qt::Key_Launch9, + XF86XK_Launch8, Qt::Key_LaunchA, + XF86XK_Launch9, Qt::Key_LaunchB, + XF86XK_LaunchA, Qt::Key_LaunchC, + XF86XK_LaunchB, Qt::Key_LaunchD, + XF86XK_LaunchC, Qt::Key_LaunchE, + XF86XK_LaunchD, Qt::Key_LaunchF, + XF86XK_LaunchE, Qt::Key_LaunchG, + XF86XK_LaunchF, Qt::Key_LaunchH, + + // Qtopia keys + QTOPIAXK_Select, Qt::Key_Select, + QTOPIAXK_Yes, Qt::Key_Yes, + QTOPIAXK_No, Qt::Key_No, + QTOPIAXK_Cancel, Qt::Key_Cancel, + QTOPIAXK_Printer, Qt::Key_Printer, + QTOPIAXK_Execute, Qt::Key_Execute, + QTOPIAXK_Sleep, Qt::Key_Sleep, + QTOPIAXK_Play, Qt::Key_Play, + QTOPIAXK_Zoom, Qt::Key_Zoom, + QTOPIAXK_Context1, Qt::Key_Context1, + QTOPIAXK_Context2, Qt::Key_Context2, + QTOPIAXK_Context3, Qt::Key_Context3, + QTOPIAXK_Context4, Qt::Key_Context4, + QTOPIAXK_Call, Qt::Key_Call, + QTOPIAXK_Hangup, Qt::Key_Hangup, + QTOPIAXK_Flip, Qt::Key_Flip, + + 0, 0 +}; + +static int translateKeySym(uint key) +{ + int code = -1; + int i = 0; // any other keys + while (KeyTbl[i]) { + if (key == KeyTbl[i]) { + code = (int)KeyTbl[i+1]; + break; + } + i += 2; + } + if (qt_meta_mask) { + // translate Super/Hyper keys to Meta if we're using them as the MetaModifier + if (qt_meta_mask == qt_super_mask && (code == Qt::Key_Super_L || code == Qt::Key_Super_R)) { + code = Qt::Key_Meta; + } else if (qt_meta_mask == qt_hyper_mask && (code == Qt::Key_Hyper_L || code == Qt::Key_Hyper_R)) { + code = Qt::Key_Meta; + } + } + return code; +} + +#if !defined(QT_NO_XIM) +static const unsigned short katakanaKeysymsToUnicode[] = { + 0x0000, 0x3002, 0x300C, 0x300D, 0x3001, 0x30FB, 0x30F2, 0x30A1, + 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30C3, + 0x30FC, 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, + 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, + 0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, + 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, + 0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, + 0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F3, 0x309B, 0x309C +}; + +static const unsigned short cyrillicKeysymsToUnicode[] = { + 0x0000, 0x0452, 0x0453, 0x0451, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x0000, 0x045e, 0x045f, + 0x2116, 0x0402, 0x0403, 0x0401, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x0000, 0x040e, 0x040f, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a +}; + +static const unsigned short greekKeysymsToUnicode[] = { + 0x0000, 0x0386, 0x0388, 0x0389, 0x038a, 0x03aa, 0x0000, 0x038c, + 0x038e, 0x03ab, 0x0000, 0x038f, 0x0000, 0x0000, 0x0385, 0x2015, + 0x0000, 0x03ac, 0x03ad, 0x03ae, 0x03af, 0x03ca, 0x0390, 0x03cc, + 0x03cd, 0x03cb, 0x03b0, 0x03ce, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, 0x03a3, 0x0000, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c3, 0x03c2, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +static const unsigned short technicalKeysymsToUnicode[] = { + 0x0000, 0x23B7, 0x250C, 0x2500, 0x2320, 0x2321, 0x2502, 0x23A1, + 0x23A3, 0x23A4, 0x23A6, 0x239B, 0x239D, 0x239E, 0x23A0, 0x23A8, + 0x23AC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x2264, 0x2260, 0x2265, 0x222B, + 0x2234, 0x221D, 0x221E, 0x0000, 0x0000, 0x2207, 0x0000, 0x0000, + 0x223C, 0x2243, 0x0000, 0x0000, 0x0000, 0x21D4, 0x21D2, 0x2261, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x221A, 0x0000, + 0x0000, 0x0000, 0x2282, 0x2283, 0x2229, 0x222A, 0x2227, 0x2228, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2202, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0192, 0x0000, + 0x0000, 0x0000, 0x0000, 0x2190, 0x2191, 0x2192, 0x2193, 0x0000 +}; + +static const unsigned short specialKeysymsToUnicode[] = { + 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x0000, 0x0000, + 0x2424, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA, + 0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, + 0x2502, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +static const unsigned short publishingKeysymsToUnicode[] = { + 0x0000, 0x2003, 0x2002, 0x2004, 0x2005, 0x2007, 0x2008, 0x2009, + 0x200a, 0x2014, 0x2013, 0x0000, 0x0000, 0x0000, 0x2026, 0x2025, + 0x2153, 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215a, + 0x2105, 0x0000, 0x0000, 0x2012, 0x2329, 0x0000, 0x232a, 0x0000, + 0x0000, 0x0000, 0x0000, 0x215b, 0x215c, 0x215d, 0x215e, 0x0000, + 0x0000, 0x2122, 0x2613, 0x0000, 0x25c1, 0x25b7, 0x25cb, 0x25af, + 0x2018, 0x2019, 0x201c, 0x201d, 0x211e, 0x0000, 0x2032, 0x2033, + 0x0000, 0x271d, 0x0000, 0x25ac, 0x25c0, 0x25b6, 0x25cf, 0x25ae, + 0x25e6, 0x25ab, 0x25ad, 0x25b3, 0x25bd, 0x2606, 0x2022, 0x25aa, + 0x25b2, 0x25bc, 0x261c, 0x261e, 0x2663, 0x2666, 0x2665, 0x0000, + 0x2720, 0x2020, 0x2021, 0x2713, 0x2717, 0x266f, 0x266d, 0x2642, + 0x2640, 0x260e, 0x2315, 0x2117, 0x2038, 0x201a, 0x201e, 0x0000 +}; + +static const unsigned short aplKeysymsToUnicode[] = { + 0x0000, 0x0000, 0x0000, 0x003c, 0x0000, 0x0000, 0x003e, 0x0000, + 0x2228, 0x2227, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00af, 0x0000, 0x22a5, 0x2229, 0x230a, 0x0000, 0x005f, 0x0000, + 0x0000, 0x0000, 0x2218, 0x0000, 0x2395, 0x0000, 0x22a4, 0x25cb, + 0x0000, 0x0000, 0x0000, 0x2308, 0x0000, 0x0000, 0x222a, 0x0000, + 0x2283, 0x0000, 0x2282, 0x0000, 0x22a2, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x22a3, 0x0000, 0x0000, 0x0000 +}; + +static const unsigned short koreanKeysymsToUnicode[] = { + 0x0000, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, + 0x3138, 0x3139, 0x313a, 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, + 0x3140, 0x3141, 0x3142, 0x3143, 0x3144, 0x3145, 0x3146, 0x3147, + 0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e, 0x314f, + 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, + 0x3158, 0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, + 0x3160, 0x3161, 0x3162, 0x3163, 0x11a8, 0x11a9, 0x11aa, 0x11ab, + 0x11ac, 0x11ad, 0x11ae, 0x11af, 0x11b0, 0x11b1, 0x11b2, 0x11b3, + 0x11b4, 0x11b5, 0x11b6, 0x11b7, 0x11b8, 0x11b9, 0x11ba, 0x11bb, + 0x11bc, 0x11bd, 0x11be, 0x11bf, 0x11c0, 0x11c1, 0x11c2, 0x316d, + 0x3171, 0x3178, 0x317f, 0x3181, 0x3184, 0x3186, 0x318d, 0x318e, + 0x11eb, 0x11f0, 0x11f9, 0x0000, 0x0000, 0x0000, 0x0000, 0x20a9 +}; + +static QChar keysymToUnicode(unsigned char byte3, unsigned char byte4) +{ + switch (byte3) { + case 0x04: + // katakana + if (byte4 > 0xa0 && byte4 < 0xe0) + return QChar(katakanaKeysymsToUnicode[byte4 - 0xa0]); + else if (byte4 == 0x7e) + return QChar(0x203e); // Overline + break; + case 0x06: + // russian, use lookup table + if (byte4 > 0xa0) + return QChar(cyrillicKeysymsToUnicode[byte4 - 0xa0]); + break; + case 0x07: + // greek + if (byte4 > 0xa0) + return QChar(greekKeysymsToUnicode[byte4 - 0xa0]); + break; + case 0x08: + // technical + if (byte4 > 0xa0) + return QChar(technicalKeysymsToUnicode[byte4 - 0xa0]); + break; + case 0x09: + // special + if (byte4 >= 0xe0) + return QChar(specialKeysymsToUnicode[byte4 - 0xe0]); + break; + case 0x0a: + // publishing + if (byte4 > 0xa0) + return QChar(publishingKeysymsToUnicode[byte4 - 0xa0]); + break; + case 0x0b: + // APL + if (byte4 > 0xa0) + return QChar(aplKeysymsToUnicode[byte4 - 0xa0]); + break; + case 0x0e: + // Korean + if (byte4 > 0xa0) + return QChar(koreanKeysymsToUnicode[byte4 - 0xa0]); + break; + default: + break; + } + return QChar(0x0); +} +#endif + +static QString translateKeySym(KeySym keysym, uint xmodifiers, + int &code, Qt::KeyboardModifiers &modifiers, + QByteArray &chars, int &count) +{ + // all keysyms smaller than 0xff00 are actally keys that can be mapped to unicode chars + + extern QTextCodec *qt_input_mapper; // from qapplication_x11.cpp + QTextCodec *mapper = qt_input_mapper; + QChar converted; + + if (count == 0 && keysym < 0xff00) { + unsigned char byte3 = (unsigned char)(keysym >> 8); + int mib = -1; + switch(byte3) { + case 0: // Latin 1 + case 1: // Latin 2 + case 2: //latin 3 + case 3: // latin4 + mib = byte3 + 4; break; + case 5: // arabic + mib = 82; break; + case 12: // Hebrew + mib = 85; break; + case 13: // Thai + mib = 2259; break; + case 4: // kana + case 6: // cyrillic + case 7: // greek + case 8: // technical, no mapping here at the moment + case 9: // Special + case 10: // Publishing + case 11: // APL + case 14: // Korean, no mapping + mib = -1; // manual conversion + mapper = 0; +#if !defined(QT_NO_XIM) + converted = keysymToUnicode(byte3, keysym & 0xff); +#endif + case 0x20: + // currency symbols + if (keysym >= 0x20a0 && keysym <= 0x20ac) { + mib = -1; // manual conversion + mapper = 0; + converted = (uint)keysym; + } + break; + default: + break; + } + if (mib != -1) { + mapper = QTextCodec::codecForMib(mib); + if (chars.isEmpty()) + chars.resize(1); + chars[0] = (unsigned char) (keysym & 0xff); // get only the fourth bit for conversion later + count++; + } + } else if (keysym >= 0x1000000 && keysym <= 0x100ffff) { + converted = (ushort) (keysym - 0x1000000); + mapper = 0; + } + if (count < (int)chars.size()-1) + chars[count] = '\0'; + + QString text; + if (!mapper && converted.unicode() != 0x0) { + text = converted; + } else if (!chars.isEmpty()) { + // convert chars (8bit) to text (unicode). + if (mapper) + text = mapper->toUnicode(chars.data(), count, 0); + if (text.isEmpty()) { + // no mapper, or codec couldn't convert to unicode (this + // can happen when running in the C locale or with no LANG + // set). try converting from latin-1 + text = QString::fromLatin1(chars); + } + } + + modifiers = X11->translateModifiers(xmodifiers); + + // Commentary in X11/keysymdef says that X codes match ASCII, so it + // is safe to use the locale functions to process X codes in ISO8859-1. + // + // This is mainly for compatibility - applications should not use the + // Qt keycodes between 128 and 255, but should rather use the + // QKeyEvent::text(). + // + extern QTextCodec *qt_input_mapper; // from qapplication_x11.cpp + if (keysym < 128 || (keysym < 256 && (!qt_input_mapper || qt_input_mapper->mibEnum()==4))) { + // upper-case key, if known + code = isprint((int)keysym) ? toupper((int)keysym) : 0; + } else if (keysym >= XK_F1 && keysym <= XK_F35) { + // function keys + code = Qt::Key_F1 + ((int)keysym - XK_F1); + } else if (keysym >= XK_KP_Space && keysym <= XK_KP_9) { + if (keysym >= XK_KP_0) { + // numeric keypad keys + code = Qt::Key_0 + ((int)keysym - XK_KP_0); + } else { + code = translateKeySym(keysym); + } + modifiers |= Qt::KeypadModifier; + } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f && text.unicode()->unicode() != 0x7f && !(keysym >= XK_dead_grave && keysym <= XK_dead_horn)) { + code = text.unicode()->toUpper().unicode(); + } else { + // any other keys + code = translateKeySym(keysym); + + if (code == Qt::Key_Tab && (modifiers & Qt::ShiftModifier)) { + // map shift+tab to shift+backtab, QShortcutMap knows about it + // and will handle it. + code = Qt::Key_Backtab; + text = QString(); + } + } + + return text; +} + +extern bool qt_use_rtl_extensions; // from qapplication_x11.cpp + +bool QKeyMapperPrivate::translateKeyEventInternal(QWidget *keyWidget, + const XEvent *event, + KeySym &keysym, + int& count, + QString& text, + Qt::KeyboardModifiers &modifiers, + int& code, + QEvent::Type &type, + bool statefulTranslation) +{ + XKeyEvent xkeyevent = event->xkey; + int keycode = event->xkey.keycode; + // save the modifier state, we will use the keystate uint later by passing + // it to translateButtonState + uint keystate = event->xkey.state; + + type = (event->type == XKeyPress) ? QEvent::KeyPress : QEvent::KeyRelease; + + static int directionKeyEvent = 0; + static unsigned int lastWinId = 0; + + // translate pending direction change + if (statefulTranslation && qt_use_rtl_extensions && type == QEvent::KeyRelease) { + if (directionKeyEvent == Qt::Key_Direction_R || directionKeyEvent == Qt::Key_Direction_L) { + type = QEvent::KeyPress; + code = directionKeyEvent; + text = QString(); + directionKeyEvent = 0; + lastWinId = 0; + return true; + } else { + directionKeyEvent = 0; + lastWinId = 0; + } + } + + // some XmbLookupString implementations don't return buffer overflow correctly, + // so we increase the input buffer to allow for long strings... + // 256 chars * 2 bytes + 1 null-term == 513 bytes + QByteArray chars; + chars.resize(513); + + count = XLookupString(&xkeyevent, chars.data(), chars.size(), &keysym, 0); + if (count && !keycode) { + extern int qt_ximComposingKeycode; // from qapplication_x11.cpp + keycode = qt_ximComposingKeycode; + qt_ximComposingKeycode = 0; + } + + // translate the keysym + xmodifiers to Qt::Key_* + Qt::KeyboardModifiers + text = translateKeySym(keysym, keystate, code, modifiers, chars, count); + + // Watch for keypresses and if its a key belonging to the Ctrl-Shift + // direction-changing accel, remember it. + // We keep track of those keys instead of using the event's state + // (to figure out whether the Ctrl modifier is held while Shift is pressed, + // or Shift is held while Ctrl is pressed) since the 'state' doesn't tell + // us whether the modifier held is Left or Right. + if (statefulTranslation && qt_use_rtl_extensions && type == QEvent::KeyPress) { + if (keysym == XK_Control_L || keysym == XK_Control_R + || keysym == XK_Shift_L || keysym == XK_Shift_R) { + if (!directionKeyEvent) { + directionKeyEvent = keysym; + // This code exists in order to check that + // the event is occurred in the same widget. + lastWinId = keyWidget->internalWinId(); + } + } else { + // this can no longer be a direction-changing accel. + // if any other key was pressed. + directionKeyEvent = Qt::Key_Space; + } + + if (directionKeyEvent && lastWinId == keyWidget->internalWinId()) { + if ((keysym == XK_Shift_L && directionKeyEvent == XK_Control_L) + || (keysym == XK_Control_L && directionKeyEvent == XK_Shift_L)) { + directionKeyEvent = Qt::Key_Direction_L; + } else if ((keysym == XK_Shift_R && directionKeyEvent == XK_Control_R) + || (keysym == XK_Control_R && directionKeyEvent == XK_Shift_R)) { + directionKeyEvent = Qt::Key_Direction_R; + } + } else if (directionKeyEvent == Qt::Key_Direction_L + || directionKeyEvent == Qt::Key_Direction_R) { + directionKeyEvent = Qt::Key_Space; // invalid + } + } + + return true; +} + + +struct qt_auto_repeat_data +{ + // match the window and keycode with timestamp delta of 10 ms + Window window; + KeyCode keycode; + Time timestamp; + + // queue scanner state + bool release; + bool error; +}; + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +static Bool qt_keypress_scanner(Display *, XEvent *event, XPointer arg) +{ + if (event->type != XKeyPress && event->type != XKeyRelease) + return false; + + qt_auto_repeat_data *data = (qt_auto_repeat_data *) arg; + if (data->error) + return false; + + if (event->xkey.window != data->window || + event->xkey.keycode != data->keycode) { + // deal breakers: key events in a different window or an event + // with a different key code + data->error = true; + return false; + } + + if (event->type == XKeyPress) { + data->error = (! data->release || event->xkey.time - data->timestamp > 10); + return (! data->error); + } + + // must be XKeyRelease event + if (data->release) { + // found a second release + data->error = true; + return false; + } + + // found a single release + data->release = true; + data->timestamp = event->xkey.time; + + return false; +} + +static Bool qt_keyrelease_scanner(Display *, XEvent *event, XPointer arg) +{ + const qt_auto_repeat_data *data = (const qt_auto_repeat_data *) arg; + return (event->type == XKeyRelease && + event->xkey.window == data->window && + event->xkey.keycode == data->keycode); +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +bool QKeyMapperPrivate::translateKeyEvent(QWidget *keyWidget, const XEvent *event, bool grab) +{ + int code = -1; + int count = 0; + Qt::KeyboardModifiers modifiers; + + if (qt_sm_blockUserInput) // block user interaction during session management + return true; + + Display *dpy = X11->display; + + if (!keyWidget->isEnabled()) + return true; + + QEvent::Type type; + bool autor = false; + QString text; + + KeySym keysym = 0; + translateKeyEventInternal(keyWidget, event, keysym, count, text, modifiers, code, type); + + // was this the last auto-repeater? + qt_auto_repeat_data auto_repeat_data; + auto_repeat_data.window = event->xkey.window; + auto_repeat_data.keycode = event->xkey.keycode; + auto_repeat_data.timestamp = event->xkey.time; + + static uint curr_autorep = 0; + if (event->type == XKeyPress) { + if (curr_autorep == event->xkey.keycode) { + autor = true; + curr_autorep = 0; + } + } else { + // look ahead for auto-repeat + XEvent nextpress; + + auto_repeat_data.release = true; + auto_repeat_data.error = false; + if (XCheckIfEvent(dpy, &nextpress, &qt_keypress_scanner, + (XPointer) &auto_repeat_data)) { + autor = true; + + // Put it back... we COULD send the event now and not need + // the static curr_autorep variable. + XPutBackEvent(dpy,&nextpress); + } + curr_autorep = autor ? event->xkey.keycode : 0; + } + +#if defined QT3_SUPPORT && !defined(QT_NO_SHORTCUT) + // process accelerators before doing key compression + if (type == QEvent::KeyPress && !grab + && QApplicationPrivate::instance()->use_compat()) { + // send accel events if the keyboard is not grabbed + QKeyEventEx a(type, code, modifiers, text, autor, qMax(qMax(count,1), int(text.length())), + event->xkey.keycode, keysym, event->xkey.state); + if (QApplicationPrivate::instance()->qt_tryAccelEvent(keyWidget, &a)) + return true; + } +#endif + +#ifndef QT_NO_IM + QInputContext *qic = keyWidget->inputContext(); +#endif + + // compress keys + if (!text.isEmpty() && keyWidget->testAttribute(Qt::WA_KeyCompression) && +#ifndef QT_NO_IM + // Ordinary input methods require discrete key events to work + // properly, so key compression has to be disabled when input + // context exists. + // + // And further consideration, some complex input method + // require all key press/release events discretely even if + // the input method awares of key compression and compressed + // keys are ordinary alphabets. For example, the uim project + // is planning to implement "combinational shift" feature for + // a Japanese input method, uim-skk. It will work as follows. + // + // 1. press "r" + // 2. press "u" + // 3. release both "r" and "u" in arbitrary order + // 4. above key sequence generates "Ru" + // + // Of course further consideration about other participants + // such as key repeat mechanism is required to implement such + // feature. + !qic && +#endif // QT_NO_IM + // do not compress keys if the key event we just got above matches + // one of the key ranges used to compute stopCompression + !((code >= Qt::Key_Escape && code <= Qt::Key_SysReq) + || (code >= Qt::Key_Home && code <= Qt::Key_PageDown) + || (code >= Qt::Key_Super_L && code <= Qt::Key_Direction_R) + || (code == 0) + || (text.length() == 1 && text.unicode()->unicode() == '\n'))) { + // the widget wants key compression so it gets it + + // sync the event queue, this makes key compress work better + XSync(dpy, false); + + for (;;) { + XEvent evRelease; + XEvent evPress; + if (!XCheckTypedWindowEvent(dpy,event->xkey.window, + XKeyRelease,&evRelease)) + break; + if (!XCheckTypedWindowEvent(dpy,event->xkey.window, + XKeyPress,&evPress)) { + XPutBackEvent(dpy, &evRelease); + break; + } + QString textIntern; + int codeIntern = -1; + int countIntern = 0; + Qt::KeyboardModifiers modifiersIntern; + QEvent::Type t; + KeySym keySymIntern; + translateKeyEventInternal(keyWidget, &evPress, keySymIntern, countIntern, textIntern, + modifiersIntern, codeIntern, t); + // use stopCompression to stop key compression for the following + // key event ranges: + bool stopCompression = + // 1) misc keys + (codeIntern >= Qt::Key_Escape && codeIntern <= Qt::Key_SysReq) + // 2) cursor movement + || (codeIntern >= Qt::Key_Home && codeIntern <= Qt::Key_PageDown) + // 3) extra keys + || (codeIntern >= Qt::Key_Super_L && codeIntern <= Qt::Key_Direction_R) + // 4) something that a) doesn't translate to text or b) translates + // to newline text + || (codeIntern == 0) + || (textIntern.length() == 1 && textIntern.unicode()->unicode() == '\n') + || (codeIntern == Qt::Key_unknown); + + if (modifiersIntern == modifiers && !textIntern.isEmpty() && !stopCompression) { + text += textIntern; + count += countIntern; + } else { + XPutBackEvent(dpy, &evPress); + XPutBackEvent(dpy, &evRelease); + break; + } + } + } + + // autorepeat compression makes sense for all widgets (Windows + // does it automatically ....) + if (event->type == XKeyPress && text.length() <= 1 +#ifndef QT_NO_IM + // input methods need discrete key events + && !qic +#endif// QT_NO_IM + ) { + XEvent dummy; + + for (;;) { + auto_repeat_data.release = false; + auto_repeat_data.error = false; + if (! XCheckIfEvent(dpy, &dummy, &qt_keypress_scanner, + (XPointer) &auto_repeat_data)) + break; + if (! XCheckIfEvent(dpy, &dummy, &qt_keyrelease_scanner, + (XPointer) &auto_repeat_data)) + break; + + count++; + if (!text.isEmpty()) + text += text[0]; + } + } + + return QKeyMapper::sendKeyEvent(keyWidget, grab, type, code, modifiers, text, autor, + qMax(qMax(count,1), int(text.length())), + event->xkey.keycode, keysym, event->xkey.state); +} + +bool QKeyMapper::sendKeyEvent(QWidget *keyWidget, bool grab, + QEvent::Type type, int code, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorepeat, int count, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, + bool *) +{ + // try the menukey first + if (type == QEvent::KeyPress && code == Qt::Key_Menu) { + QVariant v = keyWidget->inputMethodQuery(Qt::ImMicroFocus); + QPoint globalPos; + QPoint pos; + if (v.isNull()) { + globalPos = QCursor::pos(); + pos = keyWidget->mapFromGlobal(globalPos); + } else { + pos = v.toRect().center(); + globalPos = keyWidget->mapToGlobal(pos); + } + QContextMenuEvent e(QContextMenuEvent::Keyboard, pos, globalPos); + qt_sendSpontaneousEvent(keyWidget, &e); + if(e.isAccepted()) + return true; + } + + Q_UNUSED(grab); + QKeyEventEx e(type, code, modifiers, text, autorepeat, qMax(qMax(count,1), int(text.length())), + nativeScanCode, nativeVirtualKey, nativeModifiers); + return qt_sendSpontaneousEvent(keyWidget, &e); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeymapper_x11_p.cpp b/src/gui/kernel/qkeymapper_x11_p.cpp new file mode 100644 index 0000000000..2dbe1e77a4 --- /dev/null +++ b/src/gui/kernel/qkeymapper_x11_p.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is auto-generated, do not edit! +// (Generated using util/xkbdatagen) + +static struct { + const char *layout; + const char *variant; // 0 means any variant + Qt::LayoutDirection direction; + QLocale::Language language; + QLocale::Country country; +} xkbLayoutData[] = { + // name = us, description = U.S. English + { "us", "", Qt::LeftToRight, QLocale::English, QLocale::UnitedStates }, + // name = us:intl, description = U.S. English + { "us", "intl", Qt::LeftToRight, QLocale::English, QLocale::UnitedStates }, + // name = us:alt-intl, description = U.S. English + { "us", "alt-intl", Qt::LeftToRight, QLocale::English, QLocale::UnitedStates }, + // name = us:dvorak, description = U.S. English + { "us", "dvorak", Qt::LeftToRight, QLocale::English, QLocale::UnitedStates }, + // name = us:rus, description = U.S. English + { "us", "rus", Qt::LeftToRight, QLocale::Russian, QLocale::UnitedStates }, + // name = ara, description = Arabic + { "ara", "", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = ara:azerty, description = Arabic + { "ara", "azerty", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = ara:azerty_digits, description = Arabic + { "ara", "azerty_digits", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = ara:digits, description = Arabic + { "ara", "digits", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = ara:qwerty, description = Arabic + { "ara", "qwerty", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = ara:qwerty_digits, description = Arabic + { "ara", "qwerty_digits", Qt::RightToLeft, QLocale::Arabic, QLocale::UnitedArabEmirates }, + // name = al, description = Albania + { "al", "", Qt::LeftToRight, QLocale::Albanian, QLocale::Albania }, + // name = am, description = Armenia + { "am", "", Qt::LeftToRight, QLocale::Armenian, QLocale::Armenia }, + // name = am:phonetic, description = Armenia + { "am", "phonetic", Qt::LeftToRight, QLocale::Armenian, QLocale::Armenia }, + // name = az, description = Azerbaijan + { "az", "", Qt::LeftToRight, QLocale::Azerbaijani, QLocale::Azerbaijan }, + // name = az:cyrillic, description = Azerbaijan + { "az", "cyrillic", Qt::LeftToRight, QLocale::Azerbaijani, QLocale::Azerbaijan }, + // name = by, description = Belarus + { "by", "", Qt::LeftToRight, QLocale::Byelorussian, QLocale::Belarus }, + // name = by:winkeys, description = Belarus + { "by", "winkeys", Qt::LeftToRight, QLocale::Byelorussian, QLocale::Belarus }, + // name = be, description = Belgium + { "be", "", Qt::LeftToRight, QLocale::Dutch, QLocale::Belgium }, + // name = be:iso-alternate, description = Belgium + { "be", "iso-alternate", Qt::LeftToRight, QLocale::Dutch, QLocale::Belgium }, + // name = be:nodeadkeys, description = Belgium + { "be", "nodeadkeys", Qt::LeftToRight, QLocale::Dutch, QLocale::Belgium }, + // name = be:sundeadkeys, description = Belgium + { "be", "sundeadkeys", Qt::LeftToRight, QLocale::Dutch, QLocale::Belgium }, + // name = bd, description = Bangladesh + { "bd", "", Qt::LeftToRight, QLocale::Bengali, QLocale::Bangladesh }, + // name = bd:probhat, description = Bangladesh + { "bd", "probhat", Qt::LeftToRight, QLocale::Bengali, QLocale::Bangladesh }, + // name = in, description = India + { "in", "", Qt::LeftToRight, QLocale::Hindi, QLocale::India }, + // name = in:ben, description = India + { "in", "ben", Qt::LeftToRight, QLocale::Bengali, QLocale::India }, + // name = in:ben_probhat, description = India + { "in", "ben_probhat", Qt::LeftToRight, QLocale::Bengali, QLocale::India }, + // name = in:guj, description = India + { "in", "guj", Qt::LeftToRight, QLocale::Gujarati, QLocale::India }, + // name = in:guru, description = India + { "in", "guru", Qt::LeftToRight, QLocale::Punjabi, QLocale::India }, + // name = in:kan, description = India + { "in", "kan", Qt::LeftToRight, QLocale::Kannada, QLocale::India }, + // name = in:mal, description = India + { "in", "mal", Qt::LeftToRight, QLocale::Malayalam, QLocale::India }, + // name = in:ori, description = India + { "in", "ori", Qt::LeftToRight, QLocale::Oriya, QLocale::India }, + // name = in:tam_unicode, description = India + { "in", "tam_unicode", Qt::LeftToRight, QLocale::Tamil, QLocale::India }, + // name = in:tam_TAB, description = India + { "in", "tam_TAB", Qt::LeftToRight, QLocale::Tamil, QLocale::India }, + // name = in:tam_TSCII, description = India + { "in", "tam_TSCII", Qt::LeftToRight, QLocale::Tamil, QLocale::India }, + // name = in:tam, description = India + { "in", "tam", Qt::LeftToRight, QLocale::Tamil, QLocale::India }, + // name = in:tel, description = India + { "in", "tel", Qt::LeftToRight, QLocale::Telugu, QLocale::India }, + // name = in:urd, description = India + { "in", "urd", Qt::RightToLeft, QLocale::Urdu, QLocale::India }, + // name = ba, description = Bosnia and Herzegovina + { "ba", "", Qt::LeftToRight, QLocale::Bosnian, QLocale::BosniaAndHerzegowina }, + // name = br, description = Brazil + { "br", "", Qt::LeftToRight, QLocale::Portuguese, QLocale::Brazil }, + // name = br:nodeadkeys, description = Brazil + { "br", "nodeadkeys", Qt::LeftToRight, QLocale::Portuguese, QLocale::Brazil }, + // name = bg, description = Bulgaria + { "bg", "", Qt::LeftToRight, QLocale::Bulgarian, QLocale::Bulgaria }, + // name = bg:phonetic, description = Bulgaria + { "bg", "phonetic", Qt::LeftToRight, QLocale::Bulgarian, QLocale::Bulgaria }, + // name = mm, description = Myanmar + { "mm", "", Qt::LeftToRight, QLocale::Burmese, QLocale::Myanmar }, + // name = ca, description = Canada + { "ca", "", Qt::LeftToRight, QLocale::English, QLocale::Canada }, + // name = ca:fr-dvorak, description = Canada + { "ca", "fr-dvorak", Qt::LeftToRight, QLocale::French, QLocale::Canada }, + // name = ca:fr-legacy, description = Canada + { "ca", "fr-legacy", Qt::LeftToRight, QLocale::French, QLocale::Canada }, + // name = ca:multi, description = Canada + { "ca", "multi", Qt::LeftToRight, QLocale::English, QLocale::Canada }, + // name = ca:multi-2gr, description = Canada + { "ca", "multi-2gr", Qt::LeftToRight, QLocale::English, QLocale::Canada }, + // name = ca:ike, description = Canada + { "ca", "ike", Qt::LeftToRight, QLocale::Inuktitut, QLocale::Canada }, + // name = hr, description = Croatia + { "hr", "", Qt::LeftToRight, QLocale::Croatian, QLocale::Croatia }, + // name = hr:us, description = Croatia + { "hr", "us", Qt::LeftToRight, QLocale::Croatian, QLocale::Croatia }, + // name = cz, description = Czechia + { "cz", "", Qt::LeftToRight, QLocale::Czech, QLocale::CzechRepublic }, + // name = cz:bksl, description = Czechia + { "cz", "bksl", Qt::LeftToRight, QLocale::Czech, QLocale::CzechRepublic }, + // name = cz:qwerty, description = Czechia + { "cz", "qwerty", Qt::LeftToRight, QLocale::Czech, QLocale::CzechRepublic }, + // name = cz:qwerty_bksl, description = Czechia + { "cz", "qwerty_bksl", Qt::LeftToRight, QLocale::Czech, QLocale::CzechRepublic }, + // name = dk, description = Denmark + { "dk", "", Qt::LeftToRight, QLocale::Danish, QLocale::Denmark }, + // name = dk:nodeadkeys, description = Denmark + { "dk", "nodeadkeys", Qt::LeftToRight, QLocale::Danish, QLocale::Denmark }, + // name = nl, description = Netherlands + { "nl", "", Qt::LeftToRight, QLocale::Dutch, QLocale::Netherlands }, + // name = bt, description = Bhutan + { "bt", "", Qt::LeftToRight, QLocale::Bhutani, QLocale::Bhutan }, + // name = ee, description = Estonia + { "ee", "", Qt::LeftToRight, QLocale::Estonian, QLocale::Estonia }, + // name = ee:nodeadkeys, description = Estonia + { "ee", "nodeadkeys", Qt::LeftToRight, QLocale::Estonian, QLocale::Estonia }, + // name = ir, description = Iran + { "ir", "", Qt::RightToLeft, QLocale::Persian, QLocale::Iran }, + // name = fo, description = Faroe Islands + { "fo", "", Qt::LeftToRight, QLocale::Faroese, QLocale::FaroeIslands }, + // name = fo:nodeadkeys, description = Faroe Islands + { "fo", "nodeadkeys", Qt::LeftToRight, QLocale::Faroese, QLocale::FaroeIslands }, + // name = fi, description = Finland + { "fi", "", Qt::LeftToRight, QLocale::Finnish, QLocale::Finland }, + // name = fi:nodeadkeys, description = Finland + { "fi", "nodeadkeys", Qt::LeftToRight, QLocale::Finnish, QLocale::Finland }, + // name = fi:smi, description = Finland + { "fi", "smi", Qt::LeftToRight, QLocale::Finnish, QLocale::Finland }, + // name = fr, description = France + { "fr", "", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:nodeadkeys, description = France + { "fr", "nodeadkeys", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:sundeadkeys, description = France + { "fr", "sundeadkeys", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:latin9, description = France + { "fr", "latin9", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:latin9_nodeadkeys, description = France + { "fr", "latin9_nodeadkeys", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:latin9_sundeadkeys, description = France + { "fr", "latin9_sundeadkeys", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = fr:dvorak, description = France + { "fr", "dvorak", Qt::LeftToRight, QLocale::French, QLocale::France }, + // name = ge, description = Georgia + { "ge", "", Qt::LeftToRight, QLocale::Georgian, QLocale::Georgia }, + // name = ge:ru, description = Georgia + { "ge", "ru", Qt::LeftToRight, QLocale::Russian, QLocale::Georgia }, + // name = de, description = Germany + { "de", "", Qt::LeftToRight, QLocale::German, QLocale::Germany }, + // name = de:deadacute, description = Germany + { "de", "deadacute", Qt::LeftToRight, QLocale::German, QLocale::Germany }, + // name = de:deadgraveacute, description = Germany + { "de", "deadgraveacute", Qt::LeftToRight, QLocale::German, QLocale::Germany }, + // name = de:nodeadkeys, description = Germany + { "de", "nodeadkeys", Qt::LeftToRight, QLocale::German, QLocale::Germany }, + // name = de:ro, description = Germany + { "de", "ro", Qt::LeftToRight, QLocale::Romanian, QLocale::Germany }, + // name = de:ro_nodeadkeys, description = Germany + { "de", "ro_nodeadkeys", Qt::LeftToRight, QLocale::Romanian, QLocale::Germany }, + // name = de:dvorak, description = Germany + { "de", "dvorak", Qt::LeftToRight, QLocale::German, QLocale::Germany }, + // name = gr, description = Greece + { "gr", "", Qt::LeftToRight, QLocale::Greek, QLocale::Greece }, + // name = gr:extended, description = Greece + { "gr", "extended", Qt::LeftToRight, QLocale::Greek, QLocale::Greece }, + // name = gr:nodeadkeys, description = Greece + { "gr", "nodeadkeys", Qt::LeftToRight, QLocale::Greek, QLocale::Greece }, + // name = gr:polytonic, description = Greece + { "gr", "polytonic", Qt::LeftToRight, QLocale::Greek, QLocale::Greece }, + // name = hu, description = Hungary + { "hu", "", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:standard, description = Hungary + { "hu", "standard", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:nodeadkeys, description = Hungary + { "hu", "nodeadkeys", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:qwerty, description = Hungary + { "hu", "qwerty", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwertz_comma_dead, description = Hungary + { "hu", "101_qwertz_comma_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwertz_comma_nodead, description = Hungary + { "hu", "101_qwertz_comma_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwertz_dot_dead, description = Hungary + { "hu", "101_qwertz_dot_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwertz_dot_nodead, description = Hungary + { "hu", "101_qwertz_dot_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwerty_comma_dead, description = Hungary + { "hu", "101_qwerty_comma_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwerty_comma_nodead, description = Hungary + { "hu", "101_qwerty_comma_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwerty_dot_dead, description = Hungary + { "hu", "101_qwerty_dot_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:101_qwerty_dot_nodead, description = Hungary + { "hu", "101_qwerty_dot_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwertz_comma_dead, description = Hungary + { "hu", "102_qwertz_comma_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwertz_comma_nodead, description = Hungary + { "hu", "102_qwertz_comma_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwertz_dot_dead, description = Hungary + { "hu", "102_qwertz_dot_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwertz_dot_nodead, description = Hungary + { "hu", "102_qwertz_dot_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwerty_comma_dead, description = Hungary + { "hu", "102_qwerty_comma_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwerty_comma_nodead, description = Hungary + { "hu", "102_qwerty_comma_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwerty_dot_dead, description = Hungary + { "hu", "102_qwerty_dot_dead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = hu:102_qwerty_dot_nodead, description = Hungary + { "hu", "102_qwerty_dot_nodead", Qt::LeftToRight, QLocale::Hungarian, QLocale::Hungary }, + // name = is, description = Iceland + { "is", "", Qt::LeftToRight, QLocale::Icelandic, QLocale::Iceland }, + // name = is:Sundeadkeys, description = Iceland + { "is", "Sundeadkeys", Qt::LeftToRight, QLocale::Icelandic, QLocale::Iceland }, + // name = is:nodeadkeys, description = Iceland + { "is", "nodeadkeys", Qt::LeftToRight, QLocale::Icelandic, QLocale::Iceland }, + // name = il, description = Israel + { "il", "", Qt::RightToLeft, QLocale::Hebrew, QLocale::Israel }, + // name = il:lyx, description = Israel + { "il", "lyx", Qt::RightToLeft, QLocale::Hebrew, QLocale::Israel }, + // name = il:si1452, description = Israel + { "il", "si1452", Qt::RightToLeft, QLocale::Hebrew, QLocale::Israel }, + // name = il:phonetic, description = Israel + { "il", "phonetic", Qt::RightToLeft, QLocale::Hebrew, QLocale::Israel }, + // name = it, description = Italy + { "it", "", Qt::LeftToRight, QLocale::Italian, QLocale::Italy }, + // name = it:nodeadkeys, description = Italy + { "it", "nodeadkeys", Qt::LeftToRight, QLocale::Italian, QLocale::Italy }, + // name = jp, description = Japan + { "jp", "", Qt::LeftToRight, QLocale::Japanese, QLocale::Japan }, + // name = kg, description = Kyrgyzstan + { "kg", "", Qt::LeftToRight, QLocale::Kirghiz, QLocale::Kyrgyzstan }, + // name = la, description = Laos + { "la", "", Qt::LeftToRight, QLocale::Laothian, QLocale::Lao }, + // name = latam, description = Latin American + { "latam", "", Qt::LeftToRight, QLocale::Spanish, QLocale::Mexico }, + // name = latam:nodeadkeys, description = Latin American + { "latam", "nodeadkeys", Qt::LeftToRight, QLocale::Spanish, QLocale::Mexico }, + // name = latam:sundeadkeys, description = Latin American + { "latam", "sundeadkeys", Qt::LeftToRight, QLocale::Spanish, QLocale::Mexico }, + // name = lt, description = Lithuania + { "lt", "", Qt::LeftToRight, QLocale::Lithuanian, QLocale::Lithuania }, + // name = lt:std, description = Lithuania + { "lt", "std", Qt::LeftToRight, QLocale::Lithuanian, QLocale::Lithuania }, + // name = lt:us, description = Lithuania + { "lt", "us", Qt::LeftToRight, QLocale::Lithuanian, QLocale::Lithuania }, + // name = lv, description = Latvia + { "lv", "", Qt::LeftToRight, QLocale::Latvian, QLocale::Latvia }, + // name = lv:apostrophe, description = Latvia + { "lv", "apostrophe", Qt::LeftToRight, QLocale::Latvian, QLocale::Latvia }, + // name = lv:tilde, description = Latvia + { "lv", "tilde", Qt::LeftToRight, QLocale::Latvian, QLocale::Latvia }, + // name = lv:fkey, description = Latvia + { "lv", "fkey", Qt::LeftToRight, QLocale::Latvian, QLocale::Latvia }, + // name = mao, description = Maori + { "mao", "", Qt::LeftToRight, QLocale::Maori, QLocale::NewZealand }, + // name = mkd, description = Macedonian + { "mkd", "", Qt::LeftToRight, QLocale::Macedonian, QLocale::Macedonia }, + // name = mkd:nodeadkeys, description = Macedonian + { "mkd", "nodeadkeys", Qt::LeftToRight, QLocale::Macedonian, QLocale::Macedonia }, + // name = mt, description = Malta + { "mt", "", Qt::LeftToRight, QLocale::Maltese, QLocale::Malta }, + // name = mt:us, description = Malta + { "mt", "us", Qt::LeftToRight, QLocale::Maltese, QLocale::Malta }, + // name = mn, description = Mongolia + { "mn", "", Qt::LeftToRight, QLocale::Mongolian, QLocale::Mongolia }, + // name = no, description = Norway + { "no", "", Qt::LeftToRight, QLocale::Norwegian, QLocale::Norway }, + // name = no:nodeadkeys, description = Norway + { "no", "nodeadkeys", Qt::LeftToRight, QLocale::Norwegian, QLocale::Norway }, + // name = no:dvorak, description = Norway + { "no", "dvorak", Qt::LeftToRight, QLocale::Norwegian, QLocale::Norway }, + // name = no:smi, description = Norway + { "no", "smi", Qt::LeftToRight, QLocale::Norwegian, QLocale::Norway }, + // name = no:smi_nodeadkeys, description = Norway + { "no", "smi_nodeadkeys", Qt::LeftToRight, QLocale::Norwegian, QLocale::Norway }, + // name = pl, description = Poland + { "pl", "", Qt::LeftToRight, QLocale::Polish, QLocale::Poland }, + // name = pl:qwertz, description = Poland + { "pl", "qwertz", Qt::LeftToRight, QLocale::Polish, QLocale::Poland }, + // name = pl:dvorak, description = Poland + { "pl", "dvorak", Qt::LeftToRight, QLocale::Polish, QLocale::Poland }, + // name = pl:dvorak_quotes, description = Poland + { "pl", "dvorak_quotes", Qt::LeftToRight, QLocale::Polish, QLocale::Poland }, + // name = pl:dvorak_altquotes, description = Poland + { "pl", "dvorak_altquotes", Qt::LeftToRight, QLocale::Polish, QLocale::Poland }, + // name = pt, description = Portugal + { "pt", "", Qt::LeftToRight, QLocale::Portuguese, QLocale::Portugal }, + // name = pt:nodeadkeys, description = Portugal + { "pt", "nodeadkeys", Qt::LeftToRight, QLocale::Portuguese, QLocale::Portugal }, + // name = pt:sundeadkeys, description = Portugal + { "pt", "sundeadkeys", Qt::LeftToRight, QLocale::Portuguese, QLocale::Portugal }, + // name = ro, description = Romania + { "ro", "", Qt::LeftToRight, QLocale::Romanian, QLocale::Romania }, + // name = ro:us, description = Romania + { "ro", "us", Qt::LeftToRight, QLocale::English, QLocale::Romania }, + // name = ro:de, description = Romania + { "ro", "de", Qt::LeftToRight, QLocale::German, QLocale::Romania }, + // name = ru, description = Russia + { "ru", "", Qt::LeftToRight, QLocale::Russian, QLocale::RussianFederation }, + // name = ru:phonetic, description = Russia + { "ru", "phonetic", Qt::LeftToRight, QLocale::Russian, QLocale::RussianFederation }, + // name = ru:typewriter, description = Russia + { "ru", "typewriter", Qt::LeftToRight, QLocale::Russian, QLocale::RussianFederation }, + // name = ru:winkeys, description = Russia + { "ru", "winkeys", Qt::LeftToRight, QLocale::Russian, QLocale::RussianFederation }, + // name = srp, description = Serbian + { "srp", "", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:yz, description = Serbian + { "srp", "yz", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:latin, description = Serbian + { "srp", "latin", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:latinunicode, description = Serbian + { "srp", "latinunicode", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:latinyz, description = Serbian + { "srp", "latinyz", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:latinunicodeyz, description = Serbian + { "srp", "latinunicodeyz", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:alternatequotes, description = Serbian + { "srp", "alternatequotes", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = srp:latinalternatequotes, description = Serbian + { "srp", "latinalternatequotes", Qt::LeftToRight, QLocale::Serbian, QLocale::SerbiaAndMontenegro }, + // name = si, description = Slovenia + { "si", "", Qt::LeftToRight, QLocale::Slovenian, QLocale::Slovenia }, + // name = sk, description = Slovakia + { "sk", "", Qt::LeftToRight, QLocale::Slovak, QLocale::Slovakia }, + // name = sk:bksl, description = Slovakia + { "sk", "bksl", Qt::LeftToRight, QLocale::Slovak, QLocale::Slovakia }, + // name = sk:qwerty, description = Slovakia + { "sk", "qwerty", Qt::LeftToRight, QLocale::Slovak, QLocale::Slovakia }, + // name = sk:qwerty_bksl, description = Slovakia + { "sk", "qwerty_bksl", Qt::LeftToRight, QLocale::Slovak, QLocale::Slovakia }, + // name = es, description = Spain + { "es", "", Qt::LeftToRight, QLocale::Spanish, QLocale::Spain }, + // name = es:nodeadkeys, description = Spain + { "es", "nodeadkeys", Qt::LeftToRight, QLocale::Spanish, QLocale::Spain }, + // name = es:sundeadkeys, description = Spain + { "es", "sundeadkeys", Qt::LeftToRight, QLocale::Spanish, QLocale::Spain }, + // name = es:dvorak, description = Spain + { "es", "dvorak", Qt::LeftToRight, QLocale::Spanish, QLocale::Spain }, + // name = se, description = Sweden + { "se", "", Qt::LeftToRight, QLocale::Swedish, QLocale::Sweden }, + // name = se:nodeadkeys, description = Sweden + { "se", "nodeadkeys", Qt::LeftToRight, QLocale::Swedish, QLocale::Sweden }, + // name = se:dvorak, description = Sweden + { "se", "dvorak", Qt::LeftToRight, QLocale::Swedish, QLocale::Sweden }, + // name = se:rus, description = Sweden + { "se", "rus", Qt::LeftToRight, QLocale::Russian, QLocale::Sweden }, + // name = se:rus_nodeadkeys, description = Sweden + { "se", "rus_nodeadkeys", Qt::LeftToRight, QLocale::Russian, QLocale::Sweden }, + // name = se:smi, description = Sweden + { "se", "smi", Qt::LeftToRight, QLocale::Swedish, QLocale::Sweden }, + // name = ch, description = Switzerland + { "ch", "", Qt::LeftToRight, QLocale::German, QLocale::Switzerland }, + // name = ch:de_nodeadkeys, description = Switzerland + { "ch", "de_nodeadkeys", Qt::LeftToRight, QLocale::German, QLocale::Switzerland }, + // name = ch:de_sundeadkeys, description = Switzerland + { "ch", "de_sundeadkeys", Qt::LeftToRight, QLocale::German, QLocale::Switzerland }, + // name = ch:fr, description = Switzerland + { "ch", "fr", Qt::LeftToRight, QLocale::French, QLocale::Switzerland }, + // name = ch:fr_nodeadkeys, description = Switzerland + { "ch", "fr_nodeadkeys", Qt::LeftToRight, QLocale::French, QLocale::Switzerland }, + // name = ch:fr_sundeadkeys, description = Switzerland + { "ch", "fr_sundeadkeys", Qt::LeftToRight, QLocale::French, QLocale::Switzerland }, + // name = sy, description = Syria + { "sy", "", Qt::RightToLeft, QLocale::Syriac, QLocale::SyrianArabRepublic }, + // name = sy:syc, description = Syria + { "sy", "syc", Qt::RightToLeft, QLocale::Syriac, QLocale::SyrianArabRepublic }, + // name = sy:syc_phonetic, description = Syria + { "sy", "syc_phonetic", Qt::RightToLeft, QLocale::Syriac, QLocale::SyrianArabRepublic }, + // name = tj, description = Tajikistan + { "tj", "", Qt::LeftToRight, QLocale::Tajik, QLocale::Tajikistan }, + // name = lk, description = Sri Lanka + { "lk", "", Qt::LeftToRight, QLocale::Singhalese, QLocale::SriLanka }, + // name = lk:tam_unicode, description = Sri Lanka + { "lk", "tam_unicode", Qt::LeftToRight, QLocale::Tamil, QLocale::SriLanka }, + // name = lk:tam_TAB, description = Sri Lanka + { "lk", "tam_TAB", Qt::LeftToRight, QLocale::Tamil, QLocale::SriLanka }, + // name = lk:tam_TSCII, description = Sri Lanka + { "lk", "tam_TSCII", Qt::LeftToRight, QLocale::Tamil, QLocale::SriLanka }, + // name = lk:sin_phonetic, description = Sri Lanka + { "lk", "sin_phonetic", Qt::LeftToRight, QLocale::Singhalese, QLocale::SriLanka }, + // name = th, description = Thailand + { "th", "", Qt::LeftToRight, QLocale::Thai, QLocale::Thailand }, + // name = th:tis, description = Thailand + { "th", "tis", Qt::LeftToRight, QLocale::Thai, QLocale::Thailand }, + // name = th:pat, description = Thailand + { "th", "pat", Qt::LeftToRight, QLocale::Thai, QLocale::Thailand }, + // name = tr, description = Turkish + { "tr", "", Qt::LeftToRight, QLocale::Turkish, QLocale::Turkey }, + // name = tr:f, description = Turkish + { "tr", "f", Qt::LeftToRight, QLocale::Turkish, QLocale::Turkey }, + // name = tr:alt, description = Turkish + { "tr", "alt", Qt::LeftToRight, QLocale::Turkish, QLocale::Turkey }, + // name = ua, description = Ukraine + { "ua", "", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = ua:phonetic, description = Ukraine + { "ua", "phonetic", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = ua:typewriter, description = Ukraine + { "ua", "typewriter", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = ua:winkeys, description = Ukraine + { "ua", "winkeys", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = ua:rstu, description = Ukraine + { "ua", "rstu", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = ua:rstu_ru, description = Ukraine + { "ua", "rstu_ru", Qt::LeftToRight, QLocale::Ukrainian, QLocale::Ukraine }, + // name = gb, description = United Kingdom + { "gb", "", Qt::LeftToRight, QLocale::English, QLocale::UnitedKingdom }, + // name = gb:intl, description = United Kingdom + { "gb", "intl", Qt::LeftToRight, QLocale::English, QLocale::UnitedKingdom }, + // name = gb:dvorak, description = United Kingdom + { "gb", "dvorak", Qt::LeftToRight, QLocale::English, QLocale::UnitedKingdom }, + // name = uz, description = Uzbekistan + { "uz", "", Qt::LeftToRight, QLocale::Uzbek, QLocale::Uzbekistan }, + // name = vn, description = Vietnam + { "vn", "", Qt::LeftToRight, QLocale::Vietnamese, QLocale::VietNam }, + // name = nec_vndr/jp, description = PC-98xx Series + { "nec_vndr/jp", "", Qt::LeftToRight, QLocale::Japanese, QLocale::Japan }, + // name = ie, description = Ireland + { "ie", "", Qt::LeftToRight, QLocale::Irish, QLocale::Ireland }, + // name = ie:CloGaelach, description = Ireland + { "ie", "CloGaelach", Qt::LeftToRight, QLocale::Gaelic, QLocale::Ireland }, + // name = ie:UnicodeExpert, description = Ireland + { "ie", "UnicodeExpert", Qt::LeftToRight, QLocale::Irish, QLocale::Ireland }, + // name = ie:ogam, description = Ireland + { "ie", "ogam", Qt::LeftToRight, QLocale::Gaelic, QLocale::Ireland }, + // name = ie:ogam_is434, description = Ireland + { "ie", "ogam_is434", Qt::LeftToRight, QLocale::Gaelic, QLocale::Ireland }, + // name = pk, description = Pakistan + { "pk", "", Qt::RightToLeft, QLocale::Urdu, QLocale::Pakistan }, + { 0, 0, Qt::LeftToRight, QLocale::C, QLocale::AnyCountry } +}; diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp new file mode 100644 index 0000000000..3cf5dc5275 --- /dev/null +++ b/src/gui/kernel/qkeysequence.cpp @@ -0,0 +1,1726 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeysequence.h" +#include "qkeysequence_p.h" +#include "private/qapplication_p.h" + +#ifndef QT_NO_SHORTCUT + +#include "qshortcut.h" +#include "qdebug.h" +#ifndef QT_NO_REGEXP +# include "qregexp.h" +#endif +#ifndef QT_NO_DATASTREAM +# include "qdatastream.h" +#endif +#include "qvariant.h" + +#ifdef Q_WS_MAC +# include <private/qt_mac_p.h> + +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_MAC +static bool qt_sequence_no_mnemonics = true; +struct MacSpecialKey { + int key; + ushort macSymbol; +}; + +static const int NumEntries = 21; +static const MacSpecialKey entries[NumEntries] = { + { Qt::Key_Escape, 0x238B }, + { Qt::Key_Tab, 0x21E5 }, + { Qt::Key_Backtab, 0x21E4 }, + { Qt::Key_Backspace, 0x232B }, + { Qt::Key_Return, 0x21B5 }, + { Qt::Key_Enter, 0x2324 }, + { Qt::Key_Delete, 0x2326 }, + { Qt::Key_Home, 0x2196 }, + { Qt::Key_End, 0x2198 }, + { Qt::Key_Left, 0x2190 }, + { Qt::Key_Up, 0x2191 }, + { Qt::Key_Right, 0x2192 }, + { Qt::Key_Down, 0x2193 }, + { Qt::Key_PageUp, 0x21DE }, + { Qt::Key_PageDown, 0x21DF }, + { Qt::Key_Shift, kShiftUnicode }, + { Qt::Key_Control, kCommandUnicode }, + { Qt::Key_Meta, kControlUnicode }, + { Qt::Key_Alt, kOptionUnicode }, + { Qt::Key_CapsLock, 0x21EA }, +}; + +static bool operator<(const MacSpecialKey &entry, int key) +{ + return entry.key < key; +} + +static bool operator<(int key, const MacSpecialKey &entry) +{ + return key < entry.key; +} + +static const MacSpecialKey * const MacSpecialKeyEntriesEnd = entries + NumEntries; + +QChar qt_macSymbolForQtKey(int key) +{ + const MacSpecialKey *i = qBinaryFind(entries, MacSpecialKeyEntriesEnd, key); + if (i == MacSpecialKeyEntriesEnd) + return QChar(); + ushort macSymbol = i->macSymbol; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) + && (macSymbol == kControlUnicode || macSymbol == kCommandUnicode)) { + if (macSymbol == kControlUnicode) + macSymbol = kCommandUnicode; + else + macSymbol = kControlUnicode; + } + + return QChar(macSymbol); +} + +static int qtkeyForMacSymbol(const QChar ch) +{ + const ushort unicode = ch.unicode(); + for (int i = 0; i < NumEntries; ++i) { + const MacSpecialKey &entry = entries[i]; + if (entry.macSymbol == unicode) { + int key = entry.key; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) + && (unicode == kControlUnicode || unicode == kCommandUnicode)) { + if (unicode == kControlUnicode) + key = Qt::Key_Control; + else + key = Qt::Key_Meta; + } + return key; + } + } + return -1; +} + +#else +static bool qt_sequence_no_mnemonics = false; +#endif +void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemonics = !b; } + +/*! + \class QKeySequence + \brief The QKeySequence class encapsulates a key sequence as used + by shortcuts. + + \ingroup shared + + + In its most common form, a key sequence describes a combination of + keys that must be used together to perform some action. Key sequences + are used with QAction objects to specify which keyboard shortcuts can + be used to trigger actions. + + Key sequences can be constructed for use as keyboard shortcuts in + three different ways: + + \list + \o For standard shortcuts, a \l{QKeySequence::StandardKey}{standard key} + can be used to request the platform-specific key sequence associated + with each shortcut. + \o For custom shortcuts, human-readable strings such as "Ctrl+X" can + be used, and these can be translated into the appropriate shortcuts + for users of different languages. Translations are made in the + "QShortcut" context. + \o For hard-coded shortcuts, integer key codes can be specified with + a combination of values defined by the Qt::Key and Qt::Modifier enum + values. Each key code consists of a single Qt::Key value and zero or + more modifiers, such as Qt::SHIFT, Qt::CTRL, Qt::ALT and Qt::META. + \endlist + + For example, \gui{Ctrl P} might be a sequence used as a shortcut for + printing a document, and can be specified in any of the following + ways: + + \snippet doc/src/snippets/code/src_gui_kernel_qkeysequence.cpp 0 + + Note that, for letters, the case used in the specification string + does not matter. In the above examples, the user does not need to + hold down the \key{Shift} key to activate a shortcut specified + with "Ctrl+P". However, for other keys, the use of \key{Shift} as + an unspecified extra modifier key can lead to confusion for users + of an application whose keyboards have different layouts to those + used by the developers. See the \l{Keyboard Layout Issues} section + below for more details. + + It is preferable to use standard shortcuts where possible. + When creating key sequences for non-standard shortcuts, you should use + human-readable strings in preference to hard-coded integer values. + + QKeySequence objects can be cast to a QString to obtain a human-readable + translated version of the sequence. Similarly, the toString() function + produces human-readable strings for use in menus. On Mac OS X, the + appropriate symbols are used to describe keyboard shortcuts using special + keys on the Macintosh keyboard. + + An alternative way to specify hard-coded key codes is to use the Unicode + code point of the character; for example, 'A' gives the same key sequence + as Qt::Key_A. + + \bold{Note:} On Mac OS X, references to "Ctrl", Qt::CTRL, Qt::Control + and Qt::ControlModifier correspond to the \key Command keys on the + Macintosh keyboard, and references to "Meta", Qt::META, Qt::Meta and + Qt::MetaModifier correspond to the \key Control keys. Developers on + Mac OS X can use the same shortcut descriptions across all platforms, + and their applications will automatically work as expected on Mac OS X. + + \section1 Standard Shortcuts + + QKeySequence defines many \l{QKeySequence::StandardKey} {standard + keyboard shortcuts} to reduce the amount of effort required when + setting up actions in a typical application. The table below shows + some common key sequences that are often used for these standard + shortcuts by applications on four widely-used platforms. Note + that on Mac OS X, the \key Ctrl value corresponds to the \key + Command keys on the Macintosh keyboard, and the \key Meta value + corresponds to the \key Control keys. + + \table + \header \i StandardKey \i Windows \i Mac OS X \i KDE \i GNOME \i S60 + \row \i HelpContents \i F1 \i Ctrl+? \i F1 \i F1 \i F2 + \row \i WhatsThis \i Shift+F1 \i Shift+F1 \i Shift+F1 \i Shift+F1 \i Shift+F1 + \row \i Open \i Ctrl+O \i Ctrl+O \i Ctrl+O \i Ctrl+O \i (none) + \row \i Close \i Ctrl+F4, Ctrl+W \i Ctrl+W, Ctrl+F4 \i Ctrl+W \i Ctrl+W \i (none) + \row \i Save \i Ctrl+S \i Ctrl+S \i Ctrl+S \i Ctrl+S \i (none) + \row \i Quit \i \i Ctrl+Q \i Qtrl+Q \i Qtrl+Q \i (none) + \row \i SaveAs \i \i Ctrl+Shift+S \i \i Ctrl+Shift+S \i (none) + \row \i New \i Ctrl+N \i Ctrl+N \i Ctrl+N \i Ctrl+N \i (none) + \row \i Delete \i Del \i Del, Meta+D \i Del, Ctrl+D \i Del, Ctrl+D \i Del + \row \i Cut \i Ctrl+X, Shift+Del \i Ctrl+X \i Ctrl+X, F20, Shift+Del \i Ctrl+X, F20, Shift+Del \i Ctrl+X + \row \i Copy \i Ctrl+C, Ctrl+Ins \i Ctrl+C \i Ctrl+C, F16, Ctrl+Ins \i Ctrl+C, F16, Ctrl+Ins \i Ctrl+C + \row \i Paste \i Ctrl+V, Shift+Ins \i Ctrl+V \i Ctrl+V, F18, Shift+Ins \i Ctrl+V, F18, Shift+Ins \i Ctrl+V + \row \i Preferences \i \i Ctrl+, \i \i \i (none) + \row \i Undo \i Ctrl+Z, Alt+Backspace \i Ctrl+Z \i Ctrl+Z, F14 \i Ctrl+Z, F14 \i Ctrl+Z + \row \i Redo \i Ctrl+Y, Shift+Ctrl+Z, Alt+Shift+Backspace \i Ctrl+Shift+Z \i Ctrl+Shift+Z \i Ctrl+Shift+Z \i (none) + \row \i Back \i Alt+Left, Backspace \i Ctrl+[ \i Alt+Left \i Alt+Left \i (none) + \row \i Forward \i Alt+Right, Shift+Backspace \i Ctrl+] \i Alt+Right \i Alt+Right \i (none) + \row \i Refresh \i F5 \i F5 \i F5 \i Ctrl+R, F5 \i (none) + \row \i ZoomIn \i Ctrl+Plus \i Ctrl+Plus \i Ctrl+Plus \i Ctrl+Plus \i (none) + \row \i ZoomOut \i Ctrl+Minus \i Ctrl+Minus \i Ctrl+Minus \i Ctrl+Minus \i (none) + \row \i Print \i Ctrl+P \i Ctrl+P \i Ctrl+P \i Ctrl+P \i (none) + \row \i AddTab \i Ctrl+T \i Ctrl+T \i Ctrl+Shift+N, Ctrl+T \i Ctrl+T \i (none) + \row \i NextChild \i Ctrl+Tab, Forward, Ctrl+F6 \i Ctrl+}, Forward, Ctrl+Tab \i Ctrl+Tab, Forward, Ctrl+Comma \i Ctrl+Tab, Forward \i (none) + \row \i PreviousChild \i Ctrl+Shift+Tab, Back, Ctrl+Shift+F6 \i Ctrl+{, Back, Ctrl+Shift+Tab \i Ctrl+Shift+Tab, Back, Ctrl+Period \i Ctrl+Shift+Tab, Back \i (none) + \row \i Find \i Ctrl+F \i Ctrl+F \i Ctrl+F \i Ctrl+F \i (none) + \row \i FindNext \i F3, Ctrl+G \i Ctrl+G \i F3 \i Ctrl+G, F3 \i (none) + \row \i FindPrevious \i Shift+F3, Ctrl+Shift+G \i Ctrl+Shift+G \i Shift+F3 \i Ctrl+Shift+G, Shift+F3 \i (none) + \row \i Replace \i Ctrl+H \i (none) \i Ctrl+R \i Ctrl+H \i (none) + \row \i SelectAll \i Ctrl+A \i Ctrl+A \i Ctrl+A \i Ctrl+A \i (none) + \row \i Bold \i Ctrl+B \i Ctrl+B \i Ctrl+B \i Ctrl+B \i (none) + \row \i Italic \i Ctrl+I \i Ctrl+I \i Ctrl+I \i Ctrl+I \i (none) + \row \i Underline \i Ctrl+U \i Ctrl+U \i Ctrl+U \i Ctrl+U \i (none) + \row \i MoveToNextChar \i Right \i Right \i Right \i Right \i Right + \row \i MoveToPreviousChar \i Left \i Left \i Left \i Left \i Left + \row \i MoveToNextWord \i Ctrl+Right \i Alt+Right \i Ctrl+Right \i Ctrl+Right \i Ctrl+Right + \row \i MoveToPreviousWord \i Ctrl+Left \i Alt+Left \i Ctrl+Left \i Ctrl+Left \i Ctrl+Left + \row \i MoveToNextLine \i Down \i Down \i Down \i Down \i Down + \row \i MoveToPreviousLine \i Up \i Up \i Up \i Up \i Up + \row \i MoveToNextPage \i PgDown \i PgDown, Alt+PgDown, Meta+Down, Meta+PgDown\i PgDown \i PgDown \i PgDown + \row \i MoveToPreviousPage \i PgUp \i PgUp, Alt+PgUp, Meta+Up, Meta+PgUp \i PgUp \i PgUp \i PgUp + \row \i MoveToStartOfLine \i Home \i Ctrl+Left, Meta+Left \i Home \i Home \i Home + \row \i MoveToEndOfLine \i End \i Ctrl+Right, Meta+Right \i End \i End \i End + \row \i MoveToStartOfBlock \i (none) \i Alt+Up, Meta+A \i (none) \i (none) \i (none) + \row \i MoveToEndOfBlock \i (none) \i Alt+Down, Meta+E \i (none) \i (none) \i (none) + \row \i MoveToStartOfDocument\i Ctrl+Home \i Ctrl+Up, Home \i Ctrl+Home \i Ctrl+Home \i Ctrl+Home + \row \i MoveToEndOfDocument \i Ctrl+End \i Ctrl+Down, End \i Ctrl+End \i Ctrl+End \i Ctrl+End + \row \i SelectNextChar \i Shift+Right \i Shift+Right \i Shift+Right \i Shift+Right \i Shift+Right + \row \i SelectPreviousChar \i Shift+Left \i Shift+Left \i Shift+Left \i Shift+Left \i Shift+Left + \row \i SelectNextWord \i Ctrl+Shift+Right \i Alt+Shift+Right \i Ctrl+Shift+Right \i Ctrl+Shift+Right \i Ctrl+Shift+Right + \row \i SelectPreviousWord \i Ctrl+Shift+Left \i Alt+Shift+Left \i Ctrl+Shift+Left \i Ctrl+Shift+Left \i Ctrl+Shift+Left + \row \i SelectNextLine \i Shift+Down \i Shift+Down \i Shift+Down \i Shift+Down \i Shift+Down + \row \i SelectPreviousLine \i Shift+Up \i Shift+Up \i Shift+Up \i Shift+Up \i Shift+Up + \row \i SelectNextPage \i Shift+PgDown \i Shift+PgDown \i Shift+PgDown \i Shift+PgDown \i Shift+PgDown + \row \i SelectPreviousPage \i Shift+PgUp \i Shift+PgUp \i Shift+PgUp \i Shift+PgUp \i Shift+PgUp + \row \i SelectStartOfLine \i Shift+Home \i Ctrl+Shift+Left \i Shift+Home \i Shift+Home \i Shift+Home + \row \i SelectEndOfLine \i Shift+End \i Ctrl+Shift+Right \i Shift+End \i Shift+End \i Shift+End + \row \i SelectStartOfBlock \i (none) \i Alt+Shift+Up, Meta+Shift+A \i (none) \i (none) \i (none) + \row \i SelectEndOfBlock \i (none) \i Alt+Shift+Down, Meta+Shift+E \i (none) \i (none) \i (none) + \row \i SelectStartOfDocument\i Ctrl+Shift+Home \i Ctrl+Shift+Up, Shift+Home \i Ctrl+Shift+Home\i Ctrl+Shift+Home \i Ctrl+Shift+Home + \row \i SelectEndOfDocument \i Ctrl+Shift+End \i Ctrl+Shift+Down, Shift+End \i Ctrl+Shift+End \i Ctrl+Shift+End \i Ctrl+Shift+End + \row \i DeleteStartOfWord \i Ctrl+Backspace \i Alt+Backspace \i Ctrl+Backspace \i Ctrl+Backspace \i (none) + \row \i DeleteEndOfWord \i Ctrl+Del \i (none) \i Ctrl+Del \i Ctrl+Del \i (none) + \row \i DeleteEndOfLine \i (none) \i (none) \i Ctrl+K \i Ctrl+K \i (none) + \row \i InsertParagraphSeparator \i Enter \i Enter \i Enter \i Enter \i (none) + \row \i InsertLineSeparator \i Shift+Enter \i Meta+Enter \i Shift+Enter \i Shift+Enter \i (none) + \endtable + + Note that, since the key sequences used for the standard shortcuts differ + between platforms, you still need to test your shortcuts on each platform + to ensure that you do not unintentionally assign the same key sequence to + many actions. + + \section1 Keyboard Layout Issues + + Many key sequence specifications are chosen by developers based on the + layout of certain types of keyboard, rather than choosing keys that + represent the first letter of an action's name, such as \key{Ctrl S} + ("Ctrl+S") or \key{Ctrl C} ("Ctrl+C"). + Additionally, because certain symbols can only be entered with the + help of modifier keys on certain keyboard layouts, key sequences intended + for use with one keyboard layout may map to a different key, map to no + keys at all, or require an additional modifier key to be used on + different keyboard layouts. + + For example, the shortcuts, \key{Ctrl plus} and \key{Ctrl minus}, are often + used as shortcuts for zoom operations in graphics applications, and these + may be specified as "Ctrl++" and "Ctrl+-" respectively. However, the way + these shortcuts are specified and interpreted depends on the keyboard layout. + Users of Norwegian keyboards will note that the \key{+} and \key{-} keys + are not adjacent on the keyboard, but will still be able to activate both + shortcuts without needing to press the \key{Shift} key. However, users + with British keyboards will need to hold down the \key{Shift} key + to enter the \key{+} symbol, making the shortcut effectively the same as + "Ctrl+Shift+=". + + Although some developers might resort to fully specifying all the modifiers + they use on their keyboards to activate a shortcut, this will also result + in unexpected behavior for users of different keyboard layouts. + + For example, a developer using a British keyboard may decide to specify + "Ctrl+Shift+=" as the key sequence in order to create a shortcut that + coincidentally behaves in the same way as \key{Ctrl plus}. However, the + \key{=} key needs to be accessed using the \key{Shift} key on Norwegian + keyboard, making the required shortcut effectively \key{Ctrl Shift Shift =} + (an impossible key combination). + + As a result, both human-readable strings and hard-coded key codes + can both be problematic to use when specifying a key sequence that + can be used on a variety of different keyboard layouts. Only the + use of \l{QKeySequence::StandardKey} {standard shortcuts} + guarantees that the user will be able to use the shortcuts that + the developer intended. + + Despite this, we can address this issue by ensuring that human-readable + strings are used, making it possible for translations of key sequences to + be made for users of different languages. This approach will be successful + for users whose keyboards have the most typical layout for the language + they are using. + + \section1 GNU Emacs Style Key Sequences + + Key sequences similar to those used in \l{GNU Emacs}, allowing up to four + key codes, can be created by using the multiple argument constructor, + or by passing a human-readable string of comma-separated key sequences. + + For example, the key sequence, \key{Ctrl X} followed by \key{Ctrl C}, can + be specified using either of the following ways: + + \snippet doc/src/snippets/code/src_gui_kernel_qkeysequence.cpp 1 + + \warning A QApplication instance must have been constructed before a + QKeySequence is created; otherwise, your application may crash. + + \sa QShortcut +*/ + +/*! + \enum QKeySequence::SequenceMatch + + \value NoMatch The key sequences are different; not even partially + matching. + \value PartialMatch The key sequences match partially, but are not + the same. + \value ExactMatch The key sequences are the same. + \omitvalue Identical +*/ + +/*! + \enum QKeySequence::SequenceFormat + + \value NativeText The key sequence as a platform specific string. + This means that it will be shown translated and on the Mac it will + resemble a key sequence from the menu bar. This enum is best used when you + want to display the string to the user. + + \value PortableText The key sequence is given in a "portable" format, + suitable for reading and writing to a file. In many cases, it will look + similar to the native text on Windows and X11. +*/ + +static const struct { + int key; + const char* name; +} keyname[] = { + //: This and all following "incomprehensible" strings in QShortcut context + //: are key names. Please use the localized names appearing on actual + //: keyboards or whatever is commonly used. + { Qt::Key_Space, QT_TRANSLATE_NOOP("QShortcut", "Space") }, + { Qt::Key_Escape, QT_TRANSLATE_NOOP("QShortcut", "Esc") }, + { Qt::Key_Tab, QT_TRANSLATE_NOOP("QShortcut", "Tab") }, + { Qt::Key_Backtab, QT_TRANSLATE_NOOP("QShortcut", "Backtab") }, + { Qt::Key_Backspace, QT_TRANSLATE_NOOP("QShortcut", "Backspace") }, + { Qt::Key_Return, QT_TRANSLATE_NOOP("QShortcut", "Return") }, + { Qt::Key_Enter, QT_TRANSLATE_NOOP("QShortcut", "Enter") }, + { Qt::Key_Insert, QT_TRANSLATE_NOOP("QShortcut", "Ins") }, + { Qt::Key_Delete, QT_TRANSLATE_NOOP("QShortcut", "Del") }, + { Qt::Key_Pause, QT_TRANSLATE_NOOP("QShortcut", "Pause") }, + { Qt::Key_Print, QT_TRANSLATE_NOOP("QShortcut", "Print") }, + { Qt::Key_SysReq, QT_TRANSLATE_NOOP("QShortcut", "SysReq") }, + { Qt::Key_Home, QT_TRANSLATE_NOOP("QShortcut", "Home") }, + { Qt::Key_End, QT_TRANSLATE_NOOP("QShortcut", "End") }, + { Qt::Key_Left, QT_TRANSLATE_NOOP("QShortcut", "Left") }, + { Qt::Key_Up, QT_TRANSLATE_NOOP("QShortcut", "Up") }, + { Qt::Key_Right, QT_TRANSLATE_NOOP("QShortcut", "Right") }, + { Qt::Key_Down, QT_TRANSLATE_NOOP("QShortcut", "Down") }, + { Qt::Key_PageUp, QT_TRANSLATE_NOOP("QShortcut", "PgUp") }, + { Qt::Key_PageDown, QT_TRANSLATE_NOOP("QShortcut", "PgDown") }, + { Qt::Key_CapsLock, QT_TRANSLATE_NOOP("QShortcut", "CapsLock") }, + { Qt::Key_NumLock, QT_TRANSLATE_NOOP("QShortcut", "NumLock") }, + { Qt::Key_ScrollLock, QT_TRANSLATE_NOOP("QShortcut", "ScrollLock") }, + { Qt::Key_Menu, QT_TRANSLATE_NOOP("QShortcut", "Menu") }, + { Qt::Key_Help, QT_TRANSLATE_NOOP("QShortcut", "Help") }, + + // Special keys + // Includes multimedia, launcher, lan keys ( bluetooth, wireless ) + // window navigation + { Qt::Key_Back, QT_TRANSLATE_NOOP("QShortcut", "Back") }, + { Qt::Key_Forward, QT_TRANSLATE_NOOP("QShortcut", "Forward") }, + { Qt::Key_Stop, QT_TRANSLATE_NOOP("QShortcut", "Stop") }, + { Qt::Key_Refresh, QT_TRANSLATE_NOOP("QShortcut", "Refresh") }, + { Qt::Key_VolumeDown, QT_TRANSLATE_NOOP("QShortcut", "Volume Down") }, + { Qt::Key_VolumeMute, QT_TRANSLATE_NOOP("QShortcut", "Volume Mute") }, + { Qt::Key_VolumeUp, QT_TRANSLATE_NOOP("QShortcut", "Volume Up") }, + { Qt::Key_BassBoost, QT_TRANSLATE_NOOP("QShortcut", "Bass Boost") }, + { Qt::Key_BassUp, QT_TRANSLATE_NOOP("QShortcut", "Bass Up") }, + { Qt::Key_BassDown, QT_TRANSLATE_NOOP("QShortcut", "Bass Down") }, + { Qt::Key_TrebleUp, QT_TRANSLATE_NOOP("QShortcut", "Treble Up") }, + { Qt::Key_TrebleDown, QT_TRANSLATE_NOOP("QShortcut", "Treble Down") }, + { Qt::Key_MediaPlay, QT_TRANSLATE_NOOP("QShortcut", "Media Play") }, + { Qt::Key_MediaStop, QT_TRANSLATE_NOOP("QShortcut", "Media Stop") }, + { Qt::Key_MediaPrevious, QT_TRANSLATE_NOOP("QShortcut", "Media Previous") }, + { Qt::Key_MediaNext, QT_TRANSLATE_NOOP("QShortcut", "Media Next") }, + { Qt::Key_MediaRecord, QT_TRANSLATE_NOOP("QShortcut", "Media Record") }, + //: Media player pause button + { Qt::Key_MediaPause, QT_TRANSLATE_NOOP("QShortcut", "Media Pause") }, + //: Media player button to toggle between playing and paused + { Qt::Key_MediaTogglePlayPause, QT_TRANSLATE_NOOP("QShortcut", "Toggle Media Play/Pause") }, + { Qt::Key_HomePage, QT_TRANSLATE_NOOP("QShortcut", "Home Page") }, + { Qt::Key_Favorites, QT_TRANSLATE_NOOP("QShortcut", "Favorites") }, + { Qt::Key_Search, QT_TRANSLATE_NOOP("QShortcut", "Search") }, + { Qt::Key_Standby, QT_TRANSLATE_NOOP("QShortcut", "Standby") }, + { Qt::Key_OpenUrl, QT_TRANSLATE_NOOP("QShortcut", "Open URL") }, + { Qt::Key_LaunchMail, QT_TRANSLATE_NOOP("QShortcut", "Launch Mail") }, + { Qt::Key_LaunchMedia, QT_TRANSLATE_NOOP("QShortcut", "Launch Media") }, + { Qt::Key_Launch0, QT_TRANSLATE_NOOP("QShortcut", "Launch (0)") }, + { Qt::Key_Launch1, QT_TRANSLATE_NOOP("QShortcut", "Launch (1)") }, + { Qt::Key_Launch2, QT_TRANSLATE_NOOP("QShortcut", "Launch (2)") }, + { Qt::Key_Launch3, QT_TRANSLATE_NOOP("QShortcut", "Launch (3)") }, + { Qt::Key_Launch4, QT_TRANSLATE_NOOP("QShortcut", "Launch (4)") }, + { Qt::Key_Launch5, QT_TRANSLATE_NOOP("QShortcut", "Launch (5)") }, + { Qt::Key_Launch6, QT_TRANSLATE_NOOP("QShortcut", "Launch (6)") }, + { Qt::Key_Launch7, QT_TRANSLATE_NOOP("QShortcut", "Launch (7)") }, + { Qt::Key_Launch8, QT_TRANSLATE_NOOP("QShortcut", "Launch (8)") }, + { Qt::Key_Launch9, QT_TRANSLATE_NOOP("QShortcut", "Launch (9)") }, + { Qt::Key_LaunchA, QT_TRANSLATE_NOOP("QShortcut", "Launch (A)") }, + { Qt::Key_LaunchB, QT_TRANSLATE_NOOP("QShortcut", "Launch (B)") }, + { Qt::Key_LaunchC, QT_TRANSLATE_NOOP("QShortcut", "Launch (C)") }, + { Qt::Key_LaunchD, QT_TRANSLATE_NOOP("QShortcut", "Launch (D)") }, + { Qt::Key_LaunchE, QT_TRANSLATE_NOOP("QShortcut", "Launch (E)") }, + { Qt::Key_LaunchF, QT_TRANSLATE_NOOP("QShortcut", "Launch (F)") }, + { Qt::Key_MonBrightnessUp, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Up") }, + { Qt::Key_MonBrightnessDown, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Down") }, + { Qt::Key_KeyboardLightOnOff, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Light On/Off") }, + { Qt::Key_KeyboardBrightnessUp, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Brightness Up") }, + { Qt::Key_KeyboardBrightnessDown, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Brightness Down") }, + { Qt::Key_PowerOff, QT_TRANSLATE_NOOP("QShortcut", "Power Off") }, + { Qt::Key_WakeUp, QT_TRANSLATE_NOOP("QShortcut", "Wake Up") }, + { Qt::Key_Eject, QT_TRANSLATE_NOOP("QShortcut", "Eject") }, + { Qt::Key_ScreenSaver, QT_TRANSLATE_NOOP("QShortcut", "Screensaver") }, + { Qt::Key_WWW, QT_TRANSLATE_NOOP("QShortcut", "WWW") }, + { Qt::Key_Sleep, QT_TRANSLATE_NOOP("QShortcut", "Sleep") }, + { Qt::Key_LightBulb, QT_TRANSLATE_NOOP("QShortcut", "LightBulb") }, + { Qt::Key_Shop, QT_TRANSLATE_NOOP("QShortcut", "Shop") }, + { Qt::Key_History, QT_TRANSLATE_NOOP("QShortcut", "History") }, + { Qt::Key_AddFavorite, QT_TRANSLATE_NOOP("QShortcut", "Add Favorite") }, + { Qt::Key_HotLinks, QT_TRANSLATE_NOOP("QShortcut", "Hot Links") }, + { Qt::Key_BrightnessAdjust, QT_TRANSLATE_NOOP("QShortcut", "Adjust Brightness") }, + { Qt::Key_Finance, QT_TRANSLATE_NOOP("QShortcut", "Finance") }, + { Qt::Key_Community, QT_TRANSLATE_NOOP("QShortcut", "Community") }, + { Qt::Key_AudioRewind, QT_TRANSLATE_NOOP("QShortcut", "Audio Rewind") }, + { Qt::Key_BackForward, QT_TRANSLATE_NOOP("QShortcut", "Back Forward") }, + { Qt::Key_ApplicationLeft, QT_TRANSLATE_NOOP("QShortcut", "Application Left") }, + { Qt::Key_ApplicationRight, QT_TRANSLATE_NOOP("QShortcut", "Application Right") }, + { Qt::Key_Book, QT_TRANSLATE_NOOP("QShortcut", "Book") }, + { Qt::Key_CD, QT_TRANSLATE_NOOP("QShortcut", "CD") }, + { Qt::Key_Calculator, QT_TRANSLATE_NOOP("QShortcut", "Calculator") }, + { Qt::Key_Clear, QT_TRANSLATE_NOOP("QShortcut", "Clear") }, + { Qt::Key_ClearGrab, QT_TRANSLATE_NOOP("QShortcut", "Clear Grab") }, + { Qt::Key_Close, QT_TRANSLATE_NOOP("QShortcut", "Close") }, + { Qt::Key_Copy, QT_TRANSLATE_NOOP("QShortcut", "Copy") }, + { Qt::Key_Cut, QT_TRANSLATE_NOOP("QShortcut", "Cut") }, + { Qt::Key_Display, QT_TRANSLATE_NOOP("QShortcut", "Display") }, + { Qt::Key_DOS, QT_TRANSLATE_NOOP("QShortcut", "DOS") }, + { Qt::Key_Documents, QT_TRANSLATE_NOOP("QShortcut", "Documents") }, + { Qt::Key_Excel, QT_TRANSLATE_NOOP("QShortcut", "Spreadsheet") }, + { Qt::Key_Explorer, QT_TRANSLATE_NOOP("QShortcut", "Browser") }, + { Qt::Key_Game, QT_TRANSLATE_NOOP("QShortcut", "Game") }, + { Qt::Key_Go, QT_TRANSLATE_NOOP("QShortcut", "Go") }, + { Qt::Key_iTouch, QT_TRANSLATE_NOOP("QShortcut", "iTouch") }, + { Qt::Key_LogOff, QT_TRANSLATE_NOOP("QShortcut", "Logoff") }, + { Qt::Key_Market, QT_TRANSLATE_NOOP("QShortcut", "Market") }, + { Qt::Key_Meeting, QT_TRANSLATE_NOOP("QShortcut", "Meeting") }, + { Qt::Key_MenuKB, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Menu") }, + { Qt::Key_MenuPB, QT_TRANSLATE_NOOP("QShortcut", "Menu PB") }, + { Qt::Key_MySites, QT_TRANSLATE_NOOP("QShortcut", "My Sites") }, + { Qt::Key_News, QT_TRANSLATE_NOOP("QShortcut", "News") }, + { Qt::Key_OfficeHome, QT_TRANSLATE_NOOP("QShortcut", "Home Office") }, + { Qt::Key_Option, QT_TRANSLATE_NOOP("QShortcut", "Option") }, + { Qt::Key_Paste, QT_TRANSLATE_NOOP("QShortcut", "Paste") }, + { Qt::Key_Phone, QT_TRANSLATE_NOOP("QShortcut", "Phone") }, + { Qt::Key_Reply, QT_TRANSLATE_NOOP("QShortcut", "Reply") }, + { Qt::Key_Reload, QT_TRANSLATE_NOOP("QShortcut", "Reload") }, + { Qt::Key_RotateWindows, QT_TRANSLATE_NOOP("QShortcut", "Rotate Windows") }, + { Qt::Key_RotationPB, QT_TRANSLATE_NOOP("QShortcut", "Rotation PB") }, + { Qt::Key_RotationKB, QT_TRANSLATE_NOOP("QShortcut", "Rotation KB") }, + { Qt::Key_Save, QT_TRANSLATE_NOOP("QShortcut", "Save") }, + { Qt::Key_Send, QT_TRANSLATE_NOOP("QShortcut", "Send") }, + { Qt::Key_Spell, QT_TRANSLATE_NOOP("QShortcut", "Spellchecker") }, + { Qt::Key_SplitScreen, QT_TRANSLATE_NOOP("QShortcut", "Split Screen") }, + { Qt::Key_Support, QT_TRANSLATE_NOOP("QShortcut", "Support") }, + { Qt::Key_TaskPane, QT_TRANSLATE_NOOP("QShortcut", "Task Panel") }, + { Qt::Key_Terminal, QT_TRANSLATE_NOOP("QShortcut", "Terminal") }, + { Qt::Key_Tools, QT_TRANSLATE_NOOP("QShortcut", "Tools") }, + { Qt::Key_Travel, QT_TRANSLATE_NOOP("QShortcut", "Travel") }, + { Qt::Key_Video, QT_TRANSLATE_NOOP("QShortcut", "Video") }, + { Qt::Key_Word, QT_TRANSLATE_NOOP("QShortcut", "Word Processor") }, + { Qt::Key_Xfer, QT_TRANSLATE_NOOP("QShortcut", "XFer") }, + { Qt::Key_ZoomIn, QT_TRANSLATE_NOOP("QShortcut", "Zoom In") }, + { Qt::Key_ZoomOut, QT_TRANSLATE_NOOP("QShortcut", "Zoom Out") }, + { Qt::Key_Away, QT_TRANSLATE_NOOP("QShortcut", "Away") }, + { Qt::Key_Messenger, QT_TRANSLATE_NOOP("QShortcut", "Messenger") }, + { Qt::Key_WebCam, QT_TRANSLATE_NOOP("QShortcut", "WebCam") }, + { Qt::Key_MailForward, QT_TRANSLATE_NOOP("QShortcut", "Mail Forward") }, + { Qt::Key_Pictures, QT_TRANSLATE_NOOP("QShortcut", "Pictures") }, + { Qt::Key_Music, QT_TRANSLATE_NOOP("QShortcut", "Music") }, + { Qt::Key_Battery, QT_TRANSLATE_NOOP("QShortcut", "Battery") }, + { Qt::Key_Bluetooth, QT_TRANSLATE_NOOP("QShortcut", "Bluetooth") }, + { Qt::Key_WLAN, QT_TRANSLATE_NOOP("QShortcut", "Wireless") }, + { Qt::Key_UWB, QT_TRANSLATE_NOOP("QShortcut", "Ultra Wide Band") }, + { Qt::Key_AudioForward, QT_TRANSLATE_NOOP("QShortcut", "Audio Forward") }, + { Qt::Key_AudioRepeat, QT_TRANSLATE_NOOP("QShortcut", "Audio Repeat") }, + { Qt::Key_AudioRandomPlay, QT_TRANSLATE_NOOP("QShortcut", "Audio Random Play") }, + { Qt::Key_Subtitle, QT_TRANSLATE_NOOP("QShortcut", "Subtitle") }, + { Qt::Key_AudioCycleTrack, QT_TRANSLATE_NOOP("QShortcut", "Audio Cycle Track") }, + { Qt::Key_Time, QT_TRANSLATE_NOOP("QShortcut", "Time") }, + { Qt::Key_Select, QT_TRANSLATE_NOOP("QShortcut", "Select") }, + { Qt::Key_View, QT_TRANSLATE_NOOP("QShortcut", "View") }, + { Qt::Key_TopMenu, QT_TRANSLATE_NOOP("QShortcut", "Top Menu") }, + { Qt::Key_Suspend, QT_TRANSLATE_NOOP("QShortcut", "Suspend") }, + { Qt::Key_Hibernate, QT_TRANSLATE_NOOP("QShortcut", "Hibernate") }, + + // -------------------------------------------------------------- + // More consistent namings + { Qt::Key_Print, QT_TRANSLATE_NOOP("QShortcut", "Print Screen") }, + { Qt::Key_PageUp, QT_TRANSLATE_NOOP("QShortcut", "Page Up") }, + { Qt::Key_PageDown, QT_TRANSLATE_NOOP("QShortcut", "Page Down") }, + { Qt::Key_CapsLock, QT_TRANSLATE_NOOP("QShortcut", "Caps Lock") }, + { Qt::Key_NumLock, QT_TRANSLATE_NOOP("QShortcut", "Num Lock") }, + { Qt::Key_NumLock, QT_TRANSLATE_NOOP("QShortcut", "Number Lock") }, + { Qt::Key_ScrollLock, QT_TRANSLATE_NOOP("QShortcut", "Scroll Lock") }, + { Qt::Key_Insert, QT_TRANSLATE_NOOP("QShortcut", "Insert") }, + { Qt::Key_Delete, QT_TRANSLATE_NOOP("QShortcut", "Delete") }, + { Qt::Key_Escape, QT_TRANSLATE_NOOP("QShortcut", "Escape") }, + { Qt::Key_SysReq, QT_TRANSLATE_NOOP("QShortcut", "System Request") }, + + // -------------------------------------------------------------- + // Keypad navigation keys + { Qt::Key_Select, QT_TRANSLATE_NOOP("QShortcut", "Select") }, + { Qt::Key_Yes, QT_TRANSLATE_NOOP("QShortcut", "Yes") }, + { Qt::Key_No, QT_TRANSLATE_NOOP("QShortcut", "No") }, + + // -------------------------------------------------------------- + // Device keys + { Qt::Key_Context1, QT_TRANSLATE_NOOP("QShortcut", "Context1") }, + { Qt::Key_Context2, QT_TRANSLATE_NOOP("QShortcut", "Context2") }, + { Qt::Key_Context3, QT_TRANSLATE_NOOP("QShortcut", "Context3") }, + { Qt::Key_Context4, QT_TRANSLATE_NOOP("QShortcut", "Context4") }, + //: Button to start a call (note: a separate button is used to end the call) + { Qt::Key_Call, QT_TRANSLATE_NOOP("QShortcut", "Call") }, + //: Button to end a call (note: a separate button is used to start the call) + { Qt::Key_Hangup, QT_TRANSLATE_NOOP("QShortcut", "Hangup") }, + //: Button that will hang up if we're in call, or make a call if we're not. + { Qt::Key_ToggleCallHangup, QT_TRANSLATE_NOOP("QShortcut", "Toggle Call/Hangup") }, + { Qt::Key_Flip, QT_TRANSLATE_NOOP("QShortcut", "Flip") }, + //: Button to trigger voice dialing + { Qt::Key_VoiceDial, QT_TRANSLATE_NOOP("QShortcut", "Voice Dial") }, + //: Button to redial the last number called + { Qt::Key_LastNumberRedial, QT_TRANSLATE_NOOP("QShortcut", "Last Number Redial") }, + //: Button to trigger the camera shutter (take a picture) + { Qt::Key_Camera, QT_TRANSLATE_NOOP("QShortcut", "Camera Shutter") }, + //: Button to focus the camera + { Qt::Key_CameraFocus, QT_TRANSLATE_NOOP("QShortcut", "Camera Focus") }, + + // -------------------------------------------------------------- + // Japanese keyboard support + { Qt::Key_Kanji, QT_TRANSLATE_NOOP("QShortcut", "Kanji") }, + { Qt::Key_Muhenkan, QT_TRANSLATE_NOOP("QShortcut", "Muhenkan") }, + { Qt::Key_Henkan, QT_TRANSLATE_NOOP("QShortcut", "Henkan") }, + { Qt::Key_Romaji, QT_TRANSLATE_NOOP("QShortcut", "Romaji") }, + { Qt::Key_Hiragana, QT_TRANSLATE_NOOP("QShortcut", "Hiragana") }, + { Qt::Key_Katakana, QT_TRANSLATE_NOOP("QShortcut", "Katakana") }, + { Qt::Key_Hiragana_Katakana,QT_TRANSLATE_NOOP("QShortcut", "Hiragana Katakana") }, + { Qt::Key_Zenkaku, QT_TRANSLATE_NOOP("QShortcut", "Zenkaku") }, + { Qt::Key_Hankaku, QT_TRANSLATE_NOOP("QShortcut", "Hankaku") }, + { Qt::Key_Zenkaku_Hankaku, QT_TRANSLATE_NOOP("QShortcut", "Zenkaku Hankaku") }, + { Qt::Key_Touroku, QT_TRANSLATE_NOOP("QShortcut", "Touroku") }, + { Qt::Key_Massyo, QT_TRANSLATE_NOOP("QShortcut", "Massyo") }, + { Qt::Key_Kana_Lock, QT_TRANSLATE_NOOP("QShortcut", "Kana Lock") }, + { Qt::Key_Kana_Shift, QT_TRANSLATE_NOOP("QShortcut", "Kana Shift") }, + { Qt::Key_Eisu_Shift, QT_TRANSLATE_NOOP("QShortcut", "Eisu Shift") }, + { Qt::Key_Eisu_toggle, QT_TRANSLATE_NOOP("QShortcut", "Eisu toggle") }, + { Qt::Key_Codeinput, QT_TRANSLATE_NOOP("QShortcut", "Code input") }, + { Qt::Key_MultipleCandidate,QT_TRANSLATE_NOOP("QShortcut", "Multiple Candidate") }, + { Qt::Key_PreviousCandidate,QT_TRANSLATE_NOOP("QShortcut", "Previous Candidate") }, + + // -------------------------------------------------------------- + // Korean keyboard support + { Qt::Key_Hangul, QT_TRANSLATE_NOOP("QShortcut", "Hangul") }, + { Qt::Key_Hangul_Start, QT_TRANSLATE_NOOP("QShortcut", "Hangul Start") }, + { Qt::Key_Hangul_End, QT_TRANSLATE_NOOP("QShortcut", "Hangul End") }, + { Qt::Key_Hangul_Hanja, QT_TRANSLATE_NOOP("QShortcut", "Hangul Hanja") }, + { Qt::Key_Hangul_Jamo, QT_TRANSLATE_NOOP("QShortcut", "Hangul Jamo") }, + { Qt::Key_Hangul_Romaja, QT_TRANSLATE_NOOP("QShortcut", "Hangul Romaja") }, + { Qt::Key_Hangul_Jeonja, QT_TRANSLATE_NOOP("QShortcut", "Hangul Jeonja") }, + { Qt::Key_Hangul_Banja, QT_TRANSLATE_NOOP("QShortcut", "Hangul Banja") }, + { Qt::Key_Hangul_PreHanja, QT_TRANSLATE_NOOP("QShortcut", "Hangul PreHanja") }, + { Qt::Key_Hangul_PostHanja,QT_TRANSLATE_NOOP("QShortcut", "Hangul PostHanja") }, + { Qt::Key_Hangul_Special, QT_TRANSLATE_NOOP("QShortcut", "Hangul Special") }, + + { 0, 0 } +}; + +//Table of key bindings. It must be sorted on key sequence. +//A priority of 1 indicates that this is the primary key binding when multiple are defined. + +const QKeyBinding QKeySequencePrivate::keyBindings[] = { +// StandardKey Priority Key Sequence Platforms + {QKeySequence::Back, 0, Qt::Key_Backspace, QApplicationPrivate::KB_Win}, + {QKeySequence::InsertParagraphSeparator,0, Qt::Key_Return, QApplicationPrivate::KB_All}, + {QKeySequence::InsertParagraphSeparator,0, Qt::Key_Enter, QApplicationPrivate::KB_All}, + {QKeySequence::Delete, 1, Qt::Key_Delete, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToStartOfLine, 0, Qt::Key_Home, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::MoveToStartOfDocument, 0, Qt::Key_Home, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToEndOfLine, 0, Qt::Key_End, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::MoveToEndOfDocument, 0, Qt::Key_End, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToPreviousChar, 0, Qt::Key_Left, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToPreviousLine, 0, Qt::Key_Up, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToNextChar, 0, Qt::Key_Right, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToNextLine, 0, Qt::Key_Down, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToPreviousPage, 1, Qt::Key_PageUp, QApplicationPrivate::KB_All}, + {QKeySequence::MoveToNextPage, 1, Qt::Key_PageDown, QApplicationPrivate::KB_All}, + {QKeySequence::HelpContents, 0, Qt::Key_F1, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::HelpContents, 0, Qt::Key_F2, QApplicationPrivate::KB_S60}, + {QKeySequence::FindNext, 0, Qt::Key_F3, QApplicationPrivate::KB_X11}, + {QKeySequence::FindNext, 1, Qt::Key_F3, QApplicationPrivate::KB_Win}, + {QKeySequence::Refresh, 0, Qt::Key_F5, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::Undo, 0, Qt::Key_F14, QApplicationPrivate::KB_X11}, //Undo on sun keyboards + {QKeySequence::Copy, 0, Qt::Key_F16, QApplicationPrivate::KB_X11}, //Copy on sun keyboards + {QKeySequence::Paste, 0, Qt::Key_F18, QApplicationPrivate::KB_X11}, //Paste on sun keyboards + {QKeySequence::Cut, 0, Qt::Key_F20, QApplicationPrivate::KB_X11}, //Cut on sun keyboards + {QKeySequence::PreviousChild, 0, Qt::Key_Back, QApplicationPrivate::KB_All}, + {QKeySequence::NextChild, 0, Qt::Key_Forward, QApplicationPrivate::KB_All}, + {QKeySequence::Forward, 0, Qt::SHIFT | Qt::Key_Backspace, QApplicationPrivate::KB_Win}, + {QKeySequence::Delete, 0, Qt::SHIFT | Qt::Key_Backspace, QApplicationPrivate::KB_S60}, + {QKeySequence::InsertLineSeparator, 0, Qt::SHIFT | Qt::Key_Return, QApplicationPrivate::KB_All}, + {QKeySequence::InsertLineSeparator, 0, Qt::SHIFT | Qt::Key_Enter, QApplicationPrivate::KB_All}, + {QKeySequence::Paste, 0, Qt::SHIFT | Qt::Key_Insert, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::Cut, 0, Qt::SHIFT | Qt::Key_Delete, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, //## Check if this should work on mac + {QKeySequence::SelectStartOfLine, 0, Qt::SHIFT | Qt::Key_Home, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectStartOfDocument, 0, Qt::SHIFT | Qt::Key_Home, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectEndOfLine, 0, Qt::SHIFT | Qt::Key_End, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectEndOfDocument, 0, Qt::SHIFT | Qt::Key_End, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectPreviousChar, 0, Qt::SHIFT | Qt::Key_Left, QApplicationPrivate::KB_All}, + {QKeySequence::SelectPreviousLine, 0, Qt::SHIFT | Qt::Key_Up, QApplicationPrivate::KB_All}, + {QKeySequence::SelectNextChar, 0, Qt::SHIFT | Qt::Key_Right, QApplicationPrivate::KB_All}, + {QKeySequence::SelectNextLine, 0, Qt::SHIFT | Qt::Key_Down, QApplicationPrivate::KB_All}, + {QKeySequence::SelectPreviousPage, 0, Qt::SHIFT | Qt::Key_PageUp, QApplicationPrivate::KB_All}, + {QKeySequence::SelectNextPage, 0, Qt::SHIFT | Qt::Key_PageDown, QApplicationPrivate::KB_All}, + {QKeySequence::WhatsThis, 1, Qt::SHIFT | Qt::Key_F1, QApplicationPrivate::KB_All}, + {QKeySequence::FindPrevious, 0, Qt::SHIFT | Qt::Key_F3, QApplicationPrivate::KB_X11}, + {QKeySequence::FindPrevious, 1, Qt::SHIFT | Qt::Key_F3, QApplicationPrivate::KB_Win}, + {QKeySequence::ZoomIn, 1, Qt::CTRL | Qt::Key_Plus, QApplicationPrivate::KB_All}, + {QKeySequence::NextChild, 0, Qt::CTRL | Qt::Key_Comma, QApplicationPrivate::KB_KDE}, + {QKeySequence::Preferences, 0, Qt::CTRL | Qt::Key_Comma, QApplicationPrivate::KB_Mac}, + {QKeySequence::ZoomOut, 1, Qt::CTRL | Qt::Key_Minus, QApplicationPrivate::KB_All}, + {QKeySequence::PreviousChild, 0, Qt::CTRL | Qt::Key_Period, QApplicationPrivate::KB_KDE}, + {QKeySequence::HelpContents, 1, Qt::CTRL | Qt::Key_Question, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectAll, 1, Qt::CTRL | Qt::Key_A, QApplicationPrivate::KB_All}, + {QKeySequence::Bold, 1, Qt::CTRL | Qt::Key_B, QApplicationPrivate::KB_All}, + {QKeySequence::Copy, 1, Qt::CTRL | Qt::Key_C, QApplicationPrivate::KB_All}, + {QKeySequence::Delete, 0, Qt::CTRL | Qt::Key_D, QApplicationPrivate::KB_X11}, //emacs (line edit only) + {QKeySequence::Find, 0, Qt::CTRL | Qt::Key_F, QApplicationPrivate::KB_All}, + {QKeySequence::FindNext, 1, Qt::CTRL | Qt::Key_G, QApplicationPrivate::KB_Gnome | QApplicationPrivate::KB_Mac}, + {QKeySequence::FindNext, 0, Qt::CTRL | Qt::Key_G, QApplicationPrivate::KB_Win}, + {QKeySequence::Replace, 0, Qt::CTRL | Qt::Key_H, QApplicationPrivate::KB_Win}, + {QKeySequence::Replace, 0, Qt::CTRL | Qt::Key_H, QApplicationPrivate::KB_Gnome}, + {QKeySequence::Italic, 0, Qt::CTRL | Qt::Key_I, QApplicationPrivate::KB_All}, + {QKeySequence::DeleteEndOfLine, 0, Qt::CTRL | Qt::Key_K, QApplicationPrivate::KB_X11}, //emacs (line edit only) + {QKeySequence::New, 1, Qt::CTRL | Qt::Key_N, QApplicationPrivate::KB_All}, + {QKeySequence::Open, 1, Qt::CTRL | Qt::Key_O, QApplicationPrivate::KB_All}, + {QKeySequence::Print, 1, Qt::CTRL | Qt::Key_P, QApplicationPrivate::KB_All}, + {QKeySequence::Quit, 0, Qt::CTRL | Qt::Key_Q, QApplicationPrivate::KB_Gnome | QApplicationPrivate::KB_KDE | QApplicationPrivate::KB_Mac}, + {QKeySequence::Refresh, 1, Qt::CTRL | Qt::Key_R, QApplicationPrivate::KB_Gnome | QApplicationPrivate::KB_Mac}, + {QKeySequence::Replace, 0, Qt::CTRL | Qt::Key_R, QApplicationPrivate::KB_KDE}, + {QKeySequence::Save, 1, Qt::CTRL | Qt::Key_S, QApplicationPrivate::KB_All}, + {QKeySequence::AddTab, 0, Qt::CTRL | Qt::Key_T, QApplicationPrivate::KB_All}, + {QKeySequence::Underline, 1, Qt::CTRL | Qt::Key_U, QApplicationPrivate::KB_All}, + {QKeySequence::Paste, 1, Qt::CTRL | Qt::Key_V, QApplicationPrivate::KB_All}, + {QKeySequence::Close, 0, Qt::CTRL | Qt::Key_W, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::Close, 1, Qt::CTRL | Qt::Key_W, QApplicationPrivate::KB_Mac}, + {QKeySequence::Cut, 1, Qt::CTRL | Qt::Key_X, QApplicationPrivate::KB_All}, + {QKeySequence::Redo, 1, Qt::CTRL | Qt::Key_Y, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_S60}, + {QKeySequence::Undo, 1, Qt::CTRL | Qt::Key_Z, QApplicationPrivate::KB_All}, + {QKeySequence::Back, 1, Qt::CTRL | Qt::Key_BracketLeft, QApplicationPrivate::KB_Mac}, + {QKeySequence::Forward, 1, Qt::CTRL | Qt::Key_BracketRight, QApplicationPrivate::KB_Mac}, + {QKeySequence::PreviousChild, 1, Qt::CTRL | Qt::Key_BraceLeft, QApplicationPrivate::KB_Mac}, + {QKeySequence::NextChild, 1, Qt::CTRL | Qt::Key_BraceRight, QApplicationPrivate::KB_Mac}, + {QKeySequence::NextChild, 1, Qt::CTRL | Qt::Key_Tab, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::NextChild, 0, Qt::CTRL | Qt::Key_Tab, QApplicationPrivate::KB_Mac}, //different priority from above + {QKeySequence::DeleteStartOfWord, 0, Qt::CTRL | Qt::Key_Backspace, QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_Win}, + {QKeySequence::Copy, 0, Qt::CTRL | Qt::Key_Insert, QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_Win}, + {QKeySequence::DeleteEndOfWord, 0, Qt::CTRL | Qt::Key_Delete, QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_Win}, + {QKeySequence::MoveToStartOfDocument, 0, Qt::CTRL | Qt::Key_Home, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::MoveToEndOfDocument, 0, Qt::CTRL | Qt::Key_End, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::Back, 0, Qt::CTRL | Qt::Key_Left, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToPreviousWord, 0, Qt::CTRL | Qt::Key_Left, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::MoveToStartOfLine, 0, Qt::CTRL | Qt::Key_Left, QApplicationPrivate::KB_Mac }, + {QKeySequence::MoveToStartOfDocument, 1, Qt::CTRL | Qt::Key_Up, QApplicationPrivate::KB_Mac}, + {QKeySequence::Forward, 0, Qt::CTRL | Qt::Key_Right, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToEndOfLine, 0, Qt::CTRL | Qt::Key_Right, QApplicationPrivate::KB_Mac }, + {QKeySequence::MoveToNextWord, 0, Qt::CTRL | Qt::Key_Right, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::MoveToEndOfDocument, 1, Qt::CTRL | Qt::Key_Down, QApplicationPrivate::KB_Mac}, + {QKeySequence::Close, 1, Qt::CTRL | Qt::Key_F4, QApplicationPrivate::KB_Win}, + {QKeySequence::Close, 0, Qt::CTRL | Qt::Key_F4, QApplicationPrivate::KB_Mac}, + {QKeySequence::NextChild, 0, Qt::CTRL | Qt::Key_F6, QApplicationPrivate::KB_Win}, + {QKeySequence::FindPrevious, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_G, QApplicationPrivate::KB_Gnome | QApplicationPrivate::KB_Mac}, + {QKeySequence::FindPrevious, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_G, QApplicationPrivate::KB_Win}, + {QKeySequence::AddTab, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_N, QApplicationPrivate::KB_KDE}, + {QKeySequence::SaveAs, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_S, QApplicationPrivate::KB_Gnome | QApplicationPrivate::KB_Mac}, + {QKeySequence::Redo, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Z, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::Redo, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Z, QApplicationPrivate::KB_Mac}, + {QKeySequence::PreviousChild, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_Backtab, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::PreviousChild, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Backtab, QApplicationPrivate::KB_Mac },//different priority from above + {QKeySequence::Paste, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Insert, QApplicationPrivate::KB_X11}, + {QKeySequence::SelectStartOfDocument, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Home, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectEndOfDocument, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_End, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectPreviousWord, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Left, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectStartOfLine, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_Left, QApplicationPrivate::KB_Mac }, + {QKeySequence::SelectStartOfDocument, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_Up, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectNextWord, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_Right, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11 | QApplicationPrivate::KB_S60}, + {QKeySequence::SelectEndOfLine, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_Right, QApplicationPrivate::KB_Mac }, + {QKeySequence::SelectEndOfDocument, 1, Qt::CTRL | Qt::SHIFT | Qt::Key_Down, QApplicationPrivate::KB_Mac}, + {QKeySequence::PreviousChild, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_F6, QApplicationPrivate::KB_Win}, + {QKeySequence::Undo, 0, Qt::ALT | Qt::Key_Backspace, QApplicationPrivate::KB_Win}, + {QKeySequence::DeleteStartOfWord, 0, Qt::ALT | Qt::Key_Backspace, QApplicationPrivate::KB_Mac}, + {QKeySequence::DeleteEndOfWord, 0, Qt::ALT | Qt::Key_Delete, QApplicationPrivate::KB_Mac}, + {QKeySequence::Back, 1, Qt::ALT | Qt::Key_Left, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::MoveToPreviousWord, 0, Qt::ALT | Qt::Key_Left, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToStartOfBlock, 0, Qt::ALT | Qt::Key_Up, QApplicationPrivate::KB_Mac}, //mac only + {QKeySequence::MoveToNextWord, 0, Qt::ALT | Qt::Key_Right, QApplicationPrivate::KB_Mac}, + {QKeySequence::Forward, 1, Qt::ALT | Qt::Key_Right, QApplicationPrivate::KB_Win | QApplicationPrivate::KB_X11}, + {QKeySequence::MoveToEndOfBlock, 0, Qt::ALT | Qt::Key_Down, QApplicationPrivate::KB_Mac}, //mac only + {QKeySequence::MoveToPreviousPage, 0, Qt::ALT | Qt::Key_PageUp, QApplicationPrivate::KB_Mac }, + {QKeySequence::MoveToNextPage, 0, Qt::ALT | Qt::Key_PageDown, QApplicationPrivate::KB_Mac }, + {QKeySequence::Redo, 0, Qt::ALT | Qt::SHIFT | Qt::Key_Backspace,QApplicationPrivate::KB_Win}, + {QKeySequence::SelectPreviousWord, 0, Qt::ALT | Qt::SHIFT | Qt::Key_Left, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectStartOfBlock, 0, Qt::ALT | Qt::SHIFT | Qt::Key_Up, QApplicationPrivate::KB_Mac}, //mac only + {QKeySequence::SelectNextWord, 0, Qt::ALT | Qt::SHIFT | Qt::Key_Right, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectEndOfBlock, 0, Qt::ALT | Qt::SHIFT | Qt::Key_Down, QApplicationPrivate::KB_Mac}, //mac only + {QKeySequence::MoveToStartOfBlock, 0, Qt::META | Qt::Key_A, QApplicationPrivate::KB_Mac}, + {QKeySequence::Delete, 0, Qt::META | Qt::Key_D, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToEndOfBlock, 0, Qt::META | Qt::Key_E, QApplicationPrivate::KB_Mac}, + {QKeySequence::InsertLineSeparator, 0, Qt::META | Qt::Key_Return, QApplicationPrivate::KB_Mac}, + {QKeySequence::InsertLineSeparator, 0, Qt::META | Qt::Key_Enter, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToStartOfLine, 0, Qt::META | Qt::Key_Left, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToPreviousPage, 0, Qt::META | Qt::Key_Up, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToEndOfLine, 0, Qt::META | Qt::Key_Right, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToNextPage, 0, Qt::META | Qt::Key_Down, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToPreviousPage, 0, Qt::META | Qt::Key_PageUp, QApplicationPrivate::KB_Mac}, + {QKeySequence::MoveToNextPage, 0, Qt::META | Qt::Key_PageDown, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectStartOfBlock, 0, Qt::META | Qt::SHIFT | Qt::Key_A, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectEndOfBlock, 0, Qt::META | Qt::SHIFT | Qt::Key_E, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectStartOfLine, 0, Qt::META | Qt::SHIFT | Qt::Key_Left, QApplicationPrivate::KB_Mac}, + {QKeySequence::SelectEndOfLine, 0, Qt::META | Qt::SHIFT | Qt::Key_Right, QApplicationPrivate::KB_Mac} +}; + +const uint QKeySequencePrivate::numberOfKeyBindings = sizeof(QKeySequencePrivate::keyBindings)/(sizeof(QKeyBinding)); + + +/*! + \enum QKeySequence::StandardKey + \since 4.2 + + This enum represent standard key bindings. They can be used to + assign platform dependent keyboard shortcuts to a QAction. + + Note that the key bindings are platform dependent. The currently + bound shortcuts can be queried using keyBindings(). + + \value AddTab Add new tab. + \value Back Navigate back. + \value Bold Bold text. + \value Close Close document/tab. + \value Copy Copy. + \value Cut Cut. + \value Delete Delete. + \value DeleteEndOfLine Delete end of line. + \value DeleteEndOfWord Delete word from the end of the cursor. + \value DeleteStartOfWord Delete the beginning of a word up to the cursor. + \value Find Find in document. + \value FindNext Find next result. + \value FindPrevious Find previous result. + \value Forward Navigate forward. + \value HelpContents Open help contents. + \value InsertLineSeparator Insert a new line. + \value InsertParagraphSeparator Insert a new paragraph. + \value Italic Italic text. + \value MoveToEndOfBlock Move cursor to end of block. This shortcut is only used on the OS X. + \value MoveToEndOfDocument Move cursor to end of document. + \value MoveToEndOfLine Move cursor to end of line. + \value MoveToNextChar Move cursor to next character. + \value MoveToNextLine Move cursor to next line. + \value MoveToNextPage Move cursor to next page. + \value MoveToNextWord Move cursor to next word. + \value MoveToPreviousChar Move cursor to previous character. + \value MoveToPreviousLine Move cursor to previous line. + \value MoveToPreviousPage Move cursor to previous page. + \value MoveToPreviousWord Move cursor to previous word. + \value MoveToStartOfBlock Move cursor to start of a block. This shortcut is only used on OS X. + \value MoveToStartOfDocument Move cursor to start of document. + \value MoveToStartOfLine Move cursor to start of line. + \value New Create new document. + \value NextChild Navigate to next tab or child window. + \value Open Open document. + \value Paste Paste. + \value Preferences Open the preferences dialog. + \value PreviousChild Navigate to previous tab or child window. + \value Print Print document. + \value Quit Quit the application. + \value Redo Redo. + \value Refresh Refresh or reload current document. + \value Replace Find and replace. + \value SaveAs Save document after prompting the user for a file name. + \value Save Save document. + \value SelectAll Select all text. + \value SelectEndOfBlock Extend selection to the end of a text block. This shortcut is only used on OS X. + \value SelectEndOfDocument Extend selection to end of document. + \value SelectEndOfLine Extend selection to end of line. + \value SelectNextChar Extend selection to next character. + \value SelectNextLine Extend selection to next line. + \value SelectNextPage Extend selection to next page. + \value SelectNextWord Extend selection to next word. + \value SelectPreviousChar Extend selection to previous character. + \value SelectPreviousLine Extend selection to previous line. + \value SelectPreviousPage Extend selection to previous page. + \value SelectPreviousWord Extend selection to previous word. + \value SelectStartOfBlock Extend selection to the start of a text block. This shortcut is only used on OS X. + \value SelectStartOfDocument Extend selection to start of document. + \value SelectStartOfLine Extend selection to start of line. + \value Underline Underline text. + \value Undo Undo. + \value UnknownKey Unbound key. + \value WhatsThis Activate whats this. + \value ZoomIn Zoom in. + \value ZoomOut Zoom out. +*/ + +/*! + \since 4.2 + + Constructs a QKeySequence object for the given \a key. + The result will depend on the currently running platform. + + The resulting object will be based on the first element in the + list of key bindings for the \a key. +*/ +QKeySequence::QKeySequence(StandardKey key) +{ + const QList <QKeySequence> bindings = keyBindings(key); + //pick only the first/primary shortcut from current bindings + if (bindings.size() > 0) { + d = bindings.first().d; + d->ref.ref(); + } + else + d = new QKeySequencePrivate(); +} + + +/*! + Constructs an empty key sequence. +*/ +QKeySequence::QKeySequence() +{ + static QKeySequencePrivate shared_empty; + d = &shared_empty; + d->ref.ref(); +} + +/*! + Creates a key sequence from the \a key string. For example + "Ctrl+O" gives CTRL+'O'. The strings "Ctrl", + "Shift", "Alt" and "Meta" are recognized, as well as their + translated equivalents in the "QShortcut" context (using + QObject::tr()). + + Up to four key codes may be entered by separating them with + commas, e.g. "Alt+X,Ctrl+S,Q". + + \a key should be in NativeText format. + + This constructor is typically used with \link QObject::tr() tr + \endlink(), so that shortcut keys can be replaced in + translations: + + \snippet doc/src/snippets/code/src_gui_kernel_qkeysequence.cpp 2 + + Note the "File|Open" translator comment. It is by no means + necessary, but it provides some context for the human translator. +*/ +QKeySequence::QKeySequence(const QString &key) +{ + d = new QKeySequencePrivate(); + assign(key); +} + +/*! + \since 4.x + Creates a key sequence from the \a key string based on \a format. +*/ +QKeySequence::QKeySequence(const QString &key, QKeySequence::SequenceFormat format) +{ + d = new QKeySequencePrivate(); + assign(key, format); +} + +/*! + Constructs a key sequence with up to 4 keys \a k1, \a k2, + \a k3 and \a k4. + + The key codes are listed in Qt::Key and can be combined with + modifiers (see Qt::Modifier) such as Qt::SHIFT, Qt::CTRL, + Qt::ALT, or Qt::META. +*/ +QKeySequence::QKeySequence(int k1, int k2, int k3, int k4) +{ + d = new QKeySequencePrivate(); + d->key[0] = k1; + d->key[1] = k2; + d->key[2] = k3; + d->key[3] = k4; +} + +/*! + Copy constructor. Makes a copy of \a keysequence. + */ +QKeySequence::QKeySequence(const QKeySequence& keysequence) + : d(keysequence.d) +{ + d->ref.ref(); +} + +#ifdef Q_WS_MAC +static inline int maybeSwapShortcut(int shortcut) +{ + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + uint oldshortcut = shortcut; + shortcut &= ~(Qt::CTRL | Qt::META); + if (oldshortcut & Qt::CTRL) + shortcut |= Qt::META; + if (oldshortcut & Qt::META) + shortcut |= Qt::CTRL; + } + return shortcut; +} +#endif + +/*! + \since 4.2 + + Returns a list of key bindings for the given \a key. + The result of calling this function will vary based on the target platform. + The first element of the list indicates the primary shortcut for the given platform. + If the result contains more than one result, these can + be considered alternative shortcuts on the same platform for the given \a key. +*/ +QList<QKeySequence> QKeySequence::keyBindings(StandardKey key) +{ + uint platform = QApplicationPrivate::currentPlatform(); + QList <QKeySequence> list; + for (uint i = 0; i < QKeySequencePrivate::numberOfKeyBindings ; ++i) { + QKeyBinding keyBinding = QKeySequencePrivate::keyBindings[i]; + if (keyBinding.standardKey == key && (keyBinding.platform & platform)) { + uint shortcut = +#ifdef Q_WS_MAC + maybeSwapShortcut(QKeySequencePrivate::keyBindings[i].shortcut); +#else + QKeySequencePrivate::keyBindings[i].shortcut; +#endif + if (keyBinding.priority > 0) + list.prepend(QKeySequence(shortcut)); + else + list.append(QKeySequence(shortcut)); + } + } + return list; +} + +/*! + Destroys the key sequence. + */ +QKeySequence::~QKeySequence() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + \internal + KeySequences should never be modified, but rather just created. + Internally though we do need to modify to keep pace in event + delivery. +*/ + +void QKeySequence::setKey(int key, int index) +{ + Q_ASSERT_X(index >= 0 && index < 4, "QKeySequence::setKey", "index out of range"); + qAtomicDetach(d); + d->key[index] = key; +} + +/*! + Returns the number of keys in the key sequence. + The maximum is 4. + */ +uint QKeySequence::count() const +{ + if (!d->key[0]) + return 0; + if (!d->key[1]) + return 1; + if (!d->key[2]) + return 2; + if (!d->key[3]) + return 3; + return 4; +} + + +/*! + Returns true if the key sequence is empty; otherwise returns + false. +*/ +bool QKeySequence::isEmpty() const +{ + return !d->key[0]; +} + + +/*! + Returns the shortcut key sequence for the mnemonic in \a text, + or an empty key sequence if no mnemonics are found. + + For example, mnemonic("E&xit") returns \c{Qt::ALT+Qt::Key_X}, + mnemonic("&Quit") returns \c{ALT+Key_Q}, and mnemonic("Quit") + returns an empty QKeySequence. + + We provide a \l{accelerators.html}{list of common mnemonics} + in English. At the time of writing, Microsoft and Open Group do + not appear to have issued equivalent recommendations for other + languages. + + \sa qt_set_sequence_auto_mnemonic() +*/ +QKeySequence QKeySequence::mnemonic(const QString &text) +{ + QKeySequence ret; + + if(qt_sequence_no_mnemonics) + return ret; + + bool found = false; + int p = 0; + while (p >= 0) { + p = text.indexOf(QLatin1Char('&'), p) + 1; + if (p <= 0 || p >= (int)text.length()) + break; + if (text.at(p) != QLatin1Char('&')) { + QChar c = text.at(p); + if (c.isPrint()) { + if (!found) { + c = c.toUpper(); + ret = QKeySequence(c.unicode() + Qt::ALT); +#ifdef QT_NO_DEBUG + return ret; +#else + found = true; + } else { + qWarning("QKeySequence::mnemonic: \"%s\" contains multiple occurrences of '&'", qPrintable(text)); +#endif + } + } + } + p++; + } + return ret; +} + +/*! + \fn int QKeySequence::assign(const QString &keys) + + Adds the given \a keys to the key sequence. \a keys may + contain up to four key codes, provided they are separated by a + comma; for example, "Alt+X,Ctrl+S,Z". The return value is the + number of key codes added. + \a keys should be in NativeText format. +*/ +int QKeySequence::assign(const QString &ks) +{ + return assign(ks, NativeText); +} + +/*! + \fn int QKeySequence::assign(const QString &keys, QKeySequence::SequenceFormat format) + \since 4.x + + Adds the given \a keys to the key sequence (based on \a format). + \a keys may contain up to four key codes, provided they are + separated by a comma; for example, "Alt+X,Ctrl+S,Z". The return + value is the number of key codes added. +*/ +int QKeySequence::assign(const QString &ks, QKeySequence::SequenceFormat format) +{ + QString keyseq = ks; + QString part; + int n = 0; + int p = 0, diff = 0; + + // Run through the whole string, but stop + // if we have 4 keys before the end. + while (keyseq.length() && n < 4) { + // We MUST use something to separate each sequence, and space + // does not cut it, since some of the key names have space + // in them.. (Let's hope no one translate with a comma in it:) + p = keyseq.indexOf(QLatin1Char(',')); + if (-1 != p) { + if (p == keyseq.count() - 1) { // Last comma 'Ctrl+,' + p = -1; + } else { + if (QLatin1Char(',') == keyseq.at(p+1)) // e.g. 'Ctrl+,, Shift+,,' + p++; + if (QLatin1Char(' ') == keyseq.at(p+1)) { // Space after comma + diff = 1; + p++; + } else { + diff = 0; + } + } + } + part = keyseq.left(-1 == p ? keyseq.length() : p - diff); + keyseq = keyseq.right(-1 == p ? 0 : keyseq.length() - (p + 1)); + d->key[n] = QKeySequencePrivate::decodeString(part, format); + ++n; + } + return n; +} + +struct QModifKeyName { + QModifKeyName() { } + QModifKeyName(int q, QChar n) : qt_key(q), name(n) { } + QModifKeyName(int q, const QString &n) : qt_key(q), name(n) { } + int qt_key; + QString name; +}; + +Q_GLOBAL_STATIC(QList<QModifKeyName>, globalModifs) +Q_GLOBAL_STATIC(QList<QModifKeyName>, globalPortableModifs) + +/*! + Constructs a single key from the string \a str. +*/ +int QKeySequence::decodeString(const QString &str) +{ + return QKeySequencePrivate::decodeString(str, NativeText); +} + +int QKeySequencePrivate::decodeString(const QString &str, QKeySequence::SequenceFormat format) +{ + int ret = 0; + QString accel = str.toLower(); + bool nativeText = (format == QKeySequence::NativeText); + + QList<QModifKeyName> *gmodifs; + if (nativeText) { + gmodifs = globalModifs(); + if (gmodifs->isEmpty()) { +#ifdef Q_WS_MAC + const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); + if (dontSwap) + *gmodifs << QModifKeyName(Qt::META, QChar(kCommandUnicode)); + else + *gmodifs << QModifKeyName(Qt::CTRL, QChar(kCommandUnicode)); + *gmodifs << QModifKeyName(Qt::ALT, QChar(kOptionUnicode)); + if (dontSwap) + *gmodifs << QModifKeyName(Qt::CTRL, QChar(kControlUnicode)); + else + *gmodifs << QModifKeyName(Qt::META, QChar(kControlUnicode)); + *gmodifs << QModifKeyName(Qt::SHIFT, QChar(kShiftUnicode)); +#endif + *gmodifs << QModifKeyName(Qt::CTRL, QLatin1String("ctrl+")) + << QModifKeyName(Qt::SHIFT, QLatin1String("shift+")) + << QModifKeyName(Qt::ALT, QLatin1String("alt+")) + << QModifKeyName(Qt::META, QLatin1String("meta+")); + } + } else { + gmodifs = globalPortableModifs(); + if (gmodifs->isEmpty()) { + *gmodifs << QModifKeyName(Qt::CTRL, QLatin1String("ctrl+")) + << QModifKeyName(Qt::SHIFT, QLatin1String("shift+")) + << QModifKeyName(Qt::ALT, QLatin1String("alt+")) + << QModifKeyName(Qt::META, QLatin1String("meta+")); + } + } + if (!gmodifs) return ret; + + + QList<QModifKeyName> modifs; + if (nativeText) { + modifs << QModifKeyName(Qt::CTRL, QShortcut::tr("Ctrl").toLower().append(QLatin1Char('+'))) + << QModifKeyName(Qt::SHIFT, QShortcut::tr("Shift").toLower().append(QLatin1Char('+'))) + << QModifKeyName(Qt::ALT, QShortcut::tr("Alt").toLower().append(QLatin1Char('+'))) + << QModifKeyName(Qt::META, QShortcut::tr("Meta").toLower().append(QLatin1Char('+'))); + } + modifs += *gmodifs; // Test non-translated ones last + + QString sl = accel; +#ifdef Q_WS_MAC + for (int i = 0; i < modifs.size(); ++i) { + const QModifKeyName &mkf = modifs.at(i); + if (sl.contains(mkf.name)) { + ret |= mkf.qt_key; + accel.remove(mkf.name); + sl = accel; + } + } +#else + int i = 0; + int lastI = 0; + while ((i = sl.indexOf(QLatin1Char('+'), i + 1)) != -1) { + const QString sub = sl.mid(lastI, i - lastI + 1); + // Just shortcut the check here if we only have one character. + // Rational: A modifier will contain the name AND +, so longer than 1, a length of 1 is just + // the remaining part of the shortcut (ei. The 'C' in "Ctrl+C"), so no need to check that. + if (sub.length() > 1) { + for (int j = 0; j < modifs.size(); ++j) { + const QModifKeyName &mkf = modifs.at(j); + if (sub == mkf.name) { + ret |= mkf.qt_key; + break; // Shortcut, since if we find an other it would/should just be a dup + } + } + } + lastI = i + 1; + } +#endif + + int p = accel.lastIndexOf(QLatin1Char('+'), str.length() - 2); // -2 so that Ctrl++ works + if(p > 0) + accel = accel.mid(p + 1); + + int fnum = 0; + if (accel.length() == 1) { +#ifdef Q_WS_MAC + int qtKey = qtkeyForMacSymbol(accel[0]); + if (qtKey != -1) { + ret |= qtKey; + } else +#endif + { + ret |= accel[0].toUpper().unicode(); + } + } else if (accel[0] == QLatin1Char('f') && (fnum = accel.mid(1).toInt()) && (fnum >= 1) && (fnum <= 35)) { + ret |= Qt::Key_F1 + fnum - 1; + } else { + // For NativeText, check the traslation table first, + // if we don't find anything then try it out with just the untranlated stuff. + // PortableText will only try the untranlated table. + bool found = false; + for (int tran = 0; tran < 2; ++tran) { + if (!nativeText) + ++tran; + for (int i = 0; keyname[i].name; ++i) { + QString keyName(tran == 0 + ? QShortcut::tr(keyname[i].name) + : QString::fromLatin1(keyname[i].name)); + if (accel == keyName.toLower()) { + ret |= keyname[i].key; + found = true; + break; + } + } + if (found) + break; + } + } + return ret; +} + +/*! + Creates a shortcut string for \a key. For example, + Qt::CTRL+Qt::Key_O gives "Ctrl+O". The strings, "Ctrl", "Shift", etc. are + translated (using QObject::tr()) in the "QShortcut" context. + */ +QString QKeySequence::encodeString(int key) +{ + return QKeySequencePrivate::encodeString(key, NativeText); +} + +static inline void addKey(QString &str, const QString &theKey, QKeySequence::SequenceFormat format) +{ + if (!str.isEmpty()) + str += (format == QKeySequence::NativeText) ? QShortcut::tr("+") + : QString::fromLatin1("+"); + str += theKey; +} + +QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat format) +{ + bool nativeText = (format == QKeySequence::NativeText); + QString s; +#if defined(Q_WS_MAC) + if (nativeText) { + // On Mac OS X the order (by default) is Meta, Alt, Shift, Control. + // If the AA_MacDontSwapCtrlAndMeta is enabled, then the order + // is Ctrl, Alt, Shift, Meta. The macSymbolForQtKey does this swap + // for us, which means that we have to adjust our order here. + // The upshot is a lot more infrastructure to keep the number of + // if tests down and the code relatively clean. + static const int ModifierOrder[] = { Qt::META, Qt::ALT, Qt::SHIFT, Qt::CTRL, 0 }; + static const int QtKeyOrder[] = { Qt::Key_Meta, Qt::Key_Alt, Qt::Key_Shift, Qt::Key_Control, 0 }; + static const int DontSwapModifierOrder[] = { Qt::CTRL, Qt::ALT, Qt::SHIFT, Qt::META, 0 }; + static const int DontSwapQtKeyOrder[] = { Qt::Key_Control, Qt::Key_Alt, Qt::Key_Shift, Qt::Key_Meta, 0 }; + const int *modifierOrder; + const int *qtkeyOrder; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + modifierOrder = DontSwapModifierOrder; + qtkeyOrder = DontSwapQtKeyOrder; + } else { + modifierOrder = ModifierOrder; + qtkeyOrder = QtKeyOrder; + } + + for (int i = 0; modifierOrder[i] != 0; ++i) { + if (key & modifierOrder[i]) + s += qt_macSymbolForQtKey(qtkeyOrder[i]); + } + } else +#endif + { + // On other systems the order is Meta, Control, Alt, Shift + if ((key & Qt::META) == Qt::META) + s = nativeText ? QShortcut::tr("Meta") : QString::fromLatin1("Meta"); + if ((key & Qt::CTRL) == Qt::CTRL) + addKey(s, nativeText ? QShortcut::tr("Ctrl") : QString::fromLatin1("Ctrl"), format); + if ((key & Qt::ALT) == Qt::ALT) + addKey(s, nativeText ? QShortcut::tr("Alt") : QString::fromLatin1("Alt"), format); + if ((key & Qt::SHIFT) == Qt::SHIFT) + addKey(s, nativeText ? QShortcut::tr("Shift") : QString::fromLatin1("Shift"), format); + } + + + key &= ~(Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier); + QString p; + + if (key && key < Qt::Key_Escape && key != Qt::Key_Space) { + if (key < 0x10000) { + p = QChar(key & 0xffff).toUpper(); + } else { + p = QChar((key-0x10000)/0x400+0xd800); + p += QChar((key-0x10000)%400+0xdc00); + } + } else if (key >= Qt::Key_F1 && key <= Qt::Key_F35) { + p = nativeText ? QShortcut::tr("F%1").arg(key - Qt::Key_F1 + 1) + : QString::fromLatin1("F%1").arg(key - Qt::Key_F1 + 1); + } else if (key) { + int i=0; +#if defined(Q_WS_MAC) + if (nativeText) { + QChar ch = qt_macSymbolForQtKey(key); + if (!ch.isNull()) + p = ch; + else + goto NonSymbol; + } else +#endif + { +#ifdef Q_WS_MAC +NonSymbol: +#endif + while (keyname[i].name) { + if (key == keyname[i].key) { + p = nativeText ? QShortcut::tr(keyname[i].name) + : QString::fromLatin1(keyname[i].name); + break; + } + ++i; + } + // If we can't find the actual translatable keyname, + // fall back on the unicode representation of it... + // Or else characters like Qt::Key_aring may not get displayed + // (Really depends on you locale) + if (!keyname[i].name) { + if (key < 0x10000) { + p = QChar(key & 0xffff).toUpper(); + } else { + p = QChar((key-0x10000)/0x400+0xd800); + p += QChar((key-0x10000)%400+0xdc00); + } + } + } + } + +#ifdef Q_WS_MAC + if (nativeText) + s += p; + else +#endif + addKey(s, p, format); + return s; +} +/*! + Matches the sequence with \a seq. Returns ExactMatch if + successful, PartialMatch if \a seq matches incompletely, + and NoMatch if the sequences have nothing in common. + Returns NoMatch if \a seq is shorter. +*/ +QKeySequence::SequenceMatch QKeySequence::matches(const QKeySequence &seq) const +{ + uint userN = count(), + seqN = seq.count(); + + if (userN > seqN) + return NoMatch; + + // If equal in length, we have a potential ExactMatch sequence, + // else we already know it can only be partial. + SequenceMatch match = (userN == seqN ? ExactMatch : PartialMatch); + + for (uint i = 0; i < userN; ++i) { + int userKey = (*this)[i], + sequenceKey = seq[i]; + if (userKey != sequenceKey) + return NoMatch; + } + return match; +} + + +/*! + \obsolete + + Use toString() instead. + + Returns the key sequence as a QString. This is equivalent to + calling toString(QKeySequence::NativeText). Note that the + result is not platform independent. +*/ +QKeySequence::operator QString() const +{ + return QKeySequence::toString(QKeySequence::NativeText); +} + +/*! + Returns the key sequence as a QVariant +*/ +QKeySequence::operator QVariant() const +{ + return QVariant(QVariant::KeySequence, this); +} + +/*! + \obsolete + For backward compatibility: returns the first keycode + as integer. If the key sequence is empty, 0 is returned. + */ +QKeySequence::operator int () const +{ + if (1 <= count()) + return d->key[0]; + return 0; +} + + +/*! + Returns a reference to the element at position \a index in the key + sequence. This can only be used to read an element. + */ +int QKeySequence::operator[](uint index) const +{ + Q_ASSERT_X(index < 4, "QKeySequence::operator[]", "index out of range"); + return d->key[index]; +} + + +/*! + Assignment operator. Assigns the \a other key sequence to this + object. + */ +QKeySequence &QKeySequence::operator=(const QKeySequence &other) +{ + qAtomicAssign(d, other.d); + return *this; +} + +/*! + \fn void QKeySequence::swap(QKeySequence &other) + \since 4.8 + + Swaps key sequence \a other with this key sequence. This operation is very + fast and never fails. +*/ + +/*! + \fn bool QKeySequence::operator!=(const QKeySequence &other) const + + Returns true if this key sequence is not equal to the \a other + key sequence; otherwise returns false. +*/ + + +/*! + Returns true if this key sequence is equal to the \a other + key sequence; otherwise returns false. + */ +bool QKeySequence::operator==(const QKeySequence &other) const +{ + return (d->key[0] == other.d->key[0] && + d->key[1] == other.d->key[1] && + d->key[2] == other.d->key[2] && + d->key[3] == other.d->key[3]); +} + + +/*! + Provides an arbitrary comparison of this key sequence and + \a other key sequence. All that is guaranteed is that the + operator returns false if both key sequences are equal and + that (ks1 \< ks2) == !( ks2 \< ks1) if the key sequences + are not equal. + + This function is useful in some circumstances, for example + if you want to use QKeySequence objects as keys in a QMap. + + \sa operator==() operator!=() operator>() operator<=() operator>=() +*/ +bool QKeySequence::operator< (const QKeySequence &other) const +{ + for (int i = 0; i < 4; ++i) + if (d->key[i] != other.d->key[i]) + return d->key[i] < other.d->key[i]; + return false; +} + +/*! + \fn bool QKeySequence::operator> (const QKeySequence &other) const + + Returns true if this key sequence is larger than the \a other key + sequence; otherwise returns false. + + \sa operator==() operator!=() operator<() operator<=() operator>=() +*/ + +/*! + \fn bool QKeySequence::operator<= (const QKeySequence &other) const + + Returns true if this key sequence is smaller or equal to the + \a other key sequence; otherwise returns false. + + \sa operator==() operator!=() operator<() operator>() operator>=() +*/ + +/*! + \fn bool QKeySequence::operator>= (const QKeySequence &other) const + + Returns true if this key sequence is larger or equal to the + \a other key sequence; otherwise returns false. + + \sa operator==() operator!=() operator<() operator>() operator<=() +*/ + +/*! + \internal +*/ +bool QKeySequence::isDetached() const +{ + return d->ref == 1; +} + +/*! + \since 4.1 + + Return a string representation of the key sequence, + based on \a format. + + For example, the value Qt::CTRL+Qt::Key_O results in "Ctrl+O". + If the key sequence has multiple key codes, each is separated + by commas in the string returned, such as "Alt+X, Ctrl+Y, Z". + The strings, "Ctrl", "Shift", etc. are translated using + QObject::tr() in the "QShortcut" context. + + If the key sequence has no keys, an empty string is returned. + + On Mac OS X, the string returned resembles the sequence that is + shown in the menu bar. + + \sa fromString() +*/ +QString QKeySequence::toString(SequenceFormat format) const +{ + QString finalString; + // A standard string, with no translation or anything like that. In some ways it will + // look like our latin case on Windows and X11 + int end = count(); + for (int i = 0; i < end; ++i) { + finalString += d->encodeString(d->key[i], format); + finalString += QLatin1String(", "); + } + finalString.truncate(finalString.length() - 2); + return finalString; +} + +/*! + \since 4.1 + + Return a QKeySequence from the string \a str based on \a format. + + \sa toString() +*/ +QKeySequence QKeySequence::fromString(const QString &str, SequenceFormat format) +{ + return QKeySequence(str, format); +} + +/***************************************************************************** + QKeySequence stream functions + *****************************************************************************/ +#if !defined(QT_NO_DATASTREAM) +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QKeySequence &sequence) + \relates QKeySequence + + Writes the key \a sequence to the \a stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator<<(QDataStream &s, const QKeySequence &keysequence) +{ + QList<quint32> list; + list << keysequence.d->key[0]; + + if (s.version() >= 5 && keysequence.count() > 1) { + list << keysequence.d->key[1]; + list << keysequence.d->key[2]; + list << keysequence.d->key[3]; + } + s << list; + return s; +} + + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QKeySequence &sequence) + \relates QKeySequence + + Reads a key sequence from the \a stream into the key \a sequence. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator>>(QDataStream &s, QKeySequence &keysequence) +{ + qAtomicDetach(keysequence.d); + QList<quint32> list; + s >> list; + for (int i = 0; i < 4; ++i) + keysequence.d->key[i] = list.value(i); + return s; +} + +#endif //QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QKeySequence &p) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QKeySequence(" << p.toString() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QKeySequence to QDebug"); + return dbg; + Q_UNUSED(p); +#endif +} +#endif + +#endif // QT_NO_SHORTCUT + + +/*! + \typedef QKeySequence::DataPtr + \internal +*/ + + /*! + \fn DataPtr &QKeySequence::data_ptr() + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qkeysequence.h b/src/gui/kernel/qkeysequence.h new file mode 100644 index 0000000000..9eabb89d29 --- /dev/null +++ b/src/gui/kernel/qkeysequence.h @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKEYSEQUENCE_H +#define QKEYSEQUENCE_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SHORTCUT + +/***************************************************************************** + QKeySequence stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +class QKeySequence; +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &in, const QKeySequence &ks); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &out, QKeySequence &ks); +#endif + +#ifdef qdoc +void qt_set_sequence_auto_mnemonic(bool b); +#endif + +class QVariant; +class QKeySequencePrivate; + +class Q_GUI_EXPORT QKeySequence +{ +public: + enum StandardKey { + UnknownKey, + HelpContents, + WhatsThis, + Open, + Close, + Save, + New, + Delete, + Cut, + Copy, + Paste, + Undo, + Redo, + Back, + Forward, + Refresh, + ZoomIn, + ZoomOut, + Print, + AddTab, + NextChild, + PreviousChild, + Find, + FindNext, + FindPrevious, + Replace, + SelectAll, + Bold, + Italic, + Underline, + MoveToNextChar, + MoveToPreviousChar, + MoveToNextWord, + MoveToPreviousWord, + MoveToNextLine, + MoveToPreviousLine, + MoveToNextPage, + MoveToPreviousPage, + MoveToStartOfLine, + MoveToEndOfLine, + MoveToStartOfBlock, + MoveToEndOfBlock, + MoveToStartOfDocument, + MoveToEndOfDocument, + SelectNextChar, + SelectPreviousChar, + SelectNextWord, + SelectPreviousWord, + SelectNextLine, + SelectPreviousLine, + SelectNextPage, + SelectPreviousPage, + SelectStartOfLine, + SelectEndOfLine, + SelectStartOfBlock, + SelectEndOfBlock, + SelectStartOfDocument, + SelectEndOfDocument, + DeleteStartOfWord, + DeleteEndOfWord, + DeleteEndOfLine, + InsertParagraphSeparator, + InsertLineSeparator, + SaveAs, + Preferences, + Quit + }; + + enum SequenceFormat { + NativeText, + PortableText + }; + + QKeySequence(); + QKeySequence(const QString &key); + QKeySequence(const QString &key, SequenceFormat format); + QKeySequence(int k1, int k2 = 0, int k3 = 0, int k4 = 0); + QKeySequence(const QKeySequence &ks); + QKeySequence(StandardKey key); + ~QKeySequence(); + + uint count() const; // ### Qt 5: return 'int' + bool isEmpty() const; + + enum SequenceMatch { + NoMatch, + PartialMatch, + ExactMatch +#ifdef QT3_SUPPORT + , Identical = ExactMatch +#endif + }; + + QString toString(SequenceFormat format = PortableText) const; + static QKeySequence fromString(const QString &str, SequenceFormat format = PortableText); + + SequenceMatch matches(const QKeySequence &seq) const; + static QKeySequence mnemonic(const QString &text); + static QList<QKeySequence> keyBindings(StandardKey key); + + // ### Qt 5: kill 'operator QString' - it's evil + operator QString() const; + operator QVariant() const; + operator int() const; + int operator[](uint i) const; + QKeySequence &operator=(const QKeySequence &other); +#ifdef Q_COMPILER_RVALUE_REFS + inline QKeySequence &operator=(QKeySequence &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QKeySequence &other) { qSwap(d, other.d); } + bool operator==(const QKeySequence &other) const; + inline bool operator!= (const QKeySequence &other) const + { return !(*this == other); } + bool operator< (const QKeySequence &ks) const; + inline bool operator> (const QKeySequence &other) const + { return other < *this; } + inline bool operator<= (const QKeySequence &other) const + { return !(other < *this); } + inline bool operator>= (const QKeySequence &other) const + { return !(*this < other); } + + bool isDetached() const; +private: + static int decodeString(const QString &ks); + static QString encodeString(int key); + int assign(const QString &str); + int assign(const QString &str, SequenceFormat format); + void setKey(int key, int index); + + QKeySequencePrivate *d; + + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &in, const QKeySequence &ks); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &in, QKeySequence &ks); + friend class Q3AccelManager; + friend class QShortcutMap; + friend class QShortcut; + +public: + typedef QKeySequencePrivate * DataPtr; + inline DataPtr &data_ptr() { return d; } +}; +Q_DECLARE_TYPEINFO(QKeySequence, Q_MOVABLE_TYPE); +Q_DECLARE_SHARED(QKeySequence) + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QKeySequence &); +#endif + +#else + +class Q_GUI_EXPORT QKeySequence +{ +public: + QKeySequence() {} + QKeySequence(int) {} +}; + +#endif // QT_NO_SHORTCUT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QKEYSEQUENCE_H diff --git a/src/gui/kernel/qkeysequence_p.h b/src/gui/kernel/qkeysequence_p.h new file mode 100644 index 0000000000..c1e5977663 --- /dev/null +++ b/src/gui/kernel/qkeysequence_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKEYSEQUENCE_P_H +#define QKEYSEQUENCE_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 "qkeysequence.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SHORTCUT +struct Q_AUTOTEST_EXPORT QKeyBinding +{ + QKeySequence::StandardKey standardKey; + uchar priority; + uint shortcut; + uint platform; +}; + +class Q_AUTOTEST_EXPORT QKeySequencePrivate +{ +public: + inline QKeySequencePrivate() + { + ref = 1; + key[0] = key[1] = key[2] = key[3] = 0; + } + inline QKeySequencePrivate(const QKeySequencePrivate ©) + { + ref = 1; + key[0] = copy.key[0]; + key[1] = copy.key[1]; + key[2] = copy.key[2]; + key[3] = copy.key[3]; + } + QAtomicInt ref; + int key[4]; + static QString encodeString(int key, QKeySequence::SequenceFormat format); + static int decodeString(const QString &keyStr, QKeySequence::SequenceFormat format); + + static const QKeyBinding keyBindings[]; + static const uint numberOfKeyBindings; + +}; +#endif // QT_NO_SHORTCUT + +QT_END_NAMESPACE + +#endif //QKEYSEQUENCE_P_H diff --git a/src/gui/kernel/qlayout.cpp b/src/gui/kernel/qlayout.cpp new file mode 100644 index 0000000000..e014ec855f --- /dev/null +++ b/src/gui/kernel/qlayout.cpp @@ -0,0 +1,1632 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlayout.h" + +#include "qapplication.h" +#include "qlayoutengine_p.h" +#include "qmenubar.h" +#include "qtoolbar.h" +#include "qsizegrip.h" +#include "qevent.h" +#include "qstyle.h" +#include "qvariant.h" +#include "qwidget_p.h" +#include "qlayout_p.h" +#include "qformlayout.h" + +QT_BEGIN_NAMESPACE + +static int menuBarHeightForWidth(QWidget *menubar, int w) +{ + if (menubar && !menubar->isHidden() && !menubar->isWindow()) { + int result = menubar->heightForWidth(qMax(w, menubar->minimumWidth())); + if (result != -1) + return result; + result = menubar->sizeHint() + .expandedTo(menubar->minimumSize()) + .expandedTo(menubar->minimumSizeHint()) + .boundedTo(menubar->maximumSize()).height(); + if (result != -1) + return result; + } + return 0; +} + +/*! + \class QLayout + \brief The QLayout class is the base class of geometry managers. + + \ingroup geomanagement + + This is an abstract base class inherited by the concrete classes + QBoxLayout, QGridLayout, QFormLayout, and QStackedLayout. + + For users of QLayout subclasses or of QMainWindow there is seldom + any need to use the basic functions provided by QLayout, such as + setSizeConstraint() or setMenuBar(). See \l{Layout Management} + for more information. + + To make your own layout manager, implement the functions + addItem(), sizeHint(), setGeometry(), itemAt() and takeAt(). You + should also implement minimumSize() to ensure your layout isn't + resized to zero size if there is too little space. To support + children whose heights depend on their widths, implement + hasHeightForWidth() and heightForWidth(). See the + \l{layouts/borderlayout}{Border Layout} and + \l{layouts/flowlayout}{Flow Layout} examples for + more information about implementing custom layout managers. + + Geometry management stops when the layout manager is deleted. + + \sa QLayoutItem, {Layout Management}, {Basic Layouts Example}, + {Border Layout Example}, {Flow Layout Example} +*/ + + +/*! + Constructs a new top-level QLayout, with parent \a parent. + \a parent may not be 0. + + There can be only one top-level layout for a widget. It is + returned by QWidget::layout(). +*/ +QLayout::QLayout(QWidget *parent) + : QObject(*new QLayoutPrivate, parent) +{ + if (!parent) + return; + parent->setLayout(this); +} + +/*! + Constructs a new child QLayout. + + This layout has to be inserted into another layout before geometry + management will work. +*/ +QLayout::QLayout() + : QObject(*new QLayoutPrivate, 0) +{ +} + + +/*! \internal + */ +QLayout::QLayout(QLayoutPrivate &dd, QLayout *lay, QWidget *w) + : QObject(dd, lay ? static_cast<QObject*>(lay) : static_cast<QObject*>(w)) +{ + Q_D(QLayout); + if (lay) { + lay->addItem(this); + } else if (w) { + if (w->layout()) { + qWarning("QLayout: Attempting to add QLayout \"%s\" to %s \"%s\", which" + " already has a layout", + qPrintable(QObject::objectName()), w->metaObject()->className(), + w->objectName().toLocal8Bit().data()); + setParent(0); + } else { + d->topLevel = true; + w->d_func()->layout = this; + QT_TRY { + invalidate(); + } QT_CATCH(...) { + w->d_func()->layout = 0; + QT_RETHROW; + } + } + } +} + +QLayoutPrivate::QLayoutPrivate() + : QObjectPrivate(), insideSpacing(-1), userLeftMargin(-1), userTopMargin(-1), userRightMargin(-1), + userBottomMargin(-1), topLevel(false), enabled(true), activated(true), autoNewChild(false), + constraint(QLayout::SetDefaultConstraint), menubar(0) +{ +} + +void QLayoutPrivate::getMargin(int *result, int userMargin, QStyle::PixelMetric pm) const +{ + if (!result) + return; + + Q_Q(const QLayout); + if (userMargin >= 0) { + *result = userMargin; + } else if (!topLevel) { + *result = 0; + } else if (QWidget *pw = q->parentWidget()) { + *result = pw->style()->pixelMetric(pm, 0, pw); + } else { + *result = 0; + } +} + +// Static item factory functions that allow for hooking things in Designer + +QLayoutPrivate::QWidgetItemFactoryMethod QLayoutPrivate::widgetItemFactoryMethod = 0; +QLayoutPrivate::QSpacerItemFactoryMethod QLayoutPrivate::spacerItemFactoryMethod = 0; + +QWidgetItem *QLayoutPrivate::createWidgetItem(const QLayout *layout, QWidget *widget) +{ + if (widgetItemFactoryMethod) + if (QWidgetItem *wi = (*widgetItemFactoryMethod)(layout, widget)) + return wi; + return new QWidgetItemV2(widget); +} + +QSpacerItem *QLayoutPrivate::createSpacerItem(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy) +{ + if (spacerItemFactoryMethod) + if (QSpacerItem *si = (*spacerItemFactoryMethod)(layout, w, h, hPolicy, vPolicy)) + return si; + return new QSpacerItem(w, h, hPolicy, vPolicy); +} + +#ifdef QT3_SUPPORT +/*! + Constructs a new top-level QLayout called \a name, with parent + widget \a parent. \a parent may not be 0. + + The \a margin is the number of pixels between the edge of the + widget and the managed children. The \a spacing sets the value of + spacing(), which gives the spacing between the managed widgets. If + \a spacing is -1 (the default), spacing is set to the value of \a + margin. + + There can be only one top-level layout for a widget. It is + returned by QWidget::layout() + + \sa QWidget::setLayout() +*/ +QLayout::QLayout(QWidget *parent, int margin, int spacing, const char *name) + : QObject(*new QLayoutPrivate,parent) +{ + Q_D(QLayout); + setObjectName(QString::fromAscii(name)); + setMargin(margin); + if (spacing < 0) + d->insideSpacing = margin; + else + d->insideSpacing = spacing; + if (parent) { + if (parent->layout()) { + qWarning("QLayout \"%s\" added to %s \"%s\", which already has a layout", + QObject::objectName().toLocal8Bit().data(), parent->metaObject()->className(), + parent->objectName().toLocal8Bit().data()); + parent->layout()->setParent(0); + } else { + d->topLevel = true; + parent->d_func()->layout = this; + QT_TRY { + invalidate(); + } QT_CATCH(...) { + parent->d_func()->layout = 0; + QT_RETHROW; + } + } + } +} + +/*! + Constructs a new child QLayout called \a name, and places it + inside \a parentLayout by using the default placement defined by + addItem(). + + If \a spacing is -1, this QLayout inherits \a parentLayout's + spacing(), otherwise the value of \a spacing is used. +*/ +QLayout::QLayout(QLayout *parentLayout, int spacing, const char *name) + : QObject(*new QLayoutPrivate,parentLayout) + +{ + Q_D(QLayout); + setObjectName(QString::fromAscii(name)); + d->insideSpacing = spacing; + parentLayout->addItem(this); +} + +/*! + Constructs a new child QLayout called \a name. If \a spacing is + -1, this QLayout inherits its parent's spacing(); otherwise the + value of \a spacing is used. + + This layout has to be inserted into another layout before geometry + management will work. +*/ +QLayout::QLayout(int spacing, const char *name) + : QObject(*new QLayoutPrivate, 0) +{ + Q_D(QLayout); + setObjectName(QString::fromAscii(name)); + d->insideSpacing = spacing; +} + +/*! + Automatically adding widgets is deprecated. Use addWidget() or + addLayout() instead. +*/ +void QLayout::setAutoAdd(bool a) { Q_D(QLayout); d->autoNewChild = a; } + +/*! + Automatically adding widgets is deprecated. Use addWidget() or + addLayout() instead. +*/ +bool QLayout::autoAdd() const { Q_D(const QLayout); return d->autoNewChild; } +#endif + + +/*! + \fn void QLayout::addItem(QLayoutItem *item) + + Implemented in subclasses to add an \a item. How it is added is + specific to each subclass. + + This function is not usually called in application code. To add a widget + to a layout, use the addWidget() function; to add a child layout, use the + addLayout() function provided by the relevant QLayout subclass. + + \bold{Note:} The ownership of \a item is transferred to the layout, and it's + the layout's responsibility to delete it. + + \sa addWidget(), QBoxLayout::addLayout(), QGridLayout::addLayout() +*/ + +/*! + Adds widget \a w to this layout in a manner specific to the + layout. This function uses addItem(). +*/ +void QLayout::addWidget(QWidget *w) +{ + addChildWidget(w); + addItem(QLayoutPrivate::createWidgetItem(this, w)); +} + + + +/*! + Sets the alignment for widget \a w to \a alignment and returns + true if \a w is found in this layout (not including child + layouts); otherwise returns false. +*/ +bool QLayout::setAlignment(QWidget *w, Qt::Alignment alignment) +{ + int i = 0; + QLayoutItem *item = itemAt(i); + while (item) { + if (item->widget() == w) { + item->setAlignment(alignment); + invalidate(); + return true; + } + ++i; + item = itemAt(i); + } + return false; +} + +/*! + \overload + + Sets the alignment for the layout \a l to \a alignment and + returns true if \a l is found in this layout (not including child + layouts); otherwise returns false. +*/ +bool QLayout::setAlignment(QLayout *l, Qt::Alignment alignment) +{ + int i = 0; + QLayoutItem *item = itemAt(i); + while (item) { + if (item->layout() == l) { + item->setAlignment(alignment); + invalidate(); + return true; + } + ++i; + item = itemAt(i); + } + return false; +} + +/*! + \fn void QLayout::setAlignment(Qt::Alignment alignment) + + Sets the alignment of this item to \a alignment. + + \sa QLayoutItem::setAlignment() +*/ + +/*! + \fn bool QLayout::isTopLevel() const + + Returns true if this layout is a top-level layout, i.e. not a + child of another layout; otherwise returns false. +*/ + +/*! + \property QLayout::margin + \brief the width of the outside border of the layout + \obsolete + + Use setContentsMargins() and getContentsMargins() instead. + + \sa contentsRect(), spacing +*/ + +/*! + \obsolete +*/ +int QLayout::margin() const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + if (left == top && top == right && right == bottom) { + return left; + } else { + return -1; + } +} + +/*! + \property QLayout::spacing + \brief the spacing between widgets inside the layout + + If no value is explicitly set, the layout's spacing is inherited + from the parent layout, or from the style settings for the parent + widget. + + For QGridLayout and QFormLayout, it is possible to set different horizontal and + vertical spacings using \l{QGridLayout::}{setHorizontalSpacing()} + and \l{QGridLayout::}{setVerticalSpacing()}. In that case, + spacing() returns -1. + + \sa contentsRect(), getContentsMargins(), QStyle::layoutSpacing(), + QStyle::pixelMetric() +*/ + +int QLayout::spacing() const +{ + if (const QBoxLayout* boxlayout = qobject_cast<const QBoxLayout*>(this)) { + return boxlayout->spacing(); + } else if (const QGridLayout* gridlayout = qobject_cast<const QGridLayout*>(this)) { + return gridlayout->spacing(); + } else if (const QFormLayout* formlayout = qobject_cast<const QFormLayout*>(this)) { + return formlayout->spacing(); + } else { + Q_D(const QLayout); + if (d->insideSpacing >=0) { + return d->insideSpacing; + } else { + // arbitrarily prefer horizontal spacing to vertical spacing + return qSmartSpacing(this, QStyle::PM_LayoutHorizontalSpacing); + } + } +} + +/*! + \obsolete +*/ +void QLayout::setMargin(int margin) +{ + setContentsMargins(margin, margin, margin, margin); +} + +void QLayout::setSpacing(int spacing) +{ + if (QBoxLayout* boxlayout = qobject_cast<QBoxLayout*>(this)) { + boxlayout->setSpacing(spacing); + } else if (QGridLayout* gridlayout = qobject_cast<QGridLayout*>(this)) { + gridlayout->setSpacing(spacing); + } else if (QFormLayout* formlayout = qobject_cast<QFormLayout*>(this)) { + formlayout->setSpacing(spacing); + } else { + Q_D(QLayout); + d->insideSpacing = spacing; + invalidate(); + } +} + +/*! + \since 4.3 + + Sets the \a left, \a top, \a right, and \a bottom margins to use + around the layout. + + By default, QLayout uses the values provided by the style. On + most platforms, the margin is 11 pixels in all directions. + + \sa getContentsMargins(), QStyle::pixelMetric(), + {QStyle::}{PM_LayoutLeftMargin}, + {QStyle::}{PM_LayoutTopMargin}, + {QStyle::}{PM_LayoutRightMargin}, + {QStyle::}{PM_LayoutBottomMargin} +*/ +void QLayout::setContentsMargins(int left, int top, int right, int bottom) +{ + Q_D(QLayout); + + if (d->userLeftMargin == left && d->userTopMargin == top && + d->userRightMargin == right && d->userBottomMargin == bottom) + return; + + d->userLeftMargin = left; + d->userTopMargin = top; + d->userRightMargin = right; + d->userBottomMargin = bottom; + invalidate(); +} + +/*! + \since 4.6 + + Sets the \a margins to use around the layout. + + By default, QLayout uses the values provided by the style. On + most platforms, the margin is 11 pixels in all directions. + + \sa contentsMargins() +*/ +void QLayout::setContentsMargins(const QMargins &margins) +{ + setContentsMargins(margins.left(), margins.top(), margins.right(), margins.bottom()); +} + +/*! + \since 4.3 + + Extracts the left, top, right, and bottom margins used around the + layout, and assigns them to *\a left, *\a top, *\a right, and *\a + bottom (unless they are null pointers). + + By default, QLayout uses the values provided by the style. On + most platforms, the margin is 11 pixels in all directions. + + \sa setContentsMargins(), QStyle::pixelMetric(), + {QStyle::}{PM_LayoutLeftMargin}, + {QStyle::}{PM_LayoutTopMargin}, + {QStyle::}{PM_LayoutRightMargin}, + {QStyle::}{PM_LayoutBottomMargin} +*/ +void QLayout::getContentsMargins(int *left, int *top, int *right, int *bottom) const +{ + Q_D(const QLayout); + d->getMargin(left, d->userLeftMargin, QStyle::PM_LayoutLeftMargin); + d->getMargin(top, d->userTopMargin, QStyle::PM_LayoutTopMargin); + d->getMargin(right, d->userRightMargin, QStyle::PM_LayoutRightMargin); + d->getMargin(bottom, d->userBottomMargin, QStyle::PM_LayoutBottomMargin); +} + +/*! + \since 4.6 + + Returns the margins used around the layout. + + By default, QLayout uses the values provided by the style. On + most platforms, the margin is 11 pixels in all directions. + + \sa setContentsMargins() +*/ +QMargins QLayout::contentsMargins() const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + return QMargins(left, top, right, bottom); +} + +/*! + \since 4.3 + + Returns the layout's geometry() rectangle, but taking into account the + contents margins. + + \sa setContentsMargins(), getContentsMargins() +*/ +QRect QLayout::contentsRect() const +{ + Q_D(const QLayout); + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + return d->rect.adjusted(+left, +top, -right, -bottom); +} + +#ifdef QT3_SUPPORT +bool QLayout::isTopLevel() const +{ + Q_D(const QLayout); + return d->topLevel; +} +#endif + +/*! + Returns the parent widget of this layout, or 0 if this layout is + not installed on any widget. + + If the layout is a sub-layout, this function returns the parent + widget of the parent layout. + + \sa parent() +*/ +QWidget *QLayout::parentWidget() const +{ + Q_D(const QLayout); + if (!d->topLevel) { + if (parent()) { + QLayout *parentLayout = qobject_cast<QLayout*>(parent()); + if (!parentLayout) { + qWarning("QLayout::parentWidget: A layout can only have another layout as a parent."); + return 0; + } + return parentLayout->parentWidget(); + } else { + return 0; + } + } else { + Q_ASSERT(parent() && parent()->isWidgetType()); + return static_cast<QWidget *>(parent()); + } +} + +/*! + \reimp +*/ +bool QLayout::isEmpty() const +{ + int i = 0; + QLayoutItem *item = itemAt(i); + while (item) { + if (!item->isEmpty()) + return false; + ++i; + item = itemAt(i); + } + return true; +} + +/*! + \reimp +*/ +void QLayout::setGeometry(const QRect &r) +{ + Q_D(QLayout); + d->rect = r; +} + +/*! + \reimp +*/ +QRect QLayout::geometry() const +{ + Q_D(const QLayout); + return d->rect; +} + +/*! + \reimp +*/ +void QLayout::invalidate() +{ + Q_D(QLayout); + d->rect = QRect(); + update(); +} + +static bool removeWidgetRecursively(QLayoutItem *li, QWidget *w) +{ + QLayout *lay = li->layout(); + if (!lay) + return false; + int i = 0; + QLayoutItem *child; + while ((child = lay->itemAt(i))) { + if (child->widget() == w) { + delete lay->takeAt(i); + lay->invalidate(); + return true; + } else if (removeWidgetRecursively(child, w)) { + return true; + } else { + ++i; + } + } + return false; +} + + +void QLayoutPrivate::doResize(const QSize &r) +{ + Q_Q(QLayout); + int mbh = menuBarHeightForWidth(menubar, r.width()); + QWidget *mw = q->parentWidget(); + QRect rect = mw->testAttribute(Qt::WA_LayoutOnEntireRect) ? mw->rect() : mw->contentsRect(); + rect.setTop(rect.top() + mbh); + q->setGeometry(rect); +#ifndef QT_NO_MENUBAR + if (menubar) + menubar->setGeometry(0,0,r.width(), mbh); +#endif +} + + +/*! + \internal + Performs child widget layout when the parent widget is + resized. Also handles removal of widgets. \a e is the + event +*/ +void QLayout::widgetEvent(QEvent *e) +{ + Q_D(QLayout); + if (!d->enabled) + return; + + switch (e->type()) { + case QEvent::Resize: + if (d->activated) { + QResizeEvent *r = (QResizeEvent *)e; + d->doResize(r->size()); + } else { + activate(); + } + break; + case QEvent::ChildRemoved: + { + QChildEvent *c = (QChildEvent *)e; + if (c->child()->isWidgetType()) { + QWidget *w = (QWidget *)c->child(); +#ifndef QT_NO_MENUBAR + if (w == d->menubar) + d->menubar = 0; +#endif + removeWidgetRecursively(this, w); + } + } + break; +#ifdef QT3_SUPPORT + case QEvent::ChildInserted: + if (d->topLevel && d->autoNewChild) { + QChildEvent *c = (QChildEvent *)e; + if (c->child()->isWidgetType()) { + QWidget *w = (QWidget *)c->child(); + if (!w->isWindow()) { +#if !defined(QT_NO_MENUBAR) && !defined(QT_NO_TOOLBAR) + if (qobject_cast<QMenuBar*>(w) && !qobject_cast<QToolBar*>(w->parentWidget())) { + d->menubar = (QMenuBar *)w; + invalidate(); + } else +#endif +#ifndef QT_NO_SIZEGRIP + if (qobject_cast<QSizeGrip*>(w) ) { + //SizeGrip is handled by the dialog itself. + } else +#endif + addItem(QLayoutPrivate::createWidgetItem(this, w)); + } + } + } + break; + case QEvent::LayoutHint: + d->activated = false; + // fall through +#endif + case QEvent::LayoutRequest: + if (static_cast<QWidget *>(parent())->isVisible()) + activate(); + break; + default: + break; + } +} + +/*! + \reimp +*/ +void QLayout::childEvent(QChildEvent *e) +{ + Q_D(QLayout); + if (!d->enabled) + return; + + if (e->type() == QEvent::ChildRemoved) { + QChildEvent *c = (QChildEvent*)e; + int i = 0; + + QLayoutItem *item; + while ((item = itemAt(i))) { + if (item == static_cast<QLayout*>(c->child())) { + takeAt(i); + invalidate(); + break; + } else { + ++i; + } + } + } +} + +/*! + \internal + Also takes contentsMargins and menu bar into account. +*/ +int QLayout::totalHeightForWidth(int w) const +{ + Q_D(const QLayout); + int side=0, top=0; + if (d->topLevel) { + QWidget *parent = parentWidget(); + parent->ensurePolished(); + QWidgetPrivate *wd = parent->d_func(); + side += wd->leftmargin + wd->rightmargin; + top += wd->topmargin + wd->bottommargin; + } + int h = heightForWidth(w - side) + top; +#ifndef QT_NO_MENUBAR + h += menuBarHeightForWidth(d->menubar, w); +#endif + return h; +} + +/*! + \internal + Also takes contentsMargins and menu bar into account. +*/ +QSize QLayout::totalMinimumSize() const +{ + Q_D(const QLayout); + int side=0, top=0; + if (d->topLevel) { + QWidget *pw = parentWidget(); + pw->ensurePolished(); + QWidgetPrivate *wd = pw->d_func(); + side += wd->leftmargin + wd->rightmargin; + top += wd->topmargin + wd->bottommargin; + } + + QSize s = minimumSize(); +#ifndef QT_NO_MENUBAR + top += menuBarHeightForWidth(d->menubar, s.width() + side); +#endif + return s + QSize(side, top); +} + +/*! + \internal + Also takes contentsMargins and menu bar into account. +*/ +QSize QLayout::totalSizeHint() const +{ + Q_D(const QLayout); + int side=0, top=0; + if (d->topLevel) { + QWidget *pw = parentWidget(); + pw->ensurePolished(); + QWidgetPrivate *wd = pw->d_func(); + side += wd->leftmargin + wd->rightmargin; + top += wd->topmargin + wd->bottommargin; + } + + QSize s = sizeHint(); + if (hasHeightForWidth()) + s.setHeight(heightForWidth(s.width() + side)); +#ifndef QT_NO_MENUBAR + top += menuBarHeightForWidth(d->menubar, s.width()); +#endif + return s + QSize(side, top); +} + +/*! + \internal + Also takes contentsMargins and menu bar into account. +*/ +QSize QLayout::totalMaximumSize() const +{ + Q_D(const QLayout); + int side=0, top=0; + if (d->topLevel) { + QWidget *pw = parentWidget(); + pw->ensurePolished(); + QWidgetPrivate *wd = pw->d_func(); + side += wd->leftmargin + wd->rightmargin; + top += wd->topmargin + wd->bottommargin; + } + + QSize s = maximumSize(); +#ifndef QT_NO_MENUBAR + top += menuBarHeightForWidth(d->menubar, s.width()); +#endif + + if (d->topLevel) + s = QSize(qMin(s.width() + side, QLAYOUTSIZE_MAX), + qMin(s.height() + top, QLAYOUTSIZE_MAX)); + return s; +} + +/*! + \internal + Destroys the layout, deleting all child layouts. + Geometry management stops when a top-level layout is deleted. + + The layout classes will probably be fatally confused if you delete + a sublayout. +*/ +QLayout::~QLayout() +{ + Q_D(QLayout); + /* + This function may be called during the QObject destructor, + when the parent no longer is a QWidget. + */ + if (d->topLevel && parent() && parent()->isWidgetType() && + ((QWidget*)parent())->layout() == this) + ((QWidget*)parent())->d_func()->layout = 0; +} + +#ifdef QT3_SUPPORT +/*! + Removes and deletes all items in this layout. +*/ +void QLayout::deleteAllItems() +{ + QLayoutItem *l; + while ((l = takeAt(0))) + delete l; +} +#endif + +/*! + This function is called from \c addLayout() or \c insertLayout() functions in + subclasses to add layout \a l as a sub-layout. + + The only scenario in which you need to call it directly is if you + implement a custom layout that supports nested layouts. + + \sa QBoxLayout::addLayout(), QBoxLayout::insertLayout(), QGridLayout::addLayout() +*/ +void QLayout::addChildLayout(QLayout *l) +{ + if (l->parent()) { + qWarning("QLayout::addChildLayout: layout \"%s\" already has a parent", + l->objectName().toLocal8Bit().data()); + return; + } + l->setParent(this); + + if (QWidget *mw = parentWidget()) { + l->d_func()->reparentChildWidgets(mw); + } + +} + +#ifdef QT_DEBUG +static bool layoutDebug() +{ + static int checked_env = -1; + if(checked_env == -1) + checked_env = !!qgetenv("QT_LAYOUT_DEBUG").toInt(); + + return checked_env; +} +#endif + +void QLayoutPrivate::reparentChildWidgets(QWidget *mw) +{ + Q_Q(QLayout); + int n = q->count(); + +#ifndef QT_NO_MENUBAR + if (menubar && menubar->parentWidget() != mw) { + menubar->setParent(mw); + } +#endif + bool mwVisible = mw && mw->isVisible(); + for (int i = 0; i < n; ++i) { + QLayoutItem *item = q->itemAt(i); + if (QWidget *w = item->widget()) { + QWidget *pw = w->parentWidget(); +#ifdef QT_DEBUG + if (pw && pw != mw && layoutDebug()) { + qWarning("QLayout::addChildLayout: widget %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().data()); + } +#endif + bool needShow = mwVisible && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide)); + if (pw != mw) + w->setParent(mw); + if (needShow) + QMetaObject::invokeMethod(w, "_q_showIfNotHidden", Qt::QueuedConnection); //show later + } else if (QLayout *l = item->layout()) { + l->d_func()->reparentChildWidgets(mw); + } + } +} + +/*! + This function is called from \c addWidget() functions in + subclasses to add \a w as a managed widget of a layout. + + If \a w is already managed by a layout, this function will give a warning + and remove \a w from that layout. This function must therefore be + called before adding \a w to the layout's data structure. +*/ +void QLayout::addChildWidget(QWidget *w) +{ + QWidget *mw = parentWidget(); + QWidget *pw = w->parentWidget(); + + //Qt::WA_LaidOut is never reset. It only means that the widget at some point has + //been in a layout. + if (pw && w->testAttribute(Qt::WA_LaidOut)) { + QLayout *l = pw->layout(); + if (l && removeWidgetRecursively(l, w)) { +#ifdef QT_DEBUG + if (layoutDebug()) + qWarning("QLayout::addChildWidget: %s \"%s\" is already in a layout; moved to new layout", + w->metaObject()->className(), w->objectName().toLocal8Bit().data()); +#endif + } + } + if (pw && mw && pw != mw) { +#ifdef QT_DEBUG + if (layoutDebug()) + qWarning("QLayout::addChildWidget: %s \"%s\" in wrong parent; moved to correct parent", + w->metaObject()->className(), w->objectName().toLocal8Bit().data()); +#endif + pw = 0; + } + bool needShow = mw && mw->isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide)); + if (!pw && mw) + w->setParent(mw); + w->setAttribute(Qt::WA_LaidOut); + if (needShow) + QMetaObject::invokeMethod(w, "_q_showIfNotHidden", Qt::QueuedConnection); //show later +} + +#ifdef QT3_SUPPORT +/*! + \compat + + Sets this layout's parent widget to a fixed size with width \a w + and height \a h, stopping the user from resizing it, and also + prevents the layout from resizing it, even if the layout's size + hint should change. Does nothing if this is not a top-level + layout (i.e., if parent()->isWidgetType()). + + As a special case, if both \a w and \a h are 0, then the layout's + current sizeHint() is used. + + Use \c setResizeMode(Fixed) to stop the widget from being resized + by the user, while still allowing the layout to resize it when + the sizeHint() changes. + + Use \c setResizeMode(FreeResize) to allow the user to resize the + widget, while preventing the layout from resizing it. + +*/ +void QLayout::freeze(int w, int h) +{ + Q_D(QLayout); + if (!d->topLevel) + return; + if (w <= 0 || h <= 0) { + QSize s = totalSizeHint(); + w = s.width(); + h = s.height(); + } + setSizeConstraint(SetNoConstraint); // layout will not change min/max size + QWidget *parent = parentWidget(); + if (parent) + parent->setFixedSize(w, h); +} + +#endif + + + + + + + +/*! + Tells the geometry manager to place the menu bar \a widget at the + top of parentWidget(), outside QWidget::contentsMargins(). All + child widgets are placed below the bottom edge of the menu bar. +*/ +void QLayout::setMenuBar(QWidget *widget) +{ + Q_D(QLayout); + +#ifdef Q_OS_WINCE_WM + if (widget && widget->size().height() > 0) +#else + if (widget) +#endif + addChildWidget(widget); + d->menubar = widget; +} + +/*! + Returns the menu bar set for this layout, or 0 if no menu bar is + set. +*/ + +QWidget *QLayout::menuBar() const +{ + Q_D(const QLayout); + return d->menubar; +} + + +/*! + Returns the minimum size of this layout. This is the smallest + size that the layout can have while still respecting the + specifications. + + The returned value doesn't include the space required by + QWidget::setContentsMargins() or menuBar(). + + The default implementation allows unlimited resizing. +*/ +QSize QLayout::minimumSize() const +{ + return QSize(0, 0); +} + +/*! + Returns the maximum size of this layout. This is the largest size + that the layout can have while still respecting the + specifications. + + The returned value doesn't include the space required by + QWidget::setContentsMargins() or menuBar(). + + The default implementation allows unlimited resizing. +*/ +QSize QLayout::maximumSize() const +{ + return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX); +} + +/*! + Returns whether this layout can make use of more space than + sizeHint(). A value of Qt::Vertical or Qt::Horizontal means that + it wants to grow in only one dimension, whereas Qt::Vertical | + Qt::Horizontal means that it wants to grow in both dimensions. + + The default implementation returns Qt::Horizontal | Qt::Vertical. + Subclasses reimplement it to return a meaningful value based on + their child widgets's \l{QSizePolicy}{size policies}. + + \sa sizeHint() +*/ +Qt::Orientations QLayout::expandingDirections() const +{ + return Qt::Horizontal | Qt::Vertical; +} + +void QLayout::activateRecursiveHelper(QLayoutItem *item) +{ + item->invalidate(); + QLayout *layout = item->layout(); + if (layout) { + QLayoutItem *child; + int i=0; + while ((child = layout->itemAt(i++))) + activateRecursiveHelper(child); + layout->d_func()->activated = true; + } +} + +/*! + Updates the layout for parentWidget(). + + You should generally not need to call this because it is + automatically called at the most appropriate times. + + \sa activate(), invalidate() +*/ + +void QLayout::update() +{ + QLayout *layout = this; + while (layout && layout->d_func()->activated) { + layout->d_func()->activated = false; + if (layout->d_func()->topLevel) { + Q_ASSERT(layout->parent()->isWidgetType()); + QWidget *mw = static_cast<QWidget*>(layout->parent()); + QApplication::postEvent(mw, new QEvent(QEvent::LayoutRequest)); + break; + } + layout = static_cast<QLayout*>(layout->parent()); + } +} + +/*! + Redoes the layout for parentWidget() if necessary. + + You should generally not need to call this because it is + automatically called at the most appropriate times. It returns + true if the layout was redone. + + \sa update(), QWidget::updateGeometry() +*/ +bool QLayout::activate() +{ + Q_D(QLayout); + if (!d->enabled || !parent()) + return false; + if (!d->topLevel) + return static_cast<QLayout*>(parent())->activate(); + if (d->activated) + return false; + QWidget *mw = static_cast<QWidget*>(parent()); + if (mw == 0) { + qWarning("QLayout::activate: %s \"%s\" does not have a main widget", + QObject::metaObject()->className(), QObject::objectName().toLocal8Bit().data()); + return false; + } + activateRecursiveHelper(this); + + QWidgetPrivate *md = mw->d_func(); + uint explMin = md->extra ? md->extra->explicitMinSize : 0; + uint explMax = md->extra ? md->extra->explicitMaxSize : 0; + + switch (d->constraint) { + case SetFixedSize: + // will trigger resize + mw->setFixedSize(totalSizeHint()); + break; + case SetMinimumSize: + mw->setMinimumSize(totalMinimumSize()); + break; + case SetMaximumSize: + mw->setMaximumSize(totalMaximumSize()); + break; + case SetMinAndMaxSize: + mw->setMinimumSize(totalMinimumSize()); + mw->setMaximumSize(totalMaximumSize()); + break; + case SetDefaultConstraint: { + bool widthSet = explMin & Qt::Horizontal; + bool heightSet = explMin & Qt::Vertical; + if (mw->isWindow()) { + QSize ms = totalMinimumSize(); + if (widthSet) + ms.setWidth(mw->minimumSize().width()); + if (heightSet) + ms.setHeight(mw->minimumSize().height()); + if ((!heightSet || !widthSet) && hasHeightForWidth()) { + int h = minimumHeightForWidth(ms.width()); + if (h > ms.height()) { + if (!heightSet) + ms.setHeight(0); + if (!widthSet) + ms.setWidth(0); + } + } + mw->setMinimumSize(ms); + } else if (!widthSet || !heightSet) { + QSize ms = mw->minimumSize(); + if (!widthSet) + ms.setWidth(0); + if (!heightSet) + ms.setHeight(0); + mw->setMinimumSize(ms); + } + break; + } + case SetNoConstraint: + break; + } + + d->doResize(mw->size()); + + if (md->extra) { + md->extra->explicitMinSize = explMin; + md->extra->explicitMaxSize = explMax; + } + // ideally only if sizeHint() or sizePolicy() has changed + mw->updateGeometry(); + return true; +} + +/*! + \fn QLayoutItem *QLayout::itemAt(int index) const + + Must be implemented in subclasses to return the layout item at \a + index. If there is no such item, the function must return 0. + Items are numbered consecutively from 0. If an item is deleted, other items will be renumbered. + + This function can be used to iterate over a layout. The following + code will draw a rectangle for each layout item in the layout structure of the widget. + + \snippet doc/src/snippets/code/src_gui_kernel_qlayout.cpp 0 + + \sa count(), takeAt() +*/ + +/*! + \fn QLayoutItem *QLayout::takeAt(int index) + + Must be implemented in subclasses to remove the layout item at \a + index from the layout, and return the item. If there is no such + item, the function must do nothing and return 0. Items are numbered + consecutively from 0. If an item is removed, other items will be + renumbered. + + The following code fragment shows a safe way to remove all items + from a layout: + + \snippet doc/src/snippets/code/src_gui_kernel_qlayout.cpp 1 + + \sa itemAt(), count() +*/ + +/*! + \fn int *QLayout::count() const + + Must be implemented in subclasses to return the number of items + in the layout. + + \sa itemAt() +*/ + +/*! + Searches for widget \a widget in this layout (not including child + layouts). + + Returns the index of \a widget, or -1 if \a widget is not found. + + The default implementation iterates over all items using itemAt() +*/ +int QLayout::indexOf(QWidget *widget) const +{ + int i = 0; + QLayoutItem *item = itemAt(i); + while (item) { + if (item->widget() == widget) + return i; + ++i; + item = itemAt(i); + } + return -1; +} + +/*! + \enum QLayout::SizeConstraint + + The possible values are: + + \value SetDefaultConstraint The main widget's minimum size is set + to minimumSize(), unless the widget already has + a minimum size. + + \value SetFixedSize The main widget's size is set to sizeHint(); it + cannot be resized at all. + \value SetMinimumSize The main widget's minimum size is set to + minimumSize(); it cannot be smaller. + + \value SetMaximumSize The main widget's maximum size is set to + maximumSize(); it cannot be larger. + + \value SetMinAndMaxSize The main widget's minimum size is set to + minimumSize() and its maximum size is set to + maximumSize(). + + \value SetNoConstraint The widget is not constrained. + + \omitvalue Auto + \omitvalue FreeResize + \omitvalue Minimum + \omitvalue Fixed + + \sa setSizeConstraint() +*/ + +/*! + \property QLayout::sizeConstraint + \brief the resize mode of the layout + + The default mode is \l {QLayout::SetDefaultConstraint} + {SetDefaultConstraint}. +*/ +void QLayout::setSizeConstraint(SizeConstraint constraint) +{ + Q_D(QLayout); + if (constraint == d->constraint) + return; + + d->constraint = constraint; + invalidate(); +} + +QLayout::SizeConstraint QLayout::sizeConstraint() const +{ + Q_D(const QLayout); + return d->constraint; +} + +/*! + Returns the rectangle that should be covered when the geometry of + this layout is set to \a r, provided that this layout supports + setAlignment(). + + The result is derived from sizeHint() and expanding(). It is never + larger than \a r. +*/ +QRect QLayout::alignmentRect(const QRect &r) const +{ + QSize s = sizeHint(); + Qt::Alignment a = alignment(); + + /* + This is a hack to obtain the real maximum size, not + QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX), the value consistently + returned by QLayoutItems that have an alignment. + */ + QLayout *that = const_cast<QLayout *>(this); + that->setAlignment(0); + QSize ms = that->maximumSize(); + that->setAlignment(a); + + if ((expandingDirections() & Qt::Horizontal) || + !(a & Qt::AlignHorizontal_Mask)) { + s.setWidth(qMin(r.width(), ms.width())); + } + if ((expandingDirections() & Qt::Vertical) || + !(a & Qt::AlignVertical_Mask)) { + s.setHeight(qMin(r.height(), ms.height())); + } else if (hasHeightForWidth()) { + int hfw = heightForWidth(s.width()); + if (hfw < s.height()) + s.setHeight(qMin(hfw, ms.height())); + } + + s = s.boundedTo(r.size()); + int x = r.x(); + int y = r.y(); + + if (a & Qt::AlignBottom) + y += (r.height() - s.height()); + else if (!(a & Qt::AlignTop)) + y += (r.height() - s.height()) / 2; + + QWidget *parent = parentWidget(); + a = QStyle::visualAlignment(parent ? parent->layoutDirection() : QApplication::layoutDirection(), a); + if (a & Qt::AlignRight) + x += (r.width() - s.width()); + else if (!(a & Qt::AlignLeft)) + x += (r.width() - s.width()) / 2; + + return QRect(x, y, s.width(), s.height()); +} + +/*! + Removes the widget \a widget from the layout. After this call, it + is the caller's responsibility to give the widget a reasonable + geometry or to put the widget back into a layout. + + \bold{Note:} The ownership of \a widget remains the same as + when it was added. + + \sa removeItem(), QWidget::setGeometry(), addWidget() +*/ +void QLayout::removeWidget(QWidget *widget) +{ + int i = 0; + QLayoutItem *child; + while ((child = itemAt(i))) { + if (child->widget() == widget) { + delete takeAt(i); + invalidate(); + } else { + ++i; + } + } +} + +/*! + Removes the layout item \a item from the layout. It is the + caller's responsibility to delete the item. + + Notice that \a item can be a layout (since QLayout inherits + QLayoutItem). + + \sa removeWidget(), addItem() +*/ +void QLayout::removeItem(QLayoutItem *item) +{ + int i = 0; + QLayoutItem *child; + while ((child = itemAt(i))) { + if (child == item) { + takeAt(i); + invalidate(); + } else { + ++i; + } + } +} + +/*! + Enables this layout if \a enable is true, otherwise disables it. + + An enabled layout adjusts dynamically to changes; a disabled + layout acts as if it did not exist. + + By default all layouts are enabled. + + \sa isEnabled() +*/ +void QLayout::setEnabled(bool enable) +{ + Q_D(QLayout); + d->enabled = enable; +} + +/*! + Returns true if the layout is enabled; otherwise returns false. + + \sa setEnabled() +*/ +bool QLayout::isEnabled() const +{ + Q_D(const QLayout); + return d->enabled; +} + +/*! + Returns a size that satisfies all size constraints on \a widget, + including heightForWidth() and that is as close as possible to \a + size. +*/ + +QSize QLayout::closestAcceptableSize(const QWidget *widget, const QSize &size) +{ + QSize result = size.boundedTo(qSmartMaxSize(widget)); + result = result.expandedTo(qSmartMinSize(widget)); + QLayout *l = widget->layout(); + if (l && l->hasHeightForWidth() && result.height() < l->minimumHeightForWidth(result.width()) ) { + QSize current = widget->size(); + int currentHfw = l->minimumHeightForWidth(current.width()); + int newHfw = l->minimumHeightForWidth(result.width()); + if (current.height() < currentHfw || currentHfw == newHfw) { + //handle the constant hfw case and the vertical-only case, as well as the + // current-size-is-not-correct case + result.setHeight(newHfw); + } else { + // binary search; assume hfw is decreasing ### + + int maxw = qMax(widget->width(),result.width()); + int maxh = qMax(widget->height(), result.height()); + int minw = qMin(widget->width(),result.width()); + int minh = qMin(widget->height(), result.height()); + + int minhfw = l->minimumHeightForWidth(minw); + int maxhfw = l->minimumHeightForWidth(maxw); + while (minw < maxw) { + if (minhfw > maxh) { //assume decreasing + minw = maxw - (maxw-minw)/2; + minhfw = l->minimumHeightForWidth(minw); + } else if (maxhfw < minh ) { //assume decreasing + maxw = minw + (maxw-minw)/2; + maxhfw = l->minimumHeightForWidth(maxw); + } else { + break; + } + } + result = result.expandedTo(QSize(minw, minhfw)); + } + } + return result; +} + +/*! + \fn void QLayout::setResizeMode(SizeConstraint constraint) + + Use setSizeConstraint(\a constraint) instead. +*/ + +/*! + \fn QLayout::SizeConstraint QLayout::resizeMode() const + + Use sizeConstraint() instead. +*/ + +void QSizePolicy::setControlType(ControlType type) +{ + /* + The control type is a flag type, with values 0x1, 0x2, 0x4, 0x8, 0x10, + etc. In memory, we pack it onto the available bits (CTSize) in + setControlType(), and unpack it here. + + Example: + + 0x00000001 maps to 0x00000000 + 0x00000002 maps to 0x00000200 + 0x00000004 maps to 0x00000400 + 0x00000008 maps to 0x00000600 + etc. + */ + + int i = 0; + while (true) { + if (type & (0x1 << i)) { + data = (data & ~CTMask) | (i << CTShift); + return; + } + ++i; + } +} + +QSizePolicy::ControlType QSizePolicy::controlType() const +{ + return QSizePolicy::ControlType(0x1 << ((data & CTMask) >> CTShift)); +} + +#ifndef QT_NO_DATASTREAM +/*! + \relates QSizePolicy + \since 4.2 + + Writes the size \a policy to the data stream \a stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator<<(QDataStream &stream, const QSizePolicy &policy) +{ + return stream << policy.data; +} + +/*! + \relates QSizePolicy + \since 4.2 + + Reads the size \a policy from the data stream \a stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator>>(QDataStream &stream, QSizePolicy &policy) +{ + return stream >> policy.data; +} +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qlayout.h b/src/gui/kernel/qlayout.h new file mode 100644 index 0000000000..5333150072 --- /dev/null +++ b/src/gui/kernel/qlayout.h @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLAYOUT_H +#define QLAYOUT_H + +#include <QtCore/qobject.h> +#include <QtGui/qlayoutitem.h> +#include <QtGui/qsizepolicy.h> +#include <QtCore/qrect.h> +#include <QtCore/qmargins.h> + +#include <limits.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QLayout; +class QSize; + +#ifdef QT3_SUPPORT +class Q_GUI_EXPORT QLayoutIterator +{ +public: + inline QT3_SUPPORT_CONSTRUCTOR QLayoutIterator(QLayout *i) : layout(i), index(0) {} + inline QLayoutIterator(const QLayoutIterator &i) + : layout(i.layout), index(i.index) {} + inline QLayoutIterator &operator=(const QLayoutIterator &i) { + layout = i.layout; + index = i.index; + return *this; + } + inline QT3_SUPPORT QLayoutItem *operator++(); + inline QT3_SUPPORT QLayoutItem *current(); + inline QT3_SUPPORT QLayoutItem *takeCurrent(); + inline QT3_SUPPORT void deleteCurrent(); + +private: + // hack to avoid deprecated warning + friend class QLayout; + inline QLayoutIterator(QLayout *i, bool) : layout(i), index(0) {} + QLayout *layout; + int index; +}; +#endif + +class QLayoutPrivate; + +class Q_GUI_EXPORT QLayout : public QObject, public QLayoutItem +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLayout) + + Q_ENUMS(SizeConstraint) + Q_PROPERTY(int margin READ margin WRITE setMargin) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + Q_PROPERTY(SizeConstraint sizeConstraint READ sizeConstraint WRITE setSizeConstraint) +public: + enum SizeConstraint { + SetDefaultConstraint, + SetNoConstraint, + SetMinimumSize, + SetFixedSize, + SetMaximumSize, + SetMinAndMaxSize +#if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN) + , Auto = SetDefaultConstraint, + FreeResize = SetNoConstraint, + Minimum = SetMinimumSize, + Fixed = SetFixedSize +#endif + }; + + QLayout(QWidget *parent); + QLayout(); + ~QLayout(); + + int margin() const; + int spacing() const; + + void setMargin(int); + void setSpacing(int); + + void setContentsMargins(int left, int top, int right, int bottom); + void setContentsMargins(const QMargins &margins); + void getContentsMargins(int *left, int *top, int *right, int *bottom) const; + QMargins contentsMargins() const; + QRect contentsRect() const; + + bool setAlignment(QWidget *w, Qt::Alignment alignment); + bool setAlignment(QLayout *l, Qt::Alignment alignment); +#ifdef Q_NO_USING_KEYWORD + inline void setAlignment(Qt::Alignment alignment) { QLayoutItem::setAlignment(alignment); } +#else + using QLayoutItem::setAlignment; +#endif + + void setSizeConstraint(SizeConstraint); + SizeConstraint sizeConstraint() const; +#ifdef QT3_SUPPORT + inline QT3_SUPPORT void setResizeMode(SizeConstraint s) {setSizeConstraint(s);} + inline QT3_SUPPORT SizeConstraint resizeMode() const {return sizeConstraint();} +#endif + void setMenuBar(QWidget *w); + QWidget *menuBar() const; + + QWidget *parentWidget() const; + + void invalidate(); + QRect geometry() const; + bool activate(); + void update(); + + void addWidget(QWidget *w); + virtual void addItem(QLayoutItem *) = 0; + + void removeWidget(QWidget *w); + void removeItem(QLayoutItem *); + + Qt::Orientations expandingDirections() const; + QSize minimumSize() const; + QSize maximumSize() const; + virtual void setGeometry(const QRect&); + virtual QLayoutItem *itemAt(int index) const = 0; + virtual QLayoutItem *takeAt(int index) = 0; + virtual int indexOf(QWidget *) const; + virtual int count() const = 0; + bool isEmpty() const; + + int totalHeightForWidth(int w) const; + QSize totalMinimumSize() const; + QSize totalMaximumSize() const; + QSize totalSizeHint() const; + QLayout *layout(); + + void setEnabled(bool); + bool isEnabled() const; + +#ifdef QT3_SUPPORT + QT3_SUPPORT void freeze(int w=0, int h=0); + QT3_SUPPORT bool isTopLevel() const; +#endif + + static QSize closestAcceptableSize(const QWidget *w, const QSize &s); + +protected: + void widgetEvent(QEvent *); + void childEvent(QChildEvent *e); + void addChildLayout(QLayout *l); + void addChildWidget(QWidget *w); +#ifdef QT3_SUPPORT + QT3_SUPPORT void deleteAllItems(); +#endif + + QRect alignmentRect(const QRect&) const; +protected: + QLayout(QLayoutPrivate &d, QLayout*, QWidget*); + +private: + Q_DISABLE_COPY(QLayout) + + static void activateRecursiveHelper(QLayoutItem *item); + + friend class QApplicationPrivate; + friend class QWidget; + +#ifdef QT3_SUPPORT +public: + QT3_SUPPORT_CONSTRUCTOR QLayout(QWidget *parent, int margin, int spacing = -1, + const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0); + QT3_SUPPORT_CONSTRUCTOR QLayout(int spacing, const char *name = 0); + inline QT3_SUPPORT QWidget *mainWidget() const { return parentWidget(); } + inline QT3_SUPPORT void remove(QWidget *w) { removeWidget(w); } + inline QT3_SUPPORT void add(QWidget *w) { addWidget(w); } + + QT3_SUPPORT void setAutoAdd(bool a); + QT3_SUPPORT bool autoAdd() const; + inline QT3_SUPPORT QLayoutIterator iterator() { return QLayoutIterator(this,true); } + + inline QT3_SUPPORT int defaultBorder() const { return spacing(); } +#endif +}; + +#ifdef QT3_SUPPORT +inline QLayoutItem *QLayoutIterator::operator++() { return layout->itemAt(++index); } +inline QLayoutItem *QLayoutIterator::current() { return layout->itemAt(index); } +inline QLayoutItem *QLayoutIterator::takeCurrent() { return layout->takeAt(index); } +inline void QLayoutIterator::deleteCurrent() { delete layout->takeAt(index); } +#endif + +//### support old includes +#if 1 //def QT3_SUPPORT +QT_BEGIN_INCLUDE_NAMESPACE +#include <QtGui/qboxlayout.h> +#include <QtGui/qgridlayout.h> +QT_END_INCLUDE_NAMESPACE +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLAYOUT_H diff --git a/src/gui/kernel/qlayout_p.h b/src/gui/kernel/qlayout_p.h new file mode 100644 index 0000000000..342333954c --- /dev/null +++ b/src/gui/kernel/qlayout_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLAYOUT_P_H +#define QLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qlayout*.cpp, and qabstractlayout.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#include "qstyle.h" +#include "qsizepolicy.h" + +QT_BEGIN_NAMESPACE + +class QWidgetItem; +class QSpacerItem; + +class Q_GUI_EXPORT QLayoutPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QLayout) + +public: + typedef QWidgetItem * (*QWidgetItemFactoryMethod)(const QLayout *layout, QWidget *widget); + typedef QSpacerItem * (*QSpacerItemFactoryMethod)(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy, QSizePolicy::Policy); + + QLayoutPrivate(); + + void getMargin(int *result, int userMargin, QStyle::PixelMetric pm) const; + void doResize(const QSize &); + void reparentChildWidgets(QWidget *mw); + + static QWidgetItem *createWidgetItem(const QLayout *layout, QWidget *widget); + static QSpacerItem *createSpacerItem(const QLayout *layout, int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum); + + static QWidgetItemFactoryMethod widgetItemFactoryMethod; + static QSpacerItemFactoryMethod spacerItemFactoryMethod; + + int insideSpacing; + int userLeftMargin; + int userTopMargin; + int userRightMargin; + int userBottomMargin; + uint topLevel : 1; + uint enabled : 1; + uint activated : 1; + uint autoNewChild : 1; + QLayout::SizeConstraint constraint; + QRect rect; + QWidget *menubar; +}; + +QT_END_NAMESPACE + +#endif // QLAYOUT_P_H diff --git a/src/gui/kernel/qlayoutengine.cpp b/src/gui/kernel/qlayoutengine.cpp new file mode 100644 index 0000000000..fdabd8ae1f --- /dev/null +++ b/src/gui/kernel/qlayoutengine.cpp @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlayout.h" +#include "private/qlayoutengine_p.h" + +#include "qvector.h" +#include "qwidget.h" + +#include <qlist.h> +#include <qalgorithms.h> + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +//#define QLAYOUT_EXTRA_DEBUG + +typedef qint64 Fixed64; +static inline Fixed64 toFixed(int i) { return (Fixed64)i * 256; } +static inline int fRound(Fixed64 i) { + return (i % 256 < 128) ? i / 256 : 1 + i / 256; +} + +/* + This is the main workhorse of the QGridLayout. It portions out + available space to the chain's children. + + The calculation is done in fixed point: "fixed" variables are + scaled by a factor of 256. + + If the layout runs "backwards" (i.e. RightToLeft or Up) the layout + is computed mirror-reversed, and it's the caller's responsibility + do reverse the values before use. + + chain contains input and output parameters describing the geometry. + count is the count of items in the chain; pos and space give the + interval (relative to parentWidget topLeft). +*/ +void qGeomCalc(QVector<QLayoutStruct> &chain, int start, int count, + int pos, int space, int spacer) +{ + int cHint = 0; + int cMin = 0; + int cMax = 0; + int sumStretch = 0; + int sumSpacing = 0; + + bool wannaGrow = false; // anyone who really wants to grow? + // bool canShrink = false; // anyone who could be persuaded to shrink? + + bool allEmptyNonstretch = true; + int pendingSpacing = -1; + int spacerCount = 0; + int i; + + for (i = start; i < start + count; i++) { + QLayoutStruct *data = &chain[i]; + + data->done = false; + cHint += data->smartSizeHint(); + cMin += data->minimumSize; + cMax += data->maximumSize; + sumStretch += data->stretch; + if (!data->empty) { + /* + Using pendingSpacing, we ensure that the spacing for the last + (non-empty) item is ignored. + */ + if (pendingSpacing >= 0) { + sumSpacing += pendingSpacing; + ++spacerCount; + } + pendingSpacing = data->effectiveSpacer(spacer); + } + wannaGrow = wannaGrow || data->expansive || data->stretch > 0; + allEmptyNonstretch = allEmptyNonstretch && !wannaGrow && data->empty; + } + + int extraspace = 0; + + if (space < cMin + sumSpacing) { + /* + Less space than minimumSize; take from the biggest first + */ + + int minSize = cMin + sumSpacing; + + // shrink the spacers proportionally + if (spacer >= 0) { + spacer = minSize > 0 ? spacer * space / minSize : 0; + sumSpacing = spacer * spacerCount; + } + + QList<int> list; + + for (i = start; i < start + count; i++) + list << chain.at(i).minimumSize; + + qSort(list); + + int space_left = space - sumSpacing; + + int sum = 0; + int idx = 0; + int space_used=0; + int current = 0; + while (idx < count && space_used < space_left) { + current = list.at(idx); + space_used = sum + current * (count - idx); + sum += current; + ++idx; + } + --idx; + int deficit = space_used - space_left; + + int items = count - idx; + /* + * If we truncate all items to "current", we would get "deficit" too many pixels. Therefore, we have to remove + * deficit/items from each item bigger than maxval. The actual value to remove is deficitPerItem + remainder/items + * "rest" is the accumulated error from using integer arithmetic. + */ + int deficitPerItem = deficit/items; + int remainder = deficit % items; + int maxval = current - deficitPerItem; + + int rest = 0; + for (i = start; i < start + count; i++) { + int maxv = maxval; + rest += remainder; + if (rest >= items) { + maxv--; + rest-=items; + } + QLayoutStruct *data = &chain[i]; + data->size = qMin(data->minimumSize, maxv); + data->done = true; + } + } else if (space < cHint + sumSpacing) { + /* + Less space than smartSizeHint(), but more than minimumSize. + Currently take space equally from each, as in Qt 2.x. + Commented-out lines will give more space to stretchier + items. + */ + int n = count; + int space_left = space - sumSpacing; + int overdraft = cHint - space_left; + + // first give to the fixed ones: + for (i = start; i < start + count; i++) { + QLayoutStruct *data = &chain[i]; + if (!data->done + && data->minimumSize >= data->smartSizeHint()) { + data->size = data->smartSizeHint(); + data->done = true; + space_left -= data->smartSizeHint(); + // sumStretch -= data->stretch; + n--; + } + } + bool finished = n == 0; + while (!finished) { + finished = true; + Fixed64 fp_over = toFixed(overdraft); + Fixed64 fp_w = 0; + + for (i = start; i < start+count; i++) { + QLayoutStruct *data = &chain[i]; + if (data->done) + continue; + // if (sumStretch <= 0) + fp_w += fp_over / n; + // else + // fp_w += (fp_over * data->stretch) / sumStretch; + int w = fRound(fp_w); + data->size = data->smartSizeHint() - w; + fp_w -= toFixed(w); // give the difference to the next + if (data->size < data->minimumSize) { + data->done = true; + data->size = data->minimumSize; + finished = false; + overdraft -= data->smartSizeHint() - data->minimumSize; + // sumStretch -= data->stretch; + n--; + break; + } + } + } + } else { // extra space + int n = count; + int space_left = space - sumSpacing; + // first give to the fixed ones, and handle non-expansiveness + for (i = start; i < start + count; i++) { + QLayoutStruct *data = &chain[i]; + if (!data->done + && (data->maximumSize <= data->smartSizeHint() + || (wannaGrow && !data->expansive && data->stretch == 0) + || (!allEmptyNonstretch && data->empty && + !data->expansive && data->stretch == 0))) { + data->size = data->smartSizeHint(); + data->done = true; + space_left -= data->size; + sumStretch -= data->stretch; + n--; + } + } + extraspace = space_left; + + /* + Do a trial distribution and calculate how much it is off. + If there are more deficit pixels than surplus pixels, give + the minimum size items what they need, and repeat. + Otherwise give to the maximum size items, and repeat. + + Paul Olav Tvete has a wonderful mathematical proof of the + correctness of this principle, but unfortunately this + comment is too small to contain it. + */ + int surplus, deficit; + do { + surplus = deficit = 0; + Fixed64 fp_space = toFixed(space_left); + Fixed64 fp_w = 0; + for (i = start; i < start + count; i++) { + QLayoutStruct *data = &chain[i]; + if (data->done) + continue; + extraspace = 0; + if (sumStretch <= 0) + fp_w += fp_space / n; + else + fp_w += (fp_space * data->stretch) / sumStretch; + int w = fRound(fp_w); + data->size = w; + fp_w -= toFixed(w); // give the difference to the next + if (w < data->smartSizeHint()) { + deficit += data->smartSizeHint() - w; + } else if (w > data->maximumSize) { + surplus += w - data->maximumSize; + } + } + if (deficit > 0 && surplus <= deficit) { + // give to the ones that have too little + for (i = start; i < start+count; i++) { + QLayoutStruct *data = &chain[i]; + if (!data->done && data->size < data->smartSizeHint()) { + data->size = data->smartSizeHint(); + data->done = true; + space_left -= data->smartSizeHint(); + sumStretch -= data->stretch; + n--; + } + } + } + if (surplus > 0 && surplus >= deficit) { + // take from the ones that have too much + for (i = start; i < start + count; i++) { + QLayoutStruct *data = &chain[i]; + if (!data->done && data->size > data->maximumSize) { + data->size = data->maximumSize; + data->done = true; + space_left -= data->maximumSize; + sumStretch -= data->stretch; + n--; + } + } + } + } while (n > 0 && surplus != deficit); + if (n == 0) + extraspace = space_left; + } + + /* + As a last resort, we distribute the unwanted space equally + among the spacers (counting the start and end of the chain). We + could, but don't, attempt a sub-pixel allocation of the extra + space. + */ + int extra = extraspace / (spacerCount + 2); + int p = pos + extra; + for (i = start; i < start+count; i++) { + QLayoutStruct *data = &chain[i]; + data->pos = p; + p += data->size; + if (!data->empty) + p += data->effectiveSpacer(spacer) + extra; + } + +#ifdef QLAYOUT_EXTRA_DEBUG + qDebug() << "qGeomCalc" << "start" << start << "count" << count << "pos" << pos + << "space" << space << "spacer" << spacer; + for (i = start; i < start + count; ++i) { + qDebug() << i << ':' << chain[i].minimumSize << chain[i].smartSizeHint() + << chain[i].maximumSize << "stretch" << chain[i].stretch + << "empty" << chain[i].empty << "expansive" << chain[i].expansive + << "spacing" << chain[i].spacing; + qDebug() << "result pos" << chain[i].pos << "size" << chain[i].size; + } +#endif +} + +Q_GUI_EXPORT QSize qSmartMinSize(const QSize &sizeHint, const QSize &minSizeHint, + const QSize &minSize, const QSize &maxSize, + const QSizePolicy &sizePolicy) +{ + QSize s(0, 0); + + if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) { + if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag) + s.setWidth(minSizeHint.width()); + else + s.setWidth(qMax(sizeHint.width(), minSizeHint.width())); + } + + if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) { + if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) { + s.setHeight(minSizeHint.height()); + } else { + s.setHeight(qMax(sizeHint.height(), minSizeHint.height())); + } + } + + s = s.boundedTo(maxSize); + if (minSize.width() > 0) + s.setWidth(minSize.width()); + if (minSize.height() > 0) + s.setHeight(minSize.height()); + + return s.expandedTo(QSize(0,0)); +} + +Q_GUI_EXPORT QSize qSmartMinSize(const QWidgetItem *i) +{ + QWidget *w = ((QWidgetItem *)i)->widget(); + return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(), + w->minimumSize(), w->maximumSize(), + w->sizePolicy()); +} + +Q_GUI_EXPORT QSize qSmartMinSize(const QWidget *w) +{ + return qSmartMinSize(w->sizeHint(), w->minimumSizeHint(), + w->minimumSize(), w->maximumSize(), + w->sizePolicy()); +} + +Q_GUI_EXPORT QSize qSmartMaxSize(const QSize &sizeHint, + const QSize &minSize, const QSize &maxSize, + const QSizePolicy &sizePolicy, Qt::Alignment align) +{ + if (align & Qt::AlignHorizontal_Mask && align & Qt::AlignVertical_Mask) + return QSize(QLAYOUTSIZE_MAX, QLAYOUTSIZE_MAX); + QSize s = maxSize; + QSize hint = sizeHint.expandedTo(minSize); + if (s.width() == QWIDGETSIZE_MAX && !(align & Qt::AlignHorizontal_Mask)) + if (!(sizePolicy.horizontalPolicy() & QSizePolicy::GrowFlag)) + s.setWidth(hint.width()); + + if (s.height() == QWIDGETSIZE_MAX && !(align & Qt::AlignVertical_Mask)) + if (!(sizePolicy.verticalPolicy() & QSizePolicy::GrowFlag)) + s.setHeight(hint.height()); + + if (align & Qt::AlignHorizontal_Mask) + s.setWidth(QLAYOUTSIZE_MAX); + if (align & Qt::AlignVertical_Mask) + s.setHeight(QLAYOUTSIZE_MAX); + return s; +} + +Q_GUI_EXPORT QSize qSmartMaxSize(const QWidgetItem *i, Qt::Alignment align) +{ + QWidget *w = ((QWidgetItem*)i)->widget(); + + return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()), w->minimumSize(), w->maximumSize(), + w->sizePolicy(), align); +} + +Q_GUI_EXPORT QSize qSmartMaxSize(const QWidget *w, Qt::Alignment align) +{ + return qSmartMaxSize(w->sizeHint().expandedTo(w->minimumSizeHint()), w->minimumSize(), w->maximumSize(), + w->sizePolicy(), align); +} + +Q_GUI_EXPORT int qSmartSpacing(const QLayout *layout, QStyle::PixelMetric pm) +{ + QObject *parent = layout->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast<QWidget *>(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast<QLayout *>(parent)->spacing(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qlayoutengine_p.h b/src/gui/kernel/qlayoutengine_p.h new file mode 100644 index 0000000000..da07f3bab1 --- /dev/null +++ b/src/gui/kernel/qlayoutengine_p.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLAYOUTENGINE_P_H +#define QLAYOUTENGINE_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 "QtGui/qlayoutitem.h" +#include "QtGui/qstyle.h" + +QT_BEGIN_NAMESPACE + +template <typename T> class QVector; + +struct QLayoutStruct +{ + inline void init(int stretchFactor = 0, int minSize = 0) { + stretch = stretchFactor; + minimumSize = sizeHint = minSize; + maximumSize = QLAYOUTSIZE_MAX; + expansive = false; + empty = true; + spacing = 0; + } + + int smartSizeHint() { + return (stretch > 0) ? minimumSize : sizeHint; + } + int effectiveSpacer(int uniformSpacer) const { + Q_ASSERT(uniformSpacer >= 0 || spacing >= 0); + return (uniformSpacer >= 0) ? uniformSpacer : spacing; + } + + // parameters + int stretch; + int sizeHint; + int maximumSize; + int minimumSize; + bool expansive; + bool empty; + int spacing; + + // temporary storage + bool done; + + // result + int pos; + int size; +}; + + +Q_GUI_EXPORT void qGeomCalc(QVector<QLayoutStruct> &chain, int start, int count, + int pos, int space, int spacer = -1); +Q_GUI_EXPORT QSize qSmartMinSize(const QSize &sizeHint, const QSize &minSizeHint, + const QSize &minSize, const QSize &maxSize, + const QSizePolicy &sizePolicy); +Q_GUI_EXPORT QSize qSmartMinSize(const QWidgetItem *i); +Q_GUI_EXPORT QSize qSmartMinSize(const QWidget *w); +Q_GUI_EXPORT QSize qSmartMaxSize(const QSize &sizeHint, + const QSize &minSize, const QSize &maxSize, + const QSizePolicy &sizePolicy, Qt::Alignment align = 0); +Q_GUI_EXPORT QSize qSmartMaxSize(const QWidgetItem *i, Qt::Alignment align = 0); +Q_GUI_EXPORT QSize qSmartMaxSize(const QWidget *w, Qt::Alignment align = 0); + +Q_GUI_EXPORT int qSmartSpacing(const QLayout *layout, QStyle::PixelMetric pm); + +/* + Modify total maximum (max), total expansion (exp), and total empty + when adding boxmax/boxexp. + + Expansive boxes win over non-expansive boxes. + Non-empty boxes win over empty boxes. +*/ +static inline void qMaxExpCalc(int & max, bool &exp, bool &empty, + int boxmax, bool boxexp, bool boxempty) +{ + if (exp) { + if (boxexp) + max = qMax(max, boxmax); + } else { + if (boxexp || (empty && (!boxempty || max == 0))) + max = boxmax; + else if (empty == boxempty) + max = qMin(max, boxmax); + } + exp = exp || boxexp; + empty = empty && boxempty; +} + +QT_END_NAMESPACE + +#endif // QLAYOUTENGINE_P_H diff --git a/src/gui/kernel/qlayoutitem.cpp b/src/gui/kernel/qlayoutitem.cpp new file mode 100644 index 0000000000..aeb96e9ef5 --- /dev/null +++ b/src/gui/kernel/qlayoutitem.cpp @@ -0,0 +1,834 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlayout.h" + +#include "qapplication.h" +#include "qlayoutengine_p.h" +#include "qmenubar.h" +#include "qtoolbar.h" +#include "qevent.h" +#include "qstyle.h" +#include "qvariant.h" +#include "qwidget_p.h" + +QT_BEGIN_NAMESPACE + +inline static QRect fromLayoutItemRect(QWidgetPrivate *priv, const QRect &rect) +{ + return rect.adjusted(priv->leftLayoutItemMargin, priv->topLayoutItemMargin, + -priv->rightLayoutItemMargin, -priv->bottomLayoutItemMargin); +} + +inline static QSize fromLayoutItemSize(QWidgetPrivate *priv, const QSize &size) +{ + return fromLayoutItemRect(priv, QRect(QPoint(0, 0), size)).size(); +} + +inline static QRect toLayoutItemRect(QWidgetPrivate *priv, const QRect &rect) +{ + return rect.adjusted(-priv->leftLayoutItemMargin, -priv->topLayoutItemMargin, + priv->rightLayoutItemMargin, priv->bottomLayoutItemMargin); +} + +inline static QSize toLayoutItemSize(QWidgetPrivate *priv, const QSize &size) +{ + return toLayoutItemRect(priv, QRect(QPoint(0, 0), size)).size(); +} + +/*! + Returns a QVariant storing this QSizePolicy. +*/ +QSizePolicy::operator QVariant() const +{ + return QVariant(QVariant::SizePolicy, this); +} + +/*! + \class QLayoutItem + \brief The QLayoutItem class provides an abstract item that a + QLayout manipulates. + + \ingroup geomanagement + + This is used by custom layouts. + + Pure virtual functions are provided to return information about + the layout, including, sizeHint(), minimumSize(), maximumSize() + and expanding(). + + The layout's geometry can be set and retrieved with setGeometry() + and geometry(), and its alignment with setAlignment() and + alignment(). + + isEmpty() returns whether the layout item is empty. If the + concrete item is a QWidget, it can be retrieved using widget(). + Similarly for layout() and spacerItem(). + + Some layouts have width and height interdependencies. These can + be expressed using hasHeightForWidth(), heightForWidth(), and + minimumHeightForWidth(). For more explanation see the \e{Qt + Quarterly} article + \l{http://qt.nokia.com/doc/qq/qq04-height-for-width.html}{Trading + Height for Width}. + + \sa QLayout +*/ + +/*! + \class QSpacerItem + \ingroup geomanagement + \brief The QSpacerItem class provides blank space in a layout. + + Normally, you don't need to use this class directly. Qt's + built-in layout managers provide the following functions for + manipulating empty space in layouts: + + \table + \header \o Class + \o Functions + \row \o QHBoxLayout + \o \l{QBoxLayout::addSpacing()}{addSpacing()}, + \l{QBoxLayout::addStretch()}{addStretch()}, + \l{QBoxLayout::insertSpacing()}{insertSpacing()}, + \l{QBoxLayout::insertStretch()}{insertStretch()} + \row \o QGridLayout + \o \l{QGridLayout::setRowMinimumHeight()}{setRowMinimumHeight()}, + \l{QGridLayout::setRowStretch()}{setRowStretch()}, + \l{QGridLayout::setColumnMinimumWidth()}{setColumnMinimumWidth()}, + \l{QGridLayout::setColumnStretch()}{setColumnStretch()} + \endtable + + \sa QLayout, QWidgetItem, QLayoutItem::spacerItem() +*/ + +/*! + \class QWidgetItem + \ingroup geomanagement + \brief The QWidgetItem class is a layout item that represents a widget. + + Normally, you don't need to use this class directly. Qt's + built-in layout managers provide the following functions for + manipulating widgets in layouts: + + \table + \header \o Class + \o Functions + \row \o QBoxLayout + \o \l{QBoxLayout::addWidget()}{addWidget()}, + \l{QBoxLayout::insertWidget()}{insertWidget()}, + \l{QBoxLayout::setStretchFactor()}{setStretchFactor()} + \row \o QGridLayout + \o \l{QGridLayout::addWidget()}{addWidget()} + \row \o QStackedLayout + \o \l{QStackedLayout::addWidget()}{addWidget()}, + \l{QStackedLayout::insertWidget()}{insertWidget()}, + \l{QStackedLayout::currentWidget()}{currentWidget()}, + \l{QStackedLayout::setCurrentWidget()}{setCurrentWidget()}, + \l{QStackedLayout::widget()}{widget()} + \endtable + + \sa QLayout, QSpacerItem, QLayoutItem::widget() +*/ + +/*! + \fn QLayoutItem::QLayoutItem(Qt::Alignment alignment) + + Constructs a layout item with an \a alignment. + Not all subclasses support alignment. +*/ + +/*! + \fn Qt::Alignment QLayoutItem::alignment() const + + Returns the alignment of this item. +*/ + +/*! + Sets the alignment of this item to \a alignment. + + \bold{Note:} Item alignment is only supported by QLayoutItem subclasses + where it would have a visual effect. Except for QSpacerItem, which provides + blank space for layouts, all public Qt classes that inherit QLayoutItem + support item alignment. +*/ +void QLayoutItem::setAlignment(Qt::Alignment alignment) +{ + align = alignment; +} + +/*! + \fn QSize QLayoutItem::maximumSize() const + + Implemented in subclasses to return the maximum size of this item. +*/ + +/*! + \fn QSize QLayoutItem::minimumSize() const + + Implemented in subclasses to return the minimum size of this item. +*/ + +/*! + \fn QSize QLayoutItem::sizeHint() const + + Implemented in subclasses to return the preferred size of this item. +*/ + +/*! + \fn Qt::Orientations QLayoutItem::expandingDirections() const + + Returns whether this layout item can make use of more space than + sizeHint(). A value of Qt::Vertical or Qt::Horizontal means that + it wants to grow in only one dimension, whereas Qt::Vertical | + Qt::Horizontal means that it wants to grow in both dimensions. +*/ + +/*! + \fn void QLayoutItem::setGeometry(const QRect &r) + + Implemented in subclasses to set this item's geometry to \a r. + + \sa geometry() +*/ + +/*! + \fn QRect QLayoutItem::geometry() const + + Returns the rectangle covered by this layout item. + + \sa setGeometry() +*/ + +/*! + \fn virtual bool QLayoutItem::isEmpty() const + + Implemented in subclasses to return whether this item is empty, + i.e. whether it contains any widgets. +*/ + +/*! + \fn QSpacerItem::QSpacerItem(int w, int h, QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy) + + Constructs a spacer item with preferred width \a w, preferred + height \a h, horizontal size policy \a hPolicy and vertical size + policy \a vPolicy. + + The default values provide a gap that is able to stretch if + nothing else wants the space. +*/ + +/*! + Changes this spacer item to have preferred width \a w, preferred + height \a h, horizontal size policy \a hPolicy and vertical size + policy \a vPolicy. + + The default values provide a gap that is able to stretch if + nothing else wants the space. + + Note that if changeSize() is called after the spacer item has been added + to a layout, it is necessary to invalidate the layout in order for the + spacer item's new size to take effect. + + \sa QSpacerItem::invalidate() +*/ +void QSpacerItem::changeSize(int w, int h, QSizePolicy::Policy hPolicy, + QSizePolicy::Policy vPolicy) +{ + width = w; + height = h; + sizeP = QSizePolicy(hPolicy, vPolicy); +} + +/*! + \fn QWidgetItem::QWidgetItem(QWidget *widget) + + Creates an item containing the given \a widget. +*/ + +/*! + Destroys the QLayoutItem. +*/ +QLayoutItem::~QLayoutItem() +{ +} + +/*! + Invalidates any cached information in this layout item. +*/ +void QLayoutItem::invalidate() +{ +} + +/*! + If this item is a QLayout, it is returned as a QLayout; otherwise + 0 is returned. This function provides type-safe casting. +*/ +QLayout * QLayoutItem::layout() +{ + return 0; +} + +/*! + If this item is a QSpacerItem, it is returned as a QSpacerItem; + otherwise 0 is returned. This function provides type-safe casting. +*/ +QSpacerItem * QLayoutItem::spacerItem() +{ + return 0; +} + +/*! + \reimp +*/ +QLayout * QLayout::layout() +{ + return this; +} + +/*! + Returns a pointer to this object. +*/ +QSpacerItem * QSpacerItem::spacerItem() +{ + return this; +} + +/*! + If this item is a QWidget, it is returned as a QWidget; otherwise + 0 is returned. This function provides type-safe casting. +*/ +QWidget * QLayoutItem::widget() +{ + return 0; +} + +/*! + Returns the widget managed by this item. +*/ +QWidget *QWidgetItem::widget() +{ + return wid; +} + +/*! + Returns true if this layout's preferred height depends on its + width; otherwise returns false. The default implementation returns + false. + + Reimplement this function in layout managers that support height + for width. + + \sa heightForWidth(), QWidget::heightForWidth() +*/ +bool QLayoutItem::hasHeightForWidth() const +{ + return false; +} + +/*! + Returns the minimum height this widget needs for the given width, + \a w. The default implementation simply returns heightForWidth(\a + w). +*/ +int QLayoutItem::minimumHeightForWidth(int w) const +{ + return heightForWidth(w); +} + + +/*! + Returns the preferred height for this layout item, given the width + \a w. + + The default implementation returns -1, indicating that the + preferred height is independent of the width of the item. Using + the function hasHeightForWidth() will typically be much faster + than calling this function and testing for -1. + + Reimplement this function in layout managers that support height + for width. A typical implementation will look like this: + \snippet doc/src/snippets/code/src_gui_kernel_qlayoutitem.cpp 0 + + Caching is strongly recommended; without it layout will take + exponential time. + + \sa hasHeightForWidth() +*/ +int QLayoutItem::heightForWidth(int /* w */) const +{ + return -1; +} + +/*! + Returns the control type(s) for the layout item. For a + QWidgetItem, the control type comes from the widget's size + policy; for a QLayoutItem, the control types is derived from the + layout's contents. + + \sa QSizePolicy::controlType() +*/ +QSizePolicy::ControlTypes QLayoutItem::controlTypes() const +{ + // ### Qt 5: This function should probably be virtual instead + if (const QWidget *widget = const_cast<QLayoutItem*>(this)->widget()) { + return widget->sizePolicy().controlType(); + } else if (const QLayout *layout = const_cast<QLayoutItem*>(this)->layout()) { + if (layout->count() == 0) + return QSizePolicy::DefaultType; + QSizePolicy::ControlTypes types; + for (int i = layout->count() - 1; i >= 0; --i) + types |= layout->itemAt(i)->controlTypes(); + return types; + } + return QSizePolicy::DefaultType; +} + +/*! + \reimp +*/ +void QSpacerItem::setGeometry(const QRect &r) +{ + rect = r; +} + +/*! + \reimp +*/ +void QWidgetItem::setGeometry(const QRect &rect) +{ + if (isEmpty()) + return; + + QRect r = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? fromLayoutItemRect(wid->d_func(), rect) + : rect; + const QSize widgetRectSurplus = r.size() - rect.size(); + + /* + For historical reasons, this code is done using widget rect + coordinates, not layout item rect coordinates. However, + QWidgetItem's sizeHint(), maximumSize(), and heightForWidth() + all work in terms of layout item rect coordinates, so we have to + add or subtract widgetRectSurplus here and there. The code could + be much simpler if we did everything using layout item rect + coordinates and did the conversion right before the call to + QWidget::setGeometry(). + */ + + QSize s = r.size().boundedTo(maximumSize() + widgetRectSurplus); + int x = r.x(); + int y = r.y(); + if (align & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)) { + QSize pref(sizeHint()); + QSizePolicy sp = wid->sizePolicy(); + if (sp.horizontalPolicy() == QSizePolicy::Ignored) + pref.setWidth(wid->sizeHint().expandedTo(wid->minimumSize()).width()); + if (sp.verticalPolicy() == QSizePolicy::Ignored) + pref.setHeight(wid->sizeHint().expandedTo(wid->minimumSize()).height()); + pref += widgetRectSurplus; + if (align & Qt::AlignHorizontal_Mask) + s.setWidth(qMin(s.width(), pref.width())); + if (align & Qt::AlignVertical_Mask) { + if (hasHeightForWidth()) + s.setHeight(qMin(s.height(), + heightForWidth(s.width() - widgetRectSurplus.width()) + + widgetRectSurplus.height())); + else + s.setHeight(qMin(s.height(), pref.height())); + } + } + Qt::Alignment alignHoriz = QStyle::visualAlignment(wid->layoutDirection(), align); + if (alignHoriz & Qt::AlignRight) + x = x + (r.width() - s.width()); + else if (!(alignHoriz & Qt::AlignLeft)) + x = x + (r.width() - s.width()) / 2; + + if (align & Qt::AlignBottom) + y = y + (r.height() - s.height()); + else if (!(align & Qt::AlignTop)) + y = y + (r.height() - s.height()) / 2; + + wid->setGeometry(x, y, s.width(), s.height()); +} + +/*! + \reimp +*/ +QRect QSpacerItem::geometry() const +{ + return rect; +} + +/*! + \reimp +*/ +QRect QWidgetItem::geometry() const +{ + return !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? toLayoutItemRect(wid->d_func(), wid->geometry()) + : wid->geometry(); +} + + +/*! + \reimp +*/ +bool QWidgetItem::hasHeightForWidth() const +{ + if (isEmpty()) + return false; + return wid->d_func()->hasHeightForWidth(); +} + +/*! + \reimp +*/ +int QWidgetItem::heightForWidth(int w) const +{ + if (isEmpty()) + return -1; + + w = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? fromLayoutItemSize(wid->d_func(), QSize(w, 0)).width() + : w; + + int hfw; + if (wid->layout()) + hfw = wid->layout()->totalHeightForWidth(w); + else + hfw = wid->heightForWidth(w); + + if (hfw > wid->maximumHeight()) + hfw = wid->maximumHeight(); + if (hfw < wid->minimumHeight()) + hfw = wid->minimumHeight(); + + hfw = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? toLayoutItemSize(wid->d_func(), QSize(0, hfw)).height() + : hfw; + + if (hfw < 0) + hfw = 0; + return hfw; +} + +/*! + \reimp +*/ +Qt::Orientations QSpacerItem::expandingDirections() const +{ + return sizeP.expandingDirections(); +} + +/*! + \reimp +*/ +Qt::Orientations QWidgetItem::expandingDirections() const +{ + if (isEmpty()) + return Qt::Orientations(0); + + Qt::Orientations e = wid->sizePolicy().expandingDirections(); + /* + ### Qt 4.0: + If the layout is expanding, we make the widget expanding, even if + its own size policy isn't expanding. This behavior should be + reconsidered. + */ + if (wid->layout()) { + if (wid->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag + && (wid->layout()->expandingDirections() & Qt::Horizontal)) + e |= Qt::Horizontal; + if (wid->sizePolicy().verticalPolicy() & QSizePolicy::GrowFlag + && (wid->layout()->expandingDirections() & Qt::Vertical)) + e |= Qt::Vertical; + } + + if (align & Qt::AlignHorizontal_Mask) + e &= ~Qt::Horizontal; + if (align & Qt::AlignVertical_Mask) + e &= ~Qt::Vertical; + return e; +} + +/*! + \reimp +*/ +QSize QSpacerItem::minimumSize() const +{ + return QSize(sizeP.horizontalPolicy() & QSizePolicy::ShrinkFlag ? 0 : width, + sizeP.verticalPolicy() & QSizePolicy::ShrinkFlag ? 0 : height); +} + +/*! + \reimp +*/ +QSize QWidgetItem::minimumSize() const +{ + if (isEmpty()) + return QSize(0, 0); + return !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? toLayoutItemSize(wid->d_func(), qSmartMinSize(this)) + : qSmartMinSize(this); +} + +/*! + \reimp +*/ +QSize QSpacerItem::maximumSize() const +{ + return QSize(sizeP.horizontalPolicy() & QSizePolicy::GrowFlag ? QLAYOUTSIZE_MAX : width, + sizeP.verticalPolicy() & QSizePolicy::GrowFlag ? QLAYOUTSIZE_MAX : height); +} + +/*! + \reimp +*/ +QSize QWidgetItem::maximumSize() const +{ + if (isEmpty()) { + return QSize(0, 0); + } else { + return !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? toLayoutItemSize(wid->d_func(), qSmartMaxSize(this, align)) + : qSmartMaxSize(this, align); + } +} + +/*! + \reimp +*/ +QSize QSpacerItem::sizeHint() const +{ + return QSize(width, height); +} + +/*! + \reimp +*/ +QSize QWidgetItem::sizeHint() const +{ + QSize s(0, 0); + if (!isEmpty()) { + s = wid->sizeHint().expandedTo(wid->minimumSizeHint()); + s = s.boundedTo(wid->maximumSize()) + .expandedTo(wid->minimumSize()); + s = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect) + ? toLayoutItemSize(wid->d_func(), s) + : s; + + if (wid->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored) + s.setWidth(0); + if (wid->sizePolicy().verticalPolicy() == QSizePolicy::Ignored) + s.setHeight(0); + } + return s; +} + +/*! + Returns true. +*/ +bool QSpacerItem::isEmpty() const +{ + return true; +} + +/*! + Returns true if the widget is hidden; otherwise returns false. + + \sa QWidget::isHidden() +*/ +bool QWidgetItem::isEmpty() const +{ + return wid->isHidden() || wid->isWindow(); +} + +/*! + \class QWidgetItemV2 + \internal +*/ + +inline bool QWidgetItemV2::useSizeCache() const +{ + return wid->d_func()->widgetItem == this; +} + +void QWidgetItemV2::updateCacheIfNecessary() const +{ + if (q_cachedMinimumSize.width() != Dirty) + return; + + const QSize sizeHint(wid->sizeHint()); + const QSize minimumSizeHint(wid->minimumSizeHint()); + const QSize minimumSize(wid->minimumSize()); + const QSize maximumSize(wid->maximumSize()); + const QSizePolicy sizePolicy(wid->sizePolicy()); + const QSize expandedSizeHint(sizeHint.expandedTo(minimumSizeHint)); + + const QSize smartMinSize(qSmartMinSize(sizeHint, minimumSizeHint, minimumSize, maximumSize, sizePolicy)); + const QSize smartMaxSize(qSmartMaxSize(expandedSizeHint, minimumSize, maximumSize, sizePolicy, align)); + + const bool useLayoutItemRect = !wid->testAttribute(Qt::WA_LayoutUsesWidgetRect); + + q_cachedMinimumSize = useLayoutItemRect + ? toLayoutItemSize(wid->d_func(), smartMinSize) + : smartMinSize; + + q_cachedSizeHint = expandedSizeHint; + q_cachedSizeHint = q_cachedSizeHint.boundedTo(maximumSize) + .expandedTo(minimumSize); + q_cachedSizeHint = useLayoutItemRect + ? toLayoutItemSize(wid->d_func(), q_cachedSizeHint) + : q_cachedSizeHint; + + if (wid->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored) + q_cachedSizeHint.setWidth(0); + if (wid->sizePolicy().verticalPolicy() == QSizePolicy::Ignored) + q_cachedSizeHint.setHeight(0); + + q_cachedMaximumSize = useLayoutItemRect + ? toLayoutItemSize(wid->d_func(), smartMaxSize) + : smartMaxSize; +} + +QWidgetItemV2::QWidgetItemV2(QWidget *widget) + : QWidgetItem(widget), + q_cachedMinimumSize(Dirty, Dirty), + q_cachedSizeHint(Dirty, Dirty), + q_cachedMaximumSize(Dirty, Dirty), + q_firstCachedHfw(0), + q_hfwCacheSize(0), + d(0) +{ + QWidgetPrivate *wd = wid->d_func(); + if (!wd->widgetItem) + wd->widgetItem = this; +} + +QWidgetItemV2::~QWidgetItemV2() +{ + if (wid) { + QWidgetPrivate *wd = wid->d_func(); + if (wd->widgetItem == this) + wd->widgetItem = 0; + } +} + +QSize QWidgetItemV2::sizeHint() const +{ + if (isEmpty()) + return QSize(0, 0); + + if (useSizeCache()) { + updateCacheIfNecessary(); + return q_cachedSizeHint; + } else { + return QWidgetItem::sizeHint(); + } +} + +QSize QWidgetItemV2::minimumSize() const +{ + if (isEmpty()) + return QSize(0, 0); + + if (useSizeCache()) { + updateCacheIfNecessary(); + return q_cachedMinimumSize; + } else { + return QWidgetItem::minimumSize(); + } +} + +QSize QWidgetItemV2::maximumSize() const +{ + if (isEmpty()) + return QSize(0, 0); + + if (useSizeCache()) { + updateCacheIfNecessary(); + return q_cachedMaximumSize; + } else { + return QWidgetItem::maximumSize(); + } +} + +/* + The height-for-width cache is organized as a circular buffer. The entries + + q_hfwCachedHfws[q_firstCachedHfw], + ..., + q_hfwCachedHfws[(q_firstCachedHfw + q_hfwCacheSize - 1) % HfwCacheMaxSize] + + contain the last cached values. When the cache is full, the first entry to + be erased is the entry before q_hfwCachedHfws[q_firstCachedHfw]. When + values are looked up, we try to move q_firstCachedHfw to point to that new + entry (unless the cache is not full, in which case it would leave the cache + in a broken state), so that the most recently used entry is also the last + to be erased. +*/ + +int QWidgetItemV2::heightForWidth(int width) const +{ + if (isEmpty()) + return -1; + + for (int i = 0; i < q_hfwCacheSize; ++i) { + int offset = q_firstCachedHfw + i; + const QSize &size = q_cachedHfws[offset % HfwCacheMaxSize]; + if (size.width() == width) { + if (q_hfwCacheSize == HfwCacheMaxSize) + q_firstCachedHfw = offset; + return size.height(); + } + } + + if (q_hfwCacheSize < HfwCacheMaxSize) + ++q_hfwCacheSize; + q_firstCachedHfw = (q_firstCachedHfw + HfwCacheMaxSize - 1) % HfwCacheMaxSize; + + int height = QWidgetItem::heightForWidth(width); + q_cachedHfws[q_firstCachedHfw] = QSize(width, height); + return height; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qlayoutitem.h b/src/gui/kernel/qlayoutitem.h new file mode 100644 index 0000000000..a75011f3ff --- /dev/null +++ b/src/gui/kernel/qlayoutitem.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLAYOUTITEM_H +#define QLAYOUTITEM_H + +#include <QtGui/qsizepolicy.h> +#include <QtCore/qrect.h> + +#include <limits.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +static const int QLAYOUTSIZE_MAX = INT_MAX/256/16; + +class QLayout; +class QLayoutItem; +class QSpacerItem; +class QWidget; +class QSize; + +class Q_GUI_EXPORT QLayoutItem +{ +public: + inline explicit QLayoutItem(Qt::Alignment alignment = 0); + virtual ~QLayoutItem(); + virtual QSize sizeHint() const = 0; + virtual QSize minimumSize() const = 0; + virtual QSize maximumSize() const = 0; + virtual Qt::Orientations expandingDirections() const = 0; + virtual void setGeometry(const QRect&) = 0; + virtual QRect geometry() const = 0; + virtual bool isEmpty() const = 0; + virtual bool hasHeightForWidth() const; + virtual int heightForWidth(int) const; + virtual int minimumHeightForWidth(int) const; + virtual void invalidate(); + + virtual QWidget *widget(); + virtual QLayout *layout(); + virtual QSpacerItem *spacerItem(); + + Qt::Alignment alignment() const { return align; } + void setAlignment(Qt::Alignment a); + QSizePolicy::ControlTypes controlTypes() const; + +protected: + Qt::Alignment align; +}; + +inline QLayoutItem::QLayoutItem(Qt::Alignment aalignment) + : align(aalignment) { } + +class Q_GUI_EXPORT QSpacerItem : public QLayoutItem +{ +public: + QSpacerItem(int w, int h, + QSizePolicy::Policy hData = QSizePolicy::Minimum, + QSizePolicy::Policy vData = QSizePolicy::Minimum) + : width(w), height(h), sizeP(hData, vData) { } + void changeSize(int w, int h, + QSizePolicy::Policy hData = QSizePolicy::Minimum, + QSizePolicy::Policy vData = QSizePolicy::Minimum); + QSize sizeHint() const; + QSize minimumSize() const; + QSize maximumSize() const; + Qt::Orientations expandingDirections() const; + bool isEmpty() const; + void setGeometry(const QRect&); + QRect geometry() const; + QSpacerItem *spacerItem(); + +private: + int width; + int height; + QSizePolicy sizeP; + QRect rect; +}; + +class Q_GUI_EXPORT QWidgetItem : public QLayoutItem +{ + Q_DISABLE_COPY(QWidgetItem) + +public: + explicit QWidgetItem(QWidget *w) : wid(w) { } + QSize sizeHint() const; + QSize minimumSize() const; + QSize maximumSize() const; + Qt::Orientations expandingDirections() const; + bool isEmpty() const; + void setGeometry(const QRect&); + QRect geometry() const; + virtual QWidget *widget(); + + bool hasHeightForWidth() const; + int heightForWidth(int) const; + +protected: + QWidget *wid; +}; + +class Q_GUI_EXPORT QWidgetItemV2 : public QWidgetItem +{ +public: + explicit QWidgetItemV2(QWidget *widget); + ~QWidgetItemV2(); + + QSize sizeHint() const; + QSize minimumSize() const; + QSize maximumSize() const; + int heightForWidth(int width) const; + +private: + enum { Dirty = -123, HfwCacheMaxSize = 3 }; + + inline bool useSizeCache() const; + void updateCacheIfNecessary() const; + inline void invalidateSizeCache() { + q_cachedMinimumSize.setWidth(Dirty); + q_hfwCacheSize = 0; + } + + mutable QSize q_cachedMinimumSize; + mutable QSize q_cachedSizeHint; + mutable QSize q_cachedMaximumSize; + mutable QSize q_cachedHfws[HfwCacheMaxSize]; + mutable short q_firstCachedHfw; + mutable short q_hfwCacheSize; + void *d; + + friend class QWidgetPrivate; + + Q_DISABLE_COPY(QWidgetItemV2) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLAYOUTITEM_H diff --git a/src/gui/kernel/qmacdefines_mac.h b/src/gui/kernel/qmacdefines_mac.h new file mode 100644 index 0000000000..d6ccb93593 --- /dev/null +++ b/src/gui/kernel/qmacdefines_mac.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +/* + * qmacdefines_mac_p.h + * All the defines you'll ever need for Qt/Mac :-) + */ + +/* This is just many defines. Therefore it doesn't need things like: +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +QT_END_NAMESPACE + +QT_END_HEADER + +Yes, it is an informative comment ;-) +*/ + +#include <QtCore/qglobal.h> + +#ifdef qDebug +# define old_qDebug qDebug +# undef qDebug +#endif + +#ifdef __LP64__ +typedef signed int OSStatus; +#else +typedef signed long OSStatus; +#endif + +#ifdef __OBJC__ +# ifdef slots +# define old_slots slots +# undef slots +# endif +#include <Cocoa/Cocoa.h> +# ifdef old_slots +# undef slots +# define slots +# undef old_slots +# endif +#endif +#ifdef QT_MAC_USE_COCOA + typedef struct OpaqueEventHandlerCallRef * EventHandlerCallRef; + typedef struct OpaqueEventRef * EventRef; + typedef struct OpaqueMenuRef * MenuRef; + typedef struct OpaquePasteboardRef* PasteboardRef; + typedef struct OpaqueRgnHandle * RgnHandle; + typedef const struct __HIShape *HIShapeRef; + typedef struct __HIShape *HIMutableShapeRef; + typedef struct CGRect CGRect; + typedef struct CGImage *CGImageRef; + typedef struct CGContext *CGContextRef; + typedef struct GDevice * GDPtr; + typedef GDPtr * GDHandle; + typedef struct OpaqueIconRef * IconRef; +# ifdef __OBJC__ + typedef NSWindow* OSWindowRef; + typedef NSView *OSViewRef; + typedef NSMenu *OSMenuRef; + typedef NSEvent *OSEventRef; +# else + typedef void *OSWindowRef; + typedef void *OSViewRef; + typedef void *OSMenuRef; + typedef void *OSEventRef; +# endif +#else // Carbon + typedef struct OpaqueEventHandlerCallRef * EventHandlerCallRef; + typedef struct OpaqueEventRef * EventRef; + typedef struct OpaqueMenuRef * MenuRef; + typedef struct OpaquePasteboardRef* PasteboardRef; + typedef struct OpaqueRgnHandle * RgnHandle; + typedef const struct __HIShape *HIShapeRef; + typedef struct __HIShape *HIMutableShapeRef; + typedef struct CGRect CGRect; + typedef struct CGImage *CGImageRef; + typedef struct CGContext *CGContextRef; + typedef struct GDevice * GDPtr; + typedef GDPtr * GDHandle; + typedef struct OpaqueIconRef * IconRef; + typedef struct OpaqueWindowPtr * WindowRef; + typedef struct OpaqueControlRef * HIViewRef; + typedef WindowRef OSWindowRef; + typedef HIViewRef OSViewRef; + typedef MenuRef OSMenuRef; + typedef EventRef OSEventRef; +#endif // QT_MAC_USE_COCOA + +typedef PasteboardRef OSPasteboardRef; +typedef struct AEDesc AEDescList; +typedef AEDescList AERecord; +typedef AERecord AppleEvent; + +#ifdef check +#undef check +#endif + +#ifdef old_qDebug +# undef qDebug +# define qDebug QT_NO_QDEBUG_MACRO +# undef old_qDebug +#endif diff --git a/src/gui/kernel/qmacgesturerecognizer_mac.mm b/src/gui/kernel/qmacgesturerecognizer_mac.mm new file mode 100644 index 0000000000..6a4f0bb445 --- /dev/null +++ b/src/gui/kernel/qmacgesturerecognizer_mac.mm @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmacgesturerecognizer_mac_p.h" +#include "qgesture.h" +#include "qgesture_p.h" +#include "qevent.h" +#include "qevent_p.h" +#include "qwidget.h" +#include "qdebug.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +QMacSwipeGestureRecognizer::QMacSwipeGestureRecognizer() +{ +} + +QGesture *QMacSwipeGestureRecognizer::create(QObject * /*target*/) +{ + return new QSwipeGesture; +} + +QGestureRecognizer::Result +QMacSwipeGestureRecognizer::recognize(QGesture *gesture, QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::NativeGesture && obj->isWidgetType()) { + QNativeGestureEvent *ev = static_cast<QNativeGestureEvent*>(event); + switch (ev->gestureType) { + case QNativeGestureEvent::Swipe: { + QSwipeGesture *g = static_cast<QSwipeGesture *>(gesture); + g->setSwipeAngle(ev->angle); + g->setHotSpot(ev->position); + return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint; + break; } + default: + break; + } + } + + return QGestureRecognizer::Ignore; +} + +void QMacSwipeGestureRecognizer::reset(QGesture *gesture) +{ + QSwipeGesture *g = static_cast<QSwipeGesture *>(gesture); + g->setSwipeAngle(0); + QGestureRecognizer::reset(gesture); +} + +//////////////////////////////////////////////////////////////////////// + +QMacPinchGestureRecognizer::QMacPinchGestureRecognizer() +{ +} + +QGesture *QMacPinchGestureRecognizer::create(QObject * /*target*/) +{ + return new QPinchGesture; +} + +QGestureRecognizer::Result +QMacPinchGestureRecognizer::recognize(QGesture *gesture, QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::NativeGesture && obj->isWidgetType()) { + QPinchGesture *g = static_cast<QPinchGesture *>(gesture); + QNativeGestureEvent *ev = static_cast<QNativeGestureEvent*>(event); + switch(ev->gestureType) { + case QNativeGestureEvent::GestureBegin: + reset(gesture); + g->setStartCenterPoint(static_cast<QWidget*>(obj)->mapFromGlobal(ev->position)); + g->setCenterPoint(g->startCenterPoint()); + g->setChangeFlags(QPinchGesture::CenterPointChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::MayBeGesture | QGestureRecognizer::ConsumeEventHint; + case QNativeGestureEvent::Rotate: { + g->setLastScaleFactor(g->scaleFactor()); + g->setLastRotationAngle(g->rotationAngle()); + g->setRotationAngle(g->rotationAngle() + ev->percentage); + g->setChangeFlags(QPinchGesture::RotationAngleChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + } + case QNativeGestureEvent::Zoom: + g->setLastScaleFactor(g->scaleFactor()); + g->setLastRotationAngle(g->rotationAngle()); + g->setScaleFactor(g->scaleFactor() * (1 + ev->percentage)); + g->setChangeFlags(QPinchGesture::ScaleFactorChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + case QNativeGestureEvent::GestureEnd: + return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint; + default: + break; + } + } + + return QGestureRecognizer::Ignore; +} + +void QMacPinchGestureRecognizer::reset(QGesture *gesture) +{ + QPinchGesture *g = static_cast<QPinchGesture *>(gesture); + g->setChangeFlags(0); + g->setTotalChangeFlags(0); + g->setScaleFactor(1.0f); + g->setTotalScaleFactor(1.0f); + g->setLastScaleFactor(1.0f); + g->setRotationAngle(0.0f); + g->setTotalRotationAngle(0.0f); + g->setLastRotationAngle(0.0f); + g->setCenterPoint(QPointF()); + g->setStartCenterPoint(QPointF()); + g->setLastCenterPoint(QPointF()); + QGestureRecognizer::reset(gesture); +} + +//////////////////////////////////////////////////////////////////////// + +#if defined(QT_MAC_USE_COCOA) + +QMacPanGestureRecognizer::QMacPanGestureRecognizer() : _panCanceled(true) +{ +} + +QGesture *QMacPanGestureRecognizer::create(QObject *target) +{ + if (!target) + return new QPanGesture; + + if (QWidget *w = qobject_cast<QWidget *>(target)) { + w->setAttribute(Qt::WA_AcceptTouchEvents); + w->setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + return new QPanGesture; + } + return 0; +} + +QGestureRecognizer::Result +QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *event) +{ + const int panBeginDelay = 300; + const int panBeginRadius = 3; + + QPanGesture *g = static_cast<QPanGesture *>(gesture); + + switch (event->type()) { + case QEvent::TouchBegin: { + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) { + reset(gesture); + _startPos = QCursor::pos(); + _panTimer.start(panBeginDelay, target); + _panCanceled = false; + return QGestureRecognizer::MayBeGesture; + } + break;} + case QEvent::TouchEnd: { + if (_panCanceled) + break; + + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) + return QGestureRecognizer::FinishGesture; + break;} + case QEvent::TouchUpdate: { + if (_panCanceled) + break; + + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) { + if (_panTimer.isActive()) { + // INVARIANT: Still in maybeGesture. Check if the user + // moved his finger so much that it makes sense to cancel the pan: + const QPointF p = QCursor::pos(); + if ((p - _startPos).manhattanLength() > panBeginRadius) { + _panCanceled = true; + _panTimer.stop(); + return QGestureRecognizer::CancelGesture; + } + } else { + const QPointF p = QCursor::pos(); + const QPointF posOffset = p - _startPos; + g->setLastOffset(g->offset()); + g->setOffset(QPointF(posOffset.x(), posOffset.y())); + g->setHotSpot(_startPos); + return QGestureRecognizer::TriggerGesture; + } + } else if (_panTimer.isActive()) { + // I only want to cancel the pan if the user is pressing + // more than one finger, and the pan hasn't started yet: + _panCanceled = true; + _panTimer.stop(); + return QGestureRecognizer::CancelGesture; + } + break;} + case QEvent::Timer: { + QTimerEvent *ev = static_cast<QTimerEvent *>(event); + if (ev->timerId() == _panTimer.timerId()) { + _panTimer.stop(); + if (_panCanceled) + break; + // Begin new pan session! + _startPos = QCursor::pos(); + g->setHotSpot(_startPos); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + } + break; } + default: + break; + } + + return QGestureRecognizer::Ignore; +} + +void QMacPanGestureRecognizer::reset(QGesture *gesture) +{ + QPanGesture *g = static_cast<QPanGesture *>(gesture); + _startPos = QPointF(); + _panCanceled = true; + g->setOffset(QPointF(0, 0)); + g->setLastOffset(QPointF(0, 0)); + g->setAcceleration(qreal(1)); + QGestureRecognizer::reset(gesture); +} +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/kernel/qmacgesturerecognizer_mac_p.h b/src/gui/kernel/qmacgesturerecognizer_mac_p.h new file mode 100644 index 0000000000..465f6a2ac8 --- /dev/null +++ b/src/gui/kernel/qmacgesturerecognizer_mac_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACSWIPEGESTURERECOGNIZER_MAC_P_H +#define QMACSWIPEGESTURERECOGNIZER_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtimer.h" +#include "qpoint.h" +#include "qgesturerecognizer.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QMacSwipeGestureRecognizer : public QGestureRecognizer +{ +public: + QMacSwipeGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event); + void reset(QGesture *gesture); +}; + +class QMacPinchGestureRecognizer : public QGestureRecognizer +{ +public: + QMacPinchGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event); + void reset(QGesture *gesture); +}; + +#if defined(QT_MAC_USE_COCOA) + +class QMacPanGestureRecognizer : public QObject, public QGestureRecognizer +{ +public: + QMacPanGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event); + void reset(QGesture *gesture); +private: + QPointF _startPos; + QBasicTimer _panTimer; + bool _panCanceled; +}; + +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QMACSWIPEGESTURERECOGNIZER_MAC_P_H diff --git a/src/gui/kernel/qmime.cpp b/src/gui/kernel/qmime.cpp new file mode 100644 index 0000000000..4e15ddf624 --- /dev/null +++ b/src/gui/kernel/qmime.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmime.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QMimeSource + \brief The QMimeSource class is an abstraction of objects that + provided formatted data of a certain MIME type. + + \obsolete + + The preferred approach to drag and drop is to use QDrag in + conjunction with QMimeData. See \l{Drag and Drop} for details. + + \sa QMimeData, QDrag +*/ + +/*! + Destroys the MIME source. +*/ +QMimeSource::~QMimeSource() +{ +} + +/*! + \fn const char *QMimeSource::format(int i) const + + Returns the (\a i - 1)-th supported MIME format, or 0. +*/ + +/*! + \fn QByteArray QMimeSource::encodedData(const char *format) const + + Returns the encoded data of this object in the specified MIME + \a format. +*/ + +/*! + Returns true if the object can provide the data in format \a + mimeType; otherwise returns false. + + If you inherit from QMimeSource, for consistency reasons it is + better to implement the more abstract canDecode() functions such + as QTextDrag::canDecode() and QImageDrag::canDecode(). +*/ +bool QMimeSource::provides(const char* mimeType) const +{ + const char* fmt; + for (int i=0; (fmt = format(i)); i++) { + if (!qstricmp(mimeType,fmt)) + return true; + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qmime.h b/src/gui/kernel/qmime.h new file mode 100644 index 0000000000..a791f68cf0 --- /dev/null +++ b/src/gui/kernel/qmime.h @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMIME_H +#define QMIME_H + +#include <QtCore/qmimedata.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QMimeSource +{ +public: + virtual ~QMimeSource(); + virtual const char* format(int n = 0) const = 0; + virtual bool provides(const char*) const; + virtual QByteArray encodedData(const char*) const = 0; +}; + + +#if defined(Q_WS_WIN) + +QT_BEGIN_INCLUDE_NAMESPACE +typedef struct tagFORMATETC FORMATETC; +typedef struct tagSTGMEDIUM STGMEDIUM; +struct IDataObject; + +#include <QtCore/qvariant.h> +QT_END_INCLUDE_NAMESPACE + +/* + Encapsulation of conversion between MIME and Windows CLIPFORMAT. + Not need on X11, as the underlying protocol uses the MIME standard + directly. +*/ + +class Q_GUI_EXPORT QWindowsMime +{ +public: + QWindowsMime(); + virtual ~QWindowsMime(); + + // for converting from Qt + virtual bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const = 0; + virtual bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const = 0; + virtual QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const = 0; + + // for converting to Qt + virtual bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const = 0; + virtual QVariant convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const = 0; + virtual QString mimeForFormat(const FORMATETC &formatetc) const = 0; + + static int registerMimeType(const QString &mime); + +private: + friend class QClipboardWatcher; + friend class QDragManager; + friend class QDropData; + friend class QOleDataObject; + + static QWindowsMime *converterToMime(const QString &mimeType, IDataObject *pDataObj); + static QStringList allMimesForFormats(IDataObject *pDataObj); + static QWindowsMime *converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData); + static QVector<FORMATETC> allFormatsForMime(const QMimeData *mimeData); +}; + +#endif +#if defined(Q_WS_MAC) + +/* + Encapsulation of conversion between MIME and Mac flavor. + Not needed on X11, as the underlying protocol uses the MIME standard + directly. +*/ + +class Q_GUI_EXPORT QMacMime { //Obsolete + char type; +public: + enum QMacMimeType { MIME_DND=0x01, MIME_CLIP=0x02, MIME_QT_CONVERTOR=0x04, MIME_ALL=MIME_DND|MIME_CLIP }; + explicit QMacMime(char) { } + virtual ~QMacMime() { } + + static void initialize() { } + + static QList<QMacMime*> all(QMacMimeType) { return QList<QMacMime*>(); } + static QMacMime *convertor(QMacMimeType, const QString &, int) { return 0; } + static QString flavorToMime(QMacMimeType, int) { return QString(); } + + virtual QString convertorName()=0; + virtual int countFlavors()=0; + virtual int flavor(int index)=0; + virtual bool canConvert(const QString &mime, int flav)=0; + virtual QString mimeFor(int flav)=0; + virtual int flavorFor(const QString &mime)=0; + virtual QVariant convertToMime(const QString &mime, QList<QByteArray> data, int flav)=0; + virtual QList<QByteArray> convertFromMime(const QString &mime, QVariant data, int flav)=0; +}; + +class Q_GUI_EXPORT QMacPasteboardMime { + char type; +public: + enum QMacPasteboardMimeType { MIME_DND=0x01, + MIME_CLIP=0x02, + MIME_QT_CONVERTOR=0x04, + MIME_QT3_CONVERTOR=0x08, + MIME_ALL=MIME_DND|MIME_CLIP + }; + explicit QMacPasteboardMime(char); + virtual ~QMacPasteboardMime(); + + static void initialize(); + + static QList<QMacPasteboardMime*> all(uchar); + static QMacPasteboardMime *convertor(uchar, const QString &mime, QString flav); + static QString flavorToMime(uchar, QString flav); + + virtual QString convertorName() = 0; + + virtual bool canConvert(const QString &mime, QString flav) = 0; + virtual QString mimeFor(QString flav) = 0; + virtual QString flavorFor(const QString &mime) = 0; + virtual QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) = 0; + virtual QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) = 0; +}; + +// ### Qt 5: Add const QStringList& QMacPasteboardMime::supportedFlavours() +Q_GUI_EXPORT void qRegisterDraggedTypes(const QStringList &types); +#endif // Q_WS_MAC + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMIME_H diff --git a/src/gui/kernel/qmime_mac.cpp b/src/gui/kernel/qmime_mac.cpp new file mode 100644 index 0000000000..d6f6222c23 --- /dev/null +++ b/src/gui/kernel/qmime_mac.cpp @@ -0,0 +1,1310 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmime.h" + +//#define USE_INTERNET_CONFIG + +#ifndef USE_INTERNET_CONFIG +# include "qfile.h" +# include "qfileinfo.h" +# include "qtextstream.h" +# include "qdir.h" +# include <unistd.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/fcntl.h> +#endif + +#include "qdebug.h" +#include "qpixmap.h" +#include "qimagewriter.h" +#include "qimagereader.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qdatetime.h" +#include "qapplication_p.h" +#include "qtextcodec.h" +#include "qregexp.h" +#include "qurl.h" +#include "qmap.h" +#include <private/qt_mac_p.h> + + +#ifdef Q_WS_MAC32 +#include <QuickTime/QuickTime.h> +#include <qlibrary.h> +#endif + +QT_BEGIN_NAMESPACE + +extern CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0); // qpaintengine_mac.cpp + +typedef QList<QMacPasteboardMime*> MimeList; +Q_GLOBAL_STATIC(MimeList, globalMimeList) + +static void cleanup_mimes() +{ + MimeList *mimes = globalMimeList(); + while (!mimes->isEmpty()) + delete mimes->takeFirst(); +} + +Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList) + +/*! + \fn void qRegisterDraggedTypes(const QStringList &types) + \relates QMacPasteboardMime + + Registers the given \a types as custom pasteboard types. + + This function should be called to enable the Drag and Drop events + for custom pasteboard types on Cocoa implementations. This is required + in addition to a QMacPasteboardMime subclass implementation. By default + drag and drop is enabled for all standard pasteboard types. + + \sa QMacPasteboardMime +*/ +Q_GUI_EXPORT void qRegisterDraggedTypes(const QStringList &types) +{ + (*globalDraggedTypesList()) += types; +} + +const QStringList& qEnabledDraggedTypes() +{ + return (*globalDraggedTypesList()); +} + + +/***************************************************************************** + QDnD debug facilities + *****************************************************************************/ +//#define DEBUG_MIME_MAPS + +//functions +extern QString qt_mac_from_pascal_string(const Str255); //qglobal.cpp +extern void qt_mac_from_pascal_string(QString, Str255, TextEncoding encoding=0, int len=-1); //qglobal.cpp + +ScrapFlavorType qt_mac_mime_type = 'CUTE'; +CFStringRef qt_mac_mime_typeUTI = CFSTR("com.pasteboard.trolltech.marker"); + +/*! + \class QMacPasteboardMime + \brief The QMacPasteboardMime class converts between a MIME type and a + \l{http://developer.apple.com/macosx/uniformtypeidentifiers.html}{Uniform + Type Identifier (UTI)} format. + \since 4.2 + + \ingroup draganddrop + + Qt's drag and drop and clipboard facilities use the MIME + standard. On X11, this maps trivially to the Xdnd protocol. On + Mac, although some applications use MIME to describe clipboard + contents, it is more common to use Apple's UTI format. + + QMacPasteboardMime's role is to bridge the gap between MIME and UTI; + By subclasses this class, one can extend Qt's drag and drop + and clipboard handling to convert to and from unsupported, or proprietary, UTI formats. + + A subclass of QMacPasteboardMime will automatically be registered, and active, upon instantiation. + + Qt has predefined support for the following UTIs: + \list + \i public.utf8-plain-text - converts to "text/plain" + \i public.utf16-plain-text - converts to "text/plain" + \i public.html - converts to "text/html" + \i public.url - converts to "text/uri-list" + \i public.file-url - converts to "text/uri-list" + \i public.tiff - converts to "application/x-qt-image" + \i public.vcard - converts to "text/plain" + \i com.apple.traditional-mac-plain-text - converts to "text/plain" + \i com.apple.pict - converts to "application/x-qt-image" + \endlist + + When working with MIME data, Qt will interate through all instances of QMacPasteboardMime to + find an instance that can convert to, or from, a specific MIME type. It will do this by calling + canConvert() on each instance, starting with (and choosing) the last created instance first. + The actual conversions will be done by using convertToMime() and convertFromMime(). + + \note The API uses the term "flavor" in some cases. This is for backwards + compatibility reasons, and should now be understood as UTIs. +*/ + +/*! \enum QMacPasteboardMime::QMacPasteboardMimeType + \internal +*/ + +/*! + Constructs a new conversion object of type \a t, adding it to the + globally accessed list of available convertors. +*/ +QMacPasteboardMime::QMacPasteboardMime(char t) : type(t) +{ + globalMimeList()->append(this); +} + +/*! + Destroys a conversion object, removing it from the global + list of available convertors. +*/ +QMacPasteboardMime::~QMacPasteboardMime() +{ + if(!QApplication::closingDown()) + globalMimeList()->removeAll(this); +} + +class QMacPasteboardMimeAny : public QMacPasteboardMime { +private: + +public: + QMacPasteboardMimeAny() : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) { + } + ~QMacPasteboardMimeAny() { + } + 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 QMacPasteboardMimeAny::convertorName() +{ + return QLatin1String("Any-Mime"); +} + +QString QMacPasteboardMimeAny::flavorFor(const QString &mime) +{ + // do not handle the mime type name in the drag pasteboard + if(mime == QLatin1String("application/x-qt-mime-type-name")) + return QString(); + QString ret = QLatin1String("com.trolltech.anymime.") + mime; + return ret.replace(QLatin1Char('/'), QLatin1String("--")); +} + +QString QMacPasteboardMimeAny::mimeFor(QString flav) +{ + const QString any_prefix = QLatin1String("com.trolltech.anymime."); + if(flav.size() > any_prefix.length() && flav.startsWith(any_prefix)) + return flav.mid(any_prefix.length()).replace(QLatin1String("--"), QLatin1String("/")); + return QString(); +} + +bool QMacPasteboardMimeAny::canConvert(const QString &mime, QString flav) +{ + return mimeFor(flav) == mime; +} + +QVariant QMacPasteboardMimeAny::convertToMime(const QString &mime, QList<QByteArray> data, QString) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeAny: Cannot handle multiple member data"); + QVariant ret; + if (mime == QLatin1String("text/plain")) + ret = QString::fromUtf8(data.first()); + else + ret = data.first(); + return ret; +} + +QList<QByteArray> QMacPasteboardMimeAny::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) + ret.append(data.toString().toUtf8()); + else + ret.append(data.toByteArray()); + return ret; +} + +class QMacPasteboardMimeTypeName : public QMacPasteboardMime { +private: + +public: + QMacPasteboardMimeTypeName() : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) { + } + ~QMacPasteboardMimeTypeName() { + } + 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 QMacPasteboardMimeTypeName::convertorName() +{ + return QLatin1String("Qt-Mime-Type"); +} + +QString QMacPasteboardMimeTypeName::flavorFor(const QString &mime) +{ + if(mime == QLatin1String("application/x-qt-mime-type-name")) + return QLatin1String("com.trolltech.qt.MimeTypeName"); + return QString(); +} + +QString QMacPasteboardMimeTypeName::mimeFor(QString) +{ + return QString(); +} + +bool QMacPasteboardMimeTypeName::canConvert(const QString &, QString) +{ + return false; +} + +QVariant QMacPasteboardMimeTypeName::convertToMime(const QString &, QList<QByteArray>, QString) +{ + QVariant ret; + return ret; +} + +QList<QByteArray> QMacPasteboardMimeTypeName::convertFromMime(const QString &, QVariant, QString) +{ + QList<QByteArray> ret; + ret.append(QString("x-qt-mime-type-name").toUtf8()); + return ret; +} + +class QMacPasteboardMimePlainText : public QMacPasteboardMime { +public: + QMacPasteboardMimePlainText() : QMacPasteboardMime(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 QMacPasteboardMimePlainText::convertorName() +{ + return QLatin1String("PlainText"); +} + +QString QMacPasteboardMimePlainText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/plain")) + return QLatin1String("com.apple.traditional-mac-plain-text"); + return QString(); +} + +QString QMacPasteboardMimePlainText::mimeFor(QString flav) +{ + if (flav == QLatin1String("com.apple.traditional-mac-plain-text")) + return QLatin1String("text/plain"); + return QString(); +} + +bool QMacPasteboardMimePlainText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimePlainText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimePlainText: Cannot handle multiple member data"); + const QByteArray &firstData = data.first(); + QVariant ret; + if(flavor == QCFString(QLatin1String("com.apple.traditional-mac-plain-text"))) { + QCFString str(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8 *>(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList<QByteArray> QMacPasteboardMimePlainText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + QString string = data.toString(); + if(flavor == QCFString(QLatin1String("com.apple.traditional-mac-plain-text"))) + ret.append(string.toLatin1()); + return ret; +} + +class QMacPasteboardMimeUnicodeText : public QMacPasteboardMime { +public: + QMacPasteboardMimeUnicodeText() : QMacPasteboardMime(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 QMacPasteboardMimeUnicodeText::convertorName() +{ + return QLatin1String("UnicodeText"); +} + +QString QMacPasteboardMimeUnicodeText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/plain")) + return QLatin1String("public.utf16-plain-text"); + int i = mime.indexOf(QLatin1String("charset=")); + if (i >= 0) { + QString cs(mime.mid(i+8).toLower()); + i = cs.indexOf(QLatin1Char(';')); + if (i>=0) + cs = cs.left(i); + if (cs == QLatin1String("system")) + return QLatin1String("public.utf8-plain-text"); + else if (cs == QLatin1String("iso-10646-ucs-2") + || cs == QLatin1String("utf16")) + return QLatin1String("public.utf16-plain-text"); + } + return QString(); +} + +QString QMacPasteboardMimeUnicodeText::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.utf16-plain-text") || flav == QLatin1String("public.utf8-plain-text")) + return QLatin1String("text/plain"); + return QString(); +} + +bool QMacPasteboardMimeUnicodeText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimeUnicodeText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeUnicodeText: Cannot handle multiple member data"); + const QByteArray &firstData = data.first(); + // I can only handle two types (system and unicode) so deal with them that way + QVariant ret; + if(flavor == QLatin1String("public.utf8-plain-text")) { + QCFString str(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8 *>(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else if (flavor == QLatin1String("public.utf16-plain-text")) { + ret = QString(reinterpret_cast<const QChar *>(firstData.constData()), + firstData.size() / sizeof(QChar)); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList<QByteArray> QMacPasteboardMimeUnicodeText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + QString string = data.toString(); + if(flavor == QLatin1String("public.utf8-plain-text")) + ret.append(string.toUtf8()); + else if (flavor == QLatin1String("public.utf16-plain-text")) + ret.append(QByteArray((char*)string.utf16(), string.length()*2)); + return ret; +} + +class QMacPasteboardMimeHTMLText : public QMacPasteboardMime { +public: + QMacPasteboardMimeHTMLText() : QMacPasteboardMime(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 QMacPasteboardMimeHTMLText::convertorName() +{ + return QLatin1String("HTML"); +} + +QString QMacPasteboardMimeHTMLText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/html")) + return QLatin1String("public.html"); + return QString(); +} + +QString QMacPasteboardMimeHTMLText::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.html")) + return QLatin1String("text/html"); + return QString(); +} + +bool QMacPasteboardMimeHTMLText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimeHTMLText::convertToMime(const QString &mimeType, QList<QByteArray> data, QString flavor) +{ + if (!canConvert(mimeType, flavor)) + return QVariant(); + if (data.count() > 1) + qWarning("QMacPasteboardMimeHTMLText: Cannot handle multiple member data"); + return data.first(); +} + +QList<QByteArray> QMacPasteboardMimeHTMLText::convertFromMime(const QString &mime, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flavor)) + return ret; + ret.append(data.toByteArray()); + return ret; +} + + +#ifdef Q_WS_MAC32 + +// This can be removed once 10.6 is the minimum (or we have to require 64-bit) whichever comes first. + +typedef ComponentResult (*PtrGraphicsImportSetDataHandle)(GraphicsImportComponent, Handle); +typedef ComponentResult (*PtrGraphicsImportCreateCGImage)(GraphicsImportComponent, CGImageRef*, UInt32); +typedef ComponentResult (*PtrGraphicsExportSetInputCGImage)(GraphicsExportComponent, CGImageRef); +typedef ComponentResult (*PtrGraphicsExportSetOutputHandle)(GraphicsExportComponent, Handle); +typedef ComponentResult (*PtrGraphicsExportDoExport)(GraphicsExportComponent, unsigned long *); + +static PtrGraphicsImportSetDataHandle ptrGraphicsImportSetDataHandle = 0; +static PtrGraphicsImportCreateCGImage ptrGraphicsImportCreateCGImage = 0; +static PtrGraphicsExportSetInputCGImage ptrGraphicsExportSetInputCGImage = 0; +static PtrGraphicsExportSetOutputHandle ptrGraphicsExportSetOutputHandle = 0; +static PtrGraphicsExportDoExport ptrGraphicsExportDoExport = 0; + +static bool resolveMimeQuickTimeSymbols() +{ + if (ptrGraphicsImportSetDataHandle == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/QuickTime.framework/QuickTime")); + ptrGraphicsImportSetDataHandle = reinterpret_cast<PtrGraphicsImportSetDataHandle>(library.resolve("GraphicsImportSetDataHandle")); + ptrGraphicsImportCreateCGImage = reinterpret_cast<PtrGraphicsImportCreateCGImage>(library.resolve("GraphicsImportCreateCGImage")); + ptrGraphicsExportSetInputCGImage = reinterpret_cast<PtrGraphicsExportSetInputCGImage>(library.resolve("GraphicsExportSetInputCGImage")); + ptrGraphicsExportSetOutputHandle = reinterpret_cast<PtrGraphicsExportSetOutputHandle>(library.resolve("GraphicsExportSetOutputHandle")); + ptrGraphicsExportDoExport = reinterpret_cast<PtrGraphicsExportDoExport>(library.resolve("GraphicsExportDoExport")); + } + + return ptrGraphicsImportSetDataHandle != 0 + && ptrGraphicsImportCreateCGImage != 0 && ptrGraphicsExportSetInputCGImage != 0 + && ptrGraphicsExportSetOutputHandle != 0 && ptrGraphicsExportDoExport != 0; +} + +class QMacPasteboardMimePict : public QMacPasteboardMime { +public: + QMacPasteboardMimePict() : QMacPasteboardMime(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 QMacPasteboardMimePict::convertorName() +{ + return QLatin1String("Pict"); +} + +QString QMacPasteboardMimePict::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("application/x-qt-image"))) + return QLatin1String("com.apple.pict"); + return QString(); +} + +QString QMacPasteboardMimePict::mimeFor(QString flav) +{ + if(flav == QLatin1String("com.apple.pict")) + return QLatin1String("application/x-qt-image"); + return QString(); +} + +bool QMacPasteboardMimePict::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("com.apple.pict") + && mime == QLatin1String("application/x-qt-image"); +} + + +QVariant QMacPasteboardMimePict::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimePict: Cannot handle multiple member data"); + QVariant ret; + if (!resolveMimeQuickTimeSymbols()) + return ret; + + if(!canConvert(mime, flav)) + return ret; + const QByteArray &a = data.first(); + + // This function expects the 512 header (just to skip it, so create the extra space for it). + Handle pic = NewHandle(a.size() + 512); + memcpy(*pic + 512, a.constData(), a.size()); + + GraphicsImportComponent graphicsImporter; + ComponentResult result = OpenADefaultComponent(GraphicsImporterComponentType, + kQTFileTypePicture, &graphicsImporter); + QCFType<CGImageRef> cgImage; + if (!result) + result = ptrGraphicsImportSetDataHandle(graphicsImporter, pic); + if (!result) + result = ptrGraphicsImportCreateCGImage(graphicsImporter, &cgImage, + kGraphicsImportCreateCGImageUsingCurrentSettings); + if (!result) + ret = QVariant(QPixmap::fromMacCGImageRef(cgImage).toImage()); + CloseComponent(graphicsImporter); + DisposeHandle(pic); + return ret; +} + +QList<QByteArray> QMacPasteboardMimePict::convertFromMime(const QString &mime, QVariant variant, + QString flav) +{ + QList<QByteArray> ret; + if (!resolveMimeQuickTimeSymbols()) + return ret; + + if (!canConvert(mime, flav)) + return ret; + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(qvariant_cast<QImage>(variant)); + Handle pic = NewHandle(0); + GraphicsExportComponent graphicsExporter; + ComponentResult result = OpenADefaultComponent(GraphicsExporterComponentType, + kQTFileTypePicture, &graphicsExporter); + if (!result) { + unsigned long sizeWritten; + result = ptrGraphicsExportSetInputCGImage(graphicsExporter, cgimage); + if (!result) + result = ptrGraphicsExportSetOutputHandle(graphicsExporter, pic); + if (!result) + result = ptrGraphicsExportDoExport(graphicsExporter, &sizeWritten); + + CloseComponent(graphicsExporter); + } + + int size = GetHandleSize((Handle)pic); + // Skip the Picture File header (512 bytes) and feed the raw data + QByteArray ar(reinterpret_cast<char *>(*pic + 512), size - 512); + ret.append(ar); + DisposeHandle(pic); + return ret; +} + + +#endif //Q_WS_MAC32 + +class QMacPasteboardMimeTiff : public QMacPasteboardMime { +public: + QMacPasteboardMimeTiff() : QMacPasteboardMime(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 QMacPasteboardMimeTiff::convertorName() +{ + return QLatin1String("Tiff"); +} + +QString QMacPasteboardMimeTiff::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("application/x-qt-image"))) + return QLatin1String("public.tiff"); + return QString(); +} + +QString QMacPasteboardMimeTiff::mimeFor(QString flav) +{ + if(flav == QLatin1String("public.tiff")) + return QLatin1String("application/x-qt-image"); + return QString(); +} + +bool QMacPasteboardMimeTiff::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("public.tiff") && mime == QLatin1String("application/x-qt-image"); +} + +QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data"); + QVariant ret; + if (!canConvert(mime, flav)) + return ret; + const QByteArray &a = data.first(); + QCFType<CGImageRef> image; + QCFType<CFDataRef> tiffData = CFDataCreateWithBytesNoCopy(0, + reinterpret_cast<const UInt8 *>(a.constData()), + a.size(), kCFAllocatorNull); + QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); + image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + + if (image != 0) + ret = QVariant(QPixmap::fromMacCGImageRef(image).toImage()); + return ret; +} + +QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + + QImage img = qvariant_cast<QImage>(variant); + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(img); +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); + QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); + if (imageDestination != 0) { + CFTypeRef keys[2]; + QCFType<CFTypeRef> values[2]; + QCFType<CFDictionaryRef> options; + keys[0] = kCGImagePropertyPixelWidth; + keys[1] = kCGImagePropertyPixelHeight; + int width = img.width(); + int height = img.height(); + values[0] = CFNumberCreate(0, kCFNumberIntType, &width); + values[1] = CFNumberCreate(0, kCFNumberIntType, &height); + options = CFDictionaryCreate(0, reinterpret_cast<const void **>(keys), + reinterpret_cast<const void **>(values), 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CGImageDestinationAddImage(imageDestination, cgimage, options); + CGImageDestinationFinalize(imageDestination); + } + QByteArray ar(CFDataGetLength(data), 0); + CFDataGetBytes(data, + CFRangeMake(0, ar.size()), + reinterpret_cast<UInt8 *>(ar.data())); + ret.append(ar); + } else +#endif + { +#ifdef Q_WS_MAC32 + Handle tiff = NewHandle(0); + if (resolveMimeQuickTimeSymbols()) { + GraphicsExportComponent graphicsExporter; + ComponentResult result = OpenADefaultComponent(GraphicsExporterComponentType, + kQTFileTypeTIFF, &graphicsExporter); + if (!result) { + unsigned long sizeWritten; + result = ptrGraphicsExportSetInputCGImage(graphicsExporter, cgimage); + if (!result) + result = ptrGraphicsExportSetOutputHandle(graphicsExporter, tiff); + if (!result) + result = ptrGraphicsExportDoExport(graphicsExporter, &sizeWritten); + + CloseComponent(graphicsExporter); + } + } + int size = GetHandleSize((Handle)tiff); + QByteArray ar(reinterpret_cast<char *>(*tiff), size); + ret.append(ar); + DisposeHandle(tiff); +#endif + } + return ret; +} + + +class QMacPasteboardMimeFileUri : public QMacPasteboardMime { +public: + QMacPasteboardMimeFileUri() : QMacPasteboardMime(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 QMacPasteboardMimeFileUri::convertorName() +{ + return QLatin1String("FileURL"); +} + +QString QMacPasteboardMimeFileUri::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/uri-list")) + return QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0)); + return QString(); +} + +QString QMacPasteboardMimeFileUri::mimeFor(QString flav) +{ + if (flav == QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0))) + return QLatin1String("text/uri-list"); + return QString(); +} + +bool QMacPasteboardMimeFileUri::canConvert(const QString &mime, QString flav) +{ + return mime == QLatin1String("text/uri-list") + && flav == QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0)); +} + +QVariant QMacPasteboardMimeFileUri::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + QList<QVariant> ret; + for(int i = 0; i < data.size(); ++i) { + QUrl url = QUrl::fromEncoded(data.at(i)); + if (url.host().toLower() == QLatin1String("localhost")) + url.setHost(QString()); + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> QMacPasteboardMimeFileUri::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + QList<QVariant> urls = data.toList(); + for(int i = 0; i < urls.size(); ++i) { + QUrl url = urls.at(i).toUrl(); + if (url.scheme().isEmpty()) + url.setScheme(QLatin1String("file")); + if (url.scheme().toLower() == QLatin1String("file")) { + if (url.host().isEmpty()) + url.setHost(QLatin1String("localhost")); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + ret.append(url.toEncoded()); + } + return ret; +} + +class QMacPasteboardMimeUrl : public QMacPasteboardMime { +public: + QMacPasteboardMimeUrl() : QMacPasteboardMime(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 QMacPasteboardMimeUrl::convertorName() +{ + return QLatin1String("URL"); +} + +QString QMacPasteboardMimeUrl::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("text/uri-list"))) + return QLatin1String("public.url"); + return QString(); +} + +QString QMacPasteboardMimeUrl::mimeFor(QString flav) +{ + if(flav == QLatin1String("public.url")) + return QLatin1String("text/uri-list"); + return QString(); +} + +bool QMacPasteboardMimeUrl::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("public.url") + && mime == QLatin1String("text/uri-list"); +} + +QVariant QMacPasteboardMimeUrl::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + + QList<QVariant> ret; + for (int i=0; i<data.size(); ++i) { + QUrl url = QUrl::fromEncoded(data.at(i)); + if (url.host().toLower() == QLatin1String("localhost")) + url.setHost(QString()); + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> QMacPasteboardMimeUrl::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + + QList<QVariant> urls = data.toList(); + for(int i=0; i<urls.size(); ++i) { + QUrl url = urls.at(i).toUrl(); + if (url.scheme().isEmpty()) + url.setScheme(QLatin1String("file")); + if (url.scheme().toLower() == QLatin1String("file")) { + if (url.host().isEmpty()) + url.setHost(QLatin1String("localhost")); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + ret.append(url.toEncoded()); + } + return ret; +} + +class QMacPasteboardMimeVCard : public QMacPasteboardMime +{ +public: + QMacPasteboardMimeVCard() : QMacPasteboardMime(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 QMacPasteboardMimeVCard::convertorName() +{ + return QString("VCard"); +} + +bool QMacPasteboardMimeVCard::canConvert(const QString &mime, QString flav) +{ + return mimeFor(flav) == mime; +} + +QString QMacPasteboardMimeVCard::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("text/plain"))) + return QLatin1String("public.vcard"); + return QString(); +} + +QString QMacPasteboardMimeVCard::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.vcard")) + return QLatin1String("text/plain"); + return QString(); +} + +QVariant QMacPasteboardMimeVCard::convertToMime(const QString &mime, QList<QByteArray> data, QString) +{ + QByteArray cards; + if (mime == QLatin1String("text/plain")) { + for (int i=0; i<data.size(); ++i) + cards += data[i]; + } + return QVariant(cards); +} + +QList<QByteArray> QMacPasteboardMimeVCard::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) + ret.append(data.toString().toUtf8()); + return ret; +} + +#ifdef QT3_SUPPORT +class QMacPasteboardMimeQt3Any : public QMacPasteboardMime { +private: + int current_max; + QFile library_file; + QDateTime mime_registry_loaded; + QMap<QString, int> mime_registry; + int registerMimeType(const QString &mime); + bool loadMimeRegistry(); + +public: + QMacPasteboardMimeQt3Any() : QMacPasteboardMime(MIME_QT3_CONVERTOR) { + current_max = 'QT00'; + } + ~QMacPasteboardMimeQt3Any() { + } + 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); +}; + +static bool qt_mac_openMimeRegistry(bool global, QIODevice::OpenMode mode, QFile &file) +{ + QString dir = QLatin1String("/Library/Qt"); + if(!global) + dir.prepend(QDir::homePath()); + file.setFileName(dir + QLatin1String("/.mime_types")); + if(mode != QIODevice::ReadOnly) { + if(!QFile::exists(dir)) { + // Do it with a system call as I don't see much worth in + // doing it with QDir since we have to chmod anyway. + bool success = ::mkdir(dir.toLocal8Bit().constData(), S_IRUSR | S_IWUSR | S_IXUSR) == 0; + if (success) + success = ::chmod(dir.toLocal8Bit().constData(), S_IRUSR | S_IWUSR | S_IXUSR + | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH) == 0; + if (!success) + return false; + } + if (!file.exists()) { + // Create the file and chmod it so that everyone can write to it. + int fd = ::open(file.fileName().toLocal8Bit().constData(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + bool success = fd != -1; + if (success) + success = ::fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) == 0; + if (fd != -1) + ::close(fd); + if(!success) + return false; + } + } + return file.open(mode); +} + +static void qt_mac_loadMimeRegistry(QFile &file, QMap<QString, int> ®istry, int &max) +{ + file.reset(); + QTextStream stream(&file); + while(!stream.atEnd()) { + QString mime = stream.readLine(); + int mactype = stream.readLine().toInt(); + if(mactype > max) + max = mactype; + registry.insert(mime, mactype); + } +} + +bool QMacPasteboardMimeQt3Any::loadMimeRegistry() +{ + if(!library_file.isOpen()) { + if(!qt_mac_openMimeRegistry(true, QIODevice::ReadWrite, library_file)) { + QFile global; + if(qt_mac_openMimeRegistry(true, QIODevice::ReadOnly, global)) { + qt_mac_loadMimeRegistry(global, mime_registry, current_max); + global.close(); + } + if(!qt_mac_openMimeRegistry(false, QIODevice::ReadWrite, library_file)) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Failure to open mime resources %s -- %s", library_file.fileName().toLatin1().constData(), + library_file.errorString().toLatin1().constData()); + return false; + } + } + } + + QFileInfo fi(library_file); + if(!mime_registry_loaded.isNull() && mime_registry_loaded == fi.lastModified()) + return true; + mime_registry_loaded = fi.lastModified(); + qt_mac_loadMimeRegistry(library_file, mime_registry, current_max); + return true; +} + +int QMacPasteboardMimeQt3Any::registerMimeType(const QString &mime) +{ + if(!mime_registry.contains(mime)) { + if(!loadMimeRegistry()) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Internal error"); + return 0; + } + if(!mime_registry.contains(mime)) { + if(!library_file.isOpen()) { + if(!library_file.open(QIODevice::WriteOnly)) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Failure to open %s -- %s", library_file.fileName().toLatin1().constData(), + library_file.errorString().toLatin1().constData()); + return false; + } + } + int ret = ++current_max; + mime_registry_loaded = QFileInfo(library_file).lastModified(); + QTextStream stream(&library_file); + stream << mime << endl; + stream << ret << endl; + mime_registry.insert(mime, ret); + library_file.flush(); //flush and set mtime + return ret; + } + } + return mime_registry[mime]; +} + +QString QMacPasteboardMimeQt3Any::convertorName() +{ + return QLatin1String("Qt3-Any-Mime"); +} + +QString QMacPasteboardMimeQt3Any::flavorFor(const QString &mime) +{ + const int os_flav = registerMimeType(mime); + QCFType<CFArrayRef> ids = UTTypeCreateAllIdentifiersForTag(0, kUTTagClassOSType, + QCFString(UTCreateStringForOSType(os_flav))); + if(ids) { + const int type_count = CFArrayGetCount(ids); + if(type_count) { + if(type_count > 1) + qDebug("Can't happen!"); + return QCFString::toQString((CFStringRef)CFArrayGetValueAtIndex(ids, 0)); + } + } + return QString(); +} + +QString QMacPasteboardMimeQt3Any::mimeFor(QString flav) +{ + loadMimeRegistry(); + const int os_flav = UTGetOSTypeFromString(UTTypeCopyPreferredTagWithClass(QCFString(flav), kUTTagClassOSType)); + for(QMap<QString, int>::const_iterator it = mime_registry.constBegin(); + it != mime_registry.constEnd(); ++it) { + if(it.value() == os_flav) + return QString::fromLatin1(it.key().toLatin1()); + } + return QString(); +} + +bool QMacPasteboardMimeQt3Any::canConvert(const QString &mime, QString flav) +{ + loadMimeRegistry(); + const int os_flav = UTGetOSTypeFromString(UTTypeCopyPreferredTagWithClass(QCFString(flav), kUTTagClassOSType)); + if(mime_registry.contains(mime) && mime_registry[mime] == os_flav) + return true; + return false; +} + +QVariant QMacPasteboardMimeQt3Any::convertToMime(const QString &, QList<QByteArray>, QString) +{ + qWarning("QMacPasteboardMimeAnyQt3Mime: Cannot write anything!"); + return QVariant(); +} + +QList<QByteArray> QMacPasteboardMimeQt3Any::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) { + ret.append(data.toString().toUtf8()); + } else { + ret.append(data.toByteArray()); + } + return ret; +} +#endif + +/*! + \internal + + This is an internal function. +*/ +void QMacPasteboardMime::initialize() +{ + if(globalMimeList()->isEmpty()) { + qAddPostRoutine(cleanup_mimes); + + //standard types that we wrap + new QMacPasteboardMimeTiff; +#ifdef Q_WS_MAC32 + // 10.6 does automatic synthesis to and from PICT to standard image types (like TIFF), + // so don't bother doing it ourselves, especially since it's not available in 64-bit. + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_6) + new QMacPasteboardMimePict; +#endif + new QMacPasteboardMimeUnicodeText; + new QMacPasteboardMimePlainText; + new QMacPasteboardMimeHTMLText; + new QMacPasteboardMimeFileUri; + new QMacPasteboardMimeUrl; + new QMacPasteboardMimeTypeName; + new QMacPasteboardMimeVCard; + //make sure our "non-standard" types are always last! --Sam + new QMacPasteboardMimeAny; +#ifdef QT3_SUPPORT + new QMacPasteboardMimeQt3Any; +#endif + } +} + +/*! + Returns the most-recently created QMacPasteboardMime of type \a t that can convert + between the \a mime and \a flav formats. Returns 0 if no such convertor + exists. +*/ +QMacPasteboardMime* +QMacPasteboardMime::convertor(uchar t, const QString &mime, QString flav) +{ + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { +#ifdef DEBUG_MIME_MAPS + qDebug("QMacPasteboardMime::convertor: seeing if %s (%d) can convert %s to %d[%c%c%c%c] [%d]", + (*it)->convertorName().toLatin1().constData(), + (*it)->type & t, mime.toLatin1().constData(), + flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF, + (*it)->canConvert(mime,flav)); + for(int i = 0; i < (*it)->countFlavors(); ++i) { + int f = (*it)->flavor(i); + qDebug(" %d) %d[%c%c%c%c] [%s]", i, f, + (f >> 24) & 0xFF, (f >> 16) & 0xFF, (f >> 8) & 0xFF, (f) & 0xFF, + (*it)->convertorName().toLatin1().constData()); + } +#endif + if(((*it)->type & t) && (*it)->canConvert(mime, flav)) + return (*it); + } + return 0; +} +/*! + Returns a MIME type of type \a t for \a flav, or 0 if none exists. +*/ +QString QMacPasteboardMime::flavorToMime(uchar t, QString flav) +{ + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { +#ifdef DEBUG_MIME_MAPS + qDebug("QMacMIme::flavorToMime: attempting %s (%d) for flavor %d[%c%c%c%c] [%s]", + (*it)->convertorName().toLatin1().constData(), + (*it)->type & t, flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF, + (*it)->mimeFor(flav).toLatin1().constData()); + +#endif + if((*it)->type & t) { + QString mimeType = (*it)->mimeFor(flav); + if(!mimeType.isNull()) + return mimeType; + } + } + return QString(); +} + +/*! + Returns a list of all currently defined QMacPasteboardMime objects of type \a t. +*/ +QList<QMacPasteboardMime*> QMacPasteboardMime::all(uchar t) +{ + MimeList ret; + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { + if((*it)->type & t) + ret.append((*it)); + } + return ret; +} + + +/*! + \fn QString QMacPasteboardMime::convertorName() + + Returns a name for the convertor. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn bool QMacPasteboardMime::canConvert(const QString &mime, QString flav) + + Returns true if the convertor can convert (both ways) between + \a mime and \a flav; otherwise returns false. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QString QMacPasteboardMime::mimeFor(QString flav) + + Returns the MIME UTI used for Mac flavor \a flav, or 0 if this + convertor does not support \a flav. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QString QMacPasteboardMime::flavorFor(const QString &mime) + + Returns the Mac UTI used for MIME type \a mime, or 0 if this + convertor does not support \a mime. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QVariant QMacPasteboardMime::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) + + Returns \a data converted from Mac UTI \a flav to MIME type \a + mime. + + Note that Mac flavors must all be self-terminating. The input \a + data may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QList<QByteArray> QMacPasteboardMime::convertFromMime(const QString &mime, QVariant data, QString flav) + + Returns \a data converted from MIME type \a mime + to Mac UTI \a flav. + + Note that Mac flavors must all be self-terminating. The return + value may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qmime_win.cpp b/src/gui/kernel/qmime_win.cpp new file mode 100644 index 0000000000..feb8b78eca --- /dev/null +++ b/src/gui/kernel/qmime_win.cpp @@ -0,0 +1,1556 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmime.h" + +#include "qimagereader.h" +#include "qimagewriter.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qt_windows.h" +#include "qapplication_p.h" +#include "qtextcodec.h" +#include "qregexp.h" +#include "qalgorithms.h" +#include "qmap.h" +#include "qdnd_p.h" +#include <shlobj.h> +#include "qurl.h" +#include "qvariant.h" +#include "qtextdocument.h" +#include "qdir.h" + +#if defined(Q_OS_WINCE) +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_IMAGEFORMAT_BMP +#ifndef CF_DIBV5 +#define CF_DIBV5 17 +#endif +/* The MSVC compilers allows multi-byte characters, that has the behavior of + * that each character gets shifted into position. 0x73524742 below is for MSVC + * equivalent to doing 'sRGB', but this does of course not work + * on conformant C++ compilers. */ +#define BMP_LCS_sRGB 0x73524742 +#define BMP_LCS_GM_IMAGES 0x00000004L + +struct _CIEXYZ { + long ciexyzX, ciexyzY, ciexyzZ; +}; + +struct _CIEXYZTRIPLE { + _CIEXYZ ciexyzRed, ciexyzGreen, ciexyzBlue; +}; + +struct BMP_BITMAPV5HEADER { + DWORD bV5Size; + LONG bV5Width; + LONG bV5Height; + WORD bV5Planes; + WORD bV5BitCount; + DWORD bV5Compression; + DWORD bV5SizeImage; + LONG bV5XPelsPerMeter; + LONG bV5YPelsPerMeter; + DWORD bV5ClrUsed; + DWORD bV5ClrImportant; + DWORD bV5RedMask; + DWORD bV5GreenMask; + DWORD bV5BlueMask; + DWORD bV5AlphaMask; + DWORD bV5CSType; + _CIEXYZTRIPLE bV5Endpoints; + DWORD bV5GammaRed; + DWORD bV5GammaGreen; + DWORD bV5GammaBlue; + DWORD bV5Intent; + DWORD bV5ProfileData; + DWORD bV5ProfileSize; + DWORD bV5Reserved; +}; +static const int BMP_BITFIELDS = 3; + +extern bool qt_read_dib(QDataStream&, QImage&); // qimage.cpp +extern bool qt_write_dib(QDataStream&, QImage); // qimage.cpp +static bool qt_write_dibv5(QDataStream &s, QImage image); +static bool qt_read_dibv5(QDataStream &s, QImage &image); +#endif + +//#define QMIME_DEBUG + + +// helpers for using global memory + +static int getCf(const FORMATETC &formatetc) +{ + return formatetc.cfFormat; +} + +static FORMATETC setCf(int cf) +{ + FORMATETC formatetc; + formatetc.cfFormat = cf; + formatetc.dwAspect = DVASPECT_CONTENT; + formatetc.lindex = -1; + formatetc.ptd = NULL; + formatetc.tymed = TYMED_HGLOBAL; + return formatetc; +} + +static bool setData(const QByteArray &data, STGMEDIUM *pmedium) +{ + HGLOBAL hData = GlobalAlloc(0, data.size()); + if (!hData) + return false; + + void *out = GlobalLock(hData); + memcpy(out, data.data(), data.size()); + GlobalUnlock(hData); + pmedium->tymed = TYMED_HGLOBAL; + pmedium->hGlobal = hData; + pmedium->pUnkForRelease = 0; + return true; +} + +static QByteArray getData(int cf, IDataObject *pDataObj) +{ + QByteArray data; + FORMATETC formatetc = setCf(cf); + STGMEDIUM s; + if (pDataObj->GetData(&formatetc, &s) == S_OK) { + DWORD * val = (DWORD*)GlobalLock(s.hGlobal); + data = QByteArray::fromRawData((char*)val, GlobalSize(s.hGlobal)); + data.detach(); + GlobalUnlock(s.hGlobal); + ReleaseStgMedium(&s); + } else { + //Try reading IStream data + formatetc.tymed = TYMED_ISTREAM; + if (pDataObj->GetData(&formatetc, &s) == S_OK) { + char szBuffer[4096]; + ULONG actualRead = 0; + LARGE_INTEGER pos = {{0, 0}}; + //Move to front (can fail depending on the data model implemented) + HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, NULL); + while(SUCCEEDED(hr)){ + hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead); + if (SUCCEEDED(hr) && actualRead > 0) { + data += QByteArray::fromRawData(szBuffer, actualRead); + } + if (actualRead != sizeof(szBuffer)) + break; + } + data.detach(); + ReleaseStgMedium(&s); + } + } + return data; +} + +static bool canGetData(int cf, IDataObject * pDataObj) +{ + FORMATETC formatetc = setCf(cf); + if (pDataObj->QueryGetData(&formatetc) != S_OK){ + formatetc.tymed = TYMED_ISTREAM; + return pDataObj->QueryGetData(&formatetc) == S_OK; + } + return true; +} + +class QWindowsMimeList +{ +public: + QWindowsMimeList(); + ~QWindowsMimeList(); + void addWindowsMime(QWindowsMime * mime); + void removeWindowsMime(QWindowsMime * mime); + QList<QWindowsMime*> windowsMimes(); + +private: + void init(); + bool initialized; + QList<QWindowsMime*> mimes; +}; + +Q_GLOBAL_STATIC(QWindowsMimeList, theMimeList); + + +/*! + \class QWindowsMime + \brief The QWindowsMime class maps open-standard MIME to Window Clipboard formats. + \ingroup draganddrop + + 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 \o Windows Format \o Equivalent MIME type + \row \o \c CF_UNICODETEXT \o \c text/plain + \row \o \c CF_TEXT \o \c text/plain + \row \o \c CF_DIB \o \c{image/xyz}, where \c xyz is + a \l{QImageWriter::supportedImageFormats()}{Qt image format} + \row \o \c CF_HDROP \o \c text/uri-list + \row \o \c CF_INETURL \o \c text/uri-list + \row \o \c CF_HTML \o \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(). +*/ + +/*! +Constructs a new conversion object, adding it to the globally accessed +list of available converters. +*/ +QWindowsMime::QWindowsMime() +{ + theMimeList()->addWindowsMime(this); +} + +/*! +Destroys a conversion object, removing it from the global +list of available converters. +*/ +QWindowsMime::~QWindowsMime() +{ + theMimeList()->removeWindowsMime(this); +} + + +/*! + Registers the MIME type \a mime, and returns an ID number + identifying the format on Windows. +*/ +int QWindowsMime::registerMimeType(const QString &mime) +{ + int f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mime.utf16())); + if (!f) + qErrnoWarning("QWindowsMime::registerMimeType: Failed to register clipboard format"); + + return f; +} + + +/*! +\fn bool QWindowsMime::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const + + Returns 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 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 QVector<FORMATETC> QWindowsMime::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const + + Returns a QVector 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, + QVariant::Type 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. +*/ + + +QWindowsMime *QWindowsMime::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) +{ + QList<QWindowsMime*> mimes = theMimeList()->windowsMimes(); + for (int i=mimes.size()-1; i>=0; --i) { + if (mimes.at(i)->canConvertFromMime(formatetc, mimeData)) + return mimes.at(i); + } + return 0; +} + +QWindowsMime *QWindowsMime::converterToMime(const QString &mimeType, IDataObject *pDataObj) +{ + QList<QWindowsMime*> mimes = theMimeList()->windowsMimes(); + for (int i=mimes.size()-1; i>=0; --i) { + if (mimes.at(i)->canConvertToMime(mimeType, pDataObj)) + return mimes.at(i); + } + return 0; +} + +QVector<FORMATETC> QWindowsMime::allFormatsForMime(const QMimeData *mimeData) +{ + QList<QWindowsMime*> mimes = theMimeList()->windowsMimes(); + QVector<FORMATETC> formatics; + formatics.reserve(20); +#ifndef QT_NO_DRAGANDDROP + QStringList formats = QInternalMimeData::formatsHelper(mimeData); + for (int f=0; f<formats.size(); ++f) { + for (int i=mimes.size()-1; i>=0; --i) + formatics += mimes.at(i)->formatsForMime(formats.at(f), mimeData); + } +#else + Q_UNUSED(mimeData); +#endif //QT_NO_DRAGANDDROP + return formatics; +} + +QStringList QWindowsMime::allMimesForFormats(IDataObject *pDataObj) +{ + QList<QWindowsMime*> mimes = theMimeList()->windowsMimes(); + QStringList formats; + LPENUMFORMATETC FAR fmtenum; + HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum); + + if (hr == NOERROR) { + FORMATETC fmtetc; + while (S_OK == fmtenum->Next(1, &fmtetc, 0)) { +#if defined(QMIME_DEBUG) && !defined(Q_OS_WINCE) + qDebug("QWindowsMime::allMimesForFormats()"); + wchar_t buf[256] = {0}; + GetClipboardFormatName(fmtetc.cfFormat, buf, 255); + qDebug("CF = %d : %s", fmtetc.cfFormat, QString::fromWCharArray(buf)); +#endif + for (int i=mimes.size()-1; i>=0; --i) { + QString format = mimes.at(i)->mimeForFormat(fmtetc); + if (!format.isEmpty() && !formats.contains(format)) { + formats += format; + } + } + // as documented in MSDN to avoid possible memleak + if (fmtetc.ptd) + CoTaskMemFree(fmtetc.ptd); + } + fmtenum->Release(); + } + + return formats; +} + + +class QWindowsMimeText : public QWindowsMime +{ +public: + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; +}; + +bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + int cf = getCf(formatetc); + return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText(); +} + +/* +text/plain is defined as using CRLF, but so many programs don't, +and programmers just look for '\n' in strings. +Windows really needs CRLF, so we ensure it here. +*/ +bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const +{ + if (canConvertFromMime(formatetc, mimeData)) { + QByteArray data; + int cf = getCf(formatetc); + if (cf == CF_TEXT) { + data = mimeData->text().toLocal8Bit(); + // Anticipate required space for CRLFs at 1/40 + int maxsize=data.size()+data.size()/40+3; + QByteArray r(maxsize, '\0'); + char* o = r.data(); + const char* d = data.data(); + const int s = data.size(); + bool cr=false; + int j=0; + for (int i=0; i<s; i++) { + char c = d[i]; + if (c=='\r') + cr=true; + else { + if (c=='\n') { + if (!cr) + o[j++]='\r'; + } + cr=false; + } + o[j++]=c; + if (j+3 >= maxsize) { + maxsize += maxsize/4; + r.resize(maxsize); + o = r.data(); + } + } + o[j]=0; + return setData(r, pmedium); + } else if (cf == CF_UNICODETEXT) { + QString str = mimeData->text(); + const QChar *u = str.unicode(); + QString res; + const int s = str.length(); + int maxsize = s + s/40 + 3; + res.resize(maxsize); + int ri = 0; + bool cr = false; + for (int i=0; i < s; ++i) { + if (*u == QLatin1Char('\r')) + cr = true; + else { + if (*u == QLatin1Char('\n') && !cr) + res[ri++] = QLatin1Char('\r'); + cr = false; + } + res[ri++] = *u; + if (ri+3 >= maxsize) { + maxsize += maxsize/4; + res.resize(maxsize); + } + ++u; + } + res.truncate(ri); + const int byteLength = res.length() * sizeof(ushort); + QByteArray r(byteLength + 2, '\0'); + memcpy(r.data(), res.unicode(), byteLength); + r[byteLength] = 0; + r[byteLength+1] = 0; + return setData(r, pmedium); + } + } + return false; +} + +bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + return mimeType.startsWith(QLatin1String("text/plain")) + && (canGetData(CF_UNICODETEXT, pDataObj) + || canGetData(CF_TEXT, pDataObj)); +} + +QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const +{ + int cf = getCf(formatetc); + if (cf == CF_UNICODETEXT || cf == CF_TEXT) + return QLatin1String("text/plain"); + return QString(); +} + + +QVector<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector<FORMATETC> formatics; + if (mimeType.startsWith(QLatin1String("text/plain")) && mimeData->hasText()) { + formatics += setCf(CF_UNICODETEXT); + formatics += setCf(CF_TEXT); + } + return formatics; +} + +QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const +{ + QVariant ret; + + if (canConvertToMime(mime, pDataObj)) { + QString str; + QByteArray data = getData(CF_UNICODETEXT, pDataObj); + if (!data.isEmpty()) { + str = QString::fromWCharArray((const wchar_t *)data.data()); + str.replace(QLatin1String("\r\n"), QLatin1String("\n")); + } else { + data = getData(CF_TEXT, pDataObj); + if (!data.isEmpty()) { + const char* d = data.data(); + const int s = qstrlen(d); + QByteArray r(data.size()+1, '\0'); + char* o = r.data(); + int j=0; + for (int i=0; i<s; i++) { + char c = d[i]; + if (c!='\r') + o[j++]=c; + } + o[j]=0; + str = QString::fromLocal8Bit(r); + } + } + if (preferredType == QVariant::String) + ret = str; + else + ret = str.toUtf8(); + } + + return ret; +} + +class QWindowsMimeURI : public QWindowsMime +{ +public: + QWindowsMimeURI(); + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; +private: + int CF_INETURL_W; // wide char version + int CF_INETURL; +}; + +QWindowsMimeURI::QWindowsMimeURI() +{ + CF_INETURL_W = QWindowsMime::registerMimeType(QLatin1String("UniformResourceLocatorW")); + CF_INETURL = QWindowsMime::registerMimeType(QLatin1String("UniformResourceLocator")); +} + +bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + if (getCf(formatetc) == CF_HDROP) { + QList<QUrl> urls = mimeData->urls(); + for (int i=0; i<urls.size(); i++) { + if (!urls.at(i).toLocalFile().isEmpty()) + return true; + } + } + return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasFormat(QLatin1String("text/uri-list")); +} + +bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const +{ + if (canConvertFromMime(formatetc, mimeData)) { + if (getCf(formatetc) == CF_HDROP) { + QList<QUrl> urls = mimeData->urls(); + QStringList fileNames; + int size = sizeof(DROPFILES)+2; + for (int i=0; i<urls.size(); i++) { + QString fn = QDir::toNativeSeparators(urls.at(i).toLocalFile()); + if (!fn.isEmpty()) { + size += sizeof(ushort) * (fn.length() + 1); + fileNames.append(fn); + } + } + + QByteArray result(size, '\0'); + DROPFILES* d = (DROPFILES*)result.data(); + d->pFiles = sizeof(DROPFILES); + GetCursorPos(&d->pt); // try + d->fNC = true; + char* files = ((char*)d) + d->pFiles; + + d->fWide = true; + wchar_t* f = (wchar_t*)files; + for (int i=0; i<fileNames.size(); i++) { + int l = fileNames.at(i).length(); + memcpy(f, fileNames.at(i).utf16(), l * sizeof(ushort)); + f += l; + *f++ = 0; + } + *f = 0; + + return setData(result, pmedium); + } else if (getCf(formatetc) == CF_INETURL_W) { + QList<QUrl> urls = mimeData->urls(); + QByteArray result; + if (!urls.isEmpty()) { + QString url = urls.at(0).toString(); + result = QByteArray((const char *)url.utf16(), url.length() * sizeof(ushort)); + } + result.append('\0'); + result.append('\0'); + return setData(result, pmedium); + } else if (getCf(formatetc) == CF_INETURL) { + QList<QUrl> urls = mimeData->urls(); + QByteArray result; + if (!urls.isEmpty()) + result = urls.at(0).toString().toLocal8Bit(); + return setData(result, pmedium); + } + } + + return false; +} + +bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + return mimeType == QLatin1String("text/uri-list") + && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj)); +} + +QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const +{ + QString format; + if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) + format = QLatin1String("text/uri-list"); + return format; +} + +QVector<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector<FORMATETC> formatics; + if (mimeType == QLatin1String("text/uri-list")) { + if (canConvertFromMime(setCf(CF_HDROP), mimeData)) + formatics += setCf(CF_HDROP); + if (canConvertFromMime(setCf(CF_INETURL_W), mimeData)) + formatics += setCf(CF_INETURL_W); + if (canConvertFromMime(setCf(CF_INETURL), mimeData)) + formatics += setCf(CF_INETURL); + } + return formatics; +} + +QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QVariant::Type preferredType) const +{ + if (mimeType == QLatin1String("text/uri-list")) { + if (canGetData(CF_HDROP, pDataObj)) { + QByteArray texturi; + QList<QVariant> urls; + + QByteArray data = getData(CF_HDROP, pDataObj); + if (data.isEmpty()) + return QVariant(); + + LPDROPFILES hdrop = (LPDROPFILES)data.data(); + if (hdrop->fWide) { + const wchar_t* filesw = (const wchar_t *)(data.data() + hdrop->pFiles); + int i = 0; + while (filesw[i]) { + QString fileurl = QString::fromWCharArray(filesw + i); + urls += QUrl::fromLocalFile(fileurl); + i += fileurl.length()+1; + } + } else { + const char* files = (const char *)data.data() + hdrop->pFiles; + int i=0; + while (files[i]) { + urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i)); + i += int(strlen(files+i))+1; + } + } + + if (preferredType == QVariant::Url && urls.size() == 1) + return urls.at(0); + else if (!urls.isEmpty()) + return urls; + } else if (canGetData(CF_INETURL_W, pDataObj)) { + QByteArray data = getData(CF_INETURL_W, pDataObj); + if (data.isEmpty()) + return QVariant(); + return QUrl(QString::fromWCharArray((const wchar_t *)data.constData())); + } else if (canGetData(CF_INETURL, pDataObj)) { + QByteArray data = getData(CF_INETURL, pDataObj); + if (data.isEmpty()) + return QVariant(); + return QUrl(QString::fromLocal8Bit(data.constData())); + } + } + return QVariant(); +} + +class QWindowsMimeHtml : public QWindowsMime +{ +public: + QWindowsMimeHtml(); + + // for converting from Qt + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; + + // for converting to Qt + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; + +private: + int CF_HTML; +}; + +QWindowsMimeHtml::QWindowsMimeHtml() +{ + CF_HTML = QWindowsMime::registerMimeType(QLatin1String("HTML Format")); +} + +QVector<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector<FORMATETC> formatetcs; + if (mimeType == QLatin1String("text/html") && (!mimeData->html().isEmpty())) + formatetcs += setCf(CF_HTML); + return formatetcs; +} + +QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const +{ + if (getCf(formatetc) == CF_HTML) + return QLatin1String("text/html"); + return QString(); +} + +bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + return mimeType == QLatin1String("text/html") && canGetData(CF_HTML, pDataObj); +} + + +bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty()); +} + +/* +The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions +in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag + + Version: 1.0 + StartHTML:xxxxxxxxxx + EndHTML:xxxxxxxxxx + StartFragment:xxxxxxxxxx + EndFragment:xxxxxxxxxx + ...html... + +*/ +QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const +{ + Q_UNUSED(preferredType); + QVariant result; + if (canConvertToMime(mime, pDataObj)) { + QByteArray html = getData(CF_HTML, pDataObj); +#ifdef QMIME_DEBUG + qDebug("QWindowsMimeHtml::convertToMime"); + qDebug("raw :"); + qDebug(html); +#endif + int start = html.indexOf("StartFragment:"); + int end = html.indexOf("EndFragment:"); + + if (start != -1) { + int startOffset = start + 14; + int i = startOffset; + while (html.at(i) != '\r' && html.at(i) != '\n') + ++i; + QByteArray bytecount = html.mid(startOffset, i - startOffset); + start = bytecount.toInt(); + } + + if (end != -1) { + int endOffset = end + 12; + int i = endOffset ; + while (html.at(i) != '\r' && html.at(i) != '\n') + ++i; + QByteArray bytecount = html.mid(endOffset , i - endOffset); + end = bytecount.toInt(); + } + + if (end > start && start > 0) { + html = "<!--StartFragment-->" + html.mid(start, end - start); + html += "<!--EndFragment-->"; + html.replace('\r', ""); + result = QString::fromUtf8(html); + } + } + return result; +} + +bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const +{ + if (canConvertFromMime(formatetc, mimeData)) { + QByteArray data = mimeData->html().toUtf8(); + QByteArray result = + "Version:1.0\r\n" // 0-12 + "StartHTML:0000000105\r\n" // 13-35 + "EndHTML:0000000000\r\n" // 36-55 + "StartFragment:0000000000\r\n" // 58-86 + "EndFragment:0000000000\r\n\r\n"; // 87-105 + + if (data.indexOf("<!--StartFragment-->") == -1) + result += "<!--StartFragment-->"; + result += data; + if (data.indexOf("<!--EndFragment-->") == -1) + result += "<!--EndFragment-->"; + + // set the correct number for EndHTML + QByteArray pos = QString::number(result.size()).toLatin1(); + memcpy((char *)(result.data() + 53 - pos.length()), pos.constData(), pos.length()); + + // set correct numbers for StartFragment and EndFragment + pos = QString::number(result.indexOf("<!--StartFragment-->") + 20).toLatin1(); + memcpy((char *)(result.data() + 79 - pos.length()), pos.constData(), pos.length()); + pos = QString::number(result.indexOf("<!--EndFragment-->")).toLatin1(); + memcpy((char *)(result.data() + 103 - pos.length()), pos.constData(), pos.length()); + + return setData(result, pmedium); + } + return false; +} + + +#ifndef QT_NO_IMAGEFORMAT_BMP +class QWindowsMimeImage : public QWindowsMime +{ +public: + QWindowsMimeImage(); + // for converting from Qt + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; + + // for converting to Qt + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; +private: + bool hasOriginalDIBV5(IDataObject *pDataObj) const; + UINT CF_PNG; +}; + +QWindowsMimeImage::QWindowsMimeImage() +{ + CF_PNG = RegisterClipboardFormat(L"PNG"); +} + +QVector<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector<FORMATETC> formatetcs; + if (mimeData->hasImage() && mimeType == QLatin1String("application/x-qt-image")) { + //add DIBV5 if image has alpha channel + QImage image = qvariant_cast<QImage>(mimeData->imageData()); + if (!image.isNull() && image.hasAlphaChannel()) + formatetcs += setCf(CF_DIBV5); + formatetcs += setCf(CF_DIB); + } + return formatetcs; +} + +QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const +{ + int cf = getCf(formatetc); + if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) + return QLatin1String("application/x-qt-image"); + return QString(); +} + +bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + if ((mimeType == QLatin1String("application/x-qt-image")) && + (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj))) + return true; + return false; +} + +bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + int cf = getCf(formatetc); + if (mimeData->hasImage()) { + if (cf == CF_DIB) + return true; + else if (cf == CF_DIBV5) { + //support DIBV5 conversion only if the image has alpha channel + QImage image = qvariant_cast<QImage>(mimeData->imageData()); + if (!image.isNull() && image.hasAlphaChannel()) + return true; + } + } + return false; +} + +bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const +{ + int cf = getCf(formatetc); + if ((cf == CF_DIB || cf == CF_DIBV5) && mimeData->hasImage()) { + QImage img = qvariant_cast<QImage>(mimeData->imageData()); + if (img.isNull()) + return false; + QByteArray ba; + QDataStream s(&ba, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian);// Intel byte order #### + if (cf == CF_DIB) { + if (img.format() > QImage::Format_ARGB32) + img = img.convertToFormat(QImage::Format_RGB32); + if (qt_write_dib(s, img)) + return setData(ba, pmedium); + } else { + if (qt_write_dibv5(s, img)) + return setData(ba, pmedium); + } + } + return false; +} + +bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const +{ + bool isSynthesized = true; + IEnumFORMATETC *pEnum =NULL; + HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum); + if (res == S_OK && pEnum) { + FORMATETC fc; + while ((res = pEnum->Next(1, &fc, 0)) == S_OK) { + if (fc.ptd) + CoTaskMemFree(fc.ptd); + if (fc.cfFormat == CF_DIB) + break; + else if (fc.cfFormat == CF_DIBV5) { + isSynthesized = false; + break; + } + } + pEnum->Release(); + } + return !isSynthesized; +} + +QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const +{ + Q_UNUSED(preferredType); + QVariant result; + if (mimeType != QLatin1String("application/x-qt-image")) + return result; + //Try to convert from a format which has more data + //DIBV5, use only if its is not synthesized + if (canGetData(CF_DIBV5, pDataObj) && hasOriginalDIBV5(pDataObj)) { + QImage img; + QByteArray data = getData(CF_DIBV5, pDataObj); + QDataStream s(&data, QIODevice::ReadOnly); + s.setByteOrder(QDataStream::LittleEndian); + if (qt_read_dibv5(s, img)) { // #### supports only 32bit DIBV5 + return img; + } + } + //PNG, MS Office place this (undocumented) + if (canGetData(CF_PNG, pDataObj)) { + QImage img; + QByteArray data = getData(CF_PNG, pDataObj); + if (img.loadFromData(data, "PNG")) { + return img; + } + } + //Fallback to DIB + if (canGetData(CF_DIB, pDataObj)) { + QImage img; + QByteArray data = getData(CF_DIB, pDataObj); + QDataStream s(&data, QIODevice::ReadOnly); + s.setByteOrder(QDataStream::LittleEndian);// Intel byte order #### + if (qt_read_dib(s, img)) { // ##### encaps "-14" + return img; + } + } + // Failed + return result; +} +#endif + +class QBuiltInMimes : public QWindowsMime +{ +public: + QBuiltInMimes(); + + // for converting from Qt + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; + + // for converting to Qt + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; + +private: + QMap<int, QString> outFormats; + QMap<int, QString> inFormats; +}; + +QBuiltInMimes::QBuiltInMimes() +: QWindowsMime() +{ + outFormats.insert(QWindowsMime::registerMimeType(QLatin1String("application/x-color")), QLatin1String("application/x-color")); + inFormats.insert(QWindowsMime::registerMimeType(QLatin1String("application/x-color")), QLatin1String("application/x-color")); +} + +bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + // really check + return formatetc.tymed & TYMED_HGLOBAL + && outFormats.contains(formatetc.cfFormat) + && mimeData->formats().contains(outFormats.value(formatetc.cfFormat)); +} + +bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const +{ + if (canConvertFromMime(formatetc, mimeData)) { + QByteArray data; + if (outFormats.value(getCf(formatetc)) == QLatin1String("text/html")) { + // text/html is in wide chars on windows (compatible with mozillia) + QString html = mimeData->html(); + // same code as in the text converter up above + const QChar *u = html.unicode(); + QString res; + const int s = html.length(); + int maxsize = s + s/40 + 3; + res.resize(maxsize); + int ri = 0; + bool cr = false; + for (int i=0; i < s; ++i) { + if (*u == QLatin1Char('\r')) + cr = true; + else { + if (*u == QLatin1Char('\n') && !cr) + res[ri++] = QLatin1Char('\r'); + cr = false; + } + res[ri++] = *u; + if (ri+3 >= maxsize) { + maxsize += maxsize/4; + res.resize(maxsize); + } + ++u; + } + res.truncate(ri); + const int byteLength = res.length() * sizeof(ushort); + QByteArray r(byteLength + 2, '\0'); + memcpy(r.data(), res.unicode(), byteLength); + r[byteLength] = 0; + r[byteLength+1] = 0; + data = r; + } else { +#ifndef QT_NO_DRAGANDDROP + data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData); +#endif //QT_NO_DRAGANDDROP + } + return setData(data, pmedium); + } + return false; +} + +QVector<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector<FORMATETC> formatetcs; + if (!outFormats.keys(mimeType).isEmpty() && mimeData->formats().contains(mimeType)) + formatetcs += setCf(outFormats.key(mimeType)); + return formatetcs; +} + +bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + return (!inFormats.keys(mimeType).isEmpty()) + && canGetData(inFormats.key(mimeType), pDataObj); +} + +QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const +{ + QVariant val; + if (canConvertToMime(mimeType, pDataObj)) { + QByteArray data = getData(inFormats.key(mimeType), pDataObj); + if (!data.isEmpty()) { +#ifdef QMIME_DEBUG + qDebug("QBuiltInMimes::convertToMime()"); +#endif + if (mimeType == QLatin1String("text/html") && preferredType == QVariant::String) { + // text/html is in wide chars on windows (compatible with Mozilla) + val = QString::fromWCharArray((const wchar_t *)data.data()); + } else { + val = data; // it should be enough to return the data and let QMimeData do the rest. + } + } + } + return val; +} + +QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const +{ + return inFormats.value(getCf(formatetc)); +} + + +class QLastResortMimes : public QWindowsMime +{ +public: + + QLastResortMimes(); + // for converting from Qt + bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const; + bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const; + QVector<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const; + + // for converting to Qt + bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const; + QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QVariant::Type preferredType) const; + QString mimeForFormat(const FORMATETC &formatetc) const; + +private: + QMap<int, QString> formats; + static QStringList ianaTypes; + static QStringList excludeList; +}; + +QStringList QLastResortMimes::ianaTypes; +QStringList QLastResortMimes::excludeList; + +QLastResortMimes::QLastResortMimes() +{ + //MIME Media-Types + if (!ianaTypes.size()) { + ianaTypes.append(QLatin1String("application/")); + ianaTypes.append(QLatin1String("audio/")); + ianaTypes.append(QLatin1String("example/")); + ianaTypes.append(QLatin1String("image/")); + ianaTypes.append(QLatin1String("message/")); + ianaTypes.append(QLatin1String("model/")); + ianaTypes.append(QLatin1String("multipart/")); + ianaTypes.append(QLatin1String("text/")); + ianaTypes.append(QLatin1String("video/")); + } + //Types handled by other classes + if (!excludeList.size()) { + excludeList.append(QLatin1String("HTML Format")); + excludeList.append(QLatin1String("UniformResourceLocator")); + excludeList.append(QLatin1String("text/html")); + excludeList.append(QLatin1String("text/plain")); + excludeList.append(QLatin1String("text/uri-list")); + excludeList.append(QLatin1String("application/x-qt-image")); + excludeList.append(QLatin1String("application/x-color")); + } +} + +bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const +{ + // really check +#ifndef QT_NO_DRAGANDDROP + return formatetc.tymed & TYMED_HGLOBAL + && (formats.contains(formatetc.cfFormat) + && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData)); +#else + Q_UNUSED(mimeData); + Q_UNUSED(formatetc); + return formatetc.tymed & TYMED_HGLOBAL + && formats.contains(formatetc.cfFormat); +#endif //QT_NO_DRAGANDDROP +} + +bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const +{ +#ifndef QT_NO_DRAGANDDROP + return canConvertFromMime(formatetc, mimeData) + && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium); +#else + Q_UNUSED(mimeData); + Q_UNUSED(formatetc); + Q_UNUSED(pmedium); + return false; +#endif //QT_NO_DRAGANDDROP +} + +QVector<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const +{ + QVector<FORMATETC> formatetcs; + if (!formats.keys(mimeType).isEmpty()) { + formatetcs += setCf(formats.key(mimeType)); + } else if (!excludeList.contains(mimeType, Qt::CaseInsensitive)){ + // register any other available formats + int cf = QWindowsMime::registerMimeType(mimeType); + QLastResortMimes *that = const_cast<QLastResortMimes *>(this); + that->formats.insert(cf, mimeType); + formatetcs += setCf(cf); + } + return formatetcs; +} +static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\""; + +static bool isCustomMimeType(const QString &mimeType) +{ + return mimeType.startsWith(QLatin1String(x_qt_windows_mime), Qt::CaseInsensitive); +} + +static QString customMimeType(const QString &mimeType) +{ + int len = sizeof(x_qt_windows_mime) - 1; + int n = mimeType.lastIndexOf(QLatin1Char('\"'))-len; + return mimeType.mid(len, n); +} + +bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const +{ + if (isCustomMimeType(mimeType)) { + QString clipFormat = customMimeType(mimeType); + int cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16())); + return canGetData(cf, pDataObj); + } else if (formats.keys(mimeType).isEmpty()) { + // if it is not in there then register it an see if we can get it + int cf = QWindowsMime::registerMimeType(mimeType); + return canGetData(cf, pDataObj); + } else { + return canGetData(formats.key(mimeType), pDataObj); + } + return false; +} + +QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QVariant::Type preferredType) const +{ + Q_UNUSED(preferredType); + QVariant val; + if (canConvertToMime(mimeType, pDataObj)) { + QByteArray data; + if (isCustomMimeType(mimeType)) { + QString clipFormat = customMimeType(mimeType); + int cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16())); + data = getData(cf, pDataObj); + } else if (formats.keys(mimeType).isEmpty()) { + int cf = QWindowsMime::registerMimeType(mimeType); + data = getData(cf, pDataObj); + } else { + data = getData(formats.key(mimeType), pDataObj); + } + if (!data.isEmpty()) + val = data; // it should be enough to return the data and let QMimeData do the rest. + } + return val; +} + +QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const +{ + QString format = formats.value(getCf(formatetc)); + if (!format.isEmpty()) + return format; + + wchar_t buffer[256]; + int len = GetClipboardFormatName(getCf(formatetc), buffer, 256); + + if (len) { + QString clipFormat = QString::fromWCharArray(buffer, len); +#ifndef QT_NO_DRAGANDDROP + if (QInternalMimeData::canReadData(clipFormat)) + format = clipFormat; + else if((formatetc.cfFormat >= 0xC000)){ + //create the mime as custom. not registered. + if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) { + //check if this is a mime type + bool ianaType = false; + int sz = ianaTypes.size(); + for (int i = 0; i < sz; i++) { + if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) { + ianaType = true; + break; + } + } + if (!ianaType) + format = QLatin1String(x_qt_windows_mime) + clipFormat + QLatin1Char('\"'); + else + format = clipFormat; + } + } +#endif //QT_NO_DRAGANDDROP + } + + return format; +} + +QWindowsMimeList::QWindowsMimeList() + : initialized(false) +{ +} + +QWindowsMimeList::~QWindowsMimeList() +{ + while (mimes.size()) + delete mimes.first(); +} + + +void QWindowsMimeList::init() +{ + if (!initialized) { + initialized = true; +#ifndef QT_NO_IMAGEFORMAT_BMP + new QWindowsMimeImage; +#endif + new QLastResortMimes; + new QWindowsMimeText; + new QWindowsMimeURI; + + new QWindowsMimeHtml; + new QBuiltInMimes; + } +} + +void QWindowsMimeList::addWindowsMime(QWindowsMime * mime) +{ + init(); + mimes.append(mime); +} + +void QWindowsMimeList::removeWindowsMime(QWindowsMime * mime) +{ + init(); + mimes.removeAll(mime); +} + +QList<QWindowsMime*> QWindowsMimeList::windowsMimes() +{ + init(); + return mimes; +} + +#ifndef QT_NO_IMAGEFORMAT_BMP +static bool qt_write_dibv5(QDataStream &s, QImage image) +{ + QIODevice* d = s.device(); + if (!d->isWritable()) + return false; + + //depth will be always 32 + int bpl_bmp = image.width()*4; + + BMP_BITMAPV5HEADER bi ={0}; + bi.bV5Size = sizeof(BMP_BITMAPV5HEADER); + bi.bV5Width = image.width(); + bi.bV5Height = image.height(); + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5SizeImage = bpl_bmp*image.height(); + bi.bV5XPelsPerMeter = 0; + bi.bV5YPelsPerMeter = 0; + bi.bV5ClrUsed = 0; + bi.bV5ClrImportant = 0; + bi.bV5BlueMask = 0x000000ff; + bi.bV5GreenMask = 0x0000ff00; + bi.bV5RedMask = 0x00ff0000; + bi.bV5AlphaMask = 0xff000000; + bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB + bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES + + d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size); + if (s.status() != QDataStream::Ok) + return false; + + DWORD colorSpace[3] = {0x00ff0000,0x0000ff00,0x000000ff}; + d->write(reinterpret_cast<const char*>(colorSpace), sizeof(colorSpace)); + if (s.status() != QDataStream::Ok) + return false; + + if (image.format() != QImage::Format_ARGB32) + image = image.convertToFormat(QImage::Format_ARGB32); + + uchar *buf = new uchar[bpl_bmp]; + uchar *b; + + memset(buf, 0, bpl_bmp); + for (int y=image.height()-1; y>=0; y--) { + // write the image bits + QRgb *p = (QRgb *)image.scanLine(y); + QRgb *end = p + image.width(); + b = buf; + while (p < end) { + int alpha = qAlpha(*p); + if (alpha) { + *b++ = qBlue(*p); + *b++ = qGreen(*p); + *b++ = qRed(*p); + } else { + //white for fully transparent pixels. + *b++ = 0xff; + *b++ = 0xff; + *b++ = 0xff; + } + *b++ = alpha; + p++; + } + d->write((char*)buf, bpl_bmp); + if (s.status() != QDataStream::Ok) { + delete[] buf; + return false; + } + } + delete[] buf; + return true; +} + +static int calc_shift(int mask) +{ + int result = 0; + while (!(mask & 1)) { + result++; + mask >>= 1; + } + return result; +} + +//Supports only 32 bit DIBV5 +static bool qt_read_dibv5(QDataStream &s, QImage &image) +{ + BMP_BITMAPV5HEADER bi; + QIODevice* d = s.device(); + if (d->atEnd()) + return false; + + d->read((char *)&bi, sizeof(bi)); // read BITMAPV5HEADER header + if (s.status() != QDataStream::Ok) + return false; + + int nbits = bi.bV5BitCount; + int comp = bi.bV5Compression; + if (nbits != 32 || bi.bV5Planes != 1 || comp != BMP_BITFIELDS) + return false; //Unsupported DIBV5 format + + int w = bi.bV5Width, h = bi.bV5Height; + int red_mask = bi.bV5RedMask; + int green_mask = bi.bV5GreenMask; + int blue_mask = bi.bV5BlueMask; + int alpha_mask = bi.bV5AlphaMask; + int red_shift = 0; + int green_shift = 0; + int blue_shift = 0; + int alpha_shift = 0; + QImage::Format format = QImage::Format_ARGB32; + + if (bi.bV5Height < 0) + h = -h; // support images with negative height + if (image.size() != QSize(w, h) || image.format() != format) { + image = QImage(w, h, format); + if (image.isNull()) // could not create image + return false; + } + image.setDotsPerMeterX(bi.bV5XPelsPerMeter); + image.setDotsPerMeterY(bi.bV5YPelsPerMeter); + // read color table + DWORD colorSpace[3]; + if (d->read((char *)colorSpace, sizeof(colorSpace)) != sizeof(colorSpace)) + return false; + + red_shift = calc_shift(red_mask); + green_shift = calc_shift(green_mask); + blue_shift = calc_shift(blue_mask); + if (alpha_mask) { + alpha_shift = calc_shift(alpha_mask); + } + + int bpl = image.bytesPerLine(); + uchar *data = image.bits(); + register QRgb *p; + QRgb *end; + uchar *buf24 = new uchar[bpl]; + int bpl24 = ((w*nbits+31)/32)*4; + uchar *b; + unsigned int c; + + while (--h >= 0) { + p = (QRgb *)(data + h*bpl); + end = p + w; + if (d->read((char *)buf24,bpl24) != bpl24) + break; + b = buf24; + while (p < end) { + c = *b | (*(b+1))<<8 | (*(b+2))<<16 | (*(b+3))<<24; + *p++ = qRgba(((c & red_mask) >> red_shift) , + ((c & green_mask) >> green_shift), + ((c & blue_mask) >> blue_shift), + ((c & alpha_mask) >> alpha_shift)); + b += 4; + } + } + delete[] buf24; + + if (bi.bV5Height < 0) { + // Flip the image + uchar *buf = new uchar[bpl]; + h = -bi.bV5Height; + for (int y = 0; y < h/2; ++y) { + memcpy(buf, data + y*bpl, bpl); + memcpy(data + y*bpl, data + (h-y-1)*bpl, bpl); + memcpy(data + (h-y-1)*bpl, buf, bpl); + } + delete [] buf; + } + + return true; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qmotifdnd_x11.cpp b/src/gui/kernel/qmotifdnd_x11.cpp new file mode 100644 index 0000000000..eef4cc470b --- /dev/null +++ b/src/gui/kernel/qmotifdnd_x11.cpp @@ -0,0 +1,1031 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* The following copyright notice pertains to the code as contributed +to Qt, not to Nokia's modifications. It is replicated +in doc/dnd.doc, where the documentation system can see it. */ + +/* Copyright 1996 Daniel Dardailler. + + Permission to use, copy, modify, distribute, and sell this software + for any purpose is hereby granted without fee, provided that the above + copyright notice appear in all copies and that both that copyright + notice and this permission notice appear in supporting documentation, + and that the name of Daniel Dardailler not be used in advertising or + publicity pertaining to distribution of the software without specific, + written prior permission. Daniel Dardailler makes no representations + about the suitability of this software for any purpose. It is + provided "as is" without express or implied warranty. + + Modifications Copyright 1999 Matt Koss, under the same license as + above. +************************************************************/ + +/***********************************************************/ +/* Motif Drag&Drop Dynamic Protocol messaging API code */ +/* Only requires Xlib layer - not MT safe */ +/* Author: Daniel Dardailler, daniel@x.org */ +/* Adapted by: Matt Koss, koss@napri.sk */ +/* Further adaptions by: Nokia Corporation and/or its subsidiary(-ies) */ +/***********************************************************/ + +#include "qplatformdefs.h" + +#include "qapplication.h" + +#ifndef QT_NO_DRAGANDDROP + +#include "qdebug.h" +#include "qtextcodec.h" +#include "qwidget.h" +#include "qevent.h" +#include "qt_x11_p.h" +#include "qx11info_x11.h" +#include "qiodevice.h" +#include "qdnd_p.h" + +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +static Window sourceWindow = XNone; +static QWidget *dropWidget = 0; +static Qt::DropAction lastAcceptedAction = Qt::IgnoreAction; + +static Atom Dnd_selection = 0; +static Time Dnd_selection_time; + +static Atom * src_targets ; +static ushort num_src_targets ; + +// Motif definitions +#define DndVersion 1 +#define DndRevision 0 +#define DndIncludeVersion (DndVersion * 10 + DndRevision) + +/* The following values are used in the DndData structure */ + +/* protocol style */ +#define DND_DRAG_NONE 0 +#define DND_DRAG_DROP_ONLY 1 +#define DND_DRAG_DYNAMIC 5 + +/* message type */ +#define DND_TOP_LEVEL_ENTER 0 +#define DND_TOP_LEVEL_LEAVE 1 +#define DND_DRAG_MOTION 2 +#define DND_DROP_SITE_ENTER 3 +#define DND_DROP_SITE_LEAVE 4 +#define DND_DROP_START 5 +#define DND_OPERATION_CHANGED 8 + +/* operation(s) */ +#define DND_NOOP 0L +#define DND_MOVE (1L << 0) +#define DND_COPY (1L << 1) +#define DND_LINK (1L << 2) + +static Qt::DropActions DndOperationsToQtDropActions(uchar op) +{ + Qt::DropActions actions = Qt::IgnoreAction; + if (op | DND_MOVE) + actions |= Qt::MoveAction; + if (op | DND_COPY) + actions |= Qt::CopyAction; + if (op | DND_LINK) + actions |= Qt::LinkAction; + return actions; +} + +static uchar QtDropActionToDndOperation(Qt::DropAction action) +{ + switch (action & Qt::ActionMask) { + case Qt::CopyAction: + default: + return DND_COPY; + case Qt::MoveAction: + return DND_MOVE; + case Qt::LinkAction: + return DND_LINK; + } +} + + +/* status */ +#define DND_NO_DROP_SITE 1 +#define DND_INVALID_DROP_SITE 2 +#define DND_VALID_DROP_SITE 3 + +/* completion */ +#define DND_DROP 0 +#define DND_DROP_HELP 1 +#define DND_DROP_CANCEL 2 + +#define BYTE unsigned char +#define CARD32 unsigned int +#define CARD16 unsigned short +#define INT16 signed short + +/* Client side structure used in the API */ +typedef struct { + unsigned char reason; /* message type: DND_TOP_LEVEL_ENTER, etc */ + Time time ; + unsigned char operation; + unsigned char operations; + unsigned char status; + unsigned char completion; + short x ; + short y ; + Window src_window ; + Atom property ; +} DndData ; + + +typedef struct _DndSrcProp { + BYTE byte_order ; + BYTE protocol_version ; + CARD16 target_index ; + CARD32 selection ; +} DndSrcProp ; + +typedef struct _DndReceiverProp { + BYTE byte_order ; + BYTE protocol_version ; + BYTE protocol_style ; + BYTE pad1; + CARD32 proxy_window; + CARD16 num_drop_sites ; + CARD16 pad2; + CARD32 total_size; +} DndReceiverProp ; + +/* need to use some union hack since window and property are in + different order depending on the message ... */ +typedef struct _DndTop { + CARD32 src_window; + CARD32 property; +} DndTop ; + +typedef struct _DndPot { + INT16 x; + INT16 y; + CARD32 property; + CARD32 src_window; +} DndPot ; + +typedef struct _DndMessage { + BYTE reason; + BYTE byte_order; + CARD16 flags; + CARD32 time; + union { + DndTop top ; + DndPot pot ; + } data ; +} DndMessage ; + +typedef struct { + BYTE byte_order; + BYTE protocol_version; + CARD16 num_target_lists; + CARD32 data_size; + /* then come series of CARD16,CARD32,CARD32,CARD32... */ +} DndTargets; + + +/* protocol version */ +#define DND_PROTOCOL_VERSION 0 + + +#define DND_EVENT_TYPE_MASK ((BYTE)0x80) +#define DND_EVENT_TYPE_SHIFT 7 +#define DND_CLEAR_EVENT_TYPE ((BYTE)0x7F) + +/* message_type is data[0] of the client_message + this return 1 (receiver bit up) or 0 (initiator) */ +#define DND_GET_EVENT_TYPE(message_type) \ +((char) (((message_type) & DND_EVENT_TYPE_MASK) >> DND_EVENT_TYPE_SHIFT)) + +/* event_type can be 0 (initiator) or 1 (receiver) */ +#define DND_SET_EVENT_TYPE(event_type) \ +(((BYTE)(event_type) << DND_EVENT_TYPE_SHIFT) & DND_EVENT_TYPE_MASK) + + +#define DND_OPERATION_MASK ((CARD16) 0x000F) +#define DND_OPERATION_SHIFT 0 +#define DND_STATUS_MASK ((CARD16) 0x00F0) +#define DND_STATUS_SHIFT 4 +#define DND_OPERATIONS_MASK ((CARD16) 0x0F00) +#define DND_OPERATIONS_SHIFT 8 +#define DND_COMPLETION_MASK ((CARD16) 0xF000) +#define DND_COMPLETION_SHIFT 12 + +#define DND_GET_OPERATION(flags) \ +((unsigned char) \ +(((flags) & DND_OPERATION_MASK) >> DND_OPERATION_SHIFT)) + +#define DND_SET_OPERATION(operation) \ +(((CARD16)(operation) << DND_OPERATION_SHIFT)\ +& DND_OPERATION_MASK) + +#define DND_GET_STATUS(flags) \ +((unsigned char) \ +(((flags) & DND_STATUS_MASK) >> DND_STATUS_SHIFT)) + +#define DND_SET_STATUS(status) \ +(((CARD16)(status) << DND_STATUS_SHIFT)\ +& DND_STATUS_MASK) + +#define DND_GET_OPERATIONS(flags) \ +((unsigned char) \ +(((flags) & DND_OPERATIONS_MASK) >> DND_OPERATIONS_SHIFT)) + +#define DND_SET_OPERATIONS(operation) \ +(((CARD16)(operation) << DND_OPERATIONS_SHIFT)\ +& DND_OPERATIONS_MASK) + +#define DND_GET_COMPLETION(flags) \ +((unsigned char) \ +(((flags) & DND_COMPLETION_MASK) >> DND_COMPLETION_SHIFT)) + +#define DND_SET_COMPLETION(completion) \ +(((CARD16)(completion) << DND_COMPLETION_SHIFT)\ +& DND_COMPLETION_MASK) + + +#define SWAP4BYTES(l) {\ +struct { unsigned t :32;} bit32;\ +char n, *tp = (char *) &bit32;\ +bit32.t = l;\ +n = tp[0]; tp[0] = tp[3]; tp[3] = n;\ +n = tp[1]; tp[1] = tp[2]; tp[2] = n;\ +l = bit32.t;\ +} + +#define SWAP2BYTES(s) {\ +struct { unsigned t :16; } bit16;\ +char n, *tp = (char *) &bit16;\ +bit16.t = s;\ +n = tp[0]; tp[0] = tp[1]; tp[1] = n;\ +s = bit16.t;\ +} + + +/** Private extern functions */ + +static unsigned char DndByteOrder (); + + +/***** Targets/Index stuff */ + +typedef struct { + int num_targets; + Atom *targets; +} DndTargetsTableEntryRec, * DndTargetsTableEntry; + +typedef struct { + int num_entries; + DndTargetsTableEntry entries; +} DndTargetsTableRec, * DndTargetsTable; + + +static ushort _DndIndexToTargets(Display * display, + int index, + Atom ** targets); + +extern void qt_x11_intern_atom(const char *, Atom *); + +///////////////////////////////////////////////////////////////// + +static unsigned char DndByteOrder () +{ + static unsigned char byte_order = 0; + + if (!byte_order) { + unsigned int endian = 1; + byte_order = (*((char *)&endian))?'l':'B'; + } + return byte_order ; +} + + + +static void DndReadSourceProperty(Display * dpy, + Window window, Atom dnd_selection, + Atom ** targets, unsigned short * num_targets) +{ + unsigned char *retval = 0; + Atom type ; + int format ; + unsigned long bytesafter, lengthRtn; + + if ((XGetWindowProperty (dpy, window, dnd_selection, 0L, 100000L, + False, ATOM(_MOTIF_DRAG_INITIATOR_INFO), &type, + &format, &lengthRtn, &bytesafter, + &retval) != Success) + || (type == XNone)) { + *num_targets = 0; + return ; + } + + DndSrcProp * src_prop = (DndSrcProp *)retval; + + if (src_prop->byte_order != DndByteOrder()) { + SWAP2BYTES(src_prop->target_index); + SWAP4BYTES(src_prop->selection); + } + + *num_targets = _DndIndexToTargets(dpy, src_prop->target_index, targets); + + XFree((char*)src_prop); +} + + +/* Position the _MOTIF_DRAG_RECEIVER_INFO property on the dropsite window. + Called by the receiver of the drop to indicate the + supported protocol style : dynamic, drop_only or none */ +static void DndWriteReceiverProperty(Display * dpy, Window window, + unsigned char protocol_style) +{ + DndReceiverProp receiver_prop; + + // squelch potential valgrind errors about uninitialized reads + memset(&receiver_prop, 0, sizeof(receiver_prop)); + + receiver_prop.byte_order = DndByteOrder() ; + receiver_prop.protocol_version = DND_PROTOCOL_VERSION; + receiver_prop.protocol_style = protocol_style ; + receiver_prop.proxy_window = XNone ; + receiver_prop.num_drop_sites = 0 ; + receiver_prop.total_size = sizeof(DndReceiverProp); + + /* write the buffer to the property */ + XChangeProperty (dpy, window, ATOM(_MOTIF_DRAG_RECEIVER_INFO), ATOM(_MOTIF_DRAG_RECEIVER_INFO), + 8, PropModeReplace, + (unsigned char *)&receiver_prop, + sizeof(DndReceiverProp)); +} + + +/* protocol style equiv (preregister stuff really) */ +#define DND_DRAG_DROP_ONLY_EQUIV 3 +#define DND_DRAG_DYNAMIC_EQUIV1 2 +#define DND_DRAG_DYNAMIC_EQUIV2 4 + + +/* Produce a client message to be sent by the caller */ +static void DndFillClientMessage(Display * dpy, Window window, + XClientMessageEvent *cm, + DndData * dnd_data, + char receiver) +{ + DndMessage * dnd_message = (DndMessage*)&cm->data.b[0] ; + + cm->display = dpy; + cm->type = ClientMessage; + cm->serial = LastKnownRequestProcessed(dpy); + cm->send_event = True; + cm->window = window; + cm->format = 8; + cm->message_type = ATOM(_MOTIF_DRAG_AND_DROP_MESSAGE); + + dnd_message->reason = dnd_data->reason | DND_SET_EVENT_TYPE(receiver); + + dnd_message->byte_order = DndByteOrder(); + + /* we're filling in flags with more stuff that necessary, + depending on the reason, but it doesn't matter */ + dnd_message->flags = 0 ; + dnd_message->flags |= DND_SET_STATUS(dnd_data->status) ; + dnd_message->flags |= DND_SET_OPERATION(dnd_data->operation) ; + dnd_message->flags |= DND_SET_OPERATIONS(dnd_data->operations) ; + dnd_message->flags |= DND_SET_COMPLETION(dnd_data->completion) ; + + dnd_message->time = dnd_data->time ; + + switch(dnd_data->reason) { + case DND_DROP_SITE_LEAVE: break ; + case DND_TOP_LEVEL_ENTER: + case DND_TOP_LEVEL_LEAVE: + dnd_message->data.top.src_window = dnd_data->src_window ; + dnd_message->data.top.property = dnd_data->property ; + break ; /* cannot fall through since the byte layout is different in + both set of messages, see top and pot union stuff */ + + case DND_DRAG_MOTION: + case DND_OPERATION_CHANGED: + case DND_DROP_SITE_ENTER: + case DND_DROP_START: + dnd_message->data.pot.x = dnd_data->x ; /* mouse position */ + dnd_message->data.pot.y = dnd_data->y ; + dnd_message->data.pot.src_window = dnd_data->src_window ; + dnd_message->data.pot.property = dnd_data->property ; + break ; + default: + break ; + } + +} + +static Bool DndParseClientMessage(XClientMessageEvent *cm, DndData * dnd_data, + char * receiver) +{ + DndMessage * dnd_message = (DndMessage*)&cm->data.b[0] ; + + if (cm->message_type != ATOM(_MOTIF_DRAG_AND_DROP_MESSAGE)) { + return False ; + } + + if (dnd_message->byte_order != DndByteOrder()) { + SWAP2BYTES(dnd_message->flags); + SWAP4BYTES(dnd_message->time); + } /* do the rest in the switch */ + + dnd_data->reason = dnd_message->reason ; + if (DND_GET_EVENT_TYPE(dnd_data->reason)) + *receiver = 1 ; + else + *receiver = 0 ; + dnd_data->reason &= DND_CLEAR_EVENT_TYPE ; + + dnd_data->time = dnd_message->time ; + + /* we're reading in more stuff that necessary. but who cares */ + dnd_data->status = DND_GET_STATUS(dnd_message->flags) ; + dnd_data->operation = DND_GET_OPERATION(dnd_message->flags) ; + dnd_data->operations = DND_GET_OPERATIONS(dnd_message->flags) ; + dnd_data->completion = DND_GET_COMPLETION(dnd_message->flags) ; + + switch(dnd_data->reason) { + case DND_TOP_LEVEL_ENTER: + case DND_TOP_LEVEL_LEAVE: + if (dnd_message->byte_order != DndByteOrder()) { + SWAP4BYTES(dnd_message->data.top.src_window); + SWAP4BYTES(dnd_message->data.top.property); + } + dnd_data->src_window = dnd_message->data.top.src_window ; + dnd_data->property = dnd_message->data.top.property ; + break ; /* cannot fall through, see above comment in write msg */ + + case DND_DRAG_MOTION: + case DND_OPERATION_CHANGED: + case DND_DROP_SITE_ENTER: + case DND_DROP_START: + if (dnd_message->byte_order != DndByteOrder()) { + SWAP2BYTES(dnd_message->data.pot.x); + SWAP2BYTES(dnd_message->data.pot.y); + SWAP4BYTES(dnd_message->data.pot.property); + SWAP4BYTES(dnd_message->data.pot.src_window); + } + dnd_data->x = dnd_message->data.pot.x ; + dnd_data->y = dnd_message->data.pot.y ; + dnd_data->property = dnd_message->data.pot.property ; + dnd_data->src_window = dnd_message->data.pot.src_window ; + break ; + + case DND_DROP_SITE_LEAVE: + break; + default: + break ; + } + + return True ; +} + + +static Window MotifWindow(Display *display) +{ + Atom type; + int format; + unsigned long size; + unsigned long bytes_after; + unsigned char *property = 0; + Window motif_window ; + + /* this version does no caching, so it's slow: round trip each time */ + + if ((XGetWindowProperty (display, RootWindow(display, 0), + ATOM(_MOTIF_DRAG_WINDOW), + 0L, 100000L, False, AnyPropertyType, + &type, &format, &size, &bytes_after, + &property) == Success) && + (type != XNone)) { + motif_window = *(Window *)property; + } else { + XSetWindowAttributes sAttributes; + + /* really, this should be done on a separate connection, + with XSetCloseDownMode (RetainPermanent), so that + others don't have to recreate it; hopefully, some real + Motif application will be around to do it */ + + sAttributes.override_redirect = True; + sAttributes.event_mask = PropertyChangeMask; + motif_window = XCreateWindow (display, + RootWindow (display, 0), + -170, -560, 1, 1, 0, 0, + InputOnly, CopyFromParent, + (CWOverrideRedirect |CWEventMask), + &sAttributes); + XMapWindow (display, motif_window); + } + + if (property) { + XFree ((char *)property); + } + + return (motif_window); +} + + +static DndTargetsTable TargetsTable(Display *display) +{ + Atom type; + int format; + unsigned long size; + unsigned long bytes_after; + Window motif_window = MotifWindow(display) ; + unsigned char *retval; + DndTargetsTable targets_table ; + int i,j ; + char * target_data ; + + /* this version does no caching, so it's slow: round trip each time */ + /* ideally, register for property notify on this target_list + atom and update when necessary only */ + + if ((XGetWindowProperty (display, motif_window, + ATOM(_MOTIF_DRAG_TARGETS), 0L, 100000L, + False, ATOM(_MOTIF_DRAG_TARGETS), + &type, &format, &size, &bytes_after, + &retval) != Success) || + type == XNone) { + qWarning("QMotifDND: Cannot get property on Motif window"); + return 0; + } + + DndTargets * target_prop = (DndTargets *)retval; + + if (target_prop->protocol_version != DND_PROTOCOL_VERSION) { + qWarning("QMotifDND: Protocol mismatch"); + } + + if (target_prop->byte_order != DndByteOrder()) { + /* need to swap num_target_lists and size */ + SWAP2BYTES(target_prop->num_target_lists); + SWAP4BYTES(target_prop->data_size); + } + + /* now parse DndTarget prop data in a TargetsTable */ + + targets_table = (DndTargetsTable)malloc(sizeof(DndTargetsTableRec)); + targets_table->num_entries = target_prop->num_target_lists ; + targets_table->entries = (DndTargetsTableEntry) + malloc(sizeof(DndTargetsTableEntryRec) * target_prop->num_target_lists); + + target_data = (char*)target_prop + sizeof(*target_prop) ; + + for (i = 0 ; i < targets_table->num_entries; i++) { + CARD16 num_targets ; + CARD32 atom ; + + memcpy(&num_targets, target_data, 2); + target_data += 2; + + /* potential swap needed here */ + if (target_prop->byte_order != DndByteOrder()) + SWAP2BYTES(num_targets); + + targets_table->entries[i].num_targets = num_targets ; + targets_table->entries[i].targets = (Atom *) + malloc(sizeof(Atom) * targets_table->entries[i].num_targets); + + + for (j = 0; j < num_targets; j++) { + memcpy(&atom, target_data, 4); + target_data += 4; + + /* another potential swap needed here */ + if (target_prop->byte_order != DndByteOrder()) + SWAP4BYTES(atom); + + targets_table->entries[i].targets[j] = (Atom) atom ; + } + } + + if (target_prop) { + XFree((char *)target_prop); + } + + return targets_table ; +} + + +static ushort _DndIndexToTargets(Display * display, + int index, + Atom ** targets) +{ + DndTargetsTable targets_table; + int i ; + + /* again, slow: no caching here, alloc/free each time */ + + if (!(targets_table = TargetsTable (display)) || + (index >= targets_table->num_entries)) { + if (targets_table) + XFree((char*)targets_table); + return 0; + } + + /* transfer the correct target list index */ + *targets = (Atom*)malloc(sizeof(Atom)*targets_table-> + entries[index].num_targets); + memcpy((char*)*targets, + (char*)targets_table->entries[index].targets, + sizeof(Atom)*targets_table->entries[index].num_targets); + + /* free the target table and its guts */ + for (i=0 ; i < targets_table->num_entries; i++) + XFree((char*)targets_table->entries[i].targets); + + int tmp = targets_table->entries[index].num_targets; + XFree((char*)targets_table); + + return tmp; // targets_table->entries[index].num_targets; +} + + +QByteArray QX11Data::motifdndFormat(int n) +{ + if (!motifdnd_active) + return 0; // should not happen + + if (n >= num_src_targets) + return 0; + + Atom target = src_targets[n]; + + if (target == XA_STRING) + return "text/plain;charset=ISO-8859-1"; + if (target == ATOM(UTF8_STRING)) + return "text/plain;charset=UTF-8"; + if (target == ATOM(COMPOUND_TEXT)) + return QByteArray("text/plain;charset=") + QTextCodec::codecForLocale()->name(); + if (target == ATOM(TEXT)) + return "text/plain"; + + return ("x-motif-dnd/" + X11->xdndAtomToString(target)); +} + + +QVariant QX11Data::motifdndObtainData(const char *mimeType) +{ + QByteArray result; + + if (Dnd_selection == 0 || !dropWidget) + return result; + + // try to convert the selection to the requested property + // qDebug("trying to convert to '%s'", mimeType); + + int n=0; + QByteArray f; + do { + f = motifdndFormat(n); + if (f.isEmpty()) + return result; + n++; + } while(qstricmp(mimeType, f.data())); + + Atom conversion_type = XNone; + if (f == "text/plain;charset=ISO-8859-1") { + conversion_type = XA_STRING; + } else if (f == "text/plain;charset=UTF-8") { + conversion_type = ATOM(UTF8_STRING); + } else if (f == (QByteArray("text/plain;charset=") + QTextCodec::codecForLocale()->name())) { + conversion_type = ATOM(COMPOUND_TEXT); + } else if (f == "text/plain") { + conversion_type = ATOM(TEXT); + } else if (f.startsWith("x-motif-dnd/")) { + // strip off the "x-motif-dnd/" prefix + conversion_type = X11->xdndStringToAtom(f.remove(0, 12)); + } + + if (XGetSelectionOwner(X11->display, Dnd_selection) == XNone) { + return result; // should never happen? + } + + QWidget* tw = dropWidget; + if ((dropWidget->windowType() == Qt::Desktop)) { + tw = new QWidget; + } + + // convert selection to the appropriate type + XConvertSelection (X11->display, Dnd_selection, conversion_type, + Dnd_selection, tw->internalWinId(), Dnd_selection_time); + + XFlush(X11->display); + + XEvent xevent; + bool got=X11->clipboardWaitForEvent(tw->internalWinId(), SelectionNotify, &xevent, 5000); + if (got) { + Atom type; + + if (X11->clipboardReadProperty(tw->internalWinId(), Dnd_selection, true, &result, 0, &type, 0)) { + } + } + + // we have to convert selection in order to indicate success to the initiator + XConvertSelection (X11->display, Dnd_selection, ATOM(XmTRANSFER_SUCCESS), + Dnd_selection, tw->internalWinId(), Dnd_selection_time); + + // wait again for SelectionNotify event + X11->clipboardWaitForEvent(tw->internalWinId(), SelectionNotify, &xevent, 5000); + + if ((dropWidget->windowType() == Qt::Desktop)) { + delete tw; + } + + return result; +} + + +void QX11Data::motifdndEnable(QWidget *widget, bool) +{ + DndWriteReceiverProperty(display, widget->internalWinId(), DND_DRAG_DYNAMIC); +} + + +void QX11Data::motifdndHandle(QWidget *widget, const XEvent * xe, bool /* passive */) +{ + XEvent event = *xe; + XClientMessageEvent cm ; + DndData dnd_data ; + char receiver ; + + if (!(DndParseClientMessage ((XClientMessageEvent*)&event, + &dnd_data, &receiver))) { + return; + } + + switch (dnd_data.reason) { + + case DND_DRAG_MOTION: + { + QPoint p = widget->mapFromGlobal(QPoint(dnd_data.x, dnd_data.y)); + QWidget *c = widget->childAt(p); + + if (!c || !c->acceptDrops()) { + // not over a drop site + if (dropWidget) { + QDragLeaveEvent dragLeaveEvent; + QApplication::sendEvent(dropWidget, &dragLeaveEvent); + + dropWidget = 0; + lastAcceptedAction = Qt::IgnoreAction; + + dnd_data.reason = DND_DROP_SITE_LEAVE; + dnd_data.time = X11->time; + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, receiver); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm) ; + } else { + dnd_data.reason = DND_DRAG_MOTION; + dnd_data.status = DND_NO_DROP_SITE; + dnd_data.time = X11->time; + dnd_data.operation = DND_NOOP; + dnd_data.operations = DND_NOOP; + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, receiver); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm) ; + } + } else { + Q_ASSERT(c != 0); + p = c->mapFrom(widget, p); + + if (dropWidget != c) { + if (dropWidget) { + QDragLeaveEvent le; + QApplication::sendEvent(dropWidget, &le); + } + + dropWidget = c; + lastAcceptedAction = Qt::IgnoreAction; + + const Qt::DropActions possibleActions = + DndOperationsToQtDropActions(dnd_data.operations); + QDragEnterEvent de(p, possibleActions, QDragManager::self()->dropData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(dropWidget, &de); + + dnd_data.reason = DND_DROP_SITE_ENTER; + dnd_data.time = X11->time; + if (de.isAccepted()) { + lastAcceptedAction = de.dropAction(); + + dnd_data.status = DND_VALID_DROP_SITE; + dnd_data.operation = QtDropActionToDndOperation(lastAcceptedAction); + } else { + dnd_data.status = DND_INVALID_DROP_SITE; + dnd_data.operation = DND_NOOP; + dnd_data.operations = DND_NOOP; + } + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, receiver); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm); + } else { + const Qt::DropActions possibleActions = + DndOperationsToQtDropActions(dnd_data.operations); + QDragMoveEvent me(p, possibleActions, QDragManager::self()->dropData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + if (lastAcceptedAction != Qt::IgnoreAction) { + me.setDropAction(lastAcceptedAction); + me.accept(); + } + QApplication::sendEvent(dropWidget, &me); + + dnd_data.reason = DND_DRAG_MOTION; + dnd_data.time = X11->time; + + if (me.isAccepted()) { + lastAcceptedAction = me.dropAction(); + + dnd_data.status = DND_VALID_DROP_SITE; + dnd_data.operation = QtDropActionToDndOperation(lastAcceptedAction); + } else { + dnd_data.status = DND_INVALID_DROP_SITE; + dnd_data.operation = DND_NOOP; + dnd_data.operations = DND_NOOP; + } + + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, receiver); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm); + } + } + + break; + } + + case DND_TOP_LEVEL_ENTER: + { + /* get the size of our drop site for later use */ + + motifdnd_active = true; + sourceWindow = dnd_data.src_window; + + /* no answer needed, just read source property */ + DndReadSourceProperty (event.xclient.display, + sourceWindow, + dnd_data.property, + &src_targets, &num_src_targets); + + break; + } + + case DND_TOP_LEVEL_LEAVE: + { + XEvent nextEvent; + if (XCheckTypedWindowEvent(X11->display, widget->winId(), ClientMessage, &nextEvent)) { + // we just want to check, not eat (should use XPeekIfEvent) + XPutBackEvent(X11->display, &nextEvent); + + if (DndParseClientMessage (&nextEvent.xclient, &dnd_data, &receiver) + && dnd_data.reason == DND_DROP_START) { + // expecting drop next, keeping DnD alive + break; + } + } + + // not expecting drop, need to send drag leave events and such here + if (dropWidget) { + QDragLeaveEvent le; + QApplication::sendEvent(dropWidget, &le); + } + + sourceWindow = XNone; + dropWidget = 0; + lastAcceptedAction = Qt::IgnoreAction; + + motifdnd_active = false; + + break; + } + + case DND_OPERATION_CHANGED: + // ### need to echo + break; + + case DND_DROP_START: + { + Q_ASSERT(motifdnd_active); + Q_ASSERT(sourceWindow == dnd_data.src_window); + + if (!dropWidget || lastAcceptedAction == Qt::IgnoreAction) { + // echo DROP_START + dnd_data.reason = DND_DROP_START; + dnd_data.status = DND_NO_DROP_SITE; + dnd_data.operation = DND_NOOP; + dnd_data.operations = DND_NOOP; + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, 0); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm); + + // we have to convert selection in order to indicate failure to the initiator + XConvertSelection (X11->display, dnd_data.property, ATOM(XmTRANSFER_FAILURE), + dnd_data.property, dnd_data.src_window, dnd_data.time); + + if (dropWidget) { + QDragLeaveEvent e; + QApplication::sendEvent(dropWidget, &e); + } + + motifdnd_active = false; + sourceWindow = XNone; + dropWidget = 0; + lastAcceptedAction = Qt::IgnoreAction; + + return; + } + + // store selection and its time + Dnd_selection = dnd_data.property; + Dnd_selection_time = dnd_data.time; + + QPoint p(dnd_data.x, dnd_data.y); + QDropEvent de(dropWidget->mapFromGlobal(p), Qt::CopyAction, QDragManager::self()->dropData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + if (lastAcceptedAction != Qt::IgnoreAction) { + de.setDropAction(lastAcceptedAction); + de.accept(); + } + QApplication::sendEvent(dropWidget, &de); + + // reset + Dnd_selection = XNone; + Dnd_selection_time = 0; + + // echo DROP_START depending on the result of the dropEvent + if (de.isAccepted()) { + dnd_data.reason = DND_DROP_START; + dnd_data.status = DND_VALID_DROP_SITE; + dnd_data.operation = QtDropActionToDndOperation(de.dropAction()); + } else { + dnd_data.reason = DND_DROP_START; + dnd_data.status = DND_NO_DROP_SITE; + dnd_data.operation = DND_NOOP; + dnd_data.operations = DND_NOOP; + } + DndFillClientMessage (event.xclient.display, sourceWindow, &cm, &dnd_data, 0); + XSendEvent(event.xbutton.display, sourceWindow, False, 0, (XEvent *)&cm); + + sourceWindow = XNone; + dropWidget = 0; + lastAcceptedAction = Qt::IgnoreAction; + + motifdnd_active = false; + + break; + } + + default: + break; + } // end of switch (dnd_data.reason) +} + +QT_END_NAMESPACE + +#endif // QT_NO_DRAGANDDROP diff --git a/src/gui/kernel/qmultitouch_mac.mm b/src/gui/kernel/qmultitouch_mac.mm new file mode 100644 index 0000000000..d9e845a01c --- /dev/null +++ b/src/gui/kernel/qmultitouch_mac.mm @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qmultitouch_mac_p.h> +#include <qcursor.h> + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; +QPointF QCocoaTouch::_screenReferencePos; +QPointF QCocoaTouch::_trackpadReferencePos; +int QCocoaTouch::_idAssignmentCount = 0; +int QCocoaTouch::_touchCount = 0; +bool QCocoaTouch::_updateInternalStateOnly = true; + +QCocoaTouch::QCocoaTouch(NSTouch *nstouch) +{ + if (_currentTouches.size() == 0) + _idAssignmentCount = 0; + + _touchPoint.setId(_idAssignmentCount++); + _touchPoint.setPressure(1.0); + _identity = qint64([nstouch identity]); + _currentTouches.insert(_identity, this); + updateTouchData(nstouch, NSTouchPhaseBegan); +} + +QCocoaTouch::~QCocoaTouch() +{ + _currentTouches.remove(_identity); +} + +void QCocoaTouch::updateTouchData(NSTouch *nstouch, NSTouchPhase phase) +{ + if (_touchCount == 1) + _touchPoint.setState(toTouchPointState(phase) | Qt::TouchPointPrimary); + else + _touchPoint.setState(toTouchPointState(phase)); + + // From the normalized position on the trackpad, calculate + // where on screen the touchpoint should be according to the + // reference position: + NSPoint npos = [nstouch normalizedPosition]; + QPointF qnpos = QPointF(npos.x, 1 - npos.y); + _touchPoint.setNormalizedPos(qnpos); + + if (_touchPoint.id() == 0 && phase == NSTouchPhaseBegan) { + _trackpadReferencePos = qnpos; + _screenReferencePos = QCursor::pos(); + } + + NSSize dsize = [nstouch deviceSize]; + float ppiX = (qnpos.x() - _trackpadReferencePos.x()) * dsize.width; + float ppiY = (qnpos.y() - _trackpadReferencePos.y()) * dsize.height; + QPointF relativePos = _trackpadReferencePos - QPointF(ppiX, ppiY); + _touchPoint.setScreenPos(_screenReferencePos - relativePos); +} + +QCocoaTouch *QCocoaTouch::findQCocoaTouch(NSTouch *nstouch) +{ + qint64 identity = qint64([nstouch identity]); + if (_currentTouches.contains(identity)) + return _currentTouches.value(identity); + return 0; +} + +Qt::TouchPointState QCocoaTouch::toTouchPointState(NSTouchPhase nsState) +{ + Qt::TouchPointState qtState = Qt::TouchPointReleased; + switch (nsState) { + case NSTouchPhaseBegan: + qtState = Qt::TouchPointPressed; + break; + case NSTouchPhaseMoved: + qtState = Qt::TouchPointMoved; + break; + case NSTouchPhaseStationary: + qtState = Qt::TouchPointStationary; + break; + case NSTouchPhaseEnded: + case NSTouchPhaseCancelled: + qtState = Qt::TouchPointReleased; + break; + default: + break; + } + return qtState; +} + +QList<QTouchEvent::TouchPoint> +QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) +{ + QMap<int, QTouchEvent::TouchPoint> touchPoints; + NSSet *ended = [event touchesMatchingPhase:NSTouchPhaseEnded | NSTouchPhaseCancelled inView:nil]; + NSSet *active = [event + touchesMatchingPhase:NSTouchPhaseBegan | NSTouchPhaseMoved | NSTouchPhaseStationary + inView:nil]; + _touchCount = [active count]; + + // First: remove touches that were ended by the user. If we are + // currently not accepting single touches, a corresponding 'begin' + // has never been send to the app for these events. + // So should therefore not send the following removes either. + + for (int i=0; i<int([ended count]); ++i) { + NSTouch *touch = [[ended allObjects] objectAtIndex:i]; + QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch); + if (qcocoaTouch) { + qcocoaTouch->updateTouchData(touch, [touch phase]); + if (!_updateInternalStateOnly) + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + delete qcocoaTouch; + } + } + + bool wasUpdateInternalStateOnly = _updateInternalStateOnly; + _updateInternalStateOnly = !acceptSingleTouch && _touchCount < 2; + + // Next: update, or create, existing touches. + // We always keep track of all touch points, even + // when not accepting single touches. + + for (int i=0; i<int([active count]); ++i) { + NSTouch *touch = [[active allObjects] objectAtIndex:i]; + QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch); + if (!qcocoaTouch) + qcocoaTouch = new QCocoaTouch(touch); + else + qcocoaTouch->updateTouchData(touch, wasUpdateInternalStateOnly ? NSTouchPhaseBegan : [touch phase]); + if (!_updateInternalStateOnly) + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + } + + // Next: sadly, we need to check that our touch hash is in + // sync with cocoa. This is typically not the case after a system + // gesture happend (like a four-finger-swipe to show expose). + + if (_touchCount != _currentTouches.size()) { + // Remove all instances, and basically start from scratch: + touchPoints.clear(); + foreach (QCocoaTouch *qcocoaTouch, _currentTouches.values()) { + if (!_updateInternalStateOnly) { + qcocoaTouch->_touchPoint.setState(Qt::TouchPointReleased); + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + } + delete qcocoaTouch; + } + _currentTouches.clear(); + _updateInternalStateOnly = !acceptSingleTouch; + return touchPoints.values(); + } + + // Finally: If this call _started_ to reject single + // touches, we need to fake a relase for the remaining + // touch now (and refake a begin for it later, if needed). + + if (_updateInternalStateOnly && !wasUpdateInternalStateOnly && !_currentTouches.isEmpty()) { + QCocoaTouch *qcocoaTouch = _currentTouches.values().first(); + qcocoaTouch->_touchPoint.setState(Qt::TouchPointReleased); + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + // Since this last touch also will end up beeing the first + // touch (if the user adds a second finger without lifting + // the first), we promote it to be the primary touch: + qcocoaTouch->_touchPoint.setId(0); + _idAssignmentCount = 1; + } + + return touchPoints.values(); +} + +#endif + +QT_END_NAMESPACE + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + diff --git a/src/gui/kernel/qmultitouch_mac_p.h b/src/gui/kernel/qmultitouch_mac_p.h new file mode 100644 index 0000000000..16be930d0a --- /dev/null +++ b/src/gui/kernel/qmultitouch_mac_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QMULTITOUCH_MAC_P_H +#define QMULTITOUCH_MAC_P_H + +#ifdef QT_MAC_USE_COCOA +#import <Cocoa/Cocoa.h> +#endif + +#include <qevent.h> +#include <qhash.h> +#include <QtCore> + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +class QCocoaTouch +{ + public: + static QList<QTouchEvent::TouchPoint> getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch); + static void setMouseInDraggingState(bool inDraggingState); + + private: + static QHash<qint64, QCocoaTouch*> _currentTouches; + static QPointF _screenReferencePos; + static QPointF _trackpadReferencePos; + static int _idAssignmentCount; + static int _touchCount; + static bool _updateInternalStateOnly; + + QTouchEvent::TouchPoint _touchPoint; + qint64 _identity; + + QCocoaTouch(NSTouch *nstouch); + ~QCocoaTouch(); + + void updateTouchData(NSTouch *nstouch, NSTouchPhase phase); + static QCocoaTouch *findQCocoaTouch(NSTouch *nstouch); + static Qt::TouchPointState toTouchPointState(NSTouchPhase nsState); +}; + +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +#endif // QMULTITOUCH_MAC_P_H + diff --git a/src/gui/kernel/qnsframeview_mac_p.h b/src/gui/kernel/qnsframeview_mac_p.h new file mode 100644 index 0000000000..6ec3f64efa --- /dev/null +++ b/src/gui/kernel/qnsframeview_mac_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +// Private AppKit class (dumped from classdump). + +#import <Cocoa/Cocoa.h> + +@interface NSFrameView : NSView +{ + unsigned int styleMask; + NSString *_title; + NSCell *titleCell; + NSButton *closeButton; + NSButton *zoomButton; + NSButton *minimizeButton; + char resizeByIncrement; + char frameNeedsDisplay; + unsigned char tabViewCount; + NSSize resizeParameter; + int shadowState; +} + ++ (void)initialize; ++ (void)initTitleCell:fp8 styleMask:(unsigned int)fp12; ++ (struct _NSRect)frameRectForContentRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSRect)contentRectForFrameRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (struct _NSSize)minContentSizeForMinFrameSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (unsigned int)_validateStyleMask:(unsigned int)fp8; +- initWithFrame:(struct _NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- initWithFrame:(struct _NSRect)fp8; +- (void)dealloc; +- (void)shapeWindow; +- (void)tileAndSetWindowShape:(char)fp8; +- (void)tile; +- (void)drawRect:(struct _NSRect)fp8; +- (void)_drawFrameRects:(struct _NSRect)fp8; +- (void)drawFrame:(struct _NSRect)fp8; +- (void)drawThemeContentFill:(struct _NSRect)fp8 inView:fp24; +- (void)drawWindowBackgroundRect:(struct _NSRect)fp8; +- (void)drawWindowBackgroundRegion:(void *)fp8; +- (float)contentAlpha; +- (void)_windowChangedKeyState; +- (void)_updateButtonState; +- (char)_isSheet; +- (char)_isUtility; +- (void)setShadowState:(int)fp8; +- (int)shadowState; +- (char)_canHaveToolbar; +- (char)_toolbarIsInTransition; +- (char)_toolbarIsShown; +- (char)_toolbarIsHidden; +- (void)_showToolbarWithAnimation:(char)fp8; +- (void)_hideToolbarWithAnimation:(char)fp8; +- (float)_distanceFromToolbarBaseToTitlebar; +- (int)_shadowType; +- (unsigned int)_shadowFlags; +- (void)_setShadowParameters; +- (void)_drawFrameShadowAndFlushContext:fp8; +- (void)setUpGState; +- (void)adjustHalftonePhase; +- (void)systemColorsDidChange:fp8; +- frameColor; +- contentFill; +- (void)tabViewAdded; +- (void)tabViewRemoved; +- title; +- (void)setTitle:fp8; +- titleCell; +- (void)initTitleCell:fp8; +- (void)setResizeIncrements:(struct _NSSize)fp8; +- (struct _NSSize)resizeIncrements; +- (void)setAspectRatio:(struct _NSSize)fp8; +- (struct _NSSize)aspectRatio; +- (unsigned int)styleMask; +- representedFilename; +- (void)setRepresentedFilename:fp8; +- (void)setDocumentEdited:(char)fp8; +- (void)_setFrameNeedsDisplay:(char)fp8; +- (char)frameNeedsDisplay; +- titleFont; +- (struct _NSRect)_maxTitlebarTitleRect; +- (struct _NSRect)titlebarRect; +- (void)_setUtilityWindow:(char)fp8; +- (void)_setNonactivatingPanel:(char)fp8; +- (void)setIsClosable:(char)fp8; +- (void)setIsResizable:(char)fp8; +- closeButton; +- minimizeButton; +- zoomButton; +- (struct _NSSize)miniaturizedSize; +- (void)_clearDragMargins; +- (void)_resetDragMargins; +- (void)setTitle:fp8 andDefeatWrap:(char)fp12; +- (struct _NSRect)frameRectForContentRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSRect)contentRectForFrameRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; +- (struct _NSRect)dragRectForFrameRect:(struct _NSRect)fp8; +- (struct _NSRect)contentRect; +- (struct _NSSize)minFrameSize; +- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(struct _NSRect)fp8 isVisibleRect:(char)fp24 rectIsVisibleRectForView:fp28 topView:(char)fp32; + +@end diff --git a/src/gui/kernel/qnsthemeframe_mac_p.h b/src/gui/kernel/qnsthemeframe_mac_p.h new file mode 100644 index 0000000000..2cb4916c06 --- /dev/null +++ b/src/gui/kernel/qnsthemeframe_mac_p.h @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +// Private AppKit class (dumped from classdump). + +#import <Cocoa/Cocoa.h> +#import "qnstitledframe_mac_p.h" + +@interface NSThemeFrame : NSTitledFrame +{ + NSButton *toolbarButton; + int toolbarVisibleStatus; + NSImage *showToolbarTransitionImage; + NSSize showToolbarPreWindowSize; + NSButton *modeButton; + int leftGroupTrackingTagNum; + int rightGroupTrackingTagNum; + char mouseInsideLeftGroup; + char mouseInsideRightGroup; + int widgetState; + NSString *displayName; +} + ++ (void)initialize; ++ (float)_windowBorderThickness:(unsigned int)fp8; ++ (float)_minXWindowBorderWidth:(unsigned int)fp8; ++ (float)_maxXWindowBorderWidth:(unsigned int)fp8; ++ (float)_minYWindowBorderHeight:(unsigned int)fp8; ++ (float)_windowTitlebarButtonSpacingWidth:(unsigned int)fp8; ++ (float)_windowFileButtonSpacingWidth:(unsigned int)fp8; ++ (float)_minXTitlebarWidgetInset:(unsigned int)fp8; ++ (float)_maxXTitlebarWidgetInset:(unsigned int)fp8; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (float)_windowSideTitlebarTitleMinWidth:(unsigned int)fp8; ++ (float)_windowTitlebarTitleMinHeight:(unsigned int)fp8; ++ (float)_sideTitlebarWidth:(unsigned int)fp8; ++ (float)_titlebarHeight:(unsigned int)fp8; ++ (float)_resizeHeight:(unsigned int)fp8; ++ (char)_resizeFromEdge; ++ (struct _NSSize)sizeOfTitlebarButtons:(unsigned int)fp8; ++ (float)_contentToFrameMinXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMaxXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinYHeight:(unsigned int)fp8; ++ (float)_contentToFrameMaxYHeight:(unsigned int)fp8; ++ (unsigned int)_validateStyleMask:(unsigned int)fp8; +- (struct _NSSize)_topCornerSize; +- (struct _NSSize)_bottomCornerSize; +- (void *)_createWindowOpaqueShape; +- (void)shapeWindow; +- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)fp8 isVisibleRect:(char)fp24 rectIsVisibleRectForView:fp28 topView:(char)fp32; +- (void *)_regionForOpaqueDescendants:(NSRect)fp8 forMove:(char)fp24; +- (void)_drawFrameInterior:(NSRect *)fp8 clip:(NSRect)fp12; +- (void)_setTextShadow:(char)fp8; +- (void)_drawTitleBar:(NSRect)fp8; +- (void)_drawResizeIndicators:(NSRect)fp8; +- (void)_drawFrameRects:(NSRect)fp8; +- (void)drawFrame:(NSRect)fp8; +- contentFill; +- (void)viewDidEndLiveResize; +- (float)contentAlpha; +- (void)setThemeFrameWidgetState:(int)fp8; +- (char)constrainResizeEdge:(int *)fp8 withDelta:(struct _NSSize)fp12 elapsedTime:(float)fp20; +- (void)addFileButton:fp8; +- (void)_updateButtons; +- (void)_updateButtonState; +- newCloseButton; +- newZoomButton; +- newMiniaturizeButton; +- newToolbarButton; +- newFileButton; +- (void)_resetTitleBarButtons; +- (void)setDocumentEdited:(char)fp8; +- toolbarButton; +- modeButton; +- initWithFrame:(NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- (void)dealloc; +- (void)setFrameSize:(struct _NSSize)fp8; +- (char)_canHaveToolbar; +- (char)_toolbarIsInTransition; +- (char)_toolbarIsShown; +- (char)_toolbarIsHidden; +- _toolbarView; +- _toolbar; +- (float)_distanceFromToolbarBaseToTitlebar; +- (unsigned int)_shadowFlags; +- (NSRect)frameRectForContentRect:(NSRect)fp8 styleMask:(unsigned int)fp24; +- (NSRect)contentRectForFrameRect:(NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; +- (NSRect)contentRect; +- (NSRect)_contentRectExcludingToolbar; +- (NSRect)_contentRectIncludingToolbarAtHome; +- (void)_setToolbarShowHideResizeWeightingOptimizationOn:(char)fp8; +- (char)_usingToolbarShowHideWeightingOptimization; +- (void)handleSetFrameCommonRedisplay; +- (void)_startLiveResizeAsTopLevel; +- (void)_endLiveResizeAsTopLevel; +- (void)_growContentReshapeContentAndToolbarView:(int)fp8 animate:(char)fp12; +- (char)_growWindowReshapeContentAndToolbarView:(int)fp8 animate:(char)fp12; +- (void)_reshapeContentAndToolbarView:(int)fp8 resizeWindow:(char)fp12 animate:(char)fp16; +- (void)_toolbarFrameSizeChanged:fp8 oldSize:(struct _NSSize)fp12; +- (void)_syncToolbarPosition; +- (void)_showHideToolbar:(int)fp8 resizeWindow:(char)fp12 animate:(char)fp16; +- (void)_showToolbarWithAnimation:(char)fp8; +- (void)_hideToolbarWithAnimation:(char)fp8; +- (void)_drawToolbarTransitionIfNecessary; +- (void)drawRect:(NSRect)fp8; +- (void)resetCursorRects; +- (char)shouldBeTreatedAsInkEvent:fp8; +- (char)_shouldBeTreatedAsInkEventInInactiveWindow:fp8; +//- hitTest:(struct _NSPoint)fp8; // collides with hittest in qcocoasharedwindowmethods_mac_p.h +- (NSRect)_leftGroupRect; +- (NSRect)_rightGroupRect; +- (void)_updateWidgets; +- (void)_updateMouseTracking; +- (void)mouseEntered:fp8; +- (void)mouseExited:fp8; +- (void)_setMouseEnteredGroup:(char)fp8 entered:(char)fp12; +- (char)_mouseInGroup:fp8; +- (struct _NSSize)miniaturizedSize; +- (float)_minXTitlebarDecorationMinWidth; +- (float)_maxXTitlebarDecorationMinWidth; +- (struct _NSSize)minFrameSize; +- (float)_windowBorderThickness; +- (float)_windowTitlebarXResizeBorderThickness; +- (float)_windowTitlebarYResizeBorderThickness; +- (float)_windowResizeBorderThickness; +- (float)_minXWindowBorderWidth; +- (float)_maxXWindowBorderWidth; +- (float)_minYWindowBorderHeight; +- (float)_maxYWindowBorderHeight; +- (float)_minYTitlebarButtonsOffset; +- (float)_minYTitlebarTitleOffset; +- (float)_sideTitlebarWidth; +- (float)_titlebarHeight; +- (NSRect)_titlebarTitleRect; +- (NSRect)titlebarRect; +- (float)_windowTitlebarTitleMinHeight; +- (struct _NSSize)_sizeOfTitlebarFileButton; +- (struct _NSSize)sizeOfTitlebarToolbarButton; +- (float)_windowTitlebarButtonSpacingWidth; +- (float)_windowFileButtonSpacingWidth; +- (float)_minXTitlebarWidgetInset; +- (float)_maxXTitlebarWidgetInset; +- (float)_minXTitlebarButtonsWidth; +- (float)_maxXTitlebarButtonsWidth; +- (struct _NSPoint)_closeButtonOrigin; +- (struct _NSPoint)_zoomButtonOrigin; +- (struct _NSPoint)_collapseButtonOrigin; +- (struct _NSPoint)_toolbarButtonOrigin; +- (struct _NSPoint)_fileButtonOrigin; +- (void)_tileTitlebar; +- (NSRect)_commandPopupRect; +- (void)_resetDragMargins; +- (float)_maxYTitlebarDragHeight; +- (float)_minXTitlebarDragWidth; +- (float)_maxXTitlebarDragWidth; +- (float)_contentToFrameMinXWidth; +- (float)_contentToFrameMaxXWidth; +- (float)_contentToFrameMinYHeight; +- (float)_contentToFrameMaxYHeight; +- (float)_windowResizeCornerThickness; +- (NSRect)_minYResizeRect; +- (NSRect)_minYminXResizeRect; +- (NSRect)_minYmaxXResizeRect; +- (NSRect)_minXResizeRect; +- (NSRect)_minXminYResizeRect; +- (NSRect)_minXmaxYResizeRect; +- (NSRect)_maxYResizeRect; +- (NSRect)_maxYminXResizeRect; +- (NSRect)_maxYmaxXResizeRect; +- (NSRect)_maxXResizeRect; +- (NSRect)_maxXminYResizeRect; +- (NSRect)_maxXmaxYResizeRect; +- (NSRect)_minXTitlebarResizeRect; +- (NSRect)_maxXTitlebarResizeRect; +- (NSRect)_minXBorderRect; +- (NSRect)_maxXBorderRect; +- (NSRect)_maxYBorderRect; +- (NSRect)_minYBorderRect; +- (void)_setUtilityWindow:(char)fp8; +- (char)_isUtility; +- (float)_sheetHeightAdjustment; +- (void)_setSheet:(char)fp8; +- (char)_isSheet; +- (char)_isResizable; +- (char)_isClosable; +- (char)_isMiniaturizable; +- (char)_hasToolbar; +- (NSRect)_growBoxRect; +- (void)_drawGrowBoxWithClip:(NSRect)fp8; +- (char)_inactiveButtonsNeedMask; +- (void)mouseDown:fp8; +- _displayName; +- (void)_setDisplayName:fp8; + +@end diff --git a/src/gui/kernel/qnstitledframe_mac_p.h b/src/gui/kernel/qnstitledframe_mac_p.h new file mode 100644 index 0000000000..4eb5332194 --- /dev/null +++ b/src/gui/kernel/qnstitledframe_mac_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// 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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +// Private AppKit class (dumped from classdump). + +#import <Cocoa/Cocoa.h> +#import "qnsframeview_mac_p.h" + + +@interface NSTitledFrame : NSFrameView +{ + int resizeFlags; + id fileButton; /* NSDocumentDragButton* */ + NSSize titleCellSize; +} + ++ (float)_windowBorderThickness:(unsigned int)fp8; ++ (float)_minXWindowBorderWidth:(unsigned int)fp8; ++ (float)_maxXWindowBorderWidth:(unsigned int)fp8; ++ (float)_minYWindowBorderHeight:(unsigned int)fp8; ++ (char)_resizeFromEdge; ++ (NSRect)frameRectForContentRect:(NSRect)fp8 styleMask:(unsigned int)fp24; ++ (NSRect)contentRectForFrameRect:(NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (struct _NSSize)minContentSizeForMinFrameSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (struct _NSSize)_titleCellSizeForTitle:fp8 styleMask:(unsigned int)fp12; ++ (float)_titleCellHeight:(unsigned int)fp8; ++ (float)_windowTitlebarTitleMinHeight:(unsigned int)fp8; ++ (float)_titlebarHeight:(unsigned int)fp8; ++ (struct _NSSize)sizeOfTitlebarButtons:(unsigned int)fp8; ++ (float)windowTitlebarLinesSpacingWidth:(unsigned int)fp8; ++ (float)windowTitlebarTitleLinesSpacingWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMaxXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinYHeight:(unsigned int)fp8; ++ (float)_contentToFrameMaxYHeight:(unsigned int)fp8; +- initWithFrame:(NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- (void)dealloc; +- (void)setIsClosable:(char)fp8; +- (void)setIsResizable:(char)fp8; +- (void)_resetTitleFont; +- (void)_setUtilityWindow:(char)fp8; +- (char)isOpaque; +- (char)worksWhenModal; +- (void)propagateFrameDirtyRects:(NSRect)fp8; +- (void)_showDrawRect:(NSRect)fp8; +- (void)_drawFrameInterior:(NSRect *)fp8 clip:(NSRect)fp12; +- (void)drawFrame:(NSRect)fp8; +- (void)_drawFrameRects:(NSRect)fp8; +- (void)_drawTitlebar:(NSRect)fp8; +- (void)_drawTitlebarPattern:(int)fp8 inRect:(NSRect)fp12 clippedByRect:(NSRect)fp28 forKey:(char)fp44 alignment:(int)fp48; +- (void)_drawTitlebarLines:(int)fp8 inRect:(NSRect)fp12 clippedByRect:(NSRect)fp28; +- frameHighlightColor; +- frameShadowColor; +- (void)setFrameSize:(struct _NSSize)fp8; +- (void)setFrameOrigin:(struct _NSPoint)fp8; +- (void)tileAndSetWindowShape:(char)fp8; +- (void)tile; +- (void)_tileTitlebar; +- (void)setTitle:fp8; +- (char)_shouldRepresentFilename; +- (void)setRepresentedFilename:fp8; +- (void)_drawTitleStringIn:(NSRect)fp8 withColor:fp24; +- titleFont; +- (void)_drawResizeIndicators:(NSRect)fp8; +- titleButtonOfClass:(Class)fp8; +- initTitleButton:fp8; +- newCloseButton; +- newZoomButton; +- newMiniaturizeButton; +- newFileButton; +- fileButton; +- (void)_removeButtons; +- (void)_updateButtons; +- (char)_eventInTitlebar:fp8; +- (char)acceptsFirstMouse:fp8; +- (void)mouseDown:fp8; +- (void)mouseUp:fp8; +- (void)rightMouseDown:fp8; +- (void)rightMouseUp:fp8; +- (int)resizeEdgeForEvent:fp8; +- (struct _NSSize)_resizeDeltaFromPoint:(struct _NSPoint)fp8 toEvent:fp16; +- (NSRect)_validFrameForResizeFrame:(NSRect)fp8 fromResizeEdge:(int)fp24; +- (NSRect)frame:(NSRect)fp8 resizedFromEdge:(int)fp24 withDelta:(struct _NSSize)fp28; +- (char)constrainResizeEdge:(int *)fp8 withDelta:(struct _NSSize)fp12 elapsedTime:(float)fp20; +- (void)resizeWithEvent:fp8; +- (int)resizeFlags; +- (void)resetCursorRects; +- (void)setDocumentEdited:(char)fp8; +- (struct _NSSize)miniaturizedSize; +- (struct _NSSize)minFrameSize; +- (float)_windowBorderThickness; +- (float)_windowTitlebarXResizeBorderThickness; +- (float)_windowTitlebarYResizeBorderThickness; +- (float)_windowResizeBorderThickness; +- (float)_minXWindowBorderWidth; +- (float)_maxXWindowBorderWidth; +- (float)_minYWindowBorderHeight; +- (void)_invalidateTitleCellSize; +- (void)_invalidateTitleCellWidth; +- (float)_titleCellHeight; +- (struct _NSSize)_titleCellSize; +- (float)_titlebarHeight; +- (NSRect)titlebarRect; +- (NSRect)_maxTitlebarTitleRect; +- (NSRect)_titlebarTitleRect; +- (float)_windowTitlebarTitleMinHeight; +- (NSRect)dragRectForFrameRect:(NSRect)fp8; +- (struct _NSSize)sizeOfTitlebarButtons; +- (struct _NSSize)_sizeOfTitlebarFileButton; +- (float)_windowTitlebarButtonSpacingWidth; +- (float)_minXTitlebarButtonsWidth; +- (float)_maxXTitlebarButtonsWidth; +- (int)_numberOfTitlebarLines; +- (float)windowTitlebarLinesSpacingWidth; +- (float)windowTitlebarTitleLinesSpacingWidth; +- (float)_minLinesWidthWithSpace; +- (NSRect)_minXTitlebarLinesRectWithTitleCellRect:(NSRect)fp8; +- (NSRect)_maxXTitlebarLinesRectWithTitleCellRect:(NSRect)fp8; +- (float)_minXTitlebarDecorationMinWidth; +- (float)_maxXTitlebarDecorationMinWidth; +- (struct _NSPoint)_closeButtonOrigin; +- (struct _NSPoint)_zoomButtonOrigin; +- (struct _NSPoint)_collapseButtonOrigin; +- (struct _NSPoint)_fileButtonOrigin; +- (float)_maxYTitlebarDragHeight; +- (float)_minXTitlebarDragWidth; +- (float)_maxXTitlebarDragWidth; +- (float)_contentToFrameMinXWidth; +- (float)_contentToFrameMaxXWidth; +- (float)_contentToFrameMinYHeight; +- (float)_contentToFrameMaxYHeight; +- (NSRect)contentRect; +- (float)_windowResizeCornerThickness; +- (NSRect)_minYResizeRect; +- (NSRect)_minYminXResizeRect; +- (NSRect)_minYmaxXResizeRect; +- (NSRect)_minXResizeRect; +- (NSRect)_minXminYResizeRect; +- (NSRect)_minXmaxYResizeRect; +- (NSRect)_maxYResizeRect; +- (NSRect)_maxYminXResizeRect; +- (NSRect)_maxYmaxXResizeRect; +- (NSRect)_maxXResizeRect; +- (NSRect)_maxXminYResizeRect; +- (NSRect)_maxXmaxYResizeRect; +- (NSRect)_minXTitlebarResizeRect; +- (NSRect)_maxXTitlebarResizeRect; +- (NSRect)_minXBorderRect; +- (NSRect)_maxXBorderRect; +- (NSRect)_maxYBorderRect; +- (NSRect)_minYBorderRect; + +@end diff --git a/src/gui/kernel/qole_win.cpp b/src/gui/kernel/qole_win.cpp new file mode 100644 index 0000000000..24e2d5b292 --- /dev/null +++ b/src/gui/kernel/qole_win.cpp @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnd_p.h" + +#if !(defined(QT_NO_DRAGANDDROP) && defined(QT_NO_CLIPBOARD)) + +#if defined(Q_OS_WINCE) +#include <shlobj.h> +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +QOleEnumFmtEtc::QOleEnumFmtEtc(const QVector<FORMATETC> &fmtetcs) +{ + m_isNull = false; + m_dwRefs = 1; + m_nIndex = 0; + + for (int idx = 0; idx < fmtetcs.count(); ++idx) { + LPFORMATETC destetc = new FORMATETC(); + if (copyFormatEtc(destetc, (LPFORMATETC)&(fmtetcs.at(idx)))) { + m_lpfmtetcs.append(destetc); + } else { + m_isNull = true; + delete destetc; + break; + } + } +} + +QOleEnumFmtEtc::QOleEnumFmtEtc(const QVector<LPFORMATETC> &lpfmtetcs) +{ + m_isNull = false; + m_dwRefs = 1; + m_nIndex = 0; + + for (int idx = 0; idx < lpfmtetcs.count(); ++idx) { + LPFORMATETC srcetc = lpfmtetcs.at(idx); + LPFORMATETC destetc = new FORMATETC(); + if (copyFormatEtc(destetc, srcetc)) { + m_lpfmtetcs.append(destetc); + } else { + m_isNull = true; + delete destetc; + break; + } + } +} + +QOleEnumFmtEtc::~QOleEnumFmtEtc() +{ + LPMALLOC pmalloc; + +#if !defined(Q_OS_WINCE) + if (CoGetMalloc(MEMCTX_TASK, &pmalloc) == NOERROR) { +#else + if (SHGetMalloc(&pmalloc) == NOERROR) { +#endif + for (int idx = 0; idx < m_lpfmtetcs.count(); ++idx) { + LPFORMATETC tmpetc = m_lpfmtetcs.at(idx); + if (tmpetc->ptd) + pmalloc->Free(tmpetc->ptd); + delete tmpetc; + } + + pmalloc->Release(); + } + m_lpfmtetcs.clear(); +} + +bool QOleEnumFmtEtc::isNull() const +{ + return m_isNull; +} + +// IUnknown methods +STDMETHODIMP +QOleEnumFmtEtc::QueryInterface(REFIID riid, void FAR* FAR* ppvObj) +{ + if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) { + *ppvObj = this; + AddRef(); + return NOERROR; + } + *ppvObj = NULL; + return ResultFromScode(E_NOINTERFACE); +} + +STDMETHODIMP_(ULONG) +QOleEnumFmtEtc::AddRef(void) +{ + return ++m_dwRefs; +} + +STDMETHODIMP_(ULONG) +QOleEnumFmtEtc::Release(void) +{ + if (--m_dwRefs == 0) { + delete this; + return 0; + } + return m_dwRefs; +} + +// IEnumFORMATETC methods +STDMETHODIMP +QOleEnumFmtEtc::Next(ULONG celt, LPFORMATETC rgelt, ULONG FAR* pceltFetched) +{ + ULONG i=0; + ULONG nOffset; + + if (rgelt == NULL) + return ResultFromScode(E_INVALIDARG); + + while (i < celt) { + nOffset = m_nIndex + i; + + if (nOffset < ULONG(m_lpfmtetcs.count())) { + copyFormatEtc((LPFORMATETC)&(rgelt[i]), m_lpfmtetcs.at(nOffset)); + i++; + } else { + break; + } + } + + m_nIndex += (WORD)i; + + if (pceltFetched != NULL) + *pceltFetched = i; + + if (i != celt) + return ResultFromScode(S_FALSE); + + return NOERROR; +} + +STDMETHODIMP +QOleEnumFmtEtc::Skip(ULONG celt) +{ + ULONG i=0; + ULONG nOffset; + + while (i < celt) { + nOffset = m_nIndex + i; + + if (nOffset < ULONG(m_lpfmtetcs.count())) { + i++; + } else { + break; + } + } + + m_nIndex += (WORD)i; + + if (i != celt) + return ResultFromScode(S_FALSE); + + return NOERROR; +} + +STDMETHODIMP +QOleEnumFmtEtc::Reset() +{ + m_nIndex = 0; + return NOERROR; +} + +STDMETHODIMP +QOleEnumFmtEtc::Clone(LPENUMFORMATETC FAR* newEnum) +{ + if (newEnum == NULL) + return ResultFromScode(E_INVALIDARG); + + QOleEnumFmtEtc *result = new QOleEnumFmtEtc(m_lpfmtetcs); + result->m_nIndex = m_nIndex; + + if (result->isNull()) { + delete result; + return ResultFromScode(E_OUTOFMEMORY); + } else { + *newEnum = result; + } + + return NOERROR; +} + +bool QOleEnumFmtEtc::copyFormatEtc(LPFORMATETC dest, LPFORMATETC src) const +{ + if (dest == NULL || src == NULL) + return false; + + *dest = *src; + + if (src->ptd) { + LPVOID pout; + LPMALLOC pmalloc; + +#if !defined(Q_OS_WINCE) + if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) +#else + if (SHGetMalloc(&pmalloc) != NOERROR) +#endif + return false; + + pout = (LPVOID)pmalloc->Alloc(src->ptd->tdSize); + memcpy(dest->ptd, src->ptd, size_t(src->ptd->tdSize)); + + pmalloc->Release(); + } + + return true; +} + +QT_END_NAMESPACE +#endif // QT_NO_DRAGANDDROP && QT_NO_CLIPBOARD diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp new file mode 100644 index 0000000000..490d442616 --- /dev/null +++ b/src/gui/kernel/qpalette.cpp @@ -0,0 +1,1406 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpalette.h" +#include "qapplication.h" +#include "qdatastream.h" +#include "qvariant.h" + +QT_BEGIN_NAMESPACE + +static int qt_palette_count = 1; + +class QPalettePrivate { +public: + QPalettePrivate() : ref(1), ser_no(qt_palette_count++), detach_no(0) { } + QAtomicInt ref; + QBrush br[QPalette::NColorGroups][QPalette::NColorRoles]; + int ser_no; + int detach_no; +}; + +static QColor qt_mix_colors(QColor a, QColor b) +{ + return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, + (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); +} + +#ifdef QT3_SUPPORT + +#ifndef QT_NO_DATASTREAM +QDataStream &qt_stream_out_qcolorgroup(QDataStream &s, const QColorGroup &g) +{ + if(s.version() == 1) { + // Qt 1.x + s << g.color(QPalette::Foreground) << g.color(QPalette::Background) + << g.color(QPalette::Light) << g.color(QPalette::Dark) + << g.color(QPalette::Mid) << g.color(QPalette::Text) << g.color(QPalette::Base); + } else { + int max = QPalette::NColorRoles; + if (s.version() <= QDataStream::Qt_2_1) + max = QPalette::HighlightedText + 1; + else if (s.version() <= QDataStream::Qt_4_3) + max = QPalette::AlternateBase + 1; + for(int r = 0 ; r < max ; r++) + s << g.brush((QPalette::ColorRole)r); + } + return s; +} + +QDataStream &qt_stream_in_qcolorgroup(QDataStream &s, QColorGroup &g) +{ + if(s.version() == 1) { // Qt 1.x + QColor fg, bg, light, dark, mid, text, base; + s >> fg >> bg >> light >> dark >> mid >> text >> base; + QPalette p(bg); + p.setColor(QPalette::Active, QPalette::Foreground, fg); + p.setColor(QPalette::Active, QPalette::Light, light); + p.setColor(QPalette::Active, QPalette::Dark, dark); + p.setColor(QPalette::Active, QPalette::Mid, mid); + p.setColor(QPalette::Active, QPalette::Text, text); + p.setColor(QPalette::Active, QPalette::Base, base); + g = p; + g.setCurrentColorGroup(QPalette::Active); + } else { + int max = QPalette::NColorRoles; + if (s.version() <= QDataStream::Qt_2_1) + max = QPalette::HighlightedText + 1; + else if (s.version() <= QDataStream::Qt_3_0) + max = QPalette::LinkVisited + 1; + else if (s.version() <= QDataStream::Qt_4_3) + max = QPalette::AlternateBase + 1; + QBrush tmp; + for(int r = 0 ; r < max; r++) { + s >> tmp; + g.setBrush((QPalette::ColorRole)r, tmp); + } + } + return s; +} + +QDataStream &operator<<(QDataStream &s, const QColorGroup &g) +{ + return qt_stream_out_qcolorgroup(s, g); +} + +QDataStream &operator>>(QDataStream &s, QColorGroup &g) +{ + return qt_stream_in_qcolorgroup(s, g); +} +#endif // QT_NO_DATASTREAM + +/*! + Constructs a palette with the specified \a active, \a disabled and + \a inactive color groups. +*/ +QPalette::QPalette(const QColorGroup &active, const QColorGroup &disabled, + const QColorGroup &inactive) +{ + Q_ASSERT(QPalette::NColorRoles == QPalette::ToolTipText + 1); + init(); + setColorGroup(Active, active); + setColorGroup(Disabled, disabled); + setColorGroup(Inactive, inactive); +} + +QColorGroup QPalette::createColorGroup(ColorGroup cr) const +{ + QColorGroup ret(*this); + ret.setCurrentColorGroup(cr); + return ret; +} + +void QPalette::setColorGroup(ColorGroup cg, const QColorGroup &g) +{ + setColorGroup(cg, g.brush(WindowText), g.brush(Button), g.brush(Light), + g.brush(Dark), g.brush(Mid), g.brush(Text), g.brush(BrightText), + g.brush(Base), g.brush(AlternateBase), g.brush(Window), + g.brush(Midlight), g.brush(ButtonText), g.brush(Shadow), + g.brush(Highlight), g.brush(HighlightedText), g.brush(Link), + g.brush(LinkVisited), g.brush(ToolTipBase), g.brush(ToolTipText)); +} + +#endif // QT3_SUPPORT + +/*! + \fn const QColor &QPalette::color(ColorRole role) const + + \overload + + Returns the color that has been set for the given color \a role in + the current ColorGroup. + + \sa brush() ColorRole + */ + +/*! + \fn const QBrush &QPalette::brush(ColorRole role) const + + \overload + + Returns the brush that has been set for the given color \a role in + the current ColorGroup. + + \sa color() setBrush() ColorRole +*/ + +/*! + \fn void QPalette::setColor(ColorRole role, const QColor &color) + + \overload + + Sets the color used for the given color \a role, in all color + groups, to the specified solid \a color. + + \sa brush() setColor() ColorRole +*/ + +/*! + \fn void QPalette::setBrush(ColorRole role, const QBrush &brush) + + Sets the brush for the given color \a role to the specified \a + brush for all groups in the palette. + + \sa brush() setColor() ColorRole +*/ + +/*! + \fn const QBrush & QPalette::foreground() const + \obsolete + + Use windowText() instead. +*/ + +/*! + \fn const QBrush & QPalette::windowText() const + + Returns the window text (general foreground) brush of the + current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::button() const + + Returns the button brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::light() const + + Returns the light brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush& QPalette::midlight() const + + Returns the midlight brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::dark() const + + Returns the dark brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::mid() const + + Returns the mid brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::text() const + + Returns the text foreground brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::brightText() const + + Returns the bright text foreground brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::buttonText() const + + Returns the button text foreground brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::base() const + + Returns the base brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::alternateBase() const + + Returns the alternate base brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::toolTipBase() const + \since 4.4 + + Returns the tool tip base brush of the current color group. This brush is + used by QToolTip and QWhatsThis. + + \note Tool tips use the Inactive color group of QPalette, because tool + tips are not active windows. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::toolTipText() const + \since 4.4 + + Returns the tool tip text brush of the current color group. This brush is + used by QToolTip and QWhatsThis. + + \note Tool tips use the Inactive color group of QPalette, because tool + tips are not active windows. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::background() const + \obsolete + + Use window() instead. +*/ + +/*! + \fn const QBrush & QPalette::window() const + + Returns the window (general background) brush of the current + color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::shadow() const + + Returns the shadow brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::highlight() const + + Returns the highlight brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::highlightedText() const + + Returns the highlighted text brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::link() const + + Returns the unvisited link text brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn const QBrush & QPalette::linkVisited() const + + Returns the visited link text brush of the current color group. + + \sa ColorRole brush() +*/ + +/*! + \fn ColorGroup QPalette::currentColorGroup() const + + Returns the palette's current color group. +*/ + +/*! + \fn void QPalette::setCurrentColorGroup(ColorGroup cg) + + Set the palette's current color group to \a cg. +*/ + +/*! + \class QPalette + + \brief The QPalette class contains color groups for each widget state. + + \ingroup appearance + \ingroup shared + \ingroup painting + + + A palette consists of three color groups: \e Active, \e Disabled, + and \e Inactive. All widgets in Qt contain a palette and + use their palette to draw themselves. This makes the user + interface easily configurable and easier to keep consistent. + + + If you create a new widget we strongly recommend that you use the + colors in the palette rather than hard-coding specific colors. + + The color groups: + \list + \i The Active group is used for the window that has keyboard focus. + \i The Inactive group is used for other windows. + \i The Disabled group is used for widgets (not windows) that are + disabled for some reason. + \endlist + + Both active and inactive windows can contain disabled widgets. + (Disabled widgets are often called \e inaccessible or \e{grayed + out}.) + + In most styles, Active and Inactive look the same. + + Colors and brushes can be set for particular roles in any of a palette's + color groups with setColor() and setBrush(). A color group contains a + group of colors used by widgets for drawing themselves. We recommend that + widgets use color group roles from the palette such as "foreground" and + "base" rather than literal colors like "red" or "turquoise". The color + roles are enumerated and defined in the \l ColorRole documentation. + + We strongly recommend that you use the default palette of the + current style (returned by QApplication::palette()) and + modify that as necessary. This is done by Qt's widgets when they + are drawn. + + To modify a color group you call the functions + setColor() and setBrush(), depending on whether you want a pure + color or a pixmap pattern. + + There are also corresponding color() and brush() getters, and a + commonly used convenience function to get the ColorRole for the current ColorGroup: + window(), windowText(), base(), etc. + + + You can copy a palette using the copy constructor and test to see + if two palettes are \e identical using isCopyOf(). + + QPalette is optimized by the use of \l{implicit sharing}, + so it is very efficient to pass QPalette objects as arguments. + + \warning Some styles do not use the palette for all drawing, for + instance, if they make use of native theme engines. This is the + case for both the Windows XP, Windows Vista, and the Mac OS X + styles. + + \sa QApplication::setPalette(), QWidget::setPalette(), QColor +*/ + +/*! + \enum QPalette::ColorGroup + + \value Disabled + \value Active + \value Inactive + \value Normal synonym for Active + + \omitvalue All + \omitvalue NColorGroups + \omitvalue Current +*/ + +/*! + \enum QPalette::ColorRole + + \img palette.png Color Roles + + The ColorRole enum defines the different symbolic color roles used + in current GUIs. + + The central roles are: + + \value Window A general background color. + + \value Background This value is obsolete. Use Window instead. + + \value WindowText A general foreground color. + + \value Foreground This value is obsolete. Use WindowText instead. + + \value Base Used mostly as the background color for text entry widgets, + but can also be used for other painting - such as the + background of combobox drop down lists and toolbar handles. + It is usually white or another light color. + + \value AlternateBase Used as the alternate background color in views with + alternating row colors (see + QAbstractItemView::setAlternatingRowColors()). + + \value ToolTipBase Used as the background color for QToolTip and + QWhatsThis. Tool tips use the Inactive color group + of QPalette, because tool tips are not active + windows. + + \value ToolTipText Used as the foreground color for QToolTip and + QWhatsThis. Tool tips use the Inactive color group + of QPalette, because tool tips are not active + windows. + + \value Text The foreground color used with \c Base. This is usually + the same as the \c WindowText, in which case it must provide + good contrast with \c Window and \c Base. + + \value Button The general button background color. This background can be different from + \c Window as some styles require a different background color for buttons. + + \value ButtonText A foreground color used with the \c Button color. + + \value BrightText A text color that is very different from + \c WindowText, and contrasts well with e.g. \c + Dark. Typically used for text that needs to be + drawn where \c Text or \c WindowText would give + poor contrast, such as on pressed push buttons. + Note that text colors can be used for things + other than just words; text colors are \e + usually used for text, but it's quite common to + use the text color roles for lines, icons, etc. + + + There are some color roles used mostly for 3D bevel and shadow effects. + All of these are normally derived from \c Window, and used in ways that + depend on that relationship. For example, buttons depend on it to make the + bevels look attractive, and Motif scroll bars depend on \c Mid to be + slightly different from \c Window. + + \value Light Lighter than \c Button color. + + \value Midlight Between \c Button and \c Light. + + \value Dark Darker than \c Button. + + \value Mid Between \c Button and \c Dark. + + \value Shadow A very dark color. By default, the shadow color is + Qt::black. + + + Selected (marked) items have two roles: + + \value Highlight A color to indicate a selected item or the current + item. By default, the highlight color is + Qt::darkBlue. + + \value HighlightedText A text color that contrasts with \c Highlight. + By default, the highlighted text color is Qt::white. + + There are two color roles related to hyperlinks: + + \value Link A text color used for unvisited hyperlinks. + By default, the link color is Qt::blue. + + \value LinkVisited A text color used for already visited hyperlinks. + By default, the linkvisited color is Qt::magenta. + + Note that we do not use the \c Link and \c LinkVisited roles when + rendering rich text in Qt, and that we recommend that you use CSS + and the QTextDocument::setDefaultStyleSheet() function to alter + the appearance of links. For example: + + \snippet doc/src/snippets/textdocument-css/main.cpp 0 + + \value NoRole No role; this special role is often used to indicate that a + role has not been assigned. + + \omitvalue NColorRoles +*/ + +/*! + Constructs a palette object that uses the application's default palette. + + \sa QApplication::setPalette(), QApplication::palette() +*/ +QPalette::QPalette() + : d(QApplication::palette().d), + current_group(Active), + resolve_mask(0) +{ + d->ref.ref(); +} + +static void qt_palette_from_color(QPalette &pal, const QColor & button) +{ + QColor bg = button, + btn = button, + fg, base; + int h, s, v; + bg.getHsv(&h, &s, &v); + if(v > 128) { + fg = Qt::black; + base = Qt::white; + } else { + fg = Qt::white; + base = Qt::black; + } + //inactive and active are the same.. + pal.setColorGroup(QPalette::Active, QBrush(fg), QBrush(btn), QBrush(btn.lighter(150)), + QBrush(btn.darker()), QBrush(btn.darker(150)), QBrush(fg), QBrush(Qt::white), + QBrush(base), QBrush(bg)); + pal.setColorGroup(QPalette::Inactive, QBrush(fg), QBrush(btn), QBrush(btn.lighter(150)), + QBrush(btn.darker()), QBrush(btn.darker(150)), QBrush(fg), QBrush(Qt::white), + QBrush(base), QBrush(bg)); + pal.setColorGroup(QPalette::Disabled, QBrush(btn.darker()), QBrush(btn), QBrush(btn.lighter(150)), + QBrush(btn.darker()), QBrush(btn.darker(150)), QBrush(btn.darker()), + QBrush(Qt::white), QBrush(bg), QBrush(bg)); +} + + +/*! + Constructs a palette from the \a button color. The other colors are + automatically calculated, based on this color. \c Window will be + the button color as well. +*/ +QPalette::QPalette(const QColor &button) +{ + init(); + qt_palette_from_color(*this, button); +} + +/*! + Constructs a palette from the \a button color. The other colors are + automatically calculated, based on this color. \c Window will be + the button color as well. +*/ +QPalette::QPalette(Qt::GlobalColor button) +{ + init(); + qt_palette_from_color(*this, button); +} + +/*! + Constructs a palette. You can pass either brushes, pixmaps or + plain colors for \a windowText, \a button, \a light, \a dark, \a + mid, \a text, \a bright_text, \a base and \a window. + + \sa QBrush +*/ +QPalette::QPalette(const QBrush &windowText, const QBrush &button, + const QBrush &light, const QBrush &dark, + const QBrush &mid, const QBrush &text, + const QBrush &bright_text, const QBrush &base, + const QBrush &window) +{ + init(); + setColorGroup(All, windowText, button, light, dark, mid, text, bright_text, + base, window); +} + + +/*!\obsolete + + Constructs a palette with the specified \a windowText, \a + window, \a light, \a dark, \a mid, \a text, and \a base colors. + The button color will be set to the window color. +*/ +QPalette::QPalette(const QColor &windowText, const QColor &window, + const QColor &light, const QColor &dark, const QColor &mid, + const QColor &text, const QColor &base) +{ + init(); + setColorGroup(All, QBrush(windowText), QBrush(window), QBrush(light), + QBrush(dark), QBrush(mid), QBrush(text), QBrush(light), + QBrush(base), QBrush(window)); +} + +/*! + Constructs a palette from a \a button color and a \a window. + The other colors are automatically calculated, based on these + colors. +*/ +QPalette::QPalette(const QColor &button, const QColor &window) +{ + init(); + QColor bg = window, btn = button, fg, base, disfg; + int h, s, v; + bg.getHsv(&h, &s, &v); + if(v > 128) { + fg = Qt::black; + base = Qt::white; + disfg = Qt::darkGray; + } else { + fg = Qt::white; + base = Qt::black; + disfg = Qt::darkGray; + } + //inactive and active are identical + setColorGroup(Inactive, QBrush(fg), QBrush(btn), QBrush(btn.lighter(150)), QBrush(btn.darker()), + QBrush(btn.darker(150)), QBrush(fg), QBrush(Qt::white), QBrush(base), + QBrush(bg)); + setColorGroup(Active, QBrush(fg), QBrush(btn), QBrush(btn.lighter(150)), QBrush(btn.darker()), + QBrush(btn.darker(150)), QBrush(fg), QBrush(Qt::white), QBrush(base), + QBrush(bg)); + setColorGroup(Disabled, QBrush(disfg), QBrush(btn), QBrush(btn.lighter(150)), + QBrush(btn.darker()), QBrush(btn.darker(150)), QBrush(disfg), + QBrush(Qt::white), QBrush(base), QBrush(bg)); +} + +/*! + Constructs a copy of \a p. + + This constructor is fast thanks to \l{implicit sharing}. +*/ +QPalette::QPalette(const QPalette &p) +{ + d = p.d; + d->ref.ref(); + resolve_mask = p.resolve_mask; + current_group = p.current_group; +} + +/*! + Destroys the palette. +*/ +QPalette::~QPalette() +{ + if(!d->ref.deref()) + delete d; +} + +/*!\internal*/ +void QPalette::init() { + d = new QPalettePrivate; + resolve_mask = 0; + current_group = Active; //as a default.. +} + +/*! + Assigns \a p to this palette and returns a reference to this + palette. + + This operation is fast thanks to \l{implicit sharing}. +*/ +QPalette &QPalette::operator=(const QPalette &p) +{ + p.d->ref.ref(); + resolve_mask = p.resolve_mask; + current_group = p.current_group; + if(!d->ref.deref()) + delete d; + d = p.d; + return *this; +} + +/*! + Returns the palette as a QVariant +*/ +QPalette::operator QVariant() const +{ + return QVariant(QVariant::Palette, this); +} + +/*! + \fn const QColor &QPalette::color(ColorGroup group, ColorRole role) const + + Returns the color in the specified color \a group, used for the + given color \a role. + + \sa brush() setColor() ColorRole +*/ + +/*! + \fn const QBrush &QPalette::brush(ColorGroup group, ColorRole role) const + + Returns the brush in the specified color \a group, used for the + given color \a role. + + \sa color() setBrush() ColorRole +*/ +const QBrush &QPalette::brush(ColorGroup gr, ColorRole cr) const +{ + Q_ASSERT(cr < NColorRoles); + if(gr >= (int)NColorGroups) { + if(gr == Current) { + gr = (ColorGroup)current_group; + } else { + qWarning("QPalette::brush: Unknown ColorGroup: %d", (int)gr); + gr = Active; + } + } + return d->br[gr][cr]; +} + +/*! + \fn void QPalette::setColor(ColorGroup group, ColorRole role, const QColor &color) + + Sets the color in the specified color \a group, used for the given + color \a role, to the specified solid \a color. + + \sa setBrush() color() ColorRole +*/ + +/*! + \fn void QPalette::setBrush(ColorGroup group, ColorRole role, const QBrush &brush) + \overload + + Sets the brush in the specified color \a group, used for the given + color \a role, to \a brush. + + \sa brush() setColor() ColorRole +*/ +void QPalette::setBrush(ColorGroup cg, ColorRole cr, const QBrush &b) +{ + Q_ASSERT(cr < NColorRoles); + detach(); + if(cg >= (int)NColorGroups) { + if(cg == All) { + for(int i = 0; i < (int)NColorGroups; i++) + d->br[i][cr] = b; + resolve_mask |= (1<<cr); + return; + } else if(cg == Current) { + cg = (ColorGroup)current_group; + } else { + qWarning("QPalette::setBrush: Unknown ColorGroup: %d", (int)cg); + cg = Active; + } + } + d->br[cg][cr] = b; + resolve_mask |= (1<<cr); +} + +/*! + \since 4.2 + + Returns true if the ColorGroup \a cg and ColorRole \a cr has been + set previously on this palette; otherwise returns false. + + \sa setBrush() +*/ +bool QPalette::isBrushSet(ColorGroup cg, ColorRole cr) const +{ + Q_UNUSED(cg); + return (resolve_mask & (1<<cr)); +} + +/*! + \internal +*/ +void QPalette::detach() +{ + if (d->ref != 1) { + QPalettePrivate *x = new QPalettePrivate; + for(int grp = 0; grp < (int)NColorGroups; grp++) { + for(int role = 0; role < (int)NColorRoles; role++) + x->br[grp][role] = d->br[grp][role]; + } + if(!d->ref.deref()) + delete d; + d = x; + } + ++d->detach_no; +} + +/*! + \fn bool QPalette::operator!=(const QPalette &p) const + + Returns true (slowly) if this palette is different from \a p; + otherwise returns false (usually quickly). + + \note The current ColorGroup is not taken into account when + comparing palettes + + \sa operator==() +*/ + +/*! + Returns true (usually quickly) if this palette is equal to \a p; + otherwise returns false (slowly). + + \note The current ColorGroup is not taken into account when + comparing palettes + + \sa operator!=() +*/ +bool QPalette::operator==(const QPalette &p) const +{ + if (isCopyOf(p)) + return true; + for(int grp = 0; grp < (int)NColorGroups; grp++) { + for(int role = 0; role < (int)NColorRoles; role++) { + if(d->br[grp][role] != p.d->br[grp][role]) + return false; + } + } + return true; +} + +#ifdef QT3_SUPPORT +bool QColorGroup::operator==(const QColorGroup &other) const +{ + if (isCopyOf(other)) + return true; + for (int role = 0; role < int(NColorRoles); role++) { + if(d->br[current_group][role] != other.d->br[other.current_group][role]) + return false; + } + return true; +} + +/*! + Returns the color group as a QVariant +*/ +QColorGroup::operator QVariant() const +{ + return QVariant(QVariant::ColorGroup, this); +} +#endif + +/*! + \fn bool QPalette::isEqual(ColorGroup cg1, ColorGroup cg2) const + + Returns true (usually quickly) if color group \a cg1 is equal to + \a cg2; otherwise returns false. +*/ +bool QPalette::isEqual(QPalette::ColorGroup group1, QPalette::ColorGroup group2) const +{ + if(group1 >= (int)NColorGroups) { + if(group1 == Current) { + group1 = (ColorGroup)current_group; + } else { + qWarning("QPalette::brush: Unknown ColorGroup(1): %d", (int)group1); + group1 = Active; + } + } + if(group2 >= (int)NColorGroups) { + if(group2 == Current) { + group2 = (ColorGroup)current_group; + } else { + qWarning("QPalette::brush: Unknown ColorGroup(2): %d", (int)group2); + group2 = Active; + } + } + if(group1 == group2) + return true; + for(int role = 0; role < (int)NColorRoles; role++) { + if(d->br[group1][role] != d->br[group2][role]) + return false; + } + return true; +} + +/*! \obsolete + + Returns a number that identifies the contents of this QPalette + object. Distinct QPalette objects can only have the same serial + number if they refer to the same contents (but they don't have + to). Also, the serial number of a QPalette may change during the + lifetime of the object. + + Use cacheKey() instead. + + \warning The serial number doesn't necessarily change when the + palette is altered. This means that it may be dangerous to use it + as a cache key. + + \sa operator==() +*/ +int QPalette::serialNumber() const +{ + return d->ser_no; +} + +/*! + Returns a number that identifies the contents of this QPalette + object. Distinct QPalette objects can have the same key if + they refer to the same contents. + + The cacheKey() will change when the palette is altered. +*/ +qint64 QPalette::cacheKey() const +{ + return (((qint64) d->ser_no) << 32) | ((qint64) (d->detach_no)); +} + +/*! + Returns a new QPalette that has attributes copied from \a other. +*/ +QPalette QPalette::resolve(const QPalette &other) const +{ + if ((*this == other && resolve_mask == other.resolve_mask) + || resolve_mask == 0) { + QPalette o = other; + o.resolve_mask = resolve_mask; + return o; + } + + QPalette palette(*this); + palette.detach(); + + for(int role = 0; role < (int)NColorRoles; role++) + if (!(resolve_mask & (1<<role))) + for(int grp = 0; grp < (int)NColorGroups; grp++) + palette.d->br[grp][role] = other.d->br[grp][role]; + + return palette; +} + +/*! + \fn uint QPalette::resolve() const + \internal +*/ + +/*! + \fn void QPalette::resolve(uint mask) + \internal +*/ + + +/***************************************************************************** + QPalette stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM + +static const int NumOldRoles = 7; +static const int oldRoles[7] = { QPalette::Foreground, QPalette::Background, QPalette::Light, + QPalette::Dark, QPalette::Mid, QPalette::Text, QPalette::Base }; + +/*! + \relates QPalette + + Writes the palette, \a p to the stream \a s and returns a + reference to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ + +QDataStream &operator<<(QDataStream &s, const QPalette &p) +{ + for (int grp = 0; grp < (int)QPalette::NColorGroups; grp++) { + if (s.version() == 1) { + // Qt 1.x + for (int i = 0; i < NumOldRoles; ++i) + s << p.d->br[grp][oldRoles[i]].color(); + } else { + int max = QPalette::ToolTipText + 1; + if (s.version() <= QDataStream::Qt_2_1) + max = QPalette::HighlightedText + 1; + else if (s.version() <= QDataStream::Qt_4_3) + max = QPalette::AlternateBase + 1; + for (int r = 0; r < max; r++) + s << p.d->br[grp][r]; + } + } + return s; +} + +static void readV1ColorGroup(QDataStream &s, QPalette &pal, QPalette::ColorGroup grp) +{ + for (int i = 0; i < NumOldRoles; ++i) { + QColor col; + s >> col; + pal.setColor(grp, (QPalette::ColorRole)oldRoles[i], col); + } +} + +/*! + \relates QPalette + + Reads a palette from the stream, \a s into the palette \a p, and + returns a reference to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ + +QDataStream &operator>>(QDataStream &s, QPalette &p) +{ + if(s.version() == 1) { + p = QPalette(); + readV1ColorGroup(s, p, QPalette::Active); + readV1ColorGroup(s, p, QPalette::Disabled); + readV1ColorGroup(s, p, QPalette::Inactive); + } else { + int max = QPalette::NColorRoles; + if (s.version() <= QDataStream::Qt_2_1) { + p = QPalette(); + max = QPalette::HighlightedText + 1; + } else if (s.version() <= QDataStream::Qt_4_3) { + p = QPalette(); + max = QPalette::AlternateBase + 1; + } + + QBrush tmp; + for(int grp = 0; grp < (int)QPalette::NColorGroups; ++grp) { + for(int role = 0; role < max; ++role) { + s >> tmp; + p.setBrush((QPalette::ColorGroup)grp, (QPalette::ColorRole)role, tmp); + } + } + } + return s; +} +#endif //QT_NO_DATASTREAM + +/*! + Returns true if this palette and \a p are copies of each other, + i.e. one of them was created as a copy of the other and neither + was subsequently modified; otherwise returns false. This is much + stricter than equality. + + \sa operator=() operator==() +*/ + +bool QPalette::isCopyOf(const QPalette &p) const +{ + return d == p.d; +} + +/*! + + Sets a the group at \a cg. You can pass either brushes, pixmaps or + plain colors for \a windowText, \a button, \a light, \a dark, \a + mid, \a text, \a bright_text, \a base and \a window. + + \sa QBrush +*/ +void QPalette::setColorGroup(ColorGroup cg, const QBrush &windowText, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, const QBrush &base, + const QBrush &window) +{ + QBrush alt_base = QBrush(qt_mix_colors(base.color(), button.color())); + QBrush mid_light = QBrush(qt_mix_colors(button.color(), light.color())); + QColor toolTipBase(255, 255, 220); + QColor toolTipText(0, 0, 0); + + setColorGroup(cg, windowText, button, light, dark, mid, text, bright_text, base, + alt_base, window, mid_light, text, + QBrush(Qt::black), QBrush(Qt::darkBlue), QBrush(Qt::white), + QBrush(Qt::blue), QBrush(Qt::magenta), QBrush(toolTipBase), + QBrush(toolTipText)); + + resolve_mask &= ~(1 << Highlight); + resolve_mask &= ~(1 << HighlightedText); + resolve_mask &= ~(1 << LinkVisited); + resolve_mask &= ~(1 << Link); +} + + +/*!\internal*/ +void +QPalette::setColorGroup(ColorGroup cg, const QBrush &foreground, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, + const QBrush &base, const QBrush &alternate_base, + const QBrush &background, const QBrush &midlight, + const QBrush &button_text, const QBrush &shadow, + const QBrush &highlight, const QBrush &highlighted_text, + const QBrush &link, const QBrush &link_visited) +{ + setColorGroup(cg, foreground, button, light, dark, mid, + text, bright_text, base, alternate_base, background, + midlight, button_text, shadow, highlight, highlighted_text, + link, link_visited, background, foreground); +} + +/*!\internal*/ +void QPalette::setColorGroup(ColorGroup cg, const QBrush &foreground, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, + const QBrush &base, const QBrush &alternate_base, + const QBrush &background, const QBrush &midlight, + const QBrush &button_text, const QBrush &shadow, + const QBrush &highlight, const QBrush &highlighted_text, + const QBrush &link, const QBrush &link_visited, + const QBrush &toolTipBase, const QBrush &toolTipText) +{ + detach(); + setBrush(cg, WindowText, foreground); + setBrush(cg, Button, button); + setBrush(cg, Light, light); + setBrush(cg, Dark, dark); + setBrush(cg, Mid, mid); + setBrush(cg, Text, text); + setBrush(cg, BrightText, bright_text); + setBrush(cg, Base, base); + setBrush(cg, AlternateBase, alternate_base); + setBrush(cg, Window, background); + setBrush(cg, Midlight, midlight); + setBrush(cg, ButtonText, button_text); + setBrush(cg, Shadow, shadow); + setBrush(cg, Highlight, highlight); + setBrush(cg, HighlightedText, highlighted_text); + setBrush(cg, Link, link); + setBrush(cg, LinkVisited, link_visited); + setBrush(cg, ToolTipBase, toolTipBase); + setBrush(cg, ToolTipText, toolTipText); +} + +/*! + \fn QPalette QPalette::copy() const + + Use simple assignment instead. +*/ + +/*! + \fn QColorGroup QPalette::normal() const + \obsolete + + Returns the active color group. Use active() instead. + + Use createColorGroup(Active) instead. +*/ + +/*! + \fn void QPalette::setNormal(const QColorGroup &colorGroup) + + Sets the normal color group to \a colorGroup. + + \sa QColorGroup +*/ + +/*! + \fn QColorGroup QPalette::active() const + + Returns the active color group. + \sa QColorGroup +*/ + +/*! + \fn QColorGroup QPalette::disabled() const + + Returns the disabled color group. + \sa QColorGroup +*/ + +/*! + \fn QColorGroup QPalette::inactive() const + + Returns the inactive color group. + \sa QColorGroup +*/ + +/*! + \fn void QPalette::setActive(const QColorGroup &colorGroup) + + Sets the active color group to \a colorGroup. + \sa QColorGroup +*/ + +/*! + \fn void QPalette::setDisabled(const QColorGroup &colorGroup) + + Sets the disabled color group to \a colorGroup. + \sa QColorGroup +*/ + +/*! + \fn void QPalette::setInactive(const QColorGroup &colorGroup) + + Sets the inactive color group. + \sa QColorGroup +*/ + +/*! \class QColorGroup + \brief The QColorGroup class contains color groups for each widget state. + \compat +*/ + +/*! \fn QColorGroup::QColorGroup() + + Use QPalette() instead. +*/ + +/*! \fn QColorGroup::QColorGroup(const QBrush &foreground, const QBrush &button, \ + const QBrush &light, const QBrush &dark, const QBrush &mid, \ + const QBrush &text, const QBrush &bright_text, + const QBrush &base, const QBrush &background) + + Use QPalette(\a foreground, \a button, \a light, \a dark, \a mid, + \a text, \a bright_text, \a base, \a background) instead. +*/ + +/*! \fn QColorGroup::QColorGroup(const QColor &foreground, const QColor &background, \ + const QColor &light, const QColor &dark, const QColor &mid, \ + const QColor &text, const QColor &base) + + Use QColorGroup(\a foreground, \a background, \a light, \a dark, + \a mid, \a text, \a base) instead. +*/ + +/*! \fn QColorGroup::QColorGroup(const QColorGroup &other) + + Use QPalette(\a other) instead. +*/ + +/*! \fn QColorGroup::QColorGroup(const QPalette &pal) + + Use QPalette(\a pal) instead. +*/ + +/*! \fn const QColor &QColorGroup::foreground() const + + Use QPalette::windowText().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::button() const + + Use QPalette::button().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::light() const + + Use QPalette::light().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::dark() const + + Use QPalette::dark().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::mid() const + + Use QPalette::mid().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::text() const + + Use QPalette::text().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::base() const + + Use QPalette::base().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::background() const + + Use QPalette::window().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::midlight() const + + Use QPalette::midlight().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::brightText() const + + Use QPalette::brightText().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::buttonText() const + + Use QPalette::buttonText().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::shadow() const + + Use QPalette::shadow().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::highlight() const + + Use QPalette::highlight().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::highlightedText() const + + Use QPalette::highlightedText().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::link() const + + Use QPalette::link().color() instead. +*/ + +/*! \fn const QColor &QColorGroup::linkVisited() const + + Use QPalette::linkVisited().color() instead. +*/ + +/*! \fn QDataStream &operator<<(QDataStream &ds, const QColorGroup &colorGroup) + \relates QColorGroup + \compat +*/ + +/*! \fn QDataStream &operator>>(QDataStream &ds, QColorGroup &colorGroup) + \relates QColorGroup + \compat +*/ + +/*! \fn bool QColorGroup::operator==(const QColorGroup &other) const + + Returns true if this color group is equal to \a other; otherwise + returns false. +*/ + +/*! \fn bool QColorGroup::operator!=(const QColorGroup &other) const + + Returns true if this color group is not equal to \a other; + otherwise returns false. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qpalette.h b/src/gui/kernel/qpalette.h new file mode 100644 index 0000000000..dffd2cbec8 --- /dev/null +++ b/src/gui/kernel/qpalette.h @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPALETTE_H +#define QPALETTE_H + +#include <QtGui/qwindowdefs.h> +#include <QtGui/qcolor.h> +#include <QtGui/qbrush.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifdef QT3_SUPPORT +class QColorGroup; +#endif +class QPalettePrivate; +class QVariant; + +class Q_GUI_EXPORT QPalette +{ + Q_GADGET + Q_ENUMS(ColorGroup ColorRole) +public: + QPalette(); + QPalette(const QColor &button); + QPalette(Qt::GlobalColor button); + QPalette(const QColor &button, const QColor &window); + QPalette(const QBrush &windowText, const QBrush &button, const QBrush &light, + const QBrush &dark, const QBrush &mid, const QBrush &text, + const QBrush &bright_text, const QBrush &base, const QBrush &window); + QPalette(const QColor &windowText, const QColor &window, const QColor &light, + const QColor &dark, const QColor &mid, const QColor &text, const QColor &base); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QPalette(const QColorGroup &active, const QColorGroup &disabled, const QColorGroup &inactive); +#endif + QPalette(const QPalette &palette); + ~QPalette(); + QPalette &operator=(const QPalette &palette); +#ifdef Q_COMPILER_RVALUE_REFS + inline QPalette &operator=(QPalette &&other) + { + resolve_mask = other.resolve_mask; + current_group = other.current_group; + qSwap(d, other.d); return *this; + } +#endif + operator QVariant() const; + + // Do not change the order, the serialization format depends on it + enum ColorGroup { Active, Disabled, Inactive, NColorGroups, Current, All, Normal = Active }; + enum ColorRole { WindowText, Button, Light, Midlight, Dark, Mid, + Text, BrightText, ButtonText, Base, Window, Shadow, + Highlight, HighlightedText, + Link, LinkVisited, // ### Qt 5: remove + AlternateBase, + NoRole, // ### Qt 5: value should be 0 or -1 + ToolTipBase, ToolTipText, + NColorRoles = ToolTipText + 1, + Foreground = WindowText, Background = Window // ### Qt 5: remove + }; + + inline ColorGroup currentColorGroup() const { return static_cast<ColorGroup>(current_group); } + inline void setCurrentColorGroup(ColorGroup cg) { current_group = cg; } + + inline const QColor &color(ColorGroup cg, ColorRole cr) const + { return brush(cg, cr).color(); } + const QBrush &brush(ColorGroup cg, ColorRole cr) const; + inline void setColor(ColorGroup cg, ColorRole cr, const QColor &color); + inline void setColor(ColorRole cr, const QColor &color); + inline void setBrush(ColorRole cr, const QBrush &brush); + bool isBrushSet(ColorGroup cg, ColorRole cr) const; + void setBrush(ColorGroup cg, ColorRole cr, const QBrush &brush); + void setColorGroup(ColorGroup cr, const QBrush &windowText, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, const QBrush &base, + const QBrush &window); + bool isEqual(ColorGroup cr1, ColorGroup cr2) const; + + inline const QColor &color(ColorRole cr) const { return color(Current, cr); } + inline const QBrush &brush(ColorRole cr) const { return brush(Current, cr); } + inline const QBrush &foreground() const { return brush(WindowText); } + inline const QBrush &windowText() const { return brush(WindowText); } + inline const QBrush &button() const { return brush(Button); } + inline const QBrush &light() const { return brush(Light); } + inline const QBrush &dark() const { return brush(Dark); } + inline const QBrush &mid() const { return brush(Mid); } + inline const QBrush &text() const { return brush(Text); } + inline const QBrush &base() const { return brush(Base); } + inline const QBrush &alternateBase() const { return brush(AlternateBase); } + inline const QBrush &toolTipBase() const { return brush(ToolTipBase); } + inline const QBrush &toolTipText() const { return brush(ToolTipText); } + inline const QBrush &background() const { return brush(Window); } + inline const QBrush &window() const { return brush(Window); } + inline const QBrush &midlight() const { return brush(Midlight); } + inline const QBrush &brightText() const { return brush(BrightText); } + inline const QBrush &buttonText() const { return brush(ButtonText); } + inline const QBrush &shadow() const { return brush(Shadow); } + inline const QBrush &highlight() const { return brush(Highlight); } + inline const QBrush &highlightedText() const { return brush(HighlightedText); } + inline const QBrush &link() const { return brush(Link); } + inline const QBrush &linkVisited() const { return brush(LinkVisited); } + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QPalette copy() const { QPalette p = *this; p.detach(); return p; } + QT3_SUPPORT QColorGroup normal() const; + inline QT3_SUPPORT void setNormal(const QColorGroup &cg) { setColorGroup(Active, cg); } + + QT3_SUPPORT QColorGroup active() const; + QT3_SUPPORT QColorGroup disabled() const; + QT3_SUPPORT QColorGroup inactive() const; + inline QT3_SUPPORT void setActive(const QColorGroup &cg) { setColorGroup(Active, cg); } + inline QT3_SUPPORT void setDisabled(const QColorGroup &cg) { setColorGroup(Disabled, cg); } + inline QT3_SUPPORT void setInactive(const QColorGroup &cg) { setColorGroup(Inactive, cg); } +#endif + + bool operator==(const QPalette &p) const; + inline bool operator!=(const QPalette &p) const { return !(operator==(p)); } + bool isCopyOf(const QPalette &p) const; + + int serialNumber() const; + qint64 cacheKey() const; + + QPalette resolve(const QPalette &) const; + inline uint resolve() const { return resolve_mask; } + inline void resolve(uint mask) { resolve_mask = mask; } + +private: + void setColorGroup(ColorGroup cr, const QBrush &windowText, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, + const QBrush &base, const QBrush &alternate_base, + const QBrush &window, const QBrush &midlight, + const QBrush &button_text, const QBrush &shadow, + const QBrush &highlight, const QBrush &highlighted_text, + const QBrush &link, const QBrush &link_visited); + void setColorGroup(ColorGroup cr, const QBrush &windowText, const QBrush &button, + const QBrush &light, const QBrush &dark, const QBrush &mid, + const QBrush &text, const QBrush &bright_text, + const QBrush &base, const QBrush &alternate_base, + const QBrush &window, const QBrush &midlight, + const QBrush &button_text, const QBrush &shadow, + const QBrush &highlight, const QBrush &highlighted_text, + const QBrush &link, const QBrush &link_visited, + const QBrush &toolTipBase, const QBrush &toolTipText); +#ifdef QT3_SUPPORT + friend class QColorGroup; + void setColorGroup(ColorGroup, const QColorGroup &); + QColorGroup createColorGroup(ColorGroup) const; +#endif + void init(); + void detach(); + + QPalettePrivate *d; + uint current_group : 4; + uint resolve_mask : 28; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &s, const QPalette &p); +}; + +inline void QPalette::setColor(ColorGroup acg, ColorRole acr, + const QColor &acolor) +{ setBrush(acg, acr, QBrush(acolor)); } +inline void QPalette::setColor(ColorRole acr, const QColor &acolor) +{ setColor(All, acr, acolor); } +inline void QPalette::setBrush(ColorRole acr, const QBrush &abrush) +{ setBrush(All, acr, abrush); } + +#ifdef QT3_SUPPORT +class Q_GUI_EXPORT QColorGroup : public QPalette +{ +public: + inline QColorGroup() : QPalette() {} + inline QColorGroup(const QBrush &foreground, const QBrush &button, const QBrush &light, + const QBrush &dark, const QBrush &mid, const QBrush &text, + const QBrush &bright_text, const QBrush &base, const QBrush &background) + : QPalette(foreground, button, light, dark, mid, text, bright_text, base, background) + {} + inline QColorGroup(const QColor &foreground, const QColor &background, const QColor &light, + const QColor &dark, const QColor &mid, const QColor &text, const QColor &base) + : QPalette(foreground, background, light, dark, mid, text, base) {} + inline QColorGroup(const QColorGroup &cg) : QPalette(cg) {} + inline QColorGroup(const QPalette &pal) : QPalette(pal) {} + bool operator==(const QColorGroup &other) const; + inline bool operator!=(const QColorGroup &other) const { return !(operator==(other)); } + operator QVariant() const; + + inline QT3_SUPPORT const QColor &foreground() const { return color(WindowText); } + inline QT3_SUPPORT const QColor &button() const { return color(Button); } + inline QT3_SUPPORT const QColor &light() const { return color(Light); } + inline QT3_SUPPORT const QColor &dark() const { return color(Dark); } + inline QT3_SUPPORT const QColor &mid() const { return color(Mid); } + inline QT3_SUPPORT const QColor &text() const { return color(Text); } + inline QT3_SUPPORT const QColor &base() const { return color(Base); } + inline QT3_SUPPORT const QColor &background() const { return color(Window); } + inline QT3_SUPPORT const QColor &midlight() const { return color(Midlight); } + inline QT3_SUPPORT const QColor &brightText() const { return color(BrightText); } + inline QT3_SUPPORT const QColor &buttonText() const { return color(ButtonText); } + inline QT3_SUPPORT const QColor &shadow() const { return color(Shadow); } + inline QT3_SUPPORT const QColor &highlight() const { return color(Highlight); } + inline QT3_SUPPORT const QColor &highlightedText() const { return color(HighlightedText); } + inline QT3_SUPPORT const QColor &link() const { return color(Link); } + inline QT3_SUPPORT const QColor &linkVisited() const { return color(LinkVisited); } +}; + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QT3_SUPPORT QDataStream &operator<<(QDataStream &ds, const QColorGroup &cg); +Q_GUI_EXPORT QT3_SUPPORT QDataStream &operator>>(QDataStream &ds, QColorGroup &cg); +#endif + +inline QColorGroup QPalette::inactive() const { return createColorGroup(Inactive); } +inline QColorGroup QPalette::disabled() const { return createColorGroup(Disabled); } +inline QColorGroup QPalette::active() const { return createColorGroup(Active); } +inline QColorGroup QPalette::normal() const { return createColorGroup(Active); } + +#endif + +/***************************************************************************** + QPalette stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &ds, const QPalette &p); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &ds, QPalette &p); +#endif // QT_NO_DATASTREAM + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPALETTE_H diff --git a/src/gui/kernel/qplatformclipboard_qpa.cpp b/src/gui/kernel/qplatformclipboard_qpa.cpp new file mode 100644 index 0000000000..957a4dfd2e --- /dev/null +++ b/src/gui/kernel/qplatformclipboard_qpa.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qplatformclipboard_qpa.h" + +#ifndef QT_NO_CLIPBOARD + +QT_BEGIN_NAMESPACE + +class QClipboardData +{ +public: + QClipboardData(); + ~QClipboardData(); + + void setSource(QMimeData* s) + { + if (s == src) + return; + delete src; + src = s; + } + QMimeData* source() + { return src; } + +private: + QMimeData* src; +}; + +QClipboardData::QClipboardData() +{ + src = 0; +} + +QClipboardData::~QClipboardData() +{ + delete src; +} + +Q_GLOBAL_STATIC(QClipboardData,q_clipboardData); + +QPlatformClipboard::~QPlatformClipboard() +{ + +} + +const QMimeData *QPlatformClipboard::mimeData(QClipboard::Mode mode) const +{ + //we know its clipboard + Q_UNUSED(mode); + return q_clipboardData()->source(); +} + +void QPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) +{ + //we know its clipboard + Q_UNUSED(mode); + q_clipboardData()->setSource(data); +} + +bool QPlatformClipboard::supportsMode(QClipboard::Mode mode) const +{ + return mode == QClipboard::Clipboard; +} + +QT_END_NAMESPACE + +#endif //QT_NO_CLIPBOARD diff --git a/src/gui/kernel/qplatformclipboard_qpa.h b/src/gui/kernel/qplatformclipboard_qpa.h new file mode 100644 index 0000000000..3381c062b8 --- /dev/null +++ b/src/gui/kernel/qplatformclipboard_qpa.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMCLIPBOARD_QPA_H +#define QPLATFORMCLIPBOARD_QPA_H + +#include <qplatformdefs.h> + +#ifndef QT_NO_CLIPBOARD + +#include <QtGui/QClipboard> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QPlatformClipboard +{ +public: + virtual ~QPlatformClipboard(); + + virtual const QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard ) const; + virtual void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard); + virtual bool supportsMode(QClipboard::Mode mode) const; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_CLIPBOARD + +#endif //QPLATFORMCLIPBOARD_QPA_H diff --git a/src/gui/kernel/qplatformcursor_qpa.cpp b/src/gui/kernel/qplatformcursor_qpa.cpp new file mode 100644 index 0000000000..2ea8332ecc --- /dev/null +++ b/src/gui/kernel/qplatformcursor_qpa.cpp @@ -0,0 +1,660 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenVG module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qplatformcursor_qpa.h" + +#include <QWidget> +#include <QPainter> +#include <QBitmap> +#include <QApplication> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +QList <QWeakPointer<QPlatformCursor> > QPlatformCursorPrivate::instances; + +/*! + \class QGraphicsSystemCursor + + \brief The QGraphicsSystemCursor class provides information about + pointer device events (movement, buttons), and requests to change + the currently displayed cursor. + + Note that QGraphicsSystemCursor does not include any graphics for + display. An application that sets a QCursor may provide its own + graphics. + + \sa QGraphicsSystemCursorImage +*/ + +/*! + \fn virtual void QGraphicsSystemCursor::pointerEvent(const QMouseEvent & event) + + This method is called by Qt whenever a QMouseEvent is generated by the + underlying pointer input. \a event is a reference to the QMouseEvent in + question. A default do-nothing implementation is provided. + + \sa QApplicationPrivate::handleMouseEvent() +*/ + +/*! + \fn virtual void QGraphicsSystemCursor::changeCursor(QCursor * widgetCursor, QWidget * widget) + + \brief This method is called by Qt whenever the cursor graphic should be changed. + + Implementation of this method is mandatory for a subclass of QGraphicsSystemCursor. + + \a widgetCursor is a pointer to the QCursor that should be displayed. + + \a widget is a pointer to the widget currently displayed at QCursor::pos(). Note + that this may be 0 if the current position is not occupied by a displayed widget. + + \sa QApplicationPrivate::handleMouseEvent(), QCursor::pos() +*/ + +/*! + \fn QGraphicsSystemCursor::QGraphicsSystemCursor() + + \brief Constructs a QGraphicsSystemCursor +*/ +QPlatformCursor::QPlatformCursor(QPlatformScreen *scr ) + : screen(scr) +{ + QPlatformCursorPrivate::instances.append(this); +} + +// End of display and pointer event handling code +// Beginning of built-in cursor graphics +// from src/gui/embedded/QGraphicsSystemCursorImage_qws.cpp + +/*! + \class QGraphicsSystemCursorImage + + \brief The QGraphicsSystemCursorImage class provides a set of graphics + intended to be used as cursors. + + \sa QGraphicsSystemCursor +*/ + +static QPlatformCursorImage *systemCursorTable[Qt::LastCursor+1]; +static bool systemCursorTableInit = false; + +// 16 x 16 +static const uchar cur_arrow_bits[] = { + 0x07, 0x00, 0x39, 0x00, 0xc1, 0x01, 0x02, 0x0e, 0x02, 0x10, 0x02, 0x08, + 0x04, 0x04, 0x04, 0x02, 0x04, 0x04, 0x88, 0x08, 0x48, 0x11, 0x28, 0x22, + 0x10, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00 }; +static const uchar mcur_arrow_bits[] = { + 0x07, 0x00, 0x3f, 0x00, 0xff, 0x01, 0xfe, 0x0f, 0xfe, 0x1f, 0xfe, 0x0f, + 0xfc, 0x07, 0xfc, 0x03, 0xfc, 0x07, 0xf8, 0x0f, 0x78, 0x1f, 0x38, 0x3e, + 0x10, 0x7c, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00 }; + +static const unsigned char cur_up_arrow_bits[] = { + 0x80, 0x00, 0x40, 0x01, 0x40, 0x01, 0x20, 0x02, 0x20, 0x02, 0x10, 0x04, + 0x10, 0x04, 0x08, 0x08, 0x78, 0x0f, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0, 0x01}; +static const unsigned char mcur_up_arrow_bits[] = { + 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01, 0xe0, 0x03, 0xe0, 0x03, 0xf0, 0x07, + 0xf0, 0x07, 0xf8, 0x0f, 0xf8, 0x0f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01}; + +static const unsigned char cur_cross_bits[] = { + 0xc0, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x7f, 0x7f, 0x01, 0x40, 0x7f, 0x7f, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, + 0x40, 0x01, 0x40, 0x01, 0xc0, 0x01, 0x00, 0x00}; +static const unsigned char mcur_cross_bits[] = { + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; + +static const uchar cur_ibeam_bits[] = { + 0x00, 0x00, 0xe0, 0x03, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_ibeam_bits[] = { + 0xf0, 0x07, 0xf0, 0x07, 0xf0, 0x07, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, + 0xf0, 0x07, 0xf0, 0x07, 0xf0, 0x07, 0x00, 0x00 }; + +static const uchar cur_ver_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0xc0, 0x03, 0xe0, 0x07, 0xf0, 0x0f, + 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xf0, 0x0f, + 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x01, 0x00, 0x00 }; +static const uchar mcur_ver_bits[] = { + 0x00, 0x00, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, + 0xfc, 0x7f, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xfc, 0x7f, 0xf8, 0x3f, + 0xf0, 0x1f, 0xe0, 0x0f, 0xc0, 0x07, 0x80, 0x03 }; + +static const uchar cur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0x30, 0x18, + 0x38, 0x38, 0xfc, 0x7f, 0xfc, 0x7f, 0x38, 0x38, 0x30, 0x18, 0x20, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0x60, 0x0c, 0x70, 0x1c, 0x78, 0x3c, + 0xfc, 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfc, 0x7f, 0x78, 0x3c, + 0x70, 0x1c, 0x60, 0x0c, 0x40, 0x04, 0x00, 0x00 }; +static const uchar cur_bdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, + 0x00, 0x37, 0x88, 0x23, 0xd8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0xf8, 0x00, + 0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_bdiag_bits[] = { + 0x00, 0x00, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7f, 0x00, 0x7e, 0x04, 0x7f, + 0x8c, 0x7f, 0xdc, 0x77, 0xfc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, + 0xfc, 0x03, 0xfc, 0x07, 0x00, 0x00, 0x00, 0x00 }; +static const uchar cur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, + 0xf8, 0x00, 0xd8, 0x01, 0x88, 0x23, 0x00, 0x37, 0x00, 0x3e, 0x00, 0x3c, + 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00 }; +static const uchar mcur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, + 0xfc, 0x41, 0xfc, 0x63, 0xdc, 0x77, 0x8c, 0x7f, 0x04, 0x7f, 0x00, 0x7e, + 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x7f, 0x00, 0x00 }; + +// 20 x 20 +static const uchar forbidden_bits[] = { + 0x00,0x00,0x00,0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xf0,0x00,0x38,0xc0,0x01, + 0x7c,0x80,0x03,0xec,0x00,0x03,0xce,0x01,0x07,0x86,0x03,0x06,0x06,0x07,0x06, + 0x06,0x0e,0x06,0x06,0x1c,0x06,0x0e,0x38,0x07,0x0c,0x70,0x03,0x1c,0xe0,0x03, + 0x38,0xc0,0x01,0xf0,0xe0,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00,0x00,0x00,0x00 }; + +static const uchar forbiddenm_bits[] = { + 0x80,0x1f,0x00,0xe0,0x7f,0x00,0xf0,0xff,0x00,0xf8,0xff,0x01,0xfc,0xf0,0x03, + 0xfe,0xc0,0x07,0xfe,0x81,0x07,0xff,0x83,0x0f,0xcf,0x07,0x0f,0x8f,0x0f,0x0f, + 0x0f,0x1f,0x0f,0x0f,0x3e,0x0f,0x1f,0xfc,0x0f,0x1e,0xf8,0x07,0x3e,0xf0,0x07, + 0xfc,0xe0,0x03,0xf8,0xff,0x01,0xf0,0xff,0x00,0xe0,0x7f,0x00,0x80,0x1f,0x00}; + +// 32 x 32 +static const uchar wait_data_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0x04, 0x40, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x08, 0x20, 0x00, + 0x00, 0x08, 0x20, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x08, 0x20, 0x00, + 0x00, 0x50, 0x15, 0x00, 0x00, 0xa0, 0x0a, 0x00, 0x00, 0x40, 0x05, 0x00, + 0x00, 0x80, 0x02, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x20, 0x08, 0x00, + 0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x21, 0x00, 0x00, 0x88, 0x22, 0x00, + 0x00, 0x48, 0x25, 0x00, 0x00, 0xa8, 0x2a, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0x04, 0x40, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar wait_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xc0, 0x07, 0x00, + 0x00, 0x80, 0x03, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0xe0, 0x0f, 0x00, + 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, + 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xfc, 0x7f, 0x00, + 0x00, 0xfc, 0x7f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar hsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03, + 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, + 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar hsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00, + 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07, + 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00, + 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar vsplit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar vsplitm_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar phand_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, + 0x7e, 0x04, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x70, 0x08, 0x00, 0x00, + 0x08, 0x08, 0x00, 0x00, 0x70, 0x14, 0x00, 0x00, 0x08, 0x22, 0x00, 0x00, + 0x30, 0x41, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x40, 0x12, 0x00, 0x00, + 0x80, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar phandm_bits[] = { + 0xfe, 0x01, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x1f, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, + 0xfc, 0x1f, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, + 0xf8, 0xff, 0x00, 0x00, 0xf0, 0x7f, 0x00, 0x00, 0xe0, 0x3f, 0x00, 0x00, + 0xc0, 0x1f, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar size_all_data_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x81, 0x40, 0x00, 0x80, 0x81, 0xc0, 0x00, + 0xc0, 0xff, 0xff, 0x01, 0x80, 0x81, 0xc0, 0x00, 0x00, 0x81, 0x40, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const uchar size_all_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc2, 0x21, 0x00, + 0x00, 0xc3, 0x61, 0x00, 0x80, 0xc3, 0xe1, 0x00, 0xc0, 0xff, 0xff, 0x01, + 0xe0, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xff, 0x01, 0x80, 0xc3, 0xe1, 0x00, + 0x00, 0xc3, 0x61, 0x00, 0x00, 0xc2, 0x21, 0x00, 0x00, 0xc0, 0x01, 0x00, + 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00, 0x00, 0xe0, 0x03, 0x00, + 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const uchar whatsthis_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0xf0, 0x07, 0x00, + 0x09, 0x18, 0x0e, 0x00, 0x11, 0x1c, 0x0e, 0x00, 0x21, 0x1c, 0x0e, 0x00, + 0x41, 0x1c, 0x0e, 0x00, 0x81, 0x1c, 0x0e, 0x00, 0x01, 0x01, 0x07, 0x00, + 0x01, 0x82, 0x03, 0x00, 0xc1, 0xc7, 0x01, 0x00, 0x49, 0xc0, 0x01, 0x00, + 0x95, 0xc0, 0x01, 0x00, 0x93, 0xc0, 0x01, 0x00, 0x21, 0x01, 0x00, 0x00, + 0x20, 0xc1, 0x01, 0x00, 0x40, 0xc2, 0x01, 0x00, 0x40, 0x02, 0x00, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static const uchar whatsthism_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0x00, 0x07, 0xf8, 0x0f, 0x00, + 0x0f, 0xfc, 0x1f, 0x00, 0x1f, 0x3e, 0x1f, 0x00, 0x3f, 0x3e, 0x1f, 0x00, + 0x7f, 0x3e, 0x1f, 0x00, 0xff, 0x3e, 0x1f, 0x00, 0xff, 0x9d, 0x0f, 0x00, + 0xff, 0xc3, 0x07, 0x00, 0xff, 0xe7, 0x03, 0x00, 0x7f, 0xe0, 0x03, 0x00, + 0xf7, 0xe0, 0x03, 0x00, 0xf3, 0xe0, 0x03, 0x00, 0xe1, 0xe1, 0x03, 0x00, + 0xe0, 0xe1, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x00, + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + +static const uchar busy_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x41, 0xe0, 0xff, 0x00, 0x81, 0x20, 0x80, 0x00, 0x01, 0xe1, 0xff, 0x00, + 0x01, 0x42, 0x40, 0x00, 0xc1, 0x47, 0x40, 0x00, 0x49, 0x40, 0x55, 0x00, + 0x95, 0x80, 0x2a, 0x00, 0x93, 0x00, 0x15, 0x00, 0x21, 0x01, 0x0a, 0x00, + 0x20, 0x01, 0x11, 0x00, 0x40, 0x82, 0x20, 0x00, 0x40, 0x42, 0x44, 0x00, + 0x80, 0x41, 0x4a, 0x00, 0x00, 0x40, 0x55, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0x20, 0x80, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uchar busym_bits[] = { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x7f, 0xe0, 0xff, 0x00, 0xff, 0xe0, 0xff, 0x00, 0xff, 0xe1, 0xff, 0x00, + 0xff, 0xc3, 0x7f, 0x00, 0xff, 0xc7, 0x7f, 0x00, 0x7f, 0xc0, 0x7f, 0x00, + 0xf7, 0x80, 0x3f, 0x00, 0xf3, 0x00, 0x1f, 0x00, 0xe1, 0x01, 0x0e, 0x00, + 0xe0, 0x01, 0x1f, 0x00, 0xc0, 0x83, 0x3f, 0x00, 0xc0, 0xc3, 0x7f, 0x00, + 0x80, 0xc1, 0x7f, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, 0xe0, 0xff, 0x00, + 0x00, 0xe0, 0xff, 0x00, 0x00, 0xe0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// 16 x 16 +static const uchar openhand_bits[] = { + 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92, + 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20, + 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00}; +static const uchar openhandm_bits[] = { + 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff, + 0xfe,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f, + 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00}; +static const uchar closedhand_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50, + 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10, + 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}; +static const uchar closedhandm_bits[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f, + 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f, + 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00}; + +void QPlatformCursorImage::createSystemCursor(int id) +{ + if (!systemCursorTableInit) { + for (int i = 0; i <= Qt::LastCursor; i++) + systemCursorTable[i] = 0; + systemCursorTableInit = true; + } + switch (id) { + // 16x16 cursors + case Qt::ArrowCursor: + systemCursorTable[Qt::ArrowCursor] = + new QPlatformCursorImage(cur_arrow_bits, mcur_arrow_bits, 16, 16, 0, 0); + break; + + case Qt::UpArrowCursor: + systemCursorTable[Qt::UpArrowCursor] = + new QPlatformCursorImage(cur_up_arrow_bits, mcur_up_arrow_bits, 16, 16, 7, 0); + break; + + case Qt::CrossCursor: + systemCursorTable[Qt::CrossCursor] = + new QPlatformCursorImage(cur_cross_bits, mcur_cross_bits, 16, 16, 7, 7); + break; + + case Qt::IBeamCursor: + systemCursorTable[Qt::IBeamCursor] = + new QPlatformCursorImage(cur_ibeam_bits, mcur_ibeam_bits, 16, 16, 7, 7); + break; + + case Qt::SizeVerCursor: + systemCursorTable[Qt::SizeVerCursor] = + new QPlatformCursorImage(cur_ver_bits, mcur_ver_bits, 16, 16, 7, 7); + break; + + case Qt::SizeHorCursor: + systemCursorTable[Qt::SizeHorCursor] = + new QPlatformCursorImage(cur_hor_bits, mcur_hor_bits, 16, 16, 7, 7); + break; + + case Qt::SizeBDiagCursor: + systemCursorTable[Qt::SizeBDiagCursor] = + new QPlatformCursorImage(cur_bdiag_bits, mcur_bdiag_bits, 16, 16, 7, 7); + break; + + case Qt::SizeFDiagCursor: + systemCursorTable[Qt::SizeFDiagCursor] = + new QPlatformCursorImage(cur_fdiag_bits, mcur_fdiag_bits, 16, 16, 7, 7); + break; + + case Qt::BlankCursor: + systemCursorTable[Qt::BlankCursor] = + new QPlatformCursorImage(0, 0, 0, 0, 0, 0); + break; + + // 20x20 cursors + case Qt::ForbiddenCursor: + systemCursorTable[Qt::ForbiddenCursor] = + new QPlatformCursorImage(forbidden_bits, forbiddenm_bits, 20, 20, 10, 10); + break; + + // 32x32 cursors + case Qt::WaitCursor: + systemCursorTable[Qt::WaitCursor] = + new QPlatformCursorImage(wait_data_bits, wait_mask_bits, 32, 32, 15, 15); + break; + + case Qt::SplitVCursor: + systemCursorTable[Qt::SplitVCursor] = + new QPlatformCursorImage(vsplit_bits, vsplitm_bits, 32, 32, 15, 15); + break; + + case Qt::SplitHCursor: + systemCursorTable[Qt::SplitHCursor] = + new QPlatformCursorImage(hsplit_bits, hsplitm_bits, 32, 32, 15, 15); + break; + + case Qt::SizeAllCursor: + systemCursorTable[Qt::SizeAllCursor] = + new QPlatformCursorImage(size_all_data_bits, size_all_mask_bits, 32, 32, 15, 15); + break; + + case Qt::PointingHandCursor: + systemCursorTable[Qt::PointingHandCursor] = + new QPlatformCursorImage(phand_bits, phandm_bits, 32, 32, 0, 0); + break; + + case Qt::WhatsThisCursor: + systemCursorTable[Qt::WhatsThisCursor] = + new QPlatformCursorImage(whatsthis_bits, whatsthism_bits, 32, 32, 0, 0); + break; + case Qt::BusyCursor: + systemCursorTable[Qt::BusyCursor] = + new QPlatformCursorImage(busy_bits, busym_bits, 32, 32, 0, 0); + break; + + case Qt::OpenHandCursor: + systemCursorTable[Qt::OpenHandCursor] = + new QPlatformCursorImage(openhand_bits, openhandm_bits, 16, 16, 8, 8); + break; + case Qt::ClosedHandCursor: + systemCursorTable[Qt::ClosedHandCursor] = + new QPlatformCursorImage(closedhand_bits, closedhandm_bits, 16, 16, 8, 8); + break; + default: + qWarning("Unknown system cursor %d", id); + } +} + +/*! + \fn void QGraphicsSystemCursorImage::set(Qt::CursorShape id) + + \brief Calling this method sets the cursor image to the specified shape + + \a id is one of the defined Qt::CursorShape values. + + If id is invalid, Qt::BitmapCursor, or unknown by the implementation, + Qt::ArrowCursor is used instead. +*/ + +void QPlatformCursorImage::set(Qt::CursorShape id) +{ + QPlatformCursorImage *cursor = 0; + if (id >= 0 && id <= Qt::LastCursor) { + if (!systemCursorTable[id]) + createSystemCursor(id); + cursor = systemCursorTable[id]; + } + + if (cursor == 0) { + if (!systemCursorTable[Qt::ArrowCursor]) + createSystemCursor(Qt::ArrowCursor); + cursor = systemCursorTable[Qt::ArrowCursor]; + } + cursorImage = cursor->cursorImage; + hot = cursor->hot; +} + +/*! + \fn void QGraphicsSystemCursorImage::set(const QImage * image, int hx, int hy) + + \brief Set the cursor image to the specified QImage, with the hotsport at (hx, hy) + + \a image A pointer to a QImage + + \a hx The x coordinate of the cursor's hotspot + + \a hy the y coordinate of the cursor's hotspot +*/ + +void QPlatformCursorImage::set(const QImage &image, int hx, int hy) +{ + hot.setX(hx); + hot.setY(hy); + cursorImage = image; +} + +/*! + \fn void QGraphicsSystemCursorImage::set(const uchar *data, const uchar *mask, int width, int height, int hx, int hy) + + \brief set the cursor image to the graphic represented by the combination of data, mask, + width, and height + + \a data The pixel data of the graphic + + \a mask Mask data for the graphic. pixels in data with a corresponding mask bit of 0 are not drawn + + \a width The width of the graphic in pixels + + \a height The height of the graphic in pixels + + \a hx The X hotspot of the cursor graphic + + \a hy The Y hotspot of the cursor graphic +*/ +void QPlatformCursorImage::set(const uchar *data, const uchar *mask, + int width, int height, int hx, int hy) +{ + hot.setX(hx); + hot.setY(hy); + + cursorImage = QImage(width,height, QImage::Format_Indexed8); + + if (!width || !height || !data || !mask || cursorImage.isNull()) + return; + + cursorImage.setNumColors(3); + cursorImage.setColor(0, 0xff000000); + cursorImage.setColor(1, 0xffffffff); + cursorImage.setColor(2, 0x00000000); + + int bytesPerLine = (width + 7) / 8; + int p = 0; + int d, m; + + int x = -1, w = 0; + + uchar *cursor_data = cursorImage.bits(); + int bpl = cursorImage.bytesPerLine(); + for (int i = 0; i < height; i++) + { + for (int j = 0; j < bytesPerLine; j++, data++, mask++) + { + for (int b = 0; b < 8 && j*8+b < width; b++) + { + d = *data & (1 << b); + m = *mask & (1 << b); + if (d && m) p = 0; + else if (!d && m) p = 1; + else p = 2; + cursor_data[j*8+b] = p; + + // calc region + if (x < 0 && m) + x = j*8+b; + else if (x >= 0 && !m) { + x = -1; + w = 0; + } + if (m) + w++; + } + } + if (x >= 0) { + x = -1; + w = 0; + } + cursor_data += bpl; + } + +} + +/*! + \fn QGraphicsSystemCursorImage::QGraphicsSystemCursorImage(const uchar *data, const uchar *mask, int width, int height, int hotX, int hotY) + + \brief set the cursor image to the graphic represented by the combination of data, mask, + width, and height + + \a data The pixel data of the graphic + + \a mask Mask data for the graphic. pixels in data with a corresponding mask bit of 0 are not drawn + + \a width The width of the graphic in pixels + + \a height The height of the graphic in pixels + + \a hotX The X hotspot of the cursor graphic + + \a hotY The Y hotspot of the cursor graphic + + \sa set +*/ + +/*! + \fn QImage *QGraphicsSystemCursorImage::image() + + \brief Return the cursor graphic as a pointer to a QImage +*/ + +/*! + \fn QPoint QGraphicsSystemCursorImage::hotspot() + + \brief Return the cursor's hotspot +*/ + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qplatformcursor_qpa.h b/src/gui/kernel/qplatformcursor_qpa.h new file mode 100644 index 0000000000..71d0e87444 --- /dev/null +++ b/src/gui/kernel/qplatformcursor_qpa.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenVG module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGRAPHICSSYSTEMCURSOR_H +#define QGRAPHICSSYSTEMCURSOR_H + +#include <QtCore/QList> +#include <QtGui/QImage> +#include <QtGui/QMouseEvent> +#include <QtCore/QWeakPointer> +#include <QtCore/QObject> +#include <QtGui/QPlatformScreen> +#include <QtGui/QCursor> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +// Cursor graphics management +class Q_GUI_EXPORT QPlatformCursorImage { +public: + QPlatformCursorImage(const uchar *data, const uchar *mask, int width, int height, int hotX, int hotY) + { set(data, mask, width, height, hotX, hotY); } + QImage * image() { return &cursorImage; } + QPoint hotspot() { return hot; } + void set(const uchar *data, const uchar *mask, int width, int height, int hotX, int hotY); + void set(const QImage &image, int hx, int hy); + void set(Qt::CursorShape); +private: + static void createSystemCursor(int id); + QImage cursorImage; + QPoint hot; +}; + +class QPlatformCursor; + +class QPlatformCursorPrivate { +public: + static QList<QWeakPointer<QPlatformCursor> > getInstances() { return instances; } + static QList<QWeakPointer<QPlatformCursor> > instances; +}; + +class Q_GUI_EXPORT QPlatformCursor : public QObject { +public: + QPlatformCursor(QPlatformScreen *); + + // input methods + virtual void pointerEvent(const QMouseEvent & event) { Q_UNUSED(event); } + virtual void changeCursor(QCursor * widgetCursor, QWidget * widget) = 0; + +protected: + QPlatformScreen* screen; // Where to request an update + +private: + Q_DECLARE_PRIVATE(QPlatformCursor); + friend void qt_qpa_set_cursor(QWidget * w, bool force); + friend class QApplicationPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSYSTEMCURSOR_H diff --git a/src/gui/kernel/qplatformeventloopintegration_qpa.cpp b/src/gui/kernel/qplatformeventloopintegration_qpa.cpp new file mode 100644 index 0000000000..0ed43eb4b5 --- /dev/null +++ b/src/gui/kernel/qplatformeventloopintegration_qpa.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformeventloopintegration_qpa.h" + +#include <QtCore/QCoreApplication> + +#include <QtCore/QDebug> + +class QPlatformEventLoopIntegrationPrivate +{ +public: + QPlatformEventLoopIntegrationPrivate(); + qint64 nextTimerEvent; +}; + +QPlatformEventLoopIntegrationPrivate::QPlatformEventLoopIntegrationPrivate() + : nextTimerEvent(0) +{ +} + +QPlatformEventLoopIntegration::QPlatformEventLoopIntegration() + : d_ptr(new QPlatformEventLoopIntegrationPrivate) + +{ +} + +QPlatformEventLoopIntegration::~QPlatformEventLoopIntegration() +{ +} + +qint64 QPlatformEventLoopIntegration::nextTimerEvent() const +{ + Q_D(const QPlatformEventLoopIntegration); + return d->nextTimerEvent; +} + + +void QPlatformEventLoopIntegration::setNextTimerEvent(qint64 nextTimerEvent) +{ + Q_D(QPlatformEventLoopIntegration); + d->nextTimerEvent = nextTimerEvent; +} + +void QPlatformEventLoopIntegration::processEvents() +{ + QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); +} diff --git a/src/gui/kernel/qplatformeventloopintegration_qpa.h b/src/gui/kernel/qplatformeventloopintegration_qpa.h new file mode 100644 index 0000000000..87df7aefe4 --- /dev/null +++ b/src/gui/kernel/qplatformeventloopintegration_qpa.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMEVENTLOOPINTEGRATION_QPA_H +#define QPLATFORMEVENTLOOPINTEGRATION_QPA_H + +#include <QtCore/qglobal.h> +#include <QtCore/QScopedPointer> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformEventLoopIntegrationPrivate; + +class Q_GUI_EXPORT QPlatformEventLoopIntegration +{ + Q_DECLARE_PRIVATE(QPlatformEventLoopIntegration); +public: + QPlatformEventLoopIntegration(); + virtual ~QPlatformEventLoopIntegration(); + + virtual void startEventLoop() = 0; + virtual void quitEventLoop() = 0; + virtual void qtNeedsToProcessEvents() = 0; + + qint64 nextTimerEvent() const; + void setNextTimerEvent(qint64 nextTimerEvent); + + static void processEvents(); +protected: + QScopedPointer<QPlatformEventLoopIntegrationPrivate> d_ptr; +private: + Q_DISABLE_COPY(QPlatformEventLoopIntegration); + friend class QEventDispatcherQPA; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMEVENTLOOPINTEGRATION_QPA_H diff --git a/src/gui/kernel/qplatformglcontext_qpa.cpp b/src/gui/kernel/qplatformglcontext_qpa.cpp new file mode 100644 index 0000000000..181053223f --- /dev/null +++ b/src/gui/kernel/qplatformglcontext_qpa.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformglcontext_qpa.h" + +#include <QtCore/QThreadStorage> +#include <QtCore/QThread> + +#include <QDebug> + +class QPlatformGLThreadContext +{ +public: + ~QPlatformGLThreadContext() { + if (context) + context->doneCurrent(); + } + QPlatformGLContext *context; +}; + +static QThreadStorage<QPlatformGLThreadContext *> qplatformgl_context_storage; + +class QPlatformGLContextPrivate +{ +public: + QPlatformGLContextPrivate() + :qGLContextHandle(0) + { + } + + virtual ~QPlatformGLContextPrivate() + { + //do not delete the QGLContext handle here as it is deleted in + //QWidgetPrivate::deleteTLSysExtra() + } + void *qGLContextHandle; + void (*qGLContextDeleteFunction)(void *handle); + static QPlatformGLContext *staticSharedContext; + + static void setCurrentContext(QPlatformGLContext *context); +}; + +QPlatformGLContext *QPlatformGLContextPrivate::staticSharedContext = 0; + +void QPlatformGLContextPrivate::setCurrentContext(QPlatformGLContext *context) +{ + QPlatformGLThreadContext *threadContext = qplatformgl_context_storage.localData(); + if (!threadContext) { + if (!QThread::currentThread()) { + qWarning("No QTLS available. currentContext wont work"); + return; + } + threadContext = new QPlatformGLThreadContext; + qplatformgl_context_storage.setLocalData(threadContext); + } + threadContext->context = context; +} + +/*! + Returns the last context which called makeCurrent. This function is thread aware. +*/ +const QPlatformGLContext* QPlatformGLContext::currentContext() +{ + QPlatformGLThreadContext *threadContext = qplatformgl_context_storage.localData(); + if(threadContext) { + return threadContext->context; + } + return 0; +} + +/*! + All subclasses needs to specify the platformWindow. It can be a null window. +*/ +QPlatformGLContext::QPlatformGLContext() + :d_ptr(new QPlatformGLContextPrivate()) +{ +} + +/*! + If this is the current context for the thread, doneCurrent is called +*/ +QPlatformGLContext::~QPlatformGLContext() +{ + if (QPlatformGLContext::currentContext() == this) { + doneCurrent(); + } + +} + +/*! + Reimplement in subclass to do makeCurrent on native GL context +*/ +void QPlatformGLContext::makeCurrent() +{ + QPlatformGLContextPrivate::setCurrentContext(this); +} + +/*! + Reimplement in subclass to release current context. + Typically this is calling makeCurrent with 0 "surface" +*/ +void QPlatformGLContext::doneCurrent() +{ + QPlatformGLContextPrivate::setCurrentContext(0); +} + +/* + internal: Needs to have a pointer to qGLContext. But since this is in QtGui we cant + have any type information. +*/ +void *QPlatformGLContext::qGLContextHandle() const +{ + Q_D(const QPlatformGLContext); + return d->qGLContextHandle; +} + +void QPlatformGLContext::setQGLContextHandle(void *handle,void (*qGLContextDeleteFunction)(void *)) +{ + Q_D(QPlatformGLContext); + d->qGLContextHandle = handle; + d->qGLContextDeleteFunction = qGLContextDeleteFunction; +} + +void QPlatformGLContext::deleteQGLContext() +{ + Q_D(QPlatformGLContext); + if (d->qGLContextDeleteFunction && d->qGLContextHandle) { + d->qGLContextDeleteFunction(d->qGLContextHandle); + d->qGLContextDeleteFunction = 0; + d->qGLContextHandle = 0; + } +} + +/*! + \class QPlatformGLContext + \since 4.8 + \internal + \preliminary + \ingroup qpa + + \brief The QPlatformGLContext class provides an abstraction for native GL contexts. + + In QPA the way to support OpenGL or OpenVG or other technologies that requires a native GL + context is through the QPlatformGLContext wrapper. + + There is no factory function for QPlatformGLContexts, but rather only one accessor function. + The only place to retrieve a QPlatformGLContext from is through a QPlatformWindow. + + The context which is current for a specific thread can be collected by the currentContext() + function. This is how QPlatformGLContext also makes it possible to use the QtOpenGL module + withhout using QGLWidget. When using QGLContext::currentContext(), it will ask + QPlatformGLContext for the currentContext. Then a corresponding QGLContext will be returned, + which maps to the QPlatformGLContext. +*/ + +/*! \fn void swapBuffers() + Reimplement in subclass to native swap buffers calls +*/ + +/*! getProcAddress(const QString& procName) + Reimplement in subclass to native getProcAddr calls. + + Note: its convenient to use qPrintable(const QString &str) to get the const char * pointer +*/ + +/*! platformWindowFormat() const + QWidget has the function qplatformWindowFormat(). That function is for the application + programmer to request the format of the window and the context that he wants. + + Reimplement this function in a subclass to indicate what format the glContext actually has. +*/ diff --git a/src/gui/kernel/qplatformglcontext_qpa.h b/src/gui/kernel/qplatformglcontext_qpa.h new file mode 100644 index 0000000000..28923a9457 --- /dev/null +++ b/src/gui/kernel/qplatformglcontext_qpa.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtOpenGL module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORM_GL_CONTEXT_H +#define QPLATFORM_GL_CONTEXT_H + +#include <QtCore/qnamespace.h> +#include <QtGui/QPlatformWindowFormat> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformGLContextPrivate; + +class Q_OPENGL_EXPORT QPlatformGLContext +{ +Q_DECLARE_PRIVATE(QPlatformGLContext); + +public: + explicit QPlatformGLContext(); + virtual ~QPlatformGLContext(); + + virtual void makeCurrent(); + virtual void doneCurrent(); + virtual void swapBuffers() = 0; + virtual void* getProcAddress(const QString& procName) = 0; + + virtual QPlatformWindowFormat platformWindowFormat() const = 0; + + const static QPlatformGLContext *currentContext(); + +protected: + QScopedPointer<QPlatformGLContextPrivate> d_ptr; + +private: + //hack to make it work with QGLContext::CurrentContext + friend class QGLContext; + friend class QWidgetPrivate; + void *qGLContextHandle() const; + void setQGLContextHandle(void *handle,void (*qGLContextDeleteFunction)(void *)); + void deleteQGLContext(); + Q_DISABLE_COPY(QPlatformGLContext); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + + +#endif // QPLATFORM_GL_INTEGRATION_P_H diff --git a/src/gui/kernel/qplatformintegration_qpa.cpp b/src/gui/kernel/qplatformintegration_qpa.cpp new file mode 100644 index 0000000000..d559c53355 --- /dev/null +++ b/src/gui/kernel/qplatformintegration_qpa.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformintegration_qpa.h" + +#include <QtGui/QPlatformFontDatabase> +#include <QtGui/QPlatformClipboard> + +QT_BEGIN_NAMESPACE + +QPixmap QPlatformIntegration::grabWindow(WId window, int x, int y, int width, int height) const +{ + Q_UNUSED(window); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(width); + Q_UNUSED(height); + return QPixmap(); +} + +/*! + Factory function for the eventloop integration interface. + + Default implementation returns 0, which causes the eventloop to run in a single thread mode. + + \sa QPlatformEventLoopIntegration +*/ +QPlatformEventLoopIntegration *QPlatformIntegration::createEventLoopIntegration() const +{ + return 0; +} + +/*! + Accessor for the platform integrations fontdatabase. + + Default implementation returns a default QPlatformFontDatabase. + + \sa QPlatformFontDatabase +*/ +QPlatformFontDatabase *QPlatformIntegration::fontDatabase() const +{ + static QPlatformFontDatabase *db = 0; + if (!db) { + db = new QPlatformFontDatabase; + } + return db; +} + +/*! + Accessor for the platform integrations clipboard. + + Default implementation returns a default QPlatformClipboard. + + \sa QPlatformClipboard + +*/ + +#ifndef QT_NO_CLIPBOARD + +QPlatformClipboard *QPlatformIntegration::clipboard() const +{ + static QPlatformClipboard *clipboard = 0; + if (!clipboard) { + clipboard = new QPlatformClipboard; + } + return clipboard; +} + +#endif + +QPlatformNativeInterface * QPlatformIntegration::nativeInterface() const +{ + return 0; +} + +/*! + \class QPlatformIntegration + \since 4.8 + \internal + \preliminary + \ingroup qpa + \brief The QPlatformIntegration class is the entry for WindowSystem specific functionality. + + QPlatformIntegration is the single entry point for windowsystem specific functionality when + using the QPA platform. It has factory functions for creating platform specific pixmaps and + windows. The class also controls the font subsystem. + + QPlatformIntegration is a singelton class which gets instansiated in the QApplication + constructor. The QPlatformIntegration instance do not have ownership of objects it creates in + functions where the name starts with create. However, functions which don't have a name + starting with create acts as assessors to member variables. + + It is not trivial to create or build a platform plugin outside of the Qt source tree. Therefor + the recommended approach for making new platform plugin is to copy an existing plugin inside + the QTSRCTREE/src/plugins/platform and develop the plugin inside the source tree. + + The minimal platformintegration is the smallest platform integration it is possible to make, + which makes it an ideal starting point for new plugins. For a slightly more advanced plugin, + consider reviewing the directfb plugin, or the testlite plugin. +*/ + +/*! + \fn QPixmapData *createPixmapData(QPixmapData::PixelType type) const + + Factory function for QPixmapData. PixelType can be either PixmapType or BitmapType. + \sa QPixmapData +*/ + +/*! + \fn QPlatformWindow *createPlatformWindow(QWidget *widget, WId winId = 0) const + + Factory function for QPlatformWindow. The widget parameter is a pointer to the top level + widget(tlw) which the QPlatformWindow is suppose to be created for. The WId handle is actually + never used, but there for future reference. Its purpose is if it is going to be possible to + create QPlatformWindows on existing WId. + + All tlw has to have a QPlatformWindow, and it will be created when the QPlatformWindow is set + to be visible for the first time. If the tlw's window flags are changed, or if the tlw's + QPlatformWindowFormat is changed, then the tlw's QPlatformWindow is deleted and a new one is + created. + + \sa QPlatformWindow, QPlatformWindowFormat + \sa createWindowSurface(QWidget *widget, WId winId) const +*/ + +/*! + \fn QWindowSurface *createWindowSurface(QWidget *widget, WId winId) const + + Factory function for QWindowSurface. The QWidget parameter is a pointer to the + top level widget(tlw) the window surface is created for. A QPlatformWindow is always created + before the QWindowSurface for tlw where the widget also requires a WindowSurface. It is + possible to create top level QWidgets without a QWindowSurface by specifying + QPlatformWindowFormat::setWindowSurface(false) for the tlw QPlatformWindowFormat. + + \sa QWindowSurface + \sa createPlatformWindow(QWidget *widget, WId winId = 0) const +*/ + +/*! + \fn void moveToScreen(QWidget *window, int screen) + + This function is called when a QWidget is displayed on screen, or the QWidget is to be + displayed on a new screen. The QWidget parameter is a pointer to the top level widget and + the int parameter is the index to the screen in QList<QPlatformScreen *> screens() const. + + Default implementation does nothing. + + \sa screens() const +*/ + +/*! + \fn QList<QPlatformScreen *> screens() const + + Accessor function to a list of all the screens on the current system. The screen with the + index == 0 is the default/main screen. +*/ + +/*! + \fn bool isVirtualDesktop() + + Returns if the current windowing system configuration defines all the screens to be one + desktop(virtual desktop), or if each screen is a desktop of its own. + + Default implementation returns false. +*/ + +/*! + \fn QPixmap grabWindow(WId window, int x, int y, int width, int height) const + + This function is called when Qt needs to be able to grab the content of a window. + + Returnes the content of the window specified with the WId handle within the boundaries of + QRect(x,y,width,height). +*/ + + +bool QPlatformIntegration::hasCapability(Capability cap) const +{ + return false; +} + + + + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qplatformintegration_qpa.h b/src/gui/kernel/qplatformintegration_qpa.h new file mode 100644 index 0000000000..d06272ce01 --- /dev/null +++ b/src/gui/kernel/qplatformintegration_qpa.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMINTEGRATION_H +#define QPLATFORMINTEGRATION_H + +#include <QtGui/qwindowdefs.h> +#include <QtGui/private/qwindowsurface_p.h> +#include <QtGui/private/qpixmapdata_p.h> +#include <QtGui/qplatformscreen_qpa.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformWindow; +class QWindowSurface; +class QBlittable; +class QWidget; +class QPlatformEventLoopIntegration; +class QPlatformFontDatabase; +class QPlatformClipboard; +class QPlatformNativeInterface; + +class Q_GUI_EXPORT QPlatformIntegration +{ +public: + enum Capability { + ThreadedPixmaps = 1, + OpenGL = 2 + }; + + virtual ~QPlatformIntegration() { } + + virtual bool hasCapability(Capability cap) const; + +// GraphicsSystem functions + virtual QPixmapData *createPixmapData(QPixmapData::PixelType type) const = 0; + virtual QPlatformWindow *createPlatformWindow(QWidget *widget, WId winId = 0) const = 0; + virtual QWindowSurface *createWindowSurface(QWidget *widget, WId winId) const = 0; + +// Window System functions + virtual QList<QPlatformScreen *> screens() const = 0; + virtual void moveToScreen(QWidget *window, int screen) {Q_UNUSED(window); Q_UNUSED(screen);} + virtual bool isVirtualDesktop() { return false; } + virtual QPixmap grabWindow(WId window, int x, int y, int width, int height) const; + +//Deeper window system integrations + virtual QPlatformFontDatabase *fontDatabase() const; +#ifndef QT_NO_CLIPBOARD + virtual QPlatformClipboard *clipboard() const; +#endif + +// Experimental in mainthread eventloop integration +// This should only be used if it is only possible to do window system event processing in +// the gui thread. All of the functions in QWindowSystemInterface are thread safe. + virtual QPlatformEventLoopIntegration *createEventLoopIntegration() const; + +// Access native handles. The window handle is already available from Wid; + virtual QPlatformNativeInterface *nativeInterface() const; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMINTEGRATION_H diff --git a/src/gui/kernel/qplatformintegrationfactory_qpa.cpp b/src/gui/kernel/qplatformintegrationfactory_qpa.cpp new file mode 100644 index 0000000000..4135c9e86a --- /dev/null +++ b/src/gui/kernel/qplatformintegrationfactory_qpa.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformintegrationfactory_qpa_p.h" +#include <QPlatformIntegrationPlugin> +#include "private/qfactoryloader_p.h" +#include "qmutex.h" + +#include "qapplication.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QPlatformIntegrationFactoryInterface_iid, QLatin1String("/platforms"), Qt::CaseInsensitive)) +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, directLoader, + (QPlatformIntegrationFactoryInterface_iid, QLatin1String(""), Qt::CaseInsensitive)) +#endif + +QPlatformIntegration *QPlatformIntegrationFactory::create(const QString& key, const QString &platformPluginPath) +{ + QPlatformIntegration *ret = 0; + QStringList paramList = key.split(QLatin1Char(':')); + QString platform = paramList.takeFirst().toLower(); + +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + // Try loading the plugin from platformPluginPath first: + if (!platformPluginPath.isEmpty()) { + QCoreApplication::addLibraryPath(platformPluginPath); + if (QPlatformIntegrationFactoryInterface *factory = + qobject_cast<QPlatformIntegrationFactoryInterface*>(directLoader()->instance(platform))) + ret = factory->create(key, paramList); + + if (ret) + return ret; + } + if (QPlatformIntegrationFactoryInterface *factory = qobject_cast<QPlatformIntegrationFactoryInterface*>(loader()->instance(platform))) + ret = factory->create(platform, paramList); +#endif + + return ret; +} + +/*! + Returns the list of valid keys, i.e. the keys this factory can + create styles for. + + \sa create() +*/ +QStringList QPlatformIntegrationFactory::keys(const QString &platformPluginPath) +{ +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + QStringList list; + + if (!platformPluginPath.isEmpty()) { + QCoreApplication::addLibraryPath(platformPluginPath); + foreach (const QString &key, directLoader()->keys()) { + list += key + QString(QLatin1String(" (from %1)")).arg(platformPluginPath); + } + } + + list += loader()->keys(); +#else + QStringList list; +#endif + return list; +} + +QT_END_NAMESPACE + diff --git a/src/gui/kernel/qplatformintegrationfactory_qpa_p.h b/src/gui/kernel/qplatformintegrationfactory_qpa_p.h new file mode 100644 index 0000000000..a6042a81e0 --- /dev/null +++ b/src/gui/kernel/qplatformintegrationfactory_qpa_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMINTEGRATIONFACTORY_H +#define QPLATFORMINTEGRATIONFACTORY_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/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformIntegration; + +class QPlatformIntegrationFactory +{ +public: + static QStringList keys(const QString &platformPluginPath = QString()); + static QPlatformIntegration *create(const QString &key, const QString &platformPluginPath = QString()); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMINTEGRATIONFACTORY_H + diff --git a/src/gui/kernel/qplatformintegrationplugin_qpa.cpp b/src/gui/kernel/qplatformintegrationplugin_qpa.cpp new file mode 100644 index 0000000000..62920b6992 --- /dev/null +++ b/src/gui/kernel/qplatformintegrationplugin_qpa.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformintegrationplugin_qpa.h" + +QT_BEGIN_NAMESPACE + +QPlatformIntegrationPlugin::QPlatformIntegrationPlugin(QObject *parent) + : QObject(parent) +{ +} + +QPlatformIntegrationPlugin::~QPlatformIntegrationPlugin() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qplatformintegrationplugin_qpa.h b/src/gui/kernel/qplatformintegrationplugin_qpa.h new file mode 100644 index 0000000000..17bcba0e46 --- /dev/null +++ b/src/gui/kernel/qplatformintegrationplugin_qpa.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMINTEGRATIONPLUGIN_H +#define QPLATFORMINTEGRATIONPLUGIN_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/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformIntegration; + +struct QPlatformIntegrationFactoryInterface : public QFactoryInterface +{ + virtual QPlatformIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +#define QPlatformIntegrationFactoryInterface_iid "com.nokia.Qt.QPlatformIntegrationFactoryInterface" + +Q_DECLARE_INTERFACE(QPlatformIntegrationFactoryInterface, QPlatformIntegrationFactoryInterface_iid) + +class Q_GUI_EXPORT QPlatformIntegrationPlugin : public QObject, public QPlatformIntegrationFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QPlatformIntegrationFactoryInterface:QFactoryInterface) +public: + explicit QPlatformIntegrationPlugin(QObject *parent = 0); + ~QPlatformIntegrationPlugin(); + + virtual QStringList keys() const = 0; + virtual QPlatformIntegration *create(const QString &key, const QStringList ¶mList) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMINTEGRATIONPLUGIN_H diff --git a/src/gui/kernel/qplatformnativeinterface_qpa.cpp b/src/gui/kernel/qplatformnativeinterface_qpa.cpp new file mode 100644 index 0000000000..281aeba302 --- /dev/null +++ b/src/gui/kernel/qplatformnativeinterface_qpa.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformnativeinterface_qpa.h" + +QT_BEGIN_NAMESPACE + +void *QPlatformNativeInterface::nativeResourceForWidget(const QByteArray &resource, QWidget *widget) +{ + Q_UNUSED(resource); + Q_UNUSED(widget); + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qplatformnativeinterface_qpa.h b/src/gui/kernel/qplatformnativeinterface_qpa.h new file mode 100644 index 0000000000..b9d061982a --- /dev/null +++ b/src/gui/kernel/qplatformnativeinterface_qpa.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMNATIVEINTERFACE_QPA_H +#define QPLATFORMNATIVEINTERFACE_QPA_H + +#include <QtGui/qwindowdefs.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWidget; + +class Q_GUI_EXPORT QPlatformNativeInterface +{ +public: + virtual void *nativeResourceForWidget(const QByteArray &resource, QWidget *widget); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMNATIVEINTERFACE_QPA_H diff --git a/src/gui/kernel/qplatformscreen_qpa.cpp b/src/gui/kernel/qplatformscreen_qpa.cpp new file mode 100644 index 0000000000..c9f3dc6f34 --- /dev/null +++ b/src/gui/kernel/qplatformscreen_qpa.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformscreen_qpa.h" +#include <QtGui/qapplication.h> +#include <QtGui/private/qapplication_p.h> +#include <QtGui/qdesktopwidget.h> +#include <QtGui/qplatformintegration_qpa.h> +#include <QtGui/qwidget.h> +#include <QtGui/private/qwidget_p.h> + +/*! + Return the given top level widget for a given position. + + Default implementation retrieves a list of all top level widgets and finds the first widget + which contains point \a pos +*/ +QWidget *QPlatformScreen::topLevelAt(const QPoint & pos) const +{ + QWidgetList list = QApplication::topLevelWidgets(); + for (int i = list.size()-1; i >= 0; --i) { + QWidget *w = list[i]; + //### mask is ignored + if (w != QApplication::desktop() && w->isVisible() && w->geometry().contains(pos)) + return w; + } + + return 0; +} + +/*! \fn physicalSize() const + Reimplement in subclass to return the physical size of the screen. This function is used by + QFont to convert point sizes to pixel sizes. + + Default implementation takes the pixel size of the screen, considers a dpi of 100 and returns + the calculated (and probably wrong) physical size +*/ +QSize QPlatformScreen::physicalSize() const +{ + static const int dpi = 100; + int width = geometry().width() / dpi * qreal(25.4) ; + int height = geometry().height() / dpi * qreal(25.4) ; + return QSize(width,height); +} + +Q_GUI_EXPORT extern QWidgetPrivate *qt_widget_private(QWidget *widget); +QPlatformScreen * QPlatformScreen::platformScreenForWidget(const QWidget *widget) +{ + QWidget *window = widget->window(); + QWidgetPrivate *windowPrivate = qt_widget_private(window); + QTLWExtra * topData = windowPrivate->topData(); + QPlatformIntegration *integration = + QApplicationPrivate::platformIntegration(); + return integration->screens()[topData->screenIndex]; +} + +/*! + \class QPlatformScreen + \since 4.8 + \internal + \preliminary + \ingroup qpa + + \brief The QPlatformScreen class provides an abstraction for visual displays. + + Many window systems has support for retrieving information on the attached displays. To be able + to query the display QPA uses QPlatformScreen. Qt its self is most dependent on the + physicalSize() function, since this is the function it uses to calculate the dpi to use when + converting point sizes to pixels sizes. However, this is unfortunate on some systems, as the + native system fakes its dpi size. + + QPlatformScreen is also used by the public api QDesktopWidget for information about the desktop. + */ + +/*! \fn geometry() const + Reimplement in subclass to return the pixel geometry of the screen +*/ + +/*! \fn availableGeometry() const + Reimplement in subclass to return the pixel geometry of the available space + This normally is the desktop screen minus the task manager, global menubar etc. +*/ + +/*! \fn depth() const + Reimplement in subclass to return current depth of the screen +*/ + +/*! \fn format() const + Reimplement in subclass to return the image format which corresponds to the screen format +*/ + diff --git a/src/gui/kernel/qplatformscreen_qpa.h b/src/gui/kernel/qplatformscreen_qpa.h new file mode 100644 index 0000000000..b3bb121331 --- /dev/null +++ b/src/gui/kernel/qplatformscreen_qpa.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMSCREEN_H +#define QPLATFORMSCREEN_H + +#include <QtCore/qmetatype.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qcoreevent.h> +#include <QtCore/qvariant.h> +#include <QtCore/qrect.h> +#include <QtCore/qobject.h> + +#include <QtGui/qcursor.h> +#include <QtGui/qimage.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QPlatformScreen : public QObject +{ + Q_OBJECT +public: + virtual ~QPlatformScreen() { } + + virtual QRect geometry() const = 0; + virtual QRect availableGeometry() const {return geometry();} + virtual int depth() const = 0; + virtual QImage::Format format() const = 0; + virtual QSize physicalSize() const; + //jl: should setDirty be removed. + virtual void setDirty(const QRect &) {} + virtual QWidget *topLevelAt(const QPoint &point) const; + + //jl: should this function be in QPlatformIntegration + //jl: maybe screenForWidget is a better name? + static QPlatformScreen *platformScreenForWidget(const QWidget *widget); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMSCREEN_H diff --git a/src/gui/kernel/qplatformwindow_qpa.cpp b/src/gui/kernel/qplatformwindow_qpa.cpp new file mode 100644 index 0000000000..19bf7a9f29 --- /dev/null +++ b/src/gui/kernel/qplatformwindow_qpa.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformwindow_qpa.h" + +#include <QtGui/qwindowsysteminterface_qpa.h> +#include <QtGui/qwidget.h> + +class QPlatformWindowPrivate +{ + QWidget *tlw; + QRect rect; + Qt::WindowFlags flags; + friend class QPlatformWindow; +}; + +/*! + Constructs a platform window with the given top level widget. +*/ + +QPlatformWindow::QPlatformWindow(QWidget *tlw) + : d_ptr(new QPlatformWindowPrivate) +{ + Q_D(QPlatformWindow); + d->tlw = tlw; + tlw->setPlatformWindow(this); +} + +/*! + Virtual destructor does not delete its top level widget. +*/ +QPlatformWindow::~QPlatformWindow() +{ +} + +/*! + Returnes the widget which belongs to the QPlatformWindow +*/ +QWidget *QPlatformWindow::widget() const +{ + Q_D(const QPlatformWindow); + return d->tlw; +} + +/*! + This function is called by Qt whenever a window is moved or the window is resized. The resize + can happen programatically(from ie. user application) or by the window manager. This means that + there is no need to call this function specifically from the window manager callback, instead + call QWindowSystemInterface::handleGeometryChange(QWidget *w, const QRect &newRect); +*/ +void QPlatformWindow::setGeometry(const QRect &rect) +{ + Q_D(QPlatformWindow); + d->rect = rect; +} + +/*! + Returnes the current geometry of a window +*/ +QRect QPlatformWindow::geometry() const +{ + Q_D(const QPlatformWindow); + return d->rect; +} + +/*! + Reimplemented in subclasses to show the surface + if \a visible is \c true, and hide it if \a visible is \c false. +*/ +void QPlatformWindow::setVisible(bool visible) +{ + Q_UNUSED(visible); +} +/*! + Requests setting the window flags of this surface + to \a type. Returns the actual flags set. +*/ +Qt::WindowFlags QPlatformWindow::setWindowFlags(Qt::WindowFlags flags) +{ + Q_D(QPlatformWindow); + d->flags = flags; + return flags; +} + +/*! + Returns the effective window flags for this surface. +*/ +Qt::WindowFlags QPlatformWindow::windowFlags() const +{ + Q_D(const QPlatformWindow); + return d->flags; +} + +/*! + Reimplement in subclasses to return a handle to the native window +*/ +WId QPlatformWindow::winId() const { return WId(0); } + +/*! + This function is called to enable native child widgets in QPA. It is common not to support this + feature in Window systems, but can be faked. When this function is called all geometry of this + platform window will be relative to the parent. +*/ +//jl: It would be useful to have a property on the platform window which indicated if the sub-class +// supported the setParent. If not, then geometry would be in screen coordinates. +void QPlatformWindow::setParent(const QPlatformWindow *parent) +{ + Q_UNUSED(parent); + qWarning("This plugin does not support setParent!"); +} + +/*! + Reimplement to set the window title to \a title +*/ +void QPlatformWindow::setWindowTitle(const QString &title) {} + +/*! + Reimplement to be able to let Qt rais windows to the top of the desktop +*/ +void QPlatformWindow::raise() { qWarning("This plugin does not support raise()"); } + +/*! + Reimplement to be able to let Qt lower windows to the bottom of the desktop +*/ +void QPlatformWindow::lower() { qWarning("This plugin does not support lower()"); } + +/*! + Reimplement to be able to let Qt set the opacity level of a window +*/ +void QPlatformWindow::setOpacity(qreal level) +{ + Q_UNUSED(level); + qWarning("This plugin does not support setting window opacity"); +} + +/*! + Reimplement to let Qt be able to request activation/focus for a window + + Some window systems will probably not have callbacks for this functionality, + and then calling QWindowSystemInterface::handleWindowActivated(QWidget *w) + would be sufficient. + + If the window system has some event handling/callbacks then call + QWindowSystemInterface::handleWindowActivated(QWidget *w) when the window system + gives the notification. + + Default implementation calls QWindowSystem::handleWindowActivated(QWidget *w) +*/ +void QPlatformWindow::requestActivateWindow() +{ + QWindowSystemInterface::handleWindowActivated(widget()); +} + +/*! + Reimplement to return the glContext associated with the window. +*/ +QPlatformGLContext *QPlatformWindow::glContext() const +{ + return 0; +} + +/*! + \class QPlatformWindow + \since 4.8 + \internal + \preliminary + \ingroup qpa + + \brief The QPlatformWindow class provides an abstraction for top-level windows. + + The QPlatformWindow abstraction is used by QWidget for all its top level widgets. It is being + created by calling the createPlatformWindow function in the loaded QPlatformIntegration + instance. + + QPlatformWindow is used to signal to the windowing system, how Qt persieves its frame. + However, it is not concerned with how Qt renders into the window it represents. + + Top level QWidgets(tlw) will always have a QPlatformWindow. However, it is not necessary for + all tlw to have a QWindowSurface. This is the case for QGLWidget. And could be the case for + widgets where some 3.party renders into it. + + The platform specific window handle can be retrieved by the winId function. + + QPlatformWindow is also the way QPA defines how native child windows should be supported + through the setParent function. + + The only way to retrieve a QPlatformGLContext in QPA is by calling the glContext() function + on QPlatformWindow. + + \sa QWindowSurface, QWidget +*/ diff --git a/src/gui/kernel/qplatformwindow_qpa.h b/src/gui/kernel/qplatformwindow_qpa.h new file mode 100644 index 0000000000..41a4bac7e0 --- /dev/null +++ b/src/gui/kernel/qplatformwindow_qpa.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPLATFORMWINDOW_H +#define QPLATFORMWINDOW_H + + +#include <QtCore/qscopedpointer.h> +#include <QtCore/qrect.h> +#include <QtCore/qstring.h> +#include <QtGui/qwindowdefs.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformWindowPrivate; +class QWidget; +class QPlatformGLContext; + +class Q_GUI_EXPORT QPlatformWindow +{ + Q_DECLARE_PRIVATE(QPlatformWindow) +public: + QPlatformWindow(QWidget *tlw); + virtual ~QPlatformWindow(); + + QWidget *widget() const; + virtual void setGeometry(const QRect &rect); + virtual QRect geometry() const; + + virtual void setVisible(bool visible); + virtual Qt::WindowFlags setWindowFlags(Qt::WindowFlags flags); + virtual Qt::WindowFlags windowFlags() const; + virtual WId winId() const; + virtual void setParent(const QPlatformWindow *window); + + virtual void setWindowTitle(const QString &title); + virtual void raise(); + virtual void lower(); + + virtual void setOpacity(qreal level); + virtual void requestActivateWindow(); + + virtual QPlatformGLContext *glContext() const; +protected: + QScopedPointer<QPlatformWindowPrivate> d_ptr; +private: + Q_DISABLE_COPY(QPlatformWindow) +}; + +QT_END_NAMESPACE + +QT_END_HEADER +#endif //QPLATFORMWINDOW_H diff --git a/src/gui/kernel/qplatformwindowformat_qpa.cpp b/src/gui/kernel/qplatformwindowformat_qpa.cpp new file mode 100644 index 0000000000..c165c8513a --- /dev/null +++ b/src/gui/kernel/qplatformwindowformat_qpa.cpp @@ -0,0 +1,1008 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformwindowformat_qpa.h" + +#include <QtCore/QDebug> + +Q_GLOBAL_STATIC(QPlatformWindowFormat, q_platformwindow_default_format); + +class QPlatformWindowFormatPrivate +{ +public: + QPlatformWindowFormatPrivate() + : ref(1) + , opts(QPlatformWindowFormat::DoubleBuffer | QPlatformWindowFormat::DepthBuffer + | QPlatformWindowFormat::Rgba | QPlatformWindowFormat::DirectRendering + | QPlatformWindowFormat::StencilBuffer | QPlatformWindowFormat::DeprecatedFunctions + | QPlatformWindowFormat::HasWindowSurface) + , depthSize(-1) + , accumSize(-1) + , stencilSize(-1) + , redSize(-1) + , greenSize(-1) + , blueSize(-1) + , alphaSize(-1) + , numSamples(-1) + , swapInterval(-1) + , windowApi(QPlatformWindowFormat::Raster) + , sharedContext(0) + { + } + + QPlatformWindowFormatPrivate(const QPlatformWindowFormatPrivate *other) + : ref(1), + opts(other->opts), + depthSize(other->depthSize), + accumSize(other->accumSize), + stencilSize(other->stencilSize), + redSize(other->redSize), + greenSize(other->greenSize), + blueSize(other->blueSize), + alphaSize(other->alphaSize), + numSamples(other->numSamples), + swapInterval(other->swapInterval), + windowApi(other->windowApi), + sharedContext(other->sharedContext) + { + } + QAtomicInt ref; + QPlatformWindowFormat::FormatOptions opts; + int depthSize; + int accumSize; + int stencilSize; + int redSize; + int greenSize; + int blueSize; + int alphaSize; + int numSamples; + int swapInterval; + QPlatformWindowFormat::WindowApi windowApi; + QPlatformGLContext *sharedContext; +}; + +/*! + \class QPlatformWindowFormat + \brief The QPlatformWindowFormat class specifies the display format of an OpenGL + rendering context and if possible attributes of the corresponding QPlatformWindow. + + \ingroup painting + + QWidget has a setter and getter function for QPlatformWindowFormat. These functions can be used + by the application programmer to signal what kind of format he wants to the window and glcontext + should have. However, it is not always possible to fulfill these requirements. The application + programmer should therefore check the resulting QPlatformWindowFormat from QPlatformGLContext + to see the format that was actually created. + + A display format has several characteristics: + \list + \i \link setDoubleBuffer() Double or single buffering.\endlink + \i \link setDepth() Depth buffer.\endlink + \i \link setRgba() RGBA or color index mode.\endlink + \i \link setAlpha() Alpha channel.\endlink + \i \link setAccum() Accumulation buffer.\endlink + \i \link setStencil() Stencil buffer.\endlink + \i \link setStereo() Stereo buffers.\endlink + \i \link setDirectRendering() Direct rendering.\endlink + \i \link setOverlay() Presence of an overlay.\endlink + \i \link setPlane() Plane of an overlay.\endlink + \i \link setSampleBuffers() Multisample buffers.\endlink + \endlist + + You can also specify preferred bit depths for the color buffer, + depth buffer, alpha buffer, accumulation buffer and the stencil + buffer with the functions: setRedBufferSize(), setGreenBufferSize(), + setBlueBufferSize(), setDepthBufferSize(), setAlphaBufferSize(), + setAccumBufferSize() and setStencilBufferSize(). + + Note that even if you specify that you prefer a 32 bit depth + buffer (e.g. with setDepthBufferSize(32)), the format that is + chosen may not have a 32 bit depth buffer, even if there is a + format available with a 32 bit depth buffer. The main reason for + this is how the system dependant picking algorithms work on the + different platforms, and some format options may have higher + precedence than others. + + You create and tell a QPlatformWindowFormat object what rendering options you + want from an OpenGL rendering context. + + OpenGL drivers or accelerated hardware may or may not support + advanced features such as alpha channel or stereographic viewing. + If you request some features that the driver/hardware does not + provide when you create a QGLWidget, you will get a rendering + context with the nearest subset of features. + + There are different ways to define the display characteristics of + a rendering context. One is to create a QPlatformWindowFormat and make it the + default for the entire application: + \snippet doc/src/snippets/code/src_opengl_qgl.cpp 0 + + Or you can specify the desired format when creating an object of + your QGLWidget subclass: + \snippet doc/src/snippets/code/src_opengl_qgl.cpp 1 + + After the widget has been created, you can find out which of the + requested features the system was able to provide: + \snippet doc/src/snippets/code/src_opengl_qgl.cpp 2 + + \legalese + OpenGL is a trademark of Silicon Graphics, Inc. in the + United States and other countries. + \endlegalese + + \sa QPlatformContext, QWidget +*/ + +/*! + Constructs a QPlatformWindowFormat object with the following default settings: + \list + \i \link setDoubleBuffer() Double buffer:\endlink Enabled. + \i \link setDepth() Depth buffer:\endlink Enabled. + \i \link setRgba() RGBA:\endlink Enabled (i.e., color index disabled). + \i \link setAlpha() Alpha channel:\endlink Disabled. + \i \link setAccum() Accumulator buffer:\endlink Disabled. + \i \link setStencil() Stencil buffer:\endlink Enabled. + \i \link setStereo() Stereo:\endlink Disabled. + \i \link setDirectRendering() Direct rendering:\endlink Enabled. + \i \link setOverlay() Overlay:\endlink Disabled. + \i \link setPlane() Plane:\endlink 0 (i.e., normal plane). + \i \link setSampleBuffers() Multisample buffers:\endlink Disabled. + \endlist +*/ + +QPlatformWindowFormat::QPlatformWindowFormat() +{ + d = new QPlatformWindowFormatPrivate; +} + + +/*! + Creates a QPlatformWindowFormat object that is a copy of the current + defaultFormat(). + + If \a options is not 0, the default format is modified by the + specified format options. The \a options parameter should be + QGL::FormatOption values OR'ed together. + + This constructor makes it easy to specify a certain desired format + in classes derived from QGLWidget, for example: + \snippet doc/src/snippets/code/src_opengl_qgl.cpp 3 + + Note that there are QGL::FormatOption values to turn format settings + both on and off, e.g. QGL::DepthBuffer and QGL::NoDepthBuffer, + QGL::DirectRendering and QGL::IndirectRendering, etc. + + The \a plane parameter defaults to 0 and is the plane which this + format should be associated with. Not all OpenGL implementations + supports overlay/underlay rendering planes. + + \sa defaultFormat(), setOption(), setPlane() +*/ + +QPlatformWindowFormat::QPlatformWindowFormat(QPlatformWindowFormat::FormatOptions options) +{ + d = new QPlatformWindowFormatPrivate; + QPlatformWindowFormat::FormatOptions newOpts = options; + d->opts = defaultFormat().d->opts; + d->opts |= (newOpts & 0xffff); + d->opts &= ~(newOpts >> 16); +} + +/*! + \internal +*/ +void QPlatformWindowFormat::detach() +{ + if (d->ref != 1) { + QPlatformWindowFormatPrivate *newd = new QPlatformWindowFormatPrivate(d); + if (!d->ref.deref()) + delete d; + d = newd; + } +} + +/*! + Constructs a copy of \a other. +*/ + +QPlatformWindowFormat::QPlatformWindowFormat(const QPlatformWindowFormat &other) +{ + d = other.d; + d->ref.ref(); +} + +/*! + Assigns \a other to this object. +*/ + +QPlatformWindowFormat &QPlatformWindowFormat::operator=(const QPlatformWindowFormat &other) +{ + if (d != other.d) { + other.d->ref.ref(); + if (!d->ref.deref()) + delete d; + d = other.d; + } + return *this; +} + +/*! + Destroys the QPlatformWindowFormat. +*/ +QPlatformWindowFormat::~QPlatformWindowFormat() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + \fn bool QPlatformWindowFormat::doubleBuffer() const + + Returns true if double buffering is enabled; otherwise returns + false. Double buffering is enabled by default. + + \sa setDoubleBuffer() +*/ + +/*! + If \a enable is true sets double buffering; otherwise sets single + buffering. + + Double buffering is enabled by default. + + Double buffering is a technique where graphics are rendered on an + off-screen buffer and not directly to the screen. When the drawing + has been completed, the program calls a swapBuffers() function to + exchange the screen contents with the buffer. The result is + flicker-free drawing and often better performance. + + \sa doubleBuffer(), QGLContext::swapBuffers(), + QGLWidget::swapBuffers() +*/ + +void QPlatformWindowFormat::setDoubleBuffer(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::DoubleBuffer : QPlatformWindowFormat::SingleBuffer); +} + + +/*! + \fn bool QPlatformWindowFormat::depth() const + + Returns true if the depth buffer is enabled; otherwise returns + false. The depth buffer is enabled by default. + + \sa setDepth(), setDepthBufferSize() +*/ + +/*! + If \a enable is true enables the depth buffer; otherwise disables + the depth buffer. + + The depth buffer is enabled by default. + + The purpose of a depth buffer (or Z-buffering) is to remove hidden + surfaces. Pixels are assigned Z values based on the distance to + the viewer. A pixel with a high Z value is closer to the viewer + than a pixel with a low Z value. This information is used to + decide whether to draw a pixel or not. + + \sa depth(), setDepthBufferSize() +*/ + +void QPlatformWindowFormat::setDepth(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::DepthBuffer : QPlatformWindowFormat::NoDepthBuffer); +} + + +/*! + \fn bool QPlatformWindowFormat::rgba() const + + Returns true if RGBA color mode is set. Returns false if color + index mode is set. The default color mode is RGBA. + + \sa setRgba() +*/ + +/*! + If \a enable is true sets RGBA mode. If \a enable is false sets + color index mode. + + The default color mode is RGBA. + + RGBA is the preferred mode for most OpenGL applications. In RGBA + color mode you specify colors as red + green + blue + alpha + quadruplets. + + In color index mode you specify an index into a color lookup + table. + + \sa rgba() +*/ + +void QPlatformWindowFormat::setRgba(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::Rgba : QPlatformWindowFormat::ColorIndex); +} + + +/*! + \fn bool QPlatformWindowFormat::alpha() const + + Returns true if the alpha buffer in the framebuffer is enabled; + otherwise returns false. The alpha buffer is disabled by default. + + \sa setAlpha(), setAlphaBufferSize() +*/ + +/*! + If \a enable is true enables the alpha buffer; otherwise disables + the alpha buffer. + + The alpha buffer is disabled by default. + + The alpha buffer is typically used for implementing transparency + or translucency. The A in RGBA specifies the transparency of a + pixel. + + \sa alpha(), setAlphaBufferSize() +*/ + +void QPlatformWindowFormat::setAlpha(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::AlphaChannel : QPlatformWindowFormat::NoAlphaChannel); +} + + +/*! + \fn bool QPlatformWindowFormat::accum() const + + Returns true if the accumulation buffer is enabled; otherwise + returns false. The accumulation buffer is disabled by default. + + \sa setAccum(), setAccumBufferSize() +*/ + +/*! + If \a enable is true enables the accumulation buffer; otherwise + disables the accumulation buffer. + + The accumulation buffer is disabled by default. + + The accumulation buffer is used to create blur effects and + multiple exposures. + + \sa accum(), setAccumBufferSize() +*/ + +void QPlatformWindowFormat::setAccum(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::AccumBuffer : QPlatformWindowFormat::NoAccumBuffer); +} + + +/*! + \fn bool QPlatformWindowFormat::stencil() const + + Returns true if the stencil buffer is enabled; otherwise returns + false. The stencil buffer is enabled by default. + + \sa setStencil(), setStencilBufferSize() +*/ + +/*! + If \a enable is true enables the stencil buffer; otherwise + disables the stencil buffer. + + The stencil buffer is enabled by default. + + The stencil buffer masks certain parts of the drawing area so that + masked parts are not drawn on. + + \sa stencil(), setStencilBufferSize() +*/ + +void QPlatformWindowFormat::setStencil(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::StencilBuffer: QPlatformWindowFormat::NoStencilBuffer); +} + + +/*! + \fn bool QPlatformWindowFormat::stereo() const + + Returns true if stereo buffering is enabled; otherwise returns + false. Stereo buffering is disabled by default. + + \sa setStereo() +*/ + +/*! + If \a enable is true enables stereo buffering; otherwise disables + stereo buffering. + + Stereo buffering is disabled by default. + + Stereo buffering provides extra color buffers to generate left-eye + and right-eye images. + + \sa stereo() +*/ + +void QPlatformWindowFormat::setStereo(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::StereoBuffers : QPlatformWindowFormat::NoStereoBuffers); +} + + +/*! + \fn bool QPlatformWindowFormat::directRendering() const + + Returns true if direct rendering is enabled; otherwise returns + false. + + Direct rendering is enabled by default. + + \sa setDirectRendering() +*/ + +/*! + If \a enable is true enables direct rendering; otherwise disables + direct rendering. + + Direct rendering is enabled by default. + + Enabling this option will make OpenGL bypass the underlying window + system and render directly from hardware to the screen, if this is + supported by the system. + + \sa directRendering() +*/ + +void QPlatformWindowFormat::setDirectRendering(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::DirectRendering : QPlatformWindowFormat::IndirectRendering); +} + +/*! + \fn bool QPlatformWindowFormat::sampleBuffers() const + + Returns true if multisample buffer support is enabled; otherwise + returns false. + + The multisample buffer is disabled by default. + + \sa setSampleBuffers() +*/ + +/*! + If \a enable is true, a GL context with multisample buffer support + is picked; otherwise ignored. + + \sa sampleBuffers(), setSamples(), samples() +*/ +void QPlatformWindowFormat::setSampleBuffers(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::SampleBuffers : QPlatformWindowFormat::NoSampleBuffers); +} + +/*! + Returns the number of samples per pixel when multisampling is + enabled. By default, the highest number of samples that is + available is used. + + \sa setSampleBuffers(), sampleBuffers(), setSamples() +*/ +int QPlatformWindowFormat::samples() const +{ + return d->numSamples; +} + +/*! + Set the preferred number of samples per pixel when multisampling + is enabled to \a numSamples. By default, the highest number of + samples available is used. + + \sa setSampleBuffers(), sampleBuffers(), samples() +*/ +void QPlatformWindowFormat::setSamples(int numSamples) +{ + detach(); + if (numSamples < 0) { + qWarning("QPlatformWindowFormat::setSamples: Cannot have negative number of samples per pixel %d", numSamples); + return; + } + d->numSamples = numSamples; + setSampleBuffers(numSamples > 0); +} + +/*! + \since 4.2 + + Set the preferred swap interval. This can be used to sync the GL + drawing into a system window to the vertical refresh of the screen. + Setting an \a interval value of 0 will turn the vertical refresh syncing + off, any value higher than 0 will turn the vertical syncing on. + + Under Windows and under X11, where the \c{WGL_EXT_swap_control} + and \c{GLX_SGI_video_sync} extensions are used, the \a interval + parameter can be used to set the minimum number of video frames + that are displayed before a buffer swap will occur. In effect, + setting the \a interval to 10, means there will be 10 vertical + retraces between every buffer swap. + + Under Windows the \c{WGL_EXT_swap_control} extension has to be present, + and under X11 the \c{GLX_SGI_video_sync} extension has to be present. +*/ +void QPlatformWindowFormat::setSwapInterval(int interval) +{ + detach(); + d->swapInterval = interval; +} + +/*! + \since 4.2 + + Returns the currently set swap interval. -1 is returned if setting + the swap interval isn't supported in the system GL implementation. +*/ +int QPlatformWindowFormat::swapInterval() const +{ + return d->swapInterval; +} + +void QPlatformWindowFormat::setWindowApi(QPlatformWindowFormat::WindowApi api) +{ + detach(); + d->windowApi = api; +} + +QPlatformWindowFormat::WindowApi QPlatformWindowFormat::windowApi() const +{ + return d->windowApi; +} + +void QPlatformWindowFormat::setSharedContext(QPlatformGLContext *context) +{ + d->sharedContext = context; +} + +QPlatformGLContext *QPlatformWindowFormat::sharedGLContext() const +{ + return d->sharedContext; +} + +/*! + \fn bool QPlatformWindowFormat::hasWindowSurface() const + + Returns true if the corresponding widget has an instance of QWindowSurface. + + Otherwise returns false. + + WindowSurface is enabled by default. + + \sa setOverlay() +*/ + +/*! + If \a enable is true a top level QWidget will create a QWindowSurface at creation; + + otherwise the QWidget will only have a QPlatformWindow. + + This is useful for ie. QGLWidget where the QPlatformGLContext controls the surface. + + \sa hasOverlay() +*/ + +void QPlatformWindowFormat::setWindowSurface(bool enable) +{ + setOption(enable ? QPlatformWindowFormat::HasWindowSurface : QPlatformWindowFormat::NoWindowSurface); +} + +/*! + Sets the format option to \a opt. + + \sa testOption() +*/ + +void QPlatformWindowFormat::setOption(QPlatformWindowFormat::FormatOptions opt) +{ + detach(); + if (opt & 0xffff) + d->opts |= opt; + else + d->opts &= ~(opt >> 16); +} + + + +/*! + Returns true if format option \a opt is set; otherwise returns false. + + \sa setOption() +*/ + +bool QPlatformWindowFormat::testOption(QPlatformWindowFormat::FormatOptions opt) const +{ + if (opt & 0xffff) + return (d->opts & opt) != 0; + else + return (d->opts & (opt >> 16)) == 0; +} + +/*! + Set the minimum depth buffer size to \a size. + + \sa depthBufferSize(), setDepth(), depth() +*/ +void QPlatformWindowFormat::setDepthBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setDepthBufferSize: Cannot set negative depth buffer size %d", size); + return; + } + d->depthSize = size; + setDepth(size > 0); +} + +/*! + Returns the depth buffer size. + + \sa depth(), setDepth(), setDepthBufferSize() +*/ +int QPlatformWindowFormat::depthBufferSize() const +{ + return d->depthSize; +} + +/*! + \since 4.2 + + Set the preferred red buffer size to \a size. + + \sa setGreenBufferSize(), setBlueBufferSize(), setAlphaBufferSize() +*/ +void QPlatformWindowFormat::setRedBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setRedBufferSize: Cannot set negative red buffer size %d", size); + return; + } + d->redSize = size; +} + +/*! + \since 4.2 + + Returns the red buffer size. + + \sa setRedBufferSize() +*/ +int QPlatformWindowFormat::redBufferSize() const +{ + return d->redSize; +} + +/*! + \since 4.2 + + Set the preferred green buffer size to \a size. + + \sa setRedBufferSize(), setBlueBufferSize(), setAlphaBufferSize() +*/ +void QPlatformWindowFormat::setGreenBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setGreenBufferSize: Cannot set negative green buffer size %d", size); + return; + } + d->greenSize = size; +} + +/*! + \since 4.2 + + Returns the green buffer size. + + \sa setGreenBufferSize() +*/ +int QPlatformWindowFormat::greenBufferSize() const +{ + return d->greenSize; +} + +/*! + \since 4.2 + + Set the preferred blue buffer size to \a size. + + \sa setRedBufferSize(), setGreenBufferSize(), setAlphaBufferSize() +*/ +void QPlatformWindowFormat::setBlueBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setBlueBufferSize: Cannot set negative blue buffer size %d", size); + return; + } + d->blueSize = size; +} + +/*! + \since 4.2 + + Returns the blue buffer size. + + \sa setBlueBufferSize() +*/ +int QPlatformWindowFormat::blueBufferSize() const +{ + return d->blueSize; +} + +/*! + Set the preferred alpha buffer size to \a size. + This function implicitly enables the alpha channel. + + \sa setRedBufferSize(), setGreenBufferSize(), alphaBufferSize() +*/ +void QPlatformWindowFormat::setAlphaBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setAlphaBufferSize: Cannot set negative alpha buffer size %d", size); + return; + } + d->alphaSize = size; + setAlpha(size > 0); +} + +/*! + Returns the alpha buffer size. + + \sa alpha(), setAlpha(), setAlphaBufferSize() +*/ +int QPlatformWindowFormat::alphaBufferSize() const +{ + return d->alphaSize; +} + +/*! + Set the preferred accumulation buffer size, where \a size is the + bit depth for each RGBA component. + + \sa accum(), setAccum(), accumBufferSize() +*/ +void QPlatformWindowFormat::setAccumBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setAccumBufferSize: Cannot set negative accumulate buffer size %d", size); + return; + } + d->accumSize = size; + setAccum(size > 0); +} + +/*! + Returns the accumulation buffer size. + + \sa setAccumBufferSize(), accum(), setAccum() +*/ +int QPlatformWindowFormat::accumBufferSize() const +{ + return d->accumSize; +} + +/*! + Set the preferred stencil buffer size to \a size. + + \sa stencilBufferSize(), setStencil(), stencil() +*/ +void QPlatformWindowFormat::setStencilBufferSize(int size) +{ + detach(); + if (size < 0) { + qWarning("QPlatformWindowFormat::setStencilBufferSize: Cannot set negative stencil buffer size %d", size); + return; + } + d->stencilSize = size; + setStencil(size > 0); +} + +/*! + Returns the stencil buffer size. + + \sa stencil(), setStencil(), setStencilBufferSize() +*/ +int QPlatformWindowFormat::stencilBufferSize() const +{ + return d->stencilSize; +} + +/*! + Returns the default QPlatformWindowFormat for the application. All QGLWidget + objects that are created use this format unless another format is + specified, e.g. when they are constructed. + + If no special default format has been set using + setDefaultFormat(), the default format is the same as that created + with QPlatformWindowFormat(). + + \sa setDefaultFormat() +*/ + +QPlatformWindowFormat QPlatformWindowFormat::defaultFormat() +{ + return *q_platformwindow_default_format(); +} + +/*! + Sets a new default QPlatformWindowFormat for the application to \a f. For + example, to set single buffering as the default instead of double + buffering, your main() might contain code like this: + \snippet doc/src/snippets/code/src_opengl_qgl.cpp 4 + + \sa defaultFormat() +*/ + +void QPlatformWindowFormat::setDefaultFormat(const QPlatformWindowFormat &f) +{ + *q_platformwindow_default_format() = f; +} + + +/*! + Returns the default QPlatformWindowFormat for overlay contexts. + + The default overlay format is: + \list + \i \link setDoubleBuffer() Double buffer:\endlink Disabled. + \i \link setDepth() Depth buffer:\endlink Disabled. + \i \link setRgba() RGBA:\endlink Disabled (i.e., color index enabled). + \i \link setAlpha() Alpha channel:\endlink Disabled. + \i \link setAccum() Accumulator buffer:\endlink Disabled. + \i \link setStencil() Stencil buffer:\endlink Disabled. + \i \link setStereo() Stereo:\endlink Disabled. + \i \link setDirectRendering() Direct rendering:\endlink Enabled. + \i \link setOverlay() Overlay:\endlink Disabled. + \i \link setSampleBuffers() Multisample buffers:\endlink Disabled. + \i \link setPlane() Plane:\endlink 1 (i.e., first overlay plane). + \endlist + + \sa setDefaultFormat() +*/ + +//QPlatformWindowFormat QPlatformWindowFormat::defaultOverlayFormat() +//{ +// return *defaultOverlayFormatInstance(); +//} + +///*! +// Sets a new default QPlatformWindowFormat for overlay contexts to \a f. This +// format is used whenever a QGLWidget is created with a format that +// hasOverlay() enabled. + +// For example, to get a double buffered overlay context (if +// available), use code like this: + +// \snippet doc/src/snippets/code/src_opengl_qgl.cpp 5 + +// As usual, you can find out after widget creation whether the +// underlying OpenGL system was able to provide the requested +// specification: + +// \snippet doc/src/snippets/code/src_opengl_qgl.cpp 6 + +// \sa defaultOverlayFormat() +//*/ + +//void QPlatformWindowFormat::setDefaultOverlayFormat(const QPlatformWindowFormat &f) +//{ +// QPlatformWindowFormat *defaultFormat = defaultOverlayFormatInstance(); +// *defaultFormat = f; +// // Make sure the user doesn't request that the overlays themselves +// // have overlays, since it is unlikely that the system supports +// // infinitely many planes... +// defaultFormat->setOverlay(false); +//} + + +/*! + Returns true if all the options of the two QPlatformWindowFormat objects + \a a and \a b are equal; otherwise returns false. + + \relates QPlatformWindowFormat +*/ + +bool operator==(const QPlatformWindowFormat& a, const QPlatformWindowFormat& b) +{ + return (a.d == b.d) || ((int) a.d->opts == (int) b.d->opts + && a.d->alphaSize == b.d->alphaSize + && a.d->accumSize == b.d->accumSize + && a.d->stencilSize == b.d->stencilSize + && a.d->depthSize == b.d->depthSize + && a.d->redSize == b.d->redSize + && a.d->greenSize == b.d->greenSize + && a.d->blueSize == b.d->blueSize + && a.d->numSamples == b.d->numSamples + && a.d->swapInterval == b.d->swapInterval + && a.d->windowApi == b.d->windowApi); +} + + +/*! + Returns false if all the options of the two QPlatformWindowFormat objects + \a a and \a b are equal; otherwise returns true. + + \relates QPlatformWindowFormat +*/ + +bool operator!=(const QPlatformWindowFormat& a, const QPlatformWindowFormat& b) +{ + return !(a == b); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QPlatformWindowFormat &f) +{ + const QPlatformWindowFormatPrivate * const d = f.d; + + dbg.nospace() << "QGLFormat(" + << "options " << d->opts + << ", depthBufferSize " << d->depthSize + << ", accumBufferSize " << d->accumSize + << ", stencilBufferSize " << d->stencilSize + << ", redBufferSize " << d->redSize + << ", greenBufferSize " << d->greenSize + << ", blueBufferSize " << d->blueSize + << ", alphaBufferSize " << d->alphaSize + << ", samples " << d->numSamples + << ", swapInterval " << d->swapInterval + << ')'; + + return dbg.space(); +} +#endif diff --git a/src/gui/kernel/qplatformwindowformat_qpa.h b/src/gui/kernel/qplatformwindowformat_qpa.h new file mode 100644 index 0000000000..ba21ba4626 --- /dev/null +++ b/src/gui/kernel/qplatformwindowformat_qpa.h @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QPLATFORMWINDOWFORMAT_QPA_H +#define QPLATFORMWINDOWFORMAT_QPA_H + +#include <QtGui/QPlatformWindow> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPlatformWindowFormatPrivate; + +class Q_GUI_EXPORT QPlatformWindowFormat +{ +public: + enum FormatOption { + DoubleBuffer = 0x0001, + DepthBuffer = 0x0002, + Rgba = 0x0004, + AlphaChannel = 0x0008, + AccumBuffer = 0x0010, + StencilBuffer = 0x0020, + StereoBuffers = 0x0040, + DirectRendering = 0x0080, + HasOverlay = 0x0100, + SampleBuffers = 0x0200, + DeprecatedFunctions = 0x0400, + HasWindowSurface = 0x0800, + SingleBuffer = DoubleBuffer << 16, + NoDepthBuffer = DepthBuffer << 16, + ColorIndex = Rgba << 16, + NoAlphaChannel = AlphaChannel << 16, + NoAccumBuffer = AccumBuffer << 16, + NoStencilBuffer = StencilBuffer << 16, + NoStereoBuffers = StereoBuffers << 16, + IndirectRendering = DirectRendering << 16, + NoOverlay = HasOverlay << 16, + NoSampleBuffers = SampleBuffers << 16, + NoDeprecatedFunctions = DeprecatedFunctions << 16, + NoWindowSurface = HasWindowSurface << 16 + + }; + Q_DECLARE_FLAGS(FormatOptions, FormatOption) + + enum WindowApi { + Raster, + OpenGL, + OpenVG + }; + + QPlatformWindowFormat(); + QPlatformWindowFormat(FormatOptions options); + QPlatformWindowFormat(const QPlatformWindowFormat &other); + QPlatformWindowFormat &operator=(const QPlatformWindowFormat &other); + ~QPlatformWindowFormat(); + + void setDepthBufferSize(int size); + int depthBufferSize() const; + + void setAccumBufferSize(int size); + int accumBufferSize() const; + + void setRedBufferSize(int size); + int redBufferSize() const; + + void setGreenBufferSize(int size); + int greenBufferSize() const; + + void setBlueBufferSize(int size); + int blueBufferSize() const; + + void setAlphaBufferSize(int size); + int alphaBufferSize() const; + + void setStencilBufferSize(int size); + int stencilBufferSize() const; + + void setSampleBuffers(bool enable); + bool sampleBuffers() const; + + void setSamples(int numSamples); + int samples() const; + + void setSwapInterval(int interval); + int swapInterval() const; + + void setWindowApi(QPlatformWindowFormat::WindowApi api); + WindowApi windowApi() const; + + void setSharedContext(QPlatformGLContext *context); + QPlatformGLContext *sharedGLContext() const; + + bool doubleBuffer() const; + void setDoubleBuffer(bool enable); + bool depth() const; + void setDepth(bool enable); + bool rgba() const; + void setRgba(bool enable); + bool alpha() const; + void setAlpha(bool enable); + bool accum() const; + void setAccum(bool enable); + bool stencil() const; + void setStencil(bool enable); + bool stereo() const; + void setStereo(bool enable); + bool directRendering() const; + void setDirectRendering(bool enable); + bool hasWindowSurface() const; + void setWindowSurface(bool enable); + + void setOption(QPlatformWindowFormat::FormatOptions opt); + bool testOption(QPlatformWindowFormat::FormatOptions opt) const; + + static QPlatformWindowFormat defaultFormat(); + static void setDefaultFormat(const QPlatformWindowFormat& f); + +private: + QPlatformWindowFormatPrivate *d; + + void detach(); + + friend Q_GUI_EXPORT bool operator==(const QPlatformWindowFormat&, const QPlatformWindowFormat&); + friend Q_GUI_EXPORT bool operator!=(const QPlatformWindowFormat&, const QPlatformWindowFormat&); +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QPlatformWindowFormat &); +#endif +}; + +Q_GUI_EXPORT bool operator==(const QPlatformWindowFormat&, const QPlatformWindowFormat&); +Q_GUI_EXPORT bool operator!=(const QPlatformWindowFormat&, const QPlatformWindowFormat&); + +#ifndef QT_NO_DEBUG_STREAM +Q_OPENGL_EXPORT QDebug operator<<(QDebug, const QPlatformWindowFormat &); +#endif + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPlatformWindowFormat::FormatOptions) + +inline bool QPlatformWindowFormat::doubleBuffer() const +{ + return testOption(QPlatformWindowFormat::DoubleBuffer); +} + +inline bool QPlatformWindowFormat::depth() const +{ + return testOption(QPlatformWindowFormat::DepthBuffer); +} + +inline bool QPlatformWindowFormat::rgba() const +{ + return testOption(QPlatformWindowFormat::Rgba); +} + +inline bool QPlatformWindowFormat::alpha() const +{ + return testOption(QPlatformWindowFormat::AlphaChannel); +} + +inline bool QPlatformWindowFormat::accum() const +{ + return testOption(QPlatformWindowFormat::AccumBuffer); +} + +inline bool QPlatformWindowFormat::stencil() const +{ + return testOption(QPlatformWindowFormat::StencilBuffer); +} + +inline bool QPlatformWindowFormat::stereo() const +{ + return testOption(QPlatformWindowFormat::StereoBuffers); +} + +inline bool QPlatformWindowFormat::directRendering() const +{ + return testOption(QPlatformWindowFormat::DirectRendering); +} + +inline bool QPlatformWindowFormat::hasWindowSurface() const +{ + return testOption(QPlatformWindowFormat::HasWindowSurface); +} + +inline bool QPlatformWindowFormat::sampleBuffers() const +{ + return testOption(QPlatformWindowFormat::SampleBuffers); +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QPLATFORMWINDOWFORMAT_QPA_H diff --git a/src/gui/kernel/qsessionmanager.h b/src/gui/kernel/qsessionmanager.h new file mode 100644 index 0000000000..bd851b87e9 --- /dev/null +++ b/src/gui/kernel/qsessionmanager.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSESSIONMANAGER_H +#define QSESSIONMANAGER_H + +#include <QtCore/qobject.h> +#include <QtGui/qwindowdefs.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> + +#ifndef QT_NO_SESSIONMANAGER + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QSessionManagerPrivate; + +class Q_GUI_EXPORT QSessionManager : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSessionManager) + QSessionManager(QApplication *app, QString &id, QString &key); + ~QSessionManager(); +public: + QString sessionId() const; + QString sessionKey() const; +#if defined(Q_WS_X11) || defined(Q_WS_MAC) + void *handle() const; +#endif + + bool allowsInteraction(); + bool allowsErrorInteraction(); + void release(); + + void cancel(); + + enum RestartHint { + RestartIfRunning, + RestartAnyway, + RestartImmediately, + RestartNever + }; + void setRestartHint(RestartHint); + RestartHint restartHint() const; + + void setRestartCommand(const QStringList&); + QStringList restartCommand() const; + void setDiscardCommand(const QStringList&); + QStringList discardCommand() const; + + void setManagerProperty(const QString& name, const QString& value); + void setManagerProperty(const QString& name, const QStringList& value); + + bool isPhase2() const; + void requestPhase2(); + +private: + friend class QApplication; + friend class QApplicationPrivate; + friend class QBaseApplication; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SESSIONMANAGER + +#endif // QSESSIONMANAGER_H diff --git a/src/gui/kernel/qsessionmanager_qpa.cpp b/src/gui/kernel/qsessionmanager_qpa.cpp new file mode 100644 index 0000000000..ef532d7981 --- /dev/null +++ b/src/gui/kernel/qsessionmanager_qpa.cpp @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qsessionmanager.h> + +#include <private/qobject_p.h> +#ifndef QT_NO_SESSIONMANAGER + +QT_BEGIN_NAMESPACE + +class QSessionManagerPrivate : public QObjectPrivate +{ +public: + QSessionManagerPrivate(QSessionManager *m, const QString &id, + const QString &key); + + QStringList restartCommand; + QStringList discardCommand; + const QString sessionId; + const QString sessionKey; + QSessionManager::RestartHint restartHint; +}; + +QSessionManagerPrivate::QSessionManagerPrivate(QSessionManager*, + const QString &id, + const QString &key) + : QObjectPrivate(), sessionId(id), sessionKey(key) +{ +} + +QSessionManager::QSessionManager(QApplication *app, QString &id, QString &key) + : QObject(*(new QSessionManagerPrivate(this, id, key)), app) +{ + Q_D(QSessionManager); + d->restartHint = RestartIfRunning; +} + +QSessionManager::~QSessionManager() +{ +} + +QString QSessionManager::sessionId() const +{ + Q_D(const QSessionManager); + return d->sessionId; +} + +QString QSessionManager::sessionKey() const +{ + Q_D(const QSessionManager); + return d->sessionKey; +} + + +bool QSessionManager::allowsInteraction() +{ + return false; +} + +bool QSessionManager::allowsErrorInteraction() +{ + return false; +} + +void QSessionManager::release() +{ +} + +void QSessionManager::cancel() +{ +} + +void QSessionManager::setRestartHint(QSessionManager::RestartHint hint) +{ + Q_D(QSessionManager); + d->restartHint = hint; +} + +QSessionManager::RestartHint QSessionManager::restartHint() const +{ + Q_D(const QSessionManager); + return d->restartHint; +} + +void QSessionManager::setRestartCommand(const QStringList &command) +{ + Q_D(QSessionManager); + d->restartCommand = command; +} + +QStringList QSessionManager::restartCommand() const +{ + Q_D(const QSessionManager); + return d->restartCommand; +} + +void QSessionManager::setDiscardCommand(const QStringList &command) +{ + Q_D(QSessionManager); + d->discardCommand = command; +} + +QStringList QSessionManager::discardCommand() const +{ + Q_D(const QSessionManager); + return d->discardCommand; +} + +void QSessionManager::setManagerProperty(const QString &name, + const QString &value) +{ + Q_UNUSED(name); + Q_UNUSED(value); +} + +void QSessionManager::setManagerProperty(const QString &name, + const QStringList &value) +{ + Q_UNUSED(name); + Q_UNUSED(value); +} + +bool QSessionManager::isPhase2() const +{ + return false; +} + +void QSessionManager::requestPhase2() +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_SESSIONMANAGER diff --git a/src/gui/kernel/qsessionmanager_qws.cpp b/src/gui/kernel/qsessionmanager_qws.cpp new file mode 100644 index 0000000000..e355d055ad --- /dev/null +++ b/src/gui/kernel/qsessionmanager_qws.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qsessionmanager.h> + +#ifndef QT_NO_SESSIONMANAGER + +QT_BEGIN_NAMESPACE + +class QSessionManagerPrivate : public QObjectPrivate +{ +public: + QSessionManagerPrivate(QSessionManager *m, const QString &id, + const QString &key); + + QStringList restartCommand; + QStringList discardCommand; + const QString sessionId; + const QString sessionKey; + QSessionManager::RestartHint restartHint; +}; + +QSessionManagerPrivate::QSessionManagerPrivate(QSessionManager*, + const QString &id, + const QString &key) + : QObjectPrivate(), sessionId(id), sessionKey(key) +{ +} + +QSessionManager::QSessionManager(QApplication *app, QString &id, QString &key) + : QObject(*new QSessionManagerPrivate(this, id, key), app) +{ + Q_D(QSessionManager); + d->restartHint = RestartIfRunning; +} + +QSessionManager::~QSessionManager() +{ +} + +QString QSessionManager::sessionId() const +{ + Q_D(const QSessionManager); + return d->sessionId; +} + +QString QSessionManager::sessionKey() const +{ + Q_D(const QSessionManager); + return d->sessionKey; +} + + +bool QSessionManager::allowsInteraction() +{ + return false; +} + +bool QSessionManager::allowsErrorInteraction() +{ + return false; +} + +void QSessionManager::release() +{ +} + +void QSessionManager::cancel() +{ +} + +void QSessionManager::setRestartHint(QSessionManager::RestartHint hint) +{ + Q_D(QSessionManager); + d->restartHint = hint; +} + +QSessionManager::RestartHint QSessionManager::restartHint() const +{ + Q_D(const QSessionManager); + return d->restartHint; +} + +void QSessionManager::setRestartCommand(const QStringList &command) +{ + Q_D(QSessionManager); + d->restartCommand = command; +} + +QStringList QSessionManager::restartCommand() const +{ + Q_D(const QSessionManager); + return d->restartCommand; +} + +void QSessionManager::setDiscardCommand(const QStringList &command) +{ + Q_D(QSessionManager); + d->discardCommand = command; +} + +QStringList QSessionManager::discardCommand() const +{ + Q_D(const QSessionManager); + return d->discardCommand; +} + +void QSessionManager::setManagerProperty(const QString &name, + const QString &value) +{ + Q_UNUSED(name); + Q_UNUSED(value); +} + +void QSessionManager::setManagerProperty(const QString &name, + const QStringList &value) +{ + Q_UNUSED(name); + Q_UNUSED(value); +} + +bool QSessionManager::isPhase2() const +{ + return false; +} + +void QSessionManager::requestPhase2() +{ +} + +QT_END_NAMESPACE + +#endif // QT_NO_SESSIONMANAGER diff --git a/src/gui/kernel/qshortcut.cpp b/src/gui/kernel/qshortcut.cpp new file mode 100644 index 0000000000..978ef0c240 --- /dev/null +++ b/src/gui/kernel/qshortcut.cpp @@ -0,0 +1,407 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qshortcut.h" +#include "private/qwidget_p.h" + +#ifndef QT_NO_SHORTCUT +#include <qevent.h> +#include <qwhatsthis.h> +#include <qmenu.h> +#include <qapplication.h> +#include <private/qapplication_p.h> +#include <private/qshortcutmap_p.h> + +QT_BEGIN_NAMESPACE + +#define QAPP_CHECK(functionName) \ + if (!qApp) { \ + qWarning("QShortcut: Initialize QApplication before calling '" functionName "'."); \ + return; \ + } + +/*! + \class QShortcut + \brief The QShortcut class is used to create keyboard shortcuts. + + \ingroup events + + + The QShortcut class provides a way of connecting keyboard + shortcuts to Qt's \l{signals and slots} mechanism, so that + objects can be informed when a shortcut is executed. The shortcut + can be set up to contain all the key presses necessary to + describe a keyboard shortcut, including the states of modifier + keys such as \gui Shift, \gui Ctrl, and \gui Alt. + + \target mnemonic + + On certain widgets, using '&' in front of a character will + automatically create a mnemonic (a shortcut) for that character, + e.g. "E&xit" will create the shortcut \gui Alt+X (use '&&' to + display an actual ampersand). The widget might consume and perform + an action on a given shortcut. On X11 the ampersand will not be + shown and the character will be underlined. On Windows, shortcuts + are normally not displayed until the user presses the \gui Alt + key, but this is a setting the user can change. On Mac, shortcuts + are disabled by default. Call qt_set_sequence_auto_mnemonic() to + enable them. However, because mnemonic shortcuts do not fit in + with Aqua's guidelines, Qt will not show the shortcut character + underlined. + + For applications that use menus, it may be more convenient to + use the convenience functions provided in the QMenu class to + assign keyboard shortcuts to menu items as they are created. + Alternatively, shortcuts may be associated with other types of + actions in the QAction class. + + The simplest way to create a shortcut for a particular widget is + to construct the shortcut with a key sequence. For example: + + \snippet doc/src/snippets/code/src_gui_kernel_qshortcut.cpp 0 + + When the user types the \l{QKeySequence}{key sequence} + for a given shortcut, the shortcut's activated() signal is + emitted. (In the case of ambiguity, the activatedAmbiguously() + signal is emitted.) A shortcut is "listened for" by Qt's event + loop when the shortcut's parent widget is receiving events. + + A shortcut's key sequence can be set with setKey() and retrieved + with key(). A shortcut can be enabled or disabled with + setEnabled(), and can have "What's This?" help text set with + setWhatsThis(). + + \sa QShortcutEvent, QKeySequence, QAction +*/ + +/*! + \fn QWidget *QShortcut::parentWidget() const + + Returns the shortcut's parent widget. +*/ + +/*! + \fn void QShortcut::activated() + + This signal is emitted when the user types the shortcut's key + sequence. + + \sa activatedAmbiguously() +*/ + +/*! + \fn void QShortcut::activatedAmbiguously() + + When a key sequence is being typed at the keyboard, it is said to + be ambiguous as long as it matches the start of more than one + shortcut. + + When a shortcut's key sequence is completed, + activatedAmbiguously() is emitted if the key sequence is still + ambiguous (i.e., it is the start of one or more other shortcuts). + The activated() signal is not emitted in this case. + + \sa activated() +*/ + +/* + \internal + Private data accessed through d-pointer. +*/ +class QShortcutPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QShortcut) +public: + QShortcutPrivate() : sc_context(Qt::WindowShortcut), sc_enabled(true), sc_autorepeat(true), sc_id(0) {} + QKeySequence sc_sequence; + Qt::ShortcutContext sc_context; + bool sc_enabled; + bool sc_autorepeat; + int sc_id; + QString sc_whatsthis; + void redoGrab(QShortcutMap &map); +}; + +void QShortcutPrivate::redoGrab(QShortcutMap &map) +{ + Q_Q(QShortcut); + if (!parent) { + qWarning("QShortcut: No widget parent defined"); + return; + } + + if (sc_id) + map.removeShortcut(sc_id, q); + if (sc_sequence.isEmpty()) + return; + sc_id = map.addShortcut(q, sc_sequence, sc_context); + if (!sc_enabled) + map.setShortcutEnabled(false, sc_id, q); + if (!sc_autorepeat) + map.setShortcutAutoRepeat(false, sc_id, q); +} + +/*! + Constructs a QShortcut object for the \a parent widget. Since no + shortcut key sequence is specified, the shortcut will not emit any + signals. + + \sa setKey() +*/ +QShortcut::QShortcut(QWidget *parent) + : QObject(*new QShortcutPrivate, parent) +{ + Q_ASSERT(parent != 0); +} + +/*! + Constructs a QShortcut object for the \a parent widget. The shortcut + operates on its parent, listening for \l{QShortcutEvent}s that + match the \a key sequence. Depending on the ambiguity of the + event, the shortcut will call the \a member function, or the \a + ambiguousMember function, if the key press was in the shortcut's + \a context. +*/ +QShortcut::QShortcut(const QKeySequence &key, QWidget *parent, + const char *member, const char *ambiguousMember, + Qt::ShortcutContext context) + : QObject(*new QShortcutPrivate, parent) +{ + QAPP_CHECK("QShortcut"); + + Q_D(QShortcut); + Q_ASSERT(parent != 0); + d->sc_context = context; + d->sc_sequence = key; + d->redoGrab(qApp->d_func()->shortcutMap); + if (member) + connect(this, SIGNAL(activated()), parent, member); + if (ambiguousMember) + connect(this, SIGNAL(activatedAmbiguously()), parent, ambiguousMember); +} + +/*! + Destroys the shortcut. +*/ +QShortcut::~QShortcut() +{ + Q_D(QShortcut); + if (qApp) + qApp->d_func()->shortcutMap.removeShortcut(d->sc_id, this); +} + +/*! + \property QShortcut::key + \brief the shortcut's key sequence + + This is a key sequence with an optional combination of Shift, Ctrl, + and Alt. The key sequence may be supplied in a number of ways: + + \snippet doc/src/snippets/code/src_gui_kernel_qshortcut.cpp 1 + + By default, this property contains an empty key sequence. +*/ +void QShortcut::setKey(const QKeySequence &key) +{ + Q_D(QShortcut); + if (d->sc_sequence == key) + return; + QAPP_CHECK("setKey"); + d->sc_sequence = key; + d->redoGrab(qApp->d_func()->shortcutMap); +} + +QKeySequence QShortcut::key() const +{ + Q_D(const QShortcut); + return d->sc_sequence; +} + +/*! + \property QShortcut::enabled + \brief whether the shortcut is enabled + + An enabled shortcut emits the activated() or activatedAmbiguously() + signal when a QShortcutEvent occurs that matches the shortcut's + key() sequence. + + If the application is in \c WhatsThis mode the shortcut will not emit + the signals, but will show the "What's This?" text instead. + + By default, this property is true. + + \sa whatsThis +*/ +void QShortcut::setEnabled(bool enable) +{ + Q_D(QShortcut); + if (d->sc_enabled == enable) + return; + QAPP_CHECK("setEnabled"); + d->sc_enabled = enable; + qApp->d_func()->shortcutMap.setShortcutEnabled(enable, d->sc_id, this); +} + +bool QShortcut::isEnabled() const +{ + Q_D(const QShortcut); + return d->sc_enabled; +} + +/*! + \property QShortcut::context + \brief the context in which the shortcut is valid + + A shortcut's context decides in which circumstances a shortcut is + allowed to be triggered. The normal context is Qt::WindowShortcut, + which allows the shortcut to trigger if the parent (the widget + containing the shortcut) is a subwidget of the active top-level + window. + + By default, this property is set to Qt::WindowShortcut. +*/ +void QShortcut::setContext(Qt::ShortcutContext context) +{ + Q_D(QShortcut); + if(d->sc_context == context) + return; + QAPP_CHECK("setContext"); + d->sc_context = context; + d->redoGrab(qApp->d_func()->shortcutMap); +} + +Qt::ShortcutContext QShortcut::context() +{ + Q_D(QShortcut); + return d->sc_context; +} + +/*! + \property QShortcut::whatsThis + \brief the shortcut's "What's This?" help text + + The text will be shown when the application is in "What's + This?" mode and the user types the shortcut key() sequence. + + To set "What's This?" help on a menu item (with or without a + shortcut key), set the help on the item's action. + + By default, this property contains an empty string. + + \sa QWhatsThis::inWhatsThisMode(), QAction::setWhatsThis() +*/ +void QShortcut::setWhatsThis(const QString &text) +{ + Q_D(QShortcut); + d->sc_whatsthis = text; +} + +QString QShortcut::whatsThis() const +{ + Q_D(const QShortcut); + return d->sc_whatsthis; +} + +/*! + \property QShortcut::autoRepeat + \brief whether the shortcut can auto repeat + \since 4.2 + + If true, the shortcut will auto repeat when the keyboard shortcut + combination is held down, provided that keyboard auto repeat is + enabled on the system. + The default value is true. +*/ +void QShortcut::setAutoRepeat(bool on) +{ + Q_D(QShortcut); + if (d->sc_autorepeat == on) + return; + QAPP_CHECK("setAutoRepeat"); + d->sc_autorepeat = on; + qApp->d_func()->shortcutMap.setShortcutAutoRepeat(on, d->sc_id, this); +} + +bool QShortcut::autoRepeat() const +{ + Q_D(const QShortcut); + return d->sc_autorepeat; +} + +/*! + Returns the shortcut's ID. + + \sa QShortcutEvent::shortcutId() +*/ +int QShortcut::id() const +{ + Q_D(const QShortcut); + return d->sc_id; +} + +/*! + \internal +*/ +bool QShortcut::event(QEvent *e) +{ + Q_D(QShortcut); + bool handled = false; + if (d->sc_enabled && e->type() == QEvent::Shortcut) { + QShortcutEvent *se = static_cast<QShortcutEvent *>(e); + if (se->shortcutId() == d->sc_id && se->key() == d->sc_sequence){ +#ifndef QT_NO_WHATSTHIS + if (QWhatsThis::inWhatsThisMode()) { + QWhatsThis::showText(QCursor::pos(), d->sc_whatsthis); + handled = true; + } else +#endif + if (se->isAmbiguous()) + emit activatedAmbiguously(); + else + emit activated(); + handled = true; + } + } + return handled; +} +#endif // QT_NO_SHORTCUT + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qshortcut.h b/src/gui/kernel/qshortcut.h new file mode 100644 index 0000000000..f432d9ad97 --- /dev/null +++ b/src/gui/kernel/qshortcut.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSHORTCUT_H +#define QSHORTCUT_H + +#include <QtGui/qwidget.h> +#include <QtGui/qkeysequence.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SHORTCUT + +class QShortcutPrivate; +class Q_GUI_EXPORT QShortcut : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QShortcut) + Q_PROPERTY(QKeySequence key READ key WRITE setKey) + Q_PROPERTY(QString whatsThis READ whatsThis WRITE setWhatsThis) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(bool autoRepeat READ autoRepeat WRITE setAutoRepeat) + Q_PROPERTY(Qt::ShortcutContext context READ context WRITE setContext) +public: + explicit QShortcut(QWidget *parent); + QShortcut(const QKeySequence& key, QWidget *parent, + const char *member = 0, const char *ambiguousMember = 0, + Qt::ShortcutContext context = Qt::WindowShortcut); + ~QShortcut(); + + void setKey(const QKeySequence& key); + QKeySequence key() const; + + void setEnabled(bool enable); + bool isEnabled() const; + + void setContext(Qt::ShortcutContext context); + Qt::ShortcutContext context(); + + void setWhatsThis(const QString &text); + QString whatsThis() const; + + void setAutoRepeat(bool on); + bool autoRepeat() const; + + int id() const; + + inline QWidget *parentWidget() const + { return static_cast<QWidget *>(QObject::parent()); } + +Q_SIGNALS: + void activated(); + void activatedAmbiguously(); + +protected: + bool event(QEvent *e); +}; + +#endif // QT_NO_SHORTCUT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSHORTCUT_H diff --git a/src/gui/kernel/qshortcutmap.cpp b/src/gui/kernel/qshortcutmap.cpp new file mode 100644 index 0000000000..5e65e676d9 --- /dev/null +++ b/src/gui/kernel/qshortcutmap.cpp @@ -0,0 +1,897 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qshortcutmap_p.h" +#include "private/qobject_p.h" +#include "qkeysequence.h" +#include "qgraphicsscene.h" +#include "qgraphicsview.h" +#include "qdebug.h" +#include "qevent.h" +#include "qwidget.h" +#include "qapplication.h" +#include "qvector.h" +#include "qmenu.h" +#include "qmenubar.h" +#include "qshortcut.h" +#include "qapplication_p.h" +#include <private/qaction_p.h> +#include <private/qkeymapper_p.h> +#include <private/qwidget_p.h> + +#ifndef QT_NO_SHORTCUT + +QT_BEGIN_NAMESPACE + +// To enable verbose output uncomment below +//#define DEBUG_QSHORTCUTMAP + +/* \internal + Entry data for QShortcutMap + Contains: + Keysequence for entry + Pointer to parent owning the sequence +*/ +struct QShortcutEntry +{ + QShortcutEntry() + : keyseq(0), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(0) + {} + + QShortcutEntry(const QKeySequence &k) + : keyseq(k), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(0) + {} + + QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i) + : keyseq(k), context(c), enabled(true), autorepeat(1), id(i), owner(o) + {} + + QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i, bool a) + : keyseq(k), context(c), enabled(true), autorepeat(a), id(i), owner(o) + {} + + bool operator<(const QShortcutEntry &f) const + { return keyseq < f.keyseq; } + + QKeySequence keyseq; + Qt::ShortcutContext context; + bool enabled : 1; + bool autorepeat : 1; + signed int id; + QObject *owner; +}; + +#if 0 //ndef QT_NO_DEBUG_STREAM +/*! \internal + QDebug operator<< for easy debug output of the shortcut entries. +*/ +static QDebug &operator<<(QDebug &dbg, const QShortcutEntry *se) { + if (!se) + return dbg << "QShortcutEntry(0x0)"; + dbg.nospace() + << "QShortcutEntry(" << se->keyseq + << "), id(" << se->id << "), enabled(" << se->enabled << "), autorepeat(" << se->autorepeat + << "), owner(" << se->owner << ')'; + return dbg.space(); +} +#endif // QT_NO_DEBUGSTREAM + +/* \internal + Private data for QShortcutMap +*/ +class QShortcutMapPrivate +{ + Q_DECLARE_PUBLIC(QShortcutMap) + +public: + QShortcutMapPrivate(QShortcutMap* parent) + : q_ptr(parent), currentId(0), ambigCount(0), currentState(QKeySequence::NoMatch) + { + identicals.reserve(10); + currentSequences.reserve(10); + } + QShortcutMap *q_ptr; // Private's parent + + QList<QShortcutEntry> sequences; // All sequences! + + int currentId; // Global shortcut ID number + int ambigCount; // Index of last enabled ambiguous dispatch + QKeySequence::SequenceMatch currentState; + QVector<QKeySequence> currentSequences; // Sequence for the current state + QVector<QKeySequence> newEntries; + QKeySequence prevSequence; // Sequence for the previous identical match + QVector<const QShortcutEntry*> identicals; // Last identical matches +}; + + +/*! \internal + QShortcutMap constructor. +*/ +QShortcutMap::QShortcutMap() + : d_ptr(new QShortcutMapPrivate(this)) +{ + resetState(); +} + +/*! \internal + QShortcutMap destructor. +*/ +QShortcutMap::~QShortcutMap() +{ +} + +/*! \internal + Adds a shortcut to the global map. + Returns the id of the newly added shortcut. +*/ +int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context) +{ + Q_ASSERT_X(owner, "QShortcutMap::addShortcut", "All shortcuts need an owner"); + Q_ASSERT_X(!key.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map"); + Q_D(QShortcutMap); + + QShortcutEntry newEntry(owner, key, context, --(d->currentId), true); + QList<QShortcutEntry>::iterator it = qUpperBound(d->sequences.begin(), d->sequences.end(), newEntry); + d->sequences.insert(it, newEntry); // Insert sorted +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() + << "QShortcutMap::addShortcut(" << owner << ", " + << key << ", " << context << ") = " << d->currentId; +#endif + return d->currentId; +} + +/*! \internal + Removes a shortcut from the global map. + If \a owner is 0, all entries in the map with the key sequence specified + is removed. If \a key is null, all sequences for \a owner is removed from + the map. If \a id is 0, any identical \a key sequences owned by \a owner + are removed. + Returns the number of sequences removed from the map. +*/ + +int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key) +{ + Q_D(QShortcutMap); + int itemsRemoved = 0; + bool allOwners = (owner == 0); + bool allKeys = key.isEmpty(); + bool allIds = id == 0; + + // Special case, remove everything + if (allOwners && allKeys && id == 0) { + itemsRemoved = d->sequences.size(); + d->sequences.clear(); + return itemsRemoved; + } + + int i = d->sequences.size()-1; + while (i>=0) + { + const QShortcutEntry &entry = d->sequences.at(i); + int entryId = entry.id; + if ((allOwners || entry.owner == owner) + && (allIds || entry.id == id) + && (allKeys || entry.keyseq == key)) { + d->sequences.removeAt(i); + ++itemsRemoved; + } + if (id == entryId) + return itemsRemoved; + --i; + } +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() + << "QShortcutMap::removeShortcut(" << id << ", " << owner << ", " + << key << ") = " << itemsRemoved; +#endif + return itemsRemoved; +} + +/*! \internal + Changes the enable state of a shortcut to \a enable. + If \a owner is 0, all entries in the map with the key sequence specified + is removed. If \a key is null, all sequences for \a owner is removed from + the map. If \a id is 0, any identical \a key sequences owned by \a owner + are changed. + Returns the number of sequences which are matched in the map. +*/ +int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key) +{ + Q_D(QShortcutMap); + int itemsChanged = 0; + bool allOwners = (owner == 0); + bool allKeys = key.isEmpty(); + bool allIds = id == 0; + + int i = d->sequences.size()-1; + while (i>=0) + { + QShortcutEntry entry = d->sequences.at(i); + if ((allOwners || entry.owner == owner) + && (allIds || entry.id == id) + && (allKeys || entry.keyseq == key)) { + d->sequences[i].enabled = enable; + ++itemsChanged; + } + if (id == entry.id) + return itemsChanged; + --i; + } +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() + << "QShortcutMap::setShortcutEnabled(" << enable << ", " << id << ", " + << owner << ", " << key << ") = " << itemsChanged; +#endif + return itemsChanged; +} + +/*! \internal + Changes the auto repeat state of a shortcut to \a enable. + If \a owner is 0, all entries in the map with the key sequence specified + is removed. If \a key is null, all sequences for \a owner is removed from + the map. If \a id is 0, any identical \a key sequences owned by \a owner + are changed. + Returns the number of sequences which are matched in the map. +*/ +int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &key) +{ + Q_D(QShortcutMap); + int itemsChanged = 0; + bool allOwners = (owner == 0); + bool allKeys = key.isEmpty(); + bool allIds = id == 0; + + int i = d->sequences.size()-1; + while (i>=0) + { + QShortcutEntry entry = d->sequences.at(i); + if ((allOwners || entry.owner == owner) + && (allIds || entry.id == id) + && (allKeys || entry.keyseq == key)) { + d->sequences[i].autorepeat = on; + ++itemsChanged; + } + if (id == entry.id) + return itemsChanged; + --i; + } +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() + << "QShortcutMap::setShortcutAutoRepeat(" << on << ", " << id << ", " + << owner << ", " << key << ") = " << itemsChanged; +#endif + return itemsChanged; +} + +/*! \internal + Resets the state of the statemachine to NoMatch +*/ +void QShortcutMap::resetState() +{ + Q_D(QShortcutMap); + d->currentState = QKeySequence::NoMatch; + clearSequence(d->currentSequences); +} + +/*! \internal + Returns the current state of the statemachine +*/ +QKeySequence::SequenceMatch QShortcutMap::state() +{ + Q_D(QShortcutMap); + return d->currentState; +} + +/*! \internal + Uses ShortcutOverride event to see if any widgets want to override + the event. If not, uses nextState(QKeyEvent) to check for a grabbed + Shortcut, and dispatchEvent() is found an identical. + \sa nextState dispatchEvent +*/ +bool QShortcutMap::tryShortcutEvent(QObject *o, QKeyEvent *e) +{ + Q_D(QShortcutMap); + + bool wasAccepted = e->isAccepted(); + bool wasSpontaneous = e->spont; + if (d->currentState == QKeySequence::NoMatch) { + ushort orgType = e->t; + e->t = QEvent::ShortcutOverride; + e->ignore(); + QApplication::sendEvent(o, e); + e->t = orgType; + e->spont = wasSpontaneous; + if (e->isAccepted()) { + if (!wasAccepted) + e->ignore(); + return false; + } + } + + QKeySequence::SequenceMatch result = nextState(e); + bool stateWasAccepted = e->isAccepted(); + if (wasAccepted) + e->accept(); + else + e->ignore(); + + int identicalMatches = d->identicals.count(); + + switch(result) { + case QKeySequence::NoMatch: + return stateWasAccepted; + case QKeySequence::ExactMatch: + resetState(); + dispatchEvent(e); + default: + break; + } + // If nextState is QKeySequence::ExactMatch && identicals.count == 0 + // we've only found disabled shortcuts + return identicalMatches > 0 || result == QKeySequence::PartialMatch; +} + +/*! \internal + Returns the next state of the statemachine + If return value is SequenceMatch::ExactMatch, then a call to matches() + will return a QObjects* list of all matching objects for the last matching + sequence. +*/ +QKeySequence::SequenceMatch QShortcutMap::nextState(QKeyEvent *e) +{ + Q_D(QShortcutMap); + // Modifiers can NOT be shortcuts... + if (e->key() >= Qt::Key_Shift && + e->key() <= Qt::Key_Alt) + return d->currentState; + + QKeySequence::SequenceMatch result = QKeySequence::NoMatch; + + // We start fresh each time.. + d->identicals.resize(0); + + result = find(e); + if (result == QKeySequence::NoMatch && e->modifiers() & Qt::ShiftModifier) { + // If Shift + Key_Backtab, also try Shift + Qt::Key_Tab + if (e->key() == Qt::Key_Backtab) { + QKeyEvent pe = QKeyEvent(e->type(), Qt::Key_Tab, e->modifiers(), e->text()); + result = find(&pe); + } + } + + // Should we eat this key press? + if (d->currentState == QKeySequence::PartialMatch + || (d->currentState == QKeySequence::ExactMatch && d->identicals.count())) + e->accept(); + // Does the new state require us to clean up? + if (result == QKeySequence::NoMatch) + clearSequence(d->currentSequences); + d->currentState = result; + +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() << "QShortcutMap::nextState(" << e << ") = " << result; +#endif + return result; +} + + +/*! \internal + Determines if an enabled shortcut has a matcing key sequence. +*/ +bool QShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const +{ + Q_D(const QShortcutMap); + QShortcutEntry entry(seq); // needed for searching + QList<QShortcutEntry>::ConstIterator itEnd = d->sequences.constEnd(); + QList<QShortcutEntry>::ConstIterator it = qLowerBound(d->sequences.constBegin(), itEnd, entry); + + for (;it != itEnd; ++it) { + if (matches(entry.keyseq, (*it).keyseq) == QKeySequence::ExactMatch && correctContext(*it) && (*it).enabled) { + return true; + } + } + + //end of the loop: we didn't find anything + return false; +} + +/*! \internal + Returns the next state of the statemachine, based + on the new key event \a e. + Matches are appended to the vector of identicals, + which can be access through matches(). + \sa matches +*/ +QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e) +{ + Q_D(QShortcutMap); + if (!d->sequences.count()) + return QKeySequence::NoMatch; + + createNewSequences(e, d->newEntries); +#if defined(DEBUG_QSHORTCUTMAP) + qDebug() << "Possible shortcut key sequences:" << d->newEntries; +#endif + + // Should never happen + if (d->newEntries == d->currentSequences) { + Q_ASSERT_X(e->key() != Qt::Key_unknown || e->text().length(), + "QShortcutMap::find", "New sequence to find identical to previous"); + return QKeySequence::NoMatch; + } + + // Looking for new identicals, scrap old + d->identicals.resize(0); + + bool partialFound = false; + bool identicalDisabledFound = false; + QVector<QKeySequence> okEntries; + int result = QKeySequence::NoMatch; + for (int i = d->newEntries.count()-1; i >= 0 ; --i) { + QShortcutEntry entry(d->newEntries.at(i)); // needed for searching + QList<QShortcutEntry>::ConstIterator itEnd = d->sequences.constEnd(); + QList<QShortcutEntry>::ConstIterator it = + qLowerBound(d->sequences.constBegin(), itEnd, entry); + + int oneKSResult = QKeySequence::NoMatch; + int tempRes = QKeySequence::NoMatch; + do { + if (it == itEnd) + break; + tempRes = matches(entry.keyseq, (*it).keyseq); + oneKSResult = qMax(oneKSResult, tempRes); + if (tempRes != QKeySequence::NoMatch && correctContext(*it)) { + if (tempRes == QKeySequence::ExactMatch) { + if ((*it).enabled) + d->identicals.append(&*it); + else + identicalDisabledFound = true; + } else if (tempRes == QKeySequence::PartialMatch) { + // We don't need partials, if we have identicals + if (d->identicals.size()) + break; + // We only care about enabled partials, so we don't consume + // key events when all partials are disabled! + partialFound |= (*it).enabled; + } + } + ++it; + // If we got a valid match on this run, there might still be more keys to check against, + // so we'll loop once more. If we get NoMatch, there's guaranteed no more possible + // matches in the shortcutmap. + } while (tempRes != QKeySequence::NoMatch); + + // If the type of match improves (ergo, NoMatch->Partial, or Partial->Exact), clear the + // previous list. If this match is equal or better than the last match, append to the list + if (oneKSResult > result) { + okEntries.clear(); +#if defined(DEBUG_QSHORTCUTMAP) + qDebug() << "Found better match (" << d->newEntries << "), clearing key sequence list"; +#endif + } + if (oneKSResult && oneKSResult >= result) { + okEntries << d->newEntries.at(i); +#if defined(DEBUG_QSHORTCUTMAP) + qDebug() << "Added ok key sequence" << d->newEntries; +#endif + } + } + + if (d->identicals.size()) { + result = QKeySequence::ExactMatch; + } else if (partialFound) { + result = QKeySequence::PartialMatch; + } else if (identicalDisabledFound) { + result = QKeySequence::ExactMatch; + } else { + clearSequence(d->currentSequences); + result = QKeySequence::NoMatch; + } + if (result != QKeySequence::NoMatch) + d->currentSequences = okEntries; +#if defined(DEBUG_QSHORTCUTMAP) + qDebug() << "Returning shortcut match == " << result; +#endif + return QKeySequence::SequenceMatch(result); +} + +/*! \internal + Clears \a seq to an empty QKeySequence. + Same as doing (the slower) + \snippet doc/src/snippets/code/src_gui_kernel_qshortcutmap.cpp 0 +*/ +void QShortcutMap::clearSequence(QVector<QKeySequence> &ksl) +{ + ksl.clear(); + d_func()->newEntries.clear(); +} + +/*! \internal + Alters \a seq to the new sequence state, based on the + current sequence state, and the new key event \a e. +*/ +void QShortcutMap::createNewSequences(QKeyEvent *e, QVector<QKeySequence> &ksl) +{ + Q_D(QShortcutMap); + QList<int> possibleKeys = QKeyMapper::possibleKeys(e); + int pkTotal = possibleKeys.count(); + if (!pkTotal) + return; + + int ssActual = d->currentSequences.count(); + int ssTotal = qMax(1, ssActual); + // Resize to possible permutations of the current sequence(s). + ksl.resize(pkTotal * ssTotal); + + int index = ssActual ? d->currentSequences.at(0).count() : 0; + for (int pkNum = 0; pkNum < pkTotal; ++pkNum) { + for (int ssNum = 0; ssNum < ssTotal; ++ssNum) { + int i = (pkNum * ssTotal) + ssNum; + QKeySequence &curKsl = ksl[i]; + if (ssActual) { + const QKeySequence &curSeq = d->currentSequences.at(ssNum); + curKsl.setKey(curSeq[0], 0); + curKsl.setKey(curSeq[1], 1); + curKsl.setKey(curSeq[2], 2); + curKsl.setKey(curSeq[3], 3); + } else { + curKsl.setKey(0, 0); + curKsl.setKey(0, 1); + curKsl.setKey(0, 2); + curKsl.setKey(0, 3); + } + // Filtering keycode here with 0xdfffffff to ignore the Keypad modifier + curKsl.setKey(possibleKeys.at(pkNum) & 0xdfffffff, index); + } + } +} + +/*! \internal + Basically the same function as QKeySequence::matches(const QKeySequence &seq) const + only that is specially handles Key_hyphen as Key_Minus, as people mix these up all the time and + they conceptually the same. +*/ +QKeySequence::SequenceMatch QShortcutMap::matches(const QKeySequence &seq1, + const QKeySequence &seq2) const +{ + uint userN = seq1.count(), + seqN = seq2.count(); + + if (userN > seqN) + return QKeySequence::NoMatch; + + // If equal in length, we have a potential ExactMatch sequence, + // else we already know it can only be partial. + QKeySequence::SequenceMatch match = (userN == seqN + ? QKeySequence::ExactMatch + : QKeySequence::PartialMatch); + + for (uint i = 0; i < userN; ++i) { + int userKey = seq1[i], + sequenceKey = seq2[i]; + if ((userKey & Qt::Key_unknown) == Qt::Key_hyphen) + userKey = (userKey & Qt::KeyboardModifierMask) | Qt::Key_Minus; + if ((sequenceKey & Qt::Key_unknown) == Qt::Key_hyphen) + sequenceKey = (sequenceKey & Qt::KeyboardModifierMask) | Qt::Key_Minus; + if (userKey != sequenceKey) + return QKeySequence::NoMatch; + } + return match; +} + +/*! \internal + Returns true if the widget \a w is a logical sub window of the current + top-level widget. +*/ +bool QShortcutMap::correctContext(const QShortcutEntry &item) const { + Q_ASSERT_X(item.owner, "QShortcutMap", "Shortcut has no owner. Illegal map state!"); + + QWidget *active_window = QApplication::activeWindow(); + + // popups do not become the active window, + // so we fake it here to get the correct context + // for the shortcut system. + if (QApplication::activePopupWidget()) + active_window = QApplication::activePopupWidget(); + + if (!active_window) + return false; +#ifndef QT_NO_ACTION + if (QAction *a = qobject_cast<QAction *>(item.owner)) + return correctContext(item.context, a, active_window); +#endif +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsWidget *gw = qobject_cast<QGraphicsWidget *>(item.owner)) + return correctGraphicsWidgetContext(item.context, gw, active_window); +#endif + QWidget *w = qobject_cast<QWidget *>(item.owner); + if (!w) { + QShortcut *s = qobject_cast<QShortcut *>(item.owner); + w = s->parentWidget(); + } + return correctWidgetContext(item.context, w, active_window); +} + +bool QShortcutMap::correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window) const +{ + bool visible = w->isVisible(); +#ifdef Q_WS_MAC + if (!qApp->testAttribute(Qt::AA_DontUseNativeMenuBar) && qobject_cast<QMenuBar *>(w)) + visible = true; +#endif + + if (!visible || !w->isEnabled()) + return false; + + if (context == Qt::ApplicationShortcut) + return QApplicationPrivate::tryModalHelper(w, 0); // true, unless w is shadowed by a modal dialog + + if (context == Qt::WidgetShortcut) + return w == QApplication::focusWidget(); + + if (context == Qt::WidgetWithChildrenShortcut) { + const QWidget *tw = QApplication::focusWidget(); + while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup)) + tw = tw->parentWidget(); + return tw == w; + } + + // Below is Qt::WindowShortcut context + QWidget *tlw = w->window(); +#ifndef QT_NO_GRAPHICSVIEW + if (QWExtra *topData = tlw->d_func()->extra) { + if (topData->proxyWidget) { + bool res = correctGraphicsWidgetContext(context, (QGraphicsWidget *)topData->proxyWidget, active_window); + return res; + } + } +#endif + + /* if a floating tool window is active, keep shortcuts on the + * parent working */ + if (active_window != tlw && active_window && active_window->windowType() == Qt::Tool && active_window->parentWidget()) { + active_window = active_window->parentWidget()->window(); + } + + if (active_window != tlw) + return false; + + /* if we live in a MDI subwindow, ignore the event if we are + not the active document window */ + const QWidget* sw = w; + while (sw && !(sw->windowType() == Qt::SubWindow) && !sw->isWindow()) + sw = sw->parentWidget(); + if (sw && (sw->windowType() == Qt::SubWindow)) { + QWidget *focus_widget = QApplication::focusWidget(); + while (focus_widget && focus_widget != sw) + focus_widget = focus_widget->parentWidget(); + return sw == focus_widget; + } + +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() << "..true [Pass-through]"; +#endif + return true; +} + +#ifndef QT_NO_GRAPHICSVIEW +bool QShortcutMap::correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window) const +{ + bool visible = w->isVisible(); +#ifdef Q_WS_MAC + if (!qApp->testAttribute(Qt::AA_DontUseNativeMenuBar) && qobject_cast<QMenuBar *>(w)) + visible = true; +#endif + + if (!visible || !w->isEnabled() || !w->scene()) + return false; + + if (context == Qt::ApplicationShortcut) { + // Applicationwide shortcuts are always reachable unless their owner + // is shadowed by modality. In QGV there's no modality concept, but we + // must still check if all views are shadowed. + QList<QGraphicsView *> views = w->scene()->views(); + for (int i = 0; i < views.size(); ++i) { + if (QApplicationPrivate::tryModalHelper(views.at(i), 0)) + return true; + } + return false; + } + + if (context == Qt::WidgetShortcut) + return static_cast<QGraphicsItem *>(w) == w->scene()->focusItem(); + + if (context == Qt::WidgetWithChildrenShortcut) { + const QGraphicsItem *ti = w->scene()->focusItem(); + if (ti && ti->isWidget()) { + const QGraphicsWidget *tw = static_cast<const QGraphicsWidget *>(ti); + while (tw && tw != w && (tw->windowType() == Qt::Widget || tw->windowType() == Qt::Popup)) + tw = tw->parentWidget(); + return tw == w; + } + return false; + } + + // Below is Qt::WindowShortcut context + + // Find the active view (if any). + QList<QGraphicsView *> views = w->scene()->views(); + QGraphicsView *activeView = 0; + for (int i = 0; i < views.size(); ++i) { + QGraphicsView *view = views.at(i); + if (view->window() == active_window) { + activeView = view; + break; + } + } + if (!activeView) + return false; + + // The shortcut is reachable if owned by a windowless widget, or if the + // widget's window is the same as the focus item's window. + QGraphicsWidget *a = w->scene()->activeWindow(); + return !w->window() || a == w->window(); +} +#endif + +#ifndef QT_NO_ACTION +bool QShortcutMap::correctContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window) const +{ + const QList<QWidget *> &widgets = a->d_func()->widgets; +#if defined(DEBUG_QSHORTCUTMAP) + if (widgets.isEmpty()) + qDebug() << a << "not connected to any widgets; won't trigger"; +#endif + for (int i = 0; i < widgets.size(); ++i) { + QWidget *w = widgets.at(i); +#ifndef QT_NO_MENU + if (QMenu *menu = qobject_cast<QMenu *>(w)) { + QAction *a = menu->menuAction(); + if (correctContext(context, a, active_window)) + return true; + } else +#endif + if (correctWidgetContext(context, w, active_window)) + return true; + } + +#ifndef QT_NO_GRAPHICSVIEW + const QList<QGraphicsWidget *> &graphicsWidgets = a->d_func()->graphicsWidgets; +#if defined(DEBUG_QSHORTCUTMAP) + if (graphicsWidgets.isEmpty()) + qDebug() << a << "not connected to any widgets; won't trigger"; +#endif + for (int i = 0; i < graphicsWidgets.size(); ++i) { + QGraphicsWidget *w = graphicsWidgets.at(i); + if (correctGraphicsWidgetContext(context, w, active_window)) + return true; + } +#endif + return false; +} +#endif // QT_NO_ACTION + +/*! \internal + Converts keyboard button states into modifier states +*/ +int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers) +{ + int result = 0; + if (modifiers & Qt::ShiftModifier) + result |= Qt::SHIFT; + if (modifiers & Qt::ControlModifier) + result |= Qt::CTRL; + if (modifiers & Qt::MetaModifier) + result |= Qt::META; + if (modifiers & Qt::AltModifier) + result |= Qt::ALT; + return result; +} + +/*! \internal + Returns the vector of QShortcutEntry's matching the last Identical state. +*/ +QVector<const QShortcutEntry*> QShortcutMap::matches() const +{ + Q_D(const QShortcutMap); + return d->identicals; +} + +/*! \internal + Dispatches QShortcutEvents to widgets who grabbed the matched key sequence. +*/ +void QShortcutMap::dispatchEvent(QKeyEvent *e) +{ + Q_D(QShortcutMap); + if (!d->identicals.size()) + return; + + const QKeySequence &curKey = d->identicals.at(0)->keyseq; + if (d->prevSequence != curKey) { + d->ambigCount = 0; + d->prevSequence = curKey; + } + // Find next + const QShortcutEntry *current = 0, *next = 0; + int i = 0, enabledShortcuts = 0; + while(i < d->identicals.size()) { + current = d->identicals.at(i); + if (current->enabled || !next){ + ++enabledShortcuts; + if (enabledShortcuts > d->ambigCount + 1) + break; + next = current; + } + ++i; + } + d->ambigCount = (d->identicals.size() == i ? 0 : d->ambigCount + 1); + // Don't trigger shortcut if we're autorepeating and the shortcut is + // grabbed with not accepting autorepeats. + if (!next || (e->isAutoRepeat() && !next->autorepeat)) + return; + // Dispatch next enabled +#if defined(DEBUG_QSHORTCUTMAP) + qDebug().nospace() + << "QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\"" + << (QString)next->keyseq << "\", " << next->id << ", " + << (bool)(enabledShortcuts>1) << ") to object(" << next->owner << ')'; +#endif + QShortcutEvent se(next->keyseq, next->id, enabledShortcuts>1); + QApplication::sendEvent(const_cast<QObject *>(next->owner), &se); +} + +/* \internal + QShortcutMap dump function, only available when DEBUG_QSHORTCUTMAP is + defined. +*/ +#if defined(Dump_QShortcutMap) +void QShortcutMap::dumpMap() const +{ + Q_D(const QShortcutMap); + for (int i = 0; i < d->sequences.size(); ++i) + qDebug().nospace() << &(d->sequences.at(i)); +} +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_SHORTCUT diff --git a/src/gui/kernel/qshortcutmap_p.h b/src/gui/kernel/qshortcutmap_p.h new file mode 100644 index 0000000000..bc530b00b4 --- /dev/null +++ b/src/gui/kernel/qshortcutmap_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSHORTCUTMAP_P_H +#define QSHORTCUTMAP_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 "QtGui/qkeysequence.h" +#include "QtCore/qvector.h" +#include "QtCore/qscopedpointer.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SHORTCUT + +// To enable dump output uncomment below +//#define Dump_QShortcutMap + +class QKeyEvent; +struct QShortcutEntry; +class QShortcutMapPrivate; +class QGraphicsWidget; +class QWidget; +class QAction; +class QObject; + +class QShortcutMap +{ + Q_DECLARE_PRIVATE(QShortcutMap) +public: + QShortcutMap(); + ~QShortcutMap(); + + int addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context); + int removeShortcut(int id, QObject *owner, const QKeySequence &key = QKeySequence()); + int setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key = QKeySequence()); + int setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &key = QKeySequence()); + + void resetState(); + QKeySequence::SequenceMatch nextState(QKeyEvent *e); + QKeySequence::SequenceMatch state(); + void dispatchEvent(QKeyEvent *e); + bool tryShortcutEvent(QObject *o, QKeyEvent *e); + +#ifdef Dump_QShortcutMap + void dumpMap() const; +#endif + + bool hasShortcutForKeySequence(const QKeySequence &seq) const; + + +private: + bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window) const; +#ifndef QT_NO_GRAPHICSVIEW + bool correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window) const; +#endif +#ifndef QT_NO_ACTION + bool correctContext(Qt::ShortcutContext context,QAction *a, QWidget *active_window) const; +#endif + QScopedPointer<QShortcutMapPrivate> d_ptr; + + QKeySequence::SequenceMatch find(QKeyEvent *e); + QKeySequence::SequenceMatch matches(const QKeySequence &seq1, const QKeySequence &seq2) const; + QVector<const QShortcutEntry *> matches() const; + void createNewSequences(QKeyEvent *e, QVector<QKeySequence> &ksl); + void clearSequence(QVector<QKeySequence> &ksl); + bool correctContext(const QShortcutEntry &item) const; + int translateModifiers(Qt::KeyboardModifiers modifiers); +}; + +#endif // QT_NO_SHORTCUT + +QT_END_NAMESPACE + +#endif // QSHORTCUTMAP_P_H diff --git a/src/gui/kernel/qsizepolicy.h b/src/gui/kernel/qsizepolicy.h new file mode 100644 index 0000000000..c0a8cc1f18 --- /dev/null +++ b/src/gui/kernel/qsizepolicy.h @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSIZEPOLICY_H +#define QSIZEPOLICY_H + +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVariant; + +class Q_GUI_EXPORT QSizePolicy +{ + Q_GADGET + Q_ENUMS(Policy) + +private: + enum SizePolicyMasks { + HSize = 4, + HMask = 0x0f, + VMask = HMask << HSize, + CTShift = 9, + CTSize = 5, + CTMask = ((0x1 << CTSize) - 1) << CTShift, + WFHShift = CTShift + CTSize, + UnusedShift = WFHShift + 1, + UnusedSize = 1 + }; + +public: + enum PolicyFlag { + GrowFlag = 1, + ExpandFlag = 2, + ShrinkFlag = 4, + IgnoreFlag = 8 + }; + + enum Policy { + Fixed = 0, + Minimum = GrowFlag, + Maximum = ShrinkFlag, + Preferred = GrowFlag | ShrinkFlag, + MinimumExpanding = GrowFlag | ExpandFlag, + Expanding = GrowFlag | ShrinkFlag | ExpandFlag, + Ignored = ShrinkFlag | GrowFlag | IgnoreFlag + }; + + enum ControlType { + DefaultType = 0x00000001, + ButtonBox = 0x00000002, + CheckBox = 0x00000004, + ComboBox = 0x00000008, + Frame = 0x00000010, + GroupBox = 0x00000020, + Label = 0x00000040, + Line = 0x00000080, + LineEdit = 0x00000100, + PushButton = 0x00000200, + RadioButton = 0x00000400, + Slider = 0x00000800, + SpinBox = 0x00001000, + TabWidget = 0x00002000, + ToolButton = 0x00004000 + }; + Q_DECLARE_FLAGS(ControlTypes, ControlType) + + QSizePolicy() : data(0) { } + + // ### Qt 5: merge these two constructors (with type == DefaultType) + QSizePolicy(Policy horizontal, Policy vertical) + : data(horizontal | (vertical << HSize)) { } + QSizePolicy(Policy horizontal, Policy vertical, ControlType type) + : data(horizontal | (vertical << HSize)) { setControlType(type); } + + Policy horizontalPolicy() const { return static_cast<Policy>(data & HMask); } + Policy verticalPolicy() const { return static_cast<Policy>((data & VMask) >> HSize); } + ControlType controlType() const; + + void setHorizontalPolicy(Policy d) { data = (data & ~HMask) | d; } + void setVerticalPolicy(Policy d) { data = (data & ~(HMask << HSize)) | (d << HSize); } + void setControlType(ControlType type); + + Qt::Orientations expandingDirections() const { + Qt::Orientations result; + if (verticalPolicy() & ExpandFlag) + result |= Qt::Vertical; + if (horizontalPolicy() & ExpandFlag) + result |= Qt::Horizontal; + return result; + } + + void setHeightForWidth(bool b) { data = b ? (data | (1 << 2*HSize)) : (data & ~(1 << 2*HSize)); } + bool hasHeightForWidth() const { return data & (1 << 2*HSize); } + void setWidthForHeight(bool b) { data = b ? (data | (1 << (WFHShift))) : (data & ~(1 << (WFHShift))); } + bool hasWidthForHeight() const { return data & (1 << (WFHShift)); } + + bool operator==(const QSizePolicy& s) const { return data == s.data; } + bool operator!=(const QSizePolicy& s) const { return data != s.data; } + operator QVariant() const; // implemented in qabstractlayout.cpp + + int horizontalStretch() const { return data >> 24; } + int verticalStretch() const { return (data >> 16) & 0xff; } + void setHorizontalStretch(uchar stretchFactor) { data = (data&0x00ffffff) | (uint(stretchFactor)<<24); } + void setVerticalStretch(uchar stretchFactor) { data = (data&0xff00ffff) | (uint(stretchFactor)<<16); } + + void transpose(); + +#ifdef QT3_SUPPORT + typedef Policy SizeType; +#ifndef qdoc + typedef Qt::Orientations ExpandData; + enum { + NoDirection = 0, + Horizontally = 1, + Vertically = 2, + BothDirections = Horizontally | Vertically + }; +#else + enum ExpandData { + NoDirection = 0x0, + Horizontally = 0x1, + Vertically = 0x2, + BothDirections = 0x3 + }; +#endif // qdoc + + inline QT3_SUPPORT bool mayShrinkHorizontally() const + { return horizontalPolicy() & ShrinkFlag; } + inline QT3_SUPPORT bool mayShrinkVertically() const { return verticalPolicy() & ShrinkFlag; } + inline QT3_SUPPORT bool mayGrowHorizontally() const { return horizontalPolicy() & GrowFlag; } + inline QT3_SUPPORT bool mayGrowVertically() const { return verticalPolicy() & GrowFlag; } + inline QT3_SUPPORT Qt::Orientations expanding() const { return expandingDirections(); } + + QT3_SUPPORT_CONSTRUCTOR QSizePolicy(Policy hor, Policy ver, bool hfw) + : data(hor | (ver<<HSize) | (hfw ? (1U<<2*HSize) : 0)) { } + + QT3_SUPPORT_CONSTRUCTOR QSizePolicy(Policy hor, Policy ver, uchar hors, uchar vers, bool hfw = false) + : data(hor | (ver<<HSize) | (hfw ? (1U<<2*HSize) : 0)) { + setHorizontalStretch(hors); + setVerticalStretch(vers); + } + + inline QT3_SUPPORT Policy horData() const { return static_cast<Policy>(data & HMask); } + inline QT3_SUPPORT Policy verData() const { return static_cast<Policy>((data & VMask) >> HSize); } + inline QT3_SUPPORT void setHorData(Policy d) { setHorizontalPolicy(d); } + inline QT3_SUPPORT void setVerData(Policy d) { setVerticalPolicy(d); } + + inline QT3_SUPPORT uint horStretch() const { return horizontalStretch(); } + inline QT3_SUPPORT uint verStretch() const { return verticalStretch(); } + inline QT3_SUPPORT void setHorStretch(uchar sf) { setHorizontalStretch(sf); } + inline QT3_SUPPORT void setVerStretch(uchar sf) { setVerticalStretch(sf); } +#endif + +private: +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QSizePolicy &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QSizePolicy &); +#endif + QSizePolicy(int i) : data(i) { } + + quint32 data; +/* Qt5: Use bit flags instead, keep it here for improved readability for now. + We can maybe change it for Qt4, but we'd have to be careful, since the behaviour + is implementation defined. It usually varies between little- and big-endian compilers, but + it might also not vary. + quint32 horzPolicy : 4; + quint32 vertPolicy : 4; + quint32 hfw : 1; + quint32 ctype : 5; + quint32 wfh : 1; + quint32 padding : 1; // we cannot use the highest bit + quint32 verStretch : 8; + quint32 horStretch : 8; +*/ + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSizePolicy::ControlTypes) + +#ifndef QT_NO_DATASTREAM +// implemented in qlayout.cpp +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QSizePolicy &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QSizePolicy &); +#endif + +inline void QSizePolicy::transpose() { + Policy hData = horizontalPolicy(); + Policy vData = verticalPolicy(); + uchar hStretch = uchar(horizontalStretch()); + uchar vStretch = uchar(verticalStretch()); + setHorizontalPolicy(vData); + setVerticalPolicy(hData); + setHorizontalStretch(vStretch); + setVerticalStretch(hStretch); +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSIZEPOLICY_H diff --git a/src/gui/kernel/qsizepolicy.qdoc b/src/gui/kernel/qsizepolicy.qdoc new file mode 100644 index 0000000000..80e9f20f74 --- /dev/null +++ b/src/gui/kernel/qsizepolicy.qdoc @@ -0,0 +1,529 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QSizePolicy + \brief The QSizePolicy class is a layout attribute describing horizontal + and vertical resizing policy. + + \ingroup geomanagement + + The size policy of a widget is an expression of its willingness to + be resized in various ways, and affects how the widget is treated + by the \l{Layout Management}{layout engine}. Each widget returns a + QSizePolicy that describes the horizontal and vertical resizing + policy it prefers when being laid out. You can change this for + a specific widget by changing its QWidget::sizePolicy property. + + QSizePolicy contains two independent QSizePolicy::Policy values + and two stretch factors; one describes the widgets's horizontal + size policy, and the other describes its vertical size policy. It + also contains a flag to indicate whether the height and width of + its preferred size are related. + + The horizontal and vertical policies can be set in the + constructor, and altered using the setHorizontalPolicy() and + setVerticalPolicy() functions. The stretch factors can be set + using the setHorizontalStretch() and setVerticalStretch() + functions. The flag indicating whether the widget's + \l{QWidget::sizeHint()}{sizeHint()} is width-dependent (such as a + menu bar or a word-wrapping label) can be set using the + setHeightForWidth() function. + + The current size policies and stretch factors be retrieved using + the horizontalPolicy(), verticalPolicy(), horizontalStretch() and + verticalStretch() functions. Alternatively, use the transpose() + function to swap the horizontal and vertical policies and + stretches. The hasHeightForWidth() function returns the current + status of the flag indicating the size hint dependencies. + + Use the expandingDirections() function to determine whether the + associated widget can make use of more space than its + \l{QWidget::sizeHint()}{sizeHint()} function indicates, as well as + find out in which directions it can expand. + + Finally, the QSizePolicy class provides operators comparing this + size policy to a given policy, as well as a QVariant operator + storing this QSizePolicy as a QVariant object. + + \sa QSize, QWidget::sizeHint(), QWidget::sizePolicy, + QLayoutItem::sizeHint() +*/ + +/*! + \enum QSizePolicy::PolicyFlag + + These flags are combined together to form the various \l{Policy} + values: + + \value GrowFlag The widget can grow beyond its size hint if necessary. + \value ExpandFlag The widget should get as much space as possible. + \value ShrinkFlag The widget can shrink below its size hint if necessary. + \value IgnoreFlag The widget's size hint is ignored. The widget will get + as much space as possible. + + \sa Policy +*/ + +/*! + \enum QSizePolicy::Policy + + This enum describes the various per-dimension sizing types used + when constructing a QSizePolicy. + + \value Fixed The QWidget::sizeHint() is the only acceptable + alternative, so the widget can never grow or shrink (e.g. the + vertical direction of a push button). + + \value Minimum The sizeHint() is minimal, and sufficient. The + widget can be expanded, but there is no advantage to it being + larger (e.g. the horizontal direction of a push button). + It cannot be smaller than the size provided by sizeHint(). + + \value Maximum The sizeHint() is a maximum. The widget can be + shrunk any amount without detriment if other widgets need the + space (e.g. a separator line). + It cannot be larger than the size provided by sizeHint(). + + \value Preferred The sizeHint() is best, but the widget can be + shrunk and still be useful. The widget can be expanded, but there + is no advantage to it being larger than sizeHint() (the default + QWidget policy). + + \value Expanding The sizeHint() is a sensible size, but the + widget can be shrunk and still be useful. The widget can make use + of extra space, so it should get as much space as possible (e.g. + the horizontal direction of a horizontal slider). + + \value MinimumExpanding The sizeHint() is minimal, and sufficient. + The widget can make use of extra space, so it should get as much + space as possible (e.g. the horizontal direction of a horizontal + slider). + + \value Ignored The sizeHint() is ignored. The widget will get as + much space as possible. + + \sa PolicyFlag, setHorizontalPolicy(), setVerticalPolicy() +*/ + +/*! + \fn QSizePolicy::QSizePolicy() + + Constructs a QSizePolicy object with \l Fixed as its horizontal + and vertical policies. + + The policies can be altered using the setHorizontalPolicy() and + setVerticalPolicy() functions. Use the setHeightForWidth() + function if the preferred height of the widget is dependent on the + width of the widget (for example, a QLabel with line wrapping). + + \sa setHorizontalStretch(), setVerticalStretch() +*/ + +/*! + \fn QSizePolicy::QSizePolicy(Policy horizontal, Policy vertical) + + Constructs a QSizePolicy object with the given \a horizontal and + \a vertical policies, and DefaultType as the control type. + + Use setHeightForWidth() if the preferred height of the widget is + dependent on the width of the widget (for example, a QLabel with + line wrapping). + + \sa setHorizontalStretch(), setVerticalStretch() +*/ + +/*! + \fn QSizePolicy::QSizePolicy(Policy horizontal, Policy vertical, ControlType type) + \since 4.3 + + Constructs a QSizePolicy object with the given \a horizontal and + \a vertical policies, and the specified control \a type. + + Use setHeightForWidth() if the preferred height of the widget is + dependent on the width of the widget (for example, a QLabel with + line wrapping). + + \sa setHorizontalStretch(), setVerticalStretch(), controlType() +*/ + +/*! + \fn QSizePolicy::Policy QSizePolicy::horizontalPolicy() const + + Returns the horizontal component of the size policy. + + \sa setHorizontalPolicy(), verticalPolicy(), horizontalStretch() +*/ + +/*! + \fn QSizePolicy::Policy QSizePolicy::verticalPolicy() const + + Returns the vertical component of the size policy. + + \sa setVerticalPolicy(), horizontalPolicy(), verticalStretch() +*/ + +/*! + \fn void QSizePolicy::setHorizontalPolicy(Policy policy) + + Sets the horizontal component to the given \a policy. + + \sa horizontalPolicy(), setVerticalPolicy(), setHorizontalStretch() +*/ + +/*! + \fn void QSizePolicy::setVerticalPolicy(Policy policy) + + Sets the vertical component to the given \a policy. + + \sa verticalPolicy(), setHorizontalPolicy(), setVerticalStretch() +*/ + +/*! + \fn Qt::Orientations QSizePolicy::expandingDirections() const + + Returns whether a widget can make use of more space than the + QWidget::sizeHint() function indicates. + + A value of Qt::Horizontal or Qt::Vertical means that the widget + can grow horizontally or vertically (i.e., the horizontal or + vertical policy is \l Expanding or \l MinimumExpanding), whereas + Qt::Horizontal | Qt::Vertical means that it can grow in both + dimensions. + + \sa horizontalPolicy(), verticalPolicy() +*/ + +/*! + \fn ControlType QSizePolicy::controlType() const + \since 4.3 + + Returns the control type associated with the widget for which + this size policy applies. +*/ + +/*! + \fn void QSizePolicy::setControlType(ControlType type) + \since 4.3 + + Sets the control type associated with the widget for which this + size policy applies to \a type. + + The control type specifies the type of the widget for which this + size policy applies. It is used by some styles, notably + QMacStyle, to insert proper spacing between widgets. For example, + the Mac OS X Aqua guidelines specify that push buttons should be + separated by 12 pixels, whereas vertically stacked radio buttons + only require 6 pixels. + + \sa QStyle::layoutSpacing() +*/ + +/*! + \fn void QSizePolicy::setHeightForWidth(bool dependent) + + Sets the flag determining whether the widget's preferred height + depends on its width, to \a dependent. + + \sa hasHeightForWidth(), setWidthForHeight() +*/ + +/*! + \fn bool QSizePolicy::hasHeightForWidth() const + + Returns true if the widget's preferred height depends on its + width; otherwise returns false. + + \sa setHeightForWidth() +*/ + +/*! + \fn void QSizePolicy::setWidthForHeight(bool dependent) + + Sets the flag determining whether the widget's width + depends on its height, to \a dependent. + + This is only supported for QGraphicsLayout's subclasses. + It is not possible to have a layout with both height-for-width + and width-for-height constraints at the same time. + + \sa hasWidthForHeight(), setHeightForWidth() +*/ + +/*! + \fn bool QSizePolicy::hasWidthForHeight() const + + Returns true if the widget's width depends on its + height; otherwise returns false. + + \sa setWidthForHeight() +*/ + +/*! + \fn bool QSizePolicy::operator==(const QSizePolicy &other) const + + Returns true if this policy is equal to \a other; otherwise + returns false. + + \sa operator!=() +*/ + +/*! + \fn bool QSizePolicy::operator!=(const QSizePolicy &other) const + + Returns true if this policy is different from \a other; otherwise + returns false. + + \sa operator==() +*/ + +/*! + \fn int QSizePolicy::horizontalStretch() const + + Returns the horizontal stretch factor of the size policy. + + \sa setHorizontalStretch(), verticalStretch(), horizontalPolicy() +*/ + +/*! + \fn int QSizePolicy::verticalStretch() const + + Returns the vertical stretch factor of the size policy. + + \sa setVerticalStretch(), horizontalStretch(), verticalPolicy() +*/ + +/*! + \fn void QSizePolicy::setHorizontalStretch(uchar stretchFactor) + + Sets the horizontal stretch factor of the size policy to the given \a + stretchFactor. + + \sa horizontalStretch(), setVerticalStretch(), setHorizontalPolicy() +*/ + +/*! + \fn void QSizePolicy::setVerticalStretch(uchar stretchFactor) + + Sets the vertical stretch factor of the size policy to the given + \a stretchFactor. + + \sa verticalStretch(), setHorizontalStretch(), setVerticalPolicy() +*/ + +/*! + \fn void QSizePolicy::transpose() + + Swaps the horizontal and vertical policies and stretches. +*/ + +/*! + \enum QSizePolicy::ControlType + \since 4.3 + + This enum specifies the different types of widgets in terms of + layout interaction: + + \value DefaultType The default type, when none is specified. + \value ButtonBox A QDialogButtonBox instance. + \value CheckBox A QCheckBox instance. + \value ComboBox A QComboBox instance. + \value Frame A QFrame instance. + \value GroupBox A QGroupBox instance. + \value Label A QLabel instance. + \value Line A QFrame instance with QFrame::HLine or QFrame::VLine. + \value LineEdit A QLineEdit instance. + \value PushButton A QPushButton instance. + \value RadioButton A QRadioButton instance. + \value Slider A QAbstractSlider instance. + \value SpinBox A QAbstractSpinBox instance. + \value TabWidget A QTabWidget instance. + \value ToolButton A QToolButton instance. + + \sa setControlType(), controlType() +*/ + +#ifdef QT3_SUPPORT +/*! + \typedef QSizePolicy::SizeType + \compat + + Use the QSizePolicy::Policy enum instead. +*/ + +/*! + \enum QSizePolicy::ExpandData + \compat + + Use the Qt::Orientations enum instead. + + \value NoDirection Use 0 instead. + \value Horizontally Use Qt::Horizontal instead. + \value Vertically Use Qt::Vertical instead. + \value BothDirections Use Qt::Horizontal | Qt::Vertical instead. +*/ + +/*! + \fn bool QSizePolicy::mayShrinkHorizontally() const + + Use the horizontalPolicy() function combined with the + QSizePolicy::PolicyFlag enum instead. + + \oldcode + bool policy = mayShrinkHorizontally(); + \newcode + bool policy = horizontalPolicy() & QSizePolicy::ShrinkFlag; + \endcode +*/ + +/*! + \fn bool QSizePolicy::mayShrinkVertically() const + + Use the verticalPolicy() function combined with the + QSizePolicy::PolicyFlag enum instead. + + \oldcode + bool policy = mayShrinkVertically(); + \newcode + bool policy = verticalPolicy() & QSizePolicy::ShrinkFlag; + \endcode +*/ + +/*! + \fn bool QSizePolicy::mayGrowHorizontally() const + + Use the horizontalPolicy() function combined with the + QSizePolicy::PolicyFlag enum instead. + + \oldcode + bool policy = mayGrowHorizontally(); + \newcode + bool policy = horizontalPolicy() & QSizePolicy::GrowFlag; + \endcode +*/ + +/*! + \fn bool QSizePolicy::mayGrowVertically() const + + Use the verticalPolicy() function combined with the + QSizePolicy::PolicyFlag enum instead. + + \oldcode + bool policy = mayGrowVertically(); + \newcode + bool policy = verticalPolicy() & QSizePolicy::GrowFlag; + \endcode +*/ + +/*! + \fn Qt::QSizePolicy::Orientations QSizePolicy::expanding() const + + Use expandingDirections() instead. +*/ + +/*! + \fn QSizePolicy::QSizePolicy(Policy horizontal, Policy vertical, bool dependent) + + Use the QSizePolicy() constructor and the setHeightForWidth() + function instead. + + \oldcode + QSizePolicy *policy = new QSizePolicy(horizontal, vertical, dependent); + \newcode + QSizePolicy *policy = new QSizePolicy(horizontal, vertical); + policy->setHeightForWidth(dependent); + \endcode +*/ + +/*! + \fn QSizePolicy::QSizePolicy(Policy horizontal, Policy vertical, uchar horizontalStretch, + uchar verticalStretch, bool dependent) + + Use the QSizePolicy() constructor and call the + setHorizontalStretch(), setVerticalStretch(), and + setHeightForWidth() functions instead. + + \oldcode + QSizePolicy *policy = new QSizePolicy(horizontal, vertical, + horizontalStretch, verticalStretch, + dependent); + \newcode + QSizePolicy *policy = new QSizePolicy(horizontal, vertical); + policy->setHorizontalStretch(horizontalStretch); + policy->setVerticalStretch(verticalStretch); + policy->setHeightForWidth(dependent); + \endcode +*/ + +/*! + \fn QSizePolicy::Policy QSizePolicy::horData() const + + Use horizontalPolicy() instead. +*/ + +/*! + \fn QSizePolicy::Policy QSizePolicy::verData() const + + Use verticalPolicy() instead. +*/ + +/*! + \fn void QSizePolicy::setHorData(Policy policy) + + Use setHorizontalPolicy() instead. +*/ + +/*! + \fn void QSizePolicy::setVerData(Policy policy) + + Use setVerticalPolicy() instead. +*/ + +/*! + \fn uint QSizePolicy::horStretch() const + + Use horizontalStretch() instead. +*/ + +/*! + \fn uint QSizePolicy::verStretch() const + + Use verticalStretch() instead. +*/ + +/*! + \fn void QSizePolicy::setHorStretch(uchar stretch) + + Use setHorizontalStretch() instead. +*/ + +/*! + \fn void QSizePolicy::setVerStretch(uchar stretch) + + Use setVerticalStretch() instead. +*/ +#endif diff --git a/src/gui/kernel/qsoftkeymanager.cpp b/src/gui/kernel/qsoftkeymanager.cpp new file mode 100644 index 0000000000..204efe9ee9 --- /dev/null +++ b/src/gui/kernel/qsoftkeymanager.cpp @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qevent.h" +#include "qbitmap.h" +#include "private/qsoftkeymanager_p.h" +#include "private/qaction_p.h" +#include "private/qsoftkeymanager_common_p.h" + +#ifdef Q_WS_S60 +#include "private/qsoftkeymanager_s60_p.h" +#endif + +#ifdef SYMBIAN_VERSION_SYMBIAN3 +#include "private/qt_s60_p.h" +#endif + +#ifndef QT_NO_SOFTKEYMANAGER +QT_BEGIN_NAMESPACE + +QSoftKeyManager *QSoftKeyManagerPrivate::self = 0; + +QString QSoftKeyManager::standardSoftKeyText(StandardSoftKey standardKey) +{ + QString softKeyText; + switch (standardKey) { + case OkSoftKey: + softKeyText = QSoftKeyManager::tr("Ok"); + break; + case SelectSoftKey: + softKeyText = QSoftKeyManager::tr("Select"); + break; + case DoneSoftKey: + softKeyText = QSoftKeyManager::tr("Done"); + break; + case MenuSoftKey: + softKeyText = QSoftKeyManager::tr("Options"); + break; + case CancelSoftKey: + softKeyText = QSoftKeyManager::tr("Cancel"); + break; + default: + break; + }; + + return softKeyText; +} + +QSoftKeyManager *QSoftKeyManager::instance() +{ + if (!QSoftKeyManagerPrivate::self) + QSoftKeyManagerPrivate::self = new QSoftKeyManager; + + return QSoftKeyManagerPrivate::self; +} + +QSoftKeyManager::QSoftKeyManager() : +#ifdef Q_WS_S60 + QObject(*(new QSoftKeyManagerPrivateS60), 0) +#else + QObject(*(new QSoftKeyManagerPrivate), 0) +#endif +{ +} + +QAction *QSoftKeyManager::createAction(StandardSoftKey standardKey, QWidget *actionWidget) +{ + QAction *action = new QAction(standardSoftKeyText(standardKey), actionWidget); +#ifdef SYMBIAN_VERSION_SYMBIAN3 + int key = 0; + switch (standardKey) { + case OkSoftKey: + key = EAknSoftkeyOk; + break; + case SelectSoftKey: + key = EAknSoftkeySelect; + break; + case DoneSoftKey: + key = EAknSoftkeyDone; + break; + case MenuSoftKey: + key = EAknSoftkeyOptions; + break; + case CancelSoftKey: + key = EAknSoftkeyCancel; + break; + default: + break; + }; + if (key != 0) + QSoftKeyManager::instance()->d_func()->softKeyCommandActions.insert(action, key); +#endif + QAction::SoftKeyRole softKeyRole = QAction::NoSoftKey; + switch (standardKey) { + case MenuSoftKey: // FALL-THROUGH + QActionPrivate::get(action)->menuActionSoftkeys = true; + case OkSoftKey: + case SelectSoftKey: + case DoneSoftKey: + softKeyRole = QAction::PositiveSoftKey; + break; + case CancelSoftKey: + softKeyRole = QAction::NegativeSoftKey; + break; + } + action->setSoftKeyRole(softKeyRole); + action->setVisible(false); + setForceEnabledInSoftkeys(action); + return action; +} + +/*! \internal + + Creates a QAction and registers the 'triggered' signal to send the given key event to + \a actionWidget as a convenience. + +*/ +QAction *QSoftKeyManager::createKeyedAction(StandardSoftKey standardKey, Qt::Key key, QWidget *actionWidget) +{ +#ifndef QT_NO_ACTION + QScopedPointer<QAction> action(createAction(standardKey, actionWidget)); + + connect(action.data(), SIGNAL(triggered()), QSoftKeyManager::instance(), SLOT(sendKeyEvent())); + connect(action.data(), SIGNAL(destroyed(QObject*)), QSoftKeyManager::instance(), SLOT(cleanupHash(QObject*))); + QSoftKeyManager::instance()->d_func()->keyedActions.insert(action.data(), key); + return action.take(); +#endif //QT_NO_ACTION +} + +void QSoftKeyManager::cleanupHash(QObject *obj) +{ + Q_D(QSoftKeyManager); + QAction *action = qobject_cast<QAction*>(obj); + d->keyedActions.remove(action); +#ifdef SYMBIAN_VERSION_SYMBIAN3 + d->softKeyCommandActions.remove(action); +#endif +} + +void QSoftKeyManager::sendKeyEvent() +{ + Q_D(QSoftKeyManager); + QAction *action = qobject_cast<QAction*>(sender()); + + if (!action) + return; + + Qt::Key keyToSend = d->keyedActions.value(action, Qt::Key_unknown); + + if (keyToSend != Qt::Key_unknown) + QApplication::postEvent(action->parentWidget(), + new QKeyEvent(QEvent::KeyPress, keyToSend, Qt::NoModifier)); +} + +void QSoftKeyManager::updateSoftKeys() +{ + QSoftKeyManager::instance()->d_func()->pendingUpdate = true; + QEvent *event = new QEvent(QEvent::UpdateSoftKeys); + QApplication::postEvent(QSoftKeyManager::instance(), event); +} + +bool QSoftKeyManager::appendSoftkeys(const QWidget &source, int level) +{ + Q_D(QSoftKeyManager); + bool ret = false; + foreach(QAction *action, source.actions()) { + if (action->softKeyRole() != QAction::NoSoftKey + && (action->isVisible() || isForceEnabledInSofkeys(action))) { + d->requestedSoftKeyActions.insert(level, action); + ret = true; + } + } + return ret; +} + + +static bool isChildOf(const QWidget *c, const QWidget *p) +{ + while (c) { + if (c == p) + return true; + c = c->parentWidget(); + } + return false; +} + +QWidget *QSoftKeyManager::softkeySource(QWidget *previousSource, bool& recursiveMerging) +{ + Q_D(QSoftKeyManager); + QWidget *source = NULL; + if (!previousSource) { + // Initial source is primarily focuswidget and secondarily activeWindow + QWidget *focus = QApplication::focusWidget(); + QWidget *popup = QApplication::activePopupWidget(); + if (popup) { + if (isChildOf(focus, popup)) + source = focus; + else + source = popup; + } + if (!source) { + QWidget *modal = QApplication::activeModalWidget(); + if (modal) { + if (isChildOf(focus, modal)) + source = focus; + else + source = modal; + } + } + if (!source) { + source = focus; + if (!source) + source = QApplication::activeWindow(); + } + } else { + // Softkey merging is based on four criterias + // 1. Implicit merging is used whenever focus widget does not specify any softkeys + bool implicitMerging = d->requestedSoftKeyActions.isEmpty(); + // 2. Explicit merging with parent is used whenever WA_MergeSoftkeys widget attribute is set + bool explicitMerging = previousSource->testAttribute(Qt::WA_MergeSoftkeys); + // 3. Explicit merging with all parents + recursiveMerging |= previousSource->testAttribute(Qt::WA_MergeSoftkeysRecursively); + // 4. Implicit and explicit merging always stops at window boundary + bool merging = (implicitMerging || explicitMerging || recursiveMerging) && !previousSource->isWindow(); + + source = merging ? previousSource->parentWidget() : NULL; + } + return source; +} + +bool QSoftKeyManager::handleUpdateSoftKeys() +{ + Q_D(QSoftKeyManager); + int level = 0; + d->requestedSoftKeyActions.clear(); + bool recursiveMerging = false; + QWidget *source = softkeySource(NULL, recursiveMerging); + d->initialSoftKeySource = source; + while (source) { + if (appendSoftkeys(*source, level)) + ++level; + source = softkeySource(source, recursiveMerging); + } + + d->updateSoftKeys_sys(); + d->pendingUpdate = false; + return true; +} + +void QSoftKeyManager::setForceEnabledInSoftkeys(QAction *action) +{ + QActionPrivate::get(action)->forceEnabledInSoftkeys = true; +} + +bool QSoftKeyManager::isForceEnabledInSofkeys(QAction *action) +{ + return QActionPrivate::get(action)->forceEnabledInSoftkeys; +} + +bool QSoftKeyManager::event(QEvent *e) +{ +#ifndef QT_NO_ACTION + if (e->type() == QEvent::UpdateSoftKeys) + return handleUpdateSoftKeys(); +#endif //QT_NO_ACTION + return false; +} + +#ifdef Q_WS_S60 +bool QSoftKeyManager::handleCommand(int command) +{ + if (QSoftKeyManager::instance()->d_func()->pendingUpdate) + (void)QSoftKeyManager::instance()->handleUpdateSoftKeys(); + + return static_cast<QSoftKeyManagerPrivateS60*>(QSoftKeyManager::instance()->d_func())->handleCommand(command); +} +#endif + +QT_END_NAMESPACE +#endif //QT_NO_SOFTKEYMANAGER diff --git a/src/gui/kernel/qsoftkeymanager_common_p.h b/src/gui/kernel/qsoftkeymanager_common_p.h new file mode 100644 index 0000000000..02ae697eef --- /dev/null +++ b/src/gui/kernel/qsoftkeymanager_common_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOFTKEYMANAGER_COMMON_P_H +#define QSOFTKEYMANAGER_COMMON_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_HEADER + +#ifndef QT_NO_SOFTKEYMANAGER + +QT_BEGIN_NAMESPACE + +class QSoftKeyManagerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSoftKeyManager) + +public: + virtual void updateSoftKeys_sys() {}; + +protected: + static QSoftKeyManager *self; + QHash<QAction*, Qt::Key> keyedActions; + QMultiHash<int, QAction*> requestedSoftKeyActions; + QWidget *initialSoftKeySource; + bool pendingUpdate; +#ifdef SYMBIAN_VERSION_SYMBIAN3 + QHash<QAction*, int> softKeyCommandActions; +#endif +}; + +QT_END_NAMESPACE + +#endif //QT_NO_SOFTKEYMANAGER + +QT_END_HEADER + +#endif // QSOFTKEYMANAGER_COMMON_P_H diff --git a/src/gui/kernel/qsoftkeymanager_p.h b/src/gui/kernel/qsoftkeymanager_p.h new file mode 100644 index 0000000000..78999a97bd --- /dev/null +++ b/src/gui/kernel/qsoftkeymanager_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOFTKEYMANAGER_P_H +#define QSOFTKEYMANAGER_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 <QtCore/qobject.h> +#include "QtGui/qaction.h" + +QT_BEGIN_HEADER + +#ifndef QT_NO_SOFTKEYMANAGER +QT_BEGIN_NAMESPACE + +class QSoftKeyManagerPrivate; + +class Q_AUTOTEST_EXPORT QSoftKeyManager : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSoftKeyManager) + +public: + + enum StandardSoftKey { + OkSoftKey, + SelectSoftKey, + DoneSoftKey, + MenuSoftKey, + CancelSoftKey + }; + + static void updateSoftKeys(); +#ifdef Q_WS_S60 + static bool handleCommand(int); +#endif + + static QAction *createAction(StandardSoftKey standardKey, QWidget *actionWidget); + static QAction *createKeyedAction(StandardSoftKey standardKey, Qt::Key key, QWidget *actionWidget); + static QString standardSoftKeyText(StandardSoftKey standardKey); + static void setForceEnabledInSoftkeys(QAction *action); + static bool isForceEnabledInSofkeys(QAction *action); + +protected: + bool event(QEvent *e); + +private: + QSoftKeyManager(); + static QSoftKeyManager *instance(); + bool appendSoftkeys(const QWidget &source, int level); + QWidget *softkeySource(QWidget *previousSource, bool& recursiveMerging); + bool handleUpdateSoftKeys(); + +private Q_SLOTS: + void cleanupHash(QObject* obj); + void sendKeyEvent(); + +private: + Q_DISABLE_COPY(QSoftKeyManager) +}; + +QT_END_NAMESPACE +#endif //QT_NO_SOFTKEYMANAGER + +QT_END_HEADER + +#endif //QSOFTKEYMANAGER_P_H diff --git a/src/gui/kernel/qsoftkeymanager_s60.cpp b/src/gui/kernel/qsoftkeymanager_s60.cpp new file mode 100644 index 0000000000..79ed91af5b --- /dev/null +++ b/src/gui/kernel/qsoftkeymanager_s60.cpp @@ -0,0 +1,440 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qevent.h" +#include "qbitmap.h" +#include "qstyle.h" +#include "qmenubar.h" +#include "private/qt_s60_p.h" +#include "private/qmenu_p.h" +#include "private/qaction_p.h" +#include "private/qsoftkeymanager_p.h" +#include "private/qsoftkeymanager_s60_p.h" +#include "private/qobject_p.h" +#include <eiksoftkeyimage.h> +#include <eikcmbut.h> + +#ifndef QT_NO_SOFTKEYMANAGER +QT_BEGIN_NAMESPACE + +const int S60_COMMAND_START = 6000; +const int LSK_POSITION = 0; +const int MSK_POSITION = 3; +const int RSK_POSITION = 2; + +QSoftKeyManagerPrivateS60::QSoftKeyManagerPrivateS60() : cbaHasImage(4) // 4 since MSK position index is 3 +{ + cachedCbaIconSize[0] = QSize(0,0); + cachedCbaIconSize[1] = QSize(0,0); + cachedCbaIconSize[2] = QSize(0,0); + cachedCbaIconSize[3] = QSize(0,0); +} + +bool QSoftKeyManagerPrivateS60::skipCbaUpdate() +{ + // Lets not update softkeys if + // 1. We don't have application panes, i.e. cba + // 2. Our CBA is not active, i.e. S60 native dialog or menu with custom CBA is shown + // 2.1. Except if thre is no current CBA at all and WindowSoftkeysRespondHint is set + + // Note: Cannot use IsDisplayingMenuOrDialog since CBA update can be triggered before + // menu/dialog CBA is actually displayed i.e. it is being costructed. + CEikButtonGroupContainer *appUiCba = S60->buttonGroupContainer(); + if (!appUiCba) + return true; + // CEikButtonGroupContainer::Current returns 0 if CBA is not visible at all + CEikButtonGroupContainer *currentCba = CEikButtonGroupContainer::Current(); + // Check if softkey need to be update even they are not visible + bool cbaRespondsWhenInvisible = false; + QWidget *window = QApplication::activeWindow(); + if (window && (window->windowFlags() & Qt::WindowSoftkeysRespondHint)) + cbaRespondsWhenInvisible = true; + + if (QApplication::testAttribute(Qt::AA_S60DontConstructApplicationPanes) + || (appUiCba != currentCba && !cbaRespondsWhenInvisible)) { + return true; + } + return false; +} + +void QSoftKeyManagerPrivateS60::ensureCbaVisibilityAndResponsiviness(CEikButtonGroupContainer &cba) +{ + RDrawableWindow *cbaWindow = cba.DrawableWindow(); + Q_ASSERT_X(cbaWindow, Q_FUNC_INFO, "Native CBA does not have window!"); + // Make sure CBA is visible, i.e. CBA window is on top + cbaWindow->SetOrdinalPosition(0); + // Qt shares same CBA instance between top-level widgets, + // make sure we are not faded by underlying window. + cbaWindow->SetFaded(EFalse, RWindowTreeNode::EFadeIncludeChildren); + // Modal dialogs capture pointer events, but shared cba instance + // shall stay responsive. Raise pointer capture priority to keep + // softkeys responsive in modal dialogs + cbaWindow->SetPointerCapturePriority(1); +} + +void QSoftKeyManagerPrivateS60::clearSoftkeys(CEikButtonGroupContainer &cba) +{ +#ifdef SYMBIAN_VERSION_SYMBIAN3 + QT_TRAP_THROWING( + //EAknSoftkeyEmpty is used, because using -1 adds softkeys without actions on Symbian3 + cba.SetCommandL(0, EAknSoftkeyEmpty, KNullDesC); + cba.SetCommandL(2, EAknSoftkeyEmpty, KNullDesC); + ); +#else + QT_TRAP_THROWING( + //Using -1 instead of EAknSoftkeyEmpty to avoid flickering. + cba.SetCommandL(0, -1, KNullDesC); + // TODO: Should we clear also middle SK? + cba.SetCommandL(2, -1, KNullDesC); + ); +#endif + realSoftKeyActions.clear(); +} + +QString QSoftKeyManagerPrivateS60::softkeyText(QAction &softkeyAction) +{ + // In S60 softkeys and menu items do not support key accelerators (i.e. + // CTRL+X). Therefore, removing the accelerator characters from both softkey + // and menu item texts. + const int underlineShortCut = QApplication::style()->styleHint(QStyle::SH_UnderlineShortcut); + QString iconText = softkeyAction.iconText(); + return underlineShortCut ? softkeyAction.text() : iconText; +} + +QAction *QSoftKeyManagerPrivateS60::highestPrioritySoftkey(QAction::SoftKeyRole role) +{ + QAction *ret = NULL; + // Priority look up is two level + // 1. First widget with softkeys always has highest priority + for (int level = 0; !ret; level++) { + // 2. Highest priority action within widget + QList<QAction*> actions = requestedSoftKeyActions.values(level); + if (actions.isEmpty()) + break; + qSort(actions.begin(), actions.end(), QSoftKeyManagerPrivateS60::actionPriorityMoreThan); + foreach (QAction *action, actions) { + if (action->softKeyRole() == role) { + ret = action; + break; + } + } + } + return ret; +} + +bool QSoftKeyManagerPrivateS60::actionPriorityMoreThan(const QAction *firstItem, const QAction *secondItem) +{ + return firstItem->priority() > secondItem->priority(); +} + +void QSoftKeyManagerPrivateS60::setNativeSoftkey(CEikButtonGroupContainer &cba, + TInt position, TInt command, const TDesC &text) +{ + // Calling SetCommandL causes CBA redraw + QT_TRAP_THROWING(cba.SetCommandL(position, command, text)); +} + +QPoint QSoftKeyManagerPrivateS60::softkeyIconPosition(int position, QSize sourceSize, QSize targetSize) +{ + QPoint iconPosition(0,0); + switch( AknLayoutUtils::CbaLocation() ) + { + case AknLayoutUtils::EAknCbaLocationBottom: + // RSK must be moved to right, LSK in on correct position by default + if (position == RSK_POSITION) + iconPosition.setX(targetSize.width() - sourceSize.width()); + break; + case AknLayoutUtils::EAknCbaLocationRight: + case AknLayoutUtils::EAknCbaLocationLeft: + // Already in correct position + default: + break; + } + + // Align horizontally to center + iconPosition.setY((targetSize.height() - sourceSize.height()) >> 1); + return iconPosition; +} + +QPixmap QSoftKeyManagerPrivateS60::prepareSoftkeyPixmap(QPixmap src, int position, QSize targetSize) +{ + QPixmap target(targetSize); + target.fill(Qt::transparent); + QPainter p; + p.begin(&target); + p.drawPixmap(softkeyIconPosition(position, src.size(), targetSize), src); + p.end(); + return target; +} + +bool QSoftKeyManagerPrivateS60::isOrientationLandscape() +{ + // Hard to believe that there is no public API in S60 to + // get current orientation. This workaround works with currently supported resolutions + return S60->screenHeightInPixels < S60->screenWidthInPixels; +} + +QSize QSoftKeyManagerPrivateS60::cbaIconSize(CEikButtonGroupContainer *cba, int position) +{ + + int index = position; + index += isOrientationLandscape() ? 0 : 1; + if(cachedCbaIconSize[index].isNull()) { + // Only way I figured out to get CBA icon size without RnD SDK, was + // to set some dummy icon to CBA first and then ask CBA button CCoeControl::Size() + // The returned value is cached to avoid unnecessary icon setting every time. + const bool left = (position == LSK_POSITION); + if(position == LSK_POSITION || position == RSK_POSITION) { + CEikImage* tmpImage = NULL; + QT_TRAP_THROWING(tmpImage = new (ELeave) CEikImage); + EikSoftkeyImage::SetImage(cba, *tmpImage, left); // Takes myimage ownership + int command = S60_COMMAND_START + position; + setNativeSoftkey(*cba, position, command, KNullDesC()); + cachedCbaIconSize[index] = qt_TSize2QSize(cba->ControlOrNull(command)->Size()); + EikSoftkeyImage::SetLabel(cba, left); + + if(cachedCbaIconSize[index] == QSize(138,72)) { + // Hack for S60 5.0 (5800) landscape orientation, which return wrong icon size + cachedCbaIconSize[index] = QSize(60,60); + } + } + } + + return cachedCbaIconSize[index]; +} + +bool QSoftKeyManagerPrivateS60::setSoftkeyImage(CEikButtonGroupContainer *cba, + QAction &action, int position) +{ + bool ret = false; + + const bool left = (position == LSK_POSITION); + if(position == LSK_POSITION || position == RSK_POSITION) { + QIcon icon = action.icon(); + if (!icon.isNull()) { + // Get size of CBA icon area based on button position and orientation + QSize requiredIconSize = cbaIconSize(cba, position); + // Get pixmap out of icon based on preferred size, the aspect ratio is kept + QPixmap pmWihtAspectRatio = icon.pixmap(requiredIconSize); + // Native softkeys require that pixmap size is exactly the same as requiredIconSize + // prepareSoftkeyPixmap creates a new pixmap with requiredIconSize and blits the 'pmWihtAspectRatio' + // to correct location of it + QPixmap softkeyPixmap = prepareSoftkeyPixmap(pmWihtAspectRatio, position, requiredIconSize); + + QPixmap softkeyAlpha = softkeyPixmap.alphaChannel(); + // Alpha channel in 5.1 and older devices need to be inverted + // TODO: Switch to use toSymbianCFbsBitmap with invert when available + if(QSysInfo::s60Version() <= QSysInfo::SV_S60_5_1) { + QImage alphaImage = softkeyAlpha.toImage(); + alphaImage.invertPixels(); + softkeyAlpha = QPixmap::fromImage(alphaImage); + } + + CFbsBitmap* nBitmap = softkeyPixmap.toSymbianCFbsBitmap(); + CFbsBitmap* nMask = softkeyAlpha.toSymbianCFbsBitmap(); + + CEikImage* myimage = new (ELeave) CEikImage; + myimage->SetPicture( nBitmap, nMask ); // nBitmap and nMask ownership transferred + + EikSoftkeyImage::SetImage(cba, *myimage, left); // Takes myimage ownership + cbaHasImage[position] = true; + ret = true; + } else { + // Restore softkey to text based + if (cbaHasImage[position]) { + EikSoftkeyImage::SetLabel(cba, left); + cbaHasImage[position] = false; + } + } + } + return ret; +} + +bool QSoftKeyManagerPrivateS60::setSoftkey(CEikButtonGroupContainer &cba, + QAction::SoftKeyRole role, int position) +{ + QAction *action = highestPrioritySoftkey(role); + if (action) { + setSoftkeyImage(&cba, *action, position); + QString text = softkeyText(*action); + TPtrC nativeText = qt_QString2TPtrC(text); + int command = S60_COMMAND_START + position; +#ifdef SYMBIAN_VERSION_SYMBIAN3 + if (softKeyCommandActions.contains(action)) + command = softKeyCommandActions.value(action); +#endif + setNativeSoftkey(cba, position, command, nativeText); + const bool dimmed = !action->isEnabled() && !QSoftKeyManager::isForceEnabledInSofkeys(action); + cba.DimCommand(command, dimmed); + realSoftKeyActions.insert(command, action); + return true; + } + return false; +} + +bool QSoftKeyManagerPrivateS60::setLeftSoftkey(CEikButtonGroupContainer &cba) +{ + return setSoftkey(cba, QAction::PositiveSoftKey, LSK_POSITION); +} + +bool QSoftKeyManagerPrivateS60::setMiddleSoftkey(CEikButtonGroupContainer &cba) +{ + // Note: In order to get MSK working, application has to have EAknEnableMSK flag set + // Currently it is not possible very easily) + // For more information see: http://wiki.forum.nokia.com/index.php/Middle_softkey_usage + return setSoftkey(cba, QAction::SelectSoftKey, MSK_POSITION); +} + +bool QSoftKeyManagerPrivateS60::setRightSoftkey(CEikButtonGroupContainer &cba) +{ + if (!setSoftkey(cba, QAction::NegativeSoftKey, RSK_POSITION)) { + const Qt::WindowType windowType = initialSoftKeySource + ? initialSoftKeySource->window()->windowType() : Qt::Window; + if (windowType != Qt::Dialog && windowType != Qt::Popup) { + QString text(QSoftKeyManager::tr("Exit")); + TPtrC nativeText = qt_QString2TPtrC(text); + if (cbaHasImage[RSK_POSITION]) { + EikSoftkeyImage::SetLabel(&cba, false); + cbaHasImage[RSK_POSITION] = false; + } + setNativeSoftkey(cba, RSK_POSITION, EAknSoftkeyExit, nativeText); + cba.DimCommand(EAknSoftkeyExit, false); + return true; + } + } + return false; +} + +void QSoftKeyManagerPrivateS60::setSoftkeys(CEikButtonGroupContainer &cba) +{ + int requestedSoftkeyCount = requestedSoftKeyActions.count(); + const int maxSoftkeyCount = 2; // TODO: differs based on orientation ans S60 versions (some have MSK) + if (requestedSoftkeyCount > maxSoftkeyCount) { + // We have more softkeys than available slots + // Put highest priority negative action to RSK and Options menu with rest of softkey actions to LSK + // TODO: Build menu + setLeftSoftkey(cba); + if(AknLayoutUtils::MSKEnabled()) + setMiddleSoftkey(cba); + setRightSoftkey(cba); + } else { + // We have less softkeys than available slots + // Put softkeys to request slots based on role + setLeftSoftkey(cba); + if(AknLayoutUtils::MSKEnabled()) + setMiddleSoftkey(cba); + setRightSoftkey(cba); + } +} + +void QSoftKeyManagerPrivateS60::updateSoftKeys_sys() +{ + if (skipCbaUpdate()) + return; + + CEikButtonGroupContainer *nativeContainer = S60->buttonGroupContainer(); + Q_ASSERT_X(nativeContainer, Q_FUNC_INFO, "Native CBA does not exist!"); + ensureCbaVisibilityAndResponsiviness(*nativeContainer); + clearSoftkeys(*nativeContainer); + setSoftkeys(*nativeContainer); + + nativeContainer->DrawDeferred(); // 3.1 needs an extra invitation +} + +static void resetMenuBeingConstructed(TAny* /*aAny*/) +{ + S60->menuBeingConstructed = false; +} + +void QSoftKeyManagerPrivateS60::tryDisplayMenuBarL() +{ + CleanupStack::PushL(TCleanupItem(resetMenuBeingConstructed, NULL)); + S60->menuBeingConstructed = true; + S60->menuBar()->TryDisplayMenuBarL(); + CleanupStack::PopAndDestroy(); // Reset menuBeingConstructed to false in all cases +} + +bool QSoftKeyManagerPrivateS60::handleCommand(int command) +{ + QAction *action = realSoftKeyActions.value(command); + if (action) { + bool property = QActionPrivate::get(action)->menuActionSoftkeys; + if (property) { + QT_TRAP_THROWING(tryDisplayMenuBarL()); + } else if (action->menu()) { + // TODO: This is hack, in order to use exising QMenuBar implementation for Symbian + // menubar needs to have widget to which it is associated. Since we want to associate + // menubar to action (which is inherited from QObject), we create and associate QWidget + // to action and pass that for QMenuBar. This associates the menubar to action, and we + // can have own menubar for each action. + QWidget *actionContainer = action->property("_q_action_widget").value<QWidget*>(); + if(!actionContainer) { + actionContainer = new QWidget(action->parentWidget()); + QMenuBar *menuBar = new QMenuBar(actionContainer); + foreach(QAction *menuAction, action->menu()->actions()) { + QMenu *menu = menuAction->menu(); + if(menu) + menuBar->addMenu(menu); + else + menuBar->addAction(menuAction); + } + QVariant v; + v.setValue(actionContainer); + action->setProperty("_q_action_widget", v); + } + qt_symbian_next_menu_from_action(actionContainer); + QT_TRAP_THROWING(tryDisplayMenuBarL()); + } + + Q_ASSERT(action->softKeyRole() != QAction::NoSoftKey); + QWidget *actionParent = action->parentWidget(); + Q_ASSERT_X(actionParent, Q_FUNC_INFO, "No parent set for softkey action!"); + if (actionParent->isEnabled()) { + action->activate(QAction::Trigger); + return true; + } + } + return false; +} + +QT_END_NAMESPACE +#endif //QT_NO_SOFTKEYMANAGER diff --git a/src/gui/kernel/qsoftkeymanager_s60_p.h b/src/gui/kernel/qsoftkeymanager_s60_p.h new file mode 100644 index 0000000000..9cb3787cb8 --- /dev/null +++ b/src/gui/kernel/qsoftkeymanager_s60_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOFTKEYMANAGER_S60_P_H +#define QSOFTKEYMANAGER_S60_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 "qbitarray.h" +#include "private/qobject_p.h" +#include "private/qsoftkeymanager_common_p.h" + +QT_BEGIN_HEADER + +#ifndef QT_NO_SOFTKEYMANAGER + +QT_BEGIN_NAMESPACE + +class CEikButtonGroupContainer; +class QAction; + +class QSoftKeyManagerPrivateS60 : public QSoftKeyManagerPrivate +{ + Q_DECLARE_PUBLIC(QSoftKeyManager) + +public: + QSoftKeyManagerPrivateS60(); + +public: + void updateSoftKeys_sys(); + bool handleCommand(int command); + +private: + void tryDisplayMenuBarL(); + bool skipCbaUpdate(); + void ensureCbaVisibilityAndResponsiviness(CEikButtonGroupContainer &cba); + void clearSoftkeys(CEikButtonGroupContainer &cba); + QString softkeyText(QAction &softkeyAction); + QAction *highestPrioritySoftkey(QAction::SoftKeyRole role); + static bool actionPriorityMoreThan(const QAction* item1, const QAction* item2); + void setNativeSoftkey(CEikButtonGroupContainer &cba, TInt position, TInt command, const TDesC& text); + QPoint softkeyIconPosition(int position, QSize sourceSize, QSize targetSize); + QPixmap prepareSoftkeyPixmap(QPixmap src, int position, QSize targetSize); + bool isOrientationLandscape(); + QSize cbaIconSize(CEikButtonGroupContainer *cba, int position); + bool setSoftkeyImage(CEikButtonGroupContainer *cba, QAction &action, int position); + bool setSoftkey(CEikButtonGroupContainer &cba, QAction::SoftKeyRole role, int position); + bool setLeftSoftkey(CEikButtonGroupContainer &cba); + bool setMiddleSoftkey(CEikButtonGroupContainer &cba); + bool setRightSoftkey(CEikButtonGroupContainer &cba); + void setSoftkeys(CEikButtonGroupContainer &cba); + +private: + QHash<int, QAction*> realSoftKeyActions; + QSize cachedCbaIconSize[4]; + QBitArray cbaHasImage; +}; + + +QT_END_NAMESPACE + +#endif //QT_NO_SOFTKEYMANAGER + +QT_END_HEADER + +#endif // QSOFTKEYMANAGER_S60_P_H diff --git a/src/gui/kernel/qsound.cpp b/src/gui/kernel/qsound.cpp new file mode 100644 index 0000000000..a61310a4eb --- /dev/null +++ b/src/gui/kernel/qsound.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsound.h" + +#ifndef QT_NO_SOUND + +#include "qlist.h" +#include <private/qobject_p.h> +#include "qsound_p.h" + +QT_BEGIN_NAMESPACE + +static QList<QAuServer*> *servers=0; + +QAuServer::QAuServer(QObject* parent) + : QObject(parent) +{ + if (!servers) + servers = new QList<QAuServer*>; + servers->prepend(this); +} + +QAuServer::~QAuServer() +{ + servers->removeAll(this); + if (servers->count() == 0) { + delete servers; + servers = 0; + } +} + +void QAuServer::play(const QString& filename) +{ + QSound s(filename); + play(&s); +} + +extern QAuServer* qt_new_audio_server(); + +static QAuServer& server() +{ + if (!servers) qt_new_audio_server(); + return *servers->first(); +} + +class QSoundPrivate : public QObjectPrivate +{ +public: + QSoundPrivate(const QString& fname) + : filename(fname), bucket(0), looprem(0), looptotal(1) + { + } + + ~QSoundPrivate() + { + delete bucket; + } + + QString filename; + QAuBucket* bucket; + int looprem; + int looptotal; +}; + +/*! + \class QSound + \brief The QSound class provides access to the platform audio facilities. + + \ingroup multimedia + + + Qt provides the most commonly required audio operation in GUI + applications: asynchronously playing a sound file. This is most + easily accomplished using the static play() function: + + \snippet doc/src/snippets/code/src_gui_kernel_qsound.cpp 0 + + Alternatively, create a QSound object from the sound file first + and then call the play() slot: + + \snippet doc/src/snippets/code/src_gui_kernel_qsound.cpp 1 + + Once created a QSound object can be queried for its fileName() and + total number of loops() (i.e. the number of times the sound will + play). The number of repetitions can be altered using the + setLoops() function. While playing the sound, the loopsRemaining() + function returns the remaining number of repetitions. Use the + isFinished() function to determine whether the sound has finished + playing. + + Sounds played using a QSound object may use more memory than the + static play() function, but it may also play more immediately + (depending on the underlying platform audio facilities). Use the + static isAvailable() function to determine whether sound + facilities exist on the platform. Which facilities that are + actually used varies: + + \table + \header \o Platform \o Audio Facility + \row + \o Microsoft Windows + \o The underlying multimedia system is used; only WAVE format sound files + are supported. + \row + \o X11 + \o The \l{ftp://ftp.x.org/contrib/audio/nas/}{Network Audio System} + is used if available, otherwise all operations work silently. NAS + supports WAVE and AU files. + \row + \o Mac OS X + \o NSSound is used. All formats that NSSound supports, including QuickTime formats, + are supported by Qt for Mac OS X. + \row + \o Qt for Embedded Linux + \o A built-in mixing sound server is used, accessing \c /dev/dsp + directly. Only the WAVE format is supported. + \row + \o Symbian + \o CMdaAudioPlayerUtility is used. All formats that Symbian OS or devices support + are supported also by Qt. + \endtable + + Note that QSound does not support \l{resources.html}{resources}. + This might be fixed in a future Qt version. +*/ + +/*! + Plays the sound stored in the file specified by the given \a filename. + + \sa stop(), loopsRemaining(), isFinished() +*/ +void QSound::play(const QString& filename) +{ + server().play(filename); +} + +/*! + Constructs a QSound object from the file specified by the given \a + filename and with the given \a parent. + + This may use more memory than the static play() function, but it + may also play more immediately (depending on the underlying + platform audio facilities). + + \sa play() +*/ +QSound::QSound(const QString& filename, QObject* parent) + : QObject(*new QSoundPrivate(filename), parent) +{ + server().init(this); +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Constructs a QSound object from the file specified by the given \a + filename and with the given \a parent and \a name. Use the + QSound() construcor and QObject::setObjectName() instead. + + \oldcode + QSound *mySound = new QSound(filename, parent, name); + \newcode + QSounc *mySound = new QSound(filename, parent); + mySound->setObjectName(name); + \endcode +*/ +QSound::QSound(const QString& filename, QObject* parent, const char* name) + : QObject(*new QSoundPrivate(filename), parent) +{ + setObjectName(QString::fromAscii(name)); + server().init(this); +} +#endif + +/*! + Destroys this sound object. If the sound is not finished playing, + the stop() function is called before the sound object is + destructed. + + \sa stop(), isFinished() +*/ +QSound::~QSound() +{ + if (!isFinished()) + stop(); +} + +/*! + Returns true if the sound has finished playing; otherwise returns false. + + \warning On Windows this function always returns true for unlooped sounds. +*/ +bool QSound::isFinished() const +{ + Q_D(const QSound); + return d->looprem == 0; +} + +/*! + \overload + + Starts playing the sound specified by this QSound object. + + The function returns immediately. Depending on the platform audio + facilities, other sounds may stop or be mixed with the new + sound. The sound can be played again at any time, possibly mixing + or replacing previous plays of the sound. + + \sa fileName() +*/ +void QSound::play() +{ + Q_D(QSound); + d->looprem = d->looptotal; + server().play(this); +} + +/*! + Returns the number of times the sound will play. + + \sa loopsRemaining(), setLoops() +*/ +int QSound::loops() const +{ + Q_D(const QSound); + return d->looptotal; +} + +/*! + Returns the remaining number of times the sound will loop (this + value decreases each time the sound is played). + + \sa loops(), isFinished() +*/ +int QSound::loopsRemaining() const +{ + Q_D(const QSound); + return d->looprem; +} + +/*! + \fn void QSound::setLoops(int number) + + Sets the sound to repeat the given \a number of times when it is + played. + + Note that passing the value -1 will cause the sound to loop + indefinitely. + + \sa loops() +*/ +void QSound::setLoops(int n) +{ + Q_D(QSound); + d->looptotal = n; +} + +/*! + Returns the filename associated with this QSound object. + + \sa QSound() +*/ +QString QSound::fileName() const +{ + Q_D(const QSound); + return d->filename; +} + +/*! + Stops the sound playing. + + Note that on Windows the current loop will finish if a sound is + played in a loop. + + \sa play() +*/ +void QSound::stop() +{ + Q_D(QSound); + server().stop(this); + d->looprem = 0; +} + + +/*! + Returns true if sound facilities exist on the platform; otherwise + returns false. + + If no sound is available, all QSound operations work silently and + quickly. An application may choose either to notify the user if + sound is crucial to the application or to operate silently without + bothering the user. + + Note: On Windows this always returns true because some sound card + drivers do not implement a way to find out whether it is available + or not. +*/ +bool QSound::isAvailable() +{ + return server().okay(); +} + +/*! + Sets the internal bucket record of sound \a s to \a b, deleting + any previous setting. +*/ +void QAuServer::setBucket(QSound* s, QAuBucket* b) +{ + delete s->d_func()->bucket; + s->d_func()->bucket = b; +} + +/*! + Returns the internal bucket record of sound \a s. +*/ +QAuBucket* QAuServer::bucket(QSound* s) +{ + return s->d_func()->bucket; +} + +/*! + Decrements the QSound::loopRemaining() value for sound \a s, + returning the result. +*/ +int QAuServer::decLoop(QSound* s) +{ + if (s->d_func()->looprem > 0) + --s->d_func()->looprem; + return s->d_func()->looprem; +} + +/*! + Initializes the sound. The default implementation does nothing. +*/ +void QAuServer::init(QSound*) +{ +} + +QAuBucket::~QAuBucket() +{ +} +/*! + \fn bool QSound::available() + + Use the isAvailable() function instead. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_SOUND diff --git a/src/gui/kernel/qsound.h b/src/gui/kernel/qsound.h new file mode 100644 index 0000000000..70957762b2 --- /dev/null +++ b/src/gui/kernel/qsound.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOUND_H +#define QSOUND_H + +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_SOUND + +class QSoundPrivate; + +class Q_GUI_EXPORT QSound : public QObject +{ + Q_OBJECT + +public: + static bool isAvailable(); + static void play(const QString& filename); + + explicit QSound(const QString& filename, QObject* parent = 0); + ~QSound(); + + int loops() const; + int loopsRemaining() const; + void setLoops(int); + QString fileName() const; + + bool isFinished() const; + +public Q_SLOTS: + void play(); + void stop(); + +public: +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QSound(const QString& filename, QObject* parent, const char* name); + static inline QT3_SUPPORT bool available() { return isAvailable(); } +#endif +private: + Q_DECLARE_PRIVATE(QSound) + friend class QAuServer; +}; + +#endif // QT_NO_SOUND + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSOUND_H diff --git a/src/gui/kernel/qsound_mac.mm b/src/gui/kernel/qsound_mac.mm new file mode 100644 index 0000000000..5a9af135b0 --- /dev/null +++ b/src/gui/kernel/qsound_mac.mm @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qapplication.h> +#include "qsound.h" +#include "qsound_p.h" +#include <private/qt_mac_p.h> +#include <qhash.h> +#include <qdebug.h> +#import <AppKit/AppKit.h> + +#include <AppKit/NSSound.h> + +QT_BEGIN_NAMESPACE + +void qt_mac_beep() +{ + NSBeep(); +} + +QT_END_NAMESPACE + +#ifndef QT_NO_SOUND + +QT_BEGIN_NAMESPACE + +typedef QHash<QSound *, NSSound const *> Sounds; +static Sounds sounds; + +class QAuServerMac : public QAuServer +{ + Q_OBJECT +public: + QAuServerMac(QObject* parent) : QAuServer(parent) { } + void play(const QString& filename); + void play(QSound *s); + void stop(QSound*); + bool okay() { return true; } + using QAuServer::decLoop; // promote to public. +protected: + NSSound *createNSSound(const QString &filename, QSound *qSound); +}; + +QT_END_NAMESPACE + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSSoundDelegate <NSObject> +-(void)sound:(NSSound *)sound didFinishPlaying:(BOOL)aBool; +@end +#endif + +QT_USE_NAMESPACE + +@interface QT_MANGLE_NAMESPACE(QMacSoundDelegate) : NSObject<NSSoundDelegate> { + QSound *qSound; // may be null. + QAuServerMac* server; +} +-(id)initWithQSound:(QSound*)sound:(QAuServerMac*)server; +@end + +@implementation QT_MANGLE_NAMESPACE(QMacSoundDelegate) +-(id)initWithQSound:(QSound*)s:(QAuServerMac*)serv { + self = [super init]; + if(self) { + qSound = s; + server = serv; + } + return self; +} + +// Delegate function that gets called each time a sound finishes. +-(void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finishedOk +{ + // qSound is null if this sound was started by play(QString), + // in which case there is no corresponding QSound object. + if (qSound == 0) { + [sound release]; + [self release]; + return; + } + + // finishedOk is false if the sound cold not be played or + // if it was interrupted by stop(). + if (finishedOk == false) { + sounds.remove(qSound); + [sound release]; + [self release]; + return; + } + + // Check if the sound should loop "forever" (until stop). + if (qSound->loops() == -1) { + [sound play]; + return; + } + + const int remainingIterations = server->decLoop(qSound); + if (remainingIterations > 0) { + [sound play]; + } else { + sounds.remove(qSound); + [sound release]; + [self release]; + } +} +@end + +QT_BEGIN_NAMESPACE + +void QAuServerMac::play(const QString &fileName) +{ + QMacCocoaAutoReleasePool pool; + NSSound * const nsSound = createNSSound(fileName, 0); + [nsSound play]; +} + +void QAuServerMac::play(QSound *qSound) +{ + QMacCocoaAutoReleasePool pool; + NSSound * const nsSound = createNSSound(qSound->fileName(), qSound); + [nsSound play]; + // Keep track of the nsSound object so we can find it again in stop(). + sounds[qSound] = nsSound; +} + +void QAuServerMac::stop(QSound *qSound) +{ + Sounds::const_iterator it = sounds.constFind(qSound); + if (it != sounds.constEnd()) + [*it stop]; +} + +// Creates an NSSound object and installs a "sound finished" callack delegate on it. +NSSound *QAuServerMac::createNSSound(const QString &fileName, QSound *qSound) +{ + NSString *nsFileName = const_cast<NSString *>(reinterpret_cast<const NSString *>(QCFString::toCFStringRef(fileName))); + NSSound * const nsSound = [[NSSound alloc] initWithContentsOfFile: nsFileName byReference:YES]; + QT_MANGLE_NAMESPACE(QMacSoundDelegate) * const delegate = [[QT_MANGLE_NAMESPACE(QMacSoundDelegate) alloc] initWithQSound:qSound:this]; + [nsSound setDelegate:delegate]; + [nsFileName release]; + return nsSound; +} + +QAuServer* qt_new_audio_server() +{ + return new QAuServerMac(qApp); +} + +QT_END_NAMESPACE + +#include "qsound_mac.moc" + +#endif // QT_NO_SOUND diff --git a/src/gui/kernel/qsound_p.h b/src/gui/kernel/qsound_p.h new file mode 100644 index 0000000000..dfdbfff063 --- /dev/null +++ b/src/gui/kernel/qsound_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOUND_P_H +#define QSOUND_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qobject.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SOUND + +class QSound; +/* + QAuServer is an INTERNAL class. If you wish to provide support for + additional audio servers, you can make a subclass of QAuServer to do + so, HOWEVER, your class may need to be re-engineered to some degree + with each new Qt release, including minor releases. + + QAuBucket is whatever you want. +*/ + +class QAuBucket { +public: + virtual ~QAuBucket(); +}; + +class QAuServer : public QObject { + Q_OBJECT + +public: + explicit QAuServer(QObject* parent); + ~QAuServer(); + + virtual void init(QSound*); + virtual void play(const QString& filename); + virtual void play(QSound*)=0; + virtual void stop(QSound*)=0; + virtual bool okay()=0; + +protected: + void setBucket(QSound*, QAuBucket*); + QAuBucket* bucket(QSound*); + int decLoop(QSound*); +}; + +#endif // QT_NO_SOUND + +QT_END_NAMESPACE + +#endif // QSOUND_P_H diff --git a/src/gui/kernel/qsound_qws.cpp b/src/gui/kernel/qsound_qws.cpp new file mode 100644 index 0000000000..c48347bf76 --- /dev/null +++ b/src/gui/kernel/qsound_qws.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" + +#ifndef QT_NO_SOUND + +#include "qsound.h" +#include "qpaintdevice.h" +#include "qwsdisplay_qws.h" +#include "qsound_p.h" + +#include "qsoundqss_qws.h" + +#include "qhash.h" +#include "qfileinfo.h" + +#include "qbytearray.h" +#include "quuid.h" +#include "qdatastream.h" +#include "qcopchannel_qws.h" +#include "qbuffer.h" + + +QT_BEGIN_NAMESPACE + +#ifdef MEDIA_SERVER + +#define SERVER_CHANNEL "QPE/MediaServer" + +class QCopMessage : public QDataStream +{ +public: + QCopMessage( const QString& channel, const QString& message ) + : QDataStream( new QBuffer ), m_channel( channel ), m_message( message ) + { + device()->open( QIODevice::WriteOnly ); + } + + ~QCopMessage() + { + QCopChannel::send( m_channel, m_message, ((QBuffer*)device())->buffer() ); + delete device(); + } + +private: + QString m_channel; + QString m_message; +}; + +#endif // MEDIA_SERVER + +class QAuServerQWS; + +class QAuBucketQWS : public QObject, public QAuBucket +{ + Q_OBJECT +public: + QAuBucketQWS( QAuServerQWS*, QSound*, QObject* parent = 0 ); + + ~QAuBucketQWS(); + +#ifndef MEDIA_SERVER + int id() const { return id_; } +#endif + + QSound* sound() const { return sound_; } + +#ifdef MEDIA_SERVER + void play(); + + void stop(); +#endif + +signals: + // Only for Media Server + void done( QAuBucketQWS* ); + +private slots: + // Only for Media Server + void processMessage( const QString& msg, const QByteArray& data ); + +private: +#ifdef MEDIA_SERVER + QCopChannel *m_channel; + QUuid m_id; +#endif + +#ifndef MEDIA_SERVER + int id_; +#endif + QSound *sound_; + QAuServerQWS *server_; + + static int next; +}; + +int QAuBucketQWS::next = 0; + +class QAuServerQWS : public QAuServer +{ + Q_OBJECT +public: + QAuServerQWS( QObject* parent ); + + void init( QSound* s ) + { + QAuBucketQWS *bucket = new QAuBucketQWS( this, s ); +#ifdef MEDIA_SERVER + connect( bucket, SIGNAL(done(QAuBucketQWS*)), + this, SLOT(complete(QAuBucketQWS*)) ); +#endif + setBucket( s, bucket ); + } + +#ifndef MEDIA_SERVER + // Register bucket + void insert( QAuBucketQWS *bucket ) + { + buckets.insert( bucket->id(), bucket ); + } + + // Remove bucket from register + void remove( QAuBucketQWS *bucket ) + { + buckets.remove( bucket->id() ); + } +#endif + + void play( QSound* s ) + { + QString filepath = QFileInfo( s->fileName() ).absoluteFilePath(); +#if defined(QT_NO_QWS_SOUNDSERVER) + server->playFile( bucket( s )->id(), filepath ); +#elif defined(MEDIA_SERVER) + bucket( s )->play(); +#else + client->play( bucket( s )->id(), filepath ); +#endif + } + + void stop( QSound* s ) + { +#if defined(QT_NO_QWS_SOUNDSERVER) + server->stopFile( bucket( s )->id() ); +#elif defined(MEDIA_SERVER) + bucket( s )->stop(); +#else + client->stop( bucket( s )->id() ); +#endif + } + + bool okay() { return true; } + +private slots: + // Continue playing sound if loops remain + void complete( int id ) + { +#ifndef MEDIA_SERVER + QAuBucketQWS *bucket = find( id ); + if( bucket ) { + QSound *sound = bucket->sound(); + if( decLoop( sound ) ) { + play( sound ); + } + } +#else + Q_UNUSED(id); +#endif + } + + // Only for Media Server + void complete( QAuBucketQWS* bucket ) + { +#ifndef MEDIA_SERVER + Q_UNUSED(bucket); +#else + QSound *sound = bucket->sound(); + if( decLoop( sound ) ) { + play( sound ); + } +#endif + } + +protected: + QAuBucketQWS* bucket( QSound *s ) + { + return (QAuBucketQWS*)QAuServer::bucket( s ); + } + +private: +#ifndef MEDIA_SERVER + // Find registered bucket with given id, return null if none found + QAuBucketQWS* find( int id ) + { + QHash<int, QAuBucketQWS*>::Iterator it = buckets.find( id ); + if( it != buckets.end() ) { + return it.value(); + } + + return 0; + } + + QHash<int, QAuBucketQWS*> buckets; // ### possible problem with overlapping keys + +#ifdef QT_NO_QWS_SOUNDSERVER + QWSSoundServer *server; +#else + QWSSoundClient *client; +#endif + +#endif // MEDIA_SERVER +}; + +QAuServerQWS::QAuServerQWS(QObject* parent) : + QAuServer(parent) +{ +#ifndef MEDIA_SERVER + setObjectName(QLatin1String("qauserverqws")); + +#ifdef QT_NO_QWS_SOUNDSERVER + server = new QWSSoundServer( this ); // ### only suitable for single application + + connect( server, SIGNAL(soundCompleted(int)), + this, SLOT(complete(int)) ); +#else + client = new QWSSoundClient( this ); // ### requires successful connection + + connect( client, SIGNAL(soundCompleted(int)), + this, SLOT(complete(int)) ); +#endif + +#endif // MEDIA_SERVER +} + +QAuBucketQWS::QAuBucketQWS( QAuServerQWS *server, QSound *sound, QObject* parent ) + : QObject( parent ), sound_( sound ), server_( server ) +{ +#ifdef MEDIA_SERVER + m_id = QUuid::createUuid(); + + sound->setObjectName( m_id.toString() ); + + m_channel = new QCopChannel(QLatin1String("QPE/QSound/") + m_id, this ); + connect( m_channel, SIGNAL(received(QString,QByteArray)), + this, SLOT(processMessage(QString,QByteArray)) ); + + { + QCopMessage message( QLatin1String(SERVER_CHANNEL), QLatin1String("subscribe(QUuid)") ); + message << m_id; + } + + { + QString filepath = QFileInfo( sound_->fileName() ).absoluteFilePath(); + QCopMessage message( QLatin1String(SERVER_CHANNEL), QLatin1String("open(QUuid,QString)") ); + message << m_id << filepath; + } +#else + id_ = next++; + server_->insert( this ); +#endif +} + +#ifdef MEDIA_SERVER +void QAuBucketQWS::play() +{ + QString filepath = QFileInfo( sound_->fileName() ).absoluteFilePath(); + + QCopMessage message( QLatin1String(SERVER_CHANNEL), QLatin1String("play(QUuid)") ); + message << m_id; +} + +void QAuBucketQWS::stop() +{ + QCopMessage message( QLatin1String(SERVER_CHANNEL), QLatin1String("stop(QUuid)") ); + message << m_id; +} +#endif // MEDIA_SERVER + +void QAuBucketQWS::processMessage( const QString& msg, const QByteArray& data ) +{ + Q_UNUSED(data); +#ifndef MEDIA_SERVER + Q_UNUSED(msg); +#else + if( msg == QLatin1String("done()") ) { + emit done( this ); + } +#endif +} + +QAuBucketQWS::~QAuBucketQWS() +{ +#ifdef MEDIA_SERVER + QCopMessage message( QLatin1String(SERVER_CHANNEL), QLatin1String("revoke(QUuid)") ); + message << m_id; +#else + server_->remove( this ); +#endif +} + + +QAuServer* qt_new_audio_server() +{ + return new QAuServerQWS(qApp); +} + +#include "qsound_qws.moc" + +#endif // QT_NO_SOUND + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qsound_s60.cpp b/src/gui/kernel/qsound_s60.cpp new file mode 100644 index 0000000000..acc5c2a56f --- /dev/null +++ b/src/gui/kernel/qsound_s60.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#ifndef QT_NO_SOUND + +#include "qdir.h" +#include "qapplication.h" +#include "qsound.h" +#include "qsound_p.h" +#include "qfileinfo.h" +#include <private/qcore_symbian_p.h> + +#include <e32std.h> +#include <mdaaudiosampleplayer.h> + +QT_BEGIN_NAMESPACE + +class QAuServerS60; + +class QAuBucketS60 : public QAuBucket, public MMdaAudioPlayerCallback +{ +public: + QAuBucketS60(QAuServerS60 *server, QSound *sound); + ~QAuBucketS60(); + + void play(); + void stop(); + + inline QSound *sound() const { return m_sound; } + +public: // from MMdaAudioPlayerCallback + void MapcInitComplete(TInt aError, const TTimeIntervalMicroSeconds& aDuration); + void MapcPlayComplete(TInt aError); + +private: + QSound *m_sound; + QAuServerS60 *m_server; + bool m_prepared; + bool m_playCalled; + CMdaAudioPlayerUtility *m_playUtility; +}; + + +class QAuServerS60 : public QAuServer +{ +public: + QAuServerS60(QObject *parent); + + void init(QSound *s) + { + QAuBucketS60 *bucket = new QAuBucketS60(this, s); + setBucket(s, bucket); + } + + void play(QSound *s) + { + bucket(s)->play(); + } + + void stop(QSound *s) + { + bucket(s)->stop(); + } + + bool okay() { return true; } + + void play(const QString& filename); + +protected: + void playCompleted(QAuBucketS60 *bucket, int error); + +protected: + QAuBucketS60 *bucket(QSound *s) + { + return (QAuBucketS60 *)QAuServer::bucket( s ); + } + + friend class QAuBucketS60; + + // static QSound::play(filename) cannot be stopped, meaning that playCompleted + // will get always called and QSound gets removed form this list. + QList<QSound *> staticPlayingSounds; +}; + +QAuServerS60::QAuServerS60(QObject *parent) : + QAuServer(parent) +{ + setObjectName(QLatin1String("QAuServerS60")); +} + +void QAuServerS60::play(const QString& filename) +{ + QSound *s = new QSound(filename); + staticPlayingSounds.append(s); + play(s); +} + +void QAuServerS60::playCompleted(QAuBucketS60 *bucket, int error) +{ + QSound *sound = bucket->sound(); + if (!error) { + // We need to handle repeats by ourselves, since with Symbian API we don't + // know how many loops have been played when user asks it + if (decLoop(sound)) { + play(sound); + } else { + if (staticPlayingSounds.removeAll(sound)) + delete sound; + } + } else { + // We don't have a way to inform about errors -> just decrement loops + // in order that QSound::isFinished will return true; + while (decLoop(sound) > 0) {} + if (staticPlayingSounds.removeAll(sound)) + delete sound; + } +} + +QAuServer *qt_new_audio_server() +{ + return new QAuServerS60(qApp); +} + +QAuBucketS60::QAuBucketS60(QAuServerS60 *server, QSound *sound) + : m_sound(sound), m_server(server), m_prepared(false), m_playCalled(false) +{ + QString filepath = QFileInfo(m_sound->fileName()).absoluteFilePath(); + filepath = QDir::toNativeSeparators(filepath); + TPtrC filepathPtr(qt_QString2TPtrC(filepath)); + TRAPD(err, m_playUtility = CMdaAudioPlayerUtility::NewL(*this); + m_playUtility->OpenFileL(filepathPtr)); + if (err) { + m_server->playCompleted(this, err); + } +} + +void QAuBucketS60::play() +{ + if (m_prepared) { + // OpenFileL call is completed we can start playing immediately + m_playUtility->Play(); + } else { + m_playCalled = true; + } + +} + +void QAuBucketS60::stop() +{ + m_playCalled = false; + m_playUtility->Stop(); +} + +void QAuBucketS60::MapcPlayComplete(TInt aError) +{ + m_server->playCompleted(this, aError); +} + +void QAuBucketS60::MapcInitComplete(TInt aError, const TTimeIntervalMicroSeconds& /*aDuration*/) +{ + if (aError) { + m_server->playCompleted(this, aError); + } else { + m_prepared = true; + if (m_playCalled){ + play(); + } + } +} + +QAuBucketS60::~QAuBucketS60() +{ + if (m_playUtility){ + m_playUtility->Stop(); + m_playUtility->Close(); + } + + delete m_playUtility; +} + + +#endif // QT_NO_SOUND + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qsound_win.cpp b/src/gui/kernel/qsound_win.cpp new file mode 100644 index 0000000000..c11482d608 --- /dev/null +++ b/src/gui/kernel/qsound_win.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsound.h" + +#ifndef QT_NO_SOUND + +#include "qapplication.h" +#include "qapplication_p.h" +#include <qfile.h> +#include "qpointer.h" +#include "qsound_p.h" + +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +class QAuServerWindows : public QAuServer { + Q_OBJECT + +public: + QAuServerWindows(QObject* parent); + ~QAuServerWindows(); + + void playHelper(const QString &filename, int loop, QSound *snd); + void play(const QString& filename, int loop); + void play(QSound*); + + void stop(QSound*); + bool okay(); + + int decLoop(QSound *snd) { return QAuServer::decLoop(snd); } + + HANDLE current; + HANDLE mutex; + HANDLE event; +}; + +QAuServerWindows::QAuServerWindows(QObject* parent) : + QAuServer(parent), current(0) +{ + mutex = CreateMutex(0, 0, 0); + event = CreateEvent(0, FALSE, FALSE, 0); +} + +QAuServerWindows::~QAuServerWindows() +{ + HANDLE mtx = mutex; + WaitForSingleObject(mtx, INFINITE); + mutex = 0; + + ReleaseMutex(mtx); + CloseHandle(mtx); + CloseHandle(event); +} + +struct SoundInfo +{ + SoundInfo(const QString &fn, int lp, QSound *snd, QAuServerWindows *srv) + : sound(snd), server(srv), filename(fn), loops(lp) + { + } + + QSound *sound; + QAuServerWindows *server; + QString filename; + int loops; +}; + +DWORD WINAPI SoundPlayProc(LPVOID param) +{ + SoundInfo *info = (SoundInfo*)param; + + // copy data before waking up GUI thread + QAuServerWindows *server = info->server; + QSound *sound = info->sound; + int loops = info->loops; + QString filename = info->filename; + HANDLE mutex = server->mutex; + HANDLE event = server->event; + info = 0; + + // server must not be destroyed until thread finishes + // and all other sounds have to wait + WaitForSingleObject(mutex, INFINITE); + + if (loops <= 1) { + server->current = 0; + int flags = SND_FILENAME|SND_ASYNC; + if (loops == -1) + flags |= SND_LOOP; + + PlaySound((wchar_t*)filename.utf16(), 0, flags); + if (sound && loops == 1) + server->decLoop(sound); + + // GUI thread continues, but we are done as well. + SetEvent(event); + } else { + // signal GUI thread to continue - sound might be reset! + QPointer<QSound> guarded_sound = sound; + SetEvent(event); + + for (int l = 0; l < loops && server->current; ++l) { + PlaySound((wchar_t*)filename.utf16(), 0, SND_FILENAME | SND_SYNC); + + if (guarded_sound) + server->decLoop(guarded_sound); + } + server->current = 0; + } + ReleaseMutex(mutex); + + return 0; +} + +void QAuServerWindows::playHelper(const QString &filename, int loop, QSound *snd) +{ + if (loop == 0) + return; + // busy? + if (WaitForSingleObject(mutex, 0) == WAIT_TIMEOUT) + return; + ReleaseMutex(mutex); + + DWORD threadid = 0; + SoundInfo info(filename, loop, snd, this); + current = CreateThread(0, 0, SoundPlayProc, &info, 0, &threadid); + CloseHandle(current); + + WaitForSingleObject(event, INFINITE); +} + +void QAuServerWindows::play(const QString& filename, int loop) +{ + playHelper(filename, loop, 0); +} + +void QAuServerWindows::play(QSound* s) +{ + playHelper(s->fileName(), s->loops(), s); +} + +void QAuServerWindows::stop(QSound*) +{ + // stop unlooped sound + if (!current) + PlaySound(0, 0, 0); + // stop after loop is done + current = 0; +} + +bool QAuServerWindows::okay() +{ + return true; +} + +QAuServer* qt_new_audio_server() +{ + return new QAuServerWindows(qApp); +} + +QT_END_NAMESPACE + +#include "qsound_win.moc" + +#endif // QT_NO_SOUND diff --git a/src/gui/kernel/qsound_x11.cpp b/src/gui/kernel/qsound_x11.cpp new file mode 100644 index 0000000000..12c06f0aa1 --- /dev/null +++ b/src/gui/kernel/qsound_x11.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsound.h" + +#ifndef QT_NO_SOUND + +#include "qhash.h" +#include "qsocketnotifier.h" +#include "qapplication.h" +#include "qsound_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_NAS + +QT_BEGIN_INCLUDE_NAMESPACE +#include <audio/audiolib.h> +#include <audio/soundlib.h> +QT_END_INCLUDE_NAMESPACE + +static AuServer *nas=0; + +static AuBool eventPred(AuServer *, AuEvent *e, AuPointer p) +{ + if (e && (e->type == AuEventTypeElementNotify)) { + if (e->auelementnotify.flow == *((AuFlowID *)p)) + return true; + } + return false; +} + +class QAuBucketNAS : public QAuBucket { +public: + QAuBucketNAS(AuBucketID b, AuFlowID f = 0) : id(b), flow(f), stopped(true), numplaying(0) { } + ~QAuBucketNAS() + { + if (nas) { + AuSync(nas, false); + AuDestroyBucket(nas, id, NULL); + + AuEvent ev; + while (AuScanEvents(nas, AuEventsQueuedAfterFlush, true, eventPred, &flow, &ev)) + ; + } + } + + AuBucketID id; + AuFlowID flow; + bool stopped; + int numplaying; +}; + +class QAuServerNAS : public QAuServer { + Q_OBJECT + + QSocketNotifier* sn; + +public: + QAuServerNAS(QObject* parent); + ~QAuServerNAS(); + + void init(QSound*); + void play(const QString& filename); + void play(QSound*); + void stop(QSound*); + bool okay(); + void setDone(QSound*); + +public slots: + void dataReceived(); + void soundDestroyed(QObject *o); + +private: + QAuBucketNAS* bucket(QSound* s) + { + return (QAuBucketNAS*)QAuServer::bucket(s); + } +}; + +QAuServerNAS::QAuServerNAS(QObject* parent) : + QAuServer(parent) +{ + setObjectName(QLatin1String("Network Audio System")); + nas = AuOpenServer(NULL, 0, NULL, 0, NULL, NULL); + if (nas) { + AuSetCloseDownMode(nas, AuCloseDownDestroy, NULL); + // Ask Qt for async messages... + sn=new QSocketNotifier(AuServerConnectionNumber(nas), + QSocketNotifier::Read); + QObject::connect(sn, SIGNAL(activated(int)), + this, SLOT(dataReceived())); + } else { + sn = 0; + } +} + +QAuServerNAS::~QAuServerNAS() +{ + if (nas) + AuCloseServer(nas); + delete sn; + nas = 0; +} + +typedef QHash<void*,QAuServerNAS*> AuServerHash; +static AuServerHash *inprogress=0; + +void QAuServerNAS::soundDestroyed(QObject *o) +{ + if (inprogress) { + QSound *so = static_cast<QSound *>(o); + while (inprogress->remove(so)) + ; // Loop while remove returns true + } +} + +void QAuServerNAS::play(const QString& filename) +{ + if (nas) { + int iv=100; + AuFixedPoint volume=AuFixedPointFromFraction(iv,100); + AuSoundPlayFromFile(nas, filename.toLocal8Bit().constData(), AuNone, volume, + NULL, NULL, NULL, NULL, NULL, NULL); + AuFlush(nas); + dataReceived(); + AuFlush(nas); + qApp->flush(); + } +} + +static void callback(AuServer*, AuEventHandlerRec*, AuEvent* e, AuPointer p) +{ + if (inprogress->contains(p) && e) { + if (e->type==AuEventTypeElementNotify && + e->auelementnotify.kind==AuElementNotifyKindState) { + if (e->auelementnotify.cur_state == AuStateStop) { + AuServerHash::Iterator it = inprogress->find(p); + if (it != inprogress->end()) + (*it)->setDone((QSound*)p); + } + } + } +} + +void QAuServerNAS::setDone(QSound* s) +{ + if (nas) { + decLoop(s); + if (s->loopsRemaining() && !bucket(s)->stopped) { + bucket(s)->stopped = true; + play(s); + } else { + if (--(bucket(s)->numplaying) == 0) + bucket(s)->stopped = true; + inprogress->remove(s); + } + } +} + +void QAuServerNAS::play(QSound* s) +{ + if (nas) { + ++(bucket(s)->numplaying); + if (!bucket(s)->stopped) { + stop(s); + } + + bucket(s)->stopped = false; + if (!inprogress) + inprogress = new AuServerHash; + inprogress->insert(s,this); + int iv=100; + AuFixedPoint volume=AuFixedPointFromFraction(iv,100); + QAuBucketNAS *b = bucket(s); + AuSoundPlayFromBucket(nas, b->id, AuNone, volume, + callback, s, 0, &b->flow, NULL, NULL, NULL); + AuFlush(nas); + dataReceived(); + AuFlush(nas); + qApp->flush(); + } +} + +void QAuServerNAS::stop(QSound* s) +{ + if (nas && !bucket(s)->stopped) { + bucket(s)->stopped = true; + AuStopFlow(nas, bucket(s)->flow, NULL); + AuFlush(nas); + dataReceived(); + AuFlush(nas); + qApp->flush(); + } +} + +void QAuServerNAS::init(QSound* s) +{ + connect(s, SIGNAL(destroyed(QObject*)), + this, SLOT(soundDestroyed(QObject*))); + + if (nas) { + AuBucketID b_id = + AuSoundCreateBucketFromFile(nas, s->fileName().toLocal8Bit().constData(), + 0 /*AuAccessAllMasks*/, NULL, NULL); + setBucket(s, new QAuBucketNAS(b_id)); + } +} + +bool QAuServerNAS::okay() +{ + return !!nas; +} + +void QAuServerNAS::dataReceived() +{ + AuHandleEvents(nas); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qsound_x11.moc" +QT_END_INCLUDE_NAMESPACE + +#endif + + +class QAuServerNull : public QAuServer +{ +public: + QAuServerNull(QObject* parent); + + void play(const QString&) { } + void play(QSound*s) { while(decLoop(s) > 0) /* nothing */ ; } + void stop(QSound*) { } + bool okay() { return false; } +}; + +QAuServerNull::QAuServerNull(QObject* parent) + : QAuServer(parent) +{ +} + + +QAuServer* qt_new_audio_server() +{ +#ifndef QT_NO_NAS + QAuServer* s = new QAuServerNAS(qApp); + if (s->okay()) + return s; + else + delete s; +#endif + return new QAuServerNull(qApp); +} + +QT_END_NAMESPACE + +#endif // QT_NO_SOUND diff --git a/src/gui/kernel/qstackedlayout.cpp b/src/gui/kernel/qstackedlayout.cpp new file mode 100644 index 0000000000..c5ce238958 --- /dev/null +++ b/src/gui/kernel/qstackedlayout.cpp @@ -0,0 +1,543 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstackedlayout.h" +#include "qlayout_p.h" + +#include <qlist.h> +#include <qwidget.h> +#include "private/qlayoutengine_p.h" + +QT_BEGIN_NAMESPACE + +class QStackedLayoutPrivate : public QLayoutPrivate +{ + Q_DECLARE_PUBLIC(QStackedLayout) +public: + QStackedLayoutPrivate() : index(-1), stackingMode(QStackedLayout::StackOne) {} + QList<QLayoutItem *> list; + int index; + QStackedLayout::StackingMode stackingMode; +}; + +/*! + \class QStackedLayout + + \brief The QStackedLayout class provides a stack of widgets where + only one widget is visible at a time. + + \ingroup geomanagement + + QStackedLayout can be used to create a user interface similar to + the one provided by QTabWidget. There is also a convenience + QStackedWidget class built on top of QStackedLayout. + + A QStackedLayout can be populated with a number of child widgets + ("pages"). For example: + + \snippet doc/src/snippets/qstackedlayout/main.cpp 0 + \codeline + \snippet doc/src/snippets/qstackedlayout/main.cpp 2 + \snippet doc/src/snippets/qstackedlayout/main.cpp 3 + + QStackedLayout provides no intrinsic means for the user to switch + page. This is typically done through a QComboBox or a QListWidget + that stores the titles of the QStackedLayout's pages. For + example: + + \snippet doc/src/snippets/qstackedlayout/main.cpp 1 + + When populating a layout, the widgets are added to an internal + list. The indexOf() function returns the index of a widget in that + list. The widgets can either be added to the end of the list using + the addWidget() function, or inserted at a given index using the + insertWidget() function. The removeWidget() function removes the + widget at the given index from the layout. The number of widgets + contained in the layout, can be obtained using the count() + function. + + The widget() function returns the widget at a given index + position. The index of the widget that is shown on screen is given + by currentIndex() and can be changed using setCurrentIndex(). In a + similar manner, the currently shown widget can be retrieved using + the currentWidget() function, and altered using the + setCurrentWidget() function. + + Whenever the current widget in the layout changes or a widget is + removed from the layout, the currentChanged() and widgetRemoved() + signals are emitted respectively. + + \sa QStackedWidget, QTabWidget +*/ + +/*! + \fn void QStackedLayout::currentChanged(int index) + + This signal is emitted whenever the current widget in the layout + changes. The \a index specifies the index of the new current + widget, or -1 if there isn't a new one (for example, if there + are no widgets in the QStackedLayout) + + \sa currentWidget(), setCurrentWidget() +*/ + +/*! + \fn void QStackedLayout::widgetRemoved(int index) + + This signal is emitted whenever a widget is removed from the + layout. The widget's \a index is passed as parameter. + + \sa removeWidget() +*/ + +/*! + \fn QWidget *QStackedLayout::widget() + \internal +*/ + +/*! + Constructs a QStackedLayout with no parent. + + This QStackedLayout must be installed on a widget later on to + become effective. + + \sa addWidget(), insertWidget() +*/ +QStackedLayout::QStackedLayout() + : QLayout(*new QStackedLayoutPrivate, 0, 0) +{ +} + +/*! + Constructs a new QStackedLayout with the given \a parent. + + This layout will install itself on the \a parent widget and + manage the geometry of its children. +*/ +QStackedLayout::QStackedLayout(QWidget *parent) + : QLayout(*new QStackedLayoutPrivate, 0, parent) +{ +} + +/*! + Constructs a new QStackedLayout and inserts it into + the given \a parentLayout. +*/ +QStackedLayout::QStackedLayout(QLayout *parentLayout) + : QLayout(*new QStackedLayoutPrivate, parentLayout, 0) +{ +} + +/*! + Destroys this QStackedLayout. Note that the layout's widgets are + \e not destroyed. +*/ +QStackedLayout::~QStackedLayout() +{ + Q_D(QStackedLayout); + qDeleteAll(d->list); +} + +/*! + Adds the given \a widget to the end of this layout and returns the + index position of the \a widget. + + If the QStackedLayout is empty before this function is called, + the given \a widget becomes the current widget. + + \sa insertWidget(), removeWidget(), setCurrentWidget() +*/ +int QStackedLayout::addWidget(QWidget *widget) +{ + Q_D(QStackedLayout); + return insertWidget(d->list.count(), widget); +} + +/*! + Inserts the given \a widget at the given \a index in this + QStackedLayout. If \a index is out of range, the widget is + appended (in which case it is the actual index of the \a widget + that is returned). + + If the QStackedLayout is empty before this function is called, the + given \a widget becomes the current widget. + + Inserting a new widget at an index less than or equal to the current index + will increment the current index, but keep the current widget. + + \sa addWidget(), removeWidget(), setCurrentWidget() +*/ +int QStackedLayout::insertWidget(int index, QWidget *widget) +{ + Q_D(QStackedLayout); + addChildWidget(widget); + index = qMin(index, d->list.count()); + if (index < 0) + index = d->list.count(); + QWidgetItem *wi = QLayoutPrivate::createWidgetItem(this, widget); + d->list.insert(index, wi); + invalidate(); + if (d->index < 0) { + setCurrentIndex(index); + } else { + if (index <= d->index) + ++d->index; + if (d->stackingMode == StackOne) + widget->hide(); + widget->lower(); + } + return index; +} + +/*! + \reimp +*/ +QLayoutItem *QStackedLayout::itemAt(int index) const +{ + Q_D(const QStackedLayout); + return d->list.value(index); +} + +// Code that enables proper handling of the case that takeAt() is +// called somewhere inside QObject destructor (can't call hide() +// on the object then) + +class QtFriendlyLayoutWidget : public QWidget +{ +public: + inline bool wasDeleted() const { return d_ptr->wasDeleted; } +}; + +static bool qt_wasDeleted(const QWidget *w) { return static_cast<const QtFriendlyLayoutWidget*>(w)->wasDeleted(); } + + +/*! + \reimp +*/ +QLayoutItem *QStackedLayout::takeAt(int index) +{ + Q_D(QStackedLayout); + if (index <0 || index >= d->list.size()) + return 0; + QLayoutItem *item = d->list.takeAt(index); + if (index == d->index) { + d->index = -1; + if ( d->list.count() > 0 ) { + int newIndex = (index == d->list.count()) ? index-1 : index; + setCurrentIndex(newIndex); + } else { + emit currentChanged(-1); + } + } else if (index < d->index) { + --d->index; + } + emit widgetRemoved(index); + if (item->widget() && !qt_wasDeleted(item->widget())) + item->widget()->hide(); + return item; +} + +/*! + \property QStackedLayout::currentIndex + \brief the index position of the widget that is visible + + The current index is -1 if there is no current widget. + + \sa currentWidget(), indexOf() +*/ +void QStackedLayout::setCurrentIndex(int index) +{ + Q_D(QStackedLayout); + QWidget *prev = currentWidget(); + QWidget *next = widget(index); + if (!next || next == prev) + return; + + bool reenableUpdates = false; + QWidget *parent = parentWidget(); + + if (parent && parent->updatesEnabled()) { + reenableUpdates = true; + parent->setUpdatesEnabled(false); + } + + QWidget *fw = parent ? parent->window()->focusWidget() : 0; + if (prev) { + prev->clearFocus(); + if (d->stackingMode == StackOne) + prev->hide(); + } + + d->index = index; + next->raise(); + next->show(); + + // try to move focus onto the incoming widget if focus + // was somewhere on the outgoing widget. + + if (parent) { + if (fw && (prev && prev->isAncestorOf(fw))) { // focus was on old page + // look for the best focus widget we can find + if (QWidget *nfw = next->focusWidget()) + nfw->setFocus(); + else { + // second best: first child widget in the focus chain + QWidget *i = fw; + while ((i = i->nextInFocusChain()) != fw) { + if (((i->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) + && !i->focusProxy() && i->isVisibleTo(next) && i->isEnabled() + && next->isAncestorOf(i)) { + i->setFocus(); + break; + } + } + // third best: incoming widget + if (i == fw ) + next->setFocus(); + } + } + } + if (reenableUpdates) + parent->setUpdatesEnabled(true); + emit currentChanged(index); +} + +int QStackedLayout::currentIndex() const +{ + Q_D(const QStackedLayout); + return d->index; +} + + +/*! + \fn void QStackedLayout::setCurrentWidget(QWidget *widget) + + Sets the current widget to be the specified \a widget. The new + current widget must already be contained in this stacked layout. + + \sa setCurrentIndex(), currentWidget() + */ +void QStackedLayout::setCurrentWidget(QWidget *widget) +{ + int index = indexOf(widget); + if (index == -1) { + qWarning("QStackedLayout::setCurrentWidget: Widget %p not contained in stack", widget); + return; + } + setCurrentIndex(index); +} + + +/*! + Returns the current widget, or 0 if there are no widgets in this + layout. + + \sa currentIndex(), setCurrentWidget() +*/ +QWidget *QStackedLayout::currentWidget() const +{ + Q_D(const QStackedLayout); + return d->index >= 0 ? d->list.at(d->index)->widget() : 0; +} + +/*! + Returns the widget at the given \a index, or 0 if there is no + widget at the given position. + + \sa currentWidget(), indexOf() +*/ +QWidget *QStackedLayout::widget(int index) const +{ + Q_D(const QStackedLayout); + if (index < 0 || index >= d->list.size()) + return 0; + return d->list.at(index)->widget(); +} + +/*! + \property QStackedLayout::count + \brief the number of widgets contained in the layout + + \sa currentIndex(), widget() +*/ +int QStackedLayout::count() const +{ + Q_D(const QStackedLayout); + return d->list.size(); +} + + +/*! + \reimp +*/ +void QStackedLayout::addItem(QLayoutItem *item) +{ + QWidget *widget = item->widget(); + if (widget) { + addWidget(widget); + delete item; + } else { + qWarning("QStackedLayout::addItem: Only widgets can be added"); + } +} + +/*! + \reimp +*/ +QSize QStackedLayout::sizeHint() const +{ + Q_D(const QStackedLayout); + QSize s(0, 0); + int n = d->list.count(); + + for (int i = 0; i < n; ++i) + if (QWidget *widget = d->list.at(i)->widget()) { + QSize ws(widget->sizeHint()); + if (widget->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored) + ws.setWidth(0); + if (widget->sizePolicy().verticalPolicy() == QSizePolicy::Ignored) + ws.setHeight(0); + s = s.expandedTo(ws); + } + return s; +} + +/*! + \reimp +*/ +QSize QStackedLayout::minimumSize() const +{ + Q_D(const QStackedLayout); + QSize s(0, 0); + int n = d->list.count(); + + for (int i = 0; i < n; ++i) + if (QWidget *widget = d->list.at(i)->widget()) + s = s.expandedTo(qSmartMinSize(widget)); + return s; +} + +/*! + \reimp +*/ +void QStackedLayout::setGeometry(const QRect &rect) +{ + Q_D(QStackedLayout); + switch (d->stackingMode) { + case StackOne: + if (QWidget *widget = currentWidget()) + widget->setGeometry(rect); + break; + case StackAll: + if (const int n = d->list.count()) + for (int i = 0; i < n; ++i) + if (QWidget *widget = d->list.at(i)->widget()) + widget->setGeometry(rect); + break; + } +} + +/*! + \enum QStackedLayout::StackingMode + \since 4.4 + + This enum specifies how the layout handles its child widgets + regarding their visibility. + + \value StackOne + Only the current widget is visible. This is the default. + + \value StackAll + All widgets are visible. The current widget is merely raised. +*/ + + +/*! + \property QStackedLayout::stackingMode + \brief determines the way visibility of child widgets are handled. + \since 4.4 + + The default value is StackOne. Setting the property to StackAll + allows you to make use of the layout for overlay widgets + that do additional drawing on top of other widgets, for example, + graphical editors. +*/ + +QStackedLayout::StackingMode QStackedLayout::stackingMode() const +{ + Q_D(const QStackedLayout); + return d->stackingMode; +} + +void QStackedLayout::setStackingMode(StackingMode stackingMode) +{ + Q_D(QStackedLayout); + if (d->stackingMode == stackingMode) + return; + d->stackingMode = stackingMode; + + const int n = d->list.count(); + if (n == 0) + return; + + switch (d->stackingMode) { + case StackOne: + if (const int idx = currentIndex()) + for (int i = 0; i < n; ++i) + if (QWidget *widget = d->list.at(i)->widget()) + widget->setVisible(i == idx); + break; + case StackAll: { // Turn overlay on: Make sure all widgets are the same size + QRect geometry; + if (const QWidget *widget = currentWidget()) + geometry = widget->geometry(); + for (int i = 0; i < n; ++i) + if (QWidget *widget = d->list.at(i)->widget()) { + if (!geometry.isNull()) + widget->setGeometry(geometry); + widget->setVisible(true); + } + } + break; + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qstackedlayout.h b/src/gui/kernel/qstackedlayout.h new file mode 100644 index 0000000000..49b80c6445 --- /dev/null +++ b/src/gui/kernel/qstackedlayout.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTACKEDLAYOUT_H +#define QSTACKEDLAYOUT_H + +#include <QtGui/qlayout.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStackedLayoutPrivate; + +class Q_GUI_EXPORT QStackedLayout : public QLayout +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QStackedLayout) + Q_ENUMS(StackingMode) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentChanged) + Q_PROPERTY(StackingMode stackingMode READ stackingMode WRITE setStackingMode) + QDOC_PROPERTY(int count READ count) + +public: + enum StackingMode { + StackOne, + StackAll + }; + + QStackedLayout(); + explicit QStackedLayout(QWidget *parent); + explicit QStackedLayout(QLayout *parentLayout); + ~QStackedLayout(); + + int addWidget(QWidget *w); + int insertWidget(int index, QWidget *w); + + QWidget *currentWidget() const; + int currentIndex() const; +#ifdef Q_NO_USING_KEYWORD + inline QWidget *widget() { return QLayout::widget(); } +#else + using QLayout::widget; +#endif + QWidget *widget(int) const; + int count() const; + + StackingMode stackingMode() const; + void setStackingMode(StackingMode stackingMode); + + // abstract virtual functions: + void addItem(QLayoutItem *item); + QSize sizeHint() const; + QSize minimumSize() const; + QLayoutItem *itemAt(int) const; + QLayoutItem *takeAt(int); + void setGeometry(const QRect &rect); + +Q_SIGNALS: + void widgetRemoved(int index); + void currentChanged(int index); + +public Q_SLOTS: + void setCurrentIndex(int index); + void setCurrentWidget(QWidget *w); + +private: + Q_DISABLE_COPY(QStackedLayout) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTACKEDLAYOUT_H diff --git a/src/gui/kernel/qstandardgestures.cpp b/src/gui/kernel/qstandardgestures.cpp new file mode 100644 index 0000000000..6338ef7afb --- /dev/null +++ b/src/gui/kernel/qstandardgestures.cpp @@ -0,0 +1,595 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstandardgestures_p.h" +#include "qgesture.h" +#include "qgesture_p.h" +#include "qevent.h" +#include "qwidget.h" +#include "qabstractscrollarea.h" +#include <qgraphicssceneevent.h> +#include "qdebug.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +QPanGestureRecognizer::QPanGestureRecognizer() +{ +} + +QGesture *QPanGestureRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) { +#if defined(Q_OS_WIN) && !defined(QT_NO_NATIVE_GESTURES) + // for scroll areas on Windows we want to use native gestures instead + if (!qobject_cast<QAbstractScrollArea *>(target->parent())) + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); +#else + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); +#endif + } + return new QPanGesture; +} + +QGestureRecognizer::Result QPanGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + QPanGesture *q = static_cast<QPanGesture *>(state); + QPanGesturePrivate *d = q->d_func(); + + const QTouchEvent *ev = static_cast<const QTouchEvent *>(event); + + QGestureRecognizer::Result result; + switch (event->type()) { + case QEvent::TouchBegin: { + result = QGestureRecognizer::MayBeGesture; + QTouchEvent::TouchPoint p = ev->touchPoints().at(0); + d->lastOffset = d->offset = QPointF(); + break; + } + case QEvent::TouchEnd: { + if (q->state() != Qt::NoGesture) { + if (ev->touchPoints().size() == 2) { + QTouchEvent::TouchPoint p1 = ev->touchPoints().at(0); + QTouchEvent::TouchPoint p2 = ev->touchPoints().at(1); + d->lastOffset = d->offset; + d->offset = + QPointF(p1.pos().x() - p1.startPos().x() + p2.pos().x() - p2.startPos().x(), + p1.pos().y() - p1.startPos().y() + p2.pos().y() - p2.startPos().y()) / 2; + } + result = QGestureRecognizer::FinishGesture; + } else { + result = QGestureRecognizer::CancelGesture; + } + break; + } + case QEvent::TouchUpdate: { + if (ev->touchPoints().size() >= 2) { + QTouchEvent::TouchPoint p1 = ev->touchPoints().at(0); + QTouchEvent::TouchPoint p2 = ev->touchPoints().at(1); + d->lastOffset = d->offset; + d->offset = + QPointF(p1.pos().x() - p1.startPos().x() + p2.pos().x() - p2.startPos().x(), + p1.pos().y() - p1.startPos().y() + p2.pos().y() - p2.startPos().y()) / 2; + if (d->offset.x() > 10 || d->offset.y() > 10 || + d->offset.x() < -10 || d->offset.y() < -10) { + q->setHotSpot(p1.startScreenPos()); + result = QGestureRecognizer::TriggerGesture; + } else { + result = QGestureRecognizer::MayBeGesture; + } + } + break; + } + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + return result; +} + +void QPanGestureRecognizer::reset(QGesture *state) +{ + QPanGesture *pan = static_cast<QPanGesture*>(state); + QPanGesturePrivate *d = pan->d_func(); + + d->lastOffset = d->offset = QPointF(); + d->acceleration = 0; + + QGestureRecognizer::reset(state); +} + + +// +// QPinchGestureRecognizer +// + +QPinchGestureRecognizer::QPinchGestureRecognizer() +{ +} + +QGesture *QPinchGestureRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) { + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new QPinchGesture; +} + +QGestureRecognizer::Result QPinchGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + QPinchGesture *q = static_cast<QPinchGesture *>(state); + QPinchGesturePrivate *d = q->d_func(); + + const QTouchEvent *ev = static_cast<const QTouchEvent *>(event); + + QGestureRecognizer::Result result; + + switch (event->type()) { + case QEvent::TouchBegin: { + result = QGestureRecognizer::MayBeGesture; + break; + } + case QEvent::TouchEnd: { + if (q->state() != Qt::NoGesture) { + result = QGestureRecognizer::FinishGesture; + } else { + result = QGestureRecognizer::CancelGesture; + } + break; + } + case QEvent::TouchUpdate: { + d->changeFlags = 0; + if (ev->touchPoints().size() == 2) { + QTouchEvent::TouchPoint p1 = ev->touchPoints().at(0); + QTouchEvent::TouchPoint p2 = ev->touchPoints().at(1); + + d->hotSpot = p1.screenPos(); + d->isHotSpotSet = true; + + QPointF centerPoint = (p1.screenPos() + p2.screenPos()) / 2.0; + if (d->isNewSequence) { + d->startPosition[0] = p1.screenPos(); + d->startPosition[1] = p2.screenPos(); + d->lastCenterPoint = centerPoint; + } else { + d->lastCenterPoint = d->centerPoint; + } + d->centerPoint = centerPoint; + + d->changeFlags |= QPinchGesture::CenterPointChanged; + + if (d->isNewSequence) { + d->scaleFactor = 1.0; + d->lastScaleFactor = 1.0; + } else { + d->lastScaleFactor = d->scaleFactor; + QLineF line(p1.screenPos(), p2.screenPos()); + QLineF lastLine(p1.lastScreenPos(), p2.lastScreenPos()); + d->scaleFactor = line.length() / lastLine.length(); + } + d->totalScaleFactor = d->totalScaleFactor * d->scaleFactor; + d->changeFlags |= QPinchGesture::ScaleFactorChanged; + + qreal angle = QLineF(p1.screenPos(), p2.screenPos()).angle(); + if (angle > 180) + angle -= 360; + qreal startAngle = QLineF(p1.startScreenPos(), p2.startScreenPos()).angle(); + if (startAngle > 180) + startAngle -= 360; + const qreal rotationAngle = startAngle - angle; + if (d->isNewSequence) + d->lastRotationAngle = 0.0; + else + d->lastRotationAngle = d->rotationAngle; + d->rotationAngle = rotationAngle; + d->totalRotationAngle += d->rotationAngle - d->lastRotationAngle; + d->changeFlags |= QPinchGesture::RotationAngleChanged; + + d->totalChangeFlags |= d->changeFlags; + d->isNewSequence = false; + result = QGestureRecognizer::TriggerGesture; + } else { + d->isNewSequence = true; + if (q->state() == Qt::NoGesture) + result = QGestureRecognizer::Ignore; + else + result = QGestureRecognizer::FinishGesture; + } + break; + } + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + return result; +} + +void QPinchGestureRecognizer::reset(QGesture *state) +{ + QPinchGesture *pinch = static_cast<QPinchGesture *>(state); + QPinchGesturePrivate *d = pinch->d_func(); + + d->totalChangeFlags = d->changeFlags = 0; + + d->startCenterPoint = d->lastCenterPoint = d->centerPoint = QPointF(); + d->totalScaleFactor = d->lastScaleFactor = d->scaleFactor = 1; + d->totalRotationAngle = d->lastRotationAngle = d->rotationAngle = 0; + + d->isNewSequence = true; + d->startPosition[0] = d->startPosition[1] = QPointF(); + + QGestureRecognizer::reset(state); +} + +// +// QSwipeGestureRecognizer +// + +QSwipeGestureRecognizer::QSwipeGestureRecognizer() +{ +} + +QGesture *QSwipeGestureRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) { + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new QSwipeGesture; +} + +QGestureRecognizer::Result QSwipeGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + QSwipeGesture *q = static_cast<QSwipeGesture *>(state); + QSwipeGesturePrivate *d = q->d_func(); + + const QTouchEvent *ev = static_cast<const QTouchEvent *>(event); + + QGestureRecognizer::Result result; + + switch (event->type()) { + case QEvent::TouchBegin: { + d->velocityValue = 1; + d->time.start(); + d->started = true; + result = QGestureRecognizer::MayBeGesture; + break; + } + case QEvent::TouchEnd: { + if (q->state() != Qt::NoGesture) { + result = QGestureRecognizer::FinishGesture; + } else { + result = QGestureRecognizer::CancelGesture; + } + break; + } + case QEvent::TouchUpdate: { + if (!d->started) + result = QGestureRecognizer::CancelGesture; + else if (ev->touchPoints().size() == 3) { + QTouchEvent::TouchPoint p1 = ev->touchPoints().at(0); + QTouchEvent::TouchPoint p2 = ev->touchPoints().at(1); + QTouchEvent::TouchPoint p3 = ev->touchPoints().at(2); + + if (d->lastPositions[0].isNull()) { + d->lastPositions[0] = p1.startScreenPos().toPoint(); + d->lastPositions[1] = p2.startScreenPos().toPoint(); + d->lastPositions[2] = p3.startScreenPos().toPoint(); + } + d->hotSpot = p1.screenPos(); + d->isHotSpotSet = true; + + int xDistance = (p1.screenPos().x() - d->lastPositions[0].x() + + p2.screenPos().x() - d->lastPositions[1].x() + + p3.screenPos().x() - d->lastPositions[2].x()) / 3; + int yDistance = (p1.screenPos().y() - d->lastPositions[0].y() + + p2.screenPos().y() - d->lastPositions[1].y() + + p3.screenPos().y() - d->lastPositions[2].y()) / 3; + + const int distance = xDistance >= yDistance ? xDistance : yDistance; + int elapsedTime = d->time.restart(); + if (!elapsedTime) + elapsedTime = 1; + d->velocityValue = 0.9 * d->velocityValue + distance / elapsedTime; + d->swipeAngle = QLineF(p1.startScreenPos(), p1.screenPos()).angle(); + + static const int MoveThreshold = 50; + if (xDistance > MoveThreshold || yDistance > MoveThreshold) { + // measure the distance to check if the direction changed + d->lastPositions[0] = p1.screenPos().toPoint(); + d->lastPositions[1] = p2.screenPos().toPoint(); + d->lastPositions[2] = p3.screenPos().toPoint(); + QSwipeGesture::SwipeDirection horizontal = + xDistance > 0 ? QSwipeGesture::Right : QSwipeGesture::Left; + QSwipeGesture::SwipeDirection vertical = + yDistance > 0 ? QSwipeGesture::Down : QSwipeGesture::Up; + if (d->verticalDirection == QSwipeGesture::NoDirection) + d->verticalDirection = vertical; + if (d->horizontalDirection == QSwipeGesture::NoDirection) + d->horizontalDirection = horizontal; + if (d->verticalDirection != vertical || d->horizontalDirection != horizontal) { + // the user has changed the direction! + result = QGestureRecognizer::CancelGesture; + } + result = QGestureRecognizer::TriggerGesture; + } else { + if (q->state() != Qt::NoGesture) + result = QGestureRecognizer::TriggerGesture; + else + result = QGestureRecognizer::MayBeGesture; + } + } else if (ev->touchPoints().size() > 3) { + result = QGestureRecognizer::CancelGesture; + } else { // less than 3 touch points + if (d->started && (ev->touchPointStates() & Qt::TouchPointPressed)) + result = QGestureRecognizer::CancelGesture; + else if (d->started) + result = QGestureRecognizer::Ignore; + else + result = QGestureRecognizer::MayBeGesture; + } + break; + } + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + return result; +} + +void QSwipeGestureRecognizer::reset(QGesture *state) +{ + QSwipeGesture *q = static_cast<QSwipeGesture *>(state); + QSwipeGesturePrivate *d = q->d_func(); + + d->verticalDirection = d->horizontalDirection = QSwipeGesture::NoDirection; + d->swipeAngle = 0; + + d->lastPositions[0] = d->lastPositions[1] = d->lastPositions[2] = QPoint(); + d->started = false; + d->velocityValue = 0; + d->time.invalidate(); + + QGestureRecognizer::reset(state); +} + +// +// QTapGestureRecognizer +// + +QTapGestureRecognizer::QTapGestureRecognizer() +{ +} + +QGesture *QTapGestureRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) { + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new QTapGesture; +} + +QGestureRecognizer::Result QTapGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + QTapGesture *q = static_cast<QTapGesture *>(state); + QTapGesturePrivate *d = q->d_func(); + + const QTouchEvent *ev = static_cast<const QTouchEvent *>(event); + + QGestureRecognizer::Result result = QGestureRecognizer::CancelGesture; + + switch (event->type()) { + case QEvent::TouchBegin: { + d->position = ev->touchPoints().at(0).pos(); + q->setHotSpot(ev->touchPoints().at(0).screenPos()); + result = QGestureRecognizer::TriggerGesture; + break; + } + case QEvent::TouchUpdate: + case QEvent::TouchEnd: { + if (q->state() != Qt::NoGesture && ev->touchPoints().size() == 1) { + QTouchEvent::TouchPoint p = ev->touchPoints().at(0); + QPoint delta = p.pos().toPoint() - p.startPos().toPoint(); + enum { TapRadius = 40 }; + if (delta.manhattanLength() <= TapRadius) { + if (event->type() == QEvent::TouchEnd) + result = QGestureRecognizer::FinishGesture; + else + result = QGestureRecognizer::TriggerGesture; + } + } + break; + } + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + result = QGestureRecognizer::Ignore; + break; + default: + result = QGestureRecognizer::Ignore; + break; + } + return result; +} + +void QTapGestureRecognizer::reset(QGesture *state) +{ + QTapGesture *q = static_cast<QTapGesture *>(state); + QTapGesturePrivate *d = q->d_func(); + + d->position = QPointF(); + + QGestureRecognizer::reset(state); +} + +// +// QTapAndHoldGestureRecognizer +// + +QTapAndHoldGestureRecognizer::QTapAndHoldGestureRecognizer() +{ +} + +QGesture *QTapAndHoldGestureRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) { + static_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); + } + return new QTapAndHoldGesture; +} + +QGestureRecognizer::Result +QTapAndHoldGestureRecognizer::recognize(QGesture *state, QObject *object, + QEvent *event) +{ + QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state); + QTapAndHoldGesturePrivate *d = q->d_func(); + + if (object == state && event->type() == QEvent::Timer) { + q->killTimer(d->timerId); + d->timerId = 0; + return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint; + } + + const QTouchEvent *ev = static_cast<const QTouchEvent *>(event); + const QMouseEvent *me = static_cast<const QMouseEvent *>(event); +#ifndef QT_NO_GRAPHICSVIEW + const QGraphicsSceneMouseEvent *gsme = static_cast<const QGraphicsSceneMouseEvent *>(event); +#endif + + enum { TapRadius = 40 }; + + switch (event->type()) { +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMousePress: + d->position = gsme->screenPos(); + q->setHotSpot(d->position); + if (d->timerId) + q->killTimer(d->timerId); + d->timerId = q->startTimer(QTapAndHoldGesturePrivate::Timeout); + return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout +#endif + case QEvent::MouseButtonPress: + d->position = me->globalPos(); + q->setHotSpot(d->position); + if (d->timerId) + q->killTimer(d->timerId); + d->timerId = q->startTimer(QTapAndHoldGesturePrivate::Timeout); + return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout + case QEvent::TouchBegin: + d->position = ev->touchPoints().at(0).startScreenPos(); + q->setHotSpot(d->position); + if (d->timerId) + q->killTimer(d->timerId); + d->timerId = q->startTimer(QTapAndHoldGesturePrivate::Timeout); + return QGestureRecognizer::MayBeGesture; // we don't show a sign of life until the timeout +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseRelease: +#endif + case QEvent::MouseButtonRelease: + case QEvent::TouchEnd: + return QGestureRecognizer::CancelGesture; // get out of the MayBeGesture state + case QEvent::TouchUpdate: + if (d->timerId && ev->touchPoints().size() == 1) { + QTouchEvent::TouchPoint p = ev->touchPoints().at(0); + QPoint delta = p.pos().toPoint() - p.startPos().toPoint(); + if (delta.manhattanLength() <= TapRadius) + return QGestureRecognizer::MayBeGesture; + } + return QGestureRecognizer::CancelGesture; + case QEvent::MouseMove: { + QPoint delta = me->globalPos() - d->position.toPoint(); + if (d->timerId && delta.manhattanLength() <= TapRadius) + return QGestureRecognizer::MayBeGesture; + return QGestureRecognizer::CancelGesture; + } +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseMove: { + QPoint delta = gsme->screenPos() - d->position.toPoint(); + if (d->timerId && delta.manhattanLength() <= TapRadius) + return QGestureRecognizer::MayBeGesture; + return QGestureRecognizer::CancelGesture; + } +#endif + default: + return QGestureRecognizer::Ignore; + } +} + +void QTapAndHoldGestureRecognizer::reset(QGesture *state) +{ + QTapAndHoldGesture *q = static_cast<QTapAndHoldGesture *>(state); + QTapAndHoldGesturePrivate *d = q->d_func(); + + d->position = QPointF(); + if (d->timerId) + q->killTimer(d->timerId); + d->timerId = 0; + + QGestureRecognizer::reset(state); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/kernel/qstandardgestures_p.h b/src/gui/kernel/qstandardgestures_p.h new file mode 100644 index 0000000000..b3c5002565 --- /dev/null +++ b/src/gui/kernel/qstandardgestures_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTANDARDGESTURES_P_H +#define QSTANDARDGESTURES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgesturerecognizer.h" +#include "private/qgesture_p.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QPanGestureRecognizer : public QGestureRecognizer +{ +public: + QPanGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +class QPinchGestureRecognizer : public QGestureRecognizer +{ +public: + QPinchGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +class QSwipeGestureRecognizer : public QGestureRecognizer +{ +public: + QSwipeGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +class QTapGestureRecognizer : public QGestureRecognizer +{ +public: + QTapGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +class QTapAndHoldGestureRecognizer : public QGestureRecognizer +{ +public: + QTapAndHoldGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QSTANDARDGESTURES_P_H diff --git a/src/gui/kernel/qt_cocoa_helpers_mac.mm b/src/gui/kernel/qt_cocoa_helpers_mac.mm new file mode 100644 index 0000000000..32123ee682 --- /dev/null +++ b/src/gui/kernel/qt_cocoa_helpers_mac.mm @@ -0,0 +1,1824 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include <private/qcore_mac_p.h> +#include <qaction.h> +#include <qwidget.h> +#include <qdesktopwidget.h> +#include <qevent.h> +#include <qpixmapcache.h> +#include <qvarlengtharray.h> +#include <private/qevent_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qt_mac_p.h> +#include <private/qapplication_p.h> +#include <private/qcocoaapplication_mac_p.h> +#include <private/qcocoawindow_mac_p.h> +#include <private/qcocoaview_mac_p.h> +#include <private/qkeymapper_p.h> +#include <private/qwidget_p.h> +#include <private/qcocoawindow_mac_p.h> + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA +// Cmd + left mousebutton should produce a right button +// press (mainly for mac users with one-button mice): +static bool qt_leftButtonIsRightButton = false; +#endif + +Q_GLOBAL_STATIC(QMacWindowFader, macwindowFader); + +QMacWindowFader::QMacWindowFader() + : m_duration(0.250) +{ +} + +QMacWindowFader *QMacWindowFader::currentFader() +{ + return macwindowFader(); +} + +void QMacWindowFader::registerWindowToFade(QWidget *window) +{ + m_windowsToFade.append(window); +} + +void QMacWindowFader::performFade() +{ + const QWidgetList myWidgetsToFade = m_windowsToFade; + const int widgetCount = myWidgetsToFade.count(); +#if QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [NSAnimationContext beginGrouping]; + [[NSAnimationContext currentContext] setDuration:NSTimeInterval(m_duration)]; +#endif + + for (int i = 0; i < widgetCount; ++i) { + QWidget *widget = m_windowsToFade.at(i); + OSWindowRef window = qt_mac_window_for(widget); +#if QT_MAC_USE_COCOA + [[window animator] setAlphaValue:0.0]; + QTimer::singleShot(qRound(m_duration * 1000), widget, SLOT(hide())); +#else + TransitionWindowOptions options = {0, m_duration, 0, 0}; + TransitionWindowWithOptions(window, kWindowFadeTransitionEffect, kWindowHideTransitionAction, + 0, 1, &options); +#endif + } +#if QT_MAC_USE_COCOA + [NSAnimationContext endGrouping]; +#endif + m_duration = 0.250; + m_windowsToFade.clear(); +} + +extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); // qapplication.cpp; +extern QWidget * mac_mouse_grabber; +extern QWidget *qt_button_down; //qapplication_mac.cpp +extern QPointer<QWidget> qt_last_mouse_receiver; +extern OSViewRef qt_mac_effectiveview_for(const QWidget *w); +extern void qt_mac_updateCursorWithWidgetUnderMouse(QWidget *widgetUnderMouse); // qcursor_mac.mm + +void macWindowFade(void * /*OSWindowRef*/ window, float durationSeconds) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#endif + OSWindowRef wnd = static_cast<OSWindowRef>(window); + if (wnd) { + QWidget *widget; +#if QT_MAC_USE_COCOA + widget = [wnd QT_MANGLE_NAMESPACE(qt_qwidget)]; +#else + const UInt32 kWidgetCreatorQt = kEventClassQt; + enum { + kWidgetPropertyQWidget = 'QWId' //QWidget * + }; + if (GetWindowProperty(static_cast<WindowRef>(window), kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(widget), 0, &widget) != noErr) + widget = 0; +#endif + if (widget) { + QMacWindowFader::currentFader()->setFadeDuration(durationSeconds); + QMacWindowFader::currentFader()->registerWindowToFade(widget); + QMacWindowFader::currentFader()->performFade(); + } + } +} +struct dndenum_mapper +{ + NSDragOperation mac_code; + Qt::DropAction qt_code; + bool Qt2Mac; +}; + +#if defined(QT_MAC_USE_COCOA) && defined(__OBJC__) + +static dndenum_mapper dnd_enums[] = { + { NSDragOperationLink, Qt::LinkAction, true }, + { NSDragOperationMove, Qt::MoveAction, true }, + { NSDragOperationCopy, Qt::CopyAction, true }, + { NSDragOperationGeneric, Qt::CopyAction, false }, + { NSDragOperationEvery, Qt::ActionMask, false }, + { NSDragOperationNone, Qt::IgnoreAction, false } +}; + +NSDragOperation qt_mac_mapDropAction(Qt::DropAction action) +{ + for (int i=0; dnd_enums[i].qt_code; i++) { + if (dnd_enums[i].Qt2Mac && (action & dnd_enums[i].qt_code)) { + return dnd_enums[i].mac_code; + } + } + return NSDragOperationNone; +} + +NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions) +{ + NSDragOperation nsActions = NSDragOperationNone; + for (int i=0; dnd_enums[i].qt_code; i++) { + if (dnd_enums[i].Qt2Mac && (actions & dnd_enums[i].qt_code)) + nsActions |= dnd_enums[i].mac_code; + } + return nsActions; +} + +Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions) +{ + Qt::DropAction action = Qt::IgnoreAction; + for (int i=0; dnd_enums[i].mac_code; i++) { + if (nsActions & dnd_enums[i].mac_code) + return dnd_enums[i].qt_code; + } + return action; +} + +Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions) +{ + Qt::DropActions actions = Qt::IgnoreAction; + for (int i=0; dnd_enums[i].mac_code; i++) { + if (nsActions & dnd_enums[i].mac_code) + actions |= dnd_enums[i].qt_code; + } + return actions; +} + +Q_GLOBAL_STATIC(DnDParams, currentDnDParameters); +DnDParams *macCurrentDnDParameters() +{ + return currentDnDParameters(); +} +#endif + +bool macWindowIsTextured( void * /*OSWindowRef*/ window ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + return ( [wnd styleMask] & NSTexturedBackgroundWindowMask ) ? true : false; +#else + WindowAttributes currentAttributes; + GetWindowAttributes(wnd, ¤tAttributes); + return (currentAttributes & kWindowMetalAttribute) ? true : false; +#endif +} + +void macWindowToolbarShow(const QWidget *widget, bool show ) +{ + OSWindowRef wnd = qt_mac_window_for(widget); +#if QT_MAC_USE_COCOA + if (NSToolbar *toolbar = [wnd toolbar]) { + QMacCocoaAutoReleasePool pool; + if (show != [toolbar isVisible]) { + [toolbar setVisible:show]; + } else { + // The toolbar may be in sync, but we are not, update our framestrut. + qt_widget_private(const_cast<QWidget *>(widget))->updateFrameStrut(); + } + } +#else + qt_widget_private(const_cast<QWidget *>(widget))->updateFrameStrut(); + ShowHideWindowToolbar(wnd, show, false); +#endif +} + + +void macWindowToolbarSet( void * /*OSWindowRef*/ window, void *toolbarRef ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd setToolbar:static_cast<NSToolbar *>(toolbarRef)]; +#else + SetWindowToolbar(wnd, static_cast<HIToolbarRef>(toolbarRef)); +#endif +} + +bool macWindowToolbarIsVisible( void * /*OSWindowRef*/ window ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + if (NSToolbar *toolbar = [wnd toolbar]) + return [toolbar isVisible]; + return false; +#else + return IsWindowToolbarVisible(wnd); +#endif +} + +void macWindowSetHasShadow( void * /*OSWindowRef*/ window, bool hasShadow ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd setHasShadow:BOOL(hasShadow)]; +#else + if (hasShadow) + ChangeWindowAttributes(wnd, 0, kWindowNoShadowAttribute); + else + ChangeWindowAttributes(wnd, kWindowNoShadowAttribute, 0); +#endif +} + +void macWindowFlush(void * /*OSWindowRef*/ window) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd flushWindowIfNeeded]; +#else + HIWindowFlush(wnd); +#endif +} + +void * /*NSImage */qt_mac_create_nsimage(const QPixmap &pm) +{ + QMacCocoaAutoReleasePool pool; + if(QCFType<CGImageRef> image = pm.toMacCGImageRef()) { + NSImage *newImage = 0; + NSRect imageRect = NSMakeRect(0.0, 0.0, CGImageGetWidth(image), CGImageGetHeight(image)); + newImage = [[NSImage alloc] initWithSize:imageRect.size]; + [newImage lockFocus]; + { + CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image); + } + [newImage unlockFocus]; + return newImage; + } + return 0; +} + +void qt_mac_update_mouseTracking(QWidget *widget) +{ +#ifdef QT_MAC_USE_COCOA + [qt_mac_nativeview_for(widget) updateTrackingAreas]; +#else + Q_UNUSED(widget); +#endif +} + +OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage) +{ + // Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev) + OSStatus err = noErr; + + require_action(inContext != NULL, InvalidContext, err = paramErr); + require_action(inBounds != NULL, InvalidBounds, err = paramErr); + require_action(inImage != NULL, InvalidImage, err = paramErr); + + CGContextSaveGState( inContext ); + CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds)); + CGContextScaleCTM(inContext, 1, -1); + + CGContextDrawImage(inContext, *inBounds, inImage); + + CGContextRestoreGState(inContext); +InvalidImage: +InvalidBounds: +InvalidContext: + return err; +} + +bool qt_mac_checkForNativeSizeGrip(const QWidget *widget) +{ +#ifndef QT_MAC_USE_COCOA + OSViewRef nativeSizeGrip = 0; + HIViewFindByID(HIViewGetRoot(HIViewGetWindow(HIViewRef(widget->winId()))), kHIViewWindowGrowBoxID, &nativeSizeGrip); + return (nativeSizeGrip != 0); +#else + return [[reinterpret_cast<NSView *>(widget->effectiveWinId()) window] showsResizeIndicator]; +#endif +} +struct qt_mac_enum_mapper +{ + int mac_code; + int qt_code; +#if defined(DEBUG_MOUSE_MAPS) +# define QT_MAC_MAP_ENUM(x) x, #x + const char *desc; +#else +# define QT_MAC_MAP_ENUM(x) x +#endif +}; + +//mouse buttons +static qt_mac_enum_mapper qt_mac_mouse_symbols[] = { +{ kEventMouseButtonPrimary, QT_MAC_MAP_ENUM(Qt::LeftButton) }, +{ kEventMouseButtonSecondary, QT_MAC_MAP_ENUM(Qt::RightButton) }, +{ kEventMouseButtonTertiary, QT_MAC_MAP_ENUM(Qt::MidButton) }, +{ 4, QT_MAC_MAP_ENUM(Qt::XButton1) }, +{ 5, QT_MAC_MAP_ENUM(Qt::XButton2) }, +{ 0, QT_MAC_MAP_ENUM(0) } +}; +Qt::MouseButtons qt_mac_get_buttons(int buttons) +{ +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: **Mapping buttons: %d (0x%04x)", buttons, buttons); +#endif + Qt::MouseButtons ret = Qt::NoButton; + for(int i = 0; qt_mac_mouse_symbols[i].qt_code; i++) { + if (buttons & (0x01<<(qt_mac_mouse_symbols[i].mac_code-1))) { +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: got button: %s", qt_mac_mouse_symbols[i].desc); +#endif + ret |= Qt::MouseButtons(qt_mac_mouse_symbols[i].qt_code); + } + } + return ret; +} +Qt::MouseButton qt_mac_get_button(EventMouseButton button) +{ +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: **Mapping button: %d (0x%04x)", button, button); +#endif + Qt::MouseButtons ret = 0; + for(int i = 0; qt_mac_mouse_symbols[i].qt_code; i++) { + if (button == qt_mac_mouse_symbols[i].mac_code) { +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: got button: %s", qt_mac_mouse_symbols[i].desc); +#endif + return Qt::MouseButton(qt_mac_mouse_symbols[i].qt_code); + } + } + return Qt::NoButton; +} + +void macSendToolbarChangeEvent(QWidget *widget) +{ + QToolBarChangeEvent ev(!(GetCurrentKeyModifiers() & cmdKey)); + qt_sendSpontaneousEvent(widget, &ev); +} + +Q_GLOBAL_STATIC(QMacTabletHash, tablet_hash) +QMacTabletHash *qt_mac_tablet_hash() +{ + return tablet_hash(); +} + +#ifdef QT_MAC_USE_COCOA + +// Clears the QWidget pointer that each QCocoaView holds. +void qt_mac_clearCocoaViewQWidgetPointers(QWidget *widget) +{ + QT_MANGLE_NAMESPACE(QCocoaView) *cocoaView = reinterpret_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(qt_mac_nativeview_for(widget)); + if (cocoaView && [cocoaView respondsToSelector:@selector(qt_qwidget)]) { + [cocoaView qt_clearQWidget]; + } +} + +void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent) +{ + NSEvent *proximityEvent = static_cast<NSEvent *>(tabletEvent); + // simply construct a Carbon proximity record and handle it all in one spot. + TabletProximityRec carbonProximityRec = { [proximityEvent vendorID], + [proximityEvent tabletID], + [proximityEvent pointingDeviceID], + [proximityEvent deviceID], + [proximityEvent systemTabletID], + [proximityEvent vendorPointingDeviceType], + [proximityEvent pointingDeviceSerialNumber], + [proximityEvent uniqueID], + [proximityEvent capabilityMask], + [proximityEvent pointingDeviceType], + [proximityEvent isEnteringProximity] }; + qt_dispatchTabletProximityEvent(carbonProximityRec); +} +#endif // QT_MAC_USE_COCOA + +void qt_dispatchTabletProximityEvent(const ::TabletProximityRec &proxRec) +{ + QTabletDeviceData proximityDevice; + proximityDevice.tabletUniqueID = proxRec.uniqueID; + proximityDevice.capabilityMask = proxRec.capabilityMask; + + switch (proxRec.pointerType) { + case NSUnknownPointingDevice: + default: + proximityDevice.tabletPointerType = QTabletEvent::UnknownPointer; + break; + case NSPenPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Pen; + break; + case NSCursorPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Cursor; + break; + case NSEraserPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Eraser; + break; + } + uint bits = proxRec.vendorPointerType; + if (bits == 0 && proximityDevice.tabletUniqueID != 0) { + // Fallback. It seems that the driver doesn't always include all the information. + // High-End Wacom devices store their "type" in the uper bits of the Unique ID. + // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. + bits = proximityDevice.tabletUniqueID >> 32; + } + // Defined in the "EN0056-NxtGenImpGuideX" + // on Wacom's Developer Website (www.wacomeng.com) + if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { + proximityDevice.tabletDeviceType = QTabletEvent::Stylus; + } else { + switch (bits & 0x0F06) { + case 0x0802: + proximityDevice.tabletDeviceType = QTabletEvent::Stylus; + break; + case 0x0902: + proximityDevice.tabletDeviceType = QTabletEvent::Airbrush; + break; + case 0x0004: + proximityDevice.tabletDeviceType = QTabletEvent::FourDMouse; + break; + case 0x0006: + proximityDevice.tabletDeviceType = QTabletEvent::Puck; + break; + case 0x0804: + proximityDevice.tabletDeviceType = QTabletEvent::RotationStylus; + break; + default: + proximityDevice.tabletDeviceType = QTabletEvent::NoDevice; + } + } + // The deviceID is "unique" while in the proximity, it's a key that we can use for + // linking up TabletDeviceData to an event (especially if there are two devices in action). + bool entering = proxRec.enterProximity; + if (entering) { + qt_mac_tablet_hash()->insert(proxRec.deviceID, proximityDevice); + } else { + qt_mac_tablet_hash()->remove(proxRec.deviceID); + } + + QTabletEvent qtabletProximity(entering ? QEvent::TabletEnterProximity + : QEvent::TabletLeaveProximity, + QPoint(), QPoint(), QPointF(), proximityDevice.tabletDeviceType, + proximityDevice.tabletPointerType, 0., 0, 0, 0., 0., 0, 0, + proximityDevice.tabletUniqueID); + + qt_sendSpontaneousEvent(qApp, &qtabletProximity); +} + +// Use this method to keep all the information in the TextSegment. As long as it is ordered +// we are in OK shape, and we can influence that ourselves. +struct KeyPair +{ + QChar cocoaKey; + Qt::Key qtKey; +}; + +bool operator==(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey == qchar; +} + +bool operator<(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey < qchar; +} + +bool operator<(QChar qchar, const KeyPair &entry) +{ + return qchar < entry.cocoaKey; +} + +bool operator<(const Qt::Key &key, const KeyPair &entry) +{ + return key < entry.qtKey; +} + +bool operator<(const KeyPair &entry, const Qt::Key &key) +{ + return entry.qtKey < key; +} + +static bool qtKey2CocoaKeySortLessThan(const KeyPair &entry1, const KeyPair &entry2) +{ + return entry1.qtKey < entry2.qtKey; +} + +static const int NumEntries = 59; +static const KeyPair entries[NumEntries] = { + { NSEnterCharacter, Qt::Key_Enter }, + { NSBackspaceCharacter, Qt::Key_Backspace }, + { NSTabCharacter, Qt::Key_Tab }, + { NSNewlineCharacter, Qt::Key_Return }, + { NSCarriageReturnCharacter, Qt::Key_Return }, + { NSBackTabCharacter, Qt::Key_Backtab }, + { kEscapeCharCode, Qt::Key_Escape }, + // Cocoa sends us delete when pressing backspace! + // (NB when we reverse this list in qtKey2CocoaKey, there + // will be two indices of Qt::Key_Backspace. But is seems to work + // ok for menu shortcuts (which uses that function): + { NSDeleteCharacter, Qt::Key_Backspace }, + { NSUpArrowFunctionKey, Qt::Key_Up }, + { NSDownArrowFunctionKey, Qt::Key_Down }, + { NSLeftArrowFunctionKey, Qt::Key_Left }, + { NSRightArrowFunctionKey, Qt::Key_Right }, + { NSF1FunctionKey, Qt::Key_F1 }, + { NSF2FunctionKey, Qt::Key_F2 }, + { NSF3FunctionKey, Qt::Key_F3 }, + { NSF4FunctionKey, Qt::Key_F4 }, + { NSF5FunctionKey, Qt::Key_F5 }, + { NSF6FunctionKey, Qt::Key_F6 }, + { NSF7FunctionKey, Qt::Key_F7 }, + { NSF8FunctionKey, Qt::Key_F8 }, + { NSF9FunctionKey, Qt::Key_F8 }, + { NSF10FunctionKey, Qt::Key_F10 }, + { NSF11FunctionKey, Qt::Key_F11 }, + { NSF12FunctionKey, Qt::Key_F12 }, + { NSF13FunctionKey, Qt::Key_F13 }, + { NSF14FunctionKey, Qt::Key_F14 }, + { NSF15FunctionKey, Qt::Key_F15 }, + { NSF16FunctionKey, Qt::Key_F16 }, + { NSF17FunctionKey, Qt::Key_F17 }, + { NSF18FunctionKey, Qt::Key_F18 }, + { NSF19FunctionKey, Qt::Key_F19 }, + { NSF20FunctionKey, Qt::Key_F20 }, + { NSF21FunctionKey, Qt::Key_F21 }, + { NSF22FunctionKey, Qt::Key_F22 }, + { NSF23FunctionKey, Qt::Key_F23 }, + { NSF24FunctionKey, Qt::Key_F24 }, + { NSF25FunctionKey, Qt::Key_F25 }, + { NSF26FunctionKey, Qt::Key_F26 }, + { NSF27FunctionKey, Qt::Key_F27 }, + { NSF28FunctionKey, Qt::Key_F28 }, + { NSF29FunctionKey, Qt::Key_F29 }, + { NSF30FunctionKey, Qt::Key_F30 }, + { NSF31FunctionKey, Qt::Key_F31 }, + { NSF32FunctionKey, Qt::Key_F32 }, + { NSF33FunctionKey, Qt::Key_F33 }, + { NSF34FunctionKey, Qt::Key_F34 }, + { NSF35FunctionKey, Qt::Key_F35 }, + { NSInsertFunctionKey, Qt::Key_Insert }, + { NSDeleteFunctionKey, Qt::Key_Delete }, + { NSHomeFunctionKey, Qt::Key_Home }, + { NSEndFunctionKey, Qt::Key_End }, + { NSPageUpFunctionKey, Qt::Key_PageUp }, + { NSPageDownFunctionKey, Qt::Key_PageDown }, + { NSPrintScreenFunctionKey, Qt::Key_Print }, + { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, + { NSPauseFunctionKey, Qt::Key_Pause }, + { NSSysReqFunctionKey, Qt::Key_SysReq }, + { NSMenuFunctionKey, Qt::Key_Menu }, + { NSHelpFunctionKey, Qt::Key_Help }, +}; +static const KeyPair * const end = entries + NumEntries; + +QChar qtKey2CocoaKey(Qt::Key key) +{ + // The first time this function is called, create a reverse + // looup table sorted on Qt Key rather than Cocoa key: + static QVector<KeyPair> rev_entries(NumEntries); + static bool mustInit = true; + if (mustInit){ + mustInit = false; + for (int i=0; i<NumEntries; ++i) + rev_entries[i] = entries[i]; + qSort(rev_entries.begin(), rev_entries.end(), qtKey2CocoaKeySortLessThan); + } + const QVector<KeyPair>::iterator i + = qBinaryFind(rev_entries.begin(), rev_entries.end(), key); + if (i == rev_entries.end()) + return QChar(); + return i->cocoaKey; +} + +#ifdef QT_MAC_USE_COCOA +static Qt::Key cocoaKey2QtKey(QChar keyCode) +{ + const KeyPair *i = qBinaryFind(entries, end, keyCode); + if (i == end) + return Qt::Key(keyCode.unicode()); + return i->qtKey; +} + +Qt::KeyboardModifiers qt_cocoaModifiers2QtModifiers(ulong modifierFlags) +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSShiftKeyMask) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSControlKeyMask) + qtMods |= Qt::MetaModifier; + if (modifierFlags & NSAlternateKeyMask) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSCommandKeyMask) + qtMods |= Qt::ControlModifier; + if (modifierFlags & NSNumericPadKeyMask) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + int len = [string length]; + if (len) { + QVarLengthArray <unichar, 10> characters(len); + bool changed = false; + for (int i = 0; i<len; i++) { + characters[i] = [string characterAtIndex:i]; + // check if they belong to key codes in private unicode range + // currently we need to handle only the NSDeleteFunctionKey + if (characters[i] == NSDeleteFunctionKey) { + characters[i] = NSDeleteCharacter; + changed = true; + } + } + if (changed) + return [NSString stringWithCharacters:characters.data() length:len]; + } + return string; +} + +Qt::KeyboardModifiers qt_cocoaDragOperation2QtModifiers(uint dragOperations) +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (dragOperations & NSDragOperationLink) + qtMods |= Qt::MetaModifier; + if (dragOperations & NSDragOperationGeneric) + qtMods |= Qt::ControlModifier; + if (dragOperations & NSDragOperationCopy) + qtMods |= Qt::AltModifier; + return qtMods; +} + +static inline QEvent::Type cocoaEvent2QtEvent(NSUInteger eventType) +{ + // Handle the trivial cases that can be determined from the type. + switch (eventType) { + case NSKeyDown: + return QEvent::KeyPress; + case NSKeyUp: + return QEvent::KeyRelease; + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + return QEvent::MouseButtonPress; + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + return QEvent::MouseButtonRelease; + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSOtherMouseDragged: + return QEvent::MouseMove; + case NSScrollWheel: + return QEvent::Wheel; + } + return QEvent::None; +} + +static bool mustUseCocoaKeyEvent() +{ + QCFType<TISInputSourceRef> source = TISCopyCurrentKeyboardInputSource(); + return TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) == 0; +} + +bool qt_dispatchKeyEventWithCocoa(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent) +{ + NSEvent *event = static_cast<NSEvent *>(keyEvent); + NSString *keyChars = [event charactersIgnoringModifiers]; + int keyLength = [keyChars length]; + if (keyLength == 0) + return false; // Dead Key, nothing to do! + bool ignoreText = false; + Qt::Key qtKey = Qt::Key_unknown; + if (keyLength == 1) { + QChar ch([keyChars characterAtIndex:0]); + if (ch.isLower()) + ch = ch.toUpper(); + qtKey = cocoaKey2QtKey(ch); + // Do not set the text for Function-Key Unicodes characters (0xF700–0xF8FF). + ignoreText = (ch.unicode() >= 0xF700 && ch.unicode() <= 0xF8FF); + } + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + QString text; + + // To quote from the Carbon port: This is actually wrong--but it is the best that + // can be done for now because of the Control/Meta mapping issues + // (we always get text on the Mac) + if (!ignoreText && !(keyMods & (Qt::ControlModifier | Qt::MetaModifier))) + text = QCFString::toQString(reinterpret_cast<CFStringRef>(keyChars)); + + UInt32 macScanCode = 1; + QKeyEventEx ke(cocoaEvent2QtEvent([event type]), qtKey, keyMods, text, [event isARepeat], qMax(1, keyLength), + macScanCode, [event keyCode], [event modifierFlags]); + return qt_sendSpontaneousEvent(widgetToGetEvent, &ke) && ke.isAccepted(); +} +#endif + +Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) +{ + if (buttonNum == 0) + return Qt::LeftButton; + if (buttonNum == 1) + return Qt::RightButton; + if (buttonNum == 2) + return Qt::MidButton; + if (buttonNum == 3) + return Qt::XButton1; + if (buttonNum == 4) + return Qt::XButton2; + return Qt::NoButton; +} + +bool qt_dispatchKeyEvent(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(keyEvent); + Q_UNUSED(widgetToGetEvent); + return false; +#else + NSEvent *event = static_cast<NSEvent *>(keyEvent); + EventRef key_event = static_cast<EventRef>(const_cast<void *>([event eventRef])); + Q_ASSERT(key_event); + unsigned int info = 0; + + if ([event type] == NSKeyDown) { + NSString *characters = [event characters]; + if ([characters length]) { + unichar value = [characters characterAtIndex:0]; + qt_keymapper_private()->updateKeyMap(0, key_event, (void *)&value); + info = value; + } + } + + if (qt_mac_sendMacEventToWidget(widgetToGetEvent, key_event)) + return true; + + if (mustUseCocoaKeyEvent()) + return qt_dispatchKeyEventWithCocoa(keyEvent, widgetToGetEvent); + + bool consumed = qt_keymapper_private()->translateKeyEvent(widgetToGetEvent, 0, key_event, &info, true); + return consumed && (info != 0); +#endif +} + +void qt_dispatchModifiersChanged(void * /*NSEvent * */flagsChangedEvent, QWidget *widgetToGetEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(flagsChangedEvent); + Q_UNUSED(widgetToGetEvent); +#else + UInt32 modifiers = 0; + // Sync modifiers with Qt + NSEvent *event = static_cast<NSEvent *>(flagsChangedEvent); + EventRef key_event = static_cast<EventRef>(const_cast<void *>([event eventRef])); + Q_ASSERT(key_event); + GetEventParameter(key_event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(modifiers), 0, &modifiers); + extern void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object); + qt_mac_send_modifiers_changed(modifiers, widgetToGetEvent); +#endif +} + +QPointF flipPoint(const NSPoint &p) +{ + return QPointF(p.x, flipYCoordinate(p.y)); +} + +NSPoint flipPoint(const QPoint &p) +{ + return NSMakePoint(p.x(), flipYCoordinate(p.y())); +} + +NSPoint flipPoint(const QPointF &p) +{ + return NSMakePoint(p.x(), flipYCoordinate(p.y())); +} + +#if QT_MAC_USE_COCOA && __OBJC__ + +void qt_mac_handleNonClientAreaMouseEvent(NSWindow *window, NSEvent *event) +{ + QWidget *widgetToGetEvent = [window QT_MANGLE_NAMESPACE(qt_qwidget)]; + if (widgetToGetEvent == 0) + return; + + NSEventType evtType = [event type]; + QPoint qlocalPoint; + QPoint qglobalPoint; + bool processThisEvent = false; + bool fakeNCEvents = false; + bool fakeMouseEvents = false; + + // Check if this is a mouse event. + if (evtType == NSLeftMouseDown || evtType == NSLeftMouseUp + || evtType == NSRightMouseDown || evtType == NSRightMouseUp + || evtType == NSOtherMouseDown || evtType == NSOtherMouseUp + || evtType == NSMouseMoved || evtType == NSLeftMouseDragged + || evtType == NSRightMouseDragged || evtType == NSOtherMouseDragged) { + // Check if we want to pass this message to another window + if (mac_mouse_grabber && mac_mouse_grabber != widgetToGetEvent) { + NSWindow *grabWindow = static_cast<NSWindow *>(qt_mac_window_for(mac_mouse_grabber)); + if (window != grabWindow) { + window = grabWindow; + widgetToGetEvent = mac_mouse_grabber; + fakeNCEvents = true; + } + } + // Dont generate normal NC mouse events for Left Button dragged + if(evtType != NSLeftMouseDragged || fakeNCEvents) { + NSPoint windowPoint = [event locationInWindow]; + NSPoint globalPoint = [[event window] convertBaseToScreen:windowPoint]; + NSRect frameRect = [window frame]; + if (fakeNCEvents || NSMouseInRect(globalPoint, frameRect, NO)) { + NSRect contentRect = [window contentRectForFrameRect:frameRect]; + qglobalPoint = QPoint(flipPoint(globalPoint).toPoint()); + QWidget *w = widgetToGetEvent->childAt(widgetToGetEvent->mapFromGlobal(qglobalPoint)); + // check that the mouse pointer is on the non-client area and + // there are not widgets in it. + if (fakeNCEvents || (!NSMouseInRect(globalPoint, contentRect, NO) && !w)) { + qglobalPoint = QPoint(flipPoint(globalPoint).toPoint()); + qlocalPoint = widgetToGetEvent->mapFromGlobal(qglobalPoint); + processThisEvent = true; + } + } + } + } + // This is not an NC area mouse message. + if (!processThisEvent) + return; + + // If the window is frame less, generate fake mouse events instead. (floating QToolBar) + // or if someone already got an explicit or implicit grab + if (mac_mouse_grabber || qt_button_down || + (fakeNCEvents && (widgetToGetEvent->window()->windowFlags() & Qt::FramelessWindowHint))) + fakeMouseEvents = true; + + Qt::MouseButton button; + QEvent::Type eventType; + // Convert to Qt::Event type + switch (evtType) { + case NSLeftMouseDown: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSLeftMouseUp: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSRightMouseDown: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSRightMouseUp: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSOtherMouseDown: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSOtherMouseUp: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSMouseMoved: + button = Qt::NoButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSLeftMouseDragged: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSRightMouseDragged: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSOtherMouseDragged: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + default: + qWarning("not handled! Non client area mouse message"); + return; + } + + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + if (eventType == QEvent::NonClientAreaMouseButtonPress || eventType == QEvent::MouseButtonPress) { + NSInteger clickCount = [event clickCount]; + if (clickCount % 2 == 0) + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonDblClick + : QEvent::MouseButtonDblClick; + if (button == Qt::LeftButton && (keyMods & Qt::MetaModifier)) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = true; + } + } else if (eventType == QEvent::NonClientAreaMouseButtonRelease || eventType == QEvent::MouseButtonRelease) { + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = false; + } + } + + Qt::MouseButtons buttons = 0; + { + UInt32 mac_buttons; + if (GetEventParameter((EventRef)[event eventRef], kEventParamMouseChord, typeUInt32, 0, + sizeof(mac_buttons), 0, &mac_buttons) == noErr) + buttons = qt_mac_get_buttons(mac_buttons); + } + + QMouseEvent qme(eventType, qlocalPoint, qglobalPoint, button, buttons, keyMods); + qt_sendSpontaneousEvent(widgetToGetEvent, &qme); + + // We don't need to set the implicit grab widget here because we won't + // reach this point if then event type is Press over a Qt widget. + // However we might need to unset it if the event is Release. + if (eventType == QEvent::MouseButtonRelease) + qt_button_down = 0; +} + +QWidget *qt_mac_getTargetForKeyEvent(QWidget *widgetThatReceivedEvent) +{ + if (QWidget *popup = QApplication::activePopupWidget()) { + QWidget *focusInPopup = popup->focusWidget(); + return focusInPopup ? focusInPopup : popup; + } + + QWidget *widgetToGetKey = qApp->focusWidget(); + if (!widgetToGetKey) + widgetToGetKey = widgetThatReceivedEvent; + + return widgetToGetKey; +} + +// This function will find the widget that should receive the +// mouse event. Because of explicit/implicit mouse grabs, popups, +// etc, this might not end up being the same as the widget under +// the mouse (which is more interresting when handling enter/leave +// events +QWidget *qt_mac_getTargetForMouseEvent( + // You can call this function without providing an event. + NSEvent *event, + QEvent::Type eventType, + QPoint &returnLocalPoint, + QPoint &returnGlobalPoint, + QWidget *nativeWidget, + QWidget **returnWidgetUnderMouse) +{ + Q_UNUSED(event); + NSPoint nsglobalpoint = event ? [[event window] convertBaseToScreen:[event locationInWindow]] : [NSEvent mouseLocation]; + returnGlobalPoint = flipPoint(nsglobalpoint).toPoint(); + QWidget *mouseGrabber = QWidget::mouseGrabber(); + bool buttonDownNotBlockedByModal = qt_button_down && !QApplicationPrivate::isBlockedByModal(qt_button_down); + QWidget *popup = QApplication::activePopupWidget(); + + // Resolve the widget under the mouse: + QWidget *widgetUnderMouse = 0; + if (popup || qt_button_down || !nativeWidget || !nativeWidget->isVisible()) { + // Using QApplication::widgetAt for finding the widget under the mouse + // is most safe, since it ignores cocoas own mouse down redirections (which + // we need to be prepared for when using nativeWidget as starting point). + // (the only exception is for QMacNativeWidget, where QApplication::widgetAt fails). + // But it is also slower (I guess), so we try to avoid it and use nativeWidget if we can: + widgetUnderMouse = QApplication::widgetAt(returnGlobalPoint); + } + + if (!widgetUnderMouse && nativeWidget) { + // Entering here should be the common case. We + // also handle the QMacNativeWidget fallback case. + QPoint p = nativeWidget->mapFromGlobal(returnGlobalPoint); + widgetUnderMouse = nativeWidget->childAt(p); + if (!widgetUnderMouse && nativeWidget->rect().contains(p)) + widgetUnderMouse = nativeWidget; + } + + if (widgetUnderMouse) { + // Check if widgetUnderMouse is blocked by a modal + // window, or the mouse if over the frame strut: + if (widgetUnderMouse == qt_button_down) { + // Small optimization to avoid an extra call to isBlockedByModal: + if (buttonDownNotBlockedByModal == false) + widgetUnderMouse = 0; + } else if (QApplicationPrivate::isBlockedByModal(widgetUnderMouse)) { + widgetUnderMouse = 0; + } + + if (widgetUnderMouse && widgetUnderMouse->isWindow()) { + // Exclude the titlebar (and frame strut) when finding widget under mouse: + QPoint p = widgetUnderMouse->mapFromGlobal(returnGlobalPoint); + if (!widgetUnderMouse->rect().contains(p)) + widgetUnderMouse = 0; + } + } + if (returnWidgetUnderMouse) + *returnWidgetUnderMouse = widgetUnderMouse; + + // Resolve the target for the mouse event. Default will be + // widgetUnderMouse, except if there is a grab (popup/mouse/button-down): + if (popup && !mouseGrabber) { + // We special case handling of popups, since they have an implicitt mouse grab. + QWidget *candidate = buttonDownNotBlockedByModal ? qt_button_down : widgetUnderMouse; + if (!popup->isAncestorOf(candidate)) { + // INVARIANT: we have a popup, but the candidate is not + // in it. But the popup will grab the mouse anyway, + // except if the user scrolls: + if (eventType == QEvent::Wheel) + return 0; + returnLocalPoint = popup->mapFromGlobal(returnGlobalPoint); + return popup; + } else if (popup == candidate) { + // INVARIANT: The candidate is the popup itself, and not a child: + returnLocalPoint = popup->mapFromGlobal(returnGlobalPoint); + return popup; + } else { + // INVARIANT: The candidate is a child inside the popup: + returnLocalPoint = candidate->mapFromGlobal(returnGlobalPoint); + return candidate; + } + } + + QWidget *target = mouseGrabber; + if (!target && buttonDownNotBlockedByModal) + target = qt_button_down; + if (!target) + target = widgetUnderMouse; + if (!target) + return 0; + + returnLocalPoint = target->mapFromGlobal(returnGlobalPoint); + return target; +} + +QPointer<QWidget> qt_last_native_mouse_receiver = 0; + +static inline void qt_mac_checkEnterLeaveForNativeWidgets(QWidget *maybeEnterWidget) +{ + // Dispatch enter/leave for the cases where QApplicationPrivate::sendMouseEvent do + // not. This will in general be the cases when alien widgets are not involved: + // 1. from a native widget to another native widget or + // 2. from a native widget to no widget + // 3. from no widget to a native or alien widget + + if (qt_button_down || QWidget::mouseGrabber()) + return; + + if ((maybeEnterWidget == qt_last_native_mouse_receiver) && qt_last_native_mouse_receiver) + return; + if (maybeEnterWidget) { + if (!qt_last_native_mouse_receiver) { + // case 3 + QApplicationPrivate::dispatchEnterLeave(maybeEnterWidget, 0); + qt_last_native_mouse_receiver = maybeEnterWidget->internalWinId() ? maybeEnterWidget : maybeEnterWidget->nativeParentWidget(); + } else if (maybeEnterWidget->internalWinId()) { + // case 1 + QApplicationPrivate::dispatchEnterLeave(maybeEnterWidget, qt_last_native_mouse_receiver); + qt_last_native_mouse_receiver = maybeEnterWidget->internalWinId() ? maybeEnterWidget : maybeEnterWidget->nativeParentWidget(); + } // else at lest one of the widgets are alien, so enter/leave will be handled in QApplicationPrivate + } else { + if (qt_last_native_mouse_receiver) { + // case 2 + QApplicationPrivate::dispatchEnterLeave(0, qt_last_native_mouse_receiver); + qt_last_mouse_receiver = 0; + qt_last_native_mouse_receiver = 0; + } + } +} + +bool qt_mac_handleMouseEvent(NSEvent *event, QEvent::Type eventType, Qt::MouseButton button, QWidget *nativeWidget) +{ + // Give the Input Manager a chance to process the mouse events. + NSInputManager *currentIManager = [NSInputManager currentInputManager]; + if (currentIManager && [currentIManager wantsToHandleMouseEvents]) { + [currentIManager handleMouseEvent:event]; + } + + // Find the widget that should receive the event, and the widget under the mouse. Those + // can differ if an implicit or explicit mouse grab is active: + QWidget *widgetUnderMouse = 0; + QPoint localPoint, globalPoint; + QWidget *widgetToGetMouse = qt_mac_getTargetForMouseEvent(event, eventType, localPoint, globalPoint, nativeWidget, &widgetUnderMouse); + if (!widgetToGetMouse) + return false; + + // From here on, we let nativeWidget actually be the native widget under widgetUnderMouse. The reason + // for this, is that qt_mac_getTargetForMouseEvent will set cocoa's mouse event redirection aside when + // determining which widget is under the mouse (in other words, it will usually ignore nativeWidget). + // nativeWidget will be used in QApplicationPrivate::sendMouseEvent to correctly dispatch enter/leave events. + if (widgetUnderMouse) + nativeWidget = widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget(); + if (!nativeWidget) + return false; + NSView *view = qt_mac_effectiveview_for(nativeWidget); + + // Handle tablet events (if any) first. + if (qt_mac_handleTabletEvent(view, event)) { + // Tablet event was handled. In Qt we aren't supposed to send the mouse event. + return true; + } + + EventRef carbonEvent = static_cast<EventRef>(const_cast<void *>([event eventRef])); + if (qt_mac_sendMacEventToWidget(widgetToGetMouse, carbonEvent)) + return true; + + // Keep previousButton to make sure we don't send double click + // events when the user double clicks using two different buttons: + static Qt::MouseButton previousButton = Qt::NoButton; + + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + NSInteger clickCount = [event clickCount]; + Qt::MouseButtons buttons = 0; + { + UInt32 mac_buttons; + if (GetEventParameter(carbonEvent, kEventParamMouseChord, typeUInt32, 0, + sizeof(mac_buttons), 0, &mac_buttons) == noErr) + buttons = qt_mac_get_buttons(mac_buttons); + } + + // Send enter/leave events for the cases when QApplicationPrivate::sendMouseEvent do not: + qt_mac_checkEnterLeaveForNativeWidgets(widgetUnderMouse); + + switch (eventType) { + default: + qWarning("not handled! %d", eventType); + break; + case QEvent::MouseMove: + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) + button = Qt::RightButton; + break; + case QEvent::MouseButtonPress: + qt_button_down = widgetUnderMouse; + if (clickCount % 2 == 0 && (previousButton == Qt::NoButton || previousButton == button)) + eventType = QEvent::MouseButtonDblClick; + if (button == Qt::LeftButton && (keyMods & Qt::MetaModifier)) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = true; + } + break; + case QEvent::MouseButtonRelease: + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = false; + } + qt_button_down = 0; + break; + } + + qt_mac_updateCursorWithWidgetUnderMouse(widgetUnderMouse); + + DnDParams *dndParams = currentDnDParameters(); + dndParams->view = view; + dndParams->theEvent = event; + dndParams->globalPoint = globalPoint; + + // Send the mouse event: + QMouseEvent qme(eventType, localPoint, globalPoint, button, buttons, keyMods); + QApplicationPrivate::sendMouseEvent( + widgetToGetMouse, &qme, widgetUnderMouse, nativeWidget, + &qt_button_down, qt_last_mouse_receiver, true); + + if (eventType == QEvent::MouseButtonPress && button == Qt::RightButton) { + QContextMenuEvent qcme(QContextMenuEvent::Mouse, localPoint, globalPoint, keyMods); + qt_sendSpontaneousEvent(widgetToGetMouse, &qcme); + } + + if (eventType == QEvent::MouseButtonRelease) { + // A mouse button was released, which means that the implicit grab was + // released. We therefore need to re-check if should send (delayed) enter leave events: + // qt_button_down has now become NULL since the call at the top of the function. Also, since + // the relase might have closed a window, we dont give the nativeWidget hint + qt_mac_getTargetForMouseEvent(0, QEvent::None, localPoint, globalPoint, nativeWidget, &widgetUnderMouse); + qt_mac_checkEnterLeaveForNativeWidgets(widgetUnderMouse); + } + + previousButton = button; + return true; +} +#endif + +bool qt_mac_handleTabletEvent(void * /*QCocoaView * */view, void * /*NSEvent * */tabletEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(view); + Q_UNUSED(tabletEvent); + return false; +#else + QT_MANGLE_NAMESPACE(QCocoaView) *theView = static_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(view); + NSView *theNSView = static_cast<NSView *>(view); + NSEvent *theTabletEvent = static_cast<NSEvent *>(tabletEvent); + + NSEventType eventType = [theTabletEvent type]; + if (eventType != NSTabletPoint && [theTabletEvent subtype] != NSTabletPointEventSubtype) + return false; // Not a tablet event. + + NSPoint windowPoint = [theTabletEvent locationInWindow]; + NSPoint globalPoint = [[theTabletEvent window] convertBaseToScreen:windowPoint]; + + QWidget *qwidget = [theView qt_qwidget]; + QWidget *widgetToGetMouse = qwidget; + QWidget *popup = qAppInstance()->activePopupWidget(); + if (popup && popup != qwidget->window()) + widgetToGetMouse = popup; + + if (qt_mac_sendMacEventToWidget(widgetToGetMouse, + static_cast<EventRef>(const_cast<void *>([theTabletEvent eventRef])))) + return true; + if (widgetToGetMouse != qwidget) { + theNSView = qt_mac_nativeview_for(widgetToGetMouse); + windowPoint = [[theNSView window] convertScreenToBase:globalPoint]; + } + NSPoint localPoint = [theNSView convertPoint:windowPoint fromView:nil]; + // Tablet events do not handle WA_TransparentForMouseEvents ATM + // In theory, people who set the WA_TransparentForMouseEvents attribute won't handle + // tablet events either in which case they will fall into the mouse event case and get + // them passed on. This will NOT handle the raw events, but that might not be a big problem. + + const QMacTabletHash *tabletHash = qt_mac_tablet_hash(); + if (!tabletHash->contains([theTabletEvent deviceID])) { + qWarning("QCocoaView handleTabletEvent: This tablet device is unknown" + " (received no proximity event for it). Discarding event."); + return false; + } + const QTabletDeviceData &deviceData = tabletHash->value([theTabletEvent deviceID]); + + + QEvent::Type qType; + switch (eventType) { + case NSLeftMouseDown: + case NSRightMouseDown: + qType = QEvent::TabletPress; + break; + case NSLeftMouseUp: + case NSRightMouseUp: + qType = QEvent::TabletRelease; + break; + case NSMouseMoved: + case NSTabletPoint: + case NSLeftMouseDragged: + case NSRightMouseDragged: + default: + qType = QEvent::TabletMove; + break; + } + + qreal pressure; + if (eventType != NSMouseMoved) { + pressure = [theTabletEvent pressure]; + } else { + pressure = 0.0; + } + + NSPoint tilt = [theTabletEvent tilt]; + int xTilt = qRound(tilt.x * 60.0); + int yTilt = qRound(tilt.y * -60.0); + qreal tangentialPressure = 0; + qreal rotation = 0; + int z = 0; + if (deviceData.capabilityMask & 0x0200) + z = [theTabletEvent absoluteZ]; + + if (deviceData.capabilityMask & 0x0800) + tangentialPressure = [theTabletEvent tangentialPressure]; + + rotation = [theTabletEvent rotation]; + QPointF hiRes = flipPoint(globalPoint); + QTabletEvent qtabletEvent(qType, QPoint(localPoint.x, localPoint.y), + hiRes.toPoint(), hiRes, + deviceData.tabletDeviceType, deviceData.tabletPointerType, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + qt_cocoaModifiers2QtModifiers([theTabletEvent modifierFlags]), + deviceData.tabletUniqueID); + + qt_sendSpontaneousEvent(widgetToGetMouse, &qtabletEvent); + return qtabletEvent.isAccepted(); +#endif +} + +void qt_mac_updateContentBorderMetricts(void * /*OSWindowRef */window, const ::HIContentBorderMetrics &metrics) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); +#if !defined(QT_MAC_USE_COCOA) +# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + ::HIWindowSetContentBorderThickness(theWindow, &metrics); + } +# else + Q_UNUSED(window); + Q_UNUSED(metrics); +# endif +#else + if ([theWindow styleMask] & NSTexturedBackgroundWindowMask) + [theWindow setContentBorderThickness:metrics.top forEdge:NSMaxYEdge]; + [theWindow setContentBorderThickness:metrics.bottom forEdge:NSMinYEdge]; +#endif +} + +#if QT_MAC_USE_COCOA +void qt_mac_replaceDrawRect(void * /*OSWindowRef */window, QWidgetPrivate *widget) +{ + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + if(!theWindow) + return; + id theClass = [[[theWindow contentView] superview] class]; + // What we do here is basically to add a new selector to NSThemeFrame called + // "drawRectOriginal:" which will contain the original implementation of + // "drawRect:". After that we get the new implementation from QCocoaWindow + // and exchange them. The new implementation is called drawRectSpecial. + // We cannot just add the method because it might have been added before and since + // we cannot remove a method once it has been added we need to ask QCocoaWindow if + // we did the swap or not. + if(!widget->drawRectOriginalAdded) { + Method m2 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m2) { + // This case is pretty extreme, no drawRect means no drawing! + return; + } + class_addMethod(theClass, @selector(drawRectOriginal:), method_getImplementation(m2), method_getTypeEncoding(m2)); + widget->drawRectOriginalAdded = true; + } + if(widget->originalDrawMethod) { + Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:)); + if(!m0) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m1) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + // We have the original method here. Proceed and swap the methods. + method_exchangeImplementations(m1, m0); + widget->originalDrawMethod = false; + [theWindow display]; + } +} + +void qt_mac_replaceDrawRectOriginal(void * /*OSWindowRef */window, QWidgetPrivate *widget) +{ + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + id theClass = [[[theWindow contentView] superview] class]; + // Now we need to revert the methods to their original state. + // We cannot remove the method, so we just keep track of it in QCocoaWindow. + Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:)); + if(!m0) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m1) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + method_exchangeImplementations(m1, m0); + widget->originalDrawMethod = true; + [theWindow display]; +} +#endif // QT_MAC_USE_COCOA + +#if QT_MAC_USE_COCOA +void qt_mac_showBaseLineSeparator(void * /*OSWindowRef */window, bool show) +{ + if(!window) + return; + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSToolbar *macToolbar = [theWindow toolbar]; + [macToolbar setShowsBaselineSeparator:show]; +} +#endif // QT_MAC_USE_COCOA + +QStringList qt_mac_NSArrayToQStringList(void *nsarray) +{ + QStringList result; + NSArray *array = static_cast<NSArray *>(nsarray); + for (NSUInteger i=0; i<[array count]; ++i) + result << qt_mac_NSStringToQString([array objectAtIndex:i]); + return result; +} + +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list) +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:list.size()]; + for (int i=0; i<list.size(); ++i){ + [result addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(list[i]))]; + } + return result; +} + +#if QT_MAC_USE_COCOA +void qt_syncCocoaTitleBarButtons(OSWindowRef window, QWidget *widgetForWindow) +{ + if (!widgetForWindow) + return; + + Qt::WindowFlags flags = widgetForWindow->windowFlags(); + bool customize = flags & Qt::CustomizeWindowHint; + + NSButton *btn = [window standardWindowButton:NSWindowZoomButton]; + // BOOL is not an int, so the bitwise AND doesn't work. + bool go = uint(customize && !(flags & Qt::WindowMaximizeButtonHint)) == 0; + [btn setEnabled:go]; + + btn = [window standardWindowButton:NSWindowMiniaturizeButton]; + go = uint(customize && !(flags & Qt::WindowMinimizeButtonHint)) == 0; + [btn setEnabled:go]; + + btn = [window standardWindowButton:NSWindowCloseButton]; + go = uint(customize && !(flags & Qt::WindowSystemMenuHint + || flags & Qt::WindowCloseButtonHint)) == 0; + [btn setEnabled:go]; + + [window setShowsToolbarButton:uint(flags & Qt::MacWindowToolBarButtonHint) != 0]; +} +#endif // QT_MAC_USE_COCOA + +// Carbon: Make sure you call QDEndContext on the context when done with it. +CGContextRef qt_mac_graphicsContextFor(QWidget *widget) +{ + if (!widget) + return 0; + +#ifndef QT_MAC_USE_COCOA + CGContextRef context; + CGrafPtr port = GetWindowPort(qt_mac_window_for(widget)); + QDBeginCGContext(port, &context); +#else + CGContextRef context = (CGContextRef)[[NSGraphicsContext graphicsContextWithWindow:qt_mac_window_for(widget)] graphicsPort]; +#endif + return context; +} + +void qt_mac_dispatchPendingUpdateRequests(QWidget *widget) +{ + if (!widget) + return; +#ifndef QT_MAC_USE_COCOA + HIViewRender(qt_mac_nativeview_for(widget)); +#else + [qt_mac_nativeview_for(widget) displayIfNeeded]; +#endif +} + +CGFloat qt_mac_get_scalefactor() +{ +#ifndef QT_MAC_USE_COCOA + return HIGetScaleFactor(); +#else + return [[NSScreen mainScreen] userSpaceScaleFactor]; +#endif +} + +QString qt_mac_get_pasteboardString(OSPasteboardRef paste) +{ + QMacCocoaAutoReleasePool pool; + NSPasteboard *pb = nil; + CFStringRef pbname; + if (PasteboardCopyName(paste, &pbname) == noErr) { + pb = [NSPasteboard pasteboardWithName:const_cast<NSString *>(reinterpret_cast<const NSString *>(pbname))]; + CFRelease(pbname); + } else { + pb = [NSPasteboard generalPasteboard]; + } + if (pb) { + NSString *text = [pb stringForType:NSStringPboardType]; + if (text) + return qt_mac_NSStringToQString(text); + } + return QString(); +} + +QPixmap qt_mac_convert_iconref(const IconRef icon, int width, int height) +{ + QPixmap ret(width, height); + ret.fill(QColor(0, 0, 0, 0)); + + CGRect rect = CGRectMake(0, 0, width, height); + + CGContextRef ctx = qt_mac_cg_context(&ret); + CGAffineTransform old_xform = CGContextGetCTM(ctx); + CGContextConcatCTM(ctx, CGAffineTransformInvert(old_xform)); + CGContextConcatCTM(ctx, CGAffineTransformIdentity); + + ::RGBColor b; + b.blue = b.green = b.red = 255*255; + PlotIconRefInContext(ctx, &rect, kAlignNone, kTransformNone, &b, kPlotIconRefNormalFlags, icon); + CGContextRelease(ctx); + return ret; +} + +void qt_mac_constructQIconFromIconRef(const IconRef icon, const IconRef overlayIcon, QIcon *retIcon, QStyle::StandardPixmap standardIcon) +{ + int size = 16; + while (size <= 128) { + + const QString cacheKey = QLatin1String("qt_mac_constructQIconFromIconRef") + QString::number(standardIcon) + QString::number(size); + QPixmap mainIcon; + if (standardIcon >= QStyle::SP_CustomBase) { + mainIcon = qt_mac_convert_iconref(icon, size, size); + } else if (QPixmapCache::find(cacheKey, mainIcon) == false) { + mainIcon = qt_mac_convert_iconref(icon, size, size); + QPixmapCache::insert(cacheKey, mainIcon); + } + + if (overlayIcon) { + int littleSize = size / 2; + QPixmap overlayPix = qt_mac_convert_iconref(overlayIcon, littleSize, littleSize); + QPainter painter(&mainIcon); + painter.drawPixmap(size - littleSize, size - littleSize, overlayPix); + } + + retIcon->addPixmap(mainIcon); + size += size; // 16 -> 32 -> 64 -> 128 + } +} + +#ifdef QT_MAC_USE_COCOA +void qt_mac_menu_collapseSeparators(void */*NSMenu **/ theMenu, bool collapse) +{ + QMacCocoaAutoReleasePool pool; + OSMenuRef menu = static_cast<OSMenuRef>(theMenu); + if (collapse) { + bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. + NSMenuItem *previousItem = nil; + + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if ([item isSeparatorItem]) { + [item setHidden:previousIsSeparator]; + } + + if (![item isHidden]) { + previousItem = item; + previousIsSeparator = ([previousItem isSeparatorItem]); + } + } + + // We now need to check the final item since we don't want any separators at the end of the list. + if (previousItem && previousIsSeparator) + [previousItem setHidden:YES]; + } else { + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) + [item setHidden:!action->isVisible()]; + } + } +} + +class CocoaPostMessageAfterEventLoopExitHelp : public QObject +{ + id target; + SEL selector; + int argCount; + id arg1; + id arg2; +public: + CocoaPostMessageAfterEventLoopExitHelp(id target, SEL selector, int argCount, id arg1, id arg2) + : target(target), selector(selector), argCount(argCount), arg1(arg1), arg2(arg2){ + deleteLater(); + } + + ~CocoaPostMessageAfterEventLoopExitHelp() + { + qt_cocoaPostMessage(target, selector, argCount, arg1, arg2); + } +}; + +void qt_cocoaPostMessage(id target, SEL selector, int argCount, id arg1, id arg2) +{ + // WARNING: data1 and data2 is truncated to from 64-bit to 32-bit on OS 10.5! + // That is why we need to split the address in two parts: + QCocoaPostMessageArgs *args = new QCocoaPostMessageArgs(target, selector, argCount, arg1, arg2); + quint32 lower = quintptr(args); + quint32 upper = quintptr(args) >> 32; + NSEvent *e = [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 + context:nil subtype:QtCocoaEventSubTypePostMessage data1:lower data2:upper]; + [NSApp postEvent:e atStart:NO]; +} + +void qt_cocoaPostMessageAfterEventLoopExit(id target, SEL selector, int argCount, id arg1, id arg2) +{ + if (QApplicationPrivate::instance()->threadData->eventLoops.size() <= 1) + qt_cocoaPostMessage(target, selector, argCount, arg1, arg2); + else + new CocoaPostMessageAfterEventLoopExitHelp(target, selector, argCount, arg1, arg2); +} + +#endif + +QMacCocoaAutoReleasePool::QMacCocoaAutoReleasePool() +{ +#ifndef QT_MAC_USE_COCOA + NSApplicationLoad(); +#endif + pool = (void*)[[NSAutoreleasePool alloc] init]; +} + +QMacCocoaAutoReleasePool::~QMacCocoaAutoReleasePool() +{ + [(NSAutoreleasePool*)pool release]; +} + +void qt_mac_post_retranslateAppMenu() +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + qt_cocoaPostMessage([NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)], @selector(qtTranslateApplicationMenu)); +#endif +} + +QWidgetPrivate *QMacScrollOptimization::_target = 0; +bool QMacScrollOptimization::_inWheelEvent = false; +int QMacScrollOptimization::_dx = 0; +int QMacScrollOptimization::_dy = 0; +QRect QMacScrollOptimization::_scrollRect = QRect(0, 0, -1, -1); + +#ifdef QT_MAC_USE_COCOA +// This method implements the magic for the drawRectSpecial method. +// We draw a line at the upper edge of the content view in order to +// override the title baseline. +void macDrawRectOnTop(void * /*OSWindowRef */window) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSView *contentView = [theWindow contentView]; + if(!contentView) + return; + // Get coordinates of the content view + NSRect contentRect = [contentView frame]; + // Draw a line on top of the already drawn line. + // We need to check if we are active or not to use the proper color. + if([theWindow isKeyWindow] || [theWindow isMainWindow]) { + [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set]; + } else { + [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set]; + } + NSPoint origin = NSMakePoint(0, contentRect.size.height); + NSPoint end = NSMakePoint(contentRect.size.width, contentRect.size.height); + [NSBezierPath strokeLineFromPoint:origin toPoint:end]; +} + +// This method will (or at least should) get called only once. +// Its mission is to find out if we are active or not. If we are active +// we assume that we were launched via finder, otherwise we assume +// we were called from the command line. The distinction is important, +// since in the first case we don't need to trigger a paintEvent, while +// in the second case we do. +void macSyncDrawingOnFirstInvocation(void * /*OSWindowRef */window) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSApplication *application = [NSApplication sharedApplication]; + NSToolbar *toolbar = [theWindow toolbar]; + if([application isActive]) { + // Launched from finder + [toolbar setShowsBaselineSeparator:NO]; + } else { + // Launched from commandline + [toolbar setVisible:false]; + [toolbar setShowsBaselineSeparator:NO]; + [toolbar setVisible:true]; + [theWindow display]; + } +} + +void qt_cocoaStackChildWindowOnTopOfOtherChildren(QWidget *childWidget) +{ + if (!childWidget) + return; + + QWidget *parent = childWidget->parentWidget(); + if (childWidget->isWindow() && parent) { + if ([[qt_mac_window_for(parent) childWindows] containsObject:qt_mac_window_for(childWidget)]) { + QWidgetPrivate *d = qt_widget_private(childWidget); + d->setSubWindowStacking(false); + d->setSubWindowStacking(true); + } + } +} + +void qt_mac_display(QWidget *widget) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + [theNSView display]; +} + +void qt_mac_setNeedsDisplay(QWidget *widget) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + [theNSView setNeedsDisplay:YES]; +} + +void qt_mac_setNeedsDisplayInRect(QWidget *widget, QRegion region) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + if (region.isEmpty()) { + [theNSView setNeedsDisplay:YES]; + return; + } + + QVector<QRect> rects = region.rects(); + for (int i = 0; i < rects.count(); ++i) { + const QRect &rect = rects.at(i); + NSRect nsrect = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + [theNSView setNeedsDisplayInRect:nsrect]; + } + +} + +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qt_cocoa_helpers_mac_p.h b/src/gui/kernel/qt_cocoa_helpers_mac_p.h new file mode 100644 index 0000000000..a49753ae2f --- /dev/null +++ b/src/gui/kernel/qt_cocoa_helpers_mac_p.h @@ -0,0 +1,340 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#ifndef QT_COCOA_HELPERS_MAC_P_H +#define QT_COCOA_HELPERS_MAC_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, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include <private/qt_mac_p.h> + +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qwidget.h> +#include <qevent.h> +#include <qhash.h> +#include <qlabel.h> +#include <qpointer.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qstylepainter.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <private/qeffects_p.h> +#include <private/qwidget_p.h> +#include <qtextdocument.h> +#include <qdebug.h> +#include <qpoint.h> +#include "private/qt_mac_p.h" + +struct HIContentBorderMetrics; + +#ifdef Q_WS_MAC32 +typedef struct _NSPoint NSPoint; // Just redefine here so I don't have to pull in all of Cocoa. +#else +typedef struct CGPoint NSPoint; +#endif + +QT_BEGIN_NAMESPACE + +enum { + QtCocoaEventSubTypeWakeup = SHRT_MAX, + QtCocoaEventSubTypePostMessage = SHRT_MAX-1 +}; + +Qt::MouseButtons qt_mac_get_buttons(int buttons); +Qt::MouseButton qt_mac_get_button(EventMouseButton button); +void macWindowFade(void * /*OSWindowRef*/ window, float durationSeconds = 0.15); +bool macWindowIsTextured(void * /*OSWindowRef*/ window); +void macWindowToolbarShow(const QWidget *widget, bool show ); +void macWindowToolbarSet( void * /*OSWindowRef*/ window, void* toolbarRef ); +bool macWindowToolbarIsVisible( void * /*OSWindowRef*/ window ); +void macWindowSetHasShadow( void * /*OSWindowRef*/ window, bool hasShadow ); +void macWindowFlush(void * /*OSWindowRef*/ window); +void macSendToolbarChangeEvent(QWidget *widget); +void qt_mac_updateContentBorderMetricts(void * /*OSWindowRef */window, const ::HIContentBorderMetrics &metrics); +void qt_mac_replaceDrawRect(void * /*OSWindowRef */window, QWidgetPrivate *widget); +void qt_mac_replaceDrawRectOriginal(void * /*OSWindowRef */window, QWidgetPrivate *widget); +void qt_mac_showBaseLineSeparator(void * /*OSWindowRef */window, bool show); +void * /*NSImage */qt_mac_create_nsimage(const QPixmap &pm); +void qt_mac_update_mouseTracking(QWidget *widget); +OSStatus qt_mac_drawCGImage(CGContextRef cg, const CGRect *inbounds, CGImageRef); +bool qt_mac_checkForNativeSizeGrip(const QWidget *widget); +void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent); +#ifdef QT_MAC_USE_COCOA +bool qt_dispatchKeyEventWithCocoa(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent); +// These methods exists only for supporting unified mode. +void macDrawRectOnTop(void * /*OSWindowRef */ window); +void macSyncDrawingOnFirstInvocation(void * /*OSWindowRef */window); +void qt_cocoaStackChildWindowOnTopOfOtherChildren(QWidget *widget); +void qt_mac_menu_collapseSeparators(void * /*NSMenu */ menu, bool collapse); +#endif +bool qt_dispatchKeyEvent(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent); +void qt_dispatchModifiersChanged(void * /*NSEvent * */flagsChangedEvent, QWidget *widgetToGetEvent); +bool qt_mac_handleTabletEvent(void * /*QCocoaView * */view, void * /*NSEvent * */event); +inline QApplication *qAppInstance() { return static_cast<QApplication *>(QCoreApplication::instance()); } +struct ::TabletProximityRec; +void qt_dispatchTabletProximityEvent(const ::TabletProximityRec &proxRec); +Qt::KeyboardModifiers qt_cocoaModifiers2QtModifiers(ulong modifierFlags); +Qt::KeyboardModifiers qt_cocoaDragOperation2QtModifiers(uint dragOperations); +QPixmap qt_mac_convert_iconref(const IconRef icon, int width, int height); +void qt_mac_constructQIconFromIconRef(const IconRef icon, const IconRef overlayIcon, QIcon *retIcon, + QStyle::StandardPixmap standardIcon = QStyle::SP_CustomBase); + +#if QT_MAC_USE_COCOA && __OBJC__ +struct DnDParams +{ + NSView *view; + NSEvent *theEvent; + QPoint globalPoint; + NSDragOperation performedAction; +}; + +DnDParams *macCurrentDnDParameters(); +NSDragOperation qt_mac_mapDropAction(Qt::DropAction action); +NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions); +Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions); +Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions); + +QWidget *qt_mac_getTargetForKeyEvent(QWidget *widgetThatReceivedEvent); +QWidget *qt_mac_getTargetForMouseEvent(NSEvent *event, QEvent::Type eventType, + QPoint &returnLocalPoint, QPoint &returnGlobalPoint, QWidget *nativeWidget, QWidget **returnWidgetUnderMouse); +bool qt_mac_handleMouseEvent(NSEvent *event, QEvent::Type eventType, Qt::MouseButton button, QWidget *nativeWidget); +void qt_mac_handleNonClientAreaMouseEvent(NSWindow *window, NSEvent *event); +#endif + +inline int flipYCoordinate(int y) +{ + return QApplication::desktop()->screenGeometry(0).height() - y; +} + +inline qreal flipYCoordinate(qreal y) +{ + return QApplication::desktop()->screenGeometry(0).height() - y; +} + +QPointF flipPoint(const NSPoint &p); +NSPoint flipPoint(const QPoint &p); +NSPoint flipPoint(const QPointF &p); + +QStringList qt_mac_NSArrayToQStringList(void *nsarray); +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list); + +void qt_syncCocoaTitleBarButtons(OSWindowRef window, QWidget *widgetForWindow); + +CGFloat qt_mac_get_scalefactor(); +QString qt_mac_get_pasteboardString(OSPasteboardRef paste); + +#ifdef __OBJC__ +inline NSMutableArray *qt_mac_QStringListToNSMutableArray(const QStringList &qstrlist) +{ return reinterpret_cast<NSMutableArray *>(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } + +inline QString qt_mac_NSStringToQString(const NSString *nsstr) +{ return QCFString::toQString(reinterpret_cast<const CFStringRef>(nsstr)); } + +inline NSString *qt_mac_QStringToNSString(const QString &qstr) +{ return [reinterpret_cast<const NSString *>(QCFString::toCFStringRef(qstr)) autorelease]; } + +#ifdef QT_MAC_USE_COCOA +class QCocoaPostMessageArgs { +public: + id target; + SEL selector; + int argCount; + id arg1; + id arg2; + QCocoaPostMessageArgs(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0) + : target(target), selector(selector), argCount(argCount), arg1(arg1), arg2(arg2) + { + [target retain]; + [arg1 retain]; + [arg2 retain]; + } + + ~QCocoaPostMessageArgs() + { + [arg2 release]; + [arg1 release]; + [target release]; + } +}; +void qt_cocoaPostMessage(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0); +void qt_cocoaPostMessageAfterEventLoopExit(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0); +#endif + +#endif + +class QMacScrollOptimization { + // This class is made to optimize for the case when the user + // scrolls both horizontally and vertically at the same + // time. This will result in two QWheelEvents (one for each + // direction), which will typically result in two calls to + // QWidget::_scroll_sys. Rather than copying pixels twize on + // screen because of this, we add this helper class to try to + // get away with only one blit. + static QWidgetPrivate *_target; + static bool _inWheelEvent; + static int _dx; + static int _dy; + static QRect _scrollRect; + +public: + static void initDelayedScroll() + { + _inWheelEvent = true; + } + + static bool delayScroll(QWidgetPrivate *target, int dx, int dy, const QRect &scrollRect) + { + if (!_inWheelEvent) + return false; + if (_target && _target != target) + return false; + if (_scrollRect.width() != -1 && _scrollRect != scrollRect) + return false; + + _target = target; + _dx += dx; + _dy += dy; + _scrollRect = scrollRect; + return true; + } + + static void performDelayedScroll() + { + if (!_inWheelEvent) + return; + _inWheelEvent = false; + if (!_target) + return; + + _target->scroll_sys(_dx, _dy, _scrollRect); + + _target = 0; + _dx = 0; + _dy = 0; + _scrollRect = QRect(0, 0, -1, -1); + } +}; + +void qt_mac_post_retranslateAppMenu(); + +#ifdef QT_MAC_USE_COCOA +void qt_mac_display(QWidget *widget); +void qt_mac_setNeedsDisplay(QWidget *widget); +void qt_mac_setNeedsDisplayInRect(QWidget *widget, QRegion region); +#endif // QT_MAC_USE_COCOA + + +// Utility functions to ease the use of Core Graphics contexts. + +inline void qt_mac_retain_graphics_context(CGContextRef context) +{ + CGContextRetain(context); + CGContextSaveGState(context); +} + +inline void qt_mac_release_graphics_context(CGContextRef context) +{ + CGContextRestoreGState(context); + CGContextRelease(context); +} + +inline void qt_mac_draw_image(CGContextRef context, CGContextRef imageContext, CGRect area, CGRect drawingArea) +{ + CGImageRef image = CGBitmapContextCreateImage(imageContext); + CGImageRef subImage = CGImageCreateWithImageInRect(image, area); + + CGContextTranslateCTM (context, 0, drawingArea.origin.y + CGRectGetMaxY(drawingArea)); + CGContextScaleCTM(context, 1, -1); + CGContextDrawImage(context, drawingArea, subImage); + + CGImageRelease(subImage); + CGImageRelease(image); +} + +QT_END_NAMESPACE + +#endif // QT_COCOA_HELPERS_MAC_P_H diff --git a/src/gui/kernel/qt_gui_pch.h b/src/gui/kernel/qt_gui_pch.h new file mode 100644 index 0000000000..368c12d444 --- /dev/null +++ b/src/gui/kernel/qt_gui_pch.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + * This is a precompiled header file for use in Xcode / Mac GCC / + * GCC >= 3.4 / VC to greatly speed the building of Qt. It may also be + * of use to people developing their own project, but it is probably + * better to define your own header. Use of this header is currently + * UNSUPPORTED. + */ + +// from corelib/global/qt_pch.h +#if defined __cplusplus +#include <qglobal.h> + + +#ifdef Q_WS_WIN +# define _POSIX_ +# include <limits.h> +# undef _POSIX_ +#endif + +#include <qcoreapplication.h> +#include <qlist.h> +#include <qvariant.h> // All moc genereated code has this include +#include <qobject.h> +#include <qregexp.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qtextcodec.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qcursor.h> +#include <qdesktopwidget.h> +#include <qevent.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qstyle.h> +#include <qtimer.h> +#include <qwidget.h> + +#include <stdlib.h> + +#endif diff --git a/src/gui/kernel/qt_mac.cpp b/src/gui/kernel/qt_mac.cpp new file mode 100644 index 0000000000..046bcf6a54 --- /dev/null +++ b/src/gui/kernel/qt_mac.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qt_mac_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qnativeimage_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE +#ifdef QT_MAC_USE_COCOA +static CTFontRef CopyCTThemeFont(ThemeFontID themeID) +{ + CTFontUIFontType ctID = HIThemeGetUIFontType(themeID); + return CTFontCreateUIFontForLanguage(ctID, 0, 0); +} +#endif + +QFont qfontForThemeFont(ThemeFontID themeID) +{ +#ifndef QT_MAC_USE_COCOA + static const ScriptCode Script = smRoman; + Str255 f_name; + SInt16 f_size; + Style f_style; + GetThemeFont(themeID, Script, f_name, &f_size, &f_style); + extern QString qt_mac_from_pascal_string(const Str255); //qglobal.cpp + return QFont(qt_mac_from_pascal_string(f_name), f_size, + (f_style & ::bold) ? QFont::Bold : QFont::Normal, + (bool)(f_style & ::italic)); +#else + QCFType<CTFontRef> ctfont = CopyCTThemeFont(themeID); + QString familyName = QCFString(CTFontCopyFamilyName(ctfont)); + QCFType<CFDictionaryRef> dict = CTFontCopyTraits(ctfont); + CFNumberRef num = static_cast<CFNumberRef>(CFDictionaryGetValue(dict, kCTFontWeightTrait)); + float fW; + CFNumberGetValue(num, kCFNumberFloat32Type, &fW); + QFont::Weight wght = fW > 0. ? QFont::Bold : QFont::Normal; + num = static_cast<CFNumberRef>(CFDictionaryGetValue(dict, kCTFontSlantTrait)); + CFNumberGetValue(num, kCFNumberFloatType, &fW); + bool italic = (fW != 0.0); + return QFont(familyName, CTFontGetSize(ctfont), wght, italic); +#endif +} + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +static QColor qcolorFromCGColor(CGColorRef cgcolor) +{ + QColor pc; + CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(cgcolor)); + const CGFloat *components = CGColorGetComponents(cgcolor); + if (model == kCGColorSpaceModelRGB) { + pc.setRgbF(components[0], components[1], components[2], components[3]); + } else if (model == kCGColorSpaceModelCMYK) { + pc.setCmykF(components[0], components[1], components[2], components[3]); + } else if (model == kCGColorSpaceModelMonochrome) { + pc.setRgbF(components[0], components[0], components[0], components[1]); + } else { + // Colorspace we can't deal with. + qWarning("Qt: qcolorFromCGColor: cannot convert from colorspace model: %d", model); + Q_ASSERT(false); + } + return pc; +} + +static inline QColor leopardBrush(ThemeBrush brush) +{ + QCFType<CGColorRef> cgClr = 0; + HIThemeBrushCreateCGColor(brush, &cgClr); + return qcolorFromCGColor(cgClr); +} +#endif + +QColor qcolorForTheme(ThemeBrush brush) +{ +#ifndef QT_MAC_USE_COCOA +# if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + return leopardBrush(brush); + } else +# endif + { + RGBColor rgbcolor; + GetThemeBrushAsColor(brush, 32, true, &rgbcolor); + return QColor(rgbcolor.red / 256, rgbcolor.green / 256, rgbcolor.blue / 256); + } +#else + return leopardBrush(brush); +#endif +} + +QColor qcolorForThemeTextColor(ThemeTextColor themeColor) +{ +#ifdef Q_OS_MAC32 + RGBColor c; + GetThemeTextColor(themeColor, 32, true, &c); + QColor color = QColor(c.red / 256, c.green / 256, c.blue / 256); + return color; +#else + // There is no equivalent to GetThemeTextColor in 64-bit and it was rather bad that + // I didn't file a request to implement this for Snow Leopard. So, in the meantime + // I've encoded the values from the GetThemeTextColor. This is not exactly ideal + // as if someone really wants to mess with themeing, these colors will be wrong. + // It also means that we need to make sure the values for differences between + // OS releases (and it will be likely that we are a step behind.) + switch (themeColor) { + case kThemeTextColorAlertActive: + case kThemeTextColorTabFrontActive: + case kThemeTextColorBevelButtonActive: + case kThemeTextColorListView: + case kThemeTextColorPlacardActive: + case kThemeTextColorPopupButtonActive: + case kThemeTextColorPopupLabelActive: + case kThemeTextColorPushButtonActive: + return Qt::black; + case kThemeTextColorAlertInactive: + case kThemeTextColorDialogInactive: + case kThemeTextColorPlacardInactive: + return QColor(69, 69, 69, 255); + case kThemeTextColorPopupButtonInactive: + case kThemeTextColorPopupLabelInactive: + case kThemeTextColorPushButtonInactive: + case kThemeTextColorTabFrontInactive: + case kThemeTextColorBevelButtonInactive: + return QColor(127, 127, 127, 255); + default: { + QNativeImage nativeImage(16,16, QNativeImage::systemFormat()); + CGRect cgrect = CGRectMake(0, 0, 16, 16); + HIThemeSetTextFill(themeColor, 0, nativeImage.cg, kHIThemeOrientationNormal); + CGContextFillRect(nativeImage.cg, cgrect); + QColor color = nativeImage.image.pixel(0,0); + return QColor(nativeImage.image.pixel(0 , 0)); + } + } +#endif +} +QT_END_NAMESPACE diff --git a/src/gui/kernel/qt_mac_p.h b/src/gui/kernel/qt_mac_p.h new file mode 100644 index 0000000000..b2bb804ff0 --- /dev/null +++ b/src/gui/kernel/qt_mac_p.h @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_MAC_P_H +#define QT_MAC_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 "qmacdefines_mac.h" + +#ifdef __OBJC__ +#include <Cocoa/Cocoa.h> +#ifdef QT_MAC_USE_COCOA +#include <objc/runtime.h> +#endif // QT_MAC_USE_COCOA +#endif + +#include <CoreServices/CoreServices.h> + +#include "QtCore/qglobal.h" +#include "QtCore/qvariant.h" +#include "QtCore/qmimedata.h" +#include "QtCore/qpointer.h" +#include "private/qcore_mac_p.h" + + +#include "QtGui/qpainter.h" + +#include <Carbon/Carbon.h> + +QT_BEGIN_NAMESPACE +class QWidget; +class QDragMoveEvent; + +/* Event masks */ +// internal Qt types + + // Event class for our own Carbon events. +#if defined(QT_NAMESPACE) && defined(QT_NAMESPACE_MAC_CRC) +// Take the CRC we generated at configure time. This *may* result in a +// collision with another value If that is the case, please change the value +// here to something other than 'Cute'. +const UInt32 kEventClassQt = QT_NAMESPACE_MAC_CRC; +#else +const UInt32 kEventClassQt = 'Cute'; +#endif + +enum { + //AE types + typeAEClipboardChanged = 1, + //types + typeQWidget = 1, /* QWidget * */ + //params + kEventParamQWidget = 'qwid', /* typeQWidget */ + //events + kEventQtRequestContext = 13, + kEventQtRequestMenubarUpdate = 14, + kEventQtRequestShowSheet = 17, + kEventQtRequestActivate = 18, + kEventQtRequestWindowChange = 20 +}; + +// Simple class to manage short-lived regions +class QMacSmartQuickDrawRegion +{ + RgnHandle qdRgn; + Q_DISABLE_COPY(QMacSmartQuickDrawRegion) +public: + explicit QMacSmartQuickDrawRegion(RgnHandle rgn) : qdRgn(rgn) {} + ~QMacSmartQuickDrawRegion() { + extern void qt_mac_dispose_rgn(RgnHandle); // qregion_mac.cpp + qt_mac_dispose_rgn(qdRgn); + } + operator RgnHandle() { + return qdRgn; + } +}; + +// Class for chaining to gether a bunch of fades. It pretty much is only used for qmenu fading. +class QMacWindowFader +{ + QWidgetList m_windowsToFade; + float m_duration; + Q_DISABLE_COPY(QMacWindowFader) +public: + QMacWindowFader(); // PLEASE DON'T CALL THIS. + static QMacWindowFader *currentFader(); + void registerWindowToFade(QWidget *window); + void setFadeDuration(float durationInSecs) { m_duration = durationInSecs; } + float fadeDuration() const { return m_duration; } + void performFade(); +}; + +class Q_GUI_EXPORT QMacCocoaAutoReleasePool +{ +private: + void *pool; +public: + QMacCocoaAutoReleasePool(); + ~QMacCocoaAutoReleasePool(); + + inline void *handle() const { return pool; } +}; + +QString qt_mac_removeMnemonics(const QString &original); //implemented in qmacstyle_mac.cpp + +class Q_GUI_EXPORT QMacWindowChangeEvent +{ +private: + static QList<QMacWindowChangeEvent*> *change_events; +public: + QMacWindowChangeEvent() { + } + virtual ~QMacWindowChangeEvent() { + } + static inline void exec(bool ) { + } +protected: + virtual void windowChanged() = 0; + virtual void flushWindowChanged() = 0; +}; + +class QMacCGContext +{ + CGContextRef context; +public: + QMacCGContext(QPainter *p); //qpaintengine_mac.cpp + inline QMacCGContext() { context = 0; } + inline QMacCGContext(const QPaintDevice *pdev) { + extern CGContextRef qt_mac_cg_context(const QPaintDevice *); + context = qt_mac_cg_context(pdev); + } + inline QMacCGContext(CGContextRef cg, bool takeOwnership=false) { + context = cg; + if(!takeOwnership) + CGContextRetain(context); + } + inline QMacCGContext(const QMacCGContext ©) : context(0) { *this = copy; } + inline ~QMacCGContext() { + if(context) + CGContextRelease(context); + } + inline bool isNull() const { return context; } + inline operator CGContextRef() { return context; } + inline QMacCGContext &operator=(const QMacCGContext ©) { + if(context) + CGContextRelease(context); + context = copy.context; + CGContextRetain(context); + return *this; + } + inline QMacCGContext &operator=(CGContextRef cg) { + if(context) + CGContextRelease(context); + context = cg; + CGContextRetain(context); //we do not take ownership + return *this; + } +}; + +class QMacPasteboardMime; +class QMimeData; + +class QMacPasteboard +{ + struct Promise { + Promise() : itemId(0), convertor(0) { } + Promise(int itemId, QMacPasteboardMime *c, QString m, QVariant d, int o=0) : itemId(itemId), offset(o), convertor(c), mime(m), data(d) { } + int itemId, offset; + QMacPasteboardMime *convertor; + QString mime; + QVariant data; + }; + QList<Promise> promises; + + OSPasteboardRef paste; + uchar mime_type; + mutable QPointer<QMimeData> mime; + mutable bool mac_mime_source; + static OSStatus promiseKeeper(OSPasteboardRef, PasteboardItemID, CFStringRef, void *); + void clear_helper(); +public: + QMacPasteboard(OSPasteboardRef p, uchar mime_type=0); + QMacPasteboard(uchar mime_type); + QMacPasteboard(CFStringRef name=0, uchar mime_type=0); + ~QMacPasteboard(); + + bool hasFlavor(QString flavor) const; + bool hasOSType(int c_flavor) const; + + OSPasteboardRef pasteBoard() const; + QMimeData *mimeData() const; + void setMimeData(QMimeData *mime); + + QStringList formats() const; + bool hasFormat(const QString &format) const; + QVariant retrieveData(const QString &format, QVariant::Type) const; + + void clear(); + bool sync() const; +}; + +extern QPaintDevice *qt_mac_safe_pdev; //qapplication_mac.cpp + +extern OSWindowRef qt_mac_window_for(const QWidget*); //qwidget_mac.mm +extern OSViewRef qt_mac_nativeview_for(const QWidget *); //qwidget_mac.mm +extern QPoint qt_mac_nativeMapFromParent(const QWidget *child, const QPoint &pt); //qwidget_mac.mm + +#ifdef check +# undef check +#endif + +QFont qfontForThemeFont(ThemeFontID themeID); + +QColor qcolorForTheme(ThemeBrush brush); + +QColor qcolorForThemeTextColor(ThemeTextColor themeColor); + +struct QMacDndAnswerRecord { + QRect rect; + Qt::KeyboardModifiers modifiers; + Qt::MouseButtons buttons; + Qt::DropAction lastAction; + unsigned int lastOperation; + void clear() { + rect = QRect(); + modifiers = Qt::NoModifier; + buttons = Qt::NoButton; + lastAction = Qt::IgnoreAction; + lastOperation = 0; + } +}; +extern QMacDndAnswerRecord qt_mac_dnd_answer_rec; +void qt_mac_copy_answer_rect(const QDragMoveEvent &event); +bool qt_mac_mouse_inside_answer_rect(QPoint mouse); + +QT_END_NAMESPACE + +#endif // QT_MAC_P_H diff --git a/src/gui/kernel/qt_s60_p.h b/src/gui/kernel/qt_s60_p.h new file mode 100644 index 0000000000..8aba53a168 --- /dev/null +++ b/src/gui/kernel/qt_s60_p.h @@ -0,0 +1,625 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_S60_P_H +#define QT_S60_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 "QtGui/qwindowdefs.h" +#include "private/qcore_symbian_p.h" +#include "qhash.h" +#include "qpoint.h" +#include "QtGui/qfont.h" +#include "QtGui/qimage.h" +#include "QtGui/qevent.h" +#include "qpointer.h" +#include "qapplication.h" +#include "qelapsedtimer.h" +#include "QtCore/qthreadstorage.h" +#include "qwidget_p.h" +#include <w32std.h> +#include <coecntrl.h> +#include <eikenv.h> +#include <eikappui.h> + +#ifdef Q_WS_S60 +#include <AknUtils.h> // AknLayoutUtils +#include <avkon.hrh> // EEikStatusPaneUidTitle +#include <akntitle.h> // CAknTitlePane +#include <akncontext.h> // CAknContextPane +#include <eikspane.h> // CEikStatusPane +#include <AknPopupFader.h> // MAknFadedComponent and TAknPopupFader +#include <gfxtranseffect/gfxtranseffect.h> // BeginFullScreen +#ifdef QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H +#include <akntranseffect.h> // BeginFullScreen +#endif +#endif + +QT_BEGIN_NAMESPACE + +// Application internal HandleResourceChangeL events, +// system events seems to start with 0x10 +const TInt KInternalStatusPaneChange = 0x50000000; + +// For BeginFullScreen(). +const TUint KQtAppExitFlag = 0x400; + +static const int qt_symbian_max_screens = 4; + +//this macro exists because EColor16MAP enum value doesn't exist in Symbian OS 9.2 +#define Q_SYMBIAN_ECOLOR16MAP TDisplayMode(13) + +class Q_AUTOTEST_EXPORT QS60ThreadLocalData +{ +public: + QS60ThreadLocalData(); + ~QS60ThreadLocalData(); + bool usingCONEinstances; + RWsSession wsSession; + CWsScreenDevice *screenDevice; +}; + +class QS60Data +{ +public: + QS60Data(); + QThreadStorage<QS60ThreadLocalData *> tls; + TUid uid; + int screenDepth; + QPoint lastCursorPos; + QPoint lastPointerEventPos; + QPointer<QWidget> lastPointerEventTarget; + QPointer<QWidget> mousePressTarget; + int screenWidthInPixels; + int screenHeightInPixels; + int screenWidthInTwips; + int screenHeightInTwips; + int defaultDpiX; + int defaultDpiY; + WId curWin; + enum PressedKeys { + Select = 0x1, + Right = 0x2, + Down = 0x4, + Left = 0x8, + Up = 0x10, + LeftUp = 0x20, + RightUp = 0x40, + RightDown = 0x80, + LeftDown = 0x100 + }; + int virtualMousePressedKeys; // of the above type, but avoids casting problems + int virtualMouseAccelDX; + int virtualMouseAccelDY; + QElapsedTimer virtualMouseAccelTimeout; + int virtualMouseMaxAccel; +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + int brokenPointerCursors : 1; +#endif + int hasTouchscreen : 1; + int mouseInteractionEnabled : 1; + int virtualMouseRequired : 1; + int qtOwnsS60Environment : 1; + int supportsPremultipliedAlpha : 1; + int avkonComponentsSupportTransparency : 1; + int menuBeingConstructed : 1; + int orientationSet : 1; + int partial_keyboard : 1; + QApplication::QS60MainApplicationFactory s60ApplicationFactory; // typedef'ed pointer type + QPointer<QWidget> splitViewLastWidget; + + static CEikButtonGroupContainer *cba; + + enum ScanCodeState { + Unpressed, + KeyDown, + KeyDownAndKey + }; + QHash<TInt, ScanCodeState> scanCodeStates; + + static inline void updateScreenSize(); + inline RWsSession& wsSession(); + static inline int screenCount(); + static inline RWindowGroup& windowGroup(); + static inline RWindowGroup& windowGroup(const QWidget *widget); + static inline RWindowGroup& windowGroup(int screenNumber); + inline CWsScreenDevice* screenDevice(); + inline CWsScreenDevice* screenDevice(const QWidget *widget); + inline CWsScreenDevice* screenDevice(int screenNumber); + static inline int screenNumberForWidget(const QWidget *widget); + static inline CCoeAppUi* appUi(); + static inline CEikMenuBar* menuBar(); +#ifdef Q_WS_S60 + static inline CEikStatusPane* statusPane(); + static inline CCoeControl* statusPaneSubPane(TInt aPaneId); + static inline CAknTitlePane* titlePane(); + static inline CAknContextPane* contextPane(); + static inline CEikButtonGroupContainer* buttonGroupContainer(); + static inline void setButtonGroupContainer(CEikButtonGroupContainer* newCba); + static void setStatusPaneAndButtonGroupVisibility(bool statusPaneVisible, bool buttonGroupVisible); + static bool setRecursiveDecorationsVisibility(QWidget *window, Qt::WindowStates newState); +#endif + static void controlVisibilityChanged(CCoeControl *control, bool visible); + +#ifdef Q_OS_SYMBIAN + TTrapHandler *s60InstalledTrapHandler; +#endif + + int screenWidthInPixelsForScreen[qt_symbian_max_screens]; + int screenHeightInPixelsForScreen[qt_symbian_max_screens]; + int screenWidthInTwipsForScreen[qt_symbian_max_screens]; + int screenHeightInTwipsForScreen[qt_symbian_max_screens]; + + int nativeScreenWidthInPixels; + int nativeScreenHeightInPixels; + + int beginFullScreenCalled : 1; + int endFullScreenCalled : 1; +}; + +Q_AUTOTEST_EXPORT QS60Data* qGlobalS60Data(); +#define S60 qGlobalS60Data() + +class QAbstractLongTapObserver +{ +public: + virtual void HandleLongTapEventL( const TPoint& aPenEventLocation, + const TPoint& aPenEventScreenLocation ) = 0; +}; +class QLongTapTimer; + + +class QSymbianControl : public CCoeControl, public QAbstractLongTapObserver +#ifdef Q_WS_S60 +, public MAknFadedComponent, public MEikStatusPaneObserver +#endif +{ +public: + DECLARE_TYPE_ID(0x51740000) // Fun fact: the two first values are "Qt" in ASCII. + +public: + QSymbianControl(QWidget *w); + void ConstructL(bool isWindowOwning = false, bool desktop = false); + ~QSymbianControl(); + void HandleResourceChange(int resourceType); + void HandlePointerEventL(const TPointerEvent& aPointerEvent); + TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType); +#if !defined(QT_NO_IM) && defined(Q_WS_S60) + TCoeInputCapabilities InputCapabilities() const; +#endif + TTypeUid::Ptr MopSupplyObject(TTypeUid id); + + inline QWidget* widget() const { return qwidget; } + void setWidget(QWidget *w); + void sendInputEvent(QWidget *widget, QInputEvent *inputEvent); + void setIgnoreFocusChanged(bool enabled) { m_ignoreFocusChanged = enabled; } + void CancelLongTapTimer(); + + void setFocusSafely(bool focus); + + bool isControlActive(); + + void ensureFixNativeOrientation(); + QPoint translatePointForFixedNativeOrientation(const TPoint &pointerEventPos) const; + TRect translateRectForFixedNativeOrientation(const TRect &controlRect) const; + +#ifdef Q_WS_S60 + void FadeBehindPopup(bool fade){ popupFader.FadeBehindPopup( this, this, fade); } + void HandleStatusPaneSizeChange(); + +protected: // from MAknFadedComponent + TInt CountFadedComponents() {return 1;} + CCoeControl* FadedComponent(TInt /*aIndex*/) {return this;} +#else + // #warning No fallback implementation for QSymbianControl::FadeBehindPopup + void FadeBehindPopup(bool /*fade*/){ } +#endif + +protected: + void Draw(const TRect& aRect) const; + void SizeChanged(); + void PositionChanged(); + void FocusChanged(TDrawNow aDrawNow); + +protected: + void qwidgetResize_helper(const QSize &newSize); + +private: + void HandlePointerEvent(const TPointerEvent& aPointerEvent); + TKeyResponse OfferKeyEvent(const TKeyEvent& aKeyEvent,TEventCode aType); + TKeyResponse sendSymbianKeyEvent(const TKeyEvent &keyEvent, QEvent::Type type); + TKeyResponse sendKeyEvent(QWidget *widget, QKeyEvent *keyEvent); + TKeyResponse handleVirtualMouse(const TKeyEvent& keyEvent,TEventCode type); + bool sendMouseEvent(QWidget *widget, QMouseEvent *mEvent); + void sendMouseEvent( + QWidget *receiver, + QEvent::Type type, + const QPoint &globalPos, + Qt::MouseButton button, + Qt::KeyboardModifiers modifiers); + void processTouchEvent(int pointerNumber, TPointerEvent::TType type, QPointF screenPos, qreal pressure); + void HandleLongTapEventL( const TPoint& aPenEventLocation, const TPoint& aPenEventScreenLocation ); +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + void translateAdvancedPointerEvent(const TAdvancedPointerEvent *event); +#endif + bool isSplitViewWidget(QWidget *widget); + +public: + void handleClientAreaChange(); + +private: + static QSymbianControl *lastFocusedControl; + +private: + QWidget *qwidget; + QLongTapTimer* m_longTapDetector; + QElapsedTimer m_doubleClickTimer; + bool m_ignoreFocusChanged : 1; + bool m_symbianPopupIsOpen : 1; + +#ifdef Q_WS_S60 + // Fader object used to fade everything except this menu and the CBA. + TAknPopupFader popupFader; +#endif + + bool m_inExternalScreenOverride : 1; + bool m_lastStatusPaneVisibility : 1; +}; + +inline QS60Data::QS60Data() +: uid(TUid::Null()), + screenDepth(0), + screenWidthInPixels(0), + screenHeightInPixels(0), + screenWidthInTwips(0), + screenHeightInTwips(0), + defaultDpiX(0), + defaultDpiY(0), + curWin(0), + virtualMousePressedKeys(0), + virtualMouseAccelDX(0), + virtualMouseAccelDY(0), + virtualMouseMaxAccel(0), +#ifndef Q_SYMBIAN_FIXED_POINTER_CURSORS + brokenPointerCursors(0), +#endif + hasTouchscreen(0), + mouseInteractionEnabled(0), + virtualMouseRequired(0), + qtOwnsS60Environment(0), + supportsPremultipliedAlpha(0), + avkonComponentsSupportTransparency(0), + menuBeingConstructed(0), + orientationSet(0), + partial_keyboard(0), + s60ApplicationFactory(0) +#ifdef Q_OS_SYMBIAN + ,s60InstalledTrapHandler(0) +#endif + ,beginFullScreenCalled(0), + endFullScreenCalled(0) +{ +} + +inline void QS60Data::updateScreenSize() +{ + CWsScreenDevice *dev = S60->screenDevice(); + int screenModeCount = dev->NumScreenModes(); + int mode = dev->CurrentScreenMode(); + TPixelsTwipsAndRotation params; + dev->GetScreenModeSizeAndRotation(mode, params); + S60->screenWidthInPixels = params.iPixelSize.iWidth; + S60->screenHeightInPixels = params.iPixelSize.iHeight; + S60->screenWidthInTwips = params.iTwipsSize.iWidth; + S60->screenHeightInTwips = params.iTwipsSize.iHeight; + + S60->virtualMouseMaxAccel = qMax(S60->screenHeightInPixels, S60->screenWidthInPixels) / 10; + + TReal inches = S60->screenHeightInTwips / (TReal)KTwipsPerInch; + S60->defaultDpiY = S60->screenHeightInPixels / inches; + inches = S60->screenWidthInTwips / (TReal)KTwipsPerInch; + S60->defaultDpiX = S60->screenWidthInPixels / inches; + + int screens = S60->screenCount(); + for (int i = 0; i < screens; ++i) { + CWsScreenDevice *dev = S60->screenDevice(i); + mode = dev->CurrentScreenMode(); + dev->GetScreenModeSizeAndRotation(mode, params); + S60->screenWidthInPixelsForScreen[i] = params.iPixelSize.iWidth; + S60->screenHeightInPixelsForScreen[i] = params.iPixelSize.iHeight; + S60->screenWidthInTwipsForScreen[i] = params.iTwipsSize.iWidth; + S60->screenHeightInTwipsForScreen[i] = params.iTwipsSize.iHeight; + } + + // Look for a screen mode with rotation 0 + // in order to decide what the native orientation is. + for (mode = 0; mode < screenModeCount; ++mode) { + TPixelsAndRotation sizeAndRotation; + dev->GetScreenModeSizeAndRotation(mode, sizeAndRotation); + if (sizeAndRotation.iRotation == CFbsBitGc::EGraphicsOrientationNormal) { + S60->nativeScreenWidthInPixels = sizeAndRotation.iPixelSize.iWidth; + S60->nativeScreenHeightInPixels = sizeAndRotation.iPixelSize.iHeight; + break; + } + } +} + +inline RWsSession& QS60Data::wsSession() +{ + if(!tls.hasLocalData()) { + tls.setLocalData(new QS60ThreadLocalData); + } + return tls.localData()->wsSession; +} + +inline int QS60Data::screenCount() +{ +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + CCoeEnv *env = CCoeEnv::Static(); + if (env) { + return qMin(env->WsSession().NumberOfScreens(), qt_symbian_max_screens); + } +#endif + return 1; +} + +inline RWindowGroup& QS60Data::windowGroup() +{ + return CCoeEnv::Static()->RootWin(); +} + +inline RWindowGroup& QS60Data::windowGroup(const QWidget *widget) +{ + return windowGroup(screenNumberForWidget(widget)); +} + +inline RWindowGroup& QS60Data::windowGroup(int screenNumber) +{ +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + RWindowGroup *wg = CCoeEnv::Static()->RootWin(screenNumber); + return wg ? *wg : windowGroup(); +#else + Q_UNUSED(screenNumber); + return windowGroup(); +#endif +} + +inline CWsScreenDevice* QS60Data::screenDevice() +{ + if(!tls.hasLocalData()) { + tls.setLocalData(new QS60ThreadLocalData); + } + return tls.localData()->screenDevice; +} + +inline CWsScreenDevice* QS60Data::screenDevice(const QWidget *widget) +{ + return screenDevice(screenNumberForWidget(widget)); +} + +inline CWsScreenDevice* QS60Data::screenDevice(int screenNumber) +{ +#if defined(Q_SYMBIAN_SUPPORTS_MULTIPLE_SCREENS) + CCoeEnv *env = CCoeEnv::Static(); + if (env) { + CWsScreenDevice *dev = env->ScreenDevice(screenNumber); + return dev ? dev : screenDevice(); + } else { + return screenDevice(); + } +#else + return screenDevice(); +#endif +} + +inline int QS60Data::screenNumberForWidget(const QWidget *widget) +{ + if (!widget) + return 0; + const QWidget *w = widget; + while (w->parentWidget()) + w = w->parentWidget(); + return qt_widget_private(const_cast<QWidget *>(w))->symbianScreenNumber; +} + +inline CCoeAppUi* QS60Data::appUi() +{ + return CCoeEnv::Static()-> AppUi(); +} + +inline CEikMenuBar* QS60Data::menuBar() +{ + return CEikonEnv::Static()->AppUiFactory()->MenuBar(); +} + +#ifdef Q_WS_S60 +inline CEikStatusPane* QS60Data::statusPane() +{ + return CEikonEnv::Static()->AppUiFactory()->StatusPane(); +} + +// Returns the application's status pane control, if not present returns NULL. +inline CCoeControl* QS60Data::statusPaneSubPane( TInt aPaneId ) +{ + const TUid paneUid = { aPaneId }; + CEikStatusPane* statusPane = S60->statusPane(); + if (statusPane && statusPane->PaneCapabilities(paneUid).IsPresent()) { + CCoeControl* control = NULL; + // ControlL shouldn't leave because the pane is present + TRAPD(err, control = statusPane->ControlL(paneUid)); + return err != KErrNone ? NULL : control; + } + return NULL; +} + +// Returns the application's title pane, if not present returns NULL. +inline CAknTitlePane* QS60Data::titlePane() +{ + return static_cast<CAknTitlePane*>(S60->statusPaneSubPane(EEikStatusPaneUidTitle)); +} + +// Returns the application's title pane, if not present returns NULL. +inline CAknContextPane* QS60Data::contextPane() +{ + return static_cast<CAknContextPane*>(S60->statusPaneSubPane(EEikStatusPaneUidContext)); +} + +inline CEikButtonGroupContainer* QS60Data::buttonGroupContainer() +{ + return QS60Data::cba; +} + +inline void QS60Data::setButtonGroupContainer(CEikButtonGroupContainer *newCba) +{ + QS60Data::cba = newCba; +} +#endif // Q_WS_S60 + +static inline QFont qt_TFontSpec2QFontL(const TFontSpec &fontSpec) +{ + return QFont( + qt_TDesC2QString(fontSpec.iTypeface.iName), + fontSpec.iHeight / KTwipsPerPoint, + fontSpec.iFontStyle.StrokeWeight() == EStrokeWeightNormal ? QFont::Normal : QFont::Bold, + fontSpec.iFontStyle.Posture() == EPostureItalic + ); +} + +static inline QImage::Format qt_TDisplayMode2Format(TDisplayMode mode) +{ + QImage::Format format; + switch(mode) { + case EGray2: + format = QImage::Format_MonoLSB; + break; + case EColor256: + case EGray256: + format = QImage::Format_Indexed8; + break; + case EColor4K: + format = QImage::Format_RGB444; + break; + case EColor64K: + format = QImage::Format_RGB16; + break; + case EColor16M: + format = QImage::Format_RGB888; + break; + case EColor16MU: + format = QImage::Format_RGB32; + break; + case EColor16MA: + format = QImage::Format_ARGB32; + break; + case Q_SYMBIAN_ECOLOR16MAP: + format = QImage::Format_ARGB32_Premultiplied; + break; + default: + format = QImage::Format_Invalid; + break; + } + return format; +} + +#ifndef QT_NO_CURSOR +void qt_symbian_setWindowCursor(const QCursor &cursor, const CCoeControl* wid); +void qt_symbian_setWindowGroupCursor(const QCursor &cursor, RWindowTreeNode &node); +void qt_symbian_setGlobalCursor(const QCursor &cursor); +void qt_symbian_set_cursor_visible(bool visible); +bool qt_symbian_is_cursor_visible(); +#endif + +static inline bool qt_beginFullScreenEffect() +{ +#if defined(Q_WS_S60) && defined(QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H) + // Only for post-S^3. On earlier versions the system transition effects + // may not be able to capture the non-Avkon content, leading to confusing + // looking effects, so just skip the whole thing. + if (S60->beginFullScreenCalled || QSysInfo::s60Version() <= QSysInfo::SV_S60_5_2) + return false; + S60->beginFullScreenCalled = true; + // For Avkon apps the app-exit effect is triggered from CAknAppUi::PrepareToExit(). + // That is good for Avkon apps, but in case of Qt the RWindows are destroyed earlier. + // Therefore we call BeginFullScreen() ourselves. + GfxTransEffect::BeginFullScreen(AknTransEffect::EApplicationExit, + TRect(0, 0, 0, 0), + AknTransEffect::EParameterType, + AknTransEffect::GfxTransParam(S60->uid, + AknTransEffect::TParameter::EAvkonCheck | KQtAppExitFlag)); + return true; +#else + return false; +#endif +} + +static inline void qt_abortFullScreenEffect() +{ +#if defined(Q_WS_S60) && defined(QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H) + if (!S60->beginFullScreenCalled || QSysInfo::s60Version() <= QSysInfo::SV_S60_5_2) + return; + GfxTransEffect::AbortFullScreen(); + S60->beginFullScreenCalled = S60->endFullScreenCalled = false; +#endif +} + +static inline void qt_endFullScreenEffect() +{ +#if defined(Q_WS_S60) && defined(QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H) + if (S60->endFullScreenCalled || QSysInfo::s60Version() <= QSysInfo::SV_S60_5_2) + return; + S60->endFullScreenCalled = true; + GfxTransEffect::EndFullScreen(); +#endif +} + +QT_END_NAMESPACE + +#endif // QT_S60_P_H diff --git a/src/gui/kernel/qt_x11_p.h b/src/gui/kernel/qt_x11_p.h new file mode 100644 index 0000000000..69079cfaad --- /dev/null +++ b/src/gui/kernel/qt_x11_p.h @@ -0,0 +1,757 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_X11_P_H +#define QT_X11_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 "QtGui/qwindowdefs.h" +#include "QtCore/qlist.h" +#include "QtCore/qvariant.h" + +// the following is necessary to work around breakage in many versions +// of XFree86's Xlib.h still in use +// ### which versions? +#if defined(_XLIB_H_) // crude hack, but... +#error "cannot include <X11/Xlib.h> before this file" +#endif +#define XRegisterIMInstantiateCallback qt_XRegisterIMInstantiateCallback +#define XUnregisterIMInstantiateCallback qt_XUnregisterIMInstantiateCallback +#define XSetIMValues qt_XSetIMValues +#include <X11/Xlib.h> +#undef XRegisterIMInstantiateCallback +#undef XUnregisterIMInstantiateCallback +#undef XSetIMValues + +#include <X11/Xutil.h> +#include <X11/Xos.h> +#ifdef index +# undef index +#endif +#ifdef rindex +# undef rindex +#endif +#ifdef Q_OS_VXWORS +# ifdef open +# undef open +# endif +# ifdef getpid +# undef getpid +# endif +#endif // Q_OS_VXWORKS +#include <X11/Xatom.h> + +//#define QT_NO_SHAPE +#ifdef QT_NO_SHAPE +# define XShapeCombineRegion(a,b,c,d,e,f,g) +# define XShapeCombineMask(a,b,c,d,e,f,g) +#else +# include <X11/extensions/shape.h> +#endif // QT_NO_SHAPE + + +#if !defined (QT_NO_TABLET) +# include <X11/extensions/XInput.h> +#if defined (Q_OS_IRIX) +# include <X11/extensions/SGIMisc.h> +# include <wacom.h> +#endif +#endif // QT_NO_TABLET + + +// #define QT_NO_XINERAMA +#ifndef QT_NO_XINERAMA +# if 0 // ### Xsun, but how to detect it? +// Xinerama is only supported in Solaris 7 with patches 107648/108376 and +// Solaris 8 or above which introduce the X11R6.4 Xserver. +// To switch the Xinerama functionality on, you need to add the "+xinerama" +// argument to the Xsun start line. +// At least Solaris 7 and 8 are missing Xinerama system headers and function +// declarations (bug 4284701). +// The Xinerama API is not documented. In theory it could change but it +// probably won't because Sun are using it in at least dtlogin (bug 4221829). +extern "C" Bool XPanoramiXQueryExtension( + Display*, + int*, + int* +); +extern "C" Status XPanoramiXQueryVersion( + Display*, + int*, + int* +); +extern "C" Status XPanoramiXGetState( + Display*, + Drawable, + XPanoramiXInfo* +); +extern "C" Status XPanoramiXGetScreenCount( + Display *, + Drawable, + XPanoramiXInfo* +); +extern "C" Status XPanoramiXGetScreenSize( + Display*, + Drawable, + int, + XPanoramiXInfo* +); +# else // XFree86 +// XFree86 does not C++ify Xinerama (at least up to XFree86 4.0.3). +extern "C" { +# include <X11/extensions/Xinerama.h> +} +# endif +#endif // QT_NO_XINERAMA + +// #define QT_NO_XRANDR +#ifndef QT_NO_XRANDR +# include <X11/extensions/Xrandr.h> +#endif // QT_NO_XRANDR + +// #define QT_NO_XRENDER +#ifndef QT_NO_XRENDER +# include <X11/extensions/Xrender.h> +#endif // QT_NO_XRENDER + +#ifndef QT_NO_XSYNC +extern "C" { +# include "X11/extensions/sync.h" +} +#endif + +// #define QT_NO_XKB +#ifndef QT_NO_XKB +# include <X11/XKBlib.h> +#endif // QT_NO_XKB + + +#if !defined(XlibSpecificationRelease) +# define X11R4 +typedef char *XPointer; +#else +# undef X11R4 +#endif + +// #define QT_NO_XIM +#if defined(X11R4) +// X11R4 does not have XIM +#define QT_NO_XIM +#elif defined(Q_OS_OSF) && (XlibSpecificationRelease < 6) +// broken in Xlib up to OSF/1 3.2 +#define QT_NO_XIM +#elif defined(Q_OS_AIX) +// broken in Xlib up to what version of AIX? +#define QT_NO_XIM +#elif defined(QT_NO_DEBUG) && defined(Q_OS_IRIX) +// XmbLookupString broken on IRIX +// XCreateIC broken when compiling -64 on IRIX 6.5.2 +#define QT_NO_XIM +#elif defined(Q_OS_HPUX) && defined(__LP64__) +// XCreateIC broken when compiling 64-bit ELF on HP-UX 11.0 +#define QT_NO_XIM +#elif defined(Q_OS_SCO) +// ### suggested by user... +// ### #define QT_NO_XIM +#endif // QT_NO_XIM + +#ifndef QT_NO_XFIXES +typedef Bool (*PtrXFixesQueryExtension)(Display *, int *, int *); +typedef Status (*PtrXFixesQueryVersion)(Display *, int *, int *); +typedef void (*PtrXFixesSetCursorName)(Display *dpy, Cursor cursor, const char *name); +typedef void (*PtrXFixesSelectSelectionInput)(Display *dpy, Window win, Atom selection, unsigned long eventMask); +#endif // QT_NO_XFIXES + +#ifndef QT_NO_XCURSOR +#include <X11/Xcursor/Xcursor.h> +typedef Cursor (*PtrXcursorLibraryLoadCursor)(Display *, const char *); +#endif // QT_NO_XCURSOR + +#ifndef QT_NO_XINERAMA +typedef Bool (*PtrXineramaQueryExtension)(Display *dpy, int *event_base, int *error_base); +typedef Bool (*PtrXineramaIsActive)(Display *dpy); +typedef XineramaScreenInfo *(*PtrXineramaQueryScreens)(Display *dpy, int *number); +#endif // QT_NO_XINERAMA + +#ifndef QT_NO_XRANDR +typedef void (*PtrXRRSelectInput)(Display *, Window, int); +typedef int (*PtrXRRUpdateConfiguration)(XEvent *); +typedef int (*PtrXRRRootToScreen)(Display *, Window); +typedef Bool (*PtrXRRQueryExtension)(Display *, int *, int *); +typedef XRRScreenSize *(*PtrXRRSizes)(Display *, int, int *); +#endif // QT_NO_XRANDR + +#ifndef QT_NO_XINPUT +typedef int (*PtrXCloseDevice)(Display *, XDevice *); +typedef XDeviceInfo* (*PtrXListInputDevices)(Display *, int *); +typedef XDevice* (*PtrXOpenDevice)(Display *, XID); +typedef void (*PtrXFreeDeviceList)(XDeviceInfo *); +typedef int (*PtrXSelectExtensionEvent)(Display *, Window, XEventClass *, int); +#endif // QT_NO_XINPUT + +/* + * Solaris patch 108652-47 and higher fixes crases in + * XRegisterIMInstantiateCallback, but the function doesn't seem to + * work. + * + * Instead, we disabled R6 input, and open the input method + * immediately at application start. + */ +#if !defined(QT_NO_XIM) && (XlibSpecificationRelease >= 6) && \ + !defined(Q_OS_SOLARIS) +#define USE_X11R6_XIM + +//######### XFree86 has wrong declarations for XRegisterIMInstantiateCallback +//######### and XUnregisterIMInstantiateCallback in at least version 3.3.2. +//######### Many old X11R6 header files lack XSetIMValues. +//######### Therefore, we have to declare these functions ourselves. + +extern "C" Bool XRegisterIMInstantiateCallback( + Display*, + struct _XrmHashBucketRec*, + char*, + char*, + XIMProc, //XFree86 has XIDProc, which has to be wrong + XPointer +); + +extern "C" Bool XUnregisterIMInstantiateCallback( + Display*, + struct _XrmHashBucketRec*, + char*, + char*, + XIMProc, //XFree86 has XIDProc, which has to be wrong + XPointer +); + +extern "C" char *XSetIMValues(XIM /* im */, ...); + +#endif + +#ifndef QT_NO_FONTCONFIG +#include <fontconfig/fontconfig.h> +#endif + +#ifndef QT_NO_XIM +// some platforms (eg. Solaris 2.51) don't have these defines in Xlib.h +#ifndef XNResetState +#define XNResetState "resetState" +#endif +#ifndef XIMPreserveState +#define XIMPreserveState (1L<<1) +#endif +#endif + + +#ifndef X11R4 +# include <X11/Xlocale.h> +#endif // X11R4 + + +#ifndef QT_NO_MITSHM +# include <X11/extensions/XShm.h> +#endif // QT_NO_MITSHM + +QT_BEGIN_NAMESPACE + +class QWidget; + +struct QX11InfoData { + uint ref; + int screen; + int dpiX; + int dpiY; + int depth; + int cells; + Colormap colormap; + Visual *visual; + bool defaultColormap; + bool defaultVisual; + int subpixel; +}; + +class QDrag; +struct QXdndDropTransaction +{ + Time timestamp; + Window target; + Window proxy_target; + QWidget *targetWidget; + QWidget *embedding_widget; + QDrag *object; +}; + +class QMimeData; + +struct QX11Data; +extern Q_GUI_EXPORT QX11Data *qt_x11Data; + +enum DesktopEnvironment { + DE_UNKNOWN, + DE_KDE, + DE_GNOME, + DE_CDE, + DE_MEEGO_COMPOSITOR, + DE_4DWM +}; + +struct QX11Data +{ + static Qt::KeyboardModifiers translateModifiers(int s); + + Window findClientWindow(Window, Atom, bool); + + // from qclipboard_x11.cpp + bool clipboardWaitForEvent(Window win, int type, XEvent *event, int timeout); + bool clipboardReadProperty(Window win, Atom property, bool deleteProperty, + QByteArray *buffer, int *size, Atom *type, int *format); + QByteArray clipboardReadIncrementalProperty(Window win, Atom property, int nbytes, bool nullterm); + + // from qdnd_x11.cpp + bool dndEnable(QWidget* w, bool on); + static void xdndSetup(); + void xdndHandleEnter(QWidget *, const XEvent *, bool); + void xdndHandlePosition(QWidget *, const XEvent *, bool); + void xdndHandleStatus(QWidget *, const XEvent *, bool); + void xdndHandleLeave(QWidget *, const XEvent *, bool); + void xdndHandleDrop(QWidget *, const XEvent *, bool); + void xdndHandleFinished(QWidget *, const XEvent *, bool); + void xdndHandleSelectionRequest(const XSelectionRequestEvent *); + static bool xdndHandleBadwindow(); + QByteArray xdndAtomToString(Atom a); + Atom xdndStringToAtom(const char *); + + QString xdndMimeAtomToString(Atom a); + Atom xdndMimeStringToAtom(const QString &mimeType); + QStringList xdndMimeFormatsForAtom(Atom a); + bool xdndMimeDataForAtom(Atom a, QMimeData *mimeData, QByteArray *data, Atom *atomFormat, int *dataFormat); + QList<Atom> xdndMimeAtomsForFormat(const QString &format); + QVariant xdndMimeConvertToFormat(Atom a, const QByteArray &data, const QString &format, QVariant::Type requestedType, const QByteArray &encoding); + Atom xdndMimeAtomForFormat(const QString &format, QVariant::Type requestedType, const QList<Atom> &atoms, QByteArray *requestedEncoding); + + QList<QXdndDropTransaction> dndDropTransactions; + + // from qmotifdnd_x11.cpp + void motifdndHandle(QWidget *, const XEvent *, bool); + void motifdndEnable(QWidget *, bool); + QVariant motifdndObtainData(const char *format); + QByteArray motifdndFormat(int n); + bool motifdnd_active; + + Display *display; + char *displayName; + bool foreignDisplay; + // current focus model + enum { + FM_Unknown = -1, + FM_Other = 0, + FM_PointerRoot = 1 + }; + int focus_model; + + // true if Qt is compiled w/ RANDR support and RANDR is supported on the connected Display + bool use_xrandr; + int xrandr_major; + int xrandr_eventbase; + int xrandr_errorbase; + + // true if Qt is compiled w/ RENDER support and RENDER is supported on the connected Display + bool use_xrender; + int xrender_major; + int xrender_version; + + // true if Qt is compiled w/ XFIXES support and XFIXES is supported on the connected Display + bool use_xfixes; + int xfixes_major; + int xfixes_eventbase; + int xfixes_errorbase; + +#ifndef QT_NO_XFIXES + PtrXFixesQueryExtension ptrXFixesQueryExtension; + PtrXFixesQueryVersion ptrXFixesQueryVersion; + PtrXFixesSetCursorName ptrXFixesSetCursorName; + PtrXFixesSelectSelectionInput ptrXFixesSelectSelectionInput; +#endif + +#ifndef QT_NO_XINPUT + PtrXCloseDevice ptrXCloseDevice; + PtrXListInputDevices ptrXListInputDevices; + PtrXOpenDevice ptrXOpenDevice; + PtrXFreeDeviceList ptrXFreeDeviceList; + PtrXSelectExtensionEvent ptrXSelectExtensionEvent; +#endif // QT_NO_XINPUT + + + // true if Qt is compiled w/ MIT-SHM support and MIT-SHM is supported on the connected Display + bool use_mitshm; + bool use_mitshm_pixmaps; + int mitshm_major; + + // true if Qt is compiled w/ Tablet support and we have a tablet. + bool use_xinput; + int xinput_major; + int xinput_eventbase; + int xinput_errorbase; + + // for XKEYBOARD support + bool use_xkb; + int xkb_major; + int xkb_eventbase; + int xkb_errorbase; + + QList<QWidget *> deferred_map; + struct ScrollInProgress { + long id; + QWidget* scrolled_widget; + int dx, dy; + }; + long sip_serial; + QList<ScrollInProgress> sip_list; + + // window managers list of supported "stuff" + Atom *net_supported_list; + // list of virtual root windows + Window *net_virtual_root_list; + // client leader window + Window wm_client_leader; + + QX11InfoData *screens; + Visual **argbVisuals; + Colormap *argbColormaps; + int screenCount; + int defaultScreen; + + Time time; + Time userTime; + + QString default_im; + + // starts to ignore bad window errors from X + static inline void ignoreBadwindow() { + qt_x11Data->ignore_badwindow = true; + qt_x11Data->seen_badwindow = false; + } + + // ends ignoring bad window errors and returns whether an error had happened. + static inline bool badwindow() { + qt_x11Data->ignore_badwindow = false; + return qt_x11Data->seen_badwindow; + } + + bool ignore_badwindow; + bool seen_badwindow; + + // options + int visual_class; + int visual_id; + int color_count; + bool custom_cmap; + + // outside visual/colormap + Visual *visual; + Colormap colormap; + +#ifndef QT_NO_XRENDER + enum { solid_fill_count = 16 }; + struct SolidFills { + XRenderColor color; + int screen; + Picture picture; + } solid_fills[solid_fill_count]; + enum { pattern_fill_count = 16 }; + struct PatternFills { + XRenderColor color; + XRenderColor bg_color; + int screen; + int style; + bool opaque; + Picture picture; + } pattern_fills[pattern_fill_count]; + Picture getSolidFill(int screen, const QColor &c); + XRenderColor preMultiply(const QColor &c); +#endif + + bool has_fontconfig; + qreal fc_scale; + bool fc_antialias; + int fc_hint_style; + + char *startupId; + + DesktopEnvironment desktopEnvironment : 8; + uint desktopVersion : 8; /* Used only for KDE */ + + /* Warning: if you modify this list, modify the names of atoms in qapplication_x11.cpp as well! */ + enum X11Atom { + // 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, + + // ICCCM window state + WM_STATE, + WM_CHANGE_STATE, + + // Session management + WM_CLIENT_LEADER, + WM_WINDOW_ROLE, + SM_CLIENT_ID, + + // Clipboard + CLIPBOARD, + INCR, + TARGETS, + MULTIPLE, + TIMESTAMP, + SAVE_TARGETS, + CLIP_TEMPORARY, + _QT_SELECTION, + _QT_CLIPBOARD_SENTINEL, + _QT_SELECTION_SENTINEL, + CLIPBOARD_MANAGER, + + RESOURCE_MANAGER, + + _XSETROOT_ID, + + _QT_SCROLL_DONE, + _QT_INPUT_ENCODING, + + _MOTIF_WM_HINTS, + + DTWM_IS_RUNNING, + ENLIGHTENMENT_DESKTOP, + _DT_SAVE_MODE, + _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_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_STARTUP_INFO, + _NET_STARTUP_INFO_BEGIN, + + _NET_SUPPORTING_WM_CHECK, + + _NET_WM_CM_S0, + + _NET_SYSTEM_TRAY_VISUAL, + + _NET_ACTIVE_WINDOW, + + // Property formats + COMPOUND_TEXT, + TEXT, + UTF8_STRING, + + // Xdnd + XdndEnter, + XdndPosition, + XdndStatus, + XdndLeave, + XdndDrop, + XdndFinished, + XdndTypelist, + XdndActionList, + + XdndSelection, + + XdndAware, + XdndProxy, + + XdndActionCopy, + XdndActionLink, + XdndActionMove, + XdndActionPrivate, + + // Motif DND + _MOTIF_DRAG_AND_DROP_MESSAGE, + _MOTIF_DRAG_INITIATOR_INFO, + _MOTIF_DRAG_RECEIVER_INFO, + _MOTIF_DRAG_WINDOW, + _MOTIF_DRAG_TARGETS, + + XmTRANSFER_SUCCESS, + XmTRANSFER_FAILURE, + + // Xkb + _XKB_RULES_NAMES, + + // XEMBED + _XEMBED, + _XEMBED_INFO, + + XWacomStylus, + XWacomCursor, + XWacomEraser, + + XTabletStylus, + XTabletEraser, + + NPredefinedAtoms, + + _QT_SETTINGS_TIMESTAMP = NPredefinedAtoms, + NAtoms + }; + Atom atoms[NAtoms]; + + bool isSupportedByWM(Atom atom); + + bool compositingManagerRunning; + +#ifndef QT_NO_XCURSOR + PtrXcursorLibraryLoadCursor ptrXcursorLibraryLoadCursor; +#endif // QT_NO_XCURSOR + +#ifndef QT_NO_XINERAMA + PtrXineramaQueryExtension ptrXineramaQueryExtension; + PtrXineramaIsActive ptrXineramaIsActive; + PtrXineramaQueryScreens ptrXineramaQueryScreens; +#endif // QT_NO_XINERAMA + +#ifndef QT_NO_XRANDR + PtrXRRSelectInput ptrXRRSelectInput; + PtrXRRUpdateConfiguration ptrXRRUpdateConfiguration; + PtrXRRRootToScreen ptrXRRRootToScreen; + PtrXRRQueryExtension ptrXRRQueryExtension; + PtrXRRSizes ptrXRRSizes; +#endif // QT_NO_XRANDR +}; + +extern QX11Data *qt_x11Data; +#define ATOM(x) qt_x11Data->atoms[QX11Data::x] +#define X11 qt_x11Data + +// rename a couple of X defines to get rid of name clashes +// resolve the conflict between X11's FocusIn and QEvent::FocusIn +enum { + XFocusOut = FocusOut, + XFocusIn = FocusIn, + XKeyPress = KeyPress, + XKeyRelease = KeyRelease, + XNone = None, + XRevertToParent = RevertToParent, + XGrayScale = GrayScale, + XCursorShape = CursorShape +}; +#undef FocusOut +#undef FocusIn +#undef KeyPress +#undef KeyRelease +#undef None +#undef RevertToParent +#undef GrayScale +#undef CursorShape + +#ifdef FontChange +#undef FontChange +#endif + +Q_DECLARE_TYPEINFO(XPoint, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XRectangle, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XChar2b, Q_PRIMITIVE_TYPE); +#ifndef QT_NO_XRENDER +Q_DECLARE_TYPEINFO(XGlyphElt32, Q_PRIMITIVE_TYPE); +#endif + + +QT_END_NAMESPACE + +#endif // QT_X11_P_H diff --git a/src/gui/kernel/qtooltip.cpp b/src/gui/kernel/qtooltip.cpp new file mode 100644 index 0000000000..4311df58a6 --- /dev/null +++ b/src/gui/kernel/qtooltip.cpp @@ -0,0 +1,623 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifdef Q_WS_MAC +# include <private/qcore_mac_p.h> +#endif + +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qevent.h> +#include <qhash.h> +#include <qlabel.h> +#include <qpointer.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qstylepainter.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <private/qeffects_p.h> +#include <qtextdocument.h> +#include <qdebug.h> +#include <private/qstylesheetstyle_p.h> +#ifndef QT_NO_TOOLTIP + +#ifdef Q_WS_MAC +# include <private/qcore_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QToolTip + + \brief The QToolTip class provides tool tips (balloon help) for any + widget. + + \ingroup helpsystem + + + The tip is a short piece of text reminding the user of the + widget's function. It is drawn immediately below the given + position in a distinctive black-on-yellow color combination. The + tip can be any \l{QTextEdit}{rich text} formatted string. + + Rich text displayed in a tool tip is implicitly word-wrapped unless + specified differently with \c{<p style='white-space:pre'>}. + + The simplest and most common way to set a widget's tool tip is by + calling its QWidget::setToolTip() function. + + It is also possible to show different tool tips for different + regions of a widget, by using a QHelpEvent of type + QEvent::ToolTip. Intercept the help event in your widget's \l + {QWidget::}{event()} function and call QToolTip::showText() with + the text you want to display. The \l{widgets/tooltips}{Tooltips} + example illustrates this technique. + + If you are calling QToolTip::hideText(), or QToolTip::showText() + with an empty string, as a result of a \l{QEvent::}{ToolTip}-event you + should also call \l{QEvent::}{ignore()} on the event, to signal + that you don't want to start any tooltip specific modes. + + Note that, if you want to show tooltips in an item view, the + model/view architecture provides functionality to set an item's + tool tip; e.g., the QTableWidgetItem::setToolTip() function. + However, if you want to provide custom tool tips in an item view, + you must intercept the help event in the + QAbstractItemView::viewportEvent() function and handle it yourself. + + The default tool tip color and font can be customized with + setPalette() and setFont(). When a tooltip is currently on + display, isVisible() returns true and text() the currently visible + text. + + \note Tool tips use the inactive color group of QPalette, because tool + tips are not active windows. + + \sa QWidget::toolTip, QAction::toolTip, {Tool Tips Example} +*/ + +class QTipLabel : public QLabel +{ + Q_OBJECT +public: + QTipLabel(const QString &text, QWidget *w); + ~QTipLabel(); + static QTipLabel *instance; + + bool eventFilter(QObject *, QEvent *); + + QBasicTimer hideTimer, expireTimer; + + bool fadingOut; + + void reuseTip(const QString &text); + void hideTip(); + void hideTipImmediately(); + void setTipRect(QWidget *w, const QRect &r); + void restartExpireTimer(); + bool tipChanged(const QPoint &pos, const QString &text, QObject *o); + void placeTip(const QPoint &pos, QWidget *w); + + static int getTipScreen(const QPoint &pos, QWidget *w); +protected: + void timerEvent(QTimerEvent *e); + void paintEvent(QPaintEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void resizeEvent(QResizeEvent *e); + +#ifndef QT_NO_STYLE_STYLESHEET +public slots: + /** \internal + Cleanup the _q_stylesheet_parent propery. + */ + void styleSheetParentDestroyed() { + setProperty("_q_stylesheet_parent", QVariant()); + styleSheetParent = 0; + } + +private: + QWidget *styleSheetParent; +#endif + +private: + QWidget *widget; + QRect rect; +}; + +QTipLabel *QTipLabel::instance = 0; + +QTipLabel::QTipLabel(const QString &text, QWidget *w) +#ifndef QT_NO_STYLE_STYLESHEET + : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget), styleSheetParent(0), widget(0) +#else + : QLabel(w, Qt::ToolTip | Qt::BypassGraphicsProxyWidget), widget(0) +#endif +{ + delete instance; + instance = this; + setForegroundRole(QPalette::ToolTipText); + setBackgroundRole(QPalette::ToolTipBase); + setPalette(QToolTip::palette()); + ensurePolished(); + setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, 0, this)); + setFrameStyle(QFrame::NoFrame); + setAlignment(Qt::AlignLeft); + setIndent(1); + qApp->installEventFilter(this); + setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / 255.0); + setMouseTracking(true); + fadingOut = false; + reuseTip(text); +} + +void QTipLabel::restartExpireTimer() +{ + int time = 10000 + 40 * qMax(0, text().length()-100); + expireTimer.start(time, this); + hideTimer.stop(); +} + +void QTipLabel::reuseTip(const QString &text) +{ +#ifndef QT_NO_STYLE_STYLESHEET + if (styleSheetParent){ + disconnect(styleSheetParent, SIGNAL(destroyed()), + QTipLabel::instance, SLOT(styleSheetParentDestroyed())); + styleSheetParent = 0; + } +#endif + + setWordWrap(Qt::mightBeRichText(text)); + setText(text); + QFontMetrics fm(font()); + QSize extra(1, 0); + // Make it look good with the default ToolTip font on Mac, which has a small descent. + if (fm.descent() == 2 && fm.ascent() >= 11) + ++extra.rheight(); + resize(sizeHint() + extra); + restartExpireTimer(); +} + +void QTipLabel::paintEvent(QPaintEvent *ev) +{ + QStylePainter p(this); + QStyleOptionFrame opt; + opt.init(this); + p.drawPrimitive(QStyle::PE_PanelTipLabel, opt); + p.end(); + + QLabel::paintEvent(ev); +} + +void QTipLabel::resizeEvent(QResizeEvent *e) +{ + QStyleHintReturnMask frameMask; + QStyleOption option; + option.init(this); + if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) + setMask(frameMask.region); + + QLabel::resizeEvent(e); +} + +void QTipLabel::mouseMoveEvent(QMouseEvent *e) +{ + if (rect.isNull()) + return; + QPoint pos = e->globalPos(); + if (widget) + pos = widget->mapFromGlobal(pos); + if (!rect.contains(pos)) + hideTip(); + QLabel::mouseMoveEvent(e); +} + +QTipLabel::~QTipLabel() +{ + instance = 0; +} + +void QTipLabel::hideTip() +{ + if (!hideTimer.isActive()) + hideTimer.start(300, this); +} + +void QTipLabel::hideTipImmediately() +{ + close(); // to trigger QEvent::Close which stops the animation + deleteLater(); +} + +void QTipLabel::setTipRect(QWidget *w, const QRect &r) +{ + if (!rect.isNull() && !w) + qWarning("QToolTip::setTipRect: Cannot pass null widget if rect is set"); + else{ + widget = w; + rect = r; + } +} + +void QTipLabel::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == hideTimer.timerId() + || e->timerId() == expireTimer.timerId()){ + hideTimer.stop(); + expireTimer.stop(); +#if defined(Q_WS_MAC) && !defined(QT_NO_EFFECTS) + if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip)){ + // Fade out tip on mac (makes it invisible). + // The tip will not be deleted until a new tip is shown. + + // DRSWAT - Cocoa + macWindowFade(qt_mac_window_for(this)); + QTipLabel::instance->fadingOut = true; // will never be false again. + } + else + hideTipImmediately(); +#else + hideTipImmediately(); +#endif + } +} + +bool QTipLabel::eventFilter(QObject *o, QEvent *e) +{ + switch (e->type()) { +#ifdef Q_WS_MAC + case QEvent::KeyPress: + case QEvent::KeyRelease: { + int key = static_cast<QKeyEvent *>(e)->key(); + Qt::KeyboardModifiers mody = static_cast<QKeyEvent *>(e)->modifiers(); + if (!(mody & Qt::KeyboardModifierMask) + && key != Qt::Key_Shift && key != Qt::Key_Control + && key != Qt::Key_Alt && key != Qt::Key_Meta) + hideTip(); + break; + } +#endif + case QEvent::Leave: + hideTip(); + break; + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::Wheel: + hideTipImmediately(); + break; + + case QEvent::MouseMove: + if (o == widget && !rect.isNull() && !rect.contains(static_cast<QMouseEvent*>(e)->pos())) + hideTip(); + default: + break; + } + return false; +} + +int QTipLabel::getTipScreen(const QPoint &pos, QWidget *w) +{ + if (QApplication::desktop()->isVirtualDesktop()) + return QApplication::desktop()->screenNumber(pos); + else + return QApplication::desktop()->screenNumber(w); +} + +void QTipLabel::placeTip(const QPoint &pos, QWidget *w) +{ +#ifndef QT_NO_STYLE_STYLESHEET + if (testAttribute(Qt::WA_StyleSheet) || (w && qobject_cast<QStyleSheetStyle *>(w->style()))) { + //the stylesheet need to know the real parent + QTipLabel::instance->setProperty("_q_stylesheet_parent", QVariant::fromValue(w)); + //we force the style to be the QStyleSheetStyle, and force to clear the cache as well. + QTipLabel::instance->setStyleSheet(QLatin1String("/* */")); + + // Set up for cleaning up this later... + QTipLabel::instance->styleSheetParent = w; + if (w) { + connect(w, SIGNAL(destroyed()), + QTipLabel::instance, SLOT(styleSheetParentDestroyed())); + } + } +#endif //QT_NO_STYLE_STYLESHEET + + +#ifdef Q_WS_MAC + // When in full screen mode, there is no Dock nor Menu so we can use + // the whole screen for displaying the tooltip. However when not in + // full screen mode we need to save space for the dock, so we use + // availableGeometry instead. + extern bool qt_mac_app_fullscreen; //qapplication_mac.mm + QRect screen; + if(qt_mac_app_fullscreen) + screen = QApplication::desktop()->screenGeometry(getTipScreen(pos, w)); + else + screen = QApplication::desktop()->availableGeometry(getTipScreen(pos, w)); +#else + QRect screen = QApplication::desktop()->screenGeometry(getTipScreen(pos, w)); +#endif + + QPoint p = pos; + p += QPoint(2, +#ifdef Q_WS_WIN + 21 +#else + 16 +#endif + ); + if (p.x() + this->width() > screen.x() + screen.width()) + p.rx() -= 4 + this->width(); + if (p.y() + this->height() > screen.y() + screen.height()) + p.ry() -= 24 + this->height(); + if (p.y() < screen.y()) + p.setY(screen.y()); + if (p.x() + this->width() > screen.x() + screen.width()) + p.setX(screen.x() + screen.width() - this->width()); + if (p.x() < screen.x()) + p.setX(screen.x()); + if (p.y() + this->height() > screen.y() + screen.height()) + p.setY(screen.y() + screen.height() - this->height()); + this->move(p); +} + +bool QTipLabel::tipChanged(const QPoint &pos, const QString &text, QObject *o) +{ + if (QTipLabel::instance->text() != text) + return true; + + if (o != widget) + return true; + + if (!rect.isNull()) + return !rect.contains(pos); + else + return false; +} + +/*! + Shows \a text as a tool tip, with the global position \a pos as + the point of interest. The tool tip will be shown with a platform + specific offset from this point of interest. + + If you specify a non-empty rect the tip will be hidden as soon + as you move your cursor out of this area. + + The \a rect is in the coordinates of the widget you specify with + \a w. If the \a rect is not empty you must specify a widget. + Otherwise this argument can be 0 but it is used to determine the + appropriate screen on multi-head systems. + + If \a text is empty the tool tip is hidden. If the text is the + same as the currently shown tooltip, the tip will \e not move. + You can force moving by first hiding the tip with an empty text, + and then showing the new tip at the new position. +*/ + +void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect) +{ + if (QTipLabel::instance && QTipLabel::instance->isVisible()){ // a tip does already exist + if (text.isEmpty()){ // empty text means hide current tip + QTipLabel::instance->hideTip(); + return; + } + else if (!QTipLabel::instance->fadingOut){ + // If the tip has changed, reuse the one + // that is showing (removes flickering) + QPoint localPos = pos; + if (w) + localPos = w->mapFromGlobal(pos); + if (QTipLabel::instance->tipChanged(localPos, text, w)){ + QTipLabel::instance->reuseTip(text); + QTipLabel::instance->setTipRect(w, rect); + QTipLabel::instance->placeTip(pos, w); + } + return; + } + } + + if (!text.isEmpty()){ // no tip can be reused, create new tip: +#ifndef Q_WS_WIN + new QTipLabel(text, w); // sets QTipLabel::instance to itself +#else + // On windows, we can't use the widget as parent otherwise the window will be + // raised when the tooltip will be shown + new QTipLabel(text, QApplication::desktop()->screen(QTipLabel::getTipScreen(pos, w))); +#endif + QTipLabel::instance->setTipRect(w, rect); + QTipLabel::instance->placeTip(pos, w); + QTipLabel::instance->setObjectName(QLatin1String("qtooltip_label")); + + +#if !defined(QT_NO_EFFECTS) && !defined(Q_WS_MAC) + if (QApplication::isEffectEnabled(Qt::UI_FadeTooltip)) + qFadeEffect(QTipLabel::instance); + else if (QApplication::isEffectEnabled(Qt::UI_AnimateTooltip)) + qScrollEffect(QTipLabel::instance); + else + QTipLabel::instance->show(); +#else + QTipLabel::instance->show(); +#endif + } +} + +/*! + \overload + + This is analogous to calling QToolTip::showText(\a pos, \a text, \a w, QRect()) +*/ + +void QToolTip::showText(const QPoint &pos, const QString &text, QWidget *w) +{ + QToolTip::showText(pos, text, w, QRect()); +} + + +/*! + \fn void QToolTip::hideText() + \since 4.2 + + Hides the tool tip. This is the same as calling showText() with an + empty string. + + \sa showText() +*/ + + +/*! + \since 4.4 + + Returns true if this tooltip is currently shown. + + \sa showText() + */ +bool QToolTip::isVisible() +{ + return (QTipLabel::instance != 0 && QTipLabel::instance->isVisible()); +} + +/*! + \since 4.4 + + Returns the tooltip text, if a tooltip is visible, or an + empty string if a tooltip is not visible. + */ +QString QToolTip::text() +{ + if (QTipLabel::instance) + return QTipLabel::instance->text(); + return QString(); +} + + +Q_GLOBAL_STATIC(QPalette, tooltip_palette) + +/*! + Returns the palette used to render tooltips. + + \note Tool tips use the inactive color group of QPalette, because tool + tips are not active windows. +*/ +QPalette QToolTip::palette() +{ + return *tooltip_palette(); +} + +/*! + \since 4.2 + + Returns the font used to render tooltips. +*/ +QFont QToolTip::font() +{ + return QApplication::font("QTipLabel"); +} + +/*! + \since 4.2 + + Sets the \a palette used to render tooltips. + + \note Tool tips use the inactive color group of QPalette, because tool + tips are not active windows. +*/ +void QToolTip::setPalette(const QPalette &palette) +{ + *tooltip_palette() = palette; + if (QTipLabel::instance) + QTipLabel::instance->setPalette(palette); +} + +/*! + \since 4.2 + + Sets the \a font used to render tooltips. +*/ +void QToolTip::setFont(const QFont &font) +{ + QApplication::setFont(font, "QTipLabel"); +} + + +/*! + \fn void QToolTip::add(QWidget *widget, const QString &text) + + Use QWidget::setToolTip() instead. + + \oldcode + tip->add(widget, text); + \newcode + widget->setToolTip(text); + \endcode +*/ + +/*! + \fn void QToolTip::add(QWidget *widget, const QRect &rect, const QString &text) + + Intercept the QEvent::ToolTip events in your widget's + QWidget::event() function and call QToolTip::showText() with the + text you want to display. The \l{widgets/tooltips}{Tooltips} + example illustrates this technique. +*/ + +/*! + \fn void QToolTip::remove(QWidget *widget) + + Use QWidget::setToolTip() instead. + + \oldcode + tip->remove(widget); + \newcode + widget->setToolTip(""); + \endcode +*/ + +QT_END_NAMESPACE + +#include "qtooltip.moc" +#endif // QT_NO_TOOLTIP diff --git a/src/gui/kernel/qtooltip.h b/src/gui/kernel/qtooltip.h new file mode 100644 index 0000000000..4195f1cb3e --- /dev/null +++ b/src/gui/kernel/qtooltip.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOOLTIP_H +#define QTOOLTIP_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_TOOLTIP + +class Q_GUI_EXPORT QToolTip +{ + QToolTip(); +public: + static void showText(const QPoint &pos, const QString &text, QWidget *w = 0); + static void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect); + static inline void hideText() { showText(QPoint(), QString()); } + + static bool isVisible(); + static QString text(); + + static QPalette palette(); + static void setPalette(const QPalette &); + static QFont font(); + static void setFont(const QFont &); +#ifdef QT3_SUPPORT + static inline QT3_SUPPORT void add(QWidget *w, const QString &s) { w->setToolTip(s); } + static inline QT3_SUPPORT void add(QWidget *w, const QRect &, const QString &s) + { w->setToolTip(s); } + static inline QT3_SUPPORT void remove(QWidget *w) { w->setToolTip(QString()); } +#endif +}; + +#endif // QT_NO_TOOLTIP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTOOLTIP_H diff --git a/src/gui/kernel/qwhatsthis.cpp b/src/gui/kernel/qwhatsthis.cpp new file mode 100644 index 0000000000..5e47ffa56b --- /dev/null +++ b/src/gui/kernel/qwhatsthis.cpp @@ -0,0 +1,777 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwhatsthis.h" +#ifndef QT_NO_WHATSTHIS +#include "qpointer.h" +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qpixmap.h" +#include "qpainter.h" +#include "qtimer.h" +#include "qhash.h" +#include "qaction.h" +#include "qcursor.h" +#include "qbitmap.h" +#include "qtextdocument.h" +#include "../text/qtextdocumentlayout_p.h" +#include "qtoolbutton.h" +#include "qdebug.h" +#ifndef QT_NO_ACCESSIBILITY +#include "qaccessible.h" +#endif +#if defined(Q_WS_WIN) +#include "qt_windows.h" +#ifndef SPI_GETDROPSHADOW +#define SPI_GETDROPSHADOW 0x1024 +#endif +#endif +#if defined(Q_WS_X11) +#include "qx11info_x11.h" +#include <qwidget.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QWhatsThis + \brief The QWhatsThis class provides a simple description of any + widget, i.e. answering the question "What's This?". + + \ingroup helpsystem + + + "What's This?" help is part of an application's online help + system, and provides users with information about the + functionality and usage of a particular widget. "What's This?" + help texts are typically longer and more detailed than \link + QToolTip tooltips\endlink, but generally provide less information + than that supplied by separate help windows. + + QWhatsThis provides a single window with an explanatory text that + pops up when the user asks "What's This?". The default way for + users to ask the question is to move the focus to the relevant + widget and press Shift+F1. The help text appears immediately; it + goes away as soon as the user does something else. + (Note that if there is a shortcut for Shift+F1, this mechanism + will not work.) Some dialogs provide a "?" button that users can + click to enter "What's This?" mode; they then click the relevant + widget to pop up the "What's This?" window. It is also possible to + provide a a menu option or toolbar button to switch into "What's + This?" mode. + + To add "What's This?" text to a widget or an action, you simply + call QWidget::setWhatsThis() or QAction::setWhatsThis(). + + The text can be either rich text or plain text. If you specify a + rich text formatted string, it will be rendered using the default + stylesheet, making it possible to embed images in the displayed + text. To be as fast as possible, the default stylesheet uses a + simple method to determine whether the text can be rendered as + plain text. See Qt::mightBeRichText() for details. + + \snippet doc/src/snippets/whatsthis/whatsthis.cpp 0 + + An alternative way to enter "What's This?" mode is to call + createAction(), and add the returned QAction to either a menu or + a tool bar. By invoking this context help action (in the picture + below, the button with the arrow and question mark icon) the user + switches into "What's This?" mode. If they now click on a widget + the appropriate help text is shown. The mode is left when help is + given or when the user presses Esc. + + \img whatsthis.png + + You can enter "What's This?" mode programmatically with + enterWhatsThisMode(), check the mode with inWhatsThisMode(), and + return to normal mode with leaveWhatsThisMode(). + + If you want to control the "What's This?" behavior of a widget + manually see Qt::WA_CustomWhatsThis. + + It is also possible to show different help texts for different + regions of a widget, by using a QHelpEvent of type + QEvent::WhatsThis. Intercept the help event in your widget's + QWidget::event() function and call QWhatsThis::showText() with the + text you want to display for the position specified in + QHelpEvent::pos(). If the text is rich text and the user clicks + on a link, the widget also receives a QWhatsThisClickedEvent with + the link's reference as QWhatsThisClickedEvent::href(). If a + QWhatsThisClickedEvent is handled (i.e. QWidget::event() returns + true), the help window remains visible. Call + QWhatsThis::hideText() to hide it explicitly. + + \sa QToolTip +*/ + +Q_CORE_EXPORT void qDeleteInEventHandler(QObject *o); + +class QWhatsThat : public QWidget +{ + Q_OBJECT + +public: + QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor); + ~QWhatsThat() ; + + static QWhatsThat *instance; + +protected: + void showEvent(QShowEvent *e); + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void keyPressEvent(QKeyEvent*); + void paintEvent(QPaintEvent*); + +private: + QPointer<QWidget>widget; + bool pressed; + QString text; + QTextDocument* doc; + QString anchor; + QPixmap background; +}; + +QWhatsThat *QWhatsThat::instance = 0; + +// shadowWidth not const, for XP drop-shadow-fu turns it to 0 +static int shadowWidth = 6; // also used as '5' and '6' and even '8' below +static const int vMargin = 8; +static const int hMargin = 12; + +QWhatsThat::QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor) + : QWidget(parent, Qt::Popup), + widget(showTextFor), pressed(false), text(txt) +{ + delete instance; + instance = this; + setAttribute(Qt::WA_DeleteOnClose, true); + setAttribute(Qt::WA_NoSystemBackground, true); + if (parent) + setPalette(parent->palette()); + setMouseTracking(true); + setFocusPolicy(Qt::StrongFocus); +#ifndef QT_NO_CURSOR + setCursor(Qt::ArrowCursor); +#endif + QRect r; + doc = 0; + ensurePolished(); // Ensures style sheet font before size calc + if (Qt::mightBeRichText(text)) { + doc = new QTextDocument(); + doc->setUndoRedoEnabled(false); + doc->setDefaultFont(QApplication::font(this)); +#ifdef QT_NO_TEXTHTMLPARSER + doc->setPlainText(text); +#else + doc->setHtml(text); +#endif + doc->setUndoRedoEnabled(false); + doc->adjustSize(); + r.setTop(0); + r.setLeft(0); + r.setSize(doc->size().toSize()); + } + else + { + int sw = QApplication::desktop()->width() / 3; + if (sw < 200) + sw = 200; + else if (sw > 300) + sw = 300; + + r = fontMetrics().boundingRect(0, 0, sw, 1000, + Qt::AlignLeft + Qt::AlignTop + + Qt::TextWordWrap + Qt::TextExpandTabs, + text); + } +#if defined(Q_WS_WIN) + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + { + BOOL shadow; + SystemParametersInfo(SPI_GETDROPSHADOW, 0, &shadow, 0); + shadowWidth = shadow ? 0 : 6; + } +#endif + resize(r.width() + 2*hMargin + shadowWidth, r.height() + 2*vMargin + shadowWidth); +} + +QWhatsThat::~QWhatsThat() +{ + instance = 0; + if (doc) + delete doc; +} + +void QWhatsThat::showEvent(QShowEvent *) +{ + background = QPixmap::grabWindow(QApplication::desktop()->internalWinId(), + x(), y(), width(), height()); +} + +void QWhatsThat::mousePressEvent(QMouseEvent* e) +{ + pressed = true; + if (e->button() == Qt::LeftButton && rect().contains(e->pos())) { + if (doc) + anchor = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin)); + return; + } + close(); +} + +void QWhatsThat::mouseReleaseEvent(QMouseEvent* e) +{ + if (!pressed) + return; + if (widget && e->button() == Qt::LeftButton && doc && rect().contains(e->pos())) { + QString a = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin)); + QString href; + if (anchor == a) + href = a; + anchor.clear(); + if (!href.isEmpty()) { + QWhatsThisClickedEvent e(href); + if (QApplication::sendEvent(widget, &e)) + return; + } + } + close(); +} + +void QWhatsThat::mouseMoveEvent(QMouseEvent* e) +{ +#ifdef QT_NO_CURSOR + Q_UNUSED(e); +#else + if (!doc) + return; + QString a = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin)); + if (!a.isEmpty()) + setCursor(Qt::PointingHandCursor); + else + setCursor(Qt::ArrowCursor); +#endif +} + +void QWhatsThat::keyPressEvent(QKeyEvent*) +{ + close(); +} + +void QWhatsThat::paintEvent(QPaintEvent*) +{ + bool drawShadow = true; +#if defined(Q_WS_WIN) + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_XP + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + { + BOOL shadow; + SystemParametersInfo(SPI_GETDROPSHADOW, 0, &shadow, 0); + drawShadow = !shadow; + } +#elif defined(Q_WS_MAC) || defined(Q_WS_QWS) + drawShadow = false; // never draw it on OS X or QWS, as we get it for free +#endif + + QRect r = rect(); + r.adjust(0, 0, -1, -1); + if (drawShadow) + r.adjust(0, 0, -shadowWidth, -shadowWidth); + QPainter p(this); + p.drawPixmap(0, 0, background); + p.setPen(QPen(palette().toolTipText(), 0)); + p.setBrush(palette().toolTipBase()); + p.drawRect(r); + int w = r.width(); + int h = r.height(); + p.setPen(palette().brush(QPalette::Dark).color()); + p.drawRect(1, 1, w-2, h-2); + if (drawShadow) { + p.setPen(palette().shadow().color()); + p.drawPoint(w + 5, 6); + p.drawLine(w + 3, 6, w + 5, 8); + p.drawLine(w + 1, 6, w + 5, 10); + int i; + for(i=7; i < h; i += 2) + p.drawLine(w, i, w + 5, i + 5); + for(i = w - i + h; i > 6; i -= 2) + p.drawLine(i, h, i + 5, h + 5); + for(; i > 0 ; i -= 2) + p.drawLine(6, h + 6 - i, i + 5, h + 5); + } + r.adjust(0, 0, 1, 1); + p.setPen(palette().toolTipText().color()); + r.adjust(hMargin, vMargin, -hMargin, -vMargin); + + if (doc) { + p.translate(r.x(), r.y()); + QRect rect = r; + rect.translate(-r.x(), -r.y()); + p.setClipRect(rect); + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setBrush(QPalette::Text, context.palette.toolTipText()); + doc->documentLayout()->draw(&p, context); + } + else + { + p.drawText(r, Qt::AlignLeft + Qt::AlignTop + Qt::TextWordWrap + Qt::TextExpandTabs, text); + } +} + +static const char * const button_image[] = { +"16 16 3 1", +" c None", +"o c #000000", +"a c #000080", +"o aaaaa ", +"oo aaa aaa ", +"ooo aaa aaa", +"oooo aa aa", +"ooooo aa aa", +"oooooo a aaa", +"ooooooo aaa ", +"oooooooo aaa ", +"ooooooooo aaa ", +"ooooo aaa ", +"oo ooo ", +"o ooo aaa ", +" ooo aaa ", +" ooo ", +" ooo ", +" ooo "}; + +class QWhatsThisPrivate : public QObject +{ + public: + QWhatsThisPrivate(); + ~QWhatsThisPrivate(); + static QWhatsThisPrivate *instance; + bool eventFilter(QObject *, QEvent *); + QPointer<QAction> action; +#ifdef QT3_SUPPORT + QPointer<QToolButton> button; +#endif + static void say(QWidget *, const QString &, int x = 0, int y = 0); + static void notifyToplevels(QEvent *e); + bool leaveOnMouseRelease; +}; + +void QWhatsThisPrivate::notifyToplevels(QEvent *e) +{ + QWidgetList toplevels = QApplication::topLevelWidgets(); + for (int i = 0; i < toplevels.count(); ++i) { + register QWidget *w = toplevels.at(i); + QApplication::sendEvent(w, e); + } +} + +QWhatsThisPrivate *QWhatsThisPrivate::instance = 0; + +QWhatsThisPrivate::QWhatsThisPrivate() + : leaveOnMouseRelease(false) +{ + instance = this; + qApp->installEventFilter(this); + + QPoint pos = QCursor::pos(); + if (QWidget *w = QApplication::widgetAt(pos)) { + QHelpEvent e(QEvent::QueryWhatsThis, w->mapFromGlobal(pos), pos); + bool sentEvent = QApplication::sendEvent(w, &e); +#ifdef QT_NO_CURSOR + Q_UNUSED(sentEvent); +#else + QApplication::setOverrideCursor((!sentEvent || !e.isAccepted())? + Qt::ForbiddenCursor:Qt::WhatsThisCursor); + } else { + QApplication::setOverrideCursor(Qt::WhatsThisCursor); +#endif + } +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::ContextHelpStart); +#endif +} + +QWhatsThisPrivate::~QWhatsThisPrivate() +{ + if (action) + action->setChecked(false); +#ifdef QT3_SUPPORT + if (button) + button->setChecked(false); +#endif +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::ContextHelpEnd); +#endif + instance = 0; +} + +bool QWhatsThisPrivate::eventFilter(QObject *o, QEvent *e) +{ + if (!o->isWidgetType()) + return false; + QWidget * w = static_cast<QWidget *>(o); + bool customWhatsThis = w->testAttribute(Qt::WA_CustomWhatsThis); + switch (e->type()) { + case QEvent::MouseButtonPress: + { + QMouseEvent *me = static_cast<QMouseEvent*>(e); + if (me->button() == Qt::RightButton || customWhatsThis) + return false; + QHelpEvent e(QEvent::WhatsThis, me->pos(), me->globalPos()); + if (!QApplication::sendEvent(w, &e) || !e.isAccepted()) + leaveOnMouseRelease = true; + + } break; + + case QEvent::MouseMove: + { + QMouseEvent *me = static_cast<QMouseEvent*>(e); + QHelpEvent e(QEvent::QueryWhatsThis, me->pos(), me->globalPos()); + bool sentEvent = QApplication::sendEvent(w, &e); +#ifdef QT_NO_CURSOR + Q_UNUSED(sentEvent); +#else + QApplication::changeOverrideCursor((!sentEvent || !e.isAccepted())? + Qt::ForbiddenCursor:Qt::WhatsThisCursor); +#endif + } + // fall through + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + if (leaveOnMouseRelease && e->type() == QEvent::MouseButtonRelease) + QWhatsThis::leaveWhatsThisMode(); + if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton || customWhatsThis) + return false; // ignore RMB release + break; + case QEvent::KeyPress: + { + QKeyEvent* kev = (QKeyEvent*)e; + + if (kev->key() == Qt::Key_Escape) { + QWhatsThis::leaveWhatsThisMode(); + return true; + } else if (customWhatsThis) { + return false; + } else if (kev->key() == Qt::Key_Menu || + (kev->key() == Qt::Key_F10 && + kev->modifiers() == Qt::ShiftModifier)) { + // we don't react to these keys, they are used for context menus + return false; + } else if (kev->key() != Qt::Key_Shift && kev->key() != Qt::Key_Alt // not a modifier key + && kev->key() != Qt::Key_Control && kev->key() != Qt::Key_Meta) { + QWhatsThis::leaveWhatsThisMode(); + } + } break; + default: + return false; + } + return true; +} + +class QWhatsThisAction: public QAction +{ + Q_OBJECT + +public: + explicit QWhatsThisAction(QObject* parent = 0); + +private slots: + void actionTriggered(); +}; + +QWhatsThisAction::QWhatsThisAction(QObject *parent) : QAction(tr("What's This?"), parent) +{ +#ifndef QT_NO_IMAGEFORMAT_XPM + QPixmap p((const char**)button_image); + setIcon(p); +#endif + setCheckable(true); + connect(this, SIGNAL(triggered()), this, SLOT(actionTriggered())); +#ifndef QT_NO_SHORTCUT + setShortcut(Qt::ShiftModifier + Qt::Key_F1); +#endif +} + +void QWhatsThisAction::actionTriggered() +{ + if (isChecked()) { + QWhatsThis::enterWhatsThisMode(); + QWhatsThisPrivate::instance->action = this; + } +} + +QWhatsThis::QWhatsThis() +{ +} + +#ifdef QT3_SUPPORT +/*! + \obsolete + + Sets the What's This text \a s for the widget \a w. + + Use QWidget::setWhatsThis() or QAction::setWhatsThis() instead. +*/ +void QWhatsThis::add(QWidget *w, const QString &s) +{ + w->setWhatsThis(s); +} + +/*! + \obsolete + + Remove's the What's This text for the widget \a w. + + Use QWidget::setWhatsThis() or QAction::setWhatsThis() instead. +*/ +void QWhatsThis::remove(QWidget *w) +{ + w->setWhatsThis(QString()); +} + +class QWhatsThisButton : public QToolButton +{ + Q_OBJECT +public: + QWhatsThisButton(QWidget *p) : QToolButton(p) { + setCheckable(true); + QPixmap pix( const_cast<const char**>(button_image) ); + setIcon( pix ); + QObject::connect(this, SIGNAL(toggled(bool)), this, SLOT(whatToggled(bool))); + setAutoRaise(true); + setFocusPolicy(Qt::NoFocus); + } + +public slots: + void whatToggled(bool b) { + if (b) { + QWhatsThis::enterWhatsThisMode(); + QWhatsThisPrivate::instance->button = this; + } + } +}; + +/*! + Returns a new "What's This?" QToolButton with the given \a + parent. To do this now, create your own QToolButton and a + QWhatsThis object and call the QWhatsThis object's showText() + function when the QToolButton is invoked. + + Use createAction() instead. +*/ +QToolButton * QWhatsThis::whatsThisButton(QWidget * parent) +{ + return new QWhatsThisButton(parent); +} +#endif + +/*! + This function switches the user interface into "What's This?" + mode. The user interface can be switched back into normal mode by + the user (e.g. by them clicking or pressing Esc), or + programmatically by calling leaveWhatsThisMode(). + + When entering "What's This?" mode, a QEvent of type + Qt::EnterWhatsThisMode is sent to all toplevel widgets. + + \sa inWhatsThisMode() leaveWhatsThisMode() +*/ +void QWhatsThis::enterWhatsThisMode() +{ + if (QWhatsThisPrivate::instance) + return; + (void) new QWhatsThisPrivate; + QEvent e(QEvent::EnterWhatsThisMode); + QWhatsThisPrivate::notifyToplevels(&e); + } + +/*! + Returns true if the user interface is in "What's This?" mode; + otherwise returns false. + + \sa enterWhatsThisMode() +*/ +bool QWhatsThis::inWhatsThisMode() +{ + return (QWhatsThisPrivate::instance != 0); +} + +/*! + If the user interface is in "What's This?" mode, this function + switches back to normal mode; otherwise it does nothing. + + When leaving "What's This?" mode, a QEvent of type + Qt::LeaveWhatsThisMode is sent to all toplevel widgets. + + \sa enterWhatsThisMode() inWhatsThisMode() +*/ +void QWhatsThis::leaveWhatsThisMode() +{ + delete QWhatsThisPrivate::instance; + QEvent e(QEvent::LeaveWhatsThisMode); + QWhatsThisPrivate::notifyToplevels(&e); +} + +void QWhatsThisPrivate::say(QWidget * widget, const QString &text, int x, int y) +{ + if (text.size() == 0) + return; + // make a fresh widget, and set it up + QWhatsThat *whatsThat = new QWhatsThat( + text, +#if defined(Q_WS_X11) && !defined(QT_NO_CURSOR) + QApplication::desktop()->screen(widget ? widget->x11Info().screen() : QCursor::x11Screen()), +#else + 0, +#endif + widget + ); + + + // okay, now to find a suitable location + + int scr = (widget ? + QApplication::desktop()->screenNumber(widget) : +#if defined(Q_WS_X11) && !defined(QT_NO_CURSOR) + QCursor::x11Screen() +#else + QApplication::desktop()->screenNumber(QPoint(x,y)) +#endif // Q_WS_X11 + ); + QRect screen = QApplication::desktop()->screenGeometry(scr); + + int w = whatsThat->width(); + int h = whatsThat->height(); + int sx = screen.x(); + int sy = screen.y(); + + // first try locating the widget immediately above/below, + // with nice alignment if possible. + QPoint pos; + if (widget) + pos = widget->mapToGlobal(QPoint(0,0)); + + if (widget && w > widget->width() + 16) + x = pos.x() + widget->width()/2 - w/2; + else + x = x - w/2; + + // squeeze it in if that would result in part of what's this + // being only partially visible + if (x + w + shadowWidth > sx+screen.width()) + x = (widget? (qMin(screen.width(), + pos.x() + widget->width()) + ) : screen.width()) + - w; + + if (x < sx) + x = sx; + + if (widget && h > widget->height() + 16) { + y = pos.y() + widget->height() + 2; // below, two pixels spacing + // what's this is above or below, wherever there's most space + if (y + h + 10 > sy+screen.height()) + y = pos.y() + 2 - shadowWidth - h; // above, overlap + } + y = y + 2; + + // squeeze it in if that would result in part of what's this + // being only partially visible + if (y + h + shadowWidth > sy+screen.height()) + y = (widget ? (qMin(screen.height(), + pos.y() + widget->height()) + ) : screen.height()) + - h; + if (y < sy) + y = sy; + + whatsThat->move(x, y); + whatsThat->show(); + whatsThat->grabKeyboard(); +} + +/*! + Shows \a text as a "What's This?" window, at global position \a + pos. The optional widget argument, \a w, is used to determine the + appropriate screen on multi-head systems. + + \sa hideText() +*/ +void QWhatsThis::showText(const QPoint &pos, const QString &text, QWidget *w) +{ + leaveWhatsThisMode(); + QWhatsThisPrivate::say(w, text, pos.x(), pos.y()); +} + +/*! + If a "What's This?" window is showing, this destroys it. + + \sa showText() +*/ +void QWhatsThis::hideText() +{ + qDeleteInEventHandler(QWhatsThat::instance); +} + +/*! + Returns a ready-made QAction, used to invoke "What's This?" context + help, with the given \a parent. + + The returned QAction provides a convenient way to let users enter + "What's This?" mode. +*/ +QAction *QWhatsThis::createAction(QObject *parent) +{ + return new QWhatsThisAction(parent); +} + +QT_END_NAMESPACE + +#include "qwhatsthis.moc" + +#endif // QT_NO_WHATSTHIS diff --git a/src/gui/kernel/qwhatsthis.h b/src/gui/kernel/qwhatsthis.h new file mode 100644 index 0000000000..c2e396d0d0 --- /dev/null +++ b/src/gui/kernel/qwhatsthis.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWHATSTHIS_H +#define QWHATSTHIS_H + +#include <QtCore/qobject.h> +#include <QtGui/qcursor.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_WHATSTHIS + +class QAction; +#ifdef QT3_SUPPORT +class QToolButton; +#endif + +class Q_GUI_EXPORT QWhatsThis +{ + QWhatsThis(); + +public: + static void enterWhatsThisMode(); + static bool inWhatsThisMode(); + static void leaveWhatsThisMode(); + + static void showText(const QPoint &pos, const QString &text, QWidget *w = 0); + static void hideText(); + + static QAction *createAction(QObject *parent = 0); + +#ifdef QT3_SUPPORT + static QT3_SUPPORT void add(QWidget *w, const QString &s); + static QT3_SUPPORT void remove(QWidget *); + static QT3_SUPPORT QToolButton *whatsThisButton(QWidget *parent); +#endif +}; + +#endif // QT_NO_WHATSTHIS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWHATSTHIS_H diff --git a/src/gui/kernel/qwidget.cpp b/src/gui/kernel/qwidget.cpp new file mode 100644 index 0000000000..5705214762 --- /dev/null +++ b/src/gui/kernel/qwidget.cpp @@ -0,0 +1,12679 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qbrush.h" +#include "qcursor.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qhash.h" +#include "qlayout.h" +#include "qmenu.h" +#include "qmetaobject.h" +#include "qpixmap.h" +#include "qpointer.h" +#include "qstack.h" +#include "qstyle.h" +#include "qstylefactory.h" +#include "qvariant.h" +#include "qwidget.h" +#include "qstyleoption.h" +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif +#if defined(Q_WS_WIN) +# include "qt_windows.h" +#endif +#ifdef Q_WS_MAC +# include "qt_mac_p.h" +# include "qt_cocoa_helpers_mac_p.h" +# include "qmainwindow.h" +# include "qtoolbar.h" +# include <private/qmainwindowlayout_p.h> +#endif +#if defined(Q_WS_QWS) +# include "qwsdisplay_qws.h" +# include "qwsmanager_qws.h" +# include "qpaintengine.h" // for PorterDuff +# include "private/qwindowsurface_qws_p.h" +#endif +#if defined(Q_WS_QPA) +#include "qplatformwindow_qpa.h" +#endif +#include "qpainter.h" +#include "qtooltip.h" +#include "qwhatsthis.h" +#include "qdebug.h" +#include "private/qstylesheetstyle_p.h" +#include "private/qstyle_p.h" +#include "private/qinputcontext_p.h" +#include "qfileinfo.h" +#include "private/qsoftkeymanager_p.h" + +#if defined (Q_WS_WIN) +# include <private/qwininputcontext_p.h> +#endif + +#if defined(Q_WS_X11) +# include <private/qpaintengine_x11_p.h> +# include "qx11info_x11.h" +#endif + +#include <private/qgraphicseffect_p.h> +#include <private/qwindowsurface_p.h> +#include <private/qbackingstore_p.h> +#ifdef Q_WS_MAC +# include <private/qpaintengine_mac_p.h> +#endif +#include <private/qpaintengine_raster_p.h> + +#if defined(Q_OS_SYMBIAN) +#include "private/qt_s60_p.h" +#endif + +#include "qwidget_p.h" +#include "qaction_p.h" +#include "qlayout_p.h" +#include "QtGui/qgraphicsproxywidget.h" +#include "QtGui/qgraphicsscene.h" +#include "private/qgraphicsproxywidget_p.h" +#include "QtGui/qabstractscrollarea.h" +#include "private/qabstractscrollarea_p.h" +#include "private/qevent_p.h" + +#include "private/qgraphicssystem_p.h" +#include "private/qgesturemanager_p.h" + +#ifdef QT_KEYPAD_NAVIGATION +#include "qtabwidget.h" // Needed in inTabWidget() +#endif // QT_KEYPAD_NAVIGATION + +#ifdef Q_WS_S60 +#include <aknappui.h> +#endif + +// widget/widget data creation count +//#define QWIDGET_EXTRA_DEBUG +//#define ALIEN_DEBUG + +QT_BEGIN_NAMESPACE + +#if !defined(Q_WS_QWS) +static bool qt_enable_backingstore = true; +#endif +#ifdef Q_WS_X11 +// for compatibility with Qt 4.0 +Q_GUI_EXPORT void qt_x11_set_global_double_buffer(bool enable) +{ + qt_enable_backingstore = enable; +} +#endif + +#if defined(QT_MAC_USE_COCOA) +bool qt_mac_clearDirtyOnWidgetInsideDrawWidget = false; +#endif + +static inline bool qRectIntersects(const QRect &r1, const QRect &r2) +{ + return (qMax(r1.left(), r2.left()) <= qMin(r1.right(), r2.right()) && + qMax(r1.top(), r2.top()) <= qMin(r1.bottom(), r2.bottom())); +} + +static inline bool hasBackingStoreSupport() +{ +#ifdef Q_WS_MAC + return QApplicationPrivate::graphicsSystem() != 0; +#else + return true; +#endif +} + +#ifdef Q_WS_MAC +# define QT_NO_PAINT_DEBUG +#endif + +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp +extern QDesktopWidget *qt_desktopWidget; // qapplication.cpp + +/*! + \internal + \class QWidgetBackingStoreTracker + \brief Class which allows tracking of which widgets are using a given backing store + + QWidgetBackingStoreTracker is a thin wrapper around a QWidgetBackingStore pointer, + which maintains a list of the QWidgets which are currently using the backing + store. This list is modified via the registerWidget and unregisterWidget functions. + */ + +QWidgetBackingStoreTracker::QWidgetBackingStoreTracker() + : m_ptr(0) +{ + +} + +QWidgetBackingStoreTracker::~QWidgetBackingStoreTracker() +{ + delete m_ptr; +} + +/*! + \internal + Destroy the contained QWidgetBackingStore, if not null, and clear the list of + widgets using the backing store, then create a new QWidgetBackingStore, providing + the QWidget. + */ +void QWidgetBackingStoreTracker::create(QWidget *widget) +{ + destroy(); + m_ptr = new QWidgetBackingStore(widget); +} + +/*! + \internal + Destroy the contained QWidgetBackingStore, if not null, and clear the list of + widgets using the backing store. + */ +void QWidgetBackingStoreTracker::destroy() +{ + delete m_ptr; + m_ptr = 0; + m_widgets.clear(); +} + +/*! + \internal + Add the widget to the list of widgets currently using the backing store. + If the widget was already in the list, this function is a no-op. + */ +void QWidgetBackingStoreTracker::registerWidget(QWidget *w) +{ + Q_ASSERT(m_ptr); + Q_ASSERT(w->internalWinId()); + Q_ASSERT(qt_widget_private(w)->maybeBackingStore() == m_ptr); + m_widgets.insert(w); +} + +/*! + \internal + Remove the widget from the list of widgets currently using the backing store. + If the widget was in the list, and removing it causes the list to be empty, + the backing store is deleted. + If the widget was not in the list, this function is a no-op. + */ +void QWidgetBackingStoreTracker::unregisterWidget(QWidget *w) +{ + if (m_widgets.remove(w) && m_widgets.isEmpty()) { + delete m_ptr; + m_ptr = 0; + } +} + +/*! + \internal + Recursively remove widget and all of its descendents. + */ +void QWidgetBackingStoreTracker::unregisterWidgetSubtree(QWidget *widget) +{ + unregisterWidget(widget); + foreach (QObject *child, widget->children()) + if (QWidget *childWidget = qobject_cast<QWidget *>(child)) + unregisterWidgetSubtree(childWidget); +} + +QWidgetPrivate::QWidgetPrivate(int version) + : QObjectPrivate(version) + , extra(0) + , focus_next(0) + , focus_prev(0) + , focus_child(0) + , layout(0) + , needsFlush(0) + , redirectDev(0) + , widgetItem(0) + , extraPaintEngine(0) + , polished(0) + , graphicsEffect(0) +#if !defined(QT_NO_IM) + , imHints(Qt::ImhNone) +#endif + , inheritedFontResolveMask(0) + , inheritedPaletteResolveMask(0) + , leftmargin(0) + , topmargin(0) + , rightmargin(0) + , bottommargin(0) + , leftLayoutItemMargin(0) + , topLayoutItemMargin(0) + , rightLayoutItemMargin(0) + , bottomLayoutItemMargin(0) + , hd(0) + , size_policy(QSizePolicy::Preferred, QSizePolicy::Preferred) + , fg_role(QPalette::NoRole) + , bg_role(QPalette::NoRole) + , dirtyOpaqueChildren(1) + , isOpaque(0) + , inDirtyList(0) + , isScrolled(0) + , isMoved(0) + , isGLWidget(0) + , usesDoubleBufferedGLContext(0) +#ifndef QT_NO_IM + , inheritsInputMethodHints(0) +#endif +#if defined(Q_WS_X11) + , picture(0) +#elif defined(Q_WS_WIN) + , noPaintOnScreen(0) + #ifndef QT_NO_GESTURES + , nativeGesturePanEnabled(0) + #endif +#elif defined(Q_WS_MAC) + , needWindowChange(0) + , window_event(0) + , qd_hd(0) +#elif defined(Q_OS_SYMBIAN) + , symbianScreenNumber(0) + , fixNativeOrientationCalled(false) +#endif +{ + if (!qApp) { + qFatal("QWidget: Must construct a QApplication before a QPaintDevice"); + return; + } + + if (version != QObjectPrivateVersion) + qFatal("Cannot mix incompatible Qt libraries"); + + isWidget = true; + memset(high_attributes, 0, sizeof(high_attributes)); +#if QT_MAC_USE_COCOA + drawRectOriginalAdded = false; + originalDrawMethod = true; + changeMethods = false; + isInUnifiedToolbar = false; + unifiedSurface = 0; + toolbar_ancestor = 0; + flushRequested = false; + touchEventsEnabled = false; +#endif // QT_MAC_USE_COCOA +#ifdef QWIDGET_EXTRA_DEBUG + static int count = 0; + qDebug() << "widgets" << ++count; +#endif +} + + +QWidgetPrivate::~QWidgetPrivate() +{ + if (widgetItem) + widgetItem->wid = 0; + + if (extra) + deleteExtra(); + +#ifndef QT_NO_GRAPHICSEFFECT + delete graphicsEffect; +#endif //QT_NO_GRAPHICSEFFECT +} + +class QDummyWindowSurface : public QWindowSurface +{ +public: + QDummyWindowSurface(QWidget *window) : QWindowSurface(window) {} + QPaintDevice *paintDevice() { return window(); } + void flush(QWidget *, const QRegion &, const QPoint &) {} +}; + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface() +{ + Q_Q(QWidget); + + QWindowSurface *surface; +#ifndef QT_NO_PROPERTIES + if (q->property("_q_DummyWindowSurface").toBool()) { + surface = new QDummyWindowSurface(q); + } else +#endif + { + if (QApplicationPrivate::graphicsSystem()) + surface = QApplicationPrivate::graphicsSystem()->createWindowSurface(q); + else + surface = createDefaultWindowSurface_sys(); + } + + return surface; +} + +/*! + \internal +*/ +void QWidgetPrivate::scrollChildren(int dx, int dy) +{ + Q_Q(QWidget); + if (q->children().size() > 0) { // scroll children + QPoint pd(dx, dy); + QObjectList childObjects = q->children(); + for (int i = 0; i < childObjects.size(); ++i) { // move all children + QWidget *w = qobject_cast<QWidget*>(childObjects.at(i)); + if (w && !w->isWindow()) { + QPoint oldp = w->pos(); + QRect r(w->pos() + pd, w->size()); + w->data->crect = r; +#ifndef Q_WS_QWS + if (w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(); +#endif + w->d_func()->setDirtyOpaqueRegion(); + QMoveEvent e(r.topLeft(), oldp); + QApplication::sendEvent(w, &e); + } + } + } +} + +QInputContext *QWidgetPrivate::assignedInputContext() const +{ +#ifndef QT_NO_IM + const QWidget *widget = q_func(); + while (widget) { + if (QInputContext *qic = widget->d_func()->ic) + return qic; + widget = widget->parentWidget(); + } +#endif + return 0; +} + +QInputContext *QWidgetPrivate::inputContext() const +{ +#ifndef QT_NO_IM + if (QInputContext *qic = assignedInputContext()) + return qic; + return qApp->inputContext(); +#else + return 0; +#endif +} + +/*! + This function returns the QInputContext for this widget. By + default the input context is inherited from the widgets + parent. For toplevels it is inherited from QApplication. + + You can override this and set a special input context for this + widget by using the setInputContext() method. + + \sa setInputContext() +*/ +QInputContext *QWidget::inputContext() +{ + Q_D(QWidget); + if (!testAttribute(Qt::WA_InputMethodEnabled)) + return 0; + + return d->inputContext(); +} + +/*! + This function sets the input context \a context + on this widget. + + Qt takes ownership of the given input \a context. + + \sa inputContext() +*/ +void QWidget::setInputContext(QInputContext *context) +{ + Q_D(QWidget); + if (!testAttribute(Qt::WA_InputMethodEnabled)) + return; +#ifndef QT_NO_IM + if (context == d->ic) + return; + if (d->ic) + delete d->ic; + d->ic = context; + if (d->ic) + d->ic->setParent(this); +#endif +} + + +/*! + \obsolete + + This function can be called on the widget that currently has focus + to reset the input method operating on it. + + This function is providing for convenience, instead you should use + \l{QInputContext::}{reset()} on the input context that was + returned by inputContext(). + + \sa QInputContext, inputContext(), QInputContext::reset() +*/ +void QWidget::resetInputContext() +{ + if (!hasFocus()) + return; +#ifndef QT_NO_IM + QInputContext *qic = this->inputContext(); + if(qic) + qic->reset(); +#endif // QT_NO_IM +} + +#ifdef QT_KEYPAD_NAVIGATION +QPointer<QWidget> QWidgetPrivate::editingWidget; + +/*! + Returns true if this widget currently has edit focus; otherwise false. + + This feature is only available in Qt for Embedded Linux. + + \sa setEditFocus(), QApplication::keypadNavigationEnabled() +*/ +bool QWidget::hasEditFocus() const +{ + const QWidget* w = this; + while (w->d_func()->extra && w->d_func()->extra->focus_proxy) + w = w->d_func()->extra->focus_proxy; + return QWidgetPrivate::editingWidget == w; +} + +/*! + \fn void QWidget::setEditFocus(bool enable) + + If \a enable is true, make this widget have edit focus, in which + case Qt::Key_Up and Qt::Key_Down will be delivered to the widget + normally; otherwise, Qt::Key_Up and Qt::Key_Down are used to + change focus. + + This feature is only available in Qt for Embedded Linux and Qt + for Symbian. + + \sa hasEditFocus(), QApplication::keypadNavigationEnabled() +*/ +void QWidget::setEditFocus(bool on) +{ + QWidget *f = this; + while (f->d_func()->extra && f->d_func()->extra->focus_proxy) + f = f->d_func()->extra->focus_proxy; + + if (QWidgetPrivate::editingWidget && QWidgetPrivate::editingWidget != f) + QWidgetPrivate::editingWidget->setEditFocus(false); + + if (on && !f->hasFocus()) + f->setFocus(); + + if ((!on && !QWidgetPrivate::editingWidget) + || (on && QWidgetPrivate::editingWidget == f)) { + return; + } + + if (!on && QWidgetPrivate::editingWidget == f) { + QWidgetPrivate::editingWidget = 0; + QEvent event(QEvent::LeaveEditFocus); + QApplication::sendEvent(f, &event); + QApplication::sendEvent(f->style(), &event); + } else if (on) { + QWidgetPrivate::editingWidget = f; + QEvent event(QEvent::EnterEditFocus); + QApplication::sendEvent(f, &event); + QApplication::sendEvent(f->style(), &event); + } +} +#endif + +/*! + \property QWidget::autoFillBackground + \brief whether the widget background is filled automatically + \since 4.1 + + If enabled, this property will cause Qt to fill the background of the + widget before invoking the paint event. The color used is defined by the + QPalette::Window color role from the widget's \l{QPalette}{palette}. + + In addition, Windows are always filled with QPalette::Window, unless the + WA_OpaquePaintEvent or WA_NoSystemBackground attributes are set. + + This property cannot be turned off (i.e., set to false) if a widget's + parent has a static gradient for its background. + + \warning Use this property with caution in conjunction with + \l{Qt Style Sheets}. When a widget has a style sheet with a valid + background or a border-image, this property is automatically disabled. + + By default, this property is false. + + \sa Qt::WA_OpaquePaintEvent, Qt::WA_NoSystemBackground, + {QWidget#Transparency and Double Buffering}{Transparency and Double Buffering} +*/ +bool QWidget::autoFillBackground() const +{ + Q_D(const QWidget); + return d->extra && d->extra->autoFillBackground; +} + +void QWidget::setAutoFillBackground(bool enabled) +{ + Q_D(QWidget); + if (!d->extra) + d->createExtra(); + if (d->extra->autoFillBackground == enabled) + return; + + d->extra->autoFillBackground = enabled; + d->updateIsOpaque(); + update(); + d->updateIsOpaque(); +} + +/*! + \class QWidget + \brief The QWidget class is the base class of all user interface objects. + + \ingroup basicwidgets + + The widget is the atom of the user interface: it receives mouse, keyboard + and other events from the window system, and paints a representation of + itself on the screen. Every widget is rectangular, and they are sorted in a + Z-order. A widget is clipped by its parent and by the widgets in front of + it. + + A widget that is not embedded in a parent widget is called a window. + Usually, windows have a frame and a title bar, although it is also possible + to create windows without such decoration using suitable + \l{Qt::WindowFlags}{window flags}). In Qt, QMainWindow and the various + subclasses of QDialog are the most common window types. + + Every widget's constructor accepts one or two standard arguments: + + \list 1 + \i \c{QWidget *parent = 0} is the parent of the new widget. If it is 0 + (the default), the new widget will be a window. If not, it will be + a child of \e parent, and be constrained by \e parent's geometry + (unless you specify Qt::Window as window flag). + \i \c{Qt::WindowFlags f = 0} (where available) sets the window flags; + the default is suitable for almost all widgets, but to get, for + example, a window without a window system frame, you must use + special flags. + \endlist + + QWidget has many member functions, but some of them have little direct + functionality; for example, QWidget has a font property, but never uses + this itself. There are many subclasses which provide real functionality, + such as QLabel, QPushButton, QListWidget, and QTabWidget. + + + \section1 Top-Level and Child Widgets + + A widget without a parent widget is always an independent window (top-level + widget). For these widgets, setWindowTitle() and setWindowIcon() set the + title bar and icon respectively. + + Non-window widgets are child widgets, displayed within their parent + widgets. Most widgets in Qt are mainly useful as child widgets. For + example, it is possible to display a button as a top-level window, but most + people prefer to put their buttons inside other widgets, such as QDialog. + + \image parent-child-widgets.png A parent widget containing various child widgets. + + The diagram above shows a QGroupBox widget being used to hold various child + widgets in a layout provided by QGridLayout. The QLabel child widgets have + been outlined to indicate their full sizes. + + If you want to use a QWidget to hold child widgets you will usually want to + add a layout to the parent QWidget. See \l{Layout Management} for more + information. + + + \section1 Composite Widgets + + When a widget is used as a container to group a number of child widgets, it + is known as a composite widget. These can be created by constructing a + widget with the required visual properties - a QFrame, for example - and + adding child widgets to it, usually managed by a layout. The above diagram + shows such a composite widget that was created using \l{Qt Designer}. + + Composite widgets can also be created by subclassing a standard widget, + such as QWidget or QFrame, and adding the necessary layout and child + widgets in the constructor of the subclass. Many of the \l{Qt Examples} + {examples provided with Qt} use this approach, and it is also covered in + the Qt \l{Tutorials}. + + + \section1 Custom Widgets and Painting + + Since QWidget is a subclass of QPaintDevice, subclasses can be used to + display custom content that is composed using a series of painting + operations with an instance of the QPainter class. This approach contrasts + with the canvas-style approach used by the \l{Graphics View} + {Graphics View Framework} where items are added to a scene by the + application and are rendered by the framework itself. + + Each widget performs all painting operations from within its paintEvent() + function. This is called whenever the widget needs to be redrawn, either + as a result of some external change or when requested by the application. + + The \l{widgets/analogclock}{Analog Clock example} shows how a simple widget + can handle paint events. + + + \section1 Size Hints and Size Policies + + When implementing a new widget, it is almost always useful to reimplement + sizeHint() to provide a reasonable default size for the widget and to set + the correct size policy with setSizePolicy(). + + By default, composite widgets which do not provide a size hint will be + sized according to the space requirements of their child widgets. + + The size policy lets you supply good default behavior for the layout + management system, so that other widgets can contain and manage yours + easily. The default size policy indicates that the size hint represents + the preferred size of the widget, and this is often good enough for many + widgets. + + \note The size of top-level widgets are constrained to 2/3 of the desktop's + height and width. You can resize() the widget manually if these bounds are + inadequate. + + + \section1 Events + + Widgets respond to events that are typically caused by user actions. Qt + delivers events to widgets by calling specific event handler functions with + instances of QEvent subclasses containing information about each event. + + If your widget only contains child widgets, you probably do not need to + implement any event handlers. If you want to detect a mouse click in a + child widget call the child's underMouse() function inside the widget's + mousePressEvent(). + + The \l{widgets/scribble}{Scribble example} implements a wider set of + events to handle mouse movement, button presses, and window resizing. + + You will need to supply the behavior and content for your own widgets, but + here is a brief overview of the events that are relevant to QWidget, + starting with the most common ones: + + \list + \i paintEvent() is called whenever the widget needs to be repainted. + Every widget displaying custom content must implement it. Painting + using a QPainter can only take place in a paintEvent() or a + function called by a paintEvent(). + \i resizeEvent() is called when the widget has been resized. + \i mousePressEvent() is called when a mouse button is pressed while + the mouse cursor is inside the widget, or when the widget has + grabbed the mouse using grabMouse(). Pressing the mouse without + releasing it is effectively the same as calling grabMouse(). + \i mouseReleaseEvent() is called when a mouse button is released. A + widget receives mouse release events when it has received the + corresponding mouse press event. This means that if the user + presses the mouse inside \e your widget, then drags the mouse + somewhere else before releasing the mouse button, \e your widget + receives the release event. There is one exception: if a popup menu + appears while the mouse button is held down, this popup immediately + steals the mouse events. + \i mouseDoubleClickEvent() is called when the user double-clicks in + the widget. If the user double-clicks, the widget receives a mouse + press event, a mouse release event and finally this event instead + of a second mouse press event. (Some mouse move events may also be + received if the mouse is not held steady during this operation.) It + is \e{not possible} to distinguish a click from a double-click + until the second click arrives. (This is one reason why most GUI + books recommend that double-clicks be an extension of + single-clicks, rather than trigger a different action.) + \endlist + + Widgets that accept keyboard input need to reimplement a few more event + handlers: + + \list + \i keyPressEvent() is called whenever a key is pressed, and again when + a key has been held down long enough for it to auto-repeat. The + \key Tab and \key Shift+Tab keys are only passed to the widget if + they are not used by the focus-change mechanisms. To force those + keys to be processed by your widget, you must reimplement + QWidget::event(). + \i focusInEvent() is called when the widget gains keyboard focus + (assuming you have called setFocusPolicy()). Well-behaved widgets + indicate that they own the keyboard focus in a clear but discreet + way. + \i focusOutEvent() is called when the widget loses keyboard focus. + \endlist + + You may be required to also reimplement some of the less common event + handlers: + + \list + \i mouseMoveEvent() is called whenever the mouse moves while a mouse + button is held down. This can be useful during drag and drop + operations. If you call \l{setMouseTracking()}{setMouseTracking}(true), + you get mouse move events even when no buttons are held down. + (See also the \l{Drag and Drop} guide.) + \i keyReleaseEvent() is called whenever a key is released and while it + is held down (if the key is auto-repeating). In that case, the + widget will receive a pair of key release and key press event for + every repeat. The \key Tab and \key Shift+Tab keys are only passed + to the widget if they are not used by the focus-change mechanisms. + To force those keys to be processed by your widget, you must + reimplement QWidget::event(). + \i wheelEvent() is called whenever the user turns the mouse wheel + while the widget has the focus. + \i enterEvent() is called when the mouse enters the widget's screen + space. (This excludes screen space owned by any of the widget's + children.) + \i leaveEvent() is called when the mouse leaves the widget's screen + space. If the mouse enters a child widget it will not cause a + leaveEvent(). + \i moveEvent() is called when the widget has been moved relative to + its parent. + \i closeEvent() is called when the user closes the widget (or when + close() is called). + \endlist + + There are also some rather obscure events described in the documentation + for QEvent::Type. To handle these events, you need to reimplement event() + directly. + + The default implementation of event() handles \key Tab and \key Shift+Tab + (to move the keyboard focus), and passes on most of the other events to + one of the more specialized handlers above. + + Events and the mechanism used to deliver them are covered in + \l{The Event System}. + + \section1 Groups of Functions and Properties + + \table + \header \i Context \i Functions and Properties + + \row \i Window functions \i + show(), + hide(), + raise(), + lower(), + close(). + + \row \i Top-level windows \i + \l windowModified, \l windowTitle, \l windowIcon, \l windowIconText, + \l isActiveWindow, activateWindow(), \l minimized, showMinimized(), + \l maximized, showMaximized(), \l fullScreen, showFullScreen(), + showNormal(). + + \row \i Window contents \i + update(), + repaint(), + scroll(). + + \row \i Geometry \i + \l pos, x(), y(), \l rect, \l size, width(), height(), move(), resize(), + \l sizePolicy, sizeHint(), minimumSizeHint(), + updateGeometry(), layout(), + \l frameGeometry, \l geometry, \l childrenRect, \l childrenRegion, + adjustSize(), + mapFromGlobal(), mapToGlobal(), + mapFromParent(), mapToParent(), + \l maximumSize, \l minimumSize, \l sizeIncrement, + \l baseSize, setFixedSize() + + \row \i Mode \i + \l visible, isVisibleTo(), + \l enabled, isEnabledTo(), + \l modal, + isWindow(), + \l mouseTracking, + \l updatesEnabled, + visibleRegion(). + + \row \i Look and feel \i + style(), + setStyle(), + \l styleSheet, + \l cursor, + \l font, + \l palette, + backgroundRole(), setBackgroundRole(), + fontInfo(), fontMetrics(). + + \row \i Keyboard focus functions \i + \l focus, \l focusPolicy, + setFocus(), clearFocus(), setTabOrder(), setFocusProxy(), + focusNextChild(), focusPreviousChild(). + + \row \i Mouse and keyboard grabbing \i + grabMouse(), releaseMouse(), + grabKeyboard(), releaseKeyboard(), + mouseGrabber(), keyboardGrabber(). + + \row \i Event handlers \i + event(), + mousePressEvent(), + mouseReleaseEvent(), + mouseDoubleClickEvent(), + mouseMoveEvent(), + keyPressEvent(), + keyReleaseEvent(), + focusInEvent(), + focusOutEvent(), + wheelEvent(), + enterEvent(), + leaveEvent(), + paintEvent(), + moveEvent(), + resizeEvent(), + closeEvent(), + dragEnterEvent(), + dragMoveEvent(), + dragLeaveEvent(), + dropEvent(), + childEvent(), + showEvent(), + hideEvent(), + customEvent(). + changeEvent(), + + \row \i System functions \i + parentWidget(), window(), setParent(), winId(), + find(), metric(). + + \row \i Interactive help \i + setToolTip(), setWhatsThis() + + \endtable + + + \section1 Widget Style Sheets + + In addition to the standard widget styles for each platform, widgets can + also be styled according to rules specified in a \l{styleSheet} + {style sheet}. This feature enables you to customize the appearance of + specific widgets to provide visual cues to users about their purpose. For + example, a button could be styled in a particular way to indicate that it + performs a destructive action. + + The use of widget style sheets is described in more detail in the + \l{Qt Style Sheets} document. + + + \section1 Transparency and Double Buffering + + Since Qt 4.0, QWidget automatically double-buffers its painting, so there + is no need to write double-buffering code in paintEvent() to avoid + flicker. + + Since Qt 4.1, the Qt::WA_ContentsPropagated widget attribute has been + deprecated. Instead, the contents of parent widgets are propagated by + default to each of their children as long as Qt::WA_PaintOnScreen is not + set. Custom widgets can be written to take advantage of this feature by + updating irregular regions (to create non-rectangular child widgets), or + painting with colors that have less than full alpha component. The + following diagram shows how attributes and properties of a custom widget + can be fine-tuned to achieve different effects. + + \image propagation-custom.png + + In the above diagram, a semi-transparent rectangular child widget with an + area removed is constructed and added to a parent widget (a QLabel showing + a pixmap). Then, different properties and widget attributes are set to + achieve different effects: + + \list + \i The left widget has no additional properties or widget attributes + set. This default state suits most custom widgets using + transparency, are irregularly-shaped, or do not paint over their + entire area with an opaque brush. + \i The center widget has the \l autoFillBackground property set. This + property is used with custom widgets that rely on the widget to + supply a default background, and do not paint over their entire + area with an opaque brush. + \i The right widget has the Qt::WA_OpaquePaintEvent widget attribute + set. This indicates that the widget will paint over its entire area + with opaque colors. The widget's area will initially be + \e{uninitialized}, represented in the diagram with a red diagonal + grid pattern that shines through the overpainted area. The + Qt::WA_OpaquePaintArea attribute is useful for widgets that need to + paint their own specialized contents quickly and do not need a + default filled background. + \endlist + + To rapidly update custom widgets with simple background colors, such as + real-time plotting or graphing widgets, it is better to define a suitable + background color (using setBackgroundRole() with the + QPalette::Window role), set the \l autoFillBackground property, and only + implement the necessary drawing functionality in the widget's paintEvent(). + + To rapidly update custom widgets that constantly paint over their entire + areas with opaque content, e.g., video streaming widgets, it is better to + set the widget's Qt::WA_OpaquePaintEvent, avoiding any unnecessary overhead + associated with repainting the widget's background. + + If a widget has both the Qt::WA_OpaquePaintEvent widget attribute \e{and} + the \l autoFillBackground property set, the Qt::WA_OpaquePaintEvent + attribute takes precedence. Depending on your requirements, you should + choose either one of them. + + Since Qt 4.1, the contents of parent widgets are also propagated to + standard Qt widgets. This can lead to some unexpected results if the + parent widget is decorated in a non-standard way, as shown in the diagram + below. + + \image propagation-standard.png + + The scope for customizing the painting behavior of standard Qt widgets, + without resorting to subclassing, is slightly less than that possible for + custom widgets. Usually, the desired appearance of a standard widget can be + achieved by setting its \l autoFillBackground property. + + + \section1 Creating Translucent Windows + + Since Qt 4.5, it has been possible to create windows with translucent regions + on window systems that support compositing. + + To enable this feature in a top-level widget, set its Qt::WA_TranslucentBackground + attribute with setAttribute() and ensure that its background is painted with + non-opaque colors in the regions you want to be partially transparent. + + Platform notes: + + \list + \o X11: This feature relies on the use of an X server that supports ARGB visuals + and a compositing window manager. + \o Windows: The widget needs to have the Qt::FramelessWindowHint window flag set + for the translucency to work. + \endlist + + + \section1 Native Widgets vs Alien Widgets + + Introduced in Qt 4.4, alien widgets are widgets unknown to the windowing + system. They do not have a native window handle associated with them. This + feature significantly speeds up widget painting, resizing, and removes flicker. + + Should you require the old behavior with native windows, you can choose + one of the following options: + + \list 1 + \i Use the \c{QT_USE_NATIVE_WINDOWS=1} in your environment. + \i Set the Qt::AA_NativeWindows attribute on your application. All + widgets will be native widgets. + \i Set the Qt::WA_NativeWindow attribute on widgets: The widget itself + and all of its ancestors will become native (unless + Qt::WA_DontCreateNativeAncestors is set). + \i Call QWidget::winId to enforce a native window (this implies 3). + \i Set the Qt::WA_PaintOnScreen attribute to enforce a native window + (this implies 3). + \endlist + + \sa QEvent, QPainter, QGridLayout, QBoxLayout + + \section1 Softkeys + + Since Qt 4.6, Softkeys are usually physical keys on a device that have a corresponding label or + other visual representation on the screen that is generally located next to its + physical counterpart. They are most often found on mobile phone platforms. In + modern touch based user interfaces it is also possible to have softkeys that do + not correspond to any physical keys. Softkeys differ from other onscreen labels + in that they are contextual. + + In Qt, contextual softkeys are added to a widget by calling addAction() and + passing a \c QAction with a softkey role set on it. When the widget + containing the softkey actions has focus, its softkeys should appear in + the user interface. Softkeys are discovered by traversing the widget + hierarchy so it is possible to define a single set of softkeys that are + present at all times by calling addAction() for a given top level widget. + + On some platforms, this concept overlaps with \c QMenuBar such that if no + other softkeys are found and the top level widget is a QMainWindow containing + a QMenuBar, the menubar actions may appear on one of the softkeys. + + Note: Currently softkeys are only supported on the Symbian Platform. + + \sa addAction(), QAction, QMenuBar + +*/ + +QWidgetMapper *QWidgetPrivate::mapper = 0; // widget with wid +QWidgetSet *QWidgetPrivate::allWidgets = 0; // widgets with no wid + + +/***************************************************************************** + QWidget utility functions + *****************************************************************************/ + +QRegion qt_dirtyRegion(QWidget *widget) +{ + if (!widget) + return QRegion(); + + QWidgetBackingStore *bs = qt_widget_private(widget)->maybeBackingStore(); + if (!bs) + return QRegion(); + + return bs->dirtyRegion(widget); +} + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ + +/* + Widget state flags: + \list + \i Qt::WA_WState_Created The widget has a valid winId(). + \i Qt::WA_WState_Visible The widget is currently visible. + \i Qt::WA_WState_Hidden The widget is hidden, i.e. it won't + become visible unless you call show() on it. Qt::WA_WState_Hidden + implies !Qt::WA_WState_Visible. + \i Qt::WA_WState_CompressKeys Compress keyboard events. + \i Qt::WA_WState_BlockUpdates Repaints and updates are disabled. + \i Qt::WA_WState_InPaintEvent Currently processing a paint event. + \i Qt::WA_WState_Reparented The widget has been reparented. + \i Qt::WA_WState_ConfigPending A configuration (resize/move) event is pending. + \i Qt::WA_WState_DND (Deprecated) The widget supports drag and drop, see setAcceptDrops(). + \endlist +*/ + +struct QWidgetExceptionCleaner +{ + /* this cleans up when the constructor throws an exception */ + static inline void cleanup(QWidget *that, QWidgetPrivate *d) + { +#ifdef QT_NO_EXCEPTIONS + Q_UNUSED(that); + Q_UNUSED(d); +#else + QWidgetPrivate::allWidgets->remove(that); + if (d->focus_next != that) { + if (d->focus_next) + d->focus_next->d_func()->focus_prev = d->focus_prev; + if (d->focus_prev) + d->focus_prev->d_func()->focus_next = d->focus_next; + } +#endif + } +}; + +/*! + Constructs a widget which is a child of \a parent, with widget + flags set to \a f. + + If \a parent is 0, the new widget becomes a window. If + \a parent is another widget, this widget becomes a child window + inside \a parent. The new widget is deleted when its \a parent is + deleted. + + The widget flags argument, \a f, is normally 0, but it can be set + to customize the frame of a window (i.e. \a + parent must be 0). To customize the frame, use a value composed + from the bitwise OR of any of the \l{Qt::WindowFlags}{window flags}. + + If you add a child widget to an already visible widget you must + explicitly show the child to make it visible. + + Note that the X11 version of Qt may not be able to deliver all + combinations of style flags on all systems. This is because on + X11, Qt can only ask the window manager, and the window manager + can override the application's settings. On Windows, Qt can set + whatever flags you want. + + \sa windowFlags +*/ +QWidget::QWidget(QWidget *parent, Qt::WindowFlags f) + : QObject(*new QWidgetPrivate, 0), QPaintDevice() +{ + QT_TRY { + d_func()->init(parent, f); + } QT_CATCH(...) { + QWidgetExceptionCleaner::cleanup(this, d_func()); + QT_RETHROW; + } +} + +#ifdef QT3_SUPPORT +/*! + \overload + \obsolete + */ +QWidget::QWidget(QWidget *parent, const char *name, Qt::WindowFlags f) + : QObject(*new QWidgetPrivate, 0), QPaintDevice() +{ + QT_TRY { + d_func()->init(parent , f); + setObjectName(QString::fromAscii(name)); + } QT_CATCH(...) { + QWidgetExceptionCleaner::cleanup(this, d_func()); + QT_RETHROW; + } +} +#endif + +/*! \internal +*/ +QWidget::QWidget(QWidgetPrivate &dd, QWidget* parent, Qt::WindowFlags f) + : QObject(dd, 0), QPaintDevice() +{ + Q_D(QWidget); + QT_TRY { + d->init(parent, f); + } QT_CATCH(...) { + QWidgetExceptionCleaner::cleanup(this, d_func()); + QT_RETHROW; + } +} + +/*! + \internal +*/ +int QWidget::devType() const +{ + return QInternal::Widget; +} + + +//### w is a "this" ptr, passed as a param because QWorkspace needs special logic +void QWidgetPrivate::adjustFlags(Qt::WindowFlags &flags, QWidget *w) +{ + bool customize = (flags & (Qt::CustomizeWindowHint + | Qt::FramelessWindowHint + | Qt::WindowTitleHint + | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowCloseButtonHint + | Qt::WindowContextHelpButtonHint)); + + uint type = (flags & Qt::WindowType_Mask); + + if ((type == Qt::Widget || type == Qt::SubWindow) && w && !w->parent()) { + type = Qt::Window; + flags |= Qt::Window; + } + + if (flags & Qt::CustomizeWindowHint) { + // modify window flags to make them consistent. + // Only enable this on non-Mac platforms. Since the old way of doing this would + // interpret WindowSystemMenuHint as a close button and we can't change that behavior + // we can't just add this in. +#ifndef Q_WS_MAC + if (flags & (Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint)) { + flags |= Qt::WindowSystemMenuHint; +#else + if (flags & (Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint + | Qt::WindowSystemMenuHint)) { +#endif + flags |= Qt::WindowTitleHint; + flags &= ~Qt::FramelessWindowHint; + } + } else if (customize && !(flags & Qt::FramelessWindowHint)) { + // if any of the window hints that affect the titlebar are set + // and the window is supposed to have frame, we add a titlebar + // and system menu by default. + flags |= Qt::WindowSystemMenuHint; + flags |= Qt::WindowTitleHint; + } + if (customize) + ; // don't modify window flags if the user explicitly set them. + else if (type == Qt::Dialog || type == Qt::Sheet) +#ifndef Q_WS_WINCE + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowContextHelpButtonHint | Qt::WindowCloseButtonHint; +#else + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; +#endif + else if (type == Qt::Tool) + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + else + flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint; + + +} + +void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) +{ + Q_Q(QWidget); + if (QApplication::type() == QApplication::Tty) + qFatal("QWidget: Cannot create a QWidget when no GUI is being used"); + + Q_ASSERT(allWidgets); + if (allWidgets) + allWidgets->insert(q); + + QWidget *desktopWidget = 0; + if (parentWidget && parentWidget->windowType() == Qt::Desktop) { + desktopWidget = parentWidget; + parentWidget = 0; + } + + q->data = &data; + +#ifndef QT_NO_THREAD + if (!parent) { + Q_ASSERT_X(q->thread() == qApp->thread(), "QWidget", + "Widgets must be created in the GUI thread."); + } +#endif + +#if defined(Q_WS_X11) + if (desktopWidget) { + // make sure the widget is created on the same screen as the + // programmer specified desktop widget + xinfo = desktopWidget->d_func()->xinfo; + } +#elif defined(Q_OS_SYMBIAN) + if (desktopWidget) { + symbianScreenNumber = qt_widget_private(desktopWidget)->symbianScreenNumber; + } +#elif defined(Q_WS_QPA) + if (desktopWidget) { + int screen = desktopWidget->d_func()->topData()->screenIndex; + QPlatformIntegration *platform = QApplicationPrivate::platformIntegration(); + platform->moveToScreen(q, screen); + } +#else + Q_UNUSED(desktopWidget); +#endif + + data.fstrut_dirty = true; + + data.winid = 0; + data.widget_attributes = 0; + data.window_flags = f; + data.window_state = 0; + data.focus_policy = 0; + data.context_menu_policy = Qt::DefaultContextMenu; + data.window_modality = Qt::NonModal; + + data.sizehint_forced = 0; + data.is_closing = 0; + data.in_show = 0; + data.in_set_window_state = 0; + data.in_destructor = false; + + // Widgets with Qt::MSWindowsOwnDC (typically QGLWidget) must have a window handle. + if (f & Qt::MSWindowsOwnDC) + q->setAttribute(Qt::WA_NativeWindow); + +//#ifdef Q_WS_MAC +// q->setAttribute(Qt::WA_NativeWindow); +//#endif + + q->setAttribute(Qt::WA_QuitOnClose); // might be cleared in adjustQuitOnCloseAttribute() + adjustQuitOnCloseAttribute(); + + q->setAttribute(Qt::WA_WState_Hidden); + + //give potential windows a bigger "pre-initial" size; create_sys() will give them a new size later +#ifdef Q_OS_SYMBIAN + if (isGLWidget) { + // Don't waste GPU mem for unnecessary large egl surface until resized by application + data.crect = QRect(0,0,1,1); + } else { + data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,360,640); + } +#else + data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,640,480); +#endif + + focus_next = focus_prev = q; + + if ((f & Qt::WindowType_Mask) == Qt::Desktop) + q->create(); + else if (parentWidget) + q->setParent(parentWidget, data.window_flags); + else { + adjustFlags(data.window_flags, q); + resolveLayoutDirection(); + // opaque system background? + const QBrush &background = q->palette().brush(QPalette::Window); + setOpaque(q->isWindow() && background.style() != Qt::NoBrush && background.isOpaque()); + } + data.fnt = QFont(data.fnt, q); +#if defined(Q_WS_X11) + data.fnt.x11SetScreen(xinfo.screen()); +#endif // Q_WS_X11 + + q->setAttribute(Qt::WA_PendingMoveEvent); + q->setAttribute(Qt::WA_PendingResizeEvent); + + if (++QWidgetPrivate::instanceCounter > QWidgetPrivate::maxInstances) + QWidgetPrivate::maxInstances = QWidgetPrivate::instanceCounter; + + if (QApplicationPrivate::app_compile_version < 0x040200 + || QApplicationPrivate::testAttribute(Qt::AA_ImmediateWidgetCreation)) + q->create(); + + + QEvent e(QEvent::Create); + QApplication::sendEvent(q, &e); + QApplication::postEvent(q, new QEvent(QEvent::PolishRequest)); + + extraPaintEngine = 0; + +#ifdef QT_MAC_USE_COCOA + // If we add a child to the unified toolbar, we have to redirect the painting. + if (parentWidget && parentWidget->d_func() && parentWidget->d_func()->isInUnifiedToolbar) { + if (parentWidget->d_func()->unifiedSurface) { + QWidget *toolbar = parentWidget->d_func()->toolbar_ancestor; + parentWidget->d_func()->unifiedSurface->recursiveRedirect(toolbar, toolbar, toolbar->d_func()->toolbar_offset); + } + } +#endif // QT_MAC_USE_COCOA +} + + + +void QWidgetPrivate::createRecursively() +{ + Q_Q(QWidget); + q->create(0, true, true); + for (int i = 0; i < children.size(); ++i) { + QWidget *child = qobject_cast<QWidget *>(children.at(i)); + if (child && !child->isHidden() && !child->isWindow() && !child->testAttribute(Qt::WA_WState_Created)) + child->d_func()->createRecursively(); + } +} + + + + +/*! + Creates a new widget window if \a window is 0, otherwise sets the + widget's window to \a window. + + Initializes the window (sets the geometry etc.) if \a + initializeWindow is true. If \a initializeWindow is false, no + initialization is performed. This parameter only makes sense if \a + window is a valid window. + + Destroys the old window if \a destroyOldWindow is true. If \a + destroyOldWindow is false, you are responsible for destroying the + window yourself (using platform native code). + + The QWidget constructor calls create(0,true,true) to create a + window for this widget. +*/ + +void QWidget::create(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_D(QWidget); + if (testAttribute(Qt::WA_WState_Created) && window == 0 && internalWinId()) + return; + + if (d->data.in_destructor) + return; + + Qt::WindowType type = windowType(); + Qt::WindowFlags &flags = data->window_flags; + + if ((type == Qt::Widget || type == Qt::SubWindow) && !parentWidget()) { + type = Qt::Window; + flags |= Qt::Window; + } + +#ifndef Q_WS_QPA + if (QWidget *parent = parentWidget()) { + if (type & Qt::Window) { + if (!parent->testAttribute(Qt::WA_WState_Created)) + parent->createWinId(); + } else if (testAttribute(Qt::WA_NativeWindow) && !parent->internalWinId() + && !testAttribute(Qt::WA_DontCreateNativeAncestors)) { + // We're about to create a native child widget that doesn't have a native parent; + // enforce a native handle for the parent unless the Qt::WA_DontCreateNativeAncestors + // attribute is set. + d->createWinId(window); + // Nothing more to do. + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + Q_ASSERT(internalWinId()); + return; + } + } +#endif //Q_WS_QPA + +#ifdef QT3_SUPPORT + if (flags & Qt::WStaticContents) + setAttribute(Qt::WA_StaticContents); + if (flags & Qt::WDestructiveClose) + setAttribute(Qt::WA_DeleteOnClose); + if (flags & Qt::WShowModal) + setWindowModality(Qt::ApplicationModal); + if (flags & Qt::WMouseNoMask) + setAttribute(Qt::WA_MouseNoMask); + if (flags & Qt::WGroupLeader) + setAttribute(Qt::WA_GroupLeader); + if (flags & Qt::WNoMousePropagation) + setAttribute(Qt::WA_NoMousePropagation); +#endif + + static int paintOnScreenEnv = -1; + if (paintOnScreenEnv == -1) + paintOnScreenEnv = qgetenv("QT_ONSCREEN_PAINT").toInt() > 0 ? 1 : 0; + if (paintOnScreenEnv == 1) + setAttribute(Qt::WA_PaintOnScreen); + + if (QApplicationPrivate::testAttribute(Qt::AA_NativeWindows)) + setAttribute(Qt::WA_NativeWindow); + +#ifdef ALIEN_DEBUG + qDebug() << "QWidget::create:" << this << "parent:" << parentWidget() + << "Alien?" << !testAttribute(Qt::WA_NativeWindow); +#endif + +#if defined (Q_WS_WIN) && !defined(QT_NO_DRAGANDDROP) + // Unregister the dropsite (if already registered) before we + // re-create the widget with a native window. + if (testAttribute(Qt::WA_WState_Created) && !internalWinId() && testAttribute(Qt::WA_NativeWindow) + && d->extra && d->extra->dropTarget) { + d->registerDropSite(false); + } +#endif // defined (Q_WS_WIN) && !defined(QT_NO_DRAGANDDROP) + + d->updateIsOpaque(); + + setAttribute(Qt::WA_WState_Created); // set created flag + d->create_sys(window, initializeWindow, destroyOldWindow); + + // a real toplevel window needs a backing store + if (isWindow() && windowType() != Qt::Desktop) { + d->topData()->backingStore.destroy(); + if (hasBackingStoreSupport()) + d->topData()->backingStore.create(this); + } + + d->setModal_sys(); + + if (!isWindow() && parentWidget() && parentWidget()->testAttribute(Qt::WA_DropSiteRegistered)) + setAttribute(Qt::WA_DropSiteRegistered, true); + +#ifdef QT_EVAL + extern void qt_eval_init_widget(QWidget *w); + qt_eval_init_widget(this); +#endif + + // need to force the resting of the icon after changing parents + if (testAttribute(Qt::WA_SetWindowIcon)) + d->setWindowIcon_sys(true); + if (isWindow() && !d->topData()->iconText.isEmpty()) + d->setWindowIconText_helper(d->topData()->iconText); + if (isWindow() && !d->topData()->caption.isEmpty()) + d->setWindowTitle_helper(d->topData()->caption); + if (windowType() != Qt::Desktop) { + d->updateSystemBackground(); + + if (isWindow() && !testAttribute(Qt::WA_SetWindowIcon)) + d->setWindowIcon_sys(); + } +} + +/*! + Destroys the widget. + + All this widget's children are deleted first. The application + exits if this widget is the main widget. +*/ + +QWidget::~QWidget() +{ + Q_D(QWidget); + d->data.in_destructor = true; + +#if defined (QT_CHECK_STATE) + if (paintingActive()) + qWarning("QWidget: %s (%s) deleted while being painted", className(), name()); +#endif + +#ifndef QT_NO_GESTURES + foreach (Qt::GestureType type, d->gestureContext.keys()) + ungrabGesture(type); +#endif + + // force acceptDrops false before winId is destroyed. + d->registerDropSite(false); + +#ifndef QT_NO_ACTION + // remove all actions from this widget + for (int i = 0; i < d->actions.size(); ++i) { + QActionPrivate *apriv = d->actions.at(i)->d_func(); + apriv->widgets.removeAll(this); + } + d->actions.clear(); +#endif + +#ifndef QT_NO_SHORTCUT + // Remove all shortcuts grabbed by this + // widget, unless application is closing + if (!QApplicationPrivate::is_app_closing && testAttribute(Qt::WA_GrabbedShortcut)) + qApp->d_func()->shortcutMap.removeShortcut(0, this, QKeySequence()); +#endif + + // delete layout while we still are a valid widget + delete d->layout; + // Remove myself from focus list + + Q_ASSERT(d->focus_next->d_func()->focus_prev == this); + Q_ASSERT(d->focus_prev->d_func()->focus_next == this); + + if (d->focus_next != this) { + d->focus_next->d_func()->focus_prev = d->focus_prev; + d->focus_prev->d_func()->focus_next = d->focus_next; + d->focus_next = d->focus_prev = 0; + } + +#ifdef QT3_SUPPORT + if (QApplicationPrivate::main_widget == this) { // reset main widget + QApplicationPrivate::main_widget = 0; + QApplication::quit(); + } +#endif + + QT_TRY { + clearFocus(); + } QT_CATCH(...) { + // swallow this problem because we are in a destructor + } + + d->setDirtyOpaqueRegion(); + + if (isWindow() && isVisible() && internalWinId()) { + QT_TRY { + d->close_helper(QWidgetPrivate::CloseNoEvent); + } QT_CATCH(...) { + // if we're out of memory, at least hide the window. + QT_TRY { + hide(); + } QT_CATCH(...) { + // and if that also doesn't work, then give up + } + } + } + +#if defined(Q_WS_WIN) || defined(Q_WS_X11)|| defined(Q_WS_MAC) + else if (!internalWinId() && isVisible()) { + qApp->d_func()->sendSyntheticEnterLeave(this); + } +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) + else if (isVisible()) { + qApp->d_func()->sendSyntheticEnterLeave(this); + } +#endif + +#ifdef Q_OS_SYMBIAN + if (d->extra && d->extra->topextra && d->extra->topextra->backingStore) { + // Okay, we are about to destroy the top-level window that owns + // the backing store. Make sure we delete the backing store right away + // before the window handle is invalid. This is important because + // the backing store will delete its window surface, which may or may + // not have a reference to this widget that will be used later to + // notify the window it no longer has a surface. + d->extra->topextra->backingStore.destroy(); + } +#endif + if (QWidgetBackingStore *bs = d->maybeBackingStore()) { + bs->removeDirtyWidget(this); + if (testAttribute(Qt::WA_StaticContents)) + bs->removeStaticWidget(this); + } + + delete d->needsFlush; + d->needsFlush = 0; + + // set all QPointers for this object to zero + if (d->hasGuards) + QObjectPrivate::clearGuards(this); + + if (d->declarativeData) { + QAbstractDeclarativeData::destroyed(d->declarativeData, this); + d->declarativeData = 0; // don't activate again in ~QObject + } + +#ifdef QT_MAC_USE_COCOA + // QCocoaView holds a pointer back to this widget. Clear it now + // to make sure it's not followed later on. The lifetime of the + // QCocoaView might exceed the lifetime of this widget in cases + // where Cocoa itself holds references to it. + extern void qt_mac_clearCocoaViewQWidgetPointers(QWidget *); + qt_mac_clearCocoaViewQWidgetPointers(this); +#endif + + if (!d->children.isEmpty()) + d->deleteChildren(); + + QApplication::removePostedEvents(this); + + QT_TRY { + destroy(); // platform-dependent cleanup + } QT_CATCH(...) { + // if this fails we can't do anything about it but at least we are not allowed to throw. + } + --QWidgetPrivate::instanceCounter; + + if (QWidgetPrivate::allWidgets) // might have been deleted by ~QApplication + QWidgetPrivate::allWidgets->remove(this); + + QT_TRY { + QEvent e(QEvent::Destroy); + QCoreApplication::sendEvent(this, &e); + } QT_CATCH(const std::exception&) { + // if this fails we can't do anything about it but at least we are not allowed to throw. + } +} + +int QWidgetPrivate::instanceCounter = 0; // Current number of widget instances +int QWidgetPrivate::maxInstances = 0; // Maximum number of widget instances + +void QWidgetPrivate::setWinId(WId id) // set widget identifier +{ + Q_Q(QWidget); + // the user might create a widget with Qt::Desktop window + // attribute (or create another QDesktopWidget instance), which + // will have the same windowid (the root window id) as the + // qt_desktopWidget. We should not add the second desktop widget + // to the mapper. + bool userDesktopWidget = qt_desktopWidget != 0 && qt_desktopWidget != q && q->windowType() == Qt::Desktop; + if (mapper && data.winid && !userDesktopWidget) { + mapper->remove(data.winid); + } + + const WId oldWinId = data.winid; + + data.winid = id; +#if defined(Q_WS_X11) + hd = id; // X11: hd == ident +#endif + if (mapper && id && !userDesktopWidget) { + mapper->insert(data.winid, q); + } + + if(oldWinId != id) { + QEvent e(QEvent::WinIdChange); + QCoreApplication::sendEvent(q, &e); + } +} + +void QWidgetPrivate::createTLExtra() +{ + if (!extra) + createExtra(); + if (!extra->topextra) { + QTLWExtra* x = extra->topextra = new QTLWExtra; + x->icon = 0; + x->iconPixmap = 0; + x->windowSurface = 0; + x->sharedPainter = 0; + x->incw = x->inch = 0; + x->basew = x->baseh = 0; + x->frameStrut.setCoords(0, 0, 0, 0); + x->normalGeometry = QRect(0,0,-1,-1); + x->savedFlags = 0; + x->opacity = 255; + x->posFromMove = false; + x->sizeAdjusted = false; + x->inTopLevelResize = false; + x->inRepaint = false; + x->embedded = 0; +#ifdef Q_WS_MAC +#ifdef QT_MAC_USE_COCOA + x->wasMaximized = false; +#endif // QT_MAC_USE_COCOA +#endif // Q_WS_MAC + createTLSysExtra(); +#ifdef QWIDGET_EXTRA_DEBUG + static int count = 0; + qDebug() << "tlextra" << ++count; +#endif +#if defined(Q_WS_QPA) + x->platformWindow = 0; + x->platformWindowFormat = QPlatformWindowFormat::defaultFormat(); + x->screenIndex = 0; +#endif + } +} + +/*! + \internal + Creates the widget extra data. +*/ + +void QWidgetPrivate::createExtra() +{ + if (!extra) { // if not exists + extra = new QWExtra; + extra->glContext = 0; + extra->topextra = 0; +#ifndef QT_NO_GRAPHICSVIEW + extra->proxyWidget = 0; +#endif +#ifndef QT_NO_CURSOR + extra->curs = 0; +#endif + extra->minw = 0; + extra->minh = 0; + extra->maxw = QWIDGETSIZE_MAX; + extra->maxh = QWIDGETSIZE_MAX; + extra->customDpiX = 0; + extra->customDpiY = 0; + extra->explicitMinSize = 0; + extra->explicitMaxSize = 0; + extra->autoFillBackground = 0; + extra->nativeChildrenForced = 0; + extra->inRenderWithPainter = 0; + extra->hasMask = 0; + createSysExtra(); +#ifdef QWIDGET_EXTRA_DEBUG + static int count = 0; + qDebug() << "extra" << ++count; +#endif + } +} + + +/*! + \internal + Deletes the widget extra data. +*/ + +void QWidgetPrivate::deleteExtra() +{ + if (extra) { // if exists +#ifndef QT_NO_CURSOR + delete extra->curs; +#endif + deleteSysExtra(); +#ifndef QT_NO_STYLE_STYLESHEET + // dereference the stylesheet style + if (QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle *>(extra->style)) + proxy->deref(); +#endif + if (extra->topextra) { + deleteTLSysExtra(); + extra->topextra->backingStore.destroy(); + delete extra->topextra->icon; + delete extra->topextra->iconPixmap; +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_MANAGER) + delete extra->topextra->qwsManager; +#endif + delete extra->topextra->windowSurface; + delete extra->topextra; + } + delete extra; + // extra->xic destroyed in QWidget::destroy() + extra = 0; + } +} + +/* + Returns true if there are widgets above this which overlap with + \a rect, which is in parent's coordinate system (same as crect). +*/ + +bool QWidgetPrivate::isOverlapped(const QRect &rect) const +{ + Q_Q(const QWidget); + + const QWidget *w = q; + QRect r = rect; + while (w) { + if (w->isWindow()) + return false; + QWidgetPrivate *pd = w->parentWidget()->d_func(); + bool above = false; + for (int i = 0; i < pd->children.size(); ++i) { + QWidget *sibling = qobject_cast<QWidget *>(pd->children.at(i)); + if (!sibling || !sibling->isVisible() || sibling->isWindow()) + continue; + if (!above) { + above = (sibling == w); + continue; + } + + if (qRectIntersects(sibling->d_func()->effectiveRectFor(sibling->data->crect), r)) { + const QWExtra *siblingExtra = sibling->d_func()->extra; + if (siblingExtra && siblingExtra->hasMask && !sibling->d_func()->graphicsEffect + && !siblingExtra->mask.translated(sibling->data->crect.topLeft()).intersects(r)) { + continue; + } + return true; + } + } + w = w->parentWidget(); + r.translate(pd->data.crect.topLeft()); + } + return false; +} + +void QWidgetPrivate::syncBackingStore() +{ + if (paintOnScreen()) { + repaint_sys(dirty); + dirty = QRegion(); + } else if (QWidgetBackingStore *bs = maybeBackingStore()) { + bs->sync(); + } +} + +void QWidgetPrivate::syncBackingStore(const QRegion ®ion) +{ + if (paintOnScreen()) + repaint_sys(region); + else if (QWidgetBackingStore *bs = maybeBackingStore()) { + bs->sync(q_func(), region); + } +} + +void QWidgetPrivate::setUpdatesEnabled_helper(bool enable) +{ + Q_Q(QWidget); + + if (enable && !q->isWindow() && q->parentWidget() && !q->parentWidget()->updatesEnabled()) + return; // nothing we can do + + if (enable != q->testAttribute(Qt::WA_UpdatesDisabled)) + return; // nothing to do + + q->setAttribute(Qt::WA_UpdatesDisabled, !enable); + if (enable) + q->update(); + + Qt::WidgetAttribute attribute = enable ? Qt::WA_ForceUpdatesDisabled : Qt::WA_UpdatesDisabled; + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (w && !w->isWindow() && !w->testAttribute(attribute)) + w->d_func()->setUpdatesEnabled_helper(enable); + } +} + +/*! + \internal + + Propagate this widget's palette to all children, except style sheet + widgets, and windows that don't enable window propagation (palettes don't + normally propagate to windows). +*/ +void QWidgetPrivate::propagatePaletteChange() +{ + Q_Q(QWidget); + // Propagate a new inherited mask to all children. +#ifndef QT_NO_GRAPHICSVIEW + if (!q->parentWidget() && extra && extra->proxyWidget) { + QGraphicsProxyWidget *p = extra->proxyWidget; + inheritedPaletteResolveMask = p->d_func()->inheritedPaletteResolveMask | p->palette().resolve(); + } else +#endif //QT_NO_GRAPHICSVIEW + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) { + inheritedPaletteResolveMask = 0; + } + int mask = data.pal.resolve() | inheritedPaletteResolveMask; + + QEvent pc(QEvent::PaletteChange); + QApplication::sendEvent(q, &pc); + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget*>(children.at(i)); + if (w && !w->testAttribute(Qt::WA_StyleSheet) + && (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation))) { + QWidgetPrivate *wd = w->d_func(); + wd->inheritedPaletteResolveMask = mask; + wd->resolvePalette(); + } + } +#if defined(QT3_SUPPORT) + q->paletteChange(q->palette()); // compatibility +#endif +} + +/* + Returns the widget's clipping rectangle. +*/ +QRect QWidgetPrivate::clipRect() const +{ + Q_Q(const QWidget); + const QWidget * w = q; + if (!w->isVisible()) + return QRect(); + QRect r = effectiveRectFor(q->rect()); + int ox = 0; + int oy = 0; + while (w + && w->isVisible() + && !w->isWindow() + && w->parentWidget()) { + ox -= w->x(); + oy -= w->y(); + w = w->parentWidget(); + r &= QRect(ox, oy, w->width(), w->height()); + } + return r; +} + +/* + Returns the widget's clipping region (without siblings). +*/ +QRegion QWidgetPrivate::clipRegion() const +{ + Q_Q(const QWidget); + if (!q->isVisible()) + return QRegion(); + QRegion r(q->rect()); + const QWidget * w = q; + const QWidget *ignoreUpTo; + int ox = 0; + int oy = 0; + while (w + && w->isVisible() + && !w->isWindow() + && w->parentWidget()) { + ox -= w->x(); + oy -= w->y(); + ignoreUpTo = w; + w = w->parentWidget(); + r &= QRegion(ox, oy, w->width(), w->height()); + + int i = 0; + while(w->d_func()->children.at(i++) != static_cast<const QObject *>(ignoreUpTo)) + ; + for ( ; i < w->d_func()->children.size(); ++i) { + if(QWidget *sibling = qobject_cast<QWidget *>(w->d_func()->children.at(i))) { + if(sibling->isVisible() && !sibling->isWindow()) { + QRect siblingRect(ox+sibling->x(), oy+sibling->y(), + sibling->width(), sibling->height()); + if (qRectIntersects(siblingRect, q->rect())) + r -= QRegion(siblingRect); + } + } + } + } + return r; +} + +#ifndef QT_NO_GRAPHICSEFFECT +void QWidgetPrivate::invalidateGraphicsEffectsRecursively() +{ + Q_Q(QWidget); + QWidget *w = q; + do { + if (w->graphicsEffect()) { + QWidgetEffectSourcePrivate *sourced = + static_cast<QWidgetEffectSourcePrivate *>(w->graphicsEffect()->source()->d_func()); + if (!sourced->updateDueToGraphicsEffect) + w->graphicsEffect()->source()->d_func()->invalidateCache(); + } + w = w->parentWidget(); + } while (w); +} +#endif //QT_NO_GRAPHICSEFFECT + +void QWidgetPrivate::setDirtyOpaqueRegion() +{ + Q_Q(QWidget); + + dirtyOpaqueChildren = true; + +#ifndef QT_NO_GRAPHICSEFFECT + invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + if (q->isWindow()) + return; + + QWidget *parent = q->parentWidget(); + if (!parent) + return; + + // TODO: instead of setting dirtyflag, manipulate the dirtyregion directly? + QWidgetPrivate *pd = parent->d_func(); + if (!pd->dirtyOpaqueChildren) + pd->setDirtyOpaqueRegion(); +} + +const QRegion &QWidgetPrivate::getOpaqueChildren() const +{ + if (!dirtyOpaqueChildren) + return opaqueChildren; + + QWidgetPrivate *that = const_cast<QWidgetPrivate*>(this); + that->opaqueChildren = QRegion(); + + for (int i = 0; i < children.size(); ++i) { + QWidget *child = qobject_cast<QWidget *>(children.at(i)); + if (!child || !child->isVisible() || child->isWindow()) + continue; + + const QPoint offset = child->geometry().topLeft(); + QWidgetPrivate *childd = child->d_func(); + QRegion r = childd->isOpaque ? child->rect() : childd->getOpaqueChildren(); + if (childd->extra && childd->extra->hasMask) + r &= childd->extra->mask; + if (r.isEmpty()) + continue; + r.translate(offset); + that->opaqueChildren += r; + } + + that->opaqueChildren &= q_func()->rect(); + that->dirtyOpaqueChildren = false; + + return that->opaqueChildren; +} + +void QWidgetPrivate::subtractOpaqueChildren(QRegion &source, const QRect &clipRect) const +{ + if (children.isEmpty() || clipRect.isEmpty()) + return; + + const QRegion &r = getOpaqueChildren(); + if (!r.isEmpty()) + source -= (r & clipRect); +} + +//subtract any relatives that are higher up than me --- this is too expensive !!! +void QWidgetPrivate::subtractOpaqueSiblings(QRegion &sourceRegion, bool *hasDirtySiblingsAbove, + bool alsoNonOpaque) const +{ + Q_Q(const QWidget); + static int disableSubtractOpaqueSiblings = qgetenv("QT_NO_SUBTRACTOPAQUESIBLINGS").toInt(); + if (disableSubtractOpaqueSiblings || q->isWindow()) + return; + +#ifdef QT_MAC_USE_COCOA + if (q->d_func()->isInUnifiedToolbar) + return; +#endif // QT_MAC_USE_COCOA + + QRect clipBoundingRect; + bool dirtyClipBoundingRect = true; + + QRegion parentClip; + bool dirtyParentClip = true; + + QPoint parentOffset = data.crect.topLeft(); + + const QWidget *w = q; + + while (w) { + if (w->isWindow()) + break; + QWidgetPrivate *pd = w->parentWidget()->d_func(); + const int myIndex = pd->children.indexOf(const_cast<QWidget *>(w)); + const QRect widgetGeometry = w->d_func()->effectiveRectFor(w->data->crect); + for (int i = myIndex + 1; i < pd->children.size(); ++i) { + QWidget *sibling = qobject_cast<QWidget *>(pd->children.at(i)); + if (!sibling || !sibling->isVisible() || sibling->isWindow()) + continue; + + const QRect siblingGeometry = sibling->d_func()->effectiveRectFor(sibling->data->crect); + if (!qRectIntersects(siblingGeometry, widgetGeometry)) + continue; + + if (dirtyClipBoundingRect) { + clipBoundingRect = sourceRegion.boundingRect(); + dirtyClipBoundingRect = false; + } + + if (!qRectIntersects(siblingGeometry, clipBoundingRect.translated(parentOffset))) + continue; + + if (dirtyParentClip) { + parentClip = sourceRegion.translated(parentOffset); + dirtyParentClip = false; + } + + const QPoint siblingPos(sibling->data->crect.topLeft()); + const QRect siblingClipRect(sibling->d_func()->clipRect()); + QRegion siblingDirty(parentClip); + siblingDirty &= (siblingClipRect.translated(siblingPos)); + const bool hasMask = sibling->d_func()->extra && sibling->d_func()->extra->hasMask + && !sibling->d_func()->graphicsEffect; + if (hasMask) + siblingDirty &= sibling->d_func()->extra->mask.translated(siblingPos); + if (siblingDirty.isEmpty()) + continue; + + if (sibling->d_func()->isOpaque || alsoNonOpaque) { + if (hasMask) { + siblingDirty.translate(-parentOffset); + sourceRegion -= siblingDirty; + } else { + sourceRegion -= siblingGeometry.translated(-parentOffset); + } + } else { + if (hasDirtySiblingsAbove) + *hasDirtySiblingsAbove = true; + if (sibling->d_func()->children.isEmpty()) + continue; + QRegion opaqueSiblingChildren(sibling->d_func()->getOpaqueChildren()); + opaqueSiblingChildren.translate(-parentOffset + siblingPos); + sourceRegion -= opaqueSiblingChildren; + } + if (sourceRegion.isEmpty()) + return; + + dirtyClipBoundingRect = true; + dirtyParentClip = true; + } + + w = w->parentWidget(); + parentOffset += pd->data.crect.topLeft(); + dirtyParentClip = true; + } +} + +void QWidgetPrivate::clipToEffectiveMask(QRegion ®ion) const +{ + Q_Q(const QWidget); + + const QWidget *w = q; + QPoint offset; + +#ifndef QT_NO_GRAPHICSEFFECT + if (graphicsEffect) { + w = q->parentWidget(); + offset -= data.crect.topLeft(); + } +#endif //QT_NO_GRAPHICSEFFECT + + while (w) { + const QWidgetPrivate *wd = w->d_func(); + if (wd->extra && wd->extra->hasMask) + region &= (w != q) ? wd->extra->mask.translated(offset) : wd->extra->mask; + if (w->isWindow()) + return; + offset -= wd->data.crect.topLeft(); + w = w->parentWidget(); + } +} + +bool QWidgetPrivate::paintOnScreen() const +{ +#if defined(Q_WS_QWS) + return false; +#elif defined(QT_NO_BACKINGSTORE) + return true; +#else + Q_Q(const QWidget); + if (q->testAttribute(Qt::WA_PaintOnScreen) + || (!q->isWindow() && q->window()->testAttribute(Qt::WA_PaintOnScreen))) { + return true; + } + + return !qt_enable_backingstore; +#endif +} + +void QWidgetPrivate::updateIsOpaque() +{ + // hw: todo: only needed if opacity actually changed + setDirtyOpaqueRegion(); + +#ifndef QT_NO_GRAPHICSEFFECT + if (graphicsEffect) { + // ### We should probably add QGraphicsEffect::isOpaque at some point. + setOpaque(false); + return; + } +#endif //QT_NO_GRAPHICSEFFECT + + Q_Q(QWidget); +#ifdef Q_WS_X11 + if (q->testAttribute(Qt::WA_X11OpenGLOverlay)) { + setOpaque(false); + return; + } +#endif + +#ifdef Q_WS_S60 + if (q->windowType() == Qt::Dialog && q->testAttribute(Qt::WA_TranslucentBackground) + && S60->avkonComponentsSupportTransparency) { + setOpaque(false); + return; + } +#endif + + if (q->testAttribute(Qt::WA_OpaquePaintEvent) || q->testAttribute(Qt::WA_PaintOnScreen)) { + setOpaque(true); + return; + } + + const QPalette &pal = q->palette(); + + if (q->autoFillBackground()) { + const QBrush &autoFillBrush = pal.brush(q->backgroundRole()); + if (autoFillBrush.style() != Qt::NoBrush && autoFillBrush.isOpaque()) { + setOpaque(true); + return; + } + } + + if (q->isWindow() && !q->testAttribute(Qt::WA_NoSystemBackground)) { + const QBrush &windowBrush = q->palette().brush(QPalette::Window); + if (windowBrush.style() != Qt::NoBrush && windowBrush.isOpaque()) { + setOpaque(true); + return; + } + } + setOpaque(false); +} + +void QWidgetPrivate::setOpaque(bool opaque) +{ + if (isOpaque == opaque) + return; + isOpaque = opaque; +#ifdef Q_WS_MAC + macUpdateIsOpaque(); +#endif +#ifdef Q_WS_X11 + x11UpdateIsOpaque(); +#endif +#ifdef Q_WS_WIN + winUpdateIsOpaque(); +#endif +#ifdef Q_OS_SYMBIAN + s60UpdateIsOpaque(); +#endif +} + +void QWidgetPrivate::updateIsTranslucent() +{ +#ifdef Q_WS_MAC + macUpdateIsOpaque(); +#endif +#ifdef Q_WS_X11 + x11UpdateIsOpaque(); +#endif +#ifdef Q_WS_WIN + winUpdateIsOpaque(); +#endif +#ifdef Q_OS_SYMBIAN + s60UpdateIsOpaque(); +#endif +} + +/*! + \fn void QPixmap::fill(const QWidget *widget, const QPoint &offset) + + Fills the pixmap with the \a widget's background color or pixmap + according to the given offset. + + The QPoint \a offset defines a point in widget coordinates to + which the pixmap's top-left pixel will be mapped to. This is only + significant if the widget has a background pixmap; otherwise the + pixmap will simply be filled with the background color of the + widget. +*/ + +void QPixmap::fill( const QWidget *widget, const QPoint &off ) +{ + QPainter p(this); + p.translate(-off); + widget->d_func()->paintBackground(&p, QRect(off, size())); +} + +static inline void fillRegion(QPainter *painter, const QRegion &rgn, const QBrush &brush) +{ + Q_ASSERT(painter); + + if (brush.style() == Qt::TexturePattern) { +#ifdef Q_WS_MAC + // Optimize pattern filling on mac by using HITheme directly + // when filling with the standard widget background. + // Defined in qmacstyle_mac.cpp + extern void qt_mac_fill_background(QPainter *painter, const QRegion &rgn, const QBrush &brush); + qt_mac_fill_background(painter, rgn, brush); +#else +#if !defined(QT_NO_STYLE_S60) + // Defined in qs60style.cpp + extern bool qt_s60_fill_background(QPainter *painter, const QRegion &rgn, const QBrush &brush); + if (!qt_s60_fill_background(painter, rgn, brush)) +#endif // !defined(QT_NO_STYLE_S60) + { + const QRect rect(rgn.boundingRect()); + painter->setClipRegion(rgn); + painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft()); + } +#endif // Q_WS_MAC + + } else if (brush.gradient() + && brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) { + painter->save(); + painter->setClipRegion(rgn); + painter->fillRect(0, 0, painter->device()->width(), painter->device()->height(), brush); + painter->restore(); + } else { + const QVector<QRect> &rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) + painter->fillRect(rects.at(i), brush); + } +} + +void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const +{ + Q_Q(const QWidget); + +#ifndef QT_NO_SCROLLAREA + bool resetBrushOrigin = false; + QPointF oldBrushOrigin; + //If we are painting the viewport of a scrollarea, we must apply an offset to the brush in case we are drawing a texture + QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea *>(parent); + if (scrollArea && scrollArea->viewport() == q) { + QObjectData *scrollPrivate = static_cast<QWidget *>(scrollArea)->d_ptr.data(); + QAbstractScrollAreaPrivate *priv = static_cast<QAbstractScrollAreaPrivate *>(scrollPrivate); + oldBrushOrigin = painter->brushOrigin(); + resetBrushOrigin = true; + painter->setBrushOrigin(-priv->contentsOffset()); + + } +#endif // QT_NO_SCROLLAREA + + const QBrush autoFillBrush = q->palette().brush(q->backgroundRole()); + + if ((flags & DrawAsRoot) && !(q->autoFillBackground() && autoFillBrush.isOpaque())) { + const QBrush bg = q->palette().brush(QPalette::Window); +#ifdef Q_WS_QWS + if (!(flags & DontSetCompositionMode) && painter->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) + painter->setCompositionMode(QPainter::CompositionMode_Source); //copy alpha straight in +#endif + fillRegion(painter, rgn, bg); + } + + if (q->autoFillBackground()) + fillRegion(painter, rgn, autoFillBrush); + + if (q->testAttribute(Qt::WA_StyledBackground)) { + painter->setClipRegion(rgn); + QStyleOption opt; + opt.initFrom(q); + q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q); + } + +#ifndef QT_NO_SCROLLAREA + if (resetBrushOrigin) + painter->setBrushOrigin(oldBrushOrigin); +#endif // QT_NO_SCROLLAREA +} + +/* + \internal + This function is called when a widget is hidden or destroyed. + It resets some application global pointers that should only refer active, + visible widgets. +*/ + +#ifdef Q_WS_MAC + extern QPointer<QWidget> qt_button_down; +#else + extern QWidget *qt_button_down; +#endif + +void QWidgetPrivate::deactivateWidgetCleanup() +{ + Q_Q(QWidget); + // If this was the active application window, reset it + if (QApplication::activeWindow() == q) + QApplication::setActiveWindow(0); + // If the is the active mouse press widget, reset it + if (q == qt_button_down) + qt_button_down = 0; +} + + +/*! + Returns a pointer to the widget with window identifer/handle \a + id. + + The window identifier type depends on the underlying window + system, see \c qwindowdefs.h for the actual definition. If there + is no widget with this identifier, 0 is returned. +*/ + +QWidget *QWidget::find(WId id) +{ + return QWidgetPrivate::mapper ? QWidgetPrivate::mapper->value(id, 0) : 0; +} + + + +/*! + \fn WId QWidget::internalWinId() const + \internal + Returns the window system identifier of the widget, or 0 if the widget is not created yet. + +*/ + +/*! + \fn WId QWidget::winId() const + + Returns the window system identifier of the widget. + + Portable in principle, but if you use it you are probably about to + do something non-portable. Be careful. + + If a widget is non-native (alien) and winId() is invoked on it, that widget + will be provided a native handle. + + On Mac OS X, the type returned depends on which framework Qt was linked + against. If Qt is using Carbon, the {WId} is actually an HIViewRef. If Qt + is using Cocoa, {WId} is a pointer to an NSView. + + This value may change at run-time. An event with type QEvent::WinIdChange + will be sent to the widget following a change in window system identifier. + + \sa find() +*/ +WId QWidget::winId() const +{ + if (!testAttribute(Qt::WA_WState_Created) || !internalWinId()) { +#ifdef ALIEN_DEBUG + qDebug() << "QWidget::winId: creating native window for" << this; +#endif + QWidget *that = const_cast<QWidget*>(this); +#ifndef Q_WS_QPA + that->setAttribute(Qt::WA_NativeWindow); +#endif + that->d_func()->createWinId(); + return that->data->winid; + } + return data->winid; +} + + +void QWidgetPrivate::createWinId(WId winid) +{ + Q_Q(QWidget); + +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::createWinId for" << q << winid; +#endif + const bool forceNativeWindow = q->testAttribute(Qt::WA_NativeWindow); + if (!q->testAttribute(Qt::WA_WState_Created) || (forceNativeWindow && !q->internalWinId())) { +#ifndef Q_WS_QPA + if (!q->isWindow()) { + QWidget *parent = q->parentWidget(); + QWidgetPrivate *pd = parent->d_func(); + if (forceNativeWindow && !q->testAttribute(Qt::WA_DontCreateNativeAncestors)) + parent->setAttribute(Qt::WA_NativeWindow); + if (!parent->internalWinId()) { + pd->createWinId(); + } + + for (int i = 0; i < pd->children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(pd->children.at(i)); + if (w && !w->isWindow() && (!w->testAttribute(Qt::WA_WState_Created) + || (!w->internalWinId() && w->testAttribute(Qt::WA_NativeWindow)))) { + if (w!=q) { + w->create(); + } else { + w->create(winid); + // if the window has already been created, we + // need to raise it to its proper stacking position + if (winid) + w->raise(); + } + } + } + } else { + q->create(); + } +#else + Q_UNUSED(winid); + q->create(); +#endif //Q_WS_QPA + + } +} + + +/*! +\internal +Ensures that the widget has a window system identifier, i.e. that it is known to the windowing system. + +*/ + +void QWidget::createWinId() +{ + Q_D(QWidget); +#ifdef ALIEN_DEBUG + qDebug() << "QWidget::createWinId" << this; +#endif +// qWarning("QWidget::createWinId is obsolete, please fix your code."); + d->createWinId(); +} + +/*! + \since 4.4 + + Returns the effective window system identifier of the widget, i.e. the + native parent's window system identifier. + + If the widget is native, this function returns the native widget ID. + Otherwise, the window ID of the first native parent widget, i.e., the + top-level widget that contains this widget, is returned. + + \note We recommend that you do not store this value as it is likely to + change at run-time. + + \sa nativeParentWidget() +*/ +WId QWidget::effectiveWinId() const +{ + WId id = internalWinId(); + if (id || !testAttribute(Qt::WA_WState_Created)) + return id; + QWidget *realParent = nativeParentWidget(); + Q_ASSERT(realParent); + Q_ASSERT(realParent->internalWinId()); + return realParent->internalWinId(); +} + +#ifndef QT_NO_STYLE_STYLESHEET + +/*! + \property QWidget::styleSheet + \brief the widget's style sheet + \since 4.2 + + The style sheet contains a textual description of customizations to the + widget's style, as described in the \l{Qt Style Sheets} document. + + Since Qt 4.5, Qt style sheets fully supports Mac OS X. + + \warning Qt style sheets are currently not supported for custom QStyle + subclasses. We plan to address this in some future release. + + \sa setStyle(), QApplication::styleSheet, {Qt Style Sheets} +*/ +QString QWidget::styleSheet() const +{ + Q_D(const QWidget); + if (!d->extra) + return QString(); + return d->extra->styleSheet; +} + +void QWidget::setStyleSheet(const QString& styleSheet) +{ + Q_D(QWidget); + d->createExtra(); + + QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle *>(d->extra->style); + d->extra->styleSheet = styleSheet; + if (styleSheet.isEmpty()) { // stylesheet removed + if (!proxy) + return; + + d->inheritStyle(); + return; + } + + if (proxy) { // style sheet update + proxy->repolish(this); + return; + } + + if (testAttribute(Qt::WA_SetStyle)) { + d->setStyle_helper(new QStyleSheetStyle(d->extra->style), true); + } else { + d->setStyle_helper(new QStyleSheetStyle(0), true); + } +} + +#endif // QT_NO_STYLE_STYLESHEET + +/*! + \sa QWidget::setStyle(), QApplication::setStyle(), QApplication::style() +*/ + +QStyle *QWidget::style() const +{ + Q_D(const QWidget); + + if (d->extra && d->extra->style) + return d->extra->style; + return QApplication::style(); +} + +/*! + Sets the widget's GUI style to \a style. The ownership of the style + object is not transferred. + + If no style is set, the widget uses the application's style, + QApplication::style() instead. + + Setting a widget's style has no effect on existing or future child + widgets. + + \warning This function is particularly useful for demonstration + purposes, where you want to show Qt's styling capabilities. Real + applications should avoid it and use one consistent GUI style + instead. + + \warning Qt style sheets are currently not supported for custom QStyle + subclasses. We plan to address this in some future release. + + \sa style(), QStyle, QApplication::style(), QApplication::setStyle() +*/ + +void QWidget::setStyle(QStyle *style) +{ + Q_D(QWidget); + setAttribute(Qt::WA_SetStyle, style != 0); + d->createExtra(); +#ifndef QT_NO_STYLE_STYLESHEET + if (QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle *>(style)) { + //if for some reason someone try to set a QStyleSheetStyle, ref it + //(this may happen for exemple in QButtonDialogBox which propagates its style) + proxy->ref(); + d->setStyle_helper(style, false); + } else if (qobject_cast<QStyleSheetStyle *>(d->extra->style) || !qApp->styleSheet().isEmpty()) { + // if we have an application stylesheet or have a proxy already, propagate + d->setStyle_helper(new QStyleSheetStyle(style), true); + } else +#endif + d->setStyle_helper(style, false); +} + +void QWidgetPrivate::setStyle_helper(QStyle *newStyle, bool propagate, bool +#ifdef Q_WS_MAC + metalHack +#endif + ) +{ + Q_Q(QWidget); + QStyle *oldStyle = q->style(); +#ifndef QT_NO_STYLE_STYLESHEET + QWeakPointer<QStyle> origStyle; +#endif + +#ifdef Q_WS_MAC + // the metalhack boolean allows Qt/Mac to do a proper re-polish depending + // on how the Qt::WA_MacBrushedMetal attribute is set. It is only ever + // set when changing that attribute and passes the widget's CURRENT style. + // therefore no need to do a reassignment. + if (!metalHack) +#endif + { + createExtra(); + +#ifndef QT_NO_STYLE_STYLESHEET + origStyle = extra->style.data(); +#endif + extra->style = newStyle; + } + + // repolish + if (q->windowType() != Qt::Desktop) { + if (polished) { + oldStyle->unpolish(q); +#ifdef Q_WS_MAC + if (metalHack) + macUpdateMetalAttribute(); +#endif + q->style()->polish(q); +#ifdef Q_WS_MAC + } else if (metalHack) { + macUpdateMetalAttribute(); +#endif + } + } + + if (propagate) { + for (int i = 0; i < children.size(); ++i) { + QWidget *c = qobject_cast<QWidget*>(children.at(i)); + if (c) + c->d_func()->inheritStyle(); + } + } + +#ifndef QT_NO_STYLE_STYLESHEET + if (!qobject_cast<QStyleSheetStyle*>(newStyle)) { + if (const QStyleSheetStyle* cssStyle = qobject_cast<QStyleSheetStyle*>(origStyle.data())) { + cssStyle->clearWidgetFont(q); + } + } +#endif + + QEvent e(QEvent::StyleChange); + QApplication::sendEvent(q, &e); +#ifdef QT3_SUPPORT + q->styleChange(*oldStyle); +#endif + +#ifndef QT_NO_STYLE_STYLESHEET + // dereference the old stylesheet style + if (QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle *>(origStyle.data())) + proxy->deref(); +#endif +} + +// Inherits style from the current parent and propagates it as necessary +void QWidgetPrivate::inheritStyle() +{ +#ifndef QT_NO_STYLE_STYLESHEET + Q_Q(QWidget); + + QStyleSheetStyle *proxy = extra ? qobject_cast<QStyleSheetStyle *>(extra->style) : 0; + + if (!q->styleSheet().isEmpty()) { + Q_ASSERT(proxy); + proxy->repolish(q); + return; + } + + QStyle *origStyle = proxy ? proxy->base : (extra ? (QStyle*)extra->style : 0); + QWidget *parent = q->parentWidget(); + QStyle *parentStyle = (parent && parent->d_func()->extra) ? (QStyle*)parent->d_func()->extra->style : 0; + // If we have stylesheet on app or parent has stylesheet style, we need + // to be running a proxy + if (!qApp->styleSheet().isEmpty() || qobject_cast<QStyleSheetStyle *>(parentStyle)) { + QStyle *newStyle = parentStyle; + if (q->testAttribute(Qt::WA_SetStyle)) + newStyle = new QStyleSheetStyle(origStyle); + else if (QStyleSheetStyle *newProxy = qobject_cast<QStyleSheetStyle *>(parentStyle)) + newProxy->ref(); + + setStyle_helper(newStyle, true); + return; + } + + // So, we have no stylesheet on parent/app and we have an empty stylesheet + // we just need our original style back + if (origStyle == (extra ? (QStyle*)extra->style : 0)) // is it any different? + return; + + // We could have inherited the proxy from our parent (which has a custom style) + // In such a case we need to start following the application style (i.e revert + // the propagation behavior of QStyleSheetStyle) + if (!q->testAttribute(Qt::WA_SetStyle)) + origStyle = 0; + + setStyle_helper(origStyle, true); +#endif // QT_NO_STYLE_STYLESHEET +} + +#ifdef QT3_SUPPORT +/*! + \overload + + Sets the widget's GUI style to \a style using the QStyleFactory. +*/ +QStyle* QWidget::setStyle(const QString &style) +{ + QStyle *s = QStyleFactory::create(style); + setStyle(s); + return s; +} +#endif + +/*! + \fn bool QWidget::isWindow() const + + Returns true if the widget is an independent window, otherwise + returns false. + + A window is a widget that isn't visually the child of any other + widget and that usually has a frame and a + \l{QWidget::setWindowTitle()}{window title}. + + A window can have a \l{QWidget::parentWidget()}{parent widget}. + It will then be grouped with its parent and deleted when the + parent is deleted, minimized when the parent is minimized etc. If + supported by the window manager, it will also have a common + taskbar entry with its parent. + + QDialog and QMainWindow widgets are by default windows, even if a + parent widget is specified in the constructor. This behavior is + specified by the Qt::Window flag. + + \sa window(), isModal(), parentWidget() +*/ + +/*! + \property QWidget::modal + \brief whether the widget is a modal widget + + This property only makes sense for windows. A modal widget + prevents widgets in all other windows from getting any input. + + By default, this property is false. + + \sa isWindow(), windowModality, QDialog +*/ + +/*! + \property QWidget::windowModality + \brief which windows are blocked by the modal widget + \since 4.1 + + This property only makes sense for windows. A modal widget + prevents widgets in other windows from getting input. The value of + this property controls which windows are blocked when the widget + is visible. Changing this property while the window is visible has + no effect; you must hide() the widget first, then show() it again. + + By default, this property is Qt::NonModal. + + \sa isWindow(), QWidget::modal, QDialog +*/ + +Qt::WindowModality QWidget::windowModality() const +{ + return static_cast<Qt::WindowModality>(data->window_modality); +} + +void QWidget::setWindowModality(Qt::WindowModality windowModality) +{ + data->window_modality = windowModality; + // setModal_sys() will be called by setAttribute() + setAttribute(Qt::WA_ShowModal, (data->window_modality != Qt::NonModal)); + setAttribute(Qt::WA_SetWindowModality, true); +} + +/*! + \fn bool QWidget::underMouse() const + + Returns true if the widget is under the mouse cursor; otherwise + returns false. + + This value is not updated properly during drag and drop + operations. + + \sa enterEvent(), leaveEvent() +*/ + +/*! + \property QWidget::minimized + \brief whether this widget is minimized (iconified) + + This property is only relevant for windows. + + By default, this property is false. + + \sa showMinimized(), visible, show(), hide(), showNormal(), maximized +*/ +bool QWidget::isMinimized() const +{ return data->window_state & Qt::WindowMinimized; } + +/*! + Shows the widget minimized, as an icon. + + Calling this function only affects \l{isWindow()}{windows}. + + \sa showNormal(), showMaximized(), show(), hide(), isVisible(), + isMinimized() +*/ +void QWidget::showMinimized() +{ + bool isMin = isMinimized(); + if (isMin && isVisible()) + return; + + ensurePolished(); +#ifdef QT3_SUPPORT + if (parent()) + QApplication::sendPostedEvents(parent(), QEvent::ChildInserted); +#endif + + if (!isMin) + setWindowState((windowState() & ~Qt::WindowActive) | Qt::WindowMinimized); + show(); +} + +/*! + \property QWidget::maximized + \brief whether this widget is maximized + + This property is only relevant for windows. + + \note Due to limitations on some window systems, this does not always + report the expected results (e.g., if the user on X11 maximizes the + window via the window manager, Qt has no way of distinguishing this + from any other resize). This is expected to improve as window manager + protocols evolve. + + By default, this property is false. + + \sa windowState(), showMaximized(), visible, show(), hide(), showNormal(), minimized +*/ +bool QWidget::isMaximized() const +{ return data->window_state & Qt::WindowMaximized; } + + + +/*! + Returns the current window state. The window state is a OR'ed + combination of Qt::WindowState: Qt::WindowMinimized, + Qt::WindowMaximized, Qt::WindowFullScreen, and Qt::WindowActive. + + \sa Qt::WindowState setWindowState() + */ +Qt::WindowStates QWidget::windowState() const +{ + return Qt::WindowStates(data->window_state); +} + +/*!\internal + + The function sets the window state on child widgets similar to + setWindowState(). The difference is that the window state changed + event has the isOverride() flag set. It exists mainly to keep + Q3Workspace working. + */ +void QWidget::overrideWindowState(Qt::WindowStates newstate) +{ + QWindowStateChangeEvent e(Qt::WindowStates(data->window_state), true); + data->window_state = newstate; + QApplication::sendEvent(this, &e); +} + +/*! + \fn void QWidget::setWindowState(Qt::WindowStates windowState) + + Sets the window state to \a windowState. The window state is a OR'ed + combination of Qt::WindowState: Qt::WindowMinimized, + Qt::WindowMaximized, Qt::WindowFullScreen, and Qt::WindowActive. + + If the window is not visible (i.e. isVisible() returns false), the + window state will take effect when show() is called. For visible + windows, the change is immediate. For example, to toggle between + full-screen and normal mode, use the following code: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 0 + + In order to restore and activate a minimized window (while + preserving its maximized and/or full-screen state), use the following: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 1 + + Calling this function will hide the widget. You must call show() to make + the widget visible again. + + \note On some window systems Qt::WindowActive is not immediate, and may be + ignored in certain cases. + + When the window state changes, the widget receives a changeEvent() + of type QEvent::WindowStateChange. + + \sa Qt::WindowState windowState() +*/ + +/*! + \property QWidget::fullScreen + \brief whether the widget is shown in full screen mode + + A widget in full screen mode occupies the whole screen area and does not + display window decorations, such as a title bar. + + By default, this property is false. + + \sa windowState(), minimized, maximized +*/ +bool QWidget::isFullScreen() const +{ return data->window_state & Qt::WindowFullScreen; } + +/*! + Shows the widget in full-screen mode. + + Calling this function only affects \l{isWindow()}{windows}. + + To return from full-screen mode, call showNormal(). + + Full-screen mode works fine under Windows, but has certain + problems under X. These problems are due to limitations of the + ICCCM protocol that specifies the communication between X11 + clients and the window manager. ICCCM simply does not understand + the concept of non-decorated full-screen windows. Therefore, the + best we can do is to request a borderless window and place and + resize it to fill the entire screen. Depending on the window + manager, this may or may not work. The borderless window is + requested using MOTIF hints, which are at least partially + supported by virtually all modern window managers. + + An alternative would be to bypass the window manager entirely and + create a window with the Qt::X11BypassWindowManagerHint flag. This + has other severe problems though, like totally broken keyboard focus + and very strange effects on desktop changes or when the user raises + other windows. + + X11 window managers that follow modern post-ICCCM specifications + support full-screen mode properly. + + \sa showNormal(), showMaximized(), show(), hide(), isVisible() +*/ +void QWidget::showFullScreen() +{ +#ifdef Q_WS_MAC + // If the unified toolbar is enabled, we have to disable it before going fullscreen. + QMainWindow *mainWindow = qobject_cast<QMainWindow*>(this); + if (mainWindow && mainWindow->unifiedTitleAndToolBarOnMac()) { + mainWindow->setUnifiedTitleAndToolBarOnMac(false); + QMainWindowLayout *mainLayout = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + mainLayout->activateUnifiedToolbarAfterFullScreen = true; + } +#endif // Q_WS_MAC + ensurePolished(); +#ifdef QT3_SUPPORT + if (parent()) + QApplication::sendPostedEvents(parent(), QEvent::ChildInserted); +#endif + + setWindowState((windowState() & ~(Qt::WindowMinimized | Qt::WindowMaximized)) + | Qt::WindowFullScreen); + show(); + activateWindow(); +} + +/*! + Shows the widget maximized. + + Calling this function only affects \l{isWindow()}{windows}. + + On X11, this function may not work properly with certain window + managers. See the \l{Window Geometry} documentation for an explanation. + + \sa setWindowState(), showNormal(), showMinimized(), show(), hide(), isVisible() +*/ +void QWidget::showMaximized() +{ + ensurePolished(); +#ifdef QT3_SUPPORT + if (parent()) + QApplication::sendPostedEvents(parent(), QEvent::ChildInserted); +#endif + + setWindowState((windowState() & ~(Qt::WindowMinimized | Qt::WindowFullScreen)) + | Qt::WindowMaximized); +#ifdef Q_WS_MAC + // If the unified toolbar was enabled before going fullscreen, we have to enable it back. + QMainWindow *mainWindow = qobject_cast<QMainWindow*>(this); + if (mainWindow) + { + QMainWindowLayout *mainLayout = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + if (mainLayout->activateUnifiedToolbarAfterFullScreen) { + mainWindow->setUnifiedTitleAndToolBarOnMac(true); + mainLayout->activateUnifiedToolbarAfterFullScreen = false; + } + } +#endif // Q_WS_MAC + show(); +} + +/*! + Restores the widget after it has been maximized or minimized. + + Calling this function only affects \l{isWindow()}{windows}. + + \sa setWindowState(), showMinimized(), showMaximized(), show(), hide(), isVisible() +*/ +void QWidget::showNormal() +{ + ensurePolished(); +#ifdef QT3_SUPPORT + if (parent()) + QApplication::sendPostedEvents(parent(), QEvent::ChildInserted); +#endif + + setWindowState(windowState() & ~(Qt::WindowMinimized + | Qt::WindowMaximized + | Qt::WindowFullScreen)); +#ifdef Q_WS_MAC + // If the unified toolbar was enabled before going fullscreen, we have to enable it back. + QMainWindow *mainWindow = qobject_cast<QMainWindow*>(this); + if (mainWindow) + { + QMainWindowLayout *mainLayout = qobject_cast<QMainWindowLayout*>(mainWindow->layout()); + if (mainLayout->activateUnifiedToolbarAfterFullScreen) { + mainWindow->setUnifiedTitleAndToolBarOnMac(true); + mainLayout->activateUnifiedToolbarAfterFullScreen = false; + } + } +#endif // Q_WS_MAC + show(); +} + +/*! + Returns true if this widget would become enabled if \a ancestor is + enabled; otherwise returns false. + + + + This is the case if neither the widget itself nor every parent up + to but excluding \a ancestor has been explicitly disabled. + + isEnabledTo(0) is equivalent to isEnabled(). + + \sa setEnabled() enabled +*/ + +bool QWidget::isEnabledTo(QWidget* ancestor) const +{ + const QWidget * w = this; + while (!w->testAttribute(Qt::WA_ForceDisabled) + && !w->isWindow() + && w->parentWidget() + && w->parentWidget() != ancestor) + w = w->parentWidget(); + return !w->testAttribute(Qt::WA_ForceDisabled); +} + +#ifndef QT_NO_ACTION +/*! + Appends the action \a action to this widget's list of actions. + + All QWidgets have a list of \l{QAction}s, however they can be + represented graphically in many different ways. The default use of + the QAction list (as returned by actions()) is to create a context + QMenu. + + A QWidget should only have one of each action and adding an action + it already has will not cause the same action to be in the widget twice. + + The ownership of \a action is not transferred to this QWidget. + + \sa removeAction(), insertAction(), actions(), QMenu +*/ +void QWidget::addAction(QAction *action) +{ + insertAction(0, action); +} + +/*! + Appends the actions \a actions to this widget's list of actions. + + \sa removeAction(), QMenu, addAction() +*/ +void QWidget::addActions(QList<QAction*> actions) +{ + for(int i = 0; i < actions.count(); i++) + insertAction(0, actions.at(i)); +} + +/*! + Inserts the action \a action to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QWidget should only have one of each action. + + \sa removeAction(), addAction(), QMenu, contextMenuPolicy, actions() +*/ +void QWidget::insertAction(QAction *before, QAction *action) +{ + if(!action) { + qWarning("QWidget::insertAction: Attempt to insert null action"); + return; + } + + Q_D(QWidget); + if(d->actions.contains(action)) + removeAction(action); + + int pos = d->actions.indexOf(before); + if (pos < 0) { + before = 0; + pos = d->actions.size(); + } + d->actions.insert(pos, action); + + QActionPrivate *apriv = action->d_func(); + apriv->widgets.append(this); + + QActionEvent e(QEvent::ActionAdded, action, before); + QApplication::sendEvent(this, &e); +} + +/*! + Inserts the actions \a actions to this widget's list of actions, + before the action \a before. It appends the action if \a before is 0 or + \a before is not a valid action for this widget. + + A QWidget can have at most one of each action. + + \sa removeAction(), QMenu, insertAction(), contextMenuPolicy +*/ +void QWidget::insertActions(QAction *before, QList<QAction*> actions) +{ + for(int i = 0; i < actions.count(); ++i) + insertAction(before, actions.at(i)); +} + +/*! + Removes the action \a action from this widget's list of actions. + \sa insertAction(), actions(), insertAction() +*/ +void QWidget::removeAction(QAction *action) +{ + if (!action) + return; + + Q_D(QWidget); + + QActionPrivate *apriv = action->d_func(); + apriv->widgets.removeAll(this); + + if (d->actions.removeAll(action)) { + QActionEvent e(QEvent::ActionRemoved, action); + QApplication::sendEvent(this, &e); + } +} + +/*! + Returns the (possibly empty) list of this widget's actions. + + \sa contextMenuPolicy, insertAction(), removeAction() +*/ +QList<QAction*> QWidget::actions() const +{ + Q_D(const QWidget); + return d->actions; +} +#endif // QT_NO_ACTION + +/*! + \fn bool QWidget::isEnabledToTLW() const + \obsolete + + This function is deprecated. It is equivalent to isEnabled() +*/ + +/*! + \property QWidget::enabled + \brief whether the widget is enabled + + An enabled widget handles keyboard and mouse events; a disabled + widget does not. + + Some widgets display themselves differently when they are + disabled. For example a button might draw its label grayed out. If + your widget needs to know when it becomes enabled or disabled, you + can use the changeEvent() with type QEvent::EnabledChange. + + Disabling a widget implicitly disables all its children. Enabling + respectively enables all child widgets unless they have been + explicitly disabled. + + By default, this property is true. + + \sa isEnabledTo(), QKeyEvent, QMouseEvent, changeEvent() +*/ +void QWidget::setEnabled(bool enable) +{ + Q_D(QWidget); + setAttribute(Qt::WA_ForceDisabled, !enable); + d->setEnabled_helper(enable); +} + +void QWidgetPrivate::setEnabled_helper(bool enable) +{ + Q_Q(QWidget); + + if (enable && !q->isWindow() && q->parentWidget() && !q->parentWidget()->isEnabled()) + return; // nothing we can do + + if (enable != q->testAttribute(Qt::WA_Disabled)) + return; // nothing to do + + q->setAttribute(Qt::WA_Disabled, !enable); + updateSystemBackground(); + + if (!enable && q->window()->focusWidget() == q) { + bool parentIsEnabled = (!q->parentWidget() || q->parentWidget()->isEnabled()); + if (!parentIsEnabled || !q->focusNextChild()) + q->clearFocus(); + } + + Qt::WidgetAttribute attribute = enable ? Qt::WA_ForceDisabled : Qt::WA_Disabled; + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (w && !w->testAttribute(attribute)) + w->d_func()->setEnabled_helper(enable); + } +#if defined(Q_WS_X11) + if (q->testAttribute(Qt::WA_SetCursor) || q->isWindow()) { + // enforce the windows behavior of clearing the cursor on + // disabled widgets + qt_x11_enforce_cursor(q); + } +#endif +#if defined(Q_WS_MAC) + setEnabled_helper_sys(enable); +#endif +#ifndef QT_NO_IM + if (q->testAttribute(Qt::WA_InputMethodEnabled) && q->hasFocus()) { + QWidget *focusWidget = effectiveFocusWidget(); + QInputContext *qic = focusWidget->d_func()->inputContext(); + if (enable) { + if (focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) + qic->setFocusWidget(focusWidget); + } else { + qic->reset(); + qic->setFocusWidget(0); + } + } +#endif //QT_NO_IM + QEvent e(QEvent::EnabledChange); + QApplication::sendEvent(q, &e); +#ifdef QT3_SUPPORT + q->enabledChange(!enable); // compatibility +#endif +} + +/*! + \property QWidget::acceptDrops + \brief whether drop events are enabled for this widget + + Setting this property to true announces to the system that this + widget \e may be able to accept drop events. + + If the widget is the desktop (windowType() == Qt::Desktop), this may + fail if another application is using the desktop; you can call + acceptDrops() to test if this occurs. + + \warning Do not modify this property in a drag and drop event handler. + + By default, this property is false. + + \sa {Drag and Drop} +*/ +bool QWidget::acceptDrops() const +{ + return testAttribute(Qt::WA_AcceptDrops); +} + +void QWidget::setAcceptDrops(bool on) +{ + setAttribute(Qt::WA_AcceptDrops, on); + +} + +/*! + \fn void QWidget::enabledChange(bool) + + \internal + \obsolete +*/ + +/*! + \fn void QWidget::paletteChange(const QPalette &) + + \internal + \obsolete +*/ + +/*! + \fn void QWidget::fontChange(const QFont &) + + \internal + \obsolete +*/ + +/*! + \fn void QWidget::windowActivationChange(bool) + + \internal + \obsolete +*/ + +/*! + \fn void QWidget::languageChange() + + \obsolete +*/ + +/*! + \fn void QWidget::styleChange(QStyle& style) + + \internal + \obsolete +*/ + +/*! + Disables widget input events if \a disable is true; otherwise + enables input events. + + See the \l enabled documentation for more information. + + \sa isEnabledTo(), QKeyEvent, QMouseEvent, changeEvent() +*/ +void QWidget::setDisabled(bool disable) +{ + setEnabled(!disable); +} + +/*! + \property QWidget::frameGeometry + \brief geometry of the widget relative to its parent including any + window frame + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \sa geometry() x() y() pos() +*/ +QRect QWidget::frameGeometry() const +{ + Q_D(const QWidget); + if (isWindow() && ! (windowType() == Qt::Popup)) { + QRect fs = d->frameStrut(); + return QRect(data->crect.x() - fs.left(), + data->crect.y() - fs.top(), + data->crect.width() + fs.left() + fs.right(), + data->crect.height() + fs.top() + fs.bottom()); + } + return data->crect; +} + +/*! + \property QWidget::x + + \brief the x coordinate of the widget relative to its parent including + any window frame + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + By default, this property has a value of 0. + + \sa frameGeometry, y, pos +*/ +int QWidget::x() const +{ + Q_D(const QWidget); + if (isWindow() && ! (windowType() == Qt::Popup)) + return data->crect.x() - d->frameStrut().left(); + return data->crect.x(); +} + +/*! + \property QWidget::y + \brief the y coordinate of the widget relative to its parent and + including any window frame + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + By default, this property has a value of 0. + + \sa frameGeometry, x, pos +*/ +int QWidget::y() const +{ + Q_D(const QWidget); + if (isWindow() && ! (windowType() == Qt::Popup)) + return data->crect.y() - d->frameStrut().top(); + return data->crect.y(); +} + +/*! + \property QWidget::pos + \brief the position of the widget within its parent widget + + If the widget is a window, the position is that of the widget on + the desktop, including its frame. + + When changing the position, the widget, if visible, receives a + move event (moveEvent()) immediately. If the widget is not + currently visible, it is guaranteed to receive an event before it + is shown. + + By default, this property contains a position that refers to the + origin. + + \warning Calling move() or setGeometry() inside moveEvent() can + lead to infinite recursion. + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + \sa frameGeometry, size x(), y() +*/ +QPoint QWidget::pos() const +{ + Q_D(const QWidget); + if (isWindow() && ! (windowType() == Qt::Popup)) { + QRect fs = d->frameStrut(); + return QPoint(data->crect.x() - fs.left(), data->crect.y() - fs.top()); + } + return data->crect.topLeft(); +} + +/*! + \property QWidget::geometry + \brief the geometry of the widget relative to its parent and + excluding the window frame + + When changing the geometry, the widget, if visible, receives a + move event (moveEvent()) and/or a resize event (resizeEvent()) + immediately. If the widget is not currently visible, it is + guaranteed to receive appropriate events before it is shown. + + The size component is adjusted if it lies outside the range + defined by minimumSize() and maximumSize(). + + \warning Calling setGeometry() inside resizeEvent() or moveEvent() + can lead to infinite recursion. + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \sa frameGeometry(), rect(), move(), resize(), moveEvent(), + resizeEvent(), minimumSize(), maximumSize() +*/ + +/*! + \property QWidget::normalGeometry + + \brief the geometry of the widget as it will appear when shown as + a normal (not maximized or full screen) top-level widget + + For child widgets this property always holds an empty rectangle. + + By default, this property contains an empty rectangle. + + \sa QWidget::windowState(), QWidget::geometry +*/ + +/*! + \property QWidget::size + \brief the size of the widget excluding any window frame + + If the widget is visible when it is being resized, it receives a resize event + (resizeEvent()) immediately. If the widget is not currently + visible, it is guaranteed to receive an event before it is shown. + + The size is adjusted if it lies outside the range defined by + minimumSize() and maximumSize(). + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \warning Calling resize() or setGeometry() inside resizeEvent() can + lead to infinite recursion. + + \note Setting the size to \c{QSize(0, 0)} will cause the widget to not + appear on screen. This also applies to windows. + + \sa pos, geometry, minimumSize, maximumSize, resizeEvent(), adjustSize() +*/ + +/*! + \property QWidget::width + \brief the width of the widget excluding any window frame + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + \note Do not use this function to find the width of a screen on + a \l{QDesktopWidget}{multiple screen desktop}. Read + \l{QDesktopWidget#Screen Geometry}{this note} for details. + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \sa geometry, height, size +*/ + +/*! + \property QWidget::height + \brief the height of the widget excluding any window frame + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + \note Do not use this function to find the height of a screen + on a \l{QDesktopWidget}{multiple screen desktop}. Read + \l{QDesktopWidget#Screen Geometry}{this note} for details. + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \sa geometry, width, size +*/ + +/*! + \property QWidget::rect + \brief the internal geometry of the widget excluding any window + frame + + The rect property equals QRect(0, 0, width(), height()). + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + By default, this property contains a value that depends on the user's + platform and screen geometry. + + \sa size +*/ + + +QRect QWidget::normalGeometry() const +{ + Q_D(const QWidget); + if (!d->extra || !d->extra->topextra) + return QRect(); + + if (!isMaximized() && !isFullScreen()) + return geometry(); + + return d->topData()->normalGeometry; +} + + +/*! + \property QWidget::childrenRect + \brief the bounding rectangle of the widget's children + + Hidden children are excluded. + + By default, for a widget with no children, this property contains a + rectangle with zero width and height located at the origin. + + \sa childrenRegion() geometry() +*/ + +QRect QWidget::childrenRect() const +{ + Q_D(const QWidget); + QRect r(0, 0, 0, 0); + for (int i = 0; i < d->children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(d->children.at(i)); + if (w && !w->isWindow() && !w->isHidden()) + r |= w->geometry(); + } + return r; +} + +/*! + \property QWidget::childrenRegion + \brief the combined region occupied by the widget's children + + Hidden children are excluded. + + By default, for a widget with no children, this property contains an + empty region. + + \sa childrenRect() geometry() mask() +*/ + +QRegion QWidget::childrenRegion() const +{ + Q_D(const QWidget); + QRegion r; + for (int i = 0; i < d->children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(d->children.at(i)); + if (w && !w->isWindow() && !w->isHidden()) { + QRegion mask = w->mask(); + if (mask.isEmpty()) + r |= w->geometry(); + else + r |= mask.translated(w->pos()); + } + } + return r; +} + + +/*! + \property QWidget::minimumSize + \brief the widget's minimum size + + The widget cannot be resized to a smaller size than the minimum + widget size. The widget's size is forced to the minimum size if + the current size is smaller. + + The minimum size set by this function will override the minimum size + defined by QLayout. In order to unset the minimum size, use a + value of \c{QSize(0, 0)}. + + By default, this property contains a size with zero width and height. + + \sa minimumWidth, minimumHeight, maximumSize, sizeIncrement +*/ + +QSize QWidget::minimumSize() const +{ + Q_D(const QWidget); + return d->extra ? QSize(d->extra->minw, d->extra->minh) : QSize(0, 0); +} + +/*! + \property QWidget::maximumSize + \brief the widget's maximum size in pixels + + The widget cannot be resized to a larger size than the maximum + widget size. + + By default, this property contains a size in which both width and height + have values of 16777215. + + \note The definition of the \c QWIDGETSIZE_MAX macro limits the maximum size + of widgets. + + \sa maximumWidth, maximumHeight, minimumSize, sizeIncrement +*/ + +QSize QWidget::maximumSize() const +{ + Q_D(const QWidget); + return d->extra ? QSize(d->extra->maxw, d->extra->maxh) + : QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); +} + + +/*! + \property QWidget::minimumWidth + \brief the widget's minimum width in pixels + + This property corresponds to the width held by the \l minimumSize property. + + By default, this property has a value of 0. + + \sa minimumSize, minimumHeight +*/ + +/*! + \property QWidget::minimumHeight + \brief the widget's minimum height in pixels + + This property corresponds to the height held by the \l minimumSize property. + + By default, this property has a value of 0. + + \sa minimumSize, minimumWidth +*/ + +/*! + \property QWidget::maximumWidth + \brief the widget's maximum width in pixels + + This property corresponds to the width held by the \l maximumSize property. + + By default, this property contains a value of 16777215. + + \note The definition of the \c QWIDGETSIZE_MAX macro limits the maximum size + of widgets. + + \sa maximumSize, maximumHeight +*/ + +/*! + \property QWidget::maximumHeight + \brief the widget's maximum height in pixels + + This property corresponds to the height held by the \l maximumSize property. + + By default, this property contains a value of 16777215. + + \note The definition of the \c QWIDGETSIZE_MAX macro limits the maximum size + of widgets. + + \sa maximumSize, maximumWidth +*/ + +/*! + \property QWidget::sizeIncrement + \brief the size increment of the widget + + When the user resizes the window, the size will move in steps of + sizeIncrement().width() pixels horizontally and + sizeIncrement.height() pixels vertically, with baseSize() as the + basis. Preferred widget sizes are for non-negative integers \e i + and \e j: + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 2 + + Note that while you can set the size increment for all widgets, it + only affects windows. + + By default, this property contains a size with zero width and height. + + \warning The size increment has no effect under Windows, and may + be disregarded by the window manager on X11. + + \sa size, minimumSize, maximumSize +*/ +QSize QWidget::sizeIncrement() const +{ + Q_D(const QWidget); + return (d->extra && d->extra->topextra) + ? QSize(d->extra->topextra->incw, d->extra->topextra->inch) + : QSize(0, 0); +} + +/*! + \property QWidget::baseSize + \brief the base size of the widget + + The base size is used to calculate a proper widget size if the + widget defines sizeIncrement(). + + By default, for a newly-created widget, this property contains a size with + zero width and height. + + \sa setSizeIncrement() +*/ + +QSize QWidget::baseSize() const +{ + Q_D(const QWidget); + return (d->extra != 0 && d->extra->topextra != 0) + ? QSize(d->extra->topextra->basew, d->extra->topextra->baseh) + : QSize(0, 0); +} + +bool QWidgetPrivate::setMinimumSize_helper(int &minw, int &minh) +{ + Q_Q(QWidget); + +#ifdef Q_WS_QWS + if (q->isWindow()) { + const QRect maxWindowRect = QApplication::desktop()->availableGeometry(QApplication::desktop()->screenNumber(q)); + if (!maxWindowRect.isEmpty()) { + // ### This is really just a work-around. Layout shouldn't be + // asking for minimum sizes bigger than the screen. + if (minw > maxWindowRect.width()) + minw = maxWindowRect.width(); + if (minh > maxWindowRect.height()) + minh = maxWindowRect.height(); + } + } +#endif + int mw = minw, mh = minh; + if (mw == QWIDGETSIZE_MAX) + mw = 0; + if (mh == QWIDGETSIZE_MAX) + mh = 0; + if (minw > QWIDGETSIZE_MAX || minh > QWIDGETSIZE_MAX) { + qWarning("QWidget::setMinimumSize: (%s/%s) " + "The largest allowed size is (%d,%d)", + q->objectName().toLocal8Bit().data(), q->metaObject()->className(), QWIDGETSIZE_MAX, + QWIDGETSIZE_MAX); + minw = mw = qMin<int>(minw, QWIDGETSIZE_MAX); + minh = mh = qMin<int>(minh, QWIDGETSIZE_MAX); + } + if (minw < 0 || minh < 0) { + qWarning("QWidget::setMinimumSize: (%s/%s) Negative sizes (%d,%d) " + "are not possible", + q->objectName().toLocal8Bit().data(), q->metaObject()->className(), minw, minh); + minw = mw = qMax(minw, 0); + minh = mh = qMax(minh, 0); + } + createExtra(); + if (extra->minw == mw && extra->minh == mh) + return false; + extra->minw = mw; + extra->minh = mh; + extra->explicitMinSize = (mw ? Qt::Horizontal : 0) | (mh ? Qt::Vertical : 0); + return true; +} + +/*! + \overload + + This function corresponds to setMinimumSize(QSize(minw, minh)). + Sets the minimum width to \a minw and the minimum height to \a + minh. +*/ + +void QWidget::setMinimumSize(int minw, int minh) +{ + Q_D(QWidget); + if (!d->setMinimumSize_helper(minw, minh)) + return; + + if (isWindow()) + d->setConstraints_sys(); + if (minw > width() || minh > height()) { + bool resized = testAttribute(Qt::WA_Resized); + bool maximized = isMaximized(); + resize(qMax(minw,width()), qMax(minh,height())); + setAttribute(Qt::WA_Resized, resized); //not a user resize + if (maximized) + data->window_state = data->window_state | Qt::WindowMaximized; + } +#ifndef QT_NO_GRAPHICSVIEW + if (d->extra) { + if (d->extra->proxyWidget) + d->extra->proxyWidget->setMinimumSize(minw, minh); + } +#endif + d->updateGeometry_helper(d->extra->minw == d->extra->maxw && d->extra->minh == d->extra->maxh); +} + +bool QWidgetPrivate::setMaximumSize_helper(int &maxw, int &maxh) +{ + Q_Q(QWidget); + if (maxw > QWIDGETSIZE_MAX || maxh > QWIDGETSIZE_MAX) { + qWarning("QWidget::setMaximumSize: (%s/%s) " + "The largest allowed size is (%d,%d)", + q->objectName().toLocal8Bit().data(), q->metaObject()->className(), QWIDGETSIZE_MAX, + QWIDGETSIZE_MAX); + maxw = qMin<int>(maxw, QWIDGETSIZE_MAX); + maxh = qMin<int>(maxh, QWIDGETSIZE_MAX); + } + if (maxw < 0 || maxh < 0) { + qWarning("QWidget::setMaximumSize: (%s/%s) Negative sizes (%d,%d) " + "are not possible", + q->objectName().toLocal8Bit().data(), q->metaObject()->className(), maxw, maxh); + maxw = qMax(maxw, 0); + maxh = qMax(maxh, 0); + } + createExtra(); + if (extra->maxw == maxw && extra->maxh == maxh) + return false; + extra->maxw = maxw; + extra->maxh = maxh; + extra->explicitMaxSize = (maxw != QWIDGETSIZE_MAX ? Qt::Horizontal : 0) | + (maxh != QWIDGETSIZE_MAX ? Qt::Vertical : 0); + return true; +} + +/*! + \overload + + This function corresponds to setMaximumSize(QSize(\a maxw, \a + maxh)). Sets the maximum width to \a maxw and the maximum height + to \a maxh. +*/ +void QWidget::setMaximumSize(int maxw, int maxh) +{ + Q_D(QWidget); + if (!d->setMaximumSize_helper(maxw, maxh)) + return; + + if (isWindow()) + d->setConstraints_sys(); + if (maxw < width() || maxh < height()) { + bool resized = testAttribute(Qt::WA_Resized); + resize(qMin(maxw,width()), qMin(maxh,height())); + setAttribute(Qt::WA_Resized, resized); //not a user resize + } + +#ifndef QT_NO_GRAPHICSVIEW + if (d->extra) { + if (d->extra->proxyWidget) + d->extra->proxyWidget->setMaximumSize(maxw, maxh); + } +#endif + + d->updateGeometry_helper(d->extra->minw == d->extra->maxw && d->extra->minh == d->extra->maxh); +} + +/*! + \overload + + Sets the x (width) size increment to \a w and the y (height) size + increment to \a h. +*/ +void QWidget::setSizeIncrement(int w, int h) +{ + Q_D(QWidget); + d->createTLExtra(); + QTLWExtra* x = d->topData(); + if (x->incw == w && x->inch == h) + return; + x->incw = w; + x->inch = h; + if (isWindow()) + d->setConstraints_sys(); +} + +/*! + \overload + + This corresponds to setBaseSize(QSize(\a basew, \a baseh)). Sets + the widgets base size to width \a basew and height \a baseh. +*/ +void QWidget::setBaseSize(int basew, int baseh) +{ + Q_D(QWidget); + d->createTLExtra(); + QTLWExtra* x = d->topData(); + if (x->basew == basew && x->baseh == baseh) + return; + x->basew = basew; + x->baseh = baseh; + if (isWindow()) + d->setConstraints_sys(); +} + +/*! + Sets both the minimum and maximum sizes of the widget to \a s, + thereby preventing it from ever growing or shrinking. + + This will override the default size constraints set by QLayout. + + To remove constraints, set the size to QWIDGETSIZE_MAX. + + Alternatively, if you want the widget to have a + fixed size based on its contents, you can call + QLayout::setSizeConstraint(QLayout::SetFixedSize); + + \sa maximumSize, minimumSize +*/ + +void QWidget::setFixedSize(const QSize & s) +{ + setFixedSize(s.width(), s.height()); +} + + +/*! + \fn void QWidget::setFixedSize(int w, int h) + \overload + + Sets the width of the widget to \a w and the height to \a h. +*/ + +void QWidget::setFixedSize(int w, int h) +{ + Q_D(QWidget); +#ifdef Q_WS_QWS + // temporary fix for 4.3.x. + // Should move the embedded spesific contraints in setMinimumSize_helper into QLayout + int tmpW = w; + int tmpH = h; + bool minSizeSet = d->setMinimumSize_helper(tmpW, tmpH); +#else + bool minSizeSet = d->setMinimumSize_helper(w, h); +#endif + bool maxSizeSet = d->setMaximumSize_helper(w, h); + if (!minSizeSet && !maxSizeSet) + return; + + if (isWindow()) + d->setConstraints_sys(); + else + d->updateGeometry_helper(true); + + if (w != QWIDGETSIZE_MAX || h != QWIDGETSIZE_MAX) + resize(w, h); +} + +void QWidget::setMinimumWidth(int w) +{ + Q_D(QWidget); + d->createExtra(); + uint expl = d->extra->explicitMinSize | (w ? Qt::Horizontal : 0); + setMinimumSize(w, minimumSize().height()); + d->extra->explicitMinSize = expl; +} + +void QWidget::setMinimumHeight(int h) +{ + Q_D(QWidget); + d->createExtra(); + uint expl = d->extra->explicitMinSize | (h ? Qt::Vertical : 0); + setMinimumSize(minimumSize().width(), h); + d->extra->explicitMinSize = expl; +} + +void QWidget::setMaximumWidth(int w) +{ + Q_D(QWidget); + d->createExtra(); + uint expl = d->extra->explicitMaxSize | (w == QWIDGETSIZE_MAX ? 0 : Qt::Horizontal); + setMaximumSize(w, maximumSize().height()); + d->extra->explicitMaxSize = expl; +} + +void QWidget::setMaximumHeight(int h) +{ + Q_D(QWidget); + d->createExtra(); + uint expl = d->extra->explicitMaxSize | (h == QWIDGETSIZE_MAX ? 0 : Qt::Vertical); + setMaximumSize(maximumSize().width(), h); + d->extra->explicitMaxSize = expl; +} + +/*! + Sets both the minimum and maximum width of the widget to \a w + without changing the heights. Provided for convenience. + + \sa sizeHint() minimumSize() maximumSize() setFixedSize() +*/ + +void QWidget::setFixedWidth(int w) +{ + Q_D(QWidget); + d->createExtra(); + uint explMin = d->extra->explicitMinSize | Qt::Horizontal; + uint explMax = d->extra->explicitMaxSize | Qt::Horizontal; + setMinimumSize(w, minimumSize().height()); + setMaximumSize(w, maximumSize().height()); + d->extra->explicitMinSize = explMin; + d->extra->explicitMaxSize = explMax; +} + + +/*! + Sets both the minimum and maximum heights of the widget to \a h + without changing the widths. Provided for convenience. + + \sa sizeHint() minimumSize() maximumSize() setFixedSize() +*/ + +void QWidget::setFixedHeight(int h) +{ + Q_D(QWidget); + d->createExtra(); + uint explMin = d->extra->explicitMinSize | Qt::Vertical; + uint explMax = d->extra->explicitMaxSize | Qt::Vertical; + setMinimumSize(minimumSize().width(), h); + setMaximumSize(maximumSize().width(), h); + d->extra->explicitMinSize = explMin; + d->extra->explicitMaxSize = explMax; +} + + +/*! + Translates the widget coordinate \a pos to the coordinate system + of \a parent. The \a parent must not be 0 and must be a parent + of the calling widget. + + \sa mapFrom() mapToParent() mapToGlobal() underMouse() +*/ + +QPoint QWidget::mapTo(QWidget * parent, const QPoint & pos) const +{ + QPoint p = pos; + if (parent) { + const QWidget * w = this; + while (w != parent) { + Q_ASSERT_X(w, "QWidget::mapTo(QWidget *parent, const QPoint &pos)", + "parent must be in parent hierarchy"); + p = w->mapToParent(p); + w = w->parentWidget(); + } + } + return p; +} + + +/*! + Translates the widget coordinate \a pos from the coordinate system + of \a parent to this widget's coordinate system. The \a parent + must not be 0 and must be a parent of the calling widget. + + \sa mapTo() mapFromParent() mapFromGlobal() underMouse() +*/ + +QPoint QWidget::mapFrom(QWidget * parent, const QPoint & pos) const +{ + QPoint p(pos); + if (parent) { + const QWidget * w = this; + while (w != parent) { + Q_ASSERT_X(w, "QWidget::mapFrom(QWidget *parent, const QPoint &pos)", + "parent must be in parent hierarchy"); + + p = w->mapFromParent(p); + w = w->parentWidget(); + } + } + return p; +} + + +/*! + Translates the widget coordinate \a pos to a coordinate in the + parent widget. + + Same as mapToGlobal() if the widget has no parent. + + \sa mapFromParent() mapTo() mapToGlobal() underMouse() +*/ + +QPoint QWidget::mapToParent(const QPoint &pos) const +{ + return pos + data->crect.topLeft(); +} + +/*! + Translates the parent widget coordinate \a pos to widget + coordinates. + + Same as mapFromGlobal() if the widget has no parent. + + \sa mapToParent() mapFrom() mapFromGlobal() underMouse() +*/ + +QPoint QWidget::mapFromParent(const QPoint &pos) const +{ + return pos - data->crect.topLeft(); +} + + +/*! + Returns the window for this widget, i.e. the next ancestor widget + that has (or could have) a window-system frame. + + If the widget is a window, the widget itself is returned. + + Typical usage is changing the window title: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 3 + + \sa isWindow() +*/ + +QWidget *QWidget::window() const +{ + QWidget *w = (QWidget *)this; + QWidget *p = w->parentWidget(); + while (!w->isWindow() && p) { + w = p; + p = p->parentWidget(); + } + return w; +} + +/*! + \since 4.4 + + Returns the native parent for this widget, i.e. the next ancestor widget + that has a system identifier, or 0 if it does not have any native parent. + + \sa effectiveWinId() +*/ +QWidget *QWidget::nativeParentWidget() const +{ + QWidget *parent = parentWidget(); + while (parent && !parent->internalWinId()) + parent = parent->parentWidget(); + return parent; +} + +/*! \fn QWidget *QWidget::topLevelWidget() const + \obsolete + + Use window() instead. +*/ + +#ifdef QT3_SUPPORT +/*! + Returns the color role used for painting the widget's background. + + Use QPalette(backgroundRole(()) instead. +*/ +Qt::BackgroundMode QWidget::backgroundMode() const +{ + if (testAttribute(Qt::WA_NoSystemBackground)) + return Qt::NoBackground; + switch(backgroundRole()) { + case QPalette::WindowText: + return Qt::PaletteForeground; + case QPalette::Button: + return Qt::PaletteButton; + case QPalette::Light: + return Qt::PaletteLight; + case QPalette::Midlight: + return Qt::PaletteMidlight; + case QPalette::Dark: + return Qt::PaletteDark; + case QPalette::Mid: + return Qt::PaletteMid; + case QPalette::Text: + return Qt::PaletteText; + case QPalette::BrightText: + return Qt::PaletteBrightText; + case QPalette::Base: + return Qt::PaletteBase; + case QPalette::Window: + return Qt::PaletteBackground; + case QPalette::Shadow: + return Qt::PaletteShadow; + case QPalette::Highlight: + return Qt::PaletteHighlight; + case QPalette::HighlightedText: + return Qt::PaletteHighlightedText; + case QPalette::ButtonText: + return Qt::PaletteButtonText; + case QPalette::Link: + return Qt::PaletteLink; + case QPalette::LinkVisited: + return Qt::PaletteLinkVisited; + default: + break; + } + return Qt::NoBackground; +} + +/*! + \fn void QWidget::setBackgroundMode(Qt::BackgroundMode + widgetBackground, Qt::BackgroundMode paletteBackground) + + Sets the color role used for painting the widget's background to + background mode \a widgetBackground. The \a paletteBackground mode + parameter is ignored. +*/ +void QWidget::setBackgroundMode(Qt::BackgroundMode m, Qt::BackgroundMode) +{ + Q_D(QWidget); + if(m == Qt::NoBackground) { + setAttribute(Qt::WA_NoSystemBackground, true); + return; + } + setAttribute(Qt::WA_NoSystemBackground, false); + d->fg_role = QPalette::NoRole; + QPalette::ColorRole role = d->bg_role; + switch(m) { + case Qt::FixedColor: + case Qt::FixedPixmap: + break; + case Qt::PaletteForeground: + role = QPalette::WindowText; + break; + case Qt::PaletteButton: + role = QPalette::Button; + break; + case Qt::PaletteLight: + role = QPalette::Light; + break; + case Qt::PaletteMidlight: + role = QPalette::Midlight; + break; + case Qt::PaletteDark: + role = QPalette::Dark; + break; + case Qt::PaletteMid: + role = QPalette::Mid; + break; + case Qt::PaletteText: + role = QPalette::Text; + break; + case Qt::PaletteBrightText: + role = QPalette::BrightText; + break; + case Qt::PaletteBase: + role = QPalette::Base; + break; + case Qt::PaletteBackground: + role = QPalette::Window; + break; + case Qt::PaletteShadow: + role = QPalette::Shadow; + break; + case Qt::PaletteHighlight: + role = QPalette::Highlight; + break; + case Qt::PaletteHighlightedText: + role = QPalette::HighlightedText; + break; + case Qt::PaletteButtonText: + role = QPalette::ButtonText; + break; + case Qt::PaletteLink: + role = QPalette::Link; + break; + case Qt::PaletteLinkVisited: + role = QPalette::LinkVisited; + break; + case Qt::X11ParentRelative: + d->fg_role = role = QPalette::NoRole; + default: + break; + } + setBackgroundRole(role); +} + +/*! + The widget mapper is no longer part of the public API. +*/ +QT3_SUPPORT QWidgetMapper *QWidget::wmapper() { return QWidgetPrivate::mapper; } + +#endif + + +/*! + Returns the background role of the widget. + + The background role defines the brush from the widget's \l palette that + is used to render the background. + + If no explicit background role is set, the widget inherts its parent + widget's background role. + + \sa setBackgroundRole(), foregroundRole() + */ +QPalette::ColorRole QWidget::backgroundRole() const +{ + + const QWidget *w = this; + do { + QPalette::ColorRole role = w->d_func()->bg_role; + if (role != QPalette::NoRole) + return role; + if (w->isWindow() || w->windowType() == Qt::SubWindow) + break; + w = w->parentWidget(); + } while (w); + return QPalette::Window; +} + +/*! + Sets the background role of the widget to \a role. + + The background role defines the brush from the widget's \l palette that + is used to render the background. + + If \a role is QPalette::NoRole, then the widget inherits its + parent's background role. + + Note that styles are free to choose any color from the palette. + You can modify the palette or set a style sheet if you don't + achieve the result you want with setBackgroundRole(). + + \sa backgroundRole(), foregroundRole() + */ + +void QWidget::setBackgroundRole(QPalette::ColorRole role) +{ + Q_D(QWidget); + d->bg_role = role; + d->updateSystemBackground(); + d->propagatePaletteChange(); + d->updateIsOpaque(); +} + +/*! + Returns the foreground role. + + The foreground role defines the color from the widget's \l palette that + is used to draw the foreground. + + If no explicit foreground role is set, the function returns a role + that contrasts with the background role. + + \sa setForegroundRole(), backgroundRole() + */ +QPalette::ColorRole QWidget::foregroundRole() const +{ + Q_D(const QWidget); + QPalette::ColorRole rl = QPalette::ColorRole(d->fg_role); + if (rl != QPalette::NoRole) + return rl; + QPalette::ColorRole role = QPalette::WindowText; + switch (backgroundRole()) { + case QPalette::Button: + role = QPalette::ButtonText; + break; + case QPalette::Base: + role = QPalette::Text; + break; + case QPalette::Dark: + case QPalette::Shadow: + role = QPalette::Light; + break; + case QPalette::Highlight: + role = QPalette::HighlightedText; + break; + case QPalette::ToolTipBase: + role = QPalette::ToolTipText; + break; + default: + ; + } + return role; +} + +/*! + Sets the foreground role of the widget to \a role. + + The foreground role defines the color from the widget's \l palette that + is used to draw the foreground. + + If \a role is QPalette::NoRole, the widget uses a foreground role + that contrasts with the background role. + + Note that styles are free to choose any color from the palette. + You can modify the palette or set a style sheet if you don't + achieve the result you want with setForegroundRole(). + + \sa foregroundRole(), backgroundRole() + */ +void QWidget::setForegroundRole(QPalette::ColorRole role) +{ + Q_D(QWidget); + d->fg_role = role; + d->updateSystemBackground(); + d->propagatePaletteChange(); +} + +/*! + \property QWidget::palette + \brief the widget's palette + + This property describes the widget's palette. The palette is used by the + widget's style when rendering standard components, and is available as a + means to ensure that custom widgets can maintain consistency with the + native platform's look and feel. It's common that different platforms, or + different styles, have different palettes. + + When you assign a new palette to a widget, the color roles from this + palette are combined with the widget's default palette to form the + widget's final palette. The palette entry for the widget's background role + is used to fill the widget's background (see QWidget::autoFillBackground), + and the foreground role initializes QPainter's pen. + + The default depends on the system environment. QApplication maintains a + system/theme palette which serves as a default for all widgets. There may + also be special palette defaults for certain types of widgets (e.g., on + Windows XP and Vista, all classes that derive from QMenuBar have a special + default palette). You can also define default palettes for widgets + yourself by passing a custom palette and the name of a widget to + QApplication::setPalette(). Finally, the style always has the option of + polishing the palette as it's assigned (see QStyle::polish()). + + QWidget propagates explicit palette roles from parent to child. If you + assign a brush or color to a specific role on a palette and assign that + palette to a widget, that role will propagate to all the widget's + children, overriding any system defaults for that role. Note that palettes + by default don't propagate to windows (see isWindow()) unless the + Qt::WA_WindowPropagation attribute is enabled. + + QWidget's palette propagation is similar to its font propagation. + + The current style, which is used to render the content of all standard Qt + widgets, is free to choose colors and brushes from the widget palette, or + in some cases, to ignore the palette (partially, or completely). In + particular, certain styles like GTK style, Mac style, Windows XP, and + Vista style, depend on third party APIs to render the content of widgets, + and these styles typically do not follow the palette. Because of this, + assigning roles to a widget's palette is not guaranteed to change the + appearance of the widget. Instead, you may choose to apply a \l + styleSheet. You can refer to our Knowledge Base article + \l{http://qt.nokia.com/developer/knowledgebase/22}{here} for more + information. + + \warning Do not use this function in conjunction with \l{Qt Style Sheets}. + When using style sheets, the palette of a widget can be customized using + the "color", "background-color", "selection-color", + "selection-background-color" and "alternate-background-color". + + \sa QApplication::palette(), QWidget::font() +*/ +const QPalette &QWidget::palette() const +{ + if (!isEnabled()) { + data->pal.setCurrentColorGroup(QPalette::Disabled); + } else if ((!isVisible() || isActiveWindow()) +#if defined(Q_OS_WIN) && !defined(Q_WS_WINCE) + && !QApplicationPrivate::isBlockedByModal(const_cast<QWidget *>(this)) +#endif + ) { + data->pal.setCurrentColorGroup(QPalette::Active); + } else { +#ifdef Q_WS_MAC + extern bool qt_mac_can_clickThrough(const QWidget *); //qwidget_mac.cpp + if (qt_mac_can_clickThrough(this)) + data->pal.setCurrentColorGroup(QPalette::Active); + else +#endif + data->pal.setCurrentColorGroup(QPalette::Inactive); + } + return data->pal; +} + +void QWidget::setPalette(const QPalette &palette) +{ + Q_D(QWidget); + setAttribute(Qt::WA_SetPalette, palette.resolve() != 0); + + // Determine which palette is inherited from this widget's ancestors and + // QApplication::palette, resolve this against \a palette (attributes from + // the inherited palette are copied over this widget's palette). Then + // propagate this palette to this widget's children. + QPalette naturalPalette = d->naturalWidgetPalette(d->inheritedPaletteResolveMask); + QPalette resolvedPalette = palette.resolve(naturalPalette); + d->setPalette_helper(resolvedPalette); +} + +/*! + \internal + + Returns the palette that the widget \a w inherits from its ancestors and + QApplication::palette. \a inheritedMask is the combination of the widget's + ancestors palette request masks (i.e., which attributes from the parent + widget's palette are implicitly imposed on this widget by the user). Note + that this font does not take into account the palette set on \a w itself. +*/ +QPalette QWidgetPrivate::naturalWidgetPalette(uint inheritedMask) const +{ + Q_Q(const QWidget); + QPalette naturalPalette = QApplication::palette(q); + if (!q->testAttribute(Qt::WA_StyleSheet) + && (!q->isWindow() || q->testAttribute(Qt::WA_WindowPropagation) +#ifndef QT_NO_GRAPHICSVIEW + || (extra && extra->proxyWidget) +#endif //QT_NO_GRAPHICSVIEW + )) { + if (QWidget *p = q->parentWidget()) { + if (!p->testAttribute(Qt::WA_StyleSheet)) { + if (!naturalPalette.isCopyOf(QApplication::palette())) { + QPalette inheritedPalette = p->palette(); + inheritedPalette.resolve(inheritedMask); + naturalPalette = inheritedPalette.resolve(naturalPalette); + } else { + naturalPalette = p->palette(); + } + } + } +#ifndef QT_NO_GRAPHICSVIEW + else if (extra && extra->proxyWidget) { + QPalette inheritedPalette = extra->proxyWidget->palette(); + inheritedPalette.resolve(inheritedMask); + naturalPalette = inheritedPalette.resolve(naturalPalette); + } +#endif //QT_NO_GRAPHICSVIEW + } + naturalPalette.resolve(0); + return naturalPalette; +} +/*! + \internal + + Determine which palette is inherited from this widget's ancestors and + QApplication::palette, resolve this against this widget's palette + (attributes from the inherited palette are copied over this widget's + palette). Then propagate this palette to this widget's children. +*/ +void QWidgetPrivate::resolvePalette() +{ + QPalette naturalPalette = naturalWidgetPalette(inheritedPaletteResolveMask); + QPalette resolvedPalette = data.pal.resolve(naturalPalette); + setPalette_helper(resolvedPalette); +} + +void QWidgetPrivate::setPalette_helper(const QPalette &palette) +{ + Q_Q(QWidget); + if (data.pal == palette && data.pal.resolve() == palette.resolve()) + return; + data.pal = palette; + updateSystemBackground(); + propagatePaletteChange(); + updateIsOpaque(); + q->update(); + updateIsOpaque(); +} + +/*! + \property QWidget::font + \brief the font currently set for the widget + + This property describes the widget's requested font. The font is used by + the widget's style when rendering standard components, and is available as + a means to ensure that custom widgets can maintain consistency with the + native platform's look and feel. It's common that different platforms, or + different styles, define different fonts for an application. + + When you assign a new font to a widget, the properties from this font are + combined with the widget's default font to form the widget's final + font. You can call fontInfo() to get a copy of the widget's final + font. The final font is also used to initialize QPainter's font. + + The default depends on the system environment. QApplication maintains a + system/theme font which serves as a default for all widgets. There may + also be special font defaults for certain types of widgets. You can also + define default fonts for widgets yourself by passing a custom font and the + name of a widget to QApplication::setFont(). Finally, the font is matched + against Qt's font database to find the best match. + + QWidget propagates explicit font properties from parent to child. If you + change a specific property on a font and assign that font to a widget, + that property will propagate to all the widget's children, overriding any + system defaults for that property. Note that fonts by default don't + propagate to windows (see isWindow()) unless the Qt::WA_WindowPropagation + attribute is enabled. + + QWidget's font propagation is similar to its palette propagation. + + The current style, which is used to render the content of all standard Qt + widgets, is free to choose to use the widget font, or in some cases, to + ignore it (partially, or completely). In particular, certain styles like + GTK style, Mac style, Windows XP, and Vista style, apply special + modifications to the widget font to match the platform's native look and + feel. Because of this, assigning properties to a widget's font is not + guaranteed to change the appearance of the widget. Instead, you may choose + to apply a \l styleSheet. + + \note If \l{Qt Style Sheets} are used on the same widget as setFont(), + style sheets will take precedence if the settings conflict. + + \sa fontInfo(), fontMetrics() +*/ + +void QWidget::setFont(const QFont &font) +{ + Q_D(QWidget); + +#ifndef QT_NO_STYLE_STYLESHEET + const QStyleSheetStyle* style; + if (d->extra && (style = qobject_cast<const QStyleSheetStyle*>(d->extra->style))) { + style->saveWidgetFont(this, font); + } +#endif + + setAttribute(Qt::WA_SetFont, font.resolve() != 0); + + // Determine which font is inherited from this widget's ancestors and + // QApplication::font, resolve this against \a font (attributes from the + // inherited font are copied over). Then propagate this font to this + // widget's children. + QFont naturalFont = d->naturalWidgetFont(d->inheritedFontResolveMask); + QFont resolvedFont = font.resolve(naturalFont); + d->setFont_helper(resolvedFont); +} + +/* + \internal + + Returns the font that the widget \a w inherits from its ancestors and + QApplication::font. \a inheritedMask is the combination of the widget's + ancestors font request masks (i.e., which attributes from the parent + widget's font are implicitly imposed on this widget by the user). Note + that this font does not take into account the font set on \a w itself. + + ### Stylesheet has a different font propagation mechanism. When a stylesheet + is applied, fonts are not propagated anymore +*/ +QFont QWidgetPrivate::naturalWidgetFont(uint inheritedMask) const +{ + Q_Q(const QWidget); + QFont naturalFont = QApplication::font(q); + if (!q->testAttribute(Qt::WA_StyleSheet) + && (!q->isWindow() || q->testAttribute(Qt::WA_WindowPropagation) +#ifndef QT_NO_GRAPHICSVIEW + || (extra && extra->proxyWidget) +#endif //QT_NO_GRAPHICSVIEW + )) { + if (QWidget *p = q->parentWidget()) { + if (!p->testAttribute(Qt::WA_StyleSheet)) { + if (!naturalFont.isCopyOf(QApplication::font())) { + QFont inheritedFont = p->font(); + inheritedFont.resolve(inheritedMask); + naturalFont = inheritedFont.resolve(naturalFont); + } else { + naturalFont = p->font(); + } + } + } +#ifndef QT_NO_GRAPHICSVIEW + else if (extra && extra->proxyWidget) { + QFont inheritedFont = extra->proxyWidget->font(); + inheritedFont.resolve(inheritedMask); + naturalFont = inheritedFont.resolve(naturalFont); + } +#endif //QT_NO_GRAPHICSVIEW + } + naturalFont.resolve(0); + return naturalFont; +} + +/*! + \internal + + Determine which font is implicitly imposed on this widget by its ancestors + and QApplication::font, resolve this against its own font (attributes from + the implicit font are copied over). Then propagate this font to this + widget's children. +*/ +void QWidgetPrivate::resolveFont() +{ + QFont naturalFont = naturalWidgetFont(inheritedFontResolveMask); + QFont resolvedFont = data.fnt.resolve(naturalFont); + setFont_helper(resolvedFont); +} + +/*! + \internal + + Assign \a font to this widget, and propagate it to all children, except + style sheet widgets (handled differently) and windows that don't enable + window propagation. \a implicitMask is the union of all ancestor widgets' + font request masks, and determines which attributes from this widget's + font should propagate. +*/ +void QWidgetPrivate::updateFont(const QFont &font) +{ + Q_Q(QWidget); +#ifndef QT_NO_STYLE_STYLESHEET + const QStyleSheetStyle* cssStyle; + cssStyle = extra ? qobject_cast<const QStyleSheetStyle*>(extra->style) : 0; +#endif + +#ifdef QT3_SUPPORT + QFont old = data.fnt; +#endif + data.fnt = QFont(font, q); +#if defined(Q_WS_X11) + // make sure the font set on this widget is associated with the correct screen + data.fnt.x11SetScreen(xinfo.screen()); +#endif + // Combine new mask with natural mask and propagate to children. +#ifndef QT_NO_GRAPHICSVIEW + if (!q->parentWidget() && extra && extra->proxyWidget) { + QGraphicsProxyWidget *p = extra->proxyWidget; + inheritedFontResolveMask = p->d_func()->inheritedFontResolveMask | p->font().resolve(); + } else +#endif //QT_NO_GRAPHICSVIEW + if (q->isWindow() && !q->testAttribute(Qt::WA_WindowPropagation)) { + inheritedFontResolveMask = 0; + } + uint newMask = data.fnt.resolve() | inheritedFontResolveMask; + + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget*>(children.at(i)); + if (w) { + if (0) { +#ifndef QT_NO_STYLE_STYLESHEET + } else if (w->testAttribute(Qt::WA_StyleSheet)) { + // Style sheets follow a different font propagation scheme. + if (cssStyle) + cssStyle->updateStyleSheetFont(w); +#endif + } else if ((!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation))) { + // Propagate font changes. + QWidgetPrivate *wd = w->d_func(); + wd->inheritedFontResolveMask = newMask; + wd->resolveFont(); + } + } + } + +#ifndef QT_NO_STYLE_STYLESHEET + if (cssStyle) { + cssStyle->updateStyleSheetFont(q); + } +#endif + + QEvent e(QEvent::FontChange); + QApplication::sendEvent(q, &e); +#ifdef QT3_SUPPORT + q->fontChange(old); +#endif +} + +void QWidgetPrivate::setLayoutDirection_helper(Qt::LayoutDirection direction) +{ + Q_Q(QWidget); + + if ( (direction == Qt::RightToLeft) == q->testAttribute(Qt::WA_RightToLeft)) + return; + q->setAttribute(Qt::WA_RightToLeft, (direction == Qt::RightToLeft)); + if (!children.isEmpty()) { + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget*>(children.at(i)); + if (w && !w->isWindow() && !w->testAttribute(Qt::WA_SetLayoutDirection)) + w->d_func()->setLayoutDirection_helper(direction); + } + } + QEvent e(QEvent::LayoutDirectionChange); + QApplication::sendEvent(q, &e); +} + +void QWidgetPrivate::resolveLayoutDirection() +{ + Q_Q(const QWidget); + if (!q->testAttribute(Qt::WA_SetLayoutDirection)) + setLayoutDirection_helper(q->isWindow() ? QApplication::layoutDirection() : q->parentWidget()->layoutDirection()); +} + +/*! + \property QWidget::layoutDirection + + \brief the layout direction for this widget + + By default, this property is set to Qt::LeftToRight. + + When the layout direction is set on a widget, it will propagate to + the widget's children, but not to a child that is a window and not + to a child for which setLayoutDirection() has been explicitly + called. Also, child widgets added \e after setLayoutDirection() + has been called for the parent do not inherit the parent's layout + direction. + + This method no longer affects text layout direction since Qt 4.7. + + \sa QApplication::layoutDirection +*/ +void QWidget::setLayoutDirection(Qt::LayoutDirection direction) +{ + Q_D(QWidget); + + if (direction == Qt::LayoutDirectionAuto) { + unsetLayoutDirection(); + return; + } + + setAttribute(Qt::WA_SetLayoutDirection); + d->setLayoutDirection_helper(direction); +} + +Qt::LayoutDirection QWidget::layoutDirection() const +{ + return testAttribute(Qt::WA_RightToLeft) ? Qt::RightToLeft : Qt::LeftToRight; +} + +void QWidget::unsetLayoutDirection() +{ + Q_D(QWidget); + setAttribute(Qt::WA_SetLayoutDirection, false); + d->resolveLayoutDirection(); +} + +/*! + \fn QFontMetrics QWidget::fontMetrics() const + + Returns the font metrics for the widget's current font. + Equivalent to QFontMetrics(widget->font()). + + \sa font(), fontInfo(), setFont() +*/ + +/*! + \fn QFontInfo QWidget::fontInfo() const + + Returns the font info for the widget's current font. + Equivalent to QFontInto(widget->font()). + + \sa font(), fontMetrics(), setFont() +*/ + + +/*! + \property QWidget::cursor + \brief the cursor shape for this widget + + The mouse cursor will assume this shape when it's over this + widget. See the \link Qt::CursorShape list of predefined cursor + objects\endlink for a range of useful shapes. + + An editor widget might use an I-beam cursor: + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 6 + + If no cursor has been set, or after a call to unsetCursor(), the + parent's cursor is used. + + By default, this property contains a cursor with the Qt::ArrowCursor + shape. + + Some underlying window implementations will reset the cursor if it + leaves a widget even if the mouse is grabbed. If you want to have + a cursor set for all widgets, even when outside the window, consider + QApplication::setOverrideCursor(). + + \sa QApplication::setOverrideCursor() +*/ + +#ifndef QT_NO_CURSOR +QCursor QWidget::cursor() const +{ + Q_D(const QWidget); + if (testAttribute(Qt::WA_SetCursor)) + return (d->extra && d->extra->curs) + ? *d->extra->curs + : QCursor(Qt::ArrowCursor); + if (isWindow() || !parentWidget()) + return QCursor(Qt::ArrowCursor); + return parentWidget()->cursor(); +} + +void QWidget::setCursor(const QCursor &cursor) +{ + Q_D(QWidget); +// On Mac we must set the cursor even if it is the ArrowCursor. +#if !defined(Q_WS_MAC) && !defined(Q_WS_QWS) + if (cursor.shape() != Qt::ArrowCursor + || (d->extra && d->extra->curs)) +#endif + { + d->createExtra(); + QCursor *newCursor = new QCursor(cursor); + delete d->extra->curs; + d->extra->curs = newCursor; + } + setAttribute(Qt::WA_SetCursor); + d->setCursor_sys(cursor); + + QEvent event(QEvent::CursorChange); + QApplication::sendEvent(this, &event); +} + +void QWidget::unsetCursor() +{ + Q_D(QWidget); + if (d->extra) { + delete d->extra->curs; + d->extra->curs = 0; + } + if (!isWindow()) + setAttribute(Qt::WA_SetCursor, false); + d->unsetCursor_sys(); + + QEvent event(QEvent::CursorChange); + QApplication::sendEvent(this, &event); +} + +#endif + +/*! + \enum QWidget::RenderFlag + + This enum describes how to render the widget when calling QWidget::render(). + + \value DrawWindowBackground If you enable this option, the widget's background + is rendered into the target even if autoFillBackground is not set. By default, + this option is enabled. + + \value DrawChildren If you enable this option, the widget's children + are rendered recursively into the target. By default, this option is enabled. + + \value IgnoreMask If you enable this option, the widget's QWidget::mask() + is ignored when rendering into the target. By default, this option is disabled. + + \since 4.3 +*/ + +/*! + \since 4.3 + + Renders the \a sourceRegion of this widget into the \a target + using \a renderFlags to determine how to render. Rendering + starts at \a targetOffset in the \a target. For example: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 7 + + If \a sourceRegion is a null region, this function will use QWidget::rect() as + the region, i.e. the entire widget. + + Ensure that you call QPainter::end() for the \a target device's + active painter (if any) before rendering. For example: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 8 + + \note To obtain the contents of an OpenGL widget, use QGLWidget::grabFrameBuffer() + or QGLWidget::renderPixmap() instead. +*/ +void QWidget::render(QPaintDevice *target, const QPoint &targetOffset, + const QRegion &sourceRegion, RenderFlags renderFlags) +{ + d_func()->render(target, targetOffset, sourceRegion, renderFlags, false); +} + +/*! + \overload + + Renders the widget into the \a painter's QPainter::device(). + + Transformations and settings applied to the \a painter will be used + when rendering. + + \note The \a painter must be active. On Mac OS X the widget will be + rendered into a QPixmap and then drawn by the \a painter. + + \sa QPainter::device() +*/ +void QWidget::render(QPainter *painter, const QPoint &targetOffset, + const QRegion &sourceRegion, RenderFlags renderFlags) +{ + if (!painter) { + qWarning("QWidget::render: Null pointer to painter"); + return; + } + + if (!painter->isActive()) { + qWarning("QWidget::render: Cannot render with an inactive painter"); + return; + } + + const qreal opacity = painter->opacity(); + if (qFuzzyIsNull(opacity)) + return; // Fully transparent. + + Q_D(QWidget); + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; + const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) + : sourceRegion; + if (toBePainted.isEmpty()) + return; + + if (!d->extra) + d->createExtra(); + d->extra->inRenderWithPainter = true; + +#ifdef Q_WS_MAC + d->render_helper(painter, targetOffset, toBePainted, renderFlags); +#else + QPaintEngine *engine = painter->paintEngine(); + Q_ASSERT(engine); + QPaintEnginePrivate *enginePriv = engine->d_func(); + Q_ASSERT(enginePriv); + QPaintDevice *target = engine->paintDevice(); + Q_ASSERT(target); + + // Render via a pixmap when dealing with non-opaque painters or printers. + if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { + d->render_helper(painter, targetOffset, toBePainted, renderFlags); + d->extra->inRenderWithPainter = false; + return; + } + + // Set new shared painter. + QPainter *oldPainter = d->sharedPainter(); + d->setSharedPainter(painter); + + // Save current system clip, viewport and transform, + const QTransform oldTransform = enginePriv->systemTransform; + const QRegion oldSystemClip = enginePriv->systemClip; + const QRegion oldSystemViewport = enginePriv->systemViewport; + + // This ensures that all painting triggered by render() is clipped to the current engine clip. + if (painter->hasClipping()) { + const QRegion painterClip = painter->deviceTransform().map(painter->clipRegion()); + enginePriv->setSystemViewport(oldSystemClip.isEmpty() ? painterClip : oldSystemClip & painterClip); + } else { + enginePriv->setSystemViewport(oldSystemClip); + } + + render(target, targetOffset, toBePainted, renderFlags); + + // Restore system clip, viewport and transform. + enginePriv->systemClip = oldSystemClip; + enginePriv->setSystemViewport(oldSystemViewport); + enginePriv->setSystemTransform(oldTransform); + + // Restore shared painter. + d->setSharedPainter(oldPainter); +#endif + + d->extra->inRenderWithPainter = false; +} + +/*! + \brief The graphicsEffect function returns a pointer to the + widget's graphics effect. + + If the widget has no graphics effect, 0 is returned. + + \since 4.6 + + \sa setGraphicsEffect() +*/ +#ifndef QT_NO_GRAPHICSEFFECT +QGraphicsEffect *QWidget::graphicsEffect() const +{ + Q_D(const QWidget); + return d->graphicsEffect; +} +#endif //QT_NO_GRAPHICSEFFECT + +/*! + + \brief The setGraphicsEffect function is for setting the widget's graphics effect. + + Sets \a effect as the widget's effect. If there already is an effect installed + on this widget, QWidget will delete the existing effect before installing + the new \a effect. + + If \a effect is the installed on a different widget, setGraphicsEffect() will remove + the effect from the widget and install it on this widget. + + QWidget takes ownership of \a effect. + + \note This function will apply the effect on itself and all its children. + + \since 4.6 + + \sa graphicsEffect() +*/ +#ifndef QT_NO_GRAPHICSEFFECT +void QWidget::setGraphicsEffect(QGraphicsEffect *effect) +{ + Q_D(QWidget); + if (d->graphicsEffect == effect) + return; + + if (d->graphicsEffect) { + d->invalidateBuffer(rect()); + delete d->graphicsEffect; + d->graphicsEffect = 0; + } + + if (effect) { + // Set new effect. + QGraphicsEffectSourcePrivate *sourced = new QWidgetEffectSourcePrivate(this); + QGraphicsEffectSource *source = new QGraphicsEffectSource(*sourced); + d->graphicsEffect = effect; + effect->d_func()->setGraphicsEffectSource(source); + update(); + } + + d->updateIsOpaque(); +} +#endif //QT_NO_GRAPHICSEFFECT + +bool QWidgetPrivate::isAboutToShow() const +{ + if (data.in_show) + return true; + + Q_Q(const QWidget); + if (q->isHidden()) + return false; + + // The widget will be shown if any of its ancestors are about to show. + QWidget *parent = q->parentWidget(); + return parent ? parent->d_func()->isAboutToShow() : false; +} + +QRegion QWidgetPrivate::prepareToRender(const QRegion ®ion, QWidget::RenderFlags renderFlags) +{ + Q_Q(QWidget); + const bool isVisible = q->isVisible(); + + // Make sure the widget is laid out correctly. + if (!isVisible && !isAboutToShow()) { + QWidget *topLevel = q->window(); + (void)topLevel->d_func()->topData(); // Make sure we at least have top-data. + topLevel->ensurePolished(); + + // Invalidate the layout of hidden ancestors (incl. myself) and pretend + // they're not explicitly hidden. + QWidget *widget = q; + QWidgetList hiddenWidgets; + while (widget) { + if (widget->isHidden()) { + widget->setAttribute(Qt::WA_WState_Hidden, false); + hiddenWidgets.append(widget); + if (!widget->isWindow() && widget->parentWidget()->d_func()->layout) + widget->d_func()->updateGeometry_helper(true); + } + widget = widget->parentWidget(); + } + + // Activate top-level layout. + if (topLevel->d_func()->layout) + topLevel->d_func()->layout->activate(); + + // Adjust size if necessary. + QTLWExtra *topLevelExtra = topLevel->d_func()->maybeTopData(); + if (topLevelExtra && !topLevelExtra->sizeAdjusted + && !topLevel->testAttribute(Qt::WA_Resized)) { + topLevel->adjustSize(); + topLevel->setAttribute(Qt::WA_Resized, false); + } + + // Activate child layouts. + topLevel->d_func()->activateChildLayoutsRecursively(); + + // We're not cheating with WA_WState_Hidden anymore. + for (int i = 0; i < hiddenWidgets.size(); ++i) { + QWidget *widget = hiddenWidgets.at(i); + widget->setAttribute(Qt::WA_WState_Hidden); + if (!widget->isWindow() && widget->parentWidget()->d_func()->layout) + widget->parentWidget()->d_func()->layout->invalidate(); + } + } else if (isVisible) { + q->window()->d_func()->sendPendingMoveAndResizeEvents(true, true); + } + + // Calculate the region to be painted. + QRegion toBePainted = !region.isEmpty() ? region : QRegion(q->rect()); + if (!(renderFlags & QWidget::IgnoreMask) && extra && extra->hasMask) + toBePainted &= extra->mask; + return toBePainted; +} + +void QWidgetPrivate::render_helper(QPainter *painter, const QPoint &targetOffset, const QRegion &toBePainted, + QWidget::RenderFlags renderFlags) +{ + Q_ASSERT(painter); + Q_ASSERT(!toBePainted.isEmpty()); + + Q_Q(QWidget); +#ifndef Q_WS_MAC + const QTransform originalTransform = painter->worldTransform(); + const bool useDeviceCoordinates = originalTransform.isScaling(); + if (!useDeviceCoordinates) { +#endif + // Render via a pixmap. + const QRect rect = toBePainted.boundingRect(); + const QSize size = rect.size(); + if (size.isNull()) + return; + + QPixmap pixmap(size); + if (!(renderFlags & QWidget::DrawWindowBackground) || !isOpaque) + pixmap.fill(Qt::transparent); + q->render(&pixmap, QPoint(), toBePainted, renderFlags); + + const bool restore = !(painter->renderHints() & QPainter::SmoothPixmapTransform); + painter->setRenderHints(QPainter::SmoothPixmapTransform, true); + + painter->drawPixmap(targetOffset, pixmap); + + if (restore) + painter->setRenderHints(QPainter::SmoothPixmapTransform, false); + +#ifndef Q_WS_MAC + } else { + // Render via a pixmap in device coordinates (to avoid pixmap scaling). + QTransform transform = originalTransform; + transform.translate(targetOffset.x(), targetOffset.y()); + + QPaintDevice *device = painter->device(); + Q_ASSERT(device); + + // Calculate device rect. + const QRectF rect(toBePainted.boundingRect()); + QRect deviceRect = transform.mapRect(QRectF(0, 0, rect.width(), rect.height())).toAlignedRect(); + deviceRect &= QRect(0, 0, device->width(), device->height()); + + QPixmap pixmap(deviceRect.size()); + pixmap.fill(Qt::transparent); + + // Create a pixmap device coordinate painter. + QPainter pixmapPainter(&pixmap); + pixmapPainter.setRenderHints(painter->renderHints()); + transform *= QTransform::fromTranslate(-deviceRect.x(), -deviceRect.y()); + pixmapPainter.setTransform(transform); + + q->render(&pixmapPainter, QPoint(), toBePainted, renderFlags); + pixmapPainter.end(); + + // And then draw the pixmap. + painter->setTransform(QTransform()); + painter->drawPixmap(deviceRect.topLeft(), pixmap); + painter->setTransform(originalTransform); + } +#endif +} + +void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, + QPainter *sharedPainter, QWidgetBackingStore *backingStore) +{ + if (rgn.isEmpty()) + return; + +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + if (qt_mac_clearDirtyOnWidgetInsideDrawWidget) + dirtyOnWidget = QRegion(); + + // We disable the rendering of QToolBar in the backingStore if + // it's supposed to be in the unified toolbar on Mac OS X. + if (backingStore && isInUnifiedToolbar) + return; +#endif // Q_WS_MAC && QT_MAC_USE_COCOA + + + Q_Q(QWidget); +#ifndef QT_NO_GRAPHICSEFFECT + if (graphicsEffect && graphicsEffect->isEnabled()) { + QGraphicsEffectSource *source = graphicsEffect->d_func()->source; + QWidgetEffectSourcePrivate *sourced = static_cast<QWidgetEffectSourcePrivate *> + (source->d_func()); + if (!sourced->context) { + QWidgetPaintContext context(pdev, rgn, offset, flags, sharedPainter, backingStore); + sourced->context = &context; + if (!sharedPainter) { + QPaintEngine *paintEngine = pdev->paintEngine(); + paintEngine->d_func()->systemClip = rgn.translated(offset); + QPainter p(pdev); + p.translate(offset); + context.painter = &p; + graphicsEffect->draw(&p); + paintEngine->d_func()->systemClip = QRegion(); + } else { + context.painter = sharedPainter; + if (sharedPainter->worldTransform() != sourced->lastEffectTransform) { + sourced->invalidateCache(); + sourced->lastEffectTransform = sharedPainter->worldTransform(); + } + sharedPainter->save(); + sharedPainter->translate(offset); + graphicsEffect->draw(sharedPainter); + sharedPainter->restore(); + } + sourced->context = 0; + return; + } + } +#endif //QT_NO_GRAFFICSEFFECT + + const bool asRoot = flags & DrawAsRoot; + const bool alsoOnScreen = flags & DrawPaintOnScreen; + const bool recursive = flags & DrawRecursive; + const bool alsoInvisible = flags & DrawInvisible; + + Q_ASSERT(sharedPainter ? sharedPainter->isActive() : true); + + QRegion toBePainted(rgn); + if (asRoot && !alsoInvisible) + toBePainted &= clipRect(); //(rgn & visibleRegion()); + if (!(flags & DontSubtractOpaqueChildren)) + subtractOpaqueChildren(toBePainted, q->rect()); + + if (!toBePainted.isEmpty()) { + bool onScreen = paintOnScreen(); + if (!onScreen || alsoOnScreen) { + //update the "in paint event" flag + if (q->testAttribute(Qt::WA_WState_InPaintEvent)) + qWarning("QWidget::repaint: Recursive repaint detected"); + q->setAttribute(Qt::WA_WState_InPaintEvent); + + //clip away the new area +#ifndef QT_NO_PAINT_DEBUG + bool flushed = QWidgetBackingStore::flushPaint(q, toBePainted); +#endif + QPaintEngine *paintEngine = pdev->paintEngine(); + if (paintEngine) { + setRedirected(pdev, -offset); + +#ifdef Q_WS_MAC + // (Alien support) Special case for Mac when redirecting: If the paint device + // is of the Widget type we need to set WA_WState_InPaintEvent since painting + // outside the paint event is not supported on QWidgets. The attributeis + // restored further down. + if (pdev->devType() == QInternal::Widget) + static_cast<QWidget *>(pdev)->setAttribute(Qt::WA_WState_InPaintEvent); + +#endif + if (sharedPainter) + paintEngine->d_func()->systemClip = toBePainted; + else + paintEngine->d_func()->systemRect = q->data->crect; + + //paint the background + if ((asRoot || q->autoFillBackground() || onScreen || q->testAttribute(Qt::WA_StyledBackground)) + && !q->testAttribute(Qt::WA_OpaquePaintEvent) && !q->testAttribute(Qt::WA_NoSystemBackground)) { + QPainter p(q); + paintBackground(&p, toBePainted, (asRoot || onScreen) ? flags | DrawAsRoot : 0); + } + + if (!sharedPainter) + paintEngine->d_func()->systemClip = toBePainted.translated(offset); + + if (!onScreen && !asRoot && !isOpaque && q->testAttribute(Qt::WA_TintedBackground)) { + QPainter p(q); + QColor tint = q->palette().window().color(); + tint.setAlphaF(qreal(.6)); + p.fillRect(toBePainted.boundingRect(), tint); + } + } + +#if 0 + qDebug() << "painting" << q << "opaque ==" << isOpaque(); + qDebug() << "clipping to" << toBePainted << "location == " << offset + << "geometry ==" << QRect(q->mapTo(q->window(), QPoint(0, 0)), q->size()); +#endif + + //actually send the paint event + QPaintEvent e(toBePainted); + QCoreApplication::sendSpontaneousEvent(q, &e); +#if !defined(Q_WS_QWS) && !defined(Q_WS_QPA) + if (backingStore && !onScreen && !asRoot && (q->internalWinId() || !q->nativeParentWidget()->isWindow())) + backingStore->markDirtyOnScreen(toBePainted, q, offset); +#endif + + //restore + if (paintEngine) { +#ifdef Q_WS_MAC + if (pdev->devType() == QInternal::Widget) + static_cast<QWidget *>(pdev)->setAttribute(Qt::WA_WState_InPaintEvent, false); +#endif + restoreRedirected(); + if (!sharedPainter) + paintEngine->d_func()->systemRect = QRect(); + else + paintEngine->d_func()->currentClipWidget = 0; + paintEngine->d_func()->systemClip = QRegion(); + } + q->setAttribute(Qt::WA_WState_InPaintEvent, false); + if (q->paintingActive() && !q->testAttribute(Qt::WA_PaintOutsidePaintEvent)) + qWarning("QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent"); + + if (paintEngine && paintEngine->autoDestruct()) { + delete paintEngine; + } + +#ifndef QT_NO_PAINT_DEBUG + if (flushed) + QWidgetBackingStore::unflushPaint(q, toBePainted); +#endif + } else if (q->isWindow()) { + QPaintEngine *engine = pdev->paintEngine(); + if (engine) { + QPainter p(pdev); + p.setClipRegion(toBePainted); + const QBrush bg = q->palette().brush(QPalette::Window); + if (bg.style() == Qt::TexturePattern) + p.drawTiledPixmap(q->rect(), bg.texture()); + else + p.fillRect(q->rect(), bg); + + if (engine->autoDestruct()) + delete engine; + } + } + } + + if (recursive && !children.isEmpty()) { + paintSiblingsRecursive(pdev, children, children.size() - 1, rgn, offset, flags & ~DrawAsRoot +#ifdef Q_BACKINGSTORE_SUBSURFACES + , q->windowSurface() +#endif + , sharedPainter, backingStore); + } +} + +void QWidgetPrivate::render(QPaintDevice *target, const QPoint &targetOffset, + const QRegion &sourceRegion, QWidget::RenderFlags renderFlags, + bool readyToRender) +{ + if (!target) { + qWarning("QWidget::render: null pointer to paint device"); + return; + } + + const bool inRenderWithPainter = extra && extra->inRenderWithPainter; + QRegion paintRegion = !inRenderWithPainter && !readyToRender + ? prepareToRender(sourceRegion, renderFlags) + : sourceRegion; + if (paintRegion.isEmpty()) + return; + +#ifndef Q_WS_MAC + QPainter *oldSharedPainter = inRenderWithPainter ? sharedPainter() : 0; + + // Use the target's shared painter if set (typically set when doing + // "other->render(widget);" in the widget's paintEvent. + if (target->devType() == QInternal::Widget) { + QWidgetPrivate *targetPrivate = static_cast<QWidget *>(target)->d_func(); + if (targetPrivate->extra && targetPrivate->extra->inRenderWithPainter) { + QPainter *targetPainter = targetPrivate->sharedPainter(); + if (targetPainter && targetPainter->isActive()) + setSharedPainter(targetPainter); + } + } +#endif + + // Use the target's redirected device if set and adjust offset and paint + // region accordingly. This is typically the case when people call render + // from the paintEvent. + QPoint offset = targetOffset; + offset -= paintRegion.boundingRect().topLeft(); + QPoint redirectionOffset; + QPaintDevice *redirected = 0; + + if (target->devType() == QInternal::Widget) + redirected = static_cast<QWidget *>(target)->d_func()->redirected(&redirectionOffset); + if (!redirected) + redirected = QPainter::redirected(target, &redirectionOffset); + + if (redirected) { + target = redirected; + offset -= redirectionOffset; + } + + if (!inRenderWithPainter) { // Clip handled by shared painter (in qpainter.cpp). + if (QPaintEngine *targetEngine = target->paintEngine()) { + const QRegion targetSystemClip = targetEngine->systemClip(); + if (!targetSystemClip.isEmpty()) + paintRegion &= targetSystemClip.translated(-offset); + } + } + + // Set backingstore flags. + int flags = DrawPaintOnScreen | DrawInvisible; + if (renderFlags & QWidget::DrawWindowBackground) + flags |= DrawAsRoot; + + if (renderFlags & QWidget::DrawChildren) + flags |= DrawRecursive; + else + flags |= DontSubtractOpaqueChildren; + +#ifdef Q_WS_QWS + flags |= DontSetCompositionMode; +#endif + + if (target->devType() == QInternal::Printer) { + QPainter p(target); + render_helper(&p, targetOffset, paintRegion, renderFlags); + return; + } + +#ifndef Q_WS_MAC + // Render via backingstore. + drawWidget(target, paintRegion, offset, flags, sharedPainter()); + + // Restore shared painter. + if (oldSharedPainter) + setSharedPainter(oldSharedPainter); +#else + // Render via backingstore (no shared painter). + drawWidget(target, paintRegion, offset, flags, 0); +#endif +} + +void QWidgetPrivate::paintSiblingsRecursive(QPaintDevice *pdev, const QObjectList& siblings, int index, const QRegion &rgn, + const QPoint &offset, int flags +#ifdef Q_BACKINGSTORE_SUBSURFACES + , const QWindowSurface *currentSurface +#endif + , QPainter *sharedPainter, QWidgetBackingStore *backingStore) +{ + QWidget *w = 0; + QRect boundingRect; + bool dirtyBoundingRect = true; + const bool exludeOpaqueChildren = (flags & DontDrawOpaqueChildren); + const bool excludeNativeChildren = (flags & DontDrawNativeChildren); + + do { + QWidget *x = qobject_cast<QWidget*>(siblings.at(index)); + if (x && !(exludeOpaqueChildren && x->d_func()->isOpaque) && !x->isHidden() && !x->isWindow() + && !(excludeNativeChildren && x->internalWinId())) { + if (dirtyBoundingRect) { + boundingRect = rgn.boundingRect(); + dirtyBoundingRect = false; + } + + if (qRectIntersects(boundingRect, x->d_func()->effectiveRectFor(x->data->crect))) { +#ifdef Q_BACKINGSTORE_SUBSURFACES + if (x->windowSurface() == currentSurface) +#endif + { + w = x; + break; + } + } + } + --index; + } while (index >= 0); + + if (!w) + return; + + QWidgetPrivate *wd = w->d_func(); + const QPoint widgetPos(w->data->crect.topLeft()); + const bool hasMask = wd->extra && wd->extra->hasMask && !wd->graphicsEffect; + if (index > 0) { + QRegion wr(rgn); + if (wd->isOpaque) + wr -= hasMask ? wd->extra->mask.translated(widgetPos) : w->data->crect; + paintSiblingsRecursive(pdev, siblings, --index, wr, offset, flags +#ifdef Q_BACKINGSTORE_SUBSURFACES + , currentSurface +#endif + , sharedPainter, backingStore); + } + + if (w->updatesEnabled() +#ifndef QT_NO_GRAPHICSVIEW + && (!w->d_func()->extra || !w->d_func()->extra->proxyWidget) +#endif //QT_NO_GRAPHICSVIEW + ) { + QRegion wRegion(rgn); + wRegion &= wd->effectiveRectFor(w->data->crect); + wRegion.translate(-widgetPos); + if (hasMask) + wRegion &= wd->extra->mask; + wd->drawWidget(pdev, wRegion, offset + widgetPos, flags, sharedPainter, backingStore); + } +} + +#ifndef QT_NO_GRAPHICSEFFECT +QRectF QWidgetEffectSourcePrivate::boundingRect(Qt::CoordinateSystem system) const +{ + if (system != Qt::DeviceCoordinates) + return m_widget->rect(); + + if (!context) { + // Device coordinates without context not yet supported. + qWarning("QGraphicsEffectSource::boundingRect: Not yet implemented, lacking device context"); + return QRectF(); + } + + return context->painter->worldTransform().mapRect(m_widget->rect()); +} + +void QWidgetEffectSourcePrivate::draw(QPainter *painter) +{ + if (!context || context->painter != painter) { + m_widget->render(painter); + return; + } + + // The region saved in the context is neither clipped to the rect + // nor the mask, so we have to clip it here before calling drawWidget. + QRegion toBePainted = context->rgn; + toBePainted &= m_widget->rect(); + QWidgetPrivate *wd = qt_widget_private(m_widget); + if (wd->extra && wd->extra->hasMask) + toBePainted &= wd->extra->mask; + + wd->drawWidget(context->pdev, toBePainted, context->offset, context->flags, + context->sharedPainter, context->backingStore); +} + +QPixmap QWidgetEffectSourcePrivate::pixmap(Qt::CoordinateSystem system, QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const +{ + const bool deviceCoordinates = (system == Qt::DeviceCoordinates); + if (!context && deviceCoordinates) { + // Device coordinates without context not yet supported. + qWarning("QGraphicsEffectSource::pixmap: Not yet implemented, lacking device context"); + return QPixmap(); + } + + QPoint pixmapOffset; + QRectF sourceRect = m_widget->rect(); + + if (deviceCoordinates) { + const QTransform &painterTransform = context->painter->worldTransform(); + sourceRect = painterTransform.mapRect(sourceRect); + pixmapOffset = painterTransform.map(pixmapOffset); + } + + QRect effectRect; + + if (mode == QGraphicsEffect::PadToEffectiveBoundingRect) + effectRect = m_widget->graphicsEffect()->boundingRectFor(sourceRect).toAlignedRect(); + else if (mode == QGraphicsEffect::PadToTransparentBorder) + effectRect = sourceRect.adjusted(-1, -1, 1, 1).toAlignedRect(); + else + effectRect = sourceRect.toAlignedRect(); + + if (offset) + *offset = effectRect.topLeft(); + + pixmapOffset -= effectRect.topLeft(); + + QPixmap pixmap(effectRect.size()); + pixmap.fill(Qt::transparent); + m_widget->render(&pixmap, pixmapOffset, QRegion(), QWidget::DrawChildren); + return pixmap; +} +#endif //QT_NO_GRAPHICSEFFECT + +#ifndef QT_NO_GRAPHICSVIEW +/*! + \internal + + Finds the nearest widget embedded in a graphics proxy widget along the chain formed by this + widget and its ancestors. The search starts at \a origin (inclusive). + If successful, the function returns the proxy that embeds the widget, or 0 if no embedded + widget was found. +*/ +QGraphicsProxyWidget * QWidgetPrivate::nearestGraphicsProxyWidget(const QWidget *origin) +{ + if (origin) { + QWExtra *extra = origin->d_func()->extra; + if (extra && extra->proxyWidget) + return extra->proxyWidget; + return nearestGraphicsProxyWidget(origin->parentWidget()); + } + return 0; +} +#endif + +/*! + \property QWidget::locale + \brief the widget's locale + \since 4.3 + + As long as no special locale has been set, this is either + the parent's locale or (if this widget is a top level widget), + the default locale. + + If the widget displays dates or numbers, these should be formatted + using the widget's locale. + + \sa QLocale QLocale::setDefault() +*/ + +void QWidgetPrivate::setLocale_helper(const QLocale &loc, bool forceUpdate) +{ + Q_Q(QWidget); + if (locale == loc && !forceUpdate) + return; + + locale = loc; + + if (!children.isEmpty()) { + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget*>(children.at(i)); + if (!w) + continue; + if (w->testAttribute(Qt::WA_SetLocale)) + continue; + if (w->isWindow() && !w->testAttribute(Qt::WA_WindowPropagation)) + continue; + w->d_func()->setLocale_helper(loc, forceUpdate); + } + } + QEvent e(QEvent::LocaleChange); + QApplication::sendEvent(q, &e); +} + +void QWidget::setLocale(const QLocale &locale) +{ + Q_D(QWidget); + + setAttribute(Qt::WA_SetLocale); + d->setLocale_helper(locale); +} + +QLocale QWidget::locale() const +{ + Q_D(const QWidget); + + return d->locale; +} + +void QWidgetPrivate::resolveLocale() +{ + Q_Q(const QWidget); + + if (!q->testAttribute(Qt::WA_SetLocale)) { + setLocale_helper(q->isWindow() + ? QLocale() + : q->parentWidget()->locale()); + } +} + +void QWidget::unsetLocale() +{ + Q_D(QWidget); + setAttribute(Qt::WA_SetLocale, false); + d->resolveLocale(); +} + +static QString constructWindowTitleFromFilePath(const QString &filePath) +{ + QFileInfo fi(filePath); + QString windowTitle = fi.fileName() + QLatin1String("[*]"); +#ifndef Q_WS_MAC + QString appName = QApplication::applicationName(); + if (!appName.isEmpty()) + windowTitle += QLatin1Char(' ') + QChar(0x2014) + QLatin1Char(' ') + appName; +#endif + return windowTitle; +} + +/*! + \property QWidget::windowTitle + \brief the window title (caption) + + This property only makes sense for top-level widgets, such as + windows and dialogs. If no caption has been set, the title is based of the + \l windowFilePath. If neither of these is set, then the title is + an empty string. + + If you use the \l windowModified mechanism, the window title must + contain a "[*]" placeholder, which indicates where the '*' should + appear. Normally, it should appear right after the file name + (e.g., "document1.txt[*] - Text Editor"). If the \l + windowModified property is false (the default), the placeholder + is simply removed. + + \sa windowIcon, windowIconText, windowModified, windowFilePath +*/ +QString QWidget::windowTitle() const +{ + Q_D(const QWidget); + if (d->extra && d->extra->topextra) { + if (!d->extra->topextra->caption.isEmpty()) + return d->extra->topextra->caption; + if (!d->extra->topextra->filePath.isEmpty()) + return constructWindowTitleFromFilePath(d->extra->topextra->filePath); + } + return QString(); +} + +/*! + Returns a modified window title with the [*] place holder + replaced according to the rules described in QWidget::setWindowTitle + + This function assumes that "[*]" can be quoted by another + "[*]", so it will replace two place holders by one and + a single last one by either "*" or nothing depending on + the modified flag. + + \internal +*/ +QString qt_setWindowTitle_helperHelper(const QString &title, const QWidget *widget) +{ + Q_ASSERT(widget); + +#ifdef QT_EVAL + extern QString qt_eval_adapt_window_title(const QString &title); + QString cap = qt_eval_adapt_window_title(title); +#else + QString cap = title; +#endif + + if (cap.isEmpty()) + return cap; + + QLatin1String placeHolder("[*]"); + int placeHolderLength = 3; // QLatin1String doesn't have length() + + int index = cap.indexOf(placeHolder); + + // here the magic begins + while (index != -1) { + index += placeHolderLength; + int count = 1; + while (cap.indexOf(placeHolder, index) == index) { + ++count; + index += placeHolderLength; + } + + if (count%2) { // odd number of [*] -> replace last one + int lastIndex = cap.lastIndexOf(placeHolder, index - 1); + if (widget->isWindowModified() + && widget->style()->styleHint(QStyle::SH_TitleBar_ModifyNotification, 0, widget)) + cap.replace(lastIndex, 3, QWidget::tr("*")); + else + cap.remove(lastIndex, 3); + } + + index = cap.indexOf(placeHolder, index); + } + + cap.replace(QLatin1String("[*][*]"), placeHolder); + + return cap; +} + +void QWidgetPrivate::setWindowTitle_helper(const QString &title) +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created)) + setWindowTitle_sys(qt_setWindowTitle_helperHelper(title, q)); +} + +void QWidgetPrivate::setWindowIconText_helper(const QString &title) +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created)) + setWindowIconText_sys(qt_setWindowTitle_helperHelper(title, q)); +} + +void QWidget::setWindowIconText(const QString &iconText) +{ + if (QWidget::windowIconText() == iconText) + return; + + Q_D(QWidget); + d->topData()->iconText = iconText; + d->setWindowIconText_helper(iconText); + + QEvent e(QEvent::IconTextChange); + QApplication::sendEvent(this, &e); +} + +void QWidget::setWindowTitle(const QString &title) +{ + if (QWidget::windowTitle() == title && !title.isEmpty() && !title.isNull()) + return; + + Q_D(QWidget); + d->topData()->caption = title; + d->setWindowTitle_helper(title); + + QEvent e(QEvent::WindowTitleChange); + QApplication::sendEvent(this, &e); +} + + +/*! + \property QWidget::windowIcon + \brief the widget's icon + + This property only makes sense for windows. If no icon + has been set, windowIcon() returns the application icon + (QApplication::windowIcon()). + + \sa windowIconText, windowTitle +*/ +QIcon QWidget::windowIcon() const +{ + const QWidget *w = this; + while (w) { + const QWidgetPrivate *d = w->d_func(); + if (d->extra && d->extra->topextra && d->extra->topextra->icon) + return *d->extra->topextra->icon; + w = w->parentWidget(); + } + return QApplication::windowIcon(); +} + +void QWidgetPrivate::setWindowIcon_helper() +{ + QEvent e(QEvent::WindowIconChange); + QApplication::sendEvent(q_func(), &e); + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (w && !w->isWindow()) + QApplication::sendEvent(w, &e); + } +} + +void QWidget::setWindowIcon(const QIcon &icon) +{ + Q_D(QWidget); + + setAttribute(Qt::WA_SetWindowIcon, !icon.isNull()); + d->createTLExtra(); + + if (!d->extra->topextra->icon) + d->extra->topextra->icon = new QIcon(); + *d->extra->topextra->icon = icon; + + delete d->extra->topextra->iconPixmap; + d->extra->topextra->iconPixmap = 0; + + d->setWindowIcon_sys(); + d->setWindowIcon_helper(); +} + + +/*! + \property QWidget::windowIconText + \brief the widget's icon text + + This property only makes sense for windows. If no icon + text has been set, this functions returns an empty string. + + \sa windowIcon, windowTitle +*/ + +QString QWidget::windowIconText() const +{ + Q_D(const QWidget); + return (d->extra && d->extra->topextra) ? d->extra->topextra->iconText : QString(); +} + +/*! + \property QWidget::windowFilePath + \since 4.4 + \brief the file path associated with a widget + + This property only makes sense for windows. It associates a file path with + a window. If you set the file path, but have not set the window title, Qt + sets the window title to contain a string created using the following + components. + + On Mac OS X: + + \list + \o The file name of the specified path, obtained using QFileInfo::fileName(). + \endlist + + On Windows and X11: + + \list + \o The file name of the specified path, obtained using QFileInfo::fileName(). + \o An optional \c{*} character, if the \l windowModified property is set. + \o The \c{0x2014} unicode character, padded either side by spaces. + \o The application name, obtained from the application's + \l{QCoreApplication::}{applicationName} property. + \endlist + + If the window title is set at any point, then the window title takes precedence and + will be shown instead of the file path string. + + Additionally, on Mac OS X, this has an added benefit that it sets the + \l{http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/XHIGWindows/chapter_17_section_3.html}{proxy icon} + for the window, assuming that the file path exists. + + If no file path is set, this property contains an empty string. + + By default, this property contains an empty string. + + \sa windowTitle, windowIcon +*/ + +QString QWidget::windowFilePath() const +{ + Q_D(const QWidget); + return (d->extra && d->extra->topextra) ? d->extra->topextra->filePath : QString(); +} + +void QWidget::setWindowFilePath(const QString &filePath) +{ + if (filePath == windowFilePath()) + return; + + Q_D(QWidget); + + d->createTLExtra(); + d->extra->topextra->filePath = filePath; + d->setWindowFilePath_helper(filePath); +} + +void QWidgetPrivate::setWindowFilePath_helper(const QString &filePath) +{ + if (extra->topextra && extra->topextra->caption.isEmpty()) { +#ifdef Q_WS_MAC + setWindowTitle_helper(QFileInfo(filePath).fileName()); +#else + Q_Q(QWidget); + Q_UNUSED(filePath); + setWindowTitle_helper(q->windowTitle()); +#endif + } +#ifdef Q_WS_MAC + setWindowFilePath_sys(filePath); +#endif +} + +/*! + Returns the window's role, or an empty string. + + \sa windowIcon, windowTitle +*/ + +QString QWidget::windowRole() const +{ + Q_D(const QWidget); + return (d->extra && d->extra->topextra) ? d->extra->topextra->role : QString(); +} + +/*! + Sets the window's role to \a role. This only makes sense for + windows on X11. +*/ +void QWidget::setWindowRole(const QString &role) +{ +#if defined(Q_WS_X11) + Q_D(QWidget); + d->topData()->role = role; + d->setWindowRole(); +#else + Q_UNUSED(role) +#endif +} + +/*! + \property QWidget::mouseTracking + \brief whether mouse tracking is enabled for the widget + + If mouse tracking is disabled (the default), the widget only + receives mouse move events when at least one mouse button is + pressed while the mouse is being moved. + + If mouse tracking is enabled, the widget receives mouse move + events even if no buttons are pressed. + + \sa mouseMoveEvent() +*/ + + +/*! + Sets the widget's focus proxy to widget \a w. If \a w is 0, the + function resets this widget to have no focus proxy. + + Some widgets can "have focus", but create a child widget, such as + QLineEdit, to actually handle the focus. In this case, the widget + can set the line edit to be its focus proxy. + + setFocusProxy() sets the widget which will actually get focus when + "this widget" gets it. If there is a focus proxy, setFocus() and + hasFocus() operate on the focus proxy. + + \sa focusProxy() +*/ + +void QWidget::setFocusProxy(QWidget * w) +{ + Q_D(QWidget); + if (!w && !d->extra) + return; + + for (QWidget* fp = w; fp; fp = fp->focusProxy()) { + if (fp == this) { + qWarning("QWidget: %s (%s) already in focus proxy chain", metaObject()->className(), objectName().toLocal8Bit().constData()); + return; + } + } + + d->createExtra(); + d->extra->focus_proxy = w; +} + + +/*! + Returns the focus proxy, or 0 if there is no focus proxy. + + \sa setFocusProxy() +*/ + +QWidget * QWidget::focusProxy() const +{ + Q_D(const QWidget); + return d->extra ? (QWidget *)d->extra->focus_proxy : 0; +} + + +/*! + \property QWidget::focus + \brief whether this widget (or its focus proxy) has the keyboard + input focus + + By default, this property is false. + + \note Obtaining the value of this property for a widget is effectively equivalent + to checking whether QApplication::focusWidget() refers to the widget. + + \sa setFocus(), clearFocus(), setFocusPolicy(), QApplication::focusWidget() +*/ +bool QWidget::hasFocus() const +{ + const QWidget* w = this; + while (w->d_func()->extra && w->d_func()->extra->focus_proxy) + w = w->d_func()->extra->focus_proxy; + if (QWidget *window = w->window()) { +#ifndef QT_NO_GRAPHICSVIEW + QWExtra *e = window->d_func()->extra; + if (e && e->proxyWidget && e->proxyWidget->hasFocus() && window->focusWidget() == w) + return true; +#endif + } + return (QApplication::focusWidget() == w); +} + +/*! + Gives the keyboard input focus to this widget (or its focus + proxy) if this widget or one of its parents is the \link + isActiveWindow() active window\endlink. The \a reason argument will + be passed into any focus event sent from this function, it is used + to give an explanation of what caused the widget to get focus. + If the window is not active, the widget will be given the focus when + the window becomes active. + + First, a focus out event is sent to the focus widget (if any) to + tell it that it is about to lose the focus. Then a focus in event + is sent to this widget to tell it that it just received the focus. + (Nothing happens if the focus in and focus out widgets are the + same.) + + \note On embedded platforms, setFocus() will not cause an input panel + to be opened by the input method. If you want this to happen, you + have to send a QEvent::RequestSoftwareInputPanel event to the + widget yourself. + + setFocus() gives focus to a widget regardless of its focus policy, + but does not clear any keyboard grab (see grabKeyboard()). + + Be aware that if the widget is hidden, it will not accept focus + until it is shown. + + \warning If you call setFocus() in a function which may itself be + called from focusOutEvent() or focusInEvent(), you may get an + infinite recursion. + + \sa hasFocus(), clearFocus(), focusInEvent(), focusOutEvent(), + setFocusPolicy(), focusWidget(), QApplication::focusWidget(), grabKeyboard(), + grabMouse(), {Keyboard Focus}, QEvent::RequestSoftwareInputPanel +*/ + +void QWidget::setFocus(Qt::FocusReason reason) +{ + if (!isEnabled()) + return; + + QWidget *f = this; + while (f->d_func()->extra && f->d_func()->extra->focus_proxy) + f = f->d_func()->extra->focus_proxy; + + if (QApplication::focusWidget() == f +#if defined(Q_WS_WIN) + && GetFocus() == f->internalWinId() +#endif + ) + return; + +#ifndef QT_NO_GRAPHICSVIEW + QWidget *previousProxyFocus = 0; + if (QWExtra *topData = window()->d_func()->extra) { + if (topData->proxyWidget && topData->proxyWidget->hasFocus()) { + previousProxyFocus = topData->proxyWidget->widget()->focusWidget(); + if (previousProxyFocus && previousProxyFocus->focusProxy()) + previousProxyFocus = previousProxyFocus->focusProxy(); + if (previousProxyFocus == this && !topData->proxyWidget->d_func()->proxyIsGivingFocus) + return; + } + } +#endif + + QWidget *w = f; + if (isHidden()) { + while (w && w->isHidden()) { + w->d_func()->focus_child = f; + w = w->isWindow() ? 0 : w->parentWidget(); + } + } else { + while (w) { + w->d_func()->focus_child = f; + w = w->isWindow() ? 0 : w->parentWidget(); + } + } + +#ifndef QT_NO_GRAPHICSVIEW + // Update proxy state + if (QWExtra *topData = window()->d_func()->extra) { + if (topData->proxyWidget && !topData->proxyWidget->hasFocus()) { + topData->proxyWidget->d_func()->focusFromWidgetToProxy = 1; + topData->proxyWidget->setFocus(reason); + topData->proxyWidget->d_func()->focusFromWidgetToProxy = 0; + } + } +#endif + + if (f->isActiveWindow()) { + QApplicationPrivate::setFocusWidget(f, reason); +#ifndef QT_NO_ACCESSIBILITY +# ifdef Q_OS_WIN + // The negation of the condition in setFocus_sys + if (!(testAttribute(Qt::WA_WState_Created) && window()->windowType() != Qt::Popup && internalWinId())) + //setFocusWidget will already post a focus event for us (that the AT client receives) on Windows +# endif + QAccessible::updateAccessibility(f, 0, QAccessible::Focus); +#endif +#ifndef QT_NO_GRAPHICSVIEW + if (QWExtra *topData = window()->d_func()->extra) { + if (topData->proxyWidget) { + if (previousProxyFocus && previousProxyFocus != f) { + // Send event to self + QFocusEvent event(QEvent::FocusOut, reason); + QPointer<QWidget> that = previousProxyFocus; + QApplication::sendEvent(previousProxyFocus, &event); + if (that) + QApplication::sendEvent(that->style(), &event); + } + if (!isHidden()) { +#ifndef QT_NO_GRAPHICSVIEW + // Update proxy state + if (QWExtra *topData = window()->d_func()->extra) + if (topData->proxyWidget && topData->proxyWidget->hasFocus()) + topData->proxyWidget->d_func()->updateProxyInputMethodAcceptanceFromWidget(); +#endif + // Send event to self + QFocusEvent event(QEvent::FocusIn, reason); + QPointer<QWidget> that = f; + QApplication::sendEvent(f, &event); + if (that) + QApplication::sendEvent(that->style(), &event); + } + } + } +#endif + } +} + +/*! + \fn void QWidget::setFocus() + \overload + + Gives the keyboard input focus to this widget (or its focus + proxy) if this widget or one of its parents is the + \l{isActiveWindow()}{active window}. +*/ + +/*! + Takes keyboard input focus from the widget. + + If the widget has active focus, a \link focusOutEvent() focus out + event\endlink is sent to this widget to tell it that it is about + to lose the focus. + + This widget must enable focus setting in order to get the keyboard + input focus, i.e. it must call setFocusPolicy(). + + \sa hasFocus(), setFocus(), focusInEvent(), focusOutEvent(), + setFocusPolicy(), QApplication::focusWidget() +*/ + +void QWidget::clearFocus() +{ + QWidget *w = this; + while (w) { + if (w->d_func()->focus_child == this) + w->d_func()->focus_child = 0; + w = w->parentWidget(); + } +#ifndef QT_NO_GRAPHICSVIEW + QWExtra *topData = d_func()->extra; + if (topData && topData->proxyWidget) + topData->proxyWidget->clearFocus(); +#endif + + if (hasFocus()) { + // Update proxy state + QApplicationPrivate::setFocusWidget(0, Qt::OtherFocusReason); +#if defined(Q_WS_WIN) + if (!(windowType() == Qt::Popup) && GetFocus() == internalWinId()) + SetFocus(0); + else +#endif + { +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::Focus); +#endif + } + } +} + + +/*! + \fn bool QWidget::focusNextChild() + + Finds a new widget to give the keyboard focus to, as appropriate + for \key Tab, and returns true if it can find a new widget, or + false if it can't. + + \sa focusPreviousChild() +*/ + +/*! + \fn bool QWidget::focusPreviousChild() + + Finds a new widget to give the keyboard focus to, as appropriate + for \key Shift+Tab, and returns true if it can find a new widget, + or false if it can't. + + \sa focusNextChild() +*/ + +/*! + Finds a new widget to give the keyboard focus to, as appropriate + for Tab and Shift+Tab, and returns true if it can find a new + widget, or false if it can't. + + If \a next is true, this function searches forward, if \a next + is false, it searches backward. + + Sometimes, you will want to reimplement this function. For + example, a web browser might reimplement it to move its "current + active link" forward or backward, and call + focusNextPrevChild() only when it reaches the last or + first link on the "page". + + Child widgets call focusNextPrevChild() on their parent widgets, + but only the window that contains the child widgets decides where + to redirect focus. By reimplementing this function for an object, + you thus gain control of focus traversal for all child widgets. + + \sa focusNextChild(), focusPreviousChild() +*/ + +bool QWidget::focusNextPrevChild(bool next) +{ + Q_D(QWidget); + QWidget* p = parentWidget(); + bool isSubWindow = (windowType() == Qt::SubWindow); + if (!isWindow() && !isSubWindow && p) + return p->focusNextPrevChild(next); +#ifndef QT_NO_GRAPHICSVIEW + if (d->extra && d->extra->proxyWidget) + return d->extra->proxyWidget->focusNextPrevChild(next); +#endif + QWidget *w = QApplicationPrivate::focusNextPrevChild_helper(this, next); + if (!w) return false; + + w->setFocus(next ? Qt::TabFocusReason : Qt::BacktabFocusReason); + return true; +} + +/*! + Returns the last child of this widget that setFocus had been + called on. For top level widgets this is the widget that will get + focus in case this window gets activated + + This is not the same as QApplication::focusWidget(), which returns + the focus widget in the currently active window. +*/ + +QWidget *QWidget::focusWidget() const +{ + return const_cast<QWidget *>(d_func()->focus_child); +} + +/*! + Returns the next widget in this widget's focus chain. + + \sa previousInFocusChain() +*/ +QWidget *QWidget::nextInFocusChain() const +{ + return const_cast<QWidget *>(d_func()->focus_next); +} + +/*! + \brief The previousInFocusChain function returns the previous + widget in this widget's focus chain. + + \sa nextInFocusChain() + + \since 4.6 +*/ +QWidget *QWidget::previousInFocusChain() const +{ + return const_cast<QWidget *>(d_func()->focus_prev); +} + +/*! + \property QWidget::isActiveWindow + \brief whether this widget's window is the active window + + The active window is the window that contains the widget that has + keyboard focus (The window may still have focus if it has no + widgets or none of its widgets accepts keyboard focus). + + When popup windows are visible, this property is true for both the + active window \e and for the popup. + + By default, this property is false. + + \sa activateWindow(), QApplication::activeWindow() +*/ +bool QWidget::isActiveWindow() const +{ + QWidget *tlw = window(); + if(tlw == QApplication::activeWindow() || (isVisible() && (tlw->windowType() == Qt::Popup))) + return true; + +#ifndef QT_NO_GRAPHICSVIEW + if (QWExtra *tlwExtra = tlw->d_func()->extra) { + if (isVisible() && tlwExtra->proxyWidget) + return tlwExtra->proxyWidget->isActiveWindow(); + } +#endif + +#ifdef Q_WS_MAC + extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp + if(qt_mac_is_macdrawer(tlw) && + tlw->parentWidget() && tlw->parentWidget()->isActiveWindow()) + return true; + + extern bool qt_mac_insideKeyWindow(const QWidget *); //qwidget_mac.cpp + if (QApplication::testAttribute(Qt::AA_MacPluginApplication) && qt_mac_insideKeyWindow(tlw)) + return true; +#endif + if(style()->styleHint(QStyle::SH_Widget_ShareActivation, 0, this)) { + if(tlw->windowType() == Qt::Tool && + !tlw->isModal() && + (!tlw->parentWidget() || tlw->parentWidget()->isActiveWindow())) + return true; + QWidget *w = QApplication::activeWindow(); + while(w && tlw->windowType() == Qt::Tool && + !w->isModal() && w->parentWidget()) { + w = w->parentWidget()->window(); + if(w == tlw) + return true; + } + } +#if defined(Q_WS_WIN32) + HWND active = GetActiveWindow(); + if (!tlw->testAttribute(Qt::WA_WState_Created)) + return false; + return active == tlw->internalWinId() || ::IsChild(active, tlw->internalWinId()); +#else + return false; +#endif +} + +/*! + Puts the \a second widget after the \a first widget in the focus order. + + Note that since the tab order of the \a second widget is changed, you + should order a chain like this: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 9 + + \e not like this: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 10 + + If \a first or \a second has a focus proxy, setTabOrder() + correctly substitutes the proxy. + + \sa setFocusPolicy(), setFocusProxy(), {Keyboard Focus} +*/ +void QWidget::setTabOrder(QWidget* first, QWidget *second) +{ + if (!first || !second || first->focusPolicy() == Qt::NoFocus || second->focusPolicy() == Qt::NoFocus) + return; + + if (first->window() != second->window()) { + qWarning("QWidget::setTabOrder: 'first' and 'second' must be in the same window"); + return; + } + + QWidget *fp = first->focusProxy(); + if (fp) { + // If first is redirected, set first to the last child of first + // that can take keyboard focus so that second is inserted after + // that last child, and the focus order within first is (more + // likely to be) preserved. + QList<QWidget *> l = first->findChildren<QWidget *>(); + for (int i = l.size()-1; i >= 0; --i) { + QWidget * next = l.at(i); + if (next->window() == fp->window()) { + fp = next; + if (fp->focusPolicy() != Qt::NoFocus) + break; + } + } + first = fp; + } + + if (fp == second) + return; + + if (QWidget *sp = second->focusProxy()) + second = sp; + +// QWidget *fp = first->d_func()->focus_prev; + QWidget *fn = first->d_func()->focus_next; + + if (fn == second || first == second) + return; + + QWidget *sp = second->d_func()->focus_prev; + QWidget *sn = second->d_func()->focus_next; + + fn->d_func()->focus_prev = second; + first->d_func()->focus_next = second; + + second->d_func()->focus_next = fn; + second->d_func()->focus_prev = first; + + sp->d_func()->focus_next = sn; + sn->d_func()->focus_prev = sp; + + + Q_ASSERT(first->d_func()->focus_next->d_func()->focus_prev == first); + Q_ASSERT(first->d_func()->focus_prev->d_func()->focus_next == first); + + Q_ASSERT(second->d_func()->focus_next->d_func()->focus_prev == second); + Q_ASSERT(second->d_func()->focus_prev->d_func()->focus_next == second); +} + +/*!\internal + + Moves the relevant subwidgets of this widget from the \a oldtlw's + tab chain to that of the new parent, if there's anything to move and + we're really moving + + This function is called from QWidget::reparent() *after* the widget + has been reparented. + + \sa reparent() +*/ + +void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw) +{ + Q_Q(QWidget); + if (oldtlw == q->window()) + return; // nothing to do + + if(focus_child) + focus_child->clearFocus(); + + // separate the focus chain into new (children of myself) and old (the rest) + QWidget *firstOld = 0; + //QWidget *firstNew = q; //invariant + QWidget *o = 0; // last in the old list + QWidget *n = q; // last in the new list + + bool prevWasNew = true; + QWidget *w = focus_next; + + //Note: for efficiency, we do not maintain the list invariant inside the loop + //we append items to the relevant list, and we optimize by not changing pointers + //when subsequent items are going into the same list. + while (w != q) { + bool currentIsNew = q->isAncestorOf(w); + if (currentIsNew) { + if (!prevWasNew) { + //prev was old -- append to new list + n->d_func()->focus_next = w; + w->d_func()->focus_prev = n; + } + n = w; + } else { + if (prevWasNew) { + //prev was new -- append to old list, if there is one + if (o) { + o->d_func()->focus_next = w; + w->d_func()->focus_prev = o; + } else { + // "create" the old list + firstOld = w; + } + } + o = w; + } + w = w->d_func()->focus_next; + prevWasNew = currentIsNew; + } + + //repair the old list: + if (firstOld) { + o->d_func()->focus_next = firstOld; + firstOld->d_func()->focus_prev = o; + } + + if (!q->isWindow()) { + QWidget *topLevel = q->window(); + //insert new chain into toplevel's chain + + QWidget *prev = topLevel->d_func()->focus_prev; + + topLevel->d_func()->focus_prev = n; + prev->d_func()->focus_next = q; + + focus_prev = prev; + n->d_func()->focus_next = topLevel; + } else { + //repair the new list + n->d_func()->focus_next = q; + focus_prev = n; + } + +} + +/*!\internal + + Measures the shortest distance from a point to a rect. + + This function is called from QDesktopwidget::screen(QPoint) to find the + closest screen for a point. + In directional KeypadNavigation, it is called to find the closest + widget to the current focus widget center. +*/ +int QWidgetPrivate::pointToRect(const QPoint &p, const QRect &r) +{ + int dx = 0; + int dy = 0; + if (p.x() < r.left()) + dx = r.left() - p.x(); + else if (p.x() > r.right()) + dx = p.x() - r.right(); + if (p.y() < r.top()) + dy = r.top() - p.y(); + else if (p.y() > r.bottom()) + dy = p.y() - r.bottom(); + return dx + dy; +} + +/*! + \property QWidget::frameSize + \brief the size of the widget including any window frame + + By default, this property contains a value that depends on the user's + platform and screen geometry. +*/ +QSize QWidget::frameSize() const +{ + Q_D(const QWidget); + if (isWindow() && !(windowType() == Qt::Popup)) { + QRect fs = d->frameStrut(); + return QSize(data->crect.width() + fs.left() + fs.right(), + data->crect.height() + fs.top() + fs.bottom()); + } + return data->crect.size(); +} + +/*! \fn void QWidget::move(int x, int y) + + \overload + + This corresponds to move(QPoint(\a x, \a y)). +*/ + +void QWidget::move(const QPoint &p) +{ + Q_D(QWidget); + setAttribute(Qt::WA_Moved); + if (isWindow()) + d->topData()->posFromMove = true; + if (testAttribute(Qt::WA_WState_Created)) { + d->setGeometry_sys(p.x() + geometry().x() - QWidget::x(), + p.y() + geometry().y() - QWidget::y(), + width(), height(), true); + d->setDirtyOpaqueRegion(); + } else { + data->crect.moveTopLeft(p); // no frame yet + setAttribute(Qt::WA_PendingMoveEvent); + } +} + +/*! \fn void QWidget::resize(int w, int h) + \overload + + This corresponds to resize(QSize(\a w, \a h)). +*/ + +void QWidget::resize(const QSize &s) +{ + Q_D(QWidget); + setAttribute(Qt::WA_Resized); + if (testAttribute(Qt::WA_WState_Created)) { + d->setGeometry_sys(geometry().x(), geometry().y(), s.width(), s.height(), false); + d->setDirtyOpaqueRegion(); + } else { + data->crect.setSize(s.boundedTo(maximumSize()).expandedTo(minimumSize())); + setAttribute(Qt::WA_PendingResizeEvent); + } +} + +void QWidget::setGeometry(const QRect &r) +{ + Q_D(QWidget); + setAttribute(Qt::WA_Resized); + setAttribute(Qt::WA_Moved); + if (isWindow()) + d->topData()->posFromMove = false; + if (testAttribute(Qt::WA_WState_Created)) { + d->setGeometry_sys(r.x(), r.y(), r.width(), r.height(), true); + d->setDirtyOpaqueRegion(); + } else { + data->crect.setTopLeft(r.topLeft()); + data->crect.setSize(r.size().boundedTo(maximumSize()).expandedTo(minimumSize())); + setAttribute(Qt::WA_PendingMoveEvent); + setAttribute(Qt::WA_PendingResizeEvent); + } +} + +/*! + \since 4.2 + Saves the current geometry and state for top-level widgets. + + To save the geometry when the window closes, you can + implement a close event like this: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 11 + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + Use QMainWindow::saveState() to save the geometry and the state of + toolbars and dock widgets. + + \sa restoreGeometry(), QMainWindow::saveState(), QMainWindow::restoreState() +*/ +QByteArray QWidget::saveGeometry() const +{ +#ifdef QT_MAC_USE_COCOA + // We check if the window was maximized during this invocation. If so, we need to record the + // starting position as 0,0. + Q_D(const QWidget); + QRect newFramePosition = frameGeometry(); + QRect newNormalPosition = normalGeometry(); + if(d->topData()->wasMaximized && !(windowState() & Qt::WindowMaximized)) { + // Change the starting position + newFramePosition.moveTo(0, 0); + newNormalPosition.moveTo(0, 0); + } +#endif // QT_MAC_USE_COCOA + QByteArray array; + QDataStream stream(&array, QIODevice::WriteOnly); + stream.setVersion(QDataStream::Qt_4_0); + const quint32 magicNumber = 0x1D9D0CB; + quint16 majorVersion = 1; + quint16 minorVersion = 0; + stream << magicNumber + << majorVersion + << minorVersion +#ifdef QT_MAC_USE_COCOA + << newFramePosition + << newNormalPosition +#else + << frameGeometry() + << normalGeometry() +#endif // QT_MAC_USE_COCOA + << qint32(QApplication::desktop()->screenNumber(this)) + << quint8(windowState() & Qt::WindowMaximized) + << quint8(windowState() & Qt::WindowFullScreen); + return array; +} + +/*! + \since 4.2 + + Restores the geometry and state top-level widgets stored in the + byte array \a geometry. Returns true on success; otherwise + returns false. + + If the restored geometry is off-screen, it will be modified to be + inside the available screen geometry. + + To restore geometry saved using QSettings, you can use code like + this: + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 12 + + See the \l{Window Geometry} documentation for an overview of geometry + issues with windows. + + Use QMainWindow::restoreState() to restore the geometry and the + state of toolbars and dock widgets. + + \sa saveGeometry(), QSettings, QMainWindow::saveState(), QMainWindow::restoreState() +*/ +bool QWidget::restoreGeometry(const QByteArray &geometry) +{ + if (geometry.size() < 4) + return false; + QDataStream stream(geometry); + stream.setVersion(QDataStream::Qt_4_0); + + const quint32 magicNumber = 0x1D9D0CB; + quint32 storedMagicNumber; + stream >> storedMagicNumber; + if (storedMagicNumber != magicNumber) + return false; + + const quint16 currentMajorVersion = 1; + quint16 majorVersion = 0; + quint16 minorVersion = 0; + + stream >> majorVersion >> minorVersion; + + if (majorVersion != currentMajorVersion) + return false; + // (Allow all minor versions.) + + QRect restoredFrameGeometry; + QRect restoredNormalGeometry; + qint32 restoredScreenNumber; + quint8 maximized; + quint8 fullScreen; + + stream >> restoredFrameGeometry + >> restoredNormalGeometry + >> restoredScreenNumber + >> maximized + >> fullScreen; + + const int frameHeight = 20; + if (!restoredFrameGeometry.isValid()) + restoredFrameGeometry = QRect(QPoint(0,0), sizeHint()); + + if (!restoredNormalGeometry.isValid()) + restoredNormalGeometry = QRect(QPoint(0, frameHeight), sizeHint()); + if (!restoredNormalGeometry.isValid()) { + // use the widget's adjustedSize if the sizeHint() doesn't help + restoredNormalGeometry.setSize(restoredNormalGeometry + .size() + .expandedTo(d_func()->adjustedSize())); + } + + const QDesktopWidget * const desktop = QApplication::desktop(); + if (restoredScreenNumber >= desktop->numScreens()) + restoredScreenNumber = desktop->primaryScreen(); + + const QRect availableGeometry = desktop->availableGeometry(restoredScreenNumber); + + // Modify the restored geometry if we are about to restore to coordinates + // that would make the window "lost". This happens if: + // - The restored geometry is completely oustside the available geometry + // - The title bar is outside the available geometry. + // - (Mac only) The window is higher than the available geometry. It must + // be possible to bring the size grip on screen by moving the window. +#ifdef Q_WS_MAC + restoredFrameGeometry.setHeight(qMin(restoredFrameGeometry.height(), availableGeometry.height())); + restoredNormalGeometry.setHeight(qMin(restoredNormalGeometry.height(), availableGeometry.height() - frameHeight)); +#endif + + if (!restoredFrameGeometry.intersects(availableGeometry)) { + restoredFrameGeometry.moveBottom(qMin(restoredFrameGeometry.bottom(), availableGeometry.bottom())); + restoredFrameGeometry.moveLeft(qMax(restoredFrameGeometry.left(), availableGeometry.left())); + restoredFrameGeometry.moveRight(qMin(restoredFrameGeometry.right(), availableGeometry.right())); + } + restoredFrameGeometry.moveTop(qMax(restoredFrameGeometry.top(), availableGeometry.top())); + + if (!restoredNormalGeometry.intersects(availableGeometry)) { + restoredNormalGeometry.moveBottom(qMin(restoredNormalGeometry.bottom(), availableGeometry.bottom())); + restoredNormalGeometry.moveLeft(qMax(restoredNormalGeometry.left(), availableGeometry.left())); + restoredNormalGeometry.moveRight(qMin(restoredNormalGeometry.right(), availableGeometry.right())); + } + restoredNormalGeometry.moveTop(qMax(restoredNormalGeometry.top(), availableGeometry.top() + frameHeight)); + + if (maximized || fullScreen) { + // set geomerty before setting the window state to make + // sure the window is maximized to the right screen. + // Skip on windows: the window is restored into a broken + // half-maximized state. +#ifndef Q_WS_WIN + setGeometry(restoredNormalGeometry); +#endif + Qt::WindowStates ws = windowState(); + if (maximized) + ws |= Qt::WindowMaximized; + if (fullScreen) + ws |= Qt::WindowFullScreen; + setWindowState(ws); + d_func()->topData()->normalGeometry = restoredNormalGeometry; + } else { + QPoint offset; +#ifdef Q_WS_X11 + if (isFullScreen()) + offset = d_func()->topData()->fullScreenOffset; +#endif + setWindowState(windowState() & ~(Qt::WindowMaximized | Qt::WindowFullScreen)); + move(restoredFrameGeometry.topLeft() + offset); + resize(restoredNormalGeometry.size()); + } + return true; +} + +/*!\fn void QWidget::setGeometry(int x, int y, int w, int h) + \overload + + This corresponds to setGeometry(QRect(\a x, \a y, \a w, \a h)). +*/ + +/*! + Sets the margins around the contents of the widget to have the sizes + \a left, \a top, \a right, and \a bottom. The margins are used by + the layout system, and may be used by subclasses to specify the area + to draw in (e.g. excluding the frame). + + Changing the margins will trigger a resizeEvent(). + + \sa contentsRect(), getContentsMargins() +*/ +void QWidget::setContentsMargins(int left, int top, int right, int bottom) +{ + Q_D(QWidget); + if (left == d->leftmargin && top == d->topmargin + && right == d->rightmargin && bottom == d->bottommargin) + return; + d->leftmargin = left; + d->topmargin = top; + d->rightmargin = right; + d->bottommargin = bottom; + + if (QLayout *l=d->layout) + l->update(); //force activate; will do updateGeometry + else + updateGeometry(); + + // ### Qt 5: compat, remove + if (isVisible()) { + update(); + QResizeEvent e(data->crect.size(), data->crect.size()); + QApplication::sendEvent(this, &e); + } else { + setAttribute(Qt::WA_PendingResizeEvent, true); + } + + QEvent e(QEvent::ContentsRectChange); + QApplication::sendEvent(this, &e); +} + +/*! + \overload + \since 4.6 + + \brief The setContentsMargins function sets the margins around the + widget's contents. + + Sets the margins around the contents of the widget to have the + sizes determined by \a margins. The margins are + used by the layout system, and may be used by subclasses to + specify the area to draw in (e.g. excluding the frame). + + Changing the margins will trigger a resizeEvent(). + + \sa contentsRect(), getContentsMargins() +*/ +void QWidget::setContentsMargins(const QMargins &margins) +{ + setContentsMargins(margins.left(), margins.top(), + margins.right(), margins.bottom()); +} + +/*! + Returns the widget's contents margins for \a left, \a top, \a + right, and \a bottom. + + \sa setContentsMargins(), contentsRect() + */ +void QWidget::getContentsMargins(int *left, int *top, int *right, int *bottom) const +{ + Q_D(const QWidget); + if (left) + *left = d->leftmargin; + if (top) + *top = d->topmargin; + if (right) + *right = d->rightmargin; + if (bottom) + *bottom = d->bottommargin; +} + +/*! + \since 4.6 + + \brief The contentsMargins function returns the widget's contents margins. + + \sa getContentsMargins(), setContentsMargins(), contentsRect() + */ +QMargins QWidget::contentsMargins() const +{ + Q_D(const QWidget); + return QMargins(d->leftmargin, d->topmargin, d->rightmargin, d->bottommargin); +} + + +/*! + Returns the area inside the widget's margins. + + \sa setContentsMargins(), getContentsMargins() +*/ +QRect QWidget::contentsRect() const +{ + Q_D(const QWidget); + return QRect(QPoint(d->leftmargin, d->topmargin), + QPoint(data->crect.width() - 1 - d->rightmargin, + data->crect.height() - 1 - d->bottommargin)); + +} + + + +/*! + \fn void QWidget::customContextMenuRequested(const QPoint &pos) + + This signal is emitted when the widget's \l contextMenuPolicy is + Qt::CustomContextMenu, and the user has requested a context menu on + the widget. The position \a pos is the position of the context menu + event that the widget receives. Normally this is in widget + coordinates. The exception to this rule is QAbstractScrollArea and + its subclasses that map the context menu event to coordinates of the + \link QAbstractScrollArea::viewport() viewport() \endlink . + + + \sa mapToGlobal() QMenu contextMenuPolicy +*/ + + +/*! + \property QWidget::contextMenuPolicy + \brief how the widget shows a context menu + + The default value of this property is Qt::DefaultContextMenu, + which means the contextMenuEvent() handler is called. Other values + are Qt::NoContextMenu, Qt::PreventContextMenu, + Qt::ActionsContextMenu, and Qt::CustomContextMenu. With + Qt::CustomContextMenu, the signal customContextMenuRequested() is + emitted. + + \sa contextMenuEvent(), customContextMenuRequested(), actions() +*/ + +Qt::ContextMenuPolicy QWidget::contextMenuPolicy() const +{ + return (Qt::ContextMenuPolicy)data->context_menu_policy; +} + +void QWidget::setContextMenuPolicy(Qt::ContextMenuPolicy policy) +{ + data->context_menu_policy = (uint) policy; +} + +/*! + \property QWidget::focusPolicy + \brief the way the widget accepts keyboard focus + + The policy is Qt::TabFocus if the widget accepts keyboard + focus by tabbing, Qt::ClickFocus if the widget accepts + focus by clicking, Qt::StrongFocus if it accepts both, and + Qt::NoFocus (the default) if it does not accept focus at + all. + + You must enable keyboard focus for a widget if it processes + keyboard events. This is normally done from the widget's + constructor. For instance, the QLineEdit constructor calls + setFocusPolicy(Qt::StrongFocus). + + If the widget has a focus proxy, then the focus policy will + be propagated to it. + + \sa focusInEvent(), focusOutEvent(), keyPressEvent(), keyReleaseEvent(), enabled +*/ + + +Qt::FocusPolicy QWidget::focusPolicy() const +{ + return (Qt::FocusPolicy)data->focus_policy; +} + +void QWidget::setFocusPolicy(Qt::FocusPolicy policy) +{ + data->focus_policy = (uint) policy; + Q_D(QWidget); + if (d->extra && d->extra->focus_proxy) + d->extra->focus_proxy->setFocusPolicy(policy); +} + +/*! + \property QWidget::updatesEnabled + \brief whether updates are enabled + + An updates enabled widget receives paint events and has a system + background; a disabled widget does not. This also implies that + calling update() and repaint() has no effect if updates are + disabled. + + By default, this property is true. + + setUpdatesEnabled() is normally used to disable updates for a + short period of time, for instance to avoid screen flicker during + large changes. In Qt, widgets normally do not generate screen + flicker, but on X11 the server might erase regions on the screen + when widgets get hidden before they can be replaced by other + widgets. Disabling updates solves this. + + Example: + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 13 + + Disabling a widget implicitly disables all its children. Enabling a widget + enables all child widgets \e except top-level widgets or those that + have been explicitly disabled. Re-enabling updates implicitly calls + update() on the widget. + + \sa paintEvent() +*/ +void QWidget::setUpdatesEnabled(bool enable) +{ + Q_D(QWidget); + setAttribute(Qt::WA_ForceUpdatesDisabled, !enable); + d->setUpdatesEnabled_helper(enable); +} + +/*! \fn void QWidget::show() + + Shows the widget and its child widgets. This function is + equivalent to setVisible(true). + + \sa raise(), showEvent(), hide(), setVisible(), showMinimized(), showMaximized(), + showNormal(), isVisible() +*/ + + +/*! \internal + + Makes the widget visible in the isVisible() meaning of the word. + It is only called for toplevels or widgets with visible parents. + */ +void QWidgetPrivate::show_recursive() +{ + Q_Q(QWidget); + // polish if necessary + + if (!q->testAttribute(Qt::WA_WState_Created)) + createRecursively(); + q->ensurePolished(); + +#ifdef QT3_SUPPORT + if(sendChildEvents) + QApplication::sendPostedEvents(q, QEvent::ChildInserted); +#endif + if (!q->isWindow() && q->parentWidget()->d_func()->layout && !q->parentWidget()->data->in_show) + q->parentWidget()->d_func()->layout->activate(); + // activate our layout before we and our children become visible + if (layout) + layout->activate(); + + show_helper(); +} + +void QWidgetPrivate::sendPendingMoveAndResizeEvents(bool recursive, bool disableUpdates) +{ + Q_Q(QWidget); + + disableUpdates = disableUpdates && q->updatesEnabled(); + if (disableUpdates) + q->setAttribute(Qt::WA_UpdatesDisabled); + + if (q->testAttribute(Qt::WA_PendingMoveEvent)) { + QMoveEvent e(data.crect.topLeft(), data.crect.topLeft()); + QApplication::sendEvent(q, &e); + q->setAttribute(Qt::WA_PendingMoveEvent, false); + } + + if (q->testAttribute(Qt::WA_PendingResizeEvent)) { + QResizeEvent e(data.crect.size(), QSize()); + QApplication::sendEvent(q, &e); + q->setAttribute(Qt::WA_PendingResizeEvent, false); + } + + if (disableUpdates) + q->setAttribute(Qt::WA_UpdatesDisabled, false); + + if (!recursive) + return; + + for (int i = 0; i < children.size(); ++i) { + if (QWidget *child = qobject_cast<QWidget *>(children.at(i))) + child->d_func()->sendPendingMoveAndResizeEvents(recursive, disableUpdates); + } +} + +void QWidgetPrivate::activateChildLayoutsRecursively() +{ + sendPendingMoveAndResizeEvents(false, true); + + for (int i = 0; i < children.size(); ++i) { + QWidget *child = qobject_cast<QWidget *>(children.at(i)); + if (!child || child->isHidden() || child->isWindow()) + continue; + + child->ensurePolished(); + + // Activate child's layout + QWidgetPrivate *childPrivate = child->d_func(); + if (childPrivate->layout) + childPrivate->layout->activate(); + + // Pretend we're visible. + const bool wasVisible = child->isVisible(); + if (!wasVisible) + child->setAttribute(Qt::WA_WState_Visible); + + // Do the same for all my children. + childPrivate->activateChildLayoutsRecursively(); + + // We're not cheating anymore. + if (!wasVisible) + child->setAttribute(Qt::WA_WState_Visible, false); + } +} + +void QWidgetPrivate::show_helper() +{ + Q_Q(QWidget); + data.in_show = true; // qws optimization + // make sure we receive pending move and resize events + sendPendingMoveAndResizeEvents(); + + // become visible before showing all children + q->setAttribute(Qt::WA_WState_Visible); + + // finally show all children recursively + showChildren(false); + +#ifdef QT3_SUPPORT + if (q->parentWidget() && sendChildEvents) + QApplication::sendPostedEvents(q->parentWidget(), + QEvent::ChildInserted); +#endif + + + // popup handling: new popups and tools need to be raised, and + // existing popups must be closed. Also propagate the current + // windows's KeyboardFocusChange status. + if (q->isWindow()) { + if ((q->windowType() == Qt::Tool) || (q->windowType() == Qt::Popup) || q->windowType() == Qt::ToolTip) { + q->raise(); + if (q->parentWidget() && q->parentWidget()->window()->testAttribute(Qt::WA_KeyboardFocusChange)) + q->setAttribute(Qt::WA_KeyboardFocusChange); + } else { + while (QApplication::activePopupWidget()) { + if (!QApplication::activePopupWidget()->close()) + break; + } + } + } + + // Automatic embedding of child windows of widgets already embedded into + // QGraphicsProxyWidget when they are shown the first time. + bool isEmbedded = false; +#ifndef QT_NO_GRAPHICSVIEW + if (q->isWindow()) { + isEmbedded = q->graphicsProxyWidget() ? true : false; + if (!isEmbedded && !bypassGraphicsProxyWidget(q)) { + QGraphicsProxyWidget *ancestorProxy = nearestGraphicsProxyWidget(q->parentWidget()); + if (ancestorProxy) { + isEmbedded = true; + ancestorProxy->d_func()->embedSubWindow(q); + } + } + } +#else + Q_UNUSED(isEmbedded); +#endif + + // On Windows, show the popup now so that our own focus handling + // stores the correct old focus widget even if it's stolen in the + // showevent +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) || defined(Q_OS_SYMBIAN) + if (!isEmbedded && q->windowType() == Qt::Popup) + qApp->d_func()->openPopup(q); +#endif + + // send the show event before showing the window + QShowEvent showEvent; + QApplication::sendEvent(q, &showEvent); + + if (!isEmbedded && q->isModal() && q->isWindow()) + // QApplicationPrivate::enterModal *before* show, otherwise the initial + // stacking might be wrong + QApplicationPrivate::enterModal(q); + + + show_sys(); + +#if !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) + if (!isEmbedded && q->windowType() == Qt::Popup) + qApp->d_func()->openPopup(q); +#endif + +#ifndef QT_NO_ACCESSIBILITY + if (q->windowType() != Qt::ToolTip) // Tooltips are read aloud twice in MS narrator. + QAccessible::updateAccessibility(q, 0, QAccessible::ObjectShow); +#endif + + if (QApplicationPrivate::hidden_focus_widget == q) { + QApplicationPrivate::hidden_focus_widget = 0; + q->setFocus(Qt::OtherFocusReason); + } + + // Process events when showing a Qt::SplashScreen widget before the event loop + // is spinnning; otherwise it might not show up on particular platforms. + // This makes QSplashScreen behave the same on all platforms. + if (!qApp->d_func()->in_exec && q->windowType() == Qt::SplashScreen) + QApplication::processEvents(); + + data.in_show = false; // reset qws optimization +} + +/*! \fn void QWidget::hide() + + Hides the widget. This function is equivalent to + setVisible(false). + + + \note If you are working with QDialog or its subclasses and you invoke + the show() function after this function, the dialog will be displayed in + its original position. + + \sa hideEvent(), isHidden(), show(), setVisible(), isVisible(), close() +*/ + +/*!\internal + */ +void QWidgetPrivate::hide_helper() +{ + Q_Q(QWidget); + + bool isEmbedded = false; +#if !defined QT_NO_GRAPHICSVIEW + isEmbedded = q->isWindow() && !bypassGraphicsProxyWidget(q) && nearestGraphicsProxyWidget(q->parentWidget()) != 0; +#else + Q_UNUSED(isEmbedded); +#endif + + if (!isEmbedded && (q->windowType() == Qt::Popup)) + qApp->d_func()->closePopup(q); + + // Move test modal here. Otherwise, a modal dialog could get + // destroyed and we lose all access to its parent because we haven't + // left modality. (Eg. modal Progress Dialog) + if (!isEmbedded && q->isModal() && q->isWindow()) + QApplicationPrivate::leaveModal(q); + +#if defined(Q_WS_WIN) + if (q->isWindow() && !(q->windowType() == Qt::Popup) && q->parentWidget() + && !q->parentWidget()->isHidden() && q->isActiveWindow()) + q->parentWidget()->activateWindow(); // Activate parent +#endif + + q->setAttribute(Qt::WA_Mapped, false); + hide_sys(); + + bool wasVisible = q->testAttribute(Qt::WA_WState_Visible); + + if (wasVisible) { + q->setAttribute(Qt::WA_WState_Visible, false); + + } + + QHideEvent hideEvent; + QApplication::sendEvent(q, &hideEvent); + hideChildren(false); + + // next bit tries to move the focus if the focus widget is now + // hidden. + if (wasVisible) { +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined (Q_WS_QWS) || defined(Q_WS_MAC) || defined(Q_WS_QPA) + qApp->d_func()->sendSyntheticEnterLeave(q); +#endif + + QWidget *fw = QApplication::focusWidget(); + while (fw && !fw->isWindow()) { + if (fw == q) { + q->focusNextPrevChild(true); + break; + } + fw = fw->parentWidget(); + } + } + + if (QWidgetBackingStore *bs = maybeBackingStore()) + bs->removeDirtyWidget(q); + +#ifndef QT_NO_ACCESSIBILITY + if (wasVisible) + QAccessible::updateAccessibility(q, 0, QAccessible::ObjectHide); +#endif +} + +/*! + \fn bool QWidget::isHidden() const + + Returns true if the widget is hidden, otherwise returns false. + + A hidden widget will only become visible when show() is called on + it. It will not be automatically shown when the parent is shown. + + To check visibility, use !isVisible() instead (notice the exclamation mark). + + isHidden() implies !isVisible(), but a widget can be not visible + and not hidden at the same time. This is the case for widgets that are children of + widgets that are not visible. + + + Widgets are hidden if: + \list + \o they were created as independent windows, + \o they were created as children of visible widgets, + \o hide() or setVisible(false) was called. + \endlist +*/ + + +void QWidget::setVisible(bool visible) +{ + if (visible) { // show + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) + return; + + Q_D(QWidget); + + // Designer uses a trick to make grabWidget work without showing + if (!isWindow() && parentWidget() && parentWidget()->isVisible() + && !parentWidget()->testAttribute(Qt::WA_WState_Created)) + parentWidget()->window()->d_func()->createRecursively(); + + //we have to at least create toplevels before applyX11SpecificCommandLineArguments + //but not children of non-visible parents + QWidget *pw = parentWidget(); + if (!testAttribute(Qt::WA_WState_Created) + && (isWindow() || pw->testAttribute(Qt::WA_WState_Created))) { + create(); + } + +#if defined(Q_WS_X11) + if (windowType() == Qt::Window) + QApplicationPrivate::applyX11SpecificCommandLineArguments(this); +#elif defined(Q_WS_QWS) + if (windowType() == Qt::Window) + QApplicationPrivate::applyQWSSpecificCommandLineArguments(this); +#endif + + bool wasResized = testAttribute(Qt::WA_Resized); + Qt::WindowStates initialWindowState = windowState(); + + // polish if necessary + ensurePolished(); + + // remember that show was called explicitly + setAttribute(Qt::WA_WState_ExplicitShowHide); + // whether we need to inform the parent widget immediately + bool needUpdateGeometry = !isWindow() && testAttribute(Qt::WA_WState_Hidden); + // we are no longer hidden + setAttribute(Qt::WA_WState_Hidden, false); + + if (needUpdateGeometry) + d->updateGeometry_helper(true); + +#ifdef QT3_SUPPORT + QApplication::sendPostedEvents(this, QEvent::ChildInserted); +#endif + // activate our layout before we and our children become visible + if (d->layout) + d->layout->activate(); + + if (!isWindow()) { + QWidget *parent = parentWidget(); + while (parent && parent->isVisible() && parent->d_func()->layout && !parent->data->in_show) { + parent->d_func()->layout->activate(); + if (parent->isWindow()) + break; + parent = parent->parentWidget(); + } + if (parent) + parent->d_func()->setDirtyOpaqueRegion(); + } + + // adjust size if necessary + if (!wasResized + && (isWindow() || !parentWidget()->d_func()->layout)) { + if (isWindow()) { + adjustSize(); + if (windowState() != initialWindowState) + setWindowState(initialWindowState); + } else { + adjustSize(); + } + setAttribute(Qt::WA_Resized, false); + } + + setAttribute(Qt::WA_KeyboardFocusChange, false); + + if (isWindow() || parentWidget()->isVisible()) { + // remove posted quit events when showing a new window + QCoreApplication::removePostedEvents(qApp, QEvent::Quit); + + d->show_helper(); + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined (Q_WS_QWS) || defined(Q_WS_MAC) || defined(Q_WS_QPA) + qApp->d_func()->sendSyntheticEnterLeave(this); +#endif + } + + QEvent showToParentEvent(QEvent::ShowToParent); + QApplication::sendEvent(this, &showToParentEvent); + } else { // hide + if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) + return; +#if defined(Q_WS_WIN) + // reset WS_DISABLED style in a Blocked window + if(isWindow() && testAttribute(Qt::WA_WState_Created) + && QApplicationPrivate::isBlockedByModal(this)) + { + LONG dwStyle = GetWindowLong(winId(), GWL_STYLE); + dwStyle &= ~WS_DISABLED; + SetWindowLong(winId(), GWL_STYLE, dwStyle); + } +#endif + if (QApplicationPrivate::hidden_focus_widget == this) + QApplicationPrivate::hidden_focus_widget = 0; + + Q_D(QWidget); + + // hw: The test on getOpaqueRegion() needs to be more intelligent + // currently it doesn't work if the widget is hidden (the region will + // be clipped). The real check should be testing the cached region + // (and dirty flag) directly. + if (!isWindow() && parentWidget()) // && !d->getOpaqueRegion().isEmpty()) + parentWidget()->d_func()->setDirtyOpaqueRegion(); + + setAttribute(Qt::WA_WState_Hidden); + setAttribute(Qt::WA_WState_ExplicitShowHide); + if (testAttribute(Qt::WA_WState_Created)) + d->hide_helper(); + + // invalidate layout similar to updateGeometry() + if (!isWindow() && parentWidget()) { + if (parentWidget()->d_func()->layout) + parentWidget()->d_func()->layout->invalidate(); + else if (parentWidget()->isVisible()) + QApplication::postEvent(parentWidget(), new QEvent(QEvent::LayoutRequest)); + } + + QEvent hideToParentEvent(QEvent::HideToParent); + QApplication::sendEvent(this, &hideToParentEvent); + } +} + +/*!\fn void QWidget::setHidden(bool hidden) + + Convenience function, equivalent to setVisible(!\a hidden). +*/ + +/*!\fn void QWidget::setShown(bool shown) + + Use setVisible(\a shown) instead. +*/ + + +void QWidgetPrivate::_q_showIfNotHidden() +{ + Q_Q(QWidget); + if ( !(q->isHidden() && q->testAttribute(Qt::WA_WState_ExplicitShowHide)) ) + q->setVisible(true); +} + +void QWidgetPrivate::showChildren(bool spontaneous) +{ + QList<QObject*> childList = children; + for (int i = 0; i < childList.size(); ++i) { + QWidget *widget = qobject_cast<QWidget*>(childList.at(i)); + if (!widget + || widget->isWindow() + || widget->testAttribute(Qt::WA_WState_Hidden)) + continue; + if (spontaneous) { + widget->setAttribute(Qt::WA_Mapped); + widget->d_func()->showChildren(true); + QShowEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + } else { + if (widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) + widget->d_func()->show_recursive(); + else + widget->show(); + } + } +} + +void QWidgetPrivate::hideChildren(bool spontaneous) +{ + QList<QObject*> childList = children; + for (int i = 0; i < childList.size(); ++i) { + QWidget *widget = qobject_cast<QWidget*>(childList.at(i)); + if (!widget || widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden)) + continue; +#ifdef QT_MAC_USE_COCOA + // Before doing anything we need to make sure that we don't leave anything in a non-consistent state. + // When hiding a widget we need to make sure that no mouse_down events are active, because + // the mouse_up event will never be received by a hidden widget or one of its descendants. + // The solution is simple, before going through with this we check if there are any mouse_down events in + // progress, if so we check if it is related to this widget or not. If so, we just reset the mouse_down and + // then we continue. + // In X11 and Windows we send a mouse_release event, however we don't do that here because we were already + // ignoring that from before. I.e. Carbon did not send the mouse release event, so we will not send the + // mouse release event. There are two ways to interpret this: + // 1. If we don't send the mouse release event, the widget might get into an inconsistent state, i.e. it + // might be waiting for a release event that will never arrive. + // 2. If we send the mouse release event, then the widget might decide to trigger an action that is not + // supposed to trigger because it is not visible. + if(widget == qt_button_down) + qt_button_down = 0; +#endif // QT_MAC_USE_COCOA + if (spontaneous) + widget->setAttribute(Qt::WA_Mapped, false); + else + widget->setAttribute(Qt::WA_WState_Visible, false); + widget->d_func()->hideChildren(spontaneous); + QHideEvent e; + if (spontaneous) { + QApplication::sendSpontaneousEvent(widget, &e); + } else { + QApplication::sendEvent(widget, &e); + if (widget->internalWinId() + && widget->testAttribute(Qt::WA_DontCreateNativeAncestors)) { + // hide_sys() on an ancestor won't have any affect on this + // widget, so it needs an explicit hide_sys() of its own + widget->d_func()->hide_sys(); + } + } +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined (Q_WS_QWS) || defined(Q_WS_MAC) || defined(Q_WS_QPA) + qApp->d_func()->sendSyntheticEnterLeave(widget); +#endif +#ifndef QT_NO_ACCESSIBILITY + if (!spontaneous) + QAccessible::updateAccessibility(widget, 0, QAccessible::ObjectHide); +#endif + } +} + +bool QWidgetPrivate::close_helper(CloseMode mode) +{ + if (data.is_closing) + return true; + + Q_Q(QWidget); + data.is_closing = 1; + + QPointer<QWidget> that = q; + QPointer<QWidget> parentWidget = q->parentWidget(); + +#ifdef QT3_SUPPORT + bool isMain = (QApplicationPrivate::main_widget == q); +#endif + bool quitOnClose = q->testAttribute(Qt::WA_QuitOnClose); + if (mode != CloseNoEvent) { + QCloseEvent e; + if (mode == CloseWithSpontaneousEvent) + QApplication::sendSpontaneousEvent(q, &e); + else + QApplication::sendEvent(q, &e); + if (!that.isNull() && !e.isAccepted()) { + data.is_closing = 0; + return false; + } + } + + if (!that.isNull() && !q->isHidden()) + q->hide(); + +#ifdef QT3_SUPPORT + if (isMain) + QApplication::quit(); +#endif + // Attempt to close the application only if this has WA_QuitOnClose set and a non-visible parent + quitOnClose = quitOnClose && (parentWidget.isNull() || !parentWidget->isVisible()); + + if (quitOnClose) { + /* if there is no non-withdrawn primary window left (except + the ones without QuitOnClose), we emit the lastWindowClosed + signal */ + QWidgetList list = QApplication::topLevelWidgets(); + bool lastWindowClosed = true; + for (int i = 0; i < list.size(); ++i) { + QWidget *w = list.at(i); + if (!w->isVisible() || w->parentWidget() || !w->testAttribute(Qt::WA_QuitOnClose)) + continue; + lastWindowClosed = false; + break; + } + if (lastWindowClosed) + QApplicationPrivate::emitLastWindowClosed(); + } + + if (!that.isNull()) { + data.is_closing = 0; + if (q->testAttribute(Qt::WA_DeleteOnClose)) { + q->setAttribute(Qt::WA_DeleteOnClose, false); + q->deleteLater(); + } + } + return true; +} + + +/*! + Closes this widget. Returns true if the widget was closed; + otherwise returns false. + + First it sends the widget a QCloseEvent. The widget is \link + hide() hidden\endlink if it \link QCloseEvent::accept() + accepts\endlink the close event. If it \link QCloseEvent::ignore() + ignores\endlink the event, nothing happens. The default + implementation of QWidget::closeEvent() accepts the close event. + + If the widget has the Qt::WA_DeleteOnClose flag, the widget + is also deleted. A close events is delivered to the widget no + matter if the widget is visible or not. + + The \l QApplication::lastWindowClosed() signal is emitted when the + last visible primary window (i.e. window with no parent) with the + Qt::WA_QuitOnClose attribute set is closed. By default this + attribute is set for all widgets except transient windows such as + splash screens, tool windows, and popup menus. + +*/ + +bool QWidget::close() +{ + return d_func()->close_helper(QWidgetPrivate::CloseWithEvent); +} + +/*! + \property QWidget::visible + \brief whether the widget is visible + + Calling setVisible(true) or show() sets the widget to visible + status if all its parent widgets up to the window are visible. If + an ancestor is not visible, the widget won't become visible until + all its ancestors are shown. If its size or position has changed, + Qt guarantees that a widget gets move and resize events just + before it is shown. If the widget has not been resized yet, Qt + will adjust the widget's size to a useful default using + adjustSize(). + + Calling setVisible(false) or hide() hides a widget explicitly. An + explicitly hidden widget will never become visible, even if all + its ancestors become visible, unless you show it. + + A widget receives show and hide events when its visibility status + changes. Between a hide and a show event, there is no need to + waste CPU cycles preparing or displaying information to the user. + A video application, for example, might simply stop generating new + frames. + + A widget that happens to be obscured by other windows on the + screen is considered to be visible. The same applies to iconified + windows and windows that exist on another virtual + desktop (on platforms that support this concept). A widget + receives spontaneous show and hide events when its mapping status + is changed by the window system, e.g. a spontaneous hide event + when the user minimizes the window, and a spontaneous show event + when the window is restored again. + + You almost never have to reimplement the setVisible() function. If + you need to change some settings before a widget is shown, use + showEvent() instead. If you need to do some delayed initialization + use the Polish event delivered to the event() function. + + \sa show(), hide(), isHidden(), isVisibleTo(), isMinimized(), + showEvent(), hideEvent() +*/ + + +/*! + Returns true if this widget would become visible if \a ancestor is + shown; otherwise returns false. + + The true case occurs if neither the widget itself nor any parent + up to but excluding \a ancestor has been explicitly hidden. + + This function will still return true if the widget is obscured by + other windows on the screen, but could be physically visible if it + or they were to be moved. + + isVisibleTo(0) is identical to isVisible(). + + \sa show() hide() isVisible() +*/ + +bool QWidget::isVisibleTo(QWidget* ancestor) const +{ + if (!ancestor) + return isVisible(); + const QWidget * w = this; + while (!w->isHidden() + && !w->isWindow() + && w->parentWidget() + && w->parentWidget() != ancestor) + w = w->parentWidget(); + return !w->isHidden(); +} + +#ifdef QT3_SUPPORT +/*! + Use visibleRegion() instead. +*/ +QRect QWidget::visibleRect() const +{ + return d_func()->clipRect(); +} +#endif + +/*! + Returns the unobscured region where paint events can occur. + + For visible widgets, this is an approximation of the area not + covered by other widgets; otherwise, this is an empty region. + + The repaint() function calls this function if necessary, so in + general you do not need to call it. + +*/ +QRegion QWidget::visibleRegion() const +{ + Q_D(const QWidget); + + QRect clipRect = d->clipRect(); + if (clipRect.isEmpty()) + return QRegion(); + QRegion r(clipRect); + d->subtractOpaqueChildren(r, clipRect); + d->subtractOpaqueSiblings(r); +#ifdef Q_WS_QWS + const QWSWindowSurface *surface = static_cast<const QWSWindowSurface*>(windowSurface()); + if (surface) { + const QPoint offset = mapTo(surface->window(), QPoint()); + r &= surface->clipRegion().translated(-offset); + } +#endif + return r; +} + + +QSize QWidgetPrivate::adjustedSize() const +{ + Q_Q(const QWidget); + + QSize s = q->sizeHint(); + + if (q->isWindow()) { + Qt::Orientations exp; + if (layout) { + if (layout->hasHeightForWidth()) + s.setHeight(layout->totalHeightForWidth(s.width())); + exp = layout->expandingDirections(); + } else + { + if (q->sizePolicy().hasHeightForWidth()) + s.setHeight(q->heightForWidth(s.width())); + exp = q->sizePolicy().expandingDirections(); + } + if (exp & Qt::Horizontal) + s.setWidth(qMax(s.width(), 200)); + if (exp & Qt::Vertical) + s.setHeight(qMax(s.height(), 100)); +#if defined(Q_WS_X11) + QRect screen = QApplication::desktop()->screenGeometry(q->x11Info().screen()); +#else // all others + QRect screen = QApplication::desktop()->screenGeometry(q->pos()); +#endif +#if defined (Q_WS_WINCE) || defined (Q_OS_SYMBIAN) + s.setWidth(qMin(s.width(), screen.width())); + s.setHeight(qMin(s.height(), screen.height())); +#else + s.setWidth(qMin(s.width(), screen.width()*2/3)); + s.setHeight(qMin(s.height(), screen.height()*2/3)); +#endif + if (QTLWExtra *extra = maybeTopData()) + extra->sizeAdjusted = true; + } + + if (!s.isValid()) { + QRect r = q->childrenRect(); // get children rectangle + if (r.isNull()) + return s; + s = r.size() + QSize(2 * r.x(), 2 * r.y()); + } + + return s; +} + +/*! + Adjusts the size of the widget to fit its contents. + + This function uses sizeHint() if it is valid, i.e., the size hint's width + and height are \>= 0. Otherwise, it sets the size to the children + rectangle that covers all child widgets (the union of all child widget + rectangles). + + For windows, the screen size is also taken into account. If the sizeHint() + is less than (200, 100) and the size policy is \l{QSizePolicy::Expanding} + {expanding}, the window will be at least (200, 100). The maximum size of + a window is 2/3 of the screen's width and height. + + \sa sizeHint(), childrenRect() +*/ + +void QWidget::adjustSize() +{ + Q_D(QWidget); + ensurePolished(); + QSize s = d->adjustedSize(); + + if (d->layout) + d->layout->activate(); + + if (s.isValid()) + resize(s); +} + + +/*! + \property QWidget::sizeHint + \brief the recommended size for the widget + + If the value of this property is an invalid size, no size is + recommended. + + The default implementation of sizeHint() returns an invalid size + if there is no layout for this widget, and returns the layout's + preferred size otherwise. + + \sa QSize::isValid(), minimumSizeHint(), sizePolicy(), + setMinimumSize(), updateGeometry() +*/ + +QSize QWidget::sizeHint() const +{ + Q_D(const QWidget); + if (d->layout) + return d->layout->totalSizeHint(); + return QSize(-1, -1); +} + +/*! + \property QWidget::minimumSizeHint + \brief the recommended minimum size for the widget + + If the value of this property is an invalid size, no minimum size + is recommended. + + The default implementation of minimumSizeHint() returns an invalid + size if there is no layout for this widget, and returns the + layout's minimum size otherwise. Most built-in widgets reimplement + minimumSizeHint(). + + \l QLayout will never resize a widget to a size smaller than the + minimum size hint unless minimumSize() is set or the size policy is + set to QSizePolicy::Ignore. If minimumSize() is set, the minimum + size hint will be ignored. + + \sa QSize::isValid(), resize(), setMinimumSize(), sizePolicy() +*/ +QSize QWidget::minimumSizeHint() const +{ + Q_D(const QWidget); + if (d->layout) + return d->layout->totalMinimumSize(); + return QSize(-1, -1); +} + + +/*! + \fn QWidget *QWidget::parentWidget() const + + Returns the parent of this widget, or 0 if it does not have any + parent widget. +*/ + + +/*! + Returns true if this widget is a parent, (or grandparent and so on + to any level), of the given \a child, and both widgets are within + the same window; otherwise returns false. +*/ + +bool QWidget::isAncestorOf(const QWidget *child) const +{ + while (child) { + if (child == this) + return true; + if (child->isWindow()) + return false; + child = child->parentWidget(); + } + return false; +} + +#if defined(Q_WS_WIN) +inline void setDisabledStyle(QWidget *w, bool setStyle) +{ + // set/reset WS_DISABLED style. + if(w && w->isWindow() && w->isVisible() && w->isEnabled()) { + LONG dwStyle = GetWindowLong(w->winId(), GWL_STYLE); + LONG newStyle = dwStyle; + if (setStyle) + newStyle |= WS_DISABLED; + else + newStyle &= ~WS_DISABLED; + if (newStyle != dwStyle) { + SetWindowLong(w->winId(), GWL_STYLE, newStyle); + // we might need to repaint in some situations (eg. menu) + w->repaint(); + } + } +} +#endif + +/***************************************************************************** + QWidget event handling + *****************************************************************************/ + +/*! + This is the main event handler; it handles event \a event. You can + reimplement this function in a subclass, but we recommend using + one of the specialized event handlers instead. + + Key press and release events are treated differently from other + events. event() checks for Tab and Shift+Tab and tries to move the + focus appropriately. If there is no widget to move the focus to + (or the key press is not Tab or Shift+Tab), event() calls + keyPressEvent(). + + Mouse and tablet event handling is also slightly special: only + when the widget is \l enabled, event() will call the specialized + handlers such as mousePressEvent(); otherwise it will discard the + event. + + This function returns true if the event was recognized, otherwise + it returns false. If the recognized event was accepted (see \l + QEvent::accepted), any further processing such as event + propagation to the parent widget stops. + + \sa closeEvent(), focusInEvent(), focusOutEvent(), enterEvent(), + keyPressEvent(), keyReleaseEvent(), leaveEvent(), + mouseDoubleClickEvent(), mouseMoveEvent(), mousePressEvent(), + mouseReleaseEvent(), moveEvent(), paintEvent(), resizeEvent(), + QObject::event(), QObject::timerEvent() +*/ + +bool QWidget::event(QEvent *event) +{ + Q_D(QWidget); + + // ignore mouse events when disabled + if (!isEnabled()) { + switch(event->type()) { + case QEvent::TabletPress: + case QEvent::TabletRelease: + case QEvent::TabletMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::ContextMenu: +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: +#endif + return false; + default: + break; + } + } + switch (event->type()) { + case QEvent::MouseMove: + mouseMoveEvent((QMouseEvent*)event); + break; + + case QEvent::MouseButtonPress: + // Don't reset input context here. Whether reset or not is + // a responsibility of input method. reset() will be + // called by mouseHandler() of input method if necessary + // via mousePressEvent() of text widgets. +#if 0 + resetInputContext(); +#endif + mousePressEvent((QMouseEvent*)event); + break; + + case QEvent::MouseButtonRelease: + mouseReleaseEvent((QMouseEvent*)event); + break; + + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent((QMouseEvent*)event); + break; +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: + wheelEvent((QWheelEvent*)event); + break; +#endif +#ifndef QT_NO_TABLETEVENT + case QEvent::TabletMove: + case QEvent::TabletPress: + case QEvent::TabletRelease: + tabletEvent((QTabletEvent*)event); + break; +#endif +#ifdef QT3_SUPPORT + case QEvent::Accel: + event->ignore(); + return false; +#endif + case QEvent::KeyPress: { + QKeyEvent *k = (QKeyEvent *)event; + bool res = false; + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + if (k->key() == Qt::Key_Backtab + || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) + res = focusNextPrevChild(false); + else if (k->key() == Qt::Key_Tab) + res = focusNextPrevChild(true); + if (res) + break; + } + keyPressEvent(k); +#ifdef QT_KEYPAD_NAVIGATION + if (!k->isAccepted() && QApplication::keypadNavigationEnabled() + && !(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::ShiftModifier))) { + if (QApplication::navigationMode() == Qt::NavigationModeKeypadTabOrder) { + if (k->key() == Qt::Key_Up) + res = focusNextPrevChild(false); + else if (k->key() == Qt::Key_Down) + res = focusNextPrevChild(true); + } else if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional) { + if (k->key() == Qt::Key_Up) + res = QWidgetPrivate::navigateToDirection(QWidgetPrivate::DirectionNorth); + else if (k->key() == Qt::Key_Right) + res = QWidgetPrivate::navigateToDirection(QWidgetPrivate::DirectionEast); + else if (k->key() == Qt::Key_Down) + res = QWidgetPrivate::navigateToDirection(QWidgetPrivate::DirectionSouth); + else if (k->key() == Qt::Key_Left) + res = QWidgetPrivate::navigateToDirection(QWidgetPrivate::DirectionWest); + } + if (res) { + k->accept(); + break; + } + } +#endif +#ifndef QT_NO_WHATSTHIS + if (!k->isAccepted() + && k->modifiers() & Qt::ShiftModifier && k->key() == Qt::Key_F1 + && d->whatsThis.size()) { + QWhatsThis::showText(mapToGlobal(inputMethodQuery(Qt::ImMicroFocus).toRect().center()), d->whatsThis, this); + k->accept(); + } +#endif + } + break; + + case QEvent::KeyRelease: + keyReleaseEvent((QKeyEvent*)event); + // fall through + case QEvent::ShortcutOverride: + break; + + case QEvent::InputMethod: + inputMethodEvent((QInputMethodEvent *) event); + break; + + case QEvent::PolishRequest: + ensurePolished(); + break; + + case QEvent::Polish: { + style()->polish(this); + setAttribute(Qt::WA_WState_Polished); + if (!QApplication::font(this).isCopyOf(QApplication::font())) + d->resolveFont(); + if (!QApplication::palette(this).isCopyOf(QApplication::palette())) + d->resolvePalette(); +#ifdef QT3_SUPPORT + if(d->sendChildEvents) + QApplication::sendPostedEvents(this, QEvent::ChildInserted); +#endif + } + break; + + case QEvent::ApplicationWindowIconChange: + if (isWindow() && !testAttribute(Qt::WA_SetWindowIcon)) { + d->setWindowIcon_sys(); + d->setWindowIcon_helper(); + } + break; + case QEvent::FocusIn: +#ifdef QT_SOFTKEYS_ENABLED + QSoftKeyManager::updateSoftKeys(); +#endif + focusInEvent((QFocusEvent*)event); + break; + + case QEvent::FocusOut: + focusOutEvent((QFocusEvent*)event); + break; + + case QEvent::Enter: +#ifndef QT_NO_STATUSTIP + if (d->statusTip.size()) { + QStatusTipEvent tip(d->statusTip); + QApplication::sendEvent(const_cast<QWidget *>(this), &tip); + } +#endif + enterEvent(event); + break; + + case QEvent::Leave: +#ifndef QT_NO_STATUSTIP + if (d->statusTip.size()) { + QString empty; + QStatusTipEvent tip(empty); + QApplication::sendEvent(const_cast<QWidget *>(this), &tip); + } +#endif + leaveEvent(event); + break; + + case QEvent::HoverEnter: + case QEvent::HoverLeave: + update(); + break; + + case QEvent::Paint: + // At this point the event has to be delivered, regardless + // whether the widget isVisible() or not because it + // already went through the filters + paintEvent((QPaintEvent*)event); + break; + + case QEvent::Move: + moveEvent((QMoveEvent*)event); + break; + + case QEvent::Resize: + resizeEvent((QResizeEvent*)event); + break; + + case QEvent::Close: + closeEvent((QCloseEvent *)event); + break; + +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: + switch (data->context_menu_policy) { + case Qt::PreventContextMenu: + break; + case Qt::DefaultContextMenu: + contextMenuEvent(static_cast<QContextMenuEvent *>(event)); + break; + case Qt::CustomContextMenu: + emit customContextMenuRequested(static_cast<QContextMenuEvent *>(event)->pos()); + break; +#ifndef QT_NO_MENU + case Qt::ActionsContextMenu: + if (d->actions.count()) { + QMenu::exec(d->actions, static_cast<QContextMenuEvent *>(event)->globalPos(), + 0, this); + break; + } + // fall through +#endif + default: + event->ignore(); + break; + } + break; +#endif // QT_NO_CONTEXTMENU + +#ifndef QT_NO_DRAGANDDROP + case QEvent::Drop: + dropEvent((QDropEvent*) event); + break; + + case QEvent::DragEnter: + dragEnterEvent((QDragEnterEvent*) event); + break; + + case QEvent::DragMove: + dragMoveEvent((QDragMoveEvent*) event); + break; + + case QEvent::DragLeave: + dragLeaveEvent((QDragLeaveEvent*) event); + break; +#endif + + case QEvent::Show: + showEvent((QShowEvent*) event); + break; + + case QEvent::Hide: + hideEvent((QHideEvent*) event); + break; + + case QEvent::ShowWindowRequest: + if (!isHidden()) + d->show_sys(); + break; + + case QEvent::ApplicationFontChange: + d->resolveFont(); + break; + case QEvent::ApplicationPaletteChange: + if (!(windowType() == Qt::Desktop)) + d->resolvePalette(); + break; + + case QEvent::ToolBarChange: + case QEvent::ActivationChange: + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::WindowTitleChange: + case QEvent::IconTextChange: + case QEvent::ModifiedChange: + case QEvent::MouseTrackingChange: + case QEvent::ParentChange: + case QEvent::WindowStateChange: + case QEvent::LocaleChange: + case QEvent::MacSizeChange: + case QEvent::ContentsRectChange: + changeEvent(event); + break; + + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: { +#ifdef QT3_SUPPORT + windowActivationChange(event->type() != QEvent::WindowActivate); +#endif + if (isVisible() && !palette().isEqual(QPalette::Active, QPalette::Inactive)) + update(); + QList<QObject*> childList = d->children; + for (int i = 0; i < childList.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(childList.at(i)); + if (w && w->isVisible() && !w->isWindow()) + QApplication::sendEvent(w, event); + } + +#ifdef QT_SOFTKEYS_ENABLED + if (isWindow()) + QSoftKeyManager::updateSoftKeys(); +#endif + + break; } + + case QEvent::LanguageChange: +#ifdef QT3_SUPPORT + languageChange(); +#endif + changeEvent(event); + { + QList<QObject*> childList = d->children; + for (int i = 0; i < childList.size(); ++i) { + QObject *o = childList.at(i); + if (o) + QApplication::sendEvent(o, event); + } + } + update(); + break; + + case QEvent::ApplicationLayoutDirectionChange: + d->resolveLayoutDirection(); + break; + + case QEvent::LayoutDirectionChange: + if (d->layout) + d->layout->invalidate(); + update(); + changeEvent(event); + break; + case QEvent::UpdateRequest: + d->syncBackingStore(); + break; + case QEvent::UpdateLater: + update(static_cast<QUpdateLaterEvent*>(event)->region()); + break; + + case QEvent::WindowBlocked: + case QEvent::WindowUnblocked: + { + QList<QObject*> childList = d->children; + for (int i = 0; i < childList.size(); ++i) { + QObject *o = childList.at(i); + if (o && o != QApplication::activeModalWidget()) { + if (qobject_cast<QWidget *>(o) && static_cast<QWidget *>(o)->isWindow()) { + // do not forward the event to child windows, + // QApplication does this for us + continue; + } + QApplication::sendEvent(o, event); + } + } +#if defined(Q_WS_WIN) + setDisabledStyle(this, (event->type() == QEvent::WindowBlocked)); +#endif + } + break; +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: + if (!d->toolTip.isEmpty()) + QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), d->toolTip, this); + else + event->ignore(); + break; +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::WhatsThis: + if (d->whatsThis.size()) + QWhatsThis::showText(static_cast<QHelpEvent *>(event)->globalPos(), d->whatsThis, this); + else + event->ignore(); + break; + case QEvent::QueryWhatsThis: + if (d->whatsThis.isEmpty()) + event->ignore(); + break; +#endif +#ifndef QT_NO_ACCESSIBILITY + case QEvent::AccessibilityDescription: + case QEvent::AccessibilityHelp: { + QAccessibleEvent *ev = static_cast<QAccessibleEvent *>(event); + if (ev->child()) + return false; + switch (ev->type()) { +#ifndef QT_NO_TOOLTIP + case QEvent::AccessibilityDescription: + ev->setValue(d->toolTip); + break; +#endif +#ifndef QT_NO_WHATSTHIS + case QEvent::AccessibilityHelp: + ev->setValue(d->whatsThis); + break; +#endif + default: + return false; + } + break; } +#endif + case QEvent::EmbeddingControl: + d->topData()->frameStrut.setCoords(0 ,0, 0, 0); + data->fstrut_dirty = false; +#if defined(Q_WS_WIN) || defined(Q_WS_X11) + d->topData()->embedded = 1; +#endif + break; +#ifndef QT_NO_ACTION + case QEvent::ActionAdded: + case QEvent::ActionRemoved: + case QEvent::ActionChanged: +#ifdef QT_SOFTKEYS_ENABLED + QSoftKeyManager::updateSoftKeys(); +#endif + actionEvent((QActionEvent*)event); + break; +#endif + + case QEvent::KeyboardLayoutChange: + { + changeEvent(event); + + // inform children of the change + QList<QObject*> childList = d->children; + for (int i = 0; i < childList.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(childList.at(i)); + if (w && w->isVisible() && !w->isWindow()) + QApplication::sendEvent(w, event); + } + break; + } +#ifdef Q_WS_MAC + case QEvent::MacGLWindowChange: + d->needWindowChange = false; + break; +#endif + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { +#ifndef Q_WS_MAC + QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event); + const QTouchEvent::TouchPoint &touchPoint = touchEvent->touchPoints().first(); + if (touchPoint.isPrimary() || touchEvent->deviceType() == QTouchEvent::TouchPad) + break; + + // fake a mouse event! + QEvent::Type eventType = QEvent::None; + switch (touchEvent->type()) { + case QEvent::TouchBegin: + eventType = QEvent::MouseButtonPress; + break; + case QEvent::TouchUpdate: + eventType = QEvent::MouseMove; + break; + case QEvent::TouchEnd: + eventType = QEvent::MouseButtonRelease; + break; + default: + Q_ASSERT(!true); + break; + } + if (eventType == QEvent::None) + break; + + QMouseEvent mouseEvent(eventType, + touchPoint.pos().toPoint(), + touchPoint.screenPos().toPoint(), + Qt::LeftButton, + Qt::LeftButton, + touchEvent->modifiers()); + (void) QApplication::sendEvent(this, &mouseEvent); +#endif // Q_WS_MAC + break; + } +#ifndef QT_NO_GESTURES + case QEvent::Gesture: + event->ignore(); + break; +#endif +#ifndef QT_NO_PROPERTIES + case QEvent::DynamicPropertyChange: { + const QByteArray &propName = static_cast<QDynamicPropertyChangeEvent *>(event)->propertyName(); + if (!qstrncmp(propName, "_q_customDpi", 12) && propName.length() == 13) { + uint value = property(propName.constData()).toUInt(); + if (!d->extra) + d->createExtra(); + const char axis = propName.at(12); + if (axis == 'X') + d->extra->customDpiX = value; + else if (axis == 'Y') + d->extra->customDpiY = value; + d->updateFont(d->data.fnt); + } + // fall through + } +#endif + default: + return QObject::event(event); + } + return true; +} + +/*! + This event handler can be reimplemented to handle state changes. + + The state being changed in this event can be retrieved through the \a event + supplied. + + Change events include: QEvent::ToolBarChange, + QEvent::ActivationChange, QEvent::EnabledChange, QEvent::FontChange, + QEvent::StyleChange, QEvent::PaletteChange, + QEvent::WindowTitleChange, QEvent::IconTextChange, + QEvent::ModifiedChange, QEvent::MouseTrackingChange, + QEvent::ParentChange, QEvent::WindowStateChange, + QEvent::LanguageChange, QEvent::LocaleChange, + QEvent::LayoutDirectionChange. + +*/ +void QWidget::changeEvent(QEvent * event) +{ + switch(event->type()) { + case QEvent::EnabledChange: + update(); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::updateAccessibility(this, 0, QAccessible::StateChanged); +#endif + break; + + case QEvent::FontChange: + case QEvent::StyleChange: { + Q_D(QWidget); + update(); + updateGeometry(); + if (d->layout) + d->layout->invalidate(); +#ifdef Q_WS_QWS + if (isWindow()) + d->data.fstrut_dirty = true; +#endif + break; + } + + case QEvent::PaletteChange: + update(); + break; + +#ifdef Q_WS_MAC + case QEvent::MacSizeChange: + updateGeometry(); + break; + case QEvent::ToolTipChange: + case QEvent::MouseTrackingChange: + qt_mac_update_mouseTracking(this); + break; +#endif + + default: + break; + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive mouse move events for the widget. + + If mouse tracking is switched off, mouse move events only occur if + a mouse button is pressed while the mouse is being moved. If mouse + tracking is switched on, mouse move events occur even if no mouse + button is pressed. + + QMouseEvent::pos() reports the position of the mouse cursor, + relative to this widget. For press and release events, the + position is usually the same as the position of the last mouse + move event, but it might be different if the user's hand shakes. + This is a feature of the underlying window system, not Qt. + + If you want to show a tooltip immediately, while the mouse is + moving (e.g., to get the mouse coordinates with QMouseEvent::pos() + and show them as a tooltip), you must first enable mouse tracking + as described above. Then, to ensure that the tooltip is updated + immediately, you must call QToolTip::showText() instead of + setToolTip() in your implementation of mouseMoveEvent(). + + \sa setMouseTracking(), mousePressEvent(), mouseReleaseEvent(), + mouseDoubleClickEvent(), event(), QMouseEvent, {Scribble Example} +*/ + +void QWidget::mouseMoveEvent(QMouseEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive mouse press events for the widget. + + If you create new widgets in the mousePressEvent() the + mouseReleaseEvent() may not end up where you expect, depending on + the underlying window system (or X11 window manager), the widgets' + location and maybe more. + + The default implementation implements the closing of popup widgets + when you click outside the window. For other widget types it does + nothing. + + \sa mouseReleaseEvent(), mouseDoubleClickEvent(), + mouseMoveEvent(), event(), QMouseEvent, {Scribble Example} +*/ + +void QWidget::mousePressEvent(QMouseEvent *event) +{ + event->ignore(); + if ((windowType() == Qt::Popup)) { + event->accept(); + QWidget* w; + while ((w = QApplication::activePopupWidget()) && w != this){ + w->close(); + if (QApplication::activePopupWidget() == w) // widget does not want to disappear + w->hide(); // hide at least + } + if (!rect().contains(event->pos())){ + close(); + } + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive mouse release events for the widget. + + \sa mousePressEvent(), mouseDoubleClickEvent(), + mouseMoveEvent(), event(), QMouseEvent, {Scribble Example} +*/ + +void QWidget::mouseReleaseEvent(QMouseEvent *event) +{ + event->ignore(); +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive mouse double click events for the widget. + + The default implementation generates a normal mouse press event. + + \note The widget will also receive mouse press and mouse release + events in addition to the double click event. It is up to the + developer to ensure that the application interprets these events + correctly. + + \sa mousePressEvent(), mouseReleaseEvent() mouseMoveEvent(), + event(), QMouseEvent +*/ + +void QWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + mousePressEvent(event); // try mouse press event +} + +#ifndef QT_NO_WHEELEVENT +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive wheel events for the widget. + + If you reimplement this handler, it is very important that you + \link QWheelEvent ignore()\endlink the event if you do not handle + it, so that the widget's parent can interpret it. + + The default implementation ignores the event. + + \sa QWheelEvent::ignore(), QWheelEvent::accept(), event(), + QWheelEvent +*/ + +void QWidget::wheelEvent(QWheelEvent *event) +{ + event->ignore(); +} +#endif // QT_NO_WHEELEVENT + +#ifndef QT_NO_TABLETEVENT +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive tablet events for the widget. + + If you reimplement this handler, it is very important that you + \link QTabletEvent ignore()\endlink the event if you do not handle + it, so that the widget's parent can interpret it. + + The default implementation ignores the event. + + \sa QTabletEvent::ignore(), QTabletEvent::accept(), event(), + QTabletEvent +*/ + +void QWidget::tabletEvent(QTabletEvent *event) +{ + event->ignore(); +} +#endif // QT_NO_TABLETEVENT + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive key press events for the widget. + + A widget must call setFocusPolicy() to accept focus initially and + have focus in order to receive a key press event. + + If you reimplement this handler, it is very important that you + call the base class implementation if you do not act upon the key. + + The default implementation closes popup widgets if the user + presses Esc. Otherwise the event is ignored, so that the widget's + parent can interpret it. + + Note that QKeyEvent starts with isAccepted() == true, so you do not + need to call QKeyEvent::accept() - just do not call the base class + implementation if you act upon the key. + + \sa keyReleaseEvent(), setFocusPolicy(), + focusInEvent(), focusOutEvent(), event(), QKeyEvent, {Tetrix Example} +*/ + +void QWidget::keyPressEvent(QKeyEvent *event) +{ + if ((windowType() == Qt::Popup) && event->key() == Qt::Key_Escape) { + event->accept(); + close(); + } else { + event->ignore(); + } +} + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive key release events for the widget. + + A widget must \link setFocusPolicy() accept focus\endlink + initially and \link hasFocus() have focus\endlink in order to + receive a key release event. + + If you reimplement this handler, it is very important that you + call the base class implementation if you do not act upon the key. + + The default implementation ignores the event, so that the widget's + parent can interpret it. + + Note that QKeyEvent starts with isAccepted() == true, so you do not + need to call QKeyEvent::accept() - just do not call the base class + implementation if you act upon the key. + + \sa keyPressEvent(), QKeyEvent::ignore(), setFocusPolicy(), + focusInEvent(), focusOutEvent(), event(), QKeyEvent +*/ + +void QWidget::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +/*! + \fn void QWidget::focusInEvent(QFocusEvent *event) + + This event handler can be reimplemented in a subclass to receive + keyboard focus events (focus received) for the widget. The event + is passed in the \a event parameter + + A widget normally must setFocusPolicy() to something other than + Qt::NoFocus in order to receive focus events. (Note that the + application programmer can call setFocus() on any widget, even + those that do not normally accept focus.) + + The default implementation updates the widget (except for windows + that do not specify a focusPolicy()). + + \sa focusOutEvent(), setFocusPolicy(), keyPressEvent(), + keyReleaseEvent(), event(), QFocusEvent +*/ + +void QWidget::focusInEvent(QFocusEvent *) +{ + if (focusPolicy() != Qt::NoFocus || !isWindow()) { + update(); + } +} + +/*! + \fn void QWidget::focusOutEvent(QFocusEvent *event) + + This event handler can be reimplemented in a subclass to receive + keyboard focus events (focus lost) for the widget. The events is + passed in the \a event parameter. + + A widget normally must setFocusPolicy() to something other than + Qt::NoFocus in order to receive focus events. (Note that the + application programmer can call setFocus() on any widget, even + those that do not normally accept focus.) + + The default implementation updates the widget (except for windows + that do not specify a focusPolicy()). + + \sa focusInEvent(), setFocusPolicy(), keyPressEvent(), + keyReleaseEvent(), event(), QFocusEvent +*/ + +void QWidget::focusOutEvent(QFocusEvent *) +{ + if (focusPolicy() != Qt::NoFocus || !isWindow()) + update(); +} + +/*! + \fn void QWidget::enterEvent(QEvent *event) + + This event handler can be reimplemented in a subclass to receive + widget enter events which are passed in the \a event parameter. + + An event is sent to the widget when the mouse cursor enters the + widget. + + \sa leaveEvent(), mouseMoveEvent(), event() +*/ + +void QWidget::enterEvent(QEvent *) +{ +} + +/*! + \fn void QWidget::leaveEvent(QEvent *event) + + This event handler can be reimplemented in a subclass to receive + widget leave events which are passed in the \a event parameter. + + A leave event is sent to the widget when the mouse cursor leaves + the widget. + + \sa enterEvent(), mouseMoveEvent(), event() +*/ + +void QWidget::leaveEvent(QEvent *) +{ +} + +/*! + \fn void QWidget::paintEvent(QPaintEvent *event) + + This event handler can be reimplemented in a subclass to receive paint + events passed in \a event. + + A paint event is a request to repaint all or part of a widget. It can + happen for one of the following reasons: + + \list + \o repaint() or update() was invoked, + \o the widget was obscured and has now been uncovered, or + \o many other reasons. + \endlist + + Many widgets can simply repaint their entire surface when asked to, but + some slow widgets need to optimize by painting only the requested region: + QPaintEvent::region(). This speed optimization does not change the result, + as painting is clipped to that region during event processing. QListView + and QTableView do this, for example. + + Qt also tries to speed up painting by merging multiple paint events into + one. When update() is called several times or the window system sends + several paint events, Qt merges these events into one event with a larger + region (see QRegion::united()). The repaint() function does not permit this + optimization, so we suggest using update() whenever possible. + + When the paint event occurs, the update region has normally been erased, so + you are painting on the widget's background. + + The background can be set using setBackgroundRole() and setPalette(). + + Since Qt 4.0, QWidget automatically double-buffers its painting, so there + is no need to write double-buffering code in paintEvent() to avoid flicker. + + \bold{Note for the X11 platform}: It is possible to toggle global double + buffering by calling \c qt_x11_set_global_double_buffer(). For example, + + \snippet doc/src/snippets/code/src_gui_kernel_qwidget.cpp 14 + + \note Generally, you should refrain from calling update() or repaint() + \bold{inside} a paintEvent(). For example, calling update() or repaint() on + children inside a paintevent() results in undefined behavior; the child may + or may not get a paint event. + + \warning If you are using a custom paint engine without Qt's backingstore, + Qt::WA_PaintOnScreen must be set. Otherwise, QWidget::paintEngine() will + never be called; the backingstore will be used instead. + + \sa event(), repaint(), update(), QPainter, QPixmap, QPaintEvent, + {Analog Clock Example} +*/ + +void QWidget::paintEvent(QPaintEvent *) +{ +} + + +/*! + \fn void QWidget::moveEvent(QMoveEvent *event) + + This event handler can be reimplemented in a subclass to receive + widget move events which are passed in the \a event parameter. + When the widget receives this event, it is already at the new + position. + + The old position is accessible through QMoveEvent::oldPos(). + + \sa resizeEvent(), event(), move(), QMoveEvent +*/ + +void QWidget::moveEvent(QMoveEvent *) +{ +} + + +/*! + This event handler can be reimplemented in a subclass to receive + widget resize events which are passed in the \a event parameter. + When resizeEvent() is called, the widget already has its new + geometry. The old size is accessible through + QResizeEvent::oldSize(). + + The widget will be erased and receive a paint event immediately + after processing the resize event. No drawing need be (or should + be) done inside this handler. + + + \sa moveEvent(), event(), resize(), QResizeEvent, paintEvent(), + {Scribble Example} +*/ + +void QWidget::resizeEvent(QResizeEvent * /* event */) +{ +} + +#ifndef QT_NO_ACTION +/*! + \fn void QWidget::actionEvent(QActionEvent *event) + + This event handler is called with the given \a event whenever the + widget's actions are changed. + + \sa addAction(), insertAction(), removeAction(), actions(), QActionEvent +*/ +void QWidget::actionEvent(QActionEvent *) +{ + +} +#endif + +/*! + This event handler is called with the given \a event when Qt receives a window + close request for a top-level widget from the window system. + + By default, the event is accepted and the widget is closed. You can reimplement + this function to change the way the widget responds to window close requests. + For example, you can prevent the window from closing by calling \l{QEvent::}{ignore()} + on all events. + + Main window applications typically use reimplementations of this function to check + whether the user's work has been saved and ask for permission before closing. + For example, the \l{Application Example} uses a helper function to determine whether + or not to close the window: + + \snippet mainwindows/application/mainwindow.cpp 3 + \snippet mainwindows/application/mainwindow.cpp 4 + + \sa event(), hide(), close(), QCloseEvent, {Application Example} +*/ + +void QWidget::closeEvent(QCloseEvent *event) +{ + event->accept(); +} + +#ifndef QT_NO_CONTEXTMENU +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive widget context menu events. + + The handler is called when the widget's \l contextMenuPolicy is + Qt::DefaultContextMenu. + + The default implementation ignores the context event. + See the \l QContextMenuEvent documentation for more details. + + \sa event(), QContextMenuEvent customContextMenuRequested() +*/ + +void QWidget::contextMenuEvent(QContextMenuEvent *event) +{ + event->ignore(); +} +#endif // QT_NO_CONTEXTMENU + + +/*! + This event handler, for event \a event, can be reimplemented in a + subclass to receive Input Method composition events. This handler + is called when the state of the input method changes. + + Note that when creating custom text editing widgets, the + Qt::WA_InputMethodEnabled window attribute must be set explicitly + (using the setAttribute() function) in order to receive input + method events. + + The default implementation calls event->ignore(), which rejects the + Input Method event. See the \l QInputMethodEvent documentation for more + details. + + \sa event(), QInputMethodEvent +*/ +void QWidget::inputMethodEvent(QInputMethodEvent *event) +{ + event->ignore(); +} + +/*! + This method is only relevant for input widgets. It is used by the + input method to query a set of properties of the widget to be + able to support complex input method operations as support for + surrounding text and reconversions. + + \a query specifies which property is queried. + + \sa inputMethodEvent(), QInputMethodEvent, QInputContext, inputMethodHints +*/ +QVariant QWidget::inputMethodQuery(Qt::InputMethodQuery query) const +{ + switch(query) { + case Qt::ImMicroFocus: + return QRect(width()/2, 0, 1, height()); + case Qt::ImFont: + return font(); + case Qt::ImAnchorPosition: + // Fallback. + return inputMethodQuery(Qt::ImCursorPosition); + default: + return QVariant(); + } +} + +/*! + \property QWidget::inputMethodHints + \brief What input method specific hints the widget has. + + This is only relevant for input widgets. It is used by + the input method to retrieve hints as to how the input method + should operate. For example, if the Qt::ImhFormattedNumbersOnly flag + is set, the input method may change its visual components to reflect + that only numbers can be entered. + + \note The flags are only hints, so the particular input method + implementation is free to ignore them. If you want to be + sure that a certain type of characters are entered, + you should also set a QValidator on the widget. + + The default value is Qt::ImhNone. + + \since 4.6 + + \sa inputMethodQuery(), QInputContext +*/ +Qt::InputMethodHints QWidget::inputMethodHints() const +{ +#ifndef QT_NO_IM + const QWidgetPrivate *priv = d_func(); + while (priv->inheritsInputMethodHints) { + priv = priv->q_func()->parentWidget()->d_func(); + Q_ASSERT(priv); + } + return priv->imHints; +#else //QT_NO_IM + return 0; +#endif //QT_NO_IM +} + +void QWidget::setInputMethodHints(Qt::InputMethodHints hints) +{ +#ifndef QT_NO_IM + Q_D(QWidget); + d->imHints = hints; + // Optimization to update input context only it has already been created. + if (d->ic || qApp->d_func()->inputContext) { + QInputContext *ic = inputContext(); + if (ic) + ic->update(); + } +#endif //QT_NO_IM +} + + +#ifndef QT_NO_DRAGANDDROP + +/*! + \fn void QWidget::dragEnterEvent(QDragEnterEvent *event) + + This event handler is called when a drag is in progress and the + mouse enters this widget. The event is passed in the \a event parameter. + + If the event is ignored, the widget won't receive any \l{dragMoveEvent()}{drag + move events}. + + See the \link dnd.html Drag-and-drop documentation\endlink for an + overview of how to provide drag-and-drop in your application. + + \sa QDrag, QDragEnterEvent +*/ +void QWidget::dragEnterEvent(QDragEnterEvent *) +{ +} + +/*! + \fn void QWidget::dragMoveEvent(QDragMoveEvent *event) + + This event handler is called if a drag is in progress, and when + any of the following conditions occur: the cursor enters this widget, + the cursor moves within this widget, or a modifier key is pressed on + the keyboard while this widget has the focus. The event is passed + in the \a event parameter. + + See the \link dnd.html Drag-and-drop documentation\endlink for an + overview of how to provide drag-and-drop in your application. + + \sa QDrag, QDragMoveEvent +*/ +void QWidget::dragMoveEvent(QDragMoveEvent *) +{ +} + +/*! + \fn void QWidget::dragLeaveEvent(QDragLeaveEvent *event) + + This event handler is called when a drag is in progress and the + mouse leaves this widget. The event is passed in the \a event + parameter. + + See the \link dnd.html Drag-and-drop documentation\endlink for an + overview of how to provide drag-and-drop in your application. + + \sa QDrag, QDragLeaveEvent +*/ +void QWidget::dragLeaveEvent(QDragLeaveEvent *) +{ +} + +/*! + \fn void QWidget::dropEvent(QDropEvent *event) + + This event handler is called when the drag is dropped on this + widget. The event is passed in the \a event parameter. + + See the \link dnd.html Drag-and-drop documentation\endlink for an + overview of how to provide drag-and-drop in your application. + + \sa QDrag, QDropEvent +*/ +void QWidget::dropEvent(QDropEvent *) +{ +} + +#endif // QT_NO_DRAGANDDROP + +/*! + \fn void QWidget::showEvent(QShowEvent *event) + + This event handler can be reimplemented in a subclass to receive + widget show events which are passed in the \a event parameter. + + Non-spontaneous show events are sent to widgets immediately + before they are shown. The spontaneous show events of windows are + delivered afterwards. + + Note: A widget receives spontaneous show and hide events when its + mapping status is changed by the window system, e.g. a spontaneous + hide event when the user minimizes the window, and a spontaneous + show event when the window is restored again. After receiving a + spontaneous hide event, a widget is still considered visible in + the sense of isVisible(). + + \sa visible, event(), QShowEvent +*/ +void QWidget::showEvent(QShowEvent *) +{ +} + +/*! + \fn void QWidget::hideEvent(QHideEvent *event) + + This event handler can be reimplemented in a subclass to receive + widget hide events. The event is passed in the \a event parameter. + + Hide events are sent to widgets immediately after they have been + hidden. + + Note: A widget receives spontaneous show and hide events when its + mapping status is changed by the window system, e.g. a spontaneous + hide event when the user minimizes the window, and a spontaneous + show event when the window is restored again. After receiving a + spontaneous hide event, a widget is still considered visible in + the sense of isVisible(). + + \sa visible, event(), QHideEvent +*/ +void QWidget::hideEvent(QHideEvent *) +{ +} + +/* + \fn QWidget::x11Event(MSG *) + + This special event handler can be reimplemented in a subclass to receive + native X11 events. + + In your reimplementation of this function, if you want to stop Qt from + handling the event, return true. If you return false, this native event + is passed back to Qt, which translates it into a Qt event and sends it to + the widget. + + \note Events are only delivered to this event handler if the widget is + native. + + \warning This function is not portable. + + \sa QApplication::x11EventFilter(), QWidget::winId() +*/ + + +#if defined(Q_WS_MAC) + +/*! + \fn bool QWidget::macEvent(EventHandlerCallRef caller, EventRef event) + + This special event handler can be reimplemented in a subclass to + receive native Macintosh events. + + The parameters are a bit different depending if Qt is build against Carbon + or Cocoa. In Carbon, \a caller and \a event are the corresponding + EventHandlerCallRef and EventRef that correspond to the Carbon event + handlers that are installed. In Cocoa, \a caller is always 0 and the + EventRef is the EventRef generated from the NSEvent. + + In your reimplementation of this function, if you want to stop the + event being handled by Qt, return true. If you return false, this + native event is passed back to Qt, which translates the event into + a Qt event and sends it to the widget. + + \warning This function is not portable. + + \warning This function was not called inside of Qt until Qt 4.4. + If you need compatibility with earlier versions of Qt, consider QApplication::macEventFilter() instead. + + \sa QApplication::macEventFilter() +*/ + +bool QWidget::macEvent(EventHandlerCallRef, EventRef) +{ + return false; +} + +#endif +#if defined(Q_WS_WIN) + +/*! + This special event handler can be reimplemented in a subclass to + receive native Windows events which are passed in the \a message + parameter. + + In your reimplementation of this function, if you want to stop the + event being handled by Qt, return true and set \a result to the value + that the window procedure should return. If you return false, this + native event is passed back to Qt, which translates the event into + a Qt event and sends it to the widget. + + \warning This function is not portable. + + \sa QApplication::winEventFilter() +*/ +bool QWidget::winEvent(MSG *message, long *result) +{ + Q_UNUSED(message); + Q_UNUSED(result); + return false; +} + +#endif +#if defined(Q_WS_X11) + +/*! + \fn bool QWidget::x11Event(XEvent *event) + + This special event handler can be reimplemented in a subclass to receive + native X11 events passed in the \a event parameter. + + In your reimplementation of this function, if you want to stop Qt from + handling the event, return true. If you return false, this native event + is passed back to Qt, which translates it into a Qt event and sends it to + the widget. + + \note Events are only delivered to this event handler if the widget is + native. + + \warning This function is not portable. + + \sa QApplication::x11EventFilter(), QWidget::winId() +*/ +bool QWidget::x11Event(XEvent *) +{ + return false; +} + +#endif +#if defined(Q_WS_QWS) + +/*! + \fn bool QWidget::qwsEvent(QWSEvent *event) + + This special event handler can be reimplemented in a subclass to + receive native Qt for Embedded Linux events which are passed in the + \a event parameter. + + In your reimplementation of this function, if you want to stop the + event being handled by Qt, return true. If you return false, this + native event is passed back to Qt, which translates the event into + a Qt event and sends it to the widget. + + \warning This function is not portable. + + \sa QApplication::qwsEventFilter() +*/ +bool QWidget::qwsEvent(QWSEvent *) +{ + return false; +} + +#endif + + +/*! + Ensures that the widget has been polished by QStyle (i.e., has a + proper font and palette). + + QWidget calls this function after it has been fully constructed + but before it is shown the very first time. You can call this + function if you want to ensure that the widget is polished before + doing an operation, e.g., the correct font size might be needed in + the widget's sizeHint() reimplementation. Note that this function + \e is called from the default implementation of sizeHint(). + + Polishing is useful for final initialization that must happen after + all constructors (from base classes as well as from subclasses) + have been called. + + If you need to change some settings when a widget is polished, + reimplement event() and handle the QEvent::Polish event type. + + \bold{Note:} The function is declared const so that it can be called from + other const functions (e.g., sizeHint()). + + \sa event() +*/ +void QWidget::ensurePolished() const +{ + Q_D(const QWidget); + + const QMetaObject *m = metaObject(); + if (m == d->polished) + return; + d->polished = m; + + QEvent e(QEvent::Polish); + QCoreApplication::sendEvent(const_cast<QWidget *>(this), &e); + + // polish children after 'this' + QList<QObject*> children = d->children; + for (int i = 0; i < children.size(); ++i) { + QObject *o = children.at(i); + if(!o->isWidgetType()) + continue; + if (QWidget *w = qobject_cast<QWidget *>(o)) + w->ensurePolished(); + } + + if (d->parent && d->sendChildEvents) { + QChildEvent e(QEvent::ChildPolished, const_cast<QWidget *>(this)); + QCoreApplication::sendEvent(d->parent, &e); + } +} + +/*! + Returns the mask currently set on a widget. If no mask is set the + return value will be an empty region. + + \sa setMask(), clearMask(), QRegion::isEmpty(), {Shaped Clock Example} +*/ +QRegion QWidget::mask() const +{ + Q_D(const QWidget); + return d->extra ? d->extra->mask : QRegion(); +} + +/*! + Returns the layout manager that is installed on this widget, or 0 + if no layout manager is installed. + + The layout manager sets the geometry of the widget's children + that have been added to the layout. + + \sa setLayout(), sizePolicy(), {Layout Management} +*/ +QLayout *QWidget::layout() const +{ + return d_func()->layout; +} + + +/*! + \fn void QWidget::setLayout(QLayout *layout) + + Sets the layout manager for this widget to \a layout. + + If there already is a layout manager installed on this widget, + QWidget won't let you install another. You must first delete the + existing layout manager (returned by layout()) before you can + call setLayout() with the new layout. + + If \a layout is the layout manger on a different widget, setLayout() + will reparent the layout and make it the layout manager for this widget. + + Example: + + \snippet examples/uitools/textfinder/textfinder.cpp 3b + + An alternative to calling this function is to pass this widget to + the layout's constructor. + + The QWidget will take ownership of \a layout. + + \sa layout(), {Layout Management} +*/ + +void QWidget::setLayout(QLayout *l) +{ + if (!l) { + qWarning("QWidget::setLayout: Cannot set layout to 0"); + return; + } + if (layout()) { + if (layout() != l) + qWarning("QWidget::setLayout: Attempting to set QLayout \"%s\" on %s \"%s\", which already has a" + " layout", l->objectName().toLocal8Bit().data(), metaObject()->className(), + objectName().toLocal8Bit().data()); + return; + } + + QObject *oldParent = l->parent(); + if (oldParent && oldParent != this) { + if (oldParent->isWidgetType()) { + // Steal the layout off a widget parent. Takes effect when + // morphing laid-out container widgets in Designer. + QWidget *oldParentWidget = static_cast<QWidget *>(oldParent); + oldParentWidget->takeLayout(); + } else { + qWarning("QWidget::setLayout: Attempting to set QLayout \"%s\" on %s \"%s\", when the QLayout already has a parent", + l->objectName().toLocal8Bit().data(), metaObject()->className(), + objectName().toLocal8Bit().data()); + return; + } + } + + Q_D(QWidget); + l->d_func()->topLevel = true; + d->layout = l; + if (oldParent != this) { + l->setParent(this); + l->d_func()->reparentChildWidgets(this); + l->invalidate(); + } + + if (isWindow() && d->maybeTopData()) + d->topData()->sizeAdjusted = false; +} + +/*! + \fn QLayout *QWidget::takeLayout() + + Remove the layout from the widget. + \since 4.5 +*/ + +QLayout *QWidget::takeLayout() +{ + Q_D(QWidget); + QLayout *l = layout(); + if (!l) + return 0; + d->layout = 0; + l->setParent(0); + return l; +} + +/*! + \property QWidget::sizePolicy + \brief the default layout behavior of the widget + + If there is a QLayout that manages this widget's children, the + size policy specified by that layout is used. If there is no such + QLayout, the result of this function is used. + + The default policy is Preferred/Preferred, which means that the + widget can be freely resized, but prefers to be the size + sizeHint() returns. Button-like widgets set the size policy to + specify that they may stretch horizontally, but are fixed + vertically. The same applies to lineedit controls (such as + QLineEdit, QSpinBox or an editable QComboBox) and other + horizontally orientated widgets (such as QProgressBar). + QToolButton's are normally square, so they allow growth in both + directions. Widgets that support different directions (such as + QSlider, QScrollBar or QHeader) specify stretching in the + respective direction only. Widgets that can provide scroll bars + (usually subclasses of QScrollArea) tend to specify that they can + use additional space, and that they can make do with less than + sizeHint(). + + \sa sizeHint() QLayout QSizePolicy updateGeometry() +*/ +QSizePolicy QWidget::sizePolicy() const +{ + Q_D(const QWidget); + return d->size_policy; +} + +void QWidget::setSizePolicy(QSizePolicy policy) +{ + Q_D(QWidget); + setAttribute(Qt::WA_WState_OwnSizePolicy); + if (policy == d->size_policy) + return; + d->size_policy = policy; + +#ifndef QT_NO_GRAPHICSVIEW + if (QWExtra *extra = d->extra) { + if (extra->proxyWidget) + extra->proxyWidget->setSizePolicy(policy); + } +#endif + + updateGeometry(); + + if (isWindow() && d->maybeTopData()) + d->topData()->sizeAdjusted = false; +} + +/*! + \fn void QWidget::setSizePolicy(QSizePolicy::Policy horizontal, QSizePolicy::Policy vertical) + \overload + + Sets the size policy of the widget to \a horizontal and \a + vertical, with standard stretch and no height-for-width. + + \sa QSizePolicy::QSizePolicy() +*/ + +/*! + Returns the preferred height for this widget, given the width \a w. + + If this widget has a layout, the default implementation returns + the layout's preferred height. if there is no layout, the default + implementation returns -1 indicating that the preferred height + does not depend on the width. +*/ + +int QWidget::heightForWidth(int w) const +{ + if (layout() && layout()->hasHeightForWidth()) + return layout()->totalHeightForWidth(w); + return -1; +} + + +/*! + \internal + + *virtual private* + + This is a bit hackish, but ideally we would have created a virtual function + in the public API (however, too late...) so that subclasses could reimplement + their own function. + Instead we add a virtual function to QWidgetPrivate. + ### Qt5: move to public class and make virtual +*/ +bool QWidgetPrivate::hasHeightForWidth() const +{ + return layout ? layout->hasHeightForWidth() : size_policy.hasHeightForWidth(); +} + +/*! + \fn QWidget *QWidget::childAt(int x, int y) const + + Returns the visible child widget at the position (\a{x}, \a{y}) + in the widget's coordinate system. If there is no visible child + widget at the specified position, the function returns 0. +*/ + +/*! + \overload + + Returns the visible child widget at point \a p in the widget's own + coordinate system. +*/ + +QWidget *QWidget::childAt(const QPoint &p) const +{ + return d_func()->childAt_helper(p, false); +} + +QWidget *QWidgetPrivate::childAt_helper(const QPoint &p, bool ignoreChildrenInDestructor) const +{ + if (children.isEmpty()) + return 0; + +#ifdef Q_WS_MAC + Q_Q(const QWidget); + // Unified tool bars on the Mac require special handling since they live outside + // QMainWindow's geometry(). See commit: 35667fd45ada49269a5987c235fdedfc43e92bb8 + bool includeFrame = q->isWindow() && qobject_cast<const QMainWindow *>(q) + && static_cast<const QMainWindow *>(q)->unifiedTitleAndToolBarOnMac(); + if (includeFrame) + return childAtRecursiveHelper(p, ignoreChildrenInDestructor, includeFrame); +#endif + + if (!pointInsideRectAndMask(p)) + return 0; + return childAtRecursiveHelper(p, ignoreChildrenInDestructor); +} + +QWidget *QWidgetPrivate::childAtRecursiveHelper(const QPoint &p, bool ignoreChildrenInDestructor, bool includeFrame) const +{ +#ifndef Q_WS_MAC + Q_UNUSED(includeFrame); +#endif + for (int i = children.size() - 1; i >= 0; --i) { + QWidget *child = qobject_cast<QWidget *>(children.at(i)); + if (!child || child->isWindow() || child->isHidden() || child->testAttribute(Qt::WA_TransparentForMouseEvents) + || (ignoreChildrenInDestructor && child->data->in_destructor)) { + continue; + } + + // Map the point 'p' from parent coordinates to child coordinates. + QPoint childPoint = p; +#ifdef Q_WS_MAC + // 'includeFrame' is true if the child's parent is a top-level QMainWindow with an unified tool bar. + // An unified tool bar on the Mac lives outside QMainWindow's geometry(), so a normal + // QWidget::mapFromParent won't do the trick. + if (includeFrame && qobject_cast<QToolBar *>(child)) + childPoint = qt_mac_nativeMapFromParent(child, p); + else +#endif + childPoint -= child->data->crect.topLeft(); + + // Check if the point hits the child. + if (!child->d_func()->pointInsideRectAndMask(childPoint)) + continue; + + // Do the same for the child's descendants. + if (QWidget *w = child->d_func()->childAtRecursiveHelper(childPoint, ignoreChildrenInDestructor)) + return w; + + // We have found our target; namely the child at position 'p'. + return child; + } + return 0; +} + +void QWidgetPrivate::updateGeometry_helper(bool forceUpdate) +{ + Q_Q(QWidget); + if (widgetItem) + widgetItem->invalidateSizeCache(); + QWidget *parent; + if (forceUpdate || !extra || extra->minw != extra->maxw || extra->minh != extra->maxh) { + if (!q->isWindow() && !q->isHidden() && (parent = q->parentWidget())) { + if (parent->d_func()->layout) + parent->d_func()->layout->invalidate(); + else if (parent->isVisible()) + QApplication::postEvent(parent, new QEvent(QEvent::LayoutRequest)); + } + } +} + +/*! + Notifies the layout system that this widget has changed and may + need to change geometry. + + Call this function if the sizeHint() or sizePolicy() have changed. + + For explicitly hidden widgets, updateGeometry() is a no-op. The + layout system will be notified as soon as the widget is shown. +*/ + +void QWidget::updateGeometry() +{ + Q_D(QWidget); + d->updateGeometry_helper(false); +} + +/*! \property QWidget::windowFlags + + Window flags are a combination of a type (e.g. Qt::Dialog) and + zero or more hints to the window system (e.g. + Qt::FramelessWindowHint). + + If the widget had type Qt::Widget or Qt::SubWindow and becomes a + window (Qt::Window, Qt::Dialog, etc.), it is put at position (0, + 0) on the desktop. If the widget is a window and becomes a + Qt::Widget or Qt::SubWindow, it is put at position (0, 0) + relative to its parent widget. + + \note This function calls setParent() when changing the flags for + a window, causing the widget to be hidden. You must call show() to make + the widget visible again.. + + \sa windowType(), {Window Flags Example} +*/ +void QWidget::setWindowFlags(Qt::WindowFlags flags) +{ + if (data->window_flags == flags) + return; + + Q_D(QWidget); + + if ((data->window_flags | flags) & Qt::Window) { + // the old type was a window and/or the new type is a window + QPoint oldPos = pos(); + bool visible = isVisible(); + setParent(parentWidget(), flags); + + // if both types are windows or neither of them are, we restore + // the old position + if (!((data->window_flags ^ flags) & Qt::Window) + && (visible || testAttribute(Qt::WA_Moved))) { + move(oldPos); + } + // for backward-compatibility we change Qt::WA_QuitOnClose attribute value only when the window was recreated. + d->adjustQuitOnCloseAttribute(); + } else { + data->window_flags = flags; + } +} + +/*! + Sets the window flags for the widget to \a flags, + \e without telling the window system. + + \warning Do not call this function unless you really know what + you're doing. + + \sa setWindowFlags() +*/ +void QWidget::overrideWindowFlags(Qt::WindowFlags flags) +{ + data->window_flags = flags; +} + +/*! + \fn Qt::WindowType QWidget::windowType() const + + Returns the window type of this widget. This is identical to + windowFlags() & Qt::WindowType_Mask. + + \sa windowFlags +*/ + +/*! + Sets the parent of the widget to \a parent, and resets the window + flags. The widget is moved to position (0, 0) in its new parent. + + If the new parent widget is in a different window, the + reparented widget and its children are appended to the end of the + \l{setFocusPolicy()}{tab chain} of the new parent + widget, in the same internal order as before. If one of the moved + widgets had keyboard focus, setParent() calls clearFocus() for that + widget. + + If the new parent widget is in the same window as the + old parent, setting the parent doesn't change the tab order or + keyboard focus. + + If the "new" parent widget is the old parent widget, this function + does nothing. + + \note The widget becomes invisible as part of changing its parent, + even if it was previously visible. You must call show() to make the + widget visible again. + + \warning It is very unlikely that you will ever need this + function. If you have a widget that changes its content + dynamically, it is far easier to use \l QStackedWidget. + + \sa setWindowFlags() +*/ +void QWidget::setParent(QWidget *parent) +{ + if (parent == parentWidget()) + return; + setParent((QWidget*)parent, windowFlags() & ~Qt::WindowType_Mask); +} + +/*! + \overload + + This function also takes widget flags, \a f as an argument. +*/ + +void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) +{ + Q_D(QWidget); + bool resized = testAttribute(Qt::WA_Resized); + bool wasCreated = testAttribute(Qt::WA_WState_Created); + QWidget *oldtlw = window(); + + QWidget *desktopWidget = 0; + if (parent && parent->windowType() == Qt::Desktop) + desktopWidget = parent; + bool newParent = (parent != parentWidget()) || !wasCreated || desktopWidget; + +#if defined(Q_WS_X11) || defined(Q_WS_WIN) || defined(Q_WS_MAC) || defined(Q_OS_SYMBIAN) + if (newParent && parent && !desktopWidget) { + if (testAttribute(Qt::WA_NativeWindow) && !qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings) +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + // On Mac, toolbars inside the unified title bar will never overlap with + // siblings in the content view. So we skip enforce native siblings in that case + && !d->isInUnifiedToolbar && parentWidget() && parentWidget()->isWindow() +#endif // Q_WS_MAC && QT_MAC_USE_COCOA + ) + parent->d_func()->enforceNativeChildren(); + else if (parent->d_func()->nativeChildrenForced() || parent->testAttribute(Qt::WA_PaintOnScreen)) + setAttribute(Qt::WA_NativeWindow); + } +#endif + + if (wasCreated) { + if (!testAttribute(Qt::WA_WState_Hidden)) { + hide(); + setAttribute(Qt::WA_WState_ExplicitShowHide, false); + } + if (newParent) { + QEvent e(QEvent::ParentAboutToChange); + QApplication::sendEvent(this, &e); + } + } + if (newParent && isAncestorOf(focusWidget())) + focusWidget()->clearFocus(); + + QTLWExtra *oldTopExtra = window()->d_func()->maybeTopData(); + QWidgetBackingStoreTracker *oldBsTracker = oldTopExtra ? &oldTopExtra->backingStore : 0; + + d->setParent_sys(parent, f); + + QTLWExtra *topExtra = window()->d_func()->maybeTopData(); + QWidgetBackingStoreTracker *bsTracker = topExtra ? &topExtra->backingStore : 0; + if (oldBsTracker && oldBsTracker != bsTracker) + oldBsTracker->unregisterWidgetSubtree(this); + + if (desktopWidget) + parent = 0; + +#ifdef Q_BACKINGSTORE_SUBSURFACES + QTLWExtra *extra = d->maybeTopData(); + QWindowSurface *windowSurface = (extra ? extra->windowSurface : 0); + if (newParent && windowSurface) { + QWidgetBackingStore *oldBs = oldtlw->d_func()->maybeBackingStore(); + if (oldBs) + oldBs->subSurfaces.removeAll(windowSurface); + + if (parent) { + QWidgetBackingStore *newBs = parent->d_func()->maybeBackingStore(); + if (newBs) + newBs->subSurfaces.append(windowSurface); + } + } +#endif + + if (QWidgetBackingStore *oldBs = oldtlw->d_func()->maybeBackingStore()) { + if (newParent) + oldBs->removeDirtyWidget(this); + // Move the widget and all its static children from + // the old backing store to the new one. + oldBs->moveStaticWidgets(this); + } + + if ((QApplicationPrivate::app_compile_version < 0x040200 + || QApplicationPrivate::testAttribute(Qt::AA_ImmediateWidgetCreation)) + && !testAttribute(Qt::WA_WState_Created)) + create(); + + d->reparentFocusWidgets(oldtlw); + setAttribute(Qt::WA_Resized, resized); + if (!testAttribute(Qt::WA_StyleSheet) + && (!parent || !parent->testAttribute(Qt::WA_StyleSheet))) { + d->resolveFont(); + d->resolvePalette(); + } + d->resolveLayoutDirection(); + d->resolveLocale(); + + // Note: GL widgets under WGL or EGL will always need a ParentChange + // event to handle recreation/rebinding of the GL context, hence the + // (f & Qt::MSWindowsOwnDC) clause (which is set on QGLWidgets on all + // platforms). + if (newParent +#if defined(Q_WS_WIN) || defined(QT_OPENGL_ES) + || (f & Qt::MSWindowsOwnDC) +#endif + ) { + // propagate enabled updates enabled state to non-windows + if (!isWindow()) { + if (!testAttribute(Qt::WA_ForceDisabled)) + d->setEnabled_helper(parent ? parent->isEnabled() : true); + if (!testAttribute(Qt::WA_ForceUpdatesDisabled)) + d->setUpdatesEnabled_helper(parent ? parent->updatesEnabled() : true); + } + d->inheritStyle(); + + // send and post remaining QObject events + if (parent && d->sendChildEvents) { + QChildEvent e(QEvent::ChildAdded, this); + QApplication::sendEvent(parent, &e); +#ifdef QT3_SUPPORT + if (parent->d_func()->pendingChildInsertedEvents.isEmpty()) { + QApplication::postEvent(parent, + new QEvent(QEvent::ChildInsertedRequest), + Qt::HighEventPriority); + } + parent->d_func()->pendingChildInsertedEvents.append(this); +#endif + } + +//### already hidden above ---> must probably do something smart on the mac +// #ifdef Q_WS_MAC +// extern bool qt_mac_is_macdrawer(const QWidget *); //qwidget_mac.cpp +// if(!qt_mac_is_macdrawer(q)) //special case +// q->setAttribute(Qt::WA_WState_Hidden); +// #else +// q->setAttribute(Qt::WA_WState_Hidden); +//#endif + + if (parent && d->sendChildEvents && d->polished) { + QChildEvent e(QEvent::ChildPolished, this); + QCoreApplication::sendEvent(parent, &e); + } + + QEvent e(QEvent::ParentChange); + QApplication::sendEvent(this, &e); + } + + if (!wasCreated) { + if (isWindow() || parentWidget()->isVisible()) + setAttribute(Qt::WA_WState_Hidden, true); + else if (!testAttribute(Qt::WA_WState_ExplicitShowHide)) + setAttribute(Qt::WA_WState_Hidden, false); + } + + d->updateIsOpaque(); + +#ifndef QT_NO_GRAPHICSVIEW + // Embed the widget into a proxy if the parent is embedded. + // ### Doesn't handle reparenting out of an embedded widget. + if (oldtlw->graphicsProxyWidget()) { + if (QGraphicsProxyWidget *ancestorProxy = d->nearestGraphicsProxyWidget(oldtlw)) + ancestorProxy->d_func()->unembedSubWindow(this); + } + if (isWindow() && parent && !graphicsProxyWidget() && !bypassGraphicsProxyWidget(this)) { + if (QGraphicsProxyWidget *ancestorProxy = d->nearestGraphicsProxyWidget(parent)) + ancestorProxy->d_func()->embedSubWindow(this); + } +#endif +} + +/*! + Scrolls the widget including its children \a dx pixels to the + right and \a dy downward. Both \a dx and \a dy may be negative. + + After scrolling, the widgets will receive paint events for + the areas that need to be repainted. For widgets that Qt knows to + be opaque, this is only the newly exposed parts. + For example, if an opaque widget is scrolled 8 pixels to the left, + only an 8-pixel wide stripe at the right edge needs updating. + + Since widgets propagate the contents of their parents by default, + you need to set the \l autoFillBackground property, or use + setAttribute() to set the Qt::WA_OpaquePaintEvent attribute, to make + a widget opaque. + + For widgets that use contents propagation, a scroll will cause an + update of the entire scroll area. + + \sa {Transparency and Double Buffering} +*/ + +void QWidget::scroll(int dx, int dy) +{ + if ((!updatesEnabled() && children().size() == 0) || !isVisible()) + return; + if (dx == 0 && dy == 0) + return; + Q_D(QWidget); +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsProxyWidget *proxy = QWidgetPrivate::nearestGraphicsProxyWidget(this)) { + // Graphics View maintains its own dirty region as a list of rects; + // until we can connect item updates directly to the view, we must + // separately add a translated dirty region. + if (!d->dirty.isEmpty()) { + foreach (const QRect &rect, (d->dirty.translated(dx, dy)).rects()) + proxy->update(rect); + } + proxy->scroll(dx, dy, proxy->subWidgetRect(this)); + return; + } +#endif + d->setDirtyOpaqueRegion(); + d->scroll_sys(dx, dy); +} + +/*! + \overload + + This version only scrolls \a r and does not move the children of + the widget. + + If \a r is empty or invalid, the result is undefined. + + \sa QScrollArea +*/ +void QWidget::scroll(int dx, int dy, const QRect &r) +{ + + if ((!updatesEnabled() && children().size() == 0) || !isVisible()) + return; + if (dx == 0 && dy == 0) + return; + Q_D(QWidget); +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsProxyWidget *proxy = QWidgetPrivate::nearestGraphicsProxyWidget(this)) { + // Graphics View maintains its own dirty region as a list of rects; + // until we can connect item updates directly to the view, we must + // separately add a translated dirty region. + if (!d->dirty.isEmpty()) { + foreach (const QRect &rect, (d->dirty.translated(dx, dy) & r).rects()) + proxy->update(rect); + } + proxy->scroll(dx, dy, r.translated(proxy->subWidgetRect(this).topLeft().toPoint())); + return; + } +#endif + d->scroll_sys(dx, dy, r); +} + +/*! + Repaints the widget directly by calling paintEvent() immediately, + unless updates are disabled or the widget is hidden. + + We suggest only using repaint() if you need an immediate repaint, + for example during animation. In almost all circumstances update() + is better, as it permits Qt to optimize for speed and minimize + flicker. + + \warning If you call repaint() in a function which may itself be + called from paintEvent(), you may get infinite recursion. The + update() function never causes recursion. + + \sa update(), paintEvent(), setUpdatesEnabled() +*/ + +void QWidget::repaint() +{ + repaint(rect()); +} + +/*! \overload + + This version repaints a rectangle (\a x, \a y, \a w, \a h) inside + the widget. + + If \a w is negative, it is replaced with \c{width() - x}, and if + \a h is negative, it is replaced width \c{height() - y}. +*/ +void QWidget::repaint(int x, int y, int w, int h) +{ + if (x > data->crect.width() || y > data->crect.height()) + return; + + if (w < 0) + w = data->crect.width() - x; + if (h < 0) + h = data->crect.height() - y; + + repaint(QRect(x, y, w, h)); +} + +/*! \overload + + This version repaints a rectangle \a rect inside the widget. +*/ +void QWidget::repaint(const QRect &rect) +{ + Q_D(QWidget); + + if (testAttribute(Qt::WA_WState_ConfigPending)) { + update(rect); + return; + } + + if (!isVisible() || !updatesEnabled() || rect.isEmpty()) + return; + + if (hasBackingStoreSupport()) { +#ifdef QT_MAC_USE_COCOA + if (qt_widget_private(this)->isInUnifiedToolbar) { + qt_widget_private(this)->unifiedSurface->renderToolbar(this, true); + return; + } +#endif // QT_MAC_USE_COCOA + QTLWExtra *tlwExtra = window()->d_func()->maybeTopData(); + if (tlwExtra && !tlwExtra->inTopLevelResize && tlwExtra->backingStore) { + tlwExtra->inRepaint = true; + tlwExtra->backingStore->markDirty(rect, this, true); + tlwExtra->inRepaint = false; + } + } else { + d->repaint_sys(rect); + } +} + +/*! + \overload + + This version repaints a region \a rgn inside the widget. +*/ +void QWidget::repaint(const QRegion &rgn) +{ + Q_D(QWidget); + + if (testAttribute(Qt::WA_WState_ConfigPending)) { + update(rgn); + return; + } + + if (!isVisible() || !updatesEnabled() || rgn.isEmpty()) + return; + + if (hasBackingStoreSupport()) { +#ifdef QT_MAC_USE_COCOA + if (qt_widget_private(this)->isInUnifiedToolbar) { + qt_widget_private(this)->unifiedSurface->renderToolbar(this, true); + return; + } +#endif // QT_MAC_USE_COCOA + QTLWExtra *tlwExtra = window()->d_func()->maybeTopData(); + if (tlwExtra && !tlwExtra->inTopLevelResize && tlwExtra->backingStore) { + tlwExtra->inRepaint = true; + tlwExtra->backingStore->markDirty(rgn, this, true); + tlwExtra->inRepaint = false; + } + } else { + d->repaint_sys(rgn); + } +} + +/*! + Updates the widget unless updates are disabled or the widget is + hidden. + + This function does not cause an immediate repaint; instead it + schedules a paint event for processing when Qt returns to the main + event loop. This permits Qt to optimize for more speed and less + flicker than a call to repaint() does. + + Calling update() several times normally results in just one + paintEvent() call. + + Qt normally erases the widget's area before the paintEvent() call. + If the Qt::WA_OpaquePaintEvent widget attribute is set, the widget is + responsible for painting all its pixels with an opaque color. + + \sa repaint() paintEvent(), setUpdatesEnabled(), {Analog Clock Example} +*/ +void QWidget::update() +{ + update(rect()); +} + +/*! \fn void QWidget::update(int x, int y, int w, int h) + \overload + + This version updates a rectangle (\a x, \a y, \a w, \a h) inside + the widget. +*/ + +/*! + \overload + + This version updates a rectangle \a rect inside the widget. +*/ +void QWidget::update(const QRect &rect) +{ + if (!isVisible() || !updatesEnabled() || rect.isEmpty()) + return; + + if (testAttribute(Qt::WA_WState_InPaintEvent)) { + QApplication::postEvent(this, new QUpdateLaterEvent(rect)); + return; + } + + if (hasBackingStoreSupport()) { +#ifdef QT_MAC_USE_COCOA + if (qt_widget_private(this)->isInUnifiedToolbar) { + qt_widget_private(this)->unifiedSurface->renderToolbar(this, true); + return; + } +#endif // QT_MAC_USE_COCOA + QTLWExtra *tlwExtra = window()->d_func()->maybeTopData(); + if (tlwExtra && !tlwExtra->inTopLevelResize && tlwExtra->backingStore) + tlwExtra->backingStore->markDirty(rect, this); + } else { + d_func()->repaint_sys(rect); + } +} + +/*! + \overload + + This version repaints a region \a rgn inside the widget. +*/ +void QWidget::update(const QRegion &rgn) +{ + if (!isVisible() || !updatesEnabled() || rgn.isEmpty()) + return; + + if (testAttribute(Qt::WA_WState_InPaintEvent)) { + QApplication::postEvent(this, new QUpdateLaterEvent(rgn)); + return; + } + + if (hasBackingStoreSupport()) { +#ifdef QT_MAC_USE_COCOA + if (qt_widget_private(this)->isInUnifiedToolbar) { + qt_widget_private(this)->unifiedSurface->renderToolbar(this, true); + return; + } +#endif // QT_MAC_USE_COCOA + QTLWExtra *tlwExtra = window()->d_func()->maybeTopData(); + if (tlwExtra && !tlwExtra->inTopLevelResize && tlwExtra->backingStore) + tlwExtra->backingStore->markDirty(rgn, this); + } else { + d_func()->repaint_sys(rgn); + } +} + +#ifdef QT3_SUPPORT +/*! + Clear the rectangle at point (\a x, \a y) of width \a w and height + \a h. + + \warning This is best done in a paintEvent(). +*/ +void QWidget::erase_helper(int x, int y, int w, int h) +{ + if (testAttribute(Qt::WA_NoSystemBackground) || testAttribute(Qt::WA_UpdatesDisabled) || !testAttribute(Qt::WA_WState_Visible)) + return; + if (w < 0) + w = data->crect.width() - x; + if (h < 0) + h = data->crect.height() - y; + if (w != 0 && h != 0) { + QPainter p(this); + p.eraseRect(QRect(x, y, w, h)); + } +} + +/*! + \overload + + Clear the given region, \a rgn. + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your erasing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ +void QWidget::erase(const QRegion& rgn) +{ + if (testAttribute(Qt::WA_NoSystemBackground) || testAttribute(Qt::WA_UpdatesDisabled) || !testAttribute(Qt::WA_WState_Visible)) + return; + + QPainter p(this); + p.setClipRegion(rgn); + p.eraseRect(rgn.boundingRect()); +} + +void QWidget::drawText_helper(int x, int y, const QString &str) +{ + if(!testAttribute(Qt::WA_WState_Visible)) + return; + QPainter paint(this); + paint.drawText(x, y, str); +} + + +/*! + Closes the widget. + + Use the no-argument overload instead. +*/ +bool QWidget::close(bool alsoDelete) +{ + QPointer<QWidget> that = this; + bool accepted = close(); + if (alsoDelete && accepted && that) + deleteLater(); + return accepted; +} + +void QWidget::setIcon(const QPixmap &i) +{ + setWindowIcon(i); +} + +/*! + Return's the widget's icon. + + Use windowIcon() instead. +*/ +const QPixmap *QWidget::icon() const +{ + Q_D(const QWidget); + return (d->extra && d->extra->topextra) ? d->extra->topextra->iconPixmap : 0; +} + +#endif // QT3_SUPPORT + + /*! + \internal + + This just sets the corresponding attribute bit to 1 or 0 + */ +static void setAttribute_internal(Qt::WidgetAttribute attribute, bool on, QWidgetData *data, + QWidgetPrivate *d) +{ + if (attribute < int(8*sizeof(uint))) { + if (on) + data->widget_attributes |= (1<<attribute); + else + data->widget_attributes &= ~(1<<attribute); + } else { + const int x = attribute - 8*sizeof(uint); + const int int_off = x / (8*sizeof(uint)); + if (on) + d->high_attributes[int_off] |= (1<<(x-(int_off*8*sizeof(uint)))); + else + d->high_attributes[int_off] &= ~(1<<(x-(int_off*8*sizeof(uint)))); + } +} + +/*! + Sets the attribute \a attribute on this widget if \a on is true; + otherwise clears the attribute. + + \sa testAttribute() +*/ +void QWidget::setAttribute(Qt::WidgetAttribute attribute, bool on) +{ + if (testAttribute(attribute) == on) + return; + + Q_D(QWidget); + Q_ASSERT_X(sizeof(d->high_attributes)*8 >= (Qt::WA_AttributeCount - sizeof(uint)*8), + "QWidget::setAttribute(WidgetAttribute, bool)", + "QWidgetPrivate::high_attributes[] too small to contain all attributes in WidgetAttribute"); +#ifdef Q_WS_WIN + // ### Don't use PaintOnScreen+paintEngine() to do native painting in 5.0 + if (attribute == Qt::WA_PaintOnScreen && on && !inherits("QGLWidget")) { + // see qwidget_win.cpp, ::paintEngine for details + paintEngine(); + if (d->noPaintOnScreen) + return; + } +#endif + + setAttribute_internal(attribute, on, data, d); + + switch (attribute) { + +#ifndef QT_NO_DRAGANDDROP + case Qt::WA_AcceptDrops: { + if (on && !testAttribute(Qt::WA_DropSiteRegistered)) + setAttribute(Qt::WA_DropSiteRegistered, true); + else if (!on && (isWindow() || !parentWidget() || !parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) + setAttribute(Qt::WA_DropSiteRegistered, false); + QEvent e(QEvent::AcceptDropsChange); + QApplication::sendEvent(this, &e); + break; + } + case Qt::WA_DropSiteRegistered: { + d->registerDropSite(on); + for (int i = 0; i < d->children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(d->children.at(i)); + if (w && !w->isWindow() && !w->testAttribute(Qt::WA_AcceptDrops) && w->testAttribute(Qt::WA_DropSiteRegistered) != on) + w->setAttribute(Qt::WA_DropSiteRegistered, on); + } + break; + } +#endif + + case Qt::WA_NoChildEventsForParent: + d->sendChildEvents = !on; + break; + case Qt::WA_NoChildEventsFromChildren: + d->receiveChildEvents = !on; + break; + case Qt::WA_MacBrushedMetal: +#ifdef Q_WS_MAC + d->setStyle_helper(style(), false, true); // Make sure things get unpolished/polished correctly. + // fall through since changing the metal attribute affects the opaque size grip. + case Qt::WA_MacOpaqueSizeGrip: + d->macUpdateOpaqueSizeGrip(); + break; + case Qt::WA_MacShowFocusRect: + if (hasFocus()) { + clearFocus(); + setFocus(); + } + break; + case Qt::WA_Hover: + qt_mac_update_mouseTracking(this); + break; +#endif + case Qt::WA_MacAlwaysShowToolWindow: +#ifdef Q_WS_MAC + d->macUpdateHideOnSuspend(); +#endif + break; + case Qt::WA_MacNormalSize: + case Qt::WA_MacSmallSize: + case Qt::WA_MacMiniSize: +#ifdef Q_WS_MAC + { + // We can only have one of these set at a time + const Qt::WidgetAttribute MacSizes[] = { Qt::WA_MacNormalSize, Qt::WA_MacSmallSize, + Qt::WA_MacMiniSize }; + for (int i = 0; i < 3; ++i) { + if (MacSizes[i] != attribute) + setAttribute_internal(MacSizes[i], false, data, d); + } + d->macUpdateSizeAttribute(); + } +#endif + break; + case Qt::WA_ShowModal: + if (!on) { + if (isVisible()) + QApplicationPrivate::leaveModal(this); + // reset modality type to Modeless when clearing WA_ShowModal + data->window_modality = Qt::NonModal; + } else if (data->window_modality == Qt::NonModal) { + // determine the modality type if it hasn't been set prior + // to setting WA_ShowModal. set the default to WindowModal + // if we are the child of a group leader; otherwise use + // ApplicationModal. + QWidget *w = parentWidget(); + if (w) + w = w->window(); + while (w && !w->testAttribute(Qt::WA_GroupLeader)) { + w = w->parentWidget(); + if (w) + w = w->window(); + } + data->window_modality = (w && w->testAttribute(Qt::WA_GroupLeader)) + ? Qt::WindowModal + : Qt::ApplicationModal; + // Some window managers does not allow us to enter modal after the + // window is showing. Therefore, to be consistent, we cannot call + // QApplicationPrivate::enterModal(this) here. The window must be + // hidden before changing modality. + } + if (testAttribute(Qt::WA_WState_Created)) { + // don't call setModal_sys() before create_sys() + d->setModal_sys(); + } + break; + case Qt::WA_MouseTracking: { + QEvent e(QEvent::MouseTrackingChange); + QApplication::sendEvent(this, &e); + break; } + case Qt::WA_NativeWindow: { +#ifndef QT_NO_IM + QWidget *focusWidget = d->effectiveFocusWidget(); + QInputContext *ic = 0; + if (on && !internalWinId() && hasFocus() + && focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) { + ic = focusWidget->d_func()->inputContext(); + if (ic) { + ic->reset(); + ic->setFocusWidget(0); + } + } + if (!qApp->testAttribute(Qt::AA_DontCreateNativeWidgetSiblings) && parentWidget() +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + // On Mac, toolbars inside the unified title bar will never overlap with + // siblings in the content view. So we skip enforce native siblings in that case + && !d->isInUnifiedToolbar && parentWidget()->isWindow() +#endif // Q_WS_MAC && QT_MAC_USE_COCOA + ) + parentWidget()->d_func()->enforceNativeChildren(); + if (on && !internalWinId() && testAttribute(Qt::WA_WState_Created)) + d->createWinId(); + if (ic && isEnabled() && focusWidget->isEnabled() + && focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) { + ic->setFocusWidget(focusWidget); + } +#endif //QT_NO_IM + break; + } + case Qt::WA_PaintOnScreen: + d->updateIsOpaque(); +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_OS_SYMBIAN) + // Recreate the widget if it's already created as an alien widget and + // WA_PaintOnScreen is enabled. Paint on screen widgets must have win id. + // So must their children. + if (on) { + setAttribute(Qt::WA_NativeWindow); + d->enforceNativeChildren(); + } +#endif + // fall through + case Qt::WA_OpaquePaintEvent: + d->updateIsOpaque(); + break; + case Qt::WA_NoSystemBackground: + d->updateIsOpaque(); + // fall through... + case Qt::WA_UpdatesDisabled: + d->updateSystemBackground(); + break; + case Qt::WA_TransparentForMouseEvents: +#ifdef Q_WS_MAC + d->macUpdateIgnoreMouseEvents(); +#endif + break; + case Qt::WA_InputMethodEnabled: { +#ifndef QT_NO_IM + QWidget *focusWidget = d->effectiveFocusWidget(); + QInputContext *ic = focusWidget->d_func()->assignedInputContext(); + if (!ic && (!on || hasFocus())) + ic = focusWidget->d_func()->inputContext(); + if (ic) { + if (on && hasFocus() && ic->focusWidget() != focusWidget && isEnabled() + && focusWidget->testAttribute(Qt::WA_InputMethodEnabled)) { + ic->setFocusWidget(focusWidget); + } else if (!on && ic->focusWidget() == focusWidget) { + ic->reset(); + ic->setFocusWidget(0); + } + } +#endif //QT_NO_IM + break; + } + case Qt::WA_WindowPropagation: + d->resolvePalette(); + d->resolveFont(); + d->resolveLocale(); + break; +#ifdef Q_WS_X11 + case Qt::WA_NoX11EventCompression: + if (!d->extra) + d->createExtra(); + d->extra->compress_events = on; + break; + case Qt::WA_X11OpenGLOverlay: + d->updateIsOpaque(); + break; + case Qt::WA_X11DoNotAcceptFocus: + if (testAttribute(Qt::WA_WState_Created)) + d->updateX11AcceptFocus(); + break; +#endif + case Qt::WA_DontShowOnScreen: { + if (on && isVisible()) { + // Make sure we keep the current state and only hide the widget + // from the desktop. show_sys will only update platform specific + // attributes at this point. + d->hide_sys(); +#ifdef Q_WS_QWS + // Release the region for this window from qws if the widget has + // been shown before the attribute was set. + if (QWSWindowSurface *surface = static_cast<QWSWindowSurface *>(windowSurface())) { + QWidget::qwsDisplay()->requestRegion(surface->winId(), surface->key(), + surface->permanentState(), QRegion()); + } +#endif + d->show_sys(); + } + break; + } + +#ifdef Q_WS_X11 + case Qt::WA_X11NetWmWindowTypeDesktop: + case Qt::WA_X11NetWmWindowTypeDock: + case Qt::WA_X11NetWmWindowTypeToolBar: + case Qt::WA_X11NetWmWindowTypeMenu: + case Qt::WA_X11NetWmWindowTypeUtility: + case Qt::WA_X11NetWmWindowTypeSplash: + case Qt::WA_X11NetWmWindowTypeDialog: + case Qt::WA_X11NetWmWindowTypeDropDownMenu: + case Qt::WA_X11NetWmWindowTypePopupMenu: + case Qt::WA_X11NetWmWindowTypeToolTip: + case Qt::WA_X11NetWmWindowTypeNotification: + case Qt::WA_X11NetWmWindowTypeCombo: + case Qt::WA_X11NetWmWindowTypeDND: + if (testAttribute(Qt::WA_WState_Created)) + d->setNetWmWindowTypes(); + break; +#endif + + case Qt::WA_StaticContents: + if (QWidgetBackingStore *bs = d->maybeBackingStore()) { + if (on) + bs->addStaticWidget(this); + else + bs->removeStaticWidget(this); + } + break; + case Qt::WA_TranslucentBackground: + if (on) { + setAttribute(Qt::WA_NoSystemBackground); + d->updateIsTranslucent(); + } + + break; + case Qt::WA_AcceptTouchEvents: +#if defined(Q_WS_WIN) || defined(Q_WS_MAC) || defined(Q_OS_SYMBIAN) + if (on) + d->registerTouchWindow(); +#endif + break; + case Qt::WA_LockPortraitOrientation: + case Qt::WA_LockLandscapeOrientation: + case Qt::WA_AutoOrientation: { + const Qt::WidgetAttribute orientations[3] = { + Qt::WA_LockPortraitOrientation, + Qt::WA_LockLandscapeOrientation, + Qt::WA_AutoOrientation + }; + + if (on) { + // We can only have one of these set at a time + for (int i = 0; i < 3; ++i) { + if (orientations[i] != attribute) + setAttribute_internal(orientations[i], false, data, d); + } + } + +#ifdef Q_WS_S60 + CAknAppUiBase* appUi = static_cast<CAknAppUiBase*>(CEikonEnv::Static()->EikAppUi()); + const CAknAppUiBase::TAppUiOrientation s60orientations[] = { + CAknAppUiBase::EAppUiOrientationPortrait, + CAknAppUiBase::EAppUiOrientationLandscape, + CAknAppUiBase::EAppUiOrientationAutomatic + }; + CAknAppUiBase::TAppUiOrientation s60orientation = CAknAppUiBase::EAppUiOrientationUnspecified; + for (int i = 0; i < 3; ++i) { + if (testAttribute(orientations[i])) { + s60orientation = s60orientations[i]; + break; + } + } + QT_TRAP_THROWING(appUi->SetOrientationL(s60orientation)); + S60->orientationSet = true; + QSymbianControl *window = static_cast<QSymbianControl *>(internalWinId()); + if (window) + window->ensureFixNativeOrientation(); +#endif + break; + } + default: + break; + } +} + +/*! \fn bool QWidget::testAttribute(Qt::WidgetAttribute attribute) const + + Returns true if attribute \a attribute is set on this widget; + otherwise returns false. + + \sa setAttribute() + */ +bool QWidget::testAttribute_helper(Qt::WidgetAttribute attribute) const +{ + Q_D(const QWidget); + const int x = attribute - 8*sizeof(uint); + const int int_off = x / (8*sizeof(uint)); + return (d->high_attributes[int_off] & (1<<(x-(int_off*8*sizeof(uint))))); +} + +/*! + \property QWidget::windowOpacity + + \brief The level of opacity for the window. + + The valid range of opacity is from 1.0 (completely opaque) to + 0.0 (completely transparent). + + By default the value of this property is 1.0. + + This feature is available on Embedded Linux, Mac OS X, Windows, + and X11 platforms that support the Composite extension. + + This feature is not available on Windows CE. + + Note that under X11 you need to have a composite manager running, + and the X11 specific _NET_WM_WINDOW_OPACITY atom needs to be + supported by the window manager you are using. + + \warning Changing this property from opaque to transparent might issue a + paint event that needs to be processed before the window is displayed + correctly. This affects mainly the use of QPixmap::grabWindow(). Also note + that semi-transparent windows update and resize significantly slower than + opaque windows. + + \sa setMask() +*/ +qreal QWidget::windowOpacity() const +{ + Q_D(const QWidget); + return (isWindow() && d->maybeTopData()) ? d->maybeTopData()->opacity / 255. : 1.0; +} + +void QWidget::setWindowOpacity(qreal opacity) +{ + Q_D(QWidget); + if (!isWindow()) + return; + + opacity = qBound(qreal(0.0), opacity, qreal(1.0)); + QTLWExtra *extra = d->topData(); + extra->opacity = uint(opacity * 255); + setAttribute(Qt::WA_WState_WindowOpacitySet); + +#ifndef Q_WS_QWS + if (!testAttribute(Qt::WA_WState_Created)) + return; +#endif + +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsProxyWidget *proxy = graphicsProxyWidget()) { + // Avoid invalidating the cache if set. + if (proxy->cacheMode() == QGraphicsItem::NoCache) + proxy->update(); + else if (QGraphicsScene *scene = proxy->scene()) + scene->update(proxy->sceneBoundingRect()); + return; + } +#endif + + d->setWindowOpacity_sys(opacity); +} + +/*! + \property QWidget::windowModified + \brief whether the document shown in the window has unsaved changes + + A modified window is a window whose content has changed but has + not been saved to disk. This flag will have different effects + varied by the platform. On Mac OS X the close button will have a + modified look; on other platforms, the window title will have an + '*' (asterisk). + + The window title must contain a "[*]" placeholder, which + indicates where the '*' should appear. Normally, it should appear + right after the file name (e.g., "document1.txt[*] - Text + Editor"). If the window isn't modified, the placeholder is simply + removed. + + Note that if a widget is set as modified, all its ancestors will + also be set as modified. However, if you call \c + {setWindowModified(false)} on a widget, this will not propagate to + its parent because other children of the parent might have been + modified. + + \sa windowTitle, {Application Example}, {SDI Example}, {MDI Example} +*/ +bool QWidget::isWindowModified() const +{ + return testAttribute(Qt::WA_WindowModified); +} + +void QWidget::setWindowModified(bool mod) +{ + Q_D(QWidget); + setAttribute(Qt::WA_WindowModified, mod); + +#ifndef Q_WS_MAC + if (!windowTitle().contains(QLatin1String("[*]")) && mod) + qWarning("QWidget::setWindowModified: The window title does not contain a '[*]' placeholder"); +#endif + d->setWindowTitle_helper(windowTitle()); + d->setWindowIconText_helper(windowIconText()); +#ifdef Q_WS_MAC + d->setWindowModified_sys(mod); +#endif + + QEvent e(QEvent::ModifiedChange); + QApplication::sendEvent(this, &e); +} + +#ifndef QT_NO_TOOLTIP +/*! + \property QWidget::toolTip + + \brief the widget's tooltip + + Note that by default tooltips are only shown for widgets that are + children of the active window. You can change this behavior by + setting the attribute Qt::WA_AlwaysShowToolTips on the \e window, + not on the widget with the tooltip. + + If you want to control a tooltip's behavior, you can intercept the + event() function and catch the QEvent::ToolTip event (e.g., if you + want to customize the area for which the tooltip should be shown). + + By default, this property contains an empty string. + + \sa QToolTip statusTip whatsThis +*/ +void QWidget::setToolTip(const QString &s) +{ + Q_D(QWidget); + d->toolTip = s; + + QEvent event(QEvent::ToolTipChange); + QApplication::sendEvent(this, &event); +} + +QString QWidget::toolTip() const +{ + Q_D(const QWidget); + return d->toolTip; +} +#endif // QT_NO_TOOLTIP + + +#ifndef QT_NO_STATUSTIP +/*! + \property QWidget::statusTip + \brief the widget's status tip + + By default, this property contains an empty string. + + \sa toolTip whatsThis +*/ +void QWidget::setStatusTip(const QString &s) +{ + Q_D(QWidget); + d->statusTip = s; +} + +QString QWidget::statusTip() const +{ + Q_D(const QWidget); + return d->statusTip; +} +#endif // QT_NO_STATUSTIP + +#ifndef QT_NO_WHATSTHIS +/*! + \property QWidget::whatsThis + + \brief the widget's What's This help text. + + By default, this property contains an empty string. + + \sa QWhatsThis QWidget::toolTip QWidget::statusTip +*/ +void QWidget::setWhatsThis(const QString &s) +{ + Q_D(QWidget); + d->whatsThis = s; +} + +QString QWidget::whatsThis() const +{ + Q_D(const QWidget); + return d->whatsThis; +} +#endif // QT_NO_WHATSTHIS + +#ifndef QT_NO_ACCESSIBILITY +/*! + \property QWidget::accessibleName + + \brief the widget's name as seen by assistive technologies + + This property is used by accessible clients to identify, find, or announce + the widget for accessible clients. + + By default, this property contains an empty string. + + \sa QAccessibleInterface::text() +*/ +void QWidget::setAccessibleName(const QString &name) +{ + Q_D(QWidget); + d->accessibleName = name; +} + +QString QWidget::accessibleName() const +{ + Q_D(const QWidget); + return d->accessibleName; +} + +/*! + \property QWidget::accessibleDescription + + \brief the widget's description as seen by assistive technologies + + By default, this property contains an empty string. + + \sa QAccessibleInterface::text() +*/ +void QWidget::setAccessibleDescription(const QString &description) +{ + Q_D(QWidget); + d->accessibleDescription = description; +} + +QString QWidget::accessibleDescription() const +{ + Q_D(const QWidget); + return d->accessibleDescription; +} +#endif // QT_NO_ACCESSIBILITY + +#ifndef QT_NO_SHORTCUT +/*! + Adds a shortcut to Qt's shortcut system that watches for the given + \a key sequence in the given \a context. If the \a context is + Qt::ApplicationShortcut, the shortcut applies to the application as a + whole. Otherwise, it is either local to this widget, Qt::WidgetShortcut, + or to the window itself, Qt::WindowShortcut. + + If the same \a key sequence has been grabbed by several widgets, + when the \a key sequence occurs a QEvent::Shortcut event is sent + to all the widgets to which it applies in a non-deterministic + order, but with the ``ambiguous'' flag set to true. + + \warning You should not normally need to use this function; + instead create \l{QAction}s with the shortcut key sequences you + require (if you also want equivalent menu options and toolbar + buttons), or create \l{QShortcut}s if you just need key sequences. + Both QAction and QShortcut handle all the event filtering for you, + and provide signals which are triggered when the user triggers the + key sequence, so are much easier to use than this low-level + function. + + \sa releaseShortcut() setShortcutEnabled() +*/ +int QWidget::grabShortcut(const QKeySequence &key, Qt::ShortcutContext context) +{ + Q_ASSERT(qApp); + if (key.isEmpty()) + return 0; + setAttribute(Qt::WA_GrabbedShortcut); + return qApp->d_func()->shortcutMap.addShortcut(this, key, context); +} + +/*! + Removes the shortcut with the given \a id from Qt's shortcut + system. The widget will no longer receive QEvent::Shortcut events + for the shortcut's key sequence (unless it has other shortcuts + with the same key sequence). + + \warning You should not normally need to use this function since + Qt's shortcut system removes shortcuts automatically when their + parent widget is destroyed. It is best to use QAction or + QShortcut to handle shortcuts, since they are easier to use than + this low-level function. Note also that this is an expensive + operation. + + \sa grabShortcut() setShortcutEnabled() +*/ +void QWidget::releaseShortcut(int id) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.removeShortcut(id, this, 0); +} + +/*! + If \a enable is true, the shortcut with the given \a id is + enabled; otherwise the shortcut is disabled. + + \warning You should not normally need to use this function since + Qt's shortcut system enables/disables shortcuts automatically as + widgets become hidden/visible and gain or lose focus. It is best + to use QAction or QShortcut to handle shortcuts, since they are + easier to use than this low-level function. + + \sa grabShortcut() releaseShortcut() +*/ +void QWidget::setShortcutEnabled(int id, bool enable) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutEnabled(enable, id, this, 0); +} + +/*! + \since 4.2 + + If \a enable is true, auto repeat of the shortcut with the + given \a id is enabled; otherwise it is disabled. + + \sa grabShortcut() releaseShortcut() +*/ +void QWidget::setShortcutAutoRepeat(int id, bool enable) +{ + Q_ASSERT(qApp); + if (id) + qApp->d_func()->shortcutMap.setShortcutAutoRepeat(enable, id, this, 0); +} +#endif // QT_NO_SHORTCUT +/*! + Updates the widget's micro focus. + + \sa QInputContext +*/ +void QWidget::updateMicroFocus() +{ +#if !defined(QT_NO_IM) && (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) + Q_D(QWidget); + // and optimization to update input context only it has already been created. + if (d->assignedInputContext() || qApp->d_func()->inputContext) { + QInputContext *ic = inputContext(); + if (ic) + ic->update(); + } +#endif +#ifndef QT_NO_ACCESSIBILITY + // ##### is this correct + QAccessible::updateAccessibility(this, 0, QAccessible::StateChanged); +#endif +} + + +#if defined (Q_WS_WIN) +/*! + Returns the window system handle of the widget, for low-level + access. Using this function is not portable. + + An HDC acquired with getDC() has to be released with releaseDC(). + + \warning Using this function is not portable. +*/ +HDC QWidget::getDC() const +{ + Q_D(const QWidget); + if (d->hd) + return (HDC) d->hd; + return GetDC(winId()); +} + +/*! + Releases the HDC \a hdc acquired by a previous call to getDC(). + + \warning Using this function is not portable. +*/ +void QWidget::releaseDC(HDC hdc) const +{ + Q_D(const QWidget); + // If its the widgets own dc, it will be released elsewhere. If + // its a different HDC we release it and issue a warning if it + // fails. + if (hdc != d->hd && !ReleaseDC(winId(), hdc)) + qErrnoWarning("QWidget::releaseDC(): failed to release HDC"); +} +#else +/*! + Returns the window system handle of the widget, for low-level + access. Using this function is not portable. + + The HANDLE type varies with platform; see \c qwindowdefs.h for + details. +*/ +Qt::HANDLE QWidget::handle() const +{ + Q_D(const QWidget); + if (!internalWinId() && testAttribute(Qt::WA_WState_Created)) + (void)winId(); // enforce native window + return d->hd; +} +#endif + + +/*! + Raises this widget to the top of the parent widget's stack. + + After this call the widget will be visually in front of any + overlapping sibling widgets. + + \note When using activateWindow(), you can call this function to + ensure that the window is stacked on top. + + \sa lower(), stackUnder() +*/ + +void QWidget::raise() +{ + Q_D(QWidget); + if (!isWindow()) { + QWidget *p = parentWidget(); + const int parentChildCount = p->d_func()->children.size(); + if (parentChildCount < 2) + return; + const int from = p->d_func()->children.indexOf(this); + Q_ASSERT(from >= 0); + // Do nothing if the widget is already in correct stacking order _and_ created. + if (from != parentChildCount -1) + p->d_func()->children.move(from, parentChildCount - 1); + if (!testAttribute(Qt::WA_WState_Created) && p->testAttribute(Qt::WA_WState_Created)) + create(); + else if (from == parentChildCount - 1) + return; + + QRegion region(rect()); + d->subtractOpaqueSiblings(region); + d->invalidateBuffer(region); + } + if (testAttribute(Qt::WA_WState_Created)) + d->raise_sys(); + + QEvent e(QEvent::ZOrderChange); + QApplication::sendEvent(this, &e); +} + +/*! + Lowers the widget to the bottom of the parent widget's stack. + + After this call the widget will be visually behind (and therefore + obscured by) any overlapping sibling widgets. + + \sa raise(), stackUnder() +*/ + +void QWidget::lower() +{ + Q_D(QWidget); + if (!isWindow()) { + QWidget *p = parentWidget(); + const int parentChildCount = p->d_func()->children.size(); + if (parentChildCount < 2) + return; + const int from = p->d_func()->children.indexOf(this); + Q_ASSERT(from >= 0); + // Do nothing if the widget is already in correct stacking order _and_ created. + if (from != 0) + p->d_func()->children.move(from, 0); + if (!testAttribute(Qt::WA_WState_Created) && p->testAttribute(Qt::WA_WState_Created)) + create(); + else if (from == 0) + return; + } + if (testAttribute(Qt::WA_WState_Created)) + d->lower_sys(); + + QEvent e(QEvent::ZOrderChange); + QApplication::sendEvent(this, &e); +} + + +/*! + Places the widget under \a w in the parent widget's stack. + + To make this work, the widget itself and \a w must be siblings. + + \sa raise(), lower() +*/ +void QWidget::stackUnder(QWidget* w) +{ + Q_D(QWidget); + QWidget *p = parentWidget(); + if (!w || isWindow() || p != w->parentWidget() || this == w) + return; + if (p) { + int from = p->d_func()->children.indexOf(this); + int to = p->d_func()->children.indexOf(w); + Q_ASSERT(from >= 0); + Q_ASSERT(to >= 0); + if (from < to) + --to; + // Do nothing if the widget is already in correct stacking order _and_ created. + if (from != to) + p->d_func()->children.move(from, to); + if (!testAttribute(Qt::WA_WState_Created) && p->testAttribute(Qt::WA_WState_Created)) + create(); + else if (from == to) + return; + } + if (testAttribute(Qt::WA_WState_Created)) + d->stackUnder_sys(w); + + QEvent e(QEvent::ZOrderChange); + QApplication::sendEvent(this, &e); +} + +void QWidget::styleChange(QStyle&) { } +void QWidget::enabledChange(bool) { } // compat +void QWidget::paletteChange(const QPalette &) { } // compat +void QWidget::fontChange(const QFont &) { } // compat +void QWidget::windowActivationChange(bool) { } // compat +void QWidget::languageChange() { } // compat + + +/*! + \enum QWidget::BackgroundOrigin + + \compat + + \value WidgetOrigin + \value ParentOrigin + \value WindowOrigin + \value AncestorOrigin + +*/ + +/*! + \fn bool QWidget::isVisibleToTLW() const + + Use isVisible() instead. +*/ + +/*! + \fn void QWidget::iconify() + + Use showMinimized() instead. +*/ + +/*! + \fn void QWidget::constPolish() const + + Use ensurePolished() instead. +*/ + +/*! + \fn void QWidget::reparent(QWidget *parent, Qt::WindowFlags f, const QPoint &p, bool showIt) + + Use setParent() to change the parent or the widget's widget flags; + use move() to move the widget, and use show() to show the widget. +*/ + +/*! + \fn void QWidget::reparent(QWidget *parent, const QPoint &p, bool showIt) + + Use setParent() to change the parent; use move() to move the + widget, and use show() to show the widget. +*/ + +/*! + \fn void QWidget::recreate(QWidget *parent, Qt::WindowFlags f, const QPoint & p, bool showIt) + + Use setParent() to change the parent or the widget's widget flags; + use move() to move the widget, and use show() to show the widget. +*/ + +/*! + \fn bool QWidget::hasMouse() const + + Use testAttribute(Qt::WA_UnderMouse) instead. +*/ + +/*! + \fn bool QWidget::ownCursor() const + + Use testAttribute(Qt::WA_SetCursor) instead. +*/ + +/*! + \fn bool QWidget::ownFont() const + + Use testAttribute(Qt::WA_SetFont) instead. +*/ + +/*! + \fn void QWidget::unsetFont() + + Use setFont(QFont()) instead. +*/ + +/*! + \fn bool QWidget::ownPalette() const + + Use testAttribute(Qt::WA_SetPalette) instead. +*/ + +/*! + \fn void QWidget::unsetPalette() + + Use setPalette(QPalette()) instead. +*/ + +/*! + \fn void QWidget::setEraseColor(const QColor &color) + + Use the palette instead. + + \oldcode + widget->setEraseColor(color); + \newcode + QPalette palette; + palette.setColor(widget->backgroundRole(), color); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setErasePixmap(const QPixmap &pixmap) + + Use the palette instead. + + \oldcode + widget->setErasePixmap(pixmap); + \newcode + QPalette palette; + palette.setBrush(widget->backgroundRole(), QBrush(pixmap)); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setPaletteForegroundColor(const QColor &color) + + Use the palette directly. + + \oldcode + widget->setPaletteForegroundColor(color); + \newcode + QPalette palette; + palette.setColor(widget->foregroundRole(), color); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setPaletteBackgroundColor(const QColor &color) + + Use the palette directly. + + \oldcode + widget->setPaletteBackgroundColor(color); + \newcode + QPalette palette; + palette.setColor(widget->backgroundRole(), color); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setPaletteBackgroundPixmap(const QPixmap &pixmap) + + Use the palette directly. + + \oldcode + widget->setPaletteBackgroundPixmap(pixmap); + \newcode + QPalette palette; + palette.setBrush(widget->backgroundRole(), QBrush(pixmap)); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setBackgroundPixmap(const QPixmap &pixmap) + + Use the palette instead. + + \oldcode + widget->setBackgroundPixmap(pixmap); + \newcode + QPalette palette; + palette.setBrush(widget->backgroundRole(), QBrush(pixmap)); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn void QWidget::setBackgroundColor(const QColor &color) + + Use the palette instead. + + \oldcode + widget->setBackgroundColor(color); + \newcode + QPalette palette; + palette.setColor(widget->backgroundRole(), color); + widget->setPalette(palette); + \endcode +*/ + +/*! + \fn QColorGroup QWidget::colorGroup() const + + Use QColorGroup(palette()) instead. +*/ + +/*! + \fn QWidget *QWidget::parentWidget(bool sameWindow) const + + Use the no-argument overload instead. +*/ + +/*! + \fn void QWidget::setKeyCompression(bool b) + + Use setAttribute(Qt::WA_KeyCompression, b) instead. +*/ + +/*! + \fn void QWidget::setFont(const QFont &f, bool b) + + Use the single-argument overload instead. +*/ + +/*! + \fn void QWidget::setPalette(const QPalette &p, bool b) + + Use the single-argument overload instead. +*/ + +/*! + \fn void QWidget::setBackgroundOrigin(BackgroundOrigin background) + + \obsolete +*/ + +/*! + \fn BackgroundOrigin QWidget::backgroundOrigin() const + + \obsolete + + Always returns \c WindowOrigin. +*/ + +/*! + \fn QPoint QWidget::backgroundOffset() const + + \obsolete + + Always returns QPoint(). +*/ + +/*! + \fn void QWidget::repaint(bool b) + + The boolean parameter \a b is ignored. Use the no-argument overload instead. +*/ + +/*! + \fn void QWidget::repaint(int x, int y, int w, int h, bool b) + + The boolean parameter \a b is ignored. Use the four-argument overload instead. +*/ + +/*! + \fn void QWidget::repaint(const QRect &r, bool b) + + The boolean parameter \a b is ignored. Use the single rect-argument overload instead. +*/ + +/*! + \fn void QWidget::repaint(const QRegion &rgn, bool b) + + The boolean parameter \a b is ignored. Use the single region-argument overload instead. +*/ + +/*! + \fn void QWidget::erase() + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your erasing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ + +/*! + \fn void QWidget::erase(int x, int y, int w, int h) + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your erasing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ + +/*! + \fn void QWidget::erase(const QRect &rect) + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your erasing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ + +/*! + \fn void QWidget::drawText(const QPoint &p, const QString &s) + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your drawing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ + +/*! + \fn void QWidget::drawText(int x, int y, const QString &s) + + Drawing may only take place in a QPaintEvent. Overload + paintEvent() to do your drawing and call update() to schedule a + replaint whenever necessary. See also QPainter. +*/ + +/*! + \fn QWidget *QWidget::childAt(const QPoint &p, bool includeThis) const + + Use the single point argument overload instead. +*/ + +/*! + \fn void QWidget::setCaption(const QString &c) + + Use setWindowTitle() instead. +*/ + +/*! + \fn void QWidget::setIcon(const QPixmap &i) + + Use setWindowIcon() instead. +*/ + +/*! + \fn void QWidget::setIconText(const QString &it) + + Use setWindowIconText() instead. +*/ + +/*! + \fn QString QWidget::caption() const + + Use windowTitle() instead. +*/ + +/*! + \fn QString QWidget::iconText() const + + Use windowIconText() instead. +*/ + +/*! + \fn bool QWidget::isTopLevel() const + \obsolete + + Use isWindow() instead. +*/ + +/*! + \fn bool QWidget::isRightToLeft() const + \internal +*/ + +/*! + \fn bool QWidget::isLeftToRight() const + \internal +*/ + +/*! + \fn void QWidget::setInputMethodEnabled(bool enabled) + + Use setAttribute(Qt::WA_InputMethodEnabled, \a enabled) instead. +*/ + +/*! + \fn bool QWidget::isInputMethodEnabled() const + + Use testAttribute(Qt::WA_InputMethodEnabled) instead. +*/ + +/*! + \fn void QWidget::setActiveWindow() + + Use activateWindow() instead. +*/ + +/*! + \fn bool QWidget::isShown() const + + Use !isHidden() instead (notice the exclamation mark), or use isVisible() to check whether the widget is visible. +*/ + +/*! + \fn bool QWidget::isDialog() const + + Use windowType() == Qt::Dialog instead. +*/ + +/*! + \fn bool QWidget::isPopup() const + + Use windowType() == Qt::Popup instead. +*/ + +/*! + \fn bool QWidget::isDesktop() const + + Use windowType() == Qt::Desktop instead. +*/ + +/*! + \fn void QWidget::polish() + + Use ensurePolished() instead. +*/ + +/*! + \fn QWidget *QWidget::childAt(int x, int y, bool includeThis) const + + Use the childAt() overload that doesn't have an \a includeThis parameter. + + \oldcode + return widget->childAt(x, y, true); + \newcode + QWidget *child = widget->childAt(x, y, true); + if (child) + return child; + if (widget->rect().contains(x, y)) + return widget; + \endcode +*/ + +/*! + \fn void QWidget::setSizePolicy(QSizePolicy::Policy hor, QSizePolicy::Policy ver, bool hfw) + \compat + + Use the \l sizePolicy property and heightForWidth() function instead. +*/ + +/*! + \fn bool QWidget::isUpdatesEnabled() const + \compat + + Use the \l updatesEnabled property instead. +*/ + +/*! + \macro QWIDGETSIZE_MAX + \relates QWidget + + Defines the maximum size for a QWidget object. + + The largest allowed size for a widget is QSize(QWIDGETSIZE_MAX, + QWIDGETSIZE_MAX), i.e. QSize (16777215,16777215). + + \sa QWidget::setMaximumSize() +*/ + +/*! + \fn QWidget::setupUi(QWidget *widget) + + Sets up the user interface for the specified \a widget. + + \note This function is available with widgets that derive from user + interface descriptions created using \l{uic}. + + \sa {Using a Designer UI File in Your Application} +*/ + +QRect QWidgetPrivate::frameStrut() const +{ + Q_Q(const QWidget); + if (!q->isWindow() || (q->windowType() == Qt::Desktop) || q->testAttribute(Qt::WA_DontShowOnScreen)) { + // x2 = x1 + w - 1, so w/h = 1 + return QRect(0, 0, 1, 1); + } + + if (data.fstrut_dirty +#ifndef Q_WS_WIN + // ### Fix properly for 4.3 + && q->isVisible() +#endif + && q->testAttribute(Qt::WA_WState_Created)) + const_cast<QWidgetPrivate *>(this)->updateFrameStrut(); + + return maybeTopData() ? maybeTopData()->frameStrut : QRect(); +} + +#ifdef QT_KEYPAD_NAVIGATION +/*! + \internal + + Changes the focus from the current focusWidget to a widget in + the \a direction. + + Returns true, if there was a widget in that direction +*/ +bool QWidgetPrivate::navigateToDirection(Direction direction) +{ + QWidget *targetWidget = widgetInNavigationDirection(direction); + if (targetWidget) + targetWidget->setFocus(); + return (targetWidget != 0); +} + +/*! + \internal + + Searches for a widget that is positioned in the \a direction, starting + from the current focusWidget. + + Returns the pointer to a found widget or 0, if there was no widget in + that direction. +*/ +QWidget *QWidgetPrivate::widgetInNavigationDirection(Direction direction) +{ + const QWidget *sourceWidget = QApplication::focusWidget(); + if (!sourceWidget) + return 0; + const QRect sourceRect = sourceWidget->rect().translated(sourceWidget->mapToGlobal(QPoint())); + const int sourceX = + (direction == DirectionNorth || direction == DirectionSouth) ? + (sourceRect.left() + (sourceRect.right() - sourceRect.left()) / 2) + :(direction == DirectionEast ? sourceRect.right() : sourceRect.left()); + const int sourceY = + (direction == DirectionEast || direction == DirectionWest) ? + (sourceRect.top() + (sourceRect.bottom() - sourceRect.top()) / 2) + :(direction == DirectionSouth ? sourceRect.bottom() : sourceRect.top()); + const QPoint sourcePoint(sourceX, sourceY); + const QPoint sourceCenter = sourceRect.center(); + const QWidget *sourceWindow = sourceWidget->window(); + + QWidget *targetWidget = 0; + int shortestDistance = INT_MAX; + foreach(QWidget *targetCandidate, QApplication::allWidgets()) { + + const QRect targetCandidateRect = targetCandidate->rect().translated(targetCandidate->mapToGlobal(QPoint())); + + // For focus proxies, the child widget handling the focus can have keypad navigation focus, + // but the owner of the proxy cannot. + // Additionally, empty widgets should be ignored. + if (targetCandidate->focusProxy() || targetCandidateRect.isEmpty()) + continue; + + // Only navigate to a target widget that... + if ( targetCandidate != sourceWidget + // ...takes the focus, + && targetCandidate->focusPolicy() & Qt::TabFocus + // ...is above if DirectionNorth, + && !(direction == DirectionNorth && targetCandidateRect.bottom() > sourceRect.top()) + // ...is on the right if DirectionEast, + && !(direction == DirectionEast && targetCandidateRect.left() < sourceRect.right()) + // ...is below if DirectionSouth, + && !(direction == DirectionSouth && targetCandidateRect.top() < sourceRect.bottom()) + // ...is on the left if DirectionWest, + && !(direction == DirectionWest && targetCandidateRect.right() > sourceRect.left()) + // ...is enabled, + && targetCandidate->isEnabled() + // ...is visible, + && targetCandidate->isVisible() + // ...is in the same window, + && targetCandidate->window() == sourceWindow) { + const int targetCandidateDistance = pointToRect(sourcePoint, targetCandidateRect); + if (targetCandidateDistance < shortestDistance) { + shortestDistance = targetCandidateDistance; + targetWidget = targetCandidate; + } + } + } + return targetWidget; +} + +/*! + \internal + + Tells us if it there is currently a reachable widget by keypad navigation in + a certain \a orientation. + If no navigation is possible, occurring key events in that \a orientation may + be used to interact with the value in the focused widget, even though it + currently has not the editFocus. + + \sa QWidgetPrivate::widgetInNavigationDirection(), QWidget::hasEditFocus() +*/ +bool QWidgetPrivate::canKeypadNavigate(Qt::Orientation orientation) +{ + return orientation == Qt::Horizontal? + (QWidgetPrivate::widgetInNavigationDirection(QWidgetPrivate::DirectionEast) + || QWidgetPrivate::widgetInNavigationDirection(QWidgetPrivate::DirectionWest)) + :(QWidgetPrivate::widgetInNavigationDirection(QWidgetPrivate::DirectionNorth) + || QWidgetPrivate::widgetInNavigationDirection(QWidgetPrivate::DirectionSouth)); +} +/*! + \internal + + Checks, if the \a widget is inside a QTabWidget. If is is inside + one, left/right key events will be used to switch between tabs in keypad + navigation. If there is no QTabWidget, the horizontal key events can be used +to + interact with the value in the focused widget, even though it currently has + not the editFocus. + + \sa QWidget::hasEditFocus() +*/ +bool QWidgetPrivate::inTabWidget(QWidget *widget) +{ + for (QWidget *tabWidget = widget; tabWidget; tabWidget = tabWidget->parentWidget()) + if (qobject_cast<const QTabWidget*>(tabWidget)) + return true; + return false; +} +#endif + +/*! + \preliminary + \since 4.2 + \obsolete + + Sets the window surface to be the \a surface specified. + The QWidget takes will ownership of the \a surface. + widget itself is deleted. +*/ +void QWidget::setWindowSurface(QWindowSurface *surface) +{ + // ### createWinId() ?? + +#ifndef Q_BACKINGSTORE_SUBSURFACES + if (!isTopLevel()) + return; +#endif + + Q_D(QWidget); + + QTLWExtra *topData = d->topData(); + if (topData->windowSurface == surface) + return; + + QWindowSurface *oldSurface = topData->windowSurface; + delete topData->windowSurface; + topData->windowSurface = surface; + + QWidgetBackingStore *bs = d->maybeBackingStore(); + if (!bs) + return; + + if (isTopLevel()) { + if (bs->windowSurface != oldSurface && bs->windowSurface != surface) + delete bs->windowSurface; + bs->windowSurface = surface; + } +#ifdef Q_BACKINGSTORE_SUBSURFACES + else { + bs->subSurfaces.append(surface); + } + bs->subSurfaces.removeOne(oldSurface); +#endif +} + +/*! + \preliminary + \since 4.2 + + Returns the QWindowSurface this widget will be drawn into. +*/ +QWindowSurface *QWidget::windowSurface() const +{ + Q_D(const QWidget); + QTLWExtra *extra = d->maybeTopData(); + if (extra && extra->windowSurface) + return extra->windowSurface; + + QWidgetBackingStore *bs = d->maybeBackingStore(); + +#ifdef Q_BACKINGSTORE_SUBSURFACES + if (bs && bs->subSurfaces.isEmpty()) + return bs->windowSurface; + + if (!isTopLevel()) { + const QWidget *w = parentWidget(); + while (w) { + QTLWExtra *extra = w->d_func()->maybeTopData(); + if (extra && extra->windowSurface) + return extra->windowSurface; + if (w->isTopLevel()) + break; + w = w->parentWidget(); + } + } +#endif // Q_BACKINGSTORE_SUBSURFACES + + return bs ? bs->windowSurface : 0; +} + +void QWidgetPrivate::getLayoutItemMargins(int *left, int *top, int *right, int *bottom) const +{ + if (left) + *left = (int)leftLayoutItemMargin; + if (top) + *top = (int)topLayoutItemMargin; + if (right) + *right = (int)rightLayoutItemMargin; + if (bottom) + *bottom = (int)bottomLayoutItemMargin; +} + +void QWidgetPrivate::setLayoutItemMargins(int left, int top, int right, int bottom) +{ + if (leftLayoutItemMargin == left + && topLayoutItemMargin == top + && rightLayoutItemMargin == right + && bottomLayoutItemMargin == bottom) + return; + + Q_Q(QWidget); + leftLayoutItemMargin = (signed char)left; + topLayoutItemMargin = (signed char)top; + rightLayoutItemMargin = (signed char)right; + bottomLayoutItemMargin = (signed char)bottom; + q->updateGeometry(); +} + +void QWidgetPrivate::setLayoutItemMargins(QStyle::SubElement element, const QStyleOption *opt) +{ + Q_Q(QWidget); + QStyleOption myOpt; + if (!opt) { + myOpt.initFrom(q); + myOpt.rect.setRect(0, 0, 32768, 32768); // arbitrary + opt = &myOpt; + } + + QRect liRect = q->style()->subElementRect(element, opt, q); + if (liRect.isValid()) { + leftLayoutItemMargin = (signed char)(opt->rect.left() - liRect.left()); + topLayoutItemMargin = (signed char)(opt->rect.top() - liRect.top()); + rightLayoutItemMargin = (signed char)(liRect.right() - opt->rect.right()); + bottomLayoutItemMargin = (signed char)(liRect.bottom() - opt->rect.bottom()); + } else { + leftLayoutItemMargin = 0; + topLayoutItemMargin = 0; + rightLayoutItemMargin = 0; + bottomLayoutItemMargin = 0; + } +} +// resets the Qt::WA_QuitOnClose attribute to the default value for transient widgets. +void QWidgetPrivate::adjustQuitOnCloseAttribute() +{ + Q_Q(QWidget); + + if (!q->parentWidget()) { + Qt::WindowType type = q->windowType(); + if (type == Qt::Widget || type == Qt::SubWindow) + type = Qt::Window; + if (type != Qt::Widget && type != Qt::Window && type != Qt::Dialog) + q->setAttribute(Qt::WA_QuitOnClose, false); + } +} + + + +Q_GUI_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget) +{ + return widget->data; +} + +Q_GUI_EXPORT QWidgetPrivate *qt_widget_private(QWidget *widget) +{ + return widget->d_func(); +} + + +#ifndef QT_NO_GRAPHICSVIEW +/*! + \since 4.5 + + Returns the proxy widget for the corresponding embedded widget in a graphics + view; otherwise returns 0. + + \sa QGraphicsProxyWidget::createProxyForChildWidget(), + QGraphicsScene::addWidget() + */ +QGraphicsProxyWidget *QWidget::graphicsProxyWidget() const +{ + Q_D(const QWidget); + if (d->extra) { + return d->extra->proxyWidget; + } + return 0; +} +#endif + + +/*! + \typedef QWidgetList + \relates QWidget + + Synonym for QList<QWidget *>. +*/ + +#ifndef QT_NO_GESTURES +/*! + Subscribes the widget to a given \a gesture with specific \a flags. + + \sa ungrabGesture(), QGestureEvent + \since 4.6 +*/ +void QWidget::grabGesture(Qt::GestureType gesture, Qt::GestureFlags flags) +{ + Q_D(QWidget); + d->gestureContext.insert(gesture, flags); + (void)QGestureManager::instance(); // create a gesture manager +} + +/*! + Unsubscribes the widget from a given \a gesture type + + \sa grabGesture(), QGestureEvent + \since 4.6 +*/ +void QWidget::ungrabGesture(Qt::GestureType gesture) +{ + Q_D(QWidget); + if (d->gestureContext.remove(gesture)) { + if (QGestureManager *manager = QGestureManager::instance()) + manager->cleanupCachedGestures(this, gesture); + } +} +#endif // QT_NO_GESTURES + +/*! + \typedef WId + \relates QWidget + + Platform dependent window identifier. +*/ + +/*! + \fn void QWidget::destroy(bool destroyWindow, bool destroySubWindows) + + Frees up window system resources. Destroys the widget window if \a + destroyWindow is true. + + destroy() calls itself recursively for all the child widgets, + passing \a destroySubWindows for the \a destroyWindow parameter. + To have more control over destruction of subwidgets, destroy + subwidgets selectively first. + + This function is usually called from the QWidget destructor. +*/ + +/*! + \fn QPaintEngine *QWidget::paintEngine() const + + Returns the widget's paint engine. + + Note that this function should not be called explicitly by the + user, since it's meant for reimplementation purposes only. The + function is called by Qt internally, and the default + implementation may not always return a valid pointer. +*/ + +/*! + \fn QPoint QWidget::mapToGlobal(const QPoint &pos) const + + Translates the widget coordinate \a pos to global screen + coordinates. For example, \c{mapToGlobal(QPoint(0,0))} would give + the global coordinates of the top-left pixel of the widget. + + \sa mapFromGlobal() mapTo() mapToParent() +*/ + +/*! + \fn QPoint QWidget::mapFromGlobal(const QPoint &pos) const + + Translates the global screen coordinate \a pos to widget + coordinates. + + \sa mapToGlobal() mapFrom() mapFromParent() +*/ + +/*! + \fn void QWidget::grabMouse() + + Grabs the mouse input. + + This widget receives all mouse events until releaseMouse() is + called; other widgets get no mouse events at all. Keyboard + events are not affected. Use grabKeyboard() if you want to grab + that. + + \warning Bugs in mouse-grabbing applications very often lock the + terminal. Use this function with extreme caution, and consider + using the \c -nograb command line option while debugging. + + It is almost never necessary to grab the mouse when using Qt, as + Qt grabs and releases it sensibly. In particular, Qt grabs the + mouse when a mouse button is pressed and keeps it until the last + button is released. + + \note Only visible widgets can grab mouse input. If isVisible() + returns false for a widget, that widget cannot call grabMouse(). + + \note \bold{(Mac OS X developers)} For \e Cocoa, calling + grabMouse() on a widget only works when the mouse is inside the + frame of that widget. For \e Carbon, it works outside the widget's + frame as well, like for Windows and X11. + + \sa releaseMouse() grabKeyboard() releaseKeyboard() +*/ + +/*! + \fn void QWidget::grabMouse(const QCursor &cursor) + \overload grabMouse() + + Grabs the mouse input and changes the cursor shape. + + The cursor will assume shape \a cursor (for as long as the mouse + focus is grabbed) and this widget will be the only one to receive + mouse events until releaseMouse() is called(). + + \warning Grabbing the mouse might lock the terminal. + + \note \bold{(Mac OS X developers)} See the note in QWidget::grabMouse(). + + \sa releaseMouse(), grabKeyboard(), releaseKeyboard(), setCursor() +*/ + +/*! + \fn void QWidget::releaseMouse() + + Releases the mouse grab. + + \sa grabMouse(), grabKeyboard(), releaseKeyboard() +*/ + +/*! + \fn void QWidget::grabKeyboard() + + Grabs the keyboard input. + + This widget receives all keyboard events until releaseKeyboard() + is called; other widgets get no keyboard events at all. Mouse + events are not affected. Use grabMouse() if you want to grab that. + + The focus widget is not affected, except that it doesn't receive + any keyboard events. setFocus() moves the focus as usual, but the + new focus widget receives keyboard events only after + releaseKeyboard() is called. + + If a different widget is currently grabbing keyboard input, that + widget's grab is released first. + + \sa releaseKeyboard() grabMouse() releaseMouse() focusWidget() +*/ + +/*! + \fn void QWidget::releaseKeyboard() + + Releases the keyboard grab. + + \sa grabKeyboard(), grabMouse(), releaseMouse() +*/ + +/*! + \fn QWidget *QWidget::mouseGrabber() + + Returns the widget that is currently grabbing the mouse input. + + If no widget in this application is currently grabbing the mouse, + 0 is returned. + + \sa grabMouse(), keyboardGrabber() +*/ + +/*! + \fn QWidget *QWidget::keyboardGrabber() + + Returns the widget that is currently grabbing the keyboard input. + + If no widget in this application is currently grabbing the + keyboard, 0 is returned. + + \sa grabMouse(), mouseGrabber() +*/ + +/*! + \fn void QWidget::activateWindow() + + Sets the top-level widget containing this widget to be the active + window. + + An active window is a visible top-level window that has the + keyboard input focus. + + This function performs the same operation as clicking the mouse on + the title bar of a top-level window. On X11, the result depends on + the Window Manager. If you want to ensure that the window is + stacked on top as well you should also call raise(). Note that the + window must be visible, otherwise activateWindow() has no effect. + + On Windows, if you are calling this when the application is not + currently the active one then it will not make it the active + window. It will change the color of the taskbar entry to indicate + that the window has changed in some way. This is because Microsoft + does not allow an application to interrupt what the user is currently + doing in another application. + + \sa isActiveWindow(), window(), show() +*/ + +/*! + \fn int QWidget::metric(PaintDeviceMetric m) const + + Internal implementation of the virtual QPaintDevice::metric() + function. + + \a m is the metric to get. +*/ + +/*! + \fn void QWidget::setMask(const QRegion ®ion) + \overload + + Causes only the parts of the widget which overlap \a region to be + visible. If the region includes pixels outside the rect() of the + widget, window system controls in that area may or may not be + visible, depending on the platform. + + Note that this effect can be slow if the region is particularly + complex. + + \sa windowOpacity +*/ +void QWidget::setMask(const QRegion &newMask) +{ + Q_D(QWidget); + + d->createExtra(); + if (newMask == d->extra->mask) + return; + +#ifndef QT_NO_BACKINGSTORE + const QRegion oldMask(d->extra->mask); +#endif + + d->extra->mask = newMask; + d->extra->hasMask = !newMask.isEmpty(); + +#ifndef QT_MAC_USE_COCOA + if (!testAttribute(Qt::WA_WState_Created)) + return; +#endif + + d->setMask_sys(newMask); + +#ifndef QT_NO_BACKINGSTORE + if (!isVisible()) + return; + + if (!d->extra->hasMask) { + // Mask was cleared; update newly exposed area. + QRegion expose(rect()); + expose -= oldMask; + if (!expose.isEmpty()) { + d->setDirtyOpaqueRegion(); + update(expose); + } + return; + } + + if (!isWindow()) { + // Update newly exposed area on the parent widget. + QRegion parentExpose(rect()); + parentExpose -= newMask; + if (!parentExpose.isEmpty()) { + d->setDirtyOpaqueRegion(); + parentExpose.translate(data->crect.topLeft()); + parentWidget()->update(parentExpose); + } + + // Update newly exposed area on this widget + if (!oldMask.isEmpty()) + update(newMask - oldMask); + } +#endif +} + +/*! + \fn void QWidget::setMask(const QBitmap &bitmap) + + Causes only the pixels of the widget for which \a bitmap has a + corresponding 1 bit to be visible. If the region includes pixels + outside the rect() of the widget, window system controls in that + area may or may not be visible, depending on the platform. + + Note that this effect can be slow if the region is particularly + complex. + + The following code shows how an image with an alpha channel can be + used to generate a mask for a widget: + + \snippet doc/src/snippets/widget-mask/main.cpp 0 + + The label shown by this code is masked using the image it contains, + giving the appearance that an irregularly-shaped image is being drawn + directly onto the screen. + + Masked widgets receive mouse events only on their visible + portions. + + \sa clearMask(), windowOpacity(), {Shaped Clock Example} +*/ +void QWidget::setMask(const QBitmap &bitmap) +{ + setMask(QRegion(bitmap)); +} + +/*! + \fn void QWidget::clearMask() + + Removes any mask set by setMask(). + + \sa setMask() +*/ +void QWidget::clearMask() +{ + setMask(QRegion()); +} + +/*! \fn const QX11Info &QWidget::x11Info() const + Returns information about the configuration of the X display used to display + the widget. + + \warning This function is only available on X11. +*/ + +/*! \fn Qt::HANDLE QWidget::x11PictureHandle() const + Returns the X11 Picture handle of the widget for XRender + support. Use of this function is not portable. This function will + return 0 if XRender support is not compiled into Qt, if the + XRender extension is not supported on the X11 display, or if the + handle could not be created. +*/ + +#ifdef Q_OS_SYMBIAN +void QWidgetPrivate::_q_delayedDestroy(WId winId) +{ + delete winId; +} +#endif + +#if QT_MAC_USE_COCOA +void QWidgetPrivate::syncUnifiedMode() { + // The whole purpose of this method is to keep the unifiedToolbar in sync. + // That means making sure we either exchange the drawing methods or we let + // the toolbar know that it does not require to draw the baseline. + Q_Q(QWidget); + // This function makes sense only if this is a top level + if(!q->isWindow()) + return; + OSWindowRef window = qt_mac_window_for(q); + if(changeMethods) { + // Ok, we are in documentMode. + if(originalDrawMethod) + qt_mac_replaceDrawRect(window, this); + } else { + if(!originalDrawMethod) + qt_mac_replaceDrawRectOriginal(window, this); + } +} + +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE + +#include "moc_qwidget.cpp" + diff --git a/src/gui/kernel/qwidget.h b/src/gui/kernel/qwidget.h new file mode 100644 index 0000000000..7d4726ed6d --- /dev/null +++ b/src/gui/kernel/qwidget.h @@ -0,0 +1,1090 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGET_H +#define QWIDGET_H + +#include <QtGui/qwindowdefs.h> +#include <QtCore/qobject.h> +#include <QtCore/qmargins.h> +#include <QtGui/qpaintdevice.h> +#include <QtGui/qpalette.h> +#include <QtGui/qfont.h> +#include <QtGui/qfontmetrics.h> +#include <QtGui/qfontinfo.h> +#include <QtGui/qsizepolicy.h> +#include <QtGui/qregion.h> +#include <QtGui/qbrush.h> +#include <QtGui/qcursor.h> +#include <QtGui/qkeysequence.h> + +#ifdef Q_WS_QPA //should this go somewhere else? +#include <QtGui/qplatformwindowformat_qpa.h> +#endif + +#ifdef QT_INCLUDE_COMPAT +#include <QtGui/qevent.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QLayout; +class QWSRegionManager; +class QStyle; +class QAction; +class QVariant; + +class QActionEvent; +class QMouseEvent; +class QWheelEvent; +class QHoverEvent; +class QKeyEvent; +class QFocusEvent; +class QPaintEvent; +class QMoveEvent; +class QResizeEvent; +class QCloseEvent; +class QContextMenuEvent; +class QInputMethodEvent; +class QTabletEvent; +class QDragEnterEvent; +class QDragMoveEvent; +class QDragLeaveEvent; +class QDropEvent; +class QShowEvent; +class QHideEvent; +class QInputContext; +class QIcon; +class QWindowSurface; +class QPlatformWindow; +class QLocale; +class QGraphicsProxyWidget; +class QGraphicsEffect; +class QRasterWindowSurface; +class QUnifiedToolbarSurface; +#if defined(Q_WS_X11) +class QX11Info; +#endif + +class QWidgetData +{ +public: + WId winid; + uint widget_attributes; + Qt::WindowFlags window_flags; + uint window_state : 4; + uint focus_policy : 4; + uint sizehint_forced :1; + uint is_closing :1; + uint in_show : 1; + uint in_set_window_state : 1; + mutable uint fstrut_dirty : 1; + uint context_menu_policy : 3; + uint window_modality : 2; + uint in_destructor : 1; + uint unused : 13; + QRect crect; + mutable QPalette pal; + QFont fnt; +#if defined(Q_WS_QWS) +// QRegion req_region; // Requested region +// mutable QRegion paintable_region; // Paintable region +// mutable bool paintable_region_dirty;// needs to be recalculated +// mutable QRegion alloc_region; // Allocated region +// mutable bool alloc_region_dirty; // needs to be recalculated +// mutable int overlapping_children; // Handle overlapping children + + int alloc_region_index; +// int alloc_region_revision; +#endif + QRect wrect; +}; + +class QWidgetPrivate; + +class Q_GUI_EXPORT QWidget : public QObject, public QPaintDevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWidget) + + Q_PROPERTY(bool modal READ isModal) + Q_PROPERTY(Qt::WindowModality windowModality READ windowModality WRITE setWindowModality) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry) + Q_PROPERTY(QRect frameGeometry READ frameGeometry) + Q_PROPERTY(QRect normalGeometry READ normalGeometry) + Q_PROPERTY(int x READ x) + Q_PROPERTY(int y READ y) + Q_PROPERTY(QPoint pos READ pos WRITE move DESIGNABLE false STORED false) + Q_PROPERTY(QSize frameSize READ frameSize) + Q_PROPERTY(QSize size READ size WRITE resize DESIGNABLE false STORED false) + Q_PROPERTY(int width READ width) + Q_PROPERTY(int height READ height) + Q_PROPERTY(QRect rect READ rect) + Q_PROPERTY(QRect childrenRect READ childrenRect) + Q_PROPERTY(QRegion childrenRegion READ childrenRegion) + Q_PROPERTY(QSizePolicy sizePolicy READ sizePolicy WRITE setSizePolicy) + Q_PROPERTY(QSize minimumSize READ minimumSize WRITE setMinimumSize) + Q_PROPERTY(QSize maximumSize READ maximumSize WRITE setMaximumSize) + Q_PROPERTY(int minimumWidth READ minimumWidth WRITE setMinimumWidth STORED false DESIGNABLE false) + Q_PROPERTY(int minimumHeight READ minimumHeight WRITE setMinimumHeight STORED false DESIGNABLE false) + Q_PROPERTY(int maximumWidth READ maximumWidth WRITE setMaximumWidth STORED false DESIGNABLE false) + Q_PROPERTY(int maximumHeight READ maximumHeight WRITE setMaximumHeight STORED false DESIGNABLE false) + Q_PROPERTY(QSize sizeIncrement READ sizeIncrement WRITE setSizeIncrement) + Q_PROPERTY(QSize baseSize READ baseSize WRITE setBaseSize) + Q_PROPERTY(QPalette palette READ palette WRITE setPalette) + Q_PROPERTY(QFont font READ font WRITE setFont) +#ifndef QT_NO_CURSOR + Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor) +#endif + Q_PROPERTY(bool mouseTracking READ hasMouseTracking WRITE setMouseTracking) + Q_PROPERTY(bool isActiveWindow READ isActiveWindow) + Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy) + Q_PROPERTY(bool focus READ hasFocus) + Q_PROPERTY(Qt::ContextMenuPolicy contextMenuPolicy READ contextMenuPolicy WRITE setContextMenuPolicy) + Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled DESIGNABLE false) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible DESIGNABLE false) + Q_PROPERTY(bool minimized READ isMinimized) + Q_PROPERTY(bool maximized READ isMaximized) + Q_PROPERTY(bool fullScreen READ isFullScreen) + Q_PROPERTY(QSize sizeHint READ sizeHint) + Q_PROPERTY(QSize minimumSizeHint READ minimumSizeHint) + Q_PROPERTY(bool acceptDrops READ acceptDrops WRITE setAcceptDrops) + Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle DESIGNABLE isWindow) + Q_PROPERTY(QIcon windowIcon READ windowIcon WRITE setWindowIcon DESIGNABLE isWindow) + Q_PROPERTY(QString windowIconText READ windowIconText WRITE setWindowIconText DESIGNABLE isWindow) + Q_PROPERTY(double windowOpacity READ windowOpacity WRITE setWindowOpacity DESIGNABLE isWindow) + Q_PROPERTY(bool windowModified READ isWindowModified WRITE setWindowModified DESIGNABLE isWindow) +#ifndef QT_NO_TOOLTIP + Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip) +#endif +#ifndef QT_NO_STATUSTIP + Q_PROPERTY(QString statusTip READ statusTip WRITE setStatusTip) +#endif +#ifndef QT_NO_WHATSTHIS + Q_PROPERTY(QString whatsThis READ whatsThis WRITE setWhatsThis) +#endif +#ifndef QT_NO_ACCESSIBILITY + Q_PROPERTY(QString accessibleName READ accessibleName WRITE setAccessibleName) + Q_PROPERTY(QString accessibleDescription READ accessibleDescription WRITE setAccessibleDescription) +#endif + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection RESET unsetLayoutDirection) + QDOC_PROPERTY(Qt::WindowFlags windowFlags READ windowFlags WRITE setWindowFlags) + Q_PROPERTY(bool autoFillBackground READ autoFillBackground WRITE setAutoFillBackground) +#ifndef QT_NO_STYLE_STYLESHEET + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) +#endif + Q_PROPERTY(QLocale locale READ locale WRITE setLocale RESET unsetLocale) + Q_PROPERTY(QString windowFilePath READ windowFilePath WRITE setWindowFilePath DESIGNABLE isWindow) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints) + +public: + enum RenderFlag { + DrawWindowBackground = 0x1, + DrawChildren = 0x2, + IgnoreMask = 0x4 + }; + Q_DECLARE_FLAGS(RenderFlags, RenderFlag) + + explicit QWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QWidget(QWidget* parent, const char *name, Qt::WindowFlags f = 0); +#endif + ~QWidget(); + + int devType() const; + + WId winId() const; + void createWinId(); // internal, going away + inline WId internalWinId() const { return data->winid; } + WId effectiveWinId() const; + + // GUI style setting + QStyle *style() const; + void setStyle(QStyle *); + // Widget types and states + + bool isTopLevel() const; + bool isWindow() const; + + bool isModal() const; + Qt::WindowModality windowModality() const; + void setWindowModality(Qt::WindowModality windowModality); + + bool isEnabled() const; + bool isEnabledTo(QWidget*) const; + bool isEnabledToTLW() const; + +public Q_SLOTS: + void setEnabled(bool); + void setDisabled(bool); + void setWindowModified(bool); + + // Widget coordinates + +public: + QRect frameGeometry() const; + const QRect &geometry() const; + QRect normalGeometry() const; + + int x() const; + int y() const; + QPoint pos() const; + QSize frameSize() const; + QSize size() const; + inline int width() const; + inline int height() const; + inline QRect rect() const; + QRect childrenRect() const; + QRegion childrenRegion() const; + + QSize minimumSize() const; + QSize maximumSize() const; + int minimumWidth() const; + int minimumHeight() const; + int maximumWidth() const; + int maximumHeight() const; + void setMinimumSize(const QSize &); + void setMinimumSize(int minw, int minh); + void setMaximumSize(const QSize &); + void setMaximumSize(int maxw, int maxh); + void setMinimumWidth(int minw); + void setMinimumHeight(int minh); + void setMaximumWidth(int maxw); + void setMaximumHeight(int maxh); + +#ifdef Q_QDOC + void setupUi(QWidget *widget); +#endif + + QSize sizeIncrement() const; + void setSizeIncrement(const QSize &); + void setSizeIncrement(int w, int h); + QSize baseSize() const; + void setBaseSize(const QSize &); + void setBaseSize(int basew, int baseh); + + void setFixedSize(const QSize &); + void setFixedSize(int w, int h); + void setFixedWidth(int w); + void setFixedHeight(int h); + + // Widget coordinate mapping + + QPoint mapToGlobal(const QPoint &) const; + QPoint mapFromGlobal(const QPoint &) const; + QPoint mapToParent(const QPoint &) const; + QPoint mapFromParent(const QPoint &) const; + QPoint mapTo(QWidget *, const QPoint &) const; + QPoint mapFrom(QWidget *, const QPoint &) const; + + QWidget *window() const; + QWidget *nativeParentWidget() const; + inline QWidget *topLevelWidget() const { return window(); } + + // Widget appearance functions + const QPalette &palette() const; + void setPalette(const QPalette &); + + void setBackgroundRole(QPalette::ColorRole); + QPalette::ColorRole backgroundRole() const; + + void setForegroundRole(QPalette::ColorRole); + QPalette::ColorRole foregroundRole() const; + + const QFont &font() const; + void setFont(const QFont &); + QFontMetrics fontMetrics() const; + QFontInfo fontInfo() const; + +#ifndef QT_NO_CURSOR + QCursor cursor() const; + void setCursor(const QCursor &); + void unsetCursor(); +#endif + + void setMouseTracking(bool enable); + bool hasMouseTracking() const; + bool underMouse() const; + + void setMask(const QBitmap &); + void setMask(const QRegion &); + QRegion mask() const; + void clearMask(); + + void render(QPaintDevice *target, const QPoint &targetOffset = QPoint(), + const QRegion &sourceRegion = QRegion(), + RenderFlags renderFlags = RenderFlags(DrawWindowBackground | DrawChildren)); + + void render(QPainter *painter, const QPoint &targetOffset = QPoint(), + const QRegion &sourceRegion = QRegion(), + RenderFlags renderFlags = RenderFlags(DrawWindowBackground | DrawChildren)); + +#ifndef QT_NO_GRAPHICSEFFECT + QGraphicsEffect *graphicsEffect() const; + void setGraphicsEffect(QGraphicsEffect *effect); +#endif //QT_NO_GRAPHICSEFFECT + +#ifndef QT_NO_GESTURES + void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags()); + void ungrabGesture(Qt::GestureType type); +#endif + +public Q_SLOTS: + void setWindowTitle(const QString &); +#ifndef QT_NO_STYLE_STYLESHEET + void setStyleSheet(const QString& styleSheet); +#endif +public: +#ifndef QT_NO_STYLE_STYLESHEET + QString styleSheet() const; +#endif + QString windowTitle() const; + void setWindowIcon(const QIcon &icon); + QIcon windowIcon() const; + void setWindowIconText(const QString &); + QString windowIconText() const; + void setWindowRole(const QString &); + QString windowRole() const; + void setWindowFilePath(const QString &filePath); + QString windowFilePath() const; + + void setWindowOpacity(qreal level); + qreal windowOpacity() const; + + bool isWindowModified() const; +#ifndef QT_NO_TOOLTIP + void setToolTip(const QString &); + QString toolTip() const; +#endif +#ifndef QT_NO_STATUSTIP + void setStatusTip(const QString &); + QString statusTip() const; +#endif +#ifndef QT_NO_WHATSTHIS + void setWhatsThis(const QString &); + QString whatsThis() const; +#endif +#ifndef QT_NO_ACCESSIBILITY + QString accessibleName() const; + void setAccessibleName(const QString &name); + QString accessibleDescription() const; + void setAccessibleDescription(const QString &description); +#endif + + void setLayoutDirection(Qt::LayoutDirection direction); + Qt::LayoutDirection layoutDirection() const; + void unsetLayoutDirection(); + + void setLocale(const QLocale &locale); + QLocale locale() const; + void unsetLocale(); + + inline bool isRightToLeft() const { return layoutDirection() == Qt::RightToLeft; } + inline bool isLeftToRight() const { return layoutDirection() == Qt::LeftToRight; } + +public Q_SLOTS: + inline void setFocus() { setFocus(Qt::OtherFocusReason); } + +public: + bool isActiveWindow() const; + void activateWindow(); + void clearFocus(); + + void setFocus(Qt::FocusReason reason); + Qt::FocusPolicy focusPolicy() const; + void setFocusPolicy(Qt::FocusPolicy policy); + bool hasFocus() const; + static void setTabOrder(QWidget *, QWidget *); + void setFocusProxy(QWidget *); + QWidget *focusProxy() const; + Qt::ContextMenuPolicy contextMenuPolicy() const; + void setContextMenuPolicy(Qt::ContextMenuPolicy policy); + + // Grab functions + void grabMouse(); +#ifndef QT_NO_CURSOR + void grabMouse(const QCursor &); +#endif + void releaseMouse(); + void grabKeyboard(); + void releaseKeyboard(); +#ifndef QT_NO_SHORTCUT + int grabShortcut(const QKeySequence &key, Qt::ShortcutContext context = Qt::WindowShortcut); + void releaseShortcut(int id); + void setShortcutEnabled(int id, bool enable = true); + void setShortcutAutoRepeat(int id, bool enable = true); +#endif + static QWidget *mouseGrabber(); + static QWidget *keyboardGrabber(); + + // Update/refresh functions + inline bool updatesEnabled() const; + void setUpdatesEnabled(bool enable); + +#if 0 //def Q_WS_QWS + void repaintUnclipped(const QRegion &, bool erase = true); +#endif + +#ifndef QT_NO_GRAPHICSVIEW + QGraphicsProxyWidget *graphicsProxyWidget() const; +#endif + +public Q_SLOTS: + void update(); + void repaint(); + +public: + inline void update(int x, int y, int w, int h); + void update(const QRect&); + void update(const QRegion&); + + void repaint(int x, int y, int w, int h); + void repaint(const QRect &); + void repaint(const QRegion &); + +public Q_SLOTS: + // Widget management functions + + virtual void setVisible(bool visible); + inline void setHidden(bool hidden) { setVisible(!hidden); } +#ifndef Q_WS_WINCE + inline void show() { setVisible(true); } +#else + void show(); +#endif + inline void hide() { setVisible(false); } + inline QT_MOC_COMPAT void setShown(bool shown) { setVisible(shown); } + + void showMinimized(); + void showMaximized(); + void showFullScreen(); + void showNormal(); + + bool close(); + void raise(); + void lower(); + +public: + void stackUnder(QWidget*); + void move(int x, int y); + void move(const QPoint &); + void resize(int w, int h); + void resize(const QSize &); + inline void setGeometry(int x, int y, int w, int h); + void setGeometry(const QRect &); + QByteArray saveGeometry() const; + bool restoreGeometry(const QByteArray &geometry); + void adjustSize(); + bool isVisible() const; + bool isVisibleTo(QWidget*) const; + // ### Qt 5: bool isVisibleTo(_const_ QWidget *) const + inline bool isHidden() const; + + bool isMinimized() const; + bool isMaximized() const; + bool isFullScreen() const; + + Qt::WindowStates windowState() const; + void setWindowState(Qt::WindowStates state); + void overrideWindowState(Qt::WindowStates state); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + QSizePolicy sizePolicy() const; + void setSizePolicy(QSizePolicy); + inline void setSizePolicy(QSizePolicy::Policy horizontal, QSizePolicy::Policy vertical); + virtual int heightForWidth(int) const; + + QRegion visibleRegion() const; + + void setContentsMargins(int left, int top, int right, int bottom); + void setContentsMargins(const QMargins &margins); + void getContentsMargins(int *left, int *top, int *right, int *bottom) const; + QMargins contentsMargins() const; + + QRect contentsRect() const; + +public: + QLayout *layout() const; + void setLayout(QLayout *); + void updateGeometry(); + + void setParent(QWidget *parent); + void setParent(QWidget *parent, Qt::WindowFlags f); + + void scroll(int dx, int dy); + void scroll(int dx, int dy, const QRect&); + + // Misc. functions + + QWidget *focusWidget() const; + QWidget *nextInFocusChain() const; + QWidget *previousInFocusChain() const; + + // drag and drop + bool acceptDrops() const; + void setAcceptDrops(bool on); + +#ifndef QT_NO_ACTION + //actions + void addAction(QAction *action); + void addActions(QList<QAction*> actions); + void insertAction(QAction *before, QAction *action); + void insertActions(QAction *before, QList<QAction*> actions); + void removeAction(QAction *action); + QList<QAction*> actions() const; +#endif + + QWidget *parentWidget() const; + + void setWindowFlags(Qt::WindowFlags type); + inline Qt::WindowFlags windowFlags() const; + void overrideWindowFlags(Qt::WindowFlags type); + + inline Qt::WindowType windowType() const; + + static QWidget *find(WId); +#ifdef QT3_SUPPORT + static QT3_SUPPORT QWidgetMapper *wmapper(); +#endif + inline QWidget *childAt(int x, int y) const; + QWidget *childAt(const QPoint &p) const; + +#if defined(Q_WS_X11) + const QX11Info &x11Info() const; + Qt::HANDLE x11PictureHandle() const; +#endif + +#if defined(Q_WS_MAC) + Qt::HANDLE macQDHandle() const; + Qt::HANDLE macCGHandle() const; +#endif + +#if defined(Q_WS_WIN) + HDC getDC() const; + void releaseDC(HDC) const; +#else + Qt::HANDLE handle() const; +#endif + + void setAttribute(Qt::WidgetAttribute, bool on = true); + inline bool testAttribute(Qt::WidgetAttribute) const; + + QPaintEngine *paintEngine() const; + + void ensurePolished() const; + + QInputContext *inputContext(); + void setInputContext(QInputContext *); + + bool isAncestorOf(const QWidget *child) const; + +#ifdef QT_KEYPAD_NAVIGATION + bool hasEditFocus() const; + void setEditFocus(bool on); +#endif + + bool autoFillBackground() const; + void setAutoFillBackground(bool enabled); + + void setWindowSurface(QWindowSurface *surface); + QWindowSurface *windowSurface() const; + +#if defined(Q_WS_QPA) + void setPlatformWindow(QPlatformWindow *window); + QPlatformWindow *platformWindow() const; + + void setPlatformWindowFormat(const QPlatformWindowFormat &format); + QPlatformWindowFormat platformWindowFormat() const; + + friend class QDesktopScreenWidget; +#endif + +Q_SIGNALS: + void customContextMenuRequested(const QPoint &pos); + +protected: + // Event handlers + bool event(QEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseDoubleClickEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); +#ifndef QT_NO_WHEELEVENT + virtual void wheelEvent(QWheelEvent *); +#endif + virtual void keyPressEvent(QKeyEvent *); + virtual void keyReleaseEvent(QKeyEvent *); + virtual void focusInEvent(QFocusEvent *); + virtual void focusOutEvent(QFocusEvent *); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + virtual void paintEvent(QPaintEvent *); + virtual void moveEvent(QMoveEvent *); + virtual void resizeEvent(QResizeEvent *); + virtual void closeEvent(QCloseEvent *); +#ifndef QT_NO_CONTEXTMENU + virtual void contextMenuEvent(QContextMenuEvent *); +#endif +#ifndef QT_NO_TABLETEVENT + virtual void tabletEvent(QTabletEvent *); +#endif +#ifndef QT_NO_ACTION + virtual void actionEvent(QActionEvent *); +#endif + +#ifndef QT_NO_DRAGANDDROP + virtual void dragEnterEvent(QDragEnterEvent *); + virtual void dragMoveEvent(QDragMoveEvent *); + virtual void dragLeaveEvent(QDragLeaveEvent *); + virtual void dropEvent(QDropEvent *); +#endif + + virtual void showEvent(QShowEvent *); + virtual void hideEvent(QHideEvent *); + +#if defined(Q_WS_MAC) + virtual bool macEvent(EventHandlerCallRef, EventRef); +#endif +#if defined(Q_WS_WIN) + virtual bool winEvent(MSG *message, long *result); +#endif +#if defined(Q_WS_X11) + virtual bool x11Event(XEvent *); +#endif +#if defined(Q_WS_QWS) + virtual bool qwsEvent(QWSEvent *); +#endif + + // Misc. protected functions + virtual void changeEvent(QEvent *); + + int metric(PaintDeviceMetric) const; + + virtual void inputMethodEvent(QInputMethodEvent *); +public: + virtual QVariant inputMethodQuery(Qt::InputMethodQuery) const; + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + +protected: + void resetInputContext(); +protected Q_SLOTS: + void updateMicroFocus(); +protected: + + void create(WId = 0, bool initializeWindow = true, + bool destroyOldWindow = true); + void destroy(bool destroyWindow = true, + bool destroySubWindows = true); + + virtual bool focusNextPrevChild(bool next); + inline bool focusNextChild() { return focusNextPrevChild(true); } + inline bool focusPreviousChild() { return focusNextPrevChild(false); } + +protected: + QWidget(QWidgetPrivate &d, QWidget* parent, Qt::WindowFlags f); +private: + + bool testAttribute_helper(Qt::WidgetAttribute) const; + + QLayout *takeLayout(); + + friend class QBackingStoreDevice; + friend class QWidgetBackingStore; + friend class QApplication; + friend class QApplicationPrivate; + friend class QBaseApplication; + friend class QPainter; + friend class QPainterPrivate; + friend class QPixmap; // for QPixmap::fill() + friend class QFontMetrics; + friend class QFontInfo; + friend class QETWidget; + friend class QLayout; + friend class QWidgetItem; + friend class QWidgetItemV2; + friend class QGLContext; + friend class QGLWidget; + friend class QGLWindowSurface; + friend class QX11PaintEngine; + friend class QWin32PaintEngine; + friend class QShortcutPrivate; + friend class QShortcutMap; + friend class QWindowSurface; + friend class QGraphicsProxyWidget; + friend class QGraphicsProxyWidgetPrivate; + friend class QStyleSheetStyle; + friend struct QWidgetExceptionCleaner; +#ifndef QT_NO_GESTURES + friend class QGestureManager; + friend class QWinNativePanGestureRecognizer; +#endif // QT_NO_GESTURES + friend class QWidgetEffectSourcePrivate; + +#ifdef Q_WS_MAC + friend class QCoreGraphicsPaintEnginePrivate; + friend QPoint qt_mac_posInWindow(const QWidget *w); + friend OSWindowRef qt_mac_window_for(const QWidget *w); + friend bool qt_mac_is_metal(const QWidget *w); + friend OSViewRef qt_mac_nativeview_for(const QWidget *w); + friend void qt_event_request_window_change(QWidget *widget); + friend bool qt_mac_sendMacEventToWidget(QWidget *widget, EventRef ref); + friend class QRasterWindowSurface; + friend class QUnifiedToolbarSurface; +#endif +#ifdef Q_WS_QWS + friend class QWSBackingStore; + friend class QWSManager; + friend class QWSManagerPrivate; + friend class QDecoration; + friend class QWSWindowSurface; + friend class QScreen; + friend class QVNCScreen; + friend bool isWidgetOpaque(const QWidget *); + friend class QGLWidgetPrivate; +#endif +#ifdef Q_OS_SYMBIAN + friend class QSymbianControl; + friend class QS60WindowSurface; +#endif +#ifdef Q_WS_X11 + friend void qt_net_update_user_time(QWidget *tlw, unsigned long timestamp); + friend void qt_net_remove_user_time(QWidget *tlw); + friend void qt_set_winid_on_widget(QWidget*, Qt::HANDLE); +#endif + + friend Q_GUI_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget); + friend Q_GUI_EXPORT QWidgetPrivate *qt_widget_private(QWidget *widget); + +private: + Q_DISABLE_COPY(QWidget) + Q_PRIVATE_SLOT(d_func(), void _q_showIfNotHidden()) +#ifdef Q_OS_SYMBIAN + Q_PRIVATE_SLOT(d_func(), void _q_delayedDestroy(WId winId)) +#endif + + QWidgetData *data; + +#ifdef QT3_SUPPORT +public: + inline QT3_SUPPORT bool isUpdatesEnabled() const { return updatesEnabled(); } + QT3_SUPPORT QStyle *setStyle(const QString&); + inline QT3_SUPPORT bool isVisibleToTLW() const; + QT3_SUPPORT QRect visibleRect() const; + inline QT3_SUPPORT void iconify() { showMinimized(); } + inline QT3_SUPPORT void constPolish() const { ensurePolished(); } + inline QT3_SUPPORT void polish() { ensurePolished(); } + inline QT3_SUPPORT void reparent(QWidget *parent, Qt::WindowFlags f, const QPoint &p, bool showIt=false) + { setParent(parent, f); setGeometry(p.x(),p.y(),width(),height()); if (showIt) show(); } + inline QT3_SUPPORT void reparent(QWidget *parent, const QPoint &p, bool showIt=false) + { setParent(parent, windowFlags() & ~Qt::WindowType_Mask); setGeometry(p.x(),p.y(),width(),height()); if (showIt) show(); } + inline QT3_SUPPORT void recreate(QWidget *parent, Qt::WindowFlags f, const QPoint & p, bool showIt=false) + { setParent(parent, f); setGeometry(p.x(),p.y(),width(),height()); if (showIt) show(); } + inline QT3_SUPPORT void setSizePolicy(QSizePolicy::Policy hor, QSizePolicy::Policy ver, bool hfw) + { QSizePolicy sp(hor, ver); sp.setHeightForWidth(hfw); setSizePolicy(sp);} + inline QT3_SUPPORT bool hasMouse() const { return testAttribute(Qt::WA_UnderMouse); } +#ifndef QT_NO_CURSOR + inline QT3_SUPPORT bool ownCursor() const { return testAttribute(Qt::WA_SetCursor); } +#endif + inline QT3_SUPPORT bool ownFont() const { return testAttribute(Qt::WA_SetFont); } + inline QT3_SUPPORT void unsetFont() { setFont(QFont()); } + inline QT3_SUPPORT bool ownPalette() const { return testAttribute(Qt::WA_SetPalette); } + inline QT3_SUPPORT void unsetPalette() { setPalette(QPalette()); } + Qt::BackgroundMode QT3_SUPPORT backgroundMode() const; + void QT3_SUPPORT setBackgroundMode(Qt::BackgroundMode, Qt::BackgroundMode = Qt::PaletteBackground); + const QT3_SUPPORT QColor &eraseColor() const; + void QT3_SUPPORT setEraseColor(const QColor &); + const QT3_SUPPORT QColor &foregroundColor() const; + const QT3_SUPPORT QPixmap *erasePixmap() const; + void QT3_SUPPORT setErasePixmap(const QPixmap &); + const QT3_SUPPORT QColor &paletteForegroundColor() const; + void QT3_SUPPORT setPaletteForegroundColor(const QColor &); + const QT3_SUPPORT QColor &paletteBackgroundColor() const; + void QT3_SUPPORT setPaletteBackgroundColor(const QColor &); + const QT3_SUPPORT QPixmap *paletteBackgroundPixmap() const; + void QT3_SUPPORT setPaletteBackgroundPixmap(const QPixmap &); + const QT3_SUPPORT QBrush& backgroundBrush() const; + const QT3_SUPPORT QColor &backgroundColor() const; + const QT3_SUPPORT QPixmap *backgroundPixmap() const; + void QT3_SUPPORT setBackgroundPixmap(const QPixmap &); + QT3_SUPPORT void setBackgroundColor(const QColor &); + QT3_SUPPORT QColorGroup colorGroup() const; + QT3_SUPPORT QWidget *parentWidget(bool sameWindow) const; + inline QT3_SUPPORT void setKeyCompression(bool b) { setAttribute(Qt::WA_KeyCompression, b); } + inline QT3_SUPPORT void setFont(const QFont &f, bool) { setFont(f); } + inline QT3_SUPPORT void setPalette(const QPalette &p, bool) { setPalette(p); } + enum BackgroundOrigin { WidgetOrigin, ParentOrigin, WindowOrigin, AncestorOrigin }; + inline QT3_SUPPORT void setBackgroundOrigin(BackgroundOrigin) {} + inline QT3_SUPPORT BackgroundOrigin backgroundOrigin() const { return WindowOrigin; } + inline QT3_SUPPORT QPoint backgroundOffset() const { return QPoint(); } + inline QT3_SUPPORT void repaint(bool) { repaint(); } + inline QT3_SUPPORT void repaint(int x, int y, int w, int h, bool) { repaint(x,y,w,h); } + inline QT3_SUPPORT void repaint(const QRect &r, bool) { repaint(r); } + inline QT3_SUPPORT void repaint(const QRegion &rgn, bool) { repaint(rgn); } + QT3_SUPPORT void erase(); + inline QT3_SUPPORT void erase(int x, int y, int w, int h) { erase_helper(x, y, w, h); } + QT3_SUPPORT void erase(const QRect &); + QT3_SUPPORT void erase(const QRegion &); + QT3_SUPPORT void drawText(const QPoint &p, const QString &s) + { drawText_helper(p.x(), p.y(), s); } + inline QT3_SUPPORT void drawText(int x, int y, const QString &s) + { drawText_helper(x, y, s); } + QT3_SUPPORT bool close(bool); + inline QT3_SUPPORT QWidget *childAt(int x, int y, bool includeThis) const + { + QWidget *w = childAt(x, y); + return w ? w : ((includeThis && rect().contains(x,y))?const_cast<QWidget*>(this):0); + } + inline QT3_SUPPORT QWidget *childAt(const QPoint &p, bool includeThis) const + { + QWidget *w = childAt(p); + return w ? w : ((includeThis && rect().contains(p))?const_cast<QWidget*>(this):0); + } + inline QT3_SUPPORT void setCaption(const QString &c) { setWindowTitle(c); } + QT3_SUPPORT void setIcon(const QPixmap &i); + inline QT3_SUPPORT void setIconText(const QString &it) { setWindowIconText(it); } + inline QT3_SUPPORT QString caption() const { return windowTitle(); } + QT3_SUPPORT const QPixmap *icon() const; + inline QT3_SUPPORT QString iconText() const { return windowIconText(); } + inline QT3_SUPPORT void setInputMethodEnabled(bool b) { setAttribute(Qt::WA_InputMethodEnabled, b); } + inline QT3_SUPPORT bool isInputMethodEnabled() const { return testAttribute(Qt::WA_InputMethodEnabled); } + inline QT3_SUPPORT void setActiveWindow() { activateWindow(); } + inline QT3_SUPPORT bool isShown() const { return !isHidden(); } + inline QT3_SUPPORT bool isDialog() const { return windowType() == Qt::Dialog; } + inline QT3_SUPPORT bool isPopup() const { return windowType() == Qt::Popup; } + inline QT3_SUPPORT bool isDesktop() const { return windowType() == Qt::Desktop; } + + +private: + void drawText_helper(int x, int y, const QString &); + void erase_helper(int x, int y, int w, int h); +#endif // QT3_SUPPORT + +protected: + virtual void styleChange(QStyle&); // compat + virtual void enabledChange(bool); // compat + virtual void paletteChange(const QPalette &); // compat + virtual void fontChange(const QFont &); // compat + virtual void windowActivationChange(bool); // compat + virtual void languageChange(); // compat +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWidget::RenderFlags) + +template <> inline QWidget *qobject_cast<QWidget*>(QObject *o) +{ + if (!o || !o->isWidgetType()) return 0; + return static_cast<QWidget*>(o); +} +template <> inline const QWidget *qobject_cast<const QWidget*>(const QObject *o) +{ + if (!o || !o->isWidgetType()) return 0; + return static_cast<const QWidget*>(o); +} + +inline QWidget *QWidget::childAt(int ax, int ay) const +{ return childAt(QPoint(ax, ay)); } + +inline Qt::WindowType QWidget::windowType() const +{ return static_cast<Qt::WindowType>(int(data->window_flags & Qt::WindowType_Mask)); } +inline Qt::WindowFlags QWidget::windowFlags() const +{ return data->window_flags; } + +inline bool QWidget::isTopLevel() const +{ return (windowType() & Qt::Window); } + +inline bool QWidget::isWindow() const +{ return (windowType() & Qt::Window); } + +inline bool QWidget::isEnabled() const +{ return !testAttribute(Qt::WA_Disabled); } + +inline bool QWidget::isModal() const +{ return data->window_modality != Qt::NonModal; } + +inline bool QWidget::isEnabledToTLW() const +{ return isEnabled(); } + +inline int QWidget::minimumWidth() const +{ return minimumSize().width(); } + +inline int QWidget::minimumHeight() const +{ return minimumSize().height(); } + +inline int QWidget::maximumWidth() const +{ return maximumSize().width(); } + +inline int QWidget::maximumHeight() const +{ return maximumSize().height(); } + +inline void QWidget::setMinimumSize(const QSize &s) +{ setMinimumSize(s.width(),s.height()); } + +inline void QWidget::setMaximumSize(const QSize &s) +{ setMaximumSize(s.width(),s.height()); } + +inline void QWidget::setSizeIncrement(const QSize &s) +{ setSizeIncrement(s.width(),s.height()); } + +inline void QWidget::setBaseSize(const QSize &s) +{ setBaseSize(s.width(),s.height()); } + +inline const QFont &QWidget::font() const +{ return data->fnt; } + +inline QFontMetrics QWidget::fontMetrics() const +{ return QFontMetrics(data->fnt); } + +inline QFontInfo QWidget::fontInfo() const +{ return QFontInfo(data->fnt); } + +inline void QWidget::setMouseTracking(bool enable) +{ setAttribute(Qt::WA_MouseTracking, enable); } + +inline bool QWidget::hasMouseTracking() const +{ return testAttribute(Qt::WA_MouseTracking); } + +inline bool QWidget::underMouse() const +{ return testAttribute(Qt::WA_UnderMouse); } + +inline bool QWidget::updatesEnabled() const +{ return !testAttribute(Qt::WA_UpdatesDisabled); } + +inline void QWidget::update(int ax, int ay, int aw, int ah) +{ update(QRect(ax, ay, aw, ah)); } + +inline bool QWidget::isVisible() const +{ return testAttribute(Qt::WA_WState_Visible); } + +inline bool QWidget::isHidden() const +{ return testAttribute(Qt::WA_WState_Hidden); } + +inline void QWidget::move(int ax, int ay) +{ move(QPoint(ax, ay)); } + +inline void QWidget::resize(int w, int h) +{ resize(QSize(w, h)); } + +inline void QWidget::setGeometry(int ax, int ay, int aw, int ah) +{ setGeometry(QRect(ax, ay, aw, ah)); } + +inline QRect QWidget::rect() const +{ return QRect(0,0,data->crect.width(),data->crect.height()); } + +inline const QRect &QWidget::geometry() const +{ return data->crect; } + +inline QSize QWidget::size() const +{ return data->crect.size(); } + +inline int QWidget::width() const +{ return data->crect.width(); } + +inline int QWidget::height() const +{ return data->crect.height(); } + +inline QWidget *QWidget::parentWidget() const +{ return static_cast<QWidget *>(QObject::parent()); } + +inline void QWidget::setSizePolicy(QSizePolicy::Policy hor, QSizePolicy::Policy ver) +{ setSizePolicy(QSizePolicy(hor, ver)); } + +inline bool QWidget::testAttribute(Qt::WidgetAttribute attribute) const +{ + if (attribute < int(8*sizeof(uint))) + return data->widget_attributes & (1<<attribute); + return testAttribute_helper(attribute); +} + +#ifdef QT3_SUPPORT +inline bool QWidget::isVisibleToTLW() const +{ return isVisible(); } +inline QWidget *QWidget::parentWidget(bool sameWindow) const +{ + if (sameWindow && isWindow()) + return 0; + return static_cast<QWidget *>(QObject::parent()); +} +inline QColorGroup QWidget::colorGroup() const +{ return QColorGroup(palette()); } +inline void QWidget::setPaletteForegroundColor(const QColor &c) +{ QPalette p = palette(); p.setColor(foregroundRole(), c); setPalette(p); } +inline const QBrush& QWidget::backgroundBrush() const { return palette().brush(backgroundRole()); } +inline void QWidget::setBackgroundPixmap(const QPixmap &pm) +{ QPalette p = palette(); p.setBrush(backgroundRole(), QBrush(pm)); setPalette(p); } +inline const QPixmap *QWidget::backgroundPixmap() const { return 0; } +inline void QWidget::setBackgroundColor(const QColor &c) +{ QPalette p = palette(); p.setColor(backgroundRole(), c); setPalette(p); } +inline const QColor & QWidget::backgroundColor() const { return palette().color(backgroundRole()); } +inline const QColor &QWidget::foregroundColor() const { return palette().color(foregroundRole());} +inline const QColor &QWidget::eraseColor() const { return palette().color(backgroundRole()); } +inline void QWidget::setEraseColor(const QColor &c) +{ QPalette p = palette(); p.setColor(backgroundRole(), c); setPalette(p); } +inline const QPixmap *QWidget::erasePixmap() const { return 0; } +inline void QWidget::setErasePixmap(const QPixmap &pm) +{ QPalette p = palette(); p.setBrush(backgroundRole(), QBrush(pm)); setPalette(p); } +inline const QColor &QWidget::paletteForegroundColor() const { return palette().color(foregroundRole());} +inline const QColor &QWidget::paletteBackgroundColor() const { return palette().color(backgroundRole()); } +inline void QWidget::setPaletteBackgroundColor(const QColor &c) +{ QPalette p = palette(); p.setColor(backgroundRole(), c); setPalette(p); } +inline const QPixmap *QWidget::paletteBackgroundPixmap() const +{ return 0; } +inline void QWidget::setPaletteBackgroundPixmap(const QPixmap &pm) +{ QPalette p = palette(); p.setBrush(backgroundRole(), QBrush(pm)); setPalette(p); } +inline QT3_SUPPORT void QWidget::erase() { erase_helper(0, 0, data->crect.width(), data->crect.height()); } +inline QT3_SUPPORT void QWidget::erase(const QRect &r) { erase_helper(r.x(), r.y(), r.width(), r.height()); } +#endif + +#define QWIDGETSIZE_MAX ((1<<24)-1) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWIDGET_H diff --git a/src/gui/kernel/qwidget_mac.mm b/src/gui/kernel/qwidget_mac.mm new file mode 100644 index 0000000000..354f05ba10 --- /dev/null +++ b/src/gui/kernel/qwidget_mac.mm @@ -0,0 +1,5420 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include <private/qt_mac_p.h> +#include <private/qeventdispatcher_mac_p.h> + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qfileinfo.h" +#include "qimage.h" +#include "qlayout.h" +#include "qmenubar.h" +#include <private/qbackingstore_p.h> +#include <private/qwindowsurface_mac_p.h> +#include <private/qpaintengine_mac_p.h> +#include "qpainter.h" +#include "qstyle.h" +#include "qtimer.h" +#include "qfocusframe.h" +#include "qdebug.h" +#include <private/qmainwindowlayout_p.h> + +#include <private/qabstractscrollarea_p.h> +#include <qabstractscrollarea.h> +#include <ApplicationServices/ApplicationServices.h> +#include <limits.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qcocoaview_mac_p.h> +#include <private/qcocoawindow_mac_p.h> +#include <private/qcocoawindowdelegate_mac_p.h> +#include <private/qcocoapanel_mac_p.h> + +#include "qwidget_p.h" +#include "qevent_p.h" +#include "qdnd_p.h" +#include <QtGui/qgraphicsproxywidget.h> +#include "qmainwindow.h" + +QT_BEGIN_NAMESPACE + +// qmainwindow.cpp +extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); + +#define XCOORD_MAX 16383 +#define WRECT_MAX 8191 + +#ifndef QT_MAC_USE_COCOA + +extern "C" { + extern OSStatus _HIViewScrollRectWithOptions(HIViewRef, const HIRect *, CGFloat, CGFloat, + OptionBits) __attribute__ ((weak)); +} +#define kHIViewScrollRectAdjustInvalid 1 +#define kHIViewScrollRectDontInvalidateRevealedArea 2 +#endif + + +/***************************************************************************** + QWidget debug facilities + *****************************************************************************/ +//#define DEBUG_WINDOW_RGNS +//#define DEBUG_WINDOW_CREATE +//#define DEBUG_WINDOW_STATE +//#define DEBUG_WIDGET_PAINT + +/***************************************************************************** + QWidget globals + *****************************************************************************/ +#ifndef QT_MAC_USE_COCOA +typedef QHash<Qt::WindowFlags, WindowGroupRef> WindowGroupHash; +Q_GLOBAL_STATIC(WindowGroupHash, qt_mac_window_groups) +const UInt32 kWidgetCreatorQt = kEventClassQt; +enum { + kWidgetPropertyQWidget = 'QWId' //QWidget * +}; +#endif + +static bool qt_mac_raise_process = true; +static OSWindowRef qt_root_win = 0; +QWidget *mac_mouse_grabber = 0; +QWidget *mac_keyboard_grabber = 0; + +#ifndef QT_MAC_USE_COCOA +#ifdef QT_NAMESPACE + +// produce the string "com.trolltech.qt-namespace.widget", where "namespace" is the contents of QT_NAMESPACE. +#define SS(x) #x +#define S0(x) SS(x) +#define S "com.trolltech.qt-" S0(QT_NAMESPACE) ".widget" + +static CFStringRef kObjectQWidget = CFSTR(S); + +#undef SS +#undef S0 +#undef S + +#else +static CFStringRef kObjectQWidget = CFSTR("com.trolltech.qt.widget"); +#endif // QT_NAMESPACE +#endif // QT_MAC_USE_COCOA + +/***************************************************************************** + Externals + *****************************************************************************/ +extern QPointer<QWidget> qt_button_down; //qapplication_mac.cpp +extern QWidget *qt_mac_modal_blocked(QWidget *); //qapplication_mac.mm +extern void qt_event_request_activate(QWidget *); //qapplication_mac.mm +extern bool qt_event_remove_activate(); //qapplication_mac.mm +extern void qt_mac_event_release(QWidget *w); //qapplication_mac.mm +extern void qt_event_request_showsheet(QWidget *); //qapplication_mac.mm +extern void qt_event_request_window_change(QWidget *); //qapplication_mac.mm +extern QPointer<QWidget> qt_last_mouse_receiver; //qapplication_mac.mm +extern QPointer<QWidget> qt_last_native_mouse_receiver; //qt_cocoa_helpers_mac.mm +extern IconRef qt_mac_create_iconref(const QPixmap &); //qpixmap_mac.cpp +extern void qt_mac_set_cursor(const QCursor *, const QPoint &); //qcursor_mac.mm +extern void qt_mac_update_cursor(); //qcursor_mac.mm +extern bool qt_nograb(); +extern CGImageRef qt_mac_create_cgimage(const QPixmap &, bool); //qpixmap_mac.cpp +extern RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp +extern QRegion qt_mac_convert_mac_region(RgnHandle rgn); //qregion_mac.cpp +extern void qt_mac_setMouseGrabCursor(bool set, QCursor *cursor = 0); // qcursor_mac.mm +extern QPointer<QWidget> topLevelAt_cache; // qapplication_mac.mm +/***************************************************************************** + QWidget utility functions + *****************************************************************************/ +void Q_GUI_EXPORT qt_mac_set_raise_process(bool b) { qt_mac_raise_process = b; } +static QSize qt_mac_desktopSize() +{ + int w = 0, h = 0; + CGDisplayCount cg_count; + CGGetActiveDisplayList(0, 0, &cg_count); + QVector<CGDirectDisplayID> displays(cg_count); + CGGetActiveDisplayList(cg_count, displays.data(), &cg_count); + Q_ASSERT(cg_count == (CGDisplayCount)displays.size()); + for(int i = 0; i < (int)cg_count; ++i) { + CGRect r = CGDisplayBounds(displays.at(i)); + w = qMax<int>(w, qRound(r.origin.x + r.size.width)); + h = qMax<int>(h, qRound(r.origin.y + r.size.height)); + } + return QSize(w, h); +} + +#ifdef QT_MAC_USE_COCOA +static NSDrawer *qt_mac_drawer_for(const QWidget *widget) +{ + NSView *widgetView = reinterpret_cast<NSView *>(widget->window()->effectiveWinId()); + NSArray *windows = [NSApp windows]; + for (NSWindow *window in windows) { + NSArray *drawers = [window drawers]; + for (NSDrawer *drawer in drawers) { + if ([drawer contentView] == widgetView) + return drawer; + } + } + return 0; +} +#endif + +static void qt_mac_destructView(OSViewRef view) +{ +#ifdef QT_MAC_USE_COCOA + NSWindow *window = [view window]; + if ([window contentView] == view) + [window setContentView:[[NSView alloc] initWithFrame:[view bounds]]]; + [view removeFromSuperview]; + [view release]; +#else + HIViewRemoveFromSuperview(view); + CFRelease(view); +#endif +} + +static void qt_mac_destructWindow(OSWindowRef window) +{ +#ifdef QT_MAC_USE_COCOA + if ([window isVisible] && [window isSheet]){ + [NSApp endSheet:window]; + [window orderOut:window]; + } + + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] resignDelegateForWindow:window]; + [window release]; +#else + // Remove property to clean up memory: + RemoveWindowProperty(window, kWidgetCreatorQt, kWidgetPropertyQWidget); + CFRelease(window); +#endif +} + +static void qt_mac_destructDrawer(NSDrawer *drawer) +{ +#ifdef QT_MAC_USE_COCOA + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] resignDelegateForDrawer:drawer]; + [drawer release]; +#else + Q_UNUSED(drawer); +#endif +} + +bool qt_mac_can_clickThrough(const QWidget *w) +{ + static int qt_mac_carbon_clickthrough = -1; + if (qt_mac_carbon_clickthrough < 0) + qt_mac_carbon_clickthrough = !qgetenv("QT_MAC_NO_COCOA_CLICKTHROUGH").isEmpty(); + bool ret = !qt_mac_carbon_clickthrough; + for ( ; w; w = w->parentWidget()) { + if (w->testAttribute(Qt::WA_MacNoClickThrough)) { + ret = false; + break; + } + } + return ret; +} + +bool qt_mac_is_macsheet(const QWidget *w) +{ + if (!w) + return false; + + Qt::WindowModality modality = w->windowModality(); + if (modality == Qt::ApplicationModal) + return false; + return w->parentWidget() && (modality == Qt::WindowModal || w->windowType() == Qt::Sheet); +} + +bool qt_mac_is_macdrawer(const QWidget *w) +{ + return (w && w->parentWidget() && w->windowType() == Qt::Drawer); +} + +bool qt_mac_insideKeyWindow(const QWidget *w) +{ +#ifdef QT_MAC_USE_COCOA + return [[reinterpret_cast<NSView *>(w->effectiveWinId()) window] isKeyWindow]; +#else + Q_UNUSED(w); +#endif + return false; +} + +bool qt_mac_set_drawer_preferred_edge(QWidget *w, Qt::DockWidgetArea where) //users of Qt for Mac OS X can use this.. +{ + if(!qt_mac_is_macdrawer(w)) + return false; + +#if QT_MAC_USE_COCOA + NSDrawer *drawer = qt_mac_drawer_for(w); + if (!drawer) + return false; + NSRectEdge edge; + if (where & Qt::LeftDockWidgetArea) + edge = NSMinXEdge; + else if (where & Qt::RightDockWidgetArea) + edge = NSMaxXEdge; + else if (where & Qt::TopDockWidgetArea) + edge = NSMaxYEdge; + else if (where & Qt::BottomDockWidgetArea) + edge = NSMinYEdge; + else + return false; + + if (edge == [drawer preferredEdge]) //no-op + return false; + + if (w->isVisible()) { + [drawer close]; + [drawer openOnEdge:edge]; + } + [drawer setPreferredEdge:edge]; +#else + OSWindowRef window = qt_mac_window_for(w); + OptionBits edge; + if(where & Qt::LeftDockWidgetArea) + edge = kWindowEdgeLeft; + else if(where & Qt::RightDockWidgetArea) + edge = kWindowEdgeRight; + else if(where & Qt::TopDockWidgetArea) + edge = kWindowEdgeTop; + else if(where & Qt::BottomDockWidgetArea) + edge = kWindowEdgeBottom; + else + return false; + + if(edge == GetDrawerPreferredEdge(window)) //no-op + return false; + + //do it + SetDrawerPreferredEdge(window, edge); + if(w->isVisible()) { + CloseDrawer(window, false); + OpenDrawer(window, edge, true); + } +#endif + return true; +} + +#ifndef QT_MAC_USE_COCOA +Q_GUI_EXPORT +#endif +QPoint qt_mac_posInWindow(const QWidget *w) +{ + QPoint ret = w->data->wrect.topLeft(); + while(w && !w->isWindow()) { + ret += w->pos(); + w = w->parentWidget(); + } + return ret; +} + +//find a QWidget from a OSWindowRef +QWidget *qt_mac_find_window(OSWindowRef window) +{ +#ifdef QT_MAC_USE_COCOA + return [window QT_MANGLE_NAMESPACE(qt_qwidget)]; +#else + if(!window) + return 0; + + QWidget *ret; + if(GetWindowProperty(window, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(ret), 0, &ret) == noErr) + return ret; + return 0; +#endif +} + +inline static void qt_mac_set_fullscreen_mode(bool b) +{ + extern bool qt_mac_app_fullscreen; //qapplication_mac.mm + if(qt_mac_app_fullscreen == b) + return; + qt_mac_app_fullscreen = b; + if (b) { + SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar); + } else { + SetSystemUIMode(kUIModeNormal, 0); + } +} + +Q_GUI_EXPORT OSViewRef qt_mac_nativeview_for(const QWidget *w) +{ + return reinterpret_cast<OSViewRef>(w->internalWinId()); +} + +Q_GUI_EXPORT OSViewRef qt_mac_effectiveview_for(const QWidget *w) +{ + // Get the first non-alien (parent) widget for + // w, and return its NSView (if it has one): + return reinterpret_cast<OSViewRef>(w->effectiveWinId()); +} + +Q_GUI_EXPORT OSViewRef qt_mac_get_contentview_for(OSWindowRef w) +{ +#ifdef QT_MAC_USE_COCOA + return [w contentView]; +#else + HIViewRef contentView = 0; + OSStatus err = GetRootControl(w, &contentView); // Returns the window's content view (Apple QA1214) + if (err == errUnknownControl) { + contentView = HIViewGetRoot(w); + } else if (err != noErr) { + qWarning("Qt:Could not get content or root view of window! %s:%d [%ld]", + __FILE__, __LINE__, err); + } + return contentView; +#endif +} + +bool qt_mac_sendMacEventToWidget(QWidget *widget, EventRef ref) +{ + return widget->macEvent(0, ref); +} + +Q_GUI_EXPORT OSWindowRef qt_mac_window_for(OSViewRef view) +{ +#ifdef QT_MAC_USE_COCOA + if (view) + return [view window]; + return 0; +#else + return HIViewGetWindow(view); +#endif +} + +static bool qt_isGenuineQWidget(OSViewRef ref) +{ +#ifdef QT_MAC_USE_COCOA + return [ref isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaView) class]]; +#else + return HIObjectIsOfClass(HIObjectRef(ref), kObjectQWidget); +#endif +} + +bool qt_isGenuineQWidget(const QWidget *window) +{ + if (!window) + return false; + + if (!window->internalWinId()) + return true; //alien + + return qt_isGenuineQWidget(OSViewRef(window->internalWinId())); +} + +Q_GUI_EXPORT OSWindowRef qt_mac_window_for(const QWidget *w) +{ + if (OSViewRef hiview = qt_mac_effectiveview_for(w)) { + OSWindowRef window = qt_mac_window_for(hiview); + if (window) + return window; + + if (qt_isGenuineQWidget(hiview)) { + // This is a workaround for NSToolbar. When a widget is hidden + // by clicking the toolbar button, Cocoa reparents the widgets + // to another window (but Qt doesn't know about it). + // When we start showing them, it reparents back, + // but at this point it's window is nil, but the window it's being brought + // into (the Qt one) is for sure created. + // This stops the hierarchy moving under our feet. + QWidget *toplevel = w->window(); + if (toplevel != w) { + hiview = qt_mac_nativeview_for(toplevel); + if (OSWindowRef w = qt_mac_window_for(hiview)) + return w; + } + + toplevel->d_func()->createWindow_sys(); + // Reget the hiview since "create window" could potentially move the view (I guess). + hiview = qt_mac_nativeview_for(toplevel); + return qt_mac_window_for(hiview); + } + } + return 0; +} + +#ifndef QT_MAC_USE_COCOA +/* Checks if the current group is a 'stay on top' group. If so, the + group gets removed from the hash table */ +static void qt_mac_release_stays_on_top_group(WindowGroupRef group) +{ + for (WindowGroupHash::iterator it = qt_mac_window_groups()->begin(); it != qt_mac_window_groups()->end(); ++it) { + if (it.value() == group) { + qt_mac_window_groups()->remove(it.key()); + return; + } + } +} + +/* Use this function instead of ReleaseWindowGroup, this will be sure to release the + stays on top window group (created with qt_mac_get_stays_on_top_group below) */ +static void qt_mac_release_window_group(WindowGroupRef group) +{ + ReleaseWindowGroup(group); + if (GetWindowGroupRetainCount(group) == 0) + qt_mac_release_stays_on_top_group(group); +} +#define ReleaseWindowGroup(x) Are you sure you wanted to do that? (you wanted qt_mac_release_window_group) + +SInt32 qt_mac_get_group_level(WindowClass wclass) +{ + SInt32 group_level; + CGWindowLevel tmpLevel; + GetWindowGroupLevelOfType(GetWindowGroupOfClass(wclass), kWindowGroupLevelActive, &tmpLevel); + group_level = tmpLevel; + return group_level; +} +#endif + +#ifndef QT_MAC_USE_COCOA +static void qt_mac_set_window_group(OSWindowRef window, Qt::WindowFlags flags, int level) +{ + WindowGroupRef group = 0; + if (qt_mac_window_groups()->contains(flags)) { + group = qt_mac_window_groups()->value(flags); + RetainWindowGroup(group); + } else { + CreateWindowGroup(kWindowActivationScopeNone, &group); + SetWindowGroupLevel(group, level); + SetWindowGroupParent(group, GetWindowGroupOfClass(kAllWindowClasses)); + qt_mac_window_groups()->insert(flags, group); + } + SetWindowGroup(window, group); +} + +inline static void qt_mac_set_window_group_to_stays_on_top(OSWindowRef window, Qt::WindowType type) +{ + // We create one static stays on top window group so that + // all stays on top (aka popups) will fall into the same + // group and be able to be raise()'d with releation to one another (from + // within the same window group). + qt_mac_set_window_group(window, type|Qt::WindowStaysOnTopHint, qt_mac_get_group_level(kOverlayWindowClass)); +} + +inline static void qt_mac_set_window_group_to_tooltip(OSWindowRef window) +{ + // Since new groups are created for 'stays on top' windows, the + // same must be done for tooltips. Otherwise, tooltips would be drawn + // below 'stays on top' widgets even tough they are on the same level. + // Also, add 'two' to the group level to make sure they also get on top of popups. + qt_mac_set_window_group(window, Qt::ToolTip, qt_mac_get_group_level(kHelpWindowClass)+2); +} + +inline static void qt_mac_set_window_group_to_popup(OSWindowRef window) +{ + // In Qt, a popup is seen as a 'stay on top' window. + // Since new groups are created for 'stays on top' windows, the + // same must be done for popups. Otherwise, popups would be drawn + // below 'stays on top' windows. Add 1 to get above pure stay-on-top windows. + qt_mac_set_window_group(window, Qt::Popup, qt_mac_get_group_level(kOverlayWindowClass)+1); +} +#endif + +inline static bool updateRedirectedToGraphicsProxyWidget(QWidget *widget, const QRect &rect) +{ + if (!widget) + return false; + +#ifndef QT_NO_GRAPHICSVIEW + QWidget *tlw = widget->window(); + QWExtra *extra = qt_widget_private(tlw)->extra; + if (extra && extra->proxyWidget) { + extra->proxyWidget->update(rect.translated(widget->mapTo(tlw, QPoint()))); + return true; + } +#endif + + return false; +} + +inline static bool updateRedirectedToGraphicsProxyWidget(QWidget *widget, const QRegion &rgn) +{ + if (!widget) + return false; + +#ifndef QT_NO_GRAPHICSVIEW + QWidget *tlw = widget->window(); + QWExtra *extra = qt_widget_private(tlw)->extra; + if (extra && extra->proxyWidget) { + const QPoint offset(widget->mapTo(tlw, QPoint())); + const QVector<QRect> rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) + extra->proxyWidget->update(rects.at(i).translated(offset)); + return true; + } +#endif + + return false; +} + +void QWidgetPrivate::macSetNeedsDisplay(QRegion region) +{ + Q_Q(QWidget); +#ifndef QT_MAC_USE_COCOA + if (region.isEmpty()) + HIViewSetNeedsDisplay(qt_mac_nativeview_for(q), true); + else if (RgnHandle rgnHandle = region.toQDRgnForUpdate_sys()) + HIViewSetNeedsDisplayInRegion(qt_mac_nativeview_for(q), QMacSmartQuickDrawRegion(rgnHandle), true); + else + HIViewSetNeedsDisplay(qt_mac_nativeview_for(q), true); // do a complete repaint on overflow. +#else + if (NSView *nativeView = qt_mac_nativeview_for(q)) { + // INVARIANT: q is _not_ alien. So we can optimize a little: + if (region.isEmpty()) { + [nativeView setNeedsDisplay:YES]; + } else { + QVector<QRect> rects = region.rects(); + for (int i = 0; i<rects.count(); ++i) { + const QRect &rect = rects.at(i); + NSRect nsrect = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + [nativeView setNeedsDisplayInRect:nsrect]; + } + } + } else if (QWidget *effectiveWidget = q->nativeParentWidget()) { + // INVARIANT: q is alien, and effectiveWidget is native. + if (NSView *effectiveView = qt_mac_nativeview_for(effectiveWidget)) { + if (region.isEmpty()) { + const QRect &rect = q->rect(); + QPoint p = q->mapTo(effectiveWidget, rect.topLeft()); + NSRect nsrect = NSMakeRect(p.x(), p.y(), rect.width(), rect.height()); + [effectiveView setNeedsDisplayInRect:nsrect]; + } else { + QVector<QRect> rects = region.rects(); + for (int i = 0; i<rects.count(); ++i) { + const QRect &rect = rects.at(i); + QPoint p = q->mapTo(effectiveWidget, rect.topLeft()); + NSRect nsrect = NSMakeRect(p.x(), p.y(), rect.width(), rect.height()); + [effectiveView setNeedsDisplayInRect:nsrect]; + } + } + } + } +#endif +} + +void QWidgetPrivate::macUpdateIsOpaque() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; +#ifndef QT_MAC_USE_COCOA + HIViewFeatures bits; + HIViewRef hiview = qt_mac_nativeview_for(q); + HIViewGetFeatures(hiview, &bits); + if ((bits & kHIViewIsOpaque) == isOpaque) + return; + if (isOpaque) { + HIViewChangeFeatures(hiview, kHIViewIsOpaque, 0); + } else { + HIViewChangeFeatures(hiview, 0, kHIViewIsOpaque); + } + if (q->isVisible()) + HIViewReshapeStructure(qt_mac_nativeview_for(q)); +#else + if (isRealWindow() && !q->testAttribute(Qt::WA_MacBrushedMetal)) { + bool opaque = isOpaque; + if (extra && extra->imageMask) + opaque = false; // we are never opaque when we have a mask. + [qt_mac_window_for(q) setOpaque:opaque]; + } +#endif +} +#ifdef QT_MAC_USE_COCOA +static OSWindowRef qt_mac_create_window(QWidget *widget, WindowClass wclass, + NSUInteger wattr, const QRect &crect) +{ + // Determine if we need to add in our "custom window" attribute. Cocoa is rather clever + // in deciding if we need the maximize button or not (i.e., it's resizeable, so you + // must need a maximize button). So, the only buttons we have control over are the + // close and minimize buttons. If someone wants to customize and NOT have the maximize + // button, then we have to do our hack. We only do it for these cases because otherwise + // the window looks different when activated. This "QtMacCustomizeWindow" attribute is + // intruding on a public space and WILL BREAK in the future. + // One can hope that there is a more public API available by that time. + Qt::WindowFlags flags = widget ? widget->windowFlags() : Qt::WindowFlags(0); + if ((flags & Qt::CustomizeWindowHint)) { + if ((flags & (Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint | Qt::WindowTitleHint)) + && !(flags & Qt::WindowMaximizeButtonHint)) + wattr |= QtMacCustomizeWindow; + } + + // If we haven't created the desktop widget, you have to pass the rectangle + // in "cocoa coordinates" (i.e., top points to the lower left coordinate). + // Otherwise, we do the conversion for you. Since we are the only ones that + // create the desktop widget, this is OK (but confusing). + NSRect geo = NSMakeRect(crect.left(), + (qt_root_win != 0) ? flipYCoordinate(crect.bottom() + 1) : crect.top(), + crect.width(), crect.height()); + QMacCocoaAutoReleasePool pool; + OSWindowRef window; + switch (wclass) { + case kMovableModalWindowClass: + case kModalWindowClass: + case kSheetWindowClass: + case kFloatingWindowClass: + case kOverlayWindowClass: + case kHelpWindowClass: { + NSPanel *panel; + BOOL needFloating = NO; + BOOL worksWhenModal = widget && (widget->windowType() == Qt::Popup); + // Add in the extra flags if necessary. + switch (wclass) { + case kSheetWindowClass: + wattr |= NSDocModalWindowMask; + break; + case kFloatingWindowClass: + case kHelpWindowClass: + needFloating = YES; + wattr |= NSUtilityWindowMask; + break; + default: + break; + } + panel = [[QT_MANGLE_NAMESPACE(QCocoaPanel) alloc] QT_MANGLE_NAMESPACE(qt_initWithQWidget):widget contentRect:geo styleMask:wattr]; + [panel setFloatingPanel:needFloating]; + [panel setWorksWhenModal:worksWhenModal]; + window = panel; + break; + } + case kDrawerWindowClass: { + NSDrawer *drawer = [[NSDrawer alloc] initWithContentSize:geo.size preferredEdge:NSMinXEdge]; + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] becomeDelegateForDrawer:drawer widget:widget]; + QWidget *parentWidget = widget->parentWidget(); + if (parentWidget) + [drawer setParentWindow:qt_mac_window_for(parentWidget)]; + [drawer setLeadingOffset:0.0]; + [drawer setTrailingOffset:25.0]; + window = [[drawer contentView] window]; // Just to make sure we actually return a window + break; + } + default: + window = [[QT_MANGLE_NAMESPACE(QCocoaWindow) alloc] QT_MANGLE_NAMESPACE(qt_initWithQWidget):widget contentRect:geo styleMask:wattr]; + break; + } + qt_syncCocoaTitleBarButtons(window, widget); + return window; +} +#else +static OSWindowRef qt_mac_create_window(QWidget *, WindowClass wclass, WindowAttributes wattr, + const QRect &crect) +{ + OSWindowRef window; + Rect geo; + SetRect(&geo, crect.left(), crect.top(), crect.right() + 1, crect.bottom() + 1); + OSStatus err; + if(geo.right <= geo.left) geo.right = geo.left + 1; + if(geo.bottom <= geo.top) geo.bottom = geo.top + 1; + Rect null_rect; + SetRect(&null_rect, 0, 0, 1, 1); + err = CreateNewWindow(wclass, wattr, &null_rect, &window); + if(err == noErr) { + err = SetWindowBounds(window, kWindowContentRgn, &geo); + if(err != noErr) + qWarning("QWidget: Internal error (%s:%d)", __FILE__, __LINE__); + } + return window; +} + +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +/* We build the release package against the 10.4 SDK. + So, to enable gestures for applications running on + 10.6+, we define the missing constants here: */ +enum { + kEventClassGesture = 'gest', + kEventGestureStarted = 1, + kEventGestureEnded = 2, + kEventGestureMagnify = 4, + kEventGestureSwipe = 5, + kEventGestureRotate = 6, + kEventParamRotationAmount = 'rota', + kEventParamSwipeDirection = 'swip', + kEventParamMagnificationAmount = 'magn' +}; +#endif +#endif // QT_NO_GESTURES + +// window events +static EventTypeSpec window_events[] = { + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowExpanded }, + { kEventClassWindow, kEventWindowHidden }, + { kEventClassWindow, kEventWindowZoom }, + { kEventClassWindow, kEventWindowZoomed }, + { kEventClassWindow, kEventWindowCollapsed }, + { kEventClassWindow, kEventWindowToolbarSwitchMode }, + { kEventClassWindow, kEventWindowProxyBeginDrag }, + { kEventClassWindow, kEventWindowProxyEndDrag }, + { kEventClassWindow, kEventWindowResizeCompleted }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowGetRegion }, + { kEventClassWindow, kEventWindowGetClickModality }, + { kEventClassWindow, kEventWindowTransitionCompleted }, + { kEventClassGesture, kEventGestureStarted }, + { kEventClassGesture, kEventGestureEnded }, + { kEventClassGesture, kEventGestureMagnify }, + { kEventClassGesture, kEventGestureSwipe }, + { kEventClassGesture, kEventGestureRotate }, + { kEventClassMouse, kEventMouseDown } +}; +static EventHandlerUPP mac_win_eventUPP = 0; +static void cleanup_win_eventUPP() +{ + DisposeEventHandlerUPP(mac_win_eventUPP); + mac_win_eventUPP = 0; +} +static const EventHandlerUPP make_win_eventUPP() +{ + if(mac_win_eventUPP) + return mac_win_eventUPP; + qAddPostRoutine(cleanup_win_eventUPP); + return mac_win_eventUPP = NewEventHandlerUPP(QWidgetPrivate::qt_window_event); +} +OSStatus QWidgetPrivate::qt_window_event(EventHandlerCallRef er, EventRef event, void *) +{ + QScopedLoopLevelCounter loopLevelCounter(qApp->d_func()->threadData); + bool handled_event = true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassWindow: { + WindowRef wid = 0; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, + sizeof(WindowRef), 0, &wid); + QWidget *widget = qt_mac_find_window(wid); + if(!widget) { + handled_event = false; + } else if(ekind == kEventWindowGetClickModality) { + // Carbon will send us kEventWindowGetClickModality before every + // mouse press / release event. By returning 'true', we tell Carbon + // that we would like the event target to receive the mouse event even + // if the target is modally shaddowed. In Qt, this makes sense when we + // e.g. have a popup showing, as the popup will grab the event + // and perhaps use it to close itself. + // By also setting the current modal window back into the event, we + // help Carbon determining which window is supposed to be raised. + handled_event = qApp->activePopupWidget() ? true : false; + } else if(ekind == kEventWindowClose) { + widget->d_func()->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); + QMenuBar::macUpdateMenuBar(); + } else if (ekind == kEventWindowTransitionCompleted) { + WindowTransitionAction transitionAction; + GetEventParameter(event, kEventParamWindowTransitionAction, typeWindowTransitionAction, + 0, sizeof(transitionAction), 0, &transitionAction); + if (transitionAction == kWindowHideTransitionAction) + widget->hide(); + } else if(ekind == kEventWindowExpanded) { + Qt::WindowStates currState = Qt::WindowStates(widget->data->window_state); + Qt::WindowStates newState = currState; + if (currState & Qt::WindowMinimized) + newState &= ~Qt::WindowMinimized; + if (!(currState & Qt::WindowActive)) + newState |= Qt::WindowActive; + if (newState != currState) { + // newState will differ from currState if the window + // was expanded after clicking on the jewels (as opposed + // to calling QWidget::setWindowState) + widget->data->window_state = newState; + QWindowStateChangeEvent e(currState); + QApplication::sendSpontaneousEvent(widget, &e); + } + + QShowEvent qse; + QApplication::sendSpontaneousEvent(widget, &qse); + } else if(ekind == kEventWindowZoom) { + widget->d_func()->topData()->normalGeometry = widget->geometry(); + handled_event = false; + } else if(ekind == kEventWindowZoomed) { + WindowPartCode windowPart; + GetEventParameter(event, kEventParamWindowPartCode, + typeWindowPartCode, 0, sizeof(windowPart), 0, &windowPart); + if(windowPart == inZoomIn && widget->isMaximized()) { + + widget->data->window_state = widget->data->window_state & ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state | Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + } else if(windowPart == inZoomOut && !widget->isMaximized()) { + widget->data->window_state = widget->data->window_state | Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state + & ~Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + } + qt_button_down = 0; + } else if(ekind == kEventWindowCollapsed) { + if (!widget->isMinimized()) { + widget->data->window_state = widget->data->window_state | Qt::WindowMinimized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state & ~Qt::WindowMinimized)); + QApplication::sendSpontaneousEvent(widget, &e); + } + + // Deactivate this window: + if (widget->isActiveWindow() && !(widget->windowType() == Qt::Popup)) { + QWidget *w = 0; + if (widget->parentWidget()) + w = widget->parentWidget()->window(); + if (!w || (!w->isVisible() && !w->isMinimized())) { + for (WindowPtr wp = GetFrontWindowOfClass(kDocumentWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kDocumentWindowClass, true)) { + if ((w = qt_mac_find_window(wp))) + break; + } + } + if(!(w && w->isVisible() && !w->isMinimized())) + qApp->setActiveWindow(0); + } + + //we send a hide to be like X11/Windows + QEvent e(QEvent::Hide); + QApplication::sendSpontaneousEvent(widget, &e); + qt_button_down = 0; + } else if(ekind == kEventWindowToolbarSwitchMode) { + macSendToolbarChangeEvent(widget); + HIToolbarRef toolbar; + if (GetWindowToolbar(wid, &toolbar) == noErr) { + if (toolbar) { + // Let HIToolbar do its thang, but things like the OpenGL context + // needs to know about it. + CallNextEventHandler(er, event); + qt_event_request_window_change(widget); + widget->data->fstrut_dirty = true; + } + } + } else if(ekind == kEventWindowGetRegion) { + WindowRef window; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, + sizeof(window), 0, &window); + WindowRegionCode wcode; + GetEventParameter(event, kEventParamWindowRegionCode, typeWindowRegionCode, 0, + sizeof(wcode), 0, &wcode); + if (wcode != kWindowOpaqueRgn){ + // If the region is kWindowOpaqueRgn, don't call next + // event handler cause this will make the shadow of + // masked windows become offset. Unfortunately, we're not sure why. + CallNextEventHandler(er, event); + } + RgnHandle rgn; + GetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + + if(QWidgetPrivate::qt_widget_rgn(qt_mac_find_window(window), wcode, rgn, false)) + SetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, sizeof(rgn), &rgn); + } else if(ekind == kEventWindowProxyBeginDrag) { + QIconDragEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + } else if(ekind == kEventWindowResizeCompleted) { + // Create a mouse up event, since such an event is not send by carbon to the + // application event handler (while a mouse down <b>is</b> on kEventWindowResizeStarted) + EventRef mouseUpEvent; + CreateEvent(0, kEventClassMouse, kEventMouseUp, 0, kEventAttributeUserEvent, &mouseUpEvent); + UInt16 mbutton = kEventMouseButtonPrimary; + SetEventParameter(mouseUpEvent, kEventParamMouseButton, typeMouseButton, sizeof(mbutton), &mbutton); + WindowRef window; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, sizeof(window), 0, &window); + Rect dragRect; + GetWindowBounds(window, kWindowGrowRgn, &dragRect); + Point pos = {dragRect.bottom, dragRect.right}; + SetEventParameter(mouseUpEvent, kEventParamMouseLocation, typeQDPoint, sizeof(pos), &pos); + SendEventToApplication(mouseUpEvent); + ReleaseEvent(mouseUpEvent); + } else if(ekind == kEventWindowBoundsChanging) { + UInt32 flags = 0; + GetEventParameter(event, kEventParamAttributes, typeUInt32, 0, + sizeof(flags), 0, &flags); + Rect nr; + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, 0, + sizeof(nr), 0, &nr); + + QRect newRect(nr.left, nr.top, nr.right - nr.left, nr.bottom - nr.top); + + QTLWExtra * const tlwExtra = widget->d_func()->maybeTopData(); + if (tlwExtra && tlwExtra->isSetGeometry == 1) { + widget->d_func()->setGeometry_sys_helper(newRect.left(), newRect.top(), newRect.width(), newRect.height(), tlwExtra->isMove); + } else { + //implicitly removes the maximized bit + if((widget->data->window_state & Qt::WindowMaximized) && + IsWindowInStandardState(wid, 0, 0)) { + widget->data->window_state &= ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state + | Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + + } + + handled_event = false; + const QRect oldRect = widget->data->crect; + if((flags & kWindowBoundsChangeOriginChanged)) { + if(nr.left != oldRect.x() || nr.top != oldRect.y()) { + widget->data->crect.moveTo(nr.left, nr.top); + QMoveEvent qme(widget->data->crect.topLeft(), oldRect.topLeft()); + QApplication::sendSpontaneousEvent(widget, &qme); + } + } + if((flags & kWindowBoundsChangeSizeChanged)) { + if (widget->isWindow()) { + QSize newSize = QLayout::closestAcceptableSize(widget, newRect.size()); + int dh = newSize.height() - newRect.height(); + int dw = newSize.width() - newRect.width(); + if (dw != 0 || dh != 0) { + handled_event = true; // We want to change the bounds, so we handle the event + + // set the rect, so we can also do the resize down below (yes, we need to resize). + newRect.setBottom(newRect.bottom() + dh); + newRect.setRight(newRect.right() + dw); + + nr.left = newRect.x(); + nr.top = newRect.y(); + nr.right = nr.left + newRect.width(); + nr.bottom = nr.top + newRect.height(); + SetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &nr); + } + } + + if (oldRect.width() != newRect.width() || oldRect.height() != newRect.height()) { + widget->data->crect.setSize(newRect.size()); + HIRect bounds = CGRectMake(0, 0, newRect.width(), newRect.height()); + + // If the WA_StaticContents attribute is set we can optimize the resize + // by only repainting the newly exposed area. We do this by disabling + // painting when setting the size of the view. The OS will invalidate + // the newly exposed area for us. + const bool staticContents = widget->testAttribute(Qt::WA_StaticContents); + const HIViewRef view = qt_mac_nativeview_for(widget); + if (staticContents) + HIViewSetDrawingEnabled(view, false); + HIViewSetFrame(view, &bounds); + if (staticContents) + HIViewSetDrawingEnabled(view, true); + + QResizeEvent qre(newRect.size(), oldRect.size()); + QApplication::sendSpontaneousEvent(widget, &qre); + qt_event_request_window_change(widget); + } + } + } + } else if (ekind == kEventWindowHidden) { + // Make sure that we also hide any visible sheets on our window. + // Cocoa does the right thing for us. + const QObjectList children = widget->children(); + const int childCount = children.count(); + for (int i = 0; i < childCount; ++i) { + QObject *obj = children.at(i); + if (obj->isWidgetType()) { + QWidget *widget = static_cast<QWidget *>(obj); + if (qt_mac_is_macsheet(widget) && widget->isVisible()) + widget->hide(); + } + } + } else { + handled_event = false; + } + break; } + case kEventClassMouse: { +#if 0 + return SendEventToApplication(event); +#endif + + bool send_to_app = false; + { + WindowPartCode wpc; + if (GetEventParameter(event, kEventParamWindowPartCode, typeWindowPartCode, 0, + sizeof(wpc), 0, &wpc) == noErr && wpc != inContent) + send_to_app = true; + } + if(!send_to_app) { + WindowRef window; + if(GetEventParameter(event, kEventParamWindowRef, typeWindowRef, 0, + sizeof(window), 0, &window) == noErr) { + HIViewRef hiview; + if(HIViewGetViewForMouseEvent(HIViewGetRoot(window), event, &hiview) == noErr) { + if(QWidget *w = QWidget::find((WId)hiview)) { +#if 0 + send_to_app = !w->isActiveWindow(); +#else + Q_UNUSED(w); + send_to_app = true; +#endif + } + } + } + } + if(send_to_app) + return SendEventToApplication(event); + handled_event = false; + break; } + +#ifndef QT_NO_GESTURES + case kEventClassGesture: { + // First, find the widget that was under + // the mouse when the gesture happened: + HIPoint screenLocation; + if (GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, 0, + sizeof(screenLocation), 0, &screenLocation) != noErr) { + handled_event = false; + break; + } + QWidget *widget = QApplication::widgetAt(screenLocation.x, screenLocation.y); + if (!widget) { + handled_event = false; + break; + } + + QNativeGestureEvent qNGEvent; + qNGEvent.position = QPoint(screenLocation.x, screenLocation.y); + + switch (ekind) { + case kEventGestureStarted: + qNGEvent.gestureType = QNativeGestureEvent::GestureBegin; + break; + case kEventGestureEnded: + qNGEvent.gestureType = QNativeGestureEvent::GestureEnd; + break; + case kEventGestureRotate: { + CGFloat amount; + if (GetEventParameter(event, kEventParamRotationAmount, 'cgfl', 0, + sizeof(amount), 0, &amount) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Rotate; + qNGEvent.percentage = float(-amount); + break; } + case kEventGestureSwipe: { + HIPoint swipeDirection; + if (GetEventParameter(event, kEventParamSwipeDirection, typeHIPoint, 0, + sizeof(swipeDirection), 0, &swipeDirection) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Swipe; + if (swipeDirection.x == 1) + qNGEvent.angle = 180.0f; + else if (swipeDirection.x == -1) + qNGEvent.angle = 0.0f; + else if (swipeDirection.y == 1) + qNGEvent.angle = 90.0f; + else if (swipeDirection.y == -1) + qNGEvent.angle = 270.0f; + break; } + case kEventGestureMagnify: { + CGFloat amount; + if (GetEventParameter(event, kEventParamMagnificationAmount, 'cgfl', 0, + sizeof(amount), 0, &amount) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Zoom; + qNGEvent.percentage = float(amount); + break; } + } + + QApplication::sendSpontaneousEvent(widget, &qNGEvent); + break; } +#endif // QT_NO_GESTURES + + default: + handled_event = false; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +} + +// widget events +static HIObjectClassRef widget_class = 0; +static EventTypeSpec widget_events[] = { + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectDestruct }, + + { kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlInitialize }, + { kEventClassControl, kEventControlGetPartRegion }, + { kEventClassControl, kEventControlGetClickActivation }, + { kEventClassControl, kEventControlSetFocusPart }, + { kEventClassControl, kEventControlDragEnter }, + { kEventClassControl, kEventControlDragWithin }, + { kEventClassControl, kEventControlDragLeave }, + { kEventClassControl, kEventControlDragReceive }, + { kEventClassControl, kEventControlOwningWindowChanged }, + { kEventClassControl, kEventControlBoundsChanged }, + { kEventClassControl, kEventControlGetSizeConstraints }, + { kEventClassControl, kEventControlVisibilityChanged }, + + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseMoved }, + { kEventClassMouse, kEventMouseDragged } +}; +static EventHandlerUPP mac_widget_eventUPP = 0; +static void cleanup_widget_eventUPP() +{ + DisposeEventHandlerUPP(mac_widget_eventUPP); + mac_widget_eventUPP = 0; +} +static const EventHandlerUPP make_widget_eventUPP() +{ + if(mac_widget_eventUPP) + return mac_widget_eventUPP; + qAddPostRoutine(cleanup_widget_eventUPP); + return mac_widget_eventUPP = NewEventHandlerUPP(QWidgetPrivate::qt_widget_event); +} +OSStatus QWidgetPrivate::qt_widget_event(EventHandlerCallRef er, EventRef event, void *) +{ + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + + bool handled_event = true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassHIObject: { + HIViewRef view = 0; + GetEventParameter(event, kEventParamHIObjectInstance, typeHIObjectRef, + 0, sizeof(view), 0, &view); + if(ekind == kEventHIObjectConstruct) { + if(view) { + HIViewChangeFeatures(view, kHIViewAllowsSubviews, 0); + SetEventParameter(event, kEventParamHIObjectInstance, + typeVoidPtr, sizeof(view), &view); + } + } else if(ekind == kEventHIObjectDestruct) { + //nothing to really do.. or is there? + } else { + handled_event = false; + } + break; } + case kEventClassControl: { + QWidget *widget = 0; + HIViewRef hiview = 0; + if(GetEventParameter(event, kEventParamDirectObject, typeControlRef, + 0, sizeof(hiview), 0, &hiview) == noErr) + widget = QWidget::find((WId)hiview); + if (widget && widget->macEvent(er, event)) + return noErr; + if(ekind == kEventControlDraw) { + if(widget && qt_isGenuineQWidget(hiview)) { + + // if there is a window change event pending for any gl child wigets, + // send it immediately. (required for flicker-free resizing) + extern void qt_mac_send_posted_gl_updates(QWidget *widget); + qt_mac_send_posted_gl_updates(widget); + + if (QApplicationPrivate::graphicsSystem() && !widget->d_func()->paintOnScreen()) { + widget->d_func()->syncBackingStore(); + widget->d_func()->dirtyOnWidget = QRegion(); + return noErr; + } + + //requested rgn + RgnHandle rgn; + GetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, 0, sizeof(rgn), 0, &rgn); + QRegion qrgn(qt_mac_convert_mac_region(rgn)); + + //update handles + GrafPtr qd = 0; + CGContextRef cg = 0; + if(GetEventParameter(event, kEventParamCGContextRef, typeCGContextRef, 0, sizeof(cg), 0, &cg) != noErr) { + Q_ASSERT(false); + } + widget->d_func()->hd = cg; + widget->d_func()->qd_hd = qd; + CGContextSaveGState(cg); + +#ifdef DEBUG_WIDGET_PAINT + const bool doDebug = true; + if(doDebug) { + qDebug("asked to draw %p[%p] [%s::%s] %p[%p] [%d] [%dx%d]", widget, hiview, widget->metaObject()->className(), + widget->objectName().local8Bit().data(), widget->parentWidget(), + (HIViewRef)(widget->parentWidget() ? qt_mac_nativeview_for(widget->parentWidget()) : (HIViewRef)0), + HIViewIsCompositingEnabled(hiview), qt_mac_posInWindow(widget).x(), qt_mac_posInWindow(widget).y()); +#if 0 + QVector<QRect> region_rects = qrgn.rects(); + qDebug("Region! %d", region_rects.count()); + for(int i = 0; i < region_rects.count(); i++) + qDebug("%d %d %d %d", region_rects[i].x(), region_rects[i].y(), + region_rects[i].width(), region_rects[i].height()); + region_rects = widget->d_func()->clp.rects(); + qDebug("Widget Region! %d", region_rects.count()); + for(int i = 0; i < region_rects.count(); i++) + qDebug("%d %d %d %d", region_rects[i].x(), region_rects[i].y(), + region_rects[i].width(), region_rects[i].height()); +#endif + } +#endif + if (widget->isVisible() && widget->updatesEnabled()) { //process the actual paint event. + if(widget->testAttribute(Qt::WA_WState_InPaintEvent)) + qWarning("QWidget::repaint: Recursive repaint detected"); + if (widget->isWindow() && !widget->d_func()->isOpaque + && !widget->testAttribute(Qt::WA_MacBrushedMetal)) { + QRect qrgnRect = qrgn.boundingRect(); + CGContextClearRect(cg, CGRectMake(qrgnRect.x(), qrgnRect.y(), qrgnRect.width(), qrgnRect.height())); + } + + QPoint redirectionOffset(0, 0); + QWidget *tl = widget->window(); + if (tl) { + Qt::WindowFlags flags = tl->windowFlags(); + if (flags & Qt::FramelessWindowHint + || (flags & Qt::CustomizeWindowHint && !(flags & Qt::WindowTitleHint))) { + if(tl->d_func()->extra && !tl->d_func()->extra->mask.isEmpty()) + redirectionOffset += tl->d_func()->extra->mask.boundingRect().topLeft(); + } + } + + //setup the context + widget->setAttribute(Qt::WA_WState_InPaintEvent); + QPaintEngine *engine = widget->paintEngine(); + if (engine) + engine->setSystemClip(qrgn); + + //handle the erase + if (engine && (!widget->testAttribute(Qt::WA_NoSystemBackground) + && (widget->isWindow() || widget->autoFillBackground()) + || widget->testAttribute(Qt::WA_TintedBackground) + || widget->testAttribute(Qt::WA_StyledBackground))) { +#ifdef DEBUG_WIDGET_PAINT + if(doDebug) + qDebug(" Handling erase for [%s::%s]", widget->metaObject()->className(), + widget->objectName().local8Bit().data()); +#endif + if (!redirectionOffset.isNull()) + widget->d_func()->setRedirected(widget, redirectionOffset); + + bool was_unclipped = widget->testAttribute(Qt::WA_PaintUnclipped); + widget->setAttribute(Qt::WA_PaintUnclipped, false); + QPainter p(widget); + p.setClipping(false); + if(was_unclipped) + widget->setAttribute(Qt::WA_PaintUnclipped); + widget->d_func()->paintBackground(&p, qrgn, widget->isWindow() ? DrawAsRoot : 0); + if (widget->testAttribute(Qt::WA_TintedBackground)) { + QColor tint = widget->palette().window().color(); + tint.setAlphaF(.6); + const QVector<QRect> &rects = qrgn.rects(); + for (int i = 0; i < rects.size(); ++i) + p.fillRect(rects.at(i), tint); + } + p.end(); + if (!redirectionOffset.isNull()) + widget->d_func()->restoreRedirected(); + } + + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + + //send the paint + redirectionOffset += widget->data->wrect.topLeft(); // Map from system to qt coordinates + if (!redirectionOffset.isNull()) + widget->d_func()->setRedirected(widget, redirectionOffset); + qrgn.translate(redirectionOffset); + QPaintEvent e(qrgn); + widget->d_func()->dirtyOnWidget = QRegion(); +#ifdef QT3_SUPPORT + e.setErased(true); +#endif + QApplication::sendSpontaneousEvent(widget, &e); + if (!redirectionOffset.isNull()) + widget->d_func()->restoreRedirected(); + + //cleanup + if (engine) + engine->setSystemClip(QRegion()); + + widget->setAttribute(Qt::WA_WState_InPaintEvent, false); + if(!widget->testAttribute(Qt::WA_PaintOutsidePaintEvent) && widget->paintingActive()) + qWarning("QWidget: It is dangerous to leave painters active on a widget outside of the PaintEvent"); + } + + widget->d_func()->hd = 0; + widget->d_func()->qd_hd = 0; + CGContextRestoreGState(cg); + } else if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) { + CallNextEventHandler(er, event); + } + } else if(ekind == kEventControlInitialize) { + if(HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) { + UInt32 features = kControlSupportsDragAndDrop | kControlSupportsClickActivation | kControlSupportsFocus; + SetEventParameter(event, kEventParamControlFeatures, typeUInt32, sizeof(features), &features); + } else { + handled_event = false; + } + } else if(ekind == kEventControlSetFocusPart) { + if(widget) { + ControlPartCode part; + GetEventParameter(event, kEventParamControlPart, typeControlPartCode, 0, + sizeof(part), 0, &part); + if(part == kControlFocusNoPart){ + if (widget->hasFocus()) + QApplicationPrivate::setFocusWidget(0, Qt::OtherFocusReason); + } else + widget->setFocus(); + } + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + } else if(ekind == kEventControlGetClickActivation) { + ClickActivationResult clickT = kActivateAndIgnoreClick; + SetEventParameter(event, kEventParamClickActivation, typeClickActivationResult, + sizeof(clickT), &clickT); + } else if(ekind == kEventControlGetPartRegion) { + handled_event = false; + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget) && CallNextEventHandler(er, event) == noErr) { + handled_event = true; + break; + } + if(widget && !widget->isWindow()) { + ControlPartCode part; + GetEventParameter(event, kEventParamControlPart, typeControlPartCode, 0, + sizeof(part), 0, &part); + if(part == kControlClickableMetaPart && widget->testAttribute(Qt::WA_TransparentForMouseEvents)) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + SetEmptyRgn(rgn); + handled_event = true; + } else if(part == kControlStructureMetaPart || part == kControlClickableMetaPart) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + SetRectRgn(rgn, 0, 0, widget->width(), widget->height()); + if(QWidgetPrivate::qt_widget_rgn(widget, kWindowStructureRgn, rgn, false)) + handled_event = true; + } else if(part == kControlOpaqueMetaPart) { + if(widget->d_func()->isOpaque) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(RgnHandle), 0, &rgn); + SetRectRgn(rgn, 0, 0, widget->width(), widget->height()); + QWidgetPrivate::qt_widget_rgn(widget, kWindowStructureRgn, rgn, false); + SetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, + sizeof(RgnHandle), &rgn); + handled_event = true; + } + } + } + } else if(ekind == kEventControlOwningWindowChanged) { + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + if(widget && qt_mac_window_for(hiview)) { + WindowRef foo = 0; + GetEventParameter(event, kEventParamControlCurrentOwningWindow, typeWindowRef, 0, + sizeof(foo), 0, &foo); + widget->d_func()->initWindowPtr(); + } + if (widget) + qt_event_request_window_change(widget); + } else if(ekind == kEventControlDragEnter || ekind == kEventControlDragWithin || + ekind == kEventControlDragLeave || ekind == kEventControlDragReceive) { + // dnd are really handled in qdnd_mac.cpp, + // just modularize the code a little... + DragRef drag; + GetEventParameter(event, kEventParamDragRef, typeDragRef, 0, sizeof(drag), 0, &drag); + handled_event = false; + bool drag_allowed = false; + + QWidget *dropWidget = widget; + if (qobject_cast<QFocusFrame *>(widget)){ + // We might shadow widgets underneath the focus + // frame, so stay interrested, and let the dnd through + drag_allowed = true; + handled_event = true; + Point where; + GetDragMouse(drag, &where, 0); + dropWidget = QApplication::widgetAt(QPoint(where.h, where.v)); + + if (dropWidget != QDragManager::self()->currentTarget()) { + // We have to 'fake' enter and leave events for the shaddowed widgets: + if (ekind == kEventControlDragEnter) { + if (QDragManager::self()->currentTarget()) + QDragManager::self()->currentTarget()->d_func()->qt_mac_dnd_event(kEventControlDragLeave, drag); + if (dropWidget) { + dropWidget->d_func()->qt_mac_dnd_event(kEventControlDragEnter, drag); + } + // Set dropWidget to zero, so qt_mac_dnd_event + // doesn't get called a second time below: + dropWidget = 0; + } else if (ekind == kEventControlDragLeave) { + dropWidget = QDragManager::self()->currentTarget(); + if (dropWidget) { + dropWidget->d_func()->qt_mac_dnd_event(kEventControlDragLeave, drag); + } + // Set dropWidget to zero, so qt_mac_dnd_event + // doesn't get called a second time below: + dropWidget = 0; + } + } + } + + // Send the dnd event to the widget: + if (dropWidget && dropWidget->d_func()->qt_mac_dnd_event(ekind, drag)) { + drag_allowed = true; + handled_event = true; + } + + if (ekind == kEventControlDragEnter) { + // If we don't accept the enter event, we will + // receive no more drag events for this widget + const Boolean wouldAccept = drag_allowed ? true : false; + SetEventParameter(event, kEventParamControlWouldAcceptDrop, typeBoolean, + sizeof(wouldAccept), &wouldAccept); + } + } else if (ekind == kEventControlBoundsChanged) { + if (!widget || widget->isWindow() || widget->testAttribute(Qt::WA_Moved) || widget->testAttribute(Qt::WA_Resized)) { + handled_event = false; + } else { + // Sync our view in case some other (non-Qt) view is controlling us. + handled_event = true; + Rect newBounds; + GetEventParameter(event, kEventParamCurrentBounds, + typeQDRectangle, 0, sizeof(Rect), 0, &newBounds); + QRect rect(newBounds.left, newBounds.top, + newBounds.right - newBounds.left, newBounds.bottom - newBounds.top); + + bool moved = widget->testAttribute(Qt::WA_Moved); + bool resized = widget->testAttribute(Qt::WA_Resized); + widget->setGeometry(rect); + widget->setAttribute(Qt::WA_Moved, moved); + widget->setAttribute(Qt::WA_Resized, resized); + qt_event_request_window_change(widget); + } + } else if (ekind == kEventControlGetSizeConstraints) { + if (!widget || !qt_isGenuineQWidget(widget)) { + handled_event = false; + } else { + handled_event = true; + QWidgetItem item(widget); + QSize size = item.minimumSize(); + HISize hisize = { size.width(), size.height() }; + SetEventParameter(event, kEventParamMinimumSize, typeHISize, sizeof(HISize), &hisize); + size = item.maximumSize(); + hisize.width = size.width() + 2; // ### shouldn't have to add 2 (but it works). + hisize.height = size.height(); + SetEventParameter(event, kEventParamMaximumSize, typeHISize, sizeof(HISize), &hisize); + } + } else if (ekind == kEventControlVisibilityChanged) { + handled_event = false; + if (widget) { + qt_event_request_window_change(widget); + if (!HIViewIsVisible(HIViewRef(widget->winId()))) { + if (widget == qt_button_down) + qt_button_down = 0; + } + } + } + break; } + case kEventClassMouse: { + bool send_to_app = false; + if(qt_button_down) + send_to_app = true; + if(send_to_app) { + OSStatus err = SendEventToApplication(event); + if(err != noErr) + handled_event = false; + } else { + CallNextEventHandler(er, event); + } + break; } + default: + handled_event = false; + break; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +} +#endif + +OSViewRef qt_mac_create_widget(QWidget *widget, QWidgetPrivate *widgetPrivate, OSViewRef parent) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaView) *view = [[QT_MANGLE_NAMESPACE(QCocoaView) alloc] initWithQWidget:widget widgetPrivate:widgetPrivate]; + +#ifdef ALIEN_DEBUG + qDebug() << "Creating NSView for" << widget; +#endif + + if (view && parent) + [parent addSubview:view]; + return view; +#else + Q_UNUSED(widget); + Q_UNUSED(widgetPrivate); + if(!widget_class) { + OSStatus err = HIObjectRegisterSubclass(kObjectQWidget, kHIViewClassID, 0, make_widget_eventUPP(), + GetEventTypeCount(widget_events), widget_events, + 0, &widget_class); + if (err && err != hiObjectClassExistsErr) + qWarning("QWidget: Internal error (%d)", __LINE__); + } + HIViewRef ret = 0; + if(HIObjectCreate(kObjectQWidget, 0, (HIObjectRef*)&ret) != noErr) + qWarning("QWidget: Internal error (%d)", __LINE__); + if(ret && parent) + HIViewAddSubview(parent, ret); + return ret; +#endif +} + +void qt_mac_unregister_widget() +{ +#ifndef QT_MAC_USE_COCOA + HIObjectUnregisterClass(widget_class); + widget_class = 0; +#endif +} + +void QWidgetPrivate::toggleDrawers(bool visible) +{ + for (int i = 0; i < children.size(); ++i) { + register QObject *object = children.at(i); + if (!object->isWidgetType()) + continue; + QWidget *widget = static_cast<QWidget*>(object); + if(qt_mac_is_macdrawer(widget)) { + bool oldState = widget->testAttribute(Qt::WA_WState_ExplicitShowHide); + if(visible) { + if (!widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) + widget->show(); + } else { + widget->hide(); + if(!oldState) + widget->setAttribute(Qt::WA_WState_ExplicitShowHide, false); + } + } + } +} + +/***************************************************************************** + QWidgetPrivate member functions + *****************************************************************************/ +bool QWidgetPrivate::qt_mac_update_sizer(QWidget *w, int up) +{ + // I'm not sure what "up" is + if(!w || !w->isWindow()) + return false; + + QTLWExtra *topData = w->d_func()->topData(); + QWExtra *extraData = w->d_func()->extraData(); + // topData->resizer is only 4 bits, so subtracting -1 from zero causes bad stuff + // to happen, prevent that here (you really want the thing hidden). + if (up >= 0 || topData->resizer != 0) + topData->resizer += up; + OSWindowRef windowRef = qt_mac_window_for(OSViewRef(w->effectiveWinId())); + { +#ifndef QT_MAC_USE_COCOA + WindowClass wclass; + GetWindowClass(windowRef, &wclass); + if(!(GetAvailableWindowAttributes(wclass) & kWindowResizableAttribute)) + return true; +#endif + } + bool remove_grip = (topData->resizer || (w->windowFlags() & Qt::FramelessWindowHint) + || (extraData->maxw && extraData->maxh && + extraData->maxw == extraData->minw && extraData->maxh == extraData->minh)); +#ifndef QT_MAC_USE_COCOA + WindowAttributes attr; + GetWindowAttributes(windowRef, &attr); + if(remove_grip) { + if(attr & kWindowResizableAttribute) { + ChangeWindowAttributes(qt_mac_window_for(w), kWindowNoAttributes, + kWindowResizableAttribute); + ReshapeCustomWindow(qt_mac_window_for(w)); + } + } else if(!(attr & kWindowResizableAttribute)) { + ChangeWindowAttributes(windowRef, kWindowResizableAttribute, + kWindowNoAttributes); + ReshapeCustomWindow(windowRef); + } +#else + [windowRef setShowsResizeIndicator:!remove_grip]; +#endif + return true; +} + +void QWidgetPrivate::qt_clean_root_win() +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [qt_root_win release]; +#else + if(!qt_root_win) + return; + CFRelease(qt_root_win); +#endif + qt_root_win = 0; +} + +bool QWidgetPrivate::qt_create_root_win() +{ + if(qt_root_win) + return false; + const QSize desktopSize = qt_mac_desktopSize(); + QRect desktopRect(QPoint(0, 0), desktopSize); +#ifdef QT_MAC_USE_COCOA + qt_root_win = qt_mac_create_window(0, kOverlayWindowClass, NSBorderlessWindowMask, desktopRect); +#else + WindowAttributes wattr = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute); + qt_root_win = qt_mac_create_window(0, kOverlayWindowClass, wattr, desktopRect); +#endif + if(!qt_root_win) + return false; + qAddPostRoutine(qt_clean_root_win); + return true; +} + +bool QWidgetPrivate::qt_widget_rgn(QWidget *widget, short wcode, RgnHandle rgn, bool force = false) +{ + bool ret = false; +#ifndef QT_MAC_USE_COCOA + switch(wcode) { + case kWindowStructureRgn: { + if(widget) { + if(widget->d_func()->extra && !widget->d_func()->extra->mask.isEmpty()) { + QRegion rin = qt_mac_convert_mac_region(rgn); + if(!rin.isEmpty()) { + QPoint rin_tl = rin.boundingRect().topLeft(); //in offset + rin.translate(-rin_tl.x(), -rin_tl.y()); //bring into same space as below + QRegion mask = widget->d_func()->extra->mask; + Qt::WindowFlags flags = widget->windowFlags(); + if(widget->isWindow() + && !(flags & Qt::FramelessWindowHint + || (flags & Qt::CustomizeWindowHint && !(flags & Qt::WindowTitleHint)))) { + QRegion title; + { + QMacSmartQuickDrawRegion rgn(qt_mac_get_rgn()); + GetWindowRegion(qt_mac_window_for(widget), kWindowTitleBarRgn, rgn); + title = qt_mac_convert_mac_region(rgn); + } + QRect br = title.boundingRect(); + mask.translate(0, br.height()); //put the mask 'under' the title bar.. + title.translate(-br.x(), -br.y()); + mask += title; + } + + QRegion cr = rin & mask; + cr.translate(rin_tl.x(), rin_tl.y()); //translate back to incoming space + CopyRgn(QMacSmartQuickDrawRegion(cr.toQDRgn()), rgn); + } + ret = true; + } else if(force) { + QRegion cr(widget->geometry()); + CopyRgn(QMacSmartQuickDrawRegion(cr.toQDRgn()), rgn); + ret = true; + } + } + break; } + default: break; + } + //qDebug() << widget << ret << wcode << qt_mac_convert_mac_region(rgn); +#else + Q_UNUSED(widget); + Q_UNUSED(wcode); + Q_UNUSED(rgn); + Q_UNUSED(force); +#endif + return ret; +} + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ +void QWidgetPrivate::determineWindowClass() +{ + Q_Q(QWidget); +#if !defined(QT_NO_MAINWINDOW) && !defined(QT_NO_TOOLBAR) + // Make sure that QMainWindow has the MacWindowToolBarButtonHint when the + // unifiedTitleAndToolBarOnMac property is ON. This is to avoid reentry of + // setParent() triggered by the QToolBar::event(QEvent::ParentChange). + QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q); + if (mainWindow && mainWindow->unifiedTitleAndToolBarOnMac()) { + data.window_flags |= Qt::MacWindowToolBarButtonHint; + } +#endif +#ifndef QT_MAC_USE_COCOA +// ### COCOA:Interleave these better! + + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + const bool popup = (type == Qt::Popup); + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + WindowClass wclass = kSheetWindowClass; + if(qt_mac_is_macdrawer(q)) + wclass = kDrawerWindowClass; + else if (q->testAttribute(Qt::WA_ShowModal) && flags & Qt::CustomizeWindowHint) + wclass = kDocumentWindowClass; + else if(popup || (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 && type == Qt::SplashScreen)) + wclass = kModalWindowClass; + else if(q->testAttribute(Qt::WA_ShowModal)) + wclass = kMovableModalWindowClass; + else if(type == Qt::ToolTip) + wclass = kHelpWindowClass; + else if(type == Qt::Tool || (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5 + && type == Qt::SplashScreen)) + wclass = kFloatingWindowClass; + else + wclass = kDocumentWindowClass; + + WindowGroupRef grp = 0; + WindowAttributes wattr = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute); + if (q->testAttribute(Qt::WA_MacFrameworkScaled)) + wattr |= kWindowFrameworkScaledAttribute; + if(qt_mac_is_macsheet(q)) { + //grp = GetWindowGroupOfClass(kMovableModalWindowClass); + wclass = kSheetWindowClass; + } else { + grp = GetWindowGroupOfClass(wclass); + // Shift things around a bit to get the correct window class based on the presence + // (or lack) of the border. + bool customize = flags & Qt::CustomizeWindowHint; + bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint))); + if (framelessWindow) { + if(wclass == kDocumentWindowClass) { + wattr |= kWindowNoTitleBarAttribute; + } else if(wclass == kFloatingWindowClass) { + wattr |= kWindowNoTitleBarAttribute; + } else if (wclass == kMovableModalWindowClass) { + wclass = kModalWindowClass; + } + } else { + if(wclass != kModalWindowClass) + wattr |= kWindowResizableAttribute; + } + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + if(wclass != kModalWindowClass && wclass != kMovableModalWindowClass + && wclass != kSheetWindowClass && wclass != kPlainWindowClass + && !framelessWindow && wclass != kDrawerWindowClass + && wclass != kHelpWindowClass) { + if (flags & Qt::WindowMaximizeButtonHint) + wattr |= kWindowFullZoomAttribute; + if (flags & Qt::WindowMinimizeButtonHint) + wattr |= kWindowCollapseBoxAttribute; + if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint) + wattr |= kWindowCloseBoxAttribute; + if (flags & Qt::MacWindowToolBarButtonHint) + wattr |= kWindowToolbarButtonAttribute; + } else { + // Clear these hints so that we aren't call them on invalid windows + flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint + | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint); + } + } + if((popup || type == Qt::Tool) && !q->isModal()) + wattr |= kWindowHideOnSuspendAttribute; + wattr |= kWindowLiveResizeAttribute; + +#ifdef DEBUG_WINDOW_CREATE +#define ADD_DEBUG_WINDOW_NAME(x) { x, #x } + struct { + UInt32 tag; + const char *name; + } known_attribs[] = { + ADD_DEBUG_WINDOW_NAME(kWindowCompositingAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowMetalAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCollapseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHorizontalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowVerticalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowResizableAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoActivatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoUpdatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowOpaqueForEventsAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowLiveResizeAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCloseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + { 0, 0 } + }, known_classes[] = { + ADD_DEBUG_WINDOW_NAME(kHelpWindowClass), + ADD_DEBUG_WINDOW_NAME(kPlainWindowClass), + ADD_DEBUG_WINDOW_NAME(kDrawerWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kSheetWindowClass), + ADD_DEBUG_WINDOW_NAME(kFloatingWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kDocumentWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kMovableModalWindowClass), + ADD_DEBUG_WINDOW_NAME(kModalWindowClass), + { 0, 0 } + }; + qDebug("Qt: internal: ************* Creating new window %p (%s::%s)", q, q->metaObject()->className(), + q->objectName().toLocal8Bit().constData()); + bool found_class = false; + for(int i = 0; known_classes[i].name; i++) { + if(wclass == known_classes[i].tag) { + found_class = true; + qDebug("Qt: internal: ** Class: %s", known_classes[i].name); + break; + } + } + if(!found_class) + qDebug("Qt: internal: !! Class: Unknown! (%d)", (int)wclass); + if(wattr) { + WindowAttributes tmp_wattr = wattr; + qDebug("Qt: internal: ** Attributes:"); + for(int i = 0; tmp_wattr && known_attribs[i].name; i++) { + if((tmp_wattr & known_attribs[i].tag) == known_attribs[i].tag) { + tmp_wattr ^= known_attribs[i].tag; + qDebug("Qt: internal: * %s %s", known_attribs[i].name, + (GetAvailableWindowAttributes(wclass) & known_attribs[i].tag) ? "" : "(*)"); + } + } + if(tmp_wattr) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)tmp_wattr); + } +#endif + + /* Just to be extra careful we will change to the kUtilityWindowClass if the + requested attributes cannot be used */ + if((GetAvailableWindowAttributes(wclass) & wattr) != wattr) { + WindowClass tmp_class = wclass; + if(wclass == kToolbarWindowClass || wclass == kUtilityWindowClass) + wclass = kFloatingWindowClass; + if(tmp_class != wclass) { + if(!grp) + grp = GetWindowGroupOfClass(wclass); + wclass = tmp_class; + } + } + topData()->wclass = wclass; + topData()->wattr = wattr; +#else + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + const bool popup = (type == Qt::Popup); + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + WindowClass wclass = kSheetWindowClass; + if(qt_mac_is_macdrawer(q)) + wclass = kDrawerWindowClass; + else if (q->testAttribute(Qt::WA_ShowModal) && flags & Qt::CustomizeWindowHint) + wclass = kDocumentWindowClass; + else if(popup || (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 && type == Qt::SplashScreen)) + wclass = kModalWindowClass; + else if(type == Qt::Dialog) + wclass = kMovableModalWindowClass; + else if(type == Qt::ToolTip) + wclass = kHelpWindowClass; + else if(type == Qt::Tool || (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5 + && type == Qt::SplashScreen)) + wclass = kFloatingWindowClass; + else if(q->testAttribute(Qt::WA_ShowModal)) + wclass = kMovableModalWindowClass; + else + wclass = kDocumentWindowClass; + + WindowAttributes wattr = NSBorderlessWindowMask; + if(qt_mac_is_macsheet(q)) { + //grp = GetWindowGroupOfClass(kMovableModalWindowClass); + wclass = kSheetWindowClass; + wattr = NSTitledWindowMask | NSResizableWindowMask; + } else { +#ifndef QT_MAC_USE_COCOA + grp = GetWindowGroupOfClass(wclass); +#endif + // Shift things around a bit to get the correct window class based on the presence + // (or lack) of the border. + bool customize = flags & Qt::CustomizeWindowHint; + bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint))); + if (framelessWindow) { + if (wclass == kDocumentWindowClass) { + wclass = kSimpleWindowClass; + } else if (wclass == kFloatingWindowClass) { + wclass = kToolbarWindowClass; + } else if (wclass == kMovableModalWindowClass) { + wclass = kModalWindowClass; + } + } else { + wattr |= NSTitledWindowMask; + if (wclass != kModalWindowClass) + wattr |= NSResizableWindowMask; + } + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + if (wclass != kModalWindowClass + && wclass != kSheetWindowClass && wclass != kPlainWindowClass + && !framelessWindow && wclass != kDrawerWindowClass + && wclass != kHelpWindowClass) { + if (flags & Qt::WindowMinimizeButtonHint) + wattr |= NSMiniaturizableWindowMask; + if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint) + wattr |= NSClosableWindowMask; + } else { + // Clear these hints so that we aren't call them on invalid windows + flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint + | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint); + } + } + if (q->testAttribute(Qt::WA_MacBrushedMetal)) + wattr |= NSTexturedBackgroundWindowMask; + +#ifdef DEBUG_WINDOW_CREATE +#define ADD_DEBUG_WINDOW_NAME(x) { x, #x } + struct { + UInt32 tag; + const char *name; + } known_attribs[] = { + ADD_DEBUG_WINDOW_NAME(kWindowCompositingAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowMetalAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCollapseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHorizontalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowVerticalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowResizableAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoActivatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoUpdatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowOpaqueForEventsAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowLiveResizeAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCloseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + { 0, 0 } + }, known_classes[] = { + ADD_DEBUG_WINDOW_NAME(kHelpWindowClass), + ADD_DEBUG_WINDOW_NAME(kPlainWindowClass), + ADD_DEBUG_WINDOW_NAME(kDrawerWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kSheetWindowClass), + ADD_DEBUG_WINDOW_NAME(kFloatingWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kDocumentWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kMovableModalWindowClass), + ADD_DEBUG_WINDOW_NAME(kModalWindowClass), + { 0, 0 } + }; + qDebug("Qt: internal: ************* Creating new window %p (%s::%s)", q, q->metaObject()->className(), + q->objectName().toLocal8Bit().constData()); + bool found_class = false; + for(int i = 0; known_classes[i].name; i++) { + if(wclass == known_classes[i].tag) { + found_class = true; + qDebug("Qt: internal: ** Class: %s", known_classes[i].name); + break; + } + } + if(!found_class) + qDebug("Qt: internal: !! Class: Unknown! (%d)", (int)wclass); + if(wattr) { + WindowAttributes tmp_wattr = wattr; + qDebug("Qt: internal: ** Attributes:"); + for(int i = 0; tmp_wattr && known_attribs[i].name; i++) { + if((tmp_wattr & known_attribs[i].tag) == known_attribs[i].tag) { + tmp_wattr ^= known_attribs[i].tag; + } + } + if(tmp_wattr) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)tmp_wattr); + } +#endif + +#ifndef QT_MAC_USE_COCOA + /* Just to be extra careful we will change to the kUtilityWindowClass if the + requested attributes cannot be used */ + if((GetAvailableWindowAttributes(wclass) & wattr) != wattr) { + WindowClass tmp_class = wclass; + if(wclass == kToolbarWindowClass || wclass == kUtilityWindowClass) + wclass = kFloatingWindowClass; + if(tmp_class != wclass) { + if(!grp) + grp = GetWindowGroupOfClass(wclass); + wclass = tmp_class; + } + } +#endif +#endif + topData()->wclass = wclass; + topData()->wattr = wattr; +} + +#ifndef QT_MAC_USE_COCOA // This is handled in Cocoa via our category. +void QWidgetPrivate::initWindowPtr() +{ + Q_Q(QWidget); + OSWindowRef windowRef = qt_mac_window_for(qt_mac_nativeview_for(q)); //do not create! + if(!windowRef) + return; + QWidget *window = q->window(), *oldWindow = 0; + if(GetWindowProperty(windowRef, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(oldWindow), 0, &oldWindow) == noErr) { + Q_ASSERT(window == oldWindow); + return; + } + + if(SetWindowProperty(windowRef, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(window), &window) != noErr) + qWarning("Qt:Internal error (%s:%d)", __FILE__, __LINE__); //no real way to recover + if(!q->windowType() != Qt::Desktop) { //setup an event callback handler on the window + InstallWindowEventHandler(windowRef, make_win_eventUPP(), GetEventTypeCount(window_events), + window_events, static_cast<void *>(qApp), &window_event); + } +} + +void QWidgetPrivate::finishCreateWindow_sys_Carbon(OSWindowRef windowRef) +{ + Q_Q(QWidget); + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + const bool desktop = (type == Qt::Desktop); + const bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + QTLWExtra *topExtra = topData(); + quint32 wattr = topExtra->wattr; + if (!desktop) + SetAutomaticControlDragTrackingEnabledForWindow(windowRef, true); + HIWindowChangeFeatures(windowRef, kWindowCanCollapse, 0); + if (wattr & kWindowHideOnSuspendAttribute) + HIWindowChangeAvailability(windowRef, kHIWindowExposeHidden, 0); + else + HIWindowChangeAvailability(windowRef, 0, kHIWindowExposeHidden); + if ((flags & Qt::WindowStaysOnTopHint)) + ChangeWindowAttributes(windowRef, kWindowNoAttributes, kWindowHideOnSuspendAttribute); + if (qt_mac_is_macdrawer(q) && parentWidget) + SetDrawerParent(windowRef, qt_mac_window_for (parentWidget)); + if (topExtra->group) { + qt_mac_release_window_group(topExtra->group); + topExtra->group = 0; + } + if (type == Qt::ToolTip) + qt_mac_set_window_group_to_tooltip(windowRef); + else if (type == Qt::Popup && (flags & Qt::WindowStaysOnTopHint)) + qt_mac_set_window_group_to_popup(windowRef); + else if (flags & Qt::WindowStaysOnTopHint) + qt_mac_set_window_group_to_stays_on_top(windowRef, type); + else if (dialog) + SetWindowGroup(windowRef, GetWindowGroupOfClass(kMovableModalWindowClass)); + +#ifdef DEBUG_WINDOW_CREATE + if (WindowGroupRef grpf = GetWindowGroup(windowRef)) { + QCFString cfname; + CopyWindowGroupName(grpf, &cfname); + SInt32 lvl; + GetWindowGroupLevel(grpf, &lvl); + const char *from = "Default"; + if (topExtra && grpf == topData()->group) + from = "Created"; + else if (grpf == grp) + from = "Copied"; + qDebug("Qt: internal: With window group '%s' [%p] @ %d: %s", + static_cast<QString>(cfname).toLatin1().constData(), grpf, (int)lvl, from); + } else { + qDebug("Qt: internal: No window group!!!"); + } + HIWindowAvailability hi_avail = 0; + if (HIWindowGetAvailability(windowRef, &hi_avail) == noErr) { + struct { + UInt32 tag; + const char *name; + } known_avail[] = { + ADD_DEBUG_WINDOW_NAME(kHIWindowExposeHidden), + { 0, 0 } + }; + qDebug("Qt: internal: ** HIWindowAvailibility:"); + for (int i = 0; hi_avail && known_avail[i].name; i++) { + if ((hi_avail & known_avail[i].tag) == known_avail[i].tag) { + hi_avail ^= known_avail[i].tag; + qDebug("Qt: internal: * %s", known_avail[i].name); + } + } + if (hi_avail) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)hi_avail); + } +#undef ADD_DEBUG_WINDOW_NAME +#endif + if (extra && !extra->mask.isEmpty()) + ReshapeCustomWindow(windowRef); + SetWindowModality(windowRef, kWindowModalityNone, 0); + if (qt_mac_is_macdrawer(q)) + SetDrawerOffsets(windowRef, 0.0, 25.0); + data.fstrut_dirty = true; // when we create a toplevel widget, the frame strut should be dirty + HIViewRef hiview = (HIViewRef)data.winid; + HIViewRef window_hiview = qt_mac_get_contentview_for(windowRef); + if(!hiview) { + hiview = qt_mac_create_widget(q, this, window_hiview); + setWinId((WId)hiview); + } else { + HIViewAddSubview(window_hiview, hiview); + } + if (hiview) { + Rect win_rect; + GetWindowBounds(qt_mac_window_for (window_hiview), kWindowContentRgn, &win_rect); + HIRect bounds = CGRectMake(0, 0, win_rect.right-win_rect.left, win_rect.bottom-win_rect.top); + HIViewSetFrame(hiview, &bounds); + HIViewSetVisible(hiview, true); + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + transferChildren(); + } + initWindowPtr(); + + if (topExtra->posFromMove) { + updateFrameStrut(); + const QRect &fStrut = frameStrut(); + Rect r; + SetRect(&r, data.crect.left(), data.crect.top(), data.crect.right() + 1, data.crect.bottom() + 1); + SetRect(&r, r.left + fStrut.left(), r.top + fStrut.top(), + (r.left + fStrut.left() + data.crect.width()) - fStrut.right(), + (r.top + fStrut.top() + data.crect.height()) - fStrut.bottom()); + SetWindowBounds(windowRef, kWindowContentRgn, &r); + topExtra->posFromMove = false; + } + + if (q->testAttribute(Qt::WA_WState_WindowOpacitySet)){ + q->setWindowOpacity(topExtra->opacity / 255.0f); + } else if (qt_mac_is_macsheet(q)){ + SetThemeWindowBackground(qt_mac_window_for(q), kThemeBrushSheetBackgroundTransparent, true); + CGFloat alpha = 0; + GetWindowAlpha(qt_mac_window_for(q), &alpha); + if (alpha == 1){ + // For some reason the 'SetThemeWindowBackground' does not seem + // to work. So we do this little hack until it hopefully starts to + // work in newer versions of mac OS. + q->setWindowOpacity(0.95f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + } else{ + // If the window has been recreated after beeing e.g. a sheet, + // make sure that we don't report a faulty opacity: + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + // Since we only now have a window, sync our state. + macUpdateHideOnSuspend(); + macUpdateOpaqueSizeGrip(); + macUpdateMetalAttribute(); + macUpdateIgnoreMouseEvents(); + setWindowTitle_helper(extra->topextra->caption); + setWindowIconText_helper(extra->topextra->iconText); + setWindowFilePath_helper(extra->topextra->filePath); + setWindowModified_sys(q->isWindowModified()); + updateFrameStrut(); + qt_mac_update_sizer(q); + applyMaxAndMinSizeOnWindow(); +} +#else // QT_MAC_USE_COCOA + +void QWidgetPrivate::setWindowLevel() +{ + Q_Q(QWidget); + const QWidget * const windowParent = q->window()->parentWidget(); + const QWidget * const primaryWindow = windowParent ? windowParent->window() : 0; + NSInteger winLevel = -1; + + if (q->windowType() == Qt::Popup) { + winLevel = NSPopUpMenuWindowLevel; + // Popup should be in at least the same level as its parent. + if (primaryWindow) { + OSWindowRef parentRef = qt_mac_window_for(primaryWindow); + winLevel = qMax([parentRef level], winLevel); + } + } else if (q->windowType() == Qt::Tool) { + winLevel = NSFloatingWindowLevel; + } else if (q->windowType() == Qt::Dialog) { + // Correct modality level (NSModalPanelWindowLevel) will be + // set by cocoa when creating a modal session later. + winLevel = NSNormalWindowLevel; + } + + // StayOnTop window should appear above Tool windows. + if (data.window_flags & Qt::WindowStaysOnTopHint) + winLevel = NSPopUpMenuWindowLevel; + // Tooltips should appear above StayOnTop windows. + if (q->windowType() == Qt::ToolTip) + winLevel = NSScreenSaverWindowLevel; + // All other types are Normal level. + if (winLevel == -1) + winLevel = NSNormalWindowLevel; + [qt_mac_window_for(q) setLevel:winLevel]; +} + +void QWidgetPrivate::finishCreateWindow_sys_Cocoa(void * /*NSWindow * */ voidWindowRef) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + NSWindow *windowRef = static_cast<NSWindow *>(voidWindowRef); + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + const bool popup = (type == Qt::Popup); + const bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + QTLWExtra *topExtra = topData(); + + if ((popup || type == Qt::Tool || type == Qt::ToolTip) && !q->isModal()) { + [windowRef setHidesOnDeactivate:YES]; + } else { + [windowRef setHidesOnDeactivate:NO]; + } + if (q->testAttribute(Qt::WA_MacNoShadow)) + [windowRef setHasShadow:NO]; + else + [windowRef setHasShadow:YES]; + Q_UNUSED(parentWidget); + Q_UNUSED(dialog); + + data.fstrut_dirty = true; // when we create a toplevel widget, the frame strut should be dirty + + OSViewRef nsview = (OSViewRef)data.winid; + if (!nsview) { + nsview = qt_mac_create_widget(q, this, 0); + setWinId(WId(nsview)); + } + [windowRef setContentView:nsview]; + [nsview setHidden:NO]; + transferChildren(); + + // Tell Cocoa explicit that we wan't the view to receive key events + // (regardless of focus policy) because this is how it works on other + // platforms (and in the carbon port): + [windowRef makeFirstResponder:nsview]; + + if (topExtra->posFromMove) { + updateFrameStrut(); + + const QRect &fStrut = frameStrut(); + const QRect &crect = data.crect; + const QRect frameRect(QPoint(crect.left(), crect.top()), + QSize(fStrut.left() + fStrut.right() + crect.width(), + fStrut.top() + fStrut.bottom() + crect.height())); + NSRect cocoaFrameRect = NSMakeRect(frameRect.x(), flipYCoordinate(frameRect.bottom() + 1), + frameRect.width(), frameRect.height()); + [windowRef setFrame:cocoaFrameRect display:NO]; + topExtra->posFromMove = false; + } + + if (q->testAttribute(Qt::WA_WState_WindowOpacitySet)){ + q->setWindowOpacity(topExtra->opacity / 255.0f); + } else if (qt_mac_is_macsheet(q)){ + CGFloat alpha = [qt_mac_window_for(q) alphaValue]; + if (alpha >= 1.0) { + q->setWindowOpacity(0.95f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + } else{ + // If the window has been recreated after beeing e.g. a sheet, + // make sure that we don't report a faulty opacity: + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + // Its more performant to handle the mouse cursor + // ourselves, expecially when using alien widgets: + [windowRef disableCursorRects]; + + setWindowLevel(); + macUpdateHideOnSuspend(); + macUpdateOpaqueSizeGrip(); + macUpdateIgnoreMouseEvents(); + setWindowTitle_helper(extra->topextra->caption); + setWindowIconText_helper(extra->topextra->iconText); + setWindowModified_sys(q->isWindowModified()); + updateFrameStrut(); + syncCocoaMask(); + macUpdateIsOpaque(); + qt_mac_update_sizer(q); + applyMaxAndMinSizeOnWindow(); +} + +#endif // QT_MAC_USE_COCOA + +/* + Recreates widget window. Useful if immutable + properties for it has changed. + */ +void QWidgetPrivate::recreateMacWindow() +{ + Q_Q(QWidget); + OSViewRef myView = qt_mac_nativeview_for(q); + OSWindowRef oldWindow = qt_mac_window_for(myView); +#ifndef QT_MAC_USE_COCOA + HIViewRemoveFromSuperview(myView); + determineWindowClass(); + createWindow_sys(); + + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + + if (IsWindowVisible(oldWindow)) + show_sys(); +#else + QMacCocoaAutoReleasePool pool; + [myView removeFromSuperview]; + determineWindowClass(); + createWindow_sys(); + if (NSToolbar *toolbar = [oldWindow toolbar]) { + OSWindowRef newWindow = qt_mac_window_for(myView); + [newWindow setToolbar:toolbar]; + [toolbar setVisible:[toolbar isVisible]]; + } + if ([oldWindow isVisible]){ + if ([oldWindow isSheet]) + [NSApp endSheet:oldWindow]; + [oldWindow orderOut:oldWindow]; + show_sys(); + } +#endif // QT_MAC_USE_COCOA + + // Release the window after creating the new window, because releasing it early + // may cause the app to quit ("close on last window closed attribute") + qt_mac_destructWindow(oldWindow); +} + +void QWidgetPrivate::createWindow_sys() +{ + Q_Q(QWidget); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + QTLWExtra *topExtra = topData(); + if (topExtra->embedded) + return; // Simply return because this view "is" the top window. + quint32 wattr = topExtra->wattr; + + if(parentWidget && (parentWidget->window()->windowFlags() & Qt::WindowStaysOnTopHint)) // If our parent has Qt::WStyle_StaysOnTop, so must we + flags |= Qt::WindowStaysOnTopHint; + + data.fstrut_dirty = true; + + OSWindowRef windowRef = qt_mac_create_window(q, topExtra->wclass, wattr, data.crect); + if (windowRef == 0) + qWarning("QWidget: Internal error: %s:%d: If you reach this error please contact Qt Support and include the\n" + " WidgetFlags used in creating the widget.", __FILE__, __LINE__); +#ifndef QT_MAC_USE_COCOA + finishCreateWindow_sys_Carbon(windowRef); +#else + finishCreateWindow_sys_Cocoa(windowRef); +#endif +} + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + + OSViewRef destroyid = 0; +#ifndef QT_MAC_USE_COCOA + window_event = 0; +#endif + + Qt::WindowType type = q->windowType(); + Qt::WindowFlags flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + + // Determine this early for top-levels so, we can use it later. + if (topLevel) + determineWindowClass(); + + if (desktop) { + QSize desktopSize = qt_mac_desktopSize(); + q->setAttribute(Qt::WA_WState_Visible); + data.crect.setRect(0, 0, desktopSize.width(), desktopSize.height()); + dialog = popup = false; // force these flags off + } else { + if (topLevel && (type != Qt::Drawer)) { + if (QDesktopWidget *dsk = QApplication::desktop()) { // calc pos/size from screen + const bool wasResized = q->testAttribute(Qt::WA_Resized); + const bool wasMoved = q->testAttribute(Qt::WA_Moved); + int deskn = dsk->primaryScreen(); + if (parentWidget && parentWidget->windowType() != Qt::Desktop) + deskn = dsk->screenNumber(parentWidget); + QRect screenGeo = dsk->screenGeometry(deskn); + if (!wasResized) { +#ifndef QT_MAC_USE_COCOA + data.crect.setSize(QSize(screenGeo.width()/2, 4*screenGeo.height()/10)); +#else + NSRect newRect = [NSWindow frameRectForContentRect:NSMakeRect(0, 0, + screenGeo.width() / 2., + 4 * screenGeo.height() / 10.) + styleMask:topData()->wattr]; + data.crect.setSize(QSize(newRect.size.width, newRect.size.height)); +#endif + // Constrain to minimums and maximums we've set + if (extra->minw > 0) + data.crect.setWidth(qMax(extra->minw, data.crect.width())); + if (extra->minh > 0) + data.crect.setHeight(qMax(extra->minh, data.crect.height())); + if (extra->maxw > 0) + data.crect.setWidth(qMin(extra->maxw, data.crect.width())); + if (extra->maxh > 0) + data.crect.setHeight(qMin(extra->maxh, data.crect.height())); + } + if (!wasMoved && !q->testAttribute(Qt::WA_DontShowOnScreen)) + data.crect.moveTopLeft(QPoint(screenGeo.width()/4, + 3 * screenGeo.height() / 10)); + } + } + } + + + if(!window) // always initialize + initializeWindow=true; + + hd = 0; + if(window) { // override the old window (with a new NSView) + OSViewRef nativeView = OSViewRef(window); + OSViewRef parent = 0; +#ifndef QT_MAC_USE_COCOA + CFRetain(nativeView); +#else + [nativeView retain]; +#endif + if (destroyOldWindow) + destroyid = qt_mac_nativeview_for(q); + bool transfer = false; + setWinId((WId)nativeView); +#ifndef QT_MAC_USE_COCOA +#ifndef HIViewInstallEventHandler + // Macro taken from the CarbonEvents Header on Tiger +#define HIViewInstallEventHandler( target, handler, numTypes, list, userData, outHandlerRef ) \ + InstallEventHandler( HIObjectGetEventTarget( (HIObjectRef) (target) ), (handler), (numTypes), (list), (userData), (outHandlerRef) ) +#endif + HIViewInstallEventHandler(nativeView, make_widget_eventUPP(), GetEventTypeCount(widget_events), widget_events, 0, 0); +#endif + if(topLevel) { + for(int i = 0; i < 2; ++i) { + if(i == 1) { + if(!initializeWindow) + break; + createWindow_sys(); + } + if(OSWindowRef windowref = qt_mac_window_for(nativeView)) { +#ifndef QT_MAC_USE_COCOA + CFRetain(windowref); +#else + [windowref retain]; +#endif + if (initializeWindow) { + parent = qt_mac_get_contentview_for(windowref); + } else { +#ifndef QT_MAC_USE_COCOA + parent = HIViewGetSuperview(nativeView); +#else + parent = [nativeView superview]; +#endif + } + break; + } + } + if(!parent) + transfer = true; + } else if (parentWidget) { + // I need to be added to my parent, therefore my parent needs an NSView + // Alien note: a 'window' was supplied as argument, meaning this widget + // is not alien. So therefore the parent cannot be alien either. + parentWidget->createWinId(); + parent = qt_mac_nativeview_for(parentWidget); + } + if(parent != nativeView && parent) { +#ifndef QT_MAC_USE_COCOA + HIViewAddSubview(parent, nativeView); +#else + [parent addSubview:nativeView]; +#endif + } + if(transfer) + transferChildren(); + data.fstrut_dirty = true; // we'll re calculate this later + q->setAttribute(Qt::WA_WState_Visible, +#ifndef QT_MAC_USE_COCOA + HIViewIsVisible(nativeView) +#else + ![nativeView isHidden] +#endif + ); + if(initializeWindow) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + HIViewSetFrame(nativeView, &bounds); + q->setAttribute(Qt::WA_WState_Visible, HIViewIsVisible(nativeView)); +#else + NSRect bounds = NSMakeRect(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + [nativeView setFrame:bounds]; + q->setAttribute(Qt::WA_WState_Visible, [nativeView isHidden]); +#endif + } +#ifndef QT_MAC_USE_COCOA + initWindowPtr(); +#endif + } else if (desktop) { // desktop widget + if (!qt_root_win) + QWidgetPrivate::qt_create_root_win(); + Q_ASSERT(qt_root_win); + WId rootWinID = 0; +#ifndef QT_MAC_USE_COCOA + CFRetain(qt_root_win); + if(HIViewRef rootContentView = HIViewGetRoot(qt_root_win)) { + rootWinID = (WId)rootContentView; + CFRetain(rootContentView); + } +#else + [qt_root_win retain]; + if (OSViewRef rootContentView = [qt_root_win contentView]) { + rootWinID = (WId)rootContentView; + [rootContentView retain]; + } +#endif + setWinId(rootWinID); + } else if (topLevel) { + determineWindowClass(); + if(OSViewRef osview = qt_mac_create_widget(q, this, 0)) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), + data.crect.width(), data.crect.height()); + HIViewSetFrame(osview, &bounds); +#else + NSRect bounds = NSMakeRect(data.crect.x(), flipYCoordinate(data.crect.y()), + data.crect.width(), data.crect.height()); + [osview setFrame:bounds]; +#endif + setWinId((WId)osview); + } + } else { + data.fstrut_dirty = false; // non-toplevel widgets don't have a frame, so no need to update the strut + +#ifdef QT_MAC_USE_COCOA + if (q->testAttribute(Qt::WA_NativeWindow) == false || q->internalWinId() != 0) { + // INVARIANT: q is Alien, and we should not create an NSView to back it up. + } else +#endif + if (OSViewRef osview = qt_mac_create_widget(q, this, qt_mac_nativeview_for(parentWidget))) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + HIViewSetFrame(osview, &bounds); + setWinId((WId)osview); +#else + NSRect bounds = NSMakeRect(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + [osview setFrame:bounds]; + setWinId((WId)osview); + if (q->isVisible()) { + // If q were Alien before, but now became native (e.g. if a call to + // winId was done from somewhere), we need to show the view immidiatly: + QMacCocoaAutoReleasePool pool; + [osview setHidden:NO]; + } +#endif + } + } + + updateIsOpaque(); + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + if (q->hasFocus()) + setFocus_sys(); + if (!topLevel && initializeWindow) + setWSGeometry(); + if (destroyid) + qt_mac_destructView(destroyid); +} + +/*! + Returns the QuickDraw handle of the widget. Use of this function is not + portable. This function will return 0 if QuickDraw is not supported, or + if the handle could not be created. + + \warning This function is only available on Mac OS X. +*/ + +Qt::HANDLE +QWidget::macQDHandle() const +{ +#ifndef QT_MAC_USE_COCOA + return d_func()->qd_hd; +#else + return 0; +#endif +} + +/*! + Returns the CoreGraphics handle of the widget. Use of this function is + not portable. This function will return 0 if no painter context can be + established, or if the handle could not be created. + + \warning This function is only available on Mac OS X. +*/ +Qt::HANDLE +QWidget::macCGHandle() const +{ + return handle(); +} + +void qt_mac_repaintParentUnderAlienWidget(QWidget *alienWidget) +{ + QWidget *nativeParent = alienWidget->nativeParentWidget(); + if (!nativeParent) + return; + + QPoint globalPos = alienWidget->mapToGlobal(QPoint(0, 0)); + QRect dirtyRect = QRect(nativeParent->mapFromGlobal(globalPos), alienWidget->size()); + nativeParent->repaint(dirtyRect); +} + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + QMacCocoaAutoReleasePool pool; + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(d->effectiveRectFor(geometry())); + if (!internalWinId()) + qt_mac_repaintParentUnderAlienWidget(this); + d->deactivateWidgetCleanup(); + qt_mac_event_release(this); + if(testAttribute(Qt::WA_WState_Created)) { + setAttribute(Qt::WA_WState_Created, false); + QObjectList chldrn = children(); + for(int i = 0; i < chldrn.size(); i++) { // destroy all widget children + QObject *obj = chldrn.at(i); + if(obj->isWidgetType()) + static_cast<QWidget*>(obj)->destroy(destroySubWindows, destroySubWindows); + } + if(mac_mouse_grabber == this) + releaseMouse(); + if(mac_keyboard_grabber == this) + releaseKeyboard(); + + if(testAttribute(Qt::WA_ShowModal)) // just be sure we leave modal + QApplicationPrivate::leaveModal(this); + else if((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); + if (destroyWindow) { + if(OSViewRef hiview = qt_mac_nativeview_for(this)) { + OSWindowRef window = 0; + NSDrawer *drawer = nil; +#ifdef QT_MAC_USE_COCOA + if (qt_mac_is_macdrawer(this)) { + drawer = qt_mac_drawer_for(this); + } else +#endif + if (isWindow()) + window = qt_mac_window_for(hiview); + + // Because of how "destruct" works, we have to do just a normal release for the root_win. + if (window && window == qt_root_win) { +#ifndef QT_MAC_USE_COCOA + CFRelease(hiview); +#else + [hiview release]; +#endif + } else { + qt_mac_destructView(hiview); + } + if (drawer) + qt_mac_destructDrawer(drawer); + if (window) + qt_mac_destructWindow(window); + } + } + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + } +} + +void QWidgetPrivate::transferChildren() +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; // Can't add any views anyway + + QObjectList chlist = q->children(); + for (int i = 0; i < chlist.size(); ++i) { + QObject *obj = chlist.at(i); + if (obj->isWidgetType()) { + QWidget *w = (QWidget *)obj; + if (!w->isWindow()) { + // This seems weird, no need to call it in a loop right? + if (!topData()->caption.isEmpty()) + setWindowTitle_helper(extra->topextra->caption); + if (w->internalWinId()) { +#ifndef QT_MAC_USE_COCOA + HIViewAddSubview(qt_mac_nativeview_for(q), qt_mac_nativeview_for(w)); +#else + // New NSWindows get an extra reference when drops are + // registered (at least in 10.5) which means that we may + // access the window later and get a crash (becasue our + // widget is dead). Work around this be having the drop + // site disabled until it is part of the new hierarchy. + bool oldRegistered = w->testAttribute(Qt::WA_DropSiteRegistered); + w->setAttribute(Qt::WA_DropSiteRegistered, false); + [qt_mac_nativeview_for(w) retain]; + [qt_mac_nativeview_for(w) removeFromSuperview]; + [qt_mac_nativeview_for(q) addSubview:qt_mac_nativeview_for(w)]; + [qt_mac_nativeview_for(w) release]; + w->setAttribute(Qt::WA_DropSiteRegistered, oldRegistered); +#endif + } + } + } + } +} + +#ifdef QT_MAC_USE_COCOA +void QWidgetPrivate::setSubWindowStacking(bool set) +{ + // After hitting too many unforeseen bugs trying to put Qt on top of the cocoa child + // window API, we have decided to revert this behaviour as much as we can. We + // therefore now only allow child windows to exist for children of modal dialogs. + static bool use_behaviour_qt473 = !qgetenv("QT_MAC_USE_CHILDWINDOWS").isEmpty(); + + // This will set/remove a visual relationship between parent and child on screen. + // The reason for doing this is to ensure that a child always stacks infront of + // its parent. Unfortunatly is turns out that [NSWindow addChildWindow] has + // several unwanted side-effects, one of them being the moving of a child when + // moving the parent, which we choose to accept. A way tougher side-effect is + // that Cocoa will hide the parent if you hide the child. And in the case of + // a tool window, since it will normally hide when you deactivate the + // application, Cocoa will hide the parent upon deactivate as well. The result often + // being no more visible windows on screen. So, to make a long story short, we only + // allow parent-child relationships between windows that both are either a plain window + // or a dialog. + + Q_Q(QWidget); + if (!q->isWindow()) + return; + NSWindow *qwin = [qt_mac_nativeview_for(q) window]; + if (!qwin) + return; + Qt::WindowType qtype = q->windowType(); + if (set && !(qtype == Qt::Window || qtype == Qt::Dialog)) + return; + if (set && ![qwin isVisible]) + return; + + if (QWidget *parent = q->parentWidget()) { + if (NSWindow *pwin = [qt_mac_nativeview_for(parent) window]) { + if (set) { + Qt::WindowType ptype = parent->window()->windowType(); + if ([pwin isVisible] + && (ptype == Qt::Window || ptype == Qt::Dialog) + && ![qwin parentWindow] + && (use_behaviour_qt473 || parent->windowModality() == Qt::ApplicationModal)) { + NSInteger level = [qwin level]; + [pwin addChildWindow:qwin ordered:NSWindowAbove]; + if ([qwin level] < level) + [qwin setLevel:level]; + } + } else { + [pwin removeChildWindow:qwin]; + } + } + } + + // Only set-up child windows for q if q is modal: + if (set && !use_behaviour_qt473 && q->windowModality() != Qt::ApplicationModal) + return; + + QObjectList widgets = q->children(); + for (int i=0; i<widgets.size(); ++i) { + QWidget *child = qobject_cast<QWidget *>(widgets.at(i)); + if (child && child->isWindow()) { + if (NSWindow *cwin = [qt_mac_nativeview_for(child) window]) { + if (set) { + Qt::WindowType ctype = child->window()->windowType(); + if ([cwin isVisible] && (ctype == Qt::Window || ctype == Qt::Dialog) && ![cwin parentWindow]) { + NSInteger level = [cwin level]; + [qwin addChildWindow:cwin ordered:NSWindowAbove]; + if ([cwin level] < level) + [cwin setLevel:level]; + } + } else { + [qwin removeChildWindow:qt_mac_window_for(child)]; + } + } + } + } +} +#endif + +void QWidgetPrivate::setParent_sys(QWidget *parent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + QTLWExtra *topData = maybeTopData(); + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); +#ifdef QT_MAC_USE_COCOA + bool wasWindow = q->isWindow(); +#endif + OSViewRef old_id = 0; + + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + + // Maintain the glWidgets list on parent change: remove "our" gl widgets + // from the list on the old parent and grandparents. + if (glWidgets.isEmpty() == false) { + QWidget *current = q->parentWidget(); + while (current) { + for (QList<QWidgetPrivate::GlWidgetInfo>::const_iterator it = glWidgets.constBegin(); + it != glWidgets.constEnd(); ++it) + current->d_func()->glWidgets.removeAll(*it); + + if (current->isWindow()) + break; + current = current->parentWidget(); + } + } + +#ifndef QT_MAC_USE_COCOA + EventHandlerRef old_window_event = 0; +#else + bool oldToolbarVisible = false; + NSDrawer *oldDrawer = nil; + NSToolbar *oldToolbar = 0; +#endif + if (wasCreated && !(q->windowType() == Qt::Desktop)) { + old_id = qt_mac_nativeview_for(q); +#ifndef QT_MAC_USE_COCOA + old_window_event = window_event; +#else + if (qt_mac_is_macdrawer(q)) { + oldDrawer = qt_mac_drawer_for(q); + } + if (wasWindow) { + OSWindowRef oldWindow = qt_mac_window_for(old_id); + oldToolbar = [oldWindow toolbar]; + if (oldToolbar) { + [oldToolbar retain]; + oldToolbarVisible = [oldToolbar isVisible]; + [oldWindow setToolbar:nil]; + } + } +#endif + } + QWidget* oldtlw = q->window(); + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + q->setAttribute(Qt::WA_DropSiteRegistered, false); + + //recreate and setup flags + QObjectPrivate::setParent_helper(parent); + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + if (wasCreated && !qt_isGenuineQWidget(q)) + return; + + if (!q->testAttribute(Qt::WA_WState_WindowOpacitySet)) { + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + setWinId(0); //do after the above because they may want the id + + data.window_flags = f; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state. + // (but we recreate the winId for the widget being reparented, again for compatibility, + // unless this is an alien widget. ) + const bool nonWindowWithCreatedParent = !q->isWindow() && parent->testAttribute(Qt::WA_WState_Created); + const bool nativeWidget = q->internalWinId() != 0; + if (wasCreated || (nativeWidget && nonWindowWithCreatedParent)) { + createWinId(); + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + // We do this down below for wasCreated, so avoid doing this twice + // (only for performance, it gets called a lot anyway). + if (!wasCreated) { + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + } +#else + // Simply transfer our toolbar over. Everything should stay put, unlike in Carbon. + if (oldToolbar && !(f & Qt::FramelessWindowHint)) { + OSWindowRef newWindow = qt_mac_window_for(q); + [newWindow setToolbar:oldToolbar]; + [oldToolbar release]; + [oldToolbar setVisible:oldToolbarVisible]; + } +#endif + } + } + if (q->isWindow() || (!parent || parent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (wasCreated) { + transferChildren(); +#ifndef QT_MAC_USE_COCOA + // If we were a unified window, We just transfered our toolbars out of the unified toolbar. + // So redo the status one more time. It apparently is not an issue with Cocoa. + if (q->isWindow()) { + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + } +#endif + + if (topData && + (!topData->caption.isEmpty() || !topData->filePath.isEmpty())) + setWindowTitle_helper(q->windowTitle()); + } + + if (q->testAttribute(Qt::WA_AcceptDrops) + || (!q->isWindow() && q->parentWidget() + && q->parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) + q->setAttribute(Qt::WA_DropSiteRegistered, true); + + //cleanup +#ifndef QT_MAC_USE_COCOA + if (old_window_event) + RemoveEventHandler(old_window_event); +#endif + if (old_id) { //don't need old window anymore + OSWindowRef window = (oldtlw == q) ? qt_mac_window_for(old_id) : 0; + qt_mac_destructView(old_id); + +#ifdef QT_MAC_USE_COCOA + if (oldDrawer) { + qt_mac_destructDrawer(oldDrawer); + } else +#endif + if (window) + qt_mac_destructWindow(window); + } + + // Maintain the glWidgets list on parent change: add "our" gl widgets + // to the list on the new parent and grandparents. + if (glWidgets.isEmpty() == false) { + QWidget *current = q->parentWidget(); + while (current) { + current->d_func()->glWidgets += glWidgets; + if (current->isWindow()) + break; + current = current->parentWidget(); + } + } + invalidateBuffer(q->rect()); + qt_event_request_window_change(q); +} + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!internalWinId()) { + QPoint p = pos + data->crect.topLeft(); + return isWindow() ? p : parentWidget()->mapToGlobal(p); + } +#ifndef QT_MAC_USE_COCOA + QPoint tmp = d->mapToWS(pos); + HIPoint hi_pos = CGPointMake(tmp.x(), tmp.y()); + HIViewConvertPoint(&hi_pos, qt_mac_nativeview_for(this), 0); + Rect win_rect; + GetWindowBounds(qt_mac_window_for(this), kWindowStructureRgn, &win_rect); + return QPoint((int)hi_pos.x+win_rect.left, (int)hi_pos.y+win_rect.top); +#else + QPoint tmp = d->mapToWS(pos); + NSPoint hi_pos = NSMakePoint(tmp.x(), tmp.y()); + hi_pos = [qt_mac_nativeview_for(this) convertPoint:hi_pos toView:nil]; + NSRect win_rect = [qt_mac_window_for(this) frame]; + hi_pos.x += win_rect.origin.x; + hi_pos.y += win_rect.origin.y; + // If we aren't the desktop we need to flip, if you flip the desktop on itself, you get the other problem. + return ((window()->windowFlags() & Qt::Desktop) == Qt::Desktop) ? QPointF(hi_pos.x, hi_pos.y).toPoint() + : flipPoint(hi_pos).toPoint(); +#endif +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!internalWinId()) { + QPoint p = isWindow() ? pos : parentWidget()->mapFromGlobal(pos); + return p - data->crect.topLeft(); + } +#ifndef QT_MAC_USE_COCOA + Rect win_rect; + GetWindowBounds(qt_mac_window_for(this), kWindowStructureRgn, &win_rect); + HIPoint hi_pos = CGPointMake(pos.x()-win_rect.left, pos.y()-win_rect.top); + HIViewConvertPoint(&hi_pos, 0, qt_mac_nativeview_for(this)); + return d->mapFromWS(QPoint((int)hi_pos.x, (int)hi_pos.y)); +#else + NSRect win_rect = [qt_mac_window_for(this) frame]; + // The Window point is in "Cocoa coordinates," but the view is in "Qt coordinates" + // so make sure to keep them in sync. + NSPoint hi_pos = NSMakePoint(pos.x()-win_rect.origin.x, + flipYCoordinate(pos.y())-win_rect.origin.y); + hi_pos = [qt_mac_nativeview_for(this) convertPoint:hi_pos fromView:0]; + return d->mapFromWS(QPoint(qRound(hi_pos.x), qRound(hi_pos.y))); +#endif +} + +void QWidgetPrivate::updateSystemBackground() +{ +} + +void QWidgetPrivate::setCursor_sys(const QCursor &) +{ + qt_mac_update_cursor(); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + qt_mac_update_cursor(); +} + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + SetWindowTitleWithCFString(qt_mac_window_for(q), QCFString(caption)); +#else + QMacCocoaAutoReleasePool pool; + [qt_mac_window_for(q) setTitle:qt_mac_QStringToNSString(caption)]; +#endif + } +} + +void QWidgetPrivate::setWindowModified_sys(bool mod) +{ + Q_Q(QWidget); + if (q->isWindow() && q->testAttribute(Qt::WA_WState_Created)) { +#ifndef QT_MAC_USE_COCOA + SetWindowModified(qt_mac_window_for(q), mod); +#else + [qt_mac_window_for(q) setDocumentEdited:mod]; +#endif + } +} + +void QWidgetPrivate::setWindowFilePath_sys(const QString &filePath) +{ + Q_Q(QWidget); +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + QFileInfo fi(filePath); + [qt_mac_window_for(q) setRepresentedFilename:fi.exists() ? qt_mac_QStringToNSString(filePath) : @""]; +#else + bool validRef = false; + FSRef ref; + bzero(&ref, sizeof(ref)); + OSStatus status; + + if (!filePath.isEmpty()) { + status = FSPathMakeRef(reinterpret_cast<const UInt8 *>(filePath.toUtf8().constData()), &ref, 0); + validRef = (status == noErr); + } + // Set the proxy regardless, since this is our way of clearing it as well, but ignore the + // return value as well. + if (validRef) { + status = HIWindowSetProxyFSRef(qt_mac_window_for(q), &ref); + } else { + status = RemoveWindowProxy(qt_mac_window_for(q)); + } + if (status != noErr) + qWarning("QWidget::setWindowFilePath: Error setting proxyicon for path (%s):%ld", + qPrintable(filePath), status); +#endif +} + +void QWidgetPrivate::setWindowIcon_sys(bool forceReset) +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + QTLWExtra *topData = this->topData(); + if (topData->iconPixmap && !forceReset) // already set + return; + + QIcon icon = q->windowIcon(); + QPixmap *pm = 0; + if (!icon.isNull()) { + // now create the extra + if (!topData->iconPixmap) { + pm = new QPixmap(icon.pixmap(QSize(22, 22))); + topData->iconPixmap = pm; + } else { + pm = topData->iconPixmap; + } + } + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + IconRef previousIcon = 0; + if (icon.isNull()) { + RemoveWindowProxy(qt_mac_window_for(q)); + previousIcon = topData->windowIcon; + topData->windowIcon = 0; + } else { + WindowClass wclass; + GetWindowClass(qt_mac_window_for(q), &wclass); + + if (wclass == kDocumentWindowClass) { + IconRef newIcon = qt_mac_create_iconref(*pm); + previousIcon = topData->windowIcon; + topData->windowIcon = newIcon; + SetWindowProxyIcon(qt_mac_window_for(q), newIcon); + } + } + + // Release the previous icon if it was set by this function. + if (previousIcon != 0) + ReleaseIconRef(previousIcon); +#else + QMacCocoaAutoReleasePool pool; + if (icon.isNull()) + return; + NSButton *iconButton = [qt_mac_window_for(q) standardWindowButton:NSWindowDocumentIconButton]; + if (iconButton == nil) { + QCFString string(q->windowTitle()); + const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); + [qt_mac_window_for(q) setRepresentedURL:[NSURL fileURLWithPath:const_cast<NSString *>(tmpString)]]; + iconButton = [qt_mac_window_for(q) standardWindowButton:NSWindowDocumentIconButton]; + } + if (icon.isNull()) { + [iconButton setImage:nil]; + } else { + QPixmap scaled = pm->scaled(QSize(16,16), Qt::KeepAspectRatio, Qt::SmoothTransformation); + NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(scaled)); + [iconButton setImage:image]; + [image release]; + } +#endif + } +} + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_Q(QWidget); + if(q->isWindow() && !iconText.isEmpty()) { +#ifndef QT_MAC_USE_COCOA + SetWindowAlternateTitle(qt_mac_window_for(q), QCFString(iconText)); +#else + QMacCocoaAutoReleasePool pool; + [qt_mac_window_for(q) setMiniwindowTitle:qt_mac_QStringToNSString(iconText)]; +#endif + } +} + +void QWidget::grabMouse() +{ + if(isVisible() && !qt_nograb()) { + if(mac_mouse_grabber) + mac_mouse_grabber->releaseMouse(); + mac_mouse_grabber=this; + qt_mac_setMouseGrabCursor(true); + } +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if(isVisible() && !qt_nograb()) { + if(mac_mouse_grabber) + mac_mouse_grabber->releaseMouse(); + mac_mouse_grabber=this; + qt_mac_setMouseGrabCursor(true, const_cast<QCursor *>(&cursor)); + } +} +#endif + +void QWidget::releaseMouse() +{ + if(!qt_nograb() && mac_mouse_grabber == this) { + mac_mouse_grabber = 0; + qt_mac_setMouseGrabCursor(false); + } +} + +void QWidget::grabKeyboard() +{ + if(!qt_nograb()) { + if(mac_keyboard_grabber) + mac_keyboard_grabber->releaseKeyboard(); + mac_keyboard_grabber = this; + } +} + +void QWidget::releaseKeyboard() +{ + if(!qt_nograb() && mac_keyboard_grabber == this) + mac_keyboard_grabber = 0; +} + +QWidget *QWidget::mouseGrabber() +{ + return mac_mouse_grabber; +} + +QWidget *QWidget::keyboardGrabber() +{ + return mac_keyboard_grabber; +} + +void QWidget::activateWindow() +{ + QWidget *tlw = window(); + if(!tlw->isVisible() || !tlw->isWindow() || (tlw->windowType() == Qt::Desktop)) + return; + qt_event_remove_activate(); + + QWidget *fullScreenWidget = tlw; + QWidget *parentW = tlw; + // Find the oldest parent or the parent with fullscreen, whichever comes first. + while (parentW) { + fullScreenWidget = parentW->window(); + if (fullScreenWidget->windowState() & Qt::WindowFullScreen) + break; + parentW = fullScreenWidget->parentWidget(); + } + + if (fullScreenWidget->windowType() != Qt::ToolTip) { + qt_mac_set_fullscreen_mode((fullScreenWidget->windowState() & Qt::WindowFullScreen) && + qApp->desktop()->screenNumber(this) == 0); + } + + bool windowActive; + OSWindowRef win = qt_mac_window_for(tlw); +#ifndef QT_MAC_USE_COCOA + windowActive = IsWindowActive(win); +#else + QMacCocoaAutoReleasePool pool; + windowActive = [win isKeyWindow]; +#endif + if ((tlw->windowType() == Qt::Popup) + || (tlw->windowType() == Qt::Tool) + || qt_mac_is_macdrawer(tlw) + || windowActive) { +#ifndef QT_MAC_USE_COCOA + ActivateWindow(win, true); + qApp->setActiveWindow(tlw); +#else + [win makeKeyWindow]; +#endif + } else if(!isMinimized()) { +#ifndef QT_MAC_USE_COCOA + SelectWindow(win); +#else + [win makeKeyAndOrderFront:win]; +#endif + } +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + return new QMacWindowSurface(q_func()); +} + +void QWidgetPrivate::update_sys(const QRect &r) +{ + Q_Q(QWidget); + if (updateRedirectedToGraphicsProxyWidget(q, r)) + return; + dirtyOnWidget += r; + macSetNeedsDisplay(r != q->rect() ? r : QRegion()); +} + +void QWidgetPrivate::update_sys(const QRegion &rgn) +{ + Q_Q(QWidget); + if (updateRedirectedToGraphicsProxyWidget(q, rgn)) + return; + dirtyOnWidget += rgn; + macSetNeedsDisplay(rgn); +} + +bool QWidgetPrivate::isRealWindow() const +{ + return q_func()->isWindow() && !topData()->embedded; +} + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + if ((q->windowType() == Qt::Desktop)) //desktop is always visible + return; + + invalidateBuffer(q->rect()); + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + QMacCocoaAutoReleasePool pool; + q->setAttribute(Qt::WA_Mapped); + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return; + + bool realWindow = isRealWindow(); +#ifndef QT_MAC_USE_COCOA + if (realWindow && !q->testAttribute(Qt::WA_Moved)) { + if (qt_mac_is_macsheet(q)) + recreateMacWindow(); + q->createWinId(); + if (QWidget *p = q->parentWidget()) { + p->createWinId(); + RepositionWindow(qt_mac_window_for(q), qt_mac_window_for(p), kWindowCenterOnParentWindow); + } else { + RepositionWindow(qt_mac_window_for(q), 0, kWindowCenterOnMainScreen); + } + } +#endif + + data.fstrut_dirty = true; + if (realWindow) { + bool isCurrentlyMinimized = (q->windowState() & Qt::WindowMinimized); + setModal_sys(); + OSWindowRef window = qt_mac_window_for(q); +#ifndef QT_MAC_USE_COCOA + SizeWindow(window, q->width(), q->height(), true); +#endif + +#ifdef QT_MAC_USE_COCOA + // Make sure that we end up sending a repaint event to + // the widget if the window has been visible one before: + [qt_mac_get_contentview_for(window) setNeedsDisplay:YES]; +#endif + if(qt_mac_is_macsheet(q)) { + qt_event_request_showsheet(q); + } else if(qt_mac_is_macdrawer(q)) { +#ifndef QT_MAC_USE_COCOA + OpenDrawer(window, kWindowEdgeDefault, false); +#else + NSDrawer *drawer = qt_mac_drawer_for(q); + [drawer openOnEdge:[drawer preferredEdge]]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + ShowHide(window, true); +#else + // sync the opacity value back (in case of a fade). + [window setAlphaValue:q->windowOpacity()]; + + QWidget *top = 0; + if (QApplicationPrivate::tryModalHelper(q, &top)) { + [window makeKeyAndOrderFront:window]; + // If this window is app modal, we need to start spinning + // a modal session for it. Interrupting + // the event dispatcher will make this happend: + if (data.window_modality == Qt::ApplicationModal) + QEventDispatcherMac::instance()->interrupt(); + } else { + // The window is modally shaddowed, so we need to make + // sure that we don't pop in front of the modal window: + [window orderFront:window]; + if (!top->testAttribute(Qt::WA_DontShowOnScreen)) { + if (NSWindow *modalWin = qt_mac_window_for(top)) + [modalWin orderFront:window]; + } + } + setSubWindowStacking(true); + qt_mac_update_cursor(); +#endif + if (q->windowType() == Qt::Popup) { + qt_button_down = 0; + if (q->focusWidget()) + q->focusWidget()->d_func()->setFocus_sys(); + else + setFocus_sys(); + } + toggleDrawers(true); + } + if (isCurrentlyMinimized) { //show in collapsed state +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, true); +#else + [window miniaturize:window]; +#endif + } else if (!q->testAttribute(Qt::WA_ShowWithoutActivating)) { +#ifndef QT_MAC_USE_COCOA + qt_event_request_activate(q); +#endif + } + } else if(topData()->embedded || !q->parentWidget() || q->parentWidget()->isVisible()) { +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + if (NSView *view = qt_mac_nativeview_for(q)) { + // INVARIANT: q is native. Just show the view: + [view setHidden:NO]; + } else { + // INVARIANT: q is alien. Repaint q instead: + q->repaint(); + } +#endif + } + +#ifdef QT_MAC_USE_COCOA + if ([NSApp isActive] && !qt_button_down && !QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, qt_last_mouse_receiver); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +#endif + + topLevelAt_cache = 0; + qt_event_request_window_change(q); +} + +QPoint qt_mac_nativeMapFromParent(const QWidget *child, const QPoint &pt) +{ +#ifndef QT_MAC_USE_COCOA + CGPoint nativePoint = CGPointMake(pt.x(), pt.y()); + HIViewConvertPoint(&nativePoint, qt_mac_nativeview_for(child->parentWidget()), + qt_mac_nativeview_for(child)); +#else + NSPoint nativePoint = [qt_mac_nativeview_for(child) convertPoint:NSMakePoint(pt.x(), pt.y()) fromView:qt_mac_nativeview_for(child->parentWidget())]; +#endif + return QPoint(nativePoint.x, nativePoint.y); +} + + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) //you can't hide the desktop! + return; + QMacCocoaAutoReleasePool pool; + if(q->isWindow()) { +#ifdef QT_MAC_USE_COCOA + setSubWindowStacking(false); +#endif + OSWindowRef window = qt_mac_window_for(q); + if(qt_mac_is_macsheet(q)) { +#ifndef QT_MAC_USE_COCOA + WindowRef parent = 0; + if(GetSheetWindowParent(window, &parent) != noErr || !parent) + ShowHide(window, false); + else + HideSheetWindow(window); +#else + [NSApp endSheet:window]; + [window orderOut:window]; +#endif + } else if(qt_mac_is_macdrawer(q)) { +#ifndef QT_MAC_USE_COCOA + CloseDrawer(window, false); +#else + [qt_mac_drawer_for(q) close]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + ShowHide(window, false); +#else + [window orderOut:window]; + // Unfortunately it is not as easy as just hiding the window, we need + // to find out if we were in full screen mode. If we were and this is + // the last window in full screen mode then we need to unset the full screen + // mode. If this is not the last visible window in full screen mode then we + // don't change the full screen mode. + if(q->isFullScreen()) + { + bool keepFullScreen = false; + QWidgetList windowList = qApp->topLevelWidgets(); + int windowCount = windowList.count(); + for(int i = 0; i < windowCount; i++) + { + QWidget *w = windowList[i]; + // If it is the same window, we don't need to check :-) + if(q == w) + continue; + // If they are not visible or if they are minimized then + // we just ignore them. + if(!w->isVisible() || w->isMinimized()) + continue; + // Is it full screen? + // Notice that if there is one window in full screen mode then we + // cannot switch the full screen mode off, therefore we just abort. + if(w->isFullScreen()) { + keepFullScreen = true; + break; + } + } + // No windows in full screen mode, so let just unset that flag. + if(!keepFullScreen) + qt_mac_set_fullscreen_mode(false); + } +#endif + toggleDrawers(false); + qt_mac_update_cursor(); +#ifndef QT_MAC_USE_COCOA + // Clear modality (because it seems something that we've always done). + if (data.window_modality != Qt::NonModal) { + SetWindowModality(window, kWindowModalityNone, + q->parentWidget() ? qt_mac_window_for(q->parentWidget()->window()) : 0); + } +#endif + } +#ifndef QT_MAC_USE_COCOA + // If the window we now hide was the active window, we need + // to find, and activate another window on screen. NB: Cocoa takes care of this + // logic for us (and distinquishes between main windows and key windows) + if (q->isActiveWindow() && !(q->windowType() == Qt::Popup)) { + QWidget *w = 0; + if(q->parentWidget()) + w = q->parentWidget()->window(); + if(!w || (!w->isVisible() && !w->isMinimized())) { + for (WindowPtr wp = GetFrontWindowOfClass(kMovableModalWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kMovableModalWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + if (!w){ + for (WindowPtr wp = GetFrontWindowOfClass(kDocumentWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kDocumentWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + } + if (!w){ + for(WindowPtr wp = GetFrontWindowOfClass(kSimpleWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kSimpleWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + } + } + if(w && w->isVisible() && !w->isMinimized()) { + qt_event_request_activate(w); + } + } +#endif + } else { + invalidateBuffer(q->rect()); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), false); +#else + if (NSView *view = qt_mac_nativeview_for(q)) { + // INVARIANT: q is native. Just hide the view: + [view setHidden:YES]; + } else { + // INVARIANT: q is alien. Repaint where q is placed instead: + qt_mac_repaintParentUnderAlienWidget(q); + } +#endif + } + +#ifdef QT_MAC_USE_COCOA + if ([NSApp isActive] && !qt_button_down && !QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Leave, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, qt_last_native_mouse_receiver); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +#endif + + topLevelAt_cache = 0; + qt_event_request_window_change(q); + deactivateWidgetCleanup(); + qt_mac_event_release(q); +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + bool needShow = false; + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#endif + bool needSendStateChange = true; + if(isWindow()) { + if((oldstate & Qt::WindowFullScreen) != (newstate & Qt::WindowFullScreen)) { + if(newstate & Qt::WindowFullScreen) { + if(QTLWExtra *tlextra = d->topData()) { + if(tlextra->normalGeometry.width() < 0) { + if(!testAttribute(Qt::WA_Resized)) + adjustSize(); + tlextra->normalGeometry = geometry(); + } + tlextra->savedFlags = windowFlags(); + } + needShow = isVisible(); + const QRect fullscreen(qApp->desktop()->screenGeometry(qApp->desktop()->screenNumber(this))); + setParent(parentWidget(), Qt::Window | Qt::FramelessWindowHint | (windowFlags() & 0xffff0000)); //save + setGeometry(fullscreen); + if(!qApp->desktop()->screenNumber(this)) + qt_mac_set_fullscreen_mode(true); + } else { + needShow = isVisible(); + if(!qApp->desktop()->screenNumber(this)) + qt_mac_set_fullscreen_mode(false); + setParent(parentWidget(), d->topData()->savedFlags); + setGeometry(d->topData()->normalGeometry); + d->topData()->normalGeometry.setRect(0, 0, -1, -1); + } + } + + d->createWinId(); + + OSWindowRef window = qt_mac_window_for(this); + if((oldstate & Qt::WindowMinimized) != (newstate & Qt::WindowMinimized)) { + if (newstate & Qt::WindowMinimized) { +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, true); +#else + [window miniaturize:window]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, false); +#else + [window deminiaturize:window]; +#endif + } + needSendStateChange = oldstate == windowState(); // Collapse didn't change our flags. + } + + if((newstate & Qt::WindowMaximized) && !((newstate & Qt::WindowFullScreen))) { + if(QTLWExtra *tlextra = d->topData()) { + if(tlextra->normalGeometry.width() < 0) { + if(!testAttribute(Qt::WA_Resized)) + adjustSize(); + tlextra->normalGeometry = geometry(); + } + } + } else if(!(newstate & Qt::WindowFullScreen)) { +// d->topData()->normalGeometry = QRect(0, 0, -1, -1); + } + +#ifdef DEBUG_WINDOW_STATE +#define WSTATE(x) qDebug("%s -- %s --> %s", #x, (oldstate & x) ? "true" : "false", (newstate & x) ? "true" : "false") + WSTATE(Qt::WindowMinimized); + WSTATE(Qt::WindowMaximized); + WSTATE(Qt::WindowFullScreen); +#undef WSTATE +#endif + if(!(newstate & (Qt::WindowMinimized|Qt::WindowFullScreen)) && + ((oldstate & Qt::WindowFullScreen) || (oldstate & Qt::WindowMinimized) || + (oldstate & Qt::WindowMaximized) != (newstate & Qt::WindowMaximized))) { + if(newstate & Qt::WindowMaximized) { + data->fstrut_dirty = true; +#ifndef QT_MAC_USE_COCOA + HIToolbarRef toolbarRef; + if (GetWindowToolbar(window, &toolbarRef) == noErr && toolbarRef + && !isVisible() && !IsWindowToolbarVisible(window)) { + // HIToolbar, needs to be shown so that it's in the structure window + // Typically this is part of a main window and will get shown + // during the show, but it's will make the maximize all wrong. + ShowHideWindowToolbar(window, true, false); + d->updateFrameStrut(); // In theory the dirty would work, but it's optimized out if the window is not visible :( + } + Rect bounds; + QDesktopWidget *dsk = QApplication::desktop(); + QRect avail = dsk->availableGeometry(dsk->screenNumber(this)); + SetRect(&bounds, avail.x(), avail.y(), avail.x() + avail.width(), avail.y() + avail.height()); + if(QWExtra *extra = d->extraData()) { + if(bounds.right - bounds.left > extra->maxw) + bounds.right = bounds.left + extra->maxw; + if(bounds.bottom - bounds.top > extra->maxh) + bounds.bottom = bounds.top + extra->maxh; + } + if(d->topData()) { + QRect fs = d->frameStrut(); + bounds.left += fs.left(); + if(bounds.right < avail.x()+avail.width()) + bounds.right = qMin<short>((uint)avail.x()+avail.width(), bounds.right+fs.left()); + if(bounds.bottom < avail.y()+avail.height()) + bounds.bottom = qMin<short>((uint)avail.y()+avail.height(), bounds.bottom+fs.top()); + bounds.top += fs.top(); + bounds.right -= fs.right(); + bounds.bottom -= fs.bottom(); + } + QRect orect(geometry().x(), geometry().y(), width(), height()), + nrect(bounds.left, bounds.top, bounds.right - bounds.left, + bounds.bottom - bounds.top); + if(orect != nrect) { // the new rect differ from the old + Point idealSize = { nrect.height(), nrect.width() }; + ZoomWindowIdeal(window, inZoomOut, &idealSize); + } +#else + NSToolbar *toolbarRef = [window toolbar]; + if (toolbarRef && !isVisible() && ![toolbarRef isVisible]) { + // HIToolbar, needs to be shown so that it's in the structure window + // Typically this is part of a main window and will get shown + // during the show, but it's will make the maximize all wrong. + // ### Not sure this is right for NSToolbar... + [toolbarRef setVisible:true]; +// ShowHideWindowToolbar(window, true, false); + d->updateFrameStrut(); // In theory the dirty would work, but it's optimized out if the window is not visible :( + } + // Everything should be handled by Cocoa. + [window zoom:window]; +#endif + needSendStateChange = oldstate == windowState(); // Zoom didn't change flags. + } else if(oldstate & Qt::WindowMaximized && !(oldstate & Qt::WindowFullScreen)) { +#ifndef QT_MAC_USE_COCOA + Point idealSize; + ZoomWindowIdeal(window, inZoomIn, &idealSize); +#else + [window zoom:window]; +#endif + if(QTLWExtra *tlextra = d->topData()) { + setGeometry(tlextra->normalGeometry); + tlextra->normalGeometry.setRect(0, 0, -1, -1); + } + } + } + } + + data->window_state = newstate; + + if(needShow) + show(); + + if(newstate & Qt::WindowActive) + activateWindow(); + + qt_event_request_window_change(this); + if (needSendStateChange) { + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); + } +} + +void QWidgetPrivate::setFocus_sys() +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created)) { +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSView *view = qt_mac_nativeview_for(q); + [[view window] makeFirstResponder:view]; +#else + SetKeyboardFocus(qt_mac_window_for(q), qt_mac_nativeview_for(q), 1); +#endif + } +} + +NSComparisonResult compareViews2Raise(id view1, id view2, void *context) +{ + id topView = reinterpret_cast<id>(context); + if (view1 == topView) + return NSOrderedDescending; + if (view2 == topView) + return NSOrderedAscending; + return NSOrderedSame; +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) + return; + +#if QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + if (isRealWindow()) { + // With the introduction of spaces it is not as simple as just raising the window. + // First we need to check if we are in the right space. If we are, then we just continue + // as usual. The problem comes when we are not in the active space. There are two main cases: + // 1. Our parent was moved to a new space. In this case we want the window to be raised + // in the same space as its parent. + // 2. We don't have a parent. For this case we will just raise the window and let Cocoa + // switch to the corresponding space. + // NOTICE: There are a lot of corner cases here. We are keeping this simple for now, if + // required we will introduce special handling for some of them. + if (!q->testAttribute(Qt::WA_DontShowOnScreen) && q->isVisible()) { + OSWindowRef window = qt_mac_window_for(q); + // isOnActiveSpace is available only from 10.6 onwards, so we need to check if it is + // available before calling it. + if([window respondsToSelector:@selector(isOnActiveSpace)]) { + if(![window performSelector:@selector(isOnActiveSpace)]) { + QWidget *parentWidget = q->parentWidget(); + if(parentWidget) { + OSWindowRef parentWindow = qt_mac_window_for(parentWidget); + if(parentWindow && [parentWindow respondsToSelector:@selector(isOnActiveSpace)]) { + if ([parentWindow performSelector:@selector(isOnActiveSpace)]) { + // The window was created in a different space. Therefore if we want + // to show it in the current space we need to recreate it in the new + // space. + recreateMacWindow(); + window = qt_mac_window_for(q); + } + } + } + } + } + [window orderFront:window]; + } + if (qt_mac_raise_process) { //we get to be the active process now + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); + } + } else { + NSView *view = qt_mac_nativeview_for(q); + NSView *parentView = [view superview]; + [parentView sortSubviewsUsingFunction:compareViews2Raise context:reinterpret_cast<void *>(view)]; + } + topLevelAt_cache = 0; +#else + if(q->isWindow()) { + //raise this window + BringToFront(qt_mac_window_for(q)); + if(qt_mac_raise_process) { //we get to be the active process now + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); + } + } else if(q->parentWidget()) { + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderAbove, 0); + qt_event_request_window_change(q); + } +#endif +} + +NSComparisonResult compareViews2Lower(id view1, id view2, void *context) +{ + id topView = reinterpret_cast<id>(context); + if (view1 == topView) + return NSOrderedAscending; + if (view2 == topView) + return NSOrderedDescending; + return NSOrderedSame; +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) + return; +#ifdef QT_MAC_USE_COCOA + if (isRealWindow()) { + OSWindowRef window = qt_mac_window_for(q); + [window orderBack:window]; + } else { + NSView *view = qt_mac_nativeview_for(q); + NSView *parentView = [view superview]; + [parentView sortSubviewsUsingFunction:compareViews2Lower context:reinterpret_cast<void *>(view)]; + } + topLevelAt_cache = 0; +#else + if(q->isWindow()) { + SendBehind(qt_mac_window_for(q), 0); + } else if(q->parentWidget()) { + invalidateBuffer(q->rect()); + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderBelow, 0); + qt_event_request_window_change(q); + } +#endif +} + +NSComparisonResult compareViews2StackUnder(id view1, id view2, void *context) +{ + const QHash<NSView *, int> &viewOrder = *reinterpret_cast<QHash<NSView *, int> *>(context); + if (viewOrder[view1] < viewOrder[view2]) + return NSOrderedAscending; + if (viewOrder[view1] > viewOrder[view2]) + return NSOrderedDescending; + return NSOrderedSame; +} + +void QWidgetPrivate::stackUnder_sys(QWidget *w) +{ + // stackUnder + Q_Q(QWidget); + if(!w || q->isWindow() || (q->windowType() == Qt::Desktop)) + return; +#ifdef QT_MAC_USE_COCOA + // Do the same trick as lower_sys() and put this widget before the widget passed in. + NSView *myView = qt_mac_nativeview_for(q); + NSView *wView = qt_mac_nativeview_for(w); + + QHash<NSView *, int> viewOrder; + NSView *parentView = [myView superview]; + NSArray *subviews = [parentView subviews]; + NSUInteger index = 1; + // make a hash of view->zorderindex and make sure z-value is always odd, + // so that when we modify the order we create a new (even) z-value which + // will not interfere with others. + for (NSView *subview in subviews) { + viewOrder.insert(subview, index * 2); + ++index; + } + viewOrder[myView] = viewOrder[wView] - 1; + + [parentView sortSubviewsUsingFunction:compareViews2StackUnder context:reinterpret_cast<void *>(&viewOrder)]; +#else + QWidget *p = q->parentWidget(); + if(!p || p != w->parentWidget()) + return; + invalidateBuffer(q->rect()); + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderBelow, qt_mac_nativeview_for(w)); + qt_event_request_window_change(q); +#endif +} + +#ifndef QT_MAC_USE_COCOA +/* + Modifies the bounds for a widgets backing HIView during moves and resizes. Also updates the + widget, either by scrolling its contents or repainting, depending on the WA_StaticContents + flag +*/ +static void qt_mac_update_widget_position(QWidget *q, QRect oldRect, QRect newRect) +{ + HIRect bounds = CGRectMake(newRect.x(), newRect.y(), + newRect.width(), newRect.height()); + + const HIViewRef view = qt_mac_nativeview_for(q); + const bool isMove = (oldRect.topLeft() != newRect.topLeft()); + const bool isResize = (oldRect.size() != newRect.size()); + +// qDebug() << oldRect << newRect << isMove << isResize << q->testAttribute(Qt::WA_OpaquePaintEvent) << q->testAttribute(Qt::WA_StaticContents); + QWidgetPrivate *qd = qt_widget_private(q); + + // Perform a normal (complete repaint) update in some cases: + if ( + // always repaint on move. + (isMove) || + + // limited update on resize requires WA_StaticContents. + (isResize && q->testAttribute(Qt::WA_StaticContents) == false) || + + // one of the rects are invalid + (oldRect.isValid() == false || newRect.isValid() == false) || + + // the position update is a part of a drag-and-drop operation + QDragManager::self()->object || + + // we are on Panther (no HIViewSetNeedsDisplayInRect) + QSysInfo::MacintoshVersion < QSysInfo::MV_10_4 + ){ + HIViewSetFrame(view, &bounds); + return; + } + + const int dx = newRect.x() - oldRect.x(); + const int dy = newRect.y() - oldRect.y(); + + if (isMove) { + // HIViewScrollRect silently fails if we try to scroll anything under the grow box. + // Check if there's one present within the widget rect, and if there is fall back + // to repainting the entire widget. + QWidget const * const parentWidget = q->parentWidget(); + const HIViewRef parentView = qt_mac_nativeview_for(parentWidget); + HIViewRef nativeSizeGrip = 0; + if (q->testAttribute(Qt::WA_WState_Created)) + HIViewFindByID(HIViewGetRoot(HIViewGetWindow(HIViewRef(q->winId()))), kHIViewWindowGrowBoxID, &nativeSizeGrip); + if (nativeSizeGrip) { + QWidget * const window = q->window(); + + const int sizeGripSize = 20; + const QRect oldWidgetRect = QRect(q->mapTo(window, QPoint(0, 0)), QSize(oldRect.width(), oldRect.height())); + const QRect newWidgetRect = QRect(q->mapTo(window, QPoint(0, 0)), QSize(newRect.width(), newRect.height())); + const QRect sizeGripRect = QRect(window->rect().bottomRight() - QPoint(sizeGripSize, sizeGripSize), + window->rect().bottomRight()); + + if (sizeGripRect.intersects(oldWidgetRect) || sizeGripRect.intersects(newWidgetRect)) { + HIViewSetFrame(view, &bounds); + return; + } + } + + // Don't scroll anything outside the parent widget rect. + const QRect scrollRect = (oldRect | newRect) & parentWidget->rect(); + const HIRect scrollBounds = + CGRectMake(scrollRect.x(), scrollRect.y(), scrollRect.width(), scrollRect.height()); + + // We cannot scroll when the widget has a mask as that would + // scroll the masked out areas too + if (qd->extra && qd->extra->hasMask) { + HIViewMoveBy(view, dx, dy); + return; + } + + OSStatus err = HIViewScrollRect(parentView, &scrollBounds, dx, dy); + if (err != noErr) { + HIViewSetNeedsDisplay(view, true); + qWarning("QWidget: Internal error (%s:%d)", __FILE__, __LINE__); + } + } + // Set the view bounds with drawing disabled to prevent repaints. + HIViewSetDrawingEnabled(view, false); + HIViewSetFrame(view, &bounds); + HIViewSetDrawingEnabled(view, true); + + // Update any newly exposed areas due to resizing. + const int startx = oldRect.width(); + const int stopx = newRect.width(); + const int starty = oldRect.height(); + const int stopy = newRect.height(); + + const HIRect verticalSlice = CGRectMake(startx, 0, stopx , stopy); + HIViewSetNeedsDisplayInRect(view, &verticalSlice, true); + const HIRect horizontalSlice = CGRectMake(0, starty, startx, stopy); + HIViewSetNeedsDisplayInRect(view, &horizontalSlice, true); +} +#endif + +/* + Helper function for non-toplevel widgets. Helps to map Qt's 32bit + coordinate system to OS X's 16bit coordinate system. + + Sets the geometry of the widget to data.crect, but clipped to sizes + that OS X can handle. Unmaps widgets that are completely outside the + valid range. + + Maintains data.wrect, which is the geometry of the OS X widget, + measured in this widget's coordinate system. + + if the parent is not clipped, parentWRect is empty, otherwise + parentWRect is the geometry of the parent's OS X rect, measured in + parent's coord sys +*/ +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &oldRect) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (!q->internalWinId() && QApplicationPrivate::graphicsSystem() != 0) { + // We have no view to move, and no paint engine that + // we can update dirty regions on. So just return: + return; + } + + QMacCocoaAutoReleasePool pool; + + /* + There are up to four different coordinate systems here: + Qt coordinate system for this widget. + X coordinate system for this widget (relative to wrect). + Qt coordinate system for parent + X coordinate system for parent (relative to parent's wrect). + */ + + // wrect is the same as crect, except that it is + // clipped to fit inside parent (and screen): + QRect wrect; + + // wrectInParentCoordSys will be the same as wrect, except that it is + // originated in q's parent rather than q itself. It starts out in + // parent's Qt coord system, and ends up in parent's coordinate system: + QRect wrectInParentCoordSys = data.crect; + + // If q's parent has been clipped, parentWRect will + // be filled with the parents clipped crect: + QRect parentWRect; + + // Embedded have different meaning on each platform, and on + // Mac, it means that q is a QMacNativeWidget. + bool isEmbeddedWindow = (q->isWindow() && topData()->embedded); +#ifdef QT_MAC_USE_COCOA + NSView *nsview = qt_mac_nativeview_for(q); +#endif + if (!isEmbeddedWindow) { + parentWRect = q->parentWidget()->data->wrect; + } else { + // INVARIANT: q's parent view is not owned by Qt. So we need to + // do some extra calls to get the clipped rect of the parent view: +#ifndef QT_MAC_USE_COCOA + HIViewRef parentView = HIViewGetSuperview(qt_mac_nativeview_for(q)); +#else + NSView *parentView = [qt_mac_nativeview_for(q) superview]; +#endif + if (parentView) { +#ifndef QT_MAC_USE_COCOA + HIRect tmpRect; + HIViewGetFrame(parentView, &tmpRect); +#else + NSRect tmpRect = [parentView frame]; +#endif + parentWRect = QRect(tmpRect.origin.x, tmpRect.origin.y, + tmpRect.size.width, tmpRect.size.height); + } else { + const QRect wrectRange(-WRECT_MAX,-WRECT_MAX, 2*WRECT_MAX, 2*WRECT_MAX); + parentWRect = wrectRange; + } + } + + if (parentWRect.isValid()) { + // INVARIANT: q's parent has been clipped. + // So we fit our own wrects inside it: + if (!parentWRect.contains(wrectInParentCoordSys) && !isEmbeddedWindow) { + wrectInParentCoordSys &= parentWRect; + wrect = wrectInParentCoordSys; + // Make sure wrect is originated in q's coordinate system: + wrect.translate(-data.crect.topLeft()); + } + // // Make sure wrectInParentCoordSys originated in q's parent coordinate system: + wrectInParentCoordSys.translate(-parentWRect.topLeft()); + } else { + // INVARIANT: we dont know yet the clipping rect of q's parent. + // So we may or may not have to adjust our wrects: + + if (data.wrect.isValid() && QRect(QPoint(),data.crect.size()).contains(data.wrect)) { + // This is where the main optimization is: we have an old wrect from an earlier + // setGeometry call, and the new crect is smaller than it. If the final wrect is + // also inside the old wrect, we can just move q and its children to the new + // location without any clipping: + + // vrect will be the part of q that's will be visible inside + // q's parent. If it inside the old wrect, then we can just move: + QRect vrect = wrectInParentCoordSys & q->parentWidget()->rect(); + vrect.translate(-data.crect.topLeft()); + + if (data.wrect.contains(vrect)) { + wrectInParentCoordSys = data.wrect; + wrectInParentCoordSys.translate(data.crect.topLeft()); +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + HIViewSetFrame(qt_mac_nativeview_for(q), &bounds); +#else + if (nsview) { + // INVARIANT: q is native. Set view frame: + NSRect bounds = NSMakeRect(wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + [nsview setFrame:bounds]; + } else { + // INVARIANT: q is alien. Repaint wrect instead (includes old and new wrect): + QWidget *parent = q->parentWidget(); + QPoint globalPosWRect = parent->mapToGlobal(data.wrect.topLeft()); + + QWidget *nativeParent = q->nativeParentWidget(); + QRect dirtyWRect = QRect(nativeParent->mapFromGlobal(globalPosWRect), data.wrect.size()); + + nativeParent->update(dirtyWRect); + } +#endif + if (q->testAttribute(Qt::WA_OutsideWSRange)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); + if (!dontShow) { + q->setAttribute(Qt::WA_Mapped); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:NO]; +#endif + } + } + return; + } + } + +#ifndef QT_MAC_USE_COCOA + const QRect validRange(-XCOORD_MAX,-XCOORD_MAX, 2*XCOORD_MAX, 2*XCOORD_MAX); + if (!validRange.contains(wrectInParentCoordSys)) { + // We're too big, and must clip: + QPoint screenOffset(0, 0); // offset of the part being on screen + const QWidget *parentWidget = q->parentWidget(); + while (parentWidget && !parentWidget->isWindow()) { + screenOffset -= parentWidget->data->crect.topLeft(); + parentWidget = parentWidget->parentWidget(); + } + QRect cropRect(screenOffset.x() - WRECT_MAX, + screenOffset.y() - WRECT_MAX, + 2*WRECT_MAX, + 2*WRECT_MAX); + + wrectInParentCoordSys &=cropRect; + wrect = wrectInParentCoordSys; + wrect.translate(-data.crect.topLeft()); + } +#endif //QT_MAC_USE_COCOA + } + + // unmap if we are outside the valid window system coord system + bool outsideRange = !wrectInParentCoordSys.isValid(); + bool mapWindow = false; + if (q->testAttribute(Qt::WA_OutsideWSRange) != outsideRange) { + q->setAttribute(Qt::WA_OutsideWSRange, outsideRange); + if (outsideRange) { +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), false); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:YES]; +#endif + q->setAttribute(Qt::WA_Mapped, false); + } else if (!q->isHidden()) { + mapWindow = true; + } + } + + if (outsideRange) + return; + + // Store the new clipped rect: + bool jump = (data.wrect != wrect); + data.wrect = wrect; + + // and now recursively for all children... + // ### can be optimized + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(); + } + } + +#ifndef QT_MAC_USE_COCOA + // Move the actual HIView: + qt_mac_update_widget_position(q, oldRect, wrectInParentCoordSys); + if (jump) + q->update(); +#else + if (nsview) { + // INVARIANT: q is native. Move the actual NSView: + NSRect bounds = NSMakeRect( + wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + [nsview setFrame:bounds]; + if (jump) + q->update(); + } else if (QApplicationPrivate::graphicsSystem() == 0){ + // INVARIANT: q is alien and we use native paint engine. + // Schedule updates where q is moved from and to: + const QWidget *parent = q->parentWidget(); + const QPoint globalPosOldWRect = parent->mapToGlobal(oldRect.topLeft()); + const QPoint globalPosNewWRect = parent->mapToGlobal(wrectInParentCoordSys.topLeft()); + + QWidget *nativeParent = q->nativeParentWidget(); + const QRegion dirtyOldWRect = QRect(nativeParent->mapFromGlobal(globalPosOldWRect), oldRect.size()); + const QRegion dirtyNewWRect = QRect(nativeParent->mapFromGlobal(globalPosNewWRect), wrectInParentCoordSys.size()); + + const bool sizeUnchanged = oldRect.size() == wrectInParentCoordSys.size(); + const bool posUnchanged = oldRect.topLeft() == wrectInParentCoordSys.topLeft(); + + // Resolve/minimize the region that needs to update: + if (sizeUnchanged && q->testAttribute(Qt::WA_OpaquePaintEvent)) { + // INVARIANT: q is opaque, and is only moved (not resized). So in theory we only + // need to blit pixels, and skip a repaint. But we can only make this work if we + // had access to the backbuffer, so we need to update all: + nativeParent->update(dirtyOldWRect | dirtyNewWRect); + } else if (posUnchanged && q->testAttribute(Qt::WA_StaticContents)) { + // We only need to redraw exposed areas: + nativeParent->update(dirtyNewWRect - dirtyOldWRect); + } else { + nativeParent->update(dirtyOldWRect | dirtyNewWRect); + } + } +#endif + + if (mapWindow && !dontShow) { + q->setAttribute(Qt::WA_Mapped); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:NO]; +#endif + } +} + +void QWidgetPrivate::adjustWithinMaxAndMinSize(int &w, int &h) +{ + if (QWExtra *extra = extraData()) { + w = qMin(w, extra->maxw); + h = qMin(h, extra->maxh); + w = qMax(w, extra->minw); + h = qMax(h, extra->minh); + + // Deal with size increment + if (QTLWExtra *top = topData()) { + if(top->incw) { + w = w/top->incw; + w *= top->incw; + } + if(top->inch) { + h = h/top->inch; + h *= top->inch; + } + } + } + + if (isRealWindow()) { + w = qMax(0, w); + h = qMax(0, h); + } +} + +void QWidgetPrivate::applyMaxAndMinSizeOnWindow() +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + + const float max_f(20000); +#ifndef QT_MAC_USE_COCOA +#define SF(x) ((x > max_f) ? max_f : x) + HISize max = CGSizeMake(SF(extra->maxw), SF(extra->maxh)); + HISize min = CGSizeMake(SF(extra->minw), SF(extra->minh)); +#undef SF + SetWindowResizeLimits(qt_mac_window_for(q), &min, &max); +#else +#define SF(x) ((x > max_f) ? max_f : x) + NSSize max = NSMakeSize(SF(extra->maxw), SF(extra->maxh)); + NSSize min = NSMakeSize(SF(extra->minw), SF(extra->minh)); +#undef SF + [qt_mac_window_for(q) setContentMinSize:min]; + [qt_mac_window_for(q) setContentMaxSize:max]; +#endif +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if(q->windowType() == Qt::Desktop) + return; + + QMacCocoaAutoReleasePool pool; + bool realWindow = isRealWindow(); + + if (realWindow && !q->testAttribute(Qt::WA_DontShowOnScreen)){ + adjustWithinMaxAndMinSize(w, h); +#ifndef QT_MAC_USE_COCOA + if (w != 0 && h != 0) { + topData()->isSetGeometry = 1; + topData()->isMove = isMove; + Rect r; SetRect(&r, x, y, x + w, y + h); + SetWindowBounds(qt_mac_window_for(q), kWindowContentRgn, &r); + topData()->isSetGeometry = 0; + } else { + setGeometry_sys_helper(x, y, w, h, isMove); + } +#else + if (!isMove && !q->testAttribute(Qt::WA_Moved) && !q->isVisible()) { + // INVARIANT: The location of the window has not yet been set. The default will + // instead be to center it on the desktop, or over the parent, if any. Since we now + // resize the window, we need to adjust the top left position to keep the window + // centeralized. And we need to to this now (and before show) in case the positioning + // of other windows (e.g. sub-windows) depend on this position: + if (QWidget *p = q->parentWidget()) { + x = p->geometry().center().x() - (w / 2); + y = p->geometry().center().y() - (h / 2); + } else { + QRect availGeo = QApplication::desktop()->availableGeometry(q); + x = availGeo.center().x() - (w / 2); + y = availGeo.center().y() - (h / 2); + } + } + + QSize olds = q->size(); + const bool isResize = (olds != QSize(w, h)); + NSWindow *window = qt_mac_window_for(q); + const QRect &fStrut = frameStrut(); + const QRect frameRect(QPoint(x - fStrut.left(), y - fStrut.top()), + QSize(fStrut.left() + fStrut.right() + w, + fStrut.top() + fStrut.bottom() + h)); + NSRect cocoaFrameRect = NSMakeRect(frameRect.x(), flipYCoordinate(frameRect.bottom() + 1), + frameRect.width(), frameRect.height()); + // The setFrame call will trigger a 'windowDidResize' notification for the corresponding + // NSWindow. The pending flag is set, so that the resize event can be send as non-spontaneous. + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent); + QPoint currTopLeft = data.crect.topLeft(); + if (currTopLeft.x() == x && currTopLeft.y() == y + && cocoaFrameRect.size.width != 0 + && cocoaFrameRect.size.height != 0) { + [window setFrame:cocoaFrameRect display:realWindow]; + } else { + // The window is moved and resized (or resized to zero). + // Since Cocoa usually only sends us a resize callback after + // setting a window frame, we issue an explicit move as + // well. To stop Cocoa from optimize away the move (since the move + // would have the same origin as the setFrame call) we shift the + // window back and forth inbetween. + cocoaFrameRect.origin.y += 1; + [window setFrame:cocoaFrameRect display:realWindow]; + cocoaFrameRect.origin.y -= 1; + [window setFrameOrigin:cocoaFrameRect.origin]; + } +#endif + } else { + setGeometry_sys_helper(x, y, w, h, isMove); + } + + topLevelAt_cache = 0; +} + +void QWidgetPrivate::setGeometry_sys_helper(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + bool realWindow = isRealWindow(); + + QPoint oldp = q->pos(); + QSize olds = q->size(); + const bool isResize = (olds != QSize(w, h)); + + if (!realWindow && !isResize && QPoint(x, y) == oldp) + return; + + if (isResize) + data.window_state = data.window_state & ~Qt::WindowMaximized; + + const bool visible = q->isVisible(); + // Apply size restrictions, applicable for Windows & Widgets. + if (QWExtra *extra = extraData()) { + w = qMin(w, extra->maxw); + h = qMin(h, extra->maxh); + w = qMax(w, extra->minw); + h = qMax(h, extra->minh); + } + data.crect = QRect(x, y, w, h); + + if (realWindow) { + adjustWithinMaxAndMinSize(w, h); + qt_mac_update_sizer(q); + +#ifndef QT_MAC_USE_COCOA + if (q->windowFlags() & Qt::WindowMaximizeButtonHint) { + OSWindowRef window = qt_mac_window_for(q); + if (extra->maxw && extra->maxh && extra->maxw == extra->minw + && extra->maxh == extra->minh) { + ChangeWindowAttributes(window, kWindowNoAttributes, kWindowFullZoomAttribute); + } else { + ChangeWindowAttributes(window, kWindowFullZoomAttribute, kWindowNoAttributes); + } + } + HIRect bounds = CGRectMake(0, 0, w, h); + HIViewSetFrame(qt_mac_nativeview_for(q), &bounds); +#else + [qt_mac_nativeview_for(q) setFrame:NSMakeRect(0, 0, w, h)]; +#endif + } else { + const QRect oldRect(oldp, olds); + if (!isResize && QApplicationPrivate::graphicsSystem()) + moveRect(oldRect, x - oldp.x(), y - oldp.y()); + + setWSGeometry(false, oldRect); + + if (isResize && QApplicationPrivate::graphicsSystem()) + invalidateBuffer_resizeHelper(oldp, olds); + } + + if(isMove || isResize) { + if(!visible) { + if(isMove && q->pos() != oldp) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if(isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } else { + if(isResize) { //send the resize event.. + QResizeEvent e(q->size(), olds); + QApplication::sendEvent(q, &e); + } + if(isMove && q->pos() != oldp) { //send the move event.. + QMoveEvent e(q->pos(), oldp); + QApplication::sendEvent(q, &e); + } + } + } + qt_event_request_window_change(q); +} + +void QWidgetPrivate::setConstraints_sys() +{ + updateMaximizeButton_sys(); + applyMaxAndMinSizeOnWindow(); +} + +void QWidgetPrivate::updateMaximizeButton_sys() +{ + Q_Q(QWidget); + if (q->data->window_flags & Qt::CustomizeWindowHint) + return; + + OSWindowRef window = qt_mac_window_for(q); + QTLWExtra * tlwExtra = topData(); +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSButton *maximizeButton = [window standardWindowButton:NSWindowZoomButton]; +#endif + if (extra->maxw && extra->maxh + && extra->maxw == extra->minw + && extra->maxh == extra->minh) { + // The window has a fixed size, so gray out the maximize button: + if (!tlwExtra->savedWindowAttributesFromMaximized) { +#ifndef QT_MAC_USE_COCOA + GetWindowAttributes(window, + (WindowAttributes*)&extra->topextra->savedWindowAttributesFromMaximized); + +#else + tlwExtra->savedWindowAttributesFromMaximized = (![maximizeButton isHidden] && [maximizeButton isEnabled]); +#endif + } +#ifndef QT_MAC_USE_COCOA + ChangeWindowAttributes(window, kWindowNoAttributes, kWindowFullZoomAttribute); +#else + [maximizeButton setEnabled:NO]; +#endif + + + } else { + if (tlwExtra->savedWindowAttributesFromMaximized) { +#ifndef QT_MAC_USE_COCOA + ChangeWindowAttributes(window, + extra->topextra->savedWindowAttributesFromMaximized, + kWindowNoAttributes); +#else + [maximizeButton setEnabled:YES]; +#endif + tlwExtra->savedWindowAttributesFromMaximized = 0; + } + } + + +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + if (QApplicationPrivate::graphicsSystem() && !paintOnScreen()) { + // INVARIANT: Alien paint engine + scrollChildren(dx, dy); + scrollRect(q_func()->rect(), dx, dy); + } else { + scroll_sys(dx, dy, QRect()); + } +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &qscrollRect) +{ + if (QMacScrollOptimization::delayScroll(this, dx, dy, qscrollRect)) + return; + + Q_Q(QWidget); + if (QApplicationPrivate::graphicsSystem() && !paintOnScreen()) { + // INVARIANT: Alien paint engine + scrollRect(qscrollRect, dx, dy); + return; + } + + static int accelEnv = -1; + if (accelEnv == -1) { + accelEnv = qgetenv("QT_NO_FAST_SCROLL").toInt() == 0; + } + + // Scroll the whole widget if qscrollRect is not valid: + QRect validScrollRect = qscrollRect.isValid() ? qscrollRect : q->rect(); + validScrollRect &= clipRect(); + + // If q is overlapped by other widgets, we cannot just blit pixels since + // this will move overlapping widgets as well. In case we just update: + const bool overlapped = isOverlapped(validScrollRect.translated(data.crect.topLeft())); + const bool accelerateScroll = accelEnv && isOpaque && !overlapped; + const bool isAlien = (q->internalWinId() == 0); + const QPoint scrollDelta(dx, dy); + + // If qscrollRect is valid, we are _not_ supposed to scroll q's children (as documented). + // But we do scroll children (and the whole of q) if qscrollRect is invalid. This case is + // documented as undefined, but we exploit it to help factor our code into one function. + const bool scrollChildren = !qscrollRect.isValid(); + + if (!q->updatesEnabled()) { + // We are told not to update anything on q at this point. So unless + // we are supposed to scroll children, we bail out early: + if (!scrollChildren || q->children().isEmpty()) + return; + } + + if (!accelerateScroll) { + if (overlapped) { + QRegion region(validScrollRect); + subtractOpaqueSiblings(region); + update_sys(region); + }else { + update_sys(qscrollRect); + } + return; + } + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#else + Q_UNUSED(isAlien); + // We're not sure what the following call is supposed to achive + // but until we see what it breaks, we don't bring it into the + // Cocoa port: + qt_event_request_window_change(q); +#endif + + // First move all native children. Alien children will indirectly be + // moved when the parent is scrolled. All directly or indirectly moved + // children will receive a move event before the function call returns. + QWidgetList movedChildren; + if (scrollChildren) { + QObjectList children = q->children(); + + for (int i=0; i<children.size(); i++) { + QObject *obj = children.at(i); + if (QWidget *w = qobject_cast<QWidget*>(obj)) { + if (!w->isWindow()) { + w->data->crect = QRect(w->pos() + scrollDelta, w->size()); +#ifndef QT_MAC_USE_COCOA + if (w->testAttribute(Qt::WA_WState_Created)) { + HIRect bounds = CGRectMake(w->data->crect.x(), w->data->crect.y(), + w->data->crect.width(), w->data->crect.height()); + HIViewRef hiview = qt_mac_nativeview_for(w); + const bool opaque = q->testAttribute(Qt::WA_OpaquePaintEvent); + + if (opaque) + HIViewSetDrawingEnabled(hiview, false); + HIViewSetFrame(hiview, &bounds); + if (opaque) + HIViewSetDrawingEnabled(hiview, true); + } +#else + if (NSView *view = qt_mac_nativeview_for(w)) { + // INVARIANT: w is not alien + [view setFrame:NSMakeRect( + w->data->crect.x(), w->data->crect.y(), + w->data->crect.width(), w->data->crect.height())]; + } +#endif + movedChildren.append(w); + } + } + } + } + + if (q->testAttribute(Qt::WA_WState_Created) && q->isVisible()) { + // Scroll q itself according to the qscrollRect, and + // call update on any exposed areas so that they get redrawn: + +#ifndef QT_MAC_USE_COCOA + OSViewRef view = qt_mac_nativeview_for(q); + HIRect scrollrect = CGRectMake(qscrollRect.x(), qscrollRect.y(), qscrollRect.width(), qscrollRect.height()); + OSStatus err = _HIViewScrollRectWithOptions(view, qscrollRect.isValid() ? &scrollrect : 0, dx, dy, kHIViewScrollRectAdjustInvalid); + if (err) { + // The only parameter that can go wrong, is the rect. + qWarning("QWidget::scroll: Your rectangle was too big for the widget, clipping rect"); + scrollrect = CGRectMake(qMax(qscrollRect.x(), 0), qMax(qscrollRect.y(), 0), + qMin(qscrollRect.width(), q->width()), qMin(qscrollRect.height(), q->height())); + _HIViewScrollRectWithOptions(view, qscrollRect.isValid() ? &scrollrect : 0, dx, dy, kHIViewScrollRectAdjustInvalid); + } +#else + + QWidget *nativeWidget = isAlien ? q->nativeParentWidget() : q; + if (!nativeWidget) + return; + OSViewRef view = qt_mac_nativeview_for(nativeWidget); + if (!view) + return; + + // Calculate the rectangles that needs to be redrawn + // after the scroll. This will be source rect minus destination rect: + QRect deltaXRect; + if (dx != 0) { + deltaXRect.setY(validScrollRect.y()); + deltaXRect.setHeight(validScrollRect.height()); + if (dx > 0) { + deltaXRect.setX(validScrollRect.x()); + deltaXRect.setWidth(dx); + } else { + deltaXRect.setX(validScrollRect.x() + validScrollRect.width() + dx); + deltaXRect.setWidth(-dx); + } + } + + QRect deltaYRect; + if (dy != 0) { + deltaYRect.setX(validScrollRect.x()); + deltaYRect.setWidth(validScrollRect.width()); + if (dy > 0) { + deltaYRect.setY(validScrollRect.y()); + deltaYRect.setHeight(dy); + } else { + deltaYRect.setY(validScrollRect.y() + validScrollRect.height() + dy); + deltaYRect.setHeight(-dy); + } + } + + if (isAlien) { + // Adjust the scroll rect to the location as seen from the native parent: + QPoint scrollTopLeftInsideNative = nativeWidget->mapFromGlobal(q->mapToGlobal(validScrollRect.topLeft())); + validScrollRect.moveTo(scrollTopLeftInsideNative); + } + + // Make the pixel copy rect within the validScrollRect bounds: + NSRect nsscrollRect = NSMakeRect( + validScrollRect.x() + (dx < 0 ? -dx : 0), + validScrollRect.y() + (dy < 0 ? -dy : 0), + validScrollRect.width() + (dx > 0 ? -dx : 0), + validScrollRect.height() + (dy > 0 ? -dy : 0)); + + NSSize deltaSize = NSMakeSize(dx, dy); + [view scrollRect:nsscrollRect by:deltaSize]; + + // Some areas inside the scroll rect might have been marked as dirty from before, which + // means that they are scheduled to be redrawn. But as we now scroll, those dirty rects + // should also move along to ensure that q receives repaints on the correct places. + // Since some of the dirty rects might lay outside, or only intersect with, the scroll + // rect, the old calls to setNeedsDisplay still makes sense. + // NB: Using [view translateRectsNeedingDisplayInRect:nsscrollRect by:deltaSize] have + // so far not been proven fruitful to solve this problem. + const QVector<QRect> &dirtyRectsToScroll = dirtyOnWidget.rects(); + for (int i=0; i<dirtyRectsToScroll.size(); ++i) { + QRect qdirtyRect = dirtyRectsToScroll[i]; + qdirtyRect.translate(dx, dy); + update_sys(qdirtyRect); + } + + // Update newly exposed areas. This will generate new dirty areas on + // q, and therefore, we do it after updating the old dirty rects above: + if (dx != 0) + update_sys(deltaXRect); + if (dy != 0) + update_sys(deltaYRect); + +#endif // QT_MAC_USE_COCOA + } + + for (int i=0; i<movedChildren.size(); i++) { + QWidget *w = movedChildren.at(i); + QMoveEvent e(w->pos(), w->pos() - scrollDelta); + QApplication::sendEvent(w, &e); + } +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + switch(m) { + case PdmHeightMM: + return qRound(metric(PdmHeight) * 25.4 / qreal(metric(PdmDpiY))); + case PdmWidthMM: + return qRound(metric(PdmWidth) * 25.4 / qreal(metric(PdmDpiX))); + case PdmHeight: + case PdmWidth: +#ifndef QT_MAC_USE_COCOA + { HIRect rect; + HIViewGetFrame(qt_mac_nativeview_for(this), &rect); + if(m == PdmWidth) + return (int)rect.size.width; + return (int)rect.size.height; } +#else + if (m == PdmWidth) + return data->crect.width(); + else + return data->crect.height(); +#endif + case PdmDepth: + return 32; + case PdmNumColors: + return INT_MAX; + case PdmDpiX: + case PdmPhysicalDpiX: { + Q_D(const QWidget); + if (d->extra && d->extra->customDpiX) + return d->extra->customDpiX; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_x()); } + case PdmDpiY: + case PdmPhysicalDpiY: { + Q_D(const QWidget); + if (d->extra && d->extra->customDpiY) + return d->extra->customDpiY; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + extern float qt_mac_defaultDpi_y(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_y()); } + default: //leave this so the compiler complains when new ones are added + qWarning("QWidget::metric: Unhandled parameter %d", m); + return QPaintDevice::metric(m); + } + return 0; +} + +void QWidgetPrivate::createSysExtra() +{ +#ifdef QT_MAC_USE_COCOA + extra->imageMask = 0; +#endif +} + +void QWidgetPrivate::deleteSysExtra() +{ +#ifdef QT_MAC_USE_COCOA + if (extra->imageMask) + CFRelease(extra->imageMask); +#endif +} + +void QWidgetPrivate::createTLSysExtra() +{ + extra->topextra->resizer = 0; + extra->topextra->isSetGeometry = 0; + extra->topextra->isMove = 0; + extra->topextra->wattr = 0; + extra->topextra->wclass = 0; + extra->topextra->group = 0; + extra->topextra->windowIcon = 0; + extra->topextra->savedWindowAttributesFromMaximized = 0; +} + +void QWidgetPrivate::deleteTLSysExtra() +{ +#ifndef QT_MAC_USE_COCOA + if (extra->topextra->group) { + qt_mac_release_window_group(extra->topextra->group); + extra->topextra->group = 0; + } + if (extra->topextra->windowIcon) { + ReleaseIconRef(extra->topextra->windowIcon); + extra->topextra->windowIcon = 0; + } +#endif +} + +void QWidgetPrivate::updateFrameStrut() +{ + Q_Q(QWidget); + + QWidgetPrivate *that = const_cast<QWidgetPrivate*>(this); + + that->data.fstrut_dirty = false; + QTLWExtra *top = that->topData(); + +#if QT_MAC_USE_COCOA + // 1 Get the window frame + OSWindowRef oswnd = qt_mac_window_for(q); + NSRect frameW = [oswnd frame]; + // 2 Get the content frame - so now + NSRect frameC = [oswnd contentRectForFrameRect:frameW]; + top->frameStrut.setCoords(frameC.origin.x - frameW.origin.x, + (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height), + (frameW.origin.x + frameW.size.width) - (frameC.origin.x + frameC.size.width), + frameC.origin.y - frameW.origin.y); +#else + Rect window_r; + GetWindowStructureWidths(qt_mac_window_for(q), &window_r); + top->frameStrut.setCoords(window_r.left, window_r.top, window_r.right, window_r.bottom); +#endif +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; +#ifndef QT_MAC_USE_COCOA + SetControlDragTrackingEnabled(qt_mac_nativeview_for(q), on); +#else + NSWindow *win = qt_mac_window_for(q); + if (on) { + if ([win isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaWindow) class]]) + [static_cast<QT_MANGLE_NAMESPACE(QCocoaWindow) *>(win) registerDragTypes]; + else if ([win isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaPanel) class]]) + [static_cast<QT_MANGLE_NAMESPACE(QCocoaPanel) *>(win) registerDragTypes]; + } +#endif +} + +void QWidgetPrivate::registerTouchWindow(bool enable) +{ + Q_UNUSED(enable); +#ifdef QT_MAC_USE_COCOA +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_6) + return; + + Q_Q(QWidget); + if (enable == touchEventsEnabled) + return; + + QCocoaView *view = static_cast<QCocoaView *>(qt_mac_effectiveview_for(q)); + if (!view) + return; + + if (enable) { + ++view->alienTouchCount; + if (view->alienTouchCount == 1) { + touchEventsEnabled = true; + [view setAcceptsTouchEvents:YES]; + } + } else { + --view->alienTouchCount; + if (view->alienTouchCount == 0) { + touchEventsEnabled = false; + [view setAcceptsTouchEvents:NO]; + } + } +#endif +#endif +} + +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_UNUSED(region); + Q_Q(QWidget); + +#ifndef QT_MAC_USE_COCOA + if (q->isWindow()) + ReshapeCustomWindow(qt_mac_window_for(q)); + else + HIViewReshapeStructure(qt_mac_nativeview_for(q)); +#else + if (!q->internalWinId()) + return; + + if (extra->mask.isEmpty()) { + extra->maskBits = QImage(); + finishCocoaMaskSetup(); + } else { + syncCocoaMask(); + } + + topLevelAt_cache = 0; +#endif +} + +void QWidgetPrivate::setWindowOpacity_sys(qreal level) +{ + Q_Q(QWidget); + + if (!q->isWindow()) + return; + + level = qBound(0.0, level, 1.0); + topData()->opacity = (uchar)(level * 255); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + OSWindowRef oswindow = qt_mac_window_for(q); +#if QT_MAC_USE_COCOA + [oswindow setAlphaValue:level]; +#else + SetWindowAlpha(oswindow, level); +#endif +} + +#ifdef QT_MAC_USE_COCOA +void QWidgetPrivate::syncCocoaMask() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !extra) + return; + + if (extra->hasMask) { + if(extra->maskBits.size() != q->size()) { + extra->maskBits = QImage(q->size(), QImage::Format_Mono); + } + extra->maskBits.fill(QColor(Qt::color1).rgba()); + extra->maskBits.setNumColors(2); + extra->maskBits.setColor(0, QColor(Qt::color0).rgba()); + extra->maskBits.setColor(1, QColor(Qt::color1).rgba()); + QPainter painter(&extra->maskBits); + painter.setBrush(Qt::color1); + painter.setPen(Qt::NoPen); + painter.drawRects(extra->mask.rects()); + painter.end(); + finishCocoaMaskSetup(); + } +} + +void QWidgetPrivate::finishCocoaMaskSetup() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created) || !extra) + return; + + // Technically this is too late to release, because the data behind the image + // has already been released. But it's more tidy to do it here. + // If you are seeing a crash, consider doing a CFRelease before changing extra->maskBits. + if (extra->imageMask) { + CFRelease(extra->imageMask); + extra->imageMask = 0; + } + + if (!extra->maskBits.isNull()) { + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(0, + extra->maskBits.bits(), + extra->maskBits.numBytes(), + 0); // shouldn't need to release. + CGFloat decode[2] = {1, 0}; + extra->imageMask = CGImageMaskCreate(extra->maskBits.width(), extra->maskBits.height(), + 1, 1, extra->maskBits.bytesPerLine(), dataProvider, + decode, false); + } + if (q->isWindow()) { + NSWindow *window = qt_mac_window_for(q); + [window setOpaque:(extra->imageMask == 0)]; + [window invalidateShadow]; + } + macSetNeedsDisplay(QRegion()); +} +#endif + +struct QPaintEngineCleanupHandler +{ + inline QPaintEngineCleanupHandler() : engine(0) {} + inline ~QPaintEngineCleanupHandler() { delete engine; } + QPaintEngine *engine; +}; + +Q_GLOBAL_STATIC(QPaintEngineCleanupHandler, engineHandler) + +QPaintEngine *QWidget::paintEngine() const +{ + QPaintEngine *&pe = engineHandler()->engine; + if (!pe) + pe = new QCoreGraphicsPaintEngine(); + if (pe->isActive()) { + QPaintEngine *engine = new QCoreGraphicsPaintEngine(); + engine->setAutoDestruct(true); + return engine; + } + return pe; +} + +void QWidgetPrivate::setModal_sys() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow()) + return; + const QWidget * const windowParent = q->window()->parentWidget(); + const QWidget * const primaryWindow = windowParent ? windowParent->window() : 0; + OSWindowRef windowRef = qt_mac_window_for(q); + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + bool alreadySheet = [windowRef styleMask] & NSDocModalWindowMask; + + if (windowParent && q->windowModality() == Qt::WindowModal){ + // INVARIANT: Window should be window-modal (which implies a sheet). + if (!alreadySheet) { + // NB: the following call will call setModal_sys recursivly: + recreateMacWindow(); + windowRef = qt_mac_window_for(q); + } + if ([windowRef isKindOfClass:[NSPanel class]]){ + // If the primary window of the sheet parent is a child of a modal dialog, + // the sheet parent should not be modally shaddowed. + // This goes for the sheet as well: + OSWindowRef ref = primaryWindow ? qt_mac_window_for(primaryWindow) : 0; + bool isDialog = ref ? [ref isKindOfClass:[NSPanel class]] : false; + bool worksWhenModal = isDialog ? [static_cast<NSPanel *>(ref) worksWhenModal] : false; + if (worksWhenModal) + [static_cast<NSPanel *>(windowRef) setWorksWhenModal:YES]; + } + } else { + // INVARIANT: Window shold _not_ be window-modal (and as such, not a sheet). + if (alreadySheet){ + // NB: the following call will call setModal_sys recursivly: + recreateMacWindow(); + windowRef = qt_mac_window_for(q); + } + if (q->windowModality() == Qt::NonModal + && primaryWindow && primaryWindow->windowModality() == Qt::ApplicationModal) { + // INVARIANT: Our window has a parent that is application modal. + // This means that q is supposed to be on top of this window and + // not be modally shaddowed: + if ([windowRef isKindOfClass:[NSPanel class]]) + [static_cast<NSPanel *>(windowRef) setWorksWhenModal:YES]; + } + } + +#else + const bool primaryWindowModal = primaryWindow ? primaryWindow->testAttribute(Qt::WA_ShowModal) : false; + const bool modal = q->testAttribute(Qt::WA_ShowModal); + + WindowClass old_wclass; + GetWindowClass(windowRef, &old_wclass); + + if (modal || primaryWindowModal) { + if (q->windowModality() == Qt::WindowModal + || (primaryWindow && primaryWindow->windowModality() == Qt::WindowModal)){ + // Window should be window-modal (which implies a sheet). + if (old_wclass != kSheetWindowClass){ + // We cannot convert a created window to a sheet. + // So we recreate the window: + recreateMacWindow(); + return; + } + } else { + // Window should be application-modal (which implies NOT using a sheet). + if (old_wclass == kSheetWindowClass){ + // We cannot convert a sheet to a window. + // So we recreate the window: + recreateMacWindow(); + return; + } else if (!(q->data->window_flags & Qt::CustomizeWindowHint)) { + if (old_wclass == kDocumentWindowClass || old_wclass == kFloatingWindowClass || old_wclass == kUtilityWindowClass){ + // Only change the class to kMovableModalWindowClass if the no explicit jewels + // are set (kMovableModalWindowClass can't contain them), and the current window class + // can be converted to modal (according to carbon doc). Mind the order of + // HIWindowChangeClass and ChangeWindowAttributes. + WindowGroupRef group = GetWindowGroup(windowRef); + HIWindowChangeClass(windowRef, kMovableModalWindowClass); + quint32 tmpWattr = kWindowCloseBoxAttribute | kWindowHorizontalZoomAttribute; + ChangeWindowAttributes(windowRef, tmpWattr, kWindowNoAttributes); + ChangeWindowAttributes(windowRef, kWindowNoAttributes, tmpWattr); + // If the window belongs to a qt-created group, set that group once more: + if (data.window_flags & Qt::WindowStaysOnTopHint + || q->windowType() == Qt::Popup + || q->windowType() == Qt::ToolTip) + SetWindowGroup(windowRef, group); + } + // Popups are usually handled "special" and are never modal. + Qt::WindowType winType = q->windowType(); + if (winType != Qt::Popup && winType != Qt::ToolTip) + SetWindowModality(windowRef, kWindowModalityAppModal, 0); + } + } + } else if (windowRef) { + if (old_wclass == kSheetWindowClass){ + // Converting a sheet to a window is complex. It's easier to recreate: + recreateMacWindow(); + return; + } + + SetWindowModality(windowRef, kWindowModalityNone, 0); + if (!(q->data->window_flags & Qt::CustomizeWindowHint)) { + if (q->window()->d_func()->topData()->wattr |= kWindowCloseBoxAttribute) + ChangeWindowAttributes(windowRef, kWindowCloseBoxAttribute, kWindowNoAttributes); + if (q->window()->d_func()->topData()->wattr |= kWindowHorizontalZoomAttribute) + ChangeWindowAttributes(windowRef, kWindowHorizontalZoomAttribute, kWindowNoAttributes); + if (q->window()->d_func()->topData()->wattr |= kWindowCollapseBoxAttribute) + ChangeWindowAttributes(windowRef, kWindowCollapseBoxAttribute, kWindowNoAttributes); + } + + WindowClass newClass = q->window()->d_func()->topData()->wclass; + if (old_wclass != newClass && newClass != 0){ + WindowGroupRef group = GetWindowGroup(windowRef); + HIWindowChangeClass(windowRef, newClass); + // If the window belongs to a qt-created group, set that group once more: + if (data.window_flags & Qt::WindowStaysOnTopHint + || q->windowType() == Qt::Popup + || q->windowType() == Qt::ToolTip) + SetWindowGroup(windowRef, group); + } + } + + // Make sure that HIWindowChangeClass didn't remove drag support + // or reset the opaque size grip setting: + SetAutomaticControlDragTrackingEnabledForWindow(windowRef, true); + macUpdateOpaqueSizeGrip(); +#endif +} + +void QWidgetPrivate::macUpdateHideOnSuspend() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow() || q->windowType() != Qt::Tool) + return; +#ifndef QT_MAC_USE_COCOA + if(q->testAttribute(Qt::WA_MacAlwaysShowToolWindow)) + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowHideOnSuspendAttribute); + else + ChangeWindowAttributes(qt_mac_window_for(q), kWindowHideOnSuspendAttribute, 0); +#else + if(q->testAttribute(Qt::WA_MacAlwaysShowToolWindow)) + [qt_mac_window_for(q) setHidesOnDeactivate:NO]; + else + [qt_mac_window_for(q) setHidesOnDeactivate:YES]; +#endif +} + +void QWidgetPrivate::macUpdateOpaqueSizeGrip() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow()) + return; + +#ifndef QT_MAC_USE_COCOA // Growbox is always transparent on Cocoa. Can emulate with setting a QSizeGrip + HIViewRef growBox; + HIViewFindByID(HIViewGetRoot(qt_mac_window_for(q)), kHIViewWindowGrowBoxID, &growBox); + if (!growBox) + return; + HIGrowBoxViewSetTransparent(growBox, !q->testAttribute(Qt::WA_MacOpaqueSizeGrip)); +#endif +} + +void QWidgetPrivate::macUpdateSizeAttribute() +{ + Q_Q(QWidget); + QEvent event(QEvent::MacSizeChange); + QApplication::sendEvent(q, &event); + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (w && (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + && !q->testAttribute(Qt::WA_MacMiniSize) // no attribute set? inherit from parent + && !w->testAttribute(Qt::WA_MacSmallSize) + && !w->testAttribute(Qt::WA_MacNormalSize)) + w->d_func()->macUpdateSizeAttribute(); + } + resolveFont(); +} + +void QWidgetPrivate::macUpdateIgnoreMouseEvents() +{ +#ifndef QT_MAC_USE_COCOA // This is handled inside the mouse handler on Cocoa. + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + if(q->isWindow()) + { + if(q->testAttribute(Qt::WA_TransparentForMouseEvents)) + ChangeWindowAttributes(qt_mac_window_for(q), kWindowIgnoreClicksAttribute, 0); + else + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowIgnoreClicksAttribute); + ReshapeCustomWindow(qt_mac_window_for(q)); + } else { +#ifndef kHIViewFeatureIgnoresClicks +#define kHIViewFeatureIgnoresClicks kHIViewIgnoresClicks +#endif + if(q->testAttribute(Qt::WA_TransparentForMouseEvents)) + HIViewChangeFeatures(qt_mac_nativeview_for(q), kHIViewFeatureIgnoresClicks, 0); + else + HIViewChangeFeatures(qt_mac_nativeview_for(q), 0, kHIViewFeatureIgnoresClicks); + HIViewReshapeStructure(qt_mac_nativeview_for(q)); + } +#endif +} + +void QWidgetPrivate::macUpdateMetalAttribute() +{ + Q_Q(QWidget); + bool realWindow = isRealWindow(); + if (!q->testAttribute(Qt::WA_WState_Created) || !realWindow) + return; + + if (realWindow) { +#if QT_MAC_USE_COCOA + // Cocoa doesn't let us change the style mask once it's been changed + // So, that means we need to recreate the window. + OSWindowRef cocoaWindow = qt_mac_window_for(q); + if ([cocoaWindow styleMask] & NSTexturedBackgroundWindowMask) + return; + recreateMacWindow(); +#else + QMainWindowLayout *layout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q)); + if (q->testAttribute(Qt::WA_MacBrushedMetal)) { + if (layout) + layout->updateHIToolBarStatus(); + ChangeWindowAttributes(qt_mac_window_for(q), kWindowMetalAttribute, 0); + ChangeWindowAttributes(qt_mac_window_for(q), kWindowMetalNoContentSeparatorAttribute, 0); + } else { + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowMetalNoContentSeparatorAttribute); + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowMetalAttribute); + if (layout) + layout->updateHIToolBarStatus(); + } +#endif + } +} + +void QWidgetPrivate::setEnabled_helper_sys(bool enable) +{ +#ifdef QT_MAC_USE_COCOA + Q_Q(QWidget); + NSView *view = qt_mac_nativeview_for(q); + if ([view isKindOfClass:[NSControl class]]) + [static_cast<NSControl *>(view) setEnabled:enable]; +#else + Q_UNUSED(enable); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidget_p.h b/src/gui/kernel/qwidget_p.h new file mode 100644 index 0000000000..919f8bc3da --- /dev/null +++ b/src/gui/kernel/qwidget_p.h @@ -0,0 +1,1035 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGET_P_H +#define QWIDGET_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 "QtGui/qwidget.h" +#include "private/qobject_p.h" +#include "QtCore/qrect.h" +#include "QtCore/qlocale.h" +#include "QtCore/qset.h" +#include "QtGui/qregion.h" +#include "QtGui/qsizepolicy.h" +#include "QtGui/qstyle.h" +#include "QtGui/qapplication.h" +#include <private/qgraphicseffect_p.h> +#include "QtGui/qgraphicsproxywidget.h" +#include "QtGui/qgraphicsscene.h" +#include "QtGui/qgraphicsview.h" +#include <private/qgesture_p.h> + +#ifdef Q_WS_WIN +#include "QtCore/qt_windows.h" +#include <private/qdnd_p.h> +#endif // Q_WS_WIN + +#ifdef Q_WS_X11 +#include "QtGui/qx11info_x11.h" +#endif + +#ifdef Q_WS_MAC +#include <private/qt_mac_p.h> +#endif + +#if defined(Q_WS_QWS) +#include "QtGui/qinputcontext.h" +#include "QtGui/qscreen_qws.h" +#endif + +#if defined(Q_OS_SYMBIAN) +class RDrawableWindow; +class CCoeControl; +#endif + +QT_BEGIN_NAMESPACE + +// Extra QWidget data +// - to minimize memory usage for members that are seldom used. +// - top-level widgets have extra extra data to reduce cost further +#if defined(Q_WS_QWS) +class QWSManager; +#endif +#if defined(Q_WS_MAC) +class QCoreGraphicsPaintEnginePrivate; +#endif +#if defined(Q_WS_QPA) +class QPlatformWindow; +#endif +class QPaintEngine; +class QPixmap; +class QWidgetBackingStore; +class QGraphicsProxyWidget; +class QWidgetItemV2; + +class QStyle; + +class QUnifiedToolbarSurface; + +class Q_AUTOTEST_EXPORT QWidgetBackingStoreTracker +{ + +public: + QWidgetBackingStoreTracker(); + ~QWidgetBackingStoreTracker(); + + void create(QWidget *tlw); + void destroy(); + + void registerWidget(QWidget *w); + void unregisterWidget(QWidget *w); + void unregisterWidgetSubtree(QWidget *w); + + inline QWidgetBackingStore* data() + { + return m_ptr; + } + + inline QWidgetBackingStore* operator->() + { + return m_ptr; + } + + inline QWidgetBackingStore& operator*() + { + return *m_ptr; + } + + inline operator bool() const + { + return (0 != m_ptr); + } + +private: + Q_DISABLE_COPY(QWidgetBackingStoreTracker) + +private: + QWidgetBackingStore* m_ptr; + QSet<QWidget *> m_widgets; +}; + +struct QTLWExtra { + // *************************** Cross-platform variables ***************************** + + // Regular pointers (keep them together to avoid gaps on 64 bits architectures). + QIcon *icon; // widget icon + QPixmap *iconPixmap; + QWidgetBackingStoreTracker backingStore; + QWindowSurface *windowSurface; + QPainter *sharedPainter; + + // Implicit pointers (shared_null). + QString caption; // widget caption + QString iconText; // widget icon text + QString role; // widget role + QString filePath; // widget file path + + // Other variables. + short incw, inch; // size increments + short basew, baseh; // base sizes + // frame strut, don't use these directly, use QWidgetPrivate::frameStrut() instead. + QRect frameStrut; + QRect normalGeometry; // used by showMin/maximized/FullScreen + Qt::WindowFlags savedFlags; // Save widget flags while showing fullscreen + + // *************************** Cross-platform bit fields **************************** + uint opacity : 8; + uint posFromMove : 1; + uint sizeAdjusted : 1; + uint inTopLevelResize : 1; + uint inRepaint : 1; + uint embedded : 1; + + // *************************** Platform specific values (bit fields first) ********** +#if defined(Q_WS_X11) // <----------------------------------------------------------- X11 + uint spont_unmapped: 1; // window was spontaneously unmapped + uint dnd : 1; // DND properties installed + uint validWMState : 1; // is WM_STATE valid? + uint waitingForMapNotify : 1; // show() has been called, haven't got the MapNotify yet + WId parentWinId; // parent window Id (valid after reparenting) + WId userTimeWindow; // window id that contains user-time timestamp when WM supports a _NET_WM_USER_TIME_WINDOW atom + QPoint fullScreenOffset; +#ifndef QT_NO_XSYNC + WId syncUpdateCounter; + ulong syncRequestTimestamp; + qint32 newCounterValueHi; + quint32 newCounterValueLo; +#endif +#elif defined(Q_WS_WIN) // <--------------------------------------------------------- WIN + uint hotkeyRegistered: 1; // Hot key from the STARTUPINFO has been registered. + HICON winIconBig; // internal big Windows icon + HICON winIconSmall; // internal small Windows icon +#elif defined(Q_WS_MAC) // <--------------------------------------------------------- MAC + uint resizer : 4; + uint isSetGeometry : 1; + uint isMove : 1; + quint32 wattr; + quint32 wclass; + WindowGroupRef group; + IconRef windowIcon; // the current window icon, if set with setWindowIcon_sys. + quint32 savedWindowAttributesFromMaximized; // Saved attributes from when the calling updateMaximizeButton_sys() +#ifdef QT_MAC_USE_COCOA + // This value is just to make sure we maximize and restore to the right location, yet we allow apps to be maximized and + // manually resized. + // The name is misleading, since this is set when maximizing the window. It is a hint to saveGeometry(..) to record the + // starting position as 0,0 instead of the normal starting position. + bool wasMaximized; +#endif // QT_MAC_USE_COCOA + +#elif defined(Q_WS_QWS) // <--------------------------------------------------------- QWS +#ifndef QT_NO_QWS_MANAGER + QWSManager *qwsManager; +#endif +#elif defined(Q_OS_SYMBIAN) + uint inExpose : 1; // Prevents drawing recursion + uint nativeWindowTransparencyEnabled : 1; // Tracks native window transparency +#elif defined(Q_WS_QPA) + QPlatformWindow *platformWindow; + QPlatformWindowFormat platformWindowFormat; + quint32 screenIndex; // index in qplatformscreenlist +#endif +}; + +struct QWExtra { + // *************************** Cross-platform variables ***************************** + + // Regular pointers (keep them together to avoid gaps on 64 bits architectures). + void *glContext; // if the widget is hijacked by QGLWindowSurface + QTLWExtra *topextra; // only useful for TLWs +#ifndef QT_NO_GRAPHICSVIEW + QGraphicsProxyWidget *proxyWidget; // if the widget is embedded +#endif +#ifndef QT_NO_CURSOR + QCursor *curs; +#endif + QPointer<QStyle> style; + QPointer<QWidget> focus_proxy; + + // Implicit pointers (shared_empty/shared_null). + QRegion mask; // widget mask + QString styleSheet; + + // Other variables. + qint32 minw; + qint32 minh; // minimum size + qint32 maxw; + qint32 maxh; // maximum size + quint16 customDpiX; + quint16 customDpiY; + QSize staticContentsSize; + + // *************************** Cross-platform bit fields **************************** + uint explicitMinSize : 2; + uint explicitMaxSize : 2; + uint autoFillBackground : 1; + uint nativeChildrenForced : 1; + uint inRenderWithPainter : 1; + uint hasMask : 1; + + // *************************** Platform specific values (bit fields first) ********** +#if defined(Q_WS_WIN) // <----------------------------------------------------------- WIN +#ifndef QT_NO_DRAGANDDROP + QOleDropTarget *dropTarget; // drop target + QList<QPointer<QWidget> > oleDropWidgets; +#endif +#elif defined(Q_WS_X11) // <--------------------------------------------------------- X11 + uint compress_events : 1; + WId xDndProxy; // XDND forwarding to embedded windows +#elif defined(Q_WS_MAC) // <------------------------------------------------------ MAC +#ifdef QT_MAC_USE_COCOA + // Cocoa Mask stuff + QImage maskBits; + CGImageRef imageMask; +#endif +#elif defined(Q_OS_SYMBIAN) // <----------------------------------------------------- Symbian + uint activated : 1; // RWindowBase::Activated has been called + + /** + * If this bit is set, each native widget receives the signals from the + * Symbian control immediately before and immediately after draw ops are + * sent to the window server for this control: + * void beginNativePaintEvent(const QRect &paintRect); + * void endNativePaintEvent(const QRect &paintRect); + */ + uint receiveNativePaintEvents : 1; + + /** + * Defines the behaviour of QSymbianControl::Draw. + */ + enum NativePaintMode { + /** + * Normal drawing mode: blits the required region of the backing store + * via WSERV. + */ + Blit, + + /** + * Disable drawing for this widget. + */ + Disable, + + /** + * Paint zeros into the WSERV framebuffer, using BitGDI APIs. For windows + * with an EColor16MU display mode, zero is written only into the R, G and B + * channels of the pixel. + */ + ZeroFill, + + /** + * Blit backing store, propagating alpha channel into the framebuffer. + */ + BlitWriteAlpha, + + Default = Blit + }; + + NativePaintMode nativePaintMode; + +#endif +}; + +/*! + \internal + + Returns true if \a p or any of its parents enable the + Qt::BypassGraphicsProxyWidget window flag. Used in QWidget::show() and + QWidget::setParent() to determine whether it's necessary to embed the + widget into a QGraphicsProxyWidget or not. +*/ +static inline bool bypassGraphicsProxyWidget(const QWidget *p) +{ + while (p) { + if (p->windowFlags() & Qt::BypassGraphicsProxyWidget) + return true; + p = p->parentWidget(); + } + return false; +} + +class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QWidget) + +public: + // *************************** Cross-platform *************************************** + enum DrawWidgetFlags { + DrawAsRoot = 0x01, + DrawPaintOnScreen = 0x02, + DrawRecursive = 0x04, + DrawInvisible = 0x08, + DontSubtractOpaqueChildren = 0x10, + DontSetCompositionMode = 0x20, + DontDrawOpaqueChildren = 0x40, + DontDrawNativeChildren = 0x80 + }; + + enum CloseMode { + CloseNoEvent, + CloseWithEvent, + CloseWithSpontaneousEvent + }; + + enum Direction { + DirectionNorth = 0x01, + DirectionEast = 0x10, + DirectionSouth = 0x02, + DirectionWest = 0x20 + }; + + // Functions. + explicit QWidgetPrivate(int version = QObjectPrivateVersion); + ~QWidgetPrivate(); + + QWExtra *extraData() const; + QTLWExtra *topData() const; + QTLWExtra *maybeTopData() const; + QPainter *sharedPainter() const; + void setSharedPainter(QPainter *painter); + QWidgetBackingStore *maybeBackingStore() const; + void init(QWidget *desktopWidget, Qt::WindowFlags f); + void create_sys(WId window, bool initializeWindow, bool destroyOldWindow); + void createRecursively(); + void createWinId(WId id = 0); + + void createTLExtra(); + void createExtra(); + void deleteExtra(); + void createSysExtra(); + void deleteSysExtra(); + void createTLSysExtra(); + void deleteTLSysExtra(); + void updateSystemBackground(); + void propagatePaletteChange(); + + void setPalette_helper(const QPalette &); + void resolvePalette(); + QPalette naturalWidgetPalette(uint inheritedMask) const; + + void setMask_sys(const QRegion &); +#ifdef Q_OS_SYMBIAN + void setSoftKeys_sys(const QList<QAction*> &softkeys); + void activateSymbianWindow(WId wid = 0); + void _q_delayedDestroy(WId winId); +#endif + + void raise_sys(); + void lower_sys(); + void stackUnder_sys(QWidget *); + + void setFocus_sys(); + + void updateFont(const QFont &); + inline void setFont_helper(const QFont &font) { + if (data.fnt == font && data.fnt.resolve() == font.resolve()) + return; + updateFont(font); + } + void resolveFont(); + QFont naturalWidgetFont(uint inheritedMask) const; + + void setLayoutDirection_helper(Qt::LayoutDirection); + void resolveLayoutDirection(); + + void setLocale_helper(const QLocale &l, bool forceUpdate = false); + void resolveLocale(); + + void setStyle_helper(QStyle *newStyle, bool propagate, bool metalHack = false); + void inheritStyle(); + + void setUpdatesEnabled_helper(bool ); + + void paintBackground(QPainter *, const QRegion &, int flags = DrawAsRoot) const; + bool isAboutToShow() const; + QRegion prepareToRender(const QRegion ®ion, QWidget::RenderFlags renderFlags); + void render_helper(QPainter *painter, const QPoint &targetOffset, const QRegion &sourceRegion, + QWidget::RenderFlags renderFlags); + void render(QPaintDevice *target, const QPoint &targetOffset, const QRegion &sourceRegion, + QWidget::RenderFlags renderFlags, bool readyToRender); + void drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, + QPainter *sharedPainter = 0, QWidgetBackingStore *backingStore = 0); + + + void paintSiblingsRecursive(QPaintDevice *pdev, const QObjectList& children, int index, + const QRegion &rgn, const QPoint &offset, int flags +#ifdef Q_BACKINGSTORE_SUBSURFACES + , const QWindowSurface *currentSurface +#endif + , QPainter *sharedPainter, QWidgetBackingStore *backingStore); + + + QPainter *beginSharedPainter(); + bool endSharedPainter(); +#ifndef QT_NO_GRAPHICSVIEW + static QGraphicsProxyWidget * nearestGraphicsProxyWidget(const QWidget *origin); +#endif + QWindowSurface *createDefaultWindowSurface(); + QWindowSurface *createDefaultWindowSurface_sys(); + void repaint_sys(const QRegion &rgn); + + QRect clipRect() const; + QRegion clipRegion() const; + void subtractOpaqueChildren(QRegion &rgn, const QRect &clipRect) const; + void subtractOpaqueSiblings(QRegion &source, bool *hasDirtySiblingsAbove = 0, + bool alsoNonOpaque = false) const; + void clipToEffectiveMask(QRegion ®ion) const; + void updateIsOpaque(); + void setOpaque(bool opaque); + void updateIsTranslucent(); + bool paintOnScreen() const; +#ifndef QT_NO_GRAPHICSEFFECT + void invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + const QRegion &getOpaqueChildren() const; + void setDirtyOpaqueRegion(); + + bool close_helper(CloseMode mode); + + void setWindowIcon_helper(); + void setWindowIcon_sys(bool forceReset = false); + void setWindowOpacity_sys(qreal opacity); + void adjustQuitOnCloseAttribute(); + + void scrollChildren(int dx, int dy); + void moveRect(const QRect &, int dx, int dy); + void scrollRect(const QRect &, int dx, int dy); + void invalidateBuffer_resizeHelper(const QPoint &oldPos, const QSize &oldSize); + // ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). + void invalidateBuffer(const QRegion &); + void invalidateBuffer(const QRect &); + bool isOverlapped(const QRect&) const; + void syncBackingStore(); + void syncBackingStore(const QRegion ®ion); + + void reparentFocusWidgets(QWidget *oldtlw); + + static int pointToRect(const QPoint &p, const QRect &r); + + void setWinId(WId); + void showChildren(bool spontaneous); + void hideChildren(bool spontaneous); + void setParent_sys(QWidget *parent, Qt::WindowFlags); + void scroll_sys(int dx, int dy); + void scroll_sys(int dx, int dy, const QRect &r); + void deactivateWidgetCleanup(); + void setGeometry_sys(int, int, int, int, bool); + void sendPendingMoveAndResizeEvents(bool recursive = false, bool disableUpdates = false); + void activateChildLayoutsRecursively(); + void show_recursive(); + void show_helper(); + void show_sys(); + void hide_sys(); + void hide_helper(); + void _q_showIfNotHidden(); + + void setEnabled_helper(bool); + void registerDropSite(bool); + static void adjustFlags(Qt::WindowFlags &flags, QWidget *w = 0); + + void updateFrameStrut(); + QRect frameStrut() const; + +#ifdef QT_KEYPAD_NAVIGATION + static bool navigateToDirection(Direction direction); + static QWidget *widgetInNavigationDirection(Direction direction); + static bool canKeypadNavigate(Qt::Orientation orientation); + static bool inTabWidget(QWidget *widget); +#endif + + void setWindowIconText_sys(const QString &cap); + void setWindowIconText_helper(const QString &cap); + void setWindowTitle_sys(const QString &cap); + +#ifndef QT_NO_CURSOR + void setCursor_sys(const QCursor &cursor); + void unsetCursor_sys(); +#endif + + void setWindowTitle_helper(const QString &cap); + void setWindowFilePath_helper(const QString &filePath); + + bool setMinimumSize_helper(int &minw, int &minh); + bool setMaximumSize_helper(int &maxw, int &maxh); + virtual bool hasHeightForWidth() const; + void setConstraints_sys(); + bool pointInsideRectAndMask(const QPoint &) const; + QWidget *childAt_helper(const QPoint &, bool) const; + QWidget *childAtRecursiveHelper(const QPoint &p, bool, bool includeFrame = false) const; + void updateGeometry_helper(bool forceUpdate); + + void getLayoutItemMargins(int *left, int *top, int *right, int *bottom) const; + void setLayoutItemMargins(int left, int top, int right, int bottom); + void setLayoutItemMargins(QStyle::SubElement element, const QStyleOption *opt = 0); + + // aboutToDestroy() is called just before the contents of + // QWidget::destroy() is executed. It's used to signal QWidget + // sub-classes that their internals are about to be released. + virtual void aboutToDestroy() {} + + QInputContext *assignedInputContext() const; + QInputContext *inputContext() const; + inline QWidget *effectiveFocusWidget() { + QWidget *w = q_func(); + while (w->focusProxy()) + w = w->focusProxy(); + return w; + } + + void setModal_sys(); + + // This is an helper function that return the available geometry for + // a widget and takes care is this one is in QGraphicsView. + // If the widget is not embed in a scene then the geometry available is + // null, we let QDesktopWidget decide for us. + static QRect screenGeometry(const QWidget *widget) + { + QRect screen; +#ifndef QT_NO_GRAPHICSVIEW + QGraphicsProxyWidget *ancestorProxy = widget->d_func()->nearestGraphicsProxyWidget(widget); + //It's embedded if it has an ancestor + if (ancestorProxy) { + if (!bypassGraphicsProxyWidget(widget) && ancestorProxy->scene() != 0) { + // One view, let be smart and return the viewport rect then the popup is aligned + if (ancestorProxy->scene()->views().size() == 1) { + QGraphicsView *view = ancestorProxy->scene()->views().at(0); + screen = view->mapToScene(view->viewport()->rect()).boundingRect().toRect(); + } else { + screen = ancestorProxy->scene()->sceneRect().toRect(); + } + } + } +#endif + return screen; + } + + inline void setRedirected(QPaintDevice *replacement, const QPoint &offset) + { + Q_ASSERT(q_func()->testAttribute(Qt::WA_WState_InPaintEvent)); + redirectDev = replacement; + redirectOffset = offset; + } + + inline QPaintDevice *redirected(QPoint *offset) const + { + if (offset) + *offset = redirectDev ? redirectOffset : QPoint(); + return redirectDev; + } + + inline void restoreRedirected() + { redirectDev = 0; } + + inline void enforceNativeChildren() + { + if (!extra) + createExtra(); + + if (extra->nativeChildrenForced) + return; + extra->nativeChildrenForced = 1; + + for (int i = 0; i < children.size(); ++i) { + if (QWidget *child = qobject_cast<QWidget *>(children.at(i))) + child->setAttribute(Qt::WA_NativeWindow); + } + } + + inline bool nativeChildrenForced() const + { + return extra ? extra->nativeChildrenForced : false; + } + + inline QRect effectiveRectFor(const QRect &rect) const + { +#ifndef QT_NO_GRAPHICSEFFECT + if (graphicsEffect && graphicsEffect->isEnabled()) + return graphicsEffect->boundingRectFor(rect).toAlignedRect(); +#endif //QT_NO_GRAPHICSEFFECT + return rect; + } + + QSize adjustedSize() const; + + inline void handleSoftwareInputPanel(Qt::MouseButton button, bool clickCausedFocus) + { + Q_Q(QWidget); + if (button == Qt::LeftButton && qApp->autoSipEnabled()) { + QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel( + q->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); + if (!clickCausedFocus || behavior == QStyle::RSIP_OnMouseClick) { + QEvent event(QEvent::RequestSoftwareInputPanel); + QApplication::sendEvent(q, &event); + } + } + } + +#ifndef Q_WS_QWS // Almost cross-platform :-) + void setWSGeometry(bool dontShow=false, const QRect &oldRect = QRect()); + + inline QPoint mapToWS(const QPoint &p) const + { return p - data.wrect.topLeft(); } + + inline QPoint mapFromWS(const QPoint &p) const + { return p + data.wrect.topLeft(); } + + inline QRect mapToWS(const QRect &r) const + { QRect rr(r); rr.translate(-data.wrect.topLeft()); return rr; } + + inline QRect mapFromWS(const QRect &r) const + { QRect rr(r); rr.translate(data.wrect.topLeft()); return rr; } +#endif + + // Variables. + // Regular pointers (keep them together to avoid gaps on 64 bit architectures). + QWExtra *extra; + QWidget *focus_next; + QWidget *focus_prev; + QWidget *focus_child; + QLayout *layout; + QRegion *needsFlush; + QPaintDevice *redirectDev; + QWidgetItemV2 *widgetItem; + QPaintEngine *extraPaintEngine; + mutable const QMetaObject *polished; + QGraphicsEffect *graphicsEffect; + // All widgets are added into the allWidgets set. Once + // they receive a window id they are also added to the mapper. + // This should just ensure that all widgets are deleted by QApplication + static QWidgetMapper *mapper; + static QWidgetSet *allWidgets; +#if !defined(QT_NO_IM) + QPointer<QInputContext> ic; + Qt::InputMethodHints imHints; +#endif +#ifdef QT_KEYPAD_NAVIGATION + static QPointer<QWidget> editingWidget; +#endif + + // Implicit pointers (shared_null/shared_empty). + QRegion opaqueChildren; + QRegion dirty; +#ifndef QT_NO_TOOLTIP + QString toolTip; +#endif +#ifndef QT_NO_STATUSTIP + QString statusTip; +#endif +#ifndef QT_NO_WHATSTHIS + QString whatsThis; +#endif +#ifndef QT_NO_ACCESSIBILITY + QString accessibleName; + QString accessibleDescription; +#endif + + // Other variables. + uint inheritedFontResolveMask; + uint inheritedPaletteResolveMask; + short leftmargin; + short topmargin; + short rightmargin; + short bottommargin; + signed char leftLayoutItemMargin; + signed char topLayoutItemMargin; + signed char rightLayoutItemMargin; + signed char bottomLayoutItemMargin; + static int instanceCounter; // Current number of widget instances + static int maxInstances; // Maximum number of widget instances + Qt::HANDLE hd; + QWidgetData data; + QSizePolicy size_policy; + QLocale locale; + QPoint redirectOffset; +#ifndef QT_NO_ACTION + QList<QAction*> actions; +#endif +#ifndef QT_NO_GESTURES + QMap<Qt::GestureType, Qt::GestureFlags> gestureContext; +#endif + + // Bit fields. + uint high_attributes[4]; // the low ones are in QWidget::widget_attributes + QPalette::ColorRole fg_role : 8; + QPalette::ColorRole bg_role : 8; + uint dirtyOpaqueChildren : 1; + uint isOpaque : 1; + uint inDirtyList : 1; + uint isScrolled : 1; + uint isMoved : 1; + uint isGLWidget : 1; + uint usesDoubleBufferedGLContext : 1; +#ifndef QT_NO_IM + uint inheritsInputMethodHints : 1; +#endif + + // *************************** Platform specific ************************************ +#if defined(Q_WS_X11) // <----------------------------------------------------------- X11 + QX11Info xinfo; + Qt::HANDLE picture; + static QWidget *mouseGrabber; + static QWidget *keyboardGrabber; + + void setWindowRole(); + void sendStartupMessage(const char *message) const; + void setNetWmWindowTypes(); + void x11UpdateIsOpaque(); + bool isBackgroundInherited() const; + void updateX11AcceptFocus(); + QPoint mapToGlobal(const QPoint &pos) const; + QPoint mapFromGlobal(const QPoint &pos) const; +#elif defined(Q_WS_WIN) // <--------------------------------------------------------- WIN + uint noPaintOnScreen : 1; // see qwidget_win.cpp ::paintEngine() +#ifndef QT_NO_GESTURES + uint nativeGesturePanEnabled : 1; +#endif + bool shouldShowMaximizeButton(); + void winUpdateIsOpaque(); + void reparentChildren(); +#ifndef QT_NO_DRAGANDDROP + QOleDropTarget *registerOleDnd(QWidget *widget); + void unregisterOleDnd(QWidget *widget, QOleDropTarget *target); +#endif + void grabMouseWhileInWindow(); + void registerTouchWindow(); + void winSetupGestures(); +#elif defined(Q_WS_MAC) // <--------------------------------------------------------- MAC + // This is new stuff + uint needWindowChange : 1; + + // Each wiget keeps a list of all its child and grandchild OpenGL widgets. + // This list is used to update the gl context whenever a parent and a granparent + // moves, and also to check for intersections with gl widgets within the window + // when a widget moves. + struct GlWidgetInfo + { + GlWidgetInfo(QWidget *widget) : widget(widget), lastUpdateWidget(0) { } + bool operator==(const GlWidgetInfo &other) const { return (widget == other.widget); } + QWidget * widget; + QWidget * lastUpdateWidget; + }; + + // dirtyOnWidget contains the areas in the widget that needs to be repained, + // in the same way as dirtyOnScreen does for the window. Areas are added in + // dirtyWidget_sys and cleared in the paint event. In scroll_sys we then use + // this information repaint invalid areas when widgets are scrolled. + QRegion dirtyOnWidget; + EventHandlerRef window_event; + QList<GlWidgetInfo> glWidgets; + + //these are here just for code compat (HIViews) + Qt::HANDLE qd_hd; + + void macUpdateSizeAttribute(); + void macUpdateHideOnSuspend(); + void macUpdateOpaqueSizeGrip(); + void macUpdateIgnoreMouseEvents(); + void macUpdateMetalAttribute(); + void macUpdateIsOpaque(); + void macSetNeedsDisplay(QRegion region); + void setEnabled_helper_sys(bool enable); + bool isRealWindow() const; + void adjustWithinMaxAndMinSize(int &w, int &h); + void applyMaxAndMinSizeOnWindow(); + void update_sys(const QRect &rect); + void update_sys(const QRegion &rgn); + void setGeometry_sys_helper(int, int, int, int, bool); + void setWindowModified_sys(bool b); + void updateMaximizeButton_sys(); + void setWindowFilePath_sys(const QString &filePath); + void createWindow_sys(); + void recreateMacWindow(); +#ifndef QT_MAC_USE_COCOA + void initWindowPtr(); + void finishCreateWindow_sys_Carbon(OSWindowRef windowRef); +#else + void setSubWindowStacking(bool set); + void setWindowLevel(); + void finishCreateWindow_sys_Cocoa(void * /*NSWindow * */ windowRef); + void syncCocoaMask(); + void finishCocoaMaskSetup(); + void syncUnifiedMode(); + // Did we add the drawRectOriginal method? + bool drawRectOriginalAdded; + // Is the original drawRect method available? + bool originalDrawMethod; + // Do we need to change the methods? + bool changeMethods; + + // Unified toolbar variables + bool isInUnifiedToolbar; + QUnifiedToolbarSurface *unifiedSurface; + QPoint toolbar_offset; + QWidget *toolbar_ancestor; + bool flushRequested; + bool touchEventsEnabled; +#endif // QT_MAC_USE_COCOA + void determineWindowClass(); + void transferChildren(); + bool qt_mac_dnd_event(uint, DragRef); + void toggleDrawers(bool); + //mac event functions + static bool qt_create_root_win(); + static void qt_clean_root_win(); + static bool qt_mac_update_sizer(QWidget *, int up = 0); + static OSStatus qt_window_event(EventHandlerCallRef er, EventRef event, void *); + static OSStatus qt_widget_event(EventHandlerCallRef er, EventRef event, void *); + static bool qt_widget_rgn(QWidget *, short, RgnHandle, bool); + void registerTouchWindow(bool enable = true); +#elif defined(Q_WS_QWS) // <--------------------------------------------------------- QWS + void setMaxWindowState_helper(); + void setFullScreenSize_helper(); + void moveSurface(QWindowSurface *surface, const QPoint &offset); + QRegion localRequestedRegion() const; + QRegion localAllocatedRegion() const; + + friend class QWSManager; + friend class QWSManagerPrivate; + friend class QDecoration; +#ifndef QT_NO_CURSOR + void updateCursor() const; +#endif + QScreen* getScreen() const; +#elif defined(Q_WS_QPA) // <--------------------------------------------------------- QPA + void setMaxWindowState_helper(); + void setFullScreenSize_helper(); +#ifndef QT_NO_CURSOR + void updateCursor() const; +#endif +#elif defined(Q_OS_SYMBIAN) // <--------------------------------------------------------- SYMBIAN + static QWidget *mouseGrabber; + static QWidget *keyboardGrabber; + int symbianScreenNumber; // only valid for desktop widget and top-levels + bool fixNativeOrientationCalled; + void s60UpdateIsOpaque(); + void reparentChildren(); + void registerTouchWindow(); +#endif + +}; + +struct QWidgetPaintContext +{ + inline QWidgetPaintContext(QPaintDevice *d, const QRegion &r, const QPoint &o, int f, + QPainter *p, QWidgetBackingStore *b) + : pdev(d), rgn(r), offset(o), flags(f), sharedPainter(p), backingStore(b), painter(0) {} + + QPaintDevice *pdev; + QRegion rgn; + QPoint offset; + int flags; + QPainter *sharedPainter; + QWidgetBackingStore *backingStore; + QPainter *painter; +}; + +#ifndef QT_NO_GRAPHICSEFFECT +class QWidgetEffectSourcePrivate : public QGraphicsEffectSourcePrivate +{ +public: + QWidgetEffectSourcePrivate(QWidget *widget) + : QGraphicsEffectSourcePrivate(), m_widget(widget), context(0), updateDueToGraphicsEffect(false) + {} + + inline void detach() + { m_widget->d_func()->graphicsEffect = 0; } + + inline const QGraphicsItem *graphicsItem() const + { return 0; } + + inline const QWidget *widget() const + { return m_widget; } + + inline void update() + { + updateDueToGraphicsEffect = true; + m_widget->update(); + updateDueToGraphicsEffect = false; + } + + inline bool isPixmap() const + { return false; } + + inline void effectBoundingRectChanged() + { + // ### This function should take a rect parameter; then we can avoid + // updating too much on the parent widget. + if (QWidget *parent = m_widget->parentWidget()) + parent->update(); + else + update(); + } + + inline const QStyleOption *styleOption() const + { return 0; } + + inline QRect deviceRect() const + { return m_widget->window()->rect(); } + + QRectF boundingRect(Qt::CoordinateSystem system) const; + void draw(QPainter *p); + QPixmap pixmap(Qt::CoordinateSystem system, QPoint *offset, + QGraphicsEffect::PixmapPadMode mode) const; + + QWidget *m_widget; + QWidgetPaintContext *context; + QTransform lastEffectTransform; + bool updateDueToGraphicsEffect; +}; +#endif //QT_NO_GRAPHICSEFFECT + +inline QWExtra *QWidgetPrivate::extraData() const +{ + return extra; +} + +inline QTLWExtra *QWidgetPrivate::topData() const +{ + const_cast<QWidgetPrivate *>(this)->createTLExtra(); + return extra->topextra; +} + +inline QTLWExtra *QWidgetPrivate::maybeTopData() const +{ + return extra ? extra->topextra : 0; +} + +inline QPainter *QWidgetPrivate::sharedPainter() const +{ + Q_Q(const QWidget); + QTLWExtra *x = q->window()->d_func()->maybeTopData(); + return x ? x->sharedPainter : 0; +} + +inline void QWidgetPrivate::setSharedPainter(QPainter *painter) +{ + Q_Q(QWidget); + QTLWExtra *x = q->window()->d_func()->topData(); + x->sharedPainter = painter; +} + +inline bool QWidgetPrivate::pointInsideRectAndMask(const QPoint &p) const +{ + Q_Q(const QWidget); + return q->rect().contains(p) && (!extra || !extra->hasMask || q->testAttribute(Qt::WA_MouseNoMask) + || extra->mask.contains(p)); +} + +inline QWidgetBackingStore *QWidgetPrivate::maybeBackingStore() const +{ + Q_Q(const QWidget); + QTLWExtra *x = q->window()->d_func()->maybeTopData(); + return x ? x->backingStore.data() : 0; +} + +QT_END_NAMESPACE + +#endif // QWIDGET_P_H diff --git a/src/gui/kernel/qwidget_qpa.cpp b/src/gui/kernel/qwidget_qpa.cpp new file mode 100644 index 0000000000..001810e0f4 --- /dev/null +++ b/src/gui/kernel/qwidget_qpa.cpp @@ -0,0 +1,875 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "QtGui/qwidget.h" +#include "QtGui/qevent.h" +#include "QtGui/qapplication.h" +#include "QtGui/private/qbackingstore_p.h" +#include "QtGui/private/qwidget_p.h" +#include "QtGui/private/qgraphicssystem_p.h" +#include "QtGui/private/qapplication_p.h" +#include "QtGui/qdesktopwidget.h" +#include "QtGui/qplatformwindow_qpa.h" +#include "QtGui/qplatformglcontext_qpa.h" + +#include <QtGui/QPlatformCursor> + +QT_BEGIN_NAMESPACE + +void q_createNativeChildrenAndSetParent(QPlatformWindow *parentWindow, const QWidget *parentWidget) +{ + QObjectList children = parentWidget->children(); + for (int i = 0; i < children.size(); i++) { + if (children.at(i)->isWidgetType()) { + const QWidget *childWidget = qobject_cast<const QWidget *>(children.at(i)); + if (childWidget) { // should not be necessary + if (childWidget->testAttribute(Qt::WA_NativeWindow)) { + if (!childWidget->platformWindow()) + childWidget->winId(); + } + if (childWidget->platformWindow()) { + childWidget->platformWindow()->setParent(parentWindow); + } else { + q_createNativeChildrenAndSetParent(parentWindow,childWidget); + } + } + } + } + +} + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_Q(QWidget); + + Q_UNUSED(window); + Q_UNUSED(initializeWindow); + Q_UNUSED(destroyOldWindow); + + Qt::WindowFlags flags = data.window_flags; + + if ((!q->testAttribute(Qt::WA_NativeWindow) && !q->isWindow()) || q->windowType() == Qt::Desktop ) + return; // we only care about real toplevels + + QWindowSurface *surface = q->windowSurface(); + QPlatformWindow *platformWindow = q->platformWindow(); + + if (!platformWindow) { + platformWindow = QApplicationPrivate::platformIntegration()->createPlatformWindow(q); + } + Q_ASSERT(platformWindow); + + if (!surface ) { + if (platformWindow && q->platformWindowFormat().hasWindowSurface()) { + surface = QApplicationPrivate::platformIntegration()->createWindowSurface(q,platformWindow->winId()); + } else { + q->setAttribute(Qt::WA_PaintOnScreen,true); + } + } + + data.window_flags = q->platformWindow()->setWindowFlags(data.window_flags); + + setWinId(q->platformWindow()->winId()); + + //first check children. and create them if necessary + q_createNativeChildrenAndSetParent(q->platformWindow(),q); + + //if we we have a parent, then set correct parent; + if (!q->isWindow()) { + if (QWidget *nativeParent = q->nativeParentWidget()) { + if (nativeParent->platformWindow()) { + platformWindow->setParent(nativeParent->platformWindow()); + } + } + } + + QApplicationPrivate::platformIntegration()->moveToScreen(q, topData()->screenIndex); +// qDebug() << "create_sys" << q << q->internalWinId(); +} + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + + if ((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); + + //### we don't have proper focus event handling yet + if (this == QApplicationPrivate::active_window) + QApplication::setActiveWindow(0); + + if (windowType() != Qt::Desktop) { + if (destroySubWindows) { + QObjectList childList(children()); + for (int i = 0; i < childList.size(); i++) { + QWidget *widget = qobject_cast<QWidget *>(childList.at(i)); + if (widget && widget->testAttribute(Qt::WA_NativeWindow)) { + if (widget->platformWindow()) { + widget->destroy(); + } + } + } + } + if (destroyWindow) { + d->deleteTLSysExtra(); + } else { + if (parentWidget() && parentWidget()->testAttribute(Qt::WA_WState_Created)) { + d->hide_sys(); + } + } + } +} + +void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + + Qt::WindowFlags oldFlags = data.window_flags; + + int targetScreen = -1; + // Handle a request to move the widget to a particular screen + if (newparent && newparent->windowType() == Qt::Desktop) { + // make sure the widget is created on the same screen as the + // programmer specified desktop widget + + // get the desktop's screen number + targetScreen = newparent->window()->d_func()->topData()->screenIndex; + newparent = 0; + } + + if (parent != newparent) { + QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function??? + if (q->platformWindow() && newparent) { + QWidget * parentWithWindow = newparent->platformWindow()? newparent : newparent->nativeParentWidget(); + if (parentWithWindow && parentWithWindow->platformWindow()) { + q->platformWindow()->setParent(parentWithWindow->platformWindow()); + } + } + + } + + if (!newparent) { + f |= Qt::Window; + if (targetScreen == -1) { + if (parent) + targetScreen = q->parentWidget()->window()->d_func()->topData()->screenIndex; + } + } + + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + + // Reparenting toplevel to child + if (!(f&Qt::Window) && (oldFlags&Qt::Window) && !q->testAttribute(Qt::WA_NativeWindow)) { + //qDebug() << "setParent_sys() change from toplevel"; + q->destroy(); + } + + data.window_flags = f; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + + if (f & Qt::Window) { + //qDebug() << "setParent_sys" << q << newparent << hex << f; + if (QPlatformWindow *window = q->platformWindow()) + data.window_flags = window->setWindowFlags(data.window_flags); + } + + if (q->isWindow() || (!newparent || newparent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + // move the window to the selected screen + if (!newparent && targetScreen != -1) { + if (maybeTopData()) + maybeTopData()->screenIndex = targetScreen; + // only if it is already created + if (q->testAttribute(Qt::WA_WState_Created)) { + QPlatformIntegration *platform = QApplicationPrivate::platformIntegration(); + platform->moveToScreen(q, targetScreen); + } + } +} + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + int x=pos.x(), y=pos.y(); + const QWidget* w = this; + while (w) { + x += w->data->crect.x(); + y += w->data->crect.y(); + w = w->isWindow() ? 0 : w->parentWidget(); + } + return QPoint(x, y); +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + int x=pos.x(), y=pos.y(); + const QWidget* w = this; + while (w) { + x -= w->data->crect.x(); + y -= w->data->crect.y(); + w = w->isWindow() ? 0 : w->parentWidget(); + } + return QPoint(x, y); +} + +void QWidgetPrivate::updateSystemBackground() {} + +#ifndef QT_NO_CURSOR +void QWidgetPrivate::setCursor_sys(const QCursor &cursor) +{ + Q_UNUSED(cursor); + Q_Q(QWidget); + if (q->isVisible()) + qt_qpa_set_cursor(q, false); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + Q_Q(QWidget); + if (q->isVisible()) + qt_qpa_set_cursor(q, false); +} + +void QWidgetPrivate::updateCursor() const +{ + // XXX +} + +#endif //QT_NO_CURSOR + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + if (!q->isWindow()) + return; + + if (QPlatformWindow *window = q->platformWindow()) + window->setWindowTitle(caption); + +} + +void QWidgetPrivate::setWindowIcon_sys(bool /*forceReset*/) +{ +} + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_UNUSED(iconText); +} + +QWidget *qt_pressGrab = 0; +QWidget *qt_mouseGrb = 0; +static QWidget *keyboardGrb = 0; + +void QWidget::grabMouse() +{ + if (qt_mouseGrb) + qt_mouseGrb->releaseMouse(); + + // XXX + //qwsDisplay()->grabMouse(this,true); + + qt_mouseGrb = this; + qt_pressGrab = 0; +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + Q_UNUSED(cursor); + + if (qt_mouseGrb) + qt_mouseGrb->releaseMouse(); + + // XXX + //qwsDisplay()->grabMouse(this,true); + //qwsDisplay()->selectCursor(this, cursor.handle()); + qt_mouseGrb = this; + qt_pressGrab = 0; +} +#endif + +void QWidget::releaseMouse() +{ + if (qt_mouseGrb == this) { + // XXX + //qwsDisplay()->grabMouse(this,false); + qt_mouseGrb = 0; + } +} + +void QWidget::grabKeyboard() +{ + if (keyboardGrb) + keyboardGrb->releaseKeyboard(); + // XXX + //qwsDisplay()->grabKeyboard(this, true); + keyboardGrb = this; +} + +void QWidget::releaseKeyboard() +{ + if (keyboardGrb == this) { + // XXX + //qwsDisplay()->grabKeyboard(this, false); + keyboardGrb = 0; + } +} + +QWidget *QWidget::mouseGrabber() +{ + if (qt_mouseGrb) + return qt_mouseGrb; + return qt_pressGrab; +} + +QWidget *QWidget::keyboardGrabber() +{ + return keyboardGrb; +} + +void QWidget::activateWindow() +{ + if (platformWindow()) + platformWindow()->requestActivateWindow(); +} + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + q->setAttribute(Qt::WA_Mapped); + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + return; + } + + QApplication::postEvent(q, new QUpdateLaterEvent(q->rect())); + + QPlatformWindow *window = q->platformWindow(); + if (window) { + QRect geomRect = q->geometry(); + if (!q->isWindow()) { + QPoint topLeftOfWindow = q->mapTo(q->nativeParentWidget(),QPoint()); + geomRect.moveTopLeft(topLeftOfWindow); + } + const QRect windowRect = window->geometry(); + if (windowRect != geomRect) { + window->setGeometry(geomRect); + } + if (QWindowSurface *surface = q->windowSurface()) { + if (windowRect.size() != geomRect.size()) { + surface->resize(geomRect.size()); + } + } + if (window) + window->setVisible(true); + } +} + + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + q->setAttribute(Qt::WA_Mapped, false); + if (!q->isWindow()) { + QWidget *p = q->parentWidget(); + if (p &&p->isVisible()) { + invalidateBuffer(q->rect()); + } + return; + } + if (QPlatformWindow *window = q->platformWindow()) { + window->setVisible(false); + } + + //### we don't yet have proper focus event handling + if (q == QApplicationPrivate::active_window) + QApplication::setActiveWindow(0); + +} + +void QWidgetPrivate::setMaxWindowState_helper() +{ + setFullScreenSize_helper(); //### decoration size +} + +void QWidgetPrivate::setFullScreenSize_helper() +{ + Q_Q(QWidget); + + const uint old_state = data.in_set_window_state; + data.in_set_window_state = 1; + + const QRect screen = qApp->desktop()->screenGeometry(qApp->desktop()->screenNumber(q)); + q->move(screen.topLeft()); + q->resize(screen.size()); + + data.in_set_window_state = old_state; +} + +static Qt::WindowStates effectiveState(Qt::WindowStates state) + { + if (state & Qt::WindowMinimized) + return Qt::WindowMinimized; + else if (state & Qt::WindowFullScreen) + return Qt::WindowFullScreen; + else if (state & Qt::WindowMaximized) + return Qt::WindowMaximized; + return Qt::WindowNoState; + } + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + if (isWindow() && !testAttribute(Qt::WA_WState_Created)) + create(); + + data->window_state = newstate; + data->in_set_window_state = 1; + bool needShow = false; + Qt::WindowStates newEffectiveState = effectiveState(newstate); + Qt::WindowStates oldEffectiveState = effectiveState(oldstate); + if (isWindow() && newEffectiveState != oldEffectiveState) { + d->createTLExtra(); + if (oldEffectiveState == Qt::WindowNoState) { //normal + d->topData()->normalGeometry = geometry(); + } else if (oldEffectiveState == Qt::WindowFullScreen) { + setParent(0, d->topData()->savedFlags); + needShow = true; + } else if (oldEffectiveState == Qt::WindowMinimized) { + needShow = true; + } + + if (newEffectiveState == Qt::WindowMinimized) { + //### not ideal... + hide(); + needShow = false; + } else if (newEffectiveState == Qt::WindowFullScreen) { + d->topData()->savedFlags = windowFlags(); + setParent(0, Qt::FramelessWindowHint | (windowFlags() & Qt::WindowStaysOnTopHint)); + d->setFullScreenSize_helper(); + raise(); + needShow = true; + } else if (newEffectiveState == Qt::WindowMaximized) { + createWinId(); + d->setMaxWindowState_helper(); + } else { //normal + QRect r = d->topData()->normalGeometry; + if (r.width() >= 0) { + d->topData()->normalGeometry = QRect(0,0,-1,-1); + setGeometry(r); + } + } + } + data->in_set_window_state = 0; + + if (needShow) + show(); + + if (newstate & Qt::WindowActive) + activateWindow(); + + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} + +void QWidgetPrivate::setFocus_sys() +{ + +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + if (q->isWindow()) { + q->platformWindow()->raise(); + } +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + if (q->isWindow()) { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + q->platformWindow()->lower(); + } else if (QWidget *p = q->parentWidget()) { + setDirtyOpaqueRegion(); + p->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + } +} + +void QWidgetPrivate::stackUnder_sys(QWidget*) +{ + Q_Q(QWidget); + if (QWidget *p = q->parentWidget()) { + setDirtyOpaqueRegion(); + p->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + } +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + if (extra) { // any size restrictions? + w = qMin(w,extra->maxw); + h = qMin(h,extra->maxh); + w = qMax(w,extra->minw); + h = qMax(h,extra->minh); + } + + QPoint oldp = q->geometry().topLeft(); + QSize olds = q->size(); + QRect r(x, y, w, h); + + bool isResize = olds != r.size(); + isMove = oldp != r.topLeft(); //### why do we have isMove as a parameter? + + + // We only care about stuff that changes the geometry, or may + // cause the window manager to change its state + if (r.size() == olds && oldp == r.topLeft()) + return; + + if (!data.in_set_window_state) { + q->data->window_state &= ~Qt::WindowMaximized; + q->data->window_state &= ~Qt::WindowFullScreen; + if (q->isWindow()) + topData()->normalGeometry = QRect(0, 0, -1, -1); + } + + QPoint oldPos = q->pos(); + data.crect = r; + + if (q->isVisible()) { + if (q->platformWindow()) { + if (q->isWindow()) { + q->platformWindow()->setGeometry(q->geometry()); + } else { + QPoint posInNativeParent = q->mapTo(q->nativeParentWidget(),QPoint()); + q->platformWindow()->setGeometry(QRect(posInNativeParent,r.size())); + } + const QWidgetBackingStore *bs = maybeBackingStore(); + if (bs->windowSurface) { + if (isResize) + bs->windowSurface->resize(r.size()); + } + } else { + if (isMove && !isResize) + moveRect(QRect(oldPos, olds), x - oldPos.x(), y - oldPos.y()); + else + invalidateBuffer_resizeHelper(oldPos, olds); + } + + if (isMove) { + QMoveEvent e(q->pos(), oldPos); + QApplication::sendEvent(q, &e); + } + if (isResize) { + QResizeEvent e(r.size(), olds); + QApplication::sendEvent(q, &e); + if (q->platformWindow()) + q->update(); + } + } else { // not visible + if (isMove && q->pos() != oldPos) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } + +} + +void QWidgetPrivate::setConstraints_sys() +{ +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + Q_Q(QWidget); + scrollChildren(dx, dy); + scrollRect(q->rect(), dx, dy); +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r) +{ + scrollRect(r, dx, dy); +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + Q_D(const QWidget); + + QPlatformScreen *screen = QPlatformScreen::platformScreenForWidget(this); + if (!screen) { + if (m == PdmDpiX || m == PdmDpiY) + return 72; + return QPaintDevice::metric(m); + } + int val; + if (m == PdmWidth) { + val = data->crect.width(); + } else if (m == PdmWidthMM) { + val = data->crect.width() * screen->physicalSize().width() / screen->geometry().width(); + } else if (m == PdmHeight) { + val = data->crect.height(); + } else if (m == PdmHeightMM) { + val = data->crect.height() * screen->physicalSize().height() / screen->geometry().height(); + } else if (m == PdmDepth) { + return screen->depth(); + } else if (m == PdmDpiX || m == PdmPhysicalDpiX) { + if (d->extra && d->extra->customDpiX) + return d->extra->customDpiX; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + return qRound(screen->geometry().width() / double(screen->physicalSize().width() / 25.4)); + } else if (m == PdmDpiY || m == PdmPhysicalDpiY) { + if (d->extra && d->extra->customDpiY) + return d->extra->customDpiY; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + return qRound(screen->geometry().height() / double(screen->physicalSize().height() / 25.4)); + } else { + val = QPaintDevice::metric(m);// XXX + } + return val; +} + +/*! + \preliminary + + Sets the window to be the \a window specified. + The QWidget takes ownership of the \a surface. +*/ +void QWidget::setPlatformWindow(QPlatformWindow *window) +{ + Q_D(QWidget); + + QTLWExtra *topData = d->topData(); + if (topData->platformWindow == window) + return; + + delete topData->platformWindow; + topData->platformWindow = window; +} + +/*! + \preliminary + + Returns the QPlatformWindow this widget will be drawn into. +*/ +QPlatformWindow *QWidget::platformWindow() const +{ + Q_D(const QWidget); + QTLWExtra *extra = d->maybeTopData(); + if (extra && extra->platformWindow) + return extra->platformWindow; + + return 0; +} + +void QWidget::setPlatformWindowFormat(const QPlatformWindowFormat &format) +{ + if (isWindow() || testAttribute(Qt::WA_NativeWindow)) { + Q_D(QWidget); + QTLWExtra *topData = d->topData(); + topData->platformWindowFormat = format; + if (testAttribute(Qt::WA_WState_Created)) { + bool wasVisible = testAttribute(Qt::WA_WState_Visible); + destroy(); + d->create_sys(0,true,true); + if (wasVisible) + topData->platformWindow->setVisible(true); + } + } +} + +QPlatformWindowFormat QWidget::platformWindowFormat() const +{ + Q_D(const QWidget); + + QTLWExtra *extra = d->maybeTopData(); + if (extra){ + return extra->platformWindowFormat; + } else { + return QPlatformWindowFormat::defaultFormat(); + } +} + +void QWidgetPrivate::createSysExtra() +{ +} + +void QWidgetPrivate::deleteSysExtra() +{ + +} + +void QWidgetPrivate::createTLSysExtra() +{ +} + +void QWidgetPrivate::deleteTLSysExtra() +{ + if (extra && extra->topextra) { + //the toplevel might have a context with a "qglcontext associated with it. We need to + //delete the qglcontext before we delete the qplatformglcontext. + //One unfortunate thing about this is that we potentially create a glContext just to + //delete it straight afterwards. + if (extra->topextra->platformWindow) { + if (QPlatformGLContext *context = extra->topextra->platformWindow->glContext()) { + context->deleteQGLContext(); + } + } + setWinId(0); + delete extra->topextra->platformWindow; + extra->topextra->platformWindow = 0; + } +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_UNUSED(on); +} + +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_UNUSED(region); + // XXX +} + +void QWidgetPrivate::updateFrameStrut() +{ + // XXX +} + +void QWidgetPrivate::setWindowOpacity_sys(qreal level) +{ + Q_Q(QWidget); + q->platformWindow()->setOpacity(level); +} + +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &oldRect) +{ + Q_UNUSED(dontShow); + Q_UNUSED(oldRect); + // XXX +} + +QPaintEngine *QWidget::paintEngine() const +{ + qWarning("QWidget::paintEngine: Should no longer be called"); + return 0; //##### @@@ +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + Q_Q(QWidget); + if (q->platformWindowFormat().hasWindowSurface()) + return QApplicationPrivate::platformIntegration()->createWindowSurface(q,0); + else + return 0; +} + +void QWidgetPrivate::setModal_sys() +{ +} + +#ifndef QT_NO_CURSOR +void qt_qpa_set_cursor(QWidget * w, bool force) +{ + static QCursor arrowCursor(Qt::ArrowCursor); + static QPointer<QWidget> lastUnderMouse = 0; + + QCursor * override = QApplication::overrideCursor(); + + if (override && w != 0) + return; + + QWidget *cursorWidget; + QCursor cursorCursor; + + do { + if (w == 0) { + if (override) { + cursorCursor = *override; + cursorWidget = QApplication::topLevelAt(QCursor::pos()); + break; + } + w = QApplication::widgetAt(QCursor::pos()); + if (w == 0) // clear the override cursor while over empty space + w = QApplication::desktop(); + } else if (force) { + lastUnderMouse = w; + } else if (w->testAttribute(Qt::WA_WState_Created) && lastUnderMouse + && lastUnderMouse->effectiveWinId() == w->effectiveWinId()) { + w = lastUnderMouse; + } + if (w == QApplication::desktop() && !override) { + cursorCursor = arrowCursor; + cursorWidget = w; + break; + } + + QWidget * curWin = QApplication::activeWindow(); + if (!curWin && w && w->internalWinId()) + return; + QWidget* cW = w && !w->internalWinId() ? w : curWin; + + if (!cW || cW->window() != w->window() || + !cW->isVisible() || !cW->underMouse() || override) + return; + + cursorCursor = w->cursor(); + cursorWidget = w; + } while (0); + foreach (QWeakPointer<QPlatformCursor> cursor, QPlatformCursorPrivate::getInstances()) + if (cursor) + cursor.data()->changeCursor(&cursorCursor, cursorWidget); +} +#endif //QT_NO_CURSOR + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidget_qws.cpp b/src/gui/kernel/qwidget_qws.cpp new file mode 100644 index 0000000000..86ebc04165 --- /dev/null +++ b/src/gui/kernel/qwidget_qws.cpp @@ -0,0 +1,1221 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcursor.h" +#include "qapplication.h" +#include "qapplication_p.h" +#include "qpainter.h" +#include "qbitmap.h" +#include "qimage.h" +#include "qhash.h" +#include "qstack.h" +#include "qlayout.h" +#include "qtextcodec.h" +#include "qinputcontext.h" +#include "qdesktopwidget.h" + +#include "qwsdisplay_qws.h" +#include "private/qwsdisplay_qws_p.h" +#include "qscreen_qws.h" +#include "qwsmanager_qws.h" +#include <private/qwsmanager_p.h> +#include <private/qbackingstore_p.h> +#include <private/qwindowsurface_qws_p.h> +#include <private/qwslock_p.h> +#include "qpaintengine.h" + +#include "qdebug.h" + +#include "qwidget_p.h" + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +extern int *qt_last_x; +extern int *qt_last_y; +extern WId qt_last_cursor; +extern bool qws_overrideCursor; +extern QWidget *qt_pressGrab; +extern QWidget *qt_mouseGrb; + +static QWidget *keyboardGrb = 0; + +static int takeLocalId() +{ + static int n=-1000; + return --n; +} + +class QWSServer; +extern QWSServer *qwsServer; + +static inline bool isServerProcess() +{ + return (qwsServer != 0); +} + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool /*destroyOldWindow*/) +{ + Q_Q(QWidget); + Qt::WindowType type = q->windowType(); + + // Make sure the WindowTitleHint is on if any of the title bar hints are set + // Note: This might be moved to cross-platform QWidgetPrivate::adjustFlags() + if ( !(data.window_flags & Qt::CustomizeWindowHint) && ( + (data.window_flags & Qt::WindowSystemMenuHint) || + (data.window_flags & Qt::WindowContextHelpButtonHint) || + (data.window_flags & Qt::WindowMinimizeButtonHint) || + (data.window_flags & Qt::WindowMaximizeButtonHint) || + (data.window_flags & Qt::WindowCloseButtonHint) ) ) { + data.window_flags |= Qt::WindowTitleHint; + } + + // Decoration plugins on QWS don't support switching on the close button on its own + if (data.window_flags & Qt::WindowCloseButtonHint) + data.window_flags |= Qt::WindowSystemMenuHint; + + Qt::WindowFlags flags = data.window_flags; + + data.alloc_region_index = -1; + + // we don't have a "Drawer" window type + if (type == Qt::Drawer) { + type = Qt::Widget; + flags &= ~Qt::WindowType_Mask; + } + + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + bool tool = (type == Qt::Tool || type == Qt::SplashScreen || type == Qt::ToolTip); + + +#ifndef QT_NO_WARNING_OUTPUT + static bool toolWarningShown = false; + if (!toolWarningShown && type == Qt::Tool && !(flags & Qt::FramelessWindowHint)) { + qWarning("Qt for Embedded Linux " QT_VERSION_STR " does not support tool windows with frames.\n" + "This behavior will change in a later release. To ensure compatibility with\n" + "future versions, use (Qt::Tool | Qt::FramelessWindowHint)."); + toolWarningShown = true; + } +#endif + + WId id; + QWSDisplay* dpy = QWidget::qwsDisplay(); + + if (!window) // always initialize + initializeWindow = true; + + // use the size of the primary screen to determine the default window size + QList<QScreen *> screens = qt_screen->subScreens(); + if (screens.isEmpty()) + screens.append(qt_screen); + int sw = screens[0]->width(); + int sh = screens[0]->height(); + + if (desktop) { // desktop widget + dialog = popup = false; // force these flags off + data.crect.setRect(0, 0, sw, sh); + } else if (topLevel && !q->testAttribute(Qt::WA_Resized)) { + int width = sw / 2; + int height = 4 * sh / 10; + if (extra) { + width = qMax(qMin(width, extra->maxw), extra->minw); + height = qMax(qMin(height, extra->maxh), extra->minh); + } + data.crect.setSize(QSize(width, height)); + } + + if (window) { // override the old window + id = window; + setWinId(window); + } else if (desktop) { // desktop widget + id = (WId)-2; // id = root window +#if 0 + QWidget *otherDesktop = q->find(id); // is there another desktop? + if (otherDesktop && otherDesktop->testWFlags(Qt::WPaintDesktop)) { + otherDesktop->d_func()->setWinId(0); // remove id from widget mapper + setWinId(id); // make sure otherDesktop is + otherDesktop->d_func()->setWinId(id); // found first + } else +#endif + { + setWinId(id); + } + } else { + id = topLevel ? dpy->takeId() : takeLocalId(); + setWinId(id); // set widget id/handle + hd + } + + + bool hasFrame = true; + if (topLevel) { + if (desktop || popup || tool || q->testAttribute(Qt::WA_DontShowOnScreen)) + hasFrame = false; + else + hasFrame = !(flags & Qt::FramelessWindowHint); + } + if (desktop) { + q->setAttribute(Qt::WA_WState_Visible); + } else if (topLevel) { // set X cursor + //QCursor *oc = QApplication::overrideCursor(); + if (initializeWindow) { + //XXX XDefineCursor(dpy, winid, oc ? oc->handle() : cursor().handle()); + } + QWidget::qwsDisplay()->nameRegion(q->internalWinId(), q->objectName(), q->windowTitle()); + } + + if (topLevel) { + createTLExtra(); + QTLWExtra *topextra = extra->topextra; +#ifndef QT_NO_QWS_MANAGER + if (hasFrame) { + // get size of wm decoration and make the old crect the new frect + QRect cr = data.crect; + QRegion r = QApplication::qwsDecoration().region(q, cr) | cr; + QRect br(r.boundingRect()); + topextra->frameStrut.setCoords(cr.x() - br.x(), + cr.y() - br.y(), + br.right() - cr.right(), + br.bottom() - cr.bottom()); + if (!q->testAttribute(Qt::WA_Moved) || topextra->posFromMove) + data.crect.translate(topextra->frameStrut.left(), topextra->frameStrut.top()); + if (!topData()->qwsManager) { + topData()->qwsManager = new QWSManager(q); + if((q->data->window_state & ~Qt::WindowActive) == Qt::WindowMaximized) + topData()->qwsManager->maximize(); + } + + } else if (topData()->qwsManager) { + delete topData()->qwsManager; + topData()->qwsManager = 0; + data.crect.translate(-topextra->frameStrut.left(), -topextra->frameStrut.top()); + topextra->frameStrut.setCoords(0, 0, 0, 0); + } +#endif + if (!topextra->caption.isEmpty()) + setWindowTitle_helper(topextra->caption); + + //XXX If we are session managed, inform the window manager about it + } else { + if (extra && extra->topextra) { // already allocated due to reparent? + extra->topextra->frameStrut.setCoords(0, 0, 0, 0); + } + //updateRequestedRegion(mapToGlobal(QPoint(0,0))); + } +} + + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(d->effectiveRectFor(geometry())); + + d->deactivateWidgetCleanup(); + if (testAttribute(Qt::WA_WState_Created)) { + setAttribute(Qt::WA_WState_Created, false); + QObjectList childObjects = children(); + for (int i = 0; i < childObjects.size(); ++i) { + QObject *obj = childObjects.at(i); + if (obj->isWidgetType()) + static_cast<QWidget*>(obj)->destroy(destroySubWindows, + destroySubWindows); + } + releaseMouse(); + if (qt_pressGrab == this) + qt_pressGrab = 0; + + if (keyboardGrb == this) + releaseKeyboard(); + if (testAttribute(Qt::WA_ShowModal)) // just be sure we leave modal + QApplicationPrivate::leaveModal(this); + else if ((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); +#ifndef QT_NO_IM + if (d->ic) { + delete d->ic; + d->ic =0; + } else { + // release previous focus information participating with + // preedit preservation of qic -- while we still have a winId + QInputContext *qic = QApplicationPrivate::inputContext; + if (qic) + qic->widgetDestroyed(this); + } +#endif //QT_NO_IM + + if ((windowType() == Qt::Desktop)) { + } else { + if (parentWidget() && parentWidget()->testAttribute(Qt::WA_WState_Created)) { + d->hide_sys(); + } + if (destroyWindow && isWindow()) { + if (d->extra && d->extra->topextra && d->extra->topextra->backingStore) + d->extra->topextra->backingStore->windowSurface->setGeometry(QRect()); + qwsDisplay()->destroyRegion(internalWinId()); + } + } + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + } +} + + +void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); +#ifndef QT_NO_CURSOR + QCursor oldcurs; + bool setcurs=q->testAttribute(Qt::WA_SetCursor); + if (setcurs) { + oldcurs = q->cursor(); + q->unsetCursor(); + } +#endif + + WId old_winid = data.winid; + if ((q->windowType() == Qt::Desktop)) + old_winid = 0; + + if (!q->isWindow() && q->parentWidget() && q->parentWidget()->testAttribute(Qt::WA_WState_Created)) + hide_sys(); + + setWinId(0); + + if (parent != newparent) { + QWidget *oldparent = q->parentWidget(); + QObjectPrivate::setParent_helper(newparent); + if (oldparent) { +// oldparent->d_func()->setChildrenAllocatedDirty(); +// oldparent->data->paintable_region_dirty = true; + } + if (newparent) { +// newparent->d_func()->setChildrenAllocatedDirty(); +// newparent->data->paintable_region_dirty = true; + //@@@@@@@ + } + } + Qt::FocusPolicy fp = q->focusPolicy(); + QSize s = q->size(); + //QBrush bgc = background(); // save colors + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + + data.window_flags = f; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state + // (but we recreate the winId for the widget being reparented, again for compatibility) + if (wasCreated || (!q->isWindow() && newparent->testAttribute(Qt::WA_WState_Created))) + createWinId(); + if (q->isWindow() || (!newparent || newparent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (q->isWindow()) { + QRect fs = frameStrut(); + data.crect = QRect(fs.left(), fs.top(), s.width(), s.height()); + if ((data.window_flags & Qt::FramelessWindowHint) && extra && extra->topextra) + extra->topextra->frameStrut.setCoords(0, 0, 0, 0); + } else { + data.crect = QRect(0, 0, s.width(), s.height()); + } + + q->setFocusPolicy(fp); + if (extra && !extra->mask.isEmpty()) { + QRegion r = extra->mask; + extra->mask = QRegion(); + q->setMask(r); + } + if ((int)old_winid > 0) { + QWidget::qwsDisplay()->destroyRegion(old_winid); + extra->topextra->backingStore->windowSurface->setGeometry(QRect()); + } +#ifndef QT_NO_CURSOR + if (setcurs) { + q->setCursor(oldcurs); + } +#endif +} + + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + int x=pos.x(), y=pos.y(); + const QWidget* w = this; + while (w) { + x += w->data->crect.x(); + y += w->data->crect.y(); + w = w->isWindow() ? 0 : w->parentWidget(); + } + return QPoint(x, y); +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + int x=pos.x(), y=pos.y(); + const QWidget* w = this; + while (w) { + x -= w->data->crect.x(); + y -= w->data->crect.y(); + w = w->isWindow() ? 0 : w->parentWidget(); + } + return QPoint(x, y); +} + +#if 0 // ##### +void QWidget::setMicroFocusHint(int x, int y, int width, int height, + bool text, QFont *) +{ + if (QRect(x, y, width, height) != microFocusHint()) { + d->createExtra(); + d->extra->micro_focus_hint.setRect(x, y, width, height); + } +#ifndef QT_NO_QWS_INPUTMETHODS + if (text) { + QWidget *tlw = window(); + int winid = tlw->internalWinId(); + QPoint p(x, y + height); + QPoint gp = mapToGlobal(p); + + QRect r = QRect(mapToGlobal(QPoint(0,0)), + size()); + + r.setBottom(tlw->geometry().bottom()); + + //qDebug("QWidget::setMicroFocusHint %d %d %d %d", r.x(), + // r.y(), r.width(), r.height()); + QInputContext::setMicroFocusWidget(this); + + qwsDisplay()->setIMInfo(winid, gp.x(), gp.y(), r); + + //send font info, ###if necessary + qwsDisplay()->setInputFont(winid, font()); + } +#endif +} +#endif + +void QWidgetPrivate::updateSystemBackground() {} + +#ifndef QT_NO_CURSOR +void QWidgetPrivate::setCursor_sys(const QCursor &cursor) +{ + Q_UNUSED(cursor); + Q_Q(QWidget); + if (q->isVisible()) + updateCursor(); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + Q_Q(QWidget); + if (q->isVisible()) + updateCursor(); +} +#endif //QT_NO_CURSOR + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + QWidget::qwsDisplay()->setWindowCaption(q, caption); +} + +void QWidgetPrivate::setWindowIcon_sys(bool /*forceReset*/) +{ +#if 0 + QTLWExtra* x = d->topData(); + delete x->icon; + x->icon = 0; + QBitmap mask; + if (unscaledPixmap.isNull()) { + } else { + QImage unscaledIcon = unscaledPixmap.toImage(); + QPixmap pixmap = + QPixmap::fromImage(unscaledIcon.scale(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + x->icon = new QPixmap(pixmap); + mask = pixmap.mask() ? *pixmap.mask() : pixmap.createHeuristicMask(); + } +#endif +} + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_UNUSED(iconText); +} + +void QWidget::grabMouse() +{ + if (qt_mouseGrb) + qt_mouseGrb->releaseMouse(); + + qwsDisplay()->grabMouse(this,true); + + qt_mouseGrb = this; + qt_pressGrab = 0; +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if (qt_mouseGrb) + qt_mouseGrb->releaseMouse(); + + qwsDisplay()->grabMouse(this,true); + qwsDisplay()->selectCursor(this, cursor.handle()); + qt_mouseGrb = this; + qt_pressGrab = 0; +} +#endif + +void QWidget::releaseMouse() +{ + if (qt_mouseGrb == this) { + qwsDisplay()->grabMouse(this,false); + qt_mouseGrb = 0; + } +} + +void QWidget::grabKeyboard() +{ + if (keyboardGrb) + keyboardGrb->releaseKeyboard(); + qwsDisplay()->grabKeyboard(this, true); + keyboardGrb = this; +} + +void QWidget::releaseKeyboard() +{ + if (keyboardGrb == this) { + qwsDisplay()->grabKeyboard(this, false); + keyboardGrb = 0; + } +} + + +QWidget *QWidget::mouseGrabber() +{ + if (qt_mouseGrb) + return qt_mouseGrb; + return qt_pressGrab; +} + + +QWidget *QWidget::keyboardGrabber() +{ + return keyboardGrb; +} + +void QWidget::activateWindow() +{ + QWidget *tlw = window(); + if (tlw->isVisible()) { + Q_ASSERT(tlw->testAttribute(Qt::WA_WState_Created)); + qwsDisplay()->requestFocus(tlw->internalWinId(), true); + } +} + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + q->setAttribute(Qt::WA_Mapped); + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + return; + } + + if (q->isWindow()) { + + + if (!q->testAttribute(Qt::WA_ShowWithoutActivating) + && q->windowType() != Qt::Popup + && q->windowType() != Qt::Tool + && q->windowType() != Qt::ToolTip) { + QWidget::qwsDisplay()->requestFocus(data.winid,true); + } + + + if (QWindowSurface *surface = q->windowSurface()) { + const QRect frameRect = q->frameGeometry(); + if (surface->geometry() != frameRect) + surface->setGeometry(frameRect); + } + + QRegion r = localRequestedRegion(); +#ifndef QT_NO_QWS_MANAGER + if (extra && extra->topextra && extra->topextra->qwsManager) { + r.translate(data.crect.topLeft()); + r += extra->topextra->qwsManager->region(); + r.translate(-data.crect.topLeft()); + } +#endif + data.fstrut_dirty = true; + invalidateBuffer(r); + bool staysontop = + (q->windowFlags() & Qt::WindowStaysOnTopHint) + || q->windowType() == Qt::Popup; + if (!staysontop && q->parentWidget()) { // if our parent stays on top, so must we + QWidget *ptl = q->parentWidget()->window(); + if (ptl && (ptl->windowFlags() & Qt::WindowStaysOnTopHint)) + staysontop = true; + } + + QWSChangeAltitudeCommand::Altitude altitude; + altitude = staysontop ? QWSChangeAltitudeCommand::StaysOnTop : QWSChangeAltitudeCommand::Raise; + QWidget::qwsDisplay()->setAltitude(data.winid, altitude, true); + if (!q->objectName().isEmpty()) { + QWidget::qwsDisplay()->setWindowCaption(q, q->windowTitle()); + } + } +#ifdef Q_BACKINGSTORE_SUBSURFACES + else if ( extra && extra->topextra && extra->topextra->windowSurface) { + QWSWindowSurface *surface; + surface = static_cast<QWSWindowSurface*>(q->windowSurface()); + const QPoint p = q->mapToGlobal(QPoint()); + surface->setGeometry(QRect(p, q->size())); + } +#endif + + if (!q->window()->data->in_show) { + invalidateBuffer(q->rect()); + } +} + + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + deactivateWidgetCleanup(); + + if (q->isWindow()) { + q->releaseMouse(); +// requestWindowRegion(QRegion()); + + if (extra->topextra->backingStore) + extra->topextra->backingStore->releaseBuffer(); + + + QWidget::qwsDisplay()->requestFocus(data.winid,false); + } else { + QWidget *p = q->parentWidget(); + if (p &&p->isVisible()) { + invalidateBuffer(q->rect()); + } + } +} + + + +static Qt::WindowStates effectiveState(Qt::WindowStates state) + { + if (state & Qt::WindowMinimized) + return Qt::WindowMinimized; + else if (state & Qt::WindowFullScreen) + return Qt::WindowFullScreen; + else if (state & Qt::WindowMaximized) + return Qt::WindowMaximized; + return Qt::WindowNoState; + } + +void QWidgetPrivate::setMaxWindowState_helper() +{ + // in_set_window_state is usually set in setWindowState(), but this + // function is used in other functions as well + // (e.g QApplicationPrivate::setMaxWindowRect()) + const uint old_state = data.in_set_window_state; + data.in_set_window_state = 1; + +#ifndef QT_NO_QWS_MANAGER + if (extra && extra->topextra && extra->topextra->qwsManager) + extra->topextra->qwsManager->maximize(); + else +#endif + { + Q_Q(QWidget); + const QDesktopWidget *desktop = QApplication::desktop(); + const int screen = desktop->screenNumber(q); + const QRect maxWindowRect = desktop->availableGeometry(screen); + q->setGeometry(maxWindowRect); + } + data.in_set_window_state = old_state; +} + +void QWidgetPrivate::setFullScreenSize_helper() +{ + Q_Q(QWidget); + + const uint old_state = data.in_set_window_state; + data.in_set_window_state = 1; + + const QRect screen = qApp->desktop()->screenGeometry(qApp->desktop()->screenNumber(q)); + q->move(screen.topLeft()); + q->resize(screen.size()); + + data.in_set_window_state = old_state; +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + if (isWindow() && !testAttribute(Qt::WA_WState_Created)) + create(); + + data->window_state = newstate; + data->in_set_window_state = 1; + bool needShow = false; + Qt::WindowStates newEffectiveState = effectiveState(newstate); + Qt::WindowStates oldEffectiveState = effectiveState(oldstate); + if (isWindow() && newEffectiveState != oldEffectiveState) { + d->createTLExtra(); + if (oldEffectiveState == Qt::WindowNoState) { //normal + d->topData()->normalGeometry = geometry(); + } else if (oldEffectiveState == Qt::WindowFullScreen) { + setParent(0, d->topData()->savedFlags); + needShow = true; + } else if (oldEffectiveState == Qt::WindowMinimized) { + needShow = true; + } + + if (newEffectiveState == Qt::WindowMinimized) { + //### not ideal... + hide(); + needShow = false; + } else if (newEffectiveState == Qt::WindowFullScreen) { + d->topData()->savedFlags = windowFlags(); + setParent(0, Qt::FramelessWindowHint | (windowFlags() & Qt::WindowStaysOnTopHint)); + d->setFullScreenSize_helper(); + raise(); + needShow = true; + } else if (newEffectiveState == Qt::WindowMaximized) { + createWinId(); + d->setMaxWindowState_helper(); + } else { //normal + QRect r = d->topData()->normalGeometry; + if (r.width() >= 0) { + d->topData()->normalGeometry = QRect(0,0,-1,-1); + setGeometry(r); + } + } + } + data->in_set_window_state = 0; + + if (needShow) + show(); + + if (newstate & Qt::WindowActive) + activateWindow(); + + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} + +void QWidgetPrivate::setFocus_sys() +{ + +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + //@@@ transaction + if (q->isWindow()) { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + QWidget::qwsDisplay()->setAltitude(q->internalWinId(), + QWSChangeAltitudeCommand::Raise); + // XXX: subsurfaces? +#ifdef QT_NO_WINDOWGROUPHINT +#else + QObjectList childObjects = q->children(); + if (!childObjects.isEmpty()) { + QWidgetList toraise; + for (int i = 0; i < childObjects.size(); ++i) { + QObject *obj = childObjects.at(i); + if (obj->isWidgetType()) { + QWidget* w = static_cast<QWidget*>(obj); + if (w->isWindow()) + toraise.append(w); + } + } + + for (int i = 0; i < toraise.size(); ++i) { + QWidget *w = toraise.at(i); + if (w->isVisible()) + w->raise(); + } + } +#endif // QT_NO_WINDOWGROUPHINT + } +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + if (q->isWindow()) { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + QWidget::qwsDisplay()->setAltitude(data.winid, + QWSChangeAltitudeCommand::Lower); + } else if (QWidget *p = q->parentWidget()) { + setDirtyOpaqueRegion(); + p->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + } +} + +void QWidgetPrivate::stackUnder_sys(QWidget*) +{ + Q_Q(QWidget); + if (QWidget *p = q->parentWidget()) { + setDirtyOpaqueRegion(); + p->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + } +} + +void QWidgetPrivate::moveSurface(QWindowSurface *surface, const QPoint &offset) +{ + QWSWindowSurface *s = static_cast<QWSWindowSurface*>(surface); + if (!s->move(offset)) + s->invalidateBuffer(); + + QWSDisplay::instance()->moveRegion(s->winId(), offset.x(), offset.y()); +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + if (extra) { // any size restrictions? + w = qMin(w,extra->maxw); + h = qMin(h,extra->maxh); + w = qMax(w,extra->minw); + h = qMax(h,extra->minh); + } + + QPoint oldp = q->geometry().topLeft(); + QSize olds = q->size(); + QRect r(x, y, w, h); + + bool isResize = olds != r.size(); + isMove = oldp != r.topLeft(); //### why do we have isMove as a parameter? + + // We only care about stuff that changes the geometry, or may + // cause the window manager to change its state + if (r.size() == olds && oldp == r.topLeft()) + return; + + if (!data.in_set_window_state) { + q->data->window_state &= ~Qt::WindowMaximized; + q->data->window_state &= ~Qt::WindowFullScreen; + if (q->isWindow()) + topData()->normalGeometry = QRect(0, 0, -1, -1); + } + QPoint oldPos = q->pos(); + data.crect = r; + + if ((q->windowType() == Qt::Desktop)) + return; + + if (q->isVisible()) { + + bool toplevelMove = false; + QWSWindowSurface *surface = 0; + + if (q->isWindow()) { + //### ConfigPending not implemented, do we need it? + //setAttribute(Qt::WA_WState_ConfigPending); + const QWidgetBackingStore *bs = maybeBackingStore(); + if (bs) + surface = static_cast<QWSWindowSurface*>(bs->windowSurface); + if (isMove && !isResize && surface) { + const QPoint offset(x - oldp.x(), y - oldp.y()); + moveSurface(surface, offset); + toplevelMove = true; //server moves window, but we must send moveEvent, which might trigger painting + +#ifdef Q_BACKINGSTORE_SUBSURFACES + QList<QWindowSurface*> surfaces = bs->subSurfaces; + for (int i = 0; i < surfaces.size(); ++i) + moveSurface(surfaces.at(i), offset); +#endif + } else { + updateFrameStrut(); + } + } + + if (!toplevelMove) { + if (q->isWindow()) { + if (surface) + surface->setGeometry(q->frameGeometry()); + else + invalidateBuffer(q->rect()); //### + +#ifdef Q_BACKINGSTORE_SUBSURFACES + // XXX: should not resize subsurfaces. Children within a layout + // will be resized automatically while children with a static + // geometry should get a new clip region instead. + const QRect clipRect = q->geometry(); + QWidgetBackingStore *bs = maybeBackingStore(); + QList<QWindowSurface*> surfaces = bs->subSurfaces; + for (int i = 0; i < surfaces.size(); ++i) { + QWSWindowSurface *s = static_cast<QWSWindowSurface*>(surfaces.at(i)); + QRect srect = s->geometry(); + s->setGeometry(clipRect & srect); + } +#endif + } +#ifdef Q_BACKINGSTORE_SUBSURFACES + // XXX: merge this case with the isWindow() case + else if (maybeTopData() && maybeTopData()->windowSurface) { + QWSWindowSurface *surface; + surface = static_cast<QWSWindowSurface*>(q->windowSurface()); + if (isMove && !isResize) { + moveSurface(surface, QPoint(x - oldp.x(), y - oldp.y())); + } else { + const QPoint p = q->mapToGlobal(QPoint()); + surface->setGeometry(QRect(p, QSize(w, h))); + } + } +#endif + else { + if (isMove && !isResize) + moveRect(QRect(oldPos, olds), x - oldPos.x(), y - oldPos.y()); + else + invalidateBuffer_resizeHelper(oldPos, olds); + } + } + + //### must have frame geometry correct before sending move/resize events + if (isMove) { + QMoveEvent e(q->pos(), oldPos); + QApplication::sendEvent(q, &e); + } + if (isResize) { + QResizeEvent e(r.size(), olds); + QApplication::sendEvent(q, &e); + } + + } else { // not visible + if (isMove && q->pos() != oldPos) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } +} + +void QWidgetPrivate::setConstraints_sys() +{ +} + +QScreen* QWidgetPrivate::getScreen() const +{ + Q_Q(const QWidget); + + const QList<QScreen*> subScreens = qt_screen->subScreens(); + if (subScreens.isEmpty() || q->windowType() == Qt::Desktop) + return qt_screen; + + const int screen = QApplication::desktop()->screenNumber(q); + + return qt_screen->subScreens().at(screen < 0 ? 0 : screen); +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + Q_Q(QWidget); + scrollChildren(dx, dy); + scrollRect(q->rect(), dx, dy); +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r) +{ + scrollRect(r, dx, dy); +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + Q_D(const QWidget); + + int val; + if (m == PdmWidth) { + val = data->crect.width(); + } else if (m == PdmWidthMM) { + const QScreen *screen = d->getScreen(); + val = data->crect.width() * screen->physicalWidth() / screen->width(); + } else if (m == PdmHeight) { + val = data->crect.height(); + } else if (m == PdmHeightMM) { + const QScreen *screen = d->getScreen(); + val = data->crect.height() * screen->physicalHeight() / screen->height(); + } else if (m == PdmDepth) { + return qwsDisplay()->depth(); + } else if (m == PdmDpiX || m == PdmPhysicalDpiX) { + if (d->extra && d->extra->customDpiX) + return d->extra->customDpiX; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + const QScreen *screen = d->getScreen(); + return qRound(screen->width() / double(screen->physicalWidth() / 25.4)); + } else if (m == PdmDpiY || m == PdmPhysicalDpiY) { + if (d->extra && d->extra->customDpiY) + return d->extra->customDpiY; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + const QScreen *screen = d->getScreen(); + return qRound(screen->height() / double(screen->physicalHeight() / 25.4)); + } else if (m == PdmNumColors) { + QScreen *screen = d->getScreen(); + int ret = screen->colorCount(); + if (!ret) { + const int depth = qwsDisplay()->depth(); + switch (depth) { + case 1: + ret = 2; + break; + case 8: + ret = 256; + break; + case 16: + ret = 65536; + break; + case 24: + ret = 16777216; + break; + case 32: + ret = 2147483647; + break; + } + } + return ret; + } else { + val = QPaintDevice::metric(m);// XXX + } + return val; +} + +void QWidgetPrivate::createSysExtra() +{ +} + +void QWidgetPrivate::deleteSysExtra() +{ +} + +void QWidgetPrivate::createTLSysExtra() +{ +#ifndef QT_NO_QWS_MANAGER + extra->topextra->qwsManager = 0; +#endif +} + +void QWidgetPrivate::deleteTLSysExtra() +{ +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_UNUSED(on); +} + +QRegion QWidgetPrivate::localRequestedRegion() const +{ + Q_Q(const QWidget); + QRegion r(q->rect()); + if (extra && !extra->mask.isEmpty()) + r &= extra->mask; + + return r; +} + +QRegion QWidgetPrivate::localAllocatedRegion() const +{ + Q_Q(const QWidget); + + QWidgetBackingStore *wbs = q->window()->d_func()->maybeBackingStore(); + + QWindowSurface *ws = wbs ? wbs->windowSurface : 0; + if (!ws) + return QRegion(); + QRegion r = static_cast<QWSWindowSurface*>(ws)->clipRegion(); + if (!q->isWindow()) { + QPoint off = q->mapTo(q->window(), QPoint()); + r &= localRequestedRegion().translated(off); + r.translate(-off); + } + return r; +} + +inline bool QRect::intersects(const QRect &r) const +{ + return (qMax(x1, r.x1) <= qMin(x2, r.x2) && + qMax(y1, r.y1) <= qMin(y2, r.y2)); +} + +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_UNUSED(region); + Q_Q(QWidget); + + if (!q->isVisible() || !q->isWindow()) + return; + + data.fstrut_dirty = true; + invalidateBuffer(q->rect()); + QWindowSurface *surface = extra->topextra->backingStore->windowSurface; + if (surface) { + // QWSWindowSurface::setGeometry() returns without doing anything + // if old geom == new geom. Therefore, we need to reset the old value. + surface->QWindowSurface::setGeometry(QRect()); + surface->setGeometry(q->frameGeometry()); + } +} + +void QWidgetPrivate::updateFrameStrut() +{ + Q_Q(QWidget); + + if(!q->isVisible() || (q->windowType() == Qt::Desktop)) { + data.fstrut_dirty = q->isVisible(); + return; + } + +#ifndef QT_NO_QWS_MANAGER + if (extra && extra->topextra && extra->topextra->qwsManager) { + QTLWExtra *topextra = extra->topextra; + const QRect oldFrameStrut = topextra->frameStrut; + const QRect contents = data.crect; + QRegion r = localRequestedRegion().translated(contents.topLeft()); + r += extra->topextra->qwsManager->region(); + const QRect frame = r.boundingRect(); + + topextra->frameStrut.setCoords(contents.left() - frame.left(), + contents.top() - frame.top(), + frame.right() - contents.right(), + frame.bottom() - contents.bottom()); + topextra->qwsManager->repaintRegion(QDecoration::All, QDecoration::Normal); + } +#endif + data.fstrut_dirty = false; +} + +#ifndef QT_NO_CURSOR +void QWidgetPrivate::updateCursor() const +{ + Q_Q(const QWidget); + + if (QApplication::overrideCursor()) + return; + + if (qt_last_x + && (!QWidget::mouseGrabber() || QWidget::mouseGrabber() == q) + && qt_last_cursor != (WId)q->cursor().handle()) + { + const QPoint pos(*qt_last_x, *qt_last_y); + const QPoint offset = q->mapToGlobal(QPoint()); + if (!localAllocatedRegion().contains(pos - offset)) + return; + + const QWidget *w = q->childAt(q->mapFromGlobal(pos)); + if (!w || w->cursor().handle() == q->cursor().handle()) + QWidget::qwsDisplay()->selectCursor(const_cast<QWidget*>(q), + q->cursor().handle()); + } +} +#endif + +void QWidgetPrivate::setWindowOpacity_sys(qreal level) +{ + Q_Q(QWidget); + Q_UNUSED(level); + createWinId(); + QWidget::qwsDisplay()->setOpacity(q->data->winid, topData()->opacity); +} + +//static QSingleCleanupHandler<QWSPaintEngine> qt_paintengine_cleanup_handler; +//static QWSPaintEngine *qt_widget_paintengine = 0; +QPaintEngine *QWidget::paintEngine() const +{ + qWarning("QWidget::paintEngine: Should no longer be called"); + return 0; //##### @@@ +// if (!qt_widget_paintengine) { +// qt_widget_paintengine = new QRasterPaintEngine(); +// qt_paintengine_cleanup_handler.set(&qt_widget_paintengine); +// } +// if (qt_widget_paintengine->isActive()) { +// if (d->extraPaintEngine) +// return d->extraPaintEngine; +// const_cast<QWidget *>(this)->d_func()->extraPaintEngine = new QRasterPaintEngine(); +// return d->extraPaintEngine; +// } +// return qt_widget_paintengine; +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + Q_Q(QWidget); + if (q->windowType() == Qt::Desktop) + return 0; + q->ensurePolished(); + return qt_screen->createSurface(q); +} + +void QWidgetPrivate::setModal_sys() +{ +} + + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidget_s60.cpp b/src/gui/kernel/qwidget_s60.cpp new file mode 100644 index 0000000000..e28a75a6ab --- /dev/null +++ b/src/gui/kernel/qwidget_s60.cpp @@ -0,0 +1,1450 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidget_p.h" +#include "qdesktopwidget.h" +#include "qapplication.h" +#include "qapplication_p.h" +#include "private/qbackingstore_p.h" +#include "qevent.h" +#include "qt_s60_p.h" + +#include "qbitmap.h" +#include "private/qwindowsurface_s60_p.h" + +#include <qinputcontext.h> + +#ifdef Q_WS_S60 +#include <aknappui.h> +#include <eikbtgpc.h> +#endif + +// This is necessary in order to be able to perform delayed invocation on slots +// which take arguments of type WId. One example is +// QWidgetPrivate::_q_delayedDestroy, which is used to delay destruction of +// CCoeControl objects until after the CONE event handler has finished running. +Q_DECLARE_METATYPE(WId) + +// Workaround for the fact that S60 SDKs 3.x do not contain the akntoolbar.h +// header, even though the documentation says that it should be there, and indeed +// it is present in the library. +class CAknToolbar : public CAknControl, + public MCoeControlObserver, + public MCoeControlBackground, + public MEikCommandObserver, + public MAknFadedComponent +{ +public: + IMPORT_C void SetToolbarVisibility(const TBool visible); +}; + +QT_BEGIN_NAMESPACE + +extern bool qt_nograb(); + +QWidget *QWidgetPrivate::mouseGrabber = 0; +QWidget *QWidgetPrivate::keyboardGrabber = 0; +CEikButtonGroupContainer *QS60Data::cba = 0; + +int qt_symbian_create_desktop_on_screen = -1; + +static bool isEqual(const QList<QAction*>& a, const QList<QAction*>& b) +{ + if ( a.count() != b.count()) + return false; + int index=0; + while (index<a.count()) { + if (a.at(index)->softKeyRole() != b.at(index)->softKeyRole()) + return false; + if (a.at(index)->text().compare(b.at(index)->text())!=0) + return false; + index++; + } + return true; +} + +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &) +{ + // Note: based on x11 implementation + + static const int XCOORD_MAX = 16383; + static const int WRECT_MAX = 16383; + + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + /* + There are up to four different coordinate systems here: + Qt coordinate system for this widget. + Symbian coordinate system for this widget (relative to wrect). + Qt coordinate system for parent + Symbian coordinate system for parent (relative to parent's wrect). + */ + + QRect validRange(-XCOORD_MAX,-XCOORD_MAX, 2*XCOORD_MAX, 2*XCOORD_MAX); + QRect wrectRange(-WRECT_MAX,-WRECT_MAX, 2*WRECT_MAX, 2*WRECT_MAX); + QRect wrect; + //xrect is the Symbian geometry of my widget. (starts out in parent's Qt coord sys, and ends up in parent's Symbian coord sys) + QRect xrect = data.crect; + + const QWidget *const parent = q->parentWidget(); + QRect parentWRect = parent->data->wrect; + + if (parentWRect.isValid()) { + // parent is clipped, and we have to clip to the same limit as parent + if (!parentWRect.contains(xrect)) { + xrect &= parentWRect; + wrect = xrect; + //translate from parent's to my Qt coord sys + wrect.translate(-data.crect.topLeft()); + } + //translate from parent's Qt coords to parent's X coords + xrect.translate(-parentWRect.topLeft()); + + } else { + // parent is not clipped, we may or may not have to clip + + if (data.wrect.isValid() && QRect(QPoint(),data.crect.size()).contains(data.wrect)) { + // This is where the main optimization is: we are already + // clipped, and if our clip is still valid, we can just + // move our window, and do not need to move or clip + // children + + QRect vrect = xrect & parent->rect(); + vrect.translate(-data.crect.topLeft()); //the part of me that's visible through parent, in my Qt coords + if (data.wrect.contains(vrect)) { + xrect = data.wrect; + xrect.translate(data.crect.topLeft()); + if (data.winid) + data.winid->SetExtent(TPoint(xrect.x(), xrect.y()), TSize(xrect.width(), xrect.height())); + return; + } + } + + if (!validRange.contains(xrect)) { + // we are too big, and must clip + xrect &=wrectRange; + wrect = xrect; + wrect.translate(-data.crect.topLeft()); + //parent's X coord system is equal to parent's Qt coord + //sys, so we don't need to map xrect. + } + } + + // unmap if we are outside the valid window system coord system + bool outsideRange = !xrect.isValid(); + bool mapWindow = false; + if (q->testAttribute(Qt::WA_OutsideWSRange) != outsideRange) { + q->setAttribute(Qt::WA_OutsideWSRange, outsideRange); + if (outsideRange) { + if (data.winid) + data.winid->DrawableWindow()->SetVisible(EFalse); + q->setAttribute(Qt::WA_Mapped, false); + } else if (!q->isHidden()) { + mapWindow = true; + } + } + + if (outsideRange) + return; + + bool jump = (data.wrect != wrect); + data.wrect = wrect; + + // and now recursively for all children... + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(jump); + } + } + + if (data.winid) { + // move ourselves to the new position and map (if necessary) after + // the movement. Rationale: moving unmapped windows is much faster + // than moving mapped windows + if (!parent->internalWinId()) + xrect.translate(parent->mapTo(q->nativeParentWidget(), QPoint(0, 0))); + + data.winid->SetExtent(TPoint(xrect.x(), xrect.y()), TSize(xrect.width(), xrect.height())); + } + + if (mapWindow and !dontShow) { + q->setAttribute(Qt::WA_Mapped); + if (q->internalWinId()) + q->internalWinId()->DrawableWindow()->SetVisible(ETrue); + } + + if (jump && data.winid) { + RWindow *const window = static_cast<RWindow *>(data.winid->DrawableWindow()); + window->Invalidate(TRect(0, 0, wrect.width(), wrect.height())); + } +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if ((q->windowType() == Qt::Desktop)) + return; + + QPoint oldPos(q->pos()); + QSize oldSize(q->size()); + QRect oldGeom(data.crect); + + // Lose maximized status if deliberate resize + if (w != oldSize.width() || h != oldSize.height()) + data.window_state &= ~Qt::WindowMaximized; + + if (extra) { // any size restrictions? + w = qMin(w,extra->maxw); + h = qMin(h,extra->maxh); + w = qMax(w,extra->minw); + h = qMax(h,extra->minh); + } + + if (q->isWindow()) + topData()->normalGeometry = QRect(0, 0, -1, -1); + else { + uint s = data.window_state; + s &= ~(Qt::WindowMaximized | Qt::WindowFullScreen); + data.window_state = s; + } + + bool isResize = w != oldSize.width() || h != oldSize.height(); + if (!isMove && !isResize) + return; + + if (q->isWindow()) { + if (w == 0 || h == 0) { + q->setAttribute(Qt::WA_OutsideWSRange, true); + if (q->isVisible() && q->testAttribute(Qt::WA_Mapped)) + hide_sys(); + data.crect = QRect(x, y, w, h); + data.window_state &= ~Qt::WindowFullScreen; + } else if (q->isVisible() && q->testAttribute(Qt::WA_OutsideWSRange)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); + + // put the window in its place and show it + q->internalWinId()->SetRect(TRect(TPoint(x, y), TSize(w, h))); + data.crect.setRect(x, y, w, h); + show_sys(); + } else { + QRect r = QRect(x, y, w, h); + data.crect = r; + q->internalWinId()->SetRect(TRect(TPoint(x, y), TSize(w, h))); + topData()->normalGeometry = data.crect; + } + QSymbianControl *window = static_cast<QSymbianControl *>(q->internalWinId()); + window->ensureFixNativeOrientation(); + } else { + data.crect.setRect(x, y, w, h); + + QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData(); + const bool inTopLevelResize = tlwExtra ? tlwExtra->inTopLevelResize : false; + + if (q->isVisible() && (!inTopLevelResize || q->internalWinId())) { + // Top-level resize optimization does not work for native child widgets; + // disable it for this particular widget. + if (inTopLevelResize) + tlwExtra->inTopLevelResize = false; + if (!isResize && maybeBackingStore()) + moveRect(QRect(oldPos, oldSize), x - oldPos.x(), y - oldPos.y()); + else + invalidateBuffer_resizeHelper(oldPos, oldSize); + + if (inTopLevelResize) + tlwExtra->inTopLevelResize = true; + } + if (q->testAttribute(Qt::WA_WState_Created)) + setWSGeometry(); + } + + if (q->isVisible()) { + if (isMove && q->pos() != oldPos) { + QMoveEvent e(q->pos(), oldPos); + QApplication::sendEvent(q, &e); + } + if (isResize) { + bool slowResize = qgetenv("QT_SLOW_TOPLEVEL_RESIZE").toInt(); + const bool setTopLevelResize = !slowResize && q->isWindow() && extra && extra->topextra + && !extra->topextra->inTopLevelResize; + if (setTopLevelResize) + extra->topextra->inTopLevelResize = true; + QResizeEvent e(q->size(), oldSize); + QApplication::sendEvent(q, &e); + if (!q->testAttribute(Qt::WA_StaticContents) && q->internalWinId()) + q->internalWinId()->DrawDeferred(); + if (setTopLevelResize) + extra->topextra->inTopLevelResize = false; + } + } else { + if (isMove && q->pos() != oldPos) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } +} + +void QWidgetPrivate::create_sys(WId window, bool /* initializeWindow */, bool destroyOldWindow) +{ + Q_Q(QWidget); + + Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + //bool tool = (type == Qt::Tool || type == Qt::Drawer); + + if (popup) + flags |= Qt::WindowStaysOnTopHint; // a popup stays on top + + TRect clientRect = static_cast<CEikAppUi*>(S60->appUi())->ClientRect(); + int sw = clientRect.Width(); + int sh = clientRect.Height(); + + if (desktop) { + symbianScreenNumber = qMax(qt_symbian_create_desktop_on_screen, 0); + TSize screenSize = S60->screenDevice(symbianScreenNumber)->SizeInPixels(); + data.crect.setRect(0, 0, screenSize.iWidth, screenSize.iHeight); + q->setAttribute(Qt::WA_DontShowOnScreen); + } else if (topLevel && !q->testAttribute(Qt::WA_Resized)){ + int width = sw; + int height = sh; + if (symbianScreenNumber > 0) { + TSize screenSize = S60->screenDevice(symbianScreenNumber)->SizeInPixels(); + width = screenSize.iWidth; + height = screenSize.iHeight; + } + if (extra) { + width = qMax(qMin(width, extra->maxw), extra->minw); + height = qMax(qMin(height, extra->maxh), extra->minh); + } + data.crect.setSize(QSize(width, height)); + } + + CCoeControl *const destroyw = destroyOldWindow ? data.winid : 0; + + createExtra(); + if (window) { + setWinId(window); + TRect tr = window->Rect(); + data.crect.setRect(tr.iTl.iX, tr.iTl.iY, tr.Width(), tr.Height()); + + } else if (topLevel) { + if (!q->testAttribute(Qt::WA_Moved) && !q->testAttribute(Qt::WA_DontShowOnScreen)) + data.crect.moveTopLeft(QPoint(clientRect.iTl.iX, clientRect.iTl.iY)); + + QScopedPointer<QSymbianControl> control( new QSymbianControl(q) ); + Q_CHECK_PTR(control); + + QT_TRAP_THROWING(control->ConstructL(true, desktop)); + control->SetMopParent(static_cast<CEikAppUi*>(S60->appUi())); + + // Symbian windows are always created in an inactive state + // We perform this assignment for the case where the window is being re-created + // as a result of a call to setParent_sys, on either this widget or one of its + // ancestors. + extra->activated = 0; + + if (!desktop) { + TInt stackingFlags; + if ((q->windowType() & Qt::Popup) == Qt::Popup) { + stackingFlags = ECoeStackFlagRefusesAllKeys | ECoeStackFlagRefusesFocus; + } else { + stackingFlags = ECoeStackFlagStandard; + } + control->MakeVisible(false); + QT_TRAP_THROWING(control->ControlEnv()->AppUi()->AddToStackL(control.data(), ECoeStackPriorityDefault, stackingFlags)); + // Avoid keyboard focus to a hidden window. + control->setFocusSafely(false); + + RDrawableWindow *const drawableWindow = control->DrawableWindow(); + // Request mouse move events. + drawableWindow->PointerFilter(EPointerFilterEnterExit + | EPointerFilterMove | EPointerFilterDrag, 0); + drawableWindow->EnableVisibilityChangeEvents(); + + } + + q->setAttribute(Qt::WA_WState_Created); + + int x, y, w, h; + data.crect.getRect(&x, &y, &w, &h); + control->SetRect(TRect(TPoint(x, y), TSize(w, h))); + + // We wait until the control is fully constructed before calling setWinId, because + // this generates a WinIdChanged event. + setWinId(control.take()); + + if (!desktop) + s60UpdateIsOpaque(); // must be called after setWinId() + + } else if (q->testAttribute(Qt::WA_NativeWindow) || paintOnScreen()) { // create native child widget + + QScopedPointer<QSymbianControl> control( new QSymbianControl(q) ); + Q_CHECK_PTR(control); + + QT_TRAP_THROWING(control->ConstructL(!parentWidget)); + + // Symbian windows are always created in an inactive state + // We perform this assignment for the case where the window is being re-created + // as a result of a call to setParent_sys, on either this widget or one of its + // ancestors. + extra->activated = 0; + + TInt stackingFlags; + if ((q->windowType() & Qt::Popup) == Qt::Popup) { + stackingFlags = ECoeStackFlagRefusesAllKeys | ECoeStackFlagRefusesFocus; + } else { + stackingFlags = ECoeStackFlagStandard; + } + control->MakeVisible(false); + QT_TRAP_THROWING(control->ControlEnv()->AppUi()->AddToStackL(control.data(), ECoeStackPriorityDefault, stackingFlags)); + // Avoid keyboard focus to a hidden window. + control->setFocusSafely(false); + + q->setAttribute(Qt::WA_WState_Created); + int x, y, w, h; + data.crect.getRect(&x, &y, &w, &h); + control->SetRect(TRect(TPoint(x, y), TSize(w, h))); + + RDrawableWindow *const drawableWindow = control->DrawableWindow(); + // Request mouse move events. + drawableWindow->PointerFilter(EPointerFilterEnterExit + | EPointerFilterMove | EPointerFilterDrag, 0); + drawableWindow->EnableVisibilityChangeEvents(); + + if (q->isVisible() && q->testAttribute(Qt::WA_Mapped)) { + activateSymbianWindow(control.data()); + control->MakeVisible(true); + } + + // We wait until the control is fully constructed before calling setWinId, because + // this generates a WinIdChanged event. + setWinId(control.take()); + } + + if (destroyw) { + destroyw->ControlEnv()->AppUi()->RemoveFromStack(destroyw); + + // Delay deletion of the control in case this function is called in the + // context of a CONE event handler such as + // CCoeControl::ProcessPointerEventL + QMetaObject::invokeMethod(q, "_q_delayedDestroy", + Qt::QueuedConnection, Q_ARG(WId, destroyw)); + } + + if (q->testAttribute(Qt::WA_AcceptTouchEvents)) + registerTouchWindow(); +} + + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + q->setAttribute(Qt::WA_Mapped); + + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + return; + } + + if (q->internalWinId()) { + if (!extra->activated) + activateSymbianWindow(); + + QSymbianControl *id = static_cast<QSymbianControl *>(q->internalWinId()); + const bool isFullscreen = q->windowState() & Qt::WindowFullScreen; + const TBool cbaRequested = q->windowFlags() & Qt::WindowSoftkeysVisibleHint; + +#ifdef Q_WS_S60 + // Lazily initialize the S60 screen furniture when the first window is shown. + if (q->isWindow() && !QApplication::testAttribute(Qt::AA_S60DontConstructApplicationPanes) + && !S60->buttonGroupContainer() && !S60->statusPane()) { + + if (!q->testAttribute(Qt::WA_DontShowOnScreen)) { + + // Create the status pane and CBA here + CEikAppUi *ui = static_cast<CEikAppUi *>(S60->appUi()); + MEikAppUiFactory *factory = CEikonEnv::Static()->AppUiFactory(); + + QT_TRAP_THROWING( + factory->CreateResourceIndependentFurnitureL(ui); + + TRect boundingRect = static_cast<CEikAppUi*>(S60->appUi())->ClientRect(); + + CEikButtonGroupContainer *cba = CEikButtonGroupContainer::NewL(CEikButtonGroupContainer::ECba, + CEikButtonGroupContainer::EHorizontal,ui,R_AVKON_SOFTKEYS_EMPTY_WITH_IDS); + if (isFullscreen && !cbaRequested) + cba->MakeVisible(false); + + CEikButtonGroupContainer *oldCba = factory->SwapButtonGroup(cba); + Q_ASSERT(!oldCba); + S60->setButtonGroupContainer(cba); + + // If the creation of the first widget is delayed, for example by doing it + // inside the event loop, S60 somehow "forgets" to set the visibility of the + // toolbar (the three middle softkeys) when you flip the phone over, so we + // need to do it ourselves to avoid a "hole" in the application, even though + // Qt itself does not use the toolbar directly.. + CAknAppUi *appui = dynamic_cast<CAknAppUi *>(CEikonEnv::Static()->AppUi()); + if (appui) { + CAknToolbar *toolbar = appui->PopupToolbar(); + if (toolbar && !toolbar->IsVisible()) + toolbar->SetToolbarVisibility(ETrue); + } + + CEikMenuBar *menuBar = new(ELeave) CEikMenuBar; + menuBar->ConstructL(ui, 0, R_AVKON_MENUPANE_EMPTY); + menuBar->SetMenuType(CEikMenuBar::EMenuOptions); + S60->appUi()->AddToStackL(menuBar,ECoeStackPriorityMenu,ECoeStackFlagRefusesFocus); + + CEikMenuBar *oldMenu = factory->SwapMenuBar(menuBar); + Q_ASSERT(!oldMenu); + ) + + if (S60->statusPane()) { + // Use QDesktopWidget as the status pane observer to proxy for the AppUi. + // Can't use AppUi directly because it privately inherits from MEikStatusPaneObserver. + QSymbianControl *desktopControl = static_cast<QSymbianControl *>(QApplication::desktop()->winId()); + S60->statusPane()->SetObserver(desktopControl); + if (isFullscreen) { + const bool cbaVisible = S60->buttonGroupContainer() && S60->buttonGroupContainer()->IsVisible(); + S60->setStatusPaneAndButtonGroupVisibility(false, cbaVisible); + } + } + } + } +#endif + + // Fill client area if maximized OR + // Put window below status pane unless the window has an explicit position. + if (!isFullscreen) { + if (q->windowState() & Qt::WindowMaximized) { + TRect r = static_cast<CEikAppUi*>(S60->appUi())->ClientRect(); + id->SetExtent(r.iTl, r.Size()); + } else if (!q->testAttribute(Qt::WA_Moved) && q->windowType() != Qt::Dialog) { + id->SetPosition(static_cast<CEikAppUi*>(S60->appUi())->ClientRect().iTl); + } + } + + id->MakeVisible(true); + + if(q->isWindow()&&!q->testAttribute(Qt::WA_ShowWithoutActivating)) + id->setFocusSafely(true); + } + + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::activateSymbianWindow(WId wid) +{ + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + Q_ASSERT(q->testAttribute(Qt::WA_Mapped)); + Q_ASSERT(!extra->activated); + + if(!wid) + wid = q->internalWinId(); + + Q_ASSERT(wid); + + QT_TRAP_THROWING(wid->ActivateL()); + extra->activated = 1; +} + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + deactivateWidgetCleanup(); + QSymbianControl *id = static_cast<QSymbianControl *>(q->internalWinId()); + + if (id) { + //Incorrect optimization - for popup windows, Qt's focus is moved before + //hide_sys is called, resulting in the popup window keeping its elevated + //position in the CONE control stack. + //This can result in keyboard focus being in an invisible widget in some + //conditions - e.g. QTBUG-4733 + //if(id->IsFocused()) // Avoid unnecessary calls to FocusChanged() + id->setFocusSafely(false); + id->MakeVisible(false); + if (QWidgetBackingStore *bs = maybeBackingStore()) + bs->releaseBuffer(); + } else { + invalidateBuffer(q->rect()); + } + + q->setAttribute(Qt::WA_Mapped, false); +} + +void QWidgetPrivate::setFocus_sys() +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created) && q->window()->windowType() != Qt::Popup) + if (!q->effectiveWinId()->IsFocused()) // Avoid unnecessry calls to FocusChanged() + static_cast<QSymbianControl *>(q->effectiveWinId())->setFocusSafely(true); +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) { + q->internalWinId()->DrawableWindow()->SetOrdinalPosition(0); + + // If toplevel widget, raise app to foreground + if (q->isWindow()) + S60->wsSession().SetWindowGroupOrdinalPosition(S60->windowGroup(q).Identifier(), 0); + } +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) { + // If toplevel widget, lower app to background + if (q->isWindow()) + S60->wsSession().SetWindowGroupOrdinalPosition(S60->windowGroup(q).Identifier(), -1); + else + q->internalWinId()->DrawableWindow()->SetOrdinalPosition(-1); + } + + if (!q->isWindow()) + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::setModal_sys() +{ + +} + +void QWidgetPrivate::stackUnder_sys(QWidget* w) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (q->internalWinId() && w->internalWinId()) { + RDrawableWindow *const thisWindow = q->internalWinId()->DrawableWindow(); + RDrawableWindow *const otherWindow = w->internalWinId()->DrawableWindow(); + thisWindow->SetOrdinalPosition(otherWindow->OrdinalPosition() + 1); + } + + if (!q->isWindow() || !w->internalWinId()) + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::reparentChildren() +{ + Q_Q(QWidget); + + QObjectList chlist = q->children(); + for (int i = 0; i < chlist.size(); ++i) { // reparent children + QObject *obj = chlist.at(i); + if (obj->isWidgetType()) { + QWidget *w = (QWidget *)obj; + if (!w->testAttribute(Qt::WA_WState_Created)) + continue; + if (!w->isWindow()) { + w->d_func()->invalidateBuffer(w->rect()); + WId parent = q->effectiveWinId(); + WId child = w->effectiveWinId(); + if (parent != child) { + // Child widget is native. Because Symbian windows cannot be + // re-parented, we must re-create the window. + const WId window = 0; + const bool initializeWindow = false; + const bool destroyOldWindow = true; + w->d_func()->create_sys(window, initializeWindow, destroyOldWindow); + } + // ### TODO: We probably also need to update the component array here + w->d_func()->reparentChildren(); + } else { + bool showIt = w->isVisible(); + QPoint old_pos = w->pos(); + w->setParent(q, w->windowFlags()); + w->move(old_pos); + if (showIt) + w->show(); + } + } + } +} + +void QWidgetPrivate::setParent_sys(QWidget *parent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + + if (parent && parent->windowType() == Qt::Desktop) { + symbianScreenNumber = qt_widget_private(parent)->symbianScreenNumber; + parent = 0; + } + + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); + + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(q->geometry()); + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + q->setAttribute(Qt::WA_DropSiteRegistered, false); + + QSymbianControl *old_winid = static_cast<QSymbianControl *>(wasCreated ? data.winid : 0); + if ((q->windowType() == Qt::Desktop)) + old_winid = 0; + + // old_winid may not have received a 'not visible' visibility + // changed event before being destroyed; make sure that it is + // removed from the backing store's list of visible windows. + if (old_winid) + S60->controlVisibilityChanged(old_winid, false); + + setWinId(0); + + // hide and reparent our own window away. Otherwise we might get + // destroyed when emitting the child remove event below. See QWorkspace. + if (wasCreated && old_winid) { + old_winid->MakeVisible(false); + if (old_winid->IsFocused()) // Avoid unnecessary calls to FocusChanged() + old_winid->setFocusSafely(false); + old_winid->SetParent(0); + } + + QObjectPrivate::setParent_helper(parent); + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + + data.window_flags = f; + data.fstrut_dirty = true; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state + // (but we recreate the winId for the widget being reparented, again for compatibility) + if (wasCreated || (!q->isWindow() && parent->testAttribute(Qt::WA_WState_Created))) + createWinId(); + if (q->isWindow() || (!parent || parent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (wasCreated) + reparentChildren(); + + if (old_winid) { + CBase::Delete(old_winid); + } + + if (q->testAttribute(Qt::WA_AcceptDrops) + || (!q->isWindow() && q->parentWidget() && q->parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) + q->setAttribute(Qt::WA_DropSiteRegistered, true); + + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::setConstraints_sys() +{ + +} + + +void QWidgetPrivate::s60UpdateIsOpaque() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + const bool writeAlpha = extraData()->nativePaintMode == QWExtra::BlitWriteAlpha; + if (!q->testAttribute(Qt::WA_TranslucentBackground) && !writeAlpha) + return; + const bool requireAlphaChannel = !isOpaque || writeAlpha; + + createTLExtra(); + + RWindow *const window = static_cast<RWindow *>(q->effectiveWinId()->DrawableWindow()); + +#ifdef Q_SYMBIAN_SEMITRANSPARENT_BG_SURFACE + if (QApplicationPrivate::instance()->useTranslucentEGLSurfaces) { + window->SetSurfaceTransparency(!isOpaque); + extra->topextra->nativeWindowTransparencyEnabled = !isOpaque; + return; + } +#endif + if (requireAlphaChannel) { + const TDisplayMode displayMode = static_cast<TDisplayMode>(window->SetRequiredDisplayMode(EColor16MA)); + if (window->SetTransparencyAlphaChannel() == KErrNone) { + window->SetBackgroundColor(TRgb(255, 255, 255, 0)); + extra->topextra->nativeWindowTransparencyEnabled = 1; + if (extra->topextra->backingStore.data() && ( + QApplicationPrivate::graphics_system_name == QLatin1String("openvg") + || QApplicationPrivate::graphics_system_name == QLatin1String("opengl"))) { + // Semi-transparent EGL surfaces aren't supported. We need to + // recreate backing store to get translucent surface (raster surface). + extra->topextra->backingStore.create(q); + extra->topextra->backingStore.registerWidget(q); + // FixNativeOrientation() will not work without an EGL surface. + q->setAttribute(Qt::WA_SymbianNoSystemRotation, false); + } + } + } else if (extra->topextra->nativeWindowTransparencyEnabled) { + window->SetTransparentRegion(TRegionFix<1>()); + extra->topextra->nativeWindowTransparencyEnabled = 0; + } +} + +void QWidgetPrivate::setWindowIcon_sys(bool forceReset) +{ +#ifdef Q_WS_S60 + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow() ) + return; + + QTLWExtra* topData = this->topData(); + if (topData->iconPixmap && !forceReset) + // already been set + return; + + TRect cPaneRect; + TBool found = AknLayoutUtils::LayoutMetricsRect( AknLayoutUtils::EContextPane, cPaneRect ); + CAknContextPane* contextPane = S60->contextPane(); + if (found && contextPane) { // We have context pane with valid metrics + QIcon icon = q->windowIcon(); + if (!icon.isNull()) { + // Valid icon -> set it as an context pane picture + QSize size = icon.actualSize(QSize(cPaneRect.Size().iWidth, cPaneRect.Size().iHeight)); + QPixmap pm = icon.pixmap(size); + QBitmap mask = pm.mask(); + if (mask.isNull()) { + mask = QBitmap(pm.size()); + mask.fill(Qt::color1); + } + + CFbsBitmap* nBitmap = pm.toSymbianCFbsBitmap(); + CFbsBitmap* nMask = mask.toSymbianCFbsBitmap(); + contextPane->SetPicture(nBitmap,nMask); + } else { + // Icon set to null -> set context pane picture to default + QT_TRAP_THROWING(contextPane->SetPictureToDefaultL()); + } + } else { + // Context pane does not exist, try setting small icon to title pane + TRect titlePaneRect; + TBool found = AknLayoutUtils::LayoutMetricsRect( AknLayoutUtils::ETitlePane, titlePaneRect ); + CAknTitlePane* titlePane = S60->titlePane(); + if (found && titlePane) { // We have title pane with valid metrics + // The API to get title_pane graphics size is not public -> assume square space based + // on titlebar font height. CAknBitmap would be optimum, wihtout setting the size, since + // then title pane would automatically scale the bitmap. Unfortunately it is not public API + // Also this function is leaving, although it is not named as such. + const CFont * font; + QT_TRAP_THROWING(font = AknLayoutUtils::FontFromId(EAknLogicalFontTitleFont)); + TSize iconSize(font->HeightInPixels(), font->HeightInPixels()); + + QIcon icon = q->windowIcon(); + if (!icon.isNull()) { + // Valid icon -> set it as an title pane small picture + QSize size = icon.actualSize(QSize(iconSize.iWidth, iconSize.iHeight)); + QPixmap pm = icon.pixmap(size); + QBitmap mask = pm.mask(); + if (mask.isNull()) { + mask = QBitmap(pm.size()); + mask.fill(Qt::color1); + } + + CFbsBitmap* nBitmap = pm.toSymbianCFbsBitmap(); + CFbsBitmap* nMask = mask.toSymbianCFbsBitmap(); + titlePane->SetSmallPicture( nBitmap, nMask, ETrue ); + } else { + // Icon set to null -> set context pane picture to default + titlePane->SetSmallPicture( NULL, NULL, EFalse ); + } + } + } + +#else + Q_UNUSED(forceReset) +#endif +} + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ +#ifdef Q_WS_S60 + Q_Q(QWidget); + if (q->isWindow()) { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + CAknTitlePane* titlePane = S60->titlePane(); + if (titlePane) { + if (caption.isEmpty()) { + QT_TRAP_THROWING(titlePane->SetTextToDefaultL()); + } else { + QT_TRAP_THROWING(titlePane->SetTextL(qt_QString2TPtrC(caption))); + } + } + } +#else + Q_UNUSED(caption) +#endif +} + +void QWidgetPrivate::setWindowIconText_sys(const QString & /*iconText */) +{ + +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + Q_Q(QWidget); + + scrollChildren(dx, dy); + if (!paintOnScreen() || !q->internalWinId() || !q->internalWinId()->OwnsWindow()) { + scrollRect(q->rect(), dx, dy); + } else { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + RDrawableWindow *const window = q->internalWinId()->DrawableWindow(); + window->Scroll(TPoint(dx, dy)); + } +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r) +{ + Q_Q(QWidget); + + if (!paintOnScreen() || !q->internalWinId() || !q->internalWinId()->OwnsWindow()) { + scrollRect(r, dx, dy); + } else { + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + RDrawableWindow *const window = q->internalWinId()->DrawableWindow(); + window->Scroll(TPoint(dx, dy), qt_QRect2TRect(r)); + } +} + +/*! + For this function to work in the emulator, you must add: + TRANSPARENCY + To a line in the wsini.ini file. +*/ +void QWidgetPrivate::setWindowOpacity_sys(qreal) +{ + // ### TODO: Implement uniform window transparency +} + +void QWidgetPrivate::updateFrameStrut() +{ + +} + +void QWidgetPrivate::updateSystemBackground() +{ + +} + +void QWidgetPrivate::registerDropSite(bool /* on */) +{ + +} + +void QWidgetPrivate::createTLSysExtra() +{ + extra->topextra->inExpose = 0; + extra->topextra->nativeWindowTransparencyEnabled = 0; +} + +void QWidgetPrivate::deleteTLSysExtra() +{ + extra->topextra->backingStore.destroy(); +} + +void QWidgetPrivate::createSysExtra() +{ + extra->activated = 0; + extra->nativePaintMode = QWExtra::Default; + extra->receiveNativePaintEvents = 0; +} + +void QWidgetPrivate::deleteSysExtra() +{ + // this should only be non-zero if destroy() has not run due to constructor fail + if (data.winid) { + data.winid->ControlEnv()->AppUi()->RemoveFromStack(data.winid); + delete data.winid; + data.winid = 0; + } +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + return new QS60WindowSurface(q_func()); +} + +void QWidgetPrivate::setMask_sys(const QRegion& /* region */) +{ + +} + +void QWidgetPrivate::registerTouchWindow() +{ +#ifdef QT_SYMBIAN_SUPPORTS_ADVANCED_POINTER + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created) && q->windowType() != Qt::Desktop) { + RWindow *rwindow = static_cast<RWindow *>(q->effectiveWinId()->DrawableWindow()); + QSymbianControl *window = static_cast<QSymbianControl *>(q->effectiveWinId()); + //Enabling advanced pointer events for controls that already have active windows causes a panic. + if (!window->isControlActive()) + rwindow->EnableAdvancedPointers(); + } +#endif +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + Q_D(const QWidget); + int val; + if (m == PdmWidth) { + val = data->crect.width(); + } else if (m == PdmHeight) { + val = data->crect.height(); + } else { + CWsScreenDevice *scr = S60->screenDevice(this); + switch(m) { + case PdmDpiX: + case PdmPhysicalDpiX: + if (d->extra && d->extra->customDpiX) { + val = d->extra->customDpiX; + } else { + const QWidgetPrivate *p = d; + while (p->parent) { + p = static_cast<const QWidget *>(p->parent)->d_func(); + if (p->extra && p->extra->customDpiX) { + val = p->extra->customDpiX; + break; + } + } + if (p == d || !(p->extra && p->extra->customDpiX)) + val = S60->defaultDpiX; + } + break; + case PdmDpiY: + case PdmPhysicalDpiY: + if (d->extra && d->extra->customDpiY) { + val = d->extra->customDpiY; + } else { + const QWidgetPrivate *p = d; + while (p->parent) { + p = static_cast<const QWidget *>(p->parent)->d_func(); + if (p->extra && p->extra->customDpiY) { + val = p->extra->customDpiY; + break; + } + } + if (p == d || !(p->extra && p->extra->customDpiY)) + val = S60->defaultDpiY; + } + break; + case PdmWidthMM: + { + TInt twips = scr->HorizontalPixelsToTwips(data->crect.width()); + val = (int)(twips * (25.4/KTwipsPerInch)); + break; + } + case PdmHeightMM: + { + TInt twips = scr->VerticalPixelsToTwips(data->crect.height()); + val = (int)(twips * (25.4/KTwipsPerInch)); + break; + } + case PdmNumColors: + val = TDisplayModeUtils::NumDisplayModeColors(scr->DisplayMode()); + break; + case PdmDepth: + val = TDisplayModeUtils::NumDisplayModeBitsPerPixel(scr->DisplayMode()); + break; + default: + val = 0; + qWarning("QWidget::metric: Invalid metric command"); + } + } + return val; +} + +QPaintEngine *QWidget::paintEngine() const +{ + return 0; +} + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!testAttribute(Qt::WA_WState_Created) || !internalWinId()) { + + QPoint p = pos + data->crect.topLeft(); + return (isWindow() || !parentWidget()) ? p : parentWidget()->mapToGlobal(p); + + } else if ((d->data.window_flags & Qt::Window) && internalWinId()) { //toplevel + QPoint tp = geometry().topLeft(); + return pos + tp; + } + + // Native window case + const TPoint widgetScreenOffset = internalWinId()->PositionRelativeToScreen(); + const QPoint globalPos = QPoint(widgetScreenOffset.iX, widgetScreenOffset.iY) + pos; + return globalPos; +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!testAttribute(Qt::WA_WState_Created) || !internalWinId()) { + QPoint p = (isWindow() || !parentWidget()) ? pos : parentWidget()->mapFromGlobal(pos); + return p - data->crect.topLeft(); + } else if ((d->data.window_flags & Qt::Window) && internalWinId()) { //toplevel + QPoint tp = geometry().topLeft(); + return pos - tp; + } + + // Native window case + const TPoint widgetScreenOffset = internalWinId()->PositionRelativeToScreen(); + const QPoint widgetPos = pos - QPoint(widgetScreenOffset.iX, widgetScreenOffset.iY); + return widgetPos; +} + +static Qt::WindowStates effectiveState(Qt::WindowStates state) +{ + if (state & Qt::WindowMinimized) + return Qt::WindowMinimized; + else if (state & Qt::WindowFullScreen) + return Qt::WindowFullScreen; + else if (state & Qt::WindowMaximized) + return Qt::WindowMaximized; + return Qt::WindowNoState; +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + + Qt::WindowStates oldstate = windowState(); + + const TBool isFullscreen = newstate & Qt::WindowFullScreen; +#ifdef Q_WS_S60 + const TBool cbaRequested = windowFlags() & Qt::WindowSoftkeysVisibleHint; + const TBool cbaVisible = CEikButtonGroupContainer::Current() ? true : false; + const TBool softkeyVisibilityChange = isFullscreen && (cbaRequested != cbaVisible); + + if (oldstate == newstate && !softkeyVisibilityChange) + return; +#endif // Q_WS_S60 + + if (isWindow()) { + createWinId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + const bool wasResized = testAttribute(Qt::WA_Resized); + const bool wasMoved = testAttribute(Qt::WA_Moved); + + QSymbianControl *window = static_cast<QSymbianControl *>(effectiveWinId()); + if (window && newstate & Qt::WindowMinimized) { + window->setFocusSafely(false); + window->MakeVisible(false); + } else if (window && oldstate & Qt::WindowMinimized) { + window->setFocusSafely(true); + window->MakeVisible(true); + } + +#ifdef Q_WS_S60 + // The window decoration visibility has to be changed before doing actual window state + // change since in that order the availableGeometry will return directly the right size and + // we will avoid unnecessary redraws + bool decorationsVisible = S60->setRecursiveDecorationsVisibility(this, newstate); +#endif // Q_WS_S60 + + // Ensure the initial size is valid, since we store it as normalGeometry below. + if (!wasResized && !isVisible()) + adjustSize(); + + QTLWExtra *top = d->topData(); + QRect normalGeometry = (top->normalGeometry.width() < 0) ? geometry() : top->normalGeometry; + + const bool cbaVisibilityHint = windowFlags() & Qt::WindowSoftkeysVisibleHint; + if (newstate & Qt::WindowFullScreen && !cbaVisibilityHint) { + setAttribute(Qt::WA_OutsideWSRange, false); + if (d->symbianScreenNumber > 0) { + int w = S60->screenWidthInPixelsForScreen[d->symbianScreenNumber]; + int h = S60->screenHeightInPixelsForScreen[d->symbianScreenNumber]; + if (w <= 0 || h <= 0) + window->SetExtentToWholeScreen(); + else + window->SetExtent(TPoint(0, 0), TSize(w, h)); + } else { + window->SetExtentToWholeScreen(); + } + } else if (newstate & Qt::WindowMaximized || ((newstate & Qt::WindowFullScreen) && cbaVisibilityHint)) { + setAttribute(Qt::WA_OutsideWSRange, false); + TRect maxExtent = qt_QRect2TRect(qApp->desktop()->availableGeometry(this)); + window->SetExtent(maxExtent.iTl, maxExtent.Size()); + } else { +#ifdef Q_WS_S60 + // With delayed creation of S60 app panes, the normalGeometry calculated above is not + // accurate because it did not consider the status pane. This means that when returning + // normal mode after showing the status pane, the geometry would overlap so we should + // move it if it never had an explicit position. + if (!wasMoved && S60->statusPane() && decorationsVisible) { + TPoint tl = static_cast<CEikAppUi*>(S60->appUi())->ClientRect().iTl; + normalGeometry.setTopLeft(QPoint(tl.iX, tl.iY)); + } +#endif + setGeometry(normalGeometry); + } + + //restore normal geometry + top->normalGeometry = normalGeometry; + + // FixMe QTBUG-8977 + // In some platforms, WA_Resized and WA_Moved are also not set when application window state is + // anything else than normal. In Symbian we can restore them only for normal window state since + // restoring for other modes, will make fluidlauncher to be launched in wrong size (200x100) + if (effectiveState(newstate) == Qt::WindowNoState) { + setAttribute(Qt::WA_Resized, wasResized); + setAttribute(Qt::WA_Moved, wasMoved); + } + } + + data->window_state = newstate; + + if (newstate & Qt::WindowActive) + activateWindow(); + + if (isWindow()) { + // Now that the new state is set, fix the display memory layout, if needed. + QSymbianControl *window = static_cast<QSymbianControl *>(effectiveWinId()); + window->ensureFixNativeOrientation(); + } + + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} + + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(geometry()); + d->deactivateWidgetCleanup(); + QSymbianControl *id = static_cast<QSymbianControl *>(internalWinId()); + if (testAttribute(Qt::WA_WState_Created)) { + +#ifndef QT_NO_IM + if (d->ic) { + delete d->ic; + } else { + QInputContext *ic = QApplicationPrivate::inputContext; + if (ic) { + ic->widgetDestroyed(this); + } + } +#endif + + if (QWidgetPrivate::mouseGrabber == this) + releaseMouse(); + if (QWidgetPrivate::keyboardGrabber == this) + releaseKeyboard(); + setAttribute(Qt::WA_WState_Created, false); + QObjectList childList = children(); + for (int i = 0; i < childList.size(); ++i) { // destroy all widget children + register QObject *obj = childList.at(i); + if (obj->isWidgetType()) + static_cast<QWidget*>(obj)->destroy(destroySubWindows, + destroySubWindows); + } + if (destroyWindow && !(windowType() == Qt::Desktop) && id) { + if (id->IsFocused()) // Avoid unnecessry calls to FocusChanged() + id->setFocusSafely(false); + id->ControlEnv()->AppUi()->RemoveFromStack(id); + } + } + + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + + if (destroyWindow) { + delete id; + // At this point the backing store should already be destroyed + // so we flush the command buffer to ensure that the freeing of + // those resources and deleting the window can happen "atomically" + if (qApp) + S60->wsSession().Flush(); + } +} + +QWidget *QWidget::mouseGrabber() +{ + return QWidgetPrivate::mouseGrabber; +} + +QWidget *QWidget::keyboardGrabber() +{ + return QWidgetPrivate::keyboardGrabber; +} + +void QWidget::grabKeyboard() +{ + if (!qt_nograb()) { + if (QWidgetPrivate::keyboardGrabber && QWidgetPrivate::keyboardGrabber != this) + QWidgetPrivate::keyboardGrabber->releaseKeyboard(); + + // ### TODO: Native keyboard grab + + QWidgetPrivate::keyboardGrabber = this; + } +} + +void QWidget::releaseKeyboard() +{ + if (!qt_nograb() && QWidgetPrivate::keyboardGrabber == this) { + // ### TODO: Native keyboard release + QWidgetPrivate::keyboardGrabber = 0; + } +} + +void QWidget::grabMouse() +{ + if (isVisible() && !qt_nograb()) { + if (QWidgetPrivate::mouseGrabber && QWidgetPrivate::mouseGrabber != this) + QWidgetPrivate::mouseGrabber->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + WId id = effectiveWinId(); + id->SetPointerCapture(true); + QWidgetPrivate::mouseGrabber = this; + +#ifndef QT_NO_CURSOR + QApplication::setOverrideCursor(cursor()); +#endif + } +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if (isVisible() && !qt_nograb()) { + if (QWidgetPrivate::mouseGrabber && QWidgetPrivate::mouseGrabber != this) + QWidgetPrivate::mouseGrabber->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + WId id = effectiveWinId(); + id->SetPointerCapture(true); + QWidgetPrivate::mouseGrabber = this; + + QApplication::setOverrideCursor(cursor); + } +} +#endif + +void QWidget::releaseMouse() +{ + if (!qt_nograb() && QWidgetPrivate::mouseGrabber == this) { + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + if(!window()->isModal()) { + WId id = effectiveWinId(); + id->SetPointerCapture(false); + } + QWidgetPrivate::mouseGrabber = 0; +#ifndef QT_NO_CURSOR + QApplication::restoreOverrideCursor(); +#endif + } +} + +void QWidget::activateWindow() +{ + Q_D(QWidget); + + QWidget *tlw = window(); + if (tlw->isVisible()) { + window()->createWinId(); + QSymbianControl *id = static_cast<QSymbianControl *>(tlw->internalWinId()); + if (!id->IsFocused()) + id->setFocusSafely(true); + } +} + +#ifndef QT_NO_CURSOR + +void QWidgetPrivate::setCursor_sys(const QCursor &cursor) +{ + Q_UNUSED(cursor); + Q_Q(QWidget); + qt_symbian_set_cursor(q, false); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + Q_Q(QWidget); + qt_symbian_set_cursor(q, false); +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidget_win.cpp b/src/gui/kernel/qwidget_win.cpp new file mode 100644 index 0000000000..a02c5ba008 --- /dev/null +++ b/src/gui/kernel/qwidget_win.cpp @@ -0,0 +1,2139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qimage.h" +#include "qlayout.h" +#include "qpainter.h" +#include "qstack.h" +#include "qt_windows.h" +#include "qwidget.h" +#include "qwidget_p.h" +#include "private/qbackingstore_p.h" +#include "private/qwindowsurface_raster_p.h" + +#include "qscrollbar.h" +#include "qabstractscrollarea.h" +#include <private/qabstractscrollarea_p.h> + +#include <qdebug.h> + +#include <private/qapplication_p.h> +#include <private/qwininputcontext_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qsystemlibrary_p.h> + +#if defined(Q_WS_WINCE) +#include "qguifunctions_wince.h" +QT_USE_NAMESPACE +extern void qt_wince_maximize(QWidget *widget); //defined in qguifunctions_wince.cpp +extern void qt_wince_unmaximize(QWidget *widget); //defined in qguifunctions_wince.cpp +extern void qt_wince_minimize(HWND hwnd); //defined in qguifunctions_wince.cpp +extern void qt_wince_full_screen(HWND hwnd, bool fullScreen, UINT swpf); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_mobile(); //defined in qguifunctions_wince.cpp +#endif + +typedef BOOL (WINAPI *PtrSetLayeredWindowAttributes)(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); +static PtrSetLayeredWindowAttributes ptrSetLayeredWindowAttributes = 0; + +#ifndef QT_NO_DIRECTDRAW +#include <ddraw.h> +#include <private/qimage_p.h> +static IDirectDraw *qt_ddraw_object; +static IDirectDrawSurface *qt_ddraw_primary; +#endif + + + +#if defined(QT_NON_COMMERCIAL) +#include "qnc_win.h" +#endif + +#if !defined(WS_EX_TOOLWINDOW) +#define WS_EX_TOOLWINDOW 0x00000080 +#endif + +#if !defined(GWLP_WNDPROC) +#define GWLP_WNDPROC GWL_WNDPROC +#endif + +//#define TABLET_DEBUG +#define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE \ + | PK_ORIENTATION | PK_CURSOR | PK_Z) +#define PACKETMODE 0 +#include <wintab.h> +#include <pktdef.h> + +QT_BEGIN_NAMESPACE + +typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL); +typedef BOOL (API *PtrWTClose)(HCTX); +typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID); +typedef BOOL (API *PtrWTEnable)(HCTX, BOOL); +typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL); +typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID); +typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT); +typedef int (API *PtrWTQueueSizeGet)(HCTX); +typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int); + +static PtrWTOpen ptrWTOpen = 0; +static PtrWTClose ptrWTClose = 0; +static PtrWTInfo ptrWTInfo = 0; +static PtrWTQueueSizeGet ptrWTQueueSizeGet = 0; +static PtrWTQueueSizeSet ptrWTQueueSizeSet = 0; +#ifndef QT_NO_TABLETEVENT +static void init_wintab_functions(); +static void qt_tablet_init(); +static void qt_tablet_cleanup(); +#endif // QT_NO_TABLETEVENT +extern HCTX qt_tablet_context; +extern bool qt_tablet_tilt_support; + +static QWidget *qt_tablet_widget = 0; +QWidget* qt_get_tablet_widget() +{ + return qt_tablet_widget; +} + +extern bool qt_is_gui_used; + +#ifndef QT_NO_TABLETEVENT +static void init_wintab_functions() +{ +#if defined(Q_OS_WINCE) + return; +#else + if (!qt_is_gui_used) + return; + QSystemLibrary library(QLatin1String("wintab32")); + ptrWTOpen = (PtrWTOpen)library.resolve("WTOpenW"); + ptrWTInfo = (PtrWTInfo)library.resolve("WTInfoW"); + ptrWTClose = (PtrWTClose)library.resolve("WTClose"); + ptrWTQueueSizeGet = (PtrWTQueueSizeGet)library.resolve("WTQueueSizeGet"); + ptrWTQueueSizeSet = (PtrWTQueueSizeSet)library.resolve("WTQueueSizeSet"); +#endif // Q_OS_WINCE +} + +static void qt_tablet_init() +{ + static bool firstTime = true; + if (!firstTime) + return; + firstTime = false; + qt_tablet_widget = new QWidget(0); + qt_tablet_widget->createWinId(); + qt_tablet_widget->setObjectName(QLatin1String("Qt internal tablet widget")); + // We don't need this internal widget to appear in QApplication::topLevelWidgets() + if (QWidgetPrivate::allWidgets) + QWidgetPrivate::allWidgets->remove(qt_tablet_widget); + LOGCONTEXT lcMine; + qAddPostRoutine(qt_tablet_cleanup); + struct tagAXIS tpOri[3]; + init_wintab_functions(); + if (ptrWTInfo && ptrWTOpen && ptrWTQueueSizeGet && ptrWTQueueSizeSet) { + // make sure we have WinTab + if (!ptrWTInfo(0, 0, NULL)) { +#ifdef TABLET_DEBUG + qWarning("QWidget: Wintab services not available"); +#endif + return; + } + + // some tablets don't support tilt, check if it is possible, + qt_tablet_tilt_support = ptrWTInfo(WTI_DEVICES, DVC_ORIENTATION, &tpOri); + if (qt_tablet_tilt_support) { + // check for azimuth and altitude + qt_tablet_tilt_support = tpOri[0].axResolution && tpOri[1].axResolution; + } + // build our context from the default context + ptrWTInfo(WTI_DEFSYSCTX, 0, &lcMine); + // Go for the raw coordinates, the tablet event will return good stuff + lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES; + lcMine.lcPktData = PACKETDATA; + lcMine.lcPktMode = PACKETMODE; + lcMine.lcMoveMask = PACKETDATA; + lcMine.lcOutOrgX = 0; + lcMine.lcOutExtX = lcMine.lcInExtX; + lcMine.lcOutOrgY = 0; + lcMine.lcOutExtY = -lcMine.lcInExtY; + qt_tablet_context = ptrWTOpen(qt_tablet_widget->winId(), &lcMine, true); +#ifdef TABLET_DEBUG + qDebug("Tablet is %p", qt_tablet_context); +#endif + if (!qt_tablet_context) { +#ifdef TABLET_DEBUG + qWarning("QWidget: Failed to open the tablet"); +#endif + return; + } + // Set the size of the Packet Queue to the correct size... + int currSize = ptrWTQueueSizeGet(qt_tablet_context); + if (!ptrWTQueueSizeSet(qt_tablet_context, QT_TABLET_NPACKETQSIZE)) { + // Ideally one might want to use a smaller + // multiple, but for now, since we managed to destroy + // the existing Q with the previous call, set it back + // to the other size, which should work. If not, + // there will be trouble. + if (!ptrWTQueueSizeSet(qt_tablet_context, currSize)) { + Q_ASSERT_X(0, "Qt::Internal", "There is no packet queue for" + " the tablet. The tablet will not work"); + } + } + } +} + +static void qt_tablet_cleanup() +{ + if (ptrWTClose) + ptrWTClose(qt_tablet_context); + delete qt_tablet_widget; + qt_tablet_widget = 0; +} +#endif // QT_NO_TABLETEVENT + +const QString qt_reg_winclass(QWidget *w); // defined in qapplication_win.cpp + +#ifndef QT_NO_DRAGANDDROP +void qt_olednd_unregister(QWidget* widget, QOleDropTarget *dst); // dnd_win +QOleDropTarget* qt_olednd_register(QWidget* widget); +#endif + +extern bool qt_nograb(); +extern HRGN qt_win_bitmapToRegion(const QBitmap& bitmap); + +static QWidget *mouseGrb = 0; +static QCursor *mouseGrbCur = 0; +static QWidget *keyboardGrb = 0; +static HHOOK journalRec = 0; + +extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND, UINT, WPARAM, LPARAM); + +#define XCOORD_MAX 16383 +#define WRECT_MAX 16383 + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ + +#ifndef Q_WS_WINCE +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_Q(QWidget); + static int sw = -1, sh = -1; + + Qt::WindowType type = q->windowType(); + Qt::WindowFlags flags = data.window_flags; + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + bool tool = (type == Qt::Tool || type == Qt::Drawer); + + HINSTANCE appinst = qWinAppInst(); + HWND parentw, destroyw = 0; + WId id = 0; + + QString windowClassName = qt_reg_winclass(q); + + if (!window) // always initialize + initializeWindow = true; + + if (popup) + flags |= Qt::WindowStaysOnTopHint; // a popup stays on top + + if (sw < 0) { // get the (primary) screen size + sw = GetSystemMetrics(SM_CXSCREEN); + sh = GetSystemMetrics(SM_CYSCREEN); + } + + if (desktop && !q->testAttribute(Qt::WA_DontShowOnScreen)) { // desktop widget + popup = false; // force this flags off + data.crect.setRect(GetSystemMetrics(76 /* SM_XVIRTUALSCREEN */), GetSystemMetrics(77 /* SM_YVIRTUALSCREEN */), + GetSystemMetrics(78 /* SM_CXVIRTUALSCREEN */), GetSystemMetrics(79 /* SM_CYVIRTUALSCREEN */)); + } + + parentw = q->parentWidget() ? q->parentWidget()->effectiveWinId() : 0; + + QString title; + int style = WS_CHILD; + int exsty = 0; + + if (window) { + style = GetWindowLong(window, GWL_STYLE); + if (!style) + qErrnoWarning("QWidget::create: GetWindowLong failed"); + topLevel = false; // #### needed for some IE plugins?? + } else if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) { + style = WS_POPUP; + } else if (topLevel && !desktop) { + if (flags & Qt::FramelessWindowHint) + style = WS_POPUP; // no border + else if (flags & Qt::WindowTitleHint) + style = WS_OVERLAPPED; + else + style = 0; + } + if (!desktop) { + // 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 +#ifndef Q_FLATTEN_EXPOSE + style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ; +#endif + if (topLevel) { + if ((type == Qt::Window || dialog || tool)) { + if (!(flags & Qt::FramelessWindowHint)) { + style |= WS_POPUP; + if (!(flags & Qt::MSWindowsFixedSizeDialogHint)) + style |= WS_THICKFRAME; + else + style |= WS_DLGFRAME; + } + if (flags & Qt::WindowTitleHint) + style |= WS_CAPTION; + if (flags & Qt::WindowSystemMenuHint) + style |= WS_SYSMENU; + if (flags & Qt::WindowMinimizeButtonHint) + style |= WS_MINIMIZEBOX; + if (shouldShowMaximizeButton()) + style |= WS_MAXIMIZEBOX; + if (tool) + exsty |= WS_EX_TOOLWINDOW; + if (flags & Qt::WindowContextHelpButtonHint) + exsty |= WS_EX_CONTEXTHELP; + } else { + exsty |= WS_EX_TOOLWINDOW; + } + } + } + + if (flags & Qt::WindowTitleHint) { + title = q->isWindow() ? qAppName() : q->objectName(); + } + + // The Qt::WA_WState_Created flag is checked by translateConfigEvent() in + // qapplication_win.cpp. We switch it off temporarily to avoid move + // and resize events during creationt + q->setAttribute(Qt::WA_WState_Created, false); + + if (window) { // override the old window + if (destroyOldWindow) + destroyw = data.winid; + id = window; + setWinId(window); + LONG res = SetWindowLong(window, GWL_STYLE, style); + if (!res) + qErrnoWarning("QWidget::create: Failed to set window style"); +#ifdef _WIN64 + res = SetWindowLongPtr( window, GWLP_WNDPROC, (LONG_PTR)QtWndProc ); +#else + res = SetWindowLong( window, GWL_WNDPROC, (LONG)QtWndProc ); +#endif + if (!res) + qErrnoWarning("QWidget::create: Failed to set window procedure"); + } else if (desktop) { // desktop widget + id = GetDesktopWindow(); +// QWidget *otherDesktop = QWidget::find(id); // is there another desktop? +// if (otherDesktop && otherDesktop->testWFlags(Qt::WPaintDesktop)) { +// otherDesktop->d_func()->setWinId(0); // remove id from widget mapper +// d->setWinId(id); // make sure otherDesktop is +// otherDesktop->d_func()->setWinId(id); // found first +// } else { + setWinId(id); +// } + } else if (topLevel) { // create top-level widget + if (popup) + parentw = 0; + + const bool wasMoved = q->testAttribute(Qt::WA_Moved); + int x = wasMoved ? data.crect.left() : CW_USEDEFAULT; + int y = wasMoved ? data.crect.top() : CW_USEDEFAULT; + int w = CW_USEDEFAULT; + int h = CW_USEDEFAULT; + + // Adjust for framestrut when needed + RECT rect = {0,0,0,0}; + bool isVisibleOnScreen = !q->testAttribute(Qt::WA_DontShowOnScreen); + if (isVisibleOnScreen && AdjustWindowRectEx(&rect, style & ~WS_OVERLAPPED, FALSE, exsty)) { + QTLWExtra *td = maybeTopData(); + if (wasMoved && (td && !td->posFromMove)) { + x = data.crect.x() + rect.left; + y = data.crect.y() + rect.top; + } + + if (q->testAttribute(Qt::WA_Resized)) { + w = data.crect.width() + (rect.right - rect.left); + h = data.crect.height() + (rect.bottom - rect.top); + } + } + //update position & initial size of POPUP window + if (isVisibleOnScreen && topLevel && initializeWindow && (style & WS_POPUP)) { + if (!q->testAttribute(Qt::WA_Resized)) { + w = sw/2; + h = 4*sh/10; + if (extra) { + int dx = rect.right - rect.left; + int dy = rect.bottom - rect.top; + w = qMin(w, extra->maxw + dx); + h = qMin(h, extra->maxh + dy); + w = qMax(w, extra->minw + dx); + h = qMax(h, extra->minh + dy); + } + } + if (!wasMoved) { + x = sw/2 - w/2; + y = sh/2 - h/2; + } + } + + id = CreateWindowEx(exsty, reinterpret_cast<const wchar_t *>(windowClassName.utf16()), + reinterpret_cast<const wchar_t *>(title.utf16()), style, + x, y, w, h, + parentw, NULL, appinst, NULL); + if (!id) + qErrnoWarning("QWidget::create: Failed to create window"); + setWinId(id); + if ((flags & Qt::WindowStaysOnTopHint) || (type == Qt::ToolTip)) { + SetWindowPos(id, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + if (flags & Qt::WindowStaysOnBottomHint) + qWarning() << "QWidget: Incompatible window flags: the window can't be on top and on bottom at the same time"; + } else if (flags & Qt::WindowStaysOnBottomHint) + SetWindowPos(id, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + winUpdateIsOpaque(); + } else if (q->testAttribute(Qt::WA_NativeWindow) || paintOnScreen()) { // create child widget + id = CreateWindowEx(exsty, reinterpret_cast<const wchar_t *>(windowClassName.utf16()), + reinterpret_cast<const wchar_t *>(title.utf16()), style, + data.crect.left(), data.crect.top(), data.crect.width(), data.crect.height(), + parentw, NULL, appinst, NULL); + if (!id) + qErrnoWarning("QWidget::create: Failed to create window"); + SetWindowPos(id, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + setWinId(id); + } + + if (desktop) { + q->setAttribute(Qt::WA_WState_Visible); + } else if (topLevel && !q->testAttribute(Qt::WA_DontShowOnScreen)) { + RECT cr; + GetClientRect(id, &cr); + // one cannot trust cr.left and cr.top, use a correction POINT instead + POINT pt; + pt.x = 0; + pt.y = 0; + ClientToScreen(id, &pt); + + if (data.crect.width() == 0 || data.crect.height() == 0) { + data.crect = QRect(pt.x, pt.y, data.crect.width(), data.crect.height()); + } else { + data.crect = QRect(QPoint(pt.x, pt.y), + QPoint(pt.x + cr.right - 1, pt.y + cr.bottom - 1)); + } + + if (data.fstrut_dirty) { + // be nice to activeqt + updateFrameStrut(); + } + } + + if (topLevel) { + if (data.window_flags & Qt::CustomizeWindowHint + && data.window_flags & Qt::WindowTitleHint) { + HMENU systemMenu = GetSystemMenu((HWND)q->internalWinId(), FALSE); + if (data.window_flags & Qt::WindowCloseButtonHint) + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED); + else + EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED); + } + } + + q->setAttribute(Qt::WA_WState_Created); // accept move/resize events + hd = 0; // no display context + + if (q->testAttribute(Qt::WA_AcceptTouchEvents)) + registerTouchWindow(); + + if (window) { // got window from outside + if (IsWindowVisible(window)) + q->setAttribute(Qt::WA_WState_Visible); + else + q->setAttribute(Qt::WA_WState_Visible, false); + } + + if (extra && !extra->mask.isEmpty()) + setMask_sys(extra->mask); + +#if defined(QT_NON_COMMERCIAL) + QT_NC_WIDGET_CREATE +#endif + + if (q->hasFocus() && q->testAttribute(Qt::WA_InputMethodEnabled)) + q->inputContext()->setFocusWidget(q); + + if (destroyw) { + DestroyWindow(destroyw); + } + +#ifndef QT_NO_TABLETEVENT + if (q != qt_tablet_widget && QWidgetPrivate::mapper) + qt_tablet_init(); +#endif // QT_NO_TABLETEVENT + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + + if (maybeTopData() && maybeTopData()->opacity != 255) + q->setWindowOpacity(maybeTopData()->opacity/255.); + + if (topLevel && (data.crect.width() == 0 || data.crect.height() == 0)) { + q->setAttribute(Qt::WA_OutsideWSRange, true); + } + + if (!topLevel && q->testAttribute(Qt::WA_NativeWindow) && q->testAttribute(Qt::WA_Mapped)) { + Q_ASSERT(q->internalWinId()); + ShowWindow(q->internalWinId(), SW_SHOW); + } +} + +#endif //Q_WS_WINCE + + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(d->effectiveRectFor(geometry())); + d->deactivateWidgetCleanup(); + if (testAttribute(Qt::WA_WState_Created)) { + setAttribute(Qt::WA_WState_Created, false); + for(int i = 0; i < d->children.size(); ++i) { // destroy all widget children + register QObject *obj = d->children.at(i); + if (obj->isWidgetType()) + ((QWidget*)obj)->destroy(destroySubWindows, + destroySubWindows); + } + if (mouseGrb == this) + releaseMouse(); + if (keyboardGrb == this) + releaseKeyboard(); + if (testAttribute(Qt::WA_ShowModal)) // just be sure we leave modal + QApplicationPrivate::leaveModal(this); + else if ((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); + if (destroyWindow && !(windowType() == Qt::Desktop) && internalWinId()) { + DestroyWindow(internalWinId()); + } +#ifdef Q_WS_WINCE + if (destroyWindow && (windowType() == Qt::Desktop) && !GetDesktopWindow()) { + DestroyWindow(internalWinId()); + } + +#endif + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + } +} + +void QWidgetPrivate::reparentChildren() +{ + Q_Q(QWidget); + QObjectList chlist = q->children(); + for(int i = 0; i < chlist.size(); ++i) { // reparent children + QObject *obj = chlist.at(i); + if (obj->isWidgetType()) { + QWidget *w = (QWidget *)obj; + if ((w->windowType() == Qt::Popup)) { + ; + } else if (w->isWindow()) { + bool showIt = w->isVisible(); + QPoint old_pos = w->pos(); + w->setParent(q, w->windowFlags()); + w->move(old_pos); + if (showIt) + w->show(); + } else { + w->d_func()->invalidateBuffer(w->rect()); + SetParent(w->effectiveWinId(), q->effectiveWinId()); + w->d_func()->reparentChildren(); + } + } + } +} + +void QWidgetPrivate::setParent_sys(QWidget *parent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + + WId old_winid = data.winid; + // hide and reparent our own window away. Otherwise we might get + // destroyed when emitting the child remove event below. See QWorkspace. + if (q->isVisible() && data.winid) { + ShowWindow(data.winid, SW_HIDE); + SetParent(data.winid, 0); + } + bool dropSiteWasRegistered = false; + if (q->testAttribute(Qt::WA_DropSiteRegistered)) { + dropSiteWasRegistered = true; + q->setAttribute(Qt::WA_DropSiteRegistered, false); // ole dnd unregister (we will register again below) + } + + if ((q->windowType() == Qt::Desktop)) + old_winid = 0; + setWinId(0); + + QObjectPrivate::setParent_helper(parent); + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + + data.window_flags = f; + data.fstrut_dirty = true; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state + // (but we recreate the winId for the widget being reparented, again for compatibility) + if (wasCreated || (!q->isWindow() && parent->testAttribute(Qt::WA_WState_Created))) + createWinId(); + if (q->isWindow() || (!parent || parent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (wasCreated) { + reparentChildren(); + } + + if (extra && !extra->mask.isEmpty()) { + QRegion r = extra->mask; + extra->mask = QRegion(); + q->setMask(r); + } + if (extra && extra->topextra && !extra->topextra->caption.isEmpty()) { + setWindowIcon_sys(true); + setWindowTitle_helper(extra->topextra->caption); + } + if (old_winid) + DestroyWindow(old_winid); + + if (q->testAttribute(Qt::WA_AcceptDrops) || dropSiteWasRegistered + || (!q->isWindow() && q->parentWidget() && q->parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) + q->setAttribute(Qt::WA_DropSiteRegistered, true); + +#ifdef Q_WS_WINCE + // Show borderless toplevel windows in tasklist & NavBar + if (!parent) { + QString txt = q->windowTitle().isEmpty()?qAppName():q->windowTitle(); + SetWindowText(q->internalWinId(), (wchar_t*)txt.utf16()); + } +#endif + invalidateBuffer(q->rect()); +} + + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + QWidget *parentWindow = window(); + QWExtra *extra = parentWindow->d_func()->extra; + if (!isVisible() || parentWindow->isMinimized() || !testAttribute(Qt::WA_WState_Created) || !internalWinId() + || (extra +#ifndef QT_NO_GRAPHICSVIEW + && extra->proxyWidget +#endif //QT_NO_GRAPHICSVIEW + )) { + if (extra && extra->topextra && extra->topextra->embedded) { + QPoint pt = mapTo(parentWindow, pos); + POINT p = {pt.x(), pt.y()}; + ClientToScreen(parentWindow->effectiveWinId(), &p); + return QPoint(p.x, p.y); + } else { + QPoint toGlobal = mapTo(parentWindow, pos) + parentWindow->pos(); + // Adjust for window decorations + toGlobal += parentWindow->geometry().topLeft() - parentWindow->frameGeometry().topLeft(); + return toGlobal; + } + } + POINT p; + QPoint tmp = d->mapToWS(pos); + p.x = tmp.x(); + p.y = tmp.y(); + ClientToScreen(internalWinId(), &p); + return QPoint(p.x, p.y); +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + QWidget *parentWindow = window(); + QWExtra *extra = parentWindow->d_func()->extra; + if (!isVisible() || parentWindow->isMinimized() || !testAttribute(Qt::WA_WState_Created) || !internalWinId() + || (extra +#ifndef QT_NO_GRAPHICSVIEW + && extra->proxyWidget +#endif //QT_NO_GRAPHICSVIEW + )) { + if (extra && extra->topextra && extra->topextra->embedded) { + POINT p = {pos.x(), pos.y()}; + ScreenToClient(parentWindow->effectiveWinId(), &p); + return mapFrom(parentWindow, QPoint(p.x, p.y)); + } else { + QPoint fromGlobal = mapFrom(parentWindow, pos - parentWindow->pos()); + // Adjust for window decorations + fromGlobal -= parentWindow->geometry().topLeft() - parentWindow->frameGeometry().topLeft(); + return fromGlobal; + } + } + POINT p; + p.x = pos.x(); + p.y = pos.y(); + ScreenToClient(internalWinId(), &p); + return d->mapFromWS(QPoint(p.x, p.y)); +} + +void QWidgetPrivate::updateSystemBackground() {} + +#ifndef QT_NO_CURSOR +void QWidgetPrivate::setCursor_sys(const QCursor &cursor) +{ + Q_UNUSED(cursor); + Q_Q(QWidget); + qt_win_set_cursor(q, false); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + Q_Q(QWidget); + qt_win_set_cursor(q, false); +} +#endif + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + if (!q->isWindow()) + return; + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + SetWindowText(q->internalWinId(), (wchar_t*)caption.utf16()); +} + +HICON qt_createIcon(QIcon icon, int xSize, int ySize, QPixmap **cache) +{ + HICON result = 0; + if (!icon.isNull()) { // valid icon + QSize size = icon.actualSize(QSize(xSize, ySize)); + QPixmap pm = icon.pixmap(size); + if (pm.isNull()) + return 0; + + result = pm.toWinHICON(); + + if (cache) { + delete *cache; + *cache = new QPixmap(pm);; + } + } + return result; +} + +void QWidgetPrivate::setWindowIcon_sys(bool forceReset) +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow()) + return; + QTLWExtra* x = topData(); + if (x->iconPixmap && !forceReset) + // already been set + return; + + if (x->winIconBig) { + DestroyIcon(x->winIconBig); + x->winIconBig = 0; + } + if (x->winIconSmall) { + DestroyIcon(x->winIconSmall); + x->winIconSmall = 0; + } + + x->winIconSmall = qt_createIcon(q->windowIcon(), + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + &(x->iconPixmap)); + x->winIconBig = qt_createIcon(q->windowIcon(), + GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), + &(x->iconPixmap)); + if (x->winIconBig) { + SendMessage(q->internalWinId(), WM_SETICON, 0 /* ICON_SMALL */, (LPARAM)x->winIconSmall); + SendMessage(q->internalWinId(), WM_SETICON, 1 /* ICON_BIG */, (LPARAM)x->winIconBig); + } else { + SendMessage(q->internalWinId(), WM_SETICON, 0 /* ICON_SMALL */, (LPARAM)x->winIconSmall); + SendMessage(q->internalWinId(), WM_SETICON, 1 /* ICON_BIG */, (LPARAM)x->winIconSmall); + } +} + + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_UNUSED(iconText); +} + + +QCursor *qt_grab_cursor() +{ + return mouseGrbCur; +} + +// The procedure does nothing, but is required for mousegrabbing to work +#ifndef Q_WS_WINCE +LRESULT QT_WIN_CALLBACK qJournalRecordProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + return CallNextHookEx(journalRec, nCode, wParam, lParam); +} +#endif //Q_WS_WINCE + +/* Works only as long as pointer is inside the application's window, + which is good enough for QDockWidget. + + Doesn't call SetWindowsHookEx() - this function causes a system-wide + freeze if any other app on the system installs a hook and fails to + process events. */ +void QWidgetPrivate::grabMouseWhileInWindow() +{ + Q_Q(QWidget); + if (!qt_nograb()) { + if (mouseGrb) + mouseGrb->releaseMouse(); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + SetCapture(q->effectiveWinId()); + mouseGrb = q; +#ifndef QT_NO_CURSOR + mouseGrbCur = new QCursor(mouseGrb->cursor()); +#endif + } +} + +#ifndef Q_WS_WINCE +void QWidget::grabMouse() +{ + if (!qt_nograb()) { + if (mouseGrb) + mouseGrb->releaseMouse(); + journalRec = SetWindowsHookEx(WH_JOURNALRECORD, (HOOKPROC)qJournalRecordProc, GetModuleHandle(0), 0); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + SetCapture(effectiveWinId()); + mouseGrb = this; +#ifndef QT_NO_CURSOR + mouseGrbCur = new QCursor(mouseGrb->cursor()); +#endif + } +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if (!qt_nograb()) { + if (mouseGrb) + mouseGrb->releaseMouse(); + journalRec = SetWindowsHookEx(WH_JOURNALRECORD, (HOOKPROC)qJournalRecordProc, GetModuleHandle(0), 0); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + SetCapture(effectiveWinId()); + mouseGrbCur = new QCursor(cursor); + SetCursor(mouseGrbCur->handle()); + mouseGrb = this; + } +} +#endif + +void QWidget::releaseMouse() +{ + if (!qt_nograb() && mouseGrb == this) { + ReleaseCapture(); + if (journalRec) { + UnhookWindowsHookEx(journalRec); + journalRec = 0; + } + if (mouseGrbCur) { + delete mouseGrbCur; + mouseGrbCur = 0; + } + mouseGrb = 0; + } +} +#endif + +void QWidget::grabKeyboard() +{ + if (!qt_nograb()) { + if (keyboardGrb) + keyboardGrb->releaseKeyboard(); + keyboardGrb = this; + } +} + +void QWidget::releaseKeyboard() +{ + if (!qt_nograb() && keyboardGrb == this) + keyboardGrb = 0; +} + + +QWidget *QWidget::mouseGrabber() +{ + return mouseGrb; +} + +QWidget *QWidget::keyboardGrabber() +{ + return keyboardGrb; +} + +void QWidget::activateWindow() +{ + window()->createWinId(); + SetForegroundWindow(window()->internalWinId()); +} + +#ifndef Q_WS_WINCE +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + + int max = SW_MAXIMIZE; + int min = SW_MINIMIZE; + + int normal = SW_SHOWNOACTIVATE; + if (newstate & Qt::WindowActive) { + max = SW_SHOWMAXIMIZED; + min = SW_SHOWMINIMIZED; + normal = SW_SHOWNORMAL; + } + + if (isWindow()) { + createWinId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + + // Ensure the initial size is valid, since we store it as normalGeometry below. + if (!testAttribute(Qt::WA_Resized) && !isVisible()) + adjustSize(); + + if ((oldstate & Qt::WindowMaximized) != (newstate & Qt::WindowMaximized)) { + if (newstate & Qt::WindowMaximized && !(oldstate & Qt::WindowFullScreen)) + d->topData()->normalGeometry = geometry(); + if (isVisible() && !(newstate & Qt::WindowMinimized)) { + ShowWindow(internalWinId(), (newstate & Qt::WindowMaximized) ? max : normal); + if (!(newstate & Qt::WindowFullScreen)) { + QRect r = d->topData()->normalGeometry; + if (!(newstate & Qt::WindowMaximized) && r.width() >= 0) { + if (pos() != r.topLeft() || size() !=r.size()) { + d->topData()->normalGeometry = QRect(0,0,-1,-1); + setGeometry(r); + } + } + } else { + d->updateFrameStrut(); + } + } + } + + if ((oldstate & Qt::WindowFullScreen) != (newstate & Qt::WindowFullScreen)) { + if (newstate & Qt::WindowFullScreen) { + if (d->topData()->normalGeometry.width() < 0 && !(oldstate & Qt::WindowMaximized)) + d->topData()->normalGeometry = geometry(); + d->topData()->savedFlags = Qt::WindowFlags(GetWindowLong(internalWinId(), GWL_STYLE)); +#ifndef Q_FLATTEN_EXPOSE + UINT style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP; +#else + UINT style = WS_POPUP; +#endif + if (ulong(d->topData()->savedFlags) & WS_SYSMENU) + style |= WS_SYSMENU; + if (isVisible()) + style |= WS_VISIBLE; + SetWindowLong(internalWinId(), GWL_STYLE, style); + QRect r = QApplication::desktop()->screenGeometry(this); + UINT swpf = SWP_FRAMECHANGED; + if (newstate & Qt::WindowActive) + swpf |= SWP_NOACTIVATE; + + SetWindowPos(internalWinId(), HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf); + d->updateFrameStrut(); + } else { + UINT style = d->topData()->savedFlags; + if (isVisible()) + style |= WS_VISIBLE; + SetWindowLong(internalWinId(), GWL_STYLE, style); + + UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE; + if (newstate & Qt::WindowActive) + swpf |= SWP_NOACTIVATE; + SetWindowPos(internalWinId(), 0, 0, 0, 0, 0, swpf); + d->updateFrameStrut(); + + // preserve maximized state + if (isVisible()) + ShowWindow(internalWinId(), (newstate & Qt::WindowMaximized) ? max : normal); + + if (!(newstate & Qt::WindowMaximized)) { + QRect r = d->topData()->normalGeometry; + d->topData()->normalGeometry = QRect(0,0,-1,-1); + if (r.isValid()) + setGeometry(r); + } + } + } + + if ((oldstate & Qt::WindowMinimized) != (newstate & Qt::WindowMinimized)) { + if (isVisible()) + ShowWindow(internalWinId(), (newstate & Qt::WindowMinimized) ? min : + (newstate & Qt::WindowMaximized) ? max : normal); + } + } + data->window_state = newstate; + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} +#endif //Q_WS_WINCE + + +/* + \internal + Platform-specific part of QWidget::hide(). +*/ + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + deactivateWidgetCleanup(); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); +#ifdef Q_WS_WINCE + if (!qt_wince_is_mobile() && q->isFullScreen()) { + HWND handle = FindWindow(L"HHTaskBar", L""); + if (handle) { + ShowWindow(handle, 1); + EnableWindow(handle, true); + } + } +#endif + if (q->windowFlags() != Qt::Desktop) { + if ((q->windowFlags() & Qt::Popup) && q->internalWinId()) + ShowWindow(q->internalWinId(), SW_HIDE); + else if (q->internalWinId()) + SetWindowPos(q->internalWinId(),0, 0,0,0,0, SWP_HIDEWINDOW|SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER); + } + if (q->isWindow()) { + if (QWidgetBackingStore *bs = maybeBackingStore()) + bs->releaseBuffer(); + } else { + invalidateBuffer(q->rect()); + } + q->setAttribute(Qt::WA_Mapped, false); +} + + +/* + \internal + Platform-specific part of QWidget::show(). +*/ +#ifndef Q_WS_WINCE +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); +#if defined(QT_NON_COMMERCIAL) + QT_NC_SHOW_WINDOW +#endif + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + q->setAttribute(Qt::WA_Mapped); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + return; + } + + if (data.window_flags & Qt::Window) { + QTLWExtra *extra = topData(); + if (!extra->hotkeyRegistered) { + // Try to set the hotkey using information from STARTUPINFO + STARTUPINFO startupInfo; + GetStartupInfo(&startupInfo); + // If STARTF_USEHOTKEY is set, hStdInput is the virtual keycode + if (startupInfo.dwFlags & 0x00000200) { + WPARAM hotKey = (WPARAM)startupInfo.hStdInput; + SendMessage(data.winid, WM_SETHOTKEY, hotKey, 0); + } + extra->hotkeyRegistered = 1; + } + } + + int sm = SW_SHOWNORMAL; + bool fakedMaximize = false; + if (q->isWindow()) { + if (q->isMinimized()) { + sm = SW_SHOWMINIMIZED; + if (!IsWindowVisible(q->internalWinId())) + sm = SW_SHOWMINNOACTIVE; + } else if (q->isMaximized()) { + sm = SW_SHOWMAXIMIZED; + // Windows will not behave correctly when we try to maximize a window which does not + // have minimize nor maximize buttons in the window frame. Windows would then ignore + // non-available geometry, and rather maximize the widget to the full screen, minus the + // window frame (caption). So, we do a trick here, by adding a maximize button before + // maximizing the widget, and then remove the maximize button afterwards. + Qt::WindowFlags &flags = data.window_flags; + if (flags & Qt::WindowTitleHint && + !(flags & (Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint))) { + fakedMaximize = TRUE; + int style = GetWindowLong(q->internalWinId(), GWL_STYLE); + SetWindowLong(q->internalWinId(), GWL_STYLE, style | WS_MAXIMIZEBOX); + } + } + } + if (q->testAttribute(Qt::WA_ShowWithoutActivating) + || (q->windowType() == Qt::Popup) + || (q->windowType() == Qt::ToolTip) + || (q->windowType() == Qt::Tool)) { + sm = SW_SHOWNOACTIVATE; + } + + + if (q->internalWinId()) + ShowWindow(q->internalWinId(), sm); + + if (fakedMaximize) { + int style = GetWindowLong(q->internalWinId(), GWL_STYLE); + SetWindowLong(q->internalWinId(), GWL_STYLE, style & ~WS_MAXIMIZEBOX); + SetWindowPos(q->internalWinId(), 0, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER + | SWP_FRAMECHANGED); + } + + if (q->internalWinId()) { + if (IsIconic(q->internalWinId())) + data.window_state |= Qt::WindowMinimized; + if (IsZoomed(q->internalWinId())) + data.window_state |= Qt::WindowMaximized; + // This is to resolve the problem where popups are opened from the + // system tray and not being implicitly activated + if (q->windowType() == Qt::Popup && + !q->parentWidget() && !qApp->activeWindow()) + q->activateWindow(); + } + + winSetupGestures(); + + invalidateBuffer(q->rect()); +} +#endif //Q_WS_WINCE + +void QWidgetPrivate::setFocus_sys() +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created) && q->window()->windowType() != Qt::Popup) + SetFocus(q->effectiveWinId()); +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) + SetWindowPos(q->internalWinId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) + SetWindowPos(q->internalWinId(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::stackUnder_sys(QWidget* w) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId() && w->internalWinId()) + SetWindowPos(q->internalWinId(), w->internalWinId() , 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + invalidateBuffer(q->rect()); +} + + +/* + Helper function for non-toplevel widgets. Helps to map Qt's 32bit + coordinate system to Windpws's 16bit coordinate system. + + This code is duplicated from the X11 code, so any changes there + should also (most likely) be reflected here. + + (In all comments below: s/X/Windows/g) + */ + +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + /* + There are up to four different coordinate systems here: + Qt coordinate system for this widget. + X coordinate system for this widget (relative to wrect). + Qt coordinate system for parent + X coordinate system for parent (relative to parent's wrect). + */ + QRect validRange(-XCOORD_MAX,-XCOORD_MAX, 2*XCOORD_MAX, 2*XCOORD_MAX); + QRect wrectRange(-WRECT_MAX,-WRECT_MAX, 2*WRECT_MAX, 2*WRECT_MAX); + QRect wrect; + //xrect is the X geometry of my X widget. (starts out in parent's Qt coord sys, and ends up in parent's X coord sys) + QRect xrect = data.crect; + + const QWidget *const parent = q->parentWidget(); + QRect parentWRect = parent->data->wrect; + + if (parentWRect.isValid()) { + // parent is clipped, and we have to clip to the same limit as parent + if (!parentWRect.contains(xrect)) { + xrect &= parentWRect; + wrect = xrect; + //translate from parent's to my Qt coord sys + wrect.translate(-data.crect.topLeft()); + } + //translate from parent's Qt coords to parent's X coords + xrect.translate(-parentWRect.topLeft()); + + } else { + // parent is not clipped, we may or may not have to clip + + if (data.wrect.isValid() && QRect(QPoint(),data.crect.size()).contains(data.wrect)) { + // This is where the main optimization is: we are already + // clipped, and if our clip is still valid, we can just + // move our window, and do not need to move or clip + // children + + QRect vrect = xrect & parent->rect(); + vrect.translate(-data.crect.topLeft()); //the part of me that's visible through parent, in my Qt coords + if (data.wrect.contains(vrect)) { + xrect = data.wrect; + xrect.translate(data.crect.topLeft()); + if (q->internalWinId()) + MoveWindow(q->internalWinId(), xrect.x(), xrect.y(), xrect.width(), xrect.height(), true); + return; + } + } + + if (!validRange.contains(xrect)) { + // we are too big, and must clip + xrect &=wrectRange; + wrect = xrect; + wrect.translate(-data.crect.topLeft()); + //parent's X coord system is equal to parent's Qt coord + //sys, so we don't need to map xrect. + } + + } + + + // unmap if we are outside the valid window system coord system + bool outsideRange = !xrect.isValid(); + bool mapWindow = false; + if (q->testAttribute(Qt::WA_OutsideWSRange) != outsideRange) { + q->setAttribute(Qt::WA_OutsideWSRange, outsideRange); + if (outsideRange) { + if (q->internalWinId()) + ShowWindow(q->internalWinId(), SW_HIDE); + q->setAttribute(Qt::WA_Mapped, false); + } else if (!q->isHidden()) { + mapWindow = true; + } + } + + if (outsideRange) + return; + + bool jump = (data.wrect != wrect); + data.wrect = wrect; + + // and now recursively for all children... + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(); + } + } + + // move ourselves to the new position and map (if necessary) after + // the movement. Rationale: moving unmapped windows is much faster + // than moving mapped windows + if (q->internalWinId()) { + if (!parent->internalWinId()) + xrect.translate(parent->mapTo(q->nativeParentWidget(), QPoint(0, 0))); + MoveWindow(q->internalWinId(), xrect.x(), xrect.y(), xrect.width(), xrect.height(), !jump); + } + if (mapWindow && !dontShow) { + q->setAttribute(Qt::WA_Mapped); + if (q->internalWinId()) + ShowWindow(q->internalWinId(), SW_SHOWNOACTIVATE); + } + + if (jump && q->internalWinId()) + InvalidateRect(q->internalWinId(), 0, false); + +} + +// +// The internal qWinRequestConfig, defined in qapplication_win.cpp, stores move, +// resize and setGeometry requests for a widget that is already +// processing a config event. The purpose is to avoid recursion. +// +void qWinRequestConfig(WId, int, int, int, int, int); + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (extra) { // any size restrictions? + w = qMin(w,extra->maxw); + h = qMin(h,extra->maxh); + w = qMax(w,extra->minw); + h = qMax(h,extra->minh); + } + if (q->isWindow()) + topData()->normalGeometry = QRect(0, 0, -1, -1); + + QSize oldSize(q->size()); + QPoint oldPos(q->pos()); + + if (!q->isWindow()) + isMove = (data.crect.topLeft() != QPoint(x, y)); + bool isResize = w != oldSize.width() || h != oldSize.height(); + + if (!isMove && !isResize) + return; + + if (isResize && !q->testAttribute(Qt::WA_StaticContents) && q->internalWinId()) + ValidateRgn(q->internalWinId(), 0); + +#ifdef Q_WS_WINCE + // On Windows CE we can't just fiddle around with the window state. + // Too much magic in setWindowState. + if (isResize && q->isMaximized()) + q->setWindowState(q->windowState() & ~Qt::WindowMaximized); +#else + if (isResize) + data.window_state &= ~Qt::WindowMaximized; +#endif + + if (data.window_state & Qt::WindowFullScreen) { + QTLWExtra *top = topData(); + + if (q->isWindow()) { + // We need to update these flags when we remove the full screen state + // or the frame will not be updated + UINT style = top->savedFlags; + if (q->isVisible()) + style |= WS_VISIBLE; + SetWindowLong(q->internalWinId(), GWL_STYLE, style); + + UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE; + if (data.window_state & Qt::WindowActive) + swpf |= SWP_NOACTIVATE; + SetWindowPos(q->internalWinId(), 0, 0, 0, 0, 0, swpf); + updateFrameStrut(); + } + data.window_state &= ~Qt::WindowFullScreen; + topData()->savedFlags = 0; + } + + QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData(); + const bool inTopLevelResize = tlwExtra ? tlwExtra->inTopLevelResize : false; + const bool isTranslucentWindow = !isOpaque && ptrUpdateLayeredWindowIndirect && (data.window_flags & Qt::FramelessWindowHint) + && GetWindowLong(q->internalWinId(), GWL_EXSTYLE) & Q_WS_EX_LAYERED; + + if (q->testAttribute(Qt::WA_WState_ConfigPending)) { // processing config event + if (q->internalWinId()) + qWinRequestConfig(q->internalWinId(), isMove ? 2 : 1, x, y, w, h); + } else { + if (!q->testAttribute(Qt::WA_DontShowOnScreen)) + q->setAttribute(Qt::WA_WState_ConfigPending); + if (q->windowType() == Qt::Desktop) { + data.crect.setRect(x, y, w, h); + } else if (q->isWindow()) { + QRect fs(frameStrut()); + if (extra) { + fs.setLeft(x - fs.left()); + fs.setTop(y - fs.top()); + fs.setRight((x + w - 1) + fs.right()); + fs.setBottom((y + h - 1) + fs.bottom()); + } + if (w == 0 || h == 0) { + q->setAttribute(Qt::WA_OutsideWSRange, true); + if (q->isVisible() && q->testAttribute(Qt::WA_Mapped)) + hide_sys(); + data.crect = QRect(x, y, w, h); + } else if (q->isVisible() && q->testAttribute(Qt::WA_OutsideWSRange)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); + + // put the window in its place and show it + MoveWindow(q->internalWinId(), fs.x(), fs.y(), fs.width(), fs.height(), true); + RECT rect; + if (!q->testAttribute(Qt::WA_DontShowOnScreen)) { + GetClientRect(q->internalWinId(), &rect); + data.crect.setRect(x, y, rect.right - rect.left, rect.bottom - rect.top); + } else { + data.crect.setRect(x, y, w, h); + } + + show_sys(); + } else if (!q->testAttribute(Qt::WA_DontShowOnScreen)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); +#ifndef Q_WS_WINCE + // If the window is hidden and in maximized state or minimized, instead of moving the + // window, set the normal position of the window. + WINDOWPLACEMENT wndpl; + GetWindowPlacement(q->internalWinId(), &wndpl); + if ((wndpl.showCmd == SW_MAXIMIZE && !IsWindowVisible(q->internalWinId())) || wndpl.showCmd == SW_SHOWMINIMIZED) { + RECT normal = {fs.x(), fs.y(), fs.x()+fs.width(), fs.y()+fs.height()}; + wndpl.rcNormalPosition = normal; + wndpl.showCmd = wndpl.showCmd == SW_SHOWMINIMIZED ? SW_SHOWMINIMIZED : SW_HIDE; + SetWindowPlacement(q->internalWinId(), &wndpl); + } else { +#else + if (data.window_state & Qt::WindowMaximized) { + qt_wince_maximize(q); + } else { +#endif + MoveWindow(q->internalWinId(), fs.x(), fs.y(), fs.width(), fs.height(), true); + } + if (!q->isVisible()) + InvalidateRect(q->internalWinId(), 0, FALSE); + RECT rect; + // If the layout has heightForWidth, the MoveWindow() above can + // change the size/position, so refresh them. + + if (isTranslucentWindow) { + data.crect.setRect(x, y, w, h); + } else { + GetClientRect(q->internalWinId(), &rect); + RECT rcNormalPosition ={0}; + // Use (0,0) as window position for embedded ActiveQt controls. + if (!tlwExtra || !tlwExtra->embedded) + GetWindowRect(q->internalWinId(), &rcNormalPosition); + QRect fStrut(frameStrut()); + data.crect.setRect(rcNormalPosition.left + fStrut.left(), + rcNormalPosition.top + fStrut.top(), + rect.right - rect.left, + rect.bottom - rect.top); + isResize = data.crect.size() != oldSize; + } + } else { + q->setAttribute(Qt::WA_OutsideWSRange, false); + data.crect.setRect(x, y, w, h); + } + } else { + QRect oldGeom(data.crect); + data.crect.setRect(x, y, w, h); + if (q->isVisible() && (!inTopLevelResize || q->internalWinId())) { + // Top-level resize optimization does not work for native child widgets; + // disable it for this particular widget. + if (inTopLevelResize) + tlwExtra->inTopLevelResize = false; + + if (!isResize) + moveRect(QRect(oldPos, oldSize), x - oldPos.x(), y - oldPos.y()); + else + invalidateBuffer_resizeHelper(oldPos, oldSize); + + if (inTopLevelResize) + tlwExtra->inTopLevelResize = true; + } + if (q->testAttribute(Qt::WA_WState_Created)) + setWSGeometry(); + } + q->setAttribute(Qt::WA_WState_ConfigPending, false); + } + + if (q->isWindow() && q->isVisible() && isResize && !inTopLevelResize) { + invalidateBuffer(q->rect()); //after the resize + } + + // Process events immediately rather than in translateConfigEvent to + // avoid windows message process delay. + if (q->isVisible()) { + if (isMove && q->pos() != oldPos) { + QMoveEvent e(q->pos(), oldPos); + QApplication::sendEvent(q, &e); + } + if (isResize) { + static bool slowResize = qgetenv("QT_SLOW_TOPLEVEL_RESIZE").toInt(); + // If we have a backing store with static contents, we have to disable the top-level + // resize optimization in order to get invalidated regions for resized widgets. + // The optimization discards all invalidateBuffer() calls since we're going to + // repaint everything anyways, but that's not the case with static contents. + const bool setTopLevelResize = !slowResize && q->isWindow() && extra && extra->topextra + && !extra->topextra->inTopLevelResize + && (!extra->topextra->backingStore + || !extra->topextra->backingStore->hasStaticContents()); + if (setTopLevelResize) + extra->topextra->inTopLevelResize = true; + QResizeEvent e(q->size(), oldSize); + QApplication::sendEvent(q, &e); + if (setTopLevelResize) + extra->topextra->inTopLevelResize = false; + } + } else { + if (isMove && q->pos() != oldPos) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } +} + +bool QWidgetPrivate::shouldShowMaximizeButton() +{ + if (data.window_flags & Qt::MSWindowsFixedSizeDialogHint) + return false; + // if the user explicitly asked for the maximize button, we try to add + // it even if the window has fixed size. + if (data.window_flags & Qt::CustomizeWindowHint && + data.window_flags & Qt::WindowMaximizeButtonHint) + return true; + if (extra) { + if ((extra->maxw && extra->maxw != QWIDGETSIZE_MAX && extra->maxw != QLAYOUTSIZE_MAX) + || (extra->maxh && extra->maxh != QWIDGETSIZE_MAX && extra->maxh != QLAYOUTSIZE_MAX)) + return false; + } + return data.window_flags & Qt::WindowMaximizeButtonHint; +} + +void QWidgetPrivate::winUpdateIsOpaque() +{ +#ifndef Q_WS_WINCE + Q_Q(QWidget); + + if (!q->isWindow() || !q->testAttribute(Qt::WA_TranslucentBackground)) + return; + + if ((data.window_flags & Qt::FramelessWindowHint) == 0) + return; + + if (!isOpaque && ptrUpdateLayeredWindowIndirect) { + SetWindowLong(q->internalWinId(), GWL_EXSTYLE, + GetWindowLong(q->internalWinId(), GWL_EXSTYLE) | Q_WS_EX_LAYERED); + } else { + SetWindowLong(q->internalWinId(), GWL_EXSTYLE, + GetWindowLong(q->internalWinId(), GWL_EXSTYLE) & ~Q_WS_EX_LAYERED); + } +#endif +} + +void QWidgetPrivate::setConstraints_sys() +{ +#ifndef Q_WS_WINCE_WM + Q_Q(QWidget); + if (q->isWindow() && q->testAttribute(Qt::WA_WState_Created)) { + int style = GetWindowLong(q->internalWinId(), GWL_STYLE); + if (shouldShowMaximizeButton()) + style |= WS_MAXIMIZEBOX; + else + style &= ~WS_MAXIMIZEBOX; + SetWindowLong(q->internalWinId(), GWL_STYLE, style); + } +#endif +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + Q_Q(QWidget); + scrollChildren(dx, dy); + + if (!paintOnScreen()) { + scrollRect(q->rect(), dx, dy); + } else { + UINT flags = SW_INVALIDATE; + if (!q->testAttribute(Qt::WA_OpaquePaintEvent)) + flags |= SW_ERASE; + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + ScrollWindowEx(q->internalWinId(), dx, dy, 0, 0, 0, 0, flags); + UpdateWindow(q->internalWinId()); + } +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r) +{ + Q_Q(QWidget); + + if (!paintOnScreen()) { + scrollRect(r, dx, dy); + } else { + RECT wr; + wr.top = r.top(); + wr.left = r.left(); + wr.bottom = r.bottom()+1; + wr.right = r.right()+1; + + UINT flags = SW_INVALIDATE; + if (!q->testAttribute(Qt::WA_OpaquePaintEvent)) + flags |= SW_ERASE; + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + ScrollWindowEx(q->internalWinId(), dx, dy, &wr, &wr, 0, 0, flags); + UpdateWindow(q->internalWinId()); + } +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + Q_D(const QWidget); + int val; + if (m == PdmWidth) { + val = data->crect.width(); + } else if (m == PdmHeight) { + val = data->crect.height(); + } else { + HDC gdc = GetDC(0); + switch (m) { + case PdmDpiX: + case PdmPhysicalDpiX: + if (d->extra && d->extra->customDpiX) + val = d->extra->customDpiX; + else if (d->parent) + val = static_cast<QWidget *>(d->parent)->metric(m); + else + val = GetDeviceCaps(gdc, LOGPIXELSX); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + if (d->extra && d->extra->customDpiY) + val = d->extra->customDpiY; + else if (d->parent) + val = static_cast<QWidget *>(d->parent)->metric(m); + else + val = GetDeviceCaps(gdc, LOGPIXELSY); + break; + case PdmWidthMM: + val = data->crect.width() + * GetDeviceCaps(gdc, HORZSIZE) + / GetDeviceCaps(gdc, HORZRES); + break; + case PdmHeightMM: + val = data->crect.height() + * GetDeviceCaps(gdc, VERTSIZE) + / GetDeviceCaps(gdc, VERTRES); + break; + case PdmNumColors: + if (GetDeviceCaps(gdc, RASTERCAPS) & RC_PALETTE) + val = GetDeviceCaps(gdc, SIZEPALETTE); + else { + HDC hd = d->hd ? HDC(d->hd) : gdc; + int bpp = GetDeviceCaps(hd, BITSPIXEL); + if (bpp == 32) + val = INT_MAX; // ### this is bogus, it should be 2^24 colors for 32 bit as well + else if(bpp<=8) + val = GetDeviceCaps(hd, NUMCOLORS); + else + val = 1 << (bpp * GetDeviceCaps(hd, PLANES)); + } + break; + case PdmDepth: + val = GetDeviceCaps(gdc, BITSPIXEL); + break; + default: + val = 0; + qWarning("QWidget::metric: Invalid metric command"); + } + ReleaseDC(0, gdc); + } + return val; +} + +void QWidgetPrivate::createSysExtra() +{ +#ifndef QT_NO_DRAGANDDROP + extra->dropTarget = 0; +#endif +} + +#ifndef Q_WS_WINCE +void QWidgetPrivate::deleteSysExtra() +{ +} +#endif //Q_WS_WINCE + +void QWidgetPrivate::createTLSysExtra() +{ + extra->topextra->hotkeyRegistered = 0; + extra->topextra->savedFlags = 0; + extra->topextra->winIconBig = 0; + extra->topextra->winIconSmall = 0; +} + +void QWidgetPrivate::deleteTLSysExtra() +{ + if (extra->topextra->winIconSmall) + DestroyIcon(extra->topextra->winIconSmall); + if (extra->topextra->winIconBig) + DestroyIcon(extra->topextra->winIconBig); +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + // Enablement is defined by d->extra->dropTarget != 0. + if (on) { + // Turn on. + createExtra(); +#ifndef QT_NO_DRAGANDDROP + if (!q->internalWinId()) + q->nativeParentWidget()->d_func()->createExtra(); + QWExtra *extra = extraData(); + if (!extra->dropTarget) + extra->dropTarget = registerOleDnd(q); +#endif + } else { + // Turn off. + QWExtra *extra = extraData(); +#ifndef QT_NO_DRAGANDDROP + if (extra && extra->dropTarget) { + unregisterOleDnd(q, extra->dropTarget); + extra->dropTarget = 0; + } +#endif + } +} + +#ifndef QT_NO_DRAGANDDROP +QOleDropTarget* QWidgetPrivate::registerOleDnd(QWidget *widget) +{ + QOleDropTarget *dropTarget = new QOleDropTarget(widget); + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + if (!widget->internalWinId()) { + QWidget *nativeParent = widget->nativeParentWidget(); + Q_ASSERT(nativeParent); + QWExtra *nativeExtra = nativeParent->d_func()->extra; + Q_ASSERT(nativeExtra); + if (!nativeParent->acceptDrops()) + nativeParent->setAcceptDrops(true); + if (!nativeExtra->oleDropWidgets.contains(widget)) + nativeExtra->oleDropWidgets.append(widget); + if (!nativeExtra->dropTarget) { + nativeExtra->dropTarget = registerOleDnd(nativeParent); + Q_ASSERT(nativeExtra->dropTarget); +#ifndef Q_OS_WINCE + CoLockObjectExternal(nativeExtra->dropTarget, false, true); +#endif + RegisterDragDrop(nativeParent->internalWinId(), nativeExtra->dropTarget); + } + } else { + RegisterDragDrop(widget->internalWinId(), dropTarget); +#ifndef Q_OS_WINCE + CoLockObjectExternal(dropTarget, true, true); +#endif + } + return dropTarget; +} + +void QWidgetPrivate::unregisterOleDnd(QWidget *widget, QOleDropTarget *dropTarget) +{ + dropTarget->releaseQt(); + dropTarget->Release(); + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + if (!widget->internalWinId()) { + QWidget *nativeParent = widget->nativeParentWidget(); + Q_ASSERT(nativeParent); + QWExtra *nativeExtra = nativeParent->d_func()->extra; + Q_ASSERT(nativeExtra); + nativeExtra->oleDropWidgets.removeAll(widget); + nativeExtra->oleDropWidgets.removeAll(static_cast<QWidget *>(0)); + if (nativeExtra->oleDropWidgets.isEmpty() && nativeExtra->dropTarget + && !nativeParent->testAttribute(Qt::WA_DropSiteRegistered)) { +#ifndef Q_OS_WINCE + CoLockObjectExternal(nativeExtra->dropTarget, false, true); +#endif + RevokeDragDrop(nativeParent->internalWinId()); + nativeExtra->dropTarget = 0; + } + } else { +#ifndef Q_OS_WINCE + CoLockObjectExternal(dropTarget, false, true); +#endif + RevokeDragDrop(widget->internalWinId()); + } +} + +#endif //QT_NO_DRAGANDDROP + +// from qregion_win.cpp +extern HRGN qt_tryCreateRegion(QRegion::RegionType type, int left, int top, int right, int bottom); +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; + + if (region.isEmpty()) { + SetWindowRgn(q->internalWinId(), 0, true); + return; + } + + // Since SetWindowRegion takes ownership, and we need to translate, + // we take a copy. + HRGN wr = qt_tryCreateRegion(QRegion::Rectangle, 0,0,0,0); + CombineRgn(wr, region.handle(), 0, RGN_COPY); + + QPoint offset = (q->isWindow() + ? frameStrut().topLeft() + : QPoint(0, 0)); + OffsetRgn(wr, offset.x(), offset.y()); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (!SetWindowRgn(data.winid, wr, true)) + DeleteObject(wr); +} + +void QWidgetPrivate::updateFrameStrut() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + if (!q->internalWinId()) { + data.fstrut_dirty = false; + return; + } + + RECT rect = {0,0,0,0}; + + QTLWExtra *top = topData(); + uint exstyle = GetWindowLong(q->internalWinId(), GWL_EXSTYLE); + uint style = GetWindowLong(q->internalWinId(), GWL_STYLE); +#ifndef Q_WS_WINCE + if (AdjustWindowRectEx(&rect, style & ~(WS_OVERLAPPED), FALSE, exstyle)) { +#else + if (AdjustWindowRectEx(&rect, style, FALSE, exstyle)) { +#endif + top->frameStrut.setCoords(-rect.left, -rect.top, rect.right, rect.bottom); + data.fstrut_dirty = false; + } +} + +#ifndef Q_WS_WINCE +void QWidgetPrivate::setWindowOpacity_sys(qreal level) +{ + Q_Q(QWidget); + + if (!isOpaque && ptrUpdateLayeredWindow && (data.window_flags & Qt::FramelessWindowHint)) { + if (GetWindowLong(q->internalWinId(), GWL_EXSTYLE) & Q_WS_EX_LAYERED) { + BLENDFUNCTION blend = {AC_SRC_OVER, 0, (int)(255.0 * level), AC_SRC_ALPHA}; + ptrUpdateLayeredWindow(q->internalWinId(), NULL, NULL, NULL, NULL, NULL, 0, &blend, Q_ULW_ALPHA); + } + return; + } + + static bool function_resolved = false; + if (!function_resolved) { + ptrSetLayeredWindowAttributes = + (PtrSetLayeredWindowAttributes) QSystemLibrary::resolve(QLatin1String("user32"), + "SetLayeredWindowAttributes"); + function_resolved = true; + } + + if (!ptrSetLayeredWindowAttributes) + return; + + int wl = GetWindowLong(q->internalWinId(), GWL_EXSTYLE); + + if (level != 1.0) { + if ((wl&Q_WS_EX_LAYERED) == 0) + SetWindowLong(q->internalWinId(), GWL_EXSTYLE, wl | Q_WS_EX_LAYERED); + } else if (wl&Q_WS_EX_LAYERED) { + SetWindowLong(q->internalWinId(), GWL_EXSTYLE, wl & ~Q_WS_EX_LAYERED); + } + ptrSetLayeredWindowAttributes(q->internalWinId(), 0, (int)(level * 255), Q_LWA_ALPHA); +} +#endif //Q_WS_WINCE + +// class QGlobalRasterPaintEngine: public QRasterPaintEngine +// { +// public: +// inline QGlobalRasterPaintEngine() : QRasterPaintEngine() { setFlushOnEnd(false); } +// }; +// Q_GLOBAL_STATIC(QGlobalRasterPaintEngine, globalRasterPaintEngine) + + +#ifndef QT_NO_DIRECTDRAW +static uchar *qt_primary_surface_bits; +static int qt_primary_surface_stride; +static QImage::Format qt_primary_surface_format; + +void qt_win_initialize_directdraw() +{ + HRESULT res; + + // Some initialization... + if (!qt_ddraw_object) { + res = DirectDrawCreate(0, &qt_ddraw_object, 0); + + if (res != DD_OK) + qWarning("DirectDrawCreate failed: %d", res); + + qt_ddraw_object->SetCooperativeLevel(0, DDSCL_NORMAL); + + DDSURFACEDESC surfaceDesc; + memset(&surfaceDesc, 0, sizeof(DDSURFACEDESC)); + + surfaceDesc.dwSize = sizeof(DDSURFACEDESC); + surfaceDesc.dwFlags = DDSD_CAPS; + surfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + + res = qt_ddraw_object->CreateSurface(&surfaceDesc, &qt_ddraw_primary, 0); + if (res != DD_OK) + qWarning("CreateSurface failed: %d", res); + + memset(&surfaceDesc, 0, sizeof(DDSURFACEDESC)); + surfaceDesc.dwSize = sizeof(DDSURFACEDESC); + res = qt_ddraw_primary->Lock(0, &surfaceDesc, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, 0); + if (res != DD_OK) + qWarning("Locking surface failed: %d", res); + + if (surfaceDesc.ddpfPixelFormat.dwFlags == DDPF_RGB) { + qt_primary_surface_bits = (uchar *) surfaceDesc.lpSurface; + qt_primary_surface_stride = surfaceDesc.lPitch; + qt_primary_surface_format = QImage::Format_RGB32; + } else { + qWarning("QWidget painting: unsupported device depth for onscreen painting...\n"); + } + + qt_ddraw_primary->Unlock(0); + } +} + +class QOnScreenRasterPaintEngine : public QRasterPaintEngine +{ +public: + // The image allocated here leaks... Fix if this code is ifdef'ed + // in + QOnScreenRasterPaintEngine() + : QRasterPaintEngine(new QImage(qt_primary_surface_bits, + QApplication::desktop()->width(), + QApplication::desktop()->height(), + qt_primary_surface_stride, + qt_primary_surface_format)) + { + device = static_cast<QImage *>(d_func()->device); + } + + bool begin(QPaintDevice *) + { + QRegion clip = systemClip(); + originalSystemClip = clip; + clip.translate(widget->mapToGlobal(QPoint(0, 0))); + setSystemClip(clip); + + QRect bounds = clip.boundingRect(); + DDSURFACEDESC surface; + surface.dwSize = sizeof(DDSURFACEDESC); + HRESULT res = qt_ddraw_primary->Lock((RECT *) &bounds, &surface, DDLOCK_WAIT, 0); + if (res != DD_OK) { + qWarning("QWidget painting: locking onscreen bits failed: %d\n", res); + return false; + } + + if (surface.lpSurface == qt_primary_surface_bits) { + qt_primary_surface_bits = (uchar *) surface.lpSurface; + device->data_ptr()->data = qt_primary_surface_bits; + } + + return QRasterPaintEngine::begin(device); + } + + bool end() + { + HRESULT res = qt_ddraw_primary->Unlock(0); + if (res != DD_OK) + qWarning("QWidget::paint, failed to unlock DirectDraw surface: %d", res); + bool ok = QRasterPaintEngine::end(); + setSystemClip(originalSystemClip); + return ok; + } + + QPoint coordinateOffset() const { + return -widget->mapToGlobal(QPoint(0, 0)); + } + + const QWidget *widget; + QImage *device; + QRegion originalSystemClip; +}; +Q_GLOBAL_STATIC(QOnScreenRasterPaintEngine, onScreenPaintEngine) +#else +void qt_win_initialize_directdraw() { } +#endif + +QPaintEngine *QWidget::paintEngine() const +{ +#ifndef QT_NO_DIRECTDRAW + QOnScreenRasterPaintEngine *pe = onScreenPaintEngine(); + pe->widget = this; + return pe; +#endif + + // We set this bit which is checked in setAttribute for + // Qt::WA_PaintOnScreen. We do this to allow these two scenarios: + // + // 1. Users accidentally set Qt::WA_PaintOnScreen on X and port to + // windows which would mean suddenly their widgets stop working. + // + // 2. Users set paint on screen and subclass paintEngine() to + // return 0, in which case we have a "hole" in the backingstore + // allowing use of GDI or DirectX directly. + // + // 1 is WRONG, but to minimize silent failures, we have set this + // bit to ignore the setAttribute call. 2. needs to be + // supported because its our only means of embeddeding native + // graphics stuff. + const_cast<QWidgetPrivate *>(d_func())->noPaintOnScreen = 1; + + return 0; +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + Q_Q(QWidget); + return new QRasterWindowSurface(q); +} + +void QWidgetPrivate::setModal_sys() +{ +} + +void QWidgetPrivate::registerTouchWindow() +{ + Q_Q(QWidget); + + // enable WM_TOUCH* messages on our window + if (q->testAttribute(Qt::WA_WState_Created) + && QApplicationPrivate::RegisterTouchWindow + && q->windowType() != Qt::Desktop) + QApplicationPrivate::RegisterTouchWindow(q->effectiveWinId(), 0); +} + +void QWidgetPrivate::winSetupGestures() +{ +#if !defined(QT_NO_GESTURES) && !defined(QT_NO_NATIVE_GESTURES) + Q_Q(QWidget); + if (!q || !q->isVisible() || !nativeGesturePanEnabled) + return; + + if (!QApplicationPrivate::HasTouchSupport) + return; + QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); + if (!qAppPriv->SetGestureConfig) + return; + WId winid = q->internalWinId(); + + bool needh = false; + bool needv = false; + bool singleFingerPanEnabled = false; + +#ifndef QT_NO_SCROLLAREA + if (QAbstractScrollArea *asa = qobject_cast<QAbstractScrollArea*>(q->parent())) { + QScrollBar *hbar = asa->horizontalScrollBar(); + QScrollBar *vbar = asa->verticalScrollBar(); + Qt::ScrollBarPolicy hbarpolicy = asa->horizontalScrollBarPolicy(); + Qt::ScrollBarPolicy vbarpolicy = asa->verticalScrollBarPolicy(); + needh = (hbarpolicy == Qt::ScrollBarAlwaysOn || + (hbarpolicy == Qt::ScrollBarAsNeeded && hbar->minimum() < hbar->maximum())); + needv = (vbarpolicy == Qt::ScrollBarAlwaysOn || + (vbarpolicy == Qt::ScrollBarAsNeeded && vbar->minimum() < vbar->maximum())); + singleFingerPanEnabled = asa->d_func()->singleFingerPanEnabled; + if (!winid) { + winid = q->winId(); // enforces the native winid on the viewport + } + } +#endif //QT_NO_SCROLLAREA + if (winid) { + GESTURECONFIG gc[1]; + memset(gc, 0, sizeof(gc)); + gc[0].dwID = GID_PAN; + if (nativeGesturePanEnabled) { + gc[0].dwWant = GC_PAN; + if (needv && singleFingerPanEnabled) + gc[0].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + else + gc[0].dwBlock |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + if (needh && singleFingerPanEnabled) + gc[0].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + else + gc[0].dwBlock |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + } else { + gc[0].dwBlock = GC_PAN; + } + + qAppPriv->SetGestureConfig(winid, 0, sizeof(gc)/sizeof(gc[0]), gc, sizeof(gc[0])); + } +#endif +} + +QT_END_NAMESPACE + +#ifdef Q_WS_WINCE +# include "qwidget_wince.cpp" +#endif diff --git a/src/gui/kernel/qwidget_wince.cpp b/src/gui/kernel/qwidget_wince.cpp new file mode 100644 index 0000000000..7676182ef0 --- /dev/null +++ b/src/gui/kernel/qwidget_wince.cpp @@ -0,0 +1,675 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef Q_WS_WINCE + +#include "qguifunctions_wince.h" + +QT_BEGIN_NAMESPACE + +const QString qt_reg_winclass(QWidget *w); // defined in qapplication_win.cpp +extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND, UINT, WPARAM, LPARAM); + +//#define TABLET_DEBUG +#define PACKETDATA (PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE \ + | PK_ORIENTATION | PK_CURSOR | PK_Z) +#define PACKETMODE 0 + +typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL); +typedef BOOL (API *PtrWTClose)(HCTX); +typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID); +typedef BOOL (API *PtrWTEnable)(HCTX, BOOL); +typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL); +typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID); +typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT); +typedef int (API *PtrWTQueueSizeGet)(HCTX); +typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int); + +#ifndef QT_NO_TABLETEVENT +static void qt_tablet_init_wce(); +static void qt_tablet_cleanup_wce(); + +static void qt_tablet_init_wce() { + static bool firstTime = true; + if (!firstTime) + return; + firstTime = false; + qt_tablet_widget = new QWidget(0); + qt_tablet_widget->createWinId(); + qt_tablet_widget->setObjectName(QLatin1String("Qt internal tablet widget")); + LOGCONTEXT lcMine; + qAddPostRoutine(qt_tablet_cleanup_wce); + struct tagAXIS tpOri[3]; + if (ptrWTInfo && ptrWTOpen && ptrWTQueueSizeGet && ptrWTQueueSizeSet) { + // make sure we have WinTab + if (!ptrWTInfo(0, 0, NULL)) { +#ifdef TABLET_DEBUG + qWarning("QWidget: Wintab services not available"); +#endif + return; + } + + // some tablets don't support tilt, check if it is possible, + qt_tablet_tilt_support = ptrWTInfo(WTI_DEVICES, DVC_ORIENTATION, &tpOri); + if (qt_tablet_tilt_support) { + // check for azimuth and altitude + qt_tablet_tilt_support = tpOri[0].axResolution && tpOri[1].axResolution; + } + // build our context from the default context + ptrWTInfo(WTI_DEFSYSCTX, 0, &lcMine); + // Go for the raw coordinates, the tablet event will return good stuff + lcMine.lcOptions |= CXO_MESSAGES | CXO_CSRMESSAGES; + lcMine.lcPktData = PACKETDATA; + lcMine.lcPktMode = PACKETMODE; + lcMine.lcMoveMask = PACKETDATA; + lcMine.lcOutOrgX = 0; + lcMine.lcOutExtX = lcMine.lcInExtX; + lcMine.lcOutOrgY = 0; + lcMine.lcOutExtY = -lcMine.lcInExtY; + qt_tablet_context = ptrWTOpen(qt_tablet_widget->winId(), &lcMine, true); +#ifdef TABLET_DEBUG + qDebug("Tablet is %p", qt_tablet_context); +#endif + if (!qt_tablet_context) { +#ifdef TABLET_DEBUG + qWarning("QWidget: Failed to open the tablet"); +#endif + return; + } + // Set the size of the Packet Queue to the correct size... + int currSize = ptrWTQueueSizeGet(qt_tablet_context); + if (!ptrWTQueueSizeSet(qt_tablet_context, QT_TABLET_NPACKETQSIZE)) { + // Ideally one might want to use a smaller + // multiple, but for now, since we managed to destroy + // the existing Q with the previous call, set it back + // to the other size, which should work. If not, + // there will be trouble. + if (!ptrWTQueueSizeSet(qt_tablet_context, currSize)) { + Q_ASSERT_X(0, "Qt::Internal", "There is no packet queue for" + " the tablet. The tablet will not work"); + } + } + } +} + +static void qt_tablet_cleanup_wce() { + if (ptrWTClose) + ptrWTClose(qt_tablet_context); + delete qt_tablet_widget; + qt_tablet_widget = 0; +} +#endif // QT_NO_TABLETEVENT + + +// The internal qWinRequestConfig, defined in qapplication_win.cpp, stores move, +// resize and setGeometry requests for a widget that is already +// processing a config event. The purpose is to avoid recursion. +// +void qWinRequestConfig(WId, int, int, int, int, int); + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) { + Q_Q(QWidget); + static int sw = -1, sh = -1; + + Qt::WindowType type = q->windowType(); + Qt::WindowFlags flags = data.window_flags; + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + bool tool = (type == Qt::Tool || type == Qt::Drawer); + + HINSTANCE appinst = qWinAppInst(); + HWND parentw, destroyw = 0; + WId id; + + QString windowClassName = qt_reg_winclass(q); + + if (!window) // always initialize + initializeWindow = true; + + if (popup) + flags |= Qt::WindowStaysOnTopHint; // a popup stays on top + + if (flags & (Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowContextHelpButtonHint)) { + flags |= Qt::WindowSystemMenuHint; + flags |= Qt::WindowTitleHint; + flags &= ~Qt::FramelessWindowHint; + } + + if (sw < 0) { // get the (primary) screen size + sw = GetSystemMetrics(SM_CXSCREEN); + sh = GetSystemMetrics(SM_CYSCREEN); + } + + if (desktop) { // desktop widget + popup = false; // force this flags off + data.crect.setRect(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); + } + + parentw = q->parentWidget() ? q->parentWidget()->effectiveWinId() : 0; + + QString title; + int style = WS_CHILD; + int exsty = WS_EX_NOPARENTNOTIFY; + + if (topLevel) { + if (!(flags & Qt::FramelessWindowHint) && !tool && !q->testAttribute(Qt::WA_DontShowOnScreen)) + style = (WS_OVERLAPPED) | WS_SYSMENU; + else + style = WS_POPUP; + if ((type == Qt::ToolTip) || (type == Qt::SplashScreen)) { + style = WS_POPUP; + exsty |= WS_EX_NOANIMATION; + } else { + if (flags & Qt::WindowTitleHint) + style |= WS_CAPTION; + if (flags & Qt::WindowSystemMenuHint) + style |= WS_SYSMENU; + if (flags & Qt::WindowContextHelpButtonHint) + exsty |= WS_EX_CONTEXTHELP; +#ifndef Q_WS_WINCE_WM + if (flags & Qt::WindowMinimizeButtonHint) + style |= WS_MINIMIZEBOX; + if (shouldShowMaximizeButton()) + style |= WS_MAXIMIZEBOX; +#endif + if (tool) + exsty |= WS_EX_TOOLWINDOW; + } + } + if (dialog) { + style = WS_BORDER | WS_CAPTION; + if (flags & Qt::WindowOkButtonHint) + exsty |= WS_EX_CAPTIONOKBTN; + if (flags & Qt::WindowCancelButtonHint || flags & Qt::WA_DeleteOnClose) + style |= WS_SYSMENU; + if (flags & Qt::WindowContextHelpButtonHint) + exsty |= WS_EX_CONTEXTHELP; + } + if (popup) { + style = WS_POPUP; + exsty |= WS_EX_NOANIMATION; + } + + if (flags & Qt::WindowTitleHint) { + title = q->isWindow() ? qAppName() : q->objectName(); + } + + // The Qt::WA_WState_Created flag is checked by translateConfigEvent() in + // qapplication_win.cpp. We switch it off temporarily to avoid move + // and resize events during creationt + q->setAttribute(Qt::WA_WState_Created, false); + + if (window) { // override the old window + if (destroyOldWindow) + destroyw = data.winid; + id = window; + setWinId(window); + LONG res = SetWindowLong(window, GWL_STYLE, style); + if (!res) + qErrnoWarning("QWidget::create: Failed to set window style"); + + res = SetWindowLong( window, GWL_WNDPROC, (LONG)QtWndProc ); + + if (!res) + qErrnoWarning("QWidget::create: Failed to set window procedure"); + } else if (desktop) { // desktop widget + id = GetDesktopWindow(); + if (!id) { //Create a dummy desktop + RECT r; + SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0); + id = CreateWindow(reinterpret_cast<const wchar_t *>(windowClassName.utf16()), + reinterpret_cast<const wchar_t *>(title.utf16()), style, + r.left, r.top, r.right - r.left, r.bottom - r.top, + 0, 0, appinst, 0); + } + setWinId(id); + } else if (topLevel) { // create top-level widget + const bool wasMoved = q->testAttribute(Qt::WA_Moved); + + int x, y; + if (qt_wince_is_mobile()) { + x = wasMoved ? data.crect.left() : CW_USEDEFAULT; + y = wasMoved ? data.crect.top() : CW_USEDEFAULT; + } else { + x = wasMoved ? data.crect.left() : 100; + y = wasMoved ? data.crect.top() : 100; + } + + int w = CW_USEDEFAULT; + int h = CW_USEDEFAULT; + + // Adjust for framestrut when needed + RECT rect = {0,0,0,0}; + if (AdjustWindowRectEx(&rect, style, FALSE, exsty)) { + QTLWExtra *td = maybeTopData(); + if (wasMoved && (td && !td->posFromMove)) { + x = data.crect.x() + rect.left; + y = data.crect.y() + rect.top; + } + + if (q->testAttribute(Qt::WA_Resized)) { + w = data.crect.width() + (rect.right - rect.left); + h = data.crect.height() + (rect.bottom - rect.top); + } + } + + id = CreateWindowEx(exsty, reinterpret_cast<const wchar_t *>(windowClassName.utf16()), + reinterpret_cast<const wchar_t *>(title.utf16()), style, + x, y, w, h, + 0, 0, appinst, 0); + + if (!id) + qErrnoWarning("QWidget::create: Failed to create window"); + setWinId(id); + if ((flags & Qt::WindowStaysOnTopHint) || (type == Qt::ToolTip)) + SetWindowPos(id, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + } else if (q->testAttribute(Qt::WA_NativeWindow) || paintOnScreen()) { // create child widget + id = CreateWindowEx(exsty, (wchar_t*)windowClassName.utf16(), (wchar_t*)title.utf16(), style, + data.crect.left(), data.crect.top(), data.crect.width(), data.crect.height(), + parentw, NULL, appinst, NULL); + if (!id) + qErrnoWarning("QWidget::create: Failed to create window"); + SetWindowPos(id, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + setWinId(id); + } + + if (desktop) { + q->setAttribute(Qt::WA_WState_Visible); + } else if (topLevel && !q->testAttribute(Qt::WA_DontShowOnScreen)) { + RECT cr; + GetClientRect(id, &cr); + // one cannot trust cr.left and cr.top, use a correction POINT instead + POINT pt; + pt.x = 0; + pt.y = 0; + if (!q->testAttribute(Qt::WA_DontShowOnScreen) || q->testAttribute(Qt::WA_Moved)) + ClientToScreen(id, &pt); + data.crect = QRect(QPoint(pt.x, pt.y), + QPoint(pt.x + cr.right - 1, pt.y + cr.bottom - 1)); + + if (data.fstrut_dirty) { + // be nice to activeqt + updateFrameStrut(); + } + } + + q->setAttribute(Qt::WA_WState_Created); // accept move/resize events + hd = 0; // no display context + + if (window) { // got window from outside + if (IsWindowVisible(window)) + q->setAttribute(Qt::WA_WState_Visible); + else + q->setAttribute(Qt::WA_WState_Visible, false); + } + + if (extra && !extra->mask.isEmpty()) + setMask_sys(extra->mask); + +#if defined(QT_NON_COMMERCIAL) + QT_NC_WIDGET_CREATE +#endif + + if (q->hasFocus() && q->testAttribute(Qt::WA_InputMethodEnabled)) + q->inputContext()->setFocusWidget(q); + + if (destroyw) { + DestroyWindow(destroyw); + } + +#ifndef QT_NO_TABLETEVENT + if (q != qt_tablet_widget && QWidgetPrivate::mapper) + qt_tablet_init_wce(); +#endif // QT_NO_TABLETEVENT + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + + if (maybeTopData() && maybeTopData()->opacity != 255) + q->setWindowOpacity(maybeTopData()->opacity/255.); + + if (!topLevel && q->testAttribute(Qt::WA_NativeWindow) && q->testAttribute(Qt::WA_Mapped)) { + Q_ASSERT(q->internalWinId()); + ShowWindow(q->internalWinId(), SW_SHOW); + } +} + +/* + \internal + Platform-specific part of QWidget::show(). +*/ +void QWidgetPrivate::show_sys() { + Q_Q(QWidget); +#if defined(QT_NON_COMMERCIAL) + QT_NC_SHOW_WINDOW +#endif + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + + q->setAttribute(Qt::WA_Mapped); + + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + return; + } + + + int sm = SW_SHOW; + bool fakedMaximize = false; + if (q->isWindow()) { +#ifndef Q_WS_WINCE_WM + if (q->isMinimized()) { + sm = SW_SHOWMINIMIZED; + } else if (q->isMaximized()) { + sm = SW_SHOWMAXIMIZED; + // Windows will not behave correctly when we try to maximize a window which does not + // have minimize nor maximize buttons in the window frame. Windows would then ignore + // non-available geometry, and rather maximize the widget to the full screen, minus the + // window frame (caption). So, we do a trick here, by adding a maximize button before + // maximizing the widget, and then remove the maximize button afterwards. + Qt::WindowFlags &flags = data.window_flags; + if (flags & Qt::WindowTitleHint && + !(flags & (Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint))) { + fakedMaximize = TRUE; + int style = GetWindowLong(q->internalWinId(), GWL_STYLE); + SetWindowLong(q->internalWinId(), GWL_STYLE, style | WS_MAXIMIZEBOX); + } + } else +#else + // Imitate minimizing on Windows mobile by hiding the widget. + if (q->isMinimized()) + sm = SW_HIDE; +#endif + if (q->isHidden()) { + sm = SW_HIDE; + } + } + if (q->testAttribute(Qt::WA_ShowWithoutActivating) + || (q->windowType() == Qt::Popup) + || (q->windowType() == Qt::ToolTip) + || (q->windowType() == Qt::Tool)) { + sm = SW_SHOWNOACTIVATE; + } + + ShowWindow(q->internalWinId(), sm); + + if (q->isMaximized() && q->isWindow()) + qt_wince_maximize(q); + +#ifndef Q_WS_WINCE_WM + if (!qt_wince_is_mobile() && q->isFullScreen()) { + HWND handle = FindWindow(L"HHTaskBar", L""); + if (handle) { + ShowWindow(handle, SW_HIDE); + EnableWindow(handle, false); + } + } + + if (fakedMaximize) { + int style = GetWindowLong(q->internalWinId(), GWL_STYLE); + SetWindowLong(q->internalWinId(), GWL_STYLE, style & ~WS_MAXIMIZEBOX); + SetWindowPos(q->internalWinId(), 0, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER + | SWP_FRAMECHANGED); + } +#else + Q_UNUSED(fakedMaximize); +#endif + + if (q->isWindow() && sm == SW_SHOW) + SetForegroundWindow(q->internalWinId()); + + invalidateBuffer(q->rect()); +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + + int max = SW_SHOWNORMAL; + int normal = SW_SHOWNOACTIVATE; + + if ((oldstate & Qt::WindowMinimized) && !(newstate & Qt::WindowMinimized)) + newstate |= Qt::WindowActive; + if (newstate & Qt::WindowActive) + normal = SW_SHOWNORMAL; + if (isWindow()) { + createWinId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + // Ensure the initial size is valid, since we store it as normalGeometry below. + if ((!testAttribute(Qt::WA_Resized) && !isVisible())) + adjustSize(); + if (!d->topData()->normalGeometry.isValid()) { + if (newstate & Qt::WindowMaximized && !(oldstate & Qt::WindowFullScreen)) + d->topData()->normalGeometry = geometry(); + if (newstate & Qt::WindowMinimized && !(oldstate & Qt::WindowFullScreen)) + d->topData()->normalGeometry = geometry(); + } + if ((oldstate & Qt::WindowMaximized) != (newstate & Qt::WindowMaximized)) { + if (!(newstate & Qt::WindowMaximized)) { + int style = GetWindowLong(internalWinId(), GWL_STYLE) | WS_BORDER | WS_POPUP | WS_CAPTION; + SetWindowLong(internalWinId(), GWL_STYLE, style); + SetWindowLong(internalWinId(), GWL_EXSTYLE, GetWindowLong (internalWinId(), GWL_EXSTYLE) & ~ WS_EX_NODRAG); + qt_wince_unmaximize(this); + } + if (isVisible() && newstate & Qt::WindowMaximized) + qt_wince_maximize(this); + if (isVisible() && !(newstate & Qt::WindowMinimized)) { + ShowWindow(internalWinId(), (newstate & Qt::WindowMaximized) ? max : normal); + if (!(newstate & Qt::WindowFullScreen)) { + QRect r = d->topData()->normalGeometry; + if (!(newstate & Qt::WindowMaximized) && r.width() >= 0) { + if (pos() != r.topLeft() || size() !=r.size()) { + d->topData()->normalGeometry = QRect(0,0,-1,-1); + setGeometry(r); + } + } + } else { + d->updateFrameStrut(); + } + } + } + if ((oldstate & Qt::WindowFullScreen) != (newstate & Qt::WindowFullScreen)) { + if (newstate & Qt::WindowFullScreen) { + if (d->topData()->normalGeometry.width() < 0 && !(oldstate & Qt::WindowMaximized)) + d->topData()->normalGeometry = geometry(); + d->topData()->savedFlags = (Qt::WindowFlags)GetWindowLong(internalWinId(), GWL_STYLE); + UINT style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP; + if (isVisible()) + style |= WS_VISIBLE; + SetWindowLong(internalWinId(), GWL_STYLE, style); + QRect r = qApp->desktop()->screenGeometry(this); + UINT swpf = SWP_FRAMECHANGED; + if (newstate & Qt::WindowActive) + swpf |= SWP_NOACTIVATE; + qt_wince_full_screen(internalWinId(), true, swpf); + d->updateFrameStrut(); + } else { + UINT style = d->topData()->savedFlags; + if (isVisible()) + style |= WS_VISIBLE; + SetWindowLong(internalWinId(), GWL_STYLE, style); + UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE; + if (newstate & Qt::WindowActive) + swpf |= SWP_NOACTIVATE; + qt_wince_full_screen(internalWinId(), false, swpf); + d->updateFrameStrut(); + + // preserve maximized state + if (isVisible()) { + ShowWindow(internalWinId(), (newstate & Qt::WindowMaximized) ? max : normal); + if (newstate & Qt::WindowMaximized) + qt_wince_maximize(this); + } + if (!(newstate & Qt::WindowMaximized)) { + QRect r = d->topData()->normalGeometry; + d->topData()->normalGeometry = QRect(0,0,-1,-1); + if (r.isValid()) + setGeometry(r); + } + } + } + if ((oldstate & Qt::WindowMinimized) != (newstate & Qt::WindowMinimized)) { + if (newstate & Qt::WindowMinimized) + qt_wince_minimize(internalWinId()); + else if (newstate & Qt::WindowMaximized) { + ShowWindow(internalWinId(), max); + qt_wince_maximize(this); + } else { + ShowWindow(internalWinId(), normal); + } + } + } + data->window_state = newstate; + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} + +void QWidgetPrivate::deleteSysExtra() +{ + Q_Q(QWidget); + if (!qt_wince_is_mobile() && q->isFullScreen()) { + HWND handle = FindWindow(L"HHTaskBar", L""); + if (handle) { + ShowWindow(handle, SW_SHOWNORMAL); + EnableWindow(handle, true); + } + } +} + +void QWidgetPrivate::setWindowOpacity_sys(qreal level) { + Q_UNUSED(level); + return; +} + +// The procedure does nothing, but is required for mousegrabbing to work +LRESULT QT_WIN_CALLBACK qJournalRecordProc(int nCode, WPARAM wParam, LPARAM lParam) { + Q_UNUSED(nCode); + Q_UNUSED(wParam); + Q_UNUSED(lParam); + return 0; +} + +void QWidget::grabMouse() { + if (!qt_nograb()) { + if (mouseGrb) + mouseGrb->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + SetCapture(internalWinId()); + mouseGrb = this; + } +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) { + if (!qt_nograb()) { + if (mouseGrb) + mouseGrb->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + SetCapture(internalWinId()); + mouseGrbCur = new QCursor(cursor); + SetCursor(mouseGrbCur->handle()); + mouseGrb = this; + } +} +#endif + +void QWidget::releaseMouse() { + if (!qt_nograb() && mouseGrb == this) { + ReleaseCapture(); + if (journalRec) { + journalRec = 0; + } + if (mouseGrbCur) { + delete mouseGrbCur; + mouseGrbCur = 0; + } + mouseGrb = 0; + } +} + +void QWidget::show() +{ + Qt::WindowFlags flags = windowFlags() & 0xff; + int threshold = qApp->autoMaximizeThreshold(); + if ((threshold < 0) || (windowState() & Qt::WindowFullScreen) || (windowState() & Qt::WindowMaximized)) { + setVisible(true); + return; + } + int height = sizeHint().height(); + int screenHeight = (qreal(threshold) / 100.0f * qApp->desktop()->screenGeometry(this).height()); + bool maximize = height > screenHeight; + if (!maximize) { + // If we do not maximize yet we check the widget and its child widgets whether they are + //vertically expanding. If one of the widgets is expanding we maximize. + QList<QWidget *> list = findChildren<QWidget *>(); + bool expandingChild = sizePolicy().verticalPolicy () == QSizePolicy::Expanding; + for (int i = 0; (i < list.size()) && !expandingChild; ++i) { + expandingChild = list.at(i)->sizePolicy().verticalPolicy () == QSizePolicy::Expanding; + } + maximize = expandingChild; + } + if ((minimumSizeHint().height() > qApp->desktop()->screenGeometry(this).height()) || (minimumSizeHint().width() > qApp->desktop()->screenGeometry(this).width())) + maximize = false; + + if ((flags == Qt::Window || flags == Qt::Dialog) && maximize) { + setWindowState((windowState() & ~(Qt::WindowMinimized | Qt::WindowFullScreen)) + | Qt::WindowMaximized); + setVisible(true); + } + else { + setVisible(true); + } +} + +QT_END_NAMESPACE + +#endif // Q_WS_WINCE diff --git a/src/gui/kernel/qwidget_x11.cpp b/src/gui/kernel/qwidget_x11.cpp new file mode 100644 index 0000000000..5ece7d65c6 --- /dev/null +++ b/src/gui/kernel/qwidget_x11.cpp @@ -0,0 +1,3146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qevent.h" +#include "qwidget.h" +#include "qdesktopwidget.h" +#include "qapplication.h" +#include "qapplication_p.h" +#include "qnamespace.h" +#include "qpainter.h" +#include "qbitmap.h" +#include "qlayout.h" +#include "qtextcodec.h" +#include "qelapsedtimer.h" +#include "qcursor.h" +#include "qstack.h" +#include "qcolormap.h" +#include "qdebug.h" +#include "qmenu.h" +#include "private/qmenu_p.h" +#include "private/qbackingstore_p.h" +#include "private/qwindowsurface_x11_p.h" + +//extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); //qapplication_x11.cpp + +#include <private/qpixmap_x11_p.h> +#include <private/qpaintengine_x11_p.h> +#include "qt_x11_p.h" +#include "qx11info_x11.h" + +#include <stdlib.h> + +//#define ALIEN_DEBUG + +// defined in qapplication_x11.cpp +//bool qt_wstate_iconified(WId); +//void qt_updated_rootinfo(); + + +#if !defined(QT_NO_IM) +#include "qinputcontext.h" +#include "qinputcontextfactory.h" +#endif + +#include "qwidget_p.h" + +#define XCOORD_MAX 16383 +#define WRECT_MAX 8191 + +QT_BEGIN_NAMESPACE + +extern bool qt_nograb(); + +QWidget *QWidgetPrivate::mouseGrabber = 0; +QWidget *QWidgetPrivate::keyboardGrabber = 0; + +void qt_net_remove_user_time(QWidget *tlw); +void qt_net_update_user_time(QWidget *tlw, unsigned long timestamp); + +int qt_x11_create_desktop_on_screen = -1; + +extern void qt_net_update_user_time(QWidget *tlw, unsigned long timestamp); + +// MWM support +struct QtMWMHints { + ulong flags, functions, decorations; + long input_mode; + ulong status; +}; + +enum { + MWM_HINTS_FUNCTIONS = (1L << 0), + + MWM_FUNC_ALL = (1L << 0), + MWM_FUNC_RESIZE = (1L << 1), + MWM_FUNC_MOVE = (1L << 2), + MWM_FUNC_MINIMIZE = (1L << 3), + MWM_FUNC_MAXIMIZE = (1L << 4), + MWM_FUNC_CLOSE = (1L << 5), + + MWM_HINTS_DECORATIONS = (1L << 1), + + MWM_DECOR_ALL = (1L << 0), + MWM_DECOR_BORDER = (1L << 1), + MWM_DECOR_RESIZEH = (1L << 2), + MWM_DECOR_TITLE = (1L << 3), + MWM_DECOR_MENU = (1L << 4), + MWM_DECOR_MINIMIZE = (1L << 5), + MWM_DECOR_MAXIMIZE = (1L << 6), + + MWM_HINTS_INPUT_MODE = (1L << 2), + + MWM_INPUT_MODELESS = 0L, + MWM_INPUT_PRIMARY_APPLICATION_MODAL = 1L, + MWM_INPUT_FULL_APPLICATION_MODAL = 3L +}; + + +static QtMWMHints GetMWMHints(Display *display, Window window) +{ + QtMWMHints mwmhints; + + Atom type; + int format; + ulong nitems, bytesLeft; + uchar *data = 0; + if ((XGetWindowProperty(display, window, ATOM(_MOTIF_WM_HINTS), 0, 5, false, + ATOM(_MOTIF_WM_HINTS), &type, &format, &nitems, &bytesLeft, + &data) == Success) + && (type == ATOM(_MOTIF_WM_HINTS) + && format == 32 + && nitems >= 5)) { + mwmhints = *(reinterpret_cast<QtMWMHints *>(data)); + } else { + mwmhints.flags = 0L; + mwmhints.functions = MWM_FUNC_ALL; + mwmhints.decorations = MWM_DECOR_ALL; + mwmhints.input_mode = 0L; + mwmhints.status = 0L; + } + + if (data) + XFree(data); + + return mwmhints; +} + +static void SetMWMHints(Display *display, Window window, const QtMWMHints &mwmhints) +{ + if (mwmhints.flags != 0l) { + XChangeProperty(display, window, ATOM(_MOTIF_WM_HINTS), ATOM(_MOTIF_WM_HINTS), 32, + PropModeReplace, (unsigned char *) &mwmhints, 5); + } else { + XDeleteProperty(display, window, ATOM(_MOTIF_WM_HINTS)); + } +} + +// Returns true if we should set WM_TRANSIENT_FOR on \a w +static inline bool isTransient(const QWidget *w) +{ + return ((w->windowType() == Qt::Dialog + || w->windowType() == Qt::Sheet + || w->windowType() == Qt::Tool + || w->windowType() == Qt::SplashScreen + || w->windowType() == Qt::ToolTip + || w->windowType() == Qt::Drawer + || w->windowType() == Qt::Popup) + && !w->testAttribute(Qt::WA_X11BypassTransientForHint)); +} + +static void do_size_hints(QWidget* widget, QWExtra *x); + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ + +const uint stdWidgetEventMask = // X event mask + (uint)( + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + KeymapStateMask | + ButtonMotionMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | + FocusChangeMask | + ExposureMask | + PropertyChangeMask | + StructureNotifyMask + ); + +const uint stdDesktopEventMask = // X event mask + (uint)( + KeymapStateMask | + EnterWindowMask | LeaveWindowMask | + PropertyChangeMask + ); + + +/* + The qt_ functions below are implemented in qwidgetcreate_x11.cpp. +*/ + +Window qt_XCreateWindow(const QWidget *creator, + Display *display, Window parent, + int x, int y, uint w, uint h, + int borderwidth, int depth, + uint windowclass, Visual *visual, + ulong valuemask, XSetWindowAttributes *attributes); +Window qt_XCreateSimpleWindow(const QWidget *creator, + Display *display, Window parent, + int x, int y, uint w, uint h, int borderwidth, + ulong border, ulong background); +void qt_XDestroyWindow(const QWidget *destroyer, + Display *display, Window window); + + +static void qt_insert_sip(QWidget* scrolled_widget, int dx, int dy) +{ + if (!scrolled_widget->isWindow() && !scrolled_widget->internalWinId()) + return; + QX11Data::ScrollInProgress sip = { X11->sip_serial++, scrolled_widget, dx, dy }; + X11->sip_list.append(sip); + + XClientMessageEvent client_message; + client_message.type = ClientMessage; + client_message.window = scrolled_widget->internalWinId(); + client_message.format = 32; + client_message.message_type = ATOM(_QT_SCROLL_DONE); + client_message.data.l[0] = sip.id; + + XSendEvent(X11->display, scrolled_widget->internalWinId(), False, NoEventMask, + (XEvent*)&client_message); +} + +static int qt_sip_count(QWidget* scrolled_widget) +{ + int sips=0; + + for (int i = 0; i < X11->sip_list.size(); ++i) { + const QX11Data::ScrollInProgress &sip = X11->sip_list.at(i); + if (sip.scrolled_widget == scrolled_widget) + sips++; + } + + return sips; +} + +static void create_wm_client_leader() +{ + if (X11->wm_client_leader) return; + + X11->wm_client_leader = + XCreateSimpleWindow(X11->display, + QX11Info::appRootWindow(), + 0, 0, 1, 1, 0, 0, 0); + + // set client leader property to itself + XChangeProperty(X11->display, + X11->wm_client_leader, ATOM(WM_CLIENT_LEADER), + XA_WINDOW, 32, PropModeReplace, + (unsigned char *)&X11->wm_client_leader, 1); + +#ifndef QT_NO_SESSIONMANAGER + // If we are session managed, inform the window manager about it + QByteArray session = qApp->sessionId().toLatin1(); + if (!session.isEmpty()) { + XChangeProperty(X11->display, + X11->wm_client_leader, ATOM(SM_CLIENT_ID), + XA_STRING, 8, PropModeReplace, + (unsigned char *)session.data(), session.size()); + } +#endif +} + +/*! + \internal + Update the X11 cursor of the widget w. + \a force is true if this function is called from dispatchEnterLeave, it means that the + mouse is actually directly under this widget. + */ +void qt_x11_enforce_cursor(QWidget * w, bool force) +{ + if (!w->testAttribute(Qt::WA_WState_Created)) + return; + + static QPointer<QWidget> lastUnderMouse = 0; + if (force) { + lastUnderMouse = w; + } else if (lastUnderMouse && lastUnderMouse->effectiveWinId() == w->effectiveWinId()) { + w = lastUnderMouse; + } else if (!w->internalWinId()) { + return; //the mouse is not under this widget, and it's not native, so don't change it + } + + while (!w->internalWinId() && w->parentWidget() && !w->isWindow() && !w->testAttribute(Qt::WA_SetCursor)) + w = w->parentWidget(); + + QWidget *nativeParent = w; + if (!w->internalWinId()) + nativeParent = w->nativeParentWidget(); + // This does the same as effectiveWinId(), but since it is possible + // to not have a native parent widget due to a special hack in + // qwidget for reparenting widgets to a different X11 screen, + // added additional check to make sure native parent widget exists. + if (!nativeParent || !nativeParent->internalWinId()) + return; + WId winid = nativeParent->internalWinId(); + + if (w->isWindow() || w->testAttribute(Qt::WA_SetCursor)) { +#ifndef QT_NO_CURSOR + QCursor *oc = QApplication::overrideCursor(); + if (oc) { + XDefineCursor(X11->display, winid, oc->handle()); + } else if (w->isEnabled()) { + XDefineCursor(X11->display, winid, w->cursor().handle()); + } else { + // enforce the windows behavior of clearing the cursor on + // disabled widgets + XDefineCursor(X11->display, winid, XNone); + } +#endif + } else { + XDefineCursor(X11->display, winid, XNone); + } +} + +Q_GUI_EXPORT void qt_x11_enforce_cursor(QWidget * w) +{ + qt_x11_enforce_cursor(w, false); +} + +void qt_x11_wait_for_window_manager(QWidget *w, bool sendPostedEvents) +{ + if (!w || (!w->isWindow() && !w->internalWinId())) + return; + QApplication::flush(); + XEvent ev; + QElapsedTimer t; + t.start(); + static const int maximumWaitTime = 2000; + if (!w->testAttribute(Qt::WA_WState_Created)) + return; + + WId winid = w->internalWinId(); + + // first deliver events that are already in the local queue + if (sendPostedEvents) + QApplication::sendPostedEvents(); + + // the normal sequence is: + // ... ConfigureNotify ... ReparentNotify ... MapNotify ... Expose + // with X11BypassWindowManagerHint: + // ConfigureNotify ... MapNotify ... Expose + + enum State { + Initial, Mapped + } state = Initial; + + do { + if (XEventsQueued(X11->display, QueuedAlready)) { + XNextEvent(X11->display, &ev); + qApp->x11ProcessEvent(&ev); + + switch (state) { + case Initial: + if (ev.type == MapNotify && ev.xany.window == winid) + state = Mapped; + break; + case Mapped: + if (ev.type == Expose && ev.xany.window == winid) + return; + break; + } + } else { + if (!XEventsQueued(X11->display, QueuedAfterFlush)) + qApp->syncX(); // non-busy wait + } + if (t.elapsed() > maximumWaitTime) + return; + } while(1); +} + +Q_GUI_EXPORT void qt_x11_wait_for_window_manager(QWidget *w) +{ + qt_x11_wait_for_window_manager(w, true); +} + +void qt_change_net_wm_state(const QWidget* w, bool set, Atom one, Atom two = 0) +{ + if (!w->isVisible()) // not managed by the window manager + return; + + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.message_type = ATOM(_NET_WM_STATE); + e.xclient.display = X11->display; + e.xclient.window = w->internalWinId(); + e.xclient.format = 32; + e.xclient.data.l[0] = set ? 1 : 0; + e.xclient.data.l[1] = one; + e.xclient.data.l[2] = two; + e.xclient.data.l[3] = 0; + e.xclient.data.l[4] = 0; + XSendEvent(X11->display, RootWindow(X11->display, w->x11Info().screen()), + false, (SubstructureNotifyMask | SubstructureRedirectMask), &e); +} + +struct QX11WindowAttributes { + const XWindowAttributes *att; +}; + +void qt_x11_getX11InfoForWindow(QX11Info * xinfo, const XWindowAttributes &a) +{ + QX11WindowAttributes att; + att.att = &a; + qt_x11_getX11InfoForWindow(xinfo,att); +} + + +static QVector<Atom> getNetWmState(QWidget *w) +{ + QVector<Atom> returnValue; + + // Don't read anything, just get the size of the property data + Atom actualType; + int actualFormat; + ulong propertyLength; + ulong bytesLeft; + uchar *propertyData = 0; + if (XGetWindowProperty(X11->display, w->internalWinId(), ATOM(_NET_WM_STATE), 0, 0, + False, XA_ATOM, &actualType, &actualFormat, + &propertyLength, &bytesLeft, &propertyData) == Success + && actualType == XA_ATOM && actualFormat == 32) { + returnValue.resize(bytesLeft / 4); + XFree((char*) propertyData); + propertyData = 0; + + // fetch all data + if (XGetWindowProperty(X11->display, w->internalWinId(), ATOM(_NET_WM_STATE), 0, + returnValue.size(), False, XA_ATOM, &actualType, &actualFormat, + &propertyLength, &bytesLeft, &propertyData) != Success) { + returnValue.clear(); + } else if (propertyLength != (ulong)returnValue.size()) { + returnValue.resize(propertyLength); + } + + // put it into netWmState + if (!returnValue.isEmpty()) { + memcpy(returnValue.data(), propertyData, returnValue.size() * sizeof(Atom)); + } + if (propertyData) + XFree((char*) propertyData); + } + + return returnValue; +} + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_Q(QWidget); + Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + if (type == Qt::ToolTip) + flags |= Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint; + if (type == Qt::Popup) + flags |= Qt::X11BypassWindowManagerHint; + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet); + bool desktop = (type == Qt::Desktop); + bool tool = (type == Qt::Tool || type == Qt::SplashScreen + || type == Qt::ToolTip || type == Qt::Drawer); + +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::create_sys START:" << q << "topLevel?" << topLevel << "WId:" + << window << "initializeWindow:" << initializeWindow << "destroyOldWindow" << destroyOldWindow; +#endif + if (topLevel) { + if (parentWidget) { // if our parent stays on top, so must we + QWidget *ptl = parentWidget->window(); + if(ptl && (ptl->windowFlags() & Qt::WindowStaysOnTopHint)) + flags |= Qt::WindowStaysOnTopHint; + } + + if (type == Qt::SplashScreen) { + if (X11->isSupportedByWM(ATOM(_NET_WM_WINDOW_TYPE_SPLASH))) { + flags &= ~Qt::X11BypassWindowManagerHint; + } else { + flags |= Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint; + } + } + // All these buttons depend on the system menu, so we enable it + if (flags & (Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowContextHelpButtonHint)) + flags |= Qt::WindowSystemMenuHint; + } + + + Window parentw, destroyw = 0; + WId id = 0; + + // always initialize + if (!window) + initializeWindow = true; + + QX11Info *parentXinfo = parentWidget ? &parentWidget->d_func()->xinfo : 0; + + if (desktop && + qt_x11_create_desktop_on_screen >= 0 && + qt_x11_create_desktop_on_screen != xinfo.screen()) { + // desktop on a certain screen other than the default requested + QX11InfoData *xd = &X11->screens[qt_x11_create_desktop_on_screen]; + xinfo.setX11Data(xd); + } else if (parentXinfo && (parentXinfo->screen() != xinfo.screen() + || (parentXinfo->visual() != xinfo.visual() + && !q->inherits("QGLWidget")))) + { + // QGLWidgets have to be excluded here as they have a + // specially crafted QX11Info structure which can't be swapped + // out with the parent widgets QX11Info. The parent visual, + // for instance, might not even be GL capable. + xinfo = *parentXinfo; + } + + //get display, screen number, root window and desktop geometry for + //the current screen + Display *dpy = X11->display; + int scr = xinfo.screen(); + Window root_win = RootWindow(dpy, scr); + int sw = DisplayWidth(dpy,scr); + int sh = DisplayHeight(dpy,scr); + + if (desktop) { // desktop widget + dialog = popup = false; // force these flags off + data.crect.setRect(0, 0, sw, sh); + } else if (topLevel && !q->testAttribute(Qt::WA_Resized)) { + QDesktopWidget *desktopWidget = qApp->desktop(); + if (desktopWidget->isVirtualDesktop()) { + QRect r = desktopWidget->screenGeometry(); + sw = r.width(); + sh = r.height(); + } + + int width = sw / 2; + int height = 4 * sh / 10; + if (extra) { + width = qMax(qMin(width, extra->maxw), extra->minw); + height = qMax(qMin(height, extra->maxh), extra->minh); + } + data.crect.setSize(QSize(width, height)); + } + + parentw = topLevel ? root_win : parentWidget->effectiveWinId(); + + XSetWindowAttributes wsa; + + if (window) { // override the old window + if (destroyOldWindow) { + if (topLevel) + X11->dndEnable(q, false); + destroyw = data.winid; + } + id = window; + setWinId(window); + XWindowAttributes a; + XGetWindowAttributes(dpy, window, &a); + data.crect.setRect(a.x, a.y, a.width, a.height); + + if (a.map_state == IsUnmapped) + q->setAttribute(Qt::WA_WState_Visible, false); + else + q->setAttribute(Qt::WA_WState_Visible); + + qt_x11_getX11InfoForWindow(&xinfo,a); + + } else if (desktop) { // desktop widget +#ifdef QWIDGET_EXTRA_DEBUG + qDebug() << "create desktop"; +#endif + id = (WId)parentw; // id = root window +// QWidget *otherDesktop = find(id); // is there another desktop? +// if (otherDesktop && otherDesktop->testWFlags(Qt::WPaintDesktop)) { +// otherDesktop->d->setWinId(0); // remove id from widget mapper +// d->setWinId(id); // make sure otherDesktop is +// otherDesktop->d->setWinId(id); // found first +// } else { + setWinId(id); +// } + } else if (topLevel || q->testAttribute(Qt::WA_NativeWindow) || paintOnScreen()) { +#ifdef QWIDGET_EXTRA_DEBUG + static int topLevels = 0; + static int children = 0; + if (parentw == root_win) + qDebug() << "create toplevel" << ++topLevels; + else + qDebug() << "create child" << ++children; +#endif + QRect safeRect = data.crect; //##### must handle huge sizes as well.... i.e. wrect + if (safeRect.width() < 1|| safeRect.height() < 1) { + if (topLevel) { + // top-levels must be at least 1x1 + safeRect.setSize(safeRect.size().expandedTo(QSize(1, 1))); + } else { + // create it way off screen, and rely on + // setWSGeometry() to do the right thing with it later + safeRect = QRect(-1000,-1000,1,1); + } + } +#ifndef QT_NO_XRENDER + int screen = xinfo.screen(); + if (topLevel && X11->use_xrender + && xinfo.depth() != 32 && X11->argbVisuals[screen] + && q->testAttribute(Qt::WA_TranslucentBackground)) + { + QX11InfoData *xd = xinfo.getX11Data(true); + + xd->screen = screen; + xd->visual = X11->argbVisuals[screen]; + xd->colormap = X11->argbColormaps[screen]; + xd->depth = 32; + xd->defaultVisual = false; + xd->defaultColormap = false; + xd->cells = xd->visual->map_entries; + xinfo.setX11Data(xd); + } +#endif + if (xinfo.defaultVisual() && xinfo.defaultColormap()) { + id = (WId)qt_XCreateSimpleWindow(q, dpy, parentw, + safeRect.left(), safeRect.top(), + safeRect.width(), safeRect.height(), + 0, + BlackPixel(dpy, xinfo.screen()), + WhitePixel(dpy, xinfo.screen())); + } else { + wsa.background_pixel = WhitePixel(dpy, xinfo.screen()); + wsa.border_pixel = BlackPixel(dpy, xinfo.screen()); + wsa.colormap = xinfo.colormap(); + id = (WId)qt_XCreateWindow(q, dpy, parentw, + safeRect.left(), safeRect.top(), + safeRect.width(), safeRect.height(), + 0, xinfo.depth(), InputOutput, + (Visual *) xinfo.visual(), + CWBackPixel|CWBorderPixel|CWColormap, + &wsa); + } + + setWinId(id); // set widget id/handle + hd + } + +#ifndef QT_NO_XRENDER + if (picture) { + XRenderFreePicture(X11->display, picture); + picture = 0; + } + + if (X11->use_xrender && !desktop && q->internalWinId()) { + XRenderPictFormat *format = XRenderFindVisualFormat(dpy, (Visual *) xinfo.visual()); + if (format) + picture = XRenderCreatePicture(dpy, id, format, 0, 0); + } +#endif // QT_NO_XRENDER + + QtMWMHints mwmhints; + mwmhints.flags = 0L; + mwmhints.functions = 0L; + mwmhints.decorations = 0; + mwmhints.input_mode = 0L; + mwmhints.status = 0L; + + if (topLevel) { + ulong wsa_mask = 0; + if (type != Qt::SplashScreen) { // && customize) { + mwmhints.flags |= MWM_HINTS_DECORATIONS; + + bool customize = flags & Qt::CustomizeWindowHint; + if (!(flags & Qt::FramelessWindowHint) && !(customize && !(flags & Qt::WindowTitleHint))) { + mwmhints.decorations |= MWM_DECOR_BORDER; + mwmhints.decorations |= MWM_DECOR_RESIZEH; + + if (flags & Qt::WindowTitleHint) + mwmhints.decorations |= MWM_DECOR_TITLE; + + if (flags & Qt::WindowSystemMenuHint) + mwmhints.decorations |= MWM_DECOR_MENU; + + if (flags & Qt::WindowMinimizeButtonHint) { + mwmhints.decorations |= MWM_DECOR_MINIMIZE; + mwmhints.functions |= MWM_FUNC_MINIMIZE; + } + + if (flags & Qt::WindowMaximizeButtonHint) { + mwmhints.decorations |= MWM_DECOR_MAXIMIZE; + mwmhints.functions |= MWM_FUNC_MAXIMIZE; + } + + if (flags & Qt::WindowCloseButtonHint) + mwmhints.functions |= MWM_FUNC_CLOSE; + } + } else { + // if type == Qt::SplashScreen + mwmhints.decorations = MWM_DECOR_ALL; + } + + if (tool) { + wsa.save_under = True; + wsa_mask |= CWSaveUnder; + } + + if (flags & Qt::X11BypassWindowManagerHint) { + wsa.override_redirect = True; + wsa_mask |= CWOverrideRedirect; + } + + if (wsa_mask && initializeWindow) { + Q_ASSERT(id); + XChangeWindowAttributes(dpy, id, wsa_mask, &wsa); + } + + if (mwmhints.functions != 0) { + mwmhints.flags |= MWM_HINTS_FUNCTIONS; + mwmhints.functions |= MWM_FUNC_MOVE | MWM_FUNC_RESIZE; + } else { + mwmhints.functions = MWM_FUNC_ALL; + } + + if (!(flags & Qt::FramelessWindowHint) + && flags & Qt::CustomizeWindowHint + && flags & Qt::WindowTitleHint + && !(flags & + (Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowCloseButtonHint))) { + // a special case - only the titlebar without any button + mwmhints.flags = MWM_HINTS_FUNCTIONS; + mwmhints.functions = MWM_FUNC_MOVE | MWM_FUNC_RESIZE; + mwmhints.decorations = 0; + } + } + + if (!initializeWindow) { + // do no initialization + } else if (popup) { // popup widget + // set EWMH window types + setNetWmWindowTypes(); + + wsa.override_redirect = True; + wsa.save_under = True; + Q_ASSERT(id); + XChangeWindowAttributes(dpy, id, CWOverrideRedirect | CWSaveUnder, + &wsa); + } else if (topLevel && !desktop) { // top-level widget + if (!X11->wm_client_leader) + create_wm_client_leader(); + + // note: WM_TRANSIENT_FOR is set in QWidgetPrivate::show_sys() + + XSizeHints size_hints; + size_hints.flags = USSize | PSize | PWinGravity; + size_hints.x = data.crect.left(); + size_hints.y = data.crect.top(); + size_hints.width = data.crect.width(); + size_hints.height = data.crect.height(); + size_hints.win_gravity = + QApplication::isRightToLeft() ? NorthEastGravity : NorthWestGravity; + + XWMHints wm_hints; // window manager hints + memset(&wm_hints, 0, sizeof(wm_hints)); // make valgrind happy + wm_hints.flags = InputHint | StateHint | WindowGroupHint; + wm_hints.input = q->testAttribute(Qt::WA_X11DoNotAcceptFocus) ? False : True; + wm_hints.initial_state = NormalState; + wm_hints.window_group = X11->wm_client_leader; + + XClassHint class_hint; + QByteArray appName = qAppName().toLatin1(); + class_hint.res_name = appName.data(); // application name + class_hint.res_class = const_cast<char *>(QX11Info::appClass()); // application class + + XSetWMProperties(dpy, id, 0, 0, + qApp->d_func()->argv, qApp->d_func()->argc, + &size_hints, &wm_hints, &class_hint); + + XResizeWindow(dpy, id, + qBound(1, data.crect.width(), XCOORD_MAX), + qBound(1, data.crect.height(), XCOORD_MAX)); + XStoreName(dpy, id, appName.data()); + Atom protocols[5]; + int n = 0; + protocols[n++] = ATOM(WM_DELETE_WINDOW); // support del window protocol + protocols[n++] = ATOM(WM_TAKE_FOCUS); // support take focus window protocol + protocols[n++] = ATOM(_NET_WM_PING); // support _NET_WM_PING protocol +#ifndef QT_NO_XSYNC + protocols[n++] = ATOM(_NET_WM_SYNC_REQUEST); // support _NET_WM_SYNC_REQUEST protocol +#endif // QT_NO_XSYNC + if (flags & Qt::WindowContextHelpButtonHint) + protocols[n++] = ATOM(_NET_WM_CONTEXT_HELP); + XSetWMProtocols(dpy, id, protocols, n); + + // set mwm hints + SetMWMHints(dpy, id, mwmhints); + + // set EWMH window types + setNetWmWindowTypes(); + + // set _NET_WM_PID + long curr_pid = getpid(); + XChangeProperty(dpy, id, ATOM(_NET_WM_PID), XA_CARDINAL, 32, PropModeReplace, + (unsigned char *) &curr_pid, 1); + + // when we create a toplevel widget, the frame strut should be dirty + data.fstrut_dirty = 1; + + // declare the widget's window role + if (QTLWExtra *topData = maybeTopData()) { + if (!topData->role.isEmpty()) { + QByteArray windowRole = topData->role.toUtf8(); + XChangeProperty(dpy, id, + ATOM(WM_WINDOW_ROLE), XA_STRING, 8, PropModeReplace, + (unsigned char *)windowRole.constData(), windowRole.length()); + } + } + + // set client leader property + XChangeProperty(dpy, id, ATOM(WM_CLIENT_LEADER), + XA_WINDOW, 32, PropModeReplace, + (unsigned char *)&X11->wm_client_leader, 1); + } else { + // non-toplevel widgets don't have a frame, so no need to + // update the strut + data.fstrut_dirty = 0; + } + + if (initializeWindow && q->internalWinId()) { + // don't erase when resizing + wsa.bit_gravity = QApplication::isRightToLeft() ? NorthEastGravity : NorthWestGravity; + Q_ASSERT(id); + XChangeWindowAttributes(dpy, id, CWBitGravity, &wsa); + } + + // set X11 event mask + if (desktop) { +// QWidget* main_desktop = find(id); +// if (main_desktop->testWFlags(Qt::WPaintDesktop)) +// XSelectInput(dpy, id, stdDesktopEventMask | ExposureMask); +// else + XSelectInput(dpy, id, stdDesktopEventMask); + } else if (q->internalWinId()) { + XSelectInput(dpy, id, stdWidgetEventMask); +#if !defined (QT_NO_TABLET) + QTabletDeviceDataList *tablet_list = qt_tablet_devices(); + if (X11->ptrXSelectExtensionEvent) { + for (int i = 0; i < tablet_list->size(); ++i) { + QTabletDeviceData tablet = tablet_list->at(i); + X11->ptrXSelectExtensionEvent(dpy, id, reinterpret_cast<XEventClass*>(tablet.eventList), + tablet.eventCount); + } + } +#endif + } + + if (desktop) { + q->setAttribute(Qt::WA_WState_Visible); + } else if (topLevel) { // set X cursor + if (initializeWindow) { + qt_x11_enforce_cursor(q); + + if (QTLWExtra *topData = maybeTopData()) + if (!topData->caption.isEmpty()) + setWindowTitle_helper(topData->caption); + + //always enable dnd: it's not worth the effort to maintain the state + // NOTE: this always creates topData() + X11->dndEnable(q, true); + + if (maybeTopData() && maybeTopData()->opacity != 255) + q->setWindowOpacity(maybeTopData()->opacity/255.); + + } + } else if (q->internalWinId()) { + qt_x11_enforce_cursor(q); + if (QWidget *p = q->parentWidget()) // reset the cursor on the native parent + qt_x11_enforce_cursor(p); + } + + if (extra && !extra->mask.isEmpty() && q->internalWinId()) + XShapeCombineRegion(X11->display, q->internalWinId(), ShapeBounding, 0, 0, + extra->mask.handle(), ShapeSet); + + if (q->hasFocus() && q->testAttribute(Qt::WA_InputMethodEnabled)) { + QInputContext *inputContext = q->inputContext(); + if (inputContext) + inputContext->setFocusWidget(q); + } + + if (destroyw) { + qt_XDestroyWindow(q, dpy, destroyw); + if (QTLWExtra *topData = maybeTopData()) { +#ifndef QT_NO_XSYNC + if (topData->syncUpdateCounter) + XSyncDestroyCounter(dpy, topData->syncUpdateCounter); +#endif + // we destroyed our old window - reset the top-level state + createTLSysExtra(); + } + } + + // newly created windows are positioned at the window system's + // (0,0) position. If the parent uses wrect mapping to expand the + // coordinate system, we must also adjust this widget's window + // system position + if (!topLevel && !parentWidget->data->wrect.topLeft().isNull()) + setWSGeometry(); + else if (topLevel && (data.crect.width() == 0 || data.crect.height() == 0)) + q->setAttribute(Qt::WA_OutsideWSRange, true); + + if (!topLevel && q->testAttribute(Qt::WA_NativeWindow) && q->testAttribute(Qt::WA_Mapped)) { + Q_ASSERT(q->internalWinId()); + XMapWindow(X11->display, q->internalWinId()); + // Ensure that mapped alien widgets are flushed immediately when re-created as native widgets. + if (QWindowSurface *surface = q->windowSurface()) + surface->flush(q, q->rect(), q->mapTo(surface->window(), QPoint())); + } + +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::create_sys END:" << q; +#endif +} + +static void qt_x11_recreateWidget(QWidget *widget) +{ + if (widget->inherits("QGLWidget")) { + // We send QGLWidgets a ParentChange event which causes them to + // recreate their GL context, which in turn causes them to choose + // their visual again. Now that WA_TranslucentBackground is set, + // QGLContext::chooseVisual will select an ARGB visual. + QEvent e(QEvent::ParentChange); + QApplication::sendEvent(widget, &e); + } else { + // For regular widgets, reparent them with their parent which + // also triggers a recreation of the native window + QPoint pos = widget->pos(); + bool visible = widget->isVisible(); + if (visible) + widget->hide(); + + widget->setParent(widget->parentWidget(), widget->windowFlags()); + widget->move(pos); + if (visible) + widget->show(); + } +} + +static void qt_x11_recreateNativeWidgetsRecursive(QWidget *widget) +{ + if (widget->internalWinId()) + qt_x11_recreateWidget(widget); + + const QObjectList &children = widget->children(); + for (int i = 0; i < children.size(); ++i) { + QWidget *child = qobject_cast<QWidget*>(children.at(i)); + if (child) + qt_x11_recreateNativeWidgetsRecursive(child); + } +} + +void QWidgetPrivate::x11UpdateIsOpaque() +{ +#ifndef QT_NO_XRENDER + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->testAttribute(Qt::WA_TranslucentBackground)) + return; + + bool topLevel = (data.window_flags & Qt::Window); + int screen = xinfo.screen(); + if (topLevel && X11->use_xrender + && X11->argbVisuals[screen] && xinfo.depth() != 32) + { + qt_x11_recreateNativeWidgetsRecursive(q); + } +#endif +} + +/* + Returns true if the background is inherited; otherwise returns + false. + + Mainly used in the paintOnScreen case. +*/ +bool QWidgetPrivate::isBackgroundInherited() const +{ + Q_Q(const QWidget); + + // windows do not inherit their background + if (q->isWindow() || q->windowType() == Qt::SubWindow) + return false; + + if (q->testAttribute(Qt::WA_NoSystemBackground) || q->testAttribute(Qt::WA_OpaquePaintEvent)) + return false; + + const QPalette &pal = q->palette(); + QPalette::ColorRole bg = q->backgroundRole(); + QBrush brush = pal.brush(bg); + + // non opaque brushes leaves us no choice, we must inherit + if (!q->autoFillBackground() || !brush.isOpaque()) + return true; + + if (brush.style() == Qt::SolidPattern) { + // the background is just a solid color. If there is no + // propagated contents, then we claim as performance + // optimization that it was not inheritet. This is the normal + // case in standard Windows or Motif style. + const QWidget *w = q->parentWidget(); + if (!w->d_func()->isBackgroundInherited()) + return false; + } + + return true; +} + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(d->effectiveRectFor(geometry())); + d->deactivateWidgetCleanup(); + if (testAttribute(Qt::WA_WState_Created)) { + setAttribute(Qt::WA_WState_Created, false); + QObjectList childList = children(); + for (int i = 0; i < childList.size(); ++i) { // destroy all widget children + register QObject *obj = childList.at(i); + if (obj->isWidgetType()) + static_cast<QWidget*>(obj)->destroy(destroySubWindows, + destroySubWindows); + } + if (QWidgetPrivate::mouseGrabber == this) + releaseMouse(); + if (QWidgetPrivate::keyboardGrabber == this) + releaseKeyboard(); + if (isWindow()) + X11->deferred_map.removeAll(this); + if (isModal()) { + // just be sure we leave modal + QApplicationPrivate::leaveModal(this); + } + else if ((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); + +#ifndef QT_NO_XRENDER + if (d->picture) { + if (destroyWindow) + XRenderFreePicture(X11->display, d->picture); + d->picture = 0; + } +#endif // QT_NO_XRENDER + + // delete the _NET_WM_USER_TIME_WINDOW + qt_net_remove_user_time(this); + + if ((windowType() == Qt::Desktop)) { + if (acceptDrops()) + X11->dndEnable(this, false); + } else { + if (isWindow()) + X11->dndEnable(this, false); + if (destroyWindow) + qt_XDestroyWindow(this, X11->display, data->winid); + } + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + + extern void qPRCleanup(QWidget *widget); // from qapplication_x11.cpp + if (testAttribute(Qt::WA_WState_Reparented)) + qPRCleanup(this); + + if(d->ic) { + delete d->ic; + } else { + // release previous focus information participating with + // preedit preservation of qic + QInputContext *qic = QApplicationPrivate::inputContext; + if (qic) + qic->widgetDestroyed(this); + } + } +} + +void QWidgetPrivate::setParent_sys(QWidget *parent, Qt::WindowFlags f) +{ + Q_Q(QWidget); +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::setParent_sys START" << q << "parent:" << parent; +#endif + QX11Info old_xinfo = xinfo; + if (parent && parent->windowType() == Qt::Desktop) { + // make sure the widget is created on the same screen as the + // programmer specified desktop widget + xinfo = parent->d_func()->xinfo; + parent = 0; + } + + QTLWExtra *topData = maybeTopData(); + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + extern void qPRCreate(const QWidget *, Window); +#ifndef QT_NO_CURSOR + QCursor oldcurs; +#endif + + // dnd unregister (we will register again below) + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + q->setAttribute(Qt::WA_DropSiteRegistered, false); + + // if we are a top then remove our dnd prop for now + // it will get rest later + if (q->isWindow() && wasCreated) + X11->dndEnable(q, false); + + if (topData) + qt_net_remove_user_time(q); + +// QWidget *oldparent = q->parentWidget(); + WId old_winid = wasCreated ? data.winid : 0; + if ((q->windowType() == Qt::Desktop)) + old_winid = 0; + setWinId(0); + +#ifndef QT_NO_XRENDER + if (picture) { + XRenderFreePicture(X11->display, picture); + picture = 0; + } +#endif + + // hide and reparent our own window away. Otherwise we might get + // destroyed when emitting the child remove event below. See QWorkspace. + if (wasCreated && old_winid) { + XUnmapWindow(X11->display, old_winid); + if (!old_xinfo.screen() != xinfo.screen()) + XReparentWindow(X11->display, old_winid, RootWindow(X11->display, xinfo.screen()), 0, 0); + } + if (topData) { + topData->parentWinId = 0; + // zero the frame strut and mark it dirty + topData->frameStrut.setCoords(0, 0, 0, 0); + + // reparenting from top-level, make sure show() works again + topData->waitingForMapNotify = 0; + topData->validWMState = 0; + } + data.fstrut_dirty = (!parent || (f & Qt::Window)); // toplevels get a dirty framestrut + + QObjectPrivate::setParent_helper(parent); + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + + data.window_flags = f; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state + // (but we recreate the winId for the widget being reparented, again for compatibility) + if (wasCreated) + createWinId(); + if (q->isWindow() || (!parent || parent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (wasCreated) { + QObjectList chlist = q->children(); + for (int i = 0; i < chlist.size(); ++i) { // reparent children + QObject *obj = chlist.at(i); + if (obj->isWidgetType()) { + QWidget *w = (QWidget *)obj; + if (!w->testAttribute(Qt::WA_WState_Created)) + continue; + if (xinfo.screen() != w->d_func()->xinfo.screen()) { + // ### force setParent() to not shortcut out (because + // ### we're setting the parent to the current parent) + // ### setParent will add child back to the list + // ### of children so we need to make sure the + // ### widget won't be added twice. + w->d_func()->parent = 0; + this->children.removeOne(w); + w->setParent(q); + } else if (!w->isWindow()) { + w->d_func()->invalidateBuffer(w->rect()); + if (w->internalWinId()) { + if (w->testAttribute(Qt::WA_NativeWindow)) { + QWidget *nativeParentWidget = w->nativeParentWidget(); + // Qt::WA_NativeWindow ensures that we always have a nativeParentWidget + Q_ASSERT(nativeParentWidget != 0); + QPoint p = w->mapTo(nativeParentWidget, QPoint()); + XReparentWindow(X11->display, + w->internalWinId(), + nativeParentWidget->internalWinId(), + p.x(), p.y()); + } else { + w->d_func()->setParent_sys(q, w->data->window_flags); + } + } + } else if (isTransient(w)) { + /* + when reparenting toplevel windows with toplevel-transient children, + we need to make sure that the window manager gets the updated + WM_TRANSIENT_FOR information... unfortunately, some window managers + don't handle changing WM_TRANSIENT_FOR before the toplevel window is + visible, so we unmap and remap all toplevel-transient children *after* + the toplevel parent has been mapped. thankfully, this is easy in Qt :) + + note that the WM_TRANSIENT_FOR hint is actually updated in + QWidgetPrivate::show_sys() + */ + if (w->internalWinId()) + XUnmapWindow(X11->display, w->internalWinId()); + QApplication::postEvent(w, new QEvent(QEvent::ShowWindowRequest)); + } + } + } + qPRCreate(q, old_winid); + updateSystemBackground(); + + if (old_winid) { + Window *cmwret; + int count; + if (XGetWMColormapWindows(X11->display, old_winid, &cmwret, &count)) { + Window *cmw; + int cmw_size = sizeof(Window)*count; + cmw = new Window[count]; + memcpy((char *)cmw, (char *)cmwret, cmw_size); + XFree((char *)cmwret); + int i; + for (i=0; i<count; i++) { + if (cmw[i] == old_winid) { + cmw[i] = q->internalWinId(); + break; + } + } + int top_count; + if (XGetWMColormapWindows(X11->display, q->window()->internalWinId(), + &cmwret, &top_count)) + { + Window *merged_cmw = new Window[count + top_count]; + memcpy((char *)merged_cmw, (char *)cmw, cmw_size); + memcpy((char *)merged_cmw + cmw_size, (char *)cmwret, sizeof(Window)*top_count); + delete [] cmw; + XFree((char *)cmwret); + cmw = merged_cmw; + count += top_count; + } + + XSetWMColormapWindows(X11->display, q->window()->internalWinId(), cmw, count); + delete [] cmw; + } + + qt_XDestroyWindow(q, X11->display, old_winid); + } + } + + // check if we need to register our dropsite + if (q->testAttribute(Qt::WA_AcceptDrops) + || (!q->isWindow() && q->parentWidget() && q->parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) { + q->setAttribute(Qt::WA_DropSiteRegistered, true); + } +#if !defined(QT_NO_IM) + ic = 0; +#endif + invalidateBuffer(q->rect()); +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::setParent_sys END" << q; +#endif +} + +QPoint QWidgetPrivate::mapToGlobal(const QPoint &pos) const +{ + Q_Q(const QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->internalWinId()) { + QPoint p = pos + q->data->crect.topLeft(); + //cannot trust that !isWindow() implies parentWidget() before create + return (q->isWindow() || !q->parentWidget()) ? p : q->parentWidget()->d_func()->mapToGlobal(p); + } + int x, y; + Window child; + QPoint p = mapToWS(pos); + XTranslateCoordinates(X11->display, q->internalWinId(), + QApplication::desktop()->screen(xinfo.screen())->internalWinId(), + p.x(), p.y(), &x, &y, &child); + return QPoint(x, y); +} + +QPoint QWidgetPrivate::mapFromGlobal(const QPoint &pos) const +{ + Q_Q(const QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->internalWinId()) { + //cannot trust that !isWindow() implies parentWidget() before create + QPoint p = (q->isWindow() || !q->parentWidget()) ? pos : q->parentWidget()->d_func()->mapFromGlobal(pos); + return p - q->data->crect.topLeft(); + } + int x, y; + Window child; + XTranslateCoordinates(X11->display, + QApplication::desktop()->screen(xinfo.screen())->internalWinId(), + q->internalWinId(), pos.x(), pos.y(), &x, &y, &child); + return mapFromWS(QPoint(x, y)); +} + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + QPoint offset = data->crect.topLeft(); + const QWidget *w = this; + const QWidget *p = w->parentWidget(); + while (!w->isWindow() && p) { + w = p; + p = p->parentWidget(); + offset += w->data->crect.topLeft(); + } + + const QWidgetPrivate *wd = w->d_func(); + QTLWExtra *tlw = wd->topData(); + if (!tlw->embedded) + return pos + offset; + + return d->mapToGlobal(pos); +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + QPoint offset = data->crect.topLeft(); + const QWidget *w = this; + const QWidget *p = w->parentWidget(); + while (!w->isWindow() && p) { + w = p; + p = p->parentWidget(); + offset += w->data->crect.topLeft(); + } + + const QWidgetPrivate *wd = w->d_func(); + QTLWExtra *tlw = wd->topData(); + if (!tlw->embedded) + return pos - offset; + + return d->mapFromGlobal(pos); +} + +void QWidgetPrivate::updateSystemBackground() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->internalWinId()) + return; + QBrush brush = q->palette().brush(QPalette::Active, q->backgroundRole()); + Qt::WindowType type = q->windowType(); + if (brush.style() == Qt::NoBrush + || q->testAttribute(Qt::WA_NoSystemBackground) + || q->testAttribute(Qt::WA_UpdatesDisabled) + || type == Qt::Popup || type == Qt::ToolTip) { + if (QX11Info::isCompositingManagerRunning() + && q->testAttribute(Qt::WA_TranslucentBackground) + && !(q->parent())) + XSetWindowBackground(X11->display, q->internalWinId(), + QColormap::instance(xinfo.screen()).pixel(Qt::transparent)); + else + XSetWindowBackgroundPixmap(X11->display, q->internalWinId(), XNone); + } + else if (brush.style() == Qt::SolidPattern && brush.isOpaque()) + XSetWindowBackground(X11->display, q->internalWinId(), + QColormap::instance(xinfo.screen()).pixel(brush.color())); + else if (isBackgroundInherited()) + XSetWindowBackgroundPixmap(X11->display, q->internalWinId(), ParentRelative); + else if (brush.style() == Qt::TexturePattern) { + extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); // qpixmap_x11.cpp + XSetWindowBackgroundPixmap(X11->display, q->internalWinId(), + static_cast<QX11PixmapData*>(qt_toX11Pixmap(brush.texture()).data.data())->x11ConvertToDefaultDepth()); + } else + XSetWindowBackground(X11->display, q->internalWinId(), + QColormap::instance(xinfo.screen()).pixel(brush.color())); +} + +#ifndef QT_NO_CURSOR +void QWidgetPrivate::setCursor_sys(const QCursor &) +{ + Q_Q(QWidget); + qt_x11_enforce_cursor(q); + XFlush(X11->display); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + Q_Q(QWidget); + qt_x11_enforce_cursor(q); + XFlush(X11->display); +} +#endif + +static XTextProperty* +qstring_to_xtp(const QString& s) +{ + static XTextProperty tp = { 0, 0, 0, 0 }; + static bool free_prop = true; // we can't free tp.value in case it references + // the data of the static QCString below. + if (tp.value) { + if (free_prop) + XFree(tp.value); + tp.value = 0; + free_prop = true; + } + + static const QTextCodec* mapper = QTextCodec::codecForLocale(); + int errCode = 0; + if (mapper) { + QByteArray mapped = mapper->fromUnicode(s); + char* tl[2]; + tl[0] = mapped.data(); + tl[1] = 0; + errCode = XmbTextListToTextProperty(X11->display, tl, 1, XStdICCTextStyle, &tp); +#if defined(QT_DEBUG) + if (errCode < 0) + qDebug("qstring_to_xtp result code %d", errCode); +#endif + } + if (!mapper || errCode < 0) { + static QByteArray qcs; + qcs = s.toAscii(); + tp.value = (uchar*)qcs.data(); + tp.encoding = XA_STRING; + tp.format = 8; + tp.nitems = qcs.length(); + free_prop = false; + } + + // ### If we knew WM could understand unicode, we could use + // ### a much simpler, cheaper encoding... + /* + tp.value = (XChar2b*)s.unicode(); + tp.encoding = XA_UNICODE; // wish + tp.format = 16; + tp.nitems = s.length(); + */ + + return &tp; +} + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (!q->internalWinId()) + return; + XSetWMName(X11->display, q->internalWinId(), qstring_to_xtp(caption)); + + QByteArray net_wm_name = caption.toUtf8(); + XChangeProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_NAME), ATOM(UTF8_STRING), 8, + PropModeReplace, (unsigned char *)net_wm_name.data(), net_wm_name.size()); +} + +void QWidgetPrivate::setWindowIcon_sys(bool forceReset) +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + QTLWExtra *topData = this->topData(); + if (topData->iconPixmap && !forceReset) + // already been set + return; + + // preparing images to set the _NET_WM_ICON property + QIcon icon = q->windowIcon(); + QVector<long> icon_data; + Qt::HANDLE pixmap_handle = 0; + if (!icon.isNull()) { + QList<QSize> availableSizes = icon.availableSizes(); + if(availableSizes.isEmpty()) { + // try to use default sizes since the icon can be a scalable image like svg. + availableSizes.push_back(QSize(16,16)); + availableSizes.push_back(QSize(32,32)); + availableSizes.push_back(QSize(64,64)); + availableSizes.push_back(QSize(128,128)); + } + for(int i = 0; i < availableSizes.size(); ++i) { + QSize size = availableSizes.at(i); + QPixmap pixmap = icon.pixmap(size); + if (!pixmap.isNull()) { + QImage image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int pos = icon_data.size(); + icon_data.resize(pos + 2 + image.width()*image.height()); + icon_data[pos++] = image.width(); + icon_data[pos++] = image.height(); + if (sizeof(long) == sizeof(quint32)) { + memcpy(icon_data.data() + pos, image.scanLine(0), image.byteCount()); + } else { + for (int y = 0; y < image.height(); ++y) { + uint *scanLine = reinterpret_cast<uint *>(image.scanLine(y)); + for (int x = 0; x < image.width(); ++x) + icon_data[pos + y*image.width() + x] = scanLine[x]; + } + } + } + } + if (!icon_data.isEmpty()) { + extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); + /* + if the app is running on an unknown desktop, or it is not + using the default visual, convert the icon to 1bpp as stated + in the ICCCM section 4.1.2.4; otherwise, create the icon pixmap + in the default depth (even though this violates the ICCCM) + */ + if (X11->desktopEnvironment == DE_UNKNOWN + || !QX11Info::appDefaultVisual(xinfo.screen()) + || !QX11Info::appDefaultColormap(xinfo.screen())) { + // unknown DE or non-default visual/colormap, use 1bpp bitmap + if (!forceReset || !topData->iconPixmap) + topData->iconPixmap = new QPixmap(qt_toX11Pixmap(QBitmap(icon.pixmap(QSize(64,64))))); + pixmap_handle = topData->iconPixmap->handle(); + } else { + // default depth, use a normal pixmap (even though this + // violates the ICCCM), since this works on all DEs known to Qt + if (!forceReset || !topData->iconPixmap) + topData->iconPixmap = new QPixmap(qt_toX11Pixmap(icon.pixmap(QSize(64,64)))); + pixmap_handle = static_cast<QX11PixmapData*>(topData->iconPixmap->data.data())->x11ConvertToDefaultDepth(); + } + } + } + + if (!q->internalWinId()) + return; + + if (!icon_data.isEmpty()) { + XChangeProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_ICON), XA_CARDINAL, 32, + PropModeReplace, (unsigned char *) icon_data.data(), + icon_data.size()); + } else { + XDeleteProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_ICON)); + } + + XWMHints *h = XGetWMHints(X11->display, q->internalWinId()); + XWMHints wm_hints; + if (!h) { + memset(&wm_hints, 0, sizeof(wm_hints)); // make valgrind happy + h = &wm_hints; + } + + if (pixmap_handle) { + h->icon_pixmap = pixmap_handle; + h->flags |= IconPixmapHint; + } else { + h->icon_pixmap = 0; + h->flags &= ~(IconPixmapHint | IconMaskHint); + } + + XSetWMHints(X11->display, q->internalWinId(), h); + if (h != &wm_hints) + XFree((char *)h); +} + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + XSetWMIconName(X11->display, q->internalWinId(), qstring_to_xtp(iconText)); + + QByteArray icon_name = iconText.toUtf8(); + XChangeProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_ICON_NAME), ATOM(UTF8_STRING), 8, + PropModeReplace, (unsigned char *) icon_name.constData(), icon_name.size()); +} + + +void QWidget::grabMouse() +{ + if (isVisible() && !qt_nograb()) { + if (QWidgetPrivate::mouseGrabber && QWidgetPrivate::mouseGrabber != this) + QWidgetPrivate::mouseGrabber->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); +#ifndef QT_NO_DEBUG + int status = +#endif + XGrabPointer(X11->display, effectiveWinId(), False, + (uint)(ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | EnterWindowMask | + LeaveWindowMask), + GrabModeAsync, GrabModeAsync, + XNone, XNone, X11->time); +#ifndef QT_NO_DEBUG + if (status) { + const char *s = + status == GrabNotViewable ? "\"GrabNotViewable\"" : + status == AlreadyGrabbed ? "\"AlreadyGrabbed\"" : + status == GrabFrozen ? "\"GrabFrozen\"" : + status == GrabInvalidTime ? "\"GrabInvalidTime\"" : + "<?>"; + qWarning("QWidget::grabMouse: Failed with %s", s); + } +#endif + QWidgetPrivate::mouseGrabber = this; + } +} + + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if (!qt_nograb()) { + if (QWidgetPrivate::mouseGrabber && QWidgetPrivate::mouseGrabber != this) + QWidgetPrivate::mouseGrabber->releaseMouse(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); +#ifndef QT_NO_DEBUG + int status = +#endif + XGrabPointer(X11->display, effectiveWinId(), False, + (uint)(ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | EnterWindowMask | LeaveWindowMask), + GrabModeAsync, GrabModeAsync, + XNone, cursor.handle(), X11->time); +#ifndef QT_NO_DEBUG + if (status) { + const char *s = + status == GrabNotViewable ? "\"GrabNotViewable\"" : + status == AlreadyGrabbed ? "\"AlreadyGrabbed\"" : + status == GrabFrozen ? "\"GrabFrozen\"" : + status == GrabInvalidTime ? "\"GrabInvalidTime\"" : + "<?>"; + qWarning("QWidget::grabMouse: Failed with %s", s); + } +#endif + QWidgetPrivate::mouseGrabber = this; + } +} +#endif + + +void QWidget::releaseMouse() +{ + if (!qt_nograb() && QWidgetPrivate::mouseGrabber == this) { + XUngrabPointer(X11->display, X11->time); + XFlush(X11->display); + QWidgetPrivate::mouseGrabber = 0; + } +} + + +void QWidget::grabKeyboard() +{ + if (!qt_nograb()) { + if (QWidgetPrivate::keyboardGrabber && QWidgetPrivate::keyboardGrabber != this) + QWidgetPrivate::keyboardGrabber->releaseKeyboard(); + XGrabKeyboard(X11->display, effectiveWinId(), False, GrabModeAsync, GrabModeAsync, + X11->time); + QWidgetPrivate::keyboardGrabber = this; + } +} + + +void QWidget::releaseKeyboard() +{ + if (!qt_nograb() && QWidgetPrivate::keyboardGrabber == this) { + XUngrabKeyboard(X11->display, X11->time); + QWidgetPrivate::keyboardGrabber = 0; + } +} + + +QWidget *QWidget::mouseGrabber() +{ + return QWidgetPrivate::mouseGrabber; +} + + +QWidget *QWidget::keyboardGrabber() +{ + return QWidgetPrivate::keyboardGrabber; +} + +void QWidget::activateWindow() +{ + QWidget *tlw = window(); + if (tlw->isVisible() && !tlw->d_func()->topData()->embedded && !X11->deferred_map.contains(tlw)) { + if (X11->userTime == 0) + X11->userTime = X11->time; + qt_net_update_user_time(tlw, X11->userTime); + + if (X11->isSupportedByWM(ATOM(_NET_ACTIVE_WINDOW)) + && !(tlw->windowFlags() & Qt::X11BypassWindowManagerHint)) { + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.message_type = ATOM(_NET_ACTIVE_WINDOW); + e.xclient.display = X11->display; + e.xclient.window = tlw->internalWinId(); + e.xclient.format = 32; + e.xclient.data.l[0] = 1; // 1 == application + e.xclient.data.l[1] = X11->userTime; + if (QWidget *aw = QApplication::activeWindow()) + e.xclient.data.l[2] = aw->internalWinId(); + else + e.xclient.data.l[2] = XNone; + e.xclient.data.l[3] = 0; + e.xclient.data.l[4] = 0; + XSendEvent(X11->display, RootWindow(X11->display, tlw->x11Info().screen()), + false, SubstructureNotifyMask | SubstructureRedirectMask, &e); + } else { + if (!qt_widget_private(tlw)->topData()->waitingForMapNotify) + XSetInputFocus(X11->display, tlw->internalWinId(), XRevertToParent, X11->time); + } + } +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + bool needShow = false; + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + if (isWindow()) { + // Ensure the initial size is valid, since we store it as normalGeometry below. + if (!testAttribute(Qt::WA_Resized) && !isVisible()) + adjustSize(); + + QTLWExtra *top = d->topData(); + + if ((oldstate & Qt::WindowMaximized) != (newstate & Qt::WindowMaximized)) { + if (X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ)) + && X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_VERT))) { + if ((newstate & Qt::WindowMaximized) && !(oldstate & Qt::WindowFullScreen)) + top->normalGeometry = geometry(); + qt_change_net_wm_state(this, (newstate & Qt::WindowMaximized), + ATOM(_NET_WM_STATE_MAXIMIZED_HORZ), + ATOM(_NET_WM_STATE_MAXIMIZED_VERT)); + } else if (! (newstate & Qt::WindowFullScreen)) { + if (newstate & Qt::WindowMaximized) { + // save original geometry + const QRect normalGeometry = geometry(); + + if (isVisible()) { + data->fstrut_dirty = true; + const QRect maxRect = QApplication::desktop()->availableGeometry(this); + const QRect r = top->normalGeometry; + const QRect fs = d->frameStrut(); + setGeometry(maxRect.x() + fs.left(), + maxRect.y() + fs.top(), + maxRect.width() - fs.left() - fs.right(), + maxRect.height() - fs.top() - fs.bottom()); + top->normalGeometry = r; + } + + if (top->normalGeometry.width() < 0) + top->normalGeometry = normalGeometry; + } else { + // restore original geometry + setGeometry(top->normalGeometry); + } + } + } + + if ((oldstate & Qt::WindowFullScreen) != (newstate & Qt::WindowFullScreen)) { + if (X11->isSupportedByWM(ATOM(_NET_WM_STATE_FULLSCREEN))) { + if (newstate & Qt::WindowFullScreen) { + top->normalGeometry = geometry(); + top->fullScreenOffset = d->frameStrut().topLeft(); + } + qt_change_net_wm_state(this, (newstate & Qt::WindowFullScreen), + ATOM(_NET_WM_STATE_FULLSCREEN)); + } else { + needShow = isVisible(); + + if (newstate & Qt::WindowFullScreen) { + data->fstrut_dirty = true; + const QRect normalGeometry = geometry(); + const QPoint fullScreenOffset = d->frameStrut().topLeft(); + + top->savedFlags = windowFlags(); + setParent(0, Qt::Window | Qt::FramelessWindowHint); + const QRect r = top->normalGeometry; + setGeometry(qApp->desktop()->screenGeometry(this)); + top->normalGeometry = r; + + if (top->normalGeometry.width() < 0) { + top->normalGeometry = normalGeometry; + top->fullScreenOffset = fullScreenOffset; + } + } else { + setParent(0, top->savedFlags); + + if (newstate & Qt::WindowMaximized) { + // from fullscreen to maximized + data->fstrut_dirty = true; + const QRect maxRect = QApplication::desktop()->availableGeometry(this); + const QRect r = top->normalGeometry; + const QRect fs = d->frameStrut(); + setGeometry(maxRect.x() + fs.left(), + maxRect.y() + fs.top(), + maxRect.width() - fs.left() - fs.right(), + maxRect.height() - fs.top() - fs.bottom()); + top->normalGeometry = r; + } else { + // restore original geometry + setGeometry(top->normalGeometry.adjusted(-top->fullScreenOffset.x(), + -top->fullScreenOffset.y(), + -top->fullScreenOffset.x(), + -top->fullScreenOffset.y())); + } + } + } + } + + createWinId(); + Q_ASSERT(testAttribute(Qt::WA_WState_Created)); + if ((oldstate & Qt::WindowMinimized) != (newstate & Qt::WindowMinimized)) { + if (isVisible()) { + if (newstate & Qt::WindowMinimized) { + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.message_type = ATOM(WM_CHANGE_STATE); + e.xclient.display = X11->display; + e.xclient.window = data->winid; + e.xclient.format = 32; + e.xclient.data.l[0] = IconicState; + e.xclient.data.l[1] = 0; + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = 0; + e.xclient.data.l[4] = 0; + XSendEvent(X11->display, + RootWindow(X11->display,d->xinfo.screen()), + False, (SubstructureNotifyMask|SubstructureRedirectMask), &e); + } else { + setAttribute(Qt::WA_Mapped); + XMapWindow(X11->display, effectiveWinId()); + } + } + + needShow = false; + } + } + + data->window_state = newstate; + + if (needShow) + show(); + + if (newstate & Qt::WindowActive) + activateWindow(); + + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); +} + +/*! + \internal + Platform-specific part of QWidget::show(). +*/ + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (q->testAttribute(Qt::WA_DontShowOnScreen)) { + invalidateBuffer(q->rect()); + q->setAttribute(Qt::WA_Mapped); + if (QTLWExtra *tlwExtra = maybeTopData()) + tlwExtra->waitingForMapNotify = 0; + return; + } + + if (q->isWindow()) { + XWMHints *h = XGetWMHints(X11->display, q->internalWinId()); + XWMHints wm_hints; + bool got_hints = h != 0; + if (!got_hints) { + memset(&wm_hints, 0, sizeof(wm_hints)); // make valgrind happy + h = &wm_hints; + } + h->initial_state = q->isMinimized() ? IconicState : NormalState; + h->flags |= StateHint; + XSetWMHints(X11->display, q->internalWinId(), h); + if (got_hints) + XFree((char *)h); + + // update WM_NORMAL_HINTS + do_size_hints(q, extra); + + // udpate WM_TRANSIENT_FOR + if (isTransient(q)) { + QWidget *p = q->parentWidget(); + +#ifndef QT_NO_MENU + // hackish ... try to find the main window related to this QMenu + if (qobject_cast<QMenu *>(q)) { + p = static_cast<QMenuPrivate*>(this)->causedPopup.widget; + if (!p) + p = q->parentWidget(); + if (!p) + p = QApplication::widgetAt(q->pos()); + if (!p) + p = qApp->activeWindow(); + } +#endif + if (p) + p = p->window(); + if (p) { + // transient for window + XSetTransientForHint(X11->display, q->internalWinId(), p->internalWinId()); + } else { + // transient for group + XSetTransientForHint(X11->display, q->internalWinId(), X11->wm_client_leader); + } + } + + // update _MOTIF_WM_HINTS + QtMWMHints mwmhints = GetMWMHints(X11->display, q->internalWinId()); + + if (data.window_modality != Qt::NonModal) { + switch (data.window_modality) { + case Qt::WindowModal: + mwmhints.input_mode = MWM_INPUT_PRIMARY_APPLICATION_MODAL; + break; + case Qt::ApplicationModal: + default: + mwmhints.input_mode = MWM_INPUT_FULL_APPLICATION_MODAL; + break; + } + mwmhints.flags |= MWM_HINTS_INPUT_MODE; + } else { + mwmhints.input_mode = MWM_INPUT_MODELESS; + mwmhints.flags &= ~MWM_HINTS_INPUT_MODE; + } + + if (q->minimumSize() == q->maximumSize()) { + // fixed size, remove the resize handle (since mwm/dtwm + // isn't smart enough to do it itself) + mwmhints.flags |= MWM_HINTS_FUNCTIONS; + if (mwmhints.functions == MWM_FUNC_ALL) { + mwmhints.functions = MWM_FUNC_MOVE; + } else { + mwmhints.functions &= ~MWM_FUNC_RESIZE; + } + + if (mwmhints.decorations == MWM_DECOR_ALL) { + mwmhints.flags |= MWM_HINTS_DECORATIONS; + mwmhints.decorations = (MWM_DECOR_BORDER + | MWM_DECOR_TITLE + | MWM_DECOR_MENU); + } else { + mwmhints.decorations &= ~MWM_DECOR_RESIZEH; + } + + if (q->windowFlags() & Qt::WindowMinimizeButtonHint) { + mwmhints.flags |= MWM_HINTS_DECORATIONS; + mwmhints.decorations |= MWM_DECOR_MINIMIZE; + mwmhints.functions |= MWM_FUNC_MINIMIZE; + } + if (q->windowFlags() & Qt::WindowMaximizeButtonHint) { + mwmhints.flags |= MWM_HINTS_DECORATIONS; + mwmhints.decorations |= MWM_DECOR_MAXIMIZE; + mwmhints.functions |= MWM_FUNC_MAXIMIZE; + } + if (q->windowFlags() & Qt::WindowCloseButtonHint) + mwmhints.functions |= MWM_FUNC_CLOSE; + } + + SetMWMHints(X11->display, q->internalWinId(), mwmhints); + + // update _NET_WM_STATE + QVector<Atom> netWmState = getNetWmState(q); + + Qt::WindowFlags flags = q->windowFlags(); + if (flags & Qt::WindowStaysOnTopHint) { + if (flags & Qt::WindowStaysOnBottomHint) + qWarning() << "QWidget: Incompatible window flags: the window can't be on top and on bottom at the same time"; + if (!netWmState.contains(ATOM(_NET_WM_STATE_ABOVE))) + netWmState.append(ATOM(_NET_WM_STATE_ABOVE)); + if (!netWmState.contains(ATOM(_NET_WM_STATE_STAYS_ON_TOP))) + netWmState.append(ATOM(_NET_WM_STATE_STAYS_ON_TOP)); + } else if (flags & Qt::WindowStaysOnBottomHint) { + if (!netWmState.contains(ATOM(_NET_WM_STATE_BELOW))) + netWmState.append(ATOM(_NET_WM_STATE_BELOW)); + } + if (q->isFullScreen()) { + if (!netWmState.contains(ATOM(_NET_WM_STATE_FULLSCREEN))) + netWmState.append(ATOM(_NET_WM_STATE_FULLSCREEN)); + } + if (q->isMaximized()) { + if (!netWmState.contains(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ))) + netWmState.append(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ)); + if (!netWmState.contains(ATOM(_NET_WM_STATE_MAXIMIZED_VERT))) + netWmState.append(ATOM(_NET_WM_STATE_MAXIMIZED_VERT)); + } + if (data.window_modality != Qt::NonModal) { + if (!netWmState.contains(ATOM(_NET_WM_STATE_MODAL))) + netWmState.append(ATOM(_NET_WM_STATE_MODAL)); + } + + if (!netWmState.isEmpty()) { + XChangeProperty(X11->display, q->internalWinId(), + ATOM(_NET_WM_STATE), XA_ATOM, 32, PropModeReplace, + (unsigned char *) netWmState.data(), netWmState.size()); + } else { + XDeleteProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_STATE)); + } + + // set _NET_WM_USER_TIME + Time userTime = X11->userTime; + bool setUserTime = false; + if (q->testAttribute(Qt::WA_ShowWithoutActivating)) { + userTime = 0; + setUserTime = true; + } else if (userTime != CurrentTime) { + setUserTime = true; + } + if (setUserTime) + qt_net_update_user_time(q, userTime); + +#ifndef QT_NO_XSYNC + if (!topData()->syncUpdateCounter) { + XSyncValue value; + XSyncIntToValue(&value, 0); + topData()->syncUpdateCounter = XSyncCreateCounter(X11->display, value); + + XChangeProperty(X11->display, q->internalWinId(), + ATOM(_NET_WM_SYNC_REQUEST_COUNTER), + XA_CARDINAL, + 32, PropModeReplace, + (uchar *) &topData()->syncUpdateCounter, 1); + + topData()->newCounterValueHi = 0; + topData()->newCounterValueLo = 0; + } +#endif + + if (!topData()->embedded + && (topData()->validWMState || topData()->waitingForMapNotify) + && !q->isMinimized()) { + X11->deferred_map.append(q); + return; + } + + if (q->isMaximized() && !q->isFullScreen() + && !(X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ)) + && X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_VERT)))) { + XMapWindow(X11->display, q->internalWinId()); + data.fstrut_dirty = true; + qt_x11_wait_for_window_manager(q); + + // if the wm was not smart enough to adjust our size, do that manually + QRect maxRect = QApplication::desktop()->availableGeometry(q); + + QTLWExtra *top = topData(); + QRect normalRect = top->normalGeometry; + const QRect fs = frameStrut(); + + q->setGeometry(maxRect.x() + fs.left(), + maxRect.y() + fs.top(), + maxRect.width() - fs.left() - fs.right(), + maxRect.height() - fs.top() - fs.bottom()); + + // restore the original normalGeometry + top->normalGeometry = normalRect; + // internalSetGeometry() clears the maximized flag... make sure we set it back + data.window_state = data.window_state | Qt::WindowMaximized; + q->setAttribute(Qt::WA_Mapped); + return; + } + + if (q->isFullScreen() && !X11->isSupportedByWM(ATOM(_NET_WM_STATE_FULLSCREEN))) { + XMapWindow(X11->display, q->internalWinId()); + qt_x11_wait_for_window_manager(q); + q->setAttribute(Qt::WA_Mapped); + return; + } + } + + invalidateBuffer(q->rect()); + + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + q->setAttribute(Qt::WA_Mapped); + if (q->isWindow()) + topData()->waitingForMapNotify = 1; + + if (!q->isWindow() + && (!q->autoFillBackground() + || q->palette().brush(q->backgroundRole()).style() == Qt::LinearGradientPattern)) { + if (q->internalWinId()) { + XSetWindowBackgroundPixmap(X11->display, q->internalWinId(), XNone); + XMapWindow(X11->display, q->internalWinId()); + updateSystemBackground(); + } + return; + } + + if (q->internalWinId()) + XMapWindow(X11->display, q->internalWinId()); + + // Freedesktop.org Startup Notification + if (X11->startupId && q->isWindow()) { + QByteArray message("remove: ID="); + message.append(X11->startupId); + sendStartupMessage(message.constData()); + X11->startupId = 0; + } +} + +/*! + \internal + Platform-specific part of QWidget::show(). +*/ + +void QWidgetPrivate::sendStartupMessage(const char *message) const +{ + Q_Q(const QWidget); + + if (!message) + return; + + XEvent xevent; + xevent.xclient.type = ClientMessage; + xevent.xclient.message_type = ATOM(_NET_STARTUP_INFO_BEGIN); + xevent.xclient.display = X11->display; + xevent.xclient.window = q->internalWinId(); + xevent.xclient.format = 8; + + Window rootWindow = RootWindow(X11->display, DefaultScreen(X11->display)); + uint sent = 0; + uint length = strlen(message) + 1; + do { + if (sent == 20) + xevent.xclient.message_type = ATOM(_NET_STARTUP_INFO); + + for (uint i = 0; i < 20 && i + sent <= length; i++) + xevent.xclient.data.b[i] = message[i + sent++]; + + XSendEvent(X11->display, rootWindow, false, PropertyChangeMask, &xevent); + } while (sent <= length); +} + +void QWidgetPrivate::setNetWmWindowTypes() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (!q->isWindow()) { + if (q->internalWinId()) + XDeleteProperty(X11->display, q->internalWinId(), ATOM(_NET_WM_WINDOW_TYPE)); + return; + } + + QVector<long> windowTypes; + + // manual selection 1 (these are never set by Qt and take precedence) + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeDesktop)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DESKTOP)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeDock)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DOCK)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeNotification)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_NOTIFICATION)); + + // manual selection 2 (Qt uses these during auto selection); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeUtility)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_UTILITY)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeSplash)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_SPLASH)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeDialog)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DIALOG)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeToolTip)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_TOOLTIP)); + + // 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 (q->testAttribute(Qt::WA_X11NetWmWindowTypeMenu)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_MENU)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeDropDownMenu)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DROPDOWN_MENU)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypePopupMenu)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_POPUP_MENU)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeToolBar)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeCombo)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_COMBO)); + if (q->testAttribute(Qt::WA_X11NetWmWindowTypeDND)) + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DND)); + + // automatic selection + switch (q->windowType()) { + case Qt::Dialog: + case Qt::Sheet: + // dialog netwm type + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_DIALOG)); + break; + + case Qt::Tool: + case Qt::Drawer: + // utility netwm type + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_UTILITY)); + break; + + case Qt::ToolTip: + // tooltip netwm type + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_TOOLTIP)); + break; + + case Qt::SplashScreen: + // splash netwm type + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_SPLASH)); + break; + + default: + break; + } + + if (q->windowFlags() & Qt::FramelessWindowHint) { + // override netwm type - quick and easy for KDE noborder + windowTypes.append(ATOM(_KDE_NET_WM_WINDOW_TYPE_OVERRIDE)); + } + + // normal netwm type - default + windowTypes.append(ATOM(_NET_WM_WINDOW_TYPE_NORMAL)); + + if (!windowTypes.isEmpty()) { + XChangeProperty(X11->display, q->winId(), ATOM(_NET_WM_WINDOW_TYPE), XA_ATOM, 32, + PropModeReplace, (unsigned char *) windowTypes.constData(), + windowTypes.count()); + } else { + XDeleteProperty(X11->display, q->winId(), ATOM(_NET_WM_WINDOW_TYPE)); + } +} + +/*! + \internal + Platform-specific part of QWidget::hide(). +*/ + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + deactivateWidgetCleanup(); + if (q->isWindow()) { + X11->deferred_map.removeAll(q); + if (q->internalWinId()) // in nsplugin, may be 0 + XWithdrawWindow(X11->display, q->internalWinId(), xinfo.screen()); + XFlush(X11->display); + } else { + invalidateBuffer(q->rect()); + if (q->internalWinId()) // in nsplugin, may be 0 + XUnmapWindow(X11->display, q->internalWinId()); + } + q->setAttribute(Qt::WA_Mapped, false); +} + +void QWidgetPrivate::setFocus_sys() +{ + +} + + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) + XRaiseWindow(X11->display, q->internalWinId()); +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId()) + XLowerWindow(X11->display, q->internalWinId()); + if(!q->isWindow()) + invalidateBuffer(q->rect()); +} + +void QWidgetPrivate::stackUnder_sys(QWidget* w) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + if (q->internalWinId() && w->internalWinId()) { + Window stack[2]; + stack[0] = w->internalWinId();; + stack[1] = q->internalWinId(); + XRestackWindows(X11->display, stack, 2); + } + if(!q->isWindow() || !w->internalWinId()) + invalidateBuffer(q->rect()); +} + + +static void do_size_hints(QWidget* widget, QWExtra *x) +{ + Q_ASSERT(widget->testAttribute(Qt::WA_WState_Created)); + XSizeHints s; + s.flags = 0; + if (x) { + QRect g = widget->geometry(); + s.x = g.x(); + s.y = g.y(); + s.width = g.width(); + s.height = g.height(); + if (x->minw > 0 || x->minh > 0) { + // add minimum size hints + s.flags |= PMinSize; + s.min_width = qMin(XCOORD_MAX, x->minw); + s.min_height = qMin(XCOORD_MAX, x->minh); + } + if (x->maxw < QWIDGETSIZE_MAX || x->maxh < QWIDGETSIZE_MAX) { + // add maximum size hints + s.flags |= PMaxSize; + s.max_width = qMin(XCOORD_MAX, x->maxw); + s.max_height = qMin(XCOORD_MAX, x->maxh); + } + if (x->topextra && + (x->topextra->incw > 0 || x->topextra->inch > 0)) { + // add resize increment hints + s.flags |= PResizeInc | PBaseSize; + s.width_inc = x->topextra->incw; + s.height_inc = x->topextra->inch; + s.base_width = x->topextra->basew; + s.base_height = x->topextra->baseh; + } + } + if (widget->testAttribute(Qt::WA_Moved)) { + // user (i.e. command-line) specified position + s.flags |= USPosition; + s.flags |= PPosition; + } + if (widget->testAttribute(Qt::WA_Resized)) { + // user (i.e. command-line) specified size + s.flags |= USSize; + s.flags |= PSize; + } + s.flags |= PWinGravity; + if (widget->testAttribute(Qt::WA_Moved) && x && x->topextra && !x->topextra->posFromMove) { + // position came from setGeometry(), tell the WM that we don't + // want our window gravity-shifted + s.win_gravity = StaticGravity; + } else { + // position came from move() + s.x = widget->x(); + s.y = widget->y(); + s.win_gravity = QApplication::isRightToLeft() ? NorthEastGravity : NorthWestGravity; + } + if (widget->internalWinId()) + XSetWMNormalHints(X11->display, widget->internalWinId(), &s); +} + + +/* + Helper function for non-toplevel widgets. Helps to map Qt's 32bit + coordinate system to X11's 16bit coordinate system. + + Sets the geometry of the widget to data.crect, but clipped to sizes + that X can handle. Unmaps widgets that are completely outside the + valid range. + + Maintains data.wrect, which is the geometry of the X widget, + measured in this widget's coordinate system. + + if the parent is not clipped, parentWRect is empty, otherwise + parentWRect is the geometry of the parent's X rect, measured in + parent's coord sys + */ +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + /* + There are up to four different coordinate systems here: + Qt coordinate system for this widget. + X coordinate system for this widget (relative to wrect). + Qt coordinate system for parent + X coordinate system for parent (relative to parent's wrect). + */ + Display *dpy = xinfo.display(); + QRect validRange(-XCOORD_MAX,-XCOORD_MAX, 2*XCOORD_MAX, 2*XCOORD_MAX); + QRect wrectRange(-WRECT_MAX,-WRECT_MAX, 2*WRECT_MAX, 2*WRECT_MAX); + QRect wrect; + //xrect is the X geometry of my X widget. (starts out in parent's Qt coord sys, and ends up in parent's X coord sys) + QRect xrect = data.crect; + + const QWidget *const parent = q->parentWidget(); + QRect parentWRect = parent->data->wrect; + + if (parentWRect.isValid()) { + // parent is clipped, and we have to clip to the same limit as parent + if (!parentWRect.contains(xrect)) { + xrect &= parentWRect; + wrect = xrect; + //translate from parent's to my Qt coord sys + wrect.translate(-data.crect.topLeft()); + } + //translate from parent's Qt coords to parent's X coords + xrect.translate(-parentWRect.topLeft()); + + } else { + // parent is not clipped, we may or may not have to clip + + if (data.wrect.isValid() && QRect(QPoint(),data.crect.size()).contains(data.wrect)) { + // This is where the main optimization is: we are already + // clipped, and if our clip is still valid, we can just + // move our window, and do not need to move or clip + // children + + QRect vrect = xrect & parent->rect(); + vrect.translate(-data.crect.topLeft()); //the part of me that's visible through parent, in my Qt coords + if (data.wrect.contains(vrect)) { + xrect = data.wrect; + xrect.translate(data.crect.topLeft()); + if (data.winid) + XMoveWindow(dpy, data.winid, xrect.x(), xrect.y()); + return; + } + } + + if (!validRange.contains(xrect)) { + // we are too big, and must clip + xrect &=wrectRange; + wrect = xrect; + wrect.translate(-data.crect.topLeft()); + //parent's X coord system is equal to parent's Qt coord + //sys, so we don't need to map xrect. + } + + } + + // unmap if we are outside the valid window system coord system + bool outsideRange = !xrect.isValid(); + bool mapWindow = false; + if (q->testAttribute(Qt::WA_OutsideWSRange) != outsideRange) { + q->setAttribute(Qt::WA_OutsideWSRange, outsideRange); + if (outsideRange) { + if (data.winid) + XUnmapWindow(dpy, data.winid); + q->setAttribute(Qt::WA_Mapped, false); + } else if (!q->isHidden()) { + mapWindow = true; + } + } + + if (outsideRange) + return; + + bool jump = (data.wrect != wrect); + data.wrect = wrect; + + + // and now recursively for all children... + // ### can be optimized + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(jump); + } + } + + if (data.winid) { + // move ourselves to the new position and map (if necessary) after + // the movement. Rationale: moving unmapped windows is much faster + // than moving mapped windows + if (jump) //avoid flicker when jumping + XSetWindowBackgroundPixmap(dpy, data.winid, XNone); + if (!parent->internalWinId()) + xrect.translate(parent->mapTo(q->nativeParentWidget(), QPoint(0, 0))); + XMoveResizeWindow(dpy, data.winid, xrect.x(), xrect.y(), xrect.width(), xrect.height()); + } + + //to avoid flicker, we have to show children after the helper widget has moved + if (jump) { + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->testAttribute(Qt::WA_OutsideWSRange) && !w->testAttribute(Qt::WA_Mapped) && !w->isHidden()) { + w->setAttribute(Qt::WA_Mapped); + if (w->internalWinId()) + XMapWindow(dpy, w->data->winid); + } + } + } + } + + + if (jump && data.winid) + XClearArea(dpy, data.winid, 0, 0, wrect.width(), wrect.height(), True); + + if (mapWindow && !dontShow) { + q->setAttribute(Qt::WA_Mapped); + if (data.winid) + XMapWindow(dpy, data.winid); + } +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + Display *dpy = X11->display; + + if ((q->windowType() == Qt::Desktop)) + return; + if (q->isWindow()) { + if (!X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_VERT)) + && !X11->isSupportedByWM(ATOM(_NET_WM_STATE_MAXIMIZED_HORZ))) + data.window_state &= ~Qt::WindowMaximized; + if (!X11->isSupportedByWM(ATOM(_NET_WM_STATE_FULLSCREEN))) + data.window_state &= ~Qt::WindowFullScreen; + if (QTLWExtra *topData = maybeTopData()) + topData->normalGeometry = QRect(0,0,-1,-1); + } else { + uint s = data.window_state; + s &= ~(Qt::WindowMaximized | Qt::WindowFullScreen); + data.window_state = s; + } + if (extra) { // any size restrictions? + w = qMin(w,extra->maxw); + h = qMin(h,extra->maxh); + w = qMax(w,extra->minw); + h = qMax(h,extra->minh); + } + QPoint oldPos(q->pos()); + QSize oldSize(q->size()); + QRect oldGeom(data.crect); + QRect r(x, y, w, h); + + // We only care about stuff that changes the geometry, or may + // cause the window manager to change its state + if (!q->isWindow() && oldGeom == r) + return; + + data.crect = r; + bool isResize = q->size() != oldSize; + + if (q->isWindow()) { + if (w == 0 || h == 0) { + q->setAttribute(Qt::WA_OutsideWSRange, true); + if (q->isVisible() && q->testAttribute(Qt::WA_Mapped)) + hide_sys(); + } else if (q->isVisible() && q->testAttribute(Qt::WA_OutsideWSRange)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); + + // put the window in its place and show it + if (data.winid) + XMoveResizeWindow(dpy, data.winid, x, y, w, h); + topData()->posFromMove = false; // force StaticGravity + do_size_hints(q, extra); + show_sys(); + } else { + q->setAttribute(Qt::WA_OutsideWSRange, false); + if (!q->isVisible()) + do_size_hints(q, extra); + if (isMove) { + if ((data.window_flags & Qt::X11BypassWindowManagerHint) == Qt::X11BypassWindowManagerHint + // work around 4Dwm's incompliance with ICCCM 4.1.5 + || X11->desktopEnvironment == DE_4DWM) { + if (data.winid) + XMoveResizeWindow(dpy, data.winid, x, y, w, h); + } else if (q->isVisible() + && topData()->validWMState + && X11->isSupportedByWM(ATOM(_NET_MOVERESIZE_WINDOW))) { + XEvent e; + e.xclient.type = ClientMessage; + e.xclient.message_type = ATOM(_NET_MOVERESIZE_WINDOW); + e.xclient.display = X11->display; + e.xclient.window = q->internalWinId(); + e.xclient.format = 32; + e.xclient.data.l[0] = StaticGravity | 1<<8 | 1<<9 | 1<<10 | 1<<11 | 1<<12; + e.xclient.data.l[1] = x; + e.xclient.data.l[2] = y; + e.xclient.data.l[3] = w; + e.xclient.data.l[4] = h; + XSendEvent(X11->display, RootWindow(X11->display, q->x11Info().screen()), + false, (SubstructureNotifyMask | SubstructureRedirectMask), &e); + } else if (data.winid) { + // pos() is right according to ICCCM 4.1.5 + XMoveResizeWindow(dpy, data.winid, q->pos().x(), q->pos().y(), w, h); + } + } else if (isResize && data.winid) { + if (!q->isVisible() + && topData()->validWMState + && !q->testAttribute(Qt::WA_PendingMoveEvent)) { + /* + even though we've not visible, we could be in a + race w/ the window manager, and it may ignore + our ConfigureRequest. setting posFromMove to + false makes sure that doDeferredMap() in + qapplication_x11.cpp keeps the window in the + right place + */ + topData()->posFromMove = false; + } + XResizeWindow(dpy, data.winid, w, h); + } + } + if (isResize && !q->testAttribute(Qt::WA_DontShowOnScreen)) // set config pending only on resize, see qapplication_x11.cpp, translateConfigEvent() + q->setAttribute(Qt::WA_WState_ConfigPending); + + } else { + QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData(); + const bool inTopLevelResize = tlwExtra ? tlwExtra->inTopLevelResize : false; + const bool disableInTopLevelResize = inTopLevelResize && q->internalWinId(); + if (disableInTopLevelResize) { + // Top-level resize optimization does not work for native child widgets; + // disable it for this particular widget. + tlwExtra->inTopLevelResize = false; + } + + if (!isResize && (!inTopLevelResize || disableInTopLevelResize) && q->isVisible()) { + moveRect(QRect(oldPos, oldSize), x - oldPos.x(), y - oldPos.y()); + } + if (q->testAttribute(Qt::WA_WState_Created)) + setWSGeometry(); + + if (isResize && (!inTopLevelResize || disableInTopLevelResize) && q->isVisible()) + invalidateBuffer_resizeHelper(oldPos, oldSize); + + if (disableInTopLevelResize) + tlwExtra->inTopLevelResize = true; + } + + if (q->isVisible()) { + if (isMove && q->pos() != oldPos) { + if (X11->desktopEnvironment != DE_4DWM) { + // pos() is right according to ICCCM 4.1.5 + QMoveEvent e(q->pos(), oldPos); + QApplication::sendEvent(q, &e); + } else { + // work around 4Dwm's incompliance with ICCCM 4.1.5 + QMoveEvent e(data.crect.topLeft(), oldGeom.topLeft()); + QApplication::sendEvent(q, &e); + } + } + if (isResize) { + static bool slowResize = qgetenv("QT_SLOW_TOPLEVEL_RESIZE").toInt(); + // If we have a backing store with static contents, we have to disable the top-level + // resize optimization in order to get invalidated regions for resized widgets. + // The optimization discards all invalidateBuffer() calls since we're going to + // repaint everything anyways, but that's not the case with static contents. + const bool setTopLevelResize = !slowResize && q->isWindow() && extra && extra->topextra + && !extra->topextra->inTopLevelResize + && (!extra->topextra->backingStore + || !extra->topextra->backingStore->hasStaticContents()); + if (setTopLevelResize) + extra->topextra->inTopLevelResize = true; + QResizeEvent e(q->size(), oldSize); + QApplication::sendEvent(q, &e); + if (setTopLevelResize) + extra->topextra->inTopLevelResize = false; + } + } else { + if (isMove && q->pos() != oldPos) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } +} + +void QWidgetPrivate::setConstraints_sys() +{ + Q_Q(QWidget); +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::setConstraints_sys START" << q; +#endif + if (q->testAttribute(Qt::WA_WState_Created)) + do_size_hints(q, extra); +#ifdef ALIEN_DEBUG + qDebug() << "QWidgetPrivate::setConstraints_sys END" << q; +#endif +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + Q_Q(QWidget); + + scrollChildren(dx, dy); + if (!paintOnScreen()) { + scrollRect(q->rect(), dx, dy); + } else { + scroll_sys(dx, dy, QRect()); + } +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r) +{ + Q_Q(QWidget); + + if (!paintOnScreen()) { + scrollRect(r, dx, dy); + return; + } + bool valid_rect = r.isValid(); + bool just_update = qAbs(dx) > q->width() || qAbs(dy) > q->height(); + QRect sr = valid_rect ? r : clipRect(); + if (just_update) + q->update(); + else if (!valid_rect) + dirty.translate(dx, dy); + + int x1, y1, x2, y2, w = sr.width(), h = sr.height(); + if (dx > 0) { + x1 = sr.x(); + x2 = x1+dx; + w -= dx; + } else { + x2 = sr.x(); + x1 = x2-dx; + w += dx; + } + if (dy > 0) { + y1 = sr.y(); + y2 = y1+dy; + h -= dy; + } else { + y2 = sr.y(); + y1 = y2-dy; + h += dy; + } + + if (dx == 0 && dy == 0) + return; + + Display *dpy = X11->display; + // Want expose events + if (w > 0 && h > 0 && !just_update && q->internalWinId()) { + GC gc = XCreateGC(dpy, q->internalWinId(), 0, 0); + XSetGraphicsExposures(dpy, gc, True); + XCopyArea(dpy, q->internalWinId(), q->internalWinId(), gc, x1, y1, w, h, x2, y2); + XFreeGC(dpy, gc); + } + + if (!valid_rect && !children.isEmpty()) { // scroll children + QPoint pd(dx, dy); + for (int i = 0; i < children.size(); ++i) { // move all children + register QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow()) + w->move(w->pos() + pd); + } + } + } + + if (just_update) + return; + + // Don't let the server be bogged-down with repaint events + bool repaint_immediately = (qt_sip_count(q) < 3 && !q->testAttribute(Qt::WA_WState_InPaintEvent)); + + if (dx) { + int x = x2 == sr.x() ? sr.x()+w : sr.x(); + if (repaint_immediately) + q->repaint(x, sr.y(), qAbs(dx), sr.height()); + else if (q->internalWinId()) + XClearArea(dpy, data.winid, x, sr.y(), qAbs(dx), sr.height(), True); + } + if (dy) { + int y = y2 == sr.y() ? sr.y()+h : sr.y(); + if (repaint_immediately) + q->repaint(sr.x(), y, sr.width(), qAbs(dy)); + else if (q->internalWinId()) + XClearArea(dpy, data.winid, sr.x(), y, sr.width(), qAbs(dy), True); + } + + qt_insert_sip(q, dx, dy); // #### ignores r +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + Q_D(const QWidget); + int val; + if (m == PdmWidth) { + val = data->crect.width(); + } else if (m == PdmHeight) { + val = data->crect.height(); + } else { + Display *dpy = X11->display; + int scr = d->xinfo.screen(); + switch (m) { + case PdmDpiX: + case PdmPhysicalDpiX: + if (d->extra && d->extra->customDpiX) + val = d->extra->customDpiX; + else if (d->parent) + val = static_cast<QWidget *>(d->parent)->metric(m); + else + val = QX11Info::appDpiX(scr); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + if (d->extra && d->extra->customDpiY) + val = d->extra->customDpiY; + else if (d->parent) + val = static_cast<QWidget *>(d->parent)->metric(m); + else + val = QX11Info::appDpiY(scr); + break; + case PdmWidthMM: + val = (DisplayWidthMM(dpy,scr)*data->crect.width())/ + DisplayWidth(dpy,scr); + break; + case PdmHeightMM: + val = (DisplayHeightMM(dpy,scr)*data->crect.height())/ + DisplayHeight(dpy,scr); + break; + case PdmNumColors: + val = d->xinfo.cells(); + break; + case PdmDepth: + val = d->xinfo.depth(); + break; + default: + val = 0; + qWarning("QWidget::metric: Invalid metric command"); + } + } + return val; +} + +void QWidgetPrivate::createSysExtra() +{ + extra->compress_events = true; + extra->xDndProxy = 0; +} + +void QWidgetPrivate::deleteSysExtra() +{ +} + +void QWidgetPrivate::createTLSysExtra() +{ + extra->topextra->spont_unmapped = 0; + extra->topextra->dnd = 0; + extra->topextra->validWMState = 0; + extra->topextra->waitingForMapNotify = 0; + extra->topextra->parentWinId = 0; + extra->topextra->userTimeWindow = 0; +#ifndef QT_NO_XSYNC + extra->topextra->syncUpdateCounter = 0; + extra->topextra->syncRequestTimestamp = 0; + extra->topextra->newCounterValueHi = 0; + extra->topextra->newCounterValueLo = 0; +#endif +} + +void QWidgetPrivate::deleteTLSysExtra() +{ + // don't destroy input context here. it will be destroyed in + // QWidget::destroy() destroyInputContext(); +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_UNUSED(on); +} + +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; + + if (region.isEmpty()) { + XShapeCombineMask(X11->display, q->internalWinId(), ShapeBounding, 0, 0, + XNone, ShapeSet); + } else { + XShapeCombineRegion(X11->display, q->internalWinId(), ShapeBounding, 0, 0, + region.handle(), ShapeSet); + } +} + +/*! + \internal + + Computes the frame rectangle when needed. This is an internal function, you + should never call this. +*/ + +void QWidgetPrivate::updateFrameStrut() +{ + Q_Q(QWidget); + + QTLWExtra *top = topData(); + if (!top->validWMState) { + return; + } + if (!q->isWindow() && !q->internalWinId()) { + data.fstrut_dirty = false; + return; + } + + Atom type_ret; + Window l = q->effectiveWinId(), w = l, p, r; // target window, its parent, root + Window *c; + int i_unused; + unsigned int nc; + unsigned char *data_ret; + unsigned long l_unused; + + while (XQueryTree(X11->display, w, &r, &p, &c, &nc)) { + if (c && nc > 0) + XFree(c); + + if (! p) { + qWarning("QWidget::updateFrameStrut: No parent"); + return; + } + + // if the parent window is the root window, an Enlightenment virtual root or + // a NET WM virtual root window, stop here + data_ret = 0; + if (p == r || + (XGetWindowProperty(X11->display, p, + ATOM(ENLIGHTENMENT_DESKTOP), 0, 1, False, XA_CARDINAL, + &type_ret, &i_unused, &l_unused, &l_unused, + &data_ret) == Success && + type_ret == XA_CARDINAL)) { + if (data_ret) + XFree(data_ret); + + break; + } else if (X11->isSupportedByWM(ATOM(_NET_VIRTUAL_ROOTS)) && X11->net_virtual_root_list) { + int i = 0; + while (X11->net_virtual_root_list[i] != 0) { + if (X11->net_virtual_root_list[i++] == p) + break; + } + } + + l = w; + w = p; + } + + // we have our window + int transx, transy; + XWindowAttributes wattr; + if (XTranslateCoordinates(X11->display, l, w, + 0, 0, &transx, &transy, &p) && + XGetWindowAttributes(X11->display, w, &wattr)) { + top->frameStrut.setCoords(transx, + transy, + wattr.width - data.crect.width() - transx, + wattr.height - data.crect.height() - transy); + + // add the border_width for the window managers frame... some window managers + // do not use a border_width of zero for their frames, and if we the left and + // top strut, we ensure that pos() is absolutely correct. frameGeometry() + // will still be incorrect though... perhaps i should have foffset as well, to + // indicate the frame offset (equal to the border_width on X). + // - Brad + top->frameStrut.adjust(wattr.border_width, + wattr.border_width, + wattr.border_width, + wattr.border_width); + } + + data.fstrut_dirty = false; +} + +void QWidgetPrivate::setWindowOpacity_sys(qreal opacity) +{ + Q_Q(QWidget); + ulong value = ulong(opacity * 0xffffffff); + XChangeProperty(QX11Info::display(), q->internalWinId(), ATOM(_NET_WM_WINDOW_OPACITY), XA_CARDINAL, + 32, PropModeReplace, (uchar*)&value, 1); +} + +const QX11Info &QWidget::x11Info() const +{ + Q_D(const QWidget); + return d->xinfo; +} + +void QWidgetPrivate::setWindowRole() +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; + QByteArray windowRole = topData()->role.toUtf8(); + XChangeProperty(X11->display, q->internalWinId(), + ATOM(WM_WINDOW_ROLE), XA_STRING, 8, PropModeReplace, + (unsigned char *)windowRole.constData(), windowRole.length()); +} + +Q_GLOBAL_STATIC(QX11PaintEngine, qt_widget_paintengine) +QPaintEngine *QWidget::paintEngine() const +{ + Q_D(const QWidget); + if (qt_widget_paintengine()->isActive()) { + if (d->extraPaintEngine) + return d->extraPaintEngine; + QWidget *self = const_cast<QWidget *>(this); + self->d_func()->extraPaintEngine = new QX11PaintEngine(); + return d->extraPaintEngine; + } + return qt_widget_paintengine(); +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + return new QX11WindowSurface(q_func()); +} + +Qt::HANDLE QWidget::x11PictureHandle() const +{ +#ifndef QT_NO_XRENDER + Q_D(const QWidget); + if (!internalWinId() && testAttribute(Qt::WA_WState_Created)) + (void)winId(); // enforce native window + return d->picture; +#else + return 0; +#endif // QT_NO_XRENDER +} + +#ifndef QT_NO_XRENDER +XRenderColor QX11Data::preMultiply(const QColor &c) +{ + XRenderColor color; + const uint A = c.alpha(), + R = c.red(), + G = c.green(), + B = c.blue(); + color.alpha = (A | A << 8); + color.red = (R | R << 8) * color.alpha / 0x10000; + color.green = (G | G << 8) * color.alpha / 0x10000; + color.blue = (B | B << 8) * color.alpha / 0x10000; + return color; +} +Picture QX11Data::getSolidFill(int screen, const QColor &c) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = preMultiply(c); + for (int i = 0; i < X11->solid_fill_count; ++i) { + if (X11->solid_fills[i].screen == screen + && X11->solid_fills[i].color.alpha == color.alpha + && X11->solid_fills[i].color.red == color.red + && X11->solid_fills[i].color.green == color.green + && X11->solid_fills[i].color.blue == color.blue) + return X11->solid_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->solid_fills[i].screen != screen && X11->solid_fills[i].picture) { + XRenderFreePicture (X11->display, X11->solid_fills[i].picture); + X11->solid_fills[i].picture = 0; + } + + if (!X11->solid_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 1, 1, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->solid_fills[i].picture = XRenderCreatePicture (X11->display, pixmap, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (X11->display, pixmap); + } + + X11->solid_fills[i].color = color; + X11->solid_fills[i].screen = screen; + XRenderFillRectangle (X11->display, PictOpSrc, X11->solid_fills[i].picture, &color, 0, 0, 1, 1); + return X11->solid_fills[i].picture; +} +#endif + +void QWidgetPrivate::setModal_sys() +{ +} + +void qt_x11_getX11InfoForWindow(QX11Info * xinfo, const QX11WindowAttributes &att) +{ + QX11InfoData* xd = xinfo->getX11Data(true); + const XWindowAttributes &a = *(att.att); + // find which screen the window is on... + xd->screen = QX11Info::appScreen(); // by default, use the default :) + int i; + for (i = 0; i < ScreenCount(X11->display); i++) { + if (RootWindow(X11->display, i) == a.root) { + xd->screen = i; + break; + } + } + + xd->depth = a.depth; + xd->cells = DisplayCells(X11->display, xd->screen); + xd->visual = a.visual; + xd->defaultVisual = (XVisualIDFromVisual((Visual *) a.visual) == + XVisualIDFromVisual((Visual *) QX11Info::appVisual(xinfo->screen()))); + xd->colormap = a.colormap; + xd->defaultColormap = (a.colormap == QX11Info::appColormap(xinfo->screen())); + xinfo->setX11Data(xd); +} + +void QWidgetPrivate::updateX11AcceptFocus() +{ + Q_Q(QWidget); + if (!q->isWindow() || !q->internalWinId()) + return; + + XWMHints *h = XGetWMHints(X11->display, q->internalWinId()); + XWMHints wm_hints; + if (!h) { + memset(&wm_hints, 0, sizeof(wm_hints)); // make valgrind happy + h = &wm_hints; + } + h->flags |= InputHint; + h->input = q->testAttribute(Qt::WA_X11DoNotAcceptFocus) ? False : True; + + XSetWMHints(X11->display, q->internalWinId(), h); + if (h != &wm_hints) + XFree((char *)h); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwidgetaction.cpp b/src/gui/kernel/qwidgetaction.cpp new file mode 100644 index 0000000000..29586da34b --- /dev/null +++ b/src/gui/kernel/qwidgetaction.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidgetaction.h" +#include "qdebug.h" + +#ifndef QT_NO_ACTION +#include "qwidgetaction_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QWidgetAction + \since 4.2 + \brief The QWidgetAction class extends QAction by an interface + for inserting custom widgets into action based containers, such + as toolbars. + + \ingroup mainwindow-classes + + Most actions in an application are represented as items in menus or + buttons in toolbars. However sometimes more complex widgets are + necessary. For example a zoom action in a word processor may be + realized using a QComboBox in a QToolBar, presenting a range + of different zoom levels. QToolBar provides QToolBar::insertWidget() + as convenience function for inserting a single widget. + However if you want to implement an action that uses custom + widgets for visualization in multiple containers then you have to + subclass QWidgetAction. + + If a QWidgetAction is added for example to a QToolBar then + QWidgetAction::createWidget() is called. Reimplementations of that + function should create a new custom widget with the specified parent. + + If the action is removed from a container widget then + QWidgetAction::deleteWidget() is called with the previously created custom + widget as argument. The default implementation hides the widget and deletes + it using QObject::deleteLater(). + + If you have only one single custom widget then you can set it as default + widget using setDefaultWidget(). That widget will then be used if the + action is added to a QToolBar, or in general to an action container that + supports QWidgetAction. If a QWidgetAction with only a default widget is + added to two toolbars at the same time then the default widget is shown + only in the first toolbar the action was added to. QWidgetAction takes + over ownership of the default widget. + + Note that it is up to the widget to activate the action, for example by + reimplementing mouse event handlers and calling QAction::trigger(). + + \bold {Mac OS X}: If you add a widget to a menu in the application's menu + bar on Mac OS X, the widget will be added and it will function but with some + limitations: + \list 1 + \o The widget is reparented away from the QMenu to the native menu + view. If you show the menu in some other place (e.g. as a popup menu), + the widget will not be there. + \o Focus/Keyboard handling of the widget is not possible. + \o Due to Apple's design, mouse tracking on the widget currently does + not work. + \o Connecting the triggered() signal to a slot that opens a modal + dialog will cause a crash in Mac OS X 10.4 (known bug acknowledged + by Apple), a workaround is to use a QueuedConnection instead of a + DirectConnection. + \endlist + + \sa QAction, QActionGroup, QWidget +*/ + +/*! + Constructs an action with \a parent. +*/ +QWidgetAction::QWidgetAction(QObject *parent) + : QAction(*(new QWidgetActionPrivate), parent) +{ +} + +/*! + Destroys the object and frees allocated resources. +*/ +QWidgetAction::~QWidgetAction() +{ + Q_D(QWidgetAction); + for (int i = 0; i < d->createdWidgets.count(); ++i) + disconnect(d->createdWidgets.at(i), SIGNAL(destroyed(QObject*)), + this, SLOT(_q_widgetDestroyed(QObject*))); + QList<QWidget *> widgetsToDelete = d->createdWidgets; + d->createdWidgets.clear(); + qDeleteAll(widgetsToDelete); + delete d->defaultWidget; +} + +/*! + Sets \a widget to be the default widget. The ownership is + transferred to QWidgetAction. Unless createWidget() is + reimplemented by a subclass to return a new widget the default + widget is used when a container widget requests a widget through + requestWidget(). +*/ +void QWidgetAction::setDefaultWidget(QWidget *widget) +{ + Q_D(QWidgetAction); + if (widget == d->defaultWidget || d->defaultWidgetInUse) + return; + delete d->defaultWidget; + d->defaultWidget = widget; + if (!widget) + return; + + setVisible(!(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide))); + d->defaultWidget->hide(); + d->defaultWidget->setParent(0); + d->defaultWidgetInUse = false; + if (!isEnabled()) + d->defaultWidget->setEnabled(false); +} + +/*! + Returns the default widget. +*/ +QWidget *QWidgetAction::defaultWidget() const +{ + Q_D(const QWidgetAction); + return d->defaultWidget; +} + +/*! + Returns a widget that represents the action, with the given \a + parent. + + Container widgets that support actions can call this function to + request a widget as visual representation of the action. + + \sa releaseWidget(), createWidget(), defaultWidget() +*/ +QWidget *QWidgetAction::requestWidget(QWidget *parent) +{ + Q_D(QWidgetAction); + + QWidget *w = createWidget(parent); + if (!w) { + if (d->defaultWidgetInUse || !d->defaultWidget) + return 0; + d->defaultWidget->setParent(parent); + d->defaultWidgetInUse = true; + return d->defaultWidget; + } + + connect(w, SIGNAL(destroyed(QObject*)), + this, SLOT(_q_widgetDestroyed(QObject*))); + d->createdWidgets.append(w); + return w; +} + +/*! + Releases the specified \a widget. + + Container widgets that support actions call this function when a widget + action is removed. + + \sa requestWidget(), deleteWidget(), defaultWidget() +*/ +void QWidgetAction::releaseWidget(QWidget *widget) +{ + Q_D(QWidgetAction); + + if (widget == d->defaultWidget) { + d->defaultWidget->hide(); + d->defaultWidget->setParent(0); + d->defaultWidgetInUse = false; + return; + } + + if (!d->createdWidgets.contains(widget)) + return; + + disconnect(widget, SIGNAL(destroyed(QObject*)), + this, SLOT(_q_widgetDestroyed(QObject*))); + d->createdWidgets.removeAll(widget); + deleteWidget(widget); +} + +/*! + \reimp +*/ +bool QWidgetAction::event(QEvent *event) +{ + Q_D(QWidgetAction); + if (event->type() == QEvent::ActionChanged) { + if (d->defaultWidget) + d->defaultWidget->setEnabled(isEnabled()); + for (int i = 0; i < d->createdWidgets.count(); ++i) + d->createdWidgets.at(i)->setEnabled(isEnabled()); + } + return QAction::event(event); +} + +/*! + \reimp + */ +bool QWidgetAction::eventFilter(QObject *obj, QEvent *event) +{ + return QAction::eventFilter(obj,event); +} + +/*! + This function is called whenever the action is added to a container widget + that supports custom widgets. If you don't want a custom widget to be + used as representation of the action in the specified \a parent widget then + 0 should be returned. + + \sa deleteWidget() +*/ +QWidget *QWidgetAction::createWidget(QWidget *parent) +{ + Q_UNUSED(parent) + return 0; +} + +/*! + This function is called whenever the action is removed from a + container widget that displays the action using a custom \a + widget previously created using createWidget(). The default + implementation hides the \a widget and schedules it for deletion + using QObject::deleteLater(). + + \sa createWidget() +*/ +void QWidgetAction::deleteWidget(QWidget *widget) +{ + widget->hide(); + widget->deleteLater(); +} + +/*! + Returns the list of widgets that have been using createWidget() and + are currently in use by widgets the action has been added to. +*/ +QList<QWidget *> QWidgetAction::createdWidgets() const +{ + Q_D(const QWidgetAction); + return d->createdWidgets; +} + +QT_END_NAMESPACE + +#include "moc_qwidgetaction.cpp" + +#endif // QT_NO_ACTION diff --git a/src/gui/kernel/qwidgetaction.h b/src/gui/kernel/qwidgetaction.h new file mode 100644 index 0000000000..a32b5d60dd --- /dev/null +++ b/src/gui/kernel/qwidgetaction.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGETACTION_H +#define QWIDGETACTION_H + +#include <QtGui/qaction.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_ACTION + +class QWidgetActionPrivate; + +class Q_GUI_EXPORT QWidgetAction : public QAction +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QWidgetAction) + +public: + explicit QWidgetAction(QObject *parent); + virtual ~QWidgetAction(); + + void setDefaultWidget(QWidget *w); + QWidget *defaultWidget() const; + + QWidget *requestWidget(QWidget *parent); + void releaseWidget(QWidget *widget); + +protected: + virtual bool event(QEvent *); + virtual bool eventFilter(QObject *, QEvent *); + virtual QWidget *createWidget(QWidget *parent); + virtual void deleteWidget(QWidget *widget); + QList<QWidget *> createdWidgets() const; + +private: + Q_DISABLE_COPY(QWidgetAction) + Q_PRIVATE_SLOT(d_func(), void _q_widgetDestroyed(QObject *)) + friend class QToolBar; +}; + +#endif // QT_NO_ACTION + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWIDGETACTION_H diff --git a/src/gui/kernel/qwidgetaction_p.h b/src/gui/kernel/qwidgetaction_p.h new file mode 100644 index 0000000000..e4f59a04d2 --- /dev/null +++ b/src/gui/kernel/qwidgetaction_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWIDGETACTION_P_H +#define QWIDGETACTION_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/qaction_p.h" + +QT_BEGIN_NAMESPACE + +class QWidgetActionPrivate : public QActionPrivate +{ + Q_DECLARE_PUBLIC(QWidgetAction) +public: + inline QWidgetActionPrivate() : defaultWidgetInUse(false), autoCreated(false) {} + QPointer<QWidget> defaultWidget; + QList<QWidget *> createdWidgets; + uint defaultWidgetInUse : 1; + uint autoCreated : 1; // created by QToolBar::addWidget and the like + + inline void _q_widgetDestroyed(QObject *o) { + createdWidgets.removeAll(static_cast<QWidget *>(o)); + } +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/kernel/qwidgetcreate_x11.cpp b/src/gui/kernel/qwidgetcreate_x11.cpp new file mode 100644 index 0000000000..16bd6abf9a --- /dev/null +++ b/src/gui/kernel/qwidgetcreate_x11.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidget.h" +#include "qt_x11_p.h" + +/* + Internal Qt functions to create X windows. We have put them in + separate functions to allow the programmer to reimplement them by + custom versions. +*/ + +QT_BEGIN_NAMESPACE + +Window qt_XCreateWindow(const QWidget *, Display *display, Window parent, + int x, int y, uint w, uint h, + int borderwidth, int depth, + uint windowclass, Visual *visual, + ulong valuemask, XSetWindowAttributes *attributes) +{ + return XCreateWindow(display, parent, x, y, w, h, borderwidth, depth, + windowclass, visual, valuemask, attributes); +} + + +Window qt_XCreateSimpleWindow(const QWidget *, Display *display, Window parent, + int x, int y, uint w, uint h, int borderwidth, + ulong border, ulong background) +{ + return XCreateSimpleWindow(display, parent, x, y, w, h, borderwidth, + border, background); +} + + +void qt_XDestroyWindow(const QWidget *, Display *display, Window window) +{ + if (window) + XDestroyWindow(display, window); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwindowdefs.h b/src/gui/kernel/qwindowdefs.h new file mode 100644 index 0000000000..ab8af5fbb2 --- /dev/null +++ b/src/gui/kernel/qwindowdefs.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWDEFS_H +#define QWINDOWDEFS_H + +#include <QtCore/qobjectdefs.h> +#include <QtCore/qnamespace.h> + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +// Class forward definitions + +class QPaintDevice; +class QWidget; +class QDialog; +class QColor; +class QPalette; +#ifdef QT3_SUPPORT +class QColorGroup; +#endif +class QCursor; +class QPoint; +class QSize; +class QRect; +class QPolygon; +class QPainter; +class QRegion; +class QFont; +class QFontMetrics; +class QFontInfo; +class QPen; +class QBrush; +class QMatrix; +class QPixmap; +class QBitmap; +class QMovie; +class QImage; +class QPicture; +class QPrinter; +class QTimer; +class QTime; +class QClipboard; +class QString; +class QByteArray; +class QApplication; + +template<typename T> class QList; +typedef QList<QWidget *> QWidgetList; + +QT_END_NAMESPACE +QT_END_HEADER + +// Window system dependent definitions + +#if defined(Q_WS_MAC) && !defined(Q_WS_QWS) + +#include <QtGui/qmacdefines_mac.h> + +#ifdef Q_WS_MAC32 +typedef int WId; +#else +typedef long WId; +#endif + +#endif // Q_WS_MAC + +#if defined(Q_WS_WIN) +#include <QtGui/qwindowdefs_win.h> +#endif // Q_WS_WIN + +#if defined(Q_WS_X11) + +typedef struct _XDisplay Display; +typedef union _XEvent XEvent; +typedef struct _XGC *GC; +typedef struct _XRegion *Region; +typedef unsigned long WId; + +#endif // Q_WS_X11 + +#if defined(Q_WS_QWS) + +typedef unsigned long WId; +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE +struct QWSEvent; +QT_END_NAMESPACE +QT_END_HEADER + +#endif // Q_WS_QWS + +#if defined(Q_WS_QPA) + +typedef unsigned long WId; + +#endif // Q_WS_QPA + +#if defined(Q_OS_SYMBIAN) +class CCoeControl; +typedef CCoeControl * WId; +#endif // Q_OS_SYMBIAN + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +template<class K, class V> class QHash; +typedef QHash<WId, QWidget *> QWidgetMapper; + +template<class V> class QSet; +typedef QSet<QWidget *> QWidgetSet; + +QT_END_NAMESPACE +QT_END_HEADER + +#if defined(QT_NEEDS_QMAIN) +#define main qMain +#endif + +// Global platform-independent types and functions + +#endif // QWINDOWDEFS_H diff --git a/src/gui/kernel/qwindowdefs_win.h b/src/gui/kernel/qwindowdefs_win.h new file mode 100644 index 0000000000..a4dd38410c --- /dev/null +++ b/src/gui/kernel/qwindowdefs_win.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWDEFS_WIN_H +#define QWINDOWDEFS_WIN_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +QT_END_NAMESPACE + +#if !defined(Q_NOWINSTRICT) +#define Q_WINSTRICT +#endif + +#if defined(Q_WINSTRICT) + +#if !defined(STRICT) +#define STRICT +#endif +#undef NO_STRICT +#define Q_DECLARE_HANDLE(name) struct name##__; typedef struct name##__ *name + +#else + +#if !defined(NO_STRICT) +#define NO_STRICT +#endif +#undef STRICT +#define Q_DECLARE_HANDLE(name) typedef HANDLE name + +#endif + +#ifndef HINSTANCE +Q_DECLARE_HANDLE(HINSTANCE); +#endif +#ifndef HDC +Q_DECLARE_HANDLE(HDC); +#endif +#ifndef HWND +Q_DECLARE_HANDLE(HWND); +#endif +#ifndef HFONT +Q_DECLARE_HANDLE(HFONT); +#endif +#ifndef HPEN +Q_DECLARE_HANDLE(HPEN); +#endif +#ifndef HBRUSH +Q_DECLARE_HANDLE(HBRUSH); +#endif +#ifndef HBITMAP +Q_DECLARE_HANDLE(HBITMAP); +#endif +#ifndef HICON +Q_DECLARE_HANDLE(HICON); +#endif +#ifndef HCURSOR +typedef HICON HCURSOR; +#endif +#ifndef HPALETTE +Q_DECLARE_HANDLE(HPALETTE); +#endif +#ifndef HRGN +Q_DECLARE_HANDLE(HRGN); +#endif +#ifndef HMONITOR +Q_DECLARE_HANDLE(HMONITOR); +#endif +#ifndef HRESULT +typedef long HRESULT; +#endif + +typedef struct tagMSG MSG; +typedef HWND WId; + + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT HINSTANCE qWinAppInst(); +Q_CORE_EXPORT HINSTANCE qWinAppPrevInst(); +Q_CORE_EXPORT int qWinAppCmdShow(); +Q_GUI_EXPORT HDC qt_win_display_dc(); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWINDOWDEFS_WIN_H diff --git a/src/gui/kernel/qwindowsysteminterface_qpa.cpp b/src/gui/kernel/qwindowsysteminterface_qpa.cpp new file mode 100644 index 0000000000..740bb82ccc --- /dev/null +++ b/src/gui/kernel/qwindowsysteminterface_qpa.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qwindowsysteminterface_qpa.h" +#include "qwindowsysteminterface_qpa_p.h" +#include "qapplication_p.h" +#include <QAbstractEventDispatcher> + +QT_BEGIN_NAMESPACE + + +QTime QWindowSystemInterfacePrivate::eventTime; + +//------------------------------------------------------------ +// +// Callback functions for plugins: +// + +QList<QWindowSystemInterfacePrivate::WindowSystemEvent *> QWindowSystemInterfacePrivate::windowSystemEventQueue; +QMutex QWindowSystemInterfacePrivate::queueMutex; + +extern QPointer<QWidget> qt_last_mouse_receiver; + + +void QWindowSystemInterface::handleEnterEvent(QWidget *tlw) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + return; + + QWindowSystemInterfacePrivate::EnterEvent *e = new QWindowSystemInterfacePrivate::EnterEvent(tlw); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); + } +} + +void QWindowSystemInterface::handleLeaveEvent(QWidget *tlw) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + return; + } + QWindowSystemInterfacePrivate::LeaveEvent *e = new QWindowSystemInterfacePrivate::LeaveEvent(tlw); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleWindowActivated(QWidget *tlw) +{ + QWindowSystemInterfacePrivate::ActivatedWindowEvent *e = new QWindowSystemInterfacePrivate::ActivatedWindowEvent(tlw); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleGeometryChange(QWidget *tlw, const QRect &newRect) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + return; + } + QWindowSystemInterfacePrivate::GeometryChangeEvent *e = new QWindowSystemInterfacePrivate::GeometryChangeEvent(tlw,newRect); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + + +void QWindowSystemInterface::handleCloseEvent(QWidget *tlw) +{ + if (tlw) { + QWindowSystemInterfacePrivate::CloseEvent *e = + new QWindowSystemInterfacePrivate::CloseEvent(tlw); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); + } +} + +/*! + +\a tlw == 0 means that \a ev is in global coords only + + +*/ +void QWindowSystemInterface::handleMouseEvent(QWidget *w, const QPoint & local, const QPoint & global, Qt::MouseButtons b) { + unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); + handleMouseEvent(w, time, local, global, b); +} + +void QWindowSystemInterface::handleMouseEvent(QWidget *tlw, ulong timestamp, const QPoint & local, const QPoint & global, Qt::MouseButtons b) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + tlw = 0; + } + QWindowSystemInterfacePrivate::MouseEvent * e = + new QWindowSystemInterfacePrivate::MouseEvent(tlw, timestamp, local, global, b); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleKeyEvent(QWidget *w, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString & text, bool autorep, ushort count) { + unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); + handleKeyEvent(w, time, t, k, mods, text, autorep, count); +} + +void QWindowSystemInterface::handleKeyEvent(QWidget *tlw, ulong timestamp, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString & text, bool autorep, ushort count) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + tlw = 0; + } + + QWindowSystemInterfacePrivate::KeyEvent * e = + new QWindowSystemInterfacePrivate::KeyEvent(tlw, timestamp, t, k, mods, text, autorep, count); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleExtendedKeyEvent(QWidget *w, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text, bool autorep, + ushort count) +{ + unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); + handleExtendedKeyEvent(w, time, type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, + text, autorep, count); +} + +void QWindowSystemInterface::handleExtendedKeyEvent(QWidget *tlw, ulong timestamp, QEvent::Type type, int key, + Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text, bool autorep, + ushort count) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + tlw = 0; + } + + QWindowSystemInterfacePrivate::KeyEvent * e = + new QWindowSystemInterfacePrivate::KeyEvent(tlw, timestamp, type, key, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleWheelEvent(QWidget *w, const QPoint & local, const QPoint & global, int d, Qt::Orientation o) { + unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); + handleWheelEvent(w, time, local, global, d, o); +} + +void QWindowSystemInterface::handleWheelEvent(QWidget *tlw, ulong timestamp, const QPoint & local, const QPoint & global, int d, Qt::Orientation o) +{ + if (tlw) { + QWidgetData *data = qt_qwidget_data(tlw); + if (data->in_destructor) + tlw = 0; + } + + QWindowSystemInterfacePrivate::WheelEvent *e = + new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, d, o); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +int QWindowSystemInterfacePrivate::windowSystemEventsQueued() +{ + queueMutex.lock(); + int ret = windowSystemEventQueue.count(); + queueMutex.unlock(); + return ret; +} + +QWindowSystemInterfacePrivate::WindowSystemEvent * QWindowSystemInterfacePrivate::getWindowSystemEvent() +{ + queueMutex.lock(); + QWindowSystemInterfacePrivate::WindowSystemEvent *ret; + if (windowSystemEventQueue.isEmpty()) + ret = 0; + else + ret = windowSystemEventQueue.takeFirst(); + queueMutex.unlock(); + return ret; +} + +void QWindowSystemInterfacePrivate::queueWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *ev) +{ + queueMutex.lock(); + windowSystemEventQueue.append(ev); + queueMutex.unlock(); + + QAbstractEventDispatcher *dispatcher = QApplicationPrivate::qt_qpa_core_dispatcher(); + if (dispatcher) + dispatcher->wakeUp(); +} + +void QWindowSystemInterface::handleTouchEvent(QWidget *w, QEvent::Type type, QTouchEvent::DeviceType devType, const QList<struct TouchPoint> &points) { + unsigned long time = QWindowSystemInterfacePrivate::eventTime.elapsed(); + handleTouchEvent(w, time, type, devType, points); +} + +void QWindowSystemInterface::handleTouchEvent(QWidget *tlw, ulong timestamp, QEvent::Type type, QTouchEvent::DeviceType devType, const QList<struct TouchPoint> &points) +{ + if (!points.size()) // Touch events must have at least one point + return; + + QList<QTouchEvent::TouchPoint> touchPoints; + Qt::TouchPointStates states; + QTouchEvent::TouchPoint p; + + QList<struct TouchPoint>::const_iterator point = points.constBegin(); + QList<struct TouchPoint>::const_iterator end = points.constEnd(); + while (point != end) { + p.setId(point->id); + p.setPressure(point->pressure); + states |= point->state; + Qt::TouchPointStates state = point->state; + if (point->isPrimary) { + state |= Qt::TouchPointPrimary; + } + p.setState(state); + p.setRect(point->area); + p.setScreenPos(point->area.center()); + p.setNormalizedPos(point->normalPosition); + + touchPoints.append(p); + ++point; + } + + QWindowSystemInterfacePrivate::TouchEvent *e = + new QWindowSystemInterfacePrivate::TouchEvent(tlw, timestamp, type, devType, touchPoints); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleScreenGeometryChange(int screenIndex) +{ + QWindowSystemInterfacePrivate::ScreenGeometryEvent *e = + new QWindowSystemInterfacePrivate::ScreenGeometryEvent(screenIndex); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleScreenAvailableGeometryChange(int screenIndex) +{ + QWindowSystemInterfacePrivate::ScreenAvailableGeometryEvent *e = + new QWindowSystemInterfacePrivate::ScreenAvailableGeometryEvent(screenIndex); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +void QWindowSystemInterface::handleScreenCountChange(int count) +{ + QWindowSystemInterfacePrivate::ScreenCountEvent *e = + new QWindowSystemInterfacePrivate::ScreenCountEvent(count); + QWindowSystemInterfacePrivate::queueWindowSystemEvent(e); +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qwindowsysteminterface_qpa.h b/src/gui/kernel/qwindowsysteminterface_qpa.h new file mode 100644 index 0000000000..a882fc15b6 --- /dev/null +++ b/src/gui/kernel/qwindowsysteminterface_qpa.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QWINDOWSYSTEMINTERFACE_H +#define QWINDOWSYSTEMINTERFACE_H + +#include <QtCore/QTime> +#include <QtGui/qwindowdefs.h> +#include <QtCore/QEvent> +#include <QtGui/QWidget> +#include <QtCore/QWeakPointer> +#include <QtCore/QMutex> +#include <QtGui/QTouchEvent> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QWindowSystemInterface +{ +public: + static void handleMouseEvent(QWidget *w, const QPoint & local, const QPoint & global, Qt::MouseButtons b); + static void handleMouseEvent(QWidget *w, ulong timestamp, const QPoint & local, const QPoint & global, Qt::MouseButtons b); + + static void handleKeyEvent(QWidget *w, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString & text = QString(), bool autorep = false, ushort count = 1); + static void handleKeyEvent(QWidget *w, ulong timestamp, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString & text = QString(), bool autorep = false, ushort count = 1); + + static void handleExtendedKeyEvent(QWidget *w, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text = QString(), bool autorep = false, + ushort count = 1); + static void handleExtendedKeyEvent(QWidget *w, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, + const QString& text = QString(), bool autorep = false, + ushort count = 1); + + static void handleWheelEvent(QWidget *w, const QPoint & local, const QPoint & global, int d, Qt::Orientation o); + static void handleWheelEvent(QWidget *w, ulong timestamp, const QPoint & local, const QPoint & global, int d, Qt::Orientation o); + + struct TouchPoint { + int id; // for application use + bool isPrimary; // for application use + QPointF normalPosition; // touch device coordinates, (0 to 1, 0 to 1) + QRectF area; // the touched area, centered at position in screen coordinates + qreal pressure; // 0 to 1 + Qt::TouchPointState state; //Qt::TouchPoint{Pressed|Moved|Stationary|Released} + }; + + static void handleTouchEvent(QWidget *w, QEvent::Type type, QTouchEvent::DeviceType devType, const QList<struct TouchPoint> &points); + static void handleTouchEvent(QWidget *w, ulong timestamp, QEvent::Type type, QTouchEvent::DeviceType devType, const QList<struct TouchPoint> &points); + + static void handleGeometryChange(QWidget *w, const QRect &newRect); + static void handleCloseEvent(QWidget *w); + static void handleEnterEvent(QWidget *w); + static void handleLeaveEvent(QWidget *w); + static void handleWindowActivated(QWidget *w); + + // Changes to the screen + static void handleScreenGeometryChange(int screenIndex); + static void handleScreenAvailableGeometryChange(int screenIndex); + static void handleScreenCountChange(int count); +}; + +QT_END_NAMESPACE +QT_END_HEADER +#endif // QWINDOWSYSTEMINTERFACE_H diff --git a/src/gui/kernel/qwindowsysteminterface_qpa_p.h b/src/gui/kernel/qwindowsysteminterface_qpa_p.h new file mode 100644 index 0000000000..6be86ad9a5 --- /dev/null +++ b/src/gui/kernel/qwindowsysteminterface_qpa_p.h @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QWINDOWSYSTEMINTERFACE_QPA_P_H +#define QWINDOWSYSTEMINTERFACE_QPA_P_H + +#include "qwindowsysteminterface_qpa.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QWindowSystemInterfacePrivate { +public: + enum EventType { + Close, + GeometryChange, + Enter, + Leave, + ActivatedWindow, + Mouse, + Wheel, + Key, + Touch, + ScreenGeometry, + ScreenAvailableGeometry, + ScreenCountChange + }; + + class WindowSystemEvent { + public: + WindowSystemEvent(EventType t) + : type(t) { } + EventType type; + }; + + class CloseEvent : public WindowSystemEvent { + public: + CloseEvent(QWidget *tlw) + : WindowSystemEvent(Close), topLevel(tlw) { } + QWeakPointer<QWidget> topLevel; + }; + + class GeometryChangeEvent : public WindowSystemEvent { + public: + GeometryChangeEvent(QWidget *tlw, const QRect &newGeometry) + : WindowSystemEvent(GeometryChange), tlw(tlw), newGeometry(newGeometry) + { } + QWeakPointer<QWidget> tlw; + QRect newGeometry; + }; + + class EnterEvent : public WindowSystemEvent { + public: + EnterEvent(QWidget *enter) + : WindowSystemEvent(Enter), enter(enter) + { } + QWeakPointer<QWidget> enter; + }; + + class LeaveEvent : public WindowSystemEvent { + public: + LeaveEvent(QWidget *leave) + : WindowSystemEvent(Leave), leave(leave) + { } + QWeakPointer<QWidget> leave; + }; + + class ActivatedWindowEvent : public WindowSystemEvent { + public: + ActivatedWindowEvent(QWidget *activatedWindow) + : WindowSystemEvent(ActivatedWindow), activated(activatedWindow) + { } + QWeakPointer<QWidget> activated; + }; + + class UserEvent : public WindowSystemEvent { + public: + UserEvent(QWidget * w, ulong time, EventType t) + : WindowSystemEvent(t), widget(w), timestamp(time) { } + QWeakPointer<QWidget> widget; + unsigned long timestamp; + }; + + class MouseEvent : public UserEvent { + public: + MouseEvent(QWidget * w, ulong time, const QPoint & local, const QPoint & global, Qt::MouseButtons b) + : UserEvent(w, time, Mouse), localPos(local), globalPos(global), buttons(b) { } + QPoint localPos; + QPoint globalPos; + Qt::MouseButtons buttons; + }; + + class WheelEvent : public UserEvent { + public: + WheelEvent(QWidget *w, ulong time, const QPoint & local, const QPoint & global, int d, Qt::Orientation o) + : UserEvent(w, time, Wheel), delta(d), localPos(local), globalPos(global), orient(o) { } + int delta; + QPoint localPos; + QPoint globalPos; + Qt::Orientation orient; + }; + + class KeyEvent : public UserEvent { + public: + KeyEvent(QWidget *w, ulong time, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString & text = QString(), bool autorep = false, ushort count = 1) + :UserEvent(w, time, Key), key(k), unicode(text), repeat(autorep), + repeatCount(count), modifiers(mods), keyType(t), + nativeScanCode(0), nativeVirtualKey(0), nativeModifiers(0) { } + KeyEvent(QWidget *w, ulong time, QEvent::Type t, int k, Qt::KeyboardModifiers mods, + quint32 nativeSC, quint32 nativeVK, quint32 nativeMods, + const QString & text = QString(), bool autorep = false, ushort count = 1) + :UserEvent(w, time, Key), key(k), unicode(text), repeat(autorep), + repeatCount(count), modifiers(mods), keyType(t), + nativeScanCode(nativeSC), nativeVirtualKey(nativeVK), nativeModifiers(nativeMods) { } + int key; + QString unicode; + bool repeat; + ushort repeatCount; + Qt::KeyboardModifiers modifiers; + QEvent::Type keyType; + quint32 nativeScanCode; + quint32 nativeVirtualKey; + quint32 nativeModifiers; + }; + + class TouchEvent : public UserEvent { + public: + TouchEvent(QWidget *w, ulong time, QEvent::Type t, QTouchEvent::DeviceType d, const QList<QTouchEvent::TouchPoint> &p) + :UserEvent(w, time, Touch), devType(d), points(p), touchType(t) { } + QTouchEvent::DeviceType devType; + QList<QTouchEvent::TouchPoint> points; + QEvent::Type touchType; + + }; + + class ScreenCountEvent : public WindowSystemEvent { + public: + ScreenCountEvent (int count) + : WindowSystemEvent(ScreenCountChange) , count(count) { } + int count; + }; + + class ScreenGeometryEvent : public WindowSystemEvent { + public: + ScreenGeometryEvent(int index) + : WindowSystemEvent(ScreenGeometry), index(index) { } + int index; + }; + + class ScreenAvailableGeometryEvent : public WindowSystemEvent { + public: + ScreenAvailableGeometryEvent(int index) + : WindowSystemEvent(ScreenAvailableGeometry), index(index) { } + int index; + }; + + static QList<WindowSystemEvent *> windowSystemEventQueue; + static QMutex queueMutex; + + static int windowSystemEventsQueued(); + static WindowSystemEvent * getWindowSystemEvent(); + static void queueWindowSystemEvent(WindowSystemEvent *ev); + + static QTime eventTime; +}; + +QT_END_HEADER +QT_END_NAMESPACE + +#endif // QWINDOWSYSTEMINTERFACE_QPA_P_H diff --git a/src/gui/kernel/qwinnativepangesturerecognizer_win.cpp b/src/gui/kernel/qwinnativepangesturerecognizer_win.cpp new file mode 100644 index 0000000000..0d13bafc0c --- /dev/null +++ b/src/gui/kernel/qwinnativepangesturerecognizer_win.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qwinnativepangesturerecognizer_win_p.h" + +#include "qevent.h" +#include "qgraphicsitem.h" +#include "qgesture.h" + +#include "private/qgesture_p.h" +#include "private/qevent_p.h" +#include "private/qapplication_p.h" +#include "private/qwidget_p.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_NATIVE_GESTURES) + +QWinNativePanGestureRecognizer::QWinNativePanGestureRecognizer() +{ +} + +QGesture *QWinNativePanGestureRecognizer::create(QObject *target) +{ + if (!target) + return new QPanGesture; // a special case + if (!target->isWidgetType()) + return 0; + if (qobject_cast<QGraphicsObject *>(target)) + return 0; + + QWidget *q = static_cast<QWidget *>(target); + QWidgetPrivate *d = q->d_func(); + d->nativeGesturePanEnabled = true; + d->winSetupGestures(); + + return new QPanGesture; +} + +QGestureRecognizer::Result QWinNativePanGestureRecognizer::recognize(QGesture *state, + QObject *, + QEvent *event) +{ + QPanGesture *q = static_cast<QPanGesture*>(state); + QPanGesturePrivate *d = q->d_func(); + + QGestureRecognizer::Result result = QGestureRecognizer::Ignore; + if (event->type() == QEvent::NativeGesture) { + QNativeGestureEvent *ev = static_cast<QNativeGestureEvent*>(event); + switch(ev->gestureType) { + case QNativeGestureEvent::GestureBegin: + break; + case QNativeGestureEvent::Pan: + result = QGestureRecognizer::TriggerGesture; + event->accept(); + break; + case QNativeGestureEvent::GestureEnd: + if (q->state() == Qt::NoGesture) + return QGestureRecognizer::Ignore; // some other gesture has ended + result = QGestureRecognizer::FinishGesture; + break; + default: + return QGestureRecognizer::Ignore; + } + if (q->state() == Qt::NoGesture) { + d->lastOffset = d->offset = QPointF(); + d->startPosition = ev->position; + } else { + d->lastOffset = d->offset; + d->offset = QPointF(ev->position.x() - d->startPosition.x(), + ev->position.y() - d->startPosition.y()); + } + } + return result; +} + +void QWinNativePanGestureRecognizer::reset(QGesture *state) +{ + QPanGesture *pan = static_cast<QPanGesture*>(state); + QPanGesturePrivate *d = pan->d_func(); + + d->lastOffset = d->offset = QPointF(); + d->startPosition = QPoint(); + d->acceleration = 0; + + QGestureRecognizer::reset(state); +} + +#endif // QT_NO_NATIVE_GESTURES + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/kernel/qwinnativepangesturerecognizer_win_p.h b/src/gui/kernel/qwinnativepangesturerecognizer_win_p.h new file mode 100644 index 0000000000..6d23e41ce3 --- /dev/null +++ b/src/gui/kernel/qwinnativepangesturerecognizer_win_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINNATIVEPANGESTURERECOGNIZER_WIN_P_H +#define QWINNATIVEPANGESTURERECOGNIZER_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QGestureRecognizer> + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_NATIVE_GESTURES) + +class QWinNativePanGestureRecognizer : public QGestureRecognizer +{ +public: + QWinNativePanGestureRecognizer(); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); +}; + +#endif // QT_NO_NATIVE_GESTURES + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QWINNATIVEPANGESTURERECOGNIZER_WIN_P_H diff --git a/src/gui/kernel/qx11embed_x11.cpp b/src/gui/kernel/qx11embed_x11.cpp new file mode 100644 index 0000000000..49a819469e --- /dev/null +++ b/src/gui/kernel/qx11embed_x11.cpp @@ -0,0 +1,1808 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" +#include "qx11embed_x11.h" +#include <qapplication.h> +#include <qevent.h> +#include <qpainter.h> +#include <qlayout.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qelapsedtimer.h> +#include <qpointer.h> +#include <qdebug.h> +#include <qx11info_x11.h> +#include <private/qt_x11_p.h> +#include <private/qwidget_p.h> + +#define XK_MISCELLANY +#define XK_LATIN1 +#define None 0 +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/keysymdef.h> +#include <X11/X.h> + +#ifndef XK_ISO_Left_Tab +#define XK_ISO_Left_Tab 0xFE20 +#endif + +//#define QX11EMBED_DEBUG +#ifdef QX11EMBED_DEBUG +#include <qdebug.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QX11EmbedWidget + \ingroup advanced + + \brief The QX11EmbedWidget class provides an XEmbed client widget. + + XEmbed is an X11 protocol that supports the embedding of a widget + from one application into another application. + + An XEmbed \e{client widget} is a window that is embedded into a + \e container. A container is the graphical location that embeds + (or \e swallows) an external application. + + QX11EmbedWidget is a widget used for writing XEmbed applets or + plugins. When it has been embedded and the container receives tab + focus, focus is passed on to the widget. When the widget reaches + the end of its focus chain, focus is passed back to the + container. Window activation, accelerator support, modality and + drag and drop (XDND) are also handled. + + The widget and container can both initiate the embedding. If the + widget is the initiator, the X11 window ID of the container that + it wants to embed itself into must be passed to embedInto(). + + If the container initiates the embedding, the window ID of the + embedded widget must be known. The container calls embed(), + passing the window ID. + + This example shows an application that embeds a QX11EmbedWidget + subclass into the window whose ID is passed as a command-line + argument: + + \snippet doc/src/snippets/qx11embedwidget/main.cpp 0 + + The problem of obtaining the window IDs is often solved by the + container invoking the application that provides the widget as a + separate process (as a panel invokes a docked applet), passing + its window ID to the new process as a command-line argument. The + new process can then call embedInto() with the container's window + ID, as shown in the example code above. Similarly, the new + process can report its window ID to the container through IPC, in + which case the container can embed the widget. + + When the widget has been embedded, it emits the signal + embedded(). If it is closed by the container, the widget emits + containerClosed(). If an error occurs when embedding, error() is + emitted. + + There are XEmbed widgets available for KDE and GTK+. The GTK+ + equivalent of QX11EmbedWidget is GtkPlug. The corresponding KDE 3 + widget is called QXEmbed. + + \sa QX11EmbedContainer, {XEmbed Specification} +*/ + +/*! + \class QX11EmbedContainer + \ingroup advanced + + \brief The QX11EmbedContainer class provides an XEmbed container + widget. + + XEmbed is an X11 protocol that supports the embedding of a widget + from one application into another application. + + An XEmbed \e container is the graphical location that embeds an + external \e {client widget}. A client widget is a window that is + embedded into a container. + + When a widget has been embedded and the container receives tab + focus, focus is passed on to the widget. When the widget reaches + the end of its focus chain, focus is passed back to the + container. Window activation, accelerator support, modality and + drag and drop (XDND) are also handled. + + QX11EmbedContainer is commonly used for writing panels or + toolbars that hold applets, or for \e swallowing X11 + applications. When writing a panel application, one container + widget is created on the toolbar, and it can then either swallow + another widget using embed(), or allow an XEmbed widget to be + embedded into itself. The container's X11 window ID, which is + retrieved with winId(), must then be known to the client widget. + After embedding, the client's window ID can be retrieved with + clientWinId(). + + In the following example, a container widget is created as the + main widget. It then invokes an application called "playmovie", + passing its window ID as a command line argument. The "playmovie" + program is an XEmbed client widget. The widget embeds itself into + the container using the container's window ID. + + \snippet doc/src/snippets/qx11embedcontainer/main.cpp 0 + + When the client widget is embedded, the container emits the + signal clientIsEmbedded(). The signal clientClosed() is emitted + when a widget is closed. + + It is possible for QX11EmbedContainer to embed XEmbed widgets + from toolkits other than Qt, such as GTK+. Arbitrary (non-XEmbed) + X11 widgets can also be embedded, but the XEmbed-specific + features such as window activation and focus handling are then + lost. + + The GTK+ equivalent of QX11EmbedContainer is GtkSocket. The + corresponding KDE 3 widget is called QXEmbed. + + \sa QX11EmbedWidget, {XEmbed Specification} +*/ + +/*! \fn void QX11EmbedWidget::embedded() + + This signal is emitted by the widget that has been embedded by an + XEmbed container. +*/ + +/*! \fn void QX11EmbedWidget::containerClosed() + + This signal is emitted by the client widget when the container + closes the widget. This can happen if the container itself + closes, or if the widget is rejected. + + The container can reject a widget for any reason, but the most + common cause of a rejection is when an attempt is made to embed a + widget into a container that already has an embedded widget. +*/ + +/*! \fn void QX11EmbedContainer::clientIsEmbedded() + + This signal is emitted by the container when a client widget has + been embedded. +*/ + +/*! \fn void QX11EmbedContainer::clientClosed() + + This signal is emitted by the container when the client widget + closes. +*/ + +/*! + \fn void QX11EmbedWidget::error(QX11EmbedWidget::Error error) + + This signal is emitted if an error occurred as a result of + embedding into or communicating with a container. The specified + \a error describes the problem that occurred. + + \sa QX11EmbedWidget::Error +*/ + +/*! + \fn QX11EmbedContainer::Error QX11EmbedContainer::error() const + + Returns the last error that occurred. +*/ + +/*! \fn void QX11EmbedContainer::error(QX11EmbedContainer::Error error) + + This signal is emitted if an error occurred when embedding or + communicating with a client. The specified \a error describes the + problem that occurred. + + \sa QX11EmbedContainer::Error +*/ + +/*! + \enum QX11EmbedWidget::Error + + \value Unknown An unrecognized error occurred. + + \value InvalidWindowID The X11 window ID of the container was + invalid. This error is usually triggered by passing an invalid + window ID to embedInto(). + + \omitvalue Internal +*/ + +/*! + \enum QX11EmbedContainer::Error + + \value Unknown An unrecognized error occurred. + + \value InvalidWindowID The X11 window ID of the container was + invalid. This error is usually triggered by passing an invalid + window ID to embed(). + + \omitvalue Internal +*/ + +const int XButtonPress = ButtonPress; +const int XButtonRelease = ButtonRelease; +#undef ButtonPress +#undef ButtonRelease + +// This is a hack to move topData() out from QWidgetPrivate to public. We +// need to to inspect window()'s embedded state. +class QHackWidget : public QWidget +{ + Q_DECLARE_PRIVATE(QWidget) +public: + QTLWExtra* topData() { return d_func()->topData(); } +}; + +static unsigned int XEMBED_VERSION = 0; + +enum QX11EmbedMessageType { + XEMBED_EMBEDDED_NOTIFY = 0, + XEMBED_WINDOW_ACTIVATE = 1, + XEMBED_WINDOW_DEACTIVATE = 2, + XEMBED_REQUEST_FOCUS = 3, + XEMBED_FOCUS_IN = 4, + XEMBED_FOCUS_OUT = 5, + XEMBED_FOCUS_NEXT = 6, + XEMBED_FOCUS_PREV = 7, + XEMBED_MODALITY_ON = 10, + XEMBED_MODALITY_OFF = 11, + XEMBED_REGISTER_ACCELERATOR = 12, + XEMBED_UNREGISTER_ACCELERATOR = 13, + XEMBED_ACTIVATE_ACCELERATOR = 14 +}; + +enum QX11EmbedFocusInDetail { + XEMBED_FOCUS_CURRENT = 0, + XEMBED_FOCUS_FIRST = 1, + XEMBED_FOCUS_LAST = 2 +}; + +enum QX11EmbedFocusInFlags { + XEMBED_FOCUS_OTHER = (0 << 0), + XEMBED_FOCUS_WRAPAROUND = (1 << 0) +}; + +enum QX11EmbedInfoFlags { + XEMBED_MAPPED = (1 << 0) +}; + +enum QX11EmbedAccelModifiers { + XEMBED_MODIFIER_SHIFT = (1 << 0), + XEMBED_MODIFIER_CONTROL = (1 << 1), + XEMBED_MODIFIER_ALT = (1 << 2), + XEMBED_MODIFIER_SUPER = (1 << 3), + XEMBED_MODIFIER_HYPER = (1 << 4) +}; + +enum QX11EmbedAccelFlags { + XEMBED_ACCELERATOR_OVERLOADED = (1 << 0) +}; + +// Silence the default X11 error handler. +static int x11ErrorHandler(Display *, XErrorEvent *) +{ + return 0; +} + +// Returns the X11 timestamp. Maintained mainly by qapplication +// internals, but also updated by the XEmbed widgets. +static Time x11Time() +{ + return qt_x11Data->time; +} + +// Gives the version and flags of the supported XEmbed protocol. +static unsigned int XEmbedVersion() +{ + return 0; +} + +// Sends an XEmbed message. +static void sendXEmbedMessage(WId window, Display *display, long message, + long detail = 0, long data1 = 0, long data2 = 0) +{ + XClientMessageEvent c; + memset(&c, 0, sizeof(c)); + c.type = ClientMessage; + c.message_type = ATOM(_XEMBED); + c.format = 32; + c.display = display; + c.window = window; + + c.data.l[0] = x11Time(); + c.data.l[1] = message; + c.data.l[2] = detail; + c.data.l[3] = data1; + c.data.l[4] = data2; + + XSendEvent(display, window, false, NoEventMask, (XEvent *) &c); +} + +// From qapplication_x11.cpp +static XKeyEvent lastKeyEvent; + +static QCoreApplication::EventFilter oldX11EventFilter; + +// The purpose of this global x11 filter is for one to capture the key +// events in their original state, but most importantly this is the +// only way to get the WM_TAKE_FOCUS message from WM_PROTOCOLS. +static bool x11EventFilter(void *message, long *result) +{ + XEvent *event = reinterpret_cast<XEvent *>(message); + if (event->type == XKeyPress || event->type == XKeyRelease) + lastKeyEvent = event->xkey; + + if (oldX11EventFilter && oldX11EventFilter != &x11EventFilter) + return oldX11EventFilter(message, result); + else + return false; +} + +// +struct functorData +{ + Window id, rootWindow; + bool clearedWmState; + bool reparentedToRoot; +}; + +static Bool functor(Display *display, XEvent *event, XPointer arg) +{ + functorData *data = (functorData *) arg; + + if (!data->reparentedToRoot && event->type == ReparentNotify + && event->xreparent.window == data->id + && event->xreparent.parent == data->rootWindow) { + data->reparentedToRoot = true; + return true; + } + + if (!data->clearedWmState + && event->type == PropertyNotify + && event->xproperty.window == data->id + && event->xproperty.atom == ATOM(WM_STATE)) { + if (event->xproperty.state == PropertyDelete) { + data->clearedWmState = true; + return true; + } + + Atom ret; + int format, status; + unsigned char *retval; + unsigned long nitems, after; + status = XGetWindowProperty(display, data->id, ATOM(WM_STATE), 0, 2, False, ATOM(WM_STATE), + &ret, &format, &nitems, &after, &retval ); + if (status == Success && ret == ATOM(WM_STATE) && format == 32 && nitems > 0) { + long state = *(long *)retval; + XFree(retval); + if (state == WithdrawnState) { + data->clearedWmState = true; + return true; + } + } + } + + return false; +} + +class QX11EmbedWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QX11EmbedWidget) +public: + inline QX11EmbedWidgetPrivate() + { + lastError = QX11EmbedWidget::Unknown; + container = 0; + } + + void setEmbedded(); + + void emitError(QX11EmbedWidget::Error error) { + Q_Q(QX11EmbedWidget); + + lastError = error; + emit q->error(error); + } + + enum FocusWidgets { + FirstFocusWidget, + LastFocusWidget + }; + + int focusOriginator; + QWidget *getFocusWidget(FocusWidgets fw); + void checkActivateWindow(QObject *o); + QX11EmbedWidget *xEmbedWidget(QObject *o) const; + void clearFocus(); + + WId container; + QPointer<QWidget> currentFocus; + + QX11EmbedWidget::Error lastError; + +}; + +/*! + Constructs a QX11EmbedWidget object with the given \a parent. +*/ +QX11EmbedWidget::QX11EmbedWidget(QWidget *parent) + : QWidget(*new QX11EmbedWidgetPrivate, parent, 0) +{ + XSetErrorHandler(x11ErrorHandler); + + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_DontCreateNativeAncestors); + createWinId(); + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask | ButtonPressMask + | ButtonReleaseMask + | KeymapStateMask | ButtonMotionMask | PointerMotionMask + | FocusChangeMask + | ExposureMask | StructureNotifyMask + | SubstructureNotifyMask | PropertyChangeMask); + + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), internalWinId(), ATOM(_XEMBED_INFO), + ATOM(_XEMBED_INFO), 32, PropModeReplace, + (unsigned char*) data, 2); + + setFocusPolicy(Qt::StrongFocus); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QApplication::instance()->installEventFilter(this); + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::QX11EmbedWidget: constructed client" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Destructs the QX11EmbedWidget object. If the widget is embedded + when deleted, it is hidden and then detached from its container, + so that the container is free to embed a new widget. +*/ +QX11EmbedWidget::~QX11EmbedWidget() +{ + Q_D(QX11EmbedWidget); + if (d->container) { +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::~QX11EmbedWidget: unmapping" + << (void *)this << "with winId" << winId() + << "from container with winId" << d->container; +#endif + XUnmapWindow(x11Info().display(), internalWinId()); + XReparentWindow(x11Info().display(), internalWinId(), x11Info().appRootWindow(x11Info().screen()), 0, 0); + } + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::~QX11EmbedWidget: destructed client" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Returns the type of error that occurred last. This is the same error code + that is emitted by the error() signal. + + \sa Error +*/ +QX11EmbedWidget::Error QX11EmbedWidget::error() const +{ + return d_func()->lastError; +} + +/*! + When this function is called, the widget embeds itself into the + container whose window ID is \a id. + + If \a id is \e not the window ID of a container this function will + behave unpredictably. +*/ +void QX11EmbedWidget::embedInto(WId id) +{ + Q_D(QX11EmbedWidget); +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::embedInto: embedding client" + << (void *)this << "with winId" << winId() << "into container" + << id; +#endif + + d->container = id; + switch (XReparentWindow(x11Info().display(), internalWinId(), d->container, 0, 0)) { + case BadWindow: + d->emitError(InvalidWindowID); + break; + case BadMatch: + d->emitError(Internal); + break; + case Success: + default: + break; + } + QTLWExtra* x = d->extra ? d->extra->topextra : 0; + if (x) + x->frameStrut.setCoords(0, 0, 0, 0); + d->data.fstrut_dirty = false; +} + +/*! \internal + + Gets the first or last child widget that can get focus. +*/ +QWidget *QX11EmbedWidgetPrivate::getFocusWidget(FocusWidgets fw) +{ + Q_Q(QX11EmbedWidget); + QWidget *tlw = q; + QWidget *w = tlw->nextInFocusChain(); + + QWidget *last = tlw; + + extern bool qt_tab_all_widgets; + uint focus_flag = qt_tab_all_widgets ? Qt::TabFocus : Qt::StrongFocus; + + while (w != tlw) + { + if (((w->focusPolicy() & focus_flag) == focus_flag) + && w->isVisibleTo(q) && w->isEnabled()) + { + last = w; + if (fw == FirstFocusWidget) + break; + } + w = w->nextInFocusChain(); + } + + return last; +} + +/*! \internal + + Returns the xembed widget that the widget is a child of +*/ +QX11EmbedWidget *QX11EmbedWidgetPrivate::xEmbedWidget(QObject *o) const +{ + QX11EmbedWidget *xec = 0; + + // Check the widget itself, then its parents, and find the first + // QX11EmbedWidget. + do { + if ((xec = qobject_cast<QX11EmbedWidget *>(o))) + return xec; + } while ((o = o->parent())); + return 0; +} + +/*! \internal + + Checks the active window. +*/ +void QX11EmbedWidgetPrivate::checkActivateWindow(QObject *o) +{ + Q_Q(QX11EmbedWidget); + QX11EmbedWidget *xec = xEmbedWidget(o); + + // check if we are in the right xembed client + if (q != xec) + return; + + QWidget *w = qobject_cast<QWidget *>(o); + + // if it is no active window, then don't do the change + if (!(w && qApp->activeWindow())) + return; + + // if it already is the active window, don't do anything + if (w->window() != qApp->activeWindow()) + { + qApp->setActiveWindow(w->window()); + currentFocus = w; + + sendXEmbedMessage(xec->containerWinId(), q->x11Info().display(), XEMBED_REQUEST_FOCUS); + } +} + +/*! \internal + + Clears the focus +*/ +void QX11EmbedWidgetPrivate::clearFocus() +{ + Q_Q(QX11EmbedWidget); + // Setting focus on the client itself removes Qt's + // logical focus rectangle. We can't just do a + // clearFocus here, because when we send the synthetic + // FocusIn to ourselves on activation, Qt will set + // focus on focusWidget() again. This way, we "hide" + // focus rather than clearing it. + + if (!q->window()->hasFocus()) + q->window()->setFocus(Qt::OtherFocusReason); + + currentFocus = 0; +} + +/*! \internal + + Sets the embedded flag on the client. +*/ +void QX11EmbedWidgetPrivate::setEmbedded() +{ + Q_Q(QX11EmbedWidget); + ((QHackWidget *)q->window())->topData()->embedded = 1; +} + +/*! \internal + + Handles WindowActivate and FocusIn events for the client. +*/ +bool QX11EmbedWidget::eventFilter(QObject *o, QEvent *event) +{ + Q_D(QX11EmbedWidget); + switch (event->type()) { + case QEvent::FocusIn: + switch (((QFocusEvent *)event)->reason()) { + case Qt::MouseFocusReason: + // If the user clicks into one of the client widget's + // children and we didn't have focus already, we request + // focus from our container. + if (d->xEmbedWidget(o) == this) { + if (d->currentFocus.isNull()) + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_REQUEST_FOCUS); + + d->currentFocus = qobject_cast<QWidget *>(o); + } + break; + case Qt::TabFocusReason: + // If the xembed client receives a focus event because of + // a Tab, then we are at the end of our focus chain and we + // ask the container to move to its next focus widget. + if (o == this) { + d->clearFocus(); + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_FOCUS_NEXT); + return true; + } else { + // We're listening on events from qApp, so in order + // for us to know who to set focus on if we receive an + // activation event, we note the widget that got the + // focusin last. + if (d->xEmbedWidget(o) == this) + d->currentFocus = qobject_cast<QWidget *>(o); + } + break; + case Qt::BacktabFocusReason: + // If the window receives a focus event because of + // a Backtab, then we are at the start of our focus chain + // and we ask the container to move to its previous focus + // widget. + if (o == this) { + // See comment for Tab. + // If we receive a XEMBED_FOCUS_IN + // XEMBED_FOCUS_CURRENT, we will set focus in + // currentFocus. To avoid that in this case, we reset + // currentFocus. + d->clearFocus(); + sendXEmbedMessage(d->container, x11Info().display(), XEMBED_FOCUS_PREV); + return true; + } else { + if (d->xEmbedWidget(o) == this) + d->currentFocus = qobject_cast<QWidget *>(o); + } + break; + case Qt::ActiveWindowFocusReason: + if (isActiveWindow()) { + if (!d->currentFocus.isNull()) { + if (!d->currentFocus->hasFocus()) + d->currentFocus->setFocus(Qt::OtherFocusReason); + } else { + d->clearFocus(); + return true; + } + } + + break; + case Qt::PopupFocusReason: + case Qt::ShortcutFocusReason: + case Qt::OtherFocusReason: + // If focus is received to any child widget because of any + // other reason, remember the widget so that we can give + // it focus again if we're activated. + if (d->xEmbedWidget(o) == this) { + d->currentFocus = qobject_cast<QWidget *>(o); + } + break; + default: + break; + } + break; + case QEvent::MouseButtonPress: + // If we get a mouse button press event inside a embedded widget + // make sure this is the active window in qapp. + d->checkActivateWindow(o); + break; + default: + break; + } + + return QWidget::eventFilter(o, event); +} + +/*! \internal + + Handles some notification events and client messages. Client side + XEmbed message receiving is also handled here. +*/ +bool QX11EmbedWidget::x11Event(XEvent *event) +{ + Q_D(QX11EmbedWidget); + switch (event->type) { + case DestroyNotify: +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received a DestroyNotify"; +#endif + // If the container window is destroyed, we signal this to the user. + d->container = 0; + emit containerClosed(); + break; + case ReparentNotify: +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received a ReparentNotify to" + << ((event->xreparent.parent == x11Info().appRootWindow(x11Info().screen())) + ? QString::fromLatin1("root") : QString::number(event->xreparent.parent)); +#endif + // If the container shuts down, we will be reparented to the + // root window. We must also consider the case that we may be + // reparented from one container to another. + if (event->xreparent.parent == x11Info().appRootWindow(x11Info().screen())) { + if (((QHackWidget *)this)->topData()->embedded) { + d->container = 0; + emit containerClosed(); + } + return true; + } else { + d->container = event->xreparent.parent; + } + break; + case UnmapNotify: + // Mapping and unmapping are handled by changes to the + // _XEMBED_INFO property. Any default map/unmap requests are + // ignored. + return true; + case PropertyNotify: + // The container sends us map/unmap messages through the + // _XEMBED_INFO property. We adhere to the XEMBED_MAPPED bit in + // data2. + if (event->xproperty.atom == ATOM(_XEMBED_INFO)) { + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return; + unsigned long bytes_after_return; + unsigned char *prop_return = 0; + if (XGetWindowProperty(x11Info().display(), internalWinId(), ATOM(_XEMBED_INFO), 0, 2, + false, ATOM(_XEMBED_INFO), &actual_type_return, + &actual_format_return, &nitems_return, + &bytes_after_return, &prop_return) == Success) { + if (nitems_return > 1) { + if (((long * )prop_return)[1] & XEMBED_MAPPED) { + XMapWindow(x11Info().display(), internalWinId()); + } else { + XUnmapWindow(x11Info().display(), internalWinId()); + } + } + if (prop_return) + XFree(prop_return); + } + } + + break; + case ClientMessage: + // XEMBED messages have message_type _XEMBED + if (event->xclient.message_type == ATOM(_XEMBED)) { + // Discard XEMBED messages not to ourselves. (### dead code?) + if (event->xclient.window != internalWinId()) + break; + + // Update qt_x_time if necessary + Time msgtime = (Time) event->xclient.data.l[0]; + if (msgtime > X11->time) + X11->time = msgtime; + + switch (event->xclient.data.l[1]) { + case XEMBED_WINDOW_ACTIVATE: { + // When we receive an XEMBED_WINDOW_ACTIVATE, we simply send + // ourselves a WindowActivate event. Real activation happens + // when receive XEMBED_FOCUS_IN. + if (!isActiveWindow()) { + QEvent ev(QEvent::WindowActivate); + QApplication::sendEvent(this, &ev); + } + } + break; + case XEMBED_WINDOW_DEACTIVATE: { + // When we receive an XEMBED_WINDOW_DEACTIVATE, we simply send + // ourselves a WindowDeactivate event. Real activation happens + // when receive XEMBED_FOCUS_IN. + if (isActiveWindow()) { + if (!qApp->activePopupWidget()) + QApplication::setActiveWindow(0); + } else { + QEvent ev(QEvent::WindowDeactivate); + QApplication::sendEvent(this, &ev); + } + } + break; + case XEMBED_EMBEDDED_NOTIFY: { +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedWidget::x11Event: client" + << (void *)this << "with winId" << winId() + << "received an XEMBED EMBEDDED NOTIFY message"; +#endif + // In this message's l[2] we have the max version + // supported by both the client and the + // container. QX11EmbedWidget does not use this field. + + // We have been embedded, so we set our + // client's embedded flag. + d->setEmbedded(); + emit embedded(); + } + break; + case XEMBED_FOCUS_IN: + // don't set the focus if a modal dialog is open + if (qApp->activeModalWidget()) + break; + + // in case we embed more than one topLevel window inside the same + // host window. + if (window() != qApp->activeWindow()) + qApp->setActiveWindow(this); + + switch (event->xclient.data.l[2]) { + case XEMBED_FOCUS_CURRENT: + // The container sends us this message if it wants + // us to focus on the widget that last had focus. + // This is the reply when XEMBED_REQUEST_FOCUS is + // sent to the container. + if (!d->currentFocus.isNull()) { + if (!d->currentFocus->hasFocus()) + d->currentFocus->setFocus(Qt::OtherFocusReason); + } else { + // No widget currently has focus. We set focus + // on the first widget next to the + // client widget. Since the setFocus will not work + // if the window is disabled, set the currentFocus + // directly so that it's set on window activate. + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::FirstFocusWidget); + d->currentFocus->setFocus(Qt::OtherFocusReason); + } + break; + case XEMBED_FOCUS_FIRST: + // The container sends this message when it wants + // us to focus on the first widget in our focus + // chain (typically because of a tab). + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::FirstFocusWidget); + d->currentFocus->setFocus(Qt::TabFocusReason); + break; + case XEMBED_FOCUS_LAST: + // The container sends this message when it wants + // us to focus on the last widget in our focus + // chain (typically because of a backtab). + d->currentFocus = d->getFocusWidget(QX11EmbedWidgetPrivate::LastFocusWidget); + d->currentFocus->setFocus(Qt::BacktabFocusReason); + break; + default: + // Ignore any other XEMBED_FOCUS_IN details. + break; + } + break; + case XEMBED_FOCUS_OUT: + // The container sends us this message when it wants us + // to lose focus and forget about the widget that last + // had focus. Typically sent by the container when it + // loses focus because of mouse or tab activity. We do + // then not want to set focus on anything if we're + // activated. + if (isActiveWindow()) + d->clearFocus(); + + break; + default: + // Ignore any other XEMBED messages. + break; + }; + } else { + // Non-XEMBED client messages are not interesting. + } + + break; + default: + // Ignore all other x11 events. + break; + } + + // Allow default handling. + return QWidget::x11Event(event); +} + +/*! + \reimp +*/ +bool QX11EmbedWidget::event(QEvent *event) +{ + if (event->type() == QEvent::ParentChange) { + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask | ButtonPressMask + | ButtonReleaseMask + | KeymapStateMask | ButtonMotionMask | PointerMotionMask + | FocusChangeMask + | ExposureMask | StructureNotifyMask + | SubstructureNotifyMask | PropertyChangeMask); + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +void QX11EmbedWidget::resizeEvent(QResizeEvent *event) +{ + if (layout()) + layout()->update(); + QWidget::resizeEvent(event); +} + +/*! + If the widget is embedded, returns the window ID of the + container; otherwize returns 0. +*/ +WId QX11EmbedWidget::containerWinId() const +{ + Q_D(const QX11EmbedWidget); + return d->container; +} + +class QX11EmbedContainerPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QX11EmbedContainer) +public: + inline QX11EmbedContainerPrivate() + { + lastError = QX11EmbedContainer::Unknown; + client = 0; + focusProxy = 0; + clientIsXEmbed = false; + xgrab = false; + } + + bool isEmbedded() const; + void moveInputToProxy(); + + void acceptClient(WId window); + void rejectClient(WId window); + + void checkGrab(); + + WId topLevelParentWinId() const; + + void emitError(QX11EmbedContainer::Error error) { + Q_Q(QX11EmbedContainer); + lastError = error; + emit q->error(error); + } + + WId client; + QWidget *focusProxy; + bool clientIsXEmbed; + bool xgrab; + QRect clientOriginalRect; + QSize wmMinimumSizeHint; + + QX11EmbedContainer::Error lastError; + + static QX11EmbedContainer *activeContainer; +}; + +QX11EmbedContainer *QX11EmbedContainerPrivate::activeContainer = 0; + +/*! + Creates a QX11EmbedContainer object with the given \a parent. +*/ +QX11EmbedContainer::QX11EmbedContainer(QWidget *parent) + : QWidget(*new QX11EmbedContainerPrivate, parent, 0) +{ + Q_D(QX11EmbedContainer); + XSetErrorHandler(x11ErrorHandler); + + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_DontCreateNativeAncestors); + createWinId(); + + setFocusPolicy(Qt::StrongFocus); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + // ### PORT setKeyCompression(false); + setAcceptDrops(true); + setEnabled(false); + + // Everybody gets a focus proxy, but only one toplevel container's + // focus proxy is actually in use. + d->focusProxy = new QWidget(this); + d->focusProxy->setAttribute(Qt::WA_NativeWindow); + d->focusProxy->setAttribute(Qt::WA_DontCreateNativeAncestors); + d->focusProxy->createWinId(); + d->focusProxy->setGeometry(-1, -1, 1, 1); + + // We need events from the window (activation status) and + // from qApp (keypress/release). + qApp->installEventFilter(this); + + // Install X11 event filter. + if (!oldX11EventFilter) + oldX11EventFilter = QCoreApplication::instance()->setEventFilter(x11EventFilter); + + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | KeymapStateMask + | PointerMotionMask + | EnterWindowMask | LeaveWindowMask + | FocusChangeMask + | ExposureMask + | StructureNotifyMask + | SubstructureNotifyMask); + + // Make sure our new event mask takes effect as soon as possible. + XFlush(x11Info().display()); + + // Move input to our focusProxy if this widget is active, and not + // shaded by a modal dialog (in which case isActiveWindow() would + // still return true, but where we must not move input focus). + if (qApp->activeWindow() == window() && !d->isEmbedded()) + d->moveInputToProxy(); + +#ifdef QX11EMBED_DEBUG + qDebug() << "QX11EmbedContainer::QX11EmbedContainer: constructed container" + << (void *)this << "with winId" << winId(); +#endif +} + +/*! + Destructs a QX11EmbedContainer. +*/ +QX11EmbedContainer::~QX11EmbedContainer() +{ + Q_D(QX11EmbedContainer); + if (d->client) { + XUnmapWindow(x11Info().display(), d->client); + XReparentWindow(x11Info().display(), d->client, x11Info().appRootWindow(x11Info().screen()), 0, 0); + } + + if (d->xgrab) + XUngrabButton(x11Info().display(), AnyButton, AnyModifier, internalWinId()); +} + + +QX11EmbedContainer::Error QX11EmbedContainer::error() const { + return d_func()->lastError; +} + + +/*! \reimp +*/ +void QX11EmbedContainer::paintEvent(QPaintEvent *) +{ +} + +/*! \internal + + Returns whether or not the windows' embedded flag is set. +*/ +bool QX11EmbedContainerPrivate::isEmbedded() const +{ + Q_Q(const QX11EmbedContainer); + return ((QHackWidget *)q->window())->topData()->embedded == 1; +} + +/*! \internal + + Returns the parentWinId of the window. +*/ +WId QX11EmbedContainerPrivate::topLevelParentWinId() const +{ + Q_Q(const QX11EmbedContainer); + return ((QHackWidget *)q->window())->topData()->parentWinId; +} + +/*! + If the container has an embedded widget, this function returns + the X11 window ID of the client; otherwise it returns 0. +*/ +WId QX11EmbedContainer::clientWinId() const +{ + Q_D(const QX11EmbedContainer); + return d->client; +} + +/*! + Instructs the container to embed the X11 window with window ID \a + id. The client widget will then move on top of the container + window and be resized to fit into the container. + + The \a id should be the ID of a window controlled by an XEmbed + enabled application, but this is not mandatory. If \a id does not + belong to an XEmbed client widget, then focus handling, + activation, accelerators and other features will not work + properly. +*/ +void QX11EmbedContainer::embedClient(WId id) +{ + Q_D(QX11EmbedContainer); + + if (id == 0) { + d->emitError(InvalidWindowID); + return; + } + + // Walk up the tree of parent windows to prevent embedding of ancestors. + WId thisId = internalWinId(); + Window rootReturn; + Window parentReturn; + Window *childrenReturn = 0; + unsigned int nchildrenReturn; + do { + if (XQueryTree(x11Info().display(), thisId, &rootReturn, + &parentReturn, &childrenReturn, &nchildrenReturn) == 0) { + d->emitError(InvalidWindowID); + return; + } + if (childrenReturn) { + XFree(childrenReturn); + childrenReturn = 0; + } + + thisId = parentReturn; + if (id == thisId) { + d->emitError(InvalidWindowID); + return; + } + } while (thisId != rootReturn); + + // watch for property notify events (see below) + XGrabServer(x11Info().display()); + XWindowAttributes attrib; + if (!XGetWindowAttributes(x11Info().display(), id, &attrib)) { + XUngrabServer(x11Info().display()); + d->emitError(InvalidWindowID); + return; + } + XSelectInput(x11Info().display(), id, attrib.your_event_mask | PropertyChangeMask | StructureNotifyMask); + XUngrabServer(x11Info().display()); + + // Put the window into WithdrawnState + XUnmapWindow(x11Info().display(), id); + XSync(x11Info().display(), False); // make sure the window is hidden + + /* + Wait for notification from the window manager that the window is + in withdrawn state. According to the ICCCM section 4.1.3.1, + we should wait for the WM_STATE property to either be deleted or + set to WithdrawnState. + + For safety, we will not wait more than 500 ms, so that we can + preemptively workaround buggy window managers. + */ + QElapsedTimer t; + t.start(); + + functorData data; + data.id = id; + data.rootWindow = attrib.root; + data.clearedWmState = false; + data.reparentedToRoot = false; + + do { + if (t.elapsed() > 500) // time-out after 500 ms + break; + + XEvent event; + if (!XCheckIfEvent(x11Info().display(), &event, functor, (XPointer) &data)) { + XSync(x11Info().display(), False); + usleep(50000); + continue; + } + + qApp->x11ProcessEvent(&event); + } while (!data.clearedWmState || !data.reparentedToRoot); + + // restore the event mask + XSelectInput(x11Info().display(), id, attrib.your_event_mask); + + switch (XReparentWindow(x11Info().display(), id, internalWinId(), 0, 0)) { + case BadWindow: + case BadMatch: + d->emitError(InvalidWindowID); + break; + default: + break; + } +} + +/*! \internal + + Handles key, activation and focus events for the container. +*/ +bool QX11EmbedContainer::eventFilter(QObject *o, QEvent *event) +{ + Q_D(QX11EmbedContainer); + switch (event->type()) { + case QEvent::KeyPress: + // Forward any keypresses to our client. + if (o == this && d->client) { + lastKeyEvent.window = d->client; + XSendEvent(x11Info().display(), d->client, false, KeyPressMask, (XEvent *) &lastKeyEvent); + return true; + } + break; + case QEvent::KeyRelease: + // Forward any keyreleases to our client. + if (o == this && d->client) { + lastKeyEvent.window = d->client; + XSendEvent(x11Info().display(), d->client, false, KeyReleaseMask, (XEvent *) &lastKeyEvent); + return true; + } + break; + + case QEvent::WindowActivate: + // When our container window is activated, we pass the + // activation message on to our client. Note that X input + // focus is set to our focus proxy. We want to intercept all + // keypresses. + if (o == window() && d->client) { + if (d->clientIsXEmbed) { + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_WINDOW_ACTIVATE); + } else { + d->checkGrab(); + if (hasFocus()) + XSetInputFocus(x11Info().display(), d->client, XRevertToParent, x11Time()); + } + if (!d->isEmbedded()) + d->moveInputToProxy(); + } + break; + case QEvent::WindowDeactivate: + // When our container window is deactivated, we pass the + // deactivation message to our client. + if (o == window() && d->client) { + if (d->clientIsXEmbed) + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_WINDOW_DEACTIVATE); + else + d->checkGrab(); + } + break; + case QEvent::FocusIn: + // When receiving FocusIn events generated by Tab or Backtab, + // we pass focus on to our client. Any mouse activity is sent + // directly to the client, and it will ask us for focus with + // XEMBED_REQUEST_FOCUS. + if (o == this && d->client) { + if (!d->isEmbedded()) + d->activeContainer = this; + + if (d->clientIsXEmbed) { + if (!d->isEmbedded()) + d->moveInputToProxy(); + + QFocusEvent *fe = (QFocusEvent *)event; + switch (fe->reason()) { + case Qt::TabFocusReason: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + break; + case Qt::BacktabFocusReason: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_LAST); + break; + default: + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); + break; + } + } else { + d->checkGrab(); + XSetInputFocus(x11Info().display(), d->client, XRevertToParent, x11Time()); + } + } + + break; + case QEvent::FocusOut: { + // When receiving a FocusOut, we ask our client to remove its + // focus. + if (o == this && d->client) { + if (!d->isEmbedded()) { + d->activeContainer = 0; + if (isActiveWindow()) + d->moveInputToProxy(); + } + + if (d->clientIsXEmbed) { + QFocusEvent *fe = (QFocusEvent *)event; + if (o == this && d->client && fe->reason() != Qt::ActiveWindowFocusReason) + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_OUT); + } else { + d->checkGrab(); + } + } + } + break; + + case QEvent::Close: { + if (o == this && d->client) { + // Unmap the client and reparent it to the root window. + // Wait until the messages have been processed. Then ask + // the window manager to delete the window. + XUnmapWindow(x11Info().display(), d->client); + XReparentWindow(x11Info().display(), d->client, x11Info().appRootWindow(x11Info().screen()), 0, 0); + XSync(x11Info().display(), false); + + XEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = d->client; + ev.xclient.message_type = ATOM(WM_PROTOCOLS); + ev.xclient.format = 32; + ev.xclient.data.s[0] = ATOM(WM_DELETE_WINDOW); + XSendEvent(x11Info().display(), d->client, false, NoEventMask, &ev); + + XFlush(x11Info().display()); + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + setEnabled(false); + update(); + + emit clientClosed(); + } + } + default: + break; + } + + return QWidget::eventFilter(o, event); +} + +/*! \internal + + Handles X11 events for the container. +*/ +bool QX11EmbedContainer::x11Event(XEvent *event) +{ + Q_D(QX11EmbedContainer); + + switch (event->type) { + case CreateNotify: + // The client created an embedded window. + if (d->client) + d->rejectClient(event->xcreatewindow.window); + else + d->acceptClient(event->xcreatewindow.window); + break; + case DestroyNotify: + if (event->xdestroywindow.window == d->client) { + // The client died. + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } + break; + case ReparentNotify: + // The client sends us this if it reparents itself out of our + // widget. + if (event->xreparent.window == d->client && event->xreparent.parent != internalWinId()) { + d->client = 0; + d->clientIsXEmbed = false; + d->wmMinimumSizeHint = QSize(); + updateGeometry(); + update(); + setEnabled(false); + emit clientClosed(); + } else if (event->xreparent.parent == internalWinId()) { + // The client reparented itself into this window. + if (d->client) + d->rejectClient(event->xreparent.window); + else + d->acceptClient(event->xreparent.window); + } + break; + case ClientMessage: { + if (event->xclient.message_type == ATOM(_XEMBED)) { + // Ignore XEMBED messages not to ourselves + if (event->xclient.window != internalWinId()) + break; + + // Receiving an XEmbed message means the client + // is an XEmbed client. + d->clientIsXEmbed = true; + + Time msgtime = (Time) event->xclient.data.l[0]; + if (msgtime > X11->time) + X11->time = msgtime; + + switch (event->xclient.data.l[1]) { + case XEMBED_REQUEST_FOCUS: { + // This typically happens when the client gets focus + // because of a mouse click. + if (!hasFocus()) + setFocus(Qt::OtherFocusReason); + + // The message is passed along to the topmost container + // that eventually responds with a XEMBED_FOCUS_IN + // message. The focus in message is passed all the way + // back until it reaches the original focus + // requestor. In the end, not only the original client + // has focus, but also all its ancestor containers. + if (d->isEmbedded()) { + // If our window's embedded flag is set, then + // that suggests that we are part of a client. The + // parentWinId will then point to an container to whom + // we must pass this message. + sendXEmbedMessage(d->topLevelParentWinId(), x11Info().display(), XEMBED_REQUEST_FOCUS); + } else { + // Our window's embedded flag is not set, + // so we are the topmost container. We respond to + // the focus request message with a focus in + // message. This message will pass on from client + // to container to client until it reaches the + // originator of the XEMBED_REQUEST_FOCUS message. + sendXEmbedMessage(d->client, x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT); + } + + break; + } + case XEMBED_FOCUS_NEXT: + // Client sends this event when it received a tab + // forward and was at the end of its focus chain. If + // we are the only widget in the focus chain, we send + // ourselves a FocusIn event. + if (d->focus_next != this) { + focusNextPrevChild(true); + } else { + QFocusEvent event(QEvent::FocusIn, Qt::TabFocusReason); + qApp->sendEvent(this, &event); + } + + break; + case XEMBED_FOCUS_PREV: + // Client sends this event when it received a backtab + // and was at the start of its focus chain. If we are + // the only widget in the focus chain, we send + // ourselves a FocusIn event. + if (d->focus_next != this) { + focusNextPrevChild(false); + } else { + QFocusEvent event(QEvent::FocusIn, Qt::BacktabFocusReason); + qApp->sendEvent(this, &event); + } + + break; + default: + break; + } + } + } + break; + case XButtonPress: + if (!d->clientIsXEmbed) { + setFocus(Qt::MouseFocusReason); + XAllowEvents(x11Info().display(), ReplayPointer, CurrentTime); + return true; + } + break; + case XButtonRelease: + if (!d->clientIsXEmbed) + XAllowEvents(x11Info().display(), SyncPointer, CurrentTime); + break; + default: + break; + } + + return QWidget::x11Event(event); +} + +/*! \internal + + Whenever the container is resized, we need to resize our client. +*/ +void QX11EmbedContainer::resizeEvent(QResizeEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) + XResizeWindow(x11Info().display(), d->client, width(), height()); +} + +/*! \internal + + We use the QShowEvent to signal to our client that we want it to + map itself. We do this by changing its window property + XEMBED_INFO. The client will get an X11 PropertyNotify. +*/ +void QX11EmbedContainer::showEvent(QShowEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) { + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), d->client, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 32, + PropModeReplace, (unsigned char *) data, 2); + } +} + +/*! \internal + + We use the QHideEvent to signal to our client that we want it to + unmap itself. We do this by changing its window property + XEMBED_INFO. The client will get an X11 PropertyNotify. +*/ +void QX11EmbedContainer::hideEvent(QHideEvent *) +{ + Q_D(QX11EmbedContainer); + if (d->client) { + long data[] = {XEMBED_VERSION, XEMBED_MAPPED}; + XChangeProperty(x11Info().display(), d->client, ATOM(_XEMBED_INFO), ATOM(_XEMBED_INFO), 32, + PropModeReplace, (unsigned char *) data, 2); + } +} + +/*! + \reimp +*/ +bool QX11EmbedContainer::event(QEvent *event) +{ + if (event->type() == QEvent::ParentChange) { + XSelectInput(x11Info().display(), internalWinId(), + KeyPressMask | KeyReleaseMask + | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask + | KeymapStateMask + | PointerMotionMask + | EnterWindowMask | LeaveWindowMask + | FocusChangeMask + | ExposureMask + | StructureNotifyMask + | SubstructureNotifyMask); + } + return QWidget::event(event); +} + +/*! \internal + + Rejects a client window by reparenting it to the root window. The + client will receive a reparentnotify, and will most likely assume + that the container has shut down. The XEmbed protocol does not + define any way to reject a client window, but this is a clean way + to do it. +*/ +void QX11EmbedContainerPrivate::rejectClient(WId window) +{ + Q_Q(QX11EmbedContainer); + q->setEnabled(false); + XRemoveFromSaveSet(q->x11Info().display(), client); + XReparentWindow(q->x11Info().display(), window, q->x11Info().appRootWindow(q->x11Info().screen()), 0, 0); +} + +/*! \internal + + Accepts a client by mapping it, resizing it and optionally + activating and giving it logical focusing through XEMBED messages. +*/ +void QX11EmbedContainerPrivate::acceptClient(WId window) +{ + Q_Q(QX11EmbedContainer); + client = window; + q->setEnabled(true); + + // This tells Qt that we wish to forward DnD messages to + // our client. + if (!extra) + createExtra(); + extraData()->xDndProxy = client; + + unsigned int version = XEmbedVersion(); + + Atom actual_type_return; + int actual_format_return; + unsigned long nitems_return = 0; + unsigned long bytes_after_return; + unsigned char *prop_return = 0; + unsigned int clientversion = 0; + + // Add this client to our saveset, so if we crash, the client window + // doesn't get destroyed. This is useful for containers that restart + // automatically after a crash, because it can simply reembed its clients + // without having to restart them (KDE panel). + XAddToSaveSet(q->x11Info().display(), client); + + // XEmbed clients have an _XEMBED_INFO property in which we can + // fetch the version + if (XGetWindowProperty(q->x11Info().display(), client, ATOM(_XEMBED_INFO), 0, 2, false, + ATOM(_XEMBED_INFO), &actual_type_return, &actual_format_return, + &nitems_return, &bytes_after_return, &prop_return) == Success) { + + if (actual_type_return != None && actual_format_return != 0) { + // Clients with the _XEMBED_INFO property are XEMBED clients. + clientIsXEmbed = true; + + long *p = (long *)prop_return; + if (nitems_return >= 2) + clientversion = (unsigned int)p[0]; + } + + XFree(prop_return); + } + + // Store client window's original size and placement. + Window root; + int x_return, y_return; + unsigned int width_return, height_return, border_width_return, depth_return; + XGetGeometry(q->x11Info().display(), client, &root, &x_return, &y_return, + &width_return, &height_return, &border_width_return, &depth_return); + clientOriginalRect.setCoords(x_return, y_return, + x_return + width_return - 1, + y_return + height_return - 1); + + // Ask the client for its minimum size. + XSizeHints size; + long msize; + if (XGetWMNormalHints(q->x11Info().display(), client, &size, &msize) && (size.flags & PMinSize)) { + wmMinimumSizeHint = QSize(size.min_width, size.min_height); + q->updateGeometry(); + } + + // The container should set the data2 field to the lowest of its + // supported version number and that of the client (from + // _XEMBED_INFO property). + unsigned int minversion = version > clientversion ? clientversion : version; + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_EMBEDDED_NOTIFY, q->internalWinId(), minversion); + XMapWindow(q->x11Info().display(), client); + + // Resize it, but no smaller than its minimum size hint. + XResizeWindow(q->x11Info().display(), + client, + qMax(q->width(), wmMinimumSizeHint.width()), + qMax(q->height(), wmMinimumSizeHint.height())); + q->update(); + + // Not mentioned in the protocol is that if the container + // is already active, the client must be activated to work + // properly. + if (q->window()->isActiveWindow()) + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_WINDOW_ACTIVATE); + + // Also, if the container already has focus, then it must + // send a focus in message to its new client; otherwise we ask + // it to remove focus. + if (q->focusWidget() == q && q->hasFocus()) + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_FOCUS_IN, XEMBED_FOCUS_FIRST); + else + sendXEmbedMessage(client, q->x11Info().display(), XEMBED_FOCUS_OUT); + + if (!clientIsXEmbed) { + checkGrab(); + if (q->hasFocus()) { + XSetInputFocus(q->x11Info().display(), client, XRevertToParent, x11Time()); + } + } else { + if (!isEmbedded()) + moveInputToProxy(); + } + + emit q->clientIsEmbedded(); +} + +/*! \internal + + Moves X11 keyboard input focus to the focusProxy, unless the focus + is there already. When X11 keyboard input focus is on the + focusProxy, which is a child of the container and a sibling of the + client, X11 keypresses and keyreleases will always go to the proxy + and not to the client. +*/ +void QX11EmbedContainerPrivate::moveInputToProxy() +{ + Q_Q(QX11EmbedContainer); + // Following Owen Taylor's advice from the XEmbed specification to + // always use CurrentTime when no explicit user action is involved. + XSetInputFocus(q->x11Info().display(), focusProxy->internalWinId(), XRevertToParent, CurrentTime); +} + +/*! \internal + + Ask the window manager to give us a default minimum size. +*/ +QSize QX11EmbedContainer::minimumSizeHint() const +{ + Q_D(const QX11EmbedContainer); + if (!d->client || !d->wmMinimumSizeHint.isValid()) + return QWidget::minimumSizeHint(); + return d->wmMinimumSizeHint; +} + +/*! \internal + +*/ +void QX11EmbedContainerPrivate::checkGrab() +{ + Q_Q(QX11EmbedContainer); + if (!clientIsXEmbed && q->isActiveWindow() && !q->hasFocus()) { + if (!xgrab) { + XGrabButton(q->x11Info().display(), AnyButton, AnyModifier, q->internalWinId(), + true, ButtonPressMask, GrabModeSync, GrabModeAsync, + None, None); + } + xgrab = true; + } else { + if (xgrab) + XUngrabButton(q->x11Info().display(), AnyButton, AnyModifier, q->internalWinId()); + xgrab = false; + } +} + +/*! + Detaches the client from the embedder. The client will appear as a + standalone window on the desktop. +*/ +void QX11EmbedContainer::discardClient() +{ + Q_D(QX11EmbedContainer); + if (d->client) { + XResizeWindow(x11Info().display(), d->client, d->clientOriginalRect.width(), + d->clientOriginalRect.height()); + + d->rejectClient(d->client); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qx11embed_x11.h b/src/gui/kernel/qx11embed_x11.h new file mode 100644 index 0000000000..30929f7ba9 --- /dev/null +++ b/src/gui/kernel/qx11embed_x11.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QX11EMBED_X11_H +#define QX11EMBED_X11_H + +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QX11EmbedWidgetPrivate; +class Q_GUI_EXPORT QX11EmbedWidget : public QWidget +{ + Q_OBJECT +public: + QX11EmbedWidget(QWidget *parent = 0); + ~QX11EmbedWidget(); + + void embedInto(WId id); + WId containerWinId() const; + + enum Error { + Unknown, + Internal, + InvalidWindowID + }; + Error error() const; + +Q_SIGNALS: + void embedded(); + void containerClosed(); + void error(QX11EmbedWidget::Error error); + +protected: + bool x11Event(XEvent *); + bool eventFilter(QObject *, QEvent *); + bool event(QEvent *); + void resizeEvent(QResizeEvent *); + +private: + Q_DECLARE_PRIVATE(QX11EmbedWidget) + Q_DISABLE_COPY(QX11EmbedWidget) +}; + +class QX11EmbedContainerPrivate; +class Q_GUI_EXPORT QX11EmbedContainer : public QWidget +{ + Q_OBJECT +public: + QX11EmbedContainer(QWidget *parent = 0); + ~QX11EmbedContainer(); + + void embedClient(WId id); + void discardClient(); + + WId clientWinId() const; + + QSize minimumSizeHint() const; + + enum Error { + Unknown, + Internal, + InvalidWindowID + }; + Error error() const; + +Q_SIGNALS: + void clientIsEmbedded(); + void clientClosed(); + void error(QX11EmbedContainer::Error); + +protected: + bool x11Event(XEvent *); + bool eventFilter(QObject *, QEvent *); + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *); + void showEvent(QShowEvent *); + void hideEvent(QHideEvent *); + bool event(QEvent *); + +private: + Q_DECLARE_PRIVATE(QX11EmbedContainer) + Q_DISABLE_COPY(QX11EmbedContainer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QX11EMBED_X11_H diff --git a/src/gui/kernel/qx11info_x11.cpp b/src/gui/kernel/qx11info_x11.cpp new file mode 100644 index 0000000000..f52443befc --- /dev/null +++ b/src/gui/kernel/qx11info_x11.cpp @@ -0,0 +1,543 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidget.h" +#include "qpixmap.h" +#include "qx11info_x11.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QX11Info + \brief The QX11Info class provides information about the X display + configuration. + + \ingroup shared + + The class provides two APIs: a set of non-static functions that + provide information about a specific widget or pixmap, and a set + of static functions that provide the default information for the + application. + + \warning This class is only available on X11. For querying + per-screen information in a portable way, use QDesktopWidget. + + \sa QWidget::x11Info(), QPixmap::x11Info(), QDesktopWidget +*/ + +/*! + Constructs an empty QX11Info object. +*/ +QX11Info::QX11Info() + : x11data(0) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QX11Info::QX11Info(const QX11Info &other) +{ + x11data = other.x11data; + if (x11data) + ++x11data->ref; +} + +/*! + Assigns \a other to this object and returns a reference to this + object. +*/ +QX11Info &QX11Info::operator=(const QX11Info &other) +{ + if (other.x11data) + ++other.x11data->ref; + if (x11data && !--x11data->ref) + delete x11data; + x11data = other.x11data; + return *this; +} + +/*! + Destroys the QX11Info object. +*/ +QX11Info::~QX11Info() +{ + if (x11data && !--x11data->ref) + delete x11data; +} + +/*! + \internal + Makes a shallow copy of the X11-specific data of \a fromDevice, if it is not + null. Otherwise this function sets it to null. +*/ + +void QX11Info::copyX11Data(const QPaintDevice *fromDevice) +{ + QX11InfoData *xd = 0; + if (fromDevice) { + if (fromDevice->devType() == QInternal::Widget) + xd = static_cast<const QWidget *>(fromDevice)->x11Info().x11data; + else if (fromDevice->devType() == QInternal::Pixmap) + xd = static_cast<const QPixmap *>(fromDevice)->x11Info().x11data; + } + setX11Data(xd); +} + +/*! + \internal + Makes a deep copy of the X11-specific data of \a fromDevice, if it is not + null. Otherwise this function sets it to null. +*/ + +void QX11Info::cloneX11Data(const QPaintDevice *fromDevice) +{ + QX11InfoData *d = 0; + if (fromDevice) { + QX11InfoData *xd; + if (fromDevice->devType() == QInternal::Widget) { + xd = static_cast<const QWidget *>(fromDevice)->x11Info().x11data; + } else { + Q_ASSERT(fromDevice->devType() == QInternal::Pixmap); + xd = static_cast<const QPixmap *>(fromDevice)->x11Info().x11data; + } + d = new QX11InfoData(*xd); + d->ref = 0; + } + setX11Data(d); +} + +/*! + \internal + Makes a shallow copy of the X11-specific data \a d and assigns it to this + class. This function increments the reference code of \a d. +*/ + +void QX11Info::setX11Data(const QX11InfoData* d) +{ + if (x11data && !--x11data->ref) + delete x11data; + x11data = (QX11InfoData *)d; + if (x11data) + ++x11data->ref; +} + + +/*! + \internal + If \a def is false, returns a deep copy of the x11Data, or 0 if x11Data is 0. + If \a def is true, makes a QX11Data struct filled with the default + values. + + In either case the caller is responsible for deleting the returned + struct. But notice that the struct is a shared class, so other + classes might also have a reference to it. The reference count of + the returned QX11Data* is 0. +*/ + +QX11InfoData* QX11Info::getX11Data(bool def) const +{ + QX11InfoData* res = 0; + if (def) { + res = new QX11InfoData; + res->ref = 0; + res->screen = appScreen(); + res->depth = appDepth(); + res->cells = appCells(); + res->colormap = colormap(); + res->defaultColormap = appDefaultColormap(); + res->visual = (Visual*) appVisual(); + res->defaultVisual = appDefaultVisual(); + } else if (x11data) { + res = new QX11InfoData; + *res = *x11data; + res->ref = 0; + } + return res; +} + +/*! + Returns the horizontal resolution of the given \a screen in terms of the + number of dots per inch. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa setAppDpiX(), appDpiY() +*/ +int QX11Info::appDpiX(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiX; +} + +/*! + Sets the horizontal resolution of the given \a screen to the number of + dots per inch specified by \a xdpi. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa appDpiX(), setAppDpiY() +*/ + +void QX11Info::setAppDpiX(int screen, int xdpi) +{ + if (!X11) + return; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return; + X11->screens[screen].dpiX = xdpi; +} + +/*! + Returns the vertical resolution of the given \a screen in terms of the + number of dots per inch. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa setAppDpiY(), appDpiX() +*/ + +int QX11Info::appDpiY(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiY; +} + +/*! + Sets the vertical resolution of the given \a screen to the number of + dots per inch specified by \a ydpi. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa appDpiY(), setAppDpiX() +*/ +void QX11Info::setAppDpiY(int screen, int ydpi) +{ + if (!X11) + return; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return; + X11->screens[screen].dpiY = ydpi; +} + +/*! + Returns the X11 time. + + \sa setAppTime(), appUserTime() +*/ +unsigned long QX11Info::appTime() +{ + return X11 ? X11->time : 0; +} + +/*! + Sets the X11 time to the value specified by \a time. + + \sa appTime(), setAppUserTime() +*/ +void QX11Info::setAppTime(unsigned long time) +{ + if (X11) { + X11->time = time; + } +} + +/*! + Returns the X11 user time. + + \sa setAppUserTime(), appTime() +*/ +unsigned long QX11Info::appUserTime() +{ + return X11 ? X11->userTime : 0; +} + +/*! + Sets the X11 user time as specified by \a time. + + \sa appUserTime(), setAppTime() +*/ +void QX11Info::setAppUserTime(unsigned long time) +{ + if (X11) { + X11->userTime = time; + } +} + + +/*! + \fn const char *QX11Info::appClass() + + Returns the X11 application class. + + \sa display() +*/ + +/*! + Returns the default display for the application. + + \sa appScreen() +*/ + +Display *QX11Info::display() +{ + return X11 ? X11->display : 0; +} + +/*! + Returns the number of the screen where the application is being + displayed. + + \sa display(), screen() +*/ +int QX11Info::appScreen() +{ + return X11 ? X11->defaultScreen : 0; +} + +/*! + Returns a handle for the application's color map on the given \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa colormap(), defaultColormap() +*/ +Qt::HANDLE QX11Info::appColormap(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].colormap : 0; +} + +/*! + Returns the current visual used by the application on the given + \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa visual(), defaultVisual() +*/ + +void *QX11Info::appVisual(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].visual : 0; +} + +/*! + Returns a handle for the applications root window on the given \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa QApplication::desktop() +*/ +Qt::HANDLE QX11Info::appRootWindow(int screen) +{ + return X11 ? RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen) : 0; +} + +/*! + Returns the color depth (bits per pixel) used by the application on the + given \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa depth() +*/ + +int QX11Info::appDepth(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].depth : 32; +} + +/*! + Returns the number of cells used by the application on the given \a screen. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa cells() +*/ + +int QX11Info::appCells(int screen) +{ return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].cells : 0; } + +/*! + Returns true if the application has a default color map on the given + \a screen; otherwise returns false. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. +*/ +bool QX11Info::appDefaultColormap(int screen) +{ return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultColormap : true; } + +/*! + Returns true if the application has a default visual on the given \a screen; + otherwise returns false. + + The \a screen argument is an X screen number. Be aware that if + the user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. +*/ +bool QX11Info::appDefaultVisual(int screen) +{ return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultVisual : true; } + +/*! + Returns the number of the screen currently in use. + + The return value is an X screen number. Be aware that if the + user's system uses Xinerama (as opposed to traditional X11 + multiscreen), there is only one X screen. Use QDesktopWidget to + query for information about Xinerama screens. + + \sa appScreen() +*/ +int QX11Info::screen() const +{ return x11data ? x11data->screen : QX11Info::appScreen(); } + +/*! + Returns the color depth (bits per pixel) of the X display. + + \sa appDepth() +*/ + +int QX11Info::depth() const +{ return x11data ? x11data->depth : QX11Info::appDepth(); } + +/*! + Returns the number of cells. + + \sa appCells() +*/ + +int QX11Info::cells() const +{ return x11data ? x11data->cells : QX11Info::appCells(); } + +/*! + Returns a handle for the color map. + + \sa defaultColormap() +*/ + +Qt::HANDLE QX11Info::colormap() const +{ return x11data ? x11data->colormap : QX11Info::appColormap(); } + +/*! + Returns true if there is a default color map; otherwise returns false. + + \sa colormap() +*/ + +bool QX11Info::defaultColormap() const +{ return x11data ? x11data->defaultColormap : QX11Info::appDefaultColormap(); } + +/*! + Returns the current visual. + + \sa appVisual(), defaultVisual() +*/ + +void *QX11Info::visual() const +{ return x11data ? x11data->visual : QX11Info::appVisual(); } + +/*! + Returns true if there is a default visual; otherwise returns false. + + \sa visual(), appVisual() +*/ + +bool QX11Info::defaultVisual() const +{ return x11data ? x11data->defaultVisual : QX11Info::appDefaultVisual(); } + + +/*! + \since 4.4 + + Returns true if there is a compositing manager running. +*/ +bool QX11Info::isCompositingManagerRunning() +{ + return X11 ? X11->compositingManagerRunning : false; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qx11info_x11.h b/src/gui/kernel/qx11info_x11.h new file mode 100644 index 0000000000..ece85740d2 --- /dev/null +++ b/src/gui/kernel/qx11info_x11.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QX11INFO_X11_H +#define QX11INFO_X11_H + +#include <QtCore/qnamespace.h> + +typedef struct _XDisplay Display; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +struct QX11InfoData; +class QX11Info; +class QPaintDevice; +class QApplicationPrivate; +class QX11InfoPrivate; +struct QX11WindowAttributes; + +void qt_x11_getX11InfoForWindow(QX11Info * xinfo, const QX11WindowAttributes &a); +class Q_GUI_EXPORT QX11Info +{ +public: + QX11Info(); + ~QX11Info(); + QX11Info(const QX11Info &other); + QX11Info &operator=(const QX11Info &other); + + static Display *display(); + static const char *appClass(); + int screen() const; + int depth() const; + int cells() const; + Qt::HANDLE colormap() const; + bool defaultColormap() const; + void *visual() const; + bool defaultVisual() const; + + static int appScreen(); + static int appDepth(int screen = -1); + static int appCells(int screen = -1); + static Qt::HANDLE appColormap(int screen = -1); + static void *appVisual(int screen = -1); + static Qt::HANDLE appRootWindow(int screen = -1); + static bool appDefaultColormap(int screen = -1); + static bool appDefaultVisual(int screen = -1); + static int appDpiX(int screen = -1); + static int appDpiY(int screen = -1); + static void setAppDpiX(int screen, int dpi); + static void setAppDpiY(int screen, int dpi); + static unsigned long appTime(); + static unsigned long appUserTime(); + static void setAppTime(unsigned long time); + static void setAppUserTime(unsigned long time); + static bool isCompositingManagerRunning(); + +protected: + void copyX11Data(const QPaintDevice *); + void cloneX11Data(const QPaintDevice *); + void setX11Data(const QX11InfoData *); + QX11InfoData* getX11Data(bool def = false) const; + + QX11InfoData *x11data; + + friend class QX11PaintEngine; + friend class QPixmap; + friend class QX11PixmapData; + friend class QWidget; + friend class QWidgetPrivate; + friend class QGLWidget; + friend void qt_init(QApplicationPrivate *priv, int, Display *display, Qt::HANDLE visual, + Qt::HANDLE colormap); + friend void qt_cleanup(); + friend void qt_x11_getX11InfoForWindow(QX11Info * xinfo, const QX11WindowAttributes &a); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QX11INFO_X11_H diff --git a/src/gui/kernel/symbian.pri b/src/gui/kernel/symbian.pri new file mode 100644 index 0000000000..69422dd02e --- /dev/null +++ b/src/gui/kernel/symbian.pri @@ -0,0 +1,7 @@ +symbian { + contains(QT_CONFIG, s60): LIBS+= $$QMAKE_LIBS_S60 + RESOURCES += symbian/symbianresources.qrc + + HEADERS += symbian/qsymbianevent.h + SOURCES += symbian/qsymbianevent.cpp +} diff --git a/src/gui/kernel/win.pri b/src/gui/kernel/win.pri new file mode 100644 index 0000000000..5ecf4dd94a --- /dev/null +++ b/src/gui/kernel/win.pri @@ -0,0 +1,4 @@ +# Qt/Windows only configuration file +# -------------------------------------------------------------------- + + INCLUDEPATH += ../3rdparty/wintab diff --git a/src/gui/kernel/x11.pri b/src/gui/kernel/x11.pri new file mode 100644 index 0000000000..82de1b68af --- /dev/null +++ b/src/gui/kernel/x11.pri @@ -0,0 +1,4 @@ +x11 { + contains(QT_CONFIG, nas): LIBS_PRIVATE += -laudio -lXt +} + diff --git a/src/gui/mac/images/copyarrowcursor.png b/src/gui/mac/images/copyarrowcursor.png new file mode 100644 index 0000000000..13dfca95bc Binary files /dev/null and b/src/gui/mac/images/copyarrowcursor.png differ diff --git a/src/gui/mac/images/forbiddencursor.png b/src/gui/mac/images/forbiddencursor.png new file mode 100644 index 0000000000..a9f21b4a5e Binary files /dev/null and b/src/gui/mac/images/forbiddencursor.png differ diff --git a/src/gui/mac/images/leopard-unified-toolbar-on.png b/src/gui/mac/images/leopard-unified-toolbar-on.png new file mode 100644 index 0000000000..6716597046 Binary files /dev/null and b/src/gui/mac/images/leopard-unified-toolbar-on.png differ diff --git a/src/gui/mac/images/pluscursor.png b/src/gui/mac/images/pluscursor.png new file mode 100644 index 0000000000..c583c088c9 Binary files /dev/null and b/src/gui/mac/images/pluscursor.png differ diff --git a/src/gui/mac/images/spincursor.png b/src/gui/mac/images/spincursor.png new file mode 100644 index 0000000000..ca44ab50fd Binary files /dev/null and b/src/gui/mac/images/spincursor.png differ diff --git a/src/gui/mac/images/waitcursor.png b/src/gui/mac/images/waitcursor.png new file mode 100644 index 0000000000..a9abe61320 Binary files /dev/null and b/src/gui/mac/images/waitcursor.png differ diff --git a/src/gui/mac/macresources.qrc b/src/gui/mac/macresources.qrc new file mode 100644 index 0000000000..9696002205 --- /dev/null +++ b/src/gui/mac/macresources.qrc @@ -0,0 +1,12 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/mac/cursors"> +<file>images/copyarrowcursor.png</file> +<file>images/forbiddencursor.png</file> +<file>images/spincursor.png</file> +<file>images/waitcursor.png</file> +<file>images/pluscursor.png</file> +</qresource> +<qresource prefix="/trolltech/mac/style"> +<file>images/leopard-unified-toolbar-on.png</file> +</qresource> +</RCC> diff --git a/src/gui/mac/qt_menu.nib/classes.nib b/src/gui/mac/qt_menu.nib/classes.nib new file mode 100644 index 0000000000..0031e0e4e5 --- /dev/null +++ b/src/gui/mac/qt_menu.nib/classes.nib @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBClasses</key> + <array> + <dict> + <key>ACTIONS</key> + <dict> + <key>hide</key> + <string>id</string> + <key>hideOtherApplications</key> + <string>id</string> + <key>orderFrontStandardAboutPanel</key> + <string>id</string> + <key>qtDispatcherToQAction</key> + <string>id</string> + <key>terminate</key> + <string>id</string> + <key>unhideAllApplications</key> + <string>id</string> + </dict> + <key>CLASS</key> + <string>QCocoaMenuLoader</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>OUTLETS</key> + <dict> + <key>aboutItem</key> + <string>NSMenuItem</string> + <key>aboutQtItem</key> + <string>NSMenuItem</string> + <key>appMenu</key> + <string>NSMenu</string> + <key>hideItem</key> + <string>NSMenuItem</string> + <key>preferencesItem</key> + <string>NSMenuItem</string> + <key>quitItem</key> + <string>NSMenuItem</string> + <key>theMenu</key> + <string>NSMenu</string> + </dict> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>FirstResponder</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSObject</string> + </dict> + </array> + <key>IBVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/gui/mac/qt_menu.nib/info.nib b/src/gui/mac/qt_menu.nib/info.nib new file mode 100644 index 0000000000..02e5cca562 --- /dev/null +++ b/src/gui/mac/qt_menu.nib/info.nib @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBFramework Version</key> + <string>672</string> + <key>IBOldestOS</key> + <integer>5</integer> + <key>IBOpenObjects</key> + <array> + <integer>57</integer> + </array> + <key>IBSystem Version</key> + <string>9L31a</string> + <key>targetFramework</key> + <string>IBCocoaFramework</string> +</dict> +</plist> diff --git a/src/gui/mac/qt_menu.nib/keyedobjects.nib b/src/gui/mac/qt_menu.nib/keyedobjects.nib new file mode 100644 index 0000000000..3edb0ed2eb Binary files /dev/null and b/src/gui/mac/qt_menu.nib/keyedobjects.nib differ diff --git a/src/gui/math3d/math3d.pri b/src/gui/math3d/math3d.pri new file mode 100644 index 0000000000..e4dd53a68e --- /dev/null +++ b/src/gui/math3d/math3d.pri @@ -0,0 +1,15 @@ +HEADERS += \ + math3d/qgenericmatrix.h \ + math3d/qmatrix4x4.h \ + math3d/qquaternion.h \ + math3d/qvector2d.h \ + math3d/qvector3d.h \ + math3d/qvector4d.h + +SOURCES += \ + math3d/qgenericmatrix.cpp \ + math3d/qmatrix4x4.cpp \ + math3d/qquaternion.cpp \ + math3d/qvector2d.cpp \ + math3d/qvector3d.cpp \ + math3d/qvector4d.cpp diff --git a/src/gui/math3d/qgenericmatrix.cpp b/src/gui/math3d/qgenericmatrix.cpp new file mode 100644 index 0000000000..922f5e0045 --- /dev/null +++ b/src/gui/math3d/qgenericmatrix.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgenericmatrix.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGenericMatrix + \brief The QGenericMatrix class is a template class that represents a NxM transformation matrix with N columns and M rows. + \since 4.6 + \ingroup painting + \ingroup painting-3D + + The QGenericMatrix template has three parameters: + + \table + \row \i N \i Number of columns. + \row \i M \i Number of rows. + \row \i T \i Element type that is visible to users of the class. + \endtable + + \sa QMatrix4x4 +*/ + +/*! + \fn QGenericMatrix::QGenericMatrix() + + Constructs a NxM identity matrix. +*/ + +/*! + \fn QGenericMatrix::QGenericMatrix(const QGenericMatrix<N, M, T>& other) + + Constructs a copy of \a other. +*/ + +/*! + \fn QGenericMatrix::QGenericMatrix(const T *values) + + Constructs a matrix from the given N * M floating-point \a values. + The contents of the array \a values is assumed to be in + row-major order. + + \sa copyDataTo() +*/ + +/*! + \fn const T& QGenericMatrix::operator()(int row, int column) const + + Returns a constant reference to the element at position + (\a row, \a column) in this matrix. +*/ + +/*! + \fn T& QGenericMatrix::operator()(int row, int column) + + Returns a reference to the element at position (\a row, \a column) + in this matrix so that the element can be assigned to. +*/ + +/*! + \fn bool QGenericMatrix::isIdentity() const + + Returns true if this matrix is the identity; false otherwise. + + \sa setToIdentity() +*/ + +/*! + \fn void QGenericMatrix::setToIdentity() + + Sets this matrix to the identity. + + \sa isIdentity() +*/ + +/*! + \fn void QGenericMatrix::fill(T value) + + Fills all elements of this matrix with \a value. +*/ + +/*! + \fn QGenericMatrix<M, N> QGenericMatrix::transposed() const + + Returns this matrix, transposed about its diagonal. +*/ + +/*! + \fn QGenericMatrix<N, M, T>& QGenericMatrix::operator+=(const QGenericMatrix<N, M, T>& other) + + Adds the contents of \a other to this matrix. +*/ + +/*! + \fn QGenericMatrix<N, M, T>& QGenericMatrix::operator-=(const QGenericMatrix<N, M, T>& other) + + Subtracts the contents of \a other from this matrix. +*/ + +/*! + \fn QGenericMatrix<N, M, T>& QGenericMatrix::operator*=(T factor) + + Multiplies all elements of this matrix by \a factor. +*/ + +/*! + \fn QGenericMatrix<N, M, T>& QGenericMatrix::operator/=(T divisor) + + Divides all elements of this matrix by \a divisor. +*/ + +/*! + \fn bool QGenericMatrix::operator==(const QGenericMatrix<N, M, T>& other) const + + Returns true if this matrix is identical to \a other; false otherwise. +*/ + +/*! + \fn bool QGenericMatrix::operator!=(const QGenericMatrix<N, M, T>& other) const + + Returns true if this matrix is not identical to \a other; false otherwise. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator+(const QGenericMatrix<N, M, T>& m1, const QGenericMatrix<N, M, T>& m2) + \relates QGenericMatrix + + Returns the sum of \a m1 and \a m2. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator-(const QGenericMatrix<N, M, T>& m1, const QGenericMatrix<N, M, T>& m2) + \relates QGenericMatrix + + Returns the difference of \a m1 and \a m2. +*/ + +/*! + \fn QGenericMatrix<M1, M2, T> operator*(const QGenericMatrix<N, M2, T>& m1, const QGenericMatrix<M1, N, T>& m2) + \relates QGenericMatrix + + Returns the product of the NxM2 matrix \a m1 and the M1xN matrix \a m2 + to produce a M1xM2 matrix result. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator-(const QGenericMatrix<N, M, T>& matrix) + \overload + \relates QGenericMatrix + + Returns the negation of \a matrix. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator*(T factor, const QGenericMatrix<N, M, T>& matrix) + \relates QGenericMatrix + + Returns the result of multiplying all elements of \a matrix by \a factor. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator*(const QGenericMatrix<N, M, T>& matrix, T factor) + \relates QGenericMatrix + + Returns the result of multiplying all elements of \a matrix by \a factor. +*/ + +/*! + \fn QGenericMatrix<N, M, T> operator/(const QGenericMatrix<N, M, T>& matrix, T divisor) + \relates QGenericMatrix + + Returns the result of dividing all elements of \a matrix by \a divisor. +*/ + +/*! + \fn void QGenericMatrix::copyDataTo(T *values) const + + Retrieves the N * M items in this matrix and copies them to \a values + in row-major order. +*/ + +/*! + \fn T *QGenericMatrix::data() + + Returns a pointer to the raw data of this matrix. + + \sa constData() +*/ + +/*! + \fn const T *QGenericMatrix::data() const + + Returns a constant pointer to the raw data of this matrix. + + \sa constData() +*/ + +/*! + \fn const T *QGenericMatrix::constData() const + + Returns a constant pointer to the raw data of this matrix. + + \sa data() +*/ + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QGenericMatrix<N, M, T> &matrix) + \relates QGenericMatrix + + Writes the given \a matrix to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QGenericMatrix<N, M, T> &matrix) + \relates QGenericMatrix + + Reads a NxM matrix from the given \a stream into the given \a matrix + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +#endif + +/*! + \typedef QMatrix2x2 + \relates QGenericMatrix + + The QMatrix2x2 type defines a convenient instantiation of the + QGenericMatrix template for 2 columns, 2 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix2x3 + \relates QGenericMatrix + + The QMatrix2x3 type defines a convenient instantiation of the + QGenericMatrix template for 2 columns, 3 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix2x4 + \relates QGenericMatrix + + The QMatrix2x4 type defines a convenient instantiation of the + QGenericMatrix template for 2 columns, 4 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix3x2 + \relates QGenericMatrix + + The QMatrix3x2 type defines a convenient instantiation of the + QGenericMatrix template for 3 columns, 2 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix3x3 + \relates QGenericMatrix + + The QMatrix3x3 type defines a convenient instantiation of the + QGenericMatrix template for 3 columns, 3 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix3x4 + \relates QGenericMatrix + + The QMatrix3x4 type defines a convenient instantiation of the + QGenericMatrix template for 3 columns, 4 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix4x2 + \relates QGenericMatrix + + The QMatrix4x2 type defines a convenient instantiation of the + QGenericMatrix template for 4 columns, 2 rows, and qreal as + the element type. +*/ + +/*! + \typedef QMatrix4x3 + \relates QGenericMatrix + + The QMatrix4x3 type defines a convenient instantiation of the + QGenericMatrix template for 4 columns, 3 rows, and qreal as + the element type. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qgenericmatrix.h b/src/gui/math3d/qgenericmatrix.h new file mode 100644 index 0000000000..181eda8e27 --- /dev/null +++ b/src/gui/math3d/qgenericmatrix.h @@ -0,0 +1,407 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGENERICMATRIX_H +#define QGENERICMATRIX_H + +#include <QtCore/qmetatype.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdatastream.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +template <int N, int M, typename T> +class QGenericMatrix +{ +public: + QGenericMatrix(); + QGenericMatrix(const QGenericMatrix<N, M, T>& other); + explicit QGenericMatrix(const T *values); + + const T& operator()(int row, int column) const; + T& operator()(int row, int column); + + bool isIdentity() const; + void setToIdentity(); + + void fill(T value); + + QGenericMatrix<M, N, T> transposed() const; + + QGenericMatrix<N, M, T>& operator+=(const QGenericMatrix<N, M, T>& other); + QGenericMatrix<N, M, T>& operator-=(const QGenericMatrix<N, M, T>& other); + QGenericMatrix<N, M, T>& operator*=(T factor); + QGenericMatrix<N, M, T>& operator/=(T divisor); + bool operator==(const QGenericMatrix<N, M, T>& other) const; + bool operator!=(const QGenericMatrix<N, M, T>& other) const; + + void copyDataTo(T *values) const; + + T *data() { return *m; } + const T *data() const { return *m; } + const T *constData() const { return *m; } + +#if !defined(Q_NO_TEMPLATE_FRIENDS) + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator+(const QGenericMatrix<NN, MM, TT>& m1, const QGenericMatrix<NN, MM, TT>& m2); + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator-(const QGenericMatrix<NN, MM, TT>& m1, const QGenericMatrix<NN, MM, TT>& m2); + template<int NN, int M1, int M2, typename TT> + friend QGenericMatrix<M1, M2, TT> operator*(const QGenericMatrix<NN, M2, TT>& m1, const QGenericMatrix<M1, NN, TT>& m2); + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator-(const QGenericMatrix<NN, MM, TT>& matrix); + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator*(TT factor, const QGenericMatrix<NN, MM, TT>& matrix); + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator*(const QGenericMatrix<NN, MM, TT>& matrix, TT factor); + template<int NN, int MM, typename TT> + friend QGenericMatrix<NN, MM, TT> operator/(const QGenericMatrix<NN, MM, TT>& matrix, TT divisor); + +private: +#endif + T m[N][M]; // Column-major order to match OpenGL. + + QGenericMatrix(int) {} // Construct without initializing identity matrix. + +#if !defined(Q_NO_TEMPLATE_FRIENDS) + template <int NN, int MM, typename TT> + friend class QGenericMatrix; +#endif +}; + +template <int N, int M, typename T> +Q_INLINE_TEMPLATE QGenericMatrix<N, M, T>::QGenericMatrix() +{ + setToIdentity(); +} + +template <int N, int M, typename T> +Q_INLINE_TEMPLATE QGenericMatrix<N, M, T>::QGenericMatrix(const QGenericMatrix<N, M, T>& other) +{ + for (int col = 0; col < N; ++col) + for (int row = 0; row < M; ++row) + m[col][row] = other.m[col][row]; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T>::QGenericMatrix(const T *values) +{ + for (int col = 0; col < N; ++col) + for (int row = 0; row < M; ++row) + m[col][row] = values[row * N + col]; +} + +template <int N, int M, typename T> +Q_INLINE_TEMPLATE const T& QGenericMatrix<N, M, T>::operator()(int row, int column) const +{ + Q_ASSERT(row >= 0 && row < M && column >= 0 && column < N); + return m[column][row]; +} + +template <int N, int M, typename T> +Q_INLINE_TEMPLATE T& QGenericMatrix<N, M, T>::operator()(int row, int column) +{ + Q_ASSERT(row >= 0 && row < M && column >= 0 && column < N); + return m[column][row]; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE bool QGenericMatrix<N, M, T>::isIdentity() const +{ + for (int col = 0; col < N; ++col) { + for (int row = 0; row < M; ++row) { + if (row == col) { + if (m[col][row] != 1.0f) + return false; + } else { + if (m[col][row] != 0.0f) + return false; + } + } + } + return true; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE void QGenericMatrix<N, M, T>::setToIdentity() +{ + for (int col = 0; col < N; ++col) { + for (int row = 0; row < M; ++row) { + if (row == col) + m[col][row] = 1.0f; + else + m[col][row] = 0.0f; + } + } +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE void QGenericMatrix<N, M, T>::fill(T value) +{ + for (int col = 0; col < N; ++col) + for (int row = 0; row < M; ++row) + m[col][row] = value; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<M, N, T> QGenericMatrix<N, M, T>::transposed() const +{ + QGenericMatrix<M, N, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[row][col] = m[col][row]; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T>& QGenericMatrix<N, M, T>::operator+=(const QGenericMatrix<N, M, T>& other) +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + m[col][row] += other.m[col][row]; + return *this; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T>& QGenericMatrix<N, M, T>::operator-=(const QGenericMatrix<N, M, T>& other) +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + m[col][row] -= other.m[col][row]; + return *this; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T>& QGenericMatrix<N, M, T>::operator*=(T factor) +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + m[col][row] *= factor; + return *this; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE bool QGenericMatrix<N, M, T>::operator==(const QGenericMatrix<N, M, T>& other) const +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) { + if (m[col][row] != other.m[col][row]) + return false; + } + return true; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE bool QGenericMatrix<N, M, T>::operator!=(const QGenericMatrix<N, M, T>& other) const +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) { + if (m[col][row] != other.m[col][row]) + return true; + } + return false; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T>& QGenericMatrix<N, M, T>::operator/=(T divisor) +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + m[col][row] /= divisor; + return *this; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator+(const QGenericMatrix<N, M, T>& m1, const QGenericMatrix<N, M, T>& m2) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = m1.m[col][row] + m2.m[col][row]; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator-(const QGenericMatrix<N, M, T>& m1, const QGenericMatrix<N, M, T>& m2) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = m1.m[col][row] - m2.m[col][row]; + return result; +} + +template <int N, int M1, int M2, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<M1, M2, T> operator*(const QGenericMatrix<N, M2, T>& m1, const QGenericMatrix<M1, N, T>& m2) +{ + QGenericMatrix<M1, M2, T> result(1); + for (int row = 0; row < M2; ++row) { + for (int col = 0; col < M1; ++col) { + T sum(0.0f); + for (int j = 0; j < N; ++j) + sum += m1.m[j][row] * m2.m[col][j]; + result.m[col][row] = sum; + } + } + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator-(const QGenericMatrix<N, M, T>& matrix) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = -matrix.m[col][row]; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator*(T factor, const QGenericMatrix<N, M, T>& matrix) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = matrix.m[col][row] * factor; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator*(const QGenericMatrix<N, M, T>& matrix, T factor) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = matrix.m[col][row] * factor; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE QGenericMatrix<N, M, T> operator/(const QGenericMatrix<N, M, T>& matrix, T divisor) +{ + QGenericMatrix<N, M, T> result(1); + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + result.m[col][row] = matrix.m[col][row] / divisor; + return result; +} + +template <int N, int M, typename T> +Q_OUTOFLINE_TEMPLATE void QGenericMatrix<N, M, T>::copyDataTo(T *values) const +{ + for (int col = 0; col < N; ++col) + for (int row = 0; row < M; ++row) + values[row * N + col] = T(m[col][row]); +} + +// Define aliases for the useful variants of QGenericMatrix. +typedef QGenericMatrix<2, 2, qreal> QMatrix2x2; +typedef QGenericMatrix<2, 3, qreal> QMatrix2x3; +typedef QGenericMatrix<2, 4, qreal> QMatrix2x4; +typedef QGenericMatrix<3, 2, qreal> QMatrix3x2; +typedef QGenericMatrix<3, 3, qreal> QMatrix3x3; +typedef QGenericMatrix<3, 4, qreal> QMatrix3x4; +typedef QGenericMatrix<4, 2, qreal> QMatrix4x2; +typedef QGenericMatrix<4, 3, qreal> QMatrix4x3; + +#ifndef QT_NO_DEBUG_STREAM + +template <int N, int M, typename T> +QDebug operator<<(QDebug dbg, const QGenericMatrix<N, M, T> &m) +{ + dbg.nospace() << "QGenericMatrix<" << N << ", " << M + << ", " << QTypeInfo<T>::name() + << ">(" << endl << qSetFieldWidth(10); + for (int row = 0; row < M; ++row) { + for (int col = 0; col < N; ++col) + dbg << m(row, col); + dbg << endl; + } + dbg << qSetFieldWidth(0) << ')'; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +template <int N, int M, typename T> +QDataStream &operator<<(QDataStream &stream, const QGenericMatrix<N, M, T> &matrix) +{ + for (int row = 0; row < M; ++row) + for (int col = 0; col < N; ++col) + stream << double(matrix(row, col)); + return stream; +} + +template <int N, int M, typename T> +QDataStream &operator>>(QDataStream &stream, QGenericMatrix<N, M, T> &matrix) +{ + double x; + for (int row = 0; row < M; ++row) { + for (int col = 0; col < N; ++col) { + stream >> x; + matrix(row, col) = T(x); + } + } + return stream; +} + +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QMatrix2x2) +Q_DECLARE_METATYPE(QMatrix2x3) +Q_DECLARE_METATYPE(QMatrix2x4) +Q_DECLARE_METATYPE(QMatrix3x2) +Q_DECLARE_METATYPE(QMatrix3x3) +Q_DECLARE_METATYPE(QMatrix3x4) +Q_DECLARE_METATYPE(QMatrix4x2) +Q_DECLARE_METATYPE(QMatrix4x3) + +QT_END_HEADER + +#endif diff --git a/src/gui/math3d/qmatrix4x4.cpp b/src/gui/math3d/qmatrix4x4.cpp new file mode 100644 index 0000000000..c18dd3c29d --- /dev/null +++ b/src/gui/math3d/qmatrix4x4.cpp @@ -0,0 +1,1915 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmatrix4x4.h" +#include <QtCore/qmath.h> +#include <QtCore/qvariant.h> +#include <QtGui/qmatrix.h> +#include <QtGui/qtransform.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_MATRIX4X4 + +/*! + \class QMatrix4x4 + \brief The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space. + \since 4.6 + \ingroup painting-3D + + \sa QVector3D, QGenericMatrix +*/ + +static const qreal inv_dist_to_plane = 1. / 1024.; + +/*! + \fn QMatrix4x4::QMatrix4x4() + + Constructs an identity matrix. +*/ + +/*! + Constructs a matrix from the given 16 floating-point \a values. + The contents of the array \a values is assumed to be in + row-major order. + + If the matrix has a special type (identity, translate, scale, etc), + the programmer should follow this constructor with a call to + optimize() if they wish QMatrix4x4 to optimize further + calls to translate(), scale(), etc. + + \sa copyDataTo(), optimize() +*/ +QMatrix4x4::QMatrix4x4(const qreal *values) +{ + for (int row = 0; row < 4; ++row) + for (int col = 0; col < 4; ++col) + m[col][row] = values[row * 4 + col]; + flagBits = General; +} + +/*! + \fn QMatrix4x4::QMatrix4x4(qreal m11, qreal m12, qreal m13, qreal m14, qreal m21, qreal m22, qreal m23, qreal m24, qreal m31, qreal m32, qreal m33, qreal m34, qreal m41, qreal m42, qreal m43, qreal m44) + + Constructs a matrix from the 16 elements \a m11, \a m12, \a m13, \a m14, + \a m21, \a m22, \a m23, \a m24, \a m31, \a m32, \a m33, \a m34, + \a m41, \a m42, \a m43, and \a m44. The elements are specified in + row-major order. + + If the matrix has a special type (identity, translate, scale, etc), + the programmer should follow this constructor with a call to + optimize() if they wish QMatrix4x4 to optimize further + calls to translate(), scale(), etc. + + \sa optimize() +*/ + +/*! + \fn QMatrix4x4::QMatrix4x4(const QGenericMatrix<N, M, qreal>& matrix) + + Constructs a 4x4 matrix from the left-most 4 columns and top-most + 4 rows of \a matrix. If \a matrix has less than 4 columns or rows, + the remaining elements are filled with elements from the identity + matrix. + + \sa toGenericMatrix() +*/ + +/*! + \fn QGenericMatrix<N, M, qreal> QMatrix4x4::toGenericMatrix() const + + Constructs a NxM generic matrix from the left-most N columns and + top-most M rows of this 4x4 matrix. If N or M is greater than 4, + then the remaining elements are filled with elements from the + identity matrix. +*/ + +/*! + \fn QMatrix4x4 qGenericMatrixToMatrix4x4(const QGenericMatrix<N, M, qreal>& matrix) + \relates QMatrix4x4 + \obsolete + + Returns a 4x4 matrix constructed from the left-most 4 columns and + top-most 4 rows of \a matrix. If \a matrix has less than 4 columns + or rows, the remaining elements are filled with elements from the + identity matrix. + + \sa QMatrix4x4(const QGenericMatrix &) +*/ + +/*! + \fn QGenericMatrix<N, M, qreal> qGenericMatrixFromMatrix4x4(const QMatrix4x4& matrix) + \relates QMatrix4x4 + \obsolete + + Returns a NxM generic matrix constructed from the left-most N columns + and top-most M rows of \a matrix. If N or M is greater than 4, + then the remaining elements are filled with elements from the + identity matrix. + + \sa QMatrix4x4::toGenericMatrix() +*/ + +/*! + \internal +*/ +QMatrix4x4::QMatrix4x4(const qreal *values, int cols, int rows) +{ + for (int col = 0; col < 4; ++col) { + for (int row = 0; row < 4; ++row) { + if (col < cols && row < rows) + m[col][row] = values[col * rows + row]; + else if (col == row) + m[col][row] = 1.0f; + else + m[col][row] = 0.0f; + } + } + flagBits = General; +} + +/*! + Constructs a 4x4 matrix from a conventional Qt 2D affine + transformation \a matrix. + + If \a matrix has a special type (identity, translate, scale, etc), + the programmer should follow this constructor with a call to + optimize() if they wish QMatrix4x4 to optimize further + calls to translate(), scale(), etc. + + \sa toAffine(), optimize() +*/ +QMatrix4x4::QMatrix4x4(const QMatrix& matrix) +{ + m[0][0] = matrix.m11(); + m[0][1] = matrix.m12(); + m[0][2] = 0.0f; + m[0][3] = 0.0f; + m[1][0] = matrix.m21(); + m[1][1] = matrix.m22(); + m[1][2] = 0.0f; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + m[3][0] = matrix.dx(); + m[3][1] = matrix.dy(); + m[3][2] = 0.0f; + m[3][3] = 1.0f; + flagBits = General; +} + +/*! + Constructs a 4x4 matrix from the conventional Qt 2D + transformation matrix \a transform. + + If \a transform has a special type (identity, translate, scale, etc), + the programmer should follow this constructor with a call to + optimize() if they wish QMatrix4x4 to optimize further + calls to translate(), scale(), etc. + + \sa toTransform(), optimize() +*/ +QMatrix4x4::QMatrix4x4(const QTransform& transform) +{ + m[0][0] = transform.m11(); + m[0][1] = transform.m12(); + m[0][2] = 0.0f; + m[0][3] = transform.m13(); + m[1][0] = transform.m21(); + m[1][1] = transform.m22(); + m[1][2] = 0.0f; + m[1][3] = transform.m23(); + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + m[3][0] = transform.dx(); + m[3][1] = transform.dy(); + m[3][2] = 0.0f; + m[3][3] = transform.m33(); + flagBits = General; +} + +/*! + \fn const qreal& QMatrix4x4::operator()(int row, int column) const + + Returns a constant reference to the element at position + (\a row, \a column) in this matrix. + + \sa column(), row() +*/ + +/*! + \fn qreal& QMatrix4x4::operator()(int row, int column) + + Returns a reference to the element at position (\a row, \a column) + in this matrix so that the element can be assigned to. + + \sa optimize(), setColumn(), setRow() +*/ + +/*! + \fn QVector4D QMatrix4x4::column(int index) const + + Returns the elements of column \a index as a 4D vector. + + \sa setColumn(), row() +*/ + +/*! + \fn void QMatrix4x4::setColumn(int index, const QVector4D& value) + + Sets the elements of column \a index to the components of \a value. + + \sa column(), setRow() +*/ + +/*! + \fn QVector4D QMatrix4x4::row(int index) const + + Returns the elements of row \a index as a 4D vector. + + \sa setRow(), column() +*/ + +/*! + \fn void QMatrix4x4::setRow(int index, const QVector4D& value) + + Sets the elements of row \a index to the components of \a value. + + \sa row(), setColumn() +*/ + +/*! + \fn bool QMatrix4x4::isIdentity() const + + Returns true if this matrix is the identity; false otherwise. + + \sa setToIdentity() +*/ + +/*! + \fn void QMatrix4x4::setToIdentity() + + Sets this matrix to the identity. + + \sa isIdentity() +*/ + +/*! + \fn void QMatrix4x4::fill(qreal value) + + Fills all elements of this matrx with \a value. +*/ + +// The 4x4 matrix inverse algorithm is based on that described at: +// http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q24 +// Some optimization has been done to avoid making copies of 3x3 +// sub-matrices and to unroll the loops. + +// Calculate the determinant of a 3x3 sub-matrix. +// | A B C | +// M = | D E F | det(M) = A * (EI - HF) - B * (DI - GF) + C * (DH - GE) +// | G H I | +static inline qreal matrixDet3 + (const qreal m[4][4], int col0, int col1, int col2, + int row0, int row1, int row2) +{ + return m[col0][row0] * + (m[col1][row1] * m[col2][row2] - + m[col1][row2] * m[col2][row1]) - + m[col1][row0] * + (m[col0][row1] * m[col2][row2] - + m[col0][row2] * m[col2][row1]) + + m[col2][row0] * + (m[col0][row1] * m[col1][row2] - + m[col0][row2] * m[col1][row1]); +} + +// Calculate the determinant of a 4x4 matrix. +static inline qreal matrixDet4(const qreal m[4][4]) +{ + qreal det; + det = m[0][0] * matrixDet3(m, 1, 2, 3, 1, 2, 3); + det -= m[1][0] * matrixDet3(m, 0, 2, 3, 1, 2, 3); + det += m[2][0] * matrixDet3(m, 0, 1, 3, 1, 2, 3); + det -= m[3][0] * matrixDet3(m, 0, 1, 2, 1, 2, 3); + return det; +} + +/*! + Returns the determinant of this matrix. +*/ +qreal QMatrix4x4::determinant() const +{ + return qreal(matrixDet4(m)); +} + +/*! + Returns the inverse of this matrix. Returns the identity if + this matrix cannot be inverted; i.e. determinant() is zero. + If \a invertible is not null, then true will be written to + that location if the matrix can be inverted; false otherwise. + + If the matrix is recognized as the identity or an orthonormal + matrix, then this function will quickly invert the matrix + using optimized routines. + + \sa determinant(), normalMatrix() +*/ +QMatrix4x4 QMatrix4x4::inverted(bool *invertible) const +{ + // Handle some of the easy cases first. + if (flagBits == Identity) { + if (invertible) + *invertible = true; + return QMatrix4x4(); + } else if (flagBits == Translation) { + QMatrix4x4 inv; + inv.m[3][0] = -m[3][0]; + inv.m[3][1] = -m[3][1]; + inv.m[3][2] = -m[3][2]; + inv.flagBits = Translation; + if (invertible) + *invertible = true; + return inv; + } else if (flagBits == Rotation || flagBits == (Rotation | Translation)) { + if (invertible) + *invertible = true; + return orthonormalInverse(); + } + + QMatrix4x4 inv(1); // The "1" says to not load the identity. + + qreal det = matrixDet4(m); + if (det == 0.0f) { + if (invertible) + *invertible = false; + return QMatrix4x4(); + } + det = 1.0f / det; + + inv.m[0][0] = matrixDet3(m, 1, 2, 3, 1, 2, 3) * det; + inv.m[0][1] = -matrixDet3(m, 0, 2, 3, 1, 2, 3) * det; + inv.m[0][2] = matrixDet3(m, 0, 1, 3, 1, 2, 3) * det; + inv.m[0][3] = -matrixDet3(m, 0, 1, 2, 1, 2, 3) * det; + inv.m[1][0] = -matrixDet3(m, 1, 2, 3, 0, 2, 3) * det; + inv.m[1][1] = matrixDet3(m, 0, 2, 3, 0, 2, 3) * det; + inv.m[1][2] = -matrixDet3(m, 0, 1, 3, 0, 2, 3) * det; + inv.m[1][3] = matrixDet3(m, 0, 1, 2, 0, 2, 3) * det; + inv.m[2][0] = matrixDet3(m, 1, 2, 3, 0, 1, 3) * det; + inv.m[2][1] = -matrixDet3(m, 0, 2, 3, 0, 1, 3) * det; + inv.m[2][2] = matrixDet3(m, 0, 1, 3, 0, 1, 3) * det; + inv.m[2][3] = -matrixDet3(m, 0, 1, 2, 0, 1, 3) * det; + inv.m[3][0] = -matrixDet3(m, 1, 2, 3, 0, 1, 2) * det; + inv.m[3][1] = matrixDet3(m, 0, 2, 3, 0, 1, 2) * det; + inv.m[3][2] = -matrixDet3(m, 0, 1, 3, 0, 1, 2) * det; + inv.m[3][3] = matrixDet3(m, 0, 1, 2, 0, 1, 2) * det; + + if (invertible) + *invertible = true; + return inv; +} + +/*! + Returns the normal matrix corresponding to this 4x4 transformation. + The normal matrix is the transpose of the inverse of the top-left + 3x3 part of this 4x4 matrix. If the 3x3 sub-matrix is not invertible, + this function returns the identity. + + \sa inverted() +*/ +QMatrix3x3 QMatrix4x4::normalMatrix() const +{ + QMatrix3x3 inv; + + // Handle the simple cases first. + if (flagBits == Identity || flagBits == Translation) { + return inv; + } else if (flagBits == Scale || flagBits == (Translation | Scale)) { + if (m[0][0] == 0.0f || m[1][1] == 0.0f || m[2][2] == 0.0f) + return inv; + inv.data()[0] = 1.0f / m[0][0]; + inv.data()[4] = 1.0f / m[1][1]; + inv.data()[8] = 1.0f / m[2][2]; + return inv; + } + + qreal det = matrixDet3(m, 0, 1, 2, 0, 1, 2); + if (det == 0.0f) + return inv; + det = 1.0f / det; + + qreal *invm = inv.data(); + + // Invert and transpose in a single step. + invm[0 + 0 * 3] = (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * det; + invm[1 + 0 * 3] = -(m[1][0] * m[2][2] - m[1][2] * m[2][0]) * det; + invm[2 + 0 * 3] = (m[1][0] * m[2][1] - m[1][1] * m[2][0]) * det; + invm[0 + 1 * 3] = -(m[0][1] * m[2][2] - m[2][1] * m[0][2]) * det; + invm[1 + 1 * 3] = (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * det; + invm[2 + 1 * 3] = -(m[0][0] * m[2][1] - m[0][1] * m[2][0]) * det; + invm[0 + 2 * 3] = (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * det; + invm[1 + 2 * 3] = -(m[0][0] * m[1][2] - m[0][2] * m[1][0]) * det; + invm[2 + 2 * 3] = (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * det; + + return inv; +} + +/*! + Returns this matrix, transposed about its diagonal. +*/ +QMatrix4x4 QMatrix4x4::transposed() const +{ + QMatrix4x4 result(1); // The "1" says to not load the identity. + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + result.m[col][row] = m[row][col]; + } + } + return result; +} + +/*! + \fn QMatrix4x4& QMatrix4x4::operator+=(const QMatrix4x4& other) + + Adds the contents of \a other to this matrix. +*/ + +/*! + \fn QMatrix4x4& QMatrix4x4::operator-=(const QMatrix4x4& other) + + Subtracts the contents of \a other from this matrix. +*/ + +/*! + \fn QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other) + + Multiplies the contents of \a other by this matrix. +*/ + +/*! + \fn QMatrix4x4& QMatrix4x4::operator*=(qreal factor) + \overload + + Multiplies all elements of this matrix by \a factor. +*/ + +/*! + \overload + + Divides all elements of this matrix by \a divisor. +*/ +QMatrix4x4& QMatrix4x4::operator/=(qreal divisor) +{ + m[0][0] /= divisor; + m[0][1] /= divisor; + m[0][2] /= divisor; + m[0][3] /= divisor; + m[1][0] /= divisor; + m[1][1] /= divisor; + m[1][2] /= divisor; + m[1][3] /= divisor; + m[2][0] /= divisor; + m[2][1] /= divisor; + m[2][2] /= divisor; + m[2][3] /= divisor; + m[3][0] /= divisor; + m[3][1] /= divisor; + m[3][2] /= divisor; + m[3][3] /= divisor; + flagBits = General; + return *this; +} + +/*! + \fn bool QMatrix4x4::operator==(const QMatrix4x4& other) const + + Returns true if this matrix is identical to \a other; false otherwise. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn bool QMatrix4x4::operator!=(const QMatrix4x4& other) const + + Returns true if this matrix is not identical to \a other; false otherwise. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn QMatrix4x4 operator+(const QMatrix4x4& m1, const QMatrix4x4& m2) + \relates QMatrix4x4 + + Returns the sum of \a m1 and \a m2. +*/ + +/*! + \fn QMatrix4x4 operator-(const QMatrix4x4& m1, const QMatrix4x4& m2) + \relates QMatrix4x4 + + Returns the difference of \a m1 and \a m2. +*/ + +/*! + \fn QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2) + \relates QMatrix4x4 + + Returns the product of \a m1 and \a m2. +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + \fn QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix) + \relates QMatrix4x4 + + Returns the result of transforming \a vector according to \a matrix, + with the matrix applied post-vector. +*/ + +/*! + \fn QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector) + \relates QMatrix4x4 + + Returns the result of transforming \a vector according to \a matrix, + with the matrix applied pre-vector. +*/ + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + \fn QVector4D operator*(const QVector4D& vector, const QMatrix4x4& matrix) + \relates QMatrix4x4 + + Returns the result of transforming \a vector according to \a matrix, + with the matrix applied post-vector. +*/ + +/*! + \fn QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector) + \relates QMatrix4x4 + + Returns the result of transforming \a vector according to \a matrix, + with the matrix applied pre-vector. +*/ + +#endif + +/*! + \fn QPoint operator*(const QPoint& point, const QMatrix4x4& matrix) + \relates QMatrix4x4 + + Returns the result of transforming \a point according to \a matrix, + with the matrix applied post-point. +*/ + +/*! + \fn QPointF operator*(const QPointF& point, const QMatrix4x4& matrix) + \relates QMatrix4x4 + + Returns the result of transforming \a point according to \a matrix, + with the matrix applied post-point. +*/ + +/*! + \fn QPoint operator*(const QMatrix4x4& matrix, const QPoint& point) + \relates QMatrix4x4 + + Returns the result of transforming \a point according to \a matrix, + with the matrix applied pre-point. +*/ + +/*! + \fn QPointF operator*(const QMatrix4x4& matrix, const QPointF& point) + \relates QMatrix4x4 + + Returns the result of transforming \a point according to \a matrix, + with the matrix applied pre-point. +*/ + +/*! + \fn QMatrix4x4 operator-(const QMatrix4x4& matrix) + \overload + \relates QMatrix4x4 + + Returns the negation of \a matrix. +*/ + +/*! + \fn QMatrix4x4 operator*(qreal factor, const QMatrix4x4& matrix) + \relates QMatrix4x4 + + Returns the result of multiplying all elements of \a matrix by \a factor. +*/ + +/*! + \fn QMatrix4x4 operator*(const QMatrix4x4& matrix, qreal factor) + \relates QMatrix4x4 + + Returns the result of multiplying all elements of \a matrix by \a factor. +*/ + +/*! + \relates QMatrix4x4 + + Returns the result of dividing all elements of \a matrix by \a divisor. +*/ +QMatrix4x4 operator/(const QMatrix4x4& matrix, qreal divisor) +{ + QMatrix4x4 m(1); // The "1" says to not load the identity. + m.m[0][0] = matrix.m[0][0] / divisor; + m.m[0][1] = matrix.m[0][1] / divisor; + m.m[0][2] = matrix.m[0][2] / divisor; + m.m[0][3] = matrix.m[0][3] / divisor; + m.m[1][0] = matrix.m[1][0] / divisor; + m.m[1][1] = matrix.m[1][1] / divisor; + m.m[1][2] = matrix.m[1][2] / divisor; + m.m[1][3] = matrix.m[1][3] / divisor; + m.m[2][0] = matrix.m[2][0] / divisor; + m.m[2][1] = matrix.m[2][1] / divisor; + m.m[2][2] = matrix.m[2][2] / divisor; + m.m[2][3] = matrix.m[2][3] / divisor; + m.m[3][0] = matrix.m[3][0] / divisor; + m.m[3][1] = matrix.m[3][1] / divisor; + m.m[3][2] = matrix.m[3][2] / divisor; + m.m[3][3] = matrix.m[3][3] / divisor; + return m; +} + +/*! + \fn bool qFuzzyCompare(const QMatrix4x4& m1, const QMatrix4x4& m2) + \relates QMatrix4x4 + + Returns true if \a m1 and \a m2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + Multiplies this matrix by another that scales coordinates by + the components of \a vector. + + \sa translate(), rotate() +*/ +void QMatrix4x4::scale(const QVector3D& vector) +{ + qreal vx = vector.x(); + qreal vy = vector.y(); + qreal vz = vector.z(); + if (flagBits == Identity) { + m[0][0] = vx; + m[1][1] = vy; + m[2][2] = vz; + flagBits = Scale; + } else if (flagBits == Scale || flagBits == (Scale | Translation)) { + m[0][0] *= vx; + m[1][1] *= vy; + m[2][2] *= vz; + } else if (flagBits == Translation) { + m[0][0] = vx; + m[1][1] = vy; + m[2][2] = vz; + flagBits |= Scale; + } else { + m[0][0] *= vx; + m[0][1] *= vx; + m[0][2] *= vx; + m[0][3] *= vx; + m[1][0] *= vy; + m[1][1] *= vy; + m[1][2] *= vy; + m[1][3] *= vy; + m[2][0] *= vz; + m[2][1] *= vz; + m[2][2] *= vz; + m[2][3] *= vz; + flagBits = General; + } +} +#endif + +/*! + \overload + + Multiplies this matrix by another that scales coordinates by the + components \a x, and \a y. + + \sa translate(), rotate() +*/ +void QMatrix4x4::scale(qreal x, qreal y) +{ + if (flagBits == Identity) { + m[0][0] = x; + m[1][1] = y; + flagBits = Scale; + } else if (flagBits == Scale || flagBits == (Scale | Translation)) { + m[0][0] *= x; + m[1][1] *= y; + } else if (flagBits == Translation) { + m[0][0] = x; + m[1][1] = y; + flagBits |= Scale; + } else { + m[0][0] *= x; + m[0][1] *= x; + m[0][2] *= x; + m[0][3] *= x; + m[1][0] *= y; + m[1][1] *= y; + m[1][2] *= y; + m[1][3] *= y; + flagBits = General; + } +} + +/*! + \overload + + Multiplies this matrix by another that scales coordinates by the + components \a x, \a y, and \a z. + + \sa translate(), rotate() +*/ +void QMatrix4x4::scale(qreal x, qreal y, qreal z) +{ + if (flagBits == Identity) { + m[0][0] = x; + m[1][1] = y; + m[2][2] = z; + flagBits = Scale; + } else if (flagBits == Scale || flagBits == (Scale | Translation)) { + m[0][0] *= x; + m[1][1] *= y; + m[2][2] *= z; + } else if (flagBits == Translation) { + m[0][0] = x; + m[1][1] = y; + m[2][2] = z; + flagBits |= Scale; + } else { + m[0][0] *= x; + m[0][1] *= x; + m[0][2] *= x; + m[0][3] *= x; + m[1][0] *= y; + m[1][1] *= y; + m[1][2] *= y; + m[1][3] *= y; + m[2][0] *= z; + m[2][1] *= z; + m[2][2] *= z; + m[2][3] *= z; + flagBits = General; + } +} + +/*! + \overload + + Multiplies this matrix by another that scales coordinates by the + given \a factor. + + \sa translate(), rotate() +*/ +void QMatrix4x4::scale(qreal factor) +{ + if (flagBits == Identity) { + m[0][0] = factor; + m[1][1] = factor; + m[2][2] = factor; + flagBits = Scale; + } else if (flagBits == Scale || flagBits == (Scale | Translation)) { + m[0][0] *= factor; + m[1][1] *= factor; + m[2][2] *= factor; + } else if (flagBits == Translation) { + m[0][0] = factor; + m[1][1] = factor; + m[2][2] = factor; + flagBits |= Scale; + } else { + m[0][0] *= factor; + m[0][1] *= factor; + m[0][2] *= factor; + m[0][3] *= factor; + m[1][0] *= factor; + m[1][1] *= factor; + m[1][2] *= factor; + m[1][3] *= factor; + m[2][0] *= factor; + m[2][1] *= factor; + m[2][2] *= factor; + m[2][3] *= factor; + flagBits = General; + } +} + +#ifndef QT_NO_VECTOR3D +/*! + Multiplies this matrix by another that translates coordinates by + the components of \a vector. + + \sa scale(), rotate() +*/ +void QMatrix4x4::translate(const QVector3D& vector) +{ + qreal vx = vector.x(); + qreal vy = vector.y(); + qreal vz = vector.z(); + if (flagBits == Identity) { + m[3][0] = vx; + m[3][1] = vy; + m[3][2] = vz; + flagBits = Translation; + } else if (flagBits == Translation) { + m[3][0] += vx; + m[3][1] += vy; + m[3][2] += vz; + } else if (flagBits == Scale) { + m[3][0] = m[0][0] * vx; + m[3][1] = m[1][1] * vy; + m[3][2] = m[2][2] * vz; + flagBits |= Translation; + } else if (flagBits == (Scale | Translation)) { + m[3][0] += m[0][0] * vx; + m[3][1] += m[1][1] * vy; + m[3][2] += m[2][2] * vz; + } else { + m[3][0] += m[0][0] * vx + m[1][0] * vy + m[2][0] * vz; + m[3][1] += m[0][1] * vx + m[1][1] * vy + m[2][1] * vz; + m[3][2] += m[0][2] * vx + m[1][2] * vy + m[2][2] * vz; + m[3][3] += m[0][3] * vx + m[1][3] * vy + m[2][3] * vz; + if (flagBits == Rotation) + flagBits |= Translation; + else if (flagBits != (Rotation | Translation)) + flagBits = General; + } +} + +#endif + +/*! + \overload + + Multiplies this matrix by another that translates coordinates + by the components \a x, and \a y. + + \sa scale(), rotate() +*/ +void QMatrix4x4::translate(qreal x, qreal y) +{ + if (flagBits == Identity) { + m[3][0] = x; + m[3][1] = y; + flagBits = Translation; + } else if (flagBits == Translation) { + m[3][0] += x; + m[3][1] += y; + } else if (flagBits == Scale) { + m[3][0] = m[0][0] * x; + m[3][1] = m[1][1] * y; + m[3][2] = 0.; + flagBits |= Translation; + } else if (flagBits == (Scale | Translation)) { + m[3][0] += m[0][0] * x; + m[3][1] += m[1][1] * y; + } else { + m[3][0] += m[0][0] * x + m[1][0] * y; + m[3][1] += m[0][1] * x + m[1][1] * y; + m[3][2] += m[0][2] * x + m[1][2] * y; + m[3][3] += m[0][3] * x + m[1][3] * y; + if (flagBits == Rotation) + flagBits |= Translation; + else if (flagBits != (Rotation | Translation)) + flagBits = General; + } +} + +/*! + \overload + + Multiplies this matrix by another that translates coordinates + by the components \a x, \a y, and \a z. + + \sa scale(), rotate() +*/ +void QMatrix4x4::translate(qreal x, qreal y, qreal z) +{ + if (flagBits == Identity) { + m[3][0] = x; + m[3][1] = y; + m[3][2] = z; + flagBits = Translation; + } else if (flagBits == Translation) { + m[3][0] += x; + m[3][1] += y; + m[3][2] += z; + } else if (flagBits == Scale) { + m[3][0] = m[0][0] * x; + m[3][1] = m[1][1] * y; + m[3][2] = m[2][2] * z; + flagBits |= Translation; + } else if (flagBits == (Scale | Translation)) { + m[3][0] += m[0][0] * x; + m[3][1] += m[1][1] * y; + m[3][2] += m[2][2] * z; + } else { + m[3][0] += m[0][0] * x + m[1][0] * y + m[2][0] * z; + m[3][1] += m[0][1] * x + m[1][1] * y + m[2][1] * z; + m[3][2] += m[0][2] * x + m[1][2] * y + m[2][2] * z; + m[3][3] += m[0][3] * x + m[1][3] * y + m[2][3] * z; + if (flagBits == Rotation) + flagBits |= Translation; + else if (flagBits != (Rotation | Translation)) + flagBits = General; + } +} + +#ifndef QT_NO_VECTOR3D + +/*! + Multiples this matrix by another that rotates coordinates through + \a angle degrees about \a vector. + + \sa scale(), translate() +*/ +void QMatrix4x4::rotate(qreal angle, const QVector3D& vector) +{ + rotate(angle, vector.x(), vector.y(), vector.z()); +} + +#endif + +/*! + \overload + + Multiplies this matrix by another that rotates coordinates through + \a angle degrees about the vector (\a x, \a y, \a z). + + \sa scale(), translate() +*/ +void QMatrix4x4::rotate(qreal angle, qreal x, qreal y, qreal z) +{ + if (angle == 0.0f) + return; + QMatrix4x4 m(1); // The "1" says to not load the identity. + qreal c, s, ic; + if (angle == 90.0f || angle == -270.0f) { + s = 1.0f; + c = 0.0f; + } else if (angle == -90.0f || angle == 270.0f) { + s = -1.0f; + c = 0.0f; + } else if (angle == 180.0f || angle == -180.0f) { + s = 0.0f; + c = -1.0f; + } else { + qreal a = angle * M_PI / 180.0f; + c = qCos(a); + s = qSin(a); + } + bool quick = false; + if (x == 0.0f) { + if (y == 0.0f) { + if (z != 0.0f) { + // Rotate around the Z axis. + m.setToIdentity(); + m.m[0][0] = c; + m.m[1][1] = c; + if (z < 0.0f) { + m.m[1][0] = s; + m.m[0][1] = -s; + } else { + m.m[1][0] = -s; + m.m[0][1] = s; + } + m.flagBits = General; + quick = true; + } + } else if (z == 0.0f) { + // Rotate around the Y axis. + m.setToIdentity(); + m.m[0][0] = c; + m.m[2][2] = c; + if (y < 0.0f) { + m.m[2][0] = -s; + m.m[0][2] = s; + } else { + m.m[2][0] = s; + m.m[0][2] = -s; + } + m.flagBits = General; + quick = true; + } + } else if (y == 0.0f && z == 0.0f) { + // Rotate around the X axis. + m.setToIdentity(); + m.m[1][1] = c; + m.m[2][2] = c; + if (x < 0.0f) { + m.m[2][1] = s; + m.m[1][2] = -s; + } else { + m.m[2][1] = -s; + m.m[1][2] = s; + } + m.flagBits = General; + quick = true; + } + if (!quick) { + qreal len = x * x + y * y + z * z; + if (!qFuzzyIsNull(len - 1.0f) && !qFuzzyIsNull(len)) { + len = qSqrt(len); + x /= len; + y /= len; + z /= len; + } + ic = 1.0f - c; + m.m[0][0] = x * x * ic + c; + m.m[1][0] = x * y * ic - z * s; + m.m[2][0] = x * z * ic + y * s; + m.m[3][0] = 0.0f; + m.m[0][1] = y * x * ic + z * s; + m.m[1][1] = y * y * ic + c; + m.m[2][1] = y * z * ic - x * s; + m.m[3][1] = 0.0f; + m.m[0][2] = x * z * ic - y * s; + m.m[1][2] = y * z * ic + x * s; + m.m[2][2] = z * z * ic + c; + m.m[3][2] = 0.0f; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = 0.0f; + m.m[3][3] = 1.0f; + } + int flags = flagBits; + *this *= m; + if (flags != Identity) + flagBits = flags | Rotation; + else + flagBits = Rotation; +} + +/*! + \internal +*/ +void QMatrix4x4::projectedRotate(qreal angle, qreal x, qreal y, qreal z) +{ + // Used by QGraphicsRotation::applyTo() to perform a rotation + // and projection back to 2D in a single step. + if (angle == 0.0f) + return; + QMatrix4x4 m(1); // The "1" says to not load the identity. + qreal c, s, ic; + if (angle == 90.0f || angle == -270.0f) { + s = 1.0f; + c = 0.0f; + } else if (angle == -90.0f || angle == 270.0f) { + s = -1.0f; + c = 0.0f; + } else if (angle == 180.0f || angle == -180.0f) { + s = 0.0f; + c = -1.0f; + } else { + qreal a = angle * M_PI / 180.0f; + c = qCos(a); + s = qSin(a); + } + bool quick = false; + if (x == 0.0f) { + if (y == 0.0f) { + if (z != 0.0f) { + // Rotate around the Z axis. + m.setToIdentity(); + m.m[0][0] = c; + m.m[1][1] = c; + if (z < 0.0f) { + m.m[1][0] = s; + m.m[0][1] = -s; + } else { + m.m[1][0] = -s; + m.m[0][1] = s; + } + m.flagBits = General; + quick = true; + } + } else if (z == 0.0f) { + // Rotate around the Y axis. + m.setToIdentity(); + m.m[0][0] = c; + m.m[2][2] = 1.0f; + if (y < 0.0f) { + m.m[0][3] = -s * inv_dist_to_plane; + } else { + m.m[0][3] = s * inv_dist_to_plane; + } + m.flagBits = General; + quick = true; + } + } else if (y == 0.0f && z == 0.0f) { + // Rotate around the X axis. + m.setToIdentity(); + m.m[1][1] = c; + m.m[2][2] = 1.0f; + if (x < 0.0f) { + m.m[1][3] = s * inv_dist_to_plane; + } else { + m.m[1][3] = -s * inv_dist_to_plane; + } + m.flagBits = General; + quick = true; + } + if (!quick) { + qreal len = x * x + y * y + z * z; + if (!qFuzzyIsNull(len - 1.0f) && !qFuzzyIsNull(len)) { + len = qSqrt(len); + x /= len; + y /= len; + z /= len; + } + ic = 1.0f - c; + m.m[0][0] = x * x * ic + c; + m.m[1][0] = x * y * ic - z * s; + m.m[2][0] = 0.0f; + m.m[3][0] = 0.0f; + m.m[0][1] = y * x * ic + z * s; + m.m[1][1] = y * y * ic + c; + m.m[2][1] = 0.0f; + m.m[3][1] = 0.0f; + m.m[0][2] = 0.0f; + m.m[1][2] = 0.0f; + m.m[2][2] = 1.0f; + m.m[3][2] = 0.0f; + m.m[0][3] = (x * z * ic - y * s) * -inv_dist_to_plane; + m.m[1][3] = (y * z * ic + x * s) * -inv_dist_to_plane; + m.m[2][3] = 0.0f; + m.m[3][3] = 1.0f; + } + int flags = flagBits; + *this *= m; + if (flags != Identity) + flagBits = flags | Rotation; + else + flagBits = Rotation; +} + +#ifndef QT_NO_QUATERNION + +/*! + Multiples this matrix by another that rotates coordinates according + to a specified \a quaternion. The \a quaternion is assumed to have + been normalized. + + \sa scale(), translate(), QQuaternion +*/ +void QMatrix4x4::rotate(const QQuaternion& quaternion) +{ + // Algorithm from: + // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q54 + QMatrix4x4 m(1); + qreal xx = quaternion.x() * quaternion.x(); + qreal xy = quaternion.x() * quaternion.y(); + qreal xz = quaternion.x() * quaternion.z(); + qreal xw = quaternion.x() * quaternion.scalar(); + qreal yy = quaternion.y() * quaternion.y(); + qreal yz = quaternion.y() * quaternion.z(); + qreal yw = quaternion.y() * quaternion.scalar(); + qreal zz = quaternion.z() * quaternion.z(); + qreal zw = quaternion.z() * quaternion.scalar(); + m.m[0][0] = 1.0f - 2 * (yy + zz); + m.m[1][0] = 2 * (xy - zw); + m.m[2][0] = 2 * (xz + yw); + m.m[3][0] = 0.0f; + m.m[0][1] = 2 * (xy + zw); + m.m[1][1] = 1.0f - 2 * (xx + zz); + m.m[2][1] = 2 * (yz - xw); + m.m[3][1] = 0.0f; + m.m[0][2] = 2 * (xz - yw); + m.m[1][2] = 2 * (yz + xw); + m.m[2][2] = 1.0f - 2 * (xx + yy); + m.m[3][2] = 0.0f; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = 0.0f; + m.m[3][3] = 1.0f; + int flags = flagBits; + *this *= m; + if (flags != Identity) + flagBits = flags | Rotation; + else + flagBits = Rotation; +} + +#endif + +/*! + \overload + + Multiplies this matrix by another that applies an orthographic + projection for a window with boundaries specified by \a rect. + The near and far clipping planes will be -1 and 1 respectively. + + \sa frustum(), perspective() +*/ +void QMatrix4x4::ortho(const QRect& rect) +{ + // Note: rect.right() and rect.bottom() subtract 1 in QRect, + // which gives the location of a pixel within the rectangle, + // instead of the extent of the rectangle. We want the extent. + // QRectF expresses the extent properly. + ortho(rect.x(), rect.x() + rect.width(), rect.y() + rect.height(), rect.y(), -1.0f, 1.0f); +} + +/*! + \overload + + Multiplies this matrix by another that applies an orthographic + projection for a window with boundaries specified by \a rect. + The near and far clipping planes will be -1 and 1 respectively. + + \sa frustum(), perspective() +*/ +void QMatrix4x4::ortho(const QRectF& rect) +{ + ortho(rect.left(), rect.right(), rect.bottom(), rect.top(), -1.0f, 1.0f); +} + +/*! + Multiplies this matrix by another that applies an orthographic + projection for a window with lower-left corner (\a left, \a bottom), + upper-right corner (\a right, \a top), and the specified \a nearPlane + and \a farPlane clipping planes. + + \sa frustum(), perspective() +*/ +void QMatrix4x4::ortho(qreal left, qreal right, qreal bottom, qreal top, qreal nearPlane, qreal farPlane) +{ + // Bail out if the projection volume is zero-sized. + if (left == right || bottom == top || nearPlane == farPlane) + return; + + // Construct the projection. + qreal width = right - left; + qreal invheight = top - bottom; + qreal clip = farPlane - nearPlane; +#ifndef QT_NO_VECTOR3D + if (clip == 2.0f && (nearPlane + farPlane) == 0.0f) { + // We can express this projection as a translate and scale + // which will be more efficient to modify with further + // transformations than producing a "General" matrix. + translate(QVector3D + (-(left + right) / width, + -(top + bottom) / invheight, + 0.0f)); + scale(QVector3D + (2.0f / width, + 2.0f / invheight, + -1.0f)); + return; + } +#endif + QMatrix4x4 m(1); + m.m[0][0] = 2.0f / width; + m.m[1][0] = 0.0f; + m.m[2][0] = 0.0f; + m.m[3][0] = -(left + right) / width; + m.m[0][1] = 0.0f; + m.m[1][1] = 2.0f / invheight; + m.m[2][1] = 0.0f; + m.m[3][1] = -(top + bottom) / invheight; + m.m[0][2] = 0.0f; + m.m[1][2] = 0.0f; + m.m[2][2] = -2.0f / clip; + m.m[3][2] = -(nearPlane + farPlane) / clip; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = 0.0f; + m.m[3][3] = 1.0f; + + // Apply the projection. + *this *= m; + return; +} + +/*! + Multiplies this matrix by another that applies a perspective + frustum projection for a window with lower-left corner (\a left, \a bottom), + upper-right corner (\a right, \a top), and the specified \a nearPlane + and \a farPlane clipping planes. + + \sa ortho(), perspective() +*/ +void QMatrix4x4::frustum(qreal left, qreal right, qreal bottom, qreal top, qreal nearPlane, qreal farPlane) +{ + // Bail out if the projection volume is zero-sized. + if (left == right || bottom == top || nearPlane == farPlane) + return; + + // Construct the projection. + QMatrix4x4 m(1); + qreal width = right - left; + qreal invheight = top - bottom; + qreal clip = farPlane - nearPlane; + m.m[0][0] = 2.0f * nearPlane / width; + m.m[1][0] = 0.0f; + m.m[2][0] = (left + right) / width; + m.m[3][0] = 0.0f; + m.m[0][1] = 0.0f; + m.m[1][1] = 2.0f * nearPlane / invheight; + m.m[2][1] = (top + bottom) / invheight; + m.m[3][1] = 0.0f; + m.m[0][2] = 0.0f; + m.m[1][2] = 0.0f; + m.m[2][2] = -(nearPlane + farPlane) / clip; + m.m[3][2] = -2.0f * nearPlane * farPlane / clip; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = -1.0f; + m.m[3][3] = 0.0f; + + // Apply the projection. + *this *= m; +} + +/*! + Multiplies this matrix by another that applies a perspective + projection. The field of view will be \a angle degrees within + a window with a given \a aspect ratio. The projection will + have the specified \a nearPlane and \a farPlane clipping planes. + + \sa ortho(), frustum() +*/ +void QMatrix4x4::perspective(qreal angle, qreal aspect, qreal nearPlane, qreal farPlane) +{ + // Bail out if the projection volume is zero-sized. + if (nearPlane == farPlane || aspect == 0.0f) + return; + + // Construct the projection. + QMatrix4x4 m(1); + qreal radians = (angle / 2.0f) * M_PI / 180.0f; + qreal sine = qSin(radians); + if (sine == 0.0f) + return; + qreal cotan = qCos(radians) / sine; + qreal clip = farPlane - nearPlane; + m.m[0][0] = cotan / aspect; + m.m[1][0] = 0.0f; + m.m[2][0] = 0.0f; + m.m[3][0] = 0.0f; + m.m[0][1] = 0.0f; + m.m[1][1] = cotan; + m.m[2][1] = 0.0f; + m.m[3][1] = 0.0f; + m.m[0][2] = 0.0f; + m.m[1][2] = 0.0f; + m.m[2][2] = -(nearPlane + farPlane) / clip; + m.m[3][2] = -(2.0f * nearPlane * farPlane) / clip; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = -1.0f; + m.m[3][3] = 0.0f; + + // Apply the projection. + *this *= m; +} + +#ifndef QT_NO_VECTOR3D + +/*! + Multiplies this matrix by another that applies an \a eye position + transformation. The \a center value indicates the center of the + view that the \a eye is looking at. The \a up value indicates + which direction should be considered up with respect to the \a eye. +*/ +void QMatrix4x4::lookAt(const QVector3D& eye, const QVector3D& center, const QVector3D& up) +{ + QVector3D forward = (center - eye).normalized(); + QVector3D side = QVector3D::crossProduct(forward, up).normalized(); + QVector3D upVector = QVector3D::crossProduct(side, forward); + + QMatrix4x4 m(1); + + m.m[0][0] = side.x(); + m.m[1][0] = side.y(); + m.m[2][0] = side.z(); + m.m[3][0] = 0.0f; + m.m[0][1] = upVector.x(); + m.m[1][1] = upVector.y(); + m.m[2][1] = upVector.z(); + m.m[3][1] = 0.0f; + m.m[0][2] = -forward.x(); + m.m[1][2] = -forward.y(); + m.m[2][2] = -forward.z(); + m.m[3][2] = 0.0f; + m.m[0][3] = 0.0f; + m.m[1][3] = 0.0f; + m.m[2][3] = 0.0f; + m.m[3][3] = 1.0f; + + *this *= m; + translate(-eye); +} + +#endif + +/*! + Flips between right-handed and left-handed coordinate systems + by multiplying the y and z co-ordinates by -1. This is normally + used to create a left-handed orthographic view without scaling + the viewport as ortho() does. + + \sa ortho() +*/ +void QMatrix4x4::flipCoordinates() +{ + if (flagBits == Scale || flagBits == (Scale | Translation)) { + m[1][1] = -m[1][1]; + m[2][2] = -m[2][2]; + } else if (flagBits == Translation) { + m[1][1] = -m[1][1]; + m[2][2] = -m[2][2]; + flagBits |= Scale; + } else if (flagBits == Identity) { + m[1][1] = -1.0f; + m[2][2] = -1.0f; + flagBits = Scale; + } else { + m[1][0] = -m[1][0]; + m[1][1] = -m[1][1]; + m[1][2] = -m[1][2]; + m[1][3] = -m[1][3]; + m[2][0] = -m[2][0]; + m[2][1] = -m[2][1]; + m[2][2] = -m[2][2]; + m[2][3] = -m[2][3]; + flagBits = General; + } +} + +/*! + Retrieves the 16 items in this matrix and copies them to \a values + in row-major order. +*/ +void QMatrix4x4::copyDataTo(qreal *values) const +{ + for (int row = 0; row < 4; ++row) + for (int col = 0; col < 4; ++col) + values[row * 4 + col] = qreal(m[col][row]); +} + +/*! + Returns the conventional Qt 2D affine transformation matrix that + corresponds to this matrix. It is assumed that this matrix + only contains 2D affine transformation elements. + + \sa toTransform() +*/ +QMatrix QMatrix4x4::toAffine() const +{ + return QMatrix(m[0][0], m[0][1], + m[1][0], m[1][1], + m[3][0], m[3][1]); +} + +/*! + Returns the conventional Qt 2D transformation matrix that + corresponds to this matrix. + + The returned QTransform is formed by simply dropping the + third row and third column of the QMatrix4x4. This is suitable + for implementing orthographic projections where the z co-ordinate + should be dropped rather than projected. + + \sa toAffine() +*/ +QTransform QMatrix4x4::toTransform() const +{ + return QTransform(m[0][0], m[0][1], m[0][3], + m[1][0], m[1][1], m[1][3], + m[3][0], m[3][1], m[3][3]); +} + +/*! + Returns the conventional Qt 2D transformation matrix that + corresponds to this matrix. + + If \a distanceToPlane is non-zero, it indicates a projection + factor to use to adjust for the z co-ordinate. The value of + 1024 corresponds to the projection factor used + by QTransform::rotate() for the x and y axes. + + If \a distanceToPlane is zero, then the returned QTransform + is formed by simply dropping the third row and third column + of the QMatrix4x4. This is suitable for implementing + orthographic projections where the z co-ordinate should + be dropped rather than projected. + + \sa toAffine() +*/ +QTransform QMatrix4x4::toTransform(qreal distanceToPlane) const +{ + if (distanceToPlane == 1024.0f) { + // Optimize the common case with constants. + return QTransform(m[0][0], m[0][1], + m[0][3] - m[0][2] * inv_dist_to_plane, + m[1][0], m[1][1], + m[1][3] - m[1][2] * inv_dist_to_plane, + m[3][0], m[3][1], + m[3][3] - m[3][2] * inv_dist_to_plane); + } else if (distanceToPlane != 0.0f) { + // The following projection matrix is pre-multiplied with "matrix": + // | 1 0 0 0 | + // | 0 1 0 0 | + // | 0 0 1 0 | + // | 0 0 d 1 | + // where d = -1 / distanceToPlane. After projection, row 3 and + // column 3 are dropped to form the final QTransform. + qreal d = 1.0f / distanceToPlane; + return QTransform(m[0][0], m[0][1], m[0][3] - m[0][2] * d, + m[1][0], m[1][1], m[1][3] - m[1][2] * d, + m[3][0], m[3][1], m[3][3] - m[3][2] * d); + } else { + // Orthographic projection: drop row 3 and column 3. + return QTransform(m[0][0], m[0][1], m[0][3], + m[1][0], m[1][1], m[1][3], + m[3][0], m[3][1], m[3][3]); + } +} + +/*! + \fn QPoint QMatrix4x4::map(const QPoint& point) const + + Maps \a point by multiplying this matrix by \a point. + + \sa mapRect() +*/ + +/*! + \fn QPointF QMatrix4x4::map(const QPointF& point) const + + Maps \a point by multiplying this matrix by \a point. + + \sa mapRect() +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + \fn QVector3D QMatrix4x4::map(const QVector3D& point) const + + Maps \a point by multiplying this matrix by \a point. + + \sa mapRect(), mapVector() +*/ + +/*! + \fn QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const + + Maps \a vector by multiplying the top 3x3 portion of this matrix + by \a vector. The translation and projection components of + this matrix are ignored. + + \sa map() +*/ + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + \fn QVector4D QMatrix4x4::map(const QVector4D& point) const; + + Maps \a point by multiplying this matrix by \a point. + + \sa mapRect() +*/ + +#endif + +/*! + Maps \a rect by multiplying this matrix by the corners + of \a rect and then forming a new rectangle from the results. + The returned rectangle will be an ordinary 2D rectangle + with sides parallel to the horizontal and vertical axes. + + \sa map() +*/ +QRect QMatrix4x4::mapRect(const QRect& rect) const +{ + if (flagBits == (Translation | Scale) || flagBits == Scale) { + qreal x = rect.x() * m[0][0] + m[3][0]; + qreal y = rect.y() * m[1][1] + m[3][1]; + qreal w = rect.width() * m[0][0]; + qreal h = rect.height() * m[1][1]; + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return QRect(qRound(x), qRound(y), qRound(w), qRound(h)); + } else if (flagBits == Translation) { + return QRect(qRound(rect.x() + m[3][0]), + qRound(rect.y() + m[3][1]), + rect.width(), rect.height()); + } + + QPoint tl = map(rect.topLeft()); + QPoint tr = map(QPoint(rect.x() + rect.width(), rect.y())); + QPoint bl = map(QPoint(rect.x(), rect.y() + rect.height())); + QPoint br = map(QPoint(rect.x() + rect.width(), + rect.y() + rect.height())); + + int xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x())); + int xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x())); + int ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y())); + int ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y())); + + return QRect(xmin, ymin, xmax - xmin, ymax - ymin); +} + +/*! + Maps \a rect by multiplying this matrix by the corners + of \a rect and then forming a new rectangle from the results. + The returned rectangle will be an ordinary 2D rectangle + with sides parallel to the horizontal and vertical axes. + + \sa map() +*/ +QRectF QMatrix4x4::mapRect(const QRectF& rect) const +{ + if (flagBits == (Translation | Scale) || flagBits == Scale) { + qreal x = rect.x() * m[0][0] + m[3][0]; + qreal y = rect.y() * m[1][1] + m[3][1]; + qreal w = rect.width() * m[0][0]; + qreal h = rect.height() * m[1][1]; + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return QRectF(x, y, w, h); + } else if (flagBits == Translation) { + return rect.translated(m[3][0], m[3][1]); + } + + QPointF tl = map(rect.topLeft()); QPointF tr = map(rect.topRight()); + QPointF bl = map(rect.bottomLeft()); QPointF br = map(rect.bottomRight()); + + qreal xmin = qMin(qMin(tl.x(), tr.x()), qMin(bl.x(), br.x())); + qreal xmax = qMax(qMax(tl.x(), tr.x()), qMax(bl.x(), br.x())); + qreal ymin = qMin(qMin(tl.y(), tr.y()), qMin(bl.y(), br.y())); + qreal ymax = qMax(qMax(tl.y(), tr.y()), qMax(bl.y(), br.y())); + + return QRectF(QPointF(xmin, ymin), QPointF(xmax, ymax)); +} + +/*! + \fn qreal *QMatrix4x4::data() + + Returns a pointer to the raw data of this matrix. + + \sa constData(), optimize() +*/ + +/*! + \fn const qreal *QMatrix4x4::data() const + + Returns a constant pointer to the raw data of this matrix. + + \sa constData() +*/ + +/*! + \fn const qreal *QMatrix4x4::constData() const + + Returns a constant pointer to the raw data of this matrix. + + \sa data() +*/ + +// Helper routine for inverting orthonormal matrices that consist +// of just rotations and translations. +QMatrix4x4 QMatrix4x4::orthonormalInverse() const +{ + QMatrix4x4 result(1); // The '1' says not to load identity + + result.m[0][0] = m[0][0]; + result.m[1][0] = m[0][1]; + result.m[2][0] = m[0][2]; + + result.m[0][1] = m[1][0]; + result.m[1][1] = m[1][1]; + result.m[2][1] = m[1][2]; + + result.m[0][2] = m[2][0]; + result.m[1][2] = m[2][1]; + result.m[2][2] = m[2][2]; + + result.m[0][3] = 0.0f; + result.m[1][3] = 0.0f; + result.m[2][3] = 0.0f; + + result.m[3][0] = -(result.m[0][0] * m[3][0] + result.m[1][0] * m[3][1] + result.m[2][0] * m[3][2]); + result.m[3][1] = -(result.m[0][1] * m[3][0] + result.m[1][1] * m[3][1] + result.m[2][1] * m[3][2]); + result.m[3][2] = -(result.m[0][2] * m[3][0] + result.m[1][2] * m[3][1] + result.m[2][2] * m[3][2]); + result.m[3][3] = 1.0f; + + return result; +} + +/*! + Optimize the usage of this matrix from its current elements. + + Some operations such as translate(), scale(), and rotate() can be + performed more efficiently if the matrix being modified is already + known to be the identity, a previous translate(), a previous + scale(), etc. + + Normally the QMatrix4x4 class keeps track of this special type internally + as operations are performed. However, if the matrix is modified + directly with operator()() or data(), then QMatrix4x4 will lose track of + the special type and will revert to the safest but least efficient + operations thereafter. + + By calling optimize() after directly modifying the matrix, + the programmer can force QMatrix4x4 to recover the special type if + the elements appear to conform to one of the known optimized types. + + \sa operator()(), data(), translate() +*/ +void QMatrix4x4::optimize() +{ + // If the last element is not 1, then it can never be special. + if (m[3][3] != 1.0f) { + flagBits = General; + return; + } + + // If the upper three elements m12, m13, and m21 are not all zero, + // or the lower elements below the diagonal are not all zero, then + // the matrix can never be special. + if (m[1][0] != 0.0f || m[2][0] != 0.0f || m[2][1] != 0.0f) { + flagBits = General; + return; + } + if (m[0][1] != 0.0f || m[0][2] != 0.0f || m[0][3] != 0.0f || + m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][3] != 0.0f) { + flagBits = General; + return; + } + + // Determine what we have in the remaining regions of the matrix. + bool identityAlongDiagonal + = (m[0][0] == 1.0f && m[1][1] == 1.0f && m[2][2] == 1.0f); + bool translationPresent + = (m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f); + + // Now determine the special matrix type. + if (translationPresent && identityAlongDiagonal) + flagBits = Translation; + else if (translationPresent) + flagBits = (Translation | Scale); + else if (identityAlongDiagonal) + flagBits = Identity; + else + flagBits = Scale; +} + +/*! + Returns the matrix as a QVariant. +*/ +QMatrix4x4::operator QVariant() const +{ + return QVariant(QVariant::Matrix4x4, this); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QMatrix4x4 &m) +{ + // Create a string that represents the matrix type. + QByteArray bits; + if ((m.flagBits & QMatrix4x4::Identity) != 0) + bits += "Identity,"; + if ((m.flagBits & QMatrix4x4::General) != 0) + bits += "General,"; + if ((m.flagBits & QMatrix4x4::Translation) != 0) + bits += "Translation,"; + if ((m.flagBits & QMatrix4x4::Scale) != 0) + bits += "Scale,"; + if ((m.flagBits & QMatrix4x4::Rotation) != 0) + bits += "Rotation,"; + if (bits.size() > 0) + bits = bits.left(bits.size() - 1); + + // Output in row-major order because it is more human-readable. + dbg.nospace() << "QMatrix4x4(type:" << bits.constData() << endl + << qSetFieldWidth(10) + << m(0, 0) << m(0, 1) << m(0, 2) << m(0, 3) << endl + << m(1, 0) << m(1, 1) << m(1, 2) << m(1, 3) << endl + << m(2, 0) << m(2, 1) << m(2, 2) << m(2, 3) << endl + << m(3, 0) << m(3, 1) << m(3, 2) << m(3, 3) << endl + << qSetFieldWidth(0) << ')'; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QMatrix4x4 &matrix) + \relates QMatrix4x4 + + Writes the given \a matrix to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QMatrix4x4 &matrix) +{ + for (int row = 0; row < 4; ++row) + for (int col = 0; col < 4; ++col) + stream << double(matrix(row, col)); + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QMatrix4x4 &matrix) + \relates QMatrix4x4 + + Reads a 4x4 matrix from the given \a stream into the given \a matrix + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QMatrix4x4 &matrix) +{ + double x; + for (int row = 0; row < 4; ++row) { + for (int col = 0; col < 4; ++col) { + stream >> x; + matrix(row, col) = qreal(x); + } + } + matrix.optimize(); + return stream; +} + +#endif // QT_NO_DATASTREAM + +#endif // QT_NO_MATRIX4X4 + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qmatrix4x4.h b/src/gui/math3d/qmatrix4x4.h new file mode 100644 index 0000000000..ad051160c9 --- /dev/null +++ b/src/gui/math3d/qmatrix4x4.h @@ -0,0 +1,1022 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMATRIX4X4_H +#define QMATRIX4X4_H + +#include <QtGui/qvector3d.h> +#include <QtGui/qvector4d.h> +#include <QtGui/qquaternion.h> +#include <QtGui/qgenericmatrix.h> +#include <QtCore/qrect.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_MATRIX4X4 + +class QMatrix; +class QTransform; +class QVariant; + +class Q_GUI_EXPORT QMatrix4x4 +{ +public: + inline QMatrix4x4() { setToIdentity(); } + explicit QMatrix4x4(const qreal *values); + inline QMatrix4x4(qreal m11, qreal m12, qreal m13, qreal m14, + qreal m21, qreal m22, qreal m23, qreal m24, + qreal m31, qreal m32, qreal m33, qreal m34, + qreal m41, qreal m42, qreal m43, qreal m44); + + template <int N, int M> + explicit QMatrix4x4(const QGenericMatrix<N, M, qreal>& matrix); + + QMatrix4x4(const qreal *values, int cols, int rows); + QMatrix4x4(const QTransform& transform); + QMatrix4x4(const QMatrix& matrix); + + inline const qreal& operator()(int row, int column) const; + inline qreal& operator()(int row, int column); + + inline QVector4D column(int index) const; + inline void setColumn(int index, const QVector4D& value); + + inline QVector4D row(int index) const; + inline void setRow(int index, const QVector4D& value); + + inline bool isIdentity() const; + inline void setToIdentity(); + + inline void fill(qreal value); + + qreal determinant() const; + QMatrix4x4 inverted(bool *invertible = 0) const; + QMatrix4x4 transposed() const; + QMatrix3x3 normalMatrix() const; + + inline QMatrix4x4& operator+=(const QMatrix4x4& other); + inline QMatrix4x4& operator-=(const QMatrix4x4& other); + inline QMatrix4x4& operator*=(const QMatrix4x4& other); + inline QMatrix4x4& operator*=(qreal factor); + QMatrix4x4& operator/=(qreal divisor); + inline bool operator==(const QMatrix4x4& other) const; + inline bool operator!=(const QMatrix4x4& other) const; + + friend QMatrix4x4 operator+(const QMatrix4x4& m1, const QMatrix4x4& m2); + friend QMatrix4x4 operator-(const QMatrix4x4& m1, const QMatrix4x4& m2); + friend QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2); +#ifndef QT_NO_VECTOR3D + friend QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector); + friend QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix); +#endif +#ifndef QT_NO_VECTOR4D + friend QVector4D operator*(const QVector4D& vector, const QMatrix4x4& matrix); + friend QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector); +#endif + friend QPoint operator*(const QPoint& point, const QMatrix4x4& matrix); + friend QPointF operator*(const QPointF& point, const QMatrix4x4& matrix); + friend QMatrix4x4 operator-(const QMatrix4x4& matrix); + friend QPoint operator*(const QMatrix4x4& matrix, const QPoint& point); + friend QPointF operator*(const QMatrix4x4& matrix, const QPointF& point); + friend QMatrix4x4 operator*(qreal factor, const QMatrix4x4& matrix); + friend QMatrix4x4 operator*(const QMatrix4x4& matrix, qreal factor); + friend Q_GUI_EXPORT QMatrix4x4 operator/(const QMatrix4x4& matrix, qreal divisor); + + friend inline bool qFuzzyCompare(const QMatrix4x4& m1, const QMatrix4x4& m2); + +#ifndef QT_NO_VECTOR3D + void scale(const QVector3D& vector); + void translate(const QVector3D& vector); + void rotate(qreal angle, const QVector3D& vector); +#endif + void scale(qreal x, qreal y); + void scale(qreal x, qreal y, qreal z); + void scale(qreal factor); + void translate(qreal x, qreal y); + void translate(qreal x, qreal y, qreal z); + void rotate(qreal angle, qreal x, qreal y, qreal z = 0.0f); +#ifndef QT_NO_QUATERNION + void rotate(const QQuaternion& quaternion); +#endif + + void ortho(const QRect& rect); + void ortho(const QRectF& rect); + void ortho(qreal left, qreal right, qreal bottom, qreal top, qreal nearPlane, qreal farPlane); + void frustum(qreal left, qreal right, qreal bottom, qreal top, qreal nearPlane, qreal farPlane); + void perspective(qreal angle, qreal aspect, qreal nearPlane, qreal farPlane); +#ifndef QT_NO_VECTOR3D + void lookAt(const QVector3D& eye, const QVector3D& center, const QVector3D& up); +#endif + void flipCoordinates(); + + void copyDataTo(qreal *values) const; + + QMatrix toAffine() const; + QTransform toTransform() const; + QTransform toTransform(qreal distanceToPlane) const; + + QPoint map(const QPoint& point) const; + QPointF map(const QPointF& point) const; +#ifndef QT_NO_VECTOR3D + QVector3D map(const QVector3D& point) const; + QVector3D mapVector(const QVector3D& vector) const; +#endif +#ifndef QT_NO_VECTOR4D + QVector4D map(const QVector4D& point) const; +#endif + QRect mapRect(const QRect& rect) const; + QRectF mapRect(const QRectF& rect) const; + + template <int N, int M> + QGenericMatrix<N, M, qreal> toGenericMatrix() const; + + inline qreal *data(); + inline const qreal *data() const { return *m; } + inline const qreal *constData() const { return *m; } + + void optimize(); + + operator QVariant() const; + +#ifndef QT_NO_DEBUG_STREAM + friend Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QMatrix4x4 &m); +#endif + +private: + qreal m[4][4]; // Column-major order to match OpenGL. + int flagBits; // Flag bits from the enum below. + + enum { + Identity = 0x0001, // Identity matrix + General = 0x0002, // General matrix, unknown contents + Translation = 0x0004, // Contains a simple translation + Scale = 0x0008, // Contains a simple scale + Rotation = 0x0010 // Contains a simple rotation + }; + + // Construct without initializing identity matrix. + QMatrix4x4(int) { flagBits = General; } + + QMatrix4x4 orthonormalInverse() const; + + void projectedRotate(qreal angle, qreal x, qreal y, qreal z); + + friend class QGraphicsRotation; +}; + +Q_DECLARE_TYPEINFO(QMatrix4x4, Q_MOVABLE_TYPE); + +inline QMatrix4x4::QMatrix4x4 + (qreal m11, qreal m12, qreal m13, qreal m14, + qreal m21, qreal m22, qreal m23, qreal m24, + qreal m31, qreal m32, qreal m33, qreal m34, + qreal m41, qreal m42, qreal m43, qreal m44) +{ + m[0][0] = m11; m[0][1] = m21; m[0][2] = m31; m[0][3] = m41; + m[1][0] = m12; m[1][1] = m22; m[1][2] = m32; m[1][3] = m42; + m[2][0] = m13; m[2][1] = m23; m[2][2] = m33; m[2][3] = m43; + m[3][0] = m14; m[3][1] = m24; m[3][2] = m34; m[3][3] = m44; + flagBits = General; +} + +template <int N, int M> +Q_INLINE_TEMPLATE QMatrix4x4::QMatrix4x4 + (const QGenericMatrix<N, M, qreal>& matrix) +{ + const qreal *values = matrix.constData(); + for (int matrixCol = 0; matrixCol < 4; ++matrixCol) { + for (int matrixRow = 0; matrixRow < 4; ++matrixRow) { + if (matrixCol < N && matrixRow < M) + m[matrixCol][matrixRow] = values[matrixCol * M + matrixRow]; + else if (matrixCol == matrixRow) + m[matrixCol][matrixRow] = 1.0f; + else + m[matrixCol][matrixRow] = 0.0f; + } + } + flagBits = General; +} + +template <int N, int M> +QGenericMatrix<N, M, qreal> QMatrix4x4::toGenericMatrix() const +{ + QGenericMatrix<N, M, qreal> result; + qreal *values = result.data(); + for (int matrixCol = 0; matrixCol < N; ++matrixCol) { + for (int matrixRow = 0; matrixRow < M; ++matrixRow) { + if (matrixCol < 4 && matrixRow < 4) + values[matrixCol * M + matrixRow] = m[matrixCol][matrixRow]; + else if (matrixCol == matrixRow) + values[matrixCol * M + matrixRow] = 1.0f; + else + values[matrixCol * M + matrixRow] = 0.0f; + } + } + return result; +} + +inline const qreal& QMatrix4x4::operator()(int aRow, int aColumn) const +{ + Q_ASSERT(aRow >= 0 && aRow < 4 && aColumn >= 0 && aColumn < 4); + return m[aColumn][aRow]; +} + +inline qreal& QMatrix4x4::operator()(int aRow, int aColumn) +{ + Q_ASSERT(aRow >= 0 && aRow < 4 && aColumn >= 0 && aColumn < 4); + flagBits = General; + return m[aColumn][aRow]; +} + +inline QVector4D QMatrix4x4::column(int index) const +{ + Q_ASSERT(index >= 0 && index < 4); + return QVector4D(m[index][0], m[index][1], m[index][2], m[index][3]); +} + +inline void QMatrix4x4::setColumn(int index, const QVector4D& value) +{ + Q_ASSERT(index >= 0 && index < 4); + m[index][0] = value.x(); + m[index][1] = value.y(); + m[index][2] = value.z(); + m[index][3] = value.w(); + flagBits = General; +} + +inline QVector4D QMatrix4x4::row(int index) const +{ + Q_ASSERT(index >= 0 && index < 4); + return QVector4D(m[0][index], m[1][index], m[2][index], m[3][index]); +} + +inline void QMatrix4x4::setRow(int index, const QVector4D& value) +{ + Q_ASSERT(index >= 0 && index < 4); + m[0][index] = value.x(); + m[1][index] = value.y(); + m[2][index] = value.z(); + m[3][index] = value.w(); + flagBits = General; +} + +Q_GUI_EXPORT QMatrix4x4 operator/(const QMatrix4x4& matrix, qreal divisor); + +inline bool QMatrix4x4::isIdentity() const +{ + if (flagBits == Identity) + return true; + if (m[0][0] != 1.0f || m[0][1] != 0.0f || m[0][2] != 0.0f) + return false; + if (m[0][3] != 0.0f || m[1][0] != 0.0f || m[1][1] != 1.0f) + return false; + if (m[1][2] != 0.0f || m[1][3] != 0.0f || m[2][0] != 0.0f) + return false; + if (m[2][1] != 0.0f || m[2][2] != 1.0f || m[2][3] != 0.0f) + return false; + if (m[3][0] != 0.0f || m[3][1] != 0.0f || m[3][2] != 0.0f) + return false; + return (m[3][3] == 1.0f); +} + +inline void QMatrix4x4::setToIdentity() +{ + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; + flagBits = Identity; +} + +inline void QMatrix4x4::fill(qreal value) +{ + m[0][0] = value; + m[0][1] = value; + m[0][2] = value; + m[0][3] = value; + m[1][0] = value; + m[1][1] = value; + m[1][2] = value; + m[1][3] = value; + m[2][0] = value; + m[2][1] = value; + m[2][2] = value; + m[2][3] = value; + m[3][0] = value; + m[3][1] = value; + m[3][2] = value; + m[3][3] = value; + flagBits = General; +} + +inline QMatrix4x4& QMatrix4x4::operator+=(const QMatrix4x4& other) +{ + m[0][0] += other.m[0][0]; + m[0][1] += other.m[0][1]; + m[0][2] += other.m[0][2]; + m[0][3] += other.m[0][3]; + m[1][0] += other.m[1][0]; + m[1][1] += other.m[1][1]; + m[1][2] += other.m[1][2]; + m[1][3] += other.m[1][3]; + m[2][0] += other.m[2][0]; + m[2][1] += other.m[2][1]; + m[2][2] += other.m[2][2]; + m[2][3] += other.m[2][3]; + m[3][0] += other.m[3][0]; + m[3][1] += other.m[3][1]; + m[3][2] += other.m[3][2]; + m[3][3] += other.m[3][3]; + flagBits = General; + return *this; +} + +inline QMatrix4x4& QMatrix4x4::operator-=(const QMatrix4x4& other) +{ + m[0][0] -= other.m[0][0]; + m[0][1] -= other.m[0][1]; + m[0][2] -= other.m[0][2]; + m[0][3] -= other.m[0][3]; + m[1][0] -= other.m[1][0]; + m[1][1] -= other.m[1][1]; + m[1][2] -= other.m[1][2]; + m[1][3] -= other.m[1][3]; + m[2][0] -= other.m[2][0]; + m[2][1] -= other.m[2][1]; + m[2][2] -= other.m[2][2]; + m[2][3] -= other.m[2][3]; + m[3][0] -= other.m[3][0]; + m[3][1] -= other.m[3][1]; + m[3][2] -= other.m[3][2]; + m[3][3] -= other.m[3][3]; + flagBits = General; + return *this; +} + +inline QMatrix4x4& QMatrix4x4::operator*=(const QMatrix4x4& other) +{ + if (flagBits == Identity) { + *this = other; + return *this; + } else if (other.flagBits == Identity) { + return *this; + } else { + *this = *this * other; + return *this; + } +} + +inline QMatrix4x4& QMatrix4x4::operator*=(qreal factor) +{ + m[0][0] *= factor; + m[0][1] *= factor; + m[0][2] *= factor; + m[0][3] *= factor; + m[1][0] *= factor; + m[1][1] *= factor; + m[1][2] *= factor; + m[1][3] *= factor; + m[2][0] *= factor; + m[2][1] *= factor; + m[2][2] *= factor; + m[2][3] *= factor; + m[3][0] *= factor; + m[3][1] *= factor; + m[3][2] *= factor; + m[3][3] *= factor; + flagBits = General; + return *this; +} + +inline bool QMatrix4x4::operator==(const QMatrix4x4& other) const +{ + return m[0][0] == other.m[0][0] && + m[0][1] == other.m[0][1] && + m[0][2] == other.m[0][2] && + m[0][3] == other.m[0][3] && + m[1][0] == other.m[1][0] && + m[1][1] == other.m[1][1] && + m[1][2] == other.m[1][2] && + m[1][3] == other.m[1][3] && + m[2][0] == other.m[2][0] && + m[2][1] == other.m[2][1] && + m[2][2] == other.m[2][2] && + m[2][3] == other.m[2][3] && + m[3][0] == other.m[3][0] && + m[3][1] == other.m[3][1] && + m[3][2] == other.m[3][2] && + m[3][3] == other.m[3][3]; +} + +inline bool QMatrix4x4::operator!=(const QMatrix4x4& other) const +{ + return m[0][0] != other.m[0][0] || + m[0][1] != other.m[0][1] || + m[0][2] != other.m[0][2] || + m[0][3] != other.m[0][3] || + m[1][0] != other.m[1][0] || + m[1][1] != other.m[1][1] || + m[1][2] != other.m[1][2] || + m[1][3] != other.m[1][3] || + m[2][0] != other.m[2][0] || + m[2][1] != other.m[2][1] || + m[2][2] != other.m[2][2] || + m[2][3] != other.m[2][3] || + m[3][0] != other.m[3][0] || + m[3][1] != other.m[3][1] || + m[3][2] != other.m[3][2] || + m[3][3] != other.m[3][3]; +} + +inline QMatrix4x4 operator+(const QMatrix4x4& m1, const QMatrix4x4& m2) +{ + QMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] + m2.m[0][0]; + m.m[0][1] = m1.m[0][1] + m2.m[0][1]; + m.m[0][2] = m1.m[0][2] + m2.m[0][2]; + m.m[0][3] = m1.m[0][3] + m2.m[0][3]; + m.m[1][0] = m1.m[1][0] + m2.m[1][0]; + m.m[1][1] = m1.m[1][1] + m2.m[1][1]; + m.m[1][2] = m1.m[1][2] + m2.m[1][2]; + m.m[1][3] = m1.m[1][3] + m2.m[1][3]; + m.m[2][0] = m1.m[2][0] + m2.m[2][0]; + m.m[2][1] = m1.m[2][1] + m2.m[2][1]; + m.m[2][2] = m1.m[2][2] + m2.m[2][2]; + m.m[2][3] = m1.m[2][3] + m2.m[2][3]; + m.m[3][0] = m1.m[3][0] + m2.m[3][0]; + m.m[3][1] = m1.m[3][1] + m2.m[3][1]; + m.m[3][2] = m1.m[3][2] + m2.m[3][2]; + m.m[3][3] = m1.m[3][3] + m2.m[3][3]; + return m; +} + +inline QMatrix4x4 operator-(const QMatrix4x4& m1, const QMatrix4x4& m2) +{ + QMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] - m2.m[0][0]; + m.m[0][1] = m1.m[0][1] - m2.m[0][1]; + m.m[0][2] = m1.m[0][2] - m2.m[0][2]; + m.m[0][3] = m1.m[0][3] - m2.m[0][3]; + m.m[1][0] = m1.m[1][0] - m2.m[1][0]; + m.m[1][1] = m1.m[1][1] - m2.m[1][1]; + m.m[1][2] = m1.m[1][2] - m2.m[1][2]; + m.m[1][3] = m1.m[1][3] - m2.m[1][3]; + m.m[2][0] = m1.m[2][0] - m2.m[2][0]; + m.m[2][1] = m1.m[2][1] - m2.m[2][1]; + m.m[2][2] = m1.m[2][2] - m2.m[2][2]; + m.m[2][3] = m1.m[2][3] - m2.m[2][3]; + m.m[3][0] = m1.m[3][0] - m2.m[3][0]; + m.m[3][1] = m1.m[3][1] - m2.m[3][1]; + m.m[3][2] = m1.m[3][2] - m2.m[3][2]; + m.m[3][3] = m1.m[3][3] - m2.m[3][3]; + return m; +} + +inline QMatrix4x4 operator*(const QMatrix4x4& m1, const QMatrix4x4& m2) +{ + if (m1.flagBits == QMatrix4x4::Identity) + return m2; + else if (m2.flagBits == QMatrix4x4::Identity) + return m1; + + QMatrix4x4 m(1); + m.m[0][0] = m1.m[0][0] * m2.m[0][0] + + m1.m[1][0] * m2.m[0][1] + + m1.m[2][0] * m2.m[0][2] + + m1.m[3][0] * m2.m[0][3]; + m.m[0][1] = m1.m[0][1] * m2.m[0][0] + + m1.m[1][1] * m2.m[0][1] + + m1.m[2][1] * m2.m[0][2] + + m1.m[3][1] * m2.m[0][3]; + m.m[0][2] = m1.m[0][2] * m2.m[0][0] + + m1.m[1][2] * m2.m[0][1] + + m1.m[2][2] * m2.m[0][2] + + m1.m[3][2] * m2.m[0][3]; + m.m[0][3] = m1.m[0][3] * m2.m[0][0] + + m1.m[1][3] * m2.m[0][1] + + m1.m[2][3] * m2.m[0][2] + + m1.m[3][3] * m2.m[0][3]; + m.m[1][0] = m1.m[0][0] * m2.m[1][0] + + m1.m[1][0] * m2.m[1][1] + + m1.m[2][0] * m2.m[1][2] + + m1.m[3][0] * m2.m[1][3]; + m.m[1][1] = m1.m[0][1] * m2.m[1][0] + + m1.m[1][1] * m2.m[1][1] + + m1.m[2][1] * m2.m[1][2] + + m1.m[3][1] * m2.m[1][3]; + m.m[1][2] = m1.m[0][2] * m2.m[1][0] + + m1.m[1][2] * m2.m[1][1] + + m1.m[2][2] * m2.m[1][2] + + m1.m[3][2] * m2.m[1][3]; + m.m[1][3] = m1.m[0][3] * m2.m[1][0] + + m1.m[1][3] * m2.m[1][1] + + m1.m[2][3] * m2.m[1][2] + + m1.m[3][3] * m2.m[1][3]; + m.m[2][0] = m1.m[0][0] * m2.m[2][0] + + m1.m[1][0] * m2.m[2][1] + + m1.m[2][0] * m2.m[2][2] + + m1.m[3][0] * m2.m[2][3]; + m.m[2][1] = m1.m[0][1] * m2.m[2][0] + + m1.m[1][1] * m2.m[2][1] + + m1.m[2][1] * m2.m[2][2] + + m1.m[3][1] * m2.m[2][3]; + m.m[2][2] = m1.m[0][2] * m2.m[2][0] + + m1.m[1][2] * m2.m[2][1] + + m1.m[2][2] * m2.m[2][2] + + m1.m[3][2] * m2.m[2][3]; + m.m[2][3] = m1.m[0][3] * m2.m[2][0] + + m1.m[1][3] * m2.m[2][1] + + m1.m[2][3] * m2.m[2][2] + + m1.m[3][3] * m2.m[2][3]; + m.m[3][0] = m1.m[0][0] * m2.m[3][0] + + m1.m[1][0] * m2.m[3][1] + + m1.m[2][0] * m2.m[3][2] + + m1.m[3][0] * m2.m[3][3]; + m.m[3][1] = m1.m[0][1] * m2.m[3][0] + + m1.m[1][1] * m2.m[3][1] + + m1.m[2][1] * m2.m[3][2] + + m1.m[3][1] * m2.m[3][3]; + m.m[3][2] = m1.m[0][2] * m2.m[3][0] + + m1.m[1][2] * m2.m[3][1] + + m1.m[2][2] * m2.m[3][2] + + m1.m[3][2] * m2.m[3][3]; + m.m[3][3] = m1.m[0][3] * m2.m[3][0] + + m1.m[1][3] * m2.m[3][1] + + m1.m[2][3] * m2.m[3][2] + + m1.m[3][3] * m2.m[3][3]; + return m; +} + +#ifndef QT_NO_VECTOR3D + +inline QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix) +{ + qreal x, y, z, w; + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[0][1] + + vector.z() * matrix.m[0][2] + + matrix.m[0][3]; + y = vector.x() * matrix.m[1][0] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[1][2] + + matrix.m[1][3]; + z = vector.x() * matrix.m[2][0] + + vector.y() * matrix.m[2][1] + + vector.z() * matrix.m[2][2] + + matrix.m[2][3]; + w = vector.x() * matrix.m[3][0] + + vector.y() * matrix.m[3][1] + + vector.z() * matrix.m[3][2] + + matrix.m[3][3]; + if (w == 1.0f) + return QVector3D(x, y, z); + else + return QVector3D(x / w, y / w, z / w); +} + +inline QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector) +{ + qreal x, y, z, w; + if (matrix.flagBits == QMatrix4x4::Identity) { + return vector; + } else if (matrix.flagBits == QMatrix4x4::Translation) { + return QVector3D(vector.x() + matrix.m[3][0], + vector.y() + matrix.m[3][1], + vector.z() + matrix.m[3][2]); + } else if (matrix.flagBits == + (QMatrix4x4::Translation | QMatrix4x4::Scale)) { + return QVector3D(vector.x() * matrix.m[0][0] + matrix.m[3][0], + vector.y() * matrix.m[1][1] + matrix.m[3][1], + vector.z() * matrix.m[2][2] + matrix.m[3][2]); + } else if (matrix.flagBits == QMatrix4x4::Scale) { + return QVector3D(vector.x() * matrix.m[0][0], + vector.y() * matrix.m[1][1], + vector.z() * matrix.m[2][2]); + } else { + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[1][0] + + vector.z() * matrix.m[2][0] + + matrix.m[3][0]; + y = vector.x() * matrix.m[0][1] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[2][1] + + matrix.m[3][1]; + z = vector.x() * matrix.m[0][2] + + vector.y() * matrix.m[1][2] + + vector.z() * matrix.m[2][2] + + matrix.m[3][2]; + w = vector.x() * matrix.m[0][3] + + vector.y() * matrix.m[1][3] + + vector.z() * matrix.m[2][3] + + matrix.m[3][3]; + if (w == 1.0f) + return QVector3D(x, y, z); + else + return QVector3D(x / w, y / w, z / w); + } +} + +#endif + +#ifndef QT_NO_VECTOR4D + +inline QVector4D operator*(const QVector4D& vector, const QMatrix4x4& matrix) +{ + qreal x, y, z, w; + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[0][1] + + vector.z() * matrix.m[0][2] + + vector.w() * matrix.m[0][3]; + y = vector.x() * matrix.m[1][0] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[1][2] + + vector.w() * matrix.m[1][3]; + z = vector.x() * matrix.m[2][0] + + vector.y() * matrix.m[2][1] + + vector.z() * matrix.m[2][2] + + vector.w() * matrix.m[2][3]; + w = vector.x() * matrix.m[3][0] + + vector.y() * matrix.m[3][1] + + vector.z() * matrix.m[3][2] + + vector.w() * matrix.m[3][3]; + return QVector4D(x, y, z, w); +} + +inline QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector) +{ + qreal x, y, z, w; + x = vector.x() * matrix.m[0][0] + + vector.y() * matrix.m[1][0] + + vector.z() * matrix.m[2][0] + + vector.w() * matrix.m[3][0]; + y = vector.x() * matrix.m[0][1] + + vector.y() * matrix.m[1][1] + + vector.z() * matrix.m[2][1] + + vector.w() * matrix.m[3][1]; + z = vector.x() * matrix.m[0][2] + + vector.y() * matrix.m[1][2] + + vector.z() * matrix.m[2][2] + + vector.w() * matrix.m[3][2]; + w = vector.x() * matrix.m[0][3] + + vector.y() * matrix.m[1][3] + + vector.z() * matrix.m[2][3] + + vector.w() * matrix.m[3][3]; + return QVector4D(x, y, z, w); +} + +#endif + +inline QPoint operator*(const QPoint& point, const QMatrix4x4& matrix) +{ + qreal xin, yin; + qreal x, y, w; + xin = point.x(); + yin = point.y(); + x = xin * matrix.m[0][0] + + yin * matrix.m[0][1] + + matrix.m[0][3]; + y = xin * matrix.m[1][0] + + yin * matrix.m[1][1] + + matrix.m[1][3]; + w = xin * matrix.m[3][0] + + yin * matrix.m[3][1] + + matrix.m[3][3]; + if (w == 1.0f) + return QPoint(qRound(x), qRound(y)); + else + return QPoint(qRound(x / w), qRound(y / w)); +} + +inline QPointF operator*(const QPointF& point, const QMatrix4x4& matrix) +{ + qreal xin, yin; + qreal x, y, w; + xin = point.x(); + yin = point.y(); + x = xin * matrix.m[0][0] + + yin * matrix.m[0][1] + + matrix.m[0][3]; + y = xin * matrix.m[1][0] + + yin * matrix.m[1][1] + + matrix.m[1][3]; + w = xin * matrix.m[3][0] + + yin * matrix.m[3][1] + + matrix.m[3][3]; + if (w == 1.0f) { + return QPointF(qreal(x), qreal(y)); + } else { + return QPointF(qreal(x / w), qreal(y / w)); + } +} + +inline QPoint operator*(const QMatrix4x4& matrix, const QPoint& point) +{ + qreal xin, yin; + qreal x, y, w; + xin = point.x(); + yin = point.y(); + if (matrix.flagBits == QMatrix4x4::Identity) { + return point; + } else if (matrix.flagBits == QMatrix4x4::Translation) { + return QPoint(qRound(xin + matrix.m[3][0]), + qRound(yin + matrix.m[3][1])); + } else if (matrix.flagBits == + (QMatrix4x4::Translation | QMatrix4x4::Scale)) { + return QPoint(qRound(xin * matrix.m[0][0] + matrix.m[3][0]), + qRound(yin * matrix.m[1][1] + matrix.m[3][1])); + } else if (matrix.flagBits == QMatrix4x4::Scale) { + return QPoint(qRound(xin * matrix.m[0][0]), + qRound(yin * matrix.m[1][1])); + } else { + x = xin * matrix.m[0][0] + + yin * matrix.m[1][0] + + matrix.m[3][0]; + y = xin * matrix.m[0][1] + + yin * matrix.m[1][1] + + matrix.m[3][1]; + w = xin * matrix.m[0][3] + + yin * matrix.m[1][3] + + matrix.m[3][3]; + if (w == 1.0f) + return QPoint(qRound(x), qRound(y)); + else + return QPoint(qRound(x / w), qRound(y / w)); + } +} + +inline QPointF operator*(const QMatrix4x4& matrix, const QPointF& point) +{ + qreal xin, yin; + qreal x, y, w; + xin = point.x(); + yin = point.y(); + if (matrix.flagBits == QMatrix4x4::Identity) { + return point; + } else if (matrix.flagBits == QMatrix4x4::Translation) { + return QPointF(xin + matrix.m[3][0], + yin + matrix.m[3][1]); + } else if (matrix.flagBits == + (QMatrix4x4::Translation | QMatrix4x4::Scale)) { + return QPointF(xin * matrix.m[0][0] + matrix.m[3][0], + yin * matrix.m[1][1] + matrix.m[3][1]); + } else if (matrix.flagBits == QMatrix4x4::Scale) { + return QPointF(xin * matrix.m[0][0], + yin * matrix.m[1][1]); + } else { + x = xin * matrix.m[0][0] + + yin * matrix.m[1][0] + + matrix.m[3][0]; + y = xin * matrix.m[0][1] + + yin * matrix.m[1][1] + + matrix.m[3][1]; + w = xin * matrix.m[0][3] + + yin * matrix.m[1][3] + + matrix.m[3][3]; + if (w == 1.0f) { + return QPointF(qreal(x), qreal(y)); + } else { + return QPointF(qreal(x / w), qreal(y / w)); + } + } +} + +inline QMatrix4x4 operator-(const QMatrix4x4& matrix) +{ + QMatrix4x4 m(1); + m.m[0][0] = -matrix.m[0][0]; + m.m[0][1] = -matrix.m[0][1]; + m.m[0][2] = -matrix.m[0][2]; + m.m[0][3] = -matrix.m[0][3]; + m.m[1][0] = -matrix.m[1][0]; + m.m[1][1] = -matrix.m[1][1]; + m.m[1][2] = -matrix.m[1][2]; + m.m[1][3] = -matrix.m[1][3]; + m.m[2][0] = -matrix.m[2][0]; + m.m[2][1] = -matrix.m[2][1]; + m.m[2][2] = -matrix.m[2][2]; + m.m[2][3] = -matrix.m[2][3]; + m.m[3][0] = -matrix.m[3][0]; + m.m[3][1] = -matrix.m[3][1]; + m.m[3][2] = -matrix.m[3][2]; + m.m[3][3] = -matrix.m[3][3]; + return m; +} + +inline QMatrix4x4 operator*(qreal factor, const QMatrix4x4& matrix) +{ + QMatrix4x4 m(1); + m.m[0][0] = matrix.m[0][0] * factor; + m.m[0][1] = matrix.m[0][1] * factor; + m.m[0][2] = matrix.m[0][2] * factor; + m.m[0][3] = matrix.m[0][3] * factor; + m.m[1][0] = matrix.m[1][0] * factor; + m.m[1][1] = matrix.m[1][1] * factor; + m.m[1][2] = matrix.m[1][2] * factor; + m.m[1][3] = matrix.m[1][3] * factor; + m.m[2][0] = matrix.m[2][0] * factor; + m.m[2][1] = matrix.m[2][1] * factor; + m.m[2][2] = matrix.m[2][2] * factor; + m.m[2][3] = matrix.m[2][3] * factor; + m.m[3][0] = matrix.m[3][0] * factor; + m.m[3][1] = matrix.m[3][1] * factor; + m.m[3][2] = matrix.m[3][2] * factor; + m.m[3][3] = matrix.m[3][3] * factor; + return m; +} + +inline QMatrix4x4 operator*(const QMatrix4x4& matrix, qreal factor) +{ + QMatrix4x4 m(1); + m.m[0][0] = matrix.m[0][0] * factor; + m.m[0][1] = matrix.m[0][1] * factor; + m.m[0][2] = matrix.m[0][2] * factor; + m.m[0][3] = matrix.m[0][3] * factor; + m.m[1][0] = matrix.m[1][0] * factor; + m.m[1][1] = matrix.m[1][1] * factor; + m.m[1][2] = matrix.m[1][2] * factor; + m.m[1][3] = matrix.m[1][3] * factor; + m.m[2][0] = matrix.m[2][0] * factor; + m.m[2][1] = matrix.m[2][1] * factor; + m.m[2][2] = matrix.m[2][2] * factor; + m.m[2][3] = matrix.m[2][3] * factor; + m.m[3][0] = matrix.m[3][0] * factor; + m.m[3][1] = matrix.m[3][1] * factor; + m.m[3][2] = matrix.m[3][2] * factor; + m.m[3][3] = matrix.m[3][3] * factor; + return m; +} + +inline bool qFuzzyCompare(const QMatrix4x4& m1, const QMatrix4x4& m2) +{ + return qFuzzyCompare(m1.m[0][0], m2.m[0][0]) && + qFuzzyCompare(m1.m[0][1], m2.m[0][1]) && + qFuzzyCompare(m1.m[0][2], m2.m[0][2]) && + qFuzzyCompare(m1.m[0][3], m2.m[0][3]) && + qFuzzyCompare(m1.m[1][0], m2.m[1][0]) && + qFuzzyCompare(m1.m[1][1], m2.m[1][1]) && + qFuzzyCompare(m1.m[1][2], m2.m[1][2]) && + qFuzzyCompare(m1.m[1][3], m2.m[1][3]) && + qFuzzyCompare(m1.m[2][0], m2.m[2][0]) && + qFuzzyCompare(m1.m[2][1], m2.m[2][1]) && + qFuzzyCompare(m1.m[2][2], m2.m[2][2]) && + qFuzzyCompare(m1.m[2][3], m2.m[2][3]) && + qFuzzyCompare(m1.m[3][0], m2.m[3][0]) && + qFuzzyCompare(m1.m[3][1], m2.m[3][1]) && + qFuzzyCompare(m1.m[3][2], m2.m[3][2]) && + qFuzzyCompare(m1.m[3][3], m2.m[3][3]); +} + +inline QPoint QMatrix4x4::map(const QPoint& point) const +{ + return *this * point; +} + +inline QPointF QMatrix4x4::map(const QPointF& point) const +{ + return *this * point; +} + +#ifndef QT_NO_VECTOR3D + +inline QVector3D QMatrix4x4::map(const QVector3D& point) const +{ + return *this * point; +} + +inline QVector3D QMatrix4x4::mapVector(const QVector3D& vector) const +{ + if (flagBits == Identity || flagBits == Translation) { + return vector; + } else if (flagBits == Scale || flagBits == (Translation | Scale)) { + return QVector3D(vector.x() * m[0][0], + vector.y() * m[1][1], + vector.z() * m[2][2]); + } else { + return QVector3D(vector.x() * m[0][0] + + vector.y() * m[1][0] + + vector.z() * m[2][0], + vector.x() * m[0][1] + + vector.y() * m[1][1] + + vector.z() * m[2][1], + vector.x() * m[0][2] + + vector.y() * m[1][2] + + vector.z() * m[2][2]); + } +} + +#endif + +#ifndef QT_NO_VECTOR4D + +inline QVector4D QMatrix4x4::map(const QVector4D& point) const +{ + return *this * point; +} + +#endif + +inline qreal *QMatrix4x4::data() +{ + // We have to assume that the caller will modify the matrix elements, + // so we flip it over to "General" mode. + flagBits = General; + return *m; +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QMatrix4x4 &m); +#endif + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QMatrix4x4 &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QMatrix4x4 &); +#endif + +#ifdef QT_DEPRECATED +template <int N, int M> +QT_DEPRECATED QMatrix4x4 qGenericMatrixToMatrix4x4(const QGenericMatrix<N, M, qreal>& matrix) +{ + return QMatrix4x4(matrix.constData(), N, M); +} + +template <int N, int M> +QT_DEPRECATED QGenericMatrix<N, M, qreal> qGenericMatrixFromMatrix4x4(const QMatrix4x4& matrix) +{ + QGenericMatrix<N, M, qreal> result; + const qreal *m = matrix.constData(); + qreal *values = result.data(); + for (int col = 0; col < N; ++col) { + for (int row = 0; row < M; ++row) { + if (col < 4 && row < 4) + values[col * M + row] = m[col * 4 + row]; + else if (col == row) + values[col * M + row] = 1.0f; + else + values[col * M + row] = 0.0f; + } + } + return result; +} +#endif + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp new file mode 100644 index 0000000000..0819afe783 --- /dev/null +++ b/src/gui/math3d/qquaternion.cpp @@ -0,0 +1,636 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquaternion.h" +#include <QtCore/qmath.h> +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QUATERNION + +/*! + \class QQuaternion + \brief The QQuaternion class represents a quaternion consisting of a vector and scalar. + \since 4.6 + \ingroup painting-3D + + Quaternions are used to represent rotations in 3D space, and + consist of a 3D rotation axis specified by the x, y, and z + coordinates, and a scalar representing the rotation angle. +*/ + +/*! + \fn QQuaternion::QQuaternion() + + Constructs an identity quaternion, i.e. with coordinates (1, 0, 0, 0). +*/ + +/*! + \fn QQuaternion::QQuaternion(qreal scalar, qreal xpos, qreal ypos, qreal zpos) + + Constructs a quaternion with the vector (\a xpos, \a ypos, \a zpos) + and \a scalar. +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + \fn QQuaternion::QQuaternion(qreal scalar, const QVector3D& vector) + + Constructs a quaternion vector from the specified \a vector and + \a scalar. + + \sa vector(), scalar() +*/ + +/*! + \fn QVector3D QQuaternion::vector() const + + Returns the vector component of this quaternion. + + \sa setVector(), scalar() +*/ + +/*! + \fn void QQuaternion::setVector(const QVector3D& vector) + + Sets the vector component of this quaternion to \a vector. + + \sa vector(), setScalar() +*/ + +#endif + +/*! + \fn void QQuaternion::setVector(qreal x, qreal y, qreal z) + + Sets the vector component of this quaternion to (\a x, \a y, \a z). + + \sa vector(), setScalar() +*/ + +#ifndef QT_NO_VECTOR4D + +/*! + \fn QQuaternion::QQuaternion(const QVector4D& vector) + + Constructs a quaternion from the components of \a vector. +*/ + +/*! + \fn QVector4D QQuaternion::toVector4D() const + + Returns this quaternion as a 4D vector. +*/ + +#endif + +/*! + \fn bool QQuaternion::isNull() const + + Returns true if the x, y, z, and scalar components of this + quaternion are set to 0.0; otherwise returns false. +*/ + +/*! + \fn bool QQuaternion::isIdentity() const + + Returns true if the x, y, and z components of this + quaternion are set to 0.0, and the scalar component is set + to 1.0; otherwise returns false. +*/ + +/*! + \fn qreal QQuaternion::x() const + + Returns the x coordinate of this quaternion's vector. + + \sa setX(), y(), z(), scalar() +*/ + +/*! + \fn qreal QQuaternion::y() const + + Returns the y coordinate of this quaternion's vector. + + \sa setY(), x(), z(), scalar() +*/ + +/*! + \fn qreal QQuaternion::z() const + + Returns the z coordinate of this quaternion's vector. + + \sa setZ(), x(), y(), scalar() +*/ + +/*! + \fn qreal QQuaternion::scalar() const + + Returns the scalar component of this quaternion. + + \sa setScalar(), x(), y(), z() +*/ + +/*! + \fn void QQuaternion::setX(qreal x) + + Sets the x coordinate of this quaternion's vector to the given + \a x coordinate. + + \sa x(), setY(), setZ(), setScalar() +*/ + +/*! + \fn void QQuaternion::setY(qreal y) + + Sets the y coordinate of this quaternion's vector to the given + \a y coordinate. + + \sa y(), setX(), setZ(), setScalar() +*/ + +/*! + \fn void QQuaternion::setZ(qreal z) + + Sets the z coordinate of this quaternion's vector to the given + \a z coordinate. + + \sa z(), setX(), setY(), setScalar() +*/ + +/*! + \fn void QQuaternion::setScalar(qreal scalar) + + Sets the scalar component of this quaternion to \a scalar. + + \sa scalar(), setX(), setY(), setZ() +*/ + +/*! + Returns the length of the quaternion. This is also called the "norm". + + \sa lengthSquared(), normalized() +*/ +qreal QQuaternion::length() const +{ + return qSqrt(xp * xp + yp * yp + zp * zp + wp * wp); +} + +/*! + Returns the squared length of the quaternion. + + \sa length() +*/ +qreal QQuaternion::lengthSquared() const +{ + return xp * xp + yp * yp + zp * zp + wp * wp; +} + +/*! + Returns the normalized unit form of this quaternion. + + If this quaternion is null, then a null quaternion is returned. + If the length of the quaternion is very close to 1, then the quaternion + will be returned as-is. Otherwise the normalized form of the + quaternion of length 1 will be returned. + + \sa length(), normalize() +*/ +QQuaternion QQuaternion::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp) + + double(wp) * double(wp); + if (qFuzzyIsNull(len - 1.0f)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / qSqrt(len); + else + return QQuaternion(0.0f, 0.0f, 0.0f, 0.0f); +} + +/*! + Normalizes the currect quaternion in place. Nothing happens if this + is a null quaternion or the length of the quaternion is very close to 1. + + \sa length(), normalized() +*/ +void QQuaternion::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp) + + double(wp) * double(wp); + if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; + zp /= len; + wp /= len; +} + +/*! + \fn QQuaternion QQuaternion::conjugate() const + + Returns the conjugate of this quaternion, which is + (-x, -y, -z, scalar). +*/ + +/*! + Rotates \a vector with this quaternion to produce a new vector + in 3D space. The following code: + + \code + QVector3D result = q.rotatedVector(vector); + \endcode + + is equivalent to the following: + + \code + QVector3D result = (q * QQuaternion(0, vector) * q.conjugate()).vector(); + \endcode +*/ +QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const +{ + return (*this * QQuaternion(0, vector) * conjugate()).vector(); +} + +/*! + \fn QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion) + + Adds the given \a quaternion to this quaternion and returns a reference to + this quaternion. + + \sa operator-=() +*/ + +/*! + \fn QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion) + + Subtracts the given \a quaternion from this quaternion and returns a + reference to this quaternion. + + \sa operator+=() +*/ + +/*! + \fn QQuaternion &QQuaternion::operator*=(qreal factor) + + Multiplies this quaternion's components by the given \a factor, and + returns a reference to this quaternion. + + \sa operator/=() +*/ + +/*! + \fn QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion) + + Multiplies this quaternion by \a quaternion and returns a reference + to this quaternion. +*/ + +/*! + \fn QQuaternion &QQuaternion::operator/=(qreal divisor) + + Divides this quaternion's components by the given \a divisor, and + returns a reference to this quaternion. + + \sa operator*=() +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + Creates a normalized quaternion that corresponds to rotating through + \a angle degrees about the specified 3D \a axis. +*/ +QQuaternion QQuaternion::fromAxisAndAngle(const QVector3D& axis, qreal angle) +{ + // Algorithm from: + // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q56 + // We normalize the result just in case the values are close + // to zero, as suggested in the above FAQ. + qreal a = (angle / 2.0f) * M_PI / 180.0f; + qreal s = qSin(a); + qreal c = qCos(a); + QVector3D ax = axis.normalized(); + return QQuaternion(c, ax.x() * s, ax.y() * s, ax.z() * s).normalized(); +} + +#endif + +/*! + Creates a normalized quaternion that corresponds to rotating through + \a angle degrees about the 3D axis (\a x, \a y, \a z). +*/ +QQuaternion QQuaternion::fromAxisAndAngle + (qreal x, qreal y, qreal z, qreal angle) +{ + qreal length = qSqrt(x * x + y * y + z * z); + if (!qFuzzyIsNull(length - 1.0f) && !qFuzzyIsNull(length)) { + x /= length; + y /= length; + z /= length; + } + qreal a = (angle / 2.0f) * M_PI / 180.0f; + qreal s = qSin(a); + qreal c = qCos(a); + return QQuaternion(c, x * s, y * s, z * s).normalized(); +} + +/*! + \fn bool operator==(const QQuaternion &q1, const QQuaternion &q2) + \relates QQuaternion + + Returns true if \a q1 is equal to \a q2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn bool operator!=(const QQuaternion &q1, const QQuaternion &q2) + \relates QQuaternion + + Returns true if \a q1 is not equal to \a q2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2) + \relates QQuaternion + + Returns a QQuaternion object that is the sum of the given quaternions, + \a q1 and \a q2; each component is added separately. + + \sa QQuaternion::operator+=() +*/ + +/*! + \fn const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2) + \relates QQuaternion + + Returns a QQuaternion object that is formed by subtracting + \a q2 from \a q1; each component is subtracted separately. + + \sa QQuaternion::operator-=() +*/ + +/*! + \fn const QQuaternion operator*(qreal factor, const QQuaternion &quaternion) + \relates QQuaternion + + Returns a copy of the given \a quaternion, multiplied by the + given \a factor. + + \sa QQuaternion::operator*=() +*/ + +/*! + \fn const QQuaternion operator*(const QQuaternion &quaternion, qreal factor) + \relates QQuaternion + + Returns a copy of the given \a quaternion, multiplied by the + given \a factor. + + \sa QQuaternion::operator*=() +*/ + +/*! + \fn const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2) + \relates QQuaternion + + Multiplies \a q1 and \a q2 using quaternion multiplication. + The result corresponds to applying both of the rotations specified + by \a q1 and \a q2. + + \sa QQuaternion::operator*=() +*/ + +/*! + \fn const QQuaternion operator-(const QQuaternion &quaternion) + \relates QQuaternion + \overload + + Returns a QQuaternion object that is formed by changing the sign of + all three components of the given \a quaternion. + + Equivalent to \c {QQuaternion(0,0,0,0) - quaternion}. +*/ + +/*! + \fn const QQuaternion operator/(const QQuaternion &quaternion, qreal divisor) + \relates QQuaternion + + Returns the QQuaternion object formed by dividing all components of + the given \a quaternion by the given \a divisor. + + \sa QQuaternion::operator/=() +*/ + +/*! + \fn bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2) + \relates QQuaternion + + Returns true if \a q1 and \a q2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +/*! + Interpolates along the shortest spherical path between the + rotational positions \a q1 and \a q2. The value \a t should + be between 0 and 1, indicating the spherical distance to travel + between \a q1 and \a q2. + + If \a t is less than or equal to 0, then \a q1 will be returned. + If \a t is greater than or equal to 1, then \a q2 will be returned. + + \sa nlerp() +*/ +QQuaternion QQuaternion::slerp + (const QQuaternion& q1, const QQuaternion& q2, qreal t) +{ + // Handle the easy cases first. + if (t <= 0.0f) + return q1; + else if (t >= 1.0f) + return q2; + + // Determine the angle between the two quaternions. + QQuaternion q2b; + qreal dot; + dot = q1.xp * q2.xp + q1.yp * q2.yp + q1.zp * q2.zp + q1.wp * q2.wp; + if (dot >= 0.0f) { + q2b = q2; + } else { + q2b = -q2; + dot = -dot; + } + + // Get the scale factors. If they are too small, + // then revert to simple linear interpolation. + qreal factor1 = 1.0f - t; + qreal factor2 = t; + if ((1.0f - dot) > 0.0000001) { + qreal angle = qreal(qAcos(dot)); + qreal sinOfAngle = qreal(qSin(angle)); + if (sinOfAngle > 0.0000001) { + factor1 = qreal(qSin((1.0f - t) * angle)) / sinOfAngle; + factor2 = qreal(qSin(t * angle)) / sinOfAngle; + } + } + + // Construct the result quaternion. + return q1 * factor1 + q2b * factor2; +} + +/*! + Interpolates along the shortest linear path between the rotational + positions \a q1 and \a q2. The value \a t should be between 0 and 1, + indicating the distance to travel between \a q1 and \a q2. + The result will be normalized(). + + If \a t is less than or equal to 0, then \a q1 will be returned. + If \a t is greater than or equal to 1, then \a q2 will be returned. + + The nlerp() function is typically faster than slerp() and will + give approximate results to spherical interpolation that are + good enough for some applications. + + \sa slerp() +*/ +QQuaternion QQuaternion::nlerp + (const QQuaternion& q1, const QQuaternion& q2, qreal t) +{ + // Handle the easy cases first. + if (t <= 0.0f) + return q1; + else if (t >= 1.0f) + return q2; + + // Determine the angle between the two quaternions. + QQuaternion q2b; + qreal dot; + dot = q1.xp * q2.xp + q1.yp * q2.yp + q1.zp * q2.zp + q1.wp * q2.wp; + if (dot >= 0.0f) + q2b = q2; + else + q2b = -q2; + + // Perform the linear interpolation. + return (q1 * (1.0f - t) + q2b * t).normalized(); +} + +/*! + Returns the quaternion as a QVariant. +*/ +QQuaternion::operator QVariant() const +{ + return QVariant(QVariant::Quaternion, this); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QQuaternion &q) +{ + dbg.nospace() << "QQuaternion(scalar:" << q.scalar() + << ", vector:(" << q.x() << ", " + << q.y() << ", " << q.z() << "))"; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QQuaternion &quaternion) + \relates QQuaternion + + Writes the given \a quaternion to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QQuaternion &quaternion) +{ + stream << double(quaternion.scalar()) << double(quaternion.x()) + << double(quaternion.y()) << double(quaternion.z()); + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QQuaternion &quaternion) + \relates QQuaternion + + Reads a quaternion from the given \a stream into the given \a quaternion + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QQuaternion &quaternion) +{ + double scalar, x, y, z; + stream >> scalar; + stream >> x; + stream >> y; + stream >> z; + quaternion.setScalar(qreal(scalar)); + quaternion.setX(qreal(x)); + quaternion.setY(qreal(y)); + quaternion.setZ(qreal(z)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qquaternion.h b/src/gui/math3d/qquaternion.h new file mode 100644 index 0000000000..24c41a01ea --- /dev/null +++ b/src/gui/math3d/qquaternion.h @@ -0,0 +1,330 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUATERNION_H +#define QQUATERNION_H + +#include <QtGui/qvector3d.h> +#include <QtGui/qvector4d.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_QUATERNION + +class QMatrix4x4; +class QVariant; + +class Q_GUI_EXPORT QQuaternion +{ +public: + QQuaternion(); + QQuaternion(qreal scalar, qreal xpos, qreal ypos, qreal zpos); +#ifndef QT_NO_VECTOR3D + QQuaternion(qreal scalar, const QVector3D& vector); +#endif +#ifndef QT_NO_VECTOR4D + explicit QQuaternion(const QVector4D& vector); +#endif + + bool isNull() const; + bool isIdentity() const; + +#ifndef QT_NO_VECTOR3D + QVector3D vector() const; + void setVector(const QVector3D& vector); +#endif + void setVector(qreal x, qreal y, qreal z); + + qreal x() const; + qreal y() const; + qreal z() const; + qreal scalar() const; + + void setX(qreal x); + void setY(qreal y); + void setZ(qreal z); + void setScalar(qreal scalar); + + qreal length() const; + qreal lengthSquared() const; + + QQuaternion normalized() const; + void normalize(); + + QQuaternion conjugate() const; + + QVector3D rotatedVector(const QVector3D& vector) const; + + QQuaternion &operator+=(const QQuaternion &quaternion); + QQuaternion &operator-=(const QQuaternion &quaternion); + QQuaternion &operator*=(qreal factor); + QQuaternion &operator*=(const QQuaternion &quaternion); + QQuaternion &operator/=(qreal divisor); + + friend inline bool operator==(const QQuaternion &q1, const QQuaternion &q2); + friend inline bool operator!=(const QQuaternion &q1, const QQuaternion &q2); + friend inline const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2); + friend inline const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2); + friend inline const QQuaternion operator*(qreal factor, const QQuaternion &quaternion); + friend inline const QQuaternion operator*(const QQuaternion &quaternion, qreal factor); + friend inline const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2); + friend inline const QQuaternion operator-(const QQuaternion &quaternion); + friend inline const QQuaternion operator/(const QQuaternion &quaternion, qreal divisor); + + friend inline bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2); + +#ifndef QT_NO_VECTOR4D + QVector4D toVector4D() const; +#endif + + operator QVariant() const; + +#ifndef QT_NO_VECTOR3D + static QQuaternion fromAxisAndAngle(const QVector3D& axis, qreal angle); +#endif + static QQuaternion fromAxisAndAngle + (qreal x, qreal y, qreal z, qreal angle); + + static QQuaternion slerp + (const QQuaternion& q1, const QQuaternion& q2, qreal t); + static QQuaternion nlerp + (const QQuaternion& q1, const QQuaternion& q2, qreal t); + +private: + qreal wp, xp, yp, zp; +}; + +Q_DECLARE_TYPEINFO(QQuaternion, Q_MOVABLE_TYPE); + +inline QQuaternion::QQuaternion() : wp(1.0f), xp(0.0f), yp(0.0f), zp(0.0f) {} + +inline QQuaternion::QQuaternion(qreal aScalar, qreal xpos, qreal ypos, qreal zpos) : wp(aScalar), xp(xpos), yp(ypos), zp(zpos) {} + + +inline bool QQuaternion::isNull() const +{ + return qIsNull(xp) && qIsNull(yp) && qIsNull(zp) && qIsNull(wp); +} + +inline bool QQuaternion::isIdentity() const +{ + return qIsNull(xp) && qIsNull(yp) && qIsNull(zp) && wp == 1.0f; +} + +inline qreal QQuaternion::x() const { return qreal(xp); } +inline qreal QQuaternion::y() const { return qreal(yp); } +inline qreal QQuaternion::z() const { return qreal(zp); } +inline qreal QQuaternion::scalar() const { return qreal(wp); } + +inline void QQuaternion::setX(qreal aX) { xp = aX; } +inline void QQuaternion::setY(qreal aY) { yp = aY; } +inline void QQuaternion::setZ(qreal aZ) { zp = aZ; } +inline void QQuaternion::setScalar(qreal aScalar) { wp = aScalar; } + +inline QQuaternion QQuaternion::conjugate() const +{ + return QQuaternion(wp, -xp, -yp, -zp); +} + +inline QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion) +{ + xp += quaternion.xp; + yp += quaternion.yp; + zp += quaternion.zp; + wp += quaternion.wp; + return *this; +} + +inline QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion) +{ + xp -= quaternion.xp; + yp -= quaternion.yp; + zp -= quaternion.zp; + wp -= quaternion.wp; + return *this; +} + +inline QQuaternion &QQuaternion::operator*=(qreal factor) +{ + xp *= factor; + yp *= factor; + zp *= factor; + wp *= factor; + return *this; +} + +inline const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2) +{ + qreal ww = (q1.zp + q1.xp) * (q2.xp + q2.yp); + qreal yy = (q1.wp - q1.yp) * (q2.wp + q2.zp); + qreal zz = (q1.wp + q1.yp) * (q2.wp - q2.zp); + qreal xx = ww + yy + zz; + qreal qq = 0.5 * (xx + (q1.zp - q1.xp) * (q2.xp - q2.yp)); + + qreal w = qq - ww + (q1.zp - q1.yp) * (q2.yp - q2.zp); + qreal x = qq - xx + (q1.xp + q1.wp) * (q2.xp + q2.wp); + qreal y = qq - yy + (q1.wp - q1.xp) * (q2.yp + q2.zp); + qreal z = qq - zz + (q1.zp + q1.yp) * (q2.wp - q2.xp); + + return QQuaternion(w, x, y, z); +} + +inline QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion) +{ + *this = *this * quaternion; + return *this; +} + +inline QQuaternion &QQuaternion::operator/=(qreal divisor) +{ + xp /= divisor; + yp /= divisor; + zp /= divisor; + wp /= divisor; + return *this; +} + +inline bool operator==(const QQuaternion &q1, const QQuaternion &q2) +{ + return q1.xp == q2.xp && q1.yp == q2.yp && q1.zp == q2.zp && q1.wp == q2.wp; +} + +inline bool operator!=(const QQuaternion &q1, const QQuaternion &q2) +{ + return q1.xp != q2.xp || q1.yp != q2.yp || q1.zp != q2.zp || q1.wp != q2.wp; +} + +inline const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2) +{ + return QQuaternion(q1.wp + q2.wp, q1.xp + q2.xp, q1.yp + q2.yp, q1.zp + q2.zp); +} + +inline const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2) +{ + return QQuaternion(q1.wp - q2.wp, q1.xp - q2.xp, q1.yp - q2.yp, q1.zp - q2.zp); +} + +inline const QQuaternion operator*(qreal factor, const QQuaternion &quaternion) +{ + return QQuaternion(quaternion.wp * factor, quaternion.xp * factor, quaternion.yp * factor, quaternion.zp * factor); +} + +inline const QQuaternion operator*(const QQuaternion &quaternion, qreal factor) +{ + return QQuaternion(quaternion.wp * factor, quaternion.xp * factor, quaternion.yp * factor, quaternion.zp * factor); +} + +inline const QQuaternion operator-(const QQuaternion &quaternion) +{ + return QQuaternion(-quaternion.wp, -quaternion.xp, -quaternion.yp, -quaternion.zp); +} + +inline const QQuaternion operator/(const QQuaternion &quaternion, qreal divisor) +{ + return QQuaternion(quaternion.wp / divisor, quaternion.xp / divisor, quaternion.yp / divisor, quaternion.zp / divisor); +} + +inline bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2) +{ + return qFuzzyCompare(q1.xp, q2.xp) && + qFuzzyCompare(q1.yp, q2.yp) && + qFuzzyCompare(q1.zp, q2.zp) && + qFuzzyCompare(q1.wp, q2.wp); +} + +#ifndef QT_NO_VECTOR3D + +inline QQuaternion::QQuaternion(qreal aScalar, const QVector3D& aVector) + : wp(aScalar), xp(aVector.x()), yp(aVector.y()), zp(aVector.z()) {} + +inline void QQuaternion::setVector(const QVector3D& aVector) +{ + xp = aVector.x(); + yp = aVector.y(); + zp = aVector.z(); +} + +inline QVector3D QQuaternion::vector() const +{ + return QVector3D(xp, yp, zp); +} + +#endif + +inline void QQuaternion::setVector(qreal aX, qreal aY, qreal aZ) +{ + xp = aX; + yp = aY; + zp = aZ; +} + +#ifndef QT_NO_VECTOR4D + +inline QQuaternion::QQuaternion(const QVector4D& aVector) + : wp(aVector.w()), xp(aVector.x()), yp(aVector.y()), zp(aVector.z()) {} + +inline QVector4D QQuaternion::toVector4D() const +{ + return QVector4D(xp, yp, zp, wp); +} + +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QQuaternion &q); +#endif + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QQuaternion &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QQuaternion &); +#endif + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/math3d/qvector2d.cpp b/src/gui/math3d/qvector2d.cpp new file mode 100644 index 0000000000..1fccfc9f88 --- /dev/null +++ b/src/gui/math3d/qvector2d.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvector2d.h" +#include "qvector3d.h" +#include "qvector4d.h" +#include <QtCore/qdebug.h> +#include <QtCore/qvariant.h> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_VECTOR2D + +/*! + \class QVector2D + \brief The QVector2D class represents a vector or vertex in 2D space. + \since 4.6 + \ingroup painting + \ingroup painting-3D + + The QVector2D class can also be used to represent vertices in 2D space. + We therefore do not need to provide a separate vertex class. + + \bold{Note:} By design values in the QVector2D instance are stored as \c float. + This means that on platforms where the \c qreal arguments to QVector2D + functions are represented by \c double values, it is possible to + lose precision. + + \sa QVector3D, QVector4D, QQuaternion +*/ + +/*! + \fn QVector2D::QVector2D() + + Constructs a null vector, i.e. with coordinates (0, 0, 0). +*/ + +/*! + \fn QVector2D::QVector2D(qreal xpos, qreal ypos) + + Constructs a vector with coordinates (\a xpos, \a ypos). +*/ + +/*! + \fn QVector2D::QVector2D(const QPoint& point) + + Constructs a vector with x and y coordinates from a 2D \a point. +*/ + +/*! + \fn QVector2D::QVector2D(const QPointF& point) + + Constructs a vector with x and y coordinates from a 2D \a point. +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + Constructs a vector with x and y coordinates from a 3D \a vector. + The z coordinate of \a vector is dropped. + + \sa toVector3D() +*/ +QVector2D::QVector2D(const QVector3D& vector) +{ + xp = vector.xp; + yp = vector.yp; +} + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + Constructs a vector with x and y coordinates from a 3D \a vector. + The z and w coordinates of \a vector are dropped. + + \sa toVector4D() +*/ +QVector2D::QVector2D(const QVector4D& vector) +{ + xp = vector.xp; + yp = vector.yp; +} + +#endif + +/*! + \fn bool QVector2D::isNull() const + + Returns true if the x and y coordinates are set to 0.0, + otherwise returns false. +*/ + +/*! + \fn qreal QVector2D::x() const + + Returns the x coordinate of this point. + + \sa setX(), y() +*/ + +/*! + \fn qreal QVector2D::y() const + + Returns the y coordinate of this point. + + \sa setY(), x() +*/ + +/*! + \fn void QVector2D::setX(qreal x) + + Sets the x coordinate of this point to the given \a x coordinate. + + \sa x(), setY() +*/ + +/*! + \fn void QVector2D::setY(qreal y) + + Sets the y coordinate of this point to the given \a y coordinate. + + \sa y(), setX() +*/ + +/*! + Returns the length of the vector from the origin. + + \sa lengthSquared(), normalized() +*/ +qreal QVector2D::length() const +{ + return qSqrt(xp * xp + yp * yp); +} + +/*! + Returns the squared length of the vector from the origin. + This is equivalent to the dot product of the vector with itself. + + \sa length(), dotProduct() +*/ +qreal QVector2D::lengthSquared() const +{ + return xp * xp + yp * yp; +} + +/*! + Returns the normalized unit vector form of this vector. + + If this vector is null, then a null vector is returned. If the length + of the vector is very close to 1, then the vector will be returned as-is. + Otherwise the normalized form of the vector of length 1 will be returned. + + \sa length(), normalize() +*/ +QVector2D QVector2D::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp); + if (qFuzzyIsNull(len - 1.0f)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / qSqrt(len); + else + return QVector2D(); +} + +/*! + Normalizes the currect vector in place. Nothing happens if this + vector is a null vector or the length of the vector is very close to 1. + + \sa length(), normalized() +*/ +void QVector2D::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp); + if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; +} + +/*! + \fn QVector2D &QVector2D::operator+=(const QVector2D &vector) + + Adds the given \a vector to this vector and returns a reference to + this vector. + + \sa operator-=() +*/ + +/*! + \fn QVector2D &QVector2D::operator-=(const QVector2D &vector) + + Subtracts the given \a vector from this vector and returns a reference to + this vector. + + \sa operator+=() +*/ + +/*! + \fn QVector2D &QVector2D::operator*=(qreal factor) + + Multiplies this vector's coordinates by the given \a factor, and + returns a reference to this vector. + + \sa operator/=() +*/ + +/*! + \fn QVector2D &QVector2D::operator*=(const QVector2D &vector) + + Multiplies the components of this vector by the corresponding + components in \a vector. +*/ + +/*! + \fn QVector2D &QVector2D::operator/=(qreal divisor) + + Divides this vector's coordinates by the given \a divisor, and + returns a reference to this vector. + + \sa operator*=() +*/ + +/*! + Returns the dot product of \a v1 and \a v2. +*/ +qreal QVector2D::dotProduct(const QVector2D& v1, const QVector2D& v2) +{ + return v1.xp * v2.xp + v1.yp * v2.yp; +} + +/*! + \fn bool operator==(const QVector2D &v1, const QVector2D &v2) + \relates QVector2D + + Returns true if \a v1 is equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn bool operator!=(const QVector2D &v1, const QVector2D &v2) + \relates QVector2D + + Returns true if \a v1 is not equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn const QVector2D operator+(const QVector2D &v1, const QVector2D &v2) + \relates QVector2D + + Returns a QVector2D object that is the sum of the given vectors, \a v1 + and \a v2; each component is added separately. + + \sa QVector2D::operator+=() +*/ + +/*! + \fn const QVector2D operator-(const QVector2D &v1, const QVector2D &v2) + \relates QVector2D + + Returns a QVector2D object that is formed by subtracting \a v2 from \a v1; + each component is subtracted separately. + + \sa QVector2D::operator-=() +*/ + +/*! + \fn const QVector2D operator*(qreal factor, const QVector2D &vector) + \relates QVector2D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector2D::operator*=() +*/ + +/*! + \fn const QVector2D operator*(const QVector2D &vector, qreal factor) + \relates QVector2D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector2D::operator*=() +*/ + +/*! + \fn const QVector2D operator*(const QVector2D &v1, const QVector2D &v2) + \relates QVector2D + + Multiplies the components of \a v1 by the corresponding + components in \a v2. +*/ + +/*! + \fn const QVector2D operator-(const QVector2D &vector) + \relates QVector2D + \overload + + Returns a QVector2D object that is formed by changing the sign of + the components of the given \a vector. + + Equivalent to \c {QVector2D(0,0) - vector}. +*/ + +/*! + \fn const QVector2D operator/(const QVector2D &vector, qreal divisor) + \relates QVector2D + + Returns the QVector2D object formed by dividing all three components of + the given \a vector by the given \a divisor. + + \sa QVector2D::operator/=() +*/ + +/*! + \fn bool qFuzzyCompare(const QVector2D& v1, const QVector2D& v2) + \relates QVector2D + + Returns true if \a v1 and \a v2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +#ifndef QT_NO_VECTOR3D + +/*! + Returns the 3D form of this 2D vector, with the z coordinate set to zero. + + \sa toVector4D(), toPoint() +*/ +QVector3D QVector2D::toVector3D() const +{ + return QVector3D(xp, yp, 0.0f, 1); +} + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + Returns the 4D form of this 2D vector, with the z and w coordinates set to zero. + + \sa toVector3D(), toPoint() +*/ +QVector4D QVector2D::toVector4D() const +{ + return QVector4D(xp, yp, 0.0f, 0.0f, 1); +} + +#endif + +/*! + \fn QPoint QVector2D::toPoint() const + + Returns the QPoint form of this 2D vector. + + \sa toPointF(), toVector3D() +*/ + +/*! + \fn QPointF QVector2D::toPointF() const + + Returns the QPointF form of this 2D vector. + + \sa toPoint(), toVector3D() +*/ + +/*! + Returns the 2D vector as a QVariant. +*/ +QVector2D::operator QVariant() const +{ + return QVariant(QVariant::Vector2D, this); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QVector2D &vector) +{ + dbg.nospace() << "QVector2D(" << vector.x() << ", " << vector.y() << ')'; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QVector2D &vector) + \relates QVector2D + + Writes the given \a vector to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QVector2D &vector) +{ + stream << double(vector.x()) << double(vector.y()); + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QVector2D &vector) + \relates QVector2D + + Reads a 2D vector from the given \a stream into the given \a vector + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QVector2D &vector) +{ + double x, y; + stream >> x; + stream >> y; + vector.setX(qreal(x)); + vector.setY(qreal(y)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +#endif // QT_NO_VECTOR2D + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qvector2d.h b/src/gui/math3d/qvector2d.h new file mode 100644 index 0000000000..089caf3620 --- /dev/null +++ b/src/gui/math3d/qvector2d.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVECTOR2D_H +#define QVECTOR2D_H + +#include <QtCore/qpoint.h> +#include <QtCore/qmetatype.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVector3D; +class QVector4D; +class QVariant; + +#ifndef QT_NO_VECTOR2D + +class Q_GUI_EXPORT QVector2D +{ +public: + QVector2D(); + QVector2D(qreal xpos, qreal ypos); + explicit QVector2D(const QPoint& point); + explicit QVector2D(const QPointF& point); +#ifndef QT_NO_VECTOR3D + explicit QVector2D(const QVector3D& vector); +#endif +#ifndef QT_NO_VECTOR4D + explicit QVector2D(const QVector4D& vector); +#endif + + bool isNull() const; + + qreal x() const; + qreal y() const; + + void setX(qreal x); + void setY(qreal y); + + qreal length() const; + qreal lengthSquared() const; + + QVector2D normalized() const; + void normalize(); + + QVector2D &operator+=(const QVector2D &vector); + QVector2D &operator-=(const QVector2D &vector); + QVector2D &operator*=(qreal factor); + QVector2D &operator*=(const QVector2D &vector); + QVector2D &operator/=(qreal divisor); + + static qreal dotProduct(const QVector2D& v1, const QVector2D& v2); + + friend inline bool operator==(const QVector2D &v1, const QVector2D &v2); + friend inline bool operator!=(const QVector2D &v1, const QVector2D &v2); + friend inline const QVector2D operator+(const QVector2D &v1, const QVector2D &v2); + friend inline const QVector2D operator-(const QVector2D &v1, const QVector2D &v2); + friend inline const QVector2D operator*(qreal factor, const QVector2D &vector); + friend inline const QVector2D operator*(const QVector2D &vector, qreal factor); + friend inline const QVector2D operator*(const QVector2D &v1, const QVector2D &v2); + friend inline const QVector2D operator-(const QVector2D &vector); + friend inline const QVector2D operator/(const QVector2D &vector, qreal divisor); + + friend inline bool qFuzzyCompare(const QVector2D& v1, const QVector2D& v2); + +#ifndef QT_NO_VECTOR3D + QVector3D toVector3D() const; +#endif +#ifndef QT_NO_VECTOR4D + QVector4D toVector4D() const; +#endif + + QPoint toPoint() const; + QPointF toPointF() const; + + operator QVariant() const; + +private: + float xp, yp; + + QVector2D(float xpos, float ypos, int dummy); + + friend class QVector3D; + friend class QVector4D; +}; + +Q_DECLARE_TYPEINFO(QVector2D, Q_MOVABLE_TYPE); + +inline QVector2D::QVector2D() : xp(0.0f), yp(0.0f) {} + +inline QVector2D::QVector2D(float xpos, float ypos, int) : xp(xpos), yp(ypos) {} + +inline QVector2D::QVector2D(qreal xpos, qreal ypos) : xp(xpos), yp(ypos) {} + +inline QVector2D::QVector2D(const QPoint& point) : xp(point.x()), yp(point.y()) {} + +inline QVector2D::QVector2D(const QPointF& point) : xp(point.x()), yp(point.y()) {} + +inline bool QVector2D::isNull() const +{ + return qIsNull(xp) && qIsNull(yp); +} + +inline qreal QVector2D::x() const { return qreal(xp); } +inline qreal QVector2D::y() const { return qreal(yp); } + +inline void QVector2D::setX(qreal aX) { xp = aX; } +inline void QVector2D::setY(qreal aY) { yp = aY; } + +inline QVector2D &QVector2D::operator+=(const QVector2D &vector) +{ + xp += vector.xp; + yp += vector.yp; + return *this; +} + +inline QVector2D &QVector2D::operator-=(const QVector2D &vector) +{ + xp -= vector.xp; + yp -= vector.yp; + return *this; +} + +inline QVector2D &QVector2D::operator*=(qreal factor) +{ + xp *= factor; + yp *= factor; + return *this; +} + +inline QVector2D &QVector2D::operator*=(const QVector2D &vector) +{ + xp *= vector.xp; + yp *= vector.yp; + return *this; +} + +inline QVector2D &QVector2D::operator/=(qreal divisor) +{ + xp /= divisor; + yp /= divisor; + return *this; +} + +inline bool operator==(const QVector2D &v1, const QVector2D &v2) +{ + return v1.xp == v2.xp && v1.yp == v2.yp; +} + +inline bool operator!=(const QVector2D &v1, const QVector2D &v2) +{ + return v1.xp != v2.xp || v1.yp != v2.yp; +} + +inline const QVector2D operator+(const QVector2D &v1, const QVector2D &v2) +{ + return QVector2D(v1.xp + v2.xp, v1.yp + v2.yp, 1); +} + +inline const QVector2D operator-(const QVector2D &v1, const QVector2D &v2) +{ + return QVector2D(v1.xp - v2.xp, v1.yp - v2.yp, 1); +} + +inline const QVector2D operator*(qreal factor, const QVector2D &vector) +{ + return QVector2D(vector.xp * factor, vector.yp * factor, 1); +} + +inline const QVector2D operator*(const QVector2D &vector, qreal factor) +{ + return QVector2D(vector.xp * factor, vector.yp * factor, 1); +} + +inline const QVector2D operator*(const QVector2D &v1, const QVector2D &v2) +{ + return QVector2D(v1.xp * v2.xp, v1.yp * v2.yp, 1); +} + +inline const QVector2D operator-(const QVector2D &vector) +{ + return QVector2D(-vector.xp, -vector.yp, 1); +} + +inline const QVector2D operator/(const QVector2D &vector, qreal divisor) +{ + return QVector2D(vector.xp / divisor, vector.yp / divisor, 1); +} + +inline bool qFuzzyCompare(const QVector2D& v1, const QVector2D& v2) +{ + return qFuzzyCompare(v1.xp, v2.xp) && qFuzzyCompare(v1.yp, v2.yp); +} + +inline QPoint QVector2D::toPoint() const +{ + return QPoint(qRound(xp), qRound(yp)); +} + +inline QPointF QVector2D::toPointF() const +{ + return QPointF(qreal(xp), qreal(yp)); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QVector2D &vector); +#endif + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QVector2D &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QVector2D &); +#endif + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/math3d/qvector3d.cpp b/src/gui/math3d/qvector3d.cpp new file mode 100644 index 0000000000..7bf0400acb --- /dev/null +++ b/src/gui/math3d/qvector3d.cpp @@ -0,0 +1,629 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvector3d.h" +#include "qvector2d.h" +#include "qvector4d.h" +#include <QtCore/qmath.h> +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_VECTOR3D + +/*! + \class QVector3D + \brief The QVector3D class represents a vector or vertex in 3D space. + \since 4.6 + \ingroup painting-3D + + Vectors are one of the main building blocks of 3D representation and + drawing. They consist of three coordinates, traditionally called + x, y, and z. + + The QVector3D class can also be used to represent vertices in 3D space. + We therefore do not need to provide a separate vertex class. + + \bold{Note:} By design values in the QVector3D instance are stored as \c float. + This means that on platforms where the \c qreal arguments to QVector3D + functions are represented by \c double values, it is possible to + lose precision. + + \sa QVector2D, QVector4D, QQuaternion +*/ + +/*! + \fn QVector3D::QVector3D() + + Constructs a null vector, i.e. with coordinates (0, 0, 0). +*/ + +/*! + \fn QVector3D::QVector3D(qreal xpos, qreal ypos, qreal zpos) + + Constructs a vector with coordinates (\a xpos, \a ypos, \a zpos). +*/ + +/*! + \fn QVector3D::QVector3D(const QPoint& point) + + Constructs a vector with x and y coordinates from a 2D \a point, and a + z coordinate of 0. +*/ + +/*! + \fn QVector3D::QVector3D(const QPointF& point) + + Constructs a vector with x and y coordinates from a 2D \a point, and a + z coordinate of 0. +*/ + +#ifndef QT_NO_VECTOR2D + +/*! + Constructs a 3D vector from the specified 2D \a vector. The z + coordinate is set to zero. + + \sa toVector2D() +*/ +QVector3D::QVector3D(const QVector2D& vector) +{ + xp = vector.xp; + yp = vector.yp; + zp = 0.0f; +} + +/*! + Constructs a 3D vector from the specified 2D \a vector. The z + coordinate is set to \a zpos. + + \sa toVector2D() +*/ +QVector3D::QVector3D(const QVector2D& vector, qreal zpos) +{ + xp = vector.xp; + yp = vector.yp; + zp = zpos; +} + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + Constructs a 3D vector from the specified 4D \a vector. The w + coordinate is dropped. + + \sa toVector4D() +*/ +QVector3D::QVector3D(const QVector4D& vector) +{ + xp = vector.xp; + yp = vector.yp; + zp = vector.zp; +} + +#endif + +/*! + \fn bool QVector3D::isNull() const + + Returns true if the x, y, and z coordinates are set to 0.0, + otherwise returns false. +*/ + +/*! + \fn qreal QVector3D::x() const + + Returns the x coordinate of this point. + + \sa setX(), y(), z() +*/ + +/*! + \fn qreal QVector3D::y() const + + Returns the y coordinate of this point. + + \sa setY(), x(), z() +*/ + +/*! + \fn qreal QVector3D::z() const + + Returns the z coordinate of this point. + + \sa setZ(), x(), y() +*/ + +/*! + \fn void QVector3D::setX(qreal x) + + Sets the x coordinate of this point to the given \a x coordinate. + + \sa x(), setY(), setZ() +*/ + +/*! + \fn void QVector3D::setY(qreal y) + + Sets the y coordinate of this point to the given \a y coordinate. + + \sa y(), setX(), setZ() +*/ + +/*! + \fn void QVector3D::setZ(qreal z) + + Sets the z coordinate of this point to the given \a z coordinate. + + \sa z(), setX(), setY() +*/ + +/*! + Returns the normalized unit vector form of this vector. + + If this vector is null, then a null vector is returned. If the length + of the vector is very close to 1, then the vector will be returned as-is. + Otherwise the normalized form of the vector of length 1 will be returned. + + \sa length(), normalize() +*/ +QVector3D QVector3D::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp); + if (qFuzzyIsNull(len - 1.0f)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / qSqrt(len); + else + return QVector3D(); +} + +/*! + Normalizes the currect vector in place. Nothing happens if this + vector is a null vector or the length of the vector is very close to 1. + + \sa length(), normalized() +*/ +void QVector3D::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp); + if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; + zp /= len; +} + +/*! + \fn QVector3D &QVector3D::operator+=(const QVector3D &vector) + + Adds the given \a vector to this vector and returns a reference to + this vector. + + \sa operator-=() +*/ + +/*! + \fn QVector3D &QVector3D::operator-=(const QVector3D &vector) + + Subtracts the given \a vector from this vector and returns a reference to + this vector. + + \sa operator+=() +*/ + +/*! + \fn QVector3D &QVector3D::operator*=(qreal factor) + + Multiplies this vector's coordinates by the given \a factor, and + returns a reference to this vector. + + \sa operator/=() +*/ + +/*! + \fn QVector3D &QVector3D::operator*=(const QVector3D& vector) + \overload + + Multiplies the components of this vector by the corresponding + components in \a vector. + + Note: this is not the same as the crossProduct() of this + vector and \a vector. + + \sa crossProduct() +*/ + +/*! + \fn QVector3D &QVector3D::operator/=(qreal divisor) + + Divides this vector's coordinates by the given \a divisor, and + returns a reference to this vector. + + \sa operator*=() +*/ + +/*! + Returns the dot product of \a v1 and \a v2. +*/ +qreal QVector3D::dotProduct(const QVector3D& v1, const QVector3D& v2) +{ + return v1.xp * v2.xp + v1.yp * v2.yp + v1.zp * v2.zp; +} + +/*! + Returns the cross-product of vectors \a v1 and \a v2, which corresponds + to the normal vector of a plane defined by \a v1 and \a v2. + + \sa normal() +*/ +QVector3D QVector3D::crossProduct(const QVector3D& v1, const QVector3D& v2) +{ + return QVector3D(v1.yp * v2.zp - v1.zp * v2.yp, + v1.zp * v2.xp - v1.xp * v2.zp, + v1.xp * v2.yp - v1.yp * v2.xp, 1); +} + +/*! + Returns the normal vector of a plane defined by vectors \a v1 and \a v2, + normalized to be a unit vector. + + Use crossProduct() to compute the cross-product of \a v1 and \a v2 if you + do not need the result to be normalized to a unit vector. + + \sa crossProduct(), distanceToPlane() +*/ +QVector3D QVector3D::normal(const QVector3D& v1, const QVector3D& v2) +{ + return crossProduct(v1, v2).normalized(); +} + +/*! + \overload + + Returns the normal vector of a plane defined by vectors + \a v2 - \a v1 and \a v3 - \a v1, normalized to be a unit vector. + + Use crossProduct() to compute the cross-product of \a v2 - \a v1 and + \a v3 - \a v1 if you do not need the result to be normalized to a + unit vector. + + \sa crossProduct(), distanceToPlane() +*/ +QVector3D QVector3D::normal + (const QVector3D& v1, const QVector3D& v2, const QVector3D& v3) +{ + return crossProduct((v2 - v1), (v3 - v1)).normalized(); +} + +/*! + Returns the distance from this vertex to a plane defined by + the vertex \a plane and a \a normal unit vector. The \a normal + parameter is assumed to have been normalized to a unit vector. + + The return value will be negative if the vertex is below the plane, + or zero if it is on the plane. + + \sa normal(), distanceToLine() +*/ +qreal QVector3D::distanceToPlane + (const QVector3D& plane, const QVector3D& normal) const +{ + return dotProduct(*this - plane, normal); +} + +/*! + \overload + + Returns the distance from this vertex a plane defined by + the vertices \a plane1, \a plane2 and \a plane3. + + The return value will be negative if the vertex is below the plane, + or zero if it is on the plane. + + The two vectors that define the plane are \a plane2 - \a plane1 + and \a plane3 - \a plane1. + + \sa normal(), distanceToLine() +*/ +qreal QVector3D::distanceToPlane + (const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3) const +{ + QVector3D n = normal(plane2 - plane1, plane3 - plane1); + return dotProduct(*this - plane1, n); +} + +/*! + Returns the distance that this vertex is from a line defined + by \a point and the unit vector \a direction. + + If \a direction is a null vector, then it does not define a line. + In that case, the distance from \a point to this vertex is returned. + + \sa distanceToPlane() +*/ +qreal QVector3D::distanceToLine + (const QVector3D& point, const QVector3D& direction) const +{ + if (direction.isNull()) + return (*this - point).length(); + QVector3D p = point + dotProduct(*this - point, direction) * direction; + return (*this - p).length(); +} + +/*! + \fn bool operator==(const QVector3D &v1, const QVector3D &v2) + \relates QVector3D + + Returns true if \a v1 is equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn bool operator!=(const QVector3D &v1, const QVector3D &v2) + \relates QVector3D + + Returns true if \a v1 is not equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn const QVector3D operator+(const QVector3D &v1, const QVector3D &v2) + \relates QVector3D + + Returns a QVector3D object that is the sum of the given vectors, \a v1 + and \a v2; each component is added separately. + + \sa QVector3D::operator+=() +*/ + +/*! + \fn const QVector3D operator-(const QVector3D &v1, const QVector3D &v2) + \relates QVector3D + + Returns a QVector3D object that is formed by subtracting \a v2 from \a v1; + each component is subtracted separately. + + \sa QVector3D::operator-=() +*/ + +/*! + \fn const QVector3D operator*(qreal factor, const QVector3D &vector) + \relates QVector3D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector3D::operator*=() +*/ + +/*! + \fn const QVector3D operator*(const QVector3D &vector, qreal factor) + \relates QVector3D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector3D::operator*=() +*/ + +/*! + \fn const QVector3D operator*(const QVector3D &v1, const QVector3D& v2) + \relates QVector3D + + Multiplies the components of \a v1 by the corresponding components in \a v2. + + Note: this is not the same as the crossProduct() of \a v1 and \a v2. + + \sa QVector3D::crossProduct() +*/ + +/*! + \fn const QVector3D operator-(const QVector3D &vector) + \relates QVector3D + \overload + + Returns a QVector3D object that is formed by changing the sign of + all three components of the given \a vector. + + Equivalent to \c {QVector3D(0,0,0) - vector}. +*/ + +/*! + \fn const QVector3D operator/(const QVector3D &vector, qreal divisor) + \relates QVector3D + + Returns the QVector3D object formed by dividing all three components of + the given \a vector by the given \a divisor. + + \sa QVector3D::operator/=() +*/ + +/*! + \fn bool qFuzzyCompare(const QVector3D& v1, const QVector3D& v2) + \relates QVector3D + + Returns true if \a v1 and \a v2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +#ifndef QT_NO_VECTOR2D + +/*! + Returns the 2D vector form of this 3D vector, dropping the z coordinate. + + \sa toVector4D(), toPoint() +*/ +QVector2D QVector3D::toVector2D() const +{ + return QVector2D(xp, yp, 1); +} + +#endif + +#ifndef QT_NO_VECTOR4D + +/*! + Returns the 4D form of this 3D vector, with the w coordinate set to zero. + + \sa toVector2D(), toPoint() +*/ +QVector4D QVector3D::toVector4D() const +{ + return QVector4D(xp, yp, zp, 0.0f, 1); +} + +#endif + +/*! + \fn QPoint QVector3D::toPoint() const + + Returns the QPoint form of this 3D vector. The z coordinate + is dropped. + + \sa toPointF(), toVector2D() +*/ + +/*! + \fn QPointF QVector3D::toPointF() const + + Returns the QPointF form of this 3D vector. The z coordinate + is dropped. + + \sa toPoint(), toVector2D() +*/ + +/*! + Returns the 3D vector as a QVariant. +*/ +QVector3D::operator QVariant() const +{ + return QVariant(QVariant::Vector3D, this); +} + +/*! + Returns the length of the vector from the origin. + + \sa lengthSquared(), normalized() +*/ +qreal QVector3D::length() const +{ + return qSqrt(xp * xp + yp * yp + zp * zp); +} + +/*! + Returns the squared length of the vector from the origin. + This is equivalent to the dot product of the vector with itself. + + \sa length(), dotProduct() +*/ +qreal QVector3D::lengthSquared() const +{ + return xp * xp + yp * yp + zp * zp; +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QVector3D &vector) +{ + dbg.nospace() << "QVector3D(" + << vector.x() << ", " << vector.y() << ", " << vector.z() << ')'; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QVector3D &vector) + \relates QVector3D + + Writes the given \a vector to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QVector3D &vector) +{ + stream << double(vector.x()) << double(vector.y()) + << double(vector.z()); + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QVector3D &vector) + \relates QVector3D + + Reads a 3D vector from the given \a stream into the given \a vector + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QVector3D &vector) +{ + double x, y, z; + stream >> x; + stream >> y; + stream >> z; + vector.setX(qreal(x)); + vector.setY(qreal(y)); + vector.setZ(qreal(z)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +#endif // QT_NO_VECTOR3D + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qvector3d.h b/src/gui/math3d/qvector3d.h new file mode 100644 index 0000000000..c33cf42d4b --- /dev/null +++ b/src/gui/math3d/qvector3d.h @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVECTOR3D_H +#define QVECTOR3D_H + +#include <QtCore/qpoint.h> +#include <QtCore/qmetatype.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QMatrix4x4; +class QVector2D; +class QVector4D; + +#ifndef QT_NO_VECTOR3D + +class Q_GUI_EXPORT QVector3D +{ +public: + QVector3D(); + QVector3D(qreal xpos, qreal ypos, qreal zpos); + explicit QVector3D(const QPoint& point); + explicit QVector3D(const QPointF& point); +#ifndef QT_NO_VECTOR2D + QVector3D(const QVector2D& vector); + QVector3D(const QVector2D& vector, qreal zpos); +#endif +#ifndef QT_NO_VECTOR4D + explicit QVector3D(const QVector4D& vector); +#endif + + bool isNull() const; + + qreal x() const; + qreal y() const; + qreal z() const; + + void setX(qreal x); + void setY(qreal y); + void setZ(qreal z); + + qreal length() const; + qreal lengthSquared() const; + + QVector3D normalized() const; + void normalize(); + + QVector3D &operator+=(const QVector3D &vector); + QVector3D &operator-=(const QVector3D &vector); + QVector3D &operator*=(qreal factor); + QVector3D &operator*=(const QVector3D& vector); + QVector3D &operator/=(qreal divisor); + + static qreal dotProduct(const QVector3D& v1, const QVector3D& v2); + static QVector3D crossProduct(const QVector3D& v1, const QVector3D& v2); + static QVector3D normal(const QVector3D& v1, const QVector3D& v2); + static QVector3D normal + (const QVector3D& v1, const QVector3D& v2, const QVector3D& v3); + + qreal distanceToPlane(const QVector3D& plane, const QVector3D& normal) const; + qreal distanceToPlane(const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3) const; + qreal distanceToLine(const QVector3D& point, const QVector3D& direction) const; + + friend inline bool operator==(const QVector3D &v1, const QVector3D &v2); + friend inline bool operator!=(const QVector3D &v1, const QVector3D &v2); + friend inline const QVector3D operator+(const QVector3D &v1, const QVector3D &v2); + friend inline const QVector3D operator-(const QVector3D &v1, const QVector3D &v2); + friend inline const QVector3D operator*(qreal factor, const QVector3D &vector); + friend inline const QVector3D operator*(const QVector3D &vector, qreal factor); + friend const QVector3D operator*(const QVector3D &v1, const QVector3D& v2); + friend inline const QVector3D operator-(const QVector3D &vector); + friend inline const QVector3D operator/(const QVector3D &vector, qreal divisor); + + friend inline bool qFuzzyCompare(const QVector3D& v1, const QVector3D& v2); + +#ifndef QT_NO_VECTOR2D + QVector2D toVector2D() const; +#endif +#ifndef QT_NO_VECTOR4D + QVector4D toVector4D() const; +#endif + + QPoint toPoint() const; + QPointF toPointF() const; + + operator QVariant() const; + +private: + float xp, yp, zp; + + QVector3D(float xpos, float ypos, float zpos, int dummy); + + friend class QVector2D; + friend class QVector4D; +#ifndef QT_NO_MATRIX4X4 + friend QVector3D operator*(const QVector3D& vector, const QMatrix4x4& matrix); + friend QVector3D operator*(const QMatrix4x4& matrix, const QVector3D& vector); +#endif +}; + +Q_DECLARE_TYPEINFO(QVector3D, Q_MOVABLE_TYPE); + +inline QVector3D::QVector3D() : xp(0.0f), yp(0.0f), zp(0.0f) {} + +inline QVector3D::QVector3D(qreal xpos, qreal ypos, qreal zpos) : xp(xpos), yp(ypos), zp(zpos) {} + +inline QVector3D::QVector3D(float xpos, float ypos, float zpos, int) : xp(xpos), yp(ypos), zp(zpos) {} + +inline QVector3D::QVector3D(const QPoint& point) : xp(point.x()), yp(point.y()), zp(0.0f) {} + +inline QVector3D::QVector3D(const QPointF& point) : xp(point.x()), yp(point.y()), zp(0.0f) {} + +inline bool QVector3D::isNull() const +{ + return qIsNull(xp) && qIsNull(yp) && qIsNull(zp); +} + +inline qreal QVector3D::x() const { return qreal(xp); } +inline qreal QVector3D::y() const { return qreal(yp); } +inline qreal QVector3D::z() const { return qreal(zp); } + +inline void QVector3D::setX(qreal aX) { xp = aX; } +inline void QVector3D::setY(qreal aY) { yp = aY; } +inline void QVector3D::setZ(qreal aZ) { zp = aZ; } + +inline QVector3D &QVector3D::operator+=(const QVector3D &vector) +{ + xp += vector.xp; + yp += vector.yp; + zp += vector.zp; + return *this; +} + +inline QVector3D &QVector3D::operator-=(const QVector3D &vector) +{ + xp -= vector.xp; + yp -= vector.yp; + zp -= vector.zp; + return *this; +} + +inline QVector3D &QVector3D::operator*=(qreal factor) +{ + xp *= factor; + yp *= factor; + zp *= factor; + return *this; +} + +inline QVector3D &QVector3D::operator*=(const QVector3D& vector) +{ + xp *= vector.xp; + yp *= vector.yp; + zp *= vector.zp; + return *this; +} + +inline QVector3D &QVector3D::operator/=(qreal divisor) +{ + xp /= divisor; + yp /= divisor; + zp /= divisor; + return *this; +} + +inline bool operator==(const QVector3D &v1, const QVector3D &v2) +{ + return v1.xp == v2.xp && v1.yp == v2.yp && v1.zp == v2.zp; +} + +inline bool operator!=(const QVector3D &v1, const QVector3D &v2) +{ + return v1.xp != v2.xp || v1.yp != v2.yp || v1.zp != v2.zp; +} + +inline const QVector3D operator+(const QVector3D &v1, const QVector3D &v2) +{ + return QVector3D(v1.xp + v2.xp, v1.yp + v2.yp, v1.zp + v2.zp, 1); +} + +inline const QVector3D operator-(const QVector3D &v1, const QVector3D &v2) +{ + return QVector3D(v1.xp - v2.xp, v1.yp - v2.yp, v1.zp - v2.zp, 1); +} + +inline const QVector3D operator*(qreal factor, const QVector3D &vector) +{ + return QVector3D(vector.xp * factor, vector.yp * factor, vector.zp * factor, 1); +} + +inline const QVector3D operator*(const QVector3D &vector, qreal factor) +{ + return QVector3D(vector.xp * factor, vector.yp * factor, vector.zp * factor, 1); +} + +inline const QVector3D operator*(const QVector3D &v1, const QVector3D& v2) +{ + return QVector3D(v1.xp * v2.xp, v1.yp * v2.yp, v1.zp * v2.zp, 1); +} + +inline const QVector3D operator-(const QVector3D &vector) +{ + return QVector3D(-vector.xp, -vector.yp, -vector.zp, 1); +} + +inline const QVector3D operator/(const QVector3D &vector, qreal divisor) +{ + return QVector3D(vector.xp / divisor, vector.yp / divisor, vector.zp / divisor, 1); +} + +inline bool qFuzzyCompare(const QVector3D& v1, const QVector3D& v2) +{ + return qFuzzyCompare(v1.xp, v2.xp) && + qFuzzyCompare(v1.yp, v2.yp) && + qFuzzyCompare(v1.zp, v2.zp); +} + +inline QPoint QVector3D::toPoint() const +{ + return QPoint(qRound(xp), qRound(yp)); +} + +inline QPointF QVector3D::toPointF() const +{ + return QPointF(qreal(xp), qreal(yp)); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QVector3D &vector); +#endif + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QVector3D &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QVector3D &); +#endif + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/math3d/qvector4d.cpp b/src/gui/math3d/qvector4d.cpp new file mode 100644 index 0000000000..23befc01a5 --- /dev/null +++ b/src/gui/math3d/qvector4d.cpp @@ -0,0 +1,584 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvector4d.h" +#include "qvector3d.h" +#include "qvector2d.h" +#include <QtCore/qdebug.h> +#include <QtCore/qvariant.h> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_VECTOR4D + +/*! + \class QVector4D + \brief The QVector4D class represents a vector or vertex in 4D space. + \since 4.6 + \ingroup painting-3D + + The QVector4D class can also be used to represent vertices in 4D space. + We therefore do not need to provide a separate vertex class. + + \bold{Note:} By design values in the QVector4D instance are stored as \c float. + This means that on platforms where the \c qreal arguments to QVector4D + functions are represented by \c double values, it is possible to + lose precision. + + \sa QQuaternion, QVector2D, QVector3D +*/ + +/*! + \fn QVector4D::QVector4D() + + Constructs a null vector, i.e. with coordinates (0, 0, 0, 0). +*/ + +/*! + \fn QVector4D::QVector4D(qreal xpos, qreal ypos, qreal zpos, qreal wpos) + + Constructs a vector with coordinates (\a xpos, \a ypos, \a zpos, \a wpos). +*/ + +/*! + \fn QVector4D::QVector4D(const QPoint& point) + + Constructs a vector with x and y coordinates from a 2D \a point, and + z and w coordinates of 0. +*/ + +/*! + \fn QVector4D::QVector4D(const QPointF& point) + + Constructs a vector with x and y coordinates from a 2D \a point, and + z and w coordinates of 0. +*/ + +#ifndef QT_NO_VECTOR2D + +/*! + Constructs a 4D vector from the specified 2D \a vector. The z + and w coordinates are set to zero. + + \sa toVector2D() +*/ +QVector4D::QVector4D(const QVector2D& vector) +{ + xp = vector.xp; + yp = vector.yp; + zp = 0.0f; + wp = 0.0f; +} + +/*! + Constructs a 4D vector from the specified 2D \a vector. The z + and w coordinates are set to \a zpos and \a wpos respectively. + + \sa toVector2D() +*/ +QVector4D::QVector4D(const QVector2D& vector, qreal zpos, qreal wpos) +{ + xp = vector.xp; + yp = vector.yp; + zp = zpos; + wp = wpos; +} + +#endif + +#ifndef QT_NO_VECTOR3D + +/*! + Constructs a 4D vector from the specified 3D \a vector. The w + coordinate is set to zero. + + \sa toVector3D() +*/ +QVector4D::QVector4D(const QVector3D& vector) +{ + xp = vector.xp; + yp = vector.yp; + zp = vector.zp; + wp = 0.0f; +} + +/*! + Constructs a 4D vector from the specified 3D \a vector. The w + coordinate is set to \a wpos. + + \sa toVector3D() +*/ +QVector4D::QVector4D(const QVector3D& vector, qreal wpos) +{ + xp = vector.xp; + yp = vector.yp; + zp = vector.zp; + wp = wpos; +} + +#endif + +/*! + \fn bool QVector4D::isNull() const + + Returns true if the x, y, z, and w coordinates are set to 0.0, + otherwise returns false. +*/ + +/*! + \fn qreal QVector4D::x() const + + Returns the x coordinate of this point. + + \sa setX(), y(), z(), w() +*/ + +/*! + \fn qreal QVector4D::y() const + + Returns the y coordinate of this point. + + \sa setY(), x(), z(), w() +*/ + +/*! + \fn qreal QVector4D::z() const + + Returns the z coordinate of this point. + + \sa setZ(), x(), y(), w() +*/ + +/*! + \fn qreal QVector4D::w() const + + Returns the w coordinate of this point. + + \sa setW(), x(), y(), z() +*/ + +/*! + \fn void QVector4D::setX(qreal x) + + Sets the x coordinate of this point to the given \a x coordinate. + + \sa x(), setY(), setZ(), setW() +*/ + +/*! + \fn void QVector4D::setY(qreal y) + + Sets the y coordinate of this point to the given \a y coordinate. + + \sa y(), setX(), setZ(), setW() +*/ + +/*! + \fn void QVector4D::setZ(qreal z) + + Sets the z coordinate of this point to the given \a z coordinate. + + \sa z(), setX(), setY(), setW() +*/ + +/*! + \fn void QVector4D::setW(qreal w) + + Sets the w coordinate of this point to the given \a w coordinate. + + \sa w(), setX(), setY(), setZ() +*/ + +/*! + Returns the length of the vector from the origin. + + \sa lengthSquared(), normalized() +*/ +qreal QVector4D::length() const +{ + return qSqrt(xp * xp + yp * yp + zp * zp + wp * wp); +} + +/*! + Returns the squared length of the vector from the origin. + This is equivalent to the dot product of the vector with itself. + + \sa length(), dotProduct() +*/ +qreal QVector4D::lengthSquared() const +{ + return xp * xp + yp * yp + zp * zp + wp * wp; +} + +/*! + Returns the normalized unit vector form of this vector. + + If this vector is null, then a null vector is returned. If the length + of the vector is very close to 1, then the vector will be returned as-is. + Otherwise the normalized form of the vector of length 1 will be returned. + + \sa length(), normalize() +*/ +QVector4D QVector4D::normalized() const +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp) + + double(wp) * double(wp); + if (qFuzzyIsNull(len - 1.0f)) + return *this; + else if (!qFuzzyIsNull(len)) + return *this / qSqrt(len); + else + return QVector4D(); +} + +/*! + Normalizes the currect vector in place. Nothing happens if this + vector is a null vector or the length of the vector is very close to 1. + + \sa length(), normalized() +*/ +void QVector4D::normalize() +{ + // Need some extra precision if the length is very small. + double len = double(xp) * double(xp) + + double(yp) * double(yp) + + double(zp) * double(zp) + + double(wp) * double(wp); + if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len)) + return; + + len = qSqrt(len); + + xp /= len; + yp /= len; + zp /= len; + wp /= len; +} + +/*! + \fn QVector4D &QVector4D::operator+=(const QVector4D &vector) + + Adds the given \a vector to this vector and returns a reference to + this vector. + + \sa operator-=() +*/ + +/*! + \fn QVector4D &QVector4D::operator-=(const QVector4D &vector) + + Subtracts the given \a vector from this vector and returns a reference to + this vector. + + \sa operator+=() +*/ + +/*! + \fn QVector4D &QVector4D::operator*=(qreal factor) + + Multiplies this vector's coordinates by the given \a factor, and + returns a reference to this vector. + + \sa operator/=() +*/ + +/*! + \fn QVector4D &QVector4D::operator*=(const QVector4D &vector) + + Multiplies the components of this vector by the corresponding + components in \a vector. +*/ + +/*! + \fn QVector4D &QVector4D::operator/=(qreal divisor) + + Divides this vector's coordinates by the given \a divisor, and + returns a reference to this vector. + + \sa operator*=() +*/ + +/*! + Returns the dot product of \a v1 and \a v2. +*/ +qreal QVector4D::dotProduct(const QVector4D& v1, const QVector4D& v2) +{ + return v1.xp * v2.xp + v1.yp * v2.yp + v1.zp * v2.zp + v1.wp * v2.wp; +} + +/*! + \fn bool operator==(const QVector4D &v1, const QVector4D &v2) + \relates QVector4D + + Returns true if \a v1 is equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn bool operator!=(const QVector4D &v1, const QVector4D &v2) + \relates QVector4D + + Returns true if \a v1 is not equal to \a v2; otherwise returns false. + This operator uses an exact floating-point comparison. +*/ + +/*! + \fn const QVector4D operator+(const QVector4D &v1, const QVector4D &v2) + \relates QVector4D + + Returns a QVector4D object that is the sum of the given vectors, \a v1 + and \a v2; each component is added separately. + + \sa QVector4D::operator+=() +*/ + +/*! + \fn const QVector4D operator-(const QVector4D &v1, const QVector4D &v2) + \relates QVector4D + + Returns a QVector4D object that is formed by subtracting \a v2 from \a v1; + each component is subtracted separately. + + \sa QVector4D::operator-=() +*/ + +/*! + \fn const QVector4D operator*(qreal factor, const QVector4D &vector) + \relates QVector4D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector4D::operator*=() +*/ + +/*! + \fn const QVector4D operator*(const QVector4D &vector, qreal factor) + \relates QVector4D + + Returns a copy of the given \a vector, multiplied by the given \a factor. + + \sa QVector4D::operator*=() +*/ + +/*! + \fn const QVector4D operator*(const QVector4D &v1, const QVector4D& v2) + \relates QVector4D + + Returns the vector consisting of the multiplication of the + components from \a v1 and \a v2. + + \sa QVector4D::operator*=() +*/ + +/*! + \fn const QVector4D operator-(const QVector4D &vector) + \relates QVector4D + \overload + + Returns a QVector4D object that is formed by changing the sign of + all three components of the given \a vector. + + Equivalent to \c {QVector4D(0,0,0,0) - vector}. +*/ + +/*! + \fn const QVector4D operator/(const QVector4D &vector, qreal divisor) + \relates QVector4D + + Returns the QVector4D object formed by dividing all four components of + the given \a vector by the given \a divisor. + + \sa QVector4D::operator/=() +*/ + +/*! + \fn bool qFuzzyCompare(const QVector4D& v1, const QVector4D& v2) + \relates QVector4D + + Returns true if \a v1 and \a v2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +#ifndef QT_NO_VECTOR2D + +/*! + Returns the 2D vector form of this 4D vector, dropping the z and w coordinates. + + \sa toVector2DAffine(), toVector3D(), toPoint() +*/ +QVector2D QVector4D::toVector2D() const +{ + return QVector2D(xp, yp, 1); +} + +/*! + Returns the 2D vector form of this 4D vector, dividing the x and y + coordinates by the w coordinate and dropping the z coordinate. + Returns a null vector if w is zero. + + \sa toVector2D(), toVector3DAffine(), toPoint() +*/ +QVector2D QVector4D::toVector2DAffine() const +{ + if (qIsNull(wp)) + return QVector2D(); + return QVector2D(xp / wp, yp / wp, 1); +} + +#endif + +#ifndef QT_NO_VECTOR3D + +/*! + Returns the 3D vector form of this 4D vector, dropping the w coordinate. + + \sa toVector3DAffine(), toVector2D(), toPoint() +*/ +QVector3D QVector4D::toVector3D() const +{ + return QVector3D(xp, yp, zp, 1); +} + +/*! + Returns the 3D vector form of this 4D vector, dividing the x, y, and + z coordinates by the w coordinate. Returns a null vector if w is zero. + + \sa toVector3D(), toVector2DAffine(), toPoint() +*/ +QVector3D QVector4D::toVector3DAffine() const +{ + if (qIsNull(wp)) + return QVector3D(); + return QVector3D(xp / wp, yp / wp, zp / wp, 1); +} + +#endif + +/*! + \fn QPoint QVector4D::toPoint() const + + Returns the QPoint form of this 4D vector. The z and w coordinates + are dropped. + + \sa toPointF(), toVector2D() +*/ + +/*! + \fn QPointF QVector4D::toPointF() const + + Returns the QPointF form of this 4D vector. The z and w coordinates + are dropped. + + \sa toPoint(), toVector2D() +*/ + +/*! + Returns the 4D vector as a QVariant. +*/ +QVector4D::operator QVariant() const +{ + return QVariant(QVariant::Vector4D, this); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug dbg, const QVector4D &vector) +{ + dbg.nospace() << "QVector4D(" + << vector.x() << ", " << vector.y() << ", " + << vector.z() << ", " << vector.w() << ')'; + return dbg.space(); +} + +#endif + +#ifndef QT_NO_DATASTREAM + +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QVector4D &vector) + \relates QVector4D + + Writes the given \a vector to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &stream, const QVector4D &vector) +{ + stream << double(vector.x()) << double(vector.y()) + << double(vector.z()) << double(vector.w()); + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QVector4D &vector) + \relates QVector4D + + Reads a 4D vector from the given \a stream into the given \a vector + and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &stream, QVector4D &vector) +{ + double x, y, z, w; + stream >> x; + stream >> y; + stream >> z; + stream >> w; + vector.setX(qreal(x)); + vector.setY(qreal(y)); + vector.setZ(qreal(z)); + vector.setW(qreal(w)); + return stream; +} + +#endif // QT_NO_DATASTREAM + +#endif // QT_NO_VECTOR4D + +QT_END_NAMESPACE diff --git a/src/gui/math3d/qvector4d.h b/src/gui/math3d/qvector4d.h new file mode 100644 index 0000000000..1c1fb75873 --- /dev/null +++ b/src/gui/math3d/qvector4d.h @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVECTOR4D_H +#define QVECTOR4D_H + +#include <QtCore/qpoint.h> +#include <QtCore/qmetatype.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QMatrix4x4; +class QVector2D; +class QVector3D; + +#ifndef QT_NO_VECTOR4D + +class Q_GUI_EXPORT QVector4D +{ +public: + QVector4D(); + QVector4D(qreal xpos, qreal ypos, qreal zpos, qreal wpos); + explicit QVector4D(const QPoint& point); + explicit QVector4D(const QPointF& point); +#ifndef QT_NO_VECTOR2D + QVector4D(const QVector2D& vector); + QVector4D(const QVector2D& vector, qreal zpos, qreal wpos); +#endif +#ifndef QT_NO_VECTOR3D + QVector4D(const QVector3D& vector); + QVector4D(const QVector3D& vector, qreal wpos); +#endif + + bool isNull() const; + + qreal x() const; + qreal y() const; + qreal z() const; + qreal w() const; + + void setX(qreal x); + void setY(qreal y); + void setZ(qreal z); + void setW(qreal w); + + qreal length() const; + qreal lengthSquared() const; + + QVector4D normalized() const; + void normalize(); + + QVector4D &operator+=(const QVector4D &vector); + QVector4D &operator-=(const QVector4D &vector); + QVector4D &operator*=(qreal factor); + QVector4D &operator*=(const QVector4D &vector); + QVector4D &operator/=(qreal divisor); + + static qreal dotProduct(const QVector4D& v1, const QVector4D& v2); + + friend inline bool operator==(const QVector4D &v1, const QVector4D &v2); + friend inline bool operator!=(const QVector4D &v1, const QVector4D &v2); + friend inline const QVector4D operator+(const QVector4D &v1, const QVector4D &v2); + friend inline const QVector4D operator-(const QVector4D &v1, const QVector4D &v2); + friend inline const QVector4D operator*(qreal factor, const QVector4D &vector); + friend inline const QVector4D operator*(const QVector4D &vector, qreal factor); + friend inline const QVector4D operator*(const QVector4D &v1, const QVector4D& v2); + friend inline const QVector4D operator-(const QVector4D &vector); + friend inline const QVector4D operator/(const QVector4D &vector, qreal divisor); + + friend inline bool qFuzzyCompare(const QVector4D& v1, const QVector4D& v2); + +#ifndef QT_NO_VECTOR2D + QVector2D toVector2D() const; + QVector2D toVector2DAffine() const; +#endif +#ifndef QT_NO_VECTOR3D + QVector3D toVector3D() const; + QVector3D toVector3DAffine() const; +#endif + + QPoint toPoint() const; + QPointF toPointF() const; + + operator QVariant() const; + +private: + float xp, yp, zp, wp; + + QVector4D(float xpos, float ypos, float zpos, float wpos, int dummy); + + friend class QVector2D; + friend class QVector3D; +#ifndef QT_NO_MATRIX4X4 + friend QVector4D operator*(const QVector4D& vector, const QMatrix4x4& matrix); + friend QVector4D operator*(const QMatrix4x4& matrix, const QVector4D& vector); +#endif +}; + +Q_DECLARE_TYPEINFO(QVector4D, Q_MOVABLE_TYPE); + +inline QVector4D::QVector4D() : xp(0.0f), yp(0.0f), zp(0.0f), wp(0.0f) {} + +inline QVector4D::QVector4D(qreal xpos, qreal ypos, qreal zpos, qreal wpos) : xp(xpos), yp(ypos), zp(zpos), wp(wpos) {} + +inline QVector4D::QVector4D(float xpos, float ypos, float zpos, float wpos, int) : xp(xpos), yp(ypos), zp(zpos), wp(wpos) {} + +inline QVector4D::QVector4D(const QPoint& point) : xp(point.x()), yp(point.y()), zp(0.0f), wp(0.0f) {} + +inline QVector4D::QVector4D(const QPointF& point) : xp(point.x()), yp(point.y()), zp(0.0f), wp(0.0f) {} + +inline bool QVector4D::isNull() const +{ + return qIsNull(xp) && qIsNull(yp) && qIsNull(zp) && qIsNull(wp); +} + +inline qreal QVector4D::x() const { return qreal(xp); } +inline qreal QVector4D::y() const { return qreal(yp); } +inline qreal QVector4D::z() const { return qreal(zp); } +inline qreal QVector4D::w() const { return qreal(wp); } + +inline void QVector4D::setX(qreal aX) { xp = aX; } +inline void QVector4D::setY(qreal aY) { yp = aY; } +inline void QVector4D::setZ(qreal aZ) { zp = aZ; } +inline void QVector4D::setW(qreal aW) { wp = aW; } + +inline QVector4D &QVector4D::operator+=(const QVector4D &vector) +{ + xp += vector.xp; + yp += vector.yp; + zp += vector.zp; + wp += vector.wp; + return *this; +} + +inline QVector4D &QVector4D::operator-=(const QVector4D &vector) +{ + xp -= vector.xp; + yp -= vector.yp; + zp -= vector.zp; + wp -= vector.wp; + return *this; +} + +inline QVector4D &QVector4D::operator*=(qreal factor) +{ + xp *= factor; + yp *= factor; + zp *= factor; + wp *= factor; + return *this; +} + +inline QVector4D &QVector4D::operator*=(const QVector4D &vector) +{ + xp *= vector.xp; + yp *= vector.yp; + zp *= vector.zp; + wp *= vector.wp; + return *this; +} + +inline QVector4D &QVector4D::operator/=(qreal divisor) +{ + xp /= divisor; + yp /= divisor; + zp /= divisor; + wp /= divisor; + return *this; +} + +inline bool operator==(const QVector4D &v1, const QVector4D &v2) +{ + return v1.xp == v2.xp && v1.yp == v2.yp && v1.zp == v2.zp && v1.wp == v2.wp; +} + +inline bool operator!=(const QVector4D &v1, const QVector4D &v2) +{ + return v1.xp != v2.xp || v1.yp != v2.yp || v1.zp != v2.zp || v1.wp != v2.wp; +} + +inline const QVector4D operator+(const QVector4D &v1, const QVector4D &v2) +{ + return QVector4D(v1.xp + v2.xp, v1.yp + v2.yp, v1.zp + v2.zp, v1.wp + v2.wp, 1); +} + +inline const QVector4D operator-(const QVector4D &v1, const QVector4D &v2) +{ + return QVector4D(v1.xp - v2.xp, v1.yp - v2.yp, v1.zp - v2.zp, v1.wp - v2.wp, 1); +} + +inline const QVector4D operator*(qreal factor, const QVector4D &vector) +{ + return QVector4D(vector.xp * factor, vector.yp * factor, vector.zp * factor, vector.wp * factor, 1); +} + +inline const QVector4D operator*(const QVector4D &vector, qreal factor) +{ + return QVector4D(vector.xp * factor, vector.yp * factor, vector.zp * factor, vector.wp * factor, 1); +} + +inline const QVector4D operator*(const QVector4D &v1, const QVector4D& v2) +{ + return QVector4D(v1.xp * v2.xp, v1.yp * v2.yp, v1.zp * v2.zp, v1.wp * v2.wp, 1); +} + +inline const QVector4D operator-(const QVector4D &vector) +{ + return QVector4D(-vector.xp, -vector.yp, -vector.zp, -vector.wp, 1); +} + +inline const QVector4D operator/(const QVector4D &vector, qreal divisor) +{ + return QVector4D(vector.xp / divisor, vector.yp / divisor, vector.zp / divisor, vector.wp / divisor, 1); +} + +inline bool qFuzzyCompare(const QVector4D& v1, const QVector4D& v2) +{ + return qFuzzyCompare(v1.xp, v2.xp) && + qFuzzyCompare(v1.yp, v2.yp) && + qFuzzyCompare(v1.zp, v2.zp) && + qFuzzyCompare(v1.wp, v2.wp); +} + +inline QPoint QVector4D::toPoint() const +{ + return QPoint(qRound(xp), qRound(yp)); +} + +inline QPointF QVector4D::toPointF() const +{ + return QPointF(qreal(xp), qreal(yp)); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QVector4D &vector); +#endif + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QVector4D &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QVector4D &); +#endif + +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/painting/makepsheader.pl b/src/gui/painting/makepsheader.pl new file mode 100755 index 0000000000..8f952720df --- /dev/null +++ b/src/gui/painting/makepsheader.pl @@ -0,0 +1,195 @@ +#!/usr/bin/perl +############################################################################# +## +## Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +## All rights reserved. +## Contact: Nokia Corporation (qt-info@nokia.com) +## +## This file is part of the QtGui module of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL$ +## No Commercial Usage +## This file contains pre-release code and may not be distributed. +## You may use this file in accordance with the terms and conditions +## contained in the Technology Preview License Agreement accompanying +## this package. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 2.1 requirements +## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## In addition, as a special exception, Nokia gives you certain additional +## rights. These rights are described in the Nokia Qt LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## If you have questions regarding the use of this file, please contact +## Nokia at qt-info@nokia.com. +## +## +## +## +## +## +## +## +## $QT_END_LICENSE$ +## +############################################################################# + +open(INPUT, 'qpsprinter.ps') + or die "Can't open qpsprinter.ps"; + +$dontcompress = 1; +while(<INPUT>) { + $line = $_; + chomp $line; + if ( /ENDUNCOMPRESS/ ) { + $dontcompress = 0; + } + $line =~ s/%.*$//; + $line = $line; + if ( $dontcompress eq 1 ) { + push(@uncompressed, $line); + } else { + push(@lines, $line); + } +# print "$line\n"; +} + +$uc = join(" ", @uncompressed); +$uc =~ s,\t+, ,g; +$uc=~ s, +, ,g; + +$h = join(" ", @lines); +$h =~ s,\t+, ,g; +$h =~ s, +, ,g; +$h = $h.' '; + +# now compress as much as possible +$h =~ s/ bind def / BD /g; +$h =~ s/ dup dup / d2 /g; +$h =~ s/ exch def / ED /g; +$h =~ s/ setfont / F /g; +$h =~ s/ rlineto / RL /g; +$h =~ s/ newpath / n /g; +$h =~ s/ currentmatrix / CM /g; +$h =~ s/ setmatrix / SM /g; +$h =~ s/ translate / TR /g; +$h =~ s/ setdash / SD /g; +$h =~ s/ aload pop setrgbcolor / SC /g; +$h =~ s/ currentfile read pop / CR /g; +$h =~ s/ index / i /g; +$h =~ s/ bitshift / bs /g; +$h =~ s/ setcolorspace / scs /g; +$h =~ s/ dict dup begin / DB /g; +$h =~ s/ end def / DE /g; +$h =~ s/ ifelse / ie /g; + +# PDF compatible naming +$h =~ s/ setlinewidth / w /g; +$h =~ s/ setdash / d /g; + +$h =~ s/ lineto / l /g; +$h =~ s/ moveto / m /g; +$h =~ s/ curveto / c /g; +$h =~ s/ closepath / h /g; +$h =~ s/ clip / W /g; +$h =~ s/ eoclip / W* /g; + +$h =~ s/ gsave / gs /g; +$h =~ s/ grestore / gr /g; + +# add the uncompressed part of the header before +$h = $uc.' '.$h; + + + +#print $h; + +# wordwrap at col 76 +@head = split(' ', $h); +$line = shift @head; +while( @head ) { + $token = shift @head; + chomp $token; +# print "\nl=$l, len=$len, token=$token."; + $newline = $line.' '.$token; + $newline =~ s, /,/,g; + $newline =~ s, \{,\{,g; + $newline =~ s, \},\},g; + $newline =~ s, \[,\[,g; + $newline =~ s, \],\],g; + $newline =~ s,\{ ,\{,g; + $newline =~ s,\} ,\},g; + $newline =~ s,\[ ,\[,g; + $newline =~ s,\] ,\],g; + if ( length( $newline ) > 76 ) { +# print "\nline=$line\n"; + $header = $header."\n\"".$line."\\n\""; + $newline = $token; + } + $line = $newline; +} +$header = $header."\n\"".$line."\\n\""; + + +print "static const char *const ps_header ="; +print $header.";\n\n"; + +close(INPUT); +exit; + +open(INPUT, 'qpsprinter.agl') + or die "Can't open qpsprinter.ps"; + +print "static const char * const agl =\n"; + +$str = "\""; +$string =""; +$i = 0; +while(<INPUT>) { + $line = $_; + chomp $line; + $line =~ s/#.*//; + if(length($line) ne 0) { + $num = $line; + $name = $line; + $num =~ s/,.*//; + $name =~ s/.*, \"//; + $name =~ s/\".*//; + push(@qchar, $num); + push(@index, $i); + if(length($str.$name) > 76) { + $str = $str."\"\n"; + $string = $string.$str; + $str = "\""; + } + $str = $str.$name."\\0"; + $i += length($name)+1; + } +} + +print $string.";\n\n"; + +print "static const struct { quint16 u; quint16 index; } unicodetoglyph[] = {\n "; + +$loop = 0; +while( @qchar ) { + $loop = $loop + 1; + $ch = shift @qchar; + $i = shift @index; + print "{".$ch.", ".$i."}"; + if($ch ne "0xFFFF") { + print ", "; + } + if(!($loop % 4)) { + print "\n "; + } +}; + +print "\n};\n\n"; + diff --git a/src/gui/painting/painting.pri b/src/gui/painting/painting.pri new file mode 100644 index 0000000000..65e7af4742 --- /dev/null +++ b/src/gui/painting/painting.pri @@ -0,0 +1,269 @@ +# Qt gui library, paint module + +HEADERS += \ + painting/qbezier_p.h \ + painting/qbrush.h \ + painting/qcolor.h \ + painting/qcolor_p.h \ + painting/qcolormap.h \ + painting/qdrawutil.h \ + painting/qemulationpaintengine_p.h \ + painting/qgraphicssystem_p.h \ + painting/qmatrix.h \ + painting/qmemrotate_p.h \ + painting/qoutlinemapper_p.h \ + painting/qpaintdevice.h \ + painting/qpaintengine.h \ + painting/qpaintengine_p.h \ + painting/qpaintengine_alpha_p.h \ + painting/qpaintengine_preview_p.h \ + painting/qpaintengineex_p.h \ + painting/qpainter.h \ + painting/qpainter_p.h \ + painting/qpainterpath.h \ + painting/qpainterpath_p.h \ + painting/qvectorpath_p.h \ + painting/qpathclipper_p.h \ + painting/qpdf_p.h \ + painting/qpen.h \ + painting/qpolygon.h \ + painting/qpolygonclipper_p.h \ + painting/qprintengine.h \ + painting/qprintengine_pdf_p.h \ + painting/qprintengine_ps_p.h \ + painting/qprinter.h \ + painting/qprinter_p.h \ + painting/qprinterinfo.h \ + painting/qprinterinfo_p.h \ + painting/qrasterizer_p.h \ + painting/qregion.h \ + painting/qstroker_p.h \ + painting/qstylepainter.h \ + painting/qtessellator_p.h \ + painting/qtextureglyphcache_p.h \ + painting/qtransform.h \ + painting/qwindowsurface_p.h \ + painting/qwmatrix.h \ + painting/qpaintbuffer_p.h + + +SOURCES += \ + painting/qbezier.cpp \ + painting/qblendfunctions.cpp \ + painting/qbrush.cpp \ + painting/qcolor.cpp \ + painting/qcolor_p.cpp \ + painting/qcssutil.cpp \ + painting/qdrawutil.cpp \ + painting/qemulationpaintengine.cpp \ + painting/qgraphicssystem.cpp \ + painting/qmatrix.cpp \ + painting/qmemrotate.cpp \ + painting/qoutlinemapper.cpp \ + painting/qpaintdevice.cpp \ + painting/qpaintengine.cpp \ + painting/qpaintengine_alpha.cpp \ + painting/qpaintengine_preview.cpp \ + painting/qpaintengineex.cpp \ + painting/qpainter.cpp \ + painting/qpainterpath.cpp \ + painting/qpathclipper.cpp \ + painting/qpdf.cpp \ + painting/qpen.cpp \ + painting/qpolygon.cpp \ + painting/qprintengine_pdf.cpp \ + painting/qprintengine_ps.cpp \ + painting/qprinter.cpp \ + painting/qprinterinfo.cpp \ + painting/qrasterizer.cpp \ + painting/qregion.cpp \ + painting/qstroker.cpp \ + painting/qstylepainter.cpp \ + painting/qtessellator.cpp \ + painting/qtextureglyphcache.cpp \ + painting/qtransform.cpp \ + painting/qwindowsurface.cpp \ + painting/qpaintbuffer.cpp + + SOURCES += \ + painting/qpaintengine_raster.cpp \ + painting/qdrawhelper.cpp \ + painting/qimagescale.cpp \ + painting/qgrayraster.c \ + painting/qpaintengine_blitter.cpp \ + painting/qblittable.cpp \ + + HEADERS += \ + painting/qpaintengine_raster_p.h \ + painting/qdrawhelper_p.h \ + painting/qblendfunctions_p.h \ + painting/qrasterdefs_p.h \ + painting/qgrayraster_p.h \ + painting/qpaintengine_blitter_p.h \ + painting/qblittable_p.h \ + +win32 { + HEADERS += painting/qprintengine_win_p.h + + SOURCES += \ + painting/qcolormap_win.cpp \ + painting/qpaintdevice_win.cpp \ + painting/qprintengine_win.cpp \ + painting/qprinterinfo_win.cpp + + !win32-borland:!wince*:LIBS += -lmsimg32 +} + +embedded { + HEADERS += \ + painting/qgraphicssystem_qws_p.h \ + + SOURCES += \ + painting/qgraphicssystem_qws.cpp \ + +} else: if(!qpa) { + HEADERS += \ + painting/qgraphicssystem_raster_p.h \ + painting/qgraphicssystem_runtime_p.h \ + painting/qgraphicssystemfactory_p.h \ + painting/qgraphicssystemplugin_p.h \ + painting/qwindowsurface_raster_p.h + + SOURCES += \ + painting/qgraphicssystem_raster.cpp \ + painting/qgraphicssystem_runtime.cpp \ + painting/qgraphicssystemfactory.cpp \ + painting/qgraphicssystemplugin.cpp \ + painting/qwindowsurface_raster.cpp +} + +unix:x11 { + HEADERS += \ + painting/qpaintengine_x11_p.h + + SOURCES += \ + painting/qcolormap_x11.cpp \ + painting/qpaintdevice_x11.cpp \ + painting/qpaintengine_x11.cpp +} + +!embedded:!qpa:!x11:mac { + HEADERS += \ + painting/qpaintengine_mac_p.h \ + painting/qgraphicssystem_mac_p.h \ + painting/qprintengine_mac_p.h + + SOURCES += \ + painting/qcolormap_mac.cpp \ + painting/qpaintdevice_mac.cpp \ + painting/qpaintengine_mac.cpp \ + painting/qgraphicssystem_mac.cpp \ + painting/qprinterinfo_mac.cpp + OBJECTIVE_SOURCES += \ + painting/qprintengine_mac.mm \ +} + +unix:!mac:!symbian|qpa { + HEADERS += \ + painting/qprinterinfo_unix_p.h + SOURCES += \ + painting/qprinterinfo_unix.cpp +} + +win32|x11|mac|embedded|qpa|symbian { + SOURCES += painting/qbackingstore.cpp + HEADERS += painting/qbackingstore_p.h +} + +embedded { + contains(QT_CONFIG,qtopia) { + DEFINES += QTOPIA_PRINTENGINE + HEADERS += painting/qprintengine_qws_p.h + SOURCES += painting/qprintengine_qws.cpp + } + + SOURCES += \ + painting/qcolormap_qws.cpp \ + painting/qpaintdevice_qws.cpp +} + +qpa { + SOURCES += \ + painting/qcolormap_qpa.cpp \ + painting/qpaintdevice_qpa.cpp +} + +symbian { + SOURCES += \ + painting/qpaintengine_s60.cpp \ + painting/qregion_s60.cpp \ + painting/qcolormap_s60.cpp + + HEADERS += \ + painting/qpaintengine_s60_p.h +} + +x11|embedded|qpa { + contains(QT_CONFIG,qtopia) { + DEFINES += QT_NO_CUPS QT_NO_LPR + } else { + SOURCES += painting/qcups.cpp + HEADERS += painting/qcups_p.h + } +} else { + DEFINES += QT_NO_CUPS QT_NO_LPR +} + +if(mmx|3dnow|sse|sse2|iwmmxt) { + HEADERS += painting/qdrawhelper_x86_p.h \ + painting/qdrawhelper_mmx_p.h \ + painting/qdrawhelper_sse_p.h \ + painting/qdrawingprimitive_sse2_p.h + MMX_SOURCES += painting/qdrawhelper_mmx.cpp + MMX3DNOW_SOURCES += painting/qdrawhelper_mmx3dnow.cpp + SSE3DNOW_SOURCES += painting/qdrawhelper_sse3dnow.cpp + SSE_SOURCES += painting/qdrawhelper_sse.cpp + SSE2_SOURCES += painting/qdrawhelper_sse2.cpp + SSSE3_SOURCES += painting/qdrawhelper_ssse3.cpp + IWMMXT_SOURCES += painting/qdrawhelper_iwmmxt.cpp +} + +x11 { + HEADERS += painting/qwindowsurface_x11_p.h + SOURCES += painting/qwindowsurface_x11.cpp +} + +!embedded:!qpa:mac { + HEADERS += painting/qwindowsurface_mac_p.h \ + painting/qunifiedtoolbarsurface_mac_p.h + SOURCES += painting/qwindowsurface_mac.cpp \ + painting/qunifiedtoolbarsurface_mac.cpp +} + +embedded { + HEADERS += painting/qwindowsurface_qws_p.h + SOURCES += painting/qwindowsurface_qws.cpp +} + + + +symbian { + HEADERS += painting/qwindowsurface_s60_p.h \ + painting/qdrawhelper_arm_simd_p.h + SOURCES += painting/qwindowsurface_s60.cpp + armccIfdefBlock = \ + "$${LITERAL_HASH}if defined(ARMV6)" \ + "MACRO QT_HAVE_ARM_SIMD" \ + "SOURCEPATH painting" \ + "SOURCE qdrawhelper_arm_simd.cpp" \ + "$${LITERAL_HASH}endif" + + MMP_RULES += armccIfdefBlock + QMAKE_CXXFLAGS.ARMCC *= -O3 +} + +NEON_SOURCES += painting/qdrawhelper_neon.cpp +NEON_HEADERS += painting/qdrawhelper_neon_p.h +NEON_ASM += ../3rdparty/pixman/pixman-arm-neon-asm.S painting/qdrawhelper_neon_asm.S + +include($$PWD/../../3rdparty/zlib_dependency.pri) diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp new file mode 100644 index 0000000000..42c1c354b6 --- /dev/null +++ b/src/gui/painting/qbackingstore.cpp @@ -0,0 +1,1668 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qplatformdefs.h" + +#include "qbackingstore_p.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qdebug.h> +#include <QtCore/qvarlengtharray.h> +#include <QtGui/qevent.h> +#include <QtGui/qapplication.h> +#include <QtGui/qpaintengine.h> +#include <QtGui/qgraphicsproxywidget.h> + +#include <private/qwidget_p.h> +#include <private/qwindowsurface_raster_p.h> +#include <private/qapplication_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qgraphicseffect_p.h> + +#include "qgraphicssystem_p.h" + +#ifdef Q_WS_QWS +#include <QtGui/qwsmanager_qws.h> +#include <private/qwsmanager_p.h> +#endif + +QT_BEGIN_NAMESPACE + +extern QRegion qt_dirtyRegion(QWidget *); + +/* + A version of QRect::intersects() that does not normalize the rects. +*/ +static inline bool qRectIntersects(const QRect &r1, const QRect &r2) +{ + return (qMax(r1.left(), r2.left()) <= qMin(r1.right(), r2.right()) + && qMax(r1.top(), r2.top()) <= qMin(r1.bottom(), r2.bottom())); +} + +/** + * Flushes the contents of the \a windowSurface into the screen area of \a widget. + * \a tlwOffset is the position of the top level widget relative to the window surface. + * \a region is the region to be updated in \a widget coordinates. + */ +static inline void qt_flush(QWidget *widget, const QRegion ®ion, QWindowSurface *windowSurface, + QWidget *tlw, const QPoint &tlwOffset) +{ + Q_ASSERT(widget); + Q_ASSERT(!region.isEmpty()); + Q_ASSERT(windowSurface); + Q_ASSERT(tlw); + +#if !defined(QT_NO_PAINT_DEBUG) && !defined(Q_WS_QWS) + // QWS does flush update in QWindowSurface::flush (because it needs to lock the surface etc). + static int flushUpdate = qgetenv("QT_FLUSH_UPDATE").toInt(); + if (flushUpdate > 0) + QWidgetBackingStore::showYellowThing(widget, region, flushUpdate * 10, false); +#endif + + //The performance hit by doing this should be negligible. However, be aware that + //using this FPS when you have > 1 windowsurface can give you inaccurate FPS + static bool fpsDebug = qgetenv("QT_DEBUG_FPS").toInt(); + if (fpsDebug) { + static QTime time = QTime::currentTime(); + static int frames = 0; + + frames++; + + if(time.elapsed() > 5000) { + double fps = double(frames * 1000) /time.restart(); + fprintf(stderr,"FPS: %.1f\n",fps); + frames = 0; + } + } + if (widget != tlw) + windowSurface->flush(widget, region, tlwOffset + widget->mapTo(tlw, QPoint())); + else + windowSurface->flush(widget, region, tlwOffset); +} + +#ifndef QT_NO_PAINT_DEBUG +#ifdef Q_WS_WIN +static void showYellowThing_win(QWidget *widget, const QRegion ®ion, int msec) +{ + HBRUSH brush; + static int i = 0; + switch (i) { + case 0: + brush = CreateSolidBrush(RGB(255, 255, 0)); + break; + case 1: + brush = CreateSolidBrush(RGB(255, 200, 55)); + break; + case 2: + brush = CreateSolidBrush(RGB(200, 255, 55)); + break; + case 3: + brush = CreateSolidBrush(RGB(200, 200, 0)); + break; + } + i = (i + 1) & 3; + + HDC hdc = widget->getDC(); + + const QVector<QRect> &rects = region.rects(); + foreach (QRect rect, rects) { + RECT winRect; + SetRect(&winRect, rect.left(), rect.top(), rect.right(), rect.bottom()); + FillRect(hdc, &winRect, brush); + } + + widget->releaseDC(hdc); + ::Sleep(msec); +} +#endif + +void QWidgetBackingStore::showYellowThing(QWidget *widget, const QRegion &toBePainted, int msec, bool unclipped) +{ +#ifdef Q_WS_QWS + Q_UNUSED(widget); + Q_UNUSED(unclipped); + static QWSYellowSurface surface(true); + surface.setDelay(msec); + surface.flush(widget, toBePainted, QPoint()); +#else + QRegion paintRegion = toBePainted; + QRect widgetRect = widget->rect(); + + if (!widget->internalWinId()) { + QWidget *nativeParent = widget->nativeParentWidget(); + const QPoint offset = widget->mapTo(nativeParent, QPoint(0, 0)); + paintRegion.translate(offset); + widgetRect.translate(offset); + widget = nativeParent; + } + +#ifdef Q_WS_WIN + Q_UNUSED(unclipped); + showYellowThing_win(widget, paintRegion, msec); +#else + //flags to fool painter + bool paintUnclipped = widget->testAttribute(Qt::WA_PaintUnclipped); + if (unclipped && !widget->d_func()->paintOnScreen()) + widget->setAttribute(Qt::WA_PaintUnclipped); + + const bool setFlag = !widget->testAttribute(Qt::WA_WState_InPaintEvent); + if (setFlag) + widget->setAttribute(Qt::WA_WState_InPaintEvent); + + //setup the engine + QPaintEngine *pe = widget->paintEngine(); + if (pe) { + pe->setSystemClip(paintRegion); + { + QPainter p(widget); + p.setClipRegion(paintRegion); + static int i = 0; + switch (i) { + case 0: + p.fillRect(widgetRect, QColor(255,255,0)); + break; + case 1: + p.fillRect(widgetRect, QColor(255,200,55)); + break; + case 2: + p.fillRect(widgetRect, QColor(200,255,55)); + break; + case 3: + p.fillRect(widgetRect, QColor(200,200,0)); + break; + } + i = (i+1) & 3; + p.end(); + } + } + + if (setFlag) + widget->setAttribute(Qt::WA_WState_InPaintEvent, false); + + //restore + widget->setAttribute(Qt::WA_PaintUnclipped, paintUnclipped); + + if (pe) + pe->setSystemClip(QRegion()); + + QApplication::syncX(); + +#if defined(Q_OS_UNIX) + ::usleep(1000 * msec); +#endif +#endif // Q_WS_WIN +#endif // Q_WS_QWS +} + +bool QWidgetBackingStore::flushPaint(QWidget *widget, const QRegion &rgn) +{ + if (!widget) + return false; + + int delay = 0; + if (widget->testAttribute(Qt::WA_WState_InPaintEvent)) { + static int flushPaintEvent = qgetenv("QT_FLUSH_PAINT_EVENT").toInt(); + if (!flushPaintEvent) + return false; + delay = flushPaintEvent; + } else { + static int flushPaint = qgetenv("QT_FLUSH_PAINT").toInt(); + if (!flushPaint) + return false; + delay = flushPaint; + } + + QWidgetBackingStore::showYellowThing(widget, rgn, delay * 10, true); + return true; +} + +void QWidgetBackingStore::unflushPaint(QWidget *widget, const QRegion &rgn) +{ + if (widget->d_func()->paintOnScreen() || rgn.isEmpty()) + return; + + QWidget *tlw = widget->window(); + QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData(); + if (!tlwExtra) + return; + + const QPoint offset = widget->mapTo(tlw, QPoint()); + qt_flush(widget, rgn, tlwExtra->backingStore->windowSurface, tlw, offset); +} +#endif // QT_NO_PAINT_DEBUG + +/* + Moves the whole rect by (dx, dy) in widget's coordinate system. + Doesn't generate any updates. +*/ +bool QWidgetBackingStore::bltRect(const QRect &rect, int dx, int dy, QWidget *widget) +{ + const QPoint pos(tlwOffset + widget->mapTo(tlw, rect.topLeft())); + const QRect tlwRect(QRect(pos, rect.size())); + if (fullUpdatePending || dirty.intersects(tlwRect)) + return false; // We don't want to scroll junk. + return windowSurface->scroll(tlwRect, dx, dy); +} + +void QWidgetBackingStore::releaseBuffer() +{ + if (windowSurface) +#if defined(Q_WS_QPA) + windowSurface->resize(QSize()); +#else + windowSurface->setGeometry(QRect()); +#endif +#ifdef Q_BACKINGSTORE_SUBSURFACES + for (int i = 0; i < subSurfaces.size(); ++i) + subSurfaces.at(i)->setGeometry(QRect()); +#endif +} + +/*! + Prepares the window surface to paint a\ toClean region of the \a widget and + updates the BeginPaintInfo struct accordingly. + + The \a toClean region might be clipped by the window surface. +*/ +void QWidgetBackingStore::beginPaint(QRegion &toClean, QWidget *widget, QWindowSurface *windowSurface, + BeginPaintInfo *returnInfo, bool toCleanIsInTopLevelCoordinates) +{ +#ifdef Q_WS_QWS + QWSWindowSurface *surface = static_cast<QWSWindowSurface *>(windowSurface); + QWidget *surfaceWidget = surface->window(); + + if (!surface->isValid()) { + // this looks strange but it really just releases the surface + surface->releaseSurface(); + // the old window surface is deleted in setWindowSurface, which is + // called from QWindowSurface constructor. + windowSurface = tlw->d_func()->createDefaultWindowSurface(); + surface = static_cast<QWSWindowSurface *>(windowSurface); + // createDefaultWindowSurface() will set topdata->windowSurface on the + // widget to zero. However, if this is a sub-surface, it should point + // to the widget's sub windowSurface, so we set that here: + if (!surfaceWidget->isWindow()) + surfaceWidget->d_func()->topData()->windowSurface = windowSurface; + surface->setGeometry(topLevelRect()); + returnInfo->windowSurfaceRecreated = true; + } + + const QRegion toCleanUnclipped(toClean); + + if (surfaceWidget->isWindow()) + tlwOffset = surface->painterOffset(); +#ifdef Q_BACKINGSTORE_SUBSURFACES + else if (toCleanIsInTopLevelCoordinates) + toClean &= surface->clipRegion().translated(surfaceWidget->mapTo(tlw, QPoint())); + if (!toCleanIsInTopLevelCoordinates && windowSurface == this->windowSurface) + toClean &= surface->clipRegion().translated(-widget->mapTo(surfaceWidget, QPoint())); +#else + toClean &= surface->clipRegion(); +#endif + + if (toClean.isEmpty()) { + if (surfaceWidget->isWindow()) { + dirtyFromPreviousSync += toCleanUnclipped; + hasDirtyFromPreviousSync = true; + } + + returnInfo->nothingToPaint = true; + // Nothing to repaint. However, we might have newly exposed areas on the + // screen, so we have to make sure those are flushed. + flush(); + return; + } + + if (surfaceWidget->isWindow()) { + if (toCleanUnclipped != toClean) { + dirtyFromPreviousSync += (toCleanUnclipped - surface->clipRegion()); + hasDirtyFromPreviousSync = true; + } + if (hasDirtyFromPreviousSync) { + dirtyFromPreviousSync -= toClean; + hasDirtyFromPreviousSync = !dirtyFromPreviousSync.isEmpty(); + } + } + +#endif // Q_WS_QWS + + Q_UNUSED(widget); + Q_UNUSED(toCleanIsInTopLevelCoordinates); + + // Always flush repainted areas. + dirtyOnScreen += toClean; + +#if defined(Q_WS_QWS) && !defined(Q_BACKINGSTORE_SUBSURFACES) + toClean.translate(tlwOffset); +#endif + +#ifdef QT_NO_PAINT_DEBUG + windowSurface->beginPaint(toClean); +#else + returnInfo->wasFlushed = QWidgetBackingStore::flushPaint(tlw, toClean); + // Avoid deadlock with QT_FLUSH_PAINT: the server will wait for + // the BackingStore lock, so if we hold that, the server will + // never release the Communication lock that we are waiting for in + // sendSynchronousCommand + if (!returnInfo->wasFlushed) + windowSurface->beginPaint(toClean); +#endif + + Q_UNUSED(returnInfo); +} + +void QWidgetBackingStore::endPaint(const QRegion &cleaned, QWindowSurface *windowSurface, + BeginPaintInfo *beginPaintInfo) +{ +#ifndef QT_NO_PAINT_DEBUG + if (!beginPaintInfo->wasFlushed) + windowSurface->endPaint(cleaned); + else + QWidgetBackingStore::unflushPaint(tlw, cleaned); +#else + Q_UNUSED(beginPaintInfo); + windowSurface->endPaint(cleaned); +#endif + +#ifdef Q_BACKINGSTORE_SUBSURFACES + flush(static_cast<QWSWindowSurface *>(windowSurface)->window(), windowSurface); +#else + flush(); +#endif +} + +/*! + Returns the region (in top-level coordinates) that needs repaint and/or flush. + + If the widget is non-zero, only the dirty region for the widget is returned + and the region will be in widget coordinates. +*/ +QRegion QWidgetBackingStore::dirtyRegion(QWidget *widget) const +{ + const bool widgetDirty = widget && widget != tlw; + const QRect tlwRect(topLevelRect()); +#if defined(Q_WS_QPA) + const QRect surfaceGeometry(tlwRect.topLeft(), windowSurface->size()); +#else + const QRect surfaceGeometry(windowSurface->geometry()); +#endif + if (fullUpdatePending || (surfaceGeometry != tlwRect && surfaceGeometry.size() != tlwRect.size())) { + if (widgetDirty) { + const QRect dirtyTlwRect = QRect(QPoint(), tlwRect.size()); + const QPoint offset(widget->mapTo(tlw, QPoint())); + const QRect dirtyWidgetRect(dirtyTlwRect & widget->rect().translated(offset)); + return dirtyWidgetRect.translated(-offset); + } + return QRect(QPoint(), tlwRect.size()); + } + + // Calculate the region that needs repaint. + QRegion r(dirty); + for (int i = 0; i < dirtyWidgets.size(); ++i) { + QWidget *w = dirtyWidgets.at(i); + if (widgetDirty && w != widget && !widget->isAncestorOf(w)) + continue; + r += w->d_func()->dirty.translated(w->mapTo(tlw, QPoint())); + } + + // Append the region that needs flush. + r += dirtyOnScreen; + + if (dirtyOnScreenWidgets) { // Only in use with native child widgets. + for (int i = 0; i < dirtyOnScreenWidgets->size(); ++i) { + QWidget *w = dirtyOnScreenWidgets->at(i); + if (widgetDirty && w != widget && !widget->isAncestorOf(w)) + continue; + QWidgetPrivate *wd = w->d_func(); + Q_ASSERT(wd->needsFlush); + r += wd->needsFlush->translated(w->mapTo(tlw, QPoint())); + } + } + + if (widgetDirty) { + // Intersect with the widget geometry and translate to its coordinates. + const QPoint offset(widget->mapTo(tlw, QPoint())); + r &= widget->rect().translated(offset); + r.translate(-offset); + } + return r; +} + +/*! + Returns the static content inside the \a parent if non-zero; otherwise the static content + for the entire backing store is returned. The content will be clipped to \a withinClipRect + if non-empty. +*/ +QRegion QWidgetBackingStore::staticContents(QWidget *parent, const QRect &withinClipRect) const +{ + if (!parent && tlw->testAttribute(Qt::WA_StaticContents)) { +#if defined(Q_WS_QPA) + const QSize surfaceGeometry(windowSurface->size()); +#else + const QRect surfaceGeometry(windowSurface->geometry()); +#endif + QRect surfaceRect(0, 0, surfaceGeometry.width(), surfaceGeometry.height()); + if (!withinClipRect.isEmpty()) + surfaceRect &= withinClipRect; + return QRegion(surfaceRect); + } + + QRegion region; + if (parent && parent->d_func()->children.isEmpty()) + return region; + + const bool clipToRect = !withinClipRect.isEmpty(); + const int count = staticWidgets.count(); + for (int i = 0; i < count; ++i) { + QWidget *w = staticWidgets.at(i); + QWidgetPrivate *wd = w->d_func(); + if (!wd->isOpaque || !wd->extra || wd->extra->staticContentsSize.isEmpty() + || !w->isVisible() || (parent && !parent->isAncestorOf(w))) { + continue; + } + + QRect rect(0, 0, wd->extra->staticContentsSize.width(), wd->extra->staticContentsSize.height()); + const QPoint offset = w->mapTo(parent ? parent : tlw, QPoint()); + if (clipToRect) + rect &= withinClipRect.translated(-offset); + if (rect.isEmpty()) + continue; + + rect &= wd->clipRect(); + if (rect.isEmpty()) + continue; + + QRegion visible(rect); + wd->clipToEffectiveMask(visible); + if (visible.isEmpty()) + continue; + wd->subtractOpaqueSiblings(visible, 0, /*alsoNonOpaque=*/true); + + visible.translate(offset); + region += visible; + } + + return region; +} + +static inline void sendUpdateRequest(QWidget *widget, bool updateImmediately) +{ + if (!widget) + return; + + if (updateImmediately) { + QEvent event(QEvent::UpdateRequest); + QApplication::sendEvent(widget, &event); + } else { + QApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); + } +} + +/*! + Marks the region of the widget as dirty (if not already marked as dirty) and + posts an UpdateRequest event to the top-level widget (if not already posted). + + If updateImmediately is true, the event is sent immediately instead of posted. + + If invalidateBuffer is true, all widgets intersecting with the region will be dirty. + + If the widget paints directly on screen, the event is sent to the widget + instead of the top-level widget, and invalidateBuffer is completely ignored. + + ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). +*/ +void QWidgetBackingStore::markDirty(const QRegion &rgn, QWidget *widget, bool updateImmediately, + bool invalidateBuffer) +{ + Q_ASSERT(tlw->d_func()->extra); + Q_ASSERT(tlw->d_func()->extra->topextra); + Q_ASSERT(!tlw->d_func()->extra->topextra->inTopLevelResize); + Q_ASSERT(widget->isVisible() && widget->updatesEnabled()); + Q_ASSERT(widget->window() == tlw); + Q_ASSERT(!rgn.isEmpty()); + +#ifndef QT_NO_GRAPHICSEFFECT + widget->d_func()->invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + if (widget->d_func()->paintOnScreen()) { + if (widget->d_func()->dirty.isEmpty()) { + widget->d_func()->dirty = rgn; + sendUpdateRequest(widget, updateImmediately); + return; + } else if (qt_region_strictContains(widget->d_func()->dirty, widget->rect())) { + if (updateImmediately) + sendUpdateRequest(widget, updateImmediately); + return; // Already dirty. + } + + const bool eventAlreadyPosted = !widget->d_func()->dirty.isEmpty(); + widget->d_func()->dirty += rgn; + if (!eventAlreadyPosted || updateImmediately) + sendUpdateRequest(widget, updateImmediately); + return; + } + + if (fullUpdatePending) { + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (!windowSurface->hasFeature(QWindowSurface::PartialUpdates)) { + fullUpdatePending = true; + sendUpdateRequest(tlw, updateImmediately); + return; + } + + const QPoint offset = widget->mapTo(tlw, QPoint()); + const QRect widgetRect = widget->d_func()->effectiveRectFor(widget->rect()); + if (qt_region_strictContains(dirty, widgetRect.translated(offset))) { + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; // Already dirty. + } + + if (invalidateBuffer) { + const bool eventAlreadyPosted = !dirty.isEmpty(); +#ifndef QT_NO_GRAPHICSEFFECT + if (widget->d_func()->graphicsEffect) + dirty += widget->d_func()->effectiveRectFor(rgn.boundingRect()).translated(offset); + else +#endif //QT_NO_GRAPHICSEFFECT + dirty += rgn.translated(offset); + if (!eventAlreadyPosted || updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (dirtyWidgets.isEmpty()) { + addDirtyWidget(widget, rgn); + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (widget->d_func()->inDirtyList) { + if (!qt_region_strictContains(widget->d_func()->dirty, widgetRect)) { +#ifndef QT_NO_GRAPHICSEFFECT + if (widget->d_func()->graphicsEffect) + widget->d_func()->dirty += widget->d_func()->effectiveRectFor(rgn.boundingRect()); + else +#endif //QT_NO_GRAPHICSEFFECT + widget->d_func()->dirty += rgn; + } + } else { + addDirtyWidget(widget, rgn); + } + + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); +} + +/*! + This function is equivalent to calling markDirty(QRegion(rect), ...), but + is more efficient as it eliminates QRegion operations/allocations and can + use the rect more precisely for additional cut-offs. + + ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). +*/ +void QWidgetBackingStore::markDirty(const QRect &rect, QWidget *widget, bool updateImmediately, + bool invalidateBuffer) +{ + Q_ASSERT(tlw->d_func()->extra); + Q_ASSERT(tlw->d_func()->extra->topextra); + Q_ASSERT(!tlw->d_func()->extra->topextra->inTopLevelResize); + Q_ASSERT(widget->isVisible() && widget->updatesEnabled()); + Q_ASSERT(widget->window() == tlw); + Q_ASSERT(!rect.isEmpty()); + +#ifndef QT_NO_GRAPHICSEFFECT + widget->d_func()->invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT + + if (widget->d_func()->paintOnScreen()) { + if (widget->d_func()->dirty.isEmpty()) { + widget->d_func()->dirty = QRegion(rect); + sendUpdateRequest(widget, updateImmediately); + return; + } else if (qt_region_strictContains(widget->d_func()->dirty, rect)) { + if (updateImmediately) + sendUpdateRequest(widget, updateImmediately); + return; // Already dirty. + } + + const bool eventAlreadyPosted = !widget->d_func()->dirty.isEmpty(); + widget->d_func()->dirty += rect; + if (!eventAlreadyPosted || updateImmediately) + sendUpdateRequest(widget, updateImmediately); + return; + } + + if (fullUpdatePending) { + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (!windowSurface->hasFeature(QWindowSurface::PartialUpdates)) { + fullUpdatePending = true; + sendUpdateRequest(tlw, updateImmediately); + return; + } + + const QRect widgetRect = widget->d_func()->effectiveRectFor(rect); + const QRect translatedRect(widgetRect.translated(widget->mapTo(tlw, QPoint()))); + if (qt_region_strictContains(dirty, translatedRect)) { + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; // Already dirty + } + + if (invalidateBuffer) { + const bool eventAlreadyPosted = !dirty.isEmpty(); + dirty += translatedRect; + if (!eventAlreadyPosted || updateImmediately) + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (dirtyWidgets.isEmpty()) { + addDirtyWidget(widget, rect); + sendUpdateRequest(tlw, updateImmediately); + return; + } + + if (widget->d_func()->inDirtyList) { + if (!qt_region_strictContains(widget->d_func()->dirty, widgetRect)) + widget->d_func()->dirty += widgetRect; + } else { + addDirtyWidget(widget, rect); + } + + if (updateImmediately) + sendUpdateRequest(tlw, updateImmediately); +} + +/*! + Marks the \a region of the \a widget as dirty on screen. The \a region will be copied from + the backing store to the \a widget's native parent next time flush() is called. + + Paint on screen widgets are ignored. +*/ +void QWidgetBackingStore::markDirtyOnScreen(const QRegion ®ion, QWidget *widget, const QPoint &topLevelOffset) +{ + if (!widget || widget->d_func()->paintOnScreen() || region.isEmpty()) + return; + +#if defined(Q_WS_QWS) || defined(Q_WS_MAC) + if (!widget->testAttribute(Qt::WA_WState_InPaintEvent)) + dirtyOnScreen += region.translated(topLevelOffset); + return; +#endif + + // Top-level. + if (widget == tlw) { + if (!widget->testAttribute(Qt::WA_WState_InPaintEvent)) + dirtyOnScreen += region; + return; + } + + // Alien widgets. + if (!widget->internalWinId() && !widget->isWindow()) { + QWidget *nativeParent = widget->nativeParentWidget(); // Alien widgets with the top-level as the native parent (common case). + if (nativeParent == tlw) { + if (!widget->testAttribute(Qt::WA_WState_InPaintEvent)) + dirtyOnScreen += region.translated(topLevelOffset); + return; + } + + // Alien widgets with native parent != tlw. + QWidgetPrivate *nativeParentPrivate = nativeParent->d_func(); + if (!nativeParentPrivate->needsFlush) + nativeParentPrivate->needsFlush = new QRegion; + const QPoint nativeParentOffset = widget->mapTo(nativeParent, QPoint()); + *nativeParentPrivate->needsFlush += region.translated(nativeParentOffset); + appendDirtyOnScreenWidget(nativeParent); + return; + } + + // Native child widgets. + QWidgetPrivate *widgetPrivate = widget->d_func(); + if (!widgetPrivate->needsFlush) + widgetPrivate->needsFlush = new QRegion; + *widgetPrivate->needsFlush += region; + appendDirtyOnScreenWidget(widget); +} + +void QWidgetBackingStore::removeDirtyWidget(QWidget *w) +{ + if (!w) + return; + + dirtyWidgetsRemoveAll(w); + dirtyOnScreenWidgetsRemoveAll(w); + resetWidget(w); + + QWidgetPrivate *wd = w->d_func(); + const int n = wd->children.count(); + for (int i = 0; i < n; ++i) { + if (QWidget *child = qobject_cast<QWidget*>(wd->children.at(i))) + removeDirtyWidget(child); + } +} + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_MANAGER) +bool QWidgetBackingStore::hasDirtyWindowDecoration() const +{ + QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData(); + if (tlwExtra && tlwExtra->qwsManager) + return !tlwExtra->qwsManager->d_func()->dirtyRegions.isEmpty(); + return false; +} + +void QWidgetBackingStore::paintWindowDecoration() +{ + if (!hasDirtyWindowDecoration()) + return; + + QDecoration &decoration = QApplication::qwsDecoration(); + const QRect decorationRect = tlw->rect(); + QRegion decorationRegion = decoration.region(tlw, decorationRect); + + QWSManagerPrivate *managerPrivate = tlw->d_func()->topData()->qwsManager->d_func(); + const bool doClipping = !managerPrivate->entireDecorationNeedsRepaint + && !managerPrivate->dirtyClip.isEmpty(); + + if (doClipping) { + decorationRegion &= static_cast<QWSWindowSurface *>(windowSurface)->clipRegion(); + decorationRegion &= managerPrivate->dirtyClip; + } + + if (decorationRegion.isEmpty()) + return; + + //### The QWS decorations do not always paint the pixels they promise to paint. + // This causes painting problems with QWSMemorySurface. Since none of the other + // window surfaces actually use the region, passing an empty region is a safe + // workaround. + + windowSurface->beginPaint(QRegion()); + + QPaintEngine *engine = windowSurface->paintDevice()->paintEngine(); + Q_ASSERT(engine); + const QRegion oldSystemClip(engine->systemClip()); + engine->setSystemClip(decorationRegion.translated(tlwOffset)); + + QPainter painter(windowSurface->paintDevice()); + painter.setFont(QApplication::font()); + painter.translate(tlwOffset); + + const int numDirty = managerPrivate->dirtyRegions.size(); + for (int i = 0; i < numDirty; ++i) { + const int area = managerPrivate->dirtyRegions.at(i); + + QRegion clipRegion = decoration.region(tlw, decorationRect, area); + if (!clipRegion.isEmpty()) { + // Decoration styles changes the clip and assumes the old clip is non-empty, + // so we have to set it, but in theory it shouldn't be required. + painter.setClipRegion(clipRegion); + decoration.paint(&painter, tlw, area, managerPrivate->dirtyStates.at(i)); + } + } + markDirtyOnScreen(decorationRegion, tlw, QPoint()); + + painter.end(); + windowSurface->endPaint(decorationRegion); + managerPrivate->clearDirtyRegions(); + engine->setSystemClip(oldSystemClip); +} +#endif + +void QWidgetBackingStore::updateLists(QWidget *cur) +{ + if (!cur) + return; + + QList<QObject*> children = cur->children(); + for (int i = 0; i < children.size(); ++i) { + QWidget *child = qobject_cast<QWidget*>(children.at(i)); + if (!child) + continue; + + updateLists(child); + } + + if (cur->testAttribute(Qt::WA_StaticContents)) + addStaticWidget(cur); + +#ifdef Q_BACKINGSTORE_SUBSURFACES + QTLWExtra *extra = cur->d_func()->maybeTopData(); + if (extra && extra->windowSurface && cur != tlw) + subSurfaces.append(extra->windowSurface); +#endif +} + +QWidgetBackingStore::QWidgetBackingStore(QWidget *topLevel) + : tlw(topLevel), dirtyOnScreenWidgets(0), hasDirtyFromPreviousSync(false) + , fullUpdatePending(0) +{ + windowSurface = tlw->windowSurface(); + if (!windowSurface) + windowSurface = topLevel->d_func()->createDefaultWindowSurface(); + + // The QWindowSurface constructor will call QWidget::setWindowSurface(), + // but automatically created surfaces should not be added to the topdata. +#ifdef Q_BACKINGSTORE_SUBSURFACES + Q_ASSERT(topLevel->d_func()->topData()->windowSurface == windowSurface); +#endif + topLevel->d_func()->topData()->windowSurface = 0; + + // Ensure all existing subsurfaces and static widgets are added to their respective lists. + updateLists(topLevel); +} + +QWidgetBackingStore::~QWidgetBackingStore() +{ + for (int c = 0; c < dirtyWidgets.size(); ++c) { + resetWidget(dirtyWidgets.at(c)); + } + + delete windowSurface; + windowSurface = 0; + delete dirtyOnScreenWidgets; + dirtyOnScreenWidgets = 0; +} + +//parent's coordinates; move whole rect; update parent and widget +//assume the screen blt has already been done, so we don't need to refresh that part +void QWidgetPrivate::moveRect(const QRect &rect, int dx, int dy) +{ + Q_Q(QWidget); + if (!q->isVisible() || (dx == 0 && dy == 0)) + return; + + QWidget *tlw = q->window(); + QTLWExtra* x = tlw->d_func()->topData(); + if (x->inTopLevelResize) + return; + + static int accelEnv = -1; + if (accelEnv == -1) { + accelEnv = qgetenv("QT_NO_FAST_MOVE").toInt() == 0; + } + + QWidget *pw = q->parentWidget(); + QPoint toplevelOffset = pw->mapTo(tlw, QPoint()); + QWidgetPrivate *pd = pw->d_func(); + QRect clipR(pd->clipRect()); +#ifdef Q_WS_QWS + QWidgetBackingStore *wbs = x->backingStore.data(); + QWSWindowSurface *surface = static_cast<QWSWindowSurface*>(wbs->windowSurface); + clipR = clipR.intersected(surface->clipRegion().translated(-toplevelOffset).boundingRect()); +#endif + const QRect newRect(rect.translated(dx, dy)); + QRect destRect = rect.intersected(clipR); + if (destRect.isValid()) + destRect = destRect.translated(dx, dy).intersected(clipR); + const QRect sourceRect(destRect.translated(-dx, -dy)); + const QRect parentRect(rect & clipR); + + bool accelerateMove = accelEnv && isOpaque +#ifndef QT_NO_GRAPHICSVIEW + // No accelerate move for proxy widgets. + && !tlw->d_func()->extra->proxyWidget +#endif + && !isOverlapped(sourceRect) && !isOverlapped(destRect); + + if (!accelerateMove) { + QRegion parentR(effectiveRectFor(parentRect)); + if (!extra || !extra->hasMask) { + parentR -= newRect; + } else { + // invalidateBuffer() excludes anything outside the mask + parentR += newRect & clipR; + } + pd->invalidateBuffer(parentR); + invalidateBuffer((newRect & clipR).translated(-data.crect.topLeft())); + } else { + + QWidgetBackingStore *wbs = x->backingStore.data(); + QRegion childExpose(newRect & clipR); + + if (sourceRect.isValid() && wbs->bltRect(sourceRect, dx, dy, pw)) + childExpose -= destRect; + + if (!pw->updatesEnabled()) + return; + + const bool childUpdatesEnabled = q->updatesEnabled(); + if (childUpdatesEnabled && !childExpose.isEmpty()) { + childExpose.translate(-data.crect.topLeft()); + wbs->markDirty(childExpose, q); + isMoved = true; + } + + QRegion parentExpose(parentRect); + parentExpose -= newRect; + if (extra && extra->hasMask) + parentExpose += QRegion(newRect) - extra->mask.translated(data.crect.topLeft()); + + if (!parentExpose.isEmpty()) { + wbs->markDirty(parentExpose, pw); + pd->isMoved = true; + } + + if (childUpdatesEnabled) { + QRegion needsFlush(sourceRect); + needsFlush += destRect; + wbs->markDirtyOnScreen(needsFlush, pw, toplevelOffset); + } + } +} + +//widget's coordinates; scroll within rect; only update widget +void QWidgetPrivate::scrollRect(const QRect &rect, int dx, int dy) +{ + Q_Q(QWidget); + QWidget *tlw = q->window(); + QTLWExtra* x = tlw->d_func()->topData(); + if (x->inTopLevelResize) + return; + + QWidgetBackingStore *wbs = x->backingStore.data(); + if (!wbs) + return; + + static int accelEnv = -1; + if (accelEnv == -1) { + accelEnv = qgetenv("QT_NO_FAST_SCROLL").toInt() == 0; + } + + QRect scrollRect = rect & clipRect(); + bool overlapped = false; + bool accelerateScroll = accelEnv && isOpaque + && !(overlapped = isOverlapped(scrollRect.translated(data.crect.topLeft()))); + +#if defined(Q_WS_QWS) + QWSWindowSurface *surface; + surface = static_cast<QWSWindowSurface*>(wbs->windowSurface); + + if (accelerateScroll && !surface->isBuffered()) { + const QRegion surfaceClip = surface->clipRegion(); + const QRegion outsideClip = QRegion(rect) - surfaceClip; + if (!outsideClip.isEmpty()) { + const QVector<QRect> clipped = (surfaceClip & rect).rects(); + if (clipped.size() < 8) { + for (int i = 0; i < clipped.size(); ++i) + this->scrollRect(clipped.at(i), dx, dy); + return; + } else { + accelerateScroll = false; + } + } + } +#endif // Q_WS_QWS + + if (!accelerateScroll) { + if (overlapped) { + QRegion region(scrollRect); + subtractOpaqueSiblings(region); + invalidateBuffer(region); + }else { + invalidateBuffer(scrollRect); + } + } else { + const QPoint toplevelOffset = q->mapTo(tlw, QPoint()); +#ifdef Q_WS_QWS + QWSWindowSurface *surface = static_cast<QWSWindowSurface*>(wbs->windowSurface); + const QRegion clip = surface->clipRegion().translated(-toplevelOffset) & scrollRect; + const QRect clipBoundingRect = clip.boundingRect(); + scrollRect &= clipBoundingRect; +#endif + const QRect destRect = scrollRect.translated(dx, dy) & scrollRect; + const QRect sourceRect = destRect.translated(-dx, -dy); + + QRegion childExpose(scrollRect); + if (sourceRect.isValid()) { + if (wbs->bltRect(sourceRect, dx, dy, q)) + childExpose -= destRect; + } + + if (inDirtyList) { + if (rect == q->rect()) { + dirty.translate(dx, dy); + } else { + QRegion dirtyScrollRegion = dirty.intersected(scrollRect); + if (!dirtyScrollRegion.isEmpty()) { + dirty -= dirtyScrollRegion; + dirtyScrollRegion.translate(dx, dy); + dirty += dirtyScrollRegion; + } + } + } + + if (!q->updatesEnabled()) + return; + + if (!childExpose.isEmpty()) { + wbs->markDirty(childExpose, q); + isScrolled = true; + } + + // Instead of using native scroll-on-screen, we copy from + // backingstore, giving only one screen update for each + // scroll, and a solid appearance + wbs->markDirtyOnScreen(destRect, q, toplevelOffset); + } +} + +static inline bool discardSyncRequest(QWidget *tlw, QTLWExtra *tlwExtra) +{ + if (!tlw || !tlwExtra) + return true; + +#ifdef Q_WS_X11 + // Delay the sync until we get an Expose event from X11 (initial show). + // Qt::WA_Mapped is set to true, but the actual mapping has not yet occurred. + // However, we must repaint immediately regardless of the state if someone calls repaint(). + if (tlwExtra->waitingForMapNotify && !tlwExtra->inRepaint) + return true; +#endif + + if (!tlw->testAttribute(Qt::WA_Mapped)) + return true; + + if (!tlw->isVisible() +#ifndef Q_WS_X11 + // If we're minimized on X11, WA_Mapped will be false and we + // will return in the case above. Some window managers on X11 + // sends us the PropertyNotify to change the minimized state + // *AFTER* we've received the expose event, which is baaad. + || tlw->isMinimized() +#endif + ) + return true; + + return false; +} + +/*! + Synchronizes the \a exposedRegion of the \a exposedWidget with the backing store. + + If there's nothing to repaint, the area is flushed and painting does not occur; + otherwise the area is marked as dirty on screen and will be flushed right after + we are done with all painting. +*/ +void QWidgetBackingStore::sync(QWidget *exposedWidget, const QRegion &exposedRegion) +{ + QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData(); + if (discardSyncRequest(tlw, tlwExtra) || tlwExtra->inTopLevelResize) + return; + + if (!exposedWidget || !exposedWidget->internalWinId() || !exposedWidget->isVisible() + || !exposedWidget->updatesEnabled() || exposedRegion.isEmpty()) { + return; + } + + // If there's no preserved contents support we always need + // to do a full repaint before flushing + if (!windowSurface->hasFeature(QWindowSurface::PreservedContents)) + fullUpdatePending = true; + + // Nothing to repaint. + if (!isDirty()) { + qt_flush(exposedWidget, exposedRegion, windowSurface, tlw, tlwOffset); + return; + } + + if (exposedWidget != tlw) + markDirtyOnScreen(exposedRegion, exposedWidget, exposedWidget->mapTo(tlw, QPoint())); + else + markDirtyOnScreen(exposedRegion, exposedWidget, QPoint()); + sync(); +} + +/*! + Synchronizes the backing store, i.e. dirty areas are repainted and flushed. +*/ +void QWidgetBackingStore::sync() +{ + QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData(); + if (discardSyncRequest(tlw, tlwExtra)) { + // If the top-level is minimized, it's not visible on the screen so we can delay the + // update until it's shown again. In order to do that we must keep the dirty states. + // These will be cleared when we receive the first expose after showNormal(). + // However, if the widget is not visible (isVisible() returns false), everything will + // be invalidated once the widget is shown again, so clear all dirty states. + if (!tlw->isVisible()) { + dirty = QRegion(); + for (int i = 0; i < dirtyWidgets.size(); ++i) + resetWidget(dirtyWidgets.at(i)); + dirtyWidgets.clear(); + fullUpdatePending = false; + } + return; + } + + const bool updatesDisabled = !tlw->updatesEnabled(); + bool repaintAllWidgets = false; + + const bool inTopLevelResize = tlwExtra->inTopLevelResize; + const QRect tlwRect(topLevelRect()); +#ifdef Q_WS_QPA + const QRect surfaceGeometry(tlwRect.topLeft(), windowSurface->size()); +#else + const QRect surfaceGeometry(windowSurface->geometry()); +#endif + if ((fullUpdatePending || inTopLevelResize || surfaceGeometry.size() != tlwRect.size()) && !updatesDisabled) { + if (hasStaticContents()) { + // Repaint existing dirty area and newly visible area. + const QRect clipRect(0, 0, surfaceGeometry.width(), surfaceGeometry.height()); + const QRegion staticRegion(staticContents(0, clipRect)); + QRegion newVisible(0, 0, tlwRect.width(), tlwRect.height()); + newVisible -= staticRegion; + dirty += newVisible; + windowSurface->setStaticContents(staticRegion); + } else { + // Repaint everything. + dirty = QRegion(0, 0, tlwRect.width(), tlwRect.height()); + for (int i = 0; i < dirtyWidgets.size(); ++i) + resetWidget(dirtyWidgets.at(i)); + dirtyWidgets.clear(); + repaintAllWidgets = true; + } + } + +#ifdef Q_WS_QPA + if (inTopLevelResize || surfaceGeometry.size() != tlwRect.size()) + windowSurface->resize(tlwRect.size()); +#else + if (inTopLevelResize || surfaceGeometry != tlwRect) + windowSurface->setGeometry(tlwRect); +#endif + + if (updatesDisabled) + return; + + if (hasDirtyFromPreviousSync) + dirty += dirtyFromPreviousSync; + + // Contains everything that needs repaint. + QRegion toClean(dirty); + + // Loop through all update() widgets and remove them from the list before they are + // painted (in case someone calls update() in paintEvent). If the widget is opaque + // and does not have transparent overlapping siblings, append it to the + // opaqueNonOverlappedWidgets list and paint it directly without composition. + QVarLengthArray<QWidget *, 32> opaqueNonOverlappedWidgets; + for (int i = 0; i < dirtyWidgets.size(); ++i) { + QWidget *w = dirtyWidgets.at(i); + QWidgetPrivate *wd = w->d_func(); + if (wd->data.in_destructor) + continue; + + // Clip with mask() and clipRect(). + wd->dirty &= wd->clipRect(); + wd->clipToEffectiveMask(wd->dirty); + + // Subtract opaque siblings and children. + bool hasDirtySiblingsAbove = false; + // We know for sure that the widget isn't overlapped if 'isMoved' is true. + if (!wd->isMoved) + wd->subtractOpaqueSiblings(wd->dirty, &hasDirtySiblingsAbove); + // Scrolled and moved widgets must draw all children. + if (!wd->isScrolled && !wd->isMoved) + wd->subtractOpaqueChildren(wd->dirty, w->rect()); + + if (wd->dirty.isEmpty()) { + resetWidget(w); + continue; + } + + const QRegion widgetDirty(w != tlw ? wd->dirty.translated(w->mapTo(tlw, QPoint())) + : wd->dirty); + toClean += widgetDirty; + +#ifndef QT_NO_GRAPHICSVIEW + if (tlw->d_func()->extra->proxyWidget) { + resetWidget(w); + continue; + } +#endif + + if (!hasDirtySiblingsAbove && wd->isOpaque && !dirty.intersects(widgetDirty.boundingRect())) { + opaqueNonOverlappedWidgets.append(w); + } else { + resetWidget(w); + dirty += widgetDirty; + } + } + dirtyWidgets.clear(); + + fullUpdatePending = false; + + if (toClean.isEmpty()) { + // Nothing to repaint. However, we might have newly exposed areas on the + // screen if this function was called from sync(QWidget *, QRegion)), so + // we have to make sure those are flushed. + flush(); + return; + } + +#ifndef QT_NO_GRAPHICSVIEW + if (tlw->d_func()->extra->proxyWidget) { + updateStaticContentsSize(); + dirty = QRegion(); + const QVector<QRect> rects(toClean.rects()); + for (int i = 0; i < rects.size(); ++i) + tlw->d_func()->extra->proxyWidget->update(rects.at(i)); + return; + } +#endif + +#ifndef Q_BACKINGSTORE_SUBSURFACES + BeginPaintInfo beginPaintInfo; + beginPaint(toClean, tlw, windowSurface, &beginPaintInfo); + if (beginPaintInfo.nothingToPaint) { + for (int i = 0; i < opaqueNonOverlappedWidgets.size(); ++i) + resetWidget(opaqueNonOverlappedWidgets[i]); + dirty = QRegion(); + return; + } +#endif + + // Must do this before sending any paint events because + // the size may change in the paint event. + updateStaticContentsSize(); + const QRegion dirtyCopy(dirty); + dirty = QRegion(); + + // Paint opaque non overlapped widgets. + for (int i = 0; i < opaqueNonOverlappedWidgets.size(); ++i) { + QWidget *w = opaqueNonOverlappedWidgets[i]; + QWidgetPrivate *wd = w->d_func(); + + int flags = QWidgetPrivate::DrawRecursive; + // Scrolled and moved widgets must draw all children. + if (!wd->isScrolled && !wd->isMoved) + flags |= QWidgetPrivate::DontDrawOpaqueChildren; + if (w == tlw) + flags |= QWidgetPrivate::DrawAsRoot; + + QRegion toBePainted(wd->dirty); + resetWidget(w); + +#ifdef Q_BACKINGSTORE_SUBSURFACES + QWindowSurface *subSurface = w->windowSurface(); + BeginPaintInfo beginPaintInfo; + + QPoint off = w->mapTo(tlw, QPoint()); + toBePainted.translate(off); + beginPaint(toBePainted, w, subSurface, &beginPaintInfo, true); + toBePainted.translate(-off); + + if (beginPaintInfo.nothingToPaint) + continue; + + if (beginPaintInfo.windowSurfaceRecreated) { + // Eep the window surface has changed. The old one may have been + // deleted, in which case we will segfault on the call to + // painterOffset() below. Use the new window surface instead. + subSurface = w->windowSurface(); + } + + QPoint offset(tlwOffset); + if (subSurface == windowSurface) + offset += w->mapTo(tlw, QPoint()); + else + offset = static_cast<QWSWindowSurface*>(subSurface)->painterOffset(); + wd->drawWidget(subSurface->paintDevice(), toBePainted, offset, flags, 0, this); + + endPaint(toBePainted, subSurface, &beginPaintInfo); +#else + QPoint offset(tlwOffset); + if (w != tlw) + offset += w->mapTo(tlw, QPoint()); + wd->drawWidget(windowSurface->paintDevice(), toBePainted, offset, flags, 0, this); +#endif + } + + // Paint the rest with composition. +#ifndef Q_BACKINGSTORE_SUBSURFACES + if (repaintAllWidgets || !dirtyCopy.isEmpty()) { + const int flags = QWidgetPrivate::DrawAsRoot | QWidgetPrivate::DrawRecursive; + tlw->d_func()->drawWidget(windowSurface->paintDevice(), dirtyCopy, tlwOffset, flags, 0, this); + } + + endPaint(toClean, windowSurface, &beginPaintInfo); +#else + if (!repaintAllWidgets && dirtyCopy.isEmpty()) + return; // Nothing more to paint. + + QList<QWindowSurface *> surfaceList(subSurfaces); + surfaceList.prepend(windowSurface); + const QRect dirtyBoundingRect(dirtyCopy.boundingRect()); + + // Loop through all window surfaces (incl. the top-level surface) and + // repaint those intersecting with the bounding rect of the dirty region. + for (int i = 0; i < surfaceList.size(); ++i) { + QWindowSurface *subSurface = surfaceList.at(i); + QWidget *w = subSurface->window(); + QWidgetPrivate *wd = w->d_func(); + + const QRect clipRect = wd->clipRect().translated(w->mapTo(tlw, QPoint())); + if (!qRectIntersects(dirtyBoundingRect, clipRect)) + continue; + + toClean = dirtyCopy; + BeginPaintInfo beginPaintInfo; + beginPaint(toClean, w, subSurface, &beginPaintInfo); + if (beginPaintInfo.nothingToPaint) + continue; + + if (beginPaintInfo.windowSurfaceRecreated) { + // Eep the window surface has changed. The old one may have been + // deleted, in which case we will segfault on the call to + // painterOffset() below. Use the new window surface instead. + subSurface = w->windowSurface(); + } + + int flags = QWidgetPrivate::DrawRecursive; + if (w == tlw) + flags |= QWidgetPrivate::DrawAsRoot; + const QPoint painterOffset = static_cast<QWSWindowSurface*>(subSurface)->painterOffset(); + wd->drawWidget(subSurface->paintDevice(), toClean, painterOffset, flags, 0, this); + + endPaint(toClean, subSurface, &beginPaintInfo); + } +#endif +} + +/*! + Flushes the contents of the backing store into the top-level widget. + If the \a widget is non-zero, the content is flushed to the \a widget. + If the \a surface is non-zero, the content of the \a surface is flushed. +*/ +void QWidgetBackingStore::flush(QWidget *widget, QWindowSurface *surface) +{ +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_MANAGER) + paintWindowDecoration(); +#endif + + if (!dirtyOnScreen.isEmpty()) { + QWidget *target = widget ? widget : tlw; + QWindowSurface *source = surface ? surface : windowSurface; + qt_flush(target, dirtyOnScreen, source, tlw, tlwOffset); + dirtyOnScreen = QRegion(); + } + + if (!dirtyOnScreenWidgets || dirtyOnScreenWidgets->isEmpty()) + return; + + for (int i = 0; i < dirtyOnScreenWidgets->size(); ++i) { + QWidget *w = dirtyOnScreenWidgets->at(i); + QWidgetPrivate *wd = w->d_func(); + Q_ASSERT(wd->needsFlush); + qt_flush(w, *wd->needsFlush, windowSurface, tlw, tlwOffset); + *wd->needsFlush = QRegion(); + } + dirtyOnScreenWidgets->clear(); +} + +static inline bool discardInvalidateBufferRequest(QWidget *widget, QTLWExtra *tlwExtra) +{ + Q_ASSERT(widget); + if (QApplication::closingDown()) + return true; + + if (!tlwExtra || tlwExtra->inTopLevelResize || !tlwExtra->backingStore) + return true; + + if (!widget->isVisible() || !widget->updatesEnabled()) + return true; + + return false; +} + +/*! + Invalidates the buffer when the widget is resized. + Static areas are never invalidated unless absolutely needed. +*/ +void QWidgetPrivate::invalidateBuffer_resizeHelper(const QPoint &oldPos, const QSize &oldSize) +{ + Q_Q(QWidget); + Q_ASSERT(!q->isWindow()); + Q_ASSERT(q->parentWidget()); + + const bool staticContents = q->testAttribute(Qt::WA_StaticContents); + const bool sizeDecreased = (data.crect.width() < oldSize.width()) + || (data.crect.height() < oldSize.height()); + + const QPoint offset(data.crect.x() - oldPos.x(), data.crect.y() - oldPos.y()); + const bool parentAreaExposed = !offset.isNull() || sizeDecreased; + const QRect newWidgetRect(q->rect()); + const QRect oldWidgetRect(0, 0, oldSize.width(), oldSize.height()); + + if (!staticContents || graphicsEffect) { + QRegion staticChildren; + QWidgetBackingStore *bs = 0; + if (offset.isNull() && (bs = maybeBackingStore())) + staticChildren = bs->staticContents(q, oldWidgetRect); + const bool hasStaticChildren = !staticChildren.isEmpty(); + + if (hasStaticChildren) { + QRegion dirty(newWidgetRect); + dirty -= staticChildren; + invalidateBuffer(dirty); + } else { + // Entire widget needs repaint. + invalidateBuffer(newWidgetRect); + } + + if (!parentAreaExposed) + return; + + // Invalidate newly exposed area of the parent. + if (!graphicsEffect && extra && extra->hasMask) { + QRegion parentExpose(extra->mask.translated(oldPos)); + parentExpose &= QRect(oldPos, oldSize); + if (hasStaticChildren) + parentExpose -= data.crect; // Offset is unchanged, safe to do this. + q->parentWidget()->d_func()->invalidateBuffer(parentExpose); + } else { + if (hasStaticChildren && !graphicsEffect) { + QRegion parentExpose(QRect(oldPos, oldSize)); + parentExpose -= data.crect; // Offset is unchanged, safe to do this. + q->parentWidget()->d_func()->invalidateBuffer(parentExpose); + } else { + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(QRect(oldPos, oldSize))); + } + } + return; + } + + // Move static content to its new position. + if (!offset.isNull()) { + if (sizeDecreased) { + const QSize minSize(qMin(oldSize.width(), data.crect.width()), + qMin(oldSize.height(), data.crect.height())); + moveRect(QRect(oldPos, minSize), offset.x(), offset.y()); + } else { + moveRect(QRect(oldPos, oldSize), offset.x(), offset.y()); + } + } + + // Invalidate newly visible area of the widget. + if (!sizeDecreased || !oldWidgetRect.contains(newWidgetRect)) { + QRegion newVisible(newWidgetRect); + newVisible -= oldWidgetRect; + invalidateBuffer(newVisible); + } + + if (!parentAreaExposed) + return; + + // Invalidate newly exposed area of the parent. + const QRect oldRect(oldPos, oldSize); + if (extra && extra->hasMask) { + QRegion parentExpose(oldRect); + parentExpose &= extra->mask.translated(oldPos); + parentExpose -= (extra->mask.translated(data.crect.topLeft()) & data.crect); + q->parentWidget()->d_func()->invalidateBuffer(parentExpose); + } else { + QRegion parentExpose(oldRect); + parentExpose -= data.crect; + q->parentWidget()->d_func()->invalidateBuffer(parentExpose); + } +} + +/*! + Invalidates the \a rgn (in widget's coordinates) of the backing store, i.e. + all widgets intersecting with the region will be repainted when the backing store + is synced. + + ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). +*/ +void QWidgetPrivate::invalidateBuffer(const QRegion &rgn) +{ + Q_Q(QWidget); + + QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData(); + if (discardInvalidateBufferRequest(q, tlwExtra) || rgn.isEmpty()) + return; + + QRegion wrgn(rgn); + wrgn &= clipRect(); + if (!graphicsEffect && extra && extra->hasMask) + wrgn &= extra->mask; + if (wrgn.isEmpty()) + return; + + tlwExtra->backingStore->markDirty(wrgn, q, false, true); +} + +/*! + This function is equivalent to calling invalidateBuffer(QRegion(rect), ...), but + is more efficient as it eliminates QRegion operations/allocations and can + use the rect more precisely for additional cut-offs. + + ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). +*/ +void QWidgetPrivate::invalidateBuffer(const QRect &rect) +{ + Q_Q(QWidget); + + QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData(); + if (discardInvalidateBufferRequest(q, tlwExtra) || rect.isEmpty()) + return; + + QRect wRect(rect); + wRect &= clipRect(); + if (wRect.isEmpty()) + return; + + if (graphicsEffect || !extra || !extra->hasMask) { + tlwExtra->backingStore->markDirty(wRect, q, false, true); + return; + } + + QRegion wRgn(extra->mask); + wRgn &= wRect; + if (wRgn.isEmpty()) + return; + + tlwExtra->backingStore->markDirty(wRgn, q, false, true); +} + +void QWidgetPrivate::repaint_sys(const QRegion &rgn) +{ + if (data.in_destructor) + return; + + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_StaticContents)) { + if (!extra) + createExtra(); + extra->staticContentsSize = data.crect.size(); + } + +#ifdef Q_WS_QPA //Dont even call q->p + QPaintEngine *engine = 0; +#else + QPaintEngine *engine = q->paintEngine(); +#endif + // QGLWidget does not support partial updates if: + // 1) The context is double buffered + // 2) The context is single buffered and auto-fill background is enabled. + const bool noPartialUpdateSupport = (engine && (engine->type() == QPaintEngine::OpenGL + || engine->type() == QPaintEngine::OpenGL2)) + && (usesDoubleBufferedGLContext || q->autoFillBackground()); + QRegion toBePainted(noPartialUpdateSupport ? q->rect() : rgn); + +#ifdef Q_WS_MAC + // No difference between update() and repaint() on the Mac. + update_sys(toBePainted); + return; +#endif + + toBePainted &= clipRect(); + clipToEffectiveMask(toBePainted); + if (toBePainted.isEmpty()) + return; // Nothing to repaint. + +#ifndef QT_NO_PAINT_DEBUG + bool flushed = QWidgetBackingStore::flushPaint(q, toBePainted); +#endif + + drawWidget(q, toBePainted, QPoint(), QWidgetPrivate::DrawAsRoot | QWidgetPrivate::DrawPaintOnScreen, 0); + +#ifndef QT_NO_PAINT_DEBUG + if (flushed) + QWidgetBackingStore::unflushPaint(q, toBePainted); +#endif + + if (!q->testAttribute(Qt::WA_PaintOutsidePaintEvent) && q->paintingActive()) + qWarning("QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent"); +} + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qbackingstore_p.h b/src/gui/painting/qbackingstore_p.h new file mode 100644 index 0000000000..05f4bfcb6c --- /dev/null +++ b/src/gui/painting/qbackingstore_p.h @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBACKINGSTORE_P_H +#define QBACKINGSTORE_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 <QDebug> +#include <QtGui/qwidget.h> +#include <private/qwidget_p.h> +#include <private/qwindowsurface_p.h> +#ifdef Q_WS_QWS +#include <private/qwindowsurface_qws_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class QWindowSurface; + +struct BeginPaintInfo { + inline BeginPaintInfo() : wasFlushed(0), nothingToPaint(0), windowSurfaceRecreated(0) {} + uint wasFlushed : 1; + uint nothingToPaint : 1; + uint windowSurfaceRecreated : 1; +}; + +class Q_AUTOTEST_EXPORT QWidgetBackingStore +{ +public: + QWidgetBackingStore(QWidget *t); + ~QWidgetBackingStore(); + + static void showYellowThing(QWidget *widget, const QRegion &rgn, int msec, bool); + + void sync(QWidget *exposedWidget, const QRegion &exposedRegion); + void sync(); + void flush(QWidget *widget = 0, QWindowSurface *surface = 0); + + inline QPoint topLevelOffset() const { return tlwOffset; } + + QWindowSurface *surface() const { return windowSurface; } + + inline bool isDirty() const + { + return !(dirtyWidgets.isEmpty() && dirty.isEmpty() && !hasDirtyFromPreviousSync + && !fullUpdatePending +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_MANAGER) + && !hasDirtyWindowDecoration() +#endif + ); + } + + // ### Qt 4.6: Merge into a template function (after MSVC isn't supported anymore). + void markDirty(const QRegion &rgn, QWidget *widget, bool updateImmediately = false, + bool invalidateBuffer = false); + void markDirty(const QRect &rect, QWidget *widget, bool updateImmediately = false, + bool invalidateBuffer = false); + +private: + QWidget *tlw; + QRegion dirtyOnScreen; // needsFlush + QRegion dirty; // needsRepaint + QRegion dirtyFromPreviousSync; + QVector<QWidget *> dirtyWidgets; + QVector<QWidget *> *dirtyOnScreenWidgets; + QList<QWidget *> staticWidgets; + QWindowSurface *windowSurface; +#ifdef Q_BACKINGSTORE_SUBSURFACES + QList<QWindowSurface*> subSurfaces; +#endif + uint hasDirtyFromPreviousSync : 1; + uint fullUpdatePending : 1; + + QPoint tlwOffset; + + static bool flushPaint(QWidget *widget, const QRegion &rgn); + static void unflushPaint(QWidget *widget, const QRegion &rgn); + + bool bltRect(const QRect &rect, int dx, int dy, QWidget *widget); + void releaseBuffer(); + + void beginPaint(QRegion &toClean, QWidget *widget, QWindowSurface *windowSurface, + BeginPaintInfo *returnInfo, bool toCleanIsInTopLevelCoordinates = true); + void endPaint(const QRegion &cleaned, QWindowSurface *windowSurface, BeginPaintInfo *beginPaintInfo); + + QRegion dirtyRegion(QWidget *widget = 0) const; + QRegion staticContents(QWidget *widget = 0, const QRect &withinClipRect = QRect()) const; + + void markDirtyOnScreen(const QRegion &dirtyOnScreen, QWidget *widget, const QPoint &topLevelOffset); + + void removeDirtyWidget(QWidget *w); + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_MANAGER) + bool hasDirtyWindowDecoration() const; + void paintWindowDecoration(); +#endif + void updateLists(QWidget *widget); + + inline void addDirtyWidget(QWidget *widget, const QRegion &rgn) + { + if (widget && !widget->d_func()->inDirtyList && !widget->data->in_destructor) { + QWidgetPrivate *widgetPrivate = widget->d_func(); +#ifndef QT_NO_GRAPHICSEFFECT + if (widgetPrivate->graphicsEffect) + widgetPrivate->dirty = widgetPrivate->effectiveRectFor(rgn.boundingRect()); + else +#endif //QT_NO_GRAPHICSEFFECT + widgetPrivate->dirty = rgn; + dirtyWidgets.append(widget); + widgetPrivate->inDirtyList = true; + } + } + + inline void dirtyWidgetsRemoveAll(QWidget *widget) + { + int i = 0; + while (i < dirtyWidgets.size()) { + if (dirtyWidgets.at(i) == widget) + dirtyWidgets.remove(i); + else + ++i; + } + } + + inline void addStaticWidget(QWidget *widget) + { + if (!widget) + return; + + Q_ASSERT(widget->testAttribute(Qt::WA_StaticContents)); + if (!staticWidgets.contains(widget)) + staticWidgets.append(widget); + } + + inline void removeStaticWidget(QWidget *widget) + { staticWidgets.removeAll(widget); } + + // Move the reparented widget and all its static children from this backing store + // to the new backing store if reparented into another top-level / backing store. + inline void moveStaticWidgets(QWidget *reparented) + { + Q_ASSERT(reparented); + QWidgetBackingStore *newBs = reparented->d_func()->maybeBackingStore(); + if (newBs == this) + return; + + int i = 0; + while (i < staticWidgets.size()) { + QWidget *w = staticWidgets.at(i); + if (reparented == w || reparented->isAncestorOf(w)) { + staticWidgets.removeAt(i); + if (newBs) + newBs->addStaticWidget(w); + } else { + ++i; + } + } + } + + inline QRect topLevelRect() const + { +#ifdef Q_WS_QWS + return tlw->frameGeometry(); +#else + return tlw->data->crect; +#endif + } + + inline void appendDirtyOnScreenWidget(QWidget *widget) + { + if (!widget) + return; + + if (!dirtyOnScreenWidgets) { + dirtyOnScreenWidgets = new QVector<QWidget *>; + dirtyOnScreenWidgets->append(widget); + } else if (!dirtyOnScreenWidgets->contains(widget)) { + dirtyOnScreenWidgets->append(widget); + } + } + + inline void dirtyOnScreenWidgetsRemoveAll(QWidget *widget) + { + if (!widget || !dirtyOnScreenWidgets) + return; + + int i = 0; + while (i < dirtyOnScreenWidgets->size()) { + if (dirtyOnScreenWidgets->at(i) == widget) + dirtyOnScreenWidgets->remove(i); + else + ++i; + } + } + + inline void resetWidget(QWidget *widget) + { + if (widget) { + widget->d_func()->inDirtyList = false; + widget->d_func()->isScrolled = false; + widget->d_func()->isMoved = false; + widget->d_func()->dirty = QRegion(); + } + } + + inline void updateStaticContentsSize() + { + for (int i = 0; i < staticWidgets.size(); ++i) { + QWidgetPrivate *wd = staticWidgets.at(i)->d_func(); + if (!wd->extra) + wd->createExtra(); + wd->extra->staticContentsSize = wd->data.crect.size(); + } + } + + inline bool hasStaticContents() const + { return !staticWidgets.isEmpty() && windowSurface->hasFeature(QWindowSurface::StaticContents); } + + friend QRegion qt_dirtyRegion(QWidget *); + friend class QWidgetPrivate; + friend class QWidget; + friend class QWSManagerPrivate; + friend class QETWidget; + friend class QWindowSurface; + friend class QWSWindowSurface; +}; + +QT_END_NAMESPACE + +#endif // QBACKINGSTORE_P_H diff --git a/src/gui/painting/qbezier.cpp b/src/gui/painting/qbezier.cpp new file mode 100644 index 0000000000..a2f0edd397 --- /dev/null +++ b/src/gui/painting/qbezier.cpp @@ -0,0 +1,702 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbezier_p.h" +#include <qdebug.h> +#include <qline.h> +#include <qpolygon.h> +#include <qvector.h> +#include <qlist.h> +#include <qmath.h> + +#include <private/qnumeric_p.h> +#include <private/qmath_p.h> + +QT_BEGIN_NAMESPACE + +//#define QDEBUG_BEZIER + +#ifdef FLOAT_ACCURACY +#define INV_EPS (1L<<23) +#else +/* The value of 1.0 / (1L<<14) is enough for most applications */ +#define INV_EPS (1L<<14) +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 +#endif + +/*! + \internal +*/ +QBezier QBezier::fromPoints(const QPointF &p1, const QPointF &p2, + const QPointF &p3, const QPointF &p4) +{ + QBezier b; + b.x1 = p1.x(); + b.y1 = p1.y(); + b.x2 = p2.x(); + b.y2 = p2.y(); + b.x3 = p3.x(); + b.y3 = p3.y(); + b.x4 = p4.x(); + b.y4 = p4.y(); + return b; +} + +/*! + \internal +*/ +QPolygonF QBezier::toPolygon(qreal bezier_flattening_threshold) const +{ + // flattening is done by splitting the bezier until we can replace the segment by a straight + // line. We split further until the control points are close enough to the line connecting the + // boundary points. + // + // the Distance of a point p from a line given by the points (a,b) is given by: + // + // d = abs( (bx - ax)(ay - py) - (by - ay)(ax - px) ) / line_length + // + // We can stop splitting if both control points are close enough to the line. + // To make the algorithm faster we use the manhattan length of the line. + + QPolygonF polygon; + polygon.append(QPointF(x1, y1)); + addToPolygon(&polygon, bezier_flattening_threshold); + return polygon; +} + +QBezier QBezier::mapBy(const QTransform &transform) const +{ + return QBezier::fromPoints(transform.map(pt1()), transform.map(pt2()), transform.map(pt3()), transform.map(pt4())); +} + +QBezier QBezier::getSubRange(qreal t0, qreal t1) const +{ + QBezier result; + QBezier temp; + + // cut at t1 + if (qFuzzyIsNull(t1 - qreal(1.))) { + result = *this; + } else { + temp = *this; + temp.parameterSplitLeft(t1, &result); + } + + // cut at t0 + if (!qFuzzyIsNull(t0)) + result.parameterSplitLeft(t0 / t1, &temp); + + return result; +} + +static inline int quadraticRoots(qreal a, qreal b, qreal c, + qreal *x1, qreal *x2) +{ + if (qFuzzyIsNull(a)) { + if (qFuzzyIsNull(b)) + return 0; + *x1 = *x2 = (-c / b); + return 1; + } else { + const qreal det = b * b - 4 * a * c; + if (qFuzzyIsNull(det)) { + *x1 = *x2 = -b / (2 * a); + return 1; + } + if (det > 0) { + if (qFuzzyIsNull(b)) { + *x2 = qSqrt(-c / a); + *x1 = -(*x2); + return 2; + } + const qreal stableA = b / (2 * a); + const qreal stableB = c / (a * stableA * stableA); + const qreal stableC = -1 - qSqrt(1 - stableB); + *x2 = stableA * stableC; + *x1 = (stableA * stableB) / stableC; + return 2; + } else + return 0; + } +} + +static inline bool findInflections(qreal a, qreal b, qreal c, + qreal *t1 , qreal *t2, qreal *tCups) +{ + qreal r1 = 0, r2 = 0; + + short rootsCount = quadraticRoots(a, b, c, &r1, &r2); + + if (rootsCount >= 1) { + if (r1 < r2) { + *t1 = r1; + *t2 = r2; + } else { + *t1 = r2; + *t2 = r1; + } + if (!qFuzzyIsNull(a)) + *tCups = qreal(0.5) * (-b / a); + else + *tCups = 2; + + return true; + } + + return false; +} + + +void QBezier::addToPolygon(QPolygonF *polygon, qreal bezier_flattening_threshold) const +{ + QBezier beziers[32]; + beziers[0] = *this; + QBezier *b = beziers; + + while (b >= beziers) { + // check if we can pop the top bezier curve from the stack + qreal y4y1 = b->y4 - b->y1; + qreal x4x1 = b->x4 - b->x1; + qreal l = qAbs(x4x1) + qAbs(y4y1); + qreal d; + if (l > 1.) { + d = qAbs( (x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2) ) + + qAbs( (x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3) ); + } else { + d = qAbs(b->x1 - b->x2) + qAbs(b->y1 - b->y2) + + qAbs(b->x1 - b->x3) + qAbs(b->y1 - b->y3); + l = 1.; + } + if (d < bezier_flattening_threshold*l || b == beziers + 31) { + // good enough, we pop it off and add the endpoint + polygon->append(QPointF(b->x4, b->y4)); + --b; + } else { + // split, second half of the polygon goes lower into the stack + b->split(b+1, b); + ++b; + } + } +} + +QRectF QBezier::bounds() const +{ + qreal xmin = x1; + qreal xmax = x1; + if (x2 < xmin) + xmin = x2; + else if (x2 > xmax) + xmax = x2; + if (x3 < xmin) + xmin = x3; + else if (x3 > xmax) + xmax = x3; + if (x4 < xmin) + xmin = x4; + else if (x4 > xmax) + xmax = x4; + + qreal ymin = y1; + qreal ymax = y1; + if (y2 < ymin) + ymin = y2; + else if (y2 > ymax) + ymax = y2; + if (y3 < ymin) + ymin = y3; + else if (y3 > ymax) + ymax = y3; + if (y4 < ymin) + ymin = y4; + else if (y4 > ymax) + ymax = y4; + return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); +} + + +enum ShiftResult { + Ok, + Discard, + Split, + Circle +}; + +static ShiftResult good_offset(const QBezier *b1, const QBezier *b2, qreal offset, qreal threshold) +{ + const qreal o2 = offset*offset; + const qreal max_dist_line = threshold*offset*offset; + const qreal max_dist_normal = threshold*offset; + const qreal spacing = qreal(0.25); + for (qreal i = spacing; i < qreal(0.99); i += spacing) { + QPointF p1 = b1->pointAt(i); + QPointF p2 = b2->pointAt(i); + qreal d = (p1.x() - p2.x())*(p1.x() - p2.x()) + (p1.y() - p2.y())*(p1.y() - p2.y()); + if (qAbs(d - o2) > max_dist_line) + return Split; + + QPointF normalPoint = b1->normalVector(i); + qreal l = qAbs(normalPoint.x()) + qAbs(normalPoint.y()); + if (l != qreal(0.0)) { + d = qAbs( normalPoint.x()*(p1.y() - p2.y()) - normalPoint.y()*(p1.x() - p2.x()) ) / l; + if (d > max_dist_normal) + return Split; + } + } + return Ok; +} + +static ShiftResult shift(const QBezier *orig, QBezier *shifted, qreal offset, qreal threshold) +{ + int map[4]; + bool p1_p2_equal = (orig->x1 == orig->x2 && orig->y1 == orig->y2); + bool p2_p3_equal = (orig->x2 == orig->x3 && orig->y2 == orig->y3); + bool p3_p4_equal = (orig->x3 == orig->x4 && orig->y3 == orig->y4); + + QPointF points[4]; + int np = 0; + points[np] = QPointF(orig->x1, orig->y1); + map[0] = 0; + ++np; + if (!p1_p2_equal) { + points[np] = QPointF(orig->x2, orig->y2); + ++np; + } + map[1] = np - 1; + if (!p2_p3_equal) { + points[np] = QPointF(orig->x3, orig->y3); + ++np; + } + map[2] = np - 1; + if (!p3_p4_equal) { + points[np] = QPointF(orig->x4, orig->y4); + ++np; + } + map[3] = np - 1; + if (np == 1) + return Discard; + + QRectF b = orig->bounds(); + if (np == 4 && b.width() < .1*offset && b.height() < .1*offset) { + qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) + + (orig->y1 - orig->y2)*(orig->y1 - orig->y1) * + (orig->x3 - orig->x4)*(orig->x3 - orig->x4) + + (orig->y3 - orig->y4)*(orig->y3 - orig->y4); + qreal dot = (orig->x1 - orig->x2)*(orig->x3 - orig->x4) + + (orig->y1 - orig->y2)*(orig->y3 - orig->y4); + if (dot < 0 && dot*dot < 0.8*l) + // the points are close and reverse dirction. Approximate the whole + // thing by a semi circle + return Circle; + } + + QPointF points_shifted[4]; + + QLineF prev = QLineF(QPointF(), points[1] - points[0]); + QPointF prev_normal = prev.normalVector().unitVector().p2(); + + points_shifted[0] = points[0] + offset * prev_normal; + + for (int i = 1; i < np - 1; ++i) { + QLineF next = QLineF(QPointF(), points[i + 1] - points[i]); + QPointF next_normal = next.normalVector().unitVector().p2(); + + QPointF normal_sum = prev_normal + next_normal; + + qreal r = qreal(1.0) + prev_normal.x() * next_normal.x() + + prev_normal.y() * next_normal.y(); + + if (qFuzzyIsNull(r)) { + points_shifted[i] = points[i] + offset * prev_normal; + } else { + qreal k = offset / r; + points_shifted[i] = points[i] + k * normal_sum; + } + + prev_normal = next_normal; + } + + points_shifted[np - 1] = points[np - 1] + offset * prev_normal; + + *shifted = QBezier::fromPoints(points_shifted[map[0]], points_shifted[map[1]], + points_shifted[map[2]], points_shifted[map[3]]); + + return good_offset(orig, shifted, offset, threshold); +} + +// This value is used to determine the length of control point vectors +// when approximating arc segments as curves. The factor is multiplied +// with the radius of the circle. +#define KAPPA qreal(0.5522847498) + + +static bool addCircle(const QBezier *b, qreal offset, QBezier *o) +{ + QPointF normals[3]; + + normals[0] = QPointF(b->y2 - b->y1, b->x1 - b->x2); + qreal dist = qSqrt(normals[0].x()*normals[0].x() + normals[0].y()*normals[0].y()); + if (qFuzzyIsNull(dist)) + return false; + normals[0] /= dist; + normals[2] = QPointF(b->y4 - b->y3, b->x3 - b->x4); + dist = qSqrt(normals[2].x()*normals[2].x() + normals[2].y()*normals[2].y()); + if (qFuzzyIsNull(dist)) + return false; + normals[2] /= dist; + + normals[1] = QPointF(b->x1 - b->x2 - b->x3 + b->x4, b->y1 - b->y2 - b->y3 + b->y4); + normals[1] /= -1*qSqrt(normals[1].x()*normals[1].x() + normals[1].y()*normals[1].y()); + + qreal angles[2]; + qreal sign = 1.; + for (int i = 0; i < 2; ++i) { + qreal cos_a = normals[i].x()*normals[i+1].x() + normals[i].y()*normals[i+1].y(); + if (cos_a > 1.) + cos_a = 1.; + if (cos_a < -1.) + cos_a = -1; + angles[i] = qAcos(cos_a)/Q_PI; + } + + if (angles[0] + angles[1] > 1.) { + // more than 180 degrees + normals[1] = -normals[1]; + angles[0] = 1. - angles[0]; + angles[1] = 1. - angles[1]; + sign = -1.; + + } + + QPointF circle[3]; + circle[0] = QPointF(b->x1, b->y1) + normals[0]*offset; + circle[1] = QPointF(qreal(0.5)*(b->x1 + b->x4), qreal(0.5)*(b->y1 + b->y4)) + normals[1]*offset; + circle[2] = QPointF(b->x4, b->y4) + normals[2]*offset; + + for (int i = 0; i < 2; ++i) { + qreal kappa = qreal(2.0) * KAPPA * sign * offset * angles[i]; + + o->x1 = circle[i].x(); + o->y1 = circle[i].y(); + o->x2 = circle[i].x() - normals[i].y()*kappa; + o->y2 = circle[i].y() + normals[i].x()*kappa; + o->x3 = circle[i+1].x() + normals[i+1].y()*kappa; + o->y3 = circle[i+1].y() - normals[i+1].x()*kappa; + o->x4 = circle[i+1].x(); + o->y4 = circle[i+1].y(); + + ++o; + } + return true; +} + +int QBezier::shifted(QBezier *curveSegments, int maxSegments, qreal offset, float threshold) const +{ + Q_ASSERT(curveSegments); + Q_ASSERT(maxSegments > 0); + + if (x1 == x2 && x1 == x3 && x1 == x4 && + y1 == y2 && y1 == y3 && y1 == y4) + return 0; + + --maxSegments; + QBezier beziers[10]; +redo: + beziers[0] = *this; + QBezier *b = beziers; + QBezier *o = curveSegments; + + while (b >= beziers) { + int stack_segments = b - beziers + 1; + if ((stack_segments == 10) || (o - curveSegments == maxSegments - stack_segments)) { + threshold *= qreal(1.5); + if (threshold > qreal(2.0)) + goto give_up; + goto redo; + } + ShiftResult res = shift(b, o, offset, threshold); + if (res == Discard) { + --b; + } else if (res == Ok) { + ++o; + --b; + continue; + } else if (res == Circle && maxSegments - (o - curveSegments) >= 2) { + // add semi circle + if (addCircle(b, offset, o)) + o += 2; + --b; + } else { + b->split(b+1, b); + ++b; + } + } + +give_up: + while (b >= beziers) { + ShiftResult res = shift(b, o, offset, threshold); + + // if res isn't Ok or Split then *o is undefined + if (res == Ok || res == Split) + ++o; + + --b; + } + + Q_ASSERT(o - curveSegments <= maxSegments); + return o - curveSegments; +} + +#ifdef QDEBUG_BEZIER +static QDebug operator<<(QDebug dbg, const QBezier &bz) +{ + dbg << '[' << bz.x1<< ", " << bz.y1 << "], " + << '[' << bz.x2 <<", " << bz.y2 << "], " + << '[' << bz.x3 <<", " << bz.y3 << "], " + << '[' << bz.x4 <<", " << bz.y4 << ']'; + return dbg; +} +#endif + +static inline void splitBezierAt(const QBezier &bez, qreal t, + QBezier *left, QBezier *right) +{ + left->x1 = bez.x1; + left->y1 = bez.y1; + + left->x2 = bez.x1 + t * ( bez.x2 - bez.x1 ); + left->y2 = bez.y1 + t * ( bez.y2 - bez.y1 ); + + left->x3 = bez.x2 + t * ( bez.x3 - bez.x2 ); // temporary holding spot + left->y3 = bez.y2 + t * ( bez.y3 - bez.y2 ); // temporary holding spot + + right->x3 = bez.x3 + t * ( bez.x4 - bez.x3 ); + right->y3 = bez.y3 + t * ( bez.y4 - bez.y3 ); + + right->x2 = left->x3 + t * ( right->x3 - left->x3); + right->y2 = left->y3 + t * ( right->y3 - left->y3); + + left->x3 = left->x2 + t * ( left->x3 - left->x2 ); + left->y3 = left->y2 + t * ( left->y3 - left->y2 ); + + left->x4 = right->x1 = left->x3 + t * (right->x2 - left->x3); + left->y4 = right->y1 = left->y3 + t * (right->y2 - left->y3); + + right->x4 = bez.x4; + right->y4 = bez.y4; +} + +qreal QBezier::length(qreal error) const +{ + qreal length = qreal(0.0); + + addIfClose(&length, error); + + return length; +} + +void QBezier::addIfClose(qreal *length, qreal error) const +{ + QBezier left, right; /* bez poly splits */ + + qreal len = qreal(0.0); /* arc length */ + qreal chord; /* chord length */ + + len = len + QLineF(QPointF(x1, y1),QPointF(x2, y2)).length(); + len = len + QLineF(QPointF(x2, y2),QPointF(x3, y3)).length(); + len = len + QLineF(QPointF(x3, y3),QPointF(x4, y4)).length(); + + chord = QLineF(QPointF(x1, y1),QPointF(x4, y4)).length(); + + if((len-chord) > error) { + split(&left, &right); /* split in two */ + left.addIfClose(length, error); /* try left side */ + right.addIfClose(length, error); /* try right side */ + return; + } + + *length = *length + len; + + return; +} + +qreal QBezier::tForY(qreal t0, qreal t1, qreal y) const +{ + qreal py0 = pointAt(t0).y(); + qreal py1 = pointAt(t1).y(); + + if (py0 > py1) { + qSwap(py0, py1); + qSwap(t0, t1); + } + + Q_ASSERT(py0 <= py1); + + if (py0 >= y) + return t0; + else if (py1 <= y) + return t1; + + Q_ASSERT(py0 < y && y < py1); + + qreal lt = t0; + qreal dt; + do { + qreal t = qreal(0.5) * (t0 + t1); + + qreal a, b, c, d; + QBezier::coefficients(t, a, b, c, d); + qreal yt = a * y1 + b * y2 + c * y3 + d * y4; + + if (yt < y) { + t0 = t; + py0 = yt; + } else { + t1 = t; + py1 = yt; + } + dt = lt - t; + lt = t; + } while (qAbs(dt) > qreal(1e-7)); + + return t0; +} + +int QBezier::stationaryYPoints(qreal &t0, qreal &t1) const +{ + // y(t) = (1 - t)^3 * y1 + 3 * (1 - t)^2 * t * y2 + 3 * (1 - t) * t^2 * y3 + t^3 * y4 + // y'(t) = 3 * (-(1-2t+t^2) * y1 + (1 - 4 * t + 3 * t^2) * y2 + (2 * t - 3 * t^2) * y3 + t^2 * y4) + // y'(t) = 3 * ((-y1 + 3 * y2 - 3 * y3 + y4)t^2 + (2 * y1 - 4 * y2 + 2 * y3)t + (-y1 + y2)) + + const qreal a = -y1 + 3 * y2 - 3 * y3 + y4; + const qreal b = 2 * y1 - 4 * y2 + 2 * y3; + const qreal c = -y1 + y2; + + if (qFuzzyIsNull(a)) { + if (qFuzzyIsNull(b)) + return 0; + + t0 = -c / b; + return t0 > 0 && t0 < 1; + } + + qreal reciprocal = b * b - 4 * a * c; + + if (qFuzzyIsNull(reciprocal)) { + t0 = -b / (2 * a); + return t0 > 0 && t0 < 1; + } else if (reciprocal > 0) { + qreal temp = qSqrt(reciprocal); + + t0 = (-b - temp)/(2*a); + t1 = (-b + temp)/(2*a); + + if (t1 < t0) + qSwap(t0, t1); + + int count = 0; + qreal t[2] = { 0, 1 }; + + if (t0 > 0 && t0 < 1) + t[count++] = t0; + if (t1 > 0 && t1 < 1) + t[count++] = t1; + + t0 = t[0]; + t1 = t[1]; + + return count; + } + + return 0; +} + +qreal QBezier::tAtLength(qreal l) const +{ + qreal len = length(); + qreal t = qreal(1.0); + const qreal error = qreal(0.01); + if (l > len || qFuzzyCompare(l, len)) + return t; + + t *= qreal(0.5); + //int iters = 0; + //qDebug()<<"LEN is "<<l<<len; + qreal lastBigger = qreal(1.0); + while (1) { + //qDebug()<<"\tt is "<<t; + QBezier right = *this; + QBezier left; + right.parameterSplitLeft(t, &left); + qreal lLen = left.length(); + if (qAbs(lLen - l) < error) + break; + + if (lLen < l) { + t += (lastBigger - t) * qreal(0.5); + } else { + lastBigger = t; + t -= t * qreal(0.5); + } + //++iters; + } + //qDebug()<<"number of iters is "<<iters; + return t; +} + +QBezier QBezier::bezierOnInterval(qreal t0, qreal t1) const +{ + if (t0 == 0 && t1 == 1) + return *this; + + QBezier bezier = *this; + + QBezier result; + bezier.parameterSplitLeft(t0, &result); + qreal trueT = (t1-t0)/(1-t0); + bezier.parameterSplitLeft(trueT, &result); + + return result; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qbezier_p.h b/src/gui/painting/qbezier_p.h new file mode 100644 index 0000000000..8d37e9a653 --- /dev/null +++ b/src/gui/painting/qbezier_p.h @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBEZIER_P_H +#define QBEZIER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qpoint.h" +#include "QtCore/qline.h" +#include "QtCore/qrect.h" +#include "QtCore/qvector.h" +#include "QtCore/qlist.h" +#include "QtCore/qpair.h" +#include "QtGui/qtransform.h" + +QT_BEGIN_NAMESPACE + +class QPolygonF; + +class Q_GUI_EXPORT QBezier +{ +public: + static QBezier fromPoints(const QPointF &p1, const QPointF &p2, + const QPointF &p3, const QPointF &p4); + + static void coefficients(qreal t, qreal &a, qreal &b, qreal &c, qreal &d); + + inline QPointF pointAt(qreal t) const; + inline QPointF normalVector(qreal t) const; + + inline QPointF derivedAt(qreal t) const; + inline QPointF secondDerivedAt(qreal t) const; + + QPolygonF toPolygon(qreal bezier_flattening_threshold = 0.5) const; + void addToPolygon(QPolygonF *p, qreal bezier_flattening_threshold = 0.5) const; + + QRectF bounds() const; + qreal length(qreal error = 0.01) const; + void addIfClose(qreal *length, qreal error) const; + + qreal tAtLength(qreal len) const; + + int stationaryYPoints(qreal &t0, qreal &t1) const; + qreal tForY(qreal t0, qreal t1, qreal y) const; + + QPointF pt1() const { return QPointF(x1, y1); } + QPointF pt2() const { return QPointF(x2, y2); } + QPointF pt3() const { return QPointF(x3, y3); } + QPointF pt4() const { return QPointF(x4, y4); } + + QBezier mapBy(const QTransform &transform) const; + + inline QPointF midPoint() const; + inline QLineF midTangent() const; + + inline QLineF startTangent() const; + inline QLineF endTangent() const; + + inline void parameterSplitLeft(qreal t, QBezier *left); + inline void split(QBezier *firstHalf, QBezier *secondHalf) const; + + int shifted(QBezier *curveSegments, int maxSegmets, + qreal offset, float threshold) const; + + QBezier bezierOnInterval(qreal t0, qreal t1) const; + QBezier getSubRange(qreal t0, qreal t1) const; + + qreal x1, y1, x2, y2, x3, y3, x4, y4; +}; + +inline QPointF QBezier::midPoint() const +{ + return QPointF((x1 + x4 + 3*(x2 + x3))/8., (y1 + y4 + 3*(y2 + y3))/8.); +} + +inline QLineF QBezier::midTangent() const +{ + QPointF mid = midPoint(); + QLineF dir(QLineF(x1, y1, x2, y2).pointAt(0.5), QLineF(x3, y3, x4, y4).pointAt(0.5)); + return QLineF(mid.x() - dir.dx(), mid.y() - dir.dy(), + mid.x() + dir.dx(), mid.y() + dir.dy()); +} + +inline QLineF QBezier::startTangent() const +{ + QLineF tangent(pt1(), pt2()); + if (tangent.isNull()) + tangent = QLineF(pt1(), pt3()); + if (tangent.isNull()) + tangent = QLineF(pt1(), pt4()); + return tangent; +} + +inline QLineF QBezier::endTangent() const +{ + QLineF tangent(pt4(), pt3()); + if (tangent.isNull()) + tangent = QLineF(pt4(), pt2()); + if (tangent.isNull()) + tangent = QLineF(pt4(), pt1()); + return tangent; +} + +inline void QBezier::coefficients(qreal t, qreal &a, qreal &b, qreal &c, qreal &d) +{ + qreal m_t = 1. - t; + b = m_t * m_t; + c = t * t; + d = c * t; + a = b * m_t; + b *= 3. * t; + c *= 3. * m_t; +} + +inline QPointF QBezier::pointAt(qreal t) const +{ +#if 1 + qreal a, b, c, d; + coefficients(t, a, b, c, d); + return QPointF(a*x1 + b*x2 + c*x3 + d*x4, a*y1 + b*y2 + c*y3 + d*y4); +#else + // numerically more stable: + qreal m_t = 1. - t; + qreal a = x1*m_t + x2*t; + qreal b = x2*m_t + x3*t; + qreal c = x3*m_t + x4*t; + a = a*m_t + b*t; + b = b*m_t + c*t; + qreal x = a*m_t + b*t; + qreal a = y1*m_t + y2*t; + qreal b = y2*m_t + y3*t; + qreal c = y3*m_t + y4*t; + a = a*m_t + b*t; + b = b*m_t + c*t; + qreal y = a*m_t + b*t; + return QPointF(x, y); +#endif +} + +inline QPointF QBezier::normalVector(qreal t) const +{ + qreal m_t = 1. - t; + qreal a = m_t * m_t; + qreal b = t * m_t; + qreal c = t * t; + + return QPointF((y2-y1) * a + (y3-y2) * b + (y4-y3) * c, -(x2-x1) * a - (x3-x2) * b - (x4-x3) * c); +} + +inline QPointF QBezier::derivedAt(qreal t) const +{ + // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * t^2) * p2 + t^2 * p3) + + qreal m_t = 1. - t; + + qreal d = t * t; + qreal a = -m_t * m_t; + qreal b = 1 - 4 * t + 3 * d; + qreal c = 2 * t - 3 * d; + + return 3 * QPointF(a * x1 + b * x2 + c * x3 + d * x4, + a * y1 + b * y2 + c * y3 + d * y4); +} + +inline QPointF QBezier::secondDerivedAt(qreal t) const +{ + qreal a = 2. - 2. * t; + qreal b = -4 + 6 * t; + qreal c = 2 - 6 * t; + qreal d = 2 * t; + + return 3 * QPointF(a * x1 + b * x2 + c * x3 + d * x4, + a * y1 + b * y2 + c * y3 + d * y4); +} + +inline void QBezier::split(QBezier *firstHalf, QBezier *secondHalf) const +{ + Q_ASSERT(firstHalf); + Q_ASSERT(secondHalf); + + qreal c = (x2 + x3)*.5; + firstHalf->x2 = (x1 + x2)*.5; + secondHalf->x3 = (x3 + x4)*.5; + firstHalf->x1 = x1; + secondHalf->x4 = x4; + firstHalf->x3 = (firstHalf->x2 + c)*.5; + secondHalf->x2 = (secondHalf->x3 + c)*.5; + firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2)*.5; + + c = (y2 + y3)/2; + firstHalf->y2 = (y1 + y2)*.5; + secondHalf->y3 = (y3 + y4)*.5; + firstHalf->y1 = y1; + secondHalf->y4 = y4; + firstHalf->y3 = (firstHalf->y2 + c)*.5; + secondHalf->y2 = (secondHalf->y3 + c)*.5; + firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2)*.5; +} + +inline void QBezier::parameterSplitLeft(qreal t, QBezier *left) +{ + left->x1 = x1; + left->y1 = y1; + + left->x2 = x1 + t * ( x2 - x1 ); + left->y2 = y1 + t * ( y2 - y1 ); + + left->x3 = x2 + t * ( x3 - x2 ); // temporary holding spot + left->y3 = y2 + t * ( y3 - y2 ); // temporary holding spot + + x3 = x3 + t * ( x4 - x3 ); + y3 = y3 + t * ( y4 - y3 ); + + x2 = left->x3 + t * ( x3 - left->x3); + y2 = left->y3 + t * ( y3 - left->y3); + + left->x3 = left->x2 + t * ( left->x3 - left->x2 ); + left->y3 = left->y2 + t * ( left->y3 - left->y2 ); + + left->x4 = x1 = left->x3 + t * (x2 - left->x3); + left->y4 = y1 = left->y3 + t * (y2 - left->y3); +} + +QT_END_NAMESPACE + +#endif // QBEZIER_P_H diff --git a/src/gui/painting/qblendfunctions.cpp b/src/gui/painting/qblendfunctions.cpp new file mode 100644 index 0000000000..10674f8ef5 --- /dev/null +++ b/src/gui/painting/qblendfunctions.cpp @@ -0,0 +1,1644 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qmath.h> +#include "qblendfunctions_p.h" + +QT_BEGIN_NAMESPACE + +struct SourceOnlyAlpha +{ + inline uchar alpha(uchar src) const { return src; } + inline quint16 bytemul(quint16 spix) const { return spix; } +}; + + +struct SourceAndConstAlpha +{ + SourceAndConstAlpha(int a) : m_alpha256(a) { + m_alpha255 = (m_alpha256 * 255) >> 8; + }; + inline uchar alpha(uchar src) const { return (src * m_alpha256) >> 8; } + inline quint16 bytemul(quint16 x) const { + uint t = (((x & 0x07e0)*m_alpha255) >> 8) & 0x07e0; + t |= (((x & 0xf81f)*(m_alpha255>>2)) >> 6) & 0xf81f; + return t; + } + int m_alpha255; + int m_alpha256; +}; + + +/************************************************************************ + RGB16 (565) format target format + ************************************************************************/ + +static inline quint16 convert_argb32_to_rgb16(quint32 spix) +{ + quint32 b = spix; + quint32 g = spix; + b >>= 8; + g >>= 5; + b &= 0x0000f800; + g &= 0x000007e0; + spix >>= 3; + b |= g; + spix &= 0x0000001f; + b |= spix; + return b; +} + +struct Blend_RGB16_on_RGB16_NoAlpha { + inline void write(quint16 *dst, quint16 src) { *dst = src; } + + inline void flush(void *) {} +}; + +struct Blend_RGB16_on_RGB16_ConstAlpha { + inline Blend_RGB16_on_RGB16_ConstAlpha(quint32 alpha) { + m_alpha = (alpha * 255) >> 8; + m_ialpha = 255 - m_alpha; + } + + inline void write(quint16 *dst, quint16 src) { + *dst = BYTE_MUL_RGB16(src, m_alpha) + BYTE_MUL_RGB16(*dst, m_ialpha); + } + + inline void flush(void *) {} + + quint32 m_alpha; + quint32 m_ialpha; +}; + +struct Blend_ARGB24_on_RGB16_SourceAlpha { + inline void write(quint16 *dst, const qargb8565 &src) { + const uint alpha = src.alpha(); + if (alpha) { + quint16 s = src.rawValue16(); + if (alpha < 255) + s += BYTE_MUL_RGB16(*dst, 255 - alpha); + *dst = s; + } + } + + inline void flush(void *) {} +}; + +struct Blend_ARGB24_on_RGB16_SourceAndConstAlpha { + inline Blend_ARGB24_on_RGB16_SourceAndConstAlpha(quint32 alpha) { + m_alpha = (alpha * 255) >> 8; + } + + inline void write(quint16 *dst, qargb8565 src) { + src = src.byte_mul(src.alpha(m_alpha)); + const uint alpha = src.alpha(); + if (alpha) { + quint16 s = src.rawValue16(); + if (alpha < 255) + s += BYTE_MUL_RGB16(*dst, 255 - alpha); + *dst = s; + } + } + + inline void flush(void *) {} + + quint32 m_alpha; +}; + +struct Blend_ARGB32_on_RGB16_SourceAlpha { + inline void write(quint16 *dst, quint32 src) { + const quint8 alpha = qAlpha(src); + if(alpha) { + quint16 s = convert_argb32_to_rgb16(src); + if(alpha < 255) + s += BYTE_MUL_RGB16(*dst, 255 - alpha); + *dst = s; + } + } + + inline void flush(void *) {} +}; + +struct Blend_ARGB32_on_RGB16_SourceAndConstAlpha { + inline Blend_ARGB32_on_RGB16_SourceAndConstAlpha(quint32 alpha) { + m_alpha = (alpha * 255) >> 8; + } + + inline void write(quint16 *dst, quint32 src) { + src = BYTE_MUL(src, m_alpha); + const quint8 alpha = qAlpha(src); + if(alpha) { + quint16 s = convert_argb32_to_rgb16(src); + if(alpha < 255) + s += BYTE_MUL_RGB16(*dst, 255 - alpha); + *dst = s; + } + } + + inline void flush(void *) {} + + quint32 m_alpha; +}; + +void qt_scale_image_rgb16_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_scale_rgb16_on_rgb16: dst=(%p, %d), src=(%p, %d), target=(%d, %d), [%d x %d], src=(%d, %d) [%d x %d] alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, + targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height(), + sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), + const_alpha); +#endif + if (const_alpha == 256) { + Blend_RGB16_on_RGB16_NoAlpha noAlpha; + qt_scale_image_16bit<quint16>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, noAlpha); + } else { + Blend_RGB16_on_RGB16_ConstAlpha constAlpha(const_alpha); + qt_scale_image_16bit<quint16>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, constAlpha); + } +} + +void qt_scale_image_argb24_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_scale_argb24_on_rgb16: dst=(%p, %d), src=(%p, %d), target=(%d, %d), [%d x %d], src=(%d, %d) [%d x %d] alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, + targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height(), + sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), + const_alpha); +#endif + if (const_alpha == 256) { + Blend_ARGB24_on_RGB16_SourceAlpha noAlpha; + qt_scale_image_16bit<qargb8565>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, noAlpha); + } else { + Blend_ARGB24_on_RGB16_SourceAndConstAlpha constAlpha(const_alpha); + qt_scale_image_16bit<qargb8565>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, constAlpha); + } +} + + +void qt_scale_image_argb32_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_scale_argb32_on_rgb16: dst=(%p, %d), src=(%p, %d), target=(%d, %d), [%d x %d], src=(%d, %d) [%d x %d] alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, + targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height(), + sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), + const_alpha); +#endif + if (const_alpha == 256) { + Blend_ARGB32_on_RGB16_SourceAlpha noAlpha; + qt_scale_image_16bit<quint32>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, noAlpha); + } else { + Blend_ARGB32_on_RGB16_SourceAndConstAlpha constAlpha(const_alpha); + qt_scale_image_16bit<quint32>(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, constAlpha); + } +} + +void qt_blend_rgb16_on_rgb16(uchar *dst, int dbpl, + const uchar *src, int sbpl, + int w, int h, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_blend_rgb16_on_rgb16: dst=(%p, %d), src=(%p, %d), dim=(%d, %d) alpha=%d\n", + dst, dbpl, src, sbpl, w, h, const_alpha); +#endif + + if (const_alpha == 256) { + if (w <= 64) { + while (h--) { + QT_MEMCPY_USHORT(dst, src, w); + dst += dbpl; + src += sbpl; + } + } else { + int length = w << 1; + while (h--) { + memcpy(dst, src, length); + dst += dbpl; + src += sbpl; + } + } + } else if (const_alpha != 0) { + SourceAndConstAlpha alpha(const_alpha); // expects the 0-256 range + quint16 *d = (quint16 *) dst; + const quint16 *s = (const quint16 *) src; + quint8 a = (255 * const_alpha) >> 8; + quint8 ia = 255 - a; + while (h--) { + for (int x=0; x<w; ++x) { + d[x] = BYTE_MUL_RGB16(s[x], a) + BYTE_MUL_RGB16(d[x], ia); + } + d = (quint16 *)(((uchar *) d) + dbpl); + s = (const quint16 *)(((const uchar *) s) + sbpl); + } + } +} + + +template <typename T> void qt_blend_argb24_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, const T &alphaFunc) +{ + int srcOffset = w*3; + int dstJPL = dbpl / 2; + quint16 *dst = (quint16 *) destPixels; + int dstExtraStride = dstJPL - w; + + for (int y=0; y<h; ++y) { + const uchar *src = srcPixels + y * sbpl; + const uchar *srcEnd = src + srcOffset; + while (src < srcEnd) { +#if defined(QT_ARCH_ARMV5) || defined(QT_ARCH_POWERPC) || defined(QT_ARCH_SH) || defined(QT_ARCH_AVR32) || (defined(QT_ARCH_WINDOWSCE) && !defined(_X86_)) || (defined(QT_ARCH_SPARC) && defined(Q_CC_GNU)) || (defined(QT_ARCH_INTEGRITY) && !defined(_X86_)) + // non-16-bit aligned memory access is not possible on PowerPC, + // ARM <v6 (QT_ARCH_ARMV5) & SH & AVR32 & SPARC w/GCC + quint16 spix = (quint16(src[2])<<8) + src[1]; +#else + quint16 spix = *(quint16 *) (src + 1); +#endif + uchar alpha = alphaFunc.alpha(*src); + + if (alpha == 255) { + *dst = spix; + } else if (alpha != 0) { + quint16 dpix = *dst; + quint32 sia = 255 - alpha; + + quint16 dr = (dpix & 0x0000f800); + quint16 dg = (dpix & 0x000007e0); + quint16 db = (dpix & 0x0000001f); + + quint32 siar = dr * sia; + quint32 siag = dg * sia; + quint32 siab = db * sia; + + quint32 rr = ((siar + (siar>>8) + (0x80 << 8)) >> 8) & 0xf800; + quint32 rg = ((siag + (siag>>8) + (0x80 << 3)) >> 8) & 0x07e0; + quint32 rb = ((siab + (siab>>8) + (0x80 >> 3)) >> 8) & 0x001f; + + *dst = alphaFunc.bytemul(spix) + rr + rg + rb; + } + + ++dst; + src += 3; + } + dst += dstExtraStride; + } + +} + +static void qt_blend_argb24_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_blend_argb24_on_rgb16: dst=(%p, %d), src=(%p, %d), dim=(%d, %d) alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); +#endif + + if (const_alpha != 256) { + SourceAndConstAlpha alphaFunc(const_alpha); + qt_blend_argb24_on_rgb16(destPixels, dbpl, srcPixels, sbpl, w, h, alphaFunc); + } else { + SourceOnlyAlpha alphaFunc; + qt_blend_argb24_on_rgb16(destPixels, dbpl, srcPixels, sbpl, w, h, alphaFunc); + } +} + + + + +void qt_blend_argb32_on_rgb16_const_alpha(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + quint16 *dst = (quint16 *) destPixels; + const quint32 *src = (const quint32 *) srcPixels; + + const_alpha = (const_alpha * 255) >> 8; + for (int y=0; y<h; ++y) { + for (int i = 0; i < w; ++i) { + uint s = src[i]; + s = BYTE_MUL(s, const_alpha); + int alpha = qAlpha(s); + s = convert_argb32_to_rgb16(s); + s += BYTE_MUL_RGB16(dst[i], 255 - alpha); + dst[i] = s; + } + dst = (quint16 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } +} + +static void qt_blend_argb32_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + if (const_alpha != 256) { + qt_blend_argb32_on_rgb16_const_alpha(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + quint16 *dst = (quint16 *) destPixels; + quint32 *src = (quint32 *) srcPixels; + + for (int y=0; y<h; ++y) { + for (int x=0; x<w; ++x) { + + quint32 spix = src[x]; + quint32 alpha = spix >> 24; + + if (alpha == 255) { + dst[x] = convert_argb32_to_rgb16(spix); + } else if (alpha != 0) { + quint32 dpix = dst[x]; + + quint32 sia = 255 - alpha; + + quint32 sr = (spix >> 8) & 0xf800; + quint32 sg = (spix >> 5) & 0x07e0; + quint32 sb = (spix >> 3) & 0x001f; + + quint32 dr = (dpix & 0x0000f800); + quint32 dg = (dpix & 0x000007e0); + quint32 db = (dpix & 0x0000001f); + + quint32 siar = dr * sia; + quint32 siag = dg * sia; + quint32 siab = db * sia; + + quint32 rr = sr + ((siar + (siar>>8) + (0x80 << 8)) >> 8); + quint32 rg = sg + ((siag + (siag>>8) + (0x80 << 3)) >> 8); + quint32 rb = sb + ((siab + (siab>>8) + (0x80 >> 3)) >> 8); + + dst[x] = (rr & 0xf800) + | (rg & 0x07e0) + | (rb); + } + } + dst = (quint16 *) (((uchar *) dst) + dbpl); + src = (quint32 *) (((uchar *) src) + sbpl); + } +} + + +static void qt_blend_rgb32_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_blend_rgb32_on_rgb16: dst=(%p, %d), src=(%p, %d), dim=(%d, %d) alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); +#endif + + if (const_alpha != 256) { + qt_blend_argb32_on_rgb16(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + const quint32 *src = (const quint32 *) srcPixels; + int srcExtraStride = (sbpl >> 2) - w; + + int dstJPL = dbpl / 2; + + quint16 *dst = (quint16 *) destPixels; + quint16 *dstEnd = dst + dstJPL * h; + + int dstExtraStride = dstJPL - w; + + while (dst < dstEnd) { + const quint32 *srcEnd = src + w; + while (src < srcEnd) { + *dst = convert_argb32_to_rgb16(*src); + ++dst; + ++src; + } + dst += dstExtraStride; + src += srcExtraStride; + } +} + + + +/************************************************************************ + RGB32 (-888) format target format + ************************************************************************/ + +static void qt_blend_argb32_on_argb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + fprintf(stdout, "qt_blend_argb32_on_argb32: dst=(%p, %d), src=(%p, %d), dim=(%d, %d) alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + fflush(stdout); +#endif + + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + if (const_alpha == 256) { + for (int y=0; y<h; ++y) { + for (int x=0; x<w; ++x) { + uint s = src[x]; + if (s >= 0xff000000) + dst[x] = s; + else if (s != 0) + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else if (const_alpha != 0) { + const_alpha = (const_alpha * 255) >> 8; + for (int y=0; y<h; ++y) { + for (int x=0; x<w; ++x) { + uint s = BYTE_MUL(src[x], const_alpha); + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + + +void qt_blend_rgb32_on_rgb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + fprintf(stdout, "qt_blend_rgb32_on_rgb32: dst=(%p, %d), src=(%p, %d), dim=(%d, %d) alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + fflush(stdout); +#endif + + if (const_alpha != 256) { + qt_blend_argb32_on_argb32(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + if (w <= 64) { + for (int y=0; y<h; ++y) { + qt_memconvert(dst, src, w); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else { + int len = w * 4; + for (int y=0; y<h; ++y) { + memcpy(dst, src, len); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + + + +struct Blend_RGB32_on_RGB32_NoAlpha { + inline void write(quint32 *dst, quint32 src) { *dst = src; } + + inline void flush(void *) {} +}; + +struct Blend_RGB32_on_RGB32_ConstAlpha { + inline Blend_RGB32_on_RGB32_ConstAlpha(quint32 alpha) { + m_alpha = (alpha * 255) >> 8; + m_ialpha = 255 - m_alpha; + } + + inline void write(quint32 *dst, quint32 src) { + *dst = BYTE_MUL(src, m_alpha) + BYTE_MUL(*dst, m_ialpha); + } + + inline void flush(void *) {} + + quint32 m_alpha; + quint32 m_ialpha; +}; + +struct Blend_ARGB32_on_ARGB32_SourceAlpha { + inline void write(quint32 *dst, quint32 src) { + *dst = src + BYTE_MUL(*dst, qAlpha(~src)); + } + + inline void flush(void *) {} +}; + +struct Blend_ARGB32_on_ARGB32_SourceAndConstAlpha { + inline Blend_ARGB32_on_ARGB32_SourceAndConstAlpha(quint32 alpha) { + m_alpha = (alpha * 255) >> 8; + m_ialpha = 255 - m_alpha; + } + + inline void write(quint32 *dst, quint32 src) { + src = BYTE_MUL(src, m_alpha); + *dst = src + BYTE_MUL(*dst, qAlpha(~src)); + } + + inline void flush(void *) {} + + quint32 m_alpha; + quint32 m_ialpha; +}; + +void qt_scale_image_rgb32_on_rgb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_scale_rgb32_on_rgb32: dst=(%p, %d), src=(%p, %d), target=(%d, %d), [%d x %d], src=(%d, %d) [%d x %d] alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, + targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height(), + sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), + const_alpha); +#endif + if (const_alpha == 256) { + Blend_RGB32_on_RGB32_NoAlpha noAlpha; + qt_scale_image_32bit(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, noAlpha); + } else { + Blend_RGB32_on_RGB32_ConstAlpha constAlpha(const_alpha); + qt_scale_image_32bit(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, constAlpha); + } +} + +void qt_scale_image_argb32_on_argb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ +#ifdef QT_DEBUG_DRAW + printf("qt_scale_argb32_on_argb32: dst=(%p, %d), src=(%p, %d), target=(%d, %d), [%d x %d], src=(%d, %d) [%d x %d] alpha=%d\n", + destPixels, dbpl, srcPixels, sbpl, + targetRect.x(), targetRect.y(), targetRect.width(), targetRect.height(), + sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height(), + const_alpha); +#endif + if (const_alpha == 256) { + Blend_ARGB32_on_ARGB32_SourceAlpha sourceAlpha; + qt_scale_image_32bit(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, sourceAlpha); + } else { + Blend_ARGB32_on_ARGB32_SourceAndConstAlpha constAlpha(const_alpha); + qt_scale_image_32bit(destPixels, dbpl, srcPixels, sbpl, + targetRect, sourceRect, clip, constAlpha); + } +} + +void qt_transform_image_rgb16_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 256) { + Blend_RGB16_on_RGB16_NoAlpha noAlpha; + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint16 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, noAlpha); + } else { + Blend_RGB16_on_RGB16_ConstAlpha constAlpha(const_alpha); + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint16 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, constAlpha); + } +} + +void qt_transform_image_argb24_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 256) { + Blend_ARGB24_on_RGB16_SourceAlpha noAlpha; + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const qargb8565 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, noAlpha); + } else { + Blend_ARGB24_on_RGB16_SourceAndConstAlpha constAlpha(const_alpha); + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const qargb8565 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, constAlpha); + } +} + + +void qt_transform_image_argb32_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 256) { + Blend_ARGB32_on_RGB16_SourceAlpha noAlpha; + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, noAlpha); + } else { + Blend_ARGB32_on_RGB16_SourceAndConstAlpha constAlpha(const_alpha); + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, constAlpha); + } +} + + +void qt_transform_image_rgb32_on_rgb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 256) { + Blend_RGB32_on_RGB32_NoAlpha noAlpha; + qt_transform_image(reinterpret_cast<quint32 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, noAlpha); + } else { + Blend_RGB32_on_RGB32_ConstAlpha constAlpha(const_alpha); + qt_transform_image(reinterpret_cast<quint32 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, constAlpha); + } +} + +void qt_transform_image_argb32_on_argb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 256) { + Blend_ARGB32_on_ARGB32_SourceAlpha sourceAlpha; + qt_transform_image(reinterpret_cast<quint32 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, sourceAlpha); + } else { + Blend_ARGB32_on_ARGB32_SourceAndConstAlpha constAlpha(const_alpha); + qt_transform_image(reinterpret_cast<quint32 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, + targetRect, sourceRect, clip, targetRectTransform, constAlpha); + } +} + +SrcOverScaleFunc qScaleFunctions[QImage::NImageFormats][QImage::NImageFormats] = { + { // Format_Invalid + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Mono + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_MonoLSB + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Indexed8 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_scale_image_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_scale_image_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_scale_image_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_scale_image_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB16 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + qt_scale_image_argb32_on_rgb16, // Format_ARGB32_Premultiplied, + qt_scale_image_rgb16_on_rgb16, // Format_RGB16, + qt_scale_image_argb24_on_rgb16, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8565_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB666 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB6666_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB555 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8555_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB888 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB444 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB4444_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + } +}; + + +SrcOverBlendFunc qBlendFunctions[QImage::NImageFormats][QImage::NImageFormats] = { + { // Format_Invalid + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Mono + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_MonoLSB + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Indexed8 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_blend_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_blend_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_blend_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_blend_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB16 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_blend_rgb32_on_rgb16, // Format_RGB32, + 0, // Format_ARGB32, + qt_blend_argb32_on_rgb16, // Format_ARGB32_Premultiplied, + qt_blend_rgb16_on_rgb16, // Format_RGB16, + qt_blend_argb24_on_rgb16, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8565_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB666 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB6666_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB555 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8555_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB888 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB444 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB4444_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + } +}; + +SrcOverTransformFunc qTransformFunctions[QImage::NImageFormats][QImage::NImageFormats] = { + { // Format_Invalid + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Mono + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_MonoLSB + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_Indexed8 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_transform_image_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_transform_image_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB32_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + qt_transform_image_rgb32_on_rgb32, // Format_RGB32, + 0, // Format_ARGB32, + qt_transform_image_argb32_on_argb32, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB16 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + qt_transform_image_argb32_on_rgb16, // Format_ARGB32_Premultiplied, + qt_transform_image_rgb16_on_rgb16, // Format_RGB16, + qt_transform_image_argb24_on_rgb16, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8565_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB666 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB6666_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB555 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB8555_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB888 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_RGB444 + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + }, + { // Format_ARGB4444_Premultiplied + 0, // Format_Invalid, + 0, // Format_Mono, + 0, // Format_MonoLSB, + 0, // Format_Indexed8, + 0, // Format_RGB32, + 0, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied, + 0, // Format_RGB16, + 0, // Format_ARGB8565_Premultiplied, + 0, // Format_RGB666, + 0, // Format_ARGB6666_Premultiplied, + 0, // Format_RGB555, + 0, // Format_ARGB8555_Premultiplied, + 0, // Format_RGB888, + 0, // Format_RGB444, + 0 // Format_ARGB4444_Premultiplied, + } +}; + +QT_END_NAMESPACE diff --git a/src/gui/painting/qblendfunctions_p.h b/src/gui/painting/qblendfunctions_p.h new file mode 100644 index 0000000000..81f8b70caa --- /dev/null +++ b/src/gui/painting/qblendfunctions_p.h @@ -0,0 +1,497 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBLENDFUNCTIONS_P_H +#define QBLENDFUNCTIONS_P_H + +#include <qmath.h> +#include "qdrawhelper_p.h" + +QT_BEGIN_NAMESPACE + +// +// 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. +// + +template <typename SRC, typename T> +void qt_scale_image_16bit(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &srcRect, + const QRect &clip, + T blender) +{ + qreal sx = targetRect.width() / (qreal) srcRect.width(); + qreal sy = targetRect.height() / (qreal) srcRect.height(); + + int ix = 0x00010000 / sx; + int iy = 0x00010000 / sy; + +// qDebug() << "scale:" << endl +// << " - target" << targetRect << endl +// << " - source" << srcRect << endl +// << " - clip" << clip << endl +// << " - sx=" << sx << " sy=" << sy << " ix=" << ix << " iy=" << iy; + + int cx1 = clip.x(); + int cx2 = clip.x() + clip.width(); + int cy1 = clip.top(); + int cy2 = clip.y() + clip.height(); + + int tx1 = qRound(targetRect.left()); + int tx2 = qRound(targetRect.right()); + int ty1 = qRound(targetRect.top()); + int ty2 = qRound(targetRect.bottom()); + + if (tx2 < tx1) + qSwap(tx2, tx1); + + if (ty2 < ty1) + qSwap(ty2, ty1); + + if (tx1 < cx1) + tx1 = cx1; + + if (tx2 >= cx2) + tx2 = cx2; + + if (tx1 >= tx2) + return; + + if (ty1 < cy1) + ty1 = cy1; + + if (ty2 >= cy2) + ty2 = cy2; + + if (ty1 >= ty2) + return; + + int h = ty2 - ty1; + int w = tx2 - tx1; + + + quint32 basex; + quint32 srcy; + + if (sx < 0) { + int dstx = qFloor((tx1 + qreal(0.5) - targetRect.right()) * ix) + 1; + basex = quint32(srcRect.right() * 65536) + dstx; + } else { + int dstx = qCeil((tx1 + qreal(0.5) - targetRect.left()) * ix) - 1; + basex = quint32(srcRect.left() * 65536) + dstx; + } + if (sy < 0) { + int dsty = qFloor((ty1 + qreal(0.5) - targetRect.bottom()) * iy) + 1; + srcy = quint32(srcRect.bottom() * 65536) + dsty; + } else { + int dsty = qCeil((ty1 + qreal(0.5) - targetRect.top()) * iy) - 1; + srcy = quint32(srcRect.top() * 65536) + dsty; + } + + quint16 *dst = ((quint16 *) (destPixels + ty1 * dbpl)) + tx1; + + while (h--) { + const SRC *src = (const SRC *) (srcPixels + (srcy >> 16) * sbpl); + int srcx = basex; + int x = 0; + for (; x<w-7; x+=8) { + blender.write(&dst[x], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+1], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+2], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+3], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+4], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+5], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+6], src[srcx >> 16]); srcx += ix; + blender.write(&dst[x+7], src[srcx >> 16]); srcx += ix; + } + for (; x<w; ++x) { + blender.write(&dst[x], src[srcx >> 16]); + srcx += ix; + } + blender.flush(&dst[x]); + dst = (quint16 *)(((uchar *) dst) + dbpl); + srcy += iy; + } +} + +template <typename T> void qt_scale_image_32bit(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &srcRect, + const QRect &clip, + T blender) +{ + qreal sx = targetRect.width() / (qreal) srcRect.width(); + qreal sy = targetRect.height() / (qreal) srcRect.height(); + + int ix = 0x00010000 / sx; + int iy = 0x00010000 / sy; + +// qDebug() << "scale:" << endl +// << " - target" << targetRect << endl +// << " - source" << srcRect << endl +// << " - clip" << clip << endl +// << " - sx=" << sx << " sy=" << sy << " ix=" << ix << " iy=" << iy; + + int cx1 = clip.x(); + int cx2 = clip.x() + clip.width(); + int cy1 = clip.top(); + int cy2 = clip.y() + clip.height(); + + int tx1 = qRound(targetRect.left()); + int tx2 = qRound(targetRect.right()); + int ty1 = qRound(targetRect.top()); + int ty2 = qRound(targetRect.bottom()); + + if (tx2 < tx1) + qSwap(tx2, tx1); + + if (ty2 < ty1) + qSwap(ty2, ty1); + + if (tx1 < cx1) + tx1 = cx1; + + if (tx2 >= cx2) + tx2 = cx2; + + if (tx1 >= tx2) + return; + + if (ty1 < cy1) + ty1 = cy1; + + if (ty2 >= cy2) + ty2 = cy2; + + if (ty1 >= ty2) + return; + + int h = ty2 - ty1; + int w = tx2 - tx1; + + quint32 basex; + quint32 srcy; + + if (sx < 0) { + int dstx = qFloor((tx1 + qreal(0.5) - targetRect.right()) * ix) + 1; + basex = quint32(srcRect.right() * 65536) + dstx; + } else { + int dstx = qCeil((tx1 + qreal(0.5) - targetRect.left()) * ix) - 1; + basex = quint32(srcRect.left() * 65536) + dstx; + } + if (sy < 0) { + int dsty = qFloor((ty1 + qreal(0.5) - targetRect.bottom()) * iy) + 1; + srcy = quint32(srcRect.bottom() * 65536) + dsty; + } else { + int dsty = qCeil((ty1 + qreal(0.5) - targetRect.top()) * iy) - 1; + srcy = quint32(srcRect.top() * 65536) + dsty; + } + + quint32 *dst = ((quint32 *) (destPixels + ty1 * dbpl)) + tx1; + + while (h--) { + const uint *src = (const quint32 *) (srcPixels + (srcy >> 16) * sbpl); + int srcx = basex; + int x = 0; + for (; x<w; ++x) { + blender.write(&dst[x], src[srcx >> 16]); + srcx += ix; + } + blender.flush(&dst[x]); + dst = (quint32 *)(((uchar *) dst) + dbpl); + srcy += iy; + } +} + +struct QTransformImageVertex +{ + qreal x, y, u, v; // destination coordinates (x, y) and source coordinates (u, v) +}; + +template <class SrcT, class DestT, class Blender> +void qt_transform_image_rasterize(DestT *destPixels, int dbpl, + const SrcT *srcPixels, int sbpl, + const QTransformImageVertex &topLeft, const QTransformImageVertex &bottomLeft, + const QTransformImageVertex &topRight, const QTransformImageVertex &bottomRight, + const QRect &sourceRect, + const QRect &clip, + qreal topY, qreal bottomY, + int dudx, int dvdx, int dudy, int dvdy, int u0, int v0, + Blender blender) +{ + int fromY = qMax(qRound(topY), clip.top()); + int toY = qMin(qRound(bottomY), clip.top() + clip.height()); + if (fromY >= toY) + return; + + qreal leftSlope = (bottomLeft.x - topLeft.x) / (bottomLeft.y - topLeft.y); + qreal rightSlope = (bottomRight.x - topRight.x) / (bottomRight.y - topRight.y); + int dx_l = int(leftSlope * 0x10000); + int dx_r = int(rightSlope * 0x10000); + int x_l = int((topLeft.x + (qreal(0.5) + fromY - topLeft.y) * leftSlope + qreal(0.5)) * 0x10000); + int x_r = int((topRight.x + (qreal(0.5) + fromY - topRight.y) * rightSlope + qreal(0.5)) * 0x10000); + + int fromX, toX, x1, x2, u, v, i, ii; + DestT *line; + for (int y = fromY; y < toY; ++y) { + line = reinterpret_cast<DestT *>(reinterpret_cast<uchar *>(destPixels) + y * dbpl); + + fromX = qMax(x_l >> 16, clip.left()); + toX = qMin(x_r >> 16, clip.left() + clip.width()); + if (fromX < toX) { + // Because of rounding, we can get source coordinates outside the source image. + // Clamp these coordinates to the source rect to avoid segmentation fault and + // garbage on the screen. + + // Find the first pixel on the current scan line where the source coordinates are within the source rect. + x1 = fromX; + u = x1 * dudx + y * dudy + u0; + v = x1 * dvdx + y * dvdy + v0; + for (; x1 < toX; ++x1) { + int uu = u >> 16; + int vv = v >> 16; + if (uu >= sourceRect.left() && uu < sourceRect.left() + sourceRect.width() + && vv >= sourceRect.top() && vv < sourceRect.top() + sourceRect.height()) { + break; + } + u += dudx; + v += dvdx; + } + + // Find the last pixel on the current scan line where the source coordinates are within the source rect. + x2 = toX; + u = (x2 - 1) * dudx + y * dudy + u0; + v = (x2 - 1) * dvdx + y * dvdy + v0; + for (; x2 > x1; --x2) { + int uu = u >> 16; + int vv = v >> 16; + if (uu >= sourceRect.left() && uu < sourceRect.left() + sourceRect.width() + && vv >= sourceRect.top() && vv < sourceRect.top() + sourceRect.height()) { + break; + } + u -= dudx; + v -= dvdx; + } + + // Set up values at the beginning of the scan line. + u = fromX * dudx + y * dudy + u0; + v = fromX * dvdx + y * dvdy + v0; + line += fromX; + + // Beginning of the scan line, with per-pixel checks. + i = x1 - fromX; + while (i) { + int uu = qBound(sourceRect.left(), u >> 16, sourceRect.left() + sourceRect.width() - 1); + int vv = qBound(sourceRect.top(), v >> 16, sourceRect.top() + sourceRect.height() - 1); + blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + vv * sbpl)[uu]); + u += dudx; + v += dvdx; + ++line; + --i; + } + + // Middle of the scan line, without checks. + // Manual loop unrolling. + i = x2 - x1; + ii = i >> 3; + while (ii) { + blender.write(&line[0], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[1], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[2], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[3], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[4], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[5], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[6], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + blender.write(&line[7], reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; + + line += 8; + + --ii; + } + switch (i & 7) { + case 7: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 6: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 5: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 4: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 3: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 2: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + case 1: blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + (v >> 16) * sbpl)[u >> 16]); u += dudx; v += dvdx; ++line; + } + + // End of the scan line, with per-pixel checks. + i = toX - x2; + while (i) { + int uu = qBound(sourceRect.left(), u >> 16, sourceRect.left() + sourceRect.width() - 1); + int vv = qBound(sourceRect.top(), v >> 16, sourceRect.top() + sourceRect.height() - 1); + blender.write(line, reinterpret_cast<const SrcT *>(reinterpret_cast<const uchar *>(srcPixels) + vv * sbpl)[uu]); + u += dudx; + v += dvdx; + ++line; + --i; + } + + blender.flush(line); + } + x_l += dx_l; + x_r += dx_r; + } +} + +template <class SrcT, class DestT, class Blender> +void qt_transform_image(DestT *destPixels, int dbpl, + const SrcT *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + Blender blender) +{ + enum Corner + { + TopLeft, + TopRight, + BottomRight, + BottomLeft + }; + + // map source rectangle to destination. + QTransformImageVertex v[4]; + v[TopLeft].u = v[BottomLeft].u = sourceRect.left(); + v[TopLeft].v = v[TopRight].v = sourceRect.top(); + v[TopRight].u = v[BottomRight].u = sourceRect.right(); + v[BottomLeft].v = v[BottomRight].v = sourceRect.bottom(); + targetRectTransform.map(targetRect.left(), targetRect.top(), &v[TopLeft].x, &v[TopLeft].y); + targetRectTransform.map(targetRect.right(), targetRect.top(), &v[TopRight].x, &v[TopRight].y); + targetRectTransform.map(targetRect.left(), targetRect.bottom(), &v[BottomLeft].x, &v[BottomLeft].y); + targetRectTransform.map(targetRect.right(), targetRect.bottom(), &v[BottomRight].x, &v[BottomRight].y); + + // find topmost vertex. + int topmost = 0; + for (int i = 1; i < 4; ++i) { + if (v[i].y < v[topmost].y) + topmost = i; + } + // rearrange array such that topmost vertex is at index 0. + switch (topmost) { + case 1: + { + QTransformImageVertex t = v[0]; + for (int i = 0; i < 3; ++i) + v[i] = v[i+1]; + v[3] = t; + } + break; + case 2: + qSwap(v[0], v[2]); + qSwap(v[1], v[3]); + break; + case 3: + { + QTransformImageVertex t = v[3]; + for (int i = 3; i > 0; --i) + v[i] = v[i-1]; + v[0] = t; + } + break; + } + + // if necessary, swap vertex 1 and 3 such that 1 is to the left of 3. + qreal dx1 = v[1].x - v[0].x; + qreal dy1 = v[1].y - v[0].y; + qreal dx2 = v[3].x - v[0].x; + qreal dy2 = v[3].y - v[0].y; + if (dx1 * dy2 - dx2 * dy1 > 0) + qSwap(v[1], v[3]); + + QTransformImageVertex u = {v[1].x - v[0].x, v[1].y - v[0].y, v[1].u - v[0].u, v[1].v - v[0].v}; + QTransformImageVertex w = {v[2].x - v[0].x, v[2].y - v[0].y, v[2].u - v[0].u, v[2].v - v[0].v}; + + qreal det = u.x * w.y - u.y * w.x; + if (det == 0) + return; + + qreal invDet = 1.0 / det; + qreal m11, m12, m21, m22, mdx, mdy; + + m11 = (u.u * w.y - u.y * w.u) * invDet; + m12 = (u.x * w.u - u.u * w.x) * invDet; + m21 = (u.v * w.y - u.y * w.v) * invDet; + m22 = (u.x * w.v - u.v * w.x) * invDet; + mdx = v[0].u - m11 * v[0].x - m12 * v[0].y; + mdy = v[0].v - m21 * v[0].x - m22 * v[0].y; + + int dudx = int(m11 * 0x10000); + int dvdx = int(m21 * 0x10000); + int dudy = int(m12 * 0x10000); + int dvdy = int(m22 * 0x10000); + int u0 = qCeil((qreal(0.5) * m11 + qreal(0.5) * m12 + mdx) * 0x10000) - 1; + int v0 = qCeil((qreal(0.5) * m21 + qreal(0.5) * m22 + mdy) * 0x10000) - 1; + + int x1 = qFloor(sourceRect.left()); + int y1 = qFloor(sourceRect.top()); + int x2 = qCeil(sourceRect.right()); + int y2 = qCeil(sourceRect.bottom()); + QRect sourceRectI(x1, y1, x2 - x1, y2 - y1); + + // rasterize trapezoids. + if (v[1].y < v[3].y) { + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[0], v[1], v[0], v[3], sourceRectI, clip, v[0].y, v[1].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[1], v[2], v[0], v[3], sourceRectI, clip, v[1].y, v[3].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[1], v[2], v[3], v[2], sourceRectI, clip, v[3].y, v[2].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + } else { + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[0], v[1], v[0], v[3], sourceRectI, clip, v[0].y, v[3].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[0], v[1], v[3], v[2], sourceRectI, clip, v[3].y, v[1].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + qt_transform_image_rasterize(destPixels, dbpl, srcPixels, sbpl, v[1], v[2], v[3], v[2], sourceRectI, clip, v[1].y, v[2].y, dudx, dvdx, dudy, dvdy, u0, v0, blender); + } +} + +QT_END_NAMESPACE + +#endif // QBLENDFUNCTIONS_P_H diff --git a/src/gui/painting/qblittable.cpp b/src/gui/painting/qblittable.cpp new file mode 100644 index 0000000000..58025312c7 --- /dev/null +++ b/src/gui/painting/qblittable.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qblittable_p.h" + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +class QBlittablePrivate +{ +public: + QBlittablePrivate(const QSize &size, QBlittable::Capabilities caps) + : caps(caps), m_size(size), locked(false), cachedImg(0) + {} + QBlittable::Capabilities caps; + QSize m_size; + bool locked; + QImage *cachedImg; +}; + + +QBlittable::QBlittable(const QSize &size, Capabilities caps) + : d_ptr(new QBlittablePrivate(size,caps)) +{ +} + +QBlittable::~QBlittable() +{ + delete d_ptr; +} + + +QBlittable::Capabilities QBlittable::capabilities() const +{ + Q_D(const QBlittable); + return d->caps; +} + +QSize QBlittable::size() const +{ + Q_D(const QBlittable); + return d->m_size; +} + +QImage *QBlittable::lock() +{ + Q_D(QBlittable); + if (!d->locked) { + d->cachedImg = doLock(); + d->locked = true; + } + + return d->cachedImg; +} + +void QBlittable::unlock() +{ + Q_D(QBlittable); + if (d->locked) { + doUnlock(); + d->locked = false; + } +} + +QT_END_NAMESPACE +#endif //QT_NO_BLITTABLE + diff --git a/src/gui/painting/qblittable_p.h b/src/gui/painting/qblittable_p.h new file mode 100644 index 0000000000..9d0e8225b8 --- /dev/null +++ b/src/gui/painting/qblittable_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBLITTABLE_P_H +#define QBLITTABLE_P_H + +#include <QtCore/qsize.h> +#include <QtGui/private/qpixmap_blitter_p.h> + + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +class QImage; +class QBlittablePrivate; + +class Q_GUI_EXPORT QBlittable +{ + Q_DECLARE_PRIVATE(QBlittable); +public: + enum Capability { + + SolidRectCapability = 0x0001, + SourcePixmapCapability = 0x0002, + SourceOverPixmapCapability = 0x0004, + SourceOverScaledPixmapCapability = 0x0008, + + // Internal ones + OutlineCapability = 0x0001000, + }; + Q_DECLARE_FLAGS (Capabilities, Capability); + + QBlittable(const QSize &size, Capabilities caps); + virtual ~QBlittable(); + + Capabilities capabilities() const; + QSize size() const; + + virtual void fillRect(const QRectF &rect, const QColor &color) = 0; + virtual void drawPixmap(const QRectF &rect, const QPixmap &pixmap, const QRectF &subrect) = 0; + + QImage *lock(); + void unlock(); + +protected: + virtual QImage *doLock() = 0; + virtual void doUnlock() = 0; + QBlittablePrivate *d_ptr; +}; + +QT_END_NAMESPACE +#endif //QT_NO_BLITTABLE +#endif //QBLITTABLE_P_H diff --git a/src/gui/painting/qbrush.cpp b/src/gui/painting/qbrush.cpp new file mode 100644 index 0000000000..bf5764a609 --- /dev/null +++ b/src/gui/painting/qbrush.cpp @@ -0,0 +1,2196 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbrush.h" +#include "qpixmap.h" +#include "qbitmap.h" +#include "qpixmapcache.h" +#include "qdatastream.h" +#include "qvariant.h" +#include "qline.h" +#include "qdebug.h" +#include <QtCore/qcoreapplication.h> +#include "private/qstylehelper_p.h" +#include <QtCore/qnumeric.h> + +QT_BEGIN_NAMESPACE + +const uchar *qt_patternForBrush(int brushStyle, bool invert) +{ + Q_ASSERT(brushStyle > Qt::SolidPattern && brushStyle < Qt::LinearGradientPattern); + if(invert) { + static const uchar dense1_pat[] = { 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff }; + static const uchar dense2_pat[] = { 0x77, 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff }; + static const uchar dense3_pat[] = { 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55, 0xee }; + static const uchar dense4_pat[] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; + static const uchar dense5_pat[] = { 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa, 0x11 }; + static const uchar dense6_pat[] = { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }; + static const uchar dense7_pat[] = { 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00 }; + static const uchar hor_pat[] = { 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00 }; + static const uchar ver_pat[] = { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }; + static const uchar cross_pat[] = { 0x10, 0x10, 0x10, 0xff, 0x10, 0x10, 0x10, 0x10 }; + static const uchar bdiag_pat[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + static const uchar fdiag_pat[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + static const uchar dcross_pat[] = { 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 }; + static const uchar *const pat_tbl[] = { + dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat, + dense6_pat, dense7_pat, + hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat }; + return pat_tbl[brushStyle - Qt::Dense1Pattern]; + } + static const uchar dense1_pat[] = { 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00 }; + static const uchar dense2_pat[] = { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }; + static const uchar dense3_pat[] = { 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa, 0x11 }; + static const uchar dense4_pat[] = { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa }; + static const uchar dense5_pat[] = { 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55, 0xee }; + static const uchar dense6_pat[] = { 0x77, 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff }; + static const uchar dense7_pat[] = { 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff }; + static const uchar hor_pat[] = { 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff }; + static const uchar ver_pat[] = { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }; + static const uchar cross_pat[] = { 0xef, 0xef, 0xef, 0x00, 0xef, 0xef, 0xef, 0xef }; + static const uchar bdiag_pat[] = { 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe }; + static const uchar fdiag_pat[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f }; + static const uchar dcross_pat[] = { 0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e }; + static const uchar *const pat_tbl[] = { + dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat, + dense6_pat, dense7_pat, + hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat }; + return pat_tbl[brushStyle - Qt::Dense1Pattern]; +} + +QPixmap qt_pixmapForBrush(int brushStyle, bool invert) +{ + + QPixmap pm; + QString key = QLatin1Literal("$qt-brush$") + % HexString<uint>(brushStyle) + % QLatin1Char(invert ? '1' : '0'); + if (!QPixmapCache::find(key, pm)) { + pm = QBitmap::fromData(QSize(8, 8), qt_patternForBrush(brushStyle, invert), + QImage::Format_MonoLSB); + QPixmapCache::insert(key, pm); + } + + return pm; +} + +class QBrushPatternImageCache +{ +public: + QBrushPatternImageCache() + : m_initialized(false) + { + init(); + } + + void init() + { + for (int style = Qt::Dense1Pattern; style <= Qt::DiagCrossPattern; ++style) { + int i = style - Qt::Dense1Pattern; + m_images[i][0] = QImage(qt_patternForBrush(style, 0), 8, 8, 1, QImage::Format_MonoLSB); + m_images[i][1] = QImage(qt_patternForBrush(style, 1), 8, 8, 1, QImage::Format_MonoLSB); + } + m_initialized = true; + } + + QImage getImage(int brushStyle, bool invert) const + { + Q_ASSERT(brushStyle >= Qt::Dense1Pattern && brushStyle <= Qt::DiagCrossPattern); + if (!m_initialized) + const_cast<QBrushPatternImageCache*>(this)->init(); + return m_images[brushStyle - Qt::Dense1Pattern][invert]; + } + + void cleanup() { + for (int style = Qt::Dense1Pattern; style <= Qt::DiagCrossPattern; ++style) { + int i = style - Qt::Dense1Pattern; + m_images[i][0] = QImage(); + m_images[i][1] = QImage(); + } + m_initialized = false; + } + +private: + QImage m_images[Qt::DiagCrossPattern - Qt::Dense1Pattern + 1][2]; + bool m_initialized; +}; + +static void qt_cleanup_brush_pattern_image_cache(); +Q_GLOBAL_STATIC_WITH_INITIALIZER(QBrushPatternImageCache, qt_brushPatternImageCache, + { + qAddPostRoutine(qt_cleanup_brush_pattern_image_cache); + }) + +static void qt_cleanup_brush_pattern_image_cache() +{ + qt_brushPatternImageCache()->cleanup(); +} + +Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert) +{ + return qt_brushPatternImageCache()->getImage(brushStyle, invert); +} + +struct QTexturedBrushData : public QBrushData +{ + QTexturedBrushData() { + m_has_pixmap_texture = false; + m_pixmap = 0; + } + ~QTexturedBrushData() { + delete m_pixmap; + } + + void setPixmap(const QPixmap &pm) { + delete m_pixmap; + + if (pm.isNull()) { + m_pixmap = 0; + m_has_pixmap_texture = false; + } else { + m_pixmap = new QPixmap(pm); + m_has_pixmap_texture = true; + } + + m_image = QImage(); + } + + void setImage(const QImage &image) { + m_image = image; + delete m_pixmap; + m_pixmap = 0; + m_has_pixmap_texture = false; + } + + QPixmap &pixmap() { + if (!m_pixmap) { + m_pixmap = new QPixmap(QPixmap::fromImage(m_image)); + } + return *m_pixmap; + } + + QImage &image() { + if (m_image.isNull() && m_pixmap) + m_image = m_pixmap->toImage(); + return m_image; + } + + QPixmap *m_pixmap; + QImage m_image; + bool m_has_pixmap_texture; +}; + +// returns true if the brush has a pixmap (or bitmap) set as the +// brush texture, false otherwise +bool Q_GUI_EXPORT qHasPixmapTexture(const QBrush& brush) +{ + if (brush.style() != Qt::TexturePattern) + return false; + QTexturedBrushData *tx_data = static_cast<QTexturedBrushData *>(brush.d.data()); + return tx_data->m_has_pixmap_texture; +} + +struct QGradientBrushData : public QBrushData +{ + QGradient gradient; +}; + +struct QBrushDataPointerDeleter +{ + static inline void deleteData(QBrushData *d) + { + switch (d->style) { + case Qt::TexturePattern: + delete static_cast<QTexturedBrushData*>(d); + break; + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + delete static_cast<QGradientBrushData*>(d); + break; + default: + delete d; + } + } + + static inline void cleanup(QBrushData *d) + { + if (d && !d->ref.deref()) { + deleteData(d); + } + } +}; + +/*! + \class QBrush + \ingroup painting + \ingroup shared + + \brief The QBrush class defines the fill pattern of shapes drawn + by QPainter. + + A brush has a style, a color, a gradient and a texture. + + The brush style() defines the fill pattern using the + Qt::BrushStyle enum. The default brush style is Qt::NoBrush + (depending on how you construct a brush). This style tells the + painter to not fill shapes. The standard style for filling is + Qt::SolidPattern. The style can be set when the brush is created + using the appropriate constructor, and in addition the setStyle() + function provides means for altering the style once the brush is + constructed. + + \image brush-styles.png Brush Styles + + The brush color() defines the color of the fill pattern. The color + can either be one of Qt's predefined colors, Qt::GlobalColor, or + any other custom QColor. The currently set color can be retrieved + and altered using the color() and setColor() functions, + respectively. + + The gradient() defines the gradient fill used when the current + style is either Qt::LinearGradientPattern, + Qt::RadialGradientPattern or Qt::ConicalGradientPattern. Gradient + brushes are created by giving a QGradient as a constructor + argument when creating the QBrush. Qt provides three different + gradients: QLinearGradient, QConicalGradient, and QRadialGradient + - all of which inherit QGradient. + + \snippet doc/src/snippets/brush/gradientcreationsnippet.cpp 0 + + The texture() defines the pixmap used when the current style is + Qt::TexturePattern. You can create a brush with a texture by + providing the pixmap when the brush is created or by using + setTexture(). + + Note that applying setTexture() makes style() == + Qt::TexturePattern, regardless of previous style + settings. Also, calling setColor() will not make a difference if + the style is a gradient. The same is the case if the style is + Qt::TexturePattern style unless the current texture is a QBitmap. + + The isOpaque() function returns true if the brush is fully opaque + otherwise false. A brush is considered opaque if: + + \list + \o The alpha component of the color() is 255. + \o Its texture() does not have an alpha channel and is not a QBitmap. + \o The colors in the gradient() all have an alpha component that is 255. + \endlist + + \table 100% + \row + \o \inlineimage brush-outline.png Outlines + \o + + To specify the style and color of lines and outlines, use the + QPainter's \l {QPen}{pen} combined with Qt::PenStyle and + Qt::GlobalColor: + + \snippet doc/src/snippets/code/src_gui_painting_qbrush.cpp 0 + + Note that, by default, QPainter renders the outline (using the + currently set pen) when drawing shapes. Use \l {Qt::NoPen}{\c + painter.setPen(Qt::NoPen)} to disable this behavior. + + \endtable + + For more information about painting in general, see the \l{Paint + System}. + + \sa Qt::BrushStyle, QPainter, QColor +*/ + +#ifndef QT_NO_THREAD +// Special deleter that only deletes if the ref-count goes to zero +template <> +class QGlobalStaticDeleter<QBrushData> +{ +public: + QGlobalStatic<QBrushData> &globalStatic; + QGlobalStaticDeleter(QGlobalStatic<QBrushData> &_globalStatic) + : globalStatic(_globalStatic) + { } + + inline ~QGlobalStaticDeleter() + { + if (!globalStatic.pointer->ref.deref()) + delete globalStatic.pointer; + globalStatic.pointer = 0; + globalStatic.destroyed = true; + } +}; +#endif + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QBrushData, nullBrushInstance, + { + x->ref = 1; + x->style = Qt::BrushStyle(0); + x->color = Qt::black; + }) + +static bool qbrush_check_type(Qt::BrushStyle style) { + switch (style) { + case Qt::TexturePattern: + qWarning("QBrush: Incorrect use of TexturePattern"); + break; + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + qWarning("QBrush: Wrong use of a gradient pattern"); + break; + default: + return true; + } + return false; +} + +/*! + \internal + Initializes the brush. +*/ + +void QBrush::init(const QColor &color, Qt::BrushStyle style) +{ + switch(style) { + case Qt::NoBrush: + d.reset(nullBrushInstance()); + d->ref.ref(); + if (d->color != color) setColor(color); + return; + case Qt::TexturePattern: + d.reset(new QTexturedBrushData); + break; + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + d.reset(new QGradientBrushData); + break; + default: + d.reset(new QBrushData); + break; + } + d->ref = 1; + d->style = style; + d->color = color; +} + +/*! + Constructs a default black brush with the style Qt::NoBrush + (i.e. this brush will not fill shapes). +*/ + +QBrush::QBrush() + : d(nullBrushInstance()) +{ + Q_ASSERT(d); + d->ref.ref(); +} + +/*! + Constructs a brush with a black color and a texture set to the + given \a pixmap. The style is set to Qt::TexturePattern. + + \sa setTexture() +*/ + +QBrush::QBrush(const QPixmap &pixmap) +{ + init(Qt::black, Qt::TexturePattern); + setTexture(pixmap); +} + + +/*! + Constructs a brush with a black color and a texture set to the + given \a image. The style is set to Qt::TexturePattern. + + \sa setTextureImage() +*/ + +QBrush::QBrush(const QImage &image) +{ + init(Qt::black, Qt::TexturePattern); + setTextureImage(image); +} + +/*! + Constructs a black brush with the given \a style. + + \sa setStyle() +*/ + +QBrush::QBrush(Qt::BrushStyle style) +{ + if (qbrush_check_type(style)) + init(Qt::black, style); + else { + d.reset(nullBrushInstance()); + d->ref.ref(); + } +} + +/*! + Constructs a brush with the given \a color and \a style. + + \sa setColor(), setStyle() +*/ + +QBrush::QBrush(const QColor &color, Qt::BrushStyle style) +{ + if (qbrush_check_type(style)) + init(color, style); + else { + d.reset(nullBrushInstance()); + d->ref.ref(); + } +} + +/*! + \fn QBrush::QBrush(Qt::GlobalColor color, Qt::BrushStyle style) + + Constructs a brush with the given \a color and \a style. + + \sa setColor(), setStyle() +*/ +QBrush::QBrush(Qt::GlobalColor color, Qt::BrushStyle style) +{ + if (qbrush_check_type(style)) + init(color, style); + else { + d.reset(nullBrushInstance()); + d->ref.ref(); + } +} + +/*! + Constructs a brush with the given \a color and the custom pattern + stored in \a pixmap. + + The style is set to Qt::TexturePattern. The color will only have + an effect for QBitmaps. + + \sa setColor(), setPixmap() +*/ + +QBrush::QBrush(const QColor &color, const QPixmap &pixmap) +{ + init(color, Qt::TexturePattern); + setTexture(pixmap); +} + +/*! + + Constructs a brush with the given \a color and the custom pattern + stored in \a pixmap. + + The style is set to Qt::TexturePattern. The color will only have + an effect for QBitmaps. + + \sa setColor(), setPixmap() +*/ +QBrush::QBrush(Qt::GlobalColor color, const QPixmap &pixmap) +{ + init(color, Qt::TexturePattern); + setTexture(pixmap); +} + +/*! + Constructs a copy of \a other. +*/ + +QBrush::QBrush(const QBrush &other) + : d(other.d.data()) +{ + d->ref.ref(); +} + +/*! + Constructs a brush based on the given \a gradient. + + The brush style is set to the corresponding gradient style (either + Qt::LinearGradientPattern, Qt::RadialGradientPattern or + Qt::ConicalGradientPattern). +*/ +QBrush::QBrush(const QGradient &gradient) +{ + Q_ASSERT_X(gradient.type() != QGradient::NoGradient, "QBrush::QBrush", + "QGradient should not be used directly, use the linear, radial\n" + "or conical gradients instead"); + + const Qt::BrushStyle enum_table[] = { + Qt::LinearGradientPattern, + Qt::RadialGradientPattern, + Qt::ConicalGradientPattern + }; + + init(QColor(), enum_table[gradient.type()]); + QGradientBrushData *grad = static_cast<QGradientBrushData *>(d.data()); + grad->gradient = gradient; +} + +/*! + Destroys the brush. +*/ + +QBrush::~QBrush() +{ +} + +void QBrush::cleanUp(QBrushData *x) +{ + QBrushDataPointerDeleter::deleteData(x); +} + + +void QBrush::detach(Qt::BrushStyle newStyle) +{ + if (newStyle == d->style && d->ref == 1) + return; + + QScopedPointer<QBrushData> x; + switch(newStyle) { + case Qt::TexturePattern: { + QTexturedBrushData *tbd = new QTexturedBrushData; + if (d->style == Qt::TexturePattern) { + QTexturedBrushData *data = static_cast<QTexturedBrushData *>(d.data()); + if (data->m_has_pixmap_texture) + tbd->setPixmap(data->pixmap()); + else + tbd->setImage(data->image()); + } + x.reset(tbd); + break; + } + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + x.reset(new QGradientBrushData); + static_cast<QGradientBrushData *>(x.data())->gradient = + static_cast<QGradientBrushData *>(d.data())->gradient; + break; + default: + x.reset(new QBrushData); + break; + } + x->ref = 1; + x->style = newStyle; + x->color = d->color; + x->transform = d->transform; + d.reset(x.take()); +} + + +/*! + \fn QBrush &QBrush::operator=(const QBrush &brush) + + Assigns the given \a brush to \e this brush and returns a + reference to \e this brush. +*/ + +QBrush &QBrush::operator=(const QBrush &b) +{ + if (d == b.d) + return *this; + + b.d->ref.ref(); + d.reset(b.d.data()); + return *this; +} + + +/*! + \fn void QBrush::swap(QBrush &other) + \since 4.8 + + Swaps brush \a other with this brush. This operation is very + fast and never fails. +*/ + +/*! + Returns the brush as a QVariant +*/ +QBrush::operator QVariant() const +{ + return QVariant(QVariant::Brush, this); +} + +/*! + \fn Qt::BrushStyle QBrush::style() const + + Returns the brush style. + + \sa setStyle() +*/ + +/*! + Sets the brush style to \a style. + + \sa style() +*/ + +void QBrush::setStyle(Qt::BrushStyle style) +{ + if (d->style == style) + return; + + if (qbrush_check_type(style)) { + detach(style); + d->style = style; + } +} + + +/*! + \fn const QColor &QBrush::color() const + + Returns the brush color. + + \sa setColor() +*/ + +/*! + \fn void QBrush::setColor(const QColor &color) + + Sets the brush color to the given \a color. + + Note that calling setColor() will not make a difference if the + style is a gradient. The same is the case if the style is + Qt::TexturePattern style unless the current texture is a QBitmap. + + \sa color() +*/ + +void QBrush::setColor(const QColor &c) +{ + detach(d->style); + d->color = c; +} + +/*! + \fn void QBrush::setColor(Qt::GlobalColor color) + \overload + + Sets the brush color to the given \a color. +*/ + + +#ifdef QT3_SUPPORT + +/*! + \fn void QBrush::setPixmap(const QPixmap &pixmap) + + \compat + + Sets a custom pattern for this brush. + + Use setTexture() instead. +*/ + +/*! + \fn QPixmap *QBrush::pixmap() const + + Returns a pointer to the custom brush pattern. + + Use texture() instead. +*/ +QPixmap *QBrush::pixmap() const +{ + if (d->style != Qt::TexturePattern) + return 0; + QTexturedBrushData *data = static_cast<QTexturedBrushData*>(d.data()); + QPixmap &pixmap = data->pixmap(); + return pixmap.isNull() ? 0 : &pixmap; +} +#endif + +/*! + \fn QPixmap QBrush::texture() const + + Returns the custom brush pattern, or a null pixmap if no custom brush pattern + has been set. + + \sa setTexture() +*/ +QPixmap QBrush::texture() const +{ + return d->style == Qt::TexturePattern + ? (static_cast<QTexturedBrushData *>(d.data()))->pixmap() + : QPixmap(); +} + +/*! + Sets the brush pixmap to \a pixmap. The style is set to + Qt::TexturePattern. + + The current brush color will only have an effect for monochrome + pixmaps, i.e. for QPixmap::depth() == 1 (\l {QBitmap}{QBitmaps}). + + \sa texture() +*/ + +void QBrush::setTexture(const QPixmap &pixmap) +{ + if (!pixmap.isNull()) { + detach(Qt::TexturePattern); + QTexturedBrushData *data = static_cast<QTexturedBrushData *>(d.data()); + data->setPixmap(pixmap); + } else { + detach(Qt::NoBrush); + } +} + + +/*! + \since 4.2 + + Returns the custom brush pattern, or a null image if no custom + brush pattern has been set. + + If the texture was set as a QPixmap it will be converted to a + QImage. + + \sa setTextureImage() +*/ + +QImage QBrush::textureImage() const +{ + return d->style == Qt::TexturePattern + ? (static_cast<QTexturedBrushData *>(d.data()))->image() + : QImage(); +} + + +/*! + \since 4.2 + + Sets the brush image to \a image. The style is set to + Qt::TexturePattern. + + Note the current brush color will \e not have any affect on + monochrome images, as opposed to calling setTexture() with a + QBitmap. If you want to change the color of monochrome image + brushes, either convert the image to QBitmap with \c + QBitmap::fromImage() and set the resulting QBitmap as a texture, + or change the entries in the color table for the image. + + \sa textureImage(), setTexture() +*/ + +void QBrush::setTextureImage(const QImage &image) +{ + if (!image.isNull()) { + detach(Qt::TexturePattern); + QTexturedBrushData *data = static_cast<QTexturedBrushData *>(d.data()); + data->setImage(image); + } else { + detach(Qt::NoBrush); + } +} + + +/*! + Returns the gradient describing this brush. +*/ +const QGradient *QBrush::gradient() const +{ + if (d->style == Qt::LinearGradientPattern + || d->style == Qt::RadialGradientPattern + || d->style == Qt::ConicalGradientPattern) { + return &static_cast<const QGradientBrushData *>(d.data())->gradient; + } + return 0; +} + + +/*! + Returns true if the brush is fully opaque otherwise false. A brush + is considered opaque if: + + \list + \i The alpha component of the color() is 255. + \i Its texture() does not have an alpha channel and is not a QBitmap. + \i The colors in the gradient() all have an alpha component that is 255. + \endlist +*/ + +bool QBrush::isOpaque() const +{ + bool opaqueColor = d->color.alpha() == 255; + + // Test awfully simple case first + if (d->style == Qt::SolidPattern) + return opaqueColor; + + if (d->style == Qt::LinearGradientPattern + || d->style == Qt::RadialGradientPattern + || d->style == Qt::ConicalGradientPattern) { + QGradientStops stops = gradient()->stops(); + for (int i=0; i<stops.size(); ++i) + if (stops.at(i).second.alpha() != 255) + return false; + return true; + } else if (d->style == Qt::TexturePattern) { + return qHasPixmapTexture(*this) + ? !texture().hasAlphaChannel() && !texture().isQBitmap() + : !textureImage().hasAlphaChannel(); + } + + return false; +} + + +/*! + \since 4.2 + + Sets \a matrix as an explicit transformation matrix on the + current brush. The brush transformation matrix is merged with + QPainter transformation matrix to produce the final result. + + \sa matrix() +*/ +void QBrush::setMatrix(const QMatrix &matrix) +{ + setTransform(QTransform(matrix)); +} + +/*! + \since 4.3 + + Sets \a matrix as an explicit transformation matrix on the + current brush. The brush transformation matrix is merged with + QPainter transformation matrix to produce the final result. + + \sa transform() +*/ +void QBrush::setTransform(const QTransform &matrix) +{ + detach(d->style); + d->transform = matrix; +} + + +/*! + \fn void QBrush::matrix() const + \since 4.2 + + Returns the current transformation matrix for the brush. + + \sa setMatrix() +*/ + +/*! + \fn bool QBrush::operator!=(const QBrush &brush) const + + Returns true if the brush is different from the given \a brush; + otherwise returns false. + + Two brushes are different if they have different styles, colors or + transforms or different pixmaps or gradients depending on the style. + + \sa operator==() +*/ + +/*! + \fn bool QBrush::operator==(const QBrush &brush) const + + Returns true if the brush is equal to the given \a brush; + otherwise returns false. + + Two brushes are equal if they have equal styles, colors and + transforms and equal pixmaps or gradients depending on the style. + + \sa operator!=() +*/ + +bool QBrush::operator==(const QBrush &b) const +{ + if (b.d == d) + return true; + if (b.d->style != d->style || b.d->color != d->color || b.d->transform != d->transform) + return false; + switch (d->style) { + case Qt::TexturePattern: + { + const QPixmap &us = (static_cast<QTexturedBrushData *>(d.data()))->pixmap(); + const QPixmap &them = (static_cast<QTexturedBrushData *>(b.d.data()))->pixmap(); + return ((us.isNull() && them.isNull()) || us.cacheKey() == them.cacheKey()); + } + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + { + const QGradientBrushData *d1 = static_cast<QGradientBrushData *>(d.data()); + const QGradientBrushData *d2 = static_cast<QGradientBrushData *>(b.d.data()); + return d1->gradient == d2->gradient; + } + default: + return true; + } +} + +/*! + \fn QBrush::operator const QColor&() const + + Returns the brush's color. + + Use color() instead. +*/ + +#ifndef QT_NO_DEBUG_STREAM +/*! + \internal +*/ +QDebug operator<<(QDebug dbg, const QBrush &b) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + static const char *BRUSH_STYLES[] = { + "NoBrush", + "SolidPattern", + "Dense1Pattern", + "Dense2Pattern", + "Dense3Pattern", + "Dense4Pattern", + "Dense5Pattern", + "Dense6Pattern", + "Dense7Pattern", + "HorPattern", + "VerPattern", + "CrossPattern", + "BDiagPattern", + "FDiagPattern", + "DiagCrossPattern", + "LinearGradientPattern", + "RadialGradientPattern", + "ConicalGradientPattern", + 0, 0, 0, 0, 0, 0, + "TexturePattern" // 24 + }; + + dbg.nospace() << "QBrush(" << b.color() << ',' << BRUSH_STYLES[b.style()] << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QBrush to QDebug"); + return dbg; + Q_UNUSED(b); +#endif +} +#endif + +/***************************************************************************** + QBrush stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QBrush &brush) + \relates QBrush + + Writes the given \a brush to the given \a stream and returns a + reference to the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QBrush &b) +{ + quint8 style = (quint8) b.style(); + bool gradient_style = false; + + if (style == Qt::LinearGradientPattern || style == Qt::RadialGradientPattern + || style == Qt::ConicalGradientPattern) + gradient_style = true; + + if (s.version() < QDataStream::Qt_4_0 && gradient_style) + style = Qt::NoBrush; + + s << style << b.color(); + if (b.style() == Qt::TexturePattern) { + s << b.texture(); + } else if (s.version() >= QDataStream::Qt_4_0 && gradient_style) { + const QGradient *gradient = b.gradient(); + int type_as_int = int(gradient->type()); + s << type_as_int; + if (s.version() >= QDataStream::Qt_4_3) { + s << int(gradient->spread()); + s << int(gradient->coordinateMode()); + } + + if (s.version() >= QDataStream::Qt_4_5) + s << int(gradient->interpolationMode()); + + if (sizeof(qreal) == sizeof(double)) { + s << gradient->stops(); + } else { + // ensure that we write doubles here instead of streaming the stops + // directly; otherwise, platforms that redefine qreal might generate + // data that cannot be read on other platforms. + QVector<QGradientStop> stops = gradient->stops(); + s << quint32(stops.size()); + for (int i = 0; i < stops.size(); ++i) { + const QGradientStop &stop = stops.at(i); + s << QPair<double, QColor>(double(stop.first), stop.second); + } + } + + if (gradient->type() == QGradient::LinearGradient) { + s << static_cast<const QLinearGradient *>(gradient)->start(); + s << static_cast<const QLinearGradient *>(gradient)->finalStop(); + } else if (gradient->type() == QGradient::RadialGradient) { + s << static_cast<const QRadialGradient *>(gradient)->center(); + s << static_cast<const QRadialGradient *>(gradient)->focalPoint(); + s << (double) static_cast<const QRadialGradient *>(gradient)->radius(); + } else { // type == Conical + s << static_cast<const QConicalGradient *>(gradient)->center(); + s << (double) static_cast<const QConicalGradient *>(gradient)->angle(); + } + } + if (s.version() >= QDataStream::Qt_4_3) + s << b.transform(); + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QBrush &brush) + \relates QBrush + + Reads the given \a brush from the given \a stream and returns a + reference to the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QBrush &b) +{ + quint8 style; + QColor color; + s >> style; + s >> color; + if (style == Qt::TexturePattern) { + QPixmap pm; + s >> pm; + b = QBrush(color, pm); + } else if (style == Qt::LinearGradientPattern + || style == Qt::RadialGradientPattern + || style == Qt::ConicalGradientPattern) { + + int type_as_int; + QGradient::Type type; + QGradientStops stops; + QGradient::CoordinateMode cmode = QGradient::LogicalMode; + QGradient::Spread spread = QGradient::PadSpread; + QGradient::InterpolationMode imode = QGradient::ColorInterpolation; + + s >> type_as_int; + type = QGradient::Type(type_as_int); + if (s.version() >= QDataStream::Qt_4_3) { + s >> type_as_int; + spread = QGradient::Spread(type_as_int); + s >> type_as_int; + cmode = QGradient::CoordinateMode(type_as_int); + } + + if (s.version() >= QDataStream::Qt_4_5) { + s >> type_as_int; + imode = QGradient::InterpolationMode(type_as_int); + } + + if (sizeof(qreal) == sizeof(double)) { + s >> stops; + } else { + quint32 numStops; + double n; + QColor c; + + s >> numStops; + for (quint32 i = 0; i < numStops; ++i) { + s >> n >> c; + stops << QPair<qreal, QColor>(n, c); + } + } + + if (type == QGradient::LinearGradient) { + QPointF p1, p2; + s >> p1; + s >> p2; + QLinearGradient lg(p1, p2); + lg.setStops(stops); + lg.setSpread(spread); + lg.setCoordinateMode(cmode); + lg.setInterpolationMode(imode); + b = QBrush(lg); + } else if (type == QGradient::RadialGradient) { + QPointF center, focal; + double radius; + s >> center; + s >> focal; + s >> radius; + QRadialGradient rg(center, radius, focal); + rg.setStops(stops); + rg.setSpread(spread); + rg.setCoordinateMode(cmode); + rg.setInterpolationMode(imode); + b = QBrush(rg); + } else { // type == QGradient::ConicalGradient + QPointF center; + double angle; + s >> center; + s >> angle; + QConicalGradient cg(center, angle); + cg.setStops(stops); + cg.setSpread(spread); + cg.setCoordinateMode(cmode); + cg.setInterpolationMode(imode); + b = QBrush(cg); + } + } else { + b = QBrush(color, (Qt::BrushStyle)style); + } + if (s.version() >= QDataStream::Qt_4_3) { + QTransform transform; + s >> transform; + b.setTransform(transform); + } + return s; +} +#endif // QT_NO_DATASTREAM + +/******************************************************************************* + * QGradient implementations + */ + + +/*! + \class QGradient + \ingroup painting + \ingroup shared + + \brief The QGradient class is used in combination with QBrush to + specify gradient fills. + + Qt currently supports three types of gradient fills: + + \list + \o \e Linear gradients interpolate colors between start and end points. + \o \e Radial gradients interpolate colors between a focal point and end + points on a circle surrounding it. + \o \e Conical gradients interpolate colors around a center point. + \endlist + + A gradient's type can be retrieved using the type() function. + Each of the types is represented by a subclass of QGradient: + + \table + \header + \o QLinearGradient + \o QRadialGradient + \o QConicalGradient + \row + \o \inlineimage qgradient-linear.png + \o \inlineimage qgradient-radial.png + \o \inlineimage qgradient-conical.png + \endtable + + The colors in a gradient are defined using stop points of the + QGradientStop type; i.e., a position and a color. Use the setColorAt() + function to define a single stop point. Alternatively, use the + setStops() function to define several stop points in one go. Note that + the latter function \e replaces the current set of stop points. + + It is the gradient's complete set of stop points (accessible + through the stops() function) that describes how the gradient area + should be filled. If no stop points have been specified, a gradient + of black at 0 to white at 1 is used. + + A diagonal linear gradient from black at (100, 100) to white at + (200, 200) could be specified like this: + + \snippet doc/src/snippets/brush/brush.cpp 0 + + A gradient can have an arbitrary number of stop points. The + following would create a radial gradient starting with + red in the center, blue and then green on the edges: + + \snippet doc/src/snippets/brush/brush.cpp 1 + + It is possible to repeat or reflect the gradient outside its area + by specifiying the \l {QGradient::Spread}{spread method} using the + setSpread() function. The default is to pad the outside area with + the color at the closest stop point. The currently set \l + {QGradient::Spread}{spread method} can be retrieved using the + spread() function. The QGradient::Spread enum defines three + different methods: + + \table + \row + \o \inlineimage qradialgradient-pad.png + \o \inlineimage qradialgradient-repeat.png + \o \inlineimage qradialgradient-reflect.png + \row + \o \l {QGradient::PadSpread}{PadSpread} + \o \l {QGradient::RepeatSpread}{RepeatSpread} + \o \l {QGradient::ReflectSpread}{ReflectSpread} + \endtable + + Note that the setSpread() function only has effect for linear and + radial gradients. The reason is that the conical gradient is + closed by definition, i.e. the \e conical gradient fills the + entire circle from 0 - 360 degrees, while the boundary of a radial + or a linear gradient can be specified through its radius or final + stop points, respectively. + + The gradient coordinates can be specified in logical coordinates, + relative to device coordinates, or relative to object bounding box coordinates. + The \l {QGradient::CoordinateMode}{coordinate mode} can be set using the + setCoordinateMode() function. The default is LogicalMode, where the + gradient coordinates are specified in the same way as the object + coordinates. To retrieve the currently set \l {QGradient::CoordinateMode} + {coordinate mode} use coordinateMode(). + + + \sa {demos/gradients}{The Gradients Demo}, QBrush +*/ + +/*! + \internal +*/ +QGradient::QGradient() + : m_type(NoGradient), dummy(0) +{ +} + + +/*! + \enum QGradient::Type + + Specifies the type of gradient. + + \value LinearGradient Interpolates colors between start and end points + (QLinearGradient). + + \value RadialGradient Interpolate colors between a focal point and end + points on a circle surrounding it (QRadialGradient). + + \value ConicalGradient Interpolate colors around a center point (QConicalGradient). + \value NoGradient No gradient is used. + + \sa type() +*/ + +/*! + \enum QGradient::Spread + + Specifies how the area outside the gradient area should be + filled. + + \value PadSpread The area is filled with the closest stop + color. This is the default. + + \value RepeatSpread The gradient is repeated outside the gradient + area. + + \value ReflectSpread The gradient is reflected outside the + gradient area. + + \sa spread(), setSpread() +*/ + +/*! + \fn void QGradient::setSpread(Spread method) + + Specifies the spread \a method that should be used for this + gradient. + + Note that this function only has effect for linear and radial + gradients. + + \sa spread() +*/ + +/*! + \fn QGradient::Spread QGradient::spread() const + + Returns the spread method use by this gradient. The default is + PadSpread. + + \sa setSpread() +*/ + +/*! + \fn QGradient::Type QGradient::type() const + + Returns the type of gradient. +*/ + +/*! + \fn void QGradient::setColorAt(qreal position, const QColor &color) + + Creates a stop point at the given \a position with the given \a + color. The given \a position must be in the range 0 to 1. + + \sa setStops(), stops() +*/ + +void QGradient::setColorAt(qreal pos, const QColor &color) +{ + if ((pos > 1 || pos < 0) && !qIsNaN(pos)) { + qWarning("QGradient::setColorAt: Color position must be specified in the range 0 to 1"); + return; + } + + int index = 0; + if (!qIsNaN(pos)) + while (index < m_stops.size() && m_stops.at(index).first < pos) ++index; + + if (index < m_stops.size() && m_stops.at(index).first == pos) + m_stops[index].second = color; + else + m_stops.insert(index, QGradientStop(pos, color)); +} + +/*! + \fn void QGradient::setStops(const QGradientStops &stopPoints) + + Replaces the current set of stop points with the given \a + stopPoints. The positions of the points must be in the range 0 to + 1, and must be sorted with the lowest point first. + + \sa setColorAt(), stops() +*/ +void QGradient::setStops(const QGradientStops &stops) +{ + m_stops.clear(); + for (int i=0; i<stops.size(); ++i) + setColorAt(stops.at(i).first, stops.at(i).second); +} + + +/*! + Returns the stop points for this gradient. + + If no stop points have been specified, a gradient of black at 0 to white + at 1 is used. + + \sa setStops(), setColorAt() +*/ +QGradientStops QGradient::stops() const +{ + if (m_stops.isEmpty()) { + QGradientStops tmp; + tmp << QGradientStop(0, Qt::black) << QGradientStop(1, Qt::white); + return tmp; + } + return m_stops; +} + +#define Q_DUMMY_ACCESSOR union {void *p; uint i;}; p = dummy; + +/*! + \enum QGradient::CoordinateMode + \since 4.4 + + This enum specifies how gradient coordinates map to the paint + device on which the gradient is used. + + \value LogicalMode This is the default mode. The gradient coordinates + are specified logical space just like the object coordinates. + \value StretchToDeviceMode In this mode the gradient coordinates + are relative to the bounding rectangle of the paint device, + with (0,0) in the top left corner, and (1,1) in the bottom right + corner of the paint device. + \value ObjectBoundingMode In this mode the gradient coordinates are + relative to the bounding rectangle of the object being drawn, with + (0,0) in the top left corner, and (1,1) in the bottom right corner + of the object's bounding rectangle. +*/ + +/*! + \since 4.4 + + Returns the coordinate mode of this gradient. The default mode is + LogicalMode. +*/ +QGradient::CoordinateMode QGradient::coordinateMode() const +{ + Q_DUMMY_ACCESSOR + return CoordinateMode(i & 0x03); +} + +/*! + \since 4.4 + + Sets the coordinate mode of this gradient to \a mode. The default + mode is LogicalMode. +*/ +void QGradient::setCoordinateMode(CoordinateMode mode) +{ + Q_DUMMY_ACCESSOR + i &= ~0x03; + i |= uint(mode); + dummy = p; +} + +/*! + \enum QGradient::InterpolationMode + \since 4.5 + \internal + + \value ComponentInterpolation The color components and the alpha component are + independently linearly interpolated. + \value ColorInterpolation The colors are linearly interpolated in + premultiplied color space. +*/ + +/*! + \since 4.5 + \internal + + Returns the interpolation mode of this gradient. The default mode is + ColorInterpolation. +*/ +QGradient::InterpolationMode QGradient::interpolationMode() const +{ + Q_DUMMY_ACCESSOR + return InterpolationMode((i >> 2) & 0x01); +} + +/*! + \since 4.5 + \internal + + Sets the interpolation mode of this gradient to \a mode. The default + mode is ColorInterpolation. +*/ +void QGradient::setInterpolationMode(InterpolationMode mode) +{ + Q_DUMMY_ACCESSOR + i &= ~(1 << 2); + i |= (uint(mode) << 2); + dummy = p; +} + +#undef Q_DUMMY_ACCESSOR + +/*! + \fn bool QGradient::operator!=(const QGradient &gradient) const + \since 4.2 + + Returns true if the gradient is the same as the other \a gradient + specified; otherwise returns false. + + \sa operator==() +*/ + +/*! + Returns true if the gradient is the same as the other \a gradient + specified; otherwise returns false. + + \sa operator!=() +*/ +bool QGradient::operator==(const QGradient &gradient) const +{ + if (gradient.m_type != m_type + || gradient.m_spread != m_spread + || gradient.dummy != dummy) return false; + + if (m_type == LinearGradient) { + if (m_data.linear.x1 != gradient.m_data.linear.x1 + || m_data.linear.y1 != gradient.m_data.linear.y1 + || m_data.linear.x2 != gradient.m_data.linear.x2 + || m_data.linear.y2 != gradient.m_data.linear.y2) + return false; + } else if (m_type == RadialGradient) { + if (m_data.radial.cx != gradient.m_data.radial.cx + || m_data.radial.cy != gradient.m_data.radial.cy + || m_data.radial.fx != gradient.m_data.radial.fx + || m_data.radial.fy != gradient.m_data.radial.fy + || m_data.radial.radius != gradient.m_data.radial.radius) + return false; + } else { // m_type == ConicalGradient + if (m_data.conical.cx != gradient.m_data.conical.cx + || m_data.conical.cy != gradient.m_data.conical.cy + || m_data.conical.angle != gradient.m_data.conical.angle) + return false; + } + + return stops() == gradient.stops(); +} + +/*! + \internal +*/ +bool QGradient::operator==(const QGradient &gradient) +{ + return const_cast<const QGradient *>(this)->operator==(gradient); +} + +/*! + \class QLinearGradient + \ingroup painting + + \brief The QLinearGradient class is used in combination with QBrush to + specify a linear gradient brush. + + Linear gradients interpolate colors between start and end + points. Outside these points the gradient is either padded, + reflected or repeated depending on the currently set \l + {QGradient::Spread}{spread} method: + + \table + \row + \o \inlineimage qlineargradient-pad.png + \o \inlineimage qlineargradient-reflect.png + \o \inlineimage qlineargradient-repeat.png + \row + \o \l {QGradient::PadSpread}{PadSpread} (default) + \o \l {QGradient::ReflectSpread}{ReflectSpread} + \o \l {QGradient::RepeatSpread}{RepeatSpread} + \endtable + + The colors in a gradient is defined using stop points of the + QGradientStop type, i.e. a position and a color. Use the + QGradient::setColorAt() or the QGradient::setStops() function to + define the stop points. It is the gradient's complete set of stop + points that describes how the gradient area should be filled. If + no stop points have been specified, a gradient of black at 0 to + white at 1 is used. + + In addition to the functions inherited from QGradient, the + QLinearGradient class provides the finalStop() function which + returns the final stop point of the gradient, and the start() + function returning the start point of the gradient. + + \sa QRadialGradient, QConicalGradient, {demos/gradients}{The + Gradients Demo} +*/ + + +/*! + Constructs a default linear gradient with interpolation area + between (0, 0) and (1, 1). + + \sa QGradient::setColorAt(), setStart(), setFinalStop() +*/ + +QLinearGradient::QLinearGradient() +{ + m_type = LinearGradient; + m_spread = PadSpread; + m_data.linear.x1 = 0; + m_data.linear.y1 = 0; + m_data.linear.x2 = 1; + m_data.linear.y2 = 1; +} + + +/*! + Constructs a linear gradient with interpolation area between the + given \a start point and \a finalStop. + + \note The expected parameter values are in pixels. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ +QLinearGradient::QLinearGradient(const QPointF &start, const QPointF &finalStop) +{ + m_type = LinearGradient; + m_spread = PadSpread; + m_data.linear.x1 = start.x(); + m_data.linear.y1 = start.y(); + m_data.linear.x2 = finalStop.x(); + m_data.linear.y2 = finalStop.y(); +} + +/*! + \fn QLinearGradient::QLinearGradient(qreal x1, qreal y1, qreal x2, qreal y2) + + Constructs a linear gradient with interpolation area between (\a + x1, \a y1) and (\a x2, \a y2). + + \note The expected parameter values are in pixels. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ +QLinearGradient::QLinearGradient(qreal xStart, qreal yStart, qreal xFinalStop, qreal yFinalStop) +{ + m_type = LinearGradient; + m_spread = PadSpread; + m_data.linear.x1 = xStart; + m_data.linear.y1 = yStart; + m_data.linear.x2 = xFinalStop; + m_data.linear.y2 = yFinalStop; +} + + +/*! + Returns the start point of this linear gradient in logical coordinates. + + \sa QGradient::stops() +*/ + +QPointF QLinearGradient::start() const +{ + Q_ASSERT(m_type == LinearGradient); + return QPointF(m_data.linear.x1, m_data.linear.y1); +} + +/*! + \fn void QLinearGradient::setStart(qreal x, qreal y) + \overload + \since 4.2 + + Sets the start point of this linear gradient in logical + coordinates to \a x, \a y. + + \sa start() +*/ + +/*! + \since 4.2 + + Sets the start point of this linear gradient in logical + coordinates to \a start. + + \sa start() +*/ + +void QLinearGradient::setStart(const QPointF &start) +{ + Q_ASSERT(m_type == LinearGradient); + m_data.linear.x1 = start.x(); + m_data.linear.y1 = start.y(); +} + + +/*! + \fn void QLinearGradient::setFinalStop(qreal x, qreal y) + \overload + \since 4.2 + + Sets the final stop point of this linear gradient in logical + coordinates to \a x, \a y. + + \sa start() +*/ + +/*! + Returns the final stop point of this linear gradient in logical coordinates. + + \sa QGradient::stops() +*/ + +QPointF QLinearGradient::finalStop() const +{ + Q_ASSERT(m_type == LinearGradient); + return QPointF(m_data.linear.x2, m_data.linear.y2); +} + + +/*! + \since 4.2 + + Sets the final stop point of this linear gradient in logical + coordinates to \a stop. + + \sa finalStop() +*/ + +void QLinearGradient::setFinalStop(const QPointF &stop) +{ + Q_ASSERT(m_type == LinearGradient); + m_data.linear.x2 = stop.x(); + m_data.linear.y2 = stop.y(); +} + + +/*! + \class QRadialGradient + \ingroup painting + + \brief The QRadialGradient class is used in combination with QBrush to + specify a radial gradient brush. + + Radial gradients interpolate colors between a focal point and end + points on a circle surrounding it. Outside the end points the + gradient is either padded, reflected or repeated depending on the + currently set \l {QGradient::Spread}{spread} method: + + \table + \row + \o \inlineimage qradialgradient-pad.png + \o \inlineimage qradialgradient-reflect.png + \o \inlineimage qradialgradient-repeat.png + \row + \o \l {QGradient::PadSpread}{PadSpread} (default) + \o \l {QGradient::ReflectSpread}{ReflectSpread} + \o \l {QGradient::RepeatSpread}{RepeatSpread} + \endtable + + The colors in a gradient is defined using stop points of the + QGradientStop type, i.e. a position and a color. Use the + QGradient::setColorAt() or the QGradient::setStops() function to + define the stop points. It is the gradient's complete set of stop + points that describes how the gradient area should be filled. If + no stop points have been specified, a gradient of black at 0 to + white at 1 is used. + + In addition to the functions inherited from QGradient, the + QRadialGradient class provides the center(), focalPoint() and + radius() functions returning the gradient's center, focal point + and radius respectively. + + \sa QLinearGradient, QConicalGradient, {demos/gradients}{The + Gradients Demo} +*/ + +static QPointF qt_radial_gradient_adapt_focal_point(const QPointF ¢er, + qreal radius, + const QPointF &focalPoint) +{ + // We have a one pixel buffer zone to avoid numerical instability on the + // circle border + //### this is hacky because technically we should adjust based on current matrix + const qreal compensated_radius = radius - radius * qreal(0.001); + QLineF line(center, focalPoint); + if (line.length() > (compensated_radius)) + line.setLength(compensated_radius); + return line.p2(); +} + +/*! + Constructs a radial gradient with the given \a center, \a + radius and \a focalPoint. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ + +QRadialGradient::QRadialGradient(const QPointF ¢er, qreal radius, const QPointF &focalPoint) +{ + m_type = RadialGradient; + m_spread = PadSpread; + m_data.radial.cx = center.x(); + m_data.radial.cy = center.y(); + m_data.radial.radius = radius; + + QPointF adapted_focal = qt_radial_gradient_adapt_focal_point(center, radius, focalPoint); + m_data.radial.fx = adapted_focal.x(); + m_data.radial.fy = adapted_focal.y(); +} + +/*! + Constructs a radial gradient with the given \a center, \a + radius and the focal point in the circle center. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ +QRadialGradient::QRadialGradient(const QPointF ¢er, qreal radius) +{ + m_type = RadialGradient; + m_spread = PadSpread; + m_data.radial.cx = center.x(); + m_data.radial.cy = center.y(); + m_data.radial.radius = radius; + m_data.radial.fx = center.x(); + m_data.radial.fy = center.y(); +} + + +/*! + Constructs a radial gradient with the given center (\a cx, \a cy), + \a radius and focal point (\a fx, \a fy). + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ + +QRadialGradient::QRadialGradient(qreal cx, qreal cy, qreal radius, qreal fx, qreal fy) +{ + m_type = RadialGradient; + m_spread = PadSpread; + m_data.radial.cx = cx; + m_data.radial.cy = cy; + m_data.radial.radius = radius; + + QPointF adapted_focal = qt_radial_gradient_adapt_focal_point(QPointF(cx, cy), + radius, + QPointF(fx, fy)); + + m_data.radial.fx = adapted_focal.x(); + m_data.radial.fy = adapted_focal.y(); +} + +/*! + Constructs a radial gradient with the center at (\a cx, \a cy) and the + specified \a radius. The focal point lies at the center of the circle. + + \sa QGradient::setColorAt(), QGradient::setStops() + */ +QRadialGradient::QRadialGradient(qreal cx, qreal cy, qreal radius) +{ + m_type = RadialGradient; + m_spread = PadSpread; + m_data.radial.cx = cx; + m_data.radial.cy = cy; + m_data.radial.radius = radius; + m_data.radial.fx = cx; + m_data.radial.fy = cy; +} + + +/*! + Constructs a radial gradient with the center and focal point at + (0, 0) with a radius of 1. +*/ +QRadialGradient::QRadialGradient() +{ + m_type = RadialGradient; + m_spread = PadSpread; + m_data.radial.cx = 0; + m_data.radial.cy = 0; + m_data.radial.radius = 1; + m_data.radial.fx = 0; + m_data.radial.fy = 0; +} + + +/*! + Returns the center of this radial gradient in logical coordinates. + + \sa QGradient::stops() +*/ + +QPointF QRadialGradient::center() const +{ + Q_ASSERT(m_type == RadialGradient); + return QPointF(m_data.radial.cx, m_data.radial.cy); +} + +/*! + \fn void QRadialGradient::setCenter(qreal x, qreal y) + \overload + \since 4.2 + + Sets the center of this radial gradient in logical coordinates + to (\a x, \a y). + + \sa center() +*/ + +/*! + \since 4.2 + + Sets the center of this radial gradient in logical coordinates + to \a center. + + \sa center() +*/ + +void QRadialGradient::setCenter(const QPointF ¢er) +{ + Q_ASSERT(m_type == RadialGradient); + m_data.radial.cx = center.x(); + m_data.radial.cy = center.y(); +} + + +/*! + Returns the radius of this radial gradient in logical coordinates. + + \sa QGradient::stops() +*/ + +qreal QRadialGradient::radius() const +{ + Q_ASSERT(m_type == RadialGradient); + return m_data.radial.radius; +} + + +/*! + \since 4.2 + + Sets the radius of this radial gradient in logical coordinates + to \a radius +*/ +void QRadialGradient::setRadius(qreal radius) +{ + Q_ASSERT(m_type == RadialGradient); + m_data.radial.radius = radius; +} + + +/*! + Returns the focal point of this radial gradient in logical + coordinates. + + \sa QGradient::stops() +*/ + +QPointF QRadialGradient::focalPoint() const +{ + Q_ASSERT(m_type == RadialGradient); + return QPointF(m_data.radial.fx, m_data.radial.fy); +} + +/*! + \fn void QRadialGradient::setFocalPoint(qreal x, qreal y) + \overload + \since 4.2 + + Sets the focal point of this radial gradient in logical + coordinates to (\a x, \a y). + + \sa focalPoint() +*/ + +/*! + \since 4.2 + + Sets the focal point of this radial gradient in logical + coordinates to \a focalPoint. + + \sa focalPoint() +*/ + +void QRadialGradient::setFocalPoint(const QPointF &focalPoint) +{ + Q_ASSERT(m_type == RadialGradient); + m_data.radial.fx = focalPoint.x(); + m_data.radial.fy = focalPoint.y(); +} + + + +/*! + \class QConicalGradient + \ingroup painting + + \brief The QConicalGradient class is used in combination with QBrush to + specify a conical gradient brush. + + Conical gradients interpolate interpolate colors counter-clockwise + around a center point. + + \image qconicalgradient.png + + The colors in a gradient is defined using stop points of the + QGradientStop type, i.e. a position and a color. Use the + QGradient::setColorAt() or the QGradient::setStops() function to + define the stop points. It is the gradient's complete set of stop + points that describes how the gradient area should be filled. If + no stop points have been specified, a gradient of black at 0 to + white at 1 is used. + + In addition to the functions inherited from QGradient, the + QConicalGradient class provides the angle() and center() functions + returning the start angle and center of the gradient. + + Note that the setSpread() function has no effect for conical + gradients. The reason is that the conical gradient is closed by + definition, i.e. the conical gradient fills the entire circle from + 0 - 360 degrees, while the boundary of a radial or a linear + gradient can be specified through its radius or final stop points, + respectively. + + \sa QLinearGradient, QRadialGradient, {demos/gradients}{The + Gradients Demo} +*/ + + +/*! + Constructs a conical gradient with the given \a center, starting + the interpolation at the given \a angle. The \a angle must be + specified in degrees between 0 and 360. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ + +QConicalGradient::QConicalGradient(const QPointF ¢er, qreal angle) +{ + m_type = ConicalGradient; + m_spread = PadSpread; + m_data.conical.cx = center.x(); + m_data.conical.cy = center.y(); + m_data.conical.angle = angle; +} + + +/*! + Constructs a conical gradient with the given center (\a cx, \a + cy), starting the interpolation at the given \a angle. The angle + must be specified in degrees between 0 and 360. + + \sa QGradient::setColorAt(), QGradient::setStops() +*/ + +QConicalGradient::QConicalGradient(qreal cx, qreal cy, qreal angle) +{ + m_type = ConicalGradient; + m_spread = PadSpread; + m_data.conical.cx = cx; + m_data.conical.cy = cy; + m_data.conical.angle = angle; +} + + +/*! + Constructs a conical with center at (0, 0) starting the + interpolation at angle 0. + + \sa QGradient::setColorAt(), setCenter(), setAngle() +*/ + +QConicalGradient::QConicalGradient() +{ + m_type = ConicalGradient; + m_spread = PadSpread; + m_data.conical.cx = 0; + m_data.conical.cy = 0; + m_data.conical.angle = 0; +} + + +/*! + Returns the center of the conical gradient in logical + coordinates. + + \sa stops() +*/ + +QPointF QConicalGradient::center() const +{ + Q_ASSERT(m_type == ConicalGradient); + return QPointF(m_data.conical.cx, m_data.conical.cy); +} + + +/*! + \fn void QConicalGradient::setCenter(qreal x, qreal y) + + \overload + + Sets the center of this conical gradient in logical coordinates to + (\a x, \a y). + + \sa center() +*/ + +/*! + Sets the center of this conical gradient in logical coordinates to + \a center. + + \sa center() +*/ + +void QConicalGradient::setCenter(const QPointF ¢er) +{ + Q_ASSERT(m_type == ConicalGradient); + m_data.conical.cx = center.x(); + m_data.conical.cy = center.y(); +} + +/*! + Returns the start angle of the conical gradient in logical + coordinates. + + \sa stops() +*/ + +qreal QConicalGradient::angle() const +{ + Q_ASSERT(m_type == ConicalGradient); + return m_data.conical.angle; +} + + +/*! + \since 4.2 + + Sets \a angle to be the start angle for this conical gradient in + logical coordinates. + + \sa angle() +*/ + +void QConicalGradient::setAngle(qreal angle) +{ + Q_ASSERT(m_type == ConicalGradient); + m_data.conical.angle = angle; +} + +/*! + \typedef QGradientStop + \relates QGradient + + Typedef for QPair<\l qreal, QColor>. +*/ + +/*! + \typedef QGradientStops + \relates QGradient + + Typedef for QVector<QGradientStop>. +*/ + +/*! + \typedef QBrush::DataPtr + \internal +*/ + +/*! + \fn DataPtr &QBrush::data_ptr() + \internal +*/ + + +/*! + \fn bool QBrush::isDetached() const + \internal +*/ + +/*! + \fn QTransform QBrush::transform() const + \since 4.3 + + Returns the current transformation matrix for the brush. + + \sa setTransform() +*/ + +QT_END_NAMESPACE diff --git a/src/gui/painting/qbrush.h b/src/gui/painting/qbrush.h new file mode 100644 index 0000000000..8b313196db --- /dev/null +++ b/src/gui/painting/qbrush.h @@ -0,0 +1,338 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBRUSH_H +#define QBRUSH_H + +#include <QtCore/qpair.h> +#include <QtCore/qpoint.h> +#include <QtCore/qvector.h> +#include <QtCore/qscopedpointer.h> +#include <QtGui/qcolor.h> +#include <QtGui/qmatrix.h> +#include <QtGui/qtransform.h> +#include <QtGui/qimage.h> +#include <QtGui/qpixmap.h> + +#if defined(Q_OS_VXWORKS) +# if defined(m_data) +# undef m_data +# endif +# if defined(m_type) +# undef m_type +# endif +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +struct QBrushData; +class QPixmap; +class QGradient; +class QVariant; +struct QBrushDataPointerDeleter; + +class Q_GUI_EXPORT QBrush +{ +public: + QBrush(); + QBrush(Qt::BrushStyle bs); + QBrush(const QColor &color, Qt::BrushStyle bs=Qt::SolidPattern); + QBrush(Qt::GlobalColor color, Qt::BrushStyle bs=Qt::SolidPattern); + + QBrush(const QColor &color, const QPixmap &pixmap); + QBrush(Qt::GlobalColor color, const QPixmap &pixmap); + QBrush(const QPixmap &pixmap); + QBrush(const QImage &image); + + QBrush(const QBrush &brush); + + QBrush(const QGradient &gradient); + + ~QBrush(); + QBrush &operator=(const QBrush &brush); +#ifdef Q_COMPILER_RVALUE_REFS + inline QBrush &operator=(QBrush &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QBrush &other) { qSwap(d, other.d); } + + operator QVariant() const; + + inline Qt::BrushStyle style() const; + void setStyle(Qt::BrushStyle); + + inline const QMatrix &matrix() const; + void setMatrix(const QMatrix &mat); + + inline QTransform transform() const; + void setTransform(const QTransform &); + + QPixmap texture() const; + void setTexture(const QPixmap &pixmap); + + QImage textureImage() const; + void setTextureImage(const QImage &image); + + inline const QColor &color() const; + void setColor(const QColor &color); + inline void setColor(Qt::GlobalColor color); + + const QGradient *gradient() const; + + bool isOpaque() const; + + bool operator==(const QBrush &b) const; + inline bool operator!=(const QBrush &b) const { return !(operator==(b)); } + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT operator const QColor&() const; + QT3_SUPPORT QPixmap *pixmap() const; + inline QT3_SUPPORT void setPixmap(const QPixmap &pixmap) { setTexture(pixmap); } +#endif + +private: +#if defined(Q_WS_X11) + friend class QX11PaintEngine; +#endif + friend class QRasterPaintEngine; + friend class QRasterPaintEnginePrivate; + friend struct QSpanData; + friend class QPainter; + friend bool Q_GUI_EXPORT qHasPixmapTexture(const QBrush& brush); + void detach(Qt::BrushStyle newStyle); + void init(const QColor &color, Qt::BrushStyle bs); + QScopedPointer<QBrushData, QBrushDataPointerDeleter> d; + void cleanUp(QBrushData *x); + +public: + inline bool isDetached() const; + typedef QScopedPointer<QBrushData, QBrushDataPointerDeleter> DataPtr; + inline DataPtr &data_ptr() { return d; } +}; + +inline void QBrush::setColor(Qt::GlobalColor acolor) +{ setColor(QColor(acolor)); } + +Q_DECLARE_TYPEINFO(QBrush, Q_MOVABLE_TYPE); +Q_DECLARE_SHARED(QBrush) + +/***************************************************************************** + QBrush stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QBrush &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QBrush &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QBrush &); +#endif + +struct QBrushData +{ + QAtomicInt ref; + Qt::BrushStyle style; + QColor color; + QTransform transform; +}; + +inline Qt::BrushStyle QBrush::style() const { return d->style; } +inline const QColor &QBrush::color() const { return d->color; } +inline const QMatrix &QBrush::matrix() const { return d->transform.toAffine(); } +inline QTransform QBrush::transform() const { return d->transform; } +inline bool QBrush::isDetached() const { return d->ref == 1; } + +#ifdef QT3_SUPPORT +inline QBrush::operator const QColor&() const { return d->color; } +#endif + + +/******************************************************************************* + * QGradients + */ +class QGradientPrivate; + +typedef QPair<qreal, QColor> QGradientStop; +typedef QVector<QGradientStop> QGradientStops; + +class Q_GUI_EXPORT QGradient +{ + Q_GADGET + Q_ENUMS(Type Spread CoordinateMode) +public: + enum Type { + LinearGradient, + RadialGradient, + ConicalGradient, + NoGradient + }; + + enum Spread { + PadSpread, + ReflectSpread, + RepeatSpread + }; + + enum CoordinateMode { + LogicalMode, + StretchToDeviceMode, + ObjectBoundingMode + }; + + enum InterpolationMode { + ColorInterpolation, + ComponentInterpolation + }; + + QGradient(); + + Type type() const { return m_type; } + + inline void setSpread(Spread spread); + Spread spread() const { return m_spread; } + + void setColorAt(qreal pos, const QColor &color); + + void setStops(const QGradientStops &stops); + QGradientStops stops() const; + + CoordinateMode coordinateMode() const; + void setCoordinateMode(CoordinateMode mode); + + InterpolationMode interpolationMode() const; + void setInterpolationMode(InterpolationMode mode); + + bool operator==(const QGradient &gradient) const; + inline bool operator!=(const QGradient &other) const + { return !operator==(other); } + + bool operator==(const QGradient &gradient); // ### Qt 5: remove + +private: + friend class QLinearGradient; + friend class QRadialGradient; + friend class QConicalGradient; + + Type m_type; + Spread m_spread; + QGradientStops m_stops; + union { + struct { + qreal x1, y1, x2, y2; + } linear; + struct { + qreal cx, cy, fx, fy, radius; + } radial; + struct { + qreal cx, cy, angle; + } conical; + } m_data; + void *dummy; +}; + +inline void QGradient::setSpread(Spread aspread) +{ m_spread = aspread; } + +class Q_GUI_EXPORT QLinearGradient : public QGradient +{ +public: + QLinearGradient(); + QLinearGradient(const QPointF &start, const QPointF &finalStop); + QLinearGradient(qreal xStart, qreal yStart, qreal xFinalStop, qreal yFinalStop); + + QPointF start() const; + void setStart(const QPointF &start); + inline void setStart(qreal x, qreal y) { setStart(QPointF(x, y)); } + + QPointF finalStop() const; + void setFinalStop(const QPointF &stop); + inline void setFinalStop(qreal x, qreal y) { setFinalStop(QPointF(x, y)); } +}; + + +class Q_GUI_EXPORT QRadialGradient : public QGradient +{ +public: + QRadialGradient(); + QRadialGradient(const QPointF ¢er, qreal radius, const QPointF &focalPoint); + QRadialGradient(qreal cx, qreal cy, qreal radius, qreal fx, qreal fy); + + QRadialGradient(const QPointF ¢er, qreal radius); + QRadialGradient(qreal cx, qreal cy, qreal radius); + + QPointF center() const; + void setCenter(const QPointF ¢er); + inline void setCenter(qreal x, qreal y) { setCenter(QPointF(x, y)); } + + QPointF focalPoint() const; + void setFocalPoint(const QPointF &focalPoint); + inline void setFocalPoint(qreal x, qreal y) { setFocalPoint(QPointF(x, y)); } + + qreal radius() const; + void setRadius(qreal radius); +}; + + +class Q_GUI_EXPORT QConicalGradient : public QGradient +{ +public: + QConicalGradient(); + QConicalGradient(const QPointF ¢er, qreal startAngle); + QConicalGradient(qreal cx, qreal cy, qreal startAngle); + + QPointF center() const; + void setCenter(const QPointF ¢er); + inline void setCenter(qreal x, qreal y) { setCenter(QPointF(x, y)); } + + qreal angle() const; + void setAngle(qreal angle); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QBRUSH_H diff --git a/src/gui/painting/qcolor.cpp b/src/gui/painting/qcolor.cpp new file mode 100644 index 0000000000..ff6c24ee9e --- /dev/null +++ b/src/gui/painting/qcolor.cpp @@ -0,0 +1,2720 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolor.h" +#include "qcolor_p.h" +#include "qnamespace.h" +#include "qcolormap.h" +#include "qdatastream.h" +#include "qvariant.h" +#include "qdebug.h" + +#ifdef Q_WS_X11 +# include "qapplication.h" +# include "qx11info_x11.h" +# include "private/qt_x11_p.h" + +static bool allowX11ColorNames = false; + +#endif + +#include <math.h> +#include <stdio.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QColor + \brief The QColor class provides colors based on RGB, HSV or CMYK values. + + \ingroup painting + \ingroup appearance + + + A color is normally specified in terms of RGB (red, green, and + blue) components, but it is also possible to specify it in terms + of HSV (hue, saturation, and value) and CMYK (cyan, magenta, + yellow and black) components. In addition a color can be specified + using a color name. The color name can be any of the SVG 1.0 color + names. + + \table + \header + \o RGB \o HSV \o CMYK + \row + \o \inlineimage qcolor-rgb.png + \o \inlineimage qcolor-hsv.png + \o \inlineimage qcolor-cmyk.png + \endtable + + The QColor constructor creates the color based on RGB values. To + create a QColor based on either HSV or CMYK values, use the + toHsv() and toCmyk() functions respectively. These functions + return a copy of the color using the desired format. In addition + the static fromRgb(), fromHsv() and fromCmyk() functions create + colors from the specified values. Alternatively, a color can be + converted to any of the three formats using the convertTo() + function (returning a copy of the color in the desired format), or + any of the setRgb(), setHsv() and setCmyk() functions altering \e + this color's format. The spec() function tells how the color was + specified. + + A color can be set by passing an RGB string (such as "#112233"), + or a color name (such as "blue"), to the setNamedColor() function. + The color names are taken from the SVG 1.0 color names. The name() + function returns the name of the color in the format + "#RRGGBB". Colors can also be set using setRgb(), setHsv() and + setCmyk(). To get a lighter or darker color use the lighter() and + darker() functions respectively. + + The isValid() function indicates whether a QColor is legal at + all. For example, a RGB color with RGB values out of range is + illegal. For performance reasons, QColor mostly disregards illegal + colors, and for that reason, the result of using an invalid color + is undefined. + + The color components can be retrieved individually, e.g with + red(), hue() and cyan(). The values of the color components can + also be retrieved in one go using the getRgb(), getHsv() and + getCmyk() functions. Using the RGB color model, the color + components can in addition be accessed with rgb(). + + There are several related non-members: QRgb is a typdef for an + unsigned int representing the RGB value triplet (r, g, b). Note + that it also can hold a value for the alpha-channel (for more + information, see the \l {QColor#Alpha-Blended + Drawing}{Alpha-Blended Drawing} section). The qRed(), qBlue() and + qGreen() functions return the respective component of the given + QRgb value, while the qRgb() and qRgba() functions create and + return the QRgb triplet based on the given component + values. Finally, the qAlpha() function returns the alpha component + of the provided QRgb, and the qGray() function calculates and + return a gray value based on the given value. + + QColor is platform and device independent. The QColormap class + maps the color to the hardware. + + For more information about painting in general, see the \l{Paint + System} documentation. + + \tableofcontents + + \section1 Integer vs. Floating Point Precision + + QColor supports floating point precision and provides floating + point versions of all the color components functions, + e.g. getRgbF(), hueF() and fromCmykF(). Note that since the + components are stored using 16-bit integers, there might be minor + deviations between the values set using, for example, setRgbF() + and the values returned by the getRgbF() function due to rounding. + + While the integer based functions take values in the range 0-255 + (except hue() which must have values within the range 0-359), + the floating point functions accept values in the range 0.0 - 1.0. + + \section1 Alpha-Blended Drawing + + QColor also support alpha-blended outlining and filling. The + alpha channel of a color specifies the transparency effect, 0 + represents a fully transparent color, while 255 represents a fully + opaque color. For example: + + \snippet doc/src/snippets/code/src_gui_painting_qcolor.cpp 0 + + The code above produces the following output: + + \img alphafill.png + + Alpha-blended drawing is supported on Windows, Mac OS X, and on + X11 systems that have the X Render extension installed. + + The alpha channel of a color can be retrieved and set using the + alpha() and setAlpha() functions if its value is an integer, and + alphaF() and setAlphaF() if its value is qreal (double). By + default, the alpha-channel is set to 255 (opaque). To retrieve and + set \e all the RGB color components (including the alpha-channel) + in one go, use the rgba() and setRgba() functions. + + \section1 Predefined Colors + + There are 20 predefined QColors described by the Qt::GlobalColor enum, + including black, white, primary and secondary colors, darker versions + of these colors and three shades of gray. QColor also recognizes a + variety of color names; the static colorNames() function returns a + QStringList color names that QColor knows about. + + \img qt-colors.png Qt Colors + + Additionally, the Qt::color0, Qt::color1 and Qt::transparent colors + are used for special purposes. + + Qt::color0 (zero pixel value) and Qt::color1 (non-zero pixel value) + are special colors for drawing in QBitmaps. Painting with Qt::color0 + sets the bitmap bits to 0 (transparent; i.e., background), and painting + with Qt::color1 sets the bits to 1 (opaque; i.e., foreground). + + Qt::transparent is used to indicate a transparent pixel. When painting + with this value, a pixel value will be used that is appropriate for the + underlying pixel format in use. + + \section1 The HSV Color Model + + The RGB model is hardware-oriented. Its representation is close to + what most monitors show. In contrast, HSV represents color in a way + more suited to the human perception of color. For example, the + relationships "stronger than", "darker than", and "the opposite of" + are easily expressed in HSV but are much harder to express in RGB. + + HSV, like RGB, has three components: + + \list + \o H, for hue, is in the range 0 to 359 if the color is chromatic (not + gray), or meaningless if it is gray. It represents degrees on the + color wheel familiar to most people. Red is 0 (degrees), green is + 120, and blue is 240. + + \inlineimage qcolor-hue.png + + \o S, for saturation, is in the range 0 to 255, and the bigger it is, + the stronger the color is. Grayish colors have saturation near 0; very + strong colors have saturation near 255. + + \inlineimage qcolor-saturation.png + + \o V, for value, is in the range 0 to 255 and represents lightness or + brightness of the color. 0 is black; 255 is as far from black as + possible. + + \inlineimage qcolor-value.png + \endlist + + Here are some examples: pure red is H=0, S=255, V=255; a dark red, + moving slightly towards the magenta, could be H=350 (equivalent to + -10), S=255, V=180; a grayish light red could have H about 0 (say + 350-359 or 0-10), S about 50-100, and S=255. + + Qt returns a hue value of -1 for achromatic colors. If you pass a + hue value that is too large, Qt forces it into range. Hue 360 or 720 is + treated as 0; hue 540 is treated as 180. + + In addition to the standard HSV model, Qt provides an + alpha-channel to feature \l {QColor#Alpha-Blended + Drawing}{alpha-blended drawing}. + + \section1 The HSL Color Model + + HSL is similar to HSV. Instead of value parameter from HSV, + HSL has the lightness parameter. + The lightness parameter goes from black to color and from color to white. + If you go outside at the night its black or dark gray. At day its colorful but + if you look in a really strong light a things they are going to white and + wash out. + + \section1 The CMYK Color Model + + While the RGB and HSV color models are used for display on + computer monitors, the CMYK model is used in the four-color + printing process of printing presses and some hard-copy + devices. + + CMYK has four components, all in the range 0-255: cyan (C), + magenta (M), yellow (Y) and black (K). Cyan, magenta and yellow + are called subtractive colors; the CMYK color model creates color + by starting with a white surface and then subtracting color by + applying the appropriate components. While combining cyan, magenta + and yellow gives the color black, subtracting one or more will + yield any other color. When combined in various percentages, these + three colors can create the entire spectrum of colors. + + Mixing 100 percent of cyan, magenta and yellow \e does produce + black, but the result is unsatisfactory since it wastes ink, + increases drying time, and gives a muddy colour when printing. For + that reason, black is added in professional printing to provide a + solid black tone; hence the term 'four color process'. + + In addition to the standard CMYK model, Qt provides an + alpha-channel to feature \l {QColor#Alpha-Blended + Drawing}{alpha-blended drawing}. + + \sa QPalette, QBrush, QApplication::setColorSpec() +*/ + +#define QCOLOR_INT_RANGE_CHECK(fn, var) \ + do { \ + if (var < 0 || var > 255) { \ + qWarning(#fn": invalid value %d", var); \ + var = qMax(0, qMin(var, 255)); \ + } \ + } while (0) + +#define QCOLOR_REAL_RANGE_CHECK(fn, var) \ + do { \ + if (var < qreal(0.0) || var > qreal(1.0)) { \ + qWarning(#fn": invalid value %g", var); \ + var = qMax(qreal(0.0), qMin(var, qreal(1.0))); \ + } \ + } while (0) + +/***************************************************************************** + QColor member functions + *****************************************************************************/ + +/*! + \enum QColor::Spec + + The type of color specified, either RGB, HSV, CMYK or HSL. + + \value Rgb + \value Hsv + \value Cmyk + \value Hsl + \value Invalid + + \sa spec(), convertTo() +*/ + +/*! + \fn Spec QColor::spec() const + + Returns how the color was specified. + + \sa Spec, convertTo() +*/ + + +/*! + \fn QColor::QColor() + + Constructs an invalid color with the RGB value (0, 0, 0). An + invalid color is a color that is not properly set up for the + underlying window system. + + The alpha value of an invalid color is unspecified. + + \sa isValid() +*/ + +/*! + \overload + + Constructs a new color with a color value of \a color. + + \sa isValid(), {QColor#Predefined Colors}{Predefined Colors} + */ +QColor::QColor(Qt::GlobalColor color) +{ +#define QRGB(r, g, b) \ + QRgb(((0xffu << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff))) +#define QRGBA(r, g, b, a) \ + QRgb(((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)) + + static const QRgb global_colors[] = { + QRGB(255, 255, 255), // Qt::color0 + QRGB( 0, 0, 0), // Qt::color1 + QRGB( 0, 0, 0), // black + QRGB(255, 255, 255), // white + /* + * From the "The Palette Manager: How and Why" by Ron Gery, + * March 23, 1992, archived on MSDN: + * + * The Windows system palette is broken up into two + * sections, one with fixed colors and one with colors + * that can be changed by applications. The system palette + * predefines 20 entries; these colors are known as the + * static or reserved colors and consist of the 16 colors + * found in the Windows version 3.0 VGA driver and 4 + * additional colors chosen for their visual appeal. The + * DEFAULT_PALETTE stock object is, as the name implies, + * the default palette selected into a device context (DC) + * and consists of these static colors. Applications can + * set the remaining 236 colors using the Palette Manager. + * + * The 20 reserved entries have indices in [0,9] and + * [246,255]. We reuse 17 of them. + */ + QRGB(128, 128, 128), // index 248 medium gray + QRGB(160, 160, 164), // index 247 light gray + QRGB(192, 192, 192), // index 7 light gray + QRGB(255, 0, 0), // index 249 red + QRGB( 0, 255, 0), // index 250 green + QRGB( 0, 0, 255), // index 252 blue + QRGB( 0, 255, 255), // index 254 cyan + QRGB(255, 0, 255), // index 253 magenta + QRGB(255, 255, 0), // index 251 yellow + QRGB(128, 0, 0), // index 1 dark red + QRGB( 0, 128, 0), // index 2 dark green + QRGB( 0, 0, 128), // index 4 dark blue + QRGB( 0, 128, 128), // index 6 dark cyan + QRGB(128, 0, 128), // index 5 dark magenta + QRGB(128, 128, 0), // index 3 dark yellow + QRGBA(0, 0, 0, 0) // transparent + }; +#undef QRGB +#undef QRGBA + + setRgb(qRed(global_colors[color]), + qGreen(global_colors[color]), + qBlue(global_colors[color]), + qAlpha(global_colors[color])); +} + +/*! + \fn QColor::QColor(int r, int g, int b, int a = 255) + + Constructs a color with the RGB value \a r, \a g, \a b, and the + alpha-channel (transparency) value of \a a. + + The color is left invalid if any of the arguments are invalid. + + \sa setRgba(), isValid() +*/ + +/*! + Constructs a color with the value \a color. The alpha component is + ignored and set to solid. + + \sa fromRgb(), isValid() +*/ + +QColor::QColor(QRgb color) +{ + cspec = Rgb; + ct.argb.alpha = 0xffff; + ct.argb.red = qRed(color) * 0x101; + ct.argb.green = qGreen(color) * 0x101; + ct.argb.blue = qBlue(color) * 0x101; + ct.argb.pad = 0; +} + + +/*! + \internal + + Constructs a color with the given \a spec. + + This function is primarly present to avoid that QColor::Invalid + becomes a valid color by accident. +*/ + +QColor::QColor(Spec spec) +{ + switch (spec) { + case Invalid: + invalidate(); + break; + case Rgb: + setRgb(0, 0, 0); + break; + case Hsv: + setHsv(0, 0, 0); + break; + case Cmyk: + setCmyk(0, 0, 0, 0); + break; + case Hsl: + setHsl(0, 0, 0, 0); + break; + } +} + +/*! + \fn QColor::QColor(const QString &name) + + Constructs a named color in the same way as setNamedColor() using + the given \a name. + + The color is left invalid if the \a name cannot be parsed. + + \sa setNamedColor(), name(), isValid() +*/ + +/*! + \fn QColor::QColor(const char *name) + + Constructs a named color in the same way as setNamedColor() using + the given \a name. + + The color is left invalid if the \a name cannot be parsed. + + \sa setNamedColor(), name(), isValid() +*/ + +/*! + \fn QColor::QColor(const QColor &color) + + Constructs a color that is a copy of \a color. + + \sa isValid() +*/ + +/*! + \fn bool QColor::isValid() const + + Returns true if the color is valid; otherwise returns false. +*/ + +/*! + Returns the name of the color in the format "#RRGGBB"; i.e. a "#" + character followed by three two-digit hexadecimal numbers. + + \sa setNamedColor() +*/ + +QString QColor::name() const +{ + QString s; + s.sprintf("#%02x%02x%02x", red(), green(), blue()); + return s; +} + +/*! + Sets the RGB value of this QColor to \a name, which may be in one + of these formats: + + \list + \i #RGB (each of R, G, and B is a single hex digit) + \i #RRGGBB + \i #RRRGGGBBB + \i #RRRRGGGGBBBB + \i A name from the list of colors defined in the list of \l{SVG color keyword names} + provided by the World Wide Web Consortium; for example, "steelblue" or "gainsboro". + These color names work on all platforms. Note that these color names are \e not the + same as defined by the Qt::GlobalColor enums, e.g. "green" and Qt::green does not + refer to the same color. + \i \c transparent - representing the absence of a color. + \i \e{X11 only}: If allowX11ColorNames() returns true, any valid X11 color name. See + the documentation for \c XParseColor() for information about valid X11 color names. + \endlist + + The color is invalid if \a name cannot be parsed. + + \sa QColor(), name(), isValid(), allowX11ColorNames() +*/ + +void QColor::setNamedColor(const QString &name) +{ + if (!setColorFromString(name)) + qWarning("QColor::setNamedColor: Unknown color name '%s'", name.toLatin1().constData()); +} + +/*! + \since 4.7 + + Returns true if the \a name is a valid color name and can + be used to construct a valid QColor object, otherwise returns + false. + + It uses the same algorithm used in setNamedColor(). + + \sa setNamedColor() +*/ +bool QColor::isValidColor(const QString &name) +{ + return !name.isEmpty() && QColor().setColorFromString(name); +} + +bool QColor::setColorFromString(const QString &name) +{ + if (name.isEmpty()) { + invalidate(); + return true; + } + + if (name.startsWith(QLatin1Char('#'))) { + QRgb rgb; + if (qt_get_hex_rgb(name.constData(), name.length(), &rgb)) { + setRgb(rgb); + return true; + } else { + invalidate(); + return false; + } + } + +#ifndef QT_NO_COLORNAMES + QRgb rgb; + if (qt_get_named_rgb(name.constData(), name.length(), &rgb)) { + setRgba(rgb); + return true; + } else +#endif + { +#ifdef Q_WS_X11 + XColor result; + if (allowX11ColorNames() + && QApplication::instance() + && QX11Info::display() + && XParseColor(QX11Info::display(), QX11Info::appColormap(), name.toLatin1().constData(), &result)) { + setRgb(result.red >> 8, result.green >> 8, result.blue >> 8); + return true; + } else +#endif + { + invalidate(); + return false; + } + } +} + +/*! + Returns a QStringList containing the color names Qt knows about. + + \sa {QColor#Predefined Colors}{Predefined Colors} +*/ +QStringList QColor::colorNames() +{ +#ifndef QT_NO_COLORNAMES + return qt_get_colornames(); +#else + return QStringList(); +#endif +} + +/*! + Sets the contents pointed to by \a h, \a s, \a v, and \a a, to the hue, + saturation, value, and alpha-channel (transparency) components of the + color's HSV value. + + These components can be retrieved individually using the hueF(), + saturationF(), valueF() and alphaF() functions. + + \sa setHsv() {QColor#The HSV Color Model}{The HSV Color Model} +*/ +void QColor::getHsvF(qreal *h, qreal *s, qreal *v, qreal *a) const +{ + if (!h || !s || !v) + return; + + if (cspec != Invalid && cspec != Hsv) { + toHsv().getHsvF(h, s, v, a); + return; + } + + *h = ct.ahsv.hue == USHRT_MAX ? qreal(-1.0) : ct.ahsv.hue / qreal(36000.0); + *s = ct.ahsv.saturation / qreal(USHRT_MAX); + *v = ct.ahsv.value / qreal(USHRT_MAX); + + if (a) + *a = ct.ahsv.alpha / qreal(USHRT_MAX); +} + +/*! + Sets the contents pointed to by \a h, \a s, \a v, and \a a, to the hue, + saturation, value, and alpha-channel (transparency) components of the + color's HSV value. + + These components can be retrieved individually using the hue(), + saturation(), value() and alpha() functions. + + \sa setHsv(), {QColor#The HSV Color Model}{The HSV Color Model} +*/ +void QColor::getHsv(int *h, int *s, int *v, int *a) const +{ + if (!h || !s || !v) + return; + + if (cspec != Invalid && cspec != Hsv) { + toHsv().getHsv(h, s, v, a); + return; + } + + *h = ct.ahsv.hue == USHRT_MAX ? -1 : ct.ahsv.hue / 100; + *s = ct.ahsv.saturation >> 8; + *v = ct.ahsv.value >> 8; + + if (a) + *a = ct.ahsv.alpha >> 8; +} + +/*! + Sets a HSV color value; \a h is the hue, \a s is the saturation, \a v is + the value and \a a is the alpha component of the HSV color. + + All the values must be in the range 0.0-1.0. + + \sa getHsvF(), setHsv(), {QColor#The HSV Color Model}{The HSV + Color Model} +*/ +void QColor::setHsvF(qreal h, qreal s, qreal v, qreal a) +{ + if (((h < qreal(0.0) || h > qreal(1.0)) && h != qreal(-1.0)) + || (s < qreal(0.0) || s > qreal(1.0)) + || (v < qreal(0.0) || v > qreal(1.0)) + || (a < qreal(0.0) || a > qreal(1.0))) { + qWarning("QColor::setHsvF: HSV parameters out of range"); + return; + } + + cspec = Hsv; + ct.ahsv.alpha = qRound(a * USHRT_MAX); + ct.ahsv.hue = h == qreal(-1.0) ? USHRT_MAX : qRound(h * 36000); + ct.ahsv.saturation = qRound(s * USHRT_MAX); + ct.ahsv.value = qRound(v * USHRT_MAX); + ct.ahsv.pad = 0; +} + +/*! + Sets a HSV color value; \a h is the hue, \a s is the saturation, \a v is + the value and \a a is the alpha component of the HSV color. + + The saturation, value and alpha-channel values must be in the range 0-255, + and the hue value must be greater than -1. + + \sa getHsv(), setHsvF(), {QColor#The HSV Color Model}{The HSV + Color Model} +*/ +void QColor::setHsv(int h, int s, int v, int a) +{ + if (h < -1 || (uint)s > 255 || (uint)v > 255 || (uint)a > 255) { + qWarning("QColor::setHsv: HSV parameters out of range"); + invalidate(); + return; + } + + cspec = Hsv; + ct.ahsv.alpha = a * 0x101; + ct.ahsv.hue = h == -1 ? USHRT_MAX : (h % 360) * 100; + ct.ahsv.saturation = s * 0x101; + ct.ahsv.value = v * 0x101; + ct.ahsv.pad = 0; +} + +/*! + \since 4.6 + + Sets the contents pointed to by \a h, \a s, \a l, and \a a, to the hue, + saturation, lightness, and alpha-channel (transparency) components of the + color's HSL value. + + These components can be retrieved individually using the hueHslF(), + saturationHslF(), lightnessF() and alphaF() functions. + + \sa setHsl() +*/ +void QColor::getHslF(qreal *h, qreal *s, qreal *l, qreal *a) const +{ + if (!h || !s || !l) + return; + + if (cspec != Invalid && cspec != Hsl) { + toHsl().getHslF(h, s, l, a); + return; + } + + *h = ct.ahsl.hue == USHRT_MAX ? qreal(-1.0) : ct.ahsl.hue / qreal(36000.0); + *s = ct.ahsl.saturation / qreal(USHRT_MAX); + *l = ct.ahsl.lightness / qreal(USHRT_MAX); + + if (a) + *a = ct.ahsl.alpha / qreal(USHRT_MAX); +} + +/*! + \since 4.6 + + Sets the contents pointed to by \a h, \a s, \a l, and \a a, to the hue, + saturation, lightness, and alpha-channel (transparency) components of the + color's HSL value. + + These components can be retrieved individually using the hueHsl(), + saturationHsl(), lightness() and alpha() functions. + + \sa setHsl() +*/ +void QColor::getHsl(int *h, int *s, int *l, int *a) const +{ + if (!h || !s || !l) + return; + + if (cspec != Invalid && cspec != Hsl) { + toHsl().getHsl(h, s, l, a); + return; + } + + *h = ct.ahsl.hue == USHRT_MAX ? -1 : ct.ahsl.hue / 100; + *s = ct.ahsl.saturation >> 8; + *l = ct.ahsl.lightness >> 8; + + if (a) + *a = ct.ahsl.alpha >> 8; +} + +/*! + \since 4.6 + + Sets a HSL color lightness; \a h is the hue, \a s is the saturation, \a l is + the lightness and \a a is the alpha component of the HSL color. + + All the values must be in the range 0.0-1.0. + + \sa getHslF(), setHsl() +*/ +void QColor::setHslF(qreal h, qreal s, qreal l, qreal a) +{ + if (((h < qreal(0.0) || h > qreal(1.0)) && h != qreal(-1.0)) + || (s < qreal(0.0) || s > qreal(1.0)) + || (l < qreal(0.0) || l > qreal(1.0)) + || (a < qreal(0.0) || a > qreal(1.0))) { + qWarning("QColor::setHsvF: HSV parameters out of range"); + return; + } + + cspec = Hsl; + ct.ahsl.alpha = qRound(a * USHRT_MAX); + ct.ahsl.hue = h == qreal(-1.0) ? USHRT_MAX : qRound(h * 36000); + ct.ahsl.saturation = qRound(s * USHRT_MAX); + ct.ahsl.lightness = qRound(l * USHRT_MAX); + ct.ahsl.pad = 0; +} + +/*! + \since 4.6 + + Sets a HSL color value; \a h is the hue, \a s is the saturation, \a l is + the lightness and \a a is the alpha component of the HSL color. + + The saturation, value and alpha-channel values must be in the range 0-255, + and the hue value must be greater than -1. + + \sa getHsl(), setHslF() +*/ +void QColor::setHsl(int h, int s, int l, int a) +{ + if (h < -1 || (uint)s > 255 || (uint)l > 255 || (uint)a > 255) { + qWarning("QColor::setHsv: HSV parameters out of range"); + invalidate(); + return; + } + + cspec = Hsl; + ct.ahsl.alpha = a * 0x101; + ct.ahsl.hue = h == -1 ? USHRT_MAX : (h % 360) * 100; + ct.ahsl.saturation = s * 0x101; + ct.ahsl.lightness = l * 0x101; + ct.ahsl.pad = 0; +} + +/*! + Sets the contents pointed to by \a r, \a g, \a b, and \a a, to the red, + green, blue, and alpha-channel (transparency) components of the color's + RGB value. + + These components can be retrieved individually using the redF(), greenF(), + blueF() and alphaF() functions. + + \sa rgb(), setRgb() +*/ +void QColor::getRgbF(qreal *r, qreal *g, qreal *b, qreal *a) const +{ + if (!r || !g || !b) + return; + + if (cspec != Invalid && cspec != Rgb) { + toRgb().getRgbF(r, g, b, a); + return; + } + + *r = ct.argb.red / qreal(USHRT_MAX); + *g = ct.argb.green / qreal(USHRT_MAX); + *b = ct.argb.blue / qreal(USHRT_MAX); + + if (a) + *a = ct.argb.alpha / qreal(USHRT_MAX); + +} + +/*! + Sets the contents pointed to by \a r, \a g, \a b, and \a a, to the red, + green, blue, and alpha-channel (transparency) components of the color's + RGB value. + + These components can be retrieved individually using the red(), green(), + blue() and alpha() functions. + + \sa rgb(), setRgb() +*/ +void QColor::getRgb(int *r, int *g, int *b, int *a) const +{ + if (!r || !g || !b) + return; + + if (cspec != Invalid && cspec != Rgb) { + toRgb().getRgb(r, g, b, a); + return; + } + + *r = ct.argb.red >> 8; + *g = ct.argb.green >> 8; + *b = ct.argb.blue >> 8; + + if (a) + *a = ct.argb.alpha >> 8; +} + +/*! + \obsolete + \fn void QColor::getRgba(int *r, int *g, int *b, int *a) const + + Use getRgb() instead. +*/ + +/*! + \fn void QColor::setRgbF(qreal r, qreal g, qreal b, qreal a) + + Sets the color channels of this color to \a r (red), \a g (green), + \a b (blue) and \a a (alpha, transparency). + + All values must be in the range 0.0-1.0. + + \sa rgb(), getRgbF(), setRgb() +*/ +void QColor::setRgbF(qreal r, qreal g, qreal b, qreal a) +{ + if (r < qreal(0.0) || r > qreal(1.0) + || g < qreal(0.0) || g > qreal(1.0) + || b < qreal(0.0) || b > qreal(1.0) + || a < qreal(0.0) || a > qreal(1.0)) { + qWarning("QColor::setRgbF: RGB parameters out of range"); + invalidate(); + return; + } + + cspec = Rgb; + ct.argb.alpha = qRound(a * USHRT_MAX); + ct.argb.red = qRound(r * USHRT_MAX); + ct.argb.green = qRound(g * USHRT_MAX); + ct.argb.blue = qRound(b * USHRT_MAX); + ct.argb.pad = 0; +} + +/*! + Sets the RGB value to \a r, \a g, \a b and the alpha value to \a a. + + All the values must be in the range 0-255. + + \sa rgb(), getRgb(), setRgbF() +*/ +void QColor::setRgb(int r, int g, int b, int a) +{ + if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255 || (uint)a > 255) { + qWarning("QColor::setRgb: RGB parameters out of range"); + invalidate(); + return; + } + + cspec = Rgb; + ct.argb.alpha = a * 0x101; + ct.argb.red = r * 0x101; + ct.argb.green = g * 0x101; + ct.argb.blue = b * 0x101; + ct.argb.pad = 0; +} + +/*! + \obsolete + \fn void QColor::setRgba(int r, int g, int b, int a) + + Use setRgb() instead. +*/ + +/*! + \fn QRgb QColor::rgba() const + + Returns the RGB value of the color, including its alpha. + + For an invalid color, the alpha value of the returned color is unspecified. + + \sa setRgba(), rgb() +*/ + +QRgb QColor::rgba() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().rgba(); + return qRgba(ct.argb.red >> 8, ct.argb.green >> 8, ct.argb.blue >> 8, ct.argb.alpha >> 8); +} + +/*! + Sets the RGB value to \a rgba, including its alpha. + + \sa rgba(), rgb() +*/ +void QColor::setRgba(QRgb rgba) +{ + cspec = Rgb; + ct.argb.alpha = qAlpha(rgba) * 0x101; + ct.argb.red = qRed(rgba) * 0x101; + ct.argb.green = qGreen(rgba) * 0x101; + ct.argb.blue = qBlue(rgba) * 0x101; + ct.argb.pad = 0; +} + +/*! + \fn QRgb QColor::rgb() const + + Returns the RGB value of the color. The alpha value is opaque. + + \sa getRgb(), rgba() +*/ +QRgb QColor::rgb() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().rgb(); + return qRgb(ct.argb.red >> 8, ct.argb.green >> 8, ct.argb.blue >> 8); +} + +/*! + \overload + + Sets the RGB value to \a rgb. The alpha value is set to opaque. +*/ +void QColor::setRgb(QRgb rgb) +{ + cspec = Rgb; + ct.argb.alpha = 0xffff; + ct.argb.red = qRed(rgb) * 0x101; + ct.argb.green = qGreen(rgb) * 0x101; + ct.argb.blue = qBlue(rgb) * 0x101; + ct.argb.pad = 0; +} + +/*! + Returns the alpha color component of this color. + + \sa setAlpha(), alphaF(), {QColor#Alpha-Blended + Drawing}{Alpha-Blended Drawing} +*/ +int QColor::alpha() const +{ return ct.argb.alpha >> 8; } + + +/*! + Sets the alpha of this color to \a alpha. Integer alpha is specified in the + range 0-255. + + \sa alpha(), alphaF(), {QColor#Alpha-Blended + Drawing}{Alpha-Blended Drawing} +*/ + +void QColor::setAlpha(int alpha) +{ + QCOLOR_INT_RANGE_CHECK("QColor::setAlpha", alpha); + ct.argb.alpha = alpha * 0x101; +} + +/*! + Returns the alpha color component of this color. + + \sa setAlphaF(), alpha(), {QColor#Alpha-Blended + Drawing}{Alpha-Blended Drawing} +*/ +qreal QColor::alphaF() const +{ return ct.argb.alpha / qreal(USHRT_MAX); } + +/*! + Sets the alpha of this color to \a alpha. qreal alpha is specified in the + range 0.0-1.0. + + \sa alphaF(), alpha(), {QColor#Alpha-Blended + Drawing}{Alpha-Blended Drawing} + +*/ +void QColor::setAlphaF(qreal alpha) +{ + QCOLOR_REAL_RANGE_CHECK("QColor::setAlphaF", alpha); + qreal tmp = alpha * USHRT_MAX; + ct.argb.alpha = qRound(tmp); +} + + +/*! + Returns the red color component of this color. + + \sa setRed(), redF(), getRgb() +*/ +int QColor::red() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().red(); + return ct.argb.red >> 8; +} + +/*! + Sets the red color component of this color to \a red. Integer components + are specified in the range 0-255. + + \sa red(), redF(), setRgb() +*/ +void QColor::setRed(int red) +{ + QCOLOR_INT_RANGE_CHECK("QColor::setRed", red); + if (cspec != Rgb) + setRgb(red, green(), blue(), alpha()); + else + ct.argb.red = red * 0x101; +} + +/*! + Returns the green color component of this color. + + \sa setGreen(), greenF(), getRgb() +*/ +int QColor::green() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().green(); + return ct.argb.green >> 8; +} + +/*! + Sets the green color component of this color to \a green. Integer + components are specified in the range 0-255. + + \sa green(), greenF(), setRgb() +*/ +void QColor::setGreen(int green) +{ + QCOLOR_INT_RANGE_CHECK("QColor::setGreen", green); + if (cspec != Rgb) + setRgb(red(), green, blue(), alpha()); + else + ct.argb.green = green * 0x101; +} + + +/*! + Returns the blue color component of this color. + + \sa setBlue(), blueF(), getRgb() +*/ +int QColor::blue() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().blue(); + return ct.argb.blue >> 8; +} + + +/*! + Sets the blue color component of this color to \a blue. Integer components + are specified in the range 0-255. + + \sa blue(), blueF(), setRgb() +*/ +void QColor::setBlue(int blue) +{ + QCOLOR_INT_RANGE_CHECK("QColor::setBlue", blue); + if (cspec != Rgb) + setRgb(red(), green(), blue, alpha()); + else + ct.argb.blue = blue * 0x101; +} + +/*! + Returns the red color component of this color. + + \sa setRedF(), red(), getRgbF() +*/ +qreal QColor::redF() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().redF(); + return ct.argb.red / qreal(USHRT_MAX); +} + + +/*! + Sets the red color component of this color to \a red. Float components + are specified in the range 0.0-1.0. + + \sa redF(), red(), setRgbF() +*/ +void QColor::setRedF(qreal red) +{ + QCOLOR_REAL_RANGE_CHECK("QColor::setRedF", red); + if (cspec != Rgb) + setRgbF(red, greenF(), blueF(), alphaF()); + else + ct.argb.red = qRound(red * USHRT_MAX); +} + +/*! + Returns the green color component of this color. + + \sa setGreenF(), green(), getRgbF() +*/ +qreal QColor::greenF() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().greenF(); + return ct.argb.green / qreal(USHRT_MAX); +} + + +/*! + Sets the green color component of this color to \a green. Float components + are specified in the range 0.0-1.0. + + \sa greenF(), green(), setRgbF() +*/ +void QColor::setGreenF(qreal green) +{ + QCOLOR_REAL_RANGE_CHECK("QColor::setGreenF", green); + if (cspec != Rgb) + setRgbF(redF(), green, blueF(), alphaF()); + else + ct.argb.green = qRound(green * USHRT_MAX); +} + +/*! + Returns the blue color component of this color. + + \sa setBlueF(), blue(), getRgbF() +*/ +qreal QColor::blueF() const +{ + if (cspec != Invalid && cspec != Rgb) + return toRgb().blueF(); + return ct.argb.blue / qreal(USHRT_MAX); +} + +/*! + Sets the blue color component of this color to \a blue. Float components + are specified in the range 0.0-1.0. + + \sa blueF(), blue(), setRgbF() +*/ +void QColor::setBlueF(qreal blue) +{ + QCOLOR_REAL_RANGE_CHECK("QColor::setBlueF", blue); + if (cspec != Rgb) + setRgbF(redF(), greenF(), blue, alphaF()); + else + ct.argb.blue = qRound(blue * USHRT_MAX); +} + +/*! + Returns the hue color component of this color. + + The color is implicitly converted to HSV. + + \sa hsvHue(), hueF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ + +int QColor::hue() const +{ + return hsvHue(); +} + +/*! + Returns the hue color component of this color. + + \sa hueF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +int QColor::hsvHue() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().hue(); + return ct.ahsv.hue == USHRT_MAX ? -1 : ct.ahsv.hue / 100; +} + +/*! + Returns the saturation color component of this color. + + The color is implicitly converted to HSV. + + \sa hsvSaturation(), saturationF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ + +int QColor::saturation() const +{ + return hsvSaturation(); +} + +/*! + Returns the saturation color component of this color. + + \sa saturationF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +int QColor::hsvSaturation() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().saturation(); + return ct.ahsv.saturation >> 8; +} + +/*! + Returns the value color component of this color. + + \sa valueF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +int QColor::value() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().value(); + return ct.ahsv.value >> 8; +} + +/*! + Returns the hue color component of this color. + + The color is implicitly converted to HSV. + + \sa hsvHueF(), hue(), getHsvF(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +qreal QColor::hueF() const +{ + return hsvHueF(); +} + +/*! + Returns the hue color component of this color. + + \sa hue(), getHsvF(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +qreal QColor::hsvHueF() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().hueF(); + return ct.ahsv.hue == USHRT_MAX ? qreal(-1.0) : ct.ahsv.hue / qreal(36000.0); +} + +/*! + Returns the saturation color component of this color. + + The color is implicitly converted to HSV. + + \sa hsvSaturationF(), saturation() getHsvF(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +qreal QColor::saturationF() const +{ + return hsvSaturationF(); +} + +/*! + Returns the saturation color component of this color. + + \sa saturation() getHsvF(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +qreal QColor::hsvSaturationF() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().saturationF(); + return ct.ahsv.saturation / qreal(USHRT_MAX); +} + +/*! + Returns the value color component of this color. + + \sa value() getHsvF(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +qreal QColor::valueF() const +{ + if (cspec != Invalid && cspec != Hsv) + return toHsv().valueF(); + return ct.ahsv.value / qreal(USHRT_MAX); +} + +/*! + \since 4.6 + + Returns the hue color component of this color. + + \sa getHslF(), getHsl() +*/ +int QColor::hslHue() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().hslHue(); + return ct.ahsl.hue == USHRT_MAX ? -1 : ct.ahsl.hue / 100; +} + +/*! + \since 4.6 + + Returns the saturation color component of this color. + + \sa saturationF(), getHsv(), {QColor#The HSV Color Model}{The HSV Color + Model} +*/ +int QColor::hslSaturation() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().hslSaturation(); + return ct.ahsl.saturation >> 8; +} + +/*! + \since 4.6 + + Returns the lightness color component of this color. + + \sa lightnessF(), getHsl() +*/ +int QColor::lightness() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().lightness(); + return ct.ahsl.lightness >> 8; +} + +/*! + \since 4.6 + + Returns the hue color component of this color. + + \sa hue(), getHslF() +*/ +qreal QColor::hslHueF() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().hslHueF(); + return ct.ahsl.hue == USHRT_MAX ? qreal(-1.0) : ct.ahsl.hue / qreal(36000.0); +} + +/*! + \since 4.6 + + Returns the saturation color component of this color. + + \sa saturationF() getHslF() +*/ +qreal QColor::hslSaturationF() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().hslSaturationF(); + return ct.ahsl.saturation / qreal(USHRT_MAX); +} + +/*! + \since 4.6 + + Returns the lightness color component of this color. + + \sa value() getHslF() +*/ +qreal QColor::lightnessF() const +{ + if (cspec != Invalid && cspec != Hsl) + return toHsl().lightnessF(); + return ct.ahsl.lightness / qreal(USHRT_MAX); +} + +/*! + Returns the cyan color component of this color. + + \sa cyanF(), getCmyk(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +int QColor::cyan() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().cyan(); + return ct.acmyk.cyan >> 8; +} + +/*! + Returns the magenta color component of this color. + + \sa magentaF(), getCmyk(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +int QColor::magenta() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().magenta(); + return ct.acmyk.magenta >> 8; +} + +/*! + Returns the yellow color component of this color. + + \sa yellowF(), getCmyk(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +int QColor::yellow() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().yellow(); + return ct.acmyk.yellow >> 8; +} + +/*! + Returns the black color component of this color. + + \sa blackF(), getCmyk(), {QColor#The CMYK Color Model}{The CMYK + Color Model} + +*/ +int QColor::black() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().black(); + return ct.acmyk.black >> 8; +} + +/*! + Returns the cyan color component of this color. + + \sa cyan(), getCmykF(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +qreal QColor::cyanF() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().cyanF(); + return ct.acmyk.cyan / qreal(USHRT_MAX); +} + +/*! + Returns the magenta color component of this color. + + \sa magenta(), getCmykF(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +qreal QColor::magentaF() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().magentaF(); + return ct.acmyk.magenta / qreal(USHRT_MAX); +} + +/*! + Returns the yellow color component of this color. + + \sa yellow(), getCmykF(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +qreal QColor::yellowF() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().yellowF(); + return ct.acmyk.yellow / qreal(USHRT_MAX); +} + +/*! + Returns the black color component of this color. + + \sa black(), getCmykF(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +qreal QColor::blackF() const +{ + if (cspec != Invalid && cspec != Cmyk) + return toCmyk().blackF(); + return ct.acmyk.black / qreal(USHRT_MAX); +} + +/*! + Create and returns an RGB QColor based on this color. + + \sa fromRgb(), convertTo(), isValid() +*/ +QColor QColor::toRgb() const +{ + if (!isValid() || cspec == Rgb) + return *this; + + QColor color; + color.cspec = Rgb; + color.ct.argb.alpha = ct.argb.alpha; + color.ct.argb.pad = 0; + + switch (cspec) { + case Hsv: + { + if (ct.ahsv.saturation == 0 || ct.ahsv.hue == USHRT_MAX) { + // achromatic case + color.ct.argb.red = color.ct.argb.green = color.ct.argb.blue = ct.ahsv.value; + break; + } + + // chromatic case + const qreal h = ct.ahsv.hue == 36000 ? 0 : ct.ahsv.hue / 6000.; + const qreal s = ct.ahsv.saturation / qreal(USHRT_MAX); + const qreal v = ct.ahsv.value / qreal(USHRT_MAX); + const int i = int(h); + const qreal f = h - i; + const qreal p = v * (qreal(1.0) - s); + + if (i & 1) { + const qreal q = v * (qreal(1.0) - (s * f)); + + switch (i) { + case 1: + color.ct.argb.red = qRound(q * USHRT_MAX); + color.ct.argb.green = qRound(v * USHRT_MAX); + color.ct.argb.blue = qRound(p * USHRT_MAX); + break; + case 3: + color.ct.argb.red = qRound(p * USHRT_MAX); + color.ct.argb.green = qRound(q * USHRT_MAX); + color.ct.argb.blue = qRound(v * USHRT_MAX); + break; + case 5: + color.ct.argb.red = qRound(v * USHRT_MAX); + color.ct.argb.green = qRound(p * USHRT_MAX); + color.ct.argb.blue = qRound(q * USHRT_MAX); + break; + } + } else { + const qreal t = v * (qreal(1.0) - (s * (qreal(1.0) - f))); + + switch (i) { + case 0: + color.ct.argb.red = qRound(v * USHRT_MAX); + color.ct.argb.green = qRound(t * USHRT_MAX); + color.ct.argb.blue = qRound(p * USHRT_MAX); + break; + case 2: + color.ct.argb.red = qRound(p * USHRT_MAX); + color.ct.argb.green = qRound(v * USHRT_MAX); + color.ct.argb.blue = qRound(t * USHRT_MAX); + break; + case 4: + color.ct.argb.red = qRound(t * USHRT_MAX); + color.ct.argb.green = qRound(p * USHRT_MAX); + color.ct.argb.blue = qRound(v * USHRT_MAX); + break; + } + } + break; + } + case Hsl: + { + if (ct.ahsl.saturation == 0 || ct.ahsl.hue == USHRT_MAX) { + // achromatic case + color.ct.argb.red = color.ct.argb.green = color.ct.argb.blue = ct.ahsl.lightness; + } else if (ct.ahsl.lightness == 0) { + // lightness 0 + color.ct.argb.red = color.ct.argb.green = color.ct.argb.blue = 0; + } else { + // chromatic case + const qreal h = ct.ahsl.hue == 36000 ? 0 : ct.ahsl.hue / 36000.; + const qreal s = ct.ahsl.saturation / qreal(USHRT_MAX); + const qreal l = ct.ahsl.lightness / qreal(USHRT_MAX); + + qreal temp2; + if (l < qreal(0.5)) + temp2 = l * (qreal(1.0) + s); + else + temp2 = l + s - (l * s); + + const qreal temp1 = (qreal(2.0) * l) - temp2; + qreal temp3[3] = { h + (qreal(1.0) / qreal(3.0)), + h, + h - (qreal(1.0) / qreal(3.0)) }; + + for (int i = 0; i != 3; ++i) { + if (temp3[i] < qreal(0.0)) + temp3[i] += qreal(1.0); + else if (temp3[i] > qreal(1.0)) + temp3[i] -= qreal(1.0); + + const qreal sixtemp3 = temp3[i] * qreal(6.0); + if (sixtemp3 < qreal(1.0)) + color.ct.array[i+1] = qRound((temp1 + (temp2 - temp1) * sixtemp3) * USHRT_MAX); + else if ((temp3[i] * qreal(2.0)) < qreal(1.0)) + color.ct.array[i+1] = qRound(temp2 * USHRT_MAX); + else if ((temp3[i] * qreal(3.0)) < qreal(2.0)) + color.ct.array[i+1] = qRound((temp1 + (temp2 -temp1) * (qreal(2.0) /qreal(3.0) - temp3[i]) * qreal(6.0)) * USHRT_MAX); + else + color.ct.array[i+1] = qRound(temp1 * USHRT_MAX); + } + color.ct.argb.red = color.ct.argb.red == 1 ? 0 : color.ct.argb.red; + color.ct.argb.green = color.ct.argb.green == 1 ? 0 : color.ct.argb.green; + color.ct.argb.blue = color.ct.argb.blue == 1 ? 0 : color.ct.argb.blue; + } + break; + } + case Cmyk: + { + const qreal c = ct.acmyk.cyan / qreal(USHRT_MAX); + const qreal m = ct.acmyk.magenta / qreal(USHRT_MAX); + const qreal y = ct.acmyk.yellow / qreal(USHRT_MAX); + const qreal k = ct.acmyk.black / qreal(USHRT_MAX); + + color.ct.argb.red = qRound((qreal(1.0) - (c * (qreal(1.0) - k) + k)) * USHRT_MAX); + color.ct.argb.green = qRound((qreal(1.0) - (m * (qreal(1.0) - k) + k)) * USHRT_MAX); + color.ct.argb.blue = qRound((qreal(1.0) - (y * (qreal(1.0) - k) + k)) * USHRT_MAX); + break; + } + default: + break; + } + + return color; +} + + +#define Q_MAX_3(a, b, c) ( ( a > b && a > c) ? a : (b > c ? b : c) ) +#define Q_MIN_3(a, b, c) ( ( a < b && a < c) ? a : (b < c ? b : c) ) + + +/*! + Creates and returns an HSV QColor based on this color. + + \sa fromHsv(), convertTo(), isValid(), {QColor#The HSV Color + Model}{The HSV Color Model} +*/ +QColor QColor::toHsv() const +{ + if (!isValid() || cspec == Hsv) + return *this; + + if (cspec != Rgb) + return toRgb().toHsv(); + + QColor color; + color.cspec = Hsv; + color.ct.ahsv.alpha = ct.argb.alpha; + color.ct.ahsv.pad = 0; + + const qreal r = ct.argb.red / qreal(USHRT_MAX); + const qreal g = ct.argb.green / qreal(USHRT_MAX); + const qreal b = ct.argb.blue / qreal(USHRT_MAX); + const qreal max = Q_MAX_3(r, g, b); + const qreal min = Q_MIN_3(r, g, b); + const qreal delta = max - min; + color.ct.ahsv.value = qRound(max * USHRT_MAX); + if (qFuzzyIsNull(delta)) { + // achromatic case, hue is undefined + color.ct.ahsv.hue = USHRT_MAX; + color.ct.ahsv.saturation = 0; + } else { + // chromatic case + qreal hue = 0; + color.ct.ahsv.saturation = qRound((delta / max) * USHRT_MAX); + if (qFuzzyCompare(r, max)) { + hue = ((g - b) /delta); + } else if (qFuzzyCompare(g, max)) { + hue = (qreal(2.0) + (b - r) / delta); + } else if (qFuzzyCompare(b, max)) { + hue = (qreal(4.0) + (r - g) / delta); + } else { + Q_ASSERT_X(false, "QColor::toHsv", "internal error"); + } + hue *= qreal(60.0); + if (hue < qreal(0.0)) + hue += qreal(360.0); + color.ct.ahsv.hue = qRound(hue * 100); + } + + return color; +} + +/*! + Creates and returns an HSL QColor based on this color. + + \sa fromHsl(), convertTo(), isValid() +*/ +QColor QColor::toHsl() const +{ + if (!isValid() || cspec == Hsl) + return *this; + + if (cspec != Rgb) + return toRgb().toHsl(); + + QColor color; + color.cspec = Hsl; + color.ct.ahsl.alpha = ct.argb.alpha; + color.ct.ahsl.pad = 0; + + const qreal r = ct.argb.red / qreal(USHRT_MAX); + const qreal g = ct.argb.green / qreal(USHRT_MAX); + const qreal b = ct.argb.blue / qreal(USHRT_MAX); + const qreal max = Q_MAX_3(r, g, b); + const qreal min = Q_MIN_3(r, g, b); + const qreal delta = max - min; + const qreal delta2 = max + min; + const qreal lightness = qreal(0.5) * delta2; + color.ct.ahsl.lightness = qRound(lightness * USHRT_MAX); + if (qFuzzyIsNull(delta)) { + // achromatic case, hue is undefined + color.ct.ahsl.hue = USHRT_MAX; + color.ct.ahsl.saturation = 0; + } else { + // chromatic case + qreal hue = 0; + if (lightness < qreal(0.5)) + color.ct.ahsl.saturation = qRound((delta / delta2) * USHRT_MAX); + else + color.ct.ahsl.saturation = qRound((delta / (qreal(2.0) - delta2)) * USHRT_MAX); + if (qFuzzyCompare(r, max)) { + hue = ((g - b) /delta); + } else if (qFuzzyCompare(g, max)) { + hue = (qreal(2.0) + (b - r) / delta); + } else if (qFuzzyCompare(b, max)) { + hue = (qreal(4.0) + (r - g) / delta); + } else { + Q_ASSERT_X(false, "QColor::toHsv", "internal error"); + } + hue *= qreal(60.0); + if (hue < qreal(0.0)) + hue += qreal(360.0); + color.ct.ahsl.hue = qRound(hue * 100); + } + + return color; +} + +/*! + Creates and returns a CMYK QColor based on this color. + + \sa fromCmyk(), convertTo(), isValid(), {QColor#The CMYK Color + Model}{The CMYK Color Model} +*/ +QColor QColor::toCmyk() const +{ + if (!isValid() || cspec == Cmyk) + return *this; + if (cspec != Rgb) + return toRgb().toCmyk(); + + QColor color; + color.cspec = Cmyk; + color.ct.acmyk.alpha = ct.argb.alpha; + + // rgb -> cmy + const qreal r = ct.argb.red / qreal(USHRT_MAX); + const qreal g = ct.argb.green / qreal(USHRT_MAX); + const qreal b = ct.argb.blue / qreal(USHRT_MAX); + qreal c = qreal(1.0) - r; + qreal m = qreal(1.0) - g; + qreal y = qreal(1.0) - b; + + // cmy -> cmyk + const qreal k = qMin(c, qMin(m, y)); + + if (!qFuzzyIsNull(k - 1)) { + c = (c - k) / (qreal(1.0) - k); + m = (m - k) / (qreal(1.0) - k); + y = (y - k) / (qreal(1.0) - k); + } + + color.ct.acmyk.cyan = qRound(c * USHRT_MAX); + color.ct.acmyk.magenta = qRound(m * USHRT_MAX); + color.ct.acmyk.yellow = qRound(y * USHRT_MAX); + color.ct.acmyk.black = qRound(k * USHRT_MAX); + + return color; +} + +QColor QColor::convertTo(QColor::Spec colorSpec) const +{ + if (colorSpec == cspec) + return *this; + switch (colorSpec) { + case Rgb: + return toRgb(); + case Hsv: + return toHsv(); + case Cmyk: + return toCmyk(); + case Hsl: + return toHsl(); + case Invalid: + break; + } + return QColor(); // must be invalid +} + + +/*! + Static convenience function that returns a QColor constructed from the + given QRgb value \a rgb. + + The alpha component of \a rgb is ignored (i.e. it is automatically set to + 255), use the fromRgba() function to include the alpha-channel specified by + the given QRgb value. + + \sa fromRgba(), fromRgbF(), toRgb(), isValid() +*/ + +QColor QColor::fromRgb(QRgb rgb) +{ + return fromRgb(qRed(rgb), qGreen(rgb), qBlue(rgb)); +} + + +/*! + Static convenience function that returns a QColor constructed from the + given QRgb value \a rgba. + + Unlike the fromRgb() function, the alpha-channel specified by the given + QRgb value is included. + + \sa fromRgb(), isValid() +*/ + +QColor QColor::fromRgba(QRgb rgba) +{ + return fromRgb(qRed(rgba), qGreen(rgba), qBlue(rgba), qAlpha(rgba)); +} + +/*! + Static convenience function that returns a QColor constructed from the RGB + color values, \a r (red), \a g (green), \a b (blue), and \a a + (alpha-channel, i.e. transparency). + + All the values must be in the range 0-255. + + \sa toRgb(), fromRgbF(), isValid() +*/ +QColor QColor::fromRgb(int r, int g, int b, int a) +{ + if (r < 0 || r > 255 + || g < 0 || g > 255 + || b < 0 || b > 255 + || a < 0 || a > 255) { + qWarning("QColor::fromRgb: RGB parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Rgb; + color.ct.argb.alpha = a * 0x101; + color.ct.argb.red = r * 0x101; + color.ct.argb.green = g * 0x101; + color.ct.argb.blue = b * 0x101; + color.ct.argb.pad = 0; + return color; +} + +/*! + Static convenience function that returns a QColor constructed from the RGB + color values, \a r (red), \a g (green), \a b (blue), and \a a + (alpha-channel, i.e. transparency). + + All the values must be in the range 0.0-1.0. + + \sa fromRgb(), toRgb(), isValid() +*/ +QColor QColor::fromRgbF(qreal r, qreal g, qreal b, qreal a) +{ + if (r < qreal(0.0) || r > qreal(1.0) + || g < qreal(0.0) || g > qreal(1.0) + || b < qreal(0.0) || b > qreal(1.0) + || a < qreal(0.0) || a > qreal(1.0)) { + qWarning("QColor::fromRgbF: RGB parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Rgb; + color.ct.argb.alpha = qRound(a * USHRT_MAX); + color.ct.argb.red = qRound(r * USHRT_MAX); + color.ct.argb.green = qRound(g * USHRT_MAX); + color.ct.argb.blue = qRound(b * USHRT_MAX); + color.ct.argb.pad = 0; + return color; +} + +/*! + Static convenience function that returns a QColor constructed from the HSV + color values, \a h (hue), \a s (saturation), \a v (value), and \a a + (alpha-channel, i.e. transparency). + + The value of \a s, \a v, and \a a must all be in the range 0-255; the value + of \a h must be in the range 0-359. + + \sa toHsv(), fromHsvF(), isValid(), {QColor#The HSV Color + Model}{The HSV Color Model} +*/ +QColor QColor::fromHsv(int h, int s, int v, int a) +{ + if (((h < 0 || h >= 360) && h != -1) + || s < 0 || s > 255 + || v < 0 || v > 255 + || a < 0 || a > 255) { + qWarning("QColor::fromHsv: HSV parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Hsv; + color.ct.ahsv.alpha = a * 0x101; + color.ct.ahsv.hue = h == -1 ? USHRT_MAX : (h % 360) * 100; + color.ct.ahsv.saturation = s * 0x101; + color.ct.ahsv.value = v * 0x101; + color.ct.ahsv.pad = 0; + return color; +} + +/*! + \overload + + Static convenience function that returns a QColor constructed from the HSV + color values, \a h (hue), \a s (saturation), \a v (value), and \a a + (alpha-channel, i.e. transparency). + + All the values must be in the range 0.0-1.0. + + \sa toHsv(), fromHsv(), isValid(), {QColor#The HSV Color + Model}{The HSV Color Model} +*/ +QColor QColor::fromHsvF(qreal h, qreal s, qreal v, qreal a) +{ + if (((h < qreal(0.0) || h > qreal(1.0)) && h != qreal(-1.0)) + || (s < qreal(0.0) || s > qreal(1.0)) + || (v < qreal(0.0) || v > qreal(1.0)) + || (a < qreal(0.0) || a > qreal(1.0))) { + qWarning("QColor::fromHsvF: HSV parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Hsv; + color.ct.ahsv.alpha = qRound(a * USHRT_MAX); + color.ct.ahsv.hue = h == qreal(-1.0) ? USHRT_MAX : qRound(h * 36000); + color.ct.ahsv.saturation = qRound(s * USHRT_MAX); + color.ct.ahsv.value = qRound(v * USHRT_MAX); + color.ct.ahsv.pad = 0; + return color; +} + +/*! + \since 4.6 + + Static convenience function that returns a QColor constructed from the HSV + color values, \a h (hue), \a s (saturation), \a l (lightness), and \a a + (alpha-channel, i.e. transparency). + + The value of \a s, \a l, and \a a must all be in the range 0-255; the value + of \a h must be in the range 0-359. + + \sa toHsl(), fromHslF(), isValid() +*/ +QColor QColor::fromHsl(int h, int s, int l, int a) +{ + if (((h < 0 || h >= 360) && h != -1) + || s < 0 || s > 255 + || l < 0 || l > 255 + || a < 0 || a > 255) { + qWarning("QColor::fromHsv: HSV parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Hsl; + color.ct.ahsl.alpha = a * 0x101; + color.ct.ahsl.hue = h == -1 ? USHRT_MAX : (h % 360) * 100; + color.ct.ahsl.saturation = s * 0x101; + color.ct.ahsl.lightness = l * 0x101; + color.ct.ahsl.pad = 0; + return color; +} + +/*! + \overload + \since 4.6 + + Static convenience function that returns a QColor constructed from the HSV + color values, \a h (hue), \a s (saturation), \a l (lightness), and \a a + (alpha-channel, i.e. transparency). + + All the values must be in the range 0.0-1.0. + + \sa toHsl(), fromHsl(), isValid() +*/ +QColor QColor::fromHslF(qreal h, qreal s, qreal l, qreal a) +{ + if (((h < qreal(0.0) || h > qreal(1.0)) && h != qreal(-1.0)) + || (s < qreal(0.0) || s > qreal(1.0)) + || (l < qreal(0.0) || l > qreal(1.0)) + || (a < qreal(0.0) || a > qreal(1.0))) { + qWarning("QColor::fromHsvF: HSV parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Hsl; + color.ct.ahsl.alpha = qRound(a * USHRT_MAX); + color.ct.ahsl.hue = (h == qreal(-1.0)) ? USHRT_MAX : qRound(h * 36000); + if (color.ct.ahsl.hue == 36000) + color.ct.ahsl.hue = 0; + color.ct.ahsl.saturation = qRound(s * USHRT_MAX); + color.ct.ahsl.lightness = qRound(l * USHRT_MAX); + color.ct.ahsl.pad = 0; + return color; +} + + +/*! + Sets the contents pointed to by \a c, \a m, \a y, \a k, and \a a, to the + cyan, magenta, yellow, black, and alpha-channel (transparency) components + of the color's CMYK value. + + These components can be retrieved individually using the cyan(), magenta(), + yellow(), black() and alpha() functions. + + \sa setCmyk(), {QColor#The CMYK Color Model}{The CMYK Color Model} +*/ +void QColor::getCmyk(int *c, int *m, int *y, int *k, int *a) +{ + if (!c || !m || !y || !k) + return; + + if (cspec != Invalid && cspec != Cmyk) { + toCmyk().getCmyk(c, m, y, k, a); + return; + } + + *c = ct.acmyk.cyan >> 8; + *m = ct.acmyk.magenta >> 8; + *y = ct.acmyk.yellow >> 8; + *k = ct.acmyk.black >> 8; + + if (a) + *a = ct.acmyk.alpha >> 8; +} + +/*! + Sets the contents pointed to by \a c, \a m, \a y, \a k, and \a a, to the + cyan, magenta, yellow, black, and alpha-channel (transparency) components + of the color's CMYK value. + + These components can be retrieved individually using the cyanF(), + magentaF(), yellowF(), blackF() and alphaF() functions. + + \sa setCmykF(), {QColor#The CMYK Color Model}{The CMYK Color Model} +*/ +void QColor::getCmykF(qreal *c, qreal *m, qreal *y, qreal *k, qreal *a) +{ + if (!c || !m || !y || !k) + return; + + if (cspec != Invalid && cspec != Cmyk) { + toCmyk().getCmykF(c, m, y, k, a); + return; + } + + *c = ct.acmyk.cyan / qreal(USHRT_MAX); + *m = ct.acmyk.magenta / qreal(USHRT_MAX); + *y = ct.acmyk.yellow / qreal(USHRT_MAX); + *k = ct.acmyk.black / qreal(USHRT_MAX); + + if (a) + *a = ct.acmyk.alpha / qreal(USHRT_MAX); +} + +/*! + Sets the color to CMYK values, \a c (cyan), \a m (magenta), \a y (yellow), + \a k (black), and \a a (alpha-channel, i.e. transparency). + + All the values must be in the range 0-255. + + \sa getCmyk(), setCmykF(), {QColor#The CMYK Color Model}{The + CMYK Color Model} +*/ +void QColor::setCmyk(int c, int m, int y, int k, int a) +{ + if (c < 0 || c > 255 + || m < 0 || m > 255 + || y < 0 || y > 255 + || k < 0 || k > 255 + || a < 0 || a > 255) { + qWarning("QColor::setCmyk: CMYK parameters out of range"); + return; + } + + cspec = Cmyk; + ct.acmyk.alpha = a * 0x101; + ct.acmyk.cyan = c * 0x101; + ct.acmyk.magenta = m * 0x101; + ct.acmyk.yellow = y * 0x101; + ct.acmyk.black = k * 0x101; +} + +/*! + \overload + + Sets the color to CMYK values, \a c (cyan), \a m (magenta), \a y (yellow), + \a k (black), and \a a (alpha-channel, i.e. transparency). + + All the values must be in the range 0.0-1.0. + + \sa getCmykF() setCmyk(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +void QColor::setCmykF(qreal c, qreal m, qreal y, qreal k, qreal a) +{ + if (c < qreal(0.0) || c > qreal(1.0) + || m < qreal(0.0) || m > qreal(1.0) + || y < qreal(0.0) || y > qreal(1.0) + || k < qreal(0.0) || k > qreal(1.0) + || a < qreal(0.0) || a > qreal(1.0)) { + qWarning("QColor::setCmykF: CMYK parameters out of range"); + return; + } + + cspec = Cmyk; + ct.acmyk.alpha = qRound(a * USHRT_MAX); + ct.acmyk.cyan = qRound(c * USHRT_MAX); + ct.acmyk.magenta = qRound(m * USHRT_MAX); + ct.acmyk.yellow = qRound(y * USHRT_MAX); + ct.acmyk.black = qRound(k * USHRT_MAX); +} + +/*! + Static convenience function that returns a QColor constructed from the + given CMYK color values: \a c (cyan), \a m (magenta), \a y (yellow), \a k + (black), and \a a (alpha-channel, i.e. transparency). + + All the values must be in the range 0-255. + + \sa toCmyk(), fromCmykF(), isValid(), {QColor#The CMYK Color Model}{The CMYK + Color Model} +*/ +QColor QColor::fromCmyk(int c, int m, int y, int k, int a) +{ + if (c < 0 || c > 255 + || m < 0 || m > 255 + || y < 0 || y > 255 + || k < 0 || k > 255 + || a < 0 || a > 255) { + qWarning("QColor::fromCmyk: CMYK parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Cmyk; + color.ct.acmyk.alpha = a * 0x101; + color.ct.acmyk.cyan = c * 0x101; + color.ct.acmyk.magenta = m * 0x101; + color.ct.acmyk.yellow = y * 0x101; + color.ct.acmyk.black = k * 0x101; + return color; +} + +/*! + \overload + + Static convenience function that returns a QColor constructed from the + given CMYK color values: \a c (cyan), \a m (magenta), \a y (yellow), \a k + (black), and \a a (alpha-channel, i.e. transparency). + + All the values must be in the range 0.0-1.0. + + \sa toCmyk(), fromCmyk(), isValid(), {QColor#The CMYK Color + Model}{The CMYK Color Model} +*/ +QColor QColor::fromCmykF(qreal c, qreal m, qreal y, qreal k, qreal a) +{ + if (c < qreal(0.0) || c > qreal(1.0) + || m < qreal(0.0) || m > qreal(1.0) + || y < qreal(0.0) || y > qreal(1.0) + || k < qreal(0.0) || k > qreal(1.0) + || a < qreal(0.0) || a > qreal(1.0)) { + qWarning("QColor::fromCmykF: CMYK parameters out of range"); + return QColor(); + } + + QColor color; + color.cspec = Cmyk; + color.ct.acmyk.alpha = qRound(a * USHRT_MAX); + color.ct.acmyk.cyan = qRound(c * USHRT_MAX); + color.ct.acmyk.magenta = qRound(m * USHRT_MAX); + color.ct.acmyk.yellow = qRound(y * USHRT_MAX); + color.ct.acmyk.black = qRound(k * USHRT_MAX); + return color; +} + +/*! + \fn QColor QColor::lighter(int factor) const + \since 4.3 + + Returns a lighter (or darker) color, but does not change this object. + + If the \a factor is greater than 100, this functions returns a lighter + color. Setting \a factor to 150 returns a color that is 50% brighter. If + the \a factor is less than 100, the return color is darker, but we + recommend using the darker() function for this purpose. If the \a factor + is 0 or negative, the return value is unspecified. + + The function converts the current RGB color to HSV, multiplies the value + (V) component by \a factor and converts the color back to RGB. + + \sa darker(), isValid() +*/ + +/*! + \obsolete + + Use lighter(\a factor) instead. +*/ +QColor QColor::light(int factor) const +{ + if (factor <= 0) // invalid lightness factor + return *this; + else if (factor < 100) // makes color darker + return darker(10000 / factor); + + QColor hsv = toHsv(); + int s = hsv.ct.ahsv.saturation; + int v = hsv.ct.ahsv.value; + + v = (factor*v)/100; + if (v > USHRT_MAX) { + // overflow... adjust saturation + s -= v - USHRT_MAX; + if (s < 0) + s = 0; + v = USHRT_MAX; + } + + hsv.ct.ahsv.saturation = s; + hsv.ct.ahsv.value = v; + + // convert back to same color spec as original color + return hsv.convertTo(cspec); +} + +/*! + \fn QColor QColor::darker(int factor) const + \since 4.3 + + Returns a darker (or lighter) color, but does not change this object. + + If the \a factor is greater than 100, this functions returns a darker + color. Setting \a factor to 300 returns a color that has one-third the + brightness. If the \a factor is less than 100, the return color is lighter, + but we recommend using the lighter() function for this purpose. If the + \a factor is 0 or negative, the return value is unspecified. + + The function converts the current RGB color to HSV, divides the value (V) + component by \a factor and converts the color back to RGB. + + \sa lighter(), isValid() +*/ + +/*! + \obsolete + + Use darker(\a factor) instead. +*/ +QColor QColor::dark(int factor) const +{ + if (factor <= 0) // invalid darkness factor + return *this; + else if (factor < 100) // makes color lighter + return lighter(10000 / factor); + + QColor hsv = toHsv(); + hsv.ct.ahsv.value = (hsv.ct.ahsv.value * 100) / factor; + + // convert back to same color spec as original color + return hsv.convertTo(cspec); +} + +/*! + Assigns a copy of \a color to this color, and returns a reference to it. +*/ +QColor &QColor::operator=(const QColor &color) +{ + cspec = color.cspec; + ct.argb = color.ct.argb; + return *this; +} + +/*! \overload + Assigns a copy of \a color and returns a reference to this color. + */ +QColor &QColor::operator=(Qt::GlobalColor color) +{ + return operator=(QColor(color)); +} + +/*! + Returns true if this color has the same RGB and alpha values as \a color; + otherwise returns false. +*/ +bool QColor::operator==(const QColor &color) const +{ + if (cspec == Hsl && cspec == color.cspec) { + return (ct.argb.alpha == color.ct.argb.alpha + && ((((ct.ahsl.hue % 36000) == (color.ct.ahsl.hue % 36000))) + || (ct.ahsl.hue == color.ct.ahsl.hue)) + && (qAbs(ct.ahsl.saturation - color.ct.ahsl.saturation) < 50 + || ct.ahsl.lightness == 0 + || color.ct.ahsl.lightness == 0 + || ct.ahsl.lightness == USHRT_MAX + || color.ct.ahsl.lightness == USHRT_MAX) + && (qAbs(ct.ahsl.lightness - color.ct.ahsl.lightness)) < 50); + } else { + return (cspec == color.cspec + && ct.argb.alpha == color.ct.argb.alpha + && (((cspec == QColor::Hsv) + && ((ct.ahsv.hue % 36000) == (color.ct.ahsv.hue % 36000))) + || (ct.ahsv.hue == color.ct.ahsv.hue)) + && ct.argb.green == color.ct.argb.green + && ct.argb.blue == color.ct.argb.blue + && ct.argb.pad == color.ct.argb.pad); + } +} + +/*! + Returns true if this color has a different RGB and alpha values from + \a color; otherwise returns false. +*/ +bool QColor::operator!=(const QColor &color) const +{ return !operator==(color); } + + +/*! + Returns the color as a QVariant +*/ +QColor::operator QVariant() const +{ + return QVariant(QVariant::Color, this); +} + +#ifdef Q_WS_X11 +/*! + Returns true if setNamedColor() is allowed to look up colors in the X11 + color database. By default, this function returns false. + + \note This function is only available on the X11 platform. + + \sa setAllowX11ColorNames() +*/ +bool QColor::allowX11ColorNames() +{ + return ::allowX11ColorNames; +} + +/*! + Allow setNamedColor() to look up colors in the X11 color database if + \a enabled. By default, setNamedColor() does \e not look up colors in the + X11 color database. + + \note This function is only available on the X11 platform. + + \sa setNamedColor(), allowX11ColorNames() +*/ +void QColor::setAllowX11ColorNames(bool enabled) +{ + ::allowX11ColorNames = enabled; +} +#endif + +/*! \internal + + Marks the color as invalid and sets all components to zero (alpha is set + to fully opaque for compatibility with Qt 3). +*/ +void QColor::invalidate() +{ + cspec = Invalid; + ct.argb.alpha = USHRT_MAX; + ct.argb.red = 0; + ct.argb.green = 0; + ct.argb.blue = 0; + ct.argb.pad = 0; +} + +#ifdef QT3_SUPPORT + +/*! + Returns the pixel value used by the underlying window system to refer to a + color. + + Use QColormap::pixel() instead. + + \oldcode + QColor myColor; + uint pixel = myColor.pixel(screen); + \newcode + QColormap cmap = QColormap::instance(screen); + uint pixel = cmap.pixel(*this); + \endcode +*/ +uint QColor::pixel(int screen) const +{ + QColormap cmap = QColormap::instance(screen); + return cmap.pixel(*this); +} + +#endif // QT3_SUPPORT + +/***************************************************************************** + QColor stream functions + *****************************************************************************/ + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QColor &c) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + if (!c.isValid()) + dbg.nospace() << "QColor(Invalid)"; + else if (c.spec() == QColor::Rgb) + dbg.nospace() << "QColor(ARGB " << c.alphaF() << ", " << c.redF() << ", " << c.greenF() << ", " << c.blueF() << ')'; + else if (c.spec() == QColor::Hsv) + dbg.nospace() << "QColor(AHSV " << c.alphaF() << ", " << c.hueF() << ", " << c.saturationF() << ", " << c.valueF() << ')'; + else if (c.spec() == QColor::Cmyk) + dbg.nospace() << "QColor(ACMYK " << c.alphaF() << ", " << c.cyanF() << ", " << c.magentaF() << ", " << c.yellowF() << ", " + << c.blackF()<< ')'; + else if (c.spec() == QColor::Hsl) + dbg.nospace() << "QColor(AHSL " << c.alphaF() << ", " << c.hslHueF() << ", " << c.hslSaturationF() << ", " << c.lightnessF() << ')'; + + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QColor to QDebug"); + return dbg; + Q_UNUSED(c); +#endif +} +#endif + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QColor &color) + \relates QColor + + Writes the \a color to the \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &stream, const QColor &color) +{ + if (stream.version() < 7) { + if (!color.isValid()) + return stream << quint32(0x49000000); + quint32 p = (quint32)color.rgb(); + if (stream.version() == 1) // Swap red and blue + p = ((p << 16) & 0xff0000) | ((p >> 16) & 0xff) | (p & 0xff00ff00); + return stream << p; + } + + qint8 s = color.cspec; + quint16 a = color.ct.argb.alpha; + quint16 r = color.ct.argb.red; + quint16 g = color.ct.argb.green; + quint16 b = color.ct.argb.blue; + quint16 p = color.ct.argb.pad; + + stream << s; + stream << a; + stream << r; + stream << g; + stream << b; + stream << p; + + return stream; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QColor &color) + \relates QColor + + Reads the \a color from the \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &stream, QColor &color) +{ + if (stream.version() < 7) { + quint32 p; + stream >> p; + if (p == 0x49000000) { + color.invalidate(); + return stream; + } + if (stream.version() == 1) // Swap red and blue + p = ((p << 16) & 0xff0000) | ((p >> 16) & 0xff) | (p & 0xff00ff00); + color.setRgb(p); + return stream; + } + + qint8 s; + quint16 a, r, g, b, p; + stream >> s; + stream >> a; + stream >> r; + stream >> g; + stream >> b; + stream >> p; + + color.cspec = QColor::Spec(s); + color.ct.argb.alpha = a; + color.ct.argb.red = r; + color.ct.argb.green = g; + color.ct.argb.blue = b; + color.ct.argb.pad = p; + + return stream; +} +#endif // QT_NO_DATASTREAM + + +/***************************************************************************** + QColor global functions (documentation only) + *****************************************************************************/ + +/*! + \fn int qRed(QRgb rgb) + \relates QColor + + Returns the red component of the ARGB quadruplet \a rgb. + + \sa qRgb(), QColor::red() +*/ + +/*! + \fn int qGreen(QRgb rgb) + \relates QColor + + Returns the green component of the ARGB quadruplet \a rgb. + + \sa qRgb(), QColor::green() +*/ + +/*! + \fn int qBlue(QRgb rgb) + \relates QColor + + Returns the blue component of the ARGB quadruplet \a rgb. + + \sa qRgb(), QColor::blue() +*/ + +/*! + \fn int qAlpha(QRgb rgba) + \relates QColor + + Returns the alpha component of the ARGB quadruplet \a rgba. + + \sa qRgb(), QColor::alpha() +*/ + +/*! + \fn QRgb qRgb(int r, int g, int b) + \relates QColor + + Returns the ARGB quadruplet (255, \a{r}, \a{g}, \a{b}). + + \sa qRgba(), qRed(), qGreen(), qBlue() +*/ + +/*! + \fn QRgb qRgba(int r, int g, int b, int a) + \relates QColor + + Returns the ARGB quadruplet (\a{a}, \a{r}, \a{g}, \a{b}). + + \sa qRgb(), qRed(), qGreen(), qBlue() +*/ + +/*! + \fn int qGray(int r, int g, int b) + \relates QColor + + Returns a gray value (0 to 255) from the (\a r, \a g, \a b) + triplet. + + The gray value is calculated using the formula (\a r * 11 + \a g * 16 + + \a b * 5)/32. +*/ + +/*! + \fn int qGray(QRgb rgb) + \overload + \relates QColor + + Returns a gray value (0 to 255) from the given ARGB quadruplet \a rgb. + + The gray value is calculated using the formula (R * 11 + G * 16 + B * 5)/32; + the alpha-channel is ignored. +*/ + +/*! + \fn QColor::QColor(int x, int y, int z, Spec colorSpec) + + Use one of the other QColor constructors, or one of the static convenience + functions, instead. +*/ + +/*! + \fn QColor::rgb(int *r, int *g, int *b) const + + Use getRgb() instead. +*/ + +/*! + \fn QColor::hsv(int *h, int *s, int *v) const + + Use getHsv() instead. +*/ + +/*! + \fn QColor QColor::convertTo(Spec colorSpec) const + + Creates a copy of \e this color in the format specified by \a colorSpec. + + \sa spec(), toCmyk(), toHsv(), toRgb(), isValid() +*/ + +/*! + \typedef QRgb + \relates QColor + + An ARGB quadruplet on the format #AARRGGBB, equivalent to an unsigned int. + + The type also holds a value for the alpha-channel. The default alpha + channel is \c ff, i.e opaque. For more information, see the + \l{QColor#Alpha-Blended Drawing}{Alpha-Blended Drawing} section. + + \sa QColor::rgb(), QColor::rgba() +*/ + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolor.h b/src/gui/painting/qcolor.h new file mode 100644 index 0000000000..af7248ce8c --- /dev/null +++ b/src/gui/painting/qcolor.h @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLOR_H +#define QCOLOR_H + +#include <QtGui/qrgb.h> +#include <QtCore/qnamespace.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QColor; +class QColormap; +class QVariant; + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QColor &); +#endif +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QColor &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QColor &); +#endif + +class Q_GUI_EXPORT QColor +{ +public: + enum Spec { Invalid, Rgb, Hsv, Cmyk, Hsl }; + + QColor(); + QColor(Qt::GlobalColor color); + QColor(int r, int g, int b, int a = 255); + QColor(QRgb rgb); + QColor(const QString& name); + QColor(const char *name); + QColor(const QColor &color); + QColor(Spec spec); + + bool isValid() const; + + QString name() const; + void setNamedColor(const QString& name); + + static QStringList colorNames(); + + inline Spec spec() const + { return cspec; } + + int alpha() const; + void setAlpha(int alpha); + + qreal alphaF() const; + void setAlphaF(qreal alpha); + + int red() const; + int green() const; + int blue() const; + void setRed(int red); + void setGreen(int green); + void setBlue(int blue); + + qreal redF() const; + qreal greenF() const; + qreal blueF() const; + void setRedF(qreal red); + void setGreenF(qreal green); + void setBlueF(qreal blue); + + void getRgb(int *r, int *g, int *b, int *a = 0) const; + void setRgb(int r, int g, int b, int a = 255); + + void getRgbF(qreal *r, qreal *g, qreal *b, qreal *a = 0) const; + void setRgbF(qreal r, qreal g, qreal b, qreal a = 1.0); + + QRgb rgba() const; + void setRgba(QRgb rgba); + + QRgb rgb() const; + void setRgb(QRgb rgb); + + int hue() const; // 0 <= hue < 360 + int saturation() const; + int hsvHue() const; // 0 <= hue < 360 + int hsvSaturation() const; + int value() const; + + qreal hueF() const; // 0.0 <= hueF < 360.0 + qreal saturationF() const; + qreal hsvHueF() const; // 0.0 <= hueF < 360.0 + qreal hsvSaturationF() const; + qreal valueF() const; + + void getHsv(int *h, int *s, int *v, int *a = 0) const; + void setHsv(int h, int s, int v, int a = 255); + + void getHsvF(qreal *h, qreal *s, qreal *v, qreal *a = 0) const; + void setHsvF(qreal h, qreal s, qreal v, qreal a = 1.0); + + int cyan() const; + int magenta() const; + int yellow() const; + int black() const; + + qreal cyanF() const; + qreal magentaF() const; + qreal yellowF() const; + qreal blackF() const; + + void getCmyk(int *c, int *m, int *y, int *k, int *a = 0); + void setCmyk(int c, int m, int y, int k, int a = 255); + + void getCmykF(qreal *c, qreal *m, qreal *y, qreal *k, qreal *a = 0); + void setCmykF(qreal c, qreal m, qreal y, qreal k, qreal a = 1.0); + + int hslHue() const; // 0 <= hue < 360 + int hslSaturation() const; + int lightness() const; + + qreal hslHueF() const; // 0.0 <= hueF < 360.0 + qreal hslSaturationF() const; + qreal lightnessF() const; + + void getHsl(int *h, int *s, int *l, int *a = 0) const; + void setHsl(int h, int s, int l, int a = 255); + + void getHslF(qreal *h, qreal *s, qreal *l, qreal *a = 0) const; + void setHslF(qreal h, qreal s, qreal l, qreal a = 1.0); + + QColor toRgb() const; + QColor toHsv() const; + QColor toCmyk() const; + QColor toHsl() const; + + QColor convertTo(Spec colorSpec) const; + + static QColor fromRgb(QRgb rgb); + static QColor fromRgba(QRgb rgba); + + static QColor fromRgb(int r, int g, int b, int a = 255); + static QColor fromRgbF(qreal r, qreal g, qreal b, qreal a = 1.0); + + static QColor fromHsv(int h, int s, int v, int a = 255); + static QColor fromHsvF(qreal h, qreal s, qreal v, qreal a = 1.0); + + static QColor fromCmyk(int c, int m, int y, int k, int a = 255); + static QColor fromCmykF(qreal c, qreal m, qreal y, qreal k, qreal a = 1.0); + + static QColor fromHsl(int h, int s, int l, int a = 255); + static QColor fromHslF(qreal h, qreal s, qreal l, qreal a = 1.0); + + QColor light(int f = 150) const; + QColor lighter(int f = 150) const; + QColor dark(int f = 200) const; + QColor darker(int f = 200) const; + + QColor &operator=(const QColor &); + QColor &operator=(Qt::GlobalColor color); + + bool operator==(const QColor &c) const; + bool operator!=(const QColor &c) const; + + operator QVariant() const; + +#ifdef Q_WS_X11 + static bool allowX11ColorNames(); + static void setAllowX11ColorNames(bool enabled); +#endif + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT_CONSTRUCTOR QColor(int x, int y, int z, Spec colorSpec) + { if (colorSpec == Hsv) setHsv(x, y, z); else setRgb(x, y, z); } + + inline QT3_SUPPORT void rgb(int *r, int *g, int *b) const + { getRgb(r, g, b); } + inline QT3_SUPPORT void hsv(int *h, int *s, int *v) const + { getHsv(h, s, v); } + + inline QT3_SUPPORT void setRgba(int r, int g, int b, int a) + { setRgb(r, g, b, a); } + inline QT3_SUPPORT void getRgba(int *r, int *g, int *b, int *a) const + { getRgb(r, g, b, a); } + + QT3_SUPPORT uint pixel(int screen = -1) const; +#endif + + static bool isValidColor(const QString &name); + +private: +#ifndef QT3_SUPPORT + // do not allow a spec to be used as an alpha value + QColor(int, int, int, Spec); +#endif + + void invalidate(); + bool setColorFromString(const QString &name); + + Spec cspec; + union { + struct { + ushort alpha; + ushort red; + ushort green; + ushort blue; + ushort pad; + } argb; + struct { + ushort alpha; + ushort hue; + ushort saturation; + ushort value; + ushort pad; + } ahsv; + struct { + ushort alpha; + ushort cyan; + ushort magenta; + ushort yellow; + ushort black; + } acmyk; + struct { + ushort alpha; + ushort hue; + ushort saturation; + ushort lightness; + ushort pad; + } ahsl; + ushort array[5]; + } ct; + + friend class QColormap; +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QColor &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QColor &); +#endif +}; + +inline QColor::QColor() +{ invalidate(); } + +inline QColor::QColor(int r, int g, int b, int a) +{ setRgb(r, g, b, a); } + +inline QColor::QColor(const char *aname) +{ setNamedColor(QLatin1String(aname)); } + +inline QColor::QColor(const QString& aname) +{ setNamedColor(aname); } + +inline QColor::QColor(const QColor &acolor) + : cspec(acolor.cspec) +{ ct.argb = acolor.ct.argb; } + +inline bool QColor::isValid() const +{ return cspec != Invalid; } + +inline QColor QColor::lighter(int f) const +{ return light(f); } + +inline QColor QColor::darker(int f) const +{ return dark(f); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLOR_H diff --git a/src/gui/painting/qcolor_p.cpp b/src/gui/painting/qcolor_p.cpp new file mode 100644 index 0000000000..38fed7146d --- /dev/null +++ b/src/gui/painting/qcolor_p.cpp @@ -0,0 +1,370 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#if defined(Q_CC_BOR) +// needed for qsort() because of a std namespace problem on Borland +#include "qplatformdefs.h" +#endif + +#include "qrgb.h" +#include "qstringlist.h" + +QT_BEGIN_NAMESPACE + +static inline int h2i(char hex) +{ + if (hex >= '0' && hex <= '9') + return hex - '0'; + if (hex >= 'a' && hex <= 'f') + return hex - 'a' + 10; + if (hex >= 'A' && hex <= 'F') + return hex - 'A' + 10; + return -1; +} + +static inline int hex2int(const char *s) +{ + return (h2i(s[0]) << 4) | h2i(s[1]); +} + +static inline int hex2int(char s) +{ + int h = h2i(s); + return (h << 4) | h; +} + +bool qt_get_hex_rgb(const char *name, QRgb *rgb) +{ + if(name[0] != '#') + return false; + name++; + int len = qstrlen(name); + int r, g, b; + if (len == 12) { + r = hex2int(name); + g = hex2int(name + 4); + b = hex2int(name + 8); + } else if (len == 9) { + r = hex2int(name); + g = hex2int(name + 3); + b = hex2int(name + 6); + } else if (len == 6) { + r = hex2int(name); + g = hex2int(name + 2); + b = hex2int(name + 4); + } else if (len == 3) { + r = hex2int(name[0]); + g = hex2int(name[1]); + b = hex2int(name[2]); + } else { + r = g = b = -1; + } + if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) { + *rgb = 0; + return false; + } + *rgb = qRgb(r, g ,b); + return true; +} + +bool qt_get_hex_rgb(const QChar *str, int len, QRgb *rgb) +{ + if (len > 13) + return false; + char tmp[16]; + for(int i = 0; i < len; ++i) + tmp[i] = str[i].toLatin1(); + tmp[len] = 0; + return qt_get_hex_rgb(tmp, rgb); +} + +#ifndef QT_NO_COLORNAMES + +/* + CSS color names = SVG 1.0 color names + transparent (rgba(0,0,0,0)) +*/ + +#ifdef rgb +# undef rgb +#endif +#define rgb(r,g,b) (0xff000000 | (r << 16) | (g << 8) | b) + +static const struct RGBData { + const char *name; + uint value; +} rgbTbl[] = { + { "aliceblue", rgb(240, 248, 255) }, + { "antiquewhite", rgb(250, 235, 215) }, + { "aqua", rgb( 0, 255, 255) }, + { "aquamarine", rgb(127, 255, 212) }, + { "azure", rgb(240, 255, 255) }, + { "beige", rgb(245, 245, 220) }, + { "bisque", rgb(255, 228, 196) }, + { "black", rgb( 0, 0, 0) }, + { "blanchedalmond", rgb(255, 235, 205) }, + { "blue", rgb( 0, 0, 255) }, + { "blueviolet", rgb(138, 43, 226) }, + { "brown", rgb(165, 42, 42) }, + { "burlywood", rgb(222, 184, 135) }, + { "cadetblue", rgb( 95, 158, 160) }, + { "chartreuse", rgb(127, 255, 0) }, + { "chocolate", rgb(210, 105, 30) }, + { "coral", rgb(255, 127, 80) }, + { "cornflowerblue", rgb(100, 149, 237) }, + { "cornsilk", rgb(255, 248, 220) }, + { "crimson", rgb(220, 20, 60) }, + { "cyan", rgb( 0, 255, 255) }, + { "darkblue", rgb( 0, 0, 139) }, + { "darkcyan", rgb( 0, 139, 139) }, + { "darkgoldenrod", rgb(184, 134, 11) }, + { "darkgray", rgb(169, 169, 169) }, + { "darkgreen", rgb( 0, 100, 0) }, + { "darkgrey", rgb(169, 169, 169) }, + { "darkkhaki", rgb(189, 183, 107) }, + { "darkmagenta", rgb(139, 0, 139) }, + { "darkolivegreen", rgb( 85, 107, 47) }, + { "darkorange", rgb(255, 140, 0) }, + { "darkorchid", rgb(153, 50, 204) }, + { "darkred", rgb(139, 0, 0) }, + { "darksalmon", rgb(233, 150, 122) }, + { "darkseagreen", rgb(143, 188, 143) }, + { "darkslateblue", rgb( 72, 61, 139) }, + { "darkslategray", rgb( 47, 79, 79) }, + { "darkslategrey", rgb( 47, 79, 79) }, + { "darkturquoise", rgb( 0, 206, 209) }, + { "darkviolet", rgb(148, 0, 211) }, + { "deeppink", rgb(255, 20, 147) }, + { "deepskyblue", rgb( 0, 191, 255) }, + { "dimgray", rgb(105, 105, 105) }, + { "dimgrey", rgb(105, 105, 105) }, + { "dodgerblue", rgb( 30, 144, 255) }, + { "firebrick", rgb(178, 34, 34) }, + { "floralwhite", rgb(255, 250, 240) }, + { "forestgreen", rgb( 34, 139, 34) }, + { "fuchsia", rgb(255, 0, 255) }, + { "gainsboro", rgb(220, 220, 220) }, + { "ghostwhite", rgb(248, 248, 255) }, + { "gold", rgb(255, 215, 0) }, + { "goldenrod", rgb(218, 165, 32) }, + { "gray", rgb(128, 128, 128) }, + { "green", rgb( 0, 128, 0) }, + { "greenyellow", rgb(173, 255, 47) }, + { "grey", rgb(128, 128, 128) }, + { "honeydew", rgb(240, 255, 240) }, + { "hotpink", rgb(255, 105, 180) }, + { "indianred", rgb(205, 92, 92) }, + { "indigo", rgb( 75, 0, 130) }, + { "ivory", rgb(255, 255, 240) }, + { "khaki", rgb(240, 230, 140) }, + { "lavender", rgb(230, 230, 250) }, + { "lavenderblush", rgb(255, 240, 245) }, + { "lawngreen", rgb(124, 252, 0) }, + { "lemonchiffon", rgb(255, 250, 205) }, + { "lightblue", rgb(173, 216, 230) }, + { "lightcoral", rgb(240, 128, 128) }, + { "lightcyan", rgb(224, 255, 255) }, + { "lightgoldenrodyellow", rgb(250, 250, 210) }, + { "lightgray", rgb(211, 211, 211) }, + { "lightgreen", rgb(144, 238, 144) }, + { "lightgrey", rgb(211, 211, 211) }, + { "lightpink", rgb(255, 182, 193) }, + { "lightsalmon", rgb(255, 160, 122) }, + { "lightseagreen", rgb( 32, 178, 170) }, + { "lightskyblue", rgb(135, 206, 250) }, + { "lightslategray", rgb(119, 136, 153) }, + { "lightslategrey", rgb(119, 136, 153) }, + { "lightsteelblue", rgb(176, 196, 222) }, + { "lightyellow", rgb(255, 255, 224) }, + { "lime", rgb( 0, 255, 0) }, + { "limegreen", rgb( 50, 205, 50) }, + { "linen", rgb(250, 240, 230) }, + { "magenta", rgb(255, 0, 255) }, + { "maroon", rgb(128, 0, 0) }, + { "mediumaquamarine", rgb(102, 205, 170) }, + { "mediumblue", rgb( 0, 0, 205) }, + { "mediumorchid", rgb(186, 85, 211) }, + { "mediumpurple", rgb(147, 112, 219) }, + { "mediumseagreen", rgb( 60, 179, 113) }, + { "mediumslateblue", rgb(123, 104, 238) }, + { "mediumspringgreen", rgb( 0, 250, 154) }, + { "mediumturquoise", rgb( 72, 209, 204) }, + { "mediumvioletred", rgb(199, 21, 133) }, + { "midnightblue", rgb( 25, 25, 112) }, + { "mintcream", rgb(245, 255, 250) }, + { "mistyrose", rgb(255, 228, 225) }, + { "moccasin", rgb(255, 228, 181) }, + { "navajowhite", rgb(255, 222, 173) }, + { "navy", rgb( 0, 0, 128) }, + { "oldlace", rgb(253, 245, 230) }, + { "olive", rgb(128, 128, 0) }, + { "olivedrab", rgb(107, 142, 35) }, + { "orange", rgb(255, 165, 0) }, + { "orangered", rgb(255, 69, 0) }, + { "orchid", rgb(218, 112, 214) }, + { "palegoldenrod", rgb(238, 232, 170) }, + { "palegreen", rgb(152, 251, 152) }, + { "paleturquoise", rgb(175, 238, 238) }, + { "palevioletred", rgb(219, 112, 147) }, + { "papayawhip", rgb(255, 239, 213) }, + { "peachpuff", rgb(255, 218, 185) }, + { "peru", rgb(205, 133, 63) }, + { "pink", rgb(255, 192, 203) }, + { "plum", rgb(221, 160, 221) }, + { "powderblue", rgb(176, 224, 230) }, + { "purple", rgb(128, 0, 128) }, + { "red", rgb(255, 0, 0) }, + { "rosybrown", rgb(188, 143, 143) }, + { "royalblue", rgb( 65, 105, 225) }, + { "saddlebrown", rgb(139, 69, 19) }, + { "salmon", rgb(250, 128, 114) }, + { "sandybrown", rgb(244, 164, 96) }, + { "seagreen", rgb( 46, 139, 87) }, + { "seashell", rgb(255, 245, 238) }, + { "sienna", rgb(160, 82, 45) }, + { "silver", rgb(192, 192, 192) }, + { "skyblue", rgb(135, 206, 235) }, + { "slateblue", rgb(106, 90, 205) }, + { "slategray", rgb(112, 128, 144) }, + { "slategrey", rgb(112, 128, 144) }, + { "snow", rgb(255, 250, 250) }, + { "springgreen", rgb( 0, 255, 127) }, + { "steelblue", rgb( 70, 130, 180) }, + { "tan", rgb(210, 180, 140) }, + { "teal", rgb( 0, 128, 128) }, + { "thistle", rgb(216, 191, 216) }, + { "tomato", rgb(255, 99, 71) }, + { "transparent", 0 }, + { "turquoise", rgb( 64, 224, 208) }, + { "violet", rgb(238, 130, 238) }, + { "wheat", rgb(245, 222, 179) }, + { "white", rgb(255, 255, 255) }, + { "whitesmoke", rgb(245, 245, 245) }, + { "yellow", rgb(255, 255, 0) }, + { "yellowgreen", rgb(154, 205, 50) } +}; + +static const int rgbTblSize = sizeof(rgbTbl) / sizeof(RGBData); + +#undef rgb + +inline bool operator<(const char *name, const RGBData &data) +{ return qstrcmp(name, data.name) < 0; } +inline bool operator<(const RGBData &data, const char *name) +{ return qstrcmp(data.name, name) < 0; } + +static bool get_named_rgb(const char *name_no_space, QRgb *rgb) +{ + QByteArray name = QByteArray(name_no_space).toLower(); + const RGBData *r = qBinaryFind(rgbTbl, rgbTbl + rgbTblSize, name.constData()); + if (r != rgbTbl + rgbTblSize) { + *rgb = r->value; + return true; + } + return false; +} + +bool qt_get_named_rgb(const char *name, QRgb* rgb) +{ + int len = int(strlen(name)); + if(len > 255) + return false; + char name_no_space[256]; + int pos = 0; + for(int i = 0; i < len; i++) { + if(name[i] != '\t' && name[i] != ' ') + name_no_space[pos++] = name[i]; + } + name_no_space[pos] = 0; + + return get_named_rgb(name_no_space, rgb); +} + +bool qt_get_named_rgb(const QChar *name, int len, QRgb *rgb) +{ + if(len > 255) + return false; + char name_no_space[256]; + int pos = 0; + for(int i = 0; i < len; i++) { + if(name[i] != QLatin1Char('\t') && name[i] != QLatin1Char(' ')) + name_no_space[pos++] = name[i].toLatin1(); + } + name_no_space[pos] = 0; + return get_named_rgb(name_no_space, rgb); +} + + +uint qt_get_rgb_val(const char *name) +{ + QRgb r = 0; + qt_get_named_rgb(name,&r); + return r; +} + +QStringList qt_get_colornames() +{ + int i = 0; + QStringList lst; + for (i = 0; i < rgbTblSize; i++) + lst << QLatin1String(rgbTbl[i].name); + return lst; +} + +#else + +bool qt_get_named_rgb(const char *, QRgb*) +{ + return false; +} + +uint qt_get_rgb_val(const char *) +{ + return 0; +} +QStringList qt_get_colornames() +{ + return QStringList(); +} +#endif // QT_NO_COLORNAMES + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolor_p.h b/src/gui/painting/qcolor_p.h new file mode 100644 index 0000000000..0698a7643c --- /dev/null +++ b/src/gui/painting/qcolor_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLOR_P_H +#define QCOLOR_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 "QtCore/qglobal.h" +#include "QtGui/qrgb.h" +#include "QtCore/qstringlist.h" + +QT_BEGIN_NAMESPACE + +uint qt_get_rgb_val(const char *name); +bool qt_get_named_rgb(const char *, QRgb*); +bool qt_get_named_rgb(const QChar *, int len, QRgb*); +bool qt_get_hex_rgb(const char *, QRgb *); +bool qt_get_hex_rgb(const QChar *, int len, QRgb *); +QStringList qt_get_colornames(); + +QT_END_NAMESPACE + +#endif // QCOLOR_P_H diff --git a/src/gui/painting/qcolormap.h b/src/gui/painting/qcolormap.h new file mode 100644 index 0000000000..4d0521d622 --- /dev/null +++ b/src/gui/painting/qcolormap.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORMAP_H +#define QCOLORMAP_H + +#include <QtCore/qatomic.h> +#include <QtGui/qrgb.h> +#include <QtCore/qvector.h> +#include <QtGui/qwindowdefs.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QColor; +class QColormapPrivate; + +class Q_GUI_EXPORT QColormap +{ +public: + enum Mode { Direct, Indexed, Gray }; + + static void initialize(); + static void cleanup(); + + static QColormap instance(int screen = -1); + + QColormap(const QColormap &colormap); + ~QColormap(); + + QColormap &operator=(const QColormap &colormap); + + Mode mode() const; + + int depth() const; + int size() const; + + uint pixel(const QColor &color) const; + const QColor colorAt(uint pixel) const; + + const QVector<QColor> colormap() const; + +#ifdef Q_WS_WIN + static HPALETTE hPal(); +#endif + +private: + QColormap(); + QColormapPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOLORMAP_H diff --git a/src/gui/painting/qcolormap.qdoc b/src/gui/painting/qcolormap.qdoc new file mode 100644 index 0000000000..04ed407510 --- /dev/null +++ b/src/gui/painting/qcolormap.qdoc @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QColormap + \ingroup painting + + \brief The QColormap class maps device independent QColors to device + dependent pixel values. +*/ + +/*! \enum QColormap::Mode + + This enum describes how QColormap maps device independent RGB + values to device dependent pixel values. + + \value Direct Pixel values are derived directly from the RGB + values, also known as "True Color." + + \value Indexed Pixel values represent indexes into a vector of + available colors, i.e. QColormap uses the index of the color that + most closely matches an RGB value. + + \value Gray Similar to \c Indexed, pixel values represent a vector + of available gray tones. QColormap uses the index of the gray + tone that most closely matches the computed gray tone of an RGB + value. +*/ + +/*! + \fn QColormap QColormap::instance(int screen) + + Returns the colormap for the specified \a screen. If \a screen is + -1, this function returns the colormap for the default screen. +*/ + +/*! + \fn QColormap::QColormap(const QColormap &colormap) + + Constructs a copy of another \a colormap. +*/ + +/*! + \fn QColormap::~QColormap() + + Destroys the colormap. +*/ + +/*! + \fn int QColormap::size() const + + Returns the size of the colormap for \c Indexed and \c Gray modes; + Returns -1 for \c Direct mode. + + \sa colormap() +*/ + +/*! + \fn uint QColormap::pixel(const QColor &color) const + + Returns a device dependent pixel value for the \a color. + + \sa colorAt() +*/ + +/*! + \fn int QColormap::depth() const + + Returns the depth of the device. + + \sa size() +*/ + +/*! + \fn QColormap::Mode QColormap::mode() const + + Returns the mode of this colormap. + + \sa QColormap::Mode +*/ + +/*! + \fn const QColor QColormap::colorAt(uint pixel) const + + Returns a QColor for the \a pixel. + + \sa pixel() +*/ + +/*! + \fn const QVector<QColor> QColormap::colormap() const + + Returns a vector of colors which represents the devices colormap + for \c Indexed and \c Gray modes. This function returns an empty + vector for \c Direct mode. + + \sa size() +*/ + +/*! \fn HPALETTE QColormap::hPal() + + This function is only available on Windows. + + Returns an handle to the HPALETTE used by this colormap. If no + HPALETTE is being used, this function returns zero. +*/ + +/*! \since 4.2 + + \fn QColormap &QColormap::operator=(const QColormap &colormap) + + Assigns the given \a colormap to \e this color map and returns + a reference to \e this color map. +*/ + +/*! + \fn void QColormap::initialize() + \internal +*/ + +/*! + \fn void QColormap::cleanup() + \internal +*/ diff --git a/src/gui/painting/qcolormap_mac.cpp b/src/gui/painting/qcolormap_mac.cpp new file mode 100644 index 0000000000..28589f41b8 --- /dev/null +++ b/src/gui/painting/qcolormap_mac.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolormap.h" +#include "qcolor.h" + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + inline QColormapPrivate() + : ref(1) + { } + + QAtomicInt ref; +}; +static QColormap *qt_mac_global_map = 0; + +void QColormap::initialize() +{ + qt_mac_global_map = new QColormap; +} + +void QColormap::cleanup() +{ + delete qt_mac_global_map; + qt_mac_global_map = 0; +} + +QColormap QColormap::instance(int) +{ + return *qt_mac_global_map; +} + +QColormap::QColormap() : d(new QColormapPrivate) +{} + +QColormap::QColormap(const QColormap &colormap) :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) + delete d; +} + +QColormap::Mode QColormap::mode() const +{ return QColormap::Direct; } + +int QColormap::depth() const +{ + return 32; +} + +int QColormap::size() const +{ + return -1; +} + +uint QColormap::pixel(const QColor &color) const +{ return color.rgba(); } + +const QColor QColormap::colorAt(uint pixel) const +{ return QColor(pixel); } + +const QVector<QColor> QColormap::colormap() const +{ return QVector<QColor>(); } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolormap_qpa.cpp b/src/gui/painting/qcolormap_qpa.cpp new file mode 100644 index 0000000000..f66607bb05 --- /dev/null +++ b/src/gui/painting/qcolormap_qpa.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolormap.h" +#include "qcolor.h" +#include "qpaintdevice.h" +#include "private/qapplication_p.h" +#include "private/qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + inline QColormapPrivate() + : ref(1), mode(QColormap::Direct), depth(0), numcolors(0) + { } + + QAtomicInt ref; + + QColormap::Mode mode; + int depth; + int numcolors; +}; + +static QColormapPrivate *screenMap = 0; + +void QColormap::initialize() +{ + screenMap = new QColormapPrivate; + + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + QList<QPlatformScreen*> screens = pi->screens(); + + screenMap->depth = screens.at(0)->depth(); + if (screenMap->depth < 8) { + screenMap->mode = QColormap::Indexed; + screenMap->numcolors = 256; + } else { + screenMap->mode = QColormap::Direct; + screenMap->numcolors = -1; + } +} + +void QColormap::cleanup() +{ + delete screenMap; + screenMap = 0; +} + +QColormap QColormap::instance(int /*screen*/) +{ + return QColormap(); +} + +QColormap::QColormap() + : d(screenMap) +{ d->ref.ref(); } + +QColormap::QColormap(const QColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) + delete d; +} + +QColormap::Mode QColormap::mode() const +{ return d->mode; } + + +int QColormap::depth() const +{ return d->depth; } + + +int QColormap::size() const +{ + return d->numcolors; +} + +#ifndef QT_QWS_DEPTH16_RGB +#define QT_QWS_DEPTH16_RGB 565 +#endif +static const int qt_rbits = (QT_QWS_DEPTH16_RGB/100); +static const int qt_gbits = (QT_QWS_DEPTH16_RGB/10%10); +static const int qt_bbits = (QT_QWS_DEPTH16_RGB%10); +static const int qt_red_shift = qt_bbits+qt_gbits-(8-qt_rbits); +static const int qt_green_shift = qt_bbits-(8-qt_gbits); +static const int qt_neg_blue_shift = 8-qt_bbits; +static const int qt_blue_mask = (1<<qt_bbits)-1; +static const int qt_green_mask = (1<<(qt_gbits+qt_bbits))-(1<<qt_bbits); +static const int qt_red_mask = (1<<(qt_rbits+qt_gbits+qt_bbits))-(1<<(qt_gbits+qt_bbits)); + +static const int qt_red_rounding_shift = qt_red_shift + qt_rbits; +static const int qt_green_rounding_shift = qt_green_shift + qt_gbits; +static const int qt_blue_rounding_shift = qt_bbits - qt_neg_blue_shift; + +inline ushort qt_convRgbTo16(QRgb c) +{ + const int tr = qRed(c) << qt_red_shift; + const int tg = qGreen(c) << qt_green_shift; + const int tb = qBlue(c) >> qt_neg_blue_shift; + + return (tb & qt_blue_mask) | (tg & qt_green_mask) | (tr & qt_red_mask); +} + +inline QRgb qt_conv16ToRgb(ushort c) +{ + const int r=(c & qt_red_mask); + const int g=(c & qt_green_mask); + const int b=(c & qt_blue_mask); + const int tr = r >> qt_red_shift | r >> qt_red_rounding_shift; + const int tg = g >> qt_green_shift | g >> qt_green_rounding_shift; + const int tb = b << qt_neg_blue_shift | b >> qt_blue_rounding_shift; + + return qRgb(tr,tg,tb); +} + +uint QColormap::pixel(const QColor &color) const +{ + QRgb rgb = color.rgba(); + if (d->mode == QColormap::Direct) { + switch(d->depth) { + case 16: + return qt_convRgbTo16(rgb); + case 24: + case 32: + { + const int r = qRed(rgb); + const int g = qGreen(rgb); + const int b = qBlue(rgb); + const int red_shift = 16; + const int green_shift = 8; + const int red_mask = 0xff0000; + const int green_mask = 0x00ff00; + const int blue_mask = 0x0000ff; + const int tg = g << green_shift; +#ifdef QT_QWS_DEPTH_32_BGR + if (qt_screen->pixelType() == QScreen::BGRPixel) { + const int tb = b << red_shift; + return 0xff000000 | (r & blue_mask) | (tg & green_mask) | (tb & red_mask); + } +#endif + const int tr = r << red_shift; + return 0xff000000 | (b & blue_mask) | (tg & green_mask) | (tr & red_mask); + } + } + } + //XXX + //return qt_screen->alloc(qRed(rgb), qGreen(rgb), qBlue(rgb)); + return 0; +} + +const QColor QColormap::colorAt(uint pixel) const +{ + if (d->mode == Direct) { + if (d->depth == 16) { + pixel = qt_conv16ToRgb(pixel); + } + const int red_shift = 16; + const int green_shift = 8; + const int red_mask = 0xff0000; + const int green_mask = 0x00ff00; + const int blue_mask = 0x0000ff; +#ifdef QT_QWS_DEPTH_32_BGR + if (qt_screen->pixelType() == QScreen::BGRPixel) { + return QColor((pixel & blue_mask), + (pixel & green_mask) >> green_shift, + (pixel & red_mask) >> red_shift); + } +#endif + return QColor((pixel & red_mask) >> red_shift, + (pixel & green_mask) >> green_shift, + (pixel & blue_mask)); + } +#if 0 // XXX + Q_ASSERT_X(int(pixel) < qt_screen->numCols(), "QColormap::colorAt", "pixel out of bounds of palette"); + return QColor(qt_screen->clut()[pixel]); +#endif + return QColor(); +} + +const QVector<QColor> QColormap::colormap() const +{ + return QVector<QColor>(); +} + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolormap_qws.cpp b/src/gui/painting/qcolormap_qws.cpp new file mode 100644 index 0000000000..5c0933d6eb --- /dev/null +++ b/src/gui/painting/qcolormap_qws.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolormap.h" +#include "qcolor.h" +#include "qpaintdevice.h" +#include "qscreen_qws.h" +#include "qwsdisplay_qws.h" + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + inline QColormapPrivate() + : ref(1), mode(QColormap::Direct), depth(0), numcolors(0) + { } + + QAtomicInt ref; + + QColormap::Mode mode; + int depth; + int numcolors; +}; + +static QColormapPrivate *screenMap = 0; + +void QColormap::initialize() +{ + screenMap = new QColormapPrivate; + + screenMap->depth = QPaintDevice::qwsDisplay()->depth(); + if (screenMap->depth < 8) { + screenMap->mode = QColormap::Indexed; + screenMap->numcolors = 256; + } else { + screenMap->mode = QColormap::Direct; + screenMap->numcolors = -1; + } +} + +void QColormap::cleanup() +{ + delete screenMap; + screenMap = 0; +} + +QColormap QColormap::instance(int /*screen*/) +{ + return QColormap(); +} + +QColormap::QColormap() + : d(screenMap) +{ d->ref.ref(); } + +QColormap::QColormap(const QColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) + delete d; +} + +QColormap::Mode QColormap::mode() const +{ return d->mode; } + + +int QColormap::depth() const +{ return d->depth; } + + +int QColormap::size() const +{ + return d->numcolors; +} + +uint QColormap::pixel(const QColor &color) const +{ + QRgb rgb = color.rgba(); + if (d->mode == QColormap::Direct) { + switch(d->depth) { + case 16: + return qt_convRgbTo16(rgb); + case 24: + case 32: + { + const int r = qRed(rgb); + const int g = qGreen(rgb); + const int b = qBlue(rgb); + const int red_shift = 16; + const int green_shift = 8; + const int red_mask = 0xff0000; + const int green_mask = 0x00ff00; + const int blue_mask = 0x0000ff; + const int tg = g << green_shift; +#ifdef QT_QWS_DEPTH_32_BGR + if (qt_screen->pixelType() == QScreen::BGRPixel) { + const int tb = b << red_shift; + return 0xff000000 | (r & blue_mask) | (tg & green_mask) | (tb & red_mask); + } +#endif + const int tr = r << red_shift; + return 0xff000000 | (b & blue_mask) | (tg & green_mask) | (tr & red_mask); + } + } + } + return qt_screen->alloc(qRed(rgb), qGreen(rgb), qBlue(rgb)); +} + +const QColor QColormap::colorAt(uint pixel) const +{ + if (d->mode == Direct) { + if (d->depth == 16) { + pixel = qt_conv16ToRgb(pixel); + } + const int red_shift = 16; + const int green_shift = 8; + const int red_mask = 0xff0000; + const int green_mask = 0x00ff00; + const int blue_mask = 0x0000ff; +#ifdef QT_QWS_DEPTH_32_BGR + if (qt_screen->pixelType() == QScreen::BGRPixel) { + return QColor((pixel & blue_mask), + (pixel & green_mask) >> green_shift, + (pixel & red_mask) >> red_shift); + } +#endif + return QColor((pixel & red_mask) >> red_shift, + (pixel & green_mask) >> green_shift, + (pixel & blue_mask)); + } + Q_ASSERT_X(int(pixel) < qt_screen->colorCount(), "QColormap::colorAt", "pixel out of bounds of palette"); + return QColor(qt_screen->clut()[pixel]); +} + +const QVector<QColor> QColormap::colormap() const +{ + return QVector<QColor>(); +} + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolormap_s60.cpp b/src/gui/painting/qcolormap_s60.cpp new file mode 100644 index 0000000000..2c634db8a5 --- /dev/null +++ b/src/gui/painting/qcolormap_s60.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolormap.h" +#include "qcolor.h" + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + inline QColormapPrivate() + : ref(1) + { } + + QAtomicInt ref; +}; + +void QColormap::initialize() +{ +} + +void QColormap::cleanup() +{ +} + +QColormap QColormap::instance(int) +{ + return QColormap(); +} + +QColormap::QColormap() : d(new QColormapPrivate) +{} + +QColormap::QColormap(const QColormap &colormap) :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) + delete d; +} + +QColormap::Mode QColormap::mode() const +{ return QColormap::Direct; } + +int QColormap::depth() const +{ + return 32; +} + +int QColormap::size() const +{ + return -1; +} + +uint QColormap::pixel(const QColor &color) const +{ return color.rgba(); } + +const QColor QColormap::colorAt(uint pixel) const +{ return QColor(pixel); } + +const QVector<QColor> QColormap::colormap() const +{ return QVector<QColor>(); } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolormap_win.cpp b/src/gui/painting/qcolormap_win.cpp new file mode 100644 index 0000000000..1773f717c0 --- /dev/null +++ b/src/gui/painting/qcolormap_win.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolor.h" +#include "qcolormap.h" +#include "qvector.h" +#include "qt_windows.h" + +#if defined(Q_WS_WINCE) +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + inline QColormapPrivate() + : ref(1), mode(QColormap::Direct), depth(0), hpal(0) + { } + + QAtomicInt ref; + + QColormap::Mode mode; + int depth; + int numcolors; + + HPALETTE hpal; + QVector<QColor> palette; +}; + +static QColormapPrivate *screenMap = 0; + +void QColormap::initialize() +{ + HDC dc = qt_win_display_dc(); + + screenMap = new QColormapPrivate; + screenMap->depth = GetDeviceCaps(dc, BITSPIXEL); + + screenMap->numcolors = -1; + if (GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE) + screenMap->numcolors = GetDeviceCaps(dc, SIZEPALETTE); + + if (screenMap->numcolors <= 16 || screenMap->numcolors > 256) // no need to create palette + return; + + LOGPALETTE* pal = 0; + int numPalEntries = 6*6*6; // color cube + + pal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + numPalEntries * sizeof(PALETTEENTRY)); + // Make 6x6x6 color cube + int idx = 0; + for(int ir = 0x0; ir <= 0xff; ir+=0x33) { + for(int ig = 0x0; ig <= 0xff; ig+=0x33) { + for(int ib = 0x0; ib <= 0xff; ib+=0x33) { + pal->palPalEntry[idx].peRed = ir; + pal->palPalEntry[idx].peGreen = ig; + pal->palPalEntry[idx].peBlue = ib; + pal->palPalEntry[idx].peFlags = 0; + idx++; + } + } + } + + pal->palVersion = 0x300; + pal->palNumEntries = numPalEntries; + + screenMap->hpal = CreatePalette(pal); + if (!screenMap->hpal) + qErrnoWarning("QColor::initialize: Failed to create logical palette"); + free (pal); + + SelectPalette(dc, screenMap->hpal, FALSE); + RealizePalette(dc); + + PALETTEENTRY paletteEntries[256]; + screenMap->numcolors = GetPaletteEntries(screenMap->hpal, 0, 255, paletteEntries); + + screenMap->palette.resize(screenMap->numcolors); + for (int i = 0; i < screenMap->numcolors; i++) { + screenMap->palette[i] = qRgb(paletteEntries[i].peRed, + paletteEntries[i].peGreen, + paletteEntries[i].peBlue); + } +} + +void QColormap::cleanup() +{ + if (!screenMap) + return; + + if (screenMap->hpal) { // delete application global + DeleteObject(screenMap->hpal); // palette + screenMap->hpal = 0; + } + + delete screenMap; + screenMap = 0; +} + +QColormap QColormap::instance(int) +{ + Q_ASSERT_X(screenMap, "QColormap", + "A QApplication object needs to be constructed before QColormap is used."); + return QColormap(); +} + +QColormap::QColormap() + : d(screenMap) +{ d->ref.ref(); } + +QColormap::QColormap(const QColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) + delete d; +} + +QColormap::Mode QColormap::mode() const +{ return d->mode; } + +int QColormap::depth() const +{ return d->depth; } + +int QColormap::size() const +{ return d->numcolors; } + +uint QColormap::pixel(const QColor &color) const +{ + const QColor c = color.toRgb(); + COLORREF rgb = RGB(c.red(), c.green(), c.blue()); + if (d->hpal) + return PALETTEINDEX(GetNearestPaletteIndex(d->hpal, rgb)); + return rgb; +} + +const QColor QColormap::colorAt(uint pixel) const +{ + if (d->hpal) { + if (pixel < uint(d->numcolors)) + return d->palette.at(pixel); + return QColor(); + } + return QColor(GetRValue(pixel), GetGValue(pixel), GetBValue(pixel)); +} + + +HPALETTE QColormap::hPal() +{ return screenMap ? screenMap->hpal : 0; } + + +const QVector<QColor> QColormap::colormap() const +{ return d->palette; } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcolormap_x11.cpp b/src/gui/painting/qcolormap_x11.cpp new file mode 100644 index 0000000000..05eefa455b --- /dev/null +++ b/src/gui/painting/qcolormap_x11.cpp @@ -0,0 +1,670 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcolormap.h" + +#include "qapplication.h" +#include "qdebug.h" +#include "qdesktopwidget.h" +#include "qvarlengtharray.h" + +#include "qx11info_x11.h" +#include <private/qt_x11_p.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QColormapPrivate +{ +public: + QColormapPrivate() + : ref(1), mode(QColormap::Direct), depth(0), + colormap(0), defaultColormap(true), + visual(0), defaultVisual(true), + r_max(0), g_max(0), b_max(0), + r_shift(0), g_shift(0), b_shift(0) + {} + + QAtomicInt ref; + + QColormap::Mode mode; + int depth; + + Colormap colormap; + bool defaultColormap; + + Visual *visual; + bool defaultVisual; + + int r_max; + int g_max; + int b_max; + + uint r_shift; + uint g_shift; + uint b_shift; + + QVector<QColor> colors; + QVector<int> pixels; +}; + + +static uint right_align(uint v) +{ + while (!(v & 0x1)) + v >>= 1; + return v; +} + +static int lowest_bit(uint v) +{ + int i; + uint b = 1u; + for (i = 0; ((v & b) == 0u) && i < 32; ++i) + b <<= 1u; + return i == 32 ? -1 : i; +} + +static int cube_root(int v) +{ + if (v == 1) + return 1; + // brute force algorithm + int i = 1; + for (;;) { + const int b = i * i * i; + if (b <= v) { + ++i; + } else { + --i; + break; + } + } + return i; +} + +static Visual *find_visual(Display *display, + int screen, + int visual_class, + int visual_id, + int *depth, + bool *defaultVisual) +{ + XVisualInfo *vi, rvi; + int count; + + uint mask = VisualScreenMask; + rvi.screen = screen; + + if (visual_class != -1) { + rvi.c_class = visual_class; + mask |= VisualClassMask; + } + if (visual_id != -1) { + rvi.visualid = visual_id; + mask |= VisualIDMask; + } + + Visual *visual = DefaultVisual(display, screen); + *defaultVisual = true; + *depth = DefaultDepth(display, screen); + + vi = XGetVisualInfo(display, mask, &rvi, &count); + if (vi) { + int best = 0; + for (int x = 0; x < count; ++x) { + if (vi[x].depth > vi[best].depth) + best = x; + } + if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) { + visual = vi[best].visual; + *defaultVisual = (visual == DefaultVisual(display, screen)); + *depth = vi[best].depth; + } + } + if (vi) + XFree((char *)vi); + return visual; +} + +static void query_colormap(QColormapPrivate *d, int screen) +{ + Display *display = QX11Info::display(); + + // query existing colormap + int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth)); + XColor queried[256]; + memset(queried, 0, sizeof(queried)); + for (int x = 0; x < q_colors; ++x) + queried[x].pixel = x; + XQueryColors(display, d->colormap, queried, q_colors); + + d->colors.resize(q_colors); + for (int x = 0; x < q_colors; ++x) { + if (queried[x].red == 0 + && queried[x].green == 0 + && queried[x].blue == 0 + && queried[x].pixel != BlackPixel(display, screen)) { + // unallocated color cell, skip it + continue; + } + + d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX), + queried[x].green / float(USHRT_MAX), + queried[x].blue / float(USHRT_MAX)); + } + + // for missing colors, find the closest color in the existing colormap + Q_ASSERT(d->pixels.size()); + for (int x = 0; x < d->pixels.size(); ++x) { + if (d->pixels.at(x) != -1) + continue; + + QRgb rgb; + if (d->mode == QColormap::Indexed) { + const int r = (x / (d->g_max * d->b_max)) % d->r_max; + const int g = (x / d->b_max) % d->g_max; + const int b = x % d->b_max; + rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + } else { + rgb = qRgb(x, x, x); + } + + // find closest color + int mindist = INT_MAX, best = -1; + for (int y = 0; y < q_colors; ++y) { + int r = qRed(rgb) - (queried[y].red >> 8); + int g = qGreen(rgb) - (queried[y].green >> 8); + int b = qBlue(rgb) - (queried[y].blue >> 8); + int dist = (r * r) + (g * g) + (b * b); + if (dist < mindist) { + mindist = dist; + best = y; + } + } + + Q_ASSERT(best >= 0 && best < q_colors); + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = queried[best].red; + xcolor.green = queried[best].green; + xcolor.blue = queried[best].blue; + xcolor.pixel = queried[best].pixel; + + if (XAllocColor(display, d->colormap, &xcolor)) { + d->pixels[x] = xcolor.pixel; + } else { + // some weird stuff is going on... + d->pixels[x] = (qGray(rgb) < 127 + ? BlackPixel(display, screen) + : WhitePixel(display, screen)); + } + } else { + d->pixels[x] = best; + } + } +} + +static void init_gray(QColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max); + + for (int g = 0; g < d->g_max; ++g) { + const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1); + const QRgb rgb = qRgb(gray, gray, gray); + + d->pixels[g] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(QX11Info::display(), d->colormap, &xcolor)) + d->pixels[g] = xcolor.pixel; + } + } + + query_colormap(d, screen); +} + +static void init_indexed(QColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max * d->g_max * d->b_max); + + // create color cube + for (int x = 0, r = 0; r < d->r_max; ++r) { + for (int g = 0; g < d->g_max; ++g) { + for (int b = 0; b < d->b_max; ++b, ++x) { + const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + + d->pixels[x] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(QX11Info::display(), d->colormap, &xcolor)) + d->pixels[x] = xcolor.pixel; + } + } + } + } + + query_colormap(d, screen); +} + +static void init_direct(QColormapPrivate *d, bool ownColormap) +{ + if (d->visual->c_class != DirectColor || !ownColormap) + return; + + // preallocate 768 on the stack, so that we don't have to malloc + // for the common case (<= 24 bpp) + QVarLengthArray<XColor, 768> colorTable(d->r_max + d->g_max + d->b_max); + int i = 0; + + for (int r = 0; r < d->r_max; ++r) { + colorTable[i].red = r << 8 | r; + colorTable[i].pixel = r << d->r_shift; + colorTable[i].flags = DoRed; + ++i; + } + + for (int g = 0; g < d->g_max; ++g) { + colorTable[i].green = g << 8 | g; + colorTable[i].pixel = g << d->g_shift; + colorTable[i].flags = DoGreen; + ++i; + } + + for (int b = 0; b < d->b_max; ++b) { + colorTable[i].blue = (b << 8 | b); + colorTable[i].pixel = b << d->b_shift; + colorTable[i].flags = DoBlue; + ++i; + } + + XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count()); +} + +static QColormap **cmaps = 0; + +void QColormap::initialize() +{ + Display *display = QX11Info::display(); + const int screens = ScreenCount(display); + + cmaps = new QColormap*[screens]; + + for (int i = 0; i < screens; ++i) { + cmaps[i] = new QColormap; + QColormapPrivate * const d = cmaps[i]->d; + + bool use_stdcmap = false; + int color_count = X11->color_count; + + // defaults + d->depth = DefaultDepth(display, i); + d->colormap = DefaultColormap(display, i); + d->defaultColormap = true; + d->visual = DefaultVisual(display, i); + d->defaultVisual = true; + + Visual *argbVisual = 0; + + if (X11->visual && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->visual = find_visual(display, i, X11->visual->c_class, + XVisualIDFromVisual(X11->visual), + &d->depth, &d->defaultVisual); + } else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6) + || (X11->visual_id != -1)) { + // look for a specific visual or type of visual + d->visual = find_visual(display, i, X11->visual_class, X11->visual_id, + &d->depth, &d->defaultVisual); + } else if (QApplication::colorSpec() == QApplication::ManyColor) { + // look for a TrueColor w/ a depth higher than 8bpp + d->visual = find_visual(display, i, TrueColor, -1, &d->depth, &d->defaultVisual); + if (d->depth <= 8) { + d->visual = DefaultVisual(display, i); + d->defaultVisual = true; + color_count = 216; + } + } else if (!X11->custom_cmap) { + XStandardColormap *stdcmap = 0; + int ncmaps = 0; + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + int nvi; + XVisualInfo templ; + templ.screen = i; + templ.depth = 32; + templ.c_class = TrueColor; + XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask | + VisualDepthMask | + VisualClassMask, &templ, &nvi); + for (int idx = 0; idx < nvi; ++idx) { + XRenderPictFormat *format = XRenderFindVisualFormat(X11->display, + xvi[idx].visual); + if (format->type == PictTypeDirect && format->direct.alphaMask) { + argbVisual = xvi[idx].visual; + break; + } + } + XFree(xvi); + } +#endif + if (XGetRGBColormaps(display, RootWindow(display, i), + &stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) { + if (stdcmap) { + for (int c = 0; c < ncmaps; ++c) { + if (!stdcmap[c].red_max || + !stdcmap[c].green_max || + !stdcmap[c].blue_max || + !stdcmap[c].red_mult || + !stdcmap[c].green_mult || + !stdcmap[c].blue_mult) + continue; // invalid stdcmap + + XVisualInfo proto; + proto.visualid = stdcmap[c].visualid; + proto.screen = i; + + int nvisuals = 0; + XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask, + &proto, &nvisuals); + if (vi) { + if (nvisuals > 0) { + use_stdcmap = true; + + d->mode = ((vi[0].visual->c_class < StaticColor) + ? Gray + : ((vi[0].visual->c_class < TrueColor) + ? Indexed + : Direct)); + + d->depth = vi[0].depth; + d->colormap = stdcmap[c].colormap; + d->defaultColormap = true; + d->visual = vi[0].visual; + d->defaultVisual = (d->visual == DefaultVisual(display, i)); + + d->r_max = stdcmap[c].red_max + 1; + d->g_max = stdcmap[c].green_max + 1; + d->b_max = stdcmap[c].blue_max + 1; + + if (d->mode == Direct) { + // calculate offsets + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + } else { + d->r_shift = 0; + d->g_shift = 0; + d->b_shift = 0; + } + } + XFree(vi); + } + break; + } + XFree(stdcmap); + } + } + } + if (!use_stdcmap) { + switch (d->visual->c_class) { + case StaticGray: + d->mode = Gray; + + d->r_max = d->g_max = d->b_max = d->visual->map_entries; + break; + + case XGrayScale: + d->mode = Gray; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = color_count; + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 4096; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 512; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = 12; + else + d->r_max = d->g_max = d->b_max = 4; + break; + + case StaticColor: + d->mode = Indexed; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + break; + + case PseudoColor: + d->mode = Indexed; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = cube_root(color_count); + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 27; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 12; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125); + else + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries); + break; + + case TrueColor: + case DirectColor: + d->mode = Direct; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + break; + } + } + + bool ownColormap = false; + if (X11->colormap && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->colormap = X11->colormap; + d->defaultColormap = (d->colormap == DefaultColormap(display, i)); + } else if ((!use_stdcmap + && (((d->visual->c_class & 1) && X11->custom_cmap) + || d->visual != DefaultVisual(display, i))) + || d->visual->c_class == DirectColor) { + // allocate custom colormap (we always do this when using DirectColor visuals) + d->colormap = + XCreateColormap(display, RootWindow(display, i), d->visual, + d->visual->c_class == DirectColor ? AllocAll : AllocNone); + d->defaultColormap = false; + ownColormap = true; + } + + switch (d->mode) { + case Gray: + init_gray(d, i); + break; + case Indexed: + init_indexed(d, i); + break; + case Direct: + init_direct(d, ownColormap); + break; + } + + QX11InfoData *screen = X11->screens + i; + screen->depth = d->depth; + screen->visual = d->visual; + screen->defaultVisual = d->defaultVisual; + screen->colormap = d->colormap; + screen->defaultColormap = d->defaultColormap; + screen->cells = screen->visual->map_entries; + + if (argbVisual) { + X11->argbVisuals[i] = argbVisual; + X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone); + } + + // ### + // We assume that 8bpp == pseudocolor, but this is not + // always the case (according to the X server), so we need + // to make sure that our internal data is setup in a way + // that is compatible with our assumptions + if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8) + screen->cells = 256; + } +} + +void QColormap::cleanup() +{ + Display *display = QX11Info::display(); + const int screens = ScreenCount(display); + + for (int i = 0; i < screens; ++i) + delete cmaps[i]; + + delete [] cmaps; + cmaps = 0; +} + + +QColormap QColormap::instance(int screen) +{ + if (screen == -1) + screen = QX11Info::appScreen(); + return *cmaps[screen]; +} + +/*! \internal + Constructs a new colormap. +*/ +QColormap::QColormap() + : d(new QColormapPrivate) +{} + +QColormap::QColormap(const QColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QColormap::~QColormap() +{ + if (!d->ref.deref()) { + if (!d->defaultColormap) + XFreeColormap(QX11Info::display(), d->colormap); + delete d; + } +} + +QColormap::Mode QColormap::mode() const +{ return d->mode; } + +int QColormap::depth() const +{ return d->depth; } + +int QColormap::size() const +{ + return (d->mode == Gray + ? d->r_max + : (d->mode == Indexed + ? d->r_max * d->g_max * d->b_max + : -1)); +} + +uint QColormap::pixel(const QColor &color) const +{ + const QColor c = color.toRgb(); + const uint r = (c.ct.argb.red * d->r_max) >> 16; + const uint g = (c.ct.argb.green * d->g_max) >> 16; + const uint b = (c.ct.argb.blue * d->b_max) >> 16; + if (d->mode != Direct) { + if (d->mode == Gray) + return d->pixels.at((r * 30 + g * 59 + b * 11) / 100); + return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b); + } + return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift); +} + +const QColor QColormap::colorAt(uint pixel) const +{ + if (d->mode != Direct) { + Q_ASSERT(pixel <= (uint)d->colors.size()); + return d->colors.at(pixel); + } + + const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max; + const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max; + const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max; + return QColor(r, g, b); +} + +const QVector<QColor> QColormap::colormap() const +{ return d->colors; } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ + qAtomicAssign(d, colormap.d); + return *this; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcssutil.cpp b/src/gui/painting/qcssutil.cpp new file mode 100644 index 0000000000..729cff504e --- /dev/null +++ b/src/gui/painting/qcssutil.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcssutil_p.h" +#include "private/qcssparser_p.h" +#include "qpainter.h" +#include <qmath.h> + +#ifndef QT_NO_CSSPARSER + +QT_BEGIN_NAMESPACE + +using namespace QCss; + +static QPen qPenFromStyle(const QBrush& b, qreal width, BorderStyle s) +{ + Qt::PenStyle ps = Qt::NoPen; + + switch (s) { + case BorderStyle_Dotted: + ps = Qt::DotLine; + break; + case BorderStyle_Dashed: + ps = width == 1 ? Qt::DotLine : Qt::DashLine; + break; + case BorderStyle_DotDash: + ps = Qt::DashDotLine; + break; + case BorderStyle_DotDotDash: + ps = Qt::DashDotDotLine; + break; + case BorderStyle_Inset: + case BorderStyle_Outset: + case BorderStyle_Solid: + ps = Qt::SolidLine; + break; + default: + break; + } + + return QPen(b, width, ps, Qt::FlatCap); +} + +void qDrawRoundedCorners(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, + const QSizeF& r1, const QSizeF& r2, + Edge edge, BorderStyle s, QBrush c) +{ + const qreal pw = (edge == TopEdge || edge == BottomEdge) ? y2-y1 : x2-x1; + if (s == BorderStyle_Double) { + qreal wby3 = pw/3; + switch (edge) { + case TopEdge: + case BottomEdge: + qDrawRoundedCorners(p, x1, y1, x2, y1+wby3, r1, r2, edge, BorderStyle_Solid, c); + qDrawRoundedCorners(p, x1, y2-wby3, x2, y2, r1, r2, edge, BorderStyle_Solid, c); + break; + case LeftEdge: + qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, LeftEdge, BorderStyle_Solid, c); + qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, LeftEdge, BorderStyle_Solid, c); + break; + case RightEdge: + qDrawRoundedCorners(p, x1, y1+1, x1+wby3, y2, r1, r2, RightEdge, BorderStyle_Solid, c); + qDrawRoundedCorners(p, x2-wby3, y1+1, x2, y2, r1, r2, RightEdge, BorderStyle_Solid, c); + break; + default: + break; + } + return; + } else if (s == BorderStyle_Ridge || s == BorderStyle_Groove) { + BorderStyle s1, s2; + if (s == BorderStyle_Groove) { + s1 = BorderStyle_Inset; + s2 = BorderStyle_Outset; + } else { + s1 = BorderStyle_Outset; + s2 = BorderStyle_Inset; + } + int pwby2 = qRound(pw/2); + switch (edge) { + case TopEdge: + qDrawRoundedCorners(p, x1, y1, x2, y1 + pwby2, r1, r2, TopEdge, s1, c); + qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, TopEdge, s2, c); + break; + case BottomEdge: + qDrawRoundedCorners(p, x1, y1 + pwby2, x2, y2, r1, r2, BottomEdge, s1, c); + qDrawRoundedCorners(p, x1, y1, x2, y2-pwby2, r1, r2, BottomEdge, s2, c); + break; + case LeftEdge: + qDrawRoundedCorners(p, x1, y1, x1 + pwby2, y2, r1, r2, LeftEdge, s1, c); + qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, LeftEdge, s2, c); + break; + case RightEdge: + qDrawRoundedCorners(p, x1 + pwby2, y1, x2, y2, r1, r2, RightEdge, s1, c); + qDrawRoundedCorners(p, x1, y1, x2 - pwby2, y2, r1, r2, RightEdge, s2, c); + break; + default: + break; + } + } else if ((s == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge)) + || (s == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge))) + c = c.color().lighter(); + + p->save(); + qreal pwby2 = pw/2; + p->setBrush(Qt::NoBrush); + QPen pen = qPenFromStyle(c, pw, s); + pen.setCapStyle(Qt::SquareCap); // this eliminates the offby1 errors that we might hit below + p->setPen(pen); + switch (edge) { + case TopEdge: + if (!r1.isEmpty()) + p->drawArc(QRectF(x1 - r1.width() + pwby2, y1 + pwby2, + 2*r1.width() - pw, 2*r1.height() - pw), 135*16, -45*16); + if (!r2.isEmpty()) + p->drawArc(QRectF(x2 - r2.width() + pwby2, y1 + pwby2, + 2*r2.width() - pw, 2*r2.height() - pw), 45*16, 45*16); + break; + case BottomEdge: + if (!r1.isEmpty()) + p->drawArc(QRectF(x1 - r1.width() + pwby2, y2 - 2*r1.height() + pwby2, + 2*r1.width() - pw, 2*r1.height() - pw), -90 * 16, -45 * 16); + if (!r2.isEmpty()) + p->drawArc(QRectF(x2 - r2.width() + pwby2, y2 - 2*r2.height() + pwby2, + 2*r2.width() - pw, 2*r2.height() - pw), -90 * 16, 45 * 16); + break; + case LeftEdge: + if (!r1.isEmpty()) + p->drawArc(QRectF(x1 + pwby2, y1 - r1.height() + pwby2, + 2*r1.width() - pw, 2*r1.height() - pw), 135*16, 45*16); + if (!r2.isEmpty()) + p->drawArc(QRectF(x1 + pwby2, y2 - r2.height() + pwby2, + 2*r2.width() - pw, 2*r2.height() - pw), 180*16, 45*16); + break; + case RightEdge: + if (!r1.isEmpty()) + p->drawArc(QRectF(x2 - 2*r1.width() + pwby2, y1 - r1.height() + pwby2, + 2*r1.width() - pw, 2*r1.height() - pw), 45*16, -45*16); + if (!r2.isEmpty()) + p->drawArc(QRectF(x2 - 2*r2.width() + pwby2, y2 - r2.height() + pwby2, + 2*r2.width() - pw, 2*r2.height() - pw), 315*16, 45*16); + break; + default: + break; + } + p->restore(); +} + + +void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, qreal dw2, + QCss::Edge edge, QCss::BorderStyle style, QBrush c) +{ + p->save(); + const qreal width = (edge == TopEdge || edge == BottomEdge) ? (y2-y1) : (x2-x1); + + if (width <= 2 && style == BorderStyle_Double) + style = BorderStyle_Solid; + + switch (style) { + case BorderStyle_Inset: + case BorderStyle_Outset: + if ((style == BorderStyle_Outset && (edge == TopEdge || edge == LeftEdge)) + || (style == BorderStyle_Inset && (edge == BottomEdge || edge == RightEdge))) + c = c.color().lighter(); + // fall through! + case BorderStyle_Solid: { + p->setPen(Qt::NoPen); + p->setBrush(c); + if (width == 1 || (dw1 == 0 && dw2 == 0)) { + p->drawRect(QRectF(x1, y1, x2-x1, y2-y1)); + } else { // draw trapezoid + QPolygonF quad; + switch (edge) { + case TopEdge: + quad << QPointF(x1, y1) << QPointF(x1 + dw1, y2) + << QPointF(x2 - dw2, y2) << QPointF(x2, y1); + break; + case BottomEdge: + quad << QPointF(x1 + dw1, y1) << QPointF(x1, y2) + << QPointF(x2, y2) << QPointF(x2 - dw2, y1); + break; + case LeftEdge: + quad << QPointF(x1, y1) << QPointF(x1, y2) + << QPointF(x2, y2 - dw2) << QPointF(x2, y1 + dw1); + break; + case RightEdge: + quad << QPointF(x1, y1 + dw1) << QPointF(x1, y2 - dw2) + << QPointF(x2, y2) << QPointF(x2, y1); + break; + default: + break; + } + p->drawConvexPolygon(quad); + } + break; + } + case BorderStyle_Dotted: + case BorderStyle_Dashed: + case BorderStyle_DotDash: + case BorderStyle_DotDotDash: + p->setPen(qPenFromStyle(c, width, style)); + if (width == 1) + p->drawLine(QLineF(x1, y1, x2 - 1, y2 - 1)); + else if (edge == TopEdge || edge == BottomEdge) + p->drawLine(QLineF(x1 + width/2, (y1 + y2)/2, x2 - width/2, (y1 + y2)/2)); + else + p->drawLine(QLineF((x1+x2)/2, y1 + width/2, (x1+x2)/2, y2 - width/2)); + break; + + case BorderStyle_Double: { + int wby3 = qRound(width/3); + int dw1by3 = qRound(dw1/3); + int dw2by3 = qRound(dw2/3); + switch (edge) { + case TopEdge: + qDrawEdge(p, x1, y1, x2, y1 + wby3, dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c); + qDrawEdge(p, x1 + dw1 - dw1by3, y2 - wby3, x2 - dw2 + dw1by3, y2, + dw1by3, dw2by3, TopEdge, BorderStyle_Solid, c); + break; + case LeftEdge: + qDrawEdge(p, x1, y1, x1 + wby3, y2, dw1by3, dw2by3, LeftEdge, BorderStyle_Solid, c); + qDrawEdge(p, x2 - wby3, y1 + dw1 - dw1by3, x2, y2 - dw2 + dw2by3, dw1by3, dw2by3, + LeftEdge, BorderStyle_Solid, c); + break; + case BottomEdge: + qDrawEdge(p, x1 + dw1 - dw1by3, y1, x2 - dw2 + dw2by3, y1 + wby3, dw1by3, dw2by3, + BottomEdge, BorderStyle_Solid, c); + qDrawEdge(p, x1, y2 - wby3, x2, y2, dw1by3, dw2by3, BottomEdge, BorderStyle_Solid, c); + break; + case RightEdge: + qDrawEdge(p, x2 - wby3, y1, x2, y2, dw1by3, dw2by3, RightEdge, BorderStyle_Solid, c); + qDrawEdge(p, x1, y1 + dw1 - dw1by3, x1 + wby3, y2 - dw2 + dw2by3, dw1by3, dw2by3, + RightEdge, BorderStyle_Solid, c); + break; + default: + break; + } + break; + } + case BorderStyle_Ridge: + case BorderStyle_Groove: { + BorderStyle s1, s2; + if (style == BorderStyle_Groove) { + s1 = BorderStyle_Inset; + s2 = BorderStyle_Outset; + } else { + s1 = BorderStyle_Outset; + s2 = BorderStyle_Inset; + } + int dw1by2 = qFloor(dw1/2), dw2by2 = qFloor(dw2/2); + int wby2 = qRound(width/2); + switch (edge) { + case TopEdge: + qDrawEdge(p, x1, y1, x2, y1 + wby2, dw1by2, dw2by2, TopEdge, s1, c); + qDrawEdge(p, x1 + dw1by2, y1 + wby2, x2 - dw2by2, y2, dw1by2, dw2by2, TopEdge, s2, c); + break; + case BottomEdge: + qDrawEdge(p, x1, y1 + wby2, x2, y2, dw1by2, dw2by2, BottomEdge, s1, c); + qDrawEdge(p, x1 + dw1by2, y1, x2 - dw2by2, y1 + wby2, dw1by2, dw2by2, BottomEdge, s2, c); + break; + case LeftEdge: + qDrawEdge(p, x1, y1, x1 + wby2, y2, dw1by2, dw2by2, LeftEdge, s1, c); + qDrawEdge(p, x1 + wby2, y1 + dw1by2, x2, y2 - dw2by2, dw1by2, dw2by2, LeftEdge, s2, c); + break; + case RightEdge: + qDrawEdge(p, x1 + wby2, y1, x2, y2, dw1by2, dw2by2, RightEdge, s1, c); + qDrawEdge(p, x1, y1 + dw1by2, x1 + wby2, y2 - dw2by2, dw1by2, dw2by2, RightEdge, s2, c); + break; + default: + break; + } + } + default: + break; + } + p->restore(); +} + +void qNormalizeRadii(const QRect &br, const QSize *radii, + QSize *tlr, QSize *trr, QSize *blr, QSize *brr) +{ + *tlr = radii[0].expandedTo(QSize(0, 0)); + *trr = radii[1].expandedTo(QSize(0, 0)); + *blr = radii[2].expandedTo(QSize(0, 0)); + *brr = radii[3].expandedTo(QSize(0, 0)); + if (tlr->width() + trr->width() > br.width()) + *tlr = *trr = QSize(0, 0); + if (blr->width() + brr->width() > br.width()) + *blr = *brr = QSize(0, 0); + if (tlr->height() + blr->height() > br.height()) + *tlr = *blr = QSize(0, 0); + if (trr->height() + brr->height() > br.height()) + *trr = *brr = QSize(0, 0); +} + +// Determines if Edge e1 draws over Edge e2. Depending on this trapezoids or rectanges are drawn +static bool paintsOver(const QCss::BorderStyle *styles, const QBrush *colors, QCss::Edge e1, QCss::Edge e2) +{ + QCss::BorderStyle s1 = styles[e1]; + QCss::BorderStyle s2 = styles[e2]; + + if (s2 == BorderStyle_None || colors[e2] == Qt::transparent) + return true; + + if ((s1 == BorderStyle_Solid && s2 == BorderStyle_Solid) && (colors[e1] == colors[e2])) + return true; + + return false; +} + +void qDrawBorder(QPainter *p, const QRect &rect, const QCss::BorderStyle *styles, + const int *borders, const QBrush *colors, const QSize *radii) +{ + const QRectF br(rect); + QSize tlr, trr, blr, brr; + qNormalizeRadii(rect, radii, &tlr, &trr, &blr, &brr); + + // Drawn in increasing order of precendence + if (styles[BottomEdge] != BorderStyle_None && borders[BottomEdge] > 0) { + qreal dw1 = (blr.width() || paintsOver(styles, colors, BottomEdge, LeftEdge)) ? 0 : borders[LeftEdge]; + qreal dw2 = (brr.width() || paintsOver(styles, colors, BottomEdge, RightEdge)) ? 0 : borders[RightEdge]; + qreal x1 = br.x() + blr.width(); + qreal y1 = br.y() + br.height() - borders[BottomEdge]; + qreal x2 = br.x() + br.width() - brr.width(); + qreal y2 = br.y() + br.height() ; + + qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, BottomEdge, styles[BottomEdge], colors[BottomEdge]); + if (blr.width() || brr.width()) + qDrawRoundedCorners(p, x1, y1, x2, y2, blr, brr, BottomEdge, styles[BottomEdge], colors[BottomEdge]); + } + if (styles[RightEdge] != BorderStyle_None && borders[RightEdge] > 0) { + qreal dw1 = (trr.height() || paintsOver(styles, colors, RightEdge, TopEdge)) ? 0 : borders[TopEdge]; + qreal dw2 = (brr.height() || paintsOver(styles, colors, RightEdge, BottomEdge)) ? 0 : borders[BottomEdge]; + qreal x1 = br.x() + br.width() - borders[RightEdge]; + qreal y1 = br.y() + trr.height(); + qreal x2 = br.x() + br.width(); + qreal y2 = br.y() + br.height() - brr.height(); + + qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, RightEdge, styles[RightEdge], colors[RightEdge]); + if (trr.height() || brr.height()) + qDrawRoundedCorners(p, x1, y1, x2, y2, trr, brr, RightEdge, styles[RightEdge], colors[RightEdge]); + } + if (styles[LeftEdge] != BorderStyle_None && borders[LeftEdge] > 0) { + qreal dw1 = (tlr.height() || paintsOver(styles, colors, LeftEdge, TopEdge)) ? 0 : borders[TopEdge]; + qreal dw2 = (blr.height() || paintsOver(styles, colors, LeftEdge, BottomEdge)) ? 0 : borders[BottomEdge]; + qreal x1 = br.x(); + qreal y1 = br.y() + tlr.height(); + qreal x2 = br.x() + borders[LeftEdge]; + qreal y2 = br.y() + br.height() - blr.height(); + + qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, LeftEdge, styles[LeftEdge], colors[LeftEdge]); + if (tlr.height() || blr.height()) + qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, blr, LeftEdge, styles[LeftEdge], colors[LeftEdge]); + } + if (styles[TopEdge] != BorderStyle_None && borders[TopEdge] > 0) { + qreal dw1 = (tlr.width() || paintsOver(styles, colors, TopEdge, LeftEdge)) ? 0 : borders[LeftEdge]; + qreal dw2 = (trr.width() || paintsOver(styles, colors, TopEdge, RightEdge)) ? 0 : borders[RightEdge]; + qreal x1 = br.x() + tlr.width(); + qreal y1 = br.y(); + qreal x2 = br.left() + br.width() - trr.width(); + qreal y2 = br.y() + borders[TopEdge]; + + qDrawEdge(p, x1, y1, x2, y2, dw1, dw2, TopEdge, styles[TopEdge], colors[TopEdge]); + if (tlr.width() || trr.width()) + qDrawRoundedCorners(p, x1, y1, x2, y2, tlr, trr, TopEdge, styles[TopEdge], colors[TopEdge]); + } +} + +#endif //QT_NO_CSSPARSER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qcssutil_p.h b/src/gui/painting/qcssutil_p.h new file mode 100644 index 0000000000..80606b4cb9 --- /dev/null +++ b/src/gui/painting/qcssutil_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCSSUTIL_P_H +#define QCSSUTIL_P_H + +#include "QtCore/qglobal.h" + +#ifndef QT_NO_CSSPARSER + +// +// 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/qcssparser_p.h" +#include "QtCore/qsize.h" + +QT_BEGIN_NAMESPACE + +class QPainter; + +extern void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, qreal dw2, + QCss::Edge edge, QCss::BorderStyle style, QBrush c); + +extern void qDrawRoundedCorners(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, + const QSizeF& r1, const QSizeF& r2, + QCss::Edge edge, QCss::BorderStyle s, QBrush c); + +extern void qDrawBorder(QPainter *p, const QRect &rect, const QCss::BorderStyle *styles, + const int *borders, const QBrush *colors, const QSize *radii); + +extern void qNormalizeRadii(const QRect &br, const QSize *radii, + QSize *tlr, QSize *trr, QSize *blr, QSize *brr); + +QT_END_NAMESPACE + +#endif //QT_NO_CSSPARSER + +#endif // QCSSUTIL_P_H diff --git a/src/gui/painting/qcups.cpp b/src/gui/painting/qcups.cpp new file mode 100644 index 0000000000..24faa2f300 --- /dev/null +++ b/src/gui/painting/qcups.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qdebug.h> +#include "qcups_p.h" + +#ifndef QT_NO_CUPS + +#ifndef QT_LINUXBASE // LSB merges everything into cups.h +# include <cups/language.h> +#endif +#include <qtextcodec.h> + +QT_BEGIN_NAMESPACE + +typedef int (*CupsGetDests)(cups_dest_t **dests); +typedef void (*CupsFreeDests)(int num_dests, cups_dest_t *dests); +typedef const char* (*CupsGetPPD)(const char *printer); +typedef int (*CupsMarkOptions)(ppd_file_t *ppd, int num_options, cups_option_t *options); +typedef ppd_file_t* (*PPDOpenFile)(const char *filename); +typedef void (*PPDMarkDefaults)(ppd_file_t *ppd); +typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option); +typedef void (*PPDClose)(ppd_file_t *ppd); +typedef int (*PPDMarkOption)(ppd_file_t *ppd, const char *keyword, const char *option); +typedef void (*CupsFreeOptions)(int num_options, cups_option_t *options); +typedef void (*CupsSetDests)(int num_dests, cups_dest_t *dests); +typedef cups_lang_t* (*CupsLangGet)(const char *language); +typedef const char* (*CupsLangEncoding)(cups_lang_t *language); +typedef int (*CupsAddOption)(const char *name, const char *value, int num_options, cups_option_t **options); +typedef int (*CupsTempFd)(char *name, int len); +typedef int (*CupsPrintFile)(const char * name, const char * filename, const char * title, int num_options, cups_option_t * options); + +static bool cupsLoaded = false; +static int qt_cups_num_printers = 0; +static CupsGetDests _cupsGetDests = 0; +static CupsFreeDests _cupsFreeDests = 0; +static CupsGetPPD _cupsGetPPD = 0; +static PPDOpenFile _ppdOpenFile = 0; +static PPDMarkDefaults _ppdMarkDefaults = 0; +static PPDClose _ppdClose = 0; +static CupsMarkOptions _cupsMarkOptions = 0; +static PPDMarkOption _ppdMarkOption = 0; +static CupsFreeOptions _cupsFreeOptions = 0; +static CupsSetDests _cupsSetDests = 0; +static CupsLangGet _cupsLangGet = 0; +static CupsLangEncoding _cupsLangEncoding = 0; +static CupsAddOption _cupsAddOption = 0; +static CupsTempFd _cupsTempFd = 0; +static CupsPrintFile _cupsPrintFile = 0; + +static void resolveCups() +{ + QLibrary cupsLib(QLatin1String("cups"), 2); + if(cupsLib.load()) { + _cupsGetDests = (CupsGetDests) cupsLib.resolve("cupsGetDests"); + _cupsFreeDests = (CupsFreeDests) cupsLib.resolve("cupsFreeDests"); + _cupsGetPPD = (CupsGetPPD) cupsLib.resolve("cupsGetPPD"); + _cupsLangGet = (CupsLangGet) cupsLib.resolve("cupsLangGet"); + _cupsLangEncoding = (CupsLangEncoding) cupsLib.resolve("cupsLangEncoding"); + _ppdOpenFile = (PPDOpenFile) cupsLib.resolve("ppdOpenFile"); + _ppdMarkDefaults = (PPDMarkDefaults) cupsLib.resolve("ppdMarkDefaults"); + _ppdClose = (PPDClose) cupsLib.resolve("ppdClose"); + _cupsMarkOptions = (CupsMarkOptions) cupsLib.resolve("cupsMarkOptions"); + _ppdMarkOption = (PPDMarkOption) cupsLib.resolve("ppdMarkOption"); + _cupsFreeOptions = (CupsFreeOptions) cupsLib.resolve("cupsFreeOptions"); + _cupsSetDests = (CupsSetDests) cupsLib.resolve("cupsSetDests"); + _cupsAddOption = (CupsAddOption) cupsLib.resolve("cupsAddOption"); + _cupsTempFd = (CupsTempFd) cupsLib.resolve("cupsTempFd"); + _cupsPrintFile = (CupsPrintFile) cupsLib.resolve("cupsPrintFile"); + + if (_cupsGetDests && _cupsFreeDests) { + cups_dest_t *printers; + int num_printers = _cupsGetDests(&printers); + if (num_printers) + _cupsFreeDests(num_printers, printers); + qt_cups_num_printers = num_printers; + } + } + cupsLoaded = true; +} + +// ================ CUPS Support class ======================== + +QCUPSSupport::QCUPSSupport() + : + prnCount(0), + printers(0), + page_sizes(0), + currPrinterIndex(0), + currPPD(0) +{ + if (!cupsLoaded) + resolveCups(); + + // getting all available printers + if (!isAvailable()) + return; + + prnCount = _cupsGetDests(&printers); + + for (int i = 0; i < prnCount; ++i) { + if (printers[i].is_default) { + currPrinterIndex = i; + setCurrentPrinter(i); + break; + } + } + +#ifndef QT_NO_TEXTCODEC + cups_lang_t *cupsLang = _cupsLangGet(0); + codec = QTextCodec::codecForName(_cupsLangEncoding(cupsLang)); + if (!codec) + codec = QTextCodec::codecForLocale(); +#endif +} + +QCUPSSupport::~QCUPSSupport() +{ + if (currPPD) + _ppdClose(currPPD); + if (prnCount) + _cupsFreeDests(prnCount, printers); +} + +int QCUPSSupport::availablePrintersCount() const +{ + return prnCount; +} + +const cups_dest_t* QCUPSSupport::availablePrinters() const +{ + return printers; +} + +const ppd_file_t* QCUPSSupport::currentPPD() const +{ + return currPPD; +} + +const ppd_file_t* QCUPSSupport::setCurrentPrinter(int index) +{ + Q_ASSERT(index >= 0 && index <= prnCount); + if (index == prnCount) + return 0; + + currPrinterIndex = index; + + if (currPPD) + _ppdClose(currPPD); + currPPD = 0; + page_sizes = 0; + + const char *ppdFile = _cupsGetPPD(printers[index].name); + + if (!ppdFile) + return 0; + + currPPD = _ppdOpenFile(ppdFile); + unlink(ppdFile); + + // marking default options + _ppdMarkDefaults(currPPD); + + // marking options explicitly set + _cupsMarkOptions(currPPD, printers[currPrinterIndex].num_options, printers[currPrinterIndex].options); + + // getting pointer to page sizes + page_sizes = ppdOption("PageSize"); + + return currPPD; +} + +int QCUPSSupport::currentPrinterIndex() const +{ + return currPrinterIndex; +} + +bool QCUPSSupport::isAvailable() +{ + if(!cupsLoaded) + resolveCups(); + + return _cupsGetDests && + _cupsFreeDests && + _cupsGetPPD && + _ppdOpenFile && + _ppdMarkDefaults && + _ppdClose && + _cupsMarkOptions && + _ppdMarkOption && + _cupsFreeOptions && + _cupsSetDests && + _cupsLangGet && + _cupsLangEncoding && + _cupsAddOption && + (qt_cups_num_printers > 0); +} + +const ppd_option_t* QCUPSSupport::ppdOption(const char *key) const +{ + if (currPPD) { + for (int gr = 0; gr < currPPD->num_groups; ++gr) { + for (int opt = 0; opt < currPPD->groups[gr].num_options; ++opt) { + if (qstrcmp(currPPD->groups[gr].options[opt].keyword, key) == 0) + return &currPPD->groups[gr].options[opt]; + } + } + } + return 0; +} + +const cups_option_t* QCUPSSupport::printerOption(const QString &key) const +{ + for (int i = 0; i < printers[currPrinterIndex].num_options; ++i) { + if (QLatin1String(printers[currPrinterIndex].options[i].name) == key) + return &printers[currPrinterIndex].options[i]; + } + return 0; +} + +const ppd_option_t* QCUPSSupport::pageSizes() const +{ + return page_sizes; +} + +int QCUPSSupport::markOption(const char* name, const char* value) +{ + return _ppdMarkOption(currPPD, name, value); +} + +void QCUPSSupport::saveOptions(QList<const ppd_option_t*> options, QList<const char*> markedOptions) +{ + int oldOptionCount = printers[currPrinterIndex].num_options; + cups_option_t* oldOptions = printers[currPrinterIndex].options; + + int newOptionCount = 0; + cups_option_t* newOptions = 0; + + // copying old options that are not on the new list + for (int i = 0; i < oldOptionCount; ++i) { + bool contains = false; + for (int j = 0; j < options.count(); ++j) { + if (qstrcmp(options.at(j)->keyword, oldOptions[i].name) == 0) { + contains = true; + break; + } + } + + if (!contains) { + newOptionCount = _cupsAddOption(oldOptions[i].name, oldOptions[i].value, newOptionCount, &newOptions); + } + } + + // we can release old option list + _cupsFreeOptions(oldOptionCount, oldOptions); + + // adding marked options + for (int i = 0; i < markedOptions.count(); ++i) { + const char* name = markedOptions.at(i); + ++i; + newOptionCount = _cupsAddOption(name, markedOptions.at(i), newOptionCount, &newOptions); + } + + // placing the new option list + printers[currPrinterIndex].num_options = newOptionCount; + printers[currPrinterIndex].options = newOptions; + + // saving new default values + _cupsSetDests(prnCount, printers); +} + +QRect QCUPSSupport::paperRect(const char *choice) const +{ + if (!currPPD) + return QRect(); + for (int i = 0; i < currPPD->num_sizes; ++i) { + if (qstrcmp(currPPD->sizes[i].name, choice) == 0) + return QRect(0, 0, qRound(currPPD->sizes[i].width), qRound(currPPD->sizes[i].length)); + } + return QRect(); +} + +QRect QCUPSSupport::pageRect(const char *choice) const +{ + if (!currPPD) + return QRect(); + for (int i = 0; i < currPPD->num_sizes; ++i) { + if (qstrcmp(currPPD->sizes[i].name, choice) == 0) + return QRect(qRound(currPPD->sizes[i].left), + qRound(currPPD->sizes[i].length - currPPD->sizes[i].top), + qRound(currPPD->sizes[i].right - currPPD->sizes[i].left), + qRound(currPPD->sizes[i].top - currPPD->sizes[i].bottom)); + } + return QRect(); +} + +QStringList QCUPSSupport::options() const +{ + QStringList list; + collectMarkedOptions(list); + return list; +} + +bool QCUPSSupport::printerHasPPD(const char *printerName) +{ + if (!isAvailable()) + return false; + const char *ppdFile = _cupsGetPPD(printerName); + if (ppdFile) + unlink(ppdFile); + return (ppdFile != 0); +} + +QString QCUPSSupport::unicodeString(const char *s) +{ +#ifndef QT_NO_TEXTCODEC + return codec->toUnicode(s); +#else + return QLatin1String(s); +#endif +} + +void QCUPSSupport::collectMarkedOptions(QStringList& list, const ppd_group_t* group) const +{ + if (group == 0) { + if (!currPPD) + return; + for (int i = 0; i < currPPD->num_groups; ++i) { + collectMarkedOptions(list, &currPPD->groups[i]); + collectMarkedOptionsHelper(list, &currPPD->groups[i]); + } + } else { + for (int i = 0; i < group->num_subgroups; ++i) + collectMarkedOptionsHelper(list, &group->subgroups[i]); + } +} + +void QCUPSSupport::collectMarkedOptionsHelper(QStringList& list, const ppd_group_t* group) const +{ + for (int i = 0; i < group->num_options; ++i) { + for (int j = 0; j < group->options[i].num_choices; ++j) { + if (group->options[i].choices[j].marked == 1 && qstrcmp(group->options[i].choices[j].choice, group->options[i].defchoice) != 0) + list << QString::fromLocal8Bit(group->options[i].keyword) << QString::fromLocal8Bit(group->options[i].choices[j].choice); + } + } +} + +QPair<int, QString> QCUPSSupport::tempFd() +{ + char filename[512]; + int fd = _cupsTempFd(filename, 512); + return QPair<int, QString>(fd, QString::fromLocal8Bit(filename)); +} + +// Prints the given file and returns a job id. +int QCUPSSupport::printFile(const char * printerName, const char * filename, const char * title, + int num_options, cups_option_t * options) +{ + return _cupsPrintFile(printerName, filename, title, num_options, options); +} + +QT_END_NAMESPACE + +#endif // QT_NO_CUPS diff --git a/src/gui/painting/qcups_p.h b/src/gui/painting/qcups_p.h new file mode 100644 index 0000000000..bc00c7396e --- /dev/null +++ b/src/gui/painting/qcups_p.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCUPS_P_H +#define QCUPS_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 "QtCore/qstring.h" +#include "QtCore/qstringlist.h" +#include "QtGui/qprinter.h" + +#ifndef QT_NO_CUPS +#include <QtCore/qlibrary.h> +#include <cups/cups.h> +#include <cups/ppd.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_TYPEINFO(cups_option_t, Q_MOVABLE_TYPE | Q_PRIMITIVE_TYPE); + +class QCUPSSupport +{ +public: + QCUPSSupport(); + ~QCUPSSupport(); + + static bool isAvailable(); + static int cupsVersion() { return isAvailable() ? CUPS_VERSION_MAJOR*10000+CUPS_VERSION_MINOR*100+CUPS_VERSION_PATCH : 0; } + int availablePrintersCount() const; + const cups_dest_t* availablePrinters() const; + int currentPrinterIndex() const; + const ppd_file_t* setCurrentPrinter(int index); + + const ppd_file_t* currentPPD() const; + const ppd_option_t* ppdOption(const char *key) const; + + const cups_option_t* printerOption(const QString &key) const; + const ppd_option_t* pageSizes() const; + + int markOption(const char* name, const char* value); + void saveOptions(QList<const ppd_option_t*> options, QList<const char*> markedOptions); + + QRect paperRect(const char *choice) const; + QRect pageRect(const char *choice) const; + + QStringList options() const; + + static bool printerHasPPD(const char *printerName); + + QString unicodeString(const char *s); + + QPair<int, QString> tempFd(); + int printFile(const char * printerName, const char * filename, const char * title, + int num_options, cups_option_t * options); + +private: + void collectMarkedOptions(QStringList& list, const ppd_group_t* group = 0) const; + void collectMarkedOptionsHelper(QStringList& list, const ppd_group_t* group) const; + + int prnCount; + cups_dest_t *printers; + const ppd_option_t* page_sizes; + int currPrinterIndex; + ppd_file_t *currPPD; +#ifndef QT_NO_TEXTCODEC + QTextCodec *codec; +#endif +}; + +QT_END_NAMESPACE + +#endif // QT_NO_CUPS + +#endif diff --git a/src/gui/painting/qdatabuffer_p.h b/src/gui/painting/qdatabuffer_p.h new file mode 100644 index 0000000000..5e50f6e032 --- /dev/null +++ b/src/gui/painting/qdatabuffer_p.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDATABUFFER_P_H +#define QDATABUFFER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qbytearray.h" + +QT_BEGIN_NAMESPACE + +template <typename Type> class QDataBuffer +{ +public: + QDataBuffer(int res) + { + capacity = res; + if (res) + buffer = (Type*) qMalloc(capacity * sizeof(Type)); + else + buffer = 0; + siz = 0; + } + + ~QDataBuffer() + { + if (buffer) + qFree(buffer); + } + + inline void reset() { siz = 0; } + + inline bool isEmpty() const { return siz==0; } + + inline int size() const { return siz; } + inline Type *data() const { return buffer; } + + inline Type &at(int i) { Q_ASSERT(i >= 0 && i < siz); return buffer[i]; } + inline const Type &at(int i) const { Q_ASSERT(i >= 0 && i < siz); return buffer[i]; } + inline Type &last() { Q_ASSERT(!isEmpty()); return buffer[siz-1]; } + inline const Type &last() const { Q_ASSERT(!isEmpty()); return buffer[siz-1]; } + inline Type &first() { Q_ASSERT(!isEmpty()); return buffer[0]; } + inline const Type &first() const { Q_ASSERT(!isEmpty()); return buffer[0]; } + + inline void add(const Type &t) { + reserve(siz + 1); + buffer[siz] = t; + ++siz; + } + + inline void pop_back() { + Q_ASSERT(siz > 0); + --siz; + } + + inline void resize(int size) { + reserve(size); + siz = size; + } + + inline void reserve(int size) { + if (size > capacity) { + if (capacity == 0) + capacity = 1; + while (capacity < size) + capacity *= 2; + buffer = (Type*) qRealloc(buffer, capacity * sizeof(Type)); + } + } + + inline void shrink(int size) { + capacity = size; + if (size) + buffer = (Type*) qRealloc(buffer, capacity * sizeof(Type)); + else { + qFree(buffer); + buffer = 0; + } + } + + inline void swap(QDataBuffer<Type> &other) { + qSwap(capacity, other.capacity); + qSwap(siz, other.siz); + qSwap(buffer, other.buffer); + } + + inline QDataBuffer &operator<<(const Type &t) { add(t); return *this; } + +private: + int capacity; + int siz; + Type *buffer; +}; + +QT_END_NAMESPACE + +#endif // QDATABUFFER_P_H diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp new file mode 100644 index 0000000000..5e75e4a884 --- /dev/null +++ b/src/gui/painting/qdrawhelper.cpp @@ -0,0 +1,8078 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qpainter_p.h> +#include <private/qdrawhelper_x86_p.h> +#ifdef QT_HAVE_ARM_SIMD +#include <private/qdrawhelper_arm_simd_p.h> +#endif +#include <private/qdrawhelper_neon_p.h> +#include <private/qmath_p.h> +#include <qmath.h> + +QT_BEGIN_NAMESPACE + + +#define MASK(src, a) src = BYTE_MUL(src, a) + +#if defined(Q_OS_IRIX) && defined(Q_CC_GNU) && __GNUC__ == 3 && __GNUC__ < 4 && QT_POINTER_SIZE == 8 +#define Q_IRIX_GCC3_3_WORKAROUND +// +// work around http://gcc.gnu.org/bugzilla/show_bug.cgi?id=14484 +// +static uint gccBug(uint value) __attribute__((noinline)); +static uint gccBug(uint value) +{ + return value; +} +#endif + +/* + constants and structures +*/ + +enum { + fixed_scale = 1 << 16, + half_point = 1 << 15 +}; +static const int buffer_size = 2048; + +struct LinearGradientValues +{ + qreal dx; + qreal dy; + qreal l; + qreal off; +}; + +struct RadialGradientValues +{ + qreal dx; + qreal dy; + qreal a; +}; + +struct Operator; +typedef uint* (QT_FASTCALL *DestFetchProc)(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length); +typedef void (QT_FASTCALL *DestStoreProc)(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length); +typedef const uint* (QT_FASTCALL *SourceFetchProc)(uint *buffer, const Operator *o, const QSpanData *data, int y, int x, int length); + + +struct Operator +{ + QPainter::CompositionMode mode; + DestFetchProc dest_fetch; + DestStoreProc dest_store; + SourceFetchProc src_fetch; + CompositionFunctionSolid funcSolid; + CompositionFunction func; + union { + LinearGradientValues linear; + RadialGradientValues radial; +// TextureValues texture; + }; +}; + +/* + Destination fetch. This is simple as we don't have to do bounds checks or + transformations +*/ + +static uint * QT_FASTCALL destFetchMono(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length) +{ + uchar *data = (uchar *)rasterBuffer->scanLine(y); + uint *start = buffer; + const uint *end = buffer + length; + while (buffer < end) { + *buffer = data[x>>3] & (0x80 >> (x & 7)) ? rasterBuffer->destColor1 : rasterBuffer->destColor0; + ++buffer; + ++x; + } + return start; +} + +static uint * QT_FASTCALL destFetchMonoLsb(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length) +{ + uchar *data = (uchar *)rasterBuffer->scanLine(y); + uint *start = buffer; + const uint *end = buffer + length; + while (buffer < end) { + *buffer = data[x>>3] & (0x1 << (x & 7)) ? rasterBuffer->destColor1 : rasterBuffer->destColor0; + ++buffer; + ++x; + } + return start; +} + +static uint * QT_FASTCALL destFetchARGB32(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length) +{ + const uint *data = (const uint *)rasterBuffer->scanLine(y) + x; + for (int i = 0; i < length; ++i) + buffer[i] = PREMUL(data[i]); + return buffer; +} + +static uint * QT_FASTCALL destFetchARGB32P(uint *, QRasterBuffer *rasterBuffer, int x, int y, int) +{ + return (uint *)rasterBuffer->scanLine(y) + x; +} + +static uint * QT_FASTCALL destFetchRGB16(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length) +{ + const ushort *data = (const ushort *)rasterBuffer->scanLine(y) + x; + for (int i = 0; i < length; ++i) + buffer[i] = qConvertRgb16To32(data[i]); + return buffer; +} + +template <class DST> +Q_STATIC_TEMPLATE_FUNCTION uint * QT_FASTCALL destFetch(uint *buffer, QRasterBuffer *rasterBuffer, + int x, int y, int length) +{ + const DST *src = reinterpret_cast<DST*>(rasterBuffer->scanLine(y)) + x; + quint32 *dest = reinterpret_cast<quint32*>(buffer); + while (length--) + *dest++ = *src++; + return buffer; +} + +# define SPANFUNC_POINTER_DESTFETCH(Arg) destFetch<Arg> + +static DestFetchProc destFetchProc[QImage::NImageFormats] = +{ + 0, // Format_Invalid + destFetchMono, // Format_Mono, + destFetchMonoLsb, // Format_MonoLSB + 0, // Format_Indexed8 + destFetchARGB32P, // Format_RGB32 + destFetchARGB32, // Format_ARGB32, + destFetchARGB32P, // Format_ARGB32_Premultiplied + destFetchRGB16, // Format_RGB16 + SPANFUNC_POINTER_DESTFETCH(qargb8565), // Format_ARGB8565_Premultiplied + SPANFUNC_POINTER_DESTFETCH(qrgb666), // Format_RGB666 + SPANFUNC_POINTER_DESTFETCH(qargb6666), // Format_ARGB6666_Premultiplied + SPANFUNC_POINTER_DESTFETCH(qrgb555), // Format_RGB555 + SPANFUNC_POINTER_DESTFETCH(qargb8555), // Format_ARGB8555_Premultiplied + SPANFUNC_POINTER_DESTFETCH(qrgb888), // Format_RGB888 + SPANFUNC_POINTER_DESTFETCH(qrgb444), // Format_RGB444 + SPANFUNC_POINTER_DESTFETCH(qargb4444) // Format_ARGB4444_Premultiplied +}; + +/* + Returns the color in the mono destination color table + that is the "nearest" to /color/. +*/ +static inline QRgb findNearestColor(QRgb color, QRasterBuffer *rbuf) +{ + QRgb color_0 = PREMUL(rbuf->destColor0); + QRgb color_1 = PREMUL(rbuf->destColor1); + color = PREMUL(color); + + int r = qRed(color); + int g = qGreen(color); + int b = qBlue(color); + int rx, gx, bx; + int dist_0, dist_1; + + rx = r - qRed(color_0); + gx = g - qGreen(color_0); + bx = b - qBlue(color_0); + dist_0 = rx*rx + gx*gx + bx*bx; + + rx = r - qRed(color_1); + gx = g - qGreen(color_1); + bx = b - qBlue(color_1); + dist_1 = rx*rx + gx*gx + bx*bx; + + if (dist_0 < dist_1) + return color_0; + return color_1; +} + +/* + Destination store. +*/ + +static void QT_FASTCALL destStoreMono(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + uchar *data = (uchar *)rasterBuffer->scanLine(y); + if (rasterBuffer->monoDestinationWithClut) { + for (int i = 0; i < length; ++i) { + if (buffer[i] == rasterBuffer->destColor0) { + data[x >> 3] &= ~(0x80 >> (x & 7)); + } else if (buffer[i] == rasterBuffer->destColor1) { + data[x >> 3] |= 0x80 >> (x & 7); + } else if (findNearestColor(buffer[i], rasterBuffer) == rasterBuffer->destColor0) { + data[x >> 3] &= ~(0x80 >> (x & 7)); + } else { + data[x >> 3] |= 0x80 >> (x & 7); + } + ++x; + } + } else { + for (int i = 0; i < length; ++i) { + if (qGray(buffer[i]) < int(qt_bayer_matrix[y & 15][x & 15])) + data[x >> 3] |= 0x80 >> (x & 7); + else + data[x >> 3] &= ~(0x80 >> (x & 7)); + ++x; + } + } +} + +static void QT_FASTCALL destStoreMonoLsb(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + uchar *data = (uchar *)rasterBuffer->scanLine(y); + if (rasterBuffer->monoDestinationWithClut) { + for (int i = 0; i < length; ++i) { + if (buffer[i] == rasterBuffer->destColor0) { + data[x >> 3] &= ~(1 << (x & 7)); + } else if (buffer[i] == rasterBuffer->destColor1) { + data[x >> 3] |= 1 << (x & 7); + } else if (findNearestColor(buffer[i], rasterBuffer) == rasterBuffer->destColor0) { + data[x >> 3] &= ~(1 << (x & 7)); + } else { + data[x >> 3] |= 1 << (x & 7); + } + ++x; + } + } else { + for (int i = 0; i < length; ++i) { + if (qGray(buffer[i]) < int(qt_bayer_matrix[y & 15][x & 15])) + data[x >> 3] |= 1 << (x & 7); + else + data[x >> 3] &= ~(1 << (x & 7)); + ++x; + } + } +} + +static void QT_FASTCALL destStoreARGB32(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + uint *data = (uint *)rasterBuffer->scanLine(y) + x; + for (int i = 0; i < length; ++i) { + int p = buffer[i]; + int alpha = qAlpha(p); + if (alpha == 255) + data[i] = p; + else if (alpha == 0) + data[i] = 0; + else { + int inv_alpha = 0xff0000/qAlpha(buffer[i]); + data[i] = (p & 0xff000000) + | ((qRed(p)*inv_alpha) & 0xff0000) + | (((qGreen(p)*inv_alpha) >> 8) & 0xff00) + | ((qBlue(p)*inv_alpha) >> 16); + } + } +} + +static void QT_FASTCALL destStoreRGB16(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + quint16 *data = (quint16*)rasterBuffer->scanLine(y) + x; + qt_memconvert<quint16, quint32>(data, buffer, length); +} + +template <class DST> +Q_STATIC_TEMPLATE_FUNCTION void QT_FASTCALL destStore(QRasterBuffer *rasterBuffer, + int x, int y, + const uint *buffer, int length) +{ + DST *dest = reinterpret_cast<DST*>(rasterBuffer->scanLine(y)) + x; + const quint32p *src = reinterpret_cast<const quint32p*>(buffer); + while (length--) + *dest++ = DST(*src++); +} + +# define SPANFUNC_POINTER_DESTSTORE(DEST) destStore<DEST> + +static DestStoreProc destStoreProc[QImage::NImageFormats] = +{ + 0, // Format_Invalid + destStoreMono, // Format_Mono, + destStoreMonoLsb, // Format_MonoLSB + 0, // Format_Indexed8 + 0, // Format_RGB32 + destStoreARGB32, // Format_ARGB32, + 0, // Format_ARGB32_Premultiplied + destStoreRGB16, // Format_RGB16 + SPANFUNC_POINTER_DESTSTORE(qargb8565), // Format_ARGB8565_Premultiplied + SPANFUNC_POINTER_DESTSTORE(qrgb666), // Format_RGB666 + SPANFUNC_POINTER_DESTSTORE(qargb6666), // Format_ARGB6666_Premultiplied + SPANFUNC_POINTER_DESTSTORE(qrgb555), // Format_RGB555 + SPANFUNC_POINTER_DESTSTORE(qargb8555), // Format_ARGB8555_Premultiplied + SPANFUNC_POINTER_DESTSTORE(qrgb888), // Format_RGB888 + SPANFUNC_POINTER_DESTSTORE(qrgb444), // Format_RGB444 + SPANFUNC_POINTER_DESTSTORE(qargb4444) // Format_ARGB4444_Premultiplied +}; + +/* + Source fetches + + This is a bit more complicated, as we need several fetch routines for every surface type + + We need 5 fetch methods per surface type: + untransformed + transformed (tiled and not tiled) + transformed bilinear (tiled and not tiled) + + We don't need bounds checks for untransformed, but we need them for the other ones. + + The generic implementation does pixel by pixel fetches +*/ + +template <QImage::Format format> +Q_STATIC_TEMPLATE_FUNCTION uint QT_FASTCALL qt_fetchPixel(const uchar *scanLine, int x, const QVector<QRgb> *rgb); + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_Mono>(const uchar *scanLine, + int x, const QVector<QRgb> *rgb) +{ + bool pixel = scanLine[x>>3] & (0x80 >> (x & 7)); + if (rgb) return PREMUL(rgb->at(pixel ? 1 : 0)); + return pixel ? 0xff000000 : 0xffffffff; +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_MonoLSB>(const uchar *scanLine, + int x, const QVector<QRgb> *rgb) +{ + bool pixel = scanLine[x>>3] & (0x1 << (x & 7)); + if (rgb) return PREMUL(rgb->at(pixel ? 1 : 0)); + return pixel ? 0xff000000 : 0xffffffff; +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_Indexed8>(const uchar *scanLine, + int x, const QVector<QRgb> *rgb) +{ + return PREMUL(rgb->at(scanLine[x])); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB32>(const uchar *scanLine, + int x, const QVector<QRgb> *) +{ + return PREMUL(((const uint *)scanLine)[x]); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB32_Premultiplied>(const uchar *scanLine, + int x, const QVector<QRgb> *) +{ + return ((const uint *)scanLine)[x]; +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_RGB16>(const uchar *scanLine, + int x, const QVector<QRgb> *) +{ + return qConvertRgb16To32(((const ushort *)scanLine)[x]); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB8565_Premultiplied>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qargb8565 color = reinterpret_cast<const qargb8565*>(scanLine)[x]; + return qt_colorConvert<quint32, qargb8565>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_RGB666>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qrgb666 color = reinterpret_cast<const qrgb666*>(scanLine)[x]; + return qt_colorConvert<quint32, qrgb666>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB6666_Premultiplied>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qargb6666 color = reinterpret_cast<const qargb6666*>(scanLine)[x]; + return qt_colorConvert<quint32, qargb6666>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_RGB555>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qrgb555 color = reinterpret_cast<const qrgb555*>(scanLine)[x]; + return qt_colorConvert<quint32, qrgb555>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB8555_Premultiplied>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qargb8555 color = reinterpret_cast<const qargb8555*>(scanLine)[x]; + return qt_colorConvert<quint32, qargb8555>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_RGB888>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qrgb888 color = reinterpret_cast<const qrgb888*>(scanLine)[x]; + return qt_colorConvert<quint32, qrgb888>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_RGB444>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qrgb444 color = reinterpret_cast<const qrgb444*>(scanLine)[x]; + return qt_colorConvert<quint32, qrgb444>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_ARGB4444_Premultiplied>(const uchar *scanLine, + int x, + const QVector<QRgb> *) +{ + const qargb4444 color = reinterpret_cast<const qargb4444*>(scanLine)[x]; + return qt_colorConvert<quint32, qargb4444>(color, 0); +} + +template<> +Q_STATIC_TEMPLATE_SPECIALIZATION +uint QT_FASTCALL qt_fetchPixel<QImage::Format_Invalid>(const uchar *, + int , + const QVector<QRgb> *) +{ + return 0; +} + +typedef uint (QT_FASTCALL *FetchPixelProc)(const uchar *scanLine, int x, const QVector<QRgb> *); + +#define SPANFUNC_POINTER_FETCHPIXEL(Arg) qt_fetchPixel<QImage::Arg> + + +static const FetchPixelProc fetchPixelProc[QImage::NImageFormats] = +{ + 0, + SPANFUNC_POINTER_FETCHPIXEL(Format_Mono), + SPANFUNC_POINTER_FETCHPIXEL(Format_MonoLSB), + SPANFUNC_POINTER_FETCHPIXEL(Format_Indexed8), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB32_Premultiplied), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB32), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB32_Premultiplied), + SPANFUNC_POINTER_FETCHPIXEL(Format_RGB16), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB8565_Premultiplied), + SPANFUNC_POINTER_FETCHPIXEL(Format_RGB666), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB6666_Premultiplied), + SPANFUNC_POINTER_FETCHPIXEL(Format_RGB555), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB8555_Premultiplied), + SPANFUNC_POINTER_FETCHPIXEL(Format_RGB888), + SPANFUNC_POINTER_FETCHPIXEL(Format_RGB444), + SPANFUNC_POINTER_FETCHPIXEL(Format_ARGB4444_Premultiplied) +}; + +enum TextureBlendType { + BlendUntransformed, + BlendTiled, + BlendTransformed, + BlendTransformedTiled, + BlendTransformedBilinear, + BlendTransformedBilinearTiled, + NBlendTypes +}; + +template <QImage::Format format> +Q_STATIC_TEMPLATE_FUNCTION const uint * QT_FASTCALL qt_fetchUntransformed(uint *buffer, const Operator *, const QSpanData *data, + int y, int x, int length) +{ + const uchar *scanLine = data->texture.scanLine(y); + for (int i = 0; i < length; ++i) + buffer[i] = qt_fetchPixel<format>(scanLine, x + i, data->texture.colorTable); + return buffer; +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION const uint * QT_FASTCALL +qt_fetchUntransformed<QImage::Format_ARGB32_Premultiplied>(uint *, const Operator *, + const QSpanData *data, + int y, int x, int) +{ + const uchar *scanLine = data->texture.scanLine(y); + return ((const uint *)scanLine) + x; +} + +template<TextureBlendType blendType> /* either BlendTransformed or BlendTransformedTiled */ +Q_STATIC_TEMPLATE_FUNCTION +const uint * QT_FASTCALL fetchTransformed(uint *buffer, const Operator *, const QSpanData *data, + int y, int x, int length) +{ + FetchPixelProc fetch = fetchPixelProc[data->texture.format]; + + int image_width = data->texture.width; + int image_height = data->texture.height; + + const qreal cx = x + qreal(0.5); + const qreal cy = y + qreal(0.5); + + const uint *end = buffer + length; + uint *b = buffer; + if (data->fast_matrix) { + // The increment pr x in the scanline + int fdx = (int)(data->m11 * fixed_scale); + int fdy = (int)(data->m12 * fixed_scale); + + int fx = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int fy = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + + while (b < end) { + int px = fx >> 16; + int py = fy >> 16; + + if (blendType == BlendTransformedTiled) { + px %= image_width; + py %= image_height; + if (px < 0) px += image_width; + if (py < 0) py += image_height; + + const uchar *scanLine = data->texture.scanLine(py); + *b = fetch(scanLine, px, data->texture.colorTable); + } else { + if ((px < 0) || (px >= image_width) + || (py < 0) || (py >= image_height)) { + *b = uint(0); + } else { + const uchar *scanLine = data->texture.scanLine(py); + *b = fetch(scanLine, px, data->texture.colorTable); + } + } + fx += fdx; + fy += fdy; + ++b; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + + qreal fx = data->m21 * cy + data->m11 * cx + data->dx; + qreal fy = data->m22 * cy + data->m12 * cx + data->dy; + qreal fw = data->m23 * cy + data->m13 * cx + data->m33; + + while (b < end) { + const qreal iw = fw == 0 ? 1 : 1 / fw; + const qreal tx = fx * iw; + const qreal ty = fy * iw; + int px = int(tx) - (tx < 0); + int py = int(ty) - (ty < 0); + + if (blendType == BlendTransformedTiled) { + px %= image_width; + py %= image_height; + if (px < 0) px += image_width; + if (py < 0) py += image_height; + + const uchar *scanLine = data->texture.scanLine(py); + *b = fetch(scanLine, px, data->texture.colorTable); + } else { + if ((px < 0) || (px >= image_width) + || (py < 0) || (py >= image_height)) { + *b = uint(0); + } else { + const uchar *scanLine = data->texture.scanLine(py); + *b = fetch(scanLine, px, data->texture.colorTable); + } + } + fx += fdx; + fy += fdy; + fw += fdw; + //force increment to avoid /0 + if (!fw) { + fw += fdw; + } + ++b; + } + } + + return buffer; +} + +/** \internal + interpolate 4 argb pixels with the distx and disty factor. + distx and disty bust be between 0 and 16 + */ +static inline uint interpolate_4_pixels_16(uint tl, uint tr, uint bl, uint br, int distx, int disty) +{ + uint distxy = distx * disty; + //idistx * disty = (16-distx) * disty = 16*disty - distxy + //idistx * idisty = (16-distx) * (16-disty) = 16*16 - 16*distx -16*dity + distxy + uint tlrb = (tl & 0x00ff00ff) * (16*16 - 16*distx - 16*disty + distxy); + uint tlag = ((tl & 0xff00ff00) >> 8) * (16*16 - 16*distx - 16*disty + distxy); + uint trrb = ((tr & 0x00ff00ff) * (distx*16 - distxy)); + uint trag = (((tr & 0xff00ff00) >> 8) * (distx*16 - distxy)); + uint blrb = ((bl & 0x00ff00ff) * (disty*16 - distxy)); + uint blag = (((bl & 0xff00ff00) >> 8) * (disty*16 - distxy)); + uint brrb = ((br & 0x00ff00ff) * (distxy)); + uint brag = (((br & 0xff00ff00) >> 8) * (distxy)); + return (((tlrb + trrb + blrb + brrb) >> 8) & 0x00ff00ff) | ((tlag + trag + blag + brag) & 0xff00ff00); +} + +#if defined(QT_ALWAYS_HAVE_SSE2) +#define interpolate_4_pixels_16_sse2(tl, tr, bl, br, distx, disty, colorMask, v_256, b) \ +{ \ + const __m128i dxdy = _mm_mullo_epi16 (distx, disty); \ + const __m128i distx_ = _mm_slli_epi16(distx, 4); \ + const __m128i disty_ = _mm_slli_epi16(disty, 4); \ + const __m128i idxidy = _mm_add_epi16(dxdy, _mm_sub_epi16(v_256, _mm_add_epi16(distx_, disty_))); \ + const __m128i dxidy = _mm_sub_epi16(distx_, dxdy); \ + const __m128i idxdy = _mm_sub_epi16(disty_, dxdy); \ + \ + __m128i tlAG = _mm_srli_epi16(tl, 8); \ + __m128i tlRB = _mm_and_si128(tl, colorMask); \ + __m128i trAG = _mm_srli_epi16(tr, 8); \ + __m128i trRB = _mm_and_si128(tr, colorMask); \ + __m128i blAG = _mm_srli_epi16(bl, 8); \ + __m128i blRB = _mm_and_si128(bl, colorMask); \ + __m128i brAG = _mm_srli_epi16(br, 8); \ + __m128i brRB = _mm_and_si128(br, colorMask); \ + \ + tlAG = _mm_mullo_epi16(tlAG, idxidy); \ + tlRB = _mm_mullo_epi16(tlRB, idxidy); \ + trAG = _mm_mullo_epi16(trAG, dxidy); \ + trRB = _mm_mullo_epi16(trRB, dxidy); \ + blAG = _mm_mullo_epi16(blAG, idxdy); \ + blRB = _mm_mullo_epi16(blRB, idxdy); \ + brAG = _mm_mullo_epi16(brAG, dxdy); \ + brRB = _mm_mullo_epi16(brRB, dxdy); \ + \ + /* Add the values, and shift to only keep 8 significant bits per colors */ \ + __m128i rAG =_mm_add_epi16(_mm_add_epi16(tlAG, trAG), _mm_add_epi16(blAG, brAG)); \ + __m128i rRB =_mm_add_epi16(_mm_add_epi16(tlRB, trRB), _mm_add_epi16(blRB, brRB)); \ + rAG = _mm_andnot_si128(colorMask, rAG); \ + rRB = _mm_srli_epi16(rRB, 8); \ + _mm_storeu_si128((__m128i*)(b), _mm_or_si128(rAG, rRB)); \ +} +#endif + +#if defined(QT_ALWAYS_HAVE_NEON) +#define interpolate_4_pixels_16_neon(tl, tr, bl, br, distx, disty, disty_, colorMask, invColorMask, v_256, b) \ +{ \ + const int16x8_t dxdy = vmulq_s16(distx, disty); \ + const int16x8_t distx_ = vshlq_n_s16(distx, 4); \ + const int16x8_t idxidy = vaddq_s16(dxdy, vsubq_s16(v_256, vaddq_s16(distx_, disty_))); \ + const int16x8_t dxidy = vsubq_s16(distx_, dxdy); \ + const int16x8_t idxdy = vsubq_s16(disty_, dxdy); \ + \ + int16x8_t tlAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(tl), 8)); \ + int16x8_t tlRB = vandq_s16(tl, colorMask); \ + int16x8_t trAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(tr), 8)); \ + int16x8_t trRB = vandq_s16(tr, colorMask); \ + int16x8_t blAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(bl), 8)); \ + int16x8_t blRB = vandq_s16(bl, colorMask); \ + int16x8_t brAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(br), 8)); \ + int16x8_t brRB = vandq_s16(br, colorMask); \ + \ + int16x8_t rAG = vmulq_s16(tlAG, idxidy); \ + int16x8_t rRB = vmulq_s16(tlRB, idxidy); \ + rAG = vmlaq_s16(rAG, trAG, dxidy); \ + rRB = vmlaq_s16(rRB, trRB, dxidy); \ + rAG = vmlaq_s16(rAG, blAG, idxdy); \ + rRB = vmlaq_s16(rRB, blRB, idxdy); \ + rAG = vmlaq_s16(rAG, brAG, dxdy); \ + rRB = vmlaq_s16(rRB, brRB, dxdy); \ + \ + rAG = vandq_s16(invColorMask, rAG); \ + rRB = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(rRB), 8)); \ + vst1q_s16((int16_t*)(b), vorrq_s16(rAG, rRB)); \ +} +#endif + +template<TextureBlendType blendType> +Q_STATIC_TEMPLATE_FUNCTION inline void fetchTransformedBilinear_pixelBounds(int max, int l1, int l2, int &v1, int &v2) +{ + if (blendType == BlendTransformedBilinearTiled) { + v1 %= max; + if (v1 < 0) v1 += max; + v2 = v1 + 1; + v2 %= max; + } else { + if (v1 < l1) { + v2 = v1 = l1; + } else if (v1 >= l2) { + v2 = v1 = l2; + } else { + v2 = v1 + 1; + } + } + + Q_ASSERT(v1 >= 0 && v1 < max); + Q_ASSERT(v2 >= 0 && v2 < max); +} + +template<TextureBlendType blendType, QImage::Format format> /* blendType = BlendTransformedBilinear or BlendTransformedBilinearTiled */ +Q_STATIC_TEMPLATE_FUNCTION +const uint * QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Operator *, const QSpanData *data, + int y, int x, int length) +{ +#ifdef Q_CC_RVCT // needed to avoid compiler crash in RVCT 2.2 + FetchPixelProc fetch; + if (format != QImage::Format_Invalid) + fetch = qt_fetchPixel<format>; + else + fetch = fetchPixelProc[data->texture.format]; +#else + FetchPixelProc fetch = (format != QImage::Format_Invalid) ? FetchPixelProc(qt_fetchPixel<format>) : fetchPixelProc[data->texture.format]; +#endif + + int image_width = data->texture.width; + int image_height = data->texture.height; + + int image_x1 = data->texture.x1; + int image_y1 = data->texture.y1; + int image_x2 = data->texture.x2 - 1; + int image_y2 = data->texture.y2 - 1; + + const qreal cx = x + qreal(0.5); + const qreal cy = y + qreal(0.5); + + uint *end = buffer + length; + uint *b = buffer; + if (data->fast_matrix) { + // The increment pr x in the scanline + int fdx = (int)(data->m11 * fixed_scale); + int fdy = (int)(data->m12 * fixed_scale); + + int fx = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int fy = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + + fx -= half_point; + fy -= half_point; + + if (fdy == 0) { //simple scale, no rotation + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + + if (fdx <= fixed_scale && fdx > 0) { // scale up on X + int disty = (fy & 0x0000ffff) >> 8; + int idisty = 256 - disty; + int x = fx >> 16; + + // The idea is first to do the interpolation between the row s1 and the row s2 + // into an intermediate buffer, then we interpolate between two pixel of this buffer. + + // intermediate_buffer[0] is a buffer of red-blue component of the pixel, in the form 0x00RR00BB + // intermediate_buffer[1] is the alpha-green component of the pixel, in the form 0x00AA00GG + quint32 intermediate_buffer[2][buffer_size + 2]; + // count is the size used in the intermediate_buffer. + int count = qCeil(length * data->m11) + 2; //+1 for the last pixel to interpolate with, and +1 for rounding errors. + Q_ASSERT(count <= buffer_size + 2); //length is supposed to be <= buffer_size and data->m11 < 1 in this case + int f = 0; + int lim = count; + if (blendType == BlendTransformedBilinearTiled) { + x %= image_width; + if (x < 0) x += image_width; + } else { + lim = qMin(count, image_x2-x+1); + if (x < image_x1) { + Q_ASSERT(x <= image_x2); + uint t = fetch(s1, image_x1, data->texture.colorTable); + uint b = fetch(s2, image_x1, data->texture.colorTable); + quint32 rb = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; + quint32 ag = ((((t>>8) & 0xff00ff) * idisty + ((b>>8) & 0xff00ff) * disty) >> 8) & 0xff00ff; + do { + intermediate_buffer[0][f] = rb; + intermediate_buffer[1][f] = ag; + f++; + x++; + } while (x < image_x1 && f < lim); + } + } + + if (blendType != BlendTransformedBilinearTiled && + (format == QImage::Format_ARGB32_Premultiplied || format == QImage::Format_RGB32)) { +#if defined(QT_ALWAYS_HAVE_SSE2) + const __m128i disty_ = _mm_set1_epi16(disty); + const __m128i idisty_ = _mm_set1_epi16(idisty); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + + lim -= 3; + for (; f < lim; x += 4, f += 4) { + // Load 4 pixels from s1, and split the alpha-green and red-blue component + __m128i top = _mm_loadu_si128((__m128i*)((const uint *)(s1)+x)); + __m128i topAG = _mm_srli_epi16(top, 8); + __m128i topRB = _mm_and_si128(top, colorMask); + // Multiplies each colour component by idisty + topAG = _mm_mullo_epi16 (topAG, idisty_); + topRB = _mm_mullo_epi16 (topRB, idisty_); + + // Same for the s2 vector + __m128i bottom = _mm_loadu_si128((__m128i*)((const uint *)(s2)+x)); + __m128i bottomAG = _mm_srli_epi16(bottom, 8); + __m128i bottomRB = _mm_and_si128(bottom, colorMask); + bottomAG = _mm_mullo_epi16 (bottomAG, disty_); + bottomRB = _mm_mullo_epi16 (bottomRB, disty_); + + // Add the values, and shift to only keep 8 significant bits per colors + __m128i rAG =_mm_add_epi16(topAG, bottomAG); + rAG = _mm_srli_epi16(rAG, 8); + _mm_storeu_si128((__m128i*)(&intermediate_buffer[1][f]), rAG); + __m128i rRB =_mm_add_epi16(topRB, bottomRB); + rRB = _mm_srli_epi16(rRB, 8); + _mm_storeu_si128((__m128i*)(&intermediate_buffer[0][f]), rRB); + } +#elif defined(QT_ALWAYS_HAVE_NEON) + const int16x8_t disty_ = vdupq_n_s16(disty); + const int16x8_t idisty_ = vdupq_n_s16(idisty); + const int16x8_t colorMask = vdupq_n_s16(0x00ff); + + lim -= 3; + for (; f < lim; x += 4, f += 4) { + // Load 4 pixels from s1, and split the alpha-green and red-blue component + int16x8_t top = vld1q_s16((int16_t*)((const uint *)(s1)+x)); + int16x8_t topAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(top), 8)); + int16x8_t topRB = vandq_s16(top, colorMask); + // Multiplies each colour component by idisty + topAG = vmulq_s16(topAG, idisty_); + topRB = vmulq_s16(topRB, idisty_); + + // Same for the s2 vector + int16x8_t bottom = vld1q_s16((int16_t*)((const uint *)(s2)+x)); + int16x8_t bottomAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(bottom), 8)); + int16x8_t bottomRB = vandq_s16(bottom, colorMask); + bottomAG = vmulq_s16(bottomAG, disty_); + bottomRB = vmulq_s16(bottomRB, disty_); + + // Add the values, and shift to only keep 8 significant bits per colors + int16x8_t rAG = vaddq_s16(topAG, bottomAG); + rAG = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(rAG), 8)); + vst1q_s16((int16_t*)(&intermediate_buffer[1][f]), rAG); + int16x8_t rRB = vaddq_s16(topRB, bottomRB); + rRB = vreinterpretq_s16_u16(vshrq_n_u16(vreinterpretq_u16_s16(rRB), 8)); + vst1q_s16((int16_t*)(&intermediate_buffer[0][f]), rRB); + } +#endif + } + for (; f < count; f++) { // Same as above but without sse2 + if (blendType == BlendTransformedBilinearTiled) { + if (x >= image_width) x -= image_width; + } else { + x = qMin(x, image_x2); + } + + uint t = fetch(s1, x, data->texture.colorTable); + uint b = fetch(s2, x, data->texture.colorTable); + + intermediate_buffer[0][f] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; + intermediate_buffer[1][f] = ((((t>>8) & 0xff00ff) * idisty + ((b>>8) & 0xff00ff) * disty) >> 8) & 0xff00ff; + x++; + } + // Now interpolate the values from the intermediate_buffer to get the final result. + fx &= fixed_scale - 1; + Q_ASSERT((fx >> 16) == 0); + while (b < end) { + register int x1 = (fx >> 16); + register int x2 = x1 + 1; + Q_ASSERT(x1 >= 0); + Q_ASSERT(x2 < count); + + register int distx = (fx & 0x0000ffff) >> 8; + register int idistx = 256 - distx; + int rb = ((intermediate_buffer[0][x1] * idistx + intermediate_buffer[0][x2] * distx) >> 8) & 0xff00ff; + int ag = (intermediate_buffer[1][x1] * idistx + intermediate_buffer[1][x2] * distx) & 0xff00ff00; + *b = rb | ag; + b++; + fx += fdx; + } + } else if ((fdx < 0 && fdx > -(fixed_scale / 8)) || fabs(data->m22) < (1./8.)) { // scale up more than 8x + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + int disty = (fy & 0x0000ffff) >> 8; + int idisty = 256 - disty; + while (b < end) { + int x1 = (fx >> 16); + int x2; + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); + uint tl = fetch(s1, x1, data->texture.colorTable); + uint tr = fetch(s1, x2, data->texture.colorTable); + uint bl = fetch(s2, x1, data->texture.colorTable); + uint br = fetch(s2, x2, data->texture.colorTable); + + int distx = (fx & 0x0000ffff) >> 8; + int idistx = 256 - distx; + + uint xtop = INTERPOLATE_PIXEL_256(tl, idistx, tr, distx); + uint xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx); + *b = INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty); + + fx += fdx; + ++b; + } + } else { //scale down + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + int disty = (fy & 0x0000ffff) >> 12; + + if (blendType != BlendTransformedBilinearTiled && + (format == QImage::Format_ARGB32_Premultiplied || format == QImage::Format_RGB32)) { + +#define BILINEAR_DOWNSCALE_BOUNDS_PROLOG \ + while (b < end) { \ + int x1 = (fx >> 16); \ + int x2; \ + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); \ + if (x1 != x2) \ + break; \ + uint tl = fetch(s1, x1, data->texture.colorTable); \ + uint tr = fetch(s1, x2, data->texture.colorTable); \ + uint bl = fetch(s2, x1, data->texture.colorTable); \ + uint br = fetch(s2, x2, data->texture.colorTable); \ + int distx = (fx & 0x0000ffff) >> 12; \ + *b = interpolate_4_pixels_16(tl, tr, bl, br, distx, disty); \ + fx += fdx; \ + ++b; \ + } \ + uint *boundedEnd; \ + if (fdx > 0) \ + boundedEnd = qMin(end, buffer + uint((image_x2 - (fx >> 16)) / data->m11)); \ + else \ + boundedEnd = qMin(end, buffer + uint((image_x1 - (fx >> 16)) / data->m11)); \ + boundedEnd -= 3; + +#if defined(QT_ALWAYS_HAVE_SSE2) + BILINEAR_DOWNSCALE_BOUNDS_PROLOG + + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + const __m128i v_256 = _mm_set1_epi16(256); + const __m128i v_disty = _mm_set1_epi16(disty); + __m128i v_fdx = _mm_set1_epi32(fdx*4); + + ptrdiff_t secondLine = reinterpret_cast<const uint *>(s2) - reinterpret_cast<const uint *>(s1); + + union Vect_buffer { __m128i vect; quint32 i[4]; }; + Vect_buffer v_fx; + + for (int i = 0; i < 4; i++) { + v_fx.i[i] = fx; + fx += fdx; + } + + while (b < boundedEnd) { + + Vect_buffer tl, tr, bl, br; + + for (int i = 0; i < 4; i++) { + int x1 = v_fx.i[i] >> 16; + const uint *addr_tl = reinterpret_cast<const uint *>(s1) + x1; + const uint *addr_tr = addr_tl + 1; + tl.i[i] = *addr_tl; + tr.i[i] = *addr_tr; + bl.i[i] = *(addr_tl+secondLine); + br.i[i] = *(addr_tr+secondLine); + } + __m128i v_distx = _mm_srli_epi16(v_fx.vect, 12); + v_distx = _mm_shufflehi_epi16(v_distx, _MM_SHUFFLE(2,2,0,0)); + v_distx = _mm_shufflelo_epi16(v_distx, _MM_SHUFFLE(2,2,0,0)); + + interpolate_4_pixels_16_sse2(tl.vect, tr.vect, bl.vect, br.vect, v_distx, v_disty, colorMask, v_256, b); + b+=4; + v_fx.vect = _mm_add_epi32(v_fx.vect, v_fdx); + } + fx = v_fx.i[0]; +#elif defined(QT_ALWAYS_HAVE_NEON) + BILINEAR_DOWNSCALE_BOUNDS_PROLOG + + const int16x8_t colorMask = vdupq_n_s16(0x00ff); + const int16x8_t invColorMask = vmvnq_s16(colorMask); + const int16x8_t v_256 = vdupq_n_s16(256); + const int16x8_t v_disty = vdupq_n_s16(disty); + const int16x8_t v_disty_ = vshlq_n_s16(v_disty, 4); + int32x4_t v_fdx = vdupq_n_s32(fdx*4); + + ptrdiff_t secondLine = reinterpret_cast<const uint *>(s2) - reinterpret_cast<const uint *>(s1); + + union Vect_buffer { int32x4_t vect; quint32 i[4]; }; + Vect_buffer v_fx; + + for (int i = 0; i < 4; i++) { + v_fx.i[i] = fx; + fx += fdx; + } + + const int32x4_t v_ffff_mask = vdupq_n_s32(0x0000ffff); + + while (b < boundedEnd) { + + Vect_buffer tl, tr, bl, br; + + Vect_buffer v_fx_shifted; + v_fx_shifted.vect = vshrq_n_s32(v_fx.vect, 16); + + int32x4_t v_distx = vshrq_n_s32(vandq_s32(v_fx.vect, v_ffff_mask), 12); + + for (int i = 0; i < 4; i++) { + int x1 = v_fx_shifted.i[i]; + const uint *addr_tl = reinterpret_cast<const uint *>(s1) + x1; + const uint *addr_tr = addr_tl + 1; + tl.i[i] = *addr_tl; + tr.i[i] = *addr_tr; + bl.i[i] = *(addr_tl+secondLine); + br.i[i] = *(addr_tr+secondLine); + } + + v_distx = vorrq_s32(v_distx, vshlq_n_s32(v_distx, 16)); + + interpolate_4_pixels_16_neon(vreinterpretq_s16_s32(tl.vect), vreinterpretq_s16_s32(tr.vect), vreinterpretq_s16_s32(bl.vect), vreinterpretq_s16_s32(br.vect), vreinterpretq_s16_s32(v_distx), v_disty, v_disty_, colorMask, invColorMask, v_256, b); + b+=4; + v_fx.vect = vaddq_s32(v_fx.vect, v_fdx); + } + fx = v_fx.i[0]; +#endif + } + + while (b < end) { + int x1 = (fx >> 16); + int x2; + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); + uint tl = fetch(s1, x1, data->texture.colorTable); + uint tr = fetch(s1, x2, data->texture.colorTable); + uint bl = fetch(s2, x1, data->texture.colorTable); + uint br = fetch(s2, x2, data->texture.colorTable); + int distx = (fx & 0x0000ffff) >> 12; + *b = interpolate_4_pixels_16(tl, tr, bl, br, distx, disty); + fx += fdx; + ++b; + } + } + } else { //rotation + if (fabs(data->m11) > 8 || fabs(data->m22) > 8) { + //if we are zooming more than 8 times, we use 8bit precision for the position. + while (b < end) { + int x1 = (fx >> 16); + int x2; + int y1 = (fy >> 16); + int y2; + + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + + uint tl = fetch(s1, x1, data->texture.colorTable); + uint tr = fetch(s1, x2, data->texture.colorTable); + uint bl = fetch(s2, x1, data->texture.colorTable); + uint br = fetch(s2, x2, data->texture.colorTable); + + int distx = (fx & 0x0000ffff) >> 8; + int disty = (fy & 0x0000ffff) >> 8; + int idistx = 256 - distx; + int idisty = 256 - disty; + + uint xtop = INTERPOLATE_PIXEL_256(tl, idistx, tr, distx); + uint xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx); + *b = INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty); + + fx += fdx; + fy += fdy; + ++b; + } + } else { + //we are zooming less than 8x, use 4bit precision + while (b < end) { + int x1 = (fx >> 16); + int x2; + int y1 = (fy >> 16); + int y2; + + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + + uint tl = fetch(s1, x1, data->texture.colorTable); + uint tr = fetch(s1, x2, data->texture.colorTable); + uint bl = fetch(s2, x1, data->texture.colorTable); + uint br = fetch(s2, x2, data->texture.colorTable); + + int distx = (fx & 0x0000ffff) >> 12; + int disty = (fy & 0x0000ffff) >> 12; + + *b = interpolate_4_pixels_16(tl, tr, bl, br, distx, disty); + + fx += fdx; + fy += fdy; + ++b; + } + } + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + + qreal fx = data->m21 * cy + data->m11 * cx + data->dx; + qreal fy = data->m22 * cy + data->m12 * cx + data->dy; + qreal fw = data->m23 * cy + data->m13 * cx + data->m33; + + while (b < end) { + const qreal iw = fw == 0 ? 1 : 1 / fw; + const qreal px = fx * iw - qreal(0.5); + const qreal py = fy * iw - qreal(0.5); + + int x1 = int(px) - (px < 0); + int x2; + int y1 = int(py) - (py < 0); + int y2; + + int distx = int((px - x1) * 256); + int disty = int((py - y1) * 256); + int idistx = 256 - distx; + int idisty = 256 - disty; + + fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + + const uchar *s1 = data->texture.scanLine(y1); + const uchar *s2 = data->texture.scanLine(y2); + + uint tl = fetch(s1, x1, data->texture.colorTable); + uint tr = fetch(s1, x2, data->texture.colorTable); + uint bl = fetch(s2, x1, data->texture.colorTable); + uint br = fetch(s2, x2, data->texture.colorTable); + + uint xtop = INTERPOLATE_PIXEL_256(tl, idistx, tr, distx); + uint xbot = INTERPOLATE_PIXEL_256(bl, idistx, br, distx); + *b = INTERPOLATE_PIXEL_256(xtop, idisty, xbot, disty); + + fx += fdx; + fy += fdy; + fw += fdw; + //force increment to avoid /0 + if (!fw) { + fw += fdw; + } + ++b; + } + } + + return buffer; +} + +#define SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Arg) qt_fetchUntransformed<QImage::Arg> + +static const SourceFetchProc sourceFetch[NBlendTypes][QImage::NImageFormats] = { + // Untransformed + { + 0, // Invalid + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_Mono), // Mono + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_MonoLSB), // MonoLsb + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_Indexed8), // Indexed8 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32_Premultiplied), // RGB32 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32), // ARGB32 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32_Premultiplied), // ARGB32_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB16), // RGB16 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB8565_Premultiplied),// ARGB8565_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB666), // RGB666 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB6666_Premultiplied),// ARGB6666_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB555), // RGB555 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB8555_Premultiplied),// ARGB8555_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB888), // RGB888 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB444), // RGB444 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB4444_Premultiplied) // ARGB4444_Premultiplied + }, + // Tiled + { + 0, // Invalid + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_Mono), // Mono + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_MonoLSB), // MonoLsb + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_Indexed8), // Indexed8 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32_Premultiplied), // RGB32 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32), // ARGB32 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB32_Premultiplied), // ARGB32_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB16), // RGB16 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB8565_Premultiplied),// ARGB8565_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB666), // RGB666 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB6666_Premultiplied),// ARGB6666_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB555), // RGB555 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB8555_Premultiplied),// ARGB8555_Premultiplied + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB888), // RGB888 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_RGB444), // RGB444 + SPANFUNC_POINTER_FETCHHUNTRANSFORMED(Format_ARGB4444_Premultiplied) // ARGB4444_Premultiplied + }, + // Transformed + { + 0, // Invalid + fetchTransformed<BlendTransformed>, // Mono + fetchTransformed<BlendTransformed>, // MonoLsb + fetchTransformed<BlendTransformed>, // Indexed8 + fetchTransformed<BlendTransformed>, // RGB32 + fetchTransformed<BlendTransformed>, // ARGB32 + fetchTransformed<BlendTransformed>, // ARGB32_Premultiplied + fetchTransformed<BlendTransformed>, // RGB16 + fetchTransformed<BlendTransformed>, // ARGB8565_Premultiplied + fetchTransformed<BlendTransformed>, // RGB666 + fetchTransformed<BlendTransformed>, // ARGB6666_Premultiplied + fetchTransformed<BlendTransformed>, // RGB555 + fetchTransformed<BlendTransformed>, // ARGB8555_Premultiplied + fetchTransformed<BlendTransformed>, // RGB888 + fetchTransformed<BlendTransformed>, // RGB444 + fetchTransformed<BlendTransformed>, // ARGB4444_Premultiplied + }, + { + 0, // TransformedTiled + fetchTransformed<BlendTransformedTiled>, // Mono + fetchTransformed<BlendTransformedTiled>, // MonoLsb + fetchTransformed<BlendTransformedTiled>, // Indexed8 + fetchTransformed<BlendTransformedTiled>, // RGB32 + fetchTransformed<BlendTransformedTiled>, // ARGB32 + fetchTransformed<BlendTransformedTiled>, // ARGB32_Premultiplied + fetchTransformed<BlendTransformedTiled>, // RGB16 + fetchTransformed<BlendTransformedTiled>, // ARGB8565_Premultiplied + fetchTransformed<BlendTransformedTiled>, // RGB666 + fetchTransformed<BlendTransformedTiled>, // ARGB6666_Premultiplied + fetchTransformed<BlendTransformedTiled>, // RGB555 + fetchTransformed<BlendTransformedTiled>, // ARGB8555_Premultiplied + fetchTransformed<BlendTransformedTiled>, // RGB888 + fetchTransformed<BlendTransformedTiled>, // RGB444 + fetchTransformed<BlendTransformedTiled>, // ARGB4444_Premultiplied + }, + { + 0, // Bilinear + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // Mono + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // MonoLsb + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // Indexed8 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_ARGB32_Premultiplied>, // RGB32 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_ARGB32>, // ARGB32 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_ARGB32_Premultiplied>, // ARGB32_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // RGB16 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // ARGB8565_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // RGB666 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // ARGB6666_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // RGB555 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // ARGB8555_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // RGB888 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid>, // RGB444 + fetchTransformedBilinear<BlendTransformedBilinear, QImage::Format_Invalid> // ARGB4444_Premultiplied + }, + { + 0, // BilinearTiled + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // Mono + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // MonoLsb + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // Indexed8 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_ARGB32_Premultiplied>, // RGB32 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_ARGB32>, // ARGB32 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_ARGB32_Premultiplied>, // ARGB32_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // RGB16 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // ARGB8565_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // RGB666 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // ARGB6666_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // RGB555 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // ARGB8555_Premultiplied + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // RGB888 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid>, // RGB444 + fetchTransformedBilinear<BlendTransformedBilinearTiled, QImage::Format_Invalid> // ARGB4444_Premultiplied + }, +}; + + +static inline uint qt_gradient_pixel(const QGradientData *data, qreal pos) +{ + int ipos = int(pos * (GRADIENT_STOPTABLE_SIZE - 1) + qreal(0.5)); + + // calculate the actual offset. + if (ipos < 0 || ipos >= GRADIENT_STOPTABLE_SIZE) { + if (data->spread == QGradient::RepeatSpread) { + ipos = ipos % GRADIENT_STOPTABLE_SIZE; + ipos = ipos < 0 ? GRADIENT_STOPTABLE_SIZE + ipos : ipos; + + } else if (data->spread == QGradient::ReflectSpread) { + const int limit = GRADIENT_STOPTABLE_SIZE * 2 - 1; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= GRADIENT_STOPTABLE_SIZE ? limit - ipos : ipos; + + } else { + if (ipos < 0) ipos = 0; + else if (ipos >= GRADIENT_STOPTABLE_SIZE) ipos = GRADIENT_STOPTABLE_SIZE-1; + } + } + + Q_ASSERT(ipos >= 0); + Q_ASSERT(ipos < GRADIENT_STOPTABLE_SIZE); + + return data->colorTable[ipos]; +} + +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1<<FIXPT_BITS) + +static uint qt_gradient_pixel_fixed(const QGradientData *data, int fixed_pos) +{ + int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + + // calculate the actual offset. + if (ipos < 0 || ipos >= GRADIENT_STOPTABLE_SIZE) { + if (data->spread == QGradient::RepeatSpread) { + ipos = ipos % GRADIENT_STOPTABLE_SIZE; + ipos = ipos < 0 ? GRADIENT_STOPTABLE_SIZE + ipos : ipos; + + } else if (data->spread == QGradient::ReflectSpread) { + const int limit = GRADIENT_STOPTABLE_SIZE * 2 - 1; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= GRADIENT_STOPTABLE_SIZE ? limit - ipos : ipos; + + } else { + if (ipos < 0) ipos = 0; + else if (ipos >= GRADIENT_STOPTABLE_SIZE) ipos = GRADIENT_STOPTABLE_SIZE-1; + } + } + + Q_ASSERT(ipos >= 0); + Q_ASSERT(ipos < GRADIENT_STOPTABLE_SIZE); + + return data->colorTable[ipos]; +} + +static void QT_FASTCALL getLinearGradientValues(LinearGradientValues *v, const QSpanData *data) +{ + v->dx = data->gradient.linear.end.x - data->gradient.linear.origin.x; + v->dy = data->gradient.linear.end.y - data->gradient.linear.origin.y; + v->l = v->dx * v->dx + v->dy * v->dy; + v->off = 0; + if (v->l != 0) { + v->dx /= v->l; + v->dy /= v->l; + v->off = -v->dx * data->gradient.linear.origin.x - v->dy * data->gradient.linear.origin.y; + } +} + +static const uint * QT_FASTCALL fetchLinearGradient(uint *buffer, const Operator *op, const QSpanData *data, + int y, int x, int length) +{ + const uint *b = buffer; + qreal t, inc; + + bool affine = true; + qreal rx=0, ry=0; + if (op->linear.l == 0) { + t = inc = 0; + } else { + rx = data->m21 * (y + qreal(0.5)) + data->m11 * (x + qreal(0.5)) + data->dx; + ry = data->m22 * (y + qreal(0.5)) + data->m12 * (x + qreal(0.5)) + data->dy; + t = op->linear.dx*rx + op->linear.dy*ry + op->linear.off; + inc = op->linear.dx * data->m11 + op->linear.dy * data->m12; + affine = !data->m13 && !data->m23; + + if (affine) { + t *= (GRADIENT_STOPTABLE_SIZE - 1); + inc *= (GRADIENT_STOPTABLE_SIZE - 1); + } + } + + const uint *end = buffer + length; + if (affine) { + if (inc > qreal(-1e-5) && inc < qreal(1e-5)) { + QT_MEMFILL_UINT(buffer, length, qt_gradient_pixel_fixed(&data->gradient, int(t * FIXPT_SIZE))); + } else { + if (t+inc*length < qreal(INT_MAX >> (FIXPT_BITS + 1)) && + t+inc*length > qreal(INT_MIN >> (FIXPT_BITS + 1))) { + // we can use fixed point math + int t_fixed = int(t * FIXPT_SIZE); + int inc_fixed = int(inc * FIXPT_SIZE); + while (buffer < end) { + *buffer = qt_gradient_pixel_fixed(&data->gradient, t_fixed); + t_fixed += inc_fixed; + ++buffer; + } + } else { + // we have to fall back to float math + while (buffer < end) { + *buffer = qt_gradient_pixel(&data->gradient, t/GRADIENT_STOPTABLE_SIZE); + t += inc; + ++buffer; + } + } + } + } else { // fall back to float math here as well + qreal rw = data->m23 * (y + qreal(0.5)) + data->m13 * (x + qreal(0.5)) + data->m33; + while (buffer < end) { + qreal x = rx/rw; + qreal y = ry/rw; + t = (op->linear.dx*x + op->linear.dy *y) + op->linear.off; + + *buffer = qt_gradient_pixel(&data->gradient, t); + rx += data->m11; + ry += data->m12; + rw += data->m13; + if (!rw) { + rw += data->m13; + } + ++buffer; + } + } + + return b; +} + +static inline qreal determinant(qreal a, qreal b, qreal c) +{ + return (b * b) - (4 * a * c); +} + +// function to evaluate real roots +static inline qreal realRoots(qreal a, qreal b, qreal detSqrt) +{ + return (-b + detSqrt)/(2 * a); +} + +static inline qreal qSafeSqrt(qreal x) +{ + return x > 0 ? qSqrt(x) : 0; +} + +static void QT_FASTCALL getRadialGradientValues(RadialGradientValues *v, const QSpanData *data) +{ + v->dx = data->gradient.radial.center.x - data->gradient.radial.focal.x; + v->dy = data->gradient.radial.center.y - data->gradient.radial.focal.y; + v->a = data->gradient.radial.radius*data->gradient.radial.radius - v->dx*v->dx - v->dy*v->dy; +} + +static const uint * QT_FASTCALL fetchRadialGradient(uint *buffer, const Operator *op, const QSpanData *data, + int y, int x, int length) +{ + const uint *b = buffer; + qreal rx = data->m21 * (y + qreal(0.5)) + + data->dx + data->m11 * (x + qreal(0.5)); + qreal ry = data->m22 * (y + qreal(0.5)) + + data->dy + data->m12 * (x + qreal(0.5)); + bool affine = !data->m13 && !data->m23; + //qreal r = data->gradient.radial.radius; + + const uint *end = buffer + length; + if (affine) { + rx -= data->gradient.radial.focal.x; + ry -= data->gradient.radial.focal.y; + + qreal inv_a = 1 / qreal(2 * op->radial.a); + + const qreal delta_rx = data->m11; + const qreal delta_ry = data->m12; + + qreal b = 2*(rx * op->radial.dx + ry * op->radial.dy); + qreal delta_b = 2*(delta_rx * op->radial.dx + delta_ry * op->radial.dy); + const qreal b_delta_b = 2 * b * delta_b; + const qreal delta_b_delta_b = 2 * delta_b * delta_b; + + const qreal bb = b * b; + const qreal delta_bb = delta_b * delta_b; + + b *= inv_a; + delta_b *= inv_a; + + const qreal rxrxryry = rx * rx + ry * ry; + const qreal delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; + const qreal rx_plus_ry = 2*(rx * delta_rx + ry * delta_ry); + const qreal delta_rx_plus_ry = 2 * delta_rxrxryry; + + inv_a *= inv_a; + + qreal det = (bb + 4 * op->radial.a * rxrxryry) * inv_a; + qreal delta_det = (b_delta_b + delta_bb + 4 * op->radial.a * (rx_plus_ry + delta_rxrxryry)) * inv_a; + const qreal delta_delta_det = (delta_b_delta_b + 4 * op->radial.a * delta_rx_plus_ry) * inv_a; + + while (buffer < end) { + *buffer = qt_gradient_pixel(&data->gradient, qSafeSqrt(det) - b); + + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + + ++buffer; + } + } else { + qreal rw = data->m23 * (y + qreal(0.5)) + + data->m33 + data->m13 * (x + qreal(0.5)); + if (!rw) + rw = 1; + while (buffer < end) { + qreal gx = rx/rw - data->gradient.radial.focal.x; + qreal gy = ry/rw - data->gradient.radial.focal.y; + qreal b = 2*(gx*op->radial.dx + gy*op->radial.dy); + qreal det = determinant(op->radial.a, b , -(gx*gx + gy*gy)); + qreal s = realRoots(op->radial.a, b, qSafeSqrt(det)); + + *buffer = qt_gradient_pixel(&data->gradient, s); + + rx += data->m11; + ry += data->m12; + rw += data->m13; + if (!rw) { + rw += data->m13; + } + ++buffer; + } + } + + return b; +} + +static const uint * QT_FASTCALL fetchConicalGradient(uint *buffer, const Operator *, const QSpanData *data, + int y, int x, int length) +{ + const uint *b = buffer; + qreal rx = data->m21 * (y + qreal(0.5)) + + data->dx + data->m11 * (x + qreal(0.5)); + qreal ry = data->m22 * (y + qreal(0.5)) + + data->dy + data->m12 * (x + qreal(0.5)); + bool affine = !data->m13 && !data->m23; + + const uint *end = buffer + length; + if (affine) { + rx -= data->gradient.conical.center.x; + ry -= data->gradient.conical.center.y; + while (buffer < end) { + qreal angle = qAtan2(ry, rx) + data->gradient.conical.angle; + + *buffer = qt_gradient_pixel(&data->gradient, 1 - angle / (2*Q_PI)); + + rx += data->m11; + ry += data->m12; + ++buffer; + } + } else { + qreal rw = data->m23 * (y + qreal(0.5)) + + data->m33 + data->m13 * (x + qreal(0.5)); + if (!rw) + rw = 1; + while (buffer < end) { + qreal angle = qAtan2(ry/rw - data->gradient.conical.center.x, + rx/rw - data->gradient.conical.center.y) + + data->gradient.conical.angle; + + *buffer = qt_gradient_pixel(&data->gradient, 1. - angle / (2*Q_PI)); + + rx += data->m11; + ry += data->m12; + rw += data->m13; + if (!rw) { + rw += data->m13; + } + ++buffer; + } + } + return b; +} + +#if defined(Q_CC_RVCT) +// Force ARM code generation for comp_func_* -methods +# pragma push +# pragma arm +# if defined(QT_HAVE_ARMV6) +static __forceinline void preload(const uint *start) +{ + asm( "pld [start]" ); +} +static const uint L2CacheLineLength = 32; +static const uint L2CacheLineLengthInInts = L2CacheLineLength/sizeof(uint); +# define PRELOAD_INIT(x) preload(x); +# define PRELOAD_INIT2(x,y) PRELOAD_INIT(x) PRELOAD_INIT(y) +# define PRELOAD_COND(x) if (((uint)&x[i])%L2CacheLineLength == 0) preload(&x[i] + L2CacheLineLengthInInts); +// Two consecutive preloads stall, so space them out a bit by using different modulus. +# define PRELOAD_COND2(x,y) if (((uint)&x[i])%L2CacheLineLength == 0) preload(&x[i] + L2CacheLineLengthInInts); \ + if (((uint)&y[i])%L2CacheLineLength == 16) preload(&y[i] + L2CacheLineLengthInInts); +# endif // QT_HAVE_ARMV6 +#endif // Q_CC_RVCT + +#if !defined(Q_CC_RVCT) || !defined(QT_HAVE_ARMV6) +# define PRELOAD_INIT(x) +# define PRELOAD_INIT2(x,y) +# define PRELOAD_COND(x) +# define PRELOAD_COND2(x,y) +#endif + +/* The constant alpha factor describes an alpha factor that gets applied + to the result of the composition operation combining it with the destination. + + The intent is that if const_alpha == 0. we get back dest, and if const_alpha == 1. + we get the unmodified operation + + result = src op dest + dest = result * const_alpha + dest * (1. - const_alpha) + + This means that in the comments below, the first line is the const_alpha==255 case, the + second line the general one. + + In the lines below: + s == src, sa == alpha(src), sia = 1 - alpha(src) + d == dest, da == alpha(dest), dia = 1 - alpha(dest) + ca = const_alpha, cia = 1 - const_alpha + + The methods exist in two variants. One where we have a constant source, the other + where the source is an array of pixels. +*/ + +/* + result = 0 + d = d * cia +*/ +#define comp_func_Clear_impl(dest, length, const_alpha)\ +{\ + if (const_alpha == 255) {\ + QT_MEMFILL_UINT(dest, length, 0);\ + } else {\ + int ialpha = 255 - const_alpha;\ + PRELOAD_INIT(dest)\ + for (int i = 0; i < length; ++i) {\ + PRELOAD_COND(dest)\ + dest[i] = BYTE_MUL(dest[i], ialpha);\ + }\ + }\ +} + +void QT_FASTCALL comp_func_solid_Clear(uint *dest, int length, uint, uint const_alpha) +{ + comp_func_Clear_impl(dest, length, const_alpha); +} + +void QT_FASTCALL comp_func_Clear(uint *dest, const uint *, int length, uint const_alpha) +{ + comp_func_Clear_impl(dest, length, const_alpha); +} + +/* + result = s + dest = s * ca + d * cia +*/ +void QT_FASTCALL comp_func_solid_Source(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) { + QT_MEMFILL_UINT(dest, length, color); + } else { + int ialpha = 255 - const_alpha; + color = BYTE_MUL(color, const_alpha); + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } + } +} + +void QT_FASTCALL comp_func_Source(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) { + ::memcpy(dest, src, length * sizeof(uint)); + } else { + int ialpha = 255 - const_alpha; + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + dest[i] = INTERPOLATE_PIXEL_255(src[i], const_alpha, dest[i], ialpha); + } + } +} + +void QT_FASTCALL comp_func_solid_Destination(uint *, int, uint, uint) +{ +} + +void QT_FASTCALL comp_func_Destination(uint *, const uint *, int, uint) +{ +} + +/* + result = s + d * sia + dest = (s + d * sia) * ca + d * cia + = s * ca + d * (sia * ca + cia) + = s * ca + d * (1 - sa*ca) +*/ +void QT_FASTCALL comp_func_solid_SourceOver(uint *dest, int length, uint color, uint const_alpha) +{ + if ((const_alpha & qAlpha(color)) == 255) { + QT_MEMFILL_UINT(dest, length, color); + } else { + if (const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = color + BYTE_MUL(dest[i], qAlpha(~color)); + } + } +} + +void QT_FASTCALL comp_func_SourceOver(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = src[i]; + if (s >= 0xff000000) + dest[i] = s; + else if (s != 0) + dest[i] = s + BYTE_MUL(dest[i], qAlpha(~s)); + } + } else { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = BYTE_MUL(src[i], const_alpha); + dest[i] = s + BYTE_MUL(dest[i], qAlpha(~s)); + } + } +} + +/* + result = d + s * dia + dest = (d + s * dia) * ca + d * cia + = d + s * dia * ca +*/ +void QT_FASTCALL comp_func_solid_DestinationOver(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + dest[i] = d + BYTE_MUL(color, qAlpha(~d)); + } +} + +void QT_FASTCALL comp_func_DestinationOver(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + dest[i] = d + BYTE_MUL(src[i], qAlpha(~d)); + } + } else { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = BYTE_MUL(src[i], const_alpha); + dest[i] = d + BYTE_MUL(s, qAlpha(~d)); + } + } +} + +/* + result = s * da + dest = s * da * ca + d * cia +*/ +void QT_FASTCALL comp_func_solid_SourceIn(uint *dest, int length, uint color, uint const_alpha) +{ + PRELOAD_INIT(dest) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = BYTE_MUL(color, qAlpha(dest[i])); + } + } else { + color = BYTE_MUL(color, const_alpha); + uint cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, qAlpha(d), d, cia); + } + } +} + +void QT_FASTCALL comp_func_SourceIn(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + dest[i] = BYTE_MUL(src[i], qAlpha(dest[i])); + } + } else { + uint cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = BYTE_MUL(src[i], const_alpha); + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(d), d, cia); + } + } +} + +/* + result = d * sa + dest = d * sa * ca + d * cia + = d * (sa * ca + cia) +*/ +void QT_FASTCALL comp_func_solid_DestinationIn(uint *dest, int length, uint color, uint const_alpha) +{ + uint a = qAlpha(color); + if (const_alpha != 255) { + a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + } + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = BYTE_MUL(dest[i], a); + } +} + +void QT_FASTCALL comp_func_DestinationIn(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + dest[i] = BYTE_MUL(dest[i], qAlpha(src[i])); + } + } else { + int cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint a = BYTE_MUL(qAlpha(src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], a); + } + } +} + +/* + result = s * dia + dest = s * dia * ca + d * cia +*/ + +void QT_FASTCALL comp_func_solid_SourceOut(uint *dest, int length, uint color, uint const_alpha) +{ + PRELOAD_INIT(dest) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = BYTE_MUL(color, qAlpha(~dest[i])); + } + } else { + color = BYTE_MUL(color, const_alpha); + int cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, qAlpha(~d), d, cia); + } + } +} + +void QT_FASTCALL comp_func_SourceOut(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + dest[i] = BYTE_MUL(src[i], qAlpha(~dest[i])); + } + } else { + int cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = BYTE_MUL(src[i], const_alpha); + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(~d), d, cia); + } + } +} + +/* + result = d * sia + dest = d * sia * ca + d * cia + = d * (sia * ca + cia) +*/ +void QT_FASTCALL comp_func_solid_DestinationOut(uint *dest, int length, uint color, uint const_alpha) +{ + uint a = qAlpha(~color); + if (const_alpha != 255) + a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = BYTE_MUL(dest[i], a); + } +} + +void QT_FASTCALL comp_func_DestinationOut(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + dest[i] = BYTE_MUL(dest[i], qAlpha(~src[i])); + } + } else { + int cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint sia = BYTE_MUL(qAlpha(~src[i]), const_alpha) + cia; + dest[i] = BYTE_MUL(dest[i], sia); + } + } +} + +/* + result = s*da + d*sia + dest = s*da*ca + d*sia*ca + d *cia + = s*ca * da + d * (sia*ca + cia) + = s*ca * da + d * (1 - sa*ca) +*/ +void QT_FASTCALL comp_func_solid_SourceAtop(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha != 255) { + color = BYTE_MUL(color, const_alpha); + } + uint sia = qAlpha(~color); + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + dest[i] = INTERPOLATE_PIXEL_255(color, qAlpha(dest[i]), dest[i], sia); + } +} + +void QT_FASTCALL comp_func_SourceAtop(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = src[i]; + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(d), d, qAlpha(~s)); + } + } else { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = BYTE_MUL(src[i], const_alpha); + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(d), d, qAlpha(~s)); + } + } +} + +/* + result = d*sa + s*dia + dest = d*sa*ca + s*dia*ca + d *cia + = s*ca * dia + d * (sa*ca + cia) +*/ +void QT_FASTCALL comp_func_solid_DestinationAtop(uint *dest, int length, uint color, uint const_alpha) +{ + uint a = qAlpha(color); + if (const_alpha != 255) { + color = BYTE_MUL(color, const_alpha); + a = qAlpha(color) + 255 - const_alpha; + } + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(d, a, color, qAlpha(~d)); + } +} + +void QT_FASTCALL comp_func_DestinationAtop(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = src[i]; + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(d, qAlpha(s), s, qAlpha(~d)); + } + } else { + int cia = 255 - const_alpha; + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint s = BYTE_MUL(src[i], const_alpha); + uint d = dest[i]; + uint a = qAlpha(s) + cia; + dest[i] = INTERPOLATE_PIXEL_255(d, a, s, qAlpha(~d)); + } + } +} + +/* + result = d*sia + s*dia + dest = d*sia*ca + s*dia*ca + d *cia + = s*ca * dia + d * (sia*ca + cia) + = s*ca * dia + d * (1 - sa*ca) +*/ +void QT_FASTCALL comp_func_solid_XOR(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + uint sia = qAlpha(~color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + dest[i] = INTERPOLATE_PIXEL_255(color, qAlpha(~d), d, sia); + } +} + +void QT_FASTCALL comp_func_XOR(uint *dest, const uint *src, int length, uint const_alpha) +{ + PRELOAD_INIT2(dest, src) + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(~d), d, qAlpha(~s)); + } + } else { + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = BYTE_MUL(src[i], const_alpha); + dest[i] = INTERPOLATE_PIXEL_255(s, qAlpha(~d), d, qAlpha(~s)); + } + } +} + +struct QFullCoverage { + inline void store(uint *dest, const uint src) const + { + *dest = src; + } +}; + +struct QPartialCoverage { + inline QPartialCoverage(uint const_alpha) + : ca(const_alpha) + , ica(255 - const_alpha) + { + } + + inline void store(uint *dest, const uint src) const + { + *dest = INTERPOLATE_PIXEL_255(src, ca, *dest, ica); + } + +private: + const uint ca; + const uint ica; +}; + +static inline int mix_alpha(int da, int sa) +{ + return 255 - ((255 - sa) * (255 - da) >> 8); +} + +/* + Dca' = Sca.Da + Dca.Sa + Sca.(1 - Da) + Dca.(1 - Sa) + = Sca + Dca +*/ +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Plus_impl(uint *dest, int length, uint color, const T &coverage) +{ + uint s = color; + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + d = comp_func_Plus_one_pixel(d, s); + coverage.store(&dest[i], d); + } +} + +void QT_FASTCALL comp_func_solid_Plus(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Plus_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Plus_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Plus_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + d = comp_func_Plus_one_pixel(d, s); + + coverage.store(&dest[i], d); + } +} + +void QT_FASTCALL comp_func_Plus(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Plus_impl(dest, src, length, QFullCoverage()); + else + comp_func_Plus_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline int multiply_op(int dst, int src, int da, int sa) +{ + return qt_div_255(src * dst + src * (255 - da) + dst * (255 - sa)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Multiply_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) multiply_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Multiply(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Multiply_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Multiply_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Multiply_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) multiply_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Multiply(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Multiply_impl(dest, src, length, QFullCoverage()); + else + comp_func_Multiply_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = (Sca.Da + Dca.Sa - Sca.Dca) + Sca.(1 - Da) + Dca.(1 - Sa) + = Sca + Dca - Sca.Dca +*/ +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Screen_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) 255 - qt_div_255((255-a) * (255-b)) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Screen(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Screen_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Screen_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Screen_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) 255 - (((255-a) * (255-b)) >> 8) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Screen(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Screen_impl(dest, src, length, QFullCoverage()); + else + comp_func_Screen_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + if 2.Dca < Da + Dca' = 2.Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa) + otherwise + Dca' = Sa.Da - 2.(Da - Dca).(Sa - Sca) + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline int overlay_op(int dst, int src, int da, int sa) +{ + const int temp = src * (255 - da) + dst * (255 - sa); + if (2 * dst < da) + return qt_div_255(2 * src * dst + temp); + else + return qt_div_255(sa * da - 2 * (da - dst) * (sa - src) + temp); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Overlay_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) overlay_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Overlay(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Overlay_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Overlay_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Overlay_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) overlay_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Overlay(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Overlay_impl(dest, src, length, QFullCoverage()); + else + comp_func_Overlay_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa) + Da' = Sa + Da - Sa.Da +*/ +static inline int darken_op(int dst, int src, int da, int sa) +{ + return qt_div_255(qMin(src * da, dst * sa) + src * (255 - da) + dst * (255 - sa)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Darken_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) darken_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Darken(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Darken_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Darken_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Darken_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) darken_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Darken(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Darken_impl(dest, src, length, QFullCoverage()); + else + comp_func_Darken_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = max(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa) + Da' = Sa + Da - Sa.Da +*/ +static inline int lighten_op(int dst, int src, int da, int sa) +{ + return qt_div_255(qMax(src * da, dst * sa) + src * (255 - da) + dst * (255 - sa)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Lighten_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) lighten_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Lighten(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Lighten_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Lighten_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Lighten_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) lighten_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Lighten(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Lighten_impl(dest, src, length, QFullCoverage()); + else + comp_func_Lighten_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + if Sca.Da + Dca.Sa >= Sa.Da + Dca' = Sa.Da + Sca.(1 - Da) + Dca.(1 - Sa) + otherwise + Dca' = Dca.Sa/(1-Sca/Sa) + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline int color_dodge_op(int dst, int src, int da, int sa) +{ + const int sa_da = sa * da; + const int dst_sa = dst * sa; + const int src_da = src * da; + + const int temp = src * (255 - da) + dst * (255 - sa); + if (src_da + dst_sa >= sa_da) + return qt_div_255(sa_da + temp); + else + return qt_div_255(255 * dst_sa / (255 - 255 * src / sa) + temp); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_ColorDodge_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a,b) color_dodge_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_ColorDodge(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_ColorDodge_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_ColorDodge_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_ColorDodge_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) color_dodge_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_ColorDodge(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_ColorDodge_impl(dest, src, length, QFullCoverage()); + else + comp_func_ColorDodge_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + if Sca.Da + Dca.Sa <= Sa.Da + Dca' = Sca.(1 - Da) + Dca.(1 - Sa) + otherwise + Dca' = Sa.(Sca.Da + Dca.Sa - Sa.Da)/Sca + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline int color_burn_op(int dst, int src, int da, int sa) +{ + const int src_da = src * da; + const int dst_sa = dst * sa; + const int sa_da = sa * da; + + const int temp = src * (255 - da) + dst * (255 - sa); + + if (src == 0 || src_da + dst_sa <= sa_da) + return qt_div_255(temp); + return qt_div_255(sa * (src_da + dst_sa - sa_da) / src + temp); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_ColorBurn_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) color_burn_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_ColorBurn(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_ColorBurn_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_ColorBurn_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_ColorBurn_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) color_burn_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_ColorBurn(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_ColorBurn_impl(dest, src, length, QFullCoverage()); + else + comp_func_ColorBurn_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + if 2.Sca < Sa + Dca' = 2.Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa) + otherwise + Dca' = Sa.Da - 2.(Da - Dca).(Sa - Sca) + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline uint hardlight_op(int dst, int src, int da, int sa) +{ + const uint temp = src * (255 - da) + dst * (255 - sa); + + if (2 * src < sa) + return qt_div_255(2 * src * dst + temp); + else + return qt_div_255(sa * da - 2 * (da - dst) * (sa - src) + temp); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_HardLight_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) hardlight_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_HardLight(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_HardLight_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_HardLight_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_HardLight_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) hardlight_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_HardLight(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_HardLight_impl(dest, src, length, QFullCoverage()); + else + comp_func_HardLight_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + if 2.Sca <= Sa + Dca' = Dca.(Sa + (2.Sca - Sa).(1 - Dca/Da)) + Sca.(1 - Da) + Dca.(1 - Sa) + otherwise if 2.Sca > Sa and 4.Dca <= Da + Dca' = Dca.Sa + Da.(2.Sca - Sa).(4.Dca/Da.(4.Dca/Da + 1).(Dca/Da - 1) + 7.Dca/Da) + Sca.(1 - Da) + Dca.(1 - Sa) + otherwise if 2.Sca > Sa and 4.Dca > Da + Dca' = Dca.Sa + Da.(2.Sca - Sa).((Dca/Da)^0.5 - Dca/Da) + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +static inline int soft_light_op(int dst, int src, int da, int sa) +{ + const int src2 = src << 1; + const int dst_np = da != 0 ? (255 * dst) / da : 0; + const int temp = (src * (255 - da) + dst * (255 - sa)) * 255; + + if (src2 < sa) + return (dst * (sa * 255 + (src2 - sa) * (255 - dst_np)) + temp) / 65025; + else if (4 * dst <= da) + return (dst * sa * 255 + da * (src2 - sa) * ((((16 * dst_np - 12 * 255) * dst_np + 3 * 65025) * dst_np) / 65025) + temp) / 65025; + else { +# ifdef Q_CC_RVCT // needed to avoid compiler crash in RVCT 2.2 + return (dst * sa * 255 + da * (src2 - sa) * (qIntSqrtInt(dst_np * 255) - dst_np) + temp) / 65025; +# else + return (dst * sa * 255 + da * (src2 - sa) * (int(qSqrt(qreal(dst_np * 255))) - dst_np) + temp) / 65025; +# endif + } +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_SoftLight_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) soft_light_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_SoftLight(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_SoftLight_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_SoftLight_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_SoftLight_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) soft_light_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_SoftLight(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_SoftLight_impl(dest, src, length, QFullCoverage()); + else + comp_func_SoftLight_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = abs(Dca.Sa - Sca.Da) + Sca.(1 - Da) + Dca.(1 - Sa) + = Sca + Dca - 2.min(Sca.Da, Dca.Sa) +*/ +static inline int difference_op(int dst, int src, int da, int sa) +{ + return src + dst - qt_div_255(2 * qMin(src * da, dst * sa)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_solid_Difference_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) difference_op(a, b, da, sa) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Difference(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Difference_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Difference_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Difference_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) difference_op(a, b, da, sa) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Difference(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Difference_impl(dest, src, length, QFullCoverage()); + else + comp_func_Difference_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +/* + Dca' = (Sca.Da + Dca.Sa - 2.Sca.Dca) + Sca.(1 - Da) + Dca.(1 - Sa) +*/ +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void QT_FASTCALL comp_func_solid_Exclusion_impl(uint *dest, int length, uint color, const T &coverage) +{ + int sa = qAlpha(color); + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + PRELOAD_INIT(dest) + for (int i = 0; i < length; ++i) { + PRELOAD_COND(dest) + uint d = dest[i]; + int da = qAlpha(d); + +#define OP(a, b) (a + b - qt_div_255(2*(a*b))) + int r = OP( qRed(d), sr); + int b = OP( qBlue(d), sb); + int g = OP(qGreen(d), sg); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_solid_Exclusion(uint *dest, int length, uint color, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_solid_Exclusion_impl(dest, length, color, QFullCoverage()); + else + comp_func_solid_Exclusion_impl(dest, length, color, QPartialCoverage(const_alpha)); +} + +template <typename T> +Q_STATIC_TEMPLATE_FUNCTION inline void comp_func_Exclusion_impl(uint *dest, const uint *src, int length, const T &coverage) +{ + PRELOAD_INIT2(dest, src) + for (int i = 0; i < length; ++i) { + PRELOAD_COND2(dest, src) + uint d = dest[i]; + uint s = src[i]; + + int da = qAlpha(d); + int sa = qAlpha(s); + +#define OP(a, b) (a + b - ((a*b) >> 7)) + int r = OP( qRed(d), qRed(s)); + int b = OP( qBlue(d), qBlue(s)); + int g = OP(qGreen(d), qGreen(s)); + int a = mix_alpha(da, sa); +#undef OP + + coverage.store(&dest[i], qRgba(r, g, b, a)); + } +} + +void QT_FASTCALL comp_func_Exclusion(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) + comp_func_Exclusion_impl(dest, src, length, QFullCoverage()); + else + comp_func_Exclusion_impl(dest, src, length, QPartialCoverage(const_alpha)); +} + +#if defined(Q_CC_RVCT) +// Restore pragma state from previous #pragma arm +# pragma pop +#endif + +void QT_FASTCALL rasterop_solid_SourceOrDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) + *dest++ |= color; +} + +void QT_FASTCALL rasterop_SourceOrDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) + *dest++ |= *src++; +} + +void QT_FASTCALL rasterop_solid_SourceAndDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color |= 0xff000000; + while (length--) + *dest++ &= color; +} + +void QT_FASTCALL rasterop_SourceAndDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (*src & *dest) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_SourceXorDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color &= 0x00ffffff; + while (length--) + *dest++ ^= color; +} + +void QT_FASTCALL rasterop_SourceXorDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (*src ^ *dest) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_NotSourceAndNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color = ~color; + while (length--) { + *dest = (color & ~(*dest)) | 0xff000000; + ++dest; + } +} + +void QT_FASTCALL rasterop_NotSourceAndNotDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (~(*src) & ~(*dest)) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_NotSourceOrNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color = ~color | 0xff000000; + while (length--) { + *dest = color | ~(*dest); + ++dest; + } +} + +void QT_FASTCALL rasterop_NotSourceOrNotDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = ~(*src) | ~(*dest) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_NotSourceXorDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color = ~color & 0x00ffffff; + while (length--) { + *dest = color ^ (*dest); + ++dest; + } +} + +void QT_FASTCALL rasterop_NotSourceXorDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = ((~(*src)) ^ (*dest)) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_NotSource(uint *dest, int length, + uint color, uint const_alpha) +{ + Q_UNUSED(const_alpha); + qt_memfill(dest, ~color | 0xff000000, length); +} + +void QT_FASTCALL rasterop_NotSource(uint *dest, const uint *src, + int length, uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) + *dest++ = ~(*src++) | 0xff000000; +} + +void QT_FASTCALL rasterop_solid_NotSourceAndDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color = ~color | 0xff000000; + while (length--) { + *dest = color & *dest; + ++dest; + } +} + +void QT_FASTCALL rasterop_NotSourceAndDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (~(*src) & *dest) | 0xff000000; + ++dest; ++src; + } +} + +void QT_FASTCALL rasterop_solid_SourceAndNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (color & ~(*dest)) | 0xff000000; + ++dest; + } +} + +void QT_FASTCALL rasterop_SourceAndNotDestination(uint *dest, + const uint *src, + int length, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + while (length--) { + *dest = (*src & ~(*dest)) | 0xff000000; + ++dest; ++src; + } +} + +static CompositionFunctionSolid functionForModeSolid_C[] = { + comp_func_solid_SourceOver, + comp_func_solid_DestinationOver, + comp_func_solid_Clear, + comp_func_solid_Source, + comp_func_solid_Destination, + comp_func_solid_SourceIn, + comp_func_solid_DestinationIn, + comp_func_solid_SourceOut, + comp_func_solid_DestinationOut, + comp_func_solid_SourceAtop, + comp_func_solid_DestinationAtop, + comp_func_solid_XOR, + comp_func_solid_Plus, + comp_func_solid_Multiply, + comp_func_solid_Screen, + comp_func_solid_Overlay, + comp_func_solid_Darken, + comp_func_solid_Lighten, + comp_func_solid_ColorDodge, + comp_func_solid_ColorBurn, + comp_func_solid_HardLight, + comp_func_solid_SoftLight, + comp_func_solid_Difference, + comp_func_solid_Exclusion, + rasterop_solid_SourceOrDestination, + rasterop_solid_SourceAndDestination, + rasterop_solid_SourceXorDestination, + rasterop_solid_NotSourceAndNotDestination, + rasterop_solid_NotSourceOrNotDestination, + rasterop_solid_NotSourceXorDestination, + rasterop_solid_NotSource, + rasterop_solid_NotSourceAndDestination, + rasterop_solid_SourceAndNotDestination +}; + +static const CompositionFunctionSolid *functionForModeSolid = functionForModeSolid_C; + +static CompositionFunction functionForMode_C[] = { + comp_func_SourceOver, + comp_func_DestinationOver, + comp_func_Clear, + comp_func_Source, + comp_func_Destination, + comp_func_SourceIn, + comp_func_DestinationIn, + comp_func_SourceOut, + comp_func_DestinationOut, + comp_func_SourceAtop, + comp_func_DestinationAtop, + comp_func_XOR, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +static const CompositionFunction *functionForMode = functionForMode_C; + +static TextureBlendType getBlendType(const QSpanData *data) +{ + TextureBlendType ft; + if (data->txop <= QTransform::TxTranslate) + if (data->texture.type == QTextureData::Tiled) + ft = BlendTiled; + else + ft = BlendUntransformed; + else if (data->bilinear) + if (data->texture.type == QTextureData::Tiled) + ft = BlendTransformedBilinearTiled; + else + ft = BlendTransformedBilinear; + else + if (data->texture.type == QTextureData::Tiled) + ft = BlendTransformedTiled; + else + ft = BlendTransformed; + return ft; +} + +static inline Operator getOperator(const QSpanData *data, const QSpan *spans, int spanCount) +{ + Operator op; + bool solidSource = false; + + switch(data->type) { + case QSpanData::Solid: + solidSource = (qAlpha(data->solid.color) == 255); + break; + case QSpanData::LinearGradient: + solidSource = !data->gradient.alphaColor; + getLinearGradientValues(&op.linear, data); + op.src_fetch = fetchLinearGradient; + break; + case QSpanData::RadialGradient: + solidSource = !data->gradient.alphaColor; + getRadialGradientValues(&op.radial, data); + op.src_fetch = fetchRadialGradient; + break; + case QSpanData::ConicalGradient: + solidSource = !data->gradient.alphaColor; + op.src_fetch = fetchConicalGradient; + break; + case QSpanData::Texture: + op.src_fetch = sourceFetch[getBlendType(data)][data->texture.format]; + solidSource = !data->texture.hasAlpha; + default: + break; + } + + op.mode = data->rasterBuffer->compositionMode; + if (op.mode == QPainter::CompositionMode_SourceOver && solidSource) + op.mode = QPainter::CompositionMode_Source; + + op.dest_fetch = destFetchProc[data->rasterBuffer->format]; + if (op.mode == QPainter::CompositionMode_Source) { + switch (data->rasterBuffer->format) { + case QImage::Format_RGB32: + case QImage::Format_ARGB32_Premultiplied: + // don't clear dest_fetch as it sets up the pointer correctly to save one copy + break; + default: { + const QSpan *lastSpan = spans + spanCount; + bool alphaSpans = false; + while (spans < lastSpan) { + if (spans->coverage != 255) { + alphaSpans = true; + break; + } + ++spans; + } + if (!alphaSpans) + op.dest_fetch = 0; + } + } + } + + op.dest_store = destStoreProc[data->rasterBuffer->format]; + + op.funcSolid = functionForModeSolid[op.mode]; + op.func = functionForMode[op.mode]; + + return op; +} + + + +// -------------------- blend methods --------------------- + +enum SpanMethod { + RegularSpans, + CallbackSpans +}; + +#if !defined(Q_CC_SUN) +static +#endif +void drawBufferSpan(QSpanData *data, const uint *buffer, int bufsize, + int x, int y, int length, uint const_alpha) +{ +#if defined (Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) + data->rasterEngine->drawBufferSpan(buffer, bufsize, x, y, length, const_alpha); +#else + Q_UNUSED(data); + Q_UNUSED(buffer); + Q_UNUSED(bufsize); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(length); + Q_UNUSED(const_alpha); +#endif +} + +#if !defined(Q_CC_SUN) +static +#endif +void blend_color_generic(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + uint buffer[buffer_size]; + Operator op = getOperator(data, spans, count); + + while (count--) { + int x = spans->x; + int length = spans->len; + while (length) { + int l = qMin(buffer_size, length); + uint *dest = op.dest_fetch ? op.dest_fetch(buffer, data->rasterBuffer, x, spans->y, l) : buffer; + op.funcSolid(dest, l, data->solid.color, spans->coverage); + if (op.dest_store) + op.dest_store(data->rasterBuffer, x, spans->y, dest, l); + length -= l; + x += l; + } + ++spans; + } +} + +#if defined (Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +static void blend_color_generic_callback(int count, const QSpan *spans, void *userData) +{ + // ### Falcon + Q_UNUSED(count); + Q_UNUSED(spans); + Q_UNUSED(userData); +// QSpanData *data = reinterpret_cast<QSpanData*>(userData); +// data->rasterEngine->drawColorSpans(spans, count, data->solid.color); +} +#endif // QT_NO_RASTERCALLBACKS + +static void blend_color_argb(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + Operator op = getOperator(data, spans, count); + + if (op.mode == QPainter::CompositionMode_Source) { + // inline for performance + while (count--) { + uint *target = ((uint *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + if (spans->coverage == 255) { + QT_MEMFILL_UINT(target, spans->len, data->solid.color); + } else { + uint c = BYTE_MUL(data->solid.color, spans->coverage); + int ialpha = 255 - spans->coverage; + for (int i = 0; i < spans->len; ++i) + target[i] = c + BYTE_MUL(target[i], ialpha); + } + ++spans; + } + return; + } + + while (count--) { + uint *target = ((uint *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + op.funcSolid(target, spans->len, data->solid.color, spans->coverage); + ++spans; + } +} + +template <class T> +Q_STATIC_TEMPLATE_FUNCTION void blendColor(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + Operator op = getOperator(data, spans, count); + + if (op.mode == QPainter::CompositionMode_Source) { + const T c = qt_colorConvert<T, quint32p>(quint32p::fromRawData(data->solid.color), 0); + while (count--) { + T *target = ((T*)data->rasterBuffer->scanLine(spans->y)) + + spans->x; + if (spans->coverage == 255) { + qt_memfill(target, c, spans->len); + } else { + const quint8 alpha = T::alpha(spans->coverage); + const T color = c.byte_mul(alpha); + const int ialpha = T::ialpha(spans->coverage); + const T *end = target + spans->len; + while (target < end) { + *target = color + target->byte_mul(ialpha); + ++target; + } + } + ++spans; + } + return; + } + + if (op.mode == QPainter::CompositionMode_SourceOver) { + while (count--) { + const quint32 color = BYTE_MUL(data->solid.color, spans->coverage); + const T c = qt_colorConvert<T, quint32p>(quint32p::fromRawData(color), 0); + const quint8 ialpha = T::alpha(qAlpha(~color)); + T *target = ((T*)data->rasterBuffer->scanLine(spans->y)) + spans->x; + const T *end = target + spans->len; + while (target != end) { + *target = c + target->byte_mul(ialpha); + ++target; + } + ++spans; + } + return; + } + + blend_color_generic(count, spans, userData); +} + +#define SPANFUNC_POINTER_BLENDCOLOR(DST) blendColor<DST> + +static void blend_color_rgb16(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + /* + We duplicate a little logic from getOperator() and calculate the + composition mode directly. This allows blend_color_rgb16 to be used + from qt_gradient_quint16 with minimal overhead. + */ + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + if (mode == QPainter::CompositionMode_SourceOver && + qAlpha(data->solid.color) == 255) + mode = QPainter::CompositionMode_Source; + + if (mode == QPainter::CompositionMode_Source) { + // inline for performance + ushort c = qConvertRgb32To16(data->solid.color); + while (count--) { + ushort *target = ((ushort *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + if (spans->coverage == 255) { + QT_MEMFILL_USHORT(target, spans->len, c); + } else { + ushort color = BYTE_MUL_RGB16(c, spans->coverage); + int ialpha = 255 - spans->coverage; + const ushort *end = target + spans->len; + while (target < end) { + *target = color + BYTE_MUL_RGB16(*target, ialpha); + ++target; + } + } + ++spans; + } + return; + } + + if (mode == QPainter::CompositionMode_SourceOver) { + while (count--) { + uint color = BYTE_MUL(data->solid.color, spans->coverage); + int ialpha = qAlpha(~color); + ushort c = qConvertRgb32To16(color); + ushort *target = ((ushort *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + int len = spans->len; + bool pre = (((quintptr)target) & 0x3) != 0; + bool post = false; + if (pre) { + // skip to word boundary + *target = c + BYTE_MUL_RGB16(*target, ialpha); + ++target; + --len; + } + if (len & 0x1) { + post = true; + --len; + } + uint *target32 = (uint*)target; + uint c32 = c | (c<<16); + len >>= 1; + uint salpha = (ialpha+1) >> 3; // calculate here rather than in loop + while (len--) { + // blend full words + *target32 = c32 + BYTE_MUL_RGB16_32(*target32, salpha); + ++target32; + target += 2; + } + if (post) { + // one last pixel beyond a full word + *target = c + BYTE_MUL_RGB16(*target, ialpha); + } + ++spans; + } + return; + } + + blend_color_generic(count, spans, userData); +} + +template <typename T> +void handleSpans(int count, const QSpan *spans, const QSpanData *data, T &handler) +{ + uint const_alpha = 256; + if (data->type == QSpanData::Texture) + const_alpha = data->texture.const_alpha; + + int coverage = 0; + while (count) { + int x = spans->x; + const int y = spans->y; + int right = x + spans->len; + + // compute length of adjacent spans + for (int i = 1; i < count && spans[i].y == y && spans[i].x == right; ++i) + right += spans[i].len; + int length = right - x; + + while (length) { + int l = qMin(buffer_size, length); + length -= l; + + int process_length = l; + int process_x = x; + + const uint *src = handler.fetch(process_x, y, process_length); + int offset = 0; + while (l > 0) { + if (x == spans->x) // new span? + coverage = (spans->coverage * const_alpha) >> 8; + + int right = spans->x + spans->len; + int len = qMin(l, right - x); + + handler.process(x, y, len, coverage, src, offset); + + l -= len; + x += len; + offset += len; + + if (x == right) { // done with current span? + ++spans; + --count; + } + } + handler.store(process_x, y, process_length); + } + } +} + +struct QBlendBase +{ + QBlendBase(QSpanData *d, Operator o) + : data(d) + , op(o) + , dest(0) + { + } + + QSpanData *data; + Operator op; + + uint *dest; + + uint buffer[buffer_size]; + uint src_buffer[buffer_size]; +}; + +template <SpanMethod spanMethod> +class BlendSrcGeneric : public QBlendBase +{ +public: + BlendSrcGeneric(QSpanData *d, Operator o) + : QBlendBase(d, o) + { + } + + const uint *fetch(int x, int y, int len) + { + if (spanMethod == RegularSpans) + dest = op.dest_fetch ? op.dest_fetch(buffer, data->rasterBuffer, x, y, len) : buffer; + + return op.src_fetch(src_buffer, &op, data, y, x, len); + } + + void process(int x, int y, int len, int coverage, const uint *src, int offset) + { + if (spanMethod == RegularSpans) + op.func(dest + offset, src + offset, len, coverage); + else + drawBufferSpan(data, src + offset, len, x, y, len, coverage); + } + + void store(int x, int y, int len) + { + if (spanMethod == RegularSpans && op.dest_store) { + op.dest_store(data->rasterBuffer, x, y, dest, len); + } + } +}; + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_src_generic(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + BlendSrcGeneric<spanMethod> blend(data, getOperator(data, spans, count)); + handleSpans(count, spans, data, blend); +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_untransformed_generic(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + uint buffer[buffer_size]; + uint src_buffer[buffer_size]; + Operator op = getOperator(data, spans, count); + + const int image_width = data->texture.width; + const int image_height = data->texture.height; + int xoff = -qRound(-data->dx); + int yoff = -qRound(-data->dy); + + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if (sy >= 0 && sy < image_height && sx < image_width) { + if (sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + if (sx + length > image_width) + length = image_width - sx; + if (length > 0) { + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + while (length) { + int l = qMin(buffer_size, length); + const uint *src = op.src_fetch(src_buffer, &op, data, sy, sx, l); + if (spanMethod == RegularSpans) { + uint *dest = op.dest_fetch ? op.dest_fetch(buffer, data->rasterBuffer, x, spans->y, l) : buffer; + op.func(dest, src, l, coverage); + if (op.dest_store) + op.dest_store(data->rasterBuffer, x, spans->y, dest, l); + } else { + drawBufferSpan(data, src, l, x, spans->y, + l, coverage); + } + x += l; + sx += l; + length -= l; + } + } + } + ++spans; + } +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_untransformed_argb(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + if (data->texture.format != QImage::Format_ARGB32_Premultiplied + && data->texture.format != QImage::Format_RGB32) { + blend_untransformed_generic<spanMethod>(count, spans, userData); + return; + } + + Operator op = getOperator(data, spans, count); + + const int image_width = data->texture.width; + const int image_height = data->texture.height; + int xoff = -qRound(-data->dx); + int yoff = -qRound(-data->dy); + + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if (sy >= 0 && sy < image_height && sx < image_width) { + if (sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + if (sx + length > image_width) + length = image_width - sx; + if (length > 0) { + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + const uint *src = (uint *)data->texture.scanLine(sy) + sx; + if (spanMethod == RegularSpans) { + uint *dest = ((uint *)data->rasterBuffer->scanLine(spans->y)) + x; + op.func(dest, src, length, coverage); + } else { + drawBufferSpan(data, src, length, x, + spans->y, length, coverage); + } + } + } + ++spans; + } +} + +static inline quint16 interpolate_pixel_rgb16_255(quint16 x, quint8 a, + quint16 y, quint8 b) +{ + quint16 t = ((((x & 0x07e0) * a) + ((y & 0x07e0) * b)) >> 5) & 0x07e0; + t |= ((((x & 0xf81f) * a) + ((y & 0xf81f) * b)) >> 5) & 0xf81f; + + return t; +} + +static inline quint32 interpolate_pixel_rgb16x2_255(quint32 x, quint8 a, + quint32 y, quint8 b) +{ + uint t; + t = ((((x & 0xf81f07e0) >> 5) * a) + (((y & 0xf81f07e0) >> 5) * b)) & 0xf81f07e0; + t |= ((((x & 0x07e0f81f) * a) + ((y & 0x07e0f81f) * b)) >> 5) & 0x07e0f81f; + return t; +} + +static inline void blend_sourceOver_rgb16_rgb16(quint16 *dest, + const quint16 *src, + int length, + const quint8 alpha, + const quint8 ialpha) +{ + const int dstAlign = ((quintptr)dest) & 0x3; + if (dstAlign) { + *dest = interpolate_pixel_rgb16_255(*src, alpha, *dest, ialpha); + ++dest; + ++src; + --length; + } + const int srcAlign = ((quintptr)src) & 0x3; + int length32 = length >> 1; + if (length32 && srcAlign == 0) { + while (length32--) { + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + *dest32 = interpolate_pixel_rgb16x2_255(*src32, alpha, + *dest32, ialpha); + dest += 2; + src += 2; + } + length &= 0x1; + } + while (length--) { + *dest = interpolate_pixel_rgb16_255(*src, alpha, *dest, ialpha); + ++dest; + ++src; + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void madd_2(DST *dest, const quint16 alpha, const SRC *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + dest[0] = dest[0].byte_mul(alpha >> 8) + DST(src[0]); + dest[1] = dest[1].byte_mul(alpha & 0xff) + DST(src[1]); +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void madd_4(DST *dest, const quint32 alpha, const SRC *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + dest[0] = dest[0].byte_mul(alpha >> 24) + DST(src[0]); + dest[1] = dest[1].byte_mul((alpha >> 16) & 0xff) + DST(src[1]); + dest[2] = dest[2].byte_mul((alpha >> 8) & 0xff) + DST(src[2]); + dest[3] = dest[3].byte_mul(alpha & 0xff) + DST(src[3]); +} + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void madd_4(qargb8565 *dest, const quint32 a, const qargb8565 *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + quint32 x, y, t; + quint8 a8; + + { + x = dest32[0]; + y = src32[0]; + + a8 = a >> 24; + + // a0,g0 + t = ((((x & 0x0007e0ff) * a8) >> 5) & 0x0007e0ff) + (y & 0x0007c0f8); + + // r0,b0 + t |= ((((x & 0x00f81f00) * a8) >> 5) & 0x00f81f00) + (y & 0x00f81f00); + + a8 = (a >> 16) & 0xff; + + // a1 + t |= ((((x & 0xff000000) >> 5) * a8) & 0xff000000) + (y & 0xf8000000); + + dest32[0] = t; + } + { + x = dest32[1]; + y = src32[1]; + + // r1,b1 + t = ((((x & 0x0000f81f) * a8) >> 5) & 0x0000f81f) + (y & 0x0000f81f); + + // g1 + t |= ((((x & 0x000007e0) * a8) >> 5) & 0x000007e0) + (y & 0x000007c0); + + a8 = (a >> 8) & 0xff; + + // a2 + t |= ((((x & 0x00ff0000) * a8) >> 5) & 0x00ff0000) + (y & 0x00f80000); + + { + // rgb2 + quint16 x16 = (x >> 24) | ((dest32[2] & 0x000000ff) << 8); + quint16 y16 = (y >> 24) | ((src32[2] & 0x000000ff) << 8); + quint16 t16; + + t16 = ((((x16 & 0xf81f) * a8) >> 5) & 0xf81f) + (y16 & 0xf81f); + t16 |= ((((x16 & 0x07e0) * a8) >> 5) & 0x07e0) + (y16 & 0x07c0); + + // rg2 + t |= ((t16 & 0x00ff) << 24); + + dest32[1] = t; + + x = dest32[2]; + y = src32[2]; + + // gb2 + t = (t16 >> 8); + } + } + { + a8 = a & 0xff; + + // g3,a3 + t |= ((((x & 0x07e0ff00) * a8) >> 5) & 0x07e0ff00) + (y & 0x07c0f800); + + // r3,b3 + t |= ((((x & 0xf81f0000) >> 5) * a8) & 0xf81f0000)+ (y & 0xf81f0000); + + dest32[2] = t; + } +} +#endif + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void madd_4(qargb8555 *dest, const quint32 a, const qargb8555 *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + quint32 x, y, t; + quint8 a8; + + { + x = dest32[0]; + y = src32[0]; + + a8 = a >> 24; + + // a0,g0 + t = ((((x & 0x0003e0ff) * a8) >> 5) & 0x0003e0ff) + (y & 0x0003e0f8); + + // r0,b0 + t |= ((((x & 0x007c1f00) * a8) >> 5) & 0x007c1f00) + (y & 0x007c1f00); + + a8 = (a >> 16) & 0xff; + + // a1 + t |= ((((x & 0xff000000) >> 5) * a8) & 0xff000000) + (y & 0xf8000000); + + dest32[0] = t; + } + { + x = dest32[1]; + y = src32[1]; + + // r1,b1 + t = ((((x & 0x00007c1f) * a8) >> 5) & 0x00007c1f) + (y & 0x00007c1f); + + // g1 + t |= ((((x & 0x000003e0) * a8) >> 5) & 0x000003e0) + (y & 0x000003e0); + + a8 = (a >> 8) & 0xff; + + // a2 + t |= ((((x & 0x00ff0000) * a8) >> 5) & 0x00ff0000) + (y & 0x00f80000); + + { + // rgb2 + quint16 x16 = (x >> 24) | ((dest32[2] & 0x000000ff) << 8); + quint16 y16 = (y >> 24) | ((src32[2] & 0x000000ff) << 8); + quint16 t16; + + t16 = ((((x16 & 0x7c1f) * a8) >> 5) & 0x7c1f) + (y16 & 0x7c1f); + t16 |= ((((x16 & 0x03e0) * a8) >> 5) & 0x03e0) + (y16 & 0x03e0); + + // rg2 + t |= ((t16 & 0x00ff) << 24); + + dest32[1] = t; + + x = dest32[2]; + y = src32[2]; + + // gb2 + t = (t16 >> 8); + } + } + { + a8 = a & 0xff; + + // g3,a3 + t |= ((((x & 0x03e0ff00) * a8) >> 5) & 0x03e0ff00) + (y & 0x03e0f800); + + // r3,b3 + t |= ((((x & 0x7c1f0000) >> 5) * a8) & 0x7c1f0000)+ (y & 0x7c1f0000); + + dest32[2] = t; + } +} +#endif + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 alpha_2(const T *src) +{ + Q_ASSERT((quintptr(src) & 0x3) == 0); + + if (T::hasAlpha()) + return (src[0].alpha() << 8) | src[1].alpha(); + else + return 0xffff; +} + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 alpha_4(const T *src) +{ + Q_ASSERT((quintptr(src) & 0x3) == 0); + + if (T::hasAlpha()) { + return (src[0].alpha() << 24) | (src[1].alpha() << 16) + | (src[2].alpha() << 8) | src[3].alpha(); + } else { + return 0xffffffff; + } +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 alpha_4(const qargb8565 *src) +{ + const quint8 *src8 = reinterpret_cast<const quint8*>(src); + return src8[0] << 24 | src8[3] << 16 | src8[6] << 8 | src8[9]; +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 alpha_4(const qargb6666 *src) +{ + const quint8 *src8 = reinterpret_cast<const quint8*>(src); + return ((src8[2] & 0xfc) | (src8[2] >> 6)) << 24 + | ((src8[5] & 0xfc) | (src8[5] >> 6)) << 16 + | ((src8[8] & 0xfc) | (src8[8] >> 6)) << 8 + | ((src8[11] & 0xfc) | (src8[11] >> 6)); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 alpha_4(const qargb8555 *src) +{ + Q_ASSERT((quintptr(src) & 0x3) == 0); + const quint8 *src8 = reinterpret_cast<const quint8*>(src); + return src8[0] << 24 | src8[3] << 16 | src8[6] << 8 | src8[9]; +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 alpha_2(const qargb4444 *src) +{ + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + const quint32 t = (*src32 & 0xf000f000) | + ((*src32 & 0xf000f000) >> 4); + return (t >> 24) | (t & 0xff00); +} + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_alpha_2(quint16 alpha, const T*) +{ + return (T::alpha((alpha >> 8) & 0xff) << 8) + | T::alpha(alpha & 0xff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_alpha_2(quint16 a, const qrgb565*) +{ + return ((((a & 0xff00) + 0x0100) >> 3) & 0xff00) + | ((((a & 0x00ff) + 0x0001) >> 3) & 0x00ff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_alpha_2(quint16 a, const qrgb444*) +{ + return (((a & 0x00ff) + 0x0001) >> 4) + | ((((a & 0xff00) + 0x0100) >> 4) & 0xff00); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_alpha_2(quint16 a, const qargb4444*) +{ + return (((a & 0x00ff) + 0x0001) >> 4) + | ((((a & 0xff00) + 0x0100) >> 4) & 0xff00); +} + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_ialpha_2(quint16 alpha, const T*) +{ + return (T::ialpha((alpha >> 8) & 0xff) << 8) + | T::ialpha(alpha & 0xff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_ialpha_2(quint16 a, const qrgb565 *dummy) +{ + return 0x2020 - eff_alpha_2(a, dummy); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_ialpha_2(quint16 a, const qargb4444 *dummy) +{ + return 0x1010 - eff_alpha_2(a, dummy); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint16 eff_ialpha_2(quint16 a, const qrgb444 *dummy) +{ + return 0x1010 - eff_alpha_2(a, dummy); +} + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 alpha, const T*) +{ + return (T::alpha(alpha >> 24) << 24) + | (T::alpha((alpha >> 16) & 0xff) << 16) + | (T::alpha((alpha >> 8) & 0xff) << 8) + | T::alpha(alpha & 0xff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 a, const qrgb888*) +{ + return a; +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 a, const qargb8565*) +{ + return ((((a & 0xff00ff00) + 0x01000100) >> 3) & 0xff00ff00) + | ((((a & 0x00ff00ff) + 0x00010001) >> 3) & 0x00ff00ff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 a, const qargb6666*) +{ + return ((((a & 0xff00ff00) >> 2) + 0x00400040) & 0xff00ff00) + | ((((a & 0x00ff00ff) + 0x00010001) >> 2) & 0x00ff00ff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 a, const qrgb666*) +{ + return ((((a & 0xff00ff00) >> 2) + 0x00400040) & 0xff00ff00) + | ((((a & 0x00ff00ff) + 0x00010001) >> 2) & 0x00ff00ff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_alpha_4(quint32 a, const qargb8555*) +{ + return ((((a & 0xff00ff00) + 0x01000100) >> 3) & 0xff00ff00) + | ((((a & 0x00ff00ff) + 0x00010001) >> 3) & 0x00ff00ff); +} + +template <class T> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 alpha, const T*) +{ + return (T::ialpha(alpha >> 24) << 24) + | (T::ialpha((alpha >> 16) & 0xff) << 16) + | (T::ialpha((alpha >> 8) & 0xff) << 8) + | T::ialpha(alpha & 0xff); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 a, const qrgb888*) +{ + return ~a; +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 a, const qargb8565 *dummy) +{ + return 0x20202020 - eff_alpha_4(a, dummy); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 a, const qargb6666 *dummy) +{ + return 0x40404040 - eff_alpha_4(a, dummy); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 a, const qrgb666 *dummy) +{ + return 0x40404040 - eff_alpha_4(a, dummy); +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline quint32 eff_ialpha_4(quint32 a, const qargb8555 *dummy) +{ + return 0x20202020 - eff_alpha_4(a, dummy); +} + +template <class DST, class SRC> +inline void interpolate_pixel_unaligned_2(DST *dest, const SRC *src, + quint16 alpha) +{ + const quint16 a = eff_alpha_2(alpha, dest); + const quint16 ia = eff_ialpha_2(alpha, dest); + dest[0] = DST(src[0]).byte_mul(a >> 8) + dest[0].byte_mul(ia >> 8); + dest[1] = DST(src[1]).byte_mul(a & 0xff) + dest[1].byte_mul(ia & 0xff); +} + +template <class DST, class SRC> +inline void interpolate_pixel_2(DST *dest, const SRC *src, quint16 alpha) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint16 a = eff_alpha_2(alpha, dest); + const quint16 ia = eff_ialpha_2(alpha, dest); + + dest[0] = DST(src[0]).byte_mul(a >> 8) + dest[0].byte_mul(ia >> 8); + dest[1] = DST(src[1]).byte_mul(a & 0xff) + dest[1].byte_mul(ia & 0xff); +} + +template <class DST, class SRC> +inline void interpolate_pixel(DST &dest, quint8 a, const SRC &src, quint8 b) +{ + if (SRC::hasAlpha() && !DST::hasAlpha()) + interpolate_pixel(dest, a, DST(src), b); + else + dest = dest.byte_mul(a) + DST(src).byte_mul(b); +} + +template <> +inline void interpolate_pixel(qargb8565 &dest, quint8 a, + const qargb8565 &src, quint8 b) +{ + quint8 *d = reinterpret_cast<quint8*>(&dest); + const quint8 *s = reinterpret_cast<const quint8*>(&src); + d[0] = (d[0] * a + s[0] * b) >> 5; + + const quint16 x = (d[2] << 8) | d[1]; + const quint16 y = (s[2] << 8) | s[1]; + quint16 t = (((x & 0x07e0) * a + (y & 0x07e0) * b) >> 5) & 0x07e0; + t |= (((x & 0xf81f) * a + (y & 0xf81f) * b) >> 5) & 0xf81f; + + d[1] = t & 0xff; + d[2] = t >> 8; +} + +template <> +inline void interpolate_pixel(qrgb565 &dest, quint8 a, + const qrgb565 &src, quint8 b) +{ + const quint16 x = dest.rawValue(); + const quint16 y = src.rawValue(); + quint16 t = (((x & 0x07e0) * a + (y & 0x07e0) * b) >> 5) & 0x07e0; + t |= (((x & 0xf81f) * a + (y & 0xf81f) * b) >> 5) & 0xf81f; + dest = t; +} + +template <> +inline void interpolate_pixel(qrgb555 &dest, quint8 a, + const qrgb555 &src, quint8 b) +{ + const quint16 x = dest.rawValue(); + const quint16 y = src.rawValue(); + quint16 t = (((x & 0x03e0) * a + (y & 0x03e0) * b) >> 5) & 0x03e0; + t |= ((((x & 0x7c1f) * a) + ((y & 0x7c1f) * b)) >> 5) & 0x7c1f; + dest = t; +} + +template <> +inline void interpolate_pixel(qrgb444 &dest, quint8 a, + const qrgb444 &src, quint8 b) +{ + const quint16 x = dest.rawValue(); + const quint16 y = src.rawValue(); + quint16 t = ((x & 0x00f0) * a + (y & 0x00f0) * b) & 0x0f00; + t |= ((x & 0x0f0f) * a + (y & 0x0f0f) * b) & 0xf0f0; + quint16 *d = reinterpret_cast<quint16*>(&dest); + *d = (t >> 4); +} + +template <class DST, class SRC> +inline void interpolate_pixel_2(DST *dest, quint8 a, + const SRC *src, quint8 b) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + Q_ASSERT(!SRC::hasAlpha()); + + dest[0] = dest[0].byte_mul(a) + DST(src[0]).byte_mul(b); + dest[1] = dest[1].byte_mul(a) + DST(src[1]).byte_mul(b); +} + +template <> +inline void interpolate_pixel_2(qrgb565 *dest, quint8 a, + const qrgb565 *src, quint8 b) +{ + quint32 *x = reinterpret_cast<quint32*>(dest); + const quint32 *y = reinterpret_cast<const quint32*>(src); + quint32 t = (((*x & 0xf81f07e0) >> 5) * a + + ((*y & 0xf81f07e0) >> 5) * b) & 0xf81f07e0; + t |= (((*x & 0x07e0f81f) * a + + (*y & 0x07e0f81f) * b) >> 5) & 0x07e0f81f; + *x = t; +} + +template <> +inline void interpolate_pixel_2(qrgb555 *dest, quint8 a, + const qrgb555 *src, quint8 b) +{ + quint32 *x = reinterpret_cast<quint32*>(dest); + const quint32 *y = reinterpret_cast<const quint32*>(src); + quint32 t = (((*x & 0x7c1f03e0) >> 5) * a + + ((*y & 0x7c1f03e0) >> 5) * b) & 0x7c1f03e0; + t |= (((*x & 0x03e07c1f) * a + + (*y & 0x03e07c1f) * b) >> 5) & 0x03e07c1f; + *x = t; +} + +template <> +inline void interpolate_pixel_2(qrgb444 *dest, quint8 a, + const qrgb444 *src, quint8 b) +{ + quint32 *x = reinterpret_cast<quint32*>(dest); + const quint32 *y = reinterpret_cast<const quint32*>(src); + quint32 t = ((*x & 0x0f0f0f0f) * a + (*y & 0x0f0f0f0f) * b) & 0xf0f0f0f0; + t |= ((*x & 0x00f000f0) * a + (*y & 0x00f000f0) * b) & 0x0f000f00; + *x = t >> 4; +} + +template <class DST, class SRC> +inline void interpolate_pixel_4(DST *dest, const SRC *src, quint32 alpha) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = eff_alpha_4(alpha, dest); + const quint32 ia = eff_ialpha_4(alpha, dest); + dest[0] = DST(src[0]).byte_mul(a >> 24) + + dest[0].byte_mul(ia >> 24); + dest[1] = DST(src[1]).byte_mul((a >> 16) & 0xff) + + dest[1].byte_mul((ia >> 16) & 0xff); + dest[2] = DST(src[2]).byte_mul((a >> 8) & 0xff) + + dest[2].byte_mul((ia >> 8) & 0xff); + dest[3] = DST(src[3]).byte_mul(a & 0xff) + + dest[3].byte_mul(ia & 0xff); +} + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +template <> +inline void interpolate_pixel_4(qargb8565 *dest, const qargb8565 *src, + quint32 alpha) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = eff_alpha_4(alpha, dest); + const quint32 ia = eff_ialpha_4(alpha, dest); + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + + quint32 x, y, t; + quint8 a8, ia8; + { + x = src32[0]; + y = dest32[0]; + + a8 = a >> 24; + ia8 = ia >> 24; + + // a0,g0 + t = (((x & 0x0007e0ff) * a8 + (y & 0x0007e0ff) * ia8) >> 5) + & 0x0007e0ff; + + // r0,b0 + t |= (((x & 0x00f81f00) * a8 + (y & 0x00f81f00) * ia8) >> 5) + & 0x00f81f00; + + a8 = (a >> 16) & 0xff; + ia8 = (ia >> 16) & 0xff; + + // a1 + t |= (((x & 0xff000000) >> 5) * a8 + ((y & 0xff000000) >> 5) * ia8) + & 0xff000000; + + dest32[0] = t; + } + { + x = src32[1]; + y = dest32[1]; + + // r1,b1 + t = (((x & 0x0000f81f) * a8 + (y & 0x0000f81f) * ia8) >> 5) + & 0x0000f81f; + + // g1 + t |= (((x & 0x000007e0) * a8 + (y & 0x000007e0) * ia8) >> 5) + & 0x000007e0; + + a8 = (a >> 8) & 0xff; + ia8 = (ia >> 8) & 0xff; + + // a2 + t |= (((x & 0x00ff0000) * a8 + (y & 0x00ff0000) * ia8) >> 5) + & 0x00ff0000; + + { + // rgb2 + quint16 x16 = (x >> 24) | ((src32[2] & 0x000000ff) << 8); + quint16 y16 = (y >> 24) | ((dest32[2] & 0x000000ff) << 8); + quint16 t16; + + t16 = (((x16 & 0xf81f) * a8 + (y16 & 0xf81f) * ia8) >> 5) & 0xf81f; + t16 |= (((x16 & 0x07e0) * a8 + (y16 & 0x07e0) * ia8) >> 5) & 0x07e0; + + // rg2 + t |= ((t16 & 0x00ff) << 24); + + dest32[1] = t; + + x = src32[2]; + y = dest32[2]; + + // gb2 + t = (t16 >> 8); + } + } + { + a8 = a & 0xff; + ia8 = ia & 0xff; + + // g3,a3 + t |= (((x & 0x07e0ff00) * a8 + (y & 0x07e0ff00) * ia8) >> 5) + & 0x07e0ff00; + + // r3,b3 + t |= (((x & 0xf81f0000) >> 5) * a8 + ((y & 0xf81f0000) >> 5) * ia8) + & 0xf81f0000; + + dest32[2] = t; + } +} +#endif + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +template <> +inline void interpolate_pixel_4(qargb8555 *dest, const qargb8555 *src, + quint32 alpha) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + + const quint32 a = eff_alpha_4(alpha, dest); + const quint32 ia = eff_ialpha_4(alpha, dest); + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + + quint32 x, y, t; + quint8 a8, ia8; + { + x = src32[0]; + y = dest32[0]; + + a8 = a >> 24; + ia8 = ia >> 24; + + // a0,g0 + t = (((x & 0x0003e0ff) * a8 + (y & 0x0003e0ff) * ia8) >> 5) + & 0x0003e0ff; + + // r0,b0 + t |= (((x & 0x007c1f00) * a8 + (y & 0x007c1f00) * ia8) >> 5) + & 0x007c1f00; + + a8 = (a >> 16) & 0xff; + ia8 = (ia >> 16) & 0xff; + + // a1 + t |= (((x & 0xff000000) >> 5) * a8 + ((y & 0xff000000) >> 5) * ia8) + & 0xff000000; + + dest32[0] = t; + } + { + x = src32[1]; + y = dest32[1]; + + // r1,b1 + t = (((x & 0x00007c1f) * a8 + (y & 0x00007c1f) * ia8) >> 5) + & 0x00007c1f; + + // g1 + t |= (((x & 0x000003e0) * a8 + (y & 0x000003e0) * ia8) >> 5) + & 0x000003e0; + + a8 = (a >> 8) & 0xff; + ia8 = (ia >> 8) & 0xff; + + // a2 + t |= (((x & 0x00ff0000) * a8 + (y & 0x00ff0000) * ia8) >> 5) + & 0x00ff0000; + + { + // rgb2 + quint16 x16 = (x >> 24) | ((src32[2] & 0x000000ff) << 8); + quint16 y16 = (y >> 24) | ((dest32[2] & 0x000000ff) << 8); + quint16 t16; + + t16 = (((x16 & 0x7c1f) * a8 + (y16 & 0x7c1f) * ia8) >> 5) & 0x7c1f; + t16 |= (((x16 & 0x03e0) * a8 + (y16 & 0x03e0) * ia8) >> 5) & 0x03e0; + + // rg2 + t |= ((t16 & 0x00ff) << 24); + + dest32[1] = t; + + x = src32[2]; + y = dest32[2]; + + // gb2 + t = (t16 >> 8); + } + } + { + a8 = a & 0xff; + ia8 = ia & 0xff; + + // g3,a3 + t |= (((x & 0x03e0ff00) * a8 + (y & 0x03e0ff00) * ia8) >> 5) + & 0x03e0ff00; + + // r3,b3 + t |= (((x & 0x7c1f0000) >> 5) * a8 + ((y & 0x7c1f0000) >> 5) * ia8) + & 0x7c1f0000; + + dest32[2] = t; + } +} +#endif + +template <> +inline void interpolate_pixel_4(qrgb888 *dest, const qrgb888 *src, + quint32 alpha) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = eff_alpha_4(alpha, dest); + const quint32 ia = eff_ialpha_4(alpha, dest); + const quint32 *src32 = reinterpret_cast<const quint32*>(src); + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + + { + quint32 x = src32[0]; + quint32 y = dest32[0]; + + quint32 t; + t = ((x >> 8) & 0xff00ff) * (a >> 24) + + ((y >> 8) & 0xff00ff) * (ia >> 24); + t = (t + ((t >> 8) & 0xff00ff) + 0x800080); + t &= 0xff00ff00; + + x = (x & 0xff0000) * (a >> 24) + + (x & 0x0000ff) * ((a >> 16) & 0xff) + + (y & 0xff0000) * (ia >> 24) + + (y & 0x0000ff) * ((ia >> 16) & 0xff); + x = (x + ((x >> 8) & 0xff00ff) + 0x800080) >> 8; + x &= 0x00ff00ff; + + dest32[0] = x | t; + } + { + quint32 x = src32[1]; + quint32 y = dest32[1]; + + quint32 t; + t = ((x >> 8) & 0xff0000) * ((a >> 16) & 0xff) + + ((x >> 8) & 0x0000ff) * ((a >> 8) & 0xff) + + ((y >> 8) & 0xff0000) * ((ia >> 16) & 0xff) + + ((y >> 8) & 0x0000ff) * ((ia >> 8) & 0xff); + t = (t + ((t >> 8) & 0xff00ff) + 0x800080); + t &= 0xff00ff00; + + x = (x & 0xff0000) * ((a >> 16) & 0xff) + + (x & 0x0000ff) * ((a >> 8) & 0xff) + + (y & 0xff0000) * ((ia >> 16) & 0xff) + + (y & 0x0000ff) * ((ia >> 8) & 0xff); + x = (x + ((x >> 8) & 0xff00ff) + 0x800080) >> 8; + x &= 0x00ff00ff; + + dest32[1] = x | t; + } + { + quint32 x = src32[2]; + quint32 y = dest32[2]; + + quint32 t; + t = ((x >> 8) & 0xff0000) * ((a >> 8) & 0xff) + + ((x >> 8) & 0x0000ff) * (a & 0xff) + + ((y >> 8) & 0xff0000) * ((ia >> 8) & 0xff) + + ((y >> 8) & 0x0000ff) * (ia & 0xff); + t = (t + ((t >> 8) & 0xff00ff) + 0x800080); + t &= 0xff00ff00; + + x = (x & 0xff00ff) * (a & 0xff) + + (y & 0xff00ff) * (ia & 0xff); + x = (x + ((x >> 8) & 0xff00ff) + 0x800080) >> 8; + x &= 0x00ff00ff; + + dest32[2] = x | t; + } +} + +template <class DST, class SRC> +inline void interpolate_pixel_4(DST *dest, quint8 a, + const SRC *src, quint8 b) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + dest[0] = dest[0].byte_mul(a) + DST(src[0]).byte_mul(b); + dest[1] = dest[1].byte_mul(a) + DST(src[1]).byte_mul(b); + dest[2] = dest[2].byte_mul(a) + DST(src[2]).byte_mul(b); + dest[3] = dest[3].byte_mul(a) + DST(src[3]).byte_mul(b); +} + +template <class DST, class SRC> +inline void blend_sourceOver_4(DST *dest, const SRC *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = alpha_4(src); + if (a == 0xffffffff) { + qt_memconvert(dest, src, 4); + } else if (a > 0) { + quint32 buf[3]; // array of quint32 to get correct alignment + qt_memconvert((DST*)(void*)buf, src, 4); + madd_4(dest, eff_ialpha_4(a, dest), (DST*)(void*)buf); + } +} + +template <> +inline void blend_sourceOver_4(qargb8565 *dest, const qargb8565 *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = alpha_4(src); + if (a == 0xffffffff) { + qt_memconvert(dest, src, 4); + } else if (a > 0) { + madd_4(dest, eff_ialpha_4(a, dest), src); + } +} + +template <> +inline void blend_sourceOver_4(qargb8555 *dest, const qargb8555 *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = alpha_4(src); + if (a == 0xffffffff) { + qt_memconvert(dest, src, 4); + } else if (a > 0) { + madd_4(dest, eff_ialpha_4(a, dest), src); + } +} + +template <> +inline void blend_sourceOver_4(qargb6666 *dest, const qargb6666 *src) +{ + Q_ASSERT((quintptr(dest) & 0x3) == 0); + Q_ASSERT((quintptr(src) & 0x3) == 0); + + const quint32 a = alpha_4(src); + if (a == 0xffffffff) { + qt_memconvert(dest, src, 4); + } else if (a > 0) { + madd_4(dest, eff_ialpha_4(a, dest), src); + } +} + +template <class DST, class SRC> +void QT_FASTCALL blendUntransformed_unaligned(DST *dest, const SRC *src, + quint8 coverage, int length) +{ + Q_ASSERT(coverage > 0); + + if (coverage < 255) { + if (SRC::hasAlpha()) { + for (int i = 0; i < length; ++i) { + if (src[i].alpha()) { + const quint8 alpha = qt_div_255(int(src[i].alpha()) * int(coverage)); + interpolate_pixel(dest[i], DST::ialpha(alpha), + src[i], DST::alpha(alpha)); + } + } + } else { + const quint8 alpha = DST::alpha(coverage); + const quint8 ialpha = DST::ialpha(coverage); + if (alpha) { + for (int i = 0; i < length; ++i) + interpolate_pixel(dest[i], ialpha, src[i], alpha); + } + } + return; + } + + Q_ASSERT(coverage == 0xff); + Q_ASSERT(SRC::hasAlpha()); + + if (SRC::hasAlpha()) { + for (int i = 0; i < length; ++i) { + const quint8 a = src->alpha(); + if (a == 0xff) + *dest = DST(*src); + else if (a > 0) { + if (DST::hasAlpha()) + *dest = DST(*src).truncedAlpha() + dest->byte_mul(DST::ialpha(a)); + else + *dest = DST(SRC(*src).truncedAlpha()) + dest->byte_mul(DST::ialpha(a)); + } + ++src; + ++dest; + } + } +} + +template <class DST, class SRC> +void QT_FASTCALL blendUntransformed_dest16(DST *dest, const SRC *src, + quint8 coverage, int length) +{ + Q_ASSERT(sizeof(DST) == 2); + Q_ASSERT(sizeof(SRC) == 2); + Q_ASSERT((quintptr(dest) & 0x3) == (quintptr(src) & 0x3)); + Q_ASSERT(coverage > 0); + + const int align = quintptr(dest) & 0x3; + + if (coverage < 255) { + // align + if (align) { + const quint8 alpha = SRC::hasAlpha() + ? qt_div_255(int(src->alpha()) * int(coverage)) + : coverage; + if (alpha) { + interpolate_pixel(*dest, DST::ialpha(alpha), + *src, DST::alpha(alpha)); + } + ++dest; + ++src; + --length; + } + + if (SRC::hasAlpha()) { + while (length >= 2) { + const quint16 alpha16 = BYTE_MUL(uint(alpha_2(src)), uint(coverage)); + interpolate_pixel_2(dest, src, alpha16); + length -= 2; + src += 2; + dest += 2; + } + } else { + const quint8 alpha = DST::alpha(coverage); + const quint8 ialpha = DST::ialpha(coverage); + + while (length >= 2) { + interpolate_pixel_2(dest, ialpha, src, alpha); + length -= 2; + src += 2; + dest += 2; + } + } + + // tail + if (length) { + const quint8 alpha = SRC::hasAlpha() + ? qt_div_255(int(src->alpha()) * int(coverage)) + : coverage; + if (alpha) { + interpolate_pixel(*dest, DST::ialpha(alpha), + *src, DST::alpha(alpha)); + } + } + + return; + } + + Q_ASSERT(SRC::hasAlpha()); + if (SRC::hasAlpha()) { + if (align) { + const quint8 alpha = src->alpha(); + if (alpha == 0xff) + *dest = DST(*src); + else if (alpha > 0) + *dest = DST(*src).truncedAlpha() + dest->byte_mul(DST::ialpha(alpha)); + ++dest; + ++src; + --length; + } + + while (length >= 2) { + Q_ASSERT((quintptr(dest) & 3) == 0); + Q_ASSERT((quintptr(src) & 3) == 0); + + const quint16 a = alpha_2(src); + if (a == 0xffff) { + qt_memconvert(dest, src, 2); + } else if (a > 0) { + quint32 buf; + if (sizeof(DST) == 2) + qt_memconvert((DST*)(void*)&buf, src, 2); + madd_2(dest, eff_ialpha_2(a, dest), (DST*)(void*)&buf); + } + + length -= 2; + src += 2; + dest += 2; + } + + if (length) { + const quint8 alpha = src->alpha(); + if (alpha == 0xff) + *dest = DST(*src); + else if (alpha > 0) + *dest = DST(*src).truncedAlpha() + dest->byte_mul(DST::ialpha(alpha)); + } + } +} + +template <class DST, class SRC> +void QT_FASTCALL blendUntransformed_dest24(DST *dest, const SRC *src, + quint8 coverage, int length) +{ + Q_ASSERT((quintptr(dest) & 0x3) == (quintptr(src) & 0x3)); + Q_ASSERT(sizeof(DST) == 3); + Q_ASSERT(coverage > 0); + + const int align = quintptr(dest) & 0x3; + + if (coverage < 255) { + // align + for (int i = 0; i < align; ++i) { + if (SRC::hasAlpha()) { + const quint8 alpha = qt_div_255(int(src->alpha()) * int(coverage)); + if (alpha) + interpolate_pixel(*dest, DST::ialpha(alpha), + *src, DST::alpha(alpha)); + } else { + interpolate_pixel(*dest, DST::ialpha(coverage), + *src, DST::alpha(coverage)); + } + ++dest; + ++src; + --length; + } + + if (SRC::hasAlpha()) { + while (length >= 4) { + const quint32 alpha = QT_PREPEND_NAMESPACE(BYTE_MUL)(uint(alpha_4(src)), uint(coverage)); + if (alpha) + interpolate_pixel_4(dest, src, alpha); + length -= 4; + src += 4; + dest += 4; + } + } else { + const quint8 alpha = DST::alpha(coverage); + const quint8 ialpha = DST::ialpha(coverage); + while (length >= 4) { + interpolate_pixel_4(dest, ialpha, src, alpha); + length -= 4; + src += 4; + dest += 4; + } + } + + // tail + while (length--) { + if (SRC::hasAlpha()) { + const quint8 alpha = qt_div_255(int(src->alpha()) * int(coverage)); + if (alpha) + interpolate_pixel(*dest, DST::ialpha(alpha), + *src, DST::alpha(alpha)); + } else { + interpolate_pixel(*dest, DST::ialpha(coverage), + *src, DST::alpha(coverage)); + } + ++dest; + ++src; + } + + return; + } + + + Q_ASSERT(coverage == 255); + Q_ASSERT(SRC::hasAlpha()); + + if (SRC::hasAlpha()) { + // align + for (int i = 0; i < align; ++i) { + const quint8 a = src->alpha(); + if (a == 0xff) { + *dest = DST(*src); + } else if (a > 0) { + *dest = DST(*src).truncedAlpha() + dest->byte_mul(DST::ialpha(a)); + } + ++dest; + ++src; + --length; + } + + while (length >= 4) { + blend_sourceOver_4(dest, src); + length -= 4; + src += 4; + dest += 4; + } + + // tail + while (length--) { + const quint8 a = src->alpha(); + if (a == 0xff) { + *dest = DST(*src); + } else if (a > 0) { + *dest = DST(*src).truncedAlpha() + dest->byte_mul(DST::ialpha(a)); + } + ++dest; + ++src; + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_SPECIALIZATION +void QT_FASTCALL blendUntransformed(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData*>(userData); + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + + if (mode != QPainter::CompositionMode_SourceOver && + mode != QPainter::CompositionMode_Source) + { + blend_src_generic<RegularSpans>(count, spans, userData); + return; + } + + const bool modeSource = !SRC::hasAlpha() || + mode == QPainter::CompositionMode_Source; + const int image_width = data->texture.width; + const int image_height = data->texture.height; + int xoff = -qRound(-data->dx); + int yoff = -qRound(-data->dy); + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if (sy >= 0 && sy < image_height && sx < image_width) { + if (sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + if (sx + length > image_width) + length = image_width - sx; + if (length > 0) { + DST *dest = ((DST*)data->rasterBuffer->scanLine(spans->y)) + x; + const SRC *src = (SRC*)data->texture.scanLine(sy) + sx; + if (modeSource && coverage == 255) { + qt_memconvert<DST, SRC>(dest, src, length); + } else if (sizeof(DST) == 3 && sizeof(SRC) == 3 && length >= 3 && + (quintptr(dest) & 3) == (quintptr(src) & 3)) + { + blendUntransformed_dest24(dest, src, coverage, length); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && length >= 3 && + (quintptr(dest) & 3) == (quintptr(src) & 3)) + { + blendUntransformed_dest16(dest, src, coverage, length); + } else { + blendUntransformed_unaligned(dest, src, coverage, length); + } + } + } + ++spans; + } +} + +static void blend_untransformed_rgb888(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_24) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB888) + blendUntransformed<qrgb888, qrgb888>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_argb6666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendUntransformed<qargb6666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendUntransformed<qargb6666, qrgb666>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_rgb666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendUntransformed<qrgb666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendUntransformed<qrgb666, qrgb666>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_argb8565(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendUntransformed<qargb8565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendUntransformed<qargb8565, qrgb565>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_rgb565(int count, const QSpan *spans, + void *userData) +{ +#if !defined(Q_WS_QWS) || defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendUntransformed<qrgb565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendUntransformed<qrgb565, qrgb565>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_argb8555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendUntransformed<qargb8555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendUntransformed<qargb8555, qrgb555>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_rgb555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendUntransformed<qrgb555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendUntransformed<qrgb555, qrgb555>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_argb4444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendUntransformed<qargb4444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendUntransformed<qargb4444, qrgb444>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +static void blend_untransformed_rgb444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendUntransformed<qrgb444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendUntransformed<qrgb444, qrgb444>(count, spans, userData); + else +#endif + blend_untransformed_generic<RegularSpans>(count, spans, userData); +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_tiled_generic(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + uint buffer[buffer_size]; + uint src_buffer[buffer_size]; + Operator op = getOperator(data, spans, count); + + const int image_width = data->texture.width; + const int image_height = data->texture.height; + int xoff = -qRound(-data->dx) % image_width; + int yoff = -qRound(-data->dy) % image_height; + + if (xoff < 0) + xoff += image_width; + if (yoff < 0) + yoff += image_height; + + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = (xoff + spans->x) % image_width; + int sy = (spans->y + yoff) % image_height; + if (sx < 0) + sx += image_width; + if (sy < 0) + sy += image_height; + + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + while (length) { + int l = qMin(image_width - sx, length); + if (buffer_size < l) + l = buffer_size; + const uint *src = op.src_fetch(src_buffer, &op, data, sy, sx, l); + if (spanMethod == RegularSpans) { + uint *dest = op.dest_fetch ? op.dest_fetch(buffer, data->rasterBuffer, x, spans->y, l) : buffer; + op.func(dest, src, l, coverage); + if (op.dest_store) + op.dest_store(data->rasterBuffer, x, spans->y, dest, l); + } else { + drawBufferSpan(data, src, l, x, spans->y, l, + coverage); + } + x += l; + sx += l; + length -= l; + if (sx >= image_width) + sx = 0; + } + ++spans; + } +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_tiled_argb(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + if (data->texture.format != QImage::Format_ARGB32_Premultiplied + && data->texture.format != QImage::Format_RGB32) { + blend_tiled_generic<spanMethod>(count, spans, userData); + return; + } + + Operator op = getOperator(data, spans, count); + + int image_width = data->texture.width; + int image_height = data->texture.height; + int xoff = -qRound(-data->dx) % image_width; + int yoff = -qRound(-data->dy) % image_height; + + if (xoff < 0) + xoff += image_width; + if (yoff < 0) + yoff += image_height; + + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = (xoff + spans->x) % image_width; + int sy = (spans->y + yoff) % image_height; + if (sx < 0) + sx += image_width; + if (sy < 0) + sy += image_height; + + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + while (length) { + int l = qMin(image_width - sx, length); + if (buffer_size < l) + l = buffer_size; + const uint *src = (uint *)data->texture.scanLine(sy) + sx; + if (spanMethod == RegularSpans) { + uint *dest = ((uint *)data->rasterBuffer->scanLine(spans->y)) + x; + op.func(dest, src, l, coverage); + } else { + drawBufferSpan(data, src, buffer_size, + x, spans->y, l, coverage); + } + x += l; + length -= l; + sx = 0; + } + ++spans; + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION void blendTiled(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData*>(userData); + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + + if (mode != QPainter::CompositionMode_SourceOver && + mode != QPainter::CompositionMode_Source) + { + blend_src_generic<RegularSpans>(count, spans, userData); + return; + } + + const bool modeSource = !SRC::hasAlpha() || + mode == QPainter::CompositionMode_Source; + const int image_width = data->texture.width; + const int image_height = data->texture.height; + int xoff = -qRound(-data->dx) % image_width; + int yoff = -qRound(-data->dy) % image_height; + + if (xoff < 0) + xoff += image_width; + if (yoff < 0) + yoff += image_height; + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + int x = spans->x; + int length = spans->len; + int sx = (xoff + spans->x) % image_width; + int sy = (spans->y + yoff) % image_height; + if (sx < 0) + sx += image_width; + if (sy < 0) + sy += image_height; + + if (modeSource && coverage == 255) { + // Copy the first texture block + length = qMin(image_width,length); + int tx = x; + while (length) { + int l = qMin(image_width - sx, length); + if (buffer_size < l) + l = buffer_size; + DST *dest = ((DST*)data->rasterBuffer->scanLine(spans->y)) + tx; + const SRC *src = (SRC*)data->texture.scanLine(sy) + sx; + + qt_memconvert<DST, SRC>(dest, src, l); + length -= l; + tx += l; + sx = 0; + } + + // Now use the rasterBuffer as the source of the texture, + // We can now progressively copy larger blocks + // - Less cpu time in code figuring out what to copy + // We are dealing with one block of data + // - More likely to fit in the cache + // - can use memcpy + int copy_image_width = qMin(image_width, int(spans->len)); + length = spans->len - copy_image_width; + DST *src = ((DST*)data->rasterBuffer->scanLine(spans->y)) + x; + DST *dest = src + copy_image_width; + while (copy_image_width < length) { + qt_memconvert(dest, src, copy_image_width); + dest += copy_image_width; + length -= copy_image_width; + copy_image_width *= 2; + } + if (length > 0) + qt_memconvert(dest, src, length); + } else { + while (length) { + int l = qMin(image_width - sx, length); + if (buffer_size < l) + l = buffer_size; + DST *dest = ((DST*)data->rasterBuffer->scanLine(spans->y)) + x; + const SRC *src = (SRC*)data->texture.scanLine(sy) + sx; + if (sizeof(DST) == 3 && sizeof(SRC) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(src) & 3)) + { + blendUntransformed_dest24(dest, src, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(src) & 3)) + { + blendUntransformed_dest16(dest, src, coverage, l); + } else { + blendUntransformed_unaligned(dest, src, coverage, l); + } + + x += l; + length -= l; + sx = 0; + } + } + ++spans; + } +} + +static void blend_tiled_rgb888(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_24) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB888) + blendTiled<qrgb888, qrgb888>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_argb6666(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTiled<qargb6666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTiled<qargb6666, qrgb666>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_rgb666(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTiled<qrgb666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTiled<qrgb666, qrgb666>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_argb8565(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTiled<qargb8565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTiled<qargb8565, qrgb565>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_rgb565(int count, const QSpan *spans, void *userData) +{ +#if !defined(Q_WS_QWS) || defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTiled<qrgb565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTiled<qrgb565, qrgb565>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_argb8555(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTiled<qargb8555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTiled<qargb8555, qrgb555>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_rgb555(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTiled<qrgb555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTiled<qrgb555, qrgb555>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_argb4444(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTiled<qargb4444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTiled<qargb4444, qrgb444>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +static void blend_tiled_rgb444(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTiled<qrgb444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTiled<qrgb444, qrgb444>(count, spans, userData); + else +#endif + blend_tiled_generic<RegularSpans>(count, spans, userData); +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION void blendTransformedBilinear(int count, const QSpan *spans, + void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData*>(userData); + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + + + if (mode != QPainter::CompositionMode_SourceOver) { + blend_src_generic<RegularSpans>(count, spans, userData); + return; + } + + SRC buffer[buffer_size]; + + const int src_minx = data->texture.x1; + const int src_miny = data->texture.y1; + const int src_maxx = data->texture.x2 - 1; + const int src_maxy = data->texture.y2 - 1; + + if (data->fast_matrix) { + // The increment pr x in the scanline + const int fdx = (int)(data->m11 * fixed_scale); + const int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + int x = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale) - half_point; + int y = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale) - half_point; + int length = spans->len; + + while (length) { + const int l = qMin(length, buffer_size); + + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + int x1 = (x >> 16); + int x2; + int y1 = (y >> 16); + int y2; + + const int distx = (x & 0x0000ffff) >> 8; + const int disty = (y & 0x0000ffff) >> 8; + + if (x1 < src_minx) { + x2 = x1 = src_minx; + } else if (x1 >= src_maxx) { + x2 = x1 = src_maxx; + } else { + x2 = x1 + 1; + } + if (y1 < src_miny) { + y2 = y1 = src_miny; + } else if (y1 >= src_maxy) { + y2 = y1 = src_maxy; + } else { + y2 = y1 + 1; + } +#if 0 + if (x1 == x2) { + if (y1 == y2) { + *b = ((SRC*)data->texture.scanLine(y1))[x1]; + } else { + *b = ((SRC*)data->texture.scanLine(y1))[x1]; + const SRC t = data->texture.scanLine(y2)[x1]; + interpolate_pixel(*b, SRC::ialpha(disty), + t, SRC::alpha(disty)); + } + } else if (y1 == y2) { + *b = ((SRC*)data->texture.scanLine(y1))[x1]; + const SRC t = ((SRC*)data->texture.scanLine(y1))[x2]; + interpolate_pixel(*b, SRC::ialpha(distx), + t, SRC::alpha(distx)); + } else +#endif + { + const SRC *src1 = (SRC*)data->texture.scanLine(y1); + const SRC *src2 = (SRC*)data->texture.scanLine(y2); + SRC tl = src1[x1]; + const SRC tr = src1[x2]; + SRC bl = src2[x1]; + const SRC br = src2[x2]; + const quint8 ax = SRC::alpha(distx); + const quint8 iax = SRC::ialpha(distx); + + interpolate_pixel(tl, iax, tr, ax); + interpolate_pixel(bl, iax, br, ax); + interpolate_pixel(tl, SRC::ialpha(disty), + bl, SRC::alpha(disty)); + *b = tl; + } + ++b; + + x += fdx; + y += fdy; + } + + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + qreal x = data->m21 * cy + data->m11 * cx + data->dx; + qreal y = data->m22 * cy + data->m12 * cx + data->dy; + qreal w = data->m23 * cy + data->m13 * cx + data->m33; + + int length = spans->len; + while (length) { + const int l = qMin(length, buffer_size); + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + const qreal iw = w == 0 ? 1 : 1 / w; + const qreal px = x * iw - qreal(0.5); + const qreal py = y * iw - qreal(0.5); + + int x1 = int(px) - (px < 0); + int x2; + int y1 = int(py) - (py < 0); + int y2; + + const int distx = int((px - x1) * 256); + const int disty = int((py - y1) * 256); + + if (x1 < src_minx) { + x2 = x1 = src_minx; + } else if (x1 >= src_maxx) { + x2 = x1 = src_maxx; + } else { + x2 = x1 + 1; + } + if (y1 < src_miny) { + y2 = y1 = src_miny; + } else if (y1 >= src_maxy) { + y2 = y1 = src_maxy; + } else { + y2 = y1 + 1; + } + + const SRC *src1 = (SRC*)data->texture.scanLine(y1); + const SRC *src2 = (SRC*)data->texture.scanLine(y2); + SRC tl = src1[x1]; + const SRC tr = src1[x2]; + SRC bl = src2[x1]; + const SRC br = src2[x2]; + const quint8 ax = SRC::alpha(distx); + const quint8 iax = SRC::ialpha(distx); + + interpolate_pixel(tl, iax, tr, ax); + interpolate_pixel(bl, iax, br, ax); + interpolate_pixel(tl, SRC::ialpha(disty), + bl, SRC::alpha(disty)); + *b = tl; + ++b; + + x += fdx; + y += fdy; + w += fdw; + } + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } +} + +static void blend_transformed_bilinear_rgb888(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_24) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB888) + blendTransformedBilinear<qrgb888, qrgb888>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_argb6666(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformedBilinear<qargb6666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformedBilinear<qargb6666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_rgb666(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformedBilinear<qrgb666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformedBilinear<qrgb666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_argb8565(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformedBilinear<qargb8565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTransformedBilinear<qargb8565, qrgb565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_rgb565(int count, const QSpan *spans, + void *userData) +{ +#if !defined(Q_WS_QWS) || defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB16) + blendTransformedBilinear<qrgb565, qrgb565>(count, spans, userData); + else if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformedBilinear<qrgb565, qargb8565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_argb8555(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformedBilinear<qargb8555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformedBilinear<qargb8555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_rgb555(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformedBilinear<qrgb555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformedBilinear<qrgb555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_argb4444(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformedBilinear<qargb4444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformedBilinear<qargb4444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_bilinear_rgb444(int count, const QSpan *spans, void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformedBilinear<qrgb444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformedBilinear<qrgb444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_transformed_argb(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + if (data->texture.format != QImage::Format_ARGB32_Premultiplied + && data->texture.format != QImage::Format_RGB32) { + blend_src_generic<spanMethod>(count, spans, userData); + return; + } + + CompositionFunction func = functionForMode[data->rasterBuffer->compositionMode]; + uint buffer[buffer_size]; + + int image_width = data->texture.width; + int image_height = data->texture.height; + const int scanline_offset = data->texture.bytesPerLine / 4; + + if (data->fast_matrix) { + // The increment pr x in the scanline + int fdx = (int)(data->m11 * fixed_scale); + int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + void *t = data->rasterBuffer->scanLine(spans->y); + + uint *target = ((uint *)t) + spans->x; + uint *image_bits = (uint *)data->texture.imageData; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + int x = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int y = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + + int length = spans->len; + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + while (length) { + int l = qMin(length, buffer_size); + const uint *end = buffer + l; + uint *b = buffer; + while (b < end) { + int px = x >> 16; + int py = y >> 16; + + bool out = (px < 0) || (px >= image_width) + || (py < 0) || (py >= image_height); + + int y_offset = py * scanline_offset; + *b = out ? uint(0) : image_bits[y_offset + px]; + x += fdx; + y += fdy; + ++b; + } + if (spanMethod == RegularSpans) + func(target, buffer, l, coverage); + else + drawBufferSpan(data, buffer, buffer_size, + spans->x + spans->len - length, + spans->y, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + while (count--) { + void *t = data->rasterBuffer->scanLine(spans->y); + + uint *target = ((uint *)t) + spans->x; + uint *image_bits = (uint *)data->texture.imageData; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + qreal x = data->m21 * cy + data->m11 * cx + data->dx; + qreal y = data->m22 * cy + data->m12 * cx + data->dy; + qreal w = data->m23 * cy + data->m13 * cx + data->m33; + + int length = spans->len; + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + while (length) { + int l = qMin(length, buffer_size); + const uint *end = buffer + l; + uint *b = buffer; + while (b < end) { + const qreal iw = w == 0 ? 1 : 1 / w; + const qreal tx = x * iw; + const qreal ty = y * iw; + const int px = int(tx) - (tx < 0); + const int py = int(ty) - (ty < 0); + + bool out = (px < 0) || (px >= image_width) + || (py < 0) || (py >= image_height); + + int y_offset = py * scanline_offset; + *b = out ? uint(0) : image_bits[y_offset + px]; + x += fdx; + y += fdy; + w += fdw; + + ++b; + } + if (spanMethod == RegularSpans) + func(target, buffer, l, coverage); + else + drawBufferSpan(data, buffer, buffer_size, + spans->x + spans->len - length, + spans->y, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION void blendTransformed(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData*>(userData); + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + + if (mode != QPainter::CompositionMode_SourceOver) { + blend_src_generic<RegularSpans>(count, spans, userData); + return; + } + + SRC buffer[buffer_size]; + const int image_width = data->texture.width; + const int image_height = data->texture.height; + + if (data->fast_matrix) { + // The increment pr x in the scanline + const int fdx = (int)(data->m11 * fixed_scale); + const int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + int x = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int y = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + int length = spans->len; + + while (length) { + const int l = qMin(length, buffer_size); + + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + const int px = (x >> 16); + const int py = (y >> 16); + + if ((px < 0) || (px >= image_width) || + (py < 0) || (py >= image_height)) + { + *b = 0; + } else { + *b = ((SRC*)data->texture.scanLine(py))[px]; + } + ++b; + + x += fdx; + y += fdy; + } + + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && sizeof(SRC) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + qreal x = data->m21 * cy + data->m11 * cx + data->dx; + qreal y = data->m22 * cy + data->m12 * cx + data->dy; + qreal w = data->m23 * cy + data->m13 * cx + data->m33; + + int length = spans->len; + while (length) { + const int l = qMin(length, buffer_size); + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + const qreal iw = w == 0 ? 1 : 1 / w; + const qreal tx = x * iw; + const qreal ty = y * iw; + + const int px = int(tx) - (tx < 0); + const int py = int(ty) - (ty < 0); + + if ((px < 0) || (px >= image_width) || + (py < 0) || (py >= image_height)) + { + *b = 0; + } else { + *b = ((SRC*)data->texture.scanLine(py))[px]; + } + ++b; + + x += fdx; + y += fdy; + w += fdw; + } + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && sizeof(SRC) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } +} + +static void blend_transformed_rgb888(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_24) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB888) + blendTransformed<qrgb888, qrgb888>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_argb6666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformed<qargb6666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformed<qargb6666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_rgb666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformed<qrgb666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformed<qrgb666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_argb8565(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformed<qargb8565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTransformed<qargb8565, qrgb565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_rgb565(int count, const QSpan *spans, + void *userData) +{ +#if !defined(Q_WS_QWS) || defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformed<qrgb565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTransformed<qrgb565, qrgb565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_argb8555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformed<qargb8555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformed<qargb8555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_rgb555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformed<qrgb555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformed<qrgb555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_argb4444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformed<qargb4444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformed<qargb4444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_rgb444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformed<qrgb444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformed<qrgb444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +template <SpanMethod spanMethod> +Q_STATIC_TEMPLATE_FUNCTION void blend_transformed_tiled_argb(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + if (data->texture.format != QImage::Format_ARGB32_Premultiplied + && data->texture.format != QImage::Format_RGB32) { + blend_src_generic<spanMethod>(count, spans, userData); + return; + } + + CompositionFunction func = functionForMode[data->rasterBuffer->compositionMode]; + uint buffer[buffer_size]; + + int image_width = data->texture.width; + int image_height = data->texture.height; + const int scanline_offset = data->texture.bytesPerLine / 4; + + if (data->fast_matrix) { + // The increment pr x in the scanline + int fdx = (int)(data->m11 * fixed_scale); + int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + void *t = data->rasterBuffer->scanLine(spans->y); + + uint *target = ((uint *)t) + spans->x; + uint *image_bits = (uint *)data->texture.imageData; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + int x = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int y = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + int length = spans->len; + while (length) { + int l = qMin(length, buffer_size); + const uint *end = buffer + l; + uint *b = buffer; + while (b < end) { + int px = x >> 16; + int py = y >> 16; + px %= image_width; + py %= image_height; + if (px < 0) px += image_width; + if (py < 0) py += image_height; + int y_offset = py * scanline_offset; + + Q_ASSERT(px >= 0 && px < image_width); + Q_ASSERT(py >= 0 && py < image_height); + + *b = image_bits[y_offset + px]; + x += fdx; + y += fdy; + ++b; + } + if (spanMethod == RegularSpans) + func(target, buffer, l, coverage); + else + drawBufferSpan(data, buffer, buffer_size, + spans->x + spans->len - length, + spans->y, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + while (count--) { + void *t = data->rasterBuffer->scanLine(spans->y); + + uint *target = ((uint *)t) + spans->x; + uint *image_bits = (uint *)data->texture.imageData; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + qreal x = data->m21 * cy + data->m11 * cx + data->dx; + qreal y = data->m22 * cy + data->m12 * cx + data->dy; + qreal w = data->m23 * cy + data->m13 * cx + data->m33; + + const int coverage = (spans->coverage * data->texture.const_alpha) >> 8; + int length = spans->len; + while (length) { + int l = qMin(length, buffer_size); + const uint *end = buffer + l; + uint *b = buffer; + while (b < end) { + const qreal iw = w == 0 ? 1 : 1 / w; + const qreal tx = x * iw; + const qreal ty = y * iw; + int px = int(tx) - (tx < 0); + int py = int(ty) - (ty < 0); + + px %= image_width; + py %= image_height; + if (px < 0) px += image_width; + if (py < 0) py += image_height; + int y_offset = py * scanline_offset; + + Q_ASSERT(px >= 0 && px < image_width); + Q_ASSERT(py >= 0 && py < image_height); + + *b = image_bits[y_offset + px]; + x += fdx; + y += fdy; + w += fdw; + //force increment to avoid /0 + if (!w) { + w += fdw; + } + ++b; + } + if (spanMethod == RegularSpans) + func(target, buffer, l, coverage); + else + drawBufferSpan(data, buffer, buffer_size, + spans->x + spans->len - length, + spans->y, l, coverage); + target += l; + length -= l; + } + ++spans; + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION void blendTransformedTiled(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData*>(userData); + QPainter::CompositionMode mode = data->rasterBuffer->compositionMode; + + if (mode != QPainter::CompositionMode_SourceOver) { + blend_src_generic<RegularSpans>(count, spans, userData); + return; + } + + SRC buffer[buffer_size]; + const int image_width = data->texture.width; + const int image_height = data->texture.height; + + if (data->fast_matrix) { + // The increment pr x in the scanline + const int fdx = (int)(data->m11 * fixed_scale); + const int fdy = (int)(data->m12 * fixed_scale); + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + int x = int((data->m21 * cy + + data->m11 * cx + data->dx) * fixed_scale); + int y = int((data->m22 * cy + + data->m12 * cx + data->dy) * fixed_scale); + int length = spans->len; + + while (length) { + const int l = qMin(length, buffer_size); + + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + int px = (x >> 16) % image_width; + int py = (y >> 16) % image_height; + + if (px < 0) + px += image_width; + if (py < 0) + py += image_height; + + *b = ((SRC*)data->texture.scanLine(py))[px]; + ++b; + + x += fdx; + y += fdy; + } + + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && sizeof(SRC) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } else { + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + + while (count--) { + const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8; + if (coverage == 0) { + ++spans; + continue; + } + + DST *dest = (DST*)data->rasterBuffer->scanLine(spans->y) + + spans->x; + + const qreal cx = spans->x + qreal(0.5); + const qreal cy = spans->y + qreal(0.5); + + qreal x = data->m21 * cy + data->m11 * cx + data->dx; + qreal y = data->m22 * cy + data->m12 * cx + data->dy; + qreal w = data->m23 * cy + data->m13 * cx + data->m33; + + int length = spans->len; + while (length) { + const int l = qMin(length, buffer_size); + const SRC *end = buffer + l; + SRC *b = buffer; + while (b < end) { + const qreal iw = w == 0 ? 1 : 1 / w; + const qreal tx = x * iw; + const qreal ty = y * iw; + + int px = int(tx) - (tx < 0); + int py = int(ty) - (ty < 0); + + px %= image_width; + py %= image_height; + if (px < 0) + px += image_width; + if (py < 0) + py += image_height; + + *b = ((SRC*)data->texture.scanLine(py))[px]; + ++b; + + x += fdx; + y += fdy; + w += fdw; + // force increment to avoid /0 + if (!w) + w += fdw; + } + if (!SRC::hasAlpha() && coverage == 255) { + qt_memconvert(dest, buffer, l); + } else if (sizeof(DST) == 3 && sizeof(SRC) == 3 && l >= 4 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) + { + blendUntransformed_dest24(dest, buffer, coverage, l); + } else if (sizeof(DST) == 2 && sizeof(SRC) == 2 && l >= 2 && + (quintptr(dest) & 3) == (quintptr(buffer) & 3)) { + blendUntransformed_dest16(dest, buffer, coverage, l); + } else { + blendUntransformed_unaligned(dest, buffer, coverage, l); + } + + dest += l; + length -= l; + } + ++spans; + } + } +} + +static void blend_transformed_tiled_rgb888(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_24) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_RGB888) + blendTransformedTiled<qrgb888, qrgb888>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_argb6666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformedTiled<qargb6666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformedTiled<qargb6666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_rgb666(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_18) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB6666_Premultiplied) + blendTransformedTiled<qrgb666, qargb6666>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB666) + blendTransformedTiled<qrgb666, qrgb666>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_argb8565(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformedTiled<qargb8565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTransformedTiled<qargb8565, qrgb565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_rgb565(int count, const QSpan *spans, + void *userData) +{ +#if !defined(Q_WS_QWS) || defined(QT_QWS_DEPTH_16) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8565_Premultiplied) + blendTransformedTiled<qrgb565, qargb8565>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB16) + blendTransformedTiled<qrgb565, qrgb565>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_argb8555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformedTiled<qargb8555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformedTiled<qargb8555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_rgb555(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_15) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB8555_Premultiplied) + blendTransformedTiled<qrgb555, qargb8555>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB555) + blendTransformedTiled<qrgb555, qrgb555>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_argb4444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformedTiled<qargb4444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformedTiled<qargb4444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +static void blend_transformed_tiled_rgb444(int count, const QSpan *spans, + void *userData) +{ +#if defined(QT_QWS_DEPTH_12) + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + if (data->texture.format == QImage::Format_ARGB4444_Premultiplied) + blendTransformedTiled<qrgb444, qargb4444>(count, spans, userData); + else if (data->texture.format == QImage::Format_RGB444) + blendTransformedTiled<qrgb444, qrgb444>(count, spans, userData); + else +#endif + blend_src_generic<RegularSpans>(count, spans, userData); +} + +# define SPANFUNC_POINTER(Name, Arg) Name<Arg> + + +/* Image formats here are target formats */ +static const ProcessSpans processTextureSpans[NBlendTypes][QImage::NImageFormats] = { + // Untransformed + { + 0, // Invalid + SPANFUNC_POINTER(blend_untransformed_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_untransformed_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_untransformed_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_untransformed_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_untransformed_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_untransformed_argb, RegularSpans), // ARGB32_Premultiplied + blend_untransformed_rgb565, + blend_untransformed_argb8565, + blend_untransformed_rgb666, + blend_untransformed_argb6666, + blend_untransformed_rgb555, + blend_untransformed_argb8555, + blend_untransformed_rgb888, + blend_untransformed_rgb444, + blend_untransformed_argb4444, + }, + // Tiled + { + 0, // Invalid + SPANFUNC_POINTER(blend_tiled_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_tiled_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_tiled_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_tiled_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_tiled_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_tiled_argb, RegularSpans), // ARGB32_Premultiplied + blend_tiled_rgb565, + blend_tiled_argb8565, + blend_tiled_rgb666, + blend_tiled_argb6666, + blend_tiled_rgb555, + blend_tiled_argb8555, + blend_tiled_rgb888, + blend_tiled_rgb444, + blend_tiled_argb4444, + }, + // Transformed + { + 0, // Invalid + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_transformed_argb, RegularSpans), // ARGB32_Premultiplied + blend_transformed_rgb565, + blend_transformed_argb8565, + blend_transformed_rgb666, + blend_transformed_argb6666, + blend_transformed_rgb555, + blend_transformed_argb8555, + blend_transformed_rgb888, + blend_transformed_rgb444, + blend_transformed_argb4444, + }, + // TransformedTiled + { + 0, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_transformed_tiled_argb, RegularSpans), // ARGB32_Premultiplied + blend_transformed_tiled_rgb565, + blend_transformed_tiled_argb8565, + blend_transformed_tiled_rgb666, + blend_transformed_tiled_argb6666, + blend_transformed_tiled_rgb555, + blend_transformed_tiled_argb8555, + blend_transformed_tiled_rgb888, + blend_transformed_tiled_rgb444, + blend_transformed_tiled_argb4444 + }, + // Bilinear + { + 0, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32_Premultiplied + blend_transformed_bilinear_rgb565, + blend_transformed_bilinear_argb8565, + blend_transformed_bilinear_rgb666, + blend_transformed_bilinear_argb6666, + blend_transformed_bilinear_rgb555, + blend_transformed_bilinear_argb8555, + blend_transformed_bilinear_rgb888, + blend_transformed_bilinear_rgb444, + blend_transformed_bilinear_argb4444, + }, + // BilinearTiled + { + 0, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Mono + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // MonoLsb + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // Indexed8 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB32_Premultiplied + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB16 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB8565_Premultiplied + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB666 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB6666_Premultiplied + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB555 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB8555_Premultiplied + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB888 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // RGB444 + SPANFUNC_POINTER(blend_src_generic, RegularSpans), // ARGB4444_Premultiplied + } +}; + +#if defined (Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +static const ProcessSpans processTextureSpansCallback[NBlendTypes][QImage::NImageFormats] = { + // Untransformed + { + 0, // Invalid + blend_untransformed_generic<CallbackSpans>, // Mono + blend_untransformed_generic<CallbackSpans>, // MonoLsb + blend_untransformed_generic<CallbackSpans>, // Indexed8 + blend_untransformed_generic<CallbackSpans>, // RGB32 + blend_untransformed_generic<CallbackSpans>, // ARGB32 + blend_untransformed_argb<CallbackSpans>, // ARGB32_Premultiplied + blend_untransformed_generic<CallbackSpans>, // RGB16 + blend_untransformed_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_untransformed_generic<CallbackSpans>, // RGB666 + blend_untransformed_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_untransformed_generic<CallbackSpans>, // RGB555 + blend_untransformed_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_untransformed_generic<CallbackSpans>, // RGB888 + blend_untransformed_generic<CallbackSpans>, // RGB444 + blend_untransformed_generic<CallbackSpans> // ARGB4444_Premultiplied + }, + // Tiled + { + 0, // Invalid + blend_tiled_generic<CallbackSpans>, // Mono + blend_tiled_generic<CallbackSpans>, // MonoLsb + blend_tiled_generic<CallbackSpans>, // Indexed8 + blend_tiled_generic<CallbackSpans>, // RGB32 + blend_tiled_generic<CallbackSpans>, // ARGB32 + blend_tiled_argb<CallbackSpans>, // ARGB32_Premultiplied + blend_tiled_generic<CallbackSpans>, // RGB16 + blend_tiled_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_tiled_generic<CallbackSpans>, // RGB666 + blend_tiled_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_tiled_generic<CallbackSpans>, // RGB555 + blend_tiled_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_tiled_generic<CallbackSpans>, // RGB888 + blend_tiled_generic<CallbackSpans>, // RGB444 + blend_tiled_generic<CallbackSpans> // ARGB4444_Premultiplied + }, + // Transformed + { + 0, // Invalid + blend_src_generic<CallbackSpans>, // Mono + blend_src_generic<CallbackSpans>, // MonoLsb + blend_src_generic<CallbackSpans>, // Indexed8 + blend_src_generic<CallbackSpans>, // RGB32 + blend_src_generic<CallbackSpans>, // ARGB32 + blend_transformed_argb<CallbackSpans>, // ARGB32_Premultiplied + blend_src_generic<CallbackSpans>, // RGB16 + blend_src_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_src_generic<CallbackSpans>, // RGB666 + blend_src_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_src_generic<CallbackSpans>, // RGB555 + blend_src_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_src_generic<CallbackSpans>, // RGB888 + blend_src_generic<CallbackSpans>, // RGB444 + blend_src_generic<CallbackSpans>, // ARGB4444_Premultiplied + }, + // TransformedTiled + { + 0, + blend_src_generic<CallbackSpans>, // Mono + blend_src_generic<CallbackSpans>, // MonoLsb + blend_src_generic<CallbackSpans>, // Indexed8 + blend_src_generic<CallbackSpans>, // RGB32 + blend_src_generic<CallbackSpans>, // ARGB32 + blend_transformed_tiled_argb<CallbackSpans>, // ARGB32_Premultiplied + blend_src_generic<CallbackSpans>, // RGB16 + blend_src_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_src_generic<CallbackSpans>, // RGB666 + blend_src_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_src_generic<CallbackSpans>, // RGB555 + blend_src_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_src_generic<CallbackSpans>, // RGB888 + blend_src_generic<CallbackSpans>, // RGB444 + blend_src_generic<CallbackSpans> // ARGB4444_Premultiplied + }, + // Bilinear + { + 0, + blend_src_generic<CallbackSpans>, // Mono + blend_src_generic<CallbackSpans>, // MonoLsb + blend_src_generic<CallbackSpans>, // Indexed8 + blend_src_generic<CallbackSpans>, // RGB32 + blend_src_generic<CallbackSpans>, // ARGB32 + blend_src_generic<CallbackSpans>, // ARGB32_Premultiplied + blend_src_generic<CallbackSpans>, // RGB16 + blend_src_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_src_generic<CallbackSpans>, // RGB666 + blend_src_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_src_generic<CallbackSpans>, // RGB555 + blend_src_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_src_generic<CallbackSpans>, // RGB888 + blend_src_generic<CallbackSpans>, // RGB444 + blend_src_generic<CallbackSpans> // ARGB4444_Premultiplied + }, + // BilinearTiled + { + 0, + blend_src_generic<CallbackSpans>, // Mono + blend_src_generic<CallbackSpans>, // MonoLsb + blend_src_generic<CallbackSpans>, // Indexed8 + blend_src_generic<CallbackSpans>, // RGB32 + blend_src_generic<CallbackSpans>, // ARGB32 + blend_src_generic<CallbackSpans>, // ARGB32_Premultiplied + blend_src_generic<CallbackSpans>, // RGB16 + blend_src_generic<CallbackSpans>, // ARGB8565_Premultiplied + blend_src_generic<CallbackSpans>, // RGB666 + blend_src_generic<CallbackSpans>, // ARGB6666_Premultiplied + blend_src_generic<CallbackSpans>, // RGB555 + blend_src_generic<CallbackSpans>, // ARGB8555_Premultiplied + blend_src_generic<CallbackSpans>, // RGB888 + blend_src_generic<CallbackSpans>, // RGB444 + blend_src_generic<CallbackSpans> // ARGB4444_Premultiplied + } +}; +#endif // QT_NO_RASTERCALLBACKS + +void qBlendTexture(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + ProcessSpans proc = processTextureSpans[getBlendType(data)][data->rasterBuffer->format]; + proc(count, spans, userData); +} + +#if defined (Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +void qBlendTextureCallback(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + ProcessSpans proc = processTextureSpansCallback[getBlendType(data)][data->rasterBuffer->format]; + proc(count, spans, userData); +} +#endif // QT_NO_RASTERCALLBACKS + +template <class DST> +inline void qt_bitmapblit_template(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *map, + int mapWidth, int mapHeight, int mapStride, + DST dummy = 0) +{ + Q_UNUSED(dummy); + const DST c = qt_colorConvert<DST, quint32>(color, 0); + DST *dest = reinterpret_cast<DST*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(DST); + + if (mapWidth > 8) { + while (mapHeight--) { + int x0 = 0; + int n = 0; + for (int x = 0; x < mapWidth; x += 8) { + uchar s = map[x >> 3]; + for (int i = 0; i < 8; ++i) { + if (s & 0x80) { + ++n; + } else { + if (n) { + qt_memfill(dest + x0, c, n); + x0 += n + 1; + n = 0; + } else { + ++x0; + } + if (!s) { + x0 += 8 - 1 - i; + break; + } + } + s <<= 1; + } + } + if (n) + qt_memfill(dest + x0, c, n); + dest += destStride; + map += mapStride; + } + } else { + while (mapHeight--) { + int x0 = 0; + int n = 0; + for (uchar s = *map; s; s <<= 1) { + if (s & 0x80) { + ++n; + } else if (n) { + qt_memfill(dest + x0, c, n); + x0 += n + 1; + n = 0; + } else { + ++x0; + } + } + if (n) + qt_memfill(dest + x0, c, n); + dest += destStride; + map += mapStride; + } + } +} + +static void qt_gradient_quint32(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + bool isVerticalGradient = + data->txop <= QTransform::TxScale && + data->type == QSpanData::LinearGradient && + data->gradient.linear.end.x == data->gradient.linear.origin.x; + + if (isVerticalGradient) { + LinearGradientValues linear; + getLinearGradientValues(&linear, data); + + CompositionFunctionSolid funcSolid = + functionForModeSolid[data->rasterBuffer->compositionMode]; + + /* + The logic for vertical gradient calculations is a mathematically + reduced copy of that in fetchLinearGradient() - which is basically: + + qreal ry = data->m22 * (y + 0.5) + data->dy; + qreal t = linear.dy*ry + linear.off; + t *= (GRADIENT_STOPTABLE_SIZE - 1); + quint32 color = + qt_gradient_pixel_fixed(&data->gradient, + int(t * FIXPT_SIZE)); + + This has then been converted to fixed point to improve performance. + */ + const int gss = GRADIENT_STOPTABLE_SIZE - 1; + int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE); + int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE)); + + while (count--) { + int y = spans->y; + int x = spans->x; + + quint32 *dst = (quint32 *)(data->rasterBuffer->scanLine(y)) + x; + quint32 color = + qt_gradient_pixel_fixed(&data->gradient, yinc * y + off); + + funcSolid(dst, spans->len, color, spans->coverage); + ++spans; + } + + } else { + blend_src_generic<RegularSpans>(count, spans, userData); + } +} + +static void qt_gradient_quint16(int count, const QSpan *spans, void *userData) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + + bool isVerticalGradient = + data->txop <= QTransform::TxScale && + data->type == QSpanData::LinearGradient && + data->gradient.linear.end.x == data->gradient.linear.origin.x; + + if (isVerticalGradient) { + + LinearGradientValues linear; + getLinearGradientValues(&linear, data); + + /* + The logic for vertical gradient calculations is a mathematically + reduced copy of that in fetchLinearGradient() - which is basically: + + qreal ry = data->m22 * (y + 0.5) + data->dy; + qreal t = linear.dy*ry + linear.off; + t *= (GRADIENT_STOPTABLE_SIZE - 1); + quint32 color = + qt_gradient_pixel_fixed(&data->gradient, + int(t * FIXPT_SIZE)); + + This has then been converted to fixed point to improve performance. + */ + const int gss = GRADIENT_STOPTABLE_SIZE - 1; + int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE); + int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE)); + + uint oldColor = data->solid.color; + while (count--) { + int y = spans->y; + + quint32 color = qt_gradient_pixel_fixed(&data->gradient, yinc * y + off); + + data->solid.color = color; + blend_color_rgb16(1, spans, userData); + ++spans; + } + data->solid.color = oldColor; + + } else { + blend_src_generic<RegularSpans>(count, spans, userData); + } +} + +inline static void qt_bitmapblit_quint32(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *map, + int mapWidth, int mapHeight, int mapStride) +{ + qt_bitmapblit_template<quint32>(rasterBuffer, x, y, color, + map, mapWidth, mapHeight, mapStride); +} + +inline static void qt_bitmapblit_quint16(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *map, + int mapWidth, int mapHeight, int mapStride) +{ + qt_bitmapblit_template<quint16>(rasterBuffer, x, y, color, + map, mapWidth, mapHeight, mapStride); +} + + +uchar qt_pow_rgb_gamma[256]; +uchar qt_pow_rgb_invgamma[256]; + +uint qt_pow_gamma[256]; +uchar qt_pow_invgamma[2048]; + +static void qt_alphamapblit_quint16(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *map, + int mapWidth, int mapHeight, int mapStride, + const QClipData *) +{ + const quint16 c = qt_colorConvert<quint16, quint32>(color, 0); + quint16 *dest = reinterpret_cast<quint16*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint16); + + while (mapHeight--) { + for (int i = 0; i < mapWidth; ++i) { + const int coverage = map[i]; + + if (coverage == 0) { + // nothing + } else if (coverage == 255) { + dest[i] = c; + } else { + int ialpha = 255 - coverage; + dest[i] = BYTE_MUL_RGB16(c, coverage) + + BYTE_MUL_RGB16(dest[i], ialpha); + } + } + dest += destStride; + map += mapStride; + } +} + +void qt_build_pow_tables() { + qreal smoothing = qreal(1.7); + +#ifdef Q_WS_MAC + // decided by testing a few things on an iMac, should probably get this from the + // system... + smoothing = qreal(2.0); +#endif + +#ifdef Q_WS_WIN + int winSmooth; + if (SystemParametersInfo(0x200C /* SPI_GETFONTSMOOTHINGCONTRAST */, 0, &winSmooth, 0)) + smoothing = winSmooth / qreal(1000.0); + + // Safeguard ourselves against corrupt registry values... + if (smoothing > 5 || smoothing < 1) + smoothing = qreal(1.4); + +#endif + +#ifdef Q_WS_X11 + Q_UNUSED(smoothing); + for (int i=0; i<256; ++i) { + qt_pow_rgb_gamma[i] = uchar(i); + qt_pow_rgb_invgamma[i] = uchar(i); + } +#else + for (int i=0; i<256; ++i) { + qt_pow_rgb_gamma[i] = uchar(qRound(qPow(i / qreal(255.0), smoothing) * 255)); + qt_pow_rgb_invgamma[i] = uchar(qRound(qPow(i / qreal(255.), 1 / smoothing) * 255)); + } +#endif + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + const qreal gray_gamma = 2.31; + for (int i=0; i<256; ++i) + qt_pow_gamma[i] = uint(qRound(qPow(i / qreal(255.), gray_gamma) * 2047)); + for (int i=0; i<2048; ++i) + qt_pow_invgamma[i] = uchar(qRound(qPow(i / qreal(2047.0), 1 / gray_gamma) * 255)); +#endif +} + +static inline void rgbBlendPixel(quint32 *dst, int coverage, int sr, int sg, int sb) +{ + // Do a gray alphablend... + int da = qAlpha(*dst); + int dr = qRed(*dst); + int dg = qGreen(*dst); + int db = qBlue(*dst); + + if (da != 255 +#if defined (Q_WS_WIN) + // Work around GDI messing up alpha channel + && qRed(*dst) <= da && qBlue(*dst) <= da && qGreen(*dst) <= da +#endif + ) { + + int a = qGray(coverage); + sr = qt_div_255(qt_pow_rgb_invgamma[sr] * a); + sg = qt_div_255(qt_pow_rgb_invgamma[sg] * a); + sb = qt_div_255(qt_pow_rgb_invgamma[sb] * a); + + int ia = 255 - a; + dr = qt_div_255(dr * ia); + dg = qt_div_255(dg * ia); + db = qt_div_255(db * ia); + + *dst = ((a + qt_div_255((255 - a) * da)) << 24) + | ((sr + dr) << 16) + | ((sg + dg) << 8) + | ((sb + db)); + return; + } + + int mr = qRed(coverage); + int mg = qGreen(coverage); + int mb = qBlue(coverage); + + dr = qt_pow_rgb_gamma[dr]; + dg = qt_pow_rgb_gamma[dg]; + db = qt_pow_rgb_gamma[db]; + + int nr = qt_div_255((sr - dr) * mr) + dr; + int ng = qt_div_255((sg - dg) * mg) + dg; + int nb = qt_div_255((sb - db) * mb) + db; + + nr = qt_pow_rgb_invgamma[nr]; + ng = qt_pow_rgb_invgamma[ng]; + nb = qt_pow_rgb_invgamma[nb]; + + *dst = qRgb(nr, ng, nb); +} + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) +static inline void grayBlendPixel(quint32 *dst, int coverage, int sr, int sg, int sb) +{ + // Do a gammacorrected gray alphablend... + int dr = qRed(*dst); + int dg = qGreen(*dst); + int db = qBlue(*dst); + + dr = qt_pow_gamma[dr]; + dg = qt_pow_gamma[dg]; + db = qt_pow_gamma[db]; + + int alpha = coverage; + int ialpha = 255 - alpha; + int nr = (sr * alpha + ialpha * dr) / 255; + int ng = (sg * alpha + ialpha * dg) / 255; + int nb = (sb * alpha + ialpha * db) / 255; + + nr = qt_pow_invgamma[nr]; + ng = qt_pow_invgamma[ng]; + nb = qt_pow_invgamma[nb]; + + *dst = qRgb(nr, ng, nb); +} +#endif + +static void qt_alphamapblit_quint32(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *map, + int mapWidth, int mapHeight, int mapStride, + const QClipData *clip) +{ + const quint32 c = color; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint32); + +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + + sr = qt_pow_gamma[sr]; + sg = qt_pow_gamma[sg]; + sb = qt_pow_gamma[sb]; + bool opaque_src = (qAlpha(color) == 255); +#endif + + if (!clip) { + quint32 *dest = reinterpret_cast<quint32*>(rasterBuffer->scanLine(y)) + x; + while (mapHeight--) { + for (int i = 0; i < mapWidth; ++i) { + const int coverage = map[i]; + + if (coverage == 0) { + // nothing + } else if (coverage == 255) { + dest[i] = c; + } else { +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP && opaque_src + && qAlpha(dest[i]) == 255) { + grayBlendPixel(dest+i, coverage, sr, sg, sb); + } else +#endif + { + int ialpha = 255 - coverage; + dest[i] = INTERPOLATE_PIXEL_255(c, coverage, dest[i], ialpha); + } + } + } + dest += destStride; + map += mapStride; + } + } else { + int bottom = qMin(y + mapHeight, rasterBuffer->height()); + + int top = qMax(y, 0); + map += (top - y) * mapStride; + + const_cast<QClipData *>(clip)->initialize(); + for (int yp = top; yp<bottom; ++yp) { + const QClipData::ClipLine &line = clip->m_clipLines[yp]; + + quint32 *dest = reinterpret_cast<quint32 *>(rasterBuffer->scanLine(yp)); + + for (int i=0; i<line.count; ++i) { + const QSpan &clip = line.spans[i]; + + int start = qMax<int>(x, clip.x); + int end = qMin<int>(x + mapWidth, clip.x + clip.len); + + for (int xp=start; xp<end; ++xp) { + const int coverage = map[xp - x]; + + if (coverage == 0) { + // nothing + } else if (coverage == 255) { + dest[xp] = c; + } else { +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP && opaque_src + && qAlpha(dest[xp]) == 255) { + grayBlendPixel(dest+xp, coverage, sr, sg, sb); + } else +#endif + { + int ialpha = 255 - coverage; + dest[xp] = INTERPOLATE_PIXEL_255(c, coverage, dest[xp], ialpha); + } + } + + } // for (i -> line.count) + } // for (yp -> bottom) + map += mapStride; + } + } +} + +static void qt_alphargbblit_quint32(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uint *src, int mapWidth, int mapHeight, int srcStride, + const QClipData *clip) +{ + const quint32 c = color; + + int sr = qRed(color); + int sg = qGreen(color); + int sb = qBlue(color); + int sa = qAlpha(color); + + sr = qt_pow_rgb_gamma[sr]; + sg = qt_pow_rgb_gamma[sg]; + sb = qt_pow_rgb_gamma[sb]; + + if (sa == 0) + return; + + if (!clip) { + quint32 *dst = reinterpret_cast<quint32*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint32); + while (mapHeight--) { + for (int i = 0; i < mapWidth; ++i) { + const uint coverage = src[i]; + if (coverage == 0xffffffff) { + dst[i] = c; + } else if (coverage != 0xff000000) { + rgbBlendPixel(dst+i, coverage, sr, sg, sb); + } + } + + dst += destStride; + src += srcStride; + } + } else { + int bottom = qMin(y + mapHeight, rasterBuffer->height()); + + int top = qMax(y, 0); + src += (top - y) * srcStride; + + const_cast<QClipData *>(clip)->initialize(); + for (int yp = top; yp<bottom; ++yp) { + const QClipData::ClipLine &line = clip->m_clipLines[yp]; + + quint32 *dst = reinterpret_cast<quint32 *>(rasterBuffer->scanLine(yp)); + + for (int i=0; i<line.count; ++i) { + const QSpan &clip = line.spans[i]; + + int start = qMax<int>(x, clip.x); + int end = qMin<int>(x + mapWidth, clip.x + clip.len); + + for (int xp=start; xp<end; ++xp) { + const uint coverage = src[xp - x]; + if (coverage == 0xffffffff) { + dst[xp] = c; + } else if (coverage != 0xff000000) { + rgbBlendPixel(dst+xp, coverage, sr, sg, sb); + } + } + } // for (i -> line.count) + src += srcStride; + } // for (yp -> bottom) + + } +} + +template <class T> +inline void qt_rectfill_template(QRasterBuffer *rasterBuffer, + int x, int y, int width, int height, + quint32 color, T dummy = 0) +{ + Q_UNUSED(dummy); + + qt_rectfill<T>(reinterpret_cast<T*>(rasterBuffer->buffer()), + qt_colorConvert<T, quint32p>(quint32p::fromRawData(color), 0), + x, y, width, height, rasterBuffer->bytesPerLine()); +} + +#define QT_RECTFILL(T) \ + inline static void qt_rectfill_##T(QRasterBuffer *rasterBuffer, \ + int x, int y, int width, int height, \ + quint32 color) \ + { \ + qt_rectfill_template<T>(rasterBuffer, x, y, width, height, color); \ + } + +QT_RECTFILL(quint32) +QT_RECTFILL(quint16) +QT_RECTFILL(qargb8565) +QT_RECTFILL(qrgb666) +QT_RECTFILL(qargb6666) +QT_RECTFILL(qrgb555) +QT_RECTFILL(qargb8555) +QT_RECTFILL(qrgb888) +QT_RECTFILL(qrgb444) +QT_RECTFILL(qargb4444) +#undef QT_RECTFILL + +inline static void qt_rectfill_nonpremul_quint32(QRasterBuffer *rasterBuffer, + int x, int y, int width, int height, + quint32 color) +{ + qt_rectfill<quint32>(reinterpret_cast<quint32 *>(rasterBuffer->buffer()), + INV_PREMUL(color), x, y, width, height, rasterBuffer->bytesPerLine()); +} + + +// Map table for destination image format. Contains function pointers +// for blends of various types unto the destination + +DrawHelper qDrawHelper[QImage::NImageFormats] = +{ + // Format_Invalid, + { 0, 0, 0, 0, 0, 0 }, + // Format_Mono, + { + blend_color_generic, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, 0 + }, + // Format_MonoLSB, + { + blend_color_generic, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, 0 + }, + // Format_Indexed8, + { + blend_color_generic, + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, 0 + }, + // Format_RGB32, + { + blend_color_argb, + qt_gradient_quint32, + qt_bitmapblit_quint32, + qt_alphamapblit_quint32, + qt_alphargbblit_quint32, + qt_rectfill_quint32 + }, + // Format_ARGB32, + { + blend_color_generic, + qt_gradient_quint32, + qt_bitmapblit_quint32, + qt_alphamapblit_quint32, + qt_alphargbblit_quint32, + qt_rectfill_nonpremul_quint32 + }, + // Format_ARGB32_Premultiplied + { + blend_color_argb, + qt_gradient_quint32, + qt_bitmapblit_quint32, + qt_alphamapblit_quint32, + qt_alphargbblit_quint32, + qt_rectfill_quint32 + }, + // Format_RGB16 + { + blend_color_rgb16, + qt_gradient_quint16, + qt_bitmapblit_quint16, + qt_alphamapblit_quint16, + 0, + qt_rectfill_quint16 + }, + // Format_ARGB8565_Premultiplied + { + SPANFUNC_POINTER_BLENDCOLOR(qargb8565), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qargb8565 + }, + // Format_RGB666 + { + SPANFUNC_POINTER_BLENDCOLOR(qrgb666), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qrgb666 + }, + // Format_ARGB6666_Premultiplied + { + SPANFUNC_POINTER_BLENDCOLOR(qargb6666), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qargb6666 + }, + // Format_RGB555 + { + SPANFUNC_POINTER_BLENDCOLOR(qrgb555), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qrgb555 + }, + // Format_ARGB8555_Premultiplied + { + SPANFUNC_POINTER_BLENDCOLOR(qargb8555), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qargb8555 + }, + // Format_RGB888 + { + SPANFUNC_POINTER_BLENDCOLOR(qrgb888), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qrgb888 + }, + // Format_RGB444 + { + SPANFUNC_POINTER_BLENDCOLOR(qrgb444), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qrgb444 + }, + // Format_ARGB4444_Premultiplied + { + SPANFUNC_POINTER_BLENDCOLOR(qargb4444), + SPANFUNC_POINTER(blend_src_generic, RegularSpans), + 0, 0, 0, + qt_rectfill_qargb4444 + } +}; + +#if defined (Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +DrawHelper qDrawHelperCallback[QImage::NImageFormats] = +{ + // Format_Invalid, + { 0, 0, 0, 0, 0, 0 }, + // Format_Mono, + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_MonoLSB, + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_Indexed8, + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB32, + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB32, + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB32_Premultiplied + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB16 + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB8565_Premultiplied + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB666 + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB6666_Premultiplied + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB555 + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB8555_Premultiplied + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB888 + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_RGB444 + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + }, + // Format_ARGB4444_Premultiplied + { + blend_color_generic_callback, + blend_src_generic<CallbackSpans>, + 0, 0, 0, 0 + } +}; +#endif + + + +#if defined(Q_CC_MSVC) && !defined(_MIPS_) +template <class DST, class SRC> +inline void qt_memfill_template(DST *dest, SRC color, int count) +{ + const DST c = qt_colorConvert<DST, SRC>(color, 0); + while (count--) + *dest++ = c; +} + +#else + +template <class DST, class SRC> +inline void qt_memfill_template(DST *dest, SRC color, int count) +{ + const DST c = qt_colorConvert<DST, SRC>(color, 0); + int n = (count + 7) / 8; + switch (count & 0x07) + { + case 0: do { *dest++ = c; + case 7: *dest++ = c; + case 6: *dest++ = c; + case 5: *dest++ = c; + case 4: *dest++ = c; + case 3: *dest++ = c; + case 2: *dest++ = c; + case 1: *dest++ = c; + } while (--n > 0); + } +} + +template <> +inline void qt_memfill_template(quint16 *dest, quint16 value, int count) +{ + if (count < 3) { + switch (count) { + case 2: *dest++ = value; + case 1: *dest = value; + } + return; + } + + const int align = (quintptr)(dest) & 0x3; + switch (align) { + case 2: *dest++ = value; --count; + } + + const quint32 value32 = (value << 16) | value; + qt_memfill(reinterpret_cast<quint32*>(dest), value32, count / 2); + if (count & 0x1) + dest[count - 1] = value; +} +#endif + +static void qt_memfill_quint16(quint16 *dest, quint16 color, int count) +{ + qt_memfill_template<quint16, quint16>(dest, color, count); +} + +typedef void (*qt_memfill32_func)(quint32 *dest, quint32 value, int count); +typedef void (*qt_memfill16_func)(quint16 *dest, quint16 value, int count); +static void qt_memfill32_setup(quint32 *dest, quint32 value, int count); +static void qt_memfill16_setup(quint16 *dest, quint16 value, int count); + +qt_memfill32_func qt_memfill32 = qt_memfill32_setup; +qt_memfill16_func qt_memfill16 = qt_memfill16_setup; + +void qInitDrawhelperAsm() +{ + + qt_memfill32 = qt_memfill_template<quint32, quint32>; + qt_memfill16 = qt_memfill_quint16; //qt_memfill_template<quint16, quint16>; + + CompositionFunction *functionForModeAsm = 0; + CompositionFunctionSolid *functionForModeSolidAsm = 0; + + const uint features = qDetectCPUFeatures(); + if (false) { +#ifdef QT_HAVE_SSE2 + } else if (features & SSE2) { + qt_memfill32 = qt_memfill32_sse2; + qt_memfill16 = qt_memfill16_sse2; + qDrawHelper[QImage::Format_RGB32].bitmapBlit = qt_bitmapblit32_sse2; + qDrawHelper[QImage::Format_ARGB32].bitmapBlit = qt_bitmapblit32_sse2; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].bitmapBlit = qt_bitmapblit32_sse2; + qDrawHelper[QImage::Format_RGB16].bitmapBlit = qt_bitmapblit16_sse2; +#endif +#ifdef QT_HAVE_SSE + } else if (features & SSE) { +// qt_memfill32 = qt_memfill32_sse; + qDrawHelper[QImage::Format_RGB16].bitmapBlit = qt_bitmapblit16_sse; +#ifdef QT_HAVE_3DNOW + if (features & MMX3DNOW) { + qt_memfill32 = qt_memfill32_sse3dnow; + qDrawHelper[QImage::Format_RGB16].bitmapBlit = qt_bitmapblit16_sse3dnow; + } +#endif +#endif // SSE + } +#ifdef QT_HAVE_MMX + if (features & MMX) { + functionForModeAsm = qt_functionForMode_MMX; + + functionForModeSolidAsm = qt_functionForModeSolid_MMX; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].blendColor = qt_blend_color_argb_mmx; +#ifdef QT_HAVE_3DNOW + if (features & MMX3DNOW) { + functionForModeAsm = qt_functionForMode_MMX3DNOW; + functionForModeSolidAsm = qt_functionForModeSolid_MMX3DNOW; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].blendColor = qt_blend_color_argb_mmx3dnow; + } +#endif // 3DNOW + + extern void qt_blend_rgb32_on_rgb32_mmx(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + extern void qt_blend_argb32_on_argb32_mmx(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + + qBlendFunctions[QImage::Format_RGB32][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_mmx; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_mmx; + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_mmx; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_mmx; + + } +#endif // MMX + +#ifdef QT_HAVE_SSE + if (features & SSE) { + extern void qt_blend_rgb32_on_rgb32_sse(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + extern void qt_blend_argb32_on_argb32_sse(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + + qBlendFunctions[QImage::Format_RGB32][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_sse; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_sse; + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_sse; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_sse; + } +#endif // SSE + +#ifdef QT_HAVE_SSE2 + if (features & SSE2) { + extern void qt_blend_rgb32_on_rgb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + extern void qt_blend_argb32_on_argb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + + qBlendFunctions[QImage::Format_RGB32][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_sse2; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_sse2; + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_sse2; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_sse2; + } + +#ifdef QT_HAVE_SSSE3 + if (features & SSSE3) { + extern void qt_blend_argb32_on_argb32_ssse3(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_ssse3; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_ssse3; + } +#endif // SSSE3 + +#endif // SSE2 + +#ifdef QT_HAVE_SSE + if (features & SSE) { + functionForModeAsm = qt_functionForMode_SSE; + functionForModeSolidAsm = qt_functionForModeSolid_SSE; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].blendColor = qt_blend_color_argb_sse; +#ifdef QT_HAVE_3DNOW + if (features & MMX3DNOW) { + functionForModeAsm = qt_functionForMode_SSE3DNOW; + functionForModeSolidAsm = qt_functionForModeSolid_SSE3DNOW; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].blendColor = qt_blend_color_argb_sse3dnow; + } +#endif // 3DNOW + + +#ifdef QT_HAVE_SSE2 + if (features & SSE2) { + extern void QT_FASTCALL comp_func_SourceOver_sse2(uint *destPixels, + const uint *srcPixels, + int length, + uint const_alpha); + extern void QT_FASTCALL comp_func_solid_SourceOver_sse2(uint *destPixels, int length, uint color, uint const_alpha); + extern void QT_FASTCALL comp_func_Plus_sse2(uint *dst, const uint *src, int length, uint const_alpha); + extern void QT_FASTCALL comp_func_Source_sse2(uint *dst, const uint *src, int length, uint const_alpha); + + functionForModeAsm[0] = comp_func_SourceOver_sse2; + functionForModeAsm[QPainter::CompositionMode_Source] = comp_func_Source_sse2; + functionForModeAsm[QPainter::CompositionMode_Plus] = comp_func_Plus_sse2; + functionForModeSolidAsm[0] = comp_func_solid_SourceOver_sse2; + } +#endif + } +#elif defined(QT_HAVE_SSE2) + // this is the special case when SSE2 is usable but MMX/SSE is not usable (e.g.: Windows x64 + visual studio) + if (features & SSE2) { + functionForModeAsm = qt_functionForMode_onlySSE2; + functionForModeSolidAsm = qt_functionForModeSolid_onlySSE2; + } +#endif + +#ifdef QT_HAVE_IWMMXT + if (features & IWMMXT) { + functionForModeAsm = qt_functionForMode_IWMMXT; + functionForModeSolidAsm = qt_functionForModeSolid_IWMMXT; + qDrawHelper[QImage::Format_ARGB32_Premultiplied].blendColor = qt_blend_color_argb_iwmmxt; + } +#endif // IWMMXT + +#if defined(QT_HAVE_ARM_SIMD) + qBlendFunctions[QImage::Format_RGB32][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_arm_simd; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_arm_simd; + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_arm_simd; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_arm_simd; +#elif defined(QT_HAVE_NEON) + if (features & NEON) { + qBlendFunctions[QImage::Format_RGB32][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_neon; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB32] = qt_blend_rgb32_on_rgb32_neon; + qBlendFunctions[QImage::Format_RGB32][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_neon; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_argb32_neon; + qBlendFunctions[QImage::Format_RGB16][QImage::Format_ARGB32_Premultiplied] = qt_blend_argb32_on_rgb16_neon; + qBlendFunctions[QImage::Format_ARGB32_Premultiplied][QImage::Format_RGB16] = qt_blend_rgb16_on_argb32_neon; + qBlendFunctions[QImage::Format_RGB16][QImage::Format_RGB16] = qt_blend_rgb16_on_rgb16_neon; + + qScaleFunctions[QImage::Format_RGB16][QImage::Format_ARGB32_Premultiplied] = qt_scale_image_argb32_on_rgb16_neon; + qScaleFunctions[QImage::Format_RGB16][QImage::Format_RGB16] = qt_scale_image_rgb16_on_rgb16_neon; + + qTransformFunctions[QImage::Format_RGB16][QImage::Format_ARGB32_Premultiplied] = qt_transform_image_argb32_on_rgb16_neon; + qTransformFunctions[QImage::Format_RGB16][QImage::Format_RGB16] = qt_transform_image_rgb16_on_rgb16_neon; + + qDrawHelper[QImage::Format_RGB16].alphamapBlit = qt_alphamapblit_quint16_neon; + + functionForMode_C[QPainter::CompositionMode_SourceOver] = qt_blend_argb32_on_argb32_scanline_neon; + functionForModeSolid_C[QPainter::CompositionMode_SourceOver] = comp_func_solid_SourceOver_neon; + functionForMode_C[QPainter::CompositionMode_Plus] = comp_func_Plus_neon; + destFetchProc[QImage::Format_RGB16] = qt_destFetchRGB16_neon; + destStoreProc[QImage::Format_RGB16] = qt_destStoreRGB16_neon; + + qMemRotateFunctions[QImage::Format_RGB16][0] = qt_memrotate90_16_neon; + qMemRotateFunctions[QImage::Format_RGB16][2] = qt_memrotate270_16_neon; + qt_memfill32 = qt_memfill32_neon; + } +#endif + + if (functionForModeSolidAsm) { + const int destinationMode = QPainter::CompositionMode_Destination; + functionForModeSolidAsm[destinationMode] = functionForModeSolid_C[destinationMode]; + + // use the default qdrawhelper implementation for the + // extended composition modes + for (int mode = 12; mode < 24; ++mode) + functionForModeSolidAsm[mode] = functionForModeSolid_C[mode]; + + functionForModeSolid = functionForModeSolidAsm; + } + if (functionForModeAsm) + functionForMode = functionForModeAsm; + + qt_build_pow_tables(); +} + +static void qt_memfill32_setup(quint32 *dest, quint32 value, int count) +{ + qInitDrawhelperAsm(); + qt_memfill32(dest, value, count); +} + +static void qt_memfill16_setup(quint16 *dest, quint16 value, int count) +{ + qInitDrawhelperAsm(); + qt_memfill16(dest, value, count); +} + +#ifdef QT_QWS_DEPTH_GENERIC + +int qrgb::bpp = 0; +int qrgb::len_red = 0; +int qrgb::len_green = 0; +int qrgb::len_blue = 0; +int qrgb::len_alpha = 0; +int qrgb::off_red = 0; +int qrgb::off_green = 0; +int qrgb::off_blue = 0; +int qrgb::off_alpha = 0; + +template <typename SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_rectconvert_rgb(qrgb *dest, const SRC *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + quint8 *dest8 = reinterpret_cast<quint8*>(dest) + + y * dstStride + x * qrgb::bpp; + + srcStride = srcStride / sizeof(SRC) - width; + dstStride -= (width * qrgb::bpp); + + for (int j = 0; j < height; ++j) { + for (int i = 0; i < width; ++i) { + const quint32 v = qt_convertToRgb<SRC>(*src++); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + for (int j = qrgb::bpp - 1; j >= 0; --j) + *dest8++ = (v >> (8 * j)) & 0xff; +#else + for (int j = 0; j < qrgb::bpp; ++j) + *dest8++ = (v >> (8 * j)) & 0xff; +#endif + } + + dest8 += dstStride; + src += srcStride; + } +} + +template <> +void qt_rectconvert(qrgb *dest, const quint32 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_rgb<quint32>(dest, src, x, y, width, height, + dstStride, srcStride); +} + +template <> +void qt_rectconvert(qrgb *dest, const quint16 *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + qt_rectconvert_rgb<quint16>(dest, src, x, y, width, height, + dstStride, srcStride); +} + +#endif // QT_QWS_DEPTH_GENERIC + +QT_END_NAMESPACE diff --git a/src/gui/painting/qdrawhelper_arm_simd.cpp b/src/gui/painting/qdrawhelper_arm_simd.cpp new file mode 100644 index 0000000000..af9854c03b --- /dev/null +++ b/src/gui/painting/qdrawhelper_arm_simd.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdrawhelper_arm_simd_p.h" + +#include <private/qpaintengine_raster_p.h> +#include <private/qblendfunctions_p.h> + +#ifdef QT_HAVE_ARM_SIMD + +#if defined(Q_OS_SYMBIAN) +#if !defined(__SWITCH_TO_ARM) +#ifdef __MARM_THUMB__ +#ifndef __ARMCC__ +#define __SWITCH_TO_ARM asm("push {r0} ");\ + asm("add r0, pc, #4 ");\ + asm("bx r0 ");\ + asm("nop ");\ + asm(".align 2 ");\ + asm(".code 32 ");\ + asm("ldr r0, [sp], #4 ") +#define __END_ARM asm(".code 16 ") +#else +#define __SWITCH_TO_ARM asm(".code 32 "); +#define __END_ARM +#endif // __ARMCC__ +#else +#define __SWITCH_TO_ARM +#define __END_ARM +#endif //__MARM_THUMB__ +#endif +#endif + +#if defined(Q_OS_SYMBIAN) && defined(Q_CC_RVCT) +__asm void qt_blend_argb32_on_argb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ +#ifndef __ARMCC__ + __SWITCH_TO_ARM; +#else + CODE32 +#endif // __ARMCC__ + + stmfd sp!, {r4-r12, r14} + + // read arguments off the stack + add r8, sp, #10 * 4 + ldmia r8, {r4-r6} + + // adjust dbpl and sbpl + mov r14, #4 + mul r14, r4, r14 + sub r1, r1, r14 + sub r3, r3, r14 + + // load 0xFF00FF00 to r12 + mov r12, #0xFF000000 + add r12, r12, #0xFF00 + + // load 0x800080 to r14 + mov r14, #0x800000 + add r14, r14, #0x80 + + /* + Registers: + r0 dst + r1 dbpl + r2 src + r3 sbpl + r4 w + r5 h + r6 const_alpha + r12 0xFF0000 + r14 0x800080 + */ + + cmp r6, #256 //test if we have fully opaque constant alpha value + bne argb32constalpha // branch if not + +argb32_next_row + + mov r7, r4 + +argb32_next_pixel + + ldr r8, [r2], #4 // load src pixel + + // Negate r8 and extract src alpha + mvn r11, r8 // bitwise not + uxtb r11, r11, ror #24 + + cmp r11, #0 // test for full src opacity (negated) + beq argb32_no_blend + + cmp r11, #255 // test for full src transparency (negated) + addeq r0, #4 + beq argb32_nop + + ldr r9, [r0] // load dst pixel + + // blend + uxtb16 r10, r9 + uxtb16 r6, r9, ror #8 + mla r10, r11, r10, r14 + mla r9, r6, r11, r14 + uxtab16 r10, r10, r10, ror #8 + uxtab16 r9, r9, r9, ror #8 + and r9, r9, r12 + uxtab16 r10, r9, r10, ror #8 + + uqadd8 r8, r10, r8 + +argb32_no_blend + + str r8, [r0], #4 + +argb32_nop + + subs r7, r7, #1 + bgt argb32_next_pixel + + add r0, r0, r1 // dest = dest + dbpl + add r2, r2, r3 // src = src + sbpl + + subs r5, r5, #1 + bgt argb32_next_row + + b argb32_blend_exit + +argb32constalpha + + cmp r6, #0 + beq argb32_blend_exit + + ; const_alpha = (const_alpha * 255) >> 8; + mov r11, #255 + mul r6, r6, r11 + mov r11, r6, lsr #8 + +argb32constalpha_next_row + + mov r7, r4 + +argb32constalpha_next_pixel + + ldr r9, [r2], #4 // load src pixel + + // blend + uxtb16 r10, r9 + uxtb16 r6, r9, ror #8 + mla r10, r11, r10, r14 + mla r9, r6, r11, r14 + uxtab16 r10, r10, r10, ror #8 + uxtab16 r9, r9, r9, ror #8 + and r9, r9, r12 + uxtab16 r8, r9, r10, ror #8 + + ldr r9, [r0] // load dst pixel + + // blend + uxtb16 r10, r9 + uxtb16 r6, r9, ror #8 + + // Negate r8 and extract src alpha + mvn r9, r8 // bitwise not + uxtb r9, r9, ror #24 + + mla r10, r9, r10, r14 + mla r9, r6, r9, r14 + uxtab16 r10, r10, r10, ror #8 + uxtab16 r9, r9, r9, ror #8 + and r9, r9, r12 + uxtab16 r10, r9, r10, ror #8 + + uqadd8 r8, r10, r8 + + str r8, [r0], #4 + + subs r7, r7, #1 + bgt argb32constalpha_next_pixel + + add r0, r0, r1 // dest = dest + dbpl + add r2, r2, r3 // src = src + sbpl + + subs r5, r5, #1 + bgt argb32constalpha_next_row + +argb32_blend_exit + + // Restore registers + ldmfd sp!, {r4-r12, lr} + bx lr + + __END_ARM +} + +void qt_blend_rgb32_on_rgb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + if (const_alpha != 256) { + qt_blend_argb32_on_argb32_arm_simd(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + if (w <= 64) { + for (int y=0; y<h; ++y) { + qt_memconvert(dst, src, w); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else { + int len = w * 4; + for (int y=0; y<h; ++y) { + memcpy(dst, src, len); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +#else // defined(Q_OS_SYMBIAN) && defined(Q_CC_RVCT) + +// TODO: add GNU assembler instructions and support for other platforms. +// Default to C code for now + +void qt_blend_argb32_on_argb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + if (const_alpha == 256) { + for (int y=0; y<h; ++y) { + for (int x=0; x<w; ++x) { + uint s = src[x]; + if (s >= 0xff000000) + dst[x] = s; + else if (s != 0) + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else if (const_alpha != 0) { + const_alpha = (const_alpha * 255) >> 8; + for (int y=0; y<h; ++y) { + for (int x=0; x<w; ++x) { + uint s = BYTE_MUL(src[x], const_alpha); + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +void qt_blend_rgb32_on_rgb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + if (const_alpha != 256) { + qt_blend_argb32_on_argb32_arm_simd(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + if (w <= 64) { + for (int y=0; y<h; ++y) { + qt_memconvert(dst, src, w); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else { + int len = w * 4; + for (int y=0; y<h; ++y) { + memcpy(dst, src, len); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +#endif + +#endif // QT_HAVE_ARMV_SIMD diff --git a/src/gui/painting/qdrawhelper_arm_simd_p.h b/src/gui/painting/qdrawhelper_arm_simd_p.h new file mode 100644 index 0000000000..975a0244d2 --- /dev/null +++ b/src/gui/painting/qdrawhelper_arm_simd_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_ARM_SIMD_P_H +#define QDRAWHELPER_ARM_SIMD_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/qdrawhelper_p.h> + +QT_BEGIN_NAMESPACE + +#if defined(QT_HAVE_ARM_SIMD) + +void qt_blend_argb32_on_argb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb32_on_rgb32_arm_simd(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +#endif // QT_HAVE_ARM_SIMD + +QT_END_NAMESPACE + +#endif // QDRAWHELPER_ARM_SIMD_P_H diff --git a/src/gui/painting/qdrawhelper_iwmmxt.cpp b/src/gui/painting/qdrawhelper_iwmmxt.cpp new file mode 100644 index 0000000000..969224f1c8 --- /dev/null +++ b/src/gui/painting/qdrawhelper_iwmmxt.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifdef QT_HAVE_IWMMXT + +#include <mmintrin.h> +#if defined(Q_OS_WINCE) +# include "qplatformdefs.h" +#endif +#if !defined(__IWMMXT__) && !defined(Q_OS_WINCE) +# include <xmmintrin.h> +#elif defined(Q_OS_WINCE_STD) && defined(_X86_) +# pragma warning(disable: 4391) +# include <xmmintrin.h> +#endif + +#include <private/qdrawhelper_sse_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef _MM_SHUFFLE +#define _MM_SHUFFLE(fp3,fp2,fp1,fp0) \ + (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | (fp0)) +#endif + +struct QIWMMXTIntrinsics : public QMMXCommonIntrinsics +{ + static inline m64 alpha(m64 x) { + return _mm_shuffle_pi16 (x, _MM_SHUFFLE(3, 3, 3, 3)); + } + + static inline m64 _load_alpha(uint x, const m64 &mmx_0x0000) { + m64 t = _mm_unpacklo_pi8(_mm_cvtsi32_si64(x), mmx_0x0000); + return _mm_shuffle_pi16(t, _MM_SHUFFLE(0, 0, 0, 0)); + } + + static inline void end() { + } +}; + +CompositionFunctionSolid qt_functionForModeSolid_IWMMXT[numCompositionFunctions] = { + comp_func_solid_SourceOver<QIWMMXTIntrinsics>, + comp_func_solid_DestinationOver<QIWMMXTIntrinsics>, + comp_func_solid_Clear<QIWMMXTIntrinsics>, + comp_func_solid_Source<QIWMMXTIntrinsics>, + 0, + comp_func_solid_SourceIn<QIWMMXTIntrinsics>, + comp_func_solid_DestinationIn<QIWMMXTIntrinsics>, + comp_func_solid_SourceOut<QIWMMXTIntrinsics>, + comp_func_solid_DestinationOut<QIWMMXTIntrinsics>, + comp_func_solid_SourceAtop<QIWMMXTIntrinsics>, + comp_func_solid_DestinationAtop<QIWMMXTIntrinsics>, + comp_func_solid_XOR<QIWMMXTIntrinsics>, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // svg 1.2 modes + rasterop_solid_SourceOrDestination<QIWMMXTIntrinsics>, + rasterop_solid_SourceAndDestination<QIWMMXTIntrinsics>, + rasterop_solid_SourceXorDestination<QIWMMXTIntrinsics>, + rasterop_solid_NotSourceAndNotDestination<QIWMMXTIntrinsics>, + rasterop_solid_NotSourceOrNotDestination<QIWMMXTIntrinsics>, + rasterop_solid_NotSourceXorDestination<QIWMMXTIntrinsics>, + rasterop_solid_NotSource<QIWMMXTIntrinsics>, + rasterop_solid_NotSourceAndDestination<QIWMMXTIntrinsics>, + rasterop_solid_SourceAndNotDestination<QIWMMXTIntrinsics> +}; + +CompositionFunction qt_functionForMode_IWMMXT[] = { + comp_func_SourceOver<QIWMMXTIntrinsics>, + comp_func_DestinationOver<QIWMMXTIntrinsics>, + comp_func_Clear<QIWMMXTIntrinsics>, + comp_func_Source<QIWMMXTIntrinsics>, + comp_func_Destination, + comp_func_SourceIn<QIWMMXTIntrinsics>, + comp_func_DestinationIn<QIWMMXTIntrinsics>, + comp_func_SourceOut<QIWMMXTIntrinsics>, + comp_func_DestinationOut<QIWMMXTIntrinsics>, + comp_func_SourceAtop<QIWMMXTIntrinsics>, + comp_func_DestinationAtop<QIWMMXTIntrinsics>, + comp_func_XOR<QIWMMXTIntrinsics>, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_blend_color_argb_iwmmxt(int count, const QSpan *spans, void *userData) +{ + qt_blend_color_argb_x86<QIWMMXTIntrinsics>(count, spans, userData, + (CompositionFunctionSolid*)qt_functionForModeSolid_IWMMXT); +} + +#endif // QT_HAVE_IWMMXT + +QT_END_NAMESPACE diff --git a/src/gui/painting/qdrawhelper_mmx.cpp b/src/gui/painting/qdrawhelper_mmx.cpp new file mode 100644 index 0000000000..dd1f3d41e3 --- /dev/null +++ b/src/gui/painting/qdrawhelper_mmx.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_p.h> + +#if defined(QT_HAVE_MMX) + +#include <private/qdrawhelper_mmx_p.h> + +QT_BEGIN_NAMESPACE + +CompositionFunctionSolid qt_functionForModeSolid_MMX[numCompositionFunctions] = { + comp_func_solid_SourceOver<QMMXIntrinsics>, + comp_func_solid_DestinationOver<QMMXIntrinsics>, + comp_func_solid_Clear<QMMXIntrinsics>, + comp_func_solid_Source<QMMXIntrinsics>, + 0, + comp_func_solid_SourceIn<QMMXIntrinsics>, + comp_func_solid_DestinationIn<QMMXIntrinsics>, + comp_func_solid_SourceOut<QMMXIntrinsics>, + comp_func_solid_DestinationOut<QMMXIntrinsics>, + comp_func_solid_SourceAtop<QMMXIntrinsics>, + comp_func_solid_DestinationAtop<QMMXIntrinsics>, + comp_func_solid_XOR<QMMXIntrinsics>, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // svg 1.2 modes + rasterop_solid_SourceOrDestination<QMMXIntrinsics>, + rasterop_solid_SourceAndDestination<QMMXIntrinsics>, + rasterop_solid_SourceXorDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceAndNotDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceOrNotDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceXorDestination<QMMXIntrinsics>, + rasterop_solid_NotSource<QMMXIntrinsics>, + rasterop_solid_NotSourceAndDestination<QMMXIntrinsics>, + rasterop_solid_SourceAndNotDestination<QMMXIntrinsics> +}; + +CompositionFunction qt_functionForMode_MMX[numCompositionFunctions] = { + comp_func_SourceOver<QMMXIntrinsics>, + comp_func_DestinationOver<QMMXIntrinsics>, + comp_func_Clear<QMMXIntrinsics>, + comp_func_Source<QMMXIntrinsics>, + comp_func_Destination, + comp_func_SourceIn<QMMXIntrinsics>, + comp_func_DestinationIn<QMMXIntrinsics>, + comp_func_SourceOut<QMMXIntrinsics>, + comp_func_DestinationOut<QMMXIntrinsics>, + comp_func_SourceAtop<QMMXIntrinsics>, + comp_func_DestinationAtop<QMMXIntrinsics>, + comp_func_XOR<QMMXIntrinsics>, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_blend_color_argb_mmx(int count, const QSpan *spans, void *userData) +{ + qt_blend_color_argb_x86<QMMXIntrinsics>(count, spans, userData, + (CompositionFunctionSolid*)qt_functionForModeSolid_MMX); +} + + +void qt_blend_argb32_on_argb32_mmx(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + + uint ca = const_alpha - 1; + + for (int y=0; y<h; ++y) { + comp_func_SourceOver<QMMXIntrinsics>(dst, src, w, ca); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } +} + +void qt_blend_rgb32_on_rgb32_mmx(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + + uint ca = const_alpha - 1; + + for (int y=0; y<h; ++y) { + comp_func_Source<QMMXIntrinsics>(dst, src, w, ca); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_MMX + diff --git a/src/gui/painting/qdrawhelper_mmx3dnow.cpp b/src/gui/painting/qdrawhelper_mmx3dnow.cpp new file mode 100644 index 0000000000..12dde9e5bc --- /dev/null +++ b/src/gui/painting/qdrawhelper_mmx3dnow.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_x86_p.h> + +#ifdef QT_HAVE_3DNOW + +#include <private/qdrawhelper_mmx_p.h> +#include <mm3dnow.h> + +QT_BEGIN_NAMESPACE + +struct QMMX3DNOWIntrinsics : public QMMXCommonIntrinsics +{ + static inline void end() { + _m_femms(); + } +}; + +CompositionFunctionSolid qt_functionForModeSolid_MMX3DNOW[numCompositionFunctions] = { + comp_func_solid_SourceOver<QMMX3DNOWIntrinsics>, + comp_func_solid_DestinationOver<QMMX3DNOWIntrinsics>, + comp_func_solid_Clear<QMMX3DNOWIntrinsics>, + comp_func_solid_Source<QMMX3DNOWIntrinsics>, + 0, + comp_func_solid_SourceIn<QMMX3DNOWIntrinsics>, + comp_func_solid_DestinationIn<QMMX3DNOWIntrinsics>, + comp_func_solid_SourceOut<QMMX3DNOWIntrinsics>, + comp_func_solid_DestinationOut<QMMX3DNOWIntrinsics>, + comp_func_solid_SourceAtop<QMMX3DNOWIntrinsics>, + comp_func_solid_DestinationAtop<QMMX3DNOWIntrinsics>, + comp_func_solid_XOR<QMMX3DNOWIntrinsics>, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // svg 1.2 modes + rasterop_solid_SourceOrDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_SourceAndDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_SourceXorDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_NotSourceAndNotDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_NotSourceOrNotDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_NotSourceXorDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_NotSource<QMMX3DNOWIntrinsics>, + rasterop_solid_NotSourceAndDestination<QMMX3DNOWIntrinsics>, + rasterop_solid_SourceAndNotDestination<QMMX3DNOWIntrinsics> +}; + +CompositionFunction qt_functionForMode_MMX3DNOW[numCompositionFunctions] = { + comp_func_SourceOver<QMMX3DNOWIntrinsics>, + comp_func_DestinationOver<QMMX3DNOWIntrinsics>, + comp_func_Clear<QMMX3DNOWIntrinsics>, + comp_func_Source<QMMX3DNOWIntrinsics>, + comp_func_Destination, + comp_func_SourceIn<QMMX3DNOWIntrinsics>, + comp_func_DestinationIn<QMMX3DNOWIntrinsics>, + comp_func_SourceOut<QMMX3DNOWIntrinsics>, + comp_func_DestinationOut<QMMX3DNOWIntrinsics>, + comp_func_SourceAtop<QMMX3DNOWIntrinsics>, + comp_func_DestinationAtop<QMMX3DNOWIntrinsics>, + comp_func_XOR<QMMX3DNOWIntrinsics>, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_blend_color_argb_mmx3dnow(int count, const QSpan *spans, void *userData) +{ + qt_blend_color_argb_x86<QMMX3DNOWIntrinsics>(count, spans, userData, + (CompositionFunctionSolid*)qt_functionForModeSolid_MMX3DNOW); +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_3DNOW + diff --git a/src/gui/painting/qdrawhelper_mmx_p.h b/src/gui/painting/qdrawhelper_mmx_p.h new file mode 100644 index 0000000000..06e945b882 --- /dev/null +++ b/src/gui/painting/qdrawhelper_mmx_p.h @@ -0,0 +1,892 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_MMX_P_H +#define QDRAWHELPER_MMX_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/qdrawhelper_p.h> +#include <private/qdrawhelper_x86_p.h> +#include <private/qpaintengine_raster_p.h> + +#ifdef QT_HAVE_MMX +#include <mmintrin.h> +#endif + +#define C_FF const m64 mmx_0x00ff = _mm_set1_pi16(0xff) +#define C_80 const m64 mmx_0x0080 = _mm_set1_pi16(0x80) +#define C_00 const m64 mmx_0x0000 = _mm_setzero_si64() + +#ifdef Q_CC_MSVC +# pragma warning(disable: 4799) // No EMMS at end of function +#endif + +typedef __m64 m64; + +QT_BEGIN_NAMESPACE + +struct QMMXCommonIntrinsics +{ + static inline m64 alpha(m64 x) { + x = _mm_unpackhi_pi16(x, x); + x = _mm_unpackhi_pi16(x, x); + return x; + } + + static inline m64 _negate(const m64 &x, const m64 &mmx_0x00ff) { + return _mm_xor_si64(x, mmx_0x00ff); + } + + static inline m64 add(const m64 &a, const m64 &b) { + return _mm_adds_pu16 (a, b); + } + + static inline m64 _byte_mul(const m64 &a, const m64 &b, + const m64 &mmx_0x0080) + { + m64 res = _mm_mullo_pi16(a, b); + res = _mm_adds_pu16(res, mmx_0x0080); + res = _mm_adds_pu16(res, _mm_srli_pi16 (res, 8)); + return _mm_srli_pi16(res, 8); + } + + static inline m64 interpolate_pixel_256(const m64 &x, const m64 &a, + const m64 &y, const m64 &b) + { + m64 res = _mm_adds_pu16(_mm_mullo_pi16(x, a), _mm_mullo_pi16(y, b)); + return _mm_srli_pi16(res, 8); + } + + static inline m64 _interpolate_pixel_255(const m64 &x, const m64 &a, + const m64 &y, const m64 &b, + const m64 &mmx_0x0080) + { + m64 res = _mm_adds_pu16(_mm_mullo_pi16(x, a), _mm_mullo_pi16(y, b)); + res = _mm_adds_pu16(res, mmx_0x0080); + res = _mm_adds_pu16(res, _mm_srli_pi16 (res, 8)); + return _mm_srli_pi16(res, 8); + } + + static inline m64 _premul(m64 x, const m64 &mmx_0x0080) { + m64 a = alpha(x); + return _byte_mul(x, a, mmx_0x0080); + } + + static inline m64 _load(uint x, const m64 &mmx_0x0000) { + return _mm_unpacklo_pi8(_mm_cvtsi32_si64(x), mmx_0x0000); + } + + static inline m64 _load_alpha(uint x, const m64 &) { + x |= (x << 16); + return _mm_set1_pi32(x); + } + + static inline uint _store(const m64 &x, const m64 &mmx_0x0000) { + return _mm_cvtsi64_si32(_mm_packs_pu16(x, mmx_0x0000)); + } +}; + +#define negate(x) _negate(x, mmx_0x00ff) +#define byte_mul(a, b) _byte_mul(a, b, mmx_0x0080) +#define interpolate_pixel_255(x, a, y, b) _interpolate_pixel_255(x, a, y, b, mmx_0x0080) +#define premul(x) _premul(x, mmx_0x0080) +#define load(x) _load(x, mmx_0x0000) +#define load_alpha(x) _load_alpha(x, mmx_0x0000) +#define store(x) _store(x, mmx_0x0000) + +/* + result = 0 + d = d * cia +*/ +#define comp_func_Clear_impl(dest, length, const_alpha)\ +{\ + if (const_alpha == 255) {\ + qt_memfill(static_cast<quint32*>(dest), quint32(0), length);\ + } else {\ + C_FF; C_80; C_00;\ + m64 ia = MM::negate(MM::load_alpha(const_alpha));\ + for (int i = 0; i < length; ++i) {\ + dest[i] = MM::store(MM::byte_mul(MM::load(dest[i]), ia));\ + }\ + MM::end();\ + }\ +} + +template <class MM> +static void QT_FASTCALL comp_func_solid_Clear(uint *dest, int length, uint, uint const_alpha) +{ + comp_func_Clear_impl(dest, length, const_alpha); +} + +template <class MM> +static void QT_FASTCALL comp_func_Clear(uint *dest, const uint *, int length, uint const_alpha) +{ + comp_func_Clear_impl(dest, length, const_alpha); +} + +/* + result = s + dest = s * ca + d * cia +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_Source(uint *dest, int length, uint src, uint const_alpha) +{ + if (const_alpha == 255) { + qt_memfill(static_cast<quint32*>(dest), quint32(src), length); + } else { + C_FF; C_80; C_00; + const m64 a = MM::load_alpha(const_alpha); + const m64 ia = MM::negate(a); + const m64 s = MM::byte_mul(MM::load(src), a); + for (int i = 0; i < length; ++i) { + dest[i] = MM::store(MM::add(s, MM::byte_mul(MM::load(dest[i]), ia))); + } + MM::end(); + } +} + +template <class MM> +static void QT_FASTCALL comp_func_Source(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) { + ::memcpy(dest, src, length * sizeof(uint)); + } else { + C_FF; C_80; C_00; + const m64 a = MM::load_alpha(const_alpha); + const m64 ia = MM::negate(a); + for (int i = 0; i < length; ++i) + dest[i] = MM::store(MM::interpolate_pixel_255(MM::load(src[i]), a, + MM::load(dest[i]), ia)); + } + MM::end(); +} + +/* + result = s + d * sia + dest = (s + d * sia) * ca + d * cia + = s * ca + d * (sia * ca + cia) + = s * ca + d * (1 - sa*ca) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_SourceOver(uint *dest, int length, uint src, uint const_alpha) +{ + if ((const_alpha & qAlpha(src)) == 255) { + qt_memfill(static_cast<quint32*>(dest), quint32(src), length); + } else { + C_FF; C_80; C_00; + m64 s = MM::load(src); + if (const_alpha != 255) { + m64 ca = MM::load_alpha(const_alpha); + s = MM::byte_mul(s, ca); + } + m64 a = MM::negate(MM::alpha(s)); + for (int i = 0; i < length; ++i) + dest[i] = MM::store(MM::add(s, MM::byte_mul(MM::load(dest[i]), a))); + MM::end(); + } +} + +template <class MM> +static void QT_FASTCALL comp_func_SourceOver(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + const uint alphaMaskedSource = 0xff000000 & src[i]; + if (alphaMaskedSource == 0) + continue; + if (alphaMaskedSource == 0xff000000) { + dest[i] = src[i]; + } else { + m64 s = MM::load(src[i]); + m64 ia = MM::negate(MM::alpha(s)); + dest[i] = MM::store(MM::add(s, MM::byte_mul(MM::load(dest[i]), ia))); + } + } + } else { + m64 ca = MM::load_alpha(const_alpha); + for (int i = 0; i < length; ++i) { + if ((0xff000000 & src[i]) == 0) + continue; + m64 s = MM::byte_mul(MM::load(src[i]), ca); + m64 ia = MM::negate(MM::alpha(s)); + dest[i] = MM::store(MM::add(s, MM::byte_mul(MM::load(dest[i]), ia))); + } + } + MM::end(); +} + +/* + result = d + s * dia + dest = (d + s * dia) * ca + d * cia + = d + s * dia * ca +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_DestinationOver(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 s = MM::load(src); + if (const_alpha != 255) + s = MM::byte_mul(s, MM::load_alpha(const_alpha)); + + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 dia = MM::negate(MM::alpha(d)); + dest[i] = MM::store(MM::add(d, MM::byte_mul(s, dia))); + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_DestinationOver(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 ia = MM::negate(MM::alpha(d)); + dest[i] = MM::store(MM::add(d, MM::byte_mul(MM::load(src[i]), ia))); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 dia = MM::negate(MM::alpha(d)); + dia = MM::byte_mul(dia, ca); + dest[i] = MM::store(MM::add(d, MM::byte_mul(MM::load(src[i]), dia))); + } + } + MM::end(); +} + +/* + result = s * da + dest = s * da * ca + d * cia +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_SourceIn(uint *dest, int length, uint src, uint const_alpha) +{ + C_80; C_00; + if (const_alpha == 255) { + m64 s = MM::load(src); + for (int i = 0; i < length; ++i) { + m64 da = MM::alpha(MM::load(dest[i])); + dest[i] = MM::store(MM::byte_mul(s, da)); + } + } else { + C_FF; + m64 s = MM::load(src); + m64 ca = MM::load_alpha(const_alpha); + s = MM::byte_mul(s, ca); + m64 cia = MM::negate(ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::alpha(d), d, cia)); + } + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_SourceIn(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 a = MM::alpha(MM::load(dest[i])); + dest[i] = MM::store(MM::byte_mul(MM::load(src[i]), a)); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 da = MM::byte_mul(MM::alpha(d), ca); + dest[i] = MM::store(MM::interpolate_pixel_255( + MM::load(src[i]), da, d, cia)); + } + } + MM::end(); +} + +/* + result = d * sa + dest = d * sa * ca + d * cia + = d * (sa * ca + cia) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_DestinationIn(uint *dest, int length, uint src, uint const_alpha) +{ + C_80; C_00; + m64 a = MM::alpha(MM::load(src)); + if (const_alpha != 255) { + C_FF; + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + a = MM::byte_mul(a, ca); + a = MM::add(a, cia); + } + for (int i = 0; i < length; ++i) + dest[i] = MM::store(MM::byte_mul(MM::load(dest[i]), a)); + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_DestinationIn(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 a = MM::alpha(MM::load(src[i])); + dest[i] = MM::store(MM::byte_mul(MM::load(dest[i]), a)); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 a = MM::alpha(MM::load(src[i])); + a = MM::byte_mul(a, ca); + a = MM::add(a, cia); + dest[i] = MM::store(MM::byte_mul(d, a)); + } + } + MM::end(); +} + +/* + result = s * dia + dest = s * dia * ca + d * cia +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_SourceOut(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 s = MM::load(src); + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 dia = MM::negate(MM::alpha(MM::load(dest[i]))); + dest[i] = MM::store(MM::byte_mul(s, dia)); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + s = MM::byte_mul(s, ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), d, cia)); + } + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_SourceOut(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 ia = MM::negate(MM::alpha(MM::load(dest[i]))); + dest[i] = MM::store(MM::byte_mul(MM::load(src[i]), ia)); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 dia = MM::byte_mul(MM::negate(MM::alpha(d)), ca); + dest[i] = MM::store(MM::interpolate_pixel_255(MM::load(src[i]), dia, d, cia)); + } + } + MM::end(); +} + +/* + result = d * sia + dest = d * sia * ca + d * cia + = d * (sia * ca + cia) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_DestinationOut(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 a = MM::negate(MM::alpha(MM::load(src))); + if (const_alpha != 255) { + m64 ca = MM::load_alpha(const_alpha); + a = MM::byte_mul(a, ca); + a = MM::add(a, MM::negate(ca)); + } + for (int i = 0; i < length; ++i) + dest[i] = MM::store(MM::byte_mul(MM::load(dest[i]), a)); + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_DestinationOut(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 a = MM::negate(MM::alpha(MM::load(src[i]))); + dest[i] = MM::store(MM::byte_mul(MM::load(dest[i]), a)); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + m64 cia = MM::negate(ca); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + m64 a = MM::negate(MM::alpha(MM::load(src[i]))); + a = MM::byte_mul(a, ca); + a = MM::add(a, cia); + dest[i] = MM::store(MM::byte_mul(d, a)); + } + } + MM::end(); +} + +/* + result = s*da + d*sia + dest = s*da*ca + d*sia*ca + d *cia + = s*ca * da + d * (sia*ca + cia) + = s*ca * da + d * (1 - sa*ca) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_SourceAtop(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 s = MM::load(src); + if (const_alpha != 255) { + m64 ca = MM::load_alpha(const_alpha); + s = MM::byte_mul(s, ca); + } + m64 a = MM::negate(MM::alpha(s)); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::alpha(d), d, a)); + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_SourceAtop(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::alpha(d), d, + MM::negate(MM::alpha(s)))); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + s = MM::byte_mul(s, ca); + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::alpha(d), d, + MM::negate(MM::alpha(s)))); + } + } + MM::end(); +} + +/* + result = d*sa + s*dia + dest = d*sa*ca + s*dia*ca + d *cia + = s*ca * dia + d * (sa*ca + cia) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_DestinationAtop(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 s = MM::load(src); + m64 a = MM::alpha(s); + if (const_alpha != 255) { + m64 ca = MM::load_alpha(const_alpha); + s = MM::byte_mul(s, ca); + a = MM::alpha(s); + a = MM::add(a, MM::negate(ca)); + } + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), d, a)); + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_DestinationAtop(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(d, MM::alpha(s), s, + MM::negate(MM::alpha(d)))); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + s = MM::byte_mul(s, ca); + m64 d = MM::load(dest[i]); + m64 a = MM::alpha(s); + a = MM::add(a, MM::negate(ca)); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), + d, a)); + } + } + MM::end(); +} + +/* + result = d*sia + s*dia + dest = d*sia*ca + s*dia*ca + d *cia + = s*ca * dia + d * (sia*ca + cia) + = s*ca * dia + d * (1 - sa*ca) +*/ +template <class MM> +static void QT_FASTCALL comp_func_solid_XOR(uint *dest, int length, uint src, uint const_alpha) +{ + C_FF; C_80; C_00; + m64 s = MM::load(src); + if (const_alpha != 255) { + m64 ca = MM::load_alpha(const_alpha); + s = MM::byte_mul(s, ca); + } + m64 a = MM::negate(MM::alpha(s)); + for (int i = 0; i < length; ++i) { + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), + d, a)); + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL comp_func_XOR(uint *dest, const uint *src, int length, uint const_alpha) +{ + C_FF; C_80; C_00; + if (const_alpha == 255) { + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), + d, MM::negate(MM::alpha(s)))); + } + } else { + m64 ca = MM::load_alpha(const_alpha); + for (int i = 0; i < length; ++i) { + m64 s = MM::load(src[i]); + s = MM::byte_mul(s, ca); + m64 d = MM::load(dest[i]); + dest[i] = MM::store(MM::interpolate_pixel_255(s, MM::negate(MM::alpha(d)), + d, MM::negate(MM::alpha(s)))); + } + } + MM::end(); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_SourceOrDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + + if ((quintptr)(dest) & 0x7) { + *dest++ |= color; + --length; + } + + const int length64 = length / 2; + if (length64) { + __m64 *dst64 = reinterpret_cast<__m64*>(dest); + const __m64 color64 = _mm_set_pi32(color, color); + + int n = (length64 + 3) / 4; + switch (length64 & 0x3) { + case 0: do { *dst64 = _mm_or_si64(*dst64, color64); ++dst64; + case 3: *dst64 = _mm_or_si64(*dst64, color64); ++dst64; + case 2: *dst64 = _mm_or_si64(*dst64, color64); ++dst64; + case 1: *dst64 = _mm_or_si64(*dst64, color64); ++dst64; + } while (--n > 0); + } + } + + if (length & 0x1) { + dest[length - 1] |= color; + } + + MM::end(); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_SourceAndDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + + color |= 0xff000000; + + if ((quintptr)(dest) & 0x7) { // align + *dest++ &= color; + --length; + } + + const int length64 = length / 2; + if (length64) { + __m64 *dst64 = reinterpret_cast<__m64*>(dest); + const __m64 color64 = _mm_set_pi32(color, color); + + int n = (length64 + 3) / 4; + switch (length64 & 0x3) { + case 0: do { *dst64 = _mm_and_si64(*dst64, color64); ++dst64; + case 3: *dst64 = _mm_and_si64(*dst64, color64); ++dst64; + case 2: *dst64 = _mm_and_si64(*dst64, color64); ++dst64; + case 1: *dst64 = _mm_and_si64(*dst64, color64); ++dst64; + } while (--n > 0); + } + } + + if (length & 0x1) { + dest[length - 1] &= color; + } + + MM::end(); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_SourceXorDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + + color &= 0x00ffffff; + + if ((quintptr)(dest) & 0x7) { + *dest++ ^= color; + --length; + } + + const int length64 = length / 2; + if (length64) { + __m64 *dst64 = reinterpret_cast<__m64*>(dest); + const __m64 color64 = _mm_set_pi32(color, color); + + int n = (length64 + 3) / 4; + switch (length64 & 0x3) { + case 0: do { *dst64 = _mm_xor_si64(*dst64, color64); ++dst64; + case 3: *dst64 = _mm_xor_si64(*dst64, color64); ++dst64; + case 2: *dst64 = _mm_xor_si64(*dst64, color64); ++dst64; + case 1: *dst64 = _mm_xor_si64(*dst64, color64); ++dst64; + } while (--n > 0); + } + } + + if (length & 0x1) { + dest[length - 1] ^= color; + } + + MM::end(); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_SourceAndNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + + Q_UNUSED(const_alpha); + + if ((quintptr)(dest) & 0x7) { + *dest = (color & ~(*dest)) | 0xff000000; + ++dest; + --length; + } + + const int length64 = length / 2; + if (length64) { + __m64 *dst64 = reinterpret_cast<__m64*>(dest); + const __m64 color64 = _mm_set_pi32(color, color); + const m64 mmx_0xff000000 = _mm_set1_pi32(0xff000000); + __m64 tmp1, tmp2, tmp3, tmp4; + + int n = (length64 + 3) / 4; + switch (length64 & 0x3) { + case 0: do { tmp1 = _mm_andnot_si64(*dst64, color64); + *dst64++ = _mm_or_si64(tmp1, mmx_0xff000000); + case 3: tmp2 = _mm_andnot_si64(*dst64, color64); + *dst64++ = _mm_or_si64(tmp2, mmx_0xff000000); + case 2: tmp3 = _mm_andnot_si64(*dst64, color64); + *dst64++ = _mm_or_si64(tmp3, mmx_0xff000000); + case 1: tmp4 = _mm_andnot_si64(*dst64, color64); + *dst64++ = _mm_or_si64(tmp4, mmx_0xff000000); + } while (--n > 0); + } + } + + if (length & 0x1) { + dest[length - 1] = (color & ~(dest[length - 1])) | 0xff000000; + } + + MM::end(); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_NotSourceAndNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + rasterop_solid_SourceAndNotDestination<MM>(dest, length, + ~color, const_alpha); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_NotSourceOrNotDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + Q_UNUSED(const_alpha); + color = ~color | 0xff000000; + while (length--) { + *dest = color | ~(*dest); + ++dest; + } +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_NotSourceXorDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + rasterop_solid_SourceXorDestination<MM>(dest, length, ~color, const_alpha); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_NotSource(uint *dest, int length, + uint color, uint const_alpha) +{ + Q_UNUSED(const_alpha); + qt_memfill((quint32*)dest, ~color | 0xff000000, length); +} + +template <class MM> +static void QT_FASTCALL rasterop_solid_NotSourceAndDestination(uint *dest, + int length, + uint color, + uint const_alpha) +{ + rasterop_solid_SourceAndDestination<MM>(dest, length, + ~color, const_alpha); +} + +template <class MM> +static inline void qt_blend_color_argb_x86(int count, const QSpan *spans, + void *userData, + CompositionFunctionSolid *solidFunc) +{ + QSpanData *data = reinterpret_cast<QSpanData *>(userData); + if (data->rasterBuffer->compositionMode == QPainter::CompositionMode_Source + || (data->rasterBuffer->compositionMode == QPainter::CompositionMode_SourceOver + && qAlpha(data->solid.color) == 255)) { + // inline for performance + C_FF; C_80; C_00; + while (count--) { + uint *target = ((uint *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + if (spans->coverage == 255) { + qt_memfill(static_cast<quint32*>(target), quint32(data->solid.color), spans->len); + } else { + // dest = s * ca + d * (1 - sa*ca) --> dest = s * ca + d * (1-ca) + m64 ca = MM::load_alpha(spans->coverage); + m64 s = MM::byte_mul(MM::load(data->solid.color), ca); + m64 ica = MM::negate(ca); + for (int i = 0; i < spans->len; ++i) + target[i] = MM::store(MM::add(s, MM::byte_mul(MM::load(target[i]), ica))); + } + ++spans; + } + MM::end(); + return; + } + CompositionFunctionSolid func = solidFunc[data->rasterBuffer->compositionMode]; + while (count--) { + uint *target = ((uint *)data->rasterBuffer->scanLine(spans->y)) + spans->x; + func(target, spans->len, data->solid.color, spans->coverage); + ++spans; + } +} + +#ifdef QT_HAVE_MMX +struct QMMXIntrinsics : public QMMXCommonIntrinsics +{ + static inline void end() { +#if !defined(Q_OS_WINCE) || defined(_X86_) + _mm_empty(); +#endif + } +}; +#endif // QT_HAVE_MMX + +QT_END_NAMESPACE + +#endif // QDRAWHELPER_MMX_P_H diff --git a/src/gui/painting/qdrawhelper_neon.cpp b/src/gui/painting/qdrawhelper_neon.cpp new file mode 100644 index 0000000000..debca37486 --- /dev/null +++ b/src/gui/painting/qdrawhelper_neon.cpp @@ -0,0 +1,961 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_p.h> +#include <private/qblendfunctions_p.h> +#include <private/qmath_p.h> + +#ifdef QT_HAVE_NEON + +#include <private/qdrawhelper_neon_p.h> +#include <private/qpaintengine_raster_p.h> +#include <arm_neon.h> + +QT_BEGIN_NAMESPACE + +void qt_memfill32_neon(quint32 *dest, quint32 value, int count) +{ + const int epilogueSize = count % 16; + if (count >= 16) { + quint32 *const neonEnd = dest + count - epilogueSize; + register uint32x4_t valueVector1 asm ("q0") = vdupq_n_u32(value); + register uint32x4_t valueVector2 asm ("q1") = valueVector1; + while (dest != neonEnd) { + asm volatile ( + "vst2.32 { d0, d1, d2, d3 }, [%[DST]] !\n\t" + "vst2.32 { d0, d1, d2, d3 }, [%[DST]] !\n\t" + : [DST]"+r" (dest) + : [VALUE1]"w"(valueVector1), [VALUE2]"w"(valueVector2) + : "memory" + ); + } + } + + switch (epilogueSize) + { + case 15: *dest++ = value; + case 14: *dest++ = value; + case 13: *dest++ = value; + case 12: *dest++ = value; + case 11: *dest++ = value; + case 10: *dest++ = value; + case 9: *dest++ = value; + case 8: *dest++ = value; + case 7: *dest++ = value; + case 6: *dest++ = value; + case 5: *dest++ = value; + case 4: *dest++ = value; + case 3: *dest++ = value; + case 2: *dest++ = value; + case 1: *dest++ = value; + } +} + +static inline uint16x8_t qvdiv_255_u16(uint16x8_t x, uint16x8_t half) +{ + // result = (x + (x >> 8) + 0x80) >> 8 + + const uint16x8_t temp = vshrq_n_u16(x, 8); // x >> 8 + const uint16x8_t sum_part = vaddq_u16(x, half); // x + 0x80 + const uint16x8_t sum = vaddq_u16(temp, sum_part); + + return vshrq_n_u16(sum, 8); +} + +static inline uint16x8_t qvbyte_mul_u16(uint16x8_t x, uint16x8_t alpha, uint16x8_t half) +{ + // t = qRound(x * alpha / 255.0) + + const uint16x8_t t = vmulq_u16(x, alpha); // t + return qvdiv_255_u16(t, half); +} + +static inline uint16x8_t qvinterpolate_pixel_255(uint16x8_t x, uint16x8_t a, uint16x8_t y, uint16x8_t b, uint16x8_t half) +{ + // t = x * a + y * b + + const uint16x8_t ta = vmulq_u16(x, a); + const uint16x8_t tb = vmulq_u16(y, b); + + return qvdiv_255_u16(vaddq_u16(ta, tb), half); +} + +static inline uint16x8_t qvsource_over_u16(uint16x8_t src16, uint16x8_t dst16, uint16x8_t half, uint16x8_t full) +{ + const uint16x4_t alpha16_high = vdup_lane_u16(vget_high_u16(src16), 3); + const uint16x4_t alpha16_low = vdup_lane_u16(vget_low_u16(src16), 3); + + const uint16x8_t alpha16 = vsubq_u16(full, vcombine_u16(alpha16_low, alpha16_high)); + + return vaddq_u16(src16, qvbyte_mul_u16(dst16, alpha16, half)); +} + +extern "C" void +pixman_composite_over_8888_0565_asm_neon (int32_t w, + int32_t h, + uint16_t *dst, + int32_t dst_stride, + uint32_t *src, + int32_t src_stride); + +extern "C" void +pixman_composite_over_8888_8888_asm_neon (int32_t w, + int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint32_t *src, + int32_t src_stride); + +extern "C" void +pixman_composite_src_0565_8888_asm_neon (int32_t w, + int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint16_t *src, + int32_t src_stride); + +extern "C" void +pixman_composite_over_n_8_0565_asm_neon (int32_t w, + int32_t h, + uint16_t *dst, + int32_t dst_stride, + uint32_t src, + int32_t unused, + uint8_t *mask, + int32_t mask_stride); + +extern "C" void +pixman_composite_scanline_over_asm_neon (int32_t w, + const uint32_t *dst, + const uint32_t *src); + +extern "C" void +pixman_composite_src_0565_0565_asm_neon (int32_t w, + int32_t h, + uint16_t *dst, + int32_t dst_stride, + uint16_t *src, + int32_t src_stride); + +// qblendfunctions.cpp +void qt_blend_argb32_on_rgb16_const_alpha(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb16_on_argb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + dbpl /= 4; + sbpl /= 2; + + quint32 *dst = (quint32 *) destPixels; + quint16 *src = (quint16 *) srcPixels; + + if (const_alpha != 256) { + quint8 a = (255 * const_alpha) >> 8; + quint8 ia = 255 - a; + + while (h--) { + for (int x=0; x<w; ++x) + dst[x] = INTERPOLATE_PIXEL_255(qt_colorConvert(src[x], dst[x]), a, dst[x], ia); + dst += dbpl; + src += sbpl; + } + return; + } + + pixman_composite_src_0565_8888_asm_neon(w, h, dst, dbpl, src, sbpl); +} + +// qblendfunctions.cpp +void qt_blend_rgb16_on_rgb16(uchar *dst, int dbpl, + const uchar *src, int sbpl, + int w, int h, + int const_alpha); + +template <int N> +static inline void scanLineBlit16(quint16 *dst, quint16 *src, int dstride) +{ + if (N >= 2) { + ((quint32 *)dst)[0] = ((quint32 *)src)[0]; + __builtin_prefetch(dst + dstride, 1, 0); + } + for (int i = 1; i < N/2; ++i) + ((quint32 *)dst)[i] = ((quint32 *)src)[i]; + if (N & 1) + dst[N-1] = src[N-1]; +} + +template <int Width> +static inline void blockBlit16(quint16 *dst, quint16 *src, int dstride, int sstride, int h) +{ + union { + quintptr address; + quint16 *pointer; + } u; + + u.pointer = dst; + + if (u.address & 2) { + while (h--) { + // align dst + dst[0] = src[0]; + if (Width > 1) + scanLineBlit16<Width-1>(dst + 1, src + 1, dstride); + dst += dstride; + src += sstride; + } + } else { + while (h--) { + scanLineBlit16<Width>(dst, src, dstride); + + dst += dstride; + src += sstride; + } + } +} + +void qt_blend_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + // testing show that the default memcpy is faster for widths 150 and up + if (const_alpha != 256 || w >= 150) { + qt_blend_rgb16_on_rgb16(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + return; + } + + int dstride = dbpl / 2; + int sstride = sbpl / 2; + + quint16 *dst = (quint16 *) destPixels; + quint16 *src = (quint16 *) srcPixels; + + switch (w) { +#define BLOCKBLIT(n) case n: blockBlit16<n>(dst, src, dstride, sstride, h); return; + BLOCKBLIT(1); + BLOCKBLIT(2); + BLOCKBLIT(3); + BLOCKBLIT(4); + BLOCKBLIT(5); + BLOCKBLIT(6); + BLOCKBLIT(7); + BLOCKBLIT(8); + BLOCKBLIT(9); + BLOCKBLIT(10); + BLOCKBLIT(11); + BLOCKBLIT(12); + BLOCKBLIT(13); + BLOCKBLIT(14); + BLOCKBLIT(15); +#undef BLOCKBLIT + default: + break; + } + + pixman_composite_src_0565_0565_asm_neon (w, h, dst, dstride, src, sstride); +} + +extern "C" void blend_8_pixels_argb32_on_rgb16_neon(quint16 *dst, const quint32 *src, int const_alpha); + +void qt_blend_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + quint16 *dst = (quint16 *) destPixels; + quint32 *src = (quint32 *) srcPixels; + + if (const_alpha != 256) { + for (int y=0; y<h; ++y) { + int i = 0; + for (; i < w-7; i += 8) + blend_8_pixels_argb32_on_rgb16_neon(&dst[i], &src[i], const_alpha); + + if (i < w) { + int tail = w - i; + + quint16 dstBuffer[8]; + quint32 srcBuffer[8]; + + for (int j = 0; j < tail; ++j) { + dstBuffer[j] = dst[i + j]; + srcBuffer[j] = src[i + j]; + } + + blend_8_pixels_argb32_on_rgb16_neon(dstBuffer, srcBuffer, const_alpha); + + for (int j = 0; j < tail; ++j) + dst[i + j] = dstBuffer[j]; + } + + dst = (quint16 *)(((uchar *) dst) + dbpl); + src = (quint32 *)(((uchar *) src) + sbpl); + } + return; + } + + pixman_composite_over_8888_0565_asm_neon(w, h, dst, dbpl / 2, src, sbpl / 4); +} + +void qt_blend_argb32_on_argb32_scanline_neon(uint *dest, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) { + pixman_composite_scanline_over_asm_neon(length, dest, src); + } else { + qt_blend_argb32_on_argb32_neon((uchar *)dest, 4 * length, (uchar *)src, 4 * length, length, 1, (const_alpha * 256) / 255); + } +} + +void qt_blend_argb32_on_argb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + uint16x8_t half = vdupq_n_u16(0x80); + uint16x8_t full = vdupq_n_u16(0xff); + if (const_alpha == 256) { + pixman_composite_over_8888_8888_asm_neon(w, h, (uint32_t *)destPixels, dbpl / 4, (uint32_t *)srcPixels, sbpl / 4); + } else if (const_alpha != 0) { + const_alpha = (const_alpha * 255) >> 8; + uint16x8_t const_alpha16 = vdupq_n_u16(const_alpha); + for (int y = 0; y < h; ++y) { + int x = 0; + for (; x < w-3; x += 4) { + if (src[x] | src[x+1] | src[x+2] | src[x+3]) { + uint32x4_t src32 = vld1q_u32((uint32_t *)&src[x]); + uint32x4_t dst32 = vld1q_u32((uint32_t *)&dst[x]); + + const uint8x16_t src8 = vreinterpretq_u8_u32(src32); + const uint8x16_t dst8 = vreinterpretq_u8_u32(dst32); + + const uint8x8_t src8_low = vget_low_u8(src8); + const uint8x8_t dst8_low = vget_low_u8(dst8); + + const uint8x8_t src8_high = vget_high_u8(src8); + const uint8x8_t dst8_high = vget_high_u8(dst8); + + const uint16x8_t src16_low = vmovl_u8(src8_low); + const uint16x8_t dst16_low = vmovl_u8(dst8_low); + + const uint16x8_t src16_high = vmovl_u8(src8_high); + const uint16x8_t dst16_high = vmovl_u8(dst8_high); + + const uint16x8_t srcalpha16_low = qvbyte_mul_u16(src16_low, const_alpha16, half); + const uint16x8_t srcalpha16_high = qvbyte_mul_u16(src16_high, const_alpha16, half); + + const uint16x8_t result16_low = qvsource_over_u16(srcalpha16_low, dst16_low, half, full); + const uint16x8_t result16_high = qvsource_over_u16(srcalpha16_high, dst16_high, half, full); + + const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result16_low)); + const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result16_high)); + + vst1q_u32((uint32_t *)&dst[x], vcombine_u32(result32_low, result32_high)); + } + } + for (; x<w; ++x) { + uint s = src[x]; + if (s != 0) { + s = BYTE_MUL(s, const_alpha); + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); + } + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +// qblendfunctions.cpp +void qt_blend_rgb32_on_rgb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb32_on_rgb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + if (const_alpha != 256) { + if (const_alpha != 0) { + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + uint16x8_t half = vdupq_n_u16(0x80); + const_alpha = (const_alpha * 255) >> 8; + int one_minus_const_alpha = 255 - const_alpha; + uint16x8_t const_alpha16 = vdupq_n_u16(const_alpha); + uint16x8_t one_minus_const_alpha16 = vdupq_n_u16(255 - const_alpha); + for (int y = 0; y < h; ++y) { + int x = 0; + for (; x < w-3; x += 4) { + uint32x4_t src32 = vld1q_u32((uint32_t *)&src[x]); + uint32x4_t dst32 = vld1q_u32((uint32_t *)&dst[x]); + + const uint8x16_t src8 = vreinterpretq_u8_u32(src32); + const uint8x16_t dst8 = vreinterpretq_u8_u32(dst32); + + const uint8x8_t src8_low = vget_low_u8(src8); + const uint8x8_t dst8_low = vget_low_u8(dst8); + + const uint8x8_t src8_high = vget_high_u8(src8); + const uint8x8_t dst8_high = vget_high_u8(dst8); + + const uint16x8_t src16_low = vmovl_u8(src8_low); + const uint16x8_t dst16_low = vmovl_u8(dst8_low); + + const uint16x8_t src16_high = vmovl_u8(src8_high); + const uint16x8_t dst16_high = vmovl_u8(dst8_high); + + const uint16x8_t result16_low = qvinterpolate_pixel_255(src16_low, const_alpha16, dst16_low, one_minus_const_alpha16, half); + const uint16x8_t result16_high = qvinterpolate_pixel_255(src16_high, const_alpha16, dst16_high, one_minus_const_alpha16, half); + + const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result16_low)); + const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result16_high)); + + vst1q_u32((uint32_t *)&dst[x], vcombine_u32(result32_low, result32_high)); + } + for (; x<w; ++x) { + uint s = src[x]; + s = BYTE_MUL(s, const_alpha); + dst[x] = INTERPOLATE_PIXEL_255(src[x], const_alpha, dst[x], one_minus_const_alpha); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } + } else { + qt_blend_rgb32_on_rgb32(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + } +} + +void qt_alphamapblit_quint16_neon(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *bitmap, + int mapWidth, int mapHeight, int mapStride, + const QClipData *) +{ + quint16 *dest = reinterpret_cast<quint16*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint16); + + uchar *mask = const_cast<uchar *>(bitmap); + + pixman_composite_over_n_8_0565_asm_neon(mapWidth, mapHeight, dest, destStride, color, 0, mask, mapStride); +} + +extern "C" void blend_8_pixels_rgb16_on_rgb16_neon(quint16 *dst, const quint16 *src, int const_alpha); + +template <typename SRC, typename BlendFunc> +struct Blend_on_RGB16_SourceAndConstAlpha_Neon { + Blend_on_RGB16_SourceAndConstAlpha_Neon(BlendFunc blender, int const_alpha) + : m_index(0) + , m_blender(blender) + , m_const_alpha(const_alpha) + { + } + + inline void write(quint16 *dst, quint32 src) + { + srcBuffer[m_index++] = src; + + if (m_index == 8) { + m_blender(dst - 7, srcBuffer, m_const_alpha); + m_index = 0; + } + } + + inline void flush(quint16 *dst) + { + if (m_index > 0) { + quint16 dstBuffer[8]; + for (int i = 0; i < m_index; ++i) + dstBuffer[i] = dst[i - m_index]; + + m_blender(dstBuffer, srcBuffer, m_const_alpha); + + for (int i = 0; i < m_index; ++i) + dst[i - m_index] = dstBuffer[i]; + + m_index = 0; + } + } + + SRC srcBuffer[8]; + + int m_index; + BlendFunc m_blender; + int m_const_alpha; +}; + +template <typename SRC, typename BlendFunc> +Blend_on_RGB16_SourceAndConstAlpha_Neon<SRC, BlendFunc> +Blend_on_RGB16_SourceAndConstAlpha_Neon_create(BlendFunc blender, int const_alpha) +{ + return Blend_on_RGB16_SourceAndConstAlpha_Neon<SRC, BlendFunc>(blender, const_alpha); +} + +void qt_scale_image_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ + if (const_alpha == 0) + return; + + qt_scale_image_16bit<quint32>(destPixels, dbpl, srcPixels, sbpl, targetRect, sourceRect, clip, + Blend_on_RGB16_SourceAndConstAlpha_Neon_create<quint32>(blend_8_pixels_argb32_on_rgb16_neon, const_alpha)); +} + +void qt_scale_image_rgb16_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha); + +void qt_scale_image_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha) +{ + if (const_alpha == 0) + return; + + if (const_alpha == 256) { + qt_scale_image_rgb16_on_rgb16(destPixels, dbpl, srcPixels, sbpl, targetRect, sourceRect, clip, const_alpha); + return; + } + + qt_scale_image_16bit<quint16>(destPixels, dbpl, srcPixels, sbpl, targetRect, sourceRect, clip, + Blend_on_RGB16_SourceAndConstAlpha_Neon_create<quint16>(blend_8_pixels_rgb16_on_rgb16_neon, const_alpha)); +} + +extern void qt_transform_image_rgb16_on_rgb16(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha); + +void qt_transform_image_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 0) + return; + + if (const_alpha == 256) { + qt_transform_image_rgb16_on_rgb16(destPixels, dbpl, srcPixels, sbpl, targetRect, sourceRect, clip, targetRectTransform, const_alpha); + return; + } + + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint16 *>(srcPixels), sbpl, targetRect, sourceRect, clip, targetRectTransform, + Blend_on_RGB16_SourceAndConstAlpha_Neon_create<quint16>(blend_8_pixels_rgb16_on_rgb16_neon, const_alpha)); +} + +void qt_transform_image_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha) +{ + if (const_alpha == 0) + return; + + qt_transform_image(reinterpret_cast<quint16 *>(destPixels), dbpl, + reinterpret_cast<const quint32 *>(srcPixels), sbpl, targetRect, sourceRect, clip, targetRectTransform, + Blend_on_RGB16_SourceAndConstAlpha_Neon_create<quint32>(blend_8_pixels_argb32_on_rgb16_neon, const_alpha)); +} + +static inline void convert_8_pixels_rgb16_to_argb32(quint32 *dst, const quint16 *src) +{ + asm volatile ( + "vld1.16 { d0, d1 }, [%[SRC]]\n\t" + + /* convert 8 r5g6b5 pixel data from {d0, d1} to planar 8-bit format + and put data into d4 - red, d3 - green, d2 - blue */ + "vshrn.u16 d4, q0, #8\n\t" + "vshrn.u16 d3, q0, #3\n\t" + "vsli.u16 q0, q0, #5\n\t" + "vsri.u8 d4, d4, #5\n\t" + "vsri.u8 d3, d3, #6\n\t" + "vshrn.u16 d2, q0, #2\n\t" + + /* fill d5 - alpha with 0xff */ + "mov r2, #255\n\t" + "vdup.8 d5, r2\n\t" + + "vst4.8 { d2, d3, d4, d5 }, [%[DST]]" + : : [DST]"r" (dst), [SRC]"r" (src) + : "memory", "r2", "d0", "d1", "d2", "d3", "d4", "d5" + ); +} + +uint * QT_FASTCALL qt_destFetchRGB16_neon(uint *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length) +{ + const ushort *data = (const ushort *)rasterBuffer->scanLine(y) + x; + + int i = 0; + for (; i < length - 7; i += 8) + convert_8_pixels_rgb16_to_argb32(&buffer[i], &data[i]); + + if (i < length) { + quint16 srcBuffer[8]; + quint32 dstBuffer[8]; + + int tail = length - i; + for (int j = 0; j < tail; ++j) + srcBuffer[j] = data[i + j]; + + convert_8_pixels_rgb16_to_argb32(dstBuffer, srcBuffer); + + for (int j = 0; j < tail; ++j) + buffer[i + j] = dstBuffer[j]; + } + + return buffer; +} + +static inline void convert_8_pixels_argb32_to_rgb16(quint16 *dst, const quint32 *src) +{ + asm volatile ( + "vld4.8 { d0, d1, d2, d3 }, [%[SRC]]\n\t" + + /* convert to r5g6b5 and store it into {d28, d29} */ + "vshll.u8 q14, d2, #8\n\t" + "vshll.u8 q8, d1, #8\n\t" + "vshll.u8 q9, d0, #8\n\t" + "vsri.u16 q14, q8, #5\n\t" + "vsri.u16 q14, q9, #11\n\t" + + "vst1.16 { d28, d29 }, [%[DST]]" + : : [DST]"r" (dst), [SRC]"r" (src) + : "memory", "d0", "d1", "d2", "d3", "d16", "d17", "d18", "d19", "d28", "d29" + ); +} + +void QT_FASTCALL qt_destStoreRGB16_neon(QRasterBuffer *rasterBuffer, int x, int y, const uint *buffer, int length) +{ + quint16 *data = (quint16*)rasterBuffer->scanLine(y) + x; + + int i = 0; + for (; i < length - 7; i += 8) + convert_8_pixels_argb32_to_rgb16(&data[i], &buffer[i]); + + if (i < length) { + quint32 srcBuffer[8]; + quint16 dstBuffer[8]; + + int tail = length - i; + for (int j = 0; j < tail; ++j) + srcBuffer[j] = buffer[i + j]; + + convert_8_pixels_argb32_to_rgb16(dstBuffer, srcBuffer); + + for (int j = 0; j < tail; ++j) + data[i + j] = dstBuffer[j]; + } +} + +void QT_FASTCALL comp_func_solid_SourceOver_neon(uint *destPixels, int length, uint color, uint const_alpha) +{ + if ((const_alpha & qAlpha(color)) == 255) { + QT_MEMFILL_UINT(destPixels, length, color); + } else { + if (const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + + const quint32 minusAlphaOfColor = qAlpha(~color); + int x = 0; + + uint32_t *dst = (uint32_t *) destPixels; + const uint32x4_t colorVector = vdupq_n_u32(color); + uint16x8_t half = vdupq_n_u16(0x80); + const uint16x8_t minusAlphaOfColorVector = vdupq_n_u16(minusAlphaOfColor); + + for (; x < length-3; x += 4) { + uint32x4_t dstVector = vld1q_u32(&dst[x]); + + const uint8x16_t dst8 = vreinterpretq_u8_u32(dstVector); + + const uint8x8_t dst8_low = vget_low_u8(dst8); + const uint8x8_t dst8_high = vget_high_u8(dst8); + + const uint16x8_t dst16_low = vmovl_u8(dst8_low); + const uint16x8_t dst16_high = vmovl_u8(dst8_high); + + const uint16x8_t result16_low = qvbyte_mul_u16(dst16_low, minusAlphaOfColorVector, half); + const uint16x8_t result16_high = qvbyte_mul_u16(dst16_high, minusAlphaOfColorVector, half); + + const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result16_low)); + const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result16_high)); + + uint32x4_t blendedPixels = vcombine_u32(result32_low, result32_high); + uint32x4_t colorPlusBlendedPixels = vaddq_u32(colorVector, blendedPixels); + vst1q_u32(&dst[x], colorPlusBlendedPixels); + } + + for (;x < length; ++x) + destPixels[x] = color + BYTE_MUL(destPixels[x], minusAlphaOfColor); + } +} + +void QT_FASTCALL comp_func_Plus_neon(uint *dst, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) { + uint *const end = dst + length; + uint *const neonEnd = end - 3; + + while (dst < neonEnd) { + asm volatile ( + "vld2.8 { d0, d1 }, [%[SRC]] !\n\t" + "vld2.8 { d2, d3 }, [%[DST]]\n\t" + "vqadd.u8 q0, q0, q1\n\t" + "vst2.8 { d0, d1 }, [%[DST]] !\n\t" + : [DST]"+r" (dst), [SRC]"+r" (src) + : + : "memory", "d0", "d1", "d2", "d3", "q0", "q1" + ); + } + + while (dst != end) { + *dst = comp_func_Plus_one_pixel(*dst, *src); + ++dst; + ++src; + } + } else { + int x = 0; + const int one_minus_const_alpha = 255 - const_alpha; + const uint16x8_t constAlphaVector = vdupq_n_u16(const_alpha); + const uint16x8_t oneMinusconstAlphaVector = vdupq_n_u16(one_minus_const_alpha); + + const uint16x8_t half = vdupq_n_u16(0x80); + for (; x < length - 3; x += 4) { + const uint32x4_t src32 = vld1q_u32((uint32_t *)&src[x]); + const uint8x16_t src8 = vreinterpretq_u8_u32(src32); + uint8x16_t dst8 = vld1q_u8((uint8_t *)&dst[x]); + uint8x16_t result = vqaddq_u8(dst8, src8); + + uint16x8_t result_low = vmovl_u8(vget_low_u8(result)); + uint16x8_t result_high = vmovl_u8(vget_high_u8(result)); + + uint16x8_t dst_low = vmovl_u8(vget_low_u8(dst8)); + uint16x8_t dst_high = vmovl_u8(vget_high_u8(dst8)); + + result_low = qvinterpolate_pixel_255(result_low, constAlphaVector, dst_low, oneMinusconstAlphaVector, half); + result_high = qvinterpolate_pixel_255(result_high, constAlphaVector, dst_high, oneMinusconstAlphaVector, half); + + const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result_low)); + const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result_high)); + vst1q_u32((uint32_t *)&dst[x], vcombine_u32(result32_low, result32_high)); + } + + for (; x < length; ++x) + dst[x] = comp_func_Plus_one_pixel_const_alpha(dst[x], src[x], const_alpha, one_minus_const_alpha); + } +} + +static const int tileSize = 32; + +extern "C" void qt_rotate90_16_neon(quint16 *dst, const quint16 *src, int sstride, int dstride, int count); + +void qt_memrotate90_16_neon(const uchar *srcPixels, int w, int h, int sstride, uchar *destPixels, int dstride) +{ + const ushort *src = (const ushort *)srcPixels; + ushort *dest = (ushort *)destPixels; + + sstride /= sizeof(ushort); + dstride /= sizeof(ushort); + + const int pack = sizeof(quint32) / sizeof(ushort); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(ushort)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + + if (unaligned) { + for (int x = startx; x >= stopx; --x) { + ushort *d = dest + (w - x - 1) * dstride; + for (int y = 0; y < unaligned; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize + unaligned; + const int stopy = qMin(starty + tileSize, h - unoptimizedY); + + int x = startx; + // qt_rotate90_16_neon writes to eight rows, four pixels at a time + for (; x >= stopx + 7; x -= 8) { + ushort *d = dest + (w - x - 1) * dstride + starty; + const ushort *s = &src[starty * sstride + x - 7]; + qt_rotate90_16_neon(d, s, sstride * 2, dstride * 2, stopy - starty); + } + + for (; x >= stopx; --x) { + quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + starty); + for (int y = starty; y < stopy; y += pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(int) * 8 / pack * i); + const ushort color = src[(y + i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + + if (unoptimizedY) { + const int starty = h - unoptimizedY; + for (int x = startx; x >= stopx; --x) { + ushort *d = dest + (w - x - 1) * dstride + starty; + for (int y = starty; y < h; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} + +extern "C" void qt_rotate270_16_neon(quint16 *dst, const quint16 *src, int sstride, int dstride, int count); + +void qt_memrotate270_16_neon(const uchar *srcPixels, int w, int h, + int sstride, + uchar *destPixels, int dstride) +{ + const ushort *src = (const ushort *)srcPixels; + ushort *dest = (ushort *)destPixels; + + sstride /= sizeof(ushort); + dstride /= sizeof(ushort); + + const int pack = sizeof(quint32) / sizeof(ushort); + const int unaligned = + qMin(uint((long(dest) & (sizeof(quint32)-1)) / sizeof(ushort)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + + if (unaligned) { + for (int x = startx; x < stopx; ++x) { + ushort *d = dest + x * dstride; + for (int y = h - 1; y >= h - unaligned; --y) { + *d++ = src[y * sstride + x]; + } + } + } + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - unaligned - ty * tileSize; + const int stopy = qMax(starty - tileSize, unoptimizedY); + + int x = startx; + // qt_rotate90_16_neon writes to eight rows, four pixels at a time + for (; x < stopx - 7; x += 8) { + ushort *d = dest + x * dstride + h - 1 - starty; + const ushort *s = &src[starty * sstride + x]; + qt_rotate90_16_neon(d + 7 * dstride, s, -sstride * 2, -dstride * 2, starty - stopy); + } + + for (; x < stopx; ++x) { + quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride + + h - 1 - starty); + for (int y = starty; y > stopy; y -= pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(int) * 8 / pack * i); + const ushort color = src[(y - i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = unoptimizedY - 1; + for (int x = startx; x < stopx; ++x) { + ushort *d = dest + x * dstride + h - 1 - starty; + for (int y = starty; y >= 0; --y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_NEON + diff --git a/src/gui/painting/qdrawhelper_neon_asm.S b/src/gui/painting/qdrawhelper_neon_asm.S new file mode 100644 index 0000000000..e8434fc8e7 --- /dev/null +++ b/src/gui/painting/qdrawhelper_neon_asm.S @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* Prevent the stack from becoming executable for no reason... */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + +.text +.fpu neon +.arch armv7a +.altmacro + +/* void blend_8_pixels_argb32_on_rgb16_neon(quint16 *dst, const quint32 *src, int const_alpha) */ + + .func blend_8_pixels_argb32_on_rgb16_neon + .global blend_8_pixels_argb32_on_rgb16_neon + /* For ELF format also set function visibility to hidden */ +#ifdef __ELF__ + .hidden blend_8_pixels_argb32_on_rgb16_neon + .type blend_8_pixels_argb32_on_rgb16_neon, %function +#endif +blend_8_pixels_argb32_on_rgb16_neon: + vld4.8 { d0, d1, d2, d3 }, [r1] + vld1.16 { d4, d5 }, [r0] + + cmp r2, #256 + beq .blend_32_inner + + vdup.8 d6, r2 + + /* multiply by const_alpha */ + vmull.u8 q8, d6, d0 + vmull.u8 q9, d6, d1 + vmull.u8 q10, d6, d2 + vmull.u8 q11, d6, d3 + + vshrn.u16 d0, q8, #8 + vshrn.u16 d1, q9, #8 + vshrn.u16 d2, q10, #8 + vshrn.u16 d3, q11, #8 + +.blend_32_inner: + /* convert 8 r5g6b5 pixel data from {d4, d5} to planar 8-bit format + and put data into d6 - red, d7 - green, d30 - blue */ + vshrn.u16 d6, q2, #8 + vshrn.u16 d7, q2, #3 + vsli.u16 q2, q2, #5 + vsri.u8 d6, d6, #5 + vmvn.8 d3, d3 + vsri.u8 d7, d7, #6 + vshrn.u16 d30, q2, #2 + + pld [r0, #128] + + /* now do alpha blending, storing results in 8-bit planar format + into d16 - red, d19 - green, d18 - blue */ + vmull.u8 q10, d3, d6 + vmull.u8 q11, d3, d7 + vmull.u8 q12, d3, d30 + vrshr.u16 q13, q10, #8 + vrshr.u16 q3, q11, #8 + vrshr.u16 q15, q12, #8 + vraddhn.u16 d20, q10, q13 + vraddhn.u16 d23, q11, q3 + vraddhn.u16 d22, q12, q15 + vqadd.u8 d16, d2, d20 + vqadd.u8 q9, q0, q11 + /* convert the result to r5g6b5 and store it into {d28, d29} */ + vshll.u8 q14, d16, #8 + vshll.u8 q8, d19, #8 + vshll.u8 q9, d18, #8 + vsri.u16 q14, q8, #5 + vsri.u16 q14, q9, #11 + + vst1.16 { d28, d29 }, [r0] + + bx lr + + .endfunc + +/* void blend_8_pixels_rgb16_on_rgb16_neon(quint16 *dst, const quint16 *src, int const_alpha) */ + + .func blend_8_pixels_rgb16_on_rgb16_neon + .global blend_8_pixels_rgb16_on_rgb16_neon + /* For ELF format also set function visibility to hidden */ +#ifdef __ELF__ + .hidden blend_8_pixels_rgb16_on_rgb16_neon + .type blend_8_pixels_rgb16_on_rgb16_neon, %function +#endif +blend_8_pixels_rgb16_on_rgb16_neon: + vld1.16 { d0, d1 }, [r0] + vld1.16 { d2, d3 }, [r1] + + rsb r3, r2, #256 + vdup.8 d4, r2 + vdup.8 d5, r3 + + /* convert 8 r5g6b5 pixel data from {d0, d1} to planar 8-bit format + and put data into d6 - red, d7 - green, d30 - blue */ + vshrn.u16 d6, q0, #8 + vshrn.u16 d7, q0, #3 + vsli.u16 q0, q0, #5 + vsri.u8 d6, d6, #5 + vsri.u8 d7, d7, #6 + vshrn.u16 d30, q0, #2 + + /* same from {d2, d3} into {d26, d27, d28} */ + vshrn.u16 d26, q1, #8 + vshrn.u16 d27, q1, #3 + vsli.u16 q1, q1, #5 + vsri.u8 d26, d26, #5 + vsri.u8 d27, d27, #6 + vshrn.u16 d28, q1, #2 + + /* multiply dst by inv const_alpha */ + vmull.u8 q10, d5, d6 + vmull.u8 q11, d5, d7 + vmull.u8 q12, d5, d30 + + vshrn.u16 d6, q10, #8 + vshrn.u16 d7, q11, #8 + vshrn.u16 d30, q12, #8 + + /* multiply src by const_alpha */ + vmull.u8 q10, d4, d26 + vmull.u8 q11, d4, d27 + vmull.u8 q12, d4, d28 + + vshrn.u16 d26, q10, #8 + vshrn.u16 d27, q11, #8 + vshrn.u16 d28, q12, #8 + + /* preload dst + 128 */ + pld [r0, #128] + + /* add components, storing results in 8-bit planar format + into d16 - red, d19 - green, d18 - blue */ + vadd.u8 d16, d26, d6 + vadd.u8 d19, d27, d7 + vadd.u8 d18, d28, d30 + + /* convert the result to r5g6b5 and store it into {d28, d29} */ + vshll.u8 q14, d16, #8 + vshll.u8 q8, d19, #8 + vshll.u8 q9, d18, #8 + vsri.u16 q14, q8, #5 + vsri.u16 q14, q9, #11 + + vst1.16 { d28, d29 }, [r0] + + bx lr + + .endfunc + +/* void qt_rotate90_16_neon(quint16 *dst, const quint16 *src, int sstride, int dstride, int count) */ + .func qt_rotate90_16_neon + .global qt_rotate90_16_neon + /* For ELF format also set function visibility to hidden */ +#ifdef __ELF__ + .hidden qt_rotate90_16_neon + .type qt_rotate90_16_neon, %function +#endif +qt_rotate90_16_neon: + push { r4-r11, lr } + ldr r5, [sp, #(9*4)] + + /* The preloads are the key to getting good performance */ + pld [r1] + + mov r4, r5, asr #2 + add r6, r0, r3 + add r7, r6, r3 + + add r8, r7, r3 + add r9, r8, r3 + + pld [r1, r2] + + add r10, r9, r3 + add r11, r10, r3 + + add r3, r3, r11 + and r5, r5, #3 + + pld [r1, r2, lsl #1] + + cmp r4, #0 + beq .rotate90_16_tail + +.rotate90_16_loop: + vld1.16 { q8 }, [r1], r2 + + pld [r1, r2, lsl #1] + + vld1.16 { q9 }, [r1], r2 + vld1.16 { q10 }, [r1], r2 + vld1.16 { q11 }, [r1], r2 + + pld [r1] + + /* Could have used four quad-word zips instead, + but those take three cycles as opposed to one. */ + vzip.16 d16, d20 + vzip.16 d17, d21 + + vzip.16 d18, d22 + + pld [r1, r2] + + vzip.16 d19, d23 + + vzip.16 d16, d18 + vzip.16 d17, d19 + + pld [r1, r2, lsl #1] + + vzip.16 d20, d22 + vzip.16 d21, d23 + + vst1.16 { d23 }, [r0]! + vst1.16 { d21 }, [r6]! + vst1.16 { d19 }, [r7]! + vst1.16 { d17 }, [r8]! + vst1.16 { d22 }, [r9]! + vst1.16 { d20 }, [r10]! + vst1.16 { d18 }, [r11]! + vst1.16 { d16 }, [r3]! + + sub r4, r4, #1 + cmp r4, #0 + bne .rotate90_16_loop + b .rotate90_16_tail + +.rotate90_16_tail_loop: + sub r5, r5, #2 + + vld1.16 { q8 }, [r1], r2 + vld1.16 { q9 }, [r1], r2 + + vzip.16 d16, d18 + vzip.16 d17, d19 + + vst1.32 { d19[1] }, [r0]! + vst1.32 { d19[0] }, [r6]! + vst1.32 { d17[1] }, [r7]! + vst1.32 { d17[0] }, [r8]! + vst1.32 { d18[1] }, [r9]! + vst1.32 { d18[0] }, [r10]! + vst1.32 { d16[1] }, [r11]! + vst1.32 { d16[0] }, [r3]! + +.rotate90_16_tail: + cmp r5, #0 + bgt .rotate90_16_tail_loop + + pop { r4-r11, pc } + + .endfunc diff --git a/src/gui/painting/qdrawhelper_neon_p.h b/src/gui/painting/qdrawhelper_neon_p.h new file mode 100644 index 0000000000..5db66932a1 --- /dev/null +++ b/src/gui/painting/qdrawhelper_neon_p.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_NEON_P_H +#define QDRAWHELPER_NEON_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/qdrawhelper_p.h> + +QT_BEGIN_NAMESPACE + +#ifdef QT_HAVE_NEON + +void qt_blend_argb32_on_argb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb32_on_rgb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_argb32_on_argb32_scanline_neon(uint *dest, + const uint *src, + int length, + uint const_alpha); + +void qt_blend_rgb16_on_argb32_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_alphamapblit_quint16_neon(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *bitmap, + int mapWidth, int mapHeight, int mapStride, + const QClipData *clip); + +void qt_scale_image_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha); + +void qt_scale_image_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + int const_alpha); + +void qt_transform_image_argb32_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha); + +void qt_transform_image_rgb16_on_rgb16_neon(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clip, + const QTransform &targetRectTransform, + int const_alpha); + +void qt_memfill32_neon(quint32 *dest, quint32 value, int count); +void qt_memrotate90_16_neon(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl); +void qt_memrotate270_16_neon(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl); + +uint * QT_FASTCALL qt_destFetchRGB16_neon(uint *buffer, + QRasterBuffer *rasterBuffer, + int x, int y, int length); + +void QT_FASTCALL qt_destStoreRGB16_neon(QRasterBuffer *rasterBuffer, + int x, int y, const uint *buffer, int length); + +void QT_FASTCALL comp_func_solid_SourceOver_neon(uint *destPixels, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_Plus_neon(uint *dst, const uint *src, int length, uint const_alpha); + +#endif // QT_HAVE_NEON + +QT_END_NAMESPACE + +#endif // QDRAWHELPER_NEON_P_H diff --git a/src/gui/painting/qdrawhelper_p.h b/src/gui/painting/qdrawhelper_p.h new file mode 100644 index 0000000000..d4e731bbb2 --- /dev/null +++ b/src/gui/painting/qdrawhelper_p.h @@ -0,0 +1,2033 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_P_H +#define QDRAWHELPER_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 "QtCore/qglobal.h" +#include "QtGui/qcolor.h" +#include "QtGui/qpainter.h" +#include "QtGui/qimage.h" +#ifndef QT_FT_BEGIN_HEADER +#define QT_FT_BEGIN_HEADER +#define QT_FT_END_HEADER +#endif +#include "private/qrasterdefs_p.h" +#include <private/qsimd_p.h> + +#ifdef Q_WS_QWS +#include "QtGui/qscreen_qws.h" +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_CC_MSVC) && _MSCVER <= 1300 && !defined(Q_CC_INTEL) +#define Q_STATIC_TEMPLATE_SPECIALIZATION static +#else +#define Q_STATIC_TEMPLATE_SPECIALIZATION +#endif + +#if defined(Q_CC_RVCT) +// RVCT doesn't like static template functions +# define Q_STATIC_TEMPLATE_FUNCTION +# define Q_STATIC_INLINE_FUNCTION static __forceinline +#else +# define Q_STATIC_TEMPLATE_FUNCTION static +# define Q_STATIC_INLINE_FUNCTION static inline +#endif + +static const uint AMASK = 0xff000000; +static const uint RMASK = 0x00ff0000; +static const uint GMASK = 0x0000ff00; +static const uint BMASK = 0x000000ff; + +/******************************************************************************* + * QSpan + * + * duplicate definition of FT_Span + */ +typedef QT_FT_Span QSpan; + +struct QSolidData; +struct QTextureData; +struct QGradientData; +struct QLinearGradientData; +struct QRadialGradientData; +struct QConicalGradientData; +struct QSpanData; +class QGradient; +class QRasterBuffer; +class QClipData; +class QRasterPaintEngineState; + +typedef QT_FT_SpanFunc ProcessSpans; +typedef void (*BitmapBlitFunc)(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *bitmap, + int mapWidth, int mapHeight, int mapStride); + +typedef void (*AlphamapBlitFunc)(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uchar *bitmap, + int mapWidth, int mapHeight, int mapStride, + const QClipData *clip); + +typedef void (*AlphaRGBBlitFunc)(QRasterBuffer *rasterBuffer, + int x, int y, quint32 color, + const uint *rgbmask, + int mapWidth, int mapHeight, int mapStride, + const QClipData *clip); + +typedef void (*RectFillFunc)(QRasterBuffer *rasterBuffer, + int x, int y, int width, int height, + quint32 color); + +typedef void (*SrcOverBlendFunc)(uchar *destPixels, int dbpl, + const uchar *src, int spbl, + int w, int h, + int const_alpha); + +typedef void (*SrcOverScaleFunc)(uchar *destPixels, int dbpl, + const uchar *src, int spbl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clipRect, + int const_alpha); + +typedef void (*SrcOverTransformFunc)(uchar *destPixels, int dbpl, + const uchar *src, int spbl, + const QRectF &targetRect, + const QRectF &sourceRect, + const QRect &clipRect, + const QTransform &targetRectTransform, + int const_alpha); + +typedef void (*MemRotateFunc)(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl); + +struct DrawHelper { + ProcessSpans blendColor; + ProcessSpans blendGradient; + BitmapBlitFunc bitmapBlit; + AlphamapBlitFunc alphamapBlit; + AlphaRGBBlitFunc alphaRGBBlit; + RectFillFunc fillRect; +}; + +extern SrcOverBlendFunc qBlendFunctions[QImage::NImageFormats][QImage::NImageFormats]; +extern SrcOverScaleFunc qScaleFunctions[QImage::NImageFormats][QImage::NImageFormats]; +extern SrcOverTransformFunc qTransformFunctions[QImage::NImageFormats][QImage::NImageFormats]; +extern MemRotateFunc qMemRotateFunctions[QImage::NImageFormats][3]; + +extern DrawHelper qDrawHelper[QImage::NImageFormats]; + +void qBlendTexture(int count, const QSpan *spans, void *userData); +#if defined(Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +extern DrawHelper qDrawHelperCallback[QImage::NImageFormats]; +void qBlendTextureCallback(int count, const QSpan *spans, void *userData); +#endif + +typedef void (QT_FASTCALL *CompositionFunction)(uint *dest, const uint *src, int length, uint const_alpha); +typedef void (QT_FASTCALL *CompositionFunctionSolid)(uint *dest, int length, uint color, uint const_alpha); + +void qInitDrawhelperAsm(); + +class QRasterPaintEngine; + +struct QSolidData +{ + uint color; +}; + +struct QLinearGradientData +{ + struct { + qreal x; + qreal y; + } origin; + struct { + qreal x; + qreal y; + } end; +}; + +struct QRadialGradientData +{ + struct { + qreal x; + qreal y; + } center; + struct { + qreal x; + qreal y; + } focal; + qreal radius; +}; + +struct QConicalGradientData +{ + struct { + qreal x; + qreal y; + } center; + qreal angle; +}; + +struct QGradientData +{ + QGradient::Spread spread; + + union { + QLinearGradientData linear; + QRadialGradientData radial; + QConicalGradientData conical; + }; + +#ifdef Q_WS_QWS +#define GRADIENT_STOPTABLE_SIZE 256 +#else +#define GRADIENT_STOPTABLE_SIZE 1024 +#endif + + uint* colorTable; //[GRADIENT_STOPTABLE_SIZE]; + + uint alphaColor : 1; +}; + +struct QTextureData +{ + const uchar *imageData; + const uchar *scanLine(int y) const { return imageData + y*bytesPerLine; } + + int width; + int height; + // clip rect + int x1; + int y1; + int x2; + int y2; + int bytesPerLine; + QImage::Format format; + const QVector<QRgb> *colorTable; + bool hasAlpha; + enum Type { + Plain, + Tiled + }; + Type type; + int const_alpha; +}; + +struct QSpanData +{ + QSpanData() : tempImage(0) {} + ~QSpanData() { delete tempImage; } + + QRasterBuffer *rasterBuffer; +#ifdef Q_WS_QWS + QRasterPaintEngine *rasterEngine; +#endif + ProcessSpans blend; + ProcessSpans unclipped_blend; + BitmapBlitFunc bitmapBlit; + AlphamapBlitFunc alphamapBlit; + AlphaRGBBlitFunc alphaRGBBlit; + RectFillFunc fillRect; + qreal m11, m12, m13, m21, m22, m23, m33, dx, dy; // inverse xform matrix + const QClipData *clip; + enum Type { + None, + Solid, + LinearGradient, + RadialGradient, + ConicalGradient, + Texture + } type : 8; + int txop : 8; + int fast_matrix : 1; + bool bilinear; + QImage *tempImage; + union { + QSolidData solid; + QGradientData gradient; + QTextureData texture; + }; + + void init(QRasterBuffer *rb, const QRasterPaintEngine *pe); + void setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode); + void setupMatrix(const QTransform &matrix, int bilinear); + void initTexture(const QImage *image, int alpha, QTextureData::Type = QTextureData::Plain, const QRect &sourceRect = QRect()); + void adjustSpanMethods(); +}; + +#if defined(Q_CC_RVCT) +# pragma push +# pragma arm +#endif +Q_STATIC_INLINE_FUNCTION uint INTERPOLATE_PIXEL_255(uint x, uint a, uint y, uint b) { + uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} +#if defined(Q_CC_RVCT) +# pragma pop +#endif + +#if QT_POINTER_SIZE == 8 // 64-bit versions + +Q_STATIC_INLINE_FUNCTION uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) { + quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; + t += (((quint64(y)) | ((quint64(y)) << 24)) & 0x00ff00ff00ff00ff) * b; + t >>= 8; + t &= 0x00ff00ff00ff00ff; + return (uint(t)) | (uint(t >> 24)); +} + +Q_STATIC_INLINE_FUNCTION uint BYTE_MUL(uint x, uint a) { + quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8; + t &= 0x00ff00ff00ff00ff; + return (uint(t)) | (uint(t >> 24)); +} + +Q_STATIC_INLINE_FUNCTION uint PREMUL(uint x) { + uint a = x >> 24; + quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8; + t &= 0x000000ff00ff00ff; + return (uint(t)) | (uint(t >> 24)) | (a << 24); +} + +#else // 32-bit versions + +Q_STATIC_INLINE_FUNCTION uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) { + uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t >>= 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x &= 0xff00ff00; + x |= t; + return x; +} + +#if defined(Q_CC_RVCT) +# pragma push +# pragma arm +#endif +Q_STATIC_INLINE_FUNCTION uint BYTE_MUL(uint x, uint a) { + uint t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return x; +} +#if defined(Q_CC_RVCT) +# pragma pop +#endif + +Q_STATIC_INLINE_FUNCTION uint PREMUL(uint x) { + uint a = x >> 24; + uint t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff) * a; + x = (x + ((x >> 8) & 0xff) + 0x80); + x &= 0xff00; + x |= t | (a << 24); + return x; +} +#endif + + +Q_STATIC_INLINE_FUNCTION uint BYTE_MUL_RGB16(uint x, uint a) { + a += 1; + uint t = (((x & 0x07e0)*a) >> 8) & 0x07e0; + t |= (((x & 0xf81f)*(a>>2)) >> 6) & 0xf81f; + return t; +} + +Q_STATIC_INLINE_FUNCTION uint BYTE_MUL_RGB16_32(uint x, uint a) { + uint t = (((x & 0xf81f07e0) >> 5)*a) & 0xf81f07e0; + t |= (((x & 0x07e0f81f)*a) >> 5) & 0x07e0f81f; + return t; +} + +#define INV_PREMUL(p) \ + (qAlpha(p) == 0 ? 0 : \ + ((qAlpha(p) << 24) \ + | (((255*qRed(p))/ qAlpha(p)) << 16) \ + | (((255*qGreen(p)) / qAlpha(p)) << 8) \ + | ((255*qBlue(p)) / qAlpha(p)))) + +template <class DST, class SRC> +inline DST qt_colorConvert(SRC color, DST dummy) +{ + Q_UNUSED(dummy); + return DST(color); +} + + +template <> +inline quint32 qt_colorConvert(quint16 color, quint32 dummy) +{ + Q_UNUSED(dummy); + const int r = (color & 0xf800); + const int g = (color & 0x07e0); + const int b = (color & 0x001f); + const int tr = (r >> 8) | (r >> 13); + const int tg = (g >> 3) | (g >> 9); + const int tb = (b << 3) | (b >> 2); + + return qRgb(tr, tg, tb); +} + +template <> +inline quint16 qt_colorConvert(quint32 color, quint16 dummy) +{ + Q_UNUSED(dummy); + const int r = qRed(color) << 8; + const int g = qGreen(color) << 3; + const int b = qBlue(color) >> 3; + + return (r & 0xf800) | (g & 0x07e0)| (b & 0x001f); +} + +class quint32p +{ +public: + inline quint32p(quint32 v) : data(PREMUL(v)) {} + + inline operator quint32() const { return data; } + + inline operator quint16() const + { + return qt_colorConvert<quint16, quint32>(data, 0); + } + + Q_STATIC_INLINE_FUNCTION quint32p fromRawData(quint32 v) + { + quint32p p; + p.data = v; + return p; + } + +private: + quint32p() {} + quint32 data; +} Q_PACKED; + +class qabgr8888 +{ +public: + inline qabgr8888(quint32 v) + { + data = qRgba(qBlue(v), qGreen(v), qRed(v), qAlpha(v)); + } + + inline bool operator==(const qabgr8888 &v) const { return data == v.data; } + +private: + quint32 data; +} Q_PACKED; + +class qrgb565; + +class qargb8565 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return true; } + + inline qargb8565() {} + inline qargb8565(quint32 v); + inline explicit qargb8565(quint32p v); + inline qargb8565(const qargb8565 &v); + inline qargb8565(const qrgb565 &v); + + inline operator quint32() const; + inline operator quint16() const; + + inline quint8 alpha() const { return data[0]; } + inline qargb8565 truncedAlpha() { + data[0] &= 0xf8; + data[1] &= 0xdf; + return *this; + } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 3; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x20 - alpha(a); } + + inline qargb8565 byte_mul(quint8 a) const; + inline qargb8565 operator+(qargb8565 v) const; + inline bool operator==(const qargb8565 &v) const; + + inline quint32 rawValue() const; + inline quint16 rawValue16() const; + +private: + friend class qrgb565; + + quint8 data[3]; +} Q_PACKED; + +class qrgb565 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return false; } + + qrgb565(int v = 0) : data(v) {} + + inline explicit qrgb565(quint32p v); + inline explicit qrgb565(quint32 v); + inline explicit qrgb565(const qargb8565 &v); + + inline operator quint32() const; + inline operator quint16() const; + + inline qrgb565 operator+(qrgb565 v) const; + + inline quint8 alpha() const { return 0xff; } + inline qrgb565 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 3; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x20 - alpha(a); } + + inline qrgb565 byte_mul(quint8 a) const; + + inline bool operator==(const qrgb565 &v) const; + inline quint16 rawValue() const { return data; } + +private: + friend class qargb8565; + + quint16 data; +} Q_PACKED; + +qargb8565::qargb8565(quint32 v) +{ + *this = qargb8565(quint32p(v)); +} + +qargb8565::qargb8565(quint32p v) +{ + data[0] = qAlpha(v); + const int r = qRed(v); + const int g = qGreen(v); + const int b = qBlue(v); + data[1] = ((g << 3) & 0xe0) | (b >> 3); + data[2] = (r & 0xf8) | (g >> 5); +} + +qargb8565::qargb8565(const qargb8565 &v) +{ + data[0] = v.data[0]; + data[1] = v.data[1]; + data[2] = v.data[2]; +} + +qargb8565::qargb8565(const qrgb565 &v) +{ + data[0] = 0xff; + data[1] = v.data & 0xff; + data[2] = v.data >> 8; +} + +qargb8565::operator quint32() const +{ + const quint16 rgb = (data[2] << 8) | data[1]; + const int a = data[0]; + const int r = (rgb & 0xf800); + const int g = (rgb & 0x07e0); + const int b = (rgb & 0x001f); + const int tr = qMin(a, (r >> 8) | (r >> 13)); + const int tg = qMin(a, (g >> 3) | (g >> 9)); + const int tb = qMin(a, (b << 3) | (b >> 2)); + return qRgba(tr, tg, tb, data[0]); +} + +qargb8565::operator quint16() const +{ + return (data[2] << 8) | data[1]; +} + +qargb8565 qargb8565::operator+(qargb8565 v) const +{ + qargb8565 t; + t.data[0] = data[0] + v.data[0]; + const quint16 rgb = ((data[2] + v.data[2]) << 8) + + (data[1] + v.data[1]); + t.data[1] = rgb & 0xff; + t.data[2] = rgb >> 8; + return t; +} + +qargb8565 qargb8565::byte_mul(quint8 a) const +{ + qargb8565 result; + result.data[0] = (data[0] * a) >> 5; + + const quint16 x = (data[2] << 8) | data[1]; + const quint16 t = ((((x & 0x07e0) >> 5) * a) & 0x07e0) | + ((((x & 0xf81f) * a) >> 5) & 0xf81f); + result.data[1] = t & 0xff; + result.data[2] = t >> 8; + return result; +} + +bool qargb8565::operator==(const qargb8565 &v) const +{ + return data[0] == v.data[0] + && data[1] == v.data[1] + && data[2] == v.data[2]; +} + +quint32 qargb8565::rawValue() const +{ + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +quint16 qargb8565::rawValue16() const +{ + return (data[2] << 8) | data[1]; +} + +qrgb565::qrgb565(quint32p v) +{ + *this = qrgb565(quint32(v)); +} + +qrgb565::qrgb565(quint32 v) +{ + const int r = qRed(v) << 8; + const int g = qGreen(v) << 3; + const int b = qBlue(v) >> 3; + + data = (r & 0xf800) | (g & 0x07e0)| (b & 0x001f); +} + +qrgb565::qrgb565(const qargb8565 &v) +{ + data = (v.data[2] << 8) | v.data[1]; +} + +qrgb565::operator quint32() const +{ + const int r = (data & 0xf800); + const int g = (data & 0x07e0); + const int b = (data & 0x001f); + const int tr = (r >> 8) | (r >> 13); + const int tg = (g >> 3) | (g >> 9); + const int tb = (b << 3) | (b >> 2); + return qRgb(tr, tg, tb); +} + +qrgb565::operator quint16() const +{ + return data; +} + +qrgb565 qrgb565::operator+(qrgb565 v) const +{ + qrgb565 t; + t.data = data + v.data; + return t; +} + +qrgb565 qrgb565::byte_mul(quint8 a) const +{ + qrgb565 result; + result.data = ((((data & 0x07e0) >> 5) * a) & 0x07e0) | + ((((data & 0xf81f) * a) >> 5) & 0xf81f); + return result; +} + +bool qrgb565::operator==(const qrgb565 &v) const +{ + return data == v.data; +} + +class qbgr565 +{ +public: + inline qbgr565(quint16 v) + { + data = ((v & 0x001f) << 11) | + (v & 0x07e0) | + ((v & 0xf800) >> 11); + } + + inline bool operator==(const qbgr565 &v) const + { + return data == v.data; + } + +private: + quint16 data; +} Q_PACKED; + +class qrgb555; + +class qargb8555 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return true; } + + qargb8555() {} + inline qargb8555(quint32 v); + inline explicit qargb8555(quint32p v); + inline qargb8555(const qargb8555 &v); + inline qargb8555(const qrgb555 &v); + + inline operator quint32() const; + + inline quint8 alpha() const { return data[0]; } + inline qargb8555 truncedAlpha() { data[0] &= 0xf8; return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 3; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x20 - alpha(a); } + + inline qargb8555 operator+(qargb8555 v) const; + inline qargb8555 byte_mul(quint8 a) const; + + inline bool operator==(const qargb8555 &v) const; + + inline quint32 rawValue() const; + +private: + friend class qrgb555; + quint8 data[3]; +} Q_PACKED; + +class qrgb555 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return false; } + + inline qrgb555(int v = 0) : data(v) {} + + inline explicit qrgb555(quint32p v) { *this = qrgb555(quint32(v)); } + + inline explicit qrgb555(quint32 v) + { + const int r = qRed(v) << 7; + const int g = qGreen(v) << 2; + const int b = qBlue(v) >> 3; + + data = (r & 0x7c00) | (g & 0x03e0) | (b & 0x001f); + } + + inline explicit qrgb555(quint16 v) + { + data = ((v >> 1) & (0x7c00 | 0x03e0)) | + (v & 0x001f); + } + + inline explicit qrgb555(const qargb8555 &v); + + inline operator quint32() const + { + const int r = (data & 0x7c00); + const int g = (data & 0x03e0); + const int b = (data & 0x001f); + const int tr = (r >> 7) | (r >> 12); + const int tg = (g >> 2) | (g >> 7); + const int tb = (b << 3) | (b >> 2); + + return qRgb(tr, tg, tb); + } + + inline operator quint16() const + { + const int r = ((data & 0x7c00) << 1) & 0xf800; + const int g = (((data & 0x03e0) << 1) | ((data >> 4) & 0x0020)) & 0x07e0; + const int b = (data & 0x001f); + + return r | g | b; + } + + inline qrgb555 operator+(qrgb555 v) const; + inline qrgb555 byte_mul(quint8 a) const; + + inline quint8 alpha() const { return 0xff; } + inline qrgb555 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 3; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x20 - alpha(a); } + + inline bool operator==(const qrgb555 &v) const { return v.data == data; } + inline bool operator!=(const qrgb555 &v) const { return v.data != data; } + + inline quint16 rawValue() const { return data; } + +private: + friend class qargb8555; + friend class qbgr555; + quint16 data; + +} Q_PACKED; + +qrgb555::qrgb555(const qargb8555 &v) +{ + data = (v.data[2] << 8) | v.data[1]; +} + +qrgb555 qrgb555::operator+(qrgb555 v) const +{ + qrgb555 t; + t.data = data + v.data; + return t; +} + +qrgb555 qrgb555::byte_mul(quint8 a) const +{ + quint16 t = (((data & 0x3e0) * a) >> 5) & 0x03e0; + t |= (((data & 0x7c1f) * a) >> 5) & 0x7c1f; + + qrgb555 result; + result.data = t; + return result; +} + +class qbgr555 +{ +public: + inline qbgr555(quint32 v) { *this = qbgr555(qrgb555(v)); } + + inline qbgr555(qrgb555 v) + { + data = ((v.data & 0x001f) << 10) | + (v.data & 0x03e0) | + ((v.data & 0x7c00) >> 10); + } + + inline bool operator==(const qbgr555 &v) const + { + return data == v.data; + } + +private: + quint16 data; +} Q_PACKED; + +qargb8555::qargb8555(quint32 v) +{ + v = quint32p(v); + data[0] = qAlpha(v); + const int r = qRed(v); + const int g = qGreen(v); + const int b = qBlue(v); + data[1] = ((g << 2) & 0xe0) | (b >> 3); + data[2] = ((r >> 1) & 0x7c) | (g >> 6); + +} + +qargb8555::qargb8555(quint32p v) +{ + data[0] = qAlpha(v); + const int r = qRed(v); + const int g = qGreen(v); + const int b = qBlue(v); + data[1] = ((g << 2) & 0xe0) | (b >> 3); + data[2] = ((r >> 1) & 0x7c) | (g >> 6); +} + +qargb8555::qargb8555(const qargb8555 &v) +{ + data[0] = v.data[0]; + data[1] = v.data[1]; + data[2] = v.data[2]; +} + +qargb8555::qargb8555(const qrgb555 &v) +{ + data[0] = 0xff; + data[1] = v.data & 0xff; + data[2] = v.data >> 8; +} + +qargb8555::operator quint32() const +{ + const quint16 rgb = (data[2] << 8) | data[1]; + const int r = (rgb & 0x7c00); + const int g = (rgb & 0x03e0); + const int b = (rgb & 0x001f); + const int tr = (r >> 7) | (r >> 12); + const int tg = (g >> 2) | (g >> 7); + const int tb = (b << 3) | (b >> 2); + + return qRgba(tr, tg, tb, data[0]); +} + +bool qargb8555::operator==(const qargb8555 &v) const +{ + return data[0] == v.data[0] + && data[1] == v.data[1] + && data[2] == v.data[2]; +} + +quint32 qargb8555::rawValue() const +{ + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +qargb8555 qargb8555::operator+(qargb8555 v) const +{ + qargb8555 t; + t.data[0] = data[0] + v.data[0]; + const quint16 rgb = ((data[2] + v.data[2]) << 8) + + (data[1] + v.data[1]); + t.data[1] = rgb & 0xff; + t.data[2] = rgb >> 8; + return t; +} + +qargb8555 qargb8555::byte_mul(quint8 a) const +{ + qargb8555 result; + result.data[0] = (data[0] * a) >> 5; + + const quint16 x = (data[2] << 8) | data[1]; + quint16 t = (((x & 0x3e0) * a) >> 5) & 0x03e0; + t |= (((x & 0x7c1f) * a) >> 5) & 0x7c1f; + result.data[1] = t & 0xff; + result.data[2] = t >> 8; + return result; + +} + +class qrgb666; + +class qargb6666 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return true; } + + inline qargb6666() {} + inline qargb6666(quint32 v) { *this = qargb6666(quint32p(v)); } + inline explicit qargb6666(quint32p v); + inline qargb6666(const qargb6666 &v); + inline qargb6666(const qrgb666 &v); + + inline operator quint32 () const; + + inline quint8 alpha() const; + inline qargb6666 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 2; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return (255 - a + 1) >> 2; } + + inline qargb6666 byte_mul(quint8 a) const; + inline qargb6666 operator+(qargb6666 v) const; + inline bool operator==(const qargb6666 &v) const; + + inline quint32 rawValue() const; + +private: + friend class qrgb666; + quint8 data[3]; + +} Q_PACKED; + +class qrgb666 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return false; } + + inline qrgb666() {} + inline qrgb666(quint32 v); + inline qrgb666(const qargb6666 &v); + + inline operator quint32 () const; + + inline quint8 alpha() const { return 0xff; } + inline qrgb666 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 2; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return (255 - a + 1) >> 2; } + + inline qrgb666 operator+(qrgb666 v) const; + inline qrgb666 byte_mul(quint8 a) const; + + inline bool operator==(const qrgb666 &v) const; + inline bool operator!=(const qrgb666 &v) const { return !(*this == v); } + + inline quint32 rawValue() const + { + return (data[2] << 16) | (data[1] << 8) | data[0]; + } + +private: + friend class qargb6666; + + quint8 data[3]; +} Q_PACKED; + +qrgb666::qrgb666(quint32 v) +{ + const uchar b = qBlue(v); + const uchar g = qGreen(v); + const uchar r = qRed(v); + const uint p = (b >> 2) | ((g >> 2) << 6) | ((r >> 2) << 12); + data[0] = qBlue(p); + data[1] = qGreen(p); + data[2] = qRed(p); +} + +qrgb666::qrgb666(const qargb6666 &v) +{ + data[0] = v.data[0]; + data[1] = v.data[1]; + data[2] = v.data[2] & 0x03; +} + +qrgb666::operator quint32 () const +{ + const uchar r = (data[2] << 6) | ((data[1] & 0xf0) >> 2) | (data[2] & 0x3); + const uchar g = (data[1] << 4) | ((data[0] & 0xc0) >> 4) | ((data[1] & 0x0f) >> 2); + const uchar b = (data[0] << 2) | ((data[0] & 0x3f) >> 4); + return qRgb(r, g, b); +} + +qrgb666 qrgb666::operator+(qrgb666 v) const +{ + const quint32 x1 = (data[2] << 16) | (data[1] << 8) | data[0]; + const quint32 x2 = (v.data[2] << 16) | (v.data[1] << 8) | v.data[0]; + const quint32 t = x1 + x2; + qrgb666 r; + r.data[0] = t & 0xff; + r.data[1] = (t >> 8) & 0xff; + r.data[2] = (t >> 16) & 0xff; + return r; +} + +qrgb666 qrgb666::byte_mul(quint8 a) const +{ + const quint32 x = (data[2] << 16) | (data[1] << 8) | data[0]; + const quint32 t = ((((x & 0x03f03f) * a) >> 6) & 0x03f03f) | + ((((x & 0x000fc0) * a) >> 6) & 0x000fc0); + + qrgb666 r; + r.data[0] = t & 0xff; + r.data[1] = (t >> 8) & 0xff; + r.data[2] = (t >> 16) & 0xff; + return r; +} + +bool qrgb666::operator==(const qrgb666 &v) const +{ + return (data[0] == v.data[0] && + data[1] == v.data[1] && + data[2] == v.data[2]); +} + +qargb6666::qargb6666(quint32p v) +{ + const quint8 b = qBlue(v) >> 2; + const quint8 g = qGreen(v) >> 2; + const quint8 r = qRed(v) >> 2; + const quint8 a = qAlpha(v) >> 2; + const uint p = (a << 18) | (r << 12) | (g << 6) | b; + data[0] = qBlue(p); + data[1] = qGreen(p); + data[2] = qRed(p); +} + +qargb6666::qargb6666(const qargb6666 &v) +{ + data[0] = v.data[0]; + data[1] = v.data[1]; + data[2] = v.data[2]; +} + +qargb6666::qargb6666(const qrgb666 &v) +{ + data[0] = v.data[0]; + data[1] = v.data[1]; + data[2] = (v.data[2] | 0xfc); +} + +qargb6666::operator quint32 () const +{ + const quint8 r = (data[2] << 6) | ((data[1] & 0xf0) >> 2) | (data[2] & 0x3); + const quint8 g = (data[1] << 4) | ((data[0] & 0xc0) >> 4) | ((data[1] & 0x0f) >> 2); + const quint8 b = (data[0] << 2) | ((data[0] & 0x3f) >> 4); + const quint8 a = (data[2] & 0xfc) | (data[2] >> 6); + return qRgba(r, g, b, a); +} + +qargb6666 qargb6666::operator+(qargb6666 v) const +{ + const quint32 x1 = (data[2] << 16) | (data[1] << 8) | data[0]; + const quint32 x2 = (v.data[2] << 16) | (v.data[1] << 8) | v.data[0]; + const quint32 t = x1 + x2; + qargb6666 r; + r.data[0] = t & 0xff; + r.data[1] = (t >> 8) & 0xff; + r.data[2] = (t >> 16) & 0xff; + return r; +} + +quint8 qargb6666::alpha() const +{ + return (data[2] & 0xfc) | (data[2] >> 6); +} + +inline qargb6666 qargb6666::byte_mul(quint8 a) const +{ + const quint32 x = (data[2] << 16) | (data[1] << 8) | data[0]; + const quint32 t = ((((x & 0x03f03f) * a) >> 6) & 0x03f03f) | + ((((x & 0xfc0fc0) * a) >> 6) & 0xfc0fc0); + + qargb6666 r; + r.data[0] = t & 0xff; + r.data[1] = (t >> 8) & 0xff; + r.data[2] = (t >> 16) & 0xff; + return r; +} + +bool qargb6666::operator==(const qargb6666 &v) const +{ + return data[0] == v.data[0] + && data[1] == v.data[1] + && data[2] == v.data[2]; +} + +quint32 qargb6666::rawValue() const +{ + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +class qrgb888 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return false; } + + inline qrgb888() {} + inline qrgb888(quint32 v); + + inline operator quint32() const; + + inline quint8 alpha() const { return 0xff; } + inline qrgb888 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return a; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 255 - a; } + + inline qrgb888 byte_mul(quint8 a) const; + inline qrgb888 operator+(qrgb888 v) const; + inline bool operator==(qrgb888 v) const; + + inline quint32 rawValue() const; + +private: + uchar data[3]; + +} Q_PACKED; + +qrgb888::qrgb888(quint32 v) +{ + data[0] = qRed(v); + data[1] = qGreen(v); + data[2] = qBlue(v); +} + +qrgb888::operator quint32() const +{ + return qRgb(data[0], data[1], data[2]); +} + +qrgb888 qrgb888::operator+(qrgb888 v) const +{ + qrgb888 t = *this; + t.data[0] += v.data[0]; + t.data[1] += v.data[1]; + t.data[2] += v.data[2]; + return t; +} + +qrgb888 qrgb888::byte_mul(quint8 a) const +{ + quint32 x(*this); + + quint32 t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff00ff) * a; + x = (x + ((x >> 8) & 0xff00ff) + 0x800080); + x &= 0xff00ff00; + x |= t; + return qrgb888(x); +} + +bool qrgb888::operator==(qrgb888 v) const +{ + return (data[0] == v.data[0] && + data[1] == v.data[1] && + data[2] == v.data[2]); +} + +quint32 qrgb888::rawValue() const +{ + return (data[2] << 16) | (data[1] << 8) | data[0]; +} + +template <> +inline qrgb888 qt_colorConvert(quint32 color, qrgb888 dummy) +{ + Q_UNUSED(dummy); + return qrgb888(color); +} + +template <> +inline quint32 qt_colorConvert(qrgb888 color, quint32 dummy) +{ + Q_UNUSED(dummy); + return quint32(color); +} + +#ifdef QT_QWS_DEPTH_8 +template <> +inline quint8 qt_colorConvert(quint32 color, quint8 dummy) +{ + Q_UNUSED(dummy); + + uchar r = ((qRed(color) & 0xf8) + 0x19) / 0x33; + uchar g = ((qGreen(color) &0xf8) + 0x19) / 0x33; + uchar b = ((qBlue(color) &0xf8) + 0x19) / 0x33; + + return r*6*6 + g*6 + b; +} + +template <> +inline quint8 qt_colorConvert(quint16 color, quint8 dummy) +{ + Q_UNUSED(dummy); + + uchar r = (color & 0xf800) >> (11-3); + uchar g = (color & 0x07c0) >> (6-3); + uchar b = (color & 0x001f) << 3; + + uchar tr = (r + 0x19) / 0x33; + uchar tg = (g + 0x19) / 0x33; + uchar tb = (b + 0x19) / 0x33; + + return tr*6*6 + tg*6 + tb; +} + +#endif // QT_QWS_DEPTH_8 + +// hw: endianess?? +class quint24 +{ +public: + inline quint24(quint32 v) + { + data[0] = qBlue(v); + data[1] = qGreen(v); + data[2] = qRed(v); + } + + inline operator quint32 () + { + return qRgb(data[2], data[1], data[0]); + } + + inline bool operator==(const quint24 &v) const + { + return data[0] == v.data[0] + && data[1] == v.data[1] + && data[2] == v.data[2]; + } + +private: + uchar data[3]; +} Q_PACKED; + +template <> +inline quint24 qt_colorConvert(quint32 color, quint24 dummy) +{ + Q_UNUSED(dummy); + return quint24(color); +} + +// hw: endianess?? +class quint18 +{ +public: + inline quint18(quint32 v) + { + uchar b = qBlue(v); + uchar g = qGreen(v); + uchar r = qRed(v); + uint p = (b >> 2) | ((g >> 2) << 6) | ((r >> 2) << 12); + data[0] = qBlue(p); + data[1] = qGreen(p); + data[2] = qRed(p); + } + + inline operator quint32 () + { + const uchar r = (data[2] << 6) | ((data[1] & 0xf0) >> 2) | (data[2] & 0x3); + const uchar g = (data[1] << 4) | ((data[0] & 0xc0) >> 4) | ((data[1] & 0x0f) >> 2); + const uchar b = (data[0] << 2) | ((data[0] & 0x3f) >> 4); + return qRgb(r, g, b); + } + +private: + uchar data[3]; +} Q_PACKED; + +template <> +inline quint18 qt_colorConvert(quint32 color, quint18 dummy) +{ + Q_UNUSED(dummy); + return quint18(color); +} + +class qrgb444; + +class qargb4444 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return true; } + + inline qargb4444() {} + inline qargb4444(quint32 v) { *this = qargb4444(quint32p(v)); } + inline explicit qargb4444(quint32p v); + inline qargb4444(const qrgb444 &v); + + inline operator quint32() const; + inline operator quint8() const; + + inline qargb4444 operator+(qargb4444 v) const; + + inline quint8 alpha() const { return ((data & 0xf000) >> 8) | ((data & 0xf000) >> 12); } + inline qargb4444 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 4; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x10 - alpha(a); } + inline qargb4444 byte_mul(quint8 a) const; + + inline bool operator==(const qargb4444 &v) const { return data == v.data; } + + inline quint16 rawValue() const { return data; } + +private: + friend class qrgb444; + quint16 data; + +} Q_PACKED; + +class qrgb444 +{ +public: + Q_STATIC_INLINE_FUNCTION bool hasAlpha() { return false; } + + inline qrgb444() {} + inline qrgb444(quint32 v); + inline explicit qrgb444(qargb4444 v); + + inline operator quint32() const; + inline operator quint8() const; + + inline qrgb444 operator+(qrgb444 v) const; + inline quint8 alpha() const { return 0xff; } + inline qrgb444 truncedAlpha() { return *this; } + Q_STATIC_INLINE_FUNCTION quint8 alpha(quint8 a) { return (a + 1) >> 4; } + Q_STATIC_INLINE_FUNCTION quint8 ialpha(quint8 a) { return 0x10 - alpha(a); } + inline qrgb444 byte_mul(quint8 a) const; + + inline bool operator==(const qrgb444 &v) const { return data == v.data; } + inline bool operator!=(const qrgb444 &v) const { return data != v.data; } + + inline quint16 rawValue() const { return data; } + +private: + friend class qargb4444; + quint16 data; + +} Q_PACKED; + + +qargb4444::qargb4444(quint32p color) +{ + quint32 v = color; + v &= 0xf0f0f0f0; + const int a = qAlpha(v) << 8; + const int r = qRed(v) << 4; + const int g = qGreen(v); + const int b = qBlue(v) >> 4; + + data = a | r | g | b; +} + +qargb4444::qargb4444(const qrgb444 &v) +{ + data = v.data | 0xf000; +} + +qargb4444::operator quint32() const +{ + const int a = (data & 0xf000); + const int r = (data & 0x0f00); + const int g = (data & 0x00f0); + const int b = (data & 0x000f); + const int ta = (a >> 8) | (a >> 12); + const int tr = (r >> 4) | (r >> 8); + const int tg = g | (g >> 4); + const int tb = (b << 4) | b; + + return qRgba(tr, tg, tb, ta); +} + +qargb4444::operator quint8() const +{ + // hw: optimize! + return qt_colorConvert<quint8, quint32>(operator quint32(), 0); +} + +qargb4444 qargb4444::operator+(qargb4444 v) const +{ + qargb4444 t; + t.data = data + v.data; + return t; +} + +qargb4444 qargb4444::byte_mul(quint8 a) const +{ + quint16 t = (((data & 0xf0f0) * a) >> 4) & 0xf0f0; + t |= (((data & 0x0f0f) * a) >> 4) & 0x0f0f; + + qargb4444 result; + result.data = t; + return result; +} + +qrgb444::qrgb444(quint32 v) +{ + v &= 0xf0f0f0f0; + const int r = qRed(v) << 4; + const int g = qGreen(v); + const int b = qBlue(v) >> 4; + + data = r | g | b; +} + +qrgb444::qrgb444(qargb4444 v) +{ + data = v.data & 0x0fff; +} + +qrgb444::operator quint32() const +{ + const int r = (data & 0x0f00); + const int g = (data & 0x00f0); + const int b = (data & 0x000f); + const int tr = (r >> 4) | (r >> 8); + const int tg = g | (g >> 4); + const int tb = (b << 4) | b; + + return qRgb(tr, tg, tb); +} + +qrgb444::operator quint8() const +{ + // hw: optimize! + return qt_colorConvert<quint8, quint32>(operator quint32(), 0); +} + +qrgb444 qrgb444::operator+(qrgb444 v) const +{ + qrgb444 t; + t.data = data + v.data; + return t; +} + +qrgb444 qrgb444::byte_mul(quint8 a) const +{ + quint16 t = (((data & 0xf0f0) * a) >> 4) & 0xf0f0; + t |= (((data & 0x0f0f) * a) >> 4) & 0x0f0f; + + qrgb444 result; + result.data = t; + return result; +} + +#ifdef QT_QWS_DEPTH_GENERIC + +struct qrgb +{ +public: + static int bpp; + static int len_red; + static int len_green; + static int len_blue; + static int len_alpha; + static int off_red; + static int off_green; + static int off_blue; + static int off_alpha; +} Q_PACKED; + +template <typename SRC> +Q_STATIC_TEMPLATE_FUNCTION inline quint32 qt_convertToRgb(SRC color); + +template <> +inline quint32 qt_convertToRgb(quint32 color) +{ + const int r = qRed(color) >> (8 - qrgb::len_red); + const int g = qGreen(color) >> (8 - qrgb::len_green); + const int b = qBlue(color) >> (8 - qrgb::len_blue); + const int a = qAlpha(color) >> (8 - qrgb::len_alpha); + const quint32 v = (r << qrgb::off_red) + | (g << qrgb::off_green) + | (b << qrgb::off_blue) + | (a << qrgb::off_alpha); + + return v; +} + +template <> +inline quint32 qt_convertToRgb(quint16 color) +{ + return qt_convertToRgb(qt_colorConvert<quint32, quint16>(color, 0)); +} + +class qrgb_generic16 +{ +public: + inline qrgb_generic16(quint32 color) + { + const int r = qRed(color) >> (8 - qrgb::len_red); + const int g = qGreen(color) >> (8 - qrgb::len_green); + const int b = qBlue(color) >> (8 - qrgb::len_blue); + const int a = qAlpha(color) >> (8 - qrgb::len_alpha); + data = (r << qrgb::off_red) + | (g << qrgb::off_green) + | (b << qrgb::off_blue) + | (a << qrgb::off_alpha); + } + + inline operator quint16 () { return data; } + inline quint32 operator<<(int shift) const { return data << shift; } + +private: + quint16 data; +} Q_PACKED; + +template <> +inline qrgb_generic16 qt_colorConvert(quint32 color, qrgb_generic16 dummy) +{ + Q_UNUSED(dummy); + return qrgb_generic16(color); +} + +template <> +inline qrgb_generic16 qt_colorConvert(quint16 color, qrgb_generic16 dummy) +{ + Q_UNUSED(dummy); + return qrgb_generic16(qt_colorConvert<quint32, quint16>(color, 0)); +} + +#endif // QT_QWS_DEPTH_GENERIC + +template <class T> +void qt_memfill(T *dest, T value, int count); + +template<> inline void qt_memfill(quint32 *dest, quint32 color, int count) +{ + extern void (*qt_memfill32)(quint32 *dest, quint32 value, int count); + qt_memfill32(dest, color, count); +} + +template<> inline void qt_memfill(quint16 *dest, quint16 color, int count) +{ + extern void (*qt_memfill16)(quint16 *dest, quint16 value, int count); + qt_memfill16(dest, color, count); +} + +template<> inline void qt_memfill(quint8 *dest, quint8 color, int count) +{ + memset(dest, color, count); +} + +template <class T> +inline void qt_memfill(T *dest, T value, int count) +{ + if (!count) + return; + + int n = (count + 7) / 8; + switch (count & 0x07) + { + case 0: do { *dest++ = value; + case 7: *dest++ = value; + case 6: *dest++ = value; + case 5: *dest++ = value; + case 4: *dest++ = value; + case 3: *dest++ = value; + case 2: *dest++ = value; + case 1: *dest++ = value; + } while (--n > 0); + } +} + +template <class T> +inline void qt_rectfill(T *dest, T value, + int x, int y, int width, int height, int stride) +{ + char *d = reinterpret_cast<char*>(dest + x) + y * stride; + if (uint(stride) == (width * sizeof(T))) { + qt_memfill(reinterpret_cast<T*>(d), value, width * height); + } else { + for (int j = 0; j < height; ++j) { + dest = reinterpret_cast<T*>(d); + qt_memfill(dest, value, width); + d += stride; + } + } +} + +template <class DST, class SRC> +inline void qt_memconvert(DST *dest, const SRC *src, int count) +{ + if (sizeof(DST) == 1) { + while (count) { + int n = 1; + const SRC color = *src++; + const DST dstColor = qt_colorConvert<DST, SRC>(color, 0); + while (--count && (*src == color || dstColor == qt_colorConvert<DST, SRC>(*src, 0))) { + ++n; + ++src; + } + qt_memfill(dest, dstColor, n); + dest += n; + } + } else { + /* Duff's device */ + int n = (count + 7) / 8; + switch (count & 0x07) + { + case 0: do { *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 7: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 6: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 5: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 4: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 3: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 2: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + case 1: *dest++ = qt_colorConvert<DST, SRC>(*src++, 0); + } while (--n > 0); + } + } +} + +#define QT_TRIVIAL_MEMCONVERT_IMPL(T) \ + template <> \ + inline void qt_memconvert(T *dest, const T *src, int count) \ + { \ + memcpy(dest, src, count * sizeof(T)); \ + } +QT_TRIVIAL_MEMCONVERT_IMPL(quint32) +QT_TRIVIAL_MEMCONVERT_IMPL(qrgb888) +QT_TRIVIAL_MEMCONVERT_IMPL(qargb6666) +QT_TRIVIAL_MEMCONVERT_IMPL(qrgb666) +QT_TRIVIAL_MEMCONVERT_IMPL(quint16) +QT_TRIVIAL_MEMCONVERT_IMPL(qrgb565) +QT_TRIVIAL_MEMCONVERT_IMPL(qargb8565) +QT_TRIVIAL_MEMCONVERT_IMPL(qargb8555) +QT_TRIVIAL_MEMCONVERT_IMPL(qrgb555) +QT_TRIVIAL_MEMCONVERT_IMPL(qargb4444) +QT_TRIVIAL_MEMCONVERT_IMPL(qrgb444) +#undef QT_TRIVIAL_MEMCONVERT_IMPL + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +template <> +inline void qt_memconvert(qrgb666 *dest, const quint32 *src, int count) +{ + if (count < 3) { + switch (count) { + case 2: *dest++ = qrgb666(*src++); + case 1: *dest = qrgb666(*src); + } + return; + } + + const int align = (quintptr(dest) & 3); + switch (align) { + case 1: *dest++ = qrgb666(*src++); --count; + case 2: *dest++ = qrgb666(*src++); --count; + case 3: *dest++ = qrgb666(*src++); --count; + } + + quint32 *dest32 = reinterpret_cast<quint32*>(dest); + int sourceCount = count >> 2; + while (sourceCount--) { + dest32[0] = ((src[1] & 0x00000c00) << 20) + | ((src[1] & 0x000000fc) << 22) + | ((src[0] & 0x00fc0000) >> 6) + | ((src[0] & 0x0000fc00) >> 4) + | ((src[0] & 0x000000fc) >> 2); + dest32[1] = ((src[2] & 0x003c0000) << 10) + | ((src[2] & 0x0000fc00) << 12) + | ((src[2] & 0x000000fc) << 14) + | ((src[1] & 0x00fc0000) >> 14) + | ((src[1] & 0x0000f000) >> 12); + dest32[2] = ((src[3] & 0x00fc0000) << 2) + | ((src[3] & 0x0000fc00) << 4) + | ((src[3] & 0x000000fc) << 6) + | ((src[2] & 0x00c00000) >> 22); + dest32 += 3; + src += 4; + } + + dest = reinterpret_cast<qrgb666*>(dest32); + switch (count & 3) { + case 3: *dest++ = qrgb666(*src++); + case 2: *dest++ = qrgb666(*src++); + case 1: *dest = qrgb666(*src); + } +} +#endif // Q_BYTE_ORDER + +template <class T> +inline void qt_rectcopy(T *dest, const T *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + char *d = (char*)(dest + x) + y * dstStride; + const char *s = (char*)(src); + for (int i = 0; i < height; ++i) { + ::memcpy(d, s, width * sizeof(T)); + d += dstStride; + s += srcStride; + } +} + +template <class DST, class SRC> +inline void qt_rectconvert(DST *dest, const SRC *src, + int x, int y, int width, int height, + int dstStride, int srcStride) +{ + char *d = (char*)(dest + x) + y * dstStride; + const char *s = (char*)(src); + for (int i = 0; i < height; ++i) { + qt_memconvert<DST,SRC>((DST*)d, (const SRC*)s, width); + d += dstStride; + s += srcStride; + } +} + +#define QT_RECTCONVERT_TRIVIAL_IMPL(T) \ + template <> \ + inline void qt_rectconvert(T *dest, const T *src, \ + int x, int y, int width, int height, \ + int dstStride, int srcStride) \ + { \ + qt_rectcopy(dest, src, x, y, width, height, dstStride, srcStride); \ + } +QT_RECTCONVERT_TRIVIAL_IMPL(quint32) +QT_RECTCONVERT_TRIVIAL_IMPL(qrgb888) +QT_RECTCONVERT_TRIVIAL_IMPL(qargb6666) +QT_RECTCONVERT_TRIVIAL_IMPL(qrgb666) +QT_RECTCONVERT_TRIVIAL_IMPL(qrgb565) +QT_RECTCONVERT_TRIVIAL_IMPL(qargb8565) +QT_RECTCONVERT_TRIVIAL_IMPL(quint16) +QT_RECTCONVERT_TRIVIAL_IMPL(qargb8555) +QT_RECTCONVERT_TRIVIAL_IMPL(qrgb555) +QT_RECTCONVERT_TRIVIAL_IMPL(qargb4444) +QT_RECTCONVERT_TRIVIAL_IMPL(qrgb444) +#undef QT_RECTCONVERT_TRIVIAL_IMPL + +#ifdef QT_QWS_DEPTH_GENERIC +template <> void qt_rectconvert(qrgb *dest, const quint32 *src, + int x, int y, int width, int height, + int dstStride, int srcStride); + +template <> void qt_rectconvert(qrgb *dest, const quint16 *src, + int x, int y, int width, int height, + int dstStride, int srcStride); +#endif // QT_QWS_DEPTH_GENERIC + +#define QT_MEMFILL_UINT(dest, length, color) \ + qt_memfill<quint32>(dest, color, length); + +#define QT_MEMFILL_USHORT(dest, length, color) \ + qt_memfill<quint16>(dest, color, length); + +#define QT_MEMCPY_REV_UINT(dest, src, length) \ +do { \ + /* Duff's device */ \ + uint *_d = (uint*)(dest) + length; \ + const uint *_s = (uint*)(src) + length; \ + register int n = ((length) + 7) / 8; \ + switch ((length) & 0x07) \ + { \ + case 0: do { *--_d = *--_s; \ + case 7: *--_d = *--_s; \ + case 6: *--_d = *--_s; \ + case 5: *--_d = *--_s; \ + case 4: *--_d = *--_s; \ + case 3: *--_d = *--_s; \ + case 2: *--_d = *--_s; \ + case 1: *--_d = *--_s; \ + } while (--n > 0); \ + } \ +} while (0) + +#define QT_MEMCPY_USHORT(dest, src, length) \ +do { \ + /* Duff's device */ \ + ushort *_d = (ushort*)(dest); \ + const ushort *_s = (ushort*)(src); \ + register int n = ((length) + 7) / 8; \ + switch ((length) & 0x07) \ + { \ + case 0: do { *_d++ = *_s++; \ + case 7: *_d++ = *_s++; \ + case 6: *_d++ = *_s++; \ + case 5: *_d++ = *_s++; \ + case 4: *_d++ = *_s++; \ + case 3: *_d++ = *_s++; \ + case 2: *_d++ = *_s++; \ + case 1: *_d++ = *_s++; \ + } while (--n > 0); \ + } \ +} while (0) + +#if defined(Q_CC_RVCT) +# pragma push +# pragma arm +#endif +Q_STATIC_INLINE_FUNCTION int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; } +#if defined(Q_CC_RVCT) +# pragma pop +#endif + +inline ushort qConvertRgb32To16(uint c) +{ + return (((c) >> 3) & 0x001f) + | (((c) >> 5) & 0x07e0) + | (((c) >> 8) & 0xf800); +} + +#if defined(Q_WS_QWS) || (QT_VERSION >= 0x040400) +inline quint32 qConvertRgb32To16x2(quint64 c) +{ + c = (((c) >> 3) & Q_UINT64_C(0x001f0000001f)) + | (((c) >> 5) & Q_UINT64_C(0x07e0000007e0)) + | (((c) >> 8) & Q_UINT64_C(0xf8000000f800)); + return c | (c >> 16); +} +#endif + +inline QRgb qConvertRgb16To32(uint c) +{ + return 0xff000000 + | ((((c) << 3) & 0xf8) | (((c) >> 2) & 0x7)) + | ((((c) << 5) & 0xfc00) | (((c) >> 1) & 0x300)) + | ((((c) << 8) & 0xf80000) | (((c) << 3) & 0x70000)); +} + +inline int qRed565(quint16 rgb) { + const int r = (rgb & 0xf800); + return (r >> 8) | (r >> 13); +} + +inline int qGreen565(quint16 rgb) { + const int g = (rgb & 0x07e0); + return (g >> 3) | (g >> 9); +} + +inline int qBlue565(quint16 rgb) { + const int b = (rgb & 0x001f); + return (b << 3) | (b >> 2); +} + +const uint qt_bayer_matrix[16][16] = { + { 0x1, 0xc0, 0x30, 0xf0, 0xc, 0xcc, 0x3c, 0xfc, + 0x3, 0xc3, 0x33, 0xf3, 0xf, 0xcf, 0x3f, 0xff}, + { 0x80, 0x40, 0xb0, 0x70, 0x8c, 0x4c, 0xbc, 0x7c, + 0x83, 0x43, 0xb3, 0x73, 0x8f, 0x4f, 0xbf, 0x7f}, + { 0x20, 0xe0, 0x10, 0xd0, 0x2c, 0xec, 0x1c, 0xdc, + 0x23, 0xe3, 0x13, 0xd3, 0x2f, 0xef, 0x1f, 0xdf}, + { 0xa0, 0x60, 0x90, 0x50, 0xac, 0x6c, 0x9c, 0x5c, + 0xa3, 0x63, 0x93, 0x53, 0xaf, 0x6f, 0x9f, 0x5f}, + { 0x8, 0xc8, 0x38, 0xf8, 0x4, 0xc4, 0x34, 0xf4, + 0xb, 0xcb, 0x3b, 0xfb, 0x7, 0xc7, 0x37, 0xf7}, + { 0x88, 0x48, 0xb8, 0x78, 0x84, 0x44, 0xb4, 0x74, + 0x8b, 0x4b, 0xbb, 0x7b, 0x87, 0x47, 0xb7, 0x77}, + { 0x28, 0xe8, 0x18, 0xd8, 0x24, 0xe4, 0x14, 0xd4, + 0x2b, 0xeb, 0x1b, 0xdb, 0x27, 0xe7, 0x17, 0xd7}, + { 0xa8, 0x68, 0x98, 0x58, 0xa4, 0x64, 0x94, 0x54, + 0xab, 0x6b, 0x9b, 0x5b, 0xa7, 0x67, 0x97, 0x57}, + { 0x2, 0xc2, 0x32, 0xf2, 0xe, 0xce, 0x3e, 0xfe, + 0x1, 0xc1, 0x31, 0xf1, 0xd, 0xcd, 0x3d, 0xfd}, + { 0x82, 0x42, 0xb2, 0x72, 0x8e, 0x4e, 0xbe, 0x7e, + 0x81, 0x41, 0xb1, 0x71, 0x8d, 0x4d, 0xbd, 0x7d}, + { 0x22, 0xe2, 0x12, 0xd2, 0x2e, 0xee, 0x1e, 0xde, + 0x21, 0xe1, 0x11, 0xd1, 0x2d, 0xed, 0x1d, 0xdd}, + { 0xa2, 0x62, 0x92, 0x52, 0xae, 0x6e, 0x9e, 0x5e, + 0xa1, 0x61, 0x91, 0x51, 0xad, 0x6d, 0x9d, 0x5d}, + { 0xa, 0xca, 0x3a, 0xfa, 0x6, 0xc6, 0x36, 0xf6, + 0x9, 0xc9, 0x39, 0xf9, 0x5, 0xc5, 0x35, 0xf5}, + { 0x8a, 0x4a, 0xba, 0x7a, 0x86, 0x46, 0xb6, 0x76, + 0x89, 0x49, 0xb9, 0x79, 0x85, 0x45, 0xb5, 0x75}, + { 0x2a, 0xea, 0x1a, 0xda, 0x26, 0xe6, 0x16, 0xd6, + 0x29, 0xe9, 0x19, 0xd9, 0x25, 0xe5, 0x15, 0xd5}, + { 0xaa, 0x6a, 0x9a, 0x5a, 0xa6, 0x66, 0x96, 0x56, + 0xa9, 0x69, 0x99, 0x59, 0xa5, 0x65, 0x95, 0x55} +}; + +#define ARGB_COMBINE_ALPHA(argb, alpha) \ + ((((argb >> 24) * alpha) >> 8) << 24) | (argb & 0x00ffffff) + + +#if QT_POINTER_SIZE == 8 // 64-bit versions +#define AMIX(mask) (qMin(((qint64(s)&mask) + (qint64(d)&mask)), qint64(mask))) +#define MIX(mask) (qMin(((qint64(s)&mask) + (qint64(d)&mask)), qint64(mask))) +#else // 32 bits +// The mask for alpha can overflow over 32 bits +#define AMIX(mask) quint32(qMin(((qint64(s)&mask) + (qint64(d)&mask)), qint64(mask))) +#define MIX(mask) (qMin(((quint32(s)&mask) + (quint32(d)&mask)), quint32(mask))) +#endif + +inline int comp_func_Plus_one_pixel_const_alpha(uint d, const uint s, const uint const_alpha, const uint one_minus_const_alpha) +{ + const int result = (AMIX(AMASK) | MIX(RMASK) | MIX(GMASK) | MIX(BMASK)); + return INTERPOLATE_PIXEL_255(result, const_alpha, d, one_minus_const_alpha); +} + +inline int comp_func_Plus_one_pixel(uint d, const uint s) +{ + const int result = (AMIX(AMASK) | MIX(RMASK) | MIX(GMASK) | MIX(BMASK)); + return result; +} + +#undef MIX +#undef AMIX + +// prototypes of all the composition functions +void QT_FASTCALL comp_func_SourceOver(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_DestinationOver(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Clear(uint *dest, const uint *, int length, uint const_alpha); +void QT_FASTCALL comp_func_Source(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Destination(uint *, const uint *, int, uint); +void QT_FASTCALL comp_func_SourceIn(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_DestinationIn(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_SourceOut(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_DestinationOut(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_SourceAtop(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_DestinationAtop(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_XOR(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Plus(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Multiply(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Screen(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Overlay(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Darken(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Lighten(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_ColorDodge(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_ColorBurn(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_HardLight(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_SoftLight(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Difference(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL comp_func_Exclusion(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_SourceOrDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_SourceAndDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_SourceXorDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_NotSourceAndNotDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_NotSourceOrNotDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_NotSourceXorDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_NotSource(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_NotSourceAndDestination(uint *dest, const uint *src, int length, uint const_alpha); +void QT_FASTCALL rasterop_SourceAndNotDestination(uint *dest, const uint *src, int length, uint const_alpha); + +// prototypes of all the solid composition functions +void QT_FASTCALL comp_func_solid_SourceOver(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_DestinationOver(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Clear(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Source(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Destination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_SourceIn(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_DestinationIn(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_SourceOut(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_DestinationOut(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_SourceAtop(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_DestinationAtop(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_XOR(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Plus(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Multiply(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Screen(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Overlay(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Darken(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Lighten(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_ColorDodge(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_ColorBurn(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_HardLight(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_SoftLight(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Difference(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL comp_func_solid_Exclusion(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_SourceOrDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_SourceAndDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_SourceXorDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_NotSourceAndNotDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_NotSourceOrNotDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_NotSourceXorDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_NotSource(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_NotSourceAndDestination(uint *dest, int length, uint color, uint const_alpha); +void QT_FASTCALL rasterop_solid_SourceAndNotDestination(uint *dest, int length, uint color, uint const_alpha); + +QT_END_NAMESPACE + +#endif // QDRAWHELPER_P_H diff --git a/src/gui/painting/qdrawhelper_sse.cpp b/src/gui/painting/qdrawhelper_sse.cpp new file mode 100644 index 0000000000..7f7aee650a --- /dev/null +++ b/src/gui/painting/qdrawhelper_sse.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_p.h> + +#ifdef QT_HAVE_SSE + +#include <private/qdrawhelper_sse_p.h> + +QT_BEGIN_NAMESPACE + +CompositionFunctionSolid qt_functionForModeSolid_SSE[numCompositionFunctions] = { + comp_func_solid_SourceOver<QSSEIntrinsics>, + comp_func_solid_DestinationOver<QSSEIntrinsics>, + comp_func_solid_Clear<QSSEIntrinsics>, + comp_func_solid_Source<QSSEIntrinsics>, + 0, + comp_func_solid_SourceIn<QSSEIntrinsics>, + comp_func_solid_DestinationIn<QSSEIntrinsics>, + comp_func_solid_SourceOut<QSSEIntrinsics>, + comp_func_solid_DestinationOut<QSSEIntrinsics>, + comp_func_solid_SourceAtop<QSSEIntrinsics>, + comp_func_solid_DestinationAtop<QSSEIntrinsics>, + comp_func_solid_XOR<QSSEIntrinsics>, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // svg 1.2 modes + rasterop_solid_SourceOrDestination<QMMXIntrinsics>, + rasterop_solid_SourceAndDestination<QMMXIntrinsics>, + rasterop_solid_SourceXorDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceAndNotDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceOrNotDestination<QMMXIntrinsics>, + rasterop_solid_NotSourceXorDestination<QMMXIntrinsics>, + rasterop_solid_NotSource<QMMXIntrinsics>, + rasterop_solid_NotSourceAndDestination<QMMXIntrinsics>, + rasterop_solid_SourceAndNotDestination<QMMXIntrinsics> +}; + +CompositionFunction qt_functionForMode_SSE[numCompositionFunctions] = { + comp_func_SourceOver<QSSEIntrinsics>, + comp_func_DestinationOver<QSSEIntrinsics>, + comp_func_Clear<QSSEIntrinsics>, + comp_func_Source<QSSEIntrinsics>, + comp_func_Destination, + comp_func_SourceIn<QSSEIntrinsics>, + comp_func_DestinationIn<QSSEIntrinsics>, + comp_func_SourceOut<QSSEIntrinsics>, + comp_func_DestinationOut<QSSEIntrinsics>, + comp_func_SourceAtop<QSSEIntrinsics>, + comp_func_DestinationAtop<QSSEIntrinsics>, + comp_func_XOR<QSSEIntrinsics>, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_blend_color_argb_sse(int count, const QSpan *spans, void *userData) +{ + qt_blend_color_argb_x86<QSSEIntrinsics>(count, spans, userData, + (CompositionFunctionSolid*)qt_functionForModeSolid_SSE); +} + +void qt_memfill32_sse(quint32 *dest, quint32 value, int count) +{ + return qt_memfill32_sse_template<QSSEIntrinsics>(dest, value, count); +} + +void qt_bitmapblit16_sse(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, + int width, int height, int stride) +{ + return qt_bitmapblit16_sse_template<QSSEIntrinsics>(rasterBuffer, x,y, + color, src, width, + height, stride); +} + +void qt_blend_argb32_on_argb32_sse(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + + uint ca = const_alpha - 1; + + for (int y=0; y<h; ++y) { + comp_func_SourceOver<QSSEIntrinsics>(dst, src, w, ca); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } +} + +void qt_blend_rgb32_on_rgb32_sse(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const uint *src = (const uint *) srcPixels; + uint *dst = (uint *) destPixels; + + uint ca = const_alpha - 1; + + for (int y=0; y<h; ++y) { + comp_func_Source<QSSEIntrinsics>(dst, src, w, ca); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSE diff --git a/src/gui/painting/qdrawhelper_sse2.cpp b/src/gui/painting/qdrawhelper_sse2.cpp new file mode 100644 index 0000000000..aad6bc9254 --- /dev/null +++ b/src/gui/painting/qdrawhelper_sse2.cpp @@ -0,0 +1,496 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_x86_p.h> + +#ifdef QT_HAVE_SSE2 + +#include <private/qdrawingprimitive_sse2_p.h> +#include <private/qpaintengine_raster_p.h> + +QT_BEGIN_NAMESPACE + +void qt_blend_argb32_on_argb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const quint32 *src = (const quint32 *) srcPixels; + quint32 *dst = (quint32 *) destPixels; + if (const_alpha == 256) { + const __m128i alphaMask = _mm_set1_epi32(0xff000000); + const __m128i nullVector = _mm_set1_epi32(0); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i one = _mm_set1_epi16(0xff); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + for (int y = 0; y < h; ++y) { + BLEND_SOURCE_OVER_ARGB32_SSE2(dst, src, w, nullVector, half, one, colorMask, alphaMask); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else if (const_alpha != 0) { + // dest = (s + d * sia) * ca + d * cia + // = s * ca + d * (sia * ca + cia) + // = s * ca + d * (1 - sa*ca) + const_alpha = (const_alpha * 255) >> 8; + const __m128i nullVector = _mm_set1_epi32(0); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i one = _mm_set1_epi16(0xff); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + for (int y = 0; y < h; ++y) { + BLEND_SOURCE_OVER_ARGB32_WITH_CONST_ALPHA_SSE2(dst, src, w, nullVector, half, one, colorMask, constAlphaVector) + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +// qblendfunctions.cpp +void qt_blend_rgb32_on_rgb32(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +void qt_blend_rgb32_on_rgb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const quint32 *src = (const quint32 *) srcPixels; + quint32 *dst = (quint32 *) destPixels; + if (const_alpha != 256) { + if (const_alpha != 0) { + const __m128i nullVector = _mm_set1_epi32(0); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + + const_alpha = (const_alpha * 255) >> 8; + int one_minus_const_alpha = 255 - const_alpha; + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + const __m128i oneMinusConstAlpha = _mm_set1_epi16(one_minus_const_alpha); + for (int y = 0; y < h; ++y) { + int x = 0; + + // First, align dest to 16 bytes: + ALIGNMENT_PROLOGUE_16BYTES(dst, x, w) { + quint32 s = src[x]; + s = BYTE_MUL(s, const_alpha); + dst[x] = INTERPOLATE_PIXEL_255(src[x], const_alpha, dst[x], one_minus_const_alpha); + } + + for (; x < w-3; x += 4) { + __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVector, nullVector)) != 0xffff) { + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); + __m128i result; + INTERPOLATE_PIXEL_255_SSE2(result, srcVector, dstVector, constAlphaVector, oneMinusConstAlpha, colorMask, half); + _mm_store_si128((__m128i *)&dst[x], result); + } + } + for (; x<w; ++x) { + quint32 s = src[x]; + s = BYTE_MUL(s, const_alpha); + dst[x] = INTERPOLATE_PIXEL_255(src[x], const_alpha, dst[x], one_minus_const_alpha); + } + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } + } else { + qt_blend_rgb32_on_rgb32(destPixels, dbpl, srcPixels, sbpl, w, h, const_alpha); + } +} + +void QT_FASTCALL comp_func_SourceOver_sse2(uint *destPixels, const uint *srcPixels, int length, uint const_alpha) +{ + Q_ASSERT(const_alpha < 256); + + const quint32 *src = (const quint32 *) srcPixels; + quint32 *dst = (quint32 *) destPixels; + + const __m128i nullVector = _mm_set1_epi32(0); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i one = _mm_set1_epi16(0xff); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + if (const_alpha == 255) { + const __m128i alphaMask = _mm_set1_epi32(0xff000000); + BLEND_SOURCE_OVER_ARGB32_SSE2(dst, src, length, nullVector, half, one, colorMask, alphaMask); + } else { + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + BLEND_SOURCE_OVER_ARGB32_WITH_CONST_ALPHA_SSE2(dst, src, length, nullVector, half, one, colorMask, constAlphaVector); + } +} + +void QT_FASTCALL comp_func_Plus_sse2(uint *dst, const uint *src, int length, uint const_alpha) +{ + int x = 0; + + if (const_alpha == 255) { + // 1) Prologue: align destination on 16 bytes + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) + dst[x] = comp_func_Plus_one_pixel(dst[x], src[x]); + + // 2) composition with SSE2 + for (; x < length - 3; x += 4) { + const __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); + + const __m128i result = _mm_adds_epu8(srcVector, dstVector); + _mm_store_si128((__m128i *)&dst[x], result); + } + + // 3) Epilogue: + for (; x < length; ++x) + dst[x] = comp_func_Plus_one_pixel(dst[x], src[x]); + } else { + const int one_minus_const_alpha = 255 - const_alpha; + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + const __m128i oneMinusConstAlpha = _mm_set1_epi16(one_minus_const_alpha); + + // 1) Prologue: align destination on 16 bytes + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) + dst[x] = comp_func_Plus_one_pixel_const_alpha(dst[x], src[x], const_alpha, one_minus_const_alpha); + + const __m128i half = _mm_set1_epi16(0x80); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + // 2) composition with SSE2 + for (; x < length - 3; x += 4) { + const __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); + + __m128i result = _mm_adds_epu8(srcVector, dstVector); + INTERPOLATE_PIXEL_255_SSE2(result, result, dstVector, constAlphaVector, oneMinusConstAlpha, colorMask, half) + _mm_store_si128((__m128i *)&dst[x], result); + } + + // 3) Epilogue: + for (; x < length; ++x) + dst[x] = comp_func_Plus_one_pixel_const_alpha(dst[x], src[x], const_alpha, one_minus_const_alpha); + } +} + +void QT_FASTCALL comp_func_Source_sse2(uint *dst, const uint *src, int length, uint const_alpha) +{ + if (const_alpha == 255) { + ::memcpy(dst, src, length * sizeof(uint)); + } else { + const int ialpha = 255 - const_alpha; + + int x = 0; + + // 1) prologue, align on 16 bytes + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) + dst[x] = INTERPOLATE_PIXEL_255(src[x], const_alpha, dst[x], ialpha); + + // 2) interpolate pixels with SSE2 + const __m128i half = _mm_set1_epi16(0x80); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + const __m128i oneMinusConstAlpha = _mm_set1_epi16(ialpha); + for (; x < length - 3; x += 4) { + const __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); + __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); + INTERPOLATE_PIXEL_255_SSE2(dstVector, srcVector, dstVector, constAlphaVector, oneMinusConstAlpha, colorMask, half) + _mm_store_si128((__m128i *)&dst[x], dstVector); + } + + // 3) Epilogue + for (; x < length; ++x) + dst[x] = INTERPOLATE_PIXEL_255(src[x], const_alpha, dst[x], ialpha); + } +} + +void qt_memfill32_sse2(quint32 *dest, quint32 value, int count) +{ + if (count < 7) { + switch (count) { + case 6: *dest++ = value; + case 5: *dest++ = value; + case 4: *dest++ = value; + case 3: *dest++ = value; + case 2: *dest++ = value; + case 1: *dest = value; + } + return; + }; + + const int align = (quintptr)(dest) & 0xf; + switch (align) { + case 4: *dest++ = value; --count; + case 8: *dest++ = value; --count; + case 12: *dest++ = value; --count; + } + + int count128 = count / 4; + __m128i *dst128 = reinterpret_cast<__m128i*>(dest); + const __m128i value128 = _mm_set_epi32(value, value, value, value); + + int n = (count128 + 3) / 4; + switch (count128 & 0x3) { + case 0: do { _mm_stream_si128(dst128++, value128); + case 3: _mm_stream_si128(dst128++, value128); + case 2: _mm_stream_si128(dst128++, value128); + case 1: _mm_stream_si128(dst128++, value128); + } while (--n > 0); + } + + const int rest = count & 0x3; + if (rest) { + switch (rest) { + case 3: dest[count - 3] = value; + case 2: dest[count - 2] = value; + case 1: dest[count - 1] = value; + } + } +} + +void QT_FASTCALL comp_func_solid_SourceOver_sse2(uint *destPixels, int length, uint color, uint const_alpha) +{ + if ((const_alpha & qAlpha(color)) == 255) { + qt_memfill32_sse2(destPixels, color, length); + } else { + if (const_alpha != 255) + color = BYTE_MUL(color, const_alpha); + + const quint32 minusAlphaOfColor = qAlpha(~color); + int x = 0; + + quint32 *dst = (quint32 *) destPixels; + const __m128i colorVector = _mm_set1_epi32(color); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i minusAlphaOfColorVector = _mm_set1_epi16(minusAlphaOfColor); + + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) + destPixels[x] = color + BYTE_MUL(destPixels[x], minusAlphaOfColor); + + for (; x < length-3; x += 4) { + __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); + BYTE_MUL_SSE2(dstVector, dstVector, minusAlphaOfColorVector, colorMask, half); + dstVector = _mm_add_epi8(colorVector, dstVector); + _mm_store_si128((__m128i *)&dst[x], dstVector); + } + for (;x < length; ++x) + destPixels[x] = color + BYTE_MUL(destPixels[x], minusAlphaOfColor); + } +} + +CompositionFunctionSolid qt_functionForModeSolid_onlySSE2[numCompositionFunctions] = { + comp_func_solid_SourceOver_sse2, + comp_func_solid_DestinationOver, + comp_func_solid_Clear, + comp_func_solid_Source, + comp_func_solid_Destination, + comp_func_solid_SourceIn, + comp_func_solid_DestinationIn, + comp_func_solid_SourceOut, + comp_func_solid_DestinationOut, + comp_func_solid_SourceAtop, + comp_func_solid_DestinationAtop, + comp_func_solid_XOR, + comp_func_solid_Plus, + comp_func_solid_Multiply, + comp_func_solid_Screen, + comp_func_solid_Overlay, + comp_func_solid_Darken, + comp_func_solid_Lighten, + comp_func_solid_ColorDodge, + comp_func_solid_ColorBurn, + comp_func_solid_HardLight, + comp_func_solid_SoftLight, + comp_func_solid_Difference, + comp_func_solid_Exclusion, + rasterop_solid_SourceOrDestination, + rasterop_solid_SourceAndDestination, + rasterop_solid_SourceXorDestination, + rasterop_solid_NotSourceAndNotDestination, + rasterop_solid_NotSourceOrNotDestination, + rasterop_solid_NotSourceXorDestination, + rasterop_solid_NotSource, + rasterop_solid_NotSourceAndDestination, + rasterop_solid_SourceAndNotDestination +}; + +CompositionFunction qt_functionForMode_onlySSE2[numCompositionFunctions] = { + comp_func_SourceOver_sse2, + comp_func_DestinationOver, + comp_func_Clear, + comp_func_Source_sse2, + comp_func_Destination, + comp_func_SourceIn, + comp_func_DestinationIn, + comp_func_SourceOut, + comp_func_DestinationOut, + comp_func_SourceAtop, + comp_func_DestinationAtop, + comp_func_XOR, + comp_func_Plus_sse2, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_memfill16_sse2(quint16 *dest, quint16 value, int count) +{ + if (count < 3) { + switch (count) { + case 2: *dest++ = value; + case 1: *dest = value; + } + return; + } + + const int align = (quintptr)(dest) & 0x3; + switch (align) { + case 2: *dest++ = value; --count; + } + + const quint32 value32 = (value << 16) | value; + qt_memfill32_sse2(reinterpret_cast<quint32*>(dest), value32, count / 2); + + if (count & 0x1) + dest[count - 1] = value; +} + +void qt_bitmapblit32_sse2(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, int stride) +{ + quint32 *dest = reinterpret_cast<quint32*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint32); + + const __m128i c128 = _mm_set1_epi32(color); + const __m128i maskmask1 = _mm_set_epi32(0x10101010, 0x20202020, + 0x40404040, 0x80808080); + const __m128i maskadd1 = _mm_set_epi32(0x70707070, 0x60606060, + 0x40404040, 0x00000000); + + if (width > 4) { + const __m128i maskmask2 = _mm_set_epi32(0x01010101, 0x02020202, + 0x04040404, 0x08080808); + const __m128i maskadd2 = _mm_set_epi32(0x7f7f7f7f, 0x7e7e7e7e, + 0x7c7c7c7c, 0x78787878); + while (height--) { + for (int x = 0; x < width; x += 8) { + const quint8 s = src[x >> 3]; + if (!s) + continue; + __m128i mask1 = _mm_set1_epi8(s); + __m128i mask2 = mask1; + + mask1 = _mm_and_si128(mask1, maskmask1); + mask1 = _mm_add_epi8(mask1, maskadd1); + _mm_maskmoveu_si128(c128, mask1, (char*)(dest + x)); + mask2 = _mm_and_si128(mask2, maskmask2); + mask2 = _mm_add_epi8(mask2, maskadd2); + _mm_maskmoveu_si128(c128, mask2, (char*)(dest + x + 4)); + } + dest += destStride; + src += stride; + } + } else { + while (height--) { + const quint8 s = *src; + if (s) { + __m128i mask1 = _mm_set1_epi8(s); + mask1 = _mm_and_si128(mask1, maskmask1); + mask1 = _mm_add_epi8(mask1, maskadd1); + _mm_maskmoveu_si128(c128, mask1, (char*)(dest)); + } + dest += destStride; + src += stride; + } + } +} + +void qt_bitmapblit16_sse2(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, int stride) +{ + const quint16 c = qt_colorConvert<quint16, quint32>(color, 0); + quint16 *dest = reinterpret_cast<quint16*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint16); + + const __m128i c128 = _mm_set1_epi16(c); +#if defined(Q_CC_MSVC) +# pragma warning(disable: 4309) // truncation of constant value +#endif + const __m128i maskmask = _mm_set_epi16(0x0101, 0x0202, 0x0404, 0x0808, + 0x1010, 0x2020, 0x4040, 0x8080); + const __m128i maskadd = _mm_set_epi16(0x7f7f, 0x7e7e, 0x7c7c, 0x7878, + 0x7070, 0x6060, 0x4040, 0x0000); + + while (height--) { + for (int x = 0; x < width; x += 8) { + const quint8 s = src[x >> 3]; + if (!s) + continue; + __m128i mask = _mm_set1_epi8(s); + mask = _mm_and_si128(mask, maskmask); + mask = _mm_add_epi8(mask, maskadd); + _mm_maskmoveu_si128(c128, mask, (char*)(dest + x)); + } + dest += destStride; + src += stride; + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSE2 diff --git a/src/gui/painting/qdrawhelper_sse3dnow.cpp b/src/gui/painting/qdrawhelper_sse3dnow.cpp new file mode 100644 index 0000000000..fd351ed2bf --- /dev/null +++ b/src/gui/painting/qdrawhelper_sse3dnow.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_x86_p.h> + +#if defined(QT_HAVE_3DNOW) && defined(QT_HAVE_SSE) + +#include <private/qdrawhelper_sse_p.h> +#include <mm3dnow.h> + +QT_BEGIN_NAMESPACE + +struct QSSE3DNOWIntrinsics : public QSSEIntrinsics +{ + static inline void end() { + _m_femms(); + } +}; + +CompositionFunctionSolid qt_functionForModeSolid_SSE3DNOW[numCompositionFunctions] = { + comp_func_solid_SourceOver<QSSE3DNOWIntrinsics>, + comp_func_solid_DestinationOver<QSSE3DNOWIntrinsics>, + comp_func_solid_Clear<QSSE3DNOWIntrinsics>, + comp_func_solid_Source<QSSE3DNOWIntrinsics>, + 0, + comp_func_solid_SourceIn<QSSE3DNOWIntrinsics>, + comp_func_solid_DestinationIn<QSSE3DNOWIntrinsics>, + comp_func_solid_SourceOut<QSSE3DNOWIntrinsics>, + comp_func_solid_DestinationOut<QSSE3DNOWIntrinsics>, + comp_func_solid_SourceAtop<QSSE3DNOWIntrinsics>, + comp_func_solid_DestinationAtop<QSSE3DNOWIntrinsics>, + comp_func_solid_XOR<QSSE3DNOWIntrinsics>, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // svg 1.2 modes + rasterop_solid_SourceOrDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_SourceAndDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_SourceXorDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_NotSourceAndNotDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_NotSourceOrNotDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_NotSourceXorDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_NotSource<QSSE3DNOWIntrinsics>, + rasterop_solid_NotSourceAndDestination<QSSE3DNOWIntrinsics>, + rasterop_solid_SourceAndNotDestination<QSSE3DNOWIntrinsics> +}; + +CompositionFunction qt_functionForMode_SSE3DNOW[numCompositionFunctions] = { + comp_func_SourceOver<QSSE3DNOWIntrinsics>, + comp_func_DestinationOver<QSSE3DNOWIntrinsics>, + comp_func_Clear<QSSE3DNOWIntrinsics>, + comp_func_Source<QSSE3DNOWIntrinsics>, + comp_func_Destination, + comp_func_SourceIn<QSSE3DNOWIntrinsics>, + comp_func_DestinationIn<QSSE3DNOWIntrinsics>, + comp_func_SourceOut<QSSE3DNOWIntrinsics>, + comp_func_DestinationOut<QSSE3DNOWIntrinsics>, + comp_func_SourceAtop<QSSE3DNOWIntrinsics>, + comp_func_DestinationAtop<QSSE3DNOWIntrinsics>, + comp_func_XOR<QSSE3DNOWIntrinsics>, + comp_func_Plus, + comp_func_Multiply, + comp_func_Screen, + comp_func_Overlay, + comp_func_Darken, + comp_func_Lighten, + comp_func_ColorDodge, + comp_func_ColorBurn, + comp_func_HardLight, + comp_func_SoftLight, + comp_func_Difference, + comp_func_Exclusion, + rasterop_SourceOrDestination, + rasterop_SourceAndDestination, + rasterop_SourceXorDestination, + rasterop_NotSourceAndNotDestination, + rasterop_NotSourceOrNotDestination, + rasterop_NotSourceXorDestination, + rasterop_NotSource, + rasterop_NotSourceAndDestination, + rasterop_SourceAndNotDestination +}; + +void qt_blend_color_argb_sse3dnow(int count, const QSpan *spans, void *userData) +{ + qt_blend_color_argb_x86<QSSE3DNOWIntrinsics>(count, spans, userData, + (CompositionFunctionSolid*)qt_functionForModeSolid_SSE3DNOW); +} + +void qt_memfill32_sse3dnow(quint32 *dest, quint32 value, int count) +{ + return qt_memfill32_sse_template<QSSE3DNOWIntrinsics>(dest, value, count); +} + + +void qt_bitmapblit16_sse3dnow(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, + int width, int height, int stride) +{ + return qt_bitmapblit16_sse_template<QSSE3DNOWIntrinsics>(rasterBuffer, x,y, + color, src, width, + height, stride); +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_3DNOW && QT_HAVE_SSE diff --git a/src/gui/painting/qdrawhelper_sse_p.h b/src/gui/painting/qdrawhelper_sse_p.h new file mode 100644 index 0000000000..fff1e07152 --- /dev/null +++ b/src/gui/painting/qdrawhelper_sse_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_SSE_P_H +#define QDRAWHELPER_SSE_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/qdrawhelper_mmx_p.h> + +#ifdef QT_HAVE_SSE + +#ifdef QT_LINUXBASE +// this is an evil hack - the posix_memalign declaration in LSB +// is wrong - see http://bugs.linuxbase.org/show_bug.cgi?id=2431 +# define posix_memalign _lsb_hack_posix_memalign +# include <xmmintrin.h> +# undef posix_memalign +#else +# include <xmmintrin.h> +#endif + +QT_BEGIN_NAMESPACE + +#ifndef _MM_SHUFFLE +#define _MM_SHUFFLE(fp3,fp2,fp1,fp0) \ + (((fp3) << 6) | ((fp2) << 4) | ((fp1) << 2) | (fp0)) +#endif + +struct QSSEIntrinsics : public QMMXIntrinsics +{ + static inline m64 alpha(m64 x) { + return _mm_shuffle_pi16 (x, _MM_SHUFFLE(3, 3, 3, 3)); + } + + static inline m64 _load_alpha(uint x, const m64 &mmx_0x0000) { + m64 t = _mm_unpacklo_pi8(_mm_cvtsi32_si64(x), mmx_0x0000); + return _mm_shuffle_pi16 (t, _MM_SHUFFLE(0, 0, 0, 0)); + } +}; + +template <class MM> +inline void qt_memfill32_sse_template(quint32 *dest, quint32 value, int count) +{ + if (count < 7) { + switch (count) { + case 6: *dest++ = value; + case 5: *dest++ = value; + case 4: *dest++ = value; + case 3: *dest++ = value; + case 2: *dest++ = value; + case 1: *dest = value; + } + return; + }; + + __m64 *dst64 = reinterpret_cast<__m64*>(dest); + const __m64 value64 = _mm_set_pi32(value, value); + int count64 = count / 2; + + int n = (count64 + 3) / 4; + switch (count64 & 0x3) { + case 0: do { _mm_stream_pi(dst64++, value64); + case 3: _mm_stream_pi(dst64++, value64); + case 2: _mm_stream_pi(dst64++, value64); + case 1: _mm_stream_pi(dst64++, value64); + } while (--n > 0); + } + + if (count & 0x1) + dest[count - 1] = value; + + MM::end(); +} + +template <class MM> +inline void qt_bitmapblit16_sse_template(QRasterBuffer *rasterBuffer, + int x, int y, + quint32 color, + const uchar *src, + int width, int height, int stride) +{ + const quint16 c = qt_colorConvert<quint16, quint32>(color, 0); + quint16 *dest = reinterpret_cast<quint16*>(rasterBuffer->scanLine(y)) + x; + const int destStride = rasterBuffer->bytesPerLine() / sizeof(quint16); + + const __m64 c64 = _mm_set1_pi16(c); +#ifdef Q_CC_MSVC +# pragma warning(disable: 4309) // truncation of constant value +#endif + const __m64 maskmask1 = _mm_set_pi16(0x1010, 0x2020, 0x4040, 0x8080); + const __m64 maskadd1 = _mm_set_pi16(0x7070, 0x6060, 0x4040, 0x0000); + + if (width > 4) { + const __m64 maskmask2 = _mm_set_pi16(0x0101, 0x0202, 0x0404, 0x0808); + const __m64 maskadd2 = _mm_set_pi16(0x7f7f, 0x7e7e, 0x7c7c, 0x7878); + + while (height--) { + for (int x = 0; x < width; x += 8) { + const quint8 s = src[x >> 3]; + if (!s) + continue; + __m64 mask1 = _mm_set1_pi8(s); + __m64 mask2 = mask1; + mask1 = _m_pand(mask1, maskmask1); + mask1 = _mm_add_pi16(mask1, maskadd1); + _mm_maskmove_si64(c64, mask1, (char*)(dest + x)); + mask2 = _m_pand(mask2, maskmask2); + mask2 = _mm_add_pi16(mask2, maskadd2); + _mm_maskmove_si64(c64, mask2, (char*)(dest + x + 4)); + } + dest += destStride; + src += stride; + } + } else { + while (height--) { + const quint8 s = *src; + if (s) { + __m64 mask1 = _mm_set1_pi8(s); + mask1 = _m_pand(mask1, maskmask1); + mask1 = _mm_add_pi16(mask1, maskadd1); + _mm_maskmove_si64(c64, mask1, (char*)(dest)); + } + dest += destStride; + src += stride; + } + } + + MM::end(); +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSE +#endif // QDRAWHELPER_SSE_P_H diff --git a/src/gui/painting/qdrawhelper_ssse3.cpp b/src/gui/painting/qdrawhelper_ssse3.cpp new file mode 100644 index 0000000000..e392de2541 --- /dev/null +++ b/src/gui/painting/qdrawhelper_ssse3.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qdrawhelper_x86_p.h> + +#ifdef QT_HAVE_SSSE3 + +#include <private/qdrawingprimitive_sse2_p.h> + +QT_BEGIN_NAMESPACE + +inline static void blend_pixel(quint32 &dst, const quint32 src) +{ + if (src >= 0xff000000) + dst = src; + else if (src != 0) + dst = src + BYTE_MUL(dst, qAlpha(~src)); +} + + +/* The instruction palignr uses direct arguments, so we have to generate the code fo the different + shift (4, 8, 12). Checking the alignment inside the loop is unfortunatelly way too slow. + */ +#define BLENDING_LOOP(palignrOffset, length)\ + for (; x < length-3; x += 4) { \ + const __m128i srcVectorLastLoaded = _mm_load_si128((__m128i *)&src[x - minusOffsetToAlignSrcOn16Bytes + 4]);\ + const __m128i srcVector = _mm_alignr_epi8(srcVectorLastLoaded, srcVectorPrevLoaded, palignrOffset); \ + const __m128i srcVectorAlpha = _mm_and_si128(srcVector, alphaMask); \ + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, alphaMask)) == 0xffff) { \ + _mm_store_si128((__m128i *)&dst[x], srcVector); \ + } else if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, nullVector)) != 0xffff) { \ + __m128i alphaChannel = _mm_shuffle_epi8(srcVector, alphaShuffleMask); \ + alphaChannel = _mm_sub_epi16(one, alphaChannel); \ + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); \ + __m128i destMultipliedByOneMinusAlpha; \ + BYTE_MUL_SSE2(destMultipliedByOneMinusAlpha, dstVector, alphaChannel, colorMask, half); \ + const __m128i result = _mm_add_epi8(srcVector, destMultipliedByOneMinusAlpha); \ + _mm_store_si128((__m128i *)&dst[x], result); \ + } \ + srcVectorPrevLoaded = srcVectorLastLoaded;\ + } + + +// Basically blend src over dst with the const alpha defined as constAlphaVector. +// nullVector, half, one, colorMask are constant across the whole image/texture, and should be defined as: +//const __m128i nullVector = _mm_set1_epi32(0); +//const __m128i half = _mm_set1_epi16(0x80); +//const __m128i one = _mm_set1_epi16(0xff); +//const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); +//const __m128i alphaMask = _mm_set1_epi32(0xff000000); +// +// The computation being done is: +// result = s + d * (1-alpha) +// with shortcuts if fully opaque or fully transparent. +#define BLEND_SOURCE_OVER_ARGB32_SSSE3(dst, src, length, nullVector, half, one, colorMask, alphaMask) { \ + int x = 0; \ +\ + /* First, get dst aligned. */ \ + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) { \ + blend_pixel(dst[x], src[x]); \ + } \ +\ + const int minusOffsetToAlignSrcOn16Bytes = (reinterpret_cast<quintptr>(&(src[x])) >> 2) & 0x3;\ +\ + if (!minusOffsetToAlignSrcOn16Bytes) {\ + /* src is aligned, usual algorithm but with aligned operations.\ + See the SSE2 version for more documentation on the algorithm itself. */\ + const __m128i alphaShuffleMask = _mm_set_epi8(0xff,15,0xff,15,0xff,11,0xff,11,0xff,7,0xff,7,0xff,3,0xff,3);\ + for (; x < length-3; x += 4) { \ + const __m128i srcVector = _mm_load_si128((__m128i *)&src[x]); \ + const __m128i srcVectorAlpha = _mm_and_si128(srcVector, alphaMask); \ + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, alphaMask)) == 0xffff) { \ + _mm_store_si128((__m128i *)&dst[x], srcVector); \ + } else if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, nullVector)) != 0xffff) { \ + __m128i alphaChannel = _mm_shuffle_epi8(srcVector, alphaShuffleMask); \ + alphaChannel = _mm_sub_epi16(one, alphaChannel); \ + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); \ + __m128i destMultipliedByOneMinusAlpha; \ + BYTE_MUL_SSE2(destMultipliedByOneMinusAlpha, dstVector, alphaChannel, colorMask, half); \ + const __m128i result = _mm_add_epi8(srcVector, destMultipliedByOneMinusAlpha); \ + _mm_store_si128((__m128i *)&dst[x], result); \ + } \ + } /* end for() */\ + } else if ((length - x) >= 8) {\ + /* We use two vectors to extract the src: prevLoaded for the first pixels, lastLoaded for the current pixels. */\ + __m128i srcVectorPrevLoaded = _mm_load_si128((__m128i *)&src[x - minusOffsetToAlignSrcOn16Bytes]);\ + const int palignrOffset = minusOffsetToAlignSrcOn16Bytes << 2;\ +\ + const __m128i alphaShuffleMask = _mm_set_epi8(0xff,15,0xff,15,0xff,11,0xff,11,0xff,7,0xff,7,0xff,3,0xff,3);\ + switch (palignrOffset) {\ + case 4:\ + BLENDING_LOOP(4, length)\ + break;\ + case 8:\ + BLENDING_LOOP(8, length)\ + break;\ + case 12:\ + BLENDING_LOOP(12, length)\ + break;\ + }\ + }\ + for (; x < length; ++x) \ + blend_pixel(dst[x], src[x]); \ +} + +void qt_blend_argb32_on_argb32_ssse3(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha) +{ + const quint32 *src = (const quint32 *) srcPixels; + quint32 *dst = (quint32 *) destPixels; + if (const_alpha == 256) { + const __m128i alphaMask = _mm_set1_epi32(0xff000000); + const __m128i nullVector = _mm_setzero_si128(); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i one = _mm_set1_epi16(0xff); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + + for (int y = 0; y < h; ++y) { + BLEND_SOURCE_OVER_ARGB32_SSSE3(dst, src, w, nullVector, half, one, colorMask, alphaMask); + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } else if (const_alpha != 0) { + // dest = (s + d * sia) * ca + d * cia + // = s * ca + d * (sia * ca + cia) + // = s * ca + d * (1 - sa*ca) + const_alpha = (const_alpha * 255) >> 8; + const __m128i nullVector = _mm_setzero_si128(); + const __m128i half = _mm_set1_epi16(0x80); + const __m128i one = _mm_set1_epi16(0xff); + const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); + const __m128i constAlphaVector = _mm_set1_epi16(const_alpha); + for (int y = 0; y < h; ++y) { + BLEND_SOURCE_OVER_ARGB32_WITH_CONST_ALPHA_SSE2(dst, src, w, nullVector, half, one, colorMask, constAlphaVector) + dst = (quint32 *)(((uchar *) dst) + dbpl); + src = (const quint32 *)(((const uchar *) src) + sbpl); + } + } +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSSE3 diff --git a/src/gui/painting/qdrawhelper_x86_p.h b/src/gui/painting/qdrawhelper_x86_p.h new file mode 100644 index 0000000000..496ca8d141 --- /dev/null +++ b/src/gui/painting/qdrawhelper_x86_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWHELPER_X86_P_H +#define QDRAWHELPER_X86_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/qdrawhelper_p.h> + +QT_BEGIN_NAMESPACE + +#ifdef QT_HAVE_MMX +extern CompositionFunction qt_functionForMode_MMX[]; +extern CompositionFunctionSolid qt_functionForModeSolid_MMX[]; +void qt_blend_color_argb_mmx(int count, const QSpan *spans, void *userData); +#endif + +#ifdef QT_HAVE_MMXEXT +void qt_memfill32_mmxext(quint32 *dest, quint32 value, int count); +void qt_bitmapblit16_mmxext(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, const uchar *src, + int width, int height, int stride); +#endif + +#ifdef QT_HAVE_3DNOW +#if defined(QT_HAVE_MMX) || !defined(QT_HAVE_SSE) +extern CompositionFunction qt_functionForMode_MMX3DNOW[]; +extern CompositionFunctionSolid qt_functionForModeSolid_MMX3DNOW[]; + +void qt_blend_color_argb_mmx3dnow(int count, const QSpan *spans, + void *userData); +#endif // MMX + +#ifdef QT_HAVE_SSE +extern CompositionFunction qt_functionForMode_SSE3DNOW[]; +extern CompositionFunctionSolid qt_functionForModeSolid_SSE3DNOW[]; + +void qt_memfill32_sse3dnow(quint32 *dest, quint32 value, int count); +void qt_bitmapblit16_sse3dnow(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, + int stride); +void qt_blend_color_argb_sse3dnow(int count, const QSpan *spans, + void *userData); +#endif // SSE +#endif // QT_HAVE_3DNOW + +#ifdef QT_HAVE_SSE +void qt_memfill32_sse(quint32 *dest, quint32 value, int count); +void qt_bitmapblit16_sse(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, int stride); + +void qt_blend_color_argb_sse(int count, const QSpan *spans, void *userData); + +extern CompositionFunction qt_functionForMode_SSE[]; +extern CompositionFunctionSolid qt_functionForModeSolid_SSE[]; +#endif // QT_HAVE_SSE + +#ifdef QT_HAVE_SSE2 +void qt_memfill32_sse2(quint32 *dest, quint32 value, int count); +void qt_memfill16_sse2(quint16 *dest, quint16 value, int count); +void qt_bitmapblit32_sse2(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, int stride); +void qt_bitmapblit16_sse2(QRasterBuffer *rasterBuffer, int x, int y, + quint32 color, + const uchar *src, int width, int height, int stride); +void qt_blend_argb32_on_argb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); +void qt_blend_rgb32_on_rgb32_sse2(uchar *destPixels, int dbpl, + const uchar *srcPixels, int sbpl, + int w, int h, + int const_alpha); + +extern CompositionFunction qt_functionForMode_onlySSE2[]; +extern CompositionFunctionSolid qt_functionForModeSolid_onlySSE2[]; +#endif // QT_HAVE_SSE2 + +#ifdef QT_HAVE_IWMMXT +void qt_blend_color_argb_iwmmxt(int count, const QSpan *spans, void *userData); + +extern CompositionFunction qt_functionForMode_IWMMXT[]; +extern CompositionFunctionSolid qt_functionForModeSolid_IWMMXT[]; +#endif + +static const int numCompositionFunctions = 33; + +QT_END_NAMESPACE + +#endif // QDRAWHELPER_X86_P_H diff --git a/src/gui/painting/qdrawingprimitive_sse2_p.h b/src/gui/painting/qdrawingprimitive_sse2_p.h new file mode 100644 index 0000000000..d8793c3900 --- /dev/null +++ b/src/gui/painting/qdrawingprimitive_sse2_p.h @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWINGPRIMITIVE_SSE2_P_H +#define QDRAWINGPRIMITIVE_SSE2_P_H + +#include <private/qsimd_p.h> + +#ifdef QT_HAVE_SSE2 + +// +// 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 + +/* + * Multiply the components of pixelVector by alphaChannel + * Each 32bits components of alphaChannel must be in the form 0x00AA00AA + * colorMask must have 0x00ff00ff on each 32 bits component + * half must have the value 128 (0x80) for each 32 bits compnent + */ +#define BYTE_MUL_SSE2(result, pixelVector, alphaChannel, colorMask, half) \ +{ \ + /* 1. separate the colors in 2 vectors so each color is on 16 bits \ + (in order to be multiplied by the alpha \ + each 32 bit of dstVectorAG are in the form 0x00AA00GG \ + each 32 bit of dstVectorRB are in the form 0x00RR00BB */\ + __m128i pixelVectorAG = _mm_srli_epi16(pixelVector, 8); \ + __m128i pixelVectorRB = _mm_and_si128(pixelVector, colorMask); \ + \ + /* 2. multiply the vectors by the alpha channel */\ + pixelVectorAG = _mm_mullo_epi16(pixelVectorAG, alphaChannel); \ + pixelVectorRB = _mm_mullo_epi16(pixelVectorRB, alphaChannel); \ + \ + /* 3. divide by 255, that's the tricky part. \ + we do it like for BYTE_MUL(), with bit shift: X/255 ~= (X + X/256 + rounding)/256 */ \ + /** so first (X + X/256 + rounding) */\ + pixelVectorRB = _mm_add_epi16(pixelVectorRB, _mm_srli_epi16(pixelVectorRB, 8)); \ + pixelVectorRB = _mm_add_epi16(pixelVectorRB, half); \ + pixelVectorAG = _mm_add_epi16(pixelVectorAG, _mm_srli_epi16(pixelVectorAG, 8)); \ + pixelVectorAG = _mm_add_epi16(pixelVectorAG, half); \ + \ + /** second divide by 256 */\ + pixelVectorRB = _mm_srli_epi16(pixelVectorRB, 8); \ + /** for AG, we could >> 8 to divide followed by << 8 to put the \ + bytes in the correct position. By masking instead, we execute \ + only one instruction */\ + pixelVectorAG = _mm_andnot_si128(colorMask, pixelVectorAG); \ + \ + /* 4. combine the 2 pairs of colors */ \ + result = _mm_or_si128(pixelVectorAG, pixelVectorRB); \ +} + +/* + * Each 32bits components of alphaChannel must be in the form 0x00AA00AA + * oneMinusAlphaChannel must be 255 - alpha for each 32 bits component + * colorMask must have 0x00ff00ff on each 32 bits component + * half must have the value 128 (0x80) for each 32 bits compnent + */ +#define INTERPOLATE_PIXEL_255_SSE2(result, srcVector, dstVector, alphaChannel, oneMinusAlphaChannel, colorMask, half) { \ + /* interpolate AG */\ + __m128i srcVectorAG = _mm_srli_epi16(srcVector, 8); \ + __m128i dstVectorAG = _mm_srli_epi16(dstVector, 8); \ + __m128i srcVectorAGalpha = _mm_mullo_epi16(srcVectorAG, alphaChannel); \ + __m128i dstVectorAGoneMinusAlphalpha = _mm_mullo_epi16(dstVectorAG, oneMinusAlphaChannel); \ + __m128i finalAG = _mm_add_epi16(srcVectorAGalpha, dstVectorAGoneMinusAlphalpha); \ + finalAG = _mm_add_epi16(finalAG, _mm_srli_epi16(finalAG, 8)); \ + finalAG = _mm_add_epi16(finalAG, half); \ + finalAG = _mm_andnot_si128(colorMask, finalAG); \ + \ + /* interpolate RB */\ + __m128i srcVectorRB = _mm_and_si128(srcVector, colorMask); \ + __m128i dstVectorRB = _mm_and_si128(dstVector, colorMask); \ + __m128i srcVectorRBalpha = _mm_mullo_epi16(srcVectorRB, alphaChannel); \ + __m128i dstVectorRBoneMinusAlphalpha = _mm_mullo_epi16(dstVectorRB, oneMinusAlphaChannel); \ + __m128i finalRB = _mm_add_epi16(srcVectorRBalpha, dstVectorRBoneMinusAlphalpha); \ + finalRB = _mm_add_epi16(finalRB, _mm_srli_epi16(finalRB, 8)); \ + finalRB = _mm_add_epi16(finalRB, half); \ + finalRB = _mm_srli_epi16(finalRB, 8); \ + \ + /* combine */\ + result = _mm_or_si128(finalAG, finalRB); \ +} + +// Basically blend src over dst with the const alpha defined as constAlphaVector. +// nullVector, half, one, colorMask are constant across the whole image/texture, and should be defined as: +//const __m128i nullVector = _mm_set1_epi32(0); +//const __m128i half = _mm_set1_epi16(0x80); +//const __m128i one = _mm_set1_epi16(0xff); +//const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); +//const __m128i alphaMask = _mm_set1_epi32(0xff000000); +// +// The computation being done is: +// result = s + d * (1-alpha) +// with shortcuts if fully opaque or fully transparent. +#define BLEND_SOURCE_OVER_ARGB32_SSE2(dst, src, length, nullVector, half, one, colorMask, alphaMask) { \ + int x = 0; \ +\ + /* First, get dst aligned. */ \ + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) { \ + uint s = src[x]; \ + if (s >= 0xff000000) \ + dst[x] = s; \ + else if (s != 0) \ + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); \ + } \ +\ + for (; x < length-3; x += 4) { \ + const __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); \ + const __m128i srcVectorAlpha = _mm_and_si128(srcVector, alphaMask); \ + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, alphaMask)) == 0xffff) { \ + /* all opaque */ \ + _mm_store_si128((__m128i *)&dst[x], srcVector); \ + } else if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVectorAlpha, nullVector)) != 0xffff) { \ + /* not fully transparent */ \ + /* extract the alpha channel on 2 x 16 bits */ \ + /* so we have room for the multiplication */ \ + /* each 32 bits will be in the form 0x00AA00AA */ \ + /* with A being the 1 - alpha */ \ + __m128i alphaChannel = _mm_srli_epi32(srcVector, 24); \ + alphaChannel = _mm_or_si128(alphaChannel, _mm_slli_epi32(alphaChannel, 16)); \ + alphaChannel = _mm_sub_epi16(one, alphaChannel); \ + \ + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); \ + __m128i destMultipliedByOneMinusAlpha; \ + BYTE_MUL_SSE2(destMultipliedByOneMinusAlpha, dstVector, alphaChannel, colorMask, half); \ + \ + /* result = s + d * (1-alpha) */\ + const __m128i result = _mm_add_epi8(srcVector, destMultipliedByOneMinusAlpha); \ + _mm_store_si128((__m128i *)&dst[x], result); \ + } \ + } \ + for (; x < length; ++x) { \ + uint s = src[x]; \ + if (s >= 0xff000000) \ + dst[x] = s; \ + else if (s != 0) \ + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); \ + } \ +} + +// Basically blend src over dst with the const alpha defined as constAlphaVector. +// nullVector, half, one, colorMask are constant across the whole image/texture, and should be defined as: +//const __m128i nullVector = _mm_set1_epi32(0); +//const __m128i half = _mm_set1_epi16(0x80); +//const __m128i one = _mm_set1_epi16(0xff); +//const __m128i colorMask = _mm_set1_epi32(0x00ff00ff); +// +// The computation being done is: +// dest = (s + d * sia) * ca + d * cia +// = s * ca + d * (sia * ca + cia) +// = s * ca + d * (1 - sa*ca) +#define BLEND_SOURCE_OVER_ARGB32_WITH_CONST_ALPHA_SSE2(dst, src, length, nullVector, half, one, colorMask, constAlphaVector) \ +{ \ + int x = 0; \ +\ + ALIGNMENT_PROLOGUE_16BYTES(dst, x, length) { \ + quint32 s = src[x]; \ + if (s != 0) { \ + s = BYTE_MUL(s, const_alpha); \ + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); \ + } \ + } \ +\ + for (; x < length-3; x += 4) { \ + __m128i srcVector = _mm_loadu_si128((__m128i *)&src[x]); \ + if (_mm_movemask_epi8(_mm_cmpeq_epi32(srcVector, nullVector)) != 0xffff) { \ + BYTE_MUL_SSE2(srcVector, srcVector, constAlphaVector, colorMask, half); \ +\ + __m128i alphaChannel = _mm_srli_epi32(srcVector, 24); \ + alphaChannel = _mm_or_si128(alphaChannel, _mm_slli_epi32(alphaChannel, 16)); \ + alphaChannel = _mm_sub_epi16(one, alphaChannel); \ + \ + const __m128i dstVector = _mm_load_si128((__m128i *)&dst[x]); \ + __m128i destMultipliedByOneMinusAlpha; \ + BYTE_MUL_SSE2(destMultipliedByOneMinusAlpha, dstVector, alphaChannel, colorMask, half); \ + \ + const __m128i result = _mm_add_epi8(srcVector, destMultipliedByOneMinusAlpha); \ + _mm_store_si128((__m128i *)&dst[x], result); \ + } \ + } \ + for (; x < length; ++x) { \ + quint32 s = src[x]; \ + if (s != 0) { \ + s = BYTE_MUL(s, const_alpha); \ + dst[x] = s + BYTE_MUL(dst[x], qAlpha(~s)); \ + } \ + } \ +} + +QT_END_NAMESPACE + +#endif // QT_HAVE_SSE2 + +#endif // QDRAWINGPRIMITIVE_SSE2_P_H diff --git a/src/gui/painting/qdrawutil.cpp b/src/gui/painting/qdrawutil.cpp new file mode 100644 index 0000000000..89474d9679 --- /dev/null +++ b/src/gui/painting/qdrawutil.cpp @@ -0,0 +1,1360 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdrawutil.h" +#include "qbitmap.h" +#include "qpixmapcache.h" +#include "qapplication.h" +#include "qpainter.h" +#include "qpalette.h" +#include <private/qpaintengineex_p.h> +#include <qvarlengtharray.h> +#include <qmath.h> +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \headerfile <qdrawutil.h> + \title Drawing Utility Functions + + \sa QPainter +*/ + +/*! + \fn void qDrawShadeLine(QPainter *painter, int x1, int y1, int x2, int y2, + const QPalette &palette, bool sunken, + int lineWidth, int midLineWidth) + \relates <qdrawutil.h> + + Draws a horizontal (\a y1 == \a y2) or vertical (\a x1 == \a x2) + shaded line using the given \a painter. Note that nothing is + drawn if \a y1 != \a y2 and \a x1 != \a x2 (i.e. the line is + neither horizontal nor vertical). + + The provided \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). The given \a lineWidth + specifies the line width for each of the lines; it is not the + total line width. The given \a midLineWidth specifies the width of + a middle line drawn in the QPalette::mid() color. + + The line appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to + make widgets that follow the current GUI style. + + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded line: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 0 + + \sa qDrawShadeRect(), qDrawShadePanel(), QStyle +*/ + +void qDrawShadeLine(QPainter *p, int x1, int y1, int x2, int y2, + const QPalette &pal, bool sunken, + int lineWidth, int midLineWidth) +{ + if (!(p && lineWidth >= 0 && midLineWidth >= 0)) { + qWarning("qDrawShadeLine: Invalid parameters"); + return; + } + int tlw = lineWidth*2 + midLineWidth; // total line width + QPen oldPen = p->pen(); // save pen + if (sunken) + p->setPen(pal.color(QPalette::Dark)); + else + p->setPen(pal.light().color()); + QPolygon a; + int i; + if (y1 == y2) { // horizontal line + int y = y1 - tlw/2; + if (x1 > x2) { // swap x1 and x2 + int t = x1; + x1 = x2; + x2 = t; + } + x2--; + for (i=0; i<lineWidth; i++) { // draw top shadow + a.setPoints(3, x1+i, y+tlw-1-i, + x1+i, y+i, + x2-i, y+i); + p->drawPolyline(a); + } + if (midLineWidth > 0) { + p->setPen(pal.mid().color()); + for (i=0; i<midLineWidth; i++) // draw lines in the middle + p->drawLine(x1+lineWidth, y+lineWidth+i, + x2-lineWidth, y+lineWidth+i); + } + if (sunken) + p->setPen(pal.light().color()); + else + p->setPen(pal.dark().color()); + for (i=0; i<lineWidth; i++) { // draw bottom shadow + a.setPoints(3, x1+i, y+tlw-i-1, + x2-i, y+tlw-i-1, + x2-i, y+i+1); + p->drawPolyline(a); + } + } + else if (x1 == x2) { // vertical line + int x = x1 - tlw/2; + if (y1 > y2) { // swap y1 and y2 + int t = y1; + y1 = y2; + y2 = t; + } + y2--; + for (i=0; i<lineWidth; i++) { // draw left shadow + a.setPoints(3, x+i, y2, + x+i, y1+i, + x+tlw-1, y1+i); + p->drawPolyline(a); + } + if (midLineWidth > 0) { + p->setPen(pal.mid().color()); + for (i=0; i<midLineWidth; i++) // draw lines in the middle + p->drawLine(x+lineWidth+i, y1+lineWidth, x+lineWidth+i, y2); + } + if (sunken) + p->setPen(pal.light().color()); + else + p->setPen(pal.dark().color()); + for (i=0; i<lineWidth; i++) { // draw right shadow + a.setPoints(3, x+lineWidth, y2-i, + x+tlw-i-1, y2-i, + x+tlw-i-1, y1+lineWidth); + p->drawPolyline(a); + } + } + p->setPen(oldPen); +} + +/*! + \fn void qDrawShadeRect(QPainter *painter, int x, int y, int width, int height, + const QPalette &palette, bool sunken, + int lineWidth, int midLineWidth, + const QBrush *fill) + \relates <qdrawutil.h> + + Draws the shaded rectangle beginning at (\a x, \a y) with the + given \a width and \a height using the provided \a painter. + + The provide \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors. The given \a lineWidth + specifies the line width for each of the lines; it is not the + total line width. The \a midLineWidth specifies the width of a + middle line drawn in the QPalette::mid() color. The rectangle's + interior is filled with the \a fill brush unless \a fill is 0. + + The rectangle appears sunken if \a sunken is true, otherwise + raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded rectangle: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 1 + + \sa qDrawShadeLine(), qDrawShadePanel(), qDrawPlainRect(), QStyle +*/ + +void qDrawShadeRect(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + int lineWidth, int midLineWidth, + const QBrush *fill) +{ + if (w == 0 || h == 0) + return; + if (! (w > 0 && h > 0 && lineWidth >= 0 && midLineWidth >= 0)) { + qWarning("qDrawShadeRect: Invalid parameters"); + return; + } + QPen oldPen = p->pen(); + if (sunken) + p->setPen(pal.dark().color()); + else + p->setPen(pal.light().color()); + int x1=x, y1=y, x2=x+w-1, y2=y+h-1; + + if (lineWidth == 1 && midLineWidth == 0) {// standard shade rectangle + p->drawRect(x1, y1, w-2, h-2); + if (sunken) + p->setPen(pal.light().color()); + else + p->setPen(pal.dark().color()); + QLineF lines[4] = { QLineF(x1+1, y1+1, x2-2, y1+1), + QLineF(x1+1, y1+2, x1+1, y2-2), + QLineF(x1, y2, x2, y2), + QLineF(x2,y1, x2,y2-1) }; + p->drawLines(lines, 4); // draw bottom/right lines + } else { // more complicated + int m = lineWidth+midLineWidth; + int i, j=0, k=m; + for (i=0; i<lineWidth; i++) { // draw top shadow + QLineF lines[4] = { QLineF(x1+i, y2-i, x1+i, y1+i), + QLineF(x1+i, y1+i, x2-i, y1+i), + QLineF(x1+k, y2-k, x2-k, y2-k), + QLineF(x2-k, y2-k, x2-k, y1+k) }; + p->drawLines(lines, 4); + k++; + } + p->setPen(pal.mid().color()); + j = lineWidth*2; + for (i=0; i<midLineWidth; i++) { // draw lines in the middle + p->drawRect(x1+lineWidth+i, y1+lineWidth+i, w-j-1, h-j-1); + j += 2; + } + if (sunken) + p->setPen(pal.light().color()); + else + p->setPen(pal.dark().color()); + k = m; + for (i=0; i<lineWidth; i++) { // draw bottom shadow + QLineF lines[4] = { QLineF(x1+1+i, y2-i, x2-i, y2-i), + QLineF(x2-i, y2-i, x2-i, y1+i+1), + QLineF(x1+k, y2-k, x1+k, y1+k), + QLineF(x1+k, y1+k, x2-k, y1+k) }; + p->drawLines(lines, 4); + k++; + } + } + if (fill) { + QBrush oldBrush = p->brush(); + int tlw = lineWidth + midLineWidth; + p->setPen(Qt::NoPen); + p->setBrush(*fill); + p->drawRect(x+tlw, y+tlw, w-2*tlw, h-2*tlw); + p->setBrush(oldBrush); + } + p->setPen(oldPen); // restore pen +} + + +/*! + \fn void qDrawShadePanel(QPainter *painter, int x, int y, int width, int height, + const QPalette &palette, bool sunken, + int lineWidth, const QBrush *fill) + \relates <qdrawutil.h> + + Draws the shaded panel beginning at (\a x, \a y) with the given \a + width and \a height using the provided \a painter and the given \a + lineWidth. + + The given \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). The panel's interior is filled + with the \a fill brush unless \a fill is 0. + + The panel appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded panel: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 2 + + \sa qDrawWinPanel(), qDrawShadeLine(), qDrawShadeRect(), QStyle +*/ + +void qDrawShadePanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + int lineWidth, const QBrush *fill) +{ + if (w == 0 || h == 0) + return; + if (!(w > 0 && h > 0 && lineWidth >= 0)) { + qWarning("qDrawShadePanel: Invalid parameters"); + } + QColor shade = pal.dark().color(); + QColor light = pal.light().color(); + if (fill) { + if (fill->color() == shade) + shade = pal.shadow().color(); + if (fill->color() == light) + light = pal.midlight().color(); + } + QPen oldPen = p->pen(); // save pen + QVector<QLineF> lines; + lines.reserve(2*lineWidth); + + if (sunken) + p->setPen(shade); + else + p->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--); + } + p->drawLines(lines); + lines.clear(); + if (sunken) + p->setPen(light); + else + p->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); + } + p->drawLines(lines); + if (fill) // fill with fill color + p->fillRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2, *fill); + p->setPen(oldPen); // restore pen +} + + +/*! + \internal + This function draws a rectangle with two pixel line width. + It is called from qDrawWinButton() and qDrawWinPanel(). + + c1..c4 and fill are used: + + 1 1 1 1 1 2 + 1 3 3 3 4 2 + 1 3 F F 4 2 + 1 3 F F 4 2 + 1 4 4 4 4 2 + 2 2 2 2 2 2 +*/ + +static void qDrawWinShades(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill) +{ + if (w < 2 || h < 2) // can't do anything with that + return; + QPen oldPen = p->pen(); + QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) }; + p->setPen(c1); + p->drawPolyline(a, 3); + QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; + p->setPen(c2); + p->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) }; + p->setPen(c3); + p->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) }; + p->setPen(c4); + p->drawPolyline(d, 3); + if (fill) + p->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); + } + p->setPen(oldPen); +} + + +/*! + \fn void qDrawWinButton(QPainter *painter, int x, int y, int width, int height, + const QPalette &palette, bool sunken, + const QBrush *fill) + \relates <qdrawutil.h> + + Draws the Windows-style button specified by the given point (\a x, + \a y}, \a width and \a height using the provided \a painter with a + line width of 2 pixels. The button's interior is filled with the + \a{fill} brush unless \a fill is 0. + + The given \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). + + The button appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style()-> Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + \sa qDrawWinPanel(), QStyle +*/ + +void qDrawWinButton(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + const QBrush *fill) +{ + if (sunken) + qDrawWinShades(p, x, y, w, h, + pal.shadow().color(), pal.light().color(), pal.dark().color(), + pal.button().color(), fill); + else + qDrawWinShades(p, x, y, w, h, + pal.light().color(), pal.shadow().color(), pal.button().color(), + pal.dark().color(), fill); +} + +/*! + \fn void qDrawWinPanel(QPainter *painter, int x, int y, int width, int height, + const QPalette &palette, bool sunken, + const QBrush *fill) + \relates <qdrawutil.h> + + Draws the Windows-style panel specified by the given point(\a x, + \a y), \a width and \a height using the provided \a painter with a + line width of 2 pixels. The button's interior is filled with the + \a fill brush unless \a fill is 0. + + The given \a palette specifies the shading colors. The panel + appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded panel: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 3 + + \sa qDrawShadePanel(), qDrawWinButton(), QStyle +*/ + +void qDrawWinPanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + const QBrush *fill) +{ + if (sunken) + qDrawWinShades(p, x, y, w, h, + pal.dark().color(), pal.light().color(), pal.shadow().color(), + pal.midlight().color(), fill); + else + qDrawWinShades(p, x, y, w, h, + pal.light().color(), pal.shadow().color(), pal.midlight().color(), + pal.dark().color(), fill); +} + +/*! + \fn void qDrawPlainRect(QPainter *painter, int x, int y, int width, int height, const QColor &lineColor, + int lineWidth, const QBrush *fill) + \relates <qdrawutil.h> + + Draws the plain rectangle beginning at (\a x, \a y) with the given + \a width and \a height, using the specified \a painter, \a lineColor + and \a lineWidth. The rectangle's interior is filled with the \a + fill brush unless \a fill is 0. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a plain rectangle: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 4 + + \sa qDrawShadeRect(), QStyle +*/ + +void qDrawPlainRect(QPainter *p, int x, int y, int w, int h, const QColor &c, + int lineWidth, const QBrush *fill) +{ + if (w == 0 || h == 0) + return; + if (!(w > 0 && h > 0 && lineWidth >= 0)) { + qWarning("qDrawPlainRect: Invalid parameters"); + } + QPen oldPen = p->pen(); + QBrush oldBrush = p->brush(); + p->setPen(c); + p->setBrush(Qt::NoBrush); + for (int i=0; i<lineWidth; i++) + p->drawRect(x+i, y+i, w-i*2 - 1, h-i*2 - 1); + if (fill) { // fill with fill color + p->setPen(Qt::NoPen); + p->setBrush(*fill); + p->drawRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2); + } + p->setPen(oldPen); + p->setBrush(oldBrush); +} + +/***************************************************************************** + Overloaded functions. + *****************************************************************************/ + +/*! + \fn void qDrawShadeLine(QPainter *painter, const QPoint &p1, const QPoint &p2, + const QPalette &palette, bool sunken, int lineWidth, int midLineWidth) + \relates <qdrawutil.h> + \overload + + Draws a horizontal or vertical shaded line between \a p1 and \a p2 + using the given \a painter. Note that nothing is drawn if the line + between the points would be neither horizontal nor vertical. + + The provided \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). The given \a lineWidth + specifies the line width for each of the lines; it is not the + total line width. The given \a midLineWidth specifies the width of + a middle line drawn in the QPalette::mid() color. + + The line appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to + make widgets that follow the current GUI style. + + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded line: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 5 + + \sa qDrawShadeRect(), qDrawShadePanel(), QStyle +*/ + +void qDrawShadeLine(QPainter *p, const QPoint &p1, const QPoint &p2, + const QPalette &pal, bool sunken, + int lineWidth, int midLineWidth) +{ + qDrawShadeLine(p, p1.x(), p1.y(), p2.x(), p2.y(), pal, sunken, + lineWidth, midLineWidth); +} + +/*! + \fn void qDrawShadeRect(QPainter *painter, const QRect &rect, const QPalette &palette, + bool sunken, int lineWidth, int midLineWidth, const QBrush *fill) + \relates <qdrawutil.h> + \overload + + Draws the shaded rectangle specified by \a rect using the given \a painter. + + The provide \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors. The given \a lineWidth + specifies the line width for each of the lines; it is not the + total line width. The \a midLineWidth specifies the width of a + middle line drawn in the QPalette::mid() color. The rectangle's + interior is filled with the \a fill brush unless \a fill is 0. + + The rectangle appears sunken if \a sunken is true, otherwise + raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded rectangle: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 6 + + \sa qDrawShadeLine(), qDrawShadePanel(), qDrawPlainRect(), QStyle +*/ + +void qDrawShadeRect(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, + int lineWidth, int midLineWidth, + const QBrush *fill) +{ + qDrawShadeRect(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, + lineWidth, midLineWidth, fill); +} + +/*! + \fn void qDrawShadePanel(QPainter *painter, const QRect &rect, const QPalette &palette, + bool sunken, int lineWidth, const QBrush *fill) + \relates <qdrawutil.h> + \overload + + Draws the shaded panel at the rectangle specified by \a rect using the + given \a painter and the given \a lineWidth. + + The given \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). The panel's interior is filled + with the \a fill brush unless \a fill is 0. + + The panel appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded panel: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 7 + + \sa qDrawWinPanel(), qDrawShadeLine(), qDrawShadeRect(), QStyle +*/ + +void qDrawShadePanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, + int lineWidth, const QBrush *fill) +{ + qDrawShadePanel(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, + lineWidth, fill); +} + +/*! + \fn void qDrawWinButton(QPainter *painter, const QRect &rect, const QPalette &palette, + bool sunken, const QBrush *fill) + \relates <qdrawutil.h> + \overload + + Draws the Windows-style button at the rectangle specified by \a rect using + the given \a painter with a line width of 2 pixels. The button's interior + is filled with the \a{fill} brush unless \a fill is 0. + + The given \a palette specifies the shading colors (\l + {QPalette::light()}{light}, \l {QPalette::dark()}{dark} and \l + {QPalette::mid()}{middle} colors). + + The button appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style()-> Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + \sa qDrawWinPanel(), QStyle +*/ + +void qDrawWinButton(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, const QBrush *fill) +{ + qDrawWinButton(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, fill); +} + +/*! + \fn void qDrawWinPanel(QPainter *painter, const QRect &rect, const QPalette &palette, + bool sunken, const QBrush *fill) + \overload + + Draws the Windows-style panel at the rectangle specified by \a rect using + the given \a painter with a line width of 2 pixels. The button's interior + is filled with the \a fill brush unless \a fill is 0. + + The given \a palette specifies the shading colors. The panel + appears sunken if \a sunken is true, otherwise raised. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a shaded panel: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 8 + + \sa qDrawShadePanel(), qDrawWinButton(), QStyle +*/ + +void qDrawWinPanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, const QBrush *fill) +{ + qDrawWinPanel(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, fill); +} + +/*! + \fn void qDrawPlainRect(QPainter *painter, const QRect &rect, const QColor &lineColor, int lineWidth, const QBrush *fill) + \relates <qdrawutil.h> + \overload + + Draws the plain rectangle specified by \a rect using the given \a painter, + \a lineColor and \a lineWidth. The rectangle's interior is filled with the + \a fill brush unless \a fill is 0. + + \warning This function does not look at QWidget::style() or + QApplication::style(). Use the drawing functions in QStyle to make + widgets that follow the current GUI style. + + Alternatively you can use a QFrame widget and apply the + QFrame::setFrameStyle() function to display a plain rectangle: + + \snippet doc/src/snippets/code/src_gui_painting_qdrawutil.cpp 9 + + \sa qDrawShadeRect(), QStyle +*/ + +void qDrawPlainRect(QPainter *p, const QRect &r, const QColor &c, + int lineWidth, const QBrush *fill) +{ + qDrawPlainRect(p, r.x(), r.y(), r.width(), r.height(), c, + lineWidth, fill); +} + +#ifdef QT3_SUPPORT +static void qDrawWinArrow(QPainter *p, Qt::ArrowType type, bool down, + int x, int y, int w, int h, + const QPalette &pal, bool enabled) +{ + QPolygon a; // arrow polygon + switch (type) { + case Qt::UpArrow: + a.setPoints(7, -3,1, 3,1, -2,0, 2,0, -1,-1, 1,-1, 0,-2); + break; + case Qt::DownArrow: + a.setPoints(7, -3,-1, 3,-1, -2,0, 2,0, -1,1, 1,1, 0,2); + break; + case Qt::LeftArrow: + a.setPoints(7, 1,-3, 1,3, 0,-2, 0,2, -1,-1, -1,1, -2,0); + break; + case Qt::RightArrow: + a.setPoints(7, -1,-3, -1,3, 0,-2, 0,2, 1,-1, 1,1, 2,0); + break; + default: + break; + } + if (a.isEmpty()) + return; + + if (down) { + x++; + y++; + } + + QPen savePen = p->pen(); // save current pen + if (down) + p->setBrushOrigin(p->brushOrigin() + QPoint(1,1)); + p->fillRect(x, y, w, h, pal.brush(QPalette::Button)); + if (down) + p->setBrushOrigin(p->brushOrigin() - QPoint(1,1)); + if (enabled) { + a.translate(x+w/2, y+h/2); + p->setPen(pal.foreground().color()); + p->drawLine(a.at(0), a.at(1)); + p->drawLine(a.at(2), a.at(2)); + p->drawPoint(a[6]); + } else { + a.translate(x+w/2+1, y+h/2+1); + p->setPen(pal.light().color()); + p->drawLine(a.at(0), a.at(1)); + p->drawLine(a.at(2), a.at(2)); + p->drawPoint(a[6]); + a.translate(-1, -1); + p->setPen(pal.mid().color()); + p->drawLine(a.at(0), a.at(1)); + p->drawLine(a.at(2), a.at(2)); + p->drawPoint(a[6]); + } + p->setPen(savePen); // restore pen +} +#endif // QT3_SUPPORT + +#if defined(Q_CC_MSVC) +#pragma warning(disable: 4244) +#endif + +#ifdef QT3_SUPPORT +#ifndef QT_NO_STYLE_MOTIF +// motif arrows look the same whether they are used or not +// is this correct? +static void qDrawMotifArrow(QPainter *p, Qt::ArrowType type, bool down, + int x, int y, int w, int h, + const QPalette &pal, bool) +{ + QPolygon bFill; // fill polygon + QPolygon bTop; // top shadow. + QPolygon bBot; // bottom shadow. + QPolygon bLeft; // left shadow. + QTransform matrix; // xform matrix + bool vertical = type == Qt::UpArrow || type == Qt::DownArrow; + bool horizontal = !vertical; + int dim = w < h ? w : h; + int colspec = 0x0000; // color specification array + + if (dim < 2) // too small arrow + return; + + if (dim > 3) { + if (dim > 6) + bFill.resize(dim & 1 ? 3 : 4); + bTop.resize((dim/2)*2); + bBot.resize(dim & 1 ? dim + 1 : dim); + bLeft.resize(dim > 4 ? 4 : 2); + bLeft.putPoints(0, 2, 0,0, 0,dim-1); + if (dim > 4) + bLeft.putPoints(2, 2, 1,2, 1,dim-3); + bTop.putPoints(0, 4, 1,0, 1,1, 2,1, 3,1); + bBot.putPoints(0, 4, 1,dim-1, 1,dim-2, 2,dim-2, 3,dim-2); + + for(int i=0; i<dim/2-2 ; i++) { + bTop.putPoints(i*2+4, 2, 2+i*2,2+i, 5+i*2, 2+i); + bBot.putPoints(i*2+4, 2, 2+i*2,dim-3-i, 5+i*2,dim-3-i); + } + if (dim & 1) // odd number size: extra line + bBot.putPoints(dim-1, 2, dim-3,dim/2, dim-1,dim/2); + if (dim > 6) { // dim>6: must fill interior + bFill.putPoints(0, 2, 1,dim-3, 1,2); + if (dim & 1) // if size is an odd number + bFill.setPoint(2, dim - 3, dim / 2); + else + bFill.putPoints(2, 2, dim-4,dim/2-1, dim-4,dim/2); + } + } + else { + if (dim == 3) { // 3x3 arrow pattern + bLeft.setPoints(4, 0,0, 0,2, 1,1, 1,1); + bTop .setPoints(2, 1,0, 1,0); + bBot .setPoints(2, 1,2, 2,1); + } + else { // 2x2 arrow pattern + bLeft.setPoints(2, 0,0, 0,1); + bTop .setPoints(2, 1,0, 1,0); + bBot .setPoints(2, 1,1, 1,1); + } + } + + if (type == Qt::UpArrow || type == Qt::LeftArrow) { + matrix.translate(x, y); + if (vertical) { + matrix.translate(0, h - 1); + matrix.rotate(-90); + } else { + matrix.translate(w - 1, h - 1); + matrix.rotate(180); + } + if (down) + colspec = horizontal ? 0x2334 : 0x2343; + else + colspec = horizontal ? 0x1443 : 0x1434; + } + else if (type == Qt::DownArrow || type == Qt::RightArrow) { + matrix.translate(x, y); + if (vertical) { + matrix.translate(w-1, 0); + matrix.rotate(90); + } + if (down) + colspec = horizontal ? 0x2443 : 0x2434; + else + colspec = horizontal ? 0x1334 : 0x1343; + } + + const QColor *cols[5]; + cols[0] = 0; + cols[1] = &pal.button().color(); + cols[2] = &pal.mid().color(); + cols[3] = &pal.light().color(); + cols[4] = &pal.dark().color(); +#define CMID *cols[(colspec>>12) & 0xf] +#define CLEFT *cols[(colspec>>8) & 0xf] +#define CTOP *cols[(colspec>>4) & 0xf] +#define CBOT *cols[colspec & 0xf] + + QPen savePen = p->pen(); // save current pen + QBrush saveBrush = p->brush(); // save current brush + QTransform wxm = p->transform(); + QPen pen(Qt::NoPen); + const QBrush &brush = pal.brush(QPalette::Button); + + p->setPen(pen); + p->setBrush(brush); + p->setTransform(matrix, true); // set transformation matrix + p->drawPolygon(bFill); // fill arrow + p->setBrush(Qt::NoBrush); // don't fill + + p->setPen(CLEFT); + p->drawLines(bLeft); + p->setPen(CTOP); + p->drawLines(bTop); + p->setPen(CBOT); + p->drawLines(bBot); + + p->setTransform(wxm); + p->setBrush(saveBrush); // restore brush + p->setPen(savePen); // restore pen + +#undef CMID +#undef CLEFT +#undef CTOP +#undef CBOT +} +#endif // QT_NO_STYLE_MOTIF + +QRect qItemRect(QPainter *p, Qt::GUIStyle gs, + int x, int y, int w, int h, + int flags, + bool enabled, + const QPixmap *pixmap, + const QString& text, int len) +{ + QRect result; + + if (pixmap) { + if ((flags & Qt::AlignVCenter) == Qt::AlignVCenter) + y += h/2 - pixmap->height()/2; + else if ((flags & Qt::AlignBottom) == Qt::AlignBottom) + y += h - pixmap->height(); + if ((flags & Qt::AlignRight) == Qt::AlignRight) + x += w - pixmap->width(); + else if ((flags & Qt::AlignHCenter) == Qt::AlignHCenter) + x += w/2 - pixmap->width()/2; + else if ((flags & Qt::AlignLeft) != Qt::AlignLeft && QApplication::isRightToLeft()) + x += w - pixmap->width(); + result = QRect(x, y, pixmap->width(), pixmap->height()); + } else if (!text.isNull() && p) { + result = p->boundingRect(QRect(x, y, w, h), flags, text.left(len)); + if (gs == Qt::WindowsStyle && !enabled) { + result.setWidth(result.width()+1); + result.setHeight(result.height()+1); + } + } else { + result = QRect(x, y, w, h); + } + + return result; +} + +void qDrawArrow(QPainter *p, Qt::ArrowType type, Qt::GUIStyle style, bool down, + int x, int y, int w, int h, + const QPalette &pal, bool enabled) +{ + switch (style) { + case Qt::WindowsStyle: + qDrawWinArrow(p, type, down, x, y, w, h, pal, enabled); + break; +#ifndef QT_NO_STYLE_MOTIF + case Qt::MotifStyle: + qDrawMotifArrow(p, type, down, x, y, w, h, pal, enabled); + break; +#endif + default: + qWarning("qDrawArrow: Requested unsupported GUI style"); + } +} + +void qDrawItem(QPainter *p, Qt::GUIStyle gs, + int x, int y, int w, int h, + int flags, + const QPalette &pal, bool enabled, + const QPixmap *pixmap, + const QString& text, int len , const QColor* penColor) +{ + p->setPen(penColor?*penColor:pal.foreground().color()); + if (pixmap) { + QPixmap pm(*pixmap); + bool clip = (flags & Qt::TextDontClip) == 0; + if (clip) { + if (pm.width() < w && pm.height() < h) + clip = false; + else + p->setClipRect(x, y, w, h); + } + if ((flags & Qt::AlignVCenter) == Qt::AlignVCenter) + y += h/2 - pm.height()/2; + else if ((flags & Qt::AlignBottom) == Qt::AlignBottom) + y += h - pm.height(); + if ((flags & Qt::AlignRight) == Qt::AlignRight) + x += w - pm.width(); + else if ((flags & Qt::AlignHCenter) == Qt::AlignHCenter) + x += w/2 - pm.width()/2; + else if (((flags & Qt::AlignLeft) != Qt::AlignLeft) && QApplication::isRightToLeft()) // Qt::AlignAuto && rightToLeft + x += w - pm.width(); + + if (!enabled) { + if (pm.hasAlphaChannel()) { // pixmap with a mask + pm = pm.mask(); + } else if (pm.depth() == 1) { // monochrome pixmap, no mask + ; +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + } else { // color pixmap, no mask + QString k = QLatin1Literal("$qt-drawitem") + % HexString<qint64>(pm.cacheKey()); + + if (!QPixmapCache::find(k, pm)) { + pm = pm.createHeuristicMask(); + pm.setMask((QBitmap&)pm); + QPixmapCache::insert(k, pm); + } +#endif + } + if (gs == Qt::WindowsStyle) { + p->setPen(pal.light().color()); + p->drawPixmap(x+1, y+1, pm); + p->setPen(pal.text().color()); + } + } + p->drawPixmap(x, y, pm); + if (clip) + p->setClipping(false); + } else if (!text.isNull()) { + if (gs == Qt::WindowsStyle && !enabled) { + p->setPen(pal.light().color()); + p->drawText(x+1, y+1, w, h, flags, text.left(len)); + p->setPen(pal.text().color()); + } + p->drawText(x, y, w, h, flags, text.left(len)); + } +} + +#endif + +/*! + \class QTileRules + \since 4.6 + + Holds the rules used to draw a pixmap or image split into nine segments, + similar to \l{http://www.w3.org/TR/css3-background/}{CSS3 border-images}. + + \sa Qt::TileRule, QMargins +*/ + +/*! \fn QTileRules::QTileRules(Qt::TileRule horizontalRule, Qt::TileRule verticalRule) + Constructs a QTileRules with the given \a horizontalRule and + \a verticalRule. + */ + +/*! \fn QTileRules::QTileRules(Qt::TileRule rule) + Constructs a QTileRules with the given \a rule used for both + the horizontal rule and the vertical rule. + */ + +/*! + \fn void qDrawBorderPixmap(QPainter *painter, const QRect &target, const QMargins &margins, const QPixmap &pixmap) + \relates <qdrawutil.h> + \since 4.6 + \overload + + \brief The qDrawBorderPixmap function is for drawing a pixmap into + the margins of a rectangle. + + Draws the given \a pixmap into the given \a target rectangle, using the + given \a painter. The pixmap will be split into nine segments and drawn + according to the \a margins structure. +*/ + +typedef QVarLengthArray<QPainter::PixmapFragment, 16> QPixmapFragmentsArray; + +/*! + \since 4.6 + + Draws the indicated \a sourceRect rectangle from the given \a pixmap into + the given \a targetRect rectangle, using the given \a painter. The pixmap + will be split into nine segments according to the given \a targetMargins + and \a sourceMargins structures. Finally, the pixmap will be drawn + according to the given \a rules. + + This function is used to draw a scaled pixmap, similar to + \l{http://www.w3.org/TR/css3-background/}{CSS3 border-images} + + \sa Qt::TileRule, QTileRules, QMargins +*/ + +void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargins &targetMargins, + const QPixmap &pixmap, const QRect &sourceRect,const QMargins &sourceMargins, + const QTileRules &rules, QDrawBorderPixmap::DrawingHints hints) +{ + QPainter::PixmapFragment d; + d.opacity = 1.0; + d.rotation = 0.0; + + QPixmapFragmentsArray opaqueData; + QPixmapFragmentsArray translucentData; + + // source center + const int sourceCenterTop = sourceRect.top() + sourceMargins.top(); + const int sourceCenterLeft = sourceRect.left() + sourceMargins.left(); + const int sourceCenterBottom = sourceRect.bottom() - sourceMargins.bottom() + 1; + const int sourceCenterRight = sourceRect.right() - sourceMargins.right() + 1; + const int sourceCenterWidth = sourceCenterRight - sourceCenterLeft; + const int sourceCenterHeight = sourceCenterBottom - sourceCenterTop; + // target center + const int targetCenterTop = targetRect.top() + targetMargins.top(); + const int targetCenterLeft = targetRect.left() + targetMargins.left(); + const int targetCenterBottom = targetRect.bottom() - targetMargins.bottom() + 1; + const int targetCenterRight = targetRect.right() - targetMargins.right() + 1; + const int targetCenterWidth = targetCenterRight - targetCenterLeft; + const int targetCenterHeight = targetCenterBottom - targetCenterTop; + + QVarLengthArray<qreal, 16> xTarget; // x-coordinates of target rectangles + QVarLengthArray<qreal, 16> yTarget; // y-coordinates of target rectangles + + int columns = 3; + int rows = 3; + if (rules.horizontal != Qt::StretchTile && sourceCenterWidth != 0) + columns = qMax(3, 2 + qCeil(targetCenterWidth / qreal(sourceCenterWidth))); + if (rules.vertical != Qt::StretchTile && sourceCenterHeight != 0) + rows = qMax(3, 2 + qCeil(targetCenterHeight / qreal(sourceCenterHeight))); + + xTarget.resize(columns + 1); + yTarget.resize(rows + 1); + + bool oldAA = painter->testRenderHint(QPainter::Antialiasing); + if (painter->paintEngine()->type() != QPaintEngine::OpenGL + && painter->paintEngine()->type() != QPaintEngine::OpenGL2 + && oldAA && painter->combinedTransform().type() != QTransform::TxNone) { + painter->setRenderHint(QPainter::Antialiasing, false); + } + + xTarget[0] = targetRect.left(); + xTarget[1] = targetCenterLeft; + xTarget[columns - 1] = targetCenterRight; + xTarget[columns] = targetRect.left() + targetRect.width(); + + yTarget[0] = targetRect.top(); + yTarget[1] = targetCenterTop; + yTarget[rows - 1] = targetCenterBottom; + yTarget[rows] = targetRect.top() + targetRect.height(); + + qreal dx = targetCenterWidth; + qreal dy = targetCenterHeight; + + switch (rules.horizontal) { + case Qt::StretchTile: + dx = targetCenterWidth; + break; + case Qt::RepeatTile: + dx = sourceCenterWidth; + break; + case Qt::RoundTile: + dx = targetCenterWidth / qreal(columns - 2); + break; + } + + for (int i = 2; i < columns - 1; ++i) + xTarget[i] = xTarget[i - 1] + dx; + + switch (rules.vertical) { + case Qt::StretchTile: + dy = targetCenterHeight; + break; + case Qt::RepeatTile: + dy = sourceCenterHeight; + break; + case Qt::RoundTile: + dy = targetCenterHeight / qreal(rows - 2); + break; + } + + for (int i = 2; i < rows - 1; ++i) + yTarget[i] = yTarget[i - 1] + dy; + + // corners + if (targetMargins.top() > 0 && targetMargins.left() > 0 && sourceMargins.top() > 0 && sourceMargins.left() > 0) { // top left + d.x = (0.5 * (xTarget[1] + xTarget[0])); + d.y = (0.5 * (yTarget[1] + yTarget[0])); + d.sourceLeft = sourceRect.left(); + d.sourceTop = sourceRect.top(); + d.width = sourceMargins.left(); + d.height = sourceMargins.top(); + d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width; + d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height; + if (hints & QDrawBorderPixmap::OpaqueTopLeft) + opaqueData.append(d); + else + translucentData.append(d); + } + if (targetMargins.top() > 0 && targetMargins.right() > 0 && sourceMargins.top() > 0 && sourceMargins.right() > 0) { // top right + d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1])); + d.y = (0.5 * (yTarget[1] + yTarget[0])); + d.sourceLeft = sourceCenterRight; + d.sourceTop = sourceRect.top(); + d.width = sourceMargins.right(); + d.height = sourceMargins.top(); + d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width; + d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height; + if (hints & QDrawBorderPixmap::OpaqueTopRight) + opaqueData.append(d); + else + translucentData.append(d); + } + if (targetMargins.bottom() > 0 && targetMargins.left() > 0 && sourceMargins.bottom() > 0 && sourceMargins.left() > 0) { // bottom left + d.x = (0.5 * (xTarget[1] + xTarget[0])); + d.y =(0.5 * (yTarget[rows] + yTarget[rows - 1])); + d.sourceLeft = sourceRect.left(); + d.sourceTop = sourceCenterBottom; + d.width = sourceMargins.left(); + d.height = sourceMargins.bottom(); + d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width; + d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height; + if (hints & QDrawBorderPixmap::OpaqueBottomLeft) + opaqueData.append(d); + else + translucentData.append(d); + } + if (targetMargins.bottom() > 0 && targetMargins.right() > 0 && sourceMargins.bottom() > 0 && sourceMargins.right() > 0) { // bottom right + d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1])); + d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1])); + d.sourceLeft = sourceCenterRight; + d.sourceTop = sourceCenterBottom; + d.width = sourceMargins.right(); + d.height = sourceMargins.bottom(); + d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width; + d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height; + if (hints & QDrawBorderPixmap::OpaqueBottomRight) + opaqueData.append(d); + else + translucentData.append(d); + } + + // horizontal edges + if (targetCenterWidth > 0 && sourceCenterWidth > 0) { + if (targetMargins.top() > 0 && sourceMargins.top() > 0) { // top + QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueTop ? opaqueData : translucentData; + d.sourceLeft = sourceCenterLeft; + d.sourceTop = sourceRect.top(); + d.width = sourceCenterWidth; + d.height = sourceMargins.top(); + d.y = (0.5 * (yTarget[1] + yTarget[0])); + d.scaleX = dx / d.width; + d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height; + for (int i = 1; i < columns - 1; ++i) { + d.x = (0.5 * (xTarget[i + 1] + xTarget[i])); + data.append(d); + } + if (rules.horizontal == Qt::RepeatTile) + data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX); + } + if (targetMargins.bottom() > 0 && sourceMargins.bottom() > 0) { // bottom + QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueBottom ? opaqueData : translucentData; + d.sourceLeft = sourceCenterLeft; + d.sourceTop = sourceCenterBottom; + d.width = sourceCenterWidth; + d.height = sourceMargins.bottom(); + d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1])); + d.scaleX = dx / d.width; + d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height; + for (int i = 1; i < columns - 1; ++i) { + d.x = (0.5 * (xTarget[i + 1] + xTarget[i])); + data.append(d); + } + if (rules.horizontal == Qt::RepeatTile) + data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX); + } + } + + // vertical edges + if (targetCenterHeight > 0 && sourceCenterHeight > 0) { + if (targetMargins.left() > 0 && sourceMargins.left() > 0) { // left + QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueLeft ? opaqueData : translucentData; + d.sourceLeft = sourceRect.left(); + d.sourceTop = sourceCenterTop; + d.width = sourceMargins.left(); + d.height = sourceCenterHeight; + d.x = (0.5 * (xTarget[1] + xTarget[0])); + d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width; + d.scaleY = dy / d.height; + for (int i = 1; i < rows - 1; ++i) { + d.y = (0.5 * (yTarget[i + 1] + yTarget[i])); + data.append(d); + } + if (rules.vertical == Qt::RepeatTile) + data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY); + } + if (targetMargins.right() > 0 && sourceMargins.right() > 0) { // right + QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueRight ? opaqueData : translucentData; + d.sourceLeft = sourceCenterRight; + d.sourceTop = sourceCenterTop; + d.width = sourceMargins.right(); + d.height = sourceCenterHeight; + d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1])); + d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width; + d.scaleY = dy / d.height; + for (int i = 1; i < rows - 1; ++i) { + d.y = (0.5 * (yTarget[i + 1] + yTarget[i])); + data.append(d); + } + if (rules.vertical == Qt::RepeatTile) + data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY); + } + } + + // center + if (targetCenterWidth > 0 && targetCenterHeight > 0 && sourceCenterWidth > 0 && sourceCenterHeight > 0) { + QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueCenter ? opaqueData : translucentData; + d.sourceLeft = sourceCenterLeft; + d.sourceTop = sourceCenterTop; + d.width = sourceCenterWidth; + d.height = sourceCenterHeight; + d.scaleX = dx / d.width; + d.scaleY = dy / d.height; + + qreal repeatWidth = (xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX; + qreal repeatHeight = (yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY; + + for (int j = 1; j < rows - 1; ++j) { + d.y = (0.5 * (yTarget[j + 1] + yTarget[j])); + for (int i = 1; i < columns - 1; ++i) { + d.x = (0.5 * (xTarget[i + 1] + xTarget[i])); + data.append(d); + } + if (rules.horizontal == Qt::RepeatTile) + data[data.size() - 1].width = repeatWidth; + } + if (rules.vertical == Qt::RepeatTile) { + for (int i = 1; i < columns - 1; ++i) + data[data.size() - i].height = repeatHeight; + } + } + + if (opaqueData.size()) + painter->drawPixmapFragments(opaqueData.data(), opaqueData.size(), pixmap, QPainter::OpaqueHint); + if (translucentData.size()) + painter->drawPixmapFragments(translucentData.data(), translucentData.size(), pixmap); + + if (oldAA) + painter->setRenderHint(QPainter::Antialiasing, true); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qdrawutil.h b/src/gui/painting/qdrawutil.h new file mode 100644 index 0000000000..20b558d411 --- /dev/null +++ b/src/gui/painting/qdrawutil.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDRAWUTIL_H +#define QDRAWUTIL_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> // char*->QString conversion +#include <QtCore/qmargins.h> +#include <QtGui/qpixmap.h> +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPainter; +#ifndef QT3_SUPPORT +class QColorGroup; +#endif +class QPalette; +class QPoint; +class QColor; +class QBrush; +class QRect; + +// +// Standard shade drawing +// + +Q_GUI_EXPORT void qDrawShadeLine(QPainter *p, int x1, int y1, int x2, int y2, + const QPalette &pal, bool sunken = true, + int lineWidth = 1, int midLineWidth = 0); + +Q_GUI_EXPORT void qDrawShadeLine(QPainter *p, const QPoint &p1, const QPoint &p2, + const QPalette &pal, bool sunken = true, + int lineWidth = 1, int midLineWidth = 0); + +Q_GUI_EXPORT void qDrawShadeRect(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + int lineWidth = 1, int midLineWidth = 0, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawShadeRect(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + int lineWidth = 1, int midLineWidth = 0, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawShadePanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + int lineWidth = 1, const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawShadePanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + int lineWidth = 1, const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawWinButton(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawWinButton(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawWinPanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawWinPanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawPlainRect(QPainter *p, int x, int y, int w, int h, const QColor &, + int lineWidth = 1, const QBrush *fill = 0); + +Q_GUI_EXPORT void qDrawPlainRect(QPainter *p, const QRect &r, const QColor &, + int lineWidth = 1, const QBrush *fill = 0); + + +#ifdef QT3_SUPPORT +// +// Use QStyle::itemRect(), QStyle::drawItem() and QStyle::drawArrow() instead. +// +Q_GUI_EXPORT QT3_SUPPORT QRect qItemRect(QPainter *p, Qt::GUIStyle gs, int x, int y, int w, int h, + int flags, bool enabled, + const QPixmap *pixmap, const QString& text, int len=-1); + +Q_GUI_EXPORT QT3_SUPPORT void qDrawItem(QPainter *p, Qt::GUIStyle gs, int x, int y, int w, int h, + int flags, const QPalette &pal, bool enabled, + const QPixmap *pixmap, const QString& text, + int len=-1, const QColor* penColor = 0); + +Q_GUI_EXPORT QT3_SUPPORT void qDrawArrow(QPainter *p, Qt::ArrowType type, Qt::GUIStyle style, bool down, + int x, int y, int w, int h, + const QPalette &pal, bool enabled); +#endif + +struct QTileRules +{ + inline QTileRules(Qt::TileRule horizontalRule, Qt::TileRule verticalRule) + : horizontal(horizontalRule), vertical(verticalRule) {} + inline QTileRules(Qt::TileRule rule = Qt::StretchTile) + : horizontal(rule), vertical(rule) {} + Qt::TileRule horizontal; + Qt::TileRule vertical; +}; + +#ifndef Q_QDOC +// For internal use only. +namespace QDrawBorderPixmap +{ + enum DrawingHint + { + OpaqueTopLeft = 0x0001, + OpaqueTop = 0x0002, + OpaqueTopRight = 0x0004, + OpaqueLeft = 0x0008, + OpaqueCenter = 0x0010, + OpaqueRight = 0x0020, + OpaqueBottomLeft = 0x0040, + OpaqueBottom = 0x0080, + OpaqueBottomRight = 0x0100, + OpaqueCorners = OpaqueTopLeft | OpaqueTopRight | OpaqueBottomLeft | OpaqueBottomRight, + OpaqueEdges = OpaqueTop | OpaqueLeft | OpaqueRight | OpaqueBottom, + OpaqueFrame = OpaqueCorners | OpaqueEdges, + OpaqueAll = OpaqueCenter | OpaqueFrame + }; + + Q_DECLARE_FLAGS(DrawingHints, DrawingHint) +} +#endif + +Q_GUI_EXPORT void qDrawBorderPixmap(QPainter *painter, + const QRect &targetRect, + const QMargins &targetMargins, + const QPixmap &pixmap, + const QRect &sourceRect, + const QMargins &sourceMargins, + const QTileRules &rules = QTileRules() +#ifndef Q_QDOC + , QDrawBorderPixmap::DrawingHints hints = 0 +#endif + ); + +inline void qDrawBorderPixmap(QPainter *painter, + const QRect &target, + const QMargins &margins, + const QPixmap &pixmap) +{ + qDrawBorderPixmap(painter, target, margins, pixmap, pixmap.rect(), margins); +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDRAWUTIL_H diff --git a/src/gui/painting/qemulationpaintengine.cpp b/src/gui/painting/qemulationpaintengine.cpp new file mode 100644 index 0000000000..6fec3f3641 --- /dev/null +++ b/src/gui/painting/qemulationpaintengine.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qemulationpaintengine_p.h> +#include <private/qpainter_p.h> +#include <private/qtextengine_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +QEmulationPaintEngine::QEmulationPaintEngine(QPaintEngineEx *engine) + : real_engine(engine) +{ + QPaintEngine::state = real_engine->state(); +} + + +QPaintEngine::Type QEmulationPaintEngine::type() const +{ + return real_engine->type(); +} + +bool QEmulationPaintEngine::begin(QPaintDevice *) +{ + return true; +} + +bool QEmulationPaintEngine::end() +{ + return true; +} + + +QPainterState *QEmulationPaintEngine::createState(QPainterState *orig) const +{ + return real_engine->createState(orig); +} + +void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush) +{ + QPainterState *s = state(); + + if (s->bgMode == Qt::OpaqueMode) { + Qt::BrushStyle style = brush.style(); + if (style >= Qt::Dense1Pattern && style <= Qt::DiagCrossPattern) + real_engine->fill(path, s->bgBrush); + } + + Qt::BrushStyle style = qbrush_style(brush); + if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) { + const QGradient *g = brush.gradient(); + + if (g->coordinateMode() > QGradient::LogicalMode) { + if (g->coordinateMode() == QGradient::StretchToDeviceMode) { + QBrush copy = brush; + QTransform mat = copy.transform(); + mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); + copy.setTransform(mat); + real_engine->fill(path, copy); + return; + } else if (g->coordinateMode() == QGradient::ObjectBoundingMode) { + QBrush copy = brush; + QTransform mat = copy.transform(); + QRectF r = path.controlPointRect(); + mat.translate(r.x(), r.y()); + mat.scale(r.width(), r.height()); + copy.setTransform(mat); + real_engine->fill(path, copy); + return; + } + } + } + + real_engine->fill(path, brush); +} + +void QEmulationPaintEngine::stroke(const QVectorPath &path, const QPen &pen) +{ + QPainterState *s = state(); + + if (s->bgMode == Qt::OpaqueMode && pen.style() > Qt::SolidLine) { + QPen bgPen = pen; + bgPen.setBrush(s->bgBrush); + bgPen.setStyle(Qt::SolidLine); + real_engine->stroke(path, bgPen); + } + + QBrush brush = pen.brush(); + QPen copy = pen; + Qt::BrushStyle style = qbrush_style(brush); + if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) { + const QGradient *g = brush.gradient(); + + if (g->coordinateMode() > QGradient::LogicalMode) { + if (g->coordinateMode() == QGradient::StretchToDeviceMode) { + QTransform mat = brush.transform(); + mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); + brush.setTransform(mat); + copy.setBrush(brush); + real_engine->stroke(path, copy); + return; + } else if (g->coordinateMode() == QGradient::ObjectBoundingMode) { + QTransform mat = brush.transform(); + QRectF r = path.controlPointRect(); + mat.translate(r.x(), r.y()); + mat.scale(r.width(), r.height()); + brush.setTransform(mat); + copy.setBrush(brush); + real_engine->stroke(path, copy); + return; + } + } + } + + real_engine->stroke(path, pen); +} + +void QEmulationPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op) +{ + real_engine->clip(path, op); +} + +void QEmulationPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + if (state()->bgMode == Qt::OpaqueMode && pm.isQBitmap()) + fillBGRect(r); + real_engine->drawPixmap(r, pm, sr); +} + +void QEmulationPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + if (state()->bgMode == Qt::OpaqueMode) { + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRectF rect(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()); + fillBGRect(rect); + } + + QPainterState *s = state(); + Qt::BrushStyle style = qbrush_style(s->pen.brush()); + if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) + { + QPen savedPen = s->pen; + QGradient g = *s->pen.brush().gradient(); + + if (g.coordinateMode() > QGradient::LogicalMode) { + QTransform mat = s->pen.brush().transform(); + if (g.coordinateMode() == QGradient::StretchToDeviceMode) { + mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); + } else if (g.coordinateMode() == QGradient::ObjectBoundingMode) { + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRectF r(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()); + mat.translate(r.x(), r.y()); + mat.scale(r.width(), r.height()); + } + g.setCoordinateMode(QGradient::LogicalMode); + QBrush brush(g); + brush.setTransform(mat); + s->pen.setBrush(brush); + penChanged(); + real_engine->drawTextItem(p, textItem); + s->pen = savedPen; + penChanged(); + return; + } + } + + real_engine->drawTextItem(p, textItem); +} + +void QEmulationPaintEngine::drawStaticTextItem(QStaticTextItem *item) +{ + real_engine->drawStaticTextItem(item); +} + +void QEmulationPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) +{ + if (state()->bgMode == Qt::OpaqueMode && pixmap.isQBitmap()) + fillBGRect(r); + real_engine->drawTiledPixmap(r, pixmap, s); +} + +void QEmulationPaintEngine::drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + real_engine->drawImage(r, pm, sr, flags); +} + +void QEmulationPaintEngine::clipEnabledChanged() +{ + real_engine->clipEnabledChanged(); +} + +void QEmulationPaintEngine::penChanged() +{ + real_engine->penChanged(); +} + +void QEmulationPaintEngine::brushChanged() +{ + real_engine->brushChanged(); +} + +void QEmulationPaintEngine::brushOriginChanged() +{ + real_engine->brushOriginChanged(); +} + +void QEmulationPaintEngine::opacityChanged() +{ + real_engine->opacityChanged(); +} + +void QEmulationPaintEngine::compositionModeChanged() +{ + real_engine->compositionModeChanged(); +} + +void QEmulationPaintEngine::renderHintsChanged() +{ + real_engine->renderHintsChanged(); +} + +void QEmulationPaintEngine::transformChanged() +{ + real_engine->transformChanged(); +} + +void QEmulationPaintEngine::setState(QPainterState *s) +{ + QPaintEngine::state = s; + real_engine->setState(s); +} + +void QEmulationPaintEngine::beginNativePainting() +{ + real_engine->beginNativePainting(); +} + +void QEmulationPaintEngine::endNativePainting() +{ + real_engine->endNativePainting(); +} + +void QEmulationPaintEngine::fillBGRect(const QRectF &r) +{ + qreal pts[] = { r.x(), r.y(), r.x() + r.width(), r.y(), + r.x() + r.width(), r.y() + r.height(), r.x(), r.y() + r.height() }; + QVectorPath vp(pts, 4, 0, QVectorPath::RectangleHint); + real_engine->fill(vp, state()->bgBrush); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qemulationpaintengine_p.h b/src/gui/painting/qemulationpaintengine_p.h new file mode 100644 index 0000000000..20c399074d --- /dev/null +++ b/src/gui/painting/qemulationpaintengine_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QEMULATIONPAINTENGINE_P_H +#define QEMULATIONPAINTENGINE_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/qpaintengineex_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QEmulationPaintEngine : public QPaintEngineEx +{ +public: + QEmulationPaintEngine(QPaintEngineEx *engine); + + virtual bool begin(QPaintDevice *pdev); + virtual bool end(); + + virtual Type type() const; + virtual QPainterState *createState(QPainterState *orig) const; + + virtual void fill(const QVectorPath &path, const QBrush &brush); + virtual void stroke(const QVectorPath &path, const QPen &pen); + virtual void clip(const QVectorPath &path, Qt::ClipOperation op); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + virtual void drawTextItem(const QPointF &p, const QTextItem &textItem); + virtual void drawStaticTextItem(QStaticTextItem *item); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags); + + virtual void clipEnabledChanged(); + virtual void penChanged(); + virtual void brushChanged(); + virtual void brushOriginChanged(); + virtual void opacityChanged(); + virtual void compositionModeChanged(); + virtual void renderHintsChanged(); + virtual void transformChanged(); + + virtual void setState(QPainterState *s); + + virtual void beginNativePainting(); + virtual void endNativePainting(); + + virtual uint flags() const {return QPaintEngineEx::IsEmulationEngine | QPaintEngineEx::DoNotEmulate;} + + inline QPainterState *state() { return (QPainterState *)QPaintEngine::state; } + inline const QPainterState *state() const { return (const QPainterState *)QPaintEngine::state; } + + QPaintEngineEx *real_engine; +private: + void fillBGRect(const QRectF &r); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/painting/qfixed_p.h b/src/gui/painting/qfixed_p.h new file mode 100644 index 0000000000..cd96f9a0ef --- /dev/null +++ b/src/gui/painting/qfixed_p.h @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFIXED_P_H +#define QFIXED_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qdebug.h" +#include "QtCore/qpoint.h" +#include "QtCore/qsize.h" + +QT_BEGIN_NAMESPACE + +struct QFixed { +public: + QFixed() : val(0) {} + QFixed(int i) : val(i<<6) {} + QFixed(long i) : val(i<<6) {} + QFixed &operator=(int i) { val = (i<<6); return *this; } + QFixed &operator=(long i) { val = (i<<6); return *this; } + + static QFixed fromReal(qreal r) { QFixed f; f.val = (int)(r*qreal(64)); return f; } + static QFixed fromFixed(int fixed) { QFixed f; f.val = fixed; return f; } + + inline int value() const { return val; } + inline void setValue(int value) { val = value; } + + inline int toInt() const { return (((val)+32) & -64)>>6; } + inline qreal toReal() const { return ((qreal)val)/(qreal)64; } + + inline int truncate() const { return val>>6; } + inline QFixed round() const { QFixed f; f.val = ((val)+32) & -64; return f; } + inline QFixed floor() const { QFixed f; f.val = (val) & -64; return f; } + inline QFixed ceil() const { QFixed f; f.val = (val+63) & -64; return f; } + + inline QFixed operator+(int i) const { QFixed f; f.val = (val + (i<<6)); return f; } + inline QFixed operator+(uint i) const { QFixed f; f.val = (val + (i<<6)); return f; } + inline QFixed operator+(const QFixed &other) const { QFixed f; f.val = (val + other.val); return f; } + inline QFixed &operator+=(int i) { val += (i<<6); return *this; } + inline QFixed &operator+=(uint i) { val += (i<<6); return *this; } + inline QFixed &operator+=(const QFixed &other) { val += other.val; return *this; } + inline QFixed operator-(int i) const { QFixed f; f.val = (val - (i<<6)); return f; } + inline QFixed operator-(uint i) const { QFixed f; f.val = (val - (i<<6)); return f; } + inline QFixed operator-(const QFixed &other) const { QFixed f; f.val = (val - other.val); return f; } + inline QFixed &operator-=(int i) { val -= (i<<6); return *this; } + inline QFixed &operator-=(uint i) { val -= (i<<6); return *this; } + inline QFixed &operator-=(const QFixed &other) { val -= other.val; return *this; } + inline QFixed operator-() const { QFixed f; f.val = -val; return f; } + + inline bool operator==(const QFixed &other) const { return val == other.val; } + inline bool operator!=(const QFixed &other) const { return val != other.val; } + inline bool operator<(const QFixed &other) const { return val < other.val; } + inline bool operator>(const QFixed &other) const { return val > other.val; } + inline bool operator<=(const QFixed &other) const { return val <= other.val; } + inline bool operator>=(const QFixed &other) const { return val >= other.val; } + inline bool operator!() const { return !val; } + + inline QFixed &operator/=(int x) { val /= x; return *this; } + inline QFixed &operator/=(const QFixed &o) { + if (o.val == 0) { + val = 0x7FFFFFFFL; + } else { + bool neg = false; + qint64 a = val; + qint64 b = o.val; + if (a < 0) { a = -a; neg = true; } + if (b < 0) { b = -b; neg = !neg; } + + int res = (int)(((a << 6) + (b >> 1)) / b); + + val = (neg ? -res : res); + } + return *this; + } + inline QFixed operator/(int d) const { QFixed f; f.val = val/d; return f; } + inline QFixed operator/(QFixed b) const { QFixed f = *this; return (f /= b); } + inline QFixed operator>>(int d) const { QFixed f = *this; f.val >>= d; return f; } + inline QFixed &operator*=(int i) { val *= i; return *this; } + inline QFixed &operator*=(uint i) { val *= i; return *this; } + inline QFixed &operator*=(const QFixed &o) { + bool neg = false; + qint64 a = val; + qint64 b = o.val; + if (a < 0) { a = -a; neg = true; } + if (b < 0) { b = -b; neg = !neg; } + + int res = (int)((a * b + 0x20L) >> 6); + val = neg ? -res : res; + return *this; + } + inline QFixed operator*(int i) const { QFixed f = *this; return (f *= i); } + inline QFixed operator*(uint i) const { QFixed f = *this; return (f *= i); } + inline QFixed operator*(const QFixed &o) const { QFixed f = *this; return (f *= o); } + +private: + QFixed(qreal i) : val((int)(i*qreal(64))) {} + QFixed &operator=(qreal i) { val = (int)(i*qreal(64)); return *this; } + inline QFixed operator+(qreal i) const { QFixed f; f.val = (val + (int)(i*qreal(64))); return f; } + inline QFixed &operator+=(qreal i) { val += (int)(i*64); return *this; } + inline QFixed operator-(qreal i) const { QFixed f; f.val = (val - (int)(i*qreal(64))); return f; } + inline QFixed &operator-=(qreal i) { val -= (int)(i*64); return *this; } + inline QFixed &operator/=(qreal r) { val = (int)(val/r); return *this; } + inline QFixed operator/(qreal d) const { QFixed f; f.val = (int)(val/d); return f; } + inline QFixed &operator*=(qreal d) { val = (int) (val*d); return *this; } + inline QFixed operator*(qreal d) const { QFixed f = *this; return (f *= d); } + int val; +}; +Q_DECLARE_TYPEINFO(QFixed, Q_PRIMITIVE_TYPE); + +#define QFIXED_MAX (INT_MAX/256) + +inline int qRound(const QFixed &f) { return f.toInt(); } +inline int qFloor(const QFixed &f) { return f.floor().truncate(); } + +inline QFixed operator*(int i, const QFixed &d) { return d*i; } +inline QFixed operator+(int i, const QFixed &d) { return d+i; } +inline QFixed operator-(int i, const QFixed &d) { return -(d-i); } +inline QFixed operator*(uint i, const QFixed &d) { return d*i; } +inline QFixed operator+(uint i, const QFixed &d) { return d+i; } +inline QFixed operator-(uint i, const QFixed &d) { return -(d-i); } +// inline QFixed operator*(qreal d, const QFixed &d2) { return d2*d; } + +inline bool operator==(const QFixed &f, int i) { return f.value() == (i<<6); } +inline bool operator==(int i, const QFixed &f) { return f.value() == (i<<6); } +inline bool operator!=(const QFixed &f, int i) { return f.value() != (i<<6); } +inline bool operator!=(int i, const QFixed &f) { return f.value() != (i<<6); } +inline bool operator<=(const QFixed &f, int i) { return f.value() <= (i<<6); } +inline bool operator<=(int i, const QFixed &f) { return (i<<6) <= f.value(); } +inline bool operator>=(const QFixed &f, int i) { return f.value() >= (i<<6); } +inline bool operator>=(int i, const QFixed &f) { return (i<<6) >= f.value(); } +inline bool operator<(const QFixed &f, int i) { return f.value() < (i<<6); } +inline bool operator<(int i, const QFixed &f) { return (i<<6) < f.value(); } +inline bool operator>(const QFixed &f, int i) { return f.value() > (i<<6); } +inline bool operator>(int i, const QFixed &f) { return (i<<6) > f.value(); } + +#ifndef QT_NO_DEBUG_STREAM +inline QDebug &operator<<(QDebug &dbg, const QFixed &f) +{ return dbg << f.toReal(); } +#endif + +struct QFixedPoint { + QFixed x; + QFixed y; + inline QFixedPoint() {} + inline QFixedPoint(const QFixed &_x, const QFixed &_y) : x(_x), y(_y) {} + QPointF toPointF() const { return QPointF(x.toReal(), y.toReal()); } + static QFixedPoint fromPointF(const QPointF &p) { + return QFixedPoint(QFixed::fromReal(p.x()), QFixed::fromReal(p.y())); + } +}; +Q_DECLARE_TYPEINFO(QFixedPoint, Q_PRIMITIVE_TYPE); + +inline QFixedPoint operator-(const QFixedPoint &p1, const QFixedPoint &p2) +{ return QFixedPoint(p1.x - p2.x, p1.y - p2.y); } +inline QFixedPoint operator+(const QFixedPoint &p1, const QFixedPoint &p2) +{ return QFixedPoint(p1.x + p2.x, p1.y + p2.y); } + +struct QFixedSize { + QFixed width; + QFixed height; + QSizeF toSizeF() const { return QSizeF(width.toReal(), height.toReal()); } + static QFixedSize fromSizeF(const QSizeF &s) { + QFixedSize size; + size.width = QFixed::fromReal(s.width()); + size.height = QFixed::fromReal(s.height()); + return size; + } +}; +Q_DECLARE_TYPEINFO(QFixedSize, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif // QTEXTENGINE_P_H diff --git a/src/gui/painting/qgraphicssystem.cpp b/src/gui/painting/qgraphicssystem.cpp new file mode 100644 index 0000000000..171ef46f98 --- /dev/null +++ b/src/gui/painting/qgraphicssystem.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicssystem_p.h" + +#ifdef Q_WS_X11 +# include <private/qpixmap_x11_p.h> +#endif +#if defined(Q_WS_WIN) +# include <private/qpixmap_raster_p.h> +#endif +#ifdef Q_WS_MAC +# include <private/qpixmap_mac_p.h> +#endif +#ifdef Q_WS_QPA +# include <QtGui/private/qapplication_p.h> +#endif +#ifdef Q_OS_SYMBIAN +# include <private/qpixmap_s60_p.h> +#endif + +QT_BEGIN_NAMESPACE + +QGraphicsSystem::~QGraphicsSystem() +{ +} + +QPixmapData *QGraphicsSystem::createDefaultPixmapData(QPixmapData::PixelType type) +{ +#ifdef Q_WS_QWS + Q_UNUSED(type); +#endif +#if defined(Q_WS_X11) + return new QX11PixmapData(type); +#elif defined(Q_WS_WIN) + return new QRasterPixmapData(type); +#elif defined(Q_WS_MAC) + return new QMacPixmapData(type); +#elif defined(Q_WS_QPA) + return QApplicationPrivate::platformIntegration()->createPixmapData(type); +#elif defined(Q_OS_SYMBIAN) + return new QS60PixmapData(type); +#elif !defined(Q_WS_QWS) +#error QGraphicsSystem::createDefaultPixmapData() not implemented +#endif + return 0; +} + +QPixmapData *QGraphicsSystem::createPixmapData(QPixmapData *origin) +{ + return createPixmapData(origin->pixelType()); +} + +void QGraphicsSystem::releaseCachedResources() +{ + // Do nothing here +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystem_mac.cpp b/src/gui/painting/qgraphicssystem_mac.cpp new file mode 100644 index 0000000000..4f0be1f86a --- /dev/null +++ b/src/gui/painting/qgraphicssystem_mac.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicssystem_mac_p.h" + +#include <private/qpixmap_mac_p.h> +#include <private/qwindowsurface_mac_p.h> + +QT_BEGIN_NAMESPACE + +QPixmapData *QCoreGraphicsSystem::createPixmapData(QPixmapData::PixelType type) const +{ + return new QMacPixmapData(type); +} + +QWindowSurface *QCoreGraphicsSystem::createWindowSurface(QWidget *widget) const +{ + return new QMacWindowSurface(widget); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystem_mac_p.h b/src/gui/painting/qgraphicssystem_mac_p.h new file mode 100644 index 0000000000..4d2eae84d7 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_mac_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEM_MAC_P_H +#define QGRAPHICSSYSTEM_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QCoreGraphicsSystem : public QGraphicsSystem +{ +public: + QPixmapData *createPixmapData(QPixmapData::PixelType type) const; + QWindowSurface *createWindowSurface(QWidget *widget) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qgraphicssystem_p.h b/src/gui/painting/qgraphicssystem_p.h new file mode 100644 index 0000000000..0f99a311a6 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEM_P_H +#define QGRAPHICSSYSTEM_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/qpixmapdata_p.h" +#include "private/qwindowsurface_p.h" +#include "private/qpaintengine_blitter_p.h" + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +class QPixmapFilter; +class QBlittable; + +class Q_GUI_EXPORT QGraphicsSystem +{ +public: + virtual QPixmapData *createPixmapData(QPixmapData::PixelType type) const = 0; + virtual QPixmapData *createPixmapData(QPixmapData *origin); + virtual QWindowSurface *createWindowSurface(QWidget *widget) const = 0; + + virtual ~QGraphicsSystem(); + + //### Remove this & change qpixmap.cpp & qbitmap.cpp once every platform is gaurenteed + // to have a graphics system. + static QPixmapData *createDefaultPixmapData(QPixmapData::PixelType type); + + virtual void releaseCachedResources(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qgraphicssystem_qws.cpp b/src/gui/painting/qgraphicssystem_qws.cpp new file mode 100644 index 0000000000..40f9ad4839 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_qws.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qscreen_qws.h> +#include "qgraphicssystem_qws_p.h" +#include <private/qpixmap_raster_p.h> +#include <private/qwindowsurface_qws_p.h> + +QT_BEGIN_NAMESPACE + +QPixmapData *QWSGraphicsSystem::createPixmapData(QPixmapData::PixelType type) const +{ + if (screen->pixmapDataFactory()) + return screen->pixmapDataFactory()->create(type); //### For 4.4 compatibility + else + return new QRasterPixmapData(type); +} + +QWindowSurface *QWSGraphicsSystem::createWindowSurface(QWidget *widget) const +{ + return screen->createSurface(widget); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystem_qws_p.h b/src/gui/painting/qgraphicssystem_qws_p.h new file mode 100644 index 0000000000..92b92628cd --- /dev/null +++ b/src/gui/painting/qgraphicssystem_qws_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEM_QWS_P_H +#define QGRAPHICSSYSTEM_QWS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QWSGraphicsSystem : public QGraphicsSystem +{ +public: + + QWSGraphicsSystem() + : screen(QScreen::instance()) {} + + QWSGraphicsSystem(QScreen* s) + : screen(s) {} + + virtual QPixmapData *createPixmapData(QPixmapData::PixelType type) const; + QWindowSurface *createWindowSurface(QWidget *widget) const; + +private: + QScreen* screen; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qgraphicssystem_raster.cpp b/src/gui/painting/qgraphicssystem_raster.cpp new file mode 100644 index 0000000000..69bf588292 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_raster.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicssystem_raster_p.h" + +#ifdef Q_OS_SYMBIAN +#include "private/qpixmap_s60_p.h" +#include "private/qwindowsurface_s60_p.h" +#else +#include "private/qpixmap_raster_p.h" +#include "private/qwindowsurface_raster_p.h" +#endif + +QT_BEGIN_NAMESPACE + +QPixmapData *QRasterGraphicsSystem::createPixmapData(QPixmapData::PixelType type) const +{ +#ifdef Q_OS_SYMBIAN + return new QS60PixmapData(type); +#else + return new QRasterPixmapData(type); +#endif +} + +QWindowSurface *QRasterGraphicsSystem::createWindowSurface(QWidget *widget) const +{ +#ifdef Q_OS_SYMBIAN + return new QS60WindowSurface(widget); +#else + return new QRasterWindowSurface(widget); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystem_raster_p.h b/src/gui/painting/qgraphicssystem_raster_p.h new file mode 100644 index 0000000000..a66c56903f --- /dev/null +++ b/src/gui/painting/qgraphicssystem_raster_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEM_RASTER_P_H +#define QGRAPHICSSYSTEM_RASTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +class QRasterGraphicsSystem : public QGraphicsSystem +{ +public: + QPixmapData *createPixmapData(QPixmapData::PixelType type) const; + QWindowSurface *createWindowSurface(QWidget *widget) const; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qgraphicssystem_runtime.cpp b/src/gui/painting/qgraphicssystem_runtime.cpp new file mode 100644 index 0000000000..d1dd7ef392 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_runtime.cpp @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qgraphicssystem_runtime_p.h> +#include <private/qgraphicssystem_raster_p.h> +#include <private/qgraphicssystemfactory_p.h> +#include <private/qapplication_p.h> +#include <private/qwidget_p.h> +#include <QtCore/QDebug> +#include <QtCore/QTimer> +#include <QtGui/QBitmap> + +QT_BEGIN_NAMESPACE + +static int qt_pixmap_serial = 0; + +#define READBACK(f) \ + f \ + readBackInfo(); + + +class QDeferredGraphicsSystemChange : public QObject +{ + Q_OBJECT + +public: + QDeferredGraphicsSystemChange(QRuntimeGraphicsSystem *gs, const QString& graphicsSystemName) + : m_graphicsSystem(gs), m_graphicsSystemName(graphicsSystemName) + { + } + + void launch() + { + QTimer::singleShot(0, this, SLOT(doChange())); + } + +private slots: + + void doChange() + { + m_graphicsSystem->setGraphicsSystem(m_graphicsSystemName); + deleteLater(); + } + +private: + + QRuntimeGraphicsSystem *m_graphicsSystem; + QString m_graphicsSystemName; +}; + +QRuntimePixmapData::QRuntimePixmapData(const QRuntimeGraphicsSystem *gs, PixelType type) + : QPixmapData(type, RuntimeClass), m_graphicsSystem(gs) +{ + setSerialNumber(++qt_pixmap_serial); +} + +QRuntimePixmapData::~QRuntimePixmapData() +{ + if (QApplicationPrivate::graphics_system) + m_graphicsSystem->removePixmapData(this); + delete m_data; +} + +void QRuntimePixmapData::readBackInfo() +{ + w = m_data->width(); + h = m_data->height(); + d = m_data->depth(); + is_null = m_data->isNull(); +} + + +QPixmapData *QRuntimePixmapData::createCompatiblePixmapData() const +{ + QRuntimePixmapData *rtData = new QRuntimePixmapData(m_graphicsSystem, pixelType()); + rtData->m_data = m_data->createCompatiblePixmapData(); + return rtData; +} + + +void QRuntimePixmapData::resize(int width, int height) +{ + READBACK( + m_data->resize(width, height); + ) +} + + +void QRuntimePixmapData::fromImage(const QImage &image, + Qt::ImageConversionFlags flags) +{ + READBACK( + m_data->fromImage(image, flags); + ) +} + + +bool QRuntimePixmapData::fromFile(const QString &filename, const char *format, + Qt::ImageConversionFlags flags) +{ + bool success(false); + READBACK( + success = m_data->fromFile(filename, format, flags); + ) + return success; +} + +bool QRuntimePixmapData::fromData(const uchar *buffer, uint len, const char *format, + Qt::ImageConversionFlags flags) +{ + bool success(false); + READBACK( + success = m_data->fromData(buffer, len, format, flags); + ) + return success; +} + + +void QRuntimePixmapData::copy(const QPixmapData *data, const QRect &rect) +{ + if (data->runtimeData()) { + READBACK( + m_data->copy(data->runtimeData(), rect); + ) + } else { + READBACK( + m_data->copy(data, rect); + ) + } +} + +bool QRuntimePixmapData::scroll(int dx, int dy, const QRect &rect) +{ + return m_data->scroll(dx, dy, rect); +} + + +int QRuntimePixmapData::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + return m_data->metric(metric); +} + +void QRuntimePixmapData::fill(const QColor &color) +{ + return m_data->fill(color); +} + +QBitmap QRuntimePixmapData::mask() const +{ + return m_data->mask(); +} + +void QRuntimePixmapData::setMask(const QBitmap &mask) +{ + READBACK( + m_data->setMask(mask); + ) +} + +bool QRuntimePixmapData::hasAlphaChannel() const +{ + return m_data->hasAlphaChannel(); +} + +QPixmap QRuntimePixmapData::transformed(const QTransform &matrix, + Qt::TransformationMode mode) const +{ + return m_data->transformed(matrix, mode); +} + +void QRuntimePixmapData::setAlphaChannel(const QPixmap &alphaChannel) +{ + READBACK( + m_data->setAlphaChannel(alphaChannel); + ) +} + +QPixmap QRuntimePixmapData::alphaChannel() const +{ + return m_data->alphaChannel(); +} + +QImage QRuntimePixmapData::toImage() const +{ + return m_data->toImage(); +} + +QPaintEngine* QRuntimePixmapData::paintEngine() const +{ + return m_data->paintEngine(); +} + +QImage* QRuntimePixmapData::buffer() +{ + return m_data->buffer(); +} + +#if defined(Q_OS_SYMBIAN) +void* QRuntimePixmapData::toNativeType(NativeType type) +{ + return m_data->toNativeType(type); +} + +void QRuntimePixmapData::fromNativeType(void *pixmap, NativeType type) +{ + m_data->fromNativeType(pixmap, type); + readBackInfo(); +} +#endif + +QPixmapData* QRuntimePixmapData::runtimeData() const +{ + return m_data; +} + +QRuntimeWindowSurface::QRuntimeWindowSurface(const QRuntimeGraphicsSystem *gs, QWidget *window) + : QWindowSurface(window), m_graphicsSystem(gs) +{ + +} + +QRuntimeWindowSurface::~QRuntimeWindowSurface() +{ + if (QApplicationPrivate::graphics_system) + m_graphicsSystem->removeWindowSurface(this); +} + +QPaintDevice *QRuntimeWindowSurface::paintDevice() +{ + return m_windowSurface->paintDevice(); +} + +void QRuntimeWindowSurface::flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset) +{ + m_windowSurface->flush(widget, region, offset); + + int destroyPolicy = m_graphicsSystem->windowSurfaceDestroyPolicy(); + if(m_pendingWindowSurface && + destroyPolicy == QRuntimeGraphicsSystem::DestroyAfterFirstFlush) { +#ifdef QT_DEBUG + qDebug() << "QRuntimeWindowSurface::flush() - destroy pending window surface"; +#endif + m_pendingWindowSurface.reset(); + } +} + +void QRuntimeWindowSurface::setGeometry(const QRect &rect) +{ + QWindowSurface::setGeometry(rect); + m_windowSurface->setGeometry(rect); +} + +bool QRuntimeWindowSurface::scroll(const QRegion &area, int dx, int dy) +{ + return m_windowSurface->scroll(area, dx, dy); +} + +void QRuntimeWindowSurface::beginPaint(const QRegion &rgn) +{ + m_windowSurface->beginPaint(rgn); +} + +void QRuntimeWindowSurface::endPaint(const QRegion &rgn) +{ + m_windowSurface->endPaint(rgn); +} + +QImage* QRuntimeWindowSurface::buffer(const QWidget *widget) +{ + return m_windowSurface->buffer(widget); +} + +QPixmap QRuntimeWindowSurface::grabWidget(const QWidget *widget, const QRect& rectangle) const +{ + return m_windowSurface->grabWidget(widget, rectangle); +} + +QPoint QRuntimeWindowSurface::offset(const QWidget *widget) const +{ + return m_windowSurface->offset(widget); +} + +QWindowSurface::WindowSurfaceFeatures QRuntimeWindowSurface::features() const +{ + return m_windowSurface->features(); +} + +QRuntimeGraphicsSystem::QRuntimeGraphicsSystem() + : m_windowSurfaceDestroyPolicy(DestroyImmediately), + m_graphicsSystem(0) +{ + QApplicationPrivate::runtime_graphics_system = true; + +#ifdef QT_DEFAULT_RUNTIME_SYSTEM + m_graphicsSystemName = QLatin1String(QT_DEFAULT_RUNTIME_SYSTEM); + if (m_graphicsSystemName.isNull()) +#endif + m_graphicsSystemName = QLatin1String("raster"); + +#ifdef Q_OS_SYMBIAN + m_windowSurfaceDestroyPolicy = DestroyAfterFirstFlush; +#endif + + m_graphicsSystem = QGraphicsSystemFactory::create(m_graphicsSystemName); + + QApplicationPrivate::graphics_system_name = QLatin1String("runtime"); +} + + +QPixmapData *QRuntimeGraphicsSystem::createPixmapData(QPixmapData::PixelType type) const +{ + Q_ASSERT(m_graphicsSystem); + QPixmapData *data = m_graphicsSystem->createPixmapData(type); + + QRuntimePixmapData *rtData = new QRuntimePixmapData(this, type); + rtData->m_data = data; + m_pixmapDatas << rtData; + + return rtData; +} + +QWindowSurface *QRuntimeGraphicsSystem::createWindowSurface(QWidget *widget) const +{ + Q_ASSERT(m_graphicsSystem); + QRuntimeWindowSurface *rtSurface = new QRuntimeWindowSurface(this, widget); + rtSurface->m_windowSurface.reset(m_graphicsSystem->createWindowSurface(widget)); + widget->setWindowSurface(rtSurface); + m_windowSurfaces << rtSurface; + return rtSurface; +} + +void QRuntimeGraphicsSystem::setGraphicsSystem(const QString &name) +{ + if (m_graphicsSystemName == name) + return; +#ifdef QT_DEBUG + qDebug() << "QRuntimeGraphicsSystem::setGraphicsSystem( " << name << " )"; +#endif + QGraphicsSystem *oldSystem = m_graphicsSystem; + m_graphicsSystem = QGraphicsSystemFactory::create(name); + m_graphicsSystemName = name; + + Q_ASSERT(m_graphicsSystem); + + m_pendingGraphicsSystemName = QString(); + + for (int i = 0; i < m_pixmapDatas.size(); ++i) { + QRuntimePixmapData *proxy = m_pixmapDatas.at(i); + QPixmapData *newData = m_graphicsSystem->createPixmapData(proxy->m_data); + newData->fromImage(proxy->m_data->toImage(), Qt::NoOpaqueDetection); + delete proxy->m_data; + proxy->m_data = newData; + proxy->readBackInfo(); + } + + for (int i = 0; i < m_windowSurfaces.size(); ++i) { + QRuntimeWindowSurface *proxy = m_windowSurfaces.at(i); + QWidget *widget = proxy->m_windowSurface->window(); + + if(m_windowSurfaceDestroyPolicy == DestroyAfterFirstFlush) + proxy->m_pendingWindowSurface.reset(proxy->m_windowSurface.take()); + + QWindowSurface *newWindowSurface = m_graphicsSystem->createWindowSurface(widget); + newWindowSurface->setGeometry(proxy->geometry()); + + proxy->m_windowSurface.reset(newWindowSurface); + qt_widget_private(widget)->invalidateBuffer(widget->rect()); + } + + delete oldSystem; +} + +void QRuntimeGraphicsSystem::removePixmapData(QRuntimePixmapData *pixmapData) const +{ + int index = m_pixmapDatas.lastIndexOf(pixmapData); + m_pixmapDatas.removeAt(index); +} + +void QRuntimeGraphicsSystem::removeWindowSurface(QRuntimeWindowSurface *windowSurface) const +{ + int index = m_windowSurfaces.lastIndexOf(windowSurface); + m_windowSurfaces.removeAt(index); +} + +#include "qgraphicssystem_runtime.moc" + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystem_runtime_p.h b/src/gui/painting/qgraphicssystem_runtime_p.h new file mode 100644 index 0000000000..26d3777ad9 --- /dev/null +++ b/src/gui/painting/qgraphicssystem_runtime_p.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEM_RUNTIME_P_H +#define QGRAPHICSSYSTEM_RUNTIME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgraphicssystem_p.h" + +#include <private/qpixmapdata_p.h> + +QT_BEGIN_NAMESPACE + +class QRuntimeGraphicsSystem; + +class Q_GUI_EXPORT QRuntimePixmapData : public QPixmapData { +public: + QRuntimePixmapData(const QRuntimeGraphicsSystem *gs, PixelType type); + ~QRuntimePixmapData(); + + virtual QPixmapData *createCompatiblePixmapData() const; + virtual void resize(int width, int height); + virtual void fromImage(const QImage &image, + Qt::ImageConversionFlags flags); + + virtual bool fromFile(const QString &filename, const char *format, + Qt::ImageConversionFlags flags); + virtual bool fromData(const uchar *buffer, uint len, const char *format, + Qt::ImageConversionFlags flags); + + virtual void copy(const QPixmapData *data, const QRect &rect); + virtual bool scroll(int dx, int dy, const QRect &rect); + + virtual int metric(QPaintDevice::PaintDeviceMetric metric) const; + virtual void fill(const QColor &color); + virtual QBitmap mask() const; + virtual void setMask(const QBitmap &mask); + virtual bool hasAlphaChannel() const; + virtual QPixmap transformed(const QTransform &matrix, + Qt::TransformationMode mode) const; + virtual void setAlphaChannel(const QPixmap &alphaChannel); + virtual QPixmap alphaChannel() const; + virtual QImage toImage() const; + virtual QPaintEngine *paintEngine() const; + + virtual QImage *buffer(); + + void readBackInfo(); + + QPixmapData *m_data; + +#if defined(Q_OS_SYMBIAN) + void* toNativeType(NativeType type); + void fromNativeType(void* pixmap, NativeType type); +#endif + + virtual QPixmapData *runtimeData() const; + +private: + const QRuntimeGraphicsSystem *m_graphicsSystem; + +}; + +class QRuntimeWindowSurface : public QWindowSurface { +public: + QRuntimeWindowSurface(const QRuntimeGraphicsSystem *gs, QWidget *window); + ~QRuntimeWindowSurface(); + + virtual QPaintDevice *paintDevice(); + virtual void flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset); + virtual void setGeometry(const QRect &rect); + + virtual bool scroll(const QRegion &area, int dx, int dy); + + virtual void beginPaint(const QRegion &); + virtual void endPaint(const QRegion &); + + virtual QImage* buffer(const QWidget *widget); + virtual QPixmap grabWidget(const QWidget *widget, const QRect& rectangle = QRect()) const; + + virtual QPoint offset(const QWidget *widget) const; + + virtual WindowSurfaceFeatures features() const; + + QScopedPointer<QWindowSurface> m_windowSurface; + QScopedPointer<QWindowSurface> m_pendingWindowSurface; + +private: + const QRuntimeGraphicsSystem *m_graphicsSystem; +}; + +class QRuntimeGraphicsSystem : public QGraphicsSystem +{ +public: + + enum WindowSurfaceDestroyPolicy + { + DestroyImmediately, + DestroyAfterFirstFlush + }; + +public: + QRuntimeGraphicsSystem(); + + QPixmapData *createPixmapData(QPixmapData::PixelType type) const; + QWindowSurface *createWindowSurface(QWidget *widget) const; + + void removePixmapData(QRuntimePixmapData *pixmapData) const; + void removeWindowSurface(QRuntimeWindowSurface *windowSurface) const; + + void setGraphicsSystem(const QString &name); + QString graphicsSystemName() const { return m_graphicsSystemName; } + + void setWindowSurfaceDestroyPolicy(WindowSurfaceDestroyPolicy policy) + { + m_windowSurfaceDestroyPolicy = policy; + } + + int windowSurfaceDestroyPolicy() const { return m_windowSurfaceDestroyPolicy; } + + +private: + int m_windowSurfaceDestroyPolicy; + QGraphicsSystem *m_graphicsSystem; + mutable QList<QRuntimePixmapData *> m_pixmapDatas; + mutable QList<QRuntimeWindowSurface *> m_windowSurfaces; + QString m_graphicsSystemName; + + QString m_pendingGraphicsSystemName; + + friend class QRuntimePixmapData; + friend class QRuntimeWindowSurface; + friend class QMeeGoGraphicsSystem; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qgraphicssystemfactory.cpp b/src/gui/painting/qgraphicssystemfactory.cpp new file mode 100644 index 0000000000..62a60d77c0 --- /dev/null +++ b/src/gui/painting/qgraphicssystemfactory.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicssystemfactory_p.h" +#include "qgraphicssystemplugin_p.h" +#include "private/qfactoryloader_p.h" +#include "qmutex.h" + +#include "qapplication.h" +#include <private/qapplication_p.h> +#include "qgraphicssystem_raster_p.h" +#include "qgraphicssystem_runtime_p.h" +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QGraphicsSystemFactoryInterface_iid, QLatin1String("/graphicssystems"), Qt::CaseInsensitive)) +#endif + +QGraphicsSystem *QGraphicsSystemFactory::create(const QString& key) +{ + QGraphicsSystem *ret = 0; + QString system = key.toLower(); + +#if defined (QT_GRAPHICSSYSTEM_OPENGL) + if (system.isEmpty()) { + system = QLatin1String("opengl"); + } +#elif defined (QT_GRAPHICSSYSTEM_OPENVG) + if (system.isEmpty()) { + system = QLatin1String("openvg"); + } +#elif defined (QT_GRAPHICSSYSTEM_RUNTIME) + if (system.isEmpty()) { + system = QLatin1String("runtime"); + } +#elif defined (QT_GRAPHICSSYSTEM_RASTER) && !defined(Q_WS_WIN) && !defined(Q_OS_SYMBIAN) || defined(Q_WS_X11) + if (system.isEmpty()) { + system = QLatin1String("raster"); + } +#endif + + QApplicationPrivate::graphics_system_name = system; + if (system == QLatin1String("raster")) + return new QRasterGraphicsSystem; + else if (system == QLatin1String("runtime")) + return new QRuntimeGraphicsSystem; + else if (system.isEmpty() || system == QLatin1String("native")) + return 0; + +#ifndef QT_NO_LIBRARY + if (!ret) { + if (QGraphicsSystemFactoryInterface *factory = qobject_cast<QGraphicsSystemFactoryInterface*>(loader()->instance(system))) + ret = factory->create(system); + } +#endif + + if (!ret) + qWarning() << "Unable to load graphicssystem" << system; + + return ret; +} + +/*! + Returns the list of valid keys, i.e. the keys this factory can + create styles for. + + \sa create() +*/ +QStringList QGraphicsSystemFactory::keys() +{ +#ifndef QT_NO_LIBRARY + QStringList list = loader()->keys(); +#else + QStringList list; +#endif + if (!list.contains(QLatin1String("Raster"))) + list << QLatin1String("raster"); + return list; +} + +QT_END_NAMESPACE + diff --git a/src/gui/painting/qgraphicssystemfactory_p.h b/src/gui/painting/qgraphicssystemfactory_p.h new file mode 100644 index 0000000000..8a2e2b14e0 --- /dev/null +++ b/src/gui/painting/qgraphicssystemfactory_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEMFACTORY_H +#define QGRAPHICSSYSTEMFACTORY_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/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsSystem; + +class QGraphicsSystemFactory +{ +public: + static QStringList keys(); + static QGraphicsSystem *create(const QString&); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSYSTEMFACTORY_H + diff --git a/src/gui/painting/qgraphicssystemplugin.cpp b/src/gui/painting/qgraphicssystemplugin.cpp new file mode 100644 index 0000000000..8eb1278373 --- /dev/null +++ b/src/gui/painting/qgraphicssystemplugin.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicssystemplugin_p.h" +#include "qgraphicssystem_p.h" + +QT_BEGIN_NAMESPACE + +QGraphicsSystemPlugin::QGraphicsSystemPlugin(QObject *parent) + : QObject(parent) +{ +} + +QGraphicsSystemPlugin::~QGraphicsSystemPlugin() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qgraphicssystemplugin_p.h b/src/gui/painting/qgraphicssystemplugin_p.h new file mode 100644 index 0000000000..1dafe82243 --- /dev/null +++ b/src/gui/painting/qgraphicssystemplugin_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSSYSTEMPLUGIN_H +#define QGRAPHICSSYSTEMPLUGIN_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/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGraphicsSystem; + +struct QGraphicsSystemFactoryInterface : public QFactoryInterface +{ + virtual QGraphicsSystem *create(const QString &key) = 0; +}; + +#define QGraphicsSystemFactoryInterface_iid "com.trolltech.Qt.QGraphicsSystemFactoryInterface" + +Q_DECLARE_INTERFACE(QGraphicsSystemFactoryInterface, QGraphicsSystemFactoryInterface_iid) + +class Q_GUI_EXPORT QGraphicsSystemPlugin : public QObject, public QGraphicsSystemFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QGraphicsSystemFactoryInterface:QFactoryInterface) +public: + explicit QGraphicsSystemPlugin(QObject *parent = 0); + ~QGraphicsSystemPlugin(); + + virtual QStringList keys() const = 0; + virtual QGraphicsSystem *create(const QString &key) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGRAPHICSSYSTEMEPLUGIN_H diff --git a/src/gui/painting/qgrayraster.c b/src/gui/painting/qgrayraster.c new file mode 100644 index 0000000000..e5e8ba4fa1 --- /dev/null +++ b/src/gui/painting/qgrayraster.c @@ -0,0 +1,1942 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/***************************************************************************/ +/* */ +/* qgrayraster.c, derived from ftgrays.c */ +/* */ +/* A new `perfect' anti-aliasing renderer (body). */ +/* */ +/* Copyright 2000-2001, 2002, 2003 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, ../../3rdparty/freetype/docs/FTL.TXT. By continuing to use, */ +/* modify, or distribute this file you indicate that you have read */ +/* the license and understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* This file can be compiled without the rest of the FreeType engine, by */ + /* defining the _STANDALONE_ macro when compiling it. You also need to */ + /* put the files `ftgrays.h' and `ftimage.h' into the current */ + /* compilation directory. Typically, you could do something like */ + /* */ + /* - copy `src/smooth/ftgrays.c' (this file) to your current directory */ + /* */ + /* - copy `include/freetype/ftimage.h' and `src/smooth/ftgrays.h' to the */ + /* same directory */ + /* */ + /* - compile `ftgrays' with the _STANDALONE_ macro defined, as in */ + /* */ + /* cc -c -D_STANDALONE_ ftgrays.c */ + /* */ + /* The renderer can be initialized with a call to */ + /* `qt_ft_gray_raster.raster_new'; an anti-aliased bitmap can be generated */ + /* with a call to `qt_ft_gray_raster.raster_render'. */ + /* */ + /* See the comments and documentation in the file `ftimage.h' for more */ + /* details on how the raster works. */ + /* */ + /*************************************************************************/ + + /*************************************************************************/ + /* */ + /* This is a new anti-aliasing scan-converter for FreeType 2. The */ + /* algorithm used here is _very_ different from the one in the standard */ + /* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ + /* coverage of the outline on each pixel cell. */ + /* */ + /* It is based on ideas that I initially found in Raph Levien's */ + /* excellent LibArt graphics library (see http://www.levien.com/libart */ + /* for more information, though the web pages do not tell anything */ + /* about the renderer; you'll have to dive into the source code to */ + /* understand how it works). */ + /* */ + /* Note, however, that this is a _very_ different implementation */ + /* compared to Raph's. Coverage information is stored in a very */ + /* different way, and I don't use sorted vector paths. Also, it doesn't */ + /* use floating point values. */ + /* */ + /* This renderer has the following advantages: */ + /* */ + /* - It doesn't need an intermediate bitmap. Instead, one can supply a */ + /* callback function that will be called by the renderer to draw gray */ + /* spans on any target surface. You can thus do direct composition on */ + /* any kind of bitmap, provided that you give the renderer the right */ + /* callback. */ + /* */ + /* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ + /* each pixel cell. */ + /* */ + /* - It performs a single pass on the outline (the `standard' FT2 */ + /* renderer makes two passes). */ + /* */ + /* - It can easily be modified to render to _any_ number of gray levels */ + /* cheaply. */ + /* */ + /* - For small (< 20) pixel sizes, it is faster than the standard */ + /* renderer. */ + /* */ + /*************************************************************************/ + +/* experimental support for gamma correction within the rasterizer */ +#define xxxGRAYS_USE_GAMMA + + + /*************************************************************************/ + /* */ + /* The macro QT_FT_COMPONENT is used in trace mode. It is an implicit */ + /* parameter of the QT_FT_TRACE() and QT_FT_ERROR() macros, used to print/log */ + /* messages during execution. */ + /* */ +#undef QT_FT_COMPONENT +#define QT_FT_COMPONENT trace_smooth + + +#define ErrRaster_MemoryOverflow -4 + +#if defined(VXWORKS) +# include <vxWorksCommon.h> /* needed for setjmp.h */ +#endif +#include <string.h> /* for qt_ft_memcpy() */ +#include <setjmp.h> +#include <limits.h> + +#define QT_FT_UINT_MAX UINT_MAX + +#define qt_ft_memset memset + +#define qt_ft_setjmp setjmp +#define qt_ft_longjmp longjmp +#define qt_ft_jmp_buf jmp_buf + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 +#define ErrRaster_OutOfMemory -6 + +#define QT_FT_BEGIN_HEADER +#define QT_FT_END_HEADER + +#include <private/qrasterdefs_p.h> +#include <private/qgrayraster_p.h> + +#include <stdlib.h> +#include <stdio.h> + + /* This macro is used to indicate that a function parameter is unused. */ + /* Its purpose is simply to reduce compiler warnings. Note also that */ + /* simply defining it as `(void)x' doesn't avoid warnings with certain */ + /* ANSI compilers (e.g. LCC). */ +#define QT_FT_UNUSED( x ) (x) = (x) + + /* Disable the tracing mechanism for simplicity -- developers can */ + /* activate it easily by redefining these two macros. */ +#ifndef QT_FT_ERROR +#define QT_FT_ERROR( x ) do ; while ( 0 ) /* nothing */ +#endif + +#ifndef QT_FT_TRACE +#define QT_FT_TRACE( x ) do ; while ( 0 ) /* nothing */ +#endif + +#ifndef QT_FT_MEM_SET +#define QT_FT_MEM_SET( d, s, c ) qt_ft_memset( d, s, c ) +#endif + +#ifndef QT_FT_MEM_ZERO +#define QT_FT_MEM_ZERO( dest, count ) QT_FT_MEM_SET( dest, 0, count ) +#endif + + /* define this to dump debugging information */ +#define xxxDEBUG_GRAYS + + +#define RAS_ARG PWorker worker +#define RAS_ARG_ PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#define ras (*worker) + + + /* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#define ONE_PIXEL ( 1L << PIXEL_BITS ) +#define PIXEL_MASK ( -1L << PIXEL_BITS ) +#define TRUNC( x ) ( (TCoord)( (x) >> PIXEL_BITS ) ) +#define SUBPIXELS( x ) ( (TPos)(x) << PIXEL_BITS ) +#define FLOOR( x ) ( (x) & -ONE_PIXEL ) +#define CEILING( x ) ( ( (x) + ONE_PIXEL - 1 ) & -ONE_PIXEL ) +#define ROUND( x ) ( ( (x) + ONE_PIXEL / 2 ) & -ONE_PIXEL ) + +#if PIXEL_BITS >= 6 +#define UPSCALE( x ) ( (x) << ( PIXEL_BITS - 6 ) ) +#define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) ) +#else +#define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) ) +#define DOWNSCALE( x ) ( (x) << ( 6 - PIXEL_BITS ) ) +#endif + + /*************************************************************************/ + /* */ + /* TYPE DEFINITIONS */ + /* */ + + /* don't change the following types to QT_FT_Int or QT_FT_Pos, since we might */ + /* need to define them to "float" or "double" when experimenting with */ + /* new algorithms */ + + typedef int TCoord; /* integer scanline/pixel coordinate */ + typedef int TPos; /* sub-pixel coordinate */ + + /* determine the type used to store cell areas. This normally takes at */ + /* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ + /* `long' instead of `int', otherwise bad things happen */ + +#if PIXEL_BITS <= 7 + + typedef int TArea; + +#else /* PIXEL_BITS >= 8 */ + + /* approximately determine the size of integers using an ANSI-C header */ +#if QT_FT_UINT_MAX == 0xFFFFU + typedef long TArea; +#else + typedef int TArea; +#endif + +#endif /* PIXEL_BITS >= 8 */ + + + /* maximal number of gray spans in a call to the span callback */ +#define QT_FT_MAX_GRAY_SPANS 256 + + + typedef struct TCell_* PCell; + + typedef struct TCell_ + { + int x; + int cover; + TArea area; + PCell next; + + } TCell; + + + typedef struct TWorker_ + { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + int cover; + int invalid; + + PCell cells; + int max_cells; + int num_cells; + + TCoord cx, cy; + TPos x, y; + + TPos last_ey; + + QT_FT_Vector bez_stack[32 * 3 + 1]; + int lev_stack[32]; + + QT_FT_Outline outline; + QT_FT_Bitmap target; + QT_FT_BBox clip_box; + + QT_FT_Span gray_spans[QT_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + + QT_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + int conic_level; + int cubic_level; + + qt_ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + int ycount; + + int skip_spans; + } TWorker, *PWorker; + + + typedef struct TRaster_ + { + void* buffer; + long buffer_size; + long buffer_allocated_size; + int band_size; + void* memory; + PWorker worker; + + } TRaster, *PRaster; + + int q_gray_rendered_spans(TRaster *raster) + { + if ( raster && raster->worker ) + return raster->worker->skip_spans > 0 ? 0 : -raster->worker->skip_spans; + return 0; + } + + /*************************************************************************/ + /* */ + /* Initialize the cells table. */ + /* */ + static void + gray_init_cells( RAS_ARG_ void* buffer, + long byte_size ) + { + ras.buffer = buffer; + ras.buffer_size = byte_size; + + ras.ycells = (PCell*) buffer; + ras.cells = NULL; + ras.max_cells = 0; + ras.num_cells = 0; + ras.area = 0; + ras.cover = 0; + ras.invalid = 1; + } + + + /*************************************************************************/ + /* */ + /* Compute the outline bounding box. */ + /* */ + static void + gray_compute_cbox( RAS_ARG ) + { + QT_FT_Outline* outline = &ras.outline; + QT_FT_Vector* vec = outline->points; + QT_FT_Vector* limit = vec + outline->n_points; + + + if ( outline->n_points <= 0 ) + { + ras.min_ex = ras.max_ex = 0; + ras.min_ey = ras.max_ey = 0; + return; + } + + ras.min_ex = ras.max_ex = vec->x; + ras.min_ey = ras.max_ey = vec->y; + + vec++; + + for ( ; vec < limit; vec++ ) + { + TPos x = vec->x; + TPos y = vec->y; + + + if ( x < ras.min_ex ) ras.min_ex = x; + if ( x > ras.max_ex ) ras.max_ex = x; + if ( y < ras.min_ey ) ras.min_ey = y; + if ( y > ras.max_ey ) ras.max_ey = y; + } + + /* truncate the bounding box to integer pixels */ + ras.min_ex = ras.min_ex >> 6; + ras.min_ey = ras.min_ey >> 6; + ras.max_ex = ( ras.max_ex + 63 ) >> 6; + ras.max_ey = ( ras.max_ey + 63 ) >> 6; + } + + + /*************************************************************************/ + /* */ + /* Record the current cell in the table. */ + /* */ + static void + gray_record_cell( RAS_ARG ) + { + PCell *pcell, cell; + int x = ras.ex; + + if ( ras.invalid || !( ras.area | ras.cover ) ) + return; + + if ( x > ras.max_ex ) + x = ras.max_ex; + + pcell = &ras.ycells[ras.ey]; + + for (;;) + { + cell = *pcell; + if ( cell == NULL || cell->x > x ) + break; + + if ( cell->x == x ) { + cell->area += ras.area; + cell->cover += ras.cover; + return; + } + + pcell = &cell->next; + } + + if ( ras.num_cells >= ras.max_cells ) + qt_ft_longjmp( ras.jump_buffer, 1 ); + + cell = ras.cells + ras.num_cells++; + cell->x = x; + cell->area = ras.area; + cell->cover = ras.cover; + + cell->next = *pcell; + *pcell = cell; + } + + + /*************************************************************************/ + /* */ + /* Set the current cell to a new position. */ + /* */ + static void + gray_set_cell( RAS_ARG_ TCoord ex, + TCoord ey ) + { + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if ( ex > ras.max_ex ) + ex = ras.max_ex; + + ex -= ras.min_ex; + if ( ex < 0 ) + ex = -1; + + /* are we moving to a different cell ? */ + if ( ex != ras.ex || ey != ras.ey ) + { + /* record the current one if it is valid */ + if ( !ras.invalid ) + gray_record_cell( RAS_VAR ); + + ras.area = 0; + ras.cover = 0; + } + + ras.ex = ex; + ras.ey = ey; + ras.invalid = ( (unsigned)ey >= (unsigned)ras.count_ey || + ex >= ras.count_ex ); + } + + + /*************************************************************************/ + /* */ + /* Start a new contour at a given cell. */ + /* */ + static void + gray_start_cell( RAS_ARG_ TCoord ex, + TCoord ey ) + { + if ( ex > ras.max_ex ) + ex = (TCoord)( ras.max_ex ); + + if ( ex < ras.min_ex ) + ex = (TCoord)( ras.min_ex - 1 ); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex - ras.min_ex; + ras.ey = ey - ras.min_ey; + ras.last_ey = SUBPIXELS( ey ); + ras.invalid = 0; + + gray_set_cell( RAS_VAR_ ex, ey ); + } + + + /*************************************************************************/ + /* */ + /* Render a scanline as one or more cells. */ + /* */ + static void + gray_render_scanline( RAS_ARG_ TCoord ey, + TPos x1, + TCoord y1, + TPos x2, + TCoord y2 ) + { + TCoord ex1, ex2, fx1, fx2, delta; + int p, first, dx; + int incr, lift, mod, rem; + + + dx = x2 - x1; + + ex1 = TRUNC( x1 ); + ex2 = TRUNC( x2 ); + fx1 = (TCoord)( x1 - SUBPIXELS( ex1 ) ); + fx2 = (TCoord)( x2 - SUBPIXELS( ex2 ) ); + + /* trivial case. Happens often */ + if ( y1 == y2 ) + { + gray_set_cell( RAS_VAR_ ex2, ey ); + return; + } + + /* everything is located in a single cell. That is easy! */ + /* */ + if ( ex1 == ex2 ) + { + delta = y2 - y1; + ras.area += (TArea)( fx1 + fx2 ) * delta; + ras.cover += delta; + return; + } + + /* ok, we'll have to render a run of adjacent cells on the same */ + /* scanline... */ + /* */ + p = ( ONE_PIXEL - fx1 ) * ( y2 - y1 ); + first = ONE_PIXEL; + incr = 1; + + if ( dx < 0 ) + { + p = fx1 * ( y2 - y1 ); + first = 0; + incr = -1; + dx = -dx; + } + + delta = (TCoord)( p / dx ); + mod = (TCoord)( p % dx ); + if ( mod < 0 ) + { + delta--; + mod += (TCoord)dx; + } + + ras.area += (TArea)( fx1 + first ) * delta; + ras.cover += delta; + + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + y1 += delta; + + if ( ex1 != ex2 ) + { + p = ONE_PIXEL * ( y2 - y1 + delta ); + lift = (TCoord)( p / dx ); + rem = (TCoord)( p % dx ); + if ( rem < 0 ) + { + lift--; + rem += (TCoord)dx; + } + + mod -= (int)dx; + + while ( ex1 != ex2 ) + { + delta = lift; + mod += rem; + if ( mod >= 0 ) + { + mod -= (TCoord)dx; + delta++; + } + + ras.area += (TArea)ONE_PIXEL * delta; + ras.cover += delta; + y1 += delta; + ex1 += incr; + gray_set_cell( RAS_VAR_ ex1, ey ); + } + } + + delta = y2 - y1; + ras.area += (TArea)( fx2 + ONE_PIXEL - first ) * delta; + ras.cover += delta; + } + + + /*************************************************************************/ + /* */ + /* Render a given line as a series of scanlines. */ + /* */ + static void + gray_render_line( RAS_ARG_ TPos to_x, + TPos to_y ) + { + TCoord ey1, ey2, fy1, fy2; + TPos dx, dy, x, x2; + int p, first; + int delta, rem, mod, lift, incr; + + + ey1 = TRUNC( ras.last_ey ); + ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ + fy1 = (TCoord)( ras.y - ras.last_ey ); + fy2 = (TCoord)( to_y - SUBPIXELS( ey2 ) ); + + dx = to_x - ras.x; + dy = to_y - ras.y; + + /* XXX: we should do something about the trivial case where dx == 0, */ + /* as it happens very often! */ + + /* perform vertical clipping */ + { + TCoord min, max; + + + min = ey1; + max = ey2; + if ( ey1 > ey2 ) + { + min = ey2; + max = ey1; + } + if ( min >= ras.max_ey || max < ras.min_ey ) + goto End; + } + + /* everything is on a single scanline */ + if ( ey1 == ey2 ) + { + gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2 ); + goto End; + } + + /* vertical line - avoid calling gray_render_scanline */ + incr = 1; + + if ( dx == 0 ) + { + TCoord ex = TRUNC( ras.x ); + TCoord two_fx = (TCoord)( ( ras.x - SUBPIXELS( ex ) ) << 1 ); + TPos area; + + + first = ONE_PIXEL; + if ( dy < 0 ) + { + first = 0; + incr = -1; + } + + delta = (int)( first - fy1 ); + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + ey1 += incr; + + gray_set_cell( &ras, ex, ey1 ); + + delta = (int)( first + first - ONE_PIXEL ); + area = (TArea)two_fx * delta; + while ( ey1 != ey2 ) + { + ras.area += area; + ras.cover += delta; + ey1 += incr; + + gray_set_cell( &ras, ex, ey1 ); + } + + delta = (int)( fy2 - ONE_PIXEL + first ); + ras.area += (TArea)two_fx * delta; + ras.cover += delta; + + goto End; + } + + /* ok, we have to render several scanlines */ + p = ( ONE_PIXEL - fy1 ) * dx; + first = ONE_PIXEL; + incr = 1; + + if ( dy < 0 ) + { + p = fy1 * dx; + first = 0; + incr = -1; + dy = -dy; + } + + delta = (int)( p / dy ); + mod = (int)( p % dy ); + if ( mod < 0 ) + { + delta--; + mod += (TCoord)dy; + } + + x = ras.x + delta; + gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, (TCoord)first ); + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + + if ( ey1 != ey2 ) + { + p = ONE_PIXEL * dx; + lift = (int)( p / dy ); + rem = (int)( p % dy ); + if ( rem < 0 ) + { + lift--; + rem += (int)dy; + } + mod -= (int)dy; + + while ( ey1 != ey2 ) + { + delta = lift; + mod += rem; + if ( mod >= 0 ) + { + mod -= (int)dy; + delta++; + } + + x2 = x + delta; + gray_render_scanline( RAS_VAR_ ey1, x, + (TCoord)( ONE_PIXEL - first ), x2, + (TCoord)first ); + x = x2; + + ey1 += incr; + gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); + } + } + + gray_render_scanline( RAS_VAR_ ey1, x, + (TCoord)( ONE_PIXEL - first ), to_x, + fy2 ); + + End: + ras.x = to_x; + ras.y = to_y; + ras.last_ey = SUBPIXELS( ey2 ); + } + + + static void + gray_split_conic( QT_FT_Vector* base ) + { + TPos a, b; + + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = ( base[2].x + b ) / 2; + b = base[1].x = ( base[0].x + b ) / 2; + base[2].x = ( a + b ) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = ( base[2].y + b ) / 2; + b = base[1].y = ( base[0].y + b ) / 2; + base[2].y = ( a + b ) / 2; + } + + + static void + gray_render_conic( RAS_ARG_ const QT_FT_Vector* control, + const QT_FT_Vector* to ) + { + TPos dx, dy; + int top, level; + int* levels; + QT_FT_Vector* arc; + + + dx = DOWNSCALE( ras.x ) + to->x - ( control->x << 1 ); + if ( dx < 0 ) + dx = -dx; + dy = DOWNSCALE( ras.y ) + to->y - ( control->y << 1 ); + if ( dy < 0 ) + dy = -dy; + if ( dx < dy ) + dx = dy; + + level = 1; + dx = dx / ras.conic_level; + while ( dx > 0 ) + { + dx >>= 2; + level++; + } + + /* a shortcut to speed things up */ + if ( level <= 1 ) + { + /* we compute the mid-point directly in order to avoid */ + /* calling gray_split_conic() */ + TPos to_x, to_y, mid_x, mid_y; + + + to_x = UPSCALE( to->x ); + to_y = UPSCALE( to->y ); + mid_x = ( ras.x + to_x + 2 * UPSCALE( control->x ) ) / 4; + mid_y = ( ras.y + to_y + 2 * UPSCALE( control->y ) ) / 4; + + gray_render_line( RAS_VAR_ mid_x, mid_y ); + gray_render_line( RAS_VAR_ to_x, to_y ); + + return; + } + + arc = ras.bez_stack; + levels = ras.lev_stack; + top = 0; + levels[0] = level; + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control->x ); + arc[1].y = UPSCALE( control->y ); + arc[2].x = ras.x; + arc[2].y = ras.y; + + while ( top >= 0 ) + { + level = levels[top]; + if ( level > 1 ) + { + /* check that the arc crosses the current band */ + TPos min, max, y; + + + min = max = arc[0].y; + + y = arc[1].y; + if ( y < min ) min = y; + if ( y > max ) max = y; + + y = arc[2].y; + if ( y < min ) min = y; + if ( y > max ) max = y; + + if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < ras.min_ey ) + goto Draw; + + gray_split_conic( arc ); + arc += 2; + top++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + { + TPos to_x, to_y, mid_x, mid_y; + + + to_x = arc[0].x; + to_y = arc[0].y; + mid_x = ( ras.x + to_x + 2 * arc[1].x ) / 4; + mid_y = ( ras.y + to_y + 2 * arc[1].y ) / 4; + + gray_render_line( RAS_VAR_ mid_x, mid_y ); + gray_render_line( RAS_VAR_ to_x, to_y ); + + top--; + arc -= 2; + } + } + + return; + } + + + static void + gray_split_cubic( QT_FT_Vector* base ) + { + TPos a, b, c, d; + + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) / 2; + base[5].x = b = ( base[3].x + d ) / 2; + c = ( c + d ) / 2; + base[2].x = a = ( a + c ) / 2; + base[4].x = b = ( b + c ) / 2; + base[3].x = ( a + b ) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) / 2; + base[5].y = b = ( base[3].y + d ) / 2; + c = ( c + d ) / 2; + base[2].y = a = ( a + c ) / 2; + base[4].y = b = ( b + c ) / 2; + base[3].y = ( a + b ) / 2; + } + + + static void + gray_render_cubic( RAS_ARG_ const QT_FT_Vector* control1, + const QT_FT_Vector* control2, + const QT_FT_Vector* to ) + { + TPos dx, dy, da, db; + int top, level; + int* levels; + QT_FT_Vector* arc; + + + dx = DOWNSCALE( ras.x ) + to->x - ( control1->x << 1 ); + if ( dx < 0 ) + dx = -dx; + dy = DOWNSCALE( ras.y ) + to->y - ( control1->y << 1 ); + if ( dy < 0 ) + dy = -dy; + if ( dx < dy ) + dx = dy; + da = dx; + + dx = DOWNSCALE( ras.x ) + to->x - 3 * ( control1->x + control2->x ); + if ( dx < 0 ) + dx = -dx; + dy = DOWNSCALE( ras.y ) + to->y - 3 * ( control1->y + control2->y ); + if ( dy < 0 ) + dy = -dy; + if ( dx < dy ) + dx = dy; + db = dx; + + level = 1; + da = da / ras.cubic_level; + db = db / ras.conic_level; + while ( da > 0 || db > 0 ) + { + da >>= 2; + db >>= 3; + level++; + } + + if ( level <= 1 ) + { + TPos to_x, to_y, mid_x, mid_y; + + + to_x = UPSCALE( to->x ); + to_y = UPSCALE( to->y ); + mid_x = ( ras.x + to_x + + 3 * UPSCALE( control1->x + control2->x ) ) / 8; + mid_y = ( ras.y + to_y + + 3 * UPSCALE( control1->y + control2->y ) ) / 8; + + gray_render_line( RAS_VAR_ mid_x, mid_y ); + gray_render_line( RAS_VAR_ to_x, to_y ); + return; + } + + arc = ras.bez_stack; + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control2->x ); + arc[1].y = UPSCALE( control2->y ); + arc[2].x = UPSCALE( control1->x ); + arc[2].y = UPSCALE( control1->y ); + arc[3].x = ras.x; + arc[3].y = ras.y; + + levels = ras.lev_stack; + top = 0; + levels[0] = level; + + while ( top >= 0 ) + { + level = levels[top]; + if ( level > 1 ) + { + /* check that the arc crosses the current band */ + TPos min, max, y; + + + min = max = arc[0].y; + y = arc[1].y; + if ( y < min ) min = y; + if ( y > max ) max = y; + y = arc[2].y; + if ( y < min ) min = y; + if ( y > max ) max = y; + y = arc[3].y; + if ( y < min ) min = y; + if ( y > max ) max = y; + if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < 0 ) + goto Draw; + gray_split_cubic( arc ); + arc += 3; + top ++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + { + TPos to_x, to_y, mid_x, mid_y; + + + to_x = arc[0].x; + to_y = arc[0].y; + mid_x = ( ras.x + to_x + 3 * ( arc[1].x + arc[2].x ) ) / 8; + mid_y = ( ras.y + to_y + 3 * ( arc[1].y + arc[2].y ) ) / 8; + + gray_render_line( RAS_VAR_ mid_x, mid_y ); + gray_render_line( RAS_VAR_ to_x, to_y ); + top --; + arc -= 3; + } + } + + return; + } + + + + static int + gray_move_to( const QT_FT_Vector* to, + PWorker worker ) + { + TPos x, y; + + + /* record current cell, if any */ + gray_record_cell( worker ); + + /* start to a new position */ + x = UPSCALE( to->x ); + y = UPSCALE( to->y ); + + gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); + + worker->x = x; + worker->y = y; + return 0; + } + + + static int + gray_line_to( const QT_FT_Vector* to, + PWorker worker ) + { + gray_render_line( worker, UPSCALE( to->x ), UPSCALE( to->y ) ); + return 0; + } + + + static int + gray_conic_to( const QT_FT_Vector* control, + const QT_FT_Vector* to, + PWorker worker ) + { + gray_render_conic( worker, control, to ); + return 0; + } + + + static int + gray_cubic_to( const QT_FT_Vector* control1, + const QT_FT_Vector* control2, + const QT_FT_Vector* to, + PWorker worker ) + { + gray_render_cubic( worker, control1, control2, to ); + return 0; + } + + + static void + gray_render_span( int count, + const QT_FT_Span* spans, + PWorker worker ) + { + unsigned char* p; + QT_FT_Bitmap* map = &worker->target; + + for ( ; count > 0; count--, spans++ ) + { + unsigned char coverage = spans->coverage; + + /* first of all, compute the scanline offset */ + p = (unsigned char*)map->buffer - spans->y * map->pitch; + if ( map->pitch >= 0 ) + p += ( map->rows - 1 ) * map->pitch; + + + if ( coverage ) + { + /* For small-spans it is faster to do it by ourselves than + * calling `memset'. This is mainly due to the cost of the + * function call. + */ + if ( spans->len >= 8 ) + QT_FT_MEM_SET( p + spans->x, (unsigned char)coverage, spans->len ); + else + { + unsigned char* q = p + spans->x; + + + switch ( spans->len ) + { + case 7: *q++ = (unsigned char)coverage; + case 6: *q++ = (unsigned char)coverage; + case 5: *q++ = (unsigned char)coverage; + case 4: *q++ = (unsigned char)coverage; + case 3: *q++ = (unsigned char)coverage; + case 2: *q++ = (unsigned char)coverage; + case 1: *q = (unsigned char)coverage; + default: + ; + } + } + } + } + } + + + static void + gray_hline( RAS_ARG_ TCoord x, + TCoord y, + TPos area, + int acount ) + { + QT_FT_Span* span; + int coverage; + int skip; + + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)( area >> ( PIXEL_BITS * 2 + 1 - 8 ) ); + /* use range 0..256 */ + if ( coverage < 0 ) + coverage = -coverage; + + if ( ras.outline.flags & QT_FT_OUTLINE_EVEN_ODD_FILL ) + { + coverage &= 511; + + if ( coverage > 256 ) + coverage = 512 - coverage; + else if ( coverage == 256 ) + coverage = 255; + } + else + { + /* normal non-zero winding rule */ + if ( coverage >= 256 ) + coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* QT_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ + if ( x >= 32768 ) + x = 32767; + + if ( coverage ) + { + /* see whether we can add this span to the current list */ + span = ras.gray_spans + ras.num_gray_spans - 1; + if ( ras.num_gray_spans > 0 && + span->y == y && + (int)span->x + span->len == (int)x && + span->coverage == coverage ) + { + span->len = (unsigned short)( span->len + acount ); + return; + } + + if ( ras.num_gray_spans >= QT_FT_MAX_GRAY_SPANS ) + { + if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + + /* ras.render_span( span->y, ras.gray_spans, count ); */ + +#ifdef DEBUG_GRAYS + + if ( 1 ) + { + int n; + + + fprintf( stderr, "y=%3d ", y ); + span = ras.gray_spans; + for ( n = 0; n < count; n++, span++ ) + fprintf( stderr, "[%d..%d]:%02x ", + span->x, span->x + span->len - 1, span->coverage ); + fprintf( stderr, "\n" ); + } + +#endif /* DEBUG_GRAYS */ + + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } + else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->len = (unsigned short)acount; + span->y = (short)y; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } + } + + +#ifdef DEBUG_GRAYS + + /* to be called while in the debugger */ + gray_dump_cells( RAS_ARG ) + { + int yindex; + + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + { + PCell cell; + + + printf( "%3d:", yindex ); + + for ( cell = ras.ycells[yindex]; cell != NULL; cell = cell->next ) + printf( " (%3d, c:%4d, a:%6d)", cell->x, cell->cover, cell->area ); + printf( "\n" ); + } + } + +#endif /* DEBUG_GRAYS */ + + + static void + gray_sweep( RAS_ARG_ const QT_FT_Bitmap* target ) + { + int yindex; + + QT_FT_UNUSED( target ); + + + if ( ras.num_cells == 0 ) + return; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + + for ( ; cell != NULL; cell = cell->next ) + { + TArea area; + + + if ( cell->x > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + cell->x - x ); + + cover += cell->cover; + area = cover * ( ONE_PIXEL * 2 ) - cell->area; + + if ( area != 0 && cell->x >= 0 ) + gray_hline( RAS_VAR_ cell->x, yindex, area, 1 ); + + x = cell->x + 1; + } + + if ( ras.count_ex > x && cover != 0 ) + gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), + ras.count_ex - x ); + } + } + + /*************************************************************************/ + /* */ + /* The following function should only compile in stand_alone mode, */ + /* i.e., when building this component without the rest of FreeType. */ + /* */ + /*************************************************************************/ + + /*************************************************************************/ + /* */ + /* <Function> */ + /* QT_FT_Outline_Decompose */ + /* */ + /* <Description> */ + /* Walks over an outline's structure to decompose it into individual */ + /* segments and Bezier arcs. This function is also able to emit */ + /* `move to' and `close to' operations to indicate the start and end */ + /* of new contours in the outline. */ + /* */ + /* <Input> */ + /* outline :: A pointer to the source target. */ + /* */ + /* user :: A typeless pointer which is passed to each */ + /* emitter during the decomposition. It can be */ + /* used to store the state during the */ + /* decomposition. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + static + int QT_FT_Outline_Decompose( const QT_FT_Outline* outline, + void* user ) + { +#undef SCALED +#define SCALED( x ) (x) + + QT_FT_Vector v_last; + QT_FT_Vector v_control; + QT_FT_Vector v_start; + + QT_FT_Vector* point; + QT_FT_Vector* limit; + char* tags; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + int error; + char tag; /* current point's state */ + + first = 0; + + for ( n = 0; n < outline->n_contours; n++ ) + { + int last; /* index of last point in contour */ + + + last = outline->contours[n]; + limit = outline->points + last; + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_start.x = SCALED( v_start.x ); + v_start.y = SCALED( v_start.y ); + + v_last.x = SCALED( v_last.x ); + v_last.y = SCALED( v_last.y ); + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = QT_FT_CURVE_TAG( tags[0] ); + + /* A contour cannot start with a cubic control point! */ + if ( tag == QT_FT_CURVE_TAG_CUBIC ) + goto Invalid_Outline; + + /* check first point to determine origin */ + if ( tag == QT_FT_CURVE_TAG_CONIC ) + { + /* first point is conic control. Yes, this happens. */ + if ( QT_FT_CURVE_TAG( outline->tags[last] ) == QT_FT_CURVE_TAG_ON ) + { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } + else + { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = ( v_start.x + v_last.x ) / 2; + v_start.y = ( v_start.y + v_last.y ) / 2; + + v_last = v_start; + } + point--; + tags--; + } + + error = gray_move_to( &v_start, user ); + if ( error ) + goto Exit; + + while ( point < limit ) + { + point++; + tags++; + + tag = QT_FT_CURVE_TAG( tags[0] ); + switch ( tag ) + { + case QT_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + QT_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + error = gray_line_to( &vec, user ); + if ( error ) + goto Exit; + continue; + } + + case QT_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + { + v_control.x = SCALED( point->x ); + v_control.y = SCALED( point->y ); + + Do_Conic: + if ( point < limit ) + { + QT_FT_Vector vec; + QT_FT_Vector v_middle; + + + point++; + tags++; + tag = QT_FT_CURVE_TAG( tags[0] ); + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + if ( tag == QT_FT_CURVE_TAG_ON ) + { + error = gray_conic_to( &v_control, &vec, + user ); + if ( error ) + goto Exit; + continue; + } + + if ( tag != QT_FT_CURVE_TAG_CONIC ) + goto Invalid_Outline; + + v_middle.x = ( v_control.x + vec.x ) / 2; + v_middle.y = ( v_control.y + vec.y ) / 2; + + error = gray_conic_to( &v_control, &v_middle, + user ); + if ( error ) + goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = gray_conic_to( &v_control, &v_start, + user ); + goto Close; + } + + default: /* QT_FT_CURVE_TAG_CUBIC */ + { + QT_FT_Vector vec1, vec2; + + + if ( point + 1 > limit || + QT_FT_CURVE_TAG( tags[1] ) != QT_FT_CURVE_TAG_CUBIC ) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1.x = SCALED( point[-2].x ); + vec1.y = SCALED( point[-2].y ); + + vec2.x = SCALED( point[-1].x ); + vec2.y = SCALED( point[-1].y ); + + if ( point <= limit ) + { + QT_FT_Vector vec; + + + vec.x = SCALED( point->x ); + vec.y = SCALED( point->y ); + + error = gray_cubic_to( &vec1, &vec2, &vec, user ); + if ( error ) + goto Exit; + continue; + } + + error = gray_cubic_to( &vec1, &vec2, &v_start, user ); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = gray_line_to( &v_start, user ); + + Close: + if ( error ) + goto Exit; + + first = last + 1; + } + + return 0; + + Exit: + return error; + + Invalid_Outline: + return ErrRaster_Invalid_Outline; + } + + typedef struct TBand_ + { + TPos min, max; + + } TBand; + + + static int + gray_convert_glyph_inner( RAS_ARG ) + { + volatile int error = 0; + + if ( qt_ft_setjmp( ras.jump_buffer ) == 0 ) + { + error = QT_FT_Outline_Decompose( &ras.outline, &ras ); + gray_record_cell( RAS_VAR ); + } + else + { + error = ErrRaster_Memory_Overflow; + } + + return error; + } + + + static int + gray_convert_glyph( RAS_ARG ) + { + TBand bands[40]; + TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + QT_FT_BBox* clip; + int skip; + + ras.num_gray_spans = 0; + + /* Set up state in the raster object */ + gray_compute_cbox( RAS_VAR ); + + /* clip to target bitmap, exit if nothing to do */ + clip = &ras.clip_box; + + if ( ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || + ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax ) + return 0; + + if ( ras.min_ex < clip->xMin ) ras.min_ex = clip->xMin; + if ( ras.min_ey < clip->yMin ) ras.min_ey = clip->yMin; + + if ( ras.max_ex > clip->xMax ) ras.max_ex = clip->xMax; + if ( ras.max_ey > clip->yMax ) ras.max_ey = clip->yMax; + + ras.count_ex = ras.max_ex - ras.min_ex; + ras.count_ey = ras.max_ey - ras.min_ey; + + /* simple heuristic used to speed-up the bezier decomposition -- see */ + /* the code in gray_render_conic() and gray_render_cubic() for more */ + /* details */ + ras.conic_level = 32; + ras.cubic_level = 16; + + { + int level = 0; + + + if ( ras.count_ex > 24 || ras.count_ey > 24 ) + level++; + if ( ras.count_ex > 120 || ras.count_ey > 120 ) + level++; + + ras.conic_level <<= level; + ras.cubic_level <<= level; + } + + /* setup vertical bands */ + num_bands = (int)( ( ras.max_ey - ras.min_ey ) / ras.band_size ); + if ( num_bands == 0 ) num_bands = 1; + if ( num_bands >= 39 ) num_bands = 39; + + ras.band_shoot = 0; + + min = ras.min_ey; + max_y = ras.max_ey; + + for ( n = 0; n < num_bands; n++, min = max ) + { + max = min + ras.band_size; + if ( n == num_bands - 1 || max > max_y ) + max = max_y; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while ( band >= bands ) + { + TPos bottom, top, middle; + int error; + + { + PCell cells_max; + int yindex; + int cell_start, cell_end, cell_mod; + + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof ( PCell ) * ras.ycount; + cell_mod = cell_start % sizeof ( TCell ); + if ( cell_mod > 0 ) + cell_start += sizeof ( TCell ) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof( TCell ); + + cells_max = (PCell)( (char*)ras.buffer + cell_end ); + ras.cells = (PCell)( (char*)ras.buffer + cell_start ); + if ( ras.cells >= cells_max ) + goto ReduceBands; + + ras.max_cells = (int)(cells_max - ras.cells); + if ( ras.max_cells < 2 ) + goto ReduceBands; + + for ( yindex = 0; yindex < ras.ycount; yindex++ ) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner( RAS_VAR ); + + if ( !error ) + { + gray_sweep( RAS_VAR_ &ras.target ); + band--; + continue; + } + else if ( error != ErrRaster_Memory_Overflow ) + return 1; + + ReduceBands: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ( ( top - bottom ) >> 1 ); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if ( middle == bottom ) + { +#ifdef DEBUG_GRAYS + fprintf( stderr, "Rotten glyph!\n" ); +#endif + return ErrRaster_OutOfMemory; + } + + if ( bottom-top >= ras.band_size ) + ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) + { + skip = ras.skip_spans > 0 ? ras.skip_spans : 0; + ras.render_span( ras.num_gray_spans - skip, + ras.gray_spans + skip, + ras.render_span_data ); + } + + ras.skip_spans -= ras.num_gray_spans; + + if ( ras.band_shoot > 8 && ras.band_size > 16 ) + ras.band_size = ras.band_size / 2; + + return 0; + } + + + static int + gray_raster_render( QT_FT_Raster raster, + const QT_FT_Raster_Params* params ) + { + const QT_FT_Outline* outline = (const QT_FT_Outline*)params->source; + const QT_FT_Bitmap* target_map = params->target; + PWorker worker; + + + if ( !raster || !raster->buffer || !raster->buffer_size ) + return ErrRaster_Invalid_Argument; + + if ( raster->worker ) + raster->worker->skip_spans = params->skip_spans; + + // If raster object and raster buffer are allocated, but + // raster size isn't of the minimum size, indicate out of + // memory. + if (raster->buffer_allocated_size < MINIMUM_POOL_SIZE ) + return ErrRaster_OutOfMemory; + + /* return immediately if the outline is empty */ + if ( outline->n_points == 0 || outline->n_contours <= 0 ) + return 0; + + if ( !outline || !outline->contours || !outline->points ) + return ErrRaster_Invalid_Outline; + + if ( outline->n_points != + outline->contours[outline->n_contours - 1] + 1 ) + return ErrRaster_Invalid_Outline; + + worker = raster->worker; + + /* if direct mode is not set, we must have a target bitmap */ + if ( ( params->flags & QT_FT_RASTER_FLAG_DIRECT ) == 0 ) + { + if ( !target_map ) + return ErrRaster_Invalid_Argument; + + /* nothing to do */ + if ( !target_map->width || !target_map->rows ) + return 0; + + if ( !target_map->buffer ) + return ErrRaster_Invalid_Argument; + } + + /* this version does not support monochrome rendering */ + if ( !( params->flags & QT_FT_RASTER_FLAG_AA ) ) + return ErrRaster_Invalid_Mode; + + /* compute clipping box */ + if ( ( params->flags & QT_FT_RASTER_FLAG_DIRECT ) == 0 ) + { + /* compute clip box from target pixmap */ + ras.clip_box.xMin = 0; + ras.clip_box.yMin = 0; + ras.clip_box.xMax = target_map->width; + ras.clip_box.yMax = target_map->rows; + } + else if ( params->flags & QT_FT_RASTER_FLAG_CLIP ) + { + ras.clip_box = params->clip_box; + } + else + { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + gray_init_cells( worker, raster->buffer, raster->buffer_size ); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = raster->band_size; + + if ( target_map ) + ras.target = *target_map; + + ras.render_span = (QT_FT_Raster_Span_Func)gray_render_span; + ras.render_span_data = &ras; + + if ( params->flags & QT_FT_RASTER_FLAG_DIRECT ) + { + ras.render_span = (QT_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + } + + return gray_convert_glyph( worker ); + } + + + /**** RASTER OBJECT CREATION: In standalone mode, we simply use *****/ + /**** a static object. *****/ + + static int + gray_raster_new( QT_FT_Raster* araster ) + { + *araster = malloc(sizeof(TRaster)); + if (!*araster) { + *araster = 0; + return ErrRaster_Memory_Overflow; + } + QT_FT_MEM_ZERO(*araster, sizeof(TRaster)); + + return 0; + } + + + static void + gray_raster_done( QT_FT_Raster raster ) + { + free(raster); + } + + + static void + gray_raster_reset( QT_FT_Raster raster, + char* pool_base, + long pool_size ) + { + PRaster rast = (PRaster)raster; + + if ( raster ) + { + if ( pool_base && ( pool_size >= MINIMUM_POOL_SIZE ) ) + { + PWorker worker = (PWorker)pool_base; + + + rast->worker = worker; + rast->buffer = pool_base + + ( ( sizeof ( TWorker ) + sizeof ( TCell ) - 1 ) & + ~( sizeof ( TCell ) - 1 ) ); + rast->buffer_size = (long)( ( pool_base + pool_size ) - + (char*)rast->buffer ) & + ~( sizeof ( TCell ) - 1 ); + rast->band_size = (int)( rast->buffer_size / + ( sizeof ( TCell ) * 8 ) ); + } + else if ( pool_base) + { // Case when there is a raster pool allocated, but it + // doesn't have the minimum size (and so memory will be reallocated) + rast->buffer = pool_base; + rast->worker = NULL; + rast->buffer_size = pool_size; + } + else + { + rast->buffer = NULL; + rast->buffer_size = 0; + rast->worker = NULL; + } + rast->buffer_allocated_size = pool_size; + } + } + + const QT_FT_Raster_Funcs qt_ft_grays_raster = + { + QT_FT_GLYPH_FORMAT_OUTLINE, + + (QT_FT_Raster_New_Func) gray_raster_new, + (QT_FT_Raster_Reset_Func) gray_raster_reset, + (QT_FT_Raster_Set_Mode_Func)0, + (QT_FT_Raster_Render_Func) gray_raster_render, + (QT_FT_Raster_Done_Func) gray_raster_done + }; + +/* END */ diff --git a/src/gui/painting/qgrayraster_p.h b/src/gui/painting/qgrayraster_p.h new file mode 100644 index 0000000000..b9a4aed66d --- /dev/null +++ b/src/gui/painting/qgrayraster_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/***************************************************************************/ +/* */ +/* qgrayraster_p.h, derived from ftgrays.h */ +/* */ +/* FreeType smooth renderer declaration */ +/* */ +/* Copyright 1996-2001 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, ../../3rdparty/freetype/docs/FTL.TXT. By continuing to use, */ +/* modify, or distribute this file you indicate that you have read */ +/* the license and understand and accept it fully. */ +/***************************************************************************/ + + +#ifndef __FTGRAYS_H__ +#define __FTGRAYS_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. +*/ + +#ifdef __cplusplus + extern "C" { +#endif + + +#include <private/qrasterdefs_p.h> + + /*************************************************************************/ + /* */ + /* To make ftgrays.h independent from configuration files we check */ + /* whether QT_FT_EXPORT_VAR has been defined already. */ + /* */ + /* On some systems and compilers (Win32 mostly), an extra keyword is */ + /* necessary to compile the library as a DLL. */ + /* */ +#ifndef QT_FT_EXPORT_VAR +#define QT_FT_EXPORT_VAR( x ) extern x +#endif + +/* Minimum buffer size for raster object, that accounts + for TWorker and TCell sizes.*/ +#define MINIMUM_POOL_SIZE 8192 + + QT_FT_EXPORT_VAR( const QT_FT_Raster_Funcs ) qt_ft_grays_raster; + + +#ifdef __cplusplus + } +#endif + +#endif /* __FTGRAYS_H__ */ + +/* END */ diff --git a/src/gui/painting/qimagescale.cpp b/src/gui/painting/qimagescale.cpp new file mode 100644 index 0000000000..87df7976bf --- /dev/null +++ b/src/gui/painting/qimagescale.cpp @@ -0,0 +1,1031 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <private/qimagescale_p.h> +#include <private/qdrawhelper_p.h> + +#include "qimage.h" +#include "qcolor.h" + +QT_BEGIN_NAMESPACE + +namespace QImageScale { + struct QImageScaleInfo; +} + +typedef void (*qt_qimageScaleFunc)(QImageScale::QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow); + +static void qt_qimageScaleAARGB(QImageScale::QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow); + +static void qt_qimageScaleAARGBA(QImageScale::QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow); + +qt_qimageScaleFunc qt_qimageScaleArgb = qt_qimageScaleAARGBA; +qt_qimageScaleFunc qt_qimageScaleRgb = qt_qimageScaleAARGB; + + +/* + * Copyright (C) 2004, 2005 Daniel M. Duley + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* OTHER CREDITS: + * + * This is the normal smoothscale method, based on Imlib2's smoothscale. + * + * Originally I took the algorithm used in NetPBM and Qt and added MMX/3dnow + * optimizations. It ran in about 1/2 the time as Qt. Then I ported Imlib's + * C algorithm and it ran at about the same speed as my MMX optimized one... + * Finally I ported Imlib's MMX version and it ran in less than half the + * time as my MMX algorithm, (taking only a quarter of the time Qt does). + * After further optimization it seems to run at around 1/6th. + * + * Changes include formatting, namespaces and other C++'ings, removal of old + * #ifdef'ed code, and removal of unneeded border calculation code. + * + * Imlib2 is (C) Carsten Haitzler and various contributors. The MMX code + * is by Willem Monsuwe <willem@stack.nl>. All other modifications are + * (C) Daniel M. Duley. + */ + + +namespace QImageScale { + struct QImageScaleInfo { + int *xpoints; + unsigned int **ypoints; + int *xapoints, *yapoints; + int xup_yup; + }; + + unsigned int** qimageCalcYPoints(unsigned int *src, int sw, int sh, + int dh); + int* qimageCalcXPoints(int sw, int dw); + int* qimageCalcApoints(int s, int d, int up); + QImageScaleInfo* qimageFreeScaleInfo(QImageScaleInfo *isi); + QImageScaleInfo *qimageCalcScaleInfo(const QImage &img, int sw, int sh, + int dw, int dh, char aa); +} + +using namespace QImageScale; + +// +// Code ported from Imlib... +// + +// FIXME: replace with qRed, etc... These work on pointers to pixels, not +// pixel values +#define A_VAL(p) (qAlpha(*p)) +#define R_VAL(p) (qRed(*p)) +#define G_VAL(p) (qGreen(*p)) +#define B_VAL(p) (qBlue(*p)) + +#define INV_XAP (256 - xapoints[x]) +#define XAP (xapoints[x]) +#define INV_YAP (256 - yapoints[dyy + y]) +#define YAP (yapoints[dyy + y]) + +unsigned int** QImageScale::qimageCalcYPoints(unsigned int *src, + int sw, int sh, int dh) +{ + unsigned int **p; + int i, j = 0; + int val, inc, rv = 0; + + if(dh < 0){ + dh = -dh; + rv = 1; + } + p = new unsigned int* [dh+1]; + + int up = qAbs(dh) >= sh; + val = up ? 0x8000 * sh / dh - 0x8000 : 0; + inc = (sh << 16) / dh; + for(i = 0; i < dh; i++){ + p[j++] = src + qMax(0, val >> 16) * sw; + val += inc; + } + if(rv){ + for(i = dh / 2; --i >= 0; ){ + unsigned int *tmp = p[i]; + p[i] = p[dh - i - 1]; + p[dh - i - 1] = tmp; + } + } + return(p); +} + +int* QImageScale::qimageCalcXPoints(int sw, int dw) +{ + int *p, i, j = 0; + int val, inc, rv = 0; + + if(dw < 0){ + dw = -dw; + rv = 1; + } + p = new int[dw+1]; + + int up = qAbs(dw) >= sw; + val = up ? 0x8000 * sw / dw - 0x8000 : 0; + inc = (sw << 16) / dw; + for(i = 0; i < dw; i++){ + p[j++] = qMax(0, val >> 16); + val += inc; + } + + if(rv){ + for(i = dw / 2; --i >= 0; ){ + int tmp = p[i]; + p[i] = p[dw - i - 1]; + p[dw - i - 1] = tmp; + } + } + return(p); +} + +int* QImageScale::qimageCalcApoints(int s, int d, int up) +{ + int *p, i, j = 0, rv = 0; + + if(d < 0){ + rv = 1; + d = -d; + } + p = new int[d]; + + /* scaling up */ + if(up){ + int val, inc; + + val = 0x8000 * s / d - 0x8000; + inc = (s << 16) / d; + for(i = 0; i < d; i++){ + int pos = val >> 16; + if (pos < 0) + p[j++] = 0; + else if (pos >= (s - 1)) + p[j++] = 0; + else + p[j++] = (val >> 8) - ((val >> 8) & 0xffffff00); + val += inc; + } + } + /* scaling down */ + else{ + int val, inc, ap, Cp; + val = 0; + inc = (s << 16) / d; + Cp = ((d << 14) / s) + 1; + for(i = 0; i < d; i++){ + ap = ((0x100 - ((val >> 8) & 0xff)) * Cp) >> 8; + p[j] = ap | (Cp << 16); + j++; + val += inc; + } + } + if(rv){ + int tmp; + for(i = d / 2; --i >= 0; ){ + tmp = p[i]; + p[i] = p[d - i - 1]; + p[d - i - 1] = tmp; + } + } + return(p); +} + +QImageScaleInfo* QImageScale::qimageFreeScaleInfo(QImageScaleInfo *isi) +{ + if(isi){ + delete[] isi->xpoints; + delete[] isi->ypoints; + delete[] isi->xapoints; + delete[] isi->yapoints; + delete isi; + } + return 0; +} + +QImageScaleInfo* QImageScale::qimageCalcScaleInfo(const QImage &img, + int sw, int sh, + int dw, int dh, char aa) +{ + QImageScaleInfo *isi; + int scw, sch; + + scw = dw * qlonglong(img.width()) / sw; + sch = dh * qlonglong(img.height()) / sh; + + isi = new QImageScaleInfo; + if(!isi) + return 0; + memset(isi, 0, sizeof(QImageScaleInfo)); + + isi->xup_yup = (qAbs(dw) >= sw) + ((qAbs(dh) >= sh) << 1); + + isi->xpoints = qimageCalcXPoints(img.width(), scw); + if(!isi->xpoints) + return(qimageFreeScaleInfo(isi)); + isi->ypoints = qimageCalcYPoints((unsigned int *)img.scanLine(0), + img.bytesPerLine() / 4, img.height(), sch); + if (!isi->ypoints) + return(qimageFreeScaleInfo(isi)); + if(aa) { + isi->xapoints = qimageCalcApoints(img.width(), scw, isi->xup_yup & 1); + if(!isi->xapoints) + return(qimageFreeScaleInfo(isi)); + isi->yapoints = qimageCalcApoints(img.height(), sch, isi->xup_yup & 2); + if(!isi->yapoints) + return(qimageFreeScaleInfo(isi)); + } + return(isi); +} + +/* FIXME: NEED to optimize ScaleAARGBA - currently its "ok" but needs work*/ + +/* scale by area sampling */ +static void qt_qimageScaleAARGBA(QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r, g, b, a; + int rr, gg, bb, aa; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + aa = A_VAL(pix) * XAP; + pix--; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + aa += A_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + a = ((aa * YAP) + (a * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, a); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + a = A_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + a += A_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r, g, b, a; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + a = A_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + a += A_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + a >>= 8; + *dptr++ = qRgba(r, g, b, a); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * yap; + g = G_VAL(pix) * yap; + b = B_VAL(pix) * yap; + a = A_VAL(pix) * yap; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + r += R_VAL(pix) * Cy; + g += G_VAL(pix) * Cy; + b += B_VAL(pix) * Cy; + a += A_VAL(pix) * Cy; + } + if(j > 0){ + pix += sow; + r += R_VAL(pix) * j; + g += G_VAL(pix) * j; + b += B_VAL(pix) * j; + a += A_VAL(pix) * j; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = R_VAL(pix) * yap; + gg = G_VAL(pix) * yap; + bb = B_VAL(pix) * yap; + aa = A_VAL(pix) * yap; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix += sow; + rr += R_VAL(pix) * Cy; + gg += G_VAL(pix) * Cy; + bb += B_VAL(pix) * Cy; + aa += A_VAL(pix) * Cy; + } + if(j > 0){ + pix += sow; + rr += R_VAL(pix) * j; + gg += G_VAL(pix) * j; + bb += B_VAL(pix) * j; + aa += A_VAL(pix) * j; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + a = a * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + a = (a + ((aa * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r >> 10, g >> 10, b >> 10, a >> 10); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, a, rr, gg, bb, aa; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * xap; + g = G_VAL(pix) * xap; + b = B_VAL(pix) * xap; + a = A_VAL(pix) * xap; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + r += R_VAL(pix) * Cx; + g += G_VAL(pix) * Cx; + b += B_VAL(pix) * Cx; + a += A_VAL(pix) * Cx; + } + if(j > 0){ + pix++; + r += R_VAL(pix) * j; + g += G_VAL(pix) * j; + b += B_VAL(pix) * j; + a += A_VAL(pix) * j; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = R_VAL(pix) * xap; + gg = G_VAL(pix) * xap; + bb = B_VAL(pix) * xap; + aa = A_VAL(pix) * xap; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + pix++; + rr += R_VAL(pix) * Cx; + gg += G_VAL(pix) * Cx; + bb += B_VAL(pix) * Cx; + aa += A_VAL(pix) * Cx; + } + if(j > 0){ + pix++; + rr += R_VAL(pix) * j; + gg += G_VAL(pix) * j; + bb += B_VAL(pix) * j; + aa += A_VAL(pix) * j; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + a = a * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + a = (a + ((aa * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + a >>= 4; + } + *dptr = qRgba(r >> 10, g >> 10, b >> 10, a >> 10); + dptr++; + } + } + } + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification: + |*| The operation 'b = (b * c) >> 16' translates to pmulhw, + |*| so the operation 'b = (b * c) >> d' would translate to + |*| psllw (16 - d), %mmb; pmulh %mmc, %mmb + \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int a, r, g, b, ax, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + ax = A_VAL(pix) * xap; + + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + ax += A_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + ax += A_VAL(pix) * i; + } + + r = (rx >> 5) * yap; + g = (gx >> 5) * yap; + b = (bx >> 5) * yap; + a = (ax >> 5) * yap; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + ax = A_VAL(pix) * xap; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + ax += A_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + ax += A_VAL(pix) * i; + } + + r += (rx >> 5) * Cy; + g += (gx >> 5) * Cy; + b += (bx >> 5) * Cy; + a += (ax >> 5) * Cy; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + ax = A_VAL(pix) * xap; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + ax += A_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + ax += A_VAL(pix) * i; + } + + r += (rx >> 5) * j; + g += (gx >> 5) * j; + b += (bx >> 5) * j; + a += (ax >> 5) * j; + } + + *dptr = qRgba(r >> 23, g >> 23, b >> 23, a >> 23); + dptr++; + } + } + } +} + +/* scale by area sampling - IGNORE the ALPHA byte*/ +static void qt_qimageScaleAARGB(QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + unsigned int *sptr, *dptr; + int x, y, end; + unsigned int **ypoints = isi->ypoints; + int *xpoints = isi->xpoints; + int *xapoints = isi->xapoints; + int *yapoints = isi->yapoints; + + end = dxx + dw; + /* scaling up both ways */ + if(isi->xup_yup == 3){ + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + /* calculate the source line we'll scan from */ + dptr = dest + dx + ((y + dy) * dow); + sptr = ypoints[dyy + y]; + if(YAP > 0){ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + int rr = 0, gg = 0, bb = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + pix += sow; + rr = R_VAL(pix) * XAP; + gg = G_VAL(pix) * XAP; + bb = B_VAL(pix) * XAP; + pix --; + rr += R_VAL(pix) * INV_XAP; + gg += G_VAL(pix) * INV_XAP; + bb += B_VAL(pix) * INV_XAP; + r = ((rr * YAP) + (r * INV_YAP)) >> 16; + g = ((gg * YAP) + (g * INV_YAP)) >> 16; + b = ((bb * YAP) + (b * INV_YAP)) >> 16; + *dptr++ = qRgba(r, g, b, 0xff); + } + else{ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_YAP; + g = G_VAL(pix) * INV_YAP; + b = B_VAL(pix) * INV_YAP; + pix += sow; + r += R_VAL(pix) * YAP; + g += G_VAL(pix) * YAP; + b += B_VAL(pix) * YAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + } + } + else{ + for(x = dxx; x < end; x++){ + int r = 0, g = 0, b = 0; + unsigned int *pix; + + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * INV_XAP; + g = G_VAL(pix) * INV_XAP; + b = B_VAL(pix) * INV_XAP; + pix++; + r += R_VAL(pix) * XAP; + g += G_VAL(pix) * XAP; + b += B_VAL(pix) * XAP; + r >>= 8; + g >>= 8; + b >>= 8; + *dptr++ = qRgba(r, g, b, 0xff); + } + else + *dptr++ = sptr[xpoints[x] ]; + } + } + } + } + /* if we're scaling down vertically */ + else if(isi->xup_yup == 1){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cy, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int yap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * yap; + g = G_VAL(pix) * yap; + b = B_VAL(pix) * yap; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + r += R_VAL(pix) * Cy; + g += G_VAL(pix) * Cy; + b += B_VAL(pix) * Cy; + pix += sow; + } + if(j > 0){ + r += R_VAL(pix) * j; + g += G_VAL(pix) * j; + b += B_VAL(pix) * j; + } + if(XAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + 1; + rr = R_VAL(pix) * yap; + gg = G_VAL(pix) * yap; + bb = B_VAL(pix) * yap; + pix += sow; + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + rr += R_VAL(pix) * Cy; + gg += G_VAL(pix) * Cy; + bb += B_VAL(pix) * Cy; + pix += sow; + } + if(j > 0){ + rr += R_VAL(pix) * j; + gg += G_VAL(pix) * j; + bb += B_VAL(pix) * j; + } + r = r * INV_XAP; + g = g * INV_XAP; + b = b * INV_XAP; + r = (r + ((rr * XAP))) >> 12; + g = (g + ((gg * XAP))) >> 12; + b = (b + ((bb * XAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r >> 10, g >> 10, b >> 10, 0xff); + dptr++; + } + } + } + /* if we're scaling down horizontally */ + else if(isi->xup_yup == 2){ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, j; + unsigned int *pix; + int r, g, b, rr, gg, bb; + int xap; + + /* go through every scanline in the output buffer */ + for(y = 0; y < dh; y++){ + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + pix = ypoints[dyy + y] + xpoints[x]; + r = R_VAL(pix) * xap; + g = G_VAL(pix) * xap; + b = B_VAL(pix) * xap; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + r += R_VAL(pix) * Cx; + g += G_VAL(pix) * Cx; + b += B_VAL(pix) * Cx; + pix++; + } + if(j > 0){ + r += R_VAL(pix) * j; + g += G_VAL(pix) * j; + b += B_VAL(pix) * j; + } + if(YAP > 0){ + pix = ypoints[dyy + y] + xpoints[x] + sow; + rr = R_VAL(pix) * xap; + gg = G_VAL(pix) * xap; + bb = B_VAL(pix) * xap; + pix++; + for(j = (1 << 14) - xap; j > Cx; j -= Cx){ + rr += R_VAL(pix) * Cx; + gg += G_VAL(pix) * Cx; + bb += B_VAL(pix) * Cx; + pix++; + } + if(j > 0){ + rr += R_VAL(pix) * j; + gg += G_VAL(pix) * j; + bb += B_VAL(pix) * j; + } + r = r * INV_YAP; + g = g * INV_YAP; + b = b * INV_YAP; + r = (r + ((rr * YAP))) >> 12; + g = (g + ((gg * YAP))) >> 12; + b = (b + ((bb * YAP))) >> 12; + } + else{ + r >>= 4; + g >>= 4; + b >>= 4; + } + *dptr = qRgba(r >> 10, g >> 10, b >> 10, 0xff); + dptr++; + } + } + } + /* fully optimized (i think) - onyl change of algorithm can help */ + /* if we're scaling down horizontally & vertically */ + else{ + /*\ 'Correct' version, with math units prepared for MMXification \*/ + int Cx, Cy, i, j; + unsigned int *pix; + int r, g, b, rx, gx, bx; + int xap, yap; + + for(y = 0; y < dh; y++){ + Cy = YAP >> 16; + yap = YAP & 0xffff; + + dptr = dest + dx + ((y + dy) * dow); + for(x = dxx; x < end; x++){ + Cx = XAP >> 16; + xap = XAP & 0xffff; + + sptr = ypoints[dyy + y] + xpoints[x]; + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + } + + r = (rx >> 5) * yap; + g = (gx >> 5) * yap; + b = (bx >> 5) * yap; + + for(j = (1 << 14) - yap; j > Cy; j -= Cy){ + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + } + + r += (rx >> 5) * Cy; + g += (gx >> 5) * Cy; + b += (bx >> 5) * Cy; + } + if(j > 0){ + pix = sptr; + sptr += sow; + rx = R_VAL(pix) * xap; + gx = G_VAL(pix) * xap; + bx = B_VAL(pix) * xap; + pix++; + for(i = (1 << 14) - xap; i > Cx; i -= Cx){ + rx += R_VAL(pix) * Cx; + gx += G_VAL(pix) * Cx; + bx += B_VAL(pix) * Cx; + pix++; + } + if(i > 0){ + rx += R_VAL(pix) * i; + gx += G_VAL(pix) * i; + bx += B_VAL(pix) * i; + } + + r += (rx >> 5) * j; + g += (gx >> 5) * j; + b += (bx >> 5) * j; + } + + *dptr = qRgb(r >> 23, g >> 23, b >> 23); + dptr++; + } + } + } +} + +#if 0 +static void qt_qimageScaleAARGBASetup(QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + qInitDrawhelperAsm(); + qt_qimageScaleAARGBA(isi, dest, dxx, dyy, dx, dy, dw, dh, dow, sow); +} + +static void qt_qimageScaleAARGBSetup(QImageScaleInfo *isi, unsigned int *dest, + int dxx, int dyy, int dx, int dy, int dw, + int dh, int dow, int sow) +{ + qInitDrawhelperAsm(); + qt_qimageScaleAARGB(isi, dest, dxx, dyy, dx, dy, dw, dh, dow, sow); +} +#endif + +QImage qSmoothScaleImage(const QImage &src, int dw, int dh) +{ + QImage buffer; + if (src.isNull() || dw <= 0 || dh <= 0) + return buffer; + + int w = src.width(); + int h = src.height(); + QImageScaleInfo *scaleinfo = + qimageCalcScaleInfo(src, w, h, dw, dh, true); + if (!scaleinfo) + return buffer; + + buffer = QImage(dw, dh, src.format()); + if (buffer.isNull()) { + qWarning("QImage: out of memory, returning null"); + qimageFreeScaleInfo(scaleinfo); + return QImage(); + } + + if (src.format() == QImage::Format_ARGB32_Premultiplied) + qt_qimageScaleArgb(scaleinfo, (unsigned int *)buffer.scanLine(0), + 0, 0, 0, 0, dw, dh, dw, src.bytesPerLine() / 4); + else + qt_qimageScaleRgb(scaleinfo, (unsigned int *)buffer.scanLine(0), + 0, 0, 0, 0, dw, dh, dw, src.bytesPerLine() / 4); + + qimageFreeScaleInfo(scaleinfo); + return buffer; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qimagescale_p.h b/src/gui/painting/qimagescale_p.h new file mode 100644 index 0000000000..fbf162addf --- /dev/null +++ b/src/gui/painting/qimagescale_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QIMAGESCALE_P_H +#define QIMAGESCALE_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 <qimage.h> + +QT_BEGIN_NAMESPACE + +/* + This version accepts only supported formats. +*/ +QImage qSmoothScaleImage(const QImage &img, int w, int h); + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qmath_p.h b/src/gui/painting/qmath_p.h new file mode 100644 index 0000000000..63d23e0dcb --- /dev/null +++ b/src/gui/painting/qmath_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMATH_P_H +#define QMATH_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 <math.h> +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +static const qreal Q_PI = qreal(3.14159265358979323846); // pi +static const qreal Q_2PI = qreal(6.28318530717958647693); // 2*pi +static const qreal Q_PI2 = qreal(1.57079632679489661923); // pi/2 + +inline int qIntSqrtInt(int v) +{ + return static_cast<int>(qSqrt(static_cast<qreal>(v))); +} + +QT_END_NAMESPACE + +#endif // QMATH_P_H diff --git a/src/gui/painting/qmatrix.cpp b/src/gui/painting/qmatrix.cpp new file mode 100644 index 0000000000..38f78e1a46 --- /dev/null +++ b/src/gui/painting/qmatrix.cpp @@ -0,0 +1,1229 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdatastream.h" +#include "qdebug.h" +#include "qmatrix.h" +#include "qregion.h" +#include "qpainterpath.h" +#include "qvariant.h" +#include <qmath.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QMatrix + \brief The QMatrix class specifies 2D transformations of a + coordinate system. + \obsolete + + \ingroup painting + + A matrix specifies how to translate, scale, shear or rotate the + coordinate system, and is typically used when rendering graphics. + QMatrix, in contrast to QTransform, does not allow perspective + transformations. QTransform is the recommended transformation + class in Qt. + + A QMatrix object can be built using the setMatrix(), scale(), + rotate(), translate() and shear() functions. Alternatively, it + can be built by applying \l {QMatrix#Basic Matrix + Operations}{basic matrix operations}. The matrix can also be + defined when constructed, and it can be reset to the identity + matrix (the default) using the reset() function. + + The QMatrix class supports mapping of graphic primitives: A given + point, line, polygon, region, or painter path can be mapped to the + coordinate system defined by \e this matrix using the map() + function. In case of a rectangle, its coordinates can be + transformed using the mapRect() function. A rectangle can also be + transformed into a \e polygon (mapped to the coordinate system + defined by \e this matrix), using the mapToPolygon() function. + + QMatrix provides the isIdentity() function which returns true if + the matrix is the identity matrix, and the isInvertible() function + which returns true if the matrix is non-singular (i.e. AB = BA = + I). The inverted() function returns an inverted copy of \e this + matrix if it is invertible (otherwise it returns the identity + matrix). In addition, QMatrix provides the determinant() function + returning the matrix's determinant. + + Finally, the QMatrix class supports matrix multiplication, and + objects of the class can be streamed as well as compared. + + \tableofcontents + + \section1 Rendering Graphics + + When rendering graphics, the matrix defines the transformations + but the actual transformation is performed by the drawing routines + in QPainter. + + By default, QPainter operates on the associated device's own + coordinate system. The standard coordinate system of a + QPaintDevice has its origin located at the top-left position. The + \e x values increase to the right; \e y values increase + downward. For a complete description, see the \l {Coordinate + System}{coordinate system} documentation. + + QPainter has functions to translate, scale, shear and rotate the + coordinate system without using a QMatrix. For example: + + \table 100% + \row + \o \inlineimage qmatrix-simpletransformation.png + \o + \snippet doc/src/snippets/matrix/matrix.cpp 0 + \endtable + + Although these functions are very convenient, it can be more + efficient to build a QMatrix and call QPainter::setMatrix() if you + want to perform more than a single transform operation. For + example: + + \table 100% + \row + \o \inlineimage qmatrix-combinedtransformation.png + \o + \snippet doc/src/snippets/matrix/matrix.cpp 1 + \endtable + + \section1 Basic Matrix Operations + + \image qmatrix-representation.png + + A QMatrix object contains a 3 x 3 matrix. The \c dx and \c dy + elements specify horizontal and vertical translation. The \c m11 + and \c m22 elements specify horizontal and vertical scaling. And + finally, the \c m21 and \c m12 elements specify horizontal and + vertical \e shearing. + + QMatrix transforms a point in the plane to another point using the + following formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qmatrix.cpp 0 + + The point \e (x, y) is the original point, and \e (x', y') is the + transformed point. \e (x', y') can be transformed back to \e (x, + y) by performing the same operation on the inverted() matrix. + + The various matrix elements can be set when constructing the + matrix, or by using the setMatrix() function later on. They can also + be manipulated using the translate(), rotate(), scale() and + shear() convenience functions, The currently set values can be + retrieved using the m11(), m12(), m21(), m22(), dx() and dy() + functions. + + Translation is the simplest transformation. Setting \c dx and \c + dy will move the coordinate system \c dx units along the X axis + and \c dy units along the Y axis. Scaling can be done by setting + \c m11 and \c m22. For example, setting \c m11 to 2 and \c m22 to + 1.5 will double the height and increase the width by 50%. The + identity matrix has \c m11 and \c m22 set to 1 (all others are set + to 0) mapping a point to itself. Shearing is controlled by \c m12 + and \c m21. Setting these elements to values different from zero + will twist the coordinate system. Rotation is achieved by + carefully setting both the shearing factors and the scaling + factors. + + Here's the combined transformations example using basic matrix + operations: + + \table 100% + \row + \o \inlineimage qmatrix-combinedtransformation.png + \o + \snippet doc/src/snippets/matrix/matrix.cpp 2 + \endtable + + \sa QPainter, QTransform, {Coordinate System}, + {demos/affine}{Affine Transformations Demo}, {Transformations Example} +*/ + + +// some defines to inline some code +#define MAPDOUBLE(x, y, nx, ny) \ +{ \ + qreal fx = x; \ + qreal fy = y; \ + nx = _m11*fx + _m21*fy + _dx; \ + ny = _m12*fx + _m22*fy + _dy; \ +} + +#define MAPINT(x, y, nx, ny) \ +{ \ + qreal fx = x; \ + qreal fy = y; \ + nx = qRound(_m11*fx + _m21*fy + _dx); \ + ny = qRound(_m12*fx + _m22*fy + _dy); \ +} + +/***************************************************************************** + QMatrix member functions + *****************************************************************************/ +/*! + \fn QMatrix::QMatrix(Qt::Initialization) + \internal +*/ + +/*! + Constructs an identity matrix. + + All elements are set to zero except \c m11 and \c m22 (specifying + the scale), which are set to 1. + + \sa reset() +*/ + +QMatrix::QMatrix() + : _m11(1.) + , _m12(0.) + , _m21(0.) + , _m22(1.) + , _dx(0.) + , _dy(0.) +{ +} + +/*! + Constructs a matrix with the elements, \a m11, \a m12, \a m21, \a + m22, \a dx and \a dy. + + \sa setMatrix() +*/ + +QMatrix::QMatrix(qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy) + : _m11(m11) + , _m12(m12) + , _m21(m21) + , _m22(m22) + , _dx(dx) + , _dy(dy) +{ +} + + +/*! + Constructs a matrix that is a copy of the given \a matrix. + */ +QMatrix::QMatrix(const QMatrix &matrix) + : _m11(matrix._m11) + , _m12(matrix._m12) + , _m21(matrix._m21) + , _m22(matrix._m22) + , _dx(matrix._dx) + , _dy(matrix._dy) +{ +} + +/*! + Sets the matrix elements to the specified values, \a m11, \a m12, + \a m21, \a m22, \a dx and \a dy. + + Note that this function replaces the previous values. QMatrix + provide the translate(), rotate(), scale() and shear() convenience + functions to manipulate the various matrix elements based on the + currently defined coordinate system. + + \sa QMatrix() +*/ + +void QMatrix::setMatrix(qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy) +{ + _m11 = m11; + _m12 = m12; + _m21 = m21; + _m22 = m22; + _dx = dx; + _dy = dy; +} + + +/*! + \fn qreal QMatrix::m11() const + + Returns the horizontal scaling factor. + + \sa scale(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QMatrix::m12() const + + Returns the vertical shearing factor. + + \sa shear(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QMatrix::m21() const + + Returns the horizontal shearing factor. + + \sa shear(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QMatrix::m22() const + + Returns the vertical scaling factor. + + \sa scale(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QMatrix::dx() const + + Returns the horizontal translation factor. + + \sa translate(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QMatrix::dy() const + + Returns the vertical translation factor. + + \sa translate(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + + +/*! + Maps the given coordinates \a x and \a y into the coordinate + system defined by this matrix. The resulting values are put in *\a + tx and *\a ty, respectively. + + The coordinates are transformed using the following formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qmatrix.cpp 1 + + The point (x, y) is the original point, and (x', y') is the + transformed point. + + \sa {QMatrix#Basic Matrix Operations}{Basic Matrix Operations} +*/ + +void QMatrix::map(qreal x, qreal y, qreal *tx, qreal *ty) const +{ + MAPDOUBLE(x, y, *tx, *ty); +} + + + +/*! + \overload + + Maps the given coordinates \a x and \a y into the coordinate + system defined by this matrix. The resulting values are put in *\a + tx and *\a ty, respectively. Note that the transformed coordinates + are rounded to the nearest integer. +*/ + +void QMatrix::map(int x, int y, int *tx, int *ty) const +{ + MAPINT(x, y, *tx, *ty); +} + +QRect QMatrix::mapRect(const QRect &rect) const +{ + QRect result; + if (_m12 == 0.0F && _m21 == 0.0F) { + int x = qRound(_m11*rect.x() + _dx); + int y = qRound(_m22*rect.y() + _dy); + int w = qRound(_m11*rect.width()); + int h = qRound(_m22*rect.height()); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + result = QRect(x, y, w, h); + } else { + // see mapToPolygon for explanations of the algorithm. + qreal x0, y0; + qreal x, y; + MAPDOUBLE(rect.left(), rect.top(), x0, y0); + qreal xmin = x0; + qreal ymin = y0; + qreal xmax = x0; + qreal ymax = y0; + MAPDOUBLE(rect.right() + 1, rect.top(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAPDOUBLE(rect.right() + 1, rect.bottom() + 1, x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAPDOUBLE(rect.left(), rect.bottom() + 1, x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + result = QRect(qRound(xmin), qRound(ymin), qRound(xmax)-qRound(xmin), qRound(ymax)-qRound(ymin)); + } + return result; +} + +/*! + \fn QRectF QMatrix::mapRect(const QRectF &rectangle) const + + Creates and returns a QRectF object that is a copy of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. + + The rectangle's coordinates are transformed using the following + formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qmatrix.cpp 2 + + If rotation or shearing has been specified, this function returns + the \e bounding rectangle. To retrieve the exact region the given + \a rectangle maps to, use the mapToPolygon() function instead. + + \sa mapToPolygon(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ +QRectF QMatrix::mapRect(const QRectF &rect) const +{ + QRectF result; + if (_m12 == 0.0F && _m21 == 0.0F) { + qreal x = _m11*rect.x() + _dx; + qreal y = _m22*rect.y() + _dy; + qreal w = _m11*rect.width(); + qreal h = _m22*rect.height(); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + result = QRectF(x, y, w, h); + } else { + qreal x0, y0; + qreal x, y; + MAPDOUBLE(rect.x(), rect.y(), x0, y0); + qreal xmin = x0; + qreal ymin = y0; + qreal xmax = x0; + qreal ymax = y0; + MAPDOUBLE(rect.x() + rect.width(), rect.y(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAPDOUBLE(rect.x() + rect.width(), rect.y() + rect.height(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAPDOUBLE(rect.x(), rect.y() + rect.height(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + result = QRectF(xmin, ymin, xmax-xmin, ymax - ymin); + } + return result; +} + +/*! + \fn QRect QMatrix::mapRect(const QRect &rectangle) const + \overload + + Creates and returns a QRect object that is a copy of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ + + +/*! + \fn QPoint operator*(const QPoint &point, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{point}). + + \sa QMatrix::map() +*/ + +QPoint QMatrix::map(const QPoint &p) const +{ + qreal fx = p.x(); + qreal fy = p.y(); + return QPoint(qRound(_m11*fx + _m21*fy + _dx), + qRound(_m12*fx + _m22*fy + _dy)); +} + +/*! + \fn QPointF operator*(const QPointF &point, const QMatrix &matrix) + \relates QMatrix + + Same as \a{matrix}.map(\a{point}). + + \sa QMatrix::map() +*/ + +/*! + \overload + + Creates and returns a QPointF object that is a copy of the given + \a point, mapped into the coordinate system defined by this + matrix. +*/ +QPointF QMatrix::map(const QPointF &point) const +{ + qreal fx = point.x(); + qreal fy = point.y(); + return QPointF(_m11*fx + _m21*fy + _dx, _m12*fx + _m22*fy + _dy); +} + +/*! + \fn QPoint QMatrix::map(const QPoint &point) const + \overload + + Creates and returns a QPoint object that is a copy of the given \a + point, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ + +/*! + \fn QLineF operator*(const QLineF &line, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{line}). + + \sa QMatrix::map() +*/ + +/*! + \fn QLine operator*(const QLine &line, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{line}). + + \sa QMatrix::map() +*/ + +/*! + \overload + + Creates and returns a QLineF object that is a copy of the given \a + line, mapped into the coordinate system defined by this matrix. +*/ +QLineF QMatrix::map(const QLineF &line) const +{ + return QLineF(map(line.p1()), map(line.p2())); +} + +/*! + \overload + + Creates and returns a QLine object that is a copy of the given \a + line, mapped into the coordinate system defined by this matrix. + Note that the transformed coordinates are rounded to the nearest + integer. +*/ +QLine QMatrix::map(const QLine &line) const +{ + return QLine(map(line.p1()), map(line.p2())); +} + +/*! + \fn QPolygonF operator *(const QPolygonF &polygon, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{polygon}). + + \sa QMatrix::map() +*/ + +/*! + \fn QPolygon operator*(const QPolygon &polygon, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{polygon}). + + \sa QMatrix::map() +*/ + +QPolygon QMatrix::map(const QPolygon &a) const +{ + int size = a.size(); + int i; + QPolygon p(size); + const QPoint *da = a.constData(); + QPoint *dp = p.data(); + for(i = 0; i < size; i++) { + MAPINT(da[i].x(), da[i].y(), dp[i].rx(), dp[i].ry()); + } + return p; +} + +/*! + \fn QPolygonF QMatrix::map(const QPolygonF &polygon) const + \overload + + Creates and returns a QPolygonF object that is a copy of the given + \a polygon, mapped into the coordinate system defined by this + matrix. +*/ +QPolygonF QMatrix::map(const QPolygonF &a) const +{ + int size = a.size(); + int i; + QPolygonF p(size); + const QPointF *da = a.constData(); + QPointF *dp = p.data(); + for(i = 0; i < size; i++) { + MAPDOUBLE(da[i].xp, da[i].yp, dp[i].xp, dp[i].yp); + } + return p; +} + +/*! + \fn QPolygon QMatrix::map(const QPolygon &polygon) const + \overload + + Creates and returns a QPolygon object that is a copy of the given + \a polygon, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ + +/*! + \fn QRegion operator*(const QRegion ®ion, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{region}). + + \sa QMatrix::map() +*/ + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +/*! + \fn QRegion QMatrix::map(const QRegion ®ion) const + \overload + + Creates and returns a QRegion object that is a copy of the given + \a region, mapped into the coordinate system defined by this matrix. + + Calling this method can be rather expensive if rotations or + shearing are used. +*/ +QRegion QMatrix::map(const QRegion &r) const +{ + if (_m11 == 1.0 && _m22 == 1.0 && _m12 == 0.0 && _m21 == 0.0) { // translate or identity + if (_dx == 0.0 && _dy == 0.0) // Identity + return r; + QRegion copy(r); + copy.translate(qRound(_dx), qRound(_dy)); + return copy; + } + + QPainterPath p = map(qt_regionToPath(r)); + return p.toFillPolygon().toPolygon(); +} + +/*! + \fn QPainterPath operator *(const QPainterPath &path, const QMatrix &matrix) + \relates QMatrix + + This is the same as \a{matrix}.map(\a{path}). + + \sa QMatrix::map() +*/ + +/*! + \overload + + Creates and returns a QPainterPath object that is a copy of the + given \a path, mapped into the coordinate system defined by this + matrix. +*/ +QPainterPath QMatrix::map(const QPainterPath &path) const +{ + if (path.isEmpty()) + return QPainterPath(); + + QPainterPath copy = path; + + // Translate or identity + if (_m11 == 1.0 && _m22 == 1.0 && _m12 == 0.0 && _m21 == 0.0) { + + // Translate + if (_dx != 0.0 || _dy != 0.0) { + copy.detach(); + for (int i=0; i<path.elementCount(); ++i) { + QPainterPath::Element &e = copy.d_ptr->elements[i]; + e.x += _dx; + e.y += _dy; + } + } + + // Full xform + } else { + copy.detach(); + for (int i=0; i<path.elementCount(); ++i) { + QPainterPath::Element &e = copy.d_ptr->elements[i]; + qreal fx = e.x, fy = e.y; + e.x = _m11*fx + _m21*fy + _dx; + e.y = _m12*fx + _m22*fy + _dy; + } + } + + return copy; +} + +/*! + \fn QRegion QMatrix::mapToRegion(const QRect &rectangle) const + + Returns the transformed rectangle \a rectangle as a QRegion + object. A rectangle which has been rotated or sheared may result + in a non-rectangular region being returned. + + Use the mapToPolygon() or map() function instead. +*/ +#ifdef QT3_SUPPORT +QRegion QMatrix::mapToRegion(const QRect &rect) const +{ + QRegion result; + if (isIdentity()) { + result = rect; + } else if (m12() == 0.0F && m21() == 0.0F) { + int x = qRound(m11()*rect.x() + dx()); + int y = qRound(m22()*rect.y() + dy()); + int w = qRound(m11()*rect.width()); + int h = qRound(m22()*rect.height()); + if (w < 0) { + w = -w; + x -= w - 1; + } + if (h < 0) { + h = -h; + y -= h - 1; + } + result = QRect(x, y, w, h); + } else { + result = QRegion(mapToPolygon(rect)); + } + return result; + +} +#endif +/*! + \fn QPolygon QMatrix::mapToPolygon(const QRect &rectangle) const + + Creates and returns a QPolygon representation of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. + + The rectangle's coordinates are transformed using the following + formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qmatrix.cpp 3 + + Polygons and rectangles behave slightly differently when + transformed (due to integer rounding), so + \c{matrix.map(QPolygon(rectangle))} is not always the same as + \c{matrix.mapToPolygon(rectangle)}. + + \sa mapRect(), {QMatrix#Basic Matrix Operations}{Basic Matrix + Operations} +*/ +QPolygon QMatrix::mapToPolygon(const QRect &rect) const +{ + QPolygon a(4); + qreal x[4], y[4]; + if (_m12 == 0.0F && _m21 == 0.0F) { + x[0] = _m11*rect.x() + _dx; + y[0] = _m22*rect.y() + _dy; + qreal w = _m11*rect.width(); + qreal h = _m22*rect.height(); + if (w < 0) { + w = -w; + x[0] -= w; + } + if (h < 0) { + h = -h; + y[0] -= h; + } + x[1] = x[0]+w; + x[2] = x[1]; + x[3] = x[0]; + y[1] = y[0]; + y[2] = y[0]+h; + y[3] = y[2]; + } else { + qreal right = rect.x() + rect.width(); + qreal bottom = rect.y() + rect.height(); + MAPDOUBLE(rect.x(), rect.y(), x[0], y[0]); + MAPDOUBLE(right, rect.y(), x[1], y[1]); + MAPDOUBLE(right, bottom, x[2], y[2]); + MAPDOUBLE(rect.x(), bottom, x[3], y[3]); + } +#if 0 + int i; + for(i = 0; i< 4; i++) + qDebug("coords(%d) = (%f/%f) (%d/%d)", i, x[i], y[i], qRound(x[i]), qRound(y[i])); + qDebug("width=%f, height=%f", qSqrt((x[1]-x[0])*(x[1]-x[0]) + (y[1]-y[0])*(y[1]-y[0])), + qSqrt((x[0]-x[3])*(x[0]-x[3]) + (y[0]-y[3])*(y[0]-y[3]))); +#endif + // all coordinates are correctly, tranform to a pointarray + // (rounding to the next integer) + a.setPoints(4, qRound(x[0]), qRound(y[0]), + qRound(x[1]), qRound(y[1]), + qRound(x[2]), qRound(y[2]), + qRound(x[3]), qRound(y[3])); + return a; +} + +/*! + Resets the matrix to an identity matrix, i.e. all elements are set + to zero, except \c m11 and \c m22 (specifying the scale) which are + set to 1. + + \sa QMatrix(), isIdentity(), {QMatrix#Basic Matrix + Operations}{Basic Matrix Operations} +*/ + +void QMatrix::reset() +{ + _m11 = _m22 = 1.0; + _m12 = _m21 = _dx = _dy = 0.0; +} + +/*! + \fn bool QMatrix::isIdentity() const + + Returns true if the matrix is the identity matrix, otherwise + returns false. + + \sa reset() +*/ + +/*! + Moves the coordinate system \a dx along the x axis and \a dy along + the y axis, and returns a reference to the matrix. + + \sa setMatrix() +*/ + +QMatrix &QMatrix::translate(qreal dx, qreal dy) +{ + _dx += dx*_m11 + dy*_m21; + _dy += dy*_m22 + dx*_m12; + return *this; +} + +/*! + \fn QMatrix &QMatrix::scale(qreal sx, qreal sy) + + Scales the coordinate system by \a sx horizontally and \a sy + vertically, and returns a reference to the matrix. + + \sa setMatrix() +*/ + +QMatrix &QMatrix::scale(qreal sx, qreal sy) +{ + _m11 *= sx; + _m12 *= sx; + _m21 *= sy; + _m22 *= sy; + return *this; +} + +/*! + Shears the coordinate system by \a sh horizontally and \a sv + vertically, and returns a reference to the matrix. + + \sa setMatrix() +*/ + +QMatrix &QMatrix::shear(qreal sh, qreal sv) +{ + qreal tm11 = sv*_m21; + qreal tm12 = sv*_m22; + qreal tm21 = sh*_m11; + qreal tm22 = sh*_m12; + _m11 += tm11; + _m12 += tm12; + _m21 += tm21; + _m22 += tm22; + return *this; +} + +const qreal deg2rad = qreal(0.017453292519943295769); // pi/180 + +/*! + \fn QMatrix &QMatrix::rotate(qreal degrees) + + Rotates the coordinate system the given \a degrees + counterclockwise. + + Note that if you apply a QMatrix to a point defined in widget + coordinates, the direction of the rotation will be clockwise + because the y-axis points downwards. + + Returns a reference to the matrix. + + \sa setMatrix() +*/ + +QMatrix &QMatrix::rotate(qreal a) +{ + qreal sina = 0; + qreal cosa = 0; + if (a == 90. || a == -270.) + sina = 1.; + else if (a == 270. || a == -90.) + sina = -1.; + else if (a == 180.) + cosa = -1.; + else{ + qreal b = deg2rad*a; // convert to radians + sina = qSin(b); // fast and convenient + cosa = qCos(b); + } + qreal tm11 = cosa*_m11 + sina*_m21; + qreal tm12 = cosa*_m12 + sina*_m22; + qreal tm21 = -sina*_m11 + cosa*_m21; + qreal tm22 = -sina*_m12 + cosa*_m22; + _m11 = tm11; _m12 = tm12; + _m21 = tm21; _m22 = tm22; + return *this; +} + +/*! + \fn bool QMatrix::isInvertible() const + + Returns true if the matrix is invertible, otherwise returns false. + + \sa inverted() +*/ + +/*! + \obsolete + \fn qreal QMatrix::det() const + + Returns the matrix's determinant. + + \sa determinant() +*/ + +/*! + \since 4.6 + \fn qreal QMatrix::determinant() const + + Returns the matrix's determinant. +*/ + +/*! + \fn QMatrix QMatrix::invert(bool *invertible) const + + Returns an inverted copy of this matrix. + + Use the inverted() function instead. +*/ + +/*! + Returns an inverted copy of this matrix. + + If the matrix is singular (not invertible), the returned matrix is + the identity matrix. If \a invertible is valid (i.e. not 0), its + value is set to true if the matrix is invertible, otherwise it is + set to false. + + \sa isInvertible() +*/ + +QMatrix QMatrix::inverted(bool *invertible) const +{ + qreal dtr = determinant(); + if (dtr == 0.0) { + if (invertible) + *invertible = false; // singular matrix + return QMatrix(true); + } + else { // invertible matrix + if (invertible) + *invertible = true; + qreal dinv = 1.0/dtr; + return QMatrix((_m22*dinv), (-_m12*dinv), + (-_m21*dinv), (_m11*dinv), + ((_m21*_dy - _m22*_dx)*dinv), + ((_m12*_dx - _m11*_dy)*dinv), + true); + } +} + + +/*! + \fn bool QMatrix::operator==(const QMatrix &matrix) const + + Returns true if this matrix is equal to the given \a matrix, + otherwise returns false. +*/ + +bool QMatrix::operator==(const QMatrix &m) const +{ + return _m11 == m._m11 && + _m12 == m._m12 && + _m21 == m._m21 && + _m22 == m._m22 && + _dx == m._dx && + _dy == m._dy; +} + +/*! + \fn bool QMatrix::operator!=(const QMatrix &matrix) const + + Returns true if this matrix is not equal to the given \a matrix, + otherwise returns false. +*/ + +bool QMatrix::operator!=(const QMatrix &m) const +{ + return _m11 != m._m11 || + _m12 != m._m12 || + _m21 != m._m21 || + _m22 != m._m22 || + _dx != m._dx || + _dy != m._dy; +} + +/*! + \fn QMatrix &QMatrix::operator *=(const QMatrix &matrix) + \overload + + Returns the result of multiplying this matrix by the given \a + matrix. +*/ + +QMatrix &QMatrix::operator *=(const QMatrix &m) +{ + qreal tm11 = _m11*m._m11 + _m12*m._m21; + qreal tm12 = _m11*m._m12 + _m12*m._m22; + qreal tm21 = _m21*m._m11 + _m22*m._m21; + qreal tm22 = _m21*m._m12 + _m22*m._m22; + + qreal tdx = _dx*m._m11 + _dy*m._m21 + m._dx; + qreal tdy = _dx*m._m12 + _dy*m._m22 + m._dy; + + _m11 = tm11; _m12 = tm12; + _m21 = tm21; _m22 = tm22; + _dx = tdx; _dy = tdy; + return *this; +} + +/*! + \fn QMatrix QMatrix::operator *(const QMatrix &matrix) const + + Returns the result of multiplying this matrix by the given \a + matrix. + + Note that matrix multiplication is not commutative, i.e. a*b != + b*a. +*/ + +QMatrix QMatrix::operator *(const QMatrix &m) const +{ + qreal tm11 = _m11*m._m11 + _m12*m._m21; + qreal tm12 = _m11*m._m12 + _m12*m._m22; + qreal tm21 = _m21*m._m11 + _m22*m._m21; + qreal tm22 = _m21*m._m12 + _m22*m._m22; + + qreal tdx = _dx*m._m11 + _dy*m._m21 + m._dx; + qreal tdy = _dx*m._m12 + _dy*m._m22 + m._dy; + return QMatrix(tm11, tm12, tm21, tm22, tdx, tdy, true); +} + +/*! + Assigns the given \a matrix's values to this matrix. +*/ +QMatrix &QMatrix::operator=(const QMatrix &matrix) +{ + _m11 = matrix._m11; + _m12 = matrix._m12; + _m21 = matrix._m21; + _m22 = matrix._m22; + _dx = matrix._dx; + _dy = matrix._dy; + return *this; +} + +/*! + \since 4.2 + + Returns the matrix as a QVariant. +*/ +QMatrix::operator QVariant() const +{ + return QVariant(QVariant::Matrix, this); +} + +Q_GUI_EXPORT QPainterPath operator *(const QPainterPath &p, const QMatrix &m) +{ + return m.map(p); +} + + +/***************************************************************************** + QMatrix stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QMatrix &matrix) + \relates QMatrix + + Writes the given \a matrix to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QMatrix &m) +{ + if (s.version() == 1) { + s << (float)m.m11() << (float)m.m12() << (float)m.m21() + << (float)m.m22() << (float)m.dx() << (float)m.dy(); + } else { + s << double(m.m11()) + << double(m.m12()) + << double(m.m21()) + << double(m.m22()) + << double(m.dx()) + << double(m.dy()); + } + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QMatrix &matrix) + \relates QMatrix + + Reads the given \a matrix from the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QMatrix &m) +{ + if (s.version() == 1) { + float m11, m12, m21, m22, dx, dy; + s >> m11; s >> m12; s >> m21; s >> m22; + s >> dx; s >> dy; + m.setMatrix(m11, m12, m21, m22, dx, dy); + } + else { + double m11, m12, m21, m22, dx, dy; + s >> m11; + s >> m12; + s >> m21; + s >> m22; + s >> dx; + s >> dy; + m.setMatrix(m11, m12, m21, m22, dx, dy); + } + return s; +} +#endif // QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QMatrix &m) +{ + dbg.nospace() << "QMatrix(" + << "11=" << m.m11() + << " 12=" << m.m12() + << " 21=" << m.m21() + << " 22=" << m.m22() + << " dx=" << m.dx() + << " dy=" << m.dy() + << ')'; + return dbg.space(); +} +#endif + +/*! + \fn QRect QMatrix::map(const QRect &rect) const + \compat + + Creates and returns a QRect object that is a copy of the given + rectangle, mapped into the coordinate system defined by this + matrix. + + Use the mapRect() function instead. +*/ + + +/*! + \fn bool qFuzzyCompare(const QMatrix& m1, const QMatrix& m2) + + \relates QMatrix + \since 4.6 + + \brief The qFuzzyCompare function is for comparing two matrices + using a fuzziness factor. + + Returns true if \a m1 and \a m2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/painting/qmatrix.h b/src/gui/painting/qmatrix.h new file mode 100644 index 0000000000..898cfa4cf2 --- /dev/null +++ b/src/gui/painting/qmatrix.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMATRIX_H +#define QMATRIX_H + +#include <QtGui/qpolygon.h> +#include <QtGui/qregion.h> +#include <QtGui/qwindowdefs.h> +#include <QtCore/qline.h> +#include <QtCore/qpoint.h> +#include <QtCore/qrect.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPainterPath; +class QVariant; + +class Q_GUI_EXPORT QMatrix // 2D transform matrix +{ +public: + inline explicit QMatrix(Qt::Initialization) {} + QMatrix(); + QMatrix(qreal m11, qreal m12, qreal m21, qreal m22, + qreal dx, qreal dy); + QMatrix(const QMatrix &matrix); + + void setMatrix(qreal m11, qreal m12, qreal m21, qreal m22, + qreal dx, qreal dy); + + qreal m11() const { return _m11; } + qreal m12() const { return _m12; } + qreal m21() const { return _m21; } + qreal m22() const { return _m22; } + qreal dx() const { return _dx; } + qreal dy() const { return _dy; } + + void map(int x, int y, int *tx, int *ty) const; + void map(qreal x, qreal y, qreal *tx, qreal *ty) const; + QRect mapRect(const QRect &) const; + QRectF mapRect(const QRectF &) const; + + QPoint map(const QPoint &p) const; + QPointF map(const QPointF&p) const; + QLine map(const QLine &l) const; + QLineF map(const QLineF &l) const; + QPolygonF map(const QPolygonF &a) const; + QPolygon map(const QPolygon &a) const; + QRegion map(const QRegion &r) const; + QPainterPath map(const QPainterPath &p) const; + QPolygon mapToPolygon(const QRect &r) const; + + void reset(); + inline bool isIdentity() const; + + QMatrix &translate(qreal dx, qreal dy); + QMatrix &scale(qreal sx, qreal sy); + QMatrix &shear(qreal sh, qreal sv); + QMatrix &rotate(qreal a); + + bool isInvertible() const { return !qFuzzyIsNull(_m11*_m22 - _m12*_m21); } + qreal determinant() const { return _m11*_m22 - _m12*_m21; } +#ifdef QT_DEPRECATED + QT_DEPRECATED qreal det() const { return _m11*_m22 - _m12*_m21; } +#endif + + QMatrix inverted(bool *invertible = 0) const; + + bool operator==(const QMatrix &) const; + bool operator!=(const QMatrix &) const; + + QMatrix &operator*=(const QMatrix &); + QMatrix operator*(const QMatrix &o) const; + + QMatrix &operator=(const QMatrix &); + + operator QVariant() const; + +#ifdef QT3_SUPPORT + inline QT3_SUPPORT QMatrix invert(bool *invertible=0) const { return inverted(invertible); } + inline QT3_SUPPORT QRect map(const QRect &r) const { return mapRect(r); } + QT3_SUPPORT QRegion mapToRegion(const QRect &r) const; +#endif + +private: + inline QMatrix(bool) + : _m11(1.) + , _m12(0.) + , _m21(0.) + , _m22(1.) + , _dx(0.) + , _dy(0.) {} + inline QMatrix(qreal am11, qreal am12, qreal am21, qreal am22, qreal adx, qreal ady, bool) + : _m11(am11) + , _m12(am12) + , _m21(am21) + , _m22(am22) + , _dx(adx) + , _dy(ady) {} + friend class QTransform; + qreal _m11, _m12; + qreal _m21, _m22; + qreal _dx, _dy; +}; +Q_DECLARE_TYPEINFO(QMatrix, Q_MOVABLE_TYPE); + +// mathematical semantics +Q_GUI_EXPORT_INLINE QPoint operator*(const QPoint &p, const QMatrix &m) +{ return m.map(p); } +Q_GUI_EXPORT_INLINE QPointF operator*(const QPointF &p, const QMatrix &m) +{ return m.map(p); } +Q_GUI_EXPORT_INLINE QLineF operator*(const QLineF &l, const QMatrix &m) +{ return m.map(l); } +Q_GUI_EXPORT_INLINE QLine operator*(const QLine &l, const QMatrix &m) +{ return m.map(l); } +Q_GUI_EXPORT_INLINE QPolygon operator *(const QPolygon &a, const QMatrix &m) +{ return m.map(a); } +Q_GUI_EXPORT_INLINE QPolygonF operator *(const QPolygonF &a, const QMatrix &m) +{ return m.map(a); } +Q_GUI_EXPORT_INLINE QRegion operator *(const QRegion &r, const QMatrix &m) +{ return m.map(r); } +Q_GUI_EXPORT QPainterPath operator *(const QPainterPath &p, const QMatrix &m); + +inline bool QMatrix::isIdentity() const +{ + return qFuzzyIsNull(_m11 - 1) && qFuzzyIsNull(_m22 - 1) && qFuzzyIsNull(_m12) + && qFuzzyIsNull(_m21) && qFuzzyIsNull(_dx) && qFuzzyIsNull(_dy); +} + +inline bool qFuzzyCompare(const QMatrix& m1, const QMatrix& m2) +{ + return qFuzzyCompare(m1.m11(), m2.m11()) + && qFuzzyCompare(m1.m12(), m2.m12()) + && qFuzzyCompare(m1.m21(), m2.m21()) + && qFuzzyCompare(m1.m22(), m2.m22()) + && qFuzzyCompare(m1.dx(), m2.dx()) + && qFuzzyCompare(m1.dy(), m2.dy()); +} + + +/***************************************************************************** + QMatrix stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QMatrix &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QMatrix &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QMatrix &); +#endif + +#ifdef QT3_SUPPORT +QT_BEGIN_INCLUDE_NAMESPACE +#include <QtGui/qwmatrix.h> +QT_END_INCLUDE_NAMESPACE +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMATRIX_H diff --git a/src/gui/painting/qmemrotate.cpp b/src/gui/painting/qmemrotate.cpp new file mode 100644 index 0000000000..fe524aff3b --- /dev/null +++ b/src/gui/painting/qmemrotate.cpp @@ -0,0 +1,648 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qmemrotate_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_ROTATION_ALGORITHM == QT_ROTATION_TILED +static const int tileSize = 32; +#endif + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +#if QT_ROTATION_ALGORITHM == QT_ROTATION_PACKED || QT_ROTATION_ALGORITHM == QT_ROTATION_TILED +#error Big endian version not implemented for the transformed driver! +#endif +#endif + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_cachedRead(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + const char *s = reinterpret_cast<const char*>(src); + char *d = reinterpret_cast<char*>(dest); + for (int y = 0; y < h; ++y) { + for (int x = w - 1; x >= 0; --x) { + DST *destline = reinterpret_cast<DST*>(d + (w - x - 1) * dstride); + destline[y] = qt_colorConvert<DST,SRC>(src[x], 0); + } + s += sstride; + src = reinterpret_cast<const SRC*>(s); + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_cachedRead(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + const char *s = reinterpret_cast<const char*>(src); + char *d = reinterpret_cast<char*>(dest); + s += (h - 1) * sstride; + for (int y = h - 1; y >= 0; --y) { + src = reinterpret_cast<const SRC*>(s); + for (int x = 0; x < w; ++x) { + DST *destline = reinterpret_cast<DST*>(d + x * dstride); + destline[h - y - 1] = qt_colorConvert<DST,SRC>(src[x], 0); + } + s -= sstride; + } +} + +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_cachedWrite(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + for (int x = w - 1; x >= 0; --x) { + DST *d = dest + (w - x - 1) * dstride; + for (int y = 0; y < h; ++y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } + +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_cachedWrite(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + for (int x = 0; x < w; ++x) { + DST *d = dest + x * dstride; + for (int y = h - 1; y >= 0; --y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } +} + +#endif // QT_ROTATION_CACHEDWRITE + +#if QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + +// TODO: packing algorithms should probably be modified on 64-bit architectures + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_packing(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + sstride /= sizeof(SRC); + dstride /= sizeof(DST); + + const int pack = sizeof(quint32) / sizeof(DST); + const int unaligned = int((long(dest) & (sizeof(quint32)-1))) / sizeof(DST); + + for (int x = w - 1; x >= 0; --x) { + int y = 0; + + for (int i = 0; i < unaligned; ++i) { + dest[(w - x - 1) * dstride + y] + = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + ++y; + } + + quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + + unaligned); + const int rest = (h - unaligned) % pack; + while (y < h - rest) { + quint32 c = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + for (int i = 1; i < pack; ++i) { + c |= qt_colorConvert<DST,SRC>(src[(y + i) * sstride + x], 0) + << (sizeof(int) * 8 / pack * i); + } + *d++ = c; + y += pack; + } + + while (y < h) { + dest[(w - x - 1) * dstride + y] + = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + ++y; + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_packing(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + sstride /= sizeof(SRC); + dstride /= sizeof(DST); + + const int pack = sizeof(quint32) / sizeof(DST); + const int unaligned = int((long(dest) & (sizeof(quint32)-1))) / sizeof(DST); + + for (int x = 0; x < w; ++x) { + int y = h - 1; + + for (int i = 0; i < unaligned; ++i) { + dest[x * dstride + h - y - 1] + = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + --y; + } + + quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride + + unaligned); + const int rest = (h - unaligned) % pack; + while (y > rest) { + quint32 c = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + for (int i = 1; i < pack; ++i) { + c |= qt_colorConvert<DST,SRC>(src[(y - i) * sstride + x], 0) + << (sizeof(int) * 8 / pack * i); + } + *d++ = c; + y -= pack; + } + while (y >= 0) { + dest[x * dstride + h - y - 1] + = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + --y; + } + } +} + +#endif // QT_ROTATION_PACKING + +#if QT_ROTATION_ALGORITHM == QT_ROTATION_TILED +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_tiled(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + sstride /= sizeof(SRC); + dstride /= sizeof(DST); + + const int pack = sizeof(quint32) / sizeof(DST); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(DST)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + + if (unaligned) { + for (int x = startx; x >= stopx; --x) { + DST *d = dest + (w - x - 1) * dstride; + for (int y = 0; y < unaligned; ++y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } + } + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize + unaligned; + const int stopy = qMin(starty + tileSize, h - unoptimizedY); + + for (int x = startx; x >= stopx; --x) { + quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + starty); + for (int y = starty; y < stopy; y += pack) { + quint32 c = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(int) * 8 / pack * i); + const DST color = qt_colorConvert<DST,SRC>(src[(y + i) * sstride + x], 0); + c |= color << shift; + } + *d++ = c; + } + } + } + + if (unoptimizedY) { + const int starty = h - unoptimizedY; + for (int x = startx; x >= stopx; --x) { + DST *d = dest + (w - x - 1) * dstride + starty; + for (int y = starty; y < h; ++y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_tiled_unpacked(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize; + const int stopy = qMin(starty + tileSize, h); + + for (int x = startx; x >= stopx; --x) { + DST *d = (DST*)((char*)dest + (w - x - 1) * dstride) + starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y < stopy; ++y) { + *d++ = qt_colorConvert<DST,SRC>(*(const SRC*)(s), 0); + s += sstride; + } + } + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_tiled(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + sstride /= sizeof(SRC); + dstride /= sizeof(DST); + + const int pack = sizeof(quint32) / sizeof(DST); + const int unaligned = + qMin(uint((long(dest) & (sizeof(quint32)-1)) / sizeof(DST)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + + if (unaligned) { + for (int x = startx; x < stopx; ++x) { + DST *d = dest + x * dstride; + for (int y = h - 1; y >= h - unaligned; --y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } + } + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - unaligned - ty * tileSize; + const int stopy = qMax(starty - tileSize, unoptimizedY); + + for (int x = startx; x < stopx; ++x) { + quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride + + h - 1 - starty); + for (int y = starty; y > stopy; y -= pack) { + quint32 c = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(int) * 8 / pack * i); + const DST color = qt_colorConvert<DST,SRC>(src[(y - i) * sstride + x], 0); + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = unoptimizedY - 1; + for (int x = startx; x < stopx; ++x) { + DST *d = dest + x * dstride + h - 1 - starty; + for (int y = starty; y >= 0; --y) { + *d++ = qt_colorConvert<DST,SRC>(src[y * sstride + x], 0); + } + } + } + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_tiled_unpacked(const SRC *src, int w, int h, + int sstride, + DST *dest, int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - ty * tileSize; + const int stopy = qMax(starty - tileSize, 0); + + for (int x = startx; x < stopx; ++x) { + DST *d = (DST*)((char*)dest + x * dstride) + h - 1 - starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y >= stopy; --y) { + *d++ = qt_colorConvert<DST,SRC>(*(const SRC*)s, 0); + s -= sstride; + } + } + } + } +} + +#endif // QT_ROTATION_ALGORITHM + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate90_template(const SRC *src, + int srcWidth, int srcHeight, int srcStride, + DST *dest, int dstStride) +{ +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDREAD + qt_memrotate90_cachedRead<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + qt_memrotate90_cachedWrite<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + qt_memrotate90_packing<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_TILED + qt_memrotate90_tiled<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#endif +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate180_template(const SRC *src, + int w, int h, int sstride, + DST *dest, int dstride) +{ + const char *s = (const char*)(src) + (h - 1) * sstride; + for (int y = h - 1; y >= 0; --y) { + DST *d = reinterpret_cast<DST*>((char *)(dest) + (h - y - 1) * dstride); + src = reinterpret_cast<const SRC*>(s); + for (int x = w - 1; x >= 0; --x) { + d[w - x - 1] = qt_colorConvert<DST,SRC>(src[x], 0); + } + s -= sstride; + } +} + +template <class DST, class SRC> +Q_STATIC_TEMPLATE_FUNCTION inline void qt_memrotate270_template(const SRC *src, + int srcWidth, int srcHeight, int srcStride, + DST *dest, int dstStride) +{ +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDREAD + qt_memrotate270_cachedRead<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + qt_memrotate270_cachedWrite<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + qt_memrotate270_packing<DST,SRC>(src, srcWidth, srcHeight, srcStride, + dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_TILED + qt_memrotate270_tiled_unpacked<DST,SRC>(src, srcWidth, srcHeight, + srcStride, + dest, dstStride); +#endif +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void qt_memrotate90_template<quint24, quint24>(const quint24 *src, + int srcWidth, int srcHeight, int srcStride, + quint24 *dest, int dstStride) +{ +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDREAD + qt_memrotate90_cachedRead<quint24,quint24>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + qt_memrotate90_cachedWrite<quint24,quint24>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + // packed algorithm not implemented + qt_memrotate90_cachedRead<quint24,quint24>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_TILED + // packed algorithm not implemented + qt_memrotate90_tiled_unpacked<quint24,quint24>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#endif +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void qt_memrotate90_template<quint24, quint32>(const quint32 *src, + int srcWidth, int srcHeight, int srcStride, + quint24 *dest, int dstStride) +{ +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDREAD + qt_memrotate90_cachedRead<quint24,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + qt_memrotate90_cachedWrite<quint24,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + // packed algorithm not implemented + qt_memrotate90_cachedRead<quint24,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_TILED + // packed algorithm not implemented + qt_memrotate90_tiled_unpacked<quint24,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#endif +} + +template <> +Q_STATIC_TEMPLATE_SPECIALIZATION +inline void qt_memrotate90_template<quint18, quint32>(const quint32 *src, + int srcWidth, int srcHeight, int srcStride, + quint18 *dest, int dstStride) +{ +#if QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDREAD + qt_memrotate90_cachedRead<quint18,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_CACHEDWRITE + qt_memrotate90_cachedWrite<quint18,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_PACKING + // packed algorithm not implemented + qt_memrotate90_cachedRead<quint18,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#elif QT_ROTATION_ALGORITHM == QT_ROTATION_TILED + // packed algorithm not implemented + qt_memrotate90_tiled_unpacked<quint18,quint32>(src, srcWidth, srcHeight, + srcStride, dest, dstStride); +#endif +} + +#define QT_IMPL_MEMROTATE(srctype, desttype) \ +void qt_memrotate90(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate90_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate270_template(src, w, h, sstride, dest, dstride); \ +} + +#define QT_IMPL_SIMPLE_MEMROTATE(srctype, desttype) \ +void qt_memrotate90(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate90_tiled_unpacked<desttype,srctype>(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const srctype *src, int w, int h, int sstride, \ + desttype *dest, int dstride) \ +{ \ + qt_memrotate270_tiled_unpacked<desttype,srctype>(src, w, h, sstride, dest, dstride); \ +} + + + + +QT_IMPL_MEMROTATE(quint32, quint32) +QT_IMPL_MEMROTATE(quint32, quint16) +QT_IMPL_MEMROTATE(quint16, quint32) +QT_IMPL_MEMROTATE(quint16, quint16) +QT_IMPL_MEMROTATE(quint24, quint24) +QT_IMPL_MEMROTATE(quint32, quint24) +QT_IMPL_MEMROTATE(quint32, quint18) +QT_IMPL_MEMROTATE(quint32, quint8) +QT_IMPL_MEMROTATE(quint16, quint8) +QT_IMPL_MEMROTATE(qrgb444, quint8) +QT_IMPL_MEMROTATE(quint8, quint8) + +#if defined(QT_QWS_ROTATE_BGR) +QT_IMPL_SIMPLE_MEMROTATE(quint16, qbgr565) +QT_IMPL_SIMPLE_MEMROTATE(quint32, qbgr565) +QT_IMPL_SIMPLE_MEMROTATE(qrgb555, qbgr555) +QT_IMPL_SIMPLE_MEMROTATE(quint32, qbgr555) +#endif + +#ifdef QT_QWS_DEPTH_GENERIC +QT_IMPL_MEMROTATE(quint32, qrgb_generic16) +QT_IMPL_MEMROTATE(quint16, qrgb_generic16) +#endif + +struct qrgb_gl_rgba +{ +public: + inline qrgb_gl_rgba(quint32 v) { + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) + data = ((v << 16) & 0xff0000) | ((v >> 16) & 0xff) | (v & 0xff00ff00); + else + data = (v << 8) | ((v >> 24) & 0xff); + } + + inline operator quint32() const { return data; } + +private: + quint32 data; +} Q_PACKED; + +void Q_GUI_EXPORT qt_memrotate90_gl(const quint32 *src, int srcWidth, int srcHeight, int srcStride, + quint32 *dest, int dstStride) +{ + qt_memrotate90_template(src, srcWidth, srcHeight, srcStride, reinterpret_cast<qrgb_gl_rgba *>(dest), dstStride); +} + +void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} + +void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} + +void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} + +void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} + +void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} + +void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} + +MemRotateFunc qMemRotateFunctions[QImage::NImageFormats][3] = +// 90, 180, 270 +{ + { 0, 0, 0 }, // Format_Invalid, + { 0, 0, 0 }, // Format_Mono, + { 0, 0, 0 }, // Format_MonoLSB, + { 0, 0, 0 }, // Format_Indexed8, + { qt_memrotate90_32, qt_memrotate180_32, qt_memrotate270_32 }, // Format_RGB32, + { qt_memrotate90_32, qt_memrotate180_32, qt_memrotate270_32 }, // Format_ARGB32, + { qt_memrotate90_32, qt_memrotate180_32, qt_memrotate270_32 }, // Format_ARGB32_Premultiplied, + { qt_memrotate90_16, qt_memrotate180_16, qt_memrotate270_16 }, // Format_RGB16, + { 0, 0, 0 }, // Format_ARGB8565_Premultiplied, + { 0, 0, 0 }, // Format_RGB666, + { 0, 0, 0 }, // Format_ARGB6666_Premultiplied, + { 0, 0, 0 }, // Format_RGB555, + { 0, 0, 0 }, // Format_ARGB8555_Premultiplied, + { 0, 0, 0 }, // Format_RGB888, + { 0, 0, 0 }, // Format_RGB444, + { 0, 0, 0 } // Format_ARGB4444_Premultiplied, +}; + +QT_END_NAMESPACE diff --git a/src/gui/painting/qmemrotate_p.h b/src/gui/painting/qmemrotate_p.h new file mode 100644 index 0000000000..ecfa39397c --- /dev/null +++ b/src/gui/painting/qmemrotate_p.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMEMROTATE_P_H +#define QMEMROTATE_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/qdrawhelper_p.h" + +QT_BEGIN_NAMESPACE + +#define QT_ROTATION_CACHEDREAD 1 +#define QT_ROTATION_CACHEDWRITE 2 +#define QT_ROTATION_PACKING 3 +#define QT_ROTATION_TILED 4 + +#ifndef QT_ROTATION_ALGORITHM +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +#define QT_ROTATION_ALGORITHM QT_ROTATION_TILED +#else +#define QT_ROTATION_ALGORITHM QT_ROTATION_CACHEDREAD +#endif +#endif + +#ifdef Q_WS_QWS +#define Q_GUI_QWS_EXPORT Q_GUI_EXPORT +#else +#define Q_GUI_QWS_EXPORT +#endif + +#define QT_DECL_MEMROTATE(srctype, desttype) \ + void Q_GUI_QWS_EXPORT qt_memrotate90(const srctype*, int, int, int, desttype*, int); \ + void Q_GUI_QWS_EXPORT qt_memrotate180(const srctype*, int, int, int, desttype*, int); \ + void Q_GUI_QWS_EXPORT qt_memrotate270(const srctype*, int, int, int, desttype*, int) + +void Q_GUI_EXPORT qt_memrotate90(const quint32*, int, int, int, quint32*, int); +void Q_GUI_QWS_EXPORT qt_memrotate180(const quint32*, int, int, int, quint32*, int); +void Q_GUI_QWS_EXPORT qt_memrotate270(const quint32*, int, int, int, quint32*, int); + +QT_DECL_MEMROTATE(quint32, quint16); +QT_DECL_MEMROTATE(quint16, quint32); +QT_DECL_MEMROTATE(quint16, quint16); +QT_DECL_MEMROTATE(quint24, quint24); +QT_DECL_MEMROTATE(quint32, quint24); +QT_DECL_MEMROTATE(quint32, quint18); +QT_DECL_MEMROTATE(quint32, quint8); +QT_DECL_MEMROTATE(quint16, quint8); +QT_DECL_MEMROTATE(qrgb444, quint8); +QT_DECL_MEMROTATE(quint8, quint8); + +#ifdef QT_QWS_ROTATE_BGR +QT_DECL_MEMROTATE(quint16, qbgr565); +QT_DECL_MEMROTATE(quint32, qbgr565); +QT_DECL_MEMROTATE(qrgb555, qbgr555); +QT_DECL_MEMROTATE(quint32, qbgr555); +#endif + +#ifdef QT_QWS_DEPTH_GENERIC +QT_DECL_MEMROTATE(quint32, qrgb_generic16); +QT_DECL_MEMROTATE(quint16, qrgb_generic16); +#endif + +#undef QT_DECL_MEMROTATE + +QT_END_NAMESPACE + +#endif // QMEMROTATE_P_H diff --git a/src/gui/painting/qoutlinemapper.cpp b/src/gui/painting/qoutlinemapper.cpp new file mode 100644 index 0000000000..aac50838d7 --- /dev/null +++ b/src/gui/painting/qoutlinemapper.cpp @@ -0,0 +1,393 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qoutlinemapper_p.h" +#include <private/qpainterpath_p.h> +#include "qmath.h" + +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +#define qreal_to_fixed_26_6(f) (int(f * 64)) + + + + +static const QRectF boundingRect(const QPointF *points, int pointCount) +{ + const QPointF *e = points; + const QPointF *last = points + pointCount; + qreal minx, maxx, miny, maxy; + minx = maxx = e->x(); + miny = maxy = e->y(); + while (++e < last) { + if (e->x() < minx) + minx = e->x(); + else if (e->x() > maxx) + maxx = e->x(); + if (e->y() < miny) + miny = e->y(); + else if (e->y() > maxy) + maxy = e->y(); + } + return QRectF(QPointF(minx, miny), QPointF(maxx, maxy)); +} + + +QT_FT_Outline *QOutlineMapper::convertPath(const QPainterPath &path) +{ + Q_ASSERT(!path.isEmpty()); + int elmCount = path.elementCount(); +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::convertPath(), size=%d\n", elmCount); +#endif + beginOutline(path.fillRule()); + + for (int index=0; index<elmCount; ++index) { + const QPainterPath::Element &elm = path.elementAt(index); + + switch (elm.type) { + + case QPainterPath::MoveToElement: + if (index == elmCount - 1) + continue; + moveTo(elm); + break; + + case QPainterPath::LineToElement: + lineTo(elm); + break; + + case QPainterPath::CurveToElement: + curveTo(elm, path.elementAt(index + 1), path.elementAt(index + 2)); + index += 2; + break; + + default: + break; // This will never hit.. + } + } + + endOutline(); + return outline(); +} + +QT_FT_Outline *QOutlineMapper::convertPath(const QVectorPath &path) +{ + int count = path.elementCount(); + +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::convertPath(VP), size=%d\n", count); +#endif + beginOutline(path.hasWindingFill() ? Qt::WindingFill : Qt::OddEvenFill); + + if (path.elements()) { + // TODO: if we do closing of subpaths in convertElements instead we + // could avoid this loop + const QPainterPath::ElementType *elements = path.elements(); + const QPointF *points = reinterpret_cast<const QPointF *>(path.points()); + + for (int index = 0; index < count; ++index) { + switch (elements[index]) { + case QPainterPath::MoveToElement: + if (index == count - 1) + continue; + moveTo(points[index]); + break; + + case QPainterPath::LineToElement: + lineTo(points[index]); + break; + + case QPainterPath::CurveToElement: + curveTo(points[index], points[index+1], points[index+2]); + index += 2; + break; + + default: + break; // This will never hit.. + } + } + + } else { + // ### We can kill this copying and just use the buffer straight... + + m_elements.resize(count); + if (count) + memcpy(m_elements.data(), path.points(), count* sizeof(QPointF)); + + m_element_types.resize(0); + } + + endOutline(); + return outline(); +} + + +void QOutlineMapper::endOutline() +{ + closeSubpath(); + + int element_count = m_elements.size(); + + if (element_count == 0) { + memset(&m_outline, 0, sizeof(m_outline)); + return; + } + + QPointF *elements; + + // Transform the outline + if (m_txop == QTransform::TxNone) { + elements = m_elements.data(); + } else { + if (m_txop == QTransform::TxTranslate) { + for (int i=0; i<m_elements.size(); ++i) { + const QPointF &e = m_elements.at(i); + m_elements_dev << QPointF(e.x() + m_dx, e.y() + m_dy); + } + } else if (m_txop == QTransform::TxScale) { + for (int i=0; i<m_elements.size(); ++i) { + const QPointF &e = m_elements.at(i); + m_elements_dev << QPointF(m_m11 * e.x() + m_dx, m_m22 * e.y() + m_dy); + } + } else if (m_txop < QTransform::TxProject) { + for (int i=0; i<m_elements.size(); ++i) { + const QPointF &e = m_elements.at(i); + m_elements_dev << QPointF(m_m11 * e.x() + m_m21 * e.y() + m_dx, + m_m22 * e.y() + m_m12 * e.x() + m_dy); + } + } else { + const QVectorPath vp((qreal *)m_elements.data(), m_elements.size(), m_element_types.size() ? m_element_types.data() : 0); + QPainterPath path = vp.convertToPainterPath(); + path = QTransform(m_m11, m_m12, m_m13, m_m21, m_m22, m_m23, m_dx, m_dy, m_m33).map(path); + if (!(m_outline.flags & QT_FT_OUTLINE_EVEN_ODD_FILL)) + path.setFillRule(Qt::WindingFill); + uint old_txop = m_txop; + m_txop = QTransform::TxNone; + if (path.isEmpty()) + m_valid = false; + else + convertPath(path); + m_txop = old_txop; + return; + } + elements = m_elements_dev.data(); + } + + controlPointRect = boundingRect(elements, element_count); + +#ifdef QT_DEBUG_CONVERT + printf(" - control point rect (%.2f, %.2f) %.2f x %.2f, clip=(%d,%d, %dx%d)\n", + controlPointRect.x(), controlPointRect.y(), + controlPointRect.width(), controlPointRect.height(), + m_clip_rect.x(), m_clip_rect.y(), m_clip_rect.width(), m_clip_rect.height()); +#endif + + + // Check for out of dev bounds... + const bool do_clip = !m_in_clip_elements && ((controlPointRect.left() < -QT_RASTER_COORD_LIMIT + || controlPointRect.right() > QT_RASTER_COORD_LIMIT + || controlPointRect.top() < -QT_RASTER_COORD_LIMIT + || controlPointRect.bottom() > QT_RASTER_COORD_LIMIT + || controlPointRect.width() > QT_RASTER_COORD_LIMIT + || controlPointRect.height() > QT_RASTER_COORD_LIMIT)); + + if (do_clip) { + clipElements(elements, elementTypes(), element_count); + } else { + convertElements(elements, elementTypes(), element_count); + } +} + +void QOutlineMapper::convertElements(const QPointF *elements, + const QPainterPath::ElementType *types, + int element_count) +{ + + if (types) { + // Translate into FT coords + const QPointF *e = elements; + for (int i=0; i<element_count; ++i) { + switch (*types) { + case QPainterPath::MoveToElement: + { + QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()), + qreal_to_fixed_26_6(e->y()) }; + if (i != 0) + m_contours << m_points.size() - 1; + m_points << pt_fixed; + m_tags << QT_FT_CURVE_TAG_ON; + } + break; + + case QPainterPath::LineToElement: + { + QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()), + qreal_to_fixed_26_6(e->y()) }; + m_points << pt_fixed; + m_tags << QT_FT_CURVE_TAG_ON; + } + break; + + case QPainterPath::CurveToElement: + { + QT_FT_Vector cp1_fixed = { qreal_to_fixed_26_6(e->x()), + qreal_to_fixed_26_6(e->y()) }; + ++e; + QT_FT_Vector cp2_fixed = { qreal_to_fixed_26_6((e)->x()), + qreal_to_fixed_26_6((e)->y()) }; + ++e; + QT_FT_Vector ep_fixed = { qreal_to_fixed_26_6((e)->x()), + qreal_to_fixed_26_6((e)->y()) }; + + m_points << cp1_fixed << cp2_fixed << ep_fixed; + m_tags << QT_FT_CURVE_TAG_CUBIC + << QT_FT_CURVE_TAG_CUBIC + << QT_FT_CURVE_TAG_ON; + + types += 2; + i += 2; + } + break; + default: + break; + } + ++types; + ++e; + } + } else { + // Plain polygon... + const QPointF *last = elements + element_count; + const QPointF *e = elements; + while (e < last) { + QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()), + qreal_to_fixed_26_6(e->y()) }; + m_points << pt_fixed; + m_tags << QT_FT_CURVE_TAG_ON; + ++e; + } + } + + // close the very last subpath + m_contours << m_points.size() - 1; + + m_outline.n_contours = m_contours.size(); + m_outline.n_points = m_points.size(); + + m_outline.points = m_points.data(); + m_outline.tags = m_tags.data(); + m_outline.contours = m_contours.data(); + +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::endOutline\n"); + + printf(" - contours: %d\n", m_outline.n_contours); + for (int i=0; i<m_outline.n_contours; ++i) { + printf(" - %d\n", m_outline.contours[i]); + } + + printf(" - points: %d\n", m_outline.n_points); + for (int i=0; i<m_outline.n_points; ++i) { + printf(" - %d -- %.2f, %.2f, (%d, %d)\n", i, + (double) (m_outline.points[i].x / 64.0), + (double) (m_outline.points[i].y / 64.0), + (int) m_outline.points[i].x, (int) m_outline.points[i].y); + } +#endif +} + +void QOutlineMapper::clipElements(const QPointF *elements, + const QPainterPath::ElementType *types, + int element_count) +{ + // We could save a bit of time by actually implementing them fully + // instead of going through convenience functionallity, but since + // this part of code hardly every used, it shouldn't matter. + + m_in_clip_elements = true; + + QPainterPath path; + + if (!(m_outline.flags & QT_FT_OUTLINE_EVEN_ODD_FILL)) + path.setFillRule(Qt::WindingFill); + + if (types) { + for (int i=0; i<element_count; ++i) { + switch (types[i]) { + case QPainterPath::MoveToElement: + path.moveTo(elements[i]); + break; + + case QPainterPath::LineToElement: + path.lineTo(elements[i]); + break; + + case QPainterPath::CurveToElement: + path.cubicTo(elements[i], elements[i+1], elements[i+2]); + i += 2; + break; + default: + break; + } + } + } else { + path.moveTo(elements[0]); + for (int i=1; i<element_count; ++i) + path.lineTo(elements[i]); + } + + QPainterPath clipPath; + clipPath.addRect(m_clip_rect); + QPainterPath clippedPath = path.intersected(clipPath); + uint old_txop = m_txop; + m_txop = QTransform::TxNone; + if (clippedPath.isEmpty()) + m_valid = false; + else + convertPath(clippedPath); + m_txop = old_txop; + + m_in_clip_elements = false; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qoutlinemapper_p.h b/src/gui/painting/qoutlinemapper_p.h new file mode 100644 index 0000000000..4dd28ac172 --- /dev/null +++ b/src/gui/painting/qoutlinemapper_p.h @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOUTLINEMAPPER_P_H +#define QOUTLINEMAPPER_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 <QtCore/qrect.h> + +#include <QtGui/qtransform.h> +#include <QtGui/qpainterpath.h> + +#define QT_FT_BEGIN_HEADER +#define QT_FT_END_HEADER + +#include <private/qrasterdefs_p.h> +#include <private/qdatabuffer_p.h> +#include "qpaintengineex_p.h" + +QT_BEGIN_NAMESPACE + +// This limitations comes from qgrayraster.c. Any higher and +// rasterization of shapes will produce incorrect results. +const int QT_RASTER_COORD_LIMIT = 32767; + +//#define QT_DEBUG_CONVERT + +/******************************************************************************** + * class QOutlineMapper + * + * Used to map between QPainterPath and the QT_FT_Outline structure used by the + * freetype scanconvertor. + * + * The outline mapper uses a path iterator to get points from the path, + * so that it is possible to transform the points as they are converted. The + * callback can be a noop, translate or full-fledged xform. (Tests indicated + * that using a C callback was low cost). + */ +class QOutlineMapper +{ +public: + QOutlineMapper() : + m_element_types(0), + m_elements(0), + m_elements_dev(0), + m_points(0), + m_tags(0), + m_contours(0), + m_polygon_dev(0), + m_in_clip_elements(false) + { + } + + /*! + Sets up the matrix to be used for conversion. This also + sets up the qt_path_iterator function that is used as a callback + to get points. + */ + void setMatrix(const QTransform &m) + { + m_m11 = m.m11(); + m_m12 = m.m12(); + m_m13 = m.m13(); + m_m21 = m.m21(); + m_m22 = m.m22(); + m_m23 = m.m23(); + m_m33 = m.m33(); + m_dx = m.dx(); + m_dy = m.dy(); + m_txop = m.type(); + } + + void beginOutline(Qt::FillRule fillRule) + { +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::beginOutline rule=%d\n", fillRule); +#endif + m_valid = true; + m_elements.reset(); + m_elements_dev.reset(); + m_element_types.reset(); + m_points.reset(); + m_tags.reset(); + m_contours.reset(); + m_outline.flags = fillRule == Qt::WindingFill + ? QT_FT_OUTLINE_NONE + : QT_FT_OUTLINE_EVEN_ODD_FILL; + m_subpath_start = 0; + } + + void endOutline(); + + void clipElements(const QPointF *points, const QPainterPath::ElementType *types, int count); + + void convertElements(const QPointF *points, const QPainterPath::ElementType *types, int count); + + inline void moveTo(const QPointF &pt) { +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::moveTo() (%f, %f)\n", pt.x(), pt.y()); +#endif + closeSubpath(); + m_subpath_start = m_elements.size(); + m_elements << pt; + m_element_types << QPainterPath::MoveToElement; + } + + inline void lineTo(const QPointF &pt) { +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::lineTo() (%f, %f)\n", pt.x(), pt.y()); +#endif + m_elements.add(pt); + m_element_types << QPainterPath::LineToElement; + } + + inline void curveTo(const QPointF &cp1, const QPointF &cp2, const QPointF &ep) { +#ifdef QT_DEBUG_CONVERT + printf("QOutlineMapper::curveTo() (%f, %f)\n", ep.x(), ep.y()); +#endif + m_elements << cp1 << cp2 << ep; + m_element_types << QPainterPath::CurveToElement + << QPainterPath::CurveToDataElement + << QPainterPath::CurveToDataElement; + } + + inline void closeSubpath() { + int element_count = m_elements.size(); + if (element_count > 0) { + if (m_elements.at(element_count-1) != m_elements.at(m_subpath_start)) { +#ifdef QT_DEBUG_CONVERT + printf(" - implicitly closing\n"); +#endif + // Put the object on the stack to avoid the odd case where + // lineTo reallocs the databuffer and the QPointF & will + // be invalidated. + QPointF pt = m_elements.at(m_subpath_start); + + // only do lineTo if we have element_type array... + if (m_element_types.size()) + lineTo(pt); + else + m_elements << pt; + + } + } + } + + QT_FT_Outline *outline() { + if (m_valid) + return &m_outline; + return 0; + } + + QT_FT_Outline *convertPath(const QPainterPath &path); + QT_FT_Outline *convertPath(const QVectorPath &path); + + inline QPainterPath::ElementType *elementTypes() const { return m_element_types.size() == 0 ? 0 : m_element_types.data(); } + +public: + QDataBuffer<QPainterPath::ElementType> m_element_types; + QDataBuffer<QPointF> m_elements; + QDataBuffer<QPointF> m_elements_dev; + QDataBuffer<QT_FT_Vector> m_points; + QDataBuffer<char> m_tags; + QDataBuffer<int> m_contours; + + QRect m_clip_rect; + QDataBuffer<QPointF> m_polygon_dev; + + QRectF controlPointRect; // only valid after endOutline() + + QT_FT_Outline m_outline; + uint m_txop; + + int m_subpath_start; + + // Matrix + qreal m_m11; + qreal m_m12; + qreal m_m13; + qreal m_m21; + qreal m_m22; + qreal m_m23; + qreal m_m33; + qreal m_dx; + qreal m_dy; + + bool m_valid; + bool m_in_clip_elements; +}; + +QT_END_NAMESPACE + +#endif // QOUTLINEMAPPER_P_H diff --git a/src/gui/painting/qpaintbuffer.cpp b/src/gui/painting/qpaintbuffer.cpp new file mode 100644 index 0000000000..7870defd22 --- /dev/null +++ b/src/gui/painting/qpaintbuffer.cpp @@ -0,0 +1,2287 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qmath.h> +#include <private/qpainterpath_p.h> +#include <private/qpaintbuffer_p.h> +//#include <private/qtextengine_p.h> +#include <private/qfontengine_p.h> +#include <private/qemulationpaintengine_p.h> +#include <private/qimage_p.h> +#include <qstatictext.h> +#include <private/qstatictext_p.h> +#include <private/qrawfont_p.h> + +#include <QDebug> + +// #define QPAINTBUFFER_DEBUG_DRAW + +QT_BEGIN_NAMESPACE + +extern void qt_format_text(const QFont &font, + const QRectF &_r, int tf, const QTextOption *option, const QString& str, QRectF *brect, + int tabstops, int* tabarray, int tabarraylen, + QPainter *painter); + +QTextItemIntCopy::QTextItemIntCopy(const QTextItem &item) + : m_item(static_cast<const QTextItemInt &>(item)) +{ + QChar *chars = new QChar[m_item.num_chars]; + unsigned short *logClusters = new unsigned short[m_item.num_chars]; + memcpy(chars, m_item.chars, m_item.num_chars * sizeof(QChar)); + memcpy(logClusters, m_item.logClusters, m_item.num_chars * sizeof(unsigned short)); + m_item.chars = chars; + m_item.logClusters = logClusters; + + const int size = QGlyphLayout::spaceNeededForGlyphLayout(m_item.glyphs.numGlyphs); + char *glyphLayoutData = new char[size]; + QGlyphLayout glyphs(glyphLayoutData, m_item.glyphs.numGlyphs); + memcpy(glyphs.offsets, m_item.glyphs.offsets, m_item.glyphs.numGlyphs * sizeof(QFixedPoint)); + memcpy(glyphs.glyphs, m_item.glyphs.glyphs, m_item.glyphs.numGlyphs * sizeof(HB_Glyph)); + memcpy(glyphs.advances_x, m_item.glyphs.advances_x, m_item.glyphs.numGlyphs * sizeof(QFixed)); + memcpy(glyphs.advances_y, m_item.glyphs.advances_y, m_item.glyphs.numGlyphs * sizeof(QFixed)); + memcpy(glyphs.justifications, m_item.glyphs.justifications, m_item.glyphs.numGlyphs * sizeof(QGlyphJustification)); + memcpy(glyphs.attributes, m_item.glyphs.attributes, m_item.glyphs.numGlyphs * sizeof(HB_GlyphAttributes)); + m_item.glyphs = glyphs; + + m_font = *m_item.f; + m_item.f = &m_font; + + m_item.fontEngine->ref.ref(); // Increment reference count. +} + +QTextItemIntCopy::~QTextItemIntCopy() +{ + delete m_item.chars; + delete m_item.logClusters; + delete m_item.glyphs.data(); + if (!m_item.fontEngine->ref.deref()) + delete m_item.fontEngine; +} + +/************************************************************************ + * + * QPaintBufferSignalProxy + * + ************************************************************************/ + +Q_GLOBAL_STATIC(QPaintBufferSignalProxy, theSignalProxy) + +QPaintBufferSignalProxy *QPaintBufferSignalProxy::instance() +{ + return theSignalProxy(); +} + +/************************************************************************ + * + * QPaintBufferPrivate + * + ************************************************************************/ + +QPaintBufferPrivate::QPaintBufferPrivate() + : ref(1), engine(0), penWidthAdjustment(0) + , calculateBoundingRect(true) + , cache(0) +{ +} + +QPaintBufferPrivate::~QPaintBufferPrivate() +{ + QPaintBufferSignalProxy::instance()->emitAboutToDestroy(this); + + for (int i = 0; i < commands.size(); ++i) { + const QPaintBufferCommand &cmd = commands.at(i); + if (cmd.id == QPaintBufferPrivate::Cmd_DrawTextItem) + delete reinterpret_cast<QTextItemIntCopy *>(qvariant_cast<void *>(variants.at(cmd.offset))); + } +} + + +inline void QPaintBufferPrivate::updateBoundingRect(const QRectF &br) +{ + // transform to device coords and adjust for pen width + Q_ASSERT(engine && engine->painter()); + QPainter *painter = engine->painter(); + const QTransform transform = painter->transform(); + QRectF devRect = transform.mapRect(br); + if (penWidthAdjustment > 0) { + devRect = devRect.adjusted(-penWidthAdjustment, -penWidthAdjustment, + penWidthAdjustment, penWidthAdjustment); + } + + if (boundingRect.isEmpty()) { + boundingRect = devRect; + } else { + qreal min_x = qMin(devRect.left(), boundingRect.left()); + qreal min_y = qMin(devRect.top(), boundingRect.top()); + qreal max_x = qMax(devRect.right(), boundingRect.right()); + qreal max_y = qMax(devRect.bottom(), boundingRect.bottom()); + boundingRect = QRectF(min_x, min_y, max_x - min_x, max_y - min_y); + } + if (painter->hasClipping()) + boundingRect &= transform.mapRect(painter->clipRegion().boundingRect()); +} + + +/************************************************************************ + * + * QPaintBuffer + * + ************************************************************************/ + + + +QPaintBuffer::QPaintBuffer() + : d_ptr(new QPaintBufferPrivate) +{ +} + +QPaintBuffer::~QPaintBuffer() +{ + if (!d_ptr->ref.deref()) + delete d_ptr; +} + +QPaintBuffer::QPaintBuffer(const QPaintBuffer &other) + : QPaintDevice(), d_ptr(other.d_ptr) +{ + d_ptr->ref.ref(); +} + +QPaintEngine *QPaintBuffer::paintEngine() const +{ + QPaintBufferPrivate *d = const_cast<QPaintBuffer *>(this)->d_ptr; + if (!d->engine) + d->engine = new QPaintBufferEngine(d); + return d->engine; +} + + +int QPaintBuffer::metric(PaintDeviceMetric metric) const +{ + int val = 0; + switch (metric) { + case PdmWidth: + val = qCeil(d_ptr->boundingRect.width()); + break; + case PdmHeight: + val = qCeil(d_ptr->boundingRect.height()); + break; + case PdmDpiX: + case PdmPhysicalDpiX: + val = qt_defaultDpiX(); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + val = qt_defaultDpiY(); + break; + default: + val = QPaintDevice::metric(metric); + } + + return val; +} + +int QPaintBuffer::devType() const +{ + return QInternal::PaintBuffer; +} + +QPaintBuffer &QPaintBuffer::operator=(const QPaintBuffer &other) +{ + if (other.d_ptr != d_ptr) { + QPaintBufferPrivate *data = other.d_ptr; + data->ref.ref(); + if (d_ptr->ref.deref()) + delete d_ptr; + d_ptr = data; + } + return *this; +} + +bool QPaintBuffer::isEmpty() const +{ + return d_ptr->commands.isEmpty(); +} + + + +void QPaintBuffer::draw(QPainter *painter, int frame) const +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBuffer::draw() --------------------------------"; + + Q_D(const QPaintBuffer); + printf("Float buffer:"); + for (int i=0; i<d->floats.size(); i++) { + if ((i % 10) == 0) { + printf("\n%4d-%4d: ", i, i+9); + } + printf("%4.2f ", d->floats[i]); + } + printf("\n"); + + printf("Int Buffer:"); + for (int i=0; i<d->ints.size(); i++) { + if ((i % 10) == 0) { + printf("\n%4d-%4d: ", i, i+10); + } + printf("%5d", d->ints[i]); + } + printf("\n"); +#endif + + processCommands(painter, frameStartIndex(frame), frameEndIndex(frame)); + +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBuffer::draw() -------------------------------- DONE!"; +#endif +} + +int QPaintBuffer::frameStartIndex(int frame) const +{ + return (frame == 0) ? 0 : d_ptr->frames.at(frame - 1); +} + +int QPaintBuffer::frameEndIndex(int frame) const +{ + return (frame == d_ptr->frames.size()) ? d_ptr->commands.size() : d_ptr->frames.at(frame); +} + +int QPaintBuffer::processCommands(QPainter *painter, int begin, int end) const +{ + if (!painter || !painter->isActive()) + return 0; + + QPaintEngineEx *xengine = painter->paintEngine()->isExtended() + ? (QPaintEngineEx *) painter->paintEngine() : 0; + if (xengine) { + QPaintEngineExReplayer player; + player.processCommands(*this, painter, begin, end); + } else { + QPainterReplayer player; + player.processCommands(*this, painter, begin, end); + } + + int depth = 0; + for (int i = begin; i < end; ++i) { + const QPaintBufferCommand &cmd = d_ptr->commands.at(i); + if (cmd.id == QPaintBufferPrivate::Cmd_Save) + ++depth; + else if (cmd.id == QPaintBufferPrivate::Cmd_Restore) + --depth; + } + return depth; +} + +#ifndef QT_NO_DEBUG_STREAM +QString QPaintBuffer::commandDescription(int command) const +{ + QString desc; + QDebug debug(&desc); + + const QPaintBufferCommand &cmd = d_ptr->commands.at(command); + + switch (cmd.id) { + case QPaintBufferPrivate::Cmd_Save: { + debug << "Cmd_Save"; + break; } + + case QPaintBufferPrivate::Cmd_Restore: { + debug << "Cmd_Restore"; + break; } + + case QPaintBufferPrivate::Cmd_SetBrush: { + QBrush brush = qvariant_cast<QBrush>(d_ptr->variants.at(cmd.offset)); + debug << "Cmd_SetBrush: " << brush; + break; } + + case QPaintBufferPrivate::Cmd_SetBrushOrigin: { + debug << "Cmd_SetBrushOrigin: " << d_ptr->variants.at(cmd.offset).toPointF(); + break; } + + case QPaintBufferPrivate::Cmd_SetCompositionMode: { + QPainter::CompositionMode mode = (QPainter::CompositionMode) cmd.extra; + debug << "ExCmd_SetCompositionMode, mode: " << mode; + break; } + + case QPaintBufferPrivate::Cmd_SetOpacity: { + debug << "ExCmd_SetOpacity: " << d_ptr->variants.at(cmd.offset).toDouble(); + break; } + + case QPaintBufferPrivate::Cmd_DrawVectorPath: { + debug << "ExCmd_DrawVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2; + break; } + + case QPaintBufferPrivate::Cmd_StrokeVectorPath: { + QPen pen = qvariant_cast<QPen>(d_ptr->variants.at(cmd.extra)); + debug << "ExCmd_StrokeVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2 << pen; + break; } + + case QPaintBufferPrivate::Cmd_FillVectorPath: { + QBrush brush = qvariant_cast<QBrush>(d_ptr->variants.at(cmd.extra)); + debug << "ExCmd_FillVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2 << brush; + break; } + + case QPaintBufferPrivate::Cmd_FillRectBrush: { + QBrush brush = qvariant_cast<QBrush>(d_ptr->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d_ptr->floats.constData() + cmd.offset); + debug << "ExCmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " brush: " << brush; + break; } + + case QPaintBufferPrivate::Cmd_FillRectColor: { + QColor color = qvariant_cast<QColor>(d_ptr->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d_ptr->floats.constData() + cmd.offset); + debug << "ExCmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " color: " << color; + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonF: { + debug << "ExCmd_DrawPolygonF, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d_ptr->floats.at(cmd.offset) + << d_ptr->floats.at(cmd.offset+1); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonI: { + debug << "ExCmd_DrawPolygonI, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d_ptr->ints.at(cmd.offset) + << d_ptr->ints.at(cmd.offset+1); + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseF: { + debug << "ExCmd_DrawEllipseF, offset: " << cmd.offset; + break; } + + case QPaintBufferPrivate::Cmd_DrawLineF: { + debug << "ExCmd_DrawLineF, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawLineI: { + debug << "ExCmd_DrawLineI, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsF: { + debug << "ExCmd_DrawPointsF, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsI: { + debug << "ExCmd_DrawPointsI, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineF: { + debug << "ExCmd_DrawPolylineF, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineI: { + debug << "ExCmd_DrawPolylineI, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawRectF: { + debug << "ExCmd_DrawRectF, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawRectI: { + debug << "ExCmd_DrawRectI, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_SetClipEnabled: { + bool clipEnabled = d_ptr->variants.at(cmd.offset).toBool(); + debug << "ExCmd_SetClipEnabled:" << clipEnabled; + break; } + + case QPaintBufferPrivate::Cmd_ClipVectorPath: { + QVectorPathCmd path(d_ptr, cmd); + debug << "ExCmd_ClipVectorPath:" << path().elementCount(); + break; } + + case QPaintBufferPrivate::Cmd_ClipRect: { + QRect rect(QPoint(d_ptr->ints.at(cmd.offset), d_ptr->ints.at(cmd.offset + 1)), + QPoint(d_ptr->ints.at(cmd.offset + 2), d_ptr->ints.at(cmd.offset + 3))); + debug << "ExCmd_ClipRect:" << rect << cmd.extra; + break; } + + case QPaintBufferPrivate::Cmd_ClipRegion: { + QRegion region(d_ptr->variants.at(cmd.offset).value<QRegion>()); + debug << "ExCmd_ClipRegion:" << region.boundingRect() << cmd.extra; + break; } + + case QPaintBufferPrivate::Cmd_SetPen: { + QPen pen = qvariant_cast<QPen>(d_ptr->variants.at(cmd.offset)); + debug << "Cmd_SetPen: " << pen; + break; } + + case QPaintBufferPrivate::Cmd_SetTransform: { + QTransform xform = qvariant_cast<QTransform>(d_ptr->variants.at(cmd.offset)); + debug << "Cmd_SetTransform, offset: " << cmd.offset << xform; + break; } + + case QPaintBufferPrivate::Cmd_SetRenderHints: { + debug << "Cmd_SetRenderHints, hints: " << cmd.extra; + break; } + + case QPaintBufferPrivate::Cmd_SetBackgroundMode: { + debug << "Cmd_SetBackgroundMode: " << cmd.extra; + break; } + + case QPaintBufferPrivate::Cmd_DrawConvexPolygonF: { + debug << "Cmd_DrawConvexPolygonF, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawConvexPolygonI: { + debug << "Cmd_DrawConvexPolygonI, offset: " << cmd.offset << " size: " << cmd.size; + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseI: { + debug << "Cmd_DrawEllipseI, offset: " << cmd.offset; + break; } + + case QPaintBufferPrivate::Cmd_DrawPixmapRect: { + QPixmap pm(d_ptr->variants.at(cmd.offset).value<QPixmap>()); + QRectF r(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1), + d_ptr->floats.at(cmd.extra+2), d_ptr->floats.at(cmd.extra+3)); + + QRectF sr(d_ptr->floats.at(cmd.extra+4), d_ptr->floats.at(cmd.extra+5), + d_ptr->floats.at(cmd.extra+6), d_ptr->floats.at(cmd.extra+7)); + debug << "Cmd_DrawPixmapRect:" << r << sr << pm.size(); + break; } + + case QPaintBufferPrivate::Cmd_DrawPixmapPos: { + QPixmap pm(d_ptr->variants.at(cmd.offset).value<QPixmap>()); + QPointF pos(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1)); + debug << "Cmd_DrawPixmapPos:" << pos << pm.size(); + break; } + + case QPaintBufferPrivate::Cmd_DrawTiledPixmap: { + QPixmap pm(d_ptr->variants.at(cmd.offset).value<QPixmap>()); + QRectF r(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1), + d_ptr->floats.at(cmd.extra+2), d_ptr->floats.at(cmd.extra+3)); + + QPointF offset(d_ptr->floats.at(cmd.extra+4), d_ptr->floats.at(cmd.extra+5)); + debug << "Cmd_DrawTiledPixmap:" << r << offset << pm.size(); + break; } + + case QPaintBufferPrivate::Cmd_DrawImageRect: { + QImage image(d_ptr->variants.at(cmd.offset).value<QImage>()); + QRectF r(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1), + d_ptr->floats.at(cmd.extra+2), d_ptr->floats.at(cmd.extra+3)); + QRectF sr(d_ptr->floats.at(cmd.extra+4), d_ptr->floats.at(cmd.extra+5), + d_ptr->floats.at(cmd.extra+6), d_ptr->floats.at(cmd.extra+7)); + debug << "Cmd_DrawImageRect:" << r << sr << image.size(); + break; } + + case QPaintBufferPrivate::Cmd_DrawImagePos: { + QImage image(d_ptr->variants.at(cmd.offset).value<QImage>()); + QPointF pos(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1)); + debug << "Cmd_DrawImagePos:" << pos << image.size(); + break; } + + case QPaintBufferPrivate::Cmd_DrawText: { + QPointF pos(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1)); + QList<QVariant> variants(d_ptr->variants.at(cmd.offset).value<QList<QVariant> >()); + + QFont font(variants.at(0).value<QFont>()); + QString text(variants.at(1).value<QString>()); + + debug << "Cmd_DrawText:" << pos << text << font.family(); + break; } + + case QPaintBufferPrivate::Cmd_DrawTextItem: { + QPointF pos(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1)); + QTextItemIntCopy *tiCopy = reinterpret_cast<QTextItemIntCopy *>(qvariant_cast<void *>(d_ptr->variants.at(cmd.offset))); + QTextItemInt &ti = (*tiCopy)(); + QString text(ti.text()); + + QFont font(ti.font()); + font.setUnderline(false); + font.setStrikeOut(false); + font.setOverline(false); + + const QTextItemInt &si = static_cast<const QTextItemInt &>(ti); + qreal justificationWidth = 0; + if (si.justified) + justificationWidth = si.width.toReal(); + + debug << "Cmd_DrawTextItem:" << pos << " " << text; + break; } + case QPaintBufferPrivate::Cmd_SystemStateChanged: { + QRegion systemClip(d_ptr->variants.at(cmd.offset).value<QRegion>()); + + debug << "Cmd_SystemStateChanged:" << systemClip; + break; } + case QPaintBufferPrivate::Cmd_Translate: { + QPointF delta(d_ptr->floats.at(cmd.extra), d_ptr->floats.at(cmd.extra+1)); + debug << "Cmd_Translate:" << delta; + break; } + case QPaintBufferPrivate::Cmd_DrawStaticText: { + debug << "Cmd_DrawStaticText"; + break; } + } + + return desc; +} +#endif + +QRectF QPaintBuffer::boundingRect() const +{ + return d_ptr->boundingRect; +} + +void QPaintBuffer::setBoundingRect(const QRectF &rect) +{ + d_ptr->boundingRect = rect; + d_ptr->calculateBoundingRect = false; +} + + +class QPaintBufferEnginePrivate : public QPaintEngineExPrivate +{ + Q_DECLARE_PUBLIC(QPaintBufferEngine) +public: + void systemStateChanged() { + Q_Q(QPaintBufferEngine); + q->buffer->addCommand(QPaintBufferPrivate::Cmd_SystemStateChanged, QVariant(systemClip)); + } + + QTransform last; +}; + + +/************************************************************************ + * + * QPaintBufferEngine + * + ************************************************************************/ + +QPaintBufferEngine::QPaintBufferEngine(QPaintBufferPrivate *b) + : QPaintEngineEx(*(new QPaintBufferEnginePrivate)) + , buffer(b) + , m_begin_detected(false) + , m_save_detected(false) + , m_stream_raw_text_items(false) +{ +} + +bool QPaintBufferEngine::begin(QPaintDevice *) +{ + Q_D(QPaintBufferEngine); + painter()->save(); + d->systemStateChanged(); + return true; +} + +bool QPaintBufferEngine::end() +{ + painter()->restore(); + m_created_state = 0; + return true; +} + +QPainterState *QPaintBufferEngine::createState(QPainterState *orig) const +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: createState, orig=" << orig << ", current=" << state(); +#endif + + Q_ASSERT(!m_begin_detected); + Q_ASSERT(!m_save_detected); + + if (orig == 0) { + m_begin_detected = true; + return new QPainterState(); + } else { + m_save_detected = true; + return new QPainterState(orig); + } +} + +void QPaintBufferEngine::clip(const QVectorPath &path, Qt::ClipOperation op) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: clip vpath:" << path.elementCount() << "op:" << op; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_ClipVectorPath, path); + cmd->extra = op; +} + +void QPaintBufferEngine::clip(const QRect &rect, Qt::ClipOperation op) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: clip rect:" << rect << "op:" << op; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_ClipRect, (int *) &rect, 4, 1); + cmd->extra = op; +} + +void QPaintBufferEngine::clip(const QRegion ®ion, Qt::ClipOperation op) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: clip region br:" << region.boundingRect() << "op:" << op; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_ClipRegion, QVariant(region)); + cmd->extra = op; +} + +void QPaintBufferEngine::clip(const QPainterPath &path, Qt::ClipOperation op) +{ + // ### TODO +// QPaintBufferCommand *cmd = +// buffer->addCommand(QPaintBufferPrivate::Cmd_ClipPath, QVariant(path)); +// cmd->extra = op; + QPaintEngineEx::clip(path, op); +} + +void QPaintBufferEngine::clipEnabledChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: clip enable change" << state()->clipEnabled; +#endif + + buffer->addCommand(QPaintBufferPrivate::Cmd_SetClipEnabled, state()->clipEnabled); +} + +void QPaintBufferEngine::penChanged() +{ + const QPen &pen = state()->pen; + + if (!buffer->commands.isEmpty() + && buffer->commands.last().id == QPaintBufferPrivate::Cmd_SetPen) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: penChanged (compressed)" << state()->pen; +#endif + buffer->variants[buffer->commands.last().offset] = pen; + return; + } + + if (buffer->calculateBoundingRect) { + if (pen.style() == Qt::NoPen) { + buffer->penWidthAdjustment = 0; + } else { + qreal penWidth = (pen.widthF() == 0) ? 1 : pen.widthF(); + QPointF transformedWidth(penWidth, penWidth); + if (!pen.isCosmetic()) + transformedWidth = painter()->transform().map(transformedWidth); + buffer->penWidthAdjustment = transformedWidth.x() / 2.0; + } + } +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: penChanged" << state()->pen; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_SetPen, pen); +} + +void QPaintBufferEngine::brushChanged() +{ + const QBrush &brush = state()->brush; + + if (!buffer->commands.isEmpty() + && buffer->commands.last().id == QPaintBufferPrivate::Cmd_SetBrush) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: brushChanged (compressed)" << state()->brush; +#endif + buffer->variants[buffer->commands.last().offset] = brush; + return; + } + +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: brushChanged" << state()->brush; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_SetBrush, brush); +} + +void QPaintBufferEngine::brushOriginChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: brush origin changed" << state()->brushOrigin; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_SetBrushOrigin, state()->brushOrigin); +} + +void QPaintBufferEngine::opacityChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: opacity changed" << state()->opacity; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_SetOpacity, state()->opacity); +} + +void QPaintBufferEngine::compositionModeChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: composition mode" << state()->composition_mode; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_SetCompositionMode); + cmd->extra = state()->composition_mode; +} + +void QPaintBufferEngine::renderHintsChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: render hints changed" << state()->renderHints; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_SetRenderHints); + cmd->extra = state()->renderHints; +} + +void QPaintBufferEngine::transformChanged() +{ + Q_D(QPaintBufferEngine); + const QTransform &transform = state()->matrix; + + QTransform delta; + + bool invertible = false; + if (transform.type() <= QTransform::TxScale && transform.type() == d->last.type()) + delta = transform * d->last.inverted(&invertible); + + d->last = transform; + + if (invertible && delta.type() == QTransform::TxNone) + return; + + if (invertible && delta.type() == QTransform::TxTranslate) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: transformChanged (translate only) " << state()->matrix; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_Translate); + + qreal data[] = { delta.dx(), delta.dy() }; + cmd->extra = buffer->addData((qreal *) data, 2); + return; + } + + // ### accumulate, like in QBrush case... + if (!buffer->commands.isEmpty() + && buffer->commands.last().id == QPaintBufferPrivate::Cmd_SetTransform) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: transformChanged (compressing) " << state()->matrix; +#endif + buffer->variants[buffer->commands.last().offset] = state()->matrix; + return; + } + +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: transformChanged:" << state()->matrix; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_SetTransform, state()->matrix); +} + +void QPaintBufferEngine::backgroundModeChanged() +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintEngineBuffer: background mode changed" << state()->bgMode; +#endif + QPaintBufferCommand *cmd = buffer->addCommand(QPaintBufferPrivate::Cmd_SetBackgroundMode); + cmd->extra = state()->bgMode; +} + +void QPaintBufferEngine::draw(const QVectorPath &path) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: draw vpath:" << path.elementCount(); +#endif + + bool hasBrush = qbrush_style(state()->brush) != Qt::NoBrush; + bool hasPen = qpen_style(state()->pen) != Qt::NoPen + && qbrush_style(qpen_brush(state()->pen)) != Qt::NoBrush; + + if (hasPen || hasBrush) + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawVectorPath, path); +#ifdef QPAINTBUFFER_DEBUG_DRAW + else + qDebug() << " - no pen or brush active, discarded...\n"; +#endif + +// if (buffer->calculateBoundingRect) { +// QRealRect r = path.controlPointRect(); +// buffer->updateBoundingRect(QRectF(r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1)); +// } +} + +void QPaintBufferEngine::fill(const QVectorPath &path, const QBrush &brush) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: fill vpath:" << path.elementCount() << brush; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_FillVectorPath, path); + cmd->extra = buffer->addData(QVariant(brush)); +// if (buffer->calculateBoundingRect) { +// QRealRect r = path.controlPointRect(); +// buffer->updateBoundingRect(QRectF(r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1)); +// } +} + +void QPaintBufferEngine::stroke(const QVectorPath &path, const QPen &pen) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: stroke vpath:" << path.elementCount() << pen; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_StrokeVectorPath, path); + cmd->extra = buffer->addData(QVariant(pen)); +// if (buffer->calculateBoundingRect) { +// QRealRect r = path.controlPointRect(); +// buffer->updateBoundingRect(QRectF(r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1)); +// } +} + +void QPaintBufferEngine::fillRect(const QRectF &rect, const QBrush &brush) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: fillRect brush:" << rect << brush; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_FillRectBrush, (qreal *) &rect, 4, 1); + cmd->extra = buffer->addData(brush); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(rect); +} + +void QPaintBufferEngine::fillRect(const QRectF &rect, const QColor &color) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: fillRect color:" << rect << color; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_FillRectColor, (qreal *) &rect, 4, 1); + cmd->extra = buffer->addData(color); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(rect); +} + +void QPaintBufferEngine::drawRects(const QRect *rects, int rectCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawRectsI:" << rectCount; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawRectI, (int *) rects, 4 * rectCount, rectCount); + cmd->extra = rectCount; + + if (buffer->calculateBoundingRect) { + if (rectCount == 1) { + buffer->updateBoundingRect(rects[0]); + } else { + int min_x = rects[0].left(); + int min_y = rects[0].top(); + int max_x = rects[0].left() + rects[0].width(); + int max_y = rects[0].top() + rects[0].height(); + for (int i=1; i< rectCount; ++i) { + if (rects[i].left() < min_x) + min_x = rects[i].left(); + if (rects[i].top() < min_y) + min_y = rects[i].top(); + if (rects[i].right() > max_x) + max_x = rects[i].left() + rects[i].width(); + if (rects[i].bottom() > max_y) + max_y = rects[i].top() + rects[i].height(); + + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } + } +} + +void QPaintBufferEngine::drawRects(const QRectF *rects, int rectCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawRectsF:" << rectCount; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawRectF, (qreal *) rects, 4 * rectCount, rectCount); + cmd->extra = rectCount; + + if (buffer->calculateBoundingRect) { + if (rectCount == 1) { + buffer->updateBoundingRect(rects[0]); + } else { + qreal min_x = rects[0].left(); + qreal min_y = rects[0].top(); + qreal max_x = rects[0].right(); + qreal max_y = rects[0].bottom(); + for (int i=1; i< rectCount; ++i) { + if (rects[i].left() < min_x) + min_x = rects[i].left(); + if (rects[i].top() < min_y) + min_y = rects[i].top(); + if (rects[i].right() > max_x) + max_x = rects[i].right(); + if (rects[i].bottom() > max_y) + max_y = rects[i].bottom(); + + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } + } +} + +void QPaintBufferEngine::drawLines(const QLine *lines, int lineCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawLinesI:" << lineCount; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawLineI, (int *) lines, 4 * lineCount, lineCount); + cmd->extra = lineCount; + + if (buffer->calculateBoundingRect) { + int min_x = lines[0].p1().x(); + int min_y = lines[0].p1().y(); + int max_x = lines[0].p2().x(); + int max_y = lines[0].p2().y(); + if (min_x > max_x) + qSwap(min_x, max_x); + if (min_y > max_y) + qSwap(min_y, max_y); + for (int i=1; i < lineCount; ++i) { + int p1_x = lines[i].p1().x(); + int p1_y = lines[i].p1().y(); + int p2_x = lines[i].p2().x(); + int p2_y = lines[i].p2().y(); + if (p1_x > p2_x) { + min_x = qMin(p2_x, min_x); + max_x = qMax(p1_x, max_x); + } else { + min_x = qMin(p1_x, min_x); + max_x = qMax(p2_x, max_x); + } + if (p1_y > p2_y) { + min_y = qMin(p2_y, min_y); + max_y = qMax(p1_y, max_y); + } else { + min_y = qMin(p1_y, min_y); + max_y = qMax(p2_y, max_y); + } + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawLines(const QLineF *lines, int lineCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawLinesF:" << lineCount; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawLineF, (qreal *) lines, 4 * lineCount, lineCount); + cmd->extra = lineCount; + + if (buffer->calculateBoundingRect) { + qreal min_x = lines[0].p1().x(); + qreal min_y = lines[0].p1().y(); + qreal max_x = lines[0].p2().x(); + qreal max_y = lines[0].p2().y(); + if (min_x > max_x) + qSwap(min_x, max_x); + if (min_y > max_y) + qSwap(min_y, max_y); + for (int i=1; i < lineCount; ++i) { + qreal p1_x = lines[i].p1().x(); + qreal p1_y = lines[i].p1().y(); + qreal p2_x = lines[i].p2().x(); + qreal p2_y = lines[i].p2().y(); + if (p1_x > p2_x) { + min_x = qMin(p2_x, min_x); + max_x = qMax(p1_x, max_x); + } else { + min_x = qMin(p1_x, min_x); + max_x = qMax(p2_x, max_x); + } + if (p1_y > p2_y) { + min_y = qMin(p2_y, min_y); + max_y = qMax(p1_y, max_y); + } else { + min_y = qMin(p1_y, min_y); + max_y = qMax(p2_y, max_y); + } + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawEllipse(const QRectF &r) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawEllipseF:" << r; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawEllipseF, (qreal *) &r, 4, 1); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(r); +} + +void QPaintBufferEngine::drawEllipse(const QRect &r) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawEllipseI:" << r; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawEllipseI, (int *) &r, 4, 1); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(r); +} + +void QPaintBufferEngine::drawPath(const QPainterPath &path) +{ +// #ifdef QPAINTBUFFER_DEBUG_DRAW +// qDebug() << "QPaintBufferEngine: drawPath: element count:" << path.elementCount(); +// #endif +// // ### Path -> QVariant +// // buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPath, QVariant(path)); + QPaintEngineEx::drawPath(path); + +// if (buffer->calculateBoundingRect) +// buffer->updateBoundingRect(path.boundingRect()); +} + +void QPaintBufferEngine::drawPoints(const QPoint *points, int pointCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPointsI: " << pointCount; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPointsI, (int *) points, 2 * pointCount, pointCount); + + if (buffer->calculateBoundingRect) { + int min_x = points[0].x(); + int min_y = points[0].y(); + int max_x = points[0].x()+1; + int max_y = points[0].y()+1; + for (int i=1; i<pointCount; ++i) { + int x = points[i].x(); + int y = points[i].y(); + min_x = qMin(min_x, x); + min_y = qMin(min_y, y); + max_x = qMax(max_x, x+1); + max_y = qMax(max_y, y+1); + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawPoints(const QPointF *points, int pointCount) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPointsF: " << pointCount; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPointsF, (qreal *) points, 2 * pointCount, pointCount); + + if (buffer->calculateBoundingRect) { + qreal min_x = points[0].x(); + qreal min_y = points[0].y(); + qreal max_x = points[0].x()+1; + qreal max_y = points[0].y()+1; + for (int i=1; i<pointCount; ++i) { + qreal x = points[i].x(); + qreal y = points[i].y(); + min_x = qMin(min_x, x); + min_y = qMin(min_y, y); + max_x = qMax(max_x, x+1); + max_y = qMax(max_y, y+1); + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawPolygon(const QPoint *pts, int count, PolygonDrawMode mode) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPolygonI: size:" << count << ", mode:" << mode; +#endif + if (mode == QPaintEngine::OddEvenMode || mode == QPaintEngine::WindingMode) { + QPaintBufferCommand *cmd = buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPolygonI, + (int *) pts, 2 * count, count); + cmd->extra = mode; + } else if (mode == QPaintEngine::PolylineMode) { + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPolylineI, (int *) pts, 2 * count, count); + } else { + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawConvexPolygonI, (int *) pts, 2 * count, count); + } + + if (buffer->calculateBoundingRect) { + int min_x = pts[0].x(); + int min_y = pts[0].y(); + int max_x = pts[0].x(); + int max_y = pts[0].y(); + for (int i=1; i<count; ++i) { + int x = pts[i].x(); + int y = pts[i].y(); + min_x = qMin(min_x, x); + min_y = qMin(min_y, y); + max_x = qMax(max_x, x); + max_y = qMax(max_y, y); + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawPolygon(const QPointF *pts, int count, PolygonDrawMode mode) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPolygonF: size:" << count << ", mode:" << mode; +#endif + if (mode == QPaintEngine::OddEvenMode || mode == QPaintEngine::WindingMode) { + QPaintBufferCommand *cmd = buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPolygonF, + (qreal *) pts, 2 * count, count); + cmd->extra = mode; + } else if (mode == QPaintEngine::PolylineMode) { + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPolylineF, (qreal *) pts, 2 * count, count); + } else { + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawConvexPolygonF, (qreal *) pts, 2 * count, count); + } + + if (buffer->calculateBoundingRect) { + qreal min_x = pts[0].x(); + qreal min_y = pts[0].y(); + qreal max_x = pts[0].x(); + qreal max_y = pts[0].y(); + for (int i=1; i<count; ++i) { + qreal x = pts[i].x(); + qreal y = pts[i].y(); + min_x = qMin(min_x, x); + min_y = qMin(min_y, y); + max_x = qMax(max_x, x); + max_y = qMax(max_y, y); + } + buffer->updateBoundingRect(QRectF(min_x, min_y, max_x - min_x, max_y - min_y)); + } +} + +void QPaintBufferEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPixmap: src/dest rects " << r << sr; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPixmapRect, QVariant(pm)); + cmd->extra = buffer->addData((qreal *) &r, 4); + buffer->addData((qreal *) &sr, 4); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(r); +} + +void QPaintBufferEngine::drawPixmap(const QPointF &pos, const QPixmap &pm) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawPixmap: pos:" << pos; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawPixmapPos, QVariant(pm)); + cmd->extra = buffer->addData((qreal *) &pos, 2); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(QRectF(pos, pm.size())); +} + +static inline QImage qpaintbuffer_storable_image(const QImage &src) +{ + QImageData *d = const_cast<QImage &>(src).data_ptr(); + return d->own_data ? src : src.copy(); +} + +void QPaintBufferEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags /*flags */) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawImage: src/dest rects " << r << sr; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawImageRect, + QVariant(qpaintbuffer_storable_image(image))); + cmd->extra = buffer->addData((qreal *) &r, 4); + buffer->addData((qreal *) &sr, 4); + // ### flags... + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(r); +} + +void QPaintBufferEngine::drawImage(const QPointF &pos, const QImage &image) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawImage: pos:" << pos; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawImagePos, + QVariant(qpaintbuffer_storable_image(image))); + cmd->extra = buffer->addData((qreal *) &pos, 2); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(QRectF(pos, image.size())); +} + +void QPaintBufferEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &s) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawTiledPixmap: src rect/offset:" << r << s; +#endif + QPaintBufferCommand *cmd = + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawTiledPixmap, QVariant(pm)); + cmd->extra = buffer->addData((qreal *) &r, 4); + buffer->addData((qreal *) &s, 2); + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(r); +} + +void QPaintBufferEngine::drawStaticTextItem(QStaticTextItem *staticTextItem) +{ + QVariantList variants; + + variants << QVariant(staticTextItem->font); + for (int i=0; i<staticTextItem->numGlyphs; ++i) { + variants.append(staticTextItem->glyphs[i]); + variants.append(staticTextItem->glyphPositions[i].toPointF()); + } + + buffer->addCommand(QPaintBufferPrivate::Cmd_DrawStaticText, QVariant(variants)); +} + +void QPaintBufferEngine::drawTextItem(const QPointF &pos, const QTextItem &ti) +{ +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: drawTextItem: pos:" << pos << ti.text(); +#endif + if (m_stream_raw_text_items) { + QPaintBufferCommand *cmd = buffer->addCommand(QPaintBufferPrivate::Cmd_DrawTextItem, QVariant::fromValue<void *>(new QTextItemIntCopy(ti))); + + QFont font(ti.font()); + font.setUnderline(false); + font.setStrikeOut(false); + font.setOverline(false); + + const QTextItemInt &si = static_cast<const QTextItemInt &>(ti); + qreal justificationWidth = 0; + if (si.justified) + justificationWidth = si.width.toReal(); + int renderFlags = ti.renderFlags(); + qreal scaleFactor = font.d->dpi/qreal(qt_defaultDpiY()); + + buffer->addData(QVariant(font)); + cmd->extra = buffer->addData((qreal *) &pos, 2); + buffer->addData((qreal *) &justificationWidth, 1); + buffer->addData((qreal *) &scaleFactor, 1); + cmd->offset2 = buffer->addData((int *) &renderFlags, 1); + } else { + QList<QVariant> variants; + variants << QVariant(ti.font()) << QVariant(ti.text()); + QPaintBufferCommand *cmd = buffer->addCommand(QPaintBufferPrivate::Cmd_DrawText, QVariant(variants)); + cmd->extra = buffer->addData((qreal *) &pos, 2); + } + + if (buffer->calculateBoundingRect) + buffer->updateBoundingRect(QRectF(pos, QSize(ti.width(), ti.ascent() + ti.descent() + 1))); +} + + +void QPaintBufferEngine::setState(QPainterState *s) +{ + Q_D(QPaintBufferEngine); + if (m_begin_detected) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: setState: begin, ignoring."; +#endif + m_begin_detected = false; + } else if (m_save_detected) { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: setState: save."; +#endif + m_save_detected = false; + buffer->addCommand(QPaintBufferPrivate::Cmd_Save); + } else { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << "QPaintBufferEngine: setState: restore."; +#endif + buffer->addCommand(QPaintBufferPrivate::Cmd_Restore); + } + + d->last = s->matrix; + + QPaintEngineEx::setState(s); +} + + +/*********************************************************************** + * + * class QPaintBufferPlayback_Painter + * + */ + +// QFakeDevice is used to create fonts with a custom DPI +// +class QFakeDevice : public QPaintDevice +{ +public: + QFakeDevice() { dpi_x = qt_defaultDpiX(); dpi_y = qt_defaultDpiY(); } + void setDpiX(int dpi) { dpi_x = dpi; } + void setDpiY(int dpi) { dpi_y = dpi; } + QPaintEngine *paintEngine() const { return 0; } + int metric(PaintDeviceMetric m) const + { + switch(m) { + case PdmPhysicalDpiX: + case PdmDpiX: + return dpi_x; + case PdmPhysicalDpiY: + case PdmDpiY: + return dpi_y; + default: + return QPaintDevice::metric(m); + } + } + +private: + int dpi_x; + int dpi_y; +}; + + +void QPainterReplayer::setupTransform(QPainter *_painter) +{ + painter = _painter; + m_world_matrix = painter->transform(); + m_world_matrix.scale(qreal(painter->device()->logicalDpiX()) / qreal(qt_defaultDpiX()), + qreal(painter->device()->logicalDpiY()) / qreal(qt_defaultDpiY())); + painter->setTransform(m_world_matrix); +} + +void QPainterReplayer::processCommands(const QPaintBuffer &buffer, QPainter *p, int begin, int end) +{ + d = buffer.d_ptr; + painter = p; + + for (int cmdIndex = begin; cmdIndex < end; ++cmdIndex) { + const QPaintBufferCommand &cmd = d->commands.at(cmdIndex); + process(cmd); + } +} + +void QPaintBuffer::beginNewFrame() +{ + if (!d_ptr->commands.isEmpty()) + d_ptr->frames << d_ptr->commands.size(); +} + +int QPaintBuffer::numFrames() const +{ + return d_ptr->frames.size() + 1; +} + +void QPainterReplayer::process(const QPaintBufferCommand &cmd) +{ + switch (cmd.id) { + case QPaintBufferPrivate::Cmd_Save: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_Save"; +#endif + painter->save(); + break; } + + case QPaintBufferPrivate::Cmd_Restore: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_Restore"; +#endif + painter->restore(); + break; } + + case QPaintBufferPrivate::Cmd_SetPen: { + QPen pen = qvariant_cast<QPen>(d->variants.at(cmd.offset)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetPen: " << pen; +#endif + painter->setPen(pen); + break; } + + case QPaintBufferPrivate::Cmd_SetBrush: { + QBrush brush = qvariant_cast<QBrush>(d->variants.at(cmd.offset)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetBrush: " << brush; +#endif + painter->setBrush(brush); + break; } + + case QPaintBufferPrivate::Cmd_SetBrushOrigin: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetBrushOrigin: " << d->variants.at(cmd.offset).toPointF(); +#endif + painter->setBrushOrigin(d->variants.at(cmd.offset).toPointF()); + break; } + + case QPaintBufferPrivate::Cmd_SetTransform: { + QTransform xform = qvariant_cast<QTransform>(d->variants.at(cmd.offset)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetTransform, offset: " << cmd.offset << xform; +#endif + painter->setTransform(xform * m_world_matrix); + break; } + + case QPaintBufferPrivate::Cmd_Translate: { + QPointF delta(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_Translate, offset: " << cmd.offset << delta; +#endif + painter->translate(delta.x(), delta.y()); + return; + } + + case QPaintBufferPrivate::Cmd_SetCompositionMode: { + QPainter::CompositionMode mode = (QPainter::CompositionMode) cmd.extra; +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetCompositionMode, mode: " << mode; +#endif + painter->setCompositionMode(mode); + break; } + + case QPaintBufferPrivate::Cmd_SetRenderHints: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetRenderHints, hints: " << cmd.extra; +#endif + QPainter::RenderHints ph = painter->renderHints(); + QPainter::RenderHints nh = (QPainter::RenderHints) cmd.extra; + QPainter::RenderHints xored = ph ^ nh; + if (xored & QPainter::Antialiasing) + painter->setRenderHint(QPainter::Antialiasing, nh & QPainter::Antialiasing); + if (xored & QPainter::HighQualityAntialiasing) + painter->setRenderHint(QPainter::HighQualityAntialiasing, nh & QPainter::HighQualityAntialiasing); + if (xored & QPainter::TextAntialiasing) + painter->setRenderHint(QPainter::TextAntialiasing, nh & QPainter::TextAntialiasing); + if (xored & QPainter::SmoothPixmapTransform) + painter->setRenderHint(QPainter::SmoothPixmapTransform, nh & QPainter::SmoothPixmapTransform); + if (xored & QPainter::NonCosmeticDefaultPen) + painter->setRenderHint(QPainter::NonCosmeticDefaultPen, nh & QPainter::NonCosmeticDefaultPen); + break; } + + case QPaintBufferPrivate::Cmd_SetOpacity: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetOpacity: " << d->variants.at(cmd.offset).toDouble(); +#endif + painter->setOpacity(d->variants.at(cmd.offset).toDouble()); + break; } + + case QPaintBufferPrivate::Cmd_SetBackgroundMode: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetBackgroundMode: " << cmd.extra; +#endif + painter->setBackgroundMode((Qt::BGMode)cmd.extra); + break; } + + case QPaintBufferPrivate::Cmd_DrawVectorPath: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2; +#endif + QVectorPathCmd path(d, cmd); + painter->drawPath(path().convertToPainterPath()); + break; } + + case QPaintBufferPrivate::Cmd_StrokeVectorPath: { + QPen pen = qvariant_cast<QPen>(d->variants.at(cmd.extra)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_StrokeVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2; +#endif + QVectorPathCmd path(d, cmd); + painter->strokePath(path().convertToPainterPath(), pen); + break; } + + case QPaintBufferPrivate::Cmd_FillVectorPath: { + QBrush brush = qvariant_cast<QBrush>(d->variants.at(cmd.extra)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_FillVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2 << brush; +#endif + QVectorPathCmd path(d, cmd); + painter->fillPath(path().convertToPainterPath(), brush); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPolygonF, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d->floats.at(cmd.offset) + << d->floats.at(cmd.offset+1); +#endif + Qt::FillRule fill = (QPaintEngine::PolygonDrawMode) cmd.extra == QPaintEngine::OddEvenMode + ? Qt::OddEvenFill : Qt::WindingFill; + painter->drawPolygon((QPointF *) (d->floats.constData() + cmd.offset), cmd.size, fill); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPolygonI, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d->ints.at(cmd.offset) + << d->ints.at(cmd.offset+1); +#endif + Qt::FillRule fill = (QPaintEngine::PolygonDrawMode) cmd.extra == QPaintEngine::OddEvenMode + ? Qt::OddEvenFill : Qt::WindingFill; + painter->drawPolygon((QPoint *) (d->ints.constData() + cmd.offset), cmd.size, fill); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPolylineF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawPolyline((QPointF *) (d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPolylineI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawPolyline((QPoint *) (d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawConvexPolygonF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawConvexPolygonF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawConvexPolygon((QPointF *) (d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawConvexPolygonI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawConvexPolygonI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawConvexPolygon((QPoint *) (d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawEllipseF, offset: " << cmd.offset; +#endif + painter->drawEllipse(*(QRectF *)(d->floats.constData() + cmd.offset)); + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawEllipseI, offset: " << cmd.offset; +#endif + painter->drawEllipse(*(QRect *)(d->ints.constData() + cmd.offset)); + break; } + + case QPaintBufferPrivate::Cmd_DrawLineF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawLineF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawLines((QLineF *)(d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawLineI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawLineI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawLines((QLine *)(d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPointsF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawPoints((QPointF *)(d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPointsI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + painter->drawPoints((QPoint *)(d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPixmapRect: { + QPixmap pm(d->variants.at(cmd.offset).value<QPixmap>()); + QRectF r(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1), + d->floats.at(cmd.extra+2), d->floats.at(cmd.extra+3)); + + QRectF sr(d->floats.at(cmd.extra+4), d->floats.at(cmd.extra+5), + d->floats.at(cmd.extra+6), d->floats.at(cmd.extra+7)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPixmapRect:" << r << sr; +#endif + painter->drawPixmap(r, pm, sr); + break; } + + case QPaintBufferPrivate::Cmd_DrawPixmapPos: { + QPixmap pm(d->variants.at(cmd.offset).value<QPixmap>()); + QPointF pos(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawPixmapPos:" << pos; +#endif + painter->drawPixmap(pos, pm); + break; } + + case QPaintBufferPrivate::Cmd_DrawTiledPixmap: { + QPixmap pm(d->variants.at(cmd.offset).value<QPixmap>()); + QRectF r(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1), + d->floats.at(cmd.extra+2), d->floats.at(cmd.extra+3)); + + QPointF offset(d->floats.at(cmd.extra+4), d->floats.at(cmd.extra+5)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawTiledPixmap:" << r << offset; +#endif + painter->drawTiledPixmap(r, pm, offset); + break; } + + case QPaintBufferPrivate::Cmd_DrawImageRect: { + QImage image(d->variants.at(cmd.offset).value<QImage>()); + QRectF r(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1), + d->floats.at(cmd.extra+2), d->floats.at(cmd.extra+3)); + QRectF sr(d->floats.at(cmd.extra+4), d->floats.at(cmd.extra+5), + d->floats.at(cmd.extra+6), d->floats.at(cmd.extra+7)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawImageRect:" << r << sr; +#endif + painter->drawImage(r, image, sr); + break; } + + case QPaintBufferPrivate::Cmd_DrawImagePos: { + QImage image(d->variants.at(cmd.offset).value<QImage>()); + QPointF pos(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawImagePos:" << pos; +#endif + painter->drawImage(pos, image); + break; } + + case QPaintBufferPrivate::Cmd_DrawRectF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawRectF, offset: " << cmd.offset; +#endif + painter->drawRects((QRectF *)(d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawRectI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawRectI, offset: " << cmd.offset; +#endif + painter->drawRects((QRect *)(d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_FillRectBrush: { + QBrush brush = qvariant_cast<QBrush>(d->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d->floats.constData() + cmd.offset); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " brush: " << brush; +#endif + painter->fillRect(*rect, brush); + break; } + + case QPaintBufferPrivate::Cmd_FillRectColor: { + QColor color = qvariant_cast<QColor>(d->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d->floats.constData() + cmd.offset); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " color: " << color; +#endif + painter->fillRect(*rect, color); + break; } + + case QPaintBufferPrivate::Cmd_SetClipEnabled: { + bool clipEnabled = d->variants.at(cmd.offset).toBool(); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SetClipEnabled:" << clipEnabled; +#endif + painter->setClipping(clipEnabled); + break; } + + case QPaintBufferPrivate::Cmd_ClipVectorPath: { + QVectorPathCmd path(d, cmd); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_ClipVectorPath:" << path().elementCount(); +#endif + painter->setClipPath(path().convertToPainterPath(), Qt::ClipOperation(cmd.extra)); + break; } + + + case QPaintBufferPrivate::Cmd_ClipRect: { + QRect rect(QPoint(d->ints.at(cmd.offset), d->ints.at(cmd.offset + 1)), + QPoint(d->ints.at(cmd.offset + 2), d->ints.at(cmd.offset + 3))); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_ClipRect:" << rect << cmd.extra; +#endif + painter->setClipRect(rect, Qt::ClipOperation(cmd.extra)); + break; } + + case QPaintBufferPrivate::Cmd_ClipRegion: { + QRegion region(d->variants.at(cmd.offset).value<QRegion>()); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_ClipRegion:" << region.boundingRect() << cmd.extra; +#endif + painter->setClipRegion(region, Qt::ClipOperation(cmd.extra)); + break; } + +#if !defined(QT_NO_RAWFONT) + case QPaintBufferPrivate::Cmd_DrawStaticText: { + + QVariantList variants(d->variants.at(cmd.offset).value<QVariantList>()); + + QFont font = variants.at(0).value<QFont>(); + + QVector<quint32> glyphIndexes; + QVector<QPointF> positions; + + for (int i=0; i<(variants.size() - 1) / 2; ++i) { + glyphIndexes.append(variants.at(i*2 + 1).toUInt()); + positions.append(variants.at(i*2 + 2).toPointF()); + } + + painter->setFont(font); + + QRawFont rawFont; + QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont); + QFontPrivate *fontD = QFontPrivate::get(font); + rawFontD->fontEngine = fontD->engineForScript(QUnicodeTables::Common); + rawFontD->fontEngine->ref.ref(); + + QGlyphs glyphs; + glyphs.setFont(rawFont); + glyphs.setGlyphIndexes(glyphIndexes); + glyphs.setPositions(positions); + + painter->drawGlyphs(QPointF(), glyphs); + break; + } +#endif + + case QPaintBufferPrivate::Cmd_DrawText: { + QPointF pos(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1)); + QList<QVariant> variants(d->variants.at(cmd.offset).value<QList<QVariant> >()); + + QFont font(variants.at(0).value<QFont>()); + QString text(variants.at(1).value<QString>()); + + painter->setFont(font); + painter->drawText(pos, text); + break; } + + case QPaintBufferPrivate::Cmd_DrawTextItem: { + QPointF pos(d->floats.at(cmd.extra), d->floats.at(cmd.extra+1)); + QTextItemIntCopy *tiCopy = reinterpret_cast<QTextItemIntCopy *>(qvariant_cast<void *>(d->variants.at(cmd.offset))); + QTextItemInt &ti = (*tiCopy)(); + QString text(ti.text()); + + QFont font(ti.font()); + font.setUnderline(false); + font.setStrikeOut(false); + font.setOverline(false); + + const QTextItemInt &si = static_cast<const QTextItemInt &>(ti); + qreal justificationWidth = 0; + if (si.justified) + justificationWidth = si.width.toReal(); + qreal scaleFactor = font.d->dpi/qreal(qt_defaultDpiY()); + +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_DrawTextItem:" << pos << " " << text << " " << scaleFactor; +#endif + + if (scaleFactor != 1.0) { + QFont fnt(font); + QFakeDevice fake; + fake.setDpiX(qRound(scaleFactor*qt_defaultDpiX())); + fake.setDpiY(qRound(scaleFactor*qt_defaultDpiY())); + font = QFont(fnt, &fake); + } + + int flags = Qt::TextSingleLine | Qt::TextDontClip | Qt::TextForceLeftToRight; + QSizeF size(1, 1); + if (justificationWidth > 0) { + size.setWidth(justificationWidth); + flags |= Qt::TextJustificationForced; + flags |= Qt::AlignJustify; + } + + QFontMetrics fm(font); + QPointF pt(pos.x(), pos.y() - fm.ascent()); + qt_format_text(font, QRectF(pt, size), flags, /*opt*/0, + text, /*brect=*/0, /*tabstops=*/0, /*...*/0, /*tabarraylen=*/0, painter); + break; } + case QPaintBufferPrivate::Cmd_SystemStateChanged: { + QRegion systemClip(d->variants.at(cmd.offset).value<QRegion>()); + +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> Cmd_SystemStateChanged:" << systemClip; +#endif + + painter->paintEngine()->setSystemClip(systemClip); + painter->paintEngine()->d_ptr->systemStateChanged(); + break; } + } +} + +void QPaintEngineExReplayer::process(const QPaintBufferCommand &cmd) +{ + Q_ASSERT(painter->paintEngine()->isExtended()); + QPaintEngineEx *xengine = static_cast<QPaintEngineEx *>(painter->paintEngine()); + + switch (cmd.id) { + case QPaintBufferPrivate::Cmd_SetBrushOrigin: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_SetBrushOrigin: " << d->variants.at(cmd.offset).toPointF(); +#endif + xengine->state()->brushOrigin = d->variants.at(cmd.offset).toPointF(); + xengine->brushOriginChanged(); + break; } + + case QPaintBufferPrivate::Cmd_SetCompositionMode: { + QPainter::CompositionMode mode = (QPainter::CompositionMode) cmd.extra; +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_SetCompositionMode, mode: " << mode; +#endif + xengine->state()->composition_mode = mode; + xengine->compositionModeChanged(); + break; } + + case QPaintBufferPrivate::Cmd_SetOpacity: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_SetOpacity: " << d->variants.at(cmd.offset).toDouble(); +#endif + xengine->state()->opacity = d->variants.at(cmd.offset).toDouble(); + xengine->opacityChanged(); + break; } + + case QPaintBufferPrivate::Cmd_DrawVectorPath: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2; +#endif + QVectorPathCmd path(d, cmd); + xengine->draw(path()); + break; } + + case QPaintBufferPrivate::Cmd_StrokeVectorPath: { + QPen pen = qvariant_cast<QPen>(d->variants.at(cmd.extra)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_StrokeVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2; +#endif + QVectorPathCmd path(d, cmd); + xengine->stroke(path(), pen); + break; } + + case QPaintBufferPrivate::Cmd_FillVectorPath: { + QBrush brush = qvariant_cast<QBrush>(d->variants.at(cmd.extra)); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_FillVectorPath: size: " << cmd.size +// << ", hints:" << d->ints[cmd.offset2+cmd.size] + << "pts/elms:" << cmd.offset << cmd.offset2 << brush; +#endif + QVectorPathCmd path(d, cmd); + xengine->fill(path(), brush); + break; } + + case QPaintBufferPrivate::Cmd_FillRectBrush: { + QBrush brush = qvariant_cast<QBrush>(d->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d->floats.constData() + cmd.offset); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " brush: " << brush; +#endif + xengine->fillRect(*rect, brush); + break; } + + case QPaintBufferPrivate::Cmd_FillRectColor: { + QColor color = qvariant_cast<QColor>(d->variants.at(cmd.extra)); + QRectF *rect = (QRectF *)(d->floats.constData() + cmd.offset); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_FillRectBrush, offset: " << cmd.offset << " rect: " << *rect << " color: " << color; +#endif + xengine->fillRect(*rect, color); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPolygonF, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d->floats.at(cmd.offset) + << d->floats.at(cmd.offset+1); +#endif + xengine->drawPolygon((QPointF *) (d->floats.constData() + cmd.offset), cmd.size, + (QPaintEngine::PolygonDrawMode) cmd.extra); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolygonI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPolygonI, offset: " << cmd.offset << " size: " << cmd.size + << " mode: " << cmd.extra + << d->ints.at(cmd.offset) + << d->ints.at(cmd.offset+1); +#endif + xengine->drawPolygon((QPoint *) (d->ints.constData() + cmd.offset), cmd.size, + (QPaintEngine::PolygonDrawMode) cmd.extra); + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawEllipseF, offset: " << cmd.offset; +#endif + xengine->drawEllipse(*(QRectF *)(d->floats.constData() + cmd.offset)); + break; } + + case QPaintBufferPrivate::Cmd_DrawEllipseI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawEllipseI, offset: " << cmd.offset; +#endif + xengine->drawEllipse(*(QRect *)(d->ints.constData() + cmd.offset)); + break; } + + case QPaintBufferPrivate::Cmd_DrawLineF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawLineF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawLines((QLineF *)(d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawLineI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawLineI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawLines((QLine *)(d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPointsF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawPoints((QPointF *)(d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPointsI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPointsI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawPoints((QPoint *)(d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPolylineF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawPolygon((QPointF *) (d->floats.constData() + cmd.offset), cmd.size, QPaintEngine::PolylineMode); + break; } + + case QPaintBufferPrivate::Cmd_DrawPolylineI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawPolylineI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawPolygon((QPoint *) (d->ints.constData() + cmd.offset), cmd.size, QPaintEngine::PolylineMode); + break; } + + case QPaintBufferPrivate::Cmd_DrawRectF: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawRectF, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawRects((QRectF *) (d->floats.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_DrawRectI: { +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_DrawRectI, offset: " << cmd.offset << " size: " << cmd.size; +#endif + xengine->drawRects((QRect *) (d->ints.constData() + cmd.offset), cmd.size); + break; } + + case QPaintBufferPrivate::Cmd_SetClipEnabled: { + bool clipEnabled = d->variants.at(cmd.offset).toBool(); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_SetClipEnabled:" << clipEnabled; +#endif + xengine->state()->clipEnabled = clipEnabled; + xengine->clipEnabledChanged(); + break; } + + case QPaintBufferPrivate::Cmd_ClipVectorPath: { + QVectorPathCmd path(d, cmd); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_ClipVectorPath:" << path().elementCount(); +#endif + xengine->clip(path(), Qt::ClipOperation(cmd.extra)); + break; } + + + case QPaintBufferPrivate::Cmd_ClipRect: { + QRect rect(QPoint(d->ints.at(cmd.offset), d->ints.at(cmd.offset + 1)), + QPoint(d->ints.at(cmd.offset + 2), d->ints.at(cmd.offset + 3))); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_ClipRect:" << rect << cmd.extra; +#endif + xengine->clip(rect, Qt::ClipOperation(cmd.extra)); + break; } + + case QPaintBufferPrivate::Cmd_ClipRegion: { + QRegion region(d->variants.at(cmd.offset).value<QRegion>()); +#ifdef QPAINTBUFFER_DEBUG_DRAW + qDebug() << " -> ExCmd_ClipRegion:" << region.boundingRect() << cmd.extra; +#endif + xengine->clip(region, Qt::ClipOperation(cmd.extra)); + break; } + + default: + QPainterReplayer::process(cmd); + break; + } +} + +QPaintBufferResource::QPaintBufferResource(FreeFunc f, QObject *parent) : QObject(parent), free(f) +{ + connect(QPaintBufferSignalProxy::instance(), SIGNAL(aboutToDestroy(const QPaintBufferPrivate*)), this, SLOT(remove(const QPaintBufferPrivate*))); +} + +QPaintBufferResource::~QPaintBufferResource() +{ + for (Cache::iterator it = m_cache.begin(); it != m_cache.end(); ++it) + free(it.value()); +} + +void QPaintBufferResource::insert(const QPaintBufferPrivate *key, void *value) +{ + Cache::iterator it = m_cache.find(key); + if (it != m_cache.end()) { + free(it.value()); + it.value() = value; + } else { + m_cache.insert(key, value); + } +} + +void *QPaintBufferResource::value(const QPaintBufferPrivate *key) +{ + Cache::iterator it = m_cache.find(key); + if (it != m_cache.end()) + return it.value(); + return 0; +} + +void QPaintBufferResource::remove(const QPaintBufferPrivate *key) +{ + Cache::iterator it = m_cache.find(key); + if (it != m_cache.end()) { + free(it.value()); + m_cache.erase(it); + } +} + +QDataStream &operator<<(QDataStream &stream, const QPaintBufferCommand &command) +{ + quint32 id = command.id; + quint32 size = command.size; + stream << id << size; + stream << command.offset << command.offset2 << command.extra; + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QPaintBufferCommand &command) +{ + quint32 id; + quint32 size; + stream >> id >> size; + stream >> command.offset >> command.offset2 >> command.extra; + command.id = id; + command.size = size; + return stream; +} + +struct QPaintBufferCacheEntry +{ + QVariant::Type type; + quint64 cacheKey; +}; + +struct QPaintBufferCacheEntryV2 +{ + enum Type { + ImageKey, + PixmapKey + }; + + struct Flags { + uint type : 8; + uint key : 24; + }; + + union { + Flags flags; + uint bits; + }; +}; + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(QPaintBufferCacheEntry) +Q_DECLARE_METATYPE(QPaintBufferCacheEntryV2) +QT_BEGIN_NAMESPACE + +QDataStream &operator<<(QDataStream &stream, const QPaintBufferCacheEntry &entry) +{ + return stream << entry.type << entry.cacheKey; +} + +QDataStream &operator>>(QDataStream &stream, QPaintBufferCacheEntry &entry) +{ + return stream >> entry.type >> entry.cacheKey; +} + +QDataStream &operator<<(QDataStream &stream, const QPaintBufferCacheEntryV2 &entry) +{ + return stream << entry.bits; +} + +QDataStream &operator>>(QDataStream &stream, QPaintBufferCacheEntryV2 &entry) +{ + return stream >> entry.bits; +} + +static int qRegisterPaintBufferMetaTypes() +{ + qRegisterMetaType<QPaintBufferCacheEntry>(); + qRegisterMetaTypeStreamOperators<QPaintBufferCacheEntry>("QPaintBufferCacheEntry"); + qRegisterMetaType<QPaintBufferCacheEntryV2>(); + qRegisterMetaTypeStreamOperators<QPaintBufferCacheEntryV2>("QPaintBufferCacheEntryV2"); + + return 0; // something +} + +Q_CONSTRUCTOR_FUNCTION(qRegisterPaintBufferMetaTypes) + +QDataStream &operator<<(QDataStream &stream, const QPaintBuffer &buffer) +{ + QHash<qint64, uint> pixmapKeys; + QHash<qint64, uint> imageKeys; + + QHash<qint64, QPixmap> pixmaps; + QHash<qint64, QImage> images; + + QVector<QVariant> variants = buffer.d_ptr->variants; + for (int i = 0; i < variants.size(); ++i) { + const QVariant &v = variants.at(i); + if (v.type() == QVariant::Image) { + const QImage image(v.value<QImage>()); + + QPaintBufferCacheEntryV2 entry; + entry.flags.type = QPaintBufferCacheEntryV2::ImageKey; + + QHash<qint64, uint>::iterator it = imageKeys.find(image.cacheKey()); + if (it != imageKeys.end()) { + entry.flags.key = *it; + } else { + imageKeys[image.cacheKey()] = entry.flags.key = images.size(); + images[images.size()] = image; + } + + variants[i] = QVariant::fromValue(entry); + } else if (v.type() == QVariant::Pixmap) { + const QPixmap pixmap(v.value<QPixmap>()); + + QPaintBufferCacheEntryV2 entry; + entry.flags.type = QPaintBufferCacheEntryV2::PixmapKey; + + QHash<qint64, uint>::iterator it = pixmapKeys.find(pixmap.cacheKey()); + if (it != pixmapKeys.end()) { + entry.flags.key = *it; + } else { + pixmapKeys[pixmap.cacheKey()] = entry.flags.key = pixmaps.size(); + pixmaps[pixmaps.size()] = pixmap; + } + + variants[i] = QVariant::fromValue(entry); + } + } + + stream << pixmaps; + stream << images; + + stream << buffer.d_ptr->ints; + stream << buffer.d_ptr->floats; + stream << variants; + stream << buffer.d_ptr->commands; + stream << buffer.d_ptr->boundingRect; + stream << buffer.d_ptr->frames; + + return stream; +} + +QDataStream &operator>>(QDataStream &stream, QPaintBuffer &buffer) +{ + QHash<qint64, QPixmap> pixmaps; + QHash<qint64, QImage> images; + + stream >> pixmaps; + stream >> images; + + stream >> buffer.d_ptr->ints; + stream >> buffer.d_ptr->floats; + stream >> buffer.d_ptr->variants; + stream >> buffer.d_ptr->commands; + stream >> buffer.d_ptr->boundingRect; + stream >> buffer.d_ptr->frames; + + QVector<QVariant> &variants = buffer.d_ptr->variants; + for (int i = 0; i < variants.size(); ++i) { + const QVariant &v = variants.at(i); + if (v.canConvert<QPaintBufferCacheEntry>()) { + QPaintBufferCacheEntry entry = v.value<QPaintBufferCacheEntry>(); + if (entry.type == QVariant::Image) + variants[i] = QVariant(images.value(entry.cacheKey)); + else + variants[i] = QVariant(pixmaps.value(entry.cacheKey)); + } else if (v.canConvert<QPaintBufferCacheEntryV2>()) { + QPaintBufferCacheEntryV2 entry = v.value<QPaintBufferCacheEntryV2>(); + + if (entry.flags.type == QPaintBufferCacheEntryV2::ImageKey) + variants[i] = QVariant(images.value(entry.flags.key)); + else if (entry.flags.type == QPaintBufferCacheEntryV2::PixmapKey) + variants[i] = QVariant(pixmaps.value(entry.flags.key)); + else + qWarning() << "operator<<(QDataStream &stream, QPaintBuffer &buffer): unrecognized cache entry type:" << entry.flags.type; + } + } + + return stream; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintbuffer_p.h b/src/gui/painting/qpaintbuffer_p.h new file mode 100644 index 0000000000..6272dd912d --- /dev/null +++ b/src/gui/painting/qpaintbuffer_p.h @@ -0,0 +1,461 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTBUFFER_P_H +#define QPAINTBUFFER_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 <qpaintdevice.h> + +#include <private/qpaintengineex_p.h> +#include <private/qtextengine_p.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +class QPaintBufferPrivate; +class QPaintBufferPlayback; + +class Q_GUI_EXPORT QPaintBuffer : public QPaintDevice +{ + Q_DECLARE_PRIVATE(QPaintBuffer) +public: + QPaintBuffer(); + QPaintBuffer(const QPaintBuffer &other); + ~QPaintBuffer(); + + bool isEmpty() const; + + void beginNewFrame(); + int numFrames() const; + + void draw(QPainter *painter, int frame = 0) const; + + int frameStartIndex(int frame) const; + int frameEndIndex(int frame) const; + int processCommands(QPainter *painter, int begin, int end) const; +#ifndef QT_NO_DEBUG_STREAM + QString commandDescription(int command) const; +#endif + + void setBoundingRect(const QRectF &rect); + QRectF boundingRect() const; + + virtual QPaintEngine *paintEngine() const; + virtual int metric(PaintDeviceMetric m) const; + virtual int devType() const; + + QPaintBuffer &operator=(const QPaintBuffer &other); + +private: + friend class QPainterReplayer; + friend class QOpenGLReplayer; + + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QPaintBuffer &buffer); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QPaintBuffer &buffer); + + QPaintBufferPrivate *d_ptr; +}; + +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QPaintBuffer &buffer); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QPaintBuffer &buffer); + +class QPaintBufferEngine; + +class QTextItemIntCopy +{ +public: + QTextItemIntCopy(const QTextItem &item); + ~QTextItemIntCopy(); + QTextItemInt &operator () () {return m_item;} +private: + QTextItemInt m_item; + QFont m_font; +}; + +struct QPaintBufferCommand +{ + uint id : 8; + uint size : 24; + + int offset; + int offset2; + int extra; +}; + +QDataStream &operator<<(QDataStream &stream, const QPaintBufferCommand &command); +QDataStream &operator>>(QDataStream &stream, QPaintBufferCommand &command); + +Q_DECLARE_TYPEINFO(QPaintBufferCommand, Q_MOVABLE_TYPE); + +class QPaintBufferPrivate +{ +public: + enum Command { + Cmd_Save, + Cmd_Restore, + + Cmd_SetBrush, + Cmd_SetBrushOrigin, + Cmd_SetClipEnabled, + Cmd_SetCompositionMode, + Cmd_SetOpacity, + Cmd_SetPen, + Cmd_SetRenderHints, + Cmd_SetTransform, + Cmd_SetBackgroundMode, + + Cmd_ClipPath, + Cmd_ClipRect, + Cmd_ClipRegion, + Cmd_ClipVectorPath, + + Cmd_DrawVectorPath, + Cmd_FillVectorPath, + Cmd_StrokeVectorPath, + + Cmd_DrawConvexPolygonF, + Cmd_DrawConvexPolygonI, + Cmd_DrawEllipseF, + Cmd_DrawEllipseI, + Cmd_DrawLineF, + Cmd_DrawLineI, + Cmd_DrawPath, + Cmd_DrawPointsF, + Cmd_DrawPointsI, + Cmd_DrawPolygonF, + Cmd_DrawPolygonI, + Cmd_DrawPolylineF, + Cmd_DrawPolylineI, + Cmd_DrawRectF, + Cmd_DrawRectI, + + Cmd_FillRectBrush, + Cmd_FillRectColor, + + Cmd_DrawText, + Cmd_DrawTextItem, + + Cmd_DrawImagePos, + Cmd_DrawImageRect, + Cmd_DrawPixmapPos, + Cmd_DrawPixmapRect, + Cmd_DrawTiledPixmap, + + Cmd_SystemStateChanged, + Cmd_Translate, + Cmd_DrawStaticText, + + // new commands must be added above this line + + Cmd_LastCommand + }; + + QPaintBufferPrivate(); + ~QPaintBufferPrivate(); + + int addData(const int *data, int count) { + if (count <= 0) + return 0; + int pos = ints.size(); + ints.resize(pos + count); + memcpy(ints.data() + pos, data, count * sizeof(int)); + return pos; + } + + int addData(const qreal *data, int count) { + if (count <= 0) + return 0; + int pos = floats.size(); + floats.resize(pos + count); + memcpy(floats.data() + pos, data, count * sizeof(qreal)); + return pos; + } + + int addData(const QVariant &var) { + variants << var; + return variants.size() - 1; + } + + QPaintBufferCommand *addCommand(Command command) { + QPaintBufferCommand cmd; + cmd.id = command; + cmd.size = cmd.offset = cmd.offset2 = cmd.extra = 0; + commands << cmd; + return &commands.last(); + } + + QPaintBufferCommand *addCommand(Command command, const QVariant &var) { + QPaintBufferCommand cmd; + cmd.id = command; + cmd.offset = addData(var); + cmd.size = cmd.offset2 = cmd.extra = 0; + commands << cmd; + return &commands.last(); + } + + QPaintBufferCommand *addCommand(Command command, const QVectorPath &path) { + QPaintBufferCommand cmd; + cmd.id = command; + cmd.offset = addData(path.points(), path.elementCount() * 2); + cmd.offset2 = ints.size(); + ints << path.hints(); + // The absence of path elements is indicated by setting the highest bit in 'cmd.offset2'. + if (path.elements()) + addData((const int *) path.elements(), path.elementCount()); + else + cmd.offset2 |= 0x80000000; + cmd.size = path.elementCount(); + cmd.extra = 0; + commands << cmd; + return &commands.last(); + } + + QPaintBufferCommand *addCommand(Command command , const qreal *pts, int arrayLength, int elementCount) { + QPaintBufferCommand cmd; + cmd.id = command; + cmd.offset = addData(pts, arrayLength); + cmd.size = elementCount; + cmd.offset2 = cmd.extra = 0; + commands << cmd; + return &commands.last(); + } + + QPaintBufferCommand *addCommand(Command command , const int *pts, int arrayLength, int elementCount) { + QPaintBufferCommand cmd; + cmd.id = command; + cmd.offset = addData(pts, arrayLength); + cmd.size = elementCount; + cmd.offset2 = cmd.extra = 0; + commands << cmd; + return &commands.last(); + } + + inline void updateBoundingRect(const QRectF &rect); + + QAtomicInt ref; + + QVector<int> ints; + QVector<qreal> floats; + QVector<QVariant> variants; + + QVector<QPaintBufferCommand> commands; + QList<int> frames; + + QPaintBufferEngine *engine; + QRectF boundingRect; + qreal penWidthAdjustment; + uint calculateBoundingRect : 1; + + void *cache; +}; + + +struct QVectorPathCmd +{ + // The absence of path elements is indicated by setting the highest bit in 'cmd.offset2'. + QVectorPathCmd(QPaintBufferPrivate *d, const QPaintBufferCommand &cmd) + : vectorPath(d->floats.constData() + cmd.offset, + cmd.size, + cmd.offset2 & 0x80000000 + ? 0 + : (const QPainterPath::ElementType *) (d->ints.constData() + cmd.offset2 + 1), + *(d->ints.constData() + (cmd.offset2 & 0x7fffffff))) {} + + inline const QVectorPath &operator()() const { return vectorPath; } + + QVectorPath vectorPath; +}; + + +class Q_GUI_EXPORT QPainterReplayer +{ +public: + QPainterReplayer() { } + + virtual ~QPainterReplayer() { } + + void setupTransform(QPainter *painter); + virtual void process(const QPaintBufferCommand &cmd); + void processCommands(const QPaintBuffer &buffer, QPainter *painter, int begin, int end); + +protected: + QPaintBufferPrivate *d; + QTransform m_world_matrix; + + QPainter *painter; +}; + +class Q_GUI_EXPORT QPaintEngineExReplayer : public QPainterReplayer +{ +public: + QPaintEngineExReplayer() { } + + virtual void process(const QPaintBufferCommand &cmd); +}; + +class QPaintBufferEnginePrivate; + +class QPaintBufferEngine : public QPaintEngineEx +{ + Q_DECLARE_PRIVATE(QPaintBufferEngine) +public: + QPaintBufferEngine(QPaintBufferPrivate *buffer); + + virtual bool begin(QPaintDevice *device); + virtual bool end(); + + virtual Type type() const { return QPaintEngine::PaintBuffer; } + + virtual QPainterState *createState(QPainterState *orig) const; + + virtual void draw(const QVectorPath &path); + virtual void fill(const QVectorPath &path, const QBrush &brush); + virtual void stroke(const QVectorPath &path, const QPen &pen); + + virtual void clip(const QVectorPath &path, Qt::ClipOperation op); + virtual void clip(const QRect &rect, Qt::ClipOperation op); + virtual void clip(const QRegion ®ion, Qt::ClipOperation op); + virtual void clip(const QPainterPath &path, Qt::ClipOperation op); + + virtual void clipEnabledChanged(); + virtual void penChanged(); + virtual void brushChanged(); + virtual void brushOriginChanged(); + virtual void opacityChanged(); + virtual void compositionModeChanged(); + virtual void renderHintsChanged(); + virtual void transformChanged(); + virtual void backgroundModeChanged(); + + virtual void fillRect(const QRectF &rect, const QBrush &brush); + virtual void fillRect(const QRectF &rect, const QColor &color); + + virtual void drawRects(const QRect *rects, int rectCount); + virtual void drawRects(const QRectF *rects, int rectCount); + + virtual void drawLines(const QLine *lines, int lineCount); + virtual void drawLines(const QLineF *lines, int lineCount); + + virtual void drawEllipse(const QRectF &r); + virtual void drawEllipse(const QRect &r); + + virtual void drawPath(const QPainterPath &path); + + virtual void drawPoints(const QPointF *points, int pointCount); + virtual void drawPoints(const QPoint *points, int pointCount); + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + virtual void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + virtual void drawPixmap(const QPointF &pos, const QPixmap &pm); + + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + virtual void drawImage(const QPointF &pos, const QImage &image); + + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + + virtual void drawTextItem(const QPointF &pos, const QTextItem &ti); + virtual void drawStaticTextItem(QStaticTextItem *staticTextItem); + + virtual void setState(QPainterState *s); + virtual uint flags() const {return QPaintEngineEx::DoNotEmulate;} + + QPaintBufferPrivate *buffer; + + mutable int m_begin_detected : 1; + mutable int m_save_detected : 1; + mutable int m_stream_raw_text_items : 1; + mutable int m_unused : 29; + + mutable QPainterState *m_created_state; +}; + +class Q_GUI_EXPORT QPaintBufferSignalProxy : public QObject +{ + Q_OBJECT +public: + QPaintBufferSignalProxy() : QObject() {} + void emitAboutToDestroy(const QPaintBufferPrivate *buffer) { + emit aboutToDestroy(buffer); + } + static QPaintBufferSignalProxy *instance(); +Q_SIGNALS: + void aboutToDestroy(const QPaintBufferPrivate *buffer); +}; + +// One resource per paint buffer and vice versa. +class Q_GUI_EXPORT QPaintBufferResource : public QObject +{ + Q_OBJECT +public: + typedef void (*FreeFunc)(void *); + + QPaintBufferResource(FreeFunc f, QObject *parent = 0); + ~QPaintBufferResource(); + // Set resource 'value' for 'key'. + void insert(const QPaintBufferPrivate *key, void *value); + // Return resource for 'key'. + void *value(const QPaintBufferPrivate *key); +public slots: + // Remove entry 'key' from cache and delete resource. + void remove(const QPaintBufferPrivate *key); +private: + typedef QHash<const QPaintBufferPrivate *, void *> Cache; + Cache m_cache; + FreeFunc free; +}; + +QT_END_NAMESPACE + +#endif // QPAINTBUFFER_P_H diff --git a/src/gui/painting/qpaintdevice.cpp b/src/gui/painting/qpaintdevice.cpp new file mode 100644 index 0000000000..f2ba7f0744 --- /dev/null +++ b/src/gui/painting/qpaintdevice.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" + +QT_BEGIN_NAMESPACE + +extern void qt_painter_removePaintDevice(QPaintDevice *); //qpainter.cpp + +QPaintDevice::QPaintDevice() +{ + painters = 0; +} + +QPaintDevice::~QPaintDevice() +{ + if (paintingActive()) + qWarning("QPaintDevice: Cannot destroy paint device that is being " + "painted"); + qt_painter_removePaintDevice(this); +} + + +#ifndef Q_WS_QPA +int QPaintDevice::metric(PaintDeviceMetric) const +{ + qWarning("QPaintDevice::metrics: Device has no metric information"); + return 0; +} +#endif + +Q_GUI_EXPORT int qt_paint_device_metric(const QPaintDevice *device, QPaintDevice::PaintDeviceMetric metric) +{ + return device->metric(metric); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintdevice.h b/src/gui/painting/qpaintdevice.h new file mode 100644 index 0000000000..67db3f653f --- /dev/null +++ b/src/gui/painting/qpaintdevice.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTDEVICE_H +#define QPAINTDEVICE_H + +#include <QtGui/qwindowdefs.h> +#include <QtCore/qrect.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if defined(Q_WS_QWS) +class QWSDisplay; +#endif + +class QPaintEngine; + +class Q_GUI_EXPORT QPaintDevice // device for QPainter +{ +public: + enum PaintDeviceMetric { + PdmWidth = 1, + PdmHeight, + PdmWidthMM, + PdmHeightMM, + PdmNumColors, + PdmDepth, + PdmDpiX, + PdmDpiY, + PdmPhysicalDpiX, + PdmPhysicalDpiY + }; + + virtual ~QPaintDevice(); + + virtual int devType() const; + bool paintingActive() const; + virtual QPaintEngine *paintEngine() const = 0; + +#if defined(Q_WS_QWS) + static QWSDisplay *qwsDisplay(); +#endif + +#ifdef Q_WS_WIN + virtual HDC getDC() const; + virtual void releaseDC(HDC hdc) const; +#endif + + int width() const { return metric(PdmWidth); } + int height() const { return metric(PdmHeight); } + int widthMM() const { return metric(PdmWidthMM); } + int heightMM() const { return metric(PdmHeightMM); } + int logicalDpiX() const { return metric(PdmDpiX); } + int logicalDpiY() const { return metric(PdmDpiY); } + int physicalDpiX() const { return metric(PdmPhysicalDpiX); } + int physicalDpiY() const { return metric(PdmPhysicalDpiY); } +#ifdef QT_DEPRECATED + QT_DEPRECATED int numColors() const { return metric(PdmNumColors); } +#endif + int colorCount() const { return metric(PdmNumColors); } + int depth() const { return metric(PdmDepth); } + +protected: + QPaintDevice(); + virtual int metric(PaintDeviceMetric metric) const; + + ushort painters; // refcount + +private: + Q_DISABLE_COPY(QPaintDevice) + +#if defined(Q_WS_X11) && defined(QT3_SUPPORT) +public: + QT3_SUPPORT Display *x11Display() const; + QT3_SUPPORT int x11Screen() const; + QT3_SUPPORT int x11Depth() const; + QT3_SUPPORT int x11Cells() const; + QT3_SUPPORT Qt::HANDLE x11Colormap() const; + QT3_SUPPORT bool x11DefaultColormap() const; + QT3_SUPPORT void *x11Visual() const; + QT3_SUPPORT bool x11DefaultVisual() const; + + static QT3_SUPPORT Display *x11AppDisplay(); + static QT3_SUPPORT int x11AppScreen(); + static QT3_SUPPORT int x11AppDepth(int screen = -1); + static QT3_SUPPORT int x11AppCells(int screen = -1); + static QT3_SUPPORT Qt::HANDLE x11AppRootWindow(int screen = -1); + static QT3_SUPPORT Qt::HANDLE x11AppColormap(int screen = -1); + static QT3_SUPPORT void *x11AppVisual(int screen = -1); + static QT3_SUPPORT bool x11AppDefaultColormap(int screen =-1); + static QT3_SUPPORT bool x11AppDefaultVisual(int screen =-1); + static QT3_SUPPORT int x11AppDpiX(int screen = -1); + static QT3_SUPPORT int x11AppDpiY(int screen = -1); + static QT3_SUPPORT void x11SetAppDpiX(int, int); + static QT3_SUPPORT void x11SetAppDpiY(int, int); +#endif + + friend class QPainter; + friend class QFontEngineMac; + friend class QX11PaintEngine; + friend Q_GUI_EXPORT int qt_paint_device_metric(const QPaintDevice *device, PaintDeviceMetric metric); +}; + +#ifdef QT3_SUPPORT +QT3_SUPPORT Q_GUI_EXPORT +void bitBlt(QPaintDevice *dst, int dx, int dy, + const QPaintDevice *src, int sx=0, int sy=0, int sw=-1, int sh=-1, + bool ignoreMask=false); + +QT3_SUPPORT Q_GUI_EXPORT +void bitBlt(QPaintDevice *dst, int dx, int dy, + const QImage *src, int sx=0, int sy=0, int sw=-1, int sh=-1, + int conversion_flags=0); + +QT3_SUPPORT Q_GUI_EXPORT +void bitBlt(QPaintDevice *dst, const QPoint &dp, + const QPaintDevice *src, const QRect &sr=QRect(0,0,-1,-1), + bool ignoreMask=false); +#endif + +/***************************************************************************** + Inline functions + *****************************************************************************/ + +inline int QPaintDevice::devType() const +{ return QInternal::UnknownDevice; } + +inline bool QPaintDevice::paintingActive() const +{ return painters != 0; } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAINTDEVICE_H diff --git a/src/gui/painting/qpaintdevice.qdoc b/src/gui/painting/qpaintdevice.qdoc new file mode 100644 index 0000000000..dc5c3583e7 --- /dev/null +++ b/src/gui/painting/qpaintdevice.qdoc @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QPaintDevice + \brief The QPaintDevice class is the base class of objects that + can be painted. + + \ingroup painting + + A paint device is an abstraction of a two-dimensional space that + can be drawn using a QPainter. Its default coordinate system has + its origin located at the top-left position. X increases to the + right and Y increases downwards. The unit is one pixel. + + The drawing capabilities of QPaintDevice are currently implemented + by the QWidget, QImage, QPixmap, QGLPixelBuffer, QPicture, and + QPrinter subclasses. + + To implement support for a new backend, you must derive from + QPaintDevice and reimplement the virtual paintEngine() function to + tell QPainter which paint engine should be used to draw on this + particular device. Note that you also must create a corresponding + paint engine to be able to draw on the device, i.e derive from + QPaintEngine and reimplement its virtual functions. + + \warning Qt requires that a QApplication object exists before + any paint devices can be created. Paint devices access window + system resources, and these resources are not initialized before + an application object is created. + + The QPaintDevice class provides several functions returning the + various device metrics: The depth() function returns its bit depth + (number of bit planes). The height() function returns its height + in default coordinate system units (e.g. pixels for QPixmap and + QWidget) while heightMM() returns the height of the device in + millimeters. Similiarily, the width() and widthMM() functions + return the width of the device in default coordinate system units + and in millimeters, respectively. Alternatively, the protected + metric() function can be used to retrieve the metric information + by specifying the desired PaintDeviceMetric as argument. + + The logicalDpiX() and logicalDpiY() functions return the + horizontal and vertical resolution of the device in dots per + inch. The physicalDpiX() and physicalDpiY() functions also return + the resolution of the device in dots per inch, but note that if + the logical and physical resolution differ, the corresponding + QPaintEngine must handle the mapping. Finally, the colorCount() + function returns the number of different colors available for the + paint device. + + \sa QPaintEngine, QPainter, {Coordinate System}, {Paint System} +*/ + +/*! + \enum QPaintDevice::PaintDeviceMetric + + Describes the various metrics of a paint device. + + \value PdmWidth The width of the paint device in default + coordinate system units (e.g. pixels for QPixmap and QWidget). See + also width(). + + \value PdmHeight The height of the paint device in default + coordinate system units (e.g. pixels for QPixmap and QWidget). See + also height(). + + \value PdmWidthMM The width of the paint device in millimeters. See + also widthMM(). + + \value PdmHeightMM The height of the paint device in millimeters. See + also heightMM(). + + \value PdmNumColors The number of different colors available for + the paint device. See also colorCount(). + + \value PdmDepth The bit depth (number of bit planes) of the paint + device. See also depth(). + + \value PdmDpiX The horizontal resolution of the device in dots per + inch. See also logicalDpiX(). + + \value PdmDpiY The vertical resolution of the device in dots per inch. See + also logicalDpiY(). + + \value PdmPhysicalDpiX The horizontal resolution of the device in + dots per inch. See also physicalDpiX(). + + \value PdmPhysicalDpiY The vertical resolution of the device in + dots per inch. See also physicalDpiY(). + + \sa metric() +*/ + +/*! + \fn QPaintDevice::QPaintDevice() + + Constructs a paint device. This constructor can be invoked only from + subclasses of QPaintDevice. +*/ + +/*! + \fn QPaintDevice::~QPaintDevice() + + Destroys the paint device and frees window system resources. +*/ + +/*! + \fn int QPaintDevice::devType() const + + \internal + + Returns the device type identifier, which is QInternal::Widget + if the device is a QWidget, QInternal::Pixmap if it's a + QPixmap, QInternal::Printer if it's a QPrinter, + QInternal::Picture if it's a QPicture, or + QInternal::UnknownDevice in other cases. +*/ + +/*! + \fn bool QPaintDevice::paintingActive() const + + Returns true if the device is currently being painted on, i.e. someone has + called QPainter::begin() but not yet called QPainter::end() for + this device; otherwise returns false. + + \sa QPainter::isActive() +*/ + +/*! + \fn QPaintEngine *QPaintDevice::paintEngine() const + + Returns a pointer to the paint engine used for drawing on the + device. +*/ + +/*! + \fn int QPaintDevice::metric(PaintDeviceMetric metric) const + + Returns the metric information for the given paint device \a metric. + + \sa PaintDeviceMetric +*/ + +/*! + \fn int QPaintDevice::width() const + + Returns the width of the paint device in default coordinate system + units (e.g. pixels for QPixmap and QWidget). + + \sa widthMM() +*/ + +/*! + \fn int QPaintDevice::height() const + + Returns the height of the paint device in default coordinate + system units (e.g. pixels for QPixmap and QWidget). + + \sa heightMM() +*/ + +/*! + \fn int QPaintDevice::widthMM() const + + Returns the width of the paint device in millimeters. Due to platform + limitations it may not be possible to use this function to determine + the actual physical size of a widget on the screen. + + \sa width() +*/ + +/*! + \fn int QPaintDevice::heightMM() const + + Returns the height of the paint device in millimeters. Due to platform + limitations it may not be possible to use this function to determine + the actual physical size of a widget on the screen. + + \sa height() +*/ + +/*! + \fn int QPaintDevice::numColors() const + \deprecated + + Use colorCount() instead. + + Returns the number of different colors available for the paint + device. Since this value is an int, it will not be sufficient to + represent the number of colors on 32 bit displays, in this case + INT_MAX is returned instead. + */ + +/*! + \fn int QPaintDevice::colorCount() const + + Returns the number of different colors available for the paint + device. Since this value is an int, it will not be sufficient to + represent the number of colors on 32 bit displays, in this case + INT_MAX is returned instead. +*/ + +/*! + \fn int QPaintDevice::depth() const + + Returns the bit depth (number of bit planes) of the paint device. +*/ + +/*! + \fn int QPaintDevice::logicalDpiX() const + + Returns the horizontal resolution of the device in dots per inch, + which is used when computing font sizes. For X11, this is usually + the same as could be computed from widthMM(). + + Note that if the logicalDpiX() doesn't equal the physicalDpiX(), + the corresponding QPaintEngine must handle the resolution mapping. + + \sa logicalDpiY(), physicalDpiX() +*/ + +/*! + \fn int QPaintDevice::logicalDpiY() const + + Returns the vertical resolution of the device in dots per inch, + which is used when computing font sizes. For X11, this is usually + the same as could be computed from heightMM(). + + Note that if the logicalDpiY() doesn't equal the physicalDpiY(), + the corresponding QPaintEngine must handle the resolution mapping. + + \sa logicalDpiX(), physicalDpiY() +*/ + +/*! + \fn int QPaintDevice::physicalDpiX() const + + Returns the horizontal resolution of the device in dots per inch. + For example, when printing, this resolution refers to the physical + printer's resolution. The logical DPI on the other hand, refers to + the resolution used by the actual paint engine. + + Note that if the physicalDpiX() doesn't equal the logicalDpiX(), + the corresponding QPaintEngine must handle the resolution mapping. + + \sa physicalDpiY(), logicalDpiX() +*/ + +/*! + \fn int QPaintDevice::physicalDpiY() const + + Returns the horizontal resolution of the device in dots per inch. + For example, when printing, this resolution refers to the physical + printer's resolution. The logical DPI on the other hand, refers to + the resolution used by the actual paint engine. + + Note that if the physicalDpiY() doesn't equal the logicalDpiY(), + the corresponding QPaintEngine must handle the resolution mapping. + + \sa physicalDpiX(), logicalDpiY() +*/ diff --git a/src/gui/painting/qpaintdevice_mac.cpp b/src/gui/painting/qpaintdevice_mac.cpp new file mode 100644 index 0000000000..245408a0b0 --- /dev/null +++ b/src/gui/painting/qpaintdevice_mac.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qapplication.h" +#include "qprinter.h" +#include <qdebug.h> +#include <private/qt_mac_p.h> +#include <private/qprintengine_mac_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qpixmap_raster_p.h> + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ + +/*! \internal */ +float qt_mac_defaultDpi_x() +{ + // Mac OS X currently assumes things to be 72 dpi. + // (see http://developer.apple.com/releasenotes/GraphicsImaging/RN-ResolutionIndependentUI/) + // This may need to be re-worked as we go further in the resolution-independence stuff. + return 72; +} + +/*! \internal */ +float qt_mac_defaultDpi_y() +{ + // Mac OS X currently assumes things to be 72 dpi. + // (see http://developer.apple.com/releasenotes/GraphicsImaging/RN-ResolutionIndependentUI/) + // This may need to be re-worked as we go further in the resolution-independence stuff. + return 72; +} + + +/*! \internal + + Returns the QuickDraw CGrafPtr of the paint device. 0 is returned + if it can't be obtained. Do not hold the pointer around for long + as it can be relocated. + + \warning This function is only available on Mac OS X. +*/ + +Q_GUI_EXPORT GrafPtr qt_mac_qd_context(const QPaintDevice *device) +{ + if (device->devType() == QInternal::Pixmap) { + return static_cast<GrafPtr>(static_cast<const QPixmap *>(device)->macQDHandle()); + } else if(device->devType() == QInternal::Widget) { + return static_cast<GrafPtr>(static_cast<const QWidget *>(device)->macQDHandle()); + } else if(device->devType() == QInternal::Printer) { + QPaintEngine *engine = static_cast<const QPrinter *>(device)->paintEngine(); + return static_cast<GrafPtr>(static_cast<const QMacPrintEngine *>(engine)->handle()); + } + return 0; +} + +extern CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *pdev); + +/*! \internal + + Returns the CoreGraphics CGContextRef of the paint device. 0 is + returned if it can't be obtained. It is the caller's responsiblity to + CGContextRelease the context when finished using it. + + \warning This function is only available on Mac OS X. +*/ + +Q_GUI_EXPORT CGContextRef qt_mac_cg_context(const QPaintDevice *pdev) +{ + if (pdev->devType() == QInternal::Pixmap) { + const QPixmap *pm = static_cast<const QPixmap*>(pdev); + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + uint flags = kCGImageAlphaPremultipliedFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + flags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ret = 0; + + // It would make sense to put this into a mac #ifdef'ed + // virtual function in the QPixmapData at some point + if (pm->data->classId() == QPixmapData::MacClass) { + const QMacPixmapData *pmData = static_cast<const QMacPixmapData*>(pm->data.data()); + ret = CGBitmapContextCreate(pmData->pixels, pmData->w, pmData->h, + 8, pmData->bytesPerRow, colorspace, + flags); + if(!ret) + qWarning("QPaintDevice: Unable to create context for pixmap (%d/%d/%d)", + pmData->w, pmData->h, (pmData->bytesPerRow * pmData->h)); + } else if (pm->data->classId() == QPixmapData::RasterClass) { + QImage *image = pm->data->buffer(); + ret = CGBitmapContextCreate(image->bits(), image->width(), image->height(), + 8, image->bytesPerLine(), colorspace, flags); + } + + CGContextTranslateCTM(ret, 0, pm->height()); + CGContextScaleCTM(ret, 1, -1); + return ret; + } else if (pdev->devType() == QInternal::Widget) { + CGContextRef ret = static_cast<CGContextRef>(static_cast<const QWidget *>(pdev)->macCGHandle()); + CGContextRetain(ret); + return ret; + } else if (pdev->devType() == QInternal::MacQuartz) { + return static_cast<const QMacQuartzPaintDevice *>(pdev)->cgContext(); + } + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintdevice_qpa.cpp b/src/gui/painting/qpaintdevice_qpa.cpp new file mode 100644 index 0000000000..0d1ca92d45 --- /dev/null +++ b/src/gui/painting/qpaintdevice_qpa.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qapplication.h" + +QT_BEGIN_NAMESPACE + +extern void qt_painter_removePaintDevice(QPaintDevice *); //qpainter.cpp + +int QPaintDevice::metric(PaintDeviceMetric m) const +{ + qWarning("QPaintDevice::metrics: Device has no metric information"); + if (m == PdmDpiX) { + return 72; + } else if (m == PdmDpiY) { + return 72; + } else if (m == PdmNumColors) { + // FIXME: does this need to be a real value? + return 256; + } else { + qDebug("Unrecognised metric %d!",m); + return 0; + } +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintdevice_qws.cpp b/src/gui/painting/qpaintdevice_qws.cpp new file mode 100644 index 0000000000..6f9433ab62 --- /dev/null +++ b/src/gui/painting/qpaintdevice_qws.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qapplication.h" +#include "qwsdisplay_qws.h" + +QT_BEGIN_NAMESPACE + +QWSDisplay *QPaintDevice::qwsDisplay() +{ + return qt_fbdpy; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintdevice_win.cpp b/src/gui/painting/qpaintdevice_win.cpp new file mode 100644 index 0000000000..3dbe97492a --- /dev/null +++ b/src/gui/painting/qpaintdevice_win.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qapplication.h" +#include <private/qapplication_p.h> +#include "qt_windows.h" +#include "qprinter.h" + +QT_BEGIN_NAMESPACE + +HDC QPaintDevice::getDC() const +{ + return 0; +} + +void QPaintDevice::releaseDC(HDC) const +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintdevice_x11.cpp b/src/gui/painting/qpaintdevice_x11.cpp new file mode 100644 index 0000000000..b6be0761c5 --- /dev/null +++ b/src/gui/painting/qpaintdevice_x11.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qapplication.h" +#include <private/qt_x11_p.h> +#include "qx11info_x11.h" + +QT_BEGIN_NAMESPACE + +/*! \internal + + Returns the X11 Drawable of the paint device. 0 is returned if it + can't be obtained. +*/ + +Drawable Q_GUI_EXPORT qt_x11Handle(const QPaintDevice *pd) +{ + if (!pd) return 0; + if (pd->devType() == QInternal::Widget) + return static_cast<const QWidget *>(pd)->handle(); + else if (pd->devType() == QInternal::Pixmap) + return static_cast<const QPixmap *>(pd)->handle(); + return 0; +} + +/*! + \relates QPaintDevice + + Returns the QX11Info structure for the \a pd paint device. 0 is + returned if it can't be obtained. +*/ +const Q_GUI_EXPORT QX11Info *qt_x11Info(const QPaintDevice *pd) +{ + if (!pd) return 0; + if (pd->devType() == QInternal::Widget) + return &static_cast<const QWidget *>(pd)->x11Info(); + else if (pd->devType() == QInternal::Pixmap) + return &static_cast<const QPixmap *>(pd)->x11Info(); + return 0; +} + + + +#ifdef QT3_SUPPORT + +Display *QPaintDevice::x11Display() const +{ + return X11->display; +} + +int QPaintDevice::x11Screen() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->screen(); + return QX11Info::appScreen(); +} + +void *QPaintDevice::x11Visual() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->visual(); + return QX11Info::appVisual(); +} + +int QPaintDevice::x11Depth() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->depth(); + return QX11Info::appDepth(); +} + +int QPaintDevice::x11Cells() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->cells(); + return QX11Info::appCells(); +} + +Qt::HANDLE QPaintDevice::x11Colormap() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->colormap(); + return QX11Info::appColormap(); +} + +bool QPaintDevice::x11DefaultColormap() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->defaultColormap(); + return QX11Info::appDefaultColormap(); +} + +bool QPaintDevice::x11DefaultVisual() const +{ + const QX11Info *info = qt_x11Info(this); + if (info) + return info->defaultVisual(); + return QX11Info::appDefaultVisual(); +} + +void *QPaintDevice::x11AppVisual(int screen) +{ return QX11Info::appVisual(screen); } + +Qt::HANDLE QPaintDevice::x11AppColormap(int screen) +{ return QX11Info::appColormap(screen); } + +Display *QPaintDevice::x11AppDisplay() +{ return QX11Info::display(); } + +int QPaintDevice::x11AppScreen() +{ return QX11Info::appScreen(); } + +int QPaintDevice::x11AppDepth(int screen) +{ return QX11Info::appDepth(screen); } + +int QPaintDevice::x11AppCells(int screen) +{ return QX11Info::appCells(screen); } + +Qt::HANDLE QPaintDevice::x11AppRootWindow(int screen) +{ return QX11Info::appRootWindow(screen); } + +bool QPaintDevice::x11AppDefaultColormap(int screen) +{ return QX11Info::appDefaultColormap(screen); } + +bool QPaintDevice::x11AppDefaultVisual(int screen) +{ return QX11Info::appDefaultVisual(screen); } + +void QPaintDevice::x11SetAppDpiX(int dpi, int screen) +{ + QX11Info::setAppDpiX(dpi, screen); +} + +void QPaintDevice::x11SetAppDpiY(int dpi, int screen) +{ + QX11Info::setAppDpiY(dpi, screen); +} + +int QPaintDevice::x11AppDpiX(int screen) +{ + return QX11Info::appDpiX(screen); +} + +int QPaintDevice::x11AppDpiY(int screen) +{ + return QX11Info::appDpiY(screen); +} +#endif + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp new file mode 100644 index 0000000000..6eb09e5966 --- /dev/null +++ b/src/gui/painting/qpaintengine.cpp @@ -0,0 +1,1030 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qpaintengine.h" +#include "qpaintengine_p.h" +#include "qpainter_p.h" +#include "qpolygon.h" +#include "qbitmap.h" +#include "qapplication.h" +#include <qdebug.h> +#include <qmath.h> +#include <private/qtextengine_p.h> +#include <qvarlengtharray.h> +#include <private/qfontengine_p.h> +#include <private/qpaintengineex_p.h> + + +QT_BEGIN_NAMESPACE + +/*! + \class QTextItem + + \brief The QTextItem class provides all the information required to draw + text in a custom paint engine. + + When you reimplement your own paint engine, you must reimplement + QPaintEngine::drawTextItem(), a function that takes a QTextItem as + one of its arguments. +*/ + +/*! + \enum QTextItem::RenderFlag + + \value RightToLeft Render the text from right to left. + \value Overline Paint a line above the text. + \value Underline Paint a line under the text. + \value StrikeOut Paint a line through the text. + \omitvalue Dummy +*/ + + +/*! + \fn qreal QTextItem::descent() const + + Corresponds to the \l{QFontMetrics::descent()}{descent} of the piece of text that is drawn. +*/ +qreal QTextItem::descent() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return ti->descent.toReal(); +} + +/*! + \fn qreal QTextItem::ascent() const + + Corresponds to the \l{QFontMetrics::ascent()}{ascent} of the piece of text that is drawn. +*/ +qreal QTextItem::ascent() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return ti->ascent.toReal(); +} + +/*! + \fn qreal QTextItem::width() const + + Specifies the total width of the text to be drawn. +*/ +qreal QTextItem::width() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return ti->width.toReal(); +} + +/*! + \fn QTextItem::RenderFlags QTextItem::renderFlags() const + + Returns the render flags used. +*/ +QTextItem::RenderFlags QTextItem::renderFlags() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return ti->flags; +} + +/*! + \fn QString QTextItem::text() const + + Returns the text that should be drawn. +*/ +QString QTextItem::text() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return QString(ti->chars, ti->num_chars); +} + +/*! + \fn QFont QTextItem::font() const + + Returns the font that should be used to draw the text. +*/ +QFont QTextItem::font() const +{ + const QTextItemInt *ti = static_cast<const QTextItemInt *>(this); + return ti->f ? *ti->f : QApplication::font(); +} + + +/*! + \class QPaintEngine + \ingroup painting + + \brief The QPaintEngine class provides an abstract definition of how + QPainter draws to a given device on a given platform. + + Qt 4.0 provides several premade implementations of QPaintEngine for the + different painter backends we support. We provide one paint engine for each + window system and painting framework we support. This includes X11 on + Unix/Linux and CoreGraphics on Mac OS X. In addition we provide QPaintEngine + implementations for OpenGL (accessible through QGLWidget) and PostScript + (accessible through QPSPrinter on X11). Additionally there is a raster-based + paint engine that is a fallback for when an engine does not support a certain + capability. + + If one wants to use QPainter to draw to a different backend, + one must subclass QPaintEngine and reimplement all its virtual + functions. The QPaintEngine implementation is then made available by + subclassing QPaintDevice and reimplementing the virtual function + QPaintDevice::paintEngine(). + + QPaintEngine is created and owned by the QPaintDevice that created it. + + The big advantage of the QPaintEngine approach opposed to + Qt 3's QPainter/QPaintDevice::cmd() approach is that it is now + possible to adapt to multiple technologies on each platform and take + advantage of each to the fullest. + + \sa QPainter, QPaintDevice::paintEngine(), {Paint System} +*/ + +/*! + \enum QPaintEngine::PaintEngineFeature + + This enum is used to describe the features or capabilities that the + paint engine has. If a feature is not supported by the engine, + QPainter will do a best effort to emulate that feature through other + means and pass on an alpha blended QImage to the engine with the + emulated results. Some features cannot be emulated: AlphaBlend and PorterDuff. + + \value AlphaBlend The engine can alpha blend primitives. + \value Antialiasing The engine can use antialising to improve the appearance + of rendered primitives. + \value BlendModes The engine supports blending modes. + \value BrushStroke The engine supports drawing strokes that + contain brushes as fills, not just solid + colors (e.g. a dashed gradient line of + width 2). + \value ConicalGradientFill The engine supports conical gradient fills. + \value ConstantOpacity The engine supports the feature provided by + QPainter::setOpacity(). + \value LinearGradientFill The engine supports linear gradient fills. + \value MaskedBrush The engine is capable of rendering brushes that has a + texture with an alpha channel or a mask. + \value ObjectBoundingModeGradients The engine has native support for gradients + with coordinate mode QGradient::ObjectBoundingMode. + Otherwise, if QPaintEngine::PatternTransform is + supported, object bounding mode gradients are + converted to gradients with coordinate mode + QGradient::LogicalMode and a brush transform for + the coordinate mapping. + \value PainterPaths The engine has path support. + \value PaintOutsidePaintEvent The engine is capable of painting outside of + paint events. + \value PatternBrush The engine is capable of rendering brushes with + the brush patterns specified in Qt::BrushStyle. + \value PatternTransform The engine has support for transforming brush + patterns. + \value PerspectiveTransform The engine has support for performing perspective + transformations on primitives. + \value PixmapTransform The engine can transform pixmaps, including + rotation and shearing. + \value PorterDuff The engine supports Porter-Duff operations + \value PrimitiveTransform The engine has support for transforming + drawing primitives. + \value RadialGradientFill The engine supports radial gradient fills. + \value RasterOpModes The engine supports bitwise raster operations. + \value AllFeatures All of the above features. This enum value is usually + used as a bit mask. +*/ + +/*! + \enum QPaintEngine::PolygonDrawMode + + \value OddEvenMode The polygon should be drawn using OddEven fill + rule. + + \value WindingMode The polygon should be drawn using Winding fill rule. + + \value ConvexMode The polygon is a convex polygon and can be drawn + using specialized algorithms where available. + + \value PolylineMode Only the outline of the polygon should be + drawn. + +*/ + +/*! + \enum QPaintEngine::DirtyFlag + + \value DirtyPen The pen is dirty and needs to be updated. + + \value DirtyBrush The brush is dirty and needs to be updated. + + \value DirtyBrushOrigin The brush origin is dirty and needs to + updated. + + \value DirtyFont The font is dirty and needs to be updated. + + \value DirtyBackground The background is dirty and needs to be + updated. + + \value DirtyBackgroundMode The background mode is dirty and needs + to be updated. + + \value DirtyTransform The transform is dirty and needs to be + updated. + + \value DirtyClipRegion The clip region is dirty and needs to be + updated. + + \value DirtyClipPath The clip path is dirty and needs to be + updated. + + \value DirtyHints The render hints is dirty and needs to be + updated. + + \value DirtyCompositionMode The composition mode is dirty and + needs to be updated. + + \value DirtyClipEnabled Whether clipping is enabled or not is + dirty and needs to be updated. + + \value DirtyOpacity The constant opacity has changed and needs to + be updated as part of the state change in + QPaintEngine::updateState(). + + \value AllDirty Convenience enum used internally. + + These types are used by QPainter to trigger lazy updates of the + various states in the QPaintEngine using + QPaintEngine::updateState(). + + A paint engine must update every dirty state. +*/ + +/*! + \fn void QPaintEngine::syncState() + + \internal + + Updates all dirty states in this engine. This function should ONLY + be used when drawing with native handles directly and immediate sync + from QPainters state to the native state is required. +*/ +void QPaintEngine::syncState() +{ + Q_ASSERT(state); + updateState(*state); + + if (isExtended()) + static_cast<QPaintEngineEx *>(this)->sync(); +} + +static QPaintEngine *qt_polygon_recursion = 0; +struct QT_Point { + int x; + int y; +}; + +/*! + \fn void QPaintEngine::drawPolygon(const QPointF *points, int pointCount, + PolygonDrawMode mode) + + Reimplement this virtual function to draw the polygon defined + by the \a pointCount first points in \a points, using mode \a + mode. + + \note At least one of the drawPolygon() functions must be reimplemented. +*/ +void QPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_ASSERT_X(qt_polygon_recursion != this, "QPaintEngine::drawPolygon", + "At least one drawPolygon function must be implemented"); + qt_polygon_recursion = this; + Q_ASSERT(sizeof(QT_Point) == sizeof(QPoint)); + QVarLengthArray<QT_Point> p(pointCount); + for (int i = 0; i < pointCount; ++i) { + p[i].x = qRound(points[i].x()); + p[i].y = qRound(points[i].y()); + } + drawPolygon((QPoint *)p.data(), pointCount, mode); + qt_polygon_recursion = 0; +} + +struct QT_PointF { + qreal x; + qreal y; +}; +/*! + \overload + + Reimplement this virtual function to draw the polygon defined by the + \a pointCount first points in \a points, using mode \a mode. + + \note At least one of the drawPolygon() functions must be reimplemented. +*/ +void QPaintEngine::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + Q_ASSERT_X(qt_polygon_recursion != this, "QPaintEngine::drawPolygon", + "At least one drawPolygon function must be implemented"); + qt_polygon_recursion = this; + Q_ASSERT(sizeof(QT_PointF) == sizeof(QPointF)); + QVarLengthArray<QT_PointF> p(pointCount); + for (int i=0; i<pointCount; ++i) { + p[i].x = points[i].x(); + p[i].y = points[i].y(); + } + drawPolygon((QPointF *)p.data(), pointCount, mode); + qt_polygon_recursion = 0; +} + +/*! + \enum QPaintEngine::Type + + \value X11 + \value Windows + \value MacPrinter + \value CoreGraphics Mac OS X's Quartz2D (CoreGraphics) + \value QuickDraw Mac OS X's QuickDraw + \value QWindowSystem Qt for Embedded Linux + \value PostScript + \value OpenGL + \value Picture QPicture format + \value SVG Scalable Vector Graphics XML format + \value Raster + \value Direct3D Windows only, Direct3D based engine + \value Pdf Portable Document Format + \value OpenVG + \value User First user type ID + \value MaxUser Last user type ID + \value OpenGL2 + \value PaintBuffer +*/ + +/*! + \fn bool QPaintEngine::isActive() const + + Returns true if the paint engine is actively drawing; otherwise + returns false. + + \sa setActive() +*/ + +/*! + \fn void QPaintEngine::setActive(bool state) + + Sets the active state of the paint engine to \a state. + + \sa isActive() +*/ + +/*! + \fn bool QPaintEngine::begin(QPaintDevice *pdev) + + Reimplement this function to initialise your paint engine when + painting is to start on the paint device \a pdev. Return true if + the initialization was successful; otherwise return false. + + \sa end() isActive() +*/ + +/*! + \fn bool QPaintEngine::end() + + Reimplement this function to finish painting on the current paint + device. Return true if painting was finished successfully; + otherwise return false. + + \sa begin() isActive() +*/ + + +/*! + Draws the first \a pointCount points in the buffer \a points +*/ +void QPaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + QPainter *p = painter(); + if (!p) + return; + + qreal penWidth = p->pen().widthF(); + if (penWidth == 0) + penWidth = 1; + + bool ellipses = p->pen().capStyle() == Qt::RoundCap; + + p->save(); + + QTransform transform; + if (p->pen().isCosmetic()) { + transform = p->transform(); + p->setTransform(QTransform()); + } + + p->setBrush(p->pen().brush()); + p->setPen(Qt::NoPen); + + for (int i=0; i<pointCount; ++i) { + QPointF pos = transform.map(points[i]); + QRectF rect(pos.x() - penWidth / 2, pos.y() - penWidth / 2, penWidth, penWidth); + + if (ellipses) + p->drawEllipse(rect); + else + p->drawRect(rect); + } + + p->restore(); +} + + +/*! + Draws the first \a pointCount points in the buffer \a points + + The default implementation converts the first \a pointCount QPoints in \a points + to QPointFs and calls the floating point version of drawPoints. + +*/ +void QPaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_ASSERT(sizeof(QT_PointF) == sizeof(QPointF)); + QT_PointF fp[256]; + while (pointCount) { + int i = 0; + while (i < pointCount && i < 256) { + fp[i].x = points[i].x(); + fp[i].y = points[i].y(); + ++i; + } + drawPoints((QPointF *)(void *)fp, i); + points += i; + pointCount -= i; + } +} + +/*! + \fn void QPaintEngine::drawEllipse(const QRectF &rect) + + Reimplement this function to draw the largest ellipse that can be + contained within rectangle \a rect. + + The default implementation calls drawPolygon(). +*/ +void QPaintEngine::drawEllipse(const QRectF &rect) +{ + QPainterPath path; + path.addEllipse(rect); + if (hasFeature(PainterPaths)) { + drawPath(path); + } else { + QPolygonF polygon = path.toFillPolygon(); + drawPolygon(polygon.data(), polygon.size(), ConvexMode); + } +} + +/*! + The default implementation of this function calls the floating + point version of this function +*/ +void QPaintEngine::drawEllipse(const QRect &rect) +{ + drawEllipse(QRectF(rect)); +} + +/*! + \fn void QPaintEngine::drawPixmap(const QRectF &r, const QPixmap + &pm, const QRectF &sr) + + Reimplement this function to draw the part of the \a pm + specified by the \a sr rectangle in the given \a r. +*/ + + +void qt_fill_tile(QPixmap *tile, const QPixmap &pixmap) +{ + QPainter p(tile); + p.drawPixmap(0, 0, pixmap); + int x = pixmap.width(); + while (x < tile->width()) { + p.drawPixmap(x, 0, *tile, 0, 0, x, pixmap.height()); + x *= 2; + } + int y = pixmap.height(); + while (y < tile->height()) { + p.drawPixmap(0, y, *tile, 0, 0, tile->width(), y); + y *= 2; + } +} + +void qt_draw_tile(QPaintEngine *gc, qreal x, qreal y, qreal w, qreal h, + const QPixmap &pixmap, qreal xOffset, qreal yOffset) +{ + qreal yPos, xPos, drawH, drawW, yOff, xOff; + yPos = y; + yOff = yOffset; + while(yPos < y + h) { + drawH = pixmap.height() - yOff; // Cropping first row + if (yPos + drawH > y + h) // Cropping last row + drawH = y + h - yPos; + xPos = x; + xOff = xOffset; + while(xPos < x + w) { + drawW = pixmap.width() - xOff; // Cropping first column + if (xPos + drawW > x + w) // Cropping last column + drawW = x + w - xPos; + if (drawW > 0 && drawH > 0) + gc->drawPixmap(QRectF(xPos, yPos, drawW, drawH), pixmap, QRectF(xOff, yOff, drawW, drawH)); + xPos += drawW; + xOff = 0; + } + yPos += drawH; + yOff = 0; + } +} + + +/*! + Reimplement this function to draw the \a pixmap in the given \a + rect, starting at the given \a p. The pixmap will be + drawn repeatedly until the \a rect is filled. +*/ +void QPaintEngine::drawTiledPixmap(const QRectF &rect, const QPixmap &pixmap, const QPointF &p) +{ + int sw = pixmap.width(); + int sh = pixmap.height(); + + if (sw*sh < 8192 && sw*sh < 16*rect.width()*rect.height()) { + int tw = sw, th = sh; + while (tw*th < 32678 && tw < rect.width()/2) + tw *= 2; + while (tw*th < 32678 && th < rect.height()/2) + th *= 2; + QPixmap tile; + if (pixmap.depth() == 1) { + tile = QBitmap(tw, th); + } else { + tile = QPixmap(tw, th); + if (pixmap.hasAlphaChannel()) + tile.fill(Qt::transparent); + } + qt_fill_tile(&tile, pixmap); + qt_draw_tile(this, rect.x(), rect.y(), rect.width(), rect.height(), tile, p.x(), p.y()); + } else { + qt_draw_tile(this, rect.x(), rect.y(), rect.width(), rect.height(), pixmap, p.x(), p.y()); + } +} + +/*! + \fn void QPaintEngine::drawImage(const QRectF &rectangle, const QImage + &image, const QRectF &sr, Qt::ImageConversionFlags flags) + + Reimplement this function to draw the part of the \a image + specified by the \a sr rectangle in the given \a rectangle using + the given conversion flags \a flags, to convert it to a pixmap. +*/ + +void QPaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + QRectF baseSize(0, 0, image.width(), image.height()); + QImage im = image; + if (baseSize != sr) + im = im.copy(qFloor(sr.x()), qFloor(sr.y()), + qCeil(sr.width()), qCeil(sr.height())); + QPixmap pm = QPixmap::fromImage(im, flags); + drawPixmap(r, pm, QRectF(QPointF(0, 0), pm.size())); +} + +/*! + \fn Type QPaintEngine::type() const + + Reimplement this function to return the paint engine \l{Type}. +*/ + +/*! + \fn void QPaintEngine::fix_neg_rect(int *x, int *y, int *w, int *h); + + \internal +*/ + +/*! + \fn bool QPaintEngine::testDirty(DirtyFlags df) + + \internal +*/ + +/*! + \fn void QPaintEngine::clearDirty(DirtyFlags df) + + \internal +*/ + +/*! + \fn void QPaintEngine::setDirty(DirtyFlags df) + + \internal +*/ + +/*! + \fn bool QPaintEngine::hasFeature(PaintEngineFeatures feature) const + + Returns true if the paint engine supports the specified \a + feature; otherwise returns false. +*/ + +/*! + \fn bool QPaintEngine::isExtended() const + + \internal + + Returns true if the paint engine is a QPaintEngineEx derivative. +*/ + +/*! + \fn void QPaintEngine::updateState(const QPaintEngineState &state) + + Reimplement this function to update the state of a paint engine. + + When implemented, this function is responsible for checking the + paint engine's current \a state and update the properties that are + changed. Use the QPaintEngineState::state() function to find out + which properties that must be updated, then use the corresponding + \l {GetFunction}{get function} to retrieve the current values for + the given properties. + + \sa QPaintEngineState +*/ + +/*! + Creates a paint engine with the featureset specified by \a caps. +*/ + +QPaintEngine::QPaintEngine(PaintEngineFeatures caps) + : state(0), + gccaps(caps), + active(0), + selfDestruct(false), + extended(false), + d_ptr(new QPaintEnginePrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + \internal +*/ + +QPaintEngine::QPaintEngine(QPaintEnginePrivate &dptr, PaintEngineFeatures caps) + : state(0), + gccaps(caps), + active(0), + selfDestruct(false), + extended(false), + d_ptr(&dptr) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys the paint engine. +*/ +QPaintEngine::~QPaintEngine() +{ +} + +/*! + Returns the paint engine's painter. +*/ +QPainter *QPaintEngine::painter() const +{ + return state ? state->painter() : 0; +} + +/*! + The default implementation ignores the \a path and does nothing. +*/ + +void QPaintEngine::drawPath(const QPainterPath &) +{ + if (hasFeature(PainterPaths)) { + qWarning("QPaintEngine::drawPath: Must be implemented when feature PainterPaths is set"); + } +} + +/*! + This function draws the text item \a textItem at position \a p. The + default implementation of this function converts the text to a + QPainterPath and paints the resulting path. +*/ + +void QPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + + QPainterPath path; +#ifndef Q_WS_MAC + path.setFillRule(Qt::WindingFill); +#endif + if (ti.glyphs.numGlyphs) + ti.fontEngine->addOutlineToPath(p.x(), p.y(), ti.glyphs, &path, ti.flags); + if (!path.isEmpty()) { + bool oldAA = painter()->renderHints() & QPainter::Antialiasing; + painter()->setRenderHint(QPainter::Antialiasing, + bool((painter()->renderHints() & QPainter::TextAntialiasing) + && !(painter()->font().styleStrategy() & QFont::NoAntialias))); + painter()->fillPath(path, state->pen().brush()); + painter()->setRenderHint(QPainter::Antialiasing, oldAA); + } +} + +/*! + The default implementation splits the list of lines in \a lines + into \a lineCount separate calls to drawPath() or drawPolygon() + depending on the feature set of the paint engine. +*/ +void QPaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + for (int i=0; i<lineCount; ++i) { + QPointF pts[2] = { lines[i].p1(), lines[i].p2() }; + + if (pts[0] == pts[1]) { + if (state->pen().capStyle() != Qt::FlatCap) + drawPoints(pts, 1); + continue; + } + + drawPolygon(pts, 2, PolylineMode); + } +} + +/*! + \overload + + The default implementation converts the first \a lineCount lines + in \a lines to a QLineF and calls the floating point version of + this function. +*/ +void QPaintEngine::drawLines(const QLine *lines, int lineCount) +{ + struct PointF { + qreal x; + qreal y; + }; + struct LineF { + PointF p1; + PointF p2; + }; + Q_ASSERT(sizeof(PointF) == sizeof(QPointF)); + Q_ASSERT(sizeof(LineF) == sizeof(QLineF)); + LineF fl[256]; + while (lineCount) { + int i = 0; + while (i < lineCount && i < 256) { + fl[i].p1.x = lines[i].x1(); + fl[i].p1.y = lines[i].y1(); + fl[i].p2.x = lines[i].x2(); + fl[i].p2.y = lines[i].y2(); + ++i; + } + drawLines((QLineF *)(void *)fl, i); + lines += i; + lineCount -= i; + } +} + + +/*! + \overload + + The default implementation converts the first \a rectCount + rectangles in the buffer \a rects to a QRectF and calls the + floating point version of this function. +*/ +void QPaintEngine::drawRects(const QRect *rects, int rectCount) +{ + struct RectF { + qreal x; + qreal y; + qreal w; + qreal h; + }; + Q_ASSERT(sizeof(RectF) == sizeof(QRectF)); + RectF fr[256]; + while (rectCount) { + int i = 0; + while (i < rectCount && i < 256) { + fr[i].x = rects[i].x(); + fr[i].y = rects[i].y(); + fr[i].w = rects[i].width(); + fr[i].h = rects[i].height(); + ++i; + } + drawRects((QRectF *)(void *)fr, i); + rects += i; + rectCount -= i; + } +} + +/*! + Draws the first \a rectCount rectangles in the buffer \a + rects. The default implementation of this function calls drawPath() + or drawPolygon() depending on the feature set of the paint engine. +*/ +void QPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + if (hasFeature(PainterPaths) && + !state->penNeedsResolving() && + !state->brushNeedsResolving()) { + for (int i=0; i<rectCount; ++i) { + QPainterPath path; + path.addRect(rects[i]); + if (path.isEmpty()) + continue; + drawPath(path); + } + } else { + for (int i=0; i<rectCount; ++i) { + QRectF rf = rects[i]; + QPointF pts[4] = { QPointF(rf.x(), rf.y()), + QPointF(rf.x() + rf.width(), rf.y()), + QPointF(rf.x() + rf.width(), rf.y() + rf.height()), + QPointF(rf.x(), rf.y() + rf.height()) }; + drawPolygon(pts, 4, ConvexMode); + } + } +} + +/*! + \internal + Sets the paintdevice that this engine operates on to \a device +*/ +void QPaintEngine::setPaintDevice(QPaintDevice *device) +{ + d_func()->pdev = device; +} + +/*! + Returns the device that this engine is painting on, if painting is + active; otherwise returns 0. +*/ +QPaintDevice *QPaintEngine::paintDevice() const +{ + return d_func()->pdev; +} + +#ifdef Q_WS_WIN +/*! + \internal + + Empty default implementation. +*/ + +HDC QPaintEngine::getDC() const +{ + return 0; +} + + +/*! + \internal + + Empty default implementation. +*/ + +void QPaintEngine::releaseDC(HDC) const +{ +} + +#endif + +/*! + \internal + + Returns the offset from the painters origo to the engines + origo. This value is used by QPainter for engines who have + internal double buffering. + + This function only makes sense when the engine is active. +*/ +QPoint QPaintEngine::coordinateOffset() const +{ + return QPoint(); +} + +/*! + \internal + + Sets the system clip for this engine. The system clip defines the + basis area that the engine has to draw in. All clips that are + set will be be an intersection with the system clip. + + Reset the systemclip to no clip by setting an empty region. +*/ +void QPaintEngine::setSystemClip(const QRegion ®ion) +{ + Q_D(QPaintEngine); + d->systemClip = region; + // Be backward compatible and only call d->systemStateChanged() + // if we currently have a system transform/viewport set. + if (d->hasSystemTransform || d->hasSystemViewport) { + d->transformSystemClip(); + d->systemStateChanged(); + } +} + +/*! + \internal + + Returns the system clip. The system clip is read only while the + painter is active. An empty region indicates that system clip + is not in use. +*/ + +QRegion QPaintEngine::systemClip() const +{ + return d_func()->systemClip; +} + +/*! + \internal + + Sets the target rect for drawing within the backing store. This + function should ONLY be used by the backing store. +*/ +void QPaintEngine::setSystemRect(const QRect &rect) +{ + if (isActive()) { + qWarning("QPaintEngine::setSystemRect: Should not be changed while engine is active"); + return; + } + d_func()->systemRect = rect; +} + +/*! + \internal + + Retrieves the rect for drawing within the backing store. This + function should ONLY be used by the backing store. + */ +QRect QPaintEngine::systemRect() const +{ + return d_func()->systemRect; +} + +void QPaintEnginePrivate::drawBoxTextItem(const QPointF &p, const QTextItemInt &ti) +{ + if (!ti.glyphs.numGlyphs) + return; + + // any fixes here should probably also be done in QFontEngineBox::draw + const int size = qRound(ti.fontEngine->ascent()); + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = QTransform::fromTranslate(p.x(), p.y() - size); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + QSize s(size - 3, size - 3); + + QPainter *painter = q_func()->state->painter(); + painter->save(); + painter->setBrush(Qt::NoBrush); + QPen pen = painter->pen(); + pen.setWidthF(ti.fontEngine->lineThickness().toReal()); + painter->setPen(pen); + for (int k = 0; k < positions.size(); k++) + painter->drawRect(QRectF(positions[k].toPointF(), s)); + painter->restore(); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine.h b/src/gui/painting/qpaintengine.h new file mode 100644 index 0000000000..93aa63bea9 --- /dev/null +++ b/src/gui/painting/qpaintengine.h @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_H +#define QPAINTENGINE_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qobjectdefs.h> +#include <QtCore/qscopedpointer.h> +#include <QtGui/qpainter.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontEngine; +class QLineF; +class QPaintDevice; +class QPaintEnginePrivate; +class QPainterPath; +class QPointF; +class QPolygonF; +class QRectF; +struct QGlyphLayout; +class QTextItemInt; +class QPaintEngineState; + +class Q_GUI_EXPORT QTextItem { +public: + enum RenderFlag { + RightToLeft = 0x1, + Overline = 0x10, + Underline = 0x20, + StrikeOut = 0x40, + + Dummy = 0xffffffff + }; + Q_DECLARE_FLAGS(RenderFlags, RenderFlag) + qreal descent() const; + qreal ascent() const; + qreal width() const; + + RenderFlags renderFlags() const; + QString text() const; + QFont font() const; +}; +Q_DECLARE_TYPEINFO(QTextItem, Q_PRIMITIVE_TYPE); + + +class Q_GUI_EXPORT QPaintEngine +{ + Q_DECLARE_PRIVATE(QPaintEngine) +public: + enum PaintEngineFeature { + PrimitiveTransform = 0x00000001, // Can transform primitives brushes + PatternTransform = 0x00000002, // Can transform pattern brushes + PixmapTransform = 0x00000004, // Can transform pixmaps + PatternBrush = 0x00000008, // Can fill with pixmaps and standard patterns + LinearGradientFill = 0x00000010, // Can fill gradient areas + RadialGradientFill = 0x00000020, // Can render radial gradients + ConicalGradientFill = 0x00000040, // Can render conical gradients + AlphaBlend = 0x00000080, // Can do source over alpha blend + PorterDuff = 0x00000100, // Can do general porter duff compositions + PainterPaths = 0x00000200, // Can fill, outline and clip paths + Antialiasing = 0x00000400, // Can antialias lines + BrushStroke = 0x00000800, // Can render brush based pens + ConstantOpacity = 0x00001000, // Can render at constant opacity + MaskedBrush = 0x00002000, // Can fill with textures that has an alpha channel or mask + PerspectiveTransform = 0x00004000, // Can do perspective transformations + BlendModes = 0x00008000, // Can do extended Porter&Duff composition + ObjectBoundingModeGradients = 0x00010000, // Can do object bounding mode gradients + RasterOpModes = 0x00020000, // Can do logical raster operations + PaintOutsidePaintEvent = 0x20000000, // Engine is capable of painting outside paint events + /* 0x10000000, // Used for emulating + QGradient::StretchToDevice, + defined in qpainter.cpp + + 0x40000000, // Used internally for emulating opaque backgrounds + */ + + AllFeatures = 0xffffffff // For convenience + }; + Q_DECLARE_FLAGS(PaintEngineFeatures, PaintEngineFeature) + + enum DirtyFlag { + DirtyPen = 0x0001, + DirtyBrush = 0x0002, + DirtyBrushOrigin = 0x0004, + DirtyFont = 0x0008, + DirtyBackground = 0x0010, + DirtyBackgroundMode = 0x0020, + DirtyTransform = 0x0040, + DirtyClipRegion = 0x0080, + DirtyClipPath = 0x0100, + DirtyHints = 0x0200, + DirtyCompositionMode = 0x0400, + DirtyClipEnabled = 0x0800, + DirtyOpacity = 0x1000, + + AllDirty = 0xffff + }; + Q_DECLARE_FLAGS(DirtyFlags, DirtyFlag) + + enum PolygonDrawMode { + OddEvenMode, + WindingMode, + ConvexMode, + PolylineMode + }; + + explicit QPaintEngine(PaintEngineFeatures features=0); + virtual ~QPaintEngine(); + + bool isActive() const { return active; } + void setActive(bool newState) { active = newState; } + + virtual bool begin(QPaintDevice *pdev) = 0; + virtual bool end() = 0; + + virtual void updateState(const QPaintEngineState &state) = 0; + + virtual void drawRects(const QRect *rects, int rectCount); + virtual void drawRects(const QRectF *rects, int rectCount); + + virtual void drawLines(const QLine *lines, int lineCount); + virtual void drawLines(const QLineF *lines, int lineCount); + + virtual void drawEllipse(const QRectF &r); + virtual void drawEllipse(const QRect &r); + + virtual void drawPath(const QPainterPath &path); + + virtual void drawPoints(const QPointF *points, int pointCount); + virtual void drawPoints(const QPoint *points, int pointCount); + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + virtual void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) = 0; + virtual void drawTextItem(const QPointF &p, const QTextItem &textItem); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + + void setPaintDevice(QPaintDevice *device); + QPaintDevice *paintDevice() const; + + void setSystemClip(const QRegion &baseClip); + QRegion systemClip() const; + + void setSystemRect(const QRect &rect); + QRect systemRect() const; + +#ifdef Q_WS_WIN + virtual HDC getDC() const; + virtual void releaseDC(HDC hdc) const; +#endif + + virtual QPoint coordinateOffset() const; + + enum Type { + X11, + Windows, + QuickDraw, CoreGraphics, MacPrinter, + QWindowSystem, + PostScript, + OpenGL, + Picture, + SVG, + Raster, + Direct3D, + Pdf, + OpenVG, + OpenGL2, + PaintBuffer, + Blitter, + + User = 50, // first user type id + MaxUser = 100 // last user type id + }; + virtual Type type() const = 0; + + inline void fix_neg_rect(int *x, int *y, int *w, int *h); + + inline bool testDirty(DirtyFlags df); + inline void setDirty(DirtyFlags df); + inline void clearDirty(DirtyFlags df); + + bool hasFeature(PaintEngineFeatures feature) const { return (gccaps & feature) != 0; } + + QPainter *painter() const; + + void syncState(); + inline bool isExtended() const { return extended; } + +protected: + QPaintEngine(QPaintEnginePrivate &data, PaintEngineFeatures devcaps=0); + + QPaintEngineState *state; + PaintEngineFeatures gccaps; + + uint active : 1; + uint selfDestruct : 1; + uint extended : 1; + + QScopedPointer<QPaintEnginePrivate> d_ptr; + +private: + void setAutoDestruct(bool autoDestr) { selfDestruct = autoDestr; } + bool autoDestruct() const { return selfDestruct; } + Q_DISABLE_COPY(QPaintEngine) + + friend class QPainterReplayer; + friend class QFontEngineBox; + friend class QFontEngineMac; + friend class QFontEngineWin; +#ifndef QT_NO_FREETYPE + friend class QFontEngineFT; +#endif +#ifndef QT_NO_QWS_QPF + friend class QFontEngineQPF1; +#endif +#ifndef QT_NO_QWS_QPF2 + friend class QFontEngineQPF; +#endif + friend class QPSPrintEngine; + friend class QMacPrintEngine; + friend class QMacPrintEnginePrivate; +#ifdef Q_WS_QWS + friend class QtopiaPrintEngine; + friend class QtopiaPrintEnginePrivate; + friend class QProxyFontEngine; +#endif +#ifdef Q_WS_QPA + friend class QFontEngineQPA; +#endif + friend class QPainter; + friend class QPainterPrivate; + friend class QWidget; + friend class QWidgetPrivate; + friend class QWin32PaintEngine; + friend class QWin32PaintEnginePrivate; + friend class QMacCGContext; + friend class QPreviewPaintEngine; + friend class QX11GLPixmapData; +}; + + +class Q_GUI_EXPORT QPaintEngineState +{ +public: + QPaintEngine::DirtyFlags state() const { return dirtyFlags; } + + QPen pen() const; + QBrush brush() const; + QPointF brushOrigin() const; + QBrush backgroundBrush() const; + Qt::BGMode backgroundMode() const; + QFont font() const; + QMatrix matrix() const; + QTransform transform() const; + + Qt::ClipOperation clipOperation() const; + QRegion clipRegion() const; + QPainterPath clipPath() const; + bool isClipEnabled() const; + + QPainter::RenderHints renderHints() const; + QPainter::CompositionMode compositionMode() const; + qreal opacity() const; + + QPainter *painter() const; + + bool brushNeedsResolving() const; + bool penNeedsResolving() const; + +protected: + friend class QPaintEngine; + friend class QRasterPaintEngine; + friend class QWidget; + friend class QPainter; + friend class QPainterPrivate; + friend class QMacPrintEnginePrivate; + + QPaintEngine::DirtyFlags dirtyFlags; +}; + +// +// inline functions +// + +inline void QPaintEngine::fix_neg_rect(int *x, int *y, int *w, int *h) +{ + if (*w < 0) { + *w = -*w; + *x -= *w - 1; + } + if (*h < 0) { + *h = -*h; + *y -= *h - 1; + } +} + +inline bool QPaintEngine::testDirty(DirtyFlags df) { + Q_ASSERT(state); + return ((state->dirtyFlags & df) != 0); +} + +inline void QPaintEngine::setDirty(DirtyFlags df) { + Q_ASSERT(state); + state->dirtyFlags |= df; +} + +inline void QPaintEngine::clearDirty(DirtyFlags df) +{ + Q_ASSERT(state); + state->dirtyFlags &= ~static_cast<uint>(df); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextItem::RenderFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QPaintEngine::PaintEngineFeatures) +Q_DECLARE_OPERATORS_FOR_FLAGS(QPaintEngine::DirtyFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAINTENGINE_H diff --git a/src/gui/painting/qpaintengine_alpha.cpp b/src/gui/painting/qpaintengine_alpha.cpp new file mode 100644 index 0000000000..171850ded6 --- /dev/null +++ b/src/gui/painting/qpaintengine_alpha.cpp @@ -0,0 +1,516 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> + +#ifndef QT_NO_PRINTER +#include <qdebug.h> +#include "private/qpaintengine_alpha_p.h" + +#include "private/qpicture_p.h" +#include "private/qfont_p.h" +#include "QtGui/qpicture.h" + +QT_BEGIN_NAMESPACE + +QAlphaPaintEngine::QAlphaPaintEngine(QAlphaPaintEnginePrivate &data, PaintEngineFeatures devcaps) + : QPaintEngine(data, devcaps) +{ + +} + +QAlphaPaintEngine::~QAlphaPaintEngine() +{ + +} + +bool QAlphaPaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QAlphaPaintEngine); + + d->m_continueCall = true; + if (d->m_pass != 0) { + return true; + } + + d->m_savedcaps = gccaps; + d->m_pdev = pdev; + + d->m_alphaPen = false; + d->m_alphaBrush = false; + d->m_alphaOpacity = false; + d->m_hasalpha = false; + d->m_advancedPen = false; + d->m_advancedBrush = false; + d->m_complexTransform = false; + d->m_emulateProjectiveTransforms = false; + + // clear alpha region + d->m_alphargn = QRegion(); + d->m_cliprgn = QRegion(); + d->m_pen = QPen(); + d->m_transform = QTransform(); + + flushAndInit(); + + return true; +} + +bool QAlphaPaintEngine::end() +{ + Q_D(QAlphaPaintEngine); + + d->m_continueCall = true; + if (d->m_pass != 0) { + return true; + } + + flushAndInit(false); + return true; +} + +void QAlphaPaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QAlphaPaintEngine); + + DirtyFlags flags = state.state(); + if (flags & QPaintEngine::DirtyTransform) { + d->m_transform = state.transform(); + d->m_complexTransform = (d->m_transform.type() > QTransform::TxScale); + d->m_emulateProjectiveTransforms = !(d->m_savedcaps & QPaintEngine::PerspectiveTransform) + && !(d->m_savedcaps & QPaintEngine::AlphaBlend) + && (d->m_transform.type() >= QTransform::TxProject); + } + if (flags & QPaintEngine::DirtyPen) { + d->m_pen = state.pen(); + if (d->m_pen.style() == Qt::NoPen) { + d->m_advancedPen = false; + d->m_alphaPen = false; + } else { + d->m_advancedPen = (d->m_pen.brush().style() != Qt::SolidPattern); + d->m_alphaPen = !d->m_pen.brush().isOpaque(); + } + } + + if (d->m_pass != 0) { + d->m_continueCall = true; + return; + } + d->m_continueCall = false; + + if (flags & QPaintEngine::DirtyOpacity) { + d->m_alphaOpacity = (state.opacity() != 1.0f); + } + + if (flags & QPaintEngine::DirtyBrush) { + if (state.brush().style() == Qt::NoBrush) { + d->m_advancedBrush = false; + d->m_alphaBrush = false; + } else { + d->m_advancedBrush = (state.brush().style() != Qt::SolidPattern); + d->m_alphaBrush = !state.brush().isOpaque(); + } + } + + + d->m_hasalpha = d->m_alphaOpacity || d->m_alphaBrush || d->m_alphaPen; + + if (d->m_picengine) + d->m_picengine->updateState(state); +} + +void QAlphaPaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QAlphaPaintEngine); + + QRectF tr = d->addPenWidth(path); + + if (d->m_pass == 0) { + d->m_continueCall = false; + if (d->m_hasalpha || d->m_advancedPen || d->m_advancedBrush + || d->m_emulateProjectiveTransforms) + { + d->addAlphaRect(tr); + } + if (d->m_picengine) + d->m_picengine->drawPath(path); + } else { + d->m_continueCall = !d->fullyContained(tr); + } +} + +void QAlphaPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QAlphaPaintEngine); + + QPolygonF poly; + for (int i=0; i<pointCount; ++i) + poly.append(points[i]); + + QPainterPath path; + path.addPolygon(poly); + QRectF tr = d->addPenWidth(path); + + if (d->m_pass == 0) { + d->m_continueCall = false; + if (d->m_hasalpha || d->m_advancedPen || d->m_advancedBrush + || d->m_emulateProjectiveTransforms) + { + d->addAlphaRect(tr); + } + + if (d->m_picengine) + d->m_picengine->drawPolygon(points, pointCount, mode); + } else { + d->m_continueCall = !d->fullyContained(tr); + } +} + +void QAlphaPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QAlphaPaintEngine); + + QRectF tr = d->m_transform.mapRect(r); + if (d->m_pass == 0) { + d->m_continueCall = false; + if (pm.hasAlpha() || d->m_alphaOpacity || d->m_complexTransform || pm.isQBitmap()) { + d->addAlphaRect(tr); + } + + if (d->m_picengine) + d->m_picengine->drawPixmap(r, pm, sr); + + } else { + d->m_continueCall = !d->fullyContained(tr); + } +} + +void QAlphaPaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr) +{ + Q_D(QAlphaPaintEngine); + + QRectF tr = d->m_transform.mapRect(r); + if (d->m_pass == 0) { + d->m_continueCall = false; + if (image.hasAlphaChannel() || d->m_alphaOpacity || d->m_complexTransform) { + d->addAlphaRect(tr); + } + + if (d->m_picengine) + d->m_picengine->drawImage(r, image, sr); + + } else { + d->m_continueCall = !d->fullyContained(tr); + } +} + +void QAlphaPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QAlphaPaintEngine); + + QRectF tr(p.x(), p.y() - textItem.ascent(), textItem.width() + 5, textItem.ascent() + textItem.descent() + 5); + tr = d->m_transform.mapRect(tr); + + if (d->m_pass == 0) { + d->m_continueCall = false; + if (d->m_alphaPen || d->m_alphaOpacity || d->m_advancedPen) { + d->addAlphaRect(tr); + } + if (d->m_picengine) { + d->m_picengine->drawTextItem(p, textItem); + } + } else { + d->m_continueCall = !d->fullyContained(tr); + } +} + +void QAlphaPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) +{ + Q_D(QAlphaPaintEngine); + + QRectF brect = d->m_transform.mapRect(r); + + if (d->m_pass == 0) { + d->m_continueCall = false; + if (pixmap.hasAlpha() || d->m_alphaOpacity || d->m_complexTransform || pixmap.isQBitmap()) { + d->addAlphaRect(brect); + } + if (d->m_picengine) + d->m_picengine->drawTiledPixmap(r, pixmap, s); + } else { + d->m_continueCall = !d->fullyContained(brect); + } +} + +QRegion QAlphaPaintEngine::alphaClipping() const +{ + Q_D(const QAlphaPaintEngine); + return d->m_cliprgn; +} + +bool QAlphaPaintEngine::continueCall() const +{ + Q_D(const QAlphaPaintEngine); + return d->m_continueCall; +} + +void QAlphaPaintEngine::flushAndInit(bool init) +{ + Q_D(QAlphaPaintEngine); + Q_ASSERT(d->m_pass == 0); + + if (d->m_pic) { + d->m_picpainter->end(); + + // set clip region + d->m_alphargn = d->m_alphargn.intersected(QRect(0, 0, d->m_pdev->width(), d->m_pdev->height())); + + // just use the bounding rect if it's a complex region.. + QVector<QRect> rects = d->m_alphargn.rects(); + if (rects.size() > 10) { + QRect br = d->m_alphargn.boundingRect(); + d->m_alphargn = QRegion(br); + rects.clear(); + rects.append(br); + } + + d->m_cliprgn = d->m_alphargn; + + // now replay the QPicture + ++d->m_pass; // we are now doing pass #2 + + // reset states + gccaps = d->m_savedcaps; + + painter()->save(); + d->resetState(painter()); + + // make sure the output from QPicture is unscaled + QTransform mtx; + mtx.scale(1.0f / (qreal(d->m_pdev->logicalDpiX()) / qreal(qt_defaultDpiX())), + 1.0f / (qreal(d->m_pdev->logicalDpiY()) / qreal(qt_defaultDpiY()))); + painter()->setTransform(mtx); + painter()->drawPicture(0, 0, *d->m_pic); + + d->m_cliprgn = QRegion(); + d->resetState(painter()); + + // fill in the alpha images + for (int i=0; i<rects.size(); ++i) + d->drawAlphaImage(rects.at(i)); + + d->m_alphargn = QRegion(); + + painter()->restore(); + + --d->m_pass; // pass #2 finished + + cleanUp(); + } + + if (init) { + gccaps = PaintEngineFeatures(AllFeatures & ~QPaintEngine::ObjectBoundingModeGradients); + + d->m_pic = new QPicture(); + d->m_pic->d_ptr->in_memory_only = true; + d->m_picpainter = new QPainter(d->m_pic); + d->m_picengine = d->m_picpainter->paintEngine(); + + // When newPage() is called and the m_picpainter is recreated + // we have to copy the current state of the original printer + // painter back to the m_picpainter + d->m_picpainter->setPen(painter()->pen()); + d->m_picpainter->setBrush(painter()->brush()); + d->m_picpainter->setBrushOrigin(painter()->brushOrigin()); + d->m_picpainter->setFont(painter()->font()); + d->m_picpainter->setOpacity(painter()->opacity()); + d->m_picpainter->setTransform(painter()->combinedTransform()); + d->m_picengine->syncState(); + } +} + +void QAlphaPaintEngine::cleanUp() +{ + Q_D(QAlphaPaintEngine); + + delete d->m_picpainter; + delete d->m_pic; + + d->m_picpainter = 0; + d->m_pic = 0; + d->m_picengine = 0; +} + +QAlphaPaintEnginePrivate::QAlphaPaintEnginePrivate() + : m_pass(0), + m_pic(0), + m_picengine(0), + m_picpainter(0), + m_hasalpha(false), + m_alphaPen(false), + m_alphaBrush(false), + m_alphaOpacity(false), + m_advancedPen(false), + m_advancedBrush(false), + m_complexTransform(false) +{ + +} + +QAlphaPaintEnginePrivate::~QAlphaPaintEnginePrivate() +{ + delete m_picpainter; + delete m_pic; +} + +QRectF QAlphaPaintEnginePrivate::addPenWidth(const QPainterPath &path) +{ + QPainterPath tmp = path; + + if (m_pen.style() == Qt::NoPen) + return (path.controlPointRect() * m_transform).boundingRect(); + if (m_pen.isCosmetic()) + tmp = path * m_transform; + + QPainterPathStroker stroker; + if (m_pen.widthF() == 0.0f) + stroker.setWidth(1.0); + else + stroker.setWidth(m_pen.widthF()); + stroker.setJoinStyle(m_pen.joinStyle()); + stroker.setCapStyle(m_pen.capStyle()); + tmp = stroker.createStroke(tmp); + if (m_pen.isCosmetic()) + return tmp.controlPointRect(); + + return (tmp.controlPointRect() * m_transform).boundingRect(); +} + +QRect QAlphaPaintEnginePrivate::toRect(const QRectF &rect) const +{ + QRect r; + r.setLeft(int(rect.left())); + r.setTop(int(rect.top())); + r.setRight(int(rect.right() + 1)); + r.setBottom(int(rect.bottom() + 1)); + return r; +} + +void QAlphaPaintEnginePrivate::addAlphaRect(const QRectF &rect) +{ + m_alphargn |= toRect(rect); +} + +void QAlphaPaintEnginePrivate::drawAlphaImage(const QRectF &rect) +{ + Q_Q(QAlphaPaintEngine); + + qreal dpiX = qMax(m_pdev->logicalDpiX(), 300); + qreal dpiY = qMax(m_pdev->logicalDpiY(), 300); + qreal xscale = (dpiX / m_pdev->logicalDpiX()); + qreal yscale = (dpiY / m_pdev->logicalDpiY()); + + QTransform picscale; + picscale.scale(xscale, yscale); + + const int tileSize = 2048; + QSize size((int(rect.width() * xscale)), int(rect.height() * yscale)); + int divw = (size.width() / tileSize); + int divh = (size.height() / tileSize); + divw += 1; + divh += 1; + + int incx = int(rect.width() / divw); + int incy = int(rect.height() / divh); + + for (int y=0; y<divh; ++y) { + int ypos = int((incy * y) + rect.y()); + int height = int((y == (divh - 1)) ? (rect.height() - (incy * y)) : incy) + 1; + + for (int x=0; x<divw; ++x) { + int xpos = int((incx * x) + rect.x()); + int width = int((x == (divw - 1)) ? (rect.width() - (incx * x)) : incx) + 1; + + QSize imgsize((int)(width * xscale), (int)(height * yscale)); + QImage img(imgsize, QImage::Format_RGB32); + img.fill(0xffffffff); + + QPainter imgpainter(&img); + imgpainter.setTransform(picscale); + QPointF picpos(qreal(-xpos), qreal(-ypos)); + imgpainter.drawPicture(picpos, *m_pic); + imgpainter.end(); + + q->painter()->setTransform(QTransform()); + QRect r(xpos, ypos, width, height); + q->painter()->drawImage(r, img); + } + } +} + +bool QAlphaPaintEnginePrivate::fullyContained(const QRectF &rect) const +{ + QRegion r(toRect(rect)); + return (m_cliprgn.intersected(r) == r); +} + +void QAlphaPaintEnginePrivate::resetState(QPainter *p) +{ + p->setPen(QPen()); + p->setBrush(QBrush()); + p->setBrushOrigin(0,0); + p->setBackground(QBrush()); + p->setFont(QFont()); + p->setTransform(QTransform()); + // The view transform is already recorded and included in the + // picture we're about to replay. If we don't turn if off, + // the view matrix will be applied twice. + p->setViewTransformEnabled(false); + p->setClipRegion(QRegion(), Qt::NoClip); + p->setClipPath(QPainterPath(), Qt::NoClip); + p->setClipping(false); + p->setOpacity(1.0f); +} + + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qpaintengine_alpha_p.h b/src/gui/painting/qpaintengine_alpha_p.h new file mode 100644 index 0000000000..2b8b4dda07 --- /dev/null +++ b/src/gui/painting/qpaintengine_alpha_p.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_ALPHA_P_H +#define QPAINTENGINE_ALPHA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_PRINTER +#include "private/qpaintengine_p.h" + +QT_BEGIN_NAMESPACE + +class QAlphaPaintEnginePrivate; + +class QAlphaPaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QAlphaPaintEngine) +public: + ~QAlphaPaintEngine(); + + virtual bool begin(QPaintDevice *pdev); + virtual bool end(); + + virtual void updateState(const QPaintEngineState &state); + + virtual void drawPath(const QPainterPath &path); + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + virtual void drawImage(const QRectF &r, const QImage &image, const QRectF &sr); + virtual void drawTextItem(const QPointF &p, const QTextItem &textItem); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + +protected: + QAlphaPaintEngine(QAlphaPaintEnginePrivate &data, PaintEngineFeatures devcaps = 0); + QRegion alphaClipping() const; + bool continueCall() const; + void flushAndInit(bool init = true); + void cleanUp(); +}; + +class QAlphaPaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QAlphaPaintEngine) +public: + QAlphaPaintEnginePrivate(); + ~QAlphaPaintEnginePrivate(); + + int m_pass; + QPicture *m_pic; + QPaintEngine *m_picengine; + QPainter *m_picpainter; + + QPaintEngine::PaintEngineFeatures m_savedcaps; + QPaintDevice *m_pdev; + + QRegion m_alphargn; + QRegion m_cliprgn; + + bool m_hasalpha; + bool m_alphaPen; + bool m_alphaBrush; + bool m_alphaOpacity; + bool m_advancedPen; + bool m_advancedBrush; + bool m_complexTransform; + bool m_emulateProjectiveTransforms; + bool m_continueCall; + + QTransform m_transform; + QPen m_pen; + + void addAlphaRect(const QRectF &rect); + QRectF addPenWidth(const QPainterPath &path); + void drawAlphaImage(const QRectF &rect); + QRect toRect(const QRectF &rect) const; + bool fullyContained(const QRectF &rect) const; + + void resetState(QPainter *p); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPAINTENGINE_ALPHA_P_H diff --git a/src/gui/painting/qpaintengine_blitter.cpp b/src/gui/painting/qpaintengine_blitter.cpp new file mode 100644 index 0000000000..500748e159 --- /dev/null +++ b/src/gui/painting/qpaintengine_blitter.cpp @@ -0,0 +1,663 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qpaintengine_blitter_p.h" + +#include "private/qblittable_p.h" +#include "private/qpaintengine_raster_p.h" +#include "private/qpainter_p.h" +#include "private/qapplication_p.h" +#include "private/qpixmap_blitter_p.h" + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +#define STATE_XFORM_SCALE 0x00000001 +#define STATE_XFORM_COMPLEX 0x00000002 + +#define STATE_BRUSH_PATTERN 0x00000010 +#define STATE_BRUSH_ALPHA 0x00000020 + +#define STATE_PEN_ENABLED 0x00000100 + +#define STATE_ANTIALIASING 0x00001000 +#define STATE_ALPHA 0x00002000 +#define STATE_BLENDING_COMPLEX 0x00004000 + +#define STATE_CLIPSYS_COMPLEX 0x00010000 +#define STATE_CLIP_COMPLEX 0x00020000 + + +static inline void updateStateBits(uint *state, uint mask, bool on) +{ + *state = on ? (*state | mask) : (*state & ~mask); +} + +static inline bool checkStateAgainstMask(uint state, uint mask) +{ + return !state || (state & mask && !(state & ~mask)); +} + +class CapabilitiesToStateMask +{ +public: + CapabilitiesToStateMask(QBlittable::Capabilities capabilities) + : m_capabilities(capabilities), + fillRectMask(0), + drawRectMask(0), + drawPixmapMask(0), + capabillitiesState(0) + { + if (capabilities & QBlittable::SolidRectCapability) { + setFillRectMask(); + } + if (capabilities & QBlittable::SourcePixmapCapability) { + setSourcePixmapMask(); + } + if (capabilities & QBlittable::SourceOverPixmapCapability) { + setSourceOverPixmapMask(); + } + if (capabilities & QBlittable::SourceOverScaledPixmapCapability) { + setSourceOverScaledPixmapMask(); + } + } + + inline bool canBlitterFillRect() const + { + return checkStateAgainstMask(capabillitiesState,fillRectMask); + } + + inline bool canBlitterDrawRectMask() const + { + return checkStateAgainstMask(capabillitiesState,drawRectMask); + } + + bool canBlitterDrawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) const + { + if (pm.pixmapData()->classId() != QPixmapData::BlitterClass) + return false; + if (checkStateAgainstMask(capabillitiesState,drawPixmapMask)) { + if (m_capabilities & (QBlittable::SourceOverPixmapCapability + | QBlittable::SourceOverScaledPixmapCapability)) { + if (r.size() != sr.size()) { + return m_capabilities & QBlittable::SourceOverScaledPixmapCapability; + } else { + return m_capabilities & QBlittable::SourceOverPixmapCapability; + } + } + if ((m_capabilities & QBlittable::SourcePixmapCapability) && r.size() == sr.size() && !pm.hasAlphaChannel()) { + return m_capabilities & QBlittable::SourcePixmapCapability; + } + } + return false; + } + + inline void updateState(uint mask, bool on) { + updateStateBits(&capabillitiesState,mask,on); + } + +public: + + void setFillRectMask() { + updateStateBits(&fillRectMask, STATE_XFORM_SCALE, false); + updateStateBits(&fillRectMask, STATE_XFORM_COMPLEX, false); + + updateStateBits(&fillRectMask, STATE_BRUSH_PATTERN, false); + updateStateBits(&fillRectMask, STATE_BRUSH_ALPHA, false); + + updateStateBits(&fillRectMask, STATE_PEN_ENABLED, true); + + //Sub-pixel aliasing should not be sent to the blitter + updateStateBits(&fillRectMask, STATE_ANTIALIASING, true); + updateStateBits(&fillRectMask, STATE_ALPHA, false); + updateStateBits(&fillRectMask, STATE_BLENDING_COMPLEX, false); + + updateStateBits(&fillRectMask, STATE_CLIPSYS_COMPLEX, false); + updateStateBits(&fillRectMask, STATE_CLIP_COMPLEX, false); + } + + void setSourcePixmapMask() { + updateStateBits(&drawPixmapMask, STATE_XFORM_SCALE, true); + updateStateBits(&drawPixmapMask, STATE_XFORM_COMPLEX, false); + + updateStateBits(&drawPixmapMask, STATE_BRUSH_PATTERN, true); + updateStateBits(&drawPixmapMask, STATE_BRUSH_ALPHA, false); + + updateStateBits(&drawPixmapMask, STATE_PEN_ENABLED, true); + + updateStateBits(&drawPixmapMask, STATE_ANTIALIASING, true); + updateStateBits(&drawPixmapMask, STATE_ALPHA, false); + updateStateBits(&drawPixmapMask, STATE_BLENDING_COMPLEX, false); + + updateStateBits(&drawPixmapMask, STATE_CLIPSYS_COMPLEX, false); + updateStateBits(&drawPixmapMask, STATE_CLIP_COMPLEX, false); + } + + void setSourceOverPixmapMask() { + setSourcePixmapMask(); + } + + void setSourceOverScaledPixmapMask() { + setSourceOverPixmapMask(); + updateStateBits(&drawRectMask, STATE_XFORM_SCALE, true); + } + + QBlittable::Capabilities m_capabilities; + uint fillRectMask; + uint drawRectMask; + uint drawPixmapMask; + uint capabillitiesState; +}; + +class QBlitterPaintEnginePrivate : public QPaintEngineExPrivate +{ + Q_DECLARE_PUBLIC(QBlitterPaintEngine); +public: + QBlitterPaintEnginePrivate(QBlittablePixmapData *p) + : QPaintEngineExPrivate(), + pmData(p), + isBlitterLocked(false), + hasXForm(false) + + { + raster = new QRasterPaintEngine(p->buffer()); + capabillities = new CapabilitiesToStateMask(pmData->blittable()->capabilities()); + } + + inline void lock() { + if (!isBlitterLocked) { + raster->d_func()->rasterBuffer->prepare(pmData->blittable()->lock()); + isBlitterLocked = true; + } + } + + inline void unlock() { + if (isBlitterLocked) { + pmData->blittable()->unlock(); + isBlitterLocked = false; + } + } + + void fillRect(const QRectF &rect, const QColor &color) { + Q_Q(QBlitterPaintEngine); + pmData->unmarkRasterOverlay(rect); + QRectF targetRect = rect; + if (hasXForm) { + targetRect = q->state()->matrix.mapRect(rect); + } + const QClipData *clipData = q->clip(); + if (clipData) { + if (clipData->hasRectClip) { + unlock(); + pmData->blittable()->fillRect(targetRect & clipData->clipRect, color); + } else if (clipData->hasRegionClip) { + QVector<QRect> rects = clipData->clipRegion.rects(); + for ( int i = 0; i < rects.size(); i++ ) { + QRect intersectRect = rects.at(i).intersected(targetRect.toRect()); + if (!intersectRect.isEmpty()) { + unlock(); + pmData->blittable()->fillRect(intersectRect,color); + } + } + } + } else { + if (targetRect.x() >= 0 && targetRect.y() >= 0 + && targetRect.width() <= raster->paintDevice()->width() + && targetRect.height() <= raster->paintDevice()->height()) { + unlock(); + pmData->blittable()->fillRect(targetRect,color); + } else { + QRectF deviceRect(0,0,raster->paintDevice()->width(), raster->paintDevice()->height()); + unlock(); + pmData->blittable()->fillRect(deviceRect&targetRect,color); + } + } + } + + void clipAndDrawPixmap(const QRectF &clip, const QRectF &target, const QPixmap &pm, const QRectF &sr) { + QRectF intersectedRect = clip.intersected(target); + if (intersectedRect.isEmpty()) + return; + QRectF source = sr; + if(intersectedRect.size() != target.size()) { + qreal deltaTop = target.top() - intersectedRect.top(); + qreal deltaLeft = target.left() - intersectedRect.left(); + qreal deltaBottom = target.bottom() - intersectedRect.bottom(); + qreal deltaRight = target.right() - intersectedRect.right(); + source.adjust(-deltaLeft,-deltaTop,-deltaRight,-deltaBottom); + } + pmData->unmarkRasterOverlay(intersectedRect); + pmData->blittable()->drawPixmap(intersectedRect, pm, source); + } + + void updateClip() { + Q_Q(QBlitterPaintEngine); + const QClipData *clip = q->clip(); + bool complex = clip && !(clip->hasRectClip || clip->hasRegionClip); + capabillities->updateState(STATE_CLIP_COMPLEX, complex); + } + + void systemStateChanged() { + raster->d_func()->systemStateChanged(); + } + + QRasterPaintEngine *raster; + + QBlittablePixmapData *pmData; + bool isBlitterLocked; + + CapabilitiesToStateMask *capabillities; + + uint hasXForm; +}; + +QBlitterPaintEngine::QBlitterPaintEngine(QBlittablePixmapData *p) + : QPaintEngineEx(*(new QBlitterPaintEnginePrivate(p))) +{ +} + +QBlitterPaintEngine::~QBlitterPaintEngine() +{ +} + +QPainterState *QBlitterPaintEngine::createState(QPainterState *orig) const +{ + Q_D(const QBlitterPaintEngine); + return d->raster->createState(orig); +} + +bool QBlitterPaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QBlitterPaintEngine); + + setActive(true); + bool ok = d->raster->begin(pdev); +#ifdef QT_BLITTER_RASTEROVERLAY + d->pmData->unmergeOverlay(); +#endif + return ok; +} + + +bool QBlitterPaintEngine::end() +{ + Q_D(QBlitterPaintEngine); + + setActive(false); +#ifdef QT_BLITTER_RASTEROVERLAY + d->pmData->mergeOverlay(); +#endif + return d->raster->end(); +} + + +void QBlitterPaintEngine::fill(const QVectorPath &path, const QBrush &brush) +{ + Q_D(QBlitterPaintEngine); + if (path.shape() == QVectorPath::RectangleHint) { + QRectF rect(((QPointF *) path.points())[0], ((QPointF *) path.points())[2]); + fillRect(rect, brush); + } else { + d->lock(); + d->pmData->markRasterOverlay(path); + d->raster->fill(path, brush); + } +} + +void QBlitterPaintEngine::fillRect(const QRectF &rect, const QColor &color) +{ + Q_D(QBlitterPaintEngine); + if (d->capabillities->canBlitterFillRect() && color.alpha() == 0xff) { + d->fillRect(rect, color); + } else { + d->lock(); + d->pmData->markRasterOverlay(rect); + d->raster->fillRect(rect, color); + } +} + +void QBlitterPaintEngine::fillRect(const QRectF &rect, const QBrush &brush) +{ + if(rect.size().isEmpty()) + return; + + Q_D(QBlitterPaintEngine); + + if (qbrush_style(brush) == Qt::SolidPattern + && qbrush_color(brush).alpha() == 0xff + && d->capabillities->canBlitterFillRect()) + { + d->fillRect(rect, qbrush_color(brush)); + }else if (brush.style() == Qt::TexturePattern + && d->capabillities->canBlitterDrawPixmap(rect,brush.texture(),rect)) + { + bool rectIsFilled = false; + QRectF transformedRect = state()->matrix.mapRect(rect); + qreal x = transformedRect.x(); + qreal y = transformedRect.y(); + QPixmap pm = brush.texture(); + d->unlock(); + int srcX = int(rect.x() - state()->brushOrigin.x()) % pm.width(); + if (srcX < 0) + srcX = pm.width() + srcX; + const int startX = srcX; + int srcY = int(rect.y() - state()->brushOrigin.y()) % pm.height(); + if (srcY < 0) + srcY = pm.height() + srcY; + while (!rectIsFilled) { + qreal blitWidth = (pm.width() ) - srcX; + qreal blitHeight = (pm.height() ) - srcY; + if (x + blitWidth > transformedRect.right()) + blitWidth = transformedRect.right() -x; + if (y + blitHeight > transformedRect.bottom()) + blitHeight = transformedRect.bottom() - y; + const QClipData *clipData = clip(); + if (clipData->hasRectClip) { + QRect targetRect = QRect(x,y,blitWidth,blitHeight).intersected(clipData->clipRect); + if (targetRect.isValid()) { + int tmpSrcX = srcX + (targetRect.x() - x); + int tmpSrcY = srcY + (targetRect.y() - y); + QRect srcRect(tmpSrcX,tmpSrcY,targetRect.width(),targetRect.height()); + d->pmData->blittable()->drawPixmap(targetRect,pm,srcRect); + } + } else if (clipData->hasRegionClip) { + QVector<QRect> clipRects = clipData->clipRegion.rects(); + QRect unclippedTargetRect(x,y,blitWidth,blitHeight); + QRegion intersectedRects = clipData->clipRegion.intersected(unclippedTargetRect); + + for ( int i = 0; i < intersectedRects.rects().size(); i++ ) { + QRect targetRect = intersectedRects.rects().at(i); + if (!targetRect.isValid() || targetRect.isEmpty()) + continue; + int tmpSrcX = srcX + (targetRect.x() - x); + int tmpSrcY = srcY + (targetRect.y() - y); + QRect srcRect(tmpSrcX,tmpSrcY,targetRect.width(),targetRect.height()); + d->pmData->blittable()->drawPixmap(targetRect,pm,srcRect); + } + } + x+=blitWidth; + if (x>=transformedRect.right()) { + x = transformedRect.x(); + srcX = startX; + srcY = 0; + y+=blitHeight; + if (y>=transformedRect.bottom()) + rectIsFilled = true; + } else + srcX = 0; + } + } else { + d->lock(); + d->pmData->markRasterOverlay(rect); + d->raster->fillRect(rect, brush); + } + +} + +void QBlitterPaintEngine::stroke(const QVectorPath &path, const QPen &pen) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->pmData->markRasterOverlay(path); + d->raster->stroke(path, pen); +} + +void QBlitterPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->clip(path, op); + d->updateClip(); +} +void QBlitterPaintEngine::clip(const QRect &rect, Qt::ClipOperation op){ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->clip(rect,op); + d->updateClip(); +} +void QBlitterPaintEngine::clip(const QRegion ®ion, Qt::ClipOperation op) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->clip(region,op); + d->updateClip(); +} + +void QBlitterPaintEngine::clipEnabledChanged() +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->clipEnabledChanged(); +} + +void QBlitterPaintEngine::penChanged() +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->penChanged(); + d->capabillities->updateState(STATE_PEN_ENABLED,qpen_style(state()->pen) != Qt::NoPen); +} + +void QBlitterPaintEngine::brushChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->brushChanged(); + + bool solid = qbrush_style(state()->brush) == Qt::SolidPattern; + + d->capabillities->updateState(STATE_BRUSH_PATTERN, !solid); + d->capabillities->updateState(STATE_BRUSH_ALPHA, + qbrush_color(state()->brush).alpha() < 255); +} + +void QBlitterPaintEngine::brushOriginChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->brushOriginChanged(); +} + +void QBlitterPaintEngine::opacityChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->opacityChanged(); + + bool translucent = state()->opacity < 1; + d->capabillities->updateState(STATE_ALPHA,translucent); +} + +void QBlitterPaintEngine::compositionModeChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->compositionModeChanged(); + + bool nonTrivial = state()->composition_mode != QPainter::CompositionMode_SourceOver + && state()->composition_mode != QPainter::CompositionMode_Source; + + d->capabillities->updateState(STATE_BLENDING_COMPLEX,nonTrivial); +} + +void QBlitterPaintEngine::renderHintsChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->renderHintsChanged(); + + bool aa = state()->renderHints & QPainter::Antialiasing; + d->capabillities->updateState(STATE_ANTIALIASING, aa); + +} + +void QBlitterPaintEngine::transformChanged() +{ + Q_D(QBlitterPaintEngine); + d->raster->transformChanged(); + + QTransform::TransformationType type = state()->matrix.type(); + + d->capabillities->updateState(STATE_XFORM_COMPLEX, type > QTransform::TxScale); + d->capabillities->updateState(STATE_XFORM_SCALE, type > QTransform::TxTranslate); + + d->hasXForm = type >= QTransform::TxTranslate; + +} + +void QBlitterPaintEngine::drawRects(const QRect *rects, int rectCount) +{ + Q_D(QBlitterPaintEngine); + if (d->capabillities->canBlitterDrawRectMask()) { + for (int i=0; i<rectCount; ++i) { + d->fillRect(rects[i], qbrush_color(state()->brush)); + } + } else { + d->pmData->markRasterOverlay(rects,rectCount); + QPaintEngineEx::drawRects(rects, rectCount); + } +} + +void QBlitterPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QBlitterPaintEngine); + if (d->capabillities->canBlitterDrawRectMask()) { + for (int i=0; i<rectCount; ++i) { + d->fillRect(rects[i], qbrush_color(state()->brush)); + } + } else { + d->pmData->markRasterOverlay(rects,rectCount); + QPaintEngineEx::drawRects(rects, rectCount); + } +} + +void QBlitterPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QBlitterPaintEngine); + if (d->capabillities->canBlitterDrawPixmap(r,pm,sr)) { + + d->unlock(); + QRectF targetRect = r; + if (d->hasXForm) { + targetRect = state()->matrix.mapRect(r); + } + const QClipData *clipData = clip(); + if (clipData) { + if (clipData->hasRectClip) { + d->clipAndDrawPixmap(clipData->clipRect,targetRect,pm,sr); + }else if (clipData->hasRegionClip) { + QVector<QRect>rects = clipData->clipRegion.rects(); + for (int i = 0; i<rects.size(); i++) { + d->clipAndDrawPixmap(rects.at(i),targetRect,pm,sr); + } + } + } else { + QRectF deviceRect(0,0,d->raster->paintDevice()->width(), d->raster->paintDevice()->height()); + d->clipAndDrawPixmap(deviceRect,targetRect,pm,sr); + } + }else { + d->lock(); + d->pmData->markRasterOverlay(r); + d->raster->drawPixmap(r, pm, sr); + } +} + +void QBlitterPaintEngine::drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->pmData->markRasterOverlay(r); + d->raster->drawImage(r, pm, sr, flags); +} + + +void QBlitterPaintEngine::drawTextItem(const QPointF &pos, const QTextItem &ti) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->drawTextItem(pos, ti); + d->pmData->markRasterOverlay(pos,ti); +} + +void QBlitterPaintEngine::drawStaticTextItem(QStaticTextItem *sti) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->raster->drawStaticTextItem(sti); + +//#### d->pmData->markRasterOverlay(sti); + qWarning("not implemented: markRasterOverlay for QStaticTextItem"); + +} + + +void QBlitterPaintEngine::drawEllipse(const QRectF &r) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + d->pmData->markRasterOverlay(r); + d->raster->drawEllipse(r); +} + +void QBlitterPaintEngine::setState(QPainterState *s) +{ + Q_D(QBlitterPaintEngine); + d->lock(); + QPaintEngineEx::setState(s); + d->raster->setState(s); + + clipEnabledChanged(); + penChanged(); + brushChanged(); + brushOriginChanged(); + opacityChanged(); + compositionModeChanged(); + renderHintsChanged(); + transformChanged(); + + d->updateClip(); +} + +inline QRasterPaintEngine *QBlitterPaintEngine::raster() const +{ + Q_D(const QBlitterPaintEngine); + return d->raster; +} + +QT_END_NAMESPACE +#endif //QT_NO_BLITTABLE + diff --git a/src/gui/painting/qpaintengine_blitter_p.h b/src/gui/painting/qpaintengine_blitter_p.h new file mode 100644 index 0000000000..be8b2bc5ea --- /dev/null +++ b/src/gui/painting/qpaintengine_blitter_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_BLITTER_P_H +#define QPAINTENGINE_BLITTER_P_H + +#include "private/qpaintengineex_p.h" +#include "private/qpaintengine_raster_p.h" + +#ifndef QT_NO_BLITTABLE +QT_BEGIN_NAMESPACE + +class QBlitterPaintEnginePrivate; +class QBlittablePixmapData; +class QBlittable; + +class Q_GUI_EXPORT QBlitterPaintEngine : public QPaintEngineEx +{ + Q_DECLARE_PRIVATE(QBlitterPaintEngine); +public: + QBlitterPaintEngine(QBlittablePixmapData *p); + ~QBlitterPaintEngine(); + + virtual QPainterState *createState(QPainterState *orig) const; + + virtual QPaintEngine::Type type() const { return Blitter; } + + virtual bool begin(QPaintDevice *pdev); + virtual bool end(); + + virtual void fill(const QVectorPath &path, const QBrush &brush); + virtual void stroke(const QVectorPath &path, const QPen &pen); + + virtual void clip(const QVectorPath &path, Qt::ClipOperation op); + virtual void clip(const QRect &rect, Qt::ClipOperation op); + virtual void clip(const QRegion ®ion, Qt::ClipOperation op); + + virtual void clipEnabledChanged(); + virtual void penChanged(); + virtual void brushChanged(); + virtual void brushOriginChanged(); + virtual void opacityChanged(); + virtual void compositionModeChanged(); + virtual void renderHintsChanged(); + virtual void transformChanged(); + + virtual void fillRect(const QRectF &rect, const QBrush &brush); + virtual void fillRect(const QRectF &rect, const QColor &color); + + virtual void drawRects(const QRect *rects, int rectCount); + virtual void drawRects(const QRectF *rects, int rectCount); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags); + + virtual void drawTextItem(const QPointF &pos, const QTextItem &ti); + virtual void drawStaticTextItem(QStaticTextItem *); + + virtual void drawEllipse(const QRectF &r); + + virtual void setState(QPainterState *s); + + inline QPainterState *state() { return raster()->state(); } + inline const QPainterState *state() const { const QPainterState *state = raster()->state(); return state;} + inline const QClipData *clip(){return raster()->d_func()->clip();} + +private: + QRasterPaintEngine *raster() const; +}; + +QT_END_NAMESPACE +#endif //QT_NO_BLITTABLE +#endif // QPAINTENGINE_BLITTER_P_H + diff --git a/src/gui/painting/qpaintengine_mac.cpp b/src/gui/painting/qpaintengine_mac.cpp new file mode 100644 index 0000000000..8aab7c7a4b --- /dev/null +++ b/src/gui/painting/qpaintengine_mac.cpp @@ -0,0 +1,1749 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qbitmap.h> +#include <qpaintdevice.h> +#include <private/qpaintengine_mac_p.h> +#include <qpainterpath.h> +#include <qpixmapcache.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qprintengine_mac_p.h> +#include <qprinter.h> +#include <qstack.h> +#include <qtextcodec.h> +#include <qwidget.h> +#include <qvarlengtharray.h> +#include <qdebug.h> +#include <qcoreapplication.h> +#include <qmath.h> + +#include <private/qfont_p.h> +#include <private/qfontengine_p.h> +#include <private/qfontengine_coretext_p.h> +#include <private/qfontengine_mac_p.h> +#include <private/qnumeric_p.h> +#include <private/qpainter_p.h> +#include <private/qpainterpath_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qt_mac_p.h> +#include <private/qtextengine_p.h> +#include <private/qwidget_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> + +#include <string.h> + +QT_BEGIN_NAMESPACE + +extern int qt_antialiasing_threshold; // QApplication.cpp + +/***************************************************************************** + External functions + *****************************************************************************/ +extern CGImageRef qt_mac_create_imagemask(const QPixmap &px, const QRectF &sr); //qpixmap_mac.cpp +extern QPoint qt_mac_posInWindow(const QWidget *w); //qwidget_mac.cpp +extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp +extern CGContextRef qt_mac_cg_context(const QPaintDevice *); //qpaintdevice_mac.cpp +extern void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp +extern QPixmap qt_pixmapForBrush(int, bool); //qbrush.cpp + +void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform); + + +//Implemented for qt_mac_p.h +QMacCGContext::QMacCGContext(QPainter *p) +{ + QPaintEngine *pe = p->paintEngine(); + if (pe->type() == QPaintEngine::MacPrinter) + pe = static_cast<QMacPrintEngine*>(pe)->paintEngine(); + pe->syncState(); + context = 0; + if(pe->type() == QPaintEngine::CoreGraphics) + context = static_cast<QCoreGraphicsPaintEngine*>(pe)->handle(); + + int devType = p->device()->devType(); + if (pe->type() == QPaintEngine::Raster + && (devType == QInternal::Widget || devType == QInternal::Pixmap)) { + + extern CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *paintDevice); + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pe->paintDevice()); + uint flags = kCGImageAlphaPremultipliedFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + flags |= kCGBitmapByteOrder32Host; +#endif + const QImage *image = (const QImage *) pe->paintDevice(); + + context = CGBitmapContextCreate((void *) image->bits(), image->width(), image->height(), + 8, image->bytesPerLine(), colorspace, flags); + + CGContextTranslateCTM(context, 0, image->height()); + CGContextScaleCTM(context, 1, -1); + + if (devType == QInternal::Widget) { + QRegion clip = p->paintEngine()->systemClip(); + QTransform native = p->deviceTransform(); + QTransform logical = p->combinedTransform(); + + if (p->hasClipping()) { + QRegion r = p->clipRegion(); + r.translate(native.dx(), native.dy()); + if (clip.isEmpty()) + clip = r; + else + clip &= r; + } + qt_mac_clip_cg(context, clip, 0); + + CGContextTranslateCTM(context, native.dx(), native.dy()); + } + } else { + CGContextRetain(context); + } +} + + +/***************************************************************************** + QCoreGraphicsPaintEngine utility functions + *****************************************************************************/ + +//conversion +inline static float qt_mac_convert_color_to_cg(int c) { return ((float)c * 1000 / 255) / 1000; } +inline static int qt_mac_convert_color_from_cg(float c) { return qRound(c * 255); } +CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &t) { + return CGAffineTransformMake(t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy()); +} + +CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *paintDevice) +{ + bool isWidget = (paintDevice->devType() == QInternal::Widget); + return QCoreGraphicsPaintEngine::macDisplayColorSpace(isWidget ? static_cast<const QWidget *>(paintDevice) + : 0); +} + +inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col, QPaintDevice *pdev) +{ + CGFloat components[] = { + qt_mac_convert_color_to_cg(col.red()), + qt_mac_convert_color_to_cg(col.green()), + qt_mac_convert_color_to_cg(col.blue()), + qt_mac_convert_color_to_cg(col.alpha()) + }; + return CGColorCreate(qt_mac_colorSpaceForDeviceType(pdev), components); +} + +// There's architectural problems with using native gradients +// on the Mac at the moment, so disable them. +// #define QT_MAC_USE_NATIVE_GRADIENTS + +#ifdef QT_MAC_USE_NATIVE_GRADIENTS +static bool drawGradientNatively(const QGradient *gradient) +{ + return gradient->spread() == QGradient::PadSpread; +} + +// gradiant callback +static void qt_mac_color_gradient_function(void *info, const CGFloat *in, CGFloat *out) +{ + QBrush *brush = static_cast<QBrush *>(info); + Q_ASSERT(brush && brush->gradient()); + + const QGradientStops stops = brush->gradient()->stops(); + const int n = stops.count(); + Q_ASSERT(n >= 1); + const QGradientStop *begin = stops.constBegin(); + const QGradientStop *end = begin + n; + + qreal p = in[0]; + const QGradientStop *i = begin; + while (i != end && i->first < p) + ++i; + + QRgb c; + if (i == begin) { + c = begin->second.rgba(); + } else if (i == end) { + c = (end - 1)->second.rgba(); + } else { + const QGradientStop &s1 = *(i - 1); + const QGradientStop &s2 = *i; + qreal p1 = s1.first; + qreal p2 = s2.first; + QRgb c1 = s1.second.rgba(); + QRgb c2 = s2.second.rgba(); + int idist = 256 * (p - p1) / (p2 - p1); + int dist = 256 - idist; + c = qRgba(INTERPOLATE_PIXEL_256(qRed(c1), dist, qRed(c2), idist), + INTERPOLATE_PIXEL_256(qGreen(c1), dist, qGreen(c2), idist), + INTERPOLATE_PIXEL_256(qBlue(c1), dist, qBlue(c2), idist), + INTERPOLATE_PIXEL_256(qAlpha(c1), dist, qAlpha(c2), idist)); + } + + out[0] = qt_mac_convert_color_to_cg(qRed(c)); + out[1] = qt_mac_convert_color_to_cg(qGreen(c)); + out[2] = qt_mac_convert_color_to_cg(qBlue(c)); + out[3] = qt_mac_convert_color_to_cg(qAlpha(c)); +} +#endif + +//clipping handling +void QCoreGraphicsPaintEnginePrivate::resetClip() +{ + static bool inReset = false; + if (inReset) + return; + inReset = true; + + CGAffineTransform old_xform = CGContextGetCTM(hd); + + //setup xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform)); + while (stackCount > 0) { + restoreGraphicsState(); + } + saveGraphicsState(); + inReset = false; + //reset xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGContextConcatCTM(hd, old_xform); +} + +static CGRect qt_mac_compose_rect(const QRectF &r, float off=0) +{ + return CGRectMake(r.x()+off, r.y()+off, r.width(), r.height()); +} + +static CGMutablePathRef qt_mac_compose_path(const QPainterPath &p, float off=0) +{ + CGMutablePathRef ret = CGPathCreateMutable(); + QPointF startPt; + for (int i=0; i<p.elementCount(); ++i) { + const QPainterPath::Element &elm = p.elementAt(i); + switch (elm.type) { + case QPainterPath::MoveToElement: + if(i > 0 + && p.elementAt(i - 1).x == startPt.x() + && p.elementAt(i - 1).y == startPt.y()) + CGPathCloseSubpath(ret); + startPt = QPointF(elm.x, elm.y); + CGPathMoveToPoint(ret, 0, elm.x+off, elm.y+off); + break; + case QPainterPath::LineToElement: + CGPathAddLineToPoint(ret, 0, elm.x+off, elm.y+off); + break; + case QPainterPath::CurveToElement: + Q_ASSERT(p.elementAt(i+1).type == QPainterPath::CurveToDataElement); + Q_ASSERT(p.elementAt(i+2).type == QPainterPath::CurveToDataElement); + CGPathAddCurveToPoint(ret, 0, + elm.x+off, elm.y+off, + p.elementAt(i+1).x+off, p.elementAt(i+1).y+off, + p.elementAt(i+2).x+off, p.elementAt(i+2).y+off); + i+=2; + break; + default: + qFatal("QCoreGraphicsPaintEngine::drawPath(), unhandled type: %d", elm.type); + break; + } + } + if(!p.isEmpty() + && p.elementAt(p.elementCount() - 1).x == startPt.x() + && p.elementAt(p.elementCount() - 1).y == startPt.y()) + CGPathCloseSubpath(ret); + return ret; +} + +CGColorSpaceRef QCoreGraphicsPaintEngine::m_genericColorSpace = 0; +QHash<CGDirectDisplayID, CGColorSpaceRef> QCoreGraphicsPaintEngine::m_displayColorSpaceHash; +bool QCoreGraphicsPaintEngine::m_postRoutineRegistered = false; + +CGColorSpaceRef QCoreGraphicsPaintEngine::macGenericColorSpace() +{ +#if 0 + if (!m_genericColorSpace) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + m_genericColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + } else +#endif + { + m_genericColorSpace = CGColorSpaceCreateDeviceRGB(); + } + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); + } + } + return m_genericColorSpace; +#else + // Just return the main display colorspace for the moment. + return macDisplayColorSpace(); +#endif +} + +/* + Ideally, we should pass the widget in here, and use CGGetDisplaysWithRect() etc. + to support multiple displays correctly. +*/ +CGColorSpaceRef QCoreGraphicsPaintEngine::macDisplayColorSpace(const QWidget *widget) +{ + CGColorSpaceRef colorSpace; + + CGDirectDisplayID displayID; + CMProfileRef displayProfile = 0; + if (widget == 0) { + displayID = CGMainDisplayID(); + } else { + const QRect &qrect = widget->window()->geometry(); + CGRect rect = CGRectMake(qrect.x(), qrect.y(), qrect.width(), qrect.height()); + CGDisplayCount throwAway; + CGDisplayErr dErr = CGGetDisplaysWithRect(rect, 1, &displayID, &throwAway); + if (dErr != kCGErrorSuccess) + return macDisplayColorSpace(0); // fall back on main display + } + if ((colorSpace = m_displayColorSpaceHash.value(displayID))) + return colorSpace; + + CMError err = CMGetProfileByAVID((CMDisplayIDType)displayID, &displayProfile); + if (err == noErr) { + colorSpace = CGColorSpaceCreateWithPlatformColorSpace(displayProfile); + } else if (widget) { + return macDisplayColorSpace(0); // fall back on main display + } + + if (colorSpace == 0) + colorSpace = CGColorSpaceCreateDeviceRGB(); + + m_displayColorSpaceHash.insert(displayID, colorSpace); + CMCloseProfile(displayProfile); + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); + } + return colorSpace; +} + +void QCoreGraphicsPaintEngine::cleanUpMacColorSpaces() +{ + if (m_genericColorSpace) { + CFRelease(m_genericColorSpace); + m_genericColorSpace = 0; + } + QHash<CGDirectDisplayID, CGColorSpaceRef>::const_iterator it = m_displayColorSpaceHash.constBegin(); + while (it != m_displayColorSpaceHash.constEnd()) { + if (it.value()) + CFRelease(it.value()); + ++it; + } + m_displayColorSpaceHash.clear(); +} + +void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform) +{ + CGAffineTransform old_xform = CGAffineTransformIdentity; + if(orig_xform) { //setup xforms + old_xform = CGContextGetCTM(hd); + CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform)); + CGContextConcatCTM(hd, *orig_xform); + } + + //do the clipping + CGContextBeginPath(hd); + if(rgn.isEmpty()) { + CGContextAddRect(hd, CGRectMake(0, 0, 0, 0)); + } else { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + QCFType<HIMutableShapeRef> shape = rgn.toHIMutableShape(); + Q_ASSERT(!HIShapeIsEmpty(shape)); + HIShapeReplacePathInCGContext(shape, hd); + } else +#endif + { + QVector<QRect> rects = rgn.rects(); + const int count = rects.size(); + for(int i = 0; i < count; i++) { + const QRect &r = rects[i]; + CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height()); + CGContextAddRect(hd, mac_r); + } + } + + } + CGContextClip(hd); + + if(orig_xform) {//reset xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGContextConcatCTM(hd, old_xform); + } +} + + +//pattern handling (tiling) +#if 1 +# define QMACPATTERN_MASK_MULTIPLIER 32 +#else +# define QMACPATTERN_MASK_MULTIPLIER 1 +#endif +class QMacPattern +{ +public: + QMacPattern() : as_mask(false), pdev(0), image(0) { data.bytes = 0; } + ~QMacPattern() { CGImageRelease(image); } + int width() { + if(image) + return CGImageGetWidth(image); + if(data.bytes) + return 8*QMACPATTERN_MASK_MULTIPLIER; + return data.pixmap.width(); + } + int height() { + if(image) + return CGImageGetHeight(image); + if(data.bytes) + return 8*QMACPATTERN_MASK_MULTIPLIER; + return data.pixmap.height(); + } + + //input + QColor foreground; + bool as_mask; + struct { + QPixmap pixmap; + const uchar *bytes; + } data; + QPaintDevice *pdev; + //output + CGImageRef image; +}; +static void qt_mac_draw_pattern(void *info, CGContextRef c) +{ + QMacPattern *pat = (QMacPattern*)info; + int w = 0, h = 0; + bool isBitmap = (pat->data.pixmap.depth() == 1); + if(!pat->image) { //lazy cache + if(pat->as_mask) { + Q_ASSERT(pat->data.bytes); + w = h = 8; +#if (QMACPATTERN_MASK_MULTIPLIER == 1) + CGDataProviderRef provider = CGDataProviderCreateWithData(0, pat->data.bytes, w*h, 0); + pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRelease(provider); +#else + const int numBytes = (w*h)/sizeof(uchar); + uchar xor_bytes[numBytes]; + for(int i = 0; i < numBytes; ++i) + xor_bytes[i] = pat->data.bytes[i] ^ 0xFF; + CGDataProviderRef provider = CGDataProviderCreateWithData(0, xor_bytes, w*h, 0); + CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRelease(provider); + + const QColor c0(0, 0, 0, 0), c1(255, 255, 255, 255); + QPixmap pm(w*QMACPATTERN_MASK_MULTIPLIER, h*QMACPATTERN_MASK_MULTIPLIER); + pm.fill(c0); + CGContextRef pm_ctx = qt_mac_cg_context(&pm); + CGContextSetFillColorWithColor(c, cgColorForQColor(c1, pat->pdev)); + CGRect rect = CGRectMake(0, 0, w, h); + for(int x = 0; x < QMACPATTERN_MASK_MULTIPLIER; ++x) { + rect.origin.x = x * w; + for(int y = 0; y < QMACPATTERN_MASK_MULTIPLIER; ++y) { + rect.origin.y = y * h; + qt_mac_drawCGImage(pm_ctx, &rect, swatch); + } + } + pat->image = qt_mac_create_imagemask(pm, pm.rect()); + CGImageRelease(swatch); + CGContextRelease(pm_ctx); + w *= QMACPATTERN_MASK_MULTIPLIER; + h *= QMACPATTERN_MASK_MULTIPLIER; +#endif + } else { + w = pat->data.pixmap.width(); + h = pat->data.pixmap.height(); + if (isBitmap) + pat->image = qt_mac_create_imagemask(pat->data.pixmap, pat->data.pixmap.rect()); + else + pat->image = (CGImageRef)pat->data.pixmap.macCGHandle(); + } + } else { + w = CGImageGetWidth(pat->image); + h = CGImageGetHeight(pat->image); + } + + //draw + bool needRestore = false; + if (CGImageIsMask(pat->image)) { + CGContextSaveGState(c); + CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground, pat->pdev)); + } + CGRect rect = CGRectMake(0, 0, w, h); + qt_mac_drawCGImage(c, &rect, pat->image); + if(needRestore) + CGContextRestoreGState(c); +} +static void qt_mac_dispose_pattern(void *info) +{ + QMacPattern *pat = (QMacPattern*)info; + delete pat; +} + +/***************************************************************************** + QCoreGraphicsPaintEngine member functions + *****************************************************************************/ + +inline static QPaintEngine::PaintEngineFeatures qt_mac_cg_features() +{ + return QPaintEngine::PaintEngineFeatures(QPaintEngine::AllFeatures & ~QPaintEngine::PaintOutsidePaintEvent + & ~QPaintEngine::PerspectiveTransform + & ~QPaintEngine::ConicalGradientFill + & ~QPaintEngine::LinearGradientFill + & ~QPaintEngine::RadialGradientFill + & ~QPaintEngine::BrushStroke); +} + +QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine() +: QPaintEngine(*(new QCoreGraphicsPaintEnginePrivate), qt_mac_cg_features()) +{ +} + +QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr) +: QPaintEngine(dptr, qt_mac_cg_features()) +{ +} + +QCoreGraphicsPaintEngine::~QCoreGraphicsPaintEngine() +{ +} + +bool +QCoreGraphicsPaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QCoreGraphicsPaintEngine); + if(isActive()) { // already active painting + qWarning("QCoreGraphicsPaintEngine::begin: Painter already active"); + return false; + } + + //initialization + d->pdev = pdev; + d->complexXForm = false; + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; + d->cosmeticPenSize = 1; + d->current.clipEnabled = false; + d->pixelSize = QPoint(1,1); + d->hd = qt_mac_cg_context(pdev); + if(d->hd) { + d->saveGraphicsState(); + d->orig_xform = CGContextGetCTM(d->hd); + if (d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->setClip(0); //clear the context's clipping + } + + setActive(true); + + if(d->pdev->devType() == QInternal::Widget) { // device is a widget + QWidget *w = (QWidget*)d->pdev; + bool unclipped = w->testAttribute(Qt::WA_PaintUnclipped); + + if((w->windowType() == Qt::Desktop)) { + if(!unclipped) + qWarning("QCoreGraphicsPaintEngine::begin: Does not support clipped desktop on Mac OS X"); + // ## need to do [qt_mac_window_for(w) makeKeyAndOrderFront]; (need to rename the file) + } else if(unclipped) { + qWarning("QCoreGraphicsPaintEngine::begin: Does not support unclipped painting"); + } + } else if(d->pdev->devType() == QInternal::Pixmap) { // device is a pixmap + QPixmap *pm = (QPixmap*)d->pdev; + if(pm->isNull()) { + qWarning("QCoreGraphicsPaintEngine::begin: Cannot paint null pixmap"); + end(); + return false; + } + } + + setDirty(QPaintEngine::DirtyPen); + setDirty(QPaintEngine::DirtyBrush); + setDirty(QPaintEngine::DirtyBackground); + setDirty(QPaintEngine::DirtyHints); + return true; +} + +bool +QCoreGraphicsPaintEngine::end() +{ + Q_D(QCoreGraphicsPaintEngine); + setActive(false); + if(d->pdev->devType() == QInternal::Widget && static_cast<QWidget*>(d->pdev)->windowType() == Qt::Desktop) { +#ifndef QT_MAC_USE_COCOA + HideWindow(qt_mac_window_for(static_cast<QWidget*>(d->pdev))); +#else +// // ### need to do [qt_mac_window_for(static_cast<QWidget *>(d->pdev)) orderOut]; (need to rename) +#endif + + } + if(d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->pdev = 0; + if(d->hd) { + d->restoreGraphicsState(); + CGContextSynchronize(d->hd); + CGContextRelease(d->hd); + d->hd = 0; + } + return true; +} + +void +QCoreGraphicsPaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QCoreGraphicsPaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + if (flags & DirtyTransform) + updateMatrix(state.transform()); + + if (flags & DirtyClipEnabled) { + if (state.isClipEnabled()) + updateClipPath(painter()->clipPath(), Qt::ReplaceClip); + else + updateClipPath(QPainterPath(), Qt::NoClip); + } + + if (flags & DirtyClipPath) { + updateClipPath(state.clipPath(), state.clipOperation()); + } else if (flags & DirtyClipRegion) { + updateClipRegion(state.clipRegion(), state.clipOperation()); + } + + // If the clip has changed we need to update all other states + // too, since they are included in the system context on OSX, + // and changing the clip resets that context back to scratch. + if (flags & (DirtyClipPath | DirtyClipRegion | DirtyClipEnabled)) + flags |= AllDirty; + + if (flags & DirtyPen) + updatePen(state.pen()); + if (flags & (DirtyBrush|DirtyBrushOrigin)) + updateBrush(state.brush(), state.brushOrigin()); + if (flags & DirtyFont) + updateFont(state.font()); + if (flags & DirtyOpacity) + updateOpacity(state.opacity()); + if (flags & DirtyHints) + updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) + updateCompositionMode(state.compositionMode()); + + if (flags & (DirtyPen | DirtyTransform)) { + if (!d->current.pen.isCosmetic()) { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticNone; + } else if (d->current.transform.m11() < d->current.transform.m22()-1.0 || + d->current.transform.m11() > d->current.transform.m22()+1.0) { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath; + d->cosmeticPenSize = d->adjustPenWidth(d->current.pen.widthF()); + if (!d->cosmeticPenSize) + d->cosmeticPenSize = 1.0; + } else { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; + static const float sqrt2 = sqrt(2); + qreal width = d->current.pen.widthF(); + if (!width) + width = 1; + d->cosmeticPenSize = sqrt(pow(d->pixelSize.y(), 2) + pow(d->pixelSize.x(), 2)) / sqrt2 * width; + } + } +} + +void +QCoreGraphicsPaintEngine::updatePen(const QPen &pen) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + d->current.pen = pen; + d->setStrokePen(pen); +} + +void +QCoreGraphicsPaintEngine::updateBrush(const QBrush &brush, const QPointF &brushOrigin) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + d->current.brush = brush; + +#ifdef QT_MAC_USE_NATIVE_GRADIENTS + // Quartz supports only pad spread + if (const QGradient *gradient = brush.gradient()) { + if (drawGradientNatively(gradient)) { + gccaps |= QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill; + } else { + gccaps &= ~(QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill); + } + } +#endif + + if (d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->setFillBrush(brushOrigin); +} + +void +QCoreGraphicsPaintEngine::updateOpacity(qreal opacity) +{ + Q_D(QCoreGraphicsPaintEngine); + CGContextSetAlpha(d->hd, opacity); +} + +void +QCoreGraphicsPaintEngine::updateFont(const QFont &) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + updatePen(d->current.pen); +} + +void +QCoreGraphicsPaintEngine::updateMatrix(const QTransform &transform) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (qt_is_nan(transform.m11()) || qt_is_nan(transform.m12()) || qt_is_nan(transform.m13()) + || qt_is_nan(transform.m21()) || qt_is_nan(transform.m22()) || qt_is_nan(transform.m23()) + || qt_is_nan(transform.m31()) || qt_is_nan(transform.m32()) || qt_is_nan(transform.m33())) + return; + + d->current.transform = transform; + d->setTransform(transform.isIdentity() ? 0 : &transform); + d->complexXForm = (transform.m11() != 1 || transform.m22() != 1 + || transform.m12() != 0 || transform.m21() != 0); + d->pixelSize = d->devicePixelSize(d->hd); +} + +void +QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + if(op == Qt::NoClip) { + if(d->current.clipEnabled) { + d->current.clipEnabled = false; + d->current.clip = QRegion(); + d->setClip(0); + } + } else { + if(!d->current.clipEnabled) + op = Qt::ReplaceClip; + d->current.clipEnabled = true; + QRegion clipRegion(p.toFillPolygon().toPolygon(), p.fillRule()); + if(op == Qt::ReplaceClip) { + d->current.clip = clipRegion; + d->setClip(0); + if(p.isEmpty()) { + CGRect rect = CGRectMake(0, 0, 0, 0); + CGContextClipToRect(d->hd, rect); + } else { + CGMutablePathRef path = qt_mac_compose_path(p); + CGContextBeginPath(d->hd); + CGContextAddPath(d->hd, path); + if(p.fillRule() == Qt::WindingFill) + CGContextClip(d->hd); + else + CGContextEOClip(d->hd); + CGPathRelease(path); + } + } else if(op == Qt::IntersectClip) { + d->current.clip = d->current.clip.intersected(clipRegion); + d->setClip(&d->current.clip); + } else if(op == Qt::UniteClip) { + d->current.clip = d->current.clip.united(clipRegion); + d->setClip(&d->current.clip); + } + } +} + +void +QCoreGraphicsPaintEngine::updateClipRegion(const QRegion &clipRegion, Qt::ClipOperation op) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + if(op == Qt::NoClip) { + d->current.clipEnabled = false; + d->current.clip = QRegion(); + d->setClip(0); + } else { + if(!d->current.clipEnabled) + op = Qt::ReplaceClip; + d->current.clipEnabled = true; + if(op == Qt::IntersectClip) + d->current.clip = d->current.clip.intersected(clipRegion); + else if(op == Qt::ReplaceClip) + d->current.clip = clipRegion; + else if(op == Qt::UniteClip) + d->current.clip = d->current.clip.united(clipRegion); + d->setClip(&d->current.clip); + } +} + +void +QCoreGraphicsPaintEngine::drawPath(const QPainterPath &p) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = qt_mac_compose_path(p); + uchar ops = QCoreGraphicsPaintEnginePrivate::CGStroke; + if(p.fillRule() == Qt::WindingFill) + ops |= QCoreGraphicsPaintEnginePrivate::CGFill; + else + ops |= QCoreGraphicsPaintEnginePrivate::CGEOFill; + CGContextBeginPath(d->hd); + d->drawPath(ops, path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + for (int i=0; i<rectCount; ++i) { + QRectF r = rects[i]; + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, 0, qt_mac_compose_rect(r)); + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill|QCoreGraphicsPaintEnginePrivate::CGStroke, + path); + CGPathRelease(path); + } +} + +void +QCoreGraphicsPaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + if (d->current.pen.capStyle() == Qt::FlatCap) + CGContextSetLineCap(d->hd, kCGLineCapSquare); + + CGMutablePathRef path = CGPathCreateMutable(); + for(int i=0; i < pointCount; i++) { + float x = points[i].x(), y = points[i].y(); + CGPathMoveToPoint(path, 0, x, y); + CGPathAddLineToPoint(path, 0, x+0.001, y); + } + + bool doRestore = false; + if(d->cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticNone && !(state->renderHints() & QPainter::Antialiasing)) { + //we don't want adjusted pens for point rendering + doRestore = true; + d->saveGraphicsState(); + CGContextSetLineWidth(d->hd, d->current.pen.widthF()); + } + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); + if (doRestore) + d->restoreGraphicsState(); + CGPathRelease(path); + if (d->current.pen.capStyle() == Qt::FlatCap) + CGContextSetLineCap(d->hd, kCGLineCapButt); +} + +void +QCoreGraphicsPaintEngine::drawEllipse(const QRectF &r) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + CGAffineTransform transform = CGAffineTransformMakeScale(r.width() / r.height(), 1); + CGPathAddArc(path, &transform,(r.x() + (r.width() / 2)) / (r.width() / r.height()), + r.y() + (r.height() / 2), r.height() / 2, 0, (2 * M_PI), false); + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill | QCoreGraphicsPaintEnginePrivate::CGStroke, + path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathMoveToPoint(path, 0, points[0].x(), points[0].y()); + for(int x = 1; x < pointCount; ++x) + CGPathAddLineToPoint(path, 0, points[x].x(), points[x].y()); + if(mode != PolylineMode && points[0] != points[pointCount-1]) + CGPathAddLineToPoint(path, 0, points[0].x(), points[0].y()); + uint op = QCoreGraphicsPaintEnginePrivate::CGStroke; + if (mode != PolylineMode) + op |= mode == OddEvenMode ? QCoreGraphicsPaintEnginePrivate::CGEOFill + : QCoreGraphicsPaintEnginePrivate::CGFill; + d->drawPath(op, path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + for(int i = 0; i < lineCount; i++) { + const QPointF start = lines[i].p1(), end = lines[i].p2(); + CGPathMoveToPoint(path, 0, start.x(), start.y()); + CGPathAddLineToPoint(path, 0, end.x(), end.y()); + } + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); + CGPathRelease(path); +} + +void QCoreGraphicsPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + if(pm.isNull()) + return; + + bool differentSize = (QRectF(0, 0, pm.width(), pm.height()) != sr), doRestore = false; + CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + QCFType<CGImageRef> image; + bool isBitmap = (pm.depth() == 1); + if (isBitmap) { + doRestore = true; + d->saveGraphicsState(); + + const QColor &col = d->current.pen.color(); + CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col, d->pdev)); + image = qt_mac_create_imagemask(pm, sr); + } else if (differentSize) { + QCFType<CGImageRef> img = pm.toMacCGImageRef(); + image = CGImageCreateWithImageInRect(img, CGRectMake(qRound(sr.x()), qRound(sr.y()), qRound(sr.width()), qRound(sr.height()))); + } else { + image = (CGImageRef)pm.macCGHandle(); + } + qt_mac_drawCGImage(d->hd, &rect, image); + if (doRestore) + d->restoreGraphicsState(); +} + +static void drawImageReleaseData (void *info, const void *, size_t) +{ + delete static_cast<QImage *>(info); +} + +CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0) +{ + QImage *image; + if (img.depth() != 32) + image = new QImage(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + else + image = new QImage(img); + + uint cgflags = kCGImageAlphaNone; + switch (image->format()) { + case QImage::Format_ARGB32_Premultiplied: + cgflags = kCGImageAlphaPremultipliedFirst; + break; + case QImage::Format_ARGB32: + cgflags = kCGImageAlphaFirst; + break; + case QImage::Format_RGB32: + cgflags = kCGImageAlphaNoneSkipFirst; + default: + break; + } +#if defined(kCGBitmapByteOrder32Host) //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(image, + static_cast<const QImage *>(image)->bits(), + image->byteCount(), + drawImageReleaseData); + if (imagePtr) + *imagePtr = image; + return CGImageCreate(image->width(), image->height(), 8, 32, + image->bytesPerLine(), + QCoreGraphicsPaintEngine::macGenericColorSpace(), + cgflags, dataProvider, 0, false, kCGRenderingIntentDefault); + +} + +void QCoreGraphicsPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_UNUSED(flags); + Q_ASSERT(isActive()); + + if (img.isNull() || state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + const QImage *image; + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(img, &image); + CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + if (QRectF(0, 0, img.width(), img.height()) != sr) + cgimage = CGImageCreateWithImageInRect(cgimage, CGRectMake(sr.x(), sr.y(), + sr.width(), sr.height())); + qt_mac_drawCGImage(d->hd, &rect, cgimage); +} + +void QCoreGraphicsPaintEngine::initialize() +{ +} + +void QCoreGraphicsPaintEngine::cleanup() +{ +} + +CGContextRef +QCoreGraphicsPaintEngine::handle() const +{ + return d_func()->hd; +} + +void +QCoreGraphicsPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, + const QPointF &p) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + //save the old state + d->saveGraphicsState(); + + //setup the pattern + QMacPattern *qpattern = new QMacPattern; + qpattern->data.pixmap = pixmap; + qpattern->foreground = d->current.pen.color(); + qpattern->pdev = d->pdev; + CGPatternCallbacks callbks; + callbks.version = 0; + callbks.drawPattern = qt_mac_draw_pattern; + callbks.releaseInfo = qt_mac_dispose_pattern; + const int width = qpattern->width(), height = qpattern->height(); + CGAffineTransform trans = CGContextGetCTM(d->hd); + CGPatternRef pat = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), + trans, width, height, + kCGPatternTilingNoDistortion, true, &callbks); + CGColorSpaceRef cs = CGColorSpaceCreatePattern(0); + CGContextSetFillColorSpace(d->hd, cs); + CGFloat component = 1.0; //just one + CGContextSetFillPattern(d->hd, pat, &component); + CGSize phase = CGSizeApplyAffineTransform(CGSizeMake(-(p.x()-r.x()), -(p.y()-r.y())), trans); + CGContextSetPatternPhase(d->hd, phase); + + //fill the rectangle + CGRect mac_rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + CGContextFillRect(d->hd, mac_rect); + + //restore the state + d->restoreGraphicsState(); + //cleanup + CGColorSpaceRelease(cs); + CGPatternRelease(pat); +} + +void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem &item) +{ + Q_D(QCoreGraphicsPaintEngine); + if (d->current.transform.type() == QTransform::TxProject +#ifndef QMAC_NATIVE_GRADIENTS + || painter()->pen().brush().gradient() //Just let the base engine "emulate" the gradient +#endif + ) { + QPaintEngine::drawTextItem(pos, item); + return; + } + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(item); + + QPen oldPen = painter()->pen(); + QBrush oldBrush = painter()->brush(); + QPointF oldBrushOrigin = painter()->brushOrigin(); + updatePen(Qt::NoPen); + updateBrush(oldPen.brush(), QPointF(0, 0)); + + Q_ASSERT(type() == QPaintEngine::CoreGraphics); + + QFontEngine *fe = ti.fontEngine; + + const bool textAA = state->renderHints() & QPainter::TextAntialiasing && fe->fontDef.pointSize > qt_antialiasing_threshold && !(fe->fontDef.styleStrategy & QFont::NoAntialias); + const bool lineAA = state->renderHints() & QPainter::Antialiasing; + if(textAA != lineAA) + CGContextSetShouldAntialias(d->hd, textAA); + + if (ti.glyphs.numGlyphs) { + switch (fe->type()) { + case QFontEngine::Mac: +#ifdef QT_MAC_USE_COCOA + static_cast<QCoreTextFontEngine *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); +#else + static_cast<QFontEngineMac *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); +#endif + break; + case QFontEngine::Box: + d->drawBoxTextItem(pos, ti); + break; + default: + break; + } + } + + if(textAA != lineAA) + CGContextSetShouldAntialias(d->hd, !textAA); + + updatePen(oldPen); + updateBrush(oldBrush, oldBrushOrigin); +} + +QPainter::RenderHints +QCoreGraphicsPaintEngine::supportedRenderHints() const +{ + return QPainter::RenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); +} +enum CGCompositeMode { + kCGCompositeModeClear = 0, + kCGCompositeModeCopy = 1, + kCGCompositeModeSourceOver = 2, + kCGCompositeModeSourceIn = 3, + kCGCompositeModeSourceOut = 4, + kCGCompositeModeSourceAtop = 5, + kCGCompositeModeDestinationOver = 6, + kCGCompositeModeDestinationIn = 7, + kCGCompositeModeDestinationOut = 8, + kCGCompositeModeDestinationAtop = 9, + kCGCompositeModeXOR = 10, + kCGCompositeModePlusDarker = 11, // (max (0, (1-d) + (1-s))) + kCGCompositeModePlusLighter = 12, // (min (1, s + d)) + }; +extern "C" { + extern void CGContextSetCompositeOperation(CGContextRef, int); +} // private function, but is in all versions of OS X. +void +QCoreGraphicsPaintEngine::updateCompositionMode(QPainter::CompositionMode mode) +{ +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + int cg_mode = kCGBlendModeNormal; + switch(mode) { + case QPainter::CompositionMode_Multiply: + cg_mode = kCGBlendModeMultiply; + break; + case QPainter::CompositionMode_Screen: + cg_mode = kCGBlendModeScreen; + break; + case QPainter::CompositionMode_Overlay: + cg_mode = kCGBlendModeOverlay; + break; + case QPainter::CompositionMode_Darken: + cg_mode = kCGBlendModeDarken; + break; + case QPainter::CompositionMode_Lighten: + cg_mode = kCGBlendModeLighten; + break; + case QPainter::CompositionMode_ColorDodge: + cg_mode = kCGBlendModeColorDodge; + break; + case QPainter::CompositionMode_ColorBurn: + cg_mode = kCGBlendModeColorBurn; + break; + case QPainter::CompositionMode_HardLight: + cg_mode = kCGBlendModeHardLight; + break; + case QPainter::CompositionMode_SoftLight: + cg_mode = kCGBlendModeSoftLight; + break; + case QPainter::CompositionMode_Difference: + cg_mode = kCGBlendModeDifference; + break; + case QPainter::CompositionMode_Exclusion: + cg_mode = kCGBlendModeExclusion; + break; + case QPainter::CompositionMode_Plus: + cg_mode = kCGBlendModePlusLighter; + break; + case QPainter::CompositionMode_SourceOver: + cg_mode = kCGBlendModeNormal; + break; + case QPainter::CompositionMode_DestinationOver: + cg_mode = kCGBlendModeDestinationOver; + break; + case QPainter::CompositionMode_Clear: + cg_mode = kCGBlendModeClear; + break; + case QPainter::CompositionMode_Source: + cg_mode = kCGBlendModeCopy; + break; + case QPainter::CompositionMode_Destination: + cg_mode = -1; + break; + case QPainter::CompositionMode_SourceIn: + cg_mode = kCGBlendModeSourceIn; + break; + case QPainter::CompositionMode_DestinationIn: + cg_mode = kCGCompositeModeDestinationIn; + break; + case QPainter::CompositionMode_SourceOut: + cg_mode = kCGBlendModeSourceOut; + break; + case QPainter::CompositionMode_DestinationOut: + cg_mode = kCGBlendModeDestinationOver; + break; + case QPainter::CompositionMode_SourceAtop: + cg_mode = kCGBlendModeSourceAtop; + break; + case QPainter::CompositionMode_DestinationAtop: + cg_mode = kCGBlendModeDestinationAtop; + break; + case QPainter::CompositionMode_Xor: + cg_mode = kCGBlendModeXOR; + break; + default: + break; + } + if (cg_mode > -1) { + CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode)); + } + } else +#endif + // The standard porter duff ops. + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_3 + && mode <= QPainter::CompositionMode_Xor) { + int cg_mode = kCGCompositeModeCopy; + switch (mode) { + case QPainter::CompositionMode_SourceOver: + cg_mode = kCGCompositeModeSourceOver; + break; + case QPainter::CompositionMode_DestinationOver: + cg_mode = kCGCompositeModeDestinationOver; + break; + case QPainter::CompositionMode_Clear: + cg_mode = kCGCompositeModeClear; + break; + default: + qWarning("QCoreGraphicsPaintEngine: Unhandled composition mode %d", (int)mode); + break; + case QPainter::CompositionMode_Source: + cg_mode = kCGCompositeModeCopy; + break; + case QPainter::CompositionMode_Destination: + cg_mode = CGCompositeMode(-1); + break; + case QPainter::CompositionMode_SourceIn: + cg_mode = kCGCompositeModeSourceIn; + break; + case QPainter::CompositionMode_DestinationIn: + cg_mode = kCGCompositeModeDestinationIn; + break; + case QPainter::CompositionMode_SourceOut: + cg_mode = kCGCompositeModeSourceOut; + break; + case QPainter::CompositionMode_DestinationOut: + cg_mode = kCGCompositeModeDestinationOut; + break; + case QPainter::CompositionMode_SourceAtop: + cg_mode = kCGCompositeModeSourceAtop; + break; + case QPainter::CompositionMode_DestinationAtop: + cg_mode = kCGCompositeModeDestinationAtop; + break; + case QPainter::CompositionMode_Xor: + cg_mode = kCGCompositeModeXOR; + break; + } + if (cg_mode > -1) + CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode)); + } else { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + bool needPrivateAPI = false; + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + int cg_mode = kCGBlendModeNormal; + switch (mode) { + case QPainter::CompositionMode_Multiply: + cg_mode = kCGBlendModeMultiply; + break; + case QPainter::CompositionMode_Screen: + cg_mode = kCGBlendModeScreen; + break; + case QPainter::CompositionMode_Overlay: + cg_mode = kCGBlendModeOverlay; + break; + case QPainter::CompositionMode_Darken: + cg_mode = kCGBlendModeDarken; + break; + case QPainter::CompositionMode_Lighten: + cg_mode = kCGBlendModeLighten; + break; + case QPainter::CompositionMode_ColorDodge: + cg_mode = kCGBlendModeColorDodge; + break; + case QPainter::CompositionMode_ColorBurn: + cg_mode = kCGBlendModeColorBurn; + break; + case QPainter::CompositionMode_HardLight: + cg_mode = kCGBlendModeHardLight; + break; + case QPainter::CompositionMode_SoftLight: + cg_mode = kCGBlendModeSoftLight; + break; + case QPainter::CompositionMode_Difference: + cg_mode = kCGBlendModeDifference; + break; + case QPainter::CompositionMode_Exclusion: + cg_mode = kCGBlendModeExclusion; + break; + case QPainter::CompositionMode_Plus: + needPrivateAPI = true; + cg_mode = kCGCompositeModePlusLighter; + break; + default: + break; + } + if (!needPrivateAPI) + CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode)); + else + CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode)); + } +#endif + } +} + +void +QCoreGraphicsPaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QCoreGraphicsPaintEngine); + CGContextSetShouldAntialias(d->hd, hints & QPainter::Antialiasing); + static const CGFloat ScaleFactor = qt_mac_get_scalefactor(); + if (ScaleFactor > 1.) { + CGContextSetInterpolationQuality(d->hd, kCGInterpolationHigh); + } else { + CGContextSetInterpolationQuality(d->hd, (hints & QPainter::SmoothPixmapTransform) ? + kCGInterpolationHigh : kCGInterpolationNone); + } + bool textAntialiasing = (hints & QPainter::TextAntialiasing) == QPainter::TextAntialiasing; + if (!textAntialiasing || d->disabledSmoothFonts) { + d->disabledSmoothFonts = !textAntialiasing; + CGContextSetShouldSmoothFonts(d->hd, textAntialiasing); + } +} + +/* + Returns the size of one device pixel in user-space coordinates. +*/ +QPointF QCoreGraphicsPaintEnginePrivate::devicePixelSize(CGContextRef) +{ + QPointF p1 = current.transform.inverted().map(QPointF(0, 0)); + QPointF p2 = current.transform.inverted().map(QPointF(1, 1)); + return QPointF(qAbs(p2.x() - p1.x()), qAbs(p2.y() - p1.y())); +} + +/* + Adjusts the pen width so we get correct line widths in the + non-transformed, aliased case. +*/ +float QCoreGraphicsPaintEnginePrivate::adjustPenWidth(float penWidth) +{ + Q_Q(QCoreGraphicsPaintEngine); + float ret = penWidth; + if (!complexXForm && !(q->state->renderHints() & QPainter::Antialiasing)) { + if (penWidth < 2) + ret = 1; + else if (penWidth < 3) + ret = 1.5; + else + ret = penWidth -1; + } + return ret; +} + +void +QCoreGraphicsPaintEnginePrivate::setStrokePen(const QPen &pen) +{ + //pencap + CGLineCap cglinecap = kCGLineCapButt; + if(pen.capStyle() == Qt::SquareCap) + cglinecap = kCGLineCapSquare; + else if(pen.capStyle() == Qt::RoundCap) + cglinecap = kCGLineCapRound; + CGContextSetLineCap(hd, cglinecap); + CGContextSetLineWidth(hd, adjustPenWidth(pen.widthF())); + + //join + CGLineJoin cglinejoin = kCGLineJoinMiter; + if(pen.joinStyle() == Qt::BevelJoin) + cglinejoin = kCGLineJoinBevel; + else if(pen.joinStyle() == Qt::RoundJoin) + cglinejoin = kCGLineJoinRound; + CGContextSetLineJoin(hd, cglinejoin); +// CGContextSetMiterLimit(hd, pen.miterLimit()); + + //pen style + QVector<CGFloat> linedashes; + if(pen.style() == Qt::CustomDashLine) { + QVector<qreal> customs = pen.dashPattern(); + for(int i = 0; i < customs.size(); ++i) + linedashes.append(customs.at(i)); + } else if(pen.style() == Qt::DashLine) { + linedashes.append(4); + linedashes.append(2); + } else if(pen.style() == Qt::DotLine) { + linedashes.append(1); + linedashes.append(2); + } else if(pen.style() == Qt::DashDotLine) { + linedashes.append(4); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + } else if(pen.style() == Qt::DashDotDotLine) { + linedashes.append(4); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + } + const CGFloat cglinewidth = pen.widthF() <= 0.0f ? 1.0f : float(pen.widthF()); + for(int i = 0; i < linedashes.size(); ++i) { + linedashes[i] *= cglinewidth; + if(cglinewidth < 3 && (cglinecap == kCGLineCapSquare || cglinecap == kCGLineCapRound)) { + if((i%2)) + linedashes[i] += cglinewidth/2; + else + linedashes[i] -= cglinewidth/2; + } + } + CGContextSetLineDash(hd, pen.dashOffset() * cglinewidth, linedashes.data(), linedashes.size()); + + // color + CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color(), pdev)); +} + +// Add our own patterns here to deal with the fact that the coordinate system +// is flipped vertically with Quartz2D. +static const uchar *qt_mac_patternForBrush(int brushStyle) +{ + Q_ASSERT(brushStyle > Qt::SolidPattern && brushStyle < Qt::LinearGradientPattern); + static const uchar dense1_pat[] = { 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00 }; + static const uchar dense2_pat[] = { 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88 }; + static const uchar dense3_pat[] = { 0x11, 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa }; + static const uchar dense4_pat[] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; + static const uchar dense5_pat[] = { 0xee, 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55 }; + static const uchar dense6_pat[] = { 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77 }; + static const uchar dense7_pat[] = { 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff }; + static const uchar hor_pat[] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff }; + static const uchar ver_pat[] = { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }; + static const uchar cross_pat[] = { 0xef, 0xef, 0xef, 0xef, 0x00, 0xef, 0xef, 0xef }; + static const uchar fdiag_pat[] = { 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe }; + static const uchar bdiag_pat[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f }; + static const uchar dcross_pat[] = { 0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e }; + static const uchar *const pat_tbl[] = { + dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat, + dense6_pat, dense7_pat, + hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat }; + return pat_tbl[brushStyle - Qt::Dense1Pattern]; +} + +void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) +{ + // pattern + Qt::BrushStyle bs = current.brush.style(); +#ifdef QT_MAC_USE_NATIVE_GRADIENTS + if (bs == Qt::LinearGradientPattern || bs == Qt::RadialGradientPattern) { + const QGradient *grad = static_cast<const QGradient*>(current.brush.gradient()); + if (drawGradientNatively(grad)) { + Q_ASSERT(grad->spread() == QGradient::PadSpread); + + static const CGFloat domain[] = { 0.0f, +1.0f }; + static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, 0 }; + CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast<void *>(¤t.brush), + 1, domain, 4, 0, &callbacks); + + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + if (bs == Qt::LinearGradientPattern) { + const QLinearGradient *linearGrad = static_cast<const QLinearGradient *>(grad); + const QPointF start(linearGrad->start()); + const QPointF stop(linearGrad->finalStop()); + shading = CGShadingCreateAxial(colorspace, CGPointMake(start.x(), start.y()), + CGPointMake(stop.x(), stop.y()), fill_func, true, true); + } else { + Q_ASSERT(bs == Qt::RadialGradientPattern); + const QRadialGradient *radialGrad = static_cast<const QRadialGradient *>(grad); + QPointF center(radialGrad->center()); + QPointF focal(radialGrad->focalPoint()); + qreal radius = radialGrad->radius(); + shading = CGShadingCreateRadial(colorspace, CGPointMake(focal.x(), focal.y()), + 0.0, CGPointMake(center.x(), center.y()), radius, fill_func, false, true); + } + + CGFunctionRelease(fill_func); + } + } else +#endif + if(bs != Qt::SolidPattern && bs != Qt::NoBrush +#ifndef QT_MAC_USE_NATIVE_GRADIENTS + && (bs < Qt::LinearGradientPattern || bs > Qt::ConicalGradientPattern) +#endif + ) + { + QMacPattern *qpattern = new QMacPattern; + qpattern->pdev = pdev; + CGFloat components[4] = { 1.0, 1.0, 1.0, 1.0 }; + CGColorSpaceRef base_colorspace = 0; + if(bs == Qt::TexturePattern) { + qpattern->data.pixmap = current.brush.texture(); + if(qpattern->data.pixmap.isQBitmap()) { + const QColor &col = current.brush.color(); + components[0] = qt_mac_convert_color_to_cg(col.red()); + components[1] = qt_mac_convert_color_to_cg(col.green()); + components[2] = qt_mac_convert_color_to_cg(col.blue()); + base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + } + } else { + qpattern->as_mask = true; + + qpattern->data.bytes = qt_mac_patternForBrush(bs); + const QColor &col = current.brush.color(); + components[0] = qt_mac_convert_color_to_cg(col.red()); + components[1] = qt_mac_convert_color_to_cg(col.green()); + components[2] = qt_mac_convert_color_to_cg(col.blue()); + base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + } + int width = qpattern->width(), height = qpattern->height(); + qpattern->foreground = current.brush.color(); + + CGColorSpaceRef fill_colorspace = CGColorSpaceCreatePattern(base_colorspace); + CGContextSetFillColorSpace(hd, fill_colorspace); + + CGAffineTransform xform = CGContextGetCTM(hd); + xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(current.brush.transform()), xform); + xform = CGAffineTransformTranslate(xform, offset.x(), offset.y()); + + CGPatternCallbacks callbks; + callbks.version = 0; + callbks.drawPattern = qt_mac_draw_pattern; + callbks.releaseInfo = qt_mac_dispose_pattern; + CGPatternRef fill_pattern = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), + xform, width, height, kCGPatternTilingNoDistortion, + !base_colorspace, &callbks); + CGContextSetFillPattern(hd, fill_pattern, components); + + CGPatternRelease(fill_pattern); + CGColorSpaceRelease(fill_colorspace); + } else if(bs != Qt::NoBrush) { + CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color(), pdev)); + } +} + +void +QCoreGraphicsPaintEnginePrivate::setClip(const QRegion *rgn) +{ + Q_Q(QCoreGraphicsPaintEngine); + if(hd) { + resetClip(); + QRegion sysClip = q->systemClip(); + if(!sysClip.isEmpty()) + qt_mac_clip_cg(hd, sysClip, &orig_xform); + if(rgn) + qt_mac_clip_cg(hd, *rgn, 0); + } +} + +struct qt_mac_cg_transform_path { + CGMutablePathRef path; + CGAffineTransform transform; +}; + +void qt_mac_cg_transform_path_apply(void *info, const CGPathElement *element) +{ + Q_ASSERT(info && element); + qt_mac_cg_transform_path *t = (qt_mac_cg_transform_path*)info; + switch(element->type) { + case kCGPathElementMoveToPoint: + CGPathMoveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); + break; + case kCGPathElementAddLineToPoint: + CGPathAddLineToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); + break; + case kCGPathElementAddQuadCurveToPoint: + CGPathAddQuadCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, + element->points[1].x, element->points[1].y); + break; + case kCGPathElementAddCurveToPoint: + CGPathAddCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, + element->points[1].x, element->points[1].y, + element->points[2].x, element->points[2].y); + break; + case kCGPathElementCloseSubpath: + CGPathCloseSubpath(t->path); + break; + default: + qDebug() << "Unhandled path transform type: " << element->type; + } +} + +void QCoreGraphicsPaintEnginePrivate::drawPath(uchar ops, CGMutablePathRef path) +{ + Q_Q(QCoreGraphicsPaintEngine); + Q_ASSERT((ops & (CGFill | CGEOFill)) != (CGFill | CGEOFill)); //can't really happen + if((ops & (CGFill | CGEOFill))) { + if (shading) { + Q_ASSERT(path); + CGContextBeginPath(hd); + CGContextAddPath(hd, path); + saveGraphicsState(); + if (ops & CGFill) + CGContextClip(hd); + else if (ops & CGEOFill) + CGContextEOClip(hd); + if (current.brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) { + CGRect boundingBox = CGPathGetBoundingBox(path); + CGContextConcatCTM(hd, + CGAffineTransformMake(boundingBox.size.width, 0, + 0, boundingBox.size.height, + boundingBox.origin.x, boundingBox.origin.y)); + } + CGContextDrawShading(hd, shading); + restoreGraphicsState(); + ops &= ~CGFill; + ops &= ~CGEOFill; + } else if (current.brush.style() == Qt::NoBrush) { + ops &= ~CGFill; + ops &= ~CGEOFill; + } + } + if((ops & CGStroke) && current.pen.style() == Qt::NoPen) + ops &= ~CGStroke; + + if(ops & (CGEOFill | CGFill)) { + CGContextBeginPath(hd); + CGContextAddPath(hd, path); + if (ops & CGEOFill) { + CGContextEOFillPath(hd); + } else { + CGContextFillPath(hd); + } + } + + // Avoid saving and restoring the context if we can. + const bool needContextSave = (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone || + !(q->state->renderHints() & QPainter::Antialiasing)); + if(ops & CGStroke) { + if (needContextSave) + saveGraphicsState(); + CGContextBeginPath(hd); + + // Translate a fraction of a pixel size in the y direction + // to make sure that primitives painted at pixel borders + // fills the right pixel. This is needed since the y xais + // in the Quartz coordinate system is inverted compared to Qt. + if (!(q->state->renderHints() & QPainter::Antialiasing)) { + if (current.pen.style() == Qt::SolidLine || current.pen.width() >= 3) + CGContextTranslateCTM(hd, double(pixelSize.x()) * 0.25, double(pixelSize.y()) * 0.25); + else if (current.pen.style() == Qt::DotLine && QSysInfo::MacintoshVersion == QSysInfo::MV_10_3) + ; // Do nothing. + else + CGContextTranslateCTM(hd, 0, double(pixelSize.y()) * 0.1); + } + + if (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone) { + // If antialiazing is enabled, use the cosmetic pen size directly. + if (q->state->renderHints() & QPainter::Antialiasing) + CGContextSetLineWidth(hd, cosmeticPenSize); + else if (current.pen.widthF() <= 1) + CGContextSetLineWidth(hd, cosmeticPenSize * 0.9f); + else + CGContextSetLineWidth(hd, cosmeticPenSize); + } + if(cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath) { + qt_mac_cg_transform_path t; + t.transform = qt_mac_convert_transform_to_cg(current.transform); + t.path = CGPathCreateMutable(); + CGPathApply(path, &t, qt_mac_cg_transform_path_apply); //transform the path + setTransform(0); //unset the context transform + CGContextSetLineWidth(hd, cosmeticPenSize); + CGContextAddPath(hd, t.path); + CGPathRelease(t.path); + } else { + CGContextAddPath(hd, path); + } + + CGContextStrokePath(hd); + if (needContextSave) + restoreGraphicsState(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine_mac_p.h b/src/gui/painting/qpaintengine_mac_p.h new file mode 100644 index 0000000000..3e71d6ca6b --- /dev/null +++ b/src/gui/painting/qpaintengine_mac_p.h @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_MAC_P_H +#define QPAINTENGINE_MAC_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 "QtGui/qpaintengine.h" +#include "private/qt_mac_p.h" +#include "private/qpaintengine_p.h" +#include "private/qpolygonclipper_p.h" +#include "private/qfont_p.h" +#include "QtCore/qhash.h" + +typedef struct CGColorSpace *CGColorSpaceRef; +QT_BEGIN_NAMESPACE + +class QCoreGraphicsPaintEnginePrivate; +class QCoreGraphicsPaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QCoreGraphicsPaintEngine) + +public: + QCoreGraphicsPaintEngine(); + ~QCoreGraphicsPaintEngine(); + + bool begin(QPaintDevice *pdev); + bool end(); + static CGColorSpaceRef macGenericColorSpace(); + static CGColorSpaceRef macDisplayColorSpace(const QWidget *widget = 0); + + void updateState(const QPaintEngineState &state); + + void updatePen(const QPen &pen); + void updateBrush(const QBrush &brush, const QPointF &pt); + void updateFont(const QFont &font); + void updateOpacity(qreal opacity); + void updateMatrix(const QTransform &matrix); + void updateTransform(const QTransform &matrix); + void updateClipRegion(const QRegion ®ion, Qt::ClipOperation op); + void updateClipPath(const QPainterPath &path, Qt::ClipOperation op); + void updateCompositionMode(QPainter::CompositionMode mode); + void updateRenderHints(QPainter::RenderHints hints); + + void drawLines(const QLineF *lines, int lineCount); + void drawRects(const QRectF *rects, int rectCount); + void drawPoints(const QPointF *p, int pointCount); + void drawEllipse(const QRectF &r); + void drawPath(const QPainterPath &path); + + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + + void drawTextItem(const QPointF &pos, const QTextItem &item); + void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + + Type type() const { return QPaintEngine::CoreGraphics; } + + CGContextRef handle() const; + + static void initialize(); + static void cleanup(); + + QPainter::RenderHints supportedRenderHints() const; + + //avoid partial shadowed overload warnings... + void drawLines(const QLine *lines, int lineCount) { QPaintEngine::drawLines(lines, lineCount); } + void drawRects(const QRect *rects, int rectCount) { QPaintEngine::drawRects(rects, rectCount); } + void drawPoints(const QPoint *p, int pointCount) { QPaintEngine::drawPoints(p, pointCount); } + void drawEllipse(const QRect &r) { QPaintEngine::drawEllipse(r); } + void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) + { QPaintEngine::drawPolygon(points, pointCount, mode); } + +protected: + friend class QMacPrintEngine; + friend class QMacPrintEnginePrivate; + friend void qt_mac_display_change_callbk(CGDirectDisplayID, CGDisplayChangeSummaryFlags, void *); + friend void qt_color_profile_changed(CFNotificationCenterRef center, void *, + CFStringRef , const void *, CFDictionaryRef); + QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr); + +private: + static bool m_postRoutineRegistered; + static CGColorSpaceRef m_genericColorSpace; + static QHash<CGDirectDisplayID, CGColorSpaceRef> m_displayColorSpaceHash; + static void cleanUpMacColorSpaces(); + Q_DISABLE_COPY(QCoreGraphicsPaintEngine) +}; + +/***************************************************************************** + Private data + *****************************************************************************/ +class QCoreGraphicsPaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QCoreGraphicsPaintEngine) +public: + QCoreGraphicsPaintEnginePrivate() + : hd(0), shading(0), stackCount(0), complexXForm(false), disabledSmoothFonts(false) + { + } + + struct { + QPen pen; + QBrush brush; + uint clipEnabled : 1; + QRegion clip; + QTransform transform; + } current; + + //state info (shared with QD) + CGAffineTransform orig_xform; + + //cg structures + CGContextRef hd; + CGShadingRef shading; + int stackCount; + bool complexXForm; + bool disabledSmoothFonts; + enum { CosmeticNone, CosmeticTransformPath, CosmeticSetPenWidth } cosmeticPen; + + // pixel and cosmetic pen size in user coordinates. + QPointF pixelSize; + float cosmeticPenSize; + + //internal functions + enum { CGStroke=0x01, CGEOFill=0x02, CGFill=0x04 }; + void drawPath(uchar ops, CGMutablePathRef path = 0); + void setClip(const QRegion *rgn=0); + void resetClip(); + void setFillBrush(const QPointF &origin=QPoint()); + void setStrokePen(const QPen &pen); + inline void saveGraphicsState(); + inline void restoreGraphicsState(); + float penOffset(); + QPointF devicePixelSize(CGContextRef context); + float adjustPenWidth(float penWidth); + inline void setTransform(const QTransform *matrix=0) + { + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGAffineTransform xform = orig_xform; + if(matrix) { + extern CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &); + xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(*matrix), xform); + } + CGContextConcatCTM(hd, xform); + CGContextSetTextMatrix(hd, xform); + } +}; + +inline void QCoreGraphicsPaintEnginePrivate::saveGraphicsState() +{ + ++stackCount; + CGContextSaveGState(hd); +} + +inline void QCoreGraphicsPaintEnginePrivate::restoreGraphicsState() +{ + --stackCount; + Q_ASSERT(stackCount >= 0); + CGContextRestoreGState(hd); +} + +class QMacQuartzPaintDevice : public QPaintDevice +{ +public: + QMacQuartzPaintDevice(CGContextRef cg, int width, int height, int bytesPerLine) + : mCG(cg), mWidth(width), mHeight(height), mBytesPerLine(bytesPerLine) + { } + int devType() const { return QInternal::MacQuartz; } + CGContextRef cgContext() const { return mCG; } + int metric(PaintDeviceMetric metric) const { + switch (metric) { + case PdmWidth: + return mWidth; + case PdmHeight: + return mHeight; + case PdmWidthMM: + return (qt_defaultDpiX() * mWidth) / 2.54; + case PdmHeightMM: + return (qt_defaultDpiY() * mHeight) / 2.54; + case PdmNumColors: + return 0; + case PdmDepth: + return 32; + case PdmDpiX: + case PdmPhysicalDpiX: + return qt_defaultDpiX(); + case PdmDpiY: + case PdmPhysicalDpiY: + return qt_defaultDpiY(); + } + return 0; + } + QPaintEngine *paintEngine() const { qWarning("This function should never be called."); return 0; } +private: + CGContextRef mCG; + int mWidth; + int mHeight; + int mBytesPerLine; +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_MAC_P_H diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h new file mode 100644 index 0000000000..d6aaffc254 --- /dev/null +++ b/src/gui/painting/qpaintengine_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_P_H +#define QPAINTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qpainter.h" +#include "QtGui/qpaintengine.h" +#include "QtGui/qregion.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QPaintDevice; + +class QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QPaintEngine) +public: + QPaintEnginePrivate() : pdev(0), q_ptr(0), currentClipWidget(0), hasSystemTransform(0), + hasSystemViewport(0) {} + virtual ~QPaintEnginePrivate() { } + QPaintDevice *pdev; + QPaintEngine *q_ptr; + QRegion systemClip; + QRect systemRect; + QRegion systemViewport; + QTransform systemTransform; + QWidget *currentClipWidget; + uint hasSystemTransform : 1; + uint hasSystemViewport : 1; + + inline void transformSystemClip() + { + if (systemClip.isEmpty()) + return; + + if (hasSystemTransform) { + if (systemTransform.type() <= QTransform::TxTranslate) + systemClip.translate(qRound(systemTransform.dx()), qRound(systemTransform.dy())); + else + systemClip = systemTransform.map(systemClip); + } + + // Make sure we're inside the viewport. + if (hasSystemViewport) { + systemClip &= systemViewport; + if (systemClip.isEmpty()) { + // We don't want to paint without system clip, so set it to 1 pixel :) + systemClip = QRect(systemViewport.boundingRect().topLeft(), QSize(1, 1)); + } + } + } + + inline void setSystemTransform(const QTransform &xform) + { + systemTransform = xform; + if ((hasSystemTransform = !xform.isIdentity()) || hasSystemViewport) + transformSystemClip(); + systemStateChanged(); + } + + inline void setSystemViewport(const QRegion ®ion) + { + systemViewport = region; + hasSystemViewport = !systemViewport.isEmpty(); + } + + virtual void systemStateChanged() { } + + void drawBoxTextItem(const QPointF &p, const QTextItemInt &ti); +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_P_H diff --git a/src/gui/painting/qpaintengine_preview.cpp b/src/gui/painting/qpaintengine_preview.cpp new file mode 100644 index 0000000000..99fe4f7fcc --- /dev/null +++ b/src/gui/painting/qpaintengine_preview.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qpaintengine_preview_p.h> +#include <private/qpainter_p.h> +#include <private/qpaintengine_p.h> +#include <private/qpicture_p.h> + +#include <QtGui/qprintengine.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpicture.h> + +#ifndef QT_NO_PRINTPREVIEWWIDGET +QT_BEGIN_NAMESPACE + +class QPreviewPaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QPreviewPaintEngine) +public: + QPreviewPaintEnginePrivate() : state(QPrinter::Idle) {} + ~QPreviewPaintEnginePrivate() {} + + QList<const QPicture *> pages; + QPaintEngine *engine; + QPainter *painter; + QPrinter::PrinterState state; + + QPaintEngine *proxy_paint_engine; + QPrintEngine *proxy_print_engine; +}; + + +QPreviewPaintEngine::QPreviewPaintEngine() + : QPaintEngine(*(new QPreviewPaintEnginePrivate), PaintEngineFeatures(AllFeatures & ~ObjectBoundingModeGradients)) +{ + Q_D(QPreviewPaintEngine); + d->proxy_print_engine = 0; + d->proxy_paint_engine = 0; +} + +QPreviewPaintEngine::~QPreviewPaintEngine() +{ + Q_D(QPreviewPaintEngine); + + qDeleteAll(d->pages); +} + +bool QPreviewPaintEngine::begin(QPaintDevice *) +{ + Q_D(QPreviewPaintEngine); + + qDeleteAll(d->pages); + d->pages.clear(); + + QPicture *page = new QPicture; + page->d_func()->in_memory_only = true; + d->painter = new QPainter(page); + d->engine = d->painter->paintEngine(); + d->pages.append(page); + d->state = QPrinter::Active; + return true; +} + +bool QPreviewPaintEngine::end() +{ + Q_D(QPreviewPaintEngine); + + delete d->painter; + d->painter = 0; + d->engine = 0; + d->state = QPrinter::Idle; + return true; +} + +void QPreviewPaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QPreviewPaintEngine); + d->engine->updateState(state); +} + +void QPreviewPaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QPreviewPaintEngine); + d->engine->drawPath(path); +} + +void QPreviewPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QPreviewPaintEngine); + d->engine->drawPolygon(points, pointCount, mode); +} + +void QPreviewPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QPreviewPaintEngine); + d->engine->drawTextItem(p, textItem); +} + +void QPreviewPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QPreviewPaintEngine); + d->engine->drawPixmap(r, pm, sr); +} + +void QPreviewPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &p) +{ + Q_D(QPreviewPaintEngine); + d->engine->drawTiledPixmap(r, pm, p); +} + +bool QPreviewPaintEngine::newPage() +{ + Q_D(QPreviewPaintEngine); + + QPicture *page = new QPicture; + page->d_func()->in_memory_only = true; + QPainter *tmp_painter = new QPainter(page); + QPaintEngine *tmp_engine = tmp_painter->paintEngine(); + + // copy the painter state from the original painter + Q_ASSERT(painter()->d_func()->state && tmp_painter->d_func()->state); + *tmp_painter->d_func()->state = *painter()->d_func()->state; + + // composition modes aren't supported on a QPrinter and yields a + // warning, so ignore it for now + tmp_engine->setDirty(DirtyFlags(AllDirty & ~DirtyCompositionMode)); + tmp_engine->syncState(); + + delete d->painter; + d->painter = tmp_painter; + d->pages.append(page); + d->engine = tmp_engine; + return true; +} + +bool QPreviewPaintEngine::abort() +{ + Q_D(QPreviewPaintEngine); + end(); + qDeleteAll(d->pages); + d->state = QPrinter::Aborted; + + return true; +} + +QList<const QPicture *> QPreviewPaintEngine::pages() +{ + Q_D(QPreviewPaintEngine); + return d->pages; +} + +void QPreviewPaintEngine::setProxyEngines(QPrintEngine *printEngine, QPaintEngine *paintEngine) +{ + Q_D(QPreviewPaintEngine); + d->proxy_print_engine = printEngine; + d->proxy_paint_engine = paintEngine; +} + +void QPreviewPaintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QPreviewPaintEngine); + d->proxy_print_engine->setProperty(key, value); +} + +QVariant QPreviewPaintEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QPreviewPaintEngine); + return d->proxy_print_engine->property(key); +} + +int QPreviewPaintEngine::metric(QPaintDevice::PaintDeviceMetric id) const +{ + Q_D(const QPreviewPaintEngine); + return d->proxy_print_engine->metric(id); +} + +QPrinter::PrinterState QPreviewPaintEngine::printerState() const +{ + Q_D(const QPreviewPaintEngine); + return d->state; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qpaintengine_preview_p.h b/src/gui/painting/qpaintengine_preview_p.h new file mode 100644 index 0000000000..0101aa0073 --- /dev/null +++ b/src/gui/painting/qpaintengine_preview_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPREVIEWPAINTENGINE_P_H +#define QPREVIEWPAINTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of QPreviewPrinter and friends. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// +// + +#include <QtGui/qpaintengine.h> +#include <QtGui/qprintengine.h> + +#ifndef QT_NO_PRINTPREVIEWWIDGET + +QT_BEGIN_NAMESPACE + +class QPreviewPaintEnginePrivate; + +class QPreviewPaintEngine : public QPaintEngine, public QPrintEngine +{ + Q_DECLARE_PRIVATE(QPreviewPaintEngine) +public: + QPreviewPaintEngine(); + ~QPreviewPaintEngine(); + + bool begin(QPaintDevice *dev); + bool end(); + + void updateState(const QPaintEngineState &state); + + void drawPath(const QPainterPath &path); + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawTextItem(const QPointF &p, const QTextItem &textItem); + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &p); + + QList<const QPicture *> pages(); + + QPaintEngine::Type type() const { return Picture; } + + void setProxyEngines(QPrintEngine *printEngine, QPaintEngine *paintEngine); + + void setProperty(PrintEnginePropertyKey key, const QVariant &value); + QVariant property(PrintEnginePropertyKey key) const; + + bool newPage(); + bool abort(); + + int metric(QPaintDevice::PaintDeviceMetric) const; + + QPrinter::PrinterState printerState() const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTPREVIEWWIDGET + +#endif diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp new file mode 100644 index 0000000000..69025436c2 --- /dev/null +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -0,0 +1,6325 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> +#include <QtCore/qmutex.h> + +#define QT_FT_BEGIN_HEADER +#define QT_FT_END_HEADER + +#include <private/qrasterdefs_p.h> +#include <private/qgrayraster_p.h> + +#include <qpainterpath.h> +#include <qdebug.h> +#include <qhash.h> +#include <qlabel.h> +#include <qbitmap.h> +#include <qmath.h> + +#if defined (Q_WS_X11) +# include <private/qfontengine_ft_p.h> +#endif + +// #include <private/qdatabuffer_p.h> +// #include <private/qpainter_p.h> +#include <private/qmath_p.h> +#include <private/qtextengine_p.h> +#include <private/qfontengine_p.h> +#include <private/qpixmap_raster_p.h> +// #include <private/qpolygonclipper_p.h> +// #include <private/qrasterizer_p.h> +#include <private/qimage_p.h> +#include <private/qstatictext_p.h> +#include "qmemrotate_p.h" + +#include "qpaintengine_raster_p.h" +// #include "qbezier_p.h" +#include "qoutlinemapper_p.h" + +#if defined(Q_WS_WIN) +# include <qt_windows.h> +# include <qvarlengtharray.h> +# include <private/qfontengine_p.h> +# if defined(Q_OS_WINCE) +# include "qguifunctions_wince.h" +# endif +#elif defined(Q_WS_MAC) +# include <private/qt_mac_p.h> +# include <private/qpixmap_mac_p.h> +# include <private/qpaintengine_mac_p.h> +#elif defined(Q_WS_QWS) +# if !defined(QT_NO_FREETYPE) +# include <private/qfontengine_ft_p.h> +# endif +# if !defined(QT_NO_QWS_QPF2) +# include <private/qfontengine_qpf_p.h> +# endif +# include <private/qabstractfontengine_p.h> +#elif defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) +# include <private/qfontengine_s60_p.h> +#elif defined(Q_WS_QPA) +# include <private/qfontengine_ft_p.h> +#endif + +#if defined(Q_WS_WIN64) +# include <malloc.h> +#endif +#include <limits.h> + +QT_BEGIN_NAMESPACE + +Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp + +#define qreal_to_fixed_26_6(f) (int(f * 64)) +#define qt_swap_int(x, y) { int tmp = (x); (x) = (y); (y) = tmp; } +#define qt_swap_qreal(x, y) { qreal tmp = (x); (x) = (y); (y) = tmp; } + +// #define QT_DEBUG_DRAW +#ifdef QT_DEBUG_DRAW +void dumpClip(int width, int height, const QClipData *clip); +#endif + +#define QT_FAST_SPANS + + +// A little helper macro to get a better approximation of dimensions. +// If we have a rect that starting at 0.5 of width 3.5 it should span +// 4 pixels. +#define int_dim(pos, dim) (int(pos+dim) - int(pos)) + +#ifdef Q_WS_WIN +extern bool qt_cleartype_enabled; +#endif + +#ifdef Q_WS_MAC +extern bool qt_applefontsmoothing_enabled; +#endif + + +/******************************************************************************** + * Span functions + */ +static void qt_span_fill_clipRect(int count, const QSpan *spans, void *userData); +static void qt_span_fill_clipped(int count, const QSpan *spans, void *userData); +static void qt_span_clip(int count, const QSpan *spans, void *userData); +static void qt_merge_clip(const QClipData *c1, const QClipData *c2, QClipData *result); + +struct ClipData +{ + QClipData *oldClip; + QClipData *newClip; + Qt::ClipOperation operation; +}; + +enum LineDrawMode { + LineDrawClipped, + LineDrawNormal, + LineDrawIncludeLastPixel +}; + +static void drawLine_midpoint_i(int x1, int y1, int x2, int y2, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &rect); +static void drawLine_midpoint_dashed_i(int x1, int y1, int x2, int y2, + QPen *pen, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect, + int *patternOffset); +// static void drawLine_midpoint_f(qreal x1, qreal y1, qreal x2, qreal y2, +// ProcessSpans span_func, QSpanData *data, +// LineDrawMode style, const QRect &devRect); + +static void drawEllipse_midpoint_i(const QRect &rect, const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data); + +struct QRasterFloatPoint { + qreal x; + qreal y; +}; + +#ifdef QT_DEBUG_DRAW +static const QRectF boundingRect(const QPointF *points, int pointCount) +{ + const QPointF *e = points; + const QPointF *last = points + pointCount; + qreal minx, maxx, miny, maxy; + minx = maxx = e->x(); + miny = maxy = e->y(); + while (++e < last) { + if (e->x() < minx) + minx = e->x(); + else if (e->x() > maxx) + maxx = e->x(); + if (e->y() < miny) + miny = e->y(); + else if (e->y() > maxy) + maxy = e->y(); + } + return QRectF(QPointF(minx, miny), QPointF(maxx, maxy)); +} +#endif + +template <typename T> static inline bool isRect(const T *pts, int elementCount) { + return (elementCount == 5 // 5-point polygon, check for closed rect + && pts[0] == pts[8] && pts[1] == pts[9] // last point == first point + && pts[0] == pts[6] && pts[2] == pts[4] // x values equal + && pts[1] == pts[3] && pts[5] == pts[7] // y values equal... + && pts[0] < pts[4] && pts[1] < pts[5] + ) || + (elementCount == 4 // 4-point polygon, check for unclosed rect + && pts[0] == pts[6] && pts[2] == pts[4] // x values equal + && pts[1] == pts[3] && pts[5] == pts[7] // y values equal... + && pts[0] < pts[4] && pts[1] < pts[5] + ); +} + + +static void qt_ft_outline_move_to(qfixed x, qfixed y, void *data) +{ + ((QOutlineMapper *) data)->moveTo(QPointF(qt_fixed_to_real(x), qt_fixed_to_real(y))); +} + +static void qt_ft_outline_line_to(qfixed x, qfixed y, void *data) +{ + ((QOutlineMapper *) data)->lineTo(QPointF(qt_fixed_to_real(x), qt_fixed_to_real(y))); +} + +static void qt_ft_outline_cubic_to(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data) +{ + ((QOutlineMapper *) data)->curveTo(QPointF(qt_fixed_to_real(c1x), qt_fixed_to_real(c1y)), + QPointF(qt_fixed_to_real(c2x), qt_fixed_to_real(c2y)), + QPointF(qt_fixed_to_real(ex), qt_fixed_to_real(ey))); +} + + +#if !defined(QT_NO_DEBUG) && 0 +static void qt_debug_path(const QPainterPath &path) +{ + const char *names[] = { + "MoveTo ", + "LineTo ", + "CurveTo ", + "CurveToData" + }; + + fprintf(stderr,"\nQPainterPath: elementCount=%d\n", path.elementCount()); + for (int i=0; i<path.elementCount(); ++i) { + const QPainterPath::Element &e = path.elementAt(i); + Q_ASSERT(e.type >= 0 && e.type <= QPainterPath::CurveToDataElement); + fprintf(stderr," - %3d:: %s, (%.2f, %.2f)\n", i, names[e.type], e.x, e.y); + } +} +#endif + +QRasterPaintEnginePrivate::QRasterPaintEnginePrivate() : + QPaintEngineExPrivate(), + cachedLines(0) +{ +} + + +/*! + \class QRasterPaintEngine + \preliminary + \ingroup qws + \since 4.2 + + \brief The QRasterPaintEngine class enables hardware acceleration + of painting operations in Qt for Embedded Linux. + + Note that this functionality is only available in + \l{Qt for Embedded Linux}. + + In \l{Qt for Embedded Linux}, painting is a pure software + implementation. But starting with Qt 4.2, it is + possible to add an accelerated graphics driver to take advantage + of available hardware resources. + + Hardware acceleration is accomplished by creating a custom screen + driver, accelerating the copying from memory to the screen, and + implementing a custom paint engine accelerating the various + painting operations. Then a custom paint device (derived from the + QCustomRasterPaintDevice class) and a custom window surface + (derived from QWSWindowSurface) must be implemented to make + \l{Qt for Embedded Linux} aware of the accelerated driver. + + \note The QRasterPaintEngine class does not support 8-bit images. + Instead, they need to be converted to a supported format, such as + QImage::Format_ARGB32_Premultiplied. + + See the \l {Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + documentation for details. + + \sa QCustomRasterPaintDevice, QPaintEngine +*/ + +/*! + \fn Type QRasterPaintEngine::type() const + \reimp +*/ + +/*! + \typedef QSpan + \relates QRasterPaintEngine + + A struct equivalent to QT_FT_Span, containing a position (x, + y), the span's length in pixels and its color/coverage (a value + ranging from 0 to 255). +*/ + +/*! + \since 4.5 + + Creates a raster based paint engine for operating on the given + \a device, with the complete set of \l + {QPaintEngine::PaintEngineFeature}{paint engine features and + capabilities}. +*/ +QRasterPaintEngine::QRasterPaintEngine(QPaintDevice *device) + : QPaintEngineEx(*(new QRasterPaintEnginePrivate)) +{ + d_func()->device = device; + init(); +} + +/*! + \internal +*/ +QRasterPaintEngine::QRasterPaintEngine(QRasterPaintEnginePrivate &dd, QPaintDevice *device) + : QPaintEngineEx(dd) +{ + d_func()->device = device; + init(); +} + +void QRasterPaintEngine::init() +{ + Q_D(QRasterPaintEngine); + + +#ifdef Q_WS_WIN + d->hdc = 0; +#endif + + // The antialiasing raster. + d->grayRaster.reset(new QT_FT_Raster); + Q_CHECK_PTR(d->grayRaster.data()); + if (qt_ft_grays_raster.raster_new(d->grayRaster.data())) + QT_THROW(std::bad_alloc()); // an error creating the raster is caused by a bad malloc + + + d->rasterizer.reset(new QRasterizer); + d->rasterBuffer.reset(new QRasterBuffer()); + d->outlineMapper.reset(new QOutlineMapper); + d->outlinemapper_xform_dirty = true; + + d->basicStroker.setMoveToHook(qt_ft_outline_move_to); + d->basicStroker.setLineToHook(qt_ft_outline_line_to); + d->basicStroker.setCubicToHook(qt_ft_outline_cubic_to); + + d->baseClip.reset(new QClipData(d->device->height())); + d->baseClip->setClipRect(QRect(0, 0, d->device->width(), d->device->height())); + + d->image_filler.init(d->rasterBuffer.data(), this); + d->image_filler.type = QSpanData::Texture; + + d->image_filler_xform.init(d->rasterBuffer.data(), this); + d->image_filler_xform.type = QSpanData::Texture; + + d->solid_color_filler.init(d->rasterBuffer.data(), this); + d->solid_color_filler.type = QSpanData::Solid; + + d->deviceDepth = d->device->depth(); + + d->mono_surface = false; + gccaps &= ~PorterDuff; + + QImage::Format format = QImage::Format_Invalid; + + switch (d->device->devType()) { + case QInternal::Pixmap: + qWarning("QRasterPaintEngine: unsupported for pixmaps..."); + break; + case QInternal::Image: + format = d->rasterBuffer->prepare(static_cast<QImage *>(d->device)); + break; +#ifdef Q_WS_QWS + case QInternal::CustomRaster: + d->rasterBuffer->prepare(static_cast<QCustomRasterPaintDevice*>(d->device)); + break; +#endif + default: + qWarning("QRasterPaintEngine: unsupported target device %d\n", d->device->devType()); + d->device = 0; + return; + } + + switch (format) { + case QImage::Format_MonoLSB: + case QImage::Format_Mono: + d->mono_surface = true; + break; + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_ARGB32: + gccaps |= PorterDuff; + break; + case QImage::Format_RGB32: + case QImage::Format_RGB444: + case QImage::Format_RGB555: + case QImage::Format_RGB666: + case QImage::Format_RGB888: + case QImage::Format_RGB16: + break; + default: + break; + } +} + + + + +/*! + Destroys this paint engine. +*/ +QRasterPaintEngine::~QRasterPaintEngine() +{ + Q_D(QRasterPaintEngine); + + qt_ft_grays_raster.raster_done(*d->grayRaster.data()); +} + +/*! + \reimp +*/ +bool QRasterPaintEngine::begin(QPaintDevice *device) +{ + Q_D(QRasterPaintEngine); + + if (device->devType() == QInternal::Pixmap) { + QPixmap *pixmap = static_cast<QPixmap *>(device); + QPixmapData *pd = pixmap->pixmapData(); + if (pd->classId() == QPixmapData::RasterClass || pd->classId() == QPixmapData::BlitterClass) + d->device = pd->buffer(); + } else { + d->device = device; + } + + // Make sure QPaintEngine::paintDevice() returns the proper device. + d->pdev = d->device; + + Q_ASSERT(d->device->devType() == QInternal::Image + || d->device->devType() == QInternal::CustomRaster); + + d->systemStateChanged(); + + QRasterPaintEngineState *s = state(); + ensureOutlineMapper(); + d->outlineMapper->m_clip_rect = d->deviceRect; + + if (d->outlineMapper->m_clip_rect.width() > QT_RASTER_COORD_LIMIT) + d->outlineMapper->m_clip_rect.setWidth(QT_RASTER_COORD_LIMIT); + if (d->outlineMapper->m_clip_rect.height() > QT_RASTER_COORD_LIMIT) + d->outlineMapper->m_clip_rect.setHeight(QT_RASTER_COORD_LIMIT); + + d->rasterizer->setClipRect(d->deviceRect); + + s->penData.init(d->rasterBuffer.data(), this); + s->penData.setup(s->pen.brush(), s->intOpacity, s->composition_mode); + s->stroker = &d->basicStroker; + d->basicStroker.setClipRect(d->deviceRect); + + s->brushData.init(d->rasterBuffer.data(), this); + s->brushData.setup(s->brush, s->intOpacity, s->composition_mode); + + d->rasterBuffer->compositionMode = QPainter::CompositionMode_SourceOver; + + setDirty(DirtyBrushOrigin); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::begin(" << (void *) device + << ") devType:" << device->devType() + << "devRect:" << d->deviceRect; + if (d->baseClip) { + dumpClip(d->rasterBuffer->width(), d->rasterBuffer->height(), &*d->baseClip); + } +#endif + +#if defined(Q_WS_WIN) + d->isPlain45DegreeRotation = true; +#endif + + if (d->mono_surface) + d->glyphCacheType = QFontEngineGlyphCache::Raster_Mono; +#if defined(Q_WS_WIN) + else if (qt_cleartype_enabled) +#elif defined (Q_WS_MAC) + else if (qt_applefontsmoothing_enabled) +#else + else if (false) +#endif + { + QImage::Format format = static_cast<QImage *>(d->device)->format(); + if (format == QImage::Format_ARGB32_Premultiplied || format == QImage::Format_RGB32) + d->glyphCacheType = QFontEngineGlyphCache::Raster_RGBMask; + else + d->glyphCacheType = QFontEngineGlyphCache::Raster_A8; + } else + d->glyphCacheType = QFontEngineGlyphCache::Raster_A8; + + setActive(true); + return true; +} + +/*! + \reimp +*/ +bool QRasterPaintEngine::end() +{ +#ifdef QT_DEBUG_DRAW + Q_D(QRasterPaintEngine); + qDebug() << "QRasterPaintEngine::end devRect:" << d->deviceRect; + if (d->baseClip) { + dumpClip(d->rasterBuffer->width(), d->rasterBuffer->height(), &*d->baseClip); + } +#endif + + return true; +} + +/*! + \internal +*/ +void QRasterPaintEngine::releaseBuffer() +{ + Q_D(QRasterPaintEngine); + d->rasterBuffer.reset(new QRasterBuffer); +} + +/*! + \internal +*/ +QSize QRasterPaintEngine::size() const +{ + Q_D(const QRasterPaintEngine); + return QSize(d->rasterBuffer->width(), d->rasterBuffer->height()); +} + +/*! + \internal +*/ +#ifndef QT_NO_DEBUG +void QRasterPaintEngine::saveBuffer(const QString &s) const +{ + Q_D(const QRasterPaintEngine); + d->rasterBuffer->bufferImage().save(s, "PNG"); +} +#endif + +/*! + \internal +*/ +void QRasterPaintEngine::updateMatrix(const QTransform &matrix) +{ + QRasterPaintEngineState *s = state(); + // FALCON: get rid of this line, see drawImage call below. + s->matrix = matrix; + QTransform::TransformationType txop = s->matrix.type(); + + switch (txop) { + + case QTransform::TxNone: + s->flags.int_xform = true; + break; + + case QTransform::TxTranslate: + s->flags.int_xform = qreal(int(s->matrix.dx())) == s->matrix.dx() + && qreal(int(s->matrix.dy())) == s->matrix.dy(); + break; + + case QTransform::TxScale: + s->flags.int_xform = qreal(int(s->matrix.dx())) == s->matrix.dx() + && qreal(int(s->matrix.dy())) == s->matrix.dy() + && qreal(int(s->matrix.m11())) == s->matrix.m11() + && qreal(int(s->matrix.m22())) == s->matrix.m22(); + break; + + default: // shear / perspective... + s->flags.int_xform = false; + break; + } + + s->flags.tx_noshear = qt_scaleForTransform(s->matrix, &s->txscale); + + ensureOutlineMapper(); + +#ifdef Q_WS_WIN + Q_D(QRasterPaintEngine); + d->isPlain45DegreeRotation = false; + if (txop >= QTransform::TxRotate) { + d->isPlain45DegreeRotation = + (qFuzzyIsNull(matrix.m11()) + && qFuzzyIsNull(matrix.m12() - qreal(1)) + && qFuzzyIsNull(matrix.m21() + qreal(1)) + && qFuzzyIsNull(matrix.m22()) + ) + || + (qFuzzyIsNull(matrix.m11() + qreal(1)) + && qFuzzyIsNull(matrix.m12()) + && qFuzzyIsNull(matrix.m21()) + && qFuzzyIsNull(matrix.m22() + qreal(1)) + ) + || + (qFuzzyIsNull(matrix.m11()) + && qFuzzyIsNull(matrix.m12() + qreal(1)) + && qFuzzyIsNull(matrix.m21() - qreal(1)) + && qFuzzyIsNull(matrix.m22()) + ) + ; + } +#endif + +} + + + +QRasterPaintEngineState::~QRasterPaintEngineState() +{ + if (flags.has_clip_ownership) + delete clip; +} + + +QRasterPaintEngineState::QRasterPaintEngineState() +{ + stroker = 0; + + fillFlags = 0; + strokeFlags = 0; + pixmapFlags = 0; + + intOpacity = 256; + + txscale = 1.; + + flags.fast_pen = true; + flags.antialiased = false; + flags.bilinear = false; + flags.fast_text = true; + flags.int_xform = true; + flags.tx_noshear = true; + flags.fast_images = true; + + clip = 0; + flags.has_clip_ownership = false; + + dirty = 0; +} + +QRasterPaintEngineState::QRasterPaintEngineState(QRasterPaintEngineState &s) + : QPainterState(s) + , lastPen(s.lastPen) + , penData(s.penData) + , stroker(s.stroker) + , strokeFlags(s.strokeFlags) + , lastBrush(s.lastBrush) + , brushData(s.brushData) + , fillFlags(s.fillFlags) + , pixmapFlags(s.pixmapFlags) + , intOpacity(s.intOpacity) + , txscale(s.txscale) + , clip(s.clip) + , dirty(s.dirty) + , flag_bits(s.flag_bits) +{ + brushData.tempImage = 0; + penData.tempImage = 0; + flags.has_clip_ownership = false; +} + +/*! + \internal +*/ +QPainterState *QRasterPaintEngine::createState(QPainterState *orig) const +{ + QRasterPaintEngineState *s; + if (!orig) + s = new QRasterPaintEngineState(); + else + s = new QRasterPaintEngineState(*static_cast<QRasterPaintEngineState *>(orig)); + + return s; +} + +/*! + \internal +*/ +void QRasterPaintEngine::setState(QPainterState *s) +{ + Q_D(QRasterPaintEngine); + QPaintEngineEx::setState(s); + d->rasterBuffer->compositionMode = s->composition_mode; +} + +/*! + \fn QRasterPaintEngineState *QRasterPaintEngine::state() + \internal +*/ + +/*! + \fn const QRasterPaintEngineState *QRasterPaintEngine::state() const + \internal +*/ + +/*! + \internal +*/ +void QRasterPaintEngine::penChanged() +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::penChanged():" << state()->pen; +#endif + QRasterPaintEngineState *s = state(); + s->strokeFlags |= DirtyPen; + s->dirty |= DirtyPen; +} + +/*! + \internal +*/ +void QRasterPaintEngine::updatePen(const QPen &pen) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::updatePen():" << s->pen; +#endif + + Qt::PenStyle pen_style = qpen_style(pen); + + s->lastPen = pen; + s->strokeFlags = 0; + + s->penData.clip = d->clip(); + s->penData.setup(pen_style == Qt::NoPen ? QBrush() : pen.brush(), s->intOpacity, s->composition_mode); + + if (s->strokeFlags & QRasterPaintEngine::DirtyTransform + || pen.brush().transform().type() >= QTransform::TxNone) { + d->updateMatrixData(&s->penData, pen.brush(), s->matrix); + } + + // Slightly ugly handling of an uncommon case... We need to change + // the pen because it is reused in draw_midpoint to decide dashed + // or non-dashed. + if (pen_style == Qt::CustomDashLine && pen.dashPattern().size() == 0) { + pen_style = Qt::SolidLine; + s->lastPen.setStyle(Qt::SolidLine); + } + + d->basicStroker.setJoinStyle(qpen_joinStyle(pen)); + d->basicStroker.setCapStyle(qpen_capStyle(pen)); + d->basicStroker.setMiterLimit(pen.miterLimit()); + + qreal penWidth = qpen_widthf(pen); + if (penWidth == 0) + d->basicStroker.setStrokeWidth(1); + else + d->basicStroker.setStrokeWidth(penWidth); + + if(pen_style == Qt::SolidLine) { + s->stroker = &d->basicStroker; + } else if (pen_style != Qt::NoPen) { + if (!d->dashStroker) + d->dashStroker.reset(new QDashStroker(&d->basicStroker)); + if (pen.isCosmetic()) { + d->dashStroker->setClipRect(d->deviceRect); + } else { + // ### I've seen this inverted devrect multiple places now... + QRectF clipRect = s->matrix.inverted().mapRect(QRectF(d->deviceRect)); + d->dashStroker->setClipRect(clipRect); + } + d->dashStroker->setDashPattern(pen.dashPattern()); + d->dashStroker->setDashOffset(pen.dashOffset()); + s->stroker = d->dashStroker.data(); + } else { + s->stroker = 0; + } + + s->flags.fast_pen = pen_style > Qt::NoPen + && s->penData.blend + && !s->flags.antialiased + && (penWidth == 0 || (penWidth <= 1 + && (s->matrix.type() <= QTransform::TxTranslate + || pen.isCosmetic()))); + + ensureState(); // needed because of tx_noshear... + s->flags.non_complex_pen = qpen_capStyle(s->lastPen) <= Qt::SquareCap && s->flags.tx_noshear; + + s->strokeFlags = 0; +} + + + +/*! + \internal +*/ +void QRasterPaintEngine::brushOriginChanged() +{ + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::brushOriginChanged()" << s->brushOrigin; +#endif + + s->fillFlags |= DirtyBrushOrigin; +} + + +/*! + \internal +*/ +void QRasterPaintEngine::brushChanged() +{ + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::brushChanged():" << s->brush; +#endif + s->fillFlags |= DirtyBrush; +} + + + + +/*! + \internal +*/ +void QRasterPaintEngine::updateBrush(const QBrush &brush) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::updateBrush()" << brush; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + // must set clip prior to setup, as setup uses it... + s->brushData.clip = d->clip(); + s->brushData.setup(brush, s->intOpacity, s->composition_mode); + if (s->fillFlags & DirtyTransform + || brush.transform().type() >= QTransform::TxNone) + d_func()->updateMatrixData(&s->brushData, brush, d->brushMatrix()); + s->lastBrush = brush; + s->fillFlags = 0; +} + +void QRasterPaintEngine::updateOutlineMapper() +{ + Q_D(QRasterPaintEngine); + d->outlineMapper->setMatrix(state()->matrix); +} + +void QRasterPaintEngine::updateState() +{ + QRasterPaintEngineState *s = state(); + + if (s->dirty & DirtyTransform) + updateMatrix(s->matrix); + + if (s->dirty & (DirtyPen|DirtyCompositionMode|DirtyOpacity)) { + const QPainter::CompositionMode mode = s->composition_mode; + s->flags.fast_text = (s->penData.type == QSpanData::Solid) + && s->intOpacity == 256 + && (mode == QPainter::CompositionMode_Source + || (mode == QPainter::CompositionMode_SourceOver + && qAlpha(s->penData.solid.color) == 255)); + } + + s->dirty = 0; +} + + +/*! + \internal +*/ +void QRasterPaintEngine::opacityChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::opacityChanged()" << s->opacity; +#endif + + s->fillFlags |= DirtyOpacity; + s->strokeFlags |= DirtyOpacity; + s->pixmapFlags |= DirtyOpacity; + s->dirty |= DirtyOpacity; + s->intOpacity = (int) (s->opacity * 256); +} + +/*! + \internal +*/ +void QRasterPaintEngine::compositionModeChanged() +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::compositionModeChanged()" << s->composition_mode; +#endif + + s->fillFlags |= DirtyCompositionMode; + s->dirty |= DirtyCompositionMode; + + s->strokeFlags |= DirtyCompositionMode; + d->rasterBuffer->compositionMode = s->composition_mode; + + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::renderHintsChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::renderHintsChanged()" << hex << s->renderHints; +#endif + + bool was_aa = s->flags.antialiased; + bool was_bilinear = s->flags.bilinear; + + s->flags.antialiased = bool(s->renderHints & QPainter::Antialiasing); + s->flags.bilinear = bool(s->renderHints & QPainter::SmoothPixmapTransform); + + if (was_aa != s->flags.antialiased) + s->strokeFlags |= DirtyHints; + + if (was_bilinear != s->flags.bilinear) { + s->strokeFlags |= DirtyPen; + s->fillFlags |= DirtyBrush; + } + + Q_D(QRasterPaintEngine); + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::transformChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::transformChanged()" << s->matrix; +#endif + + s->fillFlags |= DirtyTransform; + s->strokeFlags |= DirtyTransform; + + s->dirty |= DirtyTransform; + + Q_D(QRasterPaintEngine); + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::clipEnabledChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clipEnabledChanged()" << s->clipEnabled; +#endif + + if (s->clip) { + s->clip->enabled = s->clipEnabled; + s->fillFlags |= DirtyClipEnabled; + s->strokeFlags |= DirtyClipEnabled; + s->pixmapFlags |= DirtyClipEnabled; + } +} + +#ifdef Q_WS_QWS +void QRasterPaintEnginePrivate::prepare(QCustomRasterPaintDevice *device) +{ + rasterBuffer->prepare(device); +} +#endif + +void QRasterPaintEnginePrivate::drawImage(const QPointF &pt, + const QImage &img, + SrcOverBlendFunc func, + const QRect &clip, + int alpha, + const QRect &sr) +{ + if (alpha == 0 || !clip.isValid()) + return; + + Q_ASSERT(img.depth() >= 8); + + int srcBPL = img.bytesPerLine(); + const uchar *srcBits = img.bits(); + int srcSize = img.depth() >> 3; // This is the part that is incompatible with lower than 8-bit.. + int iw = img.width(); + int ih = img.height(); + + if (!sr.isEmpty()) { + iw = sr.width(); + ih = sr.height(); + // Adjust the image according to the source offset... + srcBits += ((sr.y() * srcBPL) + sr.x() * srcSize); + } + + // adapt the x parameters + int x = qRound(pt.x()); + int cx1 = clip.x(); + int cx2 = clip.x() + clip.width(); + if (x < cx1) { + int d = cx1 - x; + srcBits += srcSize * d; + iw -= d; + x = cx1; + } + if (x + iw > cx2) { + int d = x + iw - cx2; + iw -= d; + } + if (iw <= 0) + return; + + // adapt the y paremeters... + int cy1 = clip.y(); + int cy2 = clip.y() + clip.height(); + int y = qRound(pt.y()); + if (y < cy1) { + int d = cy1 - y; + srcBits += srcBPL * d; + ih -= d; + y = cy1; + } + if (y + ih > cy2) { + int d = y + ih - cy2; + ih -= d; + } + if (ih <= 0) + return; + + // call the blend function... + int dstSize = rasterBuffer->bytesPerPixel(); + int dstBPL = rasterBuffer->bytesPerLine(); + func(rasterBuffer->buffer() + x * dstSize + y * dstBPL, dstBPL, + srcBits, srcBPL, + iw, ih, + alpha); +} + + +void QRasterPaintEnginePrivate::systemStateChanged() +{ + QRect clipRect(0, 0, + qMin(QT_RASTER_COORD_LIMIT, device->width()), + qMin(QT_RASTER_COORD_LIMIT, device->height())); + + if (!systemClip.isEmpty()) { + QRegion clippedDeviceRgn = systemClip & clipRect; + deviceRect = clippedDeviceRgn.boundingRect(); + baseClip->setClipRegion(clippedDeviceRgn); + } else { + deviceRect = clipRect; + baseClip->setClipRect(deviceRect); + } +#ifdef QT_DEBUG_DRAW + qDebug() << "systemStateChanged" << this << "deviceRect" << deviceRect << clipRect << systemClip; +#endif + + exDeviceRect = deviceRect; + + Q_Q(QRasterPaintEngine); + q->state()->strokeFlags |= QPaintEngine::DirtyClipRegion; + q->state()->fillFlags |= QPaintEngine::DirtyClipRegion; + q->state()->pixmapFlags |= QPaintEngine::DirtyClipRegion; +} + +void QRasterPaintEnginePrivate::updateMatrixData(QSpanData *spanData, const QBrush &b, const QTransform &m) +{ + if (b.d->style == Qt::NoBrush || b.d->style == Qt::SolidPattern) + return; + + Q_Q(QRasterPaintEngine); + bool bilinear = q->state()->flags.bilinear; + + if (b.d->transform.type() > QTransform::TxNone) { // FALCON: optimize + spanData->setupMatrix(b.transform() * m, bilinear); + } else { + if (m.type() <= QTransform::TxTranslate) { + // specialize setupMatrix for translation matrices + // to avoid needless matrix inversion + spanData->m11 = 1; + spanData->m12 = 0; + spanData->m13 = 0; + spanData->m21 = 0; + spanData->m22 = 1; + spanData->m23 = 0; + spanData->m33 = 1; + spanData->dx = -m.dx(); + spanData->dy = -m.dy(); + spanData->txop = m.type(); + spanData->bilinear = bilinear; + spanData->fast_matrix = qAbs(m.dx()) < 1e4 && qAbs(m.dy()) < 1e4; + spanData->adjustSpanMethods(); + } else { + spanData->setupMatrix(m, bilinear); + } + } +} + +// #define QT_CLIPPING_RATIOS + +#ifdef QT_CLIPPING_RATIOS +int rectClips; +int regionClips; +int totalClips; + +static void checkClipRatios(QRasterPaintEnginePrivate *d) +{ + if (d->clip()->hasRectClip) + rectClips++; + if (d->clip()->hasRegionClip) + regionClips++; + totalClips++; + + if ((totalClips % 5000) == 0) { + printf("Clipping ratio: rectangular=%f%%, region=%f%%, complex=%f%%\n", + rectClips * 100.0 / (qreal) totalClips, + regionClips * 100.0 / (qreal) totalClips, + (totalClips - rectClips - regionClips) * 100.0 / (qreal) totalClips); + totalClips = 0; + rectClips = 0; + regionClips = 0; + } + +} +#endif + +static void qrasterpaintengine_state_setNoClip(QRasterPaintEngineState *s) +{ + if (s->flags.has_clip_ownership) + delete s->clip; + s->clip = 0; + s->flags.has_clip_ownership = false; +} + +static void qrasterpaintengine_dirty_clip(QRasterPaintEnginePrivate *d, QRasterPaintEngineState *s) +{ + s->fillFlags |= QPaintEngine::DirtyClipPath; + s->strokeFlags |= QPaintEngine::DirtyClipPath; + s->pixmapFlags |= QPaintEngine::DirtyClipPath; + + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); + +#ifdef QT_DEBUG_DRAW + dumpClip(d->rasterBuffer->width(), d->rasterBuffer->height(), &*d->clip()); +#endif + +} + + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clip(): " << path << op; + + if (path.elements()) { + for (int i=0; i<path.elementCount(); ++i) { + qDebug() << " - " << path.elements()[i] + << '(' << path.points()[i*2] << ", " << path.points()[i*2+1] << ')'; + } + } else { + for (int i=0; i<path.elementCount(); ++i) { + qDebug() << " ---- " + << '(' << path.points()[i*2] << ", " << path.points()[i*2+1] << ')'; + } + } +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + const qreal *points = path.points(); + const QPainterPath::ElementType *types = path.elements(); + + // There are some cases that are not supported by clip(QRect) + if (op != Qt::UniteClip && (op != Qt::IntersectClip || !s->clip + || s->clip->hasRectClip || s->clip->hasRegionClip)) { + if (s->matrix.type() <= QTransform::TxScale + && ((path.shape() == QVectorPath::RectangleHint) + || (isRect(points, path.elementCount()) + && (!types || (types[0] == QPainterPath::MoveToElement + && types[1] == QPainterPath::LineToElement + && types[2] == QPainterPath::LineToElement + && types[3] == QPainterPath::LineToElement))))) { +#ifdef QT_DEBUG_DRAW + qDebug() << " --- optimizing vector clip to rect clip..."; +#endif + + QRectF r(points[0], points[1], points[4]-points[0], points[5]-points[1]); + if (setClipRectInDeviceCoords(s->matrix.mapRect(r).toRect(), op)) + return; + } + } + + if (op == Qt::NoClip) { + qrasterpaintengine_state_setNoClip(s); + + } else { + QClipData *base = d->baseClip.data(); + + // Intersect with current clip when available... + if (op == Qt::IntersectClip && s->clip) + base = s->clip; + + // We always intersect, except when there is nothing to + // intersect with, in which case we simplify the operation to + // a replace... + Qt::ClipOperation isectOp = Qt::IntersectClip; + if (base == 0) + isectOp = Qt::ReplaceClip; + + QClipData *newClip = new QClipData(d->rasterBuffer->height()); + newClip->initialize(); + ClipData clipData = { base, newClip, isectOp }; + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), qt_span_clip, &clipData, 0); + + newClip->fixup(); + + if (op == Qt::UniteClip) { + // merge clips + QClipData *result = new QClipData(d->rasterBuffer->height()); + QClipData *current = s->clip ? s->clip : new QClipData(d->rasterBuffer->height()); + qt_merge_clip(current, newClip, result); + result->fixup(); + delete newClip; + if (!s->clip) + delete current; + newClip = result; + } + + if (s->flags.has_clip_ownership) + delete s->clip; + + s->clip = newClip; + s->flags.has_clip_ownership = true; + } + qrasterpaintengine_dirty_clip(d, s); +} + + + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QRect &rect, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clip(): " << rect << op; +#endif + + QRasterPaintEngineState *s = state(); + + if (op == Qt::NoClip) { + qrasterpaintengine_state_setNoClip(s); + + } else if (op == Qt::UniteClip || s->matrix.type() > QTransform::TxScale) { + QPaintEngineEx::clip(rect, op); + return; + + } else if (!setClipRectInDeviceCoords(s->matrix.mapRect(rect), op)) { + QPaintEngineEx::clip(rect, op); + return; + } +} + + +bool QRasterPaintEngine::setClipRectInDeviceCoords(const QRect &r, Qt::ClipOperation op) +{ + Q_D(QRasterPaintEngine); + QRect clipRect = r & d->deviceRect; + QRasterPaintEngineState *s = state(); + + if (op == Qt::ReplaceClip || s->clip == 0) { + + // No current clip, hence we intersect with sysclip and be + // done with it... + QRegion clipRegion = systemClip(); + QClipData *clip = new QClipData(d->rasterBuffer->height()); + + if (clipRegion.isEmpty()) + clip->setClipRect(clipRect); + else + clip->setClipRegion(clipRegion & clipRect); + + if (s->flags.has_clip_ownership) + delete s->clip; + + s->clip = clip; + s->clip->enabled = true; + s->flags.has_clip_ownership = true; + + } else if (op == Qt::IntersectClip){ // intersect clip with current clip + QClipData *base = s->clip; + + Q_ASSERT(base); + if (base->hasRectClip || base->hasRegionClip) { + if (!s->flags.has_clip_ownership) { + s->clip = new QClipData(d->rasterBuffer->height()); + s->flags.has_clip_ownership = true; + } + if (base->hasRectClip) + s->clip->setClipRect(base->clipRect & clipRect); + else + s->clip->setClipRegion(base->clipRegion & clipRect); + s->clip->enabled = true; + } else { + return false; + } + } else { + return false; + } + + qrasterpaintengine_dirty_clip(d, s); + return true; +} + + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QRegion ®ion, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clip(): " << region << op; +#endif + + Q_D(QRasterPaintEngine); + + if (region.rectCount() == 1) { + clip(region.boundingRect(), op); + return; + } + + QRasterPaintEngineState *s = state(); + const QClipData *clip = d->clip(); + const QClipData *baseClip = d->baseClip.data(); + + if (op == Qt::NoClip) { + qrasterpaintengine_state_setNoClip(s); + } else if (s->matrix.type() > QTransform::TxScale + || op == Qt::UniteClip + || (op == Qt::IntersectClip && !clip->hasRectClip && !clip->hasRegionClip) + || (op == Qt::ReplaceClip && !baseClip->hasRectClip && !baseClip->hasRegionClip)) { + QPaintEngineEx::clip(region, op); + } else { + const QClipData *curClip; + QClipData *newClip; + + if (op == Qt::IntersectClip) + curClip = clip; + else + curClip = baseClip; + + if (s->flags.has_clip_ownership) { + newClip = s->clip; + Q_ASSERT(newClip); + } else { + newClip = new QClipData(d->rasterBuffer->height()); + s->clip = newClip; + s->flags.has_clip_ownership = true; + } + + QRegion r = s->matrix.map(region); + if (curClip->hasRectClip) + newClip->setClipRegion(r & curClip->clipRect); + else if (curClip->hasRegionClip) + newClip->setClipRegion(r & curClip->clipRegion); + + qrasterpaintengine_dirty_clip(d, s); + } +} + +/*! + \internal +*/ +void QRasterPaintEngine::fillPath(const QPainterPath &path, QSpanData *fillData) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " --- fillPath, bounds=" << path.boundingRect(); +#endif + + if (!fillData->blend) + return; + + Q_D(QRasterPaintEngine); + + const QRectF controlPointRect = path.controlPointRect(); + + QRasterPaintEngineState *s = state(); + const QRect deviceRect = s->matrix.mapRect(controlPointRect).toRect(); + ProcessSpans blend = d->getBrushFunc(deviceRect, fillData); + const bool do_clip = (deviceRect.left() < -QT_RASTER_COORD_LIMIT + || deviceRect.right() > QT_RASTER_COORD_LIMIT + || deviceRect.top() < -QT_RASTER_COORD_LIMIT + || deviceRect.bottom() > QT_RASTER_COORD_LIMIT); + + if (!s->flags.antialiased && !do_clip) { + d->initializeRasterizer(fillData); + d->rasterizer->rasterize(path * s->matrix, path.fillRule()); + return; + } + + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), blend, fillData, d->rasterBuffer.data()); +} + +static void fillRect_normalized(const QRect &r, QSpanData *data, + QRasterPaintEnginePrivate *pe) +{ + int x1, x2, y1, y2; + + bool rectClipped = true; + + if (data->clip) { + x1 = qMax(r.x(), data->clip->xmin); + x2 = qMin(r.x() + r.width(), data->clip->xmax); + y1 = qMax(r.y(), data->clip->ymin); + y2 = qMin(r.y() + r.height(), data->clip->ymax); + rectClipped = data->clip->hasRectClip; + + } else if (pe) { + x1 = qMax(r.x(), pe->deviceRect.x()); + x2 = qMin(r.x() + r.width(), pe->deviceRect.x() + pe->deviceRect.width()); + y1 = qMax(r.y(), pe->deviceRect.y()); + y2 = qMin(r.y() + r.height(), pe->deviceRect.y() + pe->deviceRect.height()); + } else { + x1 = qMax(r.x(), 0); + x2 = qMin(r.x() + r.width(), data->rasterBuffer->width()); + y1 = qMax(r.y(), 0); + y2 = qMin(r.y() + r.height(), data->rasterBuffer->height()); + } + + if (x2 <= x1 || y2 <= y1) + return; + + const int width = x2 - x1; + const int height = y2 - y1; + + bool isUnclipped = rectClipped + || (pe && pe->isUnclipped_normalized(QRect(x1, y1, width, height))); + + if (pe && isUnclipped) { + const QPainter::CompositionMode mode = pe->rasterBuffer->compositionMode; + + if (data->fillRect && (mode == QPainter::CompositionMode_Source + || (mode == QPainter::CompositionMode_SourceOver + && qAlpha(data->solid.color) == 255))) + { + data->fillRect(data->rasterBuffer, x1, y1, width, height, + data->solid.color); + return; + } + } + + ProcessSpans blend = isUnclipped ? data->unclipped_blend : data->blend; + + const int nspans = 256; + QT_FT_Span spans[nspans]; + + Q_ASSERT(data->blend); + int y = y1; + while (y < y2) { + int n = qMin(nspans, y2 - y); + int i = 0; + while (i < n) { + spans[i].x = x1; + spans[i].len = width; + spans[i].y = y + i; + spans[i].coverage = 255; + ++i; + } + + blend(n, spans, data); + y += n; + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawRects(const QRect *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawRect(), rectCount=%d", rectCount); +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + // Fill + ensureBrush(); + if (s->brushData.blend) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxTranslate) { + const QRect *r = rects; + const QRect *lastRect = rects + rectCount; + + int offset_x = int(s->matrix.dx()); + int offset_y = int(s->matrix.dy()); + while (r < lastRect) { + QRect rect = r->normalized(); + QRect rr = rect.translated(offset_x, offset_y); + fillRect_normalized(rr, &s->brushData, d); + ++r; + } + } else { + QRectVectorPath path; + for (int i=0; i<rectCount; ++i) { + path.set(rects[i]); + fill(path, s->brush); + } + } + } + + ensurePen(); + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) { + const QRect *r = rects; + const QRect *lastRect = rects + rectCount; + while (r < lastRect) { + int left = r->x(); + int right = r->x() + r->width(); + int top = r->y(); + int bottom = r->y() + r->height(); + +#ifdef Q_WS_MAC + int pts[] = { top, left, + top, right, + bottom, right, + bottom, left }; +#else + int pts[] = { left, top, + right, top, + right, bottom, + left, bottom }; +#endif + + strokePolygonCosmetic((QPoint *) pts, 4, WindingMode); + ++r; + } + } else { + QRectVectorPath path; + for (int i = 0; i < rectCount; ++i) { + path.set(rects[i]); + stroke(path, s->pen); + } + } + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawRect(), rectCount=%d", rectCount); +#endif +#ifdef QT_FAST_SPANS + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensureState(); + + if (s->flags.tx_noshear) { + ensureBrush(); + if (s->brushData.blend) { + d->initializeRasterizer(&s->brushData); + for (int i = 0; i < rectCount; ++i) { + const QRectF &rect = rects[i].normalized(); + if (rect.isEmpty()) + continue; + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + } + } + + ensurePen(); + if (s->penData.blend) { + qreal width = s->pen.isCosmetic() + ? (s->lastPen.widthF() == 0 ? 1 : s->lastPen.widthF()) + : s->lastPen.widthF() * s->txscale; + + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) { + for (int i = 0; i < rectCount; ++i) { + const QRectF &r = rects[i]; + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { left, top, + right, top, + right, bottom, + left, bottom }; + strokePolygonCosmetic((QPointF *) pts, 4, WindingMode); + } + } else if (width <= 1 && qpen_style(s->lastPen) == Qt::SolidLine) { + d->initializeRasterizer(&s->penData); + + for (int i = 0; i < rectCount; ++i) { + const QRectF &rect = rects[i].normalized(); + if (rect.isEmpty()) { + qreal pts[] = { rect.left(), rect.top(), rect.right(), rect.bottom() }; + QVectorPath vp(pts, 2, 0, QVectorPath::LinesHint); + QPaintEngineEx::stroke(vp, s->lastPen); + } else { + const QPointF tl = s->matrix.map(rect.topLeft()); + const QPointF tr = s->matrix.map(rect.topRight()); + const QPointF bl = s->matrix.map(rect.bottomLeft()); + const QPointF br = s->matrix.map(rect.bottomRight()); + const qreal w = width / (rect.width() * s->txscale); + const qreal h = width / (rect.height() * s->txscale); + d->rasterizer->rasterizeLine(tl, tr, w); // top + d->rasterizer->rasterizeLine(bl, br, w); // bottom + d->rasterizer->rasterizeLine(bl, tl, h); // left + d->rasterizer->rasterizeLine(br, tr, h); // right + } + } + } else { + for (int i = 0; i < rectCount; ++i) { + const QRectF &r = rects[i]; + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { left, top, + right, top, + right, bottom, + left, bottom, + left, top }; + QVectorPath vp(pts, 5, 0, QVectorPath::RectangleHint); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } + } + + return; + } +#endif // QT_FAST_SPANS + QPaintEngineEx::drawRects(rects, rectCount); +} + + +/*! + \internal +*/ +void QRasterPaintEngine::stroke(const QVectorPath &path, const QPen &pen) +{ + QRasterPaintEngineState *s = state(); + ensurePen(pen); + if (!s->penData.blend) + return; + + if (s->flags.fast_pen && !path.isCurved() + && s->lastPen.brush().isOpaque()) { + int count = path.elementCount(); + QPointF *points = (QPointF *) path.points(); + const QPainterPath::ElementType *types = path.elements(); + if (types) { + int first = 0; + int last; + while (first < count) { + while (first < count && types[first] != QPainterPath::MoveToElement) ++first; + last = first + 1; + while (last < count && types[last] == QPainterPath::LineToElement) ++last; + strokePolygonCosmetic(points + first, last - first, + path.hasImplicitClose() && last == count // only close last one.. + ? WindingMode + : PolylineMode); + first = last; + } + } else { + strokePolygonCosmetic(points, count, + path.hasImplicitClose() + ? WindingMode + : PolylineMode); + } + + } else if (s->flags.non_complex_pen && path.shape() == QVectorPath::LinesHint) { + qreal width = s->lastPen.isCosmetic() + ? (qpen_widthf(s->lastPen) == 0 ? 1 : qpen_widthf(s->lastPen)) + : qpen_widthf(s->lastPen) * s->txscale; + int dashIndex = 0; + qreal dashOffset = s->lastPen.dashOffset(); + bool inDash = true; + qreal patternLength = 0; + const QVector<qreal> pattern = s->lastPen.dashPattern(); + for (int i = 0; i < pattern.size(); ++i) + patternLength += pattern.at(i); + + if (patternLength > 0) { + int n = qFloor(dashOffset / patternLength); + dashOffset -= n * patternLength; + while (dashOffset >= pattern.at(dashIndex)) { + dashOffset -= pattern.at(dashIndex); + if (++dashIndex >= pattern.size()) + dashIndex = 0; + inDash = !inDash; + } + } + + Q_D(QRasterPaintEngine); + d->initializeRasterizer(&s->penData); + int lineCount = path.elementCount() / 2; + const QLineF *lines = reinterpret_cast<const QLineF *>(path.points()); + + for (int i = 0; i < lineCount; ++i) { + if (lines[i].p1() == lines[i].p2()) { + if (s->lastPen.capStyle() != Qt::FlatCap) { + QPointF p = lines[i].p1(); + QLineF line = s->matrix.map(QLineF(QPointF(p.x() - width*0.5, p.y()), + QPointF(p.x() + width*0.5, p.y()))); + d->rasterizer->rasterizeLine(line.p1(), line.p2(), 1); + } + continue; + } + + const QLineF line = s->matrix.map(lines[i]); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + d->rasterizer->rasterizeLine(line.p1(), line.p2(), + width / line.length(), + s->lastPen.capStyle() == Qt::SquareCap); + } else { + d->rasterizeLine_dashed(line, width, + &dashIndex, &dashOffset, &inDash); + } + } + } + else + QPaintEngineEx::stroke(path, pen); +} + +static inline QRect toNormalizedFillRect(const QRectF &rect) +{ + int x1 = qRound(rect.x()); + int y1 = qRound(rect.y()); + int x2 = qRound(rect.right()); + int y2 = qRound(rect.bottom()); + + if (x2 < x1) + qSwap(x1, x2); + if (y2 < y1) + qSwap(y1, y2); + + return QRect(x1, y1, x2 - x1, y2 - y1); +} + +/*! + \internal +*/ +void QRasterPaintEngine::fill(const QVectorPath &path, const QBrush &brush) +{ + if (path.isEmpty()) + return; +#ifdef QT_DEBUG_DRAW + QRectF rf = path.controlPointRect(); + qDebug() << "QRasterPaintEngine::fill(): " + << "size=" << path.elementCount() + << ", hints=" << hex << path.hints() + << rf << brush; +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensureBrush(brush); + if (!s->brushData.blend) + return; + + if (path.shape() == QVectorPath::RectangleHint) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxScale) { + const qreal *p = path.points(); + QPointF tl = QPointF(p[0], p[1]) * s->matrix; + QPointF br = QPointF(p[4], p[5]) * s->matrix; + fillRect_normalized(toNormalizedFillRect(QRectF(tl, br)), &s->brushData, d); + return; + } + ensureState(); + if (s->flags.tx_noshear) { + d->initializeRasterizer(&s->brushData); + // ### Is normalizing really necessary here? + const qreal *p = path.points(); + QRectF r = QRectF(p[0], p[1], p[2] - p[0], p[7] - p[1]).normalized(); + if (!r.isEmpty()) { + const QPointF a = s->matrix.map((r.topLeft() + r.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((r.topRight() + r.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, r.height() / r.width()); + } + return; + } + } + + if (path.shape() == QVectorPath::EllipseHint) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxScale) { + const qreal *p = path.points(); + QPointF tl = QPointF(p[0], p[1]) * s->matrix; + QPointF br = QPointF(p[4], p[5]) * s->matrix; + QRectF r = s->matrix.mapRect(QRectF(tl, br)); + + ProcessSpans penBlend = d->getPenFunc(r, &s->penData); + ProcessSpans brushBlend = d->getBrushFunc(r, &s->brushData); + const QRect brect = QRect(int(r.x()), int(r.y()), + int_dim(r.x(), r.width()), + int_dim(r.y(), r.height())); + if (brect == r) { + drawEllipse_midpoint_i(brect, d->deviceRect, penBlend, brushBlend, + &s->penData, &s->brushData); + return; + } + } + } + + // ### Optimize for non transformed ellipses and rectangles... + QRectF cpRect = path.controlPointRect(); + const QRect deviceRect = s->matrix.mapRect(cpRect).toRect(); + ProcessSpans blend = d->getBrushFunc(deviceRect, &s->brushData); + + // ### Falcon +// const bool do_clip = (deviceRect.left() < -QT_RASTER_COORD_LIMIT +// || deviceRect.right() > QT_RASTER_COORD_LIMIT +// || deviceRect.top() < -QT_RASTER_COORD_LIMIT +// || deviceRect.bottom() > QT_RASTER_COORD_LIMIT); + + // ### Falonc: implement.... +// if (!s->flags.antialiased && !do_clip) { +// d->initializeRasterizer(&s->brushData); +// d->rasterizer->rasterize(path * d->matrix, path.fillRule()); +// return; +// } + + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), blend, &s->brushData, d->rasterBuffer.data()); +} + +void QRasterPaintEngine::fillRect(const QRectF &r, QSpanData *data) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (!s->flags.antialiased) { + uint txop = s->matrix.type(); + if (txop == QTransform::TxNone) { + fillRect_normalized(toNormalizedFillRect(r), data, d); + return; + } else if (txop == QTransform::TxTranslate) { + const QRect rr = toNormalizedFillRect(r.translated(s->matrix.dx(), s->matrix.dy())); + fillRect_normalized(rr, data, d); + return; + } else if (txop == QTransform::TxScale) { + const QRect rr = toNormalizedFillRect(s->matrix.mapRect(r)); + fillRect_normalized(rr, data, d); + return; + } + } + ensureState(); + if (s->flags.tx_noshear) { + d->initializeRasterizer(data); + QRectF nr = r.normalized(); + if (!nr.isEmpty()) { + const QPointF a = s->matrix.map((nr.topLeft() + nr.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((nr.topRight() + nr.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, nr.height() / nr.width()); + } + return; + } + + QPainterPath path; + path.addRect(r); + ensureOutlineMapper(); + fillPath(path, data); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::fillRect(const QRectF &r, const QBrush &brush) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::fillRecct(): " << r << brush; +#endif + QRasterPaintEngineState *s = state(); + + ensureBrush(brush); + if (!s->brushData.blend) + return; + + fillRect(r, &s->brushData); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::fillRect(const QRectF &r, const QColor &color) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::fillRect(): " << r << color; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + d->solid_color_filler.solid.color = PREMUL(ARGB_COMBINE_ALPHA(color.rgba(), s->intOpacity)); + if ((d->solid_color_filler.solid.color & 0xff000000) == 0 + && s->composition_mode == QPainter::CompositionMode_SourceOver) { + return; + } + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); + fillRect(r, &d->solid_color_filler); +} + +static inline bool isAbove(const QPointF *a, const QPointF *b) +{ + return a->y() < b->y(); +} + +static bool splitPolygon(const QPointF *points, int pointCount, QVector<QPointF> *upper, QVector<QPointF> *lower) +{ + Q_ASSERT(upper); + Q_ASSERT(lower); + + Q_ASSERT(pointCount >= 2); + + QVector<const QPointF *> sorted; + sorted.reserve(pointCount); + + upper->reserve(pointCount * 3 / 4); + lower->reserve(pointCount * 3 / 4); + + for (int i = 0; i < pointCount; ++i) + sorted << points + i; + + qSort(sorted.begin(), sorted.end(), isAbove); + + qreal splitY = sorted.at(sorted.size() / 2)->y(); + + const QPointF *end = points + pointCount; + const QPointF *last = end - 1; + + QVector<QPointF> *bin[2] = { upper, lower }; + + for (const QPointF *p = points; p < end; ++p) { + int side = p->y() < splitY; + int lastSide = last->y() < splitY; + + if (side != lastSide) { + if (qFuzzyCompare(p->y(), splitY)) { + bin[!side]->append(*p); + } else if (qFuzzyCompare(last->y(), splitY)) { + bin[side]->append(*last); + } else { + QPointF delta = *p - *last; + QPointF intersection(p->x() + delta.x() * (splitY - p->y()) / delta.y(), splitY); + + bin[0]->append(intersection); + bin[1]->append(intersection); + } + } + + bin[side]->append(*p); + + last = p; + } + + // give up if we couldn't reduce the point count + return upper->size() < pointCount && lower->size() < pointCount; +} + +/*! + \internal + */ +void QRasterPaintEngine::fillPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + const int maxPoints = 0xffff; + + // max amount of points that raster engine can reliably handle + if (pointCount > maxPoints) { + QVector<QPointF> upper, lower; + + if (splitPolygon(points, pointCount, &upper, &lower)) { + fillPolygon(upper.constData(), upper.size(), mode); + fillPolygon(lower.constData(), lower.size(), mode); + } else + qWarning("Polygon too complex for filling."); + + return; + } + + // Compose polygon fill.., + QVectorPath vp((qreal *) points, pointCount, 0, QVectorPath::polygonFlags(mode)); + ensureOutlineMapper(); + QT_FT_Outline *outline = d->outlineMapper->convertPath(vp); + + // scanconvert. + ProcessSpans brushBlend = d->getBrushFunc(d->outlineMapper->controlPointRect, + &s->brushData); + d->rasterize(outline, brushBlend, &s->brushData, d->rasterBuffer.data()); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawPolygon(F), pointCount=%d", pointCount); + for (int i=0; i<pointCount; ++i) + qDebug() << " - " << points[i]; +#endif + Q_ASSERT(pointCount >= 2); + + if (mode != PolylineMode && isRect((qreal *) points, pointCount)) { + QRectF r(points[0], points[2]); + drawRects(&r, 1); + return; + } + + ensurePen(); + ensureBrush(); + if (mode != PolylineMode) { + // Do the fill... + if (s->brushData.blend) { + fillPolygon(points, pointCount, mode); + } + } + + // Do the outline... + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) + strokePolygonCosmetic(points, pointCount, mode); + else { + QVectorPath vp((qreal *) points, pointCount, 0, QVectorPath::polygonFlags(mode)); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawPolygon(I), pointCount=%d", pointCount); + for (int i=0; i<pointCount; ++i) + qDebug() << " - " << points[i]; +#endif + Q_ASSERT(pointCount >= 2); + if (mode != PolylineMode && isRect((int *) points, pointCount)) { + QRect r(points[0].x(), + points[0].y(), + points[2].x() - points[0].x(), + points[2].y() - points[0].y()); + drawRects(&r, 1); + return; + } + + ensureState(); + ensurePen(); + if (!(s->flags.int_xform && s->flags.fast_pen && (!s->penData.blend || s->pen.brush().isOpaque()))) { + // this calls the float version + QPaintEngineEx::drawPolygon(points, pointCount, mode); + return; + } + + // Do the fill + if (mode != PolylineMode) { + ensureBrush(); + if (s->brushData.blend) { + // Compose polygon fill.., + ensureOutlineMapper(); + d->outlineMapper->beginOutline(mode == WindingMode ? Qt::WindingFill : Qt::OddEvenFill); + d->outlineMapper->moveTo(*points); + const QPoint *p = points; + const QPoint *ep = points + pointCount - 1; + do { + d->outlineMapper->lineTo(*(++p)); + } while (p < ep); + d->outlineMapper->endOutline(); + + // scanconvert. + ProcessSpans brushBlend = d->getBrushFunc(d->outlineMapper->controlPointRect, + &s->brushData); + d->rasterize(d->outlineMapper->outline(), brushBlend, &s->brushData, d->rasterBuffer.data()); + } + } + + // Do the outline... + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) + strokePolygonCosmetic(points, pointCount, mode); + else { + int count = pointCount * 2; + QVarLengthArray<qreal> fpoints(count); +#ifdef Q_WS_MAC + for (int i=0; i<count; i+=2) { + fpoints[i] = ((int *) points)[i+1]; + fpoints[i+1] = ((int *) points)[i]; + } +#else + for (int i=0; i<count; ++i) + fpoints[i] = ((int *) points)[i]; +#endif + QVectorPath vp((qreal *) fpoints.data(), pointCount, 0, QVectorPath::polygonFlags(mode)); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } +} + +/*! + \internal +*/ +void QRasterPaintEngine::strokePolygonCosmetic(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + Q_ASSERT(s->penData.blend); + Q_ASSERT(s->flags.fast_pen); + + bool needs_closing = mode != PolylineMode && points[0] != points[pointCount-1]; + + // Use fast path for 0 width / trivial pens. + QIntRect devRect; + devRect.set(d->deviceRect); + + LineDrawMode mode_for_last = (s->lastPen.capStyle() != Qt::FlatCap + ? LineDrawIncludeLastPixel + : LineDrawNormal); + int dashOffset = int(s->lastPen.dashOffset()); + + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + + QPointF lp1 = points[i-1] * s->matrix; + QPointF lp2 = points[i] * s->matrix; + + const QRectF brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + drawLine_midpoint_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + } else { + drawLine_midpoint_dashed_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } + + // Polygons are implicitly closed. + if (needs_closing) { + QPointF lp1 = points[pointCount-1] * s->matrix; + QPointF lp2 = points[0] * s->matrix; + + const QRectF brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + drawLine_midpoint_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + penBlend, &s->penData, + LineDrawIncludeLastPixel, + devRect); + } else { + drawLine_midpoint_dashed_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + &s->lastPen, + penBlend, &s->penData, + LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } + +} + +/*! + \internal +*/ +void QRasterPaintEngine::strokePolygonCosmetic(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + // We assert here because this function is called from drawRects + // and drawPolygon and they already do ensurePen(), so we skip that + // here to avoid duplicate checks.. + Q_ASSERT(s->penData.blend); + + bool needs_closing = mode != PolylineMode && points[0] != points[pointCount-1]; + + QIntRect devRect; + devRect.set(d->deviceRect); + + LineDrawMode mode_for_last = (s->lastPen.capStyle() != Qt::FlatCap + ? LineDrawIncludeLastPixel + : LineDrawNormal); + + int m11 = int(s->matrix.m11()); + int m22 = int(s->matrix.m22()); + int dx = int(s->matrix.dx()); + int dy = int(s->matrix.dy()); + int m13 = int(s->matrix.m13()); + int m23 = int(s->matrix.m23()); + bool affine = !m13 && !m23; + + int dashOffset = int(s->lastPen.dashOffset()); + + if (affine) { + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + const QPoint lp1 = points[i-1] * s->matrix; + const QPoint lp2 = points[i] * s->matrix; + const QRect brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + + } + + // Polygons are implicitly closed. + if (needs_closing) { + const QPoint lp1 = points[pointCount - 1] * s->matrix; + const QPoint lp2 = points[0] * s->matrix; + const QRect brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + &s->lastPen, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } else { + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + int x1 = points[i-1].x() * m11 + dx; + int y1 = points[i-1].y() * m22 + dy; + qreal w = m13*points[i-1].x() + m23*points[i-1].y() + 1.; + w = 1/w; + x1 = int(x1*w); + y1 = int(y1*w); + int x2 = points[i].x() * m11 + dx; + int y2 = points[i].y() * m22 + dy; + w = m13*points[i].x() + m23*points[i].y() + 1.; + w = 1/w; + x2 = int(x2*w); + y2 = int(y2*w); + + const QRect brect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + + } + + int x1 = points[pointCount-1].x() * m11 + dx; + int y1 = points[pointCount-1].y() * m22 + dy; + qreal w = m13*points[pointCount-1].x() + m23*points[pointCount-1].y() + 1.; + w = 1/w; + x1 = int(x1*w); + y1 = int(y1*w); + int x2 = points[0].x() * m11 + dx; + int y2 = points[0].y() * m22 + dy; + w = m13*points[0].x() + m23*points[0].y() + 1.; + w = 1/w; + x2 = int(x2 * w); + y2 = int(y2 * w); + // Polygons are implicitly closed. + + if (needs_closing) { + const QRect brect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } +} + +/*! + \internal +*/ +void QRasterPaintEngine::drawPixmap(const QPointF &pos, const QPixmap &pixmap) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawPixmap(), pos=" << pos << " pixmap=" << pixmap.size() << "depth=" << pixmap.depth(); +#endif + + QPixmapData *pd = pixmap.pixmapData(); + if (pd->classId() == QPixmapData::RasterClass) { + const QImage &image = static_cast<QRasterPixmapData *>(pd)->image; + if (image.depth() == 1) { + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + if (s->matrix.type() <= QTransform::TxTranslate) { + ensurePen(); + drawBitmap(pos + QPointF(s->matrix.dx(), s->matrix.dy()), image, &s->penData); + } else { + drawImage(pos, d->rasterBuffer->colorizeBitmap(image, s->pen.color())); + } + } else { + QRasterPaintEngine::drawImage(pos, image); + } + } else { + const QImage image = pixmap.toImage(); + if (pixmap.depth() == 1) { + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + if (s->matrix.type() <= QTransform::TxTranslate) { + ensurePen(); + drawBitmap(pos + QPointF(s->matrix.dx(), s->matrix.dy()), image, &s->penData); + } else { + drawImage(pos, d->rasterBuffer->colorizeBitmap(image, s->pen.color())); + } + } else { + QRasterPaintEngine::drawImage(pos, image); + } + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pixmap, const QRectF &sr) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawPixmap(), r=" << r << " sr=" << sr << " pixmap=" << pixmap.size() << "depth=" << pixmap.depth(); +#endif + + QPixmapData* pd = pixmap.pixmapData(); + if (pd->classId() == QPixmapData::RasterClass) { + const QImage &image = static_cast<QRasterPixmapData *>(pd)->image; + if (image.depth() == 1) { + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + if (s->matrix.type() <= QTransform::TxTranslate + && r.size() == sr.size() + && r.size() == pixmap.size()) { + ensurePen(); + drawBitmap(r.topLeft() + QPointF(s->matrix.dx(), s->matrix.dy()), image, &s->penData); + return; + } else { + drawImage(r, d->rasterBuffer->colorizeBitmap(image, s->pen.color()), sr); + } + } else { + drawImage(r, image, sr); + } + } else { + QRect clippedSource = sr.toAlignedRect().intersected(pixmap.rect()); + const QImage image = pd->toImage(clippedSource); + QRectF translatedSource = sr.translated(-clippedSource.topLeft()); + if (image.depth() == 1) { + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + if (s->matrix.type() <= QTransform::TxTranslate + && r.size() == sr.size() + && r.size() == pixmap.size()) { + ensurePen(); + drawBitmap(r.topLeft() + QPointF(s->matrix.dx(), s->matrix.dy()), image, &s->penData); + return; + } else { + drawImage(r, d->rasterBuffer->colorizeBitmap(image, s->pen.color()), translatedSource); + } + } else { + drawImage(r, image, translatedSource); + } + } +} + +// assumes that rect has positive width and height +static inline const QRect toRect_normalized(const QRectF &rect) +{ + const int x = qRound(rect.x()); + const int y = qRound(rect.y()); + const int w = int(rect.width() + qreal(0.5)); + const int h = int(rect.height() + qreal(0.5)); + + return QRect(x, y, w, h); +} + +static inline int fast_ceil_positive(const qreal &v) +{ + const int iv = int(v); + if (v - iv == 0) + return iv; + else + return iv + 1; +} + +static inline const QRect toAlignedRect_positive(const QRectF &rect) +{ + const int xmin = int(rect.x()); + const int xmax = int(fast_ceil_positive(rect.right())); + const int ymin = int(rect.y()); + const int ymax = int(fast_ceil_positive(rect.bottom())); + return QRect(xmin, ymin, xmax - xmin, ymax - ymin); +} + +/*! + \internal +*/ +void QRasterPaintEngine::drawImage(const QPointF &p, const QImage &img) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawImage(), p=" << p << " image=" << img.size() << "depth=" << img.depth(); +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (s->matrix.type() > QTransform::TxTranslate) { + drawImage(QRectF(p.x(), p.y(), img.width(), img.height()), + img, + QRectF(0, 0, img.width(), img.height())); + } else { + + const QClipData *clip = d->clip(); + QPointF pt(p.x() + s->matrix.dx(), p.y() + s->matrix.dy()); + + if (d->canUseFastImageBlending(d->rasterBuffer->compositionMode, img)) { + SrcOverBlendFunc func = qBlendFunctions[d->rasterBuffer->format][img.format()]; + if (func) { + if (!clip) { + d->drawImage(pt, img, func, d->deviceRect, s->intOpacity); + return; + } else if (clip->hasRectClip) { + d->drawImage(pt, img, func, clip->clipRect, s->intOpacity); + return; + } + } + } + + + + d->image_filler.clip = clip; + d->image_filler.initTexture(&img, s->intOpacity, QTextureData::Plain, img.rect()); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -pt.x(); + d->image_filler.dy = -pt.y(); + QRect rr = img.rect().translated(qRound(pt.x()), qRound(pt.y())); + + fillRect_normalized(rr, &d->image_filler, d); + } + +} + +QRectF qt_mapRect_non_normalizing(const QRectF &r, const QTransform &t) +{ + return QRectF(r.topLeft() * t, r.bottomRight() * t); +} + +namespace { + enum RotationType { + Rotation90, + Rotation180, + Rotation270, + NoRotation + }; + + inline RotationType qRotationType(const QTransform &transform) + { + QTransform::TransformationType type = transform.type(); + + if (type > QTransform::TxRotate) + return NoRotation; + + if (type == QTransform::TxRotate && qFuzzyIsNull(transform.m11()) && qFuzzyCompare(transform.m12(), qreal(-1)) + && qFuzzyCompare(transform.m21(), qreal(1)) && qFuzzyIsNull(transform.m22())) + return Rotation90; + + if (type == QTransform::TxScale && qFuzzyCompare(transform.m11(), qreal(-1)) && qFuzzyIsNull(transform.m12()) + && qFuzzyIsNull(transform.m21()) && qFuzzyCompare(transform.m22(), qreal(-1))) + return Rotation180; + + if (type == QTransform::TxRotate && qFuzzyIsNull(transform.m11()) && qFuzzyCompare(transform.m12(), qreal(1)) + && qFuzzyCompare(transform.m21(), qreal(-1)) && qFuzzyIsNull(transform.m22())) + return Rotation270; + + return NoRotation; + } + + inline bool isPixelAligned(const QRectF &rect) { + return QRectF(rect.toRect()) == rect; + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawImage(), r=" << r << " sr=" << sr << " image=" << img.size() << "depth=" << img.depth(); +#endif + + if (r.isEmpty()) + return; + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + int sr_l = qFloor(sr.left()); + int sr_r = qCeil(sr.right()) - 1; + int sr_t = qFloor(sr.top()); + int sr_b = qCeil(sr.bottom()) - 1; + + if (s->matrix.type() <= QTransform::TxScale && !s->flags.antialiased && sr_l == sr_r && sr_t == sr_b) { + QTransform old = s->matrix; + + // Do whatever fillRect() does, but without premultiplying the color if it's already premultiplied. + QRgb color = img.pixel(sr_l, sr_t); + switch (img.format()) { + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + // Combine premultiplied color with the opacity set on the painter. + d->solid_color_filler.solid.color = + ((((color & 0x00ff00ff) * s->intOpacity) >> 8) & 0x00ff00ff) + | ((((color & 0xff00ff00) >> 8) * s->intOpacity) & 0xff00ff00); + break; + default: + d->solid_color_filler.solid.color = PREMUL(ARGB_COMBINE_ALPHA(color, s->intOpacity)); + break; + } + + if ((d->solid_color_filler.solid.color & 0xff000000) == 0 + && s->composition_mode == QPainter::CompositionMode_SourceOver) { + return; + } + + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); + fillRect(r, &d->solid_color_filler); + + s->matrix = old; + return; + } + + bool stretch_sr = r.width() != sr.width() || r.height() != sr.height(); + + const QClipData *clip = d->clip(); + + if (s->matrix.type() > QTransform::TxTranslate + && !stretch_sr + && (!clip || clip->hasRectClip) + && s->intOpacity == 256 + && (d->rasterBuffer->compositionMode == QPainter::CompositionMode_SourceOver + || d->rasterBuffer->compositionMode == QPainter::CompositionMode_Source) + && d->rasterBuffer->format == img.format() + && (d->rasterBuffer->format == QImage::Format_RGB16 + || d->rasterBuffer->format == QImage::Format_RGB32 + || (d->rasterBuffer->format == QImage::Format_ARGB32_Premultiplied + && d->rasterBuffer->compositionMode == QPainter::CompositionMode_Source))) + { + RotationType rotationType = qRotationType(s->matrix); + + if (rotationType != NoRotation && qMemRotateFunctions[d->rasterBuffer->format][rotationType] && img.rect().contains(sr.toAlignedRect())) { + QRectF transformedTargetRect = s->matrix.mapRect(r); + + if ((!(s->renderHints & QPainter::SmoothPixmapTransform) && !(s->renderHints & QPainter::Antialiasing)) + || (isPixelAligned(transformedTargetRect) && isPixelAligned(sr))) + { + QRect clippedTransformedTargetRect = transformedTargetRect.toRect().intersected(clip ? clip->clipRect : d->deviceRect); + if (clippedTransformedTargetRect.isNull()) + return; + + QRectF clippedTargetRect = s->matrix.inverted().mapRect(QRectF(clippedTransformedTargetRect)); + + QRect clippedSourceRect + = QRectF(sr.x() + clippedTargetRect.x() - r.x(), sr.y() + clippedTargetRect.y() - r.y(), + clippedTargetRect.width(), clippedTargetRect.height()).toRect(); + + uint dbpl = d->rasterBuffer->bytesPerLine(); + uint sbpl = img.bytesPerLine(); + + uchar *dst = d->rasterBuffer->buffer(); + uint bpp = img.depth() >> 3; + + const uchar *srcBase = img.bits() + clippedSourceRect.y() * sbpl + clippedSourceRect.x() * bpp; + uchar *dstBase = dst + clippedTransformedTargetRect.y() * dbpl + clippedTransformedTargetRect.x() * bpp; + + uint cw = clippedSourceRect.width(); + uint ch = clippedSourceRect.height(); + + qMemRotateFunctions[d->rasterBuffer->format][rotationType](srcBase, cw, ch, sbpl, dstBase, dbpl); + + return; + } + } + } + + if (s->matrix.type() > QTransform::TxTranslate || stretch_sr) { + + QRectF targetBounds = s->matrix.mapRect(r); + bool exceedsPrecision = targetBounds.width() > 0xffff + || targetBounds.height() > 0xffff; + + if (!exceedsPrecision && d->canUseFastImageBlending(d->rasterBuffer->compositionMode, img)) { + if (s->matrix.type() > QTransform::TxScale) { + SrcOverTransformFunc func = qTransformFunctions[d->rasterBuffer->format][img.format()]; + if (func && (!clip || clip->hasRectClip)) { + func(d->rasterBuffer->buffer(), d->rasterBuffer->bytesPerLine(), img.bits(), + img.bytesPerLine(), r, sr, !clip ? d->deviceRect : clip->clipRect, + s->matrix, s->intOpacity); + return; + } + } else { + SrcOverScaleFunc func = qScaleFunctions[d->rasterBuffer->format][img.format()]; + if (func && (!clip || clip->hasRectClip)) { + func(d->rasterBuffer->buffer(), d->rasterBuffer->bytesPerLine(), + img.bits(), img.bytesPerLine(), + qt_mapRect_non_normalizing(r, s->matrix), sr, + !clip ? d->deviceRect : clip->clipRect, + s->intOpacity); + return; + } + } + } + + QTransform copy = s->matrix; + copy.translate(r.x(), r.y()); + if (stretch_sr) + copy.scale(r.width() / sr.width(), r.height() / sr.height()); + copy.translate(-sr.x(), -sr.y()); + + d->image_filler_xform.clip = clip; + d->image_filler_xform.initTexture(&img, s->intOpacity, QTextureData::Plain, toAlignedRect_positive(sr)); + if (!d->image_filler_xform.blend) + return; + d->image_filler_xform.setupMatrix(copy, s->flags.bilinear); + + if (!s->flags.antialiased && s->matrix.type() == QTransform::TxScale) { + QRectF rr = s->matrix.mapRect(r); + + const int x1 = qRound(rr.x()); + const int y1 = qRound(rr.y()); + const int x2 = qRound(rr.right()); + const int y2 = qRound(rr.bottom()); + + fillRect_normalized(QRect(x1, y1, x2-x1, y2-y1), &d->image_filler_xform, d); + return; + } + +#ifdef QT_FAST_SPANS + ensureState(); + if (s->flags.tx_noshear || s->matrix.type() == QTransform::TxScale) { + d->initializeRasterizer(&d->image_filler_xform); + d->rasterizer->setAntialiased(s->flags.antialiased); + + const QRectF &rect = r.normalized(); + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f); + + if (s->flags.tx_noshear) + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + else + d->rasterizer->rasterizeLine(a, b, qAbs((s->matrix.m22() * rect.height()) / (s->matrix.m11() * rect.width()))); + return; + } +#endif + QPainterPath path; + path.addRect(r); + QTransform m = s->matrix; + s->matrix = QTransform(m.m11(), m.m12(), m.m13(), + m.m21(), m.m22(), m.m23(), + m.m31(), m.m32(), m.m33()); + fillPath(path, &d->image_filler_xform); + s->matrix = m; + } else { + if (d->canUseFastImageBlending(d->rasterBuffer->compositionMode, img)) { + SrcOverBlendFunc func = qBlendFunctions[d->rasterBuffer->format][img.format()]; + if (func) { + QPointF pt(r.x() + s->matrix.dx(), r.y() + s->matrix.dy()); + if (!clip) { + d->drawImage(pt, img, func, d->deviceRect, s->intOpacity, sr.toRect()); + return; + } else if (clip->hasRectClip) { + d->drawImage(pt, img, func, clip->clipRect, s->intOpacity, sr.toRect()); + return; + } + } + } + + d->image_filler.clip = clip; + d->image_filler.initTexture(&img, s->intOpacity, QTextureData::Plain, toAlignedRect_positive(sr)); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -(r.x() + s->matrix.dx()) + sr.x(); + d->image_filler.dy = -(r.y() + s->matrix.dy()) + sr.y(); + + QRectF rr = r; + rr.translate(s->matrix.dx(), s->matrix.dy()); + + const int x1 = qRound(rr.x()); + const int y1 = qRound(rr.y()); + const int x2 = qRound(rr.right()); + const int y2 = qRound(rr.bottom()); + + fillRect_normalized(QRect(x1, y1, x2-x1, y2-y1), &d->image_filler, d); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &sr) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawTiledPixmap(), r=" << r << "pixmap=" << pixmap.size(); +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + QImage image; + + QPixmapData *pd = pixmap.pixmapData(); + if (pd->classId() == QPixmapData::RasterClass) { + image = static_cast<QRasterPixmapData *>(pd)->image; + } else { + image = pixmap.toImage(); + } + + if (image.depth() == 1) + image = d->rasterBuffer->colorizeBitmap(image, s->pen.color()); + + if (s->matrix.type() > QTransform::TxTranslate) { + QTransform copy = s->matrix; + copy.translate(r.x(), r.y()); + copy.translate(-sr.x(), -sr.y()); + d->image_filler_xform.clip = d->clip(); + d->image_filler_xform.initTexture(&image, s->intOpacity, QTextureData::Tiled); + if (!d->image_filler_xform.blend) + return; + d->image_filler_xform.setupMatrix(copy, s->flags.bilinear); + +#ifdef QT_FAST_SPANS + ensureState(); + if (s->flags.tx_noshear || s->matrix.type() == QTransform::TxScale) { + d->initializeRasterizer(&d->image_filler_xform); + d->rasterizer->setAntialiased(s->flags.antialiased); + + const QRectF &rect = r.normalized(); + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f); + if (s->flags.tx_noshear) + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + else + d->rasterizer->rasterizeLine(a, b, qAbs((s->matrix.m22() * rect.height()) / (s->matrix.m11() * rect.width()))); + return; + } +#endif + QPainterPath path; + path.addRect(r); + fillPath(path, &d->image_filler_xform); + } else { + d->image_filler.clip = d->clip(); + + d->image_filler.initTexture(&image, s->intOpacity, QTextureData::Tiled); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -(r.x() + s->matrix.dx()) + sr.x(); + d->image_filler.dy = -(r.y() + s->matrix.dy()) + sr.y(); + + QRectF rr = r; + rr.translate(s->matrix.dx(), s->matrix.dy()); + fillRect_normalized(rr.toRect().normalized(), &d->image_filler, d); + } +} + + +//QWS hack +static inline bool monoVal(const uchar* s, int x) +{ + return (s[x>>3] << (x&7)) & 0x80; +} + +/*! + \internal +*/ +void QRasterPaintEngine::alphaPenBlt(const void* src, int bpl, int depth, int rx,int ry,int w,int h) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (!s->penData.blend) + return; + + QRasterBuffer *rb = d->rasterBuffer.data(); + + const QRect rect(rx, ry, w, h); + const QClipData *clip = d->clip(); + bool unclipped = false; + if (clip) { + // inlined QRect::intersects + const bool intersects = qMax(clip->xmin, rect.left()) <= qMin(clip->xmax - 1, rect.right()) + && qMax(clip->ymin, rect.top()) <= qMin(clip->ymax - 1, rect.bottom()); + + if (clip->hasRectClip) { + unclipped = rx > clip->xmin + && rx + w < clip->xmax + && ry > clip->ymin + && ry + h < clip->ymax; + } + + if (!intersects) + return; + } else { + // inlined QRect::intersects + const bool intersects = qMax(0, rect.left()) <= qMin(rb->width() - 1, rect.right()) + && qMax(0, rect.top()) <= qMin(rb->height() - 1, rect.bottom()); + if (!intersects) + return; + + // inlined QRect::contains + const bool contains = rect.left() >= 0 && rect.right() < rb->width() + && rect.top() >= 0 && rect.bottom() < rb->height(); + + unclipped = contains && d->isUnclipped_normalized(rect); + } + + ProcessSpans blend = unclipped ? s->penData.unclipped_blend : s->penData.blend; + const uchar * scanline = static_cast<const uchar *>(src); + + if (s->flags.fast_text) { + if (unclipped) { + if (depth == 1) { + if (s->penData.bitmapBlit) { + s->penData.bitmapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl); + return; + } + } else if (depth == 8) { + if (s->penData.alphamapBlit) { + s->penData.alphamapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl, 0); + return; + } + } else if (depth == 32) { + // (A)RGB Alpha mask where the alpha component is not used. + if (s->penData.alphaRGBBlit) { + s->penData.alphaRGBBlit(rb, rx, ry, s->penData.solid.color, + (const uint *) scanline, w, h, bpl / 4, 0); + return; + } + } + } else if (d->deviceDepth == 32 && (depth == 8 || depth == 32)) { + // (A)RGB Alpha mask where the alpha component is not used. + if (!clip) { + int nx = qMax(0, rx); + int ny = qMax(0, ry); + + // Move scanline pointer to compensate for moved x and y + int xdiff = nx - rx; + int ydiff = ny - ry; + scanline += ydiff * bpl; + scanline += xdiff * (depth == 32 ? 4 : 1); + + w -= xdiff; + h -= ydiff; + + if (nx + w > d->rasterBuffer->width()) + w = d->rasterBuffer->width() - nx; + if (ny + h > d->rasterBuffer->height()) + h = d->rasterBuffer->height() - ny; + + rx = nx; + ry = ny; + } + if (depth == 8 && s->penData.alphamapBlit) { + s->penData.alphamapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl, clip); + } else if (depth == 32 && s->penData.alphaRGBBlit) { + s->penData.alphaRGBBlit(rb, rx, ry, s->penData.solid.color, + (const uint *) scanline, w, h, bpl / 4, clip); + } + return; + } + } + + int x0 = 0; + if (rx < 0) { + x0 = -rx; + w -= x0; + } + + int y0 = 0; + if (ry < 0) { + y0 = -ry; + scanline += bpl * y0; + h -= y0; + } + + w = qMin(w, rb->width() - qMax(0, rx)); + h = qMin(h, rb->height() - qMax(0, ry)); + + if (w <= 0 || h <= 0) + return; + + const int NSPANS = 256; + QSpan spans[NSPANS]; + int current = 0; + + const int x1 = x0 + w; + const int y1 = y0 + h; + + if (depth == 1) { + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + if (!monoVal(scanline, x)) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = 255; + int len = 1; + ++x; + // extend span until we find a different one. + while (x < x1 && monoVal(scanline, x)) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + scanline += bpl; + } + } else if (depth == 8) { + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + // Skip those with 0 coverage + if (scanline[x] == 0) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + int coverage = scanline[x]; + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = coverage; + int len = 1; + ++x; + + // extend span until we find a different one. + while (x < x1 && scanline[x] == coverage) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + scanline += bpl; + } + } else { // 32-bit alpha... + uint *sl = (uint *) src; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + // Skip those with 0 coverage + if ((sl[x] & 0x00ffffff) == 0) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + uint rgbCoverage = sl[x]; + int coverage = qGreen(rgbCoverage); + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = coverage; + int len = 1; + ++x; + + // extend span until we find a different one. + while (x < x1 && sl[x] == rgbCoverage) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + sl += bpl / sizeof(uint); + } + } +// qDebug() << "alphaPenBlt: num spans=" << current +// << "span:" << spans->x << spans->y << spans->len << spans->coverage; + // Call span func for current set of spans. + if (current != 0) + blend(current, spans, &s->penData); +} + +bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs, + const QFixedPoint *positions, QFontEngine *fontEngine) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#if !defined(QT_NO_FREETYPE) + if (fontEngine->type() == QFontEngine::Freetype) { + QFontEngineFT *fe = static_cast<QFontEngineFT *>(fontEngine); + QFontEngineFT::GlyphFormat neededFormat = + painter()->device()->devType() == QInternal::Widget + ? fe->defaultGlyphFormat() + : QFontEngineFT::Format_A8; + + if (d_func()->mono_surface + || fe->isBitmapFont() // alphaPenBlt can handle mono, too + ) + neededFormat = QFontEngineFT::Format_Mono; + + if (neededFormat == QFontEngineFT::Format_None) + neededFormat = QFontEngineFT::Format_A8; + + QFontEngineFT::QGlyphSet *gset = fe->defaultGlyphs(); + if (s->matrix.type() >= QTransform::TxScale) { + if (s->matrix.isAffine()) + gset = fe->loadTransformedGlyphSet(s->matrix); + else + gset = 0; + } + + if (!gset || gset->outline_drawing + || !fe->loadGlyphs(gset, glyphs, numGlyphs, positions, neededFormat)) + return false; + + FT_Face lockedFace = 0; + + int depth; + switch (neededFormat) { + case QFontEngineFT::Format_Mono: + depth = 1; + break; + case QFontEngineFT::Format_A8: + depth = 8; + break; + case QFontEngineFT::Format_A32: + depth = 32; + break; + default: + Q_ASSERT(false); + depth = 0; + }; + + for (int i = 0; i < numGlyphs; i++) { + QFixed spp = fe->subPixelPositionForX(positions[i].x); + QFontEngineFT::Glyph *glyph = gset->getGlyph(glyphs[i], spp); + + if (!glyph || glyph->format != neededFormat) { + if (!lockedFace) + lockedFace = fe->lockFace(); + glyph = fe->loadGlyph(gset, glyphs[i], spp, neededFormat); + } + + if (!glyph || !glyph->data) + continue; + + int pitch; + switch (neededFormat) { + case QFontEngineFT::Format_Mono: + pitch = ((glyph->width + 31) & ~31) >> 3; + break; + case QFontEngineFT::Format_A8: + pitch = (glyph->width + 3) & ~3; + break; + case QFontEngineFT::Format_A32: + pitch = glyph->width * 4; + break; + default: + Q_ASSERT(false); + pitch = 0; + }; + + alphaPenBlt(glyph->data, pitch, depth, + qFloor(positions[i].x) + glyph->x, + qFloor(positions[i].y) - glyph->y, + glyph->width, glyph->height); + } + if (lockedFace) + fe->unlockFace(); + } else +#endif + { + QFontEngineGlyphCache::Type glyphType = fontEngine->glyphFormat >= 0 ? QFontEngineGlyphCache::Type(fontEngine->glyphFormat) : d->glyphCacheType; + + QImageTextureGlyphCache *cache = + static_cast<QImageTextureGlyphCache *>(fontEngine->glyphCache(0, glyphType, s->matrix)); + if (!cache) { + cache = new QImageTextureGlyphCache(glyphType, s->matrix); + fontEngine->setGlyphCache(0, cache); + } + + cache->populate(fontEngine, numGlyphs, glyphs, positions); + cache->fillInPendingGlyphs(); + + const QImage &image = cache->image(); + int bpl = image.bytesPerLine(); + + int depth = image.depth(); + int rightShift = 0; + int leftShift = 0; + if (depth == 32) + leftShift = 2; // multiply by 4 + else if (depth == 1) + rightShift = 3; // divide by 8 + + int margin = cache->glyphMargin(); + const uchar *bits = image.bits(); + for (int i=0; i<numGlyphs; ++i) { + + QFixed subPixelPosition = cache->subPixelPositionForX(positions[i].x); + QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphs[i], subPixelPosition); + const QTextureGlyphCache::Coord &c = cache->coords[glyph]; + if (c.isNull()) + continue; + + int x = qFloor(positions[i].x) + c.baseLineX - margin; + int y = qFloor(positions[i].y) - c.baseLineY - margin; + + // printf("drawing [%d %d %d %d] baseline [%d %d], glyph: %d, to: %d %d, pos: %d %d\n", + // c.x, c.y, + // c.w, c.h, + // c.baseLineX, c.baseLineY, + // glyphs[i], + // x, y, + // positions[i].x.toInt(), positions[i].y.toInt()); + + alphaPenBlt(bits + ((c.x << leftShift) >> rightShift) + c.y * bpl, bpl, depth, x, y, c.w, c.h); + } + } + return true; +} + +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) +void QRasterPaintEngine::drawGlyphsS60(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + QFontEngine *fontEngine = ti.fontEngine; + if (fontEngine->type() != QFontEngine::S60FontEngine) { + QPaintEngineEx::drawTextItem(p, ti); + return; + } + + QFontEngineS60 *fe = static_cast<QFontEngineS60 *>(fontEngine); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = s->matrix; + matrix.translate(p.x(), p.y()); + if (matrix.type() == QTransform::TxScale) + fe->setFontScale(matrix.m11()); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + for (int i=0; i<glyphs.size(); ++i) { + TOpenFontCharMetrics tmetrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + fe->getCharacterData(glyphs[i], tmetrics, glyphBitmapBytes, glyphBitmapSize); + const int x = qFloor(positions[i].x + tmetrics.HorizBearingX()); + const int y = qFloor(positions[i].y - tmetrics.HorizBearingY()); + alphaPenBlt(glyphBitmapBytes, glyphBitmapSize.iWidth, 8, x, y, glyphBitmapSize.iWidth, glyphBitmapSize.iHeight); + } + + if (matrix.type() == QTransform::TxScale) + fe->setFontScale(1.0); + + return; +} +#endif // Q_OS_SYMBIAN && QT_NO_FREETYPE + +/*! + * Returns true if the rectangle is completely within the current clip + * state of the paint engine. + */ +bool QRasterPaintEnginePrivate::isUnclipped_normalized(const QRect &r) const +{ + const QClipData *cl = clip(); + if (!cl) { + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = deviceRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } + + + if (cl->hasRectClip) { + // currently all painting functions clips to deviceRect internally + if (cl->clipRect == deviceRect) + return true; + + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = cl->clipRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } else { + return qt_region_strictContains(cl->clipRegion, r); + } +} + +bool QRasterPaintEnginePrivate::isUnclipped(const QRect &rect, + int penWidth) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + const QClipData *cl = clip(); + if (!cl) { + QRect r = rect.normalized(); + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = deviceRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } + + + // currently all painting functions that call this function clip to deviceRect internally + if (cl->hasRectClip && cl->clipRect == deviceRect) + return true; + + if (s->flags.antialiased) + ++penWidth; + + QRect r = rect.normalized(); + if (penWidth > 0) { + r.setX(r.x() - penWidth); + r.setY(r.y() - penWidth); + r.setWidth(r.width() + 2 * penWidth); + r.setHeight(r.height() + 2 * penWidth); + } + + if (cl->hasRectClip) { + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = cl->clipRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } else { + return qt_region_strictContains(cl->clipRegion, r); + } +} + +inline bool QRasterPaintEnginePrivate::isUnclipped(const QRectF &rect, + int penWidth) const +{ + return isUnclipped(rect.normalized().toAlignedRect(), penWidth); +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getBrushFunc(const QRect &rect, + const QSpanData *data) const +{ + return isUnclipped(rect, 0) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getBrushFunc(const QRectF &rect, + const QSpanData *data) const +{ + return isUnclipped(rect, 0) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getPenFunc(const QRect &rect, + const QSpanData *data) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + + if (!s->flags.fast_pen && s->matrix.type() > QTransform::TxTranslate) + return data->blend; + const int penWidth = s->flags.fast_pen ? 1 : qCeil(s->pen.widthF()); + return isUnclipped(rect, penWidth) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getPenFunc(const QRectF &rect, + const QSpanData *data) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + + if (!s->flags.fast_pen && s->matrix.type() > QTransform::TxTranslate) + return data->blend; + const int penWidth = s->flags.fast_pen ? 1 : qCeil(s->lastPen.widthF()); + return isUnclipped(rect, penWidth) ? data->unclipped_blend : data->blend; +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawStaticTextItem(QStaticTextItem *textItem) +{ + ensurePen(); + ensureState(); + + drawCachedGlyphs(textItem->numGlyphs, textItem->glyphs, textItem->glyphPositions, + textItem->fontEngine()); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + Q_D(QRasterPaintEngine); + fprintf(stderr," - QRasterPaintEngine::drawTextItem(), (%.2f,%.2f), string=%s ct=%d\n", + p.x(), p.y(), QString::fromRawData(ti.chars, ti.num_chars).toLatin1().data(), + d->glyphCacheType); +#endif + + ensurePen(); + ensureState(); + +#if defined (Q_WS_WIN) || defined(Q_WS_MAC) + + bool drawCached = true; + + if (s->matrix.type() >= QTransform::TxProject) + drawCached = false; + + // don't try to cache huge fonts + const qreal pixelSize = ti.fontEngine->fontDef.pixelSize; + if (pixelSize * pixelSize * qAbs(s->matrix.determinant()) >= 64 * 64) + drawCached = false; + + // ### Remove the TestFontEngine and Box engine crap, in these + // ### cases we should delegate painting to the font engine + // ### directly... + +#if defined(Q_WS_WIN) && !defined(Q_WS_WINCE) + QFontEngine::Type fontEngineType = ti.fontEngine->type(); + // qDebug() << "type" << fontEngineType << s->matrix.type(); + if ((fontEngineType == QFontEngine::Win && !((QFontEngineWin *) ti.fontEngine)->ttf && s->matrix.type() > QTransform::TxTranslate) + || (s->matrix.type() <= QTransform::TxTranslate + && (fontEngineType == QFontEngine::TestFontEngine + || fontEngineType == QFontEngine::Box))) { + drawCached = false; + } +#else + if (s->matrix.type() > QTransform::TxTranslate) + drawCached = false; +#endif + if (drawCached) { + QRasterPaintEngineState *s = state(); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + + QTransform matrix = s->matrix; + matrix.translate(p.x(), p.y()); + + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + drawCachedGlyphs(glyphs.size(), glyphs.constData(), positions.constData(), ti.fontEngine); + return; + } + +#elif defined (Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) // Q_WS_WIN || Q_WS_MAC + if (s->matrix.type() <= QTransform::TxTranslate + || (s->matrix.type() == QTransform::TxScale + && (qFuzzyCompare(s->matrix.m11(), s->matrix.m22())))) { + drawGlyphsS60(p, ti); + return; + } +#else // Q_WS_WIN || Q_WS_MAC + + QFontEngine *fontEngine = ti.fontEngine; + +#if defined(Q_WS_QWS) + if (fontEngine->type() == QFontEngine::Box) { + fontEngine->draw(this, qFloor(p.x()), qFloor(p.y()), ti); + return; + } + + if (s->matrix.type() < QTransform::TxScale + && (fontEngine->type() == QFontEngine::QPF1 || fontEngine->type() == QFontEngine::QPF2 + || (fontEngine->type() == QFontEngine::Proxy + && !(static_cast<QProxyFontEngine *>(fontEngine)->drawAsOutline())) + )) { + fontEngine->draw(this, qFloor(p.x()), qFloor(p.y()), ti); + return; + } +#endif // Q_WS_QWS + +#ifdef Q_WS_QPA + if (s->matrix.type() < QTransform::TxScale) { + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = state()->transform(); + + qreal _x = qFloor(p.x()); + qreal _y = qFloor(p.y()); + matrix.translate(_x, _y); + + fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + for(int i = 0; i < glyphs.size(); i++) { + QImage img = fontEngine->alphaMapForGlyph(glyphs[i]); + glyph_metrics_t metrics = fontEngine->boundingBox(glyphs[i]); + alphaPenBlt(img.bits(), img.bytesPerLine(), img.depth(), + qRound(positions[i].x + metrics.x), + qRound(positions[i].y + metrics.y), + img.width(), img.height()); + } + return; + } +#endif //Q_WS_QPA + +#if (defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) && !defined(QT_NO_FREETYPE) + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) + if (fontEngine->type() == QFontEngine::QPF2) { + QFontEngine *renderingEngine = static_cast<QFontEngineQPF *>(fontEngine)->renderingEngine(); + if (renderingEngine) + fontEngine = renderingEngine; + } +#endif + + if (fontEngine->type() != QFontEngine::Freetype) { + QPaintEngineEx::drawTextItem(p, ti); + return; + } + + QFontEngineFT *fe = static_cast<QFontEngineFT *>(fontEngine); + + QTransform matrix = s->matrix; + matrix.translate(p.x(), p.y()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + fe->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + if (!drawCachedGlyphs(glyphs.size(), glyphs.constData(), positions.constData(), fontEngine)) + QPaintEngine::drawTextItem(p, ti); + + return; +#endif +#endif + + QPaintEngineEx::drawTextItem(p, ti); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + qreal pw = s->lastPen.widthF(); + if (!s->flags.fast_pen && (s->matrix.type() > QTransform::TxTranslate || pw > 1)) { + QPaintEngineEx::drawPoints(points, pointCount); + + } else { + if (!s->penData.blend) + return; + + QVarLengthArray<QT_FT_Span, 4096> array(pointCount); + QT_FT_Span span = { 0, 1, 0, 255 }; + const QPointF *end = points + pointCount; + qreal trans_x, trans_y; + int x, y; + int left = d->deviceRect.x(); + int right = left + d->deviceRect.width(); + int top = d->deviceRect.y(); + int bottom = top + d->deviceRect.height(); + int count = 0; + while (points < end) { + s->matrix.map(points->x(), points->y(), &trans_x, &trans_y); + x = qFloor(trans_x); + y = qFloor(trans_y); + if (x >= left && x < right && y >= top && y < bottom) { + if (count > 0) { + const QT_FT_Span &last = array[count - 1]; + // spans must be sorted on y (primary) and x (secondary) + if (y < last.y || (y == last.y && x < last.x)) { + s->penData.blend(count, array.constData(), &s->penData); + count = 0; + } + } + + span.x = x; + span.y = y; + array[count++] = span; + } + ++points; + } + + if (count > 0) + s->penData.blend(count, array.constData(), &s->penData); + } +} + + +void QRasterPaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + double pw = s->lastPen.widthF(); + if (!s->flags.fast_pen && (s->matrix.type() > QTransform::TxTranslate || pw > 1)) { + QPaintEngineEx::drawPoints(points, pointCount); + + } else { + if (!s->penData.blend) + return; + + QVarLengthArray<QT_FT_Span, 4096> array(pointCount); + QT_FT_Span span = { 0, 1, 0, 255 }; + const QPoint *end = points + pointCount; + qreal trans_x, trans_y; + int x, y; + int left = d->deviceRect.x(); + int right = left + d->deviceRect.width(); + int top = d->deviceRect.y(); + int bottom = top + d->deviceRect.height(); + int count = 0; + while (points < end) { + s->matrix.map(points->x(), points->y(), &trans_x, &trans_y); + x = qFloor(trans_x); + y = qFloor(trans_y); + if (x >= left && x < right && y >= top && y < bottom) { + if (count > 0) { + const QT_FT_Span &last = array[count - 1]; + // spans must be sorted on y (primary) and x (secondary) + if (y < last.y || (y == last.y && x < last.x)) { + s->penData.blend(count, array.constData(), &s->penData); + count = 0; + } + } + + span.x = x; + span.y = y; + array[count++] = span; + } + ++points; + } + + if (count > 0) + s->penData.blend(count, array.constData(), &s->penData); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawLines(const QLine *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawLine()"; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (s->flags.fast_pen) { + QIntRect bounds; bounds.set(d->deviceRect); + LineDrawMode mode = s->lastPen.capStyle() == Qt::FlatCap + ? LineDrawNormal + : LineDrawIncludeLastPixel; + + int m11 = int(s->matrix.m11()); + int m22 = int(s->matrix.m22()); + int dx = qFloor(s->matrix.dx()); + int dy = qFloor(s->matrix.dy()); + for (int i=0; i<lineCount; ++i) { + int dashOffset = int(s->lastPen.dashOffset()); + if (s->flags.int_xform) { + const QLine &l = lines[i]; + int x1 = l.x1() * m11 + dx; + int y1 = l.y1() * m22 + dy; + int x2 = l.x2() * m11 + dx; + int y2 = l.y2() * m22 + dy; + + const QRect brect(QPoint(x1, y1), QPoint(x2, y2)); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, penBlend, + &s->penData, mode, bounds, + &dashOffset); + } else { + QLineF line = lines[i] * s->matrix; + const QRectF brect(QPointF(line.x1(), line.y1()), + QPointF(line.x2(), line.y2())); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + &s->lastPen, penBlend, + &s->penData, mode, bounds, + &dashOffset); + } + } + } else if (s->penData.blend) { + QPaintEngineEx::drawLines(lines, lineCount); + } +} + +void QRasterPaintEnginePrivate::rasterizeLine_dashed(QLineF line, + qreal width, + int *dashIndex, + qreal *dashOffset, + bool *inDash) +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + const QPen &pen = s->lastPen; + const bool squareCap = (pen.capStyle() == Qt::SquareCap); + const QVector<qreal> pattern = pen.dashPattern(); + + qreal patternLength = 0; + for (int i = 0; i < pattern.size(); ++i) + patternLength += pattern.at(i); + + if (patternLength <= 0) + return; + + qreal length = line.length(); + Q_ASSERT(length > 0); + while (length > 0) { + const bool rasterize = *inDash; + qreal dash = (pattern.at(*dashIndex) - *dashOffset) * width; + QLineF l = line; + + if (dash >= length) { + dash = length; + *dashOffset += dash / width; + length = 0; + } else { + *dashOffset = 0; + *inDash = !(*inDash); + if (++*dashIndex >= pattern.size()) + *dashIndex = 0; + length -= dash; + l.setLength(dash); + line.setP1(l.p2()); + } + + if (rasterize && dash > 0) + rasterizer->rasterizeLine(l.p1(), l.p2(), width / dash, squareCap); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawLines(const QLineF *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawLine()"; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (!s->penData.blend) + return; + if (s->flags.fast_pen) { + QIntRect bounds; + bounds.set(d->deviceRect); + LineDrawMode mode = s->lastPen.capStyle() == Qt::FlatCap + ? LineDrawNormal + : LineDrawIncludeLastPixel; + + for (int i=0; i<lineCount; ++i) { + int dashOffset = int(s->lastPen.dashOffset()); + QLineF line = lines[i] * s->matrix; + const QRectF brect(QPointF(line.x1(), line.y1()), + QPointF(line.x2(), line.y2())); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + &s->lastPen, + penBlend, &s->penData, mode, + bounds, &dashOffset); + } + } else { + QPaintEngineEx::drawLines(lines, lineCount); + } +} + + +/*! + \reimp +*/ +void QRasterPaintEngine::drawEllipse(const QRectF &rect) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (((qpen_style(s->lastPen) == Qt::SolidLine && s->flags.fast_pen) + || (qpen_style(s->lastPen) == Qt::NoPen && !s->flags.antialiased)) + && qMax(rect.width(), rect.height()) < QT_RASTER_COORD_LIMIT + && !rect.isEmpty() + && s->matrix.type() <= QTransform::TxScale) // no shear + { + ensureBrush(); + const QRectF r = s->matrix.mapRect(rect); + ProcessSpans penBlend = d->getPenFunc(r, &s->penData); + ProcessSpans brushBlend = d->getBrushFunc(r, &s->brushData); + const QRect brect = QRect(int(r.x()), int(r.y()), + int_dim(r.x(), r.width()), + int_dim(r.y(), r.height())); + if (brect == r) { + drawEllipse_midpoint_i(brect, d->deviceRect, penBlend, brushBlend, + &s->penData, &s->brushData); + return; + } + } + QPaintEngineEx::drawEllipse(rect); +} + +/*! + \internal +*/ +#ifdef Q_WS_MAC +void QRasterPaintEngine::setCGContext(CGContextRef ctx) +{ + Q_D(QRasterPaintEngine); + d->cgContext = ctx; +} + +/*! + \internal +*/ +CGContextRef QRasterPaintEngine::getCGContext() const +{ + Q_D(const QRasterPaintEngine); + return d->cgContext; +} +#endif + +#ifdef Q_WS_WIN +/*! + \internal +*/ +void QRasterPaintEngine::setDC(HDC hdc) { + Q_D(QRasterPaintEngine); + d->hdc = hdc; +} + +/*! + \internal +*/ +HDC QRasterPaintEngine::getDC() const +{ + Q_D(const QRasterPaintEngine); + return d->hdc; +} + +/*! + \internal +*/ +void QRasterPaintEngine::releaseDC(HDC) const +{ +} + +#endif + +/*! + \internal +*/ +QPoint QRasterPaintEngine::coordinateOffset() const +{ + return QPoint(0, 0); +} + +/*! + Draws the given color \a spans with the specified \a color. The \a + count parameter specifies the number of spans. + + The default implementation does nothing; reimplement this function + to draw the given color \a spans with the specified \a color. Note + that this function \e must be reimplemented if the framebuffer is + not memory-mapped. + + \sa drawBufferSpan() +*/ +#if defined(Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +void QRasterPaintEngine::drawColorSpans(const QSpan *spans, int count, uint color) +{ + Q_UNUSED(spans); + Q_UNUSED(count); + Q_UNUSED(color); + qFatal("QRasterPaintEngine::drawColorSpans must be reimplemented on " + "a non memory-mapped device"); +} + +/*! + \fn void QRasterPaintEngine::drawBufferSpan(const uint *buffer, int size, int x, int y, int length, uint alpha) + + Draws the given \a buffer. + + The default implementation does nothing; reimplement this function + to draw a buffer that contains more than one color. Note that this + function \e must be reimplemented if the framebuffer is not + memory-mapped. + + The \a size parameter specifies the total size of the given \a + buffer, while the \a length parameter specifies the number of + pixels to draw. The buffer's position is given by (\a x, \a + y). The provided \a alpha value is added to each pixel in the + buffer when drawing. + + \sa drawColorSpans() +*/ +void QRasterPaintEngine::drawBufferSpan(const uint *buffer, int bufsize, + int x, int y, int length, uint const_alpha) +{ + Q_UNUSED(buffer); + Q_UNUSED(bufsize); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(length); + Q_UNUSED(const_alpha); + qFatal("QRasterPaintEngine::drawBufferSpan must be reimplemented on " + "a non memory-mapped device"); +} +#endif // Q_WS_QWS + +void QRasterPaintEngine::drawBitmap(const QPointF &pos, const QImage &image, QSpanData *fg) +{ + Q_ASSERT(fg); + if (!fg->blend) + return; + Q_D(QRasterPaintEngine); + + Q_ASSERT(image.depth() == 1); + + const int spanCount = 256; + QT_FT_Span spans[spanCount]; + int n = 0; + + // Boundaries + int w = image.width(); + int h = image.height(); + int ymax = qMin(qRound(pos.y() + h), d->rasterBuffer->height()); + int ymin = qMax(qRound(pos.y()), 0); + int xmax = qMin(qRound(pos.x() + w), d->rasterBuffer->width()); + int xmin = qMax(qRound(pos.x()), 0); + + int x_offset = xmin - qRound(pos.x()); + + QImage::Format format = image.format(); + for (int y = ymin; y < ymax; ++y) { + const uchar *src = image.scanLine(y - qRound(pos.y())); + if (format == QImage::Format_MonoLSB) { + for (int x = 0; x < xmax - xmin; ++x) { + int src_x = x + x_offset; + uchar pixel = src[src_x >> 3]; + if (!pixel) { + x += 7 - (src_x%8); + continue; + } + if (pixel & (0x1 << (src_x & 7))) { + spans[n].x = xmin + x; + spans[n].y = y; + spans[n].coverage = 255; + int len = 1; + while (src_x < w-1 && src[(src_x+1) >> 3] & (0x1 << ((src_x+1) & 7))) { + ++src_x; + ++len; + } + spans[n].len = ((len + spans[n].x) > xmax) ? (xmax - spans[n].x) : len; + x += len; + ++n; + if (n == spanCount) { + fg->blend(n, spans, fg); + n = 0; + } + } + } + } else { + for (int x = 0; x < xmax - xmin; ++x) { + int src_x = x + x_offset; + uchar pixel = src[src_x >> 3]; + if (!pixel) { + x += 7 - (src_x%8); + continue; + } + if (pixel & (0x80 >> (x & 7))) { + spans[n].x = xmin + x; + spans[n].y = y; + spans[n].coverage = 255; + int len = 1; + while (src_x < w-1 && src[(src_x+1) >> 3] & (0x80 >> ((src_x+1) & 7))) { + ++src_x; + ++len; + } + spans[n].len = ((len + spans[n].x) > xmax) ? (xmax - spans[n].x) : len; + x += len; + ++n; + if (n == spanCount) { + fg->blend(n, spans, fg); + n = 0; + } + } + } + } + } + if (n) { + fg->blend(n, spans, fg); + n = 0; + } +} + +/*! + \enum QRasterPaintEngine::ClipType + \internal + + \value RectClip Indicates that the currently set clip is a single rectangle. + \value ComplexClip Indicates that the currently set clip is a combination of several shapes. +*/ + +/*! + \internal + Returns the type of the clip currently set. +*/ +QRasterPaintEngine::ClipType QRasterPaintEngine::clipType() const +{ + Q_D(const QRasterPaintEngine); + + const QClipData *clip = d->clip(); + if (!clip || clip->hasRectClip) + return RectClip; + else + return ComplexClip; +} + +/*! + \internal + Returns the bounding rect of the currently set clip. +*/ +QRect QRasterPaintEngine::clipBoundingRect() const +{ + Q_D(const QRasterPaintEngine); + + const QClipData *clip = d->clip(); + + if (!clip) + return d->deviceRect; + + if (clip->hasRectClip) + return clip->clipRect; + + return QRect(clip->xmin, clip->ymin, clip->xmax - clip->xmin, clip->ymax - clip->ymin); +} + +static void qt_merge_clip(const QClipData *c1, const QClipData *c2, QClipData *result) +{ + Q_ASSERT(c1->clipSpanHeight == c2->clipSpanHeight && c1->clipSpanHeight == result->clipSpanHeight); + + QVarLengthArray<short, 4096> buffer; + + QClipData::ClipLine *c1ClipLines = const_cast<QClipData *>(c1)->clipLines(); + QClipData::ClipLine *c2ClipLines = const_cast<QClipData *>(c2)->clipLines(); + result->initialize(); + + for (int y = 0; y < c1->clipSpanHeight; ++y) { + const QSpan *c1_spans = c1ClipLines[y].spans; + int c1_count = c1ClipLines[y].count; + const QSpan *c2_spans = c2ClipLines[y].spans; + int c2_count = c2ClipLines[y].count; + + if (c1_count == 0 && c2_count == 0) + continue; + if (c1_count == 0) { + result->appendSpans(c2_spans, c2_count); + continue; + } else if (c2_count == 0) { + result->appendSpans(c1_spans, c1_count); + continue; + } + + // we need to merge the two + + // find required length + int max = qMax(c1_spans[c1_count - 1].x + c1_spans[c1_count - 1].len, + c2_spans[c2_count - 1].x + c2_spans[c2_count - 1].len); + buffer.resize(max); + memset(buffer.data(), 0, buffer.size() * sizeof(short)); + + // Fill with old spans. + for (int i = 0; i < c1_count; ++i) { + const QSpan *cs = c1_spans + i; + for (int j=cs->x; j<cs->x + cs->len; ++j) + buffer[j] = cs->coverage; + } + + // Fill with new spans + for (int i = 0; i < c2_count; ++i) { + const QSpan *cs = c2_spans + i; + for (int j = cs->x; j < cs->x + cs->len; ++j) { + buffer[j] += cs->coverage; + if (buffer[j] > 255) + buffer[j] = 255; + } + } + + int x = 0; + while (x<max) { + + // Skip to next span + while (x < max && buffer[x] == 0) ++x; + if (x >= max) break; + + int sx = x; + int coverage = buffer[x]; + + // Find length of span + while (x < max && buffer[x] == coverage) + ++x; + + result->appendSpan(sx, x - sx, y, coverage); + } + } +} + +void QRasterPaintEnginePrivate::initializeRasterizer(QSpanData *data) +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + rasterizer->setAntialiased(s->flags.antialiased); + + QRect clipRect(deviceRect); + ProcessSpans blend; + // ### get from optimized rectbased QClipData + + const QClipData *c = clip(); + if (c) { + const QRect r(QPoint(c->xmin, c->ymin), + QSize(c->xmax - c->xmin, c->ymax - c->ymin)); + clipRect = clipRect.intersected(r); + blend = data->blend; + } else { + blend = data->unclipped_blend; + } + + rasterizer->setClipRect(clipRect); + rasterizer->initialize(blend, data); +} + +void QRasterPaintEnginePrivate::rasterize(QT_FT_Outline *outline, + ProcessSpans callback, + QSpanData *spanData, QRasterBuffer *rasterBuffer) +{ + if (!callback || !outline) + return; + + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + if (!s->flags.antialiased) { + initializeRasterizer(spanData); + + const Qt::FillRule fillRule = outline->flags == QT_FT_OUTLINE_NONE + ? Qt::WindingFill + : Qt::OddEvenFill; + + rasterizer->rasterize(outline, fillRule); + return; + } + + rasterize(outline, callback, (void *)spanData, rasterBuffer); +} + +extern "C" { + int q_gray_rendered_spans(QT_FT_Raster raster); +} + +void QRasterPaintEnginePrivate::rasterize(QT_FT_Outline *outline, + ProcessSpans callback, + void *userData, QRasterBuffer *) +{ + if (!callback || !outline) + return; + + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + if (!s->flags.antialiased) { + rasterizer->setAntialiased(s->flags.antialiased); + rasterizer->setClipRect(deviceRect); + rasterizer->initialize(callback, userData); + + const Qt::FillRule fillRule = outline->flags == QT_FT_OUTLINE_NONE + ? Qt::WindingFill + : Qt::OddEvenFill; + + rasterizer->rasterize(outline, fillRule); + return; + } + + // Initial size for raster pool is MINIMUM_POOL_SIZE so as to + // minimize memory reallocations. However if initial size for + // raster pool is changed for lower value, reallocations will + // occur normally. + const int rasterPoolInitialSize = MINIMUM_POOL_SIZE; + int rasterPoolSize = rasterPoolInitialSize; + unsigned char *rasterPoolBase; +#if defined(Q_WS_WIN64) + rasterPoolBase = + // We make use of setjmp and longjmp in qgrayraster.c which requires + // 16-byte alignment, hence we hardcode this requirement here.. + (unsigned char *) _aligned_malloc(rasterPoolSize, sizeof(void*) * 2); +#else + unsigned char rasterPoolOnStack[rasterPoolInitialSize]; + rasterPoolBase = rasterPoolOnStack; +#endif + Q_CHECK_PTR(rasterPoolBase); + + qt_ft_grays_raster.raster_reset(*grayRaster.data(), rasterPoolBase, rasterPoolSize); + + void *data = userData; + + QT_FT_BBox clip_box = { deviceRect.x(), + deviceRect.y(), + deviceRect.x() + deviceRect.width(), + deviceRect.y() + deviceRect.height() }; + + QT_FT_Raster_Params rasterParams; + rasterParams.target = 0; + rasterParams.source = outline; + rasterParams.flags = QT_FT_RASTER_FLAG_CLIP; + rasterParams.gray_spans = 0; + rasterParams.black_spans = 0; + rasterParams.bit_test = 0; + rasterParams.bit_set = 0; + rasterParams.user = data; + rasterParams.clip_box = clip_box; + + bool done = false; + int error; + + int rendered_spans = 0; + + while (!done) { + + rasterParams.flags |= (QT_FT_RASTER_FLAG_AA | QT_FT_RASTER_FLAG_DIRECT); + rasterParams.gray_spans = callback; + rasterParams.skip_spans = rendered_spans; + error = qt_ft_grays_raster.raster_render(*grayRaster.data(), &rasterParams); + + // Out of memory, reallocate some more and try again... + if (error == -6) { // ErrRaster_OutOfMemory from qgrayraster.c + int new_size = rasterPoolSize * 2; + if (new_size > 1024 * 1024) { + qWarning("QPainter: Rasterization of primitive failed"); + break; + } + + rendered_spans += q_gray_rendered_spans(*grayRaster.data()); + +#if defined(Q_WS_WIN64) + _aligned_free(rasterPoolBase); +#else + if (rasterPoolBase != rasterPoolOnStack) // initially on the stack + free(rasterPoolBase); +#endif + + rasterPoolSize = new_size; + rasterPoolBase = +#if defined(Q_WS_WIN64) + // We make use of setjmp and longjmp in qgrayraster.c which requires + // 16-byte alignment, hence we hardcode this requirement here.. + (unsigned char *) _aligned_malloc(rasterPoolSize, sizeof(void*) * 2); +#else + (unsigned char *) malloc(rasterPoolSize); +#endif + Q_CHECK_PTR(rasterPoolBase); // note: we just freed the old rasterPoolBase. I hope it's not fatal. + + qt_ft_grays_raster.raster_done(*grayRaster.data()); + qt_ft_grays_raster.raster_new(grayRaster.data()); + qt_ft_grays_raster.raster_reset(*grayRaster.data(), rasterPoolBase, rasterPoolSize); + } else { + done = true; + } + } + +#if defined(Q_WS_WIN64) + _aligned_free(rasterPoolBase); +#else + if (rasterPoolBase != rasterPoolOnStack) // initially on the stack + free(rasterPoolBase); +#endif +} + +void QRasterPaintEnginePrivate::recalculateFastImages() +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + s->flags.fast_images = !(s->renderHints & QPainter::SmoothPixmapTransform) + && s->matrix.type() <= QTransform::TxShear; +} + +bool QRasterPaintEnginePrivate::canUseFastImageBlending(QPainter::CompositionMode mode, const QImage &image) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + + return s->flags.fast_images + && (mode == QPainter::CompositionMode_SourceOver + || (mode == QPainter::CompositionMode_Source + && !image.hasAlphaChannel())); +} + +QImage QRasterBuffer::colorizeBitmap(const QImage &image, const QColor &color) +{ + Q_ASSERT(image.depth() == 1); + + QImage sourceImage = image.convertToFormat(QImage::Format_MonoLSB); + QImage dest = QImage(sourceImage.size(), QImage::Format_ARGB32_Premultiplied); + + QRgb fg = PREMUL(color.rgba()); + QRgb bg = 0; + + int height = sourceImage.height(); + int width = sourceImage.width(); + for (int y=0; y<height; ++y) { + uchar *source = sourceImage.scanLine(y); + QRgb *target = reinterpret_cast<QRgb *>(dest.scanLine(y)); + if (!source || !target) + QT_THROW(std::bad_alloc()); // we must have run out of memory + for (int x=0; x < width; ++x) + target[x] = (source[x>>3] >> (x&7)) & 1 ? fg : bg; + } + return dest; +} + +QRasterBuffer::~QRasterBuffer() +{ +} + +void QRasterBuffer::init() +{ + compositionMode = QPainter::CompositionMode_SourceOver; + monoDestinationWithClut = false; + destColor0 = 0; + destColor1 = 0; +} + +QImage::Format QRasterBuffer::prepare(QImage *image) +{ + m_buffer = (uchar *)image->bits(); + m_width = qMin(QT_RASTER_COORD_LIMIT, image->width()); + m_height = qMin(QT_RASTER_COORD_LIMIT, image->height()); + bytes_per_pixel = image->depth()/8; + bytes_per_line = image->bytesPerLine(); + + format = image->format(); + drawHelper = qDrawHelper + format; + if (image->depth() == 1 && image->colorTable().size() == 2) { + monoDestinationWithClut = true; + destColor0 = PREMUL(image->colorTable()[0]); + destColor1 = PREMUL(image->colorTable()[1]); + } + + return format; +} + +void QRasterBuffer::resetBuffer(int val) +{ + memset(m_buffer, val, m_height*bytes_per_line); +} + + +#if defined(Q_WS_QWS) +void QRasterBuffer::prepare(QCustomRasterPaintDevice *device) +{ + m_buffer = reinterpret_cast<uchar*>(device->memory()); + m_width = qMin(QT_RASTER_COORD_LIMIT, device->width()); + m_height = qMin(QT_RASTER_COORD_LIMIT, device->height()); + bytes_per_pixel = device->depth() / 8; + bytes_per_line = device->bytesPerLine(); + format = device->format(); +#ifndef QT_NO_RASTERCALLBACKS + if (!m_buffer) + drawHelper = qDrawHelperCallback + format; + else +#endif + drawHelper = qDrawHelper + format; +} + +int QCustomRasterPaintDevice::metric(PaintDeviceMetric m) const +{ + switch (m) { + case PdmWidth: + return widget->frameGeometry().width(); + case PdmHeight: + return widget->frameGeometry().height(); + default: + break; + } + + return qt_paint_device_metric(widget, m); +} + +int QCustomRasterPaintDevice::bytesPerLine() const +{ + return (width() * depth() + 7) / 8; +} + +#elif defined(Q_OS_SYMBIAN) + +void QRasterBuffer::prepareBuffer(int /* width */, int /* height */) +{ +} + +#endif // Q_OS_SYMBIAN + +/*! + \class QCustomRasterPaintDevice + \preliminary + \ingroup qws + \since 4.2 + + \brief The QCustomRasterPaintDevice class is provided to activate + hardware accelerated paint engines in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + In \l{Qt for Embedded Linux}, painting is a pure software + implementation. But starting with Qt 4.2, it is + possible to add an accelerated graphics driver to take advantage + of available hardware resources. + + Hardware acceleration is accomplished by creating a custom screen + driver, accelerating the copying from memory to the screen, and + implementing a custom paint engine accelerating the various + painting operations. Then a custom paint device (derived from the + QCustomRasterPaintDevice class) and a custom window surface + (derived from QWSWindowSurface) must be implemented to make + \l{Qt for Embedded Linux} aware of the accelerated driver. + + See the \l {Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + documentation for details. + + \sa QRasterPaintEngine, QPaintDevice +*/ + +/*! + \fn QCustomRasterPaintDevice::QCustomRasterPaintDevice(QWidget *widget) + + Constructs a custom raster based paint device for the given + top-level \a widget. +*/ + +/*! + \fn int QCustomRasterPaintDevice::bytesPerLine() const + + Returns the number of bytes per line in the framebuffer. Note that + this number might be larger than the framebuffer width. +*/ + +/*! + \fn int QCustomRasterPaintDevice::devType() const + \internal +*/ + +/*! + \fn QImage::Format QCustomRasterPaintDevice::format() const + + Returns the format of the device's memory buffet. + + The default format is QImage::Format_ARGB32_Premultiplied. The + only other valid format is QImage::Format_RGB16. +*/ + +/*! + \fn void * QCustomRasterPaintDevice::memory () const + + Returns a pointer to the paint device's memory buffer, or 0 if no + such buffer exists. +*/ + +/*! + \fn int QCustomRasterPaintDevice::metric ( PaintDeviceMetric m ) const + \reimp +*/ + +/*! + \fn QSize QCustomRasterPaintDevice::size () const + \internal +*/ + + +QClipData::QClipData(int height) +{ + clipSpanHeight = height; + m_clipLines = 0; + + allocated = 0; + m_spans = 0; + xmin = xmax = ymin = ymax = 0; + count = 0; + + enabled = true; + hasRectClip = hasRegionClip = false; +} + +QClipData::~QClipData() +{ + if (m_clipLines) + free(m_clipLines); + if (m_spans) + free(m_spans); +} + +void QClipData::initialize() +{ + if (m_spans) + return; + + if (!m_clipLines) + m_clipLines = (ClipLine *)calloc(sizeof(ClipLine), clipSpanHeight); + + Q_CHECK_PTR(m_clipLines); + QT_TRY { + m_spans = (QSpan *)malloc(clipSpanHeight*sizeof(QSpan)); + allocated = clipSpanHeight; + Q_CHECK_PTR(m_spans); + + QT_TRY { + if (hasRectClip) { + int y = 0; + while (y < ymin) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + const int len = clipRect.width(); + count = 0; + while (y < ymax) { + QSpan *span = m_spans + count; + span->x = xmin; + span->len = len; + span->y = y; + span->coverage = 255; + ++count; + + m_clipLines[y].spans = span; + m_clipLines[y].count = 1; + ++y; + } + + while (y < clipSpanHeight) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + } else if (hasRegionClip) { + + const QVector<QRect> rects = clipRegion.rects(); + const int numRects = rects.size(); + + { // resize + const int maxSpans = (ymax - ymin) * numRects; + if (maxSpans > allocated) { + m_spans = q_check_ptr((QSpan *)realloc(m_spans, maxSpans * sizeof(QSpan))); + allocated = maxSpans; + } + } + + int y = 0; + int firstInBand = 0; + count = 0; + while (firstInBand < numRects) { + const int currMinY = rects.at(firstInBand).y(); + const int currMaxY = currMinY + rects.at(firstInBand).height(); + + while (y < currMinY) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + int lastInBand = firstInBand; + while (lastInBand + 1 < numRects && rects.at(lastInBand+1).top() == y) + ++lastInBand; + + while (y < currMaxY) { + + m_clipLines[y].spans = m_spans + count; + m_clipLines[y].count = lastInBand - firstInBand + 1; + + for (int r = firstInBand; r <= lastInBand; ++r) { + const QRect &currRect = rects.at(r); + QSpan *span = m_spans + count; + span->x = currRect.x(); + span->len = currRect.width(); + span->y = y; + span->coverage = 255; + ++count; + } + ++y; + } + + firstInBand = lastInBand + 1; + } + + Q_ASSERT(count <= allocated); + + while (y < clipSpanHeight) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + } + } QT_CATCH(...) { + free(m_spans); // have to free m_spans again or someone might think that we were successfully initialized. + m_spans = 0; + QT_RETHROW; + } + } QT_CATCH(...) { + free(m_clipLines); // same for clipLines + m_clipLines = 0; + QT_RETHROW; + } +} + +void QClipData::fixup() +{ + Q_ASSERT(m_spans); + + if (count == 0) { + ymin = ymax = xmin = xmax = 0; + return; + } + + int y = -1; + ymin = m_spans[0].y; + ymax = m_spans[count-1].y + 1; + xmin = INT_MAX; + xmax = 0; + + const int firstLeft = m_spans[0].x; + const int firstRight = m_spans[0].x + m_spans[0].len; + bool isRect = true; + + for (int i = 0; i < count; ++i) { + QT_FT_Span_& span = m_spans[i]; + + if (span.y != y) { + if (span.y != y + 1 && y != -1) + isRect = false; + y = span.y; + m_clipLines[y].spans = &span; + m_clipLines[y].count = 1; + } else + ++m_clipLines[y].count; + + const int spanLeft = span.x; + const int spanRight = spanLeft + span.len; + + if (spanLeft < xmin) + xmin = spanLeft; + + if (spanRight > xmax) + xmax = spanRight; + + if (spanLeft != firstLeft || spanRight != firstRight) + isRect = false; + } + + if (isRect) { + hasRectClip = true; + clipRect.setRect(xmin, ymin, xmax - xmin, ymax - ymin); + } +} + +/* + Convert \a rect to clip spans. + */ +void QClipData::setClipRect(const QRect &rect) +{ + if (hasRectClip && rect == clipRect) + return; + +// qDebug() << "setClipRect" << clipSpanHeight << count << allocated << rect; + hasRectClip = true; + hasRegionClip = false; + clipRect = rect; + + xmin = rect.x(); + xmax = rect.x() + rect.width(); + ymin = qMin(rect.y(), clipSpanHeight); + ymax = qMin(rect.y() + rect.height(), clipSpanHeight); + + if (m_spans) { + free(m_spans); + m_spans = 0; + } + +// qDebug() << xmin << xmax << ymin << ymax; +} + +/* + Convert \a region to clip spans. + */ +void QClipData::setClipRegion(const QRegion ®ion) +{ + if (region.rectCount() == 1) { + setClipRect(region.rects().at(0)); + return; + } + + hasRegionClip = true; + hasRectClip = false; + clipRegion = region; + + { // set bounding rect + const QRect rect = region.boundingRect(); + xmin = rect.x(); + xmax = rect.x() + rect.width(); + ymin = rect.y(); + ymax = rect.y() + rect.height(); + } + + if (m_spans) { + free(m_spans); + m_spans = 0; + } + +} + +/*! + \internal + spans must be sorted on y +*/ +static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, + const QSpan *spans, const QSpan *end, + QSpan **outSpans, int available) +{ + const_cast<QClipData *>(clip)->initialize(); + + QSpan *out = *outSpans; + + const QSpan *clipSpans = clip->m_spans + *currentClip; + const QSpan *clipEnd = clip->m_spans + clip->count; + + while (available && spans < end ) { + if (clipSpans >= clipEnd) { + spans = end; + break; + } + if (clipSpans->y > spans->y) { + ++spans; + continue; + } + if (spans->y != clipSpans->y) { + if (spans->y < clip->count && clip->m_clipLines[spans->y].spans) + clipSpans = clip->m_clipLines[spans->y].spans; + else + ++clipSpans; + continue; + } + Q_ASSERT(spans->y == clipSpans->y); + + int sx1 = spans->x; + int sx2 = sx1 + spans->len; + int cx1 = clipSpans->x; + int cx2 = cx1 + clipSpans->len; + + if (cx1 < sx1 && cx2 < sx1) { + ++clipSpans; + continue; + } else if (sx1 < cx1 && sx2 < cx1) { + ++spans; + continue; + } + int x = qMax(sx1, cx1); + int len = qMin(sx2, cx2) - x; + if (len) { + out->x = qMax(sx1, cx1); + out->len = qMin(sx2, cx2) - out->x; + out->y = spans->y; + out->coverage = qt_div_255(spans->coverage * clipSpans->coverage); + ++out; + --available; + } + if (sx2 < cx2) { + ++spans; + } else { + ++clipSpans; + } + } + + *outSpans = out; + *currentClip = clipSpans - clip->m_spans; + return spans; +} + +static void qt_span_fill_clipped(int spanCount, const QSpan *spans, void *userData) +{ +// qDebug() << "qt_span_fill_clipped" << spanCount; + QSpanData *fillData = reinterpret_cast<QSpanData *>(userData); + + Q_ASSERT(fillData->blend && fillData->unclipped_blend); + + const int NSPANS = 256; + QSpan cspans[NSPANS]; + int currentClip = 0; + const QSpan *end = spans + spanCount; + while (spans < end) { + QSpan *clipped = cspans; + spans = qt_intersect_spans(fillData->clip, ¤tClip, spans, end, &clipped, NSPANS); +// qDebug() << "processed " << processed << "clipped" << clipped-cspans +// << "span:" << cspans->x << cspans->y << cspans->len << spans->coverage; + + if (clipped - cspans) + fillData->unclipped_blend(clipped - cspans, cspans, fillData); + } +} + +/* + \internal + Clip spans to \a{clip}-rectangle. + Returns number of unclipped spans +*/ +static int qt_intersect_spans(QT_FT_Span *spans, int numSpans, + const QRect &clip) +{ + const short minx = clip.left(); + const short miny = clip.top(); + const short maxx = clip.right(); + const short maxy = clip.bottom(); + + int n = 0; + for (int i = 0; i < numSpans; ++i) { + if (spans[i].y > maxy) + break; + if (spans[i].y < miny + || spans[i].x > maxx + || spans[i].x + spans[i].len <= minx) { + continue; + } + if (spans[i].x < minx) { + spans[n].len = qMin(spans[i].len - (minx - spans[i].x), maxx - minx + 1); + spans[n].x = minx; + } else { + spans[n].x = spans[i].x; + spans[n].len = qMin(spans[i].len, ushort(maxx - spans[n].x + 1)); + } + if (spans[n].len == 0) + continue; + spans[n].y = spans[i].y; + spans[n].coverage = spans[i].coverage; + ++n; + } + return n; +} + + +static void qt_span_fill_clipRect(int count, const QSpan *spans, + void *userData) +{ + QSpanData *fillData = reinterpret_cast<QSpanData *>(userData); + Q_ASSERT(fillData->blend && fillData->unclipped_blend); + + Q_ASSERT(fillData->clip); + Q_ASSERT(!fillData->clip->clipRect.isEmpty()); + + // hw: check if this const_cast<> is safe!!! + count = qt_intersect_spans(const_cast<QSpan*>(spans), count, + fillData->clip->clipRect); + if (count > 0) + fillData->unclipped_blend(count, spans, fillData); +} + +static void qt_span_clip(int count, const QSpan *spans, void *userData) +{ + ClipData *clipData = reinterpret_cast<ClipData *>(userData); + +// qDebug() << " qt_span_clip: " << count << clipData->operation; +// for (int i = 0; i < qMin(count, 10); ++i) { +// qDebug() << " " << spans[i].x << spans[i].y << spans[i].len << spans[i].coverage; +// } + + switch (clipData->operation) { + + case Qt::IntersectClip: + { + QClipData *newClip = clipData->newClip; + newClip->initialize(); + + int currentClip = 0; + const QSpan *end = spans + count; + while (spans < end) { + QSpan *newspans = newClip->m_spans + newClip->count; + spans = qt_intersect_spans(clipData->oldClip, ¤tClip, spans, end, + &newspans, newClip->allocated - newClip->count); + newClip->count = newspans - newClip->m_spans; + if (spans < end) { + newClip->m_spans = q_check_ptr((QSpan *)realloc(newClip->m_spans, newClip->allocated*2*sizeof(QSpan))); + newClip->allocated *= 2; + } + } + } + break; + + case Qt::UniteClip: + case Qt::ReplaceClip: + clipData->newClip->appendSpans(spans, count); + break; + case Qt::NoClip: + break; + } +} + +#ifndef QT_NO_DEBUG +QImage QRasterBuffer::bufferImage() const +{ + QImage image(m_width, m_height, QImage::Format_ARGB32_Premultiplied); + + for (int y = 0; y < m_height; ++y) { + uint *span = (uint *)const_cast<QRasterBuffer *>(this)->scanLine(y); + + for (int x=0; x<m_width; ++x) { + uint argb = span[x]; + image.setPixel(x, y, argb); + } + } + return image; +} +#endif + + +void QRasterBuffer::flushToARGBImage(QImage *target) const +{ + int w = qMin(m_width, target->width()); + int h = qMin(m_height, target->height()); + + for (int y=0; y<h; ++y) { + uint *sourceLine = (uint *)const_cast<QRasterBuffer *>(this)->scanLine(y); + QRgb *dest = (QRgb *) target->scanLine(y); + for (int x=0; x<w; ++x) { + QRgb pixel = sourceLine[x]; + int alpha = qAlpha(pixel); + if (!alpha) { + dest[x] = 0; + } else { + dest[x] = (alpha << 24) + | ((255*qRed(pixel)/alpha) << 16) + | ((255*qGreen(pixel)/alpha) << 8) + | ((255*qBlue(pixel)/alpha) << 0); + } + } + } +} + + +class QGradientCache +{ + struct CacheInfo + { + inline CacheInfo(QGradientStops s, int op, QGradient::InterpolationMode mode) : + stops(s), opacity(op), interpolationMode(mode) {} + uint buffer[GRADIENT_STOPTABLE_SIZE]; + QGradientStops stops; + int opacity; + QGradient::InterpolationMode interpolationMode; + }; + + typedef QMultiHash<quint64, CacheInfo> QGradientColorTableHash; + +public: + inline const uint *getBuffer(const QGradient &gradient, int opacity) { + quint64 hash_val = 0; + + QGradientStops stops = gradient.stops(); + for (int i = 0; i < stops.size() && i <= 2; i++) + hash_val += stops[i].second.rgba(); + + QMutexLocker lock(&mutex); + QGradientColorTableHash::const_iterator it = cache.constFind(hash_val); + + if (it == cache.constEnd()) + return addCacheElement(hash_val, gradient, opacity); + else { + do { + const CacheInfo &cache_info = it.value(); + if (cache_info.stops == stops && cache_info.opacity == opacity && cache_info.interpolationMode == gradient.interpolationMode()) + return cache_info.buffer; + ++it; + } while (it != cache.constEnd() && it.key() == hash_val); + // an exact match for these stops and opacity was not found, create new cache + return addCacheElement(hash_val, gradient, opacity); + } + } + + inline int paletteSize() const { return GRADIENT_STOPTABLE_SIZE; } +protected: + inline int maxCacheSize() const { return 60; } + inline void generateGradientColorTable(const QGradient& g, + uint *colorTable, + int size, int opacity) const; + uint *addCacheElement(quint64 hash_val, const QGradient &gradient, int opacity) { + if (cache.size() == maxCacheSize()) { + // may remove more than 1, but OK + cache.erase(cache.begin() + (qrand() % maxCacheSize())); + } + CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode()); + generateGradientColorTable(gradient, cache_entry.buffer, paletteSize(), opacity); + return cache.insert(hash_val, cache_entry).value().buffer; + } + + QGradientColorTableHash cache; + QMutex mutex; +}; + +void QGradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, int opacity) const +{ + QGradientStops stops = gradient.stops(); + int stopCount = stops.count(); + Q_ASSERT(stopCount > 0); + + bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation); + + uint current_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity); + if (stopCount == 1) { + current_color = PREMUL(current_color); + for (int i = 0; i < size; ++i) + colorTable[i] = current_color; + return; + } + + // The position where the gradient begins and ends + qreal begin_pos = stops[0].first; + qreal end_pos = stops[stopCount-1].first; + + int pos = 0; // The position in the color table. + uint next_color; + + qreal incr = 1 / qreal(size); // the double increment. + qreal dpos = 1.5 * incr; // current position in gradient stop list (0 to 1) + + // Up to first point + colorTable[pos++] = PREMUL(current_color); + while (dpos <= begin_pos) { + colorTable[pos] = colorTable[pos - 1]; + ++pos; + dpos += incr; + } + + int current_stop = 0; // We always interpolate between current and current + 1. + + qreal t; // position between current left and right stops + qreal t_delta; // the t increment per entry in the color table + + if (dpos < end_pos) { + // Gradient area + while (dpos > stops[current_stop+1].first) + ++current_stop; + + if (current_stop != 0) + current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity); + next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity); + + if (colorInterpolation) { + current_color = PREMUL(current_color); + next_color = PREMUL(next_color); + } + + qreal diff = stops[current_stop+1].first - stops[current_stop].first; + qreal c = (diff == 0) ? qreal(0) : 256 / diff; + t = (dpos - stops[current_stop].first) * c; + t_delta = incr * c; + + while (true) { + Q_ASSERT(current_stop < stopCount); + + int dist = qRound(t); + int idist = 256 - dist; + + if (colorInterpolation) + colorTable[pos] = INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist); + else + colorTable[pos] = PREMUL(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)); + + ++pos; + dpos += incr; + + if (dpos >= end_pos) + break; + + t += t_delta; + + int skip = 0; + while (dpos > stops[current_stop+skip+1].first) + ++skip; + + if (skip != 0) { + current_stop += skip; + if (skip == 1) + current_color = next_color; + else + current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity); + next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity); + + if (colorInterpolation) { + if (skip != 1) + current_color = PREMUL(current_color); + next_color = PREMUL(next_color); + } + + qreal diff = stops[current_stop+1].first - stops[current_stop].first; + qreal c = (diff == 0) ? qreal(0) : 256 / diff; + t = (dpos - stops[current_stop].first) * c; + t_delta = incr * c; + } + } + } + + // After last point + current_color = PREMUL(ARGB_COMBINE_ALPHA(stops[stopCount - 1].second.rgba(), opacity)); + while (pos < size - 1) { + colorTable[pos] = current_color; + ++pos; + } + + // Make sure the last color stop is represented at the end of the table + colorTable[size - 1] = current_color; +} + +Q_GLOBAL_STATIC(QGradientCache, qt_gradient_cache) + + +void QSpanData::init(QRasterBuffer *rb, const QRasterPaintEngine *pe) +{ + rasterBuffer = rb; +#ifdef Q_WS_QWS + rasterEngine = const_cast<QRasterPaintEngine *>(pe); +#endif + type = None; + txop = 0; + bilinear = false; + m11 = m22 = m33 = 1.; + m12 = m13 = m21 = m23 = dx = dy = 0.0; + clip = pe ? pe->d_func()->clip() : 0; +} + +Q_GUI_EXPORT extern QImage qt_imageForBrush(int brushStyle, bool invert); + +void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode) +{ + Qt::BrushStyle brushStyle = qbrush_style(brush); + switch (brushStyle) { + case Qt::SolidPattern: { + type = Solid; + QColor c = qbrush_color(brush); + QRgb rgba = c.rgba(); + solid.color = PREMUL(ARGB_COMBINE_ALPHA(rgba, alpha)); + if ((solid.color & 0xff000000) == 0 + && compositionMode == QPainter::CompositionMode_SourceOver) { + type = None; + } + break; + } + + case Qt::LinearGradientPattern: + { + type = LinearGradient; + const QLinearGradient *g = static_cast<const QLinearGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = g->spread(); + + QLinearGradientData &linearData = gradient.linear; + + linearData.origin.x = g->start().x(); + linearData.origin.y = g->start().y(); + linearData.end.x = g->finalStop().x(); + linearData.end.y = g->finalStop().y(); + break; + } + + case Qt::RadialGradientPattern: + { + type = RadialGradient; + const QRadialGradient *g = static_cast<const QRadialGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = g->spread(); + + QRadialGradientData &radialData = gradient.radial; + + QPointF center = g->center(); + radialData.center.x = center.x(); + radialData.center.y = center.y(); + QPointF focal = g->focalPoint(); + radialData.focal.x = focal.x(); + radialData.focal.y = focal.y(); + radialData.radius = g->radius(); + } + break; + + case Qt::ConicalGradientPattern: + { + type = ConicalGradient; + const QConicalGradient *g = static_cast<const QConicalGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = QGradient::RepeatSpread; + + QConicalGradientData &conicalData = gradient.conical; + + QPointF center = g->center(); + conicalData.center.x = center.x(); + conicalData.center.y = center.y(); + conicalData.angle = g->angle() * 2 * Q_PI / 360.0; + } + break; + + case Qt::Dense1Pattern: + case Qt::Dense2Pattern: + case Qt::Dense3Pattern: + case Qt::Dense4Pattern: + case Qt::Dense5Pattern: + case Qt::Dense6Pattern: + case Qt::Dense7Pattern: + case Qt::HorPattern: + case Qt::VerPattern: + case Qt::CrossPattern: + case Qt::BDiagPattern: + case Qt::FDiagPattern: + case Qt::DiagCrossPattern: + type = Texture; + if (!tempImage) + tempImage = new QImage(); + *tempImage = rasterBuffer->colorizeBitmap(qt_imageForBrush(brushStyle, true), brush.color()); + initTexture(tempImage, alpha, QTextureData::Tiled); + break; + case Qt::TexturePattern: + type = Texture; + if (!tempImage) + tempImage = new QImage(); + + if (qHasPixmapTexture(brush) && brush.texture().isQBitmap()) + *tempImage = rasterBuffer->colorizeBitmap(brush.textureImage(), brush.color()); + else + *tempImage = brush.textureImage(); + initTexture(tempImage, alpha, QTextureData::Tiled, tempImage->rect()); + break; + + case Qt::NoBrush: + default: + type = None; + break; + } + adjustSpanMethods(); +} + +void QSpanData::adjustSpanMethods() +{ + bitmapBlit = 0; + alphamapBlit = 0; + alphaRGBBlit = 0; + + fillRect = 0; + + switch(type) { + case None: + unclipped_blend = 0; + break; + case Solid: + unclipped_blend = rasterBuffer->drawHelper->blendColor; + bitmapBlit = rasterBuffer->drawHelper->bitmapBlit; + alphamapBlit = rasterBuffer->drawHelper->alphamapBlit; + alphaRGBBlit = rasterBuffer->drawHelper->alphaRGBBlit; + fillRect = rasterBuffer->drawHelper->fillRect; + break; + case LinearGradient: + case RadialGradient: + case ConicalGradient: + unclipped_blend = rasterBuffer->drawHelper->blendGradient; + break; + case Texture: +#ifdef Q_WS_QWS +#ifndef QT_NO_RASTERCALLBACKS + if (!rasterBuffer->buffer()) + unclipped_blend = qBlendTextureCallback; + else +#endif + unclipped_blend = qBlendTexture; +#else + unclipped_blend = qBlendTexture; +#endif + if (!texture.imageData) + unclipped_blend = 0; + + break; + } + // setup clipping + if (!unclipped_blend) { + blend = 0; + } else if (!clip) { + blend = unclipped_blend; + } else if (clip->hasRectClip) { + blend = clip->clipRect.isEmpty() ? 0 : qt_span_fill_clipRect; + } else { + blend = qt_span_fill_clipped; + } +} + +void QSpanData::setupMatrix(const QTransform &matrix, int bilin) +{ + QTransform delta; + // make sure we round off correctly in qdrawhelper.cpp + delta.translate(1.0 / 65536, 1.0 / 65536); + + QTransform inv = (delta * matrix).inverted(); + m11 = inv.m11(); + m12 = inv.m12(); + m13 = inv.m13(); + m21 = inv.m21(); + m22 = inv.m22(); + m23 = inv.m23(); + m33 = inv.m33(); + dx = inv.dx(); + dy = inv.dy(); + txop = inv.type(); + bilinear = bilin; + + const bool affine = !m13 && !m23; + fast_matrix = affine + && m11 * m11 + m21 * m21 < 1e4 + && m12 * m12 + m22 * m22 < 1e4 + && qAbs(dx) < 1e4 + && qAbs(dy) < 1e4; + + adjustSpanMethods(); +} + +extern const QVector<QRgb> *qt_image_colortable(const QImage &image); + +void QSpanData::initTexture(const QImage *image, int alpha, QTextureData::Type _type, const QRect &sourceRect) +{ + const QImageData *d = const_cast<QImage *>(image)->data_ptr(); + if (!d || d->height == 0) { + texture.imageData = 0; + texture.width = 0; + texture.height = 0; + texture.x1 = 0; + texture.y1 = 0; + texture.x2 = 0; + texture.y2 = 0; + texture.bytesPerLine = 0; + texture.format = QImage::Format_Invalid; + texture.colorTable = 0; + texture.hasAlpha = alpha != 256; + } else { + texture.imageData = d->data; + texture.width = d->width; + texture.height = d->height; + + if (sourceRect.isNull()) { + texture.x1 = 0; + texture.y1 = 0; + texture.x2 = texture.width; + texture.y2 = texture.height; + } else { + texture.x1 = sourceRect.x(); + texture.y1 = sourceRect.y(); + texture.x2 = qMin(texture.x1 + sourceRect.width(), d->width); + texture.y2 = qMin(texture.y1 + sourceRect.height(), d->height); + } + + texture.bytesPerLine = d->bytes_per_line; + + texture.format = d->format; + texture.colorTable = (d->format <= QImage::Format_Indexed8 && !d->colortable.isEmpty()) ? &d->colortable : 0; + texture.hasAlpha = image->hasAlphaChannel() || alpha != 256; + } + texture.const_alpha = alpha; + texture.type = _type; + + adjustSpanMethods(); +} + +#ifdef Q_WS_WIN + + +#endif + + +/*! + \internal + + Draws a line using the floating point midpoint algorithm. The line + \a line is already in device coords at this point. +*/ + +static void drawLine_midpoint_i(int x1, int y1, int x2, int y2, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - drawLine_midpoint_i" << QLine(QPoint(x1, y1), QPoint(x2, y2)); +#endif + + int x, y; + int dx, dy, d, incrE, incrNE; + + dx = x2 - x1; + dy = y2 - y1; + + const int NSPANS = 256; + QT_FT_Span spans[NSPANS]; + int current = 0; + bool ordered = true; + + if (dy == 0) { + // specialcase horizontal lines + if (y1 >= devRect.y1 && y1 < devRect.y2) { + int start = qMax(devRect.x1, qMin(x1, x2)); + int stop = qMax(x1, x2) + 1; + int stop_clipped = qMin(devRect.x2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + if (len > 0) { + spans[0].x = ushort(start); + spans[0].len = ushort(len); + spans[0].y = y1; + spans[0].coverage = 255; + span_func(1, spans, data); + } + } + return; + } else if (dx == 0) { + // specialcase vertical lines + if (x1 >= devRect.x1 && x1 < devRect.x2) { + int start = qMax(devRect.y1, qMin(y1, y2)); + int stop = qMax(y1, y2) + 1; + int stop_clipped = qMin(devRect.y2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + // hw: create spans directly instead to possibly avoid clipping + if (len > 0) + fillRect_normalized(QRect(x1, start, 1, len).normalized(), data, 0); + } + return; + } + + + if (qAbs(dx) >= qAbs(dy)) { /* if x is the major axis: */ + + if (x2 < x1) { /* if coordinates are out of order */ + qt_swap_int(x1, x2); + dx = -dx; + + qt_swap_int(y1, y2); + dy = -dy; + } + + int x_lower_limit = - 128; + if (x1 < x_lower_limit) { + int cy = dy * (x_lower_limit - x1) / dx + y1; + drawLine_midpoint_i(x_lower_limit, cy, x2, y2, span_func, data, style, devRect); + return; + } + + if (style == LineDrawNormal) + --x2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + x2 = qMin(x2, devRect.x2 - 1); + + // completely clipped, so abort + if (x2 <= x1) { + return; + } + + int x = x1; + int y = y1; + + if (y2 <= y1) + ordered = false; + + { + const int index = (ordered ? current : NSPANS - 1 - current); + spans[index].coverage = 255; + spans[index].x = x; + spans[index].y = y; + + if (x >= devRect.x1 && y >= devRect.y1 && y < devRect.y2) + spans[index].len = 1; + else + spans[index].len = 0; + } + + if (y2 > y1) { // 315 -> 360 and 135 -> 180 (unit circle degrees) + y2 = qMin(y2, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE - dx; + incrNE = (dy - dx) * 2; + + if (y > y2) + goto flush_and_return; + + while (x < x2) { + ++x; + if (d > 0) { + if (spans[current].len > 0) + ++current; + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + + ++y; + d += incrNE; + if (y > y2) + goto flush_and_return; + + spans[current].len = 0; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + } else { + d += incrE; + if (x == devRect.x1) + spans[current].x = devRect.x1; + } + + if (x < devRect.x1 || y < devRect.y1) + continue; + + Q_ASSERT(x<devRect.x2); + Q_ASSERT(y<devRect.y2); + Q_ASSERT(spans[current].y == y); + spans[current].len++; + } + if (spans[current].len > 0) { + ++current; + } + } else { // 0-45 and 180->225 (unit circle degrees) + + y1 = qMin(y1, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE + dx; + incrNE = (dy + dx) * 2; + + if (y < devRect.y1) + goto flush_and_return; + + while (x < x2) { + ++x; + if (d < 0) { + if (spans[NSPANS - 1 - current].len > 0) + ++current; + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + + --y; + d += incrNE; + if (y < devRect.y1) + goto flush_and_return; + + const int index = NSPANS - 1 - current; + spans[index].len = 0; + spans[index].coverage = 255; + spans[index].x = x; + spans[index].y = y; + } else { + d += incrE; + if (x == devRect.x1) + spans[NSPANS - 1 - current].x = devRect.x1; + } + + if (x < devRect.x1 || y > y1) + continue; + + Q_ASSERT(x<devRect.x2 && y<devRect.y2); + Q_ASSERT(spans[NSPANS - 1 - current].y == y); + spans[NSPANS - 1 - current].len++; + } + if (spans[NSPANS - 1 - current].len > 0) { + ++current; + } + } + + } else { + + // if y is the major axis: + + if (y2 < y1) { /* if coordinates are out of order */ + qt_swap_int(y1, y2); + dy = -dy; + + qt_swap_int(x1, x2); + dx = -dx; + } + + int y_lower_limit = - 128; + if (y1 < y_lower_limit) { + int cx = dx * (y_lower_limit - y1) / dy + x1; + drawLine_midpoint_i(cx, y_lower_limit, x2, y2, span_func, data, style, devRect); + return; + } + + if (style == LineDrawNormal) + --y2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + y2 = qMin(y2, devRect.y2 - 1); + + // completely clipped, so abort + if (y2 <= y1) { + return; + } + + x = x1; + y = y1; + + if (x>=devRect.x1 && y>=devRect.y1 && x < devRect.x2) { + Q_ASSERT(x >= devRect.x1 && y >= devRect.y1 && x < devRect.x2 && y < devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + + if (x2 > x1) { // 90 -> 135 and 270 -> 315 (unit circle degrees) + x2 = qMin(x2, devRect.x2 - 1); + incrE = dx * 2; + d = incrE - dy; + incrNE = (dx - dy) * 2; + + if (x > x2) + goto flush_and_return; + + while (y < y2) { + if (d > 0) { + ++x; + d += incrNE; + if (x > x2) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + if (x < devRect.x1 || y < devRect.y1) + continue; + Q_ASSERT(x<devRect.x2 && y<devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + } else { // 45 -> 90 and 225 -> 270 (unit circle degrees) + x1 = qMin(x1, devRect.x2 - 1); + incrE = dx * 2; + d = incrE + dy; + incrNE = (dx + dy) * 2; + + if (x < devRect.x1) + goto flush_and_return; + + while (y < y2) { + if (d < 0) { + --x; + d += incrNE; + if (x < devRect.x1) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + if (y < devRect.y1 || x > x1) + continue; + Q_ASSERT(x>=devRect.x1 && x<devRect.x2 && y>=devRect.y1 && y<devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + } + } +flush_and_return: + if (current > 0) + span_func(current, ordered ? spans : spans + (NSPANS - current), data); +} + +static void offset_pattern(int offset, bool *inDash, int *dashIndex, int *currentOffset, const QVarLengthArray<qreal> &pattern) +{ + while (offset--) { + if (--*currentOffset == 0) { + *inDash = !*inDash; + *dashIndex = ((*dashIndex + 1) % pattern.size()); + *currentOffset = int(pattern[*dashIndex]); + } + } +} + +static void drawLine_midpoint_dashed_i(int x1, int y1, int x2, int y2, + QPen *pen, + ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect, + int *patternOffset) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - drawLine_midpoint_dashed_i" << x1 << y1 << x2 << y2 << *patternOffset; +#endif + + int x, y; + int dx, dy, d, incrE, incrNE; + + dx = x2 - x1; + dy = y2 - y1; + + Q_ASSERT(*patternOffset >= 0); + + const QVector<qreal> penPattern = pen->dashPattern(); + QVarLengthArray<qreal> pattern(penPattern.size()); + + int patternLength = 0; + for (int i = 0; i < penPattern.size(); ++i) + patternLength += qMax<qreal>(1.0, (penPattern.at(i))); + + // pattern must be reversed if coordinates are out of order + int reverseLength = -1; + if (dy == 0 && x1 > x2) + reverseLength = x1 - x2; + else if (dx == 0 && y1 > y2) + reverseLength = y1 - y2; + else if (qAbs(dx) >= qAbs(dy) && x2 < x1) // x major axis + reverseLength = qAbs(dx); + else if (qAbs(dy) >= qAbs(dx) && y2 < y1) // y major axis + reverseLength = qAbs(dy); + + const bool reversed = (reverseLength > -1); + if (reversed) { // reverse pattern + for (int i = 0; i < penPattern.size(); ++i) + pattern[penPattern.size() - 1 - i] = qMax<qreal>(1.0, penPattern.at(i)); + + *patternOffset = (patternLength - 1 - *patternOffset); + *patternOffset += patternLength - (reverseLength % patternLength); + *patternOffset = *patternOffset % patternLength; + } else { + for (int i = 0; i < penPattern.size(); ++i) + pattern[i] = qMax<qreal>(1.0, penPattern.at(i)); + } + + int dashIndex = 0; + bool inDash = !reversed; + int currPattern = int(pattern[dashIndex]); + + // adjust pattern for offset + offset_pattern(*patternOffset, &inDash, &dashIndex, &currPattern, pattern); + + const int NSPANS = 256; + QT_FT_Span spans[NSPANS]; + int current = 0; + bool ordered = true; + + if (dy == 0) { + // specialcase horizontal lines + if (y1 >= devRect.y1 && y1 < devRect.y2) { + int start_unclipped = qMin(x1, x2); + int start = qMax(devRect.x1, start_unclipped); + int stop = qMax(x1, x2) + 1; + int stop_clipped = qMin(devRect.x2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + + // adjust pattern for starting offset + offset_pattern(start - start_unclipped, &inDash, &dashIndex, &currPattern, pattern); + + if (len > 0) { + int x = start; + while (x < stop_clipped) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + const int dash = qMin(currPattern, stop_clipped - x); + if (inDash) { + spans[current].x = ushort(x); + spans[current].len = ushort(dash); + spans[current].y = y1; + spans[current].coverage = 255; + ++current; + } + if (dash < currPattern) { + currPattern -= dash; + } else { + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + inDash = !inDash; + } + x += dash; + } + } + } + goto flush_and_return; + } else if (dx == 0) { + if (x1 >= devRect.x1 && x1 < devRect.x2) { + int start_unclipped = qMin(y1, y2); + int start = qMax(devRect.y1, start_unclipped); + int stop = qMax(y1, y2) + 1; + int stop_clipped = qMin(devRect.y2, stop); + if (style == LineDrawNormal && stop == stop_clipped) + --stop; + else + stop = stop_clipped; + + // adjust pattern for starting offset + offset_pattern(start - start_unclipped, &inDash, &dashIndex, &currPattern, pattern); + + // loop over dashes + int y = start; + while (y < stop) { + const int dash = qMin(currPattern, stop - y); + if (inDash) { + for (int i = 0; i < dash; ++i) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].x = x1; + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].y = ushort(y + i); + ++current; + } + } + if (dash < currPattern) { + currPattern -= dash; + } else { + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + inDash = !inDash; + } + y += dash; + } + } + goto flush_and_return; + } + + if (qAbs(dx) >= qAbs(dy)) { /* if x is the major axis: */ + + if (x2 < x1) { /* if coordinates are out of order */ + qt_swap_int(x1, x2); + dx = -dx; + + qt_swap_int(y1, y2); + dy = -dy; + } + + if (style == LineDrawNormal) + --x2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + x2 = qMin(x2, devRect.x2 - 1); + + // completely clipped, so abort + if (x2 <= x1) + goto flush_and_return; + + int x = x1; + int y = y1; + + if (x >= devRect.x1 && y >= devRect.y1 && y < devRect.y2) { + Q_ASSERT(x < devRect.x2); + if (inDash) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + + if (y2 > y1) { // 315 -> 360 and 135 -> 180 (unit circle degrees) + y2 = qMin(y2, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE - dx; + incrNE = (dy - dx) * 2; + + if (y > y2) + goto flush_and_return; + + while (x < x2) { + if (d > 0) { + ++y; + d += incrNE; + if (y > y2) + goto flush_and_return; + } else { + d += incrE; + } + ++x; + + const bool skip = x < devRect.x1 || y < devRect.y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } else { // 0-45 and 180->225 (unit circle degrees) + y1 = qMin(y1, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE + dx; + incrNE = (dy + dx) * 2; + + if (y < devRect.y1) + goto flush_and_return; + + while (x < x2) { + if (d < 0) { + if (current > 0) { + span_func(current, spans, data); + current = 0; + } + + --y; + d += incrNE; + if (y < devRect.y1) + goto flush_and_return; + } else { + d += incrE; + } + ++x; + + const bool skip = x < devRect.x1 || y > y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } + } else { + + // if y is the major axis: + + if (y2 < y1) { /* if coordinates are out of order */ + qt_swap_int(y1, y2); + dy = -dy; + + qt_swap_int(x1, x2); + dx = -dx; + } + + if (style == LineDrawNormal) + --y2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + y2 = qMin(y2, devRect.y2 - 1); + + // completely clipped, so abort + if (y2 <= y1) + goto flush_and_return; + + x = x1; + y = y1; + + if (x>=devRect.x1 && y>=devRect.y1 && x < devRect.x2) { + Q_ASSERT(x < devRect.x2); + if (inDash) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + + if (x2 > x1) { // 90 -> 135 and 270 -> 315 (unit circle degrees) + x2 = qMin(x2, devRect.x2 - 1); + incrE = dx * 2; + d = incrE - dy; + incrNE = (dx - dy) * 2; + + if (x > x2) + goto flush_and_return; + + while (y < y2) { + if (d > 0) { + ++x; + d += incrNE; + if (x > x2) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + const bool skip = x < devRect.x1 || y < devRect.y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } else { // 45 -> 90 and 225 -> 270 (unit circle degrees) + x1 = qMin(x1, devRect.x2 - 1); + incrE = dx * 2; + d = incrE + dy; + incrNE = (dx + dy) * 2; + + if (x < devRect.x1) + goto flush_and_return; + + while (y < y2) { + if (d < 0) { + --x; + d += incrNE; + if (x < devRect.x1) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + const bool skip = y < devRect.y1 || x > x1; + Q_ASSERT(skip || (x >= devRect.x1 && x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } + } +flush_and_return: + if (current > 0) + span_func(current, ordered ? spans : spans + (NSPANS - current), data); + + // adjust offset + if (reversed) { + *patternOffset = (patternLength - 1 - *patternOffset); + } else { + *patternOffset = 0; + for (int i = 0; i <= dashIndex; ++i) + *patternOffset += int(pattern[i]); + *patternOffset += patternLength - currPattern - 1; + *patternOffset = (*patternOffset % patternLength); + } +} + +/*! + \internal + \a x and \a y is relative to the midpoint of \a rect. +*/ +static inline void drawEllipsePoints(int x, int y, int length, + const QRect &rect, + const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data) +{ + if (length == 0) + return; + + QT_FT_Span outline[4]; + const int midx = rect.x() + (rect.width() + 1) / 2; + const int midy = rect.y() + (rect.height() + 1) / 2; + + x = x + midx; + y = midy - y; + + // topleft + outline[0].x = midx + (midx - x) - (length - 1) - (rect.width() & 0x1); + outline[0].len = qMin(length, x - outline[0].x); + outline[0].y = y; + outline[0].coverage = 255; + + // topright + outline[1].x = x; + outline[1].len = length; + outline[1].y = y; + outline[1].coverage = 255; + + // bottomleft + outline[2].x = outline[0].x; + outline[2].len = outline[0].len; + outline[2].y = midy + (midy - y) - (rect.height() & 0x1); + outline[2].coverage = 255; + + // bottomright + outline[3].x = x; + outline[3].len = length; + outline[3].y = outline[2].y; + outline[3].coverage = 255; + + if (brush_func && outline[0].x + outline[0].len < outline[1].x) { + QT_FT_Span fill[2]; + + // top fill + fill[0].x = outline[0].x + outline[0].len - 1; + fill[0].len = qMax(0, outline[1].x - fill[0].x); + fill[0].y = outline[1].y; + fill[0].coverage = 255; + + // bottom fill + fill[1].x = outline[2].x + outline[2].len - 1; + fill[1].len = qMax(0, outline[3].x - fill[1].x); + fill[1].y = outline[3].y; + fill[1].coverage = 255; + + int n = (fill[0].y >= fill[1].y ? 1 : 2); + n = qt_intersect_spans(fill, n, clip); + if (n > 0) + brush_func(n, fill, brush_data); + } + if (pen_func) { + int n = (outline[1].y >= outline[2].y ? 2 : 4); + n = qt_intersect_spans(outline, n, clip); + if (n > 0) + pen_func(n, outline, pen_data); + } +} + +/*! + \internal + Draws an ellipse using the integer point midpoint algorithm. +*/ +static void drawEllipse_midpoint_i(const QRect &rect, const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data) +{ + const qreal a = qreal(rect.width()) / 2; + const qreal b = qreal(rect.height()) / 2; + qreal d = b*b - (a*a*b) + 0.25*a*a; + + int x = 0; + int y = (rect.height() + 1) / 2; + int startx = x; + + // region 1 + while (a*a*(2*y - 1) > 2*b*b*(x + 1)) { + if (d < 0) { // select E + d += b*b*(2*x + 3); + ++x; + } else { // select SE + d += b*b*(2*x + 3) + a*a*(-2*y + 2); + drawEllipsePoints(startx, y, x - startx + 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + startx = ++x; + --y; + } + } + drawEllipsePoints(startx, y, x - startx + 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + + // region 2 + d = b*b*(x + 0.5)*(x + 0.5) + a*a*((y - 1)*(y - 1) - b*b); + const int miny = rect.height() & 0x1; + while (y > miny) { + if (d < 0) { // select SE + d += b*b*(2*x + 2) + a*a*(-2*y + 3); + ++x; + } else { // select S + d += a*a*(-2*y + 3); + } + --y; + drawEllipsePoints(x, y, 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + } +} + +/*! + \fn void QRasterPaintEngine::drawPoints(const QPoint *points, int pointCount) + \overload + + Draws the first \a pointCount points in the buffer \a points + + The default implementation converts the first \a pointCount QPoints in \a points + to QPointFs and calls the floating point version of drawPoints. +*/ + +/*! + \fn void QRasterPaintEngine::drawEllipse(const QRect &rect) + \overload + + Reimplement this function to draw the largest ellipse that can be + contained within rectangle \a rect. +*/ + +#ifdef QT_DEBUG_DRAW +void dumpClip(int width, int height, const QClipData *clip) +{ + QImage clipImg(width, height, QImage::Format_ARGB32_Premultiplied); + clipImg.fill(0xffff0000); + + int x0 = width; + int x1 = 0; + int y0 = height; + int y1 = 0; + + ((QClipData *) clip)->spans(); // Force allocation of the spans structure... + + for (int i = 0; i < clip->count; ++i) { + const QSpan *span = ((QClipData *) clip)->spans() + i; + for (int j = 0; j < span->len; ++j) + clipImg.setPixel(span->x + j, span->y, 0xffffff00); + x0 = qMin(x0, int(span->x)); + x1 = qMax(x1, int(span->x + span->len - 1)); + + y0 = qMin(y0, int(span->y)); + y1 = qMax(y1, int(span->y)); + } + + static int counter = 0; + + Q_ASSERT(y0 >= 0); + Q_ASSERT(x0 >= 0); + Q_ASSERT(y1 >= 0); + Q_ASSERT(x1 >= 0); + + fprintf(stderr,"clip %d: %d %d - %d %d\n", counter, x0, y0, x1, y1); + clipImg.save(QString::fromLatin1("clip-%0.png").arg(counter++)); +} +#endif + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine_raster_p.h b/src/gui/painting/qpaintengine_raster_p.h new file mode 100644 index 0000000000..52f51fab16 --- /dev/null +++ b/src/gui/painting/qpaintengine_raster_p.h @@ -0,0 +1,567 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_RASTER_P_H +#define QPAINTENGINE_RASTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qpaintengineex_p.h" +#include "QtGui/qpainterpath.h" +#include "private/qdatabuffer_p.h" +#include "private/qdrawhelper_p.h" +#include "private/qpaintengine_p.h" +#include "private/qrasterizer_p.h" +#include "private/qstroker_p.h" +#include "private/qpainter_p.h" +#include "private/qtextureglyphcache_p.h" +#include "private/qoutlinemapper_p.h" + +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +class QOutlineMapper; +class QRasterPaintEnginePrivate; +class QRasterBuffer; +class QClipData; +class QCustomRasterPaintDevice; + +class QRasterPaintEngineState : public QPainterState +{ +public: + QRasterPaintEngineState(QRasterPaintEngineState &other); + QRasterPaintEngineState(); + ~QRasterPaintEngineState(); + + + QPen lastPen; + QSpanData penData; + QStrokerOps *stroker; + uint strokeFlags; + + QBrush lastBrush; + QSpanData brushData; + uint fillFlags; + + uint pixmapFlags; + int intOpacity; + + qreal txscale; + + QClipData *clip; +// QRect clipRect; +// QRegion clipRegion; + +// QPainter::RenderHints hints; +// QPainter::CompositionMode compositionMode; + + uint dirty; + + struct Flags { + uint has_clip_ownership : 1; // should delete the clip member.. + uint fast_pen : 1; // cosmetic 1-width pens, using midpoint drawlines + uint non_complex_pen : 1; // can use rasterizer, rather than stroker + uint antialiased : 1; + uint bilinear : 1; + uint fast_text : 1; + uint int_xform : 1; + uint tx_noshear : 1; + uint fast_images : 1; + }; + + union { + Flags flags; + uint flag_bits; + }; +}; + + + + +/******************************************************************************* + * QRasterPaintEngine + */ +class +#ifdef Q_WS_QWS +Q_GUI_EXPORT +#endif +QRasterPaintEngine : public QPaintEngineEx +{ + Q_DECLARE_PRIVATE(QRasterPaintEngine) +public: + + QRasterPaintEngine(QPaintDevice *device); + ~QRasterPaintEngine(); + bool begin(QPaintDevice *device); + bool end(); + + void penChanged(); + void brushChanged(); + void brushOriginChanged(); + void opacityChanged(); + void compositionModeChanged(); + void renderHintsChanged(); + void transformChanged(); + void clipEnabledChanged(); + + void setState(QPainterState *s); + QPainterState *createState(QPainterState *orig) const; + inline QRasterPaintEngineState *state() { + return static_cast<QRasterPaintEngineState *>(QPaintEngineEx::state()); + } + inline const QRasterPaintEngineState *state() const { + return static_cast<const QRasterPaintEngineState *>(QPaintEngineEx::state()); + } + + void updateBrush(const QBrush &brush); + void updatePen(const QPen &pen); + + void updateMatrix(const QTransform &matrix); + + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode); + void fillPath(const QPainterPath &path, QSpanData *fillData); + void fillPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + + void drawEllipse(const QRectF &rect); + + void fillRect(const QRectF &rect, const QBrush &brush); + void fillRect(const QRectF &rect, const QColor &color); + + void drawRects(const QRect *rects, int rectCount); + void drawRects(const QRectF *rects, int rectCount); + + void drawPixmap(const QPointF &p, const QPixmap &pm); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawImage(const QPointF &p, const QImage &img); + void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags falgs = Qt::AutoColor); + void drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &sr); + void drawTextItem(const QPointF &p, const QTextItem &textItem); + + void drawLines(const QLine *line, int lineCount); + void drawLines(const QLineF *line, int lineCount); + + void drawPoints(const QPointF *points, int pointCount); + void drawPoints(const QPoint *points, int pointCount); + + void stroke(const QVectorPath &path, const QPen &pen); + void fill(const QVectorPath &path, const QBrush &brush); + + void strokePolygonCosmetic(const QPoint *pts, int pointCount, PolygonDrawMode mode); + void strokePolygonCosmetic(const QPointF *pt, int pointCount, PolygonDrawMode mode); + + void clip(const QVectorPath &path, Qt::ClipOperation op); + void clip(const QRect &rect, Qt::ClipOperation op); + void clip(const QRegion ®ion, Qt::ClipOperation op); + + void drawStaticTextItem(QStaticTextItem *textItem); + + enum ClipType { + RectClip, + ComplexClip + }; + ClipType clipType() const; + QRect clipBoundingRect() const; + +#ifdef Q_NO_USING_KEYWORD + inline void drawEllipse(const QRect &rect) { QPaintEngineEx::drawEllipse(rect); } +#else + using QPaintEngineEx::drawPolygon; + using QPaintEngineEx::drawEllipse; +#endif + + void releaseBuffer(); + + QSize size() const; + +#ifndef QT_NO_DEBUG + void saveBuffer(const QString &s) const; +#endif + +#ifdef Q_WS_MAC + void setCGContext(CGContextRef ref); + CGContextRef getCGContext() const; +#endif + +#ifdef Q_WS_WIN + void setDC(HDC hdc); + HDC getDC() const; + void releaseDC(HDC hdc) const; +#endif + + void alphaPenBlt(const void* src, int bpl, int depth, int rx,int ry,int w,int h); + + Type type() const { return Raster; } + + QPoint coordinateOffset() const; + +#if defined(Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) + virtual void drawColorSpans(const QSpan *spans, int count, uint color); + virtual void drawBufferSpan(const uint *buffer, int bufsize, + int x, int y, int length, uint const_alpha); +#endif + +protected: + QRasterPaintEngine(QRasterPaintEnginePrivate &d, QPaintDevice *); +private: + friend struct QSpanData; + friend class QBlitterPaintEngine; + friend class QBlitterPaintEnginePrivate; + void init(); + + void fillRect(const QRectF &rect, QSpanData *data); + void drawBitmap(const QPointF &pos, const QImage &image, QSpanData *fill); + + bool drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs, const QFixedPoint *positions, + QFontEngine *fontEngine); + +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + void drawGlyphsS60(const QPointF &p, const QTextItemInt &ti); +#endif // Q_OS_SYMBIAN && QT_NO_FREETYPE + + bool setClipRectInDeviceCoords(const QRect &r, Qt::ClipOperation op); + + inline void ensureBrush(const QBrush &brush) { + if (!qbrush_fast_equals(state()->lastBrush, brush) || (brush.style() != Qt::NoBrush && state()->fillFlags)) + updateBrush(brush); + } + inline void ensureBrush() { ensureBrush(state()->brush); } + + inline void ensurePen(const QPen &pen) { + if (!qpen_fast_equals(state()->lastPen, pen) || (pen.style() != Qt::NoPen && state()->strokeFlags)) + updatePen(pen); + } + inline void ensurePen() { ensurePen(state()->pen); } + + void updateOutlineMapper(); + inline void ensureOutlineMapper(); + + void updateState(); + inline void ensureState() { + if (state()->dirty) + updateState(); + } +}; + + +/******************************************************************************* + * QRasterPaintEnginePrivate + */ +class +#ifdef Q_WS_QWS +Q_GUI_EXPORT +#endif +QRasterPaintEnginePrivate : public QPaintEngineExPrivate +{ + Q_DECLARE_PUBLIC(QRasterPaintEngine) +public: + QRasterPaintEnginePrivate(); + + void rasterizeLine_dashed(QLineF line, qreal width, + int *dashIndex, qreal *dashOffset, bool *inDash); + void rasterize(QT_FT_Outline *outline, ProcessSpans callback, QSpanData *spanData, QRasterBuffer *rasterBuffer); + void rasterize(QT_FT_Outline *outline, ProcessSpans callback, void *userData, QRasterBuffer *rasterBuffer); + void updateMatrixData(QSpanData *spanData, const QBrush &brush, const QTransform &brushMatrix); + + void systemStateChanged(); + + void drawImage(const QPointF &pt, const QImage &img, SrcOverBlendFunc func, + const QRect &clip, int alpha, const QRect &sr = QRect()); + + QTransform brushMatrix() const { + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + QTransform m(s->matrix); + m.translate(s->brushOrigin.x(), s->brushOrigin.y()); + return m; + } + + bool isUnclipped_normalized(const QRect &rect) const; + bool isUnclipped(const QRect &rect, int penWidth) const; + bool isUnclipped(const QRectF &rect, int penWidth) const; + ProcessSpans getPenFunc(const QRect &rect, const QSpanData *data) const; + ProcessSpans getPenFunc(const QRectF &rect, const QSpanData *data) const; + ProcessSpans getBrushFunc(const QRect &rect, const QSpanData *data) const; + ProcessSpans getBrushFunc(const QRectF &rect, const QSpanData *data) const; + +#ifdef Q_WS_QWS + void prepare(QCustomRasterPaintDevice *); +#endif + + inline const QClipData *clip() const; + + void initializeRasterizer(QSpanData *data); + + void recalculateFastImages(); + bool canUseFastImageBlending(QPainter::CompositionMode mode, const QImage &image) const; + + QPaintDevice *device; + QScopedPointer<QOutlineMapper> outlineMapper; + QScopedPointer<QRasterBuffer> rasterBuffer; + +#if defined (Q_WS_WIN) + HDC hdc; +#elif defined(Q_WS_MAC) + CGContextRef cgContext; +#endif + + QRect deviceRect; + + QStroker basicStroker; + QScopedPointer<QDashStroker> dashStroker; + + QScopedPointer<QT_FT_Raster> grayRaster; + + QDataBuffer<QLineF> cachedLines; + QSpanData image_filler; + QSpanData image_filler_xform; + QSpanData solid_color_filler; + + + QFontEngineGlyphCache::Type glyphCacheType; + + QScopedPointer<QClipData> baseClip; + + int deviceDepth; + + uint mono_surface : 1; + uint outlinemapper_xform_dirty : 1; + +#ifdef Q_WS_WIN + uint isPlain45DegreeRotation : 1; +#endif + + QScopedPointer<QRasterizer> rasterizer; +}; + + +class +#ifdef Q_WS_QWS +Q_GUI_EXPORT +#endif +QClipData { +public: + QClipData(int height); + ~QClipData(); + + int clipSpanHeight; + struct ClipLine { + int count; + QSpan *spans; + } *m_clipLines; + + void initialize(); + + inline ClipLine *clipLines() { + if (!m_clipLines) + initialize(); + return m_clipLines; + } + + inline QSpan *spans() { + if (!m_spans) + initialize(); + return m_spans; + } + + int allocated; + int count; + QSpan *m_spans; + int xmin, xmax, ymin, ymax; + + QRect clipRect; + QRegion clipRegion; + + uint enabled : 1; + uint hasRectClip : 1; + uint hasRegionClip : 1; + + void appendSpan(int x, int length, int y, int coverage); + void appendSpans(const QSpan *s, int num); + + // ### Should optimize and actually kill the QSpans if the rect is + // ### a subset of The current region. Thus the "fast" clipspan + // ### callback can be used + void setClipRect(const QRect &rect); + void setClipRegion(const QRegion ®ion); + void fixup(); +}; + +inline void QClipData::appendSpan(int x, int length, int y, int coverage) +{ + Q_ASSERT(m_spans); // initialize() has to be called prior to adding spans.. + + if (count == allocated) { + allocated *= 2; + m_spans = (QSpan *)realloc(m_spans, allocated*sizeof(QSpan)); + } + m_spans[count].x = x; + m_spans[count].len = length; + m_spans[count].y = y; + m_spans[count].coverage = coverage; + ++count; +} + +inline void QClipData::appendSpans(const QSpan *s, int num) +{ + Q_ASSERT(m_spans); + + if (count + num > allocated) { + do { + allocated *= 2; + } while (count + num > allocated); + m_spans = (QSpan *)realloc(m_spans, allocated*sizeof(QSpan)); + } + memcpy(m_spans+count, s, num*sizeof(QSpan)); + count += num; +} + +#ifdef Q_WS_QWS +class Q_GUI_EXPORT QCustomRasterPaintDevice : public QPaintDevice +{ +public: + QCustomRasterPaintDevice(QWidget *w) : widget(w) {} + + int devType() const { return QInternal::CustomRaster; } + + virtual int metric(PaintDeviceMetric m) const; + + virtual void* memory() const { return 0; } + + virtual QImage::Format format() const { + return QImage::Format_ARGB32_Premultiplied; + } + + virtual int bytesPerLine() const; + + virtual QSize size() const { + return static_cast<QRasterPaintEngine*>(paintEngine())->size(); + } + +private: + QWidget *widget; +}; +#endif // Q_WS_QWS + +/******************************************************************************* + * QRasterBuffer + */ +class +#ifdef Q_WS_QWS +Q_GUI_EXPORT +#endif +QRasterBuffer +{ +public: + QRasterBuffer() : m_width(0), m_height(0), m_buffer(0) { init(); } + + ~QRasterBuffer(); + + void init(); + + QImage::Format prepare(QImage *image); + QImage::Format prepare(QPixmap *pix); +#ifdef Q_WS_QWS + void prepare(QCustomRasterPaintDevice *device); +#endif + void prepare(int w, int h); + void prepareBuffer(int w, int h); + + void resetBuffer(int val=0); + + uchar *scanLine(int y) { Q_ASSERT(y>=0); Q_ASSERT(y<m_height); return m_buffer + y * bytes_per_line; } + +#ifndef QT_NO_DEBUG + QImage bufferImage() const; +#endif + + void flushToARGBImage(QImage *image) const; + + int width() const { return m_width; } + int height() const { return m_height; } + int bytesPerLine() const { return bytes_per_line; } + int bytesPerPixel() const { return bytes_per_pixel; } + + uchar *buffer() const { return m_buffer; } + + bool monoDestinationWithClut; + QRgb destColor0; + QRgb destColor1; + + QPainter::CompositionMode compositionMode; + QImage::Format format; + DrawHelper *drawHelper; + QImage colorizeBitmap(const QImage &image, const QColor &color); + +private: + int m_width; + int m_height; + int bytes_per_line; + int bytes_per_pixel; + uchar *m_buffer; +}; + +inline void QRasterPaintEngine::ensureOutlineMapper() { + if (d_func()->outlinemapper_xform_dirty) + updateOutlineMapper(); +} + +inline const QClipData *QRasterPaintEnginePrivate::clip() const { + Q_Q(const QRasterPaintEngine); + if (q->state() && q->state()->clip && q->state()->clip->enabled) + return q->state()->clip; + return baseClip.data(); +} + + +QT_END_NAMESPACE +#endif // QPAINTENGINE_RASTER_P_H diff --git a/src/gui/painting/qpaintengine_s60.cpp b/src/gui/painting/qpaintengine_s60.cpp new file mode 100644 index 0000000000..ca303be03d --- /dev/null +++ b/src/gui/painting/qpaintengine_s60.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <private/qpaintengine_s60_p.h> +#include <private/qpixmap_s60_p.h> +#include <private/qt_s60_p.h> +#include <private/qvolatileimage_p.h> + +QT_BEGIN_NAMESPACE + +class QS60PaintEnginePrivate : public QRasterPaintEnginePrivate +{ +public: + QS60PaintEnginePrivate() {} +}; + +QS60PaintEngine::QS60PaintEngine(QPaintDevice *device, QS60PixmapData *data) + : QRasterPaintEngine(*(new QS60PaintEnginePrivate), device), pixmapData(data) +{ +} + +bool QS60PaintEngine::begin(QPaintDevice *device) +{ + Q_D(QS60PaintEngine); + + if (pixmapData->classId() == QPixmapData::RasterClass) { + pixmapData->beginDataAccess(); + bool ret = QRasterPaintEngine::begin(device); + // Make sure QPaintEngine::paintDevice() returns the proper device. + // QRasterPaintEngine changes pdev to QImage in case of RasterClass QPixmapData + // which is incorrect in Symbian. + d->pdev = device; + return ret; + } + + return QRasterPaintEngine::begin(device); +} + +bool QS60PaintEngine::end() +{ + if (pixmapData->classId() == QPixmapData::RasterClass) { + bool ret = QRasterPaintEngine::end(); + pixmapData->endDataAccess(); + return ret; + } + return QRasterPaintEngine::end(); +} + +void QS60PaintEngine::drawPixmap(const QPointF &p, const QPixmap &pm) +{ + if (pm.pixmapData()->classId() == QPixmapData::RasterClass) { + QS60PixmapData *srcData = static_cast<QS60PixmapData *>(pm.pixmapData()); + srcData->beginDataAccess(); + QRasterPaintEngine::drawPixmap(p, pm); + srcData->endDataAccess(); + } else { + void *nativeData = pm.pixmapData()->toNativeType(QPixmapData::VolatileImage); + if (nativeData) { + QVolatileImage *img = static_cast<QVolatileImage *>(nativeData); + img->beginDataAccess(); + QRasterPaintEngine::drawImage(p, img->imageRef()); + img->endDataAccess(true); + } else { + QRasterPaintEngine::drawPixmap(p, pm); + } + } +} + +void QS60PaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + if (pm.pixmapData()->classId() == QPixmapData::RasterClass) { + QS60PixmapData *srcData = static_cast<QS60PixmapData *>(pm.pixmapData()); + srcData->beginDataAccess(); + QRasterPaintEngine::drawPixmap(r, pm, sr); + srcData->endDataAccess(); + } else { + void *nativeData = pm.pixmapData()->toNativeType(QPixmapData::VolatileImage); + if (nativeData) { + QVolatileImage *img = static_cast<QVolatileImage *>(nativeData); + img->beginDataAccess(); + QRasterPaintEngine::drawImage(r, img->imageRef(), sr); + img->endDataAccess(true); + } else { + QRasterPaintEngine::drawPixmap(r, pm, sr); + } + } +} + +void QS60PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &sr) +{ + if (pm.pixmapData()->classId() == QPixmapData::RasterClass) { + QS60PixmapData *srcData = static_cast<QS60PixmapData *>(pm.pixmapData()); + srcData->beginDataAccess(); + QRasterPaintEngine::drawTiledPixmap(r, pm, sr); + srcData->endDataAccess(); + } else { + QRasterPaintEngine::drawTiledPixmap(r, pm, sr); + } +} + +void QS60PaintEngine::prepare(QImage *image) +{ + QRasterBuffer *buffer = d_func()->rasterBuffer.data(); + if (buffer) + buffer->prepare(image); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine_s60_p.h b/src/gui/painting/qpaintengine_s60_p.h new file mode 100644 index 0000000000..a62bdac97c --- /dev/null +++ b/src/gui/painting/qpaintengine_s60_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_S60_P_H +#define QPAINTENGINE_S60_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qpaintengine_raster_p.h" + +QT_BEGIN_NAMESPACE + +class QS60PaintEnginePrivate; +class QS60PixmapData; + +class QS60PaintEngine : public QRasterPaintEngine +{ + Q_DECLARE_PRIVATE(QS60PaintEngine) + +public: + QS60PaintEngine(QPaintDevice *device, QS60PixmapData* data); + bool begin(QPaintDevice *device); + bool end(); + + void drawPixmap(const QPointF &p, const QPixmap &pm); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &sr); + + void prepare(QImage* image); + +private: + QS60PixmapData *pixmapData; +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_S60_P_H diff --git a/src/gui/painting/qpaintengine_x11.cpp b/src/gui/painting/qpaintengine_x11.cpp new file mode 100644 index 0000000000..94828fba53 --- /dev/null +++ b/src/gui/painting/qpaintengine_x11.cpp @@ -0,0 +1,2507 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include "private/qpixmap_x11_p.h" + +#include "qapplication.h" +#include "qdebug.h" +#include "qfont.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qpixmapcache.h" +#include "qtextcodec.h" +#include "qcoreevent.h" +#include "qiodevice.h" +#include <qmath.h> + +#include "qpainter_p.h" +#include <qtextlayout.h> +#include <qvarlengtharray.h> +#include <private/qfont_p.h> +#include <private/qtextengine_p.h> +#include <private/qpaintengine_x11_p.h> +#include <private/qfontengine_x11_p.h> +#include <private/qwidget_p.h> +#include <private/qpainterpath_p.h> + +#include "qpen.h" +#include "qcolor.h" +#include "qcolormap.h" + +#include <private/qpaintengine_p.h> +#include "qpaintengine_x11_p.h" + +#include <private/qt_x11_p.h> +#include <private/qnumeric_p.h> +#include <limits.h> + +#ifndef QT_NO_XRENDER +#include <private/qtessellator_p.h> +#endif + +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +extern Drawable qt_x11Handle(const QPaintDevice *pd); +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); +extern QPixmap qt_pixmapForBrush(int brushStyle, bool invert); //in qbrush.cpp +extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); + +// use the same rounding as in qrasterizer.cpp (6 bit fixed point) +static const qreal aliasedCoordinateDelta = 0.5 - 0.015625; + +#undef X11 // defined in qt_x11_p.h +/*! + Returns the X11 specific pen GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +Q_GUI_EXPORT GC qt_x11_get_pen_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc; + } + return 0; +} + +/*! + Returns the X11 specific brush GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +Q_GUI_EXPORT GC qt_x11_get_brush_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc_brush; + } + return 0; +} +#define X11 qt_x11Data + +#ifndef QT_NO_XRENDER +static const int compositionModeToRenderOp[QPainter::CompositionMode_Xor + 1] = { + PictOpOver, //CompositionMode_SourceOver, + PictOpOverReverse, //CompositionMode_DestinationOver, + PictOpClear, //CompositionMode_Clear, + PictOpSrc, //CompositionMode_Source, + PictOpDst, //CompositionMode_Destination, + PictOpIn, //CompositionMode_SourceIn, + PictOpInReverse, //CompositionMode_DestinationIn, + PictOpOut, //CompositionMode_SourceOut, + PictOpOutReverse, //CompositionMode_DestinationOut, + PictOpAtop, //CompositionMode_SourceAtop, + PictOpAtopReverse, //CompositionMode_DestinationAtop, + PictOpXor //CompositionMode_Xor +}; + +static inline int qpainterOpToXrender(QPainter::CompositionMode mode) +{ + Q_ASSERT(mode <= QPainter::CompositionMode_Xor); + return compositionModeToRenderOp[mode]; +} +#endif + +// hack, so we don't have to make QRegion::clipRectangles() public or include +// X11 headers in qregion.h +Q_GUI_EXPORT void *qt_getClipRects(const QRegion &r, int &num) +{ + return r.clipRectangles(num); +} + +static inline void x11SetClipRegion(Display *dpy, GC gc, GC gc2, +#ifndef QT_NO_XRENDER + Picture picture, +#else + Qt::HANDLE picture, +#endif + const QRegion &r) +{ + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(r, num); + + if (gc) + XSetClipRectangles( dpy, gc, 0, 0, rects, num, YXBanded ); + if (gc2) + XSetClipRectangles( dpy, gc2, 0, 0, rects, num, YXBanded ); + +#ifndef QT_NO_XRENDER + if (picture) + XRenderSetPictureClipRectangles(dpy, picture, 0, 0, rects, num); +#else + Q_UNUSED(picture); +#endif // QT_NO_XRENDER +} + + +static inline void x11ClearClipRegion(Display *dpy, GC gc, GC gc2, +#ifndef QT_NO_XRENDER + Picture picture +#else + Qt::HANDLE picture +#endif + ) +{ + if (gc) + XSetClipMask(dpy, gc, XNone); + if (gc2) + XSetClipMask(dpy, gc2, XNone); + +#ifndef QT_NO_XRENDER + if (picture) { + XRenderPictureAttributes attrs; + attrs.clip_mask = XNone; + XRenderChangePicture (dpy, picture, CPClipMask, &attrs); + } +#else + Q_UNUSED(picture); +#endif // QT_NO_XRENDER +} + + +#define DITHER_SIZE 16 +static const uchar base_dither_matrix[DITHER_SIZE][DITHER_SIZE] = { + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + +static QPixmap qt_patternForAlpha(uchar alpha, int screen) +{ + QPixmap pm; + QString key = QLatin1Literal("$qt-alpha-brush$") + % HexString<uchar>(alpha) + % HexString<int>(screen); + + if (!QPixmapCache::find(key, pm)) { + // #### why not use a mono image here???? + QImage pattern(DITHER_SIZE, DITHER_SIZE, QImage::Format_ARGB32); + pattern.fill(0xffffffff); + for (int y = 0; y < DITHER_SIZE; ++y) { + for (int x = 0; x < DITHER_SIZE; ++x) { + if (base_dither_matrix[x][y] <= alpha) + pattern.setPixel(x, y, 0x00000000); + } + } + pm = QBitmap::fromImage(pattern); + pm.x11SetScreen(screen); + QPixmapCache::insert(key, pm); + } + return pm; +} + +#if !defined(QT_NO_XRENDER) + +class QXRenderTessellator : public QTessellator +{ +public: + QXRenderTessellator() : traps(0), allocated(0), size(0) {} + ~QXRenderTessellator() { free(traps); } + XTrapezoid *traps; + int allocated; + int size; + void addTrap(const Trapezoid &trap); + QRect tessellate(const QPointF *points, int nPoints, bool winding) { + size = 0; + setWinding(winding); + return QTessellator::tessellate(points, nPoints).toRect(); + } + void done() { + if (allocated > 64) { + free(traps); + traps = 0; + allocated = 0; + } + } +}; + +void QXRenderTessellator::addTrap(const Trapezoid &trap) +{ + if (size == allocated) { + allocated = qMax(2*allocated, 64); + traps = q_check_ptr((XTrapezoid *)realloc(traps, allocated * sizeof(XTrapezoid))); + } + traps[size].top = Q27Dot5ToXFixed(trap.top); + traps[size].bottom = Q27Dot5ToXFixed(trap.bottom); + traps[size].left.p1.x = Q27Dot5ToXFixed(trap.topLeft->x); + traps[size].left.p1.y = Q27Dot5ToXFixed(trap.topLeft->y); + traps[size].left.p2.x = Q27Dot5ToXFixed(trap.bottomLeft->x); + traps[size].left.p2.y = Q27Dot5ToXFixed(trap.bottomLeft->y); + traps[size].right.p1.x = Q27Dot5ToXFixed(trap.topRight->x); + traps[size].right.p1.y = Q27Dot5ToXFixed(trap.topRight->y); + traps[size].right.p2.x = Q27Dot5ToXFixed(trap.bottomRight->x); + traps[size].right.p2.y = Q27Dot5ToXFixed(trap.bottomRight->y); + ++size; +} + +#endif // !defined(QT_NO_XRENDER) + + +#ifndef QT_NO_XRENDER +static Picture getPatternFill(int screen, const QBrush &b) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = X11->preMultiply(b.color()); + XRenderColor bg_color; + + bg_color = X11->preMultiply(QColor(0, 0, 0, 0)); + + for (int i = 0; i < X11->pattern_fill_count; ++i) { + if (X11->pattern_fills[i].screen == screen + && X11->pattern_fills[i].opaque == false + && X11->pattern_fills[i].style == b.style() + && X11->pattern_fills[i].color.alpha == color.alpha + && X11->pattern_fills[i].color.red == color.red + && X11->pattern_fills[i].color.green == color.green + && X11->pattern_fills[i].color.blue == color.blue + && X11->pattern_fills[i].bg_color.alpha == bg_color.alpha + && X11->pattern_fills[i].bg_color.red == bg_color.red + && X11->pattern_fills[i].bg_color.green == bg_color.green + && X11->pattern_fills[i].bg_color.blue == bg_color.blue) + return X11->pattern_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->pattern_fills[i].screen != screen && X11->pattern_fills[i].picture) { + XRenderFreePicture (X11->display, X11->pattern_fills[i].picture); + X11->pattern_fills[i].picture = 0; + } + + if (!X11->pattern_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 8, 8, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->pattern_fills[i].picture = XRenderCreatePicture (X11->display, pixmap, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (X11->display, pixmap); + } + + X11->pattern_fills[i].screen = screen; + X11->pattern_fills[i].color = color; + X11->pattern_fills[i].bg_color = bg_color; + X11->pattern_fills[i].opaque = false; + X11->pattern_fills[i].style = b.style(); + + XRenderFillRectangle(X11->display, PictOpSrc, X11->pattern_fills[i].picture, &bg_color, 0, 0, 8, 8); + + QPixmap pattern(qt_pixmapForBrush(b.style(), true)); + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(X11->display, pattern.x11PictureHandle(), CPRepeat, &attrs); + + Picture fill_fg = X11->getSolidFill(screen, b.color()); + XRenderComposite(X11->display, PictOpOver, fill_fg, pattern.x11PictureHandle(), + X11->pattern_fills[i].picture, + 0, 0, 0, 0, 0, 0, 8, 8); + + return X11->pattern_fills[i].picture; +} + +static void qt_render_bitmap(Display *dpy, int scrn, Picture src, Picture dst, + int sx, int sy, int x, int y, int sw, int sh, + const QPen &pen) +{ + Picture fill_fg = X11->getSolidFill(scrn, pen.color()); + XRenderComposite(dpy, PictOpOver, + fill_fg, src, dst, sx, sy, sx, sy, x, y, sw, sh); +} +#endif + +void QX11PaintEnginePrivate::init() +{ + dpy = 0; + scrn = 0; + hd = 0; + picture = 0; + xinfo = 0; +#ifndef QT_NO_XRENDER + current_brush = 0; + composition_mode = PictOpOver; + tessellator = new QXRenderTessellator; +#endif +} + +void QX11PaintEnginePrivate::setupAdaptedOrigin(const QPoint &p) +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, p.x(), p.y()); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, p.x(), p.y()); +} + +void QX11PaintEnginePrivate::resetAdaptedOrigin() +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, 0, 0); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, 0, 0); +} + +void QX11PaintEnginePrivate::clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly) +{ + int clipped_count = 0; + qt_float_point *clipped_points = 0; + polygonClipper.clipPolygon((qt_float_point *) poly.data(), poly.size(), + &clipped_points, &clipped_count); + clipped_poly->resize(clipped_count); + for (int i=0; i<clipped_count; ++i) + (*clipped_poly)[i] = *((QPointF *)(&clipped_points[i])); +} + +void QX11PaintEnginePrivate::systemStateChanged() +{ + Q_Q(QX11PaintEngine); + QPainter *painter = q->state ? static_cast<QPainterState *>(q->state)->painter : 0; + if (painter && painter->hasClipping()) { + if (q->testDirty(QPaintEngine::DirtyTransform)) + q->updateMatrix(q->state->transform()); + QPolygonF clip_poly_dev(matrix.map(painter->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + q->updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + q->updateClipRegion_dev(QRegion(), Qt::NoClip); + } +} + +static QPaintEngine::PaintEngineFeatures qt_decide_features() +{ + QPaintEngine::PaintEngineFeatures features = + QPaintEngine::PrimitiveTransform + | QPaintEngine::PatternBrush + | QPaintEngine::AlphaBlend + | QPaintEngine::PainterPaths + | QPaintEngine::RasterOpModes; + + if (X11->use_xrender) { + features |= QPaintEngine::Antialiasing; + features |= QPaintEngine::PorterDuff; + features |= QPaintEngine::MaskedBrush; +#if 0 + if (X11->xrender_version > 10) { + features |= QPaintEngine::LinearGradientFill; + // ### + } +#endif + } + + return features; +} + +/* + * QX11PaintEngine members + */ + +QX11PaintEngine::QX11PaintEngine() + : QPaintEngine(*(new QX11PaintEnginePrivate), qt_decide_features()) +{ + d_func()->init(); +} + +QX11PaintEngine::QX11PaintEngine(QX11PaintEnginePrivate &dptr) + : QPaintEngine(dptr, qt_decide_features()) +{ + d_func()->init(); +} + +QX11PaintEngine::~QX11PaintEngine() +{ +#ifndef QT_NO_XRENDER + Q_D(QX11PaintEngine); + delete d->tessellator; +#endif +} + +bool QX11PaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QX11PaintEngine); + d->xinfo = qt_x11Info(pdev); + QWidget *w = d->pdev->devType() == QInternal::Widget ? static_cast<QWidget *>(d->pdev) : 0; + const bool isAlienWidget = w && !w->internalWinId() && w->testAttribute(Qt::WA_WState_Created); +#ifndef QT_NO_XRENDER + if (w) { + if (isAlienWidget) + d->picture = (::Picture)w->nativeParentWidget()->x11PictureHandle(); + else + d->picture = (::Picture)w->x11PictureHandle(); + } else if (pdev->devType() == QInternal::Pixmap) { + const QPixmap *pm = static_cast<const QPixmap *>(pdev); + QX11PixmapData *data = static_cast<QX11PixmapData*>(pm->data.data()); + if (X11->use_xrender && data->depth() != 32 && data->x11_mask) + data->convertToARGB32(); + d->picture = (::Picture)static_cast<const QPixmap *>(pdev)->x11PictureHandle(); + } +#else + d->picture = 0; +#endif + d->hd = !isAlienWidget ? qt_x11Handle(pdev) : qt_x11Handle(w->nativeParentWidget()); + + Q_ASSERT(d->xinfo != 0); + d->dpy = d->xinfo->display(); // get display variable + d->scrn = d->xinfo->screen(); // get screen variable + + d->crgn = QRegion(); + d->gc = XCreateGC(d->dpy, d->hd, 0, 0); + d->gc_brush = XCreateGC(d->dpy, d->hd, 0, 0); + d->has_alpha_brush = false; + d->has_alpha_pen = false; + d->has_clipping = false; + d->has_complex_xform = false; + d->has_scaling_xform = false; + d->has_non_scaling_xform = true; + d->xform_scale = 1; + d->has_custom_pen = false; + d->matrix = QTransform(); + d->pdev_depth = d->pdev->depth(); + d->render_hints = 0; + d->txop = QTransform::TxNone; + d->use_path_fallback = false; +#if !defined(QT_NO_XRENDER) + d->composition_mode = PictOpOver; +#endif + d->xlibMaxLinePoints = 32762; // a safe number used to avoid, call to XMaxRequestSize(d->dpy) - 3; + d->opacity = 1; + + // Set up the polygon clipper. Note: This will only work in + // polyline mode as long as we have a buffer zone, since a + // polyline may be clipped into several non-connected polylines. + const int BUFFERZONE = 1000; + QRect devClipRect(-BUFFERZONE, -BUFFERZONE, + pdev->width() + 2*BUFFERZONE, pdev->height() + 2*BUFFERZONE); + d->polygonClipper.setBoundingRect(devClipRect); + + if (isAlienWidget) { + // Set system clip for alien widgets painting outside the paint event. + // This is not a problem with native windows since the windowing system + // will handle the clip. + QWidgetPrivate *wd = w->d_func(); + QRegion widgetClip(wd->clipRect()); + wd->clipToEffectiveMask(widgetClip); + wd->subtractOpaqueSiblings(widgetClip); + widgetClip.translate(w->mapTo(w->nativeParentWidget(), QPoint())); + setSystemClip(widgetClip); + } + + QPixmap::x11SetDefaultScreen(d->xinfo->screen()); + + if (w && w->testAttribute(Qt::WA_PaintUnclipped)) { // paint direct on device + updatePen(QPen(Qt::black)); + updateBrush(QBrush(Qt::white), QPoint()); + XSetSubwindowMode(d->dpy, d->gc, IncludeInferiors); + XSetSubwindowMode(d->dpy, d->gc_brush, IncludeInferiors); +#ifndef QT_NO_XRENDER + XRenderPictureAttributes attrs; + attrs.subwindow_mode = IncludeInferiors; + XRenderChangePicture(d->dpy, d->picture, CPSubwindowMode, &attrs); +#endif + } + + setDirty(QPaintEngine::DirtyClipRegion); + setDirty(QPaintEngine::DirtyPen); + setDirty(QPaintEngine::DirtyBrush); + setDirty(QPaintEngine::DirtyBackground); + + return true; +} + +bool QX11PaintEngine::end() +{ + Q_D(QX11PaintEngine); + +#if !defined(QT_NO_XRENDER) + if (d->picture) { + // reset clipping/subwindow mode on our render picture + XRenderPictureAttributes attrs; + attrs.subwindow_mode = ClipByChildren; + attrs.clip_mask = XNone; + XRenderChangePicture(d->dpy, d->picture, CPClipMask|CPSubwindowMode, &attrs); + } +#endif + + if (d->gc_brush && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc_brush); + d->gc_brush = 0; + } + + if (d->gc && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc); + d->gc = 0; + } + + // Restore system clip for alien widgets painting outside the paint event. + if (d->pdev->devType() == QInternal::Widget && !static_cast<QWidget *>(d->pdev)->internalWinId()) + setSystemClip(QRegion()); + + return true; +} + +static bool clipLine(QLineF *line, const QRect &rect) +{ + qreal x1 = line->x1(); + qreal x2 = line->x2(); + qreal y1 = line->y1(); + qreal y2 = line->y2(); + + qreal left = rect.x(); + qreal right = rect.x() + rect.width() - 1; + qreal top = rect.y(); + qreal bottom = rect.y() + rect.height() - 1; + + enum { Left, Right, Top, Bottom }; + // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html + int p1 = ((x1 < left) << Left) + | ((x1 > right) << Right) + | ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + int p2 = ((x2 < left) << Left) + | ((x2 > right) << Right) + | ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + + if (p1 & p2) + // completely outside + return false; + + if (p1 | p2) { + qreal dx = x2 - x1; + qreal dy = y2 - y1; + + // clip x coordinates + if (x1 < left) { + y1 += dy/dx * (left - x1); + x1 = left; + } else if (x1 > right) { + y1 -= dy/dx * (x1 - right); + x1 = right; + } + if (x2 < left) { + y2 += dy/dx * (left - x2); + x2 = left; + } else if (x2 > right) { + y2 -= dy/dx * (x2 - right); + x2 = right; + } + p1 = ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + p2 = ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + if (p1 & p2) + return false; + // clip y coordinates + if (y1 < top) { + x1 += dx/dy * (top - y1); + y1 = top; + } else if (y1 > bottom) { + x1 -= dx/dy * (y1 - bottom); + y1 = bottom; + } + if (y2 < top) { + x2 += dx/dy * (top - y2); + y2 = top; + } else if (y2 > bottom) { + x2 -= dx/dy * (y2 - bottom); + y2 = bottom; + } + *line = QLineF(QPointF(x1, y1), QPointF(x2, y2)); + } + return true; +} + +void QX11PaintEngine::drawLines(const QLine *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef; + if (d->txop == QTransform::TxNone) { + linef = lines[i]; + } else { + linef = d->matrix.map(QLineF(lines[i])); + } + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +void QX11PaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef = d->matrix.map(lines[i]); + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +static inline QLine clipStraightLine(const QRect &clip, const QLine &l) +{ + if (l.p1().x() == l.p2().x()) { + int x = qBound(clip.left(), l.p1().x(), clip.right()); + int y1 = qBound(clip.top(), l.p1().y(), clip.bottom()); + int y2 = qBound(clip.top(), l.p2().y(), clip.bottom()); + + return QLine(x, y1, x, y2); + } else { + Q_ASSERT(l.p1().y() == l.p2().y()); + + int x1 = qBound(clip.left(), l.p1().x(), clip.right()); + int x2 = qBound(clip.left(), l.p2().x(), clip.right()); + int y = qBound(clip.top(), l.p1().y(), clip.bottom()); + + return QLine(x1, y, x2, y); + } +} + +void QX11PaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (rectCount != 1 + || d->has_pen + || d->has_alpha_brush + || d->has_complex_xform + || d->has_custom_pen + || d->cbrush.style() != Qt::SolidPattern) + { + QPaintEngine::drawRects(rects, rectCount); + return; + } + + QPoint alignedOffset; + if (d->txop == QTransform::TxTranslate) { + QPointF offset(d->matrix.dx(), d->matrix.dy()); + alignedOffset = offset.toPoint(); + if (offset != QPointF(alignedOffset)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + } + + const QRectF& r = rects[0]; + QRect alignedRect = r.toAlignedRect(); + if (r != QRectF(alignedRect)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + alignedRect.translate(alignedOffset); + + QRect clip(d->polygonClipper.boundingRect()); + alignedRect = alignedRect.intersected(clip); + if (alignedRect.isEmpty()) + return; + + // simple-case: + // the rectangle is pixel-aligned + // the fill brush is just a solid non-alpha color + // the painter transform is only integer translation + // ignore: antialiasing and just XFillRectangles directly + XRectangle xrect; + xrect.x = short(alignedRect.x()); + xrect.y = short(alignedRect.y()); + xrect.width = ushort(alignedRect.width()); + xrect.height = ushort(alignedRect.height()); + XFillRectangles(d->dpy, d->hd, d->gc_brush, &xrect, 1); +} + +void QX11PaintEngine::drawRects(const QRect *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (d->has_alpha_pen + || d->has_complex_xform + || d->has_custom_pen + || (d->render_hints & QPainter::Antialiasing)) + { + for (int i = 0; i < rectCount; ++i) { + QPainterPath path; + path.addRect(rects[i]); + drawPath(path); + } + return; + } + + QRect clip(d->polygonClipper.boundingRect()); + QPoint offset(qRound(d->matrix.dx()), qRound(d->matrix.dy())); +#if !defined(QT_NO_XRENDER) + ::Picture pict = d->picture; + + if (X11->use_xrender && pict && d->has_brush && d->pdev_depth != 1 + && (d->has_texture || d->has_alpha_brush)) + { + XRenderColor xc; + if (!d->has_texture && !d->has_pattern) + xc = X11->preMultiply(d->cbrush.color()); + + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + if (d->has_texture || d->has_pattern) { + XRenderComposite(d->dpy, d->composition_mode, d->current_brush, 0, pict, + qRound(r.x() - d->bg_origin.x()), qRound(r.y() - d->bg_origin.y()), + 0, 0, r.x(), r.y(), r.width(), r.height()); + } else { + XRenderFillRectangle(d->dpy, d->composition_mode, pict, &xc, r.x(), r.y(), r.width(), r.height()); + } + if (d->has_pen) + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + } else +#endif // !QT_NO_XRENDER + { + if (d->has_brush && d->has_pen) { + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + d->setupAdaptedOrigin(r.topLeft()); + XFillRectangle(d->dpy, d->hd, d->gc_brush, r.x(), r.y(), r.width(), r.height()); + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + d->resetAdaptedOrigin(); + } else { + QVarLengthArray<XRectangle> xrects(rectCount); + int numClipped = rectCount; + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + --numClipped; + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) { + --numClipped; + continue; + } + xrects[i].x = short(r.x()); + xrects[i].y = short(r.y()); + xrects[i].width = ushort(r.width()); + xrects[i].height = ushort(r.height()); + } + if (numClipped) { + d->setupAdaptedOrigin(rects[0].topLeft()); + if (d->has_brush) + XFillRectangles(d->dpy, d->hd, d->gc_brush, xrects.data(), numClipped); + else if (d->has_pen) + XDrawRectangles(d->dpy, d->hd, d->gc, xrects.data(), numClipped); + d->resetAdaptedOrigin(); + } + } + } +} + +static inline void setCapStyle(int cap_style, GC gc) +{ + ulong mask = GCCapStyle; + XGCValues vals; + vals.cap_style = cap_style; + XChangeGC(X11->display, gc, mask, &vals); +} + +void QX11PaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->cpen.isCosmetic() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + const QPoint *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x()+.005, points->y()); + drawPath(path); + ++points; + } + + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPoint &xformed = d->matrix.map(points[i]); + int x = xformed.x(); + int y = xformed.y(); + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +void QX11PaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->cpen.isCosmetic() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + + const QPointF *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x() + 0.005, points->y()); + drawPath(path); + ++points; + } + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPointF &xformed = d->matrix.map(points[i]); + int x = qFloor(xformed.x()); + int y = qFloor(xformed.y()); + + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +QPainter::RenderHints QX11PaintEngine::supportedRenderHints() const +{ +#if !defined(QT_NO_XRENDER) + if (X11->use_xrender) + return QPainter::Antialiasing; +#endif + return QFlag(0); +} + +void QX11PaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QX11PaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + + if (flags & DirtyOpacity) { + d->opacity = state.opacity(); + // Force update pen/brush as to get proper alpha colors propagated + flags |= DirtyPen; + flags |= DirtyBrush; + } + + if (flags & DirtyTransform) updateMatrix(state.transform()); + if (flags & DirtyPen) updatePen(state.pen()); + if (flags & (DirtyBrush | DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin()); + if (flags & DirtyFont) updateFont(state.font()); + + if (state.state() & DirtyClipEnabled) { + if (state.isClipEnabled()) { + QPolygonF clip_poly_dev(d->matrix.map(painter()->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + updateClipRegion_dev(QRegion(), Qt::NoClip); + } + } + + if (flags & DirtyClipPath) { + QPolygonF clip_poly_dev(d->matrix.map(state.clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon(), state.clipPath().fillRule()), + state.clipOperation()); + } else if (flags & DirtyClipRegion) { + extern QPainterPath qt_regionToPath(const QRegion ®ion); + QPainterPath clip_path = qt_regionToPath(state.clipRegion()); + QPolygonF clip_poly_dev(d->matrix.map(clip_path.toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), state.clipOperation()); + } + if (flags & DirtyHints) updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) { + int function = GXcopy; + if (state.compositionMode() >= QPainter::RasterOp_SourceOrDestination) { + switch (state.compositionMode()) { + case QPainter::RasterOp_SourceOrDestination: + function = GXor; + break; + case QPainter::RasterOp_SourceAndDestination: + function = GXand; + break; + case QPainter::RasterOp_SourceXorDestination: + function = GXxor; + break; + case QPainter::RasterOp_NotSourceAndNotDestination: + function = GXnor; + break; + case QPainter::RasterOp_NotSourceOrNotDestination: + function = GXnand; + break; + case QPainter::RasterOp_NotSourceXorDestination: + function = GXequiv; + break; + case QPainter::RasterOp_NotSource: + function = GXcopyInverted; + break; + case QPainter::RasterOp_SourceAndNotDestination: + function = GXandReverse; + break; + case QPainter::RasterOp_NotSourceAndDestination: + function = GXandInverted; + break; + default: + function = GXcopy; + } + } +#if !defined(QT_NO_XRENDER) + else { + d->composition_mode = + qpainterOpToXrender(state.compositionMode()); + } +#endif + XSetFunction(X11->display, d->gc, function); + XSetFunction(X11->display, d->gc_brush, function); + } + d->decidePathFallback(); + d->decideCoordAdjust(); +} + +void QX11PaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QX11PaintEngine); + d->render_hints = hints; + +#if !defined(QT_NO_XRENDER) + if (X11->use_xrender && d->picture) { + XRenderPictureAttributes attrs; + attrs.poly_edge = (hints & QPainter::Antialiasing) ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(d->dpy, d->picture, CPPolyEdge, &attrs); + } +#endif +} + +void QX11PaintEngine::updatePen(const QPen &pen) +{ + Q_D(QX11PaintEngine); + d->cpen = pen; + int cp = CapButt; + int jn = JoinMiter; + int ps = pen.style(); + + if (d->opacity < 1.0) { + QColor c = d->cpen.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cpen.setColor(c); + } + + d->has_pen = (ps != Qt::NoPen); + d->has_alpha_pen = (pen.color().alpha() != 255); + + switch (pen.capStyle()) { + case Qt::SquareCap: + cp = CapProjecting; + break; + case Qt::RoundCap: + cp = CapRound; + break; + case Qt::FlatCap: + default: + cp = CapButt; + break; + } + switch (pen.joinStyle()) { + case Qt::BevelJoin: + jn = JoinBevel; + break; + case Qt::RoundJoin: + jn = JoinRound; + break; + case Qt::MiterJoin: + default: + jn = JoinMiter; + break; + } + + d->adapted_pen_origin = false; + + char dashes[10]; // custom pen dashes + int dash_len = 0; // length of dash list + int xStyle = LineSolid; + + /* + We are emulating Windows here. Windows treats cpen.width() == 1 + (or 0) as a very special case. The fudge variable unifies this + case with the general case. + */ + qreal pen_width = pen.widthF(); + int scale = qRound(pen_width < 1 ? 1 : pen_width); + int space = (pen_width < 1 && pen_width > 0 ? 1 : (2 * scale)); + int dot = 1 * scale; + int dash = 4 * scale; + + d->has_custom_pen = false; + + switch (ps) { + case Qt::NoPen: + case Qt::SolidLine: + xStyle = LineSolid; + break; + case Qt::DashLine: + dashes[0] = dash; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DotLine: + dashes[0] = dot; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DashDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dash_len = 4; + xStyle = LineOnOffDash; + break; + case Qt::DashDotDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dashes[4] = dot; + dashes[5] = space; + dash_len = 6; + xStyle = LineOnOffDash; + break; + case Qt::CustomDashLine: + d->has_custom_pen = true; + break; + } + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures | GCLineWidth + | GCCapStyle | GCJoinStyle | GCLineStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(pen.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (d->pdev->devType() == QInternal::Pixmap && d->pdev_depth == 32 + && X11->use_xrender) { + vals.foreground = pen.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QColormap cmap = QColormap::instance(d->scrn); + vals.foreground = cmap.pixel(pen.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + } + + + vals.line_width = qRound(pen.widthF()); + vals.cap_style = cp; + vals.join_style = jn; + vals.line_style = xStyle; + + XChangeGC(d->dpy, d->gc, mask, &vals); + + if (dash_len) { // make dash list + XSetDashes(d->dpy, d->gc, 0, dashes, dash_len); + } + + if (!d->has_clipping) { // if clipping is set the paintevent clip region is merged with the clip region + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc, 0, d->picture); + } +} + +void QX11PaintEngine::updateBrush(const QBrush &brush, const QPointF &origin) +{ + Q_D(QX11PaintEngine); + d->cbrush = brush; + d->bg_origin = origin; + d->adapted_brush_origin = false; +#if !defined(QT_NO_XRENDER) + d->current_brush = 0; +#endif + if (d->opacity < 1.0) { + QColor c = d->cbrush.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cbrush.setColor(c); + } + + int s = FillSolid; + int bs = d->cbrush.style(); + d->has_brush = (bs != Qt::NoBrush); + d->has_pattern = bs >= Qt::Dense1Pattern && bs <= Qt::DiagCrossPattern; + d->has_texture = bs == Qt::TexturePattern; + d->has_alpha_brush = brush.color().alpha() != 255; + d->has_alpha_texture = d->has_texture && d->cbrush.texture().hasAlphaChannel(); + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures + | GCLineStyle | GCCapStyle | GCJoinStyle | GCFillStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(d->cbrush.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (X11->use_xrender && d->pdev->devType() == QInternal::Pixmap + && d->pdev_depth == 32) { + vals.foreground = d->cbrush.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QColormap cmap = QColormap::instance(d->scrn); + vals.foreground = cmap.pixel(d->cbrush.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + + if (!X11->use_xrender && d->has_brush && !d->has_pattern && !brush.isOpaque()) { + QPixmap pattern = qt_patternForAlpha(brush.color().alpha(), d->scrn); + mask |= GCStipple; + vals.stipple = pattern.handle(); + s = FillStippled; + d->adapted_brush_origin = true; + } + } + vals.cap_style = CapButt; + vals.join_style = JoinMiter; + vals.line_style = LineSolid; + + if (d->has_pattern || d->has_texture) { + if (bs == Qt::TexturePattern) { + d->brush_pm = qt_toX11Pixmap(d->cbrush.texture()); +#if !defined(QT_NO_XRENDER) + if (X11->use_xrender) { + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, d->brush_pm.x11PictureHandle(), CPRepeat, &attrs); + QX11PixmapData *data = static_cast<QX11PixmapData*>(d->brush_pm.data.data()); + if (data->mask_picture) + XRenderChangePicture(d->dpy, data->mask_picture, CPRepeat, &attrs); + } +#endif + } else { + d->brush_pm = qt_toX11Pixmap(qt_pixmapForBrush(bs, true)); + } + d->brush_pm.x11SetScreen(d->scrn); + if (d->brush_pm.depth() == 1) { + mask |= GCStipple; + vals.stipple = d->brush_pm.handle(); + s = FillStippled; +#if !defined(QT_NO_XRENDER) + if (X11->use_xrender) { + d->bitmap_texture = QPixmap(d->brush_pm.size()); + d->bitmap_texture.fill(Qt::transparent); + d->bitmap_texture = qt_toX11Pixmap(d->bitmap_texture); + d->bitmap_texture.x11SetScreen(d->scrn); + + ::Picture src = X11->getSolidFill(d->scrn, d->cbrush.color()); + XRenderComposite(d->dpy, PictOpSrc, src, d->brush_pm.x11PictureHandle(), + d->bitmap_texture.x11PictureHandle(), + 0, 0, d->brush_pm.width(), d->brush_pm.height(), + 0, 0, d->brush_pm.width(), d->brush_pm.height()); + + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, d->bitmap_texture.x11PictureHandle(), CPRepeat, &attrs); + + d->current_brush = d->bitmap_texture.x11PictureHandle(); + } +#endif + } else { + mask |= GCTile; +#ifndef QT_NO_XRENDER + if (d->pdev_depth == 32 && d->brush_pm.depth() != 32) { + d->brush_pm.detach(); + QX11PixmapData *brushData = static_cast<QX11PixmapData*>(d->brush_pm.data.data()); + brushData->convertToARGB32(); + } +#endif + vals.tile = (d->brush_pm.depth() == d->pdev_depth + ? d->brush_pm.handle() + : static_cast<QX11PixmapData*>(d->brush_pm.data.data())->x11ConvertToDefaultDepth()); + s = FillTiled; +#if !defined(QT_NO_XRENDER) + d->current_brush = d->cbrush.texture().x11PictureHandle(); +#endif + } + + mask |= GCTileStipXOrigin | GCTileStipYOrigin; + vals.ts_x_origin = qRound(origin.x()); + vals.ts_y_origin = qRound(origin.y()); + } +#if !defined(QT_NO_XRENDER) + else if (d->has_alpha_brush) { + d->current_brush = X11->getSolidFill(d->scrn, d->cbrush.color()); + } +#endif + + vals.fill_style = s; + XChangeGC(d->dpy, d->gc_brush, mask, &vals); + if (!d->has_clipping) { + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc_brush, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc_brush, 0, d->picture); + } +} + +void QX11PaintEngine::drawEllipse(const QRectF &rect) +{ + QRect aligned = rect.toAlignedRect(); + if (aligned == rect) + drawEllipse(aligned); + else + QPaintEngine::drawEllipse(rect); +} + +void QX11PaintEngine::drawEllipse(const QRect &rect) +{ + if (rect.isEmpty()) { + drawRects(&rect, 1); + return; + } + + Q_D(QX11PaintEngine); + QRect devclip(SHRT_MIN, SHRT_MIN, SHRT_MAX*2 - 1, SHRT_MAX*2 - 1); + QRect r(rect); + if (d->txop < QTransform::TxRotate) { + r = d->matrix.mapRect(rect); + } else if (d->txop == QTransform::TxRotate && rect.width() == rect.height()) { + QPainterPath path; + path.addEllipse(rect); + r = d->matrix.map(path).boundingRect().toRect(); + } + + if (d->has_alpha_brush || d->has_alpha_pen || d->has_custom_pen || (d->render_hints & QPainter::Antialiasing) + || d->has_alpha_texture || devclip.intersected(r) != r + || (d->has_complex_xform + && !(d->has_non_scaling_xform && rect.width() == rect.height()))) + { + QPainterPath path; + path.addEllipse(rect); + drawPath(path); + return; + } + + int x = r.x(); + int y = r.y(); + int w = r.width(); + int h = r.height(); + if (w < 1 || h < 1) + return; + if (w == 1 && h == 1) { + XDrawPoint(d->dpy, d->hd, d->has_pen ? d->gc : d->gc_brush, x, y); + return; + } + d->setupAdaptedOrigin(rect.topLeft()); + if (d->has_brush) { // draw filled ellipse + XFillArc(d->dpy, d->hd, d->gc_brush, x, y, w, h, 0, 360*64); + if (!d->has_pen) // make smoother outline + XDrawArc(d->dpy, d->hd, d->gc_brush, x, y, w-1, h-1, 0, 360*64); + } + if (d->has_pen) // draw outline + XDrawArc(d->dpy, d->hd, d->gc, x, y, w, h, 0, 360*64); + d->resetAdaptedOrigin(); +} + + + +void QX11PaintEnginePrivate::fillPolygon_translated(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + + QVarLengthArray<QPointF> translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) + offset += QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta); + + for (int i = 0; i < pointCount; ++i) { + translated_points[i] = polygonPoints[i] + offset; + + translated_points[i].rx() = qRound(translated_points[i].x()) + offs; + translated_points[i].ry() = qRound(translated_points[i].y()) + offs; + } + + fillPolygon_dev(translated_points.data(), pointCount, gcMode, mode); +} + +#ifndef QT_NO_XRENDER +static void qt_XRenderCompositeTrapezoids(Display *dpy, + int op, + Picture src, + Picture dst, + _Xconst XRenderPictFormat *maskFormat, + int xSrc, + int ySrc, + const XTrapezoid *traps, int size) +{ + const int MAX_TRAPS = 50000; + while (size) { + int to_draw = size; + if (to_draw > MAX_TRAPS) + to_draw = MAX_TRAPS; + XRenderCompositeTrapezoids(dpy, op, src, dst, + maskFormat, + xSrc, ySrc, + traps, to_draw); + size -= to_draw; + traps += to_draw; + } +} +#endif + +void QX11PaintEnginePrivate::fillPolygon_dev(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + Q_Q(QX11PaintEngine); + + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + +#ifndef QT_NO_XRENDER + //can change if we switch to pen if gcMode != BrushGC + bool has_fill_texture = has_texture; + bool has_fill_pattern = has_pattern; + ::Picture src; +#endif + QBrush fill; + GC fill_gc; + if (gcMode == BrushGC) { + fill = cbrush; + fill_gc = gc_brush; +#ifndef QT_NO_XRENDER + if (current_brush) + src = current_brush; + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } else { + fill = QBrush(cpen.brush()); + fill_gc = gc; +#ifndef QT_NO_XRENDER + //we use the pens brush + has_fill_texture = (fill.style() == Qt::TexturePattern); + has_fill_pattern = (fill.style() >= Qt::Dense1Pattern && fill.style() <= Qt::DiagCrossPattern); + if (has_fill_texture) + src = fill.texture().x11PictureHandle(); + else if (has_fill_pattern) + src = getPatternFill(scrn, fill); + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } + + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount); + +#ifndef QT_NO_XRENDER + bool solid_fill = fill.color().alpha() == 255; + if (has_fill_texture && fill.texture().depth() == 1 && solid_fill) { + has_fill_texture = false; + has_fill_pattern = true; + } + + bool antialias = render_hints & QPainter::Antialiasing; + + if (X11->use_xrender + && picture + && !has_fill_pattern + && (clippedCount > 0) + && (fill.style() != Qt::NoBrush) + && ((has_fill_texture && fill.texture().hasAlpha()) || antialias || !solid_fill || has_alpha_pen != has_alpha_brush)) + { + QRect br = tessellator->tessellate((QPointF *)clippedPoints, clippedCount, + mode == QPaintEngine::WindingMode); + if (tessellator->size > 0) { + XRenderPictureAttributes attrs; + attrs.poly_edge = antialias ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(dpy, picture, CPPolyEdge, &attrs); + int x_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.x) - bg_origin.x()); + int y_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.y) - bg_origin.y()); + qt_XRenderCompositeTrapezoids(dpy, composition_mode, src, picture, + antialias + ? XRenderFindStandardFormat(dpy, PictStandardA8) + : XRenderFindStandardFormat(dpy, PictStandardA1), + x_offset, y_offset, + tessellator->traps, tessellator->size); + tessellator->done(); + } + } else +#endif + if (fill.style() != Qt::NoBrush) { + if (clippedCount > 200000) { + QPolygon poly; + for (int i = 0; i < clippedCount; ++i) + poly << QPoint(qFloor(clippedPoints[i].x), qFloor(clippedPoints[i].y)); + + const QRect bounds = poly.boundingRect(); + const QRect aligned = bounds + & QRect(QPoint(), QSize(pdev->width(), pdev->height())); + + QImage img(aligned.size(), QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QPainter painter(&img); + painter.translate(-aligned.x(), -aligned.y()); + painter.setPen(Qt::NoPen); + painter.setBrush(fill); + if (gcMode == BrushGC) + painter.setBrushOrigin(q->painter()->brushOrigin()); + painter.drawPolygon(poly); + painter.end(); + + q->drawImage(aligned, img, img.rect(), Qt::AutoColor); + } else if (clippedCount > 0) { + QVarLengthArray<XPoint> xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qFloor(clippedPoints[i].x); + xpoints[i].y = qFloor(clippedPoints[i].y); + } + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, WindingRule); + setupAdaptedOrigin(QPoint(xpoints[0].x, xpoints[0].y)); + XFillPolygon(dpy, hd, fill_gc, + xpoints.data(), clippedCount, + mode == QPaintEngine::ConvexMode ? Convex : Complex, CoordModeOrigin); + resetAdaptedOrigin(); + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, EvenOddRule); + } + } +} + +void QX11PaintEnginePrivate::strokePolygon_translated(const QPointF *polygonPoints, int pointCount, bool close) +{ + QVarLengthArray<QPointF> translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + for (int i = 0; i < pointCount; ++i) + translated_points[i] = polygonPoints[i] + offset; + strokePolygon_dev(translated_points.data(), pointCount, close); +} + +void QX11PaintEnginePrivate::strokePolygon_dev(const QPointF *polygonPoints, int pointCount, bool close) +{ + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount, close); + + if (clippedCount > 0) { + QVarLengthArray<XPoint> xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qRound(clippedPoints[i].x + aliasedCoordinateDelta); + xpoints[i].y = qRound(clippedPoints[i].y + aliasedCoordinateDelta); + } + uint numberPoints = qMin(clippedCount, xlibMaxLinePoints); + XPoint *pts = xpoints.data(); + XDrawLines(dpy, hd, gc, pts, numberPoints, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + while (clippedCount) { + XDrawLines(dpy, hd, gc, pts-1, numberPoints+1, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + } + } +} + +void QX11PaintEngine::drawPolygon(const QPointF *polygonPoints, int pointCount, PolygonDrawMode mode) +{ + Q_D(QX11PaintEngine); + if (d->use_path_fallback) { + QPainterPath path(polygonPoints[0]); + for (int i = 1; i < pointCount; ++i) + path.lineTo(polygonPoints[i]); + if (mode == PolylineMode) { + QBrush oldBrush = d->cbrush; + d->cbrush = QBrush(Qt::NoBrush); + path.setFillRule(Qt::WindingFill); + drawPath(path); + d->cbrush = oldBrush; + } else { + path.setFillRule(mode == OddEvenMode ? Qt::OddEvenFill : Qt::WindingFill); + path.closeSubpath(); + drawPath(path); + } + return; + } + if (mode != PolylineMode && d->has_brush) + d->fillPolygon_translated(polygonPoints, pointCount, QX11PaintEnginePrivate::BrushGC, mode); + + if (d->has_pen) + d->strokePolygon_translated(polygonPoints, pointCount, mode != PolylineMode); +} + + +void QX11PaintEnginePrivate::fillPath(const QPainterPath &path, QX11PaintEnginePrivate::GCMode gc_mode, bool transform) +{ + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + + QPainterPath clippedPath; + QPainterPath clipPath; + clipPath.addRect(polygonClipper.boundingRect()); + + if (transform) + clippedPath = (path*matrix).intersected(clipPath); + else + clippedPath = path.intersected(clipPath); + + QList<QPolygonF> polys = clippedPath.toFillPolygons(); + for (int i = 0; i < polys.size(); ++i) { + QVarLengthArray<QPointF> translated_points(polys.at(i).size()); + + for (int j = 0; j < polys.at(i).size(); ++j) { + translated_points[j] = polys.at(i).at(j); + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) { + translated_points[j].rx() = qRound(translated_points[j].rx() + aliasedCoordinateDelta) + offs; + translated_points[j].ry() = qRound(translated_points[j].ry() + aliasedCoordinateDelta) + offs; + } + } + + fillPolygon_dev(translated_points.data(), polys.at(i).size(), gc_mode, + path.fillRule() == Qt::OddEvenFill ? QPaintEngine::OddEvenMode : QPaintEngine::WindingMode); + } +} + +void QX11PaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QX11PaintEngine); + if (path.isEmpty()) + return; + QTransform old_matrix = d->matrix; + + if (d->has_brush) + d->fillPath(path, QX11PaintEnginePrivate::BrushGC, true); + if (d->has_pen + && ((X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->cpen.isCosmetic() && d->txop > QTransform::TxTranslate + && !d->has_non_scaling_xform) + || (d->cpen.style() == Qt::CustomDashLine))) { + QPainterPathStroker stroker; + if (d->cpen.style() == Qt::CustomDashLine) { + stroker.setDashPattern(d->cpen.dashPattern()); + stroker.setDashOffset(d->cpen.dashOffset()); + } else { + stroker.setDashPattern(d->cpen.style()); + } + stroker.setCapStyle(d->cpen.capStyle()); + stroker.setJoinStyle(d->cpen.joinStyle()); + QPainterPath stroke; + qreal width = d->cpen.widthF(); + QPolygonF poly; + QRectF deviceRect(0, 0, d->pdev->width(), d->pdev->height()); + // necessary to get aliased alphablended primitives to be drawn correctly + if (d->cpen.isCosmetic() || d->has_scaling_xform) { + if (d->cpen.isCosmetic()) + stroker.setWidth(width == 0 ? 1 : width); + else + stroker.setWidth(width * d->xform_scale); + stroker.d_ptr->stroker.setClipRect(deviceRect); + stroke = stroker.createStroke(path * d->matrix); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, false); + } else { + stroker.setWidth(width); + stroker.d_ptr->stroker.setClipRect(d->matrix.inverted().mapRect(deviceRect)); + stroke = stroker.createStroke(path); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, true); + } + } else if (d->has_pen) { + // if we have a cosmetic pen - use XDrawLine() for speed + QList<QPolygonF> polys = path.toSubpathPolygons(d->matrix); + for (int i = 0; i < polys.size(); ++i) + d->strokePolygon_dev(polys.at(i).data(), polys.at(i).size(), false); + } +} + +Q_GUI_EXPORT void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image, + Drawable hd, GC gc, Display *dpy, Visual *visual, int depth) +{ + Q_ASSERT(image.format() == QImage::Format_RGB32); + Q_ASSERT(image.depth() == 32); + + XImage *xi; + // Note: this code assumes either RGB or BGR, 8 bpc server layouts + const uint red_mask = (uint) visual->red_mask; + bool bgr_layout = (red_mask == 0xff); + + const int w = rect.width(); + const int h = rect.height(); + + QImage im; + int image_byte_order = ImageByteOrder(X11->display); + if ((QSysInfo::ByteOrder == QSysInfo::BigEndian && ((image_byte_order == LSBFirst) || bgr_layout)) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + im = image.copy(rect); + const int iw = im.bytesPerLine() / 4; + uint *data = (uint *)im.bits(); + for (int i=0; i < h; i++) { + uint *p = data; + uint *end = p + w; + if (bgr_layout && image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + while (p < end) { + *p = ((*p << 8) & 0xffffff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == LSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) { + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + while (p < end) { + *p = ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff) + | ((*p ) & 0xff00ff00); + p++; + } + } + data += iw; + } + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) im.bits(), w, h, 32, im.bytesPerLine()); + } else { + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) image.scanLine(rect.y())+rect.x()*sizeof(uint), w, h, 32, image.bytesPerLine()); + } + XPutImage(dpy, hd, gc, xi, 0, 0, pos.x(), pos.y(), w, h); + xi->data = 0; // QImage owns these bits + XDestroyImage(xi); +} + +void QX11PaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + Q_D(QX11PaintEngine); + + if (image.format() == QImage::Format_RGB32 + && d->pdev_depth >= 24 && image.depth() == 32 + && r.size() == sr.size()) + { + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + + qt_x11_drawImage(QRect(sx, sy, w, h), QPoint(x, y), image, d->hd, d->gc, d->dpy, + (Visual *)d->xinfo->visual(), d->pdev_depth); + } else { + QPaintEngine::drawImage(r, image, sr, flags); + } +} + +void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRectF &_sr) +{ + Q_D(QX11PaintEngine); + QRectF sr = _sr; + int x = qRound(r.x()); + int y = qRound(r.y()); + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int sw = qRound(sr.width()); + int sh = qRound(sr.height()); + + QPixmap pixmap = qt_toX11Pixmap(px); + if(pixmap.isNull()) + return; + + if ((d->xinfo && d->xinfo->screen() != pixmap.x11Info().screen()) + || (pixmap.x11Info().screen() != DefaultScreen(X11->display))) { + QPixmap* p = const_cast<QPixmap *>(&pixmap); + p->x11SetScreen(d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display)); + } + + QPixmap::x11SetDefaultScreen(pixmap.x11Info().screen()); + +#ifndef QT_NO_XRENDER + ::Picture src_pict = static_cast<QX11PixmapData*>(pixmap.data.data())->picture; + if (src_pict && d->picture) { + const int pDepth = pixmap.depth(); + if (pDepth == 1 && (d->has_alpha_pen)) { + qt_render_bitmap(d->dpy, d->scrn, src_pict, d->picture, + sx, sy, x, y, sw, sh, d->cpen); + return; + } else if (pDepth != 1 && (pDepth == 32 || pDepth != d->pdev_depth)) { + XRenderComposite(d->dpy, d->composition_mode, + src_pict, 0, d->picture, sx, sy, 0, 0, x, y, sw, sh); + return; + } + } +#endif + + bool mono_src = pixmap.depth() == 1; + bool mono_dst = d->pdev_depth == 1; + bool restore_clip = false; + + if (static_cast<QX11PixmapData*>(pixmap.data.data())->x11_mask) { // pixmap has a mask + QBitmap comb(sw, sh); + GC cgc = XCreateGC(d->dpy, comb.handle(), 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, comb.handle(), cgc, 0, 0, sw, sh); + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + if (!d->crgn.isEmpty()) { + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(d->crgn, num); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects, num, Unsorted); + } else if (d->has_clipping) { + XSetClipRectangles(d->dpy, cgc, 0, 0, 0, 0, Unsorted); + } + XSetFillStyle(d->dpy, cgc, FillOpaqueStippled); + XSetTSOrigin(d->dpy, cgc, -sx, -sy); + XSetStipple(d->dpy, cgc, + static_cast<QX11PixmapData*>(pixmap.data.data())->x11_mask); + XFillRectangle(d->dpy, comb.handle(), cgc, 0, 0, sw, sh); + XFreeGC(d->dpy, cgc); + + XSetClipOrigin(d->dpy, d->gc, x, y); + XSetClipMask(d->dpy, d->gc, comb.handle()); + restore_clip = true; + } + + if (mono_src) { + if (!d->crgn.isEmpty()) { + Pixmap comb = XCreatePixmap(d->dpy, d->hd, sw, sh, 1); + GC cgc = XCreateGC(d->dpy, comb, 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, comb, cgc, 0, 0, sw, sh); + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(d->crgn, num); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects, num, Unsorted); + XCopyArea(d->dpy, pixmap.handle(), comb, cgc, sx, sy, sw, sh, 0, 0); + XFreeGC(d->dpy, cgc); + + XSetClipMask(d->dpy, d->gc, comb); + XSetClipOrigin(d->dpy, d->gc, x, y); + XFreePixmap(d->dpy, comb); + } else { + XSetClipMask(d->dpy, d->gc, pixmap.handle()); + XSetClipOrigin(d->dpy, d->gc, x - sx, y - sy); + } + + if (mono_dst) { + XSetForeground(d->dpy, d->gc, qGray(d->cpen.color().rgb()) > 127 ? 0 : 1); + } else { + QColormap cmap = QColormap::instance(d->scrn); + XSetForeground(d->dpy, d->gc, cmap.pixel(d->cpen.color())); + } + XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh); + restore_clip = true; + } else if (mono_dst && !mono_src) { + QBitmap bitmap(pixmap); + XCopyArea(d->dpy, bitmap.handle(), d->hd, d->gc, sx, sy, sw, sh, x, y); + } else { + XCopyArea(d->dpy, pixmap.handle(), d->hd, d->gc, sx, sy, sw, sh, x, y); + } + + if (d->pdev->devType() == QInternal::Pixmap) { + const QPixmap *px = static_cast<const QPixmap*>(d->pdev); + Pixmap src_mask = static_cast<QX11PixmapData*>(pixmap.data.data())->x11_mask; + Pixmap dst_mask = static_cast<QX11PixmapData*>(px->data.data())->x11_mask; + if (dst_mask) { + GC cgc = XCreateGC(d->dpy, dst_mask, 0, 0); + if (src_mask) { // copy src mask into dst mask + XCopyArea(d->dpy, src_mask, dst_mask, cgc, sx, sy, sw, sh, x, y); + } else { // no src mask, but make sure the area copied is opaque in dest + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + XFillRectangle(d->dpy, dst_mask, cgc, x, y, sw, sh); + } + XFreeGC(d->dpy, cgc); + } + } + + if (restore_clip) { + XSetClipOrigin(d->dpy, d->gc, 0, 0); + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(d->crgn, num); + if (num == 0) + XSetClipMask(d->dpy, d->gc, XNone); + else + XSetClipRectangles(d->dpy, d->gc, 0, 0, rects, num, Unsorted); + } +} + +void QX11PaintEngine::updateMatrix(const QTransform &mtx) +{ + Q_D(QX11PaintEngine); + d->txop = mtx.type(); + d->matrix = mtx; + + d->has_complex_xform = (d->txop > QTransform::TxTranslate); + + extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + bool scaling = qt_scaleForTransform(d->matrix, &d->xform_scale); + d->has_scaling_xform = scaling && d->xform_scale != 1.0; + d->has_non_scaling_xform = scaling && d->xform_scale == 1.0; +} + +/* + NB! the clip region is expected to be in dev coordinates +*/ +void QX11PaintEngine::updateClipRegion_dev(const QRegion &clipRegion, Qt::ClipOperation op) +{ + Q_D(QX11PaintEngine); + QRegion sysClip = systemClip(); + if (op == Qt::NoClip) { + d->has_clipping = false; + d->crgn = sysClip; + if (!sysClip.isEmpty()) { + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, sysClip); + } else { + x11ClearClipRegion(d->dpy, d->gc, d->gc_brush, d->picture); + } + return; + } + + switch (op) { + case Qt::IntersectClip: + if (d->has_clipping) { + d->crgn &= clipRegion; + break; + } + // fall through + case Qt::ReplaceClip: + if (!sysClip.isEmpty()) + d->crgn = clipRegion.intersected(sysClip); + else + d->crgn = clipRegion; + break; + case Qt::UniteClip: + d->crgn |= clipRegion; + if (!sysClip.isEmpty()) + d->crgn = d->crgn.intersected(sysClip); + break; + default: + break; + } + d->has_clipping = true; + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, d->crgn); +} + +void QX11PaintEngine::updateFont(const QFont &) +{ +} + +Qt::HANDLE QX11PaintEngine::handle() const +{ + Q_D(const QX11PaintEngine); + Q_ASSERT(isActive()); + Q_ASSERT(d->hd); + return d->hd; +} + +extern void qt_draw_tile(QPaintEngine *, qreal, qreal, qreal, qreal, const QPixmap &, + qreal, qreal); + +void QX11PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) +{ + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + int sx = qRound(p.x()); + int sy = qRound(p.y()); + + bool mono_src = pixmap.depth() == 1; + Q_D(QX11PaintEngine); + + if ((d->xinfo && d->xinfo->screen() != pixmap.x11Info().screen()) + || (pixmap.x11Info().screen() != DefaultScreen(X11->display))) { + QPixmap* p = const_cast<QPixmap *>(&pixmap); + p->x11SetScreen(d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display)); + } + + QPixmap::x11SetDefaultScreen(pixmap.x11Info().screen()); + +#ifndef QT_NO_XRENDER + if (X11->use_xrender && d->picture && pixmap.x11PictureHandle()) { +#if 0 + // ### Qt 5: enable this + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, pixmap.x11PictureHandle(), CPRepeat, &attrs); + + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, pixmap.x11PictureHandle(), d->picture, + sx, sy, x, y, w, h, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + pixmap.x11PictureHandle(), XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#else + const int numTiles = (w / pixmap.width()) * (h / pixmap.height()); + if (numTiles < 100) { + // this is essentially qt_draw_tile(), inlined for + // the XRenderComposite call + int yPos, xPos, drawH, drawW, yOff, xOff; + yPos = y; + yOff = sy; + while(yPos < y + h) { + drawH = pixmap.height() - yOff; // Cropping first row + if (yPos + drawH > y + h) // Cropping last row + drawH = y + h - yPos; + xPos = x; + xOff = sx; + while(xPos < x + w) { + drawW = pixmap.width() - xOff; // Cropping first column + if (xPos + drawW > x + w) // Cropping last column + drawW = x + w - xPos; + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, pixmap.x11PictureHandle(), d->picture, + xOff, yOff, xPos, yPos, drawW, drawH, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + pixmap.x11PictureHandle(), XNone, d->picture, + xOff, yOff, 0, 0, xPos, yPos, drawW, drawH); + } + xPos += drawW; + xOff = 0; + } + yPos += drawH; + yOff = 0; + } + } else { + w = qMin(w, d->pdev->width() - x); + h = qMin(h, d->pdev->height() - y); + if (w <= 0 || h <= 0) + return; + + const int pw = w + sx; + const int ph = h + sy; + QPixmap pm(pw, ph); + if (pixmap.hasAlpha() || mono_src) + pm.fill(Qt::transparent); + + const int mode = pixmap.hasAlpha() ? PictOpOver : PictOpSrc; + const ::Picture pmPicture = pm.x11PictureHandle(); + + // first tile + XRenderComposite(d->dpy, mode, + pixmap.x11PictureHandle(), XNone, pmPicture, + 0, 0, 0, 0, 0, 0, qMin(pw, pixmap.width()), qMin(ph, pixmap.height())); + + // first row of tiles + int xPos = pixmap.width(); + const int sh = qMin(ph, pixmap.height()); + while (xPos < pw) { + const int sw = qMin(xPos, pw - xPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, xPos, 0, sw, sh); + xPos *= 2; + } + + // remaining rows + int yPos = pixmap.height(); + const int sw = pw; + while (yPos < ph) { + const int sh = qMin(yPos, ph - yPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, 0, yPos, sw, sh); + yPos *= 2; + } + + // composite + if (mono_src) + qt_render_bitmap(d->dpy, d->scrn, pmPicture, d->picture, + sx, sy, x, y, w, h, d->cpen); + else + XRenderComposite(d->dpy, d->composition_mode, + pmPicture, XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#endif + } else +#endif // !QT_NO_XRENDER + if (pixmap.depth() > 1 && !static_cast<QX11PixmapData*>(pixmap.data.data())->x11_mask) { + XSetTile(d->dpy, d->gc, pixmap.handle()); + XSetFillStyle(d->dpy, d->gc, FillTiled); + XSetTSOrigin(d->dpy, d->gc, x-sx, y-sy); + XFillRectangle(d->dpy, d->hd, d->gc, x, y, w, h); + XSetTSOrigin(d->dpy, d->gc, 0, 0); + XSetFillStyle(d->dpy, d->gc, FillSolid); + } else { + qt_draw_tile(this, x, y, w, h, pixmap, sx, sy); + } +} + +void QX11PaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + + switch(ti.fontEngine->type()) { + case QFontEngine::TestFontEngine: + case QFontEngine::Box: + d_func()->drawBoxTextItem(p, ti); + break; + case QFontEngine::XLFD: + drawXLFD(p, ti); + break; +#ifndef QT_NO_FONTCONFIG + case QFontEngine::Freetype: + drawFreetype(p, ti); + break; +#endif + default: + Q_ASSERT(false); + } +} + +void QX11PaintEngine::drawXLFD(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QX11PaintEngine); + + if (d->txop > QTransform::TxTranslate) { + // XServer or font don't support server side transformations, need to do it by hand + QPaintEngine::drawTextItem(p, ti); + return; + } + + if (!ti.glyphs.numGlyphs) + return; + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = d->matrix; + matrix.translate(p.x(), p.y()); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + QFontEngineXLFD *xlfd = static_cast<QFontEngineXLFD *>(ti.fontEngine); + Qt::HANDLE font_id = xlfd->fontStruct()->fid; + + XSetFont(d->dpy, d->gc, font_id); + + const QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + for (int i = 0; i < glyphs.size(); i++) { + int xp = qRound(positions[i].x + offs); + int yp = qRound(positions[i].y + offs); + if (xp < SHRT_MAX && xp > SHRT_MIN && yp > SHRT_MIN && yp < SHRT_MAX) { + XChar2b ch; + ch.byte1 = glyphs[i] >> 8; + ch.byte2 = glyphs[i] & 0xff; + XDrawString16(d->dpy, d->hd, d->gc, xp, yp, &ch, 1); + } + } +} + +#ifndef QT_NO_FONTCONFIG +static QPainterPath path_for_glyphs(const QVarLengthArray<glyph_t> &glyphs, + const QVarLengthArray<QFixedPoint> &positions, + const QFontEngineFT *ft) +{ + QPainterPath path; + path.setFillRule(Qt::WindingFill); + ft->lockFace(); + int i = 0; + while (i < glyphs.size()) { + QFontEngineFT::Glyph *glyph = ft->loadGlyph(glyphs[i], QFontEngineFT::Format_Mono); + // #### fix case where we don't get a glyph + if (!glyph) + break; + + Q_ASSERT(glyph->format == QFontEngineFT::Format_Mono); + int n = 0; + int h = glyph->height; + int xp = qRound(positions[i].x); + int yp = qRound(positions[i].y); + + xp += glyph->x; + yp += -glyph->y + glyph->height; + int pitch = ((glyph->width + 31) & ~31) >> 3; + + uchar *src = glyph->data; + while (h--) { + for (int x = 0; x < glyph->width; ++x) { + bool set = src[x >> 3] & (0x80 >> (x & 7)); + if (set) { + QRect r(xp + x, yp - h, 1, 1); + while (x < glyph->width-1 && src[(x+1) >> 3] & (0x80 >> ((x+1) & 7))) { + ++x; + r.setRight(r.right()+1); + } + + path.addRect(r); + ++n; + } + } + src += pitch; + } + ++i; + } + ft->unlockFace(); + return path; +} + +void QX11PaintEngine::drawFreetype(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QX11PaintEngine); + if (!ti.glyphs.numGlyphs) + return; + + QFontEngineX11FT *ft = static_cast<QFontEngineX11FT *>(ti.fontEngine); + + if (!d->cpen.isSolid()) { + QPaintEngine::drawTextItem(p, ti); + return; + } + + const bool xrenderPath = (X11->use_xrender + && !(d->pdev->devType() == QInternal::Pixmap + && static_cast<const QPixmap *>(d->pdev)->data->pixelType() == QPixmapData::BitmapType)); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix; + + if (xrenderPath) + matrix = d->matrix; + matrix.translate(p.x(), p.y()); + ft->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.count() == 0) + return; + +#ifndef QT_NO_XRENDER + QFontEngineFT::QGlyphSet *set = ft->defaultGlyphs(); + if (d->txop >= QTransform::TxScale && xrenderPath) + set = ft->loadTransformedGlyphSet(d->matrix); + + if (!set || set->outline_drawing + || !ft->loadGlyphs(set, glyphs.constData(), glyphs.size(), positions.constData(), QFontEngineFT::Format_Render)) + { + QPaintEngine::drawTextItem(p, ti); + return; + } + + if (xrenderPath) { + GlyphSet glyphSet = set->id; + const QColor &pen = d->cpen.color(); + ::Picture src = X11->getSolidFill(d->scrn, pen); + XRenderPictFormat *maskFormat = 0; + if (ft->xglyph_format != PictStandardA1) + maskFormat = XRenderFindStandardFormat(X11->display, ft->xglyph_format); + + enum { t_min = SHRT_MIN, t_max = SHRT_MAX }; + + int i = 0; + for (; i < glyphs.size() + && (positions[i].x < t_min || positions[i].x > t_max + || positions[i].y < t_min || positions[i].y > t_max); + ++i) + ; + + if (i >= glyphs.size()) + return; + ++i; + + QFixed xp = positions[i - 1].x; + QFixed yp = positions[i - 1].y; + QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + + XGlyphElt32 elt; + elt.glyphset = glyphSet; + elt.chars = &glyphs[i - 1]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + for (; i < glyphs.size(); ++i) { + if (positions[i].x < t_min || positions[i].x > t_max + || positions[i].y < t_min || positions[i].y > t_max) { + break; + } + QFontEngineFT::Glyph *g = ft->cachedGlyph(glyphs[i - 1]); + if (g + && positions[i].x == xp + g->advance + && positions[i].y == yp + && elt.nchars < 253 // don't draw more than 253 characters as some X servers + // hang with it + ) { + elt.nchars++; + xp += g->advance; + } else { + xp = positions[i].x; + yp = positions[i].y; + + XRenderCompositeText32(X11->display, PictOpOver, src, d->picture, + maskFormat, 0, 0, 0, 0, + &elt, 1); + elt.chars = &glyphs[i]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + } + } + XRenderCompositeText32(X11->display, PictOpOver, src, d->picture, + maskFormat, 0, 0, 0, 0, + &elt, 1); + + return; + + } +#endif + + QPainterPath path = path_for_glyphs(glyphs, positions, ft); + if (path.elementCount() <= 1) + return; + Q_ASSERT((path.elementCount() % 5) == 0); + if (d->txop >= QTransform::TxScale) { + painter()->save(); + painter()->setBrush(d->cpen.brush()); + painter()->setPen(Qt::NoPen); + painter()->drawPath(path); + painter()->restore(); + return; + } + + const int rectcount = 256; + XRectangle rects[rectcount]; + int num_rects = 0; + + QPoint delta(qRound(d->matrix.dx()), qRound(d->matrix.dy())); + QRect clip(d->polygonClipper.boundingRect()); + for (int i=0; i < path.elementCount(); i+=5) { + int x = qRound(path.elementAt(i).x); + int y = qRound(path.elementAt(i).y); + int w = qRound(path.elementAt(i+1).x) - x; + int h = qRound(path.elementAt(i+2).y) - y; + + QRect rect = QRect(x + delta.x(), y + delta.y(), w, h); + rect = rect.intersected(clip); + if (rect.isEmpty()) + continue; + + rects[num_rects].x = short(rect.x()); + rects[num_rects].y = short(rect.y()); + rects[num_rects].width = ushort(rect.width()); + rects[num_rects].height = ushort(rect.height()); + ++num_rects; + if (num_rects == rectcount) { + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); + num_rects = 0; + } + } + if (num_rects > 0) + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); + +} +#endif // !QT_NO_XRENDER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengine_x11_p.h b/src/gui/painting/qpaintengine_x11_p.h new file mode 100644 index 0000000000..897c69f122 --- /dev/null +++ b/src/gui/painting/qpaintengine_x11_p.h @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_X11_P_H +#define QPAINTENGINE_X11_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 "QtGui/qpaintengine.h" +#include "QtGui/qregion.h" +#include "QtGui/qpen.h" +#include "QtCore/qpoint.h" +#include "private/qpaintengine_p.h" +#include "private/qpainter_p.h" +#include "private/qpolygonclipper_p.h" + +typedef unsigned long Picture; + +QT_BEGIN_NAMESPACE + +class QX11PaintEnginePrivate; +class QFontEngineFT; +class QXRenderTessellator; + +struct qt_float_point +{ + qreal x, y; +}; + +class QX11PaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QX11PaintEngine) +public: + QX11PaintEngine(); + ~QX11PaintEngine(); + + bool begin(QPaintDevice *pdev); + bool end(); + + void updateState(const QPaintEngineState &state); + + void updatePen(const QPen &pen); + void updateBrush(const QBrush &brush, const QPointF &pt); + void updateRenderHints(QPainter::RenderHints hints); + void updateFont(const QFont &font); + void updateMatrix(const QTransform &matrix); + void updateClipRegion_dev(const QRegion ®ion, Qt::ClipOperation op); + + void drawLines(const QLine *lines, int lineCount); + void drawLines(const QLineF *lines, int lineCount); + + void drawRects(const QRect *rects, int rectCount); + void drawRects(const QRectF *rects, int rectCount); + + void drawPoints(const QPoint *points, int pointCount); + void drawPoints(const QPointF *points, int pointCount); + + void drawEllipse(const QRect &r); + void drawEllipse(const QRectF &r); + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) + { QPaintEngine::drawPolygon(points, pointCount, mode); } + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + void drawPath(const QPainterPath &path); + void drawTextItem(const QPointF &p, const QTextItem &textItem); + void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + + virtual Qt::HANDLE handle() const; + inline Type type() const { return QPaintEngine::X11; } + + QPainter::RenderHints supportedRenderHints() const; + +protected: + QX11PaintEngine(QX11PaintEnginePrivate &dptr); + + void drawXLFD(const QPointF &p, const QTextItemInt &si); +#ifndef QT_NO_FONTCONFIG + void drawFreetype(const QPointF &p, const QTextItemInt &si); +#endif + + friend class QPixmap; + friend class QFontEngineBox; + friend Q_GUI_EXPORT GC qt_x11_get_pen_gc(QPainter *); + friend Q_GUI_EXPORT GC qt_x11_get_brush_gc(QPainter *); + +private: + Q_DISABLE_COPY(QX11PaintEngine) +}; + +class QX11PaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QX11PaintEngine) +public: + QX11PaintEnginePrivate() + { + scrn = -1; + hd = 0; + picture = 0; + gc = gc_brush = 0; + dpy = 0; + xinfo = 0; + txop = QTransform::TxNone; + has_clipping = false; + render_hints = 0; + xform_scale = 1; +#ifndef QT_NO_XRENDER + tessellator = 0; +#endif + } + enum GCMode { + PenGC, + BrushGC + }; + + void init(); + void fillPolygon_translated(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPolygon_dev(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPath(const QPainterPath &path, GCMode gcmode, bool transform); + void strokePolygon_dev(const QPointF *points, int pointCount, bool close); + void strokePolygon_translated(const QPointF *points, int pointCount, bool close); + void setupAdaptedOrigin(const QPoint &p); + void resetAdaptedOrigin(); + void decidePathFallback() { + use_path_fallback = has_alpha_brush + || has_alpha_pen + || has_custom_pen + || has_complex_xform + || (render_hints & QPainter::Antialiasing); + } + void decideCoordAdjust() { + adjust_coords = !(render_hints & QPainter::Antialiasing) + && (has_alpha_pen + || (has_alpha_brush && has_pen && !has_alpha_pen) + || (cpen.style() > Qt::SolidLine)); + } + void clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly); + void systemStateChanged(); + + Display *dpy; + int scrn; + int pdev_depth; + Qt::HANDLE hd; + QPixmap brush_pm; +#if !defined (QT_NO_XRENDER) + Qt::HANDLE picture; + Qt::HANDLE current_brush; + QPixmap bitmap_texture; + int composition_mode; +#else + Qt::HANDLE picture; +#endif + GC gc; + GC gc_brush; + + QPen cpen; + QBrush cbrush; + QRegion crgn; + QTransform matrix; + qreal opacity; + + uint has_complex_xform : 1; + uint has_scaling_xform : 1; + uint has_non_scaling_xform : 1; + uint has_custom_pen : 1; + uint use_path_fallback : 1; + uint adjust_coords : 1; + uint has_clipping : 1; + uint adapted_brush_origin : 1; + uint adapted_pen_origin : 1; + uint has_pen : 1; + uint has_brush : 1; + uint has_texture : 1; + uint has_alpha_texture : 1; + uint has_pattern : 1; + uint has_alpha_pen : 1; + uint has_alpha_brush : 1; + uint render_hints; + + const QX11Info *xinfo; + QPointF bg_origin; + QTransform::TransformationType txop; + qreal xform_scale; + QPolygonClipper<qt_float_point, qt_float_point, float> polygonClipper; + + int xlibMaxLinePoints; +#ifndef QT_NO_XRENDER + QXRenderTessellator *tessellator; +#endif +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_X11_P_H diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp new file mode 100644 index 0000000000..509fb77d25 --- /dev/null +++ b/src/gui/painting/qpaintengineex.cpp @@ -0,0 +1,1015 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpaintengineex_p.h" +#include "qpainter_p.h" +#include "qstroker_p.h" +#include "qbezier_p.h" +#include <private/qpainterpath_p.h> + +#include <qvarlengtharray.h> +#include <qdebug.h> + + +QT_BEGIN_NAMESPACE + +/******************************************************************************* + * + * class QVectorPath + * + */ +QVectorPath::~QVectorPath() +{ + if (m_hints & ShouldUseCacheHint) { + CacheEntry *e = m_cache; + while (e) { + if (e->data) + e->cleanup(e->engine, e->data); + CacheEntry *n = e->next; + delete e; + e = n; + } + } +} + + +QRectF QVectorPath::controlPointRect() const +{ + if (m_hints & ControlPointRect) + return QRectF(QPointF(m_cp_rect.x1, m_cp_rect.y1), QPointF(m_cp_rect.x2, m_cp_rect.y2)); + + if (m_count == 0) { + m_cp_rect.x1 = m_cp_rect.x2 = m_cp_rect.y1 = m_cp_rect.y2 = 0; + m_hints |= ControlPointRect; + return QRectF(QPointF(m_cp_rect.x1, m_cp_rect.y1), QPointF(m_cp_rect.x2, m_cp_rect.y2)); + } + Q_ASSERT(m_points && m_count > 0); + + const qreal *pts = m_points; + m_cp_rect.x1 = m_cp_rect.x2 = *pts; + ++pts; + m_cp_rect.y1 = m_cp_rect.y2 = *pts; + ++pts; + + const qreal *epts = m_points + (m_count << 1); + while (pts < epts) { + qreal x = *pts; + if (x < m_cp_rect.x1) m_cp_rect.x1 = x; + else if (x > m_cp_rect.x2) m_cp_rect.x2 = x; + ++pts; + + qreal y = *pts; + if (y < m_cp_rect.y1) m_cp_rect.y1 = y; + else if (y > m_cp_rect.y2) m_cp_rect.y2 = y; + ++pts; + } + + m_hints |= ControlPointRect; + return QRectF(QPointF(m_cp_rect.x1, m_cp_rect.y1), QPointF(m_cp_rect.x2, m_cp_rect.y2)); +} + + +QVectorPath::CacheEntry *QVectorPath::addCacheData(QPaintEngineEx *engine, void *data, + qvectorpath_cache_cleanup cleanup) const{ + Q_ASSERT(!lookupCacheData(engine)); + if ((m_hints & IsCachedHint) == 0) { + m_cache = 0; + m_hints |= IsCachedHint; + } + CacheEntry *e = new CacheEntry; + e->engine = engine; + e->data = data; + e->cleanup = cleanup; + e->next = m_cache; + m_cache = e; + return m_cache; +} + + +const QVectorPath &qtVectorPathForPath(const QPainterPath &path) +{ + Q_ASSERT(path.d_func()); + return path.d_func()->vectorPath(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug Q_GUI_EXPORT &operator<<(QDebug &s, const QVectorPath &path) +{ + QRectF rf = path.controlPointRect(); + s << "QVectorPath(size:" << path.elementCount() + << " hints:" << hex << path.hints() + << rf << ')'; + return s; +} +#endif + +/******************************************************************************* + * + * class QPaintEngineExPrivate: + * + */ + + +struct StrokeHandler { + StrokeHandler(int reserve) : pts(reserve), types(reserve) {} + QDataBuffer<qreal> pts; + QDataBuffer<QPainterPath::ElementType> types; +}; + + +QPaintEngineExPrivate::QPaintEngineExPrivate() + : dasher(&stroker), + strokeHandler(0), + activeStroker(0), + strokerPen(Qt::NoPen) +{ +} + + +QPaintEngineExPrivate::~QPaintEngineExPrivate() +{ + delete strokeHandler; +} + + +void QPaintEngineExPrivate::replayClipOperations() +{ + Q_Q(QPaintEngineEx); + + QPainter *p = q->painter(); + if (!p || !p->d_ptr) + return; + + QList<QPainterClipInfo> clipInfo = p->d_ptr->state->clipInfo; + + QTransform transform = q->state()->matrix; + + for (int i = 0; i < clipInfo.size(); ++i) { + const QPainterClipInfo &info = clipInfo.at(i); + + if (info.matrix != q->state()->matrix) { + q->state()->matrix = info.matrix; + q->transformChanged(); + } + + switch (info.clipType) { + case QPainterClipInfo::RegionClip: + q->clip(info.region, info.operation); + break; + case QPainterClipInfo::PathClip: + q->clip(info.path, info.operation); + break; + case QPainterClipInfo::RectClip: + q->clip(info.rect, info.operation); + break; + case QPainterClipInfo::RectFClip: { + qreal right = info.rectf.x() + info.rectf.width(); + qreal bottom = info.rectf.y() + info.rectf.height(); + qreal pts[] = { info.rectf.x(), info.rectf.y(), + right, info.rectf.y(), + right, bottom, + info.rectf.x(), bottom }; + QVectorPath vp(pts, 4, 0, QVectorPath::RectangleHint); + q->clip(vp, info.operation); + break; + } + } + } + + if (transform != q->state()->matrix) { + q->state()->matrix = transform; + q->transformChanged(); + } +} + + +bool QPaintEngineExPrivate::hasClipOperations() const +{ + Q_Q(const QPaintEngineEx); + + QPainter *p = q->painter(); + if (!p || !p->d_ptr) + return false; + + QList<QPainterClipInfo> clipInfo = p->d_ptr->state->clipInfo; + + return !clipInfo.isEmpty(); +} + +/******************************************************************************* + * + * class QPaintEngineEx: + * + */ + +static QPainterPath::ElementType qpaintengineex_ellipse_types[] = { + QPainterPath::MoveToElement, + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement +}; + +static QPainterPath::ElementType qpaintengineex_line_types_16[] = { + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement, + QPainterPath::MoveToElement, QPainterPath::LineToElement +}; + +static QPainterPath::ElementType qpaintengineex_rect4_types_32[] = { + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 1 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 2 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 3 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 4 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 5 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 6 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 7 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 8 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 9 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 10 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 11 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 12 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 13 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 14 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 15 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 16 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 17 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 18 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 19 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 20 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 21 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 22 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 23 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 24 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 25 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 26 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 27 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 28 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 29 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 30 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 31 + QPainterPath::MoveToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, QPainterPath::LineToElement, // 32 +}; + + +static QPainterPath::ElementType qpaintengineex_roundedrect_types[] = { + QPainterPath::MoveToElement, + QPainterPath::LineToElement, + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + QPainterPath::LineToElement, + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + QPainterPath::LineToElement, + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement, + QPainterPath::LineToElement, + QPainterPath::CurveToElement, + QPainterPath::CurveToDataElement, + QPainterPath::CurveToDataElement +}; + + + +static void qpaintengineex_moveTo(qreal x, qreal y, void *data) { + ((StrokeHandler *) data)->pts.add(x); + ((StrokeHandler *) data)->pts.add(y); + ((StrokeHandler *) data)->types.add(QPainterPath::MoveToElement); +} + +static void qpaintengineex_lineTo(qreal x, qreal y, void *data) { + ((StrokeHandler *) data)->pts.add(x); + ((StrokeHandler *) data)->pts.add(y); + ((StrokeHandler *) data)->types.add(QPainterPath::LineToElement); +} + +static void qpaintengineex_cubicTo(qreal c1x, qreal c1y, qreal c2x, qreal c2y, qreal ex, qreal ey, void *data) { + ((StrokeHandler *) data)->pts.add(c1x); + ((StrokeHandler *) data)->pts.add(c1y); + ((StrokeHandler *) data)->types.add(QPainterPath::CurveToElement); + + ((StrokeHandler *) data)->pts.add(c2x); + ((StrokeHandler *) data)->pts.add(c2y); + ((StrokeHandler *) data)->types.add(QPainterPath::CurveToDataElement); + + ((StrokeHandler *) data)->pts.add(ex); + ((StrokeHandler *) data)->pts.add(ey); + ((StrokeHandler *) data)->types.add(QPainterPath::CurveToDataElement); +} + +QPaintEngineEx::QPaintEngineEx() + : QPaintEngine(*new QPaintEngineExPrivate, AllFeatures) +{ + extended = true; +} + +QPaintEngineEx::QPaintEngineEx(QPaintEngineExPrivate &data) + : QPaintEngine(data, AllFeatures) +{ + extended = true; +} + +QPainterState *QPaintEngineEx::createState(QPainterState *orig) const +{ + if (!orig) + return new QPainterState; + return new QPainterState(orig); +} + +Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp + +void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QPaintEngineEx::stroke()" << pen; +#endif + + Q_D(QPaintEngineEx); + + if (path.isEmpty()) + return; + + if (!d->strokeHandler) { + d->strokeHandler = new StrokeHandler(path.elementCount()+4); + d->stroker.setMoveToHook(qpaintengineex_moveTo); + d->stroker.setLineToHook(qpaintengineex_lineTo); + d->stroker.setCubicToHook(qpaintengineex_cubicTo); + } + + if (!qpen_fast_equals(pen, d->strokerPen)) { + d->strokerPen = pen; + d->stroker.setJoinStyle(pen.joinStyle()); + d->stroker.setCapStyle(pen.capStyle()); + d->stroker.setMiterLimit(pen.miterLimit()); + qreal penWidth = pen.widthF(); + if (penWidth == 0) + d->stroker.setStrokeWidth(1); + else + d->stroker.setStrokeWidth(penWidth); + + Qt::PenStyle style = pen.style(); + if (style == Qt::SolidLine) { + d->activeStroker = &d->stroker; + } else if (style == Qt::NoPen) { + d->activeStroker = 0; + } else { + d->dasher.setDashPattern(pen.dashPattern()); + d->dasher.setDashOffset(pen.dashOffset()); + d->activeStroker = &d->dasher; + } + } + + if (!d->activeStroker) { + return; + } + + if (pen.style() > Qt::SolidLine) { + if (pen.isCosmetic()) { + d->activeStroker->setClipRect(d->exDeviceRect); + } else { + QRectF clipRect = state()->matrix.inverted().mapRect(QRectF(d->exDeviceRect)); + d->activeStroker->setClipRect(clipRect); + } + } + + const QPainterPath::ElementType *types = path.elements(); + const qreal *points = path.points(); + int pointCount = path.elementCount(); + + const qreal *lastPoint = points + (pointCount<<1); + + d->strokeHandler->types.reset(); + d->strokeHandler->pts.reset(); + + // Some engines might decide to optimize for the non-shape hint later on... + uint flags = QVectorPath::WindingFill; + + if (path.elementCount() > 2) + flags |= QVectorPath::NonConvexShapeMask; + + if (d->stroker.capStyle() == Qt::RoundCap || d->stroker.joinStyle() == Qt::RoundJoin) + flags |= QVectorPath::CurvedShapeMask; + + // ### Perspective Xforms are currently not supported... + if (!pen.isCosmetic()) { + // We include cosmetic pens in this case to avoid having to + // change the current transform. Normal transformed, + // non-cosmetic pens will be transformed as part of fill + // later, so they are also covered here.. + d->activeStroker->setCurveThresholdFromTransform(state()->matrix); + d->activeStroker->begin(d->strokeHandler); + if (types) { + while (points < lastPoint) { + switch (*types) { + case QPainterPath::MoveToElement: + d->activeStroker->moveTo(points[0], points[1]); + points += 2; + ++types; + break; + case QPainterPath::LineToElement: + d->activeStroker->lineTo(points[0], points[1]); + points += 2; + ++types; + break; + case QPainterPath::CurveToElement: + d->activeStroker->cubicTo(points[0], points[1], + points[2], points[3], + points[4], points[5]); + points += 6; + types += 3; + flags |= QVectorPath::CurvedShapeMask; + break; + default: + break; + } + } + if (path.hasImplicitClose()) + d->activeStroker->lineTo(path.points()[0], path.points()[1]); + + } else { + d->activeStroker->moveTo(points[0], points[1]); + points += 2; + while (points < lastPoint) { + d->activeStroker->lineTo(points[0], points[1]); + points += 2; + } + if (path.hasImplicitClose()) + d->activeStroker->lineTo(path.points()[0], path.points()[1]); + } + d->activeStroker->end(); + + if (!d->strokeHandler->types.size()) // an empty path... + return; + + QVectorPath strokePath(d->strokeHandler->pts.data(), + d->strokeHandler->types.size(), + d->strokeHandler->types.data(), + flags); + fill(strokePath, pen.brush()); + } else { + // For cosmetic pens we need a bit of trickery... We to process xform the input points + if (state()->matrix.type() >= QTransform::TxProject) { + QPainterPath painterPath = state()->matrix.map(path.convertToPainterPath()); + d->activeStroker->strokePath(painterPath, d->strokeHandler, QTransform()); + } else { + d->activeStroker->setCurveThresholdFromTransform(QTransform()); + d->activeStroker->begin(d->strokeHandler); + if (types) { + while (points < lastPoint) { + switch (*types) { + case QPainterPath::MoveToElement: { + QPointF pt = (*(QPointF *) points) * state()->matrix; + d->activeStroker->moveTo(pt.x(), pt.y()); + points += 2; + ++types; + break; + } + case QPainterPath::LineToElement: { + QPointF pt = (*(QPointF *) points) * state()->matrix; + d->activeStroker->lineTo(pt.x(), pt.y()); + points += 2; + ++types; + break; + } + case QPainterPath::CurveToElement: { + QPointF c1 = ((QPointF *) points)[0] * state()->matrix; + QPointF c2 = ((QPointF *) points)[1] * state()->matrix; + QPointF e = ((QPointF *) points)[2] * state()->matrix; + d->activeStroker->cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y()); + points += 6; + types += 3; + flags |= QVectorPath::CurvedShapeMask; + break; + } + default: + break; + } + } + if (path.hasImplicitClose()) { + QPointF pt = * ((QPointF *) path.points()) * state()->matrix; + d->activeStroker->lineTo(pt.x(), pt.y()); + } + + } else { + QPointF p = ((QPointF *)points)[0] * state()->matrix; + d->activeStroker->moveTo(p.x(), p.y()); + points += 2; + while (points < lastPoint) { + QPointF p = ((QPointF *)points)[0] * state()->matrix; + d->activeStroker->lineTo(p.x(), p.y()); + points += 2; + } + if (path.hasImplicitClose()) + d->activeStroker->lineTo(p.x(), p.y()); + } + d->activeStroker->end(); + } + + QVectorPath strokePath(d->strokeHandler->pts.data(), + d->strokeHandler->types.size(), + d->strokeHandler->types.data(), + flags); + + QTransform xform = state()->matrix; + state()->matrix = QTransform(); + transformChanged(); + + QBrush brush = pen.brush(); + if (qbrush_style(brush) != Qt::SolidPattern) + brush.setTransform(brush.transform() * xform); + + fill(strokePath, brush); + + state()->matrix = xform; + transformChanged(); + } +} + +void QPaintEngineEx::draw(const QVectorPath &path) +{ + const QBrush &brush = state()->brush; + if (qbrush_style(brush) != Qt::NoBrush) + fill(path, brush); + + const QPen &pen = state()->pen; + if (qpen_style(pen) != Qt::NoPen && qbrush_style(qpen_brush(pen)) != Qt::NoBrush) + stroke(path, pen); +} + + +void QPaintEngineEx::clip(const QRect &r, Qt::ClipOperation op) +{ + qreal right = r.x() + r.width(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { qreal(r.x()), qreal(r.y()), + right, qreal(r.y()), + right, bottom, + qreal(r.x()), bottom, + qreal(r.x()), qreal(r.y()) }; + QVectorPath vp(pts, 5, 0, QVectorPath::RectangleHint); + clip(vp, op); +} + +void QPaintEngineEx::clip(const QRegion ®ion, Qt::ClipOperation op) +{ + if (region.rectCount() == 1) + clip(region.boundingRect(), op); + + QVector<QRect> rects = region.rects(); + if (rects.size() <= 32) { + qreal pts[2*32*4]; + int pos = 0; + for (QVector<QRect>::const_iterator i = rects.constBegin(); i != rects.constEnd(); ++i) { + qreal x1 = i->x(); + qreal y1 = i->y(); + qreal x2 = i->x() + i->width(); + qreal y2 = i->y() + i->height(); + + pts[pos++] = x1; + pts[pos++] = y1; + + pts[pos++] = x2; + pts[pos++] = y1; + + pts[pos++] = x2; + pts[pos++] = y2; + + pts[pos++] = x1; + pts[pos++] = y2; + } + QVectorPath vp(pts, rects.size() * 4, qpaintengineex_rect4_types_32); + clip(vp, op); + } else { + QVarLengthArray<qreal> pts(rects.size() * 2 * 4); + QVarLengthArray<QPainterPath::ElementType> types(rects.size() * 4); + int ppos = 0; + int tpos = 0; + + for (QVector<QRect>::const_iterator i = rects.constBegin(); i != rects.constEnd(); ++i) { + qreal x1 = i->x(); + qreal y1 = i->y(); + qreal x2 = i->x() + i->width(); + qreal y2 = i->y() + i->height(); + + pts[ppos++] = x1; + pts[ppos++] = y1; + + pts[ppos++] = x2; + pts[ppos++] = y1; + + pts[ppos++] = x2; + pts[ppos++] = y2; + + pts[ppos++] = x1; + pts[ppos++] = y2; + + types[tpos++] = QPainterPath::MoveToElement; + types[tpos++] = QPainterPath::LineToElement; + types[tpos++] = QPainterPath::LineToElement; + types[tpos++] = QPainterPath::LineToElement; + } + + QVectorPath vp(pts.data(), rects.size() * 4, types.data()); + clip(vp, op); + } + +} + +void QPaintEngineEx::clip(const QPainterPath &path, Qt::ClipOperation op) +{ + if (path.isEmpty()) { + QVectorPath vp(0, 0); + clip(vp, op); + } else { + clip(qtVectorPathForPath(path), op); + } +} + +void QPaintEngineEx::fillRect(const QRectF &r, const QBrush &brush) +{ + qreal pts[] = { r.x(), r.y(), r.x() + r.width(), r.y(), + r.x() + r.width(), r.y() + r.height(), r.x(), r.y() + r.height() }; + QVectorPath vp(pts, 4, 0, QVectorPath::RectangleHint); + fill(vp, brush); +} + +void QPaintEngineEx::fillRect(const QRectF &r, const QColor &color) +{ + fillRect(r, QBrush(color)); +} + +void QPaintEngineEx::drawRects(const QRect *rects, int rectCount) +{ + for (int i=0; i<rectCount; ++i) { + const QRect &r = rects[i]; + // ### Is there a one off here? + qreal right = r.x() + r.width(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { qreal(r.x()), qreal(r.y()), + right, qreal(r.y()), + right, bottom, + qreal(r.x()), bottom, + qreal(r.x()), qreal(r.y()) }; + QVectorPath vp(pts, 5, 0, QVectorPath::RectangleHint); + draw(vp); + } +} + +void QPaintEngineEx::drawRects(const QRectF *rects, int rectCount) +{ + for (int i=0; i<rectCount; ++i) { + const QRectF &r = rects[i]; + qreal right = r.x() + r.width(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { r.x(), r.y(), + right, r.y(), + right, bottom, + r.x(), bottom, + r.x(), r.y() }; + QVectorPath vp(pts, 5, 0, QVectorPath::RectangleHint); + draw(vp); + } +} + + +void QPaintEngineEx::drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode) +{ + qreal x1 = rect.left(); + qreal x2 = rect.right(); + qreal y1 = rect.top(); + qreal y2 = rect.bottom(); + + if (mode == Qt::RelativeSize) { + xRadius = xRadius * rect.width() / 200.; + yRadius = yRadius * rect.height() / 200.; + } + + xRadius = qMin(xRadius, rect.width() / 2); + yRadius = qMin(yRadius, rect.height() / 2); + + qreal pts[] = { + x1 + xRadius, y1, // MoveTo + x2 - xRadius, y1, // LineTo + x2 - (1 - KAPPA) * xRadius, y1, // CurveTo + x2, y1 + (1 - KAPPA) * yRadius, + x2, y1 + yRadius, + x2, y2 - yRadius, // LineTo + x2, y2 - (1 - KAPPA) * yRadius, // CurveTo + x2 - (1 - KAPPA) * xRadius, y2, + x2 - xRadius, y2, + x1 + xRadius, y2, // LineTo + x1 + (1 - KAPPA) * xRadius, y2, // CurveTo + x1, y2 - (1 - KAPPA) * yRadius, + x1, y2 - yRadius, + x1, y1 + yRadius, // LineTo + x1, y1 + (1 - KAPPA) * yRadius, // CurveTo + x1 + (1 - KAPPA) * xRadius, y1, + x1 + xRadius, y1 + }; + + QVectorPath path(pts, 17, qpaintengineex_roundedrect_types, QVectorPath::RoundedRectHint); + draw(path); +} + + + +void QPaintEngineEx::drawLines(const QLine *lines, int lineCount) +{ + int elementCount = lineCount << 1; + while (elementCount > 0) { + int count = qMin(elementCount, 32); + + qreal pts[64]; + int count2 = count<<1; +#ifdef Q_WS_MAC + for (int i=0; i<count2; i+=2) { + pts[i] = ((int *) lines)[i+1]; + pts[i+1] = ((int *) lines)[i]; + } +#else + for (int i=0; i<count2; ++i) + pts[i] = ((int *) lines)[i]; +#endif + + QVectorPath path(pts, count, qpaintengineex_line_types_16, QVectorPath::LinesHint); + stroke(path, state()->pen); + + elementCount -= 32; + lines += 16; + } +} + +void QPaintEngineEx::drawLines(const QLineF *lines, int lineCount) +{ + int elementCount = lineCount << 1; + while (elementCount > 0) { + int count = qMin(elementCount, 32); + + QVectorPath path((qreal *) lines, count, qpaintengineex_line_types_16, + QVectorPath::LinesHint); + stroke(path, state()->pen); + + elementCount -= 32; + lines += 16; + } +} + +void QPaintEngineEx::drawEllipse(const QRectF &r) +{ + qreal pts[26]; // QPointF[13] without constructors... + union { + qreal *ptr; + QPointF *points; + } x; + x.ptr = pts; + + int point_count = 0; + x.points[0] = qt_curves_for_arc(r, 0, -360, x.points + 1, &point_count); + QVectorPath vp((qreal *) pts, point_count, qpaintengineex_ellipse_types, QVectorPath::EllipseHint); + draw(vp); +} + +void QPaintEngineEx::drawEllipse(const QRect &r) +{ + drawEllipse(QRectF(r)); +} + +void QPaintEngineEx::drawPath(const QPainterPath &path) +{ + if (!path.isEmpty()) + draw(qtVectorPathForPath(path)); +} + + +void QPaintEngineEx::drawPoints(const QPointF *points, int pointCount) +{ + QPen pen = state()->pen; + if (pen.capStyle() == Qt::FlatCap) + pen.setCapStyle(Qt::SquareCap); + + if (pen.brush().isOpaque()) { + while (pointCount > 0) { + int count = qMin(pointCount, 16); + qreal pts[64]; + int oset = -1; + for (int i=0; i<count; ++i) { + pts[++oset] = points[i].x(); + pts[++oset] = points[i].y(); + pts[++oset] = points[i].x() + 1/63.; + pts[++oset] = points[i].y(); + } + QVectorPath path(pts, count * 2, qpaintengineex_line_types_16, QVectorPath::LinesHint); + stroke(path, pen); + pointCount -= 16; + points += 16; + } + } else { + for (int i=0; i<pointCount; ++i) { + qreal pts[] = { points[i].x(), points[i].y(), points[i].x() + 1/63., points[i].y() }; + QVectorPath path(pts, 2, 0); + stroke(path, pen); + } + } +} + +void QPaintEngineEx::drawPoints(const QPoint *points, int pointCount) +{ + QPen pen = state()->pen; + if (pen.capStyle() == Qt::FlatCap) + pen.setCapStyle(Qt::SquareCap); + + if (pen.brush().isOpaque()) { + while (pointCount > 0) { + int count = qMin(pointCount, 16); + qreal pts[64]; + int oset = -1; + for (int i=0; i<count; ++i) { + pts[++oset] = points[i].x(); + pts[++oset] = points[i].y(); + pts[++oset] = points[i].x() + 1/63.; + pts[++oset] = points[i].y(); + } + QVectorPath path(pts, count * 2, qpaintengineex_line_types_16, QVectorPath::LinesHint); + stroke(path, pen); + pointCount -= 16; + points += 16; + } + } else { + for (int i=0; i<pointCount; ++i) { + qreal pts[] = { qreal(points[i].x()), qreal(points[i].y()), + qreal(points[i].x() +1/63.), qreal(points[i].y()) }; + QVectorPath path(pts, 2, 0); + stroke(path, pen); + } + } +} + + +void QPaintEngineEx::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + QVectorPath path((qreal *) points, pointCount, 0, QVectorPath::polygonFlags(mode)); + + if (mode == PolylineMode) + stroke(path, state()->pen); + else + draw(path); +} + +void QPaintEngineEx::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + int count = pointCount<<1; + QVarLengthArray<qreal> pts(count); + +#ifdef Q_WS_MAC + for (int i=0; i<count; i+=2) { + pts[i] = ((int *) points)[i+1]; + pts[i+1] = ((int *) points)[i]; + } +#else + for (int i=0; i<count; ++i) + pts[i] = ((int *) points)[i]; +#endif + + QVectorPath path(pts.data(), pointCount, 0, QVectorPath::polygonFlags(mode)); + + if (mode == PolylineMode) + stroke(path, state()->pen); + else + draw(path); + +} + +void QPaintEngineEx::drawPixmap(const QPointF &pos, const QPixmap &pm) +{ + drawPixmap(QRectF(pos, pm.size()), pm, pm.rect()); +} + +void QPaintEngineEx::drawImage(const QPointF &pos, const QImage &image) +{ + drawImage(QRectF(pos, image.size()), image, image.rect()); +} + +void QPaintEngineEx::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) +{ + QBrush brush(state()->pen.color(), pixmap); + QTransform xform = QTransform::fromTranslate(r.x() - s.x(), r.y() - s.y()); + brush.setTransform(xform); + + qreal pts[] = { r.x(), r.y(), + r.x() + r.width(), r.y(), + r.x() + r.width(), r.y() + r.height(), + r.x(), r.y() + r.height() }; + + QVectorPath path(pts, 4, 0, QVectorPath::RectangleHint); + fill(path, brush); +} + +void QPaintEngineEx::drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, + const QPixmap &pixmap, QPainter::PixmapFragmentHints /*hints*/) +{ + if (pixmap.isNull()) + return; + + qreal oldOpacity = state()->opacity; + QTransform oldTransform = state()->matrix; + + for (int i = 0; i < fragmentCount; ++i) { + QTransform transform = oldTransform; + transform.translate(fragments[i].x, fragments[i].y); + transform.rotate(fragments[i].rotation); + state()->opacity = oldOpacity * fragments[i].opacity; + state()->matrix = transform; + opacityChanged(); + transformChanged(); + + qreal w = fragments[i].scaleX * fragments[i].width; + qreal h = fragments[i].scaleY * fragments[i].height; + QRectF sourceRect(fragments[i].sourceLeft, fragments[i].sourceTop, + fragments[i].width, fragments[i].height); + drawPixmap(QRectF(-0.5 * w, -0.5 * h, w, h), pixmap, sourceRect); + } + + state()->opacity = oldOpacity; + state()->matrix = oldTransform; + opacityChanged(); + transformChanged(); +} + +void QPaintEngineEx::setState(QPainterState *s) +{ + QPaintEngine::state = s; +} + + +void QPaintEngineEx::updateState(const QPaintEngineState &) +{ + // do nothing... +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpaintengineex_p.h b/src/gui/painting/qpaintengineex_p.h new file mode 100644 index 0000000000..d12c602372 --- /dev/null +++ b/src/gui/painting/qpaintengineex_p.h @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINEEX_P_H +#define QPAINTENGINEEX_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 <QtGui/qpaintengine.h> +#include <QtGui/qdrawutil.h> + +#include <private/qpaintengine_p.h> +#include <private/qstroker_p.h> +#include <private/qpainter_p.h> +#include <private/qvectorpath_p.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPainterState; +class QPaintEngineExPrivate; +class QStaticTextItem; +struct StrokeHandler; + +struct QIntRect { + int x1, y1, x2, y2; + inline void set(const QRect &r) { + x1 = r.x(); + y1 = r.y(); + x2 = r.right() + 1; + y2 = r.bottom() + 1; + // We will assume normalized for later... + Q_ASSERT(x2 >= x1); + Q_ASSERT(y2 >= y1); + } +}; + +class QRectVectorPath : public QVectorPath { +public: + inline void set(const QRect &r) { + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + pts[0] = left; + pts[1] = top; + pts[2] = right; + pts[3] = top; + pts[4] = right; + pts[5] = bottom; + pts[6] = left; + pts[7] = bottom; + } + + inline void set(const QRectF &r) { + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + pts[0] = left; + pts[1] = top; + pts[2] = right; + pts[3] = top; + pts[4] = right; + pts[5] = bottom; + pts[6] = left; + pts[7] = bottom; + } + inline QRectVectorPath(const QRect &r) + : QVectorPath(pts, 4, 0, QVectorPath::RectangleHint | QVectorPath::ImplicitClose) + { + set(r); + } + inline QRectVectorPath(const QRectF &r) + : QVectorPath(pts, 4, 0, QVectorPath::RectangleHint | QVectorPath::ImplicitClose) + { + set(r); + } + inline QRectVectorPath() + : QVectorPath(pts, 4, 0, QVectorPath::RectangleHint | QVectorPath::ImplicitClose) + { } + + qreal pts[8]; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug Q_GUI_EXPORT &operator<<(QDebug &, const QVectorPath &path); +#endif + +class QPixmapFilter; + +class Q_GUI_EXPORT QPaintEngineEx : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QPaintEngineEx) +public: + QPaintEngineEx(); + + virtual QPainterState *createState(QPainterState *orig) const; + + virtual void draw(const QVectorPath &path); + virtual void fill(const QVectorPath &path, const QBrush &brush) = 0; + virtual void stroke(const QVectorPath &path, const QPen &pen); + + virtual void clip(const QVectorPath &path, Qt::ClipOperation op) = 0; + virtual void clip(const QRect &rect, Qt::ClipOperation op); + virtual void clip(const QRegion ®ion, Qt::ClipOperation op); + virtual void clip(const QPainterPath &path, Qt::ClipOperation op); + + virtual void clipEnabledChanged() = 0; + virtual void penChanged() = 0; + virtual void brushChanged() = 0; + virtual void brushOriginChanged() = 0; + virtual void opacityChanged() = 0; + virtual void compositionModeChanged() = 0; + virtual void renderHintsChanged() = 0; + virtual void transformChanged() = 0; + + virtual void fillRect(const QRectF &rect, const QBrush &brush); + virtual void fillRect(const QRectF &rect, const QColor &color); + + virtual void drawRoundedRect(const QRectF &rect, qreal xrad, qreal yrad, Qt::SizeMode mode); + + virtual void drawRects(const QRect *rects, int rectCount); + virtual void drawRects(const QRectF *rects, int rectCount); + + virtual void drawLines(const QLine *lines, int lineCount); + virtual void drawLines(const QLineF *lines, int lineCount); + + virtual void drawEllipse(const QRectF &r); + virtual void drawEllipse(const QRect &r); + + virtual void drawPath(const QPainterPath &path); + + virtual void drawPoints(const QPointF *points, int pointCount); + virtual void drawPoints(const QPoint *points, int pointCount); + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + virtual void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode); + + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) = 0; + virtual void drawPixmap(const QPointF &pos, const QPixmap &pm); + + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor) = 0; + virtual void drawImage(const QPointF &pos, const QImage &image); + + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + + virtual void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragmentCount, const QPixmap &pixmap, + QFlags<QPainter::PixmapFragmentHint> hints); + + virtual void updateState(const QPaintEngineState &state); + + virtual void drawStaticTextItem(QStaticTextItem *) = 0; + + virtual void setState(QPainterState *s); + inline QPainterState *state() { return static_cast<QPainterState *>(QPaintEngine::state); } + inline const QPainterState *state() const { return static_cast<const QPainterState *>(QPaintEngine::state); } + + virtual void sync() {} + + virtual void beginNativePainting() {} + virtual void endNativePainting() {} + + // Return a pixmap filter of "type" that can render the parameters + // in "prototype". The returned filter is owned by the engine and + // will be destroyed when the engine is destroyed. The "prototype" + // allows the engine to pick different filters based on the parameters + // that will be requested, and not just the "type". + virtual QPixmapFilter *pixmapFilter(int /*type*/, const QPixmapFilter * /*prototype*/) { return 0; } + + // These flags are needed in the implementation of paint buffers. + enum Flags + { + DoNotEmulate = 0x01, // If set, QPainter will not wrap this engine in an emulation engine. + IsEmulationEngine = 0x02 // If set, this object is a QEmulationEngine. + }; + virtual uint flags() const {return 0;} + +protected: + QPaintEngineEx(QPaintEngineExPrivate &data); +}; + +class Q_GUI_EXPORT QPaintEngineExPrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QPaintEngineEx) +public: + QPaintEngineExPrivate(); + ~QPaintEngineExPrivate(); + + void replayClipOperations(); + bool hasClipOperations() const; + + QStroker stroker; + QDashStroker dasher; + StrokeHandler *strokeHandler; + QStrokerOps *activeStroker; + QPen strokerPen; + + QRect exDeviceRect; +}; + +inline uint QVectorPath::polygonFlags(QPaintEngine::PolygonDrawMode mode) { + switch (mode) { + case QPaintEngine::ConvexMode: return ConvexPolygonHint | ImplicitClose; + case QPaintEngine::OddEvenMode: return PolygonHint | OddEvenFill | ImplicitClose; + case QPaintEngine::WindingMode: return PolygonHint | WindingFill | ImplicitClose; + case QPaintEngine::PolylineMode: return PolygonHint; + default: return 0; + } +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp new file mode 100644 index 0000000000..9fafba5f4f --- /dev/null +++ b/src/gui/painting/qpainter.cpp @@ -0,0 +1,9572 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// QtCore +#include <qdebug.h> +#include <qmath.h> +#include <qmutex.h> + +// QtGui +#include "qbitmap.h" +#include "qimage.h" +#include "qpaintdevice.h" +#include "qpaintengine.h" +#include "qpainter.h" +#include "qpainter_p.h" +#include "qpainterpath.h" +#include "qpicture.h" +#include "qpixmapcache.h" +#include "qpolygon.h" +#include "qtextlayout.h" +#include "qwidget.h" +#include "qapplication.h" +#include "qstyle.h" +#include "qthread.h" +#include "qvarlengtharray.h" +#include "qstatictext.h" +#include "qglyphs.h" + +#include <private/qfontengine_p.h> +#include <private/qpaintengine_p.h> +#include <private/qemulationpaintengine_p.h> +#include <private/qpainterpath_p.h> +#include <private/qtextengine_p.h> +#include <private/qwidget_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qmath_p.h> +#include <private/qstatictext_p.h> +#include <private/qglyphs_p.h> +#include <private/qstylehelper_p.h> +#include <private/qrawfont_p.h> + +QT_BEGIN_NAMESPACE + +#define QGradient_StretchToDevice 0x10000000 +#define QPaintEngine_OpaqueBackground 0x40000000 + +// #define QT_DEBUG_DRAW +#ifdef QT_DEBUG_DRAW +bool qt_show_painter_debug_output = true; +#endif + +extern QPixmap qt_pixmapForBrush(int style, bool invert); + +void qt_format_text(const QFont &font, + const QRectF &_r, int tf, const QTextOption *option, const QString& str, QRectF *brect, + int tabstops, int* tabarray, int tabarraylen, + QPainter *painter); +static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QFontEngine *fe, + QTextCharFormat::UnderlineStyle underlineStyle, + QTextItem::RenderFlags flags, qreal width, + const QTextCharFormat &charFormat); +// Helper function to calculate left most position, width and flags for decoration drawing +Q_GUI_EXPORT void qt_draw_decoration_for_glyphs(QPainter *painter, const glyph_t *glyphArray, + const QFixedPoint *positions, int glyphCount, + QFontEngine *fontEngine, const QFont &font, + const QTextCharFormat &charFormat); + +static inline QGradient::CoordinateMode coordinateMode(const QBrush &brush) +{ + switch (brush.style()) { + case Qt::LinearGradientPattern: + case Qt::RadialGradientPattern: + case Qt::ConicalGradientPattern: + return brush.gradient()->coordinateMode(); + default: + ; + } + return QGradient::LogicalMode; +} + +/* Returns true if the gradient requires stretch to device...*/ +static inline bool check_gradient(const QBrush &brush) +{ + return coordinateMode(brush) == QGradient::StretchToDeviceMode; +} + +extern bool qHasPixmapTexture(const QBrush &); + +static inline bool is_brush_transparent(const QBrush &brush) { + Qt::BrushStyle s = brush.style(); + bool brushBitmap = qHasPixmapTexture(brush) + ? brush.texture().isQBitmap() + : (brush.textureImage().depth() == 1); + return ((s >= Qt::Dense1Pattern && s <= Qt::DiagCrossPattern) + || (s == Qt::TexturePattern && brushBitmap)); +} + +static inline bool is_pen_transparent(const QPen &pen) { + return pen.style() > Qt::SolidLine || is_brush_transparent(pen.brush()); +} + +/* Discards the emulation flags that are not relevant for line drawing + and returns the result +*/ +static inline uint line_emulation(uint emulation) +{ + return emulation & (QPaintEngine::PrimitiveTransform + | QPaintEngine::AlphaBlend + | QPaintEngine::Antialiasing + | QPaintEngine::BrushStroke + | QPaintEngine::ConstantOpacity + | QGradient_StretchToDevice + | QPaintEngine::ObjectBoundingModeGradients + | QPaintEngine_OpaqueBackground); +} + +static bool qt_paintengine_supports_transformations(QPaintEngine::Type type) +{ + return type == QPaintEngine::OpenGL2 + || type == QPaintEngine::OpenVG + || type == QPaintEngine::OpenGL; +} + +#ifndef QT_NO_DEBUG +static bool qt_painter_thread_test(int devType, const char *what, bool extraCondition = false) +{ + switch (devType) { + case QInternal::Image: + case QInternal::Printer: + case QInternal::Picture: + // can be drawn onto these devices safely from any thread +#ifndef Q_WS_WIN + if (extraCondition) +#endif + break; + default: +#ifdef Q_WS_X11 + if (QApplication::testAttribute(Qt::AA_X11InitThreads)) + return true; +#endif + if (!extraCondition && QThread::currentThread() != qApp->thread()) { + qWarning("QPainter: It is not safe to use %s outside the GUI thread", what); + return false; + } + break; + } + return true; +} +#endif + +void QPainterPrivate::checkEmulation() +{ + Q_ASSERT(extended); + if (extended->flags() & QPaintEngineEx::DoNotEmulate) + return; + + bool doEmulation = false; + if (state->bgMode == Qt::OpaqueMode) + doEmulation = true; + + const QGradient *bg = state->brush.gradient(); + if (bg && bg->coordinateMode() > QGradient::LogicalMode) + doEmulation = true; + + const QGradient *pg = qpen_brush(state->pen).gradient(); + if (pg && pg->coordinateMode() > QGradient::LogicalMode) + doEmulation = true; + + if (doEmulation) { + if (extended != emulationEngine) { + if (!emulationEngine) + emulationEngine = new QEmulationPaintEngine(extended); + extended = emulationEngine; + extended->setState(state); + } + } else if (emulationEngine == extended) { + extended = emulationEngine->real_engine; + } +} + + +QPainterPrivate::~QPainterPrivate() +{ + delete emulationEngine; + for (int i=0; i<states.size(); ++i) + delete states.at(i); + + if (dummyState) + delete dummyState; +} + + +QTransform QPainterPrivate::viewTransform() const +{ + if (state->VxF) { + qreal scaleW = qreal(state->vw)/qreal(state->ww); + qreal scaleH = qreal(state->vh)/qreal(state->wh); + return QTransform(scaleW, 0, 0, scaleH, + state->vx - state->wx*scaleW, state->vy - state->wy*scaleH); + } + return QTransform(); +} + + +/* + \internal + Returns true if using a shared painter; otherwise false. +*/ +bool QPainterPrivate::attachPainterPrivate(QPainter *q, QPaintDevice *pdev) +{ + Q_ASSERT(q); + Q_ASSERT(pdev); + + if (pdev->devType() != QInternal::Widget) + return false; + + QWidget *widget = static_cast<QWidget *>(pdev); + Q_ASSERT(widget); + + // Someone either called QPainter::setRedirected in the widget's paint event + // right before this painter was created (or begin was called) or + // sent a paint event directly to the widget. + if (!widget->d_func()->redirectDev) + return false; + + QPainter *sp = widget->d_func()->sharedPainter(); + if (!sp || !sp->isActive()) + return false; + + if (sp->paintEngine()->paintDevice() != widget->d_func()->redirectDev) + return false; + + // Check if we're attempting to paint outside a paint event. + if (!sp->d_ptr->engine->hasFeature(QPaintEngine::PaintOutsidePaintEvent) + && !widget->testAttribute(Qt::WA_PaintOutsidePaintEvent) + && !widget->testAttribute(Qt::WA_WState_InPaintEvent)) { + + qWarning("QPainter::begin: Widget painting can only begin as a result of a paintEvent"); + return false; + } + + // Save the current state of the shared painter and assign + // the current d_ptr to the shared painter's d_ptr. + sp->save(); + if (!sp->d_ptr->d_ptrs) { + // Allocate space for 4 d-pointers (enough for up to 4 sub-sequent + // redirections within the same paintEvent(), which should be enough + // in 99% of all cases). E.g: A renders B which renders C which renders D. + sp->d_ptr->d_ptrs_size = 4; + sp->d_ptr->d_ptrs = (QPainterPrivate **)malloc(4 * sizeof(QPainterPrivate *)); + Q_CHECK_PTR(sp->d_ptr->d_ptrs); + } else if (sp->d_ptr->refcount - 1 == sp->d_ptr->d_ptrs_size) { + // However, to support corner cases we grow the array dynamically if needed. + sp->d_ptr->d_ptrs_size <<= 1; + const int newSize = sp->d_ptr->d_ptrs_size * sizeof(QPainterPrivate *); + sp->d_ptr->d_ptrs = q_check_ptr((QPainterPrivate **)realloc(sp->d_ptr->d_ptrs, newSize)); + } + sp->d_ptr->d_ptrs[++sp->d_ptr->refcount - 2] = q->d_ptr.data(); + q->d_ptr.take(); + q->d_ptr.reset(sp->d_ptr.data()); + + Q_ASSERT(q->d_ptr->state); + + // Now initialize the painter with correct widget properties. + q->initFrom(widget); + QPoint offset; + widget->d_func()->redirected(&offset); + offset += q->d_ptr->engine->coordinateOffset(); + + // Update system rect. + q->d_ptr->state->ww = q->d_ptr->state->vw = widget->width(); + q->d_ptr->state->wh = q->d_ptr->state->vh = widget->height(); + + // Update matrix. + if (q->d_ptr->state->WxF) { + q->d_ptr->state->redirectionMatrix = q->d_ptr->state->matrix; + q->d_ptr->state->redirectionMatrix.translate(-offset.x(), -offset.y()); + q->d_ptr->state->worldMatrix = QTransform(); + q->d_ptr->state->WxF = false; + } else { + q->d_ptr->state->redirectionMatrix = QTransform::fromTranslate(-offset.x(), -offset.y()); + } + q->d_ptr->updateMatrix(); + + QPaintEnginePrivate *enginePrivate = q->d_ptr->engine->d_func(); + if (enginePrivate->currentClipWidget == widget) { + enginePrivate->systemStateChanged(); + return true; + } + + // Update system transform and clip. + enginePrivate->currentClipWidget = widget; + enginePrivate->setSystemTransform(q->d_ptr->state->matrix); + return true; +} + +void QPainterPrivate::detachPainterPrivate(QPainter *q) +{ + Q_ASSERT(refcount > 1); + Q_ASSERT(q); + + QPainterPrivate *original = d_ptrs[--refcount - 1]; + if (inDestructor) { + inDestructor = false; + if (original) + original->inDestructor = true; + } else if (!original) { + original = new QPainterPrivate(q); + } + + d_ptrs[refcount - 1] = 0; + q->restore(); + q->d_ptr.take(); + q->d_ptr.reset(original); + + if (emulationEngine) { + extended = emulationEngine->real_engine; + delete emulationEngine; + emulationEngine = 0; + } +} + + +void QPainterPrivate::draw_helper(const QPainterPath &originalPath, DrawOperation op) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) { + printf("QPainter::drawHelper\n"); + } +#endif + + if (originalPath.isEmpty()) + return; + + QPaintEngine::PaintEngineFeatures gradientStretch = + QPaintEngine::PaintEngineFeatures(QGradient_StretchToDevice + | QPaintEngine::ObjectBoundingModeGradients); + + const bool mustEmulateObjectBoundingModeGradients = extended + || ((state->emulationSpecifier & QPaintEngine::ObjectBoundingModeGradients) + && !engine->hasFeature(QPaintEngine::PatternTransform)); + + if (!(state->emulationSpecifier & ~gradientStretch) + && !mustEmulateObjectBoundingModeGradients) { + drawStretchedGradient(originalPath, op); + return; + } else if (state->emulationSpecifier & QPaintEngine_OpaqueBackground) { + drawOpaqueBackground(originalPath, op); + return; + } + + Q_Q(QPainter); + + qreal strokeOffsetX = 0, strokeOffsetY = 0; + + QPainterPath path = originalPath * state->matrix; + QRectF pathBounds = path.boundingRect(); + QRectF strokeBounds; + bool doStroke = (op & StrokeDraw) && (state->pen.style() != Qt::NoPen); + if (doStroke) { + qreal penWidth = state->pen.widthF(); + if (penWidth == 0) { + strokeOffsetX = 1; + strokeOffsetY = 1; + } else { + // In case of complex xform + if (state->matrix.type() > QTransform::TxScale) { + QPainterPathStroker stroker; + stroker.setWidth(penWidth); + stroker.setJoinStyle(state->pen.joinStyle()); + stroker.setCapStyle(state->pen.capStyle()); + QPainterPath stroke = stroker.createStroke(originalPath); + strokeBounds = (stroke * state->matrix).boundingRect(); + } else { + strokeOffsetX = qAbs(penWidth * state->matrix.m11() / 2.0); + strokeOffsetY = qAbs(penWidth * state->matrix.m22() / 2.0); + } + } + } + + QRect absPathRect; + if (!strokeBounds.isEmpty()) { + absPathRect = strokeBounds.intersected(QRectF(0, 0, device->width(), device->height())).toAlignedRect(); + } else { + absPathRect = pathBounds.adjusted(-strokeOffsetX, -strokeOffsetY, strokeOffsetX, strokeOffsetY) + .intersected(QRectF(0, 0, device->width(), device->height())).toAlignedRect(); + } + + if (q->hasClipping()) { + bool hasPerspectiveTransform = false; + for (int i = 0; i < state->clipInfo.size(); ++i) { + const QPainterClipInfo &info = state->clipInfo.at(i); + if (info.matrix.type() == QTransform::TxProject) { + hasPerspectiveTransform = true; + break; + } + } + // avoid mapping QRegions with perspective transforms + if (!hasPerspectiveTransform) { + // The trick with txinv and invMatrix is done in order to + // avoid transforming the clip to logical coordinates, and + // then back to device coordinates. This is a problem with + // QRegion/QRect based clips, since they use integer + // coordinates and converting to/from logical coordinates will + // lose precision. + bool old_txinv = txinv; + QTransform old_invMatrix = invMatrix; + txinv = true; + invMatrix = QTransform(); + QPainterPath clipPath = q->clipPath(); + QRectF r = clipPath.boundingRect().intersected(absPathRect); + absPathRect = r.toAlignedRect(); + txinv = old_txinv; + invMatrix = old_invMatrix; + } + } + +// qDebug("\nQPainterPrivate::draw_helper(), x=%d, y=%d, w=%d, h=%d", +// devMinX, devMinY, device->width(), device->height()); +// qDebug() << " - matrix" << state->matrix; +// qDebug() << " - originalPath.bounds" << originalPath.boundingRect(); +// qDebug() << " - path.bounds" << path.boundingRect(); + + if (absPathRect.width() <= 0 || absPathRect.height() <= 0) + return; + + QImage image(absPathRect.width(), absPathRect.height(), QImage::Format_ARGB32_Premultiplied); + image.fill(0); + + QPainter p(&image); + + p.d_ptr->helper_device = helper_device; + + p.setOpacity(state->opacity); + p.translate(-absPathRect.x(), -absPathRect.y()); + p.setTransform(state->matrix, true); + p.setPen(doStroke ? state->pen : QPen(Qt::NoPen)); + p.setBrush((op & FillDraw) ? state->brush : QBrush(Qt::NoBrush)); + p.setBackground(state->bgBrush); + p.setBackgroundMode(state->bgMode); + p.setBrushOrigin(state->brushOrigin); + + p.setRenderHint(QPainter::Antialiasing, state->renderHints & QPainter::Antialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform, + state->renderHints & QPainter::SmoothPixmapTransform); + + p.drawPath(originalPath); + +#ifndef QT_NO_DEBUG + static bool do_fallback_overlay = qgetenv("QT_PAINT_FALLBACK_OVERLAY").size() > 0; + if (do_fallback_overlay) { + QImage block(8, 8, QImage::Format_ARGB32_Premultiplied); + QPainter pt(&block); + pt.fillRect(0, 0, 8, 8, QColor(196, 0, 196)); + pt.drawLine(0, 0, 8, 8); + pt.end(); + p.resetTransform(); + p.setCompositionMode(QPainter::CompositionMode_SourceAtop); + p.setOpacity(0.5); + p.fillRect(0, 0, image.width(), image.height(), QBrush(block)); + } +#endif + + p.end(); + + q->save(); + state->matrix = QTransform(); + state->dirtyFlags |= QPaintEngine::DirtyTransform; + updateState(state); + engine->drawImage(absPathRect, + image, + QRectF(0, 0, absPathRect.width(), absPathRect.height()), + Qt::OrderedDither | Qt::OrderedAlphaDither); + q->restore(); +} + +void QPainterPrivate::drawOpaqueBackground(const QPainterPath &path, DrawOperation op) +{ + Q_Q(QPainter); + + q->setBackgroundMode(Qt::TransparentMode); + + if (op & FillDraw && state->brush.style() != Qt::NoBrush) { + q->fillPath(path, state->bgBrush.color()); + q->fillPath(path, state->brush); + } + + if (op & StrokeDraw && state->pen.style() != Qt::NoPen) { + q->strokePath(path, QPen(state->bgBrush.color(), state->pen.width())); + q->strokePath(path, state->pen); + } + + q->setBackgroundMode(Qt::OpaqueMode); +} + +static inline QBrush stretchGradientToUserSpace(const QBrush &brush, const QRectF &boundingRect) +{ + Q_ASSERT(brush.style() >= Qt::LinearGradientPattern + && brush.style() <= Qt::ConicalGradientPattern); + + QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(), + boundingRect.x(), boundingRect.y()); + + QGradient g = *brush.gradient(); + g.setCoordinateMode(QGradient::LogicalMode); + + QBrush b(g); + b.setTransform(gradientToUser * b.transform()); + return b; +} + +void QPainterPrivate::drawStretchedGradient(const QPainterPath &path, DrawOperation op) +{ + Q_Q(QPainter); + + const qreal sw = helper_device->width(); + const qreal sh = helper_device->height(); + + bool changedPen = false; + bool changedBrush = false; + bool needsFill = false; + + const QPen pen = state->pen; + const QBrush brush = state->brush; + + const QGradient::CoordinateMode penMode = coordinateMode(pen.brush()); + const QGradient::CoordinateMode brushMode = coordinateMode(brush); + + QRectF boundingRect; + + // Draw the xformed fill if the brush is a stretch gradient. + if ((op & FillDraw) && brush.style() != Qt::NoBrush) { + if (brushMode == QGradient::StretchToDeviceMode) { + q->setPen(Qt::NoPen); + changedPen = pen.style() != Qt::NoPen; + q->scale(sw, sh); + updateState(state); + + const qreal isw = 1.0 / sw; + const qreal ish = 1.0 / sh; + QTransform inv(isw, 0, 0, ish, 0, 0); + engine->drawPath(path * inv); + q->scale(isw, ish); + } else { + needsFill = true; + + if (brushMode == QGradient::ObjectBoundingMode) { + Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform)); + boundingRect = path.boundingRect(); + q->setBrush(stretchGradientToUserSpace(brush, boundingRect)); + changedBrush = true; + } + } + } + + if ((op & StrokeDraw) && pen.style() != Qt::NoPen) { + // Draw the xformed outline if the pen is a stretch gradient. + if (penMode == QGradient::StretchToDeviceMode) { + q->setPen(Qt::NoPen); + changedPen = true; + + if (needsFill) { + updateState(state); + engine->drawPath(path); + } + + q->scale(sw, sh); + q->setBrush(pen.brush()); + changedBrush = true; + updateState(state); + + QPainterPathStroker stroker; + stroker.setDashPattern(pen.style()); + stroker.setWidth(pen.widthF()); + stroker.setJoinStyle(pen.joinStyle()); + stroker.setCapStyle(pen.capStyle()); + stroker.setMiterLimit(pen.miterLimit()); + QPainterPath stroke = stroker.createStroke(path); + + const qreal isw = 1.0 / sw; + const qreal ish = 1.0 / sh; + QTransform inv(isw, 0, 0, ish, 0, 0); + engine->drawPath(stroke * inv); + q->scale(isw, ish); + } else { + if (!needsFill && brush.style() != Qt::NoBrush) { + q->setBrush(Qt::NoBrush); + changedBrush = true; + } + + if (penMode == QGradient::ObjectBoundingMode) { + Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform)); + + // avoid computing the bounding rect twice + if (!needsFill || brushMode != QGradient::ObjectBoundingMode) + boundingRect = path.boundingRect(); + + QPen p = pen; + p.setBrush(stretchGradientToUserSpace(pen.brush(), boundingRect)); + q->setPen(p); + changedPen = true; + } else if (changedPen) { + q->setPen(pen); + changedPen = false; + } + + updateState(state); + engine->drawPath(path); + } + } else if (needsFill) { + if (pen.style() != Qt::NoPen) { + q->setPen(Qt::NoPen); + changedPen = true; + } + + updateState(state); + engine->drawPath(path); + } + + if (changedPen) + q->setPen(pen); + if (changedBrush) + q->setBrush(brush); +} + + +void QPainterPrivate::updateMatrix() +{ + state->matrix = state->WxF ? state->worldMatrix : QTransform(); + if (state->VxF) + state->matrix *= viewTransform(); + + txinv = false; // no inverted matrix + state->matrix *= state->redirectionMatrix; + if (extended) + extended->transformChanged(); + else + state->dirtyFlags |= QPaintEngine::DirtyTransform; + +// printf("VxF=%d, WxF=%d\n", state->VxF, state->WxF); +// qDebug() << " --- using matrix" << state->matrix << redirection_offset; +} + +/*! \internal */ +void QPainterPrivate::updateInvMatrix() +{ + Q_ASSERT(txinv == false); + txinv = true; // creating inverted matrix + invMatrix = state->matrix.inverted(); +} + +void QPainterPrivate::updateEmulationSpecifier(QPainterState *s) +{ + bool alpha = false; + bool linearGradient = false; + bool radialGradient = false; + bool conicalGradient = false; + bool patternBrush = false; + bool xform = false; + bool complexXform = false; + + bool skip = true; + + // Pen and brush properties (we have to check both if one changes because the + // one that's unchanged can still be in a state which requires emulation) + if (s->state() & (QPaintEngine::DirtyPen | QPaintEngine::DirtyBrush | QPaintEngine::DirtyHints)) { + // Check Brush stroke emulation + if (!s->pen.isSolid() && !engine->hasFeature(QPaintEngine::BrushStroke)) + s->emulationSpecifier |= QPaintEngine::BrushStroke; + else + s->emulationSpecifier &= ~QPaintEngine::BrushStroke; + + skip = false; + + QBrush penBrush = (qpen_style(s->pen) == Qt::NoPen) ? QBrush(Qt::NoBrush) : qpen_brush(s->pen); + Qt::BrushStyle brushStyle = qbrush_style(s->brush); + Qt::BrushStyle penBrushStyle = qbrush_style(penBrush); + alpha = (penBrushStyle != Qt::NoBrush + && (penBrushStyle < Qt::LinearGradientPattern && penBrush.color().alpha() != 255) + && !penBrush.isOpaque()) + || (brushStyle != Qt::NoBrush + && (brushStyle < Qt::LinearGradientPattern && s->brush.color().alpha() != 255) + && !s->brush.isOpaque()); + linearGradient = ((penBrushStyle == Qt::LinearGradientPattern) || + (brushStyle == Qt::LinearGradientPattern)); + radialGradient = ((penBrushStyle == Qt::RadialGradientPattern) || + (brushStyle == Qt::RadialGradientPattern)); + conicalGradient = ((penBrushStyle == Qt::ConicalGradientPattern) || + (brushStyle == Qt::ConicalGradientPattern)); + patternBrush = (((penBrushStyle > Qt::SolidPattern + && penBrushStyle < Qt::LinearGradientPattern) + || penBrushStyle == Qt::TexturePattern) || + ((brushStyle > Qt::SolidPattern + && brushStyle < Qt::LinearGradientPattern) + || brushStyle == Qt::TexturePattern)); + + bool penTextureAlpha = false; + if (penBrush.style() == Qt::TexturePattern) + penTextureAlpha = qHasPixmapTexture(penBrush) + ? (penBrush.texture().depth() > 1) && penBrush.texture().hasAlpha() + : penBrush.textureImage().hasAlphaChannel(); + bool brushTextureAlpha = false; + if (s->brush.style() == Qt::TexturePattern) { + brushTextureAlpha = qHasPixmapTexture(s->brush) + ? (s->brush.texture().depth() > 1) && s->brush.texture().hasAlpha() + : s->brush.textureImage().hasAlphaChannel(); + } + if (((penBrush.style() == Qt::TexturePattern && penTextureAlpha) + || (s->brush.style() == Qt::TexturePattern && brushTextureAlpha)) + && !engine->hasFeature(QPaintEngine::MaskedBrush)) + s->emulationSpecifier |= QPaintEngine::MaskedBrush; + else + s->emulationSpecifier &= ~QPaintEngine::MaskedBrush; + } + + if (s->state() & (QPaintEngine::DirtyHints + | QPaintEngine::DirtyOpacity + | QPaintEngine::DirtyBackgroundMode)) { + skip = false; + } + + if (skip) + return; + +#if 0 + qDebug("QPainterPrivate::updateEmulationSpecifier, state=%p\n" + " - alpha: %d\n" + " - linearGradient: %d\n" + " - radialGradient: %d\n" + " - conicalGradient: %d\n" + " - patternBrush: %d\n" + " - hints: %x\n" + " - xform: %d\n", + s, + alpha, + linearGradient, + radialGradient, + conicalGradient, + patternBrush, + uint(s->renderHints), + xform); +#endif + + // XForm properties + if (s->state() & QPaintEngine::DirtyTransform) { + xform = !s->matrix.isIdentity(); + complexXform = !s->matrix.isAffine(); + } else if (s->matrix.type() >= QTransform::TxTranslate) { + xform = true; + complexXform = !s->matrix.isAffine(); + } + + const bool brushXform = (!s->brush.transform().type() == QTransform::TxNone); + const bool penXform = (!s->pen.brush().transform().type() == QTransform::TxNone); + + const bool patternXform = patternBrush && (xform || brushXform || penXform); + + // Check alphablending + if (alpha && !engine->hasFeature(QPaintEngine::AlphaBlend)) + s->emulationSpecifier |= QPaintEngine::AlphaBlend; + else + s->emulationSpecifier &= ~QPaintEngine::AlphaBlend; + + // Linear gradient emulation + if (linearGradient && !engine->hasFeature(QPaintEngine::LinearGradientFill)) + s->emulationSpecifier |= QPaintEngine::LinearGradientFill; + else + s->emulationSpecifier &= ~QPaintEngine::LinearGradientFill; + + // Radial gradient emulation + if (radialGradient && !engine->hasFeature(QPaintEngine::RadialGradientFill)) + s->emulationSpecifier |= QPaintEngine::RadialGradientFill; + else + s->emulationSpecifier &= ~QPaintEngine::RadialGradientFill; + + // Conical gradient emulation + if (conicalGradient && !engine->hasFeature(QPaintEngine::ConicalGradientFill)) + s->emulationSpecifier |= QPaintEngine::ConicalGradientFill; + else + s->emulationSpecifier &= ~QPaintEngine::ConicalGradientFill; + + // Pattern brushes + if (patternBrush && !engine->hasFeature(QPaintEngine::PatternBrush)) + s->emulationSpecifier |= QPaintEngine::PatternBrush; + else + s->emulationSpecifier &= ~QPaintEngine::PatternBrush; + + // Pattern XForms + if (patternXform && !engine->hasFeature(QPaintEngine::PatternTransform)) + s->emulationSpecifier |= QPaintEngine::PatternTransform; + else + s->emulationSpecifier &= ~QPaintEngine::PatternTransform; + + // Primitive XForms + if (xform && !engine->hasFeature(QPaintEngine::PrimitiveTransform)) + s->emulationSpecifier |= QPaintEngine::PrimitiveTransform; + else + s->emulationSpecifier &= ~QPaintEngine::PrimitiveTransform; + + // Perspective XForms + if (complexXform && !engine->hasFeature(QPaintEngine::PerspectiveTransform)) + s->emulationSpecifier |= QPaintEngine::PerspectiveTransform; + else + s->emulationSpecifier &= ~QPaintEngine::PerspectiveTransform; + + // Constant opacity + if (state->opacity != 1 && !engine->hasFeature(QPaintEngine::ConstantOpacity)) + s->emulationSpecifier |= QPaintEngine::ConstantOpacity; + else + s->emulationSpecifier &= ~QPaintEngine::ConstantOpacity; + + bool gradientStretch = false; + bool objectBoundingMode = false; + if (linearGradient || conicalGradient || radialGradient) { + QGradient::CoordinateMode brushMode = coordinateMode(s->brush); + QGradient::CoordinateMode penMode = coordinateMode(s->pen.brush()); + + gradientStretch |= (brushMode == QGradient::StretchToDeviceMode); + gradientStretch |= (penMode == QGradient::StretchToDeviceMode); + + objectBoundingMode |= (brushMode == QGradient::ObjectBoundingMode); + objectBoundingMode |= (penMode == QGradient::ObjectBoundingMode); + } + if (gradientStretch) + s->emulationSpecifier |= QGradient_StretchToDevice; + else + s->emulationSpecifier &= ~QGradient_StretchToDevice; + + if (objectBoundingMode && !engine->hasFeature(QPaintEngine::ObjectBoundingModeGradients)) + s->emulationSpecifier |= QPaintEngine::ObjectBoundingModeGradients; + else + s->emulationSpecifier &= ~QPaintEngine::ObjectBoundingModeGradients; + + // Opaque backgrounds... + if (s->bgMode == Qt::OpaqueMode && + (is_pen_transparent(s->pen) || is_brush_transparent(s->brush))) + s->emulationSpecifier |= QPaintEngine_OpaqueBackground; + else + s->emulationSpecifier &= ~QPaintEngine_OpaqueBackground; + +#if 0 + //won't be correct either way because the device can already have + // something rendered to it in which case subsequent emulation + // on a fully transparent qimage and then blitting the results + // won't produce correct results + // Blend modes + if (state->composition_mode > QPainter::CompositionMode_Xor && + !engine->hasFeature(QPaintEngine::BlendModes)) + s->emulationSpecifier |= QPaintEngine::BlendModes; + else + s->emulationSpecifier &= ~QPaintEngine::BlendModes; +#endif +} + +void QPainterPrivate::updateStateImpl(QPainterState *newState) +{ + // ### we might have to call QPainter::begin() here... + if (!engine->state) { + engine->state = newState; + engine->setDirty(QPaintEngine::AllDirty); + } + + if (engine->state->painter() != newState->painter) + // ### this could break with clip regions vs paths. + engine->setDirty(QPaintEngine::AllDirty); + + // Upon restore, revert all changes since last save + else if (engine->state != newState) + newState->dirtyFlags |= QPaintEngine::DirtyFlags(static_cast<QPainterState *>(engine->state)->changeFlags); + + // We need to store all changes made so that restore can deal with them + else + newState->changeFlags |= newState->dirtyFlags; + + updateEmulationSpecifier(newState); + + // Unset potential dirty background mode + newState->dirtyFlags &= ~(QPaintEngine::DirtyBackgroundMode + | QPaintEngine::DirtyBackground); + + engine->state = newState; + engine->updateState(*newState); + engine->clearDirty(QPaintEngine::AllDirty); + +} + +void QPainterPrivate::updateState(QPainterState *newState) +{ + + if (!newState) { + engine->state = newState; + + } else if (newState->state() || engine->state!=newState) { + bool setNonCosmeticPen = (newState->renderHints & QPainter::NonCosmeticDefaultPen) + && newState->pen.widthF() == 0; + if (setNonCosmeticPen) { + // Override the default pen's cosmetic state if the + // NonCosmeticDefaultPen render hint is used. + QPen oldPen = newState->pen; + newState->pen.setWidth(1); + newState->pen.setCosmetic(false); + newState->dirtyFlags |= QPaintEngine::DirtyPen; + + updateStateImpl(newState); + + // Restore the state pen back to its default to preserve visible + // state. + newState->pen = oldPen; + } else { + updateStateImpl(newState); + } + } +} + + +/*! + \class QPainter + \brief The QPainter class performs low-level painting on widgets and + other paint devices. + + \ingroup painting + + \reentrant + + QPainter provides highly optimized functions to do most of the + drawing GUI programs require. It can draw everything from simple + lines to complex shapes like pies and chords. It can also draw + aligned text and pixmaps. Normally, it draws in a "natural" + coordinate system, but it can also do view and world + transformation. QPainter can operate on any object that inherits + the QPaintDevice class. + + The common use of QPainter is inside a widget's paint event: + Construct and customize (e.g. set the pen or the brush) the + painter. Then draw. Remember to destroy the QPainter object after + drawing. For example: + + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 0 + + The core functionality of QPainter is drawing, but the class also + provide several functions that allows you to customize QPainter's + settings and its rendering quality, and others that enable + clipping. In addition you can control how different shapes are + merged together by specifying the painter's composition mode. + + The isActive() function indicates whether the painter is active. A + painter is activated by the begin() function and the constructor + that takes a QPaintDevice argument. The end() function, and the + destructor, deactivates it. + + Together with the QPaintDevice and QPaintEngine classes, QPainter + form the basis for Qt's paint system. QPainter is the class used + to perform drawing operations. QPaintDevice represents a device + that can be painted on using a QPainter. QPaintEngine provides the + interface that the painter uses to draw onto different types of + devices. If the painter is active, device() returns the paint + device on which the painter paints, and paintEngine() returns the + paint engine that the painter is currently operating on. For more + information, see the \l {Paint System}. + + Sometimes it is desirable to make someone else paint on an unusual + QPaintDevice. QPainter supports a static function to do this, + setRedirected(). + + \warning When the paintdevice is a widget, QPainter can only be + used inside a paintEvent() function or in a function called by + paintEvent(); that is unless the Qt::WA_PaintOutsidePaintEvent + widget attribute is set. On Mac OS X and Windows, you can only + paint in a paintEvent() function regardless of this attribute's + setting. + + \tableofcontents + + \section1 Settings + + There are several settings that you can customize to make QPainter + draw according to your preferences: + + \list + + \o font() is the font used for drawing text. If the painter + isActive(), you can retrieve information about the currently set + font, and its metrics, using the fontInfo() and fontMetrics() + functions respectively. + + \o brush() defines the color or pattern that is used for filling + shapes. + + \o pen() defines the color or stipple that is used for drawing + lines or boundaries. + + \o backgroundMode() defines whether there is a background() or + not, i.e it is either Qt::OpaqueMode or Qt::TransparentMode. + + \o background() only applies when backgroundMode() is \l + Qt::OpaqueMode and pen() is a stipple. In that case, it + describes the color of the background pixels in the stipple. + + \o brushOrigin() defines the origin of the tiled brushes, normally + the origin of widget's background. + + \o viewport(), window(), worldTransform() make up the painter's coordinate + transformation system. For more information, see the \l + {Coordinate Transformations} section and the \l {Coordinate + System} documentation. + + \o hasClipping() tells whether the painter clips at all. (The paint + device clips, too.) If the painter clips, it clips to clipRegion(). + + \o layoutDirection() defines the layout direction used by the + painter when drawing text. + + \o worldMatrixEnabled() tells whether world transformation is enabled. + + \o viewTransformEnabled() tells whether view transformation is + enabled. + + \endlist + + Note that some of these settings mirror settings in some paint + devices, e.g. QWidget::font(). The QPainter::begin() function (or + equivalently the QPainter constructor) copies these attributes + from the paint device. + + You can at any time save the QPainter's state by calling the + save() function which saves all the available settings on an + internal stack. The restore() function pops them back. + + \section1 Drawing + + QPainter provides functions to draw most primitives: drawPoint(), + drawPoints(), drawLine(), drawRect(), drawRoundedRect(), + drawEllipse(), drawArc(), drawPie(), drawChord(), drawPolyline(), + drawPolygon(), drawConvexPolygon() and drawCubicBezier(). The two + convenience functions, drawRects() and drawLines(), draw the given + number of rectangles or lines in the given array of \l + {QRect}{QRects} or \l {QLine}{QLines} using the current pen and + brush. + + The QPainter class also provides the fillRect() function which + fills the given QRect, with the given QBrush, and the eraseRect() + function that erases the area inside the given rectangle. + + All of these functions have both integer and floating point + versions. + + \table 100% + \row + \o \inlineimage qpainter-basicdrawing.png + \o + \bold {Basic Drawing Example} + + The \l {painting/basicdrawing}{Basic Drawing} example shows how to + display basic graphics primitives in a variety of styles using the + QPainter class. + + \endtable + + If you need to draw a complex shape, especially if you need to do + so repeatedly, consider creating a QPainterPath and drawing it + using drawPath(). + + \table 100% + \row + \o + \bold {Painter Paths example} + + The QPainterPath class provides a container for painting + operations, enabling graphical shapes to be constructed and + reused. + + The \l {painting/painterpaths}{Painter Paths} example shows how + painter paths can be used to build complex shapes for rendering. + + \o \inlineimage qpainter-painterpaths.png + \endtable + + QPainter also provides the fillPath() function which fills the + given QPainterPath with the given QBrush, and the strokePath() + function that draws the outline of the given path (i.e. strokes + the path). + + See also the \l {demos/deform}{Vector Deformation} demo which + shows how to use advanced vector techniques to draw text using a + QPainterPath, the \l {demos/gradients}{Gradients} demo which shows + the different types of gradients that are available in Qt, and the \l + {demos/pathstroke}{Path Stroking} demo which shows Qt's built-in + dash patterns and shows how custom patterns can be used to extend + the range of available patterns. + + \table + \header + \o \l {demos/deform}{Vector Deformation} + \o \l {demos/gradients}{Gradients} + \o \l {demos/pathstroke}{Path Stroking} + \row + \o \inlineimage qpainter-vectordeformation.png + \o \inlineimage qpainter-gradients.png + \o \inlineimage qpainter-pathstroking.png + \endtable + + + There are functions to draw pixmaps/images, namely drawPixmap(), + drawImage() and drawTiledPixmap(). Both drawPixmap() and drawImage() + produce the same result, except that drawPixmap() is faster + on-screen while drawImage() may be faster on a QPrinter or other + devices. + + Text drawing is done using drawText(). When you need + fine-grained positioning, boundingRect() tells you where a given + drawText() command will draw. + + There is a drawPicture() function that draws the contents of an + entire QPicture. The drawPicture() function is the only function + that disregards all the painter's settings as QPicture has its own + settings. + + \section1 Rendering Quality + + To get the optimal rendering result using QPainter, you should use + the platform independent QImage as paint device; i.e. using QImage + will ensure that the result has an identical pixel representation + on any platform. + + The QPainter class also provides a means of controlling the + rendering quality through its RenderHint enum and the support for + floating point precision: All the functions for drawing primitives + has a floating point version. These are often used in combination + with the \l {RenderHint}{QPainter::Antialiasing} render hint. + + \table 100% + \row + \o \inlineimage qpainter-concentriccircles.png + \o + \bold {Concentric Circles Example} + + The \l {painting/concentriccircles}{Concentric Circles} example + shows the improved rendering quality that can be obtained using + floating point precision and anti-aliasing when drawing custom + widgets. + + The application's main window displays several widgets which are + drawn using the various combinations of precision and + anti-aliasing. + + \endtable + + The RenderHint enum specifies flags to QPainter that may or may + not be respected by any given engine. \l + {RenderHint}{QPainter::Antialiasing} indicates that the engine + should antialias edges of primitives if possible, \l + {RenderHint}{QPainter::TextAntialiasing} indicates that the engine + should antialias text if possible, and the \l + {RenderHint}{QPainter::SmoothPixmapTransform} indicates that the + engine should use a smooth pixmap transformation algorithm. + \l {RenderHint}{HighQualityAntialiasing} is an OpenGL-specific rendering hint + indicating that the engine should use fragment programs and offscreen + rendering for antialiasing. + + The renderHints() function returns a flag that specifies the + rendering hints that are set for this painter. Use the + setRenderHint() function to set or clear the currently set + RenderHints. + + \section1 Coordinate Transformations + + Normally, the QPainter operates on the device's own coordinate + system (usually pixels), but QPainter has good support for + coordinate transformations. + + \table + \header + \o nop \o rotate() \o scale() \o translate() + \row + \o \inlineimage qpainter-clock.png + \o \inlineimage qpainter-rotation.png + \o \inlineimage qpainter-scale.png + \o \inlineimage qpainter-translation.png + \endtable + + The most commonly used transformations are scaling, rotation, + translation and shearing. Use the scale() function to scale the + coordinate system by a given offset, the rotate() function to + rotate it clockwise and translate() to translate it (i.e. adding a + given offset to the points). You can also twist the coordinate + system around the origin using the shear() function. See the \l + {demos/affine}{Affine Transformations} demo for a visualization of + a sheared coordinate system. + + See also the \l {painting/transformations}{Transformations} + example which shows how transformations influence the way that + QPainter renders graphics primitives. In particular it shows how + the order of transformations affects the result. + + \table 100% + \row + \o + \bold {Affine Transformations Demo} + + The \l {demos/affine}{Affine Transformations} demo show Qt's + ability to perform affine transformations on painting + operations. The demo also allows the user to experiment with the + transformation operations and see the results immediately. + + \o \inlineimage qpainter-affinetransformations.png + \endtable + + All the tranformation operations operate on the transformation + worldTransform(). A matrix transforms a point in the plane to another + point. For more information about the transformation matrix, see + the \l {Coordinate System} and QTransform documentation. + + The setWorldTransform() function can replace or add to the currently + set worldTransform(). The resetTransform() function resets any + transformations that were made using translate(), scale(), + shear(), rotate(), setWorldTransform(), setViewport() and setWindow() + functions. The deviceTransform() returns the matrix that transforms + from logical coordinates to device coordinates of the platform + dependent paint device. The latter function is only needed when + using platform painting commands on the platform dependent handle, + and the platform does not do transformations nativly. + + When drawing with QPainter, we specify points using logical + coordinates which then are converted into the physical coordinates + of the paint device. The mapping of the logical coordinates to the + physical coordinates are handled by QPainter's combinedTransform(), a + combination of viewport() and window() and worldTransform(). The + viewport() represents the physical coordinates specifying an + arbitrary rectangle, the window() describes the same rectangle in + logical coordinates, and the worldTransform() is identical with the + transformation matrix. + + See also \l {Coordinate System} + + \section1 Clipping + + QPainter can clip any drawing operation to a rectangle, a region, + or a vector path. The current clip is available using the + functions clipRegion() and clipPath(). Whether paths or regions are + preferred (faster) depends on the underlying paintEngine(). For + example, the QImage paint engine prefers paths while the X11 paint + engine prefers regions. Setting a clip is done in the painters + logical coordinates. + + After QPainter's clipping, the paint device may also clip. For + example, most widgets clip away the pixels used by child widgets, + and most printers clip away an area near the edges of the paper. + This additional clipping is not reflected by the return value of + clipRegion() or hasClipping(). + + \section1 Composition Modes + \target Composition Modes + + QPainter provides the CompositionMode enum which defines the + Porter-Duff rules for digital image compositing; it describes a + model for combining the pixels in one image, the source, with the + pixels in another image, the destination. + + The two most common forms of composition are \l + {QPainter::CompositionMode}{Source} and \l + {QPainter::CompositionMode}{SourceOver}. \l + {QPainter::CompositionMode}{Source} is used to draw opaque objects + onto a paint device. In this mode, each pixel in the source + replaces the corresponding pixel in the destination. In \l + {QPainter::CompositionMode}{SourceOver} composition mode, the + source object is transparent and is drawn on top of the + destination. + + Note that composition transformation operates pixelwise. For that + reason, there is a difference between using the graphic primitive + itself and its bounding rectangle: The bounding rect contains + pixels with alpha == 0 (i.e the pixels surrounding the + primitive). These pixels will overwrite the other image's pixels, + affectively clearing those, while the primitive only overwrites + its own area. + + \table 100% + \row + \o \inlineimage qpainter-compositiondemo.png + + \o + \bold {Composition Modes Demo} + + The \l {demos/composition}{Composition Modes} demo, available in + Qt's demo directory, allows you to experiment with the various + composition modes and see the results immediately. + + \endtable + + \section1 Limitations + \target Limitations + + If you are using coordinates with Qt's raster-based paint engine, it is + important to note that, while coordinates greater than +/- 2\sup 15 can + be used, any painting performed with coordinates outside this range is not + guaranteed to be shown; the drawing may be clipped. This is due to the + use of \c{short int} in the implementation. + + The outlines generated by Qt's stroker are only an approximation when dealing + with curved shapes. It is in most cases impossible to represent the outline of + a bezier curve segment using another bezier curve segment, and so Qt approximates + the curve outlines by using several smaller curves. For performance reasons there + is a limit to how many curves Qt uses for these outlines, and thus when using + large pen widths or scales the outline error increases. To generate outlines with + smaller errors it is possible to use the QPainterPathStroker class, which has the + setCurveThreshold member function which let's the user specify the error tolerance. + Another workaround is to convert the paths to polygons first and then draw the + polygons instead. + + \section1 Performance + + QPainter is a rich framework that allows developers to do a great + variety of graphical operations, such as gradients, composition + modes and vector graphics. And QPainter can do this across a + variety of different hardware and software stacks. Naturally the + underlying combination of hardware and software has some + implications for performance, and ensuring that every single + operation is fast in combination with all the various combinations + of composition modes, brushes, clipping, transformation, etc, is + close to an impossible task because of the number of + permutations. As a compromise we have selected a subset of the + QPainter API and backends, where performance is guaranteed to be as + good as we can sensibly get it for the given combination of + hardware and software. + + The backends we focus on as high-performance engines are: + + \list + + \o Raster - This backend implements all rendering in pure software + and is always used to render into QImages. For optimal performance + only use the format types QImage::Format_ARGB32_Premultiplied, + QImage::Format_RGB32 or QImage::Format_RGB16. Any other format, + including QImage::Format_ARGB32, has significantly worse + performance. This engine is also used by default on Windows and on + QWS. It can be used as default graphics system on any + OS/hardware/software combination by passing \c {-graphicssystem + raster} on the command line + + \o OpenGL 2.0 (ES) - This backend is the primary backend for + hardware accelerated graphics. It can be run on desktop machines + and embedded devices supporting the OpenGL 2.0 or OpenGL/ES 2.0 + specification. This includes most graphics chips produced in the + last couple of years. The engine can be enabled by using QPainter + onto a QGLWidget or by passing \c {-graphicssystem opengl} on the + command line when the underlying system supports it. + + \o OpenVG - This backend implements the Khronos standard for 2D + and Vector Graphics. It is primarily for embedded devices with + hardware support for OpenVG. The engine can be enabled by + passing \c {-graphicssystem openvg} on the command line when + the underlying system supports it. + + \endlist + + These operations are: + + \list + + \o Simple transformations, meaning translation and scaling, pluss + 0, 90, 180, 270 degree rotations. + + \o \c drawPixmap() in combination with simple transformations and + opacity with non-smooth transformation mode + (\c QPainter::SmoothPixmapTransform not enabled as a render hint). + + \o Rectangle fills with solid color, two-color linear gradients + and simple transforms. + + \o Rectangular clipping with simple transformations and intersect + clip. + + \o Composition Modes \c QPainter::CompositionMode_Source and + QPainter::CompositionMode_SourceOver + + \o Rounded rectangle filling using solid color and two-color + linear gradients fills. + + \o 3x3 patched pixmaps, via qDrawBorderPixmap. + + \endlist + + This list gives an indication of which features to safely use in + an application where performance is critical. For certain setups, + other operations may be fast too, but before making extensive use + of them, it is recommended to benchmark and verify them on the + system where the software will run in the end. There are also + cases where expensive operations are ok to use, for instance when + the result is cached in a QPixmap. + + \sa QPaintDevice, QPaintEngine, {QtSvg Module}, {Basic Drawing Example}, + {Drawing Utility Functions} +*/ + +/*! + \enum QPainter::RenderHint + + Renderhints are used to specify flags to QPainter that may or + may not be respected by any given engine. + + \value Antialiasing Indicates that the engine should antialias + edges of primitives if possible. + + \value TextAntialiasing Indicates that the engine should antialias + text if possible. To forcibly disable antialiasing for text, do not + use this hint. Instead, set QFont::NoAntialias on your font's style + strategy. + + \value SmoothPixmapTransform Indicates that the engine should use + a smooth pixmap transformation algorithm (such as bilinear) rather + than nearest neighbor. + + \value HighQualityAntialiasing An OpenGL-specific rendering hint + indicating that the engine should use fragment programs and offscreen + rendering for antialiasing. + + \value NonCosmeticDefaultPen The engine should interpret pens with a width + of 0 (which otherwise enables QPen::isCosmetic()) as being a non-cosmetic + pen with a width of 1. + + \sa renderHints(), setRenderHint(), {QPainter#Rendering + Quality}{Rendering Quality}, {Concentric Circles Example} + +*/ + +/*! + Constructs a painter. + + \sa begin(), end() +*/ + +QPainter::QPainter() + : d_ptr(new QPainterPrivate(this)) +{ +} + +/*! + \fn QPainter::QPainter(QPaintDevice *device) + + Constructs a painter that begins painting the paint \a device + immediately. + + This constructor is convenient for short-lived painters, e.g. in a + QWidget::paintEvent() and should be used only once. The + constructor calls begin() for you and the QPainter destructor + automatically calls end(). + + Here's an example using begin() and end(): + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 1 + + The same example using this constructor: + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 2 + + Since the constructor cannot provide feedback when the initialization + of the painter failed you should rather use begin() and end() to paint + on external devices, e.g. printers. + + \sa begin(), end() +*/ + +QPainter::QPainter(QPaintDevice *pd) + : d_ptr(0) +{ + Q_ASSERT(pd != 0); + if (!QPainterPrivate::attachPainterPrivate(this, pd)) { + d_ptr.reset(new QPainterPrivate(this)); + begin(pd); + } + Q_ASSERT(d_ptr); +} + +/*! + Destroys the painter. +*/ +QPainter::~QPainter() +{ + d_ptr->inDestructor = true; + QT_TRY { + if (isActive()) + end(); + else if (d_ptr->refcount > 1) + d_ptr->detachPainterPrivate(this); + } QT_CATCH(...) { + // don't throw anything in the destructor. + } + if (d_ptr) { + // Make sure we haven't messed things up. + Q_ASSERT(d_ptr->inDestructor); + d_ptr->inDestructor = false; + Q_ASSERT(d_ptr->refcount == 1); + if (d_ptr->d_ptrs) + free(d_ptr->d_ptrs); + } +} + +/*! + Returns the paint device on which this painter is currently + painting, or 0 if the painter is not active. + + \sa isActive() +*/ + +QPaintDevice *QPainter::device() const +{ + Q_D(const QPainter); + if (isActive() && d->engine->d_func()->currentClipWidget) + return d->engine->d_func()->currentClipWidget; + return d->original_device; +} + +/*! + Returns true if begin() has been called and end() has not yet been + called; otherwise returns false. + + \sa begin(), QPaintDevice::paintingActive() +*/ + +bool QPainter::isActive() const +{ + Q_D(const QPainter); + return d->engine; +} + +/*! + Initializes the painters pen, background and font to the same as + the given \a widget. This function is called automatically when the + painter is opened on a QWidget. + + \sa begin(), {QPainter#Settings}{Settings} +*/ +void QPainter::initFrom(const QWidget *widget) +{ + Q_ASSERT_X(widget, "QPainter::initFrom(const QWidget *widget)", "Widget cannot be 0"); + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::initFrom: Painter not active, aborted"); + return; + } + + const QPalette &pal = widget->palette(); + d->state->pen = QPen(pal.brush(widget->foregroundRole()), 0); + d->state->bgBrush = pal.brush(widget->backgroundRole()); + d->state->deviceFont = QFont(widget->font(), const_cast<QWidget*> (widget)); + d->state->font = d->state->deviceFont; + if (d->extended) { + d->extended->penChanged(); + } else if (d->engine) { + d->engine->setDirty(QPaintEngine::DirtyPen); + d->engine->setDirty(QPaintEngine::DirtyBrush); + d->engine->setDirty(QPaintEngine::DirtyFont); + } +} + + +/*! + Saves the current painter state (pushes the state onto a stack). A + save() must be followed by a corresponding restore(); the end() + function unwinds the stack. + + \sa restore() +*/ + +void QPainter::save() +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::save()\n"); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::save: Painter not active"); + return; + } + + if (d->extended) { + d->state = d->extended->createState(d->states.back()); + d->extended->setState(d->state); + } else { + d->updateState(d->state); + d->state = new QPainterState(d->states.back()); + d->engine->state = d->state; + } + d->states.push_back(d->state); +} + +/*! + Restores the current painter state (pops a saved state off the + stack). + + \sa save() +*/ + +void QPainter::restore() +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::restore()\n"); +#endif + Q_D(QPainter); + if (d->states.size()<=1) { + qWarning("QPainter::restore: Unbalanced save/restore"); + return; + } else if (!d->engine) { + qWarning("QPainter::restore: Painter not active"); + return; + } + + QPainterState *tmp = d->state; + d->states.pop_back(); + d->state = d->states.back(); + d->txinv = false; + + if (d->extended) { + d->checkEmulation(); + d->extended->setState(d->state); + delete tmp; + return; + } + + // trigger clip update if the clip path/region has changed since + // last save + if (!d->state->clipInfo.isEmpty() + && (tmp->changeFlags & (QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyClipPath))) { + // reuse the tmp state to avoid any extra allocs... + tmp->dirtyFlags = QPaintEngine::DirtyClipPath; + tmp->clipOperation = Qt::NoClip; + tmp->clipPath = QPainterPath(); + d->engine->updateState(*tmp); + // replay the list of clip states, + for (int i=0; i<d->state->clipInfo.size(); ++i) { + const QPainterClipInfo &info = d->state->clipInfo.at(i); + tmp->matrix = info.matrix; + tmp->matrix *= d->state->redirectionMatrix; + tmp->clipOperation = info.operation; + if (info.clipType == QPainterClipInfo::RectClip) { + tmp->dirtyFlags = QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyTransform; + tmp->clipRegion = info.rect; + } else if (info.clipType == QPainterClipInfo::RegionClip) { + tmp->dirtyFlags = QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyTransform; + tmp->clipRegion = info.region; + } else { // clipType == QPainterClipInfo::PathClip + tmp->dirtyFlags = QPaintEngine::DirtyClipPath | QPaintEngine::DirtyTransform; + tmp->clipPath = info.path; + } + d->engine->updateState(*tmp); + } + + + //Since we've updated the clip region anyway, pretend that the clip path hasn't changed: + d->state->dirtyFlags &= ~(QPaintEngine::DirtyClipPath | QPaintEngine::DirtyClipRegion); + tmp->changeFlags &= ~(QPaintEngine::DirtyClipPath | QPaintEngine::DirtyClipRegion); + tmp->changeFlags |= QPaintEngine::DirtyTransform; + } + + d->updateState(d->state); + delete tmp; +} + + +/*! + + \fn bool QPainter::begin(QPaintDevice *device) + + Begins painting the paint \a device and returns true if + successful; otherwise returns false. + + Notice that all painter settings (setPen(), setBrush() etc.) are reset + to default values when begin() is called. + + The errors that can occur are serious problems, such as these: + + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 3 + + Note that most of the time, you can use one of the constructors + instead of begin(), and that end() is automatically done at + destruction. + + \warning A paint device can only be painted by one painter at a + time. + + \warning Painting on a QImage with the format + QImage::Format_Indexed8 is not supported. + + \sa end(), QPainter() +*/ + +static inline void qt_cleanup_painter_state(QPainterPrivate *d) +{ + d->states.clear(); + delete d->state; + d->state = 0; + d->engine = 0; + d->device = 0; +} + +bool QPainter::begin(QPaintDevice *pd) +{ + Q_ASSERT(pd); + + if (pd->painters > 0) { + qWarning("QPainter::begin: A paint device can only be painted by one painter at a time."); + return false; + } + + if (d_ptr->engine) { + qWarning("QPainter::begin: Painter already active"); + return false; + } + + if (QPainterPrivate::attachPainterPrivate(this, pd)) + return true; + + Q_D(QPainter); + + d->helper_device = pd; + d->original_device = pd; + QPaintDevice *rpd = 0; + + QPoint redirectionOffset; + // We know for sure that redirection is broken when the widget is inside + // its paint event, so it's safe to use our hard-coded redirection. However, + // there IS one particular case we still need to support, and that's + // when people call QPainter::setRedirected in the widget's paint event right + // before any painter is created (or QPainter::begin is called). In that + // particular case our hard-coded redirection is restored and the redirection + // is retrieved from QPainter::redirected (as before). + if (pd->devType() == QInternal::Widget) + rpd = static_cast<QWidget *>(pd)->d_func()->redirected(&redirectionOffset); + + if (!rpd) + rpd = redirected(pd, &redirectionOffset); + + if (rpd) + pd = rpd; + +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::begin(), device=%p, type=%d\n", pd, pd->devType()); +#endif + + if (pd->devType() == QInternal::Pixmap) + static_cast<QPixmap *>(pd)->detach(); + else if (pd->devType() == QInternal::Image) + static_cast<QImage *>(pd)->detach(); + + d->engine = pd->paintEngine(); + + if (!d->engine) { + qWarning("QPainter::begin: Paint device returned engine == 0, type: %d", pd->devType()); + return false; + } + + d->device = pd; + + d->extended = d->engine->isExtended() ? static_cast<QPaintEngineEx *>(d->engine) : 0; + if (d->emulationEngine) + d->emulationEngine->real_engine = d->extended; + + // Setup new state... + Q_ASSERT(!d->state); + d->state = d->extended ? d->extended->createState(0) : new QPainterState; + d->state->painter = this; + d->states.push_back(d->state); + + d->state->redirectionMatrix.translate(-redirectionOffset.x(), -redirectionOffset.y()); + d->state->brushOrigin = QPointF(); + + // Slip a painter state into the engine before we do any other operations + if (d->extended) + d->extended->setState(d->state); + else + d->engine->state = d->state; + + switch (pd->devType()) { + case QInternal::Widget: + { + const QWidget *widget = static_cast<const QWidget *>(pd); + Q_ASSERT(widget); + + const bool paintOutsidePaintEvent = widget->testAttribute(Qt::WA_PaintOutsidePaintEvent); + const bool inPaintEvent = widget->testAttribute(Qt::WA_WState_InPaintEvent); + if(!d->engine->hasFeature(QPaintEngine::PaintOutsidePaintEvent) + && !paintOutsidePaintEvent && !inPaintEvent) { + qWarning("QPainter::begin: Widget painting can only begin as a " + "result of a paintEvent"); + qt_cleanup_painter_state(d); + return false; + } + + // Adjust offset for alien widgets painting outside the paint event. + if (!inPaintEvent && paintOutsidePaintEvent && !widget->internalWinId() + && widget->testAttribute(Qt::WA_WState_Created)) { + const QPoint offset = widget->mapTo(widget->nativeParentWidget(), QPoint()); + d->state->redirectionMatrix.translate(offset.x(), offset.y()); + } + break; + } + case QInternal::Pixmap: + { + QPixmap *pm = static_cast<QPixmap *>(pd); + Q_ASSERT(pm); + if (pm->isNull()) { + qWarning("QPainter::begin: Cannot paint on a null pixmap"); + qt_cleanup_painter_state(d); + return false; + } + + if (pm->depth() == 1) { + d->state->pen = QPen(Qt::color1); + d->state->brush = QBrush(Qt::color0); + } + break; + } + case QInternal::Image: + { + QImage *img = static_cast<QImage *>(pd); + Q_ASSERT(img); + if (img->isNull()) { + qWarning("QPainter::begin: Cannot paint on a null image"); + qt_cleanup_painter_state(d); + return false; + } else if (img->format() == QImage::Format_Indexed8) { + // Painting on indexed8 images is not supported. + qWarning("QPainter::begin: Cannot paint on an image with the QImage::Format_Indexed8 format"); + qt_cleanup_painter_state(d); + return false; + } + if (img->depth() == 1) { + d->state->pen = QPen(Qt::color1); + d->state->brush = QBrush(Qt::color0); + } + break; + } + default: + break; + } + if (d->state->ww == 0) // For compat with 3.x painter defaults + d->state->ww = d->state->wh = d->state->vw = d->state->vh = 1024; + + d->engine->setPaintDevice(pd); + + bool begun = d->engine->begin(pd); + if (!begun) { + qWarning("QPainter::begin(): Returned false"); + if (d->engine->isActive()) { + end(); + } else { + qt_cleanup_painter_state(d); + } + return false; + } else { + d->engine->setActive(begun); + } + + // Copy painter properties from original paint device, + // required for QPixmap::grabWidget() + if (d->original_device->devType() == QInternal::Widget) { + QWidget *widget = static_cast<QWidget *>(d->original_device); + initFrom(widget); + } else { + d->state->layoutDirection = Qt::LayoutDirectionAuto; + // make sure we have a font compatible with the paintdevice + d->state->deviceFont = d->state->font = QFont(d->state->deviceFont, device()); + } + + QRect systemRect = d->engine->systemRect(); + if (!systemRect.isEmpty()) { + d->state->ww = d->state->vw = systemRect.width(); + d->state->wh = d->state->vh = systemRect.height(); + } else { + d->state->ww = d->state->vw = pd->metric(QPaintDevice::PdmWidth); + d->state->wh = d->state->vh = pd->metric(QPaintDevice::PdmHeight); + } + + const QPoint coordinateOffset = d->engine->coordinateOffset(); + d->state->redirectionMatrix.translate(-coordinateOffset.x(), -coordinateOffset.y()); + + Q_ASSERT(d->engine->isActive()); + + if (!d->state->redirectionMatrix.isIdentity()) + d->updateMatrix(); + + Q_ASSERT(d->engine->isActive()); + d->state->renderHints = QPainter::TextAntialiasing; + ++d->device->painters; + + d->state->emulationSpecifier = 0; + + return true; +} + +/*! + Ends painting. Any resources used while painting are released. You + don't normally need to call this since it is called by the + destructor. + + Returns true if the painter is no longer active; otherwise returns false. + + \sa begin(), isActive() +*/ + +bool QPainter::end() +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::end()\n"); +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::end: Painter not active, aborted"); + qt_cleanup_painter_state(d); + return false; + } + + if (d->refcount > 1) { + d->detachPainterPrivate(this); + return true; + } + + bool ended = true; + + if (d->engine->isActive()) { + ended = d->engine->end(); + d->updateState(0); + + --d->device->painters; + if (d->device->painters == 0) { + d->engine->setPaintDevice(0); + d->engine->setActive(false); + } + } + + if (d->states.size() > 1) { + qWarning("QPainter::end: Painter ended with %d saved states", + d->states.size()); + } + + if (d->engine->autoDestruct()) { + delete d->engine; + } + + if (d->emulationEngine) { + delete d->emulationEngine; + d->emulationEngine = 0; + } + + if (d->extended) { + d->extended = 0; + } + + qt_cleanup_painter_state(d); + + return ended; +} + + +/*! + Returns the paint engine that the painter is currently operating + on if the painter is active; otherwise 0. + + \sa isActive() +*/ +QPaintEngine *QPainter::paintEngine() const +{ + Q_D(const QPainter); + return d->engine; +} + +/*! + \since 4.6 + + Flushes the painting pipeline and prepares for the user issuing commands + directly to the underlying graphics context. Must be followed by a call to + endNativePainting(). + + Note that only the states the underlying paint engine changes will be reset + to their respective default states. The states we reset may change from + release to release. The following states are currently reset in the OpenGL + 2 engine: + + \list + \i blending is disabled + \i the depth, stencil and scissor tests are disabled + \i the active texture unit is reset to 0 + \i the depth mask, depth function and the clear depth are reset to their + default values + \i the stencil mask, stencil operation and stencil function are reset to + their default values + \i the current color is reset to solid white + \endlist + + If, for example, the OpenGL polygon mode is changed by the user inside a + beginNativePaint()/endNativePainting() block, it will not be reset to the + default state by endNativePainting(). Here is an example that shows + intermixing of painter commands and raw OpenGL commands: + + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 21 + + \sa endNativePainting() +*/ +void QPainter::beginNativePainting() +{ + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::beginNativePainting: Painter not active"); + return; + } + + if (d->extended) + d->extended->beginNativePainting(); +} + +/*! + \since 4.6 + + Restores the painter after manually issuing native painting commands. Lets + the painter restore any native state that it relies on before calling any + other painter commands. + + \sa beginNativePainting() +*/ +void QPainter::endNativePainting() +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::beginNativePainting: Painter not active"); + return; + } + + if (d->extended) + d->extended->endNativePainting(); + else + d->engine->syncState(); +} + +/*! + Returns the font metrics for the painter if the painter is + active. Otherwise, the return value is undefined. + + \sa font(), isActive(), {QPainter#Settings}{Settings} +*/ + +QFontMetrics QPainter::fontMetrics() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::fontMetrics: Painter not active"); + return QFontMetrics(QFont()); + } + return QFontMetrics(d->state->font); +} + + +/*! + Returns the font info for the painter if the painter is + active. Otherwise, the return value is undefined. + + \sa font(), isActive(), {QPainter#Settings}{Settings} +*/ + +QFontInfo QPainter::fontInfo() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::fontInfo: Painter not active"); + return QFontInfo(QFont()); + } + return QFontInfo(d->state->font); +} + +/*! + \since 4.2 + + Returns the opacity of the painter. The default value is + 1. +*/ + +qreal QPainter::opacity() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::opacity: Painter not active"); + return 1.0; + } + return d->state->opacity; +} + +/*! + \since 4.2 + + Sets the opacity of the painter to \a opacity. The value should + be in the range 0.0 to 1.0, where 0.0 is fully transparent and + 1.0 is fully opaque. + + Opacity set on the painter will apply to all drawing operations + individually. +*/ + +void QPainter::setOpacity(qreal opacity) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setOpacity: Painter not active"); + return; + } + + opacity = qMin(qreal(1), qMax(qreal(0), opacity)); + + if (opacity == d->state->opacity) + return; + + d->state->opacity = opacity; + + if (d->extended) + d->extended->opacityChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyOpacity; +} + + +/*! + Returns the currently set brush origin. + + \sa setBrushOrigin(), {QPainter#Settings}{Settings} +*/ + +QPoint QPainter::brushOrigin() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::brushOrigin: Painter not active"); + return QPoint(); + } + return QPointF(d->state->brushOrigin).toPoint(); +} + +/*! + \fn void QPainter::setBrushOrigin(const QPointF &position) + + Sets the brush origin to \a position. + + The brush origin specifies the (0, 0) coordinate of the painter's + brush. + + Note that while the brushOrigin() was necessary to adopt the + parent's background for a widget in Qt 3, this is no longer the + case since the Qt 4 painter doesn't paint the background unless + you explicitly tell it to do so by setting the widget's \l + {QWidget::autoFillBackground}{autoFillBackground} property to + true. + + \sa brushOrigin(), {QPainter#Settings}{Settings} +*/ + +void QPainter::setBrushOrigin(const QPointF &p) +{ + Q_D(QPainter); +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setBrushOrigin(), (%.2f,%.2f)\n", p.x(), p.y()); +#endif + + if (!d->engine) { + qWarning("QPainter::setBrushOrigin: Painter not active"); + return; + } + + d->state->brushOrigin = p; + + if (d->extended) { + d->extended->brushOriginChanged(); + return; + } + + d->state->dirtyFlags |= QPaintEngine::DirtyBrushOrigin; +} + +/*! + \fn void QPainter::setBrushOrigin(const QPoint &position) + \overload + + Sets the brush's origin to the given \a position. +*/ + +/*! + \fn void QPainter::setBrushOrigin(int x, int y) + + \overload + + Sets the brush's origin to point (\a x, \a y). +*/ + +/*! + \enum QPainter::CompositionMode + + Defines the modes supported for digital image compositing. + Composition modes are used to specify how the pixels in one image, + the source, are merged with the pixel in another image, the + destination. + + Please note that the bitwise raster operation modes, denoted with + a RasterOp prefix, are only natively supported in the X11 and + raster paint engines. This means that the only way to utilize + these modes on the Mac is via a QImage. The RasterOp denoted blend + modes are \e not supported for pens and brushes with alpha + components. Also, turning on the QPainter::Antialiasing render + hint will effectively disable the RasterOp modes. + + + \image qpainter-compositionmode1.png + \image qpainter-compositionmode2.png + + The most common type is SourceOver (often referred to as just + alpha blending) where the source pixel is blended on top of the + destination pixel in such a way that the alpha component of the + source defines the translucency of the pixel. + + When the paint device is a QImage, the image format must be set to + \l {QImage::Format}{Format_ARGB32Premultiplied} or + \l {QImage::Format}{Format_ARGB32} for the composition modes to have + any effect. For performance the premultiplied version is the preferred + format. + + When a composition mode is set it applies to all painting + operator, pens, brushes, gradients and pixmap/image drawing. + + \value CompositionMode_SourceOver This is the default mode. The + alpha of the source is used to blend the pixel on top of the + destination. + + \value CompositionMode_DestinationOver The alpha of the + destination is used to blend it on top of the source pixels. This + mode is the inverse of CompositionMode_SourceOver. + + \value CompositionMode_Clear The pixels in the destination are + cleared (set to fully transparent) independent of the source. + + \value CompositionMode_Source The output is the source + pixel. (This means a basic copy operation and is identical to + SourceOver when the source pixel is opaque). + + \value CompositionMode_Destination The output is the destination + pixel. This means that the blending has no effect. This mode is + the inverse of CompositionMode_Source. + + \value CompositionMode_SourceIn The output is the source, where + the alpha is reduced by that of the destination. + + \value CompositionMode_DestinationIn The output is the + destination, where the alpha is reduced by that of the + source. This mode is the inverse of CompositionMode_SourceIn. + + \value CompositionMode_SourceOut The output is the source, where + the alpha is reduced by the inverse of destination. + + \value CompositionMode_DestinationOut The output is the + destination, where the alpha is reduced by the inverse of the + source. This mode is the inverse of CompositionMode_SourceOut. + + \value CompositionMode_SourceAtop The source pixel is blended on + top of the destination, with the alpha of the source pixel reduced + by the alpha of the destination pixel. + + \value CompositionMode_DestinationAtop The destination pixel is + blended on top of the source, with the alpha of the destination + pixel is reduced by the alpha of the destination pixel. This mode + is the inverse of CompositionMode_SourceAtop. + + \value CompositionMode_Xor The source, whose alpha is reduced with + the inverse of the destination alpha, is merged with the + destination, whose alpha is reduced by the inverse of the source + alpha. CompositionMode_Xor is not the same as the bitwise Xor. + + \value CompositionMode_Plus Both the alpha and color of the source + and destination pixels are added together. + + \value CompositionMode_Multiply The output is the source color + multiplied by the destination. Multiplying a color with white + leaves the color unchanged, while multiplying a color + with black produces black. + + \value CompositionMode_Screen The source and destination colors + are inverted and then multiplied. Screening a color with white + produces white, whereas screening a color with black leaves the + color unchanged. + + \value CompositionMode_Overlay Multiplies or screens the colors + depending on the destination color. The destination color is mixed + with the source color to reflect the lightness or darkness of the + destination. + + \value CompositionMode_Darken The darker of the source and + destination colors is selected. + + \value CompositionMode_Lighten The lighter of the source and + destination colors is selected. + + \value CompositionMode_ColorDodge The destination color is + brightened to reflect the source color. A black source color + leaves the destination color unchanged. + + \value CompositionMode_ColorBurn The destination color is darkened + to reflect the source color. A white source color leaves the + destination color unchanged. + + \value CompositionMode_HardLight Multiplies or screens the colors + depending on the source color. A light source color will lighten + the destination color, whereas a dark source color will darken the + destination color. + + \value CompositionMode_SoftLight Darkens or lightens the colors + depending on the source color. Similar to + CompositionMode_HardLight. + + \value CompositionMode_Difference Subtracts the darker of the + colors from the lighter. Painting with white inverts the + destination color, whereas painting with black leaves the + destination color unchanged. + + \value CompositionMode_Exclusion Similar to + CompositionMode_Difference, but with a lower contrast. Painting + with white inverts the destination color, whereas painting with + black leaves the destination color unchanged. + + \value RasterOp_SourceOrDestination Does a bitwise OR operation on + the source and destination pixels (src OR dst). + + \value RasterOp_SourceAndDestination Does a bitwise AND operation + on the source and destination pixels (src AND dst). + + \value RasterOp_SourceXorDestination Does a bitwise XOR operation + on the source and destination pixels (src XOR dst). + + \value RasterOp_NotSourceAndNotDestination Does a bitwise NOR + operation on the source and destination pixels ((NOT src) AND (NOT + dst)). + + \value RasterOp_NotSourceOrNotDestination Does a bitwise NAND + operation on the source and destination pixels ((NOT src) OR (NOT + dst)). + + \value RasterOp_NotSourceXorDestination Does a bitwise operation + where the source pixels are inverted and then XOR'ed with the + destination ((NOT src) XOR dst). + + \value RasterOp_NotSource Does a bitwise operation where the + source pixels are inverted (NOT src). + + \value RasterOp_NotSourceAndDestination Does a bitwise operation + where the source is inverted and then AND'ed with the destination + ((NOT src) AND dst). + + \value RasterOp_SourceAndNotDestination Does a bitwise operation + where the source is AND'ed with the inverted destination pixels + (src AND (NOT dst)). + + \sa compositionMode(), setCompositionMode(), {QPainter#Composition + Modes}{Composition Modes}, {Image Composition Example} +*/ + +/*! + Sets the composition mode to the given \a mode. + + \warning Only a QPainter operating on a QImage fully supports all + composition modes. The RasterOp modes are supported for X11 as + described in compositionMode(). + + \sa compositionMode() +*/ +void QPainter::setCompositionMode(CompositionMode mode) +{ + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setCompositionMode: Painter not active"); + return; + } + if (d->state->composition_mode == mode) + return; + if (d->extended) { + d->state->composition_mode = mode; + d->extended->compositionModeChanged(); + return; + } + + if (mode >= QPainter::RasterOp_SourceOrDestination) { + if (!d->engine->hasFeature(QPaintEngine::RasterOpModes)) { + qWarning("QPainter::setCompositionMode: " + "Raster operation modes not supported on device"); + return; + } + } else if (mode >= QPainter::CompositionMode_Plus) { + if (!d->engine->hasFeature(QPaintEngine::BlendModes)) { + qWarning("QPainter::setCompositionMode: " + "Blend modes not supported on device"); + return; + } + } else if (!d->engine->hasFeature(QPaintEngine::PorterDuff)) { + if (mode != CompositionMode_Source && mode != CompositionMode_SourceOver) { + qWarning("QPainter::setCompositionMode: " + "PorterDuff modes not supported on device"); + return; + } + } + + d->state->composition_mode = mode; + d->state->dirtyFlags |= QPaintEngine::DirtyCompositionMode; +} + +/*! + Returns the current composition mode. + + \sa CompositionMode, setCompositionMode() +*/ +QPainter::CompositionMode QPainter::compositionMode() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::compositionMode: Painter not active"); + return QPainter::CompositionMode_SourceOver; + } + return d->state->composition_mode; +} + +/*! + Returns the current background brush. + + \sa setBackground(), {QPainter#Settings}{Settings} +*/ + +const QBrush &QPainter::background() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::background: Painter not active"); + return d->fakeState()->brush; + } + return d->state->bgBrush; +} + + +/*! + Returns true if clipping has been set; otherwise returns false. + + \sa setClipping(), {QPainter#Clipping}{Clipping} +*/ + +bool QPainter::hasClipping() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::hasClipping: Painter not active"); + return false; + } + return d->state->clipEnabled && d->state->clipOperation != Qt::NoClip; +} + + +/*! + Enables clipping if \a enable is true, or disables clipping if \a + enable is false. + + \sa hasClipping(), {QPainter#Clipping}{Clipping} +*/ + +void QPainter::setClipping(bool enable) +{ + Q_D(QPainter); +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setClipping(), enable=%s, was=%s\n", + enable ? "on" : "off", + hasClipping() ? "on" : "off"); +#endif + if (!d->engine) { + qWarning("QPainter::setClipping: Painter not active, state will be reset by begin"); + return; + } + + if (hasClipping() == enable) + return; + + // we can't enable clipping if we don't have a clip + if (enable + && (d->state->clipInfo.isEmpty() || d->state->clipInfo.last().operation == Qt::NoClip)) + return; + d->state->clipEnabled = enable; + + if (d->extended) { + d->extended->clipEnabledChanged(); + return; + } + + d->state->dirtyFlags |= QPaintEngine::DirtyClipEnabled; + d->updateState(d->state); +} + + +/*! + Returns the currently set clip region. Note that the clip region + is given in logical coordinates. + + \warning QPainter does not store the combined clip explicitly as + this is handled by the underlying QPaintEngine, so the path is + recreated on demand and transformed to the current logical + coordinate system. This is potentially an expensive operation. + + \sa setClipRegion(), clipPath(), setClipping() +*/ + +QRegion QPainter::clipRegion() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::clipRegion: Painter not active"); + return QRegion(); + } + + QRegion region; + bool lastWasNothing = true; + + if (!d->txinv) + const_cast<QPainter *>(this)->d_ptr->updateInvMatrix(); + + // ### Falcon: Use QPainterPath + for (int i=0; i<d->state->clipInfo.size(); ++i) { + const QPainterClipInfo &info = d->state->clipInfo.at(i); + switch (info.clipType) { + + case QPainterClipInfo::RegionClip: { + QTransform matrix = (info.matrix * d->invMatrix); + if (lastWasNothing) { + region = info.region * matrix; + lastWasNothing = false; + continue; + } + if (info.operation == Qt::IntersectClip) + region &= info.region * matrix; + else if (info.operation == Qt::UniteClip) + region |= info.region * matrix; + else if (info.operation == Qt::NoClip) { + lastWasNothing = true; + region = QRegion(); + } else + region = info.region * matrix; + break; + } + + case QPainterClipInfo::PathClip: { + QTransform matrix = (info.matrix * d->invMatrix); + if (lastWasNothing) { + region = QRegion((info.path * matrix).toFillPolygon().toPolygon(), + info.path.fillRule()); + lastWasNothing = false; + continue; + } + if (info.operation == Qt::IntersectClip) { + region &= QRegion((info.path * matrix).toFillPolygon().toPolygon(), + info.path.fillRule()); + } else if (info.operation == Qt::UniteClip) { + region |= QRegion((info.path * matrix).toFillPolygon().toPolygon(), + info.path.fillRule()); + } else if (info.operation == Qt::NoClip) { + lastWasNothing = true; + region = QRegion(); + } else { + region = QRegion((info.path * matrix).toFillPolygon().toPolygon(), + info.path.fillRule()); + } + break; + } + + case QPainterClipInfo::RectClip: { + QTransform matrix = (info.matrix * d->invMatrix); + if (lastWasNothing) { + region = QRegion(info.rect) * matrix; + lastWasNothing = false; + continue; + } + if (info.operation == Qt::IntersectClip) { + // Use rect intersection if possible. + if (matrix.type() <= QTransform::TxScale) + region &= matrix.mapRect(info.rect); + else + region &= matrix.map(QRegion(info.rect)); + } else if (info.operation == Qt::UniteClip) { + region |= QRegion(info.rect) * matrix; + } else if (info.operation == Qt::NoClip) { + lastWasNothing = true; + region = QRegion(); + } else { + region = QRegion(info.rect) * matrix; + } + break; + } + + case QPainterClipInfo::RectFClip: { + QTransform matrix = (info.matrix * d->invMatrix); + if (lastWasNothing) { + region = QRegion(info.rectf.toRect()) * matrix; + lastWasNothing = false; + continue; + } + if (info.operation == Qt::IntersectClip) { + // Use rect intersection if possible. + if (matrix.type() <= QTransform::TxScale) + region &= matrix.mapRect(info.rectf.toRect()); + else + region &= matrix.map(QRegion(info.rectf.toRect())); + } else if (info.operation == Qt::UniteClip) { + region |= QRegion(info.rectf.toRect()) * matrix; + } else if (info.operation == Qt::NoClip) { + lastWasNothing = true; + region = QRegion(); + } else { + region = QRegion(info.rectf.toRect()) * matrix; + } + break; + } + } + } + + return region; +} + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +/*! + Returns the currently clip as a path. Note that the clip path is + given in logical coordinates. + + \warning QPainter does not store the combined clip explicitly as + this is handled by the underlying QPaintEngine, so the path is + recreated on demand and transformed to the current logical + coordinate system. This is potentially an expensive operation. + + \sa setClipPath(), clipRegion(), setClipping() +*/ +QPainterPath QPainter::clipPath() const +{ + Q_D(const QPainter); + + // ### Since we do not support path intersections and path unions yet, + // we just use clipRegion() here... + if (!d->engine) { + qWarning("QPainter::clipPath: Painter not active"); + return QPainterPath(); + } + + // No clip, return empty + if (d->state->clipInfo.size() == 0) { + return QPainterPath(); + } else { + + // Update inverse matrix, used below. + if (!d->txinv) + const_cast<QPainter *>(this)->d_ptr->updateInvMatrix(); + + // For the simple case avoid conversion. + if (d->state->clipInfo.size() == 1 + && d->state->clipInfo.at(0).clipType == QPainterClipInfo::PathClip) { + QTransform matrix = (d->state->clipInfo.at(0).matrix * d->invMatrix); + return d->state->clipInfo.at(0).path * matrix; + + } else if (d->state->clipInfo.size() == 1 + && d->state->clipInfo.at(0).clipType == QPainterClipInfo::RectClip) { + QTransform matrix = (d->state->clipInfo.at(0).matrix * d->invMatrix); + QPainterPath path; + path.addRect(d->state->clipInfo.at(0).rect); + return path * matrix; + } else { + // Fallback to clipRegion() for now, since we don't have isect/unite for paths + return qt_regionToPath(clipRegion()); + } + } +} + +/*! + Returns the bounding rectangle of the current clip if there is a clip; + otherwise returns an empty rectangle. Note that the clip region is + given in logical coordinates. + + The bounding rectangle is not guaranteed to be tight. + + \sa setClipRect(), setClipPath(), setClipRegion() + + \since 4.8 + */ + +QRectF QPainter::clipBoundingRect() const +{ + Q_D(const QPainter); + + if (!d->engine) { + qWarning("QPainter::clipBoundingRect: Painter not active"); + return QRectF(); + } + + // Accumulate the bounding box in device space. This is not 100% + // precise, but it fits within the guarantee and it is resonably + // fast. + QRectF bounds; + for (int i=0; i<d->state->clipInfo.size(); ++i) { + QRectF r; + const QPainterClipInfo &info = d->state->clipInfo.at(i); + + if (info.clipType == QPainterClipInfo::RectClip) + r = info.rect; + else if (info.clipType == QPainterClipInfo::RectFClip) + r = info.rectf; + else if (info.clipType == QPainterClipInfo::RegionClip) + r = info.region.boundingRect(); + else + r = info.path.boundingRect(); + + r = info.matrix.mapRect(r); + + if (i == 0) + bounds = r; + else if (info.operation == Qt::IntersectClip) + bounds &= r; + else if (info.operation == Qt::UniteClip) + bounds |= r; + } + + + // Map the rectangle back into logical space using the inverse + // matrix. + if (!d->txinv) + const_cast<QPainter *>(this)->d_ptr->updateInvMatrix(); + + return d->invMatrix.mapRect(bounds); +} + +/*! + \fn void QPainter::setClipRect(const QRectF &rectangle, Qt::ClipOperation operation) + + Enables clipping, and sets the clip region to the given \a + rectangle using the given clip \a operation. The default operation + is to replace the current clip rectangle. + + Note that the clip rectangle is specified in logical (painter) + coordinates. + + \sa clipRegion(), setClipping(), {QPainter#Clipping}{Clipping} +*/ +void QPainter::setClipRect(const QRectF &rect, Qt::ClipOperation op) +{ + Q_D(QPainter); + + if (d->extended) { + if ((!d->state->clipEnabled && op != Qt::NoClip) || (d->state->clipOperation == Qt::NoClip && op == Qt::UniteClip)) + op = Qt::ReplaceClip; + + if (!d->engine) { + qWarning("QPainter::setClipRect: Painter not active"); + return; + } + qreal right = rect.x() + rect.width(); + qreal bottom = rect.y() + rect.height(); + qreal pts[] = { rect.x(), rect.y(), + right, rect.y(), + right, bottom, + rect.x(), bottom }; + QVectorPath vp(pts, 4, 0, QVectorPath::RectangleHint); + d->state->clipEnabled = true; + d->extended->clip(vp, op); + if (op == Qt::ReplaceClip || op == Qt::NoClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(rect, op, d->state->matrix); + d->state->clipOperation = op; + return; + } + + if (qreal(int(rect.top())) == rect.top() + && qreal(int(rect.bottom())) == rect.bottom() + && qreal(int(rect.left())) == rect.left() + && qreal(int(rect.right())) == rect.right()) + { + setClipRect(rect.toRect(), op); + return; + } + + if (rect.isEmpty()) { + setClipRegion(QRegion(), op); + return; + } + + QPainterPath path; + path.addRect(rect); + setClipPath(path, op); +} + +/*! + \fn void QPainter::setClipRect(const QRect &rectangle, Qt::ClipOperation operation) + \overload + + Enables clipping, and sets the clip region to the given \a rectangle using the given + clip \a operation. +*/ +void QPainter::setClipRect(const QRect &rect, Qt::ClipOperation op) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setClipRect: Painter not active"); + return; + } + + if ((!d->state->clipEnabled && op != Qt::NoClip) || (d->state->clipOperation == Qt::NoClip && op == Qt::UniteClip)) + op = Qt::ReplaceClip; + + if (d->extended) { + d->state->clipEnabled = true; + d->extended->clip(rect, op); + if (op == Qt::ReplaceClip || op == Qt::NoClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(rect, op, d->state->matrix); + d->state->clipOperation = op; + return; + } + + d->state->clipRegion = rect; + d->state->clipOperation = op; + if (op == Qt::NoClip || op == Qt::ReplaceClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(rect, op, d->state->matrix); + d->state->clipEnabled = true; + d->state->dirtyFlags |= QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyClipEnabled; + d->updateState(d->state); +} + +/*! + \fn void QPainter::setClipRect(int x, int y, int width, int height, Qt::ClipOperation operation) + + Enables clipping, and sets the clip region to the rectangle beginning at (\a x, \a y) + with the given \a width and \a height. +*/ + +/*! + \fn void QPainter::setClipRegion(const QRegion ®ion, Qt::ClipOperation operation) + + Sets the clip region to the given \a region using the specified clip + \a operation. The default clip operation is to replace the current + clip region. + + Note that the clip region is given in logical coordinates. + + \sa clipRegion(), setClipRect(), {QPainter#Clipping}{Clipping} +*/ +void QPainter::setClipRegion(const QRegion &r, Qt::ClipOperation op) +{ + Q_D(QPainter); +#ifdef QT_DEBUG_DRAW + QRect rect = r.boundingRect(); + if (qt_show_painter_debug_output) + printf("QPainter::setClipRegion(), size=%d, [%d,%d,%d,%d]\n", + r.rects().size(), rect.x(), rect.y(), rect.width(), rect.height()); +#endif + if (!d->engine) { + qWarning("QPainter::setClipRegion: Painter not active"); + return; + } + + if ((!d->state->clipEnabled && op != Qt::NoClip) || (d->state->clipOperation == Qt::NoClip && op == Qt::UniteClip)) + op = Qt::ReplaceClip; + + if (d->extended) { + d->state->clipEnabled = true; + d->extended->clip(r, op); + if (op == Qt::NoClip || op == Qt::ReplaceClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(r, op, d->state->matrix); + d->state->clipOperation = op; + return; + } + + d->state->clipRegion = r; + d->state->clipOperation = op; + if (op == Qt::NoClip || op == Qt::ReplaceClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(r, op, d->state->matrix); + d->state->clipEnabled = true; + d->state->dirtyFlags |= QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyClipEnabled; + d->updateState(d->state); +} + +/*! + \since 4.2 + \obsolete + + Sets the transformation matrix to \a matrix and enables transformations. + + \note It is advisable to use setWorldTransform() instead of this function to + preserve the properties of perspective transformations. + + If \a combine is true, then \a matrix is combined with the current + transformation matrix; otherwise \a matrix replaces the current + transformation matrix. + + If \a matrix is the identity matrix and \a combine is false, this + function calls setWorldMatrixEnabled(false). (The identity matrix is the + matrix where QMatrix::m11() and QMatrix::m22() are 1.0 and the + rest are 0.0.) + + The following functions can transform the coordinate system without using + a QMatrix: + \list + \i translate() + \i scale() + \i shear() + \i rotate() + \endlist + + They operate on the painter's worldMatrix() and are implemented like this: + + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 4 + + Note that when using setWorldMatrix() function you should always have + \a combine be true when you are drawing into a QPicture. Otherwise + it may not be possible to replay the picture with additional + transformations; using the translate(), scale(), etc. convenience + functions is safe. + + For more information about the coordinate system, transformations + and window-viewport conversion, see \l {Coordinate System}. + + \sa setWorldTransform(), QTransform +*/ + +void QPainter::setWorldMatrix(const QMatrix &matrix, bool combine) +{ + setWorldTransform(QTransform(matrix), combine); +} + +/*! + \since 4.2 + \obsolete + + Returns the world transformation matrix. + + It is advisable to use worldTransform() because worldMatrix() does not + preserve the properties of perspective transformations. + + \sa {QPainter#Coordinate Transformations}{Coordinate Transformations}, + {Coordinate System} +*/ + +const QMatrix &QPainter::worldMatrix() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::worldMatrix: Painter not active"); + return d->fakeState()->transform.toAffine(); + } + return d->state->worldMatrix.toAffine(); +} + +/*! + \obsolete + + Use setWorldTransform() instead. + + \sa setWorldTransform() +*/ + +void QPainter::setMatrix(const QMatrix &matrix, bool combine) +{ + setWorldTransform(QTransform(matrix), combine); +} + +/*! + \obsolete + + Use worldTransform() instead. + + \sa worldTransform() +*/ + +const QMatrix &QPainter::matrix() const +{ + return worldMatrix(); +} + + +/*! + \since 4.2 + \obsolete + + Returns the transformation matrix combining the current + window/viewport and world transformation. + + It is advisable to use combinedTransform() instead of this + function to preserve the properties of perspective transformations. + + \sa setWorldTransform(), setWindow(), setViewport() +*/ +QMatrix QPainter::combinedMatrix() const +{ + return combinedTransform().toAffine(); +} + + +/*! + \obsolete + + Returns the matrix that transforms from logical coordinates to + device coordinates of the platform dependent paint device. + + \note It is advisable to use deviceTransform() instead of this + function to preserve the properties of perspective transformations. + + This function is \e only needed when using platform painting + commands on the platform dependent handle (Qt::HANDLE), and the + platform does not do transformations nativly. + + The QPaintEngine::PaintEngineFeature enum can be queried to + determine whether the platform performs the transformations or + not. + + \sa worldMatrix(), QPaintEngine::hasFeature(), +*/ +const QMatrix &QPainter::deviceMatrix() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::deviceMatrix: Painter not active"); + return d->fakeState()->transform.toAffine(); + } + return d->state->matrix.toAffine(); +} + +/*! + \obsolete + + Resets any transformations that were made using translate(), scale(), + shear(), rotate(), setWorldMatrix(), setViewport() and + setWindow(). + + It is advisable to use resetTransform() instead of this function + to preserve the properties of perspective transformations. + + \sa {QPainter#Coordinate Transformations}{Coordinate + Transformations} +*/ + +void QPainter::resetMatrix() +{ + resetTransform(); +} + + +/*! + \since 4.2 + + Enables transformations if \a enable is true, or disables + transformations if \a enable is false. The world transformation + matrix is not changed. + + \sa worldMatrixEnabled(), worldTransform(), {QPainter#Coordinate + Transformations}{Coordinate Transformations} +*/ + +void QPainter::setWorldMatrixEnabled(bool enable) +{ + Q_D(QPainter); +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setMatrixEnabled(), enable=%d\n", enable); +#endif + + if (!d->engine) { + qWarning("QPainter::setMatrixEnabled: Painter not active"); + return; + } + if (enable == d->state->WxF) + return; + + d->state->WxF = enable; + d->updateMatrix(); +} + +/*! + \since 4.2 + + Returns true if world transformation is enabled; otherwise returns + false. + + \sa setWorldMatrixEnabled(), worldTransform(), {Coordinate System} +*/ + +bool QPainter::worldMatrixEnabled() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::worldMatrixEnabled: Painter not active"); + return false; + } + return d->state->WxF; +} + +/*! + \obsolete + + Use setWorldMatrixEnabled() instead. + + \sa setWorldMatrixEnabled() +*/ + +void QPainter::setMatrixEnabled(bool enable) +{ + setWorldMatrixEnabled(enable); +} + +/*! + \obsolete + + Use worldMatrixEnabled() instead + + \sa worldMatrixEnabled() +*/ + +bool QPainter::matrixEnabled() const +{ + return worldMatrixEnabled(); +} + +/*! + Scales the coordinate system by (\a{sx}, \a{sy}). + + \sa setWorldTransform() {QPainter#Coordinate Transformations}{Coordinate + Transformations} +*/ + +void QPainter::scale(qreal sx, qreal sy) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::scale(), sx=%f, sy=%f\n", sx, sy); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::scale: Painter not active"); + return; + } + + d->state->worldMatrix.scale(sx,sy); + d->state->WxF = true; + d->updateMatrix(); +} + +/*! + Shears the coordinate system by (\a{sh}, \a{sv}). + + \sa setWorldTransform(), {QPainter#Coordinate Transformations}{Coordinate + Transformations} +*/ + +void QPainter::shear(qreal sh, qreal sv) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::shear(), sh=%f, sv=%f\n", sh, sv); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::shear: Painter not active"); + return; + } + + d->state->worldMatrix.shear(sh, sv); + d->state->WxF = true; + d->updateMatrix(); +} + +/*! + \fn void QPainter::rotate(qreal angle) + + Rotates the coordinate system the given \a angle clockwise. + + \sa setWorldTransform(), {QPainter#Coordinate Transformations}{Coordinate + Transformations} +*/ + +void QPainter::rotate(qreal a) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::rotate(), angle=%f\n", a); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::rotate: Painter not active"); + return; + } + + d->state->worldMatrix.rotate(a); + d->state->WxF = true; + d->updateMatrix(); +} + +/*! + Translates the coordinate system by the given \a offset; i.e. the + given \a offset is added to points. + + \sa setWorldTransform(), {QPainter#Coordinate Transformations}{Coordinate + Transformations} +*/ +void QPainter::translate(const QPointF &offset) +{ + qreal dx = offset.x(); + qreal dy = offset.y(); +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::translate(), dx=%f, dy=%f\n", dx, dy); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::translate: Painter not active"); + return; + } + + d->state->worldMatrix.translate(dx, dy); + d->state->WxF = true; + d->updateMatrix(); +} + +/*! + \fn void QPainter::translate(const QPoint &offset) + \overload + + Translates the coordinate system by the given \a offset. +*/ + +/*! + \fn void QPainter::translate(qreal dx, qreal dy) + \overload + + Translates the coordinate system by the vector (\a dx, \a dy). +*/ + +/*! + \fn void QPainter::setClipPath(const QPainterPath &path, Qt::ClipOperation operation) + + Enables clipping, and sets the clip path for the painter to the + given \a path, with the clip \a operation. + + Note that the clip path is specified in logical (painter) + coordinates. + + \sa clipPath(), clipRegion(), {QPainter#Clipping}{Clipping} + +*/ +void QPainter::setClipPath(const QPainterPath &path, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) { + QRectF b = path.boundingRect(); + printf("QPainter::setClipPath(), size=%d, op=%d, bounds=[%.2f,%.2f,%.2f,%.2f]\n", + path.elementCount(), op, b.x(), b.y(), b.width(), b.height()); + } +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setClipPath: Painter not active"); + return; + } + + if ((!d->state->clipEnabled && op != Qt::NoClip) || (d->state->clipOperation == Qt::NoClip && op == Qt::UniteClip)) + op = Qt::ReplaceClip; + + if (d->extended) { + d->state->clipEnabled = true; + d->extended->clip(path, op); + if (op == Qt::NoClip || op == Qt::ReplaceClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(path, op, d->state->matrix); + d->state->clipOperation = op; + return; + } + + d->state->clipPath = path; + d->state->clipOperation = op; + if (op == Qt::NoClip || op == Qt::ReplaceClip) + d->state->clipInfo.clear(); + d->state->clipInfo << QPainterClipInfo(path, op, d->state->matrix); + d->state->clipEnabled = true; + d->state->dirtyFlags |= QPaintEngine::DirtyClipPath | QPaintEngine::DirtyClipEnabled; + d->updateState(d->state); +} + +/*! + Draws the outline (strokes) the path \a path with the pen specified + by \a pen + + \sa fillPath(), {QPainter#Drawing}{Drawing} +*/ +void QPainter::strokePath(const QPainterPath &path, const QPen &pen) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::strokePath: Painter not active"); + return; + } + + if (path.isEmpty()) + return; + + if (d->extended) { + const QGradient *g = qpen_brush(pen).gradient(); + if (!g || g->coordinateMode() == QGradient::LogicalMode) { + d->extended->stroke(qtVectorPathForPath(path), pen); + return; + } + } + + QBrush oldBrush = d->state->brush; + QPen oldPen = d->state->pen; + + setPen(pen); + setBrush(Qt::NoBrush); + + drawPath(path); + + // Reset old state + setPen(oldPen); + setBrush(oldBrush); +} + +/*! + Fills the given \a path using the given \a brush. The outline is + not drawn. + + Alternatively, you can specify a QColor instead of a QBrush; the + QBrush constructor (taking a QColor argument) will automatically + create a solid pattern brush. + + \sa drawPath() +*/ +void QPainter::fillPath(const QPainterPath &path, const QBrush &brush) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::fillPath: Painter not active"); + return; + } + + if (path.isEmpty()) + return; + + if (d->extended) { + const QGradient *g = brush.gradient(); + if (!g || g->coordinateMode() == QGradient::LogicalMode) { + d->extended->fill(qtVectorPathForPath(path), brush); + return; + } + } + + QBrush oldBrush = d->state->brush; + QPen oldPen = d->state->pen; + + setPen(Qt::NoPen); + setBrush(brush); + + drawPath(path); + + // Reset old state + setPen(oldPen); + setBrush(oldBrush); +} + +/*! + Draws the given painter \a path using the current pen for outline + and the current brush for filling. + + \table 100% + \row + \o \inlineimage qpainter-path.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 5 + \endtable + + \sa {painting/painterpaths}{the Painter Paths + example},{demos/deform}{the Vector Deformation demo} +*/ +void QPainter::drawPath(const QPainterPath &path) +{ +#ifdef QT_DEBUG_DRAW + QRectF pathBounds = path.boundingRect(); + if (qt_show_painter_debug_output) + printf("QPainter::drawPath(), size=%d, [%.2f,%.2f,%.2f,%.2f]\n", + path.elementCount(), + pathBounds.x(), pathBounds.y(), pathBounds.width(), pathBounds.height()); +#endif + + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::drawPath: Painter not active"); + return; + } + + if (d->extended) { + d->extended->drawPath(path); + return; + } + d->updateState(d->state); + + if (d->engine->hasFeature(QPaintEngine::PainterPaths) && d->state->emulationSpecifier == 0) { + d->engine->drawPath(path); + } else { + d->draw_helper(path); + } +} + +/*! + \fn void QPainter::drawLine(const QLineF &line) + + Draws a line defined by \a line. + + \table 100% + \row + \o \inlineimage qpainter-line.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 6 + \endtable + + \sa drawLines(), drawPolyline(), {Coordinate System} +*/ + +/*! + \fn void QPainter::drawLine(const QLine &line) + \overload + + Draws a line defined by \a line. +*/ + +/*! + \fn void QPainter::drawLine(const QPoint &p1, const QPoint &p2) + \overload + + Draws a line from \a p1 to \a p2. +*/ + +/*! + \fn void QPainter::drawLine(const QPointF &p1, const QPointF &p2) + \overload + + Draws a line from \a p1 to \a p2. +*/ + +/*! + \fn void QPainter::drawLine(int x1, int y1, int x2, int y2) + \overload + + Draws a line from (\a x1, \a y1) to (\a x2, \a y2) and sets the + current pen position to (\a x2, \a y2). +*/ + +/*! + \fn void QPainter::drawRect(const QRectF &rectangle) + + Draws the current \a rectangle with the current pen and brush. + + A filled rectangle has a size of \a{rectangle}.size(). A stroked + rectangle has a size of \a{rectangle}.size() plus the pen width. + + \table 100% + \row + \o \inlineimage qpainter-rectangle.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 7 + \endtable + + \sa drawRects(), drawPolygon(), {Coordinate System} +*/ + +/*! + \fn void QPainter::drawRect(const QRect &rectangle) + + \overload + + Draws the current \a rectangle with the current pen and brush. +*/ + +/*! + \fn void QPainter::drawRect(int x, int y, int width, int height) + + \overload + + Draws a rectangle with upper left corner at (\a{x}, \a{y}) and + with the given \a width and \a height. +*/ + +/*! + \fn void QPainter::drawRects(const QRectF *rectangles, int rectCount) + + Draws the first \a rectCount of the given \a rectangles using the + current pen and brush. + + \sa drawRect() +*/ +void QPainter::drawRects(const QRectF *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawRects(), count=%d\n", rectCount); +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::drawRects: Painter not active"); + return; + } + + if (rectCount <= 0) + return; + + if (d->extended) { + d->extended->drawRects(rects, rectCount); + return; + } + + d->updateState(d->state); + + if (!d->state->emulationSpecifier) { + d->engine->drawRects(rects, rectCount); + return; + } + + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + for (int i=0; i<rectCount; ++i) { + QRectF r(rects[i].x() + d->state->matrix.dx(), + rects[i].y() + d->state->matrix.dy(), + rects[i].width(), + rects[i].height()); + d->engine->drawRects(&r, 1); + } + } else { + if (d->state->brushNeedsResolving() || d->state->penNeedsResolving()) { + for (int i=0; i<rectCount; ++i) { + QPainterPath rectPath; + rectPath.addRect(rects[i]); + d->draw_helper(rectPath, QPainterPrivate::StrokeAndFillDraw); + } + } else { + QPainterPath rectPath; + for (int i=0; i<rectCount; ++i) + rectPath.addRect(rects[i]); + d->draw_helper(rectPath, QPainterPrivate::StrokeAndFillDraw); + } + } +} + +/*! + \fn void QPainter::drawRects(const QRect *rectangles, int rectCount) + \overload + + Draws the first \a rectCount of the given \a rectangles using the + current pen and brush. +*/ +void QPainter::drawRects(const QRect *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawRects(), count=%d\n", rectCount); +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::drawRects: Painter not active"); + return; + } + + if (rectCount <= 0) + return; + + if (d->extended) { + d->extended->drawRects(rects, rectCount); + return; + } + + d->updateState(d->state); + + if (!d->state->emulationSpecifier) { + d->engine->drawRects(rects, rectCount); + return; + } + + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + for (int i=0; i<rectCount; ++i) { + QRectF r(rects[i].x() + d->state->matrix.dx(), + rects[i].y() + d->state->matrix.dy(), + rects[i].width(), + rects[i].height()); + + d->engine->drawRects(&r, 1); + } + } else { + if (d->state->brushNeedsResolving() || d->state->penNeedsResolving()) { + for (int i=0; i<rectCount; ++i) { + QPainterPath rectPath; + rectPath.addRect(rects[i]); + d->draw_helper(rectPath, QPainterPrivate::StrokeAndFillDraw); + } + } else { + QPainterPath rectPath; + for (int i=0; i<rectCount; ++i) + rectPath.addRect(rects[i]); + + d->draw_helper(rectPath, QPainterPrivate::StrokeAndFillDraw); + } + } +} + +/*! + \fn void QPainter::drawRects(const QVector<QRectF> &rectangles) + \overload + + Draws the given \a rectangles using the current pen and brush. +*/ + +/*! + \fn void QPainter::drawRects(const QVector<QRect> &rectangles) + + \overload + + Draws the given \a rectangles using the current pen and brush. +*/ + +/*! + \fn void QPainter::drawPoint(const QPointF &position) + + Draws a single point at the given \a position using the current + pen's color. + + \sa {Coordinate System} +*/ + +/*! + \fn void QPainter::drawPoint(const QPoint &position) + \overload + + Draws a single point at the given \a position using the current + pen's color. +*/ + +/*! \fn void QPainter::drawPoint(int x, int y) + + \overload + + Draws a single point at position (\a x, \a y). +*/ + +/*! + Draws the first \a pointCount points in the array \a points using + the current pen's color. + + \sa {Coordinate System} +*/ +void QPainter::drawPoints(const QPointF *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPoints(), count=%d\n", pointCount); +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::drawPoints: Painter not active"); + return; + } + + if (pointCount <= 0) + return; + + if (d->extended) { + d->extended->drawPoints(points, pointCount); + return; + } + + d->updateState(d->state); + + if (!d->state->emulationSpecifier) { + d->engine->drawPoints(points, pointCount); + return; + } + + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + // ### use drawPoints function + for (int i=0; i<pointCount; ++i) { + QPointF pt(points[i].x() + d->state->matrix.dx(), + points[i].y() + d->state->matrix.dy()); + d->engine->drawPoints(&pt, 1); + } + } else { + QPen pen = d->state->pen; + bool flat_pen = pen.capStyle() == Qt::FlatCap; + if (flat_pen) { + save(); + pen.setCapStyle(Qt::SquareCap); + setPen(pen); + } + QPainterPath path; + for (int i=0; i<pointCount; ++i) { + path.moveTo(points[i].x(), points[i].y()); + path.lineTo(points[i].x() + 0.0001, points[i].y()); + } + d->draw_helper(path, QPainterPrivate::StrokeDraw); + if (flat_pen) + restore(); + } +} + +/*! + \overload + + Draws the first \a pointCount points in the array \a points using + the current pen's color. +*/ + +void QPainter::drawPoints(const QPoint *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPoints(), count=%d\n", pointCount); +#endif + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::drawPoints: Painter not active"); + return; + } + + if (pointCount <= 0) + return; + + if (d->extended) { + d->extended->drawPoints(points, pointCount); + return; + } + + d->updateState(d->state); + + if (!d->state->emulationSpecifier) { + d->engine->drawPoints(points, pointCount); + return; + } + + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + // ### use drawPoints function + for (int i=0; i<pointCount; ++i) { + QPointF pt(points[i].x() + d->state->matrix.dx(), + points[i].y() + d->state->matrix.dy()); + d->engine->drawPoints(&pt, 1); + } + } else { + QPen pen = d->state->pen; + bool flat_pen = (pen.capStyle() == Qt::FlatCap); + if (flat_pen) { + save(); + pen.setCapStyle(Qt::SquareCap); + setPen(pen); + } + QPainterPath path; + for (int i=0; i<pointCount; ++i) { + path.moveTo(points[i].x(), points[i].y()); + path.lineTo(points[i].x() + 0.0001, points[i].y()); + } + d->draw_helper(path, QPainterPrivate::StrokeDraw); + if (flat_pen) + restore(); + } +} + +/*! + \fn void QPainter::drawPoints(const QPolygonF &points) + + \overload + + Draws the points in the vector \a points. +*/ + +/*! + \fn void QPainter::drawPoints(const QPolygon &points) + + \overload + + Draws the points in the vector \a points. +*/ + +/*! + \fn void QPainter::drawPoints(const QPolygon &polygon, int index, + int count) + + \overload + \compat + + Draws \a count points in the vector \a polygon starting on \a index + using the current pen. + + Use drawPoints() combined with QPolygon::constData() instead. + + \oldcode + QPainter painter(this); + painter.drawPoints(polygon, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + + QPainter painter(this); + painter.drawPoints(polygon.constData() + index, pointCount); + \endcode +*/ + +/*! + Sets the background mode of the painter to the given \a mode + + Qt::TransparentMode (the default) draws stippled lines and text + without setting the background pixels. Qt::OpaqueMode fills these + space with the current background color. + + Note that in order to draw a bitmap or pixmap transparently, you + must use QPixmap::setMask(). + + \sa backgroundMode(), setBackground(), + {QPainter#Settings}{Settings} +*/ + +void QPainter::setBackgroundMode(Qt::BGMode mode) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setBackgroundMode(), mode=%d\n", mode); +#endif + + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setBackgroundMode: Painter not active"); + return; + } + if (d->state->bgMode == mode) + return; + + d->state->bgMode = mode; + if (d->extended) { + d->checkEmulation(); + } else { + d->state->dirtyFlags |= QPaintEngine::DirtyBackgroundMode; + } +} + +/*! + Returns the current background mode. + + \sa setBackgroundMode(), {QPainter#Settings}{Settings} +*/ +Qt::BGMode QPainter::backgroundMode() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::backgroundMode: Painter not active"); + return Qt::TransparentMode; + } + return d->state->bgMode; +} + + +/*! + \overload + + Sets the painter's pen to have style Qt::SolidLine, width 0 and the + specified \a color. +*/ + +void QPainter::setPen(const QColor &color) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setPen(), color=%04x\n", color.rgb()); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setPen: Painter not active"); + return; + } + + if (d->state->pen.style() == Qt::SolidLine + && d->state->pen.widthF() == 0 + && d->state->pen.isSolid() + && d->state->pen.color() == color) + return; + + QPen pen(color.isValid() ? color : QColor(Qt::black), 0, Qt::SolidLine); + + d->state->pen = pen; + if (d->extended) + d->extended->penChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyPen; +} + +/*! + Sets the painter's pen to be the given \a pen. + + The \a pen defines how to draw lines and outlines, and it also + defines the text color. + + \sa pen(), {QPainter#Settings}{Settings} +*/ + +void QPainter::setPen(const QPen &pen) +{ + +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setPen(), color=%04x, (brushStyle=%d) style=%d, cap=%d, join=%d\n", + pen.color().rgb(), pen.brush().style(), pen.style(), pen.capStyle(), pen.joinStyle()); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setPen: Painter not active"); + return; + } + + if (d->state->pen == pen) + return; + + d->state->pen = pen; + + if (d->extended) { + d->checkEmulation(); + d->extended->penChanged(); + return; + } + + d->state->dirtyFlags |= QPaintEngine::DirtyPen; +} + +/*! + \overload + + Sets the painter's pen to have the given \a style, width 0 and + black color. +*/ + +void QPainter::setPen(Qt::PenStyle style) +{ + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setPen: Painter not active"); + return; + } + + if (d->state->pen.style() == style + && (style == Qt::NoPen || (d->state->pen.widthF() == 0 + && d->state->pen.isSolid() + && d->state->pen.color() == QColor(Qt::black)))) + return; + + // QPen(Qt::NoPen) is to avoid creating QPenData, including its brush (from the color) + // Note that this works well as long as QPen(Qt::NoPen) returns a black, zero-width pen + d->state->pen = (style == Qt::NoPen) ? QPen(Qt::NoPen) : QPen(Qt::black, 0, style); + + if (d->extended) + d->extended->penChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyPen; + +} + +/*! + Returns the painter's current pen. + + \sa setPen(), {QPainter#Settings}{Settings} +*/ + +const QPen &QPainter::pen() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::pen: Painter not active"); + return d->fakeState()->pen; + } + return d->state->pen; +} + + +/*! + Sets the painter's brush to the given \a brush. + + The painter's brush defines how shapes are filled. + + \sa brush(), {QPainter#Settings}{Settings} +*/ + +void QPainter::setBrush(const QBrush &brush) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setBrush(), color=%04x, style=%d\n", brush.color().rgb(), brush.style()); +#endif + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setBrush: Painter not active"); + return; + } + + if (d->state->brush.d == brush.d) + return; + + if (d->extended) { + d->state->brush = brush; + d->checkEmulation(); + d->extended->brushChanged(); + return; + } + + d->state->brush = brush; + d->state->dirtyFlags |= QPaintEngine::DirtyBrush; +} + + +/*! + \overload + + Sets the painter's brush to black color and the specified \a + style. +*/ + +void QPainter::setBrush(Qt::BrushStyle style) +{ + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setBrush: Painter not active"); + return; + } + if (d->state->brush.style() == style && + (style == Qt::NoBrush + || (style == Qt::SolidPattern && d->state->brush.color() == QColor(0, 0, 0)))) + return; + d->state->brush = QBrush(Qt::black, style); + if (d->extended) + d->extended->brushChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyBrush; +} + +/*! + Returns the painter's current brush. + + \sa QPainter::setBrush(), {QPainter#Settings}{Settings} +*/ + +const QBrush &QPainter::brush() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::brush: Painter not active"); + return d->fakeState()->brush; + } + return d->state->brush; +} + +/*! + \fn void QPainter::setBackground(const QBrush &brush) + + Sets the background brush of the painter to the given \a brush. + + The background brush is the brush that is filled in when drawing + opaque text, stippled lines and bitmaps. The background brush has + no effect in transparent background mode (which is the default). + + \sa background(), setBackgroundMode(), + {QPainter#Settings}{Settings} +*/ + +void QPainter::setBackground(const QBrush &bg) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setBackground(), color=%04x, style=%d\n", bg.color().rgb(), bg.style()); +#endif + + Q_D(QPainter); + if (!d->engine) { + qWarning("QPainter::setBackground: Painter not active"); + return; + } + d->state->bgBrush = bg; + if (!d->extended) + d->state->dirtyFlags |= QPaintEngine::DirtyBackground; +} + +/*! + Sets the painter's font to the given \a font. + + This font is used by subsequent drawText() functions. The text + color is the same as the pen color. + + If you set a font that isn't available, Qt finds a close match. + font() will return what you set using setFont() and fontInfo() returns the + font actually being used (which may be the same). + + \sa font(), drawText(), {QPainter#Settings}{Settings} +*/ + +void QPainter::setFont(const QFont &font) +{ + Q_D(QPainter); + +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setFont(), family=%s, pointSize=%d\n", font.family().toLatin1().constData(), font.pointSize()); +#endif + + if (!d->engine) { + qWarning("QPainter::setFont: Painter not active"); + return; + } + + d->state->font = QFont(font.resolve(d->state->deviceFont), device()); + if (!d->extended) + d->state->dirtyFlags |= QPaintEngine::DirtyFont; +} + +/*! + Returns the currently set font used for drawing text. + + \sa setFont(), drawText(), {QPainter#Settings}{Settings} +*/ +const QFont &QPainter::font() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::font: Painter not active"); + return d->fakeState()->font; + } + return d->state->font; +} + +/*! + \since 4.4 + + Draws the given rectangle \a rect with rounded corners. + + The \a xRadius and \a yRadius arguments specify the radii + of the ellipses defining the corners of the rounded rectangle. + When \a mode is Qt::RelativeSize, \a xRadius and + \a yRadius are specified in percentage of half the rectangle's + width and height respectively, and should be in the range + 0.0 to 100.0. + + A filled rectangle has a size of rect.size(). A stroked rectangle + has a size of rect.size() plus the pen width. + + \table 100% + \row + \o \inlineimage qpainter-roundrect.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 8 + \endtable + + \sa drawRect(), QPen +*/ +void QPainter::drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawRoundedRect(), [%.2f,%.2f,%.2f,%.2f]\n", rect.x(), rect.y(), rect.width(), rect.height()); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + if (xRadius <= 0 || yRadius <= 0) { // draw normal rectangle + drawRect(rect); + return; + } + + if (d->extended) { + d->extended->drawRoundedRect(rect, xRadius, yRadius, mode); + return; + } + + QPainterPath path; + path.addRoundedRect(rect, xRadius, yRadius, mode); + drawPath(path); +} + +/*! + \fn void QPainter::drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + \since 4.4 + \overload + + Draws the given rectangle \a rect with rounded corners. +*/ + +/*! + \fn void QPainter::drawRoundedRect(int x, int y, int w, int h, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + \since 4.4 + \overload + + Draws the given rectangle \a x, \a y, \a w, \a h with rounded corners. +*/ + +/*! + \obsolete + + Draws a rectangle \a r with rounded corners. + + The \a xRnd and \a yRnd arguments specify how rounded the corners + should be. 0 is angled corners, 99 is maximum roundedness. + + A filled rectangle has a size of r.size(). A stroked rectangle + has a size of r.size() plus the pen width. + + \sa drawRoundedRect() +*/ +void QPainter::drawRoundRect(const QRectF &r, int xRnd, int yRnd) +{ + drawRoundedRect(r, xRnd, yRnd, Qt::RelativeSize); +} + + +/*! + \fn void QPainter::drawRoundRect(const QRect &r, int xRnd = 25, int yRnd = 25) + + \overload + \obsolete + + Draws the rectangle \a r with rounded corners. +*/ + +/*! + \obsolete + + \fn QPainter::drawRoundRect(int x, int y, int w, int h, int xRnd, int yRnd) + + \overload + + Draws the rectangle \a x, \a y, \a w, \a h with rounded corners. +*/ + +/*! + \fn void QPainter::drawEllipse(const QRectF &rectangle) + + Draws the ellipse defined by the given \a rectangle. + + A filled ellipse has a size of \a{rectangle}.\l + {QRect::size()}{size()}. A stroked ellipse has a size of + \a{rectangle}.\l {QRect::size()}{size()} plus the pen width. + + \table 100% + \row + \o \inlineimage qpainter-ellipse.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 9 + \endtable + + \sa drawPie(), {Coordinate System} +*/ +void QPainter::drawEllipse(const QRectF &r) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawEllipse(), [%.2f,%.2f,%.2f,%.2f]\n", r.x(), r.y(), r.width(), r.height()); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + QRectF rect(r.normalized()); + + if (d->extended) { + d->extended->drawEllipse(rect); + return; + } + + d->updateState(d->state); + if (d->state->emulationSpecifier) { + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + rect.translate(QPointF(d->state->matrix.dx(), d->state->matrix.dy())); + } else { + QPainterPath path; + path.addEllipse(rect); + d->draw_helper(path, QPainterPrivate::StrokeAndFillDraw); + return; + } + } + + d->engine->drawEllipse(rect); +} + +/*! + \fn QPainter::drawEllipse(const QRect &rectangle) + + \overload + + Draws the ellipse defined by the given \a rectangle. +*/ +void QPainter::drawEllipse(const QRect &r) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawEllipse(), [%d,%d,%d,%d]\n", r.x(), r.y(), r.width(), r.height()); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + QRect rect(r.normalized()); + + if (d->extended) { + d->extended->drawEllipse(rect); + return; + } + + d->updateState(d->state); + + if (d->state->emulationSpecifier) { + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + rect.translate(QPoint(qRound(d->state->matrix.dx()), qRound(d->state->matrix.dy()))); + } else { + QPainterPath path; + path.addEllipse(rect); + d->draw_helper(path, QPainterPrivate::StrokeAndFillDraw); + return; + } + } + + d->engine->drawEllipse(rect); +} + +/*! + \fn QPainter::drawEllipse(int x, int y, int width, int height) + + \overload + + Draws the ellipse defined by the rectangle beginning at (\a{x}, + \a{y}) with the given \a width and \a height. +*/ + +/*! + \since 4.4 + + \fn QPainter::drawEllipse(const QPointF ¢er, qreal rx, qreal ry) + + \overload + + Draws the ellipse positioned at \a{center} with radii \a{rx} and \a{ry}. +*/ + +/*! + \since 4.4 + + \fn QPainter::drawEllipse(const QPoint ¢er, int rx, int ry) + + \overload + + Draws the ellipse positioned at \a{center} with radii \a{rx} and \a{ry}. +*/ + +/*! + \fn void QPainter::drawArc(const QRectF &rectangle, int startAngle, int spanAngle) + + Draws the arc defined by the given \a rectangle, \a startAngle and + \a spanAngle. + + The \a startAngle and \a spanAngle must be specified in 1/16th of + a degree, i.e. a full circle equals 5760 (16 * 360). Positive + values for the angles mean counter-clockwise while negative values + mean the clockwise direction. Zero degrees is at the 3 o'clock + position. + + \table 100% + \row + \o \inlineimage qpainter-arc.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 10 + \endtable + + \sa drawPie(), drawChord(), {Coordinate System} +*/ + +void QPainter::drawArc(const QRectF &r, int a, int alen) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawArc(), [%.2f,%.2f,%.2f,%.2f], angle=%d, sweep=%d\n", + r.x(), r.y(), r.width(), r.height(), a/16, alen/16); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + QRectF rect = r.normalized(); + + QPainterPath path; + path.arcMoveTo(rect, a/16.0); + path.arcTo(rect, a/16.0, alen/16.0); + strokePath(path, d->state->pen); +} + +/*! \fn void QPainter::drawArc(const QRect &rectangle, int startAngle, + int spanAngle) + + \overload + + Draws the arc defined by the given \a rectangle, \a startAngle and + \a spanAngle. +*/ + +/*! + \fn void QPainter::drawArc(int x, int y, int width, int height, + int startAngle, int spanAngle) + + \overload + + Draws the arc defined by the rectangle beginning at (\a x, \a y) + with the specified \a width and \a height, and the given \a + startAngle and \a spanAngle. +*/ + +/*! + \fn void QPainter::drawPie(const QRectF &rectangle, int startAngle, int spanAngle) + + Draws a pie defined by the given \a rectangle, \a startAngle and + and \a spanAngle. + + The pie is filled with the current brush(). + + The startAngle and spanAngle must be specified in 1/16th of a + degree, i.e. a full circle equals 5760 (16 * 360). Positive values + for the angles mean counter-clockwise while negative values mean + the clockwise direction. Zero degrees is at the 3 o'clock + position. + + \table 100% + \row + \o \inlineimage qpainter-pie.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 11 + \endtable + + \sa drawEllipse(), drawChord(), {Coordinate System} +*/ +void QPainter::drawPie(const QRectF &r, int a, int alen) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPie(), [%.2f,%.2f,%.2f,%.2f], angle=%d, sweep=%d\n", + r.x(), r.y(), r.width(), r.height(), a/16, alen/16); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + if (a > (360*16)) { + a = a % (360*16); + } else if (a < 0) { + a = a % (360*16); + if (a < 0) a += (360*16); + } + + QRectF rect = r.normalized(); + + QPainterPath path; + path.moveTo(rect.center()); + path.arcTo(rect.x(), rect.y(), rect.width(), rect.height(), a/16.0, alen/16.0); + path.closeSubpath(); + drawPath(path); + +} + +/*! + \fn void QPainter::drawPie(const QRect &rectangle, int startAngle, int spanAngle) + \overload + + Draws a pie defined by the given \a rectangle, \a startAngle and + and \a spanAngle. +*/ + +/*! + \fn void QPainter::drawPie(int x, int y, int width, int height, int + startAngle, int spanAngle) + + \overload + + Draws the pie defined by the rectangle beginning at (\a x, \a y) with + the specified \a width and \a height, and the given \a startAngle and + \a spanAngle. +*/ + +/*! + \fn void QPainter::drawChord(const QRectF &rectangle, int startAngle, int spanAngle) + + Draws the chord defined by the given \a rectangle, \a startAngle and + \a spanAngle. The chord is filled with the current brush(). + + The startAngle and spanAngle must be specified in 1/16th of a + degree, i.e. a full circle equals 5760 (16 * 360). Positive values + for the angles mean counter-clockwise while negative values mean + the clockwise direction. Zero degrees is at the 3 o'clock + position. + + \table 100% + \row + \o \inlineimage qpainter-chord.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 12 + \endtable + + \sa drawArc(), drawPie(), {Coordinate System} +*/ +void QPainter::drawChord(const QRectF &r, int a, int alen) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawChord(), [%.2f,%.2f,%.2f,%.2f], angle=%d, sweep=%d\n", + r.x(), r.y(), r.width(), r.height(), a/16, alen/16); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + QRectF rect = r.normalized(); + + QPainterPath path; + path.arcMoveTo(rect, a/16.0); + path.arcTo(rect, a/16.0, alen/16.0); + path.closeSubpath(); + drawPath(path); +} +/*! + \fn void QPainter::drawChord(const QRect &rectangle, int startAngle, int spanAngle) + + \overload + + Draws the chord defined by the given \a rectangle, \a startAngle and + \a spanAngle. +*/ + +/*! + \fn void QPainter::drawChord(int x, int y, int width, int height, int + startAngle, int spanAngle) + + \overload + + Draws the chord defined by the rectangle beginning at (\a x, \a y) + with the specified \a width and \a height, and the given \a + startAngle and \a spanAngle. +*/ + +#ifdef QT3_SUPPORT +/*! + \fn void QPainter::drawLineSegments(const QPolygon &polygon, int + index, int count) + + Draws \a count separate lines from points defined by the \a + polygon, starting at \a{polygon}\e{[index]} (\a index defaults to + 0). If \a count is -1 (the default) all points until the end of + the array are used. + + Use drawLines() combined with QPolygon::constData() instead. + + \oldcode + QPainter painter(this); + painter.drawLineSegments(polygon, index, count); + \newcode + int lineCount = (count == -1) ? (polygon.size() - index) / 2 : count; + + QPainter painter(this); + painter.drawLines(polygon.constData() + index * 2, lineCount); + \endcode +*/ + +void QPainter::drawLineSegments(const QPolygon &a, int index, int nlines) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawLineSegments(), count=%d\n", a.size()/2); +#endif + Q_D(QPainter); + + if (!d->engine) + return; + + if (nlines < 0) + nlines = a.size()/2 - index/2; + if (index + nlines*2 > (int)a.size()) + nlines = (a.size() - index)/2; + if (nlines < 1 || index < 0) + return; + + if (d->extended) { + // FALCON: Use QVectorPath + QVector<QLineF> lines; + for (int i=index; i<index + nlines*2; i+=2) + lines << QLineF(a.at(i), a.at(i+1)); + d->extended->drawLines(lines.data(), lines.size()); + return; + } + + d->updateState(d->state); + + QVector<QLineF> lines; + if (d->state->emulationSpecifier) { + if (d->state->emulationSpecifier == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + QPointF offset(d->state->matrix.dx(), d->state->matrix.dy()); + for (int i=index; i<index + nlines*2; i+=2) + lines << QLineF(a.at(i) + offset, a.at(i+1) + offset); + } else { + QPainterPath linesPath; + for (int i=index; i<index + nlines*2; i+=2) { + linesPath.moveTo(a.at(i)); + linesPath.lineTo(a.at(i+1)); + } + d->draw_helper(linesPath, QPainterPrivate::StrokeDraw); + return; + } + } else { + for (int i=index; i<index + nlines*2; i+=2) + lines << QLineF(a.at(i), a.at(i+1)); + } + + d->engine->drawLines(lines.data(), lines.size()); +} +#endif // QT3_SUPPORT + +/*! + Draws the first \a lineCount lines in the array \a lines + using the current pen. + + \sa drawLine(), drawPolyline() +*/ +void QPainter::drawLines(const QLineF *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawLines(), line count=%d\n", lineCount); +#endif + + Q_D(QPainter); + + if (!d->engine || lineCount < 1) + return; + + if (d->extended) { + d->extended->drawLines(lines, lineCount); + return; + } + + d->updateState(d->state); + + uint lineEmulation = line_emulation(d->state->emulationSpecifier); + + if (lineEmulation) { + if (lineEmulation == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + for (int i = 0; i < lineCount; ++i) { + QLineF line = lines[i]; + line.translate(d->state->matrix.dx(), d->state->matrix.dy()); + d->engine->drawLines(&line, 1); + } + } else { + QPainterPath linePath; + for (int i = 0; i < lineCount; ++i) { + linePath.moveTo(lines[i].p1()); + linePath.lineTo(lines[i].p2()); + } + d->draw_helper(linePath, QPainterPrivate::StrokeDraw); + } + return; + } + d->engine->drawLines(lines, lineCount); +} + +/*! + \fn void QPainter::drawLines(const QLine *lines, int lineCount) + \overload + + Draws the first \a lineCount lines in the array \a lines + using the current pen. +*/ +void QPainter::drawLines(const QLine *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawLine(), line count=%d\n", lineCount); +#endif + + Q_D(QPainter); + + if (!d->engine || lineCount < 1) + return; + + if (d->extended) { + d->extended->drawLines(lines, lineCount); + return; + } + + d->updateState(d->state); + + uint lineEmulation = line_emulation(d->state->emulationSpecifier); + + if (lineEmulation) { + if (lineEmulation == QPaintEngine::PrimitiveTransform + && d->state->matrix.type() == QTransform::TxTranslate) { + for (int i = 0; i < lineCount; ++i) { + QLineF line = lines[i]; + line.translate(d->state->matrix.dx(), d->state->matrix.dy()); + d->engine->drawLines(&line, 1); + } + } else { + QPainterPath linePath; + for (int i = 0; i < lineCount; ++i) { + linePath.moveTo(lines[i].p1()); + linePath.lineTo(lines[i].p2()); + } + d->draw_helper(linePath, QPainterPrivate::StrokeDraw); + } + return; + } + d->engine->drawLines(lines, lineCount); +} + +/*! + \overload + + Draws the first \a lineCount lines in the array \a pointPairs + using the current pen. The lines are specified as pairs of points + so the number of entries in \a pointPairs must be at least \a + lineCount * 2. +*/ +void QPainter::drawLines(const QPointF *pointPairs, int lineCount) +{ + Q_ASSERT(sizeof(QLineF) == 2*sizeof(QPointF)); + + drawLines((QLineF*)pointPairs, lineCount); +} + +/*! + \overload + + Draws the first \a lineCount lines in the array \a pointPairs + using the current pen. +*/ +void QPainter::drawLines(const QPoint *pointPairs, int lineCount) +{ + Q_ASSERT(sizeof(QLine) == 2*sizeof(QPoint)); + + drawLines((QLine*)pointPairs, lineCount); +} + + +/*! + \fn void QPainter::drawLines(const QVector<QPointF> &pointPairs) + \overload + + Draws a line for each pair of points in the vector \a pointPairs + using the current pen. If there is an odd number of points in the + array, the last point will be ignored. +*/ + +/*! + \fn void QPainter::drawLines(const QVector<QPoint> &pointPairs) + \overload + + Draws a line for each pair of points in the vector \a pointPairs + using the current pen. +*/ + +/*! + \fn void QPainter::drawLines(const QVector<QLineF> &lines) + \overload + + Draws the set of lines defined by the list \a lines using the + current pen and brush. +*/ + +/*! + \fn void QPainter::drawLines(const QVector<QLine> &lines) + \overload + + Draws the set of lines defined by the list \a lines using the + current pen and brush. +*/ + +/*! + Draws the polyline defined by the first \a pointCount points in \a + points using the current pen. + + Note that unlike the drawPolygon() function the last point is \e + not connected to the first, neither is the polyline filled. + + \table 100% + \row + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 13 + \endtable + + \sa drawLines(), drawPolygon(), {Coordinate System} +*/ +void QPainter::drawPolyline(const QPointF *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPolyline(), count=%d\n", pointCount); +#endif + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::PolylineMode); + return; + } + + d->updateState(d->state); + + uint lineEmulation = line_emulation(d->state->emulationSpecifier); + + if (lineEmulation) { + // ### +// if (lineEmulation == QPaintEngine::PrimitiveTransform +// && d->state->matrix.type() == QTransform::TxTranslate) { +// } else { + QPainterPath polylinePath(points[0]); + for (int i=1; i<pointCount; ++i) + polylinePath.lineTo(points[i]); + d->draw_helper(polylinePath, QPainterPrivate::StrokeDraw); +// } + } else { + d->engine->drawPolygon(points, pointCount, QPaintEngine::PolylineMode); + } +} + +/*! + \overload + + Draws the polyline defined by the first \a pointCount points in \a + points using the current pen. + */ +void QPainter::drawPolyline(const QPoint *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPolyline(), count=%d\n", pointCount); +#endif + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::PolylineMode); + return; + } + + d->updateState(d->state); + + uint lineEmulation = line_emulation(d->state->emulationSpecifier); + + if (lineEmulation) { + // ### +// if (lineEmulation == QPaintEngine::PrimitiveTransform +// && d->state->matrix.type() == QTransform::TxTranslate) { +// } else { + QPainterPath polylinePath(points[0]); + for (int i=1; i<pointCount; ++i) + polylinePath.lineTo(points[i]); + d->draw_helper(polylinePath, QPainterPrivate::StrokeDraw); +// } + } else { + d->engine->drawPolygon(points, pointCount, QPaintEngine::PolylineMode); + } +} + +/*! + \fn void QPainter::drawPolyline(const QPolygon &polygon, int index, int + count) + + \overload + \compat + + Draws the polyline defined by the \a count lines of the given \a + polygon starting at \a index (\a index defaults to 0). + + Use drawPolyline() combined with QPolygon::constData() instead. + + \oldcode + QPainter painter(this); + painter.drawPolyline(polygon, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + + QPainter painter(this); + painter.drawPolyline(polygon.constData() + index, pointCount); + \endcode +*/ + +/*! + \fn void QPainter::drawPolyline(const QPolygonF &points) + + \overload + + Draws the polyline defined by the given \a points using the + current pen. +*/ + +/*! + \fn void QPainter::drawPolyline(const QPolygon &points) + + \overload + + Draws the polyline defined by the given \a points using the + current pen. +*/ + +/*! + Draws the polygon defined by the first \a pointCount points in the + array \a points using the current pen and brush. + + \table 100% + \row + \o \inlineimage qpainter-polygon.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 14 + \endtable + + The first point is implicitly connected to the last point, and the + polygon is filled with the current brush(). + + If \a fillRule is Qt::WindingFill, the polygon is filled using the + winding fill algorithm. If \a fillRule is Qt::OddEvenFill, the + polygon is filled using the odd-even fill algorithm. See + \l{Qt::FillRule} for a more detailed description of these fill + rules. + + \sa drawConvexPolygon(), drawPolyline(), {Coordinate System} +*/ +void QPainter::drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPolygon(), count=%d\n", pointCount); +#endif + + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::PolygonDrawMode(fillRule)); + return; + } + + d->updateState(d->state); + + uint emulationSpecifier = d->state->emulationSpecifier; + + if (emulationSpecifier) { + QPainterPath polygonPath(points[0]); + for (int i=1; i<pointCount; ++i) + polygonPath.lineTo(points[i]); + polygonPath.closeSubpath(); + polygonPath.setFillRule(fillRule); + d->draw_helper(polygonPath); + return; + } + + d->engine->drawPolygon(points, pointCount, QPaintEngine::PolygonDrawMode(fillRule)); +} + +/*! \overload + + Draws the polygon defined by the first \a pointCount points in the + array \a points. +*/ +void QPainter::drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPolygon(), count=%d\n", pointCount); +#endif + + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::PolygonDrawMode(fillRule)); + return; + } + + d->updateState(d->state); + + uint emulationSpecifier = d->state->emulationSpecifier; + + if (emulationSpecifier) { + QPainterPath polygonPath(points[0]); + for (int i=1; i<pointCount; ++i) + polygonPath.lineTo(points[i]); + polygonPath.closeSubpath(); + polygonPath.setFillRule(fillRule); + d->draw_helper(polygonPath); + return; + } + + d->engine->drawPolygon(points, pointCount, QPaintEngine::PolygonDrawMode(fillRule)); +} + +/*! \fn void QPainter::drawPolygon(const QPolygonF &polygon, bool winding, int index = 0, + int count = -1) + \compat + \overload + + Use drawPolygon() combined with QPolygonF::constData() instead. + + \oldcode + QPainter painter(this); + painter.drawPolygon(polygon, winding, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + int fillRule = winding ? Qt::WindingFill : Qt::OddEvenFill; + + QPainter painter(this); + painter.drawPolygon( polygon.constData() + index, pointCount, fillRule); + \endcode +*/ + +/*! \fn void QPainter::drawPolygon(const QPolygon &polygon, bool winding, + int index = 0, int count = -1) + + \compat + \overload + + Use drawPolygon() combined with QPolygon::constData() instead. + + \oldcode + QPainter painter(this); + painter.drawPolygon(polygon, winding, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + int fillRule = winding ? Qt::WindingFill : Qt::OddEvenFill; + + QPainter painter(this); + painter.drawPolygon( polygon.constData() + index, pointCount, fillRule); + \endcode +*/ + +/*! \fn void QPainter::drawPolygon(const QPolygonF &points, Qt::FillRule fillRule) + + \overload + + Draws the polygon defined by the given \a points using the fill + rule \a fillRule. +*/ + +/*! \fn void QPainter::drawPolygon(const QPolygon &points, Qt::FillRule fillRule) + + \overload + + Draws the polygon defined by the given \a points using the fill + rule \a fillRule. +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPointF *points, int pointCount) + + Draws the convex polygon defined by the first \a pointCount points + in the array \a points using the current pen. + + \table 100% + \row + \o \inlineimage qpainter-polygon.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 15 + \endtable + + The first point is implicitly connected to the last point, and the + polygon is filled with the current brush(). If the supplied + polygon is not convex, i.e. it contains at least one angle larger + than 180 degrees, the results are undefined. + + On some platforms (e.g. X11), the drawConvexPolygon() function can + be faster than the drawPolygon() function. + + \sa drawPolygon(), drawPolyline(), {Coordinate System} +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPoint *points, int pointCount) + \overload + + Draws the convex polygon defined by the first \a pointCount points + in the array \a points using the current pen. +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPolygonF &polygon) + + \overload + + Draws the convex polygon defined by \a polygon using the current + pen and brush. +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPolygon &polygon) + \overload + + Draws the convex polygon defined by \a polygon using the current + pen and brush. +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPolygonF &polygon, int + index, int count) + + \compat + \overload + + Use drawConvexPolygon() combined with QPolygonF::constData() + instead. + + \oldcode + QPainter painter(this); + painter.drawConvexPolygon(polygon, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + + QPainter painter(this); + painter.drawConvexPolygon(polygon.constData() + index, pointCount); + \endcode +*/ + +/*! + \fn void QPainter::drawConvexPolygon(const QPolygon &polygon, int + index, int count) + + \compat + \overload + + Use drawConvexPolygon() combined with QPolygon::constData() + instead. + + \oldcode + QPainter painter(this); + painter.drawConvexPolygon(polygon, index, count); + \newcode + int pointCount = (count == -1) ? polygon.size() - index : count; + + QPainter painter(this); + painter.drawConvexPolygon(polygon.constData() + index, pointCount); + \endcode +*/ + +void QPainter::drawConvexPolygon(const QPoint *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawConvexPolygon(), count=%d\n", pointCount); +#endif + + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::ConvexMode); + return; + } + + d->updateState(d->state); + + uint emulationSpecifier = d->state->emulationSpecifier; + + if (emulationSpecifier) { + QPainterPath polygonPath(points[0]); + for (int i=1; i<pointCount; ++i) + polygonPath.lineTo(points[i]); + polygonPath.closeSubpath(); + polygonPath.setFillRule(Qt::WindingFill); + d->draw_helper(polygonPath); + return; + } + + d->engine->drawPolygon(points, pointCount, QPaintEngine::ConvexMode); +} + +void QPainter::drawConvexPolygon(const QPointF *points, int pointCount) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawConvexPolygon(), count=%d\n", pointCount); +#endif + + Q_D(QPainter); + + if (!d->engine || pointCount < 2) + return; + + if (d->extended) { + d->extended->drawPolygon(points, pointCount, QPaintEngine::ConvexMode); + return; + } + + d->updateState(d->state); + + uint emulationSpecifier = d->state->emulationSpecifier; + + if (emulationSpecifier) { + QPainterPath polygonPath(points[0]); + for (int i=1; i<pointCount; ++i) + polygonPath.lineTo(points[i]); + polygonPath.closeSubpath(); + polygonPath.setFillRule(Qt::WindingFill); + d->draw_helper(polygonPath); + return; + } + + d->engine->drawPolygon(points, pointCount, QPaintEngine::ConvexMode); +} + +static inline QPointF roundInDeviceCoordinates(const QPointF &p, const QTransform &m) +{ + return m.inverted().map(QPointF(m.map(p).toPoint())); +} + +/*! + \fn void QPainter::drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source) + + Draws the rectangular portion \a source of the given \a pixmap + into the given \a target in the paint device. + + \note The pixmap is scaled to fit the rectangle, if both the pixmap and rectangle size disagree. + + \table 100% + \row + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 16 + \endtable + + If \a pixmap is a QBitmap it is drawn with the bits that are "set" + using the pens color. If backgroundMode is Qt::OpaqueMode, the + "unset" bits are drawn using the color of the background brush; if + backgroundMode is Qt::TransparentMode, the "unset" bits are + transparent. Drawing bitmaps with gradient or texture colors is + not supported. + + \sa drawImage() +*/ +void QPainter::drawPixmap(const QPointF &p, const QPixmap &pm) +{ +#if defined QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPixmap(), p=[%.2f,%.2f], pix=[%d,%d]\n", + p.x(), p.y(), + pm.width(), pm.height()); +#endif + + Q_D(QPainter); + + if (!d->engine || pm.isNull()) + return; + +#ifndef QT_NO_DEBUG + qt_painter_thread_test(d->device->devType(), "drawPixmap()", true); +#endif + + if (d->extended) { + d->extended->drawPixmap(p, pm); + return; + } + + qreal x = p.x(); + qreal y = p.y(); + + int w = pm.width(); + int h = pm.height(); + + if (w <= 0) + return; + + // Emulate opaque background for bitmaps + if (d->state->bgMode == Qt::OpaqueMode && pm.isQBitmap()) { + fillRect(QRectF(x, y, w, h), d->state->bgBrush.color()); + } + + d->updateState(d->state); + + if ((d->state->matrix.type() > QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) + || (!d->state->matrix.isAffine() && !d->engine->hasFeature(QPaintEngine::PerspectiveTransform)) + || (d->state->opacity != 1.0 && !d->engine->hasFeature(QPaintEngine::ConstantOpacity))) + { + save(); + // If there is no rotation involved we have to make sure we use the + // antialiased and not the aliased coordinate system by rounding the coordinates. + if (d->state->matrix.type() <= QTransform::TxScale) { + const QPointF p = roundInDeviceCoordinates(QPointF(x, y), d->state->matrix); + x = p.x(); + y = p.y(); + } + translate(x, y); + setBackgroundMode(Qt::TransparentMode); + setRenderHint(Antialiasing, renderHints() & SmoothPixmapTransform); + QBrush brush(d->state->pen.color(), pm); + setBrush(brush); + setPen(Qt::NoPen); + setBrushOrigin(QPointF(0, 0)); + + drawRect(pm.rect()); + restore(); + } else { + if (!d->engine->hasFeature(QPaintEngine::PixmapTransform)) { + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } + d->engine->drawPixmap(QRectF(x, y, w, h), pm, QRectF(0, 0, w, h)); + } +} + +void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ +#if defined QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawPixmap(), target=[%.2f,%.2f,%.2f,%.2f], pix=[%d,%d], source=[%.2f,%.2f,%.2f,%.2f]\n", + r.x(), r.y(), r.width(), r.height(), + pm.width(), pm.height(), + sr.x(), sr.y(), sr.width(), sr.height()); +#endif + + Q_D(QPainter); + if (!d->engine || pm.isNull()) + return; +#ifndef QT_NO_DEBUG + qt_painter_thread_test(d->device->devType(), "drawPixmap()", true); +#endif + + qreal x = r.x(); + qreal y = r.y(); + qreal w = r.width(); + qreal h = r.height(); + qreal sx = sr.x(); + qreal sy = sr.y(); + qreal sw = sr.width(); + qreal sh = sr.height(); + + // Sanity-check clipping + if (sw <= 0) + sw = pm.width() - sx; + + if (sh <= 0) + sh = pm.height() - sy; + + if (w < 0) + w = sw; + if (h < 0) + h = sh; + + if (sx < 0) { + qreal w_ratio = sx * w/sw; + x -= w_ratio; + w += w_ratio; + sw += sx; + sx = 0; + } + + if (sy < 0) { + qreal h_ratio = sy * h/sh; + y -= h_ratio; + h += h_ratio; + sh += sy; + sy = 0; + } + + if (sw + sx > pm.width()) { + qreal delta = sw - (pm.width() - sx); + qreal w_ratio = delta * w/sw; + sw -= delta; + w -= w_ratio; + } + + if (sh + sy > pm.height()) { + qreal delta = sh - (pm.height() - sy); + qreal h_ratio = delta * h/sh; + sh -= delta; + h -= h_ratio; + } + + if (w == 0 || h == 0 || sw <= 0 || sh <= 0) + return; + + if (d->extended) { + d->extended->drawPixmap(QRectF(x, y, w, h), pm, QRectF(sx, sy, sw, sh)); + return; + } + + // Emulate opaque background for bitmaps + if (d->state->bgMode == Qt::OpaqueMode && pm.isQBitmap()) + fillRect(QRectF(x, y, w, h), d->state->bgBrush.color()); + + d->updateState(d->state); + + if ((d->state->matrix.type() > QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) + || (!d->state->matrix.isAffine() && !d->engine->hasFeature(QPaintEngine::PerspectiveTransform)) + || (d->state->opacity != 1.0 && !d->engine->hasFeature(QPaintEngine::ConstantOpacity)) + || ((sw != w || sh != h) && !d->engine->hasFeature(QPaintEngine::PixmapTransform))) + { + save(); + // If there is no rotation involved we have to make sure we use the + // antialiased and not the aliased coordinate system by rounding the coordinates. + if (d->state->matrix.type() <= QTransform::TxScale) { + const QPointF p = roundInDeviceCoordinates(QPointF(x, y), d->state->matrix); + x = p.x(); + y = p.y(); + } + + if (d->state->matrix.type() <= QTransform::TxTranslate && sw == w && sh == h) { + sx = qRound(sx); + sy = qRound(sy); + sw = qRound(sw); + sh = qRound(sh); + } + + translate(x, y); + scale(w / sw, h / sh); + setBackgroundMode(Qt::TransparentMode); + setRenderHint(Antialiasing, renderHints() & SmoothPixmapTransform); + QBrush brush; + + if (sw == pm.width() && sh == pm.height()) + brush = QBrush(d->state->pen.color(), pm); + else + brush = QBrush(d->state->pen.color(), pm.copy(sx, sy, sw, sh)); + + setBrush(brush); + setPen(Qt::NoPen); + + drawRect(QRectF(0, 0, sw, sh)); + restore(); + } else { + if (!d->engine->hasFeature(QPaintEngine::PixmapTransform)) { + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } + d->engine->drawPixmap(QRectF(x, y, w, h), pm, QRectF(sx, sy, sw, sh)); + } +} + + +/*! + \fn void QPainter::drawPixmap(const QRect &target, const QPixmap &pixmap, + const QRect &source) + \overload + + Draws the rectangular portion \a source of the given \a pixmap + into the given \a target in the paint device. + + \note The pixmap is scaled to fit the rectangle, if both the pixmap and rectangle size disagree. +*/ + +/*! + \fn void QPainter::drawPixmap(const QPointF &point, const QPixmap &pixmap, + const QRectF &source) + \overload + + Draws the rectangular portion \a source of the given \a pixmap + with its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawPixmap(const QPoint &point, const QPixmap &pixmap, + const QRect &source) + + \overload + + Draws the rectangular portion \a source of the given \a pixmap + with its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawPixmap(const QPointF &point, const QPixmap &pixmap) + \overload + + Draws the given \a pixmap with its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawPixmap(const QPoint &point, const QPixmap &pixmap) + \overload + + Draws the given \a pixmap with its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap) + + \overload + + Draws the given \a pixmap at position (\a{x}, \a{y}). +*/ + +/*! + \fn void QPainter::drawPixmap(const QRect &rectangle, const QPixmap &pixmap) + \overload + + Draws the given \a pixmap into the given \a rectangle. + + \note The pixmap is scaled to fit the rectangle, if both the pixmap and rectangle size disagree. +*/ + +/*! + \fn void QPainter::drawPixmap(int x, int y, int width, int height, + const QPixmap &pixmap) + + \overload + + Draws the \a pixmap into the rectangle at position (\a{x}, \a{y}) + with the given \a width and \a height. +*/ + +/*! + \fn void QPainter::drawPixmap(int x, int y, int w, int h, const QPixmap &pixmap, + int sx, int sy, int sw, int sh) + + \overload + + Draws the rectangular portion with the origin (\a{sx}, \a{sy}), + width \a sw and height \a sh, of the given \a pixmap , at the + point (\a{x}, \a{y}), with a width of \a w and a height of \a h. + If sw or sh are equal to zero the width/height of the pixmap + is used and adjusted by the offset sx/sy; +*/ + +/*! + \fn void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap, + int sx, int sy, int sw, int sh) + + \overload + + Draws a pixmap at (\a{x}, \a{y}) by copying a part of the given \a + pixmap into the paint device. + + (\a{x}, \a{y}) specifies the top-left point in the paint device that is + to be drawn onto. (\a{sx}, \a{sy}) specifies the top-left point in \a + pixmap that is to be drawn. The default is (0, 0). + + (\a{sw}, \a{sh}) specifies the size of the pixmap that is to be drawn. + The default, (0, 0) (and negative) means all the way to the + bottom-right of the pixmap. +*/ + +void QPainter::drawImage(const QPointF &p, const QImage &image) +{ + Q_D(QPainter); + + if (!d->engine || image.isNull()) + return; + + if (d->extended) { + d->extended->drawImage(p, image); + return; + } + + qreal x = p.x(); + qreal y = p.y(); + + int w = image.width(); + int h = image.height(); + + d->updateState(d->state); + + if (((d->state->matrix.type() > QTransform::TxTranslate) + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) + || (!d->state->matrix.isAffine() && !d->engine->hasFeature(QPaintEngine::PerspectiveTransform)) + || (d->state->opacity != 1.0 && !d->engine->hasFeature(QPaintEngine::ConstantOpacity))) + { + save(); + // If there is no rotation involved we have to make sure we use the + // antialiased and not the aliased coordinate system by rounding the coordinates. + if (d->state->matrix.type() <= QTransform::TxScale) { + const QPointF p = roundInDeviceCoordinates(QPointF(x, y), d->state->matrix); + x = p.x(); + y = p.y(); + } + translate(x, y); + setBackgroundMode(Qt::TransparentMode); + setRenderHint(Antialiasing, renderHints() & SmoothPixmapTransform); + QBrush brush(image); + setBrush(brush); + setPen(Qt::NoPen); + setBrushOrigin(QPointF(0, 0)); + + drawRect(image.rect()); + restore(); + return; + } + + if (d->state->matrix.type() == QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) { + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } + + d->engine->drawImage(QRectF(x, y, w, h), image, QRectF(0, 0, w, h), Qt::AutoColor); +} + +void QPainter::drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect, + Qt::ImageConversionFlags flags) +{ + Q_D(QPainter); + + if (!d->engine || image.isNull()) + return; + + qreal x = targetRect.x(); + qreal y = targetRect.y(); + qreal w = targetRect.width(); + qreal h = targetRect.height(); + qreal sx = sourceRect.x(); + qreal sy = sourceRect.y(); + qreal sw = sourceRect.width(); + qreal sh = sourceRect.height(); + + // Sanity-check clipping + if (sw <= 0) + sw = image.width() - sx; + + if (sh <= 0) + sh = image.height() - sy; + + if (w < 0) + w = sw; + if (h < 0) + h = sh; + + if (sx < 0) { + qreal w_ratio = sx * w/sw; + x -= w_ratio; + w += w_ratio; + sw += sx; + sx = 0; + } + + if (sy < 0) { + qreal h_ratio = sy * h/sh; + y -= h_ratio; + h += h_ratio; + sh += sy; + sy = 0; + } + + if (sw + sx > image.width()) { + qreal delta = sw - (image.width() - sx); + qreal w_ratio = delta * w/sw; + sw -= delta; + w -= w_ratio; + } + + if (sh + sy > image.height()) { + qreal delta = sh - (image.height() - sy); + qreal h_ratio = delta * h/sh; + sh -= delta; + h -= h_ratio; + } + + if (w == 0 || h == 0 || sw <= 0 || sh <= 0) + return; + + if (d->extended) { + d->extended->drawImage(QRectF(x, y, w, h), image, QRectF(sx, sy, sw, sh), flags); + return; + } + + d->updateState(d->state); + + if (((d->state->matrix.type() > QTransform::TxTranslate || (sw != w || sh != h)) + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) + || (!d->state->matrix.isAffine() && !d->engine->hasFeature(QPaintEngine::PerspectiveTransform)) + || (d->state->opacity != 1.0 && !d->engine->hasFeature(QPaintEngine::ConstantOpacity))) + { + save(); + // If there is no rotation involved we have to make sure we use the + // antialiased and not the aliased coordinate system by rounding the coordinates. + if (d->state->matrix.type() <= QTransform::TxScale) { + const QPointF p = roundInDeviceCoordinates(QPointF(x, y), d->state->matrix); + x = p.x(); + y = p.y(); + } + + if (d->state->matrix.type() <= QTransform::TxTranslate && sw == w && sh == h) { + sx = qRound(sx); + sy = qRound(sy); + sw = qRound(sw); + sh = qRound(sh); + } + translate(x, y); + scale(w / sw, h / sh); + setBackgroundMode(Qt::TransparentMode); + setRenderHint(Antialiasing, renderHints() & SmoothPixmapTransform); + QBrush brush(image); + setBrush(brush); + setPen(Qt::NoPen); + setBrushOrigin(QPointF(-sx, -sy)); + + drawRect(QRectF(0, 0, sw, sh)); + restore(); + return; + } + + if (d->state->matrix.type() == QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) { + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } + + d->engine->drawImage(QRectF(x, y, w, h), image, QRectF(sx, sy, sw, sh), flags); +} + +/*! + Draws the glyphs represented by \a glyphs at \a position. The \a position gives the + edge of the baseline for the string of glyphs. The glyphs will be retrieved from the font + selected on \a glyphs and at offsets given by the positions in \a glyphs. + + \since 4.8 + + \sa QGlyphs::setFont(), QGlyphs::setPositions(), QGlyphs::setGlyphIndexes() +*/ +#if !defined(QT_NO_RAWFONT) +void QPainter::drawGlyphs(const QPointF &position, const QGlyphs &glyphs) +{ + Q_D(QPainter); + + QRawFont font = glyphs.font(); + if (!font.isValid()) + return; + + QVector<quint32> glyphIndexes = glyphs.glyphIndexes(); + QVector<QPointF> glyphPositions = glyphs.positions(); + + int count = qMin(glyphIndexes.size(), glyphPositions.size()); + QVarLengthArray<QFixedPoint, 128> fixedPointPositions(count); + + bool paintEngineSupportsTransformations = + d->extended != 0 + ? qt_paintengine_supports_transformations(d->extended->type()) + : false; + for (int i=0; i<count; ++i) { + QPointF processedPosition = position + glyphPositions.at(i); + if (!paintEngineSupportsTransformations) + processedPosition = d->state->transform().map(processedPosition); + fixedPointPositions[i] = QFixedPoint::fromPointF(processedPosition); + } + + d->drawGlyphs(glyphIndexes.data(), fixedPointPositions.data(), count, font, glyphs.overline(), + glyphs.underline(), glyphs.strikeOut()); +} + +void QPainterPrivate::drawGlyphs(quint32 *glyphArray, QFixedPoint *positions, int glyphCount, + const QRawFont &font, bool overline, bool underline, + bool strikeOut) +{ + Q_Q(QPainter); + + updateState(state); + + QRawFontPrivate *fontD = QRawFontPrivate::get(font); + QFontEngine *fontEngine = fontD->fontEngine; + + QFixed leftMost; + QFixed rightMost; + QFixed baseLine; + for (int i=0; i<glyphCount; ++i) { + glyph_metrics_t gm = fontEngine->boundingBox(glyphArray[i]); + if (i == 0 || leftMost > positions[i].x) + leftMost = positions[i].x; + + // We don't support glyphs that do not share a common baseline. If this turns out to + // be a relevant use case, then we need to find clusters of glyphs that share a baseline + // and do a drawTextItemDecorations call per cluster. + if (i == 0 || baseLine < positions[i].y) + baseLine = positions[i].y; + + // We use the advance rather than the actual bounds to match the algorithm in drawText() + if (i == 0 || rightMost < positions[i].x + gm.xoff) + rightMost = positions[i].x + gm.xoff; + } + + QFixed width = rightMost - leftMost; + + if (extended != 0) { + QStaticTextItem staticTextItem; + staticTextItem.color = state->pen.color(); + staticTextItem.font = state->font; + staticTextItem.setFontEngine(fontEngine); + staticTextItem.numGlyphs = glyphCount; + staticTextItem.glyphs = reinterpret_cast<glyph_t *>(const_cast<glyph_t *>(glyphArray)); + staticTextItem.glyphPositions = positions; + + extended->drawStaticTextItem(&staticTextItem); + } else { + QTextItemInt textItem; + textItem.fontEngine = fontEngine; + + QVarLengthArray<QFixed, 128> advances(glyphCount); + QVarLengthArray<QGlyphJustification, 128> glyphJustifications(glyphCount); + QVarLengthArray<HB_GlyphAttributes, 128> glyphAttributes(glyphCount); + qMemSet(glyphAttributes.data(), 0, glyphAttributes.size() * sizeof(HB_GlyphAttributes)); + qMemSet(advances.data(), 0, advances.size() * sizeof(QFixed)); + qMemSet(glyphJustifications.data(), 0, glyphJustifications.size() * sizeof(QGlyphJustification)); + + textItem.glyphs.numGlyphs = glyphCount; + textItem.glyphs.glyphs = reinterpret_cast<HB_Glyph *>(const_cast<quint32 *>(glyphArray)); + textItem.glyphs.offsets = positions; + textItem.glyphs.advances_x = advances.data(); + textItem.glyphs.advances_y = advances.data(); + textItem.glyphs.justifications = glyphJustifications.data(); + textItem.glyphs.attributes = glyphAttributes.data(); + + engine->drawTextItem(QPointF(0, 0), textItem); + } + + QTextItemInt::RenderFlags flags; + if (underline) + flags |= QTextItemInt::Underline; + if (overline) + flags |= QTextItemInt::Overline; + if (strikeOut) + flags |= QTextItemInt::StrikeOut; + + drawTextItemDecoration(q, QPointF(leftMost.toReal(), baseLine.toReal()), + fontEngine, + (underline + ? QTextCharFormat::SingleUnderline + : QTextCharFormat::NoUnderline), + flags, width.toReal(), QTextCharFormat()); +} +#endif // QT_NO_RAWFONT + +/*! + + \fn void QPainter::drawStaticText(const QPoint &topLeftPosition, const QStaticText &staticText) + \since 4.7 + \overload + + Draws the \a staticText at the \a topLeftPosition. + + \note The y-position is used as the top of the font. + +*/ + +/*! + \fn void QPainter::drawStaticText(int left, int top, const QStaticText &staticText) + \since 4.7 + \overload + + Draws the \a staticText at coordinates \a left and \a top. + + \note The y-position is used as the top of the font. +*/ + +/*! + \fn void QPainter::drawText(const QPointF &position, const QString &text) + + Draws the given \a text with the currently defined text direction, + beginning at the given \a position. + + This function does not handle the newline character (\n), as it cannot + break text into multiple lines, and it cannot display the newline character. + Use the QPainter::drawText() overload that takes a rectangle instead + if you want to draw multiple lines of text with the newline character, or + if you want the text to be wrapped. + + By default, QPainter draws text anti-aliased. + + \note The y-position is used as the baseline of the font. +*/ + +void QPainter::drawText(const QPointF &p, const QString &str) +{ + drawText(p, str, 0, 0); +} + +/*! + \since 4.7 + + Draws the given \a staticText at the given \a topLeftPosition. + + The text will be drawn using the font and the transformation set on the painter. If the + font and/or transformation set on the painter are different from the ones used to initialize + the layout of the QStaticText, then the layout will have to be recalculated. Use + QStaticText::prepare() to initialize \a staticText with the font and transformation with which + it will later be drawn. + + If \a topLeftPosition is not the same as when \a staticText was initialized, or when it was + last drawn, then there will be a slight overhead when translating the text to its new position. + + \note If the painter's transformation is not affine, then \a staticText will be drawn using + regular calls to drawText(), losing any potential for performance improvement. + + \note The y-position is used as the top of the font. + + \sa QStaticText +*/ +void QPainter::drawStaticText(const QPointF &topLeftPosition, const QStaticText &staticText) +{ + Q_D(QPainter); + if (!d->engine || staticText.text().isEmpty() || pen().style() == Qt::NoPen) + return; + + QStaticTextPrivate *staticText_d = + const_cast<QStaticTextPrivate *>(QStaticTextPrivate::get(&staticText)); + + if (font() != staticText_d->font) { + staticText_d->font = font(); + staticText_d->needsRelayout = true; + } + + // If we don't have an extended paint engine, or if the painter is projected, + // we go through standard code path + if (d->extended == 0 || !d->state->matrix.isAffine()) { + staticText_d->paintText(topLeftPosition, this); + return; + } + + bool paintEngineSupportsTransformations = qt_paintengine_supports_transformations(d->extended->type()); + if (paintEngineSupportsTransformations && !staticText_d->untransformedCoordinates) { + staticText_d->untransformedCoordinates = true; + staticText_d->needsRelayout = true; + } else if (!paintEngineSupportsTransformations && staticText_d->untransformedCoordinates) { + staticText_d->untransformedCoordinates = false; + staticText_d->needsRelayout = true; + } + + // Don't recalculate entire layout because of translation, rather add the dx and dy + // into the position to move each text item the correct distance. + QPointF transformedPosition = topLeftPosition; + if (!staticText_d->untransformedCoordinates) + transformedPosition = transformedPosition * d->state->matrix; + QTransform oldMatrix; + + // The translation has been applied to transformedPosition. Remove translation + // component from matrix. + if (d->state->matrix.isTranslating() && !staticText_d->untransformedCoordinates) { + qreal m11 = d->state->matrix.m11(); + qreal m12 = d->state->matrix.m12(); + qreal m13 = d->state->matrix.m13(); + qreal m21 = d->state->matrix.m21(); + qreal m22 = d->state->matrix.m22(); + qreal m23 = d->state->matrix.m23(); + qreal m33 = d->state->matrix.m33(); + + oldMatrix = d->state->matrix; + d->state->matrix.setMatrix(m11, m12, m13, + m21, m22, m23, + 0.0, 0.0, m33); + } + + // If the transform is not identical to the text transform, + // we have to relayout the text (for other transformations than plain translation) + bool staticTextNeedsReinit = staticText_d->needsRelayout; + if (!staticText_d->untransformedCoordinates && staticText_d->matrix != d->state->matrix) { + staticText_d->matrix = d->state->matrix; + staticTextNeedsReinit = true; + } + + // Recreate the layout of the static text because the matrix or font has changed + if (staticTextNeedsReinit) + staticText_d->init(); + + if (transformedPosition != staticText_d->position) { // Translate to actual position + QFixed fx = QFixed::fromReal(transformedPosition.x()); + QFixed fy = QFixed::fromReal(transformedPosition.y()); + QFixed oldX = QFixed::fromReal(staticText_d->position.x()); + QFixed oldY = QFixed::fromReal(staticText_d->position.y()); + for (int item=0; item<staticText_d->itemCount;++item) { + QStaticTextItem *textItem = staticText_d->items + item; + for (int i=0; i<textItem->numGlyphs; ++i) { + textItem->glyphPositions[i].x += fx - oldX; + textItem->glyphPositions[i].y += fy - oldY; + } + textItem->userDataNeedsUpdate = true; + } + + staticText_d->position = transformedPosition; + } + + QPen oldPen = d->state->pen; + QColor currentColor = oldPen.color(); + for (int i=0; i<staticText_d->itemCount; ++i) { + QStaticTextItem *item = staticText_d->items + i; + if (item->color.isValid() && currentColor != item->color) { + setPen(item->color); + currentColor = item->color; + } + d->extended->drawStaticTextItem(item); + + qt_draw_decoration_for_glyphs(this, item->glyphs, item->glyphPositions, + item->numGlyphs, item->fontEngine(), staticText_d->font, + QTextCharFormat()); + } + if (currentColor != oldPen.color()) + setPen(oldPen); + + if (!staticText_d->untransformedCoordinates && oldMatrix.isTranslating()) + d->state->matrix = oldMatrix; +} + +/*! + \internal +*/ +void QPainter::drawText(const QPointF &p, const QString &str, int tf, int justificationPadding) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawText(), pos=[%.2f,%.2f], str='%s'\n", p.x(), p.y(), str.toLatin1().constData()); +#endif + + Q_D(QPainter); + + if (!d->engine || str.isEmpty() || pen().style() == Qt::NoPen) + return; + + if (tf & Qt::TextBypassShaping) { + // Skip harfbuzz complex shaping, shape using glyph advances only + int len = str.length(); + int numGlyphs = len; + QVarLengthGlyphLayoutArray glyphs(len); + QFontEngine *fontEngine = d->state->font.d->engineForScript(QUnicodeTables::Common); + if (!fontEngine->stringToCMap(str.data(), len, &glyphs, &numGlyphs, 0)) { + glyphs.resize(numGlyphs); + if (!fontEngine->stringToCMap(str.data(), len, &glyphs, &numGlyphs, 0)) + Q_ASSERT_X(false, Q_FUNC_INFO, "stringToCMap shouldn't fail twice"); + } + + QTextItemInt gf(glyphs, &d->state->font, str.data(), len, fontEngine); + drawTextItem(p, gf); + return; + } + + QStackTextEngine engine(str, d->state->font); + engine.option.setTextDirection(d->state->layoutDirection); + if (tf & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) { + engine.ignoreBidi = true; + engine.option.setTextDirection((tf & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft); + } + engine.itemize(); + QScriptLine line; + line.length = str.length(); + engine.shapeLine(line); + + int nItems = engine.layoutData->items.size(); + QVarLengthArray<int> visualOrder(nItems); + QVarLengthArray<uchar> levels(nItems); + for (int i = 0; i < nItems; ++i) + levels[i] = engine.layoutData->items[i].analysis.bidiLevel; + QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); + + if (justificationPadding > 0) { + engine.option.setAlignment(Qt::AlignJustify); + engine.forceJustification = true; + // this works because justify() is only interested in the difference between width and textWidth + line.width = justificationPadding; + engine.justify(line); + } + QFixed x = QFixed::fromReal(p.x()); + + for (int i = 0; i < nItems; ++i) { + int item = visualOrder[i]; + const QScriptItem &si = engine.layoutData->items.at(item); + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { + x += si.width; + continue; + } + QFont f = engine.font(si); + QTextItemInt gf(si, &f); + gf.glyphs = engine.shapedGlyphs(&si); + gf.chars = engine.layoutData->string.unicode() + si.position; + gf.num_chars = engine.length(item); + if (engine.forceJustification) { + for (int j=0; j<gf.glyphs.numGlyphs; ++j) + gf.width += gf.glyphs.effectiveAdvance(j); + } else { + gf.width = si.width; + } + gf.logClusters = engine.logClusters(&si); + + drawTextItem(QPointF(x.toReal(), p.y()), gf); + + x += gf.width; + } +} + +void QPainter::drawText(const QRect &r, int flags, const QString &str, QRect *br) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawText(), r=[%d,%d,%d,%d], flags=%d, str='%s'\n", + r.x(), r.y(), r.width(), r.height(), flags, str.toLatin1().constData()); +#endif + + Q_D(QPainter); + + if (!d->engine || str.length() == 0 || pen().style() == Qt::NoPen) + return; + + if (!d->extended) + d->updateState(d->state); + + QRectF bounds; + qt_format_text(d->state->font, r, flags, 0, str, br ? &bounds : 0, 0, 0, 0, this); + if (br) + *br = bounds.toAlignedRect(); +} + +/*! + \fn void QPainter::drawText(const QPoint &position, const QString &text) + + \overload + + Draws the given \a text with the currently defined text direction, + beginning at the given \a position. + + By default, QPainter draws text anti-aliased. + + \note The y-position is used as the baseline of the font. + +*/ + +/*! + \fn void QPainter::drawText(const QRectF &rectangle, int flags, const QString &text, QRectF *boundingRect) + \overload + + Draws the given \a text within the provided \a rectangle. + + \table 100% + \row + \o \inlineimage qpainter-text.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 17 + \endtable + + The \a boundingRect (if not null) is set to the what the bounding rectangle + should be in order to enclose the whole text. The \a flags argument is a bitwise + OR of the following flags: + + \list + \o Qt::AlignLeft + \o Qt::AlignRight + \o Qt::AlignHCenter + \o Qt::AlignJustify + \o Qt::AlignTop + \o Qt::AlignBottom + \o Qt::AlignVCenter + \o Qt::AlignCenter + \o Qt::TextDontClip + \o Qt::TextSingleLine + \o Qt::TextExpandTabs + \o Qt::TextShowMnemonic + \o Qt::TextWordWrap + \o Qt::TextIncludeTrailingSpaces + \endlist + + \sa Qt::AlignmentFlag, Qt::TextFlag, boundingRect(), layoutDirection() + + By default, QPainter draws text anti-aliased. + + \note The y-coordinate of \a rectangle is used as the top of the font. +*/ +void QPainter::drawText(const QRectF &r, int flags, const QString &str, QRectF *br) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawText(), r=[%.2f,%.2f,%.2f,%.2f], flags=%d, str='%s'\n", + r.x(), r.y(), r.width(), r.height(), flags, str.toLatin1().constData()); +#endif + + Q_D(QPainter); + + if (!d->engine || str.length() == 0 || pen().style() == Qt::NoPen) + return; + + if (!d->extended) + d->updateState(d->state); + + qt_format_text(d->state->font, r, flags, 0, str, br, 0, 0, 0, this); +} + +/*! + \fn void QPainter::drawText(const QRect &rectangle, int flags, const QString &text, QRect *boundingRect) + \overload + + Draws the given \a text within the provided \a rectangle according + to the specified \a flags. The \a boundingRect (if not null) is set to + the what the bounding rectangle should be in order to enclose the whole text. + + By default, QPainter draws text anti-aliased. + + \note The y-coordinate of \a rectangle is used as the top of the font. +*/ + +/*! + \fn void QPainter::drawText(int x, int y, const QString &text) + + \overload + + Draws the given \a text at position (\a{x}, \a{y}), using the painter's + currently defined text direction. + + By default, QPainter draws text anti-aliased. + + \note The y-position is used as the baseline of the font. + +*/ + +/*! + \fn void QPainter::drawText(int x, int y, int width, int height, int flags, + const QString &text, QRect *boundingRect) + + \overload + + Draws the given \a text within the rectangle with origin (\a{x}, + \a{y}), \a width and \a height. + + The \a boundingRect (if not null) is set to the actual bounding + rectangle of the output. The \a flags argument is a bitwise OR of + the following flags: + + \list + \o Qt::AlignLeft + \o Qt::AlignRight + \o Qt::AlignHCenter + \o Qt::AlignJustify + \o Qt::AlignTop + \o Qt::AlignBottom + \o Qt::AlignVCenter + \o Qt::AlignCenter + \o Qt::TextSingleLine + \o Qt::TextExpandTabs + \o Qt::TextShowMnemonic + \o Qt::TextWordWrap + \endlist + + By default, QPainter draws text anti-aliased. + + \note The y-position is used as the top of the font. + + \sa Qt::AlignmentFlag, Qt::TextFlag +*/ + +/*! + \fn void QPainter::drawText(const QRectF &rectangle, const QString &text, + const QTextOption &option) + \overload + + Draws the given \a text in the \a rectangle specified using the \a option + to control its positioning and orientation. + + By default, QPainter draws text anti-aliased. + + \note The y-coordinate of \a rectangle is used as the top of the font. +*/ +void QPainter::drawText(const QRectF &r, const QString &text, const QTextOption &o) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawText(), r=[%.2f,%.2f,%.2f,%.2f], str='%s'\n", + r.x(), r.y(), r.width(), r.height(), text.toLatin1().constData()); +#endif + + Q_D(QPainter); + + if (!d->engine || text.length() == 0 || pen().style() == Qt::NoPen) + return; + + if (!d->extended) + d->updateState(d->state); + + qt_format_text(d->state->font, r, 0, &o, text, 0, 0, 0, 0, this); +} + +/*! + \fn void QPainter::drawTextItem(int x, int y, const QTextItem &ti) + + \internal + \overload +*/ + +/*! + \fn void QPainter::drawTextItem(const QPoint &p, const QTextItem &ti) + + \internal + \overload + + Draws the text item \a ti at position \a p. +*/ + +/*! + \fn void QPainter::drawTextItem(const QPointF &p, const QTextItem &ti) + + \internal + \since 4.1 + + Draws the text item \a ti at position \a p. + + This method ignores the painters background mode and + color. drawText and qt_format_text have to do it themselves, as + only they know the extents of the complete string. + + It ignores the font set on the painter as the text item has one of its own. + + The underline and strikeout parameters of the text items font are + ignored aswell. You'll need to pass in the correct flags to get + underlining and strikeout. +*/ + +static QPixmap generateWavyPixmap(qreal maxRadius, const QPen &pen) +{ + const qreal radiusBase = qMax(qreal(1), maxRadius); + + QString key = QLatin1Literal("WaveUnderline-") + % pen.color().name() + % HexString<qreal>(radiusBase); + + QPixmap pixmap; + if (QPixmapCache::find(key, pixmap)) + return pixmap; + + const qreal halfPeriod = qMax(qreal(2), qreal(radiusBase * 1.61803399)); // the golden ratio + const int width = qCeil(100 / (2 * halfPeriod)) * (2 * halfPeriod); + const int radius = qFloor(radiusBase); + + QPainterPath path; + + qreal xs = 0; + qreal ys = radius; + + while (xs < width) { + xs += halfPeriod; + ys = -ys; + path.quadTo(xs - halfPeriod / 2, ys, xs, 0); + } + + pixmap = QPixmap(width, radius * 2); + pixmap.fill(Qt::transparent); + { + QPen wavePen = pen; + wavePen.setCapStyle(Qt::SquareCap); + + // This is to protect against making the line too fat, as happens on Mac OS X + // due to it having a rather thick width for the regular underline. + const qreal maxPenWidth = .8 * radius; + if (wavePen.widthF() > maxPenWidth) + wavePen.setWidth(maxPenWidth); + + QPainter imgPainter(&pixmap); + imgPainter.setPen(wavePen); + imgPainter.setRenderHint(QPainter::Antialiasing); + imgPainter.translate(0, radius); + imgPainter.drawPath(path); + } + + QPixmapCache::insert(key, pixmap); + + return pixmap; +} + +static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QFontEngine *fe, + QTextCharFormat::UnderlineStyle underlineStyle, + QTextItem::RenderFlags flags, qreal width, + const QTextCharFormat &charFormat) +{ + if (underlineStyle == QTextCharFormat::NoUnderline + && !(flags & (QTextItem::StrikeOut | QTextItem::Overline))) + return; + + const QPen oldPen = painter->pen(); + const QBrush oldBrush = painter->brush(); + painter->setBrush(Qt::NoBrush); + QPen pen = oldPen; + pen.setStyle(Qt::SolidLine); + pen.setWidthF(fe->lineThickness().toReal()); + pen.setCapStyle(Qt::FlatCap); + + QLineF line(pos.x(), pos.y(), pos.x() + qFloor(width), pos.y()); + + const qreal underlineOffset = fe->underlinePosition().toReal(); + // deliberately ceil the offset to avoid the underline coming too close to + // the text above it. + const qreal aliasedCoordinateDelta = 0.5 - 0.015625; + const qreal underlinePos = pos.y() + qCeil(underlineOffset) - aliasedCoordinateDelta; + + if (underlineStyle == QTextCharFormat::SpellCheckUnderline) { + underlineStyle = QTextCharFormat::UnderlineStyle(QApplication::style()->styleHint(QStyle::SH_SpellCheckUnderlineStyle)); + } + + if (underlineStyle == QTextCharFormat::WaveUnderline) { + painter->save(); + painter->translate(0, pos.y() + 1); + + QColor uc = charFormat.underlineColor(); + if (uc.isValid()) + pen.setColor(uc); + + // Adapt wave to underlineOffset or pen width, whatever is larger, to make it work on all platforms + const QPixmap wave = generateWavyPixmap(qMax(underlineOffset, pen.widthF()), pen); + const int descent = (int) fe->descent().toReal(); + + painter->setBrushOrigin(painter->brushOrigin().x(), 0); + painter->fillRect(pos.x(), 0, qCeil(width), qMin(wave.height(), descent), wave); + painter->restore(); + } else if (underlineStyle != QTextCharFormat::NoUnderline) { + QLineF underLine(line.x1(), underlinePos, line.x2(), underlinePos); + + QColor uc = charFormat.underlineColor(); + if (uc.isValid()) + pen.setColor(uc); + + pen.setStyle((Qt::PenStyle)(underlineStyle)); + painter->setPen(pen); + painter->drawLine(underLine); + } + + pen.setStyle(Qt::SolidLine); + pen.setColor(oldPen.color()); + + if (flags & QTextItem::StrikeOut) { + QLineF strikeOutLine = line; + strikeOutLine.translate(0., - fe->ascent().toReal() / 3.); + painter->setPen(pen); + painter->drawLine(strikeOutLine); + } + + if (flags & QTextItem::Overline) { + QLineF overLine = line; + overLine.translate(0., - fe->ascent().toReal()); + painter->setPen(pen); + painter->drawLine(overLine); + } + + painter->setPen(oldPen); + painter->setBrush(oldBrush); +} + +Q_GUI_EXPORT void qt_draw_decoration_for_glyphs(QPainter *painter, const glyph_t *glyphArray, + const QFixedPoint *positions, int glyphCount, + QFontEngine *fontEngine, const QFont &font, + const QTextCharFormat &charFormat) +{ + if (!(font.underline() || font.strikeOut() || font.overline())) + return; + + QFixed leftMost; + QFixed rightMost; + QFixed baseLine; + for (int i=0; i<glyphCount; ++i) { + glyph_metrics_t gm = fontEngine->boundingBox(glyphArray[i]); + if (i == 0 || leftMost > positions[i].x) + leftMost = positions[i].x; + + // We don't support glyphs that do not share a common baseline. If this turns out to + // be a relevant use case, then we need to find clusters of glyphs that share a baseline + // and do a drawTextItemDecorations call per cluster. + if (i == 0 || baseLine < positions[i].y) + baseLine = positions[i].y; + + // We use the advance rather than the actual bounds to match the algorithm in drawText() + if (i == 0 || rightMost < positions[i].x + gm.xoff) + rightMost = positions[i].x + gm.xoff; + } + + QFixed width = rightMost - leftMost; + QTextItem::RenderFlags flags = 0; + + if (font.underline()) + flags |= QTextItem::Underline; + if (font.overline()) + flags |= QTextItem::Overline; + if (font.strikeOut()) + flags |= QTextItem::StrikeOut; + + drawTextItemDecoration(painter, QPointF(leftMost.toReal(), baseLine.toReal()), + fontEngine, + font.underline() ? QTextCharFormat::SingleUnderline + : QTextCharFormat::NoUnderline, flags, + width.toReal(), charFormat); +} + +void QPainter::drawTextItem(const QPointF &p, const QTextItem &_ti) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawTextItem(), pos=[%.f,%.f], str='%s'\n", + p.x(), p.y(), qPrintable(_ti.text())); +#endif + + Q_D(QPainter); + + if (!d->engine) + return; + +#ifndef QT_NO_DEBUG + qt_painter_thread_test(d->device->devType(), + "text and fonts", + QFontDatabase::supportsThreadedFontRendering()); +#endif + + QTextItemInt &ti = const_cast<QTextItemInt &>(static_cast<const QTextItemInt &>(_ti)); + + if (!d->extended && d->state->bgMode == Qt::OpaqueMode) { + QRectF rect(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()); + fillRect(rect, d->state->bgBrush); + } + + if (pen().style() == Qt::NoPen) + return; + + const RenderHints oldRenderHints = d->state->renderHints; + if (!d->state->renderHints & QPainter::Antialiasing && d->state->matrix.type() >= QTransform::TxScale) { + // draw antialias decoration (underline/overline/strikeout) with + // transformed text + + bool aa = true; + const QTransform &m = d->state->matrix; + if (d->state->matrix.type() < QTransform::TxShear) { + bool isPlain90DegreeRotation = + (qFuzzyIsNull(m.m11()) + && qFuzzyIsNull(m.m12() - qreal(1)) + && qFuzzyIsNull(m.m21() + qreal(1)) + && qFuzzyIsNull(m.m22()) + ) + || + (qFuzzyIsNull(m.m11() + qreal(1)) + && qFuzzyIsNull(m.m12()) + && qFuzzyIsNull(m.m21()) + && qFuzzyIsNull(m.m22() + qreal(1)) + ) + || + (qFuzzyIsNull(m.m11()) + && qFuzzyIsNull(m.m12() + qreal(1)) + && qFuzzyIsNull(m.m21() - qreal(1)) + && qFuzzyIsNull(m.m22()) + ) + ; + aa = !isPlain90DegreeRotation; + } + if (aa) + setRenderHint(QPainter::Antialiasing, true); + } + + if (!d->extended) + d->updateState(d->state); + + if (!ti.glyphs.numGlyphs) { + // nothing to do + } else if (ti.fontEngine->type() == QFontEngine::Multi) { + QFontEngineMulti *multi = static_cast<QFontEngineMulti *>(ti.fontEngine); + + const QGlyphLayout &glyphs = ti.glyphs; + int which = glyphs.glyphs[0] >> 24; + + qreal x = p.x(); + qreal y = p.y(); + + int start = 0; + int end, i; + for (end = 0; end < ti.glyphs.numGlyphs; ++end) { + const int e = glyphs.glyphs[end] >> 24; + if (e == which) + continue; + + + QTextItemInt ti2 = ti.midItem(multi->engine(which), start, end - start); + ti2.width = 0; + // set the high byte to zero and calc the width + for (i = start; i < end; ++i) { + glyphs.glyphs[i] = glyphs.glyphs[i] & 0xffffff; + ti2.width += ti.glyphs.effectiveAdvance(i); + } + + d->engine->drawTextItem(QPointF(x, y), ti2); + + // reset the high byte for all glyphs and advance to the next sub-string + const int hi = which << 24; + for (i = start; i < end; ++i) { + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + } + x += ti2.width.toReal(); + + // change engine + start = end; + which = e; + } + + QTextItemInt ti2 = ti.midItem(multi->engine(which), start, end - start); + ti2.width = 0; + // set the high byte to zero and calc the width + for (i = start; i < end; ++i) { + glyphs.glyphs[i] = glyphs.glyphs[i] & 0xffffff; + ti2.width += ti.glyphs.effectiveAdvance(i); + } + + if (d->extended) + d->extended->drawTextItem(QPointF(x, y), ti2); + else + d->engine->drawTextItem(QPointF(x,y), ti2); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + } else { + if (d->extended) + d->extended->drawTextItem(p, ti); + else + d->engine->drawTextItem(p, ti); + } + drawTextItemDecoration(this, p, ti.fontEngine, ti.underlineStyle, ti.flags, ti.width.toReal(), + ti.charFormat); + + if (d->state->renderHints != oldRenderHints) { + d->state->renderHints = oldRenderHints; + if (d->extended) + d->extended->renderHintsChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyHints; + } +} + +/*! + \fn QRectF QPainter::boundingRect(const QRectF &rectangle, int flags, const QString &text) + + Returns the bounding rectangle of the \a text as it will appear + when drawn inside the given \a rectangle with the specified \a + flags using the currently set font(); i.e the function tells you + where the drawText() function will draw when given the same + arguments. + + If the \a text does not fit within the given \a rectangle using + the specified \a flags, the function returns the required + rectangle. + + The \a flags argument is a bitwise OR of the following flags: + \list + \o Qt::AlignLeft + \o Qt::AlignRight + \o Qt::AlignHCenter + \o Qt::AlignTop + \o Qt::AlignBottom + \o Qt::AlignVCenter + \o Qt::AlignCenter + \o Qt::TextSingleLine + \o Qt::TextExpandTabs + \o Qt::TextShowMnemonic + \o Qt::TextWordWrap + \o Qt::TextIncludeTrailingSpaces + \endlist + If several of the horizontal or several of the vertical alignment + flags are set, the resulting alignment is undefined. + + \sa drawText(), Qt::Alignment, Qt::TextFlag +*/ + +/*! + \fn QRect QPainter::boundingRect(const QRect &rectangle, int flags, + const QString &text) + + \overload + + Returns the bounding rectangle of the \a text as it will appear + when drawn inside the given \a rectangle with the specified \a + flags using the currently set font(). +*/ + +/*! + \fn QRect QPainter::boundingRect(int x, int y, int w, int h, int flags, + const QString &text); + + \overload + + Returns the bounding rectangle of the given \a text as it will + appear when drawn inside the rectangle beginning at the point + (\a{x}, \a{y}) with width \a w and height \a h. +*/ +QRect QPainter::boundingRect(const QRect &rect, int flags, const QString &str) +{ + if (str.isEmpty()) + return QRect(rect.x(),rect.y(), 0,0); + QRect brect; + drawText(rect, flags | Qt::TextDontPrint, str, &brect); + return brect; +} + + + +QRectF QPainter::boundingRect(const QRectF &rect, int flags, const QString &str) +{ + if (str.isEmpty()) + return QRectF(rect.x(),rect.y(), 0,0); + QRectF brect; + drawText(rect, flags | Qt::TextDontPrint, str, &brect); + return brect; +} + +/*! + \fn QRectF QPainter::boundingRect(const QRectF &rectangle, + const QString &text, const QTextOption &option) + + \overload + + Instead of specifying flags as a bitwise OR of the + Qt::AlignmentFlag and Qt::TextFlag, this overloaded function takes + an \a option argument. The QTextOption class provides a + description of general rich text properties. + + \sa QTextOption +*/ +QRectF QPainter::boundingRect(const QRectF &r, const QString &text, const QTextOption &o) +{ + Q_D(QPainter); + + if (!d->engine || text.length() == 0) + return QRectF(r.x(),r.y(), 0,0); + + QRectF br; + qt_format_text(d->state->font, r, Qt::TextDontPrint, &o, text, &br, 0, 0, 0, this); + return br; +} + +/*! + \fn void QPainter::drawTiledPixmap(const QRectF &rectangle, const QPixmap &pixmap, const QPointF &position) + + Draws a tiled \a pixmap, inside the given \a rectangle with its + origin at the given \a position. + + Calling drawTiledPixmap() is similar to calling drawPixmap() + several times to fill (tile) an area with a pixmap, but is + potentially much more efficient depending on the underlying window + system. + + \sa drawPixmap() +*/ +void QPainter::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &sp) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::drawTiledPixmap(), target=[%.2f,%.2f,%.2f,%.2f], pix=[%d,%d], offset=[%.2f,%.2f]\n", + r.x(), r.y(), r.width(), r.height(), + pixmap.width(), pixmap.height(), + sp.x(), sp.y()); +#endif + + Q_D(QPainter); + if (!d->engine || pixmap.isNull() || r.isEmpty()) + return; + +#ifndef QT_NO_DEBUG + qt_painter_thread_test(d->device->devType(), "drawTiledPixmap()", true); +#endif + + qreal sw = pixmap.width(); + qreal sh = pixmap.height(); + qreal sx = sp.x(); + qreal sy = sp.y(); + if (sx < 0) + sx = qRound(sw) - qRound(-sx) % qRound(sw); + else + sx = qRound(sx) % qRound(sw); + if (sy < 0) + sy = qRound(sh) - -qRound(sy) % qRound(sh); + else + sy = qRound(sy) % qRound(sh); + + + if (d->extended) { + d->extended->drawTiledPixmap(r, pixmap, QPointF(sx, sy)); + return; + } + + if (d->state->bgMode == Qt::OpaqueMode && pixmap.isQBitmap()) + fillRect(r, d->state->bgBrush); + + d->updateState(d->state); + if ((d->state->matrix.type() > QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) + || (d->state->opacity != 1.0 && !d->engine->hasFeature(QPaintEngine::ConstantOpacity))) + { + save(); + setBackgroundMode(Qt::TransparentMode); + setRenderHint(Antialiasing, renderHints() & SmoothPixmapTransform); + setBrush(QBrush(d->state->pen.color(), pixmap)); + setPen(Qt::NoPen); + + // If there is no rotation involved we have to make sure we use the + // antialiased and not the aliased coordinate system by rounding the coordinates. + if (d->state->matrix.type() <= QTransform::TxScale) { + const QPointF p = roundInDeviceCoordinates(r.topLeft(), d->state->matrix); + + if (d->state->matrix.type() <= QTransform::TxTranslate) { + sx = qRound(sx); + sy = qRound(sy); + } + + setBrushOrigin(QPointF(r.x()-sx, r.y()-sy)); + drawRect(QRectF(p, r.size())); + } else { + setBrushOrigin(QPointF(r.x()-sx, r.y()-sy)); + drawRect(r); + } + restore(); + return; + } + + qreal x = r.x(); + qreal y = r.y(); + if (d->state->matrix.type() == QTransform::TxTranslate + && !d->engine->hasFeature(QPaintEngine::PixmapTransform)) { + x += d->state->matrix.dx(); + y += d->state->matrix.dy(); + } + + d->engine->drawTiledPixmap(QRectF(x, y, r.width(), r.height()), pixmap, QPointF(sx, sy)); +} + +/*! + \fn QPainter::drawTiledPixmap(const QRect &rectangle, const QPixmap &pixmap, + const QPoint &position = QPoint()) + \overload + + Draws a tiled \a pixmap, inside the given \a rectangle with its + origin at the given \a position. +*/ + +/*! + \fn void QPainter::drawTiledPixmap(int x, int y, int width, int height, const + QPixmap &pixmap, int sx, int sy); + \overload + + Draws a tiled \a pixmap in the specified rectangle. + + (\a{x}, \a{y}) specifies the top-left point in the paint device + that is to be drawn onto; with the given \a width and \a + height. (\a{sx}, \a{sy}) specifies the top-left point in the \a + pixmap that is to be drawn; this defaults to (0, 0). +*/ + +#ifndef QT_NO_PICTURE + +/*! + \fn void QPainter::drawPicture(const QPointF &point, const QPicture &picture) + + Replays the given \a picture at the given \a point. + + The QPicture class is a paint device that records and replays + QPainter commands. A picture serializes the painter commands to an + IO device in a platform-independent format. Everything that can be + painted on a widget or pixmap can also be stored in a picture. + + This function does exactly the same as QPicture::play() when + called with \a point = QPoint(0, 0). + + \table 100% + \row + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 18 + \endtable + + \sa QPicture::play() +*/ + +void QPainter::drawPicture(const QPointF &p, const QPicture &picture) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if (!d->extended) + d->updateState(d->state); + + save(); + translate(p); + const_cast<QPicture *>(&picture)->play(this); + restore(); +} + +/*! + \fn void QPainter::drawPicture(const QPoint &point, const QPicture &picture) + \overload + + Replays the given \a picture at the given \a point. +*/ + +/*! + \fn void QPainter::drawPicture(int x, int y, const QPicture &picture) + \overload + + Draws the given \a picture at point (\a x, \a y). +*/ + +#endif // QT_NO_PICTURE + +/*! + \fn void QPainter::eraseRect(const QRectF &rectangle) + + Erases the area inside the given \a rectangle. Equivalent to + calling + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 19 + + \sa fillRect() +*/ +void QPainter::eraseRect(const QRectF &r) +{ + Q_D(QPainter); + + fillRect(r, d->state->bgBrush); +} + +static inline bool needsResolving(const QBrush &brush) +{ + Qt::BrushStyle s = brush.style(); + return ((s == Qt::LinearGradientPattern || s == Qt::RadialGradientPattern || + s == Qt::ConicalGradientPattern) && + brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode); +} + +/*! + \fn void QPainter::eraseRect(const QRect &rectangle) + \overload + + Erases the area inside the given \a rectangle. +*/ + +/*! + \fn void QPainter::eraseRect(int x, int y, int width, int height) + \overload + + Erases the area inside the rectangle beginning at (\a x, \a y) + with the given \a width and \a height. +*/ + + +/*! + \fn void QPainter::fillRect(int x, int y, int width, int height, Qt::BrushStyle style) + \overload + + Fills the rectangle beginning at (\a{x}, \a{y}) with the given \a + width and \a height, using the brush \a style specified. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(const QRect &rectangle, Qt::BrushStyle style) + \overload + + Fills the given \a rectangle with the brush \a style specified. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(const QRectF &rectangle, Qt::BrushStyle style) + \overload + + Fills the given \a rectangle with the brush \a style specified. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(const QRectF &rectangle, const QBrush &brush) + + Fills the given \a rectangle with the \a brush specified. + + Alternatively, you can specify a QColor instead of a QBrush; the + QBrush constructor (taking a QColor argument) will automatically + create a solid pattern brush. + + \sa drawRect() +*/ +void QPainter::fillRect(const QRectF &r, const QBrush &brush) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if (d->extended) { + const QGradient *g = brush.gradient(); + if (!g || g->coordinateMode() == QGradient::LogicalMode) { + d->extended->fillRect(r, brush); + return; + } + } + + QPen oldPen = pen(); + QBrush oldBrush = this->brush(); + setPen(Qt::NoPen); + if (brush.style() == Qt::SolidPattern) { + d->colorBrush.setStyle(Qt::SolidPattern); + d->colorBrush.setColor(brush.color()); + setBrush(d->colorBrush); + } else { + setBrush(brush); + } + + drawRect(r); + setBrush(oldBrush); + setPen(oldPen); +} + +/*! + \fn void QPainter::fillRect(const QRect &rectangle, const QBrush &brush) + \overload + + Fills the given \a rectangle with the specified \a brush. +*/ + +void QPainter::fillRect(const QRect &r, const QBrush &brush) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if (d->extended) { + const QGradient *g = brush.gradient(); + if (!g || g->coordinateMode() == QGradient::LogicalMode) { + d->extended->fillRect(r, brush); + return; + } + } + + QPen oldPen = pen(); + QBrush oldBrush = this->brush(); + setPen(Qt::NoPen); + if (brush.style() == Qt::SolidPattern) { + d->colorBrush.setStyle(Qt::SolidPattern); + d->colorBrush.setColor(brush.color()); + setBrush(d->colorBrush); + } else { + setBrush(brush); + } + + drawRect(r); + setBrush(oldBrush); + setPen(oldPen); +} + + + +/*! + \fn void QPainter::fillRect(const QRect &rectangle, const QColor &color) + \overload + + Fills the given \a rectangle with the \a color specified. + + \since 4.5 +*/ +void QPainter::fillRect(const QRect &r, const QColor &color) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if (d->extended) { + d->extended->fillRect(r, color); + return; + } + + fillRect(r, QBrush(color)); +} + + +/*! + \fn void QPainter::fillRect(const QRectF &rectangle, const QColor &color) + \overload + + Fills the given \a rectangle with the \a color specified. + + \since 4.5 +*/ +void QPainter::fillRect(const QRectF &r, const QColor &color) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if (d->extended) { + d->extended->fillRect(r, color); + return; + } + + fillRect(r, QBrush(color)); +} + +/*! + \fn void QPainter::fillRect(int x, int y, int width, int height, const QBrush &brush) + + \overload + + Fills the rectangle beginning at (\a{x}, \a{y}) with the given \a + width and \a height, using the given \a brush. +*/ + +/*! + \fn void QPainter::fillRect(int x, int y, int width, int height, const QColor &color) + + \overload + + Fills the rectangle beginning at (\a{x}, \a{y}) with the given \a + width and \a height, using the given \a color. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(int x, int y, int width, int height, Qt::GlobalColor color) + + \overload + + Fills the rectangle beginning at (\a{x}, \a{y}) with the given \a + width and \a height, using the given \a color. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(const QRect &rectangle, Qt::GlobalColor color); + + \overload + + Fills the given \a rectangle with the specified \a color. + + \since 4.5 +*/ + +/*! + \fn void QPainter::fillRect(const QRectF &rectangle, Qt::GlobalColor color); + + \overload + + Fills the given \a rectangle with the specified \a color. + + \since 4.5 +*/ + +/*! + Sets the given render \a hint on the painter if \a on is true; + otherwise clears the render hint. + + \sa setRenderHints(), renderHints(), {QPainter#Rendering + Quality}{Rendering Quality} +*/ +void QPainter::setRenderHint(RenderHint hint, bool on) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setRenderHint: hint=%x, %s\n", hint, on ? "on" : "off"); +#endif + +#ifndef QT_NO_DEBUG + static const bool antialiasingDisabled = qgetenv("QT_NO_ANTIALIASING").toInt(); + if (hint == QPainter::Antialiasing && antialiasingDisabled) + return; +#endif + + setRenderHints(hint, on); +} + +/*! + \since 4.2 + + Sets the given render \a hints on the painter if \a on is true; + otherwise clears the render hints. + + \sa setRenderHint(), renderHints(), {QPainter#Rendering + Quality}{Rendering Quality} +*/ + +void QPainter::setRenderHints(RenderHints hints, bool on) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setRenderHint: Painter must be active to set rendering hints"); + return; + } + + if (on) + d->state->renderHints |= hints; + else + d->state->renderHints &= ~hints; + + if (d->extended) + d->extended->renderHintsChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyHints; +} + +/*! + Returns a flag that specifies the rendering hints that are set for + this painter. + + \sa testRenderHint(), {QPainter#Rendering Quality}{Rendering Quality} +*/ +QPainter::RenderHints QPainter::renderHints() const +{ + Q_D(const QPainter); + + if (!d->engine) + return 0; + + return d->state->renderHints; +} + +/*! + \fn bool QPainter::testRenderHint(RenderHint hint) const + \since 4.3 + + Returns true if \a hint is set; otherwise returns false. + + \sa renderHints(), setRenderHint() +*/ + +/*! + Returns true if view transformation is enabled; otherwise returns + false. + + \sa setViewTransformEnabled(), worldTransform() +*/ + +bool QPainter::viewTransformEnabled() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::viewTransformEnabled: Painter not active"); + return false; + } + return d->state->VxF; +} + +/*! + \fn void QPainter::setWindow(const QRect &rectangle) + + Sets the painter's window to the given \a rectangle, and enables + view transformations. + + The window rectangle is part of the view transformation. The + window specifies the logical coordinate system. Its sister, the + viewport(), specifies the device coordinate system. + + The default window rectangle is the same as the device's + rectangle. + + \sa window(), viewTransformEnabled(), {Coordinate + System#Window-Viewport Conversion}{Window-Viewport Conversion} +*/ + +/*! + \fn void QPainter::setWindow(int x, int y, int width, int height) + \overload + + Sets the painter's window to the rectangle beginning at (\a x, \a + y) and the given \a width and \a height. +*/ + +void QPainter::setWindow(const QRect &r) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setWindow(), [%d,%d,%d,%d]\n", r.x(), r.y(), r.width(), r.height()); +#endif + + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setWindow: Painter not active"); + return; + } + + d->state->wx = r.x(); + d->state->wy = r.y(); + d->state->ww = r.width(); + d->state->wh = r.height(); + + d->state->VxF = true; + d->updateMatrix(); +} + +/*! + Returns the window rectangle. + + \sa setWindow(), setViewTransformEnabled() +*/ + +QRect QPainter::window() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::window: Painter not active"); + return QRect(); + } + return QRect(d->state->wx, d->state->wy, d->state->ww, d->state->wh); +} + +/*! + \fn void QPainter::setViewport(const QRect &rectangle) + + Sets the painter's viewport rectangle to the given \a rectangle, + and enables view transformations. + + The viewport rectangle is part of the view transformation. The + viewport specifies the device coordinate system. Its sister, the + window(), specifies the logical coordinate system. + + The default viewport rectangle is the same as the device's + rectangle. + + \sa viewport(), viewTransformEnabled() {Coordinate + System#Window-Viewport Conversion}{Window-Viewport Conversion} +*/ + +/*! + \fn void QPainter::setViewport(int x, int y, int width, int height) + \overload + + Sets the painter's viewport rectangle to be the rectangle + beginning at (\a x, \a y) with the given \a width and \a height. +*/ + +void QPainter::setViewport(const QRect &r) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setViewport(), [%d,%d,%d,%d]\n", r.x(), r.y(), r.width(), r.height()); +#endif + + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setViewport: Painter not active"); + return; + } + + d->state->vx = r.x(); + d->state->vy = r.y(); + d->state->vw = r.width(); + d->state->vh = r.height(); + + d->state->VxF = true; + d->updateMatrix(); +} + +/*! + Returns the viewport rectangle. + + \sa setViewport(), setViewTransformEnabled() +*/ + +QRect QPainter::viewport() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::viewport: Painter not active"); + return QRect(); + } + return QRect(d->state->vx, d->state->vy, d->state->vw, d->state->vh); +} + +/*! \fn bool QPainter::hasViewXForm() const + \compat + + Use viewTransformEnabled() instead. +*/ + +/*! \fn bool QPainter::hasWorldXForm() const + \compat + + Use worldMatrixEnabled() instead. +*/ + +/*! \fn void QPainter::resetXForm() + \compat + + Use resetTransform() instead. +*/ + +/*! \fn void QPainter::setViewXForm(bool enabled) + \compat + + Use setViewTransformEnabled() instead. +*/ + +/*! \fn void QPainter::setWorldXForm(bool enabled) + \compat + + Use setWorldMatrixEnabled() instead. +*/ +/*! + Enables view transformations if \a enable is true, or disables + view transformations if \a enable is false. + + \sa viewTransformEnabled(), {Coordinate System#Window-Viewport + Conversion}{Window-Viewport Conversion} +*/ + +void QPainter::setViewTransformEnabled(bool enable) +{ +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::setViewTransformEnabled(), enable=%d\n", enable); +#endif + + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setViewTransformEnabled: Painter not active"); + return; + } + + if (enable == d->state->VxF) + return; + + d->state->VxF = enable; + d->updateMatrix(); +} + +#ifdef QT3_SUPPORT + +/*! + \obsolete + + Use the worldTransform() combined with QTransform::dx() instead. + + \oldcode + QPainter painter(this); + qreal x = painter.translationX(); + \newcode + QPainter painter(this); + qreal x = painter.worldTransform().dx(); + \endcode +*/ +qreal QPainter::translationX() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::translationX: Painter not active"); + return 0.0; + } + return d->state->worldMatrix.dx(); +} + +/*! + \obsolete + + Use the worldTransform() combined with QTransform::dy() instead. + + \oldcode + QPainter painter(this); + qreal y = painter.translationY(); + \newcode + QPainter painter(this); + qreal y = painter.worldTransform().dy(); + \endcode +*/ +qreal QPainter::translationY() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::translationY: Painter not active"); + return 0.0; + } + return d->state->worldMatrix.dy(); +} + +/*! + \fn void QPainter::map(int x, int y, int *rx, int *ry) const + + \internal + + Sets (\a{rx}, \a{ry}) to the point that results from applying the + painter's current transformation on the point (\a{x}, \a{y}). +*/ +void QPainter::map(int x, int y, int *rx, int *ry) const +{ + QPoint p(x, y); + p = p * combinedMatrix(); + *rx = p.x(); + *ry = p.y(); +} + +/*! + \fn QPoint QPainter::xForm(const QPoint &point) const + + Use combinedTransform() instead. +*/ + +QPoint QPainter::xForm(const QPoint &p) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xForm: Painter not active"); + return QPoint(); + } + if (d->state->matrix.type() == QTransform::TxNone) + return p; + return p * combinedMatrix(); +} + + +/*! + \fn QRect QPainter::xForm(const QRect &rectangle) const + \overload + + Use combinedTransform() instead of this function and call + mapRect() on the result to obtain a QRect. +*/ + +QRect QPainter::xForm(const QRect &r) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xForm: Painter not active"); + return QRect(); + } + if (d->state->matrix.type() == QTransform::TxNone) + return r; + return combinedMatrix().mapRect(r); +} + +/*! + \fn QPolygon QPainter::xForm(const QPolygon &polygon) const + \overload + + Use combinedTransform() instead. +*/ + +QPolygon QPainter::xForm(const QPolygon &a) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xForm: Painter not active"); + return QPolygon(); + } + if (d->state->matrix.type() == QTransform::TxNone) + return a; + return a * combinedMatrix(); +} + +/*! + \fn QPolygon QPainter::xForm(const QPolygon &polygon, int index, int count) const + \overload + + Use combinedTransform() combined with QPolygon::mid() instead. + + \oldcode + QPainter painter(this); + QPolygon transformed = painter.xForm(polygon, index, count) + \newcode + QPainter painter(this); + QPolygon transformed = polygon.mid(index, count) * painter.combinedTransform(); + \endcode +*/ + +QPolygon QPainter::xForm(const QPolygon &av, int index, int npoints) const +{ + int lastPoint = npoints < 0 ? av.size() : index+npoints; + QPolygon a(lastPoint-index); + memcpy(a.data(), av.data()+index, (lastPoint-index)*sizeof(QPoint)); + return a * combinedMatrix(); +} + +/*! + \fn QPoint QPainter::xFormDev(const QPoint &point) const + \overload + \obsolete + + Use combinedTransform() combined with QTransform::inverted() instead. + + \oldcode + QPainter painter(this); + QPoint transformed = painter.xFormDev(point); + \newcode + QPainter painter(this); + QPoint transformed = point * painter.combinedTransform().inverted(); + \endcode +*/ + +QPoint QPainter::xFormDev(const QPoint &p) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xFormDev: Painter not active"); + return QPoint(); + } + if(d->state->matrix.type() == QTransform::TxNone) + return p; + return p * combinedMatrix().inverted(); +} + +/*! + \fn QRect QPainter::xFormDev(const QRect &rectangle) const + \overload + \obsolete + + Use combinedTransform() combined with QTransform::inverted() instead. + + \oldcode + QPainter painter(this); + QRect transformed = painter.xFormDev(rectangle); + \newcode + QPainter painter(this); + QRegion region = QRegion(rectangle) * painter.combinedTransform().inverted(); + QRect transformed = region.boundingRect(); + \endcode +*/ + +QRect QPainter::xFormDev(const QRect &r) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xFormDev: Painter not active"); + return QRect(); + } + if (d->state->matrix.type() == QTransform::TxNone) + return r; + return combinedMatrix().inverted().mapRect(r); +} + +/*! + \overload + + \fn QPoint QPainter::xFormDev(const QPolygon &polygon) const + \obsolete + + Use combinedTransform() combined with QTransform::inverted() instead. + + \oldcode + QPainter painter(this); + QPolygon transformed = painter.xFormDev(rectangle); + \newcode + QPainter painter(this); + QPolygon transformed = polygon * painter.combinedTransform().inverted(); + \endcode +*/ + +QPolygon QPainter::xFormDev(const QPolygon &a) const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::xFormDev: Painter not active"); + return QPolygon(); + } + if (d->state->matrix.type() == QTransform::TxNone) + return a; + return a * combinedMatrix().inverted(); +} + +/*! + \fn QPolygon QPainter::xFormDev(const QPolygon &polygon, int index, int count) const + \overload + \obsolete + + Use combinedTransform() combined with QPolygon::mid() and QTransform::inverted() instead. + + \oldcode + QPainter painter(this); + QPolygon transformed = painter.xFormDev(polygon, index, count); + \newcode + QPainter painter(this); + QPolygon transformed = polygon.mid(index, count) * painter.combinedTransform().inverted(); + \endcode +*/ + +QPolygon QPainter::xFormDev(const QPolygon &ad, int index, int npoints) const +{ + Q_D(const QPainter); + int lastPoint = npoints < 0 ? ad.size() : index+npoints; + QPolygon a(lastPoint-index); + memcpy(a.data(), ad.data()+index, (lastPoint-index)*sizeof(QPoint)); + if (d->state->matrix.type() == QTransform::TxNone) + return a; + return a * combinedMatrix().inverted(); +} + +/*! + \fn void QPainter::drawCubicBezier(const QPolygon &controlPoints, int index) + + Draws a cubic Bezier curve defined by the \a controlPoints, + starting at \a{controlPoints}\e{[index]} (\a index defaults to 0). + Points after \a{controlPoints}\e{[index + 3]} are ignored. Nothing + happens if there aren't enough control points. + + Use strokePath() instead. + + \oldcode + QPainter painter(this); + painter.drawCubicBezier(controlPoints, index) + \newcode + QPainterPath path; + path.moveTo(controlPoints.at(index)); + path.cubicTo(controlPoints.at(index+1), + controlPoints.at(index+2), + controlPoints.at(index+3)); + + QPainter painter(this); + painter.strokePath(path, painter.pen()); + \endcode +*/ +void QPainter::drawCubicBezier(const QPolygon &a, int index) +{ + Q_D(QPainter); + + if (!d->engine) + return; + + if ((int)a.size() - index < 4) { + qWarning("QPainter::drawCubicBezier: Cubic Bezier needs 4 control " + "points"); + return; + } + + QPainterPath path; + path.moveTo(a.at(index)); + path.cubicTo(a.at(index+1), a.at(index+2), a.at(index+3)); + strokePath(path, d->state->pen); +} +#endif + +struct QPaintDeviceRedirection +{ + QPaintDeviceRedirection() : device(0), replacement(0), internalWidgetRedirectionIndex(-1) {} + QPaintDeviceRedirection(const QPaintDevice *device, QPaintDevice *replacement, + const QPoint& offset, int internalWidgetRedirectionIndex) + : device(device), replacement(replacement), offset(offset), + internalWidgetRedirectionIndex(internalWidgetRedirectionIndex) { } + const QPaintDevice *device; + QPaintDevice *replacement; + QPoint offset; + int internalWidgetRedirectionIndex; + bool operator==(const QPaintDevice *pdev) const { return device == pdev; } + Q_DUMMY_COMPARISON_OPERATOR(QPaintDeviceRedirection) +}; + +typedef QList<QPaintDeviceRedirection> QPaintDeviceRedirectionList; +Q_GLOBAL_STATIC(QPaintDeviceRedirectionList, globalRedirections) +Q_GLOBAL_STATIC(QMutex, globalRedirectionsMutex) +Q_GLOBAL_STATIC(QAtomicInt, globalRedirectionAtomic) + +/*! + \threadsafe + + \obsolete + + Please use QWidget::render() instead. + + Redirects all paint commands for the given paint \a device, to the + \a replacement device. The optional point \a offset defines an + offset within the source device. + + The redirection will not be effective until the begin() function + has been called; make sure to call end() for the given \a + device's painter (if any) before redirecting. Call + restoreRedirected() to restore the previous redirection. + + \warning Making use of redirections in the QPainter API implies + that QPainter::begin() and QPaintDevice destructors need to hold + a mutex for a short period. This can impact performance. Use of + QWidget::render is strongly encouraged. + + \sa redirected(), restoreRedirected() +*/ +void QPainter::setRedirected(const QPaintDevice *device, + QPaintDevice *replacement, + const QPoint &offset) +{ + Q_ASSERT(device != 0); + + bool hadInternalWidgetRedirection = false; + if (device->devType() == QInternal::Widget) { + const QWidgetPrivate *widgetPrivate = static_cast<const QWidget *>(device)->d_func(); + // This is the case when the widget is in a paint event. + if (widgetPrivate->redirectDev) { + // Remove internal redirection and put it back into the global redirection list. + QPoint oldOffset; + QPaintDevice *oldReplacement = widgetPrivate->redirected(&oldOffset); + const_cast<QWidgetPrivate *>(widgetPrivate)->restoreRedirected(); + setRedirected(device, oldReplacement, oldOffset); + hadInternalWidgetRedirection = true; + } + } + + QPoint roffset; + QPaintDevice *rdev = redirected(replacement, &roffset); + + QMutexLocker locker(globalRedirectionsMutex()); + QPaintDeviceRedirectionList *redirections = globalRedirections(); + Q_ASSERT(redirections != 0); + *redirections += QPaintDeviceRedirection(device, rdev ? rdev : replacement, offset + roffset, + hadInternalWidgetRedirection ? redirections->size() - 1 : -1); + globalRedirectionAtomic()->ref(); +} + +/*! + \threadsafe + + \obsolete + + Using QWidget::render() obsoletes the use of this function. + + Restores the previous redirection for the given \a device after a + call to setRedirected(). + + \warning Making use of redirections in the QPainter API implies + that QPainter::begin() and QPaintDevice destructors need to hold + a mutex for a short period. This can impact performance. Use of + QWidget::render is strongly encouraged. + + \sa redirected() + */ +void QPainter::restoreRedirected(const QPaintDevice *device) +{ + Q_ASSERT(device != 0); + QMutexLocker locker(globalRedirectionsMutex()); + QPaintDeviceRedirectionList *redirections = globalRedirections(); + Q_ASSERT(redirections != 0); + for (int i = redirections->size()-1; i >= 0; --i) { + if (redirections->at(i) == device) { + globalRedirectionAtomic()->deref(); + const int internalWidgetRedirectionIndex = redirections->at(i).internalWidgetRedirectionIndex; + redirections->removeAt(i); + // Restore the internal widget redirection, i.e. remove it from the global + // redirection list and put it back into QWidgetPrivate. The index is only set when + // someone call QPainter::setRedirected in a widget's paint event and we internally + // have a redirection set (typically set in QWidgetPrivate::drawWidget). + if (internalWidgetRedirectionIndex >= 0) { + Q_ASSERT(internalWidgetRedirectionIndex < redirections->size()); + const QPaintDeviceRedirection &redirectionDevice = redirections->at(internalWidgetRedirectionIndex); + QWidget *widget = static_cast<QWidget *>(const_cast<QPaintDevice *>(device)); + widget->d_func()->setRedirected(redirectionDevice.replacement, redirectionDevice.offset); + redirections->removeAt(internalWidgetRedirectionIndex); + } + return; + } + } +} + +/*! + \threadsafe + + \obsolete + + Using QWidget::render() obsoletes the use of this function. + + Returns the replacement for given \a device. The optional out + parameter \a offset returns the offset within the replaced device. + + \warning Making use of redirections in the QPainter API implies + that QPainter::begin() and QPaintDevice destructors need to hold + a mutex for a short period. This can impact performance. Use of + QWidget::render is strongly encouraged. + + \sa setRedirected(), restoreRedirected() +*/ +QPaintDevice *QPainter::redirected(const QPaintDevice *device, QPoint *offset) +{ + Q_ASSERT(device != 0); + + if (device->devType() == QInternal::Widget) { + const QWidgetPrivate *widgetPrivate = static_cast<const QWidget *>(device)->d_func(); + if (widgetPrivate->redirectDev) + return widgetPrivate->redirected(offset); + } + + if (!globalRedirectionAtomic() || *globalRedirectionAtomic() == 0) + return 0; + + QMutexLocker locker(globalRedirectionsMutex()); + QPaintDeviceRedirectionList *redirections = globalRedirections(); + Q_ASSERT(redirections != 0); + for (int i = redirections->size()-1; i >= 0; --i) + if (redirections->at(i) == device) { + if (offset) + *offset = redirections->at(i).offset; + return redirections->at(i).replacement; + } + if (offset) + *offset = QPoint(0, 0); + return 0; +} + + +void qt_painter_removePaintDevice(QPaintDevice *dev) +{ + if (!globalRedirectionAtomic() || *globalRedirectionAtomic() == 0) + return; + + QMutex *mutex = 0; + QT_TRY { + mutex = globalRedirectionsMutex(); + } QT_CATCH(...) { + // ignore the missing mutex, since we could be called from + // a destructor, and destructors shall not throw + } + QMutexLocker locker(mutex); + QPaintDeviceRedirectionList *redirections = 0; + QT_TRY { + redirections = globalRedirections(); + } QT_CATCH(...) { + // do nothing - code below is safe with redirections being 0. + } + if (redirections) { + for (int i = 0; i < redirections->size(); ) { + if(redirections->at(i) == dev || redirections->at(i).replacement == dev) + redirections->removeAt(i); + else + ++i; + } + } +} + +void qt_format_text(const QFont &fnt, const QRectF &_r, + int tf, const QString& str, QRectF *brect, + int tabstops, int *ta, int tabarraylen, + QPainter *painter) +{ + qt_format_text(fnt, _r, + tf, 0, str, brect, + tabstops, ta, tabarraylen, + painter); +} +void qt_format_text(const QFont &fnt, const QRectF &_r, + int tf, const QTextOption *option, const QString& str, QRectF *brect, + int tabstops, int *ta, int tabarraylen, + QPainter *painter) +{ + + Q_ASSERT( !((tf & ~Qt::TextDontPrint)!=0 && option!=0) ); // we either have an option or flags + + if (option) { + tf |= option->alignment(); + if (option->wrapMode() != QTextOption::NoWrap) + tf |= Qt::TextWordWrap; + + if (option->flags() & QTextOption::IncludeTrailingSpaces) + tf |= Qt::TextIncludeTrailingSpaces; + + if (option->tabStop() >= 0 || !option->tabArray().isEmpty()) + tf |= Qt::TextExpandTabs; + } + + // we need to copy r here to protect against the case (&r == brect). + QRectF r(_r); + + bool dontclip = (tf & Qt::TextDontClip); + bool wordwrap = (tf & Qt::TextWordWrap) || (tf & Qt::TextWrapAnywhere); + bool singleline = (tf & Qt::TextSingleLine); + bool showmnemonic = (tf & Qt::TextShowMnemonic); + bool hidemnmemonic = (tf & Qt::TextHideMnemonic); + + Qt::LayoutDirection layout_direction; + if (tf & Qt::TextForceLeftToRight) + layout_direction = Qt::LeftToRight; + else if (tf & Qt::TextForceRightToLeft) + layout_direction = Qt::RightToLeft; + else if (option) + layout_direction = option->textDirection(); + else if (painter) + layout_direction = painter->layoutDirection(); + else + layout_direction = Qt::LeftToRight; + + tf = QStyle::visualAlignment(layout_direction, QFlag(tf)); + + bool isRightToLeft = layout_direction == Qt::RightToLeft; + bool expandtabs = ((tf & Qt::TextExpandTabs) && + (((tf & Qt::AlignLeft) && !isRightToLeft) || + ((tf & Qt::AlignRight) && isRightToLeft))); + + if (!painter) + tf |= Qt::TextDontPrint; + + uint maxUnderlines = 0; + int numUnderlines = 0; + QVarLengthArray<int, 32> underlinePositions(1); + + QFontMetricsF fm(fnt); + QString text = str; + int offset = 0; +start_lengthVariant: + bool hasMoreLengthVariants = false; + // compatible behaviour to the old implementation. Replace + // tabs by spaces + int old_offset = offset; + for (; offset < text.length(); offset++) { + QChar chr = text.at(offset); + if (chr == QLatin1Char('\r') || (singleline && chr == QLatin1Char('\n'))) { + text[offset] = QLatin1Char(' '); + } else if (chr == QLatin1Char('\n')) { + text[offset] = QChar::LineSeparator; + } else if (chr == QLatin1Char('&')) { + ++maxUnderlines; + } else if (chr == QLatin1Char('\t')) { + if (!expandtabs) { + text[offset] = QLatin1Char(' '); + } else if (!tabarraylen && !tabstops) { + tabstops = qRound(fm.width(QLatin1Char('x'))*8); + } + } else if (chr == QChar(ushort(0x9c))) { + // string with multiple length variants + hasMoreLengthVariants = true; + break; + } + } + + int length = offset - old_offset; + if ((hidemnmemonic || showmnemonic) && maxUnderlines > 0) { + underlinePositions.resize(maxUnderlines + 1); + + QChar *cout = text.data() + old_offset; + QChar *cin = cout; + int l = length; + while (l) { + if (*cin == QLatin1Char('&')) { + ++cin; + --length; + --l; + if (!l) + break; + if (*cin != QLatin1Char('&') && !hidemnmemonic) + underlinePositions[numUnderlines++] = cout - text.data() - old_offset; + } + *cout = *cin; + ++cout; + ++cin; + --l; + } + } + + // no need to do extra work for underlines if we don't paint + if (tf & Qt::TextDontPrint) + numUnderlines = 0; + + underlinePositions[numUnderlines] = -1; + qreal height = 0; + qreal width = 0; + + QString finalText = text.mid(old_offset, length); + QStackTextEngine engine(finalText, fnt); + if (option) { + engine.option = *option; + } + + if (engine.option.tabStop() < 0 && tabstops > 0) + engine.option.setTabStop(tabstops); + + if (engine.option.tabs().isEmpty() && ta) { + QList<qreal> tabs; + for (int i = 0; i < tabarraylen; i++) + tabs.append(qreal(ta[i])); + engine.option.setTabArray(tabs); + } + + engine.option.setTextDirection(layout_direction); + if (tf & Qt::AlignJustify) + engine.option.setAlignment(Qt::AlignJustify); + else + engine.option.setAlignment(Qt::AlignLeft); // do not do alignment twice + + if (!option && (tf & Qt::TextWrapAnywhere)) + engine.option.setWrapMode(QTextOption::WrapAnywhere); + + if (tf & Qt::TextJustificationForced) + engine.forceJustification = true; + QTextLayout textLayout(&engine); + textLayout.setCacheEnabled(true); + textLayout.engine()->underlinePositions = underlinePositions.data(); + + if (finalText.isEmpty()) { + height = fm.height(); + width = 0; + tf |= Qt::TextDontPrint; + } else { + qreal lineWidth = 0x01000000; + if (wordwrap || (tf & Qt::TextJustificationForced)) + lineWidth = qMax<qreal>(0, r.width()); + if(!wordwrap) + tf |= Qt::TextIncludeTrailingSpaces; + textLayout.engine()->ignoreBidi = bool(tf & Qt::TextDontPrint); + textLayout.beginLayout(); + + qreal leading = fm.leading(); + height = -leading; + + while (1) { + QTextLine l = textLayout.createLine(); + if (!l.isValid()) + break; + + l.setLineWidth(lineWidth); + height += leading; + l.setPosition(QPointF(0., height)); + height += l.height(); + width = qMax(width, l.naturalTextWidth()); + if (!dontclip && !brect && height >= r.height()) + break; + } + textLayout.endLayout(); + } + + qreal yoff = 0; + qreal xoff = 0; + if (tf & Qt::AlignBottom) { + yoff = r.height() - height; + } else if (tf & Qt::AlignVCenter) { + yoff = (r.height() - height)/2; + if (painter) { + QTransform::TransformationType type = painter->transform().type(); + if (type <= QTransform::TxScale) { + // do the rounding manually to work around inconsistencies + // in the paint engines when drawing on floating point offsets + const qreal scale = painter->transform().m22(); + if (scale != 0) + yoff = -qRound(-yoff * scale) / scale; + } + } + } + if (tf & Qt::AlignRight) { + xoff = r.width() - width; + } else if (tf & Qt::AlignHCenter) { + xoff = (r.width() - width)/2; + if (painter) { + QTransform::TransformationType type = painter->transform().type(); + if (type <= QTransform::TxScale) { + // do the rounding manually to work around inconsistencies + // in the paint engines when drawing on floating point offsets + const qreal scale = painter->transform().m11(); + if (scale != 0) + xoff = qRound(xoff * scale) / scale; + } + } + } + QRectF bounds = QRectF(r.x() + xoff, r.y() + yoff, width, height); + + if (hasMoreLengthVariants && !(tf & Qt::TextLongestVariant) && !r.contains(bounds)) { + offset++; + goto start_lengthVariant; + } + if (brect) + *brect = bounds; + + if (!(tf & Qt::TextDontPrint)) { + bool restore = false; + if (!dontclip && !r.contains(bounds)) { + restore = true; + painter->save(); + painter->setClipRect(r, Qt::IntersectClip); + } + + for (int i = 0; i < textLayout.lineCount(); i++) { + QTextLine line = textLayout.lineAt(i); + + qreal advance = line.horizontalAdvance(); + xoff = 0; + if (tf & Qt::AlignRight) { + QTextEngine *eng = textLayout.engine(); + xoff = r.width() - advance - + eng->leadingSpaceWidth(eng->lines[line.lineNumber()]).toReal(); + } + else if (tf & Qt::AlignHCenter) + xoff = (r.width() - advance) / 2; + + line.draw(painter, QPointF(r.x() + xoff, r.y() + yoff)); + } + + if (restore) { + painter->restore(); + } + } +} + +/*! + Sets the layout direction used by the painter when drawing text, + to the specified \a direction. + + The default is Qt::LayoutDirectionAuto, which will implicitly determine the + direction from the text drawn. + + \sa QTextOption::setTextDirection(), layoutDirection(), drawText(), {QPainter#Settings}{Settings} +*/ +void QPainter::setLayoutDirection(Qt::LayoutDirection direction) +{ + Q_D(QPainter); + if (d->state) + d->state->layoutDirection = direction; +} + +/*! + Returns the layout direction used by the painter when drawing text. + + \sa QTextOption::textDirection(), setLayoutDirection(), drawText(), {QPainter#Settings}{Settings} +*/ +Qt::LayoutDirection QPainter::layoutDirection() const +{ + Q_D(const QPainter); + return d->state ? d->state->layoutDirection : Qt::LayoutDirectionAuto; +} + +QPainterState::QPainterState(const QPainterState *s) + : brushOrigin(s->brushOrigin), font(s->font), deviceFont(s->deviceFont), + pen(s->pen), brush(s->brush), bgBrush(s->bgBrush), + clipRegion(s->clipRegion), clipPath(s->clipPath), + clipOperation(s->clipOperation), + renderHints(s->renderHints), clipInfo(s->clipInfo), + worldMatrix(s->worldMatrix), matrix(s->matrix), redirectionMatrix(s->redirectionMatrix), + wx(s->wx), wy(s->wy), ww(s->ww), wh(s->wh), + vx(s->vx), vy(s->vy), vw(s->vw), vh(s->vh), + opacity(s->opacity), WxF(s->WxF), VxF(s->VxF), + clipEnabled(s->clipEnabled), bgMode(s->bgMode), painter(s->painter), + layoutDirection(s->layoutDirection), + composition_mode(s->composition_mode), + emulationSpecifier(s->emulationSpecifier), changeFlags(0) +{ + dirtyFlags = s->dirtyFlags; +} + +QPainterState::QPainterState() + : brushOrigin(0, 0), bgBrush(Qt::white), clipOperation(Qt::NoClip), + renderHints(0), + wx(0), wy(0), ww(0), wh(0), vx(0), vy(0), vw(0), vh(0), + opacity(1), WxF(false), VxF(false), clipEnabled(true), + bgMode(Qt::TransparentMode), painter(0), + layoutDirection(QApplication::layoutDirection()), + composition_mode(QPainter::CompositionMode_SourceOver), + emulationSpecifier(0), changeFlags(0) +{ + dirtyFlags = 0; +} + +QPainterState::~QPainterState() +{ +} + +void QPainterState::init(QPainter *p) { + bgBrush = Qt::white; + bgMode = Qt::TransparentMode; + WxF = false; + VxF = false; + clipEnabled = true; + wx = wy = ww = wh = 0; + vx = vy = vw = vh = 0; + painter = p; + pen = QPen(); + brushOrigin = QPointF(0, 0); + brush = QBrush(); + font = deviceFont = QFont(); + clipRegion = QRegion(); + clipPath = QPainterPath(); + clipOperation = Qt::NoClip; + clipInfo.clear(); + worldMatrix.reset(); + matrix.reset(); + layoutDirection = QApplication::layoutDirection(); + composition_mode = QPainter::CompositionMode_SourceOver; + emulationSpecifier = 0; + dirtyFlags = 0; + changeFlags = 0; + renderHints = 0; + opacity = 1; +} + +#ifdef QT3_SUPPORT +static void bitBlt_helper(QPaintDevice *dst, const QPoint &dp, + const QPaintDevice *src, const QRect &sr, bool) +{ + Q_ASSERT(dst); + Q_ASSERT(src); + + if (src->devType() == QInternal::Pixmap) { + const QPixmap *pixmap = static_cast<const QPixmap *>(src); + QPainter pt(dst); + pt.drawPixmap(dp, *pixmap, sr); + + } else { + qWarning("QPainter: bitBlt only works when source is of type pixmap"); + } +} + +void bitBlt(QPaintDevice *dst, int dx, int dy, + const QPaintDevice *src, int sx, int sy, int sw, int sh, + bool ignoreMask ) +{ + bitBlt_helper(dst, QPoint(dx, dy), src, QRect(sx, sy, sw, sh), ignoreMask); +} + +void bitBlt(QPaintDevice *dst, const QPoint &dp, const QPaintDevice *src, const QRect &sr, bool ignoreMask) +{ + bitBlt_helper(dst, dp, src, sr, ignoreMask); +} + +void bitBlt(QPaintDevice *dst, int dx, int dy, + const QImage *src, int sx, int sy, int sw, int sh, int fl) +{ + Qt::ImageConversionFlags flags(fl); + QPixmap srcPixmap = QPixmap::fromImage(*src, flags); + bitBlt_helper(dst, QPoint(dx, dy), &srcPixmap, QRect(sx, sy, sw, sh), false); +} + +#endif // QT3_SUPPORT + +/*! + \fn void QPainter::setBackgroundColor(const QColor &color) + + Use setBackground() instead. +*/ + +/*! + \fn const QColor &QPainter::backgroundColor() const + + Use background() and QBrush::color() instead. + + \oldcode + QColor myColor = backgroundColor(); + \newcode + QColor myColor = background().color(); + \endcode + + Note that the background can be a complex brush such as a texture + or a gradient. +*/ + +/*! + \fn void QPainter::drawText(int x, int y, const QString &text, int pos, int length) + \compat + + Use drawText() combined with QString::mid() instead. + + \oldcode + QPainter painter(this); + painter.drawText(x, y, text, pos, length); + \newcode + QPainter painter(this); + painter.drawText(x, y, text.mid(pos, length)); + \endcode +*/ + +/*! + \fn void QPainter::drawText(const QPoint &point, const QString &text, int pos, int length) + \compat + + Use drawText() combined with QString::mid() instead. + + \oldcode + QPainter painter(this); + painter.drawText(point, text, pos, length); + \newcode + QPainter painter(this); + painter.drawText(point, text.mid(pos, length)); + \endcode +*/ + +/*! + \fn void QPainter::drawText(int x, int y, const QString &text, int length) + \compat + + Use drawText() combined with QString::left() instead. + + \oldcode + QPainter painter(this); + painter.drawText(x, y, text, length); + \newcode + QPainter painter(this); + painter.drawText(x, y, text.left(length)); + \endcode +*/ + +/*! + \fn void QPainter::drawText(const QPoint &point, const QString &text, int length) + \compat + + Use drawText() combined with QString::left() instead. + + \oldcode + QPainter painter(this); + painter.drawText(point, text, length); + \newcode + QPainter painter(this); + painter.drawText(point, text.left(length)); + \endcode +*/ + +/*! + \fn bool QPainter::begin(QPaintDevice *device, const QWidget *init) + \compat + + Use begin() instead. + + If the paint \a device is a QWidget, QPainter is initialized after + the widget's settings automatically. Otherwise, you must call the + initFrom() function to initialize the painters pen, background and + font to the same as any given widget. + + \oldcode + QPainter painter(this); + painter.begin(device, init); + \newcode + QPainter painter(this); + painter.begin(device); + painter.initFrom(init); + \endcode +*/ + +/*! + \fn void QPainter::drawImage(const QRectF &target, const QImage &image, const QRectF &source, + Qt::ImageConversionFlags flags) + + Draws the rectangular portion \a source of the given \a image + into the \a target rectangle in the paint device. + + \note The image is scaled to fit the rectangle, if both the image and rectangle size disagree. + + If the image needs to be modified to fit in a lower-resolution + result (e.g. converting from 32-bit to 8-bit), use the \a flags to + specify how you would prefer this to happen. + + \table 100% + \row + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainter.cpp 20 + \endtable + + \sa drawPixmap() +*/ + +/*! + \fn void QPainter::drawImage(const QRect &target, const QImage &image, const QRect &source, + Qt::ImageConversionFlags flags) + \overload + + Draws the rectangular portion \a source of the given \a image + into the \a target rectangle in the paint device. + + \note The image is scaled to fit the rectangle, if both the image and rectangle size disagree. +*/ + +/*! + \fn void QPainter::drawImage(const QPointF &point, const QImage &image) + + \overload + + Draws the given \a image at the given \a point. +*/ + +/*! + \fn void QPainter::drawImage(const QPoint &point, const QImage &image) + + \overload + + Draws the given \a image at the given \a point. +*/ + +/*! + \fn void QPainter::drawImage(const QPointF &point, const QImage &image, const QRectF &source, + Qt::ImageConversionFlags flags = 0) + + \overload + + Draws the rectangular portion \a source of the given \a image with + its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawImage(const QPoint &point, const QImage &image, const QRect &source, + Qt::ImageConversionFlags flags = 0) + \overload + + Draws the rectangular portion \a source of the given \a image with + its origin at the given \a point. +*/ + +/*! + \fn void QPainter::drawImage(const QRectF &rectangle, const QImage &image) + + \overload + + Draws the given \a image into the given \a rectangle. + + \note The image is scaled to fit the rectangle, if both the image and rectangle size disagree. +*/ + +/*! + \fn void QPainter::drawImage(const QRect &rectangle, const QImage &image) + + \overload + + Draws the given \a image into the given \a rectangle. + + \note The image is scaled to fit the rectangle, if both the image and rectangle size disagree. +*/ + +/*! + \fn void QPainter::drawImage(int x, int y, const QImage &image, + int sx, int sy, int sw, int sh, + Qt::ImageConversionFlags flags) + \overload + + Draws an image at (\a{x}, \a{y}) by copying a part of \a image into + the paint device. + + (\a{x}, \a{y}) specifies the top-left point in the paint device that is + to be drawn onto. (\a{sx}, \a{sy}) specifies the top-left point in \a + image that is to be drawn. The default is (0, 0). + + (\a{sw}, \a{sh}) specifies the size of the image that is to be drawn. + The default, (0, 0) (and negative) means all the way to the + bottom-right of the image. +*/ + +/*! + \fn void QPainter::redirect(QPaintDevice *pdev, QPaintDevice *replacement) + + Use setRedirected() instead. +*/ + +/*! + \fn QPaintDevice *QPainter::redirect(QPaintDevice *pdev) + + Use redirected() instead. +*/ + +/*! + \fn QRect QPainter::boundingRect(const QRect &rectangle, int flags, + const QString &text, int length) + \compat + + Returns the bounding rectangle for the given \a length of the \a + text constrained by the provided \a rectangle. + + Use boundingRect() combined with QString::left() instead. + + \oldcode + QRect rectangle = boundingRect(rect, flags, text, length); + \newcode + QRect rectangle = boundingRect(rect, flags, text.left(length)); + \endcode +*/ + +/*! + \fn void QPainter::drawText(const QRect &rectangle, int flags, const QString &text, + int length, QRect *br) + \compat + + Use drawText() combined with QString::left() instead. + + \oldcode + QPainter painter(this); + painter.drawText(rectangle, flags, text, length, br ); + \newcode + QPainter painter(this); + painter.drawText(rectangle, flags, text.left(length), br ); + \endcode +*/ + +/*! + \fn QRect QPainter::boundingRect(int x, int y, int width, int height, int flags, + const QString &text, int length); + + \compat + + Returns the bounding rectangle for the given \a length of the \a + text constrained by the rectangle that begins at point (\a{x}, + \a{y}) with the given \a width and \a height. + + Use boundingRect() combined with QString::left() instead. + + \oldcode + QRect rectangle = boundingRect(x, y, width, height, flags, text, length); + \newcode + QRect rectangle = boundingRect(x, y, width, height, flags, text.left(length)); + \endcode +*/ + +/*! + \fn void QPainter::drawText(int x, int y, int width, int height, int flags, + const QString &text, int length, QRect *br) + + \compat + + Use drawText() combined with QString::left() instead. + + \oldcode + QPainter painter(this); + painter.drawText(x, y, width, height, flags, text, length, br ); + \newcode + QPainter painter(this); + painter.drawText(x, y, width, height, flags, text.left(length), br ); + \endcode +*/ + + +/*! + \class QPaintEngineState + \since 4.1 + + \brief The QPaintEngineState class provides information about the + active paint engine's current state. + \reentrant + + QPaintEngineState records which properties that have changed since + the last time the paint engine was updated, as well as their + current value. + + Which properties that have changed can at any time be retrieved + using the state() function. This function returns an instance of + the QPaintEngine::DirtyFlags type which stores an OR combination + of QPaintEngine::DirtyFlag values. The QPaintEngine::DirtyFlag + enum defines whether a property has changed since the last update + or not. + + If a property is marked with a dirty flag, its current value can + be retrieved using the corresponding get function: + + \target GetFunction + + \table + \header \o Property Flag \o Current Property Value + \row \o QPaintEngine::DirtyBackground \o backgroundBrush() + \row \o QPaintEngine::DirtyBackgroundMode \o backgroundMode() + \row \o QPaintEngine::DirtyBrush \o brush() + \row \o QPaintEngine::DirtyBrushOrigin \o brushOrigin() + \row \o QPaintEngine::DirtyClipRegion \e or QPaintEngine::DirtyClipPath + \o clipOperation() + \row \o QPaintEngine::DirtyClipPath \o clipPath() + \row \o QPaintEngine::DirtyClipRegion \o clipRegion() + \row \o QPaintEngine::DirtyCompositionMode \o compositionMode() + \row \o QPaintEngine::DirtyFont \o font() + \row \o QPaintEngine::DirtyTransform \o transform() + \row \o QPaintEngine::DirtyClipEnabled \o isClipEnabled() + \row \o QPaintEngine::DirtyPen \o pen() + \row \o QPaintEngine::DirtyHints \o renderHints() + \endtable + + The QPaintEngineState class also provide the painter() function + which returns a pointer to the painter that is currently updating + the paint engine. + + An instance of this class, representing the current state of the + active paint engine, is passed as argument to the + QPaintEngine::updateState() function. The only situation in which + you will have to use this class directly is when implementing your + own paint engine. + + \sa QPaintEngine +*/ + + +/*! + \fn QPaintEngine::DirtyFlags QPaintEngineState::state() const + + Returns a combination of flags identifying the set of properties + that need to be updated when updating the paint engine's state + (i.e. during a call to the QPaintEngine::updateState() function). + + \sa QPaintEngine::updateState() +*/ + + +/*! + Returns the pen in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyPen flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QPen QPaintEngineState::pen() const +{ + return static_cast<const QPainterState *>(this)->pen; +} + +/*! + Returns the brush in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyBrush flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QBrush QPaintEngineState::brush() const +{ + return static_cast<const QPainterState *>(this)->brush; +} + +/*! + Returns the brush origin in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyBrushOrigin flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QPointF QPaintEngineState::brushOrigin() const +{ + return static_cast<const QPainterState *>(this)->brushOrigin; +} + +/*! + Returns the background brush in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyBackground flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QBrush QPaintEngineState::backgroundBrush() const +{ + return static_cast<const QPainterState *>(this)->bgBrush; +} + +/*! + Returns the background mode in the current paint engine + state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyBackgroundMode flag. + + \sa state(), QPaintEngine::updateState() +*/ + +Qt::BGMode QPaintEngineState::backgroundMode() const +{ + return static_cast<const QPainterState *>(this)->bgMode; +} + +/*! + Returns the font in the current paint engine + state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyFont flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QFont QPaintEngineState::font() const +{ + return static_cast<const QPainterState *>(this)->font; +} + +/*! + \since 4.2 + \obsolete + + Returns the matrix in the current paint engine + state. + + \note It is advisable to use transform() instead of this function to + preserve the properties of perspective transformations. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyTransform flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QMatrix QPaintEngineState::matrix() const +{ + const QPainterState *st = static_cast<const QPainterState *>(this); + + return st->matrix.toAffine(); +} + +/*! + \since 4.3 + + Returns the matrix in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyTransform flag. + + \sa state(), QPaintEngine::updateState() +*/ + + +QTransform QPaintEngineState::transform() const +{ + const QPainterState *st = static_cast<const QPainterState *>(this); + + return st->matrix; +} + + +/*! + Returns the clip operation in the current paint engine + state. + + This variable should only be used when the state() returns a + combination which includes either the QPaintEngine::DirtyClipPath + or the QPaintEngine::DirtyClipRegion flag. + + \sa state(), QPaintEngine::updateState() +*/ + +Qt::ClipOperation QPaintEngineState::clipOperation() const +{ + return static_cast<const QPainterState *>(this)->clipOperation; +} + +/*! + \since 4.3 + + Returns whether the coordinate of the fill have been specified + as bounded by the current rendering operation and have to be + resolved (about the currently rendered primitive). +*/ +bool QPaintEngineState::brushNeedsResolving() const +{ + const QBrush &brush = static_cast<const QPainterState *>(this)->brush; + return needsResolving(brush); +} + + +/*! + \since 4.3 + + Returns whether the coordinate of the stroke have been specified + as bounded by the current rendering operation and have to be + resolved (about the currently rendered primitive). +*/ +bool QPaintEngineState::penNeedsResolving() const +{ + const QPen &pen = static_cast<const QPainterState *>(this)->pen; + return needsResolving(pen.brush()); +} + +/*! + Returns the clip region in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyClipRegion flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QRegion QPaintEngineState::clipRegion() const +{ + return static_cast<const QPainterState *>(this)->clipRegion; +} + +/*! + Returns the clip path in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyClipPath flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QPainterPath QPaintEngineState::clipPath() const +{ + return static_cast<const QPainterState *>(this)->clipPath; +} + +/*! + Returns whether clipping is enabled or not in the current paint + engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyClipEnabled + flag. + + \sa state(), QPaintEngine::updateState() +*/ + +bool QPaintEngineState::isClipEnabled() const +{ + return static_cast<const QPainterState *>(this)->clipEnabled; +} + +/*! + Returns the render hints in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyHints + flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QPainter::RenderHints QPaintEngineState::renderHints() const +{ + return static_cast<const QPainterState *>(this)->renderHints; +} + +/*! + Returns the composition mode in the current paint engine state. + + This variable should only be used when the state() returns a + combination which includes the QPaintEngine::DirtyCompositionMode + flag. + + \sa state(), QPaintEngine::updateState() +*/ + +QPainter::CompositionMode QPaintEngineState::compositionMode() const +{ + return static_cast<const QPainterState *>(this)->composition_mode; +} + + +/*! + Returns a pointer to the painter currently updating the paint + engine. +*/ + +QPainter *QPaintEngineState::painter() const +{ + return static_cast<const QPainterState *>(this)->painter; +} + + +/*! + \since 4.2 + + Returns the opacity in the current paint engine state. +*/ + +qreal QPaintEngineState::opacity() const +{ + return static_cast<const QPainterState *>(this)->opacity; +} + +/*! + \since 4.3 + + Sets the world transformation matrix. + If \a combine is true, the specified \a transform is combined with + the current matrix; otherwise it replaces the current matrix. + + \sa transform() setWorldTransform() +*/ + +void QPainter::setTransform(const QTransform &transform, bool combine ) +{ + setWorldTransform(transform, combine); +} + +/*! + Returns the world transformation matrix. + + \sa worldTransform() +*/ + +const QTransform & QPainter::transform() const +{ + return worldTransform(); +} + + +/*! + Returns the matrix that transforms from logical coordinates to + device coordinates of the platform dependent paint device. + + This function is \e only needed when using platform painting + commands on the platform dependent handle (Qt::HANDLE), and the + platform does not do transformations nativly. + + The QPaintEngine::PaintEngineFeature enum can be queried to + determine whether the platform performs the transformations or + not. + + \sa worldTransform(), QPaintEngine::hasFeature(), +*/ + +const QTransform & QPainter::deviceTransform() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::deviceTransform: Painter not active"); + return d->fakeState()->transform; + } + return d->state->matrix; +} + + +/*! + Resets any transformations that were made using translate(), + scale(), shear(), rotate(), setWorldTransform(), setViewport() + and setWindow(). + + \sa {Coordinate Transformations} +*/ + +void QPainter::resetTransform() +{ + Q_D(QPainter); +#ifdef QT_DEBUG_DRAW + if (qt_show_painter_debug_output) + printf("QPainter::resetMatrix()\n"); +#endif + if (!d->engine) { + qWarning("QPainter::resetMatrix: Painter not active"); + return; + } + + d->state->wx = d->state->wy = d->state->vx = d->state->vy = 0; // default view origins + d->state->ww = d->state->vw = d->device->metric(QPaintDevice::PdmWidth); + d->state->wh = d->state->vh = d->device->metric(QPaintDevice::PdmHeight); + d->state->worldMatrix = QTransform(); + setMatrixEnabled(false); + setViewTransformEnabled(false); + if (d->extended) + d->extended->transformChanged(); + else + d->state->dirtyFlags |= QPaintEngine::DirtyTransform; +} + +/*! + Sets the world transformation matrix. + If \a combine is true, the specified \a matrix is combined with the current matrix; + otherwise it replaces the current matrix. + + \sa transform(), setTransform() +*/ + +void QPainter::setWorldTransform(const QTransform &matrix, bool combine ) +{ + Q_D(QPainter); + + if (!d->engine) { + qWarning("QPainter::setWorldTransform: Painter not active"); + return; + } + + if (combine) + d->state->worldMatrix = matrix * d->state->worldMatrix; // combines + else + d->state->worldMatrix = matrix; // set new matrix + + d->state->WxF = true; + d->updateMatrix(); +} + +/*! + Returns the world transformation matrix. +*/ + +const QTransform & QPainter::worldTransform() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::worldTransform: Painter not active"); + return d->fakeState()->transform; + } + return d->state->worldMatrix; +} + +/*! + Returns the transformation matrix combining the current + window/viewport and world transformation. + + \sa setWorldTransform(), setWindow(), setViewport() +*/ + +QTransform QPainter::combinedTransform() const +{ + Q_D(const QPainter); + if (!d->engine) { + qWarning("QPainter::combinedTransform: Painter not active"); + return QTransform(); + } + return d->state->worldMatrix * d->viewTransform(); +} + +/*! + \since 4.7 + + This function is used to draw \a pixmap, or a sub-rectangle of \a pixmap, + at multiple positions with different scale, rotation and opacity. \a + fragments is an array of \a fragmentCount elements specifying the + parameters used to draw each pixmap fragment. The \a hints + parameter can be used to pass in drawing hints. + + This function is potentially faster than multiple calls to drawPixmap(), + since the backend can optimize state changes. + + \sa QPainter::PixmapFragment, QPainter::PixmapFragmentHint +*/ + +void QPainter::drawPixmapFragments(const PixmapFragment *fragments, int fragmentCount, + const QPixmap &pixmap, PixmapFragmentHints hints) +{ + Q_D(QPainter); + + if (!d->engine || pixmap.isNull()) + return; + +#ifndef QT_NO_DEBUG + for (int i = 0; i < fragmentCount; ++i) { + QRectF sourceRect(fragments[i].sourceLeft, fragments[i].sourceTop, + fragments[i].width, fragments[i].height); + if (!(QRectF(pixmap.rect()).contains(sourceRect))) + qWarning("QPainter::drawPixmapFragments - the source rect is not contained by the pixmap's rectangle"); + } +#endif + + if (d->engine->isExtended()) { + d->extended->drawPixmapFragments(fragments, fragmentCount, pixmap, hints); + } else { + qreal oldOpacity = opacity(); + QTransform oldTransform = transform(); + + for (int i = 0; i < fragmentCount; ++i) { + QTransform transform = oldTransform; + qreal xOffset = 0; + qreal yOffset = 0; + if (fragments[i].rotation == 0) { + xOffset = fragments[i].x; + yOffset = fragments[i].y; + } else { + transform.translate(fragments[i].x, fragments[i].y); + transform.rotate(fragments[i].rotation); + } + setOpacity(oldOpacity * fragments[i].opacity); + setTransform(transform); + + qreal w = fragments[i].scaleX * fragments[i].width; + qreal h = fragments[i].scaleY * fragments[i].height; + QRectF sourceRect(fragments[i].sourceLeft, fragments[i].sourceTop, + fragments[i].width, fragments[i].height); + drawPixmap(QRectF(-0.5 * w + xOffset, -0.5 * h + yOffset, w, h), pixmap, sourceRect); + } + + setOpacity(oldOpacity); + setTransform(oldTransform); + } +} + +/*! + \since 4.7 + \class QPainter::PixmapFragment + + \brief This class is used in conjunction with the + QPainter::drawPixmapFragments() function to specify how a pixmap, or + sub-rect of a pixmap, is drawn. + + The \a sourceLeft, \a sourceTop, \a width and \a height variables are used + as a source rectangle within the pixmap passed into the + QPainter::drawPixmapFragments() function. The variables \a x, \a y, \a + width and \a height are used to calculate the target rectangle that is + drawn. \a x and \a y denotes the center of the target rectangle. The \a + width and \a height in the target rectangle is scaled by the \a scaleX and + \a scaleY values. The resulting target rectangle is then rotated \a + rotation degrees around the \a x, \a y center point. + + \sa QPainter::drawPixmapFragments() +*/ + +/*! + \since 4.7 + + This is a convenience function that returns a QPainter::PixmapFragment that is + initialized with the \a pos, \a sourceRect, \a scaleX, \a scaleY, \a + rotation, \a opacity parameters. +*/ + +QPainter::PixmapFragment QPainter::PixmapFragment::create(const QPointF &pos, const QRectF &sourceRect, + qreal scaleX, qreal scaleY, qreal rotation, + qreal opacity) +{ + PixmapFragment fragment = {pos.x(), pos.y(), sourceRect.x(), sourceRect.y(), sourceRect.width(), + sourceRect.height(), scaleX, scaleY, rotation, opacity}; + return fragment; +} + +/*! + \variable QPainter::PixmapFragment::x + \brief the x coordinate of center point in the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::y + \brief the y coordinate of the center point in the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::sourceLeft + \brief the left coordinate of the source rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::sourceTop + \brief the top coordinate of the source rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::width + + \brief the width of the source rectangle and is used to calculate the width + of the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::height + + \brief the height of the source rectangle and is used to calculate the + height of the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::scaleX + \brief the horizontal scale of the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::scaleY + \brief the vertical scale of the target rectangle. +*/ + +/*! + \variable QPainter::PixmapFragment::rotation + + \brief the rotation of the target rectangle in degrees. The target + rectangle is rotated after it has been scaled. +*/ + +/*! + \variable QPainter::PixmapFragment::opacity + + \brief the opacity of the target rectangle, where 0.0 is fully transparent + and 1.0 is fully opaque. +*/ + +/*! + \since 4.7 + + \enum QPainter::PixmapFragmentHint + + \value OpaqueHint Indicates that the pixmap fragments to be drawn are + opaque. Opaque fragments are potentially faster to draw. + + \sa QPainter::drawPixmapFragments(), QPainter::PixmapFragment +*/ + +void qt_draw_helper(QPainterPrivate *p, const QPainterPath &path, QPainterPrivate::DrawOperation operation) +{ + p->draw_helper(path, operation); +} + +/*! \fn Display *QPaintDevice::x11Display() const + Use QX11Info::display() instead. + + \oldcode + Display *display = widget->x11Display(); + \newcode + Display *display = QX11Info::display(); + \endcode + + \sa QWidget::x11Info(), QX11Info::display() +*/ + +/*! \fn int QPaintDevice::x11Screen() const + Use QX11Info::screen() instead. + + \oldcode + int screen = widget->x11Screen(); + \newcode + int screen = widget->x11Info().screen(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn void *QPaintDevice::x11Visual() const + Use QX11Info::visual() instead. + + \oldcode + void *visual = widget->x11Visual(); + \newcode + void *visual = widget->x11Info().visual(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11Depth() const + Use QX11Info::depth() instead. + + \oldcode + int depth = widget->x11Depth(); + \newcode + int depth = widget->x11Info().depth(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11Cells() const + Use QX11Info::cells() instead. + + \oldcode + int cells = widget->x11Cells(); + \newcode + int cells = widget->x11Info().cells(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn Qt::HANDLE QPaintDevice::x11Colormap() const + Use QX11Info::colormap() instead. + + \oldcode + unsigned long screen = widget->x11Colormap(); + \newcode + unsigned long screen = widget->x11Info().colormap(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn bool QPaintDevice::x11DefaultColormap() const + Use QX11Info::defaultColormap() instead. + + \oldcode + bool isDefault = widget->x11DefaultColormap(); + \newcode + bool isDefault = widget->x11Info().defaultColormap(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn bool QPaintDevice::x11DefaultVisual() const + Use QX11Info::defaultVisual() instead. + + \oldcode + bool isDefault = widget->x11DefaultVisual(); + \newcode + bool isDefault = widget->x11Info().defaultVisual(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn void *QPaintDevice::x11AppVisual(int screen) + Use QX11Info::visual() instead. + + \oldcode + void *visual = QPaintDevice::x11AppVisual(screen); + \newcode + void *visual = qApp->x11Info(screen).visual(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn Qt::HANDLE QPaintDevice::x11AppColormap(int screen) + Use QX11Info::colormap() instead. + + \oldcode + unsigned long colormap = QPaintDevice::x11AppColormap(screen); + \newcode + unsigned long colormap = qApp->x11Info(screen).colormap(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn Display *QPaintDevice::x11AppDisplay() + Use QX11Info::display() instead. + + \oldcode + Display *display = QPaintDevice::x11AppDisplay(); + \newcode + Display *display = qApp->x11Info().display(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11AppScreen() + Use QX11Info::screen() instead. + + \oldcode + int screen = QPaintDevice::x11AppScreen(); + \newcode + int screen = qApp->x11Info().screen(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11AppDepth(int screen) + Use QX11Info::depth() instead. + + \oldcode + int depth = QPaintDevice::x11AppDepth(screen); + \newcode + int depth = qApp->x11Info(screen).depth(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11AppCells(int screen) + Use QX11Info::cells() instead. + + \oldcode + int cells = QPaintDevice::x11AppCells(screen); + \newcode + int cells = qApp->x11Info(screen).cells(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn Qt::HANDLE QPaintDevice::x11AppRootWindow(int screen) + Use QX11Info::appRootWindow() instead. + + \oldcode + unsigned long window = QPaintDevice::x11AppRootWindow(screen); + \newcode + unsigned long window = qApp->x11Info(screen).appRootWindow(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn bool QPaintDevice::x11AppDefaultColormap(int screen) + Use QX11Info::defaultColormap() instead. + + \oldcode + bool isDefault = QPaintDevice::x11AppDefaultColormap(screen); + \newcode + bool isDefault = qApp->x11Info(screen).defaultColormap(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn bool QPaintDevice::x11AppDefaultVisual(int screen) + Use QX11Info::defaultVisual() instead. + + \oldcode + bool isDefault = QPaintDevice::x11AppDefaultVisual(screen); + \newcode + bool isDefault = qApp->x11Info(screen).defaultVisual(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn void QPaintDevice::x11SetAppDpiX(int dpi, int screen) + Use QX11Info::setAppDpiX() instead. +*/ + +/*! \fn void QPaintDevice::x11SetAppDpiY(int dpi, int screen) + Use QX11Info::setAppDpiY() instead. +*/ + +/*! \fn int QPaintDevice::x11AppDpiX(int screen) + Use QX11Info::appDpiX() instead. + + \oldcode + bool isDefault = QPaintDevice::x11AppDpiX(screen); + \newcode + bool isDefault = qApp->x11Info(screen).appDpiX(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn int QPaintDevice::x11AppDpiY(int screen) + Use QX11Info::appDpiY() instead. + + \oldcode + bool isDefault = QPaintDevice::x11AppDpiY(screen); + \newcode + bool isDefault = qApp->x11Info(screen).appDpiY(); + \endcode + + \sa QWidget::x11Info(), QPixmap::x11Info() +*/ + +/*! \fn HDC QPaintDevice::getDC() const + \internal +*/ + +/*! \fn void QPaintDevice::releaseDC(HDC) const + \internal +*/ + +/*! \fn QWSDisplay *QPaintDevice::qwsDisplay() + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpainter.h b/src/gui/painting/qpainter.h new file mode 100644 index 0000000000..4b2c447cca --- /dev/null +++ b/src/gui/painting/qpainter.h @@ -0,0 +1,1008 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTER_H +#define QPAINTER_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qrect.h> +#include <QtCore/qpoint.h> +#include <QtCore/qscopedpointer.h> +#include <QtGui/qpixmap.h> +#include <QtGui/qimage.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qdrawutil.h> + +#ifndef QT_INCLUDE_COMPAT +#include <QtGui/qpolygon.h> +#include <QtGui/qpen.h> +#include <QtGui/qbrush.h> +#include <QtGui/qmatrix.h> +#include <QtGui/qtransform.h> +#include <QtGui/qfontinfo.h> +#include <QtGui/qfontmetrics.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QBrush; +class QFontInfo; +class QFontMetrics; +class QPaintDevice; +class QPainterPath; +class QPainterPrivate; +class QPen; +class QPolygon; +class QTextItem; +class QMatrix; +class QTransform; +class QStaticText; +class QGlyphs; + +class QPainterPrivateDeleter; + +class Q_GUI_EXPORT QPainter +{ + Q_DECLARE_PRIVATE(QPainter) + Q_GADGET + Q_FLAGS(RenderHint RenderHints) + +public: + enum RenderHint { + Antialiasing = 0x01, + TextAntialiasing = 0x02, + SmoothPixmapTransform = 0x04, + HighQualityAntialiasing = 0x08, + NonCosmeticDefaultPen = 0x10 + }; + + Q_DECLARE_FLAGS(RenderHints, RenderHint) + + class PixmapFragment { + public: + qreal x; + qreal y; + qreal sourceLeft; + qreal sourceTop; + qreal width; + qreal height; + qreal scaleX; + qreal scaleY; + qreal rotation; + qreal opacity; + static PixmapFragment Q_GUI_EXPORT create(const QPointF &pos, const QRectF &sourceRect, + qreal scaleX = 1, qreal scaleY = 1, + qreal rotation = 0, qreal opacity = 1); + }; + + enum PixmapFragmentHint { + OpaqueHint = 0x01 + }; + + Q_DECLARE_FLAGS(PixmapFragmentHints, PixmapFragmentHint) + + QPainter(); + explicit QPainter(QPaintDevice *); + ~QPainter(); + + QPaintDevice *device() const; + + bool begin(QPaintDevice *); + bool end(); + bool isActive() const; + + void initFrom(const QWidget *widget); + + enum CompositionMode { + CompositionMode_SourceOver, + CompositionMode_DestinationOver, + CompositionMode_Clear, + CompositionMode_Source, + CompositionMode_Destination, + CompositionMode_SourceIn, + CompositionMode_DestinationIn, + CompositionMode_SourceOut, + CompositionMode_DestinationOut, + CompositionMode_SourceAtop, + CompositionMode_DestinationAtop, + CompositionMode_Xor, + + //svg 1.2 blend modes + CompositionMode_Plus, + CompositionMode_Multiply, + CompositionMode_Screen, + CompositionMode_Overlay, + CompositionMode_Darken, + CompositionMode_Lighten, + CompositionMode_ColorDodge, + CompositionMode_ColorBurn, + CompositionMode_HardLight, + CompositionMode_SoftLight, + CompositionMode_Difference, + CompositionMode_Exclusion, + + // ROPs + RasterOp_SourceOrDestination, + RasterOp_SourceAndDestination, + RasterOp_SourceXorDestination, + RasterOp_NotSourceAndNotDestination, + RasterOp_NotSourceOrNotDestination, + RasterOp_NotSourceXorDestination, + RasterOp_NotSource, + RasterOp_NotSourceAndDestination, + RasterOp_SourceAndNotDestination + }; + void setCompositionMode(CompositionMode mode); + CompositionMode compositionMode() const; + + const QFont &font() const; + void setFont(const QFont &f); + + QFontMetrics fontMetrics() const; + QFontInfo fontInfo() const; + + void setPen(const QColor &color); + void setPen(const QPen &pen); + void setPen(Qt::PenStyle style); + const QPen &pen() const; + + void setBrush(const QBrush &brush); + void setBrush(Qt::BrushStyle style); + const QBrush &brush() const; + + // attributes/modes + void setBackgroundMode(Qt::BGMode mode); + Qt::BGMode backgroundMode() const; + + QPoint brushOrigin() const; + inline void setBrushOrigin(int x, int y); + inline void setBrushOrigin(const QPoint &); + void setBrushOrigin(const QPointF &); + + void setBackground(const QBrush &bg); + const QBrush &background() const; + + qreal opacity() const; + void setOpacity(qreal opacity); + + // Clip functions + QRegion clipRegion() const; + QPainterPath clipPath() const; + + void setClipRect(const QRectF &, Qt::ClipOperation op = Qt::ReplaceClip); + void setClipRect(const QRect &, Qt::ClipOperation op = Qt::ReplaceClip); + inline void setClipRect(int x, int y, int w, int h, Qt::ClipOperation op = Qt::ReplaceClip); + + void setClipRegion(const QRegion &, Qt::ClipOperation op = Qt::ReplaceClip); + + void setClipPath(const QPainterPath &path, Qt::ClipOperation op = Qt::ReplaceClip); + + void setClipping(bool enable); + bool hasClipping() const; + + QRectF clipBoundingRect() const; + + void save(); + void restore(); + + // XForm functions + void setMatrix(const QMatrix &matrix, bool combine = false); + const QMatrix &matrix() const; + const QMatrix &deviceMatrix() const; + void resetMatrix(); + + void setTransform(const QTransform &transform, bool combine = false); + const QTransform &transform() const; + const QTransform &deviceTransform() const; + void resetTransform(); + + void setWorldMatrix(const QMatrix &matrix, bool combine = false); + const QMatrix &worldMatrix() const; + + void setWorldTransform(const QTransform &matrix, bool combine = false); + const QTransform &worldTransform() const; + + QMatrix combinedMatrix() const; + QTransform combinedTransform() const; + + void setMatrixEnabled(bool enabled); + bool matrixEnabled() const; + + void setWorldMatrixEnabled(bool enabled); + bool worldMatrixEnabled() const; + + void scale(qreal sx, qreal sy); + void shear(qreal sh, qreal sv); + void rotate(qreal a); + + void translate(const QPointF &offset); + inline void translate(const QPoint &offset); + inline void translate(qreal dx, qreal dy); + + QRect window() const; + void setWindow(const QRect &window); + inline void setWindow(int x, int y, int w, int h); + + QRect viewport() const; + void setViewport(const QRect &viewport); + inline void setViewport(int x, int y, int w, int h); + + void setViewTransformEnabled(bool enable); + bool viewTransformEnabled() const; + + // drawing functions + void strokePath(const QPainterPath &path, const QPen &pen); + void fillPath(const QPainterPath &path, const QBrush &brush); + void drawPath(const QPainterPath &path); + + inline void drawPoint(const QPointF &pt); + inline void drawPoint(const QPoint &p); + inline void drawPoint(int x, int y); + + void drawPoints(const QPointF *points, int pointCount); + inline void drawPoints(const QPolygonF &points); + void drawPoints(const QPoint *points, int pointCount); + inline void drawPoints(const QPolygon &points); + + inline void drawLine(const QLineF &line); + inline void drawLine(const QLine &line); + inline void drawLine(int x1, int y1, int x2, int y2); + inline void drawLine(const QPoint &p1, const QPoint &p2); + inline void drawLine(const QPointF &p1, const QPointF &p2); + + void drawLines(const QLineF *lines, int lineCount); + inline void drawLines(const QVector<QLineF> &lines); + void drawLines(const QPointF *pointPairs, int lineCount); + inline void drawLines(const QVector<QPointF> &pointPairs); + void drawLines(const QLine *lines, int lineCount); + inline void drawLines(const QVector<QLine> &lines); + void drawLines(const QPoint *pointPairs, int lineCount); + inline void drawLines(const QVector<QPoint> &pointPairs); + + inline void drawRect(const QRectF &rect); + inline void drawRect(int x1, int y1, int w, int h); + inline void drawRect(const QRect &rect); + + void drawRects(const QRectF *rects, int rectCount); + inline void drawRects(const QVector<QRectF> &rectangles); + void drawRects(const QRect *rects, int rectCount); + inline void drawRects(const QVector<QRect> &rectangles); + + void drawEllipse(const QRectF &r); + void drawEllipse(const QRect &r); + inline void drawEllipse(int x, int y, int w, int h); + + inline void drawEllipse(const QPointF ¢er, qreal rx, qreal ry); + inline void drawEllipse(const QPoint ¢er, int rx, int ry); + + void drawPolyline(const QPointF *points, int pointCount); + inline void drawPolyline(const QPolygonF &polyline); + void drawPolyline(const QPoint *points, int pointCount); + inline void drawPolyline(const QPolygon &polygon); + + void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill); + inline void drawPolygon(const QPolygonF &polygon, Qt::FillRule fillRule = Qt::OddEvenFill); + void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill); + inline void drawPolygon(const QPolygon &polygon, Qt::FillRule fillRule = Qt::OddEvenFill); + + void drawConvexPolygon(const QPointF *points, int pointCount); + inline void drawConvexPolygon(const QPolygonF &polygon); + void drawConvexPolygon(const QPoint *points, int pointCount); + inline void drawConvexPolygon(const QPolygon &polygon); + + void drawArc(const QRectF &rect, int a, int alen); + inline void drawArc(const QRect &, int a, int alen); + inline void drawArc(int x, int y, int w, int h, int a, int alen); + + void drawPie(const QRectF &rect, int a, int alen); + inline void drawPie(int x, int y, int w, int h, int a, int alen); + inline void drawPie(const QRect &, int a, int alen); + + void drawChord(const QRectF &rect, int a, int alen); + inline void drawChord(int x, int y, int w, int h, int a, int alen); + inline void drawChord(const QRect &, int a, int alen); + + void drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + inline void drawRoundedRect(int x, int y, int w, int h, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + inline void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + + void drawRoundRect(const QRectF &r, int xround = 25, int yround = 25); + inline void drawRoundRect(int x, int y, int w, int h, int = 25, int = 25); + inline void drawRoundRect(const QRect &r, int xround = 25, int yround = 25); + + void drawTiledPixmap(const QRectF &rect, const QPixmap &pm, const QPointF &offset = QPointF()); + inline void drawTiledPixmap(int x, int y, int w, int h, const QPixmap &, int sx=0, int sy=0); + inline void drawTiledPixmap(const QRect &, const QPixmap &, const QPoint & = QPoint()); +#ifndef QT_NO_PICTURE + void drawPicture(const QPointF &p, const QPicture &picture); + inline void drawPicture(int x, int y, const QPicture &picture); + inline void drawPicture(const QPoint &p, const QPicture &picture); +#endif + + void drawPixmap(const QRectF &targetRect, const QPixmap &pixmap, const QRectF &sourceRect); + inline void drawPixmap(const QRect &targetRect, const QPixmap &pixmap, const QRect &sourceRect); + inline void drawPixmap(int x, int y, int w, int h, const QPixmap &pm, + int sx, int sy, int sw, int sh); + inline void drawPixmap(int x, int y, const QPixmap &pm, + int sx, int sy, int sw, int sh); + inline void drawPixmap(const QPointF &p, const QPixmap &pm, const QRectF &sr); + inline void drawPixmap(const QPoint &p, const QPixmap &pm, const QRect &sr); + void drawPixmap(const QPointF &p, const QPixmap &pm); + inline void drawPixmap(const QPoint &p, const QPixmap &pm); + inline void drawPixmap(int x, int y, const QPixmap &pm); + inline void drawPixmap(const QRect &r, const QPixmap &pm); + inline void drawPixmap(int x, int y, int w, int h, const QPixmap &pm); + + void drawPixmapFragments(const PixmapFragment *fragments, int fragmentCount, + const QPixmap &pixmap, PixmapFragmentHints hints = 0); + + void drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect, + Qt::ImageConversionFlags flags = Qt::AutoColor); + inline void drawImage(const QRect &targetRect, const QImage &image, const QRect &sourceRect, + Qt::ImageConversionFlags flags = Qt::AutoColor); + inline void drawImage(const QPointF &p, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + inline void drawImage(const QPoint &p, const QImage &image, const QRect &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + inline void drawImage(const QRectF &r, const QImage &image); + inline void drawImage(const QRect &r, const QImage &image); + void drawImage(const QPointF &p, const QImage &image); + inline void drawImage(const QPoint &p, const QImage &image); + inline void drawImage(int x, int y, const QImage &image, int sx = 0, int sy = 0, + int sw = -1, int sh = -1, Qt::ImageConversionFlags flags = Qt::AutoColor); + + void setLayoutDirection(Qt::LayoutDirection direction); + Qt::LayoutDirection layoutDirection() const; + +#if !defined(QT_NO_RAWFONT) + void drawGlyphs(const QPointF &position, const QGlyphs &glyphs); +#endif + + void drawStaticText(const QPointF &topLeftPosition, const QStaticText &staticText); + inline void drawStaticText(const QPoint &topLeftPosition, const QStaticText &staticText); + inline void drawStaticText(int left, int top, const QStaticText &staticText); + + void drawText(const QPointF &p, const QString &s); + inline void drawText(const QPoint &p, const QString &s); + inline void drawText(int x, int y, const QString &s); + + void drawText(const QPointF &p, const QString &str, int tf, int justificationPadding); + + void drawText(const QRectF &r, int flags, const QString &text, QRectF *br=0); + void drawText(const QRect &r, int flags, const QString &text, QRect *br=0); + inline void drawText(int x, int y, int w, int h, int flags, const QString &text, QRect *br=0); + + void drawText(const QRectF &r, const QString &text, const QTextOption &o = QTextOption()); + + QRectF boundingRect(const QRectF &rect, int flags, const QString &text); + QRect boundingRect(const QRect &rect, int flags, const QString &text); + inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text); + + QRectF boundingRect(const QRectF &rect, const QString &text, const QTextOption &o = QTextOption()); + + void drawTextItem(const QPointF &p, const QTextItem &ti); + inline void drawTextItem(int x, int y, const QTextItem &ti); + inline void drawTextItem(const QPoint &p, const QTextItem &ti); + + void fillRect(const QRectF &, const QBrush &); + inline void fillRect(int x, int y, int w, int h, const QBrush &); + void fillRect(const QRect &, const QBrush &); + + void fillRect(const QRectF &, const QColor &color); + inline void fillRect(int x, int y, int w, int h, const QColor &color); + void fillRect(const QRect &, const QColor &color); + + inline void fillRect(int x, int y, int w, int h, Qt::GlobalColor c); + inline void fillRect(const QRect &r, Qt::GlobalColor c); + inline void fillRect(const QRectF &r, Qt::GlobalColor c); + + inline void fillRect(int x, int y, int w, int h, Qt::BrushStyle style); + inline void fillRect(const QRect &r, Qt::BrushStyle style); + inline void fillRect(const QRectF &r, Qt::BrushStyle style); + + void eraseRect(const QRectF &); + inline void eraseRect(int x, int y, int w, int h); + inline void eraseRect(const QRect &); + + void setRenderHint(RenderHint hint, bool on = true); + void setRenderHints(RenderHints hints, bool on = true); + RenderHints renderHints() const; + inline bool testRenderHint(RenderHint hint) const { return renderHints() & hint; } + + QPaintEngine *paintEngine() const; + + static void setRedirected(const QPaintDevice *device, QPaintDevice *replacement, + const QPoint& offset = QPoint()); + static QPaintDevice *redirected(const QPaintDevice *device, QPoint *offset = 0); + static void restoreRedirected(const QPaintDevice *device); + + void beginNativePainting(); + void endNativePainting(); + +#ifdef QT3_SUPPORT + + inline QT3_SUPPORT void setBackgroundColor(const QColor &color) { setBackground(color); } + inline QT3_SUPPORT const QColor &backgroundColor() const { return background().color(); } + + inline QT3_SUPPORT void drawText(int x, int y, const QString &s, int pos, int len) + { drawText(x, y, s.mid(pos, len)); } + inline QT3_SUPPORT void drawText(const QPoint &p, const QString &s, int pos, int len) + { drawText(p, s.mid(pos, len)); } + inline QT3_SUPPORT void drawText(int x, int y, const QString &s, int len) + { drawText(x, y, s.left(len)); } + inline QT3_SUPPORT void drawText(const QPoint &p, const QString &s, int len) + { drawText(p, s.left(len)); } + inline QT3_SUPPORT void drawText(const QRect &r, int flags, const QString &str, int len, QRect *br=0) + { drawText(r, flags, str.left(len), br); } + inline QT3_SUPPORT void drawText(int x, int y, int w, int h, int flags, const QString &text, int len, QRect *br=0) + { drawText(QRect(x, y, w, h), flags, text.left(len), br); } + inline QT3_SUPPORT QRect boundingRect(const QRect &rect, int flags, const QString &text, int len) + { return boundingRect(rect, flags, text.left(len)); } + inline QT3_SUPPORT QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text, int len) + { return boundingRect(QRect(x, y, w, h), flags, text.left(len)); } + + inline QT3_SUPPORT bool begin(QPaintDevice *pdev, const QWidget *init) + { bool ret = begin(pdev); initFrom(init); return ret; } + QT3_SUPPORT void drawPoints(const QPolygon &pa, int index, int npoints = -1) + { drawPoints(pa.constData() + index, npoints == -1 ? pa.size() - index : npoints); } + + QT3_SUPPORT void drawCubicBezier(const QPolygon &pa, int index = 0); + + QT3_SUPPORT void drawLineSegments(const QPolygon &points, int index = 0, int nlines = -1); + + inline QT3_SUPPORT void drawPolyline(const QPolygon &pa, int index, int npoints = -1) + { drawPolyline(pa.constData() + index, npoints == -1 ? pa.size() - index : npoints); } + + inline QT3_SUPPORT void drawPolygon(const QPolygon &pa, bool winding, int index = 0, int npoints = -1) + { drawPolygon(pa.constData() + index, npoints == -1 ? pa.size() - index : npoints, + winding ? Qt::WindingFill : Qt::OddEvenFill); } + + inline QT3_SUPPORT void drawPolygon(const QPolygonF &polygon, bool winding, int index = 0, + int npoints = -1) + { drawPolygon(polygon.constData() + index, npoints == -1 ? polygon.size() - index : npoints, + winding ? Qt::WindingFill : Qt::OddEvenFill); } + + inline QT3_SUPPORT void drawConvexPolygon(const QPolygonF &polygon, int index, int npoints = -1) + { drawConvexPolygon(polygon.constData() + index, npoints == -1 ? polygon.size() - index : npoints); } + inline QT3_SUPPORT void drawConvexPolygon(const QPolygon &pa, int index, int npoints = -1) + { drawConvexPolygon(pa.constData() + index, npoints == -1 ? pa.size() - index : npoints); } + + static inline QT3_SUPPORT void redirect(QPaintDevice *pdev, QPaintDevice *replacement) + { setRedirected(pdev, replacement); } + static inline QT3_SUPPORT QPaintDevice *redirect(QPaintDevice *pdev) + { return const_cast<QPaintDevice*>(redirected(pdev)); } + + inline QT3_SUPPORT void setWorldXForm(bool enabled) { setMatrixEnabled(enabled); } + inline QT3_SUPPORT bool hasWorldXForm() const { return matrixEnabled(); } + inline QT3_SUPPORT void resetXForm() { resetTransform(); } + + inline QT3_SUPPORT void setViewXForm(bool enabled) { setViewTransformEnabled(enabled); } + inline QT3_SUPPORT bool hasViewXForm() const { return viewTransformEnabled(); } + + QT3_SUPPORT void map(int x, int y, int *rx, int *ry) const; + QT3_SUPPORT QPoint xForm(const QPoint &) const; // map virtual -> deviceb + QT3_SUPPORT QRect xForm(const QRect &) const; + QT3_SUPPORT QPolygon xForm(const QPolygon &) const; + QT3_SUPPORT QPolygon xForm(const QPolygon &, int index, int npoints) const; + QT3_SUPPORT QPoint xFormDev(const QPoint &) const; // map device -> virtual + QT3_SUPPORT QRect xFormDev(const QRect &) const; + QT3_SUPPORT QPolygon xFormDev(const QPolygon &) const; + QT3_SUPPORT QPolygon xFormDev(const QPolygon &, int index, int npoints) const; + QT3_SUPPORT qreal translationX() const; + QT3_SUPPORT qreal translationY() const; +#endif + +private: + Q_DISABLE_COPY(QPainter) + friend class Q3Painter; + + QScopedPointer<QPainterPrivate> d_ptr; + + friend class QFontEngine; + friend class QFontEngineBox; + friend class QFontEngineFT; + friend class QFontEngineMac; + friend class QFontEngineWin; + friend class QFontEngineXLFD; + friend class QWSManager; + friend class QPaintEngine; + friend class QPaintEngineExPrivate; + friend class QOpenGLPaintEngine; + friend class QX11PaintEngine; + friend class QX11PaintEnginePrivate; + friend class QWin32PaintEngine; + friend class QWin32PaintEnginePrivate; + friend class QRasterPaintEngine; + friend class QAlphaPaintEngine; + friend class QPreviewPaintEngine; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QPainter::RenderHints) + +// +// functions +// +inline void QPainter::drawLine(const QLineF &l) +{ + drawLines(&l, 1); +} + +inline void QPainter::drawLine(const QLine &line) +{ + drawLines(&line, 1); +} + +inline void QPainter::drawLine(int x1, int y1, int x2, int y2) +{ + QLine l(x1, y1, x2, y2); + drawLines(&l, 1); +} + +inline void QPainter::drawLine(const QPoint &p1, const QPoint &p2) +{ + QLine l(p1, p2); + drawLines(&l, 1); +} + +inline void QPainter::drawLine(const QPointF &p1, const QPointF &p2) +{ + drawLine(QLineF(p1, p2)); +} + +inline void QPainter::drawLines(const QVector<QLineF> &lines) +{ + drawLines(lines.constData(), lines.size()); +} + +inline void QPainter::drawLines(const QVector<QLine> &lines) +{ + drawLines(lines.constData(), lines.size()); +} + +inline void QPainter::drawLines(const QVector<QPointF> &pointPairs) +{ + drawLines(pointPairs.constData(), pointPairs.size() / 2); +} + +inline void QPainter::drawLines(const QVector<QPoint> &pointPairs) +{ + drawLines(pointPairs.constData(), pointPairs.size() / 2); +} + +inline void QPainter::drawPolyline(const QPolygonF &polyline) +{ + drawPolyline(polyline.constData(), polyline.size()); +} + +inline void QPainter::drawPolyline(const QPolygon &polyline) +{ + drawPolyline(polyline.constData(), polyline.size()); +} + +inline void QPainter::drawPolygon(const QPolygonF &polygon, Qt::FillRule fillRule) +{ + drawPolygon(polygon.constData(), polygon.size(), fillRule); +} + +inline void QPainter::drawPolygon(const QPolygon &polygon, Qt::FillRule fillRule) +{ + drawPolygon(polygon.constData(), polygon.size(), fillRule); +} + +inline void QPainter::drawConvexPolygon(const QPolygonF &poly) +{ + drawConvexPolygon(poly.constData(), poly.size()); +} + +inline void QPainter::drawConvexPolygon(const QPolygon &poly) +{ + drawConvexPolygon(poly.constData(), poly.size()); +} + +inline void QPainter::drawRect(const QRectF &rect) +{ + drawRects(&rect, 1); +} + +inline void QPainter::drawRect(int x, int y, int w, int h) +{ + QRect r(x, y, w, h); + drawRects(&r, 1); +} + +inline void QPainter::drawRect(const QRect &r) +{ + drawRects(&r, 1); +} + +inline void QPainter::drawRects(const QVector<QRectF> &rects) +{ + drawRects(rects.constData(), rects.size()); +} + +inline void QPainter::drawRects(const QVector<QRect> &rects) +{ + drawRects(rects.constData(), rects.size()); +} + +inline void QPainter::drawPoint(const QPointF &p) +{ + drawPoints(&p, 1); +} + +inline void QPainter::drawPoint(int x, int y) +{ + QPoint p(x, y); + drawPoints(&p, 1); +} + +inline void QPainter::drawPoint(const QPoint &p) +{ + drawPoints(&p, 1); +} + +inline void QPainter::drawPoints(const QPolygonF &points) +{ + drawPoints(points.constData(), points.size()); +} + +inline void QPainter::drawPoints(const QPolygon &points) +{ + drawPoints(points.constData(), points.size()); +} + +inline void QPainter::drawRoundRect(int x, int y, int w, int h, int xRnd, int yRnd) +{ + drawRoundRect(QRectF(x, y, w, h), xRnd, yRnd); +} + +inline void QPainter::drawRoundRect(const QRect &rect, int xRnd, int yRnd) +{ + drawRoundRect(QRectF(rect), xRnd, yRnd); +} + +inline void QPainter::drawRoundedRect(int x, int y, int w, int h, qreal xRadius, qreal yRadius, + Qt::SizeMode mode) +{ + drawRoundedRect(QRectF(x, y, w, h), xRadius, yRadius, mode); +} + +inline void QPainter::drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode) +{ + drawRoundedRect(QRectF(rect), xRadius, yRadius, mode); +} + +inline void QPainter::drawEllipse(int x, int y, int w, int h) +{ + drawEllipse(QRect(x, y, w, h)); +} + +inline void QPainter::drawEllipse(const QPointF ¢er, qreal rx, qreal ry) +{ + drawEllipse(QRectF(center.x() - rx, center.y() - ry, 2 * rx, 2 * ry)); +} + +inline void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry) +{ + drawEllipse(QRect(center.x() - rx, center.y() - ry, 2 * rx, 2 * ry)); +} + +inline void QPainter::drawArc(const QRect &r, int a, int alen) +{ + drawArc(QRectF(r), a, alen); +} + +inline void QPainter::drawArc(int x, int y, int w, int h, int a, int alen) +{ + drawArc(QRectF(x, y, w, h), a, alen); +} + +inline void QPainter::drawPie(const QRect &rect, int a, int alen) +{ + drawPie(QRectF(rect), a, alen); +} + +inline void QPainter::drawPie(int x, int y, int w, int h, int a, int alen) +{ + drawPie(QRectF(x, y, w, h), a, alen); +} + +inline void QPainter::drawChord(const QRect &rect, int a, int alen) +{ + drawChord(QRectF(rect), a, alen); +} + +inline void QPainter::drawChord(int x, int y, int w, int h, int a, int alen) +{ + drawChord(QRectF(x, y, w, h), a, alen); +} + +inline void QPainter::setClipRect(int x, int y, int w, int h, Qt::ClipOperation op) +{ + setClipRect(QRect(x, y, w, h), op); +} + +inline void QPainter::eraseRect(const QRect &rect) +{ + eraseRect(QRectF(rect)); +} + +inline void QPainter::eraseRect(int x, int y, int w, int h) +{ + eraseRect(QRectF(x, y, w, h)); +} + +inline void QPainter::fillRect(int x, int y, int w, int h, const QBrush &b) +{ + fillRect(QRect(x, y, w, h), b); +} + +inline void QPainter::fillRect(int x, int y, int w, int h, const QColor &b) +{ + fillRect(QRect(x, y, w, h), b); +} + +inline void QPainter::fillRect(int x, int y, int w, int h, Qt::GlobalColor c) +{ + fillRect(QRect(x, y, w, h), QColor(c)); +} + +inline void QPainter::fillRect(const QRect &r, Qt::GlobalColor c) +{ + fillRect(r, QColor(c)); +} + +inline void QPainter::fillRect(const QRectF &r, Qt::GlobalColor c) +{ + fillRect(r, QColor(c)); +} + +inline void QPainter::fillRect(int x, int y, int w, int h, Qt::BrushStyle style) +{ + fillRect(QRectF(x, y, w, h), QBrush(style)); +} + +inline void QPainter::fillRect(const QRect &r, Qt::BrushStyle style) +{ + fillRect(QRectF(r), QBrush(style)); +} + +inline void QPainter::fillRect(const QRectF &r, Qt::BrushStyle style) +{ + fillRect(r, QBrush(style)); +} + + +inline void QPainter::setBrushOrigin(int x, int y) +{ + setBrushOrigin(QPoint(x, y)); +} + +inline void QPainter::setBrushOrigin(const QPoint &p) +{ + setBrushOrigin(QPointF(p)); +} + +inline void QPainter::drawTiledPixmap(const QRect &rect, const QPixmap &pm, const QPoint &offset) +{ + drawTiledPixmap(QRectF(rect), pm, QPointF(offset)); +} + +inline void QPainter::drawTiledPixmap(int x, int y, int w, int h, const QPixmap &pm, int sx, int sy) +{ + drawTiledPixmap(QRectF(x, y, w, h), pm, QPointF(sx, sy)); +} + +inline void QPainter::drawPixmap(const QRect &targetRect, const QPixmap &pixmap, const QRect &sourceRect) +{ + drawPixmap(QRectF(targetRect), pixmap, QRectF(sourceRect)); +} + +inline void QPainter::drawPixmap(const QPoint &p, const QPixmap &pm) +{ + drawPixmap(QPointF(p), pm); +} + +inline void QPainter::drawPixmap(const QRect &r, const QPixmap &pm) +{ + drawPixmap(QRectF(r), pm, QRectF()); +} + +inline void QPainter::drawPixmap(int x, int y, const QPixmap &pm) +{ + drawPixmap(QPointF(x, y), pm); +} + +inline void QPainter::drawPixmap(int x, int y, int w, int h, const QPixmap &pm) +{ + drawPixmap(QRectF(x, y, w, h), pm, QRectF()); +} + +inline void QPainter::drawPixmap(int x, int y, int w, int h, const QPixmap &pm, + int sx, int sy, int sw, int sh) +{ + drawPixmap(QRectF(x, y, w, h), pm, QRectF(sx, sy, sw, sh)); +} + +inline void QPainter::drawPixmap(int x, int y, const QPixmap &pm, + int sx, int sy, int sw, int sh) +{ + drawPixmap(QRectF(x, y, -1, -1), pm, QRectF(sx, sy, sw, sh)); +} + +inline void QPainter::drawPixmap(const QPointF &p, const QPixmap &pm, const QRectF &sr) +{ + drawPixmap(QRectF(p.x(), p.y(), -1, -1), pm, sr); +} + +inline void QPainter::drawPixmap(const QPoint &p, const QPixmap &pm, const QRect &sr) +{ + drawPixmap(QRectF(p.x(), p.y(), -1, -1), pm, sr); +} + +inline void QPainter::drawTextItem(int x, int y, const QTextItem &ti) +{ + drawTextItem(QPointF(x, y), ti); +} + +inline void QPainter::drawImage(const QRect &targetRect, const QImage &image, const QRect &sourceRect, + Qt::ImageConversionFlags flags) +{ + drawImage(QRectF(targetRect), image, QRectF(sourceRect), flags); +} + +inline void QPainter::drawImage(const QPointF &p, const QImage &image, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + drawImage(QRectF(p.x(), p.y(), -1, -1), image, sr, flags); +} + +inline void QPainter::drawImage(const QPoint &p, const QImage &image, const QRect &sr, + Qt::ImageConversionFlags flags) +{ + drawImage(QRect(p.x(), p.y(), -1, -1), image, sr, flags); +} + + +inline void QPainter::drawImage(const QRectF &r, const QImage &image) +{ + drawImage(r, image, QRect(0, 0, image.width(), image.height())); +} + +inline void QPainter::drawImage(const QRect &r, const QImage &image) +{ + drawImage(r, image, QRectF(0, 0, image.width(), image.height())); +} + +inline void QPainter::drawImage(const QPoint &p, const QImage &image) +{ + drawImage(QPointF(p), image); +} + +inline void QPainter::drawImage(int x, int y, const QImage &image, int sx, int sy, int sw, int sh, + Qt::ImageConversionFlags flags) +{ + if (sx == 0 && sy == 0 && sw == -1 && sh == -1 && flags == Qt::AutoColor) + drawImage(QPointF(x, y), image); + else + drawImage(QRectF(x, y, -1, -1), image, QRectF(sx, sy, sw, sh), flags); +} + +inline void QPainter::drawStaticText(const QPoint &p, const QStaticText &staticText) +{ + drawStaticText(QPointF(p), staticText); +} + +inline void QPainter::drawStaticText(int x, int y, const QStaticText &staticText) +{ + drawStaticText(QPointF(x, y), staticText); +} + +inline void QPainter::drawTextItem(const QPoint &p, const QTextItem &ti) +{ + drawTextItem(QPointF(p), ti); +} + +inline void QPainter::drawText(const QPoint &p, const QString &s) +{ + drawText(QPointF(p), s); +} + +inline void QPainter::drawText(int x, int y, int w, int h, int flags, const QString &str, QRect *br) +{ + drawText(QRect(x, y, w, h), flags, str, br); +} + +inline void QPainter::drawText(int x, int y, const QString &s) +{ + drawText(QPointF(x, y), s); +} + +inline QRect QPainter::boundingRect(int x, int y, int w, int h, int flags, const QString &text) +{ + return boundingRect(QRect(x, y, w, h), flags, text); +} + +inline void QPainter::translate(qreal dx, qreal dy) +{ + translate(QPointF(dx, dy)); +} + +inline void QPainter::translate(const QPoint &offset) +{ + translate(offset.x(), offset.y()); +} + +inline void QPainter::setViewport(int x, int y, int w, int h) +{ + setViewport(QRect(x, y, w, h)); +} + +inline void QPainter::setWindow(int x, int y, int w, int h) +{ + setWindow(QRect(x, y, w, h)); +} + +#ifndef QT_NO_PICTURE +inline void QPainter::drawPicture(int x, int y, const QPicture &p) +{ + drawPicture(QPoint(x, y), p); +} + +inline void QPainter::drawPicture(const QPoint &pt, const QPicture &p) +{ + drawPicture(QPointF(pt), p); +} +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAINTER_H diff --git a/src/gui/painting/qpainter_p.h b/src/gui/painting/qpainter_p.h new file mode 100644 index 0000000000..205c10a09f --- /dev/null +++ b/src/gui/painting/qpainter_p.h @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTER_P_H +#define QPAINTER_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 "QtGui/qbrush.h" +#include "QtGui/qfont.h" +#include "QtGui/qpen.h" +#include "QtGui/qregion.h" +#include "QtGui/qmatrix.h" +#include "QtGui/qpainter.h" +#include "QtGui/qpainterpath.h" +#include "QtGui/qpaintengine.h" +#include <QtCore/qhash.h> + +#include <private/qpen_p.h> + +QT_BEGIN_NAMESPACE + +class QPaintEngine; +class QEmulationPaintEngine; +class QPaintEngineEx; +struct QFixedPoint; + +struct QTLWExtra; + +struct DataPtrContainer { + void *ptr; +}; + +inline void *data_ptr(const QTransform &t) { return (DataPtrContainer *) &t; } +inline bool qtransform_fast_equals(const QTransform &a, const QTransform &b) { return data_ptr(a) == data_ptr(b); } + +// QPen inline functions... +inline QPen::DataPtr &data_ptr(const QPen &p) { return const_cast<QPen &>(p).data_ptr(); } +inline bool qpen_fast_equals(const QPen &a, const QPen &b) { return data_ptr(a) == data_ptr(b); } +inline QBrush qpen_brush(const QPen &p) { return data_ptr(p)->brush; } +inline qreal qpen_widthf(const QPen &p) { return data_ptr(p)->width; } +inline Qt::PenStyle qpen_style(const QPen &p) { return data_ptr(p)->style; } +inline Qt::PenCapStyle qpen_capStyle(const QPen &p) { return data_ptr(p)->capStyle; } +inline Qt::PenJoinStyle qpen_joinStyle(const QPen &p) { return data_ptr(p)->joinStyle; } + +// QBrush inline functions... +inline QBrush::DataPtr &data_ptr(const QBrush &p) { return const_cast<QBrush &>(p).data_ptr(); } +inline bool qbrush_fast_equals(const QBrush &a, const QBrush &b) { return data_ptr(a) == data_ptr(b); } +inline Qt::BrushStyle qbrush_style(const QBrush &b) { return data_ptr(b)->style; } +inline const QColor &qbrush_color(const QBrush &b) { return data_ptr(b)->color; } +inline bool qbrush_has_transform(const QBrush &b) { return data_ptr(b)->transform.type() > QTransform::TxNone; } + +class QPainterClipInfo +{ +public: + enum ClipType { RegionClip, PathClip, RectClip, RectFClip }; + + QPainterClipInfo(const QPainterPath &p, Qt::ClipOperation op, const QTransform &m) : + clipType(PathClip), matrix(m), operation(op), path(p) { } + + QPainterClipInfo(const QRegion &r, Qt::ClipOperation op, const QTransform &m) : + clipType(RegionClip), matrix(m), operation(op), region(r) { } + + QPainterClipInfo(const QRect &r, Qt::ClipOperation op, const QTransform &m) : + clipType(RectClip), matrix(m), operation(op), rect(r) { } + + QPainterClipInfo(const QRectF &r, Qt::ClipOperation op, const QTransform &m) : + clipType(RectFClip), matrix(m), operation(op), rectf(r) { } + + ClipType clipType; + QTransform matrix; + Qt::ClipOperation operation; + QPainterPath path; + QRegion region; + QRect rect; + QRectF rectf; + + // ### +// union { +// QRegionData *d; +// QPainterPathPrivate *pathData; + +// struct { +// int x, y, w, h; +// } rectData; +// struct { +// qreal x, y, w, h; +// } rectFData; +// }; + +}; + + +class Q_GUI_EXPORT QPainterState : public QPaintEngineState +{ +public: + QPainterState(); + QPainterState(const QPainterState *s); + virtual ~QPainterState(); + void init(QPainter *p); + + QPointF brushOrigin; + QFont font; + QFont deviceFont; + QPen pen; + QBrush brush; + QBrush bgBrush; // background brush + QRegion clipRegion; + QPainterPath clipPath; + Qt::ClipOperation clipOperation; + QPainter::RenderHints renderHints; + QList<QPainterClipInfo> clipInfo; // ### Make me smaller and faster to copy around... + QTransform worldMatrix; // World transformation matrix, not window and viewport + QTransform matrix; // Complete transformation matrix, + QTransform redirectionMatrix; + int wx, wy, ww, wh; // window rectangle + int vx, vy, vw, vh; // viewport rectangle + qreal opacity; + + uint WxF:1; // World transformation + uint VxF:1; // View transformation + uint clipEnabled:1; + + Qt::BGMode bgMode; + QPainter *painter; + Qt::LayoutDirection layoutDirection; + QPainter::CompositionMode composition_mode; + uint emulationSpecifier; + uint changeFlags; +}; + +struct QPainterDummyState +{ + QFont font; + QPen pen; + QBrush brush; + QTransform transform; +}; + +class QRawFont; +class QPainterPrivate +{ + Q_DECLARE_PUBLIC(QPainter) +public: + QPainterPrivate(QPainter *painter) + : q_ptr(painter), d_ptrs(0), state(0), dummyState(0), txinv(0), inDestructor(false), d_ptrs_size(0), + refcount(1), device(0), original_device(0), helper_device(0), engine(0), emulationEngine(0), + extended(0) + { + } + + ~QPainterPrivate(); + + QPainter *q_ptr; + QPainterPrivate **d_ptrs; + + QPainterState *state; + QVector<QPainterState*> states; + + mutable QPainterDummyState *dummyState; + + QTransform invMatrix; + uint txinv:1; + uint inDestructor : 1; + uint d_ptrs_size; + uint refcount; + + enum DrawOperation { StrokeDraw = 0x1, + FillDraw = 0x2, + StrokeAndFillDraw = 0x3 + }; + + QPainterDummyState *fakeState() const { + if (!dummyState) + dummyState = new QPainterDummyState(); + return dummyState; + } + + void updateEmulationSpecifier(QPainterState *s); + void updateStateImpl(QPainterState *state); + void updateState(QPainterState *state); + + void draw_helper(const QPainterPath &path, DrawOperation operation = StrokeAndFillDraw); + void drawStretchedGradient(const QPainterPath &path, DrawOperation operation); + void drawOpaqueBackground(const QPainterPath &path, DrawOperation operation); + +#if !defined(QT_NO_RAWFONT) + void drawGlyphs(quint32 *glyphArray, QFixedPoint *positionArray, int glyphCount, + const QRawFont &font, bool overline = false, bool underline = false, + bool strikeOut = false); +#endif + + void updateMatrix(); + void updateInvMatrix(); + + int rectSubtraction() const { + return state->pen.style() != Qt::NoPen && state->pen.width() == 0 ? 1 : 0; + } + + void checkEmulation(); + + static QPainterPrivate *get(QPainter *painter) + { + return painter->d_ptr.data(); + } + + QTransform viewTransform() const; + static bool attachPainterPrivate(QPainter *q, QPaintDevice *pdev); + void detachPainterPrivate(QPainter *q); + + QPaintDevice *device; + QPaintDevice *original_device; + QPaintDevice *helper_device; + QPaintEngine *engine; + QEmulationPaintEngine *emulationEngine; + QPaintEngineEx *extended; + QBrush colorBrush; // for fill with solid color +}; + +Q_GUI_EXPORT void qt_draw_helper(QPainterPrivate *p, const QPainterPath &path, QPainterPrivate::DrawOperation operation); + +QString qt_generate_brush_key(const QBrush &brush); + +QT_END_NAMESPACE + +#endif // QPAINTER_P_H diff --git a/src/gui/painting/qpainterpath.cpp b/src/gui/painting/qpainterpath.cpp new file mode 100644 index 0000000000..27aed3226f --- /dev/null +++ b/src/gui/painting/qpainterpath.cpp @@ -0,0 +1,3420 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpainterpath.h" +#include "qpainterpath_p.h" + +#include <qbitmap.h> +#include <qdebug.h> +#include <qiodevice.h> +#include <qlist.h> +#include <qmatrix.h> +#include <qpen.h> +#include <qpolygon.h> +#include <qtextlayout.h> +#include <qvarlengtharray.h> +#include <qmath.h> + +#include <private/qbezier_p.h> +#include <private/qfontengine_p.h> +#include <private/qnumeric_p.h> +#include <private/qobject_p.h> +#include <private/qpathclipper_p.h> +#include <private/qstroker_p.h> +#include <private/qtextengine_p.h> + +#include <limits.h> + +#if 0 +#include <performance.h> +#else +#define PM_INIT +#define PM_MEASURE(x) +#define PM_DISPLAY +#endif + +QT_BEGIN_NAMESPACE + +struct QPainterPathPrivateDeleter +{ + static inline void cleanup(QPainterPathPrivate *d) + { + // note - we must up-cast to QPainterPathData since QPainterPathPrivate + // has a non-virtual destructor! + if (d && !d->ref.deref()) + delete static_cast<QPainterPathData *>(d); + } +}; + +// This value is used to determine the length of control point vectors +// when approximating arc segments as curves. The factor is multiplied +// with the radius of the circle. + +// #define QPP_DEBUG +// #define QPP_STROKE_DEBUG +//#define QPP_FILLPOLYGONS_DEBUG + +QPainterPath qt_stroke_dash(const QPainterPath &path, qreal *dashes, int dashCount); + +void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length, + QPointF* startPoint, QPointF *endPoint) +{ + if (r.isNull()) { + if (startPoint) + *startPoint = QPointF(); + if (endPoint) + *endPoint = QPointF(); + return; + } + + qreal w2 = r.width() / 2; + qreal h2 = r.height() / 2; + + qreal angles[2] = { angle, angle + length }; + QPointF *points[2] = { startPoint, endPoint }; + + for (int i = 0; i < 2; ++i) { + if (!points[i]) + continue; + + qreal theta = angles[i] - 360 * qFloor(angles[i] / 360); + qreal t = theta / 90; + // truncate + int quadrant = int(t); + t -= quadrant; + + t = qt_t_for_arc_angle(90 * t); + + // swap x and y? + if (quadrant & 1) + t = 1 - t; + + qreal a, b, c, d; + QBezier::coefficients(t, a, b, c, d); + QPointF p(a + b + c*QT_PATH_KAPPA, d + c + b*QT_PATH_KAPPA); + + // left quadrants + if (quadrant == 1 || quadrant == 2) + p.rx() = -p.x(); + + // top quadrants + if (quadrant == 0 || quadrant == 1) + p.ry() = -p.y(); + + *points[i] = r.center() + QPointF(w2 * p.x(), h2 * p.y()); + } +} + +#ifdef QPP_DEBUG +static void qt_debug_path(const QPainterPath &path) +{ + const char *names[] = { + "MoveTo ", + "LineTo ", + "CurveTo ", + "CurveToData" + }; + + printf("\nQPainterPath: elementCount=%d\n", path.elementCount()); + for (int i=0; i<path.elementCount(); ++i) { + const QPainterPath::Element &e = path.elementAt(i); + Q_ASSERT(e.type >= 0 && e.type <= QPainterPath::CurveToDataElement); + printf(" - %3d:: %s, (%.2f, %.2f)\n", i, names[e.type], e.x, e.y); + } +} +#endif + +/*! + \class QPainterPath + \ingroup painting + \ingroup shared + + \brief The QPainterPath class provides a container for painting operations, + enabling graphical shapes to be constructed and reused. + + A painter path is an object composed of a number of graphical + building blocks, such as rectangles, ellipses, lines, and curves. + Building blocks can be joined in closed subpaths, for example as a + rectangle or an ellipse. A closed path has coinciding start and + end points. Or they can exist independently as unclosed subpaths, + such as lines and curves. + + A QPainterPath object can be used for filling, outlining, and + clipping. To generate fillable outlines for a given painter path, + use the QPainterPathStroker class. The main advantage of painter + paths over normal drawing operations is that complex shapes only + need to be created once; then they can be drawn many times using + only calls to the QPainter::drawPath() function. + + QPainterPath provides a collection of functions that can be used + to obtain information about the path and its elements. In addition + it is possible to reverse the order of the elements using the + toReversed() function. There are also several functions to convert + this painter path object into a polygon representation. + + \tableofcontents + + \section1 Composing a QPainterPath + + A QPainterPath object can be constructed as an empty path, with a + given start point, or as a copy of another QPainterPath object. + Once created, lines and curves can be added to the path using the + lineTo(), arcTo(), cubicTo() and quadTo() functions. The lines and + curves stretch from the currentPosition() to the position passed + as argument. + + The currentPosition() of the QPainterPath object is always the end + position of the last subpath that was added (or the initial start + point). Use the moveTo() function to move the currentPosition() + without adding a component. The moveTo() function implicitly + starts a new subpath, and closes the previous one. Another way of + starting a new subpath is to call the closeSubpath() function + which closes the current path by adding a line from the + currentPosition() back to the path's start position. Note that the + new path will have (0, 0) as its initial currentPosition(). + + QPainterPath class also provides several convenience functions to + add closed subpaths to a painter path: addEllipse(), addPath(), + addRect(), addRegion() and addText(). The addPolygon() function + adds an \e unclosed subpath. In fact, these functions are all + collections of moveTo(), lineTo() and cubicTo() operations. + + In addition, a path can be added to the current path using the + connectPath() function. But note that this function will connect + the last element of the current path to the first element of given + one by adding a line. + + Below is a code snippet that shows how a QPainterPath object can + be used: + + \table 100% + \row + \o \inlineimage qpainterpath-construction.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 0 + \endtable + + The painter path is initially empty when constructed. We first add + a rectangle, which is a closed subpath. Then we add two bezier + curves which together form a closed subpath even though they are + not closed individually. Finally we draw the entire path. The path + is filled using the default fill rule, Qt::OddEvenFill. Qt + provides two methods for filling paths: + + \table + \header + \o Qt::OddEvenFill + \o Qt::WindingFill + \row + \o \inlineimage qt-fillrule-oddeven.png + \o \inlineimage qt-fillrule-winding.png + \endtable + + See the Qt::FillRule documentation for the definition of the + rules. A painter path's currently set fill rule can be retrieved + using the fillRule() function, and altered using the setFillRule() + function. + + \section1 QPainterPath Information + + The QPainterPath class provides a collection of functions that + returns information about the path and its elements. + + The currentPosition() function returns the end point of the last + subpath that was added (or the initial start point). The + elementAt() function can be used to retrieve the various subpath + elements, the \e number of elements can be retrieved using the + elementCount() function, and the isEmpty() function tells whether + this QPainterPath object contains any elements at all. + + The controlPointRect() function returns the rectangle containing + all the points and control points in this path. This function is + significantly faster to compute than the exact boundingRect() + which returns the bounding rectangle of this painter path with + floating point precision. + + Finally, QPainterPath provides the contains() function which can + be used to determine whether a given point or rectangle is inside + the path, and the intersects() function which determines if any of + the points inside a given rectangle also are inside this path. + + \section1 QPainterPath Conversion + + For compatibility reasons, it might be required to simplify the + representation of a painter path: QPainterPath provides the + toFillPolygon(), toFillPolygons() and toSubpathPolygons() + functions which convert the painter path into a polygon. The + toFillPolygon() returns the painter path as one single polygon, + while the two latter functions return a list of polygons. + + The toFillPolygons() and toSubpathPolygons() functions are + provided because it is usually faster to draw several small + polygons than to draw one large polygon, even though the total + number of points drawn is the same. The difference between the two + is the \e number of polygons they return: The toSubpathPolygons() + creates one polygon for each subpath regardless of intersecting + subpaths (i.e. overlapping bounding rectangles), while the + toFillPolygons() functions creates only one polygon for + overlapping subpaths. + + The toFillPolygon() and toFillPolygons() functions first convert + all the subpaths to polygons, then uses a rewinding technique to + make sure that overlapping subpaths can be filled using the + correct fill rule. Note that rewinding inserts additional lines in + the polygon so the outline of the fill polygon does not match the + outline of the path. + + \section1 Examples + + Qt provides the \l {painting/painterpaths}{Painter Paths Example} + and the \l {demos/deform}{Vector Deformation Demo} which are + located in Qt's example and demo directories respectively. + + The \l {painting/painterpaths}{Painter Paths Example} shows how + painter paths can be used to build complex shapes for rendering + and lets the user experiment with the filling and stroking. The + \l {demos/deform}{Vector Deformation Demo} shows how to use + QPainterPath to draw text. + + \table + \header + \o \l {painting/painterpaths}{Painter Paths Example} + \o \l {demos/deform}{Vector Deformation Demo} + \row + \o \inlineimage qpainterpath-example.png + \o \inlineimage qpainterpath-demo.png + \endtable + + \sa QPainterPathStroker, QPainter, QRegion, {Painter Paths Example} +*/ + +/*! + \enum QPainterPath::ElementType + + This enum describes the types of elements used to connect vertices + in subpaths. + + Note that elements added as closed subpaths using the + addEllipse(), addPath(), addPolygon(), addRect(), addRegion() and + addText() convenience functions, is actually added to the path as + a collection of separate elements using the moveTo(), lineTo() and + cubicTo() functions. + + \value MoveToElement A new subpath. See also moveTo(). + \value LineToElement A line. See also lineTo(). + \value CurveToElement A curve. See also cubicTo() and quadTo(). + \value CurveToDataElement The extra data required to describe a curve in + a CurveToElement element. + + \sa elementAt(), elementCount() +*/ + +/*! + \class QPainterPath::Element + + \brief The QPainterPath::Element class specifies the position and + type of a subpath. + + Once a QPainterPath object is constructed, subpaths like lines and + curves can be added to the path (creating + QPainterPath::LineToElement and QPainterPath::CurveToElement + components). + + The lines and curves stretch from the currentPosition() to the + position passed as argument. The currentPosition() of the + QPainterPath object is always the end position of the last subpath + that was added (or the initial start point). The moveTo() function + can be used to move the currentPosition() without adding a line or + curve, creating a QPainterPath::MoveToElement component. + + \sa QPainterPath +*/ + +/*! + \variable QPainterPath::Element::x + \brief the x coordinate of the element's position. + + \sa {operator QPointF()} +*/ + +/*! + \variable QPainterPath::Element::y + \brief the y coordinate of the element's position. + + \sa {operator QPointF()} +*/ + +/*! + \variable QPainterPath::Element::type + \brief the type of element + + \sa isCurveTo(), isLineTo(), isMoveTo() +*/ + +/*! + \fn bool QPainterPath::Element::operator==(const Element &other) const + \since 4.2 + + Returns true if this element is equal to \a other; + otherwise returns false. + + \sa operator!=() +*/ + +/*! + \fn bool QPainterPath::Element::operator!=(const Element &other) const + \since 4.2 + + Returns true if this element is not equal to \a other; + otherwise returns false. + + \sa operator==() +*/ + +/*! + \fn bool QPainterPath::Element::isCurveTo () const + + Returns true if the element is a curve, otherwise returns false. + + \sa type, QPainterPath::CurveToElement +*/ + +/*! + \fn bool QPainterPath::Element::isLineTo () const + + Returns true if the element is a line, otherwise returns false. + + \sa type, QPainterPath::LineToElement +*/ + +/*! + \fn bool QPainterPath::Element::isMoveTo () const + + Returns true if the element is moving the current position, + otherwise returns false. + + \sa type, QPainterPath::MoveToElement +*/ + +/*! + \fn QPainterPath::Element::operator QPointF () const + + Returns the element's position. + + \sa x, y +*/ + +/*! + \fn void QPainterPath::addEllipse(qreal x, qreal y, qreal width, qreal height) + \overload + + Creates an ellipse within the bounding rectangle defined by its top-left + corner at (\a x, \a y), \a width and \a height, and adds it to the + painter path as a closed subpath. +*/ + +/*! + \since 4.4 + + \fn void QPainterPath::addEllipse(const QPointF ¢er, qreal rx, qreal ry) + \overload + + Creates an ellipse positioned at \a{center} with radii \a{rx} and \a{ry}, + and adds it to the painter path as a closed subpath. +*/ + +/*! + \fn void QPainterPath::addText(qreal x, qreal y, const QFont &font, const QString &text) + \overload + + Adds the given \a text to this path as a set of closed subpaths created + from the \a font supplied. The subpaths are positioned so that the left + end of the text's baseline lies at the point specified by (\a x, \a y). +*/ + +/*! + \fn int QPainterPath::elementCount() const + + Returns the number of path elements in the painter path. + + \sa ElementType, elementAt(), isEmpty() +*/ + +/*! + \fn const QPainterPath::Element &QPainterPath::elementAt(int index) const + + Returns the element at the given \a index in the painter path. + + \sa ElementType, elementCount(), isEmpty() +*/ + +/*! + \fn void QPainterPath::setElementPositionAt(int index, qreal x, qreal y) + \since 4.2 + + Sets the x and y coordinate of the element at index \a index to \a + x and \a y. +*/ + +/*### + \fn QPainterPath &QPainterPath::operator +=(const QPainterPath &other) + + Appends the \a other painter path to this painter path and returns a + reference to the result. +*/ + +/*! + Constructs an empty QPainterPath object. +*/ +QPainterPath::QPainterPath() + : d_ptr(0) +{ +} + +/*! + \fn QPainterPath::QPainterPath(const QPainterPath &path) + + Creates a QPainterPath object that is a copy of the given \a path. + + \sa operator=() +*/ +QPainterPath::QPainterPath(const QPainterPath &other) + : d_ptr(other.d_ptr.data()) +{ + if (d_ptr) + d_ptr->ref.ref(); +} + +/*! + Creates a QPainterPath object with the given \a startPoint as its + current position. +*/ + +QPainterPath::QPainterPath(const QPointF &startPoint) + : d_ptr(new QPainterPathData) +{ + Element e = { startPoint.x(), startPoint.y(), MoveToElement }; + d_func()->elements << e; +} + +/*! + \internal +*/ +void QPainterPath::detach_helper() +{ + QPainterPathPrivate *data = new QPainterPathData(*d_func()); + d_ptr.reset(data); +} + +/*! + \internal +*/ +void QPainterPath::ensureData_helper() +{ + QPainterPathPrivate *data = new QPainterPathData; + data->elements.reserve(16); + QPainterPath::Element e = { 0, 0, QPainterPath::MoveToElement }; + data->elements << e; + d_ptr.reset(data); + Q_ASSERT(d_ptr != 0); +} + +/*! + \fn QPainterPath &QPainterPath::operator=(const QPainterPath &path) + + Assigns the given \a path to this painter path. + + \sa QPainterPath() +*/ +QPainterPath &QPainterPath::operator=(const QPainterPath &other) +{ + if (other.d_func() != d_func()) { + QPainterPathPrivate *data = other.d_func(); + if (data) + data->ref.ref(); + d_ptr.reset(data); + } + return *this; +} + +/*! + \fn void QPainterPath::swap(QPainterPath &other) + \since 4.8 + + Swaps painter path \a other with this painter path. This operation is very + fast and never fails. +*/ + +/*! + Destroys this QPainterPath object. +*/ +QPainterPath::~QPainterPath() +{ +} + +/*! + Closes the current subpath by drawing a line to the beginning of + the subpath, automatically starting a new path. The current point + of the new path is (0, 0). + + If the subpath does not contain any elements, this function does + nothing. + + \sa moveTo(), {QPainterPath#Composing a QPainterPath}{Composing + a QPainterPath} + */ +void QPainterPath::closeSubpath() +{ +#ifdef QPP_DEBUG + printf("QPainterPath::closeSubpath()\n"); +#endif + if (isEmpty()) + return; + detach(); + + d_func()->close(); +} + +/*! + \fn void QPainterPath::moveTo(qreal x, qreal y) + + \overload + + Moves the current position to (\a{x}, \a{y}) and starts a new + subpath, implicitly closing the previous path. +*/ + +/*! + \fn void QPainterPath::moveTo(const QPointF &point) + + Moves the current point to the given \a point, implicitly starting + a new subpath and closing the previous one. + + \sa closeSubpath(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} +*/ +void QPainterPath::moveTo(const QPointF &p) +{ +#ifdef QPP_DEBUG + printf("QPainterPath::moveTo() (%.2f,%.2f)\n", p.x(), p.y()); +#endif + + if (!qt_is_finite(p.x()) || !qt_is_finite(p.y())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::moveTo: Adding point where x or y is NaN or Inf, ignoring call"); +#endif + return; + } + + ensureData(); + detach(); + + QPainterPathData *d = d_func(); + Q_ASSERT(!d->elements.isEmpty()); + + d->require_moveTo = false; + + if (d->elements.last().type == MoveToElement) { + d->elements.last().x = p.x(); + d->elements.last().y = p.y(); + } else { + Element elm = { p.x(), p.y(), MoveToElement }; + d->elements.append(elm); + } + d->cStart = d->elements.size() - 1; +} + +/*! + \fn void QPainterPath::lineTo(qreal x, qreal y) + + \overload + + Draws a line from the current position to the point (\a{x}, + \a{y}). +*/ + +/*! + \fn void QPainterPath::lineTo(const QPointF &endPoint) + + Adds a straight line from the current position to the given \a + endPoint. After the line is drawn, the current position is updated + to be at the end point of the line. + + \sa addPolygon(), addRect(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} + */ +void QPainterPath::lineTo(const QPointF &p) +{ +#ifdef QPP_DEBUG + printf("QPainterPath::lineTo() (%.2f,%.2f)\n", p.x(), p.y()); +#endif + + if (!qt_is_finite(p.x()) || !qt_is_finite(p.y())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::lineTo: Adding point where x or y is NaN or Inf, ignoring call"); +#endif + return; + } + + ensureData(); + detach(); + + QPainterPathData *d = d_func(); + Q_ASSERT(!d->elements.isEmpty()); + d->maybeMoveTo(); + if (p == QPointF(d->elements.last())) + return; + Element elm = { p.x(), p.y(), LineToElement }; + d->elements.append(elm); + + d->convex = d->elements.size() == 3 || (d->elements.size() == 4 && d->isClosed()); +} + +/*! + \fn void QPainterPath::cubicTo(qreal c1X, qreal c1Y, qreal c2X, + qreal c2Y, qreal endPointX, qreal endPointY); + + \overload + + Adds a cubic Bezier curve between the current position and the end + point (\a{endPointX}, \a{endPointY}) with control points specified + by (\a{c1X}, \a{c1Y}) and (\a{c2X}, \a{c2Y}). +*/ + +/*! + \fn void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint) + + Adds a cubic Bezier curve between the current position and the + given \a endPoint using the control points specified by \a c1, and + \a c2. + + After the curve is added, the current position is updated to be at + the end point of the curve. + + \table 100% + \row + \o \inlineimage qpainterpath-cubicto.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 1 + \endtable + + \sa quadTo(), {QPainterPath#Composing a QPainterPath}{Composing + a QPainterPath} +*/ +void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &e) +{ +#ifdef QPP_DEBUG + printf("QPainterPath::cubicTo() (%.2f,%.2f), (%.2f,%.2f), (%.2f,%.2f)\n", + c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y()); +#endif + + if (!qt_is_finite(c1.x()) || !qt_is_finite(c1.y()) || !qt_is_finite(c2.x()) || !qt_is_finite(c2.y()) + || !qt_is_finite(e.x()) || !qt_is_finite(e.y())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::cubicTo: Adding point where x or y is NaN or Inf, ignoring call"); +#endif + return; + } + + ensureData(); + detach(); + + QPainterPathData *d = d_func(); + Q_ASSERT(!d->elements.isEmpty()); + + + // Abort on empty curve as a stroker cannot handle this and the + // curve is irrelevant anyway. + if (d->elements.last() == c1 && c1 == c2 && c2 == e) + return; + + d->maybeMoveTo(); + + Element ce1 = { c1.x(), c1.y(), CurveToElement }; + Element ce2 = { c2.x(), c2.y(), CurveToDataElement }; + Element ee = { e.x(), e.y(), CurveToDataElement }; + d->elements << ce1 << ce2 << ee; +} + +/*! + \fn void QPainterPath::quadTo(qreal cx, qreal cy, qreal endPointX, qreal endPointY); + + \overload + + Adds a quadratic Bezier curve between the current point and the endpoint + (\a{endPointX}, \a{endPointY}) with the control point specified by + (\a{cx}, \a{cy}). +*/ + +/*! + \fn void QPainterPath::quadTo(const QPointF &c, const QPointF &endPoint) + + Adds a quadratic Bezier curve between the current position and the + given \a endPoint with the control point specified by \a c. + + After the curve is added, the current point is updated to be at + the end point of the curve. + + \sa cubicTo(), {QPainterPath#Composing a QPainterPath}{Composing a + QPainterPath} +*/ +void QPainterPath::quadTo(const QPointF &c, const QPointF &e) +{ +#ifdef QPP_DEBUG + printf("QPainterPath::quadTo() (%.2f,%.2f), (%.2f,%.2f)\n", + c.x(), c.y(), e.x(), e.y()); +#endif + + if (!qt_is_finite(c.x()) || !qt_is_finite(c.y()) || !qt_is_finite(e.x()) || !qt_is_finite(e.y())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::quadTo: Adding point where x or y is NaN or Inf, ignoring call"); +#endif + return; + } + + ensureData(); + detach(); + + Q_D(QPainterPath); + Q_ASSERT(!d->elements.isEmpty()); + const QPainterPath::Element &elm = d->elements.at(elementCount()-1); + QPointF prev(elm.x, elm.y); + + // Abort on empty curve as a stroker cannot handle this and the + // curve is irrelevant anyway. + if (prev == c && c == e) + return; + + QPointF c1((prev.x() + 2*c.x()) / 3, (prev.y() + 2*c.y()) / 3); + QPointF c2((e.x() + 2*c.x()) / 3, (e.y() + 2*c.y()) / 3); + cubicTo(c1, c2, e); +} + +/*! + \fn void QPainterPath::arcTo(qreal x, qreal y, qreal width, qreal + height, qreal startAngle, qreal sweepLength) + + \overload + + Creates an arc that occupies the rectangle QRectF(\a x, \a y, \a + width, \a height), beginning at the specified \a startAngle and + extending \a sweepLength degrees counter-clockwise. + +*/ + +/*! + \fn void QPainterPath::arcTo(const QRectF &rectangle, qreal startAngle, qreal sweepLength) + + Creates an arc that occupies the given \a rectangle, beginning at + the specified \a startAngle and extending \a sweepLength degrees + counter-clockwise. + + Angles are specified in degrees. Clockwise arcs can be specified + using negative angles. + + Note that this function connects the starting point of the arc to + the current position if they are not already connected. After the + arc has been added, the current position is the last point in + arc. To draw a line back to the first point, use the + closeSubpath() function. + + \table 100% + \row + \o \inlineimage qpainterpath-arcto.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 2 + \endtable + + \sa arcMoveTo(), addEllipse(), QPainter::drawArc(), QPainter::drawPie(), + {QPainterPath#Composing a QPainterPath}{Composing a + QPainterPath} +*/ +void QPainterPath::arcTo(const QRectF &rect, qreal startAngle, qreal sweepLength) +{ +#ifdef QPP_DEBUG + printf("QPainterPath::arcTo() (%.2f, %.2f, %.2f, %.2f, angle=%.2f, sweep=%.2f\n", + rect.x(), rect.y(), rect.width(), rect.height(), startAngle, sweepLength); +#endif + + if ((!qt_is_finite(rect.x()) && !qt_is_finite(rect.y())) || !qt_is_finite(rect.width()) || !qt_is_finite(rect.height()) + || !qt_is_finite(startAngle) || !qt_is_finite(sweepLength)) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN or Inf, ignoring call"); +#endif + return; + } + + if (rect.isNull()) + return; + + ensureData(); + detach(); + + int point_count; + QPointF pts[15]; + QPointF curve_start = qt_curves_for_arc(rect, startAngle, sweepLength, pts, &point_count); + + lineTo(curve_start); + for (int i=0; i<point_count; i+=3) { + cubicTo(pts[i].x(), pts[i].y(), + pts[i+1].x(), pts[i+1].y(), + pts[i+2].x(), pts[i+2].y()); + } + +} + + +/*! + \fn void QPainterPath::arcMoveTo(qreal x, qreal y, qreal width, qreal height, qreal angle) + \overload + \since 4.2 + + Creates a move to that lies on the arc that occupies the + QRectF(\a x, \a y, \a width, \a height) at \a angle. +*/ + + +/*! + \fn void QPainterPath::arcMoveTo(const QRectF &rectangle, qreal angle) + \since 4.2 + + Creates a move to that lies on the arc that occupies the given \a + rectangle at \a angle. + + Angles are specified in degrees. Clockwise arcs can be specified + using negative angles. + + \sa moveTo(), arcTo() +*/ + +void QPainterPath::arcMoveTo(const QRectF &rect, qreal angle) +{ + if (rect.isNull()) + return; + + QPointF pt; + qt_find_ellipse_coords(rect, angle, 0, &pt, 0); + moveTo(pt); +} + + + +/*! + \fn QPointF QPainterPath::currentPosition() const + + Returns the current position of the path. +*/ +QPointF QPainterPath::currentPosition() const +{ + return !d_ptr || d_func()->elements.isEmpty() + ? QPointF() + : QPointF(d_func()->elements.last().x, d_func()->elements.last().y); +} + + +/*! + \fn void QPainterPath::addRect(qreal x, qreal y, qreal width, qreal height) + + \overload + + Adds a rectangle at position (\a{x}, \a{y}), with the given \a + width and \a height, as a closed subpath. +*/ + +/*! + \fn void QPainterPath::addRect(const QRectF &rectangle) + + Adds the given \a rectangle to this path as a closed subpath. + + The \a rectangle is added as a clockwise set of lines. The painter + path's current position after the \a rectangle has been added is + at the top-left corner of the rectangle. + + \table 100% + \row + \o \inlineimage qpainterpath-addrectangle.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 3 + \endtable + + \sa addRegion(), lineTo(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} +*/ +void QPainterPath::addRect(const QRectF &r) +{ + if (!qt_is_finite(r.x()) || !qt_is_finite(r.y()) || !qt_is_finite(r.width()) || !qt_is_finite(r.height())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::addRect: Adding rect where a parameter is NaN or Inf, ignoring call"); +#endif + return; + } + + if (r.isNull()) + return; + + ensureData(); + detach(); + + bool first = d_func()->elements.size() < 2; + + d_func()->elements.reserve(d_func()->elements.size() + 5); + moveTo(r.x(), r.y()); + + Element l1 = { r.x() + r.width(), r.y(), LineToElement }; + Element l2 = { r.x() + r.width(), r.y() + r.height(), LineToElement }; + Element l3 = { r.x(), r.y() + r.height(), LineToElement }; + Element l4 = { r.x(), r.y(), LineToElement }; + + d_func()->elements << l1 << l2 << l3 << l4; + d_func()->require_moveTo = true; + d_func()->convex = first; +} + +/*! + Adds the given \a polygon to the path as an (unclosed) subpath. + + Note that the current position after the polygon has been added, + is the last point in \a polygon. To draw a line back to the first + point, use the closeSubpath() function. + + \table 100% + \row + \o \inlineimage qpainterpath-addpolygon.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 4 + \endtable + + \sa lineTo(), {QPainterPath#Composing a QPainterPath}{Composing + a QPainterPath} +*/ +void QPainterPath::addPolygon(const QPolygonF &polygon) +{ + if (polygon.isEmpty()) + return; + + ensureData(); + detach(); + + d_func()->elements.reserve(d_func()->elements.size() + polygon.size()); + + moveTo(polygon.first()); + for (int i=1; i<polygon.size(); ++i) { + Element elm = { polygon.at(i).x(), polygon.at(i).y(), LineToElement }; + d_func()->elements << elm; + } +} + +/*! + \fn void QPainterPath::addEllipse(const QRectF &boundingRectangle) + + Creates an ellipse within the specified \a boundingRectangle + and adds it to the painter path as a closed subpath. + + The ellipse is composed of a clockwise curve, starting and + finishing at zero degrees (the 3 o'clock position). + + \table 100% + \row + \o \inlineimage qpainterpath-addellipse.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 5 + \endtable + + \sa arcTo(), QPainter::drawEllipse(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} +*/ +void QPainterPath::addEllipse(const QRectF &boundingRect) +{ + if (!qt_is_finite(boundingRect.x()) || !qt_is_finite(boundingRect.y()) + || !qt_is_finite(boundingRect.width()) || !qt_is_finite(boundingRect.height())) { +#ifndef QT_NO_DEBUG + qWarning("QPainterPath::addEllipse: Adding ellipse where a parameter is NaN or Inf, ignoring call"); +#endif + return; + } + + if (boundingRect.isNull()) + return; + + ensureData(); + detach(); + + Q_D(QPainterPath); + bool first = d_func()->elements.size() < 2; + d->elements.reserve(d->elements.size() + 13); + + QPointF pts[12]; + int point_count; + QPointF start = qt_curves_for_arc(boundingRect, 0, -360, pts, &point_count); + + moveTo(start); + cubicTo(pts[0], pts[1], pts[2]); // 0 -> 270 + cubicTo(pts[3], pts[4], pts[5]); // 270 -> 180 + cubicTo(pts[6], pts[7], pts[8]); // 180 -> 90 + cubicTo(pts[9], pts[10], pts[11]); // 90 - >0 + d_func()->require_moveTo = true; + + d_func()->convex = first; +} + +/*! + \fn void QPainterPath::addText(const QPointF &point, const QFont &font, const QString &text) + + Adds the given \a text to this path as a set of closed subpaths + created from the \a font supplied. The subpaths are positioned so + that the left end of the text's baseline lies at the specified \a + point. + + \table 100% + \row + \o \inlineimage qpainterpath-addtext.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpainterpath.cpp 6 + \endtable + + \sa QPainter::drawText(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} +*/ +void QPainterPath::addText(const QPointF &point, const QFont &f, const QString &text) +{ + if (text.isEmpty()) + return; + + ensureData(); + detach(); + + QTextLayout layout(text, f); + layout.setCacheEnabled(true); + QTextEngine *eng = layout.engine(); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + const QScriptLine &sl = eng->lines[0]; + if (!sl.length || !eng->layoutData) + return; + + int nItems = eng->layoutData->items.size(); + + qreal x(point.x()); + qreal y(point.y()); + + QVarLengthArray<int> visualOrder(nItems); + QVarLengthArray<uchar> levels(nItems); + for (int i = 0; i < nItems; ++i) + levels[i] = eng->layoutData->items[i].analysis.bidiLevel; + QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); + + for (int i = 0; i < nItems; ++i) { + int item = visualOrder[i]; + QScriptItem &si = eng->layoutData->items[item]; + + if (si.analysis.flags < QScriptAnalysis::TabOrObject) { + QGlyphLayout glyphs = eng->shapedGlyphs(&si); + QFontEngine *fe = f.d->engineForScript(si.analysis.script); + Q_ASSERT(fe); + fe->addOutlineToPath(x, y, glyphs, this, + si.analysis.bidiLevel % 2 + ? QTextItem::RenderFlags(QTextItem::RightToLeft) + : QTextItem::RenderFlags(0)); + + const qreal lw = fe->lineThickness().toReal(); + if (f.d->underline) { + qreal pos = fe->underlinePosition().toReal(); + addRect(x, y + pos, si.width.toReal(), lw); + } + if (f.d->overline) { + qreal pos = fe->ascent().toReal() + 1; + addRect(x, y - pos, si.width.toReal(), lw); + } + if (f.d->strikeOut) { + qreal pos = fe->ascent().toReal() / 3; + addRect(x, y - pos, si.width.toReal(), lw); + } + } + x += si.width.toReal(); + } +} + +/*! + \fn void QPainterPath::addPath(const QPainterPath &path) + + Adds the given \a path to \e this path as a closed subpath. + + \sa connectPath(), {QPainterPath#Composing a + QPainterPath}{Composing a QPainterPath} +*/ +void QPainterPath::addPath(const QPainterPath &other) +{ + if (other.isEmpty()) + return; + + ensureData(); + detach(); + + QPainterPathData *d = reinterpret_cast<QPainterPathData *>(d_func()); + // Remove last moveto so we don't get multiple moveto's + if (d->elements.last().type == MoveToElement) + d->elements.remove(d->elements.size()-1); + + // Locate where our own current subpath will start after the other path is added. + int cStart = d->elements.size() + other.d_func()->cStart; + d->elements += other.d_func()->elements; + d->cStart = cStart; + + d->require_moveTo = other.d_func()->isClosed(); +} + + +/*! + \fn void QPainterPath::connectPath(const QPainterPath &path) + + Connects the given \a path to \e this path by adding a line from the + last element of this path to the first element of the given path. + + \sa addPath(), {QPainterPath#Composing a QPainterPath}{Composing + a QPainterPath} +*/ +void QPainterPath::connectPath(const QPainterPath &other) +{ + if (other.isEmpty()) + return; + + ensureData(); + detach(); + + QPainterPathData *d = reinterpret_cast<QPainterPathData *>(d_func()); + // Remove last moveto so we don't get multiple moveto's + if (d->elements.last().type == MoveToElement) + d->elements.remove(d->elements.size()-1); + + // Locate where our own current subpath will start after the other path is added. + int cStart = d->elements.size() + other.d_func()->cStart; + int first = d->elements.size(); + d->elements += other.d_func()->elements; + + if (first != 0) + d->elements[first].type = LineToElement; + + // avoid duplicate points + if (first > 0 && QPointF(d->elements[first]) == QPointF(d->elements[first - 1])) { + d->elements.remove(first--); + --cStart; + } + + if (cStart != first) + d->cStart = cStart; +} + +/*! + Adds the given \a region to the path by adding each rectangle in + the region as a separate closed subpath. + + \sa addRect(), {QPainterPath#Composing a QPainterPath}{Composing + a QPainterPath} +*/ +void QPainterPath::addRegion(const QRegion ®ion) +{ + ensureData(); + detach(); + + QVector<QRect> rects = region.rects(); + d_func()->elements.reserve(rects.size() * 5); + for (int i=0; i<rects.size(); ++i) + addRect(rects.at(i)); +} + + +/*! + Returns the painter path's currently set fill rule. + + \sa setFillRule() +*/ +Qt::FillRule QPainterPath::fillRule() const +{ + return isEmpty() ? Qt::OddEvenFill : d_func()->fillRule; +} + +/*! + \fn void QPainterPath::setFillRule(Qt::FillRule fillRule) + + Sets the fill rule of the painter path to the given \a + fillRule. Qt provides two methods for filling paths: + + \table + \header + \o Qt::OddEvenFill (default) + \o Qt::WindingFill + \row + \o \inlineimage qt-fillrule-oddeven.png + \o \inlineimage qt-fillrule-winding.png + \endtable + + \sa fillRule() +*/ +void QPainterPath::setFillRule(Qt::FillRule fillRule) +{ + ensureData(); + if (d_func()->fillRule == fillRule) + return; + detach(); + + d_func()->fillRule = fillRule; +} + +#define QT_BEZIER_A(bezier, coord) 3 * (-bezier.coord##1 \ + + 3*bezier.coord##2 \ + - 3*bezier.coord##3 \ + +bezier.coord##4) + +#define QT_BEZIER_B(bezier, coord) 6 * (bezier.coord##1 \ + - 2*bezier.coord##2 \ + + bezier.coord##3) + +#define QT_BEZIER_C(bezier, coord) 3 * (- bezier.coord##1 \ + + bezier.coord##2) + +#define QT_BEZIER_CHECK_T(bezier, t) \ + if (t >= 0 && t <= 1) { \ + QPointF p(b.pointAt(t)); \ + if (p.x() < minx) minx = p.x(); \ + else if (p.x() > maxx) maxx = p.x(); \ + if (p.y() < miny) miny = p.y(); \ + else if (p.y() > maxy) maxy = p.y(); \ + } + + +static QRectF qt_painterpath_bezier_extrema(const QBezier &b) +{ + qreal minx, miny, maxx, maxy; + + // initialize with end points + if (b.x1 < b.x4) { + minx = b.x1; + maxx = b.x4; + } else { + minx = b.x4; + maxx = b.x1; + } + if (b.y1 < b.y4) { + miny = b.y1; + maxy = b.y4; + } else { + miny = b.y4; + maxy = b.y1; + } + + // Update for the X extrema + { + qreal ax = QT_BEZIER_A(b, x); + qreal bx = QT_BEZIER_B(b, x); + qreal cx = QT_BEZIER_C(b, x); + // specialcase quadratic curves to avoid div by zero + if (qFuzzyIsNull(ax)) { + + // linear curves are covered by initialization. + if (!qFuzzyIsNull(bx)) { + qreal t = -cx / bx; + QT_BEZIER_CHECK_T(b, t); + } + + } else { + const qreal tx = bx * bx - 4 * ax * cx; + + if (tx >= 0) { + qreal temp = qSqrt(tx); + qreal rcp = 1 / (2 * ax); + qreal t1 = (-bx + temp) * rcp; + QT_BEZIER_CHECK_T(b, t1); + + qreal t2 = (-bx - temp) * rcp; + QT_BEZIER_CHECK_T(b, t2); + } + } + } + + // Update for the Y extrema + { + qreal ay = QT_BEZIER_A(b, y); + qreal by = QT_BEZIER_B(b, y); + qreal cy = QT_BEZIER_C(b, y); + + // specialcase quadratic curves to avoid div by zero + if (qFuzzyIsNull(ay)) { + + // linear curves are covered by initialization. + if (!qFuzzyIsNull(by)) { + qreal t = -cy / by; + QT_BEZIER_CHECK_T(b, t); + } + + } else { + const qreal ty = by * by - 4 * ay * cy; + + if (ty > 0) { + qreal temp = qSqrt(ty); + qreal rcp = 1 / (2 * ay); + qreal t1 = (-by + temp) * rcp; + QT_BEZIER_CHECK_T(b, t1); + + qreal t2 = (-by - temp) * rcp; + QT_BEZIER_CHECK_T(b, t2); + } + } + } + return QRectF(minx, miny, maxx - minx, maxy - miny); +} + +/*! + Returns the bounding rectangle of this painter path as a rectangle with + floating point precision. + + \sa controlPointRect() +*/ +QRectF QPainterPath::boundingRect() const +{ + if (!d_ptr) + return QRectF(); + QPainterPathData *d = d_func(); + + if (d->dirtyBounds) + computeBoundingRect(); + return d->bounds; +} + +/*! + Returns the rectangle containing all the points and control points + in this path. + + This function is significantly faster to compute than the exact + boundingRect(), and the returned rectangle is always a superset of + the rectangle returned by boundingRect(). + + \sa boundingRect() +*/ +QRectF QPainterPath::controlPointRect() const +{ + if (!d_ptr) + return QRectF(); + QPainterPathData *d = d_func(); + + if (d->dirtyControlBounds) + computeControlPointRect(); + return d->controlBounds; +} + + +/*! + \fn bool QPainterPath::isEmpty() const + + Returns true if either there are no elements in this path, or if the only + element is a MoveToElement; otherwise returns false. + + \sa elementCount() +*/ + +/*! + Creates and returns a reversed copy of the path. + + It is the order of the elements that is reversed: If a + QPainterPath is composed by calling the moveTo(), lineTo() and + cubicTo() functions in the specified order, the reversed copy is + composed by calling cubicTo(), lineTo() and moveTo(). +*/ +QPainterPath QPainterPath::toReversed() const +{ + Q_D(const QPainterPath); + QPainterPath rev; + + if (isEmpty()) { + rev = *this; + return rev; + } + + rev.moveTo(d->elements.at(d->elements.size()-1).x, d->elements.at(d->elements.size()-1).y); + + for (int i=d->elements.size()-1; i>=1; --i) { + const QPainterPath::Element &elm = d->elements.at(i); + const QPainterPath::Element &prev = d->elements.at(i-1); + switch (elm.type) { + case LineToElement: + rev.lineTo(prev.x, prev.y); + break; + case MoveToElement: + rev.moveTo(prev.x, prev.y); + break; + case CurveToDataElement: + { + Q_ASSERT(i>=3); + const QPainterPath::Element &cp1 = d->elements.at(i-2); + const QPainterPath::Element &sp = d->elements.at(i-3); + Q_ASSERT(prev.type == CurveToDataElement); + Q_ASSERT(cp1.type == CurveToElement); + rev.cubicTo(prev.x, prev.y, cp1.x, cp1.y, sp.x, sp.y); + i -= 2; + break; + } + default: + Q_ASSERT(!"qt_reversed_path"); + break; + } + } + //qt_debug_path(rev); + return rev; +} + +/*! + Converts the path into a list of polygons using the QTransform + \a matrix, and returns the list. + + This function creates one polygon for each subpath regardless of + intersecting subpaths (i.e. overlapping bounding rectangles). To + make sure that such overlapping subpaths are filled correctly, use + the toFillPolygons() function instead. + + \sa toFillPolygons(), toFillPolygon(), {QPainterPath#QPainterPath + Conversion}{QPainterPath Conversion} +*/ +QList<QPolygonF> QPainterPath::toSubpathPolygons(const QTransform &matrix) const +{ + + Q_D(const QPainterPath); + QList<QPolygonF> flatCurves; + if (isEmpty()) + return flatCurves; + + QPolygonF current; + for (int i=0; i<elementCount(); ++i) { + const QPainterPath::Element &e = d->elements.at(i); + switch (e.type) { + case QPainterPath::MoveToElement: + if (current.size() > 1) + flatCurves += current; + current.clear(); + current.reserve(16); + current += QPointF(e.x, e.y) * matrix; + break; + case QPainterPath::LineToElement: + current += QPointF(e.x, e.y) * matrix; + break; + case QPainterPath::CurveToElement: { + Q_ASSERT(d->elements.at(i+1).type == QPainterPath::CurveToDataElement); + Q_ASSERT(d->elements.at(i+2).type == QPainterPath::CurveToDataElement); + QBezier bezier = QBezier::fromPoints(QPointF(d->elements.at(i-1).x, d->elements.at(i-1).y) * matrix, + QPointF(e.x, e.y) * matrix, + QPointF(d->elements.at(i+1).x, d->elements.at(i+1).y) * matrix, + QPointF(d->elements.at(i+2).x, d->elements.at(i+2).y) * matrix); + bezier.addToPolygon(¤t); + i+=2; + break; + } + case QPainterPath::CurveToDataElement: + Q_ASSERT(!"QPainterPath::toSubpathPolygons(), bad element type"); + break; + } + } + + if (current.size()>1) + flatCurves += current; + + return flatCurves; +} + +/*! + \overload + */ +QList<QPolygonF> QPainterPath::toSubpathPolygons(const QMatrix &matrix) const +{ + return toSubpathPolygons(QTransform(matrix)); +} + +/*! + Converts the path into a list of polygons using the + QTransform \a matrix, and returns the list. + + The function differs from the toFillPolygon() function in that it + creates several polygons. It is provided because it is usually + faster to draw several small polygons than to draw one large + polygon, even though the total number of points drawn is the same. + + The toFillPolygons() function differs from the toSubpathPolygons() + function in that it create only polygon for subpaths that have + overlapping bounding rectangles. + + Like the toFillPolygon() function, this function uses a rewinding + technique to make sure that overlapping subpaths can be filled + using the correct fill rule. Note that rewinding inserts addition + lines in the polygons so the outline of the fill polygon does not + match the outline of the path. + + \sa toSubpathPolygons(), toFillPolygon(), + {QPainterPath#QPainterPath Conversion}{QPainterPath Conversion} +*/ +QList<QPolygonF> QPainterPath::toFillPolygons(const QTransform &matrix) const +{ + + QList<QPolygonF> polys; + + QList<QPolygonF> subpaths = toSubpathPolygons(matrix); + int count = subpaths.size(); + + if (count == 0) + return polys; + + QList<QRectF> bounds; + for (int i=0; i<count; ++i) + bounds += subpaths.at(i).boundingRect(); + +#ifdef QPP_FILLPOLYGONS_DEBUG + printf("QPainterPath::toFillPolygons, subpathCount=%d\n", count); + for (int i=0; i<bounds.size(); ++i) + qDebug() << " bounds" << i << bounds.at(i); +#endif + + QVector< QList<int> > isects; + isects.resize(count); + + // find all intersections + for (int j=0; j<count; ++j) { + if (subpaths.at(j).size() <= 2) + continue; + QRectF cbounds = bounds.at(j); + for (int i=0; i<count; ++i) { + if (cbounds.intersects(bounds.at(i))) { + isects[j] << i; + } + } + } + +#ifdef QPP_FILLPOLYGONS_DEBUG + printf("Intersections before flattening:\n"); + for (int i = 0; i < count; ++i) { + printf("%d: ", i); + for (int j = 0; j < isects[i].size(); ++j) { + printf("%d ", isects[i][j]); + } + printf("\n"); + } +#endif + + // flatten the sets of intersections + for (int i=0; i<count; ++i) { + const QList<int> ¤t_isects = isects.at(i); + for (int j=0; j<current_isects.size(); ++j) { + int isect_j = current_isects.at(j); + if (isect_j == i) + continue; + for (int k=0; k<isects[isect_j].size(); ++k) { + int isect_k = isects[isect_j][k]; + if (isect_k != i && !isects.at(i).contains(isect_k)) { + isects[i] += isect_k; + } + } + isects[isect_j].clear(); + } + } + +#ifdef QPP_FILLPOLYGONS_DEBUG + printf("Intersections after flattening:\n"); + for (int i = 0; i < count; ++i) { + printf("%d: ", i); + for (int j = 0; j < isects[i].size(); ++j) { + printf("%d ", isects[i][j]); + } + printf("\n"); + } +#endif + + // Join the intersected subpaths as rewinded polygons + for (int i=0; i<count; ++i) { + const QList<int> &subpath_list = isects[i]; + if (!subpath_list.isEmpty()) { + QPolygonF buildUp; + for (int j=0; j<subpath_list.size(); ++j) { + const QPolygonF &subpath = subpaths.at(subpath_list.at(j)); + buildUp += subpath; + if (!subpath.isClosed()) + buildUp += subpath.first(); + if (!buildUp.isClosed()) + buildUp += buildUp.first(); + } + polys += buildUp; + } + } + + return polys; +} + +/*! + \overload + */ +QList<QPolygonF> QPainterPath::toFillPolygons(const QMatrix &matrix) const +{ + return toFillPolygons(QTransform(matrix)); +} + +//same as qt_polygon_isect_line in qpolygon.cpp +static void qt_painterpath_isect_line(const QPointF &p1, + const QPointF &p2, + const QPointF &pos, + int *winding) +{ + qreal x1 = p1.x(); + qreal y1 = p1.y(); + qreal x2 = p2.x(); + qreal y2 = p2.y(); + qreal y = pos.y(); + + int dir = 1; + + if (qFuzzyCompare(y1, y2)) { + // ignore horizontal lines according to scan conversion rule + return; + } else if (y2 < y1) { + qreal x_tmp = x2; x2 = x1; x1 = x_tmp; + qreal y_tmp = y2; y2 = y1; y1 = y_tmp; + dir = -1; + } + + if (y >= y1 && y < y2) { + qreal x = x1 + ((x2 - x1) / (y2 - y1)) * (y - y1); + + // count up the winding number if we're + if (x<=pos.x()) { + (*winding) += dir; + } + } +} + +static void qt_painterpath_isect_curve(const QBezier &bezier, const QPointF &pt, + int *winding, int depth = 0) +{ + qreal y = pt.y(); + qreal x = pt.x(); + QRectF bounds = bezier.bounds(); + + // potential intersection, divide and try again... + // Please note that a sideeffect of the bottom exclusion is that + // horizontal lines are dropped, but this is correct according to + // scan conversion rules. + if (y >= bounds.y() && y < bounds.y() + bounds.height()) { + + // hit lower limit... This is a rough threshold, but its a + // tradeoff between speed and precision. + const qreal lower_bound = qreal(.001); + if (depth == 32 || (bounds.width() < lower_bound && bounds.height() < lower_bound)) { + // We make the assumption here that the curve starts to + // approximate a line after while (i.e. that it doesn't + // change direction drastically during its slope) + if (bezier.pt1().x() <= x) { + (*winding) += (bezier.pt4().y() > bezier.pt1().y() ? 1 : -1); + } + return; + } + + // split curve and try again... + QBezier first_half, second_half; + bezier.split(&first_half, &second_half); + qt_painterpath_isect_curve(first_half, pt, winding, depth + 1); + qt_painterpath_isect_curve(second_half, pt, winding, depth + 1); + } +} + +/*! + \fn bool QPainterPath::contains(const QPointF &point) const + + Returns true if the given \a point is inside the path, otherwise + returns false. + + \sa intersects() +*/ +bool QPainterPath::contains(const QPointF &pt) const +{ + if (isEmpty() || !controlPointRect().contains(pt)) + return false; + + QPainterPathData *d = d_func(); + + int winding_number = 0; + + QPointF last_pt; + QPointF last_start; + for (int i=0; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + + switch (e.type) { + + case MoveToElement: + if (i > 0) // implicitly close all paths. + qt_painterpath_isect_line(last_pt, last_start, pt, &winding_number); + last_start = last_pt = e; + break; + + case LineToElement: + qt_painterpath_isect_line(last_pt, e, pt, &winding_number); + last_pt = e; + break; + + case CurveToElement: + { + const QPainterPath::Element &cp2 = d->elements.at(++i); + const QPainterPath::Element &ep = d->elements.at(++i); + qt_painterpath_isect_curve(QBezier::fromPoints(last_pt, e, cp2, ep), + pt, &winding_number); + last_pt = ep; + + } + break; + + default: + break; + } + } + + // implicitly close last subpath + if (last_pt != last_start) + qt_painterpath_isect_line(last_pt, last_start, pt, &winding_number); + + return (d->fillRule == Qt::WindingFill + ? (winding_number != 0) + : ((winding_number % 2) != 0)); +} + +static bool qt_painterpath_isect_line_rect(qreal x1, qreal y1, qreal x2, qreal y2, + const QRectF &rect) +{ + qreal left = rect.left(); + qreal right = rect.right(); + qreal top = rect.top(); + qreal bottom = rect.bottom(); + + enum { Left, Right, Top, Bottom }; + // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html + int p1 = ((x1 < left) << Left) + | ((x1 > right) << Right) + | ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + int p2 = ((x2 < left) << Left) + | ((x2 > right) << Right) + | ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + + if (p1 & p2) + // completely inside + return false; + + if (p1 | p2) { + qreal dx = x2 - x1; + qreal dy = y2 - y1; + + // clip x coordinates + if (x1 < left) { + y1 += dy/dx * (left - x1); + x1 = left; + } else if (x1 > right) { + y1 -= dy/dx * (x1 - right); + x1 = right; + } + if (x2 < left) { + y2 += dy/dx * (left - x2); + x2 = left; + } else if (x2 > right) { + y2 -= dy/dx * (x2 - right); + x2 = right; + } + + p1 = ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + p2 = ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + + if (p1 & p2) + return false; + + // clip y coordinates + if (y1 < top) { + x1 += dx/dy * (top - y1); + y1 = top; + } else if (y1 > bottom) { + x1 -= dx/dy * (y1 - bottom); + y1 = bottom; + } + if (y2 < top) { + x2 += dx/dy * (top - y2); + y2 = top; + } else if (y2 > bottom) { + x2 -= dx/dy * (y2 - bottom); + y2 = bottom; + } + + p1 = ((x1 < left) << Left) + | ((x1 > right) << Right); + p2 = ((x2 < left) << Left) + | ((x2 > right) << Right); + + if (p1 & p2) + return false; + + return true; + } + return false; +} + +static bool qt_isect_curve_horizontal(const QBezier &bezier, qreal y, qreal x1, qreal x2, int depth = 0) +{ + QRectF bounds = bezier.bounds(); + + if (y >= bounds.top() && y < bounds.bottom() + && bounds.right() >= x1 && bounds.left() < x2) { + const qreal lower_bound = qreal(.01); + if (depth == 32 || (bounds.width() < lower_bound && bounds.height() < lower_bound)) + return true; + + QBezier first_half, second_half; + bezier.split(&first_half, &second_half); + if (qt_isect_curve_horizontal(first_half, y, x1, x2, depth + 1) + || qt_isect_curve_horizontal(second_half, y, x1, x2, depth + 1)) + return true; + } + return false; +} + +static bool qt_isect_curve_vertical(const QBezier &bezier, qreal x, qreal y1, qreal y2, int depth = 0) +{ + QRectF bounds = bezier.bounds(); + + if (x >= bounds.left() && x < bounds.right() + && bounds.bottom() >= y1 && bounds.top() < y2) { + const qreal lower_bound = qreal(.01); + if (depth == 32 || (bounds.width() < lower_bound && bounds.height() < lower_bound)) + return true; + + QBezier first_half, second_half; + bezier.split(&first_half, &second_half); + if (qt_isect_curve_vertical(first_half, x, y1, y2, depth + 1) + || qt_isect_curve_vertical(second_half, x, y1, y2, depth + 1)) + return true; + } + return false; +} + +/* + Returns true if any lines or curves cross the four edges in of rect +*/ +static bool qt_painterpath_check_crossing(const QPainterPath *path, const QRectF &rect) +{ + QPointF last_pt; + QPointF last_start; + for (int i=0; i<path->elementCount(); ++i) { + const QPainterPath::Element &e = path->elementAt(i); + + switch (e.type) { + + case QPainterPath::MoveToElement: + if (i > 0 + && qFuzzyCompare(last_pt.x(), last_start.x()) + && qFuzzyCompare(last_pt.y(), last_start.y()) + && qt_painterpath_isect_line_rect(last_pt.x(), last_pt.y(), + last_start.x(), last_start.y(), rect)) + return true; + last_start = last_pt = e; + break; + + case QPainterPath::LineToElement: + if (qt_painterpath_isect_line_rect(last_pt.x(), last_pt.y(), e.x, e.y, rect)) + return true; + last_pt = e; + break; + + case QPainterPath::CurveToElement: + { + QPointF cp2 = path->elementAt(++i); + QPointF ep = path->elementAt(++i); + QBezier bezier = QBezier::fromPoints(last_pt, e, cp2, ep); + if (qt_isect_curve_horizontal(bezier, rect.top(), rect.left(), rect.right()) + || qt_isect_curve_horizontal(bezier, rect.bottom(), rect.left(), rect.right()) + || qt_isect_curve_vertical(bezier, rect.left(), rect.top(), rect.bottom()) + || qt_isect_curve_vertical(bezier, rect.right(), rect.top(), rect.bottom())) + return true; + last_pt = ep; + } + break; + + default: + break; + } + } + + // implicitly close last subpath + if (last_pt != last_start + && qt_painterpath_isect_line_rect(last_pt.x(), last_pt.y(), + last_start.x(), last_start.y(), rect)) + return true; + + return false; +} + +/*! + \fn bool QPainterPath::intersects(const QRectF &rectangle) const + + Returns true if any point in the given \a rectangle intersects the + path; otherwise returns false. + + There is an intersection if any of the lines making up the + rectangle crosses a part of the path or if any part of the + rectangle overlaps with any area enclosed by the path. This + function respects the current fillRule to determine what is + considered inside the path. + + \sa contains() +*/ +bool QPainterPath::intersects(const QRectF &rect) const +{ + if (elementCount() == 1 && rect.contains(elementAt(0))) + return true; + + if (isEmpty()) + return false; + + QRectF cp = controlPointRect(); + QRectF rn = rect.normalized(); + + // QRectF::intersects returns false if one of the rects is a null rect + // which would happen for a painter path consisting of a vertical or + // horizontal line + if (qMax(rn.left(), cp.left()) > qMin(rn.right(), cp.right()) + || qMax(rn.top(), cp.top()) > qMin(rn.bottom(), cp.bottom())) + return false; + + // If any path element cross the rect its bound to be an intersection + if (qt_painterpath_check_crossing(this, rect)) + return true; + + if (contains(rect.center())) + return true; + + Q_D(QPainterPath); + + // Check if the rectangle surounds any subpath... + for (int i=0; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + if (e.type == QPainterPath::MoveToElement && rect.contains(e)) + return true; + } + + return false; +} + +/*! + Translates all elements in the path by (\a{dx}, \a{dy}). + + \since 4.6 + \sa translated() +*/ +void QPainterPath::translate(qreal dx, qreal dy) +{ + if (!d_ptr || (dx == 0 && dy == 0)) + return; + + int elementsLeft = d_ptr->elements.size(); + if (elementsLeft <= 0) + return; + + detach(); + QPainterPath::Element *element = d_func()->elements.data(); + Q_ASSERT(element); + while (elementsLeft--) { + element->x += dx; + element->y += dy; + ++element; + } +} + +/*! + \fn void QPainterPath::translate(const QPointF &offset) + \overload + \since 4.6 + + Translates all elements in the path by the given \a offset. + + \sa translated() +*/ + +/*! + Returns a copy of the path that is translated by (\a{dx}, \a{dy}). + + \since 4.6 + \sa translate() +*/ +QPainterPath QPainterPath::translated(qreal dx, qreal dy) const +{ + QPainterPath copy(*this); + copy.translate(dx, dy); + return copy; +} + +/*! + \fn QPainterPath QPainterPath::translated(const QPointF &offset) const; + \overload + \since 4.6 + + Returns a copy of the path that is translated by the given \a offset. + + \sa translate() +*/ + +/*! + \fn bool QPainterPath::contains(const QRectF &rectangle) const + + Returns true if the given \a rectangle is inside the path, + otherwise returns false. +*/ +bool QPainterPath::contains(const QRectF &rect) const +{ + Q_D(QPainterPath); + + // the path is empty or the control point rect doesn't completely + // cover the rectangle we abort stratight away. + if (isEmpty() || !controlPointRect().contains(rect)) + return false; + + // if there are intersections, chances are that the rect is not + // contained, except if we have winding rule, in which case it + // still might. + if (qt_painterpath_check_crossing(this, rect)) { + if (fillRule() == Qt::OddEvenFill) { + return false; + } else { + // Do some wague sampling in the winding case. This is not + // precise but it should mostly be good enough. + if (!contains(rect.topLeft()) || + !contains(rect.topRight()) || + !contains(rect.bottomRight()) || + !contains(rect.bottomLeft())) + return false; + } + } + + // If there exists a point inside that is not part of the path its + // because: rectangle lies completely outside path or a subpath + // excludes parts of the rectangle. Both cases mean that the rect + // is not contained + if (!contains(rect.center())) + return false; + + // If there are any subpaths inside this rectangle we need to + // check if they are still contained as a result of the fill + // rule. This can only be the case for WindingFill though. For + // OddEvenFill the rect will never be contained if it surrounds a + // subpath. (the case where two subpaths are completely identical + // can be argued but we choose to neglect it). + for (int i=0; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + if (e.type == QPainterPath::MoveToElement && rect.contains(e)) { + if (fillRule() == Qt::OddEvenFill) + return false; + + bool stop = false; + for (; !stop && i<d->elements.size(); ++i) { + const Element &el = d->elements.at(i); + switch (el.type) { + case MoveToElement: + stop = true; + break; + case LineToElement: + if (!contains(el)) + return false; + break; + case CurveToElement: + if (!contains(d->elements.at(i+2))) + return false; + i += 2; + break; + default: + break; + } + } + + // compensate for the last ++i in the inner for + --i; + } + } + + return true; +} + +static inline bool epsilonCompare(const QPointF &a, const QPointF &b, const QSizeF &epsilon) +{ + return qAbs(a.x() - b.x()) <= epsilon.width() + && qAbs(a.y() - b.y()) <= epsilon.height(); +} + +/*! + Returns true if this painterpath is equal to the given \a path. + + Note that comparing paths may involve a per element comparison + which can be slow for complex paths. + + \sa operator!=() +*/ + +bool QPainterPath::operator==(const QPainterPath &path) const +{ + QPainterPathData *d = reinterpret_cast<QPainterPathData *>(d_func()); + if (path.d_func() == d) + return true; + else if (!d || !path.d_func()) + return false; + else if (d->fillRule != path.d_func()->fillRule) + return false; + else if (d->elements.size() != path.d_func()->elements.size()) + return false; + + const qreal qt_epsilon = sizeof(qreal) == sizeof(double) ? 1e-12 : qreal(1e-5); + + QSizeF epsilon = boundingRect().size(); + epsilon.rwidth() *= qt_epsilon; + epsilon.rheight() *= qt_epsilon; + + for (int i = 0; i < d->elements.size(); ++i) + if (d->elements.at(i).type != path.d_func()->elements.at(i).type + || !epsilonCompare(d->elements.at(i), path.d_func()->elements.at(i), epsilon)) + return false; + + return true; +} + +/*! + Returns true if this painter path differs from the given \a path. + + Note that comparing paths may involve a per element comparison + which can be slow for complex paths. + + \sa operator==() +*/ + +bool QPainterPath::operator!=(const QPainterPath &path) const +{ + return !(*this==path); +} + +/*! + \since 4.5 + + Returns the intersection of this path and the \a other path. + + \sa intersected(), operator&=(), united(), operator|() +*/ +QPainterPath QPainterPath::operator&(const QPainterPath &other) const +{ + return intersected(other); +} + +/*! + \since 4.5 + + Returns the union of this path and the \a other path. + + \sa united(), operator|=(), intersected(), operator&() +*/ +QPainterPath QPainterPath::operator|(const QPainterPath &other) const +{ + return united(other); +} + +/*! + \since 4.5 + + Returns the union of this path and the \a other path. This function is equivalent + to operator|(). + + \sa united(), operator+=(), operator-() +*/ +QPainterPath QPainterPath::operator+(const QPainterPath &other) const +{ + return united(other); +} + +/*! + \since 4.5 + + Subtracts the \a other path from a copy of this path, and returns the copy. + + \sa subtracted(), operator-=(), operator+() +*/ +QPainterPath QPainterPath::operator-(const QPainterPath &other) const +{ + return subtracted(other); +} + +/*! + \since 4.5 + + Intersects this path with \a other and returns a reference to this path. + + \sa intersected(), operator&(), operator|=() +*/ +QPainterPath &QPainterPath::operator&=(const QPainterPath &other) +{ + return *this = (*this & other); +} + +/*! + \since 4.5 + + Unites this path with \a other and returns a reference to this path. + + \sa united(), operator|(), operator&=() +*/ +QPainterPath &QPainterPath::operator|=(const QPainterPath &other) +{ + return *this = (*this | other); +} + +/*! + \since 4.5 + + Unites this path with \a other, and returns a reference to this path. This + is equivalent to operator|=(). + + \sa united(), operator+(), operator-=() +*/ +QPainterPath &QPainterPath::operator+=(const QPainterPath &other) +{ + return *this = (*this + other); +} + +/*! + \since 4.5 + + Subtracts \a other from this path, and returns a reference to this + path. + + \sa subtracted(), operator-(), operator+=() +*/ +QPainterPath &QPainterPath::operator-=(const QPainterPath &other) +{ + return *this = (*this - other); +} + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QPainterPath &path) + \relates QPainterPath + + Writes the given painter \a path to the given \a stream, and + returns a reference to the \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &s, const QPainterPath &p) +{ + if (p.isEmpty()) { + s << 0; + return s; + } + + s << p.elementCount(); + for (int i=0; i < p.d_func()->elements.size(); ++i) { + const QPainterPath::Element &e = p.d_func()->elements.at(i); + s << int(e.type); + s << double(e.x) << double(e.y); + } + s << p.d_func()->cStart; + s << int(p.d_func()->fillRule); + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QPainterPath &path) + \relates QPainterPath + + Reads a painter path from the given \a stream into the specified \a path, + and returns a reference to the \a stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &s, QPainterPath &p) +{ + int size; + s >> size; + + if (size == 0) + return s; + + p.ensureData(); // in case if p.d_func() == 0 + if (p.d_func()->elements.size() == 1) { + Q_ASSERT(p.d_func()->elements.at(0).type == QPainterPath::MoveToElement); + p.d_func()->elements.clear(); + } + p.d_func()->elements.reserve(p.d_func()->elements.size() + size); + for (int i=0; i<size; ++i) { + int type; + double x, y; + s >> type; + s >> x; + s >> y; + Q_ASSERT(type >= 0 && type <= 3); + if (!qt_is_finite(x) || !qt_is_finite(y)) { +#ifndef QT_NO_DEBUG + qWarning("QDataStream::operator>>: NaN or Inf element found in path, skipping it"); +#endif + continue; + } + QPainterPath::Element elm = { x, y, QPainterPath::ElementType(type) }; + p.d_func()->elements.append(elm); + } + s >> p.d_func()->cStart; + int fillRule; + s >> fillRule; + Q_ASSERT(fillRule == Qt::OddEvenFill || Qt::WindingFill); + p.d_func()->fillRule = Qt::FillRule(fillRule); + p.d_func()->dirtyBounds = true; + p.d_func()->dirtyControlBounds = true; + return s; +} +#endif // QT_NO_DATASTREAM + + +/******************************************************************************* + * class QPainterPathStroker + */ + +void qt_path_stroke_move_to(qfixed x, qfixed y, void *data) +{ + ((QPainterPath *) data)->moveTo(qt_fixed_to_real(x), qt_fixed_to_real(y)); +} + +void qt_path_stroke_line_to(qfixed x, qfixed y, void *data) +{ + ((QPainterPath *) data)->lineTo(qt_fixed_to_real(x), qt_fixed_to_real(y)); +} + +void qt_path_stroke_cubic_to(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data) +{ + ((QPainterPath *) data)->cubicTo(qt_fixed_to_real(c1x), qt_fixed_to_real(c1y), + qt_fixed_to_real(c2x), qt_fixed_to_real(c2y), + qt_fixed_to_real(ex), qt_fixed_to_real(ey)); +} + +/*! + \since 4.1 + \class QPainterPathStroker + \ingroup painting + + \brief The QPainterPathStroker class is used to generate fillable + outlines for a given painter path. + + By calling the createStroke() function, passing a given + QPainterPath as argument, a new painter path representing the + outline of the given path is created. The newly created painter + path can then be filled to draw the original painter path's + outline. + + You can control the various design aspects (width, cap styles, + join styles and dash pattern) of the outlining using the following + functions: + + \list + \o setWidth() + \o setCapStyle() + \o setJoinStyle() + \o setDashPattern() + \endlist + + The setDashPattern() function accepts both a Qt::PenStyle object + and a vector representation of the pattern as argument. + + In addition you can specify a curve's threshold, controlling the + granularity with which a curve is drawn, using the + setCurveThreshold() function. The default threshold is a well + adjusted value (0.25), and normally you should not need to modify + it. However, you can make the curve's appearance smoother by + decreasing its value. + + You can also control the miter limit for the generated outline + using the setMiterLimit() function. The miter limit describes how + far from each join the miter join can extend. The limit is + specified in the units of width so the pixelwise miter limit will + be \c {miterlimit * width}. This value is only used if the join + style is Qt::MiterJoin. + + The painter path generated by the createStroke() function should + only be used for outlining the given painter path. Otherwise it + may cause unexpected behavior. Generated outlines also require the + Qt::WindingFill rule which is set by default. + + \sa QPen, QBrush +*/ + +QPainterPathStrokerPrivate::QPainterPathStrokerPrivate() + : dashOffset(0) +{ + stroker.setMoveToHook(qt_path_stroke_move_to); + stroker.setLineToHook(qt_path_stroke_line_to); + stroker.setCubicToHook(qt_path_stroke_cubic_to); +} + +/*! + Creates a new stroker. + */ +QPainterPathStroker::QPainterPathStroker() + : d_ptr(new QPainterPathStrokerPrivate) +{ +} + +/*! + Destroys the stroker. +*/ +QPainterPathStroker::~QPainterPathStroker() +{ +} + + +/*! + Generates a new path that is a fillable area representing the + outline of the given \a path. + + The various design aspects of the outline are based on the + stroker's properties: width(), capStyle(), joinStyle(), + dashPattern(), curveThreshold() and miterLimit(). + + The generated path should only be used for outlining the given + painter path. Otherwise it may cause unexpected + behavior. Generated outlines also require the Qt::WindingFill rule + which is set by default. +*/ +QPainterPath QPainterPathStroker::createStroke(const QPainterPath &path) const +{ + QPainterPathStrokerPrivate *d = const_cast<QPainterPathStrokerPrivate *>(d_func()); + QPainterPath stroke; + if (path.isEmpty()) + return path; + if (d->dashPattern.isEmpty()) { + d->stroker.strokePath(path, &stroke, QTransform()); + } else { + QDashStroker dashStroker(&d->stroker); + dashStroker.setDashPattern(d->dashPattern); + dashStroker.setDashOffset(d->dashOffset); + dashStroker.setClipRect(d->stroker.clipRect()); + dashStroker.strokePath(path, &stroke, QTransform()); + } + stroke.setFillRule(Qt::WindingFill); + return stroke; +} + +/*! + Sets the width of the generated outline painter path to \a width. + + The generated outlines will extend approximately 50% of \a width + to each side of the given input path's original outline. +*/ +void QPainterPathStroker::setWidth(qreal width) +{ + Q_D(QPainterPathStroker); + if (width <= 0) + width = 1; + d->stroker.setStrokeWidth(qt_real_to_fixed(width)); +} + +/*! + Returns the width of the generated outlines. +*/ +qreal QPainterPathStroker::width() const +{ + return qt_fixed_to_real(d_func()->stroker.strokeWidth()); +} + + +/*! + Sets the cap style of the generated outlines to \a style. If a + dash pattern is set, each segment of the pattern is subject to the + cap \a style. +*/ +void QPainterPathStroker::setCapStyle(Qt::PenCapStyle style) +{ + d_func()->stroker.setCapStyle(style); +} + + +/*! + Returns the cap style of the generated outlines. +*/ +Qt::PenCapStyle QPainterPathStroker::capStyle() const +{ + return d_func()->stroker.capStyle(); +} + +/*! + Sets the join style of the generated outlines to \a style. +*/ +void QPainterPathStroker::setJoinStyle(Qt::PenJoinStyle style) +{ + d_func()->stroker.setJoinStyle(style); +} + +/*! + Returns the join style of the generated outlines. +*/ +Qt::PenJoinStyle QPainterPathStroker::joinStyle() const +{ + return d_func()->stroker.joinStyle(); +} + +/*! + Sets the miter limit of the generated outlines to \a limit. + + The miter limit describes how far from each join the miter join + can extend. The limit is specified in units of the currently set + width. So the pixelwise miter limit will be \c { miterlimit * + width}. + + This value is only used if the join style is Qt::MiterJoin. +*/ +void QPainterPathStroker::setMiterLimit(qreal limit) +{ + d_func()->stroker.setMiterLimit(qt_real_to_fixed(limit)); +} + +/*! + Returns the miter limit for the generated outlines. +*/ +qreal QPainterPathStroker::miterLimit() const +{ + return qt_fixed_to_real(d_func()->stroker.miterLimit()); +} + + +/*! + Specifies the curve flattening \a threshold, controlling the + granularity with which the generated outlines' curve is drawn. + + The default threshold is a well adjusted value (0.25), and + normally you should not need to modify it. However, you can make + the curve's appearance smoother by decreasing its value. +*/ +void QPainterPathStroker::setCurveThreshold(qreal threshold) +{ + d_func()->stroker.setCurveThreshold(qt_real_to_fixed(threshold)); +} + +/*! + Returns the curve flattening threshold for the generated + outlines. +*/ +qreal QPainterPathStroker::curveThreshold() const +{ + return qt_fixed_to_real(d_func()->stroker.curveThreshold()); +} + +/*! + Sets the dash pattern for the generated outlines to \a style. +*/ +void QPainterPathStroker::setDashPattern(Qt::PenStyle style) +{ + d_func()->dashPattern = QDashStroker::patternForStyle(style); +} + +/*! + \overload + + Sets the dash pattern for the generated outlines to \a + dashPattern. This function makes it possible to specify custom + dash patterns. + + Each element in the vector contains the lengths of the dashes and spaces + in the stroke, beginning with the first dash in the first element, the + first space in the second element, and alternating between dashes and + spaces for each following pair of elements. + + The vector can contain an odd number of elements, in which case the last + element will be extended by the length of the first element when the + pattern repeats. +*/ +void QPainterPathStroker::setDashPattern(const QVector<qreal> &dashPattern) +{ + d_func()->dashPattern.clear(); + for (int i=0; i<dashPattern.size(); ++i) + d_func()->dashPattern << qt_real_to_fixed(dashPattern.at(i)); +} + +/*! + Returns the dash pattern for the generated outlines. +*/ +QVector<qreal> QPainterPathStroker::dashPattern() const +{ + return d_func()->dashPattern; +} + +/*! + Returns the dash offset for the generated outlines. + */ +qreal QPainterPathStroker::dashOffset() const +{ + return d_func()->dashOffset; +} + +/*! + Sets the dash offset for the generated outlines to \a offset. + + See the documentation for QPen::setDashOffset() for a description of the + dash offset. + */ +void QPainterPathStroker::setDashOffset(qreal offset) +{ + d_func()->dashOffset = offset; +} + +/*! + Converts the path into a polygon using the QTransform + \a matrix, and returns the polygon. + + The polygon is created by first converting all subpaths to + polygons, then using a rewinding technique to make sure that + overlapping subpaths can be filled using the correct fill rule. + + Note that rewinding inserts addition lines in the polygon so + the outline of the fill polygon does not match the outline of + the path. + + \sa toSubpathPolygons(), toFillPolygons(), + {QPainterPath#QPainterPath Conversion}{QPainterPath Conversion} +*/ +QPolygonF QPainterPath::toFillPolygon(const QTransform &matrix) const +{ + + QList<QPolygonF> flats = toSubpathPolygons(matrix); + QPolygonF polygon; + if (flats.isEmpty()) + return polygon; + QPointF first = flats.first().first(); + for (int i=0; i<flats.size(); ++i) { + polygon += flats.at(i); + if (!flats.at(i).isClosed()) + polygon += flats.at(i).first(); + if (i > 0) + polygon += first; + } + return polygon; +} + +/*! + \overload +*/ +QPolygonF QPainterPath::toFillPolygon(const QMatrix &matrix) const +{ + return toFillPolygon(QTransform(matrix)); +} + + +//derivative of the equation +static inline qreal slopeAt(qreal t, qreal a, qreal b, qreal c, qreal d) +{ + return 3*t*t*(d - 3*c + 3*b - a) + 6*t*(c - 2*b + a) + 3*(b - a); +} + +/*! + Returns the length of the current path. +*/ +qreal QPainterPath::length() const +{ + Q_D(QPainterPath); + if (isEmpty()) + return 0; + + qreal len = 0; + for (int i=1; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + + switch (e.type) { + case MoveToElement: + break; + case LineToElement: + { + len += QLineF(d->elements.at(i-1), e).length(); + break; + } + case CurveToElement: + { + QBezier b = QBezier::fromPoints(d->elements.at(i-1), + e, + d->elements.at(i+1), + d->elements.at(i+2)); + len += b.length(); + i += 2; + break; + } + default: + break; + } + } + return len; +} + +/*! + Returns percentage of the whole path at the specified length \a len. + + Note that similarly to other percent methods, the percentage measurement + is not linear with regards to the length, if curves are present + in the path. When curves are present the percentage argument is mapped + to the t parameter of the Bezier equations. +*/ +qreal QPainterPath::percentAtLength(qreal len) const +{ + Q_D(QPainterPath); + if (isEmpty() || len <= 0) + return 0; + + qreal totalLength = length(); + if (len > totalLength) + return 1; + + qreal curLen = 0; + for (int i=1; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + + switch (e.type) { + case MoveToElement: + break; + case LineToElement: + { + QLineF line(d->elements.at(i-1), e); + qreal llen = line.length(); + curLen += llen; + if (curLen >= len) { + return len/totalLength ; + } + + break; + } + case CurveToElement: + { + QBezier b = QBezier::fromPoints(d->elements.at(i-1), + e, + d->elements.at(i+1), + d->elements.at(i+2)); + qreal blen = b.length(); + qreal prevLen = curLen; + curLen += blen; + + if (curLen >= len) { + qreal res = b.tAtLength(len - prevLen); + return (res * blen + prevLen)/totalLength; + } + + i += 2; + break; + } + default: + break; + } + } + + return 0; +} + +static inline QBezier bezierAtT(const QPainterPath &path, qreal t, qreal *startingLength, qreal *bezierLength) +{ + *startingLength = 0; + if (t > 1) + return QBezier(); + + qreal curLen = 0; + qreal totalLength = path.length(); + + const int lastElement = path.elementCount() - 1; + for (int i=0; i <= lastElement; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + + switch (e.type) { + case QPainterPath::MoveToElement: + break; + case QPainterPath::LineToElement: + { + QLineF line(path.elementAt(i-1), e); + qreal llen = line.length(); + curLen += llen; + if (i == lastElement || curLen/totalLength >= t) { + *bezierLength = llen; + QPointF a = path.elementAt(i-1); + QPointF delta = e - a; + return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e); + } + break; + } + case QPainterPath::CurveToElement: + { + QBezier b = QBezier::fromPoints(path.elementAt(i-1), + e, + path.elementAt(i+1), + path.elementAt(i+2)); + qreal blen = b.length(); + curLen += blen; + + if (i + 2 == lastElement || curLen/totalLength >= t) { + *bezierLength = blen; + return b; + } + + i += 2; + break; + } + default: + break; + } + *startingLength = curLen; + } + return QBezier(); +} + +/*! + Returns the point at at the percentage \a t of the current path. + The argument \a t has to be between 0 and 1. + + Note that similarly to other percent methods, the percentage measurement + is not linear with regards to the length, if curves are present + in the path. When curves are present the percentage argument is mapped + to the t parameter of the Bezier equations. +*/ +QPointF QPainterPath::pointAtPercent(qreal t) const +{ + if (t < 0 || t > 1) { + qWarning("QPainterPath::pointAtPercent accepts only values between 0 and 1"); + return QPointF(); + } + + if (!d_ptr || d_ptr->elements.size() == 0) + return QPointF(); + + if (d_ptr->elements.size() == 1) + return d_ptr->elements.at(0); + + qreal totalLength = length(); + qreal curLen = 0; + qreal bezierLen = 0; + QBezier b = bezierAtT(*this, t, &curLen, &bezierLen); + qreal realT = (totalLength * t - curLen) / bezierLen; + + return b.pointAt(qBound(qreal(0), realT, qreal(1))); +} + +/*! + Returns the angle of the path tangent at the percentage \a t. + The argument \a t has to be between 0 and 1. + + Positive values for the angles mean counter-clockwise while negative values + mean the clockwise direction. Zero degrees is at the 3 o'clock position. + + Note that similarly to the other percent methods, the percentage measurement + is not linear with regards to the length if curves are present + in the path. When curves are present the percentage argument is mapped + to the t parameter of the Bezier equations. +*/ +qreal QPainterPath::angleAtPercent(qreal t) const +{ + if (t < 0 || t > 1) { + qWarning("QPainterPath::angleAtPercent accepts only values between 0 and 1"); + return 0; + } + + qreal totalLength = length(); + qreal curLen = 0; + qreal bezierLen = 0; + QBezier bez = bezierAtT(*this, t, &curLen, &bezierLen); + qreal realT = (totalLength * t - curLen) / bezierLen; + + qreal m1 = slopeAt(realT, bez.x1, bez.x2, bez.x3, bez.x4); + qreal m2 = slopeAt(realT, bez.y1, bez.y2, bez.y3, bez.y4); + + return QLineF(0, 0, m1, m2).angle(); +} + +#if defined(Q_WS_WINCE) +#pragma warning( disable : 4056 4756 ) +#endif + +/*! + Returns the slope of the path at the percentage \a t. The + argument \a t has to be between 0 and 1. + + Note that similarly to other percent methods, the percentage measurement + is not linear with regards to the length, if curves are present + in the path. When curves are present the percentage argument is mapped + to the t parameter of the Bezier equations. +*/ +qreal QPainterPath::slopeAtPercent(qreal t) const +{ + if (t < 0 || t > 1) { + qWarning("QPainterPath::slopeAtPercent accepts only values between 0 and 1"); + return 0; + } + + qreal totalLength = length(); + qreal curLen = 0; + qreal bezierLen = 0; + QBezier bez = bezierAtT(*this, t, &curLen, &bezierLen); + qreal realT = (totalLength * t - curLen) / bezierLen; + + qreal m1 = slopeAt(realT, bez.x1, bez.x2, bez.x3, bez.x4); + qreal m2 = slopeAt(realT, bez.y1, bez.y2, bez.y3, bez.y4); + //tangent line + qreal slope = 0; + +#define SIGN(x) ((x < 0)?-1:1) + if (m1) + slope = m2/m1; + else { + //windows doesn't define INFINITY :( +#ifdef INFINITY + slope = INFINITY*SIGN(m2); +#else + if (sizeof(qreal) == sizeof(double)) { + return 1.79769313486231570e+308; + } else { + return ((qreal)3.40282346638528860e+38); + } +#endif + } + + return slope; +} + +/*! + \since 4.4 + + Adds the given rectangle \a rect with rounded corners to the path. + + The \a xRadius and \a yRadius arguments specify the radii of + the ellipses defining the corners of the rounded rectangle. + When \a mode is Qt::RelativeSize, \a xRadius and + \a yRadius are specified in percentage of half the rectangle's + width and height respectively, and should be in the range 0.0 to 100.0. + + \sa addRect() +*/ +void QPainterPath::addRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode) +{ + QRectF r = rect.normalized(); + + if (r.isNull()) + return; + + if (mode == Qt::AbsoluteSize) { + qreal w = r.width() / 2; + qreal h = r.height() / 2; + + if (w == 0) { + xRadius = 0; + } else { + xRadius = 100 * qMin(xRadius, w) / w; + } + if (h == 0) { + yRadius = 0; + } else { + yRadius = 100 * qMin(yRadius, h) / h; + } + } else { + if (xRadius > 100) // fix ranges + xRadius = 100; + + if (yRadius > 100) + yRadius = 100; + } + + if (xRadius <= 0 || yRadius <= 0) { // add normal rectangle + addRect(r); + return; + } + + qreal x = r.x(); + qreal y = r.y(); + qreal w = r.width(); + qreal h = r.height(); + qreal rxx2 = w*xRadius/100; + qreal ryy2 = h*yRadius/100; + + ensureData(); + detach(); + + bool first = d_func()->elements.size() < 2; + + arcMoveTo(x, y, rxx2, ryy2, 180); + arcTo(x, y, rxx2, ryy2, 180, -90); + arcTo(x+w-rxx2, y, rxx2, ryy2, 90, -90); + arcTo(x+w-rxx2, y+h-ryy2, rxx2, ryy2, 0, -90); + arcTo(x, y+h-ryy2, rxx2, ryy2, 270, -90); + closeSubpath(); + + d_func()->require_moveTo = true; + d_func()->convex = first; +} + +/*! + \fn void QPainterPath::addRoundedRect(qreal x, qreal y, qreal w, qreal h, qreal xRadius, qreal yRadius, Qt::SizeMode mode = Qt::AbsoluteSize); + \since 4.4 + \overload + + Adds the given rectangle \a x, \a y, \a w, \a h with rounded corners to the path. + */ + +/*! + \obsolete + + Adds a rectangle \a r with rounded corners to the path. + + The \a xRnd and \a yRnd arguments specify how rounded the corners + should be. 0 is angled corners, 99 is maximum roundedness. + + \sa addRoundedRect() +*/ +void QPainterPath::addRoundRect(const QRectF &r, int xRnd, int yRnd) +{ + if(xRnd >= 100) // fix ranges + xRnd = 99; + if(yRnd >= 100) + yRnd = 99; + if(xRnd <= 0 || yRnd <= 0) { // add normal rectangle + addRect(r); + return; + } + + QRectF rect = r.normalized(); + + if (rect.isNull()) + return; + + qreal x = rect.x(); + qreal y = rect.y(); + qreal w = rect.width(); + qreal h = rect.height(); + qreal rxx2 = w*xRnd/100; + qreal ryy2 = h*yRnd/100; + + ensureData(); + detach(); + + bool first = d_func()->elements.size() < 2; + + arcMoveTo(x, y, rxx2, ryy2, 180); + arcTo(x, y, rxx2, ryy2, 180, -90); + arcTo(x+w-rxx2, y, rxx2, ryy2, 90, -90); + arcTo(x+w-rxx2, y+h-ryy2, rxx2, ryy2, 0, -90); + arcTo(x, y+h-ryy2, rxx2, ryy2, 270, -90); + closeSubpath(); + + d_func()->require_moveTo = true; + d_func()->convex = first; +} + +/*! + \obsolete + + \fn bool QPainterPath::addRoundRect(const QRectF &rect, int roundness); + \since 4.3 + \overload + + Adds a rounded rectangle, \a rect, to the path. + + The \a roundness argument specifies uniform roundness for the + rectangle. Vertical and horizontal roundness factors will be + adjusted accordingly to act uniformly around both axes. Use this + method if you want a rectangle equally rounded across both the X and + Y axis. + + \sa addRoundedRect() +*/ + +/*! + \obsolete + + \fn void QPainterPath::addRoundRect(qreal x, qreal y, qreal w, qreal h, int xRnd, int yRnd); + \overload + + Adds a rectangle with rounded corners to the path. The rectangle + is constructed from \a x, \a y, and the width and height \a w + and \a h. + + The \a xRnd and \a yRnd arguments specify how rounded the corners + should be. 0 is angled corners, 99 is maximum roundedness. + + \sa addRoundedRect() + */ + +/*! + \obsolete + + \fn bool QPainterPath::addRoundRect(qreal x, qreal y, qreal width, qreal height, int roundness); + \since 4.3 + \overload + + Adds a rounded rectangle to the path, defined by the coordinates \a + x and \a y with the specified \a width and \a height. + + The \a roundness argument specifies uniform roundness for the + rectangle. Vertical and horizontal roundness factors will be + adjusted accordingly to act uniformly around both axes. Use this + method if you want a rectangle equally rounded across both the X and + Y axis. + + \sa addRoundedRect() +*/ + +/*! + \since 4.3 + + Returns a path which is the union of this path's fill area and \a p's fill area. + + Set operations on paths will treat the paths as areas. Non-closed + paths will be treated as implicitly closed. + Bezier curves may be flattened to line segments due to numerical instability of + doing bezier curve intersections. + + \sa intersected(), subtracted() +*/ +QPainterPath QPainterPath::united(const QPainterPath &p) const +{ + if (isEmpty() || p.isEmpty()) + return isEmpty() ? p : *this; + QPathClipper clipper(*this, p); + return clipper.clip(QPathClipper::BoolOr); +} + +/*! + \since 4.3 + + Returns a path which is the intersection of this path's fill area and \a p's fill area. + Bezier curves may be flattened to line segments due to numerical instability of + doing bezier curve intersections. +*/ +QPainterPath QPainterPath::intersected(const QPainterPath &p) const +{ + if (isEmpty() || p.isEmpty()) + return QPainterPath(); + QPathClipper clipper(*this, p); + return clipper.clip(QPathClipper::BoolAnd); +} + +/*! + \since 4.3 + + Returns a path which is \a p's fill area subtracted from this path's fill area. + + Set operations on paths will treat the paths as areas. Non-closed + paths will be treated as implicitly closed. + Bezier curves may be flattened to line segments due to numerical instability of + doing bezier curve intersections. +*/ +QPainterPath QPainterPath::subtracted(const QPainterPath &p) const +{ + if (isEmpty() || p.isEmpty()) + return *this; + QPathClipper clipper(*this, p); + return clipper.clip(QPathClipper::BoolSub); +} + +/*! + \since 4.3 + \obsolete + + Use subtracted() instead. + + \sa subtracted() +*/ +QPainterPath QPainterPath::subtractedInverted(const QPainterPath &p) const +{ + return p.subtracted(*this); +} + +/*! + \since 4.4 + + Returns a simplified version of this path. This implies merging all subpaths that intersect, + and returning a path containing no intersecting edges. Consecutive parallel lines will also + be merged. The simplified path will always use the default fill rule, Qt::OddEvenFill. + Bezier curves may be flattened to line segments due to numerical instability of + doing bezier curve intersections. +*/ +QPainterPath QPainterPath::simplified() const +{ + if(isEmpty()) + return *this; + QPathClipper clipper(*this, QPainterPath()); + return clipper.clip(QPathClipper::Simplify); +} + +/*! + \since 4.3 + + Returns true if the current path intersects at any point the given path \a p. + Also returns true if the current path contains or is contained by any part of \a p. + + Set operations on paths will treat the paths as areas. Non-closed + paths will be treated as implicitly closed. + + \sa contains() + */ +bool QPainterPath::intersects(const QPainterPath &p) const +{ + if (p.elementCount() == 1) + return contains(p.elementAt(0)); + if (isEmpty() || p.isEmpty()) + return false; + QPathClipper clipper(*this, p); + return clipper.intersect(); +} + +/*! + \since 4.3 + + Returns true if the given path \a p is contained within + the current path. Returns false if any edges of the current path and + \a p intersect. + + Set operations on paths will treat the paths as areas. Non-closed + paths will be treated as implicitly closed. + + \sa intersects() + */ +bool QPainterPath::contains(const QPainterPath &p) const +{ + if (p.elementCount() == 1) + return contains(p.elementAt(0)); + if (isEmpty() || p.isEmpty()) + return false; + QPathClipper clipper(*this, p); + return clipper.contains(); +} + +void QPainterPath::setDirty(bool dirty) +{ + d_func()->dirtyBounds = dirty; + d_func()->dirtyControlBounds = dirty; + delete d_func()->pathConverter; + d_func()->pathConverter = 0; + d_func()->convex = false; +} + +void QPainterPath::computeBoundingRect() const +{ + QPainterPathData *d = d_func(); + d->dirtyBounds = false; + if (!d_ptr) { + d->bounds = QRect(); + return; + } + + qreal minx, maxx, miny, maxy; + minx = maxx = d->elements.at(0).x; + miny = maxy = d->elements.at(0).y; + for (int i=1; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + + switch (e.type) { + case MoveToElement: + case LineToElement: + if (e.x > maxx) maxx = e.x; + else if (e.x < minx) minx = e.x; + if (e.y > maxy) maxy = e.y; + else if (e.y < miny) miny = e.y; + break; + case CurveToElement: + { + QBezier b = QBezier::fromPoints(d->elements.at(i-1), + e, + d->elements.at(i+1), + d->elements.at(i+2)); + QRectF r = qt_painterpath_bezier_extrema(b); + qreal right = r.right(); + qreal bottom = r.bottom(); + if (r.x() < minx) minx = r.x(); + if (right > maxx) maxx = right; + if (r.y() < miny) miny = r.y(); + if (bottom > maxy) maxy = bottom; + i += 2; + } + break; + default: + break; + } + } + d->bounds = QRectF(minx, miny, maxx - minx, maxy - miny); +} + + +void QPainterPath::computeControlPointRect() const +{ + QPainterPathData *d = d_func(); + d->dirtyControlBounds = false; + if (!d_ptr) { + d->controlBounds = QRect(); + return; + } + + qreal minx, maxx, miny, maxy; + minx = maxx = d->elements.at(0).x; + miny = maxy = d->elements.at(0).y; + for (int i=1; i<d->elements.size(); ++i) { + const Element &e = d->elements.at(i); + if (e.x > maxx) maxx = e.x; + else if (e.x < minx) minx = e.x; + if (e.y > maxy) maxy = e.y; + else if (e.y < miny) miny = e.y; + } + d->controlBounds = QRectF(minx, miny, maxx - minx, maxy - miny); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug s, const QPainterPath &p) +{ + s.nospace() << "QPainterPath: Element count=" << p.elementCount() << endl; + const char *types[] = {"MoveTo", "LineTo", "CurveTo", "CurveToData"}; + for (int i=0; i<p.elementCount(); ++i) { + s.nospace() << " -> " << types[p.elementAt(i).type] << "(x=" << p.elementAt(i).x << ", y=" << p.elementAt(i).y << ')' << endl; + + } + return s; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpainterpath.h b/src/gui/painting/qpainterpath.h new file mode 100644 index 0000000000..3c23282d8e --- /dev/null +++ b/src/gui/painting/qpainterpath.h @@ -0,0 +1,435 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTERPATH_H +#define QPAINTERPATH_H + +#include <QtGui/qmatrix.h> +#include <QtCore/qglobal.h> +#include <QtCore/qrect.h> +#include <QtCore/qline.h> +#include <QtCore/qvector.h> +#include <QtCore/qscopedpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFont; +class QPainterPathPrivate; +struct QPainterPathPrivateDeleter; +class QPainterPathData; +class QPainterPathStrokerPrivate; +class QPolygonF; +class QRegion; +class QVectorPath; + +class Q_GUI_EXPORT QPainterPath +{ +public: + enum ElementType { + MoveToElement, + LineToElement, + CurveToElement, + CurveToDataElement + }; + + class Element { + public: + qreal x; + qreal y; + ElementType type; + + bool isMoveTo() const { return type == MoveToElement; } + bool isLineTo() const { return type == LineToElement; } + bool isCurveTo() const { return type == CurveToElement; } + + operator QPointF () const { return QPointF(x, y); } + + bool operator==(const Element &e) const { return qFuzzyCompare(x, e.x) + && qFuzzyCompare(y, e.y) && type == e.type; } + inline bool operator!=(const Element &e) const { return !operator==(e); } + }; + + QPainterPath(); + explicit QPainterPath(const QPointF &startPoint); + QPainterPath(const QPainterPath &other); + QPainterPath &operator=(const QPainterPath &other); +#ifdef Q_COMPILER_RVALUE_REFS + inline QPainterPath &operator=(QPainterPath &&other) + { qSwap(d_ptr, other.d_ptr); return *this; } +#endif + ~QPainterPath(); + inline void swap(QPainterPath &other) { d_ptr.swap(other.d_ptr); } + + void closeSubpath(); + + void moveTo(const QPointF &p); + inline void moveTo(qreal x, qreal y); + + void lineTo(const QPointF &p); + inline void lineTo(qreal x, qreal y); + + void arcMoveTo(const QRectF &rect, qreal angle); + inline void arcMoveTo(qreal x, qreal y, qreal w, qreal h, qreal angle); + + void arcTo(const QRectF &rect, qreal startAngle, qreal arcLength); + inline void arcTo(qreal x, qreal y, qreal w, qreal h, qreal startAngle, qreal arcLength); + + void cubicTo(const QPointF &ctrlPt1, const QPointF &ctrlPt2, const QPointF &endPt); + inline void cubicTo(qreal ctrlPt1x, qreal ctrlPt1y, qreal ctrlPt2x, qreal ctrlPt2y, + qreal endPtx, qreal endPty); + void quadTo(const QPointF &ctrlPt, const QPointF &endPt); + inline void quadTo(qreal ctrlPtx, qreal ctrlPty, qreal endPtx, qreal endPty); + + QPointF currentPosition() const; + + void addRect(const QRectF &rect); + inline void addRect(qreal x, qreal y, qreal w, qreal h); + void addEllipse(const QRectF &rect); + inline void addEllipse(qreal x, qreal y, qreal w, qreal h); + inline void addEllipse(const QPointF ¢er, qreal rx, qreal ry); + void addPolygon(const QPolygonF &polygon); + void addText(const QPointF &point, const QFont &f, const QString &text); + inline void addText(qreal x, qreal y, const QFont &f, const QString &text); + void addPath(const QPainterPath &path); + void addRegion(const QRegion ®ion); + + void addRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + inline void addRoundedRect(qreal x, qreal y, qreal w, qreal h, + qreal xRadius, qreal yRadius, + Qt::SizeMode mode = Qt::AbsoluteSize); + + void addRoundRect(const QRectF &rect, int xRnd, int yRnd); + inline void addRoundRect(qreal x, qreal y, qreal w, qreal h, + int xRnd, int yRnd); + inline void addRoundRect(const QRectF &rect, int roundness); + inline void addRoundRect(qreal x, qreal y, qreal w, qreal h, + int roundness); + + void connectPath(const QPainterPath &path); + + bool contains(const QPointF &pt) const; + bool contains(const QRectF &rect) const; + bool intersects(const QRectF &rect) const; + + void translate(qreal dx, qreal dy); + inline void translate(const QPointF &offset); + + QPainterPath translated(qreal dx, qreal dy) const; + inline QPainterPath translated(const QPointF &offset) const; + + QRectF boundingRect() const; + QRectF controlPointRect() const; + + Qt::FillRule fillRule() const; + void setFillRule(Qt::FillRule fillRule); + + inline bool isEmpty() const; + + QPainterPath toReversed() const; + QList<QPolygonF> toSubpathPolygons(const QMatrix &matrix = QMatrix()) const; + QList<QPolygonF> toFillPolygons(const QMatrix &matrix = QMatrix()) const; + QPolygonF toFillPolygon(const QMatrix &matrix = QMatrix()) const; + QList<QPolygonF> toSubpathPolygons(const QTransform &matrix) const; + QList<QPolygonF> toFillPolygons(const QTransform &matrix) const; + QPolygonF toFillPolygon(const QTransform &matrix) const; + + inline int elementCount() const; + inline const QPainterPath::Element &elementAt(int i) const; + inline void setElementPositionAt(int i, qreal x, qreal y); + + qreal length() const; + qreal percentAtLength(qreal t) const; + QPointF pointAtPercent(qreal t) const; + qreal angleAtPercent(qreal t) const; + qreal slopeAtPercent(qreal t) const; + + bool intersects(const QPainterPath &p) const; + bool contains(const QPainterPath &p) const; + QPainterPath united(const QPainterPath &r) const; + QPainterPath intersected(const QPainterPath &r) const; + QPainterPath subtracted(const QPainterPath &r) const; + QPainterPath subtractedInverted(const QPainterPath &r) const; + + QPainterPath simplified() const; + + bool operator==(const QPainterPath &other) const; + bool operator!=(const QPainterPath &other) const; + + QPainterPath operator&(const QPainterPath &other) const; + QPainterPath operator|(const QPainterPath &other) const; + QPainterPath operator+(const QPainterPath &other) const; + QPainterPath operator-(const QPainterPath &other) const; + QPainterPath &operator&=(const QPainterPath &other); + QPainterPath &operator|=(const QPainterPath &other); + QPainterPath &operator+=(const QPainterPath &other); + QPainterPath &operator-=(const QPainterPath &other); + +private: + QScopedPointer<QPainterPathPrivate, QPainterPathPrivateDeleter> d_ptr; + + inline void ensureData() { if (!d_ptr) ensureData_helper(); } + void ensureData_helper(); + inline void detach(); + void detach_helper(); + void setDirty(bool); + void computeBoundingRect() const; + void computeControlPointRect() const; + + QPainterPathData *d_func() const { return reinterpret_cast<QPainterPathData *>(d_ptr.data()); } + + friend class QPainterPathData; + friend class QPainterPathStroker; + friend class QPainterPathStrokerPrivate; + friend class QMatrix; + friend class QTransform; + friend class QVectorPath; + friend Q_GUI_EXPORT const QVectorPath &qtVectorPathForPath(const QPainterPath &); + +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPainterPath &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPainterPath &); +#endif +}; + +class QPainterPathPrivate +{ +public: + friend class QPainterPath; + friend class QPainterPathData; + friend class QPainterPathStroker; + friend class QPainterPathStrokerPrivate; + friend class QMatrix; + friend class QTransform; + friend class QVectorPath; + friend struct QPainterPathPrivateDeleter; +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPainterPath &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPainterPath &); +#endif +private: + QAtomicInt ref; + QVector<QPainterPath::Element> elements; +}; + +Q_DECLARE_TYPEINFO(QPainterPath::Element, Q_PRIMITIVE_TYPE); + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPainterPath &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPainterPath &); +#endif + +class Q_GUI_EXPORT QPainterPathStroker +{ + Q_DECLARE_PRIVATE(QPainterPathStroker) +public: + QPainterPathStroker(); + ~QPainterPathStroker(); + + void setWidth(qreal width); + qreal width() const; + + void setCapStyle(Qt::PenCapStyle style); + Qt::PenCapStyle capStyle() const; + + void setJoinStyle(Qt::PenJoinStyle style); + Qt::PenJoinStyle joinStyle() const; + + void setMiterLimit(qreal length); + qreal miterLimit() const; + + void setCurveThreshold(qreal threshold); + qreal curveThreshold() const; + + void setDashPattern(Qt::PenStyle); + void setDashPattern(const QVector<qreal> &dashPattern); + QVector<qreal> dashPattern() const; + + void setDashOffset(qreal offset); + qreal dashOffset() const; + + QPainterPath createStroke(const QPainterPath &path) const; + +private: + Q_DISABLE_COPY(QPainterPathStroker) + + friend class QX11PaintEngine; + + QScopedPointer<QPainterPathStrokerPrivate> d_ptr; +}; + +inline void QPainterPath::moveTo(qreal x, qreal y) +{ + moveTo(QPointF(x, y)); +} + +inline void QPainterPath::lineTo(qreal x, qreal y) +{ + lineTo(QPointF(x, y)); +} + +inline void QPainterPath::arcTo(qreal x, qreal y, qreal w, qreal h, qreal startAngle, qreal arcLength) +{ + arcTo(QRectF(x, y, w, h), startAngle, arcLength); +} + +inline void QPainterPath::arcMoveTo(qreal x, qreal y, qreal w, qreal h, qreal angle) +{ + arcMoveTo(QRectF(x, y, w, h), angle); +} + +inline void QPainterPath::cubicTo(qreal ctrlPt1x, qreal ctrlPt1y, qreal ctrlPt2x, qreal ctrlPt2y, + qreal endPtx, qreal endPty) +{ + cubicTo(QPointF(ctrlPt1x, ctrlPt1y), QPointF(ctrlPt2x, ctrlPt2y), + QPointF(endPtx, endPty)); +} + +inline void QPainterPath::quadTo(qreal ctrlPtx, qreal ctrlPty, qreal endPtx, qreal endPty) +{ + quadTo(QPointF(ctrlPtx, ctrlPty), QPointF(endPtx, endPty)); +} + +inline void QPainterPath::addEllipse(qreal x, qreal y, qreal w, qreal h) +{ + addEllipse(QRectF(x, y, w, h)); +} + +inline void QPainterPath::addEllipse(const QPointF ¢er, qreal rx, qreal ry) +{ + addEllipse(QRectF(center.x() - rx, center.y() - ry, 2 * rx, 2 * ry)); +} + +inline void QPainterPath::addRect(qreal x, qreal y, qreal w, qreal h) +{ + addRect(QRectF(x, y, w, h)); +} + +inline void QPainterPath::addRoundedRect(qreal x, qreal y, qreal w, qreal h, + qreal xRadius, qreal yRadius, + Qt::SizeMode mode) +{ + addRoundedRect(QRectF(x, y, w, h), xRadius, yRadius, mode); +} + +inline void QPainterPath::addRoundRect(qreal x, qreal y, qreal w, qreal h, + int xRnd, int yRnd) +{ + addRoundRect(QRectF(x, y, w, h), xRnd, yRnd); +} + +inline void QPainterPath::addRoundRect(const QRectF &rect, + int roundness) +{ + int xRnd = roundness; + int yRnd = roundness; + if (rect.width() > rect.height()) + xRnd = int(roundness * rect.height()/rect.width()); + else + yRnd = int(roundness * rect.width()/rect.height()); + addRoundRect(rect, xRnd, yRnd); +} + +inline void QPainterPath::addRoundRect(qreal x, qreal y, qreal w, qreal h, + int roundness) +{ + addRoundRect(QRectF(x, y, w, h), roundness); +} + +inline void QPainterPath::addText(qreal x, qreal y, const QFont &f, const QString &text) +{ + addText(QPointF(x, y), f, text); +} + +inline void QPainterPath::translate(const QPointF &offset) +{ translate(offset.x(), offset.y()); } + +inline QPainterPath QPainterPath::translated(const QPointF &offset) const +{ return translated(offset.x(), offset.y()); } + +inline bool QPainterPath::isEmpty() const +{ + return !d_ptr || (d_ptr->elements.size() == 1 && d_ptr->elements.first().type == MoveToElement); +} + +inline int QPainterPath::elementCount() const +{ + return d_ptr ? d_ptr->elements.size() : 0; +} + +inline const QPainterPath::Element &QPainterPath::elementAt(int i) const +{ + Q_ASSERT(d_ptr); + Q_ASSERT(i >= 0 && i < elementCount()); + return d_ptr->elements.at(i); +} + +inline void QPainterPath::setElementPositionAt(int i, qreal x, qreal y) +{ + Q_ASSERT(d_ptr); + Q_ASSERT(i >= 0 && i < elementCount()); + detach(); + QPainterPath::Element &e = d_ptr->elements[i]; + e.x = x; + e.y = y; +} + + +inline void QPainterPath::detach() +{ + if (d_ptr->ref != 1) + detach_helper(); + setDirty(true); +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QPainterPath &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPAINTERPATH_H diff --git a/src/gui/painting/qpainterpath_p.h b/src/gui/painting/qpainterpath_p.h new file mode 100644 index 0000000000..8e05569893 --- /dev/null +++ b/src/gui/painting/qpainterpath_p.h @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTERPATH_P_H +#define QPAINTERPATH_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qpainterpath.h" +#include "QtGui/qregion.h" +#include "QtCore/qlist.h" +#include "QtCore/qvarlengtharray.h" + +#include <qdebug.h> + +#include <private/qvectorpath_p.h> +#include <private/qstroker_p.h> + +QT_BEGIN_NAMESPACE + +class QPainterPathStrokerPrivate +{ +public: + QPainterPathStrokerPrivate(); + + QStroker stroker; + QVector<qfixed> dashPattern; + qreal dashOffset; +}; + +class QPolygonF; +class QVectorPathConverter; + +class QVectorPathConverter +{ +public: + QVectorPathConverter(const QVector<QPainterPath::Element> &path, uint fillRule, bool convex) + : pathData(path, fillRule, convex), + path(pathData.points.data(), path.size(), + pathData.elements.data(), pathData.flags) {} + + const QVectorPath &vectorPath() { + return path; + } + + struct QVectorPathData { + QVectorPathData(const QVector<QPainterPath::Element> &path, uint fillRule, bool convex) + : elements(path.size()), + points(path.size() * 2), + flags(0) + { + int ptsPos = 0; + bool isLines = true; + for (int i=0; i<path.size(); ++i) { + const QPainterPath::Element &e = path.at(i); + elements[i] = e.type; + points[ptsPos++] = e.x; + points[ptsPos++] = e.y; + if (e.type == QPainterPath::CurveToElement) + flags |= QVectorPath::CurvedShapeMask; + + // This is to check if the path contains only alternating lineTo/moveTo, + // in which case we can set the LinesHint in the path. MoveTo is 0 and + // LineTo is 1 so the i%2 gets us what we want cheaply. + isLines = isLines && e.type == (QPainterPath::ElementType) (i%2); + } + + if (fillRule == Qt::WindingFill) + flags |= QVectorPath::WindingFill; + else + flags |= QVectorPath::OddEvenFill; + + if (isLines) + flags |= QVectorPath::LinesShapeMask; + else { + flags |= QVectorPath::AreaShapeMask; + if (!convex) + flags |= QVectorPath::NonConvexShapeMask; + } + + } + QVarLengthArray<QPainterPath::ElementType> elements; + QVarLengthArray<qreal> points; + uint flags; + }; + + QVectorPathData pathData; + QVectorPath path; + +private: + Q_DISABLE_COPY(QVectorPathConverter) +}; + +class QPainterPathData : public QPainterPathPrivate +{ +public: + QPainterPathData() : + cStart(0), + fillRule(Qt::OddEvenFill), + dirtyBounds(false), + dirtyControlBounds(false), + pathConverter(0) + { + ref = 1; + require_moveTo = false; + convex = false; + } + + QPainterPathData(const QPainterPathData &other) : + QPainterPathPrivate(), cStart(other.cStart), fillRule(other.fillRule), + bounds(other.bounds), + controlBounds(other.controlBounds), + dirtyBounds(other.dirtyBounds), + dirtyControlBounds(other.dirtyControlBounds), + convex(other.convex), + pathConverter(0) + { + ref = 1; + require_moveTo = false; + elements = other.elements; + } + + ~QPainterPathData() { + delete pathConverter; + } + + inline bool isClosed() const; + inline void close(); + inline void maybeMoveTo(); + + const QVectorPath &vectorPath() { + if (!pathConverter) + pathConverter = new QVectorPathConverter(elements, fillRule, convex); + return pathConverter->path; + } + + int cStart; + Qt::FillRule fillRule; + + QRectF bounds; + QRectF controlBounds; + + uint require_moveTo : 1; + uint dirtyBounds : 1; + uint dirtyControlBounds : 1; + uint convex : 1; + + QVectorPathConverter *pathConverter; +}; + + +inline const QPainterPath QVectorPath::convertToPainterPath() const +{ + QPainterPath path; + path.ensureData(); + QPainterPathData *data = path.d_func(); + data->elements.reserve(m_count); + int index = 0; + data->elements[0].x = m_points[index++]; + data->elements[0].y = m_points[index++]; + + if (m_elements) { + data->elements[0].type = m_elements[0]; + for (int i=1; i<m_count; ++i) { + QPainterPath::Element element; + element.x = m_points[index++]; + element.y = m_points[index++]; + element.type = m_elements[i]; + data->elements << element; + } + } else { + data->elements[0].type = QPainterPath::MoveToElement; + for (int i=1; i<m_count; ++i) { + QPainterPath::Element element; + element.x = m_points[index++]; + element.y = m_points[index++]; + element.type = QPainterPath::LineToElement; + data->elements << element; + } + } + + if (m_hints & OddEvenFill) + data->fillRule = Qt::OddEvenFill; + else + data->fillRule = Qt::WindingFill; + return path; +} + +void Q_GUI_EXPORT qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length, + QPointF* startPoint, QPointF *endPoint); + +inline bool QPainterPathData::isClosed() const +{ + const QPainterPath::Element &first = elements.at(cStart); + const QPainterPath::Element &last = elements.last(); + return first.x == last.x && first.y == last.y; +} + +inline void QPainterPathData::close() +{ + Q_ASSERT(ref == 1); + require_moveTo = true; + const QPainterPath::Element &first = elements.at(cStart); + QPainterPath::Element &last = elements.last(); + if (first.x != last.x || first.y != last.y) { + if (qFuzzyCompare(first.x, last.x) && qFuzzyCompare(first.y, last.y)) { + last.x = first.x; + last.y = first.y; + } else { + QPainterPath::Element e = { first.x, first.y, QPainterPath::LineToElement }; + elements << e; + } + } +} + +inline void QPainterPathData::maybeMoveTo() +{ + if (require_moveTo) { + QPainterPath::Element e = elements.last(); + e.type = QPainterPath::MoveToElement; + elements.append(e); + require_moveTo = false; + } +} + +#define KAPPA 0.5522847498 + + +QT_END_NAMESPACE + +#endif // QPAINTERPATH_P_H diff --git a/src/gui/painting/qpathclipper.cpp b/src/gui/painting/qpathclipper.cpp new file mode 100644 index 0000000000..d18fba2e7c --- /dev/null +++ b/src/gui/painting/qpathclipper.cpp @@ -0,0 +1,2149 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpathclipper_p.h" + +#include <private/qbezier_p.h> +#include <private/qdatabuffer_p.h> +#include <private/qnumeric_p.h> +#include <qmath.h> + +/** + The algorithm is as follows: + + 1. Find all intersections between the two paths (including self-intersections), + and build a winged edge structure of non-intersecting parts. + 2. While there are more unhandled edges: + 3. Pick a y-coordinate from an unhandled edge. + 4. Intersect the horizontal line at y-coordinate with all edges. + 5. Traverse intersections left to right deciding whether each subpath should be added or not. + 6. If the subpath should be added, traverse the winged-edge structure and add the edges to + a separate winged edge structure. + 7. Mark all edges in subpaths crossing the horizontal line as handled. + 8. (Optional) Simplify the resulting winged edge structure by merging shared edges. + 9. Convert the resulting winged edge structure to a painter path. + */ + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +static inline bool fuzzyIsNull(qreal d) +{ + if (sizeof(qreal) == sizeof(double)) + return qAbs(d) <= 1e-12; + else + return qAbs(d) <= 1e-5f; +} + +static inline bool comparePoints(const QPointF &a, const QPointF &b) +{ + return fuzzyIsNull(a.x() - b.x()) + && fuzzyIsNull(a.y() - b.y()); +} + +//#define QDEBUG_CLIPPER +static qreal dot(const QPointF &a, const QPointF &b) +{ + return a.x() * b.x() + a.y() * b.y(); +} + +static void normalize(double &x, double &y) +{ + double reciprocal = 1 / qSqrt(x * x + y * y); + x *= reciprocal; + y *= reciprocal; +} + +struct QIntersection +{ + qreal alphaA; + qreal alphaB; + + QPointF pos; +}; + +class QIntersectionFinder +{ +public: + void produceIntersections(QPathSegments &segments); + bool hasIntersections(const QPathSegments &a, const QPathSegments &b) const; + +private: + bool linesIntersect(const QLineF &a, const QLineF &b) const; +}; + +bool QIntersectionFinder::linesIntersect(const QLineF &a, const QLineF &b) const +{ + const QPointF p1 = a.p1(); + const QPointF p2 = a.p2(); + + const QPointF q1 = b.p1(); + const QPointF q2 = b.p2(); + + if (comparePoints(p1, p2) || comparePoints(q1, q2)) + return false; + + const bool p1_equals_q1 = comparePoints(p1, q1); + const bool p2_equals_q2 = comparePoints(p2, q2); + + if (p1_equals_q1 && p2_equals_q2) + return true; + + const bool p1_equals_q2 = comparePoints(p1, q2); + const bool p2_equals_q1 = comparePoints(p2, q1); + + if (p1_equals_q2 && p2_equals_q1) + return true; + + const QPointF pDelta = p2 - p1; + const QPointF qDelta = q2 - q1; + + const qreal par = pDelta.x() * qDelta.y() - pDelta.y() * qDelta.x(); + + if (qFuzzyIsNull(par)) { + const QPointF normal(-pDelta.y(), pDelta.x()); + + // coinciding? + if (qFuzzyIsNull(dot(normal, q1 - p1))) { + const qreal dp = dot(pDelta, pDelta); + + const qreal tq1 = dot(pDelta, q1 - p1); + const qreal tq2 = dot(pDelta, q2 - p1); + + if ((tq1 > 0 && tq1 < dp) || (tq2 > 0 && tq2 < dp)) + return true; + + const qreal dq = dot(qDelta, qDelta); + + const qreal tp1 = dot(qDelta, p1 - q1); + const qreal tp2 = dot(qDelta, p2 - q1); + + if ((tp1 > 0 && tp1 < dq) || (tp2 > 0 && tp2 < dq)) + return true; + } + + return false; + } + + const qreal invPar = 1 / par; + + const qreal tp = (qDelta.y() * (q1.x() - p1.x()) - + qDelta.x() * (q1.y() - p1.y())) * invPar; + + if (tp < 0 || tp > 1) + return false; + + const qreal tq = (pDelta.y() * (q1.x() - p1.x()) - + pDelta.x() * (q1.y() - p1.y())) * invPar; + + return tq >= 0 && tq <= 1; +} + +bool QIntersectionFinder::hasIntersections(const QPathSegments &a, const QPathSegments &b) const +{ + if (a.segments() == 0 || b.segments() == 0) + return false; + + const QRectF &rb0 = b.elementBounds(0); + + qreal minX = rb0.left(); + qreal minY = rb0.top(); + qreal maxX = rb0.right(); + qreal maxY = rb0.bottom(); + + for (int i = 1; i < b.segments(); ++i) { + const QRectF &r = b.elementBounds(i); + minX = qMin(minX, r.left()); + minY = qMin(minY, r.top()); + maxX = qMax(maxX, r.right()); + maxY = qMax(maxY, r.bottom()); + } + + QRectF rb(minX, minY, maxX - minX, maxY - minY); + + for (int i = 0; i < a.segments(); ++i) { + const QRectF &r1 = a.elementBounds(i); + + if (r1.left() > rb.right() || rb.left() > r1.right()) + continue; + if (r1.top() > rb.bottom() || rb.top() > r1.bottom()) + continue; + + for (int j = 0; j < b.segments(); ++j) { + const QRectF &r2 = b.elementBounds(j); + + if (r1.left() > r2.right() || r2.left() > r1.right()) + continue; + if (r1.top() > r2.bottom() || r2.top() > r1.bottom()) + continue; + + if (linesIntersect(a.lineAt(i), b.lineAt(j))) + return true; + } + } + + return false; +} + +namespace { +struct TreeNode +{ + qreal splitLeft; + qreal splitRight; + bool leaf; + + int lowestLeftIndex; + int lowestRightIndex; + + union { + struct { + int first; + int last; + } interval; + struct { + int left; + int right; + } children; + } index; +}; + +struct RectF +{ + qreal x1; + qreal y1; + qreal x2; + qreal y2; +}; + +class SegmentTree +{ +public: + SegmentTree(QPathSegments &segments); + + QRectF boundingRect() const; + + void produceIntersections(int segment); + +private: + TreeNode buildTree(int first, int last, int depth, const RectF &bounds); + + void produceIntersectionsLeaf(const TreeNode &node, int segment); + void produceIntersections(const TreeNode &node, int segment, const RectF &segmentBounds, const RectF &nodeBounds, int axis); + void intersectLines(const QLineF &a, const QLineF &b, QDataBuffer<QIntersection> &intersections); + + QPathSegments &m_segments; + QVector<int> m_index; + + RectF m_bounds; + + QVector<TreeNode> m_tree; + QDataBuffer<QIntersection> m_intersections; +}; + +SegmentTree::SegmentTree(QPathSegments &segments) + : m_segments(segments), + m_intersections(0) +{ + m_bounds.x1 = qt_inf(); + m_bounds.y1 = qt_inf(); + m_bounds.x2 = -qt_inf(); + m_bounds.y2 = -qt_inf(); + + m_index.resize(m_segments.segments()); + + for (int i = 0; i < m_index.size(); ++i) { + m_index[i] = i; + + const QRectF &segmentBounds = m_segments.elementBounds(i); + + if (segmentBounds.left() < m_bounds.x1) + m_bounds.x1 = segmentBounds.left(); + if (segmentBounds.top() < m_bounds.y1) + m_bounds.y1 = segmentBounds.top(); + if (segmentBounds.right() > m_bounds.x2) + m_bounds.x2 = segmentBounds.right(); + if (segmentBounds.bottom() > m_bounds.y2) + m_bounds.y2 = segmentBounds.bottom(); + } + + m_tree.resize(1); + + TreeNode root = buildTree(0, m_index.size(), 0, m_bounds); + m_tree[0] = root; +} + +QRectF SegmentTree::boundingRect() const +{ + return QRectF(QPointF(m_bounds.x1, m_bounds.y1), + QPointF(m_bounds.x2, m_bounds.y2)); +} + +static inline qreal coordinate(const QPointF &pos, int axis) +{ + return axis == 0 ? pos.x() : pos.y(); +} + +TreeNode SegmentTree::buildTree(int first, int last, int depth, const RectF &bounds) +{ + if (depth >= 24 || (last - first) <= 10) { + TreeNode node; + node.leaf = true; + node.index.interval.first = first; + node.index.interval.last = last; + + return node; + } + + int splitAxis = (depth & 1); + + TreeNode node; + node.leaf = false; + + qreal split = 0.5f * ((&bounds.x1)[splitAxis] + (&bounds.x2)[splitAxis]); + + node.splitLeft = (&bounds.x1)[splitAxis]; + node.splitRight = (&bounds.x2)[splitAxis]; + + node.lowestLeftIndex = INT_MAX; + node.lowestRightIndex = INT_MAX; + + const int treeSize = m_tree.size(); + + node.index.children.left = treeSize; + node.index.children.right = treeSize + 1; + + m_tree.resize(treeSize + 2); + + int l = first; + int r = last - 1; + + // partition into left and right sets + while (l <= r) { + const int index = m_index.at(l); + const QRectF &segmentBounds = m_segments.elementBounds(index); + + qreal lowCoordinate = coordinate(segmentBounds.topLeft(), splitAxis); + + if (coordinate(segmentBounds.center(), splitAxis) < split) { + qreal highCoordinate = coordinate(segmentBounds.bottomRight(), splitAxis); + if (highCoordinate > node.splitLeft) + node.splitLeft = highCoordinate; + if (index < node.lowestLeftIndex) + node.lowestLeftIndex = index; + ++l; + } else { + if (lowCoordinate < node.splitRight) + node.splitRight = lowCoordinate; + if (index < node.lowestRightIndex) + node.lowestRightIndex = index; + qSwap(m_index[l], m_index[r]); + --r; + } + } + + RectF lbounds = bounds; + (&lbounds.x2)[splitAxis] = node.splitLeft; + + RectF rbounds = bounds; + (&rbounds.x1)[splitAxis] = node.splitRight; + + TreeNode left = buildTree(first, l, depth + 1, lbounds); + m_tree[node.index.children.left] = left; + + TreeNode right = buildTree(l, last, depth + 1, rbounds); + m_tree[node.index.children.right] = right; + + return node; +} + +void SegmentTree::intersectLines(const QLineF &a, const QLineF &b, QDataBuffer<QIntersection> &intersections) +{ + const QPointF p1 = a.p1(); + const QPointF p2 = a.p2(); + + const QPointF q1 = b.p1(); + const QPointF q2 = b.p2(); + + if (comparePoints(p1, p2) || comparePoints(q1, q2)) + return; + + const bool p1_equals_q1 = comparePoints(p1, q1); + const bool p2_equals_q2 = comparePoints(p2, q2); + + if (p1_equals_q1 && p2_equals_q2) + return; + + const bool p1_equals_q2 = comparePoints(p1, q2); + const bool p2_equals_q1 = comparePoints(p2, q1); + + if (p1_equals_q2 && p2_equals_q1) + return; + + const QPointF pDelta = p2 - p1; + const QPointF qDelta = q2 - q1; + + const qreal par = pDelta.x() * qDelta.y() - pDelta.y() * qDelta.x(); + + if (qFuzzyIsNull(par)) { + const QPointF normal(-pDelta.y(), pDelta.x()); + + // coinciding? + if (qFuzzyIsNull(dot(normal, q1 - p1))) { + const qreal invDp = 1 / dot(pDelta, pDelta); + + const qreal tq1 = dot(pDelta, q1 - p1) * invDp; + const qreal tq2 = dot(pDelta, q2 - p1) * invDp; + + if (tq1 > 0 && tq1 < 1) { + QIntersection intersection; + intersection.alphaA = tq1; + intersection.alphaB = 0; + intersection.pos = q1; + intersections.add(intersection); + } + + if (tq2 > 0 && tq2 < 1) { + QIntersection intersection; + intersection.alphaA = tq2; + intersection.alphaB = 1; + intersection.pos = q2; + intersections.add(intersection); + } + + const qreal invDq = 1 / dot(qDelta, qDelta); + + const qreal tp1 = dot(qDelta, p1 - q1) * invDq; + const qreal tp2 = dot(qDelta, p2 - q1) * invDq; + + if (tp1 > 0 && tp1 < 1) { + QIntersection intersection; + intersection.alphaA = 0; + intersection.alphaB = tp1; + intersection.pos = p1; + intersections.add(intersection); + } + + if (tp2 > 0 && tp2 < 1) { + QIntersection intersection; + intersection.alphaA = 1; + intersection.alphaB = tp2; + intersection.pos = p2; + intersections.add(intersection); + } + } + + return; + } + + // if the lines are not parallel and share a common end point, then they + // don't intersect + if (p1_equals_q1 || p1_equals_q2 || p2_equals_q1 || p2_equals_q2) + return; + + + const qreal tp = (qDelta.y() * (q1.x() - p1.x()) - + qDelta.x() * (q1.y() - p1.y())) / par; + const qreal tq = (pDelta.y() * (q1.x() - p1.x()) - + pDelta.x() * (q1.y() - p1.y())) / par; + + if (tp<0 || tp>1 || tq<0 || tq>1) + return; + + const bool p_zero = qFuzzyIsNull(tp); + const bool p_one = qFuzzyIsNull(tp - 1); + + const bool q_zero = qFuzzyIsNull(tq); + const bool q_one = qFuzzyIsNull(tq - 1); + + if ((q_zero || q_one) && (p_zero || p_one)) + return; + + QPointF pt; + if (p_zero) { + pt = p1; + } else if (p_one) { + pt = p2; + } else if (q_zero) { + pt = q1; + } else if (q_one) { + pt = q2; + } else { + pt = q1 + (q2 - q1) * tq; + } + + QIntersection intersection; + intersection.alphaA = tp; + intersection.alphaB = tq; + intersection.pos = pt; + intersections.add(intersection); +} + +void SegmentTree::produceIntersections(int segment) +{ + const QRectF &segmentBounds = m_segments.elementBounds(segment); + + RectF sbounds; + sbounds.x1 = segmentBounds.left(); + sbounds.y1 = segmentBounds.top(); + sbounds.x2 = segmentBounds.right(); + sbounds.y2 = segmentBounds.bottom(); + + produceIntersections(m_tree.at(0), segment, sbounds, m_bounds, 0); +} + +void SegmentTree::produceIntersectionsLeaf(const TreeNode &node, int segment) +{ + const QRectF &r1 = m_segments.elementBounds(segment); + const QLineF lineA = m_segments.lineAt(segment); + + for (int i = node.index.interval.first; i < node.index.interval.last; ++i) { + const int other = m_index.at(i); + if (other >= segment) + continue; + + const QRectF &r2 = m_segments.elementBounds(other); + + if (r1.left() > r2.right() || r2.left() > r1.right()) + continue; + if (r1.top() > r2.bottom() || r2.top() > r1.bottom()) + continue; + + m_intersections.reset(); + + const QLineF lineB = m_segments.lineAt(other); + + intersectLines(lineA, lineB, m_intersections); + + for (int k = 0; k < m_intersections.size(); ++k) { + QPathSegments::Intersection i_isect, j_isect; + i_isect.vertex = j_isect.vertex = m_segments.addPoint(m_intersections.at(k).pos); + + i_isect.t = m_intersections.at(k).alphaA; + j_isect.t = m_intersections.at(k).alphaB; + + i_isect.next = 0; + j_isect.next = 0; + + m_segments.addIntersection(segment, i_isect); + m_segments.addIntersection(other, j_isect); + } + } +} + +void SegmentTree::produceIntersections(const TreeNode &node, int segment, const RectF &segmentBounds, const RectF &nodeBounds, int axis) +{ + if (node.leaf) { + produceIntersectionsLeaf(node, segment); + return; + } + + RectF lbounds = nodeBounds; + (&lbounds.x2)[axis] = node.splitLeft; + + RectF rbounds = nodeBounds; + (&rbounds.x1)[axis] = node.splitRight; + + if (segment > node.lowestLeftIndex && (&segmentBounds.x1)[axis] <= node.splitLeft) + produceIntersections(m_tree.at(node.index.children.left), segment, segmentBounds, lbounds, !axis); + + if (segment > node.lowestRightIndex && (&segmentBounds.x2)[axis] >= node.splitRight) + produceIntersections(m_tree.at(node.index.children.right), segment, segmentBounds, rbounds, !axis); +} + +} + +void QIntersectionFinder::produceIntersections(QPathSegments &segments) +{ + SegmentTree tree(segments); + + for (int i = 0; i < segments.segments(); ++i) + tree.produceIntersections(i); +} + +class QKdPointTree +{ +public: + enum Traversal { + TraverseBoth, + TraverseLeft, + TraverseRight, + TraverseNone + }; + + struct Node { + int point; + int id; + + Node *left; + Node *right; + }; + + QKdPointTree(const QPathSegments &segments) + : m_segments(&segments) + , m_nodes(m_segments->points()) + , m_id(0) + { + m_nodes.resize(m_segments->points()); + + for (int i = 0; i < m_nodes.size(); ++i) { + m_nodes.at(i).point = i; + m_nodes.at(i).id = -1; + } + + m_rootNode = build(0, m_nodes.size()); + } + + int build(int begin, int end, int depth = 0); + + Node *rootNode() + { + return &m_nodes.at(m_rootNode); + } + + inline int nextId() + { + return m_id++; + } + +private: + const QPathSegments *m_segments; + QDataBuffer<Node> m_nodes; + + int m_rootNode; + int m_id; +}; + +template <typename T> +void qTraverseKdPointTree(QKdPointTree::Node &node, T &t, int depth = 0) +{ + QKdPointTree::Traversal status = t(node, depth); + + const bool traverseRight = (status == QKdPointTree::TraverseBoth || status == QKdPointTree::TraverseRight); + const bool traverseLeft = (status == QKdPointTree::TraverseBoth || status == QKdPointTree::TraverseLeft); + + if (traverseLeft && node.left) + QT_PREPEND_NAMESPACE(qTraverseKdPointTree<T>)(*node.left, t, depth + 1); + + if (traverseRight && node.right) + QT_PREPEND_NAMESPACE(qTraverseKdPointTree<T>)(*node.right, t, depth + 1); +} + +static inline qreal component(const QPointF &point, unsigned int i) +{ + Q_ASSERT(i < 2); + const qreal components[] = { point.x(), point.y() }; + return components[i]; +} + +int QKdPointTree::build(int begin, int end, int depth) +{ + Q_ASSERT(end > begin); + + const qreal pivot = component(m_segments->pointAt(m_nodes.at(begin).point), depth & 1); + + int first = begin + 1; + int last = end - 1; + + while (first <= last) { + const qreal value = component(m_segments->pointAt(m_nodes.at(first).point), depth & 1); + + if (value < pivot) + ++first; + else { + qSwap(m_nodes.at(first), m_nodes.at(last)); + --last; + } + } + + qSwap(m_nodes.at(last), m_nodes.at(begin)); + + if (last > begin) + m_nodes.at(last).left = &m_nodes.at(build(begin, last, depth + 1)); + else + m_nodes.at(last).left = 0; + + if (last + 1 < end) + m_nodes.at(last).right = &m_nodes.at(build(last + 1, end, depth + 1)); + else + m_nodes.at(last).right = 0; + + return last; +} + +class QKdPointFinder +{ +public: + QKdPointFinder(int point, const QPathSegments &segments, QKdPointTree &tree) + : m_point(point) + , m_result(-1) + , m_segments(&segments) + , m_tree(&tree) + { + pointComponents[0] = segments.pointAt(point).x(); + pointComponents[1] = segments.pointAt(point).y(); + } + + inline QKdPointTree::Traversal operator()(QKdPointTree::Node &node, int depth) + { + if (m_result != -1) + return QKdPointTree::TraverseNone; + + const QPointF &nodePoint = m_segments->pointAt(node.point); + + const qreal pivotComponents[] = { nodePoint.x(), nodePoint.y() }; + + const qreal pivot = pivotComponents[depth & 1]; + const qreal value = pointComponents[depth & 1]; + + if (fuzzyIsNull(pivot - value)) { + const qreal pivot2 = pivotComponents[(depth + 1) & 1]; + const qreal value2 = pointComponents[(depth + 1) & 1]; + + if (fuzzyIsNull(pivot2 - value2)) { + if (node.id < 0) + node.id = m_tree->nextId(); + + m_result = node.id; + return QKdPointTree::TraverseNone; + } else + return QKdPointTree::TraverseBoth; + } else if (value < pivot) { + return QKdPointTree::TraverseLeft; + } else { + return QKdPointTree::TraverseRight; + } + } + + int result() const + { + return m_result; + } + +private: + int m_point; + qreal pointComponents[2]; + int m_result; + const QPathSegments *m_segments; + QKdPointTree *m_tree; +}; + +// merge all points that are within qFuzzyCompare range of each other +void QPathSegments::mergePoints() +{ + QKdPointTree tree(*this); + + if (tree.rootNode()) { + QDataBuffer<QPointF> mergedPoints(points()); + QDataBuffer<int> pointIndices(points()); + + for (int i = 0; i < points(); ++i) { + QKdPointFinder finder(i, *this, tree); + QT_PREPEND_NAMESPACE(qTraverseKdPointTree<QKdPointFinder>)(*tree.rootNode(), finder); + + Q_ASSERT(finder.result() != -1); + + if (finder.result() >= mergedPoints.size()) + mergedPoints << m_points.at(i); + + pointIndices << finder.result(); + } + + for (int i = 0; i < m_segments.size(); ++i) { + m_segments.at(i).va = pointIndices.at(m_segments.at(i).va); + m_segments.at(i).vb = pointIndices.at(m_segments.at(i).vb); + } + + for (int i = 0; i < m_intersections.size(); ++i) + m_intersections.at(i).vertex = pointIndices.at(m_intersections.at(i).vertex); + + m_points.swap(mergedPoints); + } +} + +void QWingedEdge::intersectAndAdd() +{ + QIntersectionFinder finder; + finder.produceIntersections(m_segments); + + m_segments.mergePoints(); + + for (int i = 0; i < m_segments.points(); ++i) + addVertex(m_segments.pointAt(i)); + + QDataBuffer<QPathSegments::Intersection> intersections(m_segments.segments()); + for (int i = 0; i < m_segments.segments(); ++i) { + intersections.reset(); + + int pathId = m_segments.pathId(i); + + const QPathSegments::Intersection *isect = m_segments.intersectionAt(i); + while (isect) { + intersections << *isect; + + if (isect->next) { + isect += isect->next; + } else { + isect = 0; + } + } + + qSort(intersections.data(), intersections.data() + intersections.size()); + + int first = m_segments.segmentAt(i).va; + int second = m_segments.segmentAt(i).vb; + + int last = first; + for (int j = 0; j < intersections.size(); ++j) { + const QPathSegments::Intersection &isect = intersections.at(j); + + QPathEdge *ep = edge(addEdge(last, isect.vertex)); + + if (ep) { + const int dir = m_segments.pointAt(last).y() < m_segments.pointAt(isect.vertex).y() ? 1 : -1; + if (pathId == 0) + ep->windingA += dir; + else + ep->windingB += dir; + } + + last = isect.vertex; + } + + QPathEdge *ep = edge(addEdge(last, second)); + + if (ep) { + const int dir = m_segments.pointAt(last).y() < m_segments.pointAt(second).y() ? 1 : -1; + if (pathId == 0) + ep->windingA += dir; + else + ep->windingB += dir; + } + } +} + +QWingedEdge::QWingedEdge() : + m_edges(0), + m_vertices(0), + m_segments(0) +{ +} + +QWingedEdge::QWingedEdge(const QPainterPath &subject, const QPainterPath &clip) : + m_edges(subject.elementCount()), + m_vertices(subject.elementCount()), + m_segments(subject.elementCount()) +{ + m_segments.setPath(subject); + m_segments.addPath(clip); + + intersectAndAdd(); +} + +QWingedEdge::TraversalStatus QWingedEdge::next(const QWingedEdge::TraversalStatus &status) const +{ + const QPathEdge *sp = edge(status.edge); + Q_ASSERT(sp); + + TraversalStatus result; + result.edge = sp->next(status.traversal, status.direction); + result.traversal = status.traversal; + result.direction = status.direction; + + const QPathEdge *rp = edge(result.edge); + Q_ASSERT(rp); + + if (sp->vertex(status.direction) == rp->vertex(status.direction)) + result.flip(); + + return result; +} + +static bool isLine(const QBezier &bezier) +{ + const bool equal_1_2 = comparePoints(bezier.pt1(), bezier.pt2()); + const bool equal_2_3 = comparePoints(bezier.pt2(), bezier.pt3()); + const bool equal_3_4 = comparePoints(bezier.pt3(), bezier.pt4()); + + // point? + if (equal_1_2 && equal_2_3 && equal_3_4) + return true; + + if (comparePoints(bezier.pt1(), bezier.pt4())) + return equal_1_2 || equal_3_4; + + return (equal_1_2 && equal_3_4) || (equal_1_2 && equal_2_3) || (equal_2_3 && equal_3_4); +} + +void QPathSegments::setPath(const QPainterPath &path) +{ + m_points.reset(); + m_intersections.reset(); + m_segments.reset(); + + m_pathId = 0; + + addPath(path); +} + +void QPathSegments::addPath(const QPainterPath &path) +{ + int firstSegment = m_segments.size(); + + bool hasMoveTo = false; + int lastMoveTo = 0; + int last = 0; + for (int i = 0; i < path.elementCount(); ++i) { + int current = m_points.size(); + + QPointF currentPoint; + if (path.elementAt(i).type == QPainterPath::CurveToElement) + currentPoint = path.elementAt(i+2); + else + currentPoint = path.elementAt(i); + + if (i > 0 && comparePoints(m_points.at(lastMoveTo), currentPoint)) + current = lastMoveTo; + else + m_points << currentPoint; + + switch (path.elementAt(i).type) { + case QPainterPath::MoveToElement: + if (hasMoveTo && last != lastMoveTo && !comparePoints(m_points.at(last), m_points.at(lastMoveTo))) + m_segments << Segment(m_pathId, last, lastMoveTo); + hasMoveTo = true; + last = lastMoveTo = current; + break; + case QPainterPath::LineToElement: + m_segments << Segment(m_pathId, last, current); + last = current; + break; + case QPainterPath::CurveToElement: + { + QBezier bezier = QBezier::fromPoints(m_points.at(last), path.elementAt(i), path.elementAt(i+1), path.elementAt(i+2)); + if (isLine(bezier)) { + m_segments << Segment(m_pathId, last, current); + } else { + QRectF bounds = bezier.bounds(); + + // threshold based on similar algorithm as in qtriangulatingstroker.cpp + int threshold = qMin<float>(64, qMax(bounds.width(), bounds.height()) * (2 * qreal(3.14) / 6)); + + if (threshold < 3) threshold = 3; + qreal one_over_threshold_minus_1 = qreal(1) / (threshold - 1); + + for (int t = 1; t < threshold - 1; ++t) { + currentPoint = bezier.pointAt(t * one_over_threshold_minus_1); + + int index = m_points.size(); + m_segments << Segment(m_pathId, last, index); + last = index; + + m_points << currentPoint; + } + + m_segments << Segment(m_pathId, last, current); + } + } + last = current; + i += 2; + break; + default: + Q_ASSERT(false); + break; + } + } + + if (hasMoveTo && last != lastMoveTo && !comparePoints(m_points.at(last), m_points.at(lastMoveTo))) + m_segments << Segment(m_pathId, last, lastMoveTo); + + for (int i = firstSegment; i < m_segments.size(); ++i) { + const QLineF line = lineAt(i); + + qreal x1 = line.p1().x(); + qreal y1 = line.p1().y(); + qreal x2 = line.p2().x(); + qreal y2 = line.p2().y(); + + if (x2 < x1) + qSwap(x1, x2); + if (y2 < y1) + qSwap(y1, y2); + + m_segments.at(i).bounds = QRectF(x1, y1, x2 - x1, y2 - y1); + } + + ++m_pathId; +} + +qreal QWingedEdge::delta(int vertex, int a, int b) const +{ + const QPathEdge *ap = edge(a); + const QPathEdge *bp = edge(b); + + double a_angle = ap->angle; + double b_angle = bp->angle; + + if (vertex == ap->second) + a_angle = ap->invAngle; + + if (vertex == bp->second) + b_angle = bp->invAngle; + + double result = b_angle - a_angle; + + if (result >= 128.) + return result - 128.; + else if (result < 0) + return result + 128.; + else + return result; +} + +static inline QPointF midPoint(const QWingedEdge &list, int ei) +{ + const QPathEdge *ep = list.edge(ei); + Q_ASSERT(ep); + + const QPointF a = *list.vertex(ep->first); + const QPointF b = *list.vertex(ep->second); + return a + 0.5 * (b - a); +} + +QWingedEdge::TraversalStatus QWingedEdge::findInsertStatus(int vi, int ei) const +{ + const QPathVertex *vp = vertex(vi); + + Q_ASSERT(vp); + Q_ASSERT(ei >= 0); + Q_ASSERT(vp->edge >= 0); + + int position = vp->edge; + qreal d = 128.; + + TraversalStatus status; + status.direction = edge(vp->edge)->directionTo(vi); + status.traversal = QPathEdge::RightTraversal; + status.edge = vp->edge; + +#ifdef QDEBUG_CLIPPER + const QPathEdge *ep = edge(ei); + qDebug() << "Finding insert status for edge" << ei << "at vertex" << QPointF(*vp) << ", angles: " << ep->angle << ep->invAngle; +#endif + + do { + status = next(status); + status.flip(); + + Q_ASSERT(edge(status.edge)->vertex(status.direction) == vi); + qreal d2 = delta(vi, ei, status.edge); + +#ifdef QDEBUG_CLIPPER + const QPathEdge *op = edge(status.edge); + qDebug() << "Delta to edge" << status.edge << d2 << ", angles: " << op->angle << op->invAngle; +#endif + + if (d2 < d) { + position = status.edge; + d = d2; + } + } while (status.edge != vp->edge); + + status.traversal = QPathEdge::LeftTraversal; + status.direction = QPathEdge::Forward; + status.edge = position; + + if (edge(status.edge)->vertex(status.direction) != vi) + status.flip(); + +#ifdef QDEBUG_CLIPPER + qDebug() << "Inserting edge" << ei << "to" << (status.traversal == QPathEdge::LeftTraversal ? "left" : "right") << "of edge" << status.edge; +#endif + + Q_ASSERT(edge(status.edge)->vertex(status.direction) == vi); + + return status; +} + +void QWingedEdge::removeEdge(int ei) +{ + QPathEdge *ep = edge(ei); + + TraversalStatus status; + status.direction = QPathEdge::Forward; + status.traversal = QPathEdge::RightTraversal; + status.edge = ei; + + TraversalStatus forwardRight = next(status); + forwardRight.flipDirection(); + + status.traversal = QPathEdge::LeftTraversal; + TraversalStatus forwardLeft = next(status); + forwardLeft.flipDirection(); + + status.direction = QPathEdge::Backward; + TraversalStatus backwardLeft = next(status); + backwardLeft.flipDirection(); + + status.traversal = QPathEdge::RightTraversal; + TraversalStatus backwardRight = next(status); + backwardRight.flipDirection(); + + edge(forwardRight.edge)->setNext(forwardRight.traversal, forwardRight.direction, forwardLeft.edge); + edge(forwardLeft.edge)->setNext(forwardLeft.traversal, forwardLeft.direction, forwardRight.edge); + + edge(backwardRight.edge)->setNext(backwardRight.traversal, backwardRight.direction, backwardLeft.edge); + edge(backwardLeft.edge)->setNext(backwardLeft.traversal, backwardLeft.direction, backwardRight.edge); + + ep->setNext(QPathEdge::Forward, ei); + ep->setNext(QPathEdge::Backward, ei); + + QPathVertex *a = vertex(ep->first); + QPathVertex *b = vertex(ep->second); + + a->edge = backwardRight.edge; + b->edge = forwardRight.edge; +} + +static int commonEdge(const QWingedEdge &list, int a, int b) +{ + const QPathVertex *ap = list.vertex(a); + Q_ASSERT(ap); + + const QPathVertex *bp = list.vertex(b); + Q_ASSERT(bp); + + if (ap->edge < 0 || bp->edge < 0) + return -1; + + QWingedEdge::TraversalStatus status; + status.edge = ap->edge; + status.direction = list.edge(status.edge)->directionTo(a); + status.traversal = QPathEdge::RightTraversal; + + do { + const QPathEdge *ep = list.edge(status.edge); + + if ((ep->first == a && ep->second == b) + || (ep->first == b && ep->second == a)) + return status.edge; + + status = list.next(status); + status.flip(); + } while (status.edge != ap->edge); + + return -1; +} + +static double computeAngle(const QPointF &v) +{ +#if 1 + if (v.x() == 0) { + return v.y() <= 0 ? 0 : 64.; + } else if (v.y() == 0) { + return v.x() <= 0 ? 32. : 96.; + } + + double vx = v.x(); + double vy = v.y(); + normalize(vx, vy); + if (vy < 0) { + if (vx < 0) { // 0 - 32 + return -32. * vx; + } else { // 96 - 128 + return 128. - 32. * vx; + } + } else { // 32 - 96 + return 64. + 32. * vx; + } +#else + // doesn't seem to be robust enough + return qAtan2(v.x(), v.y()) + Q_PI; +#endif +} + +int QWingedEdge::addEdge(const QPointF &a, const QPointF &b) +{ + int fi = insert(a); + int si = insert(b); + + return addEdge(fi, si); +} + +int QWingedEdge::addEdge(int fi, int si) +{ + if (fi == si) + return -1; + + int common = commonEdge(*this, fi, si); + if (common >= 0) + return common; + + m_edges << QPathEdge(fi, si); + + int ei = m_edges.size() - 1; + + QPathVertex *fp = vertex(fi); + QPathVertex *sp = vertex(si); + + QPathEdge *ep = edge(ei); + + const QPointF tangent = QPointF(*sp) - QPointF(*fp); + ep->angle = computeAngle(tangent); + ep->invAngle = ep->angle + 64; + if (ep->invAngle >= 128) + ep->invAngle -= 128; + + QPathVertex *vertices[2] = { fp, sp }; + QPathEdge::Direction dirs[2] = { QPathEdge::Backward, QPathEdge::Forward }; + +#ifdef QDEBUG_CLIPPER + printf("** Adding edge %d / vertices: %.07f %.07f, %.07f %.07f\n", ei, fp->x, fp->y, sp->x, sp->y); +#endif + + for (int i = 0; i < 2; ++i) { + QPathVertex *vp = vertices[i]; + if (vp->edge < 0) { + vp->edge = ei; + ep->setNext(dirs[i], ei); + } else { + int vi = ep->vertex(dirs[i]); + Q_ASSERT(vertex(vi) == vertices[i]); + + TraversalStatus os = findInsertStatus(vi, ei); + QPathEdge *op = edge(os.edge); + + Q_ASSERT(vertex(op->vertex(os.direction)) == vertices[i]); + + TraversalStatus ns = next(os); + ns.flipDirection(); + QPathEdge *np = edge(ns.edge); + + op->setNext(os.traversal, os.direction, ei); + np->setNext(ns.traversal, ns.direction, ei); + + int oe = os.edge; + int ne = ns.edge; + + os = next(os); + ns = next(ns); + + os.flipDirection(); + ns.flipDirection(); + + Q_ASSERT(os.edge == ei); + Q_ASSERT(ns.edge == ei); + + ep->setNext(os.traversal, os.direction, oe); + ep->setNext(ns.traversal, ns.direction, ne); + } + } + + Q_ASSERT(ep->next(QPathEdge::RightTraversal, QPathEdge::Forward) >= 0); + Q_ASSERT(ep->next(QPathEdge::RightTraversal, QPathEdge::Backward) >= 0); + Q_ASSERT(ep->next(QPathEdge::LeftTraversal, QPathEdge::Forward) >= 0); + Q_ASSERT(ep->next(QPathEdge::LeftTraversal, QPathEdge::Backward) >= 0); + + return ei; +} + +int QWingedEdge::insert(const QPathVertex &vertex) +{ + if (!m_vertices.isEmpty()) { + const QPathVertex &last = m_vertices.last(); + if (vertex.x == last.x && vertex.y == last.y) + return m_vertices.size() - 1; + + for (int i = 0; i < m_vertices.size(); ++i) { + const QPathVertex &v = m_vertices.at(i); + if (qFuzzyCompare(v.x, vertex.x) && qFuzzyCompare(v.y, vertex.y)) { + return i; + } + } + } + + m_vertices << vertex; + return m_vertices.size() - 1; +} + +static void addLineTo(QPainterPath &path, const QPointF &point) +{ + const int elementCount = path.elementCount(); + if (elementCount >= 2) { + const QPainterPath::Element &middle = path.elementAt(elementCount - 1); + if (middle.type == QPainterPath::LineToElement) { + const QPointF first = path.elementAt(elementCount - 2); + const QPointF d1 = point - first; + const QPointF d2 = middle - first; + + const QPointF p(-d1.y(), d1.x()); + + if (qFuzzyIsNull(dot(p, d2))) { + path.setElementPositionAt(elementCount - 1, point.x(), point.y()); + return; + } + } + } + + path.lineTo(point); +} + +static void add(QPainterPath &path, const QWingedEdge &list, int edge, QPathEdge::Traversal traversal) +{ + QWingedEdge::TraversalStatus status; + status.edge = edge; + status.traversal = traversal; + status.direction = QPathEdge::Forward; + + path.moveTo(*list.vertex(list.edge(edge)->first)); + + do { + const QPathEdge *ep = list.edge(status.edge); + + addLineTo(path, *list.vertex(ep->vertex(status.direction))); + + if (status.traversal == QPathEdge::LeftTraversal) + ep->flag &= ~16; + else + ep->flag &= ~32; + + status = list.next(status); + } while (status.edge != edge); +} + +void QWingedEdge::simplify() +{ + for (int i = 0; i < edgeCount(); ++i) { + const QPathEdge *ep = edge(i); + + // if both sides are part of the inside then we can collapse the edge + int flag = 0x3 << 4; + if ((ep->flag & flag) == flag) { + removeEdge(i); + + ep->flag &= ~flag; + } + } +} + +QPainterPath QWingedEdge::toPath() const +{ + QPainterPath path; + + for (int i = 0; i < edgeCount(); ++i) { + const QPathEdge *ep = edge(i); + + if (ep->flag & 16) { + add(path, *this, i, QPathEdge::LeftTraversal); + } + + if (ep->flag & 32) + add(path, *this, i, QPathEdge::RightTraversal); + } + + return path; +} + +bool QPathClipper::intersect() +{ + if (subjectPath == clipPath) + return true; + + QRectF r1 = subjectPath.controlPointRect(); + QRectF r2 = clipPath.controlPointRect(); + if (qMax(r1.x(), r2.x()) > qMin(r1.x() + r1.width(), r2.x() + r2.width()) || + qMax(r1.y(), r2.y()) > qMin(r1.y() + r1.height(), r2.y() + r2.height())) { + // no way we could intersect + return false; + } + + bool subjectIsRect = pathToRect(subjectPath); + bool clipIsRect = pathToRect(clipPath); + + if (subjectIsRect && clipIsRect) + return true; + else if (subjectIsRect) + return clipPath.intersects(r1); + else if (clipIsRect) + return subjectPath.intersects(r2); + + QPathSegments a(subjectPath.elementCount()); + a.setPath(subjectPath); + QPathSegments b(clipPath.elementCount()); + b.setPath(clipPath); + + QIntersectionFinder finder; + if (finder.hasIntersections(a, b)) + return true; + + for (int i = 0; i < clipPath.elementCount(); ++i) { + if (clipPath.elementAt(i).type == QPainterPath::MoveToElement) { + const QPointF point = clipPath.elementAt(i); + if (r1.contains(point) && subjectPath.contains(point)) + return true; + } + } + + for (int i = 0; i < subjectPath.elementCount(); ++i) { + if (subjectPath.elementAt(i).type == QPainterPath::MoveToElement) { + const QPointF point = subjectPath.elementAt(i); + if (r2.contains(point) && clipPath.contains(point)) + return true; + } + } + + return false; +} + +bool QPathClipper::contains() +{ + if (subjectPath == clipPath) + return false; + + QRectF r1 = subjectPath.controlPointRect(); + QRectF r2 = clipPath.controlPointRect(); + if (qMax(r1.x(), r2.x()) > qMin(r1.x() + r1.width(), r2.x() + r2.width()) || + qMax(r1.y(), r2.y()) > qMin(r1.y() + r1.height(), r2.y() + r2.height())) { + // no intersection -> not contained + return false; + } + + bool clipIsRect = pathToRect(clipPath); + if (clipIsRect) + return subjectPath.contains(r2); + + QPathSegments a(subjectPath.elementCount()); + a.setPath(subjectPath); + QPathSegments b(clipPath.elementCount()); + b.setPath(clipPath); + + QIntersectionFinder finder; + if (finder.hasIntersections(a, b)) + return false; + + for (int i = 0; i < clipPath.elementCount(); ++i) { + if (clipPath.elementAt(i).type == QPainterPath::MoveToElement) { + const QPointF point = clipPath.elementAt(i); + if (!r1.contains(point) || !subjectPath.contains(point)) + return false; + } + } + + return true; +} + +QPathClipper::QPathClipper(const QPainterPath &subject, + const QPainterPath &clip) + : subjectPath(subject) + , clipPath(clip) +{ + aMask = subjectPath.fillRule() == Qt::WindingFill ? ~0x0 : 0x1; + bMask = clipPath.fillRule() == Qt::WindingFill ? ~0x0 : 0x1; +} + +template <typename Iterator, typename Equality> +Iterator qRemoveDuplicates(Iterator begin, Iterator end, Equality eq) +{ + if (begin == end) + return end; + + Iterator last = begin; + ++begin; + Iterator insert = begin; + for (Iterator it = begin; it != end; ++it) { + if (!eq(*it, *last)) { + *insert++ = *it; + last = it; + } + } + + return insert; +} + +static void clear(QWingedEdge& list, int edge, QPathEdge::Traversal traversal) +{ + QWingedEdge::TraversalStatus status; + status.edge = edge; + status.traversal = traversal; + status.direction = QPathEdge::Forward; + + do { + if (status.traversal == QPathEdge::LeftTraversal) + list.edge(status.edge)->flag |= 1; + else + list.edge(status.edge)->flag |= 2; + + status = list.next(status); + } while (status.edge != edge); +} + +template <typename InputIterator> +InputIterator qFuzzyFind(InputIterator first, InputIterator last, qreal val) +{ + while (first != last && !QT_PREPEND_NAMESPACE(qFuzzyCompare)(qreal(*first), qreal(val))) + ++first; + return first; +} + +static bool fuzzyCompare(qreal a, qreal b) +{ + return qFuzzyCompare(a, b); +} + +bool QPathClipper::pathToRect(const QPainterPath &path, QRectF *rect) +{ + if (path.elementCount() != 5) + return false; + + const bool mightBeRect = path.elementAt(0).isMoveTo() + && path.elementAt(1).isLineTo() + && path.elementAt(2).isLineTo() + && path.elementAt(3).isLineTo() + && path.elementAt(4).isLineTo(); + + if (!mightBeRect) + return false; + + const qreal x1 = path.elementAt(0).x; + const qreal y1 = path.elementAt(0).y; + + const qreal x2 = path.elementAt(1).x; + const qreal y2 = path.elementAt(2).y; + + if (path.elementAt(1).y != y1) + return false; + + if (path.elementAt(2).x != x2) + return false; + + if (path.elementAt(3).x != x1 || path.elementAt(3).y != y2) + return false; + + if (path.elementAt(4).x != x1 || path.elementAt(4).y != y1) + return false; + + if (rect) + rect->setCoords(x1, y1, x2, y2); + + return true; +} + + +QPainterPath QPathClipper::clip(Operation operation) +{ + op = operation; + + if (op != Simplify) { + if (subjectPath == clipPath) + return op == BoolSub ? QPainterPath() : subjectPath; + + bool subjectIsRect = pathToRect(subjectPath, 0); + bool clipIsRect = pathToRect(clipPath, 0); + + const QRectF clipBounds = clipPath.boundingRect(); + const QRectF subjectBounds = subjectPath.boundingRect(); + + if (!clipBounds.intersects(subjectBounds)) { + switch (op) { + case BoolSub: + return subjectPath; + case BoolAnd: + return QPainterPath(); + case BoolOr: { + QPainterPath result = subjectPath; + if (result.fillRule() == clipPath.fillRule()) { + result.addPath(clipPath); + } else if (result.fillRule() == Qt::WindingFill) { + result = result.simplified(); + result.addPath(clipPath); + } else { + result.addPath(clipPath.simplified()); + } + return result; + } + default: + break; + } + } + + if (clipBounds.contains(subjectBounds)) { + if (clipIsRect) { + switch (op) { + case BoolSub: + return QPainterPath(); + case BoolAnd: + return subjectPath; + case BoolOr: + return clipPath; + default: + break; + } + } + } else if (subjectBounds.contains(clipBounds)) { + if (subjectIsRect) { + switch (op) { + case BoolSub: + if (clipPath.fillRule() == Qt::OddEvenFill) { + QPainterPath result = clipPath; + result.addRect(subjectBounds); + return result; + } else { + QPainterPath result = clipPath.simplified(); + result.addRect(subjectBounds); + return result; + } + break; + case BoolAnd: + return clipPath; + case BoolOr: + return subjectPath; + default: + break; + } + } + } + + if (op == BoolAnd) { + if (subjectIsRect) + return intersect(clipPath, subjectBounds); + else if (clipIsRect) + return intersect(subjectPath, clipBounds); + } + } + + QWingedEdge list(subjectPath, clipPath); + + doClip(list, ClipMode); + + QPainterPath path = list.toPath(); + return path; +} + +bool QPathClipper::doClip(QWingedEdge &list, ClipperMode mode) +{ + QVector<qreal> y_coords; + y_coords.reserve(list.vertexCount()); + for (int i = 0; i < list.vertexCount(); ++i) + y_coords << list.vertex(i)->y; + + qSort(y_coords.begin(), y_coords.end()); + y_coords.resize(qRemoveDuplicates(y_coords.begin(), y_coords.end(), fuzzyCompare) - y_coords.begin()); + +#ifdef QDEBUG_CLIPPER + printf("sorted y coords:\n"); + for (int i = 0; i < y_coords.size(); ++i) { + printf("%.9f\n", y_coords[i]); + } +#endif + + bool found; + do { + found = false; + int index = 0; + qreal maxHeight = 0; + for (int i = 0; i < list.edgeCount(); ++i) { + QPathEdge *edge = list.edge(i); + + // have both sides of this edge already been handled? + if ((edge->flag & 0x3) == 0x3) + continue; + + QPathVertex *a = list.vertex(edge->first); + QPathVertex *b = list.vertex(edge->second); + + if (qFuzzyCompare(a->y, b->y)) + continue; + + found = true; + + qreal height = qAbs(a->y - b->y); + if (height > maxHeight) { + index = i; + maxHeight = height; + } + } + + if (found) { + QPathEdge *edge = list.edge(index); + + QPathVertex *a = list.vertex(edge->first); + QPathVertex *b = list.vertex(edge->second); + + // FIXME: this can be optimized by using binary search + const int first = qFuzzyFind(y_coords.begin(), y_coords.end(), qMin(a->y, b->y)) - y_coords.begin(); + const int last = qFuzzyFind(y_coords.begin() + first, y_coords.end(), qMax(a->y, b->y)) - y_coords.begin(); + + Q_ASSERT(first < y_coords.size() - 1); + Q_ASSERT(last < y_coords.size()); + + qreal bestY = 0.5 * (y_coords[first] + y_coords[first+1]); + qreal biggestGap = y_coords[first+1] - y_coords[first]; + + for (int i = first + 1; i < last; ++i) { + qreal gap = y_coords[i+1] - y_coords[i]; + + if (gap > biggestGap) { + bestY = 0.5 * (y_coords[i] + y_coords[i+1]); + biggestGap = gap; + } + } + +#ifdef QDEBUG_CLIPPER + printf("y: %.9f, gap: %.9f\n", bestY, biggestGap); +#endif + + if (handleCrossingEdges(list, bestY, mode) && mode == CheckMode) + return true; + + edge->flag |= 0x3; + } + } while (found); + + if (mode == ClipMode) + list.simplify(); + + return false; +} + +static void traverse(QWingedEdge &list, int edge, QPathEdge::Traversal traversal) +{ + QWingedEdge::TraversalStatus status; + status.edge = edge; + status.traversal = traversal; + status.direction = QPathEdge::Forward; + + do { + int flag = status.traversal == QPathEdge::LeftTraversal ? 1 : 2; + + QPathEdge *ep = list.edge(status.edge); + + ep->flag |= (flag | (flag << 4)); + +#ifdef QDEBUG_CLIPPER + qDebug() << "traverse: adding edge " << status.edge << ", mask:" << (flag << 4) <<ep->flag; +#endif + + status = list.next(status); + } while (status.edge != edge); +} + +struct QCrossingEdge +{ + int edge; + qreal x; + + bool operator<(const QCrossingEdge &edge) const + { + return x < edge.x; + } +}; + +static bool bool_op(bool a, bool b, QPathClipper::Operation op) +{ + switch (op) { + case QPathClipper::BoolAnd: + return a && b; + case QPathClipper::BoolOr: // fall-through + case QPathClipper::Simplify: + return a || b; + case QPathClipper::BoolSub: + return a && !b; + default: + Q_ASSERT(false); + return false; + } +} + +bool QWingedEdge::isInside(qreal x, qreal y) const +{ + int winding = 0; + for (int i = 0; i < edgeCount(); ++i) { + const QPathEdge *ep = edge(i); + + // left xor right + int w = ((ep->flag >> 4) ^ (ep->flag >> 5)) & 1; + + if (!w) + continue; + + QPointF a = *vertex(ep->first); + QPointF b = *vertex(ep->second); + + if ((a.y() < y && b.y() > y) || (a.y() > y && b.y() < y)) { + qreal intersectionX = a.x() + (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()); + + if (intersectionX > x) + winding += w; + } + } + + return winding & 1; +} + +static QVector<QCrossingEdge> findCrossings(const QWingedEdge &list, qreal y) +{ + QVector<QCrossingEdge> crossings; + for (int i = 0; i < list.edgeCount(); ++i) { + const QPathEdge *edge = list.edge(i); + QPointF a = *list.vertex(edge->first); + QPointF b = *list.vertex(edge->second); + + if ((a.y() < y && b.y() > y) || (a.y() > y && b.y() < y)) { + const qreal intersection = a.x() + (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()); + const QCrossingEdge edge = { i, intersection }; + crossings << edge; + } + } + return crossings; +} + +bool QPathClipper::handleCrossingEdges(QWingedEdge &list, qreal y, ClipperMode mode) +{ + QVector<QCrossingEdge> crossings = findCrossings(list, y); + + Q_ASSERT(!crossings.isEmpty()); + qSort(crossings.begin(), crossings.end()); + + int windingA = 0; + int windingB = 0; + + int windingD = 0; + +#ifdef QDEBUG_CLIPPER + qDebug() << "crossings:" << crossings.size(); +#endif + for (int i = 0; i < crossings.size() - 1; ++i) { + int ei = crossings.at(i).edge; + const QPathEdge *edge = list.edge(ei); + + windingA += edge->windingA; + windingB += edge->windingB; + + const bool hasLeft = (edge->flag >> 4) & 1; + const bool hasRight = (edge->flag >> 4) & 2; + + windingD += hasLeft ^ hasRight; + + const bool inA = (windingA & aMask) != 0; + const bool inB = (windingB & bMask) != 0; + const bool inD = (windingD & 0x1) != 0; + + const bool inside = bool_op(inA, inB, op); + const bool add = inD ^ inside; + +#ifdef QDEBUG_CLIPPER + printf("y %f, x %f, inA: %d, inB: %d, inD: %d, inside: %d, flag: %x, bezier: %p, edge: %d\n", y, crossings.at(i).x, inA, inB, inD, inside, edge->flag, edge->bezier, ei); +#endif + + if (add) { + if (mode == CheckMode) + return true; + + qreal y0 = list.vertex(edge->first)->y; + qreal y1 = list.vertex(edge->second)->y; + + if (y0 < y1) { + if (!(edge->flag & 1)) + traverse(list, ei, QPathEdge::LeftTraversal); + + if (!(edge->flag & 2)) + clear(list, ei, QPathEdge::RightTraversal); + } else { + if (!(edge->flag & 1)) + clear(list, ei, QPathEdge::LeftTraversal); + + if (!(edge->flag & 2)) + traverse(list, ei, QPathEdge::RightTraversal); + } + + ++windingD; + } else { + if (!(edge->flag & 1)) + clear(list, ei, QPathEdge::LeftTraversal); + + if (!(edge->flag & 2)) + clear(list, ei, QPathEdge::RightTraversal); + } + } + + return false; +} + +namespace { + +QList<QPainterPath> toSubpaths(const QPainterPath &path) +{ + + QList<QPainterPath> subpaths; + if (path.isEmpty()) + return subpaths; + + QPainterPath current; + for (int i = 0; i < path.elementCount(); ++i) { + const QPainterPath::Element &e = path.elementAt(i); + switch (e.type) { + case QPainterPath::MoveToElement: + if (current.elementCount() > 1) + subpaths += current; + current = QPainterPath(); + current.moveTo(e); + break; + case QPainterPath::LineToElement: + current.lineTo(e); + break; + case QPainterPath::CurveToElement: { + current.cubicTo(e, path.elementAt(i + 1), path.elementAt(i + 2)); + i+=2; + break; + } + case QPainterPath::CurveToDataElement: + Q_ASSERT(!"toSubpaths(), bad element type"); + break; + } + } + + if (current.elementCount() > 1) + subpaths << current; + + return subpaths; +} + +enum Edge +{ + Left, Top, Right, Bottom +}; + +static bool isVertical(Edge edge) +{ + return edge == Left || edge == Right; +} + +template <Edge edge> +bool compare(const QPointF &p, qreal t) +{ + switch (edge) + { + case Left: + return p.x() < t; + case Right: + return p.x() > t; + case Top: + return p.y() < t; + default: + return p.y() > t; + } +} + +template <Edge edge> +QPointF intersectLine(const QPointF &a, const QPointF &b, qreal t) +{ + QLineF line(a, b); + switch (edge) { + case Left: // fall-through + case Right: + return line.pointAt((t - a.x()) / (b.x() - a.x())); + default: + return line.pointAt((t - a.y()) / (b.y() - a.y())); + } +} + +void addLine(QPainterPath &path, const QLineF &line) +{ + if (path.elementCount() > 0) + path.lineTo(line.p1()); + else + path.moveTo(line.p1()); + + path.lineTo(line.p2()); +} + +template <Edge edge> +void clipLine(const QPointF &a, const QPointF &b, qreal t, QPainterPath &result) +{ + bool outA = compare<edge>(a, t); + bool outB = compare<edge>(b, t); + if (outA && outB) + return; + + if (outA) + addLine(result, QLineF(intersectLine<edge>(a, b, t), b)); + else if(outB) + addLine(result, QLineF(a, intersectLine<edge>(a, b, t))); + else + addLine(result, QLineF(a, b)); +} + +void addBezier(QPainterPath &path, const QBezier &bezier) +{ + if (path.elementCount() > 0) + path.lineTo(bezier.pt1()); + else + path.moveTo(bezier.pt1()); + + path.cubicTo(bezier.pt2(), bezier.pt3(), bezier.pt4()); +} + +template <Edge edge> +void clipBezier(const QPointF &a, const QPointF &b, const QPointF &c, const QPointF &d, qreal t, QPainterPath &result) +{ + QBezier bezier = QBezier::fromPoints(a, b, c, d); + + bool outA = compare<edge>(a, t); + bool outB = compare<edge>(b, t); + bool outC = compare<edge>(c, t); + bool outD = compare<edge>(d, t); + + int outCount = int(outA) + int(outB) + int(outC) + int(outD); + + if (outCount == 4) + return; + + if (outCount == 0) { + addBezier(result, bezier); + return; + } + + QTransform flip = isVertical(edge) ? QTransform(0, 1, 1, 0, 0, 0) : QTransform(); + QBezier unflipped = bezier; + QBezier flipped = bezier.mapBy(flip); + + qreal t0 = 0, t1 = 1; + int stationary = flipped.stationaryYPoints(t0, t1); + + qreal segments[4]; + QPointF points[4]; + points[0] = unflipped.pt1(); + segments[0] = 0; + + int segmentCount = 0; + if (stationary > 0) { + ++segmentCount; + segments[segmentCount] = t0; + points[segmentCount] = unflipped.pointAt(t0); + } + if (stationary > 1) { + ++segmentCount; + segments[segmentCount] = t1; + points[segmentCount] = unflipped.pointAt(t1); + } + ++segmentCount; + segments[segmentCount] = 1; + points[segmentCount] = unflipped.pt4(); + + qreal lastIntersection = 0; + for (int i = 0; i < segmentCount; ++i) { + outA = compare<edge>(points[i], t); + outB = compare<edge>(points[i+1], t); + + if (outA != outB) { + qreal intersection = flipped.tForY(segments[i], segments[i+1], t); + + if (outB) + addBezier(result, unflipped.getSubRange(lastIntersection, intersection)); + + lastIntersection = intersection; + } + } + + if (!outB) + addBezier(result, unflipped.getSubRange(lastIntersection, 1)); +} + +// clips a single subpath against a single edge +template <Edge edge> +QPainterPath clip(const QPainterPath &path, qreal t) +{ + QPainterPath result; + for (int i = 1; i < path.elementCount(); ++i) { + const QPainterPath::Element &element = path.elementAt(i); + Q_ASSERT(!element.isMoveTo()); + if (element.isLineTo()) { + clipLine<edge>(path.elementAt(i-1), path.elementAt(i), t, result); + } else { + clipBezier<edge>(path.elementAt(i-1), path.elementAt(i), path.elementAt(i+1), path.elementAt(i+2), t, result); + i += 2; + } + } + + int last = path.elementCount() - 1; + if (QPointF(path.elementAt(last)) != QPointF(path.elementAt(0))) + clipLine<edge>(path.elementAt(last), path.elementAt(0), t, result); + + return result; +} + +QPainterPath intersectPath(const QPainterPath &path, const QRectF &rect) +{ + QList<QPainterPath> subpaths = toSubpaths(path); + + QPainterPath result; + result.setFillRule(path.fillRule()); + for (int i = 0; i < subpaths.size(); ++i) { + QPainterPath subPath = subpaths.at(i); + QRectF bounds = subPath.boundingRect(); + if (bounds.intersects(rect)) { + if (bounds.left() < rect.left()) + subPath = clip<Left>(subPath, rect.left()); + if (bounds.right() > rect.right()) + subPath = clip<Right>(subPath, rect.right()); + + bounds = subPath.boundingRect(); + + if (bounds.top() < rect.top()) + subPath = clip<Top>(subPath, rect.top()); + if (bounds.bottom() > rect.bottom()) + subPath = clip<Bottom>(subPath, rect.bottom()); + + if (subPath.elementCount() > 1) + result.addPath(subPath); + } + } + return result; +} + +} + +QPainterPath QPathClipper::intersect(const QPainterPath &path, const QRectF &rect) +{ + return intersectPath(path, rect); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpathclipper_p.h b/src/gui/painting/qpathclipper_p.h new file mode 100644 index 0000000000..226c55d29e --- /dev/null +++ b/src/gui/painting/qpathclipper_p.h @@ -0,0 +1,494 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPATHCLIPPER_P_H +#define QPATHCLIPPER_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 <QtGui/qpainterpath.h> +#include <QtCore/qlist.h> + +#include <private/qbezier_p.h> +#include <private/qdatabuffer_p.h> +#include <stdio.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWingedEdge; + +class Q_AUTOTEST_EXPORT QPathClipper +{ +public: + enum Operation { + BoolAnd, + BoolOr, + BoolSub, + Simplify + }; +public: + QPathClipper(const QPainterPath &subject, + const QPainterPath &clip); + + QPainterPath clip(Operation op = BoolAnd); + + bool intersect(); + bool contains(); + + static bool pathToRect(const QPainterPath &path, QRectF *rect = 0); + static QPainterPath intersect(const QPainterPath &path, const QRectF &rect); + +private: + Q_DISABLE_COPY(QPathClipper) + + enum ClipperMode { + ClipMode, // do the full clip + CheckMode // for contains/intersects (only interested in whether the result path is non-empty) + }; + + bool handleCrossingEdges(QWingedEdge &list, qreal y, ClipperMode mode); + bool doClip(QWingedEdge &list, ClipperMode mode); + + QPainterPath subjectPath; + QPainterPath clipPath; + Operation op; + + int aMask; + int bMask; +}; + +struct QPathVertex +{ +public: + QPathVertex(const QPointF &p = QPointF(), int e = -1); + operator QPointF() const; + + int edge; + + qreal x; + qreal y; +}; + +class QPathEdge +{ +public: + enum Traversal { + RightTraversal, + LeftTraversal + }; + + enum Direction { + Forward, + Backward + }; + + enum Type { + Line, + Curve + }; + + QPathEdge(int a = -1, int b = -1); + + mutable int flag; + + int windingA; + int windingB; + + int first; + int second; + + double angle; + double invAngle; + + int next(Traversal traversal, Direction direction) const; + + void setNext(Traversal traversal, Direction direction, int next); + void setNext(Direction direction, int next); + + Direction directionTo(int vertex) const; + int vertex(Direction direction) const; + +private: + int m_next[2][2]; +}; + +class QPathSegments +{ +public: + struct Intersection { + int vertex; + qreal t; + + int next; + + bool operator<(const Intersection &o) const { + return t < o.t; + } + }; + + struct Segment { + Segment(int pathId, int vertexA, int vertexB) + : path(pathId) + , va(vertexA) + , vb(vertexB) + , intersection(-1) + { + } + + int path; + + // vertices + int va; + int vb; + + // intersection index + int intersection; + + QRectF bounds; + }; + + + QPathSegments(int reserve); + + void setPath(const QPainterPath &path); + void addPath(const QPainterPath &path); + + int intersections() const; + int segments() const; + int points() const; + + const Segment &segmentAt(int index) const; + const QLineF lineAt(int index) const; + const QRectF &elementBounds(int index) const; + int pathId(int index) const; + + const QPointF &pointAt(int vertex) const; + int addPoint(const QPointF &point); + + const Intersection *intersectionAt(int index) const; + void addIntersection(int index, const Intersection &intersection); + + void mergePoints(); + +private: + QDataBuffer<QPointF> m_points; + QDataBuffer<Segment> m_segments; + QDataBuffer<Intersection> m_intersections; + + int m_pathId; +}; + +class Q_AUTOTEST_EXPORT QWingedEdge +{ +public: + struct TraversalStatus + { + int edge; + QPathEdge::Traversal traversal; + QPathEdge::Direction direction; + + void flipDirection(); + void flipTraversal(); + + void flip(); + }; + + QWingedEdge(); + QWingedEdge(const QPainterPath &subject, const QPainterPath &clip); + + void simplify(); + QPainterPath toPath() const; + + int edgeCount() const; + + QPathEdge *edge(int edge); + const QPathEdge *edge(int edge) const; + + int vertexCount() const; + + int addVertex(const QPointF &p); + + QPathVertex *vertex(int vertex); + const QPathVertex *vertex(int vertex) const; + + TraversalStatus next(const TraversalStatus &status) const; + + int addEdge(const QPointF &a, const QPointF &b); + int addEdge(int vertexA, int vertexB); + + bool isInside(qreal x, qreal y) const; + + static QPathEdge::Traversal flip(QPathEdge::Traversal traversal); + static QPathEdge::Direction flip(QPathEdge::Direction direction); + +private: + void intersectAndAdd(); + + void printNode(int i, FILE *handle); + + void removeEdge(int ei); + + int insert(const QPathVertex &vertex); + TraversalStatus findInsertStatus(int vertex, int edge) const; + + qreal delta(int vertex, int a, int b) const; + + QDataBuffer<QPathEdge> m_edges; + QDataBuffer<QPathVertex> m_vertices; + + QVector<qreal> m_splitPoints; + + QPathSegments m_segments; +}; + +inline QPathEdge::QPathEdge(int a, int b) + : flag(0) + , windingA(0) + , windingB(0) + , first(a) + , second(b) + , angle(0) + , invAngle(0) +{ + m_next[0][0] = -1; + m_next[1][0] = -1; + m_next[0][0] = -1; + m_next[1][0] = -1; +} + +inline int QPathEdge::next(Traversal traversal, Direction direction) const +{ + return m_next[int(traversal)][int(direction)]; +} + +inline void QPathEdge::setNext(Traversal traversal, Direction direction, int next) +{ + m_next[int(traversal)][int(direction)] = next; +} + +inline void QPathEdge::setNext(Direction direction, int next) +{ + m_next[0][int(direction)] = next; + m_next[1][int(direction)] = next; +} + +inline QPathEdge::Direction QPathEdge::directionTo(int vertex) const +{ + return first == vertex ? Backward : Forward; +} + +inline int QPathEdge::vertex(Direction direction) const +{ + return direction == Backward ? first : second; +} + +inline QPathVertex::QPathVertex(const QPointF &p, int e) + : edge(e) + , x(p.x()) + , y(p.y()) +{ +} + +inline QPathVertex::operator QPointF() const +{ + return QPointF(x, y); +} + +inline QPathSegments::QPathSegments(int reserve) : + m_points(reserve), + m_segments(reserve), + m_intersections(reserve) +{ +} + +inline int QPathSegments::segments() const +{ + return m_segments.size(); +} + +inline int QPathSegments::points() const +{ + return m_points.size(); +} + +inline const QPointF &QPathSegments::pointAt(int i) const +{ + return m_points.at(i); +} + +inline int QPathSegments::addPoint(const QPointF &point) +{ + m_points << point; + return m_points.size() - 1; +} + +inline const QPathSegments::Segment &QPathSegments::segmentAt(int index) const +{ + return m_segments.at(index); +} + +inline const QLineF QPathSegments::lineAt(int index) const +{ + const Segment &segment = m_segments.at(index); + return QLineF(m_points.at(segment.va), m_points.at(segment.vb)); +} + +inline const QRectF &QPathSegments::elementBounds(int index) const +{ + return m_segments.at(index).bounds; +} + +inline int QPathSegments::pathId(int index) const +{ + return m_segments.at(index).path; +} + +inline const QPathSegments::Intersection *QPathSegments::intersectionAt(int index) const +{ + const int intersection = m_segments.at(index).intersection; + if (intersection < 0) + return 0; + else + return &m_intersections.at(intersection); +} + +inline int QPathSegments::intersections() const +{ + return m_intersections.size(); +} + +inline void QPathSegments::addIntersection(int index, const Intersection &intersection) +{ + m_intersections << intersection; + + Segment &segment = m_segments.at(index); + if (segment.intersection < 0) { + segment.intersection = m_intersections.size() - 1; + } else { + Intersection *isect = &m_intersections.at(segment.intersection); + + while (isect->next != 0) + isect += isect->next; + + isect->next = (m_intersections.size() - 1) - (isect - m_intersections.data()); + } +} + +inline void QWingedEdge::TraversalStatus::flipDirection() +{ + direction = QWingedEdge::flip(direction); +} + +inline void QWingedEdge::TraversalStatus::flipTraversal() +{ + traversal = QWingedEdge::flip(traversal); +} + +inline void QWingedEdge::TraversalStatus::flip() +{ + flipDirection(); + flipTraversal(); +} + +inline int QWingedEdge::edgeCount() const +{ + return m_edges.size(); +} + +inline QPathEdge *QWingedEdge::edge(int edge) +{ + return edge < 0 ? 0 : &m_edges.at(edge); +} + +inline const QPathEdge *QWingedEdge::edge(int edge) const +{ + return edge < 0 ? 0 : &m_edges.at(edge); +} + +inline int QWingedEdge::vertexCount() const +{ + return m_vertices.size(); +} + +inline int QWingedEdge::addVertex(const QPointF &p) +{ + m_vertices << p; + return m_vertices.size() - 1; +} + +inline QPathVertex *QWingedEdge::vertex(int vertex) +{ + return vertex < 0 ? 0 : &m_vertices.at(vertex); +} + +inline const QPathVertex *QWingedEdge::vertex(int vertex) const +{ + return vertex < 0 ? 0 : &m_vertices.at(vertex); +} + +inline QPathEdge::Traversal QWingedEdge::flip(QPathEdge::Traversal traversal) +{ + return traversal == QPathEdge::RightTraversal ? QPathEdge::LeftTraversal : QPathEdge::RightTraversal; +} + +inline QPathEdge::Direction QWingedEdge::flip(QPathEdge::Direction direction) +{ + return direction == QPathEdge::Forward ? QPathEdge::Backward : QPathEdge::Forward; +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPATHCLIPPER_P_H diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp new file mode 100644 index 0000000000..0271a39b16 --- /dev/null +++ b/src/gui/painting/qpdf.cpp @@ -0,0 +1,2106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qplatformdefs.h" +#include <qdebug.h> +#include "qpdf_p.h" +#include <qfile.h> +#include <qtemporaryfile.h> +#include <private/qmath_p.h> +#include "private/qcups_p.h" +#include "qprinterinfo.h" +#include <qnumeric.h> +#include "private/qfont_p.h" + +#ifdef Q_OS_UNIX +#include "private/qcore_unix_p.h" // overrides QT_OPEN +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); + +/* also adds a space at the end of the number */ +const char *qt_real_to_string(qreal val, char *buf) { + const char *ret = buf; + + if (qIsNaN(val)) { + *(buf++) = '0'; + *(buf++) = ' '; + *buf = 0; + return ret; + } + + if (val < 0) { + *(buf++) = '-'; + val = -val; + } + unsigned int ival = (unsigned int) val; + qreal frac = val - (qreal)ival; + + int ifrac = (int)(frac * 1000000000); + if (ifrac == 1000000000) { + ++ival; + ifrac = 0; + } + char output[256]; + int i = 0; + while (ival) { + output[i] = '0' + (ival % 10); + ++i; + ival /= 10; + } + int fact = 100000000; + if (i == 0) { + *(buf++) = '0'; + } else { + while (i) { + *(buf++) = output[--i]; + fact /= 10; + ifrac /= 10; + } + } + + if (ifrac) { + *(buf++) = '.'; + while (fact) { + *(buf++) = '0' + ((ifrac/fact) % 10); + fact /= 10; + } + } + *(buf++) = ' '; + *buf = 0; + return ret; +} + +const char *qt_int_to_string(int val, char *buf) { + const char *ret = buf; + if (val < 0) { + *(buf++) = '-'; + val = -val; + } + char output[256]; + int i = 0; + while (val) { + output[i] = '0' + (val % 10); + ++i; + val /= 10; + } + if (i == 0) { + *(buf++) = '0'; + } else { + while (i) + *(buf++) = output[--i]; + } + *(buf++) = ' '; + *buf = 0; + return ret; +} + + +namespace QPdf { + ByteStream::ByteStream(QByteArray *byteArray, bool fileBacking) + : dev(new QBuffer(byteArray)), + fileBackingEnabled(fileBacking), + fileBackingActive(false), + handleDirty(false) + { + dev->open(QIODevice::ReadWrite | QIODevice::Append); + } + + ByteStream::ByteStream(bool fileBacking) + : dev(new QBuffer(&ba)), + fileBackingEnabled(fileBacking), + fileBackingActive(false), + handleDirty(false) + { + dev->open(QIODevice::ReadWrite); + } + + ByteStream::~ByteStream() + { + delete dev; + } + + ByteStream &ByteStream::operator <<(char chr) + { + if (handleDirty) prepareBuffer(); + dev->write(&chr, 1); + return *this; + } + + ByteStream &ByteStream::operator <<(const char *str) + { + if (handleDirty) prepareBuffer(); + dev->write(str, strlen(str)); + return *this; + } + + ByteStream &ByteStream::operator <<(const QByteArray &str) + { + if (handleDirty) prepareBuffer(); + dev->write(str); + return *this; + } + + ByteStream &ByteStream::operator <<(const ByteStream &src) + { + Q_ASSERT(!src.dev->isSequential()); + if (handleDirty) prepareBuffer(); + // We do play nice here, even though it looks ugly. + // We save the position and restore it afterwards. + ByteStream &s = const_cast<ByteStream&>(src); + qint64 pos = s.dev->pos(); + s.dev->reset(); + while (!s.dev->atEnd()) { + QByteArray buf = s.dev->read(chunkSize()); + dev->write(buf); + } + s.dev->seek(pos); + return *this; + } + + ByteStream &ByteStream::operator <<(qreal val) { + char buf[256]; + qt_real_to_string(val, buf); + *this << buf; + return *this; + } + + ByteStream &ByteStream::operator <<(int val) { + char buf[256]; + qt_int_to_string(val, buf); + *this << buf; + return *this; + } + + ByteStream &ByteStream::operator <<(const QPointF &p) { + char buf[256]; + qt_real_to_string(p.x(), buf); + *this << buf; + qt_real_to_string(p.y(), buf); + *this << buf; + return *this; + } + + QIODevice *ByteStream::stream() + { + dev->reset(); + handleDirty = true; + return dev; + } + + void ByteStream::clear() + { + dev->open(QIODevice::ReadWrite | QIODevice::Truncate); + } + + void ByteStream::constructor_helper(QByteArray *ba) + { + delete dev; + dev = new QBuffer(ba); + dev->open(QIODevice::ReadWrite); + } + + void ByteStream::prepareBuffer() + { + Q_ASSERT(!dev->isSequential()); + qint64 size = dev->size(); + if (fileBackingEnabled && !fileBackingActive + && size > maxMemorySize()) { + // Switch to file backing. + QTemporaryFile *newFile = new QTemporaryFile; + newFile->open(); + dev->reset(); + while (!dev->atEnd()) { + QByteArray buf = dev->read(chunkSize()); + newFile->write(buf); + } + delete dev; + dev = newFile; + ba.clear(); + fileBackingActive = true; + } + if (dev->pos() != size) { + dev->seek(size); + handleDirty = false; + } + } +} + +#define QT_PATH_ELEMENT(elm) + +QByteArray QPdf::generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags) +{ + QByteArray result; + if (!path.elementCount()) + return result; + + ByteStream s(&result); + + int start = -1; + for (int i = 0; i < path.elementCount(); ++i) { + const QPainterPath::Element &elm = path.elementAt(i); + switch (elm.type) { + case QPainterPath::MoveToElement: + if (start >= 0 + && path.elementAt(start).x == path.elementAt(i-1).x + && path.elementAt(start).y == path.elementAt(i-1).y) + s << "h\n"; + s << matrix.map(QPointF(elm.x, elm.y)) << "m\n"; + start = i; + break; + case QPainterPath::LineToElement: + s << matrix.map(QPointF(elm.x, elm.y)) << "l\n"; + break; + case QPainterPath::CurveToElement: + Q_ASSERT(path.elementAt(i+1).type == QPainterPath::CurveToDataElement); + Q_ASSERT(path.elementAt(i+2).type == QPainterPath::CurveToDataElement); + s << matrix.map(QPointF(elm.x, elm.y)) + << matrix.map(QPointF(path.elementAt(i+1).x, path.elementAt(i+1).y)) + << matrix.map(QPointF(path.elementAt(i+2).x, path.elementAt(i+2).y)) + << "c\n"; + i += 2; + break; + default: + qFatal("QPdf::generatePath(), unhandled type: %d", elm.type); + } + } + if (start >= 0 + && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x + && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y) + s << "h\n"; + + Qt::FillRule fillRule = path.fillRule(); + + const char *op = ""; + switch (flags) { + case ClipPath: + op = (fillRule == Qt::WindingFill) ? "W n\n" : "W* n\n"; + break; + case FillPath: + op = (fillRule == Qt::WindingFill) ? "f\n" : "f*\n"; + break; + case StrokePath: + op = "S\n"; + break; + case FillAndStrokePath: + op = (fillRule == Qt::WindingFill) ? "B\n" : "B*\n"; + break; + } + s << op; + return result; +} + +QByteArray QPdf::generateMatrix(const QTransform &matrix) +{ + QByteArray result; + ByteStream s(&result); + s << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() + << "cm\n"; + return result; +} + +QByteArray QPdf::generateDashes(const QPen &pen) +{ + QByteArray result; + ByteStream s(&result); + s << '['; + + QVector<qreal> dasharray = pen.dashPattern(); + qreal w = pen.widthF(); + if (w < 0.001) + w = 1; + for (int i = 0; i < dasharray.size(); ++i) { + qreal dw = dasharray.at(i)*w; + if (dw < 0.0001) dw = 0.0001; + s << dw; + } + s << ']'; + //qDebug() << "dasharray: pen has" << dasharray; + //qDebug() << " => " << result; + return result; +} + + + +static const char* pattern_for_brush[] = { + 0, // NoBrush + 0, // SolidPattern + "0 J\n" + "6 w\n" + "[] 0 d\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // Dense1Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[] 0 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense2Pattern + + "0 J\n" + "2 w\n" + "[6 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[6 2] -3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense3Pattern + + "0 J\n" + "2 w\n" + "[2 2] 1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] -1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 2] 1 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense4Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 2] 1 d\n" + "2 0 m\n" + "2 8 l\n" + "6 0 m\n" + "6 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense5Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n" + "[2 6] 3 d\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // Dense6Pattern + + "0 J\n" + "2 w\n" + "[2 6] -1 d\n" + "0 0 m\n" + "0 8 l\n" + "8 0 m\n" + "8 8 l\n" + "S\n", // Dense7Pattern + + "1 w\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // HorPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "S\n", // VerPattern + + "1 w\n" + "4 0 m\n" + "4 8 l\n" + "0 4 m\n" + "8 4 l\n" + "S\n", // CrossPattern + + "1 w\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", // BDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "S\n", // FDiagPattern + + "1 w\n" + "-1 3 m\n" + "5 9 l\n" + "3 -1 m\n" + "9 5 l\n" + "-1 5 m\n" + "5 -1 l\n" + "3 9 m\n" + "9 3 l\n" + "S\n", // DiagCrossPattern +}; + +QByteArray QPdf::patternForBrush(const QBrush &b) +{ + int style = b.style(); + if (style > Qt::DiagCrossPattern) + return QByteArray(); + return pattern_for_brush[style]; +} + +#ifdef USE_NATIVE_GRADIENTS +static void writeTriangleLine(uchar *&data, int xpos, int ypos, int xoff, int yoff, uint rgb, uchar flag, bool alpha) +{ + data[0] = flag; + data[1] = (uchar)(xpos >> 16); + data[2] = (uchar)(xpos >> 8); + data[3] = (uchar)(xpos >> 0); + data[4] = (uchar)(ypos >> 16); + data[5] = (uchar)(ypos >> 8); + data[6] = (uchar)(ypos >> 0); + data += 7; + if (alpha) { + *data++ = (uchar)qAlpha(rgb); + } else { + *data++ = (uchar)qRed(rgb); + *data++ = (uchar)qGreen(rgb); + *data++ = (uchar)qBlue(rgb); + } + xpos += xoff; + ypos += yoff; + data[0] = flag; + data[1] = (uchar)(xpos >> 16); + data[2] = (uchar)(xpos >> 8); + data[3] = (uchar)(xpos >> 0); + data[4] = (uchar)(ypos >> 16); + data[5] = (uchar)(ypos >> 8); + data[6] = (uchar)(ypos >> 0); + data += 7; + if (alpha) { + *data++ = (uchar)qAlpha(rgb); + } else { + *data++ = (uchar)qRed(rgb); + *data++ = (uchar)qGreen(rgb); + *data++ = (uchar)qBlue(rgb); + } +} + + +QByteArray QPdf::generateLinearGradientShader(const QLinearGradient *gradient, const QPointF *page_rect, bool alpha) +{ + // generate list of triangles with colors + QPointF start = gradient->start(); + QPointF stop = gradient->finalStop(); + QGradientStops stops = gradient->stops(); + QPointF offset = stop - start; + QGradient::Spread spread = gradient->spread(); + + if (gradient->spread() == QGradient::ReflectSpread) { + offset *= 2; + for (int i = stops.size() - 2; i >= 0; --i) { + QGradientStop stop = stops.at(i); + stop.first = 2. - stop.first; + stops.append(stop); + } + for (int i = 0 ; i < stops.size(); ++i) + stops[i].first /= 2.; + } + + QPointF orthogonal(offset.y(), -offset.x()); + qreal length = offset.x()*offset.x() + offset.y()*offset.y(); + + // find the max and min values in offset and orth direction that are needed to cover + // the whole page + int off_min = INT_MAX; + int off_max = INT_MIN; + qreal ort_min = INT_MAX; + qreal ort_max = INT_MIN; + for (int i = 0; i < 4; ++i) { + qreal off = ((page_rect[i].x() - start.x()) * offset.x() + (page_rect[i].y() - start.y()) * offset.y())/length; + qreal ort = ((page_rect[i].x() - start.x()) * orthogonal.x() + (page_rect[i].y() - start.y()) * orthogonal.y())/length; + off_min = qMin(off_min, qFloor(off)); + off_max = qMax(off_max, qCeil(off)); + ort_min = qMin(ort_min, ort); + ort_max = qMax(ort_max, ort); + } + ort_min -= 1; + ort_max += 1; + + start += off_min * offset + ort_min * orthogonal; + orthogonal *= (ort_max - ort_min); + int num = off_max - off_min; + + QPointF gradient_rect[4] = { start, + start + orthogonal, + start + num*offset, + start + num*offset + orthogonal }; + qreal xmin = gradient_rect[0].x(); + qreal xmax = gradient_rect[0].x(); + qreal ymin = gradient_rect[0].y(); + qreal ymax = gradient_rect[0].y(); + for (int i = 1; i < 4; ++i) { + xmin = qMin(xmin, gradient_rect[i].x()); + xmax = qMax(xmax, gradient_rect[i].x()); + ymin = qMin(ymin, gradient_rect[i].y()); + ymax = qMax(ymax, gradient_rect[i].y()); + } + xmin -= 1000; + xmax += 1000; + ymin -= 1000; + ymax += 1000; + start -= QPointF(xmin, ymin); + qreal factor_x = qreal(1<<24)/(xmax - xmin); + qreal factor_y = qreal(1<<24)/(ymax - ymin); + int xoff = (int)(orthogonal.x()*factor_x); + int yoff = (int)(orthogonal.y()*factor_y); + + QByteArray triangles; + triangles.resize(spread == QGradient::PadSpread ? 20*(stops.size()+2) : 20*num*stops.size()); + uchar *data = (uchar *) triangles.data(); + if (spread == QGradient::PadSpread) { + if (off_min > 0 || off_max < 1) { + // linear gradient outside of page + const QGradientStop ¤t_stop = off_min > 0 ? stops.at(stops.size()-1) : stops.at(0); + uint rgb = current_stop.second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 0, alpha); + start += num*offset; + xpos = (int)(start.x()*factor_x); + ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, 1, alpha); + } else { + int flag = 0; + if (off_min < 0) { + uint rgb = stops.at(0).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + start -= off_min*offset; + flag = 1; + } + for (int s = 0; s < stops.size(); ++s) { + const QGradientStop ¤t_stop = stops.at(s); + uint rgb = current_stop.second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + if (s < stops.size()-1) + start += offset*(stops.at(s+1).first - stops.at(s).first); + flag = 1; + } + if (off_max > 1) { + start += (off_max - 1)*offset; + uint rgb = stops.at(stops.size()-1).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + } + } + } else { + for (int i = 0; i < num; ++i) { + uchar flag = 0; + for (int s = 0; s < stops.size(); ++s) { + uint rgb = stops.at(s).second.rgba(); + int xpos = (int)(start.x()*factor_x); + int ypos = (int)(start.y()*factor_y); + writeTriangleLine(data, xpos, ypos, xoff, yoff, rgb, flag, alpha); + if (s < stops.size()-1) + start += offset*(stops.at(s+1).first - stops.at(s).first); + flag = 1; + } + } + } + triangles.resize((char *)data - triangles.constData()); + + QByteArray shader; + QPdf::ByteStream s(&shader); + s << "<<\n" + "/ShadingType 4\n" + "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") << + "/AntiAlias true\n" + "/BitsPerCoordinate 24\n" + "/BitsPerComponent 8\n" + "/BitsPerFlag 8\n" + "/Decode [" << xmin << xmax << ymin << ymax << (alpha ? "0 1]\n" : "0 1 0 1 0 1]\n") << + "/AntiAlias true\n" + "/Length " << triangles.length() << "\n" + ">>\n" + "stream\n" << triangles << "endstream\n" + "endobj\n"; + return shader; +} +#endif + +static void moveToHook(qfixed x, qfixed y, void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->first) + *t->stream << "h\n"; + if (!t->cosmeticPen) + t->matrix.map(x, y, &x, &y); + *t->stream << x << y << "m\n"; + t->first = false; +} + +static void lineToHook(qfixed x, qfixed y, void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->cosmeticPen) + t->matrix.map(x, y, &x, &y); + *t->stream << x << y << "l\n"; +} + +static void cubicToHook(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data) +{ + QPdf::Stroker *t = (QPdf::Stroker *)data; + if (!t->cosmeticPen) { + t->matrix.map(c1x, c1y, &c1x, &c1y); + t->matrix.map(c2x, c2y, &c2x, &c2y); + t->matrix.map(ex, ey, &ex, &ey); + } + *t->stream << c1x << c1y + << c2x << c2y + << ex << ey + << "c\n"; +} + +QPdf::Stroker::Stroker() + : stream(0), + first(true), + dashStroker(&basicStroker) +{ + stroker = &basicStroker; + basicStroker.setMoveToHook(moveToHook); + basicStroker.setLineToHook(lineToHook); + basicStroker.setCubicToHook(cubicToHook); + cosmeticPen = true; + basicStroker.setStrokeWidth(.1); +} + +void QPdf::Stroker::setPen(const QPen &pen) +{ + if (pen.style() == Qt::NoPen) { + stroker = 0; + return; + } + qreal w = pen.widthF(); + bool zeroWidth = w < 0.0001; + cosmeticPen = pen.isCosmetic(); + if (zeroWidth) + w = .1; + + basicStroker.setStrokeWidth(w); + basicStroker.setCapStyle(pen.capStyle()); + basicStroker.setJoinStyle(pen.joinStyle()); + basicStroker.setMiterLimit(pen.miterLimit()); + + QVector<qreal> dashpattern = pen.dashPattern(); + if (zeroWidth) { + for (int i = 0; i < dashpattern.size(); ++i) + dashpattern[i] *= 10.; + } + if (!dashpattern.isEmpty()) { + dashStroker.setDashPattern(dashpattern); + dashStroker.setDashOffset(pen.dashOffset()); + stroker = &dashStroker; + } else { + stroker = &basicStroker; + } +} + +void QPdf::Stroker::strokePath(const QPainterPath &path) +{ + if (!stroker) + return; + first = true; + + stroker->strokePath(path, this, cosmeticPen ? matrix : QTransform()); + *stream << "h f\n"; +} + +QByteArray QPdf::ascii85Encode(const QByteArray &input) +{ + int isize = input.size()/4*4; + QByteArray output; + output.resize(input.size()*5/4+7); + char *out = output.data(); + const uchar *in = (const uchar *)input.constData(); + for (int i = 0; i < isize; i += 4) { + uint val = (((uint)in[i])<<24) + (((uint)in[i+1])<<16) + (((uint)in[i+2])<<8) + (uint)in[i+3]; + if (val == 0) { + *out = 'z'; + ++out; + } else { + char base[5]; + base[4] = val % 85; + val /= 85; + base[3] = val % 85; + val /= 85; + base[2] = val % 85; + val /= 85; + base[1] = val % 85; + val /= 85; + base[0] = val % 85; + *(out++) = base[0] + '!'; + *(out++) = base[1] + '!'; + *(out++) = base[2] + '!'; + *(out++) = base[3] + '!'; + *(out++) = base[4] + '!'; + } + } + //write the last few bytes + int remaining = input.size() - isize; + if (remaining) { + uint val = 0; + for (int i = isize; i < input.size(); ++i) + val = (val << 8) + in[i]; + val <<= 8*(4-remaining); + char base[5]; + base[4] = val % 85; + val /= 85; + base[3] = val % 85; + val /= 85; + base[2] = val % 85; + val /= 85; + base[1] = val % 85; + val /= 85; + base[0] = val % 85; + for (int i = 0; i < remaining+1; ++i) + *(out++) = base[i] + '!'; + } + *(out++) = '~'; + *(out++) = '>'; + output.resize(out-output.data()); + return output; +} + +const char *QPdf::toHex(ushort u, char *buffer) +{ + int i = 3; + while (i >= 0) { + ushort hex = (u & 0x000f); + if (hex < 0x0a) + buffer[i] = '0'+hex; + else + buffer[i] = 'A'+(hex-0x0a); + u = u >> 4; + i--; + } + buffer[4] = '\0'; + return buffer; +} + +const char *QPdf::toHex(uchar u, char *buffer) +{ + int i = 1; + while (i >= 0) { + ushort hex = (u & 0x000f); + if (hex < 0x0a) + buffer[i] = '0'+hex; + else + buffer[i] = 'A'+(hex-0x0a); + u = u >> 4; + i--; + } + buffer[2] = '\0'; + return buffer; +} + +#define Q_MM(n) int((n * 720 + 127) / 254) +#define Q_IN(n) int(n * 72) + +static const char * const psToStr[QPrinter::NPaperSize+1] = +{ + "A4", "B5", "Letter", "Legal", "Executive", + "A0", "A1", "A2", "A3", "A5", "A6", "A7", "A8", "A9", "B0", "B1", + "B10", "B2", "B3", "B4", "B6", "B7", "B8", "B9", "C5E", "Comm10E", + "DLE", "Folio", "Ledger", "Tabloid", 0 +}; + +QPdf::PaperSize QPdf::paperSize(QPrinter::PaperSize paperSize) +{ + QSizeF s = qt_paperSizeToQSizeF(paperSize); + PaperSize p = { Q_MM(s.width()), Q_MM(s.height()) }; + return p; +} + +const char *QPdf::paperSizeToString(QPrinter::PaperSize paperSize) +{ + return psToStr[paperSize]; +} + +// -------------------------- base engine, shared code between PS and PDF ----------------------- + +QPdfBaseEngine::QPdfBaseEngine(QPdfBaseEnginePrivate &dd, PaintEngineFeatures f) + : QAlphaPaintEngine(dd, f) +{ + Q_D(QPdfBaseEngine); +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) { + QCUPSSupport cups; + const cups_dest_t* printers = cups.availablePrinters(); + int prnCount = cups.availablePrintersCount(); + + for (int i = 0; i < prnCount; ++i) { + if (printers[i].is_default) { + d->printerName = QString::fromLocal8Bit(printers[i].name); + break; + } + } + + } else +#endif + { + d->printerName = QString::fromLocal8Bit(qgetenv("PRINTER")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("LPDEST")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("NPRINTER")); + if (d->printerName.isEmpty()) + d->printerName = QString::fromLocal8Bit(qgetenv("NGPRINTER")); + } +} + +void QPdfBaseEngine::drawPoints (const QPointF *points, int pointCount) +{ + if (!points) + return; + + Q_D(QPdfBaseEngine); + QPainterPath p; + for (int i=0; i!=pointCount;++i) { + p.moveTo(points[i]); + p.lineTo(points[i] + QPointF(0, 0.001)); + } + + bool hadBrush = d->hasBrush; + d->hasBrush = false; + drawPath(p); + d->hasBrush = hadBrush; +} + +void QPdfBaseEngine::drawLines (const QLineF *lines, int lineCount) +{ + if (!lines) + return; + + Q_D(QPdfBaseEngine); + QPainterPath p; + for (int i=0; i!=lineCount;++i) { + p.moveTo(lines[i].p1()); + p.lineTo(lines[i].p2()); + } + bool hadBrush = d->hasBrush; + d->hasBrush = false; + drawPath(p); + d->hasBrush = hadBrush; +} + +void QPdfBaseEngine::drawRects (const QRectF *rects, int rectCount) +{ + if (!rects) + return; + + Q_D(QPdfBaseEngine); + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawRects(rects, rectCount); + if (!continueCall()) + return; + } + + if (d->clipEnabled && d->allClipped) + return; + if (!d->hasPen && !d->hasBrush) + return; + + QBrush penBrush = d->pen.brush(); + if (d->simplePen || !d->hasPen) { + // draw strokes natively in this case for better output + if(!d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << "q\n" << QPdf::generateMatrix(d->stroker.matrix); + for (int i = 0; i < rectCount; ++i) + *d->currentPage << rects[i].x() << rects[i].y() << rects[i].width() << rects[i].height() << "re\n"; + *d->currentPage << (d->hasPen ? (d->hasBrush ? "B\n" : "S\n") : "f\n"); + if(!d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << "Q\n"; + } else { + QPainterPath p; + for (int i=0; i!=rectCount; ++i) + p.addRect(rects[i]); + drawPath(p); + } +} + +void QPdfBaseEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawPolygon(points, pointCount, mode); + if (!continueCall()) + return; + } + + if (!points || !pointCount) + return; + + bool hb = d->hasBrush; + QPainterPath p; + + switch(mode) { + case OddEvenMode: + p.setFillRule(Qt::OddEvenFill); + break; + case ConvexMode: + case WindingMode: + p.setFillRule(Qt::WindingFill); + break; + case PolylineMode: + d->hasBrush = false; + break; + default: + break; + } + + p.moveTo(points[0]); + for (int i = 1; i < pointCount; ++i) + p.lineTo(points[i]); + + if (mode != PolylineMode) + p.closeSubpath(); + drawPath(p); + + d->hasBrush = hb; +} + +void QPdfBaseEngine::drawPath (const QPainterPath &p) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawPath(p); + if (!continueCall()) + return; + } + + if (d->clipEnabled && d->allClipped) + return; + if (!d->hasPen && !d->hasBrush) + return; + + if (d->simplePen) { + // draw strokes natively in this case for better output + *d->currentPage << QPdf::generatePath(p, QTransform(), d->hasBrush ? QPdf::FillAndStrokePath : QPdf::StrokePath); + } else { + if (d->hasBrush) + *d->currentPage << QPdf::generatePath(p, d->stroker.matrix, QPdf::FillPath); + if (d->hasPen) { + *d->currentPage << "q\n"; + QBrush b = d->brush; + d->brush = d->pen.brush(); + setBrush(); + d->stroker.strokePath(p); + *d->currentPage << "Q\n"; + d->brush = b; + } + } +} + +void QPdfBaseEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawTextItem(p, textItem); + if (!continueCall()) + return; + } + + if (!d->hasPen || (d->clipEnabled && d->allClipped)) + return; + + if (d->stroker.matrix.type() >= QTransform::TxProject) { + QPaintEngine::drawTextItem(p, textItem); + return; + } + + *d->currentPage << "q\n"; + if(!d->simplePen) + *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); + + bool hp = d->hasPen; + d->hasPen = false; + QBrush b = d->brush; + d->brush = d->pen.brush(); + setBrush(); + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + Q_ASSERT(ti.fontEngine->type() != QFontEngine::Multi); + d->drawTextItem(p, ti); + d->hasPen = hp; + d->brush = b; + *d->currentPage << "Q\n"; +} + + +void QPdfBaseEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QPdfBaseEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::updateState(state); + if (!continueCall()) + return; + } + + QPaintEngine::DirtyFlags flags = state.state(); + + if (flags & DirtyTransform) + d->stroker.matrix = state.transform(); + + if (flags & DirtyPen) { + d->pen = state.pen(); + d->hasPen = d->pen.style() != Qt::NoPen; + d->stroker.setPen(d->pen); + QBrush penBrush = d->pen.brush(); + bool oldSimple = d->simplePen; + d->simplePen = (d->hasPen && (penBrush.style() == Qt::SolidPattern) && penBrush.isOpaque()); + if (oldSimple != d->simplePen) + flags |= DirtyTransform; + } + if (flags & DirtyBrush) { + d->brush = state.brush(); + if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern) + d->brush.setStyle(Qt::NoBrush); + d->hasBrush = d->brush.style() != Qt::NoBrush; + } + if (flags & DirtyBrushOrigin) { + d->brushOrigin = state.brushOrigin(); + flags |= DirtyBrush; + } + if (flags & DirtyOpacity) + d->opacity = state.opacity(); + + bool ce = d->clipEnabled; + if (flags & DirtyClipPath) { + d->clipEnabled = true; + updateClipPath(state.clipPath(), state.clipOperation()); + } else if (flags & DirtyClipRegion) { + d->clipEnabled = true; + QPainterPath path; + QVector<QRect> rects = state.clipRegion().rects(); + for (int i = 0; i < rects.size(); ++i) + path.addRect(rects.at(i)); + updateClipPath(path, state.clipOperation()); + flags |= DirtyClipPath; + } else if (flags & DirtyClipEnabled) { + d->clipEnabled = state.isClipEnabled(); + } + + if (ce != d->clipEnabled) + flags |= DirtyClipPath; + else if (!d->clipEnabled) + flags &= ~DirtyClipPath; + + setupGraphicsState(flags); +} + +void QPdfBaseEngine::setupGraphicsState(QPaintEngine::DirtyFlags flags) +{ + Q_D(QPdfBaseEngine); + if (flags & DirtyClipPath) + flags |= DirtyTransform|DirtyPen|DirtyBrush; + + if (flags & DirtyTransform) { + *d->currentPage << "Q\n"; + flags |= DirtyPen|DirtyBrush; + } + + if (flags & DirtyClipPath) { + *d->currentPage << "Q q\n"; + + d->allClipped = false; + if (d->clipEnabled && !d->clips.isEmpty()) { + for (int i = 0; i < d->clips.size(); ++i) { + if (d->clips.at(i).isEmpty()) { + d->allClipped = true; + break; + } + } + if (!d->allClipped) { + for (int i = 0; i < d->clips.size(); ++i) { + *d->currentPage << QPdf::generatePath(d->clips.at(i), QTransform(), QPdf::ClipPath); + } + } + } + } + + if (flags & DirtyTransform) { + *d->currentPage << "q\n"; + if (d->simplePen && !d->stroker.matrix.isIdentity()) + *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); + } + if (flags & DirtyBrush) + setBrush(); + if (d->simplePen && (flags & DirtyPen)) + setPen(); +} + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +void QPdfBaseEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) +{ + Q_D(QPdfBaseEngine); + QPainterPath path = d->stroker.matrix.map(p); + //qDebug() << "updateClipPath: " << d->stroker.matrix << p.boundingRect() << path.boundingRect() << op; + + if (op == Qt::NoClip) { + d->clipEnabled = false; + d->clips.clear(); + } else if (op == Qt::ReplaceClip) { + d->clips.clear(); + d->clips.append(path); + } else if (op == Qt::IntersectClip) { + d->clips.append(path); + } else { // UniteClip + // ask the painter for the current clipping path. that's the easiest solution + path = painter()->clipPath(); + path = d->stroker.matrix.map(path); + d->clips.clear(); + d->clips.append(path); + } + + if (d->useAlphaEngine) { + // if we have an alpha region, we have to subtract that from the + // any existing clip region since that region will be filled in + // later with images + QPainterPath alphaClip = qt_regionToPath(alphaClipping()); + if (!alphaClip.isEmpty()) { + if (!d->clipEnabled) { + QRect r = d->fullPage ? d->paperRect() : d->pageRect(); + QPainterPath dev; + dev.addRect(QRect(0, 0, r.width(), r.height())); + if (path.isEmpty()) + path = dev; + else + path = path.intersected(dev); + d->clipEnabled = true; + } else { + path = painter()->clipPath(); + path = d->stroker.matrix.map(path); + } + path = path.subtracted(alphaClip); + d->clips.clear(); + d->clips.append(path); + } + } +} + +void QPdfBaseEngine::setPen() +{ + Q_D(QPdfBaseEngine); + if (d->pen.style() == Qt::NoPen) + return; + QBrush b = d->pen.brush(); + Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque()); + + QColor rgba = b.color(); + if (d->colorMode == QPrinter::GrayScale) { + qreal gray = qGray(rgba.rgba())/255.; + *d->currentPage << gray << gray << gray; + } else { + *d->currentPage << rgba.redF() + << rgba.greenF() + << rgba.blueF(); + } + *d->currentPage << "SCN\n"; + + *d->currentPage << d->pen.widthF() << "w "; + + int pdfCapStyle = 0; + switch(d->pen.capStyle()) { + case Qt::FlatCap: + pdfCapStyle = 0; + break; + case Qt::SquareCap: + pdfCapStyle = 2; + break; + case Qt::RoundCap: + pdfCapStyle = 1; + break; + default: + break; + } + *d->currentPage << pdfCapStyle << "J "; + + int pdfJoinStyle = 0; + switch(d->pen.joinStyle()) { + case Qt::MiterJoin: + pdfJoinStyle = 0; + break; + case Qt::BevelJoin: + pdfJoinStyle = 2; + break; + case Qt::RoundJoin: + pdfJoinStyle = 1; + break; + default: + break; + } + *d->currentPage << pdfJoinStyle << "j "; + + *d->currentPage << QPdf::generateDashes(d->pen) << " 0 d\n"; +} + +bool QPdfBaseEngine::newPage() +{ + Q_D(QPdfBaseEngine); + setupGraphicsState(DirtyBrush|DirtyPen|DirtyClipPath); + QFile *outfile = qobject_cast<QFile*> (d->outDevice); + if (outfile && outfile->error() != QFile::NoError) + return false; + return true; +} + + +int QPdfBaseEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const +{ + Q_D(const QPdfBaseEngine); + int val; + QRect r = d->fullPage ? d->paperRect() : d->pageRect(); + switch (metricType) { + case QPaintDevice::PdmWidth: + val = r.width(); + break; + case QPaintDevice::PdmHeight: + val = r.height(); + break; + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmDpiY: + val = d->resolution; + break; + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: + val = 1200; + break; + case QPaintDevice::PdmWidthMM: + val = qRound(r.width()*25.4/d->resolution); + break; + case QPaintDevice::PdmHeightMM: + val = qRound(r.height()*25.4/d->resolution); + break; + case QPaintDevice::PdmNumColors: + val = INT_MAX; + break; + case QPaintDevice::PdmDepth: + val = 32; + break; + default: + qWarning("QPrinter::metric: Invalid metric command"); + return 0; + } + return val; +} + +void QPdfBaseEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QPdfBaseEngine); + switch (int(key)) { + case PPK_CollateCopies: + d->collate = value.toBool(); + break; + case PPK_ColorMode: + d->colorMode = QPrinter::ColorMode(value.toInt()); + break; + case PPK_Creator: + d->creator = value.toString(); + break; + case PPK_DocumentName: + d->title = value.toString(); + break; + case PPK_FullPage: + d->fullPage = value.toBool(); + break; + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + d->copies = value.toInt(); + break; + case PPK_Orientation: + d->orientation = QPrinter::Orientation(value.toInt()); + break; + case PPK_OutputFileName: + d->outputFileName = value.toString(); + break; + case PPK_PageOrder: + d->pageOrder = QPrinter::PageOrder(value.toInt()); + break; + case PPK_PaperSize: + d->paperSize = QPrinter::PaperSize(value.toInt()); + break; + case PPK_PaperSource: + d->paperSource = QPrinter::PaperSource(value.toInt()); + break; + case PPK_PrinterName: + d->printerName = value.toString(); + break; + case PPK_PrinterProgram: + d->printProgram = value.toString(); + break; + case PPK_Resolution: + d->resolution = value.toInt(); + break; + case PPK_SelectionOption: + d->selectionOption = value.toString(); + break; + case PPK_FontEmbedding: + d->embedFonts = value.toBool(); + break; + case PPK_Duplex: + d->duplex = static_cast<QPrinter::DuplexMode> (value.toInt()); + break; + case PPK_CupsPageRect: + d->cupsPageRect = value.toRect(); + break; + case PPK_CupsPaperRect: + d->cupsPaperRect = value.toRect(); + break; + case PPK_CupsOptions: + d->cupsOptions = value.toStringList(); + break; + case PPK_CupsStringPageSize: + d->cupsStringPageSize = value.toString(); + break; + case PPK_CustomPaperSize: + d->paperSize = QPrinter::Custom; + d->customPaperSize = value.toSizeF(); + break; + case PPK_PageMargins: + { + QList<QVariant> margins(value.toList()); + Q_ASSERT(margins.size() == 4); + d->leftMargin = margins.at(0).toReal(); + d->topMargin = margins.at(1).toReal(); + d->rightMargin = margins.at(2).toReal(); + d->bottomMargin = margins.at(3).toReal(); + d->hasCustomPageMargins = true; + break; + } + default: + break; + } +} + +QVariant QPdfBaseEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QPdfBaseEngine); + + QVariant ret; + switch (int(key)) { + case PPK_CollateCopies: + ret = d->collate; + break; + case PPK_ColorMode: + ret = d->colorMode; + break; + case PPK_Creator: + ret = d->creator; + break; + case PPK_DocumentName: + ret = d->title; + break; + case PPK_FullPage: + ret = d->fullPage; + break; + case PPK_CopyCount: + ret = d->copies; + break; + case PPK_SupportsMultipleCopies: +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) + ret = true; + else +#endif + ret = false; + break; + case PPK_NumberOfCopies: +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) + ret = 1; + else +#endif + ret = d->copies; + break; + case PPK_Orientation: + ret = d->orientation; + break; + case PPK_OutputFileName: + ret = d->outputFileName; + break; + case PPK_PageOrder: + ret = d->pageOrder; + break; + case PPK_PaperSize: + ret = d->paperSize; + break; + case PPK_PaperSource: + ret = d->paperSource; + break; + case PPK_PrinterName: + ret = d->printerName; + break; + case PPK_PrinterProgram: + ret = d->printProgram; + break; + case PPK_Resolution: + ret = d->resolution; + break; + case PPK_SupportedResolutions: + ret = QList<QVariant>() << 72; + break; + case PPK_PaperRect: + ret = d->paperRect(); + break; + case PPK_PageRect: + ret = d->pageRect(); + break; + case PPK_SelectionOption: + ret = d->selectionOption; + break; + case PPK_FontEmbedding: + ret = d->embedFonts; + break; + case PPK_Duplex: + ret = d->duplex; + break; + case PPK_CupsPageRect: + ret = d->cupsPageRect; + break; + case PPK_CupsPaperRect: + ret = d->cupsPaperRect; + break; + case PPK_CupsOptions: + ret = d->cupsOptions; + break; + case PPK_CupsStringPageSize: + ret = d->cupsStringPageSize; + break; + case PPK_CustomPaperSize: + ret = d->customPaperSize; + break; + case PPK_PageMargins: + { + QList<QVariant> margins; + if (d->hasCustomPageMargins) { + margins << d->leftMargin << d->topMargin + << d->rightMargin << d->bottomMargin; + } else { + const qreal defaultMargin = 10; // ~3.5 mm + margins << defaultMargin << defaultMargin + << defaultMargin << defaultMargin; + } + ret = margins; + break; + } + default: + break; + } + return ret; +} + +QPdfBaseEnginePrivate::QPdfBaseEnginePrivate(QPrinter::PrinterMode m) + : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false), + useAlphaEngine(false), + outDevice(0), fd(-1), + duplex(QPrinter::DuplexNone), collate(false), fullPage(false), embedFonts(true), copies(1), + pageOrder(QPrinter::FirstPageFirst), orientation(QPrinter::Portrait), + paperSize(QPrinter::A4), colorMode(QPrinter::Color), paperSource(QPrinter::Auto), + hasCustomPageMargins(false), + leftMargin(0), topMargin(0), rightMargin(0), bottomMargin(0) +{ + resolution = 72; + if (m == QPrinter::HighResolution) + resolution = 1200; + else if (m == QPrinter::ScreenResolution) + resolution = qt_defaultDpi(); + + postscript = false; + currentObject = 1; + currentPage = 0; + stroker.stream = 0; +} + +bool QPdfBaseEngine::begin(QPaintDevice *pdev) +{ + Q_D(QPdfBaseEngine); + d->pdev = pdev; + + d->postscript = false; + d->currentObject = 1; + + d->currentPage = new QPdfPage; + d->stroker.stream = d->currentPage; + d->opacity = 1.0; + + return d->openPrintDevice(); +} + +bool QPdfBaseEngine::end() +{ + Q_D(QPdfBaseEngine); + qDeleteAll(d->fonts); + d->fonts.clear(); + delete d->currentPage; + d->currentPage = 0; + + d->closePrintDevice(); + return true; +} + +#ifndef QT_NO_LPR +static void closeAllOpenFds() +{ + // hack time... getting the maximum number of open + // files, if possible. if not we assume it's the + // larger of 256 and the fd we got + int i; +#if defined(_SC_OPEN_MAX) + i = (int)sysconf(_SC_OPEN_MAX); +#elif defined(_POSIX_OPEN_MAX) + i = (int)_POSIX_OPEN_MAX; +#elif defined(OPEN_MAX) + i = (int)OPEN_MAX; +#else + i = 256; +#endif + // leave stdin/out/err untouched + while(--i > 2) + QT_CLOSE(i); +} +#endif + +bool QPdfBaseEnginePrivate::openPrintDevice() +{ + if(outDevice) + return false; + + if (!outputFileName.isEmpty()) { + QFile *file = new QFile(outputFileName); + if (! file->open(QFile::WriteOnly|QFile::Truncate)) { + delete file; + return false; + } + outDevice = file; +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + } else if (QCUPSSupport::isAvailable()) { + QCUPSSupport cups; + QPair<int, QString> ret = cups.tempFd(); + if (ret.first < 0) { + qWarning("QPdfPrinter: Could not open temporary file to print"); + return false; + } + cupsTempFile = ret.second; + outDevice = new QFile(); + static_cast<QFile *>(outDevice)->open(ret.first, QIODevice::WriteOnly); +#endif +#ifndef QT_NO_LPR + } else { + QString pr; + if (!printerName.isEmpty()) + pr = printerName; + int fds[2]; + if (qt_safe_pipe(fds) != 0) { + qWarning("QPdfPrinter: Could not open pipe to print"); + return false; + } + + pid_t pid = fork(); + if (pid == 0) { // child process + // if possible, exit quickly, so the actual lp/lpr + // becomes a child of init, and ::waitpid() is + // guaranteed not to wait. + if (fork() > 0) { + closeAllOpenFds(); + + // try to replace this process with "true" - this prevents + // global destructors from being called (that could possibly + // do wrong things to the parent process) + (void)execlp("true", "true", (char *)0); + (void)execl("/bin/true", "true", (char *)0); + (void)execl("/usr/bin/true", "true", (char *)0); + ::_exit(0); + } + qt_safe_dup2(fds[0], 0, 0); + + closeAllOpenFds(); + + if (!printProgram.isEmpty()) { + if (!selectionOption.isEmpty()) + pr.prepend(selectionOption); + else + pr.prepend(QLatin1String("-P")); + (void)execlp(printProgram.toLocal8Bit().data(), printProgram.toLocal8Bit().data(), + pr.toLocal8Bit().data(), (char *)0); + } else { + // if no print program has been specified, be smart + // about the option string too. + QList<QByteArray> lprhack; + QList<QByteArray> lphack; + QByteArray media; + if (!pr.isEmpty() || !selectionOption.isEmpty()) { + if (!selectionOption.isEmpty()) { + QStringList list = selectionOption.split(QLatin1Char(' ')); + for (int i = 0; i < list.size(); ++i) + lprhack.append(list.at(i).toLocal8Bit()); + lphack = lprhack; + } else { + lprhack.append("-P"); + lphack.append("-d"); + } + lprhack.append(pr.toLocal8Bit()); + lphack.append(pr.toLocal8Bit()); + } + lphack.append("-s"); + + char ** lpargs = new char *[lphack.size()+6]; + char lp[] = "lp"; + lpargs[0] = lp; + int i; + for (i = 0; i < lphack.size(); ++i) + lpargs[i+1] = (char *)lphack.at(i).constData(); +#ifndef Q_OS_OSF + if (QPdf::paperSizeToString(paperSize)) { + char dash_o[] = "-o"; + lpargs[++i] = dash_o; + lpargs[++i] = const_cast<char *>(QPdf::paperSizeToString(paperSize)); + lpargs[++i] = dash_o; + media = "media="; + media += QPdf::paperSizeToString(paperSize); + lpargs[++i] = media.data(); + } +#endif + lpargs[++i] = 0; + char **lprargs = new char *[lprhack.size()+2]; + char lpr[] = "lpr"; + lprargs[0] = lpr; + for (int i = 0; i < lprhack.size(); ++i) + lprargs[i+1] = (char *)lprhack[i].constData(); + lprargs[lprhack.size() + 1] = 0; + (void)execvp("lp", lpargs); + (void)execvp("lpr", lprargs); + (void)execv("/bin/lp", lpargs); + (void)execv("/bin/lpr", lprargs); + (void)execv("/usr/bin/lp", lpargs); + (void)execv("/usr/bin/lpr", lprargs); + + delete []lpargs; + delete []lprargs; + } + // if we couldn't exec anything, close the fd, + // wait for a second so the parent process (the + // child of the GUI process) has exited. then + // exit. + QT_CLOSE(0); + (void)::sleep(1); + ::_exit(0); + } + // parent process + QT_CLOSE(fds[0]); + fd = fds[1]; + (void)qt_safe_waitpid(pid, 0, 0); + + if (fd < 0) + return false; + + outDevice = new QFile(); + static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly); +#endif + } + + return true; +} + +void QPdfBaseEnginePrivate::closePrintDevice() +{ + if (!outDevice) + return; + outDevice->close(); + if (fd >= 0) +#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400 + ::_close(fd); +#else + ::close(fd); +#endif + fd = -1; + delete outDevice; + outDevice = 0; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (!cupsTempFile.isEmpty()) { + QString tempFile = cupsTempFile; + cupsTempFile.clear(); + QCUPSSupport cups; + + // Set up print options. + QByteArray prnName; + QList<QPair<QByteArray, QByteArray> > options; + QVector<cups_option_t> cupsOptStruct; + + if (!printerName.isEmpty()) { + prnName = printerName.toLocal8Bit(); + } else { + QPrinterInfo def = QPrinterInfo::defaultPrinter(); + if (def.isNull()) { + qWarning("Could not determine printer to print to"); + QFile::remove(tempFile); + return; + } + prnName = def.printerName().toLocal8Bit(); + } + + if (!cupsStringPageSize.isEmpty()) { + options.append(QPair<QByteArray, QByteArray>("media", cupsStringPageSize.toLocal8Bit())); + } + + if (copies > 1) { + options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit())); + } + + if (collate) { + options.append(QPair<QByteArray, QByteArray>("Collate", "True")); + } + + if (duplex != QPrinter::DuplexNone) { + switch(duplex) { + case QPrinter::DuplexNone: break; + case QPrinter::DuplexAuto: + if (orientation == QPrinter::Portrait) + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); + else + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); + break; + case QPrinter::DuplexLongSide: + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); + break; + case QPrinter::DuplexShortSide: + options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); + break; + } + } + + if (QCUPSSupport::cupsVersion() >= 10300 && orientation == QPrinter::Landscape) { + options.append(QPair<QByteArray, QByteArray>("landscape", "")); + } + + QStringList::const_iterator it = cupsOptions.constBegin(); + while (it != cupsOptions.constEnd()) { + options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit())); + it += 2; + } + + for (int c = 0; c < options.size(); ++c) { + cups_option_t opt; + opt.name = options[c].first.data(); + opt.value = options[c].second.data(); + cupsOptStruct.append(opt); + } + + // Print the file. + cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0; + cups.printFile(prnName.constData(), tempFile.toLocal8Bit().constData(), + title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr); + + QFile::remove(tempFile); + } +#endif +} + +QPdfBaseEnginePrivate::~QPdfBaseEnginePrivate() +{ + qDeleteAll(fonts); + delete currentPage; +} + +void QPdfBaseEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) +{ + Q_Q(QPdfBaseEngine); + + QFontEngine *fe = ti.fontEngine; + + QFontEngine::FaceId face_id = fe->faceId(); + bool noEmbed = false; + if (face_id.filename.isEmpty() + || (!postscript && ((fe->fsType & 0x200) /* bitmap embedding only */ + || (fe->fsType == 2) /* no embedding allowed */))) { + *currentPage << "Q\n"; + q->QPaintEngine::drawTextItem(p, ti); + *currentPage << "q\n"; + if (face_id.filename.isEmpty()) + return; + noEmbed = true; + } + + QFontSubset *font = fonts.value(face_id, 0); + if (!font) { + font = new QFontSubset(fe, requestObject()); + font->noEmbed = noEmbed; + } + fonts.insert(face_id, font); + + if (!currentPage->fonts.contains(font->object_id)) + currentPage->fonts.append(font->object_id); + + qreal size = ti.fontEngine->fontDef.pixelSize; +#ifdef Q_WS_WIN + if (ti.fontEngine->type() == QFontEngine::Win) { + QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine); + size = fe->tm.tmHeight; + } +#endif + + QVarLengthArray<glyph_t> glyphs; + QVarLengthArray<QFixedPoint> positions; + QTransform m = QTransform::fromTranslate(p.x(), p.y()); + ti.fontEngine->getGlyphPositions(ti.glyphs, m, ti.flags, + glyphs, positions); + if (glyphs.size() == 0) + return; + int synthesized = ti.fontEngine->synthesized(); + qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; + + *currentPage << "BT\n" + << "/F" << font->object_id << size << "Tf " + << stretch << (synthesized & QFontEngine::SynthesizedItalic + ? "0 .3 -1 0 0 Tm\n" + : "0 0 -1 0 0 Tm\n"); + + +#if 0 + // #### implement actual text for complex languages + const unsigned short *logClusters = ti.logClusters; + int pos = 0; + do { + int end = pos + 1; + while (end < ti.num_chars && logClusters[end] == logClusters[pos]) + ++end; + *currentPage << "/Span << /ActualText <FEFF"; + for (int i = pos; i < end; ++i) { + s << toHex((ushort)ti.chars[i].unicode(), buf); + } + *currentPage << "> >>\n" + "BDC\n" + "<"; + int ge = end == ti.num_chars ? ti.num_glyphs : logClusters[end]; + for (int gs = logClusters[pos]; gs < ge; ++gs) + *currentPage << toHex((ushort)ti.glyphs[gs].glyph, buf); + *currentPage << "> Tj\n" + "EMC\n"; + pos = end; + } while (pos < ti.num_chars); +#else + qreal last_x = 0.; + qreal last_y = 0.; + for (int i = 0; i < glyphs.size(); ++i) { + qreal x = positions[i].x.toReal(); + qreal y = positions[i].y.toReal(); + if (synthesized & QFontEngine::SynthesizedItalic) + x += .3*y; + x /= stretch; + char buf[5]; + int g = font->addGlyph(glyphs[i]); + *currentPage << x - last_x << last_y - y << "Td <" + << QPdf::toHex((ushort)g, buf) << "> Tj\n"; + last_x = x; + last_y = y; + } + if (synthesized & QFontEngine::SynthesizedBold) { + *currentPage << stretch << (synthesized & QFontEngine::SynthesizedItalic + ? "0 .3 -1 0 0 Tm\n" + : "0 0 -1 0 0 Tm\n"); + *currentPage << "/Span << /ActualText <> >> BDC\n"; + last_x = 0.5*fe->lineThickness().toReal(); + last_y = 0.; + for (int i = 0; i < glyphs.size(); ++i) { + qreal x = positions[i].x.toReal(); + qreal y = positions[i].y.toReal(); + if (synthesized & QFontEngine::SynthesizedItalic) + x += .3*y; + x /= stretch; + char buf[5]; + int g = font->addGlyph(glyphs[i]); + *currentPage << x - last_x << last_y - y << "Td <" + << QPdf::toHex((ushort)g, buf) << "> Tj\n"; + last_x = x; + last_y = y; + } + *currentPage << "EMC\n"; + } +#endif + + *currentPage << "ET\n"; +} + +QRect QPdfBaseEnginePrivate::paperRect() const +{ + int w; + int h; + if (paperSize == QPrinter::Custom) { + w = qRound(customPaperSize.width()*resolution/72.); + h = qRound(customPaperSize.height()*resolution/72.); + } else { +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable() && !cupsPaperRect.isNull()) { + QRect r = cupsPaperRect; + w = r.width(); + h = r.height(); + } else +#endif + { + QPdf::PaperSize s = QPdf::paperSize(paperSize); + w = s.width; + h = s.height; + } + w = qRound(w*resolution/72.); + h = qRound(h*resolution/72.); + } + if (orientation == QPrinter::Portrait) + return QRect(0, 0, w, h); + else + return QRect(0, 0, h, w); +} + +QRect QPdfBaseEnginePrivate::pageRect() const +{ + if(fullPage) + return paperRect(); + + QRect r; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (!hasCustomPageMargins && QCUPSSupport::isAvailable() && !cupsPageRect.isNull()) { + r = cupsPageRect; + if (r == cupsPaperRect) { + // if cups doesn't define any margins, give it at least approx 3.5 mm + r = QRect(10, 10, r.width() - 20, r.height() - 20); + } + } else +#endif + { + QPdf::PaperSize s; + if (paperSize == QPrinter::Custom) { + s.width = qRound(customPaperSize.width()); + s.height = qRound(customPaperSize.height()); + } else { + s = QPdf::paperSize(paperSize); + } + if (hasCustomPageMargins) + r = QRect(0, 0, s.width, s.height); + else + r = QRect(72/3, 72/3, s.width - 2*72/3, s.height - 2*72/3); + } + + int x = qRound(r.left()*resolution/72.); + int y = qRound(r.top()*resolution/72.); + int w = qRound(r.width()*resolution/72.); + int h = qRound(r.height()*resolution/72.); + if (orientation == QPrinter::Portrait) + r = QRect(x, y, w, h); + else + r = QRect(y, x, h, w); + + if (hasCustomPageMargins) { + r.adjust(qRound(leftMargin*(resolution/72.)), + qRound(topMargin*(resolution/72.)), + -qRound(rightMargin*(resolution/72.)), + -qRound(bottomMargin*(resolution/72.))); + } + return r; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h new file mode 100644 index 0000000000..39e6c21620 --- /dev/null +++ b/src/gui/painting/qpdf_p.h @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPDF_P_H +#define QPDF_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 "QtGui/qmatrix.h" +#include "QtCore/qstring.h" +#include "QtCore/qvector.h" +#include "private/qstroker_p.h" +#include "private/qfontengine_p.h" +#include "QtGui/qprinter.h" +#include "private/qfontsubset_p.h" +#include "private/qpaintengine_alpha_p.h" +#include "qprintengine.h" +#include "qbuffer.h" + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +#define PPK_CupsOptions QPrintEngine::PrintEnginePropertyKey(0xfe00) +#define PPK_CupsPageRect QPrintEngine::PrintEnginePropertyKey(0xfe01) +#define PPK_CupsPaperRect QPrintEngine::PrintEnginePropertyKey(0xfe02) +#define PPK_CupsStringPageSize QPrintEngine::PrintEnginePropertyKey(0xfe03) + +const char *qt_real_to_string(qreal val, char *buf); +const char *qt_int_to_string(int val, char *buf); + +namespace QPdf { + + class ByteStream + { + public: + // fileBacking means that ByteStream will buffer the contents on disk + // if the size exceeds a certain threshold. In this case, if a byte + // array was passed in, its contents may no longer correspond to the + // ByteStream contents. + explicit ByteStream(bool fileBacking = false); + explicit ByteStream(QByteArray *ba, bool fileBacking = false); + ~ByteStream(); + ByteStream &operator <<(char chr); + ByteStream &operator <<(const char *str); + ByteStream &operator <<(const QByteArray &str); + ByteStream &operator <<(const ByteStream &src); + ByteStream &operator <<(qreal val); + ByteStream &operator <<(int val); + ByteStream &operator <<(const QPointF &p); + // Note that the stream may be invalidated by calls that insert data. + QIODevice *stream(); + void clear(); + + static inline int maxMemorySize() { return 100000000; } + static inline int chunkSize() { return 10000000; } + + protected: + void constructor_helper(QIODevice *dev); + void constructor_helper(QByteArray *ba); + + private: + void prepareBuffer(); + + private: + QIODevice *dev; + QByteArray ba; + bool fileBackingEnabled; + bool fileBackingActive; + bool handleDirty; + }; + + enum PathFlags { + ClipPath, + FillPath, + StrokePath, + FillAndStrokePath + }; + QByteArray generatePath(const QPainterPath &path, const QTransform &matrix, PathFlags flags); + QByteArray generateMatrix(const QTransform &matrix); + QByteArray generateDashes(const QPen &pen); + QByteArray patternForBrush(const QBrush &b); +#ifdef USE_NATIVE_GRADIENTS + QByteArray generateLinearGradientShader(const QLinearGradient *lg, const QPointF *page_rect, bool alpha = false); +#endif + + struct Stroker { + Stroker(); + void setPen(const QPen &pen); + void strokePath(const QPainterPath &path); + ByteStream *stream; + bool first; + QTransform matrix; + bool cosmeticPen; + private: + QStroker basicStroker; + QDashStroker dashStroker; + QStrokerOps *stroker; + }; + + QByteArray ascii85Encode(const QByteArray &input); + + const char *toHex(ushort u, char *buffer); + const char *toHex(uchar u, char *buffer); + + + struct PaperSize { + int width, height; // in postscript points + }; + PaperSize paperSize(QPrinter::PaperSize paperSize); + const char *paperSizeToString(QPrinter::PaperSize paperSize); + +} + + +class QPdfPage : public QPdf::ByteStream +{ +public: + QPdfPage(); + + QVector<uint> images; + QVector<uint> graphicStates; + QVector<uint> patterns; + QVector<uint> fonts; + QVector<uint> annotations; + + void streamImage(int w, int h, int object); + + QSize pageSize; +private: +}; + + +class QPdfBaseEnginePrivate; + +class QPdfBaseEngine : public QAlphaPaintEngine, public QPrintEngine +{ + Q_DECLARE_PRIVATE(QPdfBaseEngine) +public: + QPdfBaseEngine(QPdfBaseEnginePrivate &d, PaintEngineFeatures f); + ~QPdfBaseEngine() {} + + // reimplementations QPaintEngine + bool begin(QPaintDevice *pdev); + bool end(); + + void drawPoints(const QPointF *points, int pointCount); + void drawLines(const QLineF *lines, int lineCount); + void drawRects(const QRectF *rects, int rectCount); + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawPath (const QPainterPath & path); + + void drawTextItem(const QPointF &p, const QTextItem &textItem); + + void updateState(const QPaintEngineState &state); + + int metric(QPaintDevice::PaintDeviceMetric metricType) const; + // end reimplementations QPaintEngine + + // Printer stuff... + bool newPage(); + void setProperty(PrintEnginePropertyKey key, const QVariant &value); + QVariant property(PrintEnginePropertyKey key) const; + + void setPen(); + virtual void setBrush() = 0; + void setupGraphicsState(QPaintEngine::DirtyFlags flags); + +private: + void updateClipPath(const QPainterPath & path, Qt::ClipOperation op); +}; + +class QPdfBaseEnginePrivate : public QAlphaPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QPdfBaseEngine) +public: + QPdfBaseEnginePrivate(QPrinter::PrinterMode m); + ~QPdfBaseEnginePrivate(); + + bool openPrintDevice(); + void closePrintDevice(); + + + virtual void drawTextItem(const QPointF &p, const QTextItemInt &ti); + inline uint requestObject() { return currentObject++; } + + QRect paperRect() const; + QRect pageRect() const; + + bool postscript; + int currentObject; + + QPdfPage* currentPage; + QPdf::Stroker stroker; + + QPointF brushOrigin; + QBrush brush; + QPen pen; + QList<QPainterPath> clips; + bool clipEnabled; + bool allClipped; + bool hasPen; + bool hasBrush; + bool simplePen; + qreal opacity; + bool useAlphaEngine; + + QHash<QFontEngine::FaceId, QFontSubset *> fonts; + + QPaintDevice *pdev; + + // the device the output is in the end streamed to. + QIODevice *outDevice; + int fd; + + // printer options + QString outputFileName; + QString printerName; + QString printProgram; + QString selectionOption; + QString title; + QString creator; + QPrinter::DuplexMode duplex; + bool collate; + bool fullPage; + bool embedFonts; + int copies; + int resolution; + QPrinter::PageOrder pageOrder; + QPrinter::Orientation orientation; + QPrinter::PaperSize paperSize; + QPrinter::ColorMode colorMode; + QPrinter::PaperSource paperSource; + + QStringList cupsOptions; + QRect cupsPaperRect; + QRect cupsPageRect; + QString cupsStringPageSize; + QSizeF customPaperSize; // in postscript points + bool hasCustomPageMargins; + qreal leftMargin, topMargin, rightMargin, bottomMargin; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + QString cupsTempFile; +#endif +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPDF_P_H + diff --git a/src/gui/painting/qpen.cpp b/src/gui/painting/qpen.cpp new file mode 100644 index 0000000000..b69458dca5 --- /dev/null +++ b/src/gui/painting/qpen.cpp @@ -0,0 +1,1032 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qpen.h" +#include "qpen_p.h" +#include "qdatastream.h" +#include "qvariant.h" +#include "qbrush.h" + +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +typedef QPenPrivate QPenData; + +/*! + \class QPen + \ingroup painting + \ingroup shared + + + \brief The QPen class defines how a QPainter should draw lines and outlines + of shapes. + + A pen has a style(), width(), brush(), capStyle() and joinStyle(). + + The pen style defines the line type. The brush is used to fill + strokes generated with the pen. Use the QBrush class to specify + fill styles. The cap style determines the line end caps that can + be drawn using QPainter, while the join style describes how joins + between two lines are drawn. The pen width can be specified in + both integer (width()) and floating point (widthF()) precision. A + line width of zero indicates a cosmetic pen. This means that the + pen width is always drawn one pixel wide, independent of the \l + {QPainter#Coordinate Transformations}{transformation} set on the + painter. + + The various settings can easily be modified using the + corresponding setStyle(), setWidth(), setBrush(), setCapStyle() + and setJoinStyle() functions (note that the painter's pen must be + reset when altering the pen's properties). + + For example: + + \snippet doc/src/snippets/code/src_gui_painting_qpen.cpp 0 + + which is equivalent to + + \snippet doc/src/snippets/code/src_gui_painting_qpen.cpp 1 + + The default pen is a solid black brush with 0 width, square + cap style (Qt::SquareCap), and bevel join style (Qt::BevelJoin). + + In addition QPen provides the color() and setColor() + convenience functions to extract and set the color of the pen's + brush, respectively. Pens may also be compared and streamed. + + For more information about painting in general, see the \l{Paint + System} documentation. + + \tableofcontents + + \section1 Pen Style + + Qt provides several built-in styles represented by the + Qt::PenStyle enum: + + \table + \row + \o \inlineimage qpen-solid.png + \o \inlineimage qpen-dash.png + \o \inlineimage qpen-dot.png + \row + \o Qt::SolidLine + \o Qt::DashLine + \o Qt::DotLine + \row + \o \inlineimage qpen-dashdot.png + \o \inlineimage qpen-dashdotdot.png + \o \inlineimage qpen-custom.png + \row + \o Qt::DashDotLine + \o Qt::DashDotDotLine + \o Qt::CustomDashLine + \endtable + + Simply use the setStyle() function to convert the pen style to + either of the built-in styles, except the Qt::CustomDashLine style + which we will come back to shortly. Setting the style to Qt::NoPen + tells the painter to not draw lines or outlines. The default pen + style is Qt::SolidLine. + + Since Qt 4.1 it is also possible to specify a custom dash pattern + using the setDashPattern() function which implicitly converts the + style of the pen to Qt::CustomDashLine. The pattern argument, a + QVector, must be specified as an even number of \l qreal entries + where the entries 1, 3, 5... are the dashes and 2, 4, 6... are the + spaces. For example, the custom pattern shown above is created + using the following code: + + \snippet doc/src/snippets/code/src_gui_painting_qpen.cpp 2 + + Note that the dash pattern is specified in units of the pens + width, e.g. a dash of length 5 in width 10 is 50 pixels long. + + The currently set dash pattern can be retrieved using the + dashPattern() function. Use the isSolid() function to determine + whether the pen has a solid fill, or not. + + \section1 Cap Style + + The cap style defines how the end points of lines are drawn using + QPainter. The cap style only apply to wide lines, i.e. when the + width is 1 or greater. The Qt::PenCapStyle enum provides the + following styles: + + \table + \row + \o \inlineimage qpen-square.png + \o \inlineimage qpen-flat.png + \o \inlineimage qpen-roundcap.png + \row + \o Qt::SquareCap + \o Qt::FlatCap + \o Qt::RoundCap + \endtable + + The Qt::SquareCap style is a square line end that covers the end + point and extends beyond it by half the line width. The + Qt::FlatCap style is a square line end that does not cover the end + point of the line. And the Qt::RoundCap style is a rounded line + end covering the end point. + + The default is Qt::SquareCap. + + Whether or not end points are drawn when the pen width is 0 or 1 + depends on the cap style. Using Qt::SquareCap or Qt::RoundCap they + are drawn, using Qt::FlatCap they are not drawn. + + \section1 Join Style + + The join style defines how joins between two connected lines can + be drawn using QPainter. The join style only apply to wide lines, + i.e. when the width is 1 or greater. The Qt::PenJoinStyle enum + provides the following styles: + + \table + \row + \o \inlineimage qpen-bevel.png + \o \inlineimage qpen-miter.png + \o \inlineimage qpen-roundjoin.png + \row + \o Qt::BevelJoin + \o Qt::MiterJoin + \o Qt::RoundJoin + \endtable + + The Qt::BevelJoin style fills the triangular notch between the two + lines. The Qt::MiterJoin style extends the lines to meet at an + angle. And the Qt::RoundJoin style fills a circular arc between + the two lines. + + The default is Qt::BevelJoin. + + \image qpen-miterlimit.png + + When the Qt::MiterJoin style is applied, it is possible to use the + setMiterLimit() function to specify how far the miter join can + extend from the join point. The miterLimit() is used to reduce + artifacts between line joins where the lines are close to + parallel. + + The miterLimit() must be specified in units of the pens width, + e.g. a miter limit of 5 in width 10 is 50 pixels long. The + default miter limit is 2, i.e. twice the pen width in pixels. + + \table 100% + \row + \o \inlineimage qpen-demo.png + \o \bold {\l {demos/pathstroke}{The Path Stroking Demo}} + + The Path Stroking demo shows Qt's built-in dash patterns and shows + how custom patterns can be used to extend the range of available + patterns. + \endtable + + \sa QPainter, QBrush, {demos/pathstroke}{Path Stroking Demo}, + {Scribble Example} +*/ + +/*! + \internal +*/ +inline QPenPrivate::QPenPrivate(const QBrush &_brush, qreal _width, Qt::PenStyle penStyle, + Qt::PenCapStyle _capStyle, Qt::PenJoinStyle _joinStyle) + : dashOffset(0), miterLimit(2), + cosmetic(false) +{ + ref = 1; + width = _width; + brush = _brush; + style = penStyle; + capStyle = _capStyle; + joinStyle = _joinStyle; +} + +static const Qt::PenCapStyle qpen_default_cap = Qt::SquareCap; +static const Qt::PenJoinStyle qpen_default_join = Qt::BevelJoin; + +#ifndef QT_NO_THREAD +// Special deleter that only deletes if the ref-count goes to zero +template <> +class QGlobalStaticDeleter<QPenPrivate> +{ +public: + QGlobalStatic<QPenPrivate> &globalStatic; + QGlobalStaticDeleter(QGlobalStatic<QPenPrivate> &_globalStatic) + : globalStatic(_globalStatic) + { } + + inline ~QGlobalStaticDeleter() + { + if (!globalStatic.pointer->ref.deref()) + delete globalStatic.pointer; + globalStatic.pointer = 0; + globalStatic.destroyed = true; + } +}; +#endif + +Q_GLOBAL_STATIC_WITH_ARGS(QPenData, defaultPenInstance, + (Qt::black, 0, Qt::SolidLine, qpen_default_cap, qpen_default_join)) +Q_GLOBAL_STATIC_WITH_ARGS(QPenData, nullPenInstance, + (Qt::black, 0, Qt::NoPen, qpen_default_cap, qpen_default_join)) + +/*! + Constructs a default black solid line pen with 0 width. +*/ + +QPen::QPen() +{ + d = defaultPenInstance(); + d->ref.ref(); +} + +/*! + Constructs a black pen with 0 width and the given \a style. + + \sa setStyle() +*/ + +QPen::QPen(Qt::PenStyle style) +{ + if (style == Qt::NoPen) { + d = nullPenInstance(); + d->ref.ref(); + } else { + d = new QPenData(Qt::black, 0, style, qpen_default_cap, qpen_default_join); + } +} + + +/*! + Constructs a solid line pen with 0 width and the given \a color. + + \sa setBrush(), setColor() +*/ + +QPen::QPen(const QColor &color) +{ + d = new QPenData(color, 0, Qt::SolidLine, qpen_default_cap, qpen_default_join); +} + + +/*! + \fn QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle style, Qt::PenCapStyle cap, Qt::PenJoinStyle join) + + Constructs a pen with the specified \a brush, \a width, pen \a style, + \a cap style and \a join style. + + \sa setBrush(), setWidth(), setStyle(), setCapStyle(), setJoinStyle() +*/ + +QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle s, Qt::PenCapStyle c, Qt::PenJoinStyle j) +{ + d = new QPenData(brush, width, s, c, j); +} + +/*! + \fn QPen::QPen(const QPen &pen) + + Constructs a pen that is a copy of the given \a pen. +*/ + +QPen::QPen(const QPen &p) +{ + d = p.d; + d->ref.ref(); +} + + +/*! + Destroys the pen. +*/ + +QPen::~QPen() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + \fn void QPen::detach() + Detaches from shared pen data to make sure that this pen is the + only one referring the data. + + If multiple pens share common data, this pen dereferences the data + and gets a copy of the data. Nothing is done if there is just a + single reference. +*/ + +void QPen::detach() +{ + if (d->ref == 1) + return; + + QPenData *x = new QPenData(*static_cast<QPenData *>(d)); + if (!d->ref.deref()) + delete d; + x->ref = 1; + d = x; +} + + +/*! + \fn QPen &QPen::operator=(const QPen &pen) + + Assigns the given \a pen to this pen and returns a reference to + this pen. +*/ + +QPen &QPen::operator=(const QPen &p) +{ + qAtomicAssign(d, p.d); + return *this; +} + +/*! + \fn void QPen::swap(QPen &other) + \since 4.8 + + Swaps pen \a other with this pen. This operation is very + fast and never fails. +*/ + +/*! + Returns the pen as a QVariant. +*/ +QPen::operator QVariant() const +{ + return QVariant(QVariant::Pen, this); +} + +/*! + \fn Qt::PenStyle QPen::style() const + + Returns the pen style. + + \sa setStyle(), {QPen#Pen Style}{Pen Style} +*/ +Qt::PenStyle QPen::style() const +{ + return d->style; +} +/*! + \fn void QPen::setStyle(Qt::PenStyle style) + + Sets the pen style to the given \a style. + + See the \l Qt::PenStyle documentation for a list of the available + styles. Since Qt 4.1 it is also possible to specify a custom dash + pattern using the setDashPattern() function which implicitly + converts the style of the pen to Qt::CustomDashLine. + + \note This function resets the dash offset to zero. + + \sa style(), {QPen#Pen Style}{Pen Style} +*/ + +void QPen::setStyle(Qt::PenStyle s) +{ + if (d->style == s) + return; + detach(); + d->style = s; + QPenData *dd = static_cast<QPenData *>(d); + dd->dashPattern.clear(); + dd->dashOffset = 0; +} + +/*! + Returns the dash pattern of this pen. + + \sa style(), isSolid() + */ +QVector<qreal> QPen::dashPattern() const +{ + QPenData *dd = static_cast<QPenData *>(d); + if (d->style == Qt::SolidLine || d->style == Qt::NoPen) { + return QVector<qreal>(); + } else if (dd->dashPattern.isEmpty()) { + const qreal space = 2; + const qreal dot = 1; + const qreal dash = 4; + + switch (d->style) { + case Qt::DashLine: + dd->dashPattern << dash << space; + break; + case Qt::DotLine: + dd->dashPattern << dot << space; + break; + case Qt::DashDotLine: + dd->dashPattern << dash << space << dot << space; + break; + case Qt::DashDotDotLine: + dd->dashPattern << dash << space << dot << space << dot << space; + break; + default: + break; + } + } + return dd->dashPattern; +} + +/*! + Sets the dash pattern for this pen to the given \a pattern. This + implicitly converts the style of the pen to Qt::CustomDashLine. + + The pattern must be specified as an even number of positive entries + where the entries 1, 3, 5... are the dashes and 2, 4, 6... are the + spaces. For example: + + \table 100% + \row + \o \inlineimage qpen-custom.png + \o + \snippet doc/src/snippets/code/src_gui_painting_qpen.cpp 3 + \endtable + + The dash pattern is specified in units of the pens width; e.g. a + dash of length 5 in width 10 is 50 pixels long. Note that a pen + with zero width is equivalent to a cosmetic pen with a width of 1 + pixel. + + Each dash is also subject to cap styles so a dash of 1 with square + cap set will extend 0.5 pixels out in each direction resulting in + a total width of 2. + + Note that the default cap style is Qt::SquareCap, meaning that a + square line end covers the end point and extends beyond it by half + the line width. + + \sa setStyle(), dashPattern(), setCapStyle(), setCosmetic() + */ +void QPen::setDashPattern(const QVector<qreal> &pattern) +{ + if (pattern.isEmpty()) + return; + detach(); + + QPenData *dd = static_cast<QPenData *>(d); + dd->dashPattern = pattern; + d->style = Qt::CustomDashLine; + + if ((dd->dashPattern.size() % 2) == 1) { + qWarning("QPen::setDashPattern: Pattern not of even length"); + dd->dashPattern << 1; + } +} + + +/*! + Returns the dash offset for the pen. + + \sa setDashOffset() +*/ +qreal QPen::dashOffset() const +{ + QPenData *dd = static_cast<QPenData *>(d); + return dd->dashOffset; +} +/*! + Sets the dash offset (the starting point on the dash pattern) for this pen + to the \a offset specified. The offset is measured in terms of the units used + to specify the dash pattern. + + \table + \row \o \inlineimage qpen-dashpattern.png + \o For example, a pattern where each stroke is four units long, followed by a gap + of two units, will begin with the stroke when drawn as a line. + + However, if the dash offset is set to 4.0, any line drawn will begin with the gap. + Values of the offset up to 4.0 will cause part of the stroke to be drawn first, + and values of the offset between 4.0 and 6.0 will cause the line to begin with + part of the gap. + \endtable + + \note This implicitly converts the style of the pen to Qt::CustomDashLine. +*/ +void QPen::setDashOffset(qreal offset) +{ + if (qFuzzyCompare(offset, static_cast<QPenData *>(d)->dashOffset)) + return; + detach(); + QPenData *dd = static_cast<QPenData *>(d); + dd->dashOffset = offset; + if (d->style != Qt::CustomDashLine) { + dd->dashPattern = dashPattern(); + d->style = Qt::CustomDashLine; + } +} + +/*! + Returns the miter limit of the pen. The miter limit is only + relevant when the join style is set to Qt::MiterJoin. + + \sa setMiterLimit(), {QPen#Join Style}{Join Style} +*/ +qreal QPen::miterLimit() const +{ + const QPenData *dd = static_cast<QPenData *>(d); + return dd->miterLimit; +} + +/*! + Sets the miter limit of this pen to the given \a limit. + + \image qpen-miterlimit.png + + The miter limit describes how far a miter join can extend from the + join point. This is used to reduce artifacts between line joins + where the lines are close to parallel. + + This value does only have effect when the pen style is set to + Qt::MiterJoin. The value is specified in units of the pen's width, + e.g. a miter limit of 5 in width 10 is 50 pixels long. The default + miter limit is 2, i.e. twice the pen width in pixels. + + \sa miterLimit(), setJoinStyle(), {QPen#Join Style}{Join Style} +*/ +void QPen::setMiterLimit(qreal limit) +{ + detach(); + QPenData *dd = static_cast<QPenData *>(d); + dd->miterLimit = limit; +} + + +/*! + \fn qreal QPen::width() const + + Returns the pen width with integer precision. + + \sa setWidth(), widthF() +*/ + +int QPen::width() const +{ + return qRound(d->width); +} + +/*! + \fn qreal QPen::widthF() const + + Returns the pen width with floating point precision. + + \sa setWidthF() width() +*/ +qreal QPen::widthF() const +{ + return d->width; +} + +/*! + \fn QPen::setWidth(int width) + + Sets the pen width to the given \a width in pixels with integer + precision. + + A line width of zero indicates a cosmetic pen. This means that the + pen width is always drawn one pixel wide, independent of the \l + {QPainter#Coordinate Transformations}{transformation} set on the + painter. + + Setting a pen width with a negative value is not supported. + + \sa setWidthF(), width() +*/ +void QPen::setWidth(int width) +{ + if (width < 0) + qWarning("QPen::setWidth: Setting a pen width with a negative value is not defined"); + if ((qreal)width == d->width) + return; + detach(); + d->width = width; +} + +/*! + Sets the pen width to the given \a width in pixels with floating point + precision. + + A line width of zero indicates a cosmetic pen. This means that the + pen width is always drawn one pixel wide, independent of the \l + {QPainter#Coordinate Transformations}{transformation} on the + painter. + + Setting a pen width with a negative value is not supported. + + \sa setWidth() widthF() +*/ + +void QPen::setWidthF(qreal width) +{ + if (width < 0.f) + qWarning("QPen::setWidthF: Setting a pen width with a negative value is not defined"); + if (qAbs(d->width - width) < 0.00000001f) + return; + detach(); + d->width = width; +} + + +/*! + Returns the pen's cap style. + + \sa setCapStyle(), {QPen#Cap Style}{Cap Style} +*/ +Qt::PenCapStyle QPen::capStyle() const +{ + return d->capStyle; +} + +/*! + \fn void QPen::setCapStyle(Qt::PenCapStyle style) + + Sets the pen's cap style to the given \a style. The default value + is Qt::SquareCap. + + \sa capStyle(), {QPen#Cap Style}{Cap Style} +*/ + +void QPen::setCapStyle(Qt::PenCapStyle c) +{ + if (d->capStyle == c) + return; + detach(); + d->capStyle = c; +} + +/*! + Returns the pen's join style. + + \sa setJoinStyle(), {QPen#Join Style}{Join Style} +*/ +Qt::PenJoinStyle QPen::joinStyle() const +{ + return d->joinStyle; +} + +/*! + \fn void QPen::setJoinStyle(Qt::PenJoinStyle style) + + Sets the pen's join style to the given \a style. The default value + is Qt::BevelJoin. + + \sa joinStyle(), {QPen#Join Style}{Join Style} +*/ + +void QPen::setJoinStyle(Qt::PenJoinStyle j) +{ + if (d->joinStyle == j) + return; + detach(); + d->joinStyle = j; +} + +/*! + \fn const QColor &QPen::color() const + + Returns the color of this pen's brush. + + \sa brush(), setColor() +*/ +QColor QPen::color() const +{ + return d->brush.color(); +} + +/*! + \fn void QPen::setColor(const QColor &color) + + Sets the color of this pen's brush to the given \a color. + + \sa setBrush(), color() +*/ + +void QPen::setColor(const QColor &c) +{ + detach(); + d->brush = QBrush(c); +} + + +/*! + Returns the brush used to fill strokes generated with this pen. +*/ +QBrush QPen::brush() const +{ + return d->brush; +} + +/*! + Sets the brush used to fill strokes generated with this pen to the given + \a brush. + + \sa brush(), setColor() +*/ +void QPen::setBrush(const QBrush &brush) +{ + detach(); + d->brush = brush; +} + + +/*! + Returns true if the pen has a solid fill, otherwise false. + + \sa style(), dashPattern() +*/ +bool QPen::isSolid() const +{ + return d->brush.style() == Qt::SolidPattern; +} + + +/*! + Returns true if the pen is cosmetic; otherwise returns false. + + Cosmetic pens are used to draw strokes that have a constant width + regardless of any transformations applied to the QPainter they are + used with. Drawing a shape with a cosmetic pen ensures that its + outline will have the same thickness at different scale factors. + + A zero width pen is cosmetic by default; pens with a non-zero width + are non-cosmetic. + + \sa setCosmetic(), widthF() +*/ + +bool QPen::isCosmetic() const +{ + QPenData *dd = static_cast<QPenData *>(d); + return (dd->cosmetic == true) || d->width == 0; +} + + +/*! + Sets this pen to cosmetic or non-cosmetic, depending on the value of + \a cosmetic. + + \sa isCosmetic() +*/ + +void QPen::setCosmetic(bool cosmetic) +{ + detach(); + QPenData *dd = static_cast<QPenData *>(d); + dd->cosmetic = cosmetic; +} + + + +/*! + \fn bool QPen::operator!=(const QPen &pen) const + + Returns true if the pen is different from the given \a pen; + otherwise false. Two pens are different if they have different + styles, widths or colors. + + \sa operator==() +*/ + +/*! + \fn bool QPen::operator==(const QPen &pen) const + + Returns true if the pen is equal to the given \a pen; otherwise + false. Two pens are equal if they have equal styles, widths and + colors. + + \sa operator!=() +*/ + +bool QPen::operator==(const QPen &p) const +{ + QPenData *dd = static_cast<QPenData *>(d); + QPenData *pdd = static_cast<QPenData *>(p.d); + return (p.d == d) + || (p.d->style == d->style + && p.d->capStyle == d->capStyle + && p.d->joinStyle == d->joinStyle + && p.d->width == d->width + && pdd->miterLimit == dd->miterLimit + && (d->style != Qt::CustomDashLine + || (qFuzzyCompare(pdd->dashOffset, dd->dashOffset) && + pdd->dashPattern == dd->dashPattern)) + && p.d->brush == d->brush + && pdd->cosmetic == dd->cosmetic); +} + + +/*! + \fn bool QPen::isDetached() + + \internal +*/ + +bool QPen::isDetached() +{ + return d->ref == 1; +} + + +/***************************************************************************** + QPen stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QPen &pen) + \relates QPen + + Writes the given \a pen to the given \a stream and returns a reference to + the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QPen &p) +{ + QPenData *dd = static_cast<QPenData *>(p.d); + if (s.version() < 3) { + s << (quint8)p.style(); + } else if (s.version() < QDataStream::Qt_4_3) { + s << (quint8)(p.style() | p.capStyle() | p.joinStyle()); + } else { + s << (quint16)(p.style() | p.capStyle() | p.joinStyle()); + s << (bool)(dd->cosmetic); + } + + if (s.version() < 7) { + s << (quint8)p.width(); + s << p.color(); + } else { + s << double(p.widthF()); + s << p.brush(); + s << double(p.miterLimit()); + if (sizeof(qreal) == sizeof(double)) { + s << p.dashPattern(); + } else { + // ensure that we write doubles here instead of streaming the pattern + // directly; otherwise, platforms that redefine qreal might generate + // data that cannot be read on other platforms. + QVector<qreal> pattern = p.dashPattern(); + s << quint32(pattern.size()); + for (int i = 0; i < pattern.size(); ++i) + s << double(pattern.at(i)); + } + if (s.version() >= 9) + s << double(p.dashOffset()); + } + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QPen &pen) + \relates QPen + + Reads a pen from the given \a stream into the given \a pen and + returns a reference to the \a stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QPen &p) +{ + quint16 style; + quint8 width8 = 0; + double width = 0; + QColor color; + QBrush brush; + double miterLimit = 2; + QVector<qreal> dashPattern; + double dashOffset = 0; + bool cosmetic = false; + if (s.version() < QDataStream::Qt_4_3) { + quint8 style8; + s >> style8; + style = style8; + } else { + s >> style; + s >> cosmetic; + } + if (s.version() < 7) { + s >> width8; + s >> color; + brush = color; + width = width8; + } else { + s >> width; + s >> brush; + s >> miterLimit; + if (sizeof(qreal) == sizeof(double)) { + s >> dashPattern; + } else { + quint32 numDashes; + s >> numDashes; + double dash; + for (quint32 i = 0; i < numDashes; ++i) { + s >> dash; + dashPattern << dash; + } + } + if (s.version() >= 9) + s >> dashOffset; + } + + p.detach(); + QPenData *dd = static_cast<QPenData *>(p.d); + dd->width = width; + dd->brush = brush; + dd->style = Qt::PenStyle(style & Qt::MPenStyle); + dd->capStyle = Qt::PenCapStyle(style & Qt::MPenCapStyle); + dd->joinStyle = Qt::PenJoinStyle(style & Qt::MPenJoinStyle); + dd->dashPattern = dashPattern; + dd->miterLimit = miterLimit; + dd->dashOffset = dashOffset; + dd->cosmetic = cosmetic; + + return s; +} +#endif //QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QPen &p) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + const char *PEN_STYLES[] = { + "NoPen", + "SolidLine", + "DashLine", + "DotLine", + "DashDotLine", + "DashDotDotLine", + "CustomDashLine" + }; + + dbg.nospace() << "QPen(" << p.width() << ',' << p.brush() + << ',' << PEN_STYLES[p.style()] << ',' << int(p.capStyle()) + << ',' << int(p.joinStyle()) << ',' << p.dashPattern() + << ',' << p.dashOffset() + << ',' << p.miterLimit() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QPen to QDebug"); + return dbg; + Q_UNUSED(p); +#endif +} +#endif + +/*! + \fn DataPtr &QPen::data_ptr() + \internal +*/ + +/*! + \typedef QPen::DataPtr + + \internal +*/ + +QT_END_NAMESPACE + +#undef QT_COMPILING_QPEN diff --git a/src/gui/painting/qpen.h b/src/gui/painting/qpen.h new file mode 100644 index 0000000000..001cd448e0 --- /dev/null +++ b/src/gui/painting/qpen.h @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPEN_H +#define QPEN_H + +#include <QtGui/qcolor.h> +#include <QtGui/qbrush.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVariant; +class QPenPrivate; +class QBrush; +class QPen; + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPen &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPen &); +#endif + +class Q_GUI_EXPORT QPen +{ +public: + QPen(); + QPen(Qt::PenStyle); + QPen(const QColor &color); + QPen(const QBrush &brush, qreal width, Qt::PenStyle s = Qt::SolidLine, + Qt::PenCapStyle c = Qt::SquareCap, Qt::PenJoinStyle j = Qt::BevelJoin); + QPen(const QPen &pen); + + ~QPen(); + + QPen &operator=(const QPen &pen); +#ifdef Q_COMPILER_RVALUE_REFS + inline QPen &operator=(QPen &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QPen &other) { qSwap(d, other.d); } + + Qt::PenStyle style() const; + void setStyle(Qt::PenStyle); + + QVector<qreal> dashPattern() const; + void setDashPattern(const QVector<qreal> &pattern); + + qreal dashOffset() const; + void setDashOffset(qreal doffset); + + qreal miterLimit() const; + void setMiterLimit(qreal limit); + + qreal widthF() const; + void setWidthF(qreal width); + + int width() const; + void setWidth(int width); + + QColor color() const; + void setColor(const QColor &color); + + QBrush brush() const; + void setBrush(const QBrush &brush); + + bool isSolid() const; + + Qt::PenCapStyle capStyle() const; + void setCapStyle(Qt::PenCapStyle pcs); + + Qt::PenJoinStyle joinStyle() const; + void setJoinStyle(Qt::PenJoinStyle pcs); + + bool isCosmetic() const; + void setCosmetic(bool cosmetic); + + bool operator==(const QPen &p) const; + inline bool operator!=(const QPen &p) const { return !(operator==(p)); } + operator QVariant() const; + + bool isDetached(); +private: + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPen &); + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPen &); + + void detach(); + class QPenPrivate *d; + +public: + typedef QPenPrivate * DataPtr; + inline DataPtr &data_ptr() { return d; } +}; +Q_DECLARE_TYPEINFO(QPen, Q_MOVABLE_TYPE); +Q_DECLARE_SHARED(QPen) + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QPen &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPEN_H diff --git a/src/gui/painting/qpen_p.h b/src/gui/painting/qpen_p.h new file mode 100644 index 0000000000..ef63b7650f --- /dev/null +++ b/src/gui/painting/qpen_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPEN_P_H +#define QPEN_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 <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class QPenPrivate { +public: + QPenPrivate(const QBrush &brush, qreal width, Qt::PenStyle, Qt::PenCapStyle, + Qt::PenJoinStyle _joinStyle); + QAtomicInt ref; + qreal width; + QBrush brush; + Qt::PenStyle style; + Qt::PenCapStyle capStyle; + Qt::PenJoinStyle joinStyle; + mutable QVector<qreal> dashPattern; + qreal dashOffset; + qreal miterLimit; + uint cosmetic : 1; +}; + +QT_END_NAMESPACE + +#endif // QPEN_P_H diff --git a/src/gui/painting/qpolygon.cpp b/src/gui/painting/qpolygon.cpp new file mode 100644 index 0000000000..41b686adef --- /dev/null +++ b/src/gui/painting/qpolygon.cpp @@ -0,0 +1,1003 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qpolygon.h" +#include "qrect.h" +#include "qdatastream.h" +#include "qmatrix.h" +#include "qdebug.h" +#include "qpainterpath.h" +#include "qvariant.h" +#include "qpainterpath_p.h" +#include "qbezier_p.h" + +#include <stdarg.h> + +QT_BEGIN_NAMESPACE + +//same as qt_painterpath_isect_line in qpainterpath.cpp +static void qt_polygon_isect_line(const QPointF &p1, const QPointF &p2, const QPointF &pos, + int *winding) +{ + qreal x1 = p1.x(); + qreal y1 = p1.y(); + qreal x2 = p2.x(); + qreal y2 = p2.y(); + qreal y = pos.y(); + + int dir = 1; + + if (qFuzzyCompare(y1, y2)) { + // ignore horizontal lines according to scan conversion rule + return; + } else if (y2 < y1) { + qreal x_tmp = x2; x2 = x1; x1 = x_tmp; + qreal y_tmp = y2; y2 = y1; y1 = y_tmp; + dir = -1; + } + + if (y >= y1 && y < y2) { + qreal x = x1 + ((x2 - x1) / (y2 - y1)) * (y - y1); + + // count up the winding number if we're + if (x<=pos.x()) { + (*winding) += dir; + } + } +} + +/*! + \class QPolygon + \brief The QPolygon class provides a vector of points using + integer precision. + + \reentrant + + \ingroup painting + \ingroup shared + + A QPolygon object is a QVector<QPoint>. The easiest way to add + points to a QPolygon is to use QVector's streaming operator, as + illustrated below: + + \snippet doc/src/snippets/polygon/polygon.cpp 0 + + In addition to the functions provided by QVector, QPolygon + provides some point-specific functions. + + Each point in a polygon can be retrieved by passing its index to + the point() function. To populate the polygon, QPolygon provides + the setPoint() function to set the point at a given index, the + setPoints() function to set all the points in the polygon + (resizing it to the given number of points), and the putPoints() + function which copies a number of given points into the polygon + from a specified index (resizing the polygon if necessary). + + QPolygon provides the boundingRect() and translate() functions for + geometry functions. Use the QMatrix::map() function for more + general transformations of QPolygons. + + The QPolygon class is \l {Implicit Data Sharing}{implicitly + shared}. + + \sa QVector, QPolygonF, QLine +*/ + + +/***************************************************************************** + QPolygon member functions + *****************************************************************************/ + +/*! + \fn QPolygon::QPolygon() + + Constructs a polygon with no points. + + \sa QVector::isEmpty() +*/ + +/*! + \fn QPolygon::QPolygon(int size) + + Constructs a polygon of the given \a size. Creates an empty + polygon if \a size == 0. + + \sa QVector::isEmpty() +*/ + +/*! + \fn QPolygon::QPolygon(const QPolygon &polygon) + + Constructs a copy of the given \a polygon. + + \sa setPoints() +*/ + +/*! + \fn QPolygon::QPolygon(const QVector<QPoint> &points) + + Constructs a polygon containing the specified \a points. + + \sa setPoints() +*/ + +/*! + \fn QPolygon::QPolygon(const QRect &rectangle, bool closed) + + Constructs a polygon from the given \a rectangle. If \a closed is + false, the polygon just contains the four points of the rectangle + ordered clockwise, otherwise the polygon's fifth point is set to + \a {rectangle}.topLeft(). + + Note that the bottom-right corner of the rectangle is located at + (rectangle.x() + rectangle.width(), rectangle.y() + + rectangle.height()). + + \sa setPoints() +*/ + +QPolygon::QPolygon(const QRect &r, bool closed) +{ + reserve(closed ? 5 : 4); + *this << QPoint(r.x(), r.y()) + << QPoint(r.x() + r.width(), r.y()) + << QPoint(r.x() + r.width(), r.y() + r.height()) + << QPoint(r.x(), r.y() + r.height()); + if (closed) + *this << QPoint(r.left(), r.top()); +} + +/*! + \internal + Constructs a point array with \a nPoints points, taken from the + \a points array. + + Equivalent to setPoints(nPoints, points). +*/ + +QPolygon::QPolygon(int nPoints, const int *points) +{ + setPoints(nPoints, points); +} + + +/*! + \fn QPolygon::~QPolygon() + + Destroys the polygon. +*/ + + +/*! + Translates all points in the polygon by (\a{dx}, \a{dy}). + + \sa translated() +*/ + +void QPolygon::translate(int dx, int dy) +{ + if (dx == 0 && dy == 0) + return; + + register QPoint *p = data(); + register int i = size(); + QPoint pt(dx, dy); + while (i--) { + *p += pt; + ++p; + } +} + +/*! + \fn void QPolygon::translate(const QPoint &offset) + \overload + + Translates all points in the polygon by the given \a offset. + + \sa translated() +*/ + +/*! + Returns a copy of the polygon that is translated by (\a{dx}, \a{dy}). + + \since 4.6 + \sa translate() +*/ +QPolygon QPolygon::translated(int dx, int dy) const +{ + QPolygon copy(*this); + copy.translate(dx, dy); + return copy; +} + +/*! + \fn void QPolygon::translated(const QPoint &offset) const + \overload + \since 4.6 + + Returns a copy of the polygon that is translated by the given \a offset. + + \sa translate() +*/ + +/*! + Extracts the coordinates of the point at the given \a index to + *\a{x} and *\a{y} (if they are valid pointers). + + \sa setPoint() +*/ + +void QPolygon::point(int index, int *x, int *y) const +{ + QPoint p = at(index); + if (x) + *x = (int)p.x(); + if (y) + *y = (int)p.y(); +} + +/*! + \fn QPoint QPolygon::point(int index) const + \overload + + Returns the point at the given \a index. +*/ + +/*! + \fn void QPolygon::setPoint(int index, const QPoint &point) + \overload + + Sets the point at the given \a index to the given \a point. +*/ + +/*! + \fn void QPolygon::setPoint(int index, int x, int y) + + Sets the point at the given \a index to the point specified by + (\a{x}, \a{y}). + + \sa point(), putPoints(), setPoints(), +*/ + +/*! + Resizes the polygon to \a nPoints and populates it with the given + \a points. + + The example code creates a polygon with two points (10, 20) and + (30, 40): + + \snippet doc/src/snippets/polygon/polygon.cpp 2 + + \sa setPoint() putPoints() +*/ + +void QPolygon::setPoints(int nPoints, const int *points) +{ + resize(nPoints); + int i = 0; + while (nPoints--) { + setPoint(i++, *points, *(points+1)); + points += 2; + } +} + +/*! + \overload + + Resizes the polygon to \a nPoints and populates it with the points + specified by the variable argument list. The points are given as a + sequence of integers, starting with \a firstx then \a firsty, and + so on. + + The example code creates a polygon with two points (10, 20) and + (30, 40): + + \snippet doc/src/snippets/polygon/polygon.cpp 3 +*/ + +void QPolygon::setPoints(int nPoints, int firstx, int firsty, ...) +{ + va_list ap; + resize(nPoints); + setPoint(0, firstx, firsty); + int i = 0, x, y; + va_start(ap, firsty); + while (--nPoints) { + x = va_arg(ap, int); + y = va_arg(ap, int); + setPoint(++i, x, y); + } + va_end(ap); +} + +/*! + \overload + \internal + + Copies \a nPoints points from the \a points coord array into this + point array, and resizes the point array if \c{index+nPoints} + exceeds the size of the array. + + \sa setPoint() +*/ + +void QPolygon::putPoints(int index, int nPoints, const int *points) +{ + if (index + nPoints > size()) + resize(index + nPoints); + int i = index; + while (nPoints--) { + setPoint(i++, *points, *(points+1)); + points += 2; + } +} + +/*! + Copies \a nPoints points from the variable argument list into this + polygon from the given \a index. + + The points are given as a sequence of integers, starting with \a + firstx then \a firsty, and so on. The polygon is resized if + \c{index+nPoints} exceeds its current size. + + The example code creates a polygon with three points (4,5), (6,7) + and (8,9), by expanding the polygon from 1 to 3 points: + + \snippet doc/src/snippets/polygon/polygon.cpp 4 + + The following code has the same result, but here the putPoints() + function overwrites rather than extends: + + \snippet doc/src/snippets/polygon/polygon.cpp 5 + + \sa setPoints() +*/ + +void QPolygon::putPoints(int index, int nPoints, int firstx, int firsty, ...) +{ + va_list ap; + if (index + nPoints > size()) + resize(index + nPoints); + if (nPoints <= 0) + return; + setPoint(index, firstx, firsty); + int i = index, x, y; + va_start(ap, firsty); + while (--nPoints) { + x = va_arg(ap, int); + y = va_arg(ap, int); + setPoint(++i, x, y); + } + va_end(ap); +} + + +/*! + \fn void QPolygon::putPoints(int index, int nPoints, const QPolygon &fromPolygon, int fromIndex) + \overload + + Copies \a nPoints points from the given \a fromIndex ( 0 by + default) in \a fromPolygon into this polygon, starting at the + specified \a index. For example: + + \snippet doc/src/snippets/polygon/polygon.cpp 6 +*/ + +void QPolygon::putPoints(int index, int nPoints, const QPolygon & from, int fromIndex) +{ + if (index + nPoints > size()) + resize(index + nPoints); + if (nPoints <= 0) + return; + int n = 0; + while(n < nPoints) { + setPoint(index + n, from[fromIndex+n]); + ++n; + } +} + + +/*! + Returns the bounding rectangle of the polygon, or QRect(0, 0, 0, + 0) if the polygon is empty. + + \sa QVector::isEmpty() +*/ + +QRect QPolygon::boundingRect() const +{ + if (isEmpty()) + return QRect(0, 0, 0, 0); + register const QPoint *pd = constData(); + int minx, maxx, miny, maxy; + minx = maxx = pd->x(); + miny = maxy = pd->y(); + ++pd; + for (int i = 1; i < size(); ++i) { + if (pd->x() < minx) + minx = pd->x(); + else if (pd->x() > maxx) + maxx = pd->x(); + if (pd->y() < miny) + miny = pd->y(); + else if (pd->y() > maxy) + maxy = pd->y(); + ++pd; + } + return QRect(QPoint(minx,miny), QPoint(maxx,maxy)); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QPolygon &a) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QPolygon("; + for (int i = 0; i < a.count(); ++i) + dbg.nospace() << a.at(i); + dbg.nospace() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QPolygon to QDebug"); + return dbg; + Q_UNUSED(a); +#endif +} +#endif + + +/*! + \class QPolygonF + \brief The QPolygonF class provides a vector of points using + floating point precision. + + \reentrant + \ingroup painting + \ingroup shared + + A QPolygonF is a QVector<QPointF>. The easiest way to add points + to a QPolygonF is to use its streaming operator, as illustrated + below: + + \snippet doc/src/snippets/polygon/polygon.cpp 1 + + In addition to the functions provided by QVector, QPolygonF + provides the boundingRect() and translate() functions for geometry + operations. Use the QMatrix::map() function for more general + transformations of QPolygonFs. + + QPolygonF also provides the isClosed() function to determine + whether a polygon's start and end points are the same, and the + toPolygon() function returning an integer precision copy of this + polygon. + + The QPolygonF class is \l {Implicit Data Sharing}{implicitly + shared}. + + \sa QVector, QPolygon, QLineF +*/ + + +/***************************************************************************** + QPolygonF member functions + *****************************************************************************/ + +/*! + \fn QPolygonF::QPolygonF() + + Constructs a polygon with no points. + + \sa QVector::isEmpty() +*/ + +/*! + \fn QPolygonF::QPolygonF(int size) + + Constructs a polygon of the given \a size. Creates an empty + polygon if \a size == 0. + + \sa QVector::isEmpty() +*/ + +/*! + \fn QPolygonF::QPolygonF(const QPolygonF &polygon) + + Constructs a copy of the given \a polygon. +*/ + +/*! + \fn QPolygonF::QPolygonF(const QVector<QPointF> &points) + + Constructs a polygon containing the specified \a points. +*/ + +/*! + \fn QPolygonF::QPolygonF(const QRectF &rectangle) + + Constructs a closed polygon from the specified \a rectangle. + + The polygon contains the four vertices of the rectangle in + clockwise order starting and ending with the top-left vertex. + + \sa isClosed() +*/ + +QPolygonF::QPolygonF(const QRectF &r) +{ + reserve(5); + append(QPointF(r.x(), r.y())); + append(QPointF(r.x() + r.width(), r.y())); + append(QPointF(r.x() + r.width(), r.y() + r.height())); + append(QPointF(r.x(), r.y() + r.height())); + append(QPointF(r.x(), r.y())); +} + +/*! + \fn QPolygonF::QPolygonF(const QPolygon &polygon) + + Constructs a float based polygon from the specified integer based + \a polygon. + + \sa toPolygon() +*/ + +QPolygonF::QPolygonF(const QPolygon &a) +{ + reserve(a.size()); + for (int i=0; i<a.size(); ++i) + append(a.at(i)); +} + +/*! + \fn QPolygonF::~QPolygonF() + + Destroys the polygon. +*/ + + +/*! + Translate all points in the polygon by the given \a offset. + + \sa translated() +*/ + +void QPolygonF::translate(const QPointF &offset) +{ + if (offset.isNull()) + return; + + register QPointF *p = data(); + register int i = size(); + while (i--) { + *p += offset; + ++p; + } +} + +/*! + \fn void QPolygonF::translate(qreal dx, qreal dy) + \overload + + Translates all points in the polygon by (\a{dx}, \a{dy}). + + \sa translated() +*/ + +/*! + Returns a copy of the polygon that is translated by the given \a offset. + + \since 4.6 + \sa translate() +*/ +QPolygonF QPolygonF::translated(const QPointF &offset) const +{ + QPolygonF copy(*this); + copy.translate(offset); + return copy; +} + +/*! + \fn void QPolygonF::translated(qreal dx, qreal dy) const + \overload + \since 4.6 + + Returns a copy of the polygon that is translated by (\a{dx}, \a{dy}). + + \sa translate() +*/ + +/*! + \fn bool QPolygonF::isClosed() const + + Returns true if the polygon is closed; otherwise returns false. + + A polygon is said to be closed if its start point and end point are equal. + + \sa QVector::first(), QVector::last() +*/ + +/*! + Returns the bounding rectangle of the polygon, or QRectF(0,0,0,0) + if the polygon is empty. + + \sa QVector::isEmpty() +*/ + +QRectF QPolygonF::boundingRect() const +{ + if (isEmpty()) + return QRectF(0, 0, 0, 0); + register const QPointF *pd = constData(); + qreal minx, maxx, miny, maxy; + minx = maxx = pd->x(); + miny = maxy = pd->y(); + ++pd; + for (int i = 1; i < size(); ++i) { + if (pd->x() < minx) + minx = pd->x(); + else if (pd->x() > maxx) + maxx = pd->x(); + if (pd->y() < miny) + miny = pd->y(); + else if (pd->y() > maxy) + maxy = pd->y(); + ++pd; + } + return QRectF(minx,miny, maxx - minx, maxy - miny); +} + +/*! + Creates and returns a QPolygon by converting each QPointF to a + QPoint. + + \sa QPointF::toPoint() +*/ + +QPolygon QPolygonF::toPolygon() const +{ + QPolygon a; + a.reserve(size()); + for (int i=0; i<size(); ++i) + a.append(at(i).toPoint()); + return a; +} + +/*! + \fn void QPolygon::swap(QPolygon &other) + \since 4.8 + + Swaps polygon \a other with this polygon. This operation is very + fast and never fails. +*/ + +/*! + \fn void QPolygonF::swap(QPolygonF &other) + \since 4.8 + + Swaps polygon \a other with this polygon. This operation is very + fast and never fails. +*/ + +/*! + Returns the polygon as a QVariant +*/ +QPolygon::operator QVariant() const +{ + return QVariant(QVariant::Polygon, this); +} + +/***************************************************************************** + QPolygon stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QPolygon &polygon) + \since 4.4 + \relates QPolygon + + Writes the given \a polygon to the given \a stream, and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator<<(QDataStream &s, const QPolygon &a) +{ + const QVector<QPoint> &v = a; + return s << v; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QPolygon &polygon) + \since 4.4 + \relates QPolygon + + Reads a polygon from the given \a stream into the given \a + polygon, and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream &operator>>(QDataStream &s, QPolygon &a) +{ + QVector<QPoint> &v = a; + return s >> v; +} +#endif // QT_NO_DATASTREAM + +/***************************************************************************** + QPolygonF stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QPolygonF &polygon) + \relates QPolygonF + + Writes the given \a polygon to the given \a stream, and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator<<(QDataStream &s, const QPolygonF &a) +{ + quint32 len = a.size(); + uint i; + + s << len; + for (i = 0; i < len; ++i) + s << a.at(i); + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QPolygonF &polygon) + \relates QPolygonF + + Reads a polygon from the given \a stream into the given \a + polygon, and returns a reference to the stream. + + \sa {Serializing Qt Data Types} +*/ + +QDataStream &operator>>(QDataStream &s, QPolygonF &a) +{ + quint32 len; + uint i; + + s >> len; + a.reserve(a.size() + (int)len); + QPointF p; + for (i = 0; i < len; ++i) { + s >> p; + a.insert(i, p); + } + return s; +} +#endif //QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QPolygonF &a) +{ +#ifndef Q_BROKEN_DEBUG_STREAM + dbg.nospace() << "QPolygonF("; + for (int i = 0; i < a.count(); ++i) + dbg.nospace() << a.at(i); + dbg.nospace() << ')'; + return dbg.space(); +#else + qWarning("This compiler doesn't support streaming QPolygonF to QDebug"); + return dbg; + Q_UNUSED(a); +#endif +} +#endif + + +/*! + \since 4.3 + + \fn bool QPolygonF::containsPoint(const QPointF &point, Qt::FillRule fillRule) const + + Returns true if the given \a point is inside the polygon according to + the specified \a fillRule; otherwise returns false. +*/ +bool QPolygonF::containsPoint(const QPointF &pt, Qt::FillRule fillRule) const +{ + if (isEmpty()) + return false; + + int winding_number = 0; + + QPointF last_pt = at(0); + QPointF last_start = at(0); + for (int i = 1; i < size(); ++i) { + const QPointF &e = at(i); + qt_polygon_isect_line(last_pt, e, pt, &winding_number); + last_pt = e; + } + + // implicitly close last subpath + if (last_pt != last_start) + qt_polygon_isect_line(last_pt, last_start, pt, &winding_number); + + return (fillRule == Qt::WindingFill + ? (winding_number != 0) + : ((winding_number % 2) != 0)); +} + +/*! + \since 4.3 + + \fn bool QPolygon::containsPoint(const QPoint &point, Qt::FillRule fillRule) const + Returns true if the given \a point is inside the polygon according to + the specified \a fillRule; otherwise returns false. +*/ +bool QPolygon::containsPoint(const QPoint &pt, Qt::FillRule fillRule) const +{ + if (isEmpty()) + return false; + + int winding_number = 0; + + QPoint last_pt = at(0); + QPoint last_start = at(0); + for (int i = 1; i < size(); ++i) { + const QPoint &e = at(i); + qt_polygon_isect_line(last_pt, e, pt, &winding_number); + last_pt = e; + } + + // implicitly close last subpath + if (last_pt != last_start) + qt_polygon_isect_line(last_pt, last_start, pt, &winding_number); + + return (fillRule == Qt::WindingFill + ? (winding_number != 0) + : ((winding_number % 2) != 0)); +} + +/*! + \since 4.3 + + Returns a polygon which is the union of this polygon and \a r. + + Set operations on polygons, will treat the polygons as areas, and + implicitly close the polygon. + + \sa intersected(), subtracted() +*/ + +QPolygon QPolygon::united(const QPolygon &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + + return subject.united(clip).toFillPolygon().toPolygon(); +} + +/*! + \since 4.3 + + Returns a polygon which is the intersection of this polygon and \a r. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. +*/ + +QPolygon QPolygon::intersected(const QPolygon &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + + return subject.intersected(clip).toFillPolygon().toPolygon(); +} + +/*! + \since 4.3 + + Returns a polygon which is \a r subtracted from this polygon. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + +*/ + +QPolygon QPolygon::subtracted(const QPolygon &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + + return subject.subtracted(clip).toFillPolygon().toPolygon(); +} + +/*! + \since 4.3 + + Returns a polygon which is the union of this polygon and \a r. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + + \sa intersected(), subtracted() +*/ + +QPolygonF QPolygonF::united(const QPolygonF &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + + return subject.united(clip).toFillPolygon(); +} + +/*! + \since 4.3 + + Returns a polygon which is the intersection of this polygon and \a r. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + +*/ + +QPolygonF QPolygonF::intersected(const QPolygonF &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + + return subject.intersected(clip).toFillPolygon(); +} + +/*! + \since 4.3 + + Returns a polygon which is \a r subtracted from this polygon. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + +*/ + +QPolygonF QPolygonF::subtracted(const QPolygonF &r) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(r); + return subject.subtracted(clip).toFillPolygon(); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpolygon.h b/src/gui/painting/qpolygon.h new file mode 100644 index 0000000000..0f4a8b3450 --- /dev/null +++ b/src/gui/painting/qpolygon.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPOLYGON_H +#define QPOLYGON_H + +#include <QtCore/qvector.h> +#include <QtCore/qpoint.h> +#include <QtCore/qrect.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QMatrix; +class QTransform; +class QRect; +class QVariant; + +class Q_GUI_EXPORT QPolygon : public QVector<QPoint> +{ +public: + inline QPolygon() {} + inline ~QPolygon() {} + inline QPolygon(int size); + inline QPolygon(const QPolygon &a) : QVector<QPoint>(a) {} + inline QPolygon(const QVector<QPoint> &v) : QVector<QPoint>(v) {} + QPolygon(const QRect &r, bool closed=false); + QPolygon(int nPoints, const int *points); + inline void swap(QPolygon &other) { QVector<QPoint>::swap(other); } // prevent QVector<QPoint><->QPolygon swaps + + operator QVariant() const; + + void translate(int dx, int dy); + void translate(const QPoint &offset); + + QPolygon translated(int dx, int dy) const; + inline QPolygon translated(const QPoint &offset) const; + + QRect boundingRect() const; + + void point(int i, int *x, int *y) const; + QPoint point(int i) const; + void setPoint(int index, int x, int y); + void setPoint(int index, const QPoint &p); + void setPoints(int nPoints, const int *points); + void setPoints(int nPoints, int firstx, int firsty, ...); + void putPoints(int index, int nPoints, const int *points); + void putPoints(int index, int nPoints, int firstx, int firsty, ...); + void putPoints(int index, int nPoints, const QPolygon & from, int fromIndex=0); + + bool containsPoint(const QPoint &pt, Qt::FillRule fillRule) const; + + QPolygon united(const QPolygon &r) const; + QPolygon intersected(const QPolygon &r) const; + QPolygon subtracted(const QPolygon &r) const; +}; + +inline QPolygon::QPolygon(int asize) : QVector<QPoint>(asize) {} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QPolygon &); +#endif + +/***************************************************************************** + QPolygon stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QPolygon &polygon); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QPolygon &polygon); +#endif + +/***************************************************************************** + Misc. QPolygon functions + *****************************************************************************/ + +inline void QPolygon::setPoint(int index, const QPoint &pt) +{ (*this)[index] = pt; } + +inline void QPolygon::setPoint(int index, int x, int y) +{ (*this)[index] = QPoint(x, y); } + +inline QPoint QPolygon::point(int index) const +{ return at(index); } + +inline void QPolygon::translate(const QPoint &offset) +{ translate(offset.x(), offset.y()); } + +inline QPolygon QPolygon::translated(const QPoint &offset) const +{ return translated(offset.x(), offset.y()); } + +class QRectF; + +class Q_GUI_EXPORT QPolygonF : public QVector<QPointF> +{ +public: + inline QPolygonF() {} + inline ~QPolygonF() {} + inline QPolygonF(int size); + inline QPolygonF(const QPolygonF &a) : QVector<QPointF>(a) {} + inline QPolygonF(const QVector<QPointF> &v) : QVector<QPointF>(v) {} + QPolygonF(const QRectF &r); + QPolygonF(const QPolygon &a); + inline void swap(QPolygonF &other) { QVector<QPointF>::swap(other); } // prevent QVector<QPointF><->QPolygonF swaps + + inline void translate(qreal dx, qreal dy); + void translate(const QPointF &offset); + + inline QPolygonF translated(qreal dx, qreal dy) const; + QPolygonF translated(const QPointF &offset) const; + + QPolygon toPolygon() const; + + bool isClosed() const { return !isEmpty() && first() == last(); } + + QRectF boundingRect() const; + + bool containsPoint(const QPointF &pt, Qt::FillRule fillRule) const; + + QPolygonF united(const QPolygonF &r) const; + QPolygonF intersected(const QPolygonF &r) const; + QPolygonF subtracted(const QPolygonF &r) const; +}; + +inline QPolygonF::QPolygonF(int asize) : QVector<QPointF>(asize) {} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QPolygonF &); +#endif + +/***************************************************************************** + QPolygonF stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QPolygonF &array); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QPolygonF &array); +#endif + +inline void QPolygonF::translate(qreal dx, qreal dy) +{ translate(QPointF(dx, dy)); } + +inline QPolygonF QPolygonF::translated(qreal dx, qreal dy) const +{ return translated(QPointF(dx, dy)); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPOLYGON_H diff --git a/src/gui/painting/qpolygonclipper_p.h b/src/gui/painting/qpolygonclipper_p.h new file mode 100644 index 0000000000..287190ab8b --- /dev/null +++ b/src/gui/painting/qpolygonclipper_p.h @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPOLYGONCLIPPER_P_H +#define QPOLYGONCLIPPER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qdatabuffer_p.h" + +QT_BEGIN_NAMESPACE + +/* based on sutherland-hodgman line-by-line clipping, as described in + Computer Graphics and Principles */ +template <typename InType, typename OutType, typename CastType> class QPolygonClipper +{ +public: + QPolygonClipper() : + buffer1(0), buffer2(0) + { + x1 = y1 = x2 = y2 = 0; + } + + ~QPolygonClipper() + { + } + + void setBoundingRect(const QRect bounds) + { + x1 = bounds.x(); + x2 = bounds.x() + bounds.width(); + y1 = bounds.y(); + y2 = bounds.y() + bounds.height(); + } + + QRect boundingRect() + { + return QRect(QPoint(x1, y1), QPoint(x2, y2)); + } + + inline OutType intersectLeft(const OutType &p1, const OutType &p2) + { + OutType t; + qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x); + t.x = x1; + t.y = static_cast<CastType>(p2.y + (x1 - p2.x) * dy); + return t; + } + + + inline OutType intersectRight(const OutType &p1, const OutType &p2) + { + OutType t; + qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x); + t.x = x2; + t.y = static_cast<CastType>(p2.y + (x2 - p2.x) * dy); + return t; + } + + + inline OutType intersectTop(const OutType &p1, const OutType &p2) + { + OutType t; + qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y); + t.x = static_cast<CastType>(p2.x + (y1 - p2.y) * dx); + t.y = y1; + return t; + } + + + inline OutType intersectBottom(const OutType &p1, const OutType &p2) + { + OutType t; + qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y); + t.x = static_cast<CastType>(p2.x + (y2 - p2.y) * dx); + t.y = y2; + return t; + } + + + void clipPolygon(const InType *inPoints, int inCount, OutType **outPoints, int *outCount, + bool closePolygon = true) + { + Q_ASSERT(outPoints); + Q_ASSERT(outCount); + + if (inCount < 2) { + *outCount = 0; + return; + } + + buffer1.reset(); + buffer2.reset(); + + QDataBuffer<OutType> *source = &buffer1; + QDataBuffer<OutType> *clipped = &buffer2; + + // Gather some info since we are iterating through the points anyway.. + bool doLeft = false, doRight = false, doTop = false, doBottom = false; + OutType ot; + for (int i=0; i<inCount; ++i) { + ot = inPoints[i]; + clipped->add(ot); + + if (ot.x < x1) + doLeft = true; + else if (ot.x > x2) + doRight = true; + if (ot.y < y1) + doTop = true; + else if (ot.y > y2) + doBottom = true; + } + + if (doLeft && clipped->size() > 1) { + QDataBuffer<OutType> *tmp = source; + source = clipped; + clipped = tmp; + clipped->reset(); + int lastPos, start; + if (closePolygon) { + lastPos = source->size() - 1; + start = 0; + } else { + lastPos = 0; + start = 1; + if (source->at(0).x >= x1) + clipped->add(source->at(0)); + } + for (int i=start; i<inCount; ++i) { + const OutType &cpt = source->at(i); + const OutType &ppt = source->at(lastPos); + + if (cpt.x >= x1) { + if (ppt.x >= x1) { + clipped->add(cpt); + } else { + clipped->add(intersectLeft(cpt, ppt)); + clipped->add(cpt); + } + } else if (ppt.x >= x1) { + clipped->add(intersectLeft(cpt, ppt)); + } + lastPos = i; + } + } + + if (doRight && clipped->size() > 1) { + QDataBuffer<OutType> *tmp = source; + source = clipped; + clipped = tmp; + clipped->reset(); + int lastPos, start; + if (closePolygon) { + lastPos = source->size() - 1; + start = 0; + } else { + lastPos = 0; + start = 1; + if (source->at(0).x <= x2) + clipped->add(source->at(0)); + } + for (int i=start; i<source->size(); ++i) { + const OutType &cpt = source->at(i); + const OutType &ppt = source->at(lastPos); + + if (cpt.x <= x2) { + if (ppt.x <= x2) { + clipped->add(cpt); + } else { + clipped->add(intersectRight(cpt, ppt)); + clipped->add(cpt); + } + } else if (ppt.x <= x2) { + clipped->add(intersectRight(cpt, ppt)); + } + + lastPos = i; + } + + } + + if (doTop && clipped->size() > 1) { + QDataBuffer<OutType> *tmp = source; + source = clipped; + clipped = tmp; + clipped->reset(); + int lastPos, start; + if (closePolygon) { + lastPos = source->size() - 1; + start = 0; + } else { + lastPos = 0; + start = 1; + if (source->at(0).y >= y1) + clipped->add(source->at(0)); + } + for (int i=start; i<source->size(); ++i) { + const OutType &cpt = source->at(i); + const OutType &ppt = source->at(lastPos); + + if (cpt.y >= y1) { + if (ppt.y >= y1) { + clipped->add(cpt); + } else { + clipped->add(intersectTop(cpt, ppt)); + clipped->add(cpt); + } + } else if (ppt.y >= y1) { + clipped->add(intersectTop(cpt, ppt)); + } + + lastPos = i; + } + } + + if (doBottom && clipped->size() > 1) { + QDataBuffer<OutType> *tmp = source; + source = clipped; + clipped = tmp; + clipped->reset(); + int lastPos, start; + if (closePolygon) { + lastPos = source->size() - 1; + start = 0; + } else { + lastPos = 0; + start = 1; + if (source->at(0).y <= y2) + clipped->add(source->at(0)); + } + for (int i=start; i<source->size(); ++i) { + const OutType &cpt = source->at(i); + const OutType &ppt = source->at(lastPos); + + if (cpt.y <= y2) { + if (ppt.y <= y2) { + clipped->add(cpt); + } else { + clipped->add(intersectBottom(cpt, ppt)); + clipped->add(cpt); + } + } else if (ppt.y <= y2) { + clipped->add(intersectBottom(cpt, ppt)); + } + lastPos = i; + } + } + + if (closePolygon && clipped->size() > 0) { + // close clipped polygon + if (clipped->at(0).x != clipped->at(clipped->size()-1).x || + clipped->at(0).y != clipped->at(clipped->size()-1).y) { + OutType ot = clipped->at(0); + clipped->add(ot); + } + } + *outCount = clipped->size(); + *outPoints = clipped->data(); + } + +private: + int x1, x2, y1, y2; + QDataBuffer<OutType> buffer1; + QDataBuffer<OutType> buffer2; +}; + +QT_END_NAMESPACE + +#endif // QPOLYGONCLIPPER_P_H diff --git a/src/gui/painting/qprintengine.h b/src/gui/painting/qprintengine.h new file mode 100644 index 0000000000..b04ea068a3 --- /dev/null +++ b/src/gui/painting/qprintengine.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_H +#define QPRINTENGINE_H + +#include <QtCore/qvariant.h> +#include <QtGui/qprinter.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTER + +class Q_GUI_EXPORT QPrintEngine +{ +public: + virtual ~QPrintEngine() {} + enum PrintEnginePropertyKey { + PPK_CollateCopies, + PPK_ColorMode, + PPK_Creator, + PPK_DocumentName, + PPK_FullPage, + PPK_NumberOfCopies, + PPK_Orientation, + PPK_OutputFileName, + PPK_PageOrder, + PPK_PageRect, + PPK_PageSize, + PPK_PaperRect, + PPK_PaperSource, + PPK_PrinterName, + PPK_PrinterProgram, + PPK_Resolution, + PPK_SelectionOption, + PPK_SupportedResolutions, + + PPK_WindowsPageSize, + PPK_FontEmbedding, + PPK_SuppressSystemPrintStatus, + + PPK_Duplex, + + PPK_PaperSources, + PPK_CustomPaperSize, + PPK_PageMargins, + PPK_CopyCount, + PPK_SupportsMultipleCopies, + PPK_PaperSize = PPK_PageSize, + + PPK_CustomBase = 0xff00 + }; + + virtual void setProperty(PrintEnginePropertyKey key, const QVariant &value) = 0; + virtual QVariant property(PrintEnginePropertyKey key) const = 0; + + virtual bool newPage() = 0; + virtual bool abort() = 0; + + virtual int metric(QPaintDevice::PaintDeviceMetric) const = 0; + + virtual QPrinter::PrinterState printerState() const = 0; + +#ifdef Q_WS_WIN + virtual HDC getPrinterDC() const { return 0; } + virtual void releasePrinterDC(HDC) const { } +#endif + +}; + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPRINTENGINE_H diff --git a/src/gui/painting/qprintengine_mac.mm b/src/gui/painting/qprintengine_mac.mm new file mode 100644 index 0000000000..1dce30301f --- /dev/null +++ b/src/gui/painting/qprintengine_mac.mm @@ -0,0 +1,911 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qprintengine_mac_p.h> +#include <qdebug.h> +#include <qthread.h> +#include <QtCore/qcoreapplication.h> + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); + +QMacPrintEngine::QMacPrintEngine(QPrinter::PrinterMode mode) : QPaintEngine(*(new QMacPrintEnginePrivate)) +{ + Q_D(QMacPrintEngine); + d->mode = mode; + d->initialize(); +} + +bool QMacPrintEngine::begin(QPaintDevice *dev) +{ + Q_D(QMacPrintEngine); + + Q_ASSERT(dev && dev->devType() == QInternal::Printer); + if (!static_cast<QPrinter *>(dev)->isValid()) + return false; + + if (d->state == QPrinter::Idle && !d->isPrintSessionInitialized()) // Need to reinitialize + d->initialize(); + + d->paintEngine->state = state; + d->paintEngine->begin(dev); + Q_ASSERT_X(d->state == QPrinter::Idle, "QMacPrintEngine", "printer already active"); + + if (PMSessionValidatePrintSettings(d->session, d->settings, kPMDontWantBoolean) != noErr + || PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean) != noErr) { + d->state = QPrinter::Error; + return false; + } + + if (!d->outputFilename.isEmpty()) { + QCFType<CFURLRef> outFile = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, + QCFString(d->outputFilename), + kCFURLPOSIXPathStyle, + false); + if (PMSessionSetDestination(d->session, d->settings, kPMDestinationFile, + kPMDocumentFormatPDF, outFile) != noErr) { + qWarning("QMacPrintEngine::begin: Problem setting file [%s]", d->outputFilename.toUtf8().constData()); + return false; + } + } + OSStatus status = noErr; +#ifndef QT_MAC_USE_COCOA + status = d->shouldSuppressStatus() ? PMSessionBeginCGDocumentNoDialog(d->session, d->settings, d->format) + : PMSessionBeginCGDocument(d->session, d->settings, d->format); +#else + status = PMSessionBeginCGDocumentNoDialog(d->session, d->settings, d->format); +#endif + + if (status != noErr) { + d->state = QPrinter::Error; + return false; + } + + d->state = QPrinter::Active; + setActive(true); + d->newPage_helper(); + return true; +} + +bool QMacPrintEngine::end() +{ + Q_D(QMacPrintEngine); + if (d->state == QPrinter::Aborted) + return true; // I was just here a function call ago :) + if(d->paintEngine->type() == QPaintEngine::CoreGraphics) { + // We dont need the paint engine to call restoreGraphicsState() + static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->stackCount = 0; + static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->hd = 0; + } + d->paintEngine->end(); + if (d->state != QPrinter::Idle) + d->releaseSession(); + d->state = QPrinter::Idle; + return true; +} + +QPaintEngine * +QMacPrintEngine::paintEngine() const +{ + return d_func()->paintEngine; +} + +Qt::HANDLE QMacPrintEngine::handle() const +{ + QCoreGraphicsPaintEngine *cgEngine = static_cast<QCoreGraphicsPaintEngine*>(paintEngine()); + return cgEngine->d_func()->hd; +} + +QMacPrintEnginePrivate::~QMacPrintEnginePrivate() +{ +#ifdef QT_MAC_USE_COCOA + [printInfo release]; +#endif + delete paintEngine; +} + +void QMacPrintEnginePrivate::setPaperSize(QPrinter::PaperSize ps) +{ + Q_Q(QMacPrintEngine); + QSizeF newSize = qt_paperSizeToQSizeF(ps); + QCFType<CFArrayRef> formats; + PMPrinter printer; + + if (PMSessionGetCurrentPrinter(session, &printer) == noErr + && PMSessionCreatePageFormatList(session, printer, &formats) == noErr) { + CFIndex total = CFArrayGetCount(formats); + PMPageFormat tmp; + PMRect paper; + for (CFIndex idx = 0; idx < total; ++idx) { + tmp = static_cast<PMPageFormat>( + const_cast<void *>(CFArrayGetValueAtIndex(formats, idx))); + PMGetUnadjustedPaperRect(tmp, &paper); + int wMM = int((paper.right - paper.left) / 72 * 25.4 + 0.5); + int hMM = int((paper.bottom - paper.top) / 72 * 25.4 + 0.5); + if (newSize.width() == wMM && newSize.height() == hMM) { + PMCopyPageFormat(tmp, format); + // reset the orientation and resolution as they are lost in the copy. + q->setProperty(QPrintEngine::PPK_Orientation, orient); + if (PMSessionValidatePageFormat(session, format, kPMDontWantBoolean) != noErr) { + // Don't know, warn for the moment. + qWarning("QMacPrintEngine, problem setting format and resolution for this page size"); + } + break; + } + } + } +} + +QPrinter::PaperSize QMacPrintEnginePrivate::paperSize() const +{ + PMRect paper; + PMGetUnadjustedPaperRect(format, &paper); + int wMM = int((paper.right - paper.left) / 72 * 25.4 + 0.5); + int hMM = int((paper.bottom - paper.top) / 72 * 25.4 + 0.5); + for (int i = QPrinter::A4; i < QPrinter::NPaperSize; ++i) { + QSizeF s = qt_paperSizeToQSizeF(QPrinter::PaperSize(i)); + if (s.width() == wMM && s.height() == hMM) + return (QPrinter::PaperSize)i; + } + return QPrinter::Custom; +} + +QList<QVariant> QMacPrintEnginePrivate::supportedResolutions() const +{ + Q_ASSERT_X(session, "QMacPrinterEngine::supportedResolutions", + "must have a valid printer session"); + UInt32 resCount; + QList<QVariant> resolutions; + PMPrinter printer; + if (PMSessionGetCurrentPrinter(session, &printer) == noErr) { + PMResolution res; + OSStatus status = PMPrinterGetPrinterResolutionCount(printer, &resCount); + if (status == kPMNotImplemented) { +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) + // *Sigh* we have to use the non-indexed version. + if (PMPrinterGetPrinterResolution(printer, kPMMinSquareResolution, &res) == noErr) + resolutions.append(int(res.hRes)); + if (PMPrinterGetPrinterResolution(printer, kPMMaxSquareResolution, &res) == noErr) { + QVariant var(int(res.hRes)); + if (!resolutions.contains(var)) + resolutions.append(var); + } + if (PMPrinterGetPrinterResolution(printer, kPMDefaultResolution, &res) == noErr) { + QVariant var(int(res.hRes)); + if (!resolutions.contains(var)) + resolutions.append(var); + } +#endif + } else if (status == noErr) { + // According to the docs, index start at 1. + for (UInt32 i = 1; i <= resCount; ++i) { + if (PMPrinterGetIndexedPrinterResolution(printer, i, &res) == noErr) + resolutions.append(QVariant(int(res.hRes))); + } + } else { + qWarning("QMacPrintEngine::supportedResolutions: Unexpected error: %ld", long(status)); + } + } + return resolutions; +} + +bool QMacPrintEnginePrivate::shouldSuppressStatus() const +{ + if (suppressStatus == true) + return true; + + // Supress displaying the automatic progress dialog if we are printing + // from a non-gui thread. + return (qApp->thread() != QThread::currentThread()); +} + +QPrinter::PrinterState QMacPrintEngine::printerState() const +{ + return d_func()->state; +} + +bool QMacPrintEngine::newPage() +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + OSStatus err = +#ifndef QT_MAC_USE_COCOA + d->shouldSuppressStatus() ? PMSessionEndPageNoDialog(d->session) + : PMSessionEndPage(d->session); +#else + PMSessionEndPageNoDialog(d->session); +#endif + if (err != noErr) { + if (err == kPMCancel) { + // User canceled, we need to abort! + abort(); + } else { + // Not sure what the problem is... + qWarning("QMacPrintEngine::newPage: Cannot end current page. %ld", long(err)); + d->state = QPrinter::Error; + } + return false; + } + return d->newPage_helper(); +} + +bool QMacPrintEngine::abort() +{ + Q_D(QMacPrintEngine); + if (d->state != QPrinter::Active) + return false; + bool ret = end(); + d->state = QPrinter::Aborted; + return ret; +} + +static inline int qt_get_PDMWidth(PMPageFormat pformat, bool fullPage, + const PMResolution &resolution) +{ + int val = 0; + PMRect r; + qreal hRatio = resolution.hRes / 72; + if (fullPage) { + if (PMGetAdjustedPaperRect(pformat, &r) == noErr) + val = qRound((r.right - r.left) * hRatio); + } else { + if (PMGetAdjustedPageRect(pformat, &r) == noErr) + val = qRound((r.right - r.left) * hRatio); + } + return val; +} + +static inline int qt_get_PDMHeight(PMPageFormat pformat, bool fullPage, + const PMResolution &resolution) +{ + int val = 0; + PMRect r; + qreal vRatio = resolution.vRes / 72; + if (fullPage) { + if (PMGetAdjustedPaperRect(pformat, &r) == noErr) + val = qRound((r.bottom - r.top) * vRatio); + } else { + if (PMGetAdjustedPageRect(pformat, &r) == noErr) + val = qRound((r.bottom - r.top) * vRatio); + } + return val; +} + + +int QMacPrintEngine::metric(QPaintDevice::PaintDeviceMetric m) const +{ + Q_D(const QMacPrintEngine); + int val = 1; + switch (m) { + case QPaintDevice::PdmWidth: + if (d->hasCustomPaperSize) { + val = qRound(d->customSize.width()); + if (d->hasCustomPageMargins) { + val -= qRound(d->leftMargin + d->rightMargin); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + val -= qRound(margins.at(0).toDouble() + margins.at(2).toDouble()); + } + } else { + val = qt_get_PDMWidth(d->format, property(PPK_FullPage).toBool(), d->resolution); + } + break; + case QPaintDevice::PdmHeight: + if (d->hasCustomPaperSize) { + val = qRound(d->customSize.height()); + if (d->hasCustomPageMargins) { + val -= qRound(d->topMargin + d->bottomMargin); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + val -= qRound(margins.at(1).toDouble() + margins.at(3).toDouble()); + } + } else { + val = qt_get_PDMHeight(d->format, property(PPK_FullPage).toBool(), d->resolution); + } + break; + case QPaintDevice::PdmWidthMM: + val = metric(QPaintDevice::PdmWidth); + val = int((val * 254 + 5 * d->resolution.hRes) / (10 * d->resolution.hRes)); + break; + case QPaintDevice::PdmHeightMM: + val = metric(QPaintDevice::PdmHeight); + val = int((val * 254 + 5 * d->resolution.vRes) / (10 * d->resolution.vRes)); + break; + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: { + PMPrinter printer; + if(PMSessionGetCurrentPrinter(d->session, &printer) == noErr) { + PMResolution resolution; +#ifndef QT_MAC_USE_COCOA +# if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + PMPrinterGetOutputResolution(printer, d->settings, &resolution); + } else +# endif + { + PMPrinterGetPrinterResolution(printer, kPMCurrentValue, &resolution); + } +#else + PMPrinterGetOutputResolution(printer, d->settings, &resolution); +#endif + val = (int)resolution.vRes; + break; + } + //otherwise fall through + } + case QPaintDevice::PdmDpiY: + val = (int)d->resolution.vRes; + break; + case QPaintDevice::PdmDpiX: + val = (int)d->resolution.hRes; + break; + case QPaintDevice::PdmNumColors: + val = (1 << metric(QPaintDevice::PdmDepth)); + break; + case QPaintDevice::PdmDepth: + val = 24; + break; + default: + val = 0; + qWarning("QPrinter::metric: Invalid metric command"); + } + return val; +} + +void QMacPrintEnginePrivate::initialize() +{ + Q_Q(QMacPrintEngine); + +#ifndef QT_MAC_USE_COCOA + Q_ASSERT(!session); +#else + Q_ASSERT(!printInfo); +#endif + + if (!paintEngine) + paintEngine = new QCoreGraphicsPaintEngine(); + + q->gccaps = paintEngine->gccaps; + + fullPage = false; + +#ifndef QT_MAC_USE_COCOA + if (PMCreateSession(&session) != 0) + session = 0; +#else + QMacCocoaAutoReleasePool pool; + printInfo = [[NSPrintInfo alloc] initWithDictionary:[NSDictionary dictionary]]; + session = static_cast<PMPrintSession>([printInfo PMPrintSession]); +#endif + + PMPrinter printer; + if (session && PMSessionGetCurrentPrinter(session, &printer) == noErr) { + QList<QVariant> resolutions = supportedResolutions(); + if (!resolutions.isEmpty() && mode != QPrinter::ScreenResolution) { + if (resolutions.count() > 1 && mode == QPrinter::HighResolution) { + int max = 0; + for (int i = 0; i < resolutions.count(); ++i) { + int value = resolutions.at(i).toInt(); + if (value > max) + max = value; + } + resolution.hRes = resolution.vRes = max; + } else { + resolution.hRes = resolution.vRes = resolutions.at(0).toInt(); + } + if(resolution.hRes == 0) + resolution.hRes = resolution.vRes = 600; + } else { + resolution.hRes = resolution.vRes = qt_defaultDpi(); + } + } + +#ifndef QT_MAC_USE_COCOA + bool settingsInitialized = (settings != 0); + bool settingsOK = !settingsInitialized ? PMCreatePrintSettings(&settings) == noErr : true; + if (settingsOK && !settingsInitialized) + settingsOK = PMSessionDefaultPrintSettings(session, settings) == noErr; + + + bool formatInitialized = (format != 0); + bool formatOK = !formatInitialized ? PMCreatePageFormat(&format) == noErr : true; + if (formatOK) { + if (!formatInitialized) { + formatOK = PMSessionDefaultPageFormat(session, format) == noErr; + } + formatOK = PMSessionValidatePageFormat(session, format, kPMDontWantBoolean) == noErr; + } +#else + settings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]); + format = static_cast<PMPageFormat>([printInfo PMPageFormat]); +#endif + +#ifndef QT_MAC_USE_COCOA + if (!settingsOK || !formatOK) { + qWarning("QMacPrintEngine::initialize: Unable to initialize QPainter"); + state = QPrinter::Error; + } +#endif + + QHash<QMacPrintEngine::PrintEnginePropertyKey, QVariant>::const_iterator propC; + for (propC = valueCache.constBegin(); propC != valueCache.constEnd(); propC++) { + q->setProperty(propC.key(), propC.value()); + } +} + +void QMacPrintEnginePrivate::releaseSession() +{ +#ifndef QT_MAC_USE_COCOA + if (shouldSuppressStatus()) { + PMSessionEndPageNoDialog(session); + PMSessionEndDocumentNoDialog(session); + } else { + PMSessionEndPage(session); + PMSessionEndDocument(session); + } + PMRelease(session); +#else + PMSessionEndPageNoDialog(session); + PMSessionEndDocumentNoDialog(session); + [printInfo release]; +#endif + printInfo = 0; + session = 0; +} + +bool QMacPrintEnginePrivate::newPage_helper() +{ + Q_Q(QMacPrintEngine); + Q_ASSERT(state == QPrinter::Active); + + if (PMSessionError(session) != noErr) { + q->abort(); + return false; + } + + // pop the stack of saved graphic states, in case we get the same + // context back - either way, the stack count should be 0 when we + // get the new one + QCoreGraphicsPaintEngine *cgEngine = static_cast<QCoreGraphicsPaintEngine*>(paintEngine); + while (cgEngine->d_func()->stackCount > 0) + cgEngine->d_func()->restoreGraphicsState(); + + OSStatus status = +#ifndef QT_MAC_USE_COCOA + shouldSuppressStatus() ? PMSessionBeginPageNoDialog(session, format, 0) + : PMSessionBeginPage(session, format, 0); +#else + PMSessionBeginPageNoDialog(session, format, 0); +#endif + if(status != noErr) { + state = QPrinter::Error; + return false; + } + + QRect page = q->property(QPrintEngine::PPK_PageRect).toRect(); + QRect paper = q->property(QPrintEngine::PPK_PaperRect).toRect(); + + CGContextRef cgContext; + OSStatus err = noErr; + err = PMSessionGetCGGraphicsContext(session, &cgContext); + if(err != noErr) { + qWarning("QMacPrintEngine::newPage: Cannot retrieve CoreGraphics context: %ld", long(err)); + state = QPrinter::Error; + return false; + } + cgEngine->d_func()->hd = cgContext; + + // Set the resolution as a scaling ration of 72 (the default). + CGContextScaleCTM(cgContext, 72 / resolution.hRes, 72 / resolution.vRes); + + CGContextScaleCTM(cgContext, 1, -1); + CGContextTranslateCTM(cgContext, 0, -paper.height()); + if (!fullPage) + CGContextTranslateCTM(cgContext, page.x() - paper.x(), page.y() - paper.y()); + cgEngine->d_func()->orig_xform = CGContextGetCTM(cgContext); + cgEngine->d_func()->setClip(0); + cgEngine->state->dirtyFlags = QPaintEngine::DirtyFlag(QPaintEngine::AllDirty + & ~(QPaintEngine::DirtyClipEnabled + | QPaintEngine::DirtyClipRegion + | QPaintEngine::DirtyClipPath)); + if (cgEngine->painter()->hasClipping()) + cgEngine->state->dirtyFlags |= QPaintEngine::DirtyClipEnabled; + cgEngine->syncState(); + return true; +} + + +void QMacPrintEngine::updateState(const QPaintEngineState &state) +{ + d_func()->paintEngine->updateState(state); +} + +void QMacPrintEngine::drawRects(const QRectF *r, int num) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawRects(r, num); +} + +void QMacPrintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPoints(points, pointCount); +} + +void QMacPrintEngine::drawEllipse(const QRectF &r) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawEllipse(r); +} + +void QMacPrintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawLines(lines, lineCount); +} + +void QMacPrintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPolygon(points, pointCount, mode); +} + +void QMacPrintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPixmap(r, pm, sr); +} + +void QMacPrintEngine::drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawImage(r, pm, sr, flags); +} + +void QMacPrintEngine::drawTextItem(const QPointF &p, const QTextItem &ti) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawTextItem(p, ti); +} + +void QMacPrintEngine::drawTiledPixmap(const QRectF &dr, const QPixmap &pixmap, const QPointF &sr) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawTiledPixmap(dr, pixmap, sr); +} + +void QMacPrintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPath(path); +} + + +void QMacPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QMacPrintEngine); + + d->valueCache.insert(key, value); + if (!d->session) + return; + + switch (key) { + case PPK_CollateCopies: + break; + case PPK_ColorMode: + break; + case PPK_Creator: + break; + case PPK_DocumentName: + break; + case PPK_PageOrder: + break; + case PPK_PaperSource: + break; + case PPK_SelectionOption: + break; + case PPK_Resolution: { + PMPrinter printer; + UInt32 count; + if (PMSessionGetCurrentPrinter(d->session, &printer) != noErr) + break; + if (PMPrinterGetPrinterResolutionCount(printer, &count) != noErr) + break; + PMResolution resolution = { 0.0, 0.0 }; + PMResolution bestResolution = { 0.0, 0.0 }; + int dpi = value.toInt(); + int bestDistance = INT_MAX; + for (UInt32 i = 1; i <= count; ++i) { // Yes, it starts at 1 + if (PMPrinterGetIndexedPrinterResolution(printer, i, &resolution) == noErr) { + if (dpi == int(resolution.hRes)) { + bestResolution = resolution; + break; + } else { + int distance = qAbs(dpi - int(resolution.hRes)); + if (distance < bestDistance) { + bestDistance = distance; + bestResolution = resolution; + } + } + } + } + PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean); + break; + } + + case PPK_FullPage: + d->fullPage = value.toBool(); + break; + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + PMSetCopies(d->settings, value.toInt(), false); + break; + case PPK_Orientation: { + if (d->state == QPrinter::Active) { + qWarning("QMacPrintEngine::setOrientation: Orientation cannot be changed during a print job, ignoring change"); + } else { + QPrinter::Orientation newOrientation = QPrinter::Orientation(value.toInt()); + if (d->hasCustomPaperSize && (d->orient != newOrientation)) + d->customSize = QSizeF(d->customSize.height(), d->customSize.width()); + d->orient = newOrientation; + PMOrientation o = d->orient == QPrinter::Portrait ? kPMPortrait : kPMLandscape; + PMSetOrientation(d->format, o, false); + PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean); + } + break; } + case PPK_OutputFileName: + d->outputFilename = value.toString(); + break; + case PPK_PaperSize: + d->setPaperSize(QPrinter::PaperSize(value.toInt())); + break; + case PPK_PrinterName: { + bool printerNameSet = false; + OSStatus status = noErr; + QCFType<CFArrayRef> printerList; + status = PMServerCreatePrinterList(kPMServerLocal, &printerList); + if (status == noErr) { + CFIndex count = CFArrayGetCount(printerList); + for (CFIndex i=0; i<count; ++i) { + PMPrinter printer = static_cast<PMPrinter>(const_cast<void *>(CFArrayGetValueAtIndex(printerList, i))); + QString name = QCFString::toQString(PMPrinterGetName(printer)); + if (name == value.toString()) { + status = PMSessionSetCurrentPMPrinter(d->session, printer); + printerNameSet = true; + break; + } + } + } + if (status != noErr) + qWarning("QMacPrintEngine::setPrinterName: Error setting printer: %ld", long(status)); + if (!printerNameSet) { + qWarning("QMacPrintEngine::setPrinterName: Failed to set printer named '%s'.", qPrintable(value.toString())); + d->releaseSession(); + d->state = QPrinter::Idle; + } + break; } + case PPK_SuppressSystemPrintStatus: + d->suppressStatus = value.toBool(); + break; + case PPK_CustomPaperSize: + { + PMOrientation orientation; + PMGetOrientation(d->format, &orientation); + d->hasCustomPaperSize = true; + d->customSize = value.toSizeF(); + if (orientation != kPMPortrait) + d->customSize = QSizeF(d->customSize.height(), d->customSize.width()); + break; + } + case PPK_PageMargins: + { + QList<QVariant> margins(value.toList()); + Q_ASSERT(margins.size() == 4); + d->leftMargin = margins.at(0).toDouble(); + d->topMargin = margins.at(1).toDouble(); + d->rightMargin = margins.at(2).toDouble(); + d->bottomMargin = margins.at(3).toDouble(); + d->hasCustomPageMargins = true; + break; + } + + default: + break; + } +} + +QVariant QMacPrintEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QMacPrintEngine); + QVariant ret; + + if (!d->session && d->valueCache.contains(key)) + return *d->valueCache.find(key); + + switch (key) { + case PPK_CollateCopies: + ret = false; + break; + case PPK_ColorMode: + ret = QPrinter::Color; + break; + case PPK_Creator: + break; + case PPK_DocumentName: + break; + case PPK_FullPage: + ret = d->fullPage; + break; + case PPK_NumberOfCopies: + ret = 1; + break; + case PPK_CopyCount: { + UInt32 copies = 1; + PMGetCopies(d->settings, &copies); + ret = (uint) copies; + break; + } + case PPK_SupportsMultipleCopies: + ret = true; + break; + case PPK_Orientation: + PMOrientation orientation; + PMGetOrientation(d->format, &orientation); + ret = orientation == kPMPortrait ? QPrinter::Portrait : QPrinter::Landscape; + break; + case PPK_OutputFileName: + ret = d->outputFilename; + break; + case PPK_PageOrder: + break; + case PPK_PaperSource: + break; + case PPK_PageRect: { + // PageRect is returned in device pixels + QRect r; + PMRect macrect, macpaper; + qreal hRatio = d->resolution.hRes / 72; + qreal vRatio = d->resolution.vRes / 72; + if (d->hasCustomPaperSize) { + r = QRect(0, 0, qRound(d->customSize.width() * hRatio), qRound(d->customSize.height() * vRatio)); + if (d->hasCustomPageMargins) { + r.adjust(qRound(d->leftMargin * hRatio), qRound(d->topMargin * vRatio), + -qRound(d->rightMargin * hRatio), -qRound(d->bottomMargin * vRatio)); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + r.adjust(qRound(margins.at(0).toDouble() * hRatio), + qRound(margins.at(1).toDouble() * vRatio), + -qRound(margins.at(2).toDouble() * hRatio), + -qRound(margins.at(3).toDouble()) * vRatio); + } + } else if (PMGetAdjustedPageRect(d->format, ¯ect) == noErr + && PMGetAdjustedPaperRect(d->format, &macpaper) == noErr) + { + if (d->fullPage || d->hasCustomPageMargins) { + r.setCoords(int(macpaper.left * hRatio), int(macpaper.top * vRatio), + int(macpaper.right * hRatio), int(macpaper.bottom * vRatio)); + r.translate(-r.x(), -r.y()); + if (d->hasCustomPageMargins) { + r.adjust(qRound(d->leftMargin * hRatio), qRound(d->topMargin * vRatio), + -qRound(d->rightMargin * hRatio), -qRound(d->bottomMargin * vRatio)); + } + } else { + r.setCoords(int(macrect.left * hRatio), int(macrect.top * vRatio), + int(macrect.right * hRatio), int(macrect.bottom * vRatio)); + r.translate(int(-macpaper.left * hRatio), int(-macpaper.top * vRatio)); + } + } + ret = r; + break; } + case PPK_PaperSize: + ret = d->paperSize(); + break; + case PPK_PaperRect: { + QRect r; + PMRect macrect; + if (d->hasCustomPaperSize) { + r = QRect(0, 0, qRound(d->customSize.width()), qRound(d->customSize.height())); + } else if (PMGetAdjustedPaperRect(d->format, ¯ect) == noErr) { + qreal hRatio = d->resolution.hRes / 72; + qreal vRatio = d->resolution.vRes / 72; + r.setCoords(int(macrect.left * hRatio), int(macrect.top * vRatio), + int(macrect.right * hRatio), int(macrect.bottom * vRatio)); + r.translate(-r.x(), -r.y()); + } + ret = r; + break; } + case PPK_PrinterName: { + PMPrinter printer; + OSStatus status = PMSessionGetCurrentPrinter(d->session, &printer); + if (status != noErr) + qWarning("QMacPrintEngine::printerName: Failed getting current PMPrinter: %ld", long(status)); + if (printer) + ret = QCFString::toQString(PMPrinterGetName(printer)); + break; } + case PPK_Resolution: { + ret = d->resolution.hRes; + break; + } + case PPK_SupportedResolutions: + ret = d->supportedResolutions(); + break; + case PPK_CustomPaperSize: + ret = d->customSize; + break; + case PPK_PageMargins: + { + QList<QVariant> margins; + if (d->hasCustomPageMargins) { + margins << d->leftMargin << d->topMargin + << d->rightMargin << d->bottomMargin; + } else { + PMPaperMargins paperMargins; + PMPaper paper; + PMGetPageFormatPaper(d->format, &paper); + PMPaperGetMargins(paper, &paperMargins); + margins << paperMargins.left << paperMargins.top + << paperMargins.right << paperMargins.bottom; + } + ret = margins; + break; + } + default: + break; + } + return ret; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprintengine_mac_p.h b/src/gui/painting/qprintengine_mac_p.h new file mode 100644 index 0000000000..511705d26f --- /dev/null +++ b/src/gui/painting/qprintengine_mac_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_MAC_P_H +#define QPRINTENGINE_MAC_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. +// + +#ifndef QT_NO_PRINTER + +#include "QtGui/qprinter.h" +#include "QtGui/qprintengine.h" +#include "private/qpaintengine_mac_p.h" +#include "private/qpainter_p.h" + +#ifdef __OBJC__ +@class NSPrintInfo; +#else +typedef void NSPrintInfo; +#endif + +QT_BEGIN_NAMESPACE + +class QPrinterPrivate; +class QMacPrintEnginePrivate; +class QMacPrintEngine : public QPaintEngine, public QPrintEngine +{ + Q_DECLARE_PRIVATE(QMacPrintEngine) +public: + QMacPrintEngine(QPrinter::PrinterMode mode); + + Qt::HANDLE handle() const; + + bool begin(QPaintDevice *dev); + bool end(); + virtual QPaintEngine::Type type() const { return QPaintEngine::MacPrinter; } + + QPaintEngine *paintEngine() const; + + void setProperty(PrintEnginePropertyKey key, const QVariant &value); + QVariant property(PrintEnginePropertyKey key) const; + + QPrinter::PrinterState printerState() const; + + bool newPage(); + bool abort(); + int metric(QPaintDevice::PaintDeviceMetric) const; + + //forwarded functions + + void updateState(const QPaintEngineState &state); + + virtual void drawLines(const QLineF *lines, int lineCount); + virtual void drawRects(const QRectF *r, int num); + virtual void drawPoints(const QPointF *p, int pointCount); + virtual void drawEllipse(const QRectF &r); + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + virtual void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags); + virtual void drawTextItem(const QPointF &p, const QTextItem &ti); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + virtual void drawPath(const QPainterPath &); + +private: + friend class QPrintDialog; + friend class QPageSetupDialog; +}; + +class QMacPrintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QMacPrintEngine) +public: + QPrinter::PrinterMode mode; + QPrinter::PrinterState state; + QPrinter::Orientation orient; + NSPrintInfo *printInfo; + PMPageFormat format; + PMPrintSettings settings; + PMPrintSession session; + PMResolution resolution; + QString outputFilename; + bool fullPage; + QPaintEngine *paintEngine; + bool suppressStatus; + bool hasCustomPaperSize; + QSizeF customSize; + bool hasCustomPageMargins; + qreal leftMargin; + qreal topMargin; + qreal rightMargin; + qreal bottomMargin; + QHash<QMacPrintEngine::PrintEnginePropertyKey, QVariant> valueCache; + QMacPrintEnginePrivate() : mode(QPrinter::ScreenResolution), state(QPrinter::Idle), + orient(QPrinter::Portrait), printInfo(0), format(0), settings(0), + session(0), paintEngine(0), suppressStatus(false), + hasCustomPaperSize(false), hasCustomPageMargins(false) {} + ~QMacPrintEnginePrivate(); + void initialize(); + void releaseSession(); + bool newPage_helper(); + void setPaperSize(QPrinter::PaperSize ps); + QPrinter::PaperSize paperSize() const; + QList<QVariant> supportedResolutions() const; + inline bool isPrintSessionInitialized() const + { +#ifndef QT_MAC_USE_COCOA + return session != 0; +#else + return printInfo != 0; +#endif + } + bool shouldSuppressStatus() const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_WIN_P_H diff --git a/src/gui/painting/qprintengine_pdf.cpp b/src/gui/painting/qprintengine_pdf.cpp new file mode 100644 index 0000000000..b7f51606da --- /dev/null +++ b/src/gui/painting/qprintengine_pdf.cpp @@ -0,0 +1,1241 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/qprintengine.h> + +#include <qiodevice.h> +#include <qpainter.h> +#include <qbitmap.h> +#include <qpainterpath.h> +#include <qpaintdevice.h> +#include <qfile.h> +#include <qdebug.h> +#include <qimagewriter.h> +#include <qbuffer.h> +#include <qdatetime.h> + +#ifndef QT_NO_PRINTER +#include <limits.h> +#include <math.h> +#ifndef QT_NO_COMPRESS +#include <zlib.h> +#endif + +#if defined(Q_OS_WINCE) +#include "qwinfunctions_wince.h" +#endif + +#include "qprintengine_pdf_p.h" +#include "private/qdrawhelper_p.h" + +QT_BEGIN_NAMESPACE + +extern qint64 qt_pixmap_id(const QPixmap &pixmap); +extern qint64 qt_image_id(const QImage &image); + +//#define FONT_DUMP + +// might be helpful for smooth transforms of images +// Can't use it though, as gs generates completely wrong images if this is true. +static const bool interpolateImages = false; + +#ifdef QT_NO_COMPRESS +static const bool do_compress = false; +#else +static const bool do_compress = true; +#endif + +QPdfPage::QPdfPage() + : QPdf::ByteStream(true) // Enable file backing +{ +} + +void QPdfPage::streamImage(int w, int h, int object) +{ + *this << w << "0 0 " << -h << "0 " << h << "cm /Im" << object << " Do\n"; + if (!images.contains(object)) + images.append(object); +} + + +inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() +{ + QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures; + f &= ~(QPaintEngine::PorterDuff | QPaintEngine::PerspectiveTransform + | QPaintEngine::ObjectBoundingModeGradients +#ifndef USE_NATIVE_GRADIENTS + | QPaintEngine::LinearGradientFill +#endif + | QPaintEngine::RadialGradientFill + | QPaintEngine::ConicalGradientFill); + return f; +} + +QPdfEngine::QPdfEngine(QPrinter::PrinterMode m) + : QPdfBaseEngine(*new QPdfEnginePrivate(m), qt_pdf_decide_features()) +{ + state = QPrinter::Idle; +} + +QPdfEngine::~QPdfEngine() +{ +} + +bool QPdfEngine::begin(QPaintDevice *pdev) +{ + Q_D(QPdfEngine); + + if(!QPdfBaseEngine::begin(pdev)) { + state = QPrinter::Error; + return false; + } + d->stream->setDevice(d->outDevice); + + d->streampos = 0; + d->hasPen = true; + d->hasBrush = false; + d->clipEnabled = false; + d->allClipped = false; + + d->xrefPositions.clear(); + d->pageRoot = 0; + d->catalog = 0; + d->info = 0; + d->graphicsState = 0; + d->patternColorSpace = 0; + + d->pages.clear(); + d->imageCache.clear(); + + setActive(true); + state = QPrinter::Active; + d->writeHeader(); + newPage(); + + return true; +} + +bool QPdfEngine::end() +{ + Q_D(QPdfEngine); + d->writeTail(); + + d->stream->unsetDevice(); + QPdfBaseEngine::end(); + setActive(false); + state = QPrinter::Idle; + return true; +} + + +void QPdfEngine::drawPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QRectF &sr) +{ + if (sr.isEmpty() || rectangle.isEmpty() || pixmap.isNull()) + return; + Q_D(QPdfEngine); + + QBrush b = d->brush; + + QRect sourceRect = sr.toRect(); + QPixmap pm = sourceRect != pixmap.rect() ? pixmap.copy(sourceRect) : pixmap; + QImage image = pm.toImage(); + bool bitmap = true; + const int object = d->addImage(image, &bitmap, pm.cacheKey()); + if (object < 0) + return; + + *d->currentPage << "q\n/GSa gs\n"; + *d->currentPage + << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), + rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); + if (bitmap) { + // set current pen as d->brush + d->brush = d->pen.brush(); + } + setBrush(); + d->currentPage->streamImage(image.width(), image.height(), object); + *d->currentPage << "Q\n"; + + d->brush = b; +} + +void QPdfEngine::drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags) +{ + if (sr.isEmpty() || rectangle.isEmpty() || image.isNull()) + return; + Q_D(QPdfEngine); + + QRect sourceRect = sr.toRect(); + QImage im = sourceRect != image.rect() ? image.copy(sourceRect) : image; + bool bitmap = true; + const int object = d->addImage(im, &bitmap, im.cacheKey()); + if (object < 0) + return; + + *d->currentPage << "q\n/GSa gs\n"; + *d->currentPage + << QPdf::generateMatrix(QTransform(rectangle.width() / sr.width(), 0, 0, rectangle.height() / sr.height(), + rectangle.x(), rectangle.y()) * (d->simplePen ? QTransform() : d->stroker.matrix)); + setBrush(); + d->currentPage->streamImage(im.width(), im.height(), object); + *d->currentPage << "Q\n"; +} + +void QPdfEngine::drawTiledPixmap (const QRectF &rectangle, const QPixmap &pixmap, const QPointF &point) +{ + Q_D(QPdfEngine); + + bool bitmap = (pixmap.depth() == 1); + QBrush b = d->brush; + QPointF bo = d->brushOrigin; + bool hp = d->hasPen; + d->hasPen = false; + bool hb = d->hasBrush; + d->hasBrush = true; + + d->brush = QBrush(pixmap); + if (bitmap) + // #### fix bitmap case where we have a brush pen + d->brush.setColor(d->pen.color()); + + d->brushOrigin = -point; + *d->currentPage << "q\n"; + setBrush(); + + drawRects(&rectangle, 1); + *d->currentPage << "Q\n"; + + d->hasPen = hp; + d->hasBrush = hb; + d->brush = b; + d->brushOrigin = bo; +} + + +void QPdfEngine::setBrush() +{ + Q_D(QPdfEngine); + Qt::BrushStyle style = d->brush.style(); + if (style == Qt::NoBrush) + return; + + bool specifyColor; + int gStateObject = 0; + int patternObject = d->addBrushPattern(d->stroker.matrix, &specifyColor, &gStateObject); + + *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs "); + if (specifyColor) { + QColor rgba = d->brush.color(); + if (d->colorMode == QPrinter::GrayScale) { + qreal gray = qGray(rgba.rgba())/255.; + *d->currentPage << gray << gray << gray; + } else { + *d->currentPage << rgba.redF() + << rgba.greenF() + << rgba.blueF(); + } + } + if (patternObject) + *d->currentPage << "/Pat" << patternObject; + *d->currentPage << "scn\n"; + + if (gStateObject) + *d->currentPage << "/GState" << gStateObject << "gs\n"; + else + *d->currentPage << "/GSa gs\n"; +} + +QPaintEngine::Type QPdfEngine::type() const +{ + return QPaintEngine::Pdf; +} + +bool QPdfEngine::newPage() +{ + Q_D(QPdfEngine); + if (!isActive()) + return false; + d->newPage(); + return QPdfBaseEngine::newPage(); +} + +QPdfEnginePrivate::QPdfEnginePrivate(QPrinter::PrinterMode m) + : QPdfBaseEnginePrivate(m) +{ + streampos = 0; + + stream = new QDataStream; + pageOrder = QPrinter::FirstPageFirst; + orientation = QPrinter::Portrait; + fullPage = false; +} + +QPdfEnginePrivate::~QPdfEnginePrivate() +{ + delete stream; +} + + +#ifdef USE_NATIVE_GRADIENTS +int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QMatrix &matrix, int *gStateObject) +{ + const QGradient *gradient = b.gradient(); + if (!gradient) + return 0; + + QTransform inv = matrix.inverted(); + QPointF page_rect[4] = { inv.map(QPointF(0, 0)), + inv.map(QPointF(width_, 0)), + inv.map(QPointF(0, height_)), + inv.map(QPointF(width_, height_)) }; + + bool opaque = b.isOpaque(); + + QByteArray shader; + QByteArray alphaShader; + if (gradient->type() == QGradient::LinearGradient) { + const QLinearGradient *lg = static_cast<const QLinearGradient *>(gradient); + shader = QPdf::generateLinearGradientShader(lg, page_rect); + if (!opaque) + alphaShader = QPdf::generateLinearGradientShader(lg, page_rect, true); + } else { + // ############# + return 0; + } + int shaderObject = addXrefEntry(-1); + write(shader); + + QByteArray str; + QPdf::ByteStream s(&str); + s << "<<\n" + "/Type /Pattern\n" + "/PatternType 2\n" + "/Shading " << shaderObject << "0 R\n" + "/Matrix [" + << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() << "]\n"; + s << ">>\n" + "endobj\n"; + + int patternObj = addXrefEntry(-1); + write(str); + currentPage->patterns.append(patternObj); + + if (!opaque) { + bool ca = true; + QGradientStops stops = gradient->stops(); + int a = stops.at(0).second.alpha(); + for (int i = 1; i < stops.size(); ++i) { + if (stops.at(i).second.alpha() != a) { + ca = false; + break; + } + } + if (ca) { + *gStateObject = addConstantAlphaObject(stops.at(0).second.alpha()); + } else { + int alphaShaderObject = addXrefEntry(-1); + write(alphaShader); + + QByteArray content; + QPdf::ByteStream c(&content); + c << "/Shader" << alphaShaderObject << "sh\n"; + + QByteArray form; + QPdf::ByteStream f(&form); + f << "<<\n" + "/Type /XObject\n" + "/Subtype /Form\n" + "/BBox [0 0 " << width_ << height_ << "]\n" + "/Group <</S /Transparency >>\n" + "/Resources <<\n" + "/Shading << /Shader" << alphaShaderObject << alphaShaderObject << "0 R >>\n" + ">>\n"; + + f << "/Length " << content.length() << "\n" + ">>\n" + "stream\n" + << content + << "endstream\n" + "endobj\n"; + + int softMaskFormObject = addXrefEntry(-1); + write(form); + *gStateObject = addXrefEntry(-1); + xprintf("<< /SMask << /S /Alpha /G %d 0 R >> >>\n" + "endobj\n", softMaskFormObject); + currentPage->graphicStates.append(*gStateObject); + } + } + + return patternObj; +} +#endif + +int QPdfEnginePrivate::addConstantAlphaObject(int brushAlpha, int penAlpha) +{ + if (brushAlpha == 255 && penAlpha == 255) + return 0; + int object = alphaCache.value(QPair<uint, uint>(brushAlpha, penAlpha), 0); + if (!object) { + object = addXrefEntry(-1); + QByteArray alphaDef; + QPdf::ByteStream s(&alphaDef); + s << "<<\n/ca " << (brushAlpha/qreal(255.)) << '\n'; + s << "/CA " << (penAlpha/qreal(255.)) << "\n>>"; + xprintf("%s\nendobj\n", alphaDef.constData()); + alphaCache.insert(QPair<uint, uint>(brushAlpha, penAlpha), object); + } + if (currentPage->graphicStates.indexOf(object) < 0) + currentPage->graphicStates.append(object); + + return object; +} + +int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, int *gStateObject) +{ + int paintType = 2; // Uncolored tiling + int w = 8; + int h = 8; + + *specifyColor = true; + *gStateObject = 0; + + QTransform matrix = m; + matrix.translate(brushOrigin.x(), brushOrigin.y()); + matrix = matrix * pageMatrix(); + //qDebug() << brushOrigin << matrix; + + Qt::BrushStyle style = brush.style(); + if (style == Qt::LinearGradientPattern) {// && style <= Qt::ConicalGradientPattern) { +#ifdef USE_NATIVE_GRADIENTS + *specifyColor = false; + return gradientBrush(b, matrix, gStateObject); +#else + return 0; +#endif + } + + if ((!brush.isOpaque() && brush.style() < Qt::LinearGradientPattern) || opacity != 1.0) + *gStateObject = addConstantAlphaObject(qRound(brush.color().alpha() * opacity), + qRound(pen.color().alpha() * opacity)); + + int imageObject = -1; + QByteArray pattern = QPdf::patternForBrush(brush); + if (pattern.isEmpty()) { + if (brush.style() != Qt::TexturePattern) + return 0; + QImage image = brush.texture().toImage(); + bool bitmap = true; + imageObject = addImage(image, &bitmap, qt_pixmap_id(brush.texture())); + if (imageObject != -1) { + QImage::Format f = image.format(); + if (f != QImage::Format_MonoLSB && f != QImage::Format_Mono) { + paintType = 1; // Colored tiling + *specifyColor = false; + } + w = image.width(); + h = image.height(); + QTransform m(w, 0, 0, -h, 0, h); + QPdf::ByteStream s(&pattern); + s << QPdf::generateMatrix(m); + s << "/Im" << imageObject << " Do\n"; + } + } + + QByteArray str; + QPdf::ByteStream s(&str); + s << "<<\n" + "/Type /Pattern\n" + "/PatternType 1\n" + "/PaintType " << paintType << "\n" + "/TilingType 1\n" + "/BBox [0 0 " << w << h << "]\n" + "/XStep " << w << "\n" + "/YStep " << h << "\n" + "/Matrix [" + << matrix.m11() + << matrix.m12() + << matrix.m21() + << matrix.m22() + << matrix.dx() + << matrix.dy() << "]\n" + "/Resources \n<< "; // open resource tree + if (imageObject > 0) { + s << "/XObject << /Im" << imageObject << ' ' << imageObject << "0 R >> "; + } + s << ">>\n" + "/Length " << pattern.length() << "\n" + ">>\n" + "stream\n" + << pattern + << "endstream\n" + "endobj\n"; + + int patternObj = addXrefEntry(-1); + write(str); + currentPage->patterns.append(patternObj); + return patternObj; +} + +/*! + * Adds an image to the pdf and return the pdf-object id. Returns -1 if adding the image failed. + */ +int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_no) +{ + if (img.isNull()) + return -1; + + int object = imageCache.value(serial_no); + if(object) + return object; + + QImage image = img; + QImage::Format format = image.format(); + if (image.depth() == 1 && *bitmap && img.colorTable().size() == 0) { + if (format == QImage::Format_MonoLSB) + image = image.convertToFormat(QImage::Format_Mono); + format = QImage::Format_Mono; + } else { + *bitmap = false; + if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + format = QImage::Format_ARGB32; + } + } + + int w = image.width(); + int h = image.height(); + int d = image.depth(); + + if (format == QImage::Format_Mono) { + int bytesPerLine = (w + 7) >> 3; + QByteArray data; + data.resize(bytesPerLine * h); + char *rawdata = data.data(); + for (int y = 0; y < h; ++y) { + memcpy(rawdata, image.scanLine(y), bytesPerLine); + rawdata += bytesPerLine; + } + object = writeImage(data, w, h, d, 0, 0); + } else { + QByteArray softMaskData; + bool dct = false; + QByteArray imageData; + bool hasAlpha = false; + bool hasMask = false; + + if (QImageWriter::supportedImageFormats().contains("jpeg") && colorMode != QPrinter::GrayScale) { + QBuffer buffer(&imageData); + QImageWriter writer(&buffer, "jpeg"); + writer.setQuality(94); + writer.write(image); + dct = true; + + if (format != QImage::Format_RGB32) { + softMaskData.resize(w * h); + uchar *sdata = (uchar *)softMaskData.data(); + for (int y = 0; y < h; ++y) { + const QRgb *rgb = (const QRgb *)image.scanLine(y); + for (int x = 0; x < w; ++x) { + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } + } + } else { + imageData.resize(colorMode == QPrinter::GrayScale ? w * h : 3 * w * h); + uchar *data = (uchar *)imageData.data(); + softMaskData.resize(w * h); + uchar *sdata = (uchar *)softMaskData.data(); + for (int y = 0; y < h; ++y) { + const QRgb *rgb = (const QRgb *)image.scanLine(y); + if (colorMode == QPrinter::GrayScale) { + for (int x = 0; x < w; ++x) { + *(data++) = qGray(*rgb); + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } else { + for (int x = 0; x < w; ++x) { + *(data++) = qRed(*rgb); + *(data++) = qGreen(*rgb); + *(data++) = qBlue(*rgb); + uchar alpha = qAlpha(*rgb); + *sdata++ = alpha; + hasMask |= (alpha < 255); + hasAlpha |= (alpha != 0 && alpha != 255); + ++rgb; + } + } + } + if (format == QImage::Format_RGB32) + hasAlpha = hasMask = false; + } + int maskObject = 0; + int softMaskObject = 0; + if (hasAlpha) { + softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0); + } else if (hasMask) { + // dither the soft mask to 1bit and add it. This also helps PDF viewers + // without transparency support + int bytesPerLine = (w + 7) >> 3; + QByteArray mask(bytesPerLine * h, 0); + uchar *mdata = (uchar *)mask.data(); + const uchar *sdata = (const uchar *)softMaskData.constData(); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (*sdata) + mdata[x>>3] |= (0x80 >> (x&7)); + ++sdata; + } + mdata += bytesPerLine; + } + maskObject = writeImage(mask, w, h, 1, 0, 0); + } + object = writeImage(imageData, w, h, colorMode == QPrinter::GrayScale ? 8 : 32, + maskObject, softMaskObject, dct); + } + imageCache.insert(serial_no, object); + return object; +} + +void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) +{ + if (ti.charFormat.isAnchor()) { + qreal size = ti.fontEngine->fontDef.pixelSize; +#ifdef Q_WS_WIN + if (ti.fontEngine->type() == QFontEngine::Win) { + QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine); + size = fe->tm.tmHeight; + } +#endif + int synthesized = ti.fontEngine->synthesized(); + qreal stretch = synthesized & QFontEngine::SynthesizedStretch ? ti.fontEngine->fontDef.stretch/100. : 1.; + + QTransform trans; + // Build text rendering matrix (Trm). We need it to map the text area to user + // space units on the PDF page. + trans = QTransform(size*stretch, 0, 0, size, 0, 0); + // Apply text matrix (Tm). + trans *= QTransform(1,0,0,-1,p.x(),p.y()); + // Apply page displacement (Identity for first page). + trans *= stroker.matrix; + // Apply Current Transformation Matrix (CTM) + trans *= pageMatrix(); + qreal x1, y1, x2, y2; + trans.map(0, 0, &x1, &y1); + trans.map(ti.width.toReal()/size, (ti.ascent.toReal()-ti.descent.toReal())/size, &x2, &y2); + + uint annot = addXrefEntry(-1); +#ifdef Q_DEBUG_PDF_LINKS + xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [%f %f %f %f]\n/Border [16 16 1]\n/A <<\n", +#else + xprintf("<<\n/Type /Annot\n/Subtype /Link\n/Rect [%f %f %f %f]\n/Border [0 0 0]\n/A <<\n", +#endif + static_cast<double>(x1), + static_cast<double>(y1), + static_cast<double>(x2), + static_cast<double>(y2)); + xprintf("/Type /Action\n/S /URI\n/URI (%s)\n", + ti.charFormat.anchorHref().toLatin1().constData()); + xprintf(">>\n>>\n"); + xprintf("endobj\n"); + + if (!currentPage->annotations.contains(annot)) { + currentPage->annotations.append(annot); + } + } + + QPdfBaseEnginePrivate::drawTextItem(p, ti); +} + +QTransform QPdfEnginePrivate::pageMatrix() const +{ + qreal scale = 72./resolution; + QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, height()); + if (!fullPage) { + QRect r = pageRect(); + tmp.translate(r.left(), r.top()); + } + return tmp; +} + +void QPdfEnginePrivate::newPage() +{ + if (currentPage && currentPage->pageSize.isEmpty()) + currentPage->pageSize = QSize(width(), height()); + writePage(); + + delete currentPage; + currentPage = new QPdfPage; + currentPage->pageSize = QSize(width(), height()); + stroker.stream = currentPage; + pages.append(requestObject()); + + *currentPage << "/GSa gs /CSp cs /CSp CS\n" + << QPdf::generateMatrix(pageMatrix()) + << "q q\n"; +} + + +// For strings up to 10000 bytes only ! +void QPdfEnginePrivate::xprintf(const char* fmt, ...) +{ + if (!stream) + return; + + const int msize = 10000; + char buf[msize]; + + va_list args; + va_start(args, fmt); + int bufsize = qvsnprintf(buf, msize, fmt, args); + + Q_ASSERT(bufsize<msize); + + va_end(args); + + stream->writeRawData(buf, bufsize); + streampos += bufsize; +} + +int QPdfEnginePrivate::writeCompressed(QIODevice *dev) +{ +#ifndef QT_NO_COMPRESS + if (do_compress) { + int size = QPdfPage::chunkSize(); + int sum = 0; + ::z_stream zStruct; + zStruct.zalloc = Z_NULL; + zStruct.zfree = Z_NULL; + zStruct.opaque = Z_NULL; + if (::deflateInit(&zStruct, Z_DEFAULT_COMPRESSION) != Z_OK) { + qWarning("QPdfStream::writeCompressed: Error in deflateInit()"); + return sum; + } + zStruct.avail_in = 0; + QByteArray in, out; + out.resize(size); + while (!dev->atEnd() || zStruct.avail_in != 0) { + if (zStruct.avail_in == 0) { + in = dev->read(size); + zStruct.avail_in = in.size(); + zStruct.next_in = reinterpret_cast<unsigned char*>(in.data()); + if (in.size() <= 0) { + qWarning("QPdfStream::writeCompressed: Error in read()"); + ::deflateEnd(&zStruct); + return sum; + } + } + zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); + zStruct.avail_out = out.size(); + if (::deflate(&zStruct, 0) != Z_OK) { + qWarning("QPdfStream::writeCompressed: Error in deflate()"); + ::deflateEnd(&zStruct); + return sum; + } + int written = out.size() - zStruct.avail_out; + stream->writeRawData(out.constData(), written); + streampos += written; + sum += written; + } + int ret; + do { + zStruct.next_out = reinterpret_cast<unsigned char*>(out.data()); + zStruct.avail_out = out.size(); + ret = ::deflate(&zStruct, Z_FINISH); + if (ret != Z_OK && ret != Z_STREAM_END) { + qWarning("QPdfStream::writeCompressed: Error in deflate()"); + ::deflateEnd(&zStruct); + return sum; + } + int written = out.size() - zStruct.avail_out; + stream->writeRawData(out.constData(), written); + streampos += written; + sum += written; + } while (ret == Z_OK); + + ::deflateEnd(&zStruct); + + return sum; + } else +#endif + { + QByteArray arr; + int sum = 0; + while (!dev->atEnd()) { + arr = dev->read(QPdfPage::chunkSize()); + stream->writeRawData(arr.constData(), arr.size()); + streampos += arr.size(); + sum += arr.size(); + } + return sum; + } +} + +int QPdfEnginePrivate::writeCompressed(const char *src, int len) +{ +#ifndef QT_NO_COMPRESS + if(do_compress) { + uLongf destLen = len + len/100 + 13; // zlib requirement + Bytef* dest = new Bytef[destLen]; + if (Z_OK == ::compress(dest, &destLen, (const Bytef*) src, (uLongf)len)) { + stream->writeRawData((const char*)dest, destLen); + } else { + qWarning("QPdfStream::writeCompressed: Error in compress()"); + destLen = 0; + } + delete [] dest; + len = destLen; + } else +#endif + { + stream->writeRawData(src,len); + } + streampos += len; + return len; +} + +int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth, + int maskObject, int softMaskObject, bool dct) +{ + int image = addXrefEntry(-1); + xprintf("<<\n" + "/Type /XObject\n" + "/Subtype /Image\n" + "/Width %d\n" + "/Height %d\n", width, height); + + if (depth == 1) { + xprintf("/ImageMask true\n" + "/Decode [1 0]\n"); + } else { + xprintf("/BitsPerComponent 8\n" + "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray"); + } + if (maskObject > 0) + xprintf("/Mask %d 0 R\n", maskObject); + if (softMaskObject > 0) + xprintf("/SMask %d 0 R\n", softMaskObject); + + int lenobj = requestObject(); + xprintf("/Length %d 0 R\n", lenobj); + if (interpolateImages) + xprintf("/Interpolate true\n"); + int len = 0; + if (dct) { + //qDebug() << "DCT"; + xprintf("/Filter /DCTDecode\n>>\nstream\n"); + write(data); + len = data.length(); + } else { + if (do_compress) + xprintf("/Filter /FlateDecode\n>>\nstream\n"); + else + xprintf(">>\nstream\n"); + len = writeCompressed(data); + } + xprintf("endstream\n" + "endobj\n"); + addXrefEntry(lenobj); + xprintf("%d\n" + "endobj\n", len); + return image; +} + + +void QPdfEnginePrivate::writeHeader() +{ + addXrefEntry(0,false); + + xprintf("%%PDF-1.4\n"); + + writeInfo(); + + catalog = addXrefEntry(-1); + pageRoot = requestObject(); + xprintf("<<\n" + "/Type /Catalog\n" + "/Pages %d 0 R\n" + ">>\n" + "endobj\n", pageRoot); + + // graphics state + graphicsState = addXrefEntry(-1); + xprintf("<<\n" + "/Type /ExtGState\n" + "/SA true\n" + "/SM 0.02\n" + "/ca 1.0\n" + "/CA 1.0\n" + "/AIS false\n" + "/SMask /None" + ">>\n" + "endobj\n"); + + // color space for pattern + patternColorSpace = addXrefEntry(-1); + xprintf("[/Pattern /DeviceRGB]\n" + "endobj\n"); +} + +void QPdfEnginePrivate::writeInfo() +{ + info = addXrefEntry(-1); + xprintf("<<\n/Title "); + printString(title); + xprintf("\n/Creator "); + printString(creator); + xprintf("\n/Producer "); + printString(QString::fromLatin1("Qt " QT_VERSION_STR " (C) 2011 Nokia Corporation and/or its subsidiary(-ies)")); + QDateTime now = QDateTime::currentDateTime().toUTC(); + QTime t = now.time(); + QDate d = now.date(); + xprintf("\n/CreationDate (D:%d%02d%02d%02d%02d%02d)\n", + d.year(), + d.month(), + d.day(), + t.hour(), + t.minute(), + t.second()); + xprintf(">>\n" + "endobj\n"); +} + +void QPdfEnginePrivate::writePageRoot() +{ + addXrefEntry(pageRoot); + + xprintf("<<\n" + "/Type /Pages\n" + "/Kids \n" + "[\n"); + int size = pages.size(); + for (int i = 0; i < size; ++i) + xprintf("%d 0 R\n", pages[i]); + xprintf("]\n"); + + //xprintf("/Group <</S /Transparency /I true /K false>>\n"); + xprintf("/Count %d\n", pages.size()); + + xprintf("/ProcSet [/PDF /Text /ImageB /ImageC]\n" + ">>\n" + "endobj\n"); +} + + +void QPdfEnginePrivate::embedFont(QFontSubset *font) +{ + //qDebug() << "embedFont" << font->object_id; + int fontObject = font->object_id; + QByteArray fontData = font->toTruetype(); +#ifdef FONT_DUMP + static int i = 0; + QString fileName("font%1.ttf"); + fileName = fileName.arg(i++); + QFile ff(fileName); + ff.open(QFile::WriteOnly); + ff.write(fontData); + ff.close(); +#endif + + int fontDescriptor = requestObject(); + int fontstream = requestObject(); + int cidfont = requestObject(); + int toUnicode = requestObject(); + + QFontEngine::Properties properties = font->fontEngine->properties(); + + { + qreal scale = 1000/properties.emSquare.toReal(); + addXrefEntry(fontDescriptor); + QByteArray descriptor; + QPdf::ByteStream s(&descriptor); + s << "<< /Type /FontDescriptor\n" + "/FontName /Q"; + int tag = fontDescriptor; + for (int i = 0; i < 5; ++i) { + s << (char)('A' + (tag % 26)); + tag /= 26; + } + s << '+' << properties.postscriptName << "\n" + "/Flags " << 4 << "\n" + "/FontBBox [" + << properties.boundingBox.x()*scale + << -(properties.boundingBox.y() + properties.boundingBox.height())*scale + << (properties.boundingBox.x() + properties.boundingBox.width())*scale + << -properties.boundingBox.y()*scale << "]\n" + "/ItalicAngle " << properties.italicAngle.toReal() << "\n" + "/Ascent " << properties.ascent.toReal()*scale << "\n" + "/Descent " << -properties.descent.toReal()*scale << "\n" + "/CapHeight " << properties.capHeight.toReal()*scale << "\n" + "/StemV " << properties.lineWidth.toReal()*scale << "\n" + "/FontFile2 " << fontstream << "0 R\n" + ">> endobj\n"; + write(descriptor); + } + { + addXrefEntry(fontstream); + QByteArray header; + QPdf::ByteStream s(&header); + + int length_object = requestObject(); + s << "<<\n" + "/Length1 " << fontData.size() << "\n" + "/Length " << length_object << "0 R\n"; + if (do_compress) + s << "/Filter /FlateDecode\n"; + s << ">>\n" + "stream\n"; + write(header); + int len = writeCompressed(fontData); + write("endstream\n" + "endobj\n"); + addXrefEntry(length_object); + xprintf("%d\n" + "endobj\n", len); + } + { + addXrefEntry(cidfont); + QByteArray cid; + QPdf::ByteStream s(&cid); + s << "<< /Type /Font\n" + "/Subtype /CIDFontType2\n" + "/BaseFont /" << properties.postscriptName << "\n" + "/CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >>\n" + "/FontDescriptor " << fontDescriptor << "0 R\n" + "/CIDToGIDMap /Identity\n" + << font->widthArray() << + ">>\n" + "endobj\n"; + write(cid); + } + { + addXrefEntry(toUnicode); + QByteArray touc = font->createToUnicodeMap(); + xprintf("<< /Length %d >>\n" + "stream\n", touc.length()); + write(touc); + write("endstream\n" + "endobj\n"); + } + { + addXrefEntry(fontObject); + QByteArray font; + QPdf::ByteStream s(&font); + s << "<< /Type /Font\n" + "/Subtype /Type0\n" + "/BaseFont /" << properties.postscriptName << "\n" + "/Encoding /Identity-H\n" + "/DescendantFonts [" << cidfont << "0 R]\n" + "/ToUnicode " << toUnicode << "0 R" + ">>\n" + "endobj\n"; + write(font); + } +} + + +void QPdfEnginePrivate::writeFonts() +{ + for (QHash<QFontEngine::FaceId, QFontSubset *>::iterator it = fonts.begin(); it != fonts.end(); ++it) { + embedFont(*it); + delete *it; + } + fonts.clear(); +} + +void QPdfEnginePrivate::writePage() +{ + if (pages.empty()) + return; + + *currentPage << "Q Q\n"; + + uint pageStream = requestObject(); + uint pageStreamLength = requestObject(); + uint resources = requestObject(); + uint annots = requestObject(); + + addXrefEntry(pages.last()); + xprintf("<<\n" + "/Type /Page\n" + "/Parent %d 0 R\n" + "/Contents %d 0 R\n" + "/Resources %d 0 R\n" + "/Annots %d 0 R\n" + "/MediaBox [0 0 %d %d]\n" + ">>\n" + "endobj\n", + pageRoot, pageStream, resources, annots, + // make sure we use the pagesize from when we started the page, since the user may have changed it + currentPage->pageSize.width(), currentPage->pageSize.height()); + + addXrefEntry(resources); + xprintf("<<\n" + "/ColorSpace <<\n" + "/PCSp %d 0 R\n" + "/CSp /DeviceRGB\n" + "/CSpg /DeviceGray\n" + ">>\n" + "/ExtGState <<\n" + "/GSa %d 0 R\n", + patternColorSpace, graphicsState); + + for (int i = 0; i < currentPage->graphicStates.size(); ++i) + xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i)); + xprintf(">>\n"); + + xprintf("/Pattern <<\n"); + for (int i = 0; i < currentPage->patterns.size(); ++i) + xprintf("/Pat%d %d 0 R\n", currentPage->patterns.at(i), currentPage->patterns.at(i)); + xprintf(">>\n"); + + xprintf("/Font <<\n"); + for (int i = 0; i < currentPage->fonts.size();++i) + xprintf("/F%d %d 0 R\n", currentPage->fonts[i], currentPage->fonts[i]); + xprintf(">>\n"); + + xprintf("/XObject <<\n"); + for (int i = 0; i<currentPage->images.size(); ++i) { + xprintf("/Im%d %d 0 R\n", currentPage->images.at(i), currentPage->images.at(i)); + } + xprintf(">>\n"); + + xprintf(">>\n" + "endobj\n"); + + addXrefEntry(annots); + xprintf("[ "); + for (int i = 0; i<currentPage->annotations.size(); ++i) { + xprintf("%d 0 R ", currentPage->annotations.at(i)); + } + xprintf("]\nendobj\n"); + + addXrefEntry(pageStream); + xprintf("<<\n" + "/Length %d 0 R\n", pageStreamLength); // object number for stream length object + if (do_compress) + xprintf("/Filter /FlateDecode\n"); + + xprintf(">>\n"); + xprintf("stream\n"); + QIODevice *content = currentPage->stream(); + int len = writeCompressed(content); + xprintf("endstream\n" + "endobj\n"); + + addXrefEntry(pageStreamLength); + xprintf("%d\nendobj\n",len); +} + +void QPdfEnginePrivate::writeTail() +{ + writePage(); + writeFonts(); + writePageRoot(); + addXrefEntry(xrefPositions.size(),false); + xprintf("xref\n" + "0 %d\n" + "%010d 65535 f \n", xrefPositions.size()-1, xrefPositions[0]); + + for (int i = 1; i < xrefPositions.size()-1; ++i) + xprintf("%010d 00000 n \n", xrefPositions[i]); + + xprintf("trailer\n" + "<<\n" + "/Size %d\n" + "/Info %d 0 R\n" + "/Root %d 0 R\n" + ">>\n" + "startxref\n%d\n" + "%%%%EOF\n", + xrefPositions.size()-1, info, catalog, xrefPositions.last()); +} + +int QPdfEnginePrivate::addXrefEntry(int object, bool printostr) +{ + if (object < 0) + object = requestObject(); + + if (object>=xrefPositions.size()) + xrefPositions.resize(object+1); + + xrefPositions[object] = streampos; + if (printostr) + xprintf("%d 0 obj\n",object); + + return object; +} + +void QPdfEnginePrivate::printString(const QString &string) { + // The 'text string' type in PDF is encoded either as PDFDocEncoding, or + // Unicode UTF-16 with a Unicode byte order mark as the first character + // (0xfeff), with the high-order byte first. + QByteArray array("(\xfe\xff"); + const ushort *utf16 = string.utf16(); + + for (int i=0; i < string.size(); ++i) { + char part[2] = {char((*(utf16 + i)) >> 8), char((*(utf16 + i)) & 0xff)}; + for(int j=0; j < 2; ++j) { + if (part[j] == '(' || part[j] == ')' || part[j] == '\\') + array.append('\\'); + array.append(part[j]); + } + } + array.append(")"); + write(array); +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprintengine_pdf_p.h b/src/gui/painting/qprintengine_pdf_p.h new file mode 100644 index 0000000000..13171ae501 --- /dev/null +++ b/src/gui/painting/qprintengine_pdf_p.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_PDF_P_H +#define QPRINTENGINE_PDF_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 "QtGui/qprintengine.h" + +#ifndef QT_NO_PRINTER +#include "QtCore/qmap.h" +#include "QtGui/qmatrix.h" +#include "QtCore/qstring.h" +#include "QtCore/qvector.h" +#include "QtGui/qpaintengine.h" +#include "QtGui/qpainterpath.h" +#include "QtCore/qdatastream.h" + +#include "private/qfontengine_p.h" +#include "private/qpdf_p.h" +#include "private/qpaintengine_p.h" + +QT_BEGIN_NAMESPACE + +// #define USE_NATIVE_GRADIENTS + +class QImage; +class QDataStream; +class QPen; +class QPointF; +class QRegion; +class QFile; +class QPdfEngine; + +class QPdfEnginePrivate; + +class QPdfEngine : public QPdfBaseEngine +{ + Q_DECLARE_PRIVATE(QPdfEngine) +public: + QPdfEngine(QPrinter::PrinterMode m); + virtual ~QPdfEngine(); + + // reimplementations QPaintEngine + bool begin(QPaintDevice *pdev); + bool end(); + void drawPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QRectF & sr); + void drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor); + void drawTiledPixmap (const QRectF & rectangle, const QPixmap & pixmap, const QPointF & point); + + Type type() const; + // end reimplementations QPaintEngine + + // reimplementations QPrintEngine + bool abort() {return false;} + bool newPage(); + QPrinter::PrinterState printerState() const {return state;} + // end reimplementations QPrintEngine + + void setBrush(); + + // ### unused, should have something for this in QPrintEngine + void setAuthor(const QString &author); + QString author() const; + + void setDevice(QIODevice* dev); + +private: + Q_DISABLE_COPY(QPdfEngine) + + QPrinter::PrinterState state; +}; + +class QPdfEnginePrivate : public QPdfBaseEnginePrivate +{ + Q_DECLARE_PUBLIC(QPdfEngine) +public: + QPdfEnginePrivate(QPrinter::PrinterMode m); + ~QPdfEnginePrivate(); + + void newPage(); + + int width() const { + QRect r = paperRect(); + return qRound(r.width()*72./resolution); + } + int height() const { + QRect r = paperRect(); + return qRound(r.height()*72./resolution); + } + + void writeHeader(); + void writeTail(); + + int addImage(const QImage &image, bool *bitmap, qint64 serial_no); + int addConstantAlphaObject(int brushAlpha, int penAlpha = 255); + int addBrushPattern(const QTransform &matrix, bool *specifyColor, int *gStateObject); + + void drawTextItem(const QPointF &p, const QTextItemInt &ti); + + QTransform pageMatrix() const; + +private: + Q_DISABLE_COPY(QPdfEnginePrivate) + +#ifdef USE_NATIVE_GRADIENTS + int gradientBrush(const QBrush &b, const QMatrix &matrix, int *gStateObject); +#endif + + void writeInfo(); + void writePageRoot(); + void writeFonts(); + void embedFont(QFontSubset *font); + + QVector<int> xrefPositions; + QDataStream* stream; + int streampos; + + int writeImage(const QByteArray &data, int width, int height, int depth, + int maskObject, int softMaskObject, bool dct = false); + void writePage(); + + int addXrefEntry(int object, bool printostr = true); + void printString(const QString &string); + void xprintf(const char* fmt, ...); + inline void write(const QByteArray &data) { + stream->writeRawData(data.constData(), data.size()); + streampos += data.size(); + } + + int writeCompressed(const char *src, int len); + inline int writeCompressed(const QByteArray &data) { return writeCompressed(data.constData(), data.length()); } + int writeCompressed(QIODevice *dev); + + // various PDF objects + int pageRoot, catalog, info, graphicsState, patternColorSpace; + QVector<uint> pages; + QHash<qint64, uint> imageCache; + QHash<QPair<uint, uint>, uint > alphaCache; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_PDF_P_H diff --git a/src/gui/painting/qprintengine_ps.cpp b/src/gui/painting/qprintengine_ps.cpp new file mode 100644 index 0000000000..d55d532fdb --- /dev/null +++ b/src/gui/painting/qprintengine_ps.cpp @@ -0,0 +1,972 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformdefs.h" + +#include <private/qprintengine_ps_p.h> +#include <private/qpainter_p.h> +#include <private/qfontengine_p.h> +#include <private/qpaintengine_p.h> +#include <private/qpdf_p.h> + +#ifndef QT_NO_PRINTER + +#include "qprinter.h" +#include "qpainter.h" +#include "qapplication.h" +#include "qpixmap.h" +#include "qimage.h" +#include "qdatetime.h" +#include "qstring.h" +#include "qbytearray.h" +#include "qhash.h" +#include "qbuffer.h" +#include "qsettings.h" +#include "qmap.h" +#include "qbitmap.h" +#include "qregion.h" +#include "qimagewriter.h" +#include <private/qpainterpath_p.h> +#include <qdebug.h> +#include <private/qdrawhelper_p.h> +#include <private/qmutexpool_p.h> + +#ifndef Q_OS_WIN +#include <unistd.h> +#endif +#include <stdlib.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +static bool qt_gen_epsf = false; + +void qt_generate_epsf(bool b) +{ + qt_gen_epsf = b; +} + +static const char *const ps_header = +"/BD{bind def}bind def/d2{dup dup}BD/ED{exch def}BD/D0{0 ED}BD/F{setfont}BD\n" +"/RL{rlineto}BD/CM{currentmatrix}BD/SM{setmatrix}BD/TR{translate}BD/SD\n" +"{setdash}BD/SC{aload pop setrgbcolor}BD/CR{currentfile read pop}BD/i{index}\n" +"BD/scs{setcolorspace}BD/DB{dict dup begin}BD/DE{end def}BD/ie{ifelse}BD/gs\n" +"{gsave}BD/gr{grestore}BD/w{setlinewidth}BD/d{setdash}BD/J{setlinecap}BD/j\n" +"{setlinejoin}BD/scn{3 array astore/BCol exch def}BD/SCN{3 array astore/PCol\n" +"exch def}BD/cm{6 array astore concat}BD/m{moveto}BD/l{lineto}BD/c{curveto}BD\n" +"/h{closepath}BD/W{clip}BD/W*{eoclip}BD/n{newpath}BD/q{gsave 10 dict begin}BD\n" +"/Q{end grestore}BD/re{4 2 roll m dup 0 exch RL exch 0 RL 0 exch neg RL h}BD\n" +"/S{gs PCol SC stroke gr n}BD/BT{gsave 10 dict begin/_m matrix CM def BCol\n" +"SC}BD/ET{end grestore}BD/Tf{/_fs ED findfont[_fs 0 0 _fs 0 0]makefont F}BD\n" +"/Tm{6 array astore concat}BD/Td{translate}BD/Tj{0 0 m show}BD/BDC{pop pop}BD\n" +"/EMC{}BD/BSt 0 def/WFi false def/BCol[1 1 1]def/PCol[0 0 0]def/BDArr[0.94\n" +"0.88 0.63 0.50 0.37 0.12 0.06]def/level3{/languagelevel where{pop\n" +"languagelevel 3 ge}{false}ie}BD/QCIgray D0/QCIcolor D0/QCIindex D0/QCI{\n" +"/colorimage where{pop false 3 colorimage}{exec/QCIcolor ED/QCIgray QCIcolor\n" +"length 3 idiv string def 0 1 QCIcolor length 3 idiv 1 sub{/QCIindex ED/_x\n" +"QCIindex 3 mul def QCIgray QCIindex QCIcolor _x get 0.30 mul QCIcolor _x 1\n" +"add get 0.59 mul QCIcolor _x 2 add get 0.11 mul add add cvi put}for QCIgray\n" +"image}ie}BD/di{gs TR 1 i 1 eq{pop pop false 3 1 roll BCol SC imagemask}{dup\n" +"false ne{level3}{false}ie{/_ma ED 8 eq{/_dc[0 1]def/DeviceGray}{/_dc[0 1 0 1\n" +"0 1]def/DeviceRGB}ie scs/_im ED/_mt ED/_h ED/_w ED <</ImageType 3/DataDict\n" +"<</ImageType 1/Width _w/Height _h/ImageMatrix _mt/DataSource _im\n" +"/BitsPerComponent 8/Decode _dc >>/MaskDict <</ImageType 1/Width _w/Height _h\n" +"/ImageMatrix _mt/DataSource _ma/BitsPerComponent 1/Decode[0 1]>>\n" +"/InterleaveType 3 >> image}{pop 8 4 1 roll 8 eq{image}{QCI}ie}ie}ie gr}BD/BF\n" +"{gs BSt 1 eq{BCol SC WFi{fill}{eofill}ie}if BSt 2 ge BSt 8 le and{BDArr BSt\n" +"2 sub get/_sc ED BCol{1. exch sub _sc mul 1. exch sub}forall 3 array astore\n" +"SC WFi{fill}{eofill}ie}if BSt 9 ge BSt 14 le and{WFi{W}{W*}ie pathbbox 3 i 3\n" +"i TR 4 2 roll 3 2 roll exch sub/_h ED sub/_w ED BCol SC 0.3 w n BSt 9 eq BSt\n" +"11 eq or{0 4 _h{dup 0 exch m _w exch l}for}if BSt 10 eq BSt 11 eq or{0 4 _w{\n" +"dup 0 m _h l}for}if BSt 12 eq BSt 14 eq or{_w _h gt{0 6 _w _h add{dup 0 m _h\n" +"sub _h l}for}{0 6 _w _h add{dup 0 exch m _w sub _w exch l}for}ie}if BSt 13\n" +"eq BSt 14 eq or{_w _h gt{0 6 _w _h add{dup _h m _h sub 0 l}for}{0 6 _w _h\n" +"add{dup _w exch m _w sub 0 exch l}for}ie}if stroke}if BSt 15 eq{}if BSt 24\n" +"eq{}if gr}BD/f{/WFi true def BF n}BD/f*{/WFi false def BF n}BD/B{/WFi true\n" +"def BF S n}BD/B*{/WFi false def BF S n}BD/QI{/C save def pageinit q n}BD/QP{\n" +"Q C restore showpage}BD/SPD{/setpagedevice where{<< 3 1 roll >>\n" +"setpagedevice}{pop pop}ie}BD/T1AddMapping{10 dict begin/glyphs ED/fnt ED\n" +"/current fnt/NumGlyphs get def/CMap fnt/CMap get def 0 1 glyphs length 1 sub\n" +"{glyphs exch get/gn ED current dup 256 mod/min ED 256 idiv/maj ED CMap dup\n" +"maj get dup null eq{pop 256 array 0 1 255{1 i exch/.notdef put}for}if dup\n" +"min gn put maj exch put/current current 1 add def}for fnt/CMap CMap put fnt\n" +"/NumGlyphs current put end}def/T1AddGlyphs{10 dict begin/glyphs ED/fnt ED\n" +"/current fnt/NumGlyphs get def/CMap fnt/CMap get def/CharStrings fnt\n" +"/CharStrings get def 0 1 glyphs length 2 idiv 1 sub{2 mul dup glyphs exch\n" +"get/gn ED 1 add glyphs exch get/cs ED current dup 256 mod/min ED 256 idiv\n" +"/maj ED CMap dup maj get dup null eq{pop 256 array 0 1 255{1 i exch/.notdef\n" +"put}for}if dup min gn put maj exch put CharStrings gn cs put/current current\n" +"1 add def}for fnt/CharStrings CharStrings put fnt/CMap CMap put fnt\n" +"/NumGlyphs current put end}def/StringAdd{1 i length 1 i length add string 3\n" +"1 roll 2 i 0 3 i putinterval 2 i 2 i length 2 i putinterval pop pop}def\n" +"/T1Setup{10 dict begin dup/FontName ED (-Base) StringAdd cvx cvn/Font ED\n" +"/MaxPage Font/NumGlyphs get 1 sub 256 idiv def/FDepVector MaxPage 1 add\n" +"array def/Encoding MaxPage 1 add array def 0 1 MaxPage{dup Encoding exch dup\n" +"put dup/Page ED FontName (-) StringAdd exch 20 string cvs StringAdd cvn Font\n" +"0 dict copy d2/CMap get Page get/Encoding exch put definefont FDepVector\n" +"exch Page exch put}for FontName cvn <</FontType 0/FMapType 2/FontMatrix[1 0\n" +"0 1 0 0]/Encoding Encoding/FDepVector FDepVector >> definefont pop end}def\n"; + + + +// ------------------------------End of static data ---------------------------------- + +// make sure DSC comments are not longer than 255 chars per line. +static QByteArray wrapDSC(const QByteArray &str) +{ + QByteArray dsc = str.simplified(); + const int wrapAt = 254; + QByteArray wrapped; + if (dsc.length() < wrapAt) + wrapped = dsc; + else { + wrapped = dsc.left(wrapAt); + QByteArray tmp = dsc.mid(wrapAt); + while (tmp.length() > wrapAt-3) { + wrapped += "\n%%+" + tmp.left(wrapAt-3); + tmp = tmp.mid(wrapAt-3); + } + wrapped += "\n%%+" + tmp; + } + return wrapped + '\n'; +} + +// ----------------------------- Internal class declarations ----------------------------- + +QPSPrintEnginePrivate::QPSPrintEnginePrivate(QPrinter::PrinterMode m) + : QPdfBaseEnginePrivate(m), + printerState(QPrinter::Idle), hugeDocument(false), headerDone(false) +{ + useAlphaEngine = true; + postscript = true; + + firstPage = true; + +#ifndef QT_NO_SETTINGS + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + embedFonts = settings.value(QLatin1String("embedFonts"), true).toBool(); +#else + embedFonts = true; +#endif +} + +QPSPrintEnginePrivate::~QPSPrintEnginePrivate() +{ +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include <qdebug.h> +QT_END_INCLUDE_NAMESPACE + +static void ps_r7(QPdf::ByteStream& stream, const char * s, int l) +{ + int i = 0; + uchar line[84]; + int col = 0; + + while(i < l) { + line[col++] = s[i++]; + if (i < l - 1 && col >= 76) { + line[col++] = '\n'; + line[col++] = '\0'; + stream << (const char *)line; + col = 0; + } + } + if (col > 0) { + while((col&3) != 0) + line[col++] = '%'; // use a comment as padding + line[col++] = '\n'; + line[col++] = '\0'; + stream << (const char *)line; + } +} + +static QByteArray runlengthEncode(const QByteArray &input) +{ + if (!input.length()) + return input; + + const char *data = input.constData(); + + QByteArray out; + int start = 0; + char last = *data; + + enum State { + Undef, + Equal, + Diff + }; + State state = Undef; + + int i = 1; + int written = 0; + while (1) { + bool flush = (i == input.size()); + if (!flush) { + switch(state) { + case Undef: + state = (last == data[i]) ? Equal : Diff; + break; + case Equal: + if (data[i] != last) + flush = true; + break; + case Diff: + if (data[i] == last) { + --i; + flush = true; + } + } + } + if (flush || i - start == 128) { + int size = i - start; + if (state == Equal) { + out.append((char)(uchar)(257-size)); + out.append(last); + written += size; + } else { + out.append((char)(uchar)size-1); + while (start < i) + out.append(data[start++]); + written += size; + } + state = Undef; + start = i; + if (i == input.size()) + break; + } + last = data[i]; + ++i; + }; + out.append((char)(uchar)128); + return out; +} + +enum format { + Raw, + Runlength, + DCT +}; +static const char *const filters[3] = { + " ", + "/RunLengthDecode filter ", + "/DCTDecode filter " +}; + +static QByteArray compressHelper(const QImage &image, bool gray, int *format) +{ + // we can't use premultiplied here + QByteArray pixelData; + int depth = image.depth(); + + Q_ASSERT(image.format() != QImage::Format_ARGB32_Premultiplied); + + if (depth != 1 && !gray && QImageWriter::supportedImageFormats().contains("jpeg")) { + QBuffer buffer(&pixelData); + QImageWriter writer(&buffer, "jpeg"); + writer.setQuality(94); + writer.write(image); + *format = DCT; + } else { + int width = image.width(); + int height = image.height(); + int size = width*height; + + if (depth == 1) + size = (width+7)/8*height; + else if (!gray) + size = size*3; + + pixelData.resize(size); + uchar *pixel = (uchar *)pixelData.data(); + int i = 0; + if (depth == 1) { + QImage::Format format = image.format(); + memset(pixel, 0xff, size); + for(int y=0; y < height; y++) { + const uchar * s = image.scanLine(y); + for(int x=0; x < width; x++) { + // need to copy bit for bit... + bool b = (format == QImage::Format_MonoLSB) ? + (*(s + (x >> 3)) >> (x & 7)) & 1 : + (*(s + (x >> 3)) << (x & 7)) & 0x80 ; + if (b) + pixel[i >> 3] ^= (0x80 >> (i & 7)); + i++; + } + // we need to align to 8 bit here + i = (i+7) & 0xffffff8; + } + } else if (depth == 8) { + for(int y=0; y < height; y++) { + const uchar * s = image.scanLine(y); + for(int x=0; x < width; x++) { + QRgb rgb = image.color(s[x]); + if (gray) { + pixel[i] = (unsigned char) qGray(rgb); + i++; + } else { + pixel[i] = (unsigned char) qRed(rgb); + pixel[i+1] = (unsigned char) qGreen(rgb); + pixel[i+2] = (unsigned char) qBlue(rgb); + i += 3; + } + } + } + } else { + for(int y=0; y < height; y++) { + QRgb * s = (QRgb*)(image.scanLine(y)); + for(int x=0; x < width; x++) { + QRgb rgb = (*s++); + if (gray) { + pixel[i] = (unsigned char) qGray(rgb); + i++; + } else { + pixel[i] = (unsigned char) qRed(rgb); + pixel[i+1] = (unsigned char) qGreen(rgb); + pixel[i+2] = (unsigned char) qBlue(rgb); + i += 3; + } + } + } + } + *format = Raw; + if (depth == 1) { + pixelData = runlengthEncode(pixelData); + *format = Runlength; + } + } + QByteArray outarr = QPdf::ascii85Encode(pixelData); + return outarr; +} + +void QPSPrintEnginePrivate::drawImageHelper(qreal x, qreal y, qreal w, qreal h, const QImage &img, + const QImage &mask, bool gray, qreal scaleX, qreal scaleY) +{ + Q_UNUSED(h); + Q_UNUSED(w); + int width = img.width(); + int height = img.height(); + + QByteArray out; + int size = 0; + const char *bits; + + if (!mask.isNull()) { + int format; + out = compressHelper(mask, true, &format); + size = (width+7)/8*height; + *currentPage << "/mask currentfile/ASCII85Decode filter" + << filters[format] + << size << " string readstring\n"; + ps_r7(*currentPage, out, out.size()); + *currentPage << " pop def\n"; + } + if (img.depth() == 1) { + size = (width+7)/8*height; + bits = "1 "; + } else if (gray) { + size = width*height; + bits = "8 "; + } else { + size = width*height*3; + bits = "24 "; + } + + int format; + out = compressHelper(img, gray, &format); + *currentPage << "/sl currentfile/ASCII85Decode filter" + << filters[format] + << size << " string readstring\n"; + ps_r7(*currentPage, out, out.size()); + *currentPage << " pop def\n"; + *currentPage << width << ' ' << height << '[' << scaleX << " 0 0 " << scaleY << " 0 0]sl " + << bits << (!mask.isNull() ? "mask " : "false ") + << x << ' ' << y << " di\n"; +} + + +void QPSPrintEnginePrivate::drawImage(qreal x, qreal y, qreal w, qreal h, + const QImage &image, const QImage &msk) +{ + if (!w || !h || image.isNull()) return; + + QImage img(image); + QImage mask(msk); + + if (image.format() == QImage::Format_ARGB32_Premultiplied) + img = image.convertToFormat(QImage::Format_ARGB32); + + if (!msk.isNull() && msk.format() == QImage::Format_ARGB32_Premultiplied) + mask = msk.convertToFormat(QImage::Format_ARGB32); + + int width = img.width(); + int height = img.height(); + qreal scaleX = width/w; + qreal scaleY = height/h; + + bool gray = (colorMode == QPrinter::GrayScale) || img.allGray(); + int splitSize = 21830 * (gray ? 3 : 1); + if (width * height > splitSize) { // 65535/3, tolerance for broken printers + int images, subheight; + images = (width * height + splitSize - 1) / splitSize; + subheight = (height + images-1) / images; + while (subheight * width > splitSize) { + images++; + subheight = (height + images-1) / images; + } + int suby = 0; + const QImage constImg(img); + const QImage constMask(mask); + while(suby < height) { + qreal subImageHeight = qMin(subheight, height-suby); + const QImage subImage(constImg.scanLine(suby), width, subImageHeight, + constImg.bytesPerLine(), constImg.format()); + const QImage subMask = mask.isNull() ? mask : QImage(constMask.scanLine(suby), width, subImageHeight, + constMask.bytesPerLine(), constMask.format()); + drawImageHelper(x, y + suby/scaleY, w, subImageHeight/scaleY, + subImage, subMask, gray, scaleX, scaleY); + suby += subheight; + } + } else { + drawImageHelper(x, y, width, height, img, mask, gray, scaleX, scaleY); + } +} + +void QPSPrintEnginePrivate::emitHeader(bool finished) +{ + QPSPrintEngine *q = static_cast<QPSPrintEngine *>(q_ptr); + QPrinter *printer = static_cast<QPrinter*>(pdev); + + if (creator.isEmpty()) + creator = QLatin1String("Qt " QT_VERSION_STR); + + QByteArray header; + QPdf::ByteStream s(&header); + + qreal scale = 72. / ((qreal) q->metric(QPaintDevice::PdmDpiY)); + QRect pageRect = this->pageRect(); + QRect paperRect = this->paperRect(); + int mtop = pageRect.top() - paperRect.top(); + int mleft = pageRect.left() - paperRect.left(); + int mbottom = paperRect.bottom() - pageRect.bottom(); + int mright = paperRect.right() - pageRect.right(); + int width = pageRect.width(); + int height = pageRect.height(); + if (finished && pageCount == 1 && copies == 1 && + ((fullPage && qt_gen_epsf) || (outputFileName.endsWith(QLatin1String(".eps"))))) + { + // According to the EPSF 3.0 spec it is required that the PS + // version is PS-Adobe-3.0 + s << "%!PS-Adobe-3.0"; + if (!boundingBox.isValid()) + boundingBox.setRect(0, 0, width, height); + if (orientation == QPrinter::Landscape) { + if (!fullPage) + boundingBox.translate(-mleft, -mtop); + s << " EPSF-3.0\n%%BoundingBox: " + << int((printer->height() - boundingBox.bottom())*scale) // llx + << int((printer->width() - boundingBox.right())*scale - 1) // lly + << int((printer->height() - boundingBox.top())*scale + 1) // urx + << int((printer->width() - boundingBox.left())*scale); // ury + } else { + if (!fullPage) + boundingBox.translate(mleft, -mtop); + s << " EPSF-3.0\n%%BoundingBox: " + << int((boundingBox.left())*scale) + << int((printer->height() - boundingBox.bottom())*scale - 1) + << int((boundingBox.right())*scale + 1) + << int((printer->height() - boundingBox.top())*scale); + } + } else { + s << "%!PS-Adobe-1.0"; + int w = width + (fullPage ? 0 : mleft + mright); + int h = height + (fullPage ? 0 : mtop + mbottom); + w = (int)(w*scale); + h = (int)(h*scale); + // set a bounding box according to the DSC + if (orientation == QPrinter::Landscape) + s << "\n%%BoundingBox: 0 0 " << h << w; + else + s << "\n%%BoundingBox: 0 0 " << w << h; + } + s << '\n' << wrapDSC("%%Creator: " + creator.toUtf8()); + if (!title.isEmpty()) + s << wrapDSC("%%Title: " + title.toUtf8()); +#ifndef QT_NO_DATESTRING + s << "%%CreationDate: " << QDateTime::currentDateTime().toString().toUtf8(); +#endif + s << "\n%%Orientation: "; + if (orientation == QPrinter::Landscape) + s << "Landscape"; + else + s << "Portrait"; + + s << "\n%%Pages: (atend)" + "\n%%DocumentFonts: (atend)" + "\n%%EndComments\n" + + "%%BeginProlog\n" + "% Prolog copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).\n" + "% You may copy this prolog in any way that is directly related to this document.\n" + "% For other use of this prolog, see your licensing agreement for Qt.\n" + << ps_header << '\n'; + + + s << "/pageinit {\n"; + if (!fullPage) { + if (orientation == QPrinter::Portrait) + s << mleft*scale << mbottom*scale << "translate\n"; + else + s << mtop*scale << mleft*scale << "translate\n"; + } + if (orientation == QPrinter::Portrait) { + s << "% " << printer->widthMM() << '*' << printer->heightMM() + << "mm (portrait)\n0 " << height*scale + << "translate " << scale << '-' << scale << "scale } def\n"; + } else { + s << "% " << printer->heightMM() << '*' << printer->widthMM() + << " mm (landscape)\n 90 rotate " << scale << '-' << scale << "scale } def\n"; + } + s << "%%EndProlog\n"; + + outDevice->write(header); + headerDone = true; +} + + +void QPSPrintEnginePrivate::emitPages() +{ + if (!hugeDocument) { + for (QHash<QFontEngine::FaceId, QFontSubset *>::const_iterator it = fonts.constBegin(); + it != fonts.constEnd(); ++it) + outDevice->write((*it)->toType1()); + } + + QIODevice *content = buffer.stream(); + // Write the page contents in chunks. + while (!content->atEnd()) { + QByteArray buf = content->read(currentPage->chunkSize()); + if (!buf.isEmpty()) + outDevice->write(buf); + } + content = currentPage->stream(); + // Write the page contents in chunks. + while (!content->atEnd()) { + QByteArray buf = content->read(currentPage->chunkSize()); + if (!buf.isEmpty()) + outDevice->write(buf); + } + outDevice->write(trailer); + + buffer.clear(); + currentPage->clear(); + trailer = QByteArray(); + hugeDocument = true; +} + + +#ifdef Q_WS_QWS +static const int max_in_memory_size = 2000000; +#else +static const int max_in_memory_size = 32000000; +#endif + +void QPSPrintEnginePrivate::flushPage(bool last) +{ + if (!last && currentPage->stream()->size() == 0) + return; + + QPdf::ByteStream e(&trailer); + buffer << "%%Page: " + << pageCount << pageCount << "\n" + "%%BeginPageSetup\n" + "QI\n"; + if (hugeDocument) { + for (QHash<QFontEngine::FaceId, QFontSubset *>::const_iterator it = fonts.constBegin(); + it != fonts.constEnd(); ++it) { + if (currentPage->fonts.contains((*it)->object_id)) { + if ((*it)->downloaded_glyphs == 0) { + buffer << (*it)->toType1(); + (*it)->downloaded_glyphs = 0; + } else { + buffer << (*it)->type1AddedGlyphs(); + } + } + } + } + for (int i = 0; i < currentPage->fonts.size(); ++i) + buffer << "(F" << QByteArray::number(currentPage->fonts.at(i)) << ") T1Setup\n"; + + buffer << "%%EndPageSetup\nq\n"; + e << "\nQ QP\n"; + if (last || hugeDocument + || buffer.stream()->size() + currentPage->stream()->size() > max_in_memory_size) { +// qDebug("emiting header at page %d", pageCount); + if (!headerDone) + emitHeader(last); + emitPages(); + } else { + buffer << *currentPage << e; + currentPage->clear(); + trailer.clear(); + } + pageCount++; +} + +// ================ PSPrinter class ======================== + +QPSPrintEngine::QPSPrintEngine(QPrinter::PrinterMode m) + : QPdfBaseEngine(*(new QPSPrintEnginePrivate(m)), + PrimitiveTransform + | PatternTransform + | PixmapTransform + | PainterPaths + | PatternBrush + ) +{ +} + +static void ignoreSigPipe(bool b) +{ +#ifndef QT_NO_LPR + static struct sigaction *users_sigpipe_handler = 0; + static int lockCount = 0; + +#ifndef QT_NO_THREAD + QMutexLocker locker(QMutexPool::globalInstanceGet(&users_sigpipe_handler)); +#endif + + if (b) { + if (lockCount++ > 0) + return; + + if (users_sigpipe_handler != 0) + return; // already ignoring sigpipe + + users_sigpipe_handler = new struct sigaction; + struct sigaction tmp_sigpipe_handler; + tmp_sigpipe_handler.sa_handler = SIG_IGN; + sigemptyset(&tmp_sigpipe_handler.sa_mask); + tmp_sigpipe_handler.sa_flags = 0; + + if (sigaction(SIGPIPE, &tmp_sigpipe_handler, users_sigpipe_handler) == -1) { + delete users_sigpipe_handler; + users_sigpipe_handler = 0; + } + } + else { + if (--lockCount > 0) + return; + + if (users_sigpipe_handler == 0) + return; // not ignoring sigpipe + + if (sigaction(SIGPIPE, users_sigpipe_handler, 0) == -1) + qWarning("QPSPrintEngine: Could not restore SIGPIPE handler"); + + delete users_sigpipe_handler; + users_sigpipe_handler = 0; + } +#else + Q_UNUSED(b); +#endif +} +QPSPrintEngine::~QPSPrintEngine() +{ + Q_D(QPSPrintEngine); + if (d->fd >= 0) +#if defined(Q_OS_WIN) && defined(_MSC_VER) && _MSC_VER >= 1400 + ::_close(d->fd); +#else + ::close(d->fd); +#endif +} + +bool QPSPrintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QPSPrintEngine); + + if (d->fd >= 0) + return true; + + if (d->useAlphaEngine) { + QAlphaPaintEngine::begin(pdev); + if (!continueCall()) + return true; + } + + if(!QPdfBaseEngine::begin(pdev)) { + d->printerState = QPrinter::Error; + return false; + } + + d->pageCount = 1; // initialize state + + d->pen = QPen(Qt::black); + d->brush = Qt::NoBrush; + d->hasPen = true; + d->hasBrush = false; + d->clipEnabled = false; + d->allClipped = false; + d->boundingBox = QRect(); + d->fontsUsed = ""; + d->hugeDocument = false; + d->simplePen = false; + + setActive(true); + d->printerState = QPrinter::Active; + + newPage(); + + return true; +} + +bool QPSPrintEngine::end() +{ + Q_D(QPSPrintEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::end(); + if (!continueCall()) + return true; + } + + // we're writing to lp/lpr through a pipe, we don't want to crash with SIGPIPE + // if lp/lpr dies + ignoreSigPipe(true); + d->flushPage(true); + QByteArray trailer; + QPdf::ByteStream s(&trailer); + s << "%%Trailer\n" + "%%Pages: " << d->pageCount - 1 << '\n' << + wrapDSC("%%DocumentFonts: " + d->fontsUsed); + s << "%%EOF\n"; + d->outDevice->write(trailer); + + QPdfBaseEngine::end(); + ignoreSigPipe(false); + + d->firstPage = true; + d->headerDone = false; + + setActive(false); + d->printerState = QPrinter::Idle; + d->pdev = 0; + + return true; +} + +void QPSPrintEngine::setBrush() +{ + Q_D(QPSPrintEngine); +#if 0 + bool specifyColor; + int gStateObject = 0; + int patternObject = d->addBrushPattern(brush, d->stroker.matrix, brushOrigin, &specifyColor, &gStateObject); + + *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs "); + if (specifyColor) { + QColor rgba = brush.color(); + *d->currentPage << rgba.redF() + << rgba.greenF() + << rgba.blueF(); + } + if (patternObject) + *d->currentPage << "/Pat" << patternObject; + *d->currentPage << "scn\n"; +#endif + QColor rgba = d->brush.color(); + if (d->colorMode == QPrinter::GrayScale) { + qreal gray = qGray(rgba.rgba())/255.; + *d->currentPage << gray << gray << gray; + } else { + *d->currentPage << rgba.redF() + << rgba.greenF() + << rgba.blueF(); + } + *d->currentPage << "scn\n" + << "/BSt " << d->brush.style() << "def\n"; +} + +void QPSPrintEngine::drawImageInternal(const QRectF &r, QImage image, bool bitmap) +{ + Q_D(QPSPrintEngine); + if (d->clipEnabled && d->allClipped) + return; + if (bitmap && image.depth() != 1) + bitmap = false; + QImage mask; + // the below is not necessary since it's handled by the alpha + // engine + if (!d->useAlphaEngine && !bitmap) { + if (image.format() == QImage::Format_Mono || image.format() == QImage::Format_MonoLSB) + image = image.convertToFormat(QImage::Format_Indexed8); + if (image.hasAlphaChannel()) { + // get better alpha dithering + int xscale = image.width(); + xscale *= xscale <= 800 ? 4 : (xscale <= 1600 ? 2 : 1); + int yscale = image.height(); + yscale *= yscale <= 800 ? 4 : (yscale <= 1600 ? 2 : 1); + image = image.scaled(xscale, yscale); + mask = image.createAlphaMask(Qt::OrderedAlphaDither); + } + } + *d->currentPage << "q\n"; + if(!d->simplePen) + *d->currentPage << QPdf::generateMatrix(d->stroker.matrix); + QBrush b = d->brush; + if (image.depth() == 1) { + // set current pen as brush + d->brush = d->pen.brush(); + setBrush(); + } + d->drawImage(r.x(), r.y(), r.width(), r.height(), image, mask); + *d->currentPage << "Q\n"; + d->brush = b; +} + + +void QPSPrintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags) +{ + Q_D(QPSPrintEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawImage(r, img, sr); + if (!continueCall()) + return; + } + QImage image = img.copy(sr.toRect()); + drawImageInternal(r, image, false); +} + +void QPSPrintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QPSPrintEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawPixmap(r, pm, sr); + if (!continueCall()) + return; + } + + QImage img = pm.copy(sr.toRect()).toImage(); + drawImageInternal(r, img, true); +} + +void QPSPrintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) +{ + Q_D(QPSPrintEngine); + + if (d->useAlphaEngine) { + QAlphaPaintEngine::drawTiledPixmap(r, pixmap, p); + if (!continueCall()) + return; + } + + if (d->clipEnabled && d->allClipped) + return; + // ### Optimize implementation! + qreal yPos = r.y(); + qreal yOff = p.y(); + while(yPos < r.y() + r.height()) { + qreal drawH = pixmap.height() - yOff; // Cropping first row + if (yPos + drawH > r.y() + r.height()) // Cropping last row + drawH = r.y() + r.height() - yPos; + qreal xPos = r.x(); + qreal xOff = p.x(); + while(xPos < r.x() + r.width()) { + qreal drawW = pixmap.width() - xOff; // Cropping first column + if (xPos + drawW > r.x() + r.width()) // Cropping last column + drawW = r.x() + r.width() - xPos; + // ######## + painter()->drawPixmap(QPointF(xPos, yPos).toPoint(), pixmap, + QRectF(xOff, yOff, drawW, drawH).toRect()); + xPos += drawW; + xOff = 0; + } + yPos += drawH; + yOff = 0; + } + +} + +bool QPSPrintEngine::newPage() +{ + Q_D(QPSPrintEngine); + + if (!d->firstPage && d->useAlphaEngine) + flushAndInit(); + + // we're writing to lp/lpr through a pipe, we don't want to crash with SIGPIPE + // if lp/lpr dies + ignoreSigPipe(true); + if (!d->firstPage) + d->flushPage(); + d->firstPage = false; + ignoreSigPipe(false); + + delete d->currentPage; + d->currentPage = new QPdfPage; + d->stroker.stream = d->currentPage; + + return QPdfBaseEngine::newPage(); +} + +bool QPSPrintEngine::abort() +{ + // ### abort!?! + return false; +} + +QPrinter::PrinterState QPSPrintEngine::printerState() const +{ + Q_D(const QPSPrintEngine); + return d->printerState; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprintengine_ps_p.h b/src/gui/painting/qprintengine_ps_p.h new file mode 100644 index 0000000000..a1185d7867 --- /dev/null +++ b/src/gui/painting/qprintengine_ps_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_PS_P_H +#define QPRINTENGINE_PS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qpsprinter.cpp and qprinter_x11.cpp. +// This header file may change from version to version without notice, +// or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_PRINTER + +#include "private/qpdf_p.h" +#include "qplatformdefs.h" +#include "QtCore/qlibrary.h" +#include "QtCore/qstringlist.h" +#include "QtCore/qhash.h" +#include "QtCore/qabstractitemmodel.h" + +QT_BEGIN_NAMESPACE + +class QPrinter; +class QPSPrintEnginePrivate; + +class QPSPrintEngine : public QPdfBaseEngine +{ + Q_DECLARE_PRIVATE(QPSPrintEngine) +public: + // QPrinter uses these + explicit QPSPrintEngine(QPrinter::PrinterMode m); + ~QPSPrintEngine(); + + + virtual bool begin(QPaintDevice *pdev); + virtual bool end(); + + void setBrush(); + + virtual void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, Qt::ImageConversionFlags); + virtual void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + virtual void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s); + + virtual void drawImageInternal(const QRectF &r, QImage img, bool bitmap); + + virtual QPaintEngine::Type type() const { return QPaintEngine::PostScript; } + + virtual bool newPage(); + virtual bool abort(); + + virtual QPrinter::PrinterState printerState() const; + + virtual Qt::HANDLE handle() const { return 0; } + +private: + Q_DISABLE_COPY(QPSPrintEngine) +}; + +class QPSPrintEnginePrivate : public QPdfBaseEnginePrivate { +public: + QPSPrintEnginePrivate(QPrinter::PrinterMode m); + ~QPSPrintEnginePrivate(); + + void emitHeader(bool finished); + void emitPages(); + void drawImage(qreal x, qreal y, qreal w, qreal h, const QImage &img, const QImage &mask); + void flushPage(bool last = false); + void drawImageHelper(qreal x, qreal y, qreal w, qreal h, const QImage &img, const QImage &mask, + bool gray, qreal scaleX, qreal scaleY); + + int pageCount; + bool epsf; + QByteArray fontsUsed; + + // stores the descriptions of the n first pages. + QPdf::ByteStream buffer; + QByteArray trailer; + + bool firstPage; + + QRect boundingBox; + + QPrinter::PrinterState printerState; + bool hugeDocument; + bool headerDone; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_PS_P_H diff --git a/src/gui/painting/qprintengine_qws.cpp b/src/gui/painting/qprintengine_qws.cpp new file mode 100644 index 0000000000..1aef2c68cf --- /dev/null +++ b/src/gui/painting/qprintengine_qws.cpp @@ -0,0 +1,886 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qprintengine_qws_p.h> + +#ifndef QT_NO_PRINTER + +#include <private/qpaintengine_raster_p.h> +#include <qimage.h> +#include <qfile.h> +#include <qdebug.h> +#include <QCopChannel> + +QT_BEGIN_NAMESPACE + +#define MM(n) int((n * 720 + 127) / 254) +#define IN(n) int(n * 72) + +extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); + +QtopiaPrintEngine::QtopiaPrintEngine(QPrinter::PrinterMode mode) + : QPaintEngine(*(new QtopiaPrintEnginePrivate( mode ))) +{ + d_func()->initialize(); +} + +bool QtopiaPrintEngine::begin(QPaintDevice *) +{ + Q_D(QtopiaPrintEngine); + Q_ASSERT_X(d->printerState == QPrinter::Idle, "QtopiaPrintEngine", "printer already active"); + + // Create a new off-screen monochrome image to handle the drawing process. + QSize size = paperRect().size(); + if ( d->pageImage ) + delete d->pageImage; + d->pageImage = new QImage( size, QImage::Format_RGB32 ); + if ( !(d->pageImage) ) + return false; + + // Recreate the paint engine on the new image. + delete d->_paintEngine; + d->_paintEngine = 0; + d->paintEngine()->state = state; + + // Begin the paint process on the image. + if (!d->paintEngine()->begin(d->pageImage)) + return false; + + // Clear the first page to all-white. + clearPage(); + + // Clear the print buffer and output the image header. + d->buffer.clear(); + d->writeG3FaxHeader(); + + // The print engine is currently active. + d->printerState = QPrinter::Active; + return true; +} + +bool QtopiaPrintEngine::end() +{ + Q_D(QtopiaPrintEngine); + + d->paintEngine()->end(); + + // Flush the last page. + flushPage(); + + // Output the fax data to a file (TODO: send to the print queuing daemon). + QString filename; + if ( !d->outputFileName.isEmpty() ) + filename = QString::fromLocal8Bit(qgetenv("HOME").constData()) + QLatin1String("/Documents/") + d->outputFileName; + else + filename = QString::fromLocal8Bit(qgetenv("HOME").constData()) + QLatin1String("/tmp/qwsfax.tiff"); + + setProperty(QPrintEngine::PPK_OutputFileName, filename); + QFile file( filename ); + if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { + qDebug( "Failed to open %s for printer output", + filename.toLatin1().constData() ); + } else { + file.write( d->buffer.data() ); + file.close(); + } + + // Free up the memory for the image buffer. + d->buffer.clear(); + + // Finalize the print job. + d->printerState = QPrinter::Idle; + + // call qcop service + QMap<QString, QVariant> map; + for ( int x = 0; x <= QPrintEngine::PPK_Duplex; x++ ) + map.insert( QString::number(x), property((QPrintEngine::PrintEnginePropertyKey)(x))); + QVariant variant(map); + + QByteArray data; + QDataStream out(&data, QIODevice::WriteOnly); + out << variant; + QCopChannel::send(QLatin1String("QPE/Service/Print"), QLatin1String("print(QVariant)"), data); + + return true; +} + +QPaintEngine *QtopiaPrintEngine::paintEngine() const +{ + return const_cast<QtopiaPrintEnginePrivate *>(d_func())->paintEngine(); +} + +void QtopiaPrintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QtopiaPrintEngine); + Q_ASSERT(d->printerState == QPrinter::Active); + d->paintEngine()->drawPixmap(r, pm, sr); +} + +void QtopiaPrintEngine::drawTextItem(const QPointF &p, const QTextItem &ti) +{ + Q_D(QtopiaPrintEngine); + Q_ASSERT(d->printerState == QPrinter::Active); + d->paintEngine()->drawTextItem(p, ti); +} + +void QtopiaPrintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QtopiaPrintEngine); + d->paintEngine()->updateState(state); +} + +QRect QtopiaPrintEngine::paperRect() const +{ + QSizeF s = qt_paperSizeToQSizeF(d_func()->paperSize); + s.rwidth() = MM(s.width()); + s.rheight() = MM(s.height()); + int w = qRound(s.width()*d_func()->resolution/72.); + int h = qRound(s.height()*d_func()->resolution/72.); + if (d_func()->orientation == QPrinter::Portrait) + return QRect(0, 0, w, h); + else + return QRect(0, 0, h, w); +} + +QRect QtopiaPrintEngine::pageRect() const +{ + QRect r = paperRect(); + if (d_func()->fullPage) + return r; + // would be nice to get better margins than this. + return QRect(d_func()->resolution/3, d_func()->resolution/3, r.width()-2*d_func()->resolution/3, r.height()-2*d_func()->resolution/3); +} + +bool QtopiaPrintEngine::newPage() +{ + flushPage(); + clearPage(); + ++(d_func()->pageNumber); + return true; +} + +bool QtopiaPrintEngine::abort() +{ + return false; +} + +QPrinter::PrinterState QtopiaPrintEngine::printerState() const +{ + return d_func()->printerState; +} + +int QtopiaPrintEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const +{ + int val; + QRect r = d_func()->fullPage ? paperRect() : pageRect(); + switch (metricType) { + case QPaintDevice::PdmWidth: + val = r.width(); + break; + case QPaintDevice::PdmHeight: + val = r.height(); + break; + case QPaintDevice::PdmDpiX: + val = d_func()->resolution; + break; + case QPaintDevice::PdmDpiY: + val = d_func()->resolution; + break; + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: + val = QT_QWS_PRINTER_DEFAULT_DPI; + break; + case QPaintDevice::PdmWidthMM: + val = qRound(r.width()*25.4/d_func()->resolution); + break; + case QPaintDevice::PdmHeightMM: + val = qRound(r.height()*25.4/d_func()->resolution); + break; + case QPaintDevice::PdmNumColors: + val = 2; + break; + case QPaintDevice::PdmDepth: + val = 1; + break; + default: + qWarning("QtopiaPrintEngine::metric: Invalid metric command"); + return 0; + } + return val; +} + +QVariant QtopiaPrintEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QtopiaPrintEngine); + QVariant ret; + + switch (key) { + case PPK_CollateCopies: + ret = d->collateCopies; + break; + case PPK_ColorMode: + ret = d->colorMode; + break; + case PPK_Creator: + ret = d->creator; + break; + case PPK_DocumentName: + ret = d->docName; + break; + case PPK_FullPage: + ret = d->fullPage; + break; + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + ret = d->numCopies; + break; + case PPK_SupportsMultipleCopies: + ret = false; + break; + case PPK_Orientation: + ret = d->orientation; + break; + case PPK_OutputFileName: + ret = d->outputFileName; + break; + case PPK_PageOrder: + ret = d->pageOrder; + break; + case PPK_PageRect: + ret = pageRect(); + break; + case PPK_PaperSize: + ret = d->paperSize; + break; + case PPK_PaperRect: + ret = paperRect(); + break; + case PPK_PaperSource: + ret = d->paperSource; + break; + case PPK_PrinterName: + ret = d->printerName; + break; + case PPK_PrinterProgram: + ret = d->printProgram; + break; + case PPK_Resolution: + ret = d->resolution; + break; + case PPK_SupportedResolutions: + ret = QList<QVariant>() << QT_QWS_PRINTER_DEFAULT_DPI; + break; + default: + break; + } + return ret; +} + +void QtopiaPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QtopiaPrintEngine); + switch (key) { + case PPK_CollateCopies: + d->collateCopies = value.toBool(); + break; + case PPK_ColorMode: + d->colorMode = QPrinter::ColorMode(value.toInt()); + break; + case PPK_Creator: + d->creator = value.toString(); + break; + case PPK_DocumentName: + d->docName = value.toString(); + break; + case PPK_FullPage: + d->fullPage = value.toBool(); + break; + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + d->numCopies = value.toInt(); + break; + case PPK_Orientation: + d->orientation = QPrinter::Orientation(value.toInt()); + break; + case PPK_OutputFileName: + d->outputFileName = value.toString(); + break; + case PPK_PageOrder: + d->pageOrder = QPrinter::PageOrder(value.toInt()); + break; + case PPK_PaperSize: + d->paperSize = QPrinter::PaperSize(value.toInt()); + break; + case PPK_PaperSource: + d->paperSource = QPrinter::PaperSource(value.toInt()); + case PPK_PrinterName: + d->printerName = value.toString(); + break; + case PPK_PrinterProgram: + d->printProgram = value.toString(); + break; + case PPK_Resolution: + d->resolution = value.toInt(); + break; + default: + break; + } +} + +void QtopiaPrintEngine::clearPage() +{ + d_func()->pageImage->fill(QColor(255, 255, 255).rgb()); +} + +void QtopiaPrintEngine::flushPage() +{ + d_func()->writeG3FaxPage(); +} + +QtopiaPrintEnginePrivate::~QtopiaPrintEnginePrivate() +{ + if ( pageImage ) + delete pageImage; +} + +void QtopiaPrintEnginePrivate::initialize() +{ + _paintEngine = 0; +} + +QPaintEngine *QtopiaPrintEnginePrivate::paintEngine() +{ + if (!_paintEngine) + _paintEngine = new QRasterPaintEngine(pageImage); + return _paintEngine; +} + +void QtopiaPrintEnginePrivate::writeG3FaxHeader() +{ + // Write the TIFF file magic number (little-endian TIFF). + buffer.append( (char)'I' ); + buffer.append( (char)'I' ); + buffer.append( (char)42 ); + buffer.append( (char)0 ); + + // Leave a place-holder for the IFD offset of the first page. + ifdPatch = buffer.size(); + buffer.append( (int)0 ); +} + +// Tag values, from RFC 2301. +#define TIFF_IFD_NEW_SUB_FILE_TYPE 254 +#define TIFF_IFD_IMAGE_WIDTH 256 +#define TIFF_IFD_IMAGE_LENGTH 257 +#define TIFF_IFD_BITS_PER_SAMPLE 258 +#define TIFF_IFD_COMPRESSION 259 +#define TIFF_IFD_PHOTOMETRIC_INTERP 262 +#define TIFF_IFD_FILL_ORDER 266 +#define TIFF_IFD_STRIP_OFFSETS 273 +#define TIFF_IFD_ORIENTATION 274 +#define TIFF_IFD_SAMPLES_PER_PIXEL 277 +#define TIFF_IFD_ROWS_PER_STRIP 278 +#define TIFF_IFD_STRIP_BYTE_COUNTS 279 +#define TIFF_IFD_X_RESOLUTION 282 +#define TIFF_IFD_Y_RESOLUTION 283 +#define TIFF_IFD_PLANAR_CONFIG 284 +#define TIFF_IFD_T4_OPTIONS 292 +#define TIFF_IFD_RESOLUTION_UNIT 296 +#define TIFF_IFD_PAGE_NUMBER 297 +#define TIFF_IFD_CLEAN_FAX_DATA 327 + +// IFD type values. +#define TIFF_TYPE_SHORT 3 +#define TIFF_TYPE_LONG 4 +#define TIFF_TYPE_RATIONAL 5 + +// Construct a SHORT pair from two values. +#define TIFF_SHORT_PAIR(a,b) (((a) & 0xFFFF) | ((b) << 16)) + +// Width of a FAX page in pixels, in the baseline specification from RFC 2301. +// This must be hard-wired, as per the RFC. We truncate any pixels that +// are beyond this limit, or pad lines to reach this limit. +#define TIFF_FAX_WIDTH 1728 + +void QtopiaPrintEnginePrivate::writeG3FaxPage() +{ + // Pad the image file to a word boundary, just in case. + buffer.pad(); + + // Back-patch the IFD link for the previous page. + buffer.patch( ifdPatch, buffer.size() ); + + // Output the contents of the IFD for this page (these must be + // in ascending order of tag value). + buffer.append( (short)19 ); // Number of IFD entries. + writeG3IFDEntry( TIFF_IFD_NEW_SUB_FILE_TYPE, TIFF_TYPE_LONG, 1, 2 ); + writeG3IFDEntry( TIFF_IFD_IMAGE_WIDTH, TIFF_TYPE_LONG, 1, TIFF_FAX_WIDTH ); + writeG3IFDEntry + ( TIFF_IFD_IMAGE_LENGTH, TIFF_TYPE_LONG, 1, pageImage->height() ); + writeG3IFDEntry( TIFF_IFD_BITS_PER_SAMPLE, TIFF_TYPE_SHORT, 1, 1 ); + writeG3IFDEntry( TIFF_IFD_COMPRESSION, TIFF_TYPE_SHORT, 1, 3 ); + writeG3IFDEntry( TIFF_IFD_PHOTOMETRIC_INTERP, TIFF_TYPE_SHORT, 1, 0 ); + writeG3IFDEntry( TIFF_IFD_FILL_ORDER, TIFF_TYPE_SHORT, 1, 1 ); + int stripOffsets = + writeG3IFDEntry( TIFF_IFD_STRIP_OFFSETS, TIFF_TYPE_LONG, 1, 0 ); + writeG3IFDEntry( TIFF_IFD_ORIENTATION, TIFF_TYPE_SHORT, 1, 1 ); + writeG3IFDEntry( TIFF_IFD_SAMPLES_PER_PIXEL, TIFF_TYPE_SHORT, 1, 1 ); + writeG3IFDEntry + ( TIFF_IFD_ROWS_PER_STRIP, TIFF_TYPE_LONG, 1, pageImage->height() ); + int stripBytes = writeG3IFDEntry + ( TIFF_IFD_STRIP_BYTE_COUNTS, TIFF_TYPE_LONG, 1, 0 ); + int xres = + writeG3IFDEntry( TIFF_IFD_X_RESOLUTION, TIFF_TYPE_RATIONAL, 1, 0 ); + int yres = + writeG3IFDEntry( TIFF_IFD_Y_RESOLUTION, TIFF_TYPE_RATIONAL, 1, 0 ); + writeG3IFDEntry( TIFF_IFD_PLANAR_CONFIG, TIFF_TYPE_SHORT, 1, 1 ); + writeG3IFDEntry( TIFF_IFD_T4_OPTIONS, TIFF_TYPE_LONG, 1, 2 ); + writeG3IFDEntry( TIFF_IFD_RESOLUTION_UNIT, TIFF_TYPE_SHORT, 1, 2 ); + writeG3IFDEntry( TIFF_IFD_PAGE_NUMBER, TIFF_TYPE_SHORT, 2, + TIFF_SHORT_PAIR( pageNumber, 0 ) ); + writeG3IFDEntry( TIFF_IFD_CLEAN_FAX_DATA, TIFF_TYPE_SHORT, 1, 0 ); + + // Leave a place-holder for the IFD offset of the next page. + ifdPatch = buffer.size(); + buffer.append( (int)0 ); + + // Output the X and Y resolutions, as rational values (usually 200/1). + buffer.patch( xres, buffer.size() ); + buffer.append( (int)resolution ); + buffer.append( (int)1 ); + buffer.patch( yres, buffer.size() ); + buffer.append( (int)resolution ); + buffer.append( (int)1 ); + + // We are now at the start of the image data - set the strip offset. + int start = buffer.size(); + buffer.patch( stripOffsets, start ); + + // Output the image data. + int width = pageImage->width(); + QImage::Format imageFormat = pageImage->format(); + for ( int y = 0; y < pageImage->height(); ++y ) { + unsigned char *scan = pageImage->scanLine(y); + int prev, pixel, len; + writeG3EOL(); + prev = 0; + len = 0; + + uint currentColor = qRgb(255, 255, 255); // start with white + + for ( int x = 0; x < width && x < TIFF_FAX_WIDTH; ++x ) { + if ( imageFormat == QImage::Format_RGB32 ) { + // read color of the current pixel + uint *p = (uint *)scan + x; + + if ( *p == currentColor ) { // if it is the same color + len++; // imcrement length + } else { // otherwise write color into the buffer + if ( len > 0 ) { + if ( currentColor == qRgb(0, 0, 0) ) + writeG3BlackRun( len ); + else + writeG3WhiteRun( len ); + } + // initialise length and color; + len = 1; + currentColor = *p; + } + } else if ( imageFormat == QImage::Format_Mono ) { + pixel = ((scan[x >> 3] & (1 << (x & 7))) != 0); + if ( pixel != prev ) { + if ( prev ) { + writeG3BlackRun( len ); + } else { + writeG3WhiteRun( len ); + } + prev = pixel; + len = 1; + } else { + ++len; + } + } + } + + if ( imageFormat == QImage::Format_RGB32 ) { + // Output the last run on the line, and pad to TIFF_FAX_WIDTH. + if ( len != 0 ) { + if ( currentColor == qRgb(0, 0, 0) ) + writeG3BlackRun( len ); + else + writeG3WhiteRun( len ); + } + if ( width < TIFF_FAX_WIDTH ) + writeG3WhiteRun( TIFF_FAX_WIDTH - width ); + } else if ( imageFormat == QImage::Format_Mono ) { + if ( len != 0 ) { + if ( prev ) { + writeG3BlackRun( len ); + if ( width < TIFF_FAX_WIDTH ) { + writeG3WhiteRun( TIFF_FAX_WIDTH - width ); + } + } else { + if ( width < TIFF_FAX_WIDTH ) { + writeG3WhiteRun( len + ( TIFF_FAX_WIDTH - width ) ); + } else { + writeG3WhiteRun( len ); + } + } + } + } + } + + // Flush the last partial byte, which is padded with zero fill bits. + if ( partialBits > 0 ) { + buffer.append( (char)( partialByte << ( 8 - partialBits ) ) ); + partialByte = 0; + partialBits = 0; + } + + // end of page add six EOLs + for ( int i = 0; i < 6; i++ ) + writeG3EOL(); + + // Update the byte count for the image data strip. + buffer.patch( stripBytes, buffer.size() - start ); +} + +int QtopiaPrintEnginePrivate::writeG3IFDEntry + ( int tag, int type, int count, int value ) +{ + buffer.append( (short)tag ); + buffer.append( (short)type ); + buffer.append( count ); + buffer.append( value ); + return buffer.size() - 4; // Offset of the value for back-patching. +} + +void QtopiaPrintEnginePrivate::writeG3Code( int code, int bits ) +{ + partialByte = ( ( partialByte << bits ) | code ); + partialBits += bits; + while ( partialBits >= 8 ) { + partialBits -= 8; + buffer.append( (char)( partialByte >> partialBits ) ); + } +} + +void QtopiaPrintEnginePrivate::writeG3WhiteRun( int len ) +{ + static struct { + unsigned short code; + unsigned short bits; + } whiteCodes[64 + 27] = { + {0x0035, 8}, // 0 + {0x0007, 6}, + {0x0007, 4}, + {0x0008, 4}, + {0x000B, 4}, + {0x000C, 4}, + {0x000E, 4}, + {0x000F, 4}, + {0x0013, 5}, // 8 + {0x0014, 5}, + {0x0007, 5}, + {0x0008, 5}, + {0x0008, 6}, + {0x0003, 6}, + {0x0034, 6}, + {0x0035, 6}, + {0x002A, 6}, // 16 + {0x002B, 6}, + {0x0027, 7}, + {0x000C, 7}, + {0x0008, 7}, + {0x0017, 7}, + {0x0003, 7}, + {0x0004, 7}, + {0x0028, 7}, // 24 + {0x002B, 7}, + {0x0013, 7}, + {0x0024, 7}, + {0x0018, 7}, + {0x0002, 8}, + {0x0003, 8}, + {0x001A, 8}, + {0x001B, 8}, // 32 + {0x0012, 8}, + {0x0013, 8}, + {0x0014, 8}, + {0x0015, 8}, + {0x0016, 8}, + {0x0017, 8}, + {0x0028, 8}, + {0x0029, 8}, // 40 + {0x002A, 8}, + {0x002B, 8}, + {0x002C, 8}, + {0x002D, 8}, + {0x0004, 8}, + {0x0005, 8}, + {0x000A, 8}, + {0x000B, 8}, // 48 + {0x0052, 8}, + {0x0053, 8}, + {0x0054, 8}, + {0x0055, 8}, + {0x0024, 8}, + {0x0025, 8}, + {0x0058, 8}, + {0x0059, 8}, // 56 + {0x005A, 8}, + {0x005B, 8}, + {0x004A, 8}, + {0x004B, 8}, + {0x0032, 8}, + {0x0033, 8}, + {0x0034, 8}, + {0x001B, 5}, // Make up codes: 64 + {0x0012, 5}, // 128 + {0x0017, 6}, // 192 + {0x0037, 7}, // 256 + {0x0036, 8}, // 320 + {0x0037, 8}, // 384 + {0x0064, 8}, // 448 + {0x0065, 8}, // 512 + {0x0068, 8}, // 576 + {0x0067, 8}, // 640 + {0x00CC, 9}, // 704 + {0x00CD, 9}, // 768 + {0x00D2, 9}, // 832 + {0x00D3, 9}, // 896 + {0x00D4, 9}, // 960 + {0x00D5, 9}, // 1024 + {0x00D6, 9}, // 1088 + {0x00D7, 9}, // 1152 + {0x00D8, 9}, // 1216 + {0x00D9, 9}, // 1280 + {0x00DA, 9}, // 1344 + {0x00DB, 9}, // 1408 + {0x0098, 9}, // 1472 + {0x0099, 9}, // 1536 + {0x009A, 9}, // 1600 + {0x0018, 6}, // 1664 + {0x009B, 9}, // 1728 + }; + if ( len >= 64 ) { + int index = 63 + (len >> 6); + writeG3Code( whiteCodes[index].code, whiteCodes[index].bits ); + len &= 63; + } + writeG3Code( whiteCodes[len].code, whiteCodes[len].bits ); +} + +void QtopiaPrintEnginePrivate::writeG3BlackRun( int len ) +{ + static struct { + unsigned short code; + unsigned short bits; + } blackCodes[64 + 27] = { + {0x0037, 10}, // 0 + {0x0002, 3}, + {0x0003, 2}, + {0x0002, 2}, + {0x0003, 3}, + {0x0003, 4}, + {0x0002, 4}, + {0x0003, 5}, + {0x0005, 6}, // 8 + {0x0004, 6}, + {0x0004, 7}, + {0x0005, 7}, + {0x0007, 7}, + {0x0004, 8}, + {0x0007, 8}, + {0x0018, 9}, + {0x0017, 10}, // 16 + {0x0018, 10}, + {0x0008, 10}, + {0x0067, 11}, + {0x0068, 11}, + {0x006C, 11}, + {0x0037, 11}, + {0x0028, 11}, + {0x0017, 11}, // 24 + {0x0018, 11}, + {0x00CA, 12}, + {0x00CB, 12}, + {0x00CC, 12}, + {0x00CD, 12}, + {0x0068, 12}, + {0x0069, 12}, + {0x006A, 12}, // 32 + {0x006B, 12}, + {0x00D2, 12}, + {0x00D3, 12}, + {0x00D4, 12}, + {0x00D5, 12}, + {0x00D6, 12}, + {0x00D7, 12}, + {0x006C, 12}, // 40 + {0x006D, 12}, + {0x00DA, 12}, + {0x00DB, 12}, + {0x0054, 12}, + {0x0055, 12}, + {0x0056, 12}, + {0x0057, 12}, + {0x0064, 12}, // 48 + {0x0065, 12}, + {0x0052, 12}, + {0x0053, 12}, + {0x0024, 12}, + {0x0037, 12}, + {0x0038, 12}, + {0x0027, 12}, + {0x0028, 12}, // 56 + {0x0058, 12}, + {0x0059, 12}, + {0x002B, 12}, + {0x002C, 12}, + {0x005A, 12}, + {0x0066, 12}, + {0x0067, 12}, + {0x000F, 10}, // Make up codes: 64 + {0x00C8, 12}, // 128 + {0x00C9, 12}, // 192 + {0x005B, 12}, // 256 + {0x0033, 12}, // 320 + {0x0034, 12}, // 384 + {0x0035, 12}, // 448 + {0x006C, 13}, // 512 + {0x006D, 13}, // 576 + {0x004A, 13}, // 640 + {0x004B, 13}, // 704 + {0x004C, 13}, // 768 + {0x004D, 13}, // 832 + {0x0072, 13}, // 896 + {0x0073, 13}, // 960 + {0x0074, 13}, // 1024 + {0x0075, 13}, // 1088 + {0x0076, 13}, // 1152 + {0x0077, 13}, // 1216 + {0x0052, 13}, // 1280 + {0x0053, 13}, // 1344 + {0x0054, 13}, // 1408 + {0x0055, 13}, // 1472 + {0x005A, 13}, // 1536 + {0x005B, 13}, // 1600 + {0x0064, 13}, // 1664 + {0x0065, 13}, // 1728 + }; + if ( len >= 64 ) { + int index = 63 + (len >> 6); + writeG3Code( blackCodes[index].code, blackCodes[index].bits ); + len &= 63; + } + writeG3Code( blackCodes[len].code, blackCodes[len].bits ); +} + +void QtopiaPrintEnginePrivate::writeG3EOL() +{ + int bitToPad; + if ( partialBits <= 4 ) { + bitToPad = 4 - partialBits; + } else { + bitToPad = 8 - partialBits + 4; + } + + partialByte = ((partialByte << (bitToPad + 12)) | 0x0001); + partialBits += bitToPad + 12; + + while ( partialBits >= 8 ) { + partialBits -= 8; + buffer.append( (char)(partialByte >> partialBits ) ); + } +// writeG3Code( 0x0001, 12 ); +} + +void QtopiaPrintBuffer::append( short value ) +{ + if ( _bigEndian ) { + _data.append( (char)(value >> 8) ); + _data.append( (char)value ); + } else { + _data.append( (char)value ); + _data.append( (char)(value >> 8) ); + } +} + +void QtopiaPrintBuffer::append( int value ) +{ + if ( _bigEndian ) { + _data.append( (char)(value >> 24) ); + _data.append( (char)(value >> 16) ); + _data.append( (char)(value >> 8) ); + _data.append( (char)value ); + } else { + _data.append( (char)value ); + _data.append( (char)(value >> 8) ); + _data.append( (char)(value >> 16) ); + _data.append( (char)(value >> 24) ); + } +} + +void QtopiaPrintBuffer::patch( int posn, int value ) +{ + if ( _bigEndian ) { + _data[posn] = (char)(value >> 24); + _data[posn + 1] = (char)(value >> 16); + _data[posn + 2] = (char)(value >> 8); + _data[posn + 3] = (char)value; + } else { + _data[posn] = (char)value; + _data[posn + 1] = (char)(value >> 8); + _data[posn + 2] = (char)(value >> 16); + _data[posn + 3] = (char)(value >> 24); + } +} + +void QtopiaPrintBuffer::pad() +{ + while ( ( _data.size() % 4 ) != 0 ) + _data.append( (char)0 ); +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprintengine_qws_p.h b/src/gui/painting/qprintengine_qws_p.h new file mode 100644 index 0000000000..59cbe3eba8 --- /dev/null +++ b/src/gui/painting/qprintengine_qws_p.h @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_QWS_P_H +#define QPRINTENGINE_QWS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qprinter.h" + +#ifndef QT_NO_PRINTER + +#include "QtGui/qprintengine.h" +#include "QtCore/qbytearray.h" +#include "private/qpaintengine_p.h" + +QT_BEGIN_NAMESPACE + +class QtopiaPrintEnginePrivate; +class QRasterPaintEngine; +class QPrinterPrivate; +class QImage; + +class QtopiaPrintEngine : public QPaintEngine, public QPrintEngine +{ + Q_DECLARE_PRIVATE(QtopiaPrintEngine) +public: + QtopiaPrintEngine(QPrinter::PrinterMode mode); + + // override QWSPaintEngine + bool begin(QPaintDevice *dev); + bool end(); + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTextItem(const QPointF &p, const QTextItem &ti); + QPaintEngine::Type type() const { return QPaintEngine::X11; } + + QPaintEngine *paintEngine() const; + + void updateState(const QPaintEngineState &state); + + QRect paperRect() const; + QRect pageRect() const; + + bool newPage(); + bool abort(); + + QPrinter::PrinterState printerState() const; + + int metric(QPaintDevice::PaintDeviceMetric metricType) const; + + QVariant property(PrintEnginePropertyKey key) const; + void setProperty(PrintEnginePropertyKey key, const QVariant &value); + +private: + friend class QPrintDialog; + friend class QPageSetupDialog; + + void clearPage(); + void flushPage(); +}; + +class QtopiaPrintBuffer +{ +public: + QtopiaPrintBuffer( bool bigEndian=FALSE ) { _bigEndian = bigEndian; } + ~QtopiaPrintBuffer() {} + + const QByteArray& data() const { return _data; } + + int size() const { return _data.size(); } + + void clear() { _data.clear(); } + + void append( char value ) { _data.append( value ); } + void append( short value ); + void append( int value ); + void append( const QByteArray& array ) { _data.append( array ); } + + void patch( int posn, int value ); + + void pad(); + +private: + QByteArray _data; + bool _bigEndian; +}; + +#define QT_QWS_PRINTER_DEFAULT_DPI 200 + +class QtopiaPrintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QtopiaPrintEngine) +public: + QtopiaPrintEnginePrivate(QPrinter::PrinterMode m) : + mode(m), + printerState(QPrinter::Idle), + orientation(QPrinter::Portrait), + paperSize(QPrinter::A4), + pageOrder(QPrinter::FirstPageFirst), + colorMode(QPrinter::GrayScale), + paperSource(QPrinter::OnlyOne), + resolution(QT_QWS_PRINTER_DEFAULT_DPI), + _paintEngine(0), + numCopies(1), + outputToFile(false), + fullPage(false), + collateCopies(false), + pageNumber(0), + pageImage(0), + partialByte(0), + partialBits(0) + { + } + ~QtopiaPrintEnginePrivate(); + + void initialize(); + QPaintEngine *paintEngine(); + + QPrinter::PrinterMode mode; + + QString printerName; + QString outputFileName; + QString printProgram; + QString docName; + QString creator; + + QPrinter::PrinterState printerState; + + QPrinter::Orientation orientation; + QPrinter::PaperSize paperSize; + QPrinter::PageOrder pageOrder; + QPrinter::ColorMode colorMode; + QPrinter::PaperSource paperSource; + + int resolution; + QPaintEngine *_paintEngine; + int numCopies; + + bool outputToFile; + bool fullPage; + bool collateCopies; + + int pageNumber; + + QImage *pageImage; + + QtopiaPrintBuffer buffer; + + // Definitions that are only relevant to G3FAX output. + int ifdPatch; + int partialByte; + int partialBits; + void writeG3FaxHeader(); + void writeG3FaxPage(); + int writeG3IFDEntry( int tag, int type, int count, int value ); + void writeG3Code( int code, int bits ); + void writeG3WhiteRun( int len ); + void writeG3BlackRun( int len ); + void writeG3EOL(); +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_QWS_P_H diff --git a/src/gui/painting/qprintengine_win.cpp b/src/gui/painting/qprintengine_win.cpp new file mode 100644 index 0000000000..07d66f5bd0 --- /dev/null +++ b/src/gui/painting/qprintengine_win.cpp @@ -0,0 +1,1776 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_PRINTER + +#include "qprinter_p.h" +#include "qprintengine_win_p.h" + +#include <limits.h> + +#include <private/qfont_p.h> +#include <private/qfontengine_p.h> +#include <private/qpainter_p.h> + +#include <qbitmap.h> +#include <qdebug.h> +#include <qvector.h> +#include <qpicture.h> +#include <private/qpicture_p.h> + +QT_BEGIN_NAMESPACE + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +// #define QT_DEBUG_DRAW + +static void draw_text_item_win(const QPointF &_pos, const QTextItemInt &ti, HDC hdc, + bool convertToText, const QTransform &xform, const QPointF &topLeft); + +static const struct { + int winSizeName; + QPrinter::PaperSize qtSizeName; +} dmMapping[] = { + { DMPAPER_LETTER, QPrinter::Letter }, + { DMPAPER_LETTERSMALL, QPrinter::Letter }, + { DMPAPER_TABLOID, QPrinter::Tabloid }, + { DMPAPER_LEDGER, QPrinter::Ledger }, + { DMPAPER_LEGAL, QPrinter::Legal }, + { DMPAPER_EXECUTIVE, QPrinter::Executive }, + { DMPAPER_A3, QPrinter::A3 }, + { DMPAPER_A4, QPrinter::A4 }, + { DMPAPER_A4SMALL, QPrinter::A4 }, + { DMPAPER_A5, QPrinter::A5 }, + { DMPAPER_B4, QPrinter::B4 }, + { DMPAPER_B5, QPrinter::B5 }, + { DMPAPER_FOLIO, QPrinter::Folio }, + { DMPAPER_ENV_10, QPrinter::Comm10E }, + { DMPAPER_ENV_DL, QPrinter::DLE }, + { DMPAPER_ENV_C3, QPrinter::C5E }, + { DMPAPER_LETTER_EXTRA, QPrinter::Letter }, + { DMPAPER_LEGAL_EXTRA, QPrinter::Legal }, + { DMPAPER_TABLOID_EXTRA, QPrinter::Tabloid }, + { DMPAPER_A4_EXTRA, QPrinter::A4}, + { DMPAPER_LETTER_TRANSVERSE, QPrinter::Letter}, + { DMPAPER_A4_TRANSVERSE, QPrinter::A4}, + { DMPAPER_LETTER_EXTRA_TRANSVERSE, QPrinter::Letter }, + { DMPAPER_A_PLUS, QPrinter::A4 }, + { DMPAPER_B_PLUS, QPrinter::A3 }, + { DMPAPER_LETTER_PLUS, QPrinter::Letter }, + { DMPAPER_A4_PLUS, QPrinter::A4 }, + { DMPAPER_A5_TRANSVERSE, QPrinter::A5 }, + { DMPAPER_B5_TRANSVERSE, QPrinter::B5 }, + { DMPAPER_A3_EXTRA, QPrinter::A3 }, + { DMPAPER_A5_EXTRA, QPrinter::A5 }, + { DMPAPER_B5_EXTRA, QPrinter::B5 }, + { DMPAPER_A2, QPrinter::A2 }, + { DMPAPER_A3_TRANSVERSE, QPrinter::A3 }, + { DMPAPER_A3_EXTRA_TRANSVERSE,QPrinter::A3 }, + { 0, QPrinter::Custom } +}; + +QPrinter::PaperSize mapDevmodePaperSize(int s) +{ + int i = 0; + while ((dmMapping[i].winSizeName > 0) && (dmMapping[i].winSizeName != s)) + i++; + return dmMapping[i].qtSizeName; +} + +static int mapPaperSizeDevmode(QPrinter::PaperSize s) +{ + int i = 0; + while ((dmMapping[i].winSizeName > 0) && (dmMapping[i].qtSizeName != s)) + i++; + return dmMapping[i].winSizeName; +} + +static const struct { + int winSourceName; + QPrinter::PaperSource qtSourceName; +} sources[] = { + { DMBIN_ONLYONE, QPrinter::OnlyOne }, + { DMBIN_LOWER, QPrinter::Lower }, + { DMBIN_MIDDLE, QPrinter::Middle }, + { DMBIN_MANUAL, QPrinter::Manual }, + { DMBIN_ENVELOPE, QPrinter::Envelope }, + { DMBIN_ENVMANUAL, QPrinter::EnvelopeManual }, + { DMBIN_AUTO, QPrinter::Auto }, + { DMBIN_TRACTOR, QPrinter::Tractor }, + { DMBIN_SMALLFMT, QPrinter::SmallFormat }, + { DMBIN_LARGEFMT, QPrinter::LargeFormat }, + { DMBIN_LARGECAPACITY, QPrinter::LargeCapacity }, + { DMBIN_CASSETTE, QPrinter::Cassette }, + { DMBIN_FORMSOURCE, QPrinter::FormSource }, + { 0, (QPrinter::PaperSource) -1 } +}; + +static QPrinter::PaperSource mapDevmodePaperSource(int s) +{ + int i = 0; + while ((sources[i].winSourceName > 0) && (sources[i].winSourceName != s)) + i++; + return sources[i].winSourceName ? sources[i].qtSourceName : (QPrinter::PaperSource) s; +} + +static int mapPaperSourceDevmode(QPrinter::PaperSource s) +{ + int i = 0; + while ((sources[i].qtSourceName >= 0) && (sources[i].qtSourceName != s)) + i++; + return sources[i].winSourceName ? sources[i].winSourceName : s; +} + +QWin32PrintEngine::QWin32PrintEngine(QPrinter::PrinterMode mode) + : QAlphaPaintEngine(*(new QWin32PrintEnginePrivate), + PaintEngineFeatures(PrimitiveTransform + | PixmapTransform + | PerspectiveTransform + | PainterPaths + | Antialiasing + | PaintOutsidePaintEvent)) +{ + Q_D(QWin32PrintEngine); + d->docName = QLatin1String("document1"); + d->mode = mode; + d->queryDefault(); + d->initialize(); +} + +bool QWin32PrintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QWin32PrintEngine); + + QAlphaPaintEngine::begin(pdev); + if (!continueCall()) + return true; + + if (d->reinit) { + d->resetDC(); + d->reinit = false; + } + + // ### set default colors and stuff... + + bool ok = d->state == QPrinter::Idle; + + if (!d->hdc) + return false; + + // Assign the FILE: to get the query... + if (d->printToFile && d->fileName.isEmpty()) + d->fileName = d->port; + + d->devMode->dmCopies = d->num_copies; + + DOCINFO di; + memset(&di, 0, sizeof(DOCINFO)); + di.cbSize = sizeof(DOCINFO); + di.lpszDocName = reinterpret_cast<const wchar_t *>(d->docName.utf16()); + if (d->printToFile && !d->fileName.isEmpty()) + di.lpszOutput = reinterpret_cast<const wchar_t *>(d->fileName.utf16()); + if (ok && StartDoc(d->hdc, &di) == SP_ERROR) { + qErrnoWarning("QWin32PrintEngine::begin: StartDoc failed"); + ok = false; + } + + if (StartPage(d->hdc) <= 0) { + qErrnoWarning("QWin32PrintEngine::begin: StartPage failed"); + ok = false; + } + + if (!ok) { + d->state = QPrinter::Idle; + } else { + d->state = QPrinter::Active; + } + + d->matrix = QTransform(); + d->has_pen = true; + d->pen = QColor(Qt::black); + d->has_brush = false; + + d->complex_xform = false; + + updateMatrix(d->matrix); + + if (!ok) + cleanUp(); + + return ok; +} + +bool QWin32PrintEngine::end() +{ + Q_D(QWin32PrintEngine); + + if (d->hdc) { + if (d->state == QPrinter::Aborted) { + cleanUp(); + AbortDoc(d->hdc); + return true; + } + } + + QAlphaPaintEngine::end(); + if (!continueCall()) + return true; + + if (d->hdc) { + EndPage(d->hdc); // end; printing done + EndDoc(d->hdc); + } + + d->state = QPrinter::Idle; + d->reinit = true; + return true; +} + +bool QWin32PrintEngine::newPage() +{ + Q_D(QWin32PrintEngine); + Q_ASSERT(isActive()); + + Q_ASSERT(d->hdc); + + flushAndInit(); + + bool transparent = GetBkMode(d->hdc) == TRANSPARENT; + + if (!EndPage(d->hdc)) { + qErrnoWarning("QWin32PrintEngine::newPage: EndPage failed"); + return false; + } + + if (d->reinit) { + if (!d->resetDC()) { + qErrnoWarning("QWin32PrintEngine::newPage: ResetDC failed"); + return false; + } + d->reinit = false; + } + + if (!StartPage(d->hdc)) { + qErrnoWarning("Win32PrintEngine::newPage: StartPage failed"); + return false; + } + + SetTextAlign(d->hdc, TA_BASELINE); + if (transparent) + SetBkMode(d->hdc, TRANSPARENT); + + // ### + return true; + + bool success = false; + if (d->hdc && d->state == QPrinter::Active) { + if (EndPage(d->hdc) != SP_ERROR) { + // reinitialize the DC before StartPage if needed, + // because resetdc is disabled between calls to the StartPage and EndPage functions + // (see StartPage documentation in the Platform SDK:Windows GDI) +// state = PST_ACTIVEDOC; +// reinit(); +// state = PST_ACTIVE; + // start the new page now + if (d->reinit) { + if (!d->resetDC()) + qErrnoWarning("QWin32PrintEngine::newPage(), ResetDC failed (2)"); + d->reinit = false; + } + success = (StartPage(d->hdc) != SP_ERROR); + } + if (!success) { + d->state = QPrinter::Aborted; + return false; + } + } + return true; +} + +bool QWin32PrintEngine::abort() +{ + // do nothing loop. + return false; +} + +void QWin32PrintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(const QWin32PrintEngine); + + QAlphaPaintEngine::drawTextItem(p, textItem); + if (!continueCall()) + return; + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRgb brushColor = state->pen().brush().color().rgb(); + bool fallBack = state->pen().brush().style() != Qt::SolidPattern + || qAlpha(brushColor) != 0xff + || d->txop >= QTransform::TxProject + || ti.fontEngine->type() != QFontEngine::Win; + + + if (!fallBack) { + QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine); + + // Try selecting the font to see if we get a substitution font + SelectObject(d->hdc, fe->hfont); + + if (GetDeviceCaps(d->hdc, TECHNOLOGY) != DT_CHARSTREAM) { + wchar_t n[64]; + GetTextFace(d->hdc, 64, n); + fallBack = QString::fromWCharArray(n) + != QString::fromWCharArray(fe->logfont.lfFaceName); + } + } + + + if (fallBack) { + QPaintEngine::drawTextItem(p, textItem); + return ; + } + + // We only want to convert the glyphs to text if the entire string is compatible with ASCII + // and if we actually have access to the chars. + bool convertToText = ti.chars != 0; + for (int i=0; i < ti.num_chars; ++i) { + if (ti.chars[i].unicode() >= 0x80) { + convertToText = false; + break; + } + + if (ti.logClusters[i] != i) { + convertToText = false; + break; + } + } + + COLORREF cf = RGB(qRed(brushColor), qGreen(brushColor), qBlue(brushColor)); + SelectObject(d->hdc, CreateSolidBrush(cf)); + SelectObject(d->hdc, CreatePen(PS_SOLID, 1, cf)); + SetTextColor(d->hdc, cf); + + draw_text_item_win(p, ti, d->hdc, convertToText, d->matrix, d->devPaperRect.topLeft()); + DeleteObject(SelectObject(d->hdc,GetStockObject(HOLLOW_BRUSH))); + DeleteObject(SelectObject(d->hdc,GetStockObject(BLACK_PEN))); +} + +static inline qreal mmToInches(double mm) +{ + return mm*0.039370147; +} + +static inline qreal inchesToMM(double in) +{ + return in/0.039370147; +} + +int QWin32PrintEngine::metric(QPaintDevice::PaintDeviceMetric m) const +{ + Q_D(const QWin32PrintEngine); + + if (!d->hdc) + return 0; + + int val; + int res = d->resolution; + + switch (m) { + case QPaintDevice::PdmWidth: + if (d->has_custom_paper_size) { + val = qRound(d->paper_size.width() * res / 72.0); + } else { + int logPixelsX = GetDeviceCaps(d->hdc, LOGPIXELSX); + if (logPixelsX == 0) { + qWarning("QWin32PrintEngine::metric: GetDeviceCaps() failed, " + "might be a driver problem"); + logPixelsX = 600; // Reasonable default + } + val = res + * GetDeviceCaps(d->hdc, d->fullPage ? PHYSICALWIDTH : HORZRES) + / logPixelsX; + } + if (d->pageMarginsSet) + val -= int(mmToInches((d->previousDialogMargins.left() + + d->previousDialogMargins.width()) / 100.0) * res); + break; + case QPaintDevice::PdmHeight: + if (d->has_custom_paper_size) { + val = qRound(d->paper_size.height() * res / 72.0); + } else { + int logPixelsY = GetDeviceCaps(d->hdc, LOGPIXELSY); + if (logPixelsY == 0) { + qWarning("QWin32PrintEngine::metric: GetDeviceCaps() failed, " + "might be a driver problem"); + logPixelsY = 600; // Reasonable default + } + val = res + * GetDeviceCaps(d->hdc, d->fullPage ? PHYSICALHEIGHT : VERTRES) + / logPixelsY; + } + if (d->pageMarginsSet) + val -= int(mmToInches((d->previousDialogMargins.top() + + d->previousDialogMargins.height()) / 100.0) * res); + break; + case QPaintDevice::PdmDpiX: + val = res; + break; + case QPaintDevice::PdmDpiY: + val = res; + break; + case QPaintDevice::PdmPhysicalDpiX: + val = GetDeviceCaps(d->hdc, LOGPIXELSX); + break; + case QPaintDevice::PdmPhysicalDpiY: + val = GetDeviceCaps(d->hdc, LOGPIXELSY); + break; + case QPaintDevice::PdmWidthMM: + if (d->has_custom_paper_size) { + val = qRound(d->paper_size.width()*25.4/72); + } else { + if (!d->fullPage) { + val = GetDeviceCaps(d->hdc, HORZSIZE); + } else { + float wi = 25.4 * GetDeviceCaps(d->hdc, PHYSICALWIDTH); + int logPixelsX = GetDeviceCaps(d->hdc, LOGPIXELSX); + if (logPixelsX == 0) { + qWarning("QWin32PrintEngine::metric: GetDeviceCaps() failed, " + "might be a driver problem"); + logPixelsX = 600; // Reasonable default + } + val = qRound(wi / logPixelsX); + } + } + if (d->pageMarginsSet) + val -= (d->previousDialogMargins.left() + + d->previousDialogMargins.width()) / 100.0; + break; + case QPaintDevice::PdmHeightMM: + if (d->has_custom_paper_size) { + val = qRound(d->paper_size.height()*25.4/72); + } else { + if (!d->fullPage) { + val = GetDeviceCaps(d->hdc, VERTSIZE); + } else { + float hi = 25.4 * GetDeviceCaps(d->hdc, PHYSICALHEIGHT); + int logPixelsY = GetDeviceCaps(d->hdc, LOGPIXELSY); + if (logPixelsY == 0) { + qWarning("QWin32PrintEngine::metric: GetDeviceCaps() failed, " + "might be a driver problem"); + logPixelsY = 600; // Reasonable default + } + val = qRound(hi / logPixelsY); + } + } + if (d->pageMarginsSet) + val -= (d->previousDialogMargins.top() + + d->previousDialogMargins.height()) / 100.0; + break; + case QPaintDevice::PdmNumColors: + { + int bpp = GetDeviceCaps(d->hdc, BITSPIXEL); + if(bpp==32) + val = INT_MAX; + else if(bpp<=8) + val = GetDeviceCaps(d->hdc, NUMCOLORS); + else + val = 1 << (bpp * GetDeviceCaps(d->hdc, PLANES)); + } + break; + case QPaintDevice::PdmDepth: + val = GetDeviceCaps(d->hdc, PLANES); + break; + default: + qWarning("QPrinter::metric: Invalid metric command"); + return 0; + } + return val; +} + +void QWin32PrintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QWin32PrintEngine); + + QAlphaPaintEngine::updateState(state); + if (!continueCall()) + return; + + if (state.state() & DirtyTransform) { + updateMatrix(state.transform()); + } + + if (state.state() & DirtyPen) { + d->pen = state.pen(); + d->has_pen = d->pen.style() != Qt::NoPen && d->pen.isSolid(); + } + + if (state.state() & DirtyBrush) { + QBrush brush = state.brush(); + d->has_brush = brush.style() == Qt::SolidPattern; + d->brush_color = brush.color(); + } + + if (state.state() & DirtyClipEnabled) { + if (state.isClipEnabled()) + updateClipPath(painter()->clipPath(), Qt::ReplaceClip); + else + updateClipPath(QPainterPath(), Qt::NoClip); + } + + if (state.state() & DirtyClipPath) { + updateClipPath(state.clipPath(), state.clipOperation()); + } + + if (state.state() & DirtyClipRegion) { + QRegion clipRegion = state.clipRegion(); + QPainterPath clipPath = qt_regionToPath(clipRegion); + updateClipPath(clipPath, state.clipOperation()); + } +} + +void QWin32PrintEngine::updateClipPath(const QPainterPath &clipPath, Qt::ClipOperation op) +{ + Q_D(QWin32PrintEngine); + + bool doclip = true; + if (op == Qt::NoClip) { + SelectClipRgn(d->hdc, 0); + doclip = false; + } + + if (doclip) { + QPainterPath xformed = clipPath * d->matrix; + + if (xformed.isEmpty()) { + QRegion empty(-0x1000000, -0x1000000, 1, 1); + SelectClipRgn(d->hdc, empty.handle()); + } else { + d->composeGdiPath(xformed); + const int ops[] = { + -1, // Qt::NoClip, covered above + RGN_COPY, // Qt::ReplaceClip + RGN_AND, // Qt::IntersectClip + RGN_OR // Qt::UniteClip + }; + Q_ASSERT(op > 0 && unsigned(op) <= sizeof(ops) / sizeof(int)); + SelectClipPath(d->hdc, ops[op]); + } + } + + QPainterPath aclip = qt_regionToPath(alphaClipping()); + if (!aclip.isEmpty()) { + QTransform tx(d->stretch_x, 0, 0, d->stretch_y, d->origin_x, d->origin_y); + d->composeGdiPath(tx.map(aclip)); + SelectClipPath(d->hdc, RGN_DIFF); + } +} + +void QWin32PrintEngine::updateMatrix(const QTransform &m) +{ + Q_D(QWin32PrintEngine); + + QTransform stretch(d->stretch_x, 0, 0, d->stretch_y, d->origin_x, d->origin_y); + d->painterMatrix = m; + d->matrix = d->painterMatrix * stretch; + d->txop = d->matrix.type(); + d->complex_xform = (d->txop > QTransform::TxScale); +} + +void QWin32PrintEngine::drawPixmap(const QRectF &targetRect, + const QPixmap &originalPixmap, + const QRectF &sourceRect) +{ + Q_D(QWin32PrintEngine); + + QAlphaPaintEngine::drawPixmap(targetRect, originalPixmap, sourceRect); + if (!continueCall()) + return; + + const int tileSize = 2048; + + QRectF r = targetRect; + QRectF sr = sourceRect; + + QPixmap pixmap = originalPixmap; + if (sr.size() != pixmap.size()) { + pixmap = pixmap.copy(sr.toRect()); + } + + qreal scaleX = 1.0f; + qreal scaleY = 1.0f; + + QTransform scaleMatrix = QTransform::fromScale(r.width() / pixmap.width(), r.height() / pixmap.height()); + QTransform adapted = QPixmap::trueMatrix(d->painterMatrix * scaleMatrix, + pixmap.width(), pixmap.height()); + + qreal xform_offset_x = adapted.dx(); + qreal xform_offset_y = adapted.dy(); + + if (d->complex_xform) { + pixmap = pixmap.transformed(adapted); + scaleX = d->stretch_x; + scaleY = d->stretch_y; + } else { + scaleX = d->stretch_x * (r.width() / pixmap.width()) * d->painterMatrix.m11(); + scaleY = d->stretch_y * (r.height() / pixmap.height()) * d->painterMatrix.m22(); + } + + QPointF topLeft = r.topLeft() * d->painterMatrix; + int tx = int(topLeft.x() * d->stretch_x + d->origin_x); + int ty = int(topLeft.y() * d->stretch_y + d->origin_y); + int tw = qAbs(int(pixmap.width() * scaleX)); + int th = qAbs(int(pixmap.height() * scaleY)); + + xform_offset_x *= d->stretch_x; + xform_offset_y *= d->stretch_y; + + int dc_state = SaveDC(d->hdc); + + int tilesw = pixmap.width() / tileSize; + int tilesh = pixmap.height() / tileSize; + ++tilesw; + ++tilesh; + + int txinc = tileSize*scaleX; + int tyinc = tileSize*scaleY; + + for (int y = 0; y < tilesh; ++y) { + int tposy = ty + (y * tyinc); + int imgh = tileSize; + int height = tyinc; + if (y == (tilesh - 1)) { + imgh = pixmap.height() - (y * tileSize); + height = (th - (y * tyinc)); + } + for (int x = 0; x < tilesw; ++x) { + int tposx = tx + (x * txinc); + int imgw = tileSize; + int width = txinc; + if (x == (tilesw - 1)) { + imgw = pixmap.width() - (x * tileSize); + width = (tw - (x * txinc)); + } + + QPixmap p = pixmap.copy(tileSize * x, tileSize * y, imgw, imgh); + HBITMAP hbitmap = p.toWinHBITMAP(QPixmap::NoAlpha); + HDC display_dc = GetDC(0); + HDC hbitmap_hdc = CreateCompatibleDC(display_dc); + HGDIOBJ null_bitmap = SelectObject(hbitmap_hdc, hbitmap); + + ReleaseDC(0, display_dc); + + if (!StretchBlt(d->hdc, qRound(tposx - xform_offset_x), qRound(tposy - xform_offset_y), width, height, + hbitmap_hdc, 0, 0, p.width(), p.height(), SRCCOPY)) + qErrnoWarning("QWin32PrintEngine::drawPixmap, StretchBlt failed"); + + SelectObject(hbitmap_hdc, null_bitmap); + DeleteObject(hbitmap); + DeleteDC(hbitmap_hdc); + } + } + + RestoreDC(d->hdc, dc_state); +} + + +void QWin32PrintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &pos) +{ + Q_D(QWin32PrintEngine); + + QAlphaPaintEngine::drawTiledPixmap(r, pm, pos); + if (!continueCall()) + return; + + if (d->complex_xform || !pos.isNull()) { + QPaintEngine::drawTiledPixmap(r, pm, pos); + } else { + int dc_state = SaveDC(d->hdc); + + HDC display_dc = GetDC(0); + HBITMAP hbitmap = pm.toWinHBITMAP(QPixmap::NoAlpha); + HDC hbitmap_hdc = CreateCompatibleDC(display_dc); + HGDIOBJ null_bitmap = SelectObject(hbitmap_hdc, hbitmap); + + ReleaseDC(0, display_dc); + + QRectF trect = d->painterMatrix.mapRect(r); + int tx = int(trect.left() * d->stretch_x + d->origin_x); + int ty = int(trect.top() * d->stretch_y + d->origin_y); + + int xtiles = int(trect.width() / pm.width()) + 1; + int ytiles = int(trect.height() / pm.height()) + 1; + int xinc = int(pm.width() * d->stretch_x); + int yinc = int(pm.height() * d->stretch_y); + + for (int y = 0; y < ytiles; ++y) { + int ity = ty + (yinc * y); + int ith = pm.height(); + if (y == (ytiles - 1)) { + ith = int(trect.height() - (pm.height() * y)); + } + + for (int x = 0; x < xtiles; ++x) { + int itx = tx + (xinc * x); + int itw = pm.width(); + if (x == (xtiles - 1)) { + itw = int(trect.width() - (pm.width() * x)); + } + + if (!StretchBlt(d->hdc, itx, ity, int(itw * d->stretch_x), int(ith * d->stretch_y), + hbitmap_hdc, 0, 0, itw, ith, SRCCOPY)) + qErrnoWarning("QWin32PrintEngine::drawPixmap, StretchBlt failed"); + + } + } + + SelectObject(hbitmap_hdc, null_bitmap); + DeleteObject(hbitmap); + DeleteDC(hbitmap_hdc); + + RestoreDC(d->hdc, dc_state); + } +} + + +void QWin32PrintEnginePrivate::composeGdiPath(const QPainterPath &path) +{ + if (!BeginPath(hdc)) + qErrnoWarning("QWin32PrintEnginePrivate::drawPath: BeginPath failed"); + + // Drawing the subpaths + int start = -1; + for (int i=0; i<path.elementCount(); ++i) { + const QPainterPath::Element &elm = path.elementAt(i); + switch (elm.type) { + case QPainterPath::MoveToElement: + if (start >= 0 + && path.elementAt(start).x == path.elementAt(i-1).x + && path.elementAt(start).y == path.elementAt(i-1).y) + CloseFigure(hdc); + start = i; + MoveToEx(hdc, qRound(elm.x), qRound(elm.y), 0); + break; + case QPainterPath::LineToElement: + LineTo(hdc, qRound(elm.x), qRound(elm.y)); + break; + case QPainterPath::CurveToElement: { + POINT pts[3] = { + { qRound(elm.x), qRound(elm.y) }, + { qRound(path.elementAt(i+1).x), qRound(path.elementAt(i+1).y) }, + { qRound(path.elementAt(i+2).x), qRound(path.elementAt(i+2).y) } + }; + i+=2; + PolyBezierTo(hdc, pts, 3); + break; + } + default: + qFatal("QWin32PaintEngine::drawPath: Unhandled type: %d", elm.type); + } + } + + if (start >= 0 + && path.elementAt(start).x == path.elementAt(path.elementCount()-1).x + && path.elementAt(start).y == path.elementAt(path.elementCount()-1).y) + CloseFigure(hdc); + + if (!EndPath(hdc)) + qErrnoWarning("QWin32PaintEngine::drawPath: EndPath failed"); + + SetPolyFillMode(hdc, path.fillRule() == Qt::WindingFill ? WINDING : ALTERNATE); +} + + +void QWin32PrintEnginePrivate::fillPath_dev(const QPainterPath &path, const QColor &color) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " --- QWin32PrintEnginePrivate::fillPath() bound:" << path.boundingRect() << color; +#endif + + composeGdiPath(path); + + HBRUSH brush = CreateSolidBrush(RGB(color.red(), color.green(), color.blue())); + HGDIOBJ old_brush = SelectObject(hdc, brush); + FillPath(hdc); + DeleteObject(SelectObject(hdc, old_brush)); +} + +void QWin32PrintEnginePrivate::strokePath_dev(const QPainterPath &path, const QColor &color, qreal penWidth) +{ + composeGdiPath(path); + LOGBRUSH brush; + brush.lbStyle = BS_SOLID; + brush.lbColor = RGB(color.red(), color.green(), color.blue()); + DWORD capStyle = PS_ENDCAP_SQUARE; + DWORD joinStyle = PS_JOIN_BEVEL; + if (pen.capStyle() == Qt::FlatCap) + capStyle = PS_ENDCAP_FLAT; + else if (pen.capStyle() == Qt::RoundCap) + capStyle = PS_ENDCAP_ROUND; + + if (pen.joinStyle() == Qt::MiterJoin) + joinStyle = PS_JOIN_MITER; + else if (pen.joinStyle() == Qt::RoundJoin) + joinStyle = PS_JOIN_ROUND; + + HPEN pen = ExtCreatePen(((penWidth == 0) ? PS_COSMETIC : PS_GEOMETRIC) + | PS_SOLID | capStyle | joinStyle, + (penWidth == 0) ? 1 : penWidth, &brush, 0, 0); + + HGDIOBJ old_pen = SelectObject(hdc, pen); + StrokePath(hdc); + DeleteObject(SelectObject(hdc, old_pen)); +} + + +void QWin32PrintEnginePrivate::fillPath(const QPainterPath &path, const QColor &color) +{ + fillPath_dev(path * matrix, color); +} + +void QWin32PrintEnginePrivate::strokePath(const QPainterPath &path, const QColor &color) +{ + QPainterPathStroker stroker; + if (pen.style() == Qt::CustomDashLine) { + stroker.setDashPattern(pen.dashPattern()); + stroker.setDashOffset(pen.dashOffset()); + } else { + stroker.setDashPattern(pen.style()); + } + stroker.setCapStyle(pen.capStyle()); + stroker.setJoinStyle(pen.joinStyle()); + stroker.setMiterLimit(pen.miterLimit()); + + QPainterPath stroke; + qreal width = pen.widthF(); + if (pen.style() == Qt::SolidLine && (pen.isCosmetic() || matrix.type() < QTransform::TxScale)) { + strokePath_dev(path * matrix, color, width); + } else { + stroker.setWidth(width); + if (pen.isCosmetic()) { + stroke = stroker.createStroke(path * matrix); + } else { + stroke = stroker.createStroke(path) * painterMatrix; + QTransform stretch(stretch_x, 0, 0, stretch_y, origin_x, origin_y); + stroke = stroke * stretch; + } + + if (stroke.isEmpty()) + return; + + fillPath_dev(stroke, color); + } +} + + +void QWin32PrintEngine::drawPath(const QPainterPath &path) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QWin32PrintEngine::drawPath(), bounds: " << path.boundingRect(); +#endif + + Q_D(QWin32PrintEngine); + + QAlphaPaintEngine::drawPath(path); + if (!continueCall()) + return; + + if (d->has_brush) + d->fillPath(path, d->brush_color); + + if (d->has_pen) + d->strokePath(path, d->pen.color()); +} + + +void QWin32PrintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QWin32PrintEngine::drawPolygon(), pointCount: " << pointCount; +#endif + + QAlphaPaintEngine::drawPolygon(points, pointCount, mode); + if (!continueCall()) + return; + + Q_ASSERT(pointCount > 1); + + QPainterPath path(points[0]); + + for (int i=1; i<pointCount; ++i) { + path.lineTo(points[i]); + } + + Q_D(QWin32PrintEngine); + + bool has_brush = d->has_brush; + + if (mode == PolylineMode) + d->has_brush = false; // No brush for polylines + else + path.closeSubpath(); // polygons are should always be closed. + + drawPath(path); + d->has_brush = has_brush; +} + +void QWin32PrintEnginePrivate::queryDefault() +{ + /* Read the default printer name, driver and port with the intuitive function + * Strings "windows" and "device" are specified in the MSDN under EnumPrinters() + */ + QString noPrinters(QLatin1String("qt_no_printers")); + wchar_t buffer[256]; + GetProfileString(L"windows", L"device", + reinterpret_cast<const wchar_t *>(noPrinters.utf16()), + buffer, 256); + QString output = QString::fromWCharArray(buffer); + if (output.isEmpty() || output == noPrinters) // no printers + return; + + QStringList info = output.split(QLatin1Char(',')); + int infoSize = info.size(); + if (infoSize > 0) { + if (name.isEmpty()) + name = info.at(0); + if (program.isEmpty() && infoSize > 1) + program = info.at(1); + if (port.isEmpty() && infoSize > 2) + port = info.at(2); + } +} + +QWin32PrintEnginePrivate::~QWin32PrintEnginePrivate() +{ + if (hdc) + release(); +} + +void QWin32PrintEnginePrivate::initialize() +{ + if (hdc) + release(); + Q_ASSERT(!hPrinter); + Q_ASSERT(!hdc); + Q_ASSERT(!devMode); + Q_ASSERT(!pInfo); + + if (name.isEmpty()) + return; + + txop = QTransform::TxNone; + + bool ok = OpenPrinter((LPWSTR)name.utf16(), (LPHANDLE)&hPrinter, 0); + if (!ok) { + qErrnoWarning("QWin32PrintEngine::initialize: OpenPrinter failed"); + return; + } + + // Fetch the PRINTER_INFO_2 with DEVMODE data containing the + // printer settings. + DWORD infoSize, numBytes; + GetPrinter(hPrinter, 2, NULL, 0, &infoSize); + hMem = GlobalAlloc(GHND, infoSize); + pInfo = (PRINTER_INFO_2*) GlobalLock(hMem); + ok = GetPrinter(hPrinter, 2, (LPBYTE)pInfo, infoSize, &numBytes); + + if (!ok) { + qErrnoWarning("QWin32PrintEngine::initialize: GetPrinter failed"); + GlobalUnlock(pInfo); + GlobalFree(hMem); + ClosePrinter(hPrinter); + pInfo = 0; + hMem = 0; + hPrinter = 0; + return; + } + + devMode = pInfo->pDevMode; + hdc = CreateDC(reinterpret_cast<const wchar_t *>(program.utf16()), + reinterpret_cast<const wchar_t *>(name.utf16()), 0, devMode); + + Q_ASSERT(hPrinter); + Q_ASSERT(pInfo); + + if (devMode) { + num_copies = devMode->dmCopies; + } + + initHDC(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QWin32PrintEngine::initialize()" << endl + << " - paperRect" << devPaperRect << endl + << " - pageRect" << devPageRect << endl + << " - stretch_x" << stretch_x << endl + << " - stretch_y" << stretch_y << endl + << " - origin_x" << origin_x << endl + << " - origin_y" << origin_y << endl; +#endif +} + +void QWin32PrintEnginePrivate::initHDC() +{ + Q_ASSERT(hdc); + + HDC display_dc = GetDC(0); + dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); + dpi_y = GetDeviceCaps(hdc, LOGPIXELSY); + dpi_display = GetDeviceCaps(display_dc, LOGPIXELSY); + ReleaseDC(0, display_dc); + if (dpi_display == 0) { + qWarning("QWin32PrintEngine::metric: GetDeviceCaps() failed, " + "might be a driver problem"); + dpi_display = 96; // Reasonable default + } + + switch(mode) { + case QPrinter::ScreenResolution: + resolution = dpi_display; + stretch_x = dpi_x / double(dpi_display); + stretch_y = dpi_y / double(dpi_display); + break; + case QPrinter::PrinterResolution: + case QPrinter::HighResolution: + resolution = dpi_y; + stretch_x = 1; + stretch_y = 1; + break; + default: + break; + } + + initDevRects(); +} + +void QWin32PrintEnginePrivate::initDevRects() +{ + devPaperRect = QRect(0, 0, + GetDeviceCaps(hdc, PHYSICALWIDTH), + GetDeviceCaps(hdc, PHYSICALHEIGHT)); + devPhysicalPageRect = QRect(GetDeviceCaps(hdc, PHYSICALOFFSETX), + GetDeviceCaps(hdc, PHYSICALOFFSETY), + GetDeviceCaps(hdc, HORZRES), + GetDeviceCaps(hdc, VERTRES)); + if (!pageMarginsSet) + devPageRect = devPhysicalPageRect; + else + devPageRect = devPaperRect.adjusted(qRound(mmToInches(previousDialogMargins.left() / 100.0) * dpi_x), + qRound(mmToInches(previousDialogMargins.top() / 100.0) * dpi_y), + -qRound(mmToInches(previousDialogMargins.width() / 100.0) * dpi_x), + -qRound(mmToInches(previousDialogMargins.height() / 100.0) * dpi_y)); + updateOrigin(); +} + +void QWin32PrintEnginePrivate::setPageMargins(int marginLeft, int marginTop, int marginRight, int marginBottom) +{ + pageMarginsSet = true; + previousDialogMargins = QRect(marginLeft, marginTop, marginRight, marginBottom); + + devPageRect = devPaperRect.adjusted(qRound(mmToInches(marginLeft / 100.0) * dpi_x), + qRound(mmToInches(marginTop / 100.0) * dpi_y), + - qRound(mmToInches(marginRight / 100.0) * dpi_x), + - qRound(mmToInches(marginBottom / 100.0) * dpi_y)); + updateOrigin(); +} + +QRect QWin32PrintEnginePrivate::getPageMargins() const +{ + if (pageMarginsSet) + return previousDialogMargins; + else + return QRect(qRound(inchesToMM(devPhysicalPageRect.left()) * 100.0 / dpi_x), + qRound(inchesToMM(devPhysicalPageRect.top()) * 100.0 / dpi_y), + qRound(inchesToMM(devPaperRect.right() - devPhysicalPageRect.right()) * 100.0 / dpi_x), + qRound(inchesToMM(devPaperRect.bottom() - devPhysicalPageRect.bottom()) * 100.0 / dpi_y)); +} + +void QWin32PrintEnginePrivate::release() +{ + if (hdc == 0) + return; + + if (globalDevMode) { // Devmode comes from print dialog + GlobalUnlock(globalDevMode); + } else { // Devmode comes from initialize... + // devMode is a part of the same memory block as pInfo so one free is enough... + GlobalUnlock(hMem); + GlobalFree(hMem); + } + if (hPrinter) + ClosePrinter(hPrinter); + DeleteDC(hdc); + + hdc = 0; + hPrinter = 0; + pInfo = 0; + hMem = 0; + devMode = 0; +} + +QList<QVariant> QWin32PrintEnginePrivate::queryResolutions() const +{ + // Read the supported resolutions of the printer. + QList<QVariant> list; + + DWORD numRes = DeviceCapabilities(reinterpret_cast<const wchar_t *>(name.utf16()), + reinterpret_cast<const wchar_t *>(port.utf16()), + DC_ENUMRESOLUTIONS, 0, 0); + if (numRes == (DWORD)-1) + return list; + + LONG *enumRes = (LONG*)malloc(numRes * 2 * sizeof(LONG)); + DWORD errRes = DeviceCapabilities(reinterpret_cast<const wchar_t *>(name.utf16()), + reinterpret_cast<const wchar_t *>(port.utf16()), + DC_ENUMRESOLUTIONS, (LPWSTR)enumRes, 0); + + if (errRes == (DWORD)-1) { + qErrnoWarning("QWin32PrintEngine::queryResolutions: DeviceCapabilities failed"); + return list; + } + + for (uint i=0; i<numRes; ++i) + list.append(int(enumRes[i * 2])); + + return list; +} + +void QWin32PrintEnginePrivate::doReinit() +{ + if (state == QPrinter::Active) { + reinit = true; + } else { + resetDC(); + initDevRects(); + reinit = false; + } +} + +void QWin32PrintEnginePrivate::updateOrigin() +{ + if (fullPage) { + // subtract physical margins to make (0,0) absolute top corner of paper + // then add user defined margins + origin_x = -devPhysicalPageRect.x(); + origin_y = -devPhysicalPageRect.y(); + if (pageMarginsSet) { + origin_x += devPageRect.left(); + origin_y += devPageRect.top(); + } + } else { + origin_x = 0; + origin_y = 0; + if (pageMarginsSet) { + origin_x = devPageRect.left() - devPhysicalPageRect.x(); + origin_y = devPageRect.top() - devPhysicalPageRect.y(); + } + } +} + +void QWin32PrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QWin32PrintEngine); + switch (key) { + case PPK_CollateCopies: + { + if (!d->devMode) + break; + d->devMode->dmCollate = value.toBool() ? DMCOLLATE_TRUE : DMCOLLATE_FALSE; + d->doReinit(); + } + break; + + case PPK_ColorMode: + { + if (!d->devMode) + break; + d->devMode->dmColor = (value.toInt() == QPrinter::Color) ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME; + d->doReinit(); + } + break; + + case PPK_Creator: + + break; + + case PPK_DocumentName: + if (isActive()) { + qWarning("QWin32PrintEngine: Cannot change document name while printing is active"); + return; + } + d->docName = value.toString(); + break; + + case PPK_FullPage: + d->fullPage = value.toBool(); + d->updateOrigin(); + break; + + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + if (!d->devMode) + break; + d->num_copies = value.toInt(); + d->devMode->dmCopies = d->num_copies; + d->doReinit(); + break; + + case PPK_Orientation: + { + if (!d->devMode) + break; + int orientation = value.toInt() == QPrinter::Landscape ? DMORIENT_LANDSCAPE : DMORIENT_PORTRAIT; + int old_orientation = d->devMode->dmOrientation; + d->devMode->dmOrientation = orientation; + if (d->has_custom_paper_size && old_orientation != orientation) + d->paper_size = QSizeF(d->paper_size.height(), d->paper_size.width()); + d->doReinit(); + } + break; + + case PPK_OutputFileName: + if (isActive()) { + qWarning("QWin32PrintEngine: Cannot change filename while printing"); + } else { + d->fileName = value.toString(); + d->printToFile = !value.toString().isEmpty(); + } + break; + + case PPK_PaperSize: + if (!d->devMode) + break; + d->devMode->dmPaperSize = mapPaperSizeDevmode(QPrinter::PaperSize(value.toInt())); + d->has_custom_paper_size = (QPrinter::PaperSize(value.toInt()) == QPrinter::Custom); + d->doReinit(); + break; + + case PPK_PaperSource: + { + if (!d->devMode) + break; + int dmMapped = DMBIN_AUTO; + + QList<QVariant> v = property(PPK_PaperSources).toList(); + if (v.contains(value)) + dmMapped = mapPaperSourceDevmode(QPrinter::PaperSource(value.toInt())); + + d->devMode->dmDefaultSource = dmMapped; + d->doReinit(); + } + break; + + case PPK_PrinterName: + d->name = value.toString(); + if(d->name.isEmpty()) + d->queryDefault(); + d->initialize(); + break; + + case PPK_Resolution: + { + d->resolution = value.toInt(); + + d->stretch_x = d->dpi_x / double(d->resolution); + d->stretch_y = d->dpi_y / double(d->resolution); + } + break; + + case PPK_SelectionOption: + + break; + + case PPK_SupportedResolutions: + + break; + + + case PPK_WindowsPageSize: + if (!d->devMode) + break; + d->has_custom_paper_size = false; + d->devMode->dmPaperSize = value.toInt(); + d->doReinit(); + break; + + case PPK_CustomPaperSize: + { + d->has_custom_paper_size = true; + d->paper_size = value.toSizeF(); + if (!d->devMode) + break; + int orientation = d->devMode->dmOrientation; + DWORD needed = 0; + DWORD returned = 0; + if (!EnumForms(d->hPrinter, 1, 0, 0, &needed, &returned)) { + BYTE *forms = (BYTE *) malloc(needed); + if (EnumForms(d->hPrinter, 1, forms, needed, &needed, &returned)) { + for (DWORD i=0; i< returned; ++i) { + FORM_INFO_1 *formArray = reinterpret_cast<FORM_INFO_1 *>(forms); + // the form sizes are specified in 1000th of a mm, + // convert the size to Points + QSizeF size((formArray[i].Size.cx * 72/25.4)/1000.0, + (formArray[i].Size.cy * 72/25.4)/1000.0); + if (qAbs(d->paper_size.width() - size.width()) <= 2 + && qAbs(d->paper_size.height() - size.height()) <= 2) + { + d->devMode->dmPaperSize = i + 1; + break; + } + } + } + free(forms); + } + if (orientation != DMORIENT_PORTRAIT) + d->paper_size = QSizeF(d->paper_size.height(), d->paper_size.width()); + break; + } + + case PPK_PageMargins: + { + QList<QVariant> margins(value.toList()); + Q_ASSERT(margins.size() == 4); + int left, top, right, bottom; + // specified in 1/100 mm + left = (margins.at(0).toReal()*25.4/72.0) * 100; + top = (margins.at(1).toReal()*25.4/72.0) * 100; + right = (margins.at(2).toReal()*25.4/72.0) * 100; + bottom = (margins.at(3).toReal()*25.4/72.0) * 100; + d->setPageMargins(left, top, right, bottom); + break; + } + default: + // Do nothing + break; + } +} + +QVariant QWin32PrintEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QWin32PrintEngine); + QVariant value; + switch (key) { + + case PPK_CollateCopies: + value = false; + break; + + case PPK_ColorMode: + { + if (!d->devMode) { + value = QPrinter::Color; + } else { + value = (d->devMode->dmColor == DMCOLOR_COLOR) ? QPrinter::Color : QPrinter::GrayScale; + } + } + break; + + case PPK_DocumentName: + value = d->docName; + break; + + case PPK_FullPage: + value = d->fullPage; + break; + + case PPK_CopyCount: + value = d->num_copies; + break; + + case PPK_SupportsMultipleCopies: + value = true; + break; + + case PPK_NumberOfCopies: + value = 1; + break; + + case PPK_Orientation: + { + if (!d->devMode) { + value = QPrinter::Portrait; + } else { + value = (d->devMode->dmOrientation == DMORIENT_LANDSCAPE) ? QPrinter::Landscape : QPrinter::Portrait; + } + } + break; + + case PPK_OutputFileName: + value = d->fileName; + break; + + case PPK_PageRect: + if (d->has_custom_paper_size) { + QRect rect(0, 0, + qRound(d->paper_size.width() * d->resolution / 72.0), + qRound(d->paper_size.height() * d->resolution / 72.0)); + if (d->pageMarginsSet) { + rect = rect.adjusted(qRound(mmToInches(d->previousDialogMargins.left()/100.0) * d->resolution), + qRound(mmToInches(d->previousDialogMargins.top()/100.0) * d->resolution), + -qRound(mmToInches(d->previousDialogMargins.width()/100.0) * d->resolution), + -qRound(mmToInches(d->previousDialogMargins.height()/100.0) * d->resolution)); + } + value = rect; + } else { + value = QTransform(1/d->stretch_x, 0, 0, 1/d->stretch_y, 0, 0) + .mapRect(d->fullPage ? d->devPhysicalPageRect : d->devPageRect); + } + break; + + case PPK_PaperSize: + if (d->has_custom_paper_size) { + value = QPrinter::Custom; + } else { + if (!d->devMode) { + value = QPrinter::A4; + } else { + value = mapDevmodePaperSize(d->devMode->dmPaperSize); + } + } + break; + + case PPK_PaperRect: + if (d->has_custom_paper_size) { + value = QRect(0, 0, + qRound(d->paper_size.width() * d->resolution / 72.0), + qRound(d->paper_size.height() * d->resolution / 72.0)); + } else { + value = QTransform(1/d->stretch_x, 0, 0, 1/d->stretch_y, 0, 0).mapRect(d->devPaperRect); + } + break; + + case PPK_PaperSource: + if (!d->devMode) { + value = QPrinter::Auto; + } else { + value = mapDevmodePaperSource(d->devMode->dmDefaultSource); + } + break; + + case PPK_PrinterName: + value = d->name; + break; + + case PPK_Resolution: + if (d->resolution || !d->name.isEmpty()) + value = d->resolution; + break; + + case PPK_SupportedResolutions: + value = d->queryResolutions(); + break; + + case PPK_WindowsPageSize: + if (!d->devMode) { + value = -1; + } else { + value = d->devMode->dmPaperSize; + } + break; + + case PPK_PaperSources: + { + int available = DeviceCapabilities((const wchar_t *)d->name.utf16(), + (const wchar_t *)d->port.utf16(), DC_BINS, 0, d->devMode); + + if (available <= 0) + break; + + wchar_t *data = new wchar_t[available]; + int count = DeviceCapabilities((const wchar_t *)d->name.utf16(), + (const wchar_t *)d->port.utf16(), DC_BINS, data, d->devMode); + + QList<QVariant> out; + for (int i=0; i<count; ++i) { + QPrinter::PaperSource src = mapDevmodePaperSource(data[i]); + if (src != -1) + out << (int) src; + } + value = out; + + delete [] data; + } + break; + + case PPK_CustomPaperSize: + value = d->paper_size; + break; + + case PPK_PageMargins: + { + QList<QVariant> margins; + QRect pageMargins(d->getPageMargins()); + + // specified in 1/100 mm + margins << (mmToInches(pageMargins.left()/100.0) * 72) + << (mmToInches(pageMargins.top()/100.0) * 72) + << (mmToInches(pageMargins.width()/100.0) * 72) + << (mmToInches(pageMargins.height()/100.0) * 72); + value = margins; + break; + } + default: + // Do nothing + break; + } + return value; +} + +QPrinter::PrinterState QWin32PrintEngine::printerState() const +{ + return d_func()->state; +} + +HDC QWin32PrintEngine::getDC() const +{ + return d_func()->hdc; +} + +void QWin32PrintEngine::releaseDC(HDC) const +{ + +} + +HGLOBAL *QWin32PrintEnginePrivate::createDevNames() +{ + int size = sizeof(DEVNAMES) + + program.length() * 2 + 2 + + name.length() * 2 + 2 + + port.length() * 2 + 2; + HGLOBAL *hGlobal = (HGLOBAL *) GlobalAlloc(GMEM_MOVEABLE, size); + DEVNAMES *dn = (DEVNAMES*) GlobalLock(hGlobal); + + dn->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t); + dn->wDeviceOffset = dn->wDriverOffset + program.length() + 1; + dn->wOutputOffset = dn->wDeviceOffset + name.length() + 1; + + memcpy((ushort*)dn + dn->wDriverOffset, program.utf16(), program.length() * 2 + 2); + memcpy((ushort*)dn + dn->wDeviceOffset, name.utf16(), name.length() * 2 + 2); + memcpy((ushort*)dn + dn->wOutputOffset, port.utf16(), port.length() * 2 + 2); + dn->wDefault = 0; + + GlobalUnlock(hGlobal); + +// printf("QPrintDialogWinPrivate::createDevNames()\n" +// " -> wDriverOffset: %d\n" +// " -> wDeviceOffset: %d\n" +// " -> wOutputOffset: %d\n", +// dn->wDriverOffset, +// dn->wDeviceOffset, +// dn->wOutputOffset); + +// printf("QPrintDialogWinPrivate::createDevNames(): %s, %s, %s\n", +// QString::fromWCharArray((wchar_t*)(dn) + dn->wDriverOffset).latin1(), +// QString::fromWCharArray((wchar_t*)(dn) + dn->wDeviceOffset).latin1(), +// QString::fromWCharArray((wchar_t*)(dn) + dn->wOutputOffset).latin1()); + + return hGlobal; +} + +void QWin32PrintEnginePrivate::readDevnames(HGLOBAL globalDevnames) +{ + if (globalDevnames) { + DEVNAMES *dn = (DEVNAMES*) GlobalLock(globalDevnames); + name = QString::fromWCharArray((wchar_t*)(dn) + dn->wDeviceOffset); + port = QString::fromWCharArray((wchar_t*)(dn) + dn->wOutputOffset); + program = QString::fromWCharArray((wchar_t*)(dn) + dn->wDriverOffset); + GlobalUnlock(globalDevnames); + } +} + +void QWin32PrintEnginePrivate::readDevmode(HGLOBAL globalDevmode) +{ + if (globalDevmode) { + DEVMODE *dm = (DEVMODE*) GlobalLock(globalDevmode); + release(); + globalDevMode = globalDevmode; + devMode = dm; + hdc = CreateDC(reinterpret_cast<const wchar_t *>(program.utf16()), + reinterpret_cast<const wchar_t *>(name.utf16()), 0, dm); + + num_copies = devMode->dmCopies; + if (!OpenPrinter((wchar_t*)name.utf16(), &hPrinter, 0)) + qWarning("QPrinter: OpenPrinter() failed after reading DEVMODE."); + } + + if (hdc) + initHDC(); +} + +static void draw_text_item_win(const QPointF &pos, const QTextItemInt &ti, HDC hdc, + bool convertToText, const QTransform &xform, const QPointF &topLeft) +{ + QFontEngine *fe = ti.fontEngine; + QPointF baseline_pos = xform.inverted().map(xform.map(pos) - topLeft); + + SetTextAlign(hdc, TA_BASELINE); + SetBkMode(hdc, TRANSPARENT); + + bool has_kerning = ti.f && ti.f->kerning(); + QFontEngineWin *winfe = (fe->type() == QFontEngine::Win) ? static_cast<QFontEngineWin *>(fe) : 0; + + HFONT hfont; + bool ttf = false; + + if (winfe) { + hfont = winfe->hfont; + ttf = winfe->ttf; + } else { + hfont = (HFONT)GetStockObject(ANSI_VAR_FONT); + } + + HGDIOBJ old_font = SelectObject(hdc, hfont); + unsigned int options = (ttf && !convertToText) ? ETO_GLYPH_INDEX : 0; + wchar_t *convertedGlyphs = (wchar_t *)ti.chars; + QGlyphLayout glyphs = ti.glyphs; + + bool fast = !has_kerning && !(ti.flags & QTextItem::RightToLeft); + for (int i = 0; fast && i < glyphs.numGlyphs; i++) { + if (glyphs.offsets[i].x != 0 || glyphs.offsets[i].y != 0 || glyphs.justifications[i].space_18d6 != 0 + || glyphs.attributes[i].dontPrint) { + fast = false; + break; + } + } + +#if !defined(Q_OS_WINCE) + // Scale, rotate and translate here. + XFORM win_xform; + win_xform.eM11 = xform.m11(); + win_xform.eM12 = xform.m12(); + win_xform.eM21 = xform.m21(); + win_xform.eM22 = xform.m22(); + win_xform.eDx = xform.dx(); + win_xform.eDy = xform.dy(); + + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &win_xform); +#endif + + if (fast) { + // fast path + QVarLengthArray<wchar_t> g(glyphs.numGlyphs); + for (int i = 0; i < glyphs.numGlyphs; ++i) + g[i] = glyphs.glyphs[i]; + ExtTextOut(hdc, + qRound(baseline_pos.x() + glyphs.offsets[0].x.toReal()), + qRound(baseline_pos.y() + glyphs.offsets[0].y.toReal()), + options, 0, convertToText ? convertedGlyphs : g.data(), glyphs.numGlyphs, 0); + } else { + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> _glyphs; + + QTransform matrix = QTransform::fromTranslate(baseline_pos.x(), baseline_pos.y()); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, + _glyphs, positions); + if (_glyphs.size() == 0) { + SelectObject(hdc, old_font); + return; + } + + convertToText = convertToText && glyphs.numGlyphs == _glyphs.size(); + bool outputEntireItem = _glyphs.size() > 0; + + if (outputEntireItem) { + options |= ETO_PDY; + QVarLengthArray<INT> glyphDistances(_glyphs.size() * 2); + QVarLengthArray<wchar_t> g(_glyphs.size()); + for (int i=0; i<_glyphs.size() - 1; ++i) { + glyphDistances[i * 2] = qRound(positions[i + 1].x) - qRound(positions[i].x); + glyphDistances[i * 2 + 1] = qRound(positions[i + 1].y) - qRound(positions[i].y); + g[i] = _glyphs[i]; + } + glyphDistances[(_glyphs.size() - 1) * 2] = 0; + glyphDistances[(_glyphs.size() - 1) * 2 + 1] = 0; + g[_glyphs.size() - 1] = _glyphs[_glyphs.size() - 1]; + ExtTextOut(hdc, qRound(positions[0].x), qRound(positions[0].y), options, 0, + convertToText ? convertedGlyphs : g.data(), _glyphs.size(), + glyphDistances.data()); + } else { + int i = 0; + while(i < _glyphs.size()) { + wchar_t g = _glyphs[i]; + + ExtTextOut(hdc, qRound(positions[i].x), + qRound(positions[i].y), options, 0, + convertToText ? convertedGlyphs + i : &g, 1, 0); + ++i; + } + } + } + +#if !defined(Q_OS_WINCE) + win_xform.eM11 = win_xform.eM22 = 1.0; + win_xform.eM12 = win_xform.eM21 = win_xform.eDx = win_xform.eDy = 0.0; + SetWorldTransform(hdc, &win_xform); +#endif + + SelectObject(hdc, old_font); +} + + +void QWin32PrintEnginePrivate::updateCustomPaperSize() +{ + uint paperSize = devMode->dmPaperSize; + if (paperSize > 0 && mapDevmodePaperSize(paperSize) == QPrinter::Custom) { + has_custom_paper_size = true; + DWORD needed = 0; + DWORD returned = 0; + if (!EnumForms(hPrinter, 1, 0, 0, &needed, &returned)) { + BYTE *forms = (BYTE *) malloc(needed); + if (EnumForms(hPrinter, 1, forms, needed, &needed, &returned)) { + if (paperSize <= returned) { + FORM_INFO_1 *formArray = (FORM_INFO_1 *) forms; + int width = formArray[paperSize - 1].Size.cx; // 1/1000 of a mm + int height = formArray[paperSize - 1].Size.cy; // 1/1000 of a mm + paper_size = QSizeF((width * 72 /25.4) / 1000.0, (height * 72 / 25.4) / 1000.0); + } else { + has_custom_paper_size = false; + } + } + free(forms); + } + } else { + has_custom_paper_size = false; + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprintengine_win_p.h b/src/gui/painting/qprintengine_win_p.h new file mode 100644 index 0000000000..b4d0670e7b --- /dev/null +++ b/src/gui/painting/qprintengine_win_p.h @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTENGINE_WIN_P_H +#define QPRINTENGINE_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_PRINTER + +#include "QtGui/qprinter.h" +#include "QtGui/qprintengine.h" +#include "QtGui/qpaintengine.h" +#include "QtCore/qt_windows.h" +#include "private/qpaintengine_alpha_p.h" + +QT_BEGIN_NAMESPACE + +class QWin32PrintEnginePrivate; +class QPrinterPrivate; +class QPainterState; + +class QWin32PrintEngine : public QAlphaPaintEngine, public QPrintEngine +{ + Q_DECLARE_PRIVATE(QWin32PrintEngine) +public: + QWin32PrintEngine(QPrinter::PrinterMode mode); + + // override QWin32PaintEngine + bool begin(QPaintDevice *dev); + bool end(); + + void updateState(const QPaintEngineState &state); + + void updateMatrix(const QTransform &matrix); + void updateClipPath(const QPainterPath &clip, Qt::ClipOperation op); + + void drawPath(const QPainterPath &path); + void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode); + void drawTextItem(const QPointF &p, const QTextItem &textItem); + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr); + void drawTiledPixmap(const QRectF &r, const QPixmap &pm, const QPointF &p); + void setProperty(PrintEnginePropertyKey key, const QVariant &value); + QVariant property(PrintEnginePropertyKey key) const; + + bool newPage(); + bool abort(); + int metric(QPaintDevice::PaintDeviceMetric) const; + + QPrinter::PrinterState printerState() const; + + QPaintEngine::Type type() const { return Windows; } + + HDC getDC() const; + void releaseDC(HDC) const; + + HDC getPrinterDC() const { return getDC(); } + void releasePrinterDC(HDC dc) const { releaseDC(dc); } + +private: + friend class QPrintDialog; + friend class QPageSetupDialog; +}; + +class QWin32PrintEnginePrivate : public QAlphaPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QWin32PrintEngine) +public: + QWin32PrintEnginePrivate() : + hPrinter(0), + globalDevMode(0), + devMode(0), + pInfo(0), + hdc(0), + mode(QPrinter::ScreenResolution), + state(QPrinter::Idle), + resolution(0), + pageMarginsSet(false), + num_copies(1), + printToFile(false), + fullPage(false), + reinit(false), + has_custom_paper_size(false) + { + } + + ~QWin32PrintEnginePrivate(); + + + /* Reads the default printer name and its driver (printerProgram) into + the engines private data. */ + void queryDefault(); + + /* Initializes the printer data based on the current printer name. This + function creates a DEVMODE struct, HDC and a printer handle. If these + structures are already in use, they are freed using release + */ + void initialize(); + + /* Initializes data in the print engine whenever the HDC has been renewed + */ + void initHDC(); + + /* Releases all the handles the printer currently holds, HDC, DEVMODE, + etc and resets the corresponding members to 0. */ + void release(); + + /* Queries the resolutions for the current printer, and returns them + in a list. */ + QList<QVariant> queryResolutions() const; + + /* Resets the DC with changes in devmode. If the printer is active + this function only sets the reinit variable to true so it + is handled in the next begin or newpage. */ + void doReinit(); + + /* Used by print/page setup dialogs */ + HGLOBAL *createDevNames(); + + void readDevmode(HGLOBAL globalDevmode); + void readDevnames(HGLOBAL globalDevnames); + + inline bool resetDC() { + hdc = ResetDC(hdc, devMode); + return hdc != 0; + } + + void strokePath(const QPainterPath &path, const QColor &color); + void fillPath(const QPainterPath &path, const QColor &color); + + void composeGdiPath(const QPainterPath &path); + void fillPath_dev(const QPainterPath &path, const QColor &color); + void strokePath_dev(const QPainterPath &path, const QColor &color, qreal width); + + void updateOrigin(); + + void initDevRects(); + void setPageMargins(int margin_left, int margin_top, int margin_right, int margin_bottom); + QRect getPageMargins() const; + void updateCustomPaperSize(); + + // Windows GDI printer references. + HANDLE hPrinter; + + HGLOBAL globalDevMode; + DEVMODE *devMode; + PRINTER_INFO_2 *pInfo; + HGLOBAL hMem; + + HDC hdc; + + QPrinter::PrinterMode mode; + + // Printer info + QString name; + QString program; + QString port; + + // Document info + QString docName; + QString fileName; + + QPrinter::PrinterState state; + int resolution; + + // This QRect is used to store the exact values + // entered into the PageSetup Dialog because those are + // entered in mm but are since converted to device coordinates. + // If they were to be converted back when displaying the dialog + // again, there would be inaccuracies so when the user entered 10 + // it may show up as 9.99 the next time the dialog is opened. + // We don't want that confusion. + QRect previousDialogMargins; + + bool pageMarginsSet; + QRect devPageRect; + QRect devPhysicalPageRect; + QRect devPaperRect; + qreal stretch_x; + qreal stretch_y; + int origin_x; + int origin_y; + + int dpi_x; + int dpi_y; + int dpi_display; + int num_copies; + + uint printToFile : 1; + uint fullPage : 1; + uint reinit : 1; + + uint complex_xform : 1; + uint has_pen : 1; + uint has_brush : 1; + uint has_custom_paper_size : 1; + + uint txop; + + QColor brush_color; + QPen pen; + QColor pen_color; + QSizeF paper_size; + + QTransform painterMatrix; + QTransform matrix; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_WIN_P_H diff --git a/src/gui/painting/qprinter.cpp b/src/gui/painting/qprinter.cpp new file mode 100644 index 0000000000..5111bf4e2d --- /dev/null +++ b/src/gui/painting/qprinter.cpp @@ -0,0 +1,2453 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprinter_p.h" +#include "qprinter.h" +#include "qprintengine.h" +#include "qprinterinfo.h" +#include "qlist.h" +#include <qpagesetupdialog.h> +#include <qapplication.h> +#include <qfileinfo.h> +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +#include "private/qcups_p.h" +#endif + +#ifndef QT_NO_PRINTER + +#if defined (Q_WS_WIN) +#include <private/qprintengine_win_p.h> +#elif defined (Q_WS_MAC) +#include <private/qprintengine_mac_p.h> +#elif defined (QTOPIA_PRINTENGINE) +#include <private/qprintengine_qws_p.h> +#endif +#include <private/qprintengine_ps_p.h> + +#if defined(Q_WS_X11) +#include <private/qt_x11_p.h> +#endif + +#ifndef QT_NO_PDF +#include "qprintengine_pdf_p.h" +#endif + +#include <qpicture.h> +#include <private/qpaintengine_preview_p.h> + +#if defined(QT3_SUPPORT) +# include "qprintdialog.h" +#endif // QT3_SUPPORT + +QT_BEGIN_NAMESPACE + +#define ABORT_IF_ACTIVE(location) \ + if (d->printEngine->printerState() == QPrinter::Active) { \ + qWarning("%s: Cannot be changed while printer is active", location); \ + return; \ + } + +// NB! This table needs to be in sync with QPrinter::PaperSize +static const float qt_paperSizes[][2] = { + {210, 297}, // A4 + {176, 250}, // B5 + {215.9f, 279.4f}, // Letter + {215.9f, 355.6f}, // Legal + {190.5f, 254}, // Executive + {841, 1189}, // A0 + {594, 841}, // A1 + {420, 594}, // A2 + {297, 420}, // A3 + {148, 210}, // A5 + {105, 148}, // A6 + {74, 105}, // A7 + {52, 74}, // A8 + {37, 52}, // A8 + {1000, 1414}, // B0 + {707, 1000}, // B1 + {31, 44}, // B10 + {500, 707}, // B2 + {353, 500}, // B3 + {250, 353}, // B4 + {125, 176}, // B6 + {88, 125}, // B7 + {62, 88}, // B8 + {33, 62}, // B9 + {163, 229}, // C5E + {105, 241}, // US Common + {110, 220}, // DLE + {210, 330}, // Folio + {431.8f, 279.4f}, // Ledger + {279.4f, 431.8f} // Tabloid +}; + +/// return the multiplier of converting from the unit value to postscript-points. +double qt_multiplierForUnit(QPrinter::Unit unit, int resolution) +{ + switch(unit) { + case QPrinter::Millimeter: + return 2.83464566929; + case QPrinter::Point: + return 1.0; + case QPrinter::Inch: + return 72.0; + case QPrinter::Pica: + return 12; + case QPrinter::Didot: + return 1.065826771; + case QPrinter::Cicero: + return 12.789921252; + case QPrinter::DevicePixel: + return 72.0/resolution; + } + return 1.0; +} + +// not static: it's needed in qpagesetupdialog_unix.cpp +QSizeF qt_printerPaperSize(QPrinter::Orientation orientation, + QPrinter::PaperSize paperSize, + QPrinter::Unit unit, + int resolution) +{ + int width_index = 0; + int height_index = 1; + if (orientation == QPrinter::Landscape) { + width_index = 1; + height_index = 0; + } + const qreal multiplier = qt_multiplierForUnit(unit, resolution); + return QSizeF((qt_paperSizes[paperSize][width_index] * 72 / 25.4) / multiplier, + (qt_paperSizes[paperSize][height_index] * 72 / 25.4) / multiplier); +} + +void QPrinterPrivate::createDefaultEngines() +{ + QPrinter::OutputFormat realOutputFormat = outputFormat; +#if !defined (QTOPIA_PRINTENGINE) +#if defined (Q_OS_UNIX) && ! defined (Q_WS_MAC) + if(outputFormat == QPrinter::NativeFormat) { + realOutputFormat = QPrinter::PostScriptFormat; + } +#endif +#endif + + switch (realOutputFormat) { + case QPrinter::NativeFormat: { +#if defined (Q_WS_WIN) + QWin32PrintEngine *winEngine = new QWin32PrintEngine(printerMode); + paintEngine = winEngine; + printEngine = winEngine; +#elif defined (Q_WS_MAC) + QMacPrintEngine *macEngine = new QMacPrintEngine(printerMode); + paintEngine = macEngine; + printEngine = macEngine; +#elif defined (QTOPIA_PRINTENGINE) + QtopiaPrintEngine *qwsEngine = new QtopiaPrintEngine(printerMode); + paintEngine = qwsEngine; + printEngine = qwsEngine; +#elif defined (Q_OS_UNIX) + Q_ASSERT(false); +#endif + } + break; + case QPrinter::PdfFormat: { + QPdfEngine *pdfEngine = new QPdfEngine(printerMode); + paintEngine = pdfEngine; + printEngine = pdfEngine; + } + break; + case QPrinter::PostScriptFormat: { + QPSPrintEngine *psEngine = new QPSPrintEngine(printerMode); + paintEngine = psEngine; + printEngine = psEngine; + } + break; + } + use_default_engine = true; + had_default_engines = true; +} + +#ifndef QT_NO_PRINTPREVIEWWIDGET +QList<const QPicture *> QPrinterPrivate::previewPages() const +{ + if (previewEngine) + return previewEngine->pages(); + return QList<const QPicture *>(); +} + +void QPrinterPrivate::setPreviewMode(bool enable) +{ + Q_Q(QPrinter); + if (enable) { + if (!previewEngine) + previewEngine = new QPreviewPaintEngine; + had_default_engines = use_default_engine; + use_default_engine = false; + realPrintEngine = printEngine; + realPaintEngine = paintEngine; + q->setEngines(previewEngine, previewEngine); + previewEngine->setProxyEngines(realPrintEngine, realPaintEngine); + } else { + q->setEngines(realPrintEngine, realPaintEngine); + use_default_engine = had_default_engines; + } +} +#endif // QT_NO_PRINTPREVIEWWIDGET + +void QPrinterPrivate::addToManualSetList(QPrintEngine::PrintEnginePropertyKey key) +{ + for (int c = 0; c < manualSetList.size(); ++c) { + if (manualSetList[c] == key) return; + } + manualSetList.append(key); +} + + +/*! + \class QPrinter + \reentrant + + \brief The QPrinter class is a paint device that paints on a printer. + + \ingroup printing + + + This device represents a series of pages of printed output, and is + used in almost exactly the same way as other paint devices such as + QWidget and QPixmap. + A set of additional functions are provided to manage device-specific + features, such as orientation and resolution, and to step through + the pages in a document as it is generated. + + When printing directly to a printer on Windows or Mac OS X, QPrinter uses + the built-in printer drivers. On X11, QPrinter uses the + \l{Common Unix Printing System (CUPS)} or the standard Unix \l lpr utility + to send PostScript or PDF output to the printer. As an alternative, + the printProgram() function can be used to specify the command or utility + to use instead of the system default. + + Note that setting parameters like paper size and resolution on an + invalid printer is undefined. You can use QPrinter::isValid() to + verify this before changing any parameters. + + QPrinter supports a number of parameters, most of which can be + changed by the end user through a \l{QPrintDialog}{print dialog}. In + general, QPrinter passes these functions onto the underlying QPrintEngine. + + The most important parameters are: + \list + \i setOrientation() tells QPrinter which page orientation to use. + \i setPaperSize() tells QPrinter what paper size to expect from the + printer. + \i setResolution() tells QPrinter what resolution you wish the + printer to provide, in dots per inch (DPI). + \i setFullPage() tells QPrinter whether you want to deal with the + full page or just with the part the printer can draw on. + \i setCopyCount() tells QPrinter how many copies of the document + it should print. + \endlist + + Many of these functions can only be called before the actual printing + begins (i.e., before QPainter::begin() is called). This usually makes + sense because, for example, it's not possible to change the number of + copies when you are halfway through printing. There are also some + settings that the user sets (through the printer dialog) and that + applications are expected to obey. See QAbstractPrintDialog's + documentation for more details. + + When QPainter::begin() is called, the QPrinter it operates on is prepared for + a new page, enabling the QPainter to be used immediately to paint the first + page in a document. Once the first page has been painted, newPage() can be + called to request a new blank page to paint on, or QPainter::end() can be + called to finish printing. The second page and all following pages are + prepared using a call to newPage() before they are painted. + + The first page in a document does not need to be preceded by a call to + newPage(). You only need to calling newPage() after QPainter::begin() if you + need to insert a blank page at the beginning of a printed document. + Similarly, calling newPage() after the last page in a document is painted will + result in a trailing blank page appended to the end of the printed document. + + If you want to abort the print job, abort() will try its best to + stop printing. It may cancel the entire job or just part of it. + + Since QPrinter can print to any QPrintEngine subclass, it is possible to + extend printing support to cover new types of printing subsystem by + subclassing QPrintEngine and reimplementing its interface. + + \sa QPrintDialog, {Printing with Qt} +*/ + +/*! + \enum QPrinter::PrinterState + + \value Idle + \value Active + \value Aborted + \value Error +*/ + +/*! + \enum QPrinter::PrinterMode + + This enum describes the mode the printer should work in. It + basically presets a certain resolution and working mode. + + \value ScreenResolution Sets the resolution of the print device to + the screen resolution. This has the big advantage that the results + obtained when painting on the printer will match more or less + exactly the visible output on the screen. It is the easiest to + use, as font metrics on the screen and on the printer are the + same. This is the default value. ScreenResolution will produce a + lower quality output than HighResolution and should only be used + for drafts. + + \value PrinterResolution This value is deprecated. Is is + equivalent to ScreenResolution on Unix and HighResolution on + Windows and Mac. Due do the difference between ScreenResolution + and HighResolution, use of this value may lead to non-portable + printer code. + + \value HighResolution On Windows, sets the printer resolution to that + defined for the printer in use. For PostScript printing, sets the + resolution of the PostScript driver to 1200 dpi. + + \note When rendering text on a QPrinter device, it is important + to realize that the size of text, when specified in points, is + independent of the resolution specified for the device itself. + Therefore, it may be useful to specify the font size in pixels + when combining text with graphics to ensure that their relative + sizes are what you expect. +*/ + +/*! + \enum QPrinter::Orientation + + This enum type (not to be confused with \c Orientation) is used + to specify each page's orientation. + + \value Portrait the page's height is greater than its width. + + \value Landscape the page's width is greater than its height. + + This type interacts with \l QPrinter::PaperSize and + QPrinter::setFullPage() to determine the final size of the page + available to the application. +*/ + + +/*! + \enum QPrinter::PrintRange + + Used to specify the print range selection option. + + \value AllPages All pages should be printed. + \value Selection Only the selection should be printed. + \value PageRange The specified page range should be printed. + \value CurrentPage Only the current page should be printed. + + \sa QAbstractPrintDialog::PrintRange +*/ + +/*! + \enum QPrinter::PrinterOption + \compat + + Use QAbstractPrintDialog::PrintDialogOption instead. + + \value PrintToFile + \value PrintSelection + \value PrintPageRange +*/ + +/*! + \enum QPrinter::PageSize + + \obsolete + Use QPrinter::PaperSize instead. + + \value A0 841 x 1189 mm + \value A1 594 x 841 mm + \value A2 420 x 594 mm + \value A3 297 x 420 mm + \value A4 210 x 297 mm, 8.26 x 11.69 inches + \value A5 148 x 210 mm + \value A6 105 x 148 mm + \value A7 74 x 105 mm + \value A8 52 x 74 mm + \value A9 37 x 52 mm + \value B0 1030 x 1456 mm + \value B1 728 x 1030 mm + \value B10 32 x 45 mm + \value B2 515 x 728 mm + \value B3 364 x 515 mm + \value B4 257 x 364 mm + \value B5 182 x 257 mm, 7.17 x 10.13 inches + \value B6 128 x 182 mm + \value B7 91 x 128 mm + \value B8 64 x 91 mm + \value B9 45 x 64 mm + \value C5E 163 x 229 mm + \value Comm10E 105 x 241 mm, U.S. Common 10 Envelope + \value DLE 110 x 220 mm + \value Executive 7.5 x 10 inches, 191 x 254 mm + \value Folio 210 x 330 mm + \value Ledger 432 x 279 mm + \value Legal 8.5 x 14 inches, 216 x 356 mm + \value Letter 8.5 x 11 inches, 216 x 279 mm + \value Tabloid 279 x 432 mm + \value Custom Unknown, or a user defined size. + + \omitvalue NPageSize + */ + +/*! + \enum QPrinter::PaperSize + \since 4.4 + + This enum type specifies what paper size QPrinter should use. + QPrinter does not check that the paper size is available; it just + uses this information, together with QPrinter::Orientation and + QPrinter::setFullPage(), to determine the printable area. + + The defined sizes (with setFullPage(true)) are: + + \value A0 841 x 1189 mm + \value A1 594 x 841 mm + \value A2 420 x 594 mm + \value A3 297 x 420 mm + \value A4 210 x 297 mm, 8.26 x 11.69 inches + \value A5 148 x 210 mm + \value A6 105 x 148 mm + \value A7 74 x 105 mm + \value A8 52 x 74 mm + \value A9 37 x 52 mm + \value B0 1000 x 1414 mm + \value B1 707 x 1000 mm + \value B2 500 x 707 mm + \value B3 353 x 500 mm + \value B4 250 x 353 mm + \value B5 176 x 250 mm, 6.93 x 9.84 inches + \value B6 125 x 176 mm + \value B7 88 x 125 mm + \value B8 62 x 88 mm + \value B9 33 x 62 mm + \value B10 31 x 44 mm + \value C5E 163 x 229 mm + \value Comm10E 105 x 241 mm, U.S. Common 10 Envelope + \value DLE 110 x 220 mm + \value Executive 7.5 x 10 inches, 190.5 x 254 mm + \value Folio 210 x 330 mm + \value Ledger 431.8 x 279.4 mm + \value Legal 8.5 x 14 inches, 215.9 x 355.6 mm + \value Letter 8.5 x 11 inches, 215.9 x 279.4 mm + \value Tabloid 279.4 x 431.8 mm + \value Custom Unknown, or a user defined size. + + With setFullPage(false) (the default), the metrics will be a bit + smaller; how much depends on the printer in use. + + \omitvalue NPageSize + \omitvalue NPaperSize +*/ + + +/*! + \enum QPrinter::PageOrder + + This enum type is used by QPrinter to tell the application program + how to print. + + \value FirstPageFirst the lowest-numbered page should be printed + first. + + \value LastPageFirst the highest-numbered page should be printed + first. +*/ + +/*! + \enum QPrinter::ColorMode + + This enum type is used to indicate whether QPrinter should print + in color or not. + + \value Color print in color if available, otherwise in grayscale. + + \value GrayScale print in grayscale, even on color printers. +*/ + +/*! + \enum QPrinter::PaperSource + + This enum type specifies what paper source QPrinter is to use. + QPrinter does not check that the paper source is available; it + just uses this information to try and set the paper source. + Whether it will set the paper source depends on whether the + printer has that particular source. + + \warning This is currently only implemented for Windows. + + \value Auto + \value Cassette + \value Envelope + \value EnvelopeManual + \value FormSource + \value LargeCapacity + \value LargeFormat + \value Lower + \value MaxPageSource + \value Middle + \value Manual + \value OnlyOne + \value Tractor + \value SmallFormat +*/ + +/*! + \enum QPrinter::Unit + \since 4.4 + + This enum type is used to specify the measurement unit for page and + paper sizes. + + \value Millimeter + \value Point + \value Inch + \value Pica + \value Didot + \value Cicero + \value DevicePixel + + Note the difference between Point and DevicePixel. The Point unit is + defined to be 1/72th of an inch, while the DevicePixel unit is + resolution dependant and is based on the actual pixels, or dots, on + the printer. +*/ + + +/* + \enum QPrinter::PrintRange + + This enum is used to specify which print range the application + should use to print. + + \value AllPages All the pages should be printed. + \value Selection Only the selection should be printed. + \value PageRange Print according to the from page and to page options. + \value CurrentPage Only the current page should be printed. + + \sa setPrintRange(), printRange() +*/ + +/* + \enum QPrinter::PrinterOption + + This enum describes various printer options that appear in the + printer setup dialog. It is used to enable and disable these + options in the setup dialog. + + \value PrintToFile Describes if print to file should be enabled. + \value PrintSelection Describes if printing selections should be enabled. + \value PrintPageRange Describes if printing page ranges (from, to) should + be enabled + \value PrintCurrentPage if Print Current Page option should be enabled + + \sa setOptionEnabled(), isOptionEnabled() +*/ + +/*! + Creates a new printer object with the given \a mode. +*/ +QPrinter::QPrinter(PrinterMode mode) + : QPaintDevice(), + d_ptr(new QPrinterPrivate(this)) +{ + init(mode); + QPrinterInfo defPrn(QPrinterInfo::defaultPrinter()); + if (!defPrn.isNull()) { + setPrinterName(defPrn.printerName()); + } else if (QPrinterInfo::availablePrinters().isEmpty() + && d_ptr->paintEngine->type() != QPaintEngine::Windows + && d_ptr->paintEngine->type() != QPaintEngine::MacPrinter) { + setOutputFormat(QPrinter::PdfFormat); + } +} + +/*! + \since 4.4 + + Creates a new printer object with the given \a printer and \a mode. +*/ +QPrinter::QPrinter(const QPrinterInfo& printer, PrinterMode mode) + : QPaintDevice(), + d_ptr(new QPrinterPrivate(this)) +{ + init(mode); + setPrinterName(printer.printerName()); +} + +void QPrinter::init(PrinterMode mode) +{ +#if !defined(Q_WS_X11) + if (!qApp) { +#else + if (!qApp || !X11) { +#endif + qFatal("QPrinter: Must construct a QApplication before a QPaintDevice"); + return; + } + Q_D(QPrinter); + + d->printerMode = mode; + d->outputFormat = QPrinter::NativeFormat; + d->createDefaultEngines(); + +#ifndef QT_NO_PRINTPREVIEWWIDGET + d->previewEngine = 0; +#endif + d->realPrintEngine = 0; + d->realPaintEngine = 0; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::cupsVersion() >= 10200 && QCUPSSupport().currentPPD()) { + setOutputFormat(QPrinter::PdfFormat); + d->outputFormat = QPrinter::NativeFormat; + } +#endif +} + +/*! + This function is used by subclasses of QPrinter to specify custom + print and paint engines (\a printEngine and \a paintEngine, + respectively). + + QPrinter does not take ownership of the engines, so you need to + manage these engine instances yourself. + + Note that changing the engines will reset the printer state and + all its properties. + + \sa printEngine() paintEngine() setOutputFormat() + + \since 4.1 +*/ +void QPrinter::setEngines(QPrintEngine *printEngine, QPaintEngine *paintEngine) +{ + Q_D(QPrinter); + + if (d->use_default_engine) + delete d->printEngine; + + d->printEngine = printEngine; + d->paintEngine = paintEngine; + d->use_default_engine = false; +} + +/*! + Destroys the printer object and frees any allocated resources. If + the printer is destroyed while a print job is in progress this may + or may not affect the print job. +*/ +QPrinter::~QPrinter() +{ + Q_D(QPrinter); + if (d->use_default_engine) + delete d->printEngine; +#ifndef QT_NO_PRINTPREVIEWWIDGET + delete d->previewEngine; +#endif +} + +/*! + \enum QPrinter::OutputFormat + + The OutputFormat enum is used to describe the format QPrinter should + use for printing. + + \value NativeFormat QPrinter will print output using a method defined + by the platform it is running on. This mode is the default when printing + directly to a printer. + + \value PdfFormat QPrinter will generate its output as a searchable PDF file. + This mode is the default when printing to a file. + + \value PostScriptFormat QPrinter will generate its output as in the PostScript format. + (This feature was introduced in Qt 4.2.) + + \sa outputFormat(), setOutputFormat(), setOutputFileName() +*/ + +/*! + \since 4.1 + + Sets the output format for this printer to \a format. +*/ +void QPrinter::setOutputFormat(OutputFormat format) +{ + +#ifndef QT_NO_PDF + Q_D(QPrinter); + if (d->validPrinter && d->outputFormat == format) + return; + d->outputFormat = format; + + QPrintEngine *oldPrintEngine = d->printEngine; + const bool def_engine = d->use_default_engine; + d->printEngine = 0; + + d->createDefaultEngines(); + + if (oldPrintEngine) { + for (int i = 0; i < d->manualSetList.size(); ++i) { + QPrintEngine::PrintEnginePropertyKey key = d->manualSetList[i]; + QVariant prop; + // PPK_NumberOfCopies need special treatmeant since it in most cases + // will return 1, disregarding the actual value that was set + if (key == QPrintEngine::PPK_NumberOfCopies) + prop = QVariant(copyCount()); + else + prop = oldPrintEngine->property(key); + if (prop.isValid()) + d->printEngine->setProperty(key, prop); + } + } + + if (def_engine) + delete oldPrintEngine; + + if (d->outputFormat == QPrinter::PdfFormat || d->outputFormat == QPrinter::PostScriptFormat) + d->validPrinter = true; +#else + Q_UNUSED(format); +#endif +} + +/*! + \since 4.1 + + Returns the output format for this printer. +*/ +QPrinter::OutputFormat QPrinter::outputFormat() const +{ + Q_D(const QPrinter); + return d->outputFormat; +} + + + +/*! \internal +*/ +int QPrinter::devType() const +{ + return QInternal::Printer; +} + +/*! + Returns the printer name. This value is initially set to the name + of the default printer. + + \sa setPrinterName() +*/ +QString QPrinter::printerName() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_PrinterName).toString(); +} + +/*! + Sets the printer name to \a name. + + \sa printerName(), isValid() +*/ +void QPrinter::setPrinterName(const QString &name) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setPrinterName"); + +#if defined(Q_OS_UNIX) && !defined(QT_NO_CUPS) + if(d->use_default_engine + && d->outputFormat == QPrinter::NativeFormat) { + if (QCUPSSupport::cupsVersion() >= 10200 + && QCUPSSupport::printerHasPPD(name.toLocal8Bit().constData())) + setOutputFormat(QPrinter::PdfFormat); + else + setOutputFormat(QPrinter::PostScriptFormat); + d->outputFormat = QPrinter::NativeFormat; + } +#endif + + QList<QPrinterInfo> prnList = QPrinterInfo::availablePrinters(); + if (name.isEmpty()) { + d->validPrinter = d->outputFormat == QPrinter::PdfFormat || d->outputFormat == QPrinter::PostScriptFormat; + } else { + d->validPrinter = false; + for (int i = 0; i < prnList.size(); ++i) { + if (prnList[i].printerName() == name) { + d->validPrinter = true; + break; + } + } + } + + d->printEngine->setProperty(QPrintEngine::PPK_PrinterName, name); + d->addToManualSetList(QPrintEngine::PPK_PrinterName); +} + + +/*! + \since 4.4 + + Returns true if the printer currently selected is a valid printer + in the system, or a pure PDF/PostScript printer; otherwise returns false. + + To detect other failures check the output of QPainter::begin() or QPrinter::newPage(). + + \snippet doc/src/snippets/printing-qprinter/errors.cpp 0 + + \sa setPrinterName() +*/ +bool QPrinter::isValid() const +{ + Q_D(const QPrinter); +#if defined(Q_WS_X11) + if (!qApp || !X11) { + return false; + } +#endif + return d->validPrinter; +} + + +/*! + \fn bool QPrinter::outputToFile() const + + Returns true if the output should be written to a file, or false + if the output should be sent directly to the printer. The default + setting is false. + + \sa setOutputToFile(), setOutputFileName() +*/ + + +/*! + \fn void QPrinter::setOutputToFile(bool enable) + + Specifies whether the output should be written to a file or sent + directly to the printer. + + Will output to a file if \a enable is true, or will output + directly to the printer if \a enable is false. + + \sa outputToFile(), setOutputFileName() +*/ + + +/*! + \fn QString QPrinter::outputFileName() const + + Returns the name of the output file. By default, this is an empty string + (indicating that the printer shouldn't print to file). + + \sa QPrintEngine::PrintEnginePropertyKey + +*/ + +QString QPrinter::outputFileName() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_OutputFileName).toString(); +} + +/*! + Sets the name of the output file to \a fileName. + + Setting a null or empty name (0 or "") disables printing to a file. + Setting a non-empty name enables printing to a file. + + This can change the value of outputFormat(). If the file name has the + suffix ".ps" then PostScript is automatically selected as output format. + If the file name has the ".pdf" suffix PDF is generated. If the file name + has a suffix other than ".ps" and ".pdf", the output format used is the + one set with setOutputFormat(). + + QPrinter uses Qt's cross-platform PostScript or PDF print engines + respectively. If you can produce this format natively, for example + Mac OS X can generate PDF's from its print engine, set the output format + back to NativeFormat. + + \sa outputFileName() setOutputToFile() setOutputFormat() +*/ + +void QPrinter::setOutputFileName(const QString &fileName) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setOutputFileName"); + + QFileInfo fi(fileName); + if (!fi.suffix().compare(QLatin1String("ps"), Qt::CaseInsensitive)) + setOutputFormat(QPrinter::PostScriptFormat); + else if (!fi.suffix().compare(QLatin1String("pdf"), Qt::CaseInsensitive)) + setOutputFormat(QPrinter::PdfFormat); + else if (fileName.isEmpty()) + setOutputFormat(QPrinter::NativeFormat); + + d->printEngine->setProperty(QPrintEngine::PPK_OutputFileName, fileName); + d->addToManualSetList(QPrintEngine::PPK_OutputFileName); +} + + +/*! + Returns the name of the program that sends the print output to the + printer. + + The default is to return an empty string; meaning that QPrinter will try to + be smart in a system-dependent way. On X11 only, you can set it to something + different to use a specific print program. On the other platforms, this + returns an empty string. + + \sa setPrintProgram(), setPrinterSelectionOption() +*/ +QString QPrinter::printProgram() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_PrinterProgram).toString(); +} + + +/*! + Sets the name of the program that should do the print job to \a + printProg. + + On X11, this function sets the program to call with the PostScript + output. On other platforms, it has no effect. + + \sa printProgram() +*/ +void QPrinter::setPrintProgram(const QString &printProg) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setPrintProgram"); + d->printEngine->setProperty(QPrintEngine::PPK_PrinterProgram, printProg); + d->addToManualSetList(QPrintEngine::PPK_PrinterProgram); +} + + +/*! + Returns the document name. + + \sa setDocName(), QPrintEngine::PrintEnginePropertyKey +*/ +QString QPrinter::docName() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_DocumentName).toString(); +} + + +/*! + Sets the document name to \a name. + + On X11, the document name is for example used as the default + output filename in QPrintDialog. Note that the document name does + not affect the file name if the printer is printing to a file. + Use the setOutputFile() function for this. + + \sa docName(), QPrintEngine::PrintEnginePropertyKey +*/ +void QPrinter::setDocName(const QString &name) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setDocName"); + d->printEngine->setProperty(QPrintEngine::PPK_DocumentName, name); + d->addToManualSetList(QPrintEngine::PPK_DocumentName); +} + + +/*! + Returns the name of the application that created the document. + + \sa setCreator() +*/ +QString QPrinter::creator() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_Creator).toString(); +} + + +/*! + Sets the name of the application that created the document to \a + creator. + + This function is only applicable to the X11 version of Qt. If no + creator name is specified, the creator will be set to "Qt" + followed by some version number. + + \sa creator() +*/ +void QPrinter::setCreator(const QString &creator) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setCreator"); + d->printEngine->setProperty(QPrintEngine::PPK_Creator, creator); + d->addToManualSetList(QPrintEngine::PPK_Creator); +} + + +/*! + Returns the orientation setting. This is driver-dependent, but is usually + QPrinter::Portrait. + + \sa setOrientation() +*/ +QPrinter::Orientation QPrinter::orientation() const +{ + Q_D(const QPrinter); + return QPrinter::Orientation(d->printEngine->property(QPrintEngine::PPK_Orientation).toInt()); +} + + +/*! + Sets the print orientation to \a orientation. + + The orientation can be either QPrinter::Portrait or + QPrinter::Landscape. + + The printer driver reads this setting and prints using the + specified orientation. + + On Windows, this option can be changed while printing and will + take effect from the next call to newPage(). + + On Mac OS X, changing the orientation during a print job has no effect. + + \sa orientation() +*/ + +void QPrinter::setOrientation(Orientation orientation) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_Orientation, orientation); + d->addToManualSetList(QPrintEngine::PPK_Orientation); +} + + +/*! + \since 4.4 + Returns the printer paper size. The default value is driver-dependent. + + \sa setPaperSize() pageRect() paperRect() +*/ + +QPrinter::PaperSize QPrinter::paperSize() const +{ + Q_D(const QPrinter); + return QPrinter::PaperSize(d->printEngine->property(QPrintEngine::PPK_PaperSize).toInt()); +} + +/*! + \since 4.4 + + Sets the printer paper size to \a newPaperSize if that size is + supported. The result is undefined if \a newPaperSize is not + supported. + + The default paper size is driver-dependent. + + This function is useful mostly for setting a default value that + the user can override in the print dialog. + + \sa paperSize() PaperSize setFullPage() setResolution() pageRect() paperRect() +*/ +void QPrinter::setPaperSize(PaperSize newPaperSize) +{ + Q_D(QPrinter); + if (d->paintEngine->type() != QPaintEngine::Pdf) + ABORT_IF_ACTIVE("QPrinter::setPaperSize"); + if (newPaperSize < 0 || newPaperSize >= NPaperSize) { + qWarning("QPrinter::setPaperSize: Illegal paper size %d", newPaperSize); + return; + } + d->printEngine->setProperty(QPrintEngine::PPK_PaperSize, newPaperSize); + d->addToManualSetList(QPrintEngine::PPK_PaperSize); + d->hasUserSetPageSize = true; +} + +/*! + \obsolete + + Returns the printer page size. The default value is driver-dependent. + + Use paperSize() instead. +*/ +QPrinter::PageSize QPrinter::pageSize() const +{ + return paperSize(); +} + + +/*! + \obsolete + + Sets the printer page size based on \a newPageSize. + + Use setPaperSize() instead. +*/ + +void QPrinter::setPageSize(PageSize newPageSize) +{ + setPaperSize(newPageSize); +} + +/*! + \since 4.4 + + Sets the paper size based on \a paperSize in \a unit. + + \sa paperSize() +*/ + +void QPrinter::setPaperSize(const QSizeF &paperSize, QPrinter::Unit unit) +{ + Q_D(QPrinter); + if (d->paintEngine->type() != QPaintEngine::Pdf) + ABORT_IF_ACTIVE("QPrinter::setPaperSize"); + const qreal multiplier = qt_multiplierForUnit(unit, resolution()); + QSizeF size(paperSize.width() * multiplier, paperSize.height() * multiplier); + d->printEngine->setProperty(QPrintEngine::PPK_CustomPaperSize, size); + d->addToManualSetList(QPrintEngine::PPK_CustomPaperSize); + d->hasUserSetPageSize = true; +} + +/*! + \since 4.4 + + Returns the paper size in \a unit. + + \sa setPaperSize() +*/ + +QSizeF QPrinter::paperSize(Unit unit) const +{ + Q_D(const QPrinter); + int res = resolution(); + const qreal multiplier = qt_multiplierForUnit(unit, res); + PaperSize paperType = paperSize(); + if (paperType == Custom) { + QSizeF size = d->printEngine->property(QPrintEngine::PPK_CustomPaperSize).toSizeF(); + return QSizeF(size.width() / multiplier, size.height() / multiplier); + } + else { + return qt_printerPaperSize(orientation(), paperType, unit, res); + } +} + +/*! + Sets the page order to \a pageOrder. + + The page order can be QPrinter::FirstPageFirst or + QPrinter::LastPageFirst. The application is responsible for + reading the page order and printing accordingly. + + This function is mostly useful for setting a default value that + the user can override in the print dialog. + + This function is only supported under X11. +*/ + +void QPrinter::setPageOrder(PageOrder pageOrder) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setPageOrder"); + d->printEngine->setProperty(QPrintEngine::PPK_PageOrder, pageOrder); + d->addToManualSetList(QPrintEngine::PPK_PageOrder); +} + + +/*! + Returns the current page order. + + The default page order is \c FirstPageFirst. +*/ + +QPrinter::PageOrder QPrinter::pageOrder() const +{ + Q_D(const QPrinter); + return QPrinter::PageOrder(d->printEngine->property(QPrintEngine::PPK_PageOrder).toInt()); +} + + +/*! + Sets the printer's color mode to \a newColorMode, which can be + either \c Color or \c GrayScale. + + \sa colorMode() +*/ + +void QPrinter::setColorMode(ColorMode newColorMode) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setColorMode"); + d->printEngine->setProperty(QPrintEngine::PPK_ColorMode, newColorMode); + d->addToManualSetList(QPrintEngine::PPK_ColorMode); +} + + +/*! + Returns the current color mode. + + \sa setColorMode() +*/ +QPrinter::ColorMode QPrinter::colorMode() const +{ + Q_D(const QPrinter); + return QPrinter::ColorMode(d->printEngine->property(QPrintEngine::PPK_ColorMode).toInt()); +} + + +/*! + \obsolete + Returns the number of copies to be printed. The default value is 1. + + On Windows, Mac OS X and X11 systems that support CUPS, this will always + return 1 as these operating systems can internally handle the number + of copies. + + On X11, this value will return the number of times the application is + required to print in order to match the number specified in the printer setup + dialog. This has been done since some printer drivers are not capable of + buffering up the copies and in those cases the application must make an + explicit call to the print code for each copy. + + Use copyCount() in conjunction with supportsMultipleCopies() instead. + + \sa setNumCopies(), actualNumCopies() +*/ + +int QPrinter::numCopies() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_NumberOfCopies).toInt(); +} + + +/*! + \obsolete + \since 4.6 + + Returns the number of copies that will be printed. The default + value is 1. + + This function always returns the actual value specified in the print + dialog or using setNumCopies(). + + Use copyCount() instead. + + \sa setNumCopies(), numCopies() +*/ +int QPrinter::actualNumCopies() const +{ + return copyCount(); +} + + + +/*! + \obsolete + Sets the number of copies to be printed to \a numCopies. + + The printer driver reads this setting and prints the specified + number of copies. + + Use setCopyCount() instead. + + \sa numCopies() +*/ + +void QPrinter::setNumCopies(int numCopies) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setNumCopies"); + d->printEngine->setProperty(QPrintEngine::PPK_NumberOfCopies, numCopies); + d->addToManualSetList(QPrintEngine::PPK_NumberOfCopies); +} + +/*! + \since 4.7 + + Sets the number of copies to be printed to \a count. + + The printer driver reads this setting and prints the specified number of + copies. + + \sa copyCount(), supportsMultipleCopies() +*/ + +void QPrinter::setCopyCount(int count) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setCopyCount;"); + d->printEngine->setProperty(QPrintEngine::PPK_CopyCount, count); + d->addToManualSetList(QPrintEngine::PPK_CopyCount); +} + +/*! + \since 4.7 + + Returns the number of copies that will be printed. The default value is 1. + + \sa setCopyCount(), supportsMultipleCopies() +*/ + +int QPrinter::copyCount() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_CopyCount).toInt(); +} + +/*! + \since 4.7 + + Returns true if the printer supports printing multiple copies of the same + document in one job; otherwise false is returned. + + On most systems this function will return true. However, on X11 systems + that do not support CUPS, this function will return false. That means the + application has to handle the number of copies by printing the same + document the required number of times. + + \sa setCopyCount(), copyCount() +*/ + +bool QPrinter::supportsMultipleCopies() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_SupportsMultipleCopies).toBool(); +} + +/*! + \since 4.1 + + Returns true if collation is turned on when multiple copies is selected. + Returns false if it is turned off when multiple copies is selected. + When collating is turned off the printing of each individual page will be repeated + the numCopies() amount before the next page is started. With collating turned on + all pages are printed before the next copy of those pages is started. + + \sa setCollateCopies() +*/ +bool QPrinter::collateCopies() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_CollateCopies).toBool(); +} + + +/*! + \since 4.1 + + Sets the default value for collation checkbox when the print + dialog appears. If \a collate is true, it will enable + setCollateCopiesEnabled(). The default value is false. This value + will be changed by what the user presses in the print dialog. + + \sa collateCopies() +*/ +void QPrinter::setCollateCopies(bool collate) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setCollateCopies"); + d->printEngine->setProperty(QPrintEngine::PPK_CollateCopies, collate); + d->addToManualSetList(QPrintEngine::PPK_CollateCopies); +} + + + +/*! + If \a fp is true, enables support for painting over the entire page; + otherwise restricts painting to the printable area reported by the + device. + + By default, full page printing is disabled. In this case, the origin + of the QPrinter's coordinate system coincides with the top-left + corner of the printable area. + + If full page printing is enabled, the origin of the QPrinter's + coordinate system coincides with the top-left corner of the paper + itself. In this case, the + \l{QPaintDevice::PaintDeviceMetric}{device metrics} will report + the exact same dimensions as indicated by \l{PaperSize}. It may not + be possible to print on the entire physical page because of the + printer's margins, so the application must account for the margins + itself. + + \sa fullPage(), setPaperSize(), width(), height(), {Printing with Qt} +*/ + +void QPrinter::setFullPage(bool fp) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_FullPage, fp); + d->addToManualSetList(QPrintEngine::PPK_FullPage); +} + + +/*! + Returns true if the origin of the printer's coordinate system is + at the corner of the page and false if it is at the edge of the + printable area. + + See setFullPage() for details and caveats. + + \sa setFullPage() PaperSize +*/ + +bool QPrinter::fullPage() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_FullPage).toBool(); +} + + +/*! + Requests that the printer prints at \a dpi or as near to \a dpi as + possible. + + This setting affects the coordinate system as returned by, for + example QPainter::viewport(). + + This function must be called before QPainter::begin() to have an effect on + all platforms. + + \sa resolution() setPaperSize() +*/ + +void QPrinter::setResolution(int dpi) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setResolution"); + d->printEngine->setProperty(QPrintEngine::PPK_Resolution, dpi); + d->addToManualSetList(QPrintEngine::PPK_Resolution); +} + + +/*! + Returns the current assumed resolution of the printer, as set by + setResolution() or by the printer driver. + + \sa setResolution() +*/ + +int QPrinter::resolution() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_Resolution).toInt(); +} + +/*! + Sets the paper source setting to \a source. + + Windows only: This option can be changed while printing and will + take effect from the next call to newPage() + + \sa paperSource() +*/ + +void QPrinter::setPaperSource(PaperSource source) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_PaperSource, source); + d->addToManualSetList(QPrintEngine::PPK_PaperSource); +} + +/*! + Returns the printer's paper source. This is \c Manual or a printer + tray or paper cassette. +*/ +QPrinter::PaperSource QPrinter::paperSource() const +{ + Q_D(const QPrinter); + return QPrinter::PaperSource(d->printEngine->property(QPrintEngine::PPK_PaperSource).toInt()); +} + + +/*! + \since 4.1 + + Enabled or disables font embedding depending on \a enable. + + Currently this option is only supported on X11. + + \sa fontEmbeddingEnabled() +*/ +void QPrinter::setFontEmbeddingEnabled(bool enable) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_FontEmbedding, enable); + d->addToManualSetList(QPrintEngine::PPK_FontEmbedding); +} + +/*! + \since 4.1 + + Returns true if font embedding is enabled. + + Currently this option is only supported on X11. + + \sa setFontEmbeddingEnabled() +*/ +bool QPrinter::fontEmbeddingEnabled() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_FontEmbedding).toBool(); +} + +/*! + \enum QPrinter::DuplexMode + \since 4.4 + + This enum is used to indicate whether printing will occur on one or both sides + of each sheet of paper (simplex or duplex printing). + + \value DuplexNone Single sided (simplex) printing only. + \value DuplexAuto The printer's default setting is used to determine whether + duplex printing is used. + \value DuplexLongSide Both sides of each sheet of paper are used for printing. + The paper is turned over its longest edge before the second + side is printed + \value DuplexShortSide Both sides of each sheet of paper are used for printing. + The paper is turned over its shortest edge before the second + side is printed +*/ + +/*! + \since 4.2 + + Enables double sided printing if \a doubleSided is true; otherwise disables it. + + Currently this option is only supported on X11. +*/ +void QPrinter::setDoubleSidedPrinting(bool doubleSided) +{ + setDuplex(doubleSided ? DuplexAuto : DuplexNone); +} + + +/*! + \since 4.2 + + Returns true if double side printing is enabled. + + Currently this option is only supported on X11. +*/ +bool QPrinter::doubleSidedPrinting() const +{ + return duplex() != DuplexNone; +} + +/*! + \since 4.4 + + Enables double sided printing based on the \a duplex mode. + + Currently this option is only supported on X11. +*/ +void QPrinter::setDuplex(DuplexMode duplex) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_Duplex, duplex); + d->addToManualSetList(QPrintEngine::PPK_Duplex); +} + +/*! + \since 4.4 + + Returns the current duplex mode. + + Currently this option is only supported on X11. +*/ +QPrinter::DuplexMode QPrinter::duplex() const +{ + Q_D(const QPrinter); + return static_cast <DuplexMode> (d->printEngine->property(QPrintEngine::PPK_Duplex).toInt()); +} + +/*! + \since 4.4 + + Returns the page's rectangle in \a unit; this is usually smaller + than the paperRect() since the page normally has margins between + its borders and the paper. + + \sa paperSize() +*/ +QRectF QPrinter::pageRect(Unit unit) const +{ + Q_D(const QPrinter); + int res = resolution(); + const qreal multiplier = qt_multiplierForUnit(unit, res); + // the page rect is in device pixels + QRect devRect(d->printEngine->property(QPrintEngine::PPK_PageRect).toRect()); + if (unit == DevicePixel) + return devRect; + QRectF diRect(devRect.x()*72.0/res, + devRect.y()*72.0/res, + devRect.width()*72.0/res, + devRect.height()*72.0/res); + return QRectF(diRect.x()/multiplier, diRect.y()/multiplier, + diRect.width()/multiplier, diRect.height()/multiplier); +} + + +/*! + \since 4.4 + + Returns the paper's rectangle in \a unit; this is usually larger + than the pageRect(). + + \sa pageRect() +*/ +QRectF QPrinter::paperRect(Unit unit) const +{ + Q_D(const QPrinter); + int res = resolution(); + const qreal multiplier = qt_multiplierForUnit(unit, resolution()); + // the page rect is in device pixels + QRect devRect(d->printEngine->property(QPrintEngine::PPK_PaperRect).toRect()); + if (unit == DevicePixel) + return devRect; + QRectF diRect(devRect.x()*72.0/res, + devRect.y()*72.0/res, + devRect.width()*72.0/res, + devRect.height()*72.0/res); + return QRectF(diRect.x()/multiplier, diRect.y()/multiplier, + diRect.width()/multiplier, diRect.height()/multiplier); +} + +/*! + Returns the page's rectangle; this is usually smaller than the + paperRect() since the page normally has margins between its + borders and the paper. + + The unit of the returned rectangle is DevicePixel. + + \sa paperSize() +*/ +QRect QPrinter::pageRect() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_PageRect).toRect(); +} + +/*! + Returns the paper's rectangle; this is usually larger than the + pageRect(). + + The unit of the returned rectangle is DevicePixel. + + \sa pageRect() +*/ +QRect QPrinter::paperRect() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_PaperRect).toRect(); +} + + +/*! + \since 4.4 + + This function sets the \a left, \a top, \a right and \a bottom + page margins for this printer. The unit of the margins are + specified with the \a unit parameter. +*/ +void QPrinter::setPageMargins(qreal left, qreal top, qreal right, qreal bottom, QPrinter::Unit unit) +{ + Q_D(QPrinter); + const qreal multiplier = qt_multiplierForUnit(unit, resolution()); + QList<QVariant> margins; + margins << (left * multiplier) << (top * multiplier) + << (right * multiplier) << (bottom * multiplier); + d->printEngine->setProperty(QPrintEngine::PPK_PageMargins, margins); + d->addToManualSetList(QPrintEngine::PPK_PageMargins); + d->hasCustomPageMargins = true; +} + +/*! + \since 4.4 + + Returns the page margins for this printer in \a left, \a top, \a + right, \a bottom. The unit of the returned margins are specified + with the \a unit parameter. +*/ +void QPrinter::getPageMargins(qreal *left, qreal *top, qreal *right, qreal *bottom, QPrinter::Unit unit) const +{ + Q_D(const QPrinter); + Q_ASSERT(left && top && right && bottom); + const qreal multiplier = qt_multiplierForUnit(unit, resolution()); + QList<QVariant> margins(d->printEngine->property(QPrintEngine::PPK_PageMargins).toList()); + *left = margins.at(0).toReal() / multiplier; + *top = margins.at(1).toReal() / multiplier; + *right = margins.at(2).toReal() / multiplier; + *bottom = margins.at(3).toReal() / multiplier; +} + +/*! + \internal + + Returns the metric for the given \a id. +*/ +int QPrinter::metric(PaintDeviceMetric id) const +{ + Q_D(const QPrinter); + return d->printEngine->metric(id); +} + +/*! + Returns the paint engine used by the printer. +*/ +QPaintEngine *QPrinter::paintEngine() const +{ + Q_D(const QPrinter); + return d->paintEngine; +} + +/*! + \since 4.1 + + Returns the print engine used by the printer. +*/ +QPrintEngine *QPrinter::printEngine() const +{ + Q_D(const QPrinter); + return d->printEngine; +} + +#if defined (Q_WS_WIN) +/*! + Sets the page size to be used by the printer under Windows to \a + pageSize. + + \warning This function is not portable so you may prefer to use + setPaperSize() instead. + + \sa winPageSize() +*/ +void QPrinter::setWinPageSize(int pageSize) +{ + Q_D(QPrinter); + ABORT_IF_ACTIVE("QPrinter::setWinPageSize"); + d->printEngine->setProperty(QPrintEngine::PPK_WindowsPageSize, pageSize); + d->addToManualSetList(QPrintEngine::PPK_WindowsPageSize); +} + +/*! + Returns the page size used by the printer under Windows. + + \warning This function is not portable so you may prefer to use + paperSize() instead. + + \sa setWinPageSize() +*/ +int QPrinter::winPageSize() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_WindowsPageSize).toInt(); +} +#endif // Q_WS_WIN + +/*! + Returns a list of the resolutions (a list of dots-per-inch + integers) that the printer says it supports. + + For X11 where all printing is directly to postscript, this + function will always return a one item list containing only the + postscript resolution, i.e., 72 (72 dpi -- but see PrinterMode). +*/ +QList<int> QPrinter::supportedResolutions() const +{ + Q_D(const QPrinter); + QList<QVariant> varlist + = d->printEngine->property(QPrintEngine::PPK_SupportedResolutions).toList(); + QList<int> intlist; + for (int i=0; i<varlist.size(); ++i) + intlist << varlist.at(i).toInt(); + return intlist; +} + +/*! + Tells the printer to eject the current page and to continue + printing on a new page. Returns true if this was successful; + otherwise returns false. + + Calling newPage() on an inactive QPrinter object will always + fail. +*/ +bool QPrinter::newPage() +{ + Q_D(QPrinter); + if (d->printEngine->printerState() != QPrinter::Active) + return false; + return d->printEngine->newPage(); +} + +/*! + Aborts the current print run. Returns true if the print run was + successfully aborted and printerState() will return QPrinter::Aborted; otherwise + returns false. + + It is not always possible to abort a print job. For example, + all the data has gone to the printer but the printer cannot or + will not cancel the job when asked to. +*/ +bool QPrinter::abort() +{ + Q_D(QPrinter); + return d->printEngine->abort(); +} + +/*! + Returns the current state of the printer. This may not always be + accurate (for example if the printer doesn't have the capability + of reporting its state to the operating system). +*/ +QPrinter::PrinterState QPrinter::printerState() const +{ + Q_D(const QPrinter); + return d->printEngine->printerState(); +} + + +/*! \fn void QPrinter::margins(uint *top, uint *left, uint *bottom, uint *right) const + + Sets *\a top, *\a left, *\a bottom, *\a right to be the top, + left, bottom, and right margins. + + This function has been superseded by paperRect() and pageRect(). + Use paperRect().top() - pageRect().top() for the top margin, + paperRect().left() - pageRect().left() for the left margin, + paperRect().bottom() - pageRect().bottom() for the bottom margin, + and papaerRect().right() - pageRect().right() for the right + margin. + + \oldcode + uint rightMargin; + uint bottomMargin; + printer->margins(0, 0, &bottomMargin, &rightMargin); + \newcode + int rightMargin = printer->paperRect().right() - printer->pageRect().right(); + int bottomMargin = printer->paperRect().bottom() - printer->pageRect().bottom(); + \endcode +*/ + +/*! \fn QSize QPrinter::margins() const + + \overload + + Returns a QSize containing the left margin and the top margin. + + This function has been superseded by paperRect() and pageRect(). + Use paperRect().left() - pageRect().left() for the left margin, + and paperRect().top() - pageRect().top() for the top margin. + + \oldcode + QSize margins = printer->margins(); + int leftMargin = margins.width(); + int topMargin = margins.height(); + \newcode + int leftMargin = printer->paperRect().left() - printer->pageRect().left(); + int topMargin = printer->paperRect().top() - printer->pageRect().top(); + \endcode +*/ + +/*! \fn bool QPrinter::aborted() + + Use printerState() == QPrinter::Aborted instead. +*/ + +#ifdef Q_WS_WIN +/*! + \internal +*/ +HDC QPrinter::getDC() const +{ + Q_D(const QPrinter); + return d->printEngine->getPrinterDC(); +} + +/*! + \internal +*/ +void QPrinter::releaseDC(HDC hdc) const +{ + Q_D(const QPrinter); + d->printEngine->releasePrinterDC(hdc); +} + +/*! + Returns the supported paper sizes for this printer. + + The values will be either a value that matches an entry in the + QPrinter::PaperSource enum or a driver spesific value. The driver + spesific values are greater than the constant DMBIN_USER declared + in wingdi.h. + + \warning This function is only available in windows. +*/ + +QList<QPrinter::PaperSource> QPrinter::supportedPaperSources() const +{ + Q_D(const QPrinter); + QVariant v = d->printEngine->property(QPrintEngine::PPK_PaperSources); + + QList<QVariant> variant_list = v.toList(); + QList<QPrinter::PaperSource> int_list; + for (int i=0; i<variant_list.size(); ++i) + int_list << (QPrinter::PaperSource) variant_list.at(i).toInt(); + + return int_list; +} + +#endif + +/*! + \fn QString QPrinter::printerSelectionOption() const + + Returns the printer options selection string. This is useful only + if the print command has been explicitly set. + + The default value (an empty string) implies that the printer should + be selected in a system-dependent manner. + + Any other value implies that the given value should be used. + + \warning This function is not available on Windows. + + \sa setPrinterSelectionOption() +*/ + +/*! + \fn void QPrinter::setPrinterSelectionOption(const QString &option) + + Sets the printer to use \a option to select the printer. \a option + is null by default (which implies that Qt should be smart enough + to guess correctly), but it can be set to other values to use a + specific printer selection option. + + If the printer selection option is changed while the printer is + active, the current print job may or may not be affected. + + \warning This function is not available on Windows. + + \sa printerSelectionOption() +*/ + +#ifndef Q_WS_WIN +QString QPrinter::printerSelectionOption() const +{ + Q_D(const QPrinter); + return d->printEngine->property(QPrintEngine::PPK_SelectionOption).toString(); +} + +void QPrinter::setPrinterSelectionOption(const QString &option) +{ + Q_D(QPrinter); + d->printEngine->setProperty(QPrintEngine::PPK_SelectionOption, option); + d->addToManualSetList(QPrintEngine::PPK_SelectionOption); +} +#endif + +/*! + \since 4.1 + \fn int QPrinter::fromPage() const + + Returns the number of the first page in a range of pages to be printed + (the "from page" setting). Pages in a document are numbered according to + the convention that the first page is page 1. + + By default, this function returns a special value of 0, meaning that + the "from page" setting is unset. + + \note If fromPage() and toPage() both return 0, this indicates that + \e{the whole document will be printed}. + + \sa setFromTo(), toPage() +*/ + +int QPrinter::fromPage() const +{ + Q_D(const QPrinter); + return d->fromPage; +} + +/*! + \since 4.1 + + Returns the number of the last page in a range of pages to be printed + (the "to page" setting). Pages in a document are numbered according to + the convention that the first page is page 1. + + By default, this function returns a special value of 0, meaning that + the "to page" setting is unset. + + \note If fromPage() and toPage() both return 0, this indicates that + \e{the whole document will be printed}. + + The programmer is responsible for reading this setting and + printing accordingly. + + \sa setFromTo(), fromPage() +*/ + +int QPrinter::toPage() const +{ + Q_D(const QPrinter); + return d->toPage; +} + +/*! + \since 4.1 + + Sets the range of pages to be printed to cover the pages with numbers + specified by \a from and \a to, where \a from corresponds to the first + page in the range and \a to corresponds to the last. + + \note Pages in a document are numbered according to the convention that + the first page is page 1. However, if \a from and \a to are both set to 0, + the \e{whole document will be printed}. + + This function is mostly used to set a default value that the user can + override in the print dialog when you call setup(). + + \sa fromPage(), toPage() +*/ + +void QPrinter::setFromTo(int from, int to) +{ + Q_D(QPrinter); + if (from > to) { + qWarning() << "QPrinter::setFromTo: 'from' must be less than or equal to 'to'"; + from = to; + } + d->fromPage = from; + d->toPage = to; + + if (d->minPage == 0 && d->maxPage == 0) { + d->minPage = 1; + d->maxPage = to; + d->options |= QAbstractPrintDialog::PrintPageRange; + } +} + +/*! + \since 4.1 + + Sets the print range option in to be \a range. +*/ +void QPrinter::setPrintRange( PrintRange range ) +{ + Q_D(QPrinter); + d->printRange = QAbstractPrintDialog::PrintRange(range); +} + +/*! + \since 4.1 + + Returns the page range of the QPrinter. After the print setup + dialog has been opened, this function returns the value selected + by the user. + + \sa setPrintRange() +*/ +QPrinter::PrintRange QPrinter::printRange() const +{ + Q_D(const QPrinter); + return PrintRange(d->printRange); +} + +#if defined(QT3_SUPPORT) + +void QPrinter::setOutputToFile(bool f) +{ + if (f) { + if (outputFileName().isEmpty()) + setOutputFileName(QLatin1String("untitled_printer_document")); + } else { + setOutputFileName(QString()); + } +} + +bool qt_compat_QPrinter_printSetup(QPrinter *printer, QPrinterPrivate *pd, QWidget *parent) +{ + Q_UNUSED(pd); + QPrintDialog dlg(printer, parent); + return dlg.exec() != 0; +} + + +#ifdef Q_WS_MAC +bool qt_compat_QPrinter_pageSetup(QPrinter *p, QWidget *parent) +{ + QPageSetupDialog psd(p, parent); + return psd.exec() != 0; +} + +/*! + Executes a page setup dialog so that the user can configure the type of + page used for printing. Returns true if the contents of the dialog are + accepted; returns false if the dialog is canceled. +*/ +bool QPrinter::pageSetup(QWidget *parent) +{ + return qt_compat_QPrinter_pageSetup(this, parent); +} + +/*! + Executes a print setup dialog so that the user can configure the printing + process. Returns true if the contents of the dialog are accepted; returns + false if the dialog is canceled. +*/ +bool QPrinter::printSetup(QWidget *parent) +{ + Q_D(QPrinter); + return qt_compat_QPrinter_printSetup(this, d, parent); +} +#endif // Q_WS_MAC + +/*! + Use QPrintDialog instead. + + \oldcode + if (printer->setup(parent)) + ... + \newcode + QPrintDialog dialog(printer, parent); + if (dialog.exec()) + ... + \endcode +*/ +bool QPrinter::setup(QWidget *parent) +{ + Q_D(QPrinter); + return qt_compat_QPrinter_printSetup(this, d, parent) +#ifdef Q_WS_MAC + && qt_compat_QPrinter_pageSetup(this, parent); +#endif + ; +} + +/*! + Use QPrintDialog::minPage() instead. +*/ +int QPrinter::minPage() const +{ + Q_D(const QPrinter); + return d->minPage; +} + +/*! + Use QPrintDialog::maxPage() instead. +*/ +int QPrinter::maxPage() const +{ + Q_D(const QPrinter); + return d->maxPage; +} + +/*! + Use QPrintDialog::setMinMax() instead. +*/ +void QPrinter::setMinMax( int minPage, int maxPage ) +{ + Q_D(QPrinter); + Q_ASSERT_X(minPage <= maxPage, "QPrinter::setMinMax", + "'min' must be less than or equal to 'max'"); + d->minPage = minPage; + d->maxPage = maxPage; + d->options |= QPrintDialog::PrintPageRange; +} + +/*! + Returns true if the printer is set up to collate copies of printed documents; + otherwise returns false. + + Use QPrintDialog::isOptionEnabled(QPrintDialog::PrintCollateCopies) + instead. + + \sa collateCopies() +*/ +bool QPrinter::collateCopiesEnabled() const +{ + Q_D(const QPrinter); + return (d->options & QPrintDialog::PrintCollateCopies); +} + +/*! + Use QPrintDialog::setOption(QPrintDialog::PrintCollateCopies) + or QPrintDialog::setOptions(QPrintDialog::options() + & ~QPrintDialog::PrintCollateCopies) instead, depending on \a + enable. +*/ +void QPrinter::setCollateCopiesEnabled(bool enable) +{ + Q_D(QPrinter); + + if (enable) + d->options |= QPrintDialog::PrintCollateCopies; + else + d->options &= ~QPrintDialog::PrintCollateCopies; +} + +/*! + Use QPrintDialog instead. +*/ +void QPrinter::setOptionEnabled( PrinterOption option, bool enable ) +{ + Q_D(QPrinter); + if (enable) + d->options |= QPrintDialog::PrintDialogOption(1 << option); + else + d->options &= ~QPrintDialog::PrintDialogOption(1 << option); +} + +/*! + Use QPrintDialog instead. +*/ +bool QPrinter::isOptionEnabled( PrinterOption option ) const +{ + Q_D(const QPrinter); + return (d->options & QPrintDialog::PrintDialogOption(option)); +} + +#endif // QT3_SUPPORT + +/*! + \class QPrintEngine + \reentrant + + \ingroup printing + + \brief The QPrintEngine class defines an interface for how QPrinter + interacts with a given printing subsystem. + + The common case when creating your own print engine is to derive from both + QPaintEngine and QPrintEngine. Various properties of a print engine are + given with property() and set with setProperty(). + + \sa QPaintEngine +*/ + +/*! + \enum QPrintEngine::PrintEnginePropertyKey + + This enum is used to communicate properties between the print + engine and QPrinter. A property may or may not be supported by a + given print engine. + + \value PPK_CollateCopies A boolean value indicating whether the + printout should be collated or not. + + \value PPK_ColorMode Refers to QPrinter::ColorMode, either color or + monochrome. + + \value PPK_Creator A string describing the document's creator. + + \value PPK_Duplex A boolean value indicating whether both sides of + the printer paper should be used for the printout. + + \value PPK_DocumentName A string describing the document name in + the spooler. + + \value PPK_FontEmbedding A boolean value indicating whether data for + the document's fonts should be embedded in the data sent to the + printer. + + \value PPK_FullPage A boolean describing if the printer should be + full page or not. + + \value PPK_NumberOfCopies Obsolete. An integer specifying the number of + copies. Use PPK_CopyCount instead. + + \value PPK_Orientation Specifies a QPrinter::Orientation value. + + \value PPK_OutputFileName The output file name as a string. An + empty file name indicates that the printer should not print to a file. + + \value PPK_PageOrder Specifies a QPrinter::PageOrder value. + + \value PPK_PageRect A QRect specifying the page rectangle + + \value PPK_PageSize Obsolete. Use PPK_PaperSize instead. + + \value PPK_PaperRect A QRect specifying the paper rectangle. + + \value PPK_PaperSource Specifies a QPrinter::PaperSource value. + + \value PPK_PaperSources Specifies more than one QPrinter::PaperSource value. + + \value PPK_PaperSize Specifies a QPrinter::PaperSize value. + + \value PPK_PrinterName A string specifying the name of the printer. + + \value PPK_PrinterProgram A string specifying the name of the + printer program used for printing, + + \value PPK_Resolution An integer describing the dots per inch for + this printer. + + \value PPK_SelectionOption + + \value PPK_SupportedResolutions A list of integer QVariants + describing the set of supported resolutions that the printer has. + + \value PPK_SuppressSystemPrintStatus Suppress the built-in dialog for showing + printing progress. As of 4.1 this only has effect on Mac OS X where, by default, + a status dialog is shown. + + \value PPK_WindowsPageSize An integer specifying a DM_PAPER entry + on Windows. + + \value PPK_CustomPaperSize A QSizeF specifying a custom paper size + in the QPrinter::Point unit. + + \value PPK_PageMargins A QList<QVariant> containing the left, top, + right and bottom margin values. + + \value PPK_CopyCount An integer specifying the number of copies to print. + + \value PPK_SupportsMultipleCopies A boolean value indicating whether or not + the printer supports printing multiple copies in one job. + + \value PPK_CustomBase Basis for extension. +*/ + +/*! + \fn QPrintEngine::~QPrintEngine() + + Destroys the print engine. +*/ + +/*! + \fn void QPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) + + Sets the print engine's property specified by \a key to the given \a value. + + \sa property() +*/ + +/*! + \fn void QPrintEngine::property(PrintEnginePropertyKey key) const + + Returns the print engine's property specified by \a key. + + \sa setProperty() +*/ + +/*! + \fn bool QPrintEngine::newPage() + + Instructs the print engine to start a new page. Returns true if + the printer was able to create the new page; otherwise returns false. +*/ + +/*! + \fn bool QPrintEngine::abort() + + Instructs the print engine to abort the printing process. Returns + true if successful; otherwise returns false. +*/ + +/*! + \fn int QPrintEngine::metric(QPaintDevice::PaintDeviceMetric id) const + + Returns the metric for the given \a id. +*/ + +/*! + \fn QPrinter::PrinterState QPrintEngine::printerState() const + + Returns the current state of the printer being used by the print engine. +*/ + +/*! + \fn HDC QPrintEngine::getPrinterDC() const + \internal +*/ + +/*! + \fn void QPrintEngine::releasePrinterDC(HDC) const + \internal +*/ + +/* + Returns the dimensions for the given paper size, \a size, in millimeters. +*/ +QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size) +{ + if (size == QPrinter::Custom) return QSizeF(0, 0); + return QSizeF(qt_paperSizes[size][0], qt_paperSizes[size][1]); +} + +/* + Returns the PaperSize type that matches \a size, where \a size + is in millimeters. + + Because dimensions may not always be completely accurate (for + example when converting between units), a particular PaperSize + will be returned if it matches within -1/+1 millimeters. +*/ +QPrinter::PaperSize qSizeFTopaperSize(const QSizeF& size) +{ + for (int i = 0; i < static_cast<int>(QPrinter::NPaperSize); ++i) { + if (qt_paperSizes[i][0] >= size.width() - 1 && + qt_paperSizes[i][0] <= size.width() + 1 && + qt_paperSizes[i][1] >= size.height() - 1 && + qt_paperSizes[i][1] <= size.height() + 1) { + return QPrinter::PaperSize(i); + } + } + + return QPrinter::Custom; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprinter.h b/src/gui/painting/qprinter.h new file mode 100644 index 0000000000..6a5d0b7566 --- /dev/null +++ b/src/gui/painting/qprinter.h @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTER_H +#define QPRINTER_H + +#include <QtCore/qstring.h> +#include <QtCore/qscopedpointer.h> +#include <QtGui/qpaintdevice.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTER + +#if defined(B0) +#undef B0 // Terminal hang-up. We assume that you do not want that. +#endif + +class QPrinterPrivate; +class QPaintEngine; +class QPrintEngine; +class QPrinterInfo; + +class Q_GUI_EXPORT QPrinter : public QPaintDevice +{ + Q_DECLARE_PRIVATE(QPrinter) +public: + enum PrinterMode { ScreenResolution, PrinterResolution, HighResolution }; + + explicit QPrinter(PrinterMode mode = ScreenResolution); + explicit QPrinter(const QPrinterInfo& printer, PrinterMode mode = ScreenResolution); + ~QPrinter(); + + int devType() const; + + enum Orientation { Portrait, Landscape }; + +#ifndef Q_QDOC + enum PageSize { A4, B5, Letter, Legal, Executive, + A0, A1, A2, A3, A5, A6, A7, A8, A9, B0, B1, + B10, B2, B3, B4, B6, B7, B8, B9, C5E, Comm10E, + DLE, Folio, Ledger, Tabloid, Custom, NPageSize = Custom, NPaperSize = Custom }; + typedef PageSize PaperSize; +#else + enum PageSize { A4, B5, Letter, Legal, Executive, + A0, A1, A2, A3, A5, A6, A7, A8, A9, B0, B1, + B10, B2, B3, B4, B6, B7, B8, B9, C5E, Comm10E, + DLE, Folio, Ledger, Tabloid, Custom, NPageSize = Custom }; + enum PaperSize { A4, B5, Letter, Legal, Executive, + A0, A1, A2, A3, A5, A6, A7, A8, A9, B0, B1, + B10, B2, B3, B4, B6, B7, B8, B9, C5E, Comm10E, + DLE, Folio, Ledger, Tabloid, Custom, NPageSize = Custom, NPaperSize = Custom }; +#endif + + enum PageOrder { FirstPageFirst, + LastPageFirst }; + + enum ColorMode { GrayScale, + Color }; + + enum PaperSource { OnlyOne, + Lower, + Middle, + Manual, + Envelope, + EnvelopeManual, + Auto, + Tractor, + SmallFormat, + LargeFormat, + LargeCapacity, + Cassette, + FormSource, + MaxPageSource + }; + + enum PrinterState { Idle, + Active, + Aborted, + Error }; + + enum OutputFormat { NativeFormat, PdfFormat, PostScriptFormat }; + + // ### Qt 5: Merge with QAbstractPrintDialog::PrintRange + enum PrintRange { AllPages, Selection, PageRange, CurrentPage }; + + enum Unit { + Millimeter, + Point, + Inch, + Pica, + Didot, + Cicero, + DevicePixel + }; + + enum DuplexMode { + DuplexNone = 0, + DuplexAuto, + DuplexLongSide, + DuplexShortSide + }; + +#ifdef QT3_SUPPORT + enum PrinterOption { PrintToFile, PrintSelection, PrintPageRange }; +#endif // QT3_SUPPORT + + void setOutputFormat(OutputFormat format); + OutputFormat outputFormat() const; + + void setPrinterName(const QString &); + QString printerName() const; + + bool isValid() const; + + void setOutputFileName(const QString &); + QString outputFileName()const; + + void setPrintProgram(const QString &); + QString printProgram() const; + + void setDocName(const QString &); + QString docName() const; + + void setCreator(const QString &); + QString creator() const; + + void setOrientation(Orientation); + Orientation orientation() const; + + void setPageSize(PageSize); + PageSize pageSize() const; + + void setPaperSize(PaperSize); + PaperSize paperSize() const; + + void setPaperSize(const QSizeF &paperSize, Unit unit); + QSizeF paperSize(Unit unit) const; + + void setPageOrder(PageOrder); + PageOrder pageOrder() const; + + void setResolution(int); + int resolution() const; + + void setColorMode(ColorMode); + ColorMode colorMode() const; + + void setCollateCopies(bool collate); + bool collateCopies() const; + + void setFullPage(bool); + bool fullPage() const; + + void setNumCopies(int); + int numCopies() const; + + int actualNumCopies() const; + + void setCopyCount(int); + int copyCount() const; + bool supportsMultipleCopies() const; + + void setPaperSource(PaperSource); + PaperSource paperSource() const; + + void setDuplex(DuplexMode duplex); + DuplexMode duplex() const; + + QList<int> supportedResolutions() const; + +#ifdef Q_WS_WIN + QList<PaperSource> supportedPaperSources() const; +#endif + + void setFontEmbeddingEnabled(bool enable); + bool fontEmbeddingEnabled() const; + + void setDoubleSidedPrinting(bool enable); + bool doubleSidedPrinting() const; + +#ifdef Q_WS_WIN + void setWinPageSize(int winPageSize); + int winPageSize() const; +#endif + + QRect paperRect() const; + QRect pageRect() const; + QRectF paperRect(Unit) const; + QRectF pageRect(Unit) const; + +#if !defined(Q_WS_WIN) || defined(qdoc) + QString printerSelectionOption() const; + void setPrinterSelectionOption(const QString &); +#endif + + bool newPage(); + bool abort(); + + PrinterState printerState() const; + + QPaintEngine *paintEngine() const; + QPrintEngine *printEngine() const; + +#ifdef Q_WS_WIN + HDC getDC() const; + void releaseDC(HDC hdc) const; +#endif + + void setFromTo(int fromPage, int toPage); + int fromPage() const; + int toPage() const; + + void setPrintRange(PrintRange range); + PrintRange printRange() const; + + void setPageMargins(qreal left, qreal top, qreal right, qreal bottom, Unit unit); + void getPageMargins(qreal *left, qreal *top, qreal *right, qreal *bottom, Unit unit) const; + +#ifdef QT3_SUPPORT +#ifdef Q_WS_MAC + QT3_SUPPORT bool pageSetup(QWidget *parent = 0); + QT3_SUPPORT bool printSetup(QWidget *parent = 0); +#endif + + QT3_SUPPORT bool setup(QWidget *parent = 0); + + QT3_SUPPORT void setMinMax(int minPage, int maxPage); + QT3_SUPPORT int minPage() const; + QT3_SUPPORT int maxPage() const; + + QT3_SUPPORT void setCollateCopiesEnabled(bool); + QT3_SUPPORT bool collateCopiesEnabled() const; + + QT3_SUPPORT void setOptionEnabled(PrinterOption, bool enable); + QT3_SUPPORT bool isOptionEnabled(PrinterOption) const; + + inline QT3_SUPPORT QSize margins() const; + inline QT3_SUPPORT void margins(uint *top, uint *left, uint *bottom, uint *right) const; + + inline QT3_SUPPORT bool aborted() { return printerState() == Aborted; } + + QT3_SUPPORT void setOutputToFile(bool); + inline QT3_SUPPORT bool outputToFile() const { return !outputFileName().isEmpty(); } +#endif + +protected: + int metric(PaintDeviceMetric) const; + void setEngines(QPrintEngine *printEngine, QPaintEngine *paintEngine); + +private: + void init(PrinterMode mode); + + Q_DISABLE_COPY(QPrinter) + + QScopedPointer<QPrinterPrivate> d_ptr; + + friend class QPrintDialogPrivate; + friend class QAbstractPrintDialog; + friend class QAbstractPrintDialogPrivate; + friend class QPrintPreviewWidgetPrivate; + friend class QTextDocument; + friend class QPageSetupWidget; +}; + +#ifdef QT3_SUPPORT +inline QSize QPrinter::margins() const +{ + QRect page = pageRect(); + QRect paper = paperRect(); + return QSize(page.left() - paper.left(), page.top() - paper.top()); +} + +inline void QPrinter::margins(uint *top, uint *left, uint *bottom, uint *right) const +{ + QRect page = pageRect(); + QRect paper = paperRect(); + if (top) + *top = page.top() - paper.top(); + if (left) + *left = page.left() - paper.left(); + if (bottom) + *bottom = paper.bottom() - page.bottom(); + if (right) + *right = paper.right() - page.right(); +} +#endif + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPRINTER_H diff --git a/src/gui/painting/qprinter_p.h b/src/gui/painting/qprinter_p.h new file mode 100644 index 0000000000..556f0f06c2 --- /dev/null +++ b/src/gui/painting/qprinter_p.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTER_P_H +#define QPRINTER_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 "QtCore/qglobal.h" + +#ifndef QT_NO_PRINTER + +#include "QtGui/qprinter.h" +#include "QtGui/qprintengine.h" +#include "QtGui/qprintdialog.h" +#include "QtCore/qpointer.h" + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +class QPrintEngine; +class QPreviewPaintEngine; +class QPicture; + +class QPrinterPrivate +{ + Q_DECLARE_PUBLIC(QPrinter) +public: + QPrinterPrivate(QPrinter *printer) + : printEngine(0) + , paintEngine(0) + , q_ptr(printer) + , options(QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintPageRange | + QAbstractPrintDialog::PrintCollateCopies | QAbstractPrintDialog::PrintShowPageSize) + , printRange(QAbstractPrintDialog::AllPages) + , minPage(1) + , maxPage(INT_MAX) + , fromPage(0) + , toPage(0) + , use_default_engine(true) + , validPrinter(false) + , hasCustomPageMargins(false) + , hasUserSetPageSize(false) + { + } + + ~QPrinterPrivate() { + + } + + void createDefaultEngines(); +#ifndef QT_NO_PRINTPREVIEWWIDGET + QList<const QPicture *> previewPages() const; + void setPreviewMode(bool); +#endif + + void addToManualSetList(QPrintEngine::PrintEnginePropertyKey key); + + QPrinter::PrinterMode printerMode; + QPrinter::OutputFormat outputFormat; + QPrintEngine *printEngine; + QPaintEngine *paintEngine; + + QPrintEngine *realPrintEngine; + QPaintEngine *realPaintEngine; +#ifndef QT_NO_PRINTPREVIEWWIDGET + QPreviewPaintEngine *previewEngine; +#endif + + QPrinter *q_ptr; + + QAbstractPrintDialog::PrintDialogOptions options; + QAbstractPrintDialog::PrintRange printRange; + int minPage, maxPage, fromPage, toPage; + + uint use_default_engine : 1; + uint had_default_engines : 1; + + uint validPrinter : 1; + uint hasCustomPageMargins : 1; + uint hasUserSetPageSize : 1; + + // Used to remember which properties have been manually set by the user. + QList<QPrintEngine::PrintEnginePropertyKey> manualSetList; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTER_P_H diff --git a/src/gui/painting/qprinterinfo.cpp b/src/gui/painting/qprinterinfo.cpp new file mode 100644 index 0000000000..f680ce1950 --- /dev/null +++ b/src/gui/painting/qprinterinfo.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprinterinfo.h" +#include "qprinterinfo_p.h" + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +QPrinterInfoPrivate QPrinterInfoPrivate::shared_null; + + +/*! + \class QPrinterInfo + + \brief The QPrinterInfo class gives access to information about + existing printers. + + \ingroup printing + + Use the static functions to generate a list of QPrinterInfo + objects. Each QPrinterInfo object in the list represents a single + printer and can be queried for name, supported paper sizes, and + whether or not it is the default printer. + + \since 4.4 +*/ + +/*! + \fn QList<QPrinterInfo> QPrinterInfo::availablePrinters() + + Returns a list of available printers on the system. +*/ + +/*! + \fn QPrinterInfo QPrinterInfo::defaultPrinter() + + Returns the default printer on the system. + + The return value should be checked using isNull() before being + used, in case there is no default printer. + + \sa isNull() +*/ + +/*! + Constructs an empty QPrinterInfo object. + + \sa isNull() +*/ +QPrinterInfo::QPrinterInfo() + : d_ptr(&QPrinterInfoPrivate::shared_null) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QPrinterInfo::QPrinterInfo(const QPrinterInfo &other) + : d_ptr(new QPrinterInfoPrivate(*other.d_ptr)) +{ +} + +/*! + Constructs a QPrinterInfo object from \a printer. +*/ +QPrinterInfo::QPrinterInfo(const QPrinter &printer) + : d_ptr(&QPrinterInfoPrivate::shared_null) +{ + foreach (const QPrinterInfo &printerInfo, availablePrinters()) { + if (printerInfo.printerName() == printer.printerName()) { + d_ptr.reset(new QPrinterInfoPrivate(*printerInfo.d_ptr)); + break; + } + } +} + +/*! + \internal +*/ +QPrinterInfo::QPrinterInfo(const QString &name) + : d_ptr(new QPrinterInfoPrivate(name)) +{ +} + +/*! + Destroys the QPrinterInfo object. References to the values in the + object become invalid. +*/ +QPrinterInfo::~QPrinterInfo() +{ +} + +/*! + Sets the QPrinterInfo object to be equal to \a other. +*/ +QPrinterInfo &QPrinterInfo::operator=(const QPrinterInfo &other) +{ + Q_ASSERT(d_ptr); + d_ptr.reset(new QPrinterInfoPrivate(*other.d_ptr)); + return *this; +} + +/*! + Returns the name of the printer. + + \sa QPrinter::setPrinterName() +*/ +QString QPrinterInfo::printerName() const +{ + const Q_D(QPrinterInfo); + return d->name; +} + +/*! + Returns whether this QPrinterInfo object holds a printer definition. + + An empty QPrinterInfo object could result for example from calling + defaultPrinter() when there are no printers on the system. +*/ +bool QPrinterInfo::isNull() const +{ + const Q_D(QPrinterInfo); + return d == &QPrinterInfoPrivate::shared_null; +} + +/*! + Returns whether this printer is the default printer. +*/ +bool QPrinterInfo::isDefault() const +{ + const Q_D(QPrinterInfo); + return d->isDefault; +} + +/*! + \fn QList< QPrinter::PaperSize> QPrinterInfo::supportedPaperSizes() const + \since 4.4 + + Returns a list of supported paper sizes by the printer. + + Not all printer drivers support this query, so the list may be empty. + On Mac OS X 10.3, this function always returns an empty list. +*/ + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/painting/qprinterinfo.h b/src/gui/painting/qprinterinfo.h new file mode 100644 index 0000000000..34379613de --- /dev/null +++ b/src/gui/painting/qprinterinfo.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTERINFO_H +#define QPRINTERINFO_H + +#include <QtCore/QList> + +#include <QtGui/QPrinter> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_PRINTER +class QPrinterInfoPrivate; +class QPrinterInfoPrivateDeleter; +class Q_GUI_EXPORT QPrinterInfo +{ +public: + QPrinterInfo(); + QPrinterInfo(const QPrinterInfo &other); + QPrinterInfo(const QPrinter &printer); + ~QPrinterInfo(); + + QPrinterInfo &operator=(const QPrinterInfo &other); + + QString printerName() const; + bool isNull() const; + bool isDefault() const; + QList<QPrinter::PaperSize> supportedPaperSizes() const; + + static QList<QPrinterInfo> availablePrinters(); + static QPrinterInfo defaultPrinter(); + +private: + QPrinterInfo(const QString &name); + +private: + Q_DECLARE_PRIVATE(QPrinterInfo) + QScopedPointer<QPrinterInfoPrivate, QPrinterInfoPrivateDeleter> d_ptr; +}; + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPRINTERINFO_H diff --git a/src/gui/painting/qprinterinfo_mac.cpp b/src/gui/painting/qprinterinfo_mac.cpp new file mode 100644 index 0000000000..b24ab70267 --- /dev/null +++ b/src/gui/painting/qprinterinfo_mac.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprinterinfo.h" +#include "qprinterinfo_p.h" + +#include "private/qt_mac_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +extern QPrinter::PaperSize qSizeFTopaperSize(const QSizeF &size); + +QList<QPrinterInfo> QPrinterInfo::availablePrinters() +{ + QList<QPrinterInfo> printers; + + QCFType<CFArrayRef> array; + if (PMServerCreatePrinterList(kPMServerLocal, &array) == noErr) { + CFIndex count = CFArrayGetCount(array); + for (int i = 0; i < count; ++i) { + PMPrinter printer = static_cast<PMPrinter>(const_cast<void *>(CFArrayGetValueAtIndex(array, i))); + QString printerName = QCFString::toQString(PMPrinterGetName(printer)); + + QPrinterInfo printerInfo(printerName); + if (PMPrinterIsDefault(printer)) + printerInfo.d_ptr->isDefault = true; + printers.append(printerInfo); + } + } + + return printers; +} + +QPrinterInfo QPrinterInfo::defaultPrinter() +{ + QList<QPrinterInfo> printers = availablePrinters(); + foreach (const QPrinterInfo &printerInfo, printers) { + if (printerInfo.isDefault()) + return printerInfo; + } + + return printers.value(0); +} + +QList<QPrinter::PaperSize> QPrinterInfo::supportedPaperSizes() const +{ + const Q_D(QPrinterInfo); + + QList<QPrinter::PaperSize> paperSizes; + if (isNull()) + return paperSizes; + + PMPrinter cfPrn = PMPrinterCreateFromPrinterID(QCFString::toCFStringRef(d->name)); + if (!cfPrn) + return paperSizes; + + CFArrayRef array; + if (PMPrinterGetPaperList(cfPrn, &array) != noErr) { + PMRelease(cfPrn); + return paperSizes; + } + + int count = CFArrayGetCount(array); + for (int i = 0; i < count; ++i) { + PMPaper paper = static_cast<PMPaper>(const_cast<void *>(CFArrayGetValueAtIndex(array, i))); + double width, height; + if (PMPaperGetWidth(paper, &width) == noErr && PMPaperGetHeight(paper, &height) == noErr) { + QSizeF size(width * 0.3527, height * 0.3527); + paperSizes.append(qSizeFTopaperSize(size)); + } + } + + PMRelease(cfPrn); + + return paperSizes; +} + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qprinterinfo_p.h b/src/gui/painting/qprinterinfo_p.h new file mode 100644 index 0000000000..fcc1acb5d6 --- /dev/null +++ b/src/gui/painting/qprinterinfo_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTERINFO_P_H +#define QPRINTERINFO_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 "QtCore/qglobal.h" + +#ifndef QT_NO_PRINTER + +#include "QtCore/qlist.h" + +QT_BEGIN_NAMESPACE + +class QPrinterInfoPrivate +{ +public: + QPrinterInfoPrivate(const QString& name = QString()) : + name(name), isDefault(false) +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_SYMBIAN)) || defined(Q_WS_QPA) +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + , cupsPrinterIndex(0), hasPaperSizes(false) +#endif +#endif + {} + ~QPrinterInfoPrivate() + {} + + static QPrinterInfoPrivate shared_null; + + QString name; + bool isDefault; + +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_SYMBIAN)) || defined(Q_WS_QPA) +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + int cupsPrinterIndex; + mutable bool hasPaperSizes; + mutable QList<QPrinter::PaperSize> paperSizes; +#endif +#endif +}; + + +class QPrinterInfoPrivateDeleter +{ +public: + static inline void cleanup(QPrinterInfoPrivate *d) + { + if (d != &QPrinterInfoPrivate::shared_null) + delete d; + } +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTERINFO_P_H diff --git a/src/gui/painting/qprinterinfo_unix.cpp b/src/gui/painting/qprinterinfo_unix.cpp new file mode 100644 index 0000000000..38e9590b1d --- /dev/null +++ b/src/gui/painting/qprinterinfo_unix.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprinterinfo.h" +#include "qprinterinfo_p.h" + +#include <qfile.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qprintdialog.h> +#include <qlibrary.h> +#include <qtextstream.h> + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +# include <private/qcups_p.h> +# include <cups/cups.h> +# include <private/qpdf_p.h> +#endif + +#include <private/qprinterinfo_unix_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) +// preserver names in ascending order for the binary search +static const struct NamedPaperSize { + const char *const name; + QPrinter::PaperSize size; +} named_sizes_map[QPrinter::NPageSize] = { + { "A0", QPrinter::A0 }, + { "A1", QPrinter::A1 }, + { "A2", QPrinter::A2 }, + { "A3", QPrinter::A3 }, + { "A4", QPrinter::A4 }, + { "A5", QPrinter::A5 }, + { "A6", QPrinter::A6 }, + { "A7", QPrinter::A7 }, + { "A8", QPrinter::A8 }, + { "A9", QPrinter::A9 }, + { "B0", QPrinter::B0 }, + { "B1", QPrinter::B1 }, + { "B10", QPrinter::B10 }, + { "B2", QPrinter::B2 }, + { "B4", QPrinter::B4 }, + { "B5", QPrinter::B5 }, + { "B6", QPrinter::B6 }, + { "B7", QPrinter::B7 }, + { "B8", QPrinter::B8 }, + { "B9", QPrinter::B9 }, + { "C5E", QPrinter::C5E }, + { "Comm10E", QPrinter::Comm10E }, + { "Custom", QPrinter::Custom }, + { "DLE", QPrinter::DLE }, + { "Executive", QPrinter::Executive }, + { "Folio", QPrinter::Folio }, + { "Ledger", QPrinter::Ledger }, + { "Legal", QPrinter::Legal }, + { "Letter", QPrinter::Letter }, + { "Tabloid", QPrinter::Tabloid } +}; + +inline bool operator<(const char *name, const NamedPaperSize &data) +{ return qstrcmp(name, data.name) < 0; } +inline bool operator<(const NamedPaperSize &data, const char *name) +{ return qstrcmp(data.name, name) < 0; } + +static inline QPrinter::PaperSize string2PaperSize(const char *name) +{ + const NamedPaperSize *r = qBinaryFind(named_sizes_map, named_sizes_map + QPrinter::NPageSize, name); + if (r - named_sizes_map != QPrinter::NPageSize) + return r->size; + return QPrinter::Custom; +} + +static inline const char *paperSize2String(QPrinter::PaperSize size) +{ + for (int i = 0; i < QPrinter::NPageSize; ++i) { + if (size == named_sizes_map[i].size) + return named_sizes_map[i].name; + } + return 0; +} +#endif + +void qt_perhapsAddPrinter(QList<QPrinterDescription> *printers, const QString &name, + QString host, QString comment, + QStringList aliases) +{ + for (int i = 0; i < printers->size(); ++i) + if (printers->at(i).samePrinter(name)) + return; + +#ifndef QT_NO_PRINTDIALOG + if (host.isEmpty()) + host = QPrintDialog::tr("locally connected"); +#endif + printers->append(QPrinterDescription(name.simplified(), host.simplified(), comment.simplified(), aliases)); +} + +void qt_parsePrinterDesc(QString printerDesc, QList<QPrinterDescription> *printers) +{ + if (printerDesc.length() < 1) + return; + + printerDesc = printerDesc.simplified(); + int i = printerDesc.indexOf(QLatin1Char(':')); + QString printerName, printerComment, printerHost; + QStringList aliases; + + if (i >= 0) { + // have ':' want '|' + int j = printerDesc.indexOf(QLatin1Char('|')); + if (j > 0 && j < i) { + printerName = printerDesc.left(j); + aliases = printerDesc.mid(j + 1, i - j - 1).split(QLatin1Char('|')); +#ifndef QT_NO_PRINTDIALOG + // try extracting a comment from the aliases + printerComment = QPrintDialog::tr("Aliases: %1") + .arg(aliases.join(QLatin1String(", "))); +#endif + } else { + printerName = printerDesc.left(i); + } + // look for lprng pseudo all printers entry + i = printerDesc.indexOf(QRegExp(QLatin1String(": *all *="))); + if (i >= 0) + printerName = QString(); + // look for signs of this being a remote printer + i = printerDesc.indexOf(QRegExp(QLatin1String(": *rm *="))); + if (i >= 0) { + // point k at the end of remote host name + while (printerDesc[i] != QLatin1Char('=')) + i++; + while (printerDesc[i] == QLatin1Char('=') || printerDesc[i].isSpace()) + i++; + j = i; + while (j < (int)printerDesc.length() && printerDesc[j] != QLatin1Char(':')) + j++; + + // and stuff that into the string + printerHost = printerDesc.mid(i, j - i); + } + } + if (printerName.length()) + qt_perhapsAddPrinter(printers, printerName, printerHost, printerComment, + aliases); +} + +int qt_parsePrintcap(QList<QPrinterDescription> *printers, const QString& fileName) +{ + QFile printcap(fileName); + if (!printcap.open(QIODevice::ReadOnly)) + return NotFound; + + char *line_ascii = new char[1025]; + line_ascii[1024] = '\0'; + + QString printerDesc; + bool atEnd = false; + + while (!atEnd) { + if (printcap.atEnd() || printcap.readLine(line_ascii, 1024) <= 0) + atEnd = true; + QString line = QString::fromLocal8Bit(line_ascii); + line = line.trimmed(); + if (line.length() >= 1 && line[int(line.length()) - 1] == QLatin1Char('\\')) + line.chop(1); + if (line[0] == QLatin1Char('#')) { + if (!atEnd) + continue; + } else if (line[0] == QLatin1Char('|') || line[0] == QLatin1Char(':') + || line.isEmpty()) { + printerDesc += line; + if (!atEnd) + continue; + } + + qt_parsePrinterDesc(printerDesc, printers); + + // add the first line of the new printer definition + printerDesc = line; + } + delete[] line_ascii; + return Success; +} + +/*! + \internal + + Checks $HOME/.printers for a line matching '_default <name>' (where + <name> does not contain any white space). The first such match + results in <name> being returned. + If no lines match then an empty string is returned. +*/ +QString qt_getDefaultFromHomePrinters() +{ + QFile file(QDir::homePath() + QLatin1String("/.printers")); + if (!file.open(QIODevice::ReadOnly)) + return QString(); + QString all(QLatin1String(file.readAll())); + QStringList words = all.split(QRegExp(QLatin1String("\\W+")), QString::SkipEmptyParts); + const int i = words.indexOf(QLatin1String("_default")); + if (i != -1 && i < words.size() - 1) + return words.at(i + 1); + return QString(); +} + +// solaris, not 2.6 +void qt_parseEtcLpPrinters(QList<QPrinterDescription> *printers) +{ + QDir lp(QLatin1String("/etc/lp/printers")); + QFileInfoList dirs = lp.entryInfoList(); + if (dirs.isEmpty()) + return; + + QString tmp; + for (int i = 0; i < dirs.size(); ++i) { + QFileInfo printer = dirs.at(i); + if (printer.isDir()) { + tmp.sprintf("/etc/lp/printers/%s/configuration", + printer.fileName().toAscii().data()); + QFile configuration(tmp); + char *line = new char[1025]; + QString remote(QLatin1String("Remote:")); + QString contentType(QLatin1String("Content types:")); + QString printerHost; + bool canPrintPostscript = false; + if (configuration.open(QIODevice::ReadOnly)) { + while (!configuration.atEnd() && + configuration.readLine(line, 1024) > 0) { + if (QString::fromLatin1(line).startsWith(remote)) { + const char *p = line; + while (*p != ':') + p++; + p++; + while (isspace((uchar) *p)) + p++; + printerHost = QString::fromLocal8Bit(p); + printerHost = printerHost.simplified(); + } else if (QString::fromLatin1(line).startsWith(contentType)) { + char *p = line; + while (*p != ':') + p++; + p++; + char *e; + while (*p) { + while (isspace((uchar) *p)) + p++; + if (*p) { + char s; + e = p; + while (isalnum((uchar) *e)) + e++; + s = *e; + *e = '\0'; + if (!qstrcmp(p, "postscript") || + !qstrcmp(p, "any")) + canPrintPostscript = true; + *e = s; + if (s == ',') + e++; + p = e; + } + } + } + } + if (canPrintPostscript) + qt_perhapsAddPrinter(printers, printer.fileName(), + printerHost, QLatin1String("")); + } + delete[] line; + } + } +} + +// solaris 2.6 +char *qt_parsePrintersConf(QList<QPrinterDescription> *printers, bool *found) +{ + QFile pc(QLatin1String("/etc/printers.conf")); + if (!pc.open(QIODevice::ReadOnly)) { + if (found) + *found = false; + return 0; + } + if (found) + *found = true; + + char *line = new char[1025]; + line[1024] = '\0'; + + QString printerDesc; + int lineLength = 0; + + char *defaultPrinter = 0; + + while (!pc.atEnd() && + (lineLength=pc.readLine(line, 1024)) > 0) { + if (*line == '#') { + *line = '\0'; + lineLength = 0; + } + if (lineLength >= 2 && line[lineLength-2] == '\\') { + line[lineLength-2] = '\0'; + printerDesc += QString::fromLocal8Bit(line); + } else { + printerDesc += QString::fromLocal8Bit(line); + printerDesc = printerDesc.simplified(); + int i = printerDesc.indexOf(QLatin1Char(':')); + QString printerName, printerHost, printerComment; + QStringList aliases; + if (i >= 0) { + // have : want | + int j = printerDesc.indexOf(QLatin1Char('|')); + if (j >= i) + j = -1; + printerName = printerDesc.mid(0, j < 0 ? i : j); + if (printerName == QLatin1String("_default")) { + i = printerDesc.indexOf( + QRegExp(QLatin1String(": *use *="))); + while (printerDesc[i] != QLatin1Char('=')) + i++; + while (printerDesc[i] == QLatin1Char('=') || printerDesc[i].isSpace()) + i++; + j = i; + while (j < (int)printerDesc.length() && + printerDesc[j] != QLatin1Char(':') && printerDesc[j] != QLatin1Char(',')) + j++; + // that's our default printer + defaultPrinter = + qstrdup(printerDesc.mid(i, j-i).toAscii().data()); + printerName = QString(); + printerDesc = QString(); + } else if (printerName == QLatin1String("_all")) { + // skip it.. any other cases we want to skip? + printerName = QString(); + printerDesc = QString(); + } + + if (j > 0) { + // try extracting a comment from the aliases + aliases = printerDesc.mid(j + 1, i - j - 1).split(QLatin1Char('|')); +#ifndef QT_NO_PRINTDIALOG + printerComment = QPrintDialog::tr("Aliases: %1") + .arg(aliases.join(QLatin1String(", "))); +#endif + } + // look for signs of this being a remote printer + i = printerDesc.indexOf( + QRegExp(QLatin1String(": *bsdaddr *="))); + if (i >= 0) { + // point k at the end of remote host name + while (printerDesc[i] != QLatin1Char('=')) + i++; + while (printerDesc[i] == QLatin1Char('=') || printerDesc[i].isSpace()) + i++; + j = i; + while (j < (int)printerDesc.length() && + printerDesc[j] != QLatin1Char(':') && printerDesc[j] != QLatin1Char(',')) + j++; + // and stuff that into the string + printerHost = printerDesc.mid(i, j-i); + // maybe stick the remote printer name into the comment + if (printerDesc[j] == QLatin1Char(',')) { + i = ++j; + while (printerDesc[i].isSpace()) + i++; + j = i; + while (j < (int)printerDesc.length() && + printerDesc[j] != QLatin1Char(':') && printerDesc[j] != QLatin1Char(',')) + j++; + if (printerName != printerDesc.mid(i, j-i)) { + printerComment = + QLatin1String("Remote name: "); + printerComment += printerDesc.mid(i, j-i); + } + } + } + } + if (printerComment == QLatin1String(":")) + printerComment = QString(); // for cups + if (printerName.length()) + qt_perhapsAddPrinter(printers, printerName, printerHost, + printerComment, aliases); + // chop away the line, for processing the next one + printerDesc = QString(); + } + } + delete[] line; + return defaultPrinter; +} + +#ifndef QT_NO_NIS + +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif + +int qt_pd_foreach(int /*status */, char * /*key */, int /*keyLen */, + char *val, int valLen, char *data) +{ + qt_parsePrinterDesc(QString::fromLatin1(val, valLen), (QList<QPrinterDescription> *)data); + return 0; +} + +#if defined(Q_C_CALLBACKS) +} +#endif + +int qt_retrieveNisPrinters(QList<QPrinterDescription> *printers) +{ +#ifndef QT_NO_LIBRARY + typedef int (*WildCast)(int, char *, int, char *, int, char *); + char printersConfByname[] = "printers.conf.byname"; + char *domain; + int err; + + QLibrary lib(QLatin1String("nsl")); + typedef int (*ypGetDefaultDomain)(char **); + ypGetDefaultDomain _ypGetDefaultDomain = (ypGetDefaultDomain)lib.resolve("yp_get_default_domain"); + typedef int (*ypAll)(const char *, const char *, const struct ypall_callback *); + ypAll _ypAll = (ypAll)lib.resolve("yp_all"); + + if (_ypGetDefaultDomain && _ypAll) { + err = _ypGetDefaultDomain(&domain); + if (err == 0) { + ypall_callback cb; + // wild cast to support K&R-style system headers + (WildCast &) cb.foreach = (WildCast) qt_pd_foreach; + cb.data = (char *) printers; + err = _ypAll(domain, printersConfByname, &cb); + } + if (!err) + return Success; + } +#endif //QT_NO_LIBRARY + return Unavail; +} + +#endif // QT_NO_NIS + +char *qt_parseNsswitchPrintersEntry(QList<QPrinterDescription> *printers, char *line) +{ +#define skipSpaces() \ + while (line[k] != '\0' && isspace((uchar) line[k])) \ + k++ + + char *defaultPrinter = 0; + bool stop = false; + int lastStatus = NotFound; + + int k = 8; + skipSpaces(); + if (line[k] != ':') + return 0; + k++; + + char *cp = strchr(line, '#'); + if (cp != 0) + *cp = '\0'; + + while (line[k] != '\0') { + if (isspace((uchar) line[k])) { + k++; + } else if (line[k] == '[') { + k++; + skipSpaces(); + while (line[k] != '\0') { + char status = tolower(line[k]); + char action = '?'; + + while (line[k] != '=' && line[k] != ']' && line[k] != '\0') + k++; + if (line[k] == '=') { + k++; + skipSpaces(); + action = tolower(line[k]); + while (line[k] != '\0' && !isspace((uchar) line[k]) && line[k] != ']') + k++; + } else if (line[k] == ']') { + k++; + break; + } + skipSpaces(); + + if (lastStatus == status) + stop = (action == (char) Return); + } + } else { + if (stop) + break; + + QByteArray source; + while (line[k] != '\0' && !isspace((uchar) line[k]) && line[k] != '[') { + source += line[k]; + k++; + } + + if (source == "user") { + lastStatus = qt_parsePrintcap(printers, + QDir::homePath() + QLatin1String("/.printers")); + } else if (source == "files") { + bool found; + defaultPrinter = qt_parsePrintersConf(printers, &found); + if (found) + lastStatus = Success; +#ifndef QT_NO_NIS + } else if (source == "nis") { + lastStatus = qt_retrieveNisPrinters(printers); +#endif + } else { + // nisplus, dns, etc., are not implemented yet + lastStatus = NotFound; + } + stop = (lastStatus == Success); + } + } + return defaultPrinter; +} + +char *qt_parseNsswitchConf(QList<QPrinterDescription> *printers) +{ + QFile nc(QLatin1String("/etc/nsswitch.conf")); + if (!nc.open(QIODevice::ReadOnly)) + return 0; + + char *defaultPrinter = 0; + + char *line = new char[1025]; + line[1024] = '\0'; + + while (!nc.atEnd() && + nc.readLine(line, 1024) > 0) { + if (qstrncmp(line, "printers", 8) == 0) { + defaultPrinter = qt_parseNsswitchPrintersEntry(printers, line); + delete[] line; + return defaultPrinter; + } + } + + strcpy(line, "printers: user files nis nisplus xfn"); + defaultPrinter = qt_parseNsswitchPrintersEntry(printers, line); + delete[] line; + return defaultPrinter; +} + +// HP-UX +void qt_parseEtcLpMember(QList<QPrinterDescription> *printers) +{ + QDir lp(QLatin1String("/etc/lp/member")); + if (!lp.exists()) + return; + QFileInfoList dirs = lp.entryInfoList(); + if (dirs.isEmpty()) + return; + +#ifdef QT_NO_PRINTDIALOG + Q_UNUSED(printers); +#else + QString tmp; + for (int i = 0; i < dirs.size(); ++i) { + QFileInfo printer = dirs.at(i); + // I haven't found any real documentation, so I'm guessing that + // since lpstat uses /etc/lp/member rather than one of the + // other directories, it's the one to use. I did not find a + // decent way to locate aliases and remote printers. + if (printer.isFile()) + qt_perhapsAddPrinter(printers, printer.fileName(), + QPrintDialog::tr("unknown"), + QLatin1String("")); + } +#endif +} + +// IRIX 6.x +void qt_parseSpoolInterface(QList<QPrinterDescription> *printers) +{ + QDir lp(QLatin1String("/usr/spool/lp/interface")); + if (!lp.exists()) + return; + QFileInfoList files = lp.entryInfoList(); + if(files.isEmpty()) + return; + + for (int i = 0; i < files.size(); ++i) { + QFileInfo printer = files.at(i); + + if (!printer.isFile()) + continue; + + // parse out some information + QFile configFile(printer.filePath()); + if (!configFile.open(QIODevice::ReadOnly)) + continue; + + QByteArray line; + line.resize(1025); + QString namePrinter; + QString hostName; + QString hostPrinter; + QString printerType; + + QString nameKey(QLatin1String("NAME=")); + QString typeKey(QLatin1String("TYPE=")); + QString hostKey(QLatin1String("HOSTNAME=")); + QString hostPrinterKey(QLatin1String("HOSTPRINTER=")); + + while (!configFile.atEnd() && + (configFile.readLine(line.data(), 1024)) > 0) { + QString uline = QString::fromLocal8Bit(line); + if (uline.startsWith(typeKey) ) { + printerType = uline.mid(nameKey.length()); + printerType = printerType.simplified(); + } else if (uline.startsWith(hostKey)) { + hostName = uline.mid(hostKey.length()); + hostName = hostName.simplified(); + } else if (uline.startsWith(hostPrinterKey)) { + hostPrinter = uline.mid(hostPrinterKey.length()); + hostPrinter = hostPrinter.simplified(); + } else if (uline.startsWith(nameKey)) { + namePrinter = uline.mid(nameKey.length()); + namePrinter = namePrinter.simplified(); + } + } + configFile.close(); + + printerType = printerType.trimmed(); + if (printerType.indexOf(QLatin1String("postscript"), 0, Qt::CaseInsensitive) < 0) + continue; + + int ii = 0; + while ((ii = namePrinter.indexOf(QLatin1Char('"'), ii)) >= 0) + namePrinter.remove(ii, 1); + + if (hostName.isEmpty() || hostPrinter.isEmpty()) { + qt_perhapsAddPrinter(printers, printer.fileName(), + QLatin1String(""), namePrinter); + } else { + QString comment; + comment = namePrinter; + comment += QLatin1String(" ("); + comment += hostPrinter; + comment += QLatin1Char(')'); + qt_perhapsAddPrinter(printers, printer.fileName(), + hostName, comment); + } + } +} + + +// Every unix must have its own. It's a standard. Here is AIX. +void qt_parseQconfig(QList<QPrinterDescription> *printers) +{ + QFile qconfig(QLatin1String("/etc/qconfig")); + if (!qconfig.open(QIODevice::ReadOnly)) + return; + + QTextStream ts(&qconfig); + QString line; + + QString stanzaName; // either a queue or a device name + bool up = true; // queue up? default true, can be false + QString remoteHost; // null if local + QString deviceName; // null if remote + + QRegExp newStanza(QLatin1String("^[0-z\\-]*:$")); + + // our basic strategy here is to process each line, detecting new + // stanzas. each time we see a new stanza, we check if the + // previous stanza was a valid queue for a) a remote printer or b) + // a local printer. if it wasn't, we assume that what we see is + // the start of the first stanza, or that the previous stanza was + // a device stanza, or that there is some syntax error (we don't + // report those). + + do { + line = ts.readLine(); + bool indented = line[0].isSpace(); + line = line.simplified(); + + int i = line.indexOf(QLatin1Char('=')); + if (indented && i != -1) { // line in stanza + QString variable = line.left(i).simplified(); + QString value=line.mid(i+1, line.length()).simplified(); + if (variable == QLatin1String("device")) + deviceName = value; + else if (variable == QLatin1String("host")) + remoteHost = value; + else if (variable == QLatin1String("up")) + up = !(value.toLower() == QLatin1String("false")); + } else if (line[0] == QLatin1Char('*')) { // comment + // nothing to do + } else if (ts.atEnd() || // end of file, or beginning of new stanza + (!indented && line.contains(newStanza))) { + if (up && stanzaName.length() > 0 && stanzaName.length() < 21) { + if (remoteHost.length()) // remote printer + qt_perhapsAddPrinter(printers, stanzaName, remoteHost, + QString()); + else if (deviceName.length()) // local printer + qt_perhapsAddPrinter(printers, stanzaName, QString(), + QString()); + } + line.chop(1); + if (line.length() >= 1 && line.length() <= 20) + stanzaName = line; + up = true; + remoteHost.clear(); + deviceName.clear(); + } else { + // syntax error? ignore. + } + } while (!ts.atEnd()); +} + +int qt_getLprPrinters(QList<QPrinterDescription>& printers) +{ + QByteArray etcLpDefault; + qt_parsePrintcap(&printers, QLatin1String("/etc/printcap")); + qt_parseEtcLpMember(&printers); + qt_parseSpoolInterface(&printers); + qt_parseQconfig(&printers); + + QFileInfo f; + f.setFile(QLatin1String("/etc/lp/printers")); + if (f.isDir()) { + qt_parseEtcLpPrinters(&printers); + QFile def(QLatin1String("/etc/lp/default")); + if (def.open(QIODevice::ReadOnly)) { + etcLpDefault.resize(1025); + if (def.readLine(etcLpDefault.data(), 1024) > 0) { + QRegExp rx(QLatin1String("^(\\S+)")); + if (rx.indexIn(QString::fromLatin1(etcLpDefault)) != -1) + etcLpDefault = rx.cap(1).toAscii(); + } + } + } + + char *def = 0; + f.setFile(QLatin1String("/etc/nsswitch.conf")); + if (f.isFile()) { + def = qt_parseNsswitchConf(&printers); + } else { + f.setFile(QLatin1String("/etc/printers.conf")); + if (f.isFile()) + def = qt_parsePrintersConf(&printers); + } + + if (def) { + etcLpDefault = def; + delete [] def; + } + + QString homePrintersDefault = qt_getDefaultFromHomePrinters(); + + // all printers hopefully known. try to find a good default + QString dollarPrinter; + { + dollarPrinter = QString::fromLocal8Bit(qgetenv("PRINTER")); + if (dollarPrinter.isEmpty()) + dollarPrinter = QString::fromLocal8Bit(qgetenv("LPDEST")); + if (dollarPrinter.isEmpty()) + dollarPrinter = QString::fromLocal8Bit(qgetenv("NPRINTER")); + if (dollarPrinter.isEmpty()) + dollarPrinter = QString::fromLocal8Bit(qgetenv("NGPRINTER")); +#ifndef QT_NO_PRINTDIALOG + if (!dollarPrinter.isEmpty()) + qt_perhapsAddPrinter(&printers, dollarPrinter, + QPrintDialog::tr("unknown"), + QLatin1String("")); +#endif + } + + QRegExp ps(QLatin1String("[^a-z]ps(?:[^a-z]|$)")); + QRegExp lp(QLatin1String("[^a-z]lp(?:[^a-z]|$)")); + + int quality = 0; + int best = 0; + for (int i = 0; i < printers.size(); ++i) { + QString name = printers.at(i).name; + QString comment = printers.at(i).comment; + if (quality < 5 && name == dollarPrinter) { + best = i; + quality = 5; + } else if (quality < 4 && !homePrintersDefault.isEmpty() && + name == homePrintersDefault) { + best = i; + quality = 4; + } else if (quality < 3 && !etcLpDefault.isEmpty() && + name == QLatin1String(etcLpDefault)) { + best = i; + quality = 3; + } else if (quality < 2 && + (name == QLatin1String("ps") || + ps.indexIn(comment) != -1)) { + best = i; + quality = 2; + } else if (quality < 1 && + (name == QLatin1String("lp") || + lp.indexIn(comment) > -1)) { + best = i; + quality = 1; + } + } + + return best; +} + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +QList<QPrinterInfo> QPrinterInfo::availablePrinters() +{ + QList<QPrinterInfo> printers; + +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + if (QCUPSSupport::isAvailable()) { + QCUPSSupport cups; + int cupsPrinterCount = cups.availablePrintersCount(); + const cups_dest_t* cupsPrinters = cups.availablePrinters(); + for (int i = 0; i < cupsPrinterCount; ++i) { + QString printerName(QString::fromLocal8Bit(cupsPrinters[i].name)); + if (cupsPrinters[i].instance) + printerName += QLatin1Char('/') + QString::fromLocal8Bit(cupsPrinters[i].instance); + + QPrinterInfo printerInfo(printerName); + if (cupsPrinters[i].is_default) + printerInfo.d_ptr->isDefault = true; + printerInfo.d_ptr->cupsPrinterIndex = i; + printers.append(printerInfo); + } + } else +#endif + { + QList<QPrinterDescription> lprPrinters; + int defprn = qt_getLprPrinters(lprPrinters); + // populating printer combo + foreach (const QPrinterDescription &description, lprPrinters) + printers.append(QPrinterInfo(description.name)); + if (defprn >= 0 && defprn < printers.size()) + printers[defprn].d_ptr->isDefault = true; + } + + return printers; +} + +QPrinterInfo QPrinterInfo::defaultPrinter() +{ + QList<QPrinterInfo> printers = availablePrinters(); + foreach (const QPrinterInfo &printerInfo, printers) { + if (printerInfo.isDefault()) + return printerInfo; + } + + return printers.value(0); +} + +QList<QPrinter::PaperSize> QPrinterInfo::supportedPaperSizes() const +{ +#if !defined(QT_NO_CUPS) && !defined(QT_NO_LIBRARY) + const Q_D(QPrinterInfo); + + if (isNull()) + return d->paperSizes; + + if (!d->hasPaperSizes) { + d->hasPaperSizes = true; + + if (QCUPSSupport::isAvailable()) { + // Find paper sizes from CUPS. + QCUPSSupport cups; + cups.setCurrentPrinter(d->cupsPrinterIndex); + const ppd_option_t* sizes = cups.pageSizes(); + if (sizes) { + for (int j = 0; j < sizes->num_choices; ++j) + d->paperSizes.append(string2PaperSize(sizes->choices[j].choice)); + } + } + } + + return d->paperSizes; +#else + return QList<QPrinter::PaperSize>(); +#endif +} + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qprinterinfo_unix_p.h b/src/gui/painting/qprinterinfo_unix_p.h new file mode 100644 index 0000000000..c5a62cefcd --- /dev/null +++ b/src/gui/painting/qprinterinfo_unix_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPRINTERINFO_UNIX_P_H +#define QPRINTERINFO_UNIX_P_H + +#ifndef QT_NO_NIS +# ifndef BOOL_DEFINED +# define BOOL_DEFINED +# endif + +# include <sys/types.h> +# include <rpc/rpc.h> +# include <rpcsvc/ypclnt.h> +# include <rpcsvc/yp_prot.h> +#endif // QT_NO_NIS + +#ifdef Success +# undef Success +#endif + +#include <ctype.h> + +QT_BEGIN_NAMESPACE + +// +// 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. +// + +#ifndef QT_NO_PRINTER + +struct QPrinterDescription { + QPrinterDescription(const QString &n, const QString &h, const QString &c, const QStringList &a) + : name(n), host(h), comment(c), aliases(a) {} + QString name; + QString host; + QString comment; + QStringList aliases; + bool samePrinter(const QString& printer) const { + return name == printer || aliases.contains(printer); + } +}; + +enum { Success = 's', Unavail = 'u', NotFound = 'n', TryAgain = 't' }; +enum { Continue = 'c', Return = 'r' }; + +void qt_perhapsAddPrinter(QList<QPrinterDescription> *printers, const QString &name, + QString host, QString comment, + QStringList aliases = QStringList()); +void qt_parsePrinterDesc(QString printerDesc, QList<QPrinterDescription> *printers); + +int qt_parsePrintcap(QList<QPrinterDescription> *printers, const QString& fileName); +QString qt_getDefaultFromHomePrinters(); +void qt_parseEtcLpPrinters(QList<QPrinterDescription> *printers); +char *qt_parsePrintersConf(QList<QPrinterDescription> *printers, bool *found = 0); + +#ifndef QT_NO_NIS +#if defined(Q_C_CALLBACKS) +extern "C" { +#endif +int qt_pd_foreach(int /*status */, char * /*key */, int /*keyLen */, + char *val, int valLen, char *data); + +#if defined(Q_C_CALLBACKS) +} +#endif +int qt_retrieveNisPrinters(QList<QPrinterDescription> *printers); +#endif // QT_NO_NIS +char *qt_parseNsswitchPrintersEntry(QList<QPrinterDescription> *printers, char *line); +char *qt_parseNsswitchConf(QList<QPrinterDescription> *printers); +void qt_parseEtcLpMember(QList<QPrinterDescription> *printers); +void qt_parseSpoolInterface(QList<QPrinterDescription> *printers); +void qt_parseQconfig(QList<QPrinterDescription> *printers); +int qt_getLprPrinters(QList<QPrinterDescription>& printers); + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE + +#endif // QPRINTERINFO_UNIX_P_H diff --git a/src/gui/painting/qprinterinfo_win.cpp b/src/gui/painting/qprinterinfo_win.cpp new file mode 100644 index 0000000000..2c4014d8dc --- /dev/null +++ b/src/gui/painting/qprinterinfo_win.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qprinterinfo.h" +#include "qprinterinfo_p.h" + +#include <qstringlist.h> + +#include <qt_windows.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +extern QPrinter::PaperSize mapDevmodePaperSize(int s); + +QList<QPrinterInfo> QPrinterInfo::availablePrinters() +{ + QList<QPrinterInfo> printers; + + DWORD needed = 0; + DWORD returned = 0; + if (!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 4, 0, 0, &needed, &returned)) { + LPBYTE buffer = new BYTE[needed]; + if (EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, NULL, 4, buffer, needed, &needed, &returned)) { + PPRINTER_INFO_4 infoList = reinterpret_cast<PPRINTER_INFO_4>(buffer); + QPrinterInfo defPrn = defaultPrinter(); + for (uint i = 0; i < returned; ++i) { + QString printerName(QString::fromWCharArray(infoList[i].pPrinterName)); + + QPrinterInfo printerInfo(printerName); + if (printerInfo.printerName() == defPrn.printerName()) + printerInfo.d_ptr->isDefault = true; + printers.append(printerInfo); + } + } + delete [] buffer; + } + + return printers; +} + +QPrinterInfo QPrinterInfo::defaultPrinter() +{ + QString noPrinters(QLatin1String("qt_no_printers")); + wchar_t buffer[256]; + GetProfileString(L"windows", L"device", (wchar_t*)noPrinters.utf16(), buffer, 256); + QString output = QString::fromWCharArray(buffer); + if (output != noPrinters) { + // Filter out the name of the printer, which should be everything before a comma. + QString printerName = output.split(QLatin1Char(',')).value(0); + QPrinterInfo printerInfo(printerName); + printerInfo.d_ptr->isDefault = true; + return printerInfo; + } + + return QPrinterInfo(); +} + +QList<QPrinter::PaperSize> QPrinterInfo::supportedPaperSizes() const +{ + const Q_D(QPrinterInfo); + + QList<QPrinter::PaperSize> paperSizes; + if (isNull()) + return paperSizes; + + DWORD size = DeviceCapabilities(reinterpret_cast<const wchar_t*>(d->name.utf16()), + NULL, DC_PAPERS, NULL, NULL); + if ((int)size != -1) { + wchar_t *papers = new wchar_t[size]; + size = DeviceCapabilities(reinterpret_cast<const wchar_t*>(d->name.utf16()), + NULL, DC_PAPERS, papers, NULL); + for (int c = 0; c < (int)size; ++c) + paperSizes.append(mapDevmodePaperSize(papers[c])); + delete [] papers; + } + + return paperSizes; +} + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qpsprinter.agl b/src/gui/painting/qpsprinter.agl new file mode 100644 index 0000000000..137b64c53b --- /dev/null +++ b/src/gui/painting/qpsprinter.agl @@ -0,0 +1,452 @@ +# the next table is derived from a list provided by Adobe on its web +# server: http://partners.adobe.com/asn/developer/typeforum/glyphlist.txt + +# the start of the header comment: +# +# Name: Adobe Glyph List +# Table version: 1.2 +# Date: 22 Oct 1998 +# +# Description: +# +# The Adobe Glyph List (AGL) list relates Unicode values (UVs) to glyph +# names, and should be used only as described in the document "Unicode and +# Glyph Names," at +# http://partners.adobe.com:80/asn/developer/type/unicodegn.html +# +# IMPORTANT NOTE: +# the list contains glyphs in the private use area of unicode. +# These should get removed when regenerating the glyphlist. +# +# also 0 should be mapped to .notdef +# +# grep '^[0-9A-F][0-9A-F][0-9A-F][0-9A-F];' < /tmp/glyphlist.txt | sed -e 's/;/, "/' -e 's-;-" }, // -' -e 's/^/ { 0x/' | sort +# +0x0000, ".notdef" +0x0020, "space" # SPACE +0x0021, "exclam" # EXCLAMATION MARK +0x0022, "quotedbl" # QUOTATION MARK +0x0023, "numbersign" # NUMBER SIGN +0x0024, "dollar" # DOLLAR SIGN +0x0025, "percent" # PERCENT SIGN +0x0026, "ampersand" # AMPERSAND +0x0027, "quotesingle" # APOSTROPHE +0x0028, "parenleft" # LEFT PARENTHESIS +0x0029, "parenright" # RIGHT PARENTHESIS +0x002A, "asterisk" # ASTERISK +0x002B, "plus" # PLUS SIGN +0x002C, "comma" # COMMA +0x002D, "hyphen" # HYPHEN-MINUS +0x002E, "period" # FULL STOP +0x002F, "slash" # SOLIDUS +0x0030, "zero" # DIGIT ZERO +0x0031, "one" # DIGIT ONE +0x0032, "two" # DIGIT TWO +0x0033, "three" # DIGIT THREE +0x0034, "four" # DIGIT FOUR +0x0035, "five" # DIGIT FIVE +0x0036, "six" # DIGIT SIX +0x0037, "seven" # DIGIT SEVEN +0x0038, "eight" # DIGIT EIGHT +0x0039, "nine" # DIGIT NINE +0x003A, "colon" # COLON +0x003B, "semicolon" # SEMICOLON +0x003C, "less" # LESS-THAN SIGN +0x003D, "equal" # EQUALS SIGN +0x003E, "greater" # GREATER-THAN SIGN +0x003F, "question" # QUESTION MARK +0x0040, "at" # COMMERCIAL AT +0x0041, "A" # LATIN CAPITAL LETTER A +0x0042, "B" # LATIN CAPITAL LETTER B +0x0043, "C" # LATIN CAPITAL LETTER C +0x0044, "D" # LATIN CAPITAL LETTER D +0x0045, "E" # LATIN CAPITAL LETTER E +0x0046, "F" # LATIN CAPITAL LETTER F +0x0047, "G" # LATIN CAPITAL LETTER G +0x0048, "H" # LATIN CAPITAL LETTER H +0x0049, "I" # LATIN CAPITAL LETTER I +0x004A, "J" # LATIN CAPITAL LETTER J +0x004B, "K" # LATIN CAPITAL LETTER K +0x004C, "L" # LATIN CAPITAL LETTER L +0x004D, "M" # LATIN CAPITAL LETTER M +0x004E, "N" # LATIN CAPITAL LETTER N +0x004F, "O" # LATIN CAPITAL LETTER O +0x0050, "P" # LATIN CAPITAL LETTER P +0x0051, "Q" # LATIN CAPITAL LETTER Q +0x0052, "R" # LATIN CAPITAL LETTER R +0x0053, "S" # LATIN CAPITAL LETTER S +0x0054, "T" # LATIN CAPITAL LETTER T +0x0055, "U" # LATIN CAPITAL LETTER U +0x0056, "V" # LATIN CAPITAL LETTER V +0x0057, "W" # LATIN CAPITAL LETTER W +0x0058, "X" # LATIN CAPITAL LETTER X +0x0059, "Y" # LATIN CAPITAL LETTER Y +0x005A, "Z" # LATIN CAPITAL LETTER Z +0x005B, "bracketleft" # LEFT SQUARE BRACKET +0x005C, "backslash" # REVERSE SOLIDUS +0x005D, "bracketright" # RIGHT SQUARE BRACKET +0x005E, "asciicircum" # CIRCUMFLEX ACCENT +0x005F, "underscore" # LOW LINE +0x0060, "grave" # GRAVE ACCENT +0x0061, "a" # LATIN SMALL LETTER A +0x0062, "b" # LATIN SMALL LETTER B +0x0063, "c" # LATIN SMALL LETTER C +0x0064, "d" # LATIN SMALL LETTER D +0x0065, "e" # LATIN SMALL LETTER E +0x0066, "f" # LATIN SMALL LETTER F +0x0067, "g" # LATIN SMALL LETTER G +0x0068, "h" # LATIN SMALL LETTER H +0x0069, "i" # LATIN SMALL LETTER I +0x006A, "j" # LATIN SMALL LETTER J +0x006B, "k" # LATIN SMALL LETTER K +0x006C, "l" # LATIN SMALL LETTER L +0x006D, "m" # LATIN SMALL LETTER M +0x006E, "n" # LATIN SMALL LETTER N +0x006F, "o" # LATIN SMALL LETTER O +0x0070, "p" # LATIN SMALL LETTER P +0x0071, "q" # LATIN SMALL LETTER Q +0x0072, "r" # LATIN SMALL LETTER R +0x0073, "s" # LATIN SMALL LETTER S +0x0074, "t" # LATIN SMALL LETTER T +0x0075, "u" # LATIN SMALL LETTER U +0x0076, "v" # LATIN SMALL LETTER V +0x0077, "w" # LATIN SMALL LETTER W +0x0078, "x" # LATIN SMALL LETTER X +0x0079, "y" # LATIN SMALL LETTER Y +0x007A, "z" # LATIN SMALL LETTER Z +0x007B, "braceleft" # LEFT CURLY BRACKET +0x007C, "bar" # VERTICAL LINE +0x007D, "braceright" # RIGHT CURLY BRACKET +0x007E, "asciitilde" # TILDE +0x00A0, "space" # NO-BREAK SPACE;Duplicate +0x00A1, "exclamdown" # INVERTED EXCLAMATION MARK +0x00A2, "cent" # CENT SIGN +0x00A3, "sterling" # POUND SIGN +0x00A4, "currency" # CURRENCY SIGN +0x00A5, "yen" # YEN SIGN +0x00A6, "brokenbar" # BROKEN BAR +0x00A7, "section" # SECTION SIGN +0x00A8, "dieresis" # DIAERESIS +0x00A9, "copyright" # COPYRIGHT SIGN +0x00AA, "ordfeminine" # FEMININE ORDINAL INDICATOR +0x00AB, "guillemotleft" # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +0x00AC, "logicalnot" # NOT SIGN +0x00AD, "hyphen" # SOFT HYPHEN;Duplicate +0x00AE, "registered" # REGISTERED SIGN +0x00AF, "macron" # MACRON +0x00B0, "degree" # DEGREE SIGN +0x00B1, "plusminus" # PLUS-MINUS SIGN +0x00B2, "twosuperior" # SUPERSCRIPT TWO +0x00B3, "threesuperior" # SUPERSCRIPT THREE +0x00B4, "acute" # ACUTE ACCENT +0x00B5, "mu" # MICRO SIGN +0x00B6, "paragraph" # PILCROW SIGN +0x00B7, "periodcentered" # MIDDLE DOT +0x00B8, "cedilla" # CEDILLA +0x00B9, "onesuperior" # SUPERSCRIPT ONE +0x00BA, "ordmasculine" # MASCULINE ORDINAL INDICATOR +0x00BB, "guillemotright" # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +0x00BC, "onequarter" # VULGAR FRACTION ONE QUARTER +0x00BD, "onehalf" # VULGAR FRACTION ONE HALF +0x00BE, "threequarters" # VULGAR FRACTION THREE QUARTERS +0x00BF, "questiondown" # INVERTED QUESTION MARK +0x00C0, "Agrave" # LATIN CAPITAL LETTER A WITH GRAVE +0x00C1, "Aacute" # LATIN CAPITAL LETTER A WITH ACUTE +0x00C2, "Acircumflex" # LATIN CAPITAL LETTER A WITH CIRCUMFLEX +0x00C3, "Atilde" # LATIN CAPITAL LETTER A WITH TILDE +0x00C4, "Adieresis" # LATIN CAPITAL LETTER A WITH DIAERESIS +0x00C5, "Aring" # LATIN CAPITAL LETTER A WITH RING ABOVE +0x00C6, "AE" # LATIN CAPITAL LETTER AE +0x00C7, "Ccedilla" # LATIN CAPITAL LETTER C WITH CEDILLA +0x00C8, "Egrave" # LATIN CAPITAL LETTER E WITH GRAVE +0x00C9, "Eacute" # LATIN CAPITAL LETTER E WITH ACUTE +0x00CA, "Ecircumflex" # LATIN CAPITAL LETTER E WITH CIRCUMFLEX +0x00CB, "Edieresis" # LATIN CAPITAL LETTER E WITH DIAERESIS +0x00CC, "Igrave" # LATIN CAPITAL LETTER I WITH GRAVE +0x00CD, "Iacute" # LATIN CAPITAL LETTER I WITH ACUTE +0x00CE, "Icircumflex" # LATIN CAPITAL LETTER I WITH CIRCUMFLEX +0x00CF, "Idieresis" # LATIN CAPITAL LETTER I WITH DIAERESIS +0x00D0, "Eth" # LATIN CAPITAL LETTER ETH +0x00D1, "Ntilde" # LATIN CAPITAL LETTER N WITH TILDE +0x00D2, "Ograve" # LATIN CAPITAL LETTER O WITH GRAVE +0x00D3, "Oacute" # LATIN CAPITAL LETTER O WITH ACUTE +0x00D4, "Ocircumflex" # LATIN CAPITAL LETTER O WITH CIRCUMFLEX +0x00D5, "Otilde" # LATIN CAPITAL LETTER O WITH TILDE +0x00D6, "Odieresis" # LATIN CAPITAL LETTER O WITH DIAERESIS +0x00D7, "multiply" # MULTIPLICATION SIGN +0x00D8, "Oslash" # LATIN CAPITAL LETTER O WITH STROKE +0x00D9, "Ugrave" # LATIN CAPITAL LETTER U WITH GRAVE +0x00DA, "Uacute" # LATIN CAPITAL LETTER U WITH ACUTE +0x00DB, "Ucircumflex" # LATIN CAPITAL LETTER U WITH CIRCUMFLEX +0x00DC, "Udieresis" # LATIN CAPITAL LETTER U WITH DIAERESIS +0x00DD, "Yacute" # LATIN CAPITAL LETTER Y WITH ACUTE +0x00DE, "Thorn" # LATIN CAPITAL LETTER THORN +0x00DF, "germandbls" # LATIN SMALL LETTER SHARP S +0x00E0, "agrave" # LATIN SMALL LETTER A WITH GRAVE +0x00E1, "aacute" # LATIN SMALL LETTER A WITH ACUTE +0x00E2, "acircumflex" # LATIN SMALL LETTER A WITH CIRCUMFLEX +0x00E3, "atilde" # LATIN SMALL LETTER A WITH TILDE +0x00E4, "adieresis" # LATIN SMALL LETTER A WITH DIAERESIS +0x00E5, "aring" # LATIN SMALL LETTER A WITH RING ABOVE +0x00E6, "ae" # LATIN SMALL LETTER AE +0x00E7, "ccedilla" # LATIN SMALL LETTER C WITH CEDILLA +0x00E8, "egrave" # LATIN SMALL LETTER E WITH GRAVE +0x00E9, "eacute" # LATIN SMALL LETTER E WITH ACUTE +0x00EA, "ecircumflex" # LATIN SMALL LETTER E WITH CIRCUMFLEX +0x00EB, "edieresis" # LATIN SMALL LETTER E WITH DIAERESIS +0x00EC, "igrave" # LATIN SMALL LETTER I WITH GRAVE +0x00ED, "iacute" # LATIN SMALL LETTER I WITH ACUTE +0x00EE, "icircumflex" # LATIN SMALL LETTER I WITH CIRCUMFLEX +0x00EF, "idieresis" # LATIN SMALL LETTER I WITH DIAERESIS +0x00F0, "eth" # LATIN SMALL LETTER ETH +0x00F1, "ntilde" # LATIN SMALL LETTER N WITH TILDE +0x00F2, "ograve" # LATIN SMALL LETTER O WITH GRAVE +0x00F3, "oacute" # LATIN SMALL LETTER O WITH ACUTE +0x00F4, "ocircumflex" # LATIN SMALL LETTER O WITH CIRCUMFLEX +0x00F5, "otilde" # LATIN SMALL LETTER O WITH TILDE +0x00F6, "odieresis" # LATIN SMALL LETTER O WITH DIAERESIS +0x00F7, "divide" # DIVISION SIGN +0x00F8, "oslash" # LATIN SMALL LETTER O WITH STROKE +0x00F9, "ugrave" # LATIN SMALL LETTER U WITH GRAVE +0x00FA, "uacute" # LATIN SMALL LETTER U WITH ACUTE +0x00FB, "ucircumflex" # LATIN SMALL LETTER U WITH CIRCUMFLEX +0x00FC, "udieresis" # LATIN SMALL LETTER U WITH DIAERESIS +0x00FD, "yacute" # LATIN SMALL LETTER Y WITH ACUTE +0x00FE, "thorn" # LATIN SMALL LETTER THORN +0x00FF, "ydieresis" # LATIN SMALL LETTER Y WITH DIAERESIS +0x0100, "Amacron" # LATIN CAPITAL LETTER A WITH MACRON +0x0101, "amacron" # LATIN SMALL LETTER A WITH MACRON +0x0102, "Abreve" # LATIN CAPITAL LETTER A WITH BREVE +0x0103, "abreve" # LATIN SMALL LETTER A WITH BREVE +0x0104, "Aogonek" # LATIN CAPITAL LETTER A WITH OGONEK +0x0105, "aogonek" # LATIN SMALL LETTER A WITH OGONEK +0x0106, "Cacute" # LATIN CAPITAL LETTER C WITH ACUTE +0x0107, "cacute" # LATIN SMALL LETTER C WITH ACUTE +0x0108, "Ccircumflex" # LATIN CAPITAL LETTER C WITH CIRCUMFLEX +0x0109, "ccircumflex" # LATIN SMALL LETTER C WITH CIRCUMFLEX +0x010A, "Cdotaccent" # LATIN CAPITAL LETTER C WITH DOT ABOVE +0x010B, "cdotaccent" # LATIN SMALL LETTER C WITH DOT ABOVE +0x010C, "Ccaron" # LATIN CAPITAL LETTER C WITH CARON +0x010D, "ccaron" # LATIN SMALL LETTER C WITH CARON +0x010E, "Dcaron" # LATIN CAPITAL LETTER D WITH CARON +0x010F, "dcaron" # LATIN SMALL LETTER D WITH CARON +0x0110, "Dcroat" # LATIN CAPITAL LETTER D WITH STROKE +0x0111, "dcroat" # LATIN SMALL LETTER D WITH STROKE +0x0112, "Emacron" # LATIN CAPITAL LETTER E WITH MACRON +0x0113, "emacron" # LATIN SMALL LETTER E WITH MACRON +0x0114, "Ebreve" # LATIN CAPITAL LETTER E WITH BREVE +0x0115, "ebreve" # LATIN SMALL LETTER E WITH BREVE +0x0116, "Edotaccent" # LATIN CAPITAL LETTER E WITH DOT ABOVE +0x0117, "edotaccent" # LATIN SMALL LETTER E WITH DOT ABOVE +0x0118, "Eogonek" # LATIN CAPITAL LETTER E WITH OGONEK +0x0119, "eogonek" # LATIN SMALL LETTER E WITH OGONEK +0x011A, "Ecaron" # LATIN CAPITAL LETTER E WITH CARON +0x011B, "ecaron" # LATIN SMALL LETTER E WITH CARON +0x011C, "Gcircumflex" # LATIN CAPITAL LETTER G WITH CIRCUMFLEX +0x011D, "gcircumflex" # LATIN SMALL LETTER G WITH CIRCUMFLEX +0x011E, "Gbreve" # LATIN CAPITAL LETTER G WITH BREVE +0x011F, "gbreve" # LATIN SMALL LETTER G WITH BREVE +0x0120, "Gdotaccent" # LATIN CAPITAL LETTER G WITH DOT ABOVE +0x0121, "gdotaccent" # LATIN SMALL LETTER G WITH DOT ABOVE +0x0122, "Gcommaaccent" # LATIN CAPITAL LETTER G WITH CEDILLA +0x0123, "gcommaaccent" # LATIN SMALL LETTER G WITH CEDILLA +0x0124, "Hcircumflex" # LATIN CAPITAL LETTER H WITH CIRCUMFLEX +0x0125, "hcircumflex" # LATIN SMALL LETTER H WITH CIRCUMFLEX +0x0126, "Hbar" # LATIN CAPITAL LETTER H WITH STROKE +0x0127, "hbar" # LATIN SMALL LETTER H WITH STROKE +0x0128, "Itilde" # LATIN CAPITAL LETTER I WITH TILDE +0x0129, "itilde" # LATIN SMALL LETTER I WITH TILDE +0x012A, "Imacron" # LATIN CAPITAL LETTER I WITH MACRON +0x012B, "imacron" # LATIN SMALL LETTER I WITH MACRON +0x012C, "Ibreve" # LATIN CAPITAL LETTER I WITH BREVE +0x012D, "ibreve" # LATIN SMALL LETTER I WITH BREVE +0x012E, "Iogonek" # LATIN CAPITAL LETTER I WITH OGONEK +0x012F, "iogonek" # LATIN SMALL LETTER I WITH OGONEK +0x0130, "Idotaccent" # LATIN CAPITAL LETTER I WITH DOT ABOVE +0x0131, "dotlessi" # LATIN SMALL LETTER DOTLESS I +0x0132, "IJ" # LATIN CAPITAL LIGATURE IJ +0x0133, "ij" # LATIN SMALL LIGATURE IJ +0x0134, "Jcircumflex" # LATIN CAPITAL LETTER J WITH CIRCUMFLEX +0x0135, "jcircumflex" # LATIN SMALL LETTER J WITH CIRCUMFLEX +0x0136, "Kcommaaccent" # LATIN CAPITAL LETTER K WITH CEDILLA +0x0137, "kcommaaccent" # LATIN SMALL LETTER K WITH CEDILLA +0x0138, "kgreenlandic" # LATIN SMALL LETTER KRA +0x0139, "Lacute" # LATIN CAPITAL LETTER L WITH ACUTE +0x013A, "lacute" # LATIN SMALL LETTER L WITH ACUTE +0x013B, "Lcommaaccent" # LATIN CAPITAL LETTER L WITH CEDILLA +0x013C, "lcommaaccent" # LATIN SMALL LETTER L WITH CEDILLA +0x013D, "Lcaron" # LATIN CAPITAL LETTER L WITH CARON +0x013E, "lcaron" # LATIN SMALL LETTER L WITH CARON +0x013F, "Ldot" # LATIN CAPITAL LETTER L WITH MIDDLE DOT +0x0140, "ldot" # LATIN SMALL LETTER L WITH MIDDLE DOT +0x0141, "Lslash" # LATIN CAPITAL LETTER L WITH STROKE +0x0142, "lslash" # LATIN SMALL LETTER L WITH STROKE +0x0143, "Nacute" # LATIN CAPITAL LETTER N WITH ACUTE +0x0144, "nacute" # LATIN SMALL LETTER N WITH ACUTE +0x0145, "Ncommaaccent" # LATIN CAPITAL LETTER N WITH CEDILLA +0x0146, "ncommaaccent" # LATIN SMALL LETTER N WITH CEDILLA +0x0147, "Ncaron" # LATIN CAPITAL LETTER N WITH CARON +0x0148, "ncaron" # LATIN SMALL LETTER N WITH CARON +0x0149, "napostrophe" # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +0x014A, "Eng" # LATIN CAPITAL LETTER ENG +0x014B, "eng" # LATIN SMALL LETTER ENG +0x014C, "Omacron" # LATIN CAPITAL LETTER O WITH MACRON +0x014D, "omacron" # LATIN SMALL LETTER O WITH MACRON +0x014E, "Obreve" # LATIN CAPITAL LETTER O WITH BREVE +0x014F, "obreve" # LATIN SMALL LETTER O WITH BREVE +0x0150, "Ohungarumlaut" # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +0x0151, "ohungarumlaut" # LATIN SMALL LETTER O WITH DOUBLE ACUTE +0x0152, "OE" # LATIN CAPITAL LIGATURE OE +0x0153, "oe" # LATIN SMALL LIGATURE OE +0x0154, "Racute" # LATIN CAPITAL LETTER R WITH ACUTE +0x0155, "racute" # LATIN SMALL LETTER R WITH ACUTE +0x0156, "Rcommaaccent" # LATIN CAPITAL LETTER R WITH CEDILLA +0x0157, "rcommaaccent" # LATIN SMALL LETTER R WITH CEDILLA +0x0158, "Rcaron" # LATIN CAPITAL LETTER R WITH CARON +0x0159, "rcaron" # LATIN SMALL LETTER R WITH CARON +0x015A, "Sacute" # LATIN CAPITAL LETTER S WITH ACUTE +0x015B, "sacute" # LATIN SMALL LETTER S WITH ACUTE +0x015C, "Scircumflex" # LATIN CAPITAL LETTER S WITH CIRCUMFLEX +0x015D, "scircumflex" # LATIN SMALL LETTER S WITH CIRCUMFLEX +0x015E, "Scedilla" # LATIN CAPITAL LETTER S WITH CEDILLA +0x015F, "scedilla" # LATIN SMALL LETTER S WITH CEDILLA +0x0160, "Scaron" # LATIN CAPITAL LETTER S WITH CARON +0x0161, "scaron" # LATIN SMALL LETTER S WITH CARON +0x0164, "Tcaron" # LATIN CAPITAL LETTER T WITH CARON +0x0165, "tcaron" # LATIN SMALL LETTER T WITH CARON +0x0166, "Tbar" # LATIN CAPITAL LETTER T WITH STROKE +0x0167, "tbar" # LATIN SMALL LETTER T WITH STROKE +0x0168, "Utilde" # LATIN CAPITAL LETTER U WITH TILDE +0x0169, "utilde" # LATIN SMALL LETTER U WITH TILDE +0x016A, "Umacron" # LATIN CAPITAL LETTER U WITH MACRON +0x016B, "umacron" # LATIN SMALL LETTER U WITH MACRON +0x016C, "Ubreve" # LATIN CAPITAL LETTER U WITH BREVE +0x016D, "ubreve" # LATIN SMALL LETTER U WITH BREVE +0x016E, "Uring" # LATIN CAPITAL LETTER U WITH RING ABOVE +0x016F, "uring" # LATIN SMALL LETTER U WITH RING ABOVE +0x0170, "Uhungarumlaut" # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +0x0171, "uhungarumlaut" # LATIN SMALL LETTER U WITH DOUBLE ACUTE +0x0172, "Uogonek" # LATIN CAPITAL LETTER U WITH OGONEK +0x0173, "uogonek" # LATIN SMALL LETTER U WITH OGONEK +0x0174, "Wcircumflex" # LATIN CAPITAL LETTER W WITH CIRCUMFLEX +0x0175, "wcircumflex" # LATIN SMALL LETTER W WITH CIRCUMFLEX +0x0176, "Ycircumflex" # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +0x0177, "ycircumflex" # LATIN SMALL LETTER Y WITH CIRCUMFLEX +0x0178, "Ydieresis" # LATIN CAPITAL LETTER Y WITH DIAERESIS +0x0179, "Zacute" # LATIN CAPITAL LETTER Z WITH ACUTE +0x017A, "zacute" # LATIN SMALL LETTER Z WITH ACUTE +0x017B, "Zdotaccent" # LATIN CAPITAL LETTER Z WITH DOT ABOVE +0x017C, "zdotaccent" # LATIN SMALL LETTER Z WITH DOT ABOVE +0x017D, "Zcaron" # LATIN CAPITAL LETTER Z WITH CARON +0x017E, "zcaron" # LATIN SMALL LETTER Z WITH CARON +0x017F, "longs" # LATIN SMALL LETTER LONG S +0x0192, "florin" # LATIN SMALL LETTER F WITH HOOK +0x01A0, "Ohorn" # LATIN CAPITAL LETTER O WITH HORN +0x01A1, "ohorn" # LATIN SMALL LETTER O WITH HORN +0x01AF, "Uhorn" # LATIN CAPITAL LETTER U WITH HORN +0x01B0, "uhorn" # LATIN SMALL LETTER U WITH HORN +0x01E6, "Gcaron" # LATIN CAPITAL LETTER G WITH CARON +0x01E7, "gcaron" # LATIN SMALL LETTER G WITH CARON +0x01FA, "Aringacute" # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE +0x01FB, "aringacute" # LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE +0x01FC, "AEacute" # LATIN CAPITAL LETTER AE WITH ACUTE +0x01FD, "aeacute" # LATIN SMALL LETTER AE WITH ACUTE +0x01FE, "Oslashacute" # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE +0x01FF, "oslashacute" # LATIN SMALL LETTER O WITH STROKE AND ACUTE +0x0218, "Scommaaccent" # LATIN CAPITAL LETTER S WITH COMMA BELOW +0x0219, "scommaaccent" # LATIN SMALL LETTER S WITH COMMA BELOW +0x021A, "Tcommaaccent" # LATIN CAPITAL LETTER T WITH COMMA BELOW +0x021B, "tcommaaccent" # LATIN SMALL LETTER T WITH COMMA BELOW +0x02BC, "afii57929" # MODIFIER LETTER APOSTROPHE +0x02BD, "afii64937" # MODIFIER LETTER REVERSED COMMA +0x02C6, "circumflex" # MODIFIER LETTER CIRCUMFLEX ACCENT +0x02C7, "caron" # CARON +0x02D8, "breve" # BREVE +0x02D9, "dotaccent" # DOT ABOVE +0x02DA, "ring" # RING ABOVE +0x02DB, "ogonek" # OGONEK +0x02DC, "tilde" # SMALL TILDE +0x02DD, "hungarumlaut" # DOUBLE ACUTE ACCENT +0x0300, "gravecomb" # COMBINING GRAVE ACCENT +0x0301, "acutecomb" # COMBINING ACUTE ACCENT +0x0303, "tildecomb" # COMBINING TILDE +0x0309, "hookabovecomb" # COMBINING HOOK ABOVE +0x0323, "dotbelowcomb" # COMBINING DOT BELOW +0x0384, "tonos" # GREEK TONOS +0x0385, "dieresistonos" # GREEK DIALYTIKA TONOS +0x0386, "Alphatonos" # GREEK CAPITAL LETTER ALPHA WITH TONOS +0x0387, "anoteleia" # GREEK ANO TELEIA +0x0388, "Epsilontonos" # GREEK CAPITAL LETTER EPSILON WITH TONOS +0x0389, "Etatonos" # GREEK CAPITAL LETTER ETA WITH TONOS +0x038A, "Iotatonos" # GREEK CAPITAL LETTER IOTA WITH TONOS +0x038C, "Omicrontonos" # GREEK CAPITAL LETTER OMICRON WITH TONOS +0x038E, "Upsilontonos" # GREEK CAPITAL LETTER UPSILON WITH TONOS +0x038F, "Omegatonos" # GREEK CAPITAL LETTER OMEGA WITH TONOS +0x0390, "iotadieresistonos" # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +0x0391, "Alpha" # GREEK CAPITAL LETTER ALPHA +0x0392, "Beta" # GREEK CAPITAL LETTER BETA +0x0393, "Gamma" # GREEK CAPITAL LETTER GAMMA +0x0394, "Delta" # GREEK CAPITAL LETTER DELTA +0x0395, "Epsilon" # GREEK CAPITAL LETTER EPSILON +0x0396, "Zeta" # GREEK CAPITAL LETTER ZETA +0x0397, "Eta" # GREEK CAPITAL LETTER ETA +0x0398, "Theta" # GREEK CAPITAL LETTER THETA +0x0399, "Iota" # GREEK CAPITAL LETTER IOTA +0x039A, "Kappa" # GREEK CAPITAL LETTER KAPPA +0x039B, "Lambda" # GREEK CAPITAL LETTER LAMDA +0x039C, "Mu" # GREEK CAPITAL LETTER MU +0x039D, "Nu" # GREEK CAPITAL LETTER NU +0x039E, "Xi" # GREEK CAPITAL LETTER XI +0x039F, "Omicron" # GREEK CAPITAL LETTER OMICRON +0x03A0, "Pi" # GREEK CAPITAL LETTER PI +0x03A1, "Rho" # GREEK CAPITAL LETTER RHO +0x03A3, "Sigma" # GREEK CAPITAL LETTER SIGMA +0x03A4, "Tau" # GREEK CAPITAL LETTER TAU +0x03A5, "Upsilon" # GREEK CAPITAL LETTER UPSILON +0x03A6, "Phi" # GREEK CAPITAL LETTER PHI +0x03A7, "Chi" # GREEK CAPITAL LETTER CHI +0x03A8, "Psi" # GREEK CAPITAL LETTER PSI +0x03A9, "Omega" # GREEK CAPITAL LETTER OMEGA +0x03AA, "Iotadieresis" # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA +0x03AB, "Upsilondieresis" # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +0x03AC, "alphatonos" # GREEK SMALL LETTER ALPHA WITH TONOS +0x03AD, "epsilontonos" # GREEK SMALL LETTER EPSILON WITH TONOS +0x03AE, "etatonos" # GREEK SMALL LETTER ETA WITH TONOS +0x03AF, "iotatonos" # GREEK SMALL LETTER IOTA WITH TONOS +0x03B0, "upsilondieresistonos" # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +0x03B1, "alpha" # GREEK SMALL LETTER ALPHA +0x03B2, "beta" # GREEK SMALL LETTER BETA +0x03B3, "gamma" # GREEK SMALL LETTER GAMMA +0x03B4, "delta" # GREEK SMALL LETTER DELTA +0x03B5, "epsilon" # GREEK SMALL LETTER EPSILON +0x03B6, "zeta" # GREEK SMALL LETTER ZETA +0x03B7, "eta" # GREEK SMALL LETTER ETA +0x03B8, "theta" # GREEK SMALL LETTER THETA +0x03B9, "iota" # GREEK SMALL LETTER IOTA +0x03BA, "kappa" # GREEK SMALL LETTER KAPPA +0x03BB, "lambda" # GREEK SMALL LETTER LAMDA +0x03BC, "mu" # GREEK SMALL LETTER MU;Duplicate +0x03BD, "nu" # GREEK SMALL LETTER NU +0x03BE, "xi" # GREEK SMALL LETTER XI +0x03BF, "omicron" # GREEK SMALL LETTER OMICRON +0x03C0, "pi" # GREEK SMALL LETTER PI +0x03C1, "rho" # GREEK SMALL LETTER RHO +0x03C2, "sigma1" # GREEK SMALL LETTER FINAL SIGMA +0x03C3, "sigma" # GREEK SMALL LETTER SIGMA +0x03C4, "tau" # GREEK SMALL LETTER TAU +0x03C5, "upsilon" # GREEK SMALL LETTER UPSILON +0x03C6, "phi" # GREEK SMALL LETTER PHI +0x03C7, "chi" # GREEK SMALL LETTER CHI +0x03C8, "psi" # GREEK SMALL LETTER PSI +0x03C9, "omega" # GREEK SMALL LETTER OMEGA +0x03CA, "iotadieresis" # GREEK SMALL LETTER IOTA WITH DIALYTIKA +0x03CB, "upsilondieresis" # GREEK SMALL LETTER UPSILON WITH DIALYTIKA +0x03CC, "omicrontonos" # GREEK SMALL LETTER OMICRON WITH TONOS +0x03CD, "upsilontonos" # GREEK SMALL LETTER UPSILON WITH TONOS +0x03CE, "omegatonos" # GREEK SMALL LETTER OMEGA WITH TONOS +0x03D1, "theta1" # GREEK THETA SYMBOL +0x03D2, "Upsilon1" # GREEK UPSILON WITH HOOK SYMBOL +0x03D5, "phi1" # GREEK PHI SYMBOL +0x03D6, "omega1" # GREEK PI SYMBOL +# end of stuff from glyphlist.txt +0xFFFF, "" diff --git a/src/gui/painting/qpsprinter.ps b/src/gui/painting/qpsprinter.ps new file mode 100644 index 0000000000..ef3f42970b --- /dev/null +++ b/src/gui/painting/qpsprinter.ps @@ -0,0 +1,449 @@ +% the postscript header we use for our qpsprinter in uncompressed and commented form. +% use the makepsheader perl script to generate a compressed version of this header +% you can then paste into qpsprinter.cpp +% +% some compression of the code is done by the makepsheader script, so we don't need to +% write too criptically here. + +/BD {bind def} bind def +/d2 {dup dup} BD +/ED {exch def} BD +/D0 {0 ED} BD + +/F {setfont} BD +/RL {rlineto} BD +/CM {currentmatrix} BD +/SM {setmatrix} BD +/TR {translate} BD +/SD {setdash} BD +/SC {aload pop setrgbcolor} BD +/CR {currentfile read pop} BD +/i {index} BD +/scs {setcolorspace} BD +/DB {dict dup begin} BD +/DE {end def} BD +/ie {ifelse} BD +/gs {gsave} BD +/gr {grestore} BD + +% these use PDF syntax +/w {setlinewidth} BD +/d {setdash} BD +/J {setlinecap} BD +/j {setlinejoin} BD +/scn {3 array astore /BCol exch def} BD +/SCN {3 array astore /PCol exch def} BD +/cm {6 array astore concat} BD + +/m {moveto} BD +/l {lineto} BD +/c {curveto} BD +/h {closepath} BD + +/W {clip} BD +/W* {eoclip} BD +/n {newpath} BD +% ENDUNCOMPRESSED: Warning: leave this line in. +% Everything before this line will be left untouched by the compression + +/q {gsave 10 dict begin} BD +/Q {end grestore} BD + +% PDF operators +/re { % PDF re operator + 4 2 roll % w h x y + moveto % w h + dup % w h h + 0 exch rlineto % w h + exch 0 rlineto % h + 0 exch neg rlineto + closepath +} bind def + +/S { + gsave + PCol SC stroke + grestore + newpath +} BD + +% PDF text operators +/BT {gsave 10 dict begin /_m matrix currentmatrix def BCol SC} BD +/ET {end grestore} BD +/Tf { + /_fs exch def + findfont + [ _fs 0 0 _fs 0 0 ] + makefont + setfont +} BD +/Tm {6 array astore concat} BD +/Td {translate} BD +/Tj {0 0 moveto show} BD +/BDC {pop pop} BD +/EMC {} BD + +% old operators + +/BSt 0 def % brush style +/WFi false def % winding fill + +/BCol [ 1 1 1 ] def % brush color +/PCol [ 0 0 0 ] def % pen color +/BDArr [ % Brush dense patterns + 0.94 + 0.88 + 0.63 + 0.50 + 0.37 + 0.12 + 0.06 +] def + +% -- level3 true/false +/level3 { + /languagelevel where { + pop + languagelevel 3 ge + } { false } ifelse +} bind def + + +%% image drawing routines + +% defines for QCI +/QCIgray D0 /QCIcolor D0 /QCIindex D0 + +% this method prints color images if colorimage is available, otherwise +% converts the string to a grayscale image and uses the reular postscript image +% operator for printing. +% Arguments are the same as for the image operator: +% +% width height bits/sample matrix datasrc QCI - +/QCI { + /colorimage where { + pop + false 3 colorimage + }{ % the hard way, based on PD code by John Walker <kelvin@autodesk.com> + exec /QCIcolor exch def + /QCIgray QCIcolor length 3 idiv string def + 0 1 QCIcolor length 3 idiv 1 sub + { /QCIindex exch def + /_x QCIindex 3 mul def + QCIgray QCIindex + QCIcolor _x get 0.30 mul + QCIcolor _x 1 add get 0.59 mul + QCIcolor _x 2 add get 0.11 mul + add add cvi + put + } for + QCIgray image + } ifelse +} bind def + +% general image drawing routine, used from the postscript driver +% +% Draws images with and without mask with 1, 8 and 24(rgb) bits depth. +% +% width height matrix image 1|8|24 mask|false x y di +% +% width and height specify the width/height of the image, +% matrix a transformation matrix, image a procedure holding the image data +% (same for mask) and x/y an additional translation. +% +% ### should move the translation into the matrix!!! +/di +{ + gsave + translate + 1 index 1 eq { % bitmap + pop pop % get rid of mask and depth + false 3 1 roll % width height false matrix image + BCol SC + imagemask + } { + dup false ne { + % have a mask, see if we can use it + level3 + } { + false + } ifelse + + { + % languagelevel3, we can use image mask and dicts + + % store the image mask + /_ma exch def + % select colorspace according to 8|24 bit depth and set the decode array /dc + 8 eq { + /_dc [0 1] def + /DeviceGray + } { + /_dc [0 1 0 1 0 1] def + /DeviceRGB + } ifelse + setcolorspace + % the image data + /_im exch def + % transformation matrix + /_mt exch def + % width and height + /_h exch def + /_w exch def + % and the combined image dict + << + /ImageType 3 + % the image dict + /DataDict << + /ImageType 1 + /Width _w + /Height _h + /ImageMatrix _mt + /DataSource _im + /BitsPerComponent 8 + /Decode _dc + >> + % the mask dictionary + /MaskDict << + /ImageType 1 + /Width _w + /Height _h + /ImageMatrix _mt + /DataSource _ma + /BitsPerComponent 1 + /Decode [0 1] + >> + /InterleaveType 3 + >> + image + } { + pop % no mask or can't use it, get rid of it + 8 % width height image 8|24 8 matrix + 4 1 roll + 8 eq { % grayscale + image + } { %color + QCI + } ifelse + } ifelse + } ifelse + grestore +} bind def + + +/BF { % brush fill + gsave + BSt 1 eq % solid brush? + { + BCol SC + WFi { fill } { eofill } ifelse + } if + BSt 2 ge BSt 8 le and % dense pattern? + { + BDArr BSt 2 sub get /_sc exch def + % the following line scales the brush color according to the pattern. the higher the pattern the lighter the color. + BCol + { + 1. exch sub _sc mul 1. exch sub + } forall + 3 array astore + SC + WFi { fill } { eofill } ifelse + } if + BSt 9 ge BSt 14 le and % brush pattern? + { + WFi { clip } { eoclip } ifelse + pathbbox % left upper right lower + 3 index 3 index translate + 4 2 roll % right lower left upper + 3 2 roll % right left upper lower + exch % left right lower upper + sub /_h exch def + sub /_w exch def + BCol SC + 0.3 setlinewidth + newpath + BSt 9 eq BSt 11 eq or % horiz or cross pattern + { 0 4 _h + { dup 0 exch moveto _w exch lineto } for + } if + BSt 10 eq BSt 11 eq or % vert or cross pattern + { 0 4 _w + { dup 0 moveto _h lineto } for + } if + BSt 12 eq BSt 14 eq or % F-diag or diag cross + { _w _h gt + { 0 6 _w _h add + { dup 0 moveto _h sub _h lineto } for + } { 0 6 _w _h add + { dup 0 exch moveto _w sub _w exch lineto } for + } ifelse + } if + BSt 13 eq BSt 14 eq or % B-diag or diag cross + { _w _h gt + { 0 6 _w _h add + { dup _h moveto _h sub 0 lineto } for + } { 0 6 _w _h add + { dup _w exch moveto _w sub 0 exch lineto } for + } ifelse + } if + stroke + } if + BSt 15 eq + { + } if + BSt 24 eq % TexturePattern + { + } if + grestore +} bind def + +% more PDF operators +/f { /WFi true def BF newpath } bind def +/f* { /WFi false def BF newpath } bind def +/B { /WFi true def BF S newpath } bind def +/B* { /WFi false def BF S newpath } bind def + +%% start of page +/QI { + /C save def + pageinit + q + newpath +} bind def + +%% end of page +/QP { + Q % show page + C restore + showpage +} bind def + +% merges one key value pair into the page device dict +% +% key value SPD - +/SPD { + /setpagedevice where { + << 3 1 roll >> + setpagedevice + } { pop pop } ifelse +} bind def + + +% font handling + +/T1AddMapping { % basefont [glyphname ...] T1AddMapping - + 10 dict begin + /glyphs exch def + /fnt exch def + /current fnt /NumGlyphs get def + /CMap fnt /CMap get def + + 0 1 glyphs length 1 sub % 0 1 (num glyphs - 1) + { + glyphs exch get /gn exch def + + current dup % glyph_index glyph_index + 256 mod /min exch def % glyph_index + 256 idiv /maj exch def % - + CMap dup maj get dup % cmap cmap_maj cmap_maj + null eq { + pop 256 array + 0 1 255 {1 index exch /.notdef put} for + } if + dup % cmap cmap_maj cmap_maj + min gn put % cmap cmap_maj + maj exch put % - + + /current current 1 add def + } for + + fnt /CMap CMap put + fnt /NumGlyphs current put + end +} def + +/T1AddGlyphs { % basefont [glyphname charstring ...] T1AddGlyphs - + 10 dict begin + /glyphs exch def + /fnt exch def + /current fnt /NumGlyphs get def + /CMap fnt /CMap get def + /CharStrings fnt /CharStrings get def + + 0 1 glyphs length 2 idiv 1 sub % 0 1 (num glyphs - 1) + { + 2 mul dup + glyphs exch get /gn exch def + 1 add + glyphs exch get /cs exch def + + current dup % glyph_index glyph_index + 256 mod /min exch def % glyph_index + 256 idiv /maj exch def % - + CMap dup maj get dup % cmap cmap_maj cmap_maj + null eq { + pop 256 array + 0 1 255 {1 index exch /.notdef put} for + } if + dup % cmap cmap_maj cmap_maj + min gn put % cmap cmap_maj + maj exch put % - + + CharStrings gn cs put + /current current 1 add def + } for + + fnt /CharStrings CharStrings put + fnt /CMap CMap put + fnt /NumGlyphs current put + end +} def + + + +/StringAdd { % string1 string2 stringadd result + 1 index length 1 index length add + string + 3 1 roll + 2 index 0 3 index putinterval + 2 index 2 index length 2 index putinterval + pop pop +} def + + +/T1Setup { % fontname T1Setup - +10 dict begin + dup /FontName exch def + (-Base) StringAdd cvx cvn /Font exch def + /MaxPage Font /NumGlyphs get 1 sub 256 idiv def + + /FDepVector MaxPage 1 add array def + /Encoding MaxPage 1 add array def + + 0 1 MaxPage { + dup Encoding exch dup put + + + dup /Page exch def + FontName (-) StringAdd + exch + 20 string cvs StringAdd % page fontname + cvn + + Font 0 dict copy dup dup /CMap get + Page get + /Encoding exch put definefont + FDepVector exch Page exch put + } for + + FontName cvn << + /FontType 0 + /FMapType 2 + /FontMatrix[1 0 0 1 0 0] + /Encoding Encoding + /FDepVector FDepVector + >> definefont pop + end +} def + diff --git a/src/gui/painting/qrasterdefs_p.h b/src/gui/painting/qrasterdefs_p.h new file mode 100644 index 0000000000..706064c305 --- /dev/null +++ b/src/gui/painting/qrasterdefs_p.h @@ -0,0 +1,1279 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2001, 2002, 2003, 2004 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* Note: A `raster' is simply a scan-line converter, used to render */ + /* QT_FT_Outlines into QT_FT_Bitmaps. */ + /* */ + /*************************************************************************/ + + +#ifndef __QT_FTIMAGE_H__ +#define __QT_FTIMAGE_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_FT_BEGIN_HEADER + + /*************************************************************************/ + /* */ + /* <Section> */ + /* basic_types */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* <Type> */ + /* QT_FT_Pos */ + /* */ + /* <Description> */ + /* The type QT_FT_Pos is a 32-bit integer used to store vectorial */ + /* coordinates. Depending on the context, these can represent */ + /* distances in integer font units, or 16,16, or 26.6 fixed float */ + /* pixel coordinates. */ + /* */ + typedef signed int QT_FT_Pos; + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Vector */ + /* */ + /* <Description> */ + /* A simple structure used to store a 2D vector; coordinates are of */ + /* the QT_FT_Pos type. */ + /* */ + /* <Fields> */ + /* x :: The horizontal coordinate. */ + /* y :: The vertical coordinate. */ + /* */ + typedef struct QT_FT_Vector_ + { + QT_FT_Pos x; + QT_FT_Pos y; + + } QT_FT_Vector; + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_BBox */ + /* */ + /* <Description> */ + /* A structure used to hold an outline's bounding box, i.e., the */ + /* coordinates of its extrema in the horizontal and vertical */ + /* directions. */ + /* */ + /* <Fields> */ + /* xMin :: The horizontal minimum (left-most). */ + /* */ + /* yMin :: The vertical minimum (bottom-most). */ + /* */ + /* xMax :: The horizontal maximum (right-most). */ + /* */ + /* yMax :: The vertical maximum (top-most). */ + /* */ + typedef struct QT_FT_BBox_ + { + QT_FT_Pos xMin, yMin; + QT_FT_Pos xMax, yMax; + + } QT_FT_BBox; + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* QT_FT_Pixel_Mode */ + /* */ + /* <Description> */ + /* An enumeration type used to describe the format of pixels in a */ + /* given bitmap. Note that additional formats may be added in the */ + /* future. */ + /* */ + /* <Values> */ + /* QT_FT_PIXEL_MODE_NONE :: */ + /* Value 0 is reserved. */ + /* */ + /* QT_FT_PIXEL_MODE_MONO :: */ + /* A monochrome bitmap, using 1 bit per pixel. Note that pixels */ + /* are stored in most-significant order (MSB), which means that */ + /* the left-most pixel in a byte has value 128. */ + /* */ + /* QT_FT_PIXEL_MODE_GRAY :: */ + /* An 8-bit bitmap, generally used to represent anti-aliased glyph */ + /* images. Each pixel is stored in one byte. Note that the number */ + /* of value "gray" levels is stored in the `num_bytes' field of */ + /* the @QT_FT_Bitmap structure (it generally is 256). */ + /* */ + /* QT_FT_PIXEL_MODE_GRAY2 :: */ + /* A 2-bit/pixel bitmap, used to represent embedded anti-aliased */ + /* bitmaps in font files according to the OpenType specification. */ + /* We haven't found a single font using this format, however. */ + /* */ + /* QT_FT_PIXEL_MODE_GRAY4 :: */ + /* A 4-bit/pixel bitmap, used to represent embedded anti-aliased */ + /* bitmaps in font files according to the OpenType specification. */ + /* We haven't found a single font using this format, however. */ + /* */ + /* QT_FT_PIXEL_MODE_LCD :: */ + /* An 8-bit bitmap, used to represent RGB or BGR decimated glyph */ + /* images used for display on LCD displays; the bitmap's width is */ + /* three times wider than the original glyph image. See also */ + /* @QT_FT_RENDER_MODE_LCD. */ + /* */ + /* QT_FT_PIXEL_MODE_LCD_V :: */ + /* An 8-bit bitmap, used to represent RGB or BGR decimated glyph */ + /* images used for display on rotated LCD displays; the bitmap's */ + /* height is three times taller than the original glyph image. */ + /* See also @QT_FT_RENDER_MODE_LCD_V. */ + /* */ + typedef enum QT_FT_Pixel_Mode_ + { + QT_FT_PIXEL_MODE_NONE = 0, + QT_FT_PIXEL_MODE_MONO, + QT_FT_PIXEL_MODE_GRAY, + QT_FT_PIXEL_MODE_GRAY2, + QT_FT_PIXEL_MODE_GRAY4, + QT_FT_PIXEL_MODE_LCD, + QT_FT_PIXEL_MODE_LCD_V, + + QT_FT_PIXEL_MODE_MAX /* do not remove */ + + } QT_FT_Pixel_Mode; + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* qt_ft_pixel_mode_xxx */ + /* */ + /* <Description> */ + /* A list of deprecated constants. Use the corresponding */ + /* @QT_FT_Pixel_Mode values instead. */ + /* */ + /* <Values> */ + /* qt_ft_pixel_mode_none :: see @QT_FT_PIXEL_MODE_NONE */ + /* qt_ft_pixel_mode_mono :: see @QT_FT_PIXEL_MODE_MONO */ + /* qt_ft_pixel_mode_grays :: see @QT_FT_PIXEL_MODE_GRAY */ + /* qt_ft_pixel_mode_pal2 :: see @QT_FT_PIXEL_MODE_GRAY2 */ + /* qt_ft_pixel_mode_pal4 :: see @QT_FT_PIXEL_MODE_GRAY4 */ + /* */ +#define qt_ft_pixel_mode_none QT_FT_PIXEL_MODE_NONE +#define qt_ft_pixel_mode_mono QT_FT_PIXEL_MODE_MONO +#define qt_ft_pixel_mode_grays QT_FT_PIXEL_MODE_GRAY +#define qt_ft_pixel_mode_pal2 QT_FT_PIXEL_MODE_GRAY2 +#define qt_ft_pixel_mode_pal4 QT_FT_PIXEL_MODE_GRAY4 + + /* */ + +#if 0 + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* QT_FT_Palette_Mode */ + /* */ + /* <Description> */ + /* THIS TYPE IS DEPRECATED. DO NOT USE IT! */ + /* */ + /* An enumeration type used to describe the format of a bitmap */ + /* palette, used with qt_ft_pixel_mode_pal4 and qt_ft_pixel_mode_pal8. */ + /* */ + /* <Fields> */ + /* qt_ft_palette_mode_rgb :: The palette is an array of 3-bytes RGB */ + /* records. */ + /* */ + /* qt_ft_palette_mode_rgba :: The palette is an array of 4-bytes RGBA */ + /* records. */ + /* */ + /* <Note> */ + /* As qt_ft_pixel_mode_pal2, pal4 and pal8 are currently unused by */ + /* FreeType, these types are not handled by the library itself. */ + /* */ + typedef enum QT_FT_Palette_Mode_ + { + qt_ft_palette_mode_rgb = 0, + qt_ft_palette_mode_rgba, + + qt_ft_palettte_mode_max /* do not remove */ + + } QT_FT_Palette_Mode; + + /* */ + +#endif + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Bitmap */ + /* */ + /* <Description> */ + /* A structure used to describe a bitmap or pixmap to the raster. */ + /* Note that we now manage pixmaps of various depths through the */ + /* `pixel_mode' field. */ + /* */ + /* <Fields> */ + /* rows :: The number of bitmap rows. */ + /* */ + /* width :: The number of pixels in bitmap row. */ + /* */ + /* pitch :: The pitch's absolute value is the number of bytes */ + /* taken by one bitmap row, including padding. */ + /* However, the pitch is positive when the bitmap has */ + /* a `down' flow, and negative when it has an `up' */ + /* flow. In all cases, the pitch is an offset to add */ + /* to a bitmap pointer in order to go down one row. */ + /* */ + /* buffer :: A typeless pointer to the bitmap buffer. This */ + /* value should be aligned on 32-bit boundaries in */ + /* most cases. */ + /* */ + /* num_grays :: This field is only used with */ + /* `QT_FT_PIXEL_MODE_GRAY'; it gives the number of gray */ + /* levels used in the bitmap. */ + /* */ + /* pixel_mode :: The pixel mode, i.e., how pixel bits are stored. */ + /* See @QT_FT_Pixel_Mode for possible values. */ + /* */ + /* palette_mode :: This field is only used with paletted pixel modes; */ + /* it indicates how the palette is stored. */ + /* */ + /* palette :: A typeless pointer to the bitmap palette; only */ + /* used for paletted pixel modes. */ + /* */ + /* <Note> */ + /* For now, the only pixel mode supported by FreeType are mono and */ + /* grays. However, drivers might be added in the future to support */ + /* more `colorful' options. */ + /* */ + /* When using pixel modes pal2, pal4 and pal8 with a void `palette' */ + /* field, a gray pixmap with respectively 4, 16, and 256 levels of */ + /* gray is assumed. This, in order to be compatible with some */ + /* embedded bitmap formats defined in the TrueType specification. */ + /* */ + /* Note that no font was found presenting such embedded bitmaps, so */ + /* this is currently completely unhandled by the library. */ + /* */ + typedef struct QT_FT_Bitmap_ + { + int rows; + int width; + int pitch; + unsigned char* buffer; + short num_grays; + char pixel_mode; + char palette_mode; + void* palette; + + } QT_FT_Bitmap; + + + /*************************************************************************/ + /* */ + /* <Section> */ + /* outline_processing */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Outline */ + /* */ + /* <Description> */ + /* This structure is used to describe an outline to the scan-line */ + /* converter. */ + /* */ + /* <Fields> */ + /* n_contours :: The number of contours in the outline. */ + /* */ + /* n_points :: The number of points in the outline. */ + /* */ + /* points :: A pointer to an array of `n_points' QT_FT_Vector */ + /* elements, giving the outline's point coordinates. */ + /* */ + /* tags :: A pointer to an array of `n_points' chars, giving */ + /* each outline point's type. If bit 0 is unset, the */ + /* point is `off' the curve, i.e. a Bezier control */ + /* point, while it is `on' when set. */ + /* */ + /* Bit 1 is meaningful for `off' points only. If set, */ + /* it indicates a third-order Bezier arc control point; */ + /* and a second-order control point if unset. */ + /* */ + /* contours :: An array of `n_contours' shorts, giving the end */ + /* point of each contour within the outline. For */ + /* example, the first contour is defined by the points */ + /* `0' to `contours[0]', the second one is defined by */ + /* the points `contours[0]+1' to `contours[1]', etc. */ + /* */ + /* flags :: A set of bit flags used to characterize the outline */ + /* and give hints to the scan-converter and hinter on */ + /* how to convert/grid-fit it. See QT_FT_Outline_Flags. */ + /* */ + typedef struct QT_FT_Outline_ + { + int n_contours; /* number of contours in glyph */ + int n_points; /* number of points in the glyph */ + + QT_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + int* contours; /* the contour end points */ + + int flags; /* outline masks */ + + } QT_FT_Outline; + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* QT_FT_OUTLINE_FLAGS */ + /* */ + /* <Description> */ + /* A list of bit-field constants use for the flags in an outline's */ + /* `flags' field. */ + /* */ + /* <Values> */ + /* QT_FT_OUTLINE_NONE :: Value 0 is reserved. */ + /* */ + /* QT_FT_OUTLINE_OWNER :: If set, this flag indicates that the */ + /* outline's field arrays (i.e. */ + /* `points', `flags' & `contours') are */ + /* `owned' by the outline object, and */ + /* should thus be freed when it is */ + /* destroyed. */ + /* */ + /* QT_FT_OUTLINE_EVEN_ODD_FILL :: By default, outlines are filled using */ + /* the non-zero winding rule. If set to */ + /* 1, the outline will be filled using */ + /* the even-odd fill rule (only works */ + /* with the smooth raster). */ + /* */ + /* QT_FT_OUTLINE_REVERSE_FILL :: By default, outside contours of an */ + /* outline are oriented in clock-wise */ + /* direction, as defined in the TrueType */ + /* specification. This flag is set if */ + /* the outline uses the opposite */ + /* direction (typically for Type 1 */ + /* fonts). This flag is ignored by the */ + /* scan-converter. However, it is very */ + /* important for the auto-hinter. */ + /* */ + /* QT_FT_OUTLINE_IGNORE_DROPOUTS :: By default, the scan converter will */ + /* try to detect drop-outs in an outline */ + /* and correct the glyph bitmap to */ + /* ensure consistent shape continuity. */ + /* If set, this flag hints the scan-line */ + /* converter to ignore such cases. */ + /* */ + /* QT_FT_OUTLINE_HIGH_PRECISION :: This flag indicates that the */ + /* scan-line converter should try to */ + /* convert this outline to bitmaps with */ + /* the highest possible quality. It is */ + /* typically set for small character */ + /* sizes. Note that this is only a */ + /* hint, that might be completely */ + /* ignored by a given scan-converter. */ + /* */ + /* QT_FT_OUTLINE_SINGLE_PASS :: This flag is set to force a given */ + /* scan-converter to only use a single */ + /* pass over the outline to render a */ + /* bitmap glyph image. Normally, it is */ + /* set for very large character sizes. */ + /* It is only a hint, that might be */ + /* completely ignored by a given */ + /* scan-converter. */ + /* */ +#define QT_FT_OUTLINE_NONE 0x0 +#define QT_FT_OUTLINE_OWNER 0x1 +#define QT_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define QT_FT_OUTLINE_REVERSE_FILL 0x4 +#define QT_FT_OUTLINE_IGNORE_DROPOUTS 0x8 + +#define QT_FT_OUTLINE_HIGH_PRECISION 0x100 +#define QT_FT_OUTLINE_SINGLE_PASS 0x200 + + + /************************************************************************* + * + * @enum: + * qt_ft_outline_flags + * + * @description: + * These constants are deprecated. Please use the corresponding + * @QT_FT_OUTLINE_FLAGS values. + * + * @values: + * qt_ft_outline_none :: See @QT_FT_OUTLINE_NONE. + * qt_ft_outline_owner :: See @QT_FT_OUTLINE_OWNER. + * qt_ft_outline_even_odd_fill :: See @QT_FT_OUTLINE_EVEN_ODD_FILL. + * qt_ft_outline_reverse_fill :: See @QT_FT_OUTLINE_REVERSE_FILL. + * qt_ft_outline_ignore_dropouts :: See @QT_FT_OUTLINE_IGNORE_DROPOUTS. + * qt_ft_outline_high_precision :: See @QT_FT_OUTLINE_HIGH_PRECISION. + * qt_ft_outline_single_pass :: See @QT_FT_OUTLINE_SINGLE_PASS. + */ +#define qt_ft_outline_none QT_FT_OUTLINE_NONE +#define qt_ft_outline_owner QT_FT_OUTLINE_OWNER +#define qt_ft_outline_even_odd_fill QT_FT_OUTLINE_EVEN_ODD_FILL +#define qt_ft_outline_reverse_fill QT_FT_OUTLINE_REVERSE_FILL +#define qt_ft_outline_ignore_dropouts QT_FT_OUTLINE_IGNORE_DROPOUTS +#define qt_ft_outline_high_precision QT_FT_OUTLINE_HIGH_PRECISION +#define qt_ft_outline_single_pass QT_FT_OUTLINE_SINGLE_PASS + + /* */ + +#define QT_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define QT_FT_CURVE_TAG_ON 1 +#define QT_FT_CURVE_TAG_CONIC 0 +#define QT_FT_CURVE_TAG_CUBIC 2 + +#define QT_FT_CURVE_TAG_TOUCH_X 8 /* reserved for the TrueType hinter */ +#define QT_FT_CURVE_TAG_TOUCH_Y 16 /* reserved for the TrueType hinter */ + +#define QT_FT_CURVE_TAG_TOUCH_BOTH ( QT_FT_CURVE_TAG_TOUCH_X | \ + QT_FT_CURVE_TAG_TOUCH_Y ) + +#define QT_FT_Curve_Tag_On QT_FT_CURVE_TAG_ON +#define QT_FT_Curve_Tag_Conic QT_FT_CURVE_TAG_CONIC +#define QT_FT_Curve_Tag_Cubic QT_FT_CURVE_TAG_CUBIC +#define QT_FT_Curve_Tag_Touch_X QT_FT_CURVE_TAG_TOUCH_X +#define QT_FT_Curve_Tag_Touch_Y QT_FT_CURVE_TAG_TOUCH_Y + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Outline_MoveToFunc */ + /* */ + /* <Description> */ + /* A function pointer type used to describe the signature of a `move */ + /* to' function during outline walking/decomposition. */ + /* */ + /* A `move to' is emitted to start a new contour in an outline. */ + /* */ + /* <Input> */ + /* to :: A pointer to the target point of the `move to'. */ + /* */ + /* user :: A typeless pointer which is passed from the caller of the */ + /* decomposition function. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + typedef int + (*QT_FT_Outline_MoveToFunc)( QT_FT_Vector* to, + void* user ); + +#define QT_FT_Outline_MoveTo_Func QT_FT_Outline_MoveToFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Outline_LineToFunc */ + /* */ + /* <Description> */ + /* A function pointer type used to describe the signature of a `line */ + /* to' function during outline walking/decomposition. */ + /* */ + /* A `line to' is emitted to indicate a segment in the outline. */ + /* */ + /* <Input> */ + /* to :: A pointer to the target point of the `line to'. */ + /* */ + /* user :: A typeless pointer which is passed from the caller of the */ + /* decomposition function. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + typedef int + (*QT_FT_Outline_LineToFunc)( QT_FT_Vector* to, + void* user ); + +#define QT_FT_Outline_LineTo_Func QT_FT_Outline_LineToFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Outline_ConicToFunc */ + /* */ + /* <Description> */ + /* A function pointer type use to describe the signature of a `conic */ + /* to' function during outline walking/decomposition. */ + /* */ + /* A `conic to' is emitted to indicate a second-order Bezier arc in */ + /* the outline. */ + /* */ + /* <Input> */ + /* control :: An intermediate control point between the last position */ + /* and the new target in `to'. */ + /* */ + /* to :: A pointer to the target end point of the conic arc. */ + /* */ + /* user :: A typeless pointer which is passed from the caller of */ + /* the decomposition function. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + typedef int + (*QT_FT_Outline_ConicToFunc)( QT_FT_Vector* control, + QT_FT_Vector* to, + void* user ); + +#define QT_FT_Outline_ConicTo_Func QT_FT_Outline_ConicToFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Outline_CubicToFunc */ + /* */ + /* <Description> */ + /* A function pointer type used to describe the signature of a `cubic */ + /* to' function during outline walking/decomposition. */ + /* */ + /* A `cubic to' is emitted to indicate a third-order Bezier arc. */ + /* */ + /* <Input> */ + /* control1 :: A pointer to the first Bezier control point. */ + /* */ + /* control2 :: A pointer to the second Bezier control point. */ + /* */ + /* to :: A pointer to the target end point. */ + /* */ + /* user :: A typeless pointer which is passed from the caller of */ + /* the decomposition function. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + typedef int + (*QT_FT_Outline_CubicToFunc)( QT_FT_Vector* control1, + QT_FT_Vector* control2, + QT_FT_Vector* to, + void* user ); + +#define QT_FT_Outline_CubicTo_Func QT_FT_Outline_CubicToFunc + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Outline_Funcs */ + /* */ + /* <Description> */ + /* A structure to hold various function pointers used during outline */ + /* decomposition in order to emit segments, conic, and cubic Beziers, */ + /* as well as `move to' and `close to' operations. */ + /* */ + /* <Fields> */ + /* move_to :: The `move to' emitter. */ + /* */ + /* line_to :: The segment emitter. */ + /* */ + /* conic_to :: The second-order Bezier arc emitter. */ + /* */ + /* cubic_to :: The third-order Bezier arc emitter. */ + /* */ + /* shift :: The shift that is applied to coordinates before they */ + /* are sent to the emitter. */ + /* */ + /* delta :: The delta that is applied to coordinates before they */ + /* are sent to the emitter, but after the shift. */ + /* */ + /* <Note> */ + /* The point coordinates sent to the emitters are the transformed */ + /* version of the original coordinates (this is important for high */ + /* accuracy during scan-conversion). The transformation is simple: */ + /* */ + /* x' = (x << shift) - delta */ + /* y' = (x << shift) - delta */ + /* */ + /* Set the value of `shift' and `delta' to 0 to get the original */ + /* point coordinates. */ + /* */ + typedef struct QT_FT_Outline_Funcs_ + { + QT_FT_Outline_MoveToFunc move_to; + QT_FT_Outline_LineToFunc line_to; + QT_FT_Outline_ConicToFunc conic_to; + QT_FT_Outline_CubicToFunc cubic_to; + + int shift; + QT_FT_Pos delta; + + } QT_FT_Outline_Funcs; + + + /*************************************************************************/ + /* */ + /* <Section> */ + /* basic_types */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* <Macro> */ + /* QT_FT_IMAGE_TAG */ + /* */ + /* <Description> */ + /* This macro converts four letter tags into an unsigned long. */ + /* */ + /* <Note> */ + /* Since many 16bit compilers don't like 32bit enumerations, you */ + /* should redefine this macro in case of problems to something like */ + /* this: */ + /* */ + /* #define QT_FT_IMAGE_TAG( value, _x1, _x2, _x3, _x4 ) value */ + /* */ + /* to get a simple enumeration without assigning special numbers. */ + /* */ +#ifndef QT_FT_IMAGE_TAG +#define QT_FT_IMAGE_TAG( value, _x1, _x2, _x3, _x4 ) \ + value = ( ( (unsigned long)_x1 << 24 ) | \ + ( (unsigned long)_x2 << 16 ) | \ + ( (unsigned long)_x3 << 8 ) | \ + (unsigned long)_x4 ) +#endif /* QT_FT_IMAGE_TAG */ + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* QT_FT_Glyph_Format */ + /* */ + /* <Description> */ + /* An enumeration type used to describe the format of a given glyph */ + /* image. Note that this version of FreeType only supports two image */ + /* formats, even though future font drivers will be able to register */ + /* their own format. */ + /* */ + /* <Values> */ + /* QT_FT_GLYPH_FORMAT_NONE :: */ + /* The value 0 is reserved and does describe a glyph format. */ + /* */ + /* QT_FT_GLYPH_FORMAT_COMPOSITE :: */ + /* The glyph image is a composite of several other images. This */ + /* format is _only_ used with @QT_FT_LOAD_NO_RECURSE, and is used to */ + /* report compound glyphs (like accented characters). */ + /* */ + /* QT_FT_GLYPH_FORMAT_BITMAP :: */ + /* The glyph image is a bitmap, and can be described as an */ + /* @QT_FT_Bitmap. You generally need to access the `bitmap' field of */ + /* the @QT_FT_GlyphSlotRec structure to read it. */ + /* */ + /* QT_FT_GLYPH_FORMAT_OUTLINE :: */ + /* The glyph image is a vertorial outline made of line segments */ + /* and Bezier arcs; it can be described as an @QT_FT_Outline; you */ + /* generally want to access the `outline' field of the */ + /* @QT_FT_GlyphSlotRec structure to read it. */ + /* */ + /* QT_FT_GLYPH_FORMAT_PLOTTER :: */ + /* The glyph image is a vectorial path with no inside/outside */ + /* contours. Some Type 1 fonts, like those in the Hershey family, */ + /* contain glyphs in this format. These are described as */ + /* @QT_FT_Outline, but FreeType isn't currently capable of rendering */ + /* them correctly. */ + /* */ + typedef enum QT_FT_Glyph_Format_ + { + QT_FT_IMAGE_TAG( QT_FT_GLYPH_FORMAT_NONE, 0, 0, 0, 0 ), + + QT_FT_IMAGE_TAG( QT_FT_GLYPH_FORMAT_COMPOSITE, 'c', 'o', 'm', 'p' ), + QT_FT_IMAGE_TAG( QT_FT_GLYPH_FORMAT_BITMAP, 'b', 'i', 't', 's' ), + QT_FT_IMAGE_TAG( QT_FT_GLYPH_FORMAT_OUTLINE, 'o', 'u', 't', 'l' ), + QT_FT_IMAGE_TAG( QT_FT_GLYPH_FORMAT_PLOTTER, 'p', 'l', 'o', 't' ) + + } QT_FT_Glyph_Format; + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* qt_ft_glyph_format_xxx */ + /* */ + /* <Description> */ + /* A list of decprecated constants. Use the corresponding */ + /* @QT_FT_Glyph_Format values instead. */ + /* */ + /* <Values> */ + /* qt_ft_glyph_format_none :: see @QT_FT_GLYPH_FORMAT_NONE */ + /* qt_ft_glyph_format_composite :: see @QT_FT_GLYPH_FORMAT_COMPOSITE */ + /* qt_ft_glyph_format_bitmap :: see @QT_FT_GLYPH_FORMAT_BITMAP */ + /* qt_ft_glyph_format_outline :: see @QT_FT_GLYPH_FORMAT_OUTLINE */ + /* qt_ft_glyph_format_plotter :: see @QT_FT_GLYPH_FORMAT_PLOTTER */ + /* */ +#define qt_ft_glyph_format_none QT_FT_GLYPH_FORMAT_NONE +#define qt_ft_glyph_format_composite QT_FT_GLYPH_FORMAT_COMPOSITE +#define qt_ft_glyph_format_bitmap QT_FT_GLYPH_FORMAT_BITMAP +#define qt_ft_glyph_format_outline QT_FT_GLYPH_FORMAT_OUTLINE +#define qt_ft_glyph_format_plotter QT_FT_GLYPH_FORMAT_PLOTTER + + + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + /***** *****/ + /***** R A S T E R D E F I N I T I O N S *****/ + /***** *****/ + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* A raster is a scan converter, in charge of rendering an outline into */ + /* a a bitmap. This section contains the public API for rasters. */ + /* */ + /* Note that in FreeType 2, all rasters are now encapsulated within */ + /* specific modules called `renderers'. See `freetype/ftrender.h' for */ + /* more details on renderers. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* <Section> */ + /* raster */ + /* */ + /* <Title> */ + /* Scanline converter */ + /* */ + /* <Abstract> */ + /* How vectorial outlines are converted into bitmaps and pixmaps. */ + /* */ + /* <Description> */ + /* This section contains technical definitions. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* <Type> */ + /* QT_FT_Raster */ + /* */ + /* <Description> */ + /* A handle (pointer) to a raster object. Each object can be used */ + /* independently to convert an outline into a bitmap or pixmap. */ + /* */ + typedef struct TRaster_ *QT_FT_Raster; + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Span */ + /* */ + /* <Description> */ + /* A structure used to model a single span of gray (or black) pixels */ + /* when rendering a monochrome or anti-aliased bitmap. */ + /* */ + /* <Fields> */ + /* x :: The span's horizontal start position. */ + /* */ + /* len :: The span's length in pixels. */ + /* */ + /* coverage :: The span color/coverage, ranging from 0 (background) */ + /* to 255 (foreground). Only used for anti-aliased */ + /* rendering. */ + /* */ + /* <Note> */ + /* This structure is used by the span drawing callback type named */ + /* QT_FT_SpanFunc which takes the y-coordinate of the span as a */ + /* a parameter. */ + /* */ + /* The coverage value is always between 0 and 255, even if the number */ + /* of gray levels have been set through QT_FT_Set_Gray_Levels(). */ + /* */ + typedef struct QT_FT_Span_ + { + short x; + unsigned short len; + short y; + unsigned char coverage; + } QT_FT_Span; + + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_SpanFunc */ + /* */ + /* <Description> */ + /* A function used as a call-back by the anti-aliased renderer in */ + /* order to let client applications draw themselves the gray pixel */ + /* spans on each scan line. */ + /* */ + /* <Input> */ + /* y :: The scanline's y-coordinate. */ + /* */ + /* count :: The number of spans to draw on this scanline. */ + /* */ + /* spans :: A table of `count' spans to draw on the scanline. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* <Note> */ + /* This callback allows client applications to directly render the */ + /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ + /* */ + /* This can be used to write anti-aliased outlines directly to a */ + /* given background bitmap, and even perform translucency. */ + /* */ + /* Note that the `count' field cannot be greater than a fixed value */ + /* defined by the QT_FT_MAX_GRAY_SPANS configuration macro in */ + /* ftoption.h. By default, this value is set to 32, which means that */ + /* if there are more than 32 spans on a given scanline, the callback */ + /* will be called several times with the same `y' parameter in order */ + /* to draw all callbacks. */ + /* */ + /* Otherwise, the callback is only called once per scan-line, and */ + /* only for those scanlines that do have `gray' pixels on them. */ + /* */ + typedef void + (*QT_FT_SpanFunc)(int count, + const QT_FT_Span* spans, + void* worker); + +#define QT_FT_Raster_Span_Func QT_FT_SpanFunc + + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_BitTest_Func */ + /* */ + /* <Description> */ + /* THIS TYPE IS DEPRECATED. DO NOT USE IT. */ + /* */ + /* A function used as a call-back by the monochrome scan-converter */ + /* to test whether a given target pixel is already set to the drawing */ + /* `color'. These tests are crucial to implement drop-out control */ + /* per-se the TrueType spec. */ + /* */ + /* <Input> */ + /* y :: The pixel's y-coordinate. */ + /* */ + /* x :: The pixel's x-coordinate. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* <Return> */ + /* 1 if the pixel is `set', 0 otherwise. */ + /* */ + typedef int + (*QT_FT_Raster_BitTest_Func)( int y, + int x, + void* user ); + + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_BitSet_Func */ + /* */ + /* <Description> */ + /* THIS TYPE IS DEPRECATED. DO NOT USE IT. */ + /* */ + /* A function used as a call-back by the monochrome scan-converter */ + /* to set an individual target pixel. This is crucial to implement */ + /* drop-out control according to the TrueType specification. */ + /* */ + /* <Input> */ + /* y :: The pixel's y-coordinate. */ + /* */ + /* x :: The pixel's x-coordinate. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* <Return> */ + /* 1 if the pixel is `set', 0 otherwise. */ + /* */ + typedef void + (*QT_FT_Raster_BitSet_Func)( int y, + int x, + void* user ); + + + /*************************************************************************/ + /* */ + /* <Enum> */ + /* QT_FT_RASTER_FLAG_XXX */ + /* */ + /* <Description> */ + /* A list of bit flag constants as used in the `flags' field of a */ + /* @QT_FT_Raster_Params structure. */ + /* */ + /* <Values> */ + /* QT_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ + /* */ + /* QT_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ + /* anti-aliased glyph image should be */ + /* generated. Otherwise, it will be */ + /* monochrome (1-bit). */ + /* */ + /* QT_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ + /* rendering. In this mode, client */ + /* applications must provide their own span */ + /* callback. This lets them directly */ + /* draw or compose over an existing bitmap. */ + /* If this bit is not set, the target */ + /* pixmap's buffer _must_ be zeroed before */ + /* rendering. */ + /* */ + /* Note that for now, direct rendering is */ + /* only possible with anti-aliased glyphs. */ + /* */ + /* QT_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ + /* rendering mode. If set, the output will */ + /* be clipped to a box specified in the */ + /* "clip_box" field of the QT_FT_Raster_Params */ + /* structure. */ + /* */ + /* Note that by default, the glyph bitmap */ + /* is clipped to the target pixmap, except */ + /* in direct rendering mode where all spans */ + /* are generated if no clipping box is set. */ + /* */ +#define QT_FT_RASTER_FLAG_DEFAULT 0x0 +#define QT_FT_RASTER_FLAG_AA 0x1 +#define QT_FT_RASTER_FLAG_DIRECT 0x2 +#define QT_FT_RASTER_FLAG_CLIP 0x4 + + /* deprecated */ +#define qt_ft_raster_flag_default QT_FT_RASTER_FLAG_DEFAULT +#define qt_ft_raster_flag_aa QT_FT_RASTER_FLAG_AA +#define qt_ft_raster_flag_direct QT_FT_RASTER_FLAG_DIRECT +#define qt_ft_raster_flag_clip QT_FT_RASTER_FLAG_CLIP + + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Raster_Params */ + /* */ + /* <Description> */ + /* A structure to hold the arguments used by a raster's render */ + /* function. */ + /* */ + /* <Fields> */ + /* target :: The target bitmap. */ + /* */ + /* source :: A pointer to the source glyph image (e.g. an */ + /* QT_FT_Outline). */ + /* */ + /* flags :: The rendering flags. */ + /* */ + /* gray_spans :: The gray span drawing callback. */ + /* */ + /* black_spans :: The black span drawing callback. */ + /* */ + /* bit_test :: The bit test callback. UNIMPLEMENTED! */ + /* */ + /* bit_set :: The bit set callback. UNIMPLEMENTED! */ + /* */ + /* user :: User-supplied data that is passed to each drawing */ + /* callback. */ + /* */ + /* clip_box :: An optional clipping box. It is only used in */ + /* direct rendering mode. Note that coordinates here */ + /* should be expressed in _integer_ pixels (and not in */ + /* 26.6 fixed-point units). */ + /* */ + /* <Note> */ + /* An anti-aliased glyph bitmap is drawn if the QT_FT_RASTER_FLAG_AA bit */ + /* flag is set in the `flags' field, otherwise a monochrome bitmap */ + /* will be generated. */ + /* */ + /* If the QT_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ + /* raster will call the `gray_spans' callback to draw gray pixel */ + /* spans, in the case of an aa glyph bitmap, it will call */ + /* `black_spans', and `bit_test' and `bit_set' in the case of a */ + /* monochrome bitmap. This allows direct composition over a */ + /* pre-existing bitmap through user-provided callbacks to perform the */ + /* span drawing/composition. */ + /* */ + /* Note that the `bit_test' and `bit_set' callbacks are required when */ + /* rendering a monochrome bitmap, as they are crucial to implement */ + /* correct drop-out control as defined in the TrueType specification. */ + /* */ + typedef struct QT_FT_Raster_Params_ + { + QT_FT_Bitmap* target; + void* source; + int flags; + QT_FT_SpanFunc gray_spans; + QT_FT_SpanFunc black_spans; + QT_FT_Raster_BitTest_Func bit_test; /* doesn't work! */ + QT_FT_Raster_BitSet_Func bit_set; /* doesn't work! */ + void* user; + QT_FT_BBox clip_box; + int skip_spans; + + } QT_FT_Raster_Params; + + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_NewFunc */ + /* */ + /* <Description> */ + /* A function used to create a new raster object. */ + /* */ + /* <Input> */ + /* memory :: A handle to the memory allocator. */ + /* */ + /* <Output> */ + /* raster :: A handle to the new raster object. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + /* <Note> */ + /* The `memory' parameter is a typeless pointer in order to avoid */ + /* un-wanted dependencies on the rest of the FreeType code. In */ + /* practice, it is a QT_FT_Memory, i.e., a handle to the standard */ + /* FreeType memory allocator. However, this field can be completely */ + /* ignored by a given raster implementation. */ + /* */ + typedef int + (*QT_FT_Raster_NewFunc)( QT_FT_Raster* raster ); + +#define QT_FT_Raster_New_Func QT_FT_Raster_NewFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_DoneFunc */ + /* */ + /* <Description> */ + /* A function used to destroy a given raster object. */ + /* */ + /* <Input> */ + /* raster :: A handle to the raster object. */ + /* */ + typedef void + (*QT_FT_Raster_DoneFunc)( QT_FT_Raster raster ); + +#define QT_FT_Raster_Done_Func QT_FT_Raster_DoneFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_ResetFunc */ + /* */ + /* <Description> */ + /* FreeType provides an area of memory called the `render pool', */ + /* available to all registered rasters. This pool can be freely used */ + /* during a given scan-conversion but is shared by all rasters. Its */ + /* content is thus transient. */ + /* */ + /* This function is called each time the render pool changes, or just */ + /* after a new raster object is created. */ + /* */ + /* <Input> */ + /* raster :: A handle to the new raster object. */ + /* */ + /* pool_base :: The address in memory of the render pool. */ + /* */ + /* pool_size :: The size in bytes of the render pool. */ + /* */ + /* <Note> */ + /* Rasters can ignore the render pool and rely on dynamic memory */ + /* allocation if they want to (a handle to the memory allocator is */ + /* passed to the raster constructor). However, this is not */ + /* recommended for efficiency purposes. */ + /* */ + typedef void + (*QT_FT_Raster_ResetFunc)( QT_FT_Raster raster, + unsigned char* pool_base, + unsigned long pool_size ); + +#define QT_FT_Raster_Reset_Func QT_FT_Raster_ResetFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_SetModeFunc */ + /* */ + /* <Description> */ + /* This function is a generic facility to change modes or attributes */ + /* in a given raster. This can be used for debugging purposes, or */ + /* simply to allow implementation-specific `features' in a given */ + /* raster module. */ + /* */ + /* <Input> */ + /* raster :: A handle to the new raster object. */ + /* */ + /* mode :: A 4-byte tag used to name the mode or property. */ + /* */ + /* args :: A pointer to the new mode/property to use. */ + /* */ + typedef int + (*QT_FT_Raster_SetModeFunc)( QT_FT_Raster raster, + unsigned long mode, + void* args ); + +#define QT_FT_Raster_Set_Mode_Func QT_FT_Raster_SetModeFunc + + /*************************************************************************/ + /* */ + /* <FuncType> */ + /* QT_FT_Raster_RenderFunc */ + /* */ + /* <Description> */ + /* Invokes a given raster to scan-convert a given glyph image into a */ + /* target bitmap. */ + /* */ + /* <Input> */ + /* raster :: A handle to the raster object. */ + /* */ + /* params :: A pointer to a QT_FT_Raster_Params structure used to store */ + /* the rendering parameters. */ + /* */ + /* <Return> */ + /* Error code. 0 means success. */ + /* */ + /* <Note> */ + /* The exact format of the source image depends on the raster's glyph */ + /* format defined in its QT_FT_Raster_Funcs structure. It can be an */ + /* QT_FT_Outline or anything else in order to support a large array of */ + /* glyph formats. */ + /* */ + /* Note also that the render function can fail and return a */ + /* QT_FT_Err_Unimplemented_Feature error code if the raster used does */ + /* not support direct composition. */ + /* */ + /* XXX: For now, the standard raster doesn't support direct */ + /* composition but this should change for the final release (see */ + /* the files demos/src/ftgrays.c and demos/src/ftgrays2.c for */ + /* examples of distinct implementations which support direct */ + /* composition). */ + /* */ + typedef int + (*QT_FT_Raster_RenderFunc)( QT_FT_Raster raster, + QT_FT_Raster_Params* params ); + +#define QT_FT_Raster_Render_Func QT_FT_Raster_RenderFunc + + /*************************************************************************/ + /* */ + /* <Struct> */ + /* QT_FT_Raster_Funcs */ + /* */ + /* <Description> */ + /* A structure used to describe a given raster class to the library. */ + /* */ + /* <Fields> */ + /* glyph_format :: The supported glyph format for this raster. */ + /* */ + /* raster_new :: The raster constructor. */ + /* */ + /* raster_reset :: Used to reset the render pool within the raster. */ + /* */ + /* raster_render :: A function to render a glyph into a given bitmap. */ + /* */ + /* raster_done :: The raster destructor. */ + /* */ + typedef struct QT_FT_Raster_Funcs_ + { + QT_FT_Glyph_Format glyph_format; + QT_FT_Raster_NewFunc raster_new; + QT_FT_Raster_ResetFunc raster_reset; + QT_FT_Raster_SetModeFunc raster_set_mode; + QT_FT_Raster_RenderFunc raster_render; + QT_FT_Raster_DoneFunc raster_done; + + } QT_FT_Raster_Funcs; + + + /* */ + + +QT_FT_END_HEADER + +#endif /* __FTIMAGE_H__ */ + + +/* END */ diff --git a/src/gui/painting/qrasterizer.cpp b/src/gui/painting/qrasterizer.cpp new file mode 100644 index 0000000000..75116c2420 --- /dev/null +++ b/src/gui/painting/qrasterizer.cpp @@ -0,0 +1,1270 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qrasterizer_p.h" + +#include <QPoint> +#include <QRect> + +#include <private/qmath_p.h> +#include <private/qdatabuffer_p.h> +#include <private/qdrawhelper_p.h> + +QT_BEGIN_NAMESPACE + +typedef int Q16Dot16; +#define Q16Dot16ToFloat(i) ((i)/65536.) +#define FloatToQ16Dot16(i) (int)((i) * 65536.) +#define IntToQ16Dot16(i) ((i) << 16) +#define Q16Dot16ToInt(i) ((i) >> 16) +#define Q16Dot16Factor 65536 + +#define Q16Dot16Multiply(x, y) (int)((qlonglong(x) * qlonglong(y)) >> 16) +#define Q16Dot16FastMultiply(x, y) (((x) * (y)) >> 16) + +#define SPAN_BUFFER_SIZE 256 + +#define COORD_ROUNDING 0 // 0: round up, 1: round down +#define COORD_OFFSET 0 // 26.6, 32 is half a pixel + +static inline QT_FT_Vector PointToVector(const QPointF &p) +{ + QT_FT_Vector result = { QT_FT_Pos(p.x() * 64), QT_FT_Pos(p.y() * 64) }; + return result; +} + +class QSpanBuffer { +public: + QSpanBuffer(ProcessSpans blend, void *data, const QRect &clipRect) + : m_spanCount(0) + , m_blend(blend) + , m_data(data) + , m_clipRect(clipRect) + { + } + + ~QSpanBuffer() + { + flushSpans(); + } + + void addSpan(int x, unsigned int len, int y, unsigned char coverage) + { + if (!coverage || !len) + return; + + Q_ASSERT(y >= m_clipRect.top()); + Q_ASSERT(y <= m_clipRect.bottom()); + Q_ASSERT(x >= m_clipRect.left()); + Q_ASSERT(x + int(len) - 1 <= m_clipRect.right()); + + m_spans[m_spanCount].x = x; + m_spans[m_spanCount].len = len; + m_spans[m_spanCount].y = y; + m_spans[m_spanCount].coverage = coverage; + + if (++m_spanCount == SPAN_BUFFER_SIZE) + flushSpans(); + } + +private: + void flushSpans() + { + m_blend(m_spanCount, m_spans, m_data); + m_spanCount = 0; + } + + QT_FT_Span m_spans[SPAN_BUFFER_SIZE]; + int m_spanCount; + + ProcessSpans m_blend; + void *m_data; + + QRect m_clipRect; +}; + +#define CHUNK_SIZE 64 +class QScanConverter +{ +public: + QScanConverter(); + ~QScanConverter(); + + void begin(int top, int bottom, int left, int right, + Qt::FillRule fillRule, QSpanBuffer *spanBuffer); + void end(); + + void mergeCurve(const QT_FT_Vector &a, const QT_FT_Vector &b, + const QT_FT_Vector &c, const QT_FT_Vector &d); + void mergeLine(QT_FT_Vector a, QT_FT_Vector b); + + struct Line + { + Q16Dot16 x; + Q16Dot16 delta; + + int top, bottom; + + int winding; + }; + +private: + struct Intersection + { + int x; + int winding; + + int left, right; + }; + + inline bool clip(Q16Dot16 &xFP, int &iTop, int &iBottom, Q16Dot16 slopeFP, Q16Dot16 edgeFP, int winding); + inline void mergeIntersection(Intersection *head, const Intersection &isect); + + void prepareChunk(); + + void emitNode(const Intersection *node); + void emitSpans(int chunk); + + inline void allocate(int size); + + QDataBuffer<Line> m_lines; + + int m_alloc; + int m_size; + + int m_top; + int m_bottom; + + Q16Dot16 m_leftFP; + Q16Dot16 m_rightFP; + + int m_fillRuleMask; + + int m_x; + int m_y; + int m_winding; + + Intersection *m_intersections; + + QSpanBuffer *m_spanBuffer; + + QDataBuffer<Line *> m_active; + + template <typename T> + friend void qScanConvert(QScanConverter &d, T allVertical); +}; + +class QRasterizerPrivate +{ +public: + bool antialiased; + ProcessSpans blend; + void *data; + QRect clipRect; + + QScanConverter scanConverter; +}; + +QScanConverter::QScanConverter() + : m_lines(0) + , m_alloc(0) + , m_size(0) + , m_intersections(0) + , m_active(0) +{ +} + +QScanConverter::~QScanConverter() +{ + if (m_intersections) + free(m_intersections); +} + +void QScanConverter::begin(int top, int bottom, int left, int right, + Qt::FillRule fillRule, QSpanBuffer *spanBuffer) +{ + m_top = top; + m_bottom = bottom; + m_leftFP = IntToQ16Dot16(left); + m_rightFP = IntToQ16Dot16(right + 1); + + m_lines.reset(); + + m_fillRuleMask = fillRule == Qt::WindingFill ? ~0x0 : 0x1; + m_spanBuffer = spanBuffer; +} + +void QScanConverter::prepareChunk() +{ + m_size = CHUNK_SIZE; + + allocate(CHUNK_SIZE); + memset(m_intersections, 0, CHUNK_SIZE * sizeof(Intersection)); +} + +void QScanConverter::emitNode(const Intersection *node) +{ +tail_call: + if (node->left) + emitNode(node + node->left); + + if (m_winding & m_fillRuleMask) + m_spanBuffer->addSpan(m_x, node->x - m_x, m_y, 0xff); + + m_x = node->x; + m_winding += node->winding; + + if (node->right) { + node += node->right; + goto tail_call; + } +} + +void QScanConverter::emitSpans(int chunk) +{ + for (int dy = 0; dy < CHUNK_SIZE; ++dy) { + m_x = 0; + m_y = chunk + dy; + m_winding = 0; + + emitNode(&m_intersections[dy]); + } +} + +// split control points b[0] ... b[3] into +// left (b[0] ... b[3]) and right (b[3] ... b[6]) +static void split(QT_FT_Vector *b) +{ + b[6] = b[3]; + + { + const QT_FT_Pos temp = (b[1].x + b[2].x)/2; + + b[1].x = (b[0].x + b[1].x)/2; + b[5].x = (b[2].x + b[3].x)/2; + b[2].x = (b[1].x + temp)/2; + b[4].x = (b[5].x + temp)/2; + b[3].x = (b[2].x + b[4].x)/2; + } + { + const QT_FT_Pos temp = (b[1].y + b[2].y)/2; + + b[1].y = (b[0].y + b[1].y)/2; + b[5].y = (b[2].y + b[3].y)/2; + b[2].y = (b[1].y + temp)/2; + b[4].y = (b[5].y + temp)/2; + b[3].y = (b[2].y + b[4].y)/2; + } +} + +static inline bool topOrder(const QScanConverter::Line &a, const QScanConverter::Line &b) +{ + return a.top < b.top; +} + +static inline bool xOrder(const QScanConverter::Line *a, const QScanConverter::Line *b) +{ + return a->x < b->x; +} + +template <bool B> +struct QBoolToType +{ + inline bool operator()() const + { + return B; + } +}; + +// should be a member function but VC6 doesn't support member template functions +template <typename T> +void qScanConvert(QScanConverter &d, T allVertical) +{ + if (!d.m_lines.size()) { + d.m_active.reset(); + return; + } + qSort(d.m_lines.data(), d.m_lines.data() + d.m_lines.size(), QT_PREPEND_NAMESPACE(topOrder)); + int line = 0; + for (int y = d.m_lines.first().top; y <= d.m_bottom; ++y) { + for (; line < d.m_lines.size() && d.m_lines.at(line).top == y; ++line) { + // add node to active list + if (allVertical()) { + QScanConverter::Line *l = &d.m_lines.at(line); + d.m_active.resize(d.m_active.size() + 1); + int j; + for (j = d.m_active.size() - 2; j >= 0 && QT_PREPEND_NAMESPACE(xOrder)(l, d.m_active.at(j)); --j) + d.m_active.at(j+1) = d.m_active.at(j); + d.m_active.at(j+1) = l; + } else { + d.m_active << &d.m_lines.at(line); + } + } + + int numActive = d.m_active.size(); + if (!allVertical()) { + // use insertion sort instead of qSort, as the active edge list is quite small + // and in the average case already sorted + for (int i = 1; i < numActive; ++i) { + QScanConverter::Line *l = d.m_active.at(i); + int j; + for (j = i-1; j >= 0 && QT_PREPEND_NAMESPACE(xOrder)(l, d.m_active.at(j)); --j) + d.m_active.at(j+1) = d.m_active.at(j); + d.m_active.at(j+1) = l; + } + } + + int x = 0; + int winding = 0; + for (int i = 0; i < numActive; ++i) { + QScanConverter::Line *node = d.m_active.at(i); + + const int current = Q16Dot16ToInt(node->x); + if (winding & d.m_fillRuleMask) + d.m_spanBuffer->addSpan(x, current - x, y, 0xff); + + x = current; + winding += node->winding; + + if (node->bottom == y) { + // remove node from active list + for (int j = i; j < numActive - 1; ++j) + d.m_active.at(j) = d.m_active.at(j+1); + + d.m_active.resize(--numActive); + --i; + } else if (!allVertical()) + node->x += node->delta; + } + } + d.m_active.reset(); +} + +void QScanConverter::end() +{ + if (m_lines.isEmpty()) + return; + + if (m_lines.size() <= 32) { + bool allVertical = true; + for (int i = 0; i < m_lines.size(); ++i) { + if (m_lines.at(i).delta) { + allVertical = false; + break; + } + } + if (allVertical) + qScanConvert(*this, QBoolToType<true>()); + else + qScanConvert(*this, QBoolToType<false>()); + } else { + for (int chunkTop = m_top; chunkTop <= m_bottom; chunkTop += CHUNK_SIZE) { + prepareChunk(); + + Intersection isect = { 0, 0, 0, 0 }; + + const int chunkBottom = chunkTop + CHUNK_SIZE; + for (int i = 0; i < m_lines.size(); ++i) { + Line &line = m_lines.at(i); + + if ((line.bottom < chunkTop) || (line.top > chunkBottom)) + continue; + + const int top = qMax(0, line.top - chunkTop); + const int bottom = qMin(CHUNK_SIZE, line.bottom + 1 - chunkTop); + allocate(m_size + bottom - top); + + isect.winding = line.winding; + + Intersection *it = m_intersections + top; + Intersection *end = m_intersections + bottom; + + if (line.delta) { + for (; it != end; ++it) { + isect.x = Q16Dot16ToInt(line.x); + line.x += line.delta; + mergeIntersection(it, isect); + } + } else { + isect.x = Q16Dot16ToInt(line.x); + for (; it != end; ++it) + mergeIntersection(it, isect); + } + } + + emitSpans(chunkTop); + } + } + + if (m_alloc > 1024) { + free(m_intersections); + m_alloc = 0; + m_size = 0; + m_intersections = 0; + } + + if (m_lines.size() > 1024) + m_lines.shrink(1024); +} + +inline void QScanConverter::allocate(int size) +{ + if (m_alloc < size) { + int newAlloc = qMax(size, 2 * m_alloc); + m_intersections = q_check_ptr((Intersection *)realloc(m_intersections, newAlloc * sizeof(Intersection))); + m_alloc = newAlloc; + } +} + +inline void QScanConverter::mergeIntersection(Intersection *it, const Intersection &isect) +{ + Intersection *current = it; + + while (isect.x != current->x) { + int &next = isect.x < current->x ? current->left : current->right; + if (next) + current += next; + else { + Intersection *last = m_intersections + m_size; + next = last - current; + *last = isect; + ++m_size; + return; + } + } + + current->winding += isect.winding; +} + +void QScanConverter::mergeCurve(const QT_FT_Vector &pa, const QT_FT_Vector &pb, + const QT_FT_Vector &pc, const QT_FT_Vector &pd) +{ + // make room for 32 splits + QT_FT_Vector beziers[4 + 3 * 32]; + + QT_FT_Vector *b = beziers; + + b[0] = pa; + b[1] = pb; + b[2] = pc; + b[3] = pd; + + const QT_FT_Pos flatness = 16; + + while (b >= beziers) { + QT_FT_Vector delta = { b[3].x - b[0].x, b[3].y - b[0].y }; + QT_FT_Pos l = qAbs(delta.x) + qAbs(delta.y); + + bool belowThreshold; + if (l > 64) { + qlonglong d2 = qAbs(qlonglong(b[1].x-b[0].x) * qlonglong(delta.y) - + qlonglong(b[1].y-b[0].y) * qlonglong(delta.x)); + qlonglong d3 = qAbs(qlonglong(b[2].x-b[0].x) * qlonglong(delta.y) - + qlonglong(b[2].y-b[0].y) * qlonglong(delta.x)); + + qlonglong d = d2 + d3; + + belowThreshold = (d <= qlonglong(flatness) * qlonglong(l)); + } else { + QT_FT_Pos d = qAbs(b[0].x-b[1].x) + qAbs(b[0].y-b[1].y) + + qAbs(b[0].x-b[2].x) + qAbs(b[0].y-b[2].y); + + belowThreshold = (d <= flatness); + } + + if (belowThreshold || b == beziers + 3 * 32) { + mergeLine(b[0], b[3]); + b -= 3; + continue; + } + + split(b); + b += 3; + } +} + +inline bool QScanConverter::clip(Q16Dot16 &xFP, int &iTop, int &iBottom, Q16Dot16 slopeFP, Q16Dot16 edgeFP, int winding) +{ + bool right = edgeFP == m_rightFP; + + if (xFP == edgeFP) { + if ((slopeFP > 0) ^ right) + return false; + else { + Line line = { edgeFP, 0, iTop, iBottom, winding }; + m_lines.add(line); + return true; + } + } + + Q16Dot16 lastFP = xFP + slopeFP * (iBottom - iTop); + + if (lastFP == edgeFP) { + if ((slopeFP < 0) ^ right) + return false; + else { + Line line = { edgeFP, 0, iTop, iBottom, winding }; + m_lines.add(line); + return true; + } + } + + // does line cross edge? + if ((lastFP < edgeFP) ^ (xFP < edgeFP)) { + Q16Dot16 deltaY = Q16Dot16((edgeFP - xFP) / Q16Dot16ToFloat(slopeFP)); + + if ((xFP < edgeFP) ^ right) { + // top segment needs to be clipped + int iHeight = Q16Dot16ToInt(deltaY + 1); + int iMiddle = iTop + iHeight; + + Line line = { edgeFP, 0, iTop, iMiddle, winding }; + m_lines.add(line); + + if (iMiddle != iBottom) { + xFP += slopeFP * (iHeight + 1); + iTop = iMiddle + 1; + } else + return true; + } else { + // bottom segment needs to be clipped + int iHeight = Q16Dot16ToInt(deltaY); + int iMiddle = iTop + iHeight; + + if (iMiddle != iBottom) { + Line line = { edgeFP, 0, iMiddle + 1, iBottom, winding }; + m_lines.add(line); + + iBottom = iMiddle; + } + } + return false; + } else if ((xFP < edgeFP) ^ right) { + Line line = { edgeFP, 0, iTop, iBottom, winding }; + m_lines.add(line); + return true; + } + + return false; +} + +void QScanConverter::mergeLine(QT_FT_Vector a, QT_FT_Vector b) +{ + int winding = 1; + + if (a.y > b.y) { + qSwap(a, b); + winding = -1; + } + + a.x += COORD_OFFSET; + a.y += COORD_OFFSET; + b.x += COORD_OFFSET; + b.y += COORD_OFFSET; + + int iTop = qMax(m_top, int((a.y + 32 - COORD_ROUNDING) >> 6)); + int iBottom = qMin(m_bottom, int((b.y - 32 - COORD_ROUNDING) >> 6)); + + if (iTop <= iBottom) { + Q16Dot16 aFP = Q16Dot16Factor/2 + (a.x << 10) - COORD_ROUNDING; + + if (b.x == a.x) { + Line line = { qBound(m_leftFP, aFP, m_rightFP), 0, iTop, iBottom, winding }; + m_lines.add(line); + } else { + const qreal slope = (b.x - a.x) / qreal(b.y - a.y); + + const Q16Dot16 slopeFP = FloatToQ16Dot16(slope); + + Q16Dot16 xFP = aFP + Q16Dot16Multiply(slopeFP, + IntToQ16Dot16(iTop) + + Q16Dot16Factor/2 - (a.y << 10)); + + if (clip(xFP, iTop, iBottom, slopeFP, m_leftFP, winding)) + return; + + if (clip(xFP, iTop, iBottom, slopeFP, m_rightFP, winding)) + return; + + Q_ASSERT(xFP >= m_leftFP); + + Line line = { xFP, slopeFP, iTop, iBottom, winding }; + m_lines.add(line); + } + } +} + +QRasterizer::QRasterizer() + : d(new QRasterizerPrivate) +{ +} + +QRasterizer::~QRasterizer() +{ + delete d; +} + +void QRasterizer::setAntialiased(bool antialiased) +{ + d->antialiased = antialiased; +} + +void QRasterizer::initialize(ProcessSpans blend, void *data) +{ + d->blend = blend; + d->data = data; +} + +void QRasterizer::setClipRect(const QRect &clipRect) +{ + d->clipRect = clipRect; +} + +static Q16Dot16 intersectPixelFP(int x, Q16Dot16 top, Q16Dot16 bottom, Q16Dot16 leftIntersectX, Q16Dot16 rightIntersectX, Q16Dot16 slope, Q16Dot16 invSlope) +{ + Q16Dot16 leftX = IntToQ16Dot16(x); + Q16Dot16 rightX = IntToQ16Dot16(x) + Q16Dot16Factor; + + Q16Dot16 leftIntersectY, rightIntersectY; + if (slope > 0) { + leftIntersectY = top + Q16Dot16Multiply(leftX - leftIntersectX, invSlope); + rightIntersectY = leftIntersectY + invSlope; + } else { + leftIntersectY = top + Q16Dot16Multiply(leftX - rightIntersectX, invSlope); + rightIntersectY = leftIntersectY + invSlope; + } + + if (leftIntersectX >= leftX && rightIntersectX <= rightX) { + return Q16Dot16Multiply(bottom - top, leftIntersectX - leftX + ((rightIntersectX - leftIntersectX) >> 1)); + } else if (leftIntersectX >= rightX) { + return bottom - top; + } else if (leftIntersectX >= leftX) { + if (slope > 0) { + return (bottom - top) - Q16Dot16FastMultiply((rightX - leftIntersectX) >> 1, rightIntersectY - top); + } else { + return (bottom - top) - Q16Dot16FastMultiply((rightX - leftIntersectX) >> 1, bottom - rightIntersectY); + } + } else if (rightIntersectX <= leftX) { + return 0; + } else if (rightIntersectX <= rightX) { + if (slope > 0) { + return Q16Dot16FastMultiply((rightIntersectX - leftX) >> 1, bottom - leftIntersectY); + } else { + return Q16Dot16FastMultiply((rightIntersectX - leftX) >> 1, leftIntersectY - top); + } + } else { + if (slope > 0) { + return (bottom - rightIntersectY) + ((rightIntersectY - leftIntersectY) >> 1); + } else { + return (rightIntersectY - top) + ((leftIntersectY - rightIntersectY) >> 1); + } + } +} + +static inline bool q26Dot6Compare(qreal p1, qreal p2) +{ + return int((p2 - p1) * 64.) == 0; +} + +static inline qreal qFloorF(qreal v) +{ +#ifdef QT_USE_MATH_H_FLOATS + if (sizeof(qreal) == sizeof(float)) + return floorf(v); + else +#endif + return floor(v); +} + +static inline QPointF snapTo26Dot6Grid(const QPointF &p) +{ + return QPointF(qFloorF(p.x() * 64) * (1 / qreal(64)), + qFloorF(p.y() * 64) * (1 / qreal(64))); +} + +void QRasterizer::rasterizeLine(const QPointF &a, const QPointF &b, qreal width, bool squareCap) +{ + if (a == b || width == 0 || d->clipRect.isEmpty()) + return; + + Q_ASSERT(width > 0.0); + + QPointF pa = a; + QPointF pb = b; + + if (squareCap) { + QPointF delta = pb - pa; + pa -= (0.5f * width) * delta; + pb += (0.5f * width) * delta; + } + + QPointF offs = QPointF(qAbs(b.y() - a.y()), qAbs(b.x() - a.x())) * width * 0.5; + const QRectF clip(d->clipRect.topLeft() - offs, d->clipRect.bottomRight() + QPoint(1, 1) + offs); + + if (!clip.contains(pa) || !clip.contains(pb)) { + qreal t1 = 0; + qreal t2 = 1; + + const qreal o[2] = { pa.x(), pa.y() }; + const qreal d[2] = { pb.x() - pa.x(), pb.y() - pa.y() }; + + const qreal low[2] = { clip.left(), clip.top() }; + const qreal high[2] = { clip.right(), clip.bottom() }; + + for (int i = 0; i < 2; ++i) { + if (d[i] == 0) { + if (o[i] <= low[i] || o[i] >= high[i]) + return; + continue; + } + const qreal d_inv = 1 / d[i]; + qreal t_low = (low[i] - o[i]) * d_inv; + qreal t_high = (high[i] - o[i]) * d_inv; + if (t_low > t_high) + qSwap(t_low, t_high); + if (t1 < t_low) + t1 = t_low; + if (t2 > t_high) + t2 = t_high; + if (t1 >= t2) + return; + } + + QPointF npa = pa + (pb - pa) * t1; + QPointF npb = pa + (pb - pa) * t2; + + pa = npa; + pb = npb; + } + + if (!d->antialiased) { + pa.rx() += (COORD_OFFSET - COORD_ROUNDING)/64.; + pa.ry() += (COORD_OFFSET - COORD_ROUNDING)/64.; + pb.rx() += (COORD_OFFSET - COORD_ROUNDING)/64.; + pb.ry() += (COORD_OFFSET - COORD_ROUNDING)/64.; + } + + { + // old delta + const QPointF d0 = a - b; + const qreal w0 = d0.x() * d0.x() + d0.y() * d0.y(); + + // new delta + const QPointF d = pa - pb; + const qreal w = d.x() * d.x() + d.y() * d.y(); + + if (w == 0) + return; + + // adjust width which is given relative to |b - a| + width *= sqrt(w0 / w); + } + + QSpanBuffer buffer(d->blend, d->data, d->clipRect); + + if (q26Dot6Compare(pa.y(), pb.y())) { + const qreal x = (pa.x() + pb.x()) * 0.5f; + const qreal dx = qAbs(pb.x() - pa.x()) * 0.5f; + + const qreal y = pa.y(); + const qreal dy = width * dx; + + pa = QPointF(x, y - dy); + pb = QPointF(x, y + dy); + + width = 1 / width; + } + + if (q26Dot6Compare(pa.x(), pb.x())) { + if (pa.y() > pb.y()) + qSwap(pa, pb); + + const qreal dy = pb.y() - pa.y(); + const qreal halfWidth = 0.5f * width * dy; + + qreal left = pa.x() - halfWidth; + qreal right = pa.x() + halfWidth; + + left = qBound(qreal(d->clipRect.left()), left, qreal(d->clipRect.right() + 1)); + right = qBound(qreal(d->clipRect.left()), right, qreal(d->clipRect.right() + 1)); + + pa.ry() = qBound(qreal(d->clipRect.top()), pa.y(), qreal(d->clipRect.bottom() + 1)); + pb.ry() = qBound(qreal(d->clipRect.top()), pb.y(), qreal(d->clipRect.bottom() + 1)); + + if (q26Dot6Compare(left, right) || q26Dot6Compare(pa.y(), pb.y())) + return; + + if (d->antialiased) { + const Q16Dot16 iLeft = int(left); + const Q16Dot16 iRight = int(right); + const Q16Dot16 leftWidth = IntToQ16Dot16(iLeft + 1) + - FloatToQ16Dot16(left); + const Q16Dot16 rightWidth = FloatToQ16Dot16(right) + - IntToQ16Dot16(iRight); + + Q16Dot16 coverage[3]; + int x[3]; + int len[3]; + + int n = 1; + if (iLeft == iRight) { + coverage[0] = (leftWidth + rightWidth) * 255; + x[0] = iLeft; + len[0] = 1; + } else { + coverage[0] = leftWidth * 255; + x[0] = iLeft; + len[0] = 1; + if (leftWidth == Q16Dot16Factor) { + len[0] = iRight - iLeft; + } else if (iRight - iLeft > 1) { + coverage[1] = IntToQ16Dot16(255); + x[1] = iLeft + 1; + len[1] = iRight - iLeft - 1; + ++n; + } + if (rightWidth) { + coverage[n] = rightWidth * 255; + x[n] = iRight; + len[n] = 1; + ++n; + } + } + + const Q16Dot16 iTopFP = IntToQ16Dot16(int(pa.y())); + const Q16Dot16 iBottomFP = IntToQ16Dot16(int(pb.y())); + const Q16Dot16 yPa = FloatToQ16Dot16(pa.y()); + const Q16Dot16 yPb = FloatToQ16Dot16(pb.y()); + for (Q16Dot16 yFP = iTopFP; yFP <= iBottomFP; yFP += Q16Dot16Factor) { + const Q16Dot16 rowHeight = qMin(yFP + Q16Dot16Factor, yPb) + - qMax(yFP, yPa); + const int y = Q16Dot16ToInt(yFP); + for (int i = 0; i < n; ++i) { + buffer.addSpan(x[i], len[i], y, + Q16Dot16ToInt(Q16Dot16Multiply(rowHeight, coverage[i]))); + } + } + } else { // aliased + int iTop = int(pa.y() + 0.5f); + int iBottom = pb.y() < 0.5f ? -1 : int(pb.y() - 0.5f); + int iLeft = int(left + 0.5f); + int iRight = right < 0.5f ? -1 : int(right - 0.5f); + + int iWidth = iRight - iLeft + 1; + for (int y = iTop; y <= iBottom; ++y) + buffer.addSpan(iLeft, iWidth, y, 255); + } + } else { + if (pa.y() > pb.y()) + qSwap(pa, pb); + + QPointF delta = pb - pa; + delta *= 0.5f * width; + const QPointF perp(delta.y(), -delta.x()); + + QPointF top; + QPointF left; + QPointF right; + QPointF bottom; + + if (pa.x() < pb.x()) { + top = pa + perp; + left = pa - perp; + right = pb + perp; + bottom = pb - perp; + } else { + top = pa - perp; + left = pb - perp; + right = pa + perp; + bottom = pb + perp; + } + + top = snapTo26Dot6Grid(top); + bottom = snapTo26Dot6Grid(bottom); + left = snapTo26Dot6Grid(left); + right = snapTo26Dot6Grid(right); + + const qreal topBound = qBound(qreal(d->clipRect.top()), top.y(), qreal(d->clipRect.bottom())); + const qreal bottomBound = qBound(qreal(d->clipRect.top()), bottom.y(), qreal(d->clipRect.bottom())); + + const QPointF topLeftEdge = left - top; + const QPointF topRightEdge = right - top; + const QPointF bottomLeftEdge = bottom - left; + const QPointF bottomRightEdge = bottom - right; + + const qreal topLeftSlope = topLeftEdge.x() / topLeftEdge.y(); + const qreal bottomLeftSlope = bottomLeftEdge.x() / bottomLeftEdge.y(); + + const qreal topRightSlope = topRightEdge.x() / topRightEdge.y(); + const qreal bottomRightSlope = bottomRightEdge.x() / bottomRightEdge.y(); + + const Q16Dot16 topLeftSlopeFP = FloatToQ16Dot16(topLeftSlope); + const Q16Dot16 topRightSlopeFP = FloatToQ16Dot16(topRightSlope); + + const Q16Dot16 bottomLeftSlopeFP = FloatToQ16Dot16(bottomLeftSlope); + const Q16Dot16 bottomRightSlopeFP = FloatToQ16Dot16(bottomRightSlope); + + const Q16Dot16 invTopLeftSlopeFP = FloatToQ16Dot16(1 / topLeftSlope); + const Q16Dot16 invTopRightSlopeFP = FloatToQ16Dot16(1 / topRightSlope); + + const Q16Dot16 invBottomLeftSlopeFP = FloatToQ16Dot16(1 / bottomLeftSlope); + const Q16Dot16 invBottomRightSlopeFP = FloatToQ16Dot16(1 / bottomRightSlope); + + if (d->antialiased) { + const Q16Dot16 iTopFP = IntToQ16Dot16(int(topBound)); + const Q16Dot16 iLeftFP = IntToQ16Dot16(int(left.y())); + const Q16Dot16 iRightFP = IntToQ16Dot16(int(right.y())); + const Q16Dot16 iBottomFP = IntToQ16Dot16(int(bottomBound)); + + Q16Dot16 leftIntersectAf = FloatToQ16Dot16(top.x() + (int(topBound) - top.y()) * topLeftSlope); + Q16Dot16 rightIntersectAf = FloatToQ16Dot16(top.x() + (int(topBound) - top.y()) * topRightSlope); + Q16Dot16 leftIntersectBf = 0; + Q16Dot16 rightIntersectBf = 0; + + if (iLeftFP < iTopFP) + leftIntersectBf = FloatToQ16Dot16(left.x() + (int(topBound) - left.y()) * bottomLeftSlope); + + if (iRightFP < iTopFP) + rightIntersectBf = FloatToQ16Dot16(right.x() + (int(topBound) - right.y()) * bottomRightSlope); + + Q16Dot16 rowTop, rowBottomLeft, rowBottomRight, rowTopLeft, rowTopRight, rowBottom; + Q16Dot16 topLeftIntersectAf, topLeftIntersectBf, topRightIntersectAf, topRightIntersectBf; + Q16Dot16 bottomLeftIntersectAf, bottomLeftIntersectBf, bottomRightIntersectAf, bottomRightIntersectBf; + + int leftMin, leftMax, rightMin, rightMax; + + const Q16Dot16 yTopFP = FloatToQ16Dot16(top.y()); + const Q16Dot16 yLeftFP = FloatToQ16Dot16(left.y()); + const Q16Dot16 yRightFP = FloatToQ16Dot16(right.y()); + const Q16Dot16 yBottomFP = FloatToQ16Dot16(bottom.y()); + + rowTop = qMax(iTopFP, yTopFP); + topLeftIntersectAf = leftIntersectAf + + Q16Dot16Multiply(topLeftSlopeFP, rowTop - iTopFP); + topRightIntersectAf = rightIntersectAf + + Q16Dot16Multiply(topRightSlopeFP, rowTop - iTopFP); + + Q16Dot16 yFP = iTopFP; + while (yFP <= iBottomFP) { + rowBottomLeft = qMin(yFP + Q16Dot16Factor, yLeftFP); + rowBottomRight = qMin(yFP + Q16Dot16Factor, yRightFP); + rowTopLeft = qMax(yFP, yLeftFP); + rowTopRight = qMax(yFP, yRightFP); + rowBottom = qMin(yFP + Q16Dot16Factor, yBottomFP); + + if (yFP == iLeftFP) { + const int y = Q16Dot16ToInt(yFP); + leftIntersectBf = FloatToQ16Dot16(left.x() + (y - left.y()) * bottomLeftSlope); + topLeftIntersectBf = leftIntersectBf + Q16Dot16Multiply(bottomLeftSlopeFP, rowTopLeft - yFP); + bottomLeftIntersectAf = leftIntersectAf + Q16Dot16Multiply(topLeftSlopeFP, rowBottomLeft - yFP); + } else { + topLeftIntersectBf = leftIntersectBf; + bottomLeftIntersectAf = leftIntersectAf + topLeftSlopeFP; + } + + if (yFP == iRightFP) { + const int y = Q16Dot16ToInt(yFP); + rightIntersectBf = FloatToQ16Dot16(right.x() + (y - right.y()) * bottomRightSlope); + topRightIntersectBf = rightIntersectBf + Q16Dot16Multiply(bottomRightSlopeFP, rowTopRight - yFP); + bottomRightIntersectAf = rightIntersectAf + Q16Dot16Multiply(topRightSlopeFP, rowBottomRight - yFP); + } else { + topRightIntersectBf = rightIntersectBf; + bottomRightIntersectAf = rightIntersectAf + topRightSlopeFP; + } + + if (yFP == iBottomFP) { + bottomLeftIntersectBf = leftIntersectBf + Q16Dot16Multiply(bottomLeftSlopeFP, rowBottom - yFP); + bottomRightIntersectBf = rightIntersectBf + Q16Dot16Multiply(bottomRightSlopeFP, rowBottom - yFP); + } else { + bottomLeftIntersectBf = leftIntersectBf + bottomLeftSlopeFP; + bottomRightIntersectBf = rightIntersectBf + bottomRightSlopeFP; + } + + if (yFP < iLeftFP) { + leftMin = Q16Dot16ToInt(bottomLeftIntersectAf); + leftMax = Q16Dot16ToInt(topLeftIntersectAf); + } else if (yFP == iLeftFP) { + leftMin = Q16Dot16ToInt(qMax(bottomLeftIntersectAf, topLeftIntersectBf)); + leftMax = Q16Dot16ToInt(qMax(topLeftIntersectAf, bottomLeftIntersectBf)); + } else { + leftMin = Q16Dot16ToInt(topLeftIntersectBf); + leftMax = Q16Dot16ToInt(bottomLeftIntersectBf); + } + + leftMin = qBound(d->clipRect.left(), leftMin, d->clipRect.right()); + leftMax = qBound(d->clipRect.left(), leftMax, d->clipRect.right()); + + if (yFP < iRightFP) { + rightMin = Q16Dot16ToInt(topRightIntersectAf); + rightMax = Q16Dot16ToInt(bottomRightIntersectAf); + } else if (yFP == iRightFP) { + rightMin = Q16Dot16ToInt(qMin(topRightIntersectAf, bottomRightIntersectBf)); + rightMax = Q16Dot16ToInt(qMin(bottomRightIntersectAf, topRightIntersectBf)); + } else { + rightMin = Q16Dot16ToInt(bottomRightIntersectBf); + rightMax = Q16Dot16ToInt(topRightIntersectBf); + } + + rightMin = qBound(d->clipRect.left(), rightMin, d->clipRect.right()); + rightMax = qBound(d->clipRect.left(), rightMax, d->clipRect.right()); + + if (leftMax > rightMax) + leftMax = rightMax; + if (rightMin < leftMin) + rightMin = leftMin; + + Q16Dot16 rowHeight = rowBottom - rowTop; + + int x = leftMin; + while (x <= leftMax) { + Q16Dot16 excluded = 0; + + if (yFP <= iLeftFP) + excluded += intersectPixelFP(x, rowTop, rowBottomLeft, + bottomLeftIntersectAf, topLeftIntersectAf, + topLeftSlopeFP, invTopLeftSlopeFP); + if (yFP >= iLeftFP) + excluded += intersectPixelFP(x, rowTopLeft, rowBottom, + topLeftIntersectBf, bottomLeftIntersectBf, + bottomLeftSlopeFP, invBottomLeftSlopeFP); + + if (x >= rightMin) { + if (yFP <= iRightFP) + excluded += (rowBottomRight - rowTop) - intersectPixelFP(x, rowTop, rowBottomRight, + topRightIntersectAf, bottomRightIntersectAf, + topRightSlopeFP, invTopRightSlopeFP); + if (yFP >= iRightFP) + excluded += (rowBottom - rowTopRight) - intersectPixelFP(x, rowTopRight, rowBottom, + bottomRightIntersectBf, topRightIntersectBf, + bottomRightSlopeFP, invBottomRightSlopeFP); + } + + Q16Dot16 coverage = rowHeight - excluded; + buffer.addSpan(x, 1, Q16Dot16ToInt(yFP), + Q16Dot16ToInt(255 * coverage)); + ++x; + } + if (x < rightMin) { + buffer.addSpan(x, rightMin - x, Q16Dot16ToInt(yFP), + Q16Dot16ToInt(255 * rowHeight)); + x = rightMin; + } + while (x <= rightMax) { + Q16Dot16 excluded = 0; + if (yFP <= iRightFP) + excluded += (rowBottomRight - rowTop) - intersectPixelFP(x, rowTop, rowBottomRight, + topRightIntersectAf, bottomRightIntersectAf, + topRightSlopeFP, invTopRightSlopeFP); + if (yFP >= iRightFP) + excluded += (rowBottom - rowTopRight) - intersectPixelFP(x, rowTopRight, rowBottom, + bottomRightIntersectBf, topRightIntersectBf, + bottomRightSlopeFP, invBottomRightSlopeFP); + + Q16Dot16 coverage = rowHeight - excluded; + buffer.addSpan(x, 1, Q16Dot16ToInt(yFP), + Q16Dot16ToInt(255 * coverage)); + ++x; + } + + leftIntersectAf += topLeftSlopeFP; + leftIntersectBf += bottomLeftSlopeFP; + rightIntersectAf += topRightSlopeFP; + rightIntersectBf += bottomRightSlopeFP; + topLeftIntersectAf = leftIntersectAf; + topRightIntersectAf = rightIntersectAf; + + yFP += Q16Dot16Factor; + rowTop = yFP; + } + } else { // aliased + int iTop = int(top.y() + 0.5f); + int iLeft = left.y() < 0.5f ? -1 : int(left.y() - 0.5f); + int iRight = right.y() < 0.5f ? -1 : int(right.y() - 0.5f); + int iBottom = bottom.y() < 0.5f? -1 : int(bottom.y() - 0.5f); + int iMiddle = qMin(iLeft, iRight); + + Q16Dot16 leftIntersectAf = FloatToQ16Dot16(top.x() + 0.5f + (iTop + 0.5f - top.y()) * topLeftSlope); + Q16Dot16 leftIntersectBf = FloatToQ16Dot16(left.x() + 0.5f + (iLeft + 1.5f - left.y()) * bottomLeftSlope); + Q16Dot16 rightIntersectAf = FloatToQ16Dot16(top.x() - 0.5f + (iTop + 0.5f - top.y()) * topRightSlope); + Q16Dot16 rightIntersectBf = FloatToQ16Dot16(right.x() - 0.5f + (iRight + 1.5f - right.y()) * bottomRightSlope); + + int ny; + int y = iTop; +#define DO_SEGMENT(next, li, ri, ls, rs) \ + ny = qMin(next + 1, d->clipRect.top()); \ + if (y < ny) { \ + li += ls * (ny - y); \ + ri += rs * (ny - y); \ + y = ny; \ + } \ + if (next > d->clipRect.bottom()) \ + next = d->clipRect.bottom(); \ + for (; y <= next; ++y) { \ + const int x1 = qMax(Q16Dot16ToInt(li), d->clipRect.left()); \ + const int x2 = qMin(Q16Dot16ToInt(ri), d->clipRect.right()); \ + if (x2 >= x1) \ + buffer.addSpan(x1, x2 - x1 + 1, y, 255); \ + li += ls; \ + ri += rs; \ + } + + DO_SEGMENT(iMiddle, leftIntersectAf, rightIntersectAf, topLeftSlopeFP, topRightSlopeFP) + DO_SEGMENT(iRight, leftIntersectBf, rightIntersectAf, bottomLeftSlopeFP, topRightSlopeFP) + DO_SEGMENT(iLeft, leftIntersectAf, rightIntersectBf, topLeftSlopeFP, bottomRightSlopeFP); + DO_SEGMENT(iBottom, leftIntersectBf, rightIntersectBf, bottomLeftSlopeFP, bottomRightSlopeFP); +#undef DO_SEGMENT + } + } +} + +void QRasterizer::rasterize(const QT_FT_Outline *outline, Qt::FillRule fillRule) +{ + if (outline->n_points < 3 || outline->n_contours == 0) + return; + + const QT_FT_Vector *points = outline->points; + + QSpanBuffer buffer(d->blend, d->data, d->clipRect); + + // ### QT_FT_Outline already has a bounding rect which is + // ### precomputed at this point, so we should probably just be + // ### using that instead... + QT_FT_Pos min_y = points[0].y, max_y = points[0].y; + for (int i = 1; i < outline->n_points; ++i) { + const QT_FT_Vector &p = points[i]; + min_y = qMin(p.y, min_y); + max_y = qMax(p.y, max_y); + } + + int iTopBound = qMax(d->clipRect.top(), int((min_y + 32 + COORD_OFFSET - COORD_ROUNDING) >> 6)); + int iBottomBound = qMin(d->clipRect.bottom(), int((max_y - 32 + COORD_OFFSET - COORD_ROUNDING) >> 6)); + + if (iTopBound > iBottomBound) + return; + + d->scanConverter.begin(iTopBound, iBottomBound, d->clipRect.left(), d->clipRect.right(), fillRule, &buffer); + + int first = 0; + for (int i = 0; i < outline->n_contours; ++i) { + const int last = outline->contours[i]; + for (int j = first; j < last; ++j) { + if (outline->tags[j+1] == QT_FT_CURVE_TAG_CUBIC) { + Q_ASSERT(outline->tags[j+2] == QT_FT_CURVE_TAG_CUBIC); + d->scanConverter.mergeCurve(points[j], points[j+1], points[j+2], points[j+3]); + j += 2; + } else { + d->scanConverter.mergeLine(points[j], points[j+1]); + } + } + + first = last + 1; + } + + d->scanConverter.end(); +} + +void QRasterizer::rasterize(const QPainterPath &path, Qt::FillRule fillRule) +{ + if (path.isEmpty()) + return; + + QSpanBuffer buffer(d->blend, d->data, d->clipRect); + + QRectF bounds = path.controlPointRect(); + + int iTopBound = qMax(d->clipRect.top(), int(bounds.top() + 0.5 + (COORD_OFFSET - COORD_ROUNDING)/64.)); + int iBottomBound = qMin(d->clipRect.bottom(), int(bounds.bottom() - 0.5 + (COORD_OFFSET - COORD_ROUNDING)/64.)); + + if (iTopBound > iBottomBound) + return; + + d->scanConverter.begin(iTopBound, iBottomBound, d->clipRect.left(), d->clipRect.right(), fillRule, &buffer); + + int subpathStart = 0; + QT_FT_Vector last = { 0, 0 }; + for (int i = 0; i < path.elementCount(); ++i) { + switch (path.elementAt(i).type) { + case QPainterPath::LineToElement: + { + QT_FT_Vector p1 = last; + QT_FT_Vector p2 = PointToVector(path.elementAt(i)); + d->scanConverter.mergeLine(p1, p2); + last = p2; + break; + } + case QPainterPath::MoveToElement: + { + if (i != 0) { + QT_FT_Vector first = PointToVector(path.elementAt(subpathStart)); + // close previous subpath + if (first.x != last.x || first.y != last.y) + d->scanConverter.mergeLine(last, first); + } + subpathStart = i; + last = PointToVector(path.elementAt(i)); + break; + } + case QPainterPath::CurveToElement: + { + QT_FT_Vector p1 = last; + QT_FT_Vector p2 = PointToVector(path.elementAt(i)); + QT_FT_Vector p3 = PointToVector(path.elementAt(++i)); + QT_FT_Vector p4 = PointToVector(path.elementAt(++i)); + d->scanConverter.mergeCurve(p1, p2, p3, p4); + last = p4; + break; + } + default: + Q_ASSERT(false); + break; + } + } + + QT_FT_Vector first = PointToVector(path.elementAt(subpathStart)); + + // close path + if (first.x != last.x || first.y != last.y) + d->scanConverter.mergeLine(last, first); + + d->scanConverter.end(); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qrasterizer_p.h b/src/gui/painting/qrasterizer_p.h new file mode 100644 index 0000000000..d640600172 --- /dev/null +++ b/src/gui/painting/qrasterizer_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRASTERIZER_P_H +#define QRASTERIZER_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 "QtCore/qglobal.h" +#include "QtGui/qpainter.h" + +#include <private/qdrawhelper_p.h> +#include <private/qrasterdefs_p.h> + +QT_BEGIN_NAMESPACE + +struct QSpanData; +class QRasterBuffer; +class QRasterizerPrivate; + +class +#ifdef Q_WS_QWS +Q_GUI_EXPORT +#endif +QRasterizer +{ +public: + QRasterizer(); + ~QRasterizer(); + + void setAntialiased(bool antialiased); + void setClipRect(const QRect &clipRect); + + void initialize(ProcessSpans blend, void *data); + + void rasterize(const QT_FT_Outline *outline, Qt::FillRule fillRule); + void rasterize(const QPainterPath &path, Qt::FillRule fillRule); + + // width should be in units of |a-b| + void rasterizeLine(const QPointF &a, const QPointF &b, qreal width, bool squareCap = false); + +private: + QRasterizerPrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qregion.cpp b/src/gui/painting/qregion.cpp new file mode 100644 index 0000000000..d713346902 --- /dev/null +++ b/src/gui/painting/qregion.cpp @@ -0,0 +1,4365 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qregion.h" +#include "qpainterpath.h" +#include "qpolygon.h" +#include "qbuffer.h" +#include "qdatastream.h" +#include "qvariant.h" +#include "qvarlengtharray.h" + +#include <qdebug.h> + +#if defined(Q_OS_UNIX) || defined(Q_WS_WIN) +#include "qimage.h" +#include "qbitmap.h" +#include <stdlib.h> +#endif + +QT_BEGIN_NAMESPACE + +/*! + \class QRegion + \brief The QRegion class specifies a clip region for a painter. + + \ingroup painting + \ingroup shared + + QRegion is used with QPainter::setClipRegion() to limit the paint + area to what needs to be painted. There is also a QWidget::repaint() + function that takes a QRegion parameter. QRegion is the best tool for + minimizing the amount of screen area to be updated by a repaint. + + This class is not suitable for constructing shapes for rendering, especially + as outlines. Use QPainterPath to create paths and shapes for use with + QPainter. + + QRegion is an \l{implicitly shared} class. + + \section1 Creating and Using Regions + + A region can be created from a rectangle, an ellipse, a polygon or + a bitmap. Complex regions may be created by combining simple + regions using united(), intersected(), subtracted(), or xored() (exclusive + or). You can move a region using translate(). + + You can test whether a region isEmpty() or if it + contains() a QPoint or QRect. The bounding rectangle can be found + with boundingRect(). + + The function rects() gives a decomposition of the region into + rectangles. + + Example of using complex regions: + \snippet doc/src/snippets/code/src_gui_painting_qregion.cpp 0 + + \section1 Additional License Information + + On Embedded Linux, Windows CE and X11 platforms, parts of this class rely on + code obtained under the following licenses: + + \legalese + Copyright (c) 1987 X Consortium + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the X Consortium shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the X Consortium. + \endlegalese + + \br + + \legalese + Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation, and that the name of Digital not be + used in advertising or publicity pertaining to distribution of the + software without specific, written prior permission. + + DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + SOFTWARE. + \endlegalese + + \sa QPainter::setClipRegion(), QPainter::setClipRect(), QPainterPath +*/ + + +/*! + \enum QRegion::RegionType + + Specifies the shape of the region to be created. + + \value Rectangle the region covers the entire rectangle. + \value Ellipse the region is an ellipse inside the rectangle. +*/ + +/*! + \fn void QRegion::translate(const QPoint &point) + + \overload + + Translates the region \a{point}\e{.x()} along the x axis and + \a{point}\e{.y()} along the y axis, relative to the current + position. Positive values move the region to the right and down. + + Translates to the given \a point. +*/ + +/*! + \fn Handle QRegion::handle() const + + Returns a platform-specific region handle. The \c Handle type is + \c HRGN on Windows, \c Region on X11, and \c RgnHandle on Mac OS + X. On \l{Qt for Embedded Linux} it is \c {void *}. + + \warning This function is not portable. +*/ + +/***************************************************************************** + QRegion member functions + *****************************************************************************/ + +/*! + \fn QRegion::QRegion() + + Constructs an empty region. + + \sa isEmpty() +*/ + +/*! + \fn QRegion::QRegion(const QRect &r, RegionType t) + \overload + + Create a region based on the rectange \a r with region type \a t. + + If the rectangle is invalid a null region will be created. + + \sa QRegion::RegionType +*/ + +/*! + \fn QRegion::QRegion(const QPolygon &a, Qt::FillRule fillRule) + + Constructs a polygon region from the point array \a a with the fill rule + specified by \a fillRule. + + If \a fillRule is \l{Qt::WindingFill}, the polygon region is defined + using the winding algorithm; if it is \l{Qt::OddEvenFill}, the odd-even fill + algorithm is used. + + \warning This constructor can be used to create complex regions that will + slow down painting when used. +*/ + +/*! + \fn QRegion::QRegion(const QRegion &r) + + Constructs a new region which is equal to region \a r. +*/ + +/*! + \fn QRegion::QRegion(const QBitmap &bm) + + Constructs a region from the bitmap \a bm. + + The resulting region consists of the pixels in bitmap \a bm that + are Qt::color1, as if each pixel was a 1 by 1 rectangle. + + This constructor may create complex regions that will slow down + painting when used. Note that drawing masked pixmaps can be done + much faster using QPixmap::setMask(). +*/ + +/*! + Constructs a rectangular or elliptic region. + + If \a t is \c Rectangle, the region is the filled rectangle (\a x, + \a y, \a w, \a h). If \a t is \c Ellipse, the region is the filled + ellipse with center at (\a x + \a w / 2, \a y + \a h / 2) and size + (\a w ,\a h). +*/ +QRegion::QRegion(int x, int y, int w, int h, RegionType t) +{ + QRegion tmp(QRect(x, y, w, h), t); + tmp.d->ref.ref(); + d = tmp.d; +} + +#ifdef QT3_SUPPORT +/*! + Use the constructor tha takes a Qt::FillRule as the second + argument instead. +*/ +QRegion::QRegion(const QPolygon &pa, bool winding) +{ + new (this) QRegion(pa, winding ? Qt::WindingFill : Qt::OddEvenFill); +} +#endif + +/*! + \fn QRegion::~QRegion() + \internal + + Destroys the region. +*/ + +void QRegion::detach() +{ + if (d->ref != 1) + *this = copy(); +#if defined(Q_WS_X11) + else if (d->xrectangles) { + free(d->xrectangles); + d->xrectangles = 0; + } +#endif +} + +// duplicates in qregion_win.cpp and qregion_wce.cpp +#define QRGN_SETRECT 1 // region stream commands +#define QRGN_SETELLIPSE 2 // (these are internal) +#define QRGN_SETPTARRAY_ALT 3 +#define QRGN_SETPTARRAY_WIND 4 +#define QRGN_TRANSLATE 5 +#define QRGN_OR 6 +#define QRGN_AND 7 +#define QRGN_SUB 8 +#define QRGN_XOR 9 +#define QRGN_RECTS 10 + + +#ifndef QT_NO_DATASTREAM + +/* + Executes region commands in the internal buffer and rebuilds the + original region. + + We do this when we read a region from the data stream. + + If \a ver is non-0, uses the format version \a ver on reading the + byte array. +*/ +void QRegion::exec(const QByteArray &buffer, int ver, QDataStream::ByteOrder byteOrder) +{ + QByteArray copy = buffer; + QDataStream s(©, QIODevice::ReadOnly); + if (ver) + s.setVersion(ver); + s.setByteOrder(byteOrder); + QRegion rgn; +#ifndef QT_NO_DEBUG + int test_cnt = 0; +#endif + while (!s.atEnd()) { + qint32 id; + if (s.version() == 1) { + int id_int; + s >> id_int; + id = id_int; + } else { + s >> id; + } +#ifndef QT_NO_DEBUG + if (test_cnt > 0 && id != QRGN_TRANSLATE) + qWarning("QRegion::exec: Internal error"); + test_cnt++; +#endif + if (id == QRGN_SETRECT || id == QRGN_SETELLIPSE) { + QRect r; + s >> r; + rgn = QRegion(r, id == QRGN_SETRECT ? Rectangle : Ellipse); + } else if (id == QRGN_SETPTARRAY_ALT || id == QRGN_SETPTARRAY_WIND) { + QPolygon a; + s >> a; + rgn = QRegion(a, id == QRGN_SETPTARRAY_WIND ? Qt::WindingFill : Qt::OddEvenFill); + } else if (id == QRGN_TRANSLATE) { + QPoint p; + s >> p; + rgn.translate(p.x(), p.y()); + } else if (id >= QRGN_OR && id <= QRGN_XOR) { + QByteArray bop1, bop2; + QRegion r1, r2; + s >> bop1; + r1.exec(bop1); + s >> bop2; + r2.exec(bop2); + + switch (id) { + case QRGN_OR: + rgn = r1.united(r2); + break; + case QRGN_AND: + rgn = r1.intersected(r2); + break; + case QRGN_SUB: + rgn = r1.subtracted(r2); + break; + case QRGN_XOR: + rgn = r1.xored(r2); + break; + } + } else if (id == QRGN_RECTS) { + // (This is the only form used in Qt 2.0) + quint32 n; + s >> n; + QRect r; + for (int i=0; i<(int)n; i++) { + s >> r; + rgn = rgn.united(QRegion(r)); + } + } + } + *this = rgn; +} + + +/***************************************************************************** + QRegion stream functions + *****************************************************************************/ + +/*! + \fn QRegion &QRegion::operator=(const QRegion &r) + + Assigns \a r to this region and returns a reference to the region. +*/ + +/*! + \fn void QRegion::swap(QRegion &other) + \since 4.8 + + Swaps region \a other with this region. This operation is very + fast and never fails. +*/ + +/*! + \relates QRegion + + Writes the region \a r to the stream \a s and returns a reference + to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ + +QDataStream &operator<<(QDataStream &s, const QRegion &r) +{ + QVector<QRect> a = r.rects(); + if (a.isEmpty()) { + s << (quint32)0; + } else { + if (s.version() == 1) { + int i; + for (i = a.size() - 1; i > 0; --i) { + s << (quint32)(12 + i * 24); + s << (int)QRGN_OR; + } + for (i = 0; i < a.size(); ++i) { + s << (quint32)(4+8) << (int)QRGN_SETRECT << a[i]; + } + } else { + s << (quint32)(4 + 4 + 16 * a.size()); // 16: storage size of QRect + s << (qint32)QRGN_RECTS; + s << a; + } + } + return s; +} + +/*! + \relates QRegion + + Reads a region from the stream \a s into \a r and returns a + reference to the stream. + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ + +QDataStream &operator>>(QDataStream &s, QRegion &r) +{ + QByteArray b; + s >> b; + r.exec(b, s.version(), s.byteOrder()); + return s; +} +#endif //QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug s, const QRegion &r) +{ + QVector<QRect> rects = r.rects(); + s.nospace() << "QRegion(size=" << rects.size() << "), " + << "bounds = " << r.boundingRect() << '\n'; + for (int i=0; i<rects.size(); ++i) + s << "- " << i << rects.at(i) << '\n'; + return s; +} +#endif + + +// These are not inline - they can be implemented better on some platforms +// (eg. Windows at least provides 3-variable operations). For now, simple. + + +/*! + Applies the united() function to this region and \a r. \c r1|r2 is + equivalent to \c r1.united(r2). + + \sa united(), operator+() +*/ +const QRegion QRegion::operator|(const QRegion &r) const + { return united(r); } + +/*! + Applies the united() function to this region and \a r. \c r1+r2 is + equivalent to \c r1.united(r2). + + \sa united(), operator|() +*/ +const QRegion QRegion::operator+(const QRegion &r) const + { return united(r); } + +/*! + \overload + \since 4.4 + */ +const QRegion QRegion::operator+(const QRect &r) const + { return united(r); } + +/*! + Applies the intersected() function to this region and \a r. \c r1&r2 + is equivalent to \c r1.intersected(r2). + + \sa intersected() +*/ +const QRegion QRegion::operator&(const QRegion &r) const + { return intersected(r); } + +/*! + \overload + \since 4.4 + */ +const QRegion QRegion::operator&(const QRect &r) const +{ + return intersected(r); +} + +/*! + Applies the subtracted() function to this region and \a r. \c r1-r2 + is equivalent to \c r1.subtracted(r2). + + \sa subtracted() +*/ +const QRegion QRegion::operator-(const QRegion &r) const + { return subtracted(r); } + +/*! + Applies the xored() function to this region and \a r. \c r1^r2 is + equivalent to \c r1.xored(r2). + + \sa xored() +*/ +const QRegion QRegion::operator^(const QRegion &r) const + { return xored(r); } + +/*! + Applies the united() function to this region and \a r and assigns + the result to this region. \c r1|=r2 is equivalent to \c + {r1 = r1.united(r2)}. + + \sa united() +*/ +QRegion& QRegion::operator|=(const QRegion &r) + { return *this = *this | r; } + +/*! + \fn QRegion& QRegion::operator+=(const QRect &rect) + + Returns a region that is the union of this region with the specified \a rect. + + \sa united() +*/ +/*! + \fn QRegion& QRegion::operator+=(const QRegion &r) + + Applies the united() function to this region and \a r and assigns + the result to this region. \c r1+=r2 is equivalent to \c + {r1 = r1.united(r2)}. + + \sa intersected() +*/ +#if !defined (Q_OS_UNIX) && !defined (Q_WS_WIN) +QRegion& QRegion::operator+=(const QRect &r) +{ + return operator+=(QRegion(r)); +} +#endif + +/*! + \fn QRegion& QRegion::operator&=(const QRegion &r) + + Applies the intersected() function to this region and \a r and + assigns the result to this region. \c r1&=r2 is equivalent to \c + r1 = r1.intersected(r2). + + \sa intersected() +*/ +QRegion& QRegion::operator&=(const QRegion &r) + { return *this = *this & r; } + +/*! + \overload + \since 4.4 + */ +#if defined (Q_OS_UNIX) || defined (Q_WS_WIN) +QRegion& QRegion::operator&=(const QRect &r) +{ + return *this = *this & r; +} +#else +QRegion& QRegion::operator&=(const QRect &r) +{ + return *this &= (QRegion(r)); +} +#endif + +/*! + \fn QRegion& QRegion::operator-=(const QRegion &r) + + Applies the subtracted() function to this region and \a r and + assigns the result to this region. \c r1-=r2 is equivalent to \c + {r1 = r1.subtracted(r2)}. + + \sa subtracted() +*/ +QRegion& QRegion::operator-=(const QRegion &r) + { return *this = *this - r; } + +/*! + Applies the xored() function to this region and \a r and + assigns the result to this region. \c r1^=r2 is equivalent to \c + {r1 = r1.xored(r2)}. + + \sa xored() +*/ +QRegion& QRegion::operator^=(const QRegion &r) + { return *this = *this ^ r; } + +/*! + \fn bool QRegion::operator!=(const QRegion &other) const + + Returns true if this region is different from the \a other region; + otherwise returns false. +*/ + +/*! + Returns the region as a QVariant +*/ +QRegion::operator QVariant() const +{ + return QVariant(QVariant::Region, this); +} + +/*! + \fn bool QRegion::operator==(const QRegion &r) const + + Returns true if the region is equal to \a r; otherwise returns + false. +*/ + +/*! + \fn bool QRegion::isNull() const + + Use isEmpty() instead. +*/ + + +/*! + \fn void QRegion::translate(int dx, int dy) + + Translates (moves) the region \a dx along the X axis and \a dy + along the Y axis. +*/ + +/*! + \fn QRegion QRegion::translated(const QPoint &p) const + \overload + \since 4.1 + + Returns a copy of the regtion that is translated \a{p}\e{.x()} + along the x axis and \a{p}\e{.y()} along the y axis, relative to + the current position. Positive values move the rectangle to the + right and down. + + \sa translate() +*/ + +/*! + \since 4.1 + + Returns a copy of the region that is translated \a dx along the + x axis and \a dy along the y axis, relative to the current + position. Positive values move the region to the right and + down. + + \sa translate() +*/ + +QRegion +QRegion::translated(int dx, int dy) const +{ + QRegion ret(*this); + ret.translate(dx, dy); + return ret; +} + + +inline bool rect_intersects(const QRect &r1, const QRect &r2) +{ + return (r1.right() >= r2.left() && r1.left() <= r2.right() && + r1.bottom() >= r2.top() && r1.top() <= r2.bottom()); +} + +/*! + \since 4.2 + + Returns true if this region intersects with \a region, otherwise + returns false. +*/ +bool QRegion::intersects(const QRegion ®ion) const +{ + if (isEmpty() || region.isEmpty()) + return false; + + if (!rect_intersects(boundingRect(), region.boundingRect())) + return false; + if (rectCount() == 1 && region.rectCount() == 1) + return true; + + const QVector<QRect> myRects = rects(); + const QVector<QRect> otherRects = region.rects(); + + for (QVector<QRect>::const_iterator i1 = myRects.constBegin(); i1 < myRects.constEnd(); ++i1) + for (QVector<QRect>::const_iterator i2 = otherRects.constBegin(); i2 < otherRects.constEnd(); ++i2) + if (rect_intersects(*i1, *i2)) + return true; + return false; +} + +/*! + \fn bool QRegion::intersects(const QRect &rect) const + \since 4.2 + + Returns true if this region intersects with \a rect, otherwise + returns false. +*/ + + +#if !defined (Q_OS_UNIX) && !defined (Q_WS_WIN) +/*! + \overload + \since 4.4 +*/ +QRegion QRegion::intersect(const QRect &r) const +{ + return intersect(QRegion(r)); +} +#endif + +/*! + \obsolete + \fn int QRegion::numRects() const + \since 4.4 + + Returns the number of rectangles that will be returned in rects(). +*/ + +/*! + \fn int QRegion::rectCount() const + \since 4.6 + + Returns the number of rectangles that will be returned in rects(). +*/ + +/*! + \fn bool QRegion::isEmpty() const + + Returns true if the region is empty; otherwise returns false. An + empty region is a region that contains no points. + + Example: + \snippet doc/src/snippets/code/src_gui_painting_qregion_unix.cpp 0 +*/ + +/*! + \fn bool QRegion::contains(const QPoint &p) const + + Returns true if the region contains the point \a p; otherwise + returns false. +*/ + +/*! + \fn bool QRegion::contains(const QRect &r) const + \overload + + Returns true if the region overlaps the rectangle \a r; otherwise + returns false. +*/ + +/*! + \fn QRegion QRegion::unite(const QRegion &r) const + \obsolete + + Use united(\a r) instead. +*/ + +/*! + \fn QRegion QRegion::unite(const QRect &rect) const + \since 4.4 + \obsolete + + Use united(\a rect) instead. +*/ + +/*! + \fn QRegion QRegion::united(const QRect &rect) const + \since 4.4 + + Returns a region which is the union of this region and the given \a rect. + + \sa intersected(), subtracted(), xored() +*/ + +/*! + \fn QRegion QRegion::united(const QRegion &r) const + \since 4.2 + + Returns a region which is the union of this region and \a r. + + \img runion.png Region Union + + The figure shows the union of two elliptical regions. + + \sa intersected(), subtracted(), xored() +*/ + +/*! + \fn QRegion QRegion::intersect(const QRegion &r) const + \obsolete + + Use intersected(\a r) instead. +*/ + +/*! + \fn QRegion QRegion::intersect(const QRect &rect) const + \since 4.4 + \obsolete + + Use intersected(\a rect) instead. +*/ + +/*! + \fn QRegion QRegion::intersected(const QRect &rect) const + \since 4.4 + + Returns a region which is the intersection of this region and the given \a rect. + + \sa subtracted(), united(), xored() +*/ + +/*! + \fn QRegion QRegion::intersected(const QRegion &r) const + \since 4.2 + + Returns a region which is the intersection of this region and \a r. + + \img rintersect.png Region Intersection + + The figure shows the intersection of two elliptical regions. + + \sa subtracted(), united(), xored() +*/ + +/*! + \fn QRegion QRegion::subtract(const QRegion &r) const + \obsolete + + Use subtracted(\a r) instead. +*/ + +/*! + \fn QRegion QRegion::subtracted(const QRegion &r) const + \since 4.2 + + Returns a region which is \a r subtracted from this region. + + \img rsubtract.png Region Subtraction + + The figure shows the result when the ellipse on the right is + subtracted from the ellipse on the left (\c {left - right}). + + \sa intersected(), united(), xored() +*/ + +/*! + \fn QRegion QRegion::eor(const QRegion &r) const + \obsolete + + Use xored(\a r) instead. +*/ + +/*! + \fn QRegion QRegion::xored(const QRegion &r) const + \since 4.2 + + Returns a region which is the exclusive or (XOR) of this region + and \a r. + + \img rxor.png Region XORed + + The figure shows the exclusive or of two elliptical regions. + + \sa intersected(), united(), subtracted() +*/ + +/*! + \fn QRect QRegion::boundingRect() const + + Returns the bounding rectangle of this region. An empty region + gives a rectangle that is QRect::isNull(). +*/ + +/*! + \fn QVector<QRect> QRegion::rects() const + + Returns an array of non-overlapping rectangles that make up the + region. + + The union of all the rectangles is equal to the original region. +*/ + +/*! + \fn void QRegion::setRects(const QRect *rects, int number) + + Sets the region using the array of rectangles specified by \a rects and + \a number. + The rectangles \e must be optimally Y-X sorted and follow these restrictions: + + \list + \o The rectangles must not intersect. + \o All rectangles with a given top coordinate must have the same height. + \o No two rectangles may abut horizontally (they should be combined + into a single wider rectangle in that case). + \o The rectangles must be sorted in ascending order, with Y as the major + sort key and X as the minor sort key. + \endlist + \omit + Only some platforms have these restrictions (Qt for Embedded Linux, X11 and Mac OS X). + \endomit +*/ + +namespace { + +struct Segment +{ + Segment() {} + Segment(const QPoint &p) + : added(false) + , point(p) + { + } + + int left() const + { + return qMin(point.x(), next->point.x()); + } + + int right() const + { + return qMax(point.x(), next->point.x()); + } + + bool overlaps(const Segment &other) const + { + return left() < other.right() && other.left() < right(); + } + + void connect(Segment &other) + { + next = &other; + other.prev = this; + + horizontal = (point.y() == other.point.y()); + } + + void merge(Segment &other) + { + if (right() <= other.right()) { + QPoint p = other.point; + Segment *oprev = other.prev; + + other.point = point; + other.prev = prev; + prev->next = &other; + + point = p; + prev = oprev; + oprev->next = this; + } else { + Segment *onext = other.next; + other.next = next; + next->prev = &other; + + next = onext; + next->prev = this; + } + } + + int horizontal : 1; + int added : 1; + + QPoint point; + Segment *prev; + Segment *next; +}; + +void mergeSegments(Segment *a, int na, Segment *b, int nb) +{ + int i = 0; + int j = 0; + + while (i != na && j != nb) { + Segment &sa = a[i]; + Segment &sb = b[j]; + const int ra = sa.right(); + const int rb = sb.right(); + if (sa.overlaps(sb)) + sa.merge(sb); + i += (rb >= ra); + j += (ra >= rb); + } +} + +void addSegmentsToPath(Segment *segment, QPainterPath &path) +{ + Segment *current = segment; + path.moveTo(current->point); + + current->added = true; + + Segment *last = current; + current = current->next; + while (current != segment) { + if (current->horizontal != last->horizontal) + path.lineTo(current->point); + current->added = true; + last = current; + current = current->next; + } +} + +} + +Q_AUTOTEST_EXPORT QPainterPath qt_regionToPath(const QRegion ®ion) +{ + QPainterPath result; + if (region.rectCount() == 1) { + result.addRect(region.boundingRect()); + return result; + } + + const QVector<QRect> rects = region.rects(); + + QVarLengthArray<Segment> segments; + segments.resize(4 * rects.size()); + + const QRect *rect = rects.constData(); + const QRect *end = rect + rects.size(); + + int lastRowSegmentCount = 0; + Segment *lastRowSegments = 0; + + int lastSegment = 0; + int lastY = 0; + while (rect != end) { + const int y = rect[0].y(); + int count = 0; + while (&rect[count] != end && rect[count].y() == y) + ++count; + + for (int i = 0; i < count; ++i) { + int offset = lastSegment + i; + segments[offset] = Segment(rect[i].topLeft()); + segments[offset += count] = Segment(rect[i].topRight() + QPoint(1, 0)); + segments[offset += count] = Segment(rect[i].bottomRight() + QPoint(1, 1)); + segments[offset += count] = Segment(rect[i].bottomLeft() + QPoint(0, 1)); + + offset = lastSegment + i; + for (int j = 0; j < 4; ++j) + segments[offset + j * count].connect(segments[offset + ((j + 1) % 4) * count]); + } + + if (lastRowSegments && lastY == y) + mergeSegments(lastRowSegments, lastRowSegmentCount, &segments[lastSegment], count); + + lastRowSegments = &segments[lastSegment + 2 * count]; + lastRowSegmentCount = count; + lastSegment += 4 * count; + lastY = y + rect[0].height(); + rect += count; + } + + for (int i = 0; i < lastSegment; ++i) { + Segment *segment = &segments[i]; + if (!segment->added) + addSegmentsToPath(segment, result); + } + + return result; +} + +#if defined(Q_OS_UNIX) || defined(Q_WS_WIN) + +//#define QT_REGION_DEBUG +/* + * clip region + */ + +struct QRegionPrivate { + int numRects; + QVector<QRect> rects; + QRect extents; + QRect innerRect; + int innerArea; + + inline QRegionPrivate() : numRects(0), innerArea(-1) {} + inline QRegionPrivate(const QRect &r) { + numRects = 1; + extents = r; + innerRect = r; + innerArea = r.width() * r.height(); + } + + inline QRegionPrivate(const QRegionPrivate &r) { + rects = r.rects; + numRects = r.numRects; + extents = r.extents; + innerRect = r.innerRect; + innerArea = r.innerArea; + } + + inline QRegionPrivate &operator=(const QRegionPrivate &r) { + rects = r.rects; + numRects = r.numRects; + extents = r.extents; + innerRect = r.innerRect; + innerArea = r.innerArea; + return *this; + } + + void intersect(const QRect &r); + + /* + * Returns true if r is guaranteed to be fully contained in this region. + * A false return value does not guarantee the opposite. + */ + inline bool contains(const QRegionPrivate &r) const { + return contains(r.extents); + } + + inline bool contains(const QRect &r2) const { + const QRect &r1 = innerRect; + return r2.left() >= r1.left() && r2.right() <= r1.right() + && r2.top() >= r1.top() && r2.bottom() <= r1.bottom(); + } + + /* + * Returns true if this region is guaranteed to be fully contained in r. + */ + inline bool within(const QRect &r1) const { + const QRect &r2 = extents; + return r2.left() >= r1.left() && r2.right() <= r1.right() + && r2.top() >= r1.top() && r2.bottom() <= r1.bottom(); + } + + inline void updateInnerRect(const QRect &rect) { + const int area = rect.width() * rect.height(); + if (area > innerArea) { + innerArea = area; + innerRect = rect; + } + } + + inline void vectorize() { + if (numRects == 1) { + if (!rects.size()) + rects.resize(1); + rects[0] = extents; + } + } + + inline void append(const QRect *r); + void append(const QRegionPrivate *r); + void prepend(const QRect *r); + void prepend(const QRegionPrivate *r); + inline bool canAppend(const QRect *r) const; + inline bool canAppend(const QRegionPrivate *r) const; + inline bool canPrepend(const QRect *r) const; + inline bool canPrepend(const QRegionPrivate *r) const; + + inline bool mergeFromRight(QRect *left, const QRect *right); + inline bool mergeFromLeft(QRect *left, const QRect *right); + inline bool mergeFromBelow(QRect *top, const QRect *bottom, + const QRect *nextToTop, + const QRect *nextToBottom); + inline bool mergeFromAbove(QRect *bottom, const QRect *top, + const QRect *nextToBottom, + const QRect *nextToTop); + +#ifdef QT_REGION_DEBUG + void selfTest() const; +#endif +}; + +static inline bool isEmptyHelper(const QRegionPrivate *preg) +{ + return !preg || preg->numRects == 0; +} + +static inline bool canMergeFromRight(const QRect *left, const QRect *right) +{ + return (right->top() == left->top() + && right->bottom() == left->bottom() + && right->left() <= (left->right() + 1)); +} + +static inline bool canMergeFromLeft(const QRect *right, const QRect *left) +{ + return canMergeFromRight(left, right); +} + +bool QRegionPrivate::mergeFromRight(QRect *left, const QRect *right) +{ + if (canMergeFromRight(left, right)) { + left->setRight(right->right()); + updateInnerRect(*left); + return true; + } + return false; +} + +bool QRegionPrivate::mergeFromLeft(QRect *right, const QRect *left) +{ + if (canMergeFromLeft(right, left)) { + right->setLeft(left->left()); + updateInnerRect(*right); + return true; + } + return false; +} + +static inline bool canMergeFromBelow(const QRect *top, const QRect *bottom, + const QRect *nextToTop, + const QRect *nextToBottom) +{ + if (nextToTop && nextToTop->y() == top->y()) + return false; + if (nextToBottom && nextToBottom->y() == bottom->y()) + return false; + + return ((top->bottom() >= (bottom->top() - 1)) + && top->left() == bottom->left() + && top->right() == bottom->right()); +} + +bool QRegionPrivate::mergeFromBelow(QRect *top, const QRect *bottom, + const QRect *nextToTop, + const QRect *nextToBottom) +{ + if (canMergeFromBelow(top, bottom, nextToTop, nextToBottom)) { + top->setBottom(bottom->bottom()); + updateInnerRect(*top); + return true; + } + return false; +} + +bool QRegionPrivate::mergeFromAbove(QRect *bottom, const QRect *top, + const QRect *nextToBottom, + const QRect *nextToTop) +{ + if (canMergeFromBelow(top, bottom, nextToTop, nextToBottom)) { + bottom->setTop(top->top()); + updateInnerRect(*bottom); + return true; + } + return false; +} + +static inline QRect qt_rect_intersect_normalized(const QRect &r1, + const QRect &r2) +{ + QRect r; + r.setLeft(qMax(r1.left(), r2.left())); + r.setRight(qMin(r1.right(), r2.right())); + r.setTop(qMax(r1.top(), r2.top())); + r.setBottom(qMin(r1.bottom(), r2.bottom())); + return r; +} + +void QRegionPrivate::intersect(const QRect &rect) +{ + Q_ASSERT(extents.intersects(rect)); + Q_ASSERT(numRects > 1); + +#ifdef QT_REGION_DEBUG + selfTest(); +#endif + + const QRect r = rect.normalized(); + extents = QRect(); + innerRect = QRect(); + innerArea = -1; + + QRect *dest = rects.data(); + const QRect *src = dest; + int n = numRects; + numRects = 0; + while (n--) { + *dest = qt_rect_intersect_normalized(*src++, r); + if (dest->isEmpty()) + continue; + + if (numRects == 0) { + extents = *dest; + } else { + extents.setLeft(qMin(extents.left(), dest->left())); + // hw: extents.top() will never change after initialization + //extents.setTop(qMin(extents.top(), dest->top())); + extents.setRight(qMax(extents.right(), dest->right())); + extents.setBottom(qMax(extents.bottom(), dest->bottom())); + + const QRect *nextToLast = (numRects > 1 ? dest - 2 : 0); + + // mergeFromBelow inlined and optimized + if (canMergeFromBelow(dest - 1, dest, nextToLast, 0)) { + if (!n || src->y() != dest->y() || src->left() > r.right()) { + QRect *prev = dest - 1; + prev->setBottom(dest->bottom()); + updateInnerRect(*prev); + continue; + } + } + } + updateInnerRect(*dest); + ++dest; + ++numRects; + } +#ifdef QT_REGION_DEBUG + selfTest(); +#endif +} + +void QRegionPrivate::append(const QRect *r) +{ + Q_ASSERT(!r->isEmpty()); + + QRect *myLast = (numRects == 1 ? &extents : rects.data() + (numRects - 1)); + if (mergeFromRight(myLast, r)) { + if (numRects > 1) { + const QRect *nextToTop = (numRects > 2 ? myLast - 2 : 0); + if (mergeFromBelow(myLast - 1, myLast, nextToTop, 0)) + --numRects; + } + } else if (mergeFromBelow(myLast, r, (numRects > 1 ? myLast - 1 : 0), 0)) { + // nothing + } else { + vectorize(); + ++numRects; + updateInnerRect(*r); + if (rects.size() < numRects) + rects.resize(numRects); + rects[numRects - 1] = *r; + } + extents.setCoords(qMin(extents.left(), r->left()), + qMin(extents.top(), r->top()), + qMax(extents.right(), r->right()), + qMax(extents.bottom(), r->bottom())); + +#ifdef QT_REGION_DEBUG + selfTest(); +#endif +} + +void QRegionPrivate::append(const QRegionPrivate *r) +{ + Q_ASSERT(!isEmptyHelper(r)); + + if (r->numRects == 1) { + append(&r->extents); + return; + } + + vectorize(); + + QRect *destRect = rects.data() + numRects; + const QRect *srcRect = r->rects.constData(); + int numAppend = r->numRects; + + // try merging + { + const QRect *rFirst = srcRect; + QRect *myLast = destRect - 1; + const QRect *nextToLast = (numRects > 1 ? myLast - 1 : 0); + if (mergeFromRight(myLast, rFirst)) { + ++srcRect; + --numAppend; + const QRect *rNextToFirst = (numAppend > 1 ? rFirst + 2 : 0); + if (mergeFromBelow(myLast, rFirst + 1, nextToLast, rNextToFirst)) { + ++srcRect; + --numAppend; + } + if (numRects > 1) { + nextToLast = (numRects > 2 ? myLast - 2 : 0); + rNextToFirst = (numAppend > 0 ? srcRect : 0); + if (mergeFromBelow(myLast - 1, myLast, nextToLast, rNextToFirst)) { + --destRect; + --numRects; + } + } + } else if (mergeFromBelow(myLast, rFirst, nextToLast, rFirst + 1)) { + ++srcRect; + --numAppend; + } + } + + // append rectangles + if (numAppend > 0) { + const int newNumRects = numRects + numAppend; + if (newNumRects > rects.size()) { + rects.resize(newNumRects); + destRect = rects.data() + numRects; + } + memcpy(destRect, srcRect, numAppend * sizeof(QRect)); + + numRects = newNumRects; + } + + // update inner rectangle + if (innerArea < r->innerArea) { + innerArea = r->innerArea; + innerRect = r->innerRect; + } + + // update extents + destRect = &extents; + srcRect = &r->extents; + extents.setCoords(qMin(destRect->left(), srcRect->left()), + qMin(destRect->top(), srcRect->top()), + qMax(destRect->right(), srcRect->right()), + qMax(destRect->bottom(), srcRect->bottom())); + +#ifdef QT_REGION_DEBUG + selfTest(); +#endif +} + +void QRegionPrivate::prepend(const QRegionPrivate *r) +{ + Q_ASSERT(!isEmptyHelper(r)); + + if (r->numRects == 1) { + prepend(&r->extents); + return; + } + + vectorize(); + + int numPrepend = r->numRects; + int numSkip = 0; + + // try merging + { + QRect *myFirst = rects.data(); + const QRect *nextToFirst = (numRects > 1 ? myFirst + 1 : 0); + const QRect *rLast = r->rects.constData() + r->numRects - 1; + const QRect *rNextToLast = (r->numRects > 1 ? rLast - 1 : 0); + if (mergeFromLeft(myFirst, rLast)) { + --numPrepend; + --rLast; + rNextToLast = (numPrepend > 1 ? rLast - 1 : 0); + if (mergeFromAbove(myFirst, rLast, nextToFirst, rNextToLast)) { + --numPrepend; + --rLast; + } + if (numRects > 1) { + nextToFirst = (numRects > 2? myFirst + 2 : 0); + rNextToLast = (numPrepend > 0 ? rLast : 0); + if (mergeFromAbove(myFirst + 1, myFirst, nextToFirst, rNextToLast)) { + --numRects; + ++numSkip; + } + } + } else if (mergeFromAbove(myFirst, rLast, nextToFirst, rNextToLast)) { + --numPrepend; + } + } + + if (numPrepend > 0) { + const int newNumRects = numRects + numPrepend; + if (newNumRects > rects.size()) + rects.resize(newNumRects); + + // move existing rectangles + memmove(rects.data() + numPrepend, rects.constData() + numSkip, + numRects * sizeof(QRect)); + + // prepend new rectangles + memcpy(rects.data(), r->rects.constData(), numPrepend * sizeof(QRect)); + + numRects = newNumRects; + } + + // update inner rectangle + if (innerArea < r->innerArea) { + innerArea = r->innerArea; + innerRect = r->innerRect; + } + + // update extents + extents.setCoords(qMin(extents.left(), r->extents.left()), + qMin(extents.top(), r->extents.top()), + qMax(extents.right(), r->extents.right()), + qMax(extents.bottom(), r->extents.bottom())); + +#ifdef QT_REGION_DEBUG + selfTest(); +#endif +} + +void QRegionPrivate::prepend(const QRect *r) +{ + Q_ASSERT(!r->isEmpty()); + + QRect *myFirst = (numRects == 1 ? &extents : rects.data()); + if (mergeFromLeft(myFirst, r)) { + if (numRects > 1) { + const QRect *nextToFirst = (numRects > 2 ? myFirst + 2 : 0); + if (mergeFromAbove(myFirst + 1, myFirst, nextToFirst, 0)) { + --numRects; + memmove(rects.data(), rects.constData() + 1, + numRects * sizeof(QRect)); + } + } + } else if (mergeFromAbove(myFirst, r, (numRects > 1 ? myFirst + 1 : 0), 0)) { + // nothing + } else { + vectorize(); + ++numRects; + updateInnerRect(*r); + rects.prepend(*r); + } + extents.setCoords(qMin(extents.left(), r->left()), + qMin(extents.top(), r->top()), + qMax(extents.right(), r->right()), + qMax(extents.bottom(), r->bottom())); + +#ifdef QT_REGION_DEBUG + selfTest(); +#endif +} + +bool QRegionPrivate::canAppend(const QRect *r) const +{ + Q_ASSERT(!r->isEmpty()); + + const QRect *myLast = (numRects == 1) ? &extents : (rects.constData() + (numRects - 1)); + if (r->top() > myLast->bottom()) + return true; + if (r->top() == myLast->top() + && r->height() == myLast->height() + && r->left() > myLast->right()) + { + return true; + } + + return false; +} + +bool QRegionPrivate::canAppend(const QRegionPrivate *r) const +{ + return canAppend(r->numRects == 1 ? &r->extents : r->rects.constData()); +} + +bool QRegionPrivate::canPrepend(const QRect *r) const +{ + Q_ASSERT(!r->isEmpty()); + + const QRect *myFirst = (numRects == 1) ? &extents : rects.constData(); + if (r->bottom() < myFirst->top()) // not overlapping + return true; + if (r->top() == myFirst->top() + && r->height() == myFirst->height() + && r->right() < myFirst->left()) + { + return true; + } + + return false; +} + +bool QRegionPrivate::canPrepend(const QRegionPrivate *r) const +{ + return canPrepend(r->numRects == 1 ? &r->extents : r->rects.constData() + r->numRects - 1); +} + +#ifdef QT_REGION_DEBUG +void QRegionPrivate::selfTest() const +{ + if (numRects == 0) { + Q_ASSERT(extents.isEmpty()); + Q_ASSERT(innerRect.isEmpty()); + return; + } + + Q_ASSERT(innerArea == (innerRect.width() * innerRect.height())); + + if (numRects == 1) { + Q_ASSERT(innerRect == extents); + Q_ASSERT(!innerRect.isEmpty()); + return; + } + + for (int i = 0; i < numRects; ++i) { + const QRect r = rects.at(i); + if ((r.width() * r.height()) > innerArea) + qDebug() << "selfTest(): innerRect" << innerRect << '<' << r; + } + + QRect r = rects.first(); + for (int i = 1; i < numRects; ++i) { + const QRect r2 = rects.at(i); + Q_ASSERT(!r2.isEmpty()); + if (r2.y() == r.y()) { + Q_ASSERT(r.bottom() == r2.bottom()); + Q_ASSERT(r.right() < (r2.left() + 1)); + } else { + Q_ASSERT(r2.y() >= r.bottom()); + } + r = r2; + } +} +#endif // QT_REGION_DEBUG + +#if defined(Q_WS_X11) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qregion_x11.cpp" +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_MAC) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qregion_mac.cpp" +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_WIN) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qregion_win.cpp" +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) +static QRegionPrivate qrp; +QRegion::QRegionData QRegion::shared_empty = {Q_BASIC_ATOMIC_INITIALIZER(1), &qrp}; +#endif + +typedef void (*OverlapFunc)(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2); +typedef void (*NonOverlapFunc)(register QRegionPrivate &dest, register const QRect *r, const QRect *rEnd, + register int y1, register int y2); + +static bool EqualRegion(const QRegionPrivate *r1, const QRegionPrivate *r2); +static void UnionRegion(const QRegionPrivate *reg1, const QRegionPrivate *reg2, QRegionPrivate &dest); +static void miRegionOp(register QRegionPrivate &dest, const QRegionPrivate *reg1, const QRegionPrivate *reg2, + OverlapFunc overlapFunc, NonOverlapFunc nonOverlap1Func, + NonOverlapFunc nonOverlap2Func); + +#define RectangleOut 0 +#define RectangleIn 1 +#define RectanglePart 2 +#define EvenOddRule 0 +#define WindingRule 1 + +// START OF region.h extract +/* $XConsortium: region.h,v 11.14 94/04/17 20:22:20 rws Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ + +#ifndef _XREGION_H +#define _XREGION_H + +QT_BEGIN_INCLUDE_NAMESPACE +#include <limits.h> +QT_END_INCLUDE_NAMESPACE + +/* 1 if two BOXes overlap. + * 0 if two BOXes do not overlap. + * Remember, x2 and y2 are not in the region + */ +#define EXTENTCHECK(r1, r2) \ + ((r1)->right() >= (r2)->left() && \ + (r1)->left() <= (r2)->right() && \ + (r1)->bottom() >= (r2)->top() && \ + (r1)->top() <= (r2)->bottom()) + +/* + * update region extents + */ +#define EXTENTS(r,idRect){\ + if((r)->left() < (idRect)->extents.left())\ + (idRect)->extents.setLeft((r)->left());\ + if((r)->top() < (idRect)->extents.top())\ + (idRect)->extents.setTop((r)->top());\ + if((r)->right() > (idRect)->extents.right())\ + (idRect)->extents.setRight((r)->right());\ + if((r)->bottom() > (idRect)->extents.bottom())\ + (idRect)->extents.setBottom((r)->bottom());\ + } + +/* + * Check to see if there is enough memory in the present region. + */ +#define MEMCHECK(dest, rect, firstrect){\ + if ((dest).numRects >= ((dest).rects.size()-1)){\ + firstrect.resize(firstrect.size() * 2); \ + (rect) = (firstrect).data() + (dest).numRects;\ + }\ + } + + +/* + * number of points to buffer before sending them off + * to scanlines(): Must be an even number + */ +#define NUMPTSTOBUFFER 200 + +/* + * used to allocate buffers for points and link + * the buffers together + */ +typedef struct _POINTBLOCK { + int data[NUMPTSTOBUFFER * sizeof(QPoint)]; + QPoint *pts; + struct _POINTBLOCK *next; +} POINTBLOCK; + +#endif +// END OF region.h extract + +// START OF Region.c extract +/* $XConsortium: Region.c /main/30 1996/10/22 14:21:24 kaleb $ */ +/************************************************************************ + +Copyright (c) 1987, 1988 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ +/* + * The functions in this file implement the Region abstraction, similar to one + * used in the X11 sample server. A Region is simply an area, as the name + * implies, and is implemented as a "y-x-banded" array of rectangles. To + * explain: Each Region is made up of a certain number of rectangles sorted + * by y coordinate first, and then by x coordinate. + * + * Furthermore, the rectangles are banded such that every rectangle with a + * given upper-left y coordinate (y1) will have the same lower-right y + * coordinate (y2) and vice versa. If a rectangle has scanlines in a band, it + * will span the entire vertical distance of the band. This means that some + * areas that could be merged into a taller rectangle will be represented as + * several shorter rectangles to account for shorter rectangles to its left + * or right but within its "vertical scope". + * + * An added constraint on the rectangles is that they must cover as much + * horizontal area as possible. E.g. no two rectangles in a band are allowed + * to touch. + * + * Whenever possible, bands will be merged together to cover a greater vertical + * distance (and thus reduce the number of rectangles). Two bands can be merged + * only if the bottom of one touches the top of the other and they have + * rectangles in the same places (of the same width, of course). This maintains + * the y-x-banding that's so nice to have... + */ +/* $XFree86: xc/lib/X11/Region.c,v 1.1.1.2.2.2 1998/10/04 15:22:50 hohndel Exp $ */ + +static void UnionRectWithRegion(register const QRect *rect, const QRegionPrivate *source, + QRegionPrivate &dest) +{ + if (rect->isEmpty()) + return; + + Q_ASSERT(EqualRegion(source, &dest)); + + if (dest.numRects == 0) { + dest = QRegionPrivate(*rect); + } else if (dest.canAppend(rect)) { + dest.append(rect); + } else { + QRegionPrivate p(*rect); + UnionRegion(&p, source, dest); + } +} + +/*- + *----------------------------------------------------------------------- + * miSetExtents -- + * Reset the extents and innerRect of a region to what they should be. + * Called by miSubtract and miIntersect b/c they can't figure it out + * along the way or do so easily, as miUnion can. + * + * Results: + * None. + * + * Side Effects: + * The region's 'extents' and 'innerRect' structure is overwritten. + * + *----------------------------------------------------------------------- + */ +static void miSetExtents(QRegionPrivate &dest) +{ + register const QRect *pBox, + *pBoxEnd; + register QRect *pExtents; + + dest.innerRect.setCoords(0, 0, -1, -1); + dest.innerArea = -1; + if (dest.numRects == 0) { + dest.extents.setCoords(0, 0, -1, -1); + return; + } + + pExtents = &dest.extents; + if (dest.rects.isEmpty()) + pBox = &dest.extents; + else + pBox = dest.rects.constData(); + pBoxEnd = pBox + dest.numRects - 1; + + /* + * Since pBox is the first rectangle in the region, it must have the + * smallest y1 and since pBoxEnd is the last rectangle in the region, + * it must have the largest y2, because of banding. Initialize x1 and + * x2 from pBox and pBoxEnd, resp., as good things to initialize them + * to... + */ + pExtents->setLeft(pBox->left()); + pExtents->setTop(pBox->top()); + pExtents->setRight(pBoxEnd->right()); + pExtents->setBottom(pBoxEnd->bottom()); + + Q_ASSERT(pExtents->top() <= pExtents->bottom()); + while (pBox <= pBoxEnd) { + if (pBox->left() < pExtents->left()) + pExtents->setLeft(pBox->left()); + if (pBox->right() > pExtents->right()) + pExtents->setRight(pBox->right()); + dest.updateInnerRect(*pBox); + ++pBox; + } + Q_ASSERT(pExtents->left() <= pExtents->right()); +} + +/* TranslateRegion(pRegion, x, y) + translates in place + added by raymond +*/ + +static void OffsetRegion(register QRegionPrivate ®ion, register int x, register int y) +{ + if (region.rects.size()) { + register QRect *pbox = region.rects.data(); + register int nbox = region.numRects; + + while (nbox--) { + pbox->translate(x, y); + ++pbox; + } + } + region.extents.translate(x, y); + region.innerRect.translate(x, y); +} + +/*====================================================================== + * Region Intersection + *====================================================================*/ +/*- + *----------------------------------------------------------------------- + * miIntersectO -- + * Handle an overlapping band for miIntersect. + * + * Results: + * None. + * + * Side Effects: + * Rectangles may be added to the region. + * + *----------------------------------------------------------------------- + */ +static void miIntersectO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, int y1, int y2) +{ + register int x1; + register int x2; + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + while (r1 != r1End && r2 != r2End) { + x1 = qMax(r1->left(), r2->left()); + x2 = qMin(r1->right(), r2->right()); + + /* + * If there's any overlap between the two rectangles, add that + * overlap to the new region. + * There's no need to check for subsumption because the only way + * such a need could arise is if some region has two rectangles + * right next to each other. Since that should never happen... + */ + if (x1 <= x2) { + Q_ASSERT(y1 <= y2); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, x2, y2); + ++dest.numRects; + ++pNextRect; + } + + /* + * Need to advance the pointers. Shift the one that extends + * to the right the least, since the other still has a chance to + * overlap with that region's next rectangle, if you see what I mean. + */ + if (r1->right() < r2->right()) { + ++r1; + } else if (r2->right() < r1->right()) { + ++r2; + } else { + ++r1; + ++r2; + } + } +} + +/*====================================================================== + * Generic Region Operator + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miCoalesce -- + * Attempt to merge the boxes in the current band with those in the + * previous one. Used only by miRegionOp. + * + * Results: + * The new index for the previous band. + * + * Side Effects: + * If coalescing takes place: + * - rectangles in the previous band will have their y2 fields + * altered. + * - dest.numRects will be decreased. + * + *----------------------------------------------------------------------- + */ +static int miCoalesce(register QRegionPrivate &dest, int prevStart, int curStart) +{ + register QRect *pPrevBox; /* Current box in previous band */ + register QRect *pCurBox; /* Current box in current band */ + register QRect *pRegEnd; /* End of region */ + int curNumRects; /* Number of rectangles in current band */ + int prevNumRects; /* Number of rectangles in previous band */ + int bandY1; /* Y1 coordinate for current band */ + QRect *rData = dest.rects.data(); + + pRegEnd = rData + dest.numRects; + + pPrevBox = rData + prevStart; + prevNumRects = curStart - prevStart; + + /* + * Figure out how many rectangles are in the current band. Have to do + * this because multiple bands could have been added in miRegionOp + * at the end when one region has been exhausted. + */ + pCurBox = rData + curStart; + bandY1 = pCurBox->top(); + for (curNumRects = 0; pCurBox != pRegEnd && pCurBox->top() == bandY1; ++curNumRects) { + ++pCurBox; + } + + if (pCurBox != pRegEnd) { + /* + * If more than one band was added, we have to find the start + * of the last band added so the next coalescing job can start + * at the right place... (given when multiple bands are added, + * this may be pointless -- see above). + */ + --pRegEnd; + while ((pRegEnd - 1)->top() == pRegEnd->top()) + --pRegEnd; + curStart = pRegEnd - rData; + pRegEnd = rData + dest.numRects; + } + + if (curNumRects == prevNumRects && curNumRects != 0) { + pCurBox -= curNumRects; + /* + * The bands may only be coalesced if the bottom of the previous + * matches the top scanline of the current. + */ + if (pPrevBox->bottom() == pCurBox->top() - 1) { + /* + * Make sure the bands have boxes in the same places. This + * assumes that boxes have been added in such a way that they + * cover the most area possible. I.e. two boxes in a band must + * have some horizontal space between them. + */ + do { + if (pPrevBox->left() != pCurBox->left() || pPrevBox->right() != pCurBox->right()) { + // The bands don't line up so they can't be coalesced. + return curStart; + } + ++pPrevBox; + ++pCurBox; + --prevNumRects; + } while (prevNumRects != 0); + + dest.numRects -= curNumRects; + pCurBox -= curNumRects; + pPrevBox -= curNumRects; + + /* + * The bands may be merged, so set the bottom y of each box + * in the previous band to that of the corresponding box in + * the current band. + */ + do { + pPrevBox->setBottom(pCurBox->bottom()); + dest.updateInnerRect(*pPrevBox); + ++pPrevBox; + ++pCurBox; + curNumRects -= 1; + } while (curNumRects != 0); + + /* + * If only one band was added to the region, we have to backup + * curStart to the start of the previous band. + * + * If more than one band was added to the region, copy the + * other bands down. The assumption here is that the other bands + * came from the same region as the current one and no further + * coalescing can be done on them since it's all been done + * already... curStart is already in the right place. + */ + if (pCurBox == pRegEnd) { + curStart = prevStart; + } else { + do { + *pPrevBox++ = *pCurBox++; + dest.updateInnerRect(*pPrevBox); + } while (pCurBox != pRegEnd); + } + } + } + return curStart; +} + +/*- + *----------------------------------------------------------------------- + * miRegionOp -- + * Apply an operation to two regions. Called by miUnion, miInverse, + * miSubtract, miIntersect... + * + * Results: + * None. + * + * Side Effects: + * The new region is overwritten. + * + * Notes: + * The idea behind this function is to view the two regions as sets. + * Together they cover a rectangle of area that this function divides + * into horizontal bands where points are covered only by one region + * or by both. For the first case, the nonOverlapFunc is called with + * each the band and the band's upper and lower extents. For the + * second, the overlapFunc is called to process the entire band. It + * is responsible for clipping the rectangles in the band, though + * this function provides the boundaries. + * At the end of each band, the new region is coalesced, if possible, + * to reduce the number of rectangles in the region. + * + *----------------------------------------------------------------------- + */ +static void miRegionOp(register QRegionPrivate &dest, + const QRegionPrivate *reg1, const QRegionPrivate *reg2, + OverlapFunc overlapFunc, NonOverlapFunc nonOverlap1Func, + NonOverlapFunc nonOverlap2Func) +{ + register const QRect *r1; // Pointer into first region + register const QRect *r2; // Pointer into 2d region + const QRect *r1End; // End of 1st region + const QRect *r2End; // End of 2d region + register int ybot; // Bottom of intersection + register int ytop; // Top of intersection + int prevBand; // Index of start of previous band in dest + int curBand; // Index of start of current band in dest + register const QRect *r1BandEnd; // End of current band in r1 + register const QRect *r2BandEnd; // End of current band in r2 + int top; // Top of non-overlapping band + int bot; // Bottom of non-overlapping band + + /* + * Initialization: + * set r1, r2, r1End and r2End appropriately, preserve the important + * parts of the destination region until the end in case it's one of + * the two source regions, then mark the "new" region empty, allocating + * another array of rectangles for it to use. + */ + if (reg1->numRects == 1) + r1 = ®1->extents; + else + r1 = reg1->rects.constData(); + if (reg2->numRects == 1) + r2 = ®2->extents; + else + r2 = reg2->rects.constData(); + + r1End = r1 + reg1->numRects; + r2End = r2 + reg2->numRects; + + dest.vectorize(); + + QVector<QRect> oldRects = dest.rects; + + dest.numRects = 0; + + /* + * Allocate a reasonable number of rectangles for the new region. The idea + * is to allocate enough so the individual functions don't need to + * reallocate and copy the array, which is time consuming, yet we don't + * have to worry about using too much memory. I hope to be able to + * nuke the realloc() at the end of this function eventually. + */ + dest.rects.resize(qMax(reg1->numRects,reg2->numRects) * 2); + + /* + * Initialize ybot and ytop. + * In the upcoming loop, ybot and ytop serve different functions depending + * on whether the band being handled is an overlapping or non-overlapping + * band. + * In the case of a non-overlapping band (only one of the regions + * has points in the band), ybot is the bottom of the most recent + * intersection and thus clips the top of the rectangles in that band. + * ytop is the top of the next intersection between the two regions and + * serves to clip the bottom of the rectangles in the current band. + * For an overlapping band (where the two regions intersect), ytop clips + * the top of the rectangles of both regions and ybot clips the bottoms. + */ + if (reg1->extents.top() < reg2->extents.top()) + ybot = reg1->extents.top() - 1; + else + ybot = reg2->extents.top() - 1; + + /* + * prevBand serves to mark the start of the previous band so rectangles + * can be coalesced into larger rectangles. qv. miCoalesce, above. + * In the beginning, there is no previous band, so prevBand == curBand + * (curBand is set later on, of course, but the first band will always + * start at index 0). prevBand and curBand must be indices because of + * the possible expansion, and resultant moving, of the new region's + * array of rectangles. + */ + prevBand = 0; + + do { + curBand = dest.numRects; + + /* + * This algorithm proceeds one source-band (as opposed to a + * destination band, which is determined by where the two regions + * intersect) at a time. r1BandEnd and r2BandEnd serve to mark the + * rectangle after the last one in the current band for their + * respective regions. + */ + r1BandEnd = r1; + while (r1BandEnd != r1End && r1BandEnd->top() == r1->top()) + ++r1BandEnd; + + r2BandEnd = r2; + while (r2BandEnd != r2End && r2BandEnd->top() == r2->top()) + ++r2BandEnd; + + /* + * First handle the band that doesn't intersect, if any. + * + * Note that attention is restricted to one band in the + * non-intersecting region at once, so if a region has n + * bands between the current position and the next place it overlaps + * the other, this entire loop will be passed through n times. + */ + if (r1->top() < r2->top()) { + top = qMax(r1->top(), ybot + 1); + bot = qMin(r1->bottom(), r2->top() - 1); + + if (nonOverlap1Func != 0 && bot >= top) + (*nonOverlap1Func)(dest, r1, r1BandEnd, top, bot); + ytop = r2->top(); + } else if (r2->top() < r1->top()) { + top = qMax(r2->top(), ybot + 1); + bot = qMin(r2->bottom(), r1->top() - 1); + + if (nonOverlap2Func != 0 && bot >= top) + (*nonOverlap2Func)(dest, r2, r2BandEnd, top, bot); + ytop = r1->top(); + } else { + ytop = r1->top(); + } + + /* + * If any rectangles got added to the region, try and coalesce them + * with rectangles from the previous band. Note we could just do + * this test in miCoalesce, but some machines incur a not + * inconsiderable cost for function calls, so... + */ + if (dest.numRects != curBand) + prevBand = miCoalesce(dest, prevBand, curBand); + + /* + * Now see if we've hit an intersecting band. The two bands only + * intersect if ybot >= ytop + */ + ybot = qMin(r1->bottom(), r2->bottom()); + curBand = dest.numRects; + if (ybot >= ytop) + (*overlapFunc)(dest, r1, r1BandEnd, r2, r2BandEnd, ytop, ybot); + + if (dest.numRects != curBand) + prevBand = miCoalesce(dest, prevBand, curBand); + + /* + * If we've finished with a band (y2 == ybot) we skip forward + * in the region to the next band. + */ + if (r1->bottom() == ybot) + r1 = r1BandEnd; + if (r2->bottom() == ybot) + r2 = r2BandEnd; + } while (r1 != r1End && r2 != r2End); + + /* + * Deal with whichever region still has rectangles left. + */ + curBand = dest.numRects; + if (r1 != r1End) { + if (nonOverlap1Func != 0) { + do { + r1BandEnd = r1; + while (r1BandEnd < r1End && r1BandEnd->top() == r1->top()) + ++r1BandEnd; + (*nonOverlap1Func)(dest, r1, r1BandEnd, qMax(r1->top(), ybot + 1), r1->bottom()); + r1 = r1BandEnd; + } while (r1 != r1End); + } + } else if ((r2 != r2End) && (nonOverlap2Func != 0)) { + do { + r2BandEnd = r2; + while (r2BandEnd < r2End && r2BandEnd->top() == r2->top()) + ++r2BandEnd; + (*nonOverlap2Func)(dest, r2, r2BandEnd, qMax(r2->top(), ybot + 1), r2->bottom()); + r2 = r2BandEnd; + } while (r2 != r2End); + } + + if (dest.numRects != curBand) + (void)miCoalesce(dest, prevBand, curBand); + + /* + * A bit of cleanup. To keep regions from growing without bound, + * we shrink the array of rectangles to match the new number of + * rectangles in the region. + * + * Only do this stuff if the number of rectangles allocated is more than + * twice the number of rectangles in the region (a simple optimization). + */ + if (qMax(4, dest.numRects) < (dest.rects.size() >> 1)) + dest.rects.resize(dest.numRects); +} + +/*====================================================================== + * Region Union + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miUnionNonO -- + * Handle a non-overlapping band for the union operation. Just + * Adds the rectangles into the region. Doesn't have to check for + * subsumption or anything. + * + * Results: + * None. + * + * Side Effects: + * dest.numRects is incremented and the final rectangles overwritten + * with the rectangles we're passed. + * + *----------------------------------------------------------------------- + */ + +static void miUnionNonO(register QRegionPrivate &dest, register const QRect *r, const QRect *rEnd, + register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + Q_ASSERT(y1 <= y2); + + while (r != rEnd) { + Q_ASSERT(r->left() <= r->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(r->left(), y1, r->right(), y2); + dest.numRects++; + ++pNextRect; + ++r; + } +} + + +/*- + *----------------------------------------------------------------------- + * miUnionO -- + * Handle an overlapping band for the union operation. Picks the + * left-most rectangle each time and merges it into the region. + * + * Results: + * None. + * + * Side Effects: + * Rectangles are overwritten in dest.rects and dest.numRects will + * be changed. + * + *----------------------------------------------------------------------- + */ + +static void miUnionO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + +#define MERGERECT(r) \ + if ((dest.numRects != 0) && \ + (pNextRect[-1].top() == y1) && \ + (pNextRect[-1].bottom() == y2) && \ + (pNextRect[-1].right() >= r->left()-1)) { \ + if (pNextRect[-1].right() < r->right()) { \ + pNextRect[-1].setRight(r->right()); \ + dest.updateInnerRect(pNextRect[-1]); \ + Q_ASSERT(pNextRect[-1].left() <= pNextRect[-1].right()); \ + } \ + } else { \ + MEMCHECK(dest, pNextRect, dest.rects) \ + pNextRect->setCoords(r->left(), y1, r->right(), y2); \ + dest.updateInnerRect(*pNextRect); \ + dest.numRects++; \ + pNextRect++; \ + } \ + r++; + + Q_ASSERT(y1 <= y2); + while (r1 != r1End && r2 != r2End) { + if (r1->left() < r2->left()) { + MERGERECT(r1) + } else { + MERGERECT(r2) + } + } + + if (r1 != r1End) { + do { + MERGERECT(r1) + } while (r1 != r1End); + } else { + while (r2 != r2End) { + MERGERECT(r2) + } + } +} + +static void UnionRegion(const QRegionPrivate *reg1, const QRegionPrivate *reg2, QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(reg1) && !isEmptyHelper(reg2)); + Q_ASSERT(!reg1->contains(*reg2)); + Q_ASSERT(!reg2->contains(*reg1)); + Q_ASSERT(!EqualRegion(reg1, reg2)); + Q_ASSERT(!reg1->canAppend(reg2)); + Q_ASSERT(!reg2->canAppend(reg1)); + + if (reg1->innerArea > reg2->innerArea) { + dest.innerArea = reg1->innerArea; + dest.innerRect = reg1->innerRect; + } else { + dest.innerArea = reg2->innerArea; + dest.innerRect = reg2->innerRect; + } + miRegionOp(dest, reg1, reg2, miUnionO, miUnionNonO, miUnionNonO); + + dest.extents.setCoords(qMin(reg1->extents.left(), reg2->extents.left()), + qMin(reg1->extents.top(), reg2->extents.top()), + qMax(reg1->extents.right(), reg2->extents.right()), + qMax(reg1->extents.bottom(), reg2->extents.bottom())); +} + +/*====================================================================== + * Region Subtraction + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miSubtractNonO -- + * Deal with non-overlapping band for subtraction. Any parts from + * region 2 we discard. Anything from region 1 we add to the region. + * + * Results: + * None. + * + * Side Effects: + * dest may be affected. + * + *----------------------------------------------------------------------- + */ + +static void miSubtractNonO1(register QRegionPrivate &dest, register const QRect *r, + const QRect *rEnd, register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + Q_ASSERT(y1<=y2); + + while (r != rEnd) { + Q_ASSERT(r->left() <= r->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(r->left(), y1, r->right(), y2); + ++dest.numRects; + ++pNextRect; + ++r; + } +} + +/*- + *----------------------------------------------------------------------- + * miSubtractO -- + * Overlapping band subtraction. x1 is the left-most point not yet + * checked. + * + * Results: + * None. + * + * Side Effects: + * dest may have rectangles added to it. + * + *----------------------------------------------------------------------- + */ + +static void miSubtractO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2) +{ + register QRect *pNextRect; + register int x1; + + x1 = r1->left(); + + Q_ASSERT(y1 <= y2); + pNextRect = dest.rects.data() + dest.numRects; + + while (r1 != r1End && r2 != r2End) { + if (r2->right() < x1) { + /* + * Subtrahend missed the boat: go to next subtrahend. + */ + ++r2; + } else if (r2->left() <= x1) { + /* + * Subtrahend precedes minuend: nuke left edge of minuend. + */ + x1 = r2->right() + 1; + if (x1 > r1->right()) { + /* + * Minuend completely covered: advance to next minuend and + * reset left fence to edge of new minuend. + */ + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } else { + // Subtrahend now used up since it doesn't extend beyond minuend + ++r2; + } + } else if (r2->left() <= r1->right()) { + /* + * Left part of subtrahend covers part of minuend: add uncovered + * part of minuend to region and skip to next subtrahend. + */ + Q_ASSERT(x1 < r2->left()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r2->left() - 1, y2); + ++dest.numRects; + ++pNextRect; + + x1 = r2->right() + 1; + if (x1 > r1->right()) { + /* + * Minuend used up: advance to new... + */ + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } else { + // Subtrahend used up + ++r2; + } + } else { + /* + * Minuend used up: add any remaining piece before advancing. + */ + if (r1->right() >= x1) { + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r1->right(), y2); + ++dest.numRects; + ++pNextRect; + } + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } + } + + /* + * Add remaining minuend rectangles to region. + */ + while (r1 != r1End) { + Q_ASSERT(x1 <= r1->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r1->right(), y2); + ++dest.numRects; + ++pNextRect; + + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } +} + +/*- + *----------------------------------------------------------------------- + * miSubtract -- + * Subtract regS from regM and leave the result in regD. + * S stands for subtrahend, M for minuend and D for difference. + * + * Side Effects: + * regD is overwritten. + * + *----------------------------------------------------------------------- + */ + +static void SubtractRegion(QRegionPrivate *regM, QRegionPrivate *regS, + register QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(regM)); + Q_ASSERT(!isEmptyHelper(regS)); + Q_ASSERT(EXTENTCHECK(®M->extents, ®S->extents)); + Q_ASSERT(!regS->contains(*regM)); + Q_ASSERT(!EqualRegion(regM, regS)); + + miRegionOp(dest, regM, regS, miSubtractO, miSubtractNonO1, 0); + + /* + * Can't alter dest's extents before we call miRegionOp because + * it might be one of the source regions and miRegionOp depends + * on the extents of those regions being the unaltered. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + miSetExtents(dest); +} + +static void XorRegion(QRegionPrivate *sra, QRegionPrivate *srb, QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(sra) && !isEmptyHelper(srb)); + Q_ASSERT(EXTENTCHECK(&sra->extents, &srb->extents)); + Q_ASSERT(!EqualRegion(sra, srb)); + + QRegionPrivate tra, trb; + + if (!srb->contains(*sra)) + SubtractRegion(sra, srb, tra); + if (!sra->contains(*srb)) + SubtractRegion(srb, sra, trb); + + Q_ASSERT(isEmptyHelper(&trb) || !tra.contains(trb)); + Q_ASSERT(isEmptyHelper(&tra) || !trb.contains(tra)); + + if (isEmptyHelper(&tra)) { + dest = trb; + } else if (isEmptyHelper(&trb)) { + dest = tra; + } else if (tra.canAppend(&trb)) { + dest = tra; + dest.append(&trb); + } else if (trb.canAppend(&tra)) { + dest = trb; + dest.append(&tra); + } else { + UnionRegion(&tra, &trb, dest); + } +} + +/* + * Check to see if two regions are equal + */ +static bool EqualRegion(const QRegionPrivate *r1, const QRegionPrivate *r2) +{ + if (r1->numRects != r2->numRects) { + return false; + } else if (r1->numRects == 0) { + return true; + } else if (r1->extents != r2->extents) { + return false; + } else if (r1->numRects == 1 && r2->numRects == 1) { + return true; // equality tested in previous if-statement + } else { + const QRect *rr1 = (r1->numRects == 1) ? &r1->extents : r1->rects.constData(); + const QRect *rr2 = (r2->numRects == 1) ? &r2->extents : r2->rects.constData(); + for (int i = 0; i < r1->numRects; ++i, ++rr1, ++rr2) { + if (*rr1 != *rr2) + return false; + } + } + + return true; +} + +static bool PointInRegion(QRegionPrivate *pRegion, int x, int y) +{ + int i; + + if (isEmptyHelper(pRegion)) + return false; + if (!pRegion->extents.contains(x, y)) + return false; + if (pRegion->numRects == 1) + return pRegion->extents.contains(x, y); + if (pRegion->innerRect.contains(x, y)) + return true; + for (i = 0; i < pRegion->numRects; ++i) { + if (pRegion->rects[i].contains(x, y)) + return true; + } + return false; +} + +static bool RectInRegion(register QRegionPrivate *region, int rx, int ry, uint rwidth, uint rheight) +{ + register const QRect *pbox; + register const QRect *pboxEnd; + QRect rect(rx, ry, rwidth, rheight); + register QRect *prect = ▭ + int partIn, partOut; + + if (!region || region->numRects == 0 || !EXTENTCHECK(®ion->extents, prect)) + return RectangleOut; + + partOut = false; + partIn = false; + + /* can stop when both partOut and partIn are true, or we reach prect->y2 */ + pbox = (region->numRects == 1) ? ®ion->extents : region->rects.constData(); + pboxEnd = pbox + region->numRects; + for (; pbox < pboxEnd; ++pbox) { + if (pbox->bottom() < ry) + continue; + + if (pbox->top() > ry) { + partOut = true; + if (partIn || pbox->top() > prect->bottom()) + break; + ry = pbox->top(); + } + + if (pbox->right() < rx) + continue; /* not far enough over yet */ + + if (pbox->left() > rx) { + partOut = true; /* missed part of rectangle to left */ + if (partIn) + break; + } + + if (pbox->left() <= prect->right()) { + partIn = true; /* definitely overlap */ + if (partOut) + break; + } + + if (pbox->right() >= prect->right()) { + ry = pbox->bottom() + 1; /* finished with this band */ + if (ry > prect->bottom()) + break; + rx = prect->left(); /* reset x out to left again */ + } else { + /* + * Because boxes in a band are maximal width, if the first box + * to overlap the rectangle doesn't completely cover it in that + * band, the rectangle must be partially out, since some of it + * will be uncovered in that band. partIn will have been set true + * by now... + */ + break; + } + } + return partIn ? ((ry <= prect->bottom()) ? RectanglePart : RectangleIn) : RectangleOut; +} +// END OF Region.c extract +// START OF poly.h extract +/* $XConsortium: poly.h,v 1.4 94/04/17 20:22:19 rws Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ + +/* + * This file contains a few macros to help track + * the edge of a filled object. The object is assumed + * to be filled in scanline order, and thus the + * algorithm used is an extension of Bresenham's line + * drawing algorithm which assumes that y is always the + * major axis. + * Since these pieces of code are the same for any filled shape, + * it is more convenient to gather the library in one + * place, but since these pieces of code are also in + * the inner loops of output primitives, procedure call + * overhead is out of the question. + * See the author for a derivation if needed. + */ + + +/* + * In scan converting polygons, we want to choose those pixels + * which are inside the polygon. Thus, we add .5 to the starting + * x coordinate for both left and right edges. Now we choose the + * first pixel which is inside the pgon for the left edge and the + * first pixel which is outside the pgon for the right edge. + * Draw the left pixel, but not the right. + * + * How to add .5 to the starting x coordinate: + * If the edge is moving to the right, then subtract dy from the + * error term from the general form of the algorithm. + * If the edge is moving to the left, then add dy to the error term. + * + * The reason for the difference between edges moving to the left + * and edges moving to the right is simple: If an edge is moving + * to the right, then we want the algorithm to flip immediately. + * If it is moving to the left, then we don't want it to flip until + * we traverse an entire pixel. + */ +#define BRESINITPGON(dy, x1, x2, xStart, d, m, m1, incr1, incr2) { \ + int dx; /* local storage */ \ +\ + /* \ + * if the edge is horizontal, then it is ignored \ + * and assumed not to be processed. Otherwise, do this stuff. \ + */ \ + if ((dy) != 0) { \ + xStart = (x1); \ + dx = (x2) - xStart; \ + if (dx < 0) { \ + m = dx / (dy); \ + m1 = m - 1; \ + incr1 = -2 * dx + 2 * (dy) * m1; \ + incr2 = -2 * dx + 2 * (dy) * m; \ + d = 2 * m * (dy) - 2 * dx - 2 * (dy); \ + } else { \ + m = dx / (dy); \ + m1 = m + 1; \ + incr1 = 2 * dx - 2 * (dy) * m1; \ + incr2 = 2 * dx - 2 * (dy) * m; \ + d = -2 * m * (dy) + 2 * dx; \ + } \ + } \ +} + +#define BRESINCRPGON(d, minval, m, m1, incr1, incr2) { \ + if (m1 > 0) { \ + if (d > 0) { \ + minval += m1; \ + d += incr1; \ + } \ + else { \ + minval += m; \ + d += incr2; \ + } \ + } else {\ + if (d >= 0) { \ + minval += m1; \ + d += incr1; \ + } \ + else { \ + minval += m; \ + d += incr2; \ + } \ + } \ +} + + +/* + * This structure contains all of the information needed + * to run the bresenham algorithm. + * The variables may be hardcoded into the declarations + * instead of using this structure to make use of + * register declarations. + */ +typedef struct { + int minor_axis; /* minor axis */ + int d; /* decision variable */ + int m, m1; /* slope and slope+1 */ + int incr1, incr2; /* error increments */ +} BRESINFO; + + +#define BRESINITPGONSTRUCT(dmaj, min1, min2, bres) \ + BRESINITPGON(dmaj, min1, min2, bres.minor_axis, bres.d, \ + bres.m, bres.m1, bres.incr1, bres.incr2) + +#define BRESINCRPGONSTRUCT(bres) \ + BRESINCRPGON(bres.d, bres.minor_axis, bres.m, bres.m1, bres.incr1, bres.incr2) + + + +/* + * These are the data structures needed to scan + * convert regions. Two different scan conversion + * methods are available -- the even-odd method, and + * the winding number method. + * The even-odd rule states that a point is inside + * the polygon if a ray drawn from that point in any + * direction will pass through an odd number of + * path segments. + * By the winding number rule, a point is decided + * to be inside the polygon if a ray drawn from that + * point in any direction passes through a different + * number of clockwise and counter-clockwise path + * segments. + * + * These data structures are adapted somewhat from + * the algorithm in (Foley/Van Dam) for scan converting + * polygons. + * The basic algorithm is to start at the top (smallest y) + * of the polygon, stepping down to the bottom of + * the polygon by incrementing the y coordinate. We + * keep a list of edges which the current scanline crosses, + * sorted by x. This list is called the Active Edge Table (AET) + * As we change the y-coordinate, we update each entry in + * in the active edge table to reflect the edges new xcoord. + * This list must be sorted at each scanline in case + * two edges intersect. + * We also keep a data structure known as the Edge Table (ET), + * which keeps track of all the edges which the current + * scanline has not yet reached. The ET is basically a + * list of ScanLineList structures containing a list of + * edges which are entered at a given scanline. There is one + * ScanLineList per scanline at which an edge is entered. + * When we enter a new edge, we move it from the ET to the AET. + * + * From the AET, we can implement the even-odd rule as in + * (Foley/Van Dam). + * The winding number rule is a little trickier. We also + * keep the EdgeTableEntries in the AET linked by the + * nextWETE (winding EdgeTableEntry) link. This allows + * the edges to be linked just as before for updating + * purposes, but only uses the edges linked by the nextWETE + * link as edges representing spans of the polygon to + * drawn (as with the even-odd rule). + */ + +/* + * for the winding number rule + */ +#define CLOCKWISE 1 +#define COUNTERCLOCKWISE -1 + +typedef struct _EdgeTableEntry { + int ymax; /* ycoord at which we exit this edge. */ + BRESINFO bres; /* Bresenham info to run the edge */ + struct _EdgeTableEntry *next; /* next in the list */ + struct _EdgeTableEntry *back; /* for insertion sort */ + struct _EdgeTableEntry *nextWETE; /* for winding num rule */ + int ClockWise; /* flag for winding number rule */ +} EdgeTableEntry; + + +typedef struct _ScanLineList{ + int scanline; /* the scanline represented */ + EdgeTableEntry *edgelist; /* header node */ + struct _ScanLineList *next; /* next in the list */ +} ScanLineList; + + +typedef struct { + int ymax; /* ymax for the polygon */ + int ymin; /* ymin for the polygon */ + ScanLineList scanlines; /* header node */ +} EdgeTable; + + +/* + * Here is a struct to help with storage allocation + * so we can allocate a big chunk at a time, and then take + * pieces from this heap when we need to. + */ +#define SLLSPERBLOCK 25 + +typedef struct _ScanLineListBlock { + ScanLineList SLLs[SLLSPERBLOCK]; + struct _ScanLineListBlock *next; +} ScanLineListBlock; + + + +/* + * + * a few macros for the inner loops of the fill code where + * performance considerations don't allow a procedure call. + * + * Evaluate the given edge at the given scanline. + * If the edge has expired, then we leave it and fix up + * the active edge table; otherwise, we increment the + * x value to be ready for the next scanline. + * The winding number rule is in effect, so we must notify + * the caller when the edge has been removed so he + * can reorder the Winding Active Edge Table. + */ +#define EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET) { \ + if (pAET->ymax == y) { /* leaving this edge */ \ + pPrevAET->next = pAET->next; \ + pAET = pPrevAET->next; \ + fixWAET = 1; \ + if (pAET) \ + pAET->back = pPrevAET; \ + } \ + else { \ + BRESINCRPGONSTRUCT(pAET->bres) \ + pPrevAET = pAET; \ + pAET = pAET->next; \ + } \ +} + + +/* + * Evaluate the given edge at the given scanline. + * If the edge has expired, then we leave it and fix up + * the active edge table; otherwise, we increment the + * x value to be ready for the next scanline. + * The even-odd rule is in effect. + */ +#define EVALUATEEDGEEVENODD(pAET, pPrevAET, y) { \ + if (pAET->ymax == y) { /* leaving this edge */ \ + pPrevAET->next = pAET->next; \ + pAET = pPrevAET->next; \ + if (pAET) \ + pAET->back = pPrevAET; \ + } \ + else { \ + BRESINCRPGONSTRUCT(pAET->bres) \ + pPrevAET = pAET; \ + pAET = pAET->next; \ + } \ +} +// END OF poly.h extract +// START OF PolyReg.c extract +/* $XConsortium: PolyReg.c,v 11.23 94/11/17 21:59:37 converse Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ +/* $XFree86: xc/lib/X11/PolyReg.c,v 1.1.1.2.8.2 1998/10/04 15:22:49 hohndel Exp $ */ + +#define LARGE_COORDINATE INT_MAX +#define SMALL_COORDINATE INT_MIN + +/* + * InsertEdgeInET + * + * Insert the given edge into the edge table. + * First we must find the correct bucket in the + * Edge table, then find the right slot in the + * bucket. Finally, we can insert it. + * + */ +static void InsertEdgeInET(EdgeTable *ET, EdgeTableEntry *ETE, int scanline, + ScanLineListBlock **SLLBlock, int *iSLLBlock) +{ + register EdgeTableEntry *start, *prev; + register ScanLineList *pSLL, *pPrevSLL; + ScanLineListBlock *tmpSLLBlock; + + /* + * find the right bucket to put the edge into + */ + pPrevSLL = &ET->scanlines; + pSLL = pPrevSLL->next; + while (pSLL && (pSLL->scanline < scanline)) { + pPrevSLL = pSLL; + pSLL = pSLL->next; + } + + /* + * reassign pSLL (pointer to ScanLineList) if necessary + */ + if ((!pSLL) || (pSLL->scanline > scanline)) { + if (*iSLLBlock > SLLSPERBLOCK-1) + { + tmpSLLBlock = + (ScanLineListBlock *)malloc(sizeof(ScanLineListBlock)); + Q_CHECK_PTR(tmpSLLBlock); + (*SLLBlock)->next = tmpSLLBlock; + tmpSLLBlock->next = (ScanLineListBlock *)NULL; + *SLLBlock = tmpSLLBlock; + *iSLLBlock = 0; + } + pSLL = &((*SLLBlock)->SLLs[(*iSLLBlock)++]); + + pSLL->next = pPrevSLL->next; + pSLL->edgelist = (EdgeTableEntry *)NULL; + pPrevSLL->next = pSLL; + } + pSLL->scanline = scanline; + + /* + * now insert the edge in the right bucket + */ + prev = 0; + start = pSLL->edgelist; + while (start && (start->bres.minor_axis < ETE->bres.minor_axis)) { + prev = start; + start = start->next; + } + ETE->next = start; + + if (prev) + prev->next = ETE; + else + pSLL->edgelist = ETE; +} + +/* + * CreateEdgeTable + * + * This routine creates the edge table for + * scan converting polygons. + * The Edge Table (ET) looks like: + * + * EdgeTable + * -------- + * | ymax | ScanLineLists + * |scanline|-->------------>-------------->... + * -------- |scanline| |scanline| + * |edgelist| |edgelist| + * --------- --------- + * | | + * | | + * V V + * list of ETEs list of ETEs + * + * where ETE is an EdgeTableEntry data structure, + * and there is one ScanLineList per scanline at + * which an edge is initially entered. + * + */ + +static void CreateETandAET(register int count, register const QPoint *pts, + EdgeTable *ET, EdgeTableEntry *AET, register EdgeTableEntry *pETEs, + ScanLineListBlock *pSLLBlock) +{ + register const QPoint *top, + *bottom, + *PrevPt, + *CurrPt; + int iSLLBlock = 0; + int dy; + + if (count < 2) + return; + + /* + * initialize the Active Edge Table + */ + AET->next = 0; + AET->back = 0; + AET->nextWETE = 0; + AET->bres.minor_axis = SMALL_COORDINATE; + + /* + * initialize the Edge Table. + */ + ET->scanlines.next = 0; + ET->ymax = SMALL_COORDINATE; + ET->ymin = LARGE_COORDINATE; + pSLLBlock->next = 0; + + PrevPt = &pts[count - 1]; + + /* + * for each vertex in the array of points. + * In this loop we are dealing with two vertices at + * a time -- these make up one edge of the polygon. + */ + while (count--) { + CurrPt = pts++; + + /* + * find out which point is above and which is below. + */ + if (PrevPt->y() > CurrPt->y()) { + bottom = PrevPt; + top = CurrPt; + pETEs->ClockWise = 0; + } else { + bottom = CurrPt; + top = PrevPt; + pETEs->ClockWise = 1; + } + + /* + * don't add horizontal edges to the Edge table. + */ + if (bottom->y() != top->y()) { + pETEs->ymax = bottom->y() - 1; /* -1 so we don't get last scanline */ + + /* + * initialize integer edge algorithm + */ + dy = bottom->y() - top->y(); + BRESINITPGONSTRUCT(dy, top->x(), bottom->x(), pETEs->bres) + + InsertEdgeInET(ET, pETEs, top->y(), &pSLLBlock, &iSLLBlock); + + if (PrevPt->y() > ET->ymax) + ET->ymax = PrevPt->y(); + if (PrevPt->y() < ET->ymin) + ET->ymin = PrevPt->y(); + ++pETEs; + } + + PrevPt = CurrPt; + } +} + +/* + * loadAET + * + * This routine moves EdgeTableEntries from the + * EdgeTable into the Active Edge Table, + * leaving them sorted by smaller x coordinate. + * + */ + +static void loadAET(register EdgeTableEntry *AET, register EdgeTableEntry *ETEs) +{ + register EdgeTableEntry *pPrevAET; + register EdgeTableEntry *tmp; + + pPrevAET = AET; + AET = AET->next; + while (ETEs) { + while (AET && AET->bres.minor_axis < ETEs->bres.minor_axis) { + pPrevAET = AET; + AET = AET->next; + } + tmp = ETEs->next; + ETEs->next = AET; + if (AET) + AET->back = ETEs; + ETEs->back = pPrevAET; + pPrevAET->next = ETEs; + pPrevAET = ETEs; + + ETEs = tmp; + } +} + +/* + * computeWAET + * + * This routine links the AET by the + * nextWETE (winding EdgeTableEntry) link for + * use by the winding number rule. The final + * Active Edge Table (AET) might look something + * like: + * + * AET + * ---------- --------- --------- + * |ymax | |ymax | |ymax | + * | ... | |... | |... | + * |next |->|next |->|next |->... + * |nextWETE| |nextWETE| |nextWETE| + * --------- --------- ^-------- + * | | | + * V-------------------> V---> ... + * + */ +static void computeWAET(register EdgeTableEntry *AET) +{ + register EdgeTableEntry *pWETE; + register int inside = 1; + register int isInside = 0; + + AET->nextWETE = 0; + pWETE = AET; + AET = AET->next; + while (AET) { + if (AET->ClockWise) + ++isInside; + else + --isInside; + + if ((!inside && !isInside) || (inside && isInside)) { + pWETE->nextWETE = AET; + pWETE = AET; + inside = !inside; + } + AET = AET->next; + } + pWETE->nextWETE = 0; +} + +/* + * InsertionSort + * + * Just a simple insertion sort using + * pointers and back pointers to sort the Active + * Edge Table. + * + */ + +static int InsertionSort(register EdgeTableEntry *AET) +{ + register EdgeTableEntry *pETEchase; + register EdgeTableEntry *pETEinsert; + register EdgeTableEntry *pETEchaseBackTMP; + register int changed = 0; + + AET = AET->next; + while (AET) { + pETEinsert = AET; + pETEchase = AET; + while (pETEchase->back->bres.minor_axis > AET->bres.minor_axis) + pETEchase = pETEchase->back; + + AET = AET->next; + if (pETEchase != pETEinsert) { + pETEchaseBackTMP = pETEchase->back; + pETEinsert->back->next = AET; + if (AET) + AET->back = pETEinsert->back; + pETEinsert->next = pETEchase; + pETEchase->back->next = pETEinsert; + pETEchase->back = pETEinsert; + pETEinsert->back = pETEchaseBackTMP; + changed = 1; + } + } + return changed; +} + +/* + * Clean up our act. + */ +static void FreeStorage(register ScanLineListBlock *pSLLBlock) +{ + register ScanLineListBlock *tmpSLLBlock; + + while (pSLLBlock) { + tmpSLLBlock = pSLLBlock->next; + free(pSLLBlock); + pSLLBlock = tmpSLLBlock; + } +} + +struct QRegionSpan { + QRegionSpan() {} + QRegionSpan(int x1_, int x2_) : x1(x1_), x2(x2_) {} + + int x1; + int x2; + int width() const { return x2 - x1; } +}; + +Q_DECLARE_TYPEINFO(QRegionSpan, Q_PRIMITIVE_TYPE); + +static inline void flushRow(const QRegionSpan *spans, int y, int numSpans, QRegionPrivate *reg, int *lastRow, int *extendTo, bool *needsExtend) +{ + QRect *regRects = reg->rects.data() + *lastRow; + bool canExtend = reg->rects.size() - *lastRow == numSpans + && !(*needsExtend && *extendTo + 1 != y) + && (*needsExtend || regRects[0].y() + regRects[0].height() == y); + + for (int i = 0; i < numSpans && canExtend; ++i) { + if (regRects[i].x() != spans[i].x1 || regRects[i].right() != spans[i].x2 - 1) + canExtend = false; + } + + if (canExtend) { + *extendTo = y; + *needsExtend = true; + } else { + if (*needsExtend) { + for (int i = 0; i < reg->rects.size() - *lastRow; ++i) + regRects[i].setBottom(*extendTo); + } + + *lastRow = reg->rects.size(); + reg->rects.reserve(*lastRow + numSpans); + for (int i = 0; i < numSpans; ++i) + reg->rects << QRect(spans[i].x1, y, spans[i].width(), 1); + + if (spans[0].x1 < reg->extents.left()) + reg->extents.setLeft(spans[0].x1); + + if (spans[numSpans-1].x2 - 1 > reg->extents.right()) + reg->extents.setRight(spans[numSpans-1].x2 - 1); + + *needsExtend = false; + } +} + +/* + * Create an array of rectangles from a list of points. + * If indeed these things (POINTS, RECTS) are the same, + * then this proc is still needed, because it allocates + * storage for the array, which was allocated on the + * stack by the calling procedure. + * + */ +static void PtsToRegion(register int numFullPtBlocks, register int iCurPtBlock, + POINTBLOCK *FirstPtBlock, QRegionPrivate *reg) +{ + int lastRow = 0; + int extendTo = 0; + bool needsExtend = false; + QVarLengthArray<QRegionSpan> row; + int rowSize = 0; + + reg->extents.setLeft(INT_MAX); + reg->extents.setRight(INT_MIN); + reg->innerArea = -1; + + POINTBLOCK *CurPtBlock = FirstPtBlock; + for (; numFullPtBlocks >= 0; --numFullPtBlocks) { + /* the loop uses 2 points per iteration */ + int i = NUMPTSTOBUFFER >> 1; + if (!numFullPtBlocks) + i = iCurPtBlock >> 1; + if(i) { + row.resize(qMax(row.size(), rowSize + i)); + for (QPoint *pts = CurPtBlock->pts; i--; pts += 2) { + const int width = pts[1].x() - pts[0].x(); + if (width) { + if (rowSize && row[rowSize-1].x2 == pts[0].x()) + row[rowSize-1].x2 = pts[1].x(); + else + row[rowSize++] = QRegionSpan(pts[0].x(), pts[1].x()); + } + + if (rowSize) { + QPoint *next = i ? &pts[2] : (numFullPtBlocks ? CurPtBlock->next->pts : 0); + + if (!next || next->y() != pts[0].y()) { + flushRow(row.data(), pts[0].y(), rowSize, reg, &lastRow, &extendTo, &needsExtend); + rowSize = 0; + } + } + } + } + CurPtBlock = CurPtBlock->next; + } + + if (needsExtend) { + for (int i = lastRow; i < reg->rects.size(); ++i) + reg->rects[i].setBottom(extendTo); + } + + reg->numRects = reg->rects.size(); + + if (reg->numRects) { + reg->extents.setTop(reg->rects[0].top()); + reg->extents.setBottom(reg->rects[lastRow].bottom()); + + for (int i = 0; i < reg->rects.size(); ++i) + reg->updateInnerRect(reg->rects[i]); + } else { + reg->extents.setCoords(0, 0, 0, 0); + } +} + +/* + * polytoregion + * + * Scan converts a polygon by returning a run-length + * encoding of the resultant bitmap -- the run-length + * encoding is in the form of an array of rectangles. + * + * Can return 0 in case of errors. + */ +static QRegionPrivate *PolygonRegion(const QPoint *Pts, int Count, int rule) + //Point *Pts; /* the pts */ + //int Count; /* number of pts */ + //int rule; /* winding rule */ +{ + QRegionPrivate *region; + register EdgeTableEntry *pAET; /* Active Edge Table */ + register int y; /* current scanline */ + register int iPts = 0; /* number of pts in buffer */ + register EdgeTableEntry *pWETE; /* Winding Edge Table Entry*/ + register ScanLineList *pSLL; /* current scanLineList */ + register QPoint *pts; /* output buffer */ + EdgeTableEntry *pPrevAET; /* ptr to previous AET */ + EdgeTable ET; /* header node for ET */ + EdgeTableEntry AET; /* header node for AET */ + EdgeTableEntry *pETEs; /* EdgeTableEntries pool */ + ScanLineListBlock SLLBlock; /* header for scanlinelist */ + int fixWAET = false; + POINTBLOCK FirstPtBlock, *curPtBlock; /* PtBlock buffers */ + FirstPtBlock.pts = reinterpret_cast<QPoint *>(FirstPtBlock.data); + POINTBLOCK *tmpPtBlock; + int numFullPtBlocks = 0; + + if (!(region = new QRegionPrivate)) + return 0; + + /* special case a rectangle */ + if (((Count == 4) || + ((Count == 5) && (Pts[4].x() == Pts[0].x()) && (Pts[4].y() == Pts[0].y()))) + && (((Pts[0].y() == Pts[1].y()) && (Pts[1].x() == Pts[2].x()) && (Pts[2].y() == Pts[3].y()) + && (Pts[3].x() == Pts[0].x())) || ((Pts[0].x() == Pts[1].x()) + && (Pts[1].y() == Pts[2].y()) && (Pts[2].x() == Pts[3].x()) + && (Pts[3].y() == Pts[0].y())))) { + int x = qMin(Pts[0].x(), Pts[2].x()); + region->extents.setLeft(x); + int y = qMin(Pts[0].y(), Pts[2].y()); + region->extents.setTop(y); + region->extents.setWidth(qMax(Pts[0].x(), Pts[2].x()) - x); + region->extents.setHeight(qMax(Pts[0].y(), Pts[2].y()) - y); + if ((region->extents.left() <= region->extents.right()) && + (region->extents.top() <= region->extents.bottom())) { + region->numRects = 1; + region->innerRect = region->extents; + region->innerArea = region->innerRect.width() * region->innerRect.height(); + } + return region; + } + + if (!(pETEs = static_cast<EdgeTableEntry *>(malloc(sizeof(EdgeTableEntry) * Count)))) + return 0; + + region->vectorize(); + + pts = FirstPtBlock.pts; + CreateETandAET(Count, Pts, &ET, &AET, pETEs, &SLLBlock); + + pSLL = ET.scanlines.next; + curPtBlock = &FirstPtBlock; + + // sanity check that the region won't become too big... + if (ET.ymax - ET.ymin > 100000) { + // clean up region ptr +#ifndef QT_NO_DEBUG + qWarning("QRegion: creating region from big polygon failed...!"); +#endif + delete region; + return 0; + } + + + QT_TRY { + if (rule == EvenOddRule) { + /* + * for each scanline + */ + for (y = ET.ymin; y < ET.ymax; ++y) { + + /* + * Add a new edge to the active edge table when we + * get to the next edge. + */ + if (pSLL && y == pSLL->scanline) { + loadAET(&AET, pSLL->edgelist); + pSLL = pSLL->next; + } + pPrevAET = &AET; + pAET = AET.next; + + /* + * for each active edge + */ + while (pAET) { + pts->setX(pAET->bres.minor_axis); + pts->setY(y); + ++pts; + ++iPts; + + /* + * send out the buffer + */ + if (iPts == NUMPTSTOBUFFER) { + tmpPtBlock = (POINTBLOCK *)malloc(sizeof(POINTBLOCK)); + Q_CHECK_PTR(tmpPtBlock); + tmpPtBlock->pts = reinterpret_cast<QPoint *>(tmpPtBlock->data); + curPtBlock->next = tmpPtBlock; + curPtBlock = tmpPtBlock; + pts = curPtBlock->pts; + ++numFullPtBlocks; + iPts = 0; + } + EVALUATEEDGEEVENODD(pAET, pPrevAET, y) + } + InsertionSort(&AET); + } + } else { + /* + * for each scanline + */ + for (y = ET.ymin; y < ET.ymax; ++y) { + /* + * Add a new edge to the active edge table when we + * get to the next edge. + */ + if (pSLL && y == pSLL->scanline) { + loadAET(&AET, pSLL->edgelist); + computeWAET(&AET); + pSLL = pSLL->next; + } + pPrevAET = &AET; + pAET = AET.next; + pWETE = pAET; + + /* + * for each active edge + */ + while (pAET) { + /* + * add to the buffer only those edges that + * are in the Winding active edge table. + */ + if (pWETE == pAET) { + pts->setX(pAET->bres.minor_axis); + pts->setY(y); + ++pts; + ++iPts; + + /* + * send out the buffer + */ + if (iPts == NUMPTSTOBUFFER) { + tmpPtBlock = static_cast<POINTBLOCK *>(malloc(sizeof(POINTBLOCK))); + tmpPtBlock->pts = reinterpret_cast<QPoint *>(tmpPtBlock->data); + curPtBlock->next = tmpPtBlock; + curPtBlock = tmpPtBlock; + pts = curPtBlock->pts; + ++numFullPtBlocks; + iPts = 0; + } + pWETE = pWETE->nextWETE; + } + EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET) + } + + /* + * recompute the winding active edge table if + * we just resorted or have exited an edge. + */ + if (InsertionSort(&AET) || fixWAET) { + computeWAET(&AET); + fixWAET = false; + } + } + } + } QT_CATCH(...) { + FreeStorage(SLLBlock.next); + PtsToRegion(numFullPtBlocks, iPts, &FirstPtBlock, region); + for (curPtBlock = FirstPtBlock.next; --numFullPtBlocks >= 0;) { + tmpPtBlock = curPtBlock->next; + free(curPtBlock); + curPtBlock = tmpPtBlock; + } + free(pETEs); + return 0; // this function returns 0 in case of an error + } + + FreeStorage(SLLBlock.next); + PtsToRegion(numFullPtBlocks, iPts, &FirstPtBlock, region); + for (curPtBlock = FirstPtBlock.next; --numFullPtBlocks >= 0;) { + tmpPtBlock = curPtBlock->next; + free(curPtBlock); + curPtBlock = tmpPtBlock; + } + free(pETEs); + return region; +} +// END OF PolyReg.c extract + +QRegionPrivate *qt_bitmapToRegion(const QBitmap& bitmap) +{ + QImage image = bitmap.toImage(); + + QRegionPrivate *region = new QRegionPrivate; + + QRect xr; + +#define AddSpan \ + { \ + xr.setCoords(prev1, y, x-1, y); \ + UnionRectWithRegion(&xr, region, *region); \ + } + + const uchar zero = 0; + bool little = image.format() == QImage::Format_MonoLSB; + + int x, + y; + for (y = 0; y < image.height(); ++y) { + uchar *line = image.scanLine(y); + int w = image.width(); + uchar all = zero; + int prev1 = -1; + for (x = 0; x < w;) { + uchar byte = line[x / 8]; + if (x > w - 8 || byte!=all) { + if (little) { + for (int b = 8; b > 0 && x < w; --b) { + if (!(byte & 0x01) == !all) { + // More of the same + } else { + // A change. + if (all!=zero) { + AddSpan + all = zero; + } else { + prev1 = x; + all = ~zero; + } + } + byte >>= 1; + ++x; + } + } else { + for (int b = 8; b > 0 && x < w; --b) { + if (!(byte & 0x80) == !all) { + // More of the same + } else { + // A change. + if (all != zero) { + AddSpan + all = zero; + } else { + prev1 = x; + all = ~zero; + } + } + byte <<= 1; + ++x; + } + } + } else { + x += 8; + } + } + if (all != zero) { + AddSpan + } + } +#undef AddSpan + + return region; +} + +QRegion::QRegion() + : d(&shared_empty) +{ + d->ref.ref(); +} + +QRegion::QRegion(const QRect &r, RegionType t) +{ + if (r.isEmpty()) { + d = &shared_empty; + d->ref.ref(); + } else { + d = new QRegionData; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_WIN) + d->rgn = 0; +#endif + if (t == Rectangle) { + d->qt_rgn = new QRegionPrivate(r); + } else if (t == Ellipse) { + QPainterPath path; + path.addEllipse(r.x(), r.y(), r.width(), r.height()); + QPolygon a = path.toSubpathPolygons().at(0).toPolygon(); + d->qt_rgn = PolygonRegion(a.constData(), a.size(), EvenOddRule); + } + } +} + +QRegion::QRegion(const QPolygon &a, Qt::FillRule fillRule) +{ + if (a.count() > 2) { + QRegionPrivate *qt_rgn = PolygonRegion(a.constData(), a.size(), + fillRule == Qt::WindingFill ? WindingRule : EvenOddRule); + if (qt_rgn) { + d = new QRegionData; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_WIN) + d->rgn = 0; +#endif + d->qt_rgn = qt_rgn; + } else { + d = &shared_empty; + d->ref.ref(); + } + } else { + d = &shared_empty; + d->ref.ref(); + } +} + +QRegion::QRegion(const QRegion &r) +{ + d = r.d; + d->ref.ref(); +} + + +QRegion::QRegion(const QBitmap &bm) +{ + if (bm.isNull()) { + d = &shared_empty; + d->ref.ref(); + } else { + d = new QRegionData; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_WIN) + d->rgn = 0; +#endif + d->qt_rgn = qt_bitmapToRegion(bm); + } +} + +void QRegion::cleanUp(QRegion::QRegionData *x) +{ + delete x->qt_rgn; +#if defined(Q_WS_X11) + if (x->rgn) + XDestroyRegion(x->rgn); + if (x->xrectangles) + free(x->xrectangles); +#elif defined(Q_WS_WIN) + if (x->rgn) + qt_win_dispose_rgn(x->rgn); +#endif + delete x; +} + +QRegion::~QRegion() +{ + if (!d->ref.deref()) + cleanUp(d); +} + + +QRegion &QRegion::operator=(const QRegion &r) +{ + r.d->ref.ref(); + if (!d->ref.deref()) + cleanUp(d); + d = r.d; + return *this; +} + + +/*! + \internal +*/ +QRegion QRegion::copy() const +{ + QRegion r; + QScopedPointer<QRegionData> x(new QRegionData); + x->ref = 1; +#if defined(Q_WS_X11) + x->rgn = 0; + x->xrectangles = 0; +#elif defined(Q_WS_WIN) + x->rgn = 0; +#endif + if (d->qt_rgn) + x->qt_rgn = new QRegionPrivate(*d->qt_rgn); + else + x->qt_rgn = new QRegionPrivate; + if (!r.d->ref.deref()) + cleanUp(r.d); + r.d = x.take(); + return r; +} + +bool QRegion::isEmpty() const +{ + return d == &shared_empty || d->qt_rgn->numRects == 0; +} + + +bool QRegion::contains(const QPoint &p) const +{ + return PointInRegion(d->qt_rgn, p.x(), p.y()); +} + +bool QRegion::contains(const QRect &r) const +{ + return RectInRegion(d->qt_rgn, r.left(), r.top(), r.width(), r.height()) != RectangleOut; +} + + + +void QRegion::translate(int dx, int dy) +{ + if ((dx == 0 && dy == 0) || isEmptyHelper(d->qt_rgn)) + return; + + detach(); + OffsetRegion(*d->qt_rgn, dx, dy); +} + +QRegion QRegion::unite(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn)) + return r; + if (isEmptyHelper(r.d->qt_rgn)) + return *this; + if (d == r.d) + return *this; + + if (d->qt_rgn->contains(*r.d->qt_rgn)) { + return *this; + } else if (r.d->qt_rgn->contains(*d->qt_rgn)) { + return r; + } else if (d->qt_rgn->canAppend(r.d->qt_rgn)) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->append(r.d->qt_rgn); + return result; + } else if (d->qt_rgn->canPrepend(r.d->qt_rgn)) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->prepend(r.d->qt_rgn); + return result; + } else if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return *this; + } else { + QRegion result; + result.detach(); + UnionRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); + return result; + } +} + +QRegion& QRegion::operator+=(const QRegion &r) +{ + if (isEmptyHelper(d->qt_rgn)) + return *this = r; + if (isEmptyHelper(r.d->qt_rgn)) + return *this; + if (d == r.d) + return *this; + + if (d->qt_rgn->contains(*r.d->qt_rgn)) { + return *this; + } else if (r.d->qt_rgn->contains(*d->qt_rgn)) { + return *this = r; + } else if (d->qt_rgn->canAppend(r.d->qt_rgn)) { + detach(); + d->qt_rgn->append(r.d->qt_rgn); + return *this; + } else if (d->qt_rgn->canPrepend(r.d->qt_rgn)) { + detach(); + d->qt_rgn->prepend(r.d->qt_rgn); + return *this; + } else if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return *this; + } else { + detach(); + UnionRegion(d->qt_rgn, r.d->qt_rgn, *d->qt_rgn); + return *this; + } +} + +QRegion QRegion::unite(const QRect &r) const +{ + if (isEmptyHelper(d->qt_rgn)) + return r; + if (r.isEmpty()) + return *this; + + if (d->qt_rgn->contains(r)) { + return *this; + } else if (d->qt_rgn->within(r)) { + return r; + } else if (d->qt_rgn->numRects == 1 && d->qt_rgn->extents == r) { + return *this; + } else if (d->qt_rgn->canAppend(&r)) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->append(&r); + return result; + } else if (d->qt_rgn->canPrepend(&r)) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->prepend(&r); + return result; + } else { + QRegion result; + result.detach(); + QRegionPrivate rp(r); + UnionRegion(d->qt_rgn, &rp, *result.d->qt_rgn); + return result; + } +} + +QRegion& QRegion::operator+=(const QRect &r) +{ + if (isEmptyHelper(d->qt_rgn)) + return *this = r; + if (r.isEmpty()) + return *this; + + if (d->qt_rgn->contains(r)) { + return *this; + } else if (d->qt_rgn->within(r)) { + return *this = r; + } else if (d->qt_rgn->canAppend(&r)) { + detach(); + d->qt_rgn->append(&r); + return *this; + } else if (d->qt_rgn->canPrepend(&r)) { + detach(); + d->qt_rgn->prepend(&r); + return *this; + } else if (d->qt_rgn->numRects == 1 && d->qt_rgn->extents == r) { + return *this; + } else { + detach(); + QRegionPrivate p(r); + UnionRegion(d->qt_rgn, &p, *d->qt_rgn); + return *this; + } +} + +QRegion QRegion::intersect(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn) || isEmptyHelper(r.d->qt_rgn) + || !EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) + return QRegion(); + + /* this is fully contained in r */ + if (r.d->qt_rgn->contains(*d->qt_rgn)) + return *this; + + /* r is fully contained in this */ + if (d->qt_rgn->contains(*r.d->qt_rgn)) + return r; + + if (r.d->qt_rgn->numRects == 1 && d->qt_rgn->numRects == 1) { + const QRect rect = qt_rect_intersect_normalized(r.d->qt_rgn->extents, + d->qt_rgn->extents); + return QRegion(rect); + } else if (r.d->qt_rgn->numRects == 1) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->intersect(r.d->qt_rgn->extents); + return result; + } else if (d->qt_rgn->numRects == 1) { + QRegion result(r); + result.detach(); + result.d->qt_rgn->intersect(d->qt_rgn->extents); + return result; + } + + QRegion result; + result.detach(); + miRegionOp(*result.d->qt_rgn, d->qt_rgn, r.d->qt_rgn, miIntersectO, 0, 0); + + /* + * Can't alter dest's extents before we call miRegionOp because + * it might be one of the source regions and miRegionOp depends + * on the extents of those regions being the same. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + miSetExtents(*result.d->qt_rgn); + return result; +} + +QRegion QRegion::intersect(const QRect &r) const +{ + if (isEmptyHelper(d->qt_rgn) || r.isEmpty() + || !EXTENTCHECK(&d->qt_rgn->extents, &r)) + return QRegion(); + + /* this is fully contained in r */ + if (d->qt_rgn->within(r)) + return *this; + + /* r is fully contained in this */ + if (d->qt_rgn->contains(r)) + return r; + + if (d->qt_rgn->numRects == 1) { + const QRect rect = qt_rect_intersect_normalized(d->qt_rgn->extents, + r.normalized()); + return QRegion(rect); + } + + QRegion result(*this); + result.detach(); + result.d->qt_rgn->intersect(r); + return result; +} + +QRegion QRegion::subtract(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn) || isEmptyHelper(r.d->qt_rgn)) + return *this; + if (r.d->qt_rgn->contains(*d->qt_rgn)) + return QRegion(); + if (!EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) + return *this; + if (d == r.d || EqualRegion(d->qt_rgn, r.d->qt_rgn)) + return QRegion(); + +#ifdef QT_REGION_DEBUG + d->qt_rgn->selfTest(); + r.d->qt_rgn->selfTest(); +#endif + + QRegion result; + result.detach(); + SubtractRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); +#ifdef QT_REGION_DEBUG + result.d->qt_rgn->selfTest(); +#endif + return result; +} + +QRegion QRegion::eor(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn)) { + return r; + } else if (isEmptyHelper(r.d->qt_rgn)) { + return *this; + } else if (!EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) { + return (*this + r); + } else if (d == r.d || EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return QRegion(); + } else { + QRegion result; + result.detach(); + XorRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); + return result; + } +} + +QRect QRegion::boundingRect() const +{ + if (isEmpty()) + return QRect(); + return d->qt_rgn->extents; +} + +/*! \internal + Returns true if \a rect is guaranteed to be fully contained in \a region. + A false return value does not guarantee the opposite. +*/ +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) +Q_GUI_EXPORT +#endif +bool qt_region_strictContains(const QRegion ®ion, const QRect &rect) +{ + if (isEmptyHelper(region.d->qt_rgn) || !rect.isValid()) + return false; + +#if 0 // TEST_INNERRECT + static bool guard = false; + if (guard) + return false; + guard = true; + QRegion inner = region.d->qt_rgn->innerRect; + Q_ASSERT((inner - region).isEmpty()); + guard = false; + + int maxArea = 0; + for (int i = 0; i < region.d->qt_rgn->numRects; ++i) { + const QRect r = region.d->qt_rgn->rects.at(i); + if (r.width() * r.height() > maxArea) + maxArea = r.width() * r.height(); + } + + if (maxArea > region.d->qt_rgn->innerArea) { + qDebug() << "not largest rectangle" << region << region.d->qt_rgn->innerRect; + } + Q_ASSERT(maxArea <= region.d->qt_rgn->innerArea); +#endif + + const QRect r1 = region.d->qt_rgn->innerRect; + return (rect.left() >= r1.left() && rect.right() <= r1.right() + && rect.top() >= r1.top() && rect.bottom() <= r1.bottom()); +} + +QVector<QRect> QRegion::rects() const +{ + if (d->qt_rgn) { + d->qt_rgn->vectorize(); + // hw: modify the vector size directly to avoid reallocation + d->qt_rgn->rects.d->size = d->qt_rgn->numRects; + return d->qt_rgn->rects; + } else { + return QVector<QRect>(); + } +} + +void QRegion::setRects(const QRect *rects, int num) +{ + *this = QRegion(); + if (!rects || num == 0 || (num == 1 && rects->isEmpty())) + return; + + detach(); + + d->qt_rgn->numRects = num; + if (num == 1) { + d->qt_rgn->extents = *rects; + d->qt_rgn->innerRect = *rects; + } else { + d->qt_rgn->rects.resize(num); + + int left = INT_MAX, + right = INT_MIN, + top = INT_MAX, + bottom = INT_MIN; + for (int i = 0; i < num; ++i) { + const QRect &rect = rects[i]; + d->qt_rgn->rects[i] = rect; + left = qMin(rect.left(), left); + right = qMax(rect.right(), right); + top = qMin(rect.top(), top); + bottom = qMax(rect.bottom(), bottom); + d->qt_rgn->updateInnerRect(rect); + } + d->qt_rgn->extents = QRect(QPoint(left, top), QPoint(right, bottom)); + } +} + +int QRegion::numRects() const +{ + return (d->qt_rgn ? d->qt_rgn->numRects : 0); +} + +int QRegion::rectCount() const +{ + return (d->qt_rgn ? d->qt_rgn->numRects : 0); +} + + +bool QRegion::operator==(const QRegion &r) const +{ + if (!d->qt_rgn) + return r.isEmpty(); + if (!r.d->qt_rgn) + return isEmpty(); + + if (d == r.d) + return true; + else + return EqualRegion(d->qt_rgn, r.d->qt_rgn); +} + +bool QRegion::intersects(const QRect &rect) const +{ + if (isEmptyHelper(d->qt_rgn) || rect.isNull()) + return false; + + const QRect r = rect.normalized(); + if (!rect_intersects(d->qt_rgn->extents, r)) + return false; + if (d->qt_rgn->numRects == 1) + return true; + + const QVector<QRect> myRects = rects(); + for (QVector<QRect>::const_iterator it = myRects.constBegin(); it < myRects.constEnd(); ++it) + if (rect_intersects(r, *it)) + return true; + return false; +} + + +#endif +QT_END_NAMESPACE diff --git a/src/gui/painting/qregion.h b/src/gui/painting/qregion.h new file mode 100644 index 0000000000..1cd38d41b8 --- /dev/null +++ b/src/gui/painting/qregion.h @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QREGION_H +#define QREGION_H + +#include <QtCore/qatomic.h> +#include <QtCore/qrect.h> +#include <QtGui/qwindowdefs.h> + +#ifndef QT_NO_DATASTREAM +#include <QtCore/qdatastream.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +template <class T> class QVector; +class QVariant; + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_WIN) || defined(Q_OS_SYMBIAN) +struct QRegionPrivate; +#endif + +class QBitmap; + +class Q_GUI_EXPORT QRegion +{ +public: + enum RegionType { Rectangle, Ellipse }; + + QRegion(); + QRegion(int x, int y, int w, int h, RegionType t = Rectangle); + QRegion(const QRect &r, RegionType t = Rectangle); + QRegion(const QPolygon &pa, Qt::FillRule fillRule = Qt::OddEvenFill); +#ifdef QT3_SUPPORT + QT3_SUPPORT_CONSTRUCTOR QRegion(const QPolygon &pa, bool winding); +#endif + QRegion(const QRegion ®ion); + QRegion(const QBitmap &bitmap); + ~QRegion(); + QRegion &operator=(const QRegion &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QRegion &operator=(QRegion &&other) + { qSwap(d, other.d); return *this; } +#endif + inline void swap(QRegion &other) { qSwap(d, other.d); } +#ifdef QT3_SUPPORT + inline QT3_SUPPORT bool isNull() const { return isEmpty(); } +#endif + bool isEmpty() const; + + bool contains(const QPoint &p) const; + bool contains(const QRect &r) const; + + void translate(int dx, int dy); + inline void translate(const QPoint &p) { translate(p.x(), p.y()); } + QRegion translated(int dx, int dy) const; + inline QRegion translated(const QPoint &p) const { return translated(p.x(), p.y()); } + + // ### Qt 5: make these four functions QT4_SUPPORT + QRegion unite(const QRegion &r) const; + QRegion unite(const QRect &r) const; + QRegion intersect(const QRegion &r) const; + QRegion intersect(const QRect &r) const; + QRegion subtract(const QRegion &r) const; + QRegion eor(const QRegion &r) const; + + inline QRegion united(const QRegion &r) const { return unite(r); } + inline QRegion united(const QRect &r) const { return unite(r); } + inline QRegion intersected(const QRegion &r) const { return intersect(r); } + inline QRegion intersected(const QRect &r) const { return intersect(r); } + inline QRegion subtracted(const QRegion &r) const { return subtract(r); } + inline QRegion xored(const QRegion &r) const { return eor(r); } + + bool intersects(const QRegion &r) const; + bool intersects(const QRect &r) const; + + QRect boundingRect() const; + QVector<QRect> rects() const; + void setRects(const QRect *rect, int num); +#ifdef QT_DEPRECATED + QT_DEPRECATED int numRects() const; +#endif + int rectCount() const; + + const QRegion operator|(const QRegion &r) const; + const QRegion operator+(const QRegion &r) const; + const QRegion operator+(const QRect &r) const; + const QRegion operator&(const QRegion &r) const; + const QRegion operator&(const QRect &r) const; + const QRegion operator-(const QRegion &r) const; + const QRegion operator^(const QRegion &r) const; + QRegion& operator|=(const QRegion &r); + QRegion& operator+=(const QRegion &r); + QRegion& operator+=(const QRect &r); + QRegion& operator&=(const QRegion &r); + QRegion& operator&=(const QRect &r); + QRegion& operator-=(const QRegion &r); + QRegion& operator^=(const QRegion &r); + + bool operator==(const QRegion &r) const; + inline bool operator!=(const QRegion &r) const { return !(operator==(r)); } + operator QVariant() const; + +#ifdef qdoc + Handle handle() const; +#endif +#ifndef qdoc +#if defined(Q_WS_WIN) + inline HRGN handle() const { ensureHandle(); return d->rgn; } +#elif defined(Q_WS_X11) + inline Region handle() const { if(!d->rgn) updateX11Region(); return d->rgn; } +#elif defined(Q_WS_MAC) +#if defined Q_WS_MAC32 + RgnHandle toQDRgn() const; + RgnHandle toQDRgnForUpdate_sys() const; + static QRegion fromQDRgn(RgnHandle shape); +#endif +#ifdef QT_MAC_USE_COCOA + inline HIMutableShapeRef handle(bool unused = false) const + { Q_UNUSED(unused); return toHIMutableShape(); } +#else + inline RgnHandle handle() const { return handle(false); } + inline RgnHandle handle(bool) const { return toQDRgn(); } +#endif + HIMutableShapeRef toHIMutableShape() const; + static QRegion fromHIShapeRef(HIShapeRef shape); +#elif defined(Q_WS_QWS) || defined(Q_WS_QPA) + inline void *handle() const { return d->qt_rgn; } +#endif +#endif + +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QRegion &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QRegion &); +#endif +private: + QRegion copy() const; // helper of detach. + void detach(); +#if defined(Q_WS_WIN) + void ensureHandle() const; + QRegion winCombine(const QRegion &r, int num) const; +#elif defined(Q_WS_X11) + void updateX11Region() const; + void *clipRectangles(int &num) const; + friend void *qt_getClipRects(const QRegion &r, int &num); +#elif defined(Q_WS_MAC) + static OSStatus shape2QRegionHelper(int inMessage, HIShapeRef inShape, + const CGRect *inRect, void *inRefcon); +#endif + friend bool qt_region_strictContains(const QRegion ®ion, + const QRect &rect); + friend struct QRegionPrivate; + +#ifndef QT_NO_DATASTREAM + void exec(const QByteArray &ba, int ver = 0, QDataStream::ByteOrder byteOrder = QDataStream::BigEndian); +#endif + struct QRegionData { + QBasicAtomicInt ref; +#if defined(Q_WS_WIN) + HRGN rgn; +#elif defined(Q_WS_X11) + Region rgn; + void *xrectangles; +#elif defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + mutable RgnHandle unused; // Here for binary compatibility reasons. ### Qt 5 remove. +#endif +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_WS_X11) || defined(Q_WS_MAC) || defined(Q_WS_WIN) || defined(Q_OS_SYMBIAN) + QRegionPrivate *qt_rgn; +#endif + }; +#if defined(Q_WS_WIN) + friend class QETWidget; +#endif + struct QRegionData *d; + static struct QRegionData shared_empty; + static void cleanUp(QRegionData *x); +}; + +/***************************************************************************** + QRegion stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QRegion &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QRegion &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QRegion &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QREGION_H diff --git a/src/gui/painting/qregion_mac.cpp b/src/gui/painting/qregion_mac.cpp new file mode 100644 index 0000000000..50fd783df4 --- /dev/null +++ b/src/gui/painting/qregion_mac.cpp @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qt_mac_p.h> +#include "qcoreapplication.h" +#include <qlibrary.h> + +QT_BEGIN_NAMESPACE + +QRegion::QRegionData QRegion::shared_empty = { Q_BASIC_ATOMIC_INITIALIZER(1), 0 }; + +#if defined(Q_WS_MAC32) && !defined(QT_MAC_USE_COCOA) +#define RGN_CACHE_SIZE 200 +#ifdef RGN_CACHE_SIZE +static bool rgncache_init = false; +static int rgncache_used; +static RgnHandle rgncache[RGN_CACHE_SIZE]; +static void qt_mac_cleanup_rgncache() +{ + rgncache_init = false; + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(rgncache[i]) { + --rgncache_used; + DisposeRgn(rgncache[i]); + rgncache[i] = 0; + } + } +} +#endif + +Q_GUI_EXPORT RgnHandle qt_mac_get_rgn() +{ +#ifdef RGN_CACHE_SIZE + if(!rgncache_init) { + rgncache_used = 0; + rgncache_init = true; + for(int i = 0; i < RGN_CACHE_SIZE; ++i) + rgncache[i] = 0; + qAddPostRoutine(qt_mac_cleanup_rgncache); + } else if(rgncache_used) { + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(rgncache[i]) { + RgnHandle ret = rgncache[i]; + SetEmptyRgn(ret); + rgncache[i] = 0; + --rgncache_used; + return ret; + } + } + } +#endif + return NewRgn(); +} + +Q_GUI_EXPORT void qt_mac_dispose_rgn(RgnHandle r) +{ +#ifdef RGN_CACHE_SIZE + if(rgncache_init && rgncache_used < RGN_CACHE_SIZE) { + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(!rgncache[i]) { + ++rgncache_used; + rgncache[i] = r; + return; + } + } + } +#endif + DisposeRgn(r); +} + +static OSStatus qt_mac_get_rgn_rect(UInt16 msg, RgnHandle, const Rect *rect, void *reg) +{ + if(msg == kQDRegionToRectsMsgParse) { + QRect rct(rect->left, rect->top, (rect->right - rect->left), (rect->bottom - rect->top)); + if(!rct.isEmpty()) + *((QRegion *)reg) += rct; + } + return noErr; +} + +Q_GUI_EXPORT QRegion qt_mac_convert_mac_region(RgnHandle rgn) +{ + return QRegion::fromQDRgn(rgn); +} + +QRegion QRegion::fromQDRgn(RgnHandle rgn) +{ + QRegion ret; + ret.detach(); + OSStatus oss = QDRegionToRects(rgn, kQDParseRegionFromTopLeft, qt_mac_get_rgn_rect, (void *)&ret); + if(oss != noErr) + return QRegion(); + return ret; +} + +/*! + \internal + Create's a RegionHandle, it's the caller's responsibility to release. +*/ +RgnHandle QRegion::toQDRgn() const +{ + RgnHandle rgnHandle = qt_mac_get_rgn(); + if(d->qt_rgn && d->qt_rgn->numRects) { + RgnHandle tmp_rgn = qt_mac_get_rgn(); + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + SetRectRgn(tmp_rgn, + qMax(SHRT_MIN, qt_r->x()), + qMax(SHRT_MIN, qt_r->y()), + qMin(SHRT_MAX, qt_r->right() + 1), + qMin(SHRT_MAX, qt_r->bottom() + 1)); + UnionRgn(rgnHandle, tmp_rgn, rgnHandle); + ++qt_r; + } + qt_mac_dispose_rgn(tmp_rgn); + } + return rgnHandle; +} + +/*! + \internal + Create's a RegionHandle, it's the caller's responsibility to release. + Returns 0 if the QRegion overflows. +*/ +RgnHandle QRegion::toQDRgnForUpdate_sys() const +{ + RgnHandle rgnHandle = qt_mac_get_rgn(); + if(d->qt_rgn && d->qt_rgn->numRects) { + RgnHandle tmp_rgn = qt_mac_get_rgn(); + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + + // detect overflow. Tested for use with HIViewSetNeedsDisplayInRegion + // in QWidgetPrivate::update_sys(). + enum { HIViewSetNeedsDisplayInRegionOverflow = 10000 }; // empirically determined conservative value + if (qt_r->right() > HIViewSetNeedsDisplayInRegionOverflow || qt_r->bottom() > HIViewSetNeedsDisplayInRegionOverflow) { + qt_mac_dispose_rgn(tmp_rgn); + qt_mac_dispose_rgn(rgnHandle); + return 0; + } + + SetRectRgn(tmp_rgn, + qMax(SHRT_MIN, qt_r->x()), + qMax(SHRT_MIN, qt_r->y()), + qMin(SHRT_MAX, qt_r->right() + 1), + qMin(SHRT_MAX, qt_r->bottom() + 1)); + UnionRgn(rgnHandle, tmp_rgn, rgnHandle); + ++qt_r; + } + qt_mac_dispose_rgn(tmp_rgn); + } + return rgnHandle; +} + +#endif + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +OSStatus QRegion::shape2QRegionHelper(int inMessage, HIShapeRef, + const CGRect *inRect, void *inRefcon) +{ + QRegion *region = static_cast<QRegion *>(inRefcon); + if (!region) + return paramErr; + + switch (inMessage) { + case kHIShapeEnumerateRect: + *region += QRect(inRect->origin.x, inRect->origin.y, + inRect->size.width, inRect->size.height); + break; + case kHIShapeEnumerateInit: + // Assume the region is already setup correctly + case kHIShapeEnumerateTerminate: + default: + break; + } + return noErr; +} +#endif + +/*! + \internal + Create's a mutable shape, it's the caller's responsibility to release. + WARNING: this function clamps the coordinates to SHRT_MIN/MAX on 10.4 and below. +*/ +HIMutableShapeRef QRegion::toHIMutableShape() const +{ + HIMutableShapeRef shape = HIShapeCreateMutable(); +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + if (d->qt_rgn && d->qt_rgn->numRects) { + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + CGRect cgRect = CGRectMake(qt_r->x(), qt_r->y(), qt_r->width(), qt_r->height()); + HIShapeUnionWithRect(shape, &cgRect); + ++qt_r; + } + } + } else +#endif + { +#ifndef QT_MAC_USE_COCOA + QCFType<HIShapeRef> qdShape = HIShapeCreateWithQDRgn(QMacSmartQuickDrawRegion(toQDRgn())); + HIShapeUnion(qdShape, shape, shape); +#endif + } + return shape; +} + +#if !defined(Q_WS_MAC64) && !defined(QT_MAC_USE_COCOA) +typedef OSStatus (*PtrHIShapeGetAsQDRgn)(HIShapeRef, RgnHandle); +static PtrHIShapeGetAsQDRgn ptrHIShapeGetAsQDRgn = 0; +#endif + + +QRegion QRegion::fromHIShapeRef(HIShapeRef shape) +{ + QRegion returnRegion; + returnRegion.detach(); + // Begin gratuitous #if-defery +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +# ifndef Q_WS_MAC64 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { +# endif + HIShapeEnumerate(shape, kHIShapeParseFromTopLeft, shape2QRegionHelper, &returnRegion); +# ifndef Q_WS_MAC64 + } else +# endif +#endif + { +#if !defined(Q_WS_MAC64) && !defined(QT_MAC_USE_COCOA) + if (ptrHIShapeGetAsQDRgn == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/Carbon.framework/Carbon")); + library.setLoadHints(QLibrary::ExportExternalSymbolsHint); + ptrHIShapeGetAsQDRgn = reinterpret_cast<PtrHIShapeGetAsQDRgn>(library.resolve("HIShapeGetAsQDRgn")); + } + RgnHandle rgn = qt_mac_get_rgn(); + ptrHIShapeGetAsQDRgn(shape, rgn); + returnRegion = QRegion::fromQDRgn(rgn); + qt_mac_dispose_rgn(rgn); +#endif + } + return returnRegion; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qregion_qws.cpp b/src/gui/painting/qregion_qws.cpp new file mode 100644 index 0000000000..dca46d3ec0 --- /dev/null +++ b/src/gui/painting/qregion_qws.cpp @@ -0,0 +1,3183 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// XXX - add appropriate friendship relationships +#define private public +#include "qregion.h" +#undef private +#include "qpainterpath.h" +#include "qpolygon.h" +#include "qbuffer.h" +#include "qimage.h" +#include <qdebug.h> +#include "qbitmap.h" +#include <stdlib.h> +#include <qatomic.h> +#include <qsemaphore.h> + +QT_BEGIN_NAMESPACE + +class QFastMutex +{ + QAtomicInt contenders; + QSemaphore semaphore; +public: + inline QFastMutex() + : contenders(0), semaphore(0) + { } + inline void lock() + { + if (contenders.fetchAndAddAcquire(1) != 0) { + semaphore.acquire(); + contenders.deref(); + } + } + inline bool tryLock() + { + return contenders.testAndSetAcquire(0, 1); + } + inline void unlock() + { + if (!contenders.testAndSetRelease(1, 0)) + semaphore.release(); + } +}; + + +/* + * 1 if r1 contains r2 + * 0 if r1 does not completely contain r2 + */ +#define CONTAINSCHECK(r1, r2) \ + ((r2).left() >= (r1).left() && (r2).right() <= (r1).right() && \ + (r2).top() >= (r1).top() && (r2).bottom() <= (r1).bottom()) + +/* + * clip region + */ +struct QRegionPrivate : public QRegion::QRegionData { + enum { Single, Vector } mode; + int numRects; + QVector<QRect> rects; + QRect single; + QRect extents; + QRect innerRect; + union { + int innerArea; + QRegionPrivate *next; + }; + + inline void vector() + { + if(mode != Vector && numRects) { + if(rects.size() < 1) rects.resize(1); + rects[0] = single; + } + mode = Vector; + } + + inline QRegionPrivate() : mode(Single), numRects(0), innerArea(-1) {} + inline QRegionPrivate(const QRect &r) : mode(Single) { + numRects = 1; +// rects[0] = r; + single = r; + extents = r; + innerRect = r; + innerArea = r.width() * r.height(); + } + + inline QRegionPrivate(const QRegionPrivate &r) { + mode = r.mode; + rects = r.rects; + single = r.single; + numRects = r.numRects; + extents = r.extents; + innerRect = r.innerRect; + innerArea = r.innerArea; + } + + inline QRegionPrivate &operator=(const QRegionPrivate &r) { + mode = r.mode; + rects = r.rects; + single = r.single; + numRects = r.numRects; + extents = r.extents; + innerRect = r.innerRect; + innerArea = r.innerArea; + return *this; + } + + /* + * Returns true if r is guaranteed to be fully contained in this region. + * A false return value does not guarantee the opposite. + */ + inline bool contains(const QRegionPrivate &r) const { + const QRect &r1 = innerRect; + const QRect &r2 = r.extents; + return CONTAINSCHECK(r1, r2); + } + + inline void updateInnerRect(const QRect &rect) { + const int area = rect.width() * rect.height(); + if (area > innerArea) { + innerArea = area; + innerRect = rect; + } + } + + void append(const QRegionPrivate *r); + void prepend(const QRegionPrivate *r); + inline bool canAppend(const QRegionPrivate *r) const; + inline bool canPrepend(const QRegionPrivate *r) const; +}; + +static QRegionPrivate *qt_nextRegionPtr = 0; +static QFastMutex qt_nextRegionLock; + +static QRegionPrivate *qt_allocRegionMemory() +{ + QRegionPrivate *rv = 0; + qt_nextRegionLock.lock(); + + if(qt_nextRegionPtr) { + rv = qt_nextRegionPtr; + qt_nextRegionPtr = rv->next; + } else { + qt_nextRegionPtr = + (QRegionPrivate *)malloc(256 * sizeof(QRegionPrivate)); + for(int ii = 0; ii < 256; ++ii) { + if(ii == 255) { + qt_nextRegionPtr[ii].next = 0; + } else { + qt_nextRegionPtr[ii].next = &qt_nextRegionPtr[ii + 1]; + } + } + + rv = qt_nextRegionPtr; + qt_nextRegionPtr = rv->next; + } + + qt_nextRegionLock.unlock(); + return rv; +} + +static void qt_freeRegionMemory(QRegionPrivate *rp) +{ + qt_nextRegionLock.lock(); + rp->next = qt_nextRegionPtr; + qt_nextRegionPtr = rp; + qt_nextRegionLock.unlock(); +} + +static QRegionPrivate *qt_allocRegion() +{ + QRegionPrivate *mem = qt_allocRegionMemory(); + return new (mem) QRegionPrivate; +} + +static QRegionPrivate *qt_allocRegion(const QRect &r) +{ + QRegionPrivate *mem = qt_allocRegionMemory(); + return new (mem) QRegionPrivate(r); +} + +static QRegionPrivate *qt_allocRegion(const QRegionPrivate &r) +{ + QRegionPrivate *mem = qt_allocRegionMemory(); + return new (mem) QRegionPrivate(r); +} + +void qt_freeRegion(QRegionPrivate *rp) +{ + rp->~QRegionPrivate(); + qt_freeRegionMemory(rp); +// delete rp; +} + +static inline bool isEmptyHelper(const QRegionPrivate *preg) +{ + return !preg || preg->numRects == 0; +} + +void QRegionPrivate::append(const QRegionPrivate *r) +{ + Q_ASSERT(!isEmptyHelper(r)); + + vector(); + QRect *destRect = rects.data() + numRects; + const QRect *srcRect = (r->mode==Vector)?r->rects.constData():&r->single; + int numAppend = r->numRects; + + // test for merge in x direction + { + const QRect *rFirst = srcRect; + QRect *myLast = rects.data() + (numRects - 1); + if (rFirst->top() == myLast->top() + && rFirst->height() == myLast->height() + && rFirst->left() == (myLast->right() + 1)) + { + myLast->setWidth(myLast->width() + rFirst->width()); + updateInnerRect(*myLast); + ++srcRect; + --numAppend; + } + } + + // append rectangles + const int newNumRects = numRects + numAppend; + if (newNumRects > rects.size()) { + rects.resize(newNumRects); + destRect = rects.data() + numRects; + } + memcpy(destRect, srcRect, numAppend * sizeof(QRect)); + + // update inner rectangle + if (innerArea < r->innerArea) { + innerArea = r->innerArea; + innerRect = r->innerRect; + } + + // update extents + destRect = &extents; + srcRect = &r->extents; + extents.setCoords(qMin(destRect->left(), srcRect->left()), + qMin(destRect->top(), srcRect->top()), + qMax(destRect->right(), srcRect->right()), + qMax(destRect->bottom(), srcRect->bottom())); + + numRects = newNumRects; +} + +void QRegionPrivate::prepend(const QRegionPrivate *r) +{ +#if 1 + Q_UNUSED(r); +#else + // XXX ak: does not respect vectorization of region + + Q_ASSERT(!isEmpty(r)); + + // move existing rectangles + memmove(rects.data() + r->numRects, rects.constData(), + numRects * sizeof(QRect)); + + // prepend new rectangles + memcpy(rects.data(), r->rects.constData(), r->numRects * sizeof(QRect)); + + // update inner rectangle + if (innerArea < r->innerArea) { + innerArea = r->innerArea; + innerRect = r->innerRect; + } + + // update extents + destRect = &extents; + srcRect = &r->extents; + extents.setCoords(qMin(destRect->left(), srcRect->left()), + qMin(destRect->top(), srcRect->top()), + qMax(destRect->right(), srcRect->right()), + qMax(destRect->bottom(), srcRect->bottom())); + + numRects = newNumRects; +#endif +} + +bool QRegionPrivate::canAppend(const QRegionPrivate *r) const +{ + Q_ASSERT(!isEmptyHelper(r)); + + const QRect *rFirst = (r->mode==Vector)?r->rects.constData():&r->single; + const QRect *myLast = (mode==Vector)?(rects.constData() + (numRects - 1)):&single; + // XXX: possible improvements: + // - nFirst->top() == myLast->bottom() + 1, must possibly merge bands + if (rFirst->top() > (myLast->bottom() + 1) + || (rFirst->top() == myLast->top() + && rFirst->height() == myLast->height() + && rFirst->left() > myLast->right())) + { + return true; + } + + return false; +} + +bool QRegionPrivate::canPrepend(const QRegionPrivate *r) const +{ +#if 1 + Q_UNUSED(r); + return false; +#else + return r->canAppend(this); +#endif +} + +#if defined(Q_WS_X11) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qregion_x11.cpp" +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_MAC) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qregion_mac.cpp" +QT_END_INCLUDE_NAMESPACE +#elif defined(Q_WS_QWS) +static QRegionPrivate qrp; +QRegion::QRegionData QRegion::shared_empty = {Q_BASIC_ATOMIC_INITIALIZER(1), &qrp}; +#endif + +typedef void (*OverlapFunc)(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2); +typedef void (*NonOverlapFunc)(register QRegionPrivate &dest, register const QRect *r, const QRect *rEnd, + register int y1, register int y2); + +static bool EqualRegion(const QRegionPrivate *r1, const QRegionPrivate *r2); +static void UnionRegion(const QRegionPrivate *reg1, const QRegionPrivate *reg2, QRegionPrivate &dest); +static void miRegionOp(register QRegionPrivate &dest, const QRegionPrivate *reg1, const QRegionPrivate *reg2, + OverlapFunc overlapFunc, NonOverlapFunc nonOverlap1Func, + NonOverlapFunc nonOverlap2Func); + +#define RectangleOut 0 +#define RectangleIn 1 +#define RectanglePart 2 +#define EvenOddRule 0 +#define WindingRule 1 + +// START OF region.h extract +/* $XConsortium: region.h,v 11.14 94/04/17 20:22:20 rws Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ + +#ifndef _XREGION_H +#define _XREGION_H + +QT_BEGIN_INCLUDE_NAMESPACE +#include <limits.h> +QT_END_INCLUDE_NAMESPACE + +/* 1 if two BOXs overlap. + * 0 if two BOXs do not overlap. + * Remember, x2 and y2 are not in the region + */ +#define EXTENTCHECK(r1, r2) \ + ((r1)->right() >= (r2)->left() && \ + (r1)->left() <= (r2)->right() && \ + (r1)->bottom() >= (r2)->top() && \ + (r1)->top() <= (r2)->bottom()) + +/* + * update region extents + */ +#define EXTENTS(r,idRect){\ + if((r)->left() < (idRect)->extents.left())\ + (idRect)->extents.setLeft((r)->left());\ + if((r)->top() < (idRect)->extents.top())\ + (idRect)->extents.setTop((r)->top());\ + if((r)->right() > (idRect)->extents.right())\ + (idRect)->extents.setRight((r)->right());\ + if((r)->bottom() > (idRect)->extents.bottom())\ + (idRect)->extents.setBottom((r)->bottom());\ + } + +/* + * Check to see if there is enough memory in the present region. + */ +#define MEMCHECK(dest, rect, firstrect){\ + if ((dest).numRects >= ((dest).rects.size()-1)){\ + firstrect.resize(firstrect.size() * 2); \ + (rect) = (firstrect).data() + (dest).numRects;\ + }\ + } + + +/* + * number of points to buffer before sending them off + * to scanlines(): Must be an even number + */ +#define NUMPTSTOBUFFER 200 + +/* + * used to allocate buffers for points and link + * the buffers together + */ +typedef struct _POINTBLOCK { + QPoint pts[NUMPTSTOBUFFER]; + struct _POINTBLOCK *next; +} POINTBLOCK; + +#endif +// END OF region.h extract + +// START OF Region.c extract +/* $XConsortium: Region.c /main/30 1996/10/22 14:21:24 kaleb $ */ +/************************************************************************ + +Copyright (c) 1987, 1988 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ +/* + * The functions in this file implement the Region abstraction, similar to one + * used in the X11 sample server. A Region is simply an area, as the name + * implies, and is implemented as a "y-x-banded" array of rectangles. To + * explain: Each Region is made up of a certain number of rectangles sorted + * by y coordinate first, and then by x coordinate. + * + * Furthermore, the rectangles are banded such that every rectangle with a + * given upper-left y coordinate (y1) will have the same lower-right y + * coordinate (y2) and vice versa. If a rectangle has scanlines in a band, it + * will span the entire vertical distance of the band. This means that some + * areas that could be merged into a taller rectangle will be represented as + * several shorter rectangles to account for shorter rectangles to its left + * or right but within its "vertical scope". + * + * An added constraint on the rectangles is that they must cover as much + * horizontal area as possible. E.g. no two rectangles in a band are allowed + * to touch. + * + * Whenever possible, bands will be merged together to cover a greater vertical + * distance (and thus reduce the number of rectangles). Two bands can be merged + * only if the bottom of one touches the top of the other and they have + * rectangles in the same places (of the same width, of course). This maintains + * the y-x-banding that's so nice to have... + */ +/* $XFree86: xc/lib/X11/Region.c,v 1.1.1.2.2.2 1998/10/04 15:22:50 hohndel Exp $ */ + +static void UnionRectWithRegion(register const QRect *rect, const QRegionPrivate *source, + QRegionPrivate &dest) +{ + if (!rect->width() || !rect->height()) + return; + + QRegionPrivate region(*rect); + + Q_ASSERT(EqualRegion(source, &dest)); + Q_ASSERT(!isEmptyHelper(®ion)); + + if (dest.numRects == 0) + dest = region; + else if (dest.canAppend(®ion)) + dest.append(®ion); + else + UnionRegion(®ion, source, dest); +} + +/*- + *----------------------------------------------------------------------- + * miSetExtents -- + * Reset the extents and innerRect of a region to what they should be. + * Called by miSubtract and miIntersect b/c they can't figure it out + * along the way or do so easily, as miUnion can. + * + * Results: + * None. + * + * Side Effects: + * The region's 'extents' and 'innerRect' structure is overwritten. + * + *----------------------------------------------------------------------- + */ +static void miSetExtents(QRegionPrivate &dest) +{ + register const QRect *pBox, + *pBoxEnd; + register QRect *pExtents; + + dest.innerRect.setCoords(0, 0, -1, -1); + dest.innerArea = -1; + if (dest.numRects == 0) { + dest.extents.setCoords(0, 0, 0, 0); + return; + } + + pExtents = &dest.extents; + pBox = (dest.mode==QRegionPrivate::Vector)?(dest.rects.constData()):(&dest.single); + pBoxEnd = (dest.mode==QRegionPrivate::Vector)?(&pBox[dest.numRects - 1]):(&dest.single); + + /* + * Since pBox is the first rectangle in the region, it must have the + * smallest y1 and since pBoxEnd is the last rectangle in the region, + * it must have the largest y2, because of banding. Initialize x1 and + * x2 from pBox and pBoxEnd, resp., as good things to initialize them + * to... + */ + pExtents->setLeft(pBox->left()); + pExtents->setTop(pBox->top()); + pExtents->setRight(pBoxEnd->right()); + pExtents->setBottom(pBoxEnd->bottom()); + + Q_ASSERT(pExtents->top() <= pExtents->bottom()); + while (pBox <= pBoxEnd) { + if (pBox->left() < pExtents->left()) + pExtents->setLeft(pBox->left()); + if (pBox->right() > pExtents->right()) + pExtents->setRight(pBox->right()); + dest.updateInnerRect(*pBox); + ++pBox; + } + Q_ASSERT(pExtents->left() <= pExtents->right()); +} + +/* TranslateRegion(pRegion, x, y) + translates in place + added by raymond +*/ + +static void OffsetRegion(register QRegionPrivate ®ion, register int x, register int y) +{ + register int nbox; + register QRect *pbox; + + if(region.mode == QRegionPrivate::Single) { + region.single.translate(x, y); + } else { + pbox = region.rects.data(); + nbox = region.numRects; + + while (nbox--) { + pbox->translate(x, y); + ++pbox; + } + } + region.extents.translate(x, y); + region.innerRect.translate(x, y); +} + +/*====================================================================== + * Region Intersection + *====================================================================*/ +/*- + *----------------------------------------------------------------------- + * miIntersectO -- + * Handle an overlapping band for miIntersect. + * + * Results: + * None. + * + * Side Effects: + * Rectangles may be added to the region. + * + *----------------------------------------------------------------------- + */ +static void miIntersectO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, int y1, int y2) +{ + register int x1; + register int x2; + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + while (r1 != r1End && r2 != r2End) { + x1 = qMax(r1->left(), r2->left()); + x2 = qMin(r1->right(), r2->right()); + + /* + * If there's any overlap between the two rectangles, add that + * overlap to the new region. + * There's no need to check for subsumption because the only way + * such a need could arise is if some region has two rectangles + * right next to each other. Since that should never happen... + */ + if (x1 <= x2) { + Q_ASSERT(y1 <= y2); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, x2, y2); + ++dest.numRects; + ++pNextRect; + } + + /* + * Need to advance the pointers. Shift the one that extends + * to the right the least, since the other still has a chance to + * overlap with that region's next rectangle, if you see what I mean. + */ + if (r1->right() < r2->right()) { + ++r1; + } else if (r2->right() < r1->right()) { + ++r2; + } else { + ++r1; + ++r2; + } + } +} + +/*====================================================================== + * Generic Region Operator + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miCoalesce -- + * Attempt to merge the boxes in the current band with those in the + * previous one. Used only by miRegionOp. + * + * Results: + * The new index for the previous band. + * + * Side Effects: + * If coalescing takes place: + * - rectangles in the previous band will have their y2 fields + * altered. + * - dest.numRects will be decreased. + * + *----------------------------------------------------------------------- + */ +static int miCoalesce(register QRegionPrivate &dest, int prevStart, int curStart) +{ + register QRect *pPrevBox; /* Current box in previous band */ + register QRect *pCurBox; /* Current box in current band */ + register QRect *pRegEnd; /* End of region */ + int curNumRects; /* Number of rectangles in current band */ + int prevNumRects; /* Number of rectangles in previous band */ + int bandY1; /* Y1 coordinate for current band */ + QRect *rData = dest.rects.data(); + + pRegEnd = rData + dest.numRects; + + pPrevBox = rData + prevStart; + prevNumRects = curStart - prevStart; + + /* + * Figure out how many rectangles are in the current band. Have to do + * this because multiple bands could have been added in miRegionOp + * at the end when one region has been exhausted. + */ + pCurBox = rData + curStart; + bandY1 = pCurBox->top(); + for (curNumRects = 0; pCurBox != pRegEnd && pCurBox->top() == bandY1; ++curNumRects) { + ++pCurBox; + } + + if (pCurBox != pRegEnd) { + /* + * If more than one band was added, we have to find the start + * of the last band added so the next coalescing job can start + * at the right place... (given when multiple bands are added, + * this may be pointless -- see above). + */ + --pRegEnd; + while ((pRegEnd - 1)->top() == pRegEnd->top()) + --pRegEnd; + curStart = pRegEnd - rData; + pRegEnd = rData + dest.numRects; + } + + if (curNumRects == prevNumRects && curNumRects != 0) { + pCurBox -= curNumRects; + /* + * The bands may only be coalesced if the bottom of the previous + * matches the top scanline of the current. + */ + if (pPrevBox->bottom() == pCurBox->top() - 1) { + /* + * Make sure the bands have boxes in the same places. This + * assumes that boxes have been added in such a way that they + * cover the most area possible. I.e. two boxes in a band must + * have some horizontal space between them. + */ + do { + if (pPrevBox->left() != pCurBox->left() || pPrevBox->right() != pCurBox->right()) { + // The bands don't line up so they can't be coalesced. + return curStart; + } + ++pPrevBox; + ++pCurBox; + --prevNumRects; + } while (prevNumRects != 0); + + dest.numRects -= curNumRects; + pCurBox -= curNumRects; + pPrevBox -= curNumRects; + + /* + * The bands may be merged, so set the bottom y of each box + * in the previous band to that of the corresponding box in + * the current band. + */ + do { + pPrevBox->setBottom(pCurBox->bottom()); + dest.updateInnerRect(*pPrevBox); + ++pPrevBox; + ++pCurBox; + curNumRects -= 1; + } while (curNumRects != 0); + + /* + * If only one band was added to the region, we have to backup + * curStart to the start of the previous band. + * + * If more than one band was added to the region, copy the + * other bands down. The assumption here is that the other bands + * came from the same region as the current one and no further + * coalescing can be done on them since it's all been done + * already... curStart is already in the right place. + */ + if (pCurBox == pRegEnd) { + curStart = prevStart; + } else { + do { + *pPrevBox++ = *pCurBox++; + dest.updateInnerRect(*pPrevBox); + } while (pCurBox != pRegEnd); + } + } + } + return curStart; +} + +/*- + *----------------------------------------------------------------------- + * miRegionOp -- + * Apply an operation to two regions. Called by miUnion, miInverse, + * miSubtract, miIntersect... + * + * Results: + * None. + * + * Side Effects: + * The new region is overwritten. + * + * Notes: + * The idea behind this function is to view the two regions as sets. + * Together they cover a rectangle of area that this function divides + * into horizontal bands where points are covered only by one region + * or by both. For the first case, the nonOverlapFunc is called with + * each the band and the band's upper and lower extents. For the + * second, the overlapFunc is called to process the entire band. It + * is responsible for clipping the rectangles in the band, though + * this function provides the boundaries. + * At the end of each band, the new region is coalesced, if possible, + * to reduce the number of rectangles in the region. + * + *----------------------------------------------------------------------- + */ +static void miRegionOp(register QRegionPrivate &dest, const QRegionPrivate *reg1, const QRegionPrivate *reg2, + OverlapFunc overlapFunc, NonOverlapFunc nonOverlap1Func, + NonOverlapFunc nonOverlap2Func) +{ + register const QRect *r1; // Pointer into first region + register const QRect *r2; // Pointer into 2d region + const QRect *r1End; // End of 1st region + const QRect *r2End; // End of 2d region + register int ybot; // Bottom of intersection + register int ytop; // Top of intersection + int prevBand; // Index of start of previous band in dest + int curBand; // Index of start of current band in dest + register const QRect *r1BandEnd; // End of current band in r1 + register const QRect *r2BandEnd; // End of current band in r2 + int top; // Top of non-overlapping band + int bot; // Bottom of non-overlapping band + + /* + * Initialization: + * set r1, r2, r1End and r2End appropriately, preserve the important + * parts of the destination region until the end in case it's one of + * the two source regions, then mark the "new" region empty, allocating + * another array of rectangles for it to use. + */ + r1 = (reg1->mode==QRegionPrivate::Vector)?reg1->rects.data():®1->single; + r2 = (reg2->mode==QRegionPrivate::Vector)?reg2->rects.data():®2->single; + r1End = r1 + reg1->numRects; + r2End = r2 + reg2->numRects; + + dest.vector(); + QVector<QRect> oldRects = dest.rects; + + dest.numRects = 0; + + /* + * Allocate a reasonable number of rectangles for the new region. The idea + * is to allocate enough so the individual functions don't need to + * reallocate and copy the array, which is time consuming, yet we don't + * have to worry about using too much memory. I hope to be able to + * nuke the realloc() at the end of this function eventually. + */ + dest.rects.resize(qMax(reg1->numRects,reg2->numRects) * 2); + + /* + * Initialize ybot and ytop. + * In the upcoming loop, ybot and ytop serve different functions depending + * on whether the band being handled is an overlapping or non-overlapping + * band. + * In the case of a non-overlapping band (only one of the regions + * has points in the band), ybot is the bottom of the most recent + * intersection and thus clips the top of the rectangles in that band. + * ytop is the top of the next intersection between the two regions and + * serves to clip the bottom of the rectangles in the current band. + * For an overlapping band (where the two regions intersect), ytop clips + * the top of the rectangles of both regions and ybot clips the bottoms. + */ + if (reg1->extents.top() < reg2->extents.top()) + ybot = reg1->extents.top() - 1; + else + ybot = reg2->extents.top() - 1; + + /* + * prevBand serves to mark the start of the previous band so rectangles + * can be coalesced into larger rectangles. qv. miCoalesce, above. + * In the beginning, there is no previous band, so prevBand == curBand + * (curBand is set later on, of course, but the first band will always + * start at index 0). prevBand and curBand must be indices because of + * the possible expansion, and resultant moving, of the new region's + * array of rectangles. + */ + prevBand = 0; + + do { + curBand = dest.numRects; + + /* + * This algorithm proceeds one source-band (as opposed to a + * destination band, which is determined by where the two regions + * intersect) at a time. r1BandEnd and r2BandEnd serve to mark the + * rectangle after the last one in the current band for their + * respective regions. + */ + r1BandEnd = r1; + while (r1BandEnd != r1End && r1BandEnd->top() == r1->top()) + ++r1BandEnd; + + r2BandEnd = r2; + while (r2BandEnd != r2End && r2BandEnd->top() == r2->top()) + ++r2BandEnd; + + /* + * First handle the band that doesn't intersect, if any. + * + * Note that attention is restricted to one band in the + * non-intersecting region at once, so if a region has n + * bands between the current position and the next place it overlaps + * the other, this entire loop will be passed through n times. + */ + if (r1->top() < r2->top()) { + top = qMax(r1->top(), ybot + 1); + bot = qMin(r1->bottom(), r2->top() - 1); + + if (nonOverlap1Func != 0 && bot >= top) + (*nonOverlap1Func)(dest, r1, r1BandEnd, top, bot); + ytop = r2->top(); + } else if (r2->top() < r1->top()) { + top = qMax(r2->top(), ybot + 1); + bot = qMin(r2->bottom(), r1->top() - 1); + + if (nonOverlap2Func != 0 && bot >= top) + (*nonOverlap2Func)(dest, r2, r2BandEnd, top, bot); + ytop = r1->top(); + } else { + ytop = r1->top(); + } + + /* + * If any rectangles got added to the region, try and coalesce them + * with rectangles from the previous band. Note we could just do + * this test in miCoalesce, but some machines incur a not + * inconsiderable cost for function calls, so... + */ + if (dest.numRects != curBand) + prevBand = miCoalesce(dest, prevBand, curBand); + + /* + * Now see if we've hit an intersecting band. The two bands only + * intersect if ybot >= ytop + */ + ybot = qMin(r1->bottom(), r2->bottom()); + curBand = dest.numRects; + if (ybot >= ytop) + (*overlapFunc)(dest, r1, r1BandEnd, r2, r2BandEnd, ytop, ybot); + + if (dest.numRects != curBand) + prevBand = miCoalesce(dest, prevBand, curBand); + + /* + * If we've finished with a band (y2 == ybot) we skip forward + * in the region to the next band. + */ + if (r1->bottom() == ybot) + r1 = r1BandEnd; + if (r2->bottom() == ybot) + r2 = r2BandEnd; + } while (r1 != r1End && r2 != r2End); + + /* + * Deal with whichever region still has rectangles left. + */ + curBand = dest.numRects; + if (r1 != r1End) { + if (nonOverlap1Func != 0) { + do { + r1BandEnd = r1; + while (r1BandEnd < r1End && r1BandEnd->top() == r1->top()) + ++r1BandEnd; + (*nonOverlap1Func)(dest, r1, r1BandEnd, qMax(r1->top(), ybot + 1), r1->bottom()); + r1 = r1BandEnd; + } while (r1 != r1End); + } + } else if ((r2 != r2End) && (nonOverlap2Func != 0)) { + do { + r2BandEnd = r2; + while (r2BandEnd < r2End && r2BandEnd->top() == r2->top()) + ++r2BandEnd; + (*nonOverlap2Func)(dest, r2, r2BandEnd, qMax(r2->top(), ybot + 1), r2->bottom()); + r2 = r2BandEnd; + } while (r2 != r2End); + } + + if (dest.numRects != curBand) + (void)miCoalesce(dest, prevBand, curBand); + + /* + * A bit of cleanup. To keep regions from growing without bound, + * we shrink the array of rectangles to match the new number of + * rectangles in the region. + * + * Only do this stuff if the number of rectangles allocated is more than + * twice the number of rectangles in the region (a simple optimization). + */ + if (qMax(4, dest.numRects) < (dest.rects.size() >> 1)) + dest.rects.resize(dest.numRects); +} + +/*====================================================================== + * Region Union + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miUnionNonO -- + * Handle a non-overlapping band for the union operation. Just + * Adds the rectangles into the region. Doesn't have to check for + * subsumption or anything. + * + * Results: + * None. + * + * Side Effects: + * dest.numRects is incremented and the final rectangles overwritten + * with the rectangles we're passed. + * + *----------------------------------------------------------------------- + */ + +static void miUnionNonO(register QRegionPrivate &dest, register const QRect *r, const QRect *rEnd, + register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + Q_ASSERT(y1 <= y2); + + while (r != rEnd) { + Q_ASSERT(r->left() <= r->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(r->left(), y1, r->right(), y2); + dest.numRects++; + ++pNextRect; + ++r; + } +} + + +/*- + *----------------------------------------------------------------------- + * miUnionO -- + * Handle an overlapping band for the union operation. Picks the + * left-most rectangle each time and merges it into the region. + * + * Results: + * None. + * + * Side Effects: + * Rectangles are overwritten in dest.rects and dest.numRects will + * be changed. + * + *----------------------------------------------------------------------- + */ + +static void miUnionO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + +#define MERGERECT(r) \ + if ((dest.numRects != 0) && \ + (pNextRect[-1].top() == y1) && \ + (pNextRect[-1].bottom() == y2) && \ + (pNextRect[-1].right() >= r->left()-1)) { \ + if (pNextRect[-1].right() < r->right()) { \ + pNextRect[-1].setRight(r->right()); \ + dest.updateInnerRect(pNextRect[-1]); \ + Q_ASSERT(pNextRect[-1].left() <= pNextRect[-1].right()); \ + } \ + } else { \ + MEMCHECK(dest, pNextRect, dest.rects) \ + pNextRect->setCoords(r->left(), y1, r->right(), y2); \ + dest.updateInnerRect(*pNextRect); \ + dest.numRects++; \ + pNextRect++; \ + } \ + r++; + + Q_ASSERT(y1 <= y2); + while (r1 != r1End && r2 != r2End) { + if (r1->left() < r2->left()) { + MERGERECT(r1) + } else { + MERGERECT(r2) + } + } + + if (r1 != r1End) { + do { + MERGERECT(r1) + } while (r1 != r1End); + } else { + while (r2 != r2End) { + MERGERECT(r2) + } + } +} + +static void UnionRegion(const QRegionPrivate *reg1, const QRegionPrivate *reg2, QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(reg1) && !isEmptyHelper(reg2)); + Q_ASSERT(!reg1->contains(*reg2)); + Q_ASSERT(!reg2->contains(*reg1)); + Q_ASSERT(!EqualRegion(reg1, reg2)); + Q_ASSERT(!reg1->canAppend(reg2)); + Q_ASSERT(!reg2->canAppend(reg1)); + + if (reg1->innerArea > reg2->innerArea) { + dest.innerArea = reg1->innerArea; + dest.innerRect = reg1->innerRect; + } else { + dest.innerArea = reg2->innerArea; + dest.innerRect = reg2->innerRect; + } + miRegionOp(dest, reg1, reg2, miUnionO, miUnionNonO, miUnionNonO); + + dest.extents.setCoords(qMin(reg1->extents.left(), reg2->extents.left()), + qMin(reg1->extents.top(), reg2->extents.top()), + qMax(reg1->extents.right(), reg2->extents.right()), + qMax(reg1->extents.bottom(), reg2->extents.bottom())); +} + +/*====================================================================== + * Region Subtraction + *====================================================================*/ + +/*- + *----------------------------------------------------------------------- + * miSubtractNonO -- + * Deal with non-overlapping band for subtraction. Any parts from + * region 2 we discard. Anything from region 1 we add to the region. + * + * Results: + * None. + * + * Side Effects: + * dest may be affected. + * + *----------------------------------------------------------------------- + */ + +static void miSubtractNonO1(register QRegionPrivate &dest, register const QRect *r, + const QRect *rEnd, register int y1, register int y2) +{ + register QRect *pNextRect; + + pNextRect = dest.rects.data() + dest.numRects; + + Q_ASSERT(y1<=y2); + + while (r != rEnd) { + Q_ASSERT(r->left() <= r->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(r->left(), y1, r->right(), y2); + ++dest.numRects; + ++pNextRect; + ++r; + } +} + +/*- + *----------------------------------------------------------------------- + * miSubtractO -- + * Overlapping band subtraction. x1 is the left-most point not yet + * checked. + * + * Results: + * None. + * + * Side Effects: + * dest may have rectangles added to it. + * + *----------------------------------------------------------------------- + */ + +static void miSubtractO(register QRegionPrivate &dest, register const QRect *r1, const QRect *r1End, + register const QRect *r2, const QRect *r2End, register int y1, register int y2) +{ + register QRect *pNextRect; + register int x1; + + x1 = r1->left(); + + Q_ASSERT(y1 <= y2); + pNextRect = dest.rects.data() + dest.numRects; + + while (r1 != r1End && r2 != r2End) { + if (r2->right() < x1) { + /* + * Subtrahend missed the boat: go to next subtrahend. + */ + ++r2; + } else if (r2->left() <= x1) { + /* + * Subtrahend precedes minuend: nuke left edge of minuend. + */ + x1 = r2->right() + 1; + if (x1 > r1->right()) { + /* + * Minuend completely covered: advance to next minuend and + * reset left fence to edge of new minuend. + */ + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } else { + // Subtrahend now used up since it doesn't extend beyond minuend + ++r2; + } + } else if (r2->left() <= r1->right()) { + /* + * Left part of subtrahend covers part of minuend: add uncovered + * part of minuend to region and skip to next subtrahend. + */ + Q_ASSERT(x1 < r2->left()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r2->left() - 1, y2); + ++dest.numRects; + ++pNextRect; + + x1 = r2->right() + 1; + if (x1 > r1->right()) { + /* + * Minuend used up: advance to new... + */ + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } else { + // Subtrahend used up + ++r2; + } + } else { + /* + * Minuend used up: add any remaining piece before advancing. + */ + if (r1->right() >= x1) { + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r1->right(), y2); + ++dest.numRects; + ++pNextRect; + } + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } + } + + /* + * Add remaining minuend rectangles to region. + */ + while (r1 != r1End) { + Q_ASSERT(x1 <= r1->right()); + MEMCHECK(dest, pNextRect, dest.rects) + pNextRect->setCoords(x1, y1, r1->right(), y2); + ++dest.numRects; + ++pNextRect; + + ++r1; + if (r1 != r1End) + x1 = r1->left(); + } +} + +/*- + *----------------------------------------------------------------------- + * miSubtract -- + * Subtract regS from regM and leave the result in regD. + * S stands for subtrahend, M for minuend and D for difference. + * + * Side Effects: + * regD is overwritten. + * + *----------------------------------------------------------------------- + */ + +static void SubtractRegion(QRegionPrivate *regM, QRegionPrivate *regS, + register QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(regM)); + Q_ASSERT(!isEmptyHelper(regS)); + Q_ASSERT(EXTENTCHECK(®M->extents, ®S->extents)); + Q_ASSERT(!regS->contains(*regM)); + Q_ASSERT(!EqualRegion(regM, regS)); + + miRegionOp(dest, regM, regS, miSubtractO, miSubtractNonO1, 0); + + /* + * Can't alter dest's extents before we call miRegionOp because + * it might be one of the source regions and miRegionOp depends + * on the extents of those regions being the unaltered. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + miSetExtents(dest); +} + +static void XorRegion(QRegionPrivate *sra, QRegionPrivate *srb, QRegionPrivate &dest) +{ + Q_ASSERT(!isEmptyHelper(sra) && !isEmptyHelper(srb)); + Q_ASSERT(EXTENTCHECK(&sra->extents, &srb->extents)); + Q_ASSERT(!EqualRegion(sra, srb)); + + QRegionPrivate tra, trb; + + if (!srb->contains(*sra)) + SubtractRegion(sra, srb, tra); + if (!sra->contains(*srb)) + SubtractRegion(srb, sra, trb); + + Q_ASSERT(isEmptyHelper(&trb) || !tra.contains(trb)); + Q_ASSERT(isEmptyHelper(&tra) || !trb.contains(tra)); + + if (isEmptyHelper(&tra)) { + dest = trb; + } else if (isEmptyHelper(&trb)) { + dest = tra; + } else if (tra.canAppend(&trb)) { + dest = tra; + dest.append(&trb); + } else if (trb.canAppend(&tra)) { + dest = trb; + dest.append(&tra); + } else { + UnionRegion(&tra, &trb, dest); + } +} + +/* + * Check to see if two regions are equal + */ +static bool EqualRegion(const QRegionPrivate *r1, const QRegionPrivate *r2) +{ + if (r1->numRects != r2->numRects) { + return false; + } else if (r1->numRects == 0) { + return true; + } else if (r1->extents != r2->extents) { + return false; + } else if (r1->mode == QRegionPrivate::Single && r2->mode == QRegionPrivate::Single) { + return r1->single == r2->single; + } else { + const QRect *rr1 = (r1->mode==QRegionPrivate::Vector)?r1->rects.constData():&r1->single; + const QRect *rr2 = (r2->mode==QRegionPrivate::Vector)?r2->rects.constData():&r2->single; + for (int i = 0; i < r1->numRects; ++i, ++rr1, ++rr2) { + if (*rr1 != *rr2) + return false; + } + } + + return true; +} + +static bool PointInRegion(QRegionPrivate *pRegion, int x, int y) +{ + int i; + + if (pRegion->mode == QRegionPrivate::Single) + return pRegion->single.contains(x, y); + if (isEmptyHelper(pRegion)) + return false; + if (!pRegion->extents.contains(x, y)) + return false; + if (pRegion->innerRect.contains(x, y)) + return true; + for (i = 0; i < pRegion->numRects; ++i) { + if (pRegion->rects[i].contains(x, y)) + return true; + } + return false; +} + +static bool RectInRegion(register QRegionPrivate *region, int rx, int ry, uint rwidth, uint rheight) +{ + register const QRect *pbox; + register const QRect *pboxEnd; + QRect rect(rx, ry, rwidth, rheight); + register QRect *prect = ▭ + int partIn, partOut; + + if (!region || region->numRects == 0 || !EXTENTCHECK(®ion->extents, prect)) + return RectangleOut; + + partOut = false; + partIn = false; + + /* can stop when both partOut and partIn are true, or we reach prect->y2 */ + for (pbox = (region->mode==QRegionPrivate::Vector)?region->rects.constData():®ion->single, pboxEnd = pbox + region->numRects; + pbox < pboxEnd; ++pbox) { + if (pbox->bottom() < ry) + continue; + + if (pbox->top() > ry) { + partOut = true; + if (partIn || pbox->top() > prect->bottom()) + break; + ry = pbox->top(); + } + + if (pbox->right() < rx) + continue; /* not far enough over yet */ + + if (pbox->left() > rx) { + partOut = true; /* missed part of rectangle to left */ + if (partIn) + break; + } + + if (pbox->left() <= prect->right()) { + partIn = true; /* definitely overlap */ + if (partOut) + break; + } + + if (pbox->right() >= prect->right()) { + ry = pbox->bottom() + 1; /* finished with this band */ + if (ry > prect->bottom()) + break; + rx = prect->left(); /* reset x out to left again */ + } else { + /* + * Because boxes in a band are maximal width, if the first box + * to overlap the rectangle doesn't completely cover it in that + * band, the rectangle must be partially out, since some of it + * will be uncovered in that band. partIn will have been set true + * by now... + */ + break; + } + } + return partIn ? ((ry <= prect->bottom()) ? RectanglePart : RectangleIn) : RectangleOut; +} +// END OF Region.c extract +// START OF poly.h extract +/* $XConsortium: poly.h,v 1.4 94/04/17 20:22:19 rws Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ + +/* + * This file contains a few macros to help track + * the edge of a filled object. The object is assumed + * to be filled in scanline order, and thus the + * algorithm used is an extension of Bresenham's line + * drawing algorithm which assumes that y is always the + * major axis. + * Since these pieces of code are the same for any filled shape, + * it is more convenient to gather the library in one + * place, but since these pieces of code are also in + * the inner loops of output primitives, procedure call + * overhead is out of the question. + * See the author for a derivation if needed. + */ + + +/* + * In scan converting polygons, we want to choose those pixels + * which are inside the polygon. Thus, we add .5 to the starting + * x coordinate for both left and right edges. Now we choose the + * first pixel which is inside the pgon for the left edge and the + * first pixel which is outside the pgon for the right edge. + * Draw the left pixel, but not the right. + * + * How to add .5 to the starting x coordinate: + * If the edge is moving to the right, then subtract dy from the + * error term from the general form of the algorithm. + * If the edge is moving to the left, then add dy to the error term. + * + * The reason for the difference between edges moving to the left + * and edges moving to the right is simple: If an edge is moving + * to the right, then we want the algorithm to flip immediately. + * If it is moving to the left, then we don't want it to flip until + * we traverse an entire pixel. + */ +#define BRESINITPGON(dy, x1, x2, xStart, d, m, m1, incr1, incr2) { \ + int dx; /* local storage */ \ +\ + /* \ + * if the edge is horizontal, then it is ignored \ + * and assumed not to be processed. Otherwise, do this stuff. \ + */ \ + if ((dy) != 0) { \ + xStart = (x1); \ + dx = (x2) - xStart; \ + if (dx < 0) { \ + m = dx / (dy); \ + m1 = m - 1; \ + incr1 = -2 * dx + 2 * (dy) * m1; \ + incr2 = -2 * dx + 2 * (dy) * m; \ + d = 2 * m * (dy) - 2 * dx - 2 * (dy); \ + } else { \ + m = dx / (dy); \ + m1 = m + 1; \ + incr1 = 2 * dx - 2 * (dy) * m1; \ + incr2 = 2 * dx - 2 * (dy) * m; \ + d = -2 * m * (dy) + 2 * dx; \ + } \ + } \ +} + +#define BRESINCRPGON(d, minval, m, m1, incr1, incr2) { \ + if (m1 > 0) { \ + if (d > 0) { \ + minval += m1; \ + d += incr1; \ + } \ + else { \ + minval += m; \ + d += incr2; \ + } \ + } else {\ + if (d >= 0) { \ + minval += m1; \ + d += incr1; \ + } \ + else { \ + minval += m; \ + d += incr2; \ + } \ + } \ +} + + +/* + * This structure contains all of the information needed + * to run the bresenham algorithm. + * The variables may be hardcoded into the declarations + * instead of using this structure to make use of + * register declarations. + */ +typedef struct { + int minor_axis; /* minor axis */ + int d; /* decision variable */ + int m, m1; /* slope and slope+1 */ + int incr1, incr2; /* error increments */ +} BRESINFO; + + +#define BRESINITPGONSTRUCT(dmaj, min1, min2, bres) \ + BRESINITPGON(dmaj, min1, min2, bres.minor_axis, bres.d, \ + bres.m, bres.m1, bres.incr1, bres.incr2) + +#define BRESINCRPGONSTRUCT(bres) \ + BRESINCRPGON(bres.d, bres.minor_axis, bres.m, bres.m1, bres.incr1, bres.incr2) + + + +/* + * These are the data structures needed to scan + * convert regions. Two different scan conversion + * methods are available -- the even-odd method, and + * the winding number method. + * The even-odd rule states that a point is inside + * the polygon if a ray drawn from that point in any + * direction will pass through an odd number of + * path segments. + * By the winding number rule, a point is decided + * to be inside the polygon if a ray drawn from that + * point in any direction passes through a different + * number of clockwise and counter-clockwise path + * segments. + * + * These data structures are adapted somewhat from + * the algorithm in (Foley/Van Dam) for scan converting + * polygons. + * The basic algorithm is to start at the top (smallest y) + * of the polygon, stepping down to the bottom of + * the polygon by incrementing the y coordinate. We + * keep a list of edges which the current scanline crosses, + * sorted by x. This list is called the Active Edge Table (AET) + * As we change the y-coordinate, we update each entry in + * in the active edge table to reflect the edges new xcoord. + * This list must be sorted at each scanline in case + * two edges intersect. + * We also keep a data structure known as the Edge Table (ET), + * which keeps track of all the edges which the current + * scanline has not yet reached. The ET is basically a + * list of ScanLineList structures containing a list of + * edges which are entered at a given scanline. There is one + * ScanLineList per scanline at which an edge is entered. + * When we enter a new edge, we move it from the ET to the AET. + * + * From the AET, we can implement the even-odd rule as in + * (Foley/Van Dam). + * The winding number rule is a little trickier. We also + * keep the EdgeTableEntries in the AET linked by the + * nextWETE (winding EdgeTableEntry) link. This allows + * the edges to be linked just as before for updating + * purposes, but only uses the edges linked by the nextWETE + * link as edges representing spans of the polygon to + * drawn (as with the even-odd rule). + */ + +/* + * for the winding number rule + */ +#define CLOCKWISE 1 +#define COUNTERCLOCKWISE -1 + +typedef struct _EdgeTableEntry { + int ymax; /* ycoord at which we exit this edge. */ + BRESINFO bres; /* Bresenham info to run the edge */ + struct _EdgeTableEntry *next; /* next in the list */ + struct _EdgeTableEntry *back; /* for insertion sort */ + struct _EdgeTableEntry *nextWETE; /* for winding num rule */ + int ClockWise; /* flag for winding number rule */ +} EdgeTableEntry; + + +typedef struct _ScanLineList{ + int scanline; /* the scanline represented */ + EdgeTableEntry *edgelist; /* header node */ + struct _ScanLineList *next; /* next in the list */ +} ScanLineList; + + +typedef struct { + int ymax; /* ymax for the polygon */ + int ymin; /* ymin for the polygon */ + ScanLineList scanlines; /* header node */ +} EdgeTable; + + +/* + * Here is a struct to help with storage allocation + * so we can allocate a big chunk at a time, and then take + * pieces from this heap when we need to. + */ +#define SLLSPERBLOCK 25 + +typedef struct _ScanLineListBlock { + ScanLineList SLLs[SLLSPERBLOCK]; + struct _ScanLineListBlock *next; +} ScanLineListBlock; + + + +/* + * + * a few macros for the inner loops of the fill code where + * performance considerations don't allow a procedure call. + * + * Evaluate the given edge at the given scanline. + * If the edge has expired, then we leave it and fix up + * the active edge table; otherwise, we increment the + * x value to be ready for the next scanline. + * The winding number rule is in effect, so we must notify + * the caller when the edge has been removed so he + * can reorder the Winding Active Edge Table. + */ +#define EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET) { \ + if (pAET->ymax == y) { /* leaving this edge */ \ + pPrevAET->next = pAET->next; \ + pAET = pPrevAET->next; \ + fixWAET = 1; \ + if (pAET) \ + pAET->back = pPrevAET; \ + } \ + else { \ + BRESINCRPGONSTRUCT(pAET->bres) \ + pPrevAET = pAET; \ + pAET = pAET->next; \ + } \ +} + + +/* + * Evaluate the given edge at the given scanline. + * If the edge has expired, then we leave it and fix up + * the active edge table; otherwise, we increment the + * x value to be ready for the next scanline. + * The even-odd rule is in effect. + */ +#define EVALUATEEDGEEVENODD(pAET, pPrevAET, y) { \ + if (pAET->ymax == y) { /* leaving this edge */ \ + pPrevAET->next = pAET->next; \ + pAET = pPrevAET->next; \ + if (pAET) \ + pAET->back = pPrevAET; \ + } \ + else { \ + BRESINCRPGONSTRUCT(pAET->bres) \ + pPrevAET = pAET; \ + pAET = pAET->next; \ + } \ +} +// END OF poly.h extract +// START OF PolyReg.c extract +/* $XConsortium: PolyReg.c,v 11.23 94/11/17 21:59:37 converse Exp $ */ +/************************************************************************ + +Copyright (c) 1987 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +************************************************************************/ +/* $XFree86: xc/lib/X11/PolyReg.c,v 1.1.1.2.8.2 1998/10/04 15:22:49 hohndel Exp $ */ + +#define LARGE_COORDINATE 1000000 +#define SMALL_COORDINATE -LARGE_COORDINATE + +/* + * InsertEdgeInET + * + * Insert the given edge into the edge table. + * First we must find the correct bucket in the + * Edge table, then find the right slot in the + * bucket. Finally, we can insert it. + * + */ +static void InsertEdgeInET(EdgeTable *ET, EdgeTableEntry *ETE, int scanline, + ScanLineListBlock **SLLBlock, int *iSLLBlock) +{ + register EdgeTableEntry *start, *prev; + register ScanLineList *pSLL, *pPrevSLL; + ScanLineListBlock *tmpSLLBlock; + + /* + * find the right bucket to put the edge into + */ + pPrevSLL = &ET->scanlines; + pSLL = pPrevSLL->next; + while (pSLL && (pSLL->scanline < scanline)) { + pPrevSLL = pSLL; + pSLL = pSLL->next; + } + + /* + * reassign pSLL (pointer to ScanLineList) if necessary + */ + if ((!pSLL) || (pSLL->scanline > scanline)) { + if (*iSLLBlock > SLLSPERBLOCK-1) + { + tmpSLLBlock = + (ScanLineListBlock *)malloc(sizeof(ScanLineListBlock)); + (*SLLBlock)->next = tmpSLLBlock; + tmpSLLBlock->next = (ScanLineListBlock *)NULL; + *SLLBlock = tmpSLLBlock; + *iSLLBlock = 0; + } + pSLL = &((*SLLBlock)->SLLs[(*iSLLBlock)++]); + + pSLL->next = pPrevSLL->next; + pSLL->edgelist = (EdgeTableEntry *)NULL; + pPrevSLL->next = pSLL; + } + pSLL->scanline = scanline; + + /* + * now insert the edge in the right bucket + */ + prev = 0; + start = pSLL->edgelist; + while (start && (start->bres.minor_axis < ETE->bres.minor_axis)) { + prev = start; + start = start->next; + } + ETE->next = start; + + if (prev) + prev->next = ETE; + else + pSLL->edgelist = ETE; +} + +/* + * CreateEdgeTable + * + * This routine creates the edge table for + * scan converting polygons. + * The Edge Table (ET) looks like: + * + * EdgeTable + * -------- + * | ymax | ScanLineLists + * |scanline|-->------------>-------------->... + * -------- |scanline| |scanline| + * |edgelist| |edgelist| + * --------- --------- + * | | + * | | + * V V + * list of ETEs list of ETEs + * + * where ETE is an EdgeTableEntry data structure, + * and there is one ScanLineList per scanline at + * which an edge is initially entered. + * + */ + +static void CreateETandAET(register int count, register const QPoint *pts, + EdgeTable *ET, EdgeTableEntry *AET, register EdgeTableEntry *pETEs, + ScanLineListBlock *pSLLBlock) +{ + register const QPoint *top, + *bottom, + *PrevPt, + *CurrPt; + int iSLLBlock = 0; + int dy; + + if (count < 2) + return; + + /* + * initialize the Active Edge Table + */ + AET->next = 0; + AET->back = 0; + AET->nextWETE = 0; + AET->bres.minor_axis = SMALL_COORDINATE; + + /* + * initialize the Edge Table. + */ + ET->scanlines.next = 0; + ET->ymax = SMALL_COORDINATE; + ET->ymin = LARGE_COORDINATE; + pSLLBlock->next = 0; + + PrevPt = &pts[count - 1]; + + /* + * for each vertex in the array of points. + * In this loop we are dealing with two vertices at + * a time -- these make up one edge of the polygon. + */ + while (count--) { + CurrPt = pts++; + + /* + * find out which point is above and which is below. + */ + if (PrevPt->y() > CurrPt->y()) { + bottom = PrevPt; + top = CurrPt; + pETEs->ClockWise = 0; + } else { + bottom = CurrPt; + top = PrevPt; + pETEs->ClockWise = 1; + } + + /* + * don't add horizontal edges to the Edge table. + */ + if (bottom->y() != top->y()) { + pETEs->ymax = bottom->y() - 1; /* -1 so we don't get last scanline */ + + /* + * initialize integer edge algorithm + */ + dy = bottom->y() - top->y(); + BRESINITPGONSTRUCT(dy, top->x(), bottom->x(), pETEs->bres) + + InsertEdgeInET(ET, pETEs, top->y(), &pSLLBlock, &iSLLBlock); + + if (PrevPt->y() > ET->ymax) + ET->ymax = PrevPt->y(); + if (PrevPt->y() < ET->ymin) + ET->ymin = PrevPt->y(); + ++pETEs; + } + + PrevPt = CurrPt; + } +} + +/* + * loadAET + * + * This routine moves EdgeTableEntries from the + * EdgeTable into the Active Edge Table, + * leaving them sorted by smaller x coordinate. + * + */ + +static void loadAET(register EdgeTableEntry *AET, register EdgeTableEntry *ETEs) +{ + register EdgeTableEntry *pPrevAET; + register EdgeTableEntry *tmp; + + pPrevAET = AET; + AET = AET->next; + while (ETEs) { + while (AET && AET->bres.minor_axis < ETEs->bres.minor_axis) { + pPrevAET = AET; + AET = AET->next; + } + tmp = ETEs->next; + ETEs->next = AET; + if (AET) + AET->back = ETEs; + ETEs->back = pPrevAET; + pPrevAET->next = ETEs; + pPrevAET = ETEs; + + ETEs = tmp; + } +} + +/* + * computeWAET + * + * This routine links the AET by the + * nextWETE (winding EdgeTableEntry) link for + * use by the winding number rule. The final + * Active Edge Table (AET) might look something + * like: + * + * AET + * ---------- --------- --------- + * |ymax | |ymax | |ymax | + * | ... | |... | |... | + * |next |->|next |->|next |->... + * |nextWETE| |nextWETE| |nextWETE| + * --------- --------- ^-------- + * | | | + * V-------------------> V---> ... + * + */ +static void computeWAET(register EdgeTableEntry *AET) +{ + register EdgeTableEntry *pWETE; + register int inside = 1; + register int isInside = 0; + + AET->nextWETE = 0; + pWETE = AET; + AET = AET->next; + while (AET) { + if (AET->ClockWise) + ++isInside; + else + --isInside; + + if (!inside && !isInside || inside && isInside) { + pWETE->nextWETE = AET; + pWETE = AET; + inside = !inside; + } + AET = AET->next; + } + pWETE->nextWETE = 0; +} + +/* + * InsertionSort + * + * Just a simple insertion sort using + * pointers and back pointers to sort the Active + * Edge Table. + * + */ + +static int InsertionSort(register EdgeTableEntry *AET) +{ + register EdgeTableEntry *pETEchase; + register EdgeTableEntry *pETEinsert; + register EdgeTableEntry *pETEchaseBackTMP; + register int changed = 0; + + AET = AET->next; + while (AET) { + pETEinsert = AET; + pETEchase = AET; + while (pETEchase->back->bres.minor_axis > AET->bres.minor_axis) + pETEchase = pETEchase->back; + + AET = AET->next; + if (pETEchase != pETEinsert) { + pETEchaseBackTMP = pETEchase->back; + pETEinsert->back->next = AET; + if (AET) + AET->back = pETEinsert->back; + pETEinsert->next = pETEchase; + pETEchase->back->next = pETEinsert; + pETEchase->back = pETEinsert; + pETEinsert->back = pETEchaseBackTMP; + changed = 1; + } + } + return changed; +} + +/* + * Clean up our act. + */ +static void FreeStorage(register ScanLineListBlock *pSLLBlock) +{ + register ScanLineListBlock *tmpSLLBlock; + + while (pSLLBlock) { + tmpSLLBlock = pSLLBlock->next; + free(pSLLBlock); + pSLLBlock = tmpSLLBlock; + } +} + +/* + * Create an array of rectangles from a list of points. + * If indeed these things (POINTS, RECTS) are the same, + * then this proc is still needed, because it allocates + * storage for the array, which was allocated on the + * stack by the calling procedure. + * + */ +static void PtsToRegion(register int numFullPtBlocks, register int iCurPtBlock, + POINTBLOCK *FirstPtBlock, QRegionPrivate *reg) +{ + register QRect *rects; + register QPoint *pts; + register POINTBLOCK *CurPtBlock; + register int i; + register QRect *extents; + register int numRects; + + extents = ®->extents; + numRects = ((numFullPtBlocks * NUMPTSTOBUFFER) + iCurPtBlock) >> 1; + + reg->rects.resize(numRects); + + CurPtBlock = FirstPtBlock; + rects = reg->rects.data() - 1; + numRects = 0; + extents->setLeft(INT_MAX); + extents->setRight(INT_MIN); + reg->innerArea = -1; + + for (; numFullPtBlocks >= 0; --numFullPtBlocks) { + /* the loop uses 2 points per iteration */ + i = NUMPTSTOBUFFER >> 1; + if (!numFullPtBlocks) + i = iCurPtBlock >> 1; + if(i) { + for (pts = CurPtBlock->pts; i--; pts += 2) { + if (pts->x() == pts[1].x()) + continue; + if (numRects && pts->x() == rects->left() && pts->y() == rects->bottom() + 1 + && pts[1].x() == rects->right()+1 && (numRects == 1 || rects[-1].top() != rects->top()) + && (i && pts[2].y() > pts[1].y())) { + rects->setBottom(pts[1].y()); + reg->updateInnerRect(*rects); + continue; + } + ++numRects; + ++rects; + rects->setCoords(pts->x(), pts->y(), pts[1].x() - 1, pts[1].y()); + if (rects->left() < extents->left()) + extents->setLeft(rects->left()); + if (rects->right() > extents->right()) + extents->setRight(rects->right()); + reg->updateInnerRect(*rects); + } + } + CurPtBlock = CurPtBlock->next; + } + + if (numRects) { + extents->setTop(reg->rects[0].top()); + extents->setBottom(rects->bottom()); + } else { + extents->setCoords(0, 0, 0, 0); + } + reg->numRects = numRects; +} + +/* + * polytoregion + * + * Scan converts a polygon by returning a run-length + * encoding of the resultant bitmap -- the run-length + * encoding is in the form of an array of rectangles. + */ +static QRegionPrivate *PolygonRegion(const QPoint *Pts, int Count, int rule, + QRegionPrivate *region) + //Point *Pts; /* the pts */ + //int Count; /* number of pts */ + //int rule; /* winding rule */ +{ + register EdgeTableEntry *pAET; /* Active Edge Table */ + register int y; /* current scanline */ + register int iPts = 0; /* number of pts in buffer */ + register EdgeTableEntry *pWETE; /* Winding Edge Table Entry*/ + register ScanLineList *pSLL; /* current scanLineList */ + register QPoint *pts; /* output buffer */ + EdgeTableEntry *pPrevAET; /* ptr to previous AET */ + EdgeTable ET; /* header node for ET */ + EdgeTableEntry AET; /* header node for AET */ + EdgeTableEntry *pETEs; /* EdgeTableEntries pool */ + ScanLineListBlock SLLBlock; /* header for scanlinelist */ + int fixWAET = false; + POINTBLOCK FirstPtBlock, *curPtBlock; /* PtBlock buffers */ + POINTBLOCK *tmpPtBlock; + int numFullPtBlocks = 0; + + region->vector(); + + /* special case a rectangle */ + if (((Count == 4) || + ((Count == 5) && (Pts[4].x() == Pts[0].x()) && (Pts[4].y() == Pts[0].y()))) + && (((Pts[0].y() == Pts[1].y()) && (Pts[1].x() == Pts[2].x()) && (Pts[2].y() == Pts[3].y()) + && (Pts[3].x() == Pts[0].x())) || ((Pts[0].x() == Pts[1].x()) + && (Pts[1].y() == Pts[2].y()) && (Pts[2].x() == Pts[3].x()) + && (Pts[3].y() == Pts[0].y())))) { + int x = qMin(Pts[0].x(), Pts[2].x()); + region->extents.setLeft(x); + int y = qMin(Pts[0].y(), Pts[2].y()); + region->extents.setTop(y); + region->extents.setWidth(qMax(Pts[0].x(), Pts[2].x()) - x); + region->extents.setHeight(qMax(Pts[0].y(), Pts[2].y()) - y); + if ((region->extents.left() <= region->extents.right()) && + (region->extents.top() <= region->extents.bottom())) { + region->numRects = 1; + region->rects.resize(1); + region->rects[0] = region->extents; + region->innerRect = region->extents; + region->innerArea = region->innerRect.width() * region->innerRect.height(); + } + return region; + } + + if (!(pETEs = static_cast<EdgeTableEntry *>(malloc(sizeof(EdgeTableEntry) * Count)))) + return 0; + + pts = FirstPtBlock.pts; + CreateETandAET(Count, Pts, &ET, &AET, pETEs, &SLLBlock); + pSLL = ET.scanlines.next; + curPtBlock = &FirstPtBlock; + + if (rule == EvenOddRule) { + /* + * for each scanline + */ + for (y = ET.ymin; y < ET.ymax; ++y) { + /* + * Add a new edge to the active edge table when we + * get to the next edge. + */ + if (pSLL && y == pSLL->scanline) { + loadAET(&AET, pSLL->edgelist); + pSLL = pSLL->next; + } + pPrevAET = &AET; + pAET = AET.next; + + /* + * for each active edge + */ + while (pAET) { + pts->setX(pAET->bres.minor_axis); + pts->setY(y); + ++pts; + ++iPts; + + /* + * send out the buffer + */ + if (iPts == NUMPTSTOBUFFER) { + tmpPtBlock = (POINTBLOCK *)malloc(sizeof(POINTBLOCK)); + curPtBlock->next = tmpPtBlock; + curPtBlock = tmpPtBlock; + pts = curPtBlock->pts; + ++numFullPtBlocks; + iPts = 0; + } + EVALUATEEDGEEVENODD(pAET, pPrevAET, y) + } + InsertionSort(&AET); + } + } else { + /* + * for each scanline + */ + for (y = ET.ymin; y < ET.ymax; ++y) { + /* + * Add a new edge to the active edge table when we + * get to the next edge. + */ + if (pSLL && y == pSLL->scanline) { + loadAET(&AET, pSLL->edgelist); + computeWAET(&AET); + pSLL = pSLL->next; + } + pPrevAET = &AET; + pAET = AET.next; + pWETE = pAET; + + /* + * for each active edge + */ + while (pAET) { + /* + * add to the buffer only those edges that + * are in the Winding active edge table. + */ + if (pWETE == pAET) { + pts->setX(pAET->bres.minor_axis); + pts->setY(y); + ++pts; + ++iPts; + + /* + * send out the buffer + */ + if (iPts == NUMPTSTOBUFFER) { + tmpPtBlock = static_cast<POINTBLOCK *>(malloc(sizeof(POINTBLOCK))); + curPtBlock->next = tmpPtBlock; + curPtBlock = tmpPtBlock; + pts = curPtBlock->pts; + ++numFullPtBlocks; + iPts = 0; + } + pWETE = pWETE->nextWETE; + } + EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET) + } + + /* + * recompute the winding active edge table if + * we just resorted or have exited an edge. + */ + if (InsertionSort(&AET) || fixWAET) { + computeWAET(&AET); + fixWAET = false; + } + } + } + FreeStorage(SLLBlock.next); + PtsToRegion(numFullPtBlocks, iPts, &FirstPtBlock, region); + for (curPtBlock = FirstPtBlock.next; --numFullPtBlocks >= 0;) { + tmpPtBlock = curPtBlock->next; + free(curPtBlock); + curPtBlock = tmpPtBlock; + } + free(pETEs); + return region; +} +// END OF PolyReg.c extract + +QRegionPrivate *qt_bitmapToRegion(const QBitmap& bitmap, QRegionPrivate *region) +{ + region->vector(); + + QImage image = bitmap.toImage(); + + QRect xr; + +#define AddSpan \ + { \ + xr.setCoords(prev1, y, x-1, y); \ + UnionRectWithRegion(&xr, region, *region); \ + } + + const uchar zero = 0; + bool little = image.format() == QImage::Format_MonoLSB; + + int x, + y; + for (y = 0; y < image.height(); ++y) { + uchar *line = image.scanLine(y); + int w = image.width(); + uchar all = zero; + int prev1 = -1; + for (x = 0; x < w;) { + uchar byte = line[x / 8]; + if (x > w - 8 || byte!=all) { + if (little) { + for (int b = 8; b > 0 && x < w; --b) { + if (!(byte & 0x01) == !all) { + // More of the same + } else { + // A change. + if (all!=zero) { + AddSpan + all = zero; + } else { + prev1 = x; + all = ~zero; + } + } + byte >>= 1; + ++x; + } + } else { + for (int b = 8; b > 0 && x < w; --b) { + if (!(byte & 0x80) == !all) { + // More of the same + } else { + // A change. + if (all != zero) { + AddSpan + all = zero; + } else { + prev1 = x; + all = ~zero; + } + } + byte <<= 1; + ++x; + } + } + } else { + x += 8; + } + } + if (all != zero) { + AddSpan + } + } +#undef AddSpan + + return region; +} + +/* + Constructs an empty region. + + \sa isEmpty() +*/ + +QRegion::QRegion() + : d(&shared_empty) +{ + d->ref.ref(); +} + +/* + \overload + + Create a region based on the rectange \a r with region type \a t. + + If the rectangle is invalid a null region will be created. + + \sa QRegion::RegionType +*/ + +QRegion::QRegion(const QRect &r, RegionType t) +{ + if (r.isEmpty()) { + d = &shared_empty; + d->ref.ref(); + } else { +// d = new QRegionData; + QRegionPrivate *rp = 0; + if (t == Rectangle) { +// rp = new QRegionPrivate(r); + rp = qt_allocRegion(r); + } else if (t == Ellipse) { + QPainterPath path; + path.addEllipse(r.x(), r.y(), r.width(), r.height()); + QPolygon a = path.toSubpathPolygons().at(0).toPolygon(); + rp = qt_allocRegion(); +// rp = new QRegionPrivate; + PolygonRegion(a.constData(), a.size(), EvenOddRule, rp); + } + d = rp; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_MAC) + d->rgn = 0; +#endif + d->qt_rgn = rp; + } +} + +/* + Constructs a polygon region from the point array \a a with the fill rule + specified by \a fillRule. + + If \a fillRule is \l{Qt::WindingFill}, the polygon region is defined + using the winding algorithm; if it is \l{Qt::OddEvenFill}, the odd-even fill + algorithm is used. + + \warning This constructor can be used to create complex regions that will + slow down painting when used. +*/ + +QRegion::QRegion(const QPolygon &a, Qt::FillRule fillRule) +{ + if (a.count() > 2) { + //d = new QRegionData; + // QRegionPrivate *rp = new QRegionPrivate; + QRegionPrivate *rp = qt_allocRegion(); + PolygonRegion(a.constData(), a.size(), + fillRule == Qt::WindingFill ? WindingRule : EvenOddRule, rp); + d = rp; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_MAC) + d->rgn = 0; +#endif + d->qt_rgn = rp; + } else { + d = &shared_empty; + d->ref.ref(); + } +} + + +/* + Constructs a new region which is equal to region \a r. +*/ + +QRegion::QRegion(const QRegion &r) +{ + d = r.d; + d->ref.ref(); +} + + +/* + Constructs a region from the bitmap \a bm. + + The resulting region consists of the pixels in bitmap \a bm that + are Qt::color1, as if each pixel was a 1 by 1 rectangle. + + This constructor may create complex regions that will slow down + painting when used. Note that drawing masked pixmaps can be done + much faster using QPixmap::setMask(). +*/ +QRegion::QRegion(const QBitmap &bm) +{ + if (bm.isNull()) { + d = &shared_empty; + d->ref.ref(); + } else { + // d = new QRegionData; +// QRegionPrivate *rp = new QRegionPrivate; + QRegionPrivate *rp = qt_allocRegion(); + + qt_bitmapToRegion(bm, rp); + d = rp; + d->ref = 1; +#if defined(Q_WS_X11) + d->rgn = 0; + d->xrectangles = 0; +#elif defined(Q_WS_MAC) + d->rgn = 0; +#endif + d->qt_rgn = rp; + } +} + +void QRegion::cleanUp(QRegion::QRegionData *x) +{ + // delete x->qt_rgn; +#if defined(Q_WS_X11) + if (x->rgn) + XDestroyRegion(x->rgn); + if (x->xrectangles) + free(x->xrectangles); +#elif defined(Q_WS_MAC) + if (x->rgn) + qt_mac_dispose_rgn(x->rgn); +#endif + if(x->qt_rgn) { +// delete x->qt_rgn; + qt_freeRegion(x->qt_rgn); + } else { + delete x; + } +} + +/* + Destroys the region. +*/ + +QRegion::~QRegion() +{ + if (!d->ref.deref()) + cleanUp(d); +} + + +/* + Assigns \a r to this region and returns a reference to the region. +*/ + +QRegion &QRegion::operator=(const QRegion &r) +{ + r.d->ref.ref(); + if (!d->ref.deref()) + cleanUp(d); + d = r.d; + return *this; +} + + +/* + \internal +*/ + +QRegion QRegion::copy() const +{ + QRegion r; + QRegionData *x = 0; // new QRegionData; + QRegionPrivate *rp = 0; + if (d->qt_rgn) +// rp = new QRegionPrivate(*d->qt_rgn); + rp = qt_allocRegion(*d->qt_rgn); + else + rp = qt_allocRegion(); + x = rp; + x->qt_rgn = rp; + x->ref = 1; +#if defined(Q_WS_X11) + x->rgn = 0; + x->xrectangles = 0; +#elif defined(Q_WS_MAC) + x->rgn = 0; +#endif + + if (!r.d->ref.deref()) + cleanUp(r.d); + r.d = x; + return r; +} + +/* + Returns true if the region is empty; otherwise returns false. An + empty region is a region that contains no points. + + Example: + \snippet doc/src/snippets/code/src.gui.painting.qregion_qws.cpp 0 +*/ + +bool QRegion::isEmpty() const +{ + return d == &shared_empty || d->qt_rgn->numRects == 0; +} + + +/* + Returns true if the region contains the point \a p; otherwise + returns false. +*/ + +bool QRegion::contains(const QPoint &p) const +{ + return PointInRegion(d->qt_rgn, p.x(), p.y()); +} + +/* + \overload + + Returns true if the region overlaps the rectangle \a r; otherwise + returns false. +*/ + +bool QRegion::contains(const QRect &r) const +{ + if(!d->qt_rgn) + return false; + if(d->qt_rgn->mode == QRegionPrivate::Single) + return d->qt_rgn->single.contains(r); + + return RectInRegion(d->qt_rgn, r.left(), r.top(), r.width(), r.height()) != RectangleOut; +} + + + +/* + Translates (moves) the region \a dx along the X axis and \a dy + along the Y axis. +*/ + +void QRegion::translate(int dx, int dy) +{ + if ((dx == 0 && dy == 0) || isEmptyHelper(d->qt_rgn)) + return; + + detach(); + OffsetRegion(*d->qt_rgn, dx, dy); +#if defined(Q_WS_X11) + if (d->xrectangles) { + free(d->xrectangles); + d->xrectangles = 0; + } +#elif defined(Q_WS_MAC) + if(d->rgn) { + qt_mac_dispose_rgn(d->rgn); + d->rgn = 0; + } +#endif +} + +/* + \fn QRegion QRegion::unite(const QRegion &r) const + \obsolete + + Use united(\a r) instead. +*/ + +/* + \fn QRegion QRegion::united(const QRegion &r) const + \since 4.2 + + Returns a region which is the union of this region and \a r. + + \img runion.png Region Union + + The figure shows the union of two elliptical regions. + + \sa intersected(), subtracted(), xored() +*/ + +QRegion QRegion::unite(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn)) + return r; + if (isEmptyHelper(r.d->qt_rgn)) + return *this; + + if (d->qt_rgn->contains(*r.d->qt_rgn)) { + return *this; + } else if (r.d->qt_rgn->contains(*d->qt_rgn)) { + return r; + } else if (d->qt_rgn->canAppend(r.d->qt_rgn)) { + QRegion result(*this); + result.detach(); + result.d->qt_rgn->append(r.d->qt_rgn); + return result; + } else if (r.d->qt_rgn->canAppend(d->qt_rgn)) { + QRegion result(r); + result.detach(); + result.d->qt_rgn->append(d->qt_rgn); + return result; + } else if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return *this; + } else { + QRegion result; + result.detach(); + UnionRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); + return result; + } +} + +QRegion& QRegion::operator+=(const QRegion &r) +{ + if (isEmptyHelper(d->qt_rgn)) + return *this = r; + if (isEmptyHelper(r.d->qt_rgn)) + return *this; + + if (d->qt_rgn->contains(*r.d->qt_rgn)) { + return *this; + } else if (r.d->qt_rgn->contains(*d->qt_rgn)) { + return *this = r; + } else if (d->qt_rgn->canAppend(r.d->qt_rgn)) { + detach(); + d->qt_rgn->append(r.d->qt_rgn); + return *this; + } else if (d->qt_rgn->canPrepend(r.d->qt_rgn)) { + detach(); + d->qt_rgn->prepend(r.d->qt_rgn); + return *this; + } else if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return *this; + } + + return *this = unite(r); +} + +/* + \fn QRegion QRegion::intersect(const QRegion &r) const + \obsolete + + Use intersected(\a r) instead. +*/ + +/* + \fn QRegion QRegion::intersected(const QRegion &r) const + \since 4.2 + + Returns a region which is the intersection of this region and \a r. + + \img rintersect.png Region Intersection + + The figure shows the intersection of two elliptical regions. +*/ + +QRegion QRegion::intersect(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn) || isEmptyHelper(r.d->qt_rgn) + || !EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) + return QRegion(); + + /* this is fully contained in r */ + if (r.d->qt_rgn->contains(*d->qt_rgn)) + return *this; + + /* r is fully contained in this */ + if (d->qt_rgn->contains(*r.d->qt_rgn)) + return r; + + if(r.d->qt_rgn->mode == QRegionPrivate::Single && + d->qt_rgn->mode == QRegionPrivate::Single) + return QRegion(r.d->qt_rgn->single.intersected(d->qt_rgn->single)); +#ifdef QT_GREENPHONE_OPT + else if(r.d->qt_rgn->mode == QRegionPrivate::Single) + return intersect(r.d->qt_rgn->single); + else if(d->qt_rgn->mode == QRegionPrivate::Single) + return r.intersect(d->qt_rgn->single); +#endif + + QRegion result; + result.detach(); + miRegionOp(*result.d->qt_rgn, d->qt_rgn, r.d->qt_rgn, miIntersectO, 0, 0); + + /* + * Can't alter dest's extents before we call miRegionOp because + * it might be one of the source regions and miRegionOp depends + * on the extents of those regions being the same. Besides, this + * way there's no checking against rectangles that will be nuked + * due to coalescing, so we have to examine fewer rectangles. + */ + miSetExtents(*result.d->qt_rgn); + return result; +} + +#ifdef QT_GREENPHONE_OPT +/* + \overload + */ +QRegion QRegion::intersect(const QRect &r) const +{ + // No intersection + if(r.isEmpty() || isEmpty() || !EXTENTCHECK(&r, &d->qt_rgn->extents)) + return QRegion(); + + // This is fully contained in r + if(CONTAINSCHECK(r, d->qt_rgn->extents)) + return *this; + + // r is fully contained in this + if(CONTAINSCHECK(d->qt_rgn->innerRect, r)) + return QRegion(r); + + if(d->qt_rgn->mode == QRegionPrivate::Single) { + return QRegion(d->qt_rgn->single & r); + } else { + QRegion rv(*this); + rv.detach(); + + rv.d->qt_rgn->extents &= r; + rv.d->qt_rgn->innerRect &= r; + rv.d->qt_rgn->innerArea = rv.d->qt_rgn->innerRect.height() * + rv.d->qt_rgn->innerRect.width(); + + int numRects = 0; + for(int ii = 0; ii < rv.d->qt_rgn->numRects; ++ii) { + QRect result = rv.d->qt_rgn->rects[ii] & r; + if(!result.isEmpty()) + rv.d->qt_rgn->rects[numRects++] = result; + } + rv.d->qt_rgn->numRects = numRects; + return rv; + } +} + +/* + \overload + */ +const QRegion QRegion::operator&(const QRect &r) const +{ + return intersect(r); +} + +/* + \overload + */ +QRegion& QRegion::operator&=(const QRect &r) +{ + if(isEmpty() || CONTAINSCHECK(r, d->qt_rgn->extents)) { + // Do nothing + } else if(r.isEmpty() || !EXTENTCHECK(&r, &d->qt_rgn->extents)) { + *this = QRegion(); + } else if(CONTAINSCHECK(d->qt_rgn->innerRect, r)) { + *this = QRegion(r); + } else { + detach(); + if(d->qt_rgn->mode == QRegionPrivate::Single) { + QRect result = d->qt_rgn->single & r; + d->qt_rgn->single = result; + d->qt_rgn->extents = result; + d->qt_rgn->innerRect = result; + d->qt_rgn->innerArea = result.height() * result.width(); + } else { + d->qt_rgn->extents &= r; + d->qt_rgn->innerRect &= r; + d->qt_rgn->innerArea = d->qt_rgn->innerRect.height() * + d->qt_rgn->innerRect.width(); + + int numRects = 0; + for(int ii = 0; ii < d->qt_rgn->numRects; ++ii) { + QRect result = d->qt_rgn->rects[ii] & r; + if(!result.isEmpty()) + d->qt_rgn->rects[numRects++] = result; + } + d->qt_rgn->numRects = numRects; + } + } + return *this; +} +#endif + +/* + \fn QRegion QRegion::subtract(const QRegion &r) const + \obsolete + + Use subtracted(\a r) instead. +*/ + +/* + \fn QRegion QRegion::subtracted(const QRegion &r) const + \since 4.2 + + Returns a region which is \a r subtracted from this region. + + \img rsubtract.png Region Subtraction + + The figure shows the result when the ellipse on the right is + subtracted from the ellipse on the left (\c {left - right}). + + \sa intersected(), united(), xored() +*/ + +QRegion QRegion::subtract(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn) || isEmptyHelper(r.d->qt_rgn)) + return *this; + if (r.d->qt_rgn->contains(*d->qt_rgn)) + return QRegion(); + if (!EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) + return *this; + if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) + return QRegion(); + + QRegion result; + result.detach(); + SubtractRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); + return result; +} + +/* + \fn QRegion QRegion::eor(const QRegion &r) const + \obsolete + + Use xored(\a r) instead. +*/ + +/* + \fn QRegion QRegion::xored(const QRegion &r) const + \since 4.2 + + Returns a region which is the exclusive or (XOR) of this region + and \a r. + + \img rxor.png Region XORed + + The figure shows the exclusive or of two elliptical regions. + + \sa intersected(), united(), subtracted() +*/ + +QRegion QRegion::eor(const QRegion &r) const +{ + if (isEmptyHelper(d->qt_rgn)) { + return r; + } else if (isEmptyHelper(r.d->qt_rgn)) { + return *this; + } else if (!EXTENTCHECK(&d->qt_rgn->extents, &r.d->qt_rgn->extents)) { + return (*this + r); + } else if (EqualRegion(d->qt_rgn, r.d->qt_rgn)) { + return QRegion(); + } else { + QRegion result; + result.detach(); + XorRegion(d->qt_rgn, r.d->qt_rgn, *result.d->qt_rgn); + return result; + } +} + +/* + Returns the bounding rectangle of this region. An empty region + gives a rectangle that is QRect::isNull(). +*/ + +QRect QRegion::boundingRect() const +{ + if (isEmpty()) + return QRect(); + return d->qt_rgn->extents; +} + +/* \internal + Returns true if \a rect is guaranteed to be fully contained in \a region. + A false return value does not guarantee the opposite. +*/ +bool qt_region_strictContains(const QRegion ®ion, const QRect &rect) +{ + if (isEmptyHelper(region.d->qt_rgn) || !rect.isValid()) + return false; + +#if 0 // TEST_INNERRECT + static bool guard = false; + if (guard) + return QRect(); + guard = true; + QRegion inner = region.d->qt_rgn->innerRect; + Q_ASSERT((inner - region).isEmpty()); + guard = false; + + int maxArea = 0; + for (int i = 0; i < region.d->qt_rgn->numRects; ++i) { + const QRect r = region.d->qt_rgn->rects.at(i); + if (r.width() * r.height() > maxArea) + maxArea = r.width() * r.height(); + } + + if (maxArea > region.d->qt_rgn->innerArea) { + qDebug() << "not largest rectangle" << region << region.d->qt_rgn->innerRect; + } + Q_ASSERT(maxArea <= region.d->qt_rgn->innerArea); +#endif + + const QRect r1 = region.d->qt_rgn->innerRect; + return (rect.left() >= r1.left() && rect.right() <= r1.right() + && rect.top() >= r1.top() && rect.bottom() <= r1.bottom()); +} + +/* + Returns an array of non-overlapping rectangles that make up the + region. + + The union of all the rectangles is equal to the original region. +*/ +QVector<QRect> QRegion::rects() const +{ + if (d->qt_rgn) { + d->qt_rgn->vector(); + d->qt_rgn->rects.resize(d->qt_rgn->numRects); + return d->qt_rgn->rects; + } else { + return QVector<QRect>(); + } +} + +/* + \fn void QRegion::setRects(const QRect *rects, int number) + + Sets the region using the array of rectangles specified by \a rects and + \a number. + The rectangles \e must be optimally Y-X sorted and follow these restrictions: + + \list + \o The rectangles must not intersect. + \o All rectangles with a given top coordinate must have the same height. + \o No two rectangles may abut horizontally (they should be combined + into a single wider rectangle in that case). + \o The rectangles must be sorted in ascending order, with Y as the major + sort key and X as the minor sort key. + \endlist + \omit + Only some platforms have these restrictions (Qt for Embedded Linux, X11 and Mac OS X). + \endomit +*/ +void QRegion::setRects(const QRect *rects, int num) +{ + *this = QRegion(); + if (!rects || num == 0 || (num == 1 && rects->isEmpty())) + return; + + detach(); + + if(num == 1) { + d->qt_rgn->single = *rects; + d->qt_rgn->mode = QRegionPrivate::Single; + d->qt_rgn->numRects = num; + d->qt_rgn->extents = *rects; + d->qt_rgn->innerRect = *rects; + } else { + d->qt_rgn->mode = QRegionPrivate::Vector; + d->qt_rgn->rects.resize(num); + d->qt_rgn->numRects = num; + int left = INT_MAX, + right = INT_MIN, + top = INT_MAX, + bottom = INT_MIN; + for (int i = 0; i < num; ++i) { + const QRect &rect = rects[i]; + d->qt_rgn->rects[i] = rect; + left = qMin(rect.left(), left); + right = qMax(rect.right(), right); + top = qMin(rect.top(), top); + bottom = qMax(rect.bottom(), bottom); + d->qt_rgn->updateInnerRect(rect); + } + d->qt_rgn->extents = QRect(QPoint(left, top), QPoint(right, bottom)); + } +} + +/* + Returns true if the region is equal to \a r; otherwise returns + false. +*/ + +bool QRegion::operator==(const QRegion &r) const +{ + if (!d->qt_rgn || !r.d->qt_rgn) + return r.d->qt_rgn == d->qt_rgn; + + if (d == r.d) + return true; + else + return EqualRegion(d->qt_rgn, r.d->qt_rgn); +} + +#ifdef QT_GREENPHONE_OPT +bool QRegion::isRect() const +{ + return d->qt_rgn && d->qt_rgn->mode == QRegionPrivate::Single; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/painting/qregion_s60.cpp b/src/gui/painting/qregion_s60.cpp new file mode 100644 index 0000000000..eafff1b965 --- /dev/null +++ b/src/gui/painting/qregion_s60.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbitmap.h" +#include "qbuffer.h" +#include "qimage.h" +#include "qpolygon.h" +#include "qregion.h" + +QT_BEGIN_NAMESPACE + +QRegion::QRegionData QRegion::shared_empty = { Q_BASIC_ATOMIC_INITIALIZER(1), 0 }; + +QT_END_NAMESPACE diff --git a/src/gui/painting/qregion_win.cpp b/src/gui/painting/qregion_win.cpp new file mode 100644 index 0000000000..3466b62cbd --- /dev/null +++ b/src/gui/painting/qregion_win.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qatomic.h" +#include "qbitmap.h" +#include "qbuffer.h" +#include "qimage.h" +#include "qpolygon.h" +#include "qregion.h" +#include "qt_windows.h" +#include "qpainterpath.h" + +QT_BEGIN_NAMESPACE + +QRegion::QRegionData QRegion::shared_empty = { Q_BASIC_ATOMIC_INITIALIZER(1), 0, 0 }; + +HRGN qt_tryCreateRegion(QRegion::RegionType type, int left, int top, int right, int bottom) +{ + const int tries = 10; + for (int i = 0; i < tries; ++i) { + HRGN region = 0; + switch (type) { + case QRegion::Rectangle: + region = CreateRectRgn(left, top, right, bottom); + break; + case QRegion::Ellipse: +#ifndef Q_OS_WINCE + region = CreateEllipticRgn(left, top, right, bottom); +#endif + break; + } + if (region) { + if (GetRegionData(region, 0, 0)) + return region; + else + DeleteObject(region); + } + } + return 0; +} + +QRegion qt_region_from_HRGN(HRGN rgn) +{ + int numBytes = GetRegionData(rgn, 0, 0); + if (numBytes == 0) + return QRegion(); + + char *buf = new char[numBytes]; + if (buf == 0) + return QRegion(); + + RGNDATA *rd = reinterpret_cast<RGNDATA*>(buf); + if (GetRegionData(rgn, numBytes, rd) == 0) { + delete [] buf; + return QRegion(); + } + + QRegion region; + RECT *r = reinterpret_cast<RECT*>(rd->Buffer); + for (uint i = 0; i < rd->rdh.nCount; ++i) { + QRect rect; + rect.setCoords(r->left, r->top, r->right - 1, r->bottom - 1); + ++r; + region |= rect; + } + + delete [] buf; + + return region; +} + +void qt_win_dispose_rgn(HRGN r) +{ + if (r) + DeleteObject(r); +} + +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); + } +} + +void QRegion::ensureHandle() const +{ + if (d->rgn) + DeleteObject(d->rgn); + d->rgn = CreateRectRgn(0,0,0,0); + if (d->qt_rgn) { + if (d->qt_rgn->numRects == 1) { + QRect r = d->qt_rgn->extents; + qt_add_rect(d->rgn, r); + return; + } + for (int i = 0;i < d->qt_rgn->numRects;i++) { + QRect r = d->qt_rgn->rects.at(i); + qt_add_rect(d->rgn, r); + } + } +} + + +QT_END_NAMESPACE diff --git a/src/gui/painting/qregion_x11.cpp b/src/gui/painting/qregion_x11.cpp new file mode 100644 index 0000000000..ef4e844bfa --- /dev/null +++ b/src/gui/painting/qregion_x11.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qt_x11_p.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +QRegion::QRegionData QRegion::shared_empty = {Q_BASIC_ATOMIC_INITIALIZER(1), 0, 0, 0}; + +void QRegion::updateX11Region() const +{ + d->rgn = XCreateRegion(); + if (!d->qt_rgn) + return; + + int n = d->qt_rgn->numRects; + const QRect *rect = (n == 1 ? &d->qt_rgn->extents : d->qt_rgn->rects.constData()); + while (n--) { + XRectangle r; + r.x = qMax(SHRT_MIN, rect->x()); + r.y = qMax(SHRT_MIN, rect->y()); + r.width = qMin((int)USHRT_MAX, rect->width()); + r.height = qMin((int)USHRT_MAX, rect->height()); + XUnionRectWithRegion(&r, d->rgn, d->rgn); + ++rect; + } +} + +void *QRegion::clipRectangles(int &num) const +{ + if (!d->xrectangles && !(d == &shared_empty || d->qt_rgn->numRects == 0)) { + XRectangle *r = static_cast<XRectangle*>(malloc(d->qt_rgn->numRects * sizeof(XRectangle))); + d->xrectangles = r; + int n = d->qt_rgn->numRects; + const QRect *rect = (n == 1 ? &d->qt_rgn->extents : d->qt_rgn->rects.constData()); + while (n--) { + r->x = qMax(SHRT_MIN, rect->x()); + r->y = qMax(SHRT_MIN, rect->y()); + r->width = qMin((int)USHRT_MAX, rect->width()); + r->height = qMin((int)USHRT_MAX, rect->height()); + ++r; + ++rect; + } + } + if (d == &shared_empty || d->qt_rgn->numRects == 0) + num = 0; + else + num = d->qt_rgn->numRects; + return d->xrectangles; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qrgb.h b/src/gui/painting/qrgb.h new file mode 100644 index 0000000000..df6a920e80 --- /dev/null +++ b/src/gui/painting/qrgb.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRGB_H +#define QRGB_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +typedef unsigned int QRgb; // RGB triplet + +const QRgb RGB_MASK = 0x00ffffff; // masks RGB values + +Q_GUI_EXPORT_INLINE int qRed(QRgb rgb) // get red part of RGB +{ return ((rgb >> 16) & 0xff); } + +Q_GUI_EXPORT_INLINE int qGreen(QRgb rgb) // get green part of RGB +{ return ((rgb >> 8) & 0xff); } + +Q_GUI_EXPORT_INLINE int qBlue(QRgb rgb) // get blue part of RGB +{ return (rgb & 0xff); } + +Q_GUI_EXPORT_INLINE int qAlpha(QRgb rgb) // get alpha part of RGBA +{ return rgb >> 24; } + +Q_GUI_EXPORT_INLINE QRgb qRgb(int r, int g, int b)// set RGB value +{ return (0xffu << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } + +Q_GUI_EXPORT_INLINE QRgb qRgba(int r, int g, int b, int a)// set RGBA value +{ return ((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); } + +Q_GUI_EXPORT_INLINE int qGray(int r, int g, int b)// convert R,G,B to gray 0..255 +{ return (r*11+g*16+b*5)/32; } + +Q_GUI_EXPORT_INLINE int qGray(QRgb rgb) // convert RGB to gray 0..255 +{ return qGray(qRed(rgb), qGreen(rgb), qBlue(rgb)); } + +Q_GUI_EXPORT_INLINE bool qIsGray(QRgb rgb) +{ return qRed(rgb) == qGreen(rgb) && qRed(rgb) == qBlue(rgb); } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QRGB_H diff --git a/src/gui/painting/qstroker.cpp b/src/gui/painting/qstroker.cpp new file mode 100644 index 0000000000..fca46b45f7 --- /dev/null +++ b/src/gui/painting/qstroker.cpp @@ -0,0 +1,1263 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qstroker_p.h" +#include "private/qbezier_p.h" +#include "private/qmath_p.h" +#include "qline.h" +#include "qtransform.h" +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +// #define QPP_STROKE_DEBUG + +class QSubpathForwardIterator +{ +public: + QSubpathForwardIterator(const QDataBuffer<QStrokerOps::Element> *path) + : m_path(path), m_pos(0) { } + inline int position() const { return m_pos; } + inline bool hasNext() const { return m_pos < m_path->size(); } + inline QStrokerOps::Element next() { Q_ASSERT(hasNext()); return m_path->at(m_pos++); } + +private: + const QDataBuffer<QStrokerOps::Element> *m_path; + int m_pos; +}; + +class QSubpathBackwardIterator +{ +public: + QSubpathBackwardIterator(const QDataBuffer<QStrokerOps::Element> *path) + : m_path(path), m_pos(path->size() - 1) { } + + inline int position() const { return m_pos; } + + inline bool hasNext() const { return m_pos >= 0; } + + inline QStrokerOps::Element next() + { + Q_ASSERT(hasNext()); + + QStrokerOps::Element ce = m_path->at(m_pos); // current element + + if (m_pos == m_path->size() - 1) { + --m_pos; + ce.type = QPainterPath::MoveToElement; + return ce; + } + + const QStrokerOps::Element &pe = m_path->at(m_pos + 1); // previous element + + switch (pe.type) { + case QPainterPath::LineToElement: + ce.type = QPainterPath::LineToElement; + break; + case QPainterPath::CurveToDataElement: + // First control point? + if (ce.type == QPainterPath::CurveToElement) { + ce.type = QPainterPath::CurveToDataElement; + } else { // Second control point then + ce.type = QPainterPath::CurveToElement; + } + break; + case QPainterPath::CurveToElement: + ce.type = QPainterPath::CurveToDataElement; + break; + default: + qWarning("QSubpathReverseIterator::next: Case %d unhandled", ce.type); + break; + } + --m_pos; + + return ce; + } + +private: + const QDataBuffer<QStrokerOps::Element> *m_path; + int m_pos; +}; + +class QSubpathFlatIterator +{ +public: + QSubpathFlatIterator(const QDataBuffer<QStrokerOps::Element> *path, qreal threshold) + : m_path(path), m_pos(0), m_curve_index(-1), m_curve_threshold(threshold) { } + + inline bool hasNext() const { return m_curve_index >= 0 || m_pos < m_path->size(); } + + QStrokerOps::Element next() + { + Q_ASSERT(hasNext()); + + if (m_curve_index >= 0) { + QStrokerOps::Element e = { QPainterPath::LineToElement, + qt_real_to_fixed(m_curve.at(m_curve_index).x()), + qt_real_to_fixed(m_curve.at(m_curve_index).y()) + }; + ++m_curve_index; + if (m_curve_index >= m_curve.size()) + m_curve_index = -1; + return e; + } + + QStrokerOps::Element e = m_path->at(m_pos); + if (e.isCurveTo()) { + Q_ASSERT(m_pos > 0); + Q_ASSERT(m_pos < m_path->size()); + + m_curve = QBezier::fromPoints(QPointF(qt_fixed_to_real(m_path->at(m_pos-1).x), + qt_fixed_to_real(m_path->at(m_pos-1).y)), + QPointF(qt_fixed_to_real(e.x), + qt_fixed_to_real(e.y)), + QPointF(qt_fixed_to_real(m_path->at(m_pos+1).x), + qt_fixed_to_real(m_path->at(m_pos+1).y)), + QPointF(qt_fixed_to_real(m_path->at(m_pos+2).x), + qt_fixed_to_real(m_path->at(m_pos+2).y))).toPolygon(m_curve_threshold); + m_curve_index = 1; + e.type = QPainterPath::LineToElement; + e.x = m_curve.at(0).x(); + e.y = m_curve.at(0).y(); + m_pos += 2; + } + Q_ASSERT(e.isLineTo() || e.isMoveTo()); + ++m_pos; + return e; + } + +private: + const QDataBuffer<QStrokerOps::Element> *m_path; + int m_pos; + QPolygonF m_curve; + int m_curve_index; + qreal m_curve_threshold; +}; + +template <class Iterator> bool qt_stroke_side(Iterator *it, QStroker *stroker, + bool capFirst, QLineF *startTangent); + +/******************************************************************************* + * QLineF::angle gives us the smalles angle between two lines. Here we + * want to identify the line's angle direction on the unit circle. + */ +static inline qreal adapted_angle_on_x(const QLineF &line) +{ + qreal angle = line.angle(QLineF(0, 0, 1, 0)); + if (line.dy() > 0) + angle = 360 - angle; + return angle; +} + +QStrokerOps::QStrokerOps() + : m_elements(0) + , m_curveThreshold(qt_real_to_fixed(0.25)) + , m_dashThreshold(qt_real_to_fixed(0.25)) + , m_customData(0) + , m_moveTo(0) + , m_lineTo(0) + , m_cubicTo(0) +{ +} + +QStrokerOps::~QStrokerOps() +{ +} + +/*! + Prepares the stroker. Call this function once before starting a + stroke by calling moveTo, lineTo or cubicTo. + + The \a customData is passed back through that callback functions + and can be used by the user to for instance maintain state + information. +*/ +void QStrokerOps::begin(void *customData) +{ + m_customData = customData; + m_elements.reset(); +} + + +/*! + Finishes the stroke. Call this function once when an entire + primitive has been stroked. +*/ +void QStrokerOps::end() +{ + if (m_elements.size() > 1) + processCurrentSubpath(); + m_customData = 0; +} + +/*! + Convenience function that decomposes \a path into begin(), + moveTo(), lineTo(), curevTo() and end() calls. + + The \a customData parameter is used in the callback functions + + The \a matrix is used to transform the points before input to the + stroker. + + \sa begin() +*/ +void QStrokerOps::strokePath(const QPainterPath &path, void *customData, const QTransform &matrix) +{ + if (path.isEmpty()) + return; + + setCurveThresholdFromTransform(QTransform()); + begin(customData); + int count = path.elementCount(); + if (matrix.isIdentity()) { + for (int i=0; i<count; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + switch (e.type) { + case QPainterPath::MoveToElement: + moveTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y)); + break; + case QPainterPath::LineToElement: + lineTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y)); + break; + case QPainterPath::CurveToElement: + { + const QPainterPath::Element &cp2 = path.elementAt(++i); + const QPainterPath::Element &ep = path.elementAt(++i); + cubicTo(qt_real_to_fixed(e.x), qt_real_to_fixed(e.y), + qt_real_to_fixed(cp2.x), qt_real_to_fixed(cp2.y), + qt_real_to_fixed(ep.x), qt_real_to_fixed(ep.y)); + } + break; + default: + break; + } + } + } else { + for (int i=0; i<count; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + QPointF pt = QPointF(e.x, e.y) * matrix; + switch (e.type) { + case QPainterPath::MoveToElement: + moveTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y())); + break; + case QPainterPath::LineToElement: + lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y())); + break; + case QPainterPath::CurveToElement: + { + QPointF cp2 = ((QPointF) path.elementAt(++i)) * matrix; + QPointF ep = ((QPointF) path.elementAt(++i)) * matrix; + cubicTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y()), + qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()), + qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y())); + } + break; + default: + break; + } + } + } + end(); +} + +/*! + Convenience function for stroking a polygon of the \a pointCount + first points in \a points. If \a implicit_close is set to true a + line is implictly drawn between the first and last point in the + polygon. Typically true for polygons and false for polylines. + + The \a matrix is used to transform the points before they enter the + stroker. + + \sa begin() +*/ + +void QStrokerOps::strokePolygon(const QPointF *points, int pointCount, bool implicit_close, + void *data, const QTransform &matrix) +{ + if (!pointCount) + return; + + setCurveThresholdFromTransform(QTransform()); + begin(data); + if (matrix.isIdentity()) { + moveTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y())); + for (int i=1; i<pointCount; ++i) + lineTo(qt_real_to_fixed(points[i].x()), + qt_real_to_fixed(points[i].y())); + if (implicit_close) + lineTo(qt_real_to_fixed(points[0].x()), qt_real_to_fixed(points[0].y())); + } else { + QPointF start = points[0] * matrix; + moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y())); + for (int i=1; i<pointCount; ++i) { + QPointF pt = points[i] * matrix; + lineTo(qt_real_to_fixed(pt.x()), qt_real_to_fixed(pt.y())); + } + if (implicit_close) + lineTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y())); + } + end(); +} + +/*! + Convenience function for stroking an ellipse with bounding rect \a + rect. The \a matrix is used to transform the coordinates before + they enter the stroker. +*/ +void QStrokerOps::strokeEllipse(const QRectF &rect, void *data, const QTransform &matrix) +{ + int count = 0; + QPointF pts[12]; + QPointF start = qt_curves_for_arc(rect, 0, -360, pts, &count); + Q_ASSERT(count == 12); // a perfect circle.. + + if (!matrix.isIdentity()) { + start = start * matrix; + for (int i=0; i<12; ++i) { + pts[i] = pts[i] * matrix; + } + } + + setCurveThresholdFromTransform(QTransform()); + begin(data); + moveTo(qt_real_to_fixed(start.x()), qt_real_to_fixed(start.y())); + for (int i=0; i<12; i+=3) { + cubicTo(qt_real_to_fixed(pts[i].x()), qt_real_to_fixed(pts[i].y()), + qt_real_to_fixed(pts[i+1].x()), qt_real_to_fixed(pts[i+1].y()), + qt_real_to_fixed(pts[i+2].x()), qt_real_to_fixed(pts[i+2].y())); + } + end(); +} + + +QStroker::QStroker() + : m_capStyle(SquareJoin), m_joinStyle(FlatJoin), + m_back1X(0), m_back1Y(0), + m_back2X(0), m_back2Y(0) +{ + m_strokeWidth = qt_real_to_fixed(1); + m_miterLimit = qt_real_to_fixed(2); +} + +QStroker::~QStroker() +{ +} + +Qt::PenCapStyle QStroker::capForJoinMode(LineJoinMode mode) +{ + if (mode == FlatJoin) return Qt::FlatCap; + else if (mode == SquareJoin) return Qt::SquareCap; + else return Qt::RoundCap; +} + +QStroker::LineJoinMode QStroker::joinModeForCap(Qt::PenCapStyle style) +{ + if (style == Qt::FlatCap) return FlatJoin; + else if (style == Qt::SquareCap) return SquareJoin; + else return RoundCap; +} + +Qt::PenJoinStyle QStroker::joinForJoinMode(LineJoinMode mode) +{ + if (mode == FlatJoin) return Qt::BevelJoin; + else if (mode == MiterJoin) return Qt::MiterJoin; + else if (mode == SvgMiterJoin) return Qt::SvgMiterJoin; + else return Qt::RoundJoin; +} + +QStroker::LineJoinMode QStroker::joinModeForJoin(Qt::PenJoinStyle joinStyle) +{ + if (joinStyle == Qt::BevelJoin) return FlatJoin; + else if (joinStyle == Qt::MiterJoin) return MiterJoin; + else if (joinStyle == Qt::SvgMiterJoin) return SvgMiterJoin; + else return RoundJoin; +} + + +/*! + This function is called to stroke the currently built up + subpath. The subpath is cleared when the function completes. +*/ +void QStroker::processCurrentSubpath() +{ + Q_ASSERT(!m_elements.isEmpty()); + Q_ASSERT(m_elements.first().type == QPainterPath::MoveToElement); + Q_ASSERT(m_elements.size() > 1); + + QSubpathForwardIterator fwit(&m_elements); + QSubpathBackwardIterator bwit(&m_elements); + + QLineF fwStartTangent, bwStartTangent; + + bool fwclosed = qt_stroke_side(&fwit, this, false, &fwStartTangent); + bool bwclosed = qt_stroke_side(&bwit, this, !fwclosed, &bwStartTangent); + + if (!bwclosed) + joinPoints(m_elements.at(0).x, m_elements.at(0).y, fwStartTangent, m_capStyle); +} + + +/*! + \internal +*/ +void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine, LineJoinMode join) +{ +#ifdef QPP_STROKE_DEBUG + printf(" -----> joinPoints: around=(%.0f, %.0f), next_p1=(%.0f, %.f) next_p2=(%.0f, %.f)\n", + qt_fixed_to_real(focal_x), + qt_fixed_to_real(focal_y), + nextLine.x1(), nextLine.y1(), nextLine.x2(), nextLine.y2()); +#endif + // points connected already, don't join + +#if !defined (QFIXED_26_6) && !defined (Q_FIXED_32_32) + if (qFuzzyCompare(m_back1X, nextLine.x1()) && qFuzzyCompare(m_back1Y, nextLine.y1())) + return; +#else + if (m_back1X == qt_real_to_fixed(nextLine.x1()) + && m_back1Y == qt_real_to_fixed(nextLine.y1())) { + return; + } +#endif + + if (join == FlatJoin) { + QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y), + qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y)); + QPointF isect; + QLineF::IntersectType type = prevLine.intersect(nextLine, &isect); + QLineF shortCut(prevLine.p2(), nextLine.p1()); + qreal angle = shortCut.angleTo(prevLine); + if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) { + emitLineTo(focal_x, focal_y); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + return; + } + emitLineTo(qt_real_to_fixed(nextLine.x1()), + qt_real_to_fixed(nextLine.y1())); + + } else { + QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y), + qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y)); + + QPointF isect; + QLineF::IntersectType type = prevLine.intersect(nextLine, &isect); + + if (join == MiterJoin) { + qreal appliedMiterLimit = qt_fixed_to_real(m_strokeWidth * m_miterLimit); + + // If we are on the inside, do the short cut... + QLineF shortCut(prevLine.p2(), nextLine.p1()); + qreal angle = shortCut.angleTo(prevLine); + if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) { + emitLineTo(focal_x, focal_y); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + return; + } + QLineF miterLine(QPointF(qt_fixed_to_real(m_back1X), + qt_fixed_to_real(m_back1Y)), isect); + if (type == QLineF::NoIntersection || miterLine.length() > appliedMiterLimit) { + QLineF l1(prevLine); + l1.setLength(appliedMiterLimit); + l1.translate(prevLine.dx(), prevLine.dy()); + + QLineF l2(nextLine); + l2.setLength(appliedMiterLimit); + l2.translate(-l2.dx(), -l2.dy()); + + emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2())); + emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1())); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + } else { + emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y())); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + } + + } else if (join == SquareJoin) { + qfixed offset = m_strokeWidth / 2; + + QLineF l1(prevLine); + l1.translate(l1.dx(), l1.dy()); + l1.setLength(qt_fixed_to_real(offset)); + QLineF l2(nextLine.p2(), nextLine.p1()); + l2.translate(l2.dx(), l2.dy()); + l2.setLength(qt_fixed_to_real(offset)); + emitLineTo(qt_real_to_fixed(l1.x2()), qt_real_to_fixed(l1.y2())); + emitLineTo(qt_real_to_fixed(l2.x2()), qt_real_to_fixed(l2.y2())); + emitLineTo(qt_real_to_fixed(l2.x1()), qt_real_to_fixed(l2.y1())); + + } else if (join == RoundJoin) { + qfixed offset = m_strokeWidth / 2; + + QLineF shortCut(prevLine.p2(), nextLine.p1()); + qreal angle = shortCut.angleTo(prevLine); + if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) { + emitLineTo(focal_x, focal_y); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + return; + } + qreal l1_on_x = adapted_angle_on_x(prevLine); + qreal l2_on_x = adapted_angle_on_x(nextLine); + + qreal sweepLength = qAbs(l2_on_x - l1_on_x); + + int point_count; + QPointF curves[15]; + + QPointF curve_start = + qt_curves_for_arc(QRectF(qt_fixed_to_real(focal_x - offset), + qt_fixed_to_real(focal_y - offset), + qt_fixed_to_real(offset * 2), + qt_fixed_to_real(offset * 2)), + l1_on_x + 90, -sweepLength, + curves, &point_count); + +// // line to the beginning of the arc segment, (should not be needed). +// emitLineTo(qt_real_to_fixed(curve_start.x()), qt_real_to_fixed(curve_start.y())); + + for (int i=0; i<point_count; i+=3) { + emitCubicTo(qt_real_to_fixed(curves[i].x()), + qt_real_to_fixed(curves[i].y()), + qt_real_to_fixed(curves[i+1].x()), + qt_real_to_fixed(curves[i+1].y()), + qt_real_to_fixed(curves[i+2].x()), + qt_real_to_fixed(curves[i+2].y())); + } + + // line to the end of the arc segment, (should also not be needed). + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + + // Same as round join except we know its 180 degrees. Can also optimize this + // later based on the addEllipse logic + } else if (join == RoundCap) { + qfixed offset = m_strokeWidth / 2; + + // first control line + QLineF l1 = prevLine; + l1.translate(l1.dx(), l1.dy()); + l1.setLength(QT_PATH_KAPPA * offset); + + // second control line, find through normal between prevLine and focal. + QLineF l2(qt_fixed_to_real(focal_x), qt_fixed_to_real(focal_y), + prevLine.x2(), prevLine.y2()); + l2.translate(-l2.dy(), l2.dx()); + l2.setLength(QT_PATH_KAPPA * offset); + + emitCubicTo(qt_real_to_fixed(l1.x2()), + qt_real_to_fixed(l1.y2()), + qt_real_to_fixed(l2.x2()), + qt_real_to_fixed(l2.y2()), + qt_real_to_fixed(l2.x1()), + qt_real_to_fixed(l2.y1())); + + // move so that it matches + l2 = QLineF(l2.x1(), l2.y1(), l2.x1()-l2.dx(), l2.y1()-l2.dy()); + + // last line is parallel to l1 so just shift it down. + l1.translate(nextLine.x1() - l1.x1(), nextLine.y1() - l1.y1()); + + emitCubicTo(qt_real_to_fixed(l2.x2()), + qt_real_to_fixed(l2.y2()), + qt_real_to_fixed(l1.x2()), + qt_real_to_fixed(l1.y2()), + qt_real_to_fixed(l1.x1()), + qt_real_to_fixed(l1.y1())); + } else if (join == SvgMiterJoin) { + QLineF shortCut(prevLine.p2(), nextLine.p1()); + qreal angle = shortCut.angleTo(prevLine); + if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) { + emitLineTo(focal_x, focal_y); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + return; + } + QLineF miterLine(QPointF(qt_fixed_to_real(focal_x), + qt_fixed_to_real(focal_y)), isect); + if (type == QLineF::NoIntersection || miterLine.length() > qt_fixed_to_real(m_strokeWidth * m_miterLimit) / 2) { + emitLineTo(qt_real_to_fixed(nextLine.x1()), + qt_real_to_fixed(nextLine.y1())); + } else { + emitLineTo(qt_real_to_fixed(isect.x()), qt_real_to_fixed(isect.y())); + emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1())); + } + } else { + Q_ASSERT(!"QStroker::joinPoints(), bad join style..."); + } + } +} + + +/* + Strokes a subpath side using the \a it as source. Results are put into + \a stroke. The function returns true if the subpath side was closed. + If \a capFirst is true, we will use capPoints instead of joinPoints to + connect the first segment, other segments will be joined using joinPoints. + This is to put capping in order... +*/ +template <class Iterator> bool qt_stroke_side(Iterator *it, + QStroker *stroker, + bool capFirst, + QLineF *startTangent) +{ + // Used in CurveToElement section below. + const int MAX_OFFSET = 16; + QBezier offsetCurves[MAX_OFFSET]; + + Q_ASSERT(it->hasNext()); // The initaial move to + QStrokerOps::Element first_element = it->next(); + Q_ASSERT(first_element.isMoveTo()); + + qfixed2d start = first_element; + +#ifdef QPP_STROKE_DEBUG + qDebug(" -> (side) [%.2f, %.2f], startPos=%d", + qt_fixed_to_real(start.x), + qt_fixed_to_real(start.y)); +#endif + + qfixed2d prev = start; + + bool first = true; + + qfixed offset = stroker->strokeWidth() / 2; + + while (it->hasNext()) { + QStrokerOps::Element e = it->next(); + + // LineToElement + if (e.isLineTo()) { +#ifdef QPP_STROKE_DEBUG + qDebug("\n ---> (side) lineto [%.2f, %.2f]", e.x, e.y); +#endif + QLineF line(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y), + qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)); + if (line.p1() != line.p2()) { + QLineF normal = line.normalVector(); + normal.setLength(offset); + line.translate(normal.dx(), normal.dy()); + + // If we are starting a new subpath, move to correct starting point. + if (first) { + if (capFirst) + stroker->joinPoints(prev.x, prev.y, line, stroker->capStyleMode()); + else + stroker->emitMoveTo(qt_real_to_fixed(line.x1()), qt_real_to_fixed(line.y1())); + *startTangent = line; + first = false; + } else { + stroker->joinPoints(prev.x, prev.y, line, stroker->joinStyleMode()); + } + + // Add the stroke for this line. + stroker->emitLineTo(qt_real_to_fixed(line.x2()), + qt_real_to_fixed(line.y2())); + prev = e; + } + + // CurveToElement + } else if (e.isCurveTo()) { + QStrokerOps::Element cp2 = it->next(); // control point 2 + QStrokerOps::Element ep = it->next(); // end point + +#ifdef QPP_STROKE_DEBUG + qDebug("\n ---> (side) cubicTo [%.2f, %.2f]", + qt_fixed_to_real(ep.x), + qt_fixed_to_real(ep.y)); +#endif + + QBezier bezier = + QBezier::fromPoints(QPointF(qt_fixed_to_real(prev.x), qt_fixed_to_real(prev.y)), + QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)), + QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)), + QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y))); + + int count = bezier.shifted(offsetCurves, + MAX_OFFSET, + offset, + stroker->curveThreshold()); + + if (count) { + // If we are starting a new subpath, move to correct starting point + QLineF tangent = bezier.startTangent(); + tangent.translate(offsetCurves[0].pt1() - bezier.pt1()); + if (first) { + QPointF pt = offsetCurves[0].pt1(); + if (capFirst) { + stroker->joinPoints(prev.x, prev.y, + tangent, + stroker->capStyleMode()); + } else { + stroker->emitMoveTo(qt_real_to_fixed(pt.x()), + qt_real_to_fixed(pt.y())); + } + *startTangent = tangent; + first = false; + } else { + stroker->joinPoints(prev.x, prev.y, + tangent, + stroker->joinStyleMode()); + } + + // Add these beziers + for (int i=0; i<count; ++i) { + QPointF cp1 = offsetCurves[i].pt2(); + QPointF cp2 = offsetCurves[i].pt3(); + QPointF ep = offsetCurves[i].pt4(); + stroker->emitCubicTo(qt_real_to_fixed(cp1.x()), qt_real_to_fixed(cp1.y()), + qt_real_to_fixed(cp2.x()), qt_real_to_fixed(cp2.y()), + qt_real_to_fixed(ep.x()), qt_real_to_fixed(ep.y())); + } + } + + prev = ep; + } + } + + if (start == prev) { + // closed subpath, join first and last point +#ifdef QPP_STROKE_DEBUG + qDebug("\n ---> (side) closed subpath"); +#endif + stroker->joinPoints(prev.x, prev.y, *startTangent, stroker->joinStyleMode()); + return true; + } else { +#ifdef QPP_STROKE_DEBUG + qDebug("\n ---> (side) open subpath"); +#endif + return false; + } +} + +/*! + \internal + + For a given angle in the range [0 .. 90], finds the corresponding parameter t + of the prototype cubic bezier arc segment + b = fromPoints(QPointF(1, 0), QPointF(1, KAPPA), QPointF(KAPPA, 1), QPointF(0, 1)); + + From the bezier equation: + b.pointAt(t).x() = (1-t)^3 + t*(1-t)^2 + t^2*(1-t)*KAPPA + b.pointAt(t).y() = t*(1-t)^2 * KAPPA + t^2*(1-t) + t^3 + + Third degree coefficients: + b.pointAt(t).x() = at^3 + bt^2 + ct + d + where a = 2-3*KAPPA, b = 3*(KAPPA-1), c = 0, d = 1 + + b.pointAt(t).y() = at^3 + bt^2 + ct + d + where a = 3*KAPPA-2, b = 6*KAPPA+3, c = 3*KAPPA, d = 0 + + Newton's method to find the zero of a function: + given a function f(x) and initial guess x_0 + x_1 = f(x_0) / f'(x_0) + x_2 = f(x_1) / f'(x_1) + etc... +*/ + +qreal qt_t_for_arc_angle(qreal angle) +{ + if (qFuzzyIsNull(angle)) + return 0; + + if (qFuzzyCompare(angle, qreal(90))) + return 1; + + qreal radians = Q_PI * angle / 180; + qreal cosAngle = qCos(radians); + qreal sinAngle = qSin(radians); + + // initial guess + qreal tc = angle / 90; + // do some iterations of newton's method to approximate cosAngle + // finds the zero of the function b.pointAt(tc).x() - cosAngle + tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value + / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative + tc -= ((((2-3*QT_PATH_KAPPA) * tc + 3*(QT_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value + / (((6-9*QT_PATH_KAPPA) * tc + 6*(QT_PATH_KAPPA-1)) * tc); // derivative + + // initial guess + qreal ts = tc; + // do some iterations of newton's method to approximate sinAngle + // finds the zero of the function b.pointAt(tc).y() - sinAngle + ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle) + / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA); + ts -= ((((3*QT_PATH_KAPPA-2) * ts - 6*QT_PATH_KAPPA + 3) * ts + 3*QT_PATH_KAPPA) * ts - sinAngle) + / (((9*QT_PATH_KAPPA-6) * ts + 12*QT_PATH_KAPPA - 6) * ts + 3*QT_PATH_KAPPA); + + // use the average of the t that best approximates cosAngle + // and the t that best approximates sinAngle + qreal t = 0.5 * (tc + ts); + +#if 0 + printf("angle: %f, t: %f\n", angle, t); + qreal a, b, c, d; + bezierCoefficients(t, a, b, c, d); + printf("cosAngle: %.10f, value: %.10f\n", cosAngle, a + b + c * QT_PATH_KAPPA); + printf("sinAngle: %.10f, value: %.10f\n", sinAngle, b * QT_PATH_KAPPA + c + d); +#endif + + return t; +} + +Q_GUI_EXPORT void qt_find_ellipse_coords(const QRectF &r, qreal angle, qreal length, + QPointF* startPoint, QPointF *endPoint); + +/*! + \internal + + Creates a number of curves for a given arc definition. The arc is + defined an arc along the ellipses that fits into \a rect starting + at \a startAngle and an arc length of \a sweepLength. + + The function has three out parameters. The return value is the + starting point of the arc. The \a curves array represents the list + of cubicTo elements up to a maximum of \a point_count. There are of course + 3 points pr curve. +*/ +QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength, + QPointF *curves, int *point_count) +{ + Q_ASSERT(point_count); + Q_ASSERT(curves); + + *point_count = 0; + if (qt_is_nan(rect.x()) || qt_is_nan(rect.y()) || qt_is_nan(rect.width()) || qt_is_nan(rect.height()) + || qt_is_nan(startAngle) || qt_is_nan(sweepLength)) { + qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined"); + return QPointF(); + } + + if (rect.isNull()) { + return QPointF(); + } + + qreal x = rect.x(); + qreal y = rect.y(); + + qreal w = rect.width(); + qreal w2 = rect.width() / 2; + qreal w2k = w2 * QT_PATH_KAPPA; + + qreal h = rect.height(); + qreal h2 = rect.height() / 2; + qreal h2k = h2 * QT_PATH_KAPPA; + + QPointF points[16] = + { + // start point + QPointF(x + w, y + h2), + + // 0 -> 270 degrees + QPointF(x + w, y + h2 + h2k), + QPointF(x + w2 + w2k, y + h), + QPointF(x + w2, y + h), + + // 270 -> 180 degrees + QPointF(x + w2 - w2k, y + h), + QPointF(x, y + h2 + h2k), + QPointF(x, y + h2), + + // 180 -> 90 degrees + QPointF(x, y + h2 - h2k), + QPointF(x + w2 - w2k, y), + QPointF(x + w2, y), + + // 90 -> 0 degrees + QPointF(x + w2 + w2k, y), + QPointF(x + w, y + h2 - h2k), + QPointF(x + w, y + h2) + }; + + if (sweepLength > 360) sweepLength = 360; + else if (sweepLength < -360) sweepLength = -360; + + // Special case fast paths + if (startAngle == 0.0) { + if (sweepLength == 360.0) { + for (int i = 11; i >= 0; --i) + curves[(*point_count)++] = points[i]; + return points[12]; + } else if (sweepLength == -360.0) { + for (int i = 1; i <= 12; ++i) + curves[(*point_count)++] = points[i]; + return points[0]; + } + } + + int startSegment = int(qFloor(startAngle / 90)); + int endSegment = int(qFloor((startAngle + sweepLength) / 90)); + + qreal startT = (startAngle - startSegment * 90) / 90; + qreal endT = (startAngle + sweepLength - endSegment * 90) / 90; + + int delta = sweepLength > 0 ? 1 : -1; + if (delta < 0) { + startT = 1 - startT; + endT = 1 - endT; + } + + // avoid empty start segment + if (qFuzzyIsNull(startT - qreal(1))) { + startT = 0; + startSegment += delta; + } + + // avoid empty end segment + if (qFuzzyIsNull(endT)) { + endT = 1; + endSegment -= delta; + } + + startT = qt_t_for_arc_angle(startT * 90); + endT = qt_t_for_arc_angle(endT * 90); + + const bool splitAtStart = !qFuzzyIsNull(startT); + const bool splitAtEnd = !qFuzzyIsNull(endT - qreal(1)); + + const int end = endSegment + delta; + + // empty arc? + if (startSegment == end) { + const int quadrant = 3 - ((startSegment % 4) + 4) % 4; + const int j = 3 * quadrant; + return delta > 0 ? points[j + 3] : points[j]; + } + + QPointF startPoint, endPoint; + qt_find_ellipse_coords(rect, startAngle, sweepLength, &startPoint, &endPoint); + + for (int i = startSegment; i != end; i += delta) { + const int quadrant = 3 - ((i % 4) + 4) % 4; + const int j = 3 * quadrant; + + QBezier b; + if (delta > 0) + b = QBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]); + else + b = QBezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]); + + // empty arc? + if (startSegment == endSegment && qFuzzyCompare(startT, endT)) + return startPoint; + + if (i == startSegment) { + if (i == endSegment && splitAtEnd) + b = b.bezierOnInterval(startT, endT); + else if (splitAtStart) + b = b.bezierOnInterval(startT, 1); + } else if (i == endSegment && splitAtEnd) { + b = b.bezierOnInterval(0, endT); + } + + // push control points + curves[(*point_count)++] = b.pt2(); + curves[(*point_count)++] = b.pt3(); + curves[(*point_count)++] = b.pt4(); + } + + Q_ASSERT(*point_count > 0); + curves[*(point_count)-1] = endPoint; + + return startPoint; +} + + +static inline void qdashstroker_moveTo(qfixed x, qfixed y, void *data) { + ((QStroker *) data)->moveTo(x, y); +} + +static inline void qdashstroker_lineTo(qfixed x, qfixed y, void *data) { + ((QStroker *) data)->lineTo(x, y); +} + +static inline void qdashstroker_cubicTo(qfixed, qfixed, qfixed, qfixed, qfixed, qfixed, void *) { + Q_ASSERT(0); +// ((QStroker *) data)->cubicTo(c1x, c1y, c2x, c2y, ex, ey); +} + + +/******************************************************************************* + * QDashStroker members + */ +QDashStroker::QDashStroker(QStroker *stroker) + : m_stroker(stroker), m_dashOffset(0), m_stroke_width(1), m_miter_limit(1) +{ + if (m_stroker) { + setMoveToHook(qdashstroker_moveTo); + setLineToHook(qdashstroker_lineTo); + setCubicToHook(qdashstroker_cubicTo); + } +} + +QVector<qfixed> QDashStroker::patternForStyle(Qt::PenStyle style) +{ + const qfixed space = 2; + const qfixed dot = 1; + const qfixed dash = 4; + + QVector<qfixed> pattern; + + switch (style) { + case Qt::DashLine: + pattern << dash << space; + break; + case Qt::DotLine: + pattern << dot << space; + break; + case Qt::DashDotLine: + pattern << dash << space << dot << space; + break; + case Qt::DashDotDotLine: + pattern << dash << space << dot << space << dot << space; + break; + default: + break; + } + + return pattern; +} + +static inline bool lineRectIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br) +{ + return ((p1.x > tl.x || p2.x > tl.x) && (p1.x < br.x || p2.x < br.x) + && (p1.y > tl.y || p2.y > tl.y) && (p1.y < br.y || p2.y < br.y)); +} + +// If the line intersects the rectangle, this function will return true. +static bool lineIntersectsRect(qfixed2d p1, qfixed2d p2, const qfixed2d &tl, const qfixed2d &br) +{ + if (!lineRectIntersectsRect(p1, p2, tl, br)) + return false; + if (p1.x == p2.x || p1.y == p2.y) + return true; + + if (p1.y > p2.y) + qSwap(p1, p2); // make p1 above p2 + qfixed2d u; + qfixed2d v; + qfixed2d w = {p2.x - p1.x, p2.y - p1.y}; + if (p1.x < p2.x) { + // backslash + u.x = tl.x - p1.x; u.y = br.y - p1.y; + v.x = br.x - p1.x; v.y = tl.y - p1.y; + } else { + // slash + u.x = tl.x - p1.x; u.y = tl.y - p1.y; + v.x = br.x - p1.x; v.y = br.y - p1.y; + } +#if defined(QFIXED_IS_26_6) || defined(QFIXED_IS_16_16) + qint64 val1 = qint64(u.x) * qint64(w.y) - qint64(u.y) * qint64(w.x); + qint64 val2 = qint64(v.x) * qint64(w.y) - qint64(v.y) * qint64(w.x); + return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0); +#elif defined(QFIXED_IS_32_32) + // Cannot do proper test because it may overflow. + return true; +#else + qreal val1 = u.x * w.y - u.y * w.x; + qreal val2 = v.x * w.y - v.y * w.x; + return (val1 < 0 && val2 > 0) || (val1 > 0 && val2 < 0); +#endif +} + +void QDashStroker::processCurrentSubpath() +{ + int dashCount = qMin(m_dashPattern.size(), 32); + qfixed dashes[32]; + + if (m_stroker) { + m_customData = m_stroker; + m_stroke_width = m_stroker->strokeWidth(); + m_miter_limit = m_stroker->miterLimit(); + } + + qreal longestLength = 0; + qreal sumLength = 0; + for (int i=0; i<dashCount; ++i) { + dashes[i] = qMax(m_dashPattern.at(i), qreal(0)) * m_stroke_width; + sumLength += dashes[i]; + if (dashes[i] > longestLength) + longestLength = dashes[i]; + } + + if (qFuzzyIsNull(sumLength)) + return; + + qreal invSumLength = qreal(1) / sumLength; + + Q_ASSERT(dashCount > 0); + + dashCount = dashCount & -2; // Round down to even number + + int idash = 0; // Index to current dash + qreal pos = 0; // The position on the curve, 0 <= pos <= path.length + qreal elen = 0; // element length + qreal doffset = m_dashOffset * m_stroke_width; + + // make sure doffset is in range [0..sumLength) + doffset -= qFloor(doffset * invSumLength) * sumLength; + + while (doffset >= dashes[idash]) { + doffset -= dashes[idash]; + if (++idash >= dashCount) + idash = 0; + } + + qreal estart = 0; // The elements starting position + qreal estop = 0; // The element stop position + + QLineF cline; + + QPainterPath dashPath; + + QSubpathFlatIterator it(&m_elements, m_dashThreshold); + qfixed2d prev = it.next(); + + bool clipping = !m_clip_rect.isEmpty(); + qfixed2d move_to_pos = prev; + qfixed2d line_to_pos; + + // Pad to avoid clipping the borders of thick pens. + qfixed padding = qt_real_to_fixed(qMax(m_stroke_width, m_miter_limit) * longestLength); + qfixed2d clip_tl = { qt_real_to_fixed(m_clip_rect.left()) - padding, + qt_real_to_fixed(m_clip_rect.top()) - padding }; + qfixed2d clip_br = { qt_real_to_fixed(m_clip_rect.right()) + padding , + qt_real_to_fixed(m_clip_rect.bottom()) + padding }; + + bool hasMoveTo = false; + while (it.hasNext()) { + QStrokerOps::Element e = it.next(); + + Q_ASSERT(e.isLineTo()); + cline = QLineF(qt_fixed_to_real(prev.x), + qt_fixed_to_real(prev.y), + qt_fixed_to_real(e.x), + qt_fixed_to_real(e.y)); + elen = cline.length(); + + estop = estart + elen; + + bool done = pos >= estop; + + if (clipping) { + // Check if the entire line can be clipped away. + if (!lineIntersectsRect(prev, e, clip_tl, clip_br)) { + // Cut away full dash sequences. + elen -= qFloor(elen * invSumLength) * sumLength; + // Update dash offset. + while (!done) { + qreal dpos = pos + dashes[idash] - doffset - estart; + + Q_ASSERT(dpos >= 0); + + if (dpos > elen) { // dash extends this line + doffset = dashes[idash] - (dpos - elen); // subtract the part already used + pos = estop; // move pos to next path element + done = true; + } else { // Dash is on this line + pos = dpos + estart; + done = pos >= estop; + if (++idash >= dashCount) + idash = 0; + doffset = 0; // full segment so no offset on next. + } + } + hasMoveTo = false; + move_to_pos = e; + } + } + + // Dash away... + while (!done) { + QPointF p2; + + bool has_offset = doffset > 0; + bool evenDash = (idash & 1) == 0; + qreal dpos = pos + dashes[idash] - doffset - estart; + + Q_ASSERT(dpos >= 0); + + if (dpos > elen) { // dash extends this line + doffset = dashes[idash] - (dpos - elen); // subtract the part already used + pos = estop; // move pos to next path element + done = true; + p2 = cline.p2(); + } else { // Dash is on this line + p2 = cline.pointAt(dpos/elen); + pos = dpos + estart; + done = pos >= estop; + if (++idash >= dashCount) + idash = 0; + doffset = 0; // full segment so no offset on next. + } + + if (evenDash) { + line_to_pos.x = qt_real_to_fixed(p2.x()); + line_to_pos.y = qt_real_to_fixed(p2.y()); + + if (!clipping + || lineRectIntersectsRect(move_to_pos, line_to_pos, clip_tl, clip_br)) + { + // If we have an offset, we're continuing a dash + // from a previous element and should only + // continue the current dash, without starting a + // new subpath. + if (!has_offset || !hasMoveTo) { + emitMoveTo(move_to_pos.x, move_to_pos.y); + hasMoveTo = true; + } + + emitLineTo(line_to_pos.x, line_to_pos.y); + } else { + hasMoveTo = false; + } + move_to_pos = line_to_pos; + } else { + move_to_pos.x = qt_real_to_fixed(p2.x()); + move_to_pos.y = qt_real_to_fixed(p2.y()); + } + } + + // Shuffle to the next cycle... + estart = estop; + prev = e; + } + +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qstroker_p.h b/src/gui/painting/qstroker_p.h new file mode 100644 index 0000000000..8344f6cf86 --- /dev/null +++ b/src/gui/painting/qstroker_p.h @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTROKER_P_H +#define QSTROKER_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 "QtGui/qpainterpath.h" +#include "private/qdatabuffer_p.h" +#include "private/qnumeric_p.h" + +QT_BEGIN_NAMESPACE + +// #define QFIXED_IS_26_6 + +#if defined QFIXED_IS_26_6 +typedef int qfixed; +#define qt_real_to_fixed(real) qfixed(real * 64) +#define qt_int_to_fixed(real) qfixed(int(real) << 6) +#define qt_fixed_to_real(fixed) qreal(fixed / qreal(64)) +#define qt_fixed_to_int(fixed) int(fixed >> 6) +struct qfixed2d +{ + qfixed x; + qfixed y; + + bool operator==(const qfixed2d &other) const { return x == other.x && y == other.y; } +}; +#elif defined QFIXED_IS_32_32 +typedef qint64 qfixed; +#define qt_real_to_fixed(real) qfixed(real * double(qint64(1) << 32)) +#define qt_fixed_to_real(fixed) qreal(fixed / double(qint64(1) << 32)) +struct qfixed2d +{ + qfixed x; + qfixed y; + + bool operator==(const qfixed2d &other) const { return x == other.x && y == other.y; } +}; +#elif defined QFIXED_IS_16_16 +typedef int qfixed; +#define qt_real_to_fixed(real) qfixed(real * qreal(1 << 16)) +#define qt_fixed_to_real(fixed) qreal(fixed / qreal(1 << 16)) +struct qfixed2d +{ + qfixed x; + qfixed y; + + bool operator==(const qfixed2d &other) const { return x == other.x && y == other.y; } +}; +#else +typedef qreal qfixed; +#define qt_real_to_fixed(real) qfixed(real) +#define qt_fixed_to_real(fixed) fixed +struct qfixed2d +{ + qfixed x; + qfixed y; + + bool operator==(const qfixed2d &other) const { return qFuzzyCompare(x, other.x) + && qFuzzyCompare(y, other.y); } +}; +#endif + +#define QT_PATH_KAPPA 0.5522847498 + +QPointF qt_curves_for_arc(const QRectF &rect, qreal startAngle, qreal sweepLength, + QPointF *controlPoints, int *point_count); + +qreal qt_t_for_arc_angle(qreal angle); + +typedef void (*qStrokerMoveToHook)(qfixed x, qfixed y, void *data); +typedef void (*qStrokerLineToHook)(qfixed x, qfixed y, void *data); +typedef void (*qStrokerCubicToHook)(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data); + +// qtransform.cpp +Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + +class Q_GUI_EXPORT QStrokerOps +{ +public: + struct Element { + QPainterPath::ElementType type; + qfixed x; + qfixed y; + + inline bool isMoveTo() const { return type == QPainterPath::MoveToElement; } + inline bool isLineTo() const { return type == QPainterPath::LineToElement; } + inline bool isCurveTo() const { return type == QPainterPath::CurveToElement; } + + operator qfixed2d () { qfixed2d pt = { x, y }; return pt; } + }; + + QStrokerOps(); + virtual ~QStrokerOps(); + + void setMoveToHook(qStrokerMoveToHook moveToHook) { m_moveTo = moveToHook; } + void setLineToHook(qStrokerLineToHook lineToHook) { m_lineTo = lineToHook; } + void setCubicToHook(qStrokerCubicToHook cubicToHook) { m_cubicTo = cubicToHook; } + + virtual void begin(void *customData); + virtual void end(); + + inline void moveTo(qfixed x, qfixed y); + inline void lineTo(qfixed x, qfixed y); + inline void cubicTo(qfixed x1, qfixed y1, qfixed x2, qfixed y2, qfixed ex, qfixed ey); + + void strokePath(const QPainterPath &path, void *data, const QTransform &matrix); + void strokePolygon(const QPointF *points, int pointCount, bool implicit_close, + void *data, const QTransform &matrix); + void strokeEllipse(const QRectF &ellipse, void *data, const QTransform &matrix); + + QRectF clipRect() const { return m_clip_rect; } + void setClipRect(const QRectF &clip) { m_clip_rect = clip; } + + void setCurveThresholdFromTransform(const QTransform &transform) + { + qreal scale; + qt_scaleForTransform(transform, &scale); + m_dashThreshold = scale == 0 ? qreal(0.5) : (qreal(0.5) / scale); + } + + void setCurveThreshold(qfixed threshold) { m_curveThreshold = threshold; } + qfixed curveThreshold() const { return m_curveThreshold; } + +protected: + inline void emitMoveTo(qfixed x, qfixed y); + inline void emitLineTo(qfixed x, qfixed y); + inline void emitCubicTo(qfixed c1x, qfixed c1y, qfixed c2x, qfixed c2y, qfixed ex, qfixed ey); + + virtual void processCurrentSubpath() = 0; + QDataBuffer<Element> m_elements; + + QRectF m_clip_rect; + qfixed m_curveThreshold; + qfixed m_dashThreshold; + + void *m_customData; + qStrokerMoveToHook m_moveTo; + qStrokerLineToHook m_lineTo; + qStrokerCubicToHook m_cubicTo; + +}; + +class Q_GUI_EXPORT QStroker : public QStrokerOps +{ +public: + + enum LineJoinMode { + FlatJoin, + SquareJoin, + MiterJoin, + RoundJoin, + RoundCap, + SvgMiterJoin + }; + + QStroker(); + ~QStroker(); + + void setStrokeWidth(qfixed width) { m_strokeWidth = width; } + qfixed strokeWidth() const { return m_strokeWidth; } + + void setCapStyle(Qt::PenCapStyle capStyle) { m_capStyle = joinModeForCap(capStyle); } + Qt::PenCapStyle capStyle() const { return capForJoinMode(m_capStyle); } + LineJoinMode capStyleMode() const { return m_capStyle; } + + void setJoinStyle(Qt::PenJoinStyle style) { m_joinStyle = joinModeForJoin(style); } + Qt::PenJoinStyle joinStyle() const { return joinForJoinMode(m_joinStyle); } + LineJoinMode joinStyleMode() const { return m_joinStyle; } + + void setMiterLimit(qfixed length) { m_miterLimit = length; } + qfixed miterLimit() const { return m_miterLimit; } + + void joinPoints(qfixed x, qfixed y, const QLineF &nextLine, LineJoinMode join); + inline void emitMoveTo(qfixed x, qfixed y); + inline void emitLineTo(qfixed x, qfixed y); + inline void emitCubicTo(qfixed c1x, qfixed c1y, qfixed c2x, qfixed c2y, qfixed ex, qfixed ey); + +protected: + static Qt::PenCapStyle capForJoinMode(LineJoinMode mode); + static LineJoinMode joinModeForCap(Qt::PenCapStyle); + + static Qt::PenJoinStyle joinForJoinMode(LineJoinMode mode); + static LineJoinMode joinModeForJoin(Qt::PenJoinStyle joinStyle); + + virtual void processCurrentSubpath(); + + qfixed m_strokeWidth; + qfixed m_miterLimit; + + LineJoinMode m_capStyle; + LineJoinMode m_joinStyle; + + qfixed m_back1X; + qfixed m_back1Y; + + qfixed m_back2X; + qfixed m_back2Y; +}; + +class Q_GUI_EXPORT QDashStroker : public QStrokerOps +{ +public: + QDashStroker(QStroker *stroker); + + QStroker *stroker() const { return m_stroker; } + + static QVector<qfixed> patternForStyle(Qt::PenStyle style); + + void setDashPattern(const QVector<qfixed> &dashPattern) { m_dashPattern = dashPattern; } + QVector<qfixed> dashPattern() const { return m_dashPattern; } + + void setDashOffset(qreal offset) { m_dashOffset = offset; } + qreal dashOffset() const { return m_dashOffset; } + + virtual void begin(void *data); + virtual void end(); + + inline void setStrokeWidth(qreal width) { m_stroke_width = width; } + inline void setMiterLimit(qreal limit) { m_miter_limit = limit; } + +protected: + virtual void processCurrentSubpath(); + + QStroker *m_stroker; + QVector<qfixed> m_dashPattern; + qreal m_dashOffset; + + qreal m_stroke_width; + qreal m_miter_limit; +}; + + +/******************************************************************************* + * QStrokerOps inline membmers + */ + +inline void QStrokerOps::emitMoveTo(qfixed x, qfixed y) +{ + Q_ASSERT(m_moveTo); + m_moveTo(x, y, m_customData); +} + +inline void QStrokerOps::emitLineTo(qfixed x, qfixed y) +{ + Q_ASSERT(m_lineTo); + m_lineTo(x, y, m_customData); +} + +inline void QStrokerOps::emitCubicTo(qfixed c1x, qfixed c1y, qfixed c2x, qfixed c2y, qfixed ex, qfixed ey) +{ + Q_ASSERT(m_cubicTo); + m_cubicTo(c1x, c1y, c2x, c2y, ex, ey, m_customData); +} + +inline void QStrokerOps::moveTo(qfixed x, qfixed y) +{ + if (m_elements.size()>1) + processCurrentSubpath(); + m_elements.reset(); + Element e = { QPainterPath::MoveToElement, x, y }; + m_elements.add(e); +} + +inline void QStrokerOps::lineTo(qfixed x, qfixed y) +{ + Element e = { QPainterPath::LineToElement, x, y }; + m_elements.add(e); +} + +inline void QStrokerOps::cubicTo(qfixed x1, qfixed y1, qfixed x2, qfixed y2, qfixed ex, qfixed ey) +{ + Element c1 = { QPainterPath::CurveToElement, x1, y1 }; + Element c2 = { QPainterPath::CurveToDataElement, x2, y2 }; + Element e = { QPainterPath::CurveToDataElement, ex, ey }; + m_elements.add(c1); + m_elements.add(c2); + m_elements.add(e); +} + +/******************************************************************************* + * QStroker inline members + */ +inline void QStroker::emitMoveTo(qfixed x, qfixed y) +{ + m_back2X = m_back1X; + m_back2Y = m_back1Y; + m_back1X = x; + m_back1Y = y; + QStrokerOps::emitMoveTo(x, y); +} + +inline void QStroker::emitLineTo(qfixed x, qfixed y) +{ + m_back2X = m_back1X; + m_back2Y = m_back1Y; + m_back1X = x; + m_back1Y = y; + QStrokerOps::emitLineTo(x, y); +} + +inline void QStroker::emitCubicTo(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey) +{ + if (c2x == ex && c2y == ey) { + if (c1x == ex && c1y == ey) { + m_back2X = m_back1X; + m_back2Y = m_back1Y; + } else { + m_back2X = c1x; + m_back2Y = c1y; + } + } else { + m_back2X = c2x; + m_back2Y = c2y; + } + m_back1X = ex; + m_back1Y = ey; + QStrokerOps::emitCubicTo(c1x, c1y, c2x, c2y, ex, ey); +} + +/******************************************************************************* + * QDashStroker inline members + */ +inline void QDashStroker::begin(void *data) +{ + if (m_stroker) + m_stroker->begin(data); + QStrokerOps::begin(data); +} + +inline void QDashStroker::end() +{ + QStrokerOps::end(); + if (m_stroker) + m_stroker->end(); +} + +QT_END_NAMESPACE + +#endif // QSTROKER_P_H diff --git a/src/gui/painting/qstylepainter.cpp b/src/gui/painting/qstylepainter.cpp new file mode 100644 index 0000000000..c8f9d05315 --- /dev/null +++ b/src/gui/painting/qstylepainter.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstylepainter.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QStylePainter + + \brief The QStylePainter class is a convenience class for drawing QStyle + elements inside a widget. + + \ingroup appearance + \ingroup painting + + QStylePainter extends QPainter with a set of high-level \c + draw...() functions implemented on top of QStyle's API. The + advantage of using QStylePainter is that the parameter lists get + considerably shorter. Whereas a QStyle object must be able to + draw on any widget using any painter (because the application + normally has one QStyle object shared by all widget), a + QStylePainter is initialized with a widget, eliminating the need + to specify the QWidget, the QPainter, and the QStyle for every + function call. + + Example using QStyle directly: + + \snippet doc/src/snippets/styles/styles.cpp 1 + + Example using QStylePainter: + + \snippet doc/src/snippets/styles/styles.cpp 0 + \snippet doc/src/snippets/styles/styles.cpp 4 + \snippet doc/src/snippets/styles/styles.cpp 6 + + \sa QStyle, QStyleOption +*/ + +/*! + \fn QStylePainter::QStylePainter() + + Constructs a QStylePainter. +*/ + +/*! + \fn QStylePainter::QStylePainter(QWidget *widget) + + Construct a QStylePainter using widget \a widget for its paint device. +*/ + +/*! + \fn QStylePainter::QStylePainter(QPaintDevice *pd, QWidget *widget) + + Construct a QStylePainter using \a pd for its paint device, and + attributes from \a widget. +*/ + + +/*! + \fn bool QStylePainter::begin(QWidget *widget) + + Begin painting operations on the specified \a widget. + Returns true if the painter is ready to use; otherwise returns false. + + This is automatically called by the constructor that takes a QWidget. +*/ + +/*! + \fn bool QStylePainter::begin(QPaintDevice *pd, QWidget *widget) + \overload + + Begin painting operations on paint device \a pd as if it was \a + widget. + + This is automatically called by the constructor that + takes a QPaintDevice and a QWidget. +*/ + + +/*! + \fn void QStylePainter::drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption &option) + + Use the widget's style to draw a primitive element \a pe specified by QStyleOption \a option. + + \sa QStyle::drawPrimitive() +*/ + +/*! + \fn void QStylePainter::drawControl(QStyle::ControlElement ce, const QStyleOption &option) + + Use the widget's style to draw a control element \a ce specified by QStyleOption \a option. + + \sa QStyle::drawControl() +*/ + +/*! + \fn void QStylePainter::drawComplexControl(QStyle::ComplexControl cc, + const QStyleOptionComplex &option) + + Use the widget's style to draw a complex control \a cc specified by the + QStyleOptionComplex \a option. + + \sa QStyle::drawComplexControl() +*/ + +/*! + \fn void QStylePainter::drawItemText(const QRect &rect, int flags, const QPalette &pal, + bool enabled, const QString &text, + QPalette::ColorRole textRole = QPalette::NoRole) + + Draws the \a text in rectangle \a rect and palette \a pal. + The text is aligned and wrapped according to \a + flags. + + The pen color is specified with \a textRole. The \a enabled bool + indicates whether or not the item is enabled; when reimplementing + this bool should influence how the item is drawn. + + \sa QStyle::drawItemText(), Qt::Alignment +*/ + +/*! + \fn void QStylePainter::drawItemPixmap(const QRect &rect, int flags, const QPixmap &pixmap) + + Draws the \a pixmap in rectangle \a rect. + The pixmap is aligned according to \a flags. + + \sa QStyle::drawItemPixmap(), Qt::Alignment +*/ + +/*! + \fn QStyle *QStylePainter::style() const + + Return the current style used by the QStylePainter. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/painting/qstylepainter.h b/src/gui/painting/qstylepainter.h new file mode 100644 index 0000000000..0fc3f2b4b9 --- /dev/null +++ b/src/gui/painting/qstylepainter.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEPAINTER_H +#define QSTYLEPAINTER_H + +#include <QtGui/qpainter.h> +#include <QtGui/qstyle.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStylePainter : public QPainter +{ +public: + inline QStylePainter() : QPainter(), widget(0), wstyle(0) {} + inline explicit QStylePainter(QWidget *w) { begin(w, w); } + inline QStylePainter(QPaintDevice *pd, QWidget *w) { begin(pd, w); } + inline bool begin(QWidget *w) { return begin(w, w); } + inline bool begin(QPaintDevice *pd, QWidget *w) { + Q_ASSERT_X(w, "QStylePainter::QStylePainter", "Widget must be non-zero"); + widget = w; + wstyle = w->style(); + return QPainter::begin(pd); + }; + inline void drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption &opt); + inline void drawControl(QStyle::ControlElement ce, const QStyleOption &opt); + inline void drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex &opt); + inline void drawItemText(const QRect &r, int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole); + inline void drawItemPixmap(const QRect &r, int flags, const QPixmap &pixmap); + inline QStyle *style() const { return wstyle; } + +private: + QWidget *widget; + QStyle *wstyle; + Q_DISABLE_COPY(QStylePainter) +}; + +void QStylePainter::drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption &opt) +{ + wstyle->drawPrimitive(pe, &opt, this, widget); +} + +void QStylePainter::drawControl(QStyle::ControlElement ce, const QStyleOption &opt) +{ + wstyle->drawControl(ce, &opt, this, widget); +} + +void QStylePainter::drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex &opt) +{ + wstyle->drawComplexControl(cc, &opt, this, widget); +} + +void QStylePainter::drawItemText(const QRect &r, int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole) +{ + wstyle->drawItemText(this, r, flags, pal, enabled, text, textRole); +} + +void QStylePainter::drawItemPixmap(const QRect &r, int flags, const QPixmap &pixmap) +{ + wstyle->drawItemPixmap(this, r, flags, pixmap); +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEPAINTER_H diff --git a/src/gui/painting/qtessellator.cpp b/src/gui/painting/qtessellator.cpp new file mode 100644 index 0000000000..328f4dd96a --- /dev/null +++ b/src/gui/painting/qtessellator.cpp @@ -0,0 +1,1498 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtessellator_p.h" + +#include <QRect> +#include <QList> +#include <QDebug> + +#include <qmath.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +//#define DEBUG +#ifdef DEBUG +#define QDEBUG qDebug +#else +#define QDEBUG if (1){} else qDebug +#endif + +static const bool emit_clever = true; +static const bool mark_clever = false; + +enum VertexFlags { + LineBeforeStarts = 0x1, + LineBeforeEnds = 0x2, + LineBeforeHorizontal = 0x4, + LineAfterStarts = 0x8, + LineAfterEnds = 0x10, + LineAfterHorizontal = 0x20 +}; + + + +class QTessellatorPrivate { +public: + struct Vertices; + + QTessellatorPrivate() {} + + QRectF collectAndSortVertices(const QPointF *points, int *maxActiveEdges); + void cancelCoincidingEdges(); + + void emitEdges(QTessellator *tessellator); + void processIntersections(); + void removeEdges(); + void addEdges(); + void addIntersections(); + + struct Vertex : public QTessellator::Vertex + { + int flags; + }; + + struct Intersection + { + Q27Dot5 y; + int edge; + bool operator <(const Intersection &other) const { + if (y != other.y) + return y < other.y; + return edge < other.edge; + } + }; + struct IntersectionLink + { + int next; + int prev; + }; + typedef QMap<Intersection, IntersectionLink> Intersections; + + struct Edge { + Edge(const Vertices &v, int _edge); + int edge; + const Vertex *v0; + const Vertex *v1; + Q27Dot5 y_left; + Q27Dot5 y_right; + signed int winding : 8; + bool mark; + bool free; + bool intersect_left; + bool intersect_right; + bool isLeftOf(const Edge &other, Q27Dot5 y) const; + Q27Dot5 positionAt(Q27Dot5 y) const; + bool intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const; + + }; + + class EdgeSorter + { + public: + EdgeSorter(int _y) : y(_y) {} + bool operator() (const Edge *e1, const Edge *e2); + int y; + }; + + class Scanline { + public: + Scanline(); + ~Scanline(); + + void init(int maxActiveEdges); + void done(); + + int findEdgePosition(Q27Dot5 x, Q27Dot5 y) const; + int findEdgePosition(const Edge &e) const; + int findEdge(int edge) const; + void clearMarks(); + + void swap(int p1, int p2) { + Edge *tmp = edges[p1]; + edges[p1] = edges[p2]; + edges[p2] = tmp; + } + void insert(int pos, const Edge &e); + void removeAt(int pos); + void markEdges(int pos1, int pos2); + + void prepareLine(); + void lineDone(); + + Edge **old; + int old_size; + + Edge **edges; + int size; + + private: + Edge *edge_table; + int first_unused; + int max_edges; + enum { default_alloc = 32 }; + }; + + struct Vertices { + enum { default_alloc = 128 }; + Vertices(); + ~Vertices(); + void init(int maxVertices); + void done(); + Vertex *storage; + Vertex **sorted; + + Vertex *operator[] (int i) { return storage + i; } + const Vertex *operator[] (int i) const { return storage + i; } + int position(const Vertex *v) const { + return v - storage; + } + Vertex *next(Vertex *v) { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + const Vertex *next(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + int nextPos(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + return 0; + return v - storage; + } + Vertex *prev(Vertex *v) { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + const Vertex *prev(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + int prevPos(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v - storage; + } + int nPoints; + int allocated; + }; + Vertices vertices; + Intersections intersections; + Scanline scanline; + bool winding; + Q27Dot5 y; + int currentVertex; + +private: + void addIntersection(const Edge *e1, const Edge *e2); + bool edgeInChain(Intersection i, int edge); +}; + + +QTessellatorPrivate::Edge::Edge(const QTessellatorPrivate::Vertices &vertices, int edge) +{ + this->edge = edge; + intersect_left = intersect_right = true; + mark = false; + free = false; + + v0 = vertices[edge]; + v1 = vertices.next(v0); + + Q_ASSERT(v0->y != v1->y); + + if (v0->y > v1->y) { + qSwap(v0, v1); + winding = -1; + } else { + winding = 1; + } + y_left = y_right = v0->y; +} + +// This is basically the algorithm from graphics gems. The algorithm +// is cubic in the coordinates at one place. Since we use 64bit +// integers, this implies, that the allowed range for our coordinates +// is limited to 21 bits. With 5 bits behind the decimal, this +// implies that differences in coordaintes can range from 2*SHORT_MIN +// to 2*SHORT_MAX, giving us efficiently a coordinate system from +// SHORT_MIN to SHORT_MAX. +// + +// WARNING: It's absolutely critical that the intersect() and isLeftOf() methods use +// exactly the same algorithm to calulate yi. It's also important to be sure the algorithms +// are transitive (ie. the conditions below are true for all input data): +// +// a.intersect(b) == b.intersect(a) +// a.isLeftOf(b) != b.isLeftOf(a) +// +// This is tricky to get right, so be very careful when changing anything in here! + +static inline bool sameSign(qint64 a, qint64 b) { + return (((qint64) ((quint64) a ^ (quint64) b)) >= 0 ); +} + +bool QTessellatorPrivate::Edge::intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const +{ + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) + return false; + + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 r3 = a1 * other.v0->x + b1 * other.v0->y + c1; + qint64 r4 = a1 * other.v1->x + b1 * other.v1->y + c1; + + // Check signs of r3 and r4. If both point 3 and point 4 lie on + // same side of line 1, the line segments do not intersect. + QDEBUG() << " " << r3 << r4; + if (r3 != 0 && r4 != 0 && sameSign( r3, r4 )) + return false; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; + qint64 r2 = a2 * v1->x + b2 * v1->y + c2; + + // Check signs of r1 and r2. If both point 1 and point 2 lie + // on same side of second line segment, the line segments do not intersect. + QDEBUG() << " " << r1 << r2; + if (r1 != 0 && r2 != 0 && sameSign( r1, r2 )) + return false; + + // The det/2 is to get rounding instead of truncating. It + // is added or subtracted to the numerator, depending upon the + // sign of the numerator. + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + *y = ( num < 0 ? num - offset : num + offset ) / det; + + *det_positive = (det > 0); + + return true; +} + +#undef SAME_SIGNS + +bool QTessellatorPrivate::Edge::isLeftOf(const Edge &other, Q27Dot5 y) const +{ +// QDEBUG() << "isLeftOf" << edge << other.edge << y; + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) { + // lines are parallel. Only need to check side of one point + // fixed ordering for coincident edges + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; +// QDEBUG() << "det = 0" << r1; + if (r1 == 0) + return edge < other.edge; + return (r1 < 0); + } + + // not parallel, need to find the y coordinate of the intersection point + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + qint64 yi = ( num < 0 ? num - offset : num + offset ) / det; +// QDEBUG() << " num=" << num << "offset=" << offset << "det=" << det; + + return ((yi > y) ^ (det < 0)); +} + +static inline bool compareVertex(const QTessellatorPrivate::Vertex *p1, + const QTessellatorPrivate::Vertex *p2) +{ + if (p1->y == p2->y) { + if (p1->x == p2->x) + return p1 < p2; + return p1->x < p2->x; + } + return p1->y < p2->y; +} + +Q27Dot5 QTessellatorPrivate::Edge::positionAt(Q27Dot5 y) const +{ + if (y == v0->y) + return v0->x; + else if (y == v1->y) + return v1->x; + + qint64 d = v1->x - v0->x; + return (v0->x + d*(y - v0->y)/(v1->y-v0->y)); +} + +bool QTessellatorPrivate::EdgeSorter::operator() (const Edge *e1, const Edge *e2) +{ + return e1->isLeftOf(*e2, y); +} + + +QTessellatorPrivate::Scanline::Scanline() +{ + edges = 0; + edge_table = 0; + old = 0; +} + +void QTessellatorPrivate::Scanline::init(int maxActiveEdges) +{ + maxActiveEdges *= 2; + if (!edges || maxActiveEdges > default_alloc) { + max_edges = maxActiveEdges; + int s = qMax(maxActiveEdges + 1, default_alloc + 1); + edges = q_check_ptr((Edge **)realloc(edges, s*sizeof(Edge *))); + edge_table = q_check_ptr((Edge *)realloc(edge_table, s*sizeof(Edge))); + old = q_check_ptr((Edge **)realloc(old, s*sizeof(Edge *))); + } + size = 0; + old_size = 0; + first_unused = 0; + for (int i = 0; i < maxActiveEdges; ++i) + edge_table[i].edge = i+1; + edge_table[maxActiveEdges].edge = -1; +} + +void QTessellatorPrivate::Scanline::done() +{ + if (max_edges > default_alloc) { + free(edges); + free(old); + free(edge_table); + edges = 0; + old = 0; + edge_table = 0; + } +} + +QTessellatorPrivate::Scanline::~Scanline() +{ + free(edges); + free(old); + free(edge_table); +} + +int QTessellatorPrivate::Scanline::findEdgePosition(Q27Dot5 x, Q27Dot5 y) const +{ + int min = 0; + int max = size - 1; + while (min < max) { + int pos = min + ((max - min + 1) >> 1); + Q27Dot5 ax = edges[pos]->positionAt(y); + if (ax > x) { + max = pos - 1; + } else { + min = pos; + } + } + return min; +} + +int QTessellatorPrivate::Scanline::findEdgePosition(const Edge &e) const +{ +// qDebug() << ">> findEdgePosition"; + int min = 0; + int max = size; + while (min < max) { + int pos = min + ((max - min) >> 1); +// qDebug() << " " << min << max << pos << edges[pos]->isLeftOf(e, e.y0); + if (edges[pos]->isLeftOf(e, e.v0->y)) { + min = pos + 1; + } else { + max = pos; + } + } +// qDebug() << "<< findEdgePosition got" << min; + return min; +} + +int QTessellatorPrivate::Scanline::findEdge(int edge) const +{ + for (int i = 0; i < size; ++i) { + int item_edge = edges[i]->edge; + if (item_edge == edge) + return i; + } + //Q_ASSERT(false); + return -1; +} + +void QTessellatorPrivate::Scanline::clearMarks() +{ + for (int i = 0; i < size; ++i) { + edges[i]->mark = false; + edges[i]->intersect_left = false; + edges[i]->intersect_right = false; + } +} + +void QTessellatorPrivate::Scanline::prepareLine() +{ + Edge **end = edges + size; + Edge **e = edges; + Edge **o = old; + while (e < end) { + *o = *e; + ++o; + ++e; + } + old_size = size; +} + +void QTessellatorPrivate::Scanline::lineDone() +{ + Edge **end = old + old_size; + Edge **e = old; + while (e < end) { + if ((*e)->free) { + (*e)->edge = first_unused; + first_unused = (*e - edge_table); + } + ++e; + } +} + +void QTessellatorPrivate::Scanline::insert(int pos, const Edge &e) +{ + Edge *edge = edge_table + first_unused; + first_unused = edge->edge; + Q_ASSERT(first_unused != -1); + *edge = e; + memmove(edges + pos + 1, edges + pos, (size - pos)*sizeof(Edge *)); + edges[pos] = edge; + ++size; +} + +void QTessellatorPrivate::Scanline::removeAt(int pos) +{ + Edge *e = edges[pos]; + e->free = true; + --size; + memmove(edges + pos, edges + pos + 1, (size - pos)*sizeof(Edge *)); +} + +void QTessellatorPrivate::Scanline::markEdges(int pos1, int pos2) +{ + if (pos2 < pos1) + return; + + for (int i = pos1; i <= pos2; ++i) + edges[i]->mark = true; +} + + +QTessellatorPrivate::Vertices::Vertices() +{ + storage = 0; + sorted = 0; + allocated = 0; + nPoints = 0; +} + +QTessellatorPrivate::Vertices::~Vertices() +{ + if (storage) { + free(storage); + free(sorted); + } +} + +void QTessellatorPrivate::Vertices::init(int maxVertices) +{ + if (!storage || maxVertices > allocated) { + int size = qMax((int)default_alloc, maxVertices); + storage = q_check_ptr((Vertex *)realloc(storage, size*sizeof(Vertex))); + sorted = q_check_ptr((Vertex **)realloc(sorted, size*sizeof(Vertex *))); + allocated = maxVertices; + } +} + +void QTessellatorPrivate::Vertices::done() +{ + if (allocated > default_alloc) { + free(storage); + free(sorted); + storage = 0; + sorted = 0; + allocated = 0; + } +} + + + +static inline void fillTrapezoid(Q27Dot5 y1, Q27Dot5 y2, int left, int right, + const QTessellatorPrivate::Vertices &vertices, + QTessellator::Trapezoid *trap) +{ + trap->top = y1; + trap->bottom = y2; + const QTessellatorPrivate::Vertex *v = vertices[left]; + trap->topLeft = v; + trap->bottomLeft = vertices.next(v); + if (trap->topLeft->y > trap->bottomLeft->y) + qSwap(trap->topLeft,trap->bottomLeft); + v = vertices[right]; + trap->topRight = v; + trap->bottomRight = vertices.next(v); + if (trap->topRight->y > trap->bottomRight->y) + qSwap(trap->topRight, trap->bottomRight); +} + +QRectF QTessellatorPrivate::collectAndSortVertices(const QPointF *points, int *maxActiveEdges) +{ + *maxActiveEdges = 0; + Vertex *v = vertices.storage; + Vertex **vv = vertices.sorted; + + qreal xmin(points[0].x()); + qreal xmax(points[0].x()); + qreal ymin(points[0].y()); + qreal ymax(points[0].y()); + + // collect vertex data + Q27Dot5 y_prev = FloatToQ27Dot5(points[vertices.nPoints-1].y()); + Q27Dot5 x_next = FloatToQ27Dot5(points[0].x()); + Q27Dot5 y_next = FloatToQ27Dot5(points[0].y()); + int j = 0; + int i = 0; + while (i < vertices.nPoints) { + Q27Dot5 y_curr = y_next; + + *vv = v; + + v->x = x_next; + v->y = y_next; + v->flags = 0; + + next_point: + + xmin = qMin(xmin, points[i+1].x()); + xmax = qMax(xmax, points[i+1].x()); + ymin = qMin(ymin, points[i+1].y()); + ymax = qMax(ymax, points[i+1].y()); + + y_next = FloatToQ27Dot5(points[i+1].y()); + x_next = FloatToQ27Dot5(points[i+1].x()); + + // skip vertices on top of each other + if (v->x == x_next && v->y == y_next) { + ++i; + if (i < vertices.nPoints) + goto next_point; + Vertex *v0 = vertices.storage; + v0->flags &= ~(LineBeforeStarts|LineBeforeEnds|LineBeforeHorizontal); + if (y_prev < y_curr) + v0->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v0->flags |= LineBeforeStarts; + else + v0->flags |= LineBeforeHorizontal; + if ((v0->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v0->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + break; + } + + if (y_prev < y_curr) + v->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v->flags |= LineBeforeStarts; + else + v->flags |= LineBeforeHorizontal; + + + if (y_curr < y_next) + v->flags |= LineAfterStarts; + else if (y_curr > y_next) + v->flags |= LineAfterEnds; + else + v->flags |= LineAfterHorizontal; + // ### could probably get better limit by looping over sorted list and counting down on ending edges + if ((v->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + y_prev = y_curr; + ++v; + ++vv; + ++j; + ++i; + } + vertices.nPoints = j; + + QDEBUG() << "maxActiveEdges=" << *maxActiveEdges; + vv = vertices.sorted; + qSort(vv, vv + vertices.nPoints, compareVertex); + + return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); +} + +struct QCoincidingEdge { + QTessellatorPrivate::Vertex *start; + QTessellatorPrivate::Vertex *end; + bool used; + bool before; + + inline bool operator<(const QCoincidingEdge &e2) const + { + return end->y == e2.end->y ? end->x < e2.end->x : end->y < e2.end->y; + } +}; + +static void cancelEdges(QCoincidingEdge &e1, QCoincidingEdge &e2) +{ + if (e1.before) { + e1.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e1.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e1.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e1.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + if (e2.before) { + e2.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e2.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e2.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e2.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + e1.used = e2.used = true; +} + +void QTessellatorPrivate::cancelCoincidingEdges() +{ + Vertex **vv = vertices.sorted; + + QCoincidingEdge *tl = 0; + int tlSize = 0; + + for (int i = 0; i < vertices.nPoints - 1; ++i) { + Vertex *v = vv[i]; + int testListSize = 0; + while (i < vertices.nPoints - 1) { + Vertex *n = vv[i]; + if (v->x != n->x || v->y != n->y) + break; + + if (testListSize > tlSize - 2) { + tlSize = qMax(tlSize*2, 16); + tl = q_check_ptr((QCoincidingEdge *)realloc(tl, tlSize*sizeof(QCoincidingEdge))); + } + if (n->flags & (LineBeforeStarts|LineBeforeHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.prev(n); + tl[testListSize].used = false; + tl[testListSize].before = true; + ++testListSize; + } + if (n->flags & (LineAfterStarts|LineAfterHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.next(n); + tl[testListSize].used = false; + tl[testListSize].before = false; + ++testListSize; + } + ++i; + } + if (!testListSize) + continue; + + qSort(tl, tl + testListSize); + + for (int j = 0; j < testListSize; ++j) { + if (tl[j].used) + continue; + + for (int k = j + 1; k < testListSize; ++k) { + if (tl[j].end->x != tl[k].end->x + || tl[j].end->y != tl[k].end->y + || tl[k].used) + break; + + if (!winding || tl[j].before != tl[k].before) { + cancelEdges(tl[j], tl[k]); + break; + } + ++k; + } + ++j; + } + } + free(tl); +} + + +void QTessellatorPrivate::emitEdges(QTessellator *tessellator) +{ + //QDEBUG() << "TRAPS:"; + if (!scanline.old_size) + return; + + // emit edges + if (winding) { + // winding fill rule + int w = 0; + + scanline.old[0]->y_left = y; + + for (int i = 0; i < scanline.old_size - 1; ++i) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + w += left->winding; +// qDebug() << "i=" << i << "edge->winding=" << left->winding << "winding=" << winding; + if (w == 0) { + left->y_right = y; + right->y_left = y; + } else if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + } + right->y_left = y; + left->y_right = y; + } + left->mark = false; + } + if (scanline.old[scanline.old_size - 1]->mark) { + scanline.old[scanline.old_size - 1]->y_right = y; + scanline.old[scanline.old_size - 1]->mark = false; + } + } else { + // odd-even fill rule + for (int i = 0; i < scanline.old_size; i += 2) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); + } +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + left->y_left = y; + left->y_right = y; + right->y_left = y; + right->y_right = y; + left->mark = right->mark = false; + } + } + } +} + + +void QTessellatorPrivate::processIntersections() +{ + QDEBUG() << "PROCESS INTERSECTIONS"; + // process intersections + while (!intersections.isEmpty()) { + Intersections::iterator it = intersections.begin(); + if (it.key().y != y) + break; + + // swap edges + QDEBUG() << " swapping intersecting edges "; + int min = scanline.size; + int max = 0; + Q27Dot5 xmin = INT_MAX; + Q27Dot5 xmax = INT_MIN; + int num = 0; + while (1) { + const Intersection &i = it.key(); + int next = it->next; + + int edgePos = scanline.findEdge(i.edge); + if (edgePos >= 0) { + ++num; + min = qMin(edgePos, min); + max = qMax(edgePos, max); + Edge *edge = scanline.edges[edgePos]; + xmin = qMin(xmin, edge->positionAt(y)); + xmax = qMax(xmax, edge->positionAt(y)); + } + Intersection key; + key.y = y; + key.edge = next; + it = intersections.find(key); + intersections.remove(i); + if (it == intersections.end()) + break; + } + if (num < 2) + continue; + + Q_ASSERT(min != max); + QDEBUG() << "sorting between" << min << "and" << max << "xpos=" << xmin << xmax; + while (min > 0 && scanline.edges[min - 1]->positionAt(y) >= xmin) { + QDEBUG() << " adding edge on left"; + --min; + } + while (max < scanline.size - 1 && scanline.edges[max + 1]->positionAt(y) <= xmax) { + QDEBUG() << " adding edge on right"; + ++max; + } + + qSort(scanline.edges + min, scanline.edges + max + 1, EdgeSorter(y)); +#ifdef DEBUG + for (int i = min; i <= max; ++i) + QDEBUG() << " " << scanline.edges[i]->edge << "at pos" << i; +#endif + for (int i = min; i <= max; ++i) { + Edge *edge = scanline.edges[i]; + edge->intersect_left = true; + edge->intersect_right = true; + edge->mark = true; + } + } +} + +void QTessellatorPrivate::removeEdges() +{ + int cv = currentVertex; + while (cv < vertices.nPoints) { + const Vertex *v = vertices.sorted[cv]; + if (v->y > y) + break; + if (v->flags & LineBeforeEnds) { + QDEBUG() << " removing edge" << vertices.prevPos(v); + int pos = scanline.findEdge(vertices.prevPos(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + if (v->flags & LineAfterEnds) { + QDEBUG() << " removing edge" << vertices.position(v); + int pos = scanline.findEdge(vertices.position(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + ++cv; + } +} + +void QTessellatorPrivate::addEdges() +{ + while (currentVertex < vertices.nPoints) { + const Vertex *v = vertices.sorted[currentVertex]; + if (v->y > y) + break; + if (v->flags & LineBeforeStarts) { + // add new edge + int start = vertices.prevPos(v); + Edge e(vertices, start); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << start << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineAfterEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterStarts) { + Edge e(vertices, vertices.position(v)); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << vertices.position(v) << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineBeforeEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterHorizontal) { + int pos1 = scanline.findEdgePosition(v->x, v->y); + const Vertex *next = vertices.next(v); + Q_ASSERT(v->y == next->y); + int pos2 = scanline.findEdgePosition(next->x, next->y); + if (pos2 < pos1) + qSwap(pos1, pos2); + if (pos1 > 0) + --pos1; + if (pos2 == scanline.size) + --pos2; + //QDEBUG() << "marking horizontal edge from " << pos1 << "to" << pos2; + scanline.markEdges(pos1, pos2); + } + ++currentVertex; + } +} + +#ifdef DEBUG +static void checkLinkChain(const QTessellatorPrivate::Intersections &intersections, + QTessellatorPrivate::Intersection i) +{ +// qDebug() << " Link chain: "; + int end = i.edge; + while (1) { + QTessellatorPrivate::IntersectionLink l = intersections.value(i); +// qDebug() << " " << i.edge << "next=" << l.next << "prev=" << l.prev; + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + QTessellatorPrivate::Intersection i2 = i; + i2.edge = l.next; + QTessellatorPrivate::IntersectionLink l2 = intersections.value(i2); + + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); + i = i2; + } +} +#endif + +bool QTessellatorPrivate::edgeInChain(Intersection i, int edge) +{ + int end = i.edge; + while (1) { + if (i.edge == edge) + return true; + IntersectionLink l = intersections.value(i); + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + Intersection i2 = i; + i2.edge = l.next; + +#ifndef QT_NO_DEBUG + IntersectionLink l2 = intersections.value(i2); + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); +#endif + i = i2; + } + return false; +} + + +void QTessellatorPrivate::addIntersection(const Edge *e1, const Edge *e2) +{ + const IntersectionLink emptyLink = {-1, -1}; + + int next = vertices.nextPos(vertices[e1->edge]); + if (e2->edge == next) + return; + int prev = vertices.prevPos(vertices[e1->edge]); + if (e2->edge == prev) + return; + + Q27Dot5 yi; + bool det_positive; + bool isect = e1->intersect(*e2, &yi, &det_positive); + QDEBUG("checking edges %d and %d", e1->edge, e2->edge); + if (!isect) { + QDEBUG() << " no intersection"; + return; + } + + // don't emit an intersection if it's at the start of a line segment or above us + if (yi <= y) { + if (!det_positive) + return; + QDEBUG() << " ----->>>>>> WRONG ORDER!"; + yi = y; + } + QDEBUG() << " between edges " << e1->edge << "and" << e2->edge << "at point (" + << Q27Dot5ToDouble(yi) << ')'; + + Intersection i1; + i1.y = yi; + i1.edge = e1->edge; + IntersectionLink link1 = intersections.value(i1, emptyLink); + Intersection i2; + i2.y = yi; + i2.edge = e2->edge; + IntersectionLink link2 = intersections.value(i2, emptyLink); + + // new pair of edges + if (link1.next == -1 && link2.next == -1) { + link1.next = link1.prev = i2.edge; + link2.next = link2.prev = i1.edge; + } else if (link1.next == i2.edge || link1.prev == i2.edge + || link2.next == i1.edge || link2.prev == i1.edge) { +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + } else if (link1.next == -1 || link2.next == -1) { + if (link2.next == -1) { + qSwap(i1, i2); + qSwap(link1, link2); + } + Q_ASSERT(link1.next == -1); +#ifdef DEBUG + checkLinkChain(intersections, i2); +#endif + // only i2 in list + link1.next = i2.edge; + link1.prev = link2.prev; + link2.prev = i1.edge; + Intersection other; + other.y = yi; + other.edge = link1.prev; + IntersectionLink link = intersections.value(other, emptyLink); + Q_ASSERT(link.next == i2.edge); + Q_ASSERT(link.prev != -1); + link.next = i1.edge; + intersections.insert(other, link); + } else { + bool connected = edgeInChain(i1, i2.edge); + if (connected) + return; +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); +#endif + // both already in some list. Have to make sure they are connected + // this can be done by cutting open the ring(s) after the two eges and + // connecting them again + Intersection other1; + other1.y = yi; + other1.edge = link1.next; + IntersectionLink linko1 = intersections.value(other1, emptyLink); + Intersection other2; + other2.y = yi; + other2.edge = link2.next; + IntersectionLink linko2 = intersections.value(other2, emptyLink); + + linko1.prev = i2.edge; + link2.next = other1.edge; + + linko2.prev = i1.edge; + link1.next = other2.edge; + intersections.insert(other1, linko1); + intersections.insert(other2, linko2); + } + intersections.insert(i1, link1); + intersections.insert(i2, link2); +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + +} + + +void QTessellatorPrivate::addIntersections() +{ + if (scanline.size) { + QDEBUG() << "INTERSECTIONS"; + // check marked edges for intersections +#ifdef DEBUG + for (int i = 0; i < scanline.size; ++i) { + Edge *e = scanline.edges[i]; + QDEBUG() << " " << i << e->edge << "isect=(" << e->intersect_left << e->intersect_right + << ')'; + } +#endif + + for (int i = 0; i < scanline.size - 1; ++i) { + Edge *e1 = scanline.edges[i]; + Edge *e2 = scanline.edges[i + 1]; + // check for intersection + if (e1->intersect_right || e2->intersect_left) + addIntersection(e1, e2); + } + } +#if 0 + if (intersections.constBegin().key().y == y) { + QDEBUG() << "----------------> intersection on same line"; + scanline.clearMarks(); + scanline.processIntersections(y, &intersections); + goto redo; + } +#endif +} + + +QTessellator::QTessellator() +{ + d = new QTessellatorPrivate; +} + +QTessellator::~QTessellator() +{ + delete d; +} + +void QTessellator::setWinding(bool w) +{ + d->winding = w; +} + + +QRectF QTessellator::tessellate(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + +#ifdef DEBUG + QDEBUG()<< "POINTS:"; + for (int i = 0; i < nPoints; ++i) { + QDEBUG() << points[i]; + } +#endif + + // collect edges and calculate bounds + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + int maxActiveEdges = 0; + QRectF br = d->collectAndSortVertices(points, &maxActiveEdges); + d->cancelCoincidingEdges(); + +#ifdef DEBUG + QDEBUG() << "nPoints = " << nPoints << "using " << d->vertices.nPoints; + QDEBUG()<< "VERTICES:"; + for (int i = 0; i < d->vertices.nPoints; ++i) { + QDEBUG() << " " << i << ": " + << "point=" << d->vertices.position(d->vertices.sorted[i]) + << "flags=" << d->vertices.sorted[i]->flags + << "pos=(" << Q27Dot5ToDouble(d->vertices.sorted[i]->x) << '/' + << Q27Dot5ToDouble(d->vertices.sorted[i]->y) << ')'; + } +#endif + + d->scanline.init(maxActiveEdges); + d->y = INT_MIN/256; + d->currentVertex = 0; + + while (d->currentVertex < d->vertices.nPoints) { + d->scanline.clearMarks(); + + d->y = d->vertices.sorted[d->currentVertex]->y; + if (!d->intersections.isEmpty()) + d->y = qMin(d->y, d->intersections.constBegin().key().y); + + QDEBUG()<< "===== SCANLINE: y =" << Q27Dot5ToDouble(d->y) << " ====="; + + d->scanline.prepareLine(); + d->processIntersections(); + d->removeEdges(); + d->addEdges(); + d->addIntersections(); + d->emitEdges(this); + d->scanline.lineDone(); + +#ifdef DEBUG + QDEBUG()<< "===== edges:"; + for (int i = 0; i < d->scanline.size; ++i) { + QDEBUG() << " " << d->scanline.edges[i]->edge + << "p0= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v0->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v0->y) + << ") p1= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v1->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v1->y) << ')' + << "x=" << Q27Dot5ToDouble(d->scanline.edges[i]->positionAt(d->y)) + << "isLeftOfNext=" + << ((i < d->scanline.size - 1) + ? d->scanline.edges[i]->isLeftOf(*d->scanline.edges[i+1], d->y) + : true); + } +#endif +} + + d->scanline.done(); + d->intersections.clear(); + return br; +} + +// tessellates the given convex polygon +void QTessellator::tessellateConvex(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + for (int i = 0; i < nPoints; ++i) { + d->vertices[i]->x = FloatToQ27Dot5(points[i].x()); + d->vertices[i]->y = FloatToQ27Dot5(points[i].y()); + } + + int left = 0, right = 0; + + int top = 0; + for (int i = 1; i < nPoints; ++i) { + if (d->vertices[i]->y < d->vertices[top]->y) + top = i; + } + + left = (top + nPoints - 1) % nPoints; + right = (top + 1) % nPoints; + + while (d->vertices[left]->x == d->vertices[top]->x && d->vertices[left]->y == d->vertices[top]->y && left != right) + left = (left + nPoints - 1) % nPoints; + + while (d->vertices[right]->x == d->vertices[top]->x && d->vertices[right]->y == d->vertices[top]->y && left != right) + right = (right + 1) % nPoints; + + if (left == right) + return; + + int dir = 1; + + Vertex dLeft = { d->vertices[top]->x - d->vertices[left]->x, + d->vertices[top]->y - d->vertices[left]->y }; + + Vertex dRight = { d->vertices[right]->x - d->vertices[top]->x, + d->vertices[right]->y - d->vertices[top]->y }; + + Q27Dot5 cross = dLeft.x * dRight.y - dLeft.y * dRight.x; + + // flip direction if polygon is clockwise + if (cross < 0 || (cross == 0 && dLeft.x > 0)) { + qSwap(left, right); + dir = -1; + } + + Vertex *lastLeft = d->vertices[top]; + Vertex *lastRight = d->vertices[top]; + + QTessellator::Trapezoid trap; + + while (lastLeft->y == d->vertices[left]->y && left != right) { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + + while (lastRight->y == d->vertices[right]->y && left != right) { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + + while (true) { + trap.top = qMax(lastRight->y, lastLeft->y); + trap.bottom = qMin(d->vertices[left]->y, d->vertices[right]->y); + trap.topLeft = lastLeft; + trap.topRight = lastRight; + trap.bottomLeft = d->vertices[left]; + trap.bottomRight = d->vertices[right]; + + if (trap.bottom > trap.top) + addTrap(trap); + + if (left == right) + break; + + if (d->vertices[right]->y < d->vertices[left]->y) { + do { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + while (lastRight->y == d->vertices[right]->y && left != right); + } else { + do { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + while (lastLeft->y == d->vertices[left]->y && left != right); + } + } +} + +// tessellates the stroke of the line from a_ to b_ with the given width and a flat cap +void QTessellator::tessellateRect(const QPointF &a_, const QPointF &b_, qreal width) +{ + Vertex a = { FloatToQ27Dot5(a_.x()), FloatToQ27Dot5(a_.y()) }; + Vertex b = { FloatToQ27Dot5(b_.x()), FloatToQ27Dot5(b_.y()) }; + + QPointF pa = a_, pb = b_; + + if (a.y > b.y) { + qSwap(a, b); + qSwap(pa, pb); + } + + Vertex delta = { b.x - a.x, b.y - a.y }; + + if (delta.x == 0 && delta.y == 0) + return; + + qreal hw = 0.5 * width; + + if (delta.x == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + Vertex topLeft = { a.x - halfWidth, a.y }; + Vertex topRight = { a.x + halfWidth, a.y }; + Vertex bottomLeft = { a.x - halfWidth, b.y }; + Vertex bottomRight = { a.x + halfWidth, b.y }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else if (delta.y == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + if (a.x > b.x) + qSwap(a.x, b.x); + + Vertex topLeft = { a.x, a.y - halfWidth }; + Vertex topRight = { b.x, a.y - halfWidth }; + Vertex bottomLeft = { a.x, a.y + halfWidth }; + Vertex bottomRight = { b.x, a.y + halfWidth }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else { + QPointF perp(pb.y() - pa.y(), pa.x() - pb.x()); + qreal length = qSqrt(perp.x() * perp.x() + perp.y() * perp.y()); + + if (qFuzzyIsNull(length)) + return; + + // need the half of the width + perp *= hw / length; + + QPointF pta = pa + perp; + QPointF ptb = pa - perp; + QPointF ptc = pb - perp; + QPointF ptd = pb + perp; + + Vertex ta = { FloatToQ27Dot5(pta.x()), FloatToQ27Dot5(pta.y()) }; + Vertex tb = { FloatToQ27Dot5(ptb.x()), FloatToQ27Dot5(ptb.y()) }; + Vertex tc = { FloatToQ27Dot5(ptc.x()), FloatToQ27Dot5(ptc.y()) }; + Vertex td = { FloatToQ27Dot5(ptd.x()), FloatToQ27Dot5(ptd.y()) }; + + if (ta.y < tb.y) { + if (tb.y < td.y) { + QTessellator::Trapezoid top = { ta.y, tb.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { td.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { tb.y, td.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { ta.y, td.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { tb.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + if (tb.y != td.y) { + QTessellator::Trapezoid middle = { td.y, tb.y, &ta, &tb, &td, &tc }; + addTrap(middle); + } + } + } else { + if (ta.y < tc.y) { + QTessellator::Trapezoid top = { tb.y, ta.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { tc.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { ta.y, tc.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { tb.y, tc.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { ta.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + if (ta.y != tc.y) { + QTessellator::Trapezoid middle = { tc.y, ta.y, &tc, &td, &tb, &ta }; + addTrap(middle); + } + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qtessellator_p.h b/src/gui/painting/qtessellator_p.h new file mode 100644 index 0000000000..ad3ef9776f --- /dev/null +++ b/src/gui/painting/qtessellator_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTESSELATOR_P_H +#define QTESSELATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qpoint.h> +#include <qrect.h> + +QT_BEGIN_NAMESPACE + +class QTessellatorPrivate; + +typedef int Q27Dot5; +#define Q27Dot5ToDouble(i) ((i)/32.) +#define FloatToQ27Dot5(i) (int)((i) * 32) +#define IntToQ27Dot5(i) ((i) << 5) +#define Q27Dot5ToXFixed(i) ((i) << 11) +#define Q27Dot5Factor 32 + +class Q_GUI_EXPORT QTessellator { +public: + QTessellator(); + virtual ~QTessellator(); + + QRectF tessellate(const QPointF *points, int nPoints); + void tessellateConvex(const QPointF *points, int nPoints); + void tessellateRect(const QPointF &a, const QPointF &b, qreal width); + + void setWinding(bool w); + + struct Vertex { + Q27Dot5 x; + Q27Dot5 y; + }; + struct Trapezoid { + Q27Dot5 top; + Q27Dot5 bottom; + const Vertex *topLeft; + const Vertex *bottomLeft; + const Vertex *topRight; + const Vertex *bottomRight; + }; + virtual void addTrap(const Trapezoid &trap) = 0; + +private: + friend class QTessellatorPrivate; + QTessellatorPrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qtextureglyphcache.cpp b/src/gui/painting/qtextureglyphcache.cpp new file mode 100644 index 0000000000..53f025f819 --- /dev/null +++ b/src/gui/painting/qtextureglyphcache.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qmath.h> + +#include "qtextureglyphcache_p.h" + +#include "private/qnumeric_p.h" +#include "private/qnativeimage_p.h" +#include "private/qfontengine_ft_p.h" + +QT_BEGIN_NAMESPACE + +// #define CACHE_DEBUG + +// returns the highest number closest to v, which is a power of 2 +// NB! assumes 32 bit ints +static inline int qt_next_power_of_two(int v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + return v; +} + +int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const +{ + // Test 12 different subpixel positions since it factors into 3*4 so it gives + // the coverage we need. + + QList<QImage> images; + for (int i=0; i<12; ++i) { + QImage img = textureMapForGlyph(glyph, QFixed::fromReal(i / 12.0)); + + if (images.isEmpty()) { + QPainterPath path; + QFixedPoint point; + m_current_fontengine->addGlyphsToPath(&glyph, &point, 1, &path, QTextItem::RenderFlags()); + + // Glyph is space, return 0 to indicate that we need to keep trying + if (path.isEmpty()) + break; + + images.append(img); + } else { + bool found = false; + for (int j=0; j<images.size(); ++j) { + if (images.at(j) == img) { + found = true; + break; + } + } + if (!found) + images.append(img); + } + } + + return images.size(); +} + +QFixed QTextureGlyphCache::subPixelPositionForX(QFixed x) const +{ + if (m_subPixelPositionCount <= 1) + return QFixed(); + + QFixed subPixelPosition; + if (x != 0) { + subPixelPosition = x - x.floor(); + QFixed fraction = (subPixelPosition / QFixed::fromReal(1.0 / m_subPixelPositionCount)).floor(); + + // Compensate for precision loss in fixed point to make sure we are always drawing at a subpixel position over + // the lower boundary for the selected rasterization by adding 1/64. + subPixelPosition = fraction / QFixed(m_subPixelPositionCount) + QFixed::fromReal(0.015625); + } + return subPixelPosition; +} + +bool QTextureGlyphCache::populate(QFontEngine *fontEngine, int numGlyphs, const glyph_t *glyphs, + const QFixedPoint *positions) +{ +#ifdef CACHE_DEBUG + printf("Populating with %d glyphs\n", numGlyphs); + qDebug() << " -> current transformation: " << m_transform; +#endif + + m_current_fontengine = fontEngine; + const int margin = glyphMargin(); + const int paddingDoubled = glyphPadding() * 2; + + bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions(); + if (m_subPixelPositionCount == 0) { + if (!supportsSubPixelPositions) { + m_subPixelPositionCount = 1; + } else { +#if !defined(Q_WS_X11) + int i = 0; + while (m_subPixelPositionCount == 0 && i < numGlyphs) + m_subPixelPositionCount = calculateSubPixelPositionCount(glyphs[i++]); +#else + m_subPixelPositionCount = 4; +#endif + } + } + + QHash<GlyphAndSubPixelPosition, Coord> listItemCoordinates; + int rowHeight = 0; + + QFontEngine::GlyphFormat format; + switch (m_type) { + case Raster_A8: format = QFontEngine::Format_A8; break; + case Raster_RGBMask: format = QFontEngine::Format_A32; break; + default: format = QFontEngine::Format_Mono; break; + } + + // check each glyph for its metrics and get the required rowHeight. + for (int i=0; i < numGlyphs; ++i) { + const glyph_t glyph = glyphs[i]; + + QFixed subPixelPosition; + if (supportsSubPixelPositions) { + QFixed x = positions != 0 ? positions[i].x : QFixed(); + subPixelPosition = subPixelPositionForX(x); + } + + if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) + continue; + if (listItemCoordinates.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) + continue; + glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, m_transform, format); + +#ifdef CACHE_DEBUG + printf("(%4x): w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n", + glyph, + metrics.width.toReal(), + metrics.height.toReal(), + metrics.xoff.toReal(), + metrics.yoff.toReal(), + metrics.x.toReal(), + metrics.y.toReal()); +#endif + GlyphAndSubPixelPosition key(glyph, subPixelPosition); + int glyph_width = metrics.width.ceil().toInt(); + int glyph_height = metrics.height.ceil().toInt(); + if (glyph_height == 0 || glyph_width == 0) { + // Avoid multiple calls to boundingBox() for non-printable characters + Coord c = { 0, 0, 0, 0, 0, 0 }; + coords.insert(key, c); + continue; + } + glyph_width += margin * 2 + 4; + glyph_height += margin * 2 + 4; + // align to 8-bit boundary + if (m_type == QFontEngineGlyphCache::Raster_Mono) + glyph_width = (glyph_width+7)&~7; + + Coord c = { 0, 0, // will be filled in later + glyph_width, + glyph_height, // texture coords + metrics.x.round().truncate(), + -metrics.y.truncate() }; // baseline for horizontal scripts + + listItemCoordinates.insert(key, c); + rowHeight = qMax(rowHeight, glyph_height); + } + if (listItemCoordinates.isEmpty()) + return true; + + rowHeight += margin * 2 + paddingDoubled; + + if (m_w == 0) { + if (fontEngine->maxCharWidth() <= QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH) + m_w = QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; + else + m_w = qt_next_power_of_two(fontEngine->maxCharWidth()); + } + + // now actually use the coords and paint the wanted glyps into cache. + QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = listItemCoordinates.begin(); + int requiredWidth = m_w; + while (iter != listItemCoordinates.end()) { + Coord c = iter.value(); + + m_currentRowHeight = qMax(m_currentRowHeight, c.h + margin * 2); + + if (m_cx + c.w > requiredWidth) { + int new_width = requiredWidth*2; + while (new_width < m_cx + c.w) + new_width *= 2; + if (new_width <= maxTextureWidth()) { + requiredWidth = new_width; + } else { + // no room on the current line, start new glyph strip + m_cx = 0; + m_cy += m_currentRowHeight + paddingDoubled; + m_currentRowHeight = c.h + margin * 2; // New row + } + } + + if (maxTextureHeight() > 0 && m_cy + c.h > maxTextureHeight()) { + // We can't make a cache of the required size, so we bail out + return false; + } + + c.x = m_cx; + c.y = m_cy; + + coords.insert(iter.key(), c); + m_pendingGlyphs.insert(iter.key(), c); + + m_cx += c.w + paddingDoubled; + ++iter; + } + return true; + +} + +void QTextureGlyphCache::fillInPendingGlyphs() +{ + if (m_pendingGlyphs.isEmpty()) + return; + + int requiredHeight = m_h; + int requiredWidth = m_w; // Use a minimum size to avoid a lot of initial reallocations + { + QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); + while (iter != m_pendingGlyphs.end()) { + Coord c = iter.value(); + requiredHeight = qMax(requiredHeight, c.y + c.h); + requiredWidth = qMax(requiredWidth, c.x + c.w); + ++iter; + } + } + + if (isNull() || requiredHeight > m_h || requiredWidth > m_w) { + if (isNull()) + createCache(qt_next_power_of_two(requiredWidth), qt_next_power_of_two(requiredHeight)); + else + resizeCache(qt_next_power_of_two(requiredWidth), qt_next_power_of_two(requiredHeight)); + } + + { + QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); + while (iter != m_pendingGlyphs.end()) { + GlyphAndSubPixelPosition key = iter.key(); + fillTexture(iter.value(), key.glyph, key.subPixelPosition); + + ++iter; + } + } + + m_pendingGlyphs.clear(); +} + +QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, QFixed subPixelPosition) const +{ +#if defined(Q_WS_X11) + if (m_transform.type() > QTransform::TxTranslate) { + QFontEngineFT::GlyphFormat format = QFontEngineFT::Format_None; + QImage::Format imageFormat = QImage::Format_Invalid; + switch (m_type) { + case Raster_RGBMask: + format = QFontEngineFT::Format_A32; + imageFormat = QImage::Format_RGB32; + break; + case Raster_A8: + format = QFontEngineFT::Format_A8; + imageFormat = QImage::Format_Indexed8; + break; + case Raster_Mono: + format = QFontEngineFT::Format_Mono; + imageFormat = QImage::Format_Mono; + break; + }; + + QFontEngineFT *ft = static_cast<QFontEngineFT*> (m_current_fontengine); + QFontEngineFT::QGlyphSet *gset = ft->loadTransformedGlyphSet(m_transform); + QFixedPoint positions[1]; + positions[0].x = subPixelPosition; + + if (gset && ft->loadGlyphs(gset, &g, 1, positions, format)) { + QFontEngineFT::Glyph *glyph = gset->getGlyph(g, subPixelPosition); + const int bytesPerLine = (format == QFontEngineFT::Format_Mono ? ((glyph->width + 31) & ~31) >> 3 + : (glyph->width + 3) & ~3); + return QImage(glyph->data, glyph->width, glyph->height, bytesPerLine, imageFormat); + } + } else +#endif + if (m_type == QFontEngineGlyphCache::Raster_RGBMask) + return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, glyphMargin(), m_transform); + else + return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, m_transform); + + return QImage(); +} + +/************************************************************************ + * QImageTextureGlyphCache + */ + +void QImageTextureGlyphCache::resizeTextureData(int width, int height) +{ + m_image = m_image.copy(0, 0, width, height); +} + +void QImageTextureGlyphCache::createTextureData(int width, int height) +{ + switch (m_type) { + case QFontEngineGlyphCache::Raster_Mono: + m_image = QImage(width, height, QImage::Format_Mono); + break; + case QFontEngineGlyphCache::Raster_A8: { + m_image = QImage(width, height, QImage::Format_Indexed8); + m_image.fill(0); + QVector<QRgb> colors(256); + QRgb *it = colors.data(); + for (int i=0; i<256; ++i, ++it) + *it = 0xff000000 | i | (i<<8) | (i<<16); + m_image.setColorTable(colors); + break; } + case QFontEngineGlyphCache::Raster_RGBMask: + m_image = QImage(width, height, QImage::Format_RGB32); + break; + } +} + +int QImageTextureGlyphCache::glyphMargin() const +{ +#if (defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA)) || defined(Q_WS_X11) + return 0; +#else + return m_type == QFontEngineGlyphCache::Raster_RGBMask ? 2 : 0; +#endif +} + +void QImageTextureGlyphCache::fillTexture(const Coord &c, glyph_t g, QFixed subPixelPosition) +{ + QImage mask = textureMapForGlyph(g, subPixelPosition); + +#ifdef CACHE_DEBUG + printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height()); + if (mask.width() > c.w || mask.height() > c.h) { + printf(" ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n", mask.width(), mask.height(), c.w,c.h); + return; + } +#endif + + if (m_type == QFontEngineGlyphCache::Raster_RGBMask) { + QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()), + qMax(mask.width(), c.w), qMax(mask.height(), c.h), m_image.bytesPerLine(), + m_image.format()); + QPainter p(&ref); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, c.w, c.h, QColor(0,0,0,0)); // TODO optimize this + p.drawImage(0, 0, mask); + p.end(); + } else if (m_type == QFontEngineGlyphCache::Raster_Mono) { + if (mask.depth() > 1) { + // TODO optimize this + mask = mask.alphaChannel(); + mask.invertPixels(); + mask = mask.convertToFormat(QImage::Format_Mono); + } + + int mw = qMin(mask.width(), c.w); + int mh = qMin(mask.height(), c.h); + uchar *d = m_image.bits(); + int dbpl = m_image.bytesPerLine(); + + for (int y = 0; y < c.h; ++y) { + uchar *dest = d + (c.y + y) *dbpl + c.x/8; + + if (y < mh) { + uchar *src = mask.scanLine(y); + for (int x = 0; x < c.w/8; ++x) { + if (x < (mw+7)/8) + dest[x] = src[x]; + else + dest[x] = 0; + } + } else { + for (int x = 0; x < c.w/8; ++x) + dest[x] = 0; + } + } + } else { // A8 + int mw = qMin(mask.width(), c.w); + int mh = qMin(mask.height(), c.h); + uchar *d = m_image.bits(); + int dbpl = m_image.bytesPerLine(); + + if (mask.depth() == 1) { + for (int y = 0; y < c.h; ++y) { + uchar *dest = d + (c.y + y) *dbpl + c.x; + if (y < mh) { + uchar *src = (uchar *) mask.scanLine(y); + for (int x = 0; x < c.w; ++x) { + if (x < mw) + dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0; + } + } + } + } else if (mask.depth() == 8) { + for (int y = 0; y < c.h; ++y) { + uchar *dest = d + (c.y + y) *dbpl + c.x; + if (y < mh) { + uchar *src = (uchar *) mask.scanLine(y); + for (int x = 0; x < c.w; ++x) { + if (x < mw) + dest[x] = src[x]; + } + } + } + } + } + +#ifdef CACHE_DEBUG +// QPainter p(&m_image); +// p.drawLine( + QPoint base(c.x + glyphMargin(), c.y + glyphMargin() + c.baseLineY-1); + if (m_image.rect().contains(base)) + m_image.setPixel(base, 255); + m_image.save(QString::fromLatin1("cache-%1.png").arg(qint64(this))); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qtextureglyphcache_p.h b/src/gui/painting/qtextureglyphcache_p.h new file mode 100644 index 0000000000..922e2901bd --- /dev/null +++ b/src/gui/painting/qtextureglyphcache_p.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTUREGLYPHCACHE_P_H +#define QTEXTUREGLYPHCACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qhash.h> +#include <qimage.h> +#include <qobject.h> +#include <qtransform.h> + +#include <private/qfontengineglyphcache_p.h> + +#if defined(Q_OS_VXWORKS) && defined(m_type) +# undef m_type +#endif + +#ifndef QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH +#define QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH 256 +#endif + +struct glyph_metrics_t; +typedef unsigned int glyph_t; + + +QT_BEGIN_NAMESPACE + +class QTextItemInt; + +class Q_GUI_EXPORT QTextureGlyphCache : public QFontEngineGlyphCache +{ +public: + QTextureGlyphCache(QFontEngineGlyphCache::Type type, const QTransform &matrix) + : QFontEngineGlyphCache(matrix, type), m_current_fontengine(0), + m_w(0), m_h(0), m_cx(0), m_cy(0), m_currentRowHeight(0), m_subPixelPositionCount(0) + { } + + virtual ~QTextureGlyphCache() { } + + struct GlyphAndSubPixelPosition + { + GlyphAndSubPixelPosition(glyph_t g, QFixed spp) : glyph(g), subPixelPosition(spp) {} + + bool operator==(const GlyphAndSubPixelPosition &other) const + { + return glyph == other.glyph && subPixelPosition == other.subPixelPosition; + } + + glyph_t glyph; + QFixed subPixelPosition; + }; + + struct Coord { + int x; + int y; + int w; + int h; + + int baseLineX; + int baseLineY; + + bool isNull() const + { + return w == 0 || h == 0; + } + }; + + bool populate(QFontEngine *fontEngine, int numGlyphs, const glyph_t *glyphs, + const QFixedPoint *positions); + void fillInPendingGlyphs(); + + virtual void createTextureData(int width, int height) = 0; + virtual void resizeTextureData(int width, int height) = 0; + virtual int glyphMargin() const { return 0; } + virtual int glyphPadding() const { return 0; } + + virtual void fillTexture(const Coord &coord, glyph_t glyph, QFixed subPixelPosition) = 0; + + inline void createCache(int width, int height) { + m_w = width; + m_h = height; + createTextureData(width, height); + } + + inline void resizeCache(int width, int height) + { + resizeTextureData(width, height); + m_w = width; + m_h = height; + } + + inline bool isNull() const { return m_h == 0; } + + QHash<GlyphAndSubPixelPosition, Coord> coords; + virtual int maxTextureWidth() const { return QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; } + virtual int maxTextureHeight() const { return -1; } + + QImage textureMapForGlyph(glyph_t g, QFixed subPixelPosition) const; + + QFixed subPixelPositionForX(QFixed x) const; + +protected: + int calculateSubPixelPositionCount(glyph_t) const; + + QFontEngine *m_current_fontengine; + QHash<GlyphAndSubPixelPosition, Coord> m_pendingGlyphs; + + int m_w; // image width + int m_h; // image height + int m_cx; // current x + int m_cy; // current y + int m_currentRowHeight; // Height of last row + int m_subPixelPositionCount; // Number of positions within a single pixel for this cache +}; + +inline uint qHash(const QTextureGlyphCache::GlyphAndSubPixelPosition &g) +{ + return (g.glyph << 8) | (g.subPixelPosition * 10).round().toInt(); +} + + +class Q_GUI_EXPORT QImageTextureGlyphCache : public QTextureGlyphCache +{ +public: + QImageTextureGlyphCache(QFontEngineGlyphCache::Type type, const QTransform &matrix) + : QTextureGlyphCache(type, matrix) { } + virtual int glyphMargin() const; + virtual void createTextureData(int width, int height); + virtual void resizeTextureData(int width, int height); + virtual void fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition); + + inline const QImage &image() const { return m_image; } + +private: + QImage m_image; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/painting/qtransform.cpp b/src/gui/painting/qtransform.cpp new file mode 100644 index 0000000000..7d11e2fd07 --- /dev/null +++ b/src/gui/painting/qtransform.cpp @@ -0,0 +1,2301 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qtransform.h" + +#include "qdatastream.h" +#include "qdebug.h" +#include "qmatrix.h" +#include "qregion.h" +#include "qpainterpath.h" +#include "qvariant.h" +#include <qmath.h> +#include <qnumeric.h> + +#include <private/qbezier_p.h> + +QT_BEGIN_NAMESPACE + +#define Q_NEAR_CLIP (sizeof(qreal) == sizeof(double) ? 0.000001 : 0.0001) + +#ifdef MAP +# undef MAP +#endif +#define MAP(x, y, nx, ny) \ + do { \ + qreal FX_ = x; \ + qreal FY_ = y; \ + switch(t) { \ + case TxNone: \ + nx = FX_; \ + ny = FY_; \ + break; \ + case TxTranslate: \ + nx = FX_ + affine._dx; \ + ny = FY_ + affine._dy; \ + break; \ + case TxScale: \ + nx = affine._m11 * FX_ + affine._dx; \ + ny = affine._m22 * FY_ + affine._dy; \ + break; \ + case TxRotate: \ + case TxShear: \ + case TxProject: \ + nx = affine._m11 * FX_ + affine._m21 * FY_ + affine._dx; \ + ny = affine._m12 * FX_ + affine._m22 * FY_ + affine._dy; \ + if (t == TxProject) { \ + qreal w = (m_13 * FX_ + m_23 * FY_ + m_33); \ + if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP); \ + w = 1./w; \ + nx *= w; \ + ny *= w; \ + } \ + } \ + } while (0) + +/*! + \class QTransform + \brief The QTransform class specifies 2D transformations of a coordinate system. + \since 4.3 + \ingroup painting + + A transformation specifies how to translate, scale, shear, rotate + or project the coordinate system, and is typically used when + rendering graphics. + + QTransform differs from QMatrix in that it is a true 3x3 matrix, + allowing perspective transformations. QTransform's toAffine() + method allows casting QTransform to QMatrix. If a perspective + transformation has been specified on the matrix, then the + conversion will cause loss of data. + + QTransform is the recommended transformation class in Qt. + + A QTransform object can be built using the setMatrix(), scale(), + rotate(), translate() and shear() functions. Alternatively, it + can be built by applying \l {QTransform#Basic Matrix + Operations}{basic matrix operations}. The matrix can also be + defined when constructed, and it can be reset to the identity + matrix (the default) using the reset() function. + + The QTransform class supports mapping of graphic primitives: A given + point, line, polygon, region, or painter path can be mapped to the + coordinate system defined by \e this matrix using the map() + function. In case of a rectangle, its coordinates can be + transformed using the mapRect() function. A rectangle can also be + transformed into a \e polygon (mapped to the coordinate system + defined by \e this matrix), using the mapToPolygon() function. + + QTransform provides the isIdentity() function which returns true if + the matrix is the identity matrix, and the isInvertible() function + which returns true if the matrix is non-singular (i.e. AB = BA = + I). The inverted() function returns an inverted copy of \e this + matrix if it is invertible (otherwise it returns the identity + matrix), and adjoint() returns the matrix's classical adjoint. + In addition, QTransform provides the determinant() function which + returns the matrix's determinant. + + Finally, the QTransform class supports matrix multiplication, addition + and subtraction, and objects of the class can be streamed as well + as compared. + + \tableofcontents + + \section1 Rendering Graphics + + When rendering graphics, the matrix defines the transformations + but the actual transformation is performed by the drawing routines + in QPainter. + + By default, QPainter operates on the associated device's own + coordinate system. The standard coordinate system of a + QPaintDevice has its origin located at the top-left position. The + \e x values increase to the right; \e y values increase + downward. For a complete description, see the \l {Coordinate + System} {coordinate system} documentation. + + QPainter has functions to translate, scale, shear and rotate the + coordinate system without using a QTransform. For example: + + \table 100% + \row + \o \inlineimage qtransform-simpletransformation.png + \o + \snippet doc/src/snippets/transform/main.cpp 0 + \endtable + + Although these functions are very convenient, it can be more + efficient to build a QTransform and call QPainter::setTransform() if you + want to perform more than a single transform operation. For + example: + + \table 100% + \row + \o \inlineimage qtransform-combinedtransformation.png + \o + \snippet doc/src/snippets/transform/main.cpp 1 + \endtable + + \section1 Basic Matrix Operations + + \image qtransform-representation.png + + A QTransform object contains a 3 x 3 matrix. The \c m31 (\c dx) and + \c m32 (\c dy) elements specify horizontal and vertical translation. + The \c m11 and \c m22 elements specify horizontal and vertical scaling. + The \c m21 and \c m12 elements specify horizontal and vertical \e shearing. + And finally, the \c m13 and \c m23 elements specify horizontal and vertical + projection, with \c m33 as an additional projection factor. + + QTransform transforms a point in the plane to another point using the + following formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qtransform.cpp 0 + + The point \e (x, y) is the original point, and \e (x', y') is the + transformed point. \e (x', y') can be transformed back to \e (x, + y) by performing the same operation on the inverted() matrix. + + The various matrix elements can be set when constructing the + matrix, or by using the setMatrix() function later on. They can also + be manipulated using the translate(), rotate(), scale() and + shear() convenience functions. The currently set values can be + retrieved using the m11(), m12(), m13(), m21(), m22(), m23(), + m31(), m32(), m33(), dx() and dy() functions. + + Translation is the simplest transformation. Setting \c dx and \c + dy will move the coordinate system \c dx units along the X axis + and \c dy units along the Y axis. Scaling can be done by setting + \c m11 and \c m22. For example, setting \c m11 to 2 and \c m22 to + 1.5 will double the height and increase the width by 50%. The + identity matrix has \c m11, \c m22, and \c m33 set to 1 (all others are set + to 0) mapping a point to itself. Shearing is controlled by \c m12 + and \c m21. Setting these elements to values different from zero + will twist the coordinate system. Rotation is achieved by + setting both the shearing factors and the scaling factors. Perspective + transformation is achieved by setting both the projection factors and + the scaling factors. + + Here's the combined transformations example using basic matrix + operations: + + \table 100% + \row + \o \inlineimage qtransform-combinedtransformation2.png + \o + \snippet doc/src/snippets/transform/main.cpp 2 + \endtable + + \sa QPainter, {Coordinate System}, {demos/affine}{Affine + Transformations Demo}, {Transformations Example} +*/ + +/*! + \enum QTransform::TransformationType + + \value TxNone + \value TxTranslate + \value TxScale + \value TxRotate + \value TxShear + \value TxProject +*/ + +/*! + \fn QTransform::QTransform(Qt::Initialization) + \internal +*/ + +/*! + Constructs an identity matrix. + + All elements are set to zero except \c m11 and \c m22 (specifying + the scale) and \c m13 which are set to 1. + + \sa reset() +*/ +QTransform::QTransform() + : affine(true) + , m_13(0), m_23(0), m_33(1) + , m_type(TxNone) + , m_dirty(TxNone) +{ +} + +/*! + \fn QTransform::QTransform(qreal m11, qreal m12, qreal m13, qreal m21, qreal m22, qreal m23, qreal m31, qreal m32, qreal m33) + + Constructs a matrix with the elements, \a m11, \a m12, \a m13, + \a m21, \a m22, \a m23, \a m31, \a m32, \a m33. + + \sa setMatrix() +*/ +QTransform::QTransform(qreal h11, qreal h12, qreal h13, + qreal h21, qreal h22, qreal h23, + qreal h31, qreal h32, qreal h33) + : affine(h11, h12, h21, h22, h31, h32, true) + , m_13(h13), m_23(h23), m_33(h33) + , m_type(TxNone) + , m_dirty(TxProject) +{ +} + +/*! + \fn QTransform::QTransform(qreal m11, qreal m12, qreal m21, qreal m22, qreal dx, qreal dy) + + Constructs a matrix with the elements, \a m11, \a m12, \a m21, \a m22, \a dx and \a dy. + + \sa setMatrix() +*/ +QTransform::QTransform(qreal h11, qreal h12, qreal h21, + qreal h22, qreal dx, qreal dy) + : affine(h11, h12, h21, h22, dx, dy, true) + , m_13(0), m_23(0), m_33(1) + , m_type(TxNone) + , m_dirty(TxShear) +{ +} + +/*! + \fn QTransform::QTransform(const QMatrix &matrix) + + Constructs a matrix that is a copy of the given \a matrix. + Note that the \c m13, \c m23, and \c m33 elements are set to 0, 0, + and 1 respectively. + */ +QTransform::QTransform(const QMatrix &mtx) + : affine(mtx._m11, mtx._m12, mtx._m21, mtx._m22, mtx._dx, mtx._dy, true), + m_13(0), m_23(0), m_33(1) + , m_type(TxNone) + , m_dirty(TxShear) +{ +} + +/*! + Returns the adjoint of this matrix. +*/ +QTransform QTransform::adjoint() const +{ + qreal h11, h12, h13, + h21, h22, h23, + h31, h32, h33; + h11 = affine._m22*m_33 - m_23*affine._dy; + h21 = m_23*affine._dx - affine._m21*m_33; + h31 = affine._m21*affine._dy - affine._m22*affine._dx; + h12 = m_13*affine._dy - affine._m12*m_33; + h22 = affine._m11*m_33 - m_13*affine._dx; + h32 = affine._m12*affine._dx - affine._m11*affine._dy; + h13 = affine._m12*m_23 - m_13*affine._m22; + h23 = m_13*affine._m21 - affine._m11*m_23; + h33 = affine._m11*affine._m22 - affine._m12*affine._m21; + + return QTransform(h11, h12, h13, + h21, h22, h23, + h31, h32, h33, true); +} + +/*! + Returns the transpose of this matrix. +*/ +QTransform QTransform::transposed() const +{ + QTransform t(affine._m11, affine._m21, affine._dx, + affine._m12, affine._m22, affine._dy, + m_13, m_23, m_33, true); + t.m_type = m_type; + t.m_dirty = m_dirty; + return t; +} + +/*! + Returns an inverted copy of this matrix. + + If the matrix is singular (not invertible), the returned matrix is + the identity matrix. If \a invertible is valid (i.e. not 0), its + value is set to true if the matrix is invertible, otherwise it is + set to false. + + \sa isInvertible() +*/ +QTransform QTransform::inverted(bool *invertible) const +{ + QTransform invert(true); + bool inv = true; + + switch(inline_type()) { + case TxNone: + break; + case TxTranslate: + invert.affine._dx = -affine._dx; + invert.affine._dy = -affine._dy; + break; + case TxScale: + inv = !qFuzzyIsNull(affine._m11); + inv &= !qFuzzyIsNull(affine._m22); + if (inv) { + invert.affine._m11 = 1. / affine._m11; + invert.affine._m22 = 1. / affine._m22; + invert.affine._dx = -affine._dx * invert.affine._m11; + invert.affine._dy = -affine._dy * invert.affine._m22; + } + break; + case TxRotate: + case TxShear: + invert.affine = affine.inverted(&inv); + break; + default: + // general case + qreal det = determinant(); + inv = !qFuzzyIsNull(det); + if (inv) + invert = adjoint() / det; + break; + } + + if (invertible) + *invertible = inv; + + if (inv) { + // inverting doesn't change the type + invert.m_type = m_type; + invert.m_dirty = m_dirty; + } + + return invert; +} + +/*! + Moves the coordinate system \a dx along the x axis and \a dy along + the y axis, and returns a reference to the matrix. + + \sa setMatrix() +*/ +QTransform &QTransform::translate(qreal dx, qreal dy) +{ + if (dx == 0 && dy == 0) + return *this; +#ifndef QT_NO_DEBUG + if (qIsNaN(dx) | qIsNaN(dy)) { + qWarning() << "QTransform::translate with NaN called"; + return *this; + } +#endif + + switch(inline_type()) { + case TxNone: + affine._dx = dx; + affine._dy = dy; + break; + case TxTranslate: + affine._dx += dx; + affine._dy += dy; + break; + case TxScale: + affine._dx += dx*affine._m11; + affine._dy += dy*affine._m22; + break; + case TxProject: + m_33 += dx*m_13 + dy*m_23; + // Fall through + case TxShear: + case TxRotate: + affine._dx += dx*affine._m11 + dy*affine._m21; + affine._dy += dy*affine._m22 + dx*affine._m12; + break; + } + if (m_dirty < TxTranslate) + m_dirty = TxTranslate; + return *this; +} + +/*! + Creates a matrix which corresponds to a translation of \a dx along + the x axis and \a dy along the y axis. This is the same as + QTransform().translate(dx, dy) but slightly faster. + + \since 4.5 +*/ +QTransform QTransform::fromTranslate(qreal dx, qreal dy) +{ +#ifndef QT_NO_DEBUG + if (qIsNaN(dx) | qIsNaN(dy)) { + qWarning() << "QTransform::fromTranslate with NaN called"; + return QTransform(); +} +#endif + QTransform transform(1, 0, 0, 0, 1, 0, dx, dy, 1, true); + if (dx == 0 && dy == 0) + transform.m_type = TxNone; + else + transform.m_type = TxTranslate; + transform.m_dirty = TxNone; + return transform; +} + +/*! + Scales the coordinate system by \a sx horizontally and \a sy + vertically, and returns a reference to the matrix. + + \sa setMatrix() +*/ +QTransform & QTransform::scale(qreal sx, qreal sy) +{ + if (sx == 1 && sy == 1) + return *this; +#ifndef QT_NO_DEBUG + if (qIsNaN(sx) | qIsNaN(sy)) { + qWarning() << "QTransform::scale with NaN called"; + return *this; + } +#endif + + switch(inline_type()) { + case TxNone: + case TxTranslate: + affine._m11 = sx; + affine._m22 = sy; + break; + case TxProject: + m_13 *= sx; + m_23 *= sy; + // fall through + case TxRotate: + case TxShear: + affine._m12 *= sx; + affine._m21 *= sy; + // fall through + case TxScale: + affine._m11 *= sx; + affine._m22 *= sy; + break; + } + if (m_dirty < TxScale) + m_dirty = TxScale; + return *this; +} + +/*! + Creates a matrix which corresponds to a scaling of + \a sx horizontally and \a sy vertically. + This is the same as QTransform().scale(sx, sy) but slightly faster. + + \since 4.5 +*/ +QTransform QTransform::fromScale(qreal sx, qreal sy) +{ +#ifndef QT_NO_DEBUG + if (qIsNaN(sx) | qIsNaN(sy)) { + qWarning() << "QTransform::fromScale with NaN called"; + return QTransform(); +} +#endif + QTransform transform(sx, 0, 0, 0, sy, 0, 0, 0, 1, true); + if (sx == 1. && sy == 1.) + transform.m_type = TxNone; + else + transform.m_type = TxScale; + transform.m_dirty = TxNone; + return transform; +} + +/*! + Shears the coordinate system by \a sh horizontally and \a sv + vertically, and returns a reference to the matrix. + + \sa setMatrix() +*/ +QTransform & QTransform::shear(qreal sh, qreal sv) +{ + if (sh == 0 && sv == 0) + return *this; +#ifndef QT_NO_DEBUG + if (qIsNaN(sh) | qIsNaN(sv)) { + qWarning() << "QTransform::shear with NaN called"; + return *this; + } +#endif + + switch(inline_type()) { + case TxNone: + case TxTranslate: + affine._m12 = sv; + affine._m21 = sh; + break; + case TxScale: + affine._m12 = sv*affine._m22; + affine._m21 = sh*affine._m11; + break; + case TxProject: { + qreal tm13 = sv*m_23; + qreal tm23 = sh*m_13; + m_13 += tm13; + m_23 += tm23; + } + // fall through + case TxRotate: + case TxShear: { + qreal tm11 = sv*affine._m21; + qreal tm22 = sh*affine._m12; + qreal tm12 = sv*affine._m22; + qreal tm21 = sh*affine._m11; + affine._m11 += tm11; affine._m12 += tm12; + affine._m21 += tm21; affine._m22 += tm22; + break; + } + } + if (m_dirty < TxShear) + m_dirty = TxShear; + return *this; +} + +const qreal deg2rad = qreal(0.017453292519943295769); // pi/180 +const qreal inv_dist_to_plane = 1. / 1024.; + +/*! + \fn QTransform &QTransform::rotate(qreal angle, Qt::Axis axis) + + Rotates the coordinate system counterclockwise by the given \a angle + about the specified \a axis and returns a reference to the matrix. + + Note that if you apply a QTransform to a point defined in widget + coordinates, the direction of the rotation will be clockwise + because the y-axis points downwards. + + The angle is specified in degrees. + + \sa setMatrix() +*/ +QTransform & QTransform::rotate(qreal a, Qt::Axis axis) +{ + if (a == 0) + return *this; +#ifndef QT_NO_DEBUG + if (qIsNaN(a)) { + qWarning() << "QTransform::rotate with NaN called"; + return *this; + } +#endif + + qreal sina = 0; + qreal cosa = 0; + if (a == 90. || a == -270.) + sina = 1.; + else if (a == 270. || a == -90.) + sina = -1.; + else if (a == 180.) + cosa = -1.; + else{ + qreal b = deg2rad*a; // convert to radians + sina = qSin(b); // fast and convenient + cosa = qCos(b); + } + + if (axis == Qt::ZAxis) { + switch(inline_type()) { + case TxNone: + case TxTranslate: + affine._m11 = cosa; + affine._m12 = sina; + affine._m21 = -sina; + affine._m22 = cosa; + break; + case TxScale: { + qreal tm11 = cosa*affine._m11; + qreal tm12 = sina*affine._m22; + qreal tm21 = -sina*affine._m11; + qreal tm22 = cosa*affine._m22; + affine._m11 = tm11; affine._m12 = tm12; + affine._m21 = tm21; affine._m22 = tm22; + break; + } + case TxProject: { + qreal tm13 = cosa*m_13 + sina*m_23; + qreal tm23 = -sina*m_13 + cosa*m_23; + m_13 = tm13; + m_23 = tm23; + // fall through + } + case TxRotate: + case TxShear: { + qreal tm11 = cosa*affine._m11 + sina*affine._m21; + qreal tm12 = cosa*affine._m12 + sina*affine._m22; + qreal tm21 = -sina*affine._m11 + cosa*affine._m21; + qreal tm22 = -sina*affine._m12 + cosa*affine._m22; + affine._m11 = tm11; affine._m12 = tm12; + affine._m21 = tm21; affine._m22 = tm22; + break; + } + } + if (m_dirty < TxRotate) + m_dirty = TxRotate; + } else { + QTransform result; + if (axis == Qt::YAxis) { + result.affine._m11 = cosa; + result.m_13 = -sina * inv_dist_to_plane; + } else { + result.affine._m22 = cosa; + result.m_23 = -sina * inv_dist_to_plane; + } + result.m_type = TxProject; + *this = result * *this; + } + + return *this; +} + +/*! + \fn QTransform & QTransform::rotateRadians(qreal angle, Qt::Axis axis) + + Rotates the coordinate system counterclockwise by the given \a angle + about the specified \a axis and returns a reference to the matrix. + + Note that if you apply a QTransform to a point defined in widget + coordinates, the direction of the rotation will be clockwise + because the y-axis points downwards. + + The angle is specified in radians. + + \sa setMatrix() +*/ +QTransform & QTransform::rotateRadians(qreal a, Qt::Axis axis) +{ +#ifndef QT_NO_DEBUG + if (qIsNaN(a)) { + qWarning() << "QTransform::rotateRadians with NaN called"; + return *this; + } +#endif + qreal sina = qSin(a); + qreal cosa = qCos(a); + + if (axis == Qt::ZAxis) { + switch(inline_type()) { + case TxNone: + case TxTranslate: + affine._m11 = cosa; + affine._m12 = sina; + affine._m21 = -sina; + affine._m22 = cosa; + break; + case TxScale: { + qreal tm11 = cosa*affine._m11; + qreal tm12 = sina*affine._m22; + qreal tm21 = -sina*affine._m11; + qreal tm22 = cosa*affine._m22; + affine._m11 = tm11; affine._m12 = tm12; + affine._m21 = tm21; affine._m22 = tm22; + break; + } + case TxProject: { + qreal tm13 = cosa*m_13 + sina*m_23; + qreal tm23 = -sina*m_13 + cosa*m_23; + m_13 = tm13; + m_23 = tm23; + // fall through + } + case TxRotate: + case TxShear: { + qreal tm11 = cosa*affine._m11 + sina*affine._m21; + qreal tm12 = cosa*affine._m12 + sina*affine._m22; + qreal tm21 = -sina*affine._m11 + cosa*affine._m21; + qreal tm22 = -sina*affine._m12 + cosa*affine._m22; + affine._m11 = tm11; affine._m12 = tm12; + affine._m21 = tm21; affine._m22 = tm22; + break; + } + } + if (m_dirty < TxRotate) + m_dirty = TxRotate; + } else { + QTransform result; + if (axis == Qt::YAxis) { + result.affine._m11 = cosa; + result.m_13 = -sina * inv_dist_to_plane; + } else { + result.affine._m22 = cosa; + result.m_23 = -sina * inv_dist_to_plane; + } + result.m_type = TxProject; + *this = result * *this; + } + return *this; +} + +/*! + \fn bool QTransform::operator==(const QTransform &matrix) const + Returns true if this matrix is equal to the given \a matrix, + otherwise returns false. +*/ +bool QTransform::operator==(const QTransform &o) const +{ + return affine._m11 == o.affine._m11 && + affine._m12 == o.affine._m12 && + affine._m21 == o.affine._m21 && + affine._m22 == o.affine._m22 && + affine._dx == o.affine._dx && + affine._dy == o.affine._dy && + m_13 == o.m_13 && + m_23 == o.m_23 && + m_33 == o.m_33; +} + +/*! + \fn bool QTransform::operator!=(const QTransform &matrix) const + Returns true if this matrix is not equal to the given \a matrix, + otherwise returns false. +*/ +bool QTransform::operator!=(const QTransform &o) const +{ + return !operator==(o); +} + +/*! + \fn QTransform & QTransform::operator*=(const QTransform &matrix) + \overload + + Returns the result of multiplying this matrix by the given \a + matrix. +*/ +QTransform & QTransform::operator*=(const QTransform &o) +{ + const TransformationType otherType = o.inline_type(); + if (otherType == TxNone) + return *this; + + const TransformationType thisType = inline_type(); + if (thisType == TxNone) + return operator=(o); + + TransformationType t = qMax(thisType, otherType); + switch(t) { + case TxNone: + break; + case TxTranslate: + affine._dx += o.affine._dx; + affine._dy += o.affine._dy; + break; + case TxScale: + { + qreal m11 = affine._m11*o.affine._m11; + qreal m22 = affine._m22*o.affine._m22; + + qreal m31 = affine._dx*o.affine._m11 + o.affine._dx; + qreal m32 = affine._dy*o.affine._m22 + o.affine._dy; + + affine._m11 = m11; + affine._m22 = m22; + affine._dx = m31; affine._dy = m32; + break; + } + case TxRotate: + case TxShear: + { + qreal m11 = affine._m11*o.affine._m11 + affine._m12*o.affine._m21; + qreal m12 = affine._m11*o.affine._m12 + affine._m12*o.affine._m22; + + qreal m21 = affine._m21*o.affine._m11 + affine._m22*o.affine._m21; + qreal m22 = affine._m21*o.affine._m12 + affine._m22*o.affine._m22; + + qreal m31 = affine._dx*o.affine._m11 + affine._dy*o.affine._m21 + o.affine._dx; + qreal m32 = affine._dx*o.affine._m12 + affine._dy*o.affine._m22 + o.affine._dy; + + affine._m11 = m11; affine._m12 = m12; + affine._m21 = m21; affine._m22 = m22; + affine._dx = m31; affine._dy = m32; + break; + } + case TxProject: + { + qreal m11 = affine._m11*o.affine._m11 + affine._m12*o.affine._m21 + m_13*o.affine._dx; + qreal m12 = affine._m11*o.affine._m12 + affine._m12*o.affine._m22 + m_13*o.affine._dy; + qreal m13 = affine._m11*o.m_13 + affine._m12*o.m_23 + m_13*o.m_33; + + qreal m21 = affine._m21*o.affine._m11 + affine._m22*o.affine._m21 + m_23*o.affine._dx; + qreal m22 = affine._m21*o.affine._m12 + affine._m22*o.affine._m22 + m_23*o.affine._dy; + qreal m23 = affine._m21*o.m_13 + affine._m22*o.m_23 + m_23*o.m_33; + + qreal m31 = affine._dx*o.affine._m11 + affine._dy*o.affine._m21 + m_33*o.affine._dx; + qreal m32 = affine._dx*o.affine._m12 + affine._dy*o.affine._m22 + m_33*o.affine._dy; + qreal m33 = affine._dx*o.m_13 + affine._dy*o.m_23 + m_33*o.m_33; + + affine._m11 = m11; affine._m12 = m12; m_13 = m13; + affine._m21 = m21; affine._m22 = m22; m_23 = m23; + affine._dx = m31; affine._dy = m32; m_33 = m33; + } + } + + m_dirty = t; + m_type = t; + + return *this; +} + +/*! + \fn QTransform QTransform::operator*(const QTransform &matrix) const + Returns the result of multiplying this matrix by the given \a + matrix. + + Note that matrix multiplication is not commutative, i.e. a*b != + b*a. +*/ +QTransform QTransform::operator*(const QTransform &m) const +{ + const TransformationType otherType = m.inline_type(); + if (otherType == TxNone) + return *this; + + const TransformationType thisType = inline_type(); + if (thisType == TxNone) + return m; + + QTransform t(true); + TransformationType type = qMax(thisType, otherType); + switch(type) { + case TxNone: + break; + case TxTranslate: + t.affine._dx = affine._dx + m.affine._dx; + t.affine._dy += affine._dy + m.affine._dy; + break; + case TxScale: + { + qreal m11 = affine._m11*m.affine._m11; + qreal m22 = affine._m22*m.affine._m22; + + qreal m31 = affine._dx*m.affine._m11 + m.affine._dx; + qreal m32 = affine._dy*m.affine._m22 + m.affine._dy; + + t.affine._m11 = m11; + t.affine._m22 = m22; + t.affine._dx = m31; t.affine._dy = m32; + break; + } + case TxRotate: + case TxShear: + { + qreal m11 = affine._m11*m.affine._m11 + affine._m12*m.affine._m21; + qreal m12 = affine._m11*m.affine._m12 + affine._m12*m.affine._m22; + + qreal m21 = affine._m21*m.affine._m11 + affine._m22*m.affine._m21; + qreal m22 = affine._m21*m.affine._m12 + affine._m22*m.affine._m22; + + qreal m31 = affine._dx*m.affine._m11 + affine._dy*m.affine._m21 + m.affine._dx; + qreal m32 = affine._dx*m.affine._m12 + affine._dy*m.affine._m22 + m.affine._dy; + + t.affine._m11 = m11; t.affine._m12 = m12; + t.affine._m21 = m21; t.affine._m22 = m22; + t.affine._dx = m31; t.affine._dy = m32; + break; + } + case TxProject: + { + qreal m11 = affine._m11*m.affine._m11 + affine._m12*m.affine._m21 + m_13*m.affine._dx; + qreal m12 = affine._m11*m.affine._m12 + affine._m12*m.affine._m22 + m_13*m.affine._dy; + qreal m13 = affine._m11*m.m_13 + affine._m12*m.m_23 + m_13*m.m_33; + + qreal m21 = affine._m21*m.affine._m11 + affine._m22*m.affine._m21 + m_23*m.affine._dx; + qreal m22 = affine._m21*m.affine._m12 + affine._m22*m.affine._m22 + m_23*m.affine._dy; + qreal m23 = affine._m21*m.m_13 + affine._m22*m.m_23 + m_23*m.m_33; + + qreal m31 = affine._dx*m.affine._m11 + affine._dy*m.affine._m21 + m_33*m.affine._dx; + qreal m32 = affine._dx*m.affine._m12 + affine._dy*m.affine._m22 + m_33*m.affine._dy; + qreal m33 = affine._dx*m.m_13 + affine._dy*m.m_23 + m_33*m.m_33; + + t.affine._m11 = m11; t.affine._m12 = m12; t.m_13 = m13; + t.affine._m21 = m21; t.affine._m22 = m22; t.m_23 = m23; + t.affine._dx = m31; t.affine._dy = m32; t.m_33 = m33; + } + } + + t.m_dirty = type; + t.m_type = type; + + return t; +} + +/*! + \fn QTransform & QTransform::operator*=(qreal scalar) + \overload + + Returns the result of performing an element-wise multiplication of this + matrix with the given \a scalar. +*/ + +/*! + \fn QTransform & QTransform::operator/=(qreal scalar) + \overload + + Returns the result of performing an element-wise division of this + matrix by the given \a scalar. +*/ + +/*! + \fn QTransform & QTransform::operator+=(qreal scalar) + \overload + + Returns the matrix obtained by adding the given \a scalar to each + element of this matrix. +*/ + +/*! + \fn QTransform & QTransform::operator-=(qreal scalar) + \overload + + Returns the matrix obtained by subtracting the given \a scalar from each + element of this matrix. +*/ + +/*! + Assigns the given \a matrix's values to this matrix. +*/ +QTransform & QTransform::operator=(const QTransform &matrix) +{ + affine._m11 = matrix.affine._m11; + affine._m12 = matrix.affine._m12; + affine._m21 = matrix.affine._m21; + affine._m22 = matrix.affine._m22; + affine._dx = matrix.affine._dx; + affine._dy = matrix.affine._dy; + m_13 = matrix.m_13; + m_23 = matrix.m_23; + m_33 = matrix.m_33; + m_type = matrix.m_type; + m_dirty = matrix.m_dirty; + + return *this; +} + +/*! + Resets the matrix to an identity matrix, i.e. all elements are set + to zero, except \c m11 and \c m22 (specifying the scale) and \c m33 + which are set to 1. + + \sa QTransform(), isIdentity(), {QTransform#Basic Matrix + Operations}{Basic Matrix Operations} +*/ +void QTransform::reset() +{ + affine._m11 = affine._m22 = m_33 = 1.0; + affine._m12 = m_13 = affine._m21 = m_23 = affine._dx = affine._dy = 0; + m_type = TxNone; + m_dirty = TxNone; +} + +#ifndef QT_NO_DATASTREAM +/*! + \fn QDataStream &operator<<(QDataStream &stream, const QTransform &matrix) + \since 4.3 + \relates QTransform + + Writes the given \a matrix to the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream & operator<<(QDataStream &s, const QTransform &m) +{ + s << double(m.m11()) + << double(m.m12()) + << double(m.m13()) + << double(m.m21()) + << double(m.m22()) + << double(m.m23()) + << double(m.m31()) + << double(m.m32()) + << double(m.m33()); + return s; +} + +/*! + \fn QDataStream &operator>>(QDataStream &stream, QTransform &matrix) + \since 4.3 + \relates QTransform + + Reads the given \a matrix from the given \a stream and returns a + reference to the stream. + + \sa {Serializing Qt Data Types} +*/ +QDataStream & operator>>(QDataStream &s, QTransform &t) +{ + double m11, m12, m13, + m21, m22, m23, + m31, m32, m33; + + s >> m11; + s >> m12; + s >> m13; + s >> m21; + s >> m22; + s >> m23; + s >> m31; + s >> m32; + s >> m33; + t.setMatrix(m11, m12, m13, + m21, m22, m23, + m31, m32, m33); + return s; +} + +#endif // QT_NO_DATASTREAM + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QTransform &m) +{ + static const char *typeStr[] = + { + "TxNone", + "TxTranslate", + "TxScale", + 0, + "TxRotate", + 0, 0, 0, + "TxShear", + 0, 0, 0, 0, 0, 0, 0, + "TxProject" + }; + + dbg.nospace() << "QTransform(type=" << typeStr[m.type()] << ',' + << " 11=" << m.m11() + << " 12=" << m.m12() + << " 13=" << m.m13() + << " 21=" << m.m21() + << " 22=" << m.m22() + << " 23=" << m.m23() + << " 31=" << m.m31() + << " 32=" << m.m32() + << " 33=" << m.m33() + << ')'; + + return dbg.space(); +} +#endif + +/*! + \fn QPoint operator*(const QPoint &point, const QTransform &matrix) + \relates QTransform + + This is the same as \a{matrix}.map(\a{point}). + + \sa QTransform::map() +*/ +QPoint QTransform::map(const QPoint &p) const +{ + qreal fx = p.x(); + qreal fy = p.y(); + + qreal x = 0, y = 0; + + TransformationType t = inline_type(); + switch(t) { + case TxNone: + x = fx; + y = fy; + break; + case TxTranslate: + x = fx + affine._dx; + y = fy + affine._dy; + break; + case TxScale: + x = affine._m11 * fx + affine._dx; + y = affine._m22 * fy + affine._dy; + break; + case TxRotate: + case TxShear: + case TxProject: + x = affine._m11 * fx + affine._m21 * fy + affine._dx; + y = affine._m12 * fx + affine._m22 * fy + affine._dy; + if (t == TxProject) { + qreal w = 1./(m_13 * fx + m_23 * fy + m_33); + x *= w; + y *= w; + } + } + return QPoint(qRound(x), qRound(y)); +} + + +/*! + \fn QPointF operator*(const QPointF &point, const QTransform &matrix) + \relates QTransform + + Same as \a{matrix}.map(\a{point}). + + \sa QTransform::map() +*/ + +/*! + \overload + + Creates and returns a QPointF object that is a copy of the given point, + \a p, mapped into the coordinate system defined by this matrix. +*/ +QPointF QTransform::map(const QPointF &p) const +{ + qreal fx = p.x(); + qreal fy = p.y(); + + qreal x = 0, y = 0; + + TransformationType t = inline_type(); + switch(t) { + case TxNone: + x = fx; + y = fy; + break; + case TxTranslate: + x = fx + affine._dx; + y = fy + affine._dy; + break; + case TxScale: + x = affine._m11 * fx + affine._dx; + y = affine._m22 * fy + affine._dy; + break; + case TxRotate: + case TxShear: + case TxProject: + x = affine._m11 * fx + affine._m21 * fy + affine._dx; + y = affine._m12 * fx + affine._m22 * fy + affine._dy; + if (t == TxProject) { + qreal w = 1./(m_13 * fx + m_23 * fy + m_33); + x *= w; + y *= w; + } + } + return QPointF(x, y); +} + +/*! + \fn QPoint QTransform::map(const QPoint &point) const + \overload + + Creates and returns a QPoint object that is a copy of the given \a + point, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ + +/*! + \fn QLineF operator*(const QLineF &line, const QTransform &matrix) + \relates QTransform + + This is the same as \a{matrix}.map(\a{line}). + + \sa QTransform::map() +*/ + +/*! + \fn QLine operator*(const QLine &line, const QTransform &matrix) + \relates QTransform + + This is the same as \a{matrix}.map(\a{line}). + + \sa QTransform::map() +*/ + +/*! + \overload + + Creates and returns a QLineF object that is a copy of the given line, + \a l, mapped into the coordinate system defined by this matrix. +*/ +QLine QTransform::map(const QLine &l) const +{ + qreal fx1 = l.x1(); + qreal fy1 = l.y1(); + qreal fx2 = l.x2(); + qreal fy2 = l.y2(); + + qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; + + TransformationType t = inline_type(); + switch(t) { + case TxNone: + x1 = fx1; + y1 = fy1; + x2 = fx2; + y2 = fy2; + break; + case TxTranslate: + x1 = fx1 + affine._dx; + y1 = fy1 + affine._dy; + x2 = fx2 + affine._dx; + y2 = fy2 + affine._dy; + break; + case TxScale: + x1 = affine._m11 * fx1 + affine._dx; + y1 = affine._m22 * fy1 + affine._dy; + x2 = affine._m11 * fx2 + affine._dx; + y2 = affine._m22 * fy2 + affine._dy; + break; + case TxRotate: + case TxShear: + case TxProject: + x1 = affine._m11 * fx1 + affine._m21 * fy1 + affine._dx; + y1 = affine._m12 * fx1 + affine._m22 * fy1 + affine._dy; + x2 = affine._m11 * fx2 + affine._m21 * fy2 + affine._dx; + y2 = affine._m12 * fx2 + affine._m22 * fy2 + affine._dy; + if (t == TxProject) { + qreal w = 1./(m_13 * fx1 + m_23 * fy1 + m_33); + x1 *= w; + y1 *= w; + w = 1./(m_13 * fx2 + m_23 * fy2 + m_33); + x2 *= w; + y2 *= w; + } + } + return QLine(qRound(x1), qRound(y1), qRound(x2), qRound(y2)); +} + +/*! + \overload + + \fn QLineF QTransform::map(const QLineF &line) const + + Creates and returns a QLine object that is a copy of the given \a + line, mapped into the coordinate system defined by this matrix. + Note that the transformed coordinates are rounded to the nearest + integer. +*/ + +QLineF QTransform::map(const QLineF &l) const +{ + qreal fx1 = l.x1(); + qreal fy1 = l.y1(); + qreal fx2 = l.x2(); + qreal fy2 = l.y2(); + + qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0; + + TransformationType t = inline_type(); + switch(t) { + case TxNone: + x1 = fx1; + y1 = fy1; + x2 = fx2; + y2 = fy2; + break; + case TxTranslate: + x1 = fx1 + affine._dx; + y1 = fy1 + affine._dy; + x2 = fx2 + affine._dx; + y2 = fy2 + affine._dy; + break; + case TxScale: + x1 = affine._m11 * fx1 + affine._dx; + y1 = affine._m22 * fy1 + affine._dy; + x2 = affine._m11 * fx2 + affine._dx; + y2 = affine._m22 * fy2 + affine._dy; + break; + case TxRotate: + case TxShear: + case TxProject: + x1 = affine._m11 * fx1 + affine._m21 * fy1 + affine._dx; + y1 = affine._m12 * fx1 + affine._m22 * fy1 + affine._dy; + x2 = affine._m11 * fx2 + affine._m21 * fy2 + affine._dx; + y2 = affine._m12 * fx2 + affine._m22 * fy2 + affine._dy; + if (t == TxProject) { + qreal w = 1./(m_13 * fx1 + m_23 * fy1 + m_33); + x1 *= w; + y1 *= w; + w = 1./(m_13 * fx2 + m_23 * fy2 + m_33); + x2 *= w; + y2 *= w; + } + } + return QLineF(x1, y1, x2, y2); +} + +static QPolygonF mapProjective(const QTransform &transform, const QPolygonF &poly) +{ + if (poly.size() == 0) + return poly; + + if (poly.size() == 1) + return QPolygonF() << transform.map(poly.at(0)); + + QPainterPath path; + path.addPolygon(poly); + + path = transform.map(path); + + QPolygonF result; + for (int i = 0; i < path.elementCount(); ++i) + result << path.elementAt(i); + return result; +} + + +/*! + \fn QPolygonF operator *(const QPolygonF &polygon, const QTransform &matrix) + \since 4.3 + \relates QTransform + + This is the same as \a{matrix}.map(\a{polygon}). + + \sa QTransform::map() +*/ + +/*! + \fn QPolygon operator*(const QPolygon &polygon, const QTransform &matrix) + \relates QTransform + + This is the same as \a{matrix}.map(\a{polygon}). + + \sa QTransform::map() +*/ + +/*! + \fn QPolygonF QTransform::map(const QPolygonF &polygon) const + \overload + + Creates and returns a QPolygonF object that is a copy of the given + \a polygon, mapped into the coordinate system defined by this + matrix. +*/ +QPolygonF QTransform::map(const QPolygonF &a) const +{ + TransformationType t = inline_type(); + if (t <= TxTranslate) + return a.translated(affine._dx, affine._dy); + + if (t >= QTransform::TxProject) + return mapProjective(*this, a); + + int size = a.size(); + int i; + QPolygonF p(size); + const QPointF *da = a.constData(); + QPointF *dp = p.data(); + + for(i = 0; i < size; ++i) { + MAP(da[i].xp, da[i].yp, dp[i].xp, dp[i].yp); + } + return p; +} + +/*! + \fn QPolygon QTransform::map(const QPolygon &polygon) const + \overload + + Creates and returns a QPolygon object that is a copy of the given + \a polygon, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ +QPolygon QTransform::map(const QPolygon &a) const +{ + TransformationType t = inline_type(); + if (t <= TxTranslate) + return a.translated(qRound(affine._dx), qRound(affine._dy)); + + if (t >= QTransform::TxProject) + return mapProjective(*this, QPolygonF(a)).toPolygon(); + + int size = a.size(); + int i; + QPolygon p(size); + const QPoint *da = a.constData(); + QPoint *dp = p.data(); + + for(i = 0; i < size; ++i) { + qreal nx = 0, ny = 0; + MAP(da[i].xp, da[i].yp, nx, ny); + dp[i].xp = qRound(nx); + dp[i].yp = qRound(ny); + } + return p; +} + +/*! + \fn QRegion operator*(const QRegion ®ion, const QTransform &matrix) + \relates QTransform + + This is the same as \a{matrix}.map(\a{region}). + + \sa QTransform::map() +*/ + +extern QPainterPath qt_regionToPath(const QRegion ®ion); + +/*! + \fn QRegion QTransform::map(const QRegion ®ion) const + \overload + + Creates and returns a QRegion object that is a copy of the given + \a region, mapped into the coordinate system defined by this matrix. + + Calling this method can be rather expensive if rotations or + shearing are used. +*/ +QRegion QTransform::map(const QRegion &r) const +{ + TransformationType t = inline_type(); + if (t == TxNone) + return r; + + if (t == TxTranslate) { + QRegion copy(r); + copy.translate(qRound(affine._dx), qRound(affine._dy)); + return copy; + } + + if (t == TxScale && r.rectCount() == 1) + return QRegion(mapRect(r.boundingRect())); + + QPainterPath p = map(qt_regionToPath(r)); + return p.toFillPolygon(QTransform()).toPolygon(); +} + +struct QHomogeneousCoordinate +{ + qreal x; + qreal y; + qreal w; + + QHomogeneousCoordinate() {} + QHomogeneousCoordinate(qreal x_, qreal y_, qreal w_) : x(x_), y(y_), w(w_) {} + + const QPointF toPoint() const { + qreal iw = 1. / w; + return QPointF(x * iw, y * iw); + } +}; + +static inline QHomogeneousCoordinate mapHomogeneous(const QTransform &transform, const QPointF &p) +{ + QHomogeneousCoordinate c; + c.x = transform.m11() * p.x() + transform.m21() * p.y() + transform.m31(); + c.y = transform.m12() * p.x() + transform.m22() * p.y() + transform.m32(); + c.w = transform.m13() * p.x() + transform.m23() * p.y() + transform.m33(); + return c; +} + +static inline bool lineTo_clipped(QPainterPath &path, const QTransform &transform, const QPointF &a, const QPointF &b, + bool needsMoveTo, bool needsLineTo = true) +{ + QHomogeneousCoordinate ha = mapHomogeneous(transform, a); + QHomogeneousCoordinate hb = mapHomogeneous(transform, b); + + if (ha.w < Q_NEAR_CLIP && hb.w < Q_NEAR_CLIP) + return false; + + if (hb.w < Q_NEAR_CLIP) { + const qreal t = (Q_NEAR_CLIP - hb.w) / (ha.w - hb.w); + + hb.x += (ha.x - hb.x) * t; + hb.y += (ha.y - hb.y) * t; + hb.w = qreal(Q_NEAR_CLIP); + } else if (ha.w < Q_NEAR_CLIP) { + const qreal t = (Q_NEAR_CLIP - ha.w) / (hb.w - ha.w); + + ha.x += (hb.x - ha.x) * t; + ha.y += (hb.y - ha.y) * t; + ha.w = qreal(Q_NEAR_CLIP); + + const QPointF p = ha.toPoint(); + if (needsMoveTo) { + path.moveTo(p); + needsMoveTo = false; + } else { + path.lineTo(p); + } + } + + if (needsMoveTo) + path.moveTo(ha.toPoint()); + + if (needsLineTo) + path.lineTo(hb.toPoint()); + + return true; +} +Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + +static inline bool cubicTo_clipped(QPainterPath &path, const QTransform &transform, const QPointF &a, const QPointF &b, const QPointF &c, const QPointF &d, bool needsMoveTo) +{ + // Convert projective xformed curves to line + // segments so they can be transformed more accurately + + qreal scale; + qt_scaleForTransform(transform, &scale); + + qreal curveThreshold = scale == 0 ? qreal(0.25) : (qreal(0.25) / scale); + + QPolygonF segment = QBezier::fromPoints(a, b, c, d).toPolygon(curveThreshold); + + for (int i = 0; i < segment.size() - 1; ++i) + if (lineTo_clipped(path, transform, segment.at(i), segment.at(i+1), needsMoveTo)) + needsMoveTo = false; + + return !needsMoveTo; +} + +static QPainterPath mapProjective(const QTransform &transform, const QPainterPath &path) +{ + QPainterPath result; + + QPointF last; + QPointF lastMoveTo; + bool needsMoveTo = true; + for (int i = 0; i < path.elementCount(); ++i) { + switch (path.elementAt(i).type) { + case QPainterPath::MoveToElement: + if (i > 0 && lastMoveTo != last) + lineTo_clipped(result, transform, last, lastMoveTo, needsMoveTo); + + lastMoveTo = path.elementAt(i); + last = path.elementAt(i); + needsMoveTo = true; + break; + case QPainterPath::LineToElement: + if (lineTo_clipped(result, transform, last, path.elementAt(i), needsMoveTo)) + needsMoveTo = false; + last = path.elementAt(i); + break; + case QPainterPath::CurveToElement: + if (cubicTo_clipped(result, transform, last, path.elementAt(i), path.elementAt(i+1), path.elementAt(i+2), needsMoveTo)) + needsMoveTo = false; + i += 2; + last = path.elementAt(i); + break; + default: + Q_ASSERT(false); + } + } + + if (path.elementCount() > 0 && lastMoveTo != last) + lineTo_clipped(result, transform, last, lastMoveTo, needsMoveTo, false); + + result.setFillRule(path.fillRule()); + return result; +} + +/*! + \fn QPainterPath operator *(const QPainterPath &path, const QTransform &matrix) + \since 4.3 + \relates QTransform + + This is the same as \a{matrix}.map(\a{path}). + + \sa QTransform::map() +*/ + +/*! + \overload + + Creates and returns a QPainterPath object that is a copy of the + given \a path, mapped into the coordinate system defined by this + matrix. +*/ +QPainterPath QTransform::map(const QPainterPath &path) const +{ + TransformationType t = inline_type(); + if (t == TxNone || path.elementCount() == 0) + return path; + + if (t >= TxProject) + return mapProjective(*this, path); + + QPainterPath copy = path; + + if (t == TxTranslate) { + copy.translate(affine._dx, affine._dy); + } else { + copy.detach(); + // Full xform + for (int i=0; i<path.elementCount(); ++i) { + QPainterPath::Element &e = copy.d_ptr->elements[i]; + MAP(e.x, e.y, e.x, e.y); + } + } + + return copy; +} + +/*! + \fn QPolygon QTransform::mapToPolygon(const QRect &rectangle) const + + Creates and returns a QPolygon representation of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. + + The rectangle's coordinates are transformed using the following + formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qtransform.cpp 1 + + Polygons and rectangles behave slightly differently when + transformed (due to integer rounding), so + \c{matrix.map(QPolygon(rectangle))} is not always the same as + \c{matrix.mapToPolygon(rectangle)}. + + \sa mapRect(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ +QPolygon QTransform::mapToPolygon(const QRect &rect) const +{ + TransformationType t = inline_type(); + + QPolygon a(4); + qreal x[4] = { 0, 0, 0, 0 }, y[4] = { 0, 0, 0, 0 }; + if (t <= TxScale) { + x[0] = affine._m11*rect.x() + affine._dx; + y[0] = affine._m22*rect.y() + affine._dy; + qreal w = affine._m11*rect.width(); + qreal h = affine._m22*rect.height(); + if (w < 0) { + w = -w; + x[0] -= w; + } + if (h < 0) { + h = -h; + y[0] -= h; + } + x[1] = x[0]+w; + x[2] = x[1]; + x[3] = x[0]; + y[1] = y[0]; + y[2] = y[0]+h; + y[3] = y[2]; + } else { + qreal right = rect.x() + rect.width(); + qreal bottom = rect.y() + rect.height(); + MAP(rect.x(), rect.y(), x[0], y[0]); + MAP(right, rect.y(), x[1], y[1]); + MAP(right, bottom, x[2], y[2]); + MAP(rect.x(), bottom, x[3], y[3]); + } + + // all coordinates are correctly, tranform to a pointarray + // (rounding to the next integer) + a.setPoints(4, qRound(x[0]), qRound(y[0]), + qRound(x[1]), qRound(y[1]), + qRound(x[2]), qRound(y[2]), + qRound(x[3]), qRound(y[3])); + return a; +} + +/*! + Creates a transformation matrix, \a trans, that maps a unit square + to a four-sided polygon, \a quad. Returns true if the transformation + is constructed or false if such a transformation does not exist. + + \sa quadToSquare(), quadToQuad() +*/ +bool QTransform::squareToQuad(const QPolygonF &quad, QTransform &trans) +{ + if (quad.count() != 4) + return false; + + qreal dx0 = quad[0].x(); + qreal dx1 = quad[1].x(); + qreal dx2 = quad[2].x(); + qreal dx3 = quad[3].x(); + + qreal dy0 = quad[0].y(); + qreal dy1 = quad[1].y(); + qreal dy2 = quad[2].y(); + qreal dy3 = quad[3].y(); + + double ax = dx0 - dx1 + dx2 - dx3; + double ay = dy0 - dy1 + dy2 - dy3; + + if (!ax && !ay) { //afine transform + trans.setMatrix(dx1 - dx0, dy1 - dy0, 0, + dx2 - dx1, dy2 - dy1, 0, + dx0, dy0, 1); + } else { + double ax1 = dx1 - dx2; + double ax2 = dx3 - dx2; + double ay1 = dy1 - dy2; + double ay2 = dy3 - dy2; + + /*determinants */ + double gtop = ax * ay2 - ax2 * ay; + double htop = ax1 * ay - ax * ay1; + double bottom = ax1 * ay2 - ax2 * ay1; + + double a, b, c, d, e, f, g, h; /*i is always 1*/ + + if (!bottom) + return false; + + g = gtop/bottom; + h = htop/bottom; + + a = dx1 - dx0 + g * dx1; + b = dx3 - dx0 + h * dx3; + c = dx0; + d = dy1 - dy0 + g * dy1; + e = dy3 - dy0 + h * dy3; + f = dy0; + + trans.setMatrix(a, d, g, + b, e, h, + c, f, 1.0); + } + + return true; +} + +/*! + \fn bool QTransform::quadToSquare(const QPolygonF &quad, QTransform &trans) + + Creates a transformation matrix, \a trans, that maps a four-sided polygon, + \a quad, to a unit square. Returns true if the transformation is constructed + or false if such a transformation does not exist. + + \sa squareToQuad(), quadToQuad() +*/ +bool QTransform::quadToSquare(const QPolygonF &quad, QTransform &trans) +{ + if (!squareToQuad(quad, trans)) + return false; + + bool invertible = false; + trans = trans.inverted(&invertible); + + return invertible; +} + +/*! + Creates a transformation matrix, \a trans, that maps a four-sided + polygon, \a one, to another four-sided polygon, \a two. + Returns true if the transformation is possible; otherwise returns + false. + + This is a convenience method combining quadToSquare() and + squareToQuad() methods. It allows the input quad to be + transformed into any other quad. + + \sa squareToQuad(), quadToSquare() +*/ +bool QTransform::quadToQuad(const QPolygonF &one, + const QPolygonF &two, + QTransform &trans) +{ + QTransform stq; + if (!quadToSquare(one, trans)) + return false; + if (!squareToQuad(two, stq)) + return false; + trans *= stq; + //qDebug()<<"Final = "<<trans; + return true; +} + +/*! + Sets the matrix elements to the specified values, \a m11, + \a m12, \a m13 \a m21, \a m22, \a m23 \a m31, \a m32 and + \a m33. Note that this function replaces the previous values. + QTransform provides the translate(), rotate(), scale() and shear() + convenience functions to manipulate the various matrix elements + based on the currently defined coordinate system. + + \sa QTransform() +*/ + +void QTransform::setMatrix(qreal m11, qreal m12, qreal m13, + qreal m21, qreal m22, qreal m23, + qreal m31, qreal m32, qreal m33) +{ + affine._m11 = m11; affine._m12 = m12; m_13 = m13; + affine._m21 = m21; affine._m22 = m22; m_23 = m23; + affine._dx = m31; affine._dy = m32; m_33 = m33; + m_type = TxNone; + m_dirty = TxProject; +} + +static inline bool needsPerspectiveClipping(const QRectF &rect, const QTransform &transform) +{ + const qreal wx = qMin(transform.m13() * rect.left(), transform.m13() * rect.right()); + const qreal wy = qMin(transform.m23() * rect.top(), transform.m23() * rect.bottom()); + + return wx + wy + transform.m33() < Q_NEAR_CLIP; +} + +QRect QTransform::mapRect(const QRect &rect) const +{ + TransformationType t = inline_type(); + if (t <= TxTranslate) + return rect.translated(qRound(affine._dx), qRound(affine._dy)); + + if (t <= TxScale) { + int x = qRound(affine._m11*rect.x() + affine._dx); + int y = qRound(affine._m22*rect.y() + affine._dy); + int w = qRound(affine._m11*rect.width()); + int h = qRound(affine._m22*rect.height()); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return QRect(x, y, w, h); + } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) { + // see mapToPolygon for explanations of the algorithm. + qreal x = 0, y = 0; + MAP(rect.left(), rect.top(), x, y); + qreal xmin = x; + qreal ymin = y; + qreal xmax = x; + qreal ymax = y; + MAP(rect.right() + 1, rect.top(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAP(rect.right() + 1, rect.bottom() + 1, x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAP(rect.left(), rect.bottom() + 1, x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + return QRect(qRound(xmin), qRound(ymin), qRound(xmax)-qRound(xmin), qRound(ymax)-qRound(ymin)); + } else { + QPainterPath path; + path.addRect(rect); + return map(path).boundingRect().toRect(); + } +} + +/*! + \fn QRectF QTransform::mapRect(const QRectF &rectangle) const + + Creates and returns a QRectF object that is a copy of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. + + The rectangle's coordinates are transformed using the following + formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qtransform.cpp 2 + + If rotation or shearing has been specified, this function returns + the \e bounding rectangle. To retrieve the exact region the given + \a rectangle maps to, use the mapToPolygon() function instead. + + \sa mapToPolygon(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ +QRectF QTransform::mapRect(const QRectF &rect) const +{ + TransformationType t = inline_type(); + if (t <= TxTranslate) + return rect.translated(affine._dx, affine._dy); + + if (t <= TxScale) { + qreal x = affine._m11*rect.x() + affine._dx; + qreal y = affine._m22*rect.y() + affine._dy; + qreal w = affine._m11*rect.width(); + qreal h = affine._m22*rect.height(); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return QRectF(x, y, w, h); + } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) { + qreal x = 0, y = 0; + MAP(rect.x(), rect.y(), x, y); + qreal xmin = x; + qreal ymin = y; + qreal xmax = x; + qreal ymax = y; + MAP(rect.x() + rect.width(), rect.y(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAP(rect.x() + rect.width(), rect.y() + rect.height(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + MAP(rect.x(), rect.y() + rect.height(), x, y); + xmin = qMin(xmin, x); + ymin = qMin(ymin, y); + xmax = qMax(xmax, x); + ymax = qMax(ymax, y); + return QRectF(xmin, ymin, xmax-xmin, ymax - ymin); + } else { + QPainterPath path; + path.addRect(rect); + return map(path).boundingRect(); + } +} + +/*! + \fn QRect QTransform::mapRect(const QRect &rectangle) const + \overload + + Creates and returns a QRect object that is a copy of the given \a + rectangle, mapped into the coordinate system defined by this + matrix. Note that the transformed coordinates are rounded to the + nearest integer. +*/ + +/*! + Maps the given coordinates \a x and \a y into the coordinate + system defined by this matrix. The resulting values are put in *\a + tx and *\a ty, respectively. + + The coordinates are transformed using the following formulas: + + \snippet doc/src/snippets/code/src_gui_painting_qtransform.cpp 3 + + The point (x, y) is the original point, and (x', y') is the + transformed point. + + \sa {QTransform#Basic Matrix Operations}{Basic Matrix Operations} +*/ +void QTransform::map(qreal x, qreal y, qreal *tx, qreal *ty) const +{ + TransformationType t = inline_type(); + MAP(x, y, *tx, *ty); +} + +/*! + \overload + + Maps the given coordinates \a x and \a y into the coordinate + system defined by this matrix. The resulting values are put in *\a + tx and *\a ty, respectively. Note that the transformed coordinates + are rounded to the nearest integer. +*/ +void QTransform::map(int x, int y, int *tx, int *ty) const +{ + TransformationType t = inline_type(); + qreal fx = 0, fy = 0; + MAP(x, y, fx, fy); + *tx = qRound(fx); + *ty = qRound(fy); +} + +/*! + Returns the QTransform as an affine matrix. + + \warning If a perspective transformation has been specified, + then the conversion will cause loss of data. +*/ +const QMatrix &QTransform::toAffine() const +{ + return affine; +} + +/*! + Returns the transformation type of this matrix. + + The transformation type is the highest enumeration value + capturing all of the matrix's transformations. For example, + if the matrix both scales and shears, the type would be \c TxShear, + because \c TxShear has a higher enumeration value than \c TxScale. + + Knowing the transformation type of a matrix is useful for optimization: + you can often handle specific types more optimally than handling + the generic case. + */ +QTransform::TransformationType QTransform::type() const +{ + if(m_dirty == TxNone || m_dirty < m_type) + return static_cast<TransformationType>(m_type); + + switch (static_cast<TransformationType>(m_dirty)) { + case TxProject: + if (!qFuzzyIsNull(m_13) || !qFuzzyIsNull(m_23) || !qFuzzyIsNull(m_33 - 1)) { + m_type = TxProject; + break; + } + case TxShear: + case TxRotate: + if (!qFuzzyIsNull(affine._m12) || !qFuzzyIsNull(affine._m21)) { + const qreal dot = affine._m11 * affine._m12 + affine._m21 * affine._m22; + if (qFuzzyIsNull(dot)) + m_type = TxRotate; + else + m_type = TxShear; + break; + } + case TxScale: + if (!qFuzzyIsNull(affine._m11 - 1) || !qFuzzyIsNull(affine._m22 - 1)) { + m_type = TxScale; + break; + } + case TxTranslate: + if (!qFuzzyIsNull(affine._dx) || !qFuzzyIsNull(affine._dy)) { + m_type = TxTranslate; + break; + } + case TxNone: + m_type = TxNone; + break; + } + + m_dirty = TxNone; + return static_cast<TransformationType>(m_type); +} + +/*! + + Returns the transform as a QVariant. +*/ +QTransform::operator QVariant() const +{ + return QVariant(QVariant::Transform, this); +} + + +/*! + \fn bool QTransform::isInvertible() const + + Returns true if the matrix is invertible, otherwise returns false. + + \sa inverted() +*/ + +/*! + \fn qreal QTransform::det() const + \obsolete + + Returns the matrix's determinant. Use determinant() instead. +*/ + + +/*! + \fn qreal QTransform::m11() const + + Returns the horizontal scaling factor. + + \sa scale(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m12() const + + Returns the vertical shearing factor. + + \sa shear(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m21() const + + Returns the horizontal shearing factor. + + \sa shear(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m22() const + + Returns the vertical scaling factor. + + \sa scale(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::dx() const + + Returns the horizontal translation factor. + + \sa m31(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::dy() const + + Returns the vertical translation factor. + + \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + + +/*! + \fn qreal QTransform::m13() const + + Returns the horizontal projection factor. + + \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + + +/*! + \fn qreal QTransform::m23() const + + Returns the vertical projection factor. + + \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m31() const + + Returns the horizontal translation factor. + + \sa dx(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m32() const + + Returns the vertical translation factor. + + \sa dy(), translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::m33() const + + Returns the division factor. + + \sa translate(), {QTransform#Basic Matrix Operations}{Basic Matrix + Operations} +*/ + +/*! + \fn qreal QTransform::determinant() const + + Returns the matrix's determinant. +*/ + +/*! + \fn bool QTransform::isIdentity() const + + Returns true if the matrix is the identity matrix, otherwise + returns false. + + \sa reset() +*/ + +/*! + \fn bool QTransform::isAffine() const + + Returns true if the matrix represent an affine transformation, + otherwise returns false. +*/ + +/*! + \fn bool QTransform::isScaling() const + + Returns true if the matrix represents a scaling + transformation, otherwise returns false. + + \sa reset() +*/ + +/*! + \fn bool QTransform::isRotating() const + + Returns true if the matrix represents some kind of a + rotating transformation, otherwise returns false. + + \sa reset() +*/ + +/*! + \fn bool QTransform::isTranslating() const + + Returns true if the matrix represents a translating + transformation, otherwise returns false. + + \sa reset() +*/ + +/*! + \fn bool qFuzzyCompare(const QTransform& t1, const QTransform& t2) + + \relates QTransform + \since 4.6 + + Returns true if \a t1 and \a t2 are equal, allowing for a small + fuzziness factor for floating-point comparisons; false otherwise. +*/ + + +// returns true if the transform is uniformly scaling +// (same scale in x and y direction) +// scale is set to the max of x and y scaling factors +Q_GUI_EXPORT +bool qt_scaleForTransform(const QTransform &transform, qreal *scale) +{ + const QTransform::TransformationType type = transform.type(); + if (type <= QTransform::TxTranslate) { + if (scale) + *scale = 1; + return true; + } else if (type == QTransform::TxScale) { + const qreal xScale = qAbs(transform.m11()); + const qreal yScale = qAbs(transform.m22()); + if (scale) + *scale = qMax(xScale, yScale); + return qFuzzyCompare(xScale, yScale); + } + + const qreal xScale = transform.m11() * transform.m11() + + transform.m21() * transform.m21(); + const qreal yScale = transform.m12() * transform.m12() + + transform.m22() * transform.m22(); + if (scale) + *scale = qSqrt(qMax(xScale, yScale)); + return type == QTransform::TxRotate && qFuzzyCompare(xScale, yScale); +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qtransform.h b/src/gui/painting/qtransform.h new file mode 100644 index 0000000000..a165bd5ce3 --- /dev/null +++ b/src/gui/painting/qtransform.h @@ -0,0 +1,395 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QTRANSFORM_H +#define QTRANSFORM_H + +#include <QtGui/qmatrix.h> +#include <QtGui/qpainterpath.h> +#include <QtGui/qpolygon.h> +#include <QtGui/qregion.h> +#include <QtGui/qwindowdefs.h> +#include <QtCore/qline.h> +#include <QtCore/qpoint.h> +#include <QtCore/qrect.h> + +#if defined(Q_OS_VXWORKS) && defined(m_type) +# undef m_type +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QVariant; + +class Q_GUI_EXPORT QTransform +{ +public: + enum TransformationType { + TxNone = 0x00, + TxTranslate = 0x01, + TxScale = 0x02, + TxRotate = 0x04, + TxShear = 0x08, + TxProject = 0x10 + }; + + inline explicit QTransform(Qt::Initialization) : affine(Qt::Uninitialized) {} + QTransform(); + QTransform(qreal h11, qreal h12, qreal h13, + qreal h21, qreal h22, qreal h23, + qreal h31, qreal h32, qreal h33 = 1.0); + QTransform(qreal h11, qreal h12, qreal h21, + qreal h22, qreal dx, qreal dy); + explicit QTransform(const QMatrix &mtx); + + bool isAffine() const; + bool isIdentity() const; + bool isInvertible() const; + bool isScaling() const; + bool isRotating() const; + bool isTranslating() const; + + TransformationType type() const; + + inline qreal determinant() const; + qreal det() const; + + qreal m11() const; + qreal m12() const; + qreal m13() const; + qreal m21() const; + qreal m22() const; + qreal m23() const; + qreal m31() const; + qreal m32() const; + qreal m33() const; + qreal dx() const; + qreal dy() const; + + void setMatrix(qreal m11, qreal m12, qreal m13, + qreal m21, qreal m22, qreal m23, + qreal m31, qreal m32, qreal m33); + + QTransform inverted(bool *invertible = 0) const; + QTransform adjoint() const; + QTransform transposed() const; + + QTransform &translate(qreal dx, qreal dy); + QTransform &scale(qreal sx, qreal sy); + QTransform &shear(qreal sh, qreal sv); + QTransform &rotate(qreal a, Qt::Axis axis = Qt::ZAxis); + QTransform &rotateRadians(qreal a, Qt::Axis axis = Qt::ZAxis); + + static bool squareToQuad(const QPolygonF &square, QTransform &result); + static bool quadToSquare(const QPolygonF &quad, QTransform &result); + static bool quadToQuad(const QPolygonF &one, + const QPolygonF &two, + QTransform &result); + + bool operator==(const QTransform &) const; + bool operator!=(const QTransform &) const; + + QTransform &operator*=(const QTransform &); + QTransform operator*(const QTransform &o) const; + + QTransform &operator=(const QTransform &); + + operator QVariant() const; + + void reset(); + QPoint map(const QPoint &p) const; + QPointF map(const QPointF &p) const; + QLine map(const QLine &l) const; + QLineF map(const QLineF &l) const; + QPolygonF map(const QPolygonF &a) const; + QPolygon map(const QPolygon &a) const; + QRegion map(const QRegion &r) const; + QPainterPath map(const QPainterPath &p) const; + QPolygon mapToPolygon(const QRect &r) const; + QRect mapRect(const QRect &) const; + QRectF mapRect(const QRectF &) const; + void map(int x, int y, int *tx, int *ty) const; + void map(qreal x, qreal y, qreal *tx, qreal *ty) const; + + const QMatrix &toAffine() const; + + QTransform &operator*=(qreal div); + QTransform &operator/=(qreal div); + QTransform &operator+=(qreal div); + QTransform &operator-=(qreal div); + + static QTransform fromTranslate(qreal dx, qreal dy); + static QTransform fromScale(qreal dx, qreal dy); + +private: + inline QTransform(qreal h11, qreal h12, qreal h13, + qreal h21, qreal h22, qreal h23, + qreal h31, qreal h32, qreal h33, bool) + : affine(h11, h12, h21, h22, h31, h32, true) + , m_13(h13), m_23(h23), m_33(h33) + , m_type(TxNone) + , m_dirty(TxProject) {} + inline QTransform(bool) + : affine(true) + , m_13(0), m_23(0), m_33(1) + , m_type(TxNone) + , m_dirty(TxNone) {} + inline TransformationType inline_type() const; + QMatrix affine; + qreal m_13; + qreal m_23; + qreal m_33; + + mutable uint m_type : 5; + mutable uint m_dirty : 5; + + class Private; + Private *d; +}; +Q_DECLARE_TYPEINFO(QTransform, Q_MOVABLE_TYPE); + +/******* inlines *****/ +inline QTransform::TransformationType QTransform::inline_type() const +{ + if (m_dirty == TxNone) + return static_cast<TransformationType>(m_type); + return type(); +} + +inline bool QTransform::isAffine() const +{ + return inline_type() < TxProject; +} +inline bool QTransform::isIdentity() const +{ + return inline_type() == TxNone; +} + +inline bool QTransform::isInvertible() const +{ + return !qFuzzyIsNull(determinant()); +} + +inline bool QTransform::isScaling() const +{ + return type() >= TxScale; +} +inline bool QTransform::isRotating() const +{ + return inline_type() >= TxRotate; +} + +inline bool QTransform::isTranslating() const +{ + return inline_type() >= TxTranslate; +} + +inline qreal QTransform::determinant() const +{ + return affine._m11*(m_33*affine._m22-affine._dy*m_23) - + affine._m21*(m_33*affine._m12-affine._dy*m_13)+affine._dx*(m_23*affine._m12-affine._m22*m_13); +} +inline qreal QTransform::det() const +{ + return determinant(); +} +inline qreal QTransform::m11() const +{ + return affine._m11; +} +inline qreal QTransform::m12() const +{ + return affine._m12; +} +inline qreal QTransform::m13() const +{ + return m_13; +} +inline qreal QTransform::m21() const +{ + return affine._m21; +} +inline qreal QTransform::m22() const +{ + return affine._m22; +} +inline qreal QTransform::m23() const +{ + return m_23; +} +inline qreal QTransform::m31() const +{ + return affine._dx; +} +inline qreal QTransform::m32() const +{ + return affine._dy; +} +inline qreal QTransform::m33() const +{ + return m_33; +} +inline qreal QTransform::dx() const +{ + return affine._dx; +} +inline qreal QTransform::dy() const +{ + return affine._dy; +} + +inline QTransform &QTransform::operator*=(qreal num) +{ + if (num == 1.) + return *this; + affine._m11 *= num; + affine._m12 *= num; + m_13 *= num; + affine._m21 *= num; + affine._m22 *= num; + m_23 *= num; + affine._dx *= num; + affine._dy *= num; + m_33 *= num; + if (m_dirty < TxScale) + m_dirty = TxScale; + return *this; +} +inline QTransform &QTransform::operator/=(qreal div) +{ + if (div == 0) + return *this; + div = 1/div; + return operator*=(div); +} +inline QTransform &QTransform::operator+=(qreal num) +{ + if (num == 0) + return *this; + affine._m11 += num; + affine._m12 += num; + m_13 += num; + affine._m21 += num; + affine._m22 += num; + m_23 += num; + affine._dx += num; + affine._dy += num; + m_33 += num; + m_dirty = TxProject; + return *this; +} +inline QTransform &QTransform::operator-=(qreal num) +{ + if (num == 0) + return *this; + affine._m11 -= num; + affine._m12 -= num; + m_13 -= num; + affine._m21 -= num; + affine._m22 -= num; + m_23 -= num; + affine._dx -= num; + affine._dy -= num; + m_33 -= num; + m_dirty = TxProject; + return *this; +} + +inline bool qFuzzyCompare(const QTransform& t1, const QTransform& t2) +{ + return qFuzzyCompare(t1.m11(), t2.m11()) + && qFuzzyCompare(t1.m12(), t2.m12()) + && qFuzzyCompare(t1.m13(), t2.m13()) + && qFuzzyCompare(t1.m21(), t2.m21()) + && qFuzzyCompare(t1.m22(), t2.m22()) + && qFuzzyCompare(t1.m23(), t2.m23()) + && qFuzzyCompare(t1.m31(), t2.m31()) + && qFuzzyCompare(t1.m32(), t2.m32()) + && qFuzzyCompare(t1.m33(), t2.m33()); +} + + +/****** stream functions *******************/ +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTransform &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTransform &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QTransform &); +#endif +/****** end stream functions *******************/ + +// mathematical semantics +Q_GUI_EXPORT_INLINE QPoint operator*(const QPoint &p, const QTransform &m) +{ return m.map(p); } +Q_GUI_EXPORT_INLINE QPointF operator*(const QPointF &p, const QTransform &m) +{ return m.map(p); } +Q_GUI_EXPORT_INLINE QLineF operator*(const QLineF &l, const QTransform &m) +{ return m.map(l); } +Q_GUI_EXPORT_INLINE QLine operator*(const QLine &l, const QTransform &m) +{ return m.map(l); } +Q_GUI_EXPORT_INLINE QPolygon operator *(const QPolygon &a, const QTransform &m) +{ return m.map(a); } +Q_GUI_EXPORT_INLINE QPolygonF operator *(const QPolygonF &a, const QTransform &m) +{ return m.map(a); } +Q_GUI_EXPORT_INLINE QRegion operator *(const QRegion &r, const QTransform &m) +{ return m.map(r); } +Q_GUI_EXPORT_INLINE QPainterPath operator *(const QPainterPath &p, const QTransform &m) +{ return m.map(p); } + +Q_GUI_EXPORT_INLINE QTransform operator *(const QTransform &a, qreal n) +{ QTransform t(a); t *= n; return t; } +Q_GUI_EXPORT_INLINE QTransform operator /(const QTransform &a, qreal n) +{ QTransform t(a); t /= n; return t; } +Q_GUI_EXPORT_INLINE QTransform operator +(const QTransform &a, qreal n) +{ QTransform t(a); t += n; return t; } +Q_GUI_EXPORT_INLINE QTransform operator -(const QTransform &a, qreal n) +{ QTransform t(a); t -= n; return t; } + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/painting/qunifiedtoolbarsurface_mac.cpp b/src/gui/painting/qunifiedtoolbarsurface_mac.cpp new file mode 100644 index 0000000000..3876c3d1a2 --- /dev/null +++ b/src/gui/painting/qunifiedtoolbarsurface_mac.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qunifiedtoolbarsurface_mac_p.h" +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qbackingstore_p.h> +#include <private/qmainwindowlayout_p.h> + +#include <QDebug> + +#ifdef QT_MAC_USE_COCOA + +QT_BEGIN_NAMESPACE + +QUnifiedToolbarSurface::QUnifiedToolbarSurface(QWidget *widget) + : QRasterWindowSurface(widget, false), d_ptr(new QUnifiedToolbarSurfacePrivate) +{ + d_ptr->image = 0; + d_ptr->inSetGeometry = false; + + setGeometry(QRect(QPoint(0, 0), QSize(widget->width(), 100))); // FIXME: Fix height. +} + +QUnifiedToolbarSurface::~QUnifiedToolbarSurface() +{ + if (d_ptr->image) + delete d_ptr->image; +} + +QPaintDevice *QUnifiedToolbarSurface::paintDevice() +{ + return &d_ptr->image->image; +} + +void QUnifiedToolbarSurface::recursiveRedirect(QObject *object, QWidget *parent_toolbar, const QPoint &offset) +{ + if (object != 0) { + if (object->isWidgetType()) { + QWidget *widget = qobject_cast<QWidget *>(object); + + // We redirect the painting only if the widget is in the same window + // and is not a window in itself. + if (!(widget->windowType() & Qt::Window)) { + widget->d_func()->unifiedSurface = this; + widget->d_func()->isInUnifiedToolbar = true; + widget->d_func()->toolbar_offset = offset; + widget->d_func()->toolbar_ancestor = parent_toolbar; + + for (int i = 0; i < object->children().size(); ++i) { + recursiveRedirect(object->children().at(i), parent_toolbar, offset); + } + } + } + } +} + +void QUnifiedToolbarSurface::insertToolbar(QWidget *toolbar, const QPoint &offset) +{ + setGeometry(QRect(QPoint(0, 0), QSize(offset.x() + toolbar->width(), 100))); // FIXME + recursiveRedirect(toolbar, toolbar, offset); +} + +// We basically undo what we set in recursiveRedirect(). +void QUnifiedToolbarSurface::recursiveRemoval(QObject *object) +{ + if (object != 0) { + if (object->isWidgetType()) { + QWidget *widget = qobject_cast<QWidget *>(object); + + // If it's a pop-up or something similar, we don't redirect it. + if (widget->windowType() & Qt::Window) + return; + + widget->d_func()->unifiedSurface = 0; + widget->d_func()->isInUnifiedToolbar = false; + widget->d_func()->toolbar_offset = QPoint(); + widget->d_func()->toolbar_ancestor = 0; + } + + for (int i = 0; i < object->children().size(); ++i) { + recursiveRemoval(object->children().at(i)); + } + } +} + +void QUnifiedToolbarSurface::removeToolbar(QToolBar *toolbar) +{ + recursiveRemoval(toolbar); +} + +void QUnifiedToolbarSurface::setGeometry(const QRect &rect) +{ + QWindowSurface::setGeometry(rect); + Q_D(QUnifiedToolbarSurface); + d->inSetGeometry = true; + if (d->image == 0 || d->image->width() < rect.width() || d->image->height() < rect.height()) + prepareBuffer(QImage::Format_ARGB32_Premultiplied, window()); + d->inSetGeometry = false; + + // FIXME: set unified toolbar height. +} + +void QUnifiedToolbarSurface::beginPaint(const QRegion &rgn) +{ + QPainter p(&d_ptr->image->image); + p.setCompositionMode(QPainter::CompositionMode_Source); + const QVector<QRect> rects = rgn.rects(); + const QColor blank = Qt::transparent; + for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { + p.fillRect(*it, blank); + } +} + +void QUnifiedToolbarSurface::updateToolbarOffset(QWidget *widget) +{ + QMainWindowLayout *mlayout = qobject_cast<QMainWindowLayout*> (widget->window()->layout()); + mlayout->updateUnifiedToolbarOffset(); +} + +void QUnifiedToolbarSurface::flush(QWidget *widget, const QRegion ®ion, const QPoint &offset) +{ + Q_UNUSED(region); + Q_UNUSED(offset); + + this->flush(widget); +} + +void QUnifiedToolbarSurface::flush(QWidget *widget) +{ + Q_D(QUnifiedToolbarSurface); + + if (!d->image) + return; + + if (widget->d_func()->flushRequested) { + // We call display: directly to avoid flickering in the toolbar. + qt_mac_display(widget); + } +} + +void QUnifiedToolbarSurface::prepareBuffer(QImage::Format format, QWidget *widget) +{ + Q_D(QUnifiedToolbarSurface); + + int width = geometry().width(); + int height = 100; // FIXME + if (d->image) { + width = qMax(d->image->width(), width); + height = qMax(d->image->height(), height); + } + + if (width == 0 || height == 0) { + delete d->image; + d->image = 0; + return; + } + + QNativeImage *oldImage = d->image; + + d->image = new QNativeImage(width, height, format, false, widget); + + if (oldImage && d->inSetGeometry && hasStaticContents()) { + // Make sure we use the const version of bits() (no detach). + const uchar *src = const_cast<const QImage &>(oldImage->image).bits(); + uchar *dst = d->image->image.bits(); + + const int srcBytesPerLine = oldImage->image.bytesPerLine(); + const int dstBytesPerLine = d->image->image.bytesPerLine(); + const int bytesPerPixel = oldImage->image.depth() >> 3; + + QRegion staticRegion(staticContents()); + // Make sure we're inside the boundaries of the old image. + staticRegion &= QRect(0, 0, oldImage->image.width(), oldImage->image.height()); + const QVector<QRect> &rects = staticRegion.rects(); + const QRect *srcRect = rects.constData(); + + // Copy the static content of the old image into the new one. + int numRectsLeft = rects.size(); + do { + const int bytesOffset = srcRect->x() * bytesPerPixel; + const int dy = srcRect->y(); + + // Adjust src and dst to point to the right offset. + const uchar *s = src + dy * srcBytesPerLine + bytesOffset; + uchar *d = dst + dy * dstBytesPerLine + bytesOffset; + const int numBytes = srcRect->width() * bytesPerPixel; + + int numScanLinesLeft = srcRect->height(); + do { + ::memcpy(d, s, numBytes); + d += dstBytesPerLine; + s += srcBytesPerLine; + } while (--numScanLinesLeft); + + ++srcRect; + } while (--numRectsLeft); + } + + delete oldImage; +} + +CGContextRef QUnifiedToolbarSurface::imageContext() +{ + Q_D(QUnifiedToolbarSurface); + return d->image->cg; +} + +void QUnifiedToolbarSurface::renderToolbar(QWidget *widget, bool forceFlush) +{ + QWidget *toolbar = widget->d_func()->toolbar_ancestor; + + updateToolbarOffset(toolbar); + QRect beginPaintRect(toolbar->d_func()->toolbar_offset.x(), toolbar->d_func()->toolbar_offset.y(), toolbar->geometry().width(), toolbar->geometry().height()); + QRegion beginPaintRegion(beginPaintRect); + + beginPaint(beginPaintRegion); + toolbar->render(paintDevice(), toolbar->d_func()->toolbar_offset, QRegion(toolbar->geometry()), QWidget::DrawChildren); + toolbar->d_func()->flushRequested = true; + + if (forceFlush) + flush(toolbar); +} + +QT_END_NAMESPACE + +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/painting/qunifiedtoolbarsurface_mac_p.h b/src/gui/painting/qunifiedtoolbarsurface_mac_p.h new file mode 100644 index 0000000000..0a7ebf1759 --- /dev/null +++ b/src/gui/painting/qunifiedtoolbarsurface_mac_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUNIFIEDTOOLBARSURFACE_MAC_P_H +#define QUNIFIEDTOOLBARSURFACE_MAC_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/qwindowsurface_raster_p.h> +#include <QWidget> +#include <QToolBar> +#include <private/qwidget_p.h> +#include <private/qnativeimage_p.h> + +#ifdef QT_MAC_USE_COCOA + +QT_BEGIN_NAMESPACE + +class QNativeImage; + + +class QUnifiedToolbarSurfacePrivate +{ +public: + QNativeImage *image; + uint inSetGeometry : 1; +}; + +class Q_GUI_EXPORT QUnifiedToolbarSurface : public QRasterWindowSurface +{ +public: + QUnifiedToolbarSurface(QWidget *widget); + ~QUnifiedToolbarSurface(); + + void flush(QWidget *widget); + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + void setGeometry(const QRect &rect); + void beginPaint(const QRegion &rgn); + void insertToolbar(QWidget *toolbar, const QPoint &offset); + void removeToolbar(QToolBar *toolbar); + void updateToolbarOffset(QWidget *widget); + void renderToolbar(QWidget *widget, bool forceFlush = false); + void recursiveRedirect(QObject *widget, QWidget *parent_toolbar, const QPoint &offset); + + QPaintDevice *paintDevice(); + CGContextRef imageContext(); + +private: + void prepareBuffer(QImage::Format format, QWidget *widget); + void recursiveRemoval(QObject *object); + + Q_DECLARE_PRIVATE(QUnifiedToolbarSurface) + QScopedPointer<QUnifiedToolbarSurfacePrivate> d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QT_MAC_USE_COCOA + +#endif // QUNIFIEDTOOLBARSURFACE_MAC_P_H diff --git a/src/gui/painting/qvectorpath_p.h b/src/gui/painting/qvectorpath_p.h new file mode 100644 index 0000000000..76212be5d4 --- /dev/null +++ b/src/gui/painting/qvectorpath_p.h @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVECTORPATH_P_H +#define QVECTORPATH_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 <QtGui/qpaintengine.h> + +#include <private/qpaintengine_p.h> +#include <private/qstroker_p.h> +#include <private/qpainter_p.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QPaintEngineEx; + +typedef void (*qvectorpath_cache_cleanup)(QPaintEngineEx *engine, void *data); + +struct QRealRect { + qreal x1, y1, x2, y2; +}; + +class Q_GUI_EXPORT QVectorPath +{ +public: + enum Hint { + // Shape hints, in 0x000000ff, access using shape() + AreaShapeMask = 0x0001, // shape covers an area + NonConvexShapeMask = 0x0002, // shape is not convex + CurvedShapeMask = 0x0004, // shape contains curves... + LinesShapeMask = 0x0008, + RectangleShapeMask = 0x0010, + ShapeMask = 0x001f, + + // Shape hints merged into basic shapes.. + LinesHint = LinesShapeMask, + RectangleHint = AreaShapeMask | RectangleShapeMask, + EllipseHint = AreaShapeMask | CurvedShapeMask, + ConvexPolygonHint = AreaShapeMask, + PolygonHint = AreaShapeMask | NonConvexShapeMask, + RoundedRectHint = AreaShapeMask | CurvedShapeMask, + ArbitraryShapeHint = AreaShapeMask | NonConvexShapeMask | CurvedShapeMask, + + // Other hints + IsCachedHint = 0x0100, // Set if the cache hint is set + ShouldUseCacheHint = 0x0200, // Set if the path should be cached when possible.. + ControlPointRect = 0x0400, // Set if the control point rect has been calculated... + + // Shape rendering specifiers... + OddEvenFill = 0x1000, + WindingFill = 0x2000, + ImplicitClose = 0x4000 + }; + + // ### Falcon: introduca a struct XY for points so lars is not so confused... + QVectorPath(const qreal *points, + int count, + const QPainterPath::ElementType *elements = 0, + uint hints = ArbitraryShapeHint) + : m_elements(elements), + m_points(points), + m_count(count), + m_hints(hints) + { + } + + ~QVectorPath(); + + QRectF controlPointRect() const; + + inline Hint shape() const { return (Hint) (m_hints & ShapeMask); } + inline bool isConvex() const { return (m_hints & NonConvexShapeMask) == 0; } + inline bool isCurved() const { return m_hints & CurvedShapeMask; } + + inline bool isCacheable() const { return m_hints & ShouldUseCacheHint; } + inline bool hasImplicitClose() const { return m_hints & ImplicitClose; } + inline bool hasWindingFill() const { return m_hints & WindingFill; } + + inline void makeCacheable() const { m_hints |= ShouldUseCacheHint; m_cache = 0; } + inline uint hints() const { return m_hints; } + + inline const QPainterPath::ElementType *elements() const { return m_elements; } + inline const qreal *points() const { return m_points; } + inline bool isEmpty() const { return m_points == 0; } + + inline int elementCount() const { return m_count; } + inline const QPainterPath convertToPainterPath() const; + + static inline uint polygonFlags(QPaintEngine::PolygonDrawMode mode); + + struct CacheEntry { + QPaintEngineEx *engine; + void *data; + qvectorpath_cache_cleanup cleanup; + CacheEntry *next; + }; + + CacheEntry *addCacheData(QPaintEngineEx *engine, void *data, qvectorpath_cache_cleanup cleanup) const; + inline CacheEntry *lookupCacheData(QPaintEngineEx *engine) const { + Q_ASSERT(m_hints & ShouldUseCacheHint); + CacheEntry *e = m_cache; + while (e) { + if (e->engine == engine) + return e; + e = e->next; + } + return 0; + } + + +private: + Q_DISABLE_COPY(QVectorPath) + + const QPainterPath::ElementType *m_elements; + const qreal *m_points; + const int m_count; + + mutable uint m_hints; + mutable QRealRect m_cp_rect; + + mutable CacheEntry *m_cache; +}; + +Q_GUI_EXPORT const QVectorPath &qtVectorPathForPath(const QPainterPath &path); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/painting/qwindowsurface.cpp b/src/gui/painting/qwindowsurface.cpp new file mode 100644 index 0000000000..0fb9bf78a9 --- /dev/null +++ b/src/gui/painting/qwindowsurface.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qwindowsurface_p.h> +#include <qwidget.h> +#include <private/qwidget_p.h> +#include <private/qbackingstore_p.h> +#include <private/qapplication_p.h> + +QT_BEGIN_NAMESPACE + +class QWindowSurfacePrivate +{ +public: + QWindowSurfacePrivate(QWidget *w) + : window(w) + { + } + + QWidget *window; +#if !defined(Q_WS_QPA) + QRect geometry; +#else + QSize size; +#endif //Q_WS_QPA + QRegion staticContents; + QList<QImage*> bufferImages; +}; + +/*! + \class QWindowSurface + \since 4.3 + \internal + \preliminary + \ingroup qws qpa + + \brief The QWindowSurface class provides the drawing area for top-level + windows. +*/ + + +/*! + \fn void QWindowSurface::beginPaint(const QRegion ®ion) + + This function is called before painting onto the surface begins, + with the \a region in which the painting will occur. + + \sa endPaint(), paintDevice() +*/ + +/*! + \fn void QWindowSurface::endPaint(const QRegion ®ion) + + This function is called after painting onto the surface has ended, + with the \a region in which the painting was performed. + + \sa beginPaint(), paintDevice() +*/ + +/*! + \fn void QWindowSurface::flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset) + + Flushes the given \a region from the specified \a widget onto the + screen. + + Note that the \a offset parameter is currently unused. +*/ + +/*! + \fn QPaintDevice* QWindowSurface::paintDevice() + + Implement this function to return the appropriate paint device. +*/ + +/*! + Constructs an empty surface for the given top-level \a window. +*/ +QWindowSurface::QWindowSurface(QWidget *window, bool setDefaultSurface) + : d_ptr(new QWindowSurfacePrivate(window)) +{ + if (!QApplicationPrivate::runtime_graphics_system) { + if(setDefaultSurface && window) + window->setWindowSurface(this); + } +} + +/*! + Destroys this surface. +*/ +QWindowSurface::~QWindowSurface() +{ + if (d_ptr->window) + d_ptr->window->d_func()->extra->topextra->windowSurface = 0; + delete d_ptr; +} + +/*! + Returns a pointer to the top-level window associated with this + surface. +*/ +QWidget* QWindowSurface::window() const +{ + return d_ptr->window; +} + +void QWindowSurface::beginPaint(const QRegion &) +{ +} + +void QWindowSurface::endPaint(const QRegion &) +{ +// QApplication::syncX(); + qDeleteAll(d_ptr->bufferImages); + d_ptr->bufferImages.clear(); +} + +#if !defined(Q_WS_QPA) +/*! + Sets the currently allocated area to be the given \a rect. + + This function is called whenever area covered by the top-level + window changes. + + \sa geometry() +*/ +void QWindowSurface::setGeometry(const QRect &rect) +{ + d_ptr->geometry = rect; +} + +/*! + Returns the currently allocated area on the screen. +*/ +QRect QWindowSurface::geometry() const +{ + return d_ptr->geometry; +} +#else + +/*! + Sets the size of the windowsurface to be \a size. + + \sa size() +*/ +void QWindowSurface::resize(const QSize &size) +{ + d_ptr->size = size; +} + +/*! + Returns the current size of the windowsurface. +*/ +QSize QWindowSurface::size() const +{ + return d_ptr->size; +} +#endif //Q_WS_QPA + +/*! + Scrolls the given \a area \a dx pixels to the right and \a dy + downward; both \a dx and \a dy may be negative. + + Returns true if the area was scrolled successfully; false otherwise. +*/ +bool QWindowSurface::scroll(const QRegion &area, int dx, int dy) +{ + Q_UNUSED(area); + Q_UNUSED(dx); + Q_UNUSED(dy); + + return false; +} + +/*! + Returns a QImage pointer which represents the actual buffer the \a widget + is drawn into or 0 if this is unavailable. + + You must call beginPaint() before you call this function and the returned + pointer is only valid until endPaint() is called. +*/ +QImage* QWindowSurface::buffer(const QWidget *widget) +{ + if (widget->window() != window()) + return 0; + + QPaintDevice *pdev = paintDevice(); + if (!pdev || pdev->devType() != QInternal::Image) + return 0; + + const QPoint off = offset(widget); + QImage *img = static_cast<QImage*>(pdev); + + QRect rect(off, widget->size()); + rect &= QRect(QPoint(), img->size()); + + if (rect.isEmpty()) + return 0; + + img = new QImage(img->scanLine(rect.y()) + rect.x() * img->depth() / 8, + rect.width(), rect.height(), + img->bytesPerLine(), img->format()); + d_ptr->bufferImages.append(img); + + return img; +} + +/*! + Returns a QPixmap generated from the part of the backing store + corresponding to \a widget. Returns a null QPixmap if an error + occurs. The contents of the pixmap are only defined for the regions + of \a widget that have received paint events since the last resize + of the backing store. + + If \a rectangle is a null rectangle (the default), the entire widget + is grabbed. Otherwise, the grabbed area is limited to \a rectangle. + + The default implementation uses QWindowSurface::buffer(). + + \sa QPixmap::grabWidget() +*/ +QPixmap QWindowSurface::grabWidget(const QWidget *widget, const QRect &rectangle) const +{ + QPixmap result; + + if (widget->window() != window()) + return result; + + const QImage *img = const_cast<QWindowSurface *>(this)->buffer(widget->window()); + + if (!img || img->isNull()) + return result; + + QRect rect = rectangle.isEmpty() ? widget->rect() : (widget->rect() & rectangle); + + rect.translate(offset(widget) - offset(widget->window())); + rect &= QRect(QPoint(), img->size()); + + if (rect.isEmpty()) + return result; + + QImage subimg(img->scanLine(rect.y()) + rect.x() * img->depth() / 8, + rect.width(), rect.height(), + img->bytesPerLine(), img->format()); + subimg.detach(); //### expensive -- maybe we should have a real SubImage that shares reference count + + result = QPixmap::fromImage(subimg); + return result; +} + +/*! + Returns the offset of \a widget in the coordinates of this + window surface. + */ +QPoint QWindowSurface::offset(const QWidget *widget) const +{ + QWidget *window = d_ptr->window; + QPoint offset = widget->mapTo(window, QPoint()); +#ifdef Q_WS_QWS + offset += window->geometry().topLeft() - window->frameGeometry().topLeft(); +#endif + return offset; +} + +/*! + \fn QRect QWindowSurface::rect(const QWidget *widget) const + + Returns the rectangle for \a widget in the coordinates of this + window surface. +*/ + +void QWindowSurface::setStaticContents(const QRegion ®ion) +{ + d_ptr->staticContents = region; +} + +QRegion QWindowSurface::staticContents() const +{ + return d_ptr->staticContents; +} + +bool QWindowSurface::hasStaticContents() const +{ + return hasFeature(QWindowSurface::StaticContents) && !d_ptr->staticContents.isEmpty(); +} + +QWindowSurface::WindowSurfaceFeatures QWindowSurface::features() const +{ + return PartialUpdates | PreservedContents; +} + +#ifdef Q_WS_QPA +#define Q_EXPORT_SCROLLRECT Q_GUI_EXPORT +#else +#define Q_EXPORT_SCROLLRECT +#endif + +void Q_EXPORT_SCROLLRECT qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset) +{ + // make sure we don't detach + uchar *mem = const_cast<uchar*>(const_cast<const QImage &>(img).bits()); + + int lineskip = img.bytesPerLine(); + int depth = img.depth() >> 3; + + const QRect imageRect(0, 0, img.width(), img.height()); + const QRect r = rect & imageRect & imageRect.translated(-offset); + const QPoint p = rect.topLeft() + offset; + + if (r.isEmpty()) + return; + + const uchar *src; + uchar *dest; + + if (r.top() < p.y()) { + src = mem + r.bottom() * lineskip + r.left() * depth; + dest = mem + (p.y() + r.height() - 1) * lineskip + p.x() * depth; + lineskip = -lineskip; + } else { + src = mem + r.top() * lineskip + r.left() * depth; + dest = mem + p.y() * lineskip + p.x() * depth; + } + + const int w = r.width(); + int h = r.height(); + const int bytes = w * depth; + + // overlapping segments? + if (offset.y() == 0 && qAbs(offset.x()) < w) { + do { + ::memmove(dest, src, bytes); + dest += lineskip; + src += lineskip; + } while (--h); + } else { + do { + ::memcpy(dest, src, bytes); + dest += lineskip; + src += lineskip; + } while (--h); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qwindowsurface_mac.cpp b/src/gui/painting/qwindowsurface_mac.cpp new file mode 100644 index 0000000000..620a50f3ea --- /dev/null +++ b/src/gui/painting/qwindowsurface_mac.cpp @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsurface_mac_p.h" + +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_NAMESPACE + +struct QMacWindowSurfacePrivate +{ + QWidget *widget; + QPixmap device; +}; + +QMacWindowSurface::QMacWindowSurface(QWidget *widget) + : QWindowSurface(widget), d_ptr(new QMacWindowSurfacePrivate) +{ + d_ptr->widget = widget; +} + +QMacWindowSurface::~QMacWindowSurface() +{ + delete d_ptr; +} + +QPaintDevice *QMacWindowSurface::paintDevice() +{ + return &d_ptr->device; +} + +void QMacWindowSurface::flush(QWidget *widget, const QRegion &rgn, const QPoint &offset) +{ + Q_UNUSED(offset); + + // Get a context for the widget. +#ifndef QT_MAC_USE_COCOA + CGContextRef context; + CGrafPtr port = GetWindowPort(qt_mac_window_for(widget)); + QDBeginCGContext(port, &context); +#else + extern CGContextRef qt_mac_graphicsContextFor(QWidget *); + CGContextRef context = qt_mac_graphicsContextFor(widget); +#endif + CGContextRetain(context); + CGContextSaveGState(context); + + // Flip context. + CGContextTranslateCTM(context, 0, widget->height()); + CGContextScaleCTM(context, 1, -1); + + // Clip to region. + const QVector<QRect> &rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) { + const QRect &rect = rects.at(i); + CGContextAddRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height())); + } + CGContextClip(context); + + // Draw the image onto the window. + const CGRect dest = CGRectMake(0, 0, widget->width(), widget->height()); + const CGImageRef image = d_ptr->device.toMacCGImageRef(); + qt_mac_drawCGImage(context, &dest, image); + CFRelease(image); + + // Restore context. + CGContextRestoreGState(context); +#ifndef QT_MAC_USE_COCOA + QDEndCGContext(port, &context); +#else + CGContextFlush(context); +#endif + CGContextRelease(context); +} + +void QMacWindowSurface::setGeometry(const QRect &rect) +{ + QWindowSurface::setGeometry(rect); + const QSize size = rect.size(); + if (d_ptr->device.size() != size) + d_ptr->device = QPixmap(size); +} + +bool QMacWindowSurface::scroll(const QRegion &area, int dx, int dy) +{ + if (d_ptr->device.size().isNull()) + return false; + + QCFType<CGImageRef> image = d_ptr->device.toMacCGImageRef(); + const QRect rect(area.boundingRect()); + const CGRect dest = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + QCFType<CGImageRef> subimage = CGImageCreateWithImageInRect(image, dest); + QCFType<CGContextRef> context = qt_mac_cg_context(&d_ptr->device); + CGContextTranslateCTM(context, dx, dy); + qt_mac_drawCGImage(context, &dest, subimage); + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qwindowsurface_mac_p.h b/src/gui/painting/qwindowsurface_mac_p.h new file mode 100644 index 0000000000..fd68b1fe91 --- /dev/null +++ b/src/gui/painting/qwindowsurface_mac_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_MAC_P_H +#define QWINDOWSURFACE_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include "private/qwindowsurface_p.h" + +QT_BEGIN_NAMESPACE + +class QPaintDevice; +class QPoint; +class QRegion; +class QWidget; +struct QMacWindowSurfacePrivate; + +class QMacWindowSurface : public QWindowSurface +{ +public: + QMacWindowSurface(QWidget *widget); + ~QMacWindowSurface(); + + QPaintDevice *paintDevice(); + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + void setGeometry(const QRect &rect); + bool scroll(const QRegion &area, int dx, int dy); + +private: + QMacWindowSurfacePrivate *d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_MAC_P_H diff --git a/src/gui/painting/qwindowsurface_p.h b/src/gui/painting/qwindowsurface_p.h new file mode 100644 index 0000000000..a3fea67f24 --- /dev/null +++ b/src/gui/painting/qwindowsurface_p.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_P_H +#define QWINDOWSURFACE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qwidget.h> + +QT_BEGIN_NAMESPACE + +class QPaintDevice; +class QRegion; +class QRect; +class QPoint; +class QImage; +class QWindowSurfacePrivate; +class QPlatformWindow; + +class Q_GUI_EXPORT QWindowSurface +{ +public: + enum WindowSurfaceFeature { + PartialUpdates = 0x00000001, // Supports doing partial updates. + PreservedContents = 0x00000002, // Supports doing flush without first doing a repaint. + StaticContents = 0x00000004, // Supports having static content regions when being resized. + AllFeatures = 0xffffffff // For convenience + }; + Q_DECLARE_FLAGS(WindowSurfaceFeatures, WindowSurfaceFeature) + + QWindowSurface(QWidget *window, bool setDefaultSurface = true); + virtual ~QWindowSurface(); + + QWidget *window() const; + + virtual QPaintDevice *paintDevice() = 0; + + // 'widget' can be a child widget, in which case 'region' is in child widget coordinates and + // offset is the (child) widget's offset in relation to the window surface. On QWS, 'offset' + // can be larger than just the offset from the top-level widget as there may also be window + // decorations which are painted into the window surface. + virtual void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset) = 0; +#if !defined(Q_WS_QPA) + virtual void setGeometry(const QRect &rect); + QRect geometry() const; +#else + virtual void resize(const QSize &size); + QSize size() const; + inline QRect geometry() const { return QRect(QPoint(), size()); } //### cleanup before Qt 5 +#endif + + virtual bool scroll(const QRegion &area, int dx, int dy); + + virtual void beginPaint(const QRegion &); + virtual void endPaint(const QRegion &); + + virtual QImage* buffer(const QWidget *widget); + virtual QPixmap grabWidget(const QWidget *widget, const QRect& rectangle = QRect()) const; + + virtual QPoint offset(const QWidget *widget) const; + inline QRect rect(const QWidget *widget) const; + + bool hasFeature(WindowSurfaceFeature feature) const; + virtual WindowSurfaceFeatures features() const; + + void setStaticContents(const QRegion ®ion); + QRegion staticContents() const; + +protected: + bool hasStaticContents() const; + +private: + QWindowSurfacePrivate *d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWindowSurface::WindowSurfaceFeatures) + +inline QRect QWindowSurface::rect(const QWidget *widget) const +{ + return widget->rect().translated(offset(widget)); +} + +inline bool QWindowSurface::hasFeature(WindowSurfaceFeature feature) const +{ + return (features() & feature) != 0; +} + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_P_H diff --git a/src/gui/painting/qwindowsurface_qws.cpp b/src/gui/painting/qwindowsurface_qws.cpp new file mode 100644 index 0000000000..cb293cb135 --- /dev/null +++ b/src/gui/painting/qwindowsurface_qws.cpp @@ -0,0 +1,1433 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsurface_qws_p.h" +#include <qwidget.h> +#include <qscreen_qws.h> +#include <qwsmanager_qws.h> +#include <qapplication.h> +#include <qwsdisplay_qws.h> +#include <qrgb.h> +#include <qpaintengine.h> +#include <qdesktopwidget.h> +#include <private/qapplication_p.h> +#include <private/qwsdisplay_qws_p.h> +#include <private/qwidget_p.h> +#include <private/qwsmanager_p.h> +#include <private/qwslock_p.h> +#include <private/qbackingstore_p.h> +#include <stdio.h> + +QT_BEGIN_NAMESPACE + +#ifdef Q_BACKINGSTORE_SUBSURFACES + +typedef QMap<int, QWSWindowSurface*> SurfaceMap; +Q_GLOBAL_STATIC(SurfaceMap, winIdToSurfaceMap); + +QWSWindowSurface* qt_findWindowSurface(int winId) +{ + return winIdToSurfaceMap()->value(winId); +} + +static void qt_insertWindowSurface(int winId, QWSWindowSurface *surface) +{ + if (!surface) + winIdToSurfaceMap()->remove(winId); + else + winIdToSurfaceMap()->insert(winId, surface); +} + +#endif // Q_BACKINGSTORE_SUBSURFACES + +inline bool isWidgetOpaque(const QWidget *w) +{ + return w->d_func()->isOpaque && !w->testAttribute(Qt::WA_TranslucentBackground); +} + +static inline QScreen *getScreen(const QWidget *w) +{ + const QList<QScreen*> subScreens = qt_screen->subScreens(); + if (subScreens.isEmpty()) + return qt_screen; + + const int screen = QApplication::desktop()->screenNumber(w); + + return qt_screen->subScreens().at(screen < 0 ? 0 : screen); +} + +static int bytesPerPixel(QImage::Format format) +{ + switch (format) { + case QImage::Format_Invalid: + return 0; +#ifndef QT_NO_DEBUG + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + qFatal("QWSWindowSurface: Invalid backingstore format: %i", + int(format)); +#endif + case QImage::Format_Indexed8: + return 1; + case QImage::Format_RGB32: + case QImage::Format_ARGB32: + case QImage::Format_ARGB32_Premultiplied: + return 4; + case QImage::Format_RGB16: + case QImage::Format_RGB555: + case QImage::Format_RGB444: + case QImage::Format_ARGB4444_Premultiplied: + return 2; + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_RGB666: + case QImage::Format_RGB888: + return 3; + default: +#ifndef QT_NO_DEBUG + qFatal("QWSWindowSurface: Invalid backingstore format: %i", + int(format)); +#endif + return 0; + } +} + +static inline int nextMulOf4(int n) +{ + return ((n + 3) & 0xfffffffc); +} + +static inline void setImageMetrics(QImage &img, QWidget *window) { + QScreen *myScreen = getScreen(window); + if (myScreen) { + int dpmx = myScreen->width()*1000 / myScreen->physicalWidth(); + int dpmy = myScreen->height()*1000 / myScreen->physicalHeight(); + img.setDotsPerMeterX(dpmx); + img.setDotsPerMeterY(dpmy); + } +} + +void QWSWindowSurface::invalidateBuffer() +{ + + QWidget *win = window(); + if (win) { + win->d_func()->invalidateBuffer(win->rect()); +#ifndef QT_NO_QWS_MANAGER + QTLWExtra *topextra = win->d_func()->extra->topextra; + QWSManager *manager = topextra->qwsManager; + if (manager) + manager->d_func()->dirtyRegion(QDecoration::All, + QDecoration::Normal); +#endif + } +} + +QWSWindowSurfacePrivate::QWSWindowSurfacePrivate() + : flags(0), +#ifdef QT_QWS_CLIENTBLIT + directId(-1), +#endif + winId(0) +{ +} + +void QWSWindowSurfacePrivate::setWinId(int id) +{ + winId = id; +} + +int QWSWindowSurface::winId() const +{ + // XXX: the widget winId may change during the lifetime of the widget!!! + + const QWidget *win = window(); + if (win && win->isWindow()) + return win->internalWinId(); + +#ifdef Q_BACKINGSTORE_SUBSURFACES + if (!d_ptr->winId) { + QWSWindowSurface *that = const_cast<QWSWindowSurface*>(this); + QWSDisplay *display = QWSDisplay::instance(); + const int id = display->takeId(); + qt_insertWindowSurface(id, that); + that->d_ptr->winId = id; + + if (win) + display->nameRegion(id, win->objectName(), win->windowTitle()); + else + display->nameRegion(id, QString(), QString()); + + display->setAltitude(id, 1, true); // XXX + } +#endif + + return d_ptr->winId; +} + +void QWSWindowSurface::setWinId(int id) +{ + d_ptr->winId = id; +} + +/*! + \class QWSWindowSurface + \since 4.2 + \ingroup qws + \preliminary + \internal + + \brief The QWSWindowSurface class provides the drawing area for top-level + windows in Qt for Embedded Linux. + + Note that this class is only available in Qt for Embedded Linux. + + In \l{Qt for Embedded Linux}, the default behavior is for each client to + render its widgets into memory while the server is responsible for + putting the contents of the memory onto the + screen. QWSWindowSurface is used by the window system to implement + the associated memory allocation. + + When a screen update is required, the server runs through all the + top-level windows that intersect with the region that is about to + be updated, and ensures that the associated clients have updated + their memory buffer. Then the server uses the screen driver to + copy the content of the memory to the screen. To locate the + relevant parts of memory, the driver is provided with the list of + top-level windows that intersect with the given region. Associated + with each of the top-level windows there is a window surface + representing the drawing area of the window. + + When deriving from the QWSWindowSurface class, e.g., when adding + an \l {Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + {accelerated graphics driver}, there are several pure virtual + functions that must be implemented. In addition, QWSWindowSurface + provides several virtual functions that can be reimplemented to + customize the drawing process. + + \tableofcontents + + \section1 Pure Virtual Functions + + There are in fact two window surface instances for each top-level + window; one used by the application when drawing a window, and + another used by the server application to perform window + compositioning. Implement the attach() to create the server-side + representation of the surface. The data() function must be + implemented to provide the required data. + + Implement the key() function to uniquely identify the surface + class, and the isValid() function to determine is a surface + corresponds to a given widget. + + The geometry() function must be implemented to let the window + system determine the area required by the window surface + (QWSWindowSurface also provides a corresponding virtual + setGeometry() function that is called whenever the area necessary + for the top-level window to be drawn, changes). The image() + function is called by the window system during window + compositioning, and must be implemented to return an image of the + top-level window. + + Finally, the paintDevice() function must be implemented to return + the appropriate paint device, and the scroll() function must be + implemented to scroll the given region of the surface the given + number of pixels. + + \section1 Virtual Functions + + When painting onto the surface, the window system will always call + the beginPaint() function before any painting operations are + performed. Likewise the endPaint() function is automatically + called when the painting is done. Reimplement the painterOffset() + function to alter the offset that is applied when drawing. + + The window system uses the flush() function to put a given region + of the widget onto the screen, and the release() function to + deallocate the screen region corresponding to this window surface. + + \section1 Other Members + + QWSWindowSurface provides the window() function returning a + pointer to the top-level window the surface is representing. The + currently visible region of the associated widget can be retrieved + and set using the clipRegion() and setClipRegion() functions, + respectively. + + When the window system performs the window compositioning, it uses + the SurfaceFlag enum describing the surface content. The currently + set surface flags can be retrieved and altered using the + surfaceFlags() and setSurfaceFlags() functions. In addition, + QWSWindowSurface provides the isBuffered(), isOpaque() and + isRegionReserved() convenience functions. + + \sa {Qt for Embedded Linux Architecture#Drawing on Screen}{Qt for + Embedded Linux Architecture} +*/ + +/*! + \enum QWSWindowSurface::SurfaceFlag + + This enum is used to describe the window surface's contents. It + is used by the screen driver to handle region allocation and + composition. + + \value RegionReserved The surface contains a reserved area. Once + allocated, a reserved area can not not be changed by the window + system, i.e., no other widgets can be drawn on top of this. + + \value Buffered + The surface is in a memory area which is not part of a framebuffer. + (A top-level window with QWidget::windowOpacity() other than 1.0 must use + a buffered surface in order to making blending with the background work.) + + \value Opaque + The surface contains only opaque pixels. + + \sa surfaceFlags(), setSurfaceFlags() +*/ + +/*! + \fn bool QWSWindowSurface::isValid() const + \since 4.3 + + Implement this function to return true if the surface is a valid + surface for the given top-level \a window; otherwise return + false. + + \sa window(), key() +*/ + +/*! + \fn QString QWSWindowSurface::key() const + + Implement this function to return a string that uniquely + identifies the class of this surface. + + \sa window(), isValid() +*/ + +/*! + \fn QByteArray QWSWindowSurface::permanentState() const + \since 4.3 + + Implement this function to return the data required for creating a + server-side representation of the surface. + + \sa attach() +*/ + +/*! + \fn void QWSWindowSurface::setPermanentState(const QByteArray &data) + \since 4.3 + + Implement this function to attach a server-side surface instance + to the corresponding client side instance using the given \a + data. Return true if successful; otherwise return false. + + \sa data() +*/ + +/*! + \fn const QImage QWSWindowSurface::image() const + + Implement this function to return an image of the top-level window. + + \sa geometry() +*/ + +/*! + \fn bool QWSWindowSurface::isRegionReserved() const + + Returns true if the QWSWindowSurface::RegionReserved is set; otherwise + returns false. + + \sa surfaceFlags() +*/ + +/*! + \fn bool QWSWindowSurface::isBuffered() const + + Returns true if the QWSWindowSurface::Buffered is set; otherwise returns false. + + \sa surfaceFlags() +*/ + +/*! + \fn bool QWSWindowSurface::isOpaque() const + + Returns true if the QWSWindowSurface::Opaque is set; otherwise + returns false. + + \sa surfaceFlags() +*/ + + +/*! + Constructs an empty surface. +*/ +QWSWindowSurface::QWSWindowSurface() + : QWindowSurface(0), d_ptr(new QWSWindowSurfacePrivate) +{ +} + +/*! + Constructs an empty surface for the given top-level \a widget. +*/ +QWSWindowSurface::QWSWindowSurface(QWidget *widget) + : QWindowSurface(widget), d_ptr(new QWSWindowSurfacePrivate) +{ +} + +QWSWindowSurface::~QWSWindowSurface() +{ +#ifdef Q_BACKINGSTORE_SUBSURFACES + if (d_ptr->winId) + winIdToSurfaceMap()->remove(d_ptr->winId); +#endif + + delete d_ptr; +} + +/*! + Returns the offset to be used when painting. + + \sa paintDevice() +*/ +QPoint QWSWindowSurface::painterOffset() const +{ + const QWidget *w = window(); + if (!w) + return QPoint(); + return w->geometry().topLeft() - w->frameGeometry().topLeft(); +} + +void QWSWindowSurface::beginPaint(const QRegion &) +{ + lock(); +} + +void QWSWindowSurface::endPaint(const QRegion &) +{ + unlock(); +} + +// XXX: documentation!!! +QByteArray QWSWindowSurface::transientState() const +{ + return QByteArray(); +} + +QByteArray QWSWindowSurface::permanentState() const +{ + return QByteArray(); +} + +void QWSWindowSurface::setTransientState(const QByteArray &state) +{ + Q_UNUSED(state); +} + +void QWSWindowSurface::setPermanentState(const QByteArray &state) +{ + Q_UNUSED(state); +} + +bool QWSWindowSurface::lock(int timeout) +{ + Q_UNUSED(timeout); + return true; +} + +void QWSWindowSurface::unlock() +{ +} + +#ifdef QT_QWS_CLIENTBLIT +/*! \internal */ +const QRegion QWSWindowSurface::directRegion() const +{ + return d_ptr->direct; +} + +/*! \internal */ +int QWSWindowSurface::directRegionId() const +{ + return d_ptr->directId; +} + +/*! \internal */ +void QWSWindowSurface::setDirectRegion(const QRegion &r, int id) +{ + d_ptr->direct = r; + d_ptr->directId = id; +} +#endif + +/*! + Returns the region currently visible on the screen. + + \sa setClipRegion() +*/ +const QRegion QWSWindowSurface::clipRegion() const +{ + return d_ptr->clip; +} + +/*! + Sets the region currently visible on the screen to be the given \a + clip region. + + \sa clipRegion() +*/ +void QWSWindowSurface::setClipRegion(const QRegion &clip) +{ + if (clip == d_ptr->clip) + return; + + QRegion expose = (clip - d_ptr->clip); + d_ptr->clip = clip; + + if (expose.isEmpty() || clip.isEmpty()) + return; // No repaint or flush required. + + QWidget *win = window(); + if (!win) + return; + + if (isBuffered()) { + // No repaint required. Flush exposed area via the backing store. + win->d_func()->syncBackingStore(expose); + return; + } + +#ifndef QT_NO_QWS_MANAGER + // Invalidate exposed decoration area. + if (win && win->isWindow()) { + QTLWExtra *topextra = win->d_func()->extra->topextra; + if (QWSManager *manager = topextra->qwsManager) { + QRegion decorationExpose(manager->region()); + decorationExpose.translate(-win->geometry().topLeft()); + decorationExpose &= expose; + if (!decorationExpose.isEmpty()) { + expose -= decorationExpose; + manager->d_func()->dirtyRegion(QDecoration::All, QDecoration::Normal, decorationExpose); + } + } + } +#endif + + // Invalidate exposed widget area. + win->d_func()->invalidateBuffer(expose); +} + +/*! + Returns the surface flags describing the contents of this surface. + + \sa isBuffered(), isOpaque(), isRegionReserved() +*/ +QWSWindowSurface::SurfaceFlags QWSWindowSurface::surfaceFlags() const +{ + return d_ptr->flags; +} + +/*! + Sets the surface flags describing the contents of this surface, to + be the given \a flags. + + \sa surfaceFlags() +*/ +void QWSWindowSurface::setSurfaceFlags(SurfaceFlags flags) +{ + d_ptr->flags = flags; +} + +void QWSWindowSurface::setGeometry(const QRect &rect) +{ + QRegion mask = rect; + + const QWidget *win = window(); + if (win) { +#ifndef QT_NO_QWS_MANAGER + if (win->isWindow()) { + QTLWExtra *topextra = win->d_func()->extra->topextra; + QWSManager *manager = topextra->qwsManager; + + if (manager) { + // The frame geometry is the bounding rect of manager->region, + // which could be too much, so we need to clip. + mask &= (manager->region() + win->geometry()); + } + } +#endif + + const QRegion winMask = win->mask(); + if (!winMask.isEmpty()) + mask &= winMask.translated(win->geometry().topLeft()); + } + + setGeometry(rect, mask); +} + +void QWSWindowSurface::setGeometry(const QRect &rect, const QRegion &mask) +{ + if (rect == geometry()) // XXX: && mask == prevMask + return; + + const bool isResize = rect.size() != geometry().size(); + const bool needsRepaint = isResize || !isBuffered(); + + QWindowSurface::setGeometry(rect); + + const QRegion region = mask & rect; + QWidget *win = window(); + // Only request regions for widgets visible on the screen. + // (Added the !win check for compatibility reasons, because + // there was no "if (win)" check before). + const bool requestQWSRegion = !win || !win->testAttribute(Qt::WA_DontShowOnScreen); + if (requestQWSRegion) + QWidget::qwsDisplay()->requestRegion(winId(), key(), permanentState(), region); + + if (needsRepaint) + invalidateBuffer(); + + if (!requestQWSRegion) { + // We didn't request a region, hence we won't get a QWSRegionEvent::Allocation + // event back from the server so we set the clip directly. We have to + // do this after the invalidateBuffer() call above, as it might trigger a + // backing store sync, resulting in too many update requests. + setClipRegion(region); + } +} + +static inline void flushUpdate(QWidget *widget, const QRegion ®ion, + const QPoint &offset) +{ +#ifdef QT_NO_PAINT_DEBUG + Q_UNUSED(widget); + Q_UNUSED(region); + Q_UNUSED(offset); +#else + static int delay = -1; + + if (delay == -1) + delay = qgetenv("QT_FLUSH_UPDATE").toInt() * 10; + + if (delay == 0) + return; + + static QWSYellowSurface surface(true); + surface.setDelay(delay); + surface.flush(widget, region, offset); +#endif // QT_NO_PAINT_DEBUG +} + +void QWSWindowSurface::flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset) +{ + const QWidget *win = window(); + if (!win) + return; + +#ifndef QT_NO_GRAPHICSVIEW + QWExtra *extra = win->d_func()->extra; + if (extra && extra->proxyWidget) + return; +#endif //QT_NO_GRAPHICSVIEW + + Q_UNUSED(offset); + + const bool opaque = isOpaque(); +#ifdef QT_QWS_DISABLE_FLUSHCLIPPING + QRegion toFlush = region; +#else + QRegion toFlush = region & d_ptr->clip; +#endif + + if (!toFlush.isEmpty()) { + flushUpdate(widget, toFlush, QPoint(0, 0)); + QPoint globalZero = win->mapToGlobal(QPoint(0, 0)); + toFlush.translate(globalZero); + +#ifdef QT_QWS_CLIENTBLIT + bool needRepaint = true; + if (opaque) { + QScreen* widgetScreen = getScreen(widget); + if (widgetScreen->supportsBlitInClients()) { + + QWSDisplay::grab(); + if(directRegion().intersected(toFlush) == toFlush) { + QPoint translate = -globalZero + painterOffset() + geometry().topLeft(); + QRegion flushRegion = toFlush.translated(translate); + widgetScreen->blit(image(), geometry().topLeft(), flushRegion); + widgetScreen->setDirty(toFlush.boundingRect()); + needRepaint = false; + } + QWSDisplay::ungrab(); + } + } + + if(needRepaint) +#endif + win->qwsDisplay()->repaintRegion(winId(), win->windowFlags(), opaque, toFlush); + } +} + +/*! + Move the surface with the given \a offset. + + A subclass may reimplement this function to enable accelerated window move. + It must return true if the move was successful and no repaint is necessary, + false otherwise. + + The default implementation updates the QWindowSurface geometry and + returns true if the surface is buffered; false otherwise. + + This function is called by the window system on the client instance. + + \sa isBuffered() +*/ +bool QWSWindowSurface::move(const QPoint &offset) +{ + QWindowSurface::setGeometry(geometry().translated(offset)); + return isBuffered(); +} + +/*! + Move the surface with the given \a offset. + + The new visible region after the window move is given by \a newClip + in screen coordinates. + + A subclass may reimplement this function to enable accelerated window move. + The returned region indicates the area that still needs to be composed + on the screen. + + The default implementation updates the QWindowSurface geometry and + returns the union of the old and new geometry. + + This function is called by the window system on the server instance. +*/ +QRegion QWSWindowSurface::move(const QPoint &offset, const QRegion &newClip) +{ + const QRegion oldGeometry = geometry(); + QWindowSurface::setGeometry(geometry().translated(offset)); + return oldGeometry + newClip; +} + +void QWSWindowSurface::releaseSurface() +{ +} + +bool QWSMemorySurface::lock(int timeout) +{ + Q_UNUSED(timeout); +#ifndef QT_NO_QWS_MULTIPROCESS + if (memlock && !memlock->lock(QWSLock::BackingStore)) + return false; +#endif +#ifndef QT_NO_THREAD + threadLock.lock(); +#endif + return true; +} + +void QWSMemorySurface::unlock() +{ +#ifndef QT_NO_THREAD + threadLock.unlock(); +#endif +#ifndef QT_NO_QWS_MULTIPROCESS + if (memlock) + memlock->unlock(QWSLock::BackingStore); +#endif +} + +QWSMemorySurface::QWSMemorySurface() + : QWSWindowSurface() +#ifndef QT_NO_QWS_MULTIPROCESS + , memlock(0) +#endif +{ + setSurfaceFlags(Buffered); +} + +QWSMemorySurface::QWSMemorySurface(QWidget *w) + : QWSWindowSurface(w) +{ + SurfaceFlags flags = Buffered; + if (isWidgetOpaque(w)) + flags |= Opaque; + setSurfaceFlags(flags); + +#ifndef QT_NO_QWS_MULTIPROCESS + memlock = QWSDisplay::Data::getClientLock(); +#endif +} + +QWSMemorySurface::~QWSMemorySurface() +{ +} + + +QImage::Format +QWSMemorySurface::preferredImageFormat(const QWidget *widget) const +{ + QScreen *screen = getScreen(widget); + const int depth = screen->depth(); + const bool opaque = isWidgetOpaque(widget); + + if (!opaque) { + if (depth <= 12) + return QImage::Format_ARGB4444_Premultiplied; + else if (depth <= 15) + return QImage::Format_ARGB8555_Premultiplied; + else if (depth <= 16) + return QImage::Format_ARGB8565_Premultiplied; + else if (depth <= 18) + return QImage::Format_ARGB6666_Premultiplied; + else + return QImage::Format_ARGB32_Premultiplied; + } + + QImage::Format format = screen->pixelFormat(); + if (format > QImage::Format_Indexed8) // ### assumes all new image formats supports a QPainter + return format; + + if (depth <= 12) + return QImage::Format_RGB444; + else if (depth <= 15) + return QImage::Format_RGB555; + else if (depth <= 16) + return QImage::Format_RGB16; + else if (depth <= 18) + return QImage::Format_RGB666; + else if (depth <= 24) + return QImage::Format_RGB888; + else + return QImage::Format_ARGB32_Premultiplied; +} + +#ifndef QT_NO_QWS_MULTIPROCESS +void QWSMemorySurface::setLock(int lockId) +{ + if (memlock && memlock->id() == lockId) + return; + delete memlock; + memlock = (lockId == -1 ? 0 : new QWSLock(lockId)); + return; +} +#endif // QT_NO_QWS_MULTIPROCESS + +bool QWSMemorySurface::isValid() const +{ + if (img.isNull()) + return true; + + const QWidget *win = window(); + if (preferredImageFormat(win) != img.format()) + return false; + + if (isOpaque() != isWidgetOpaque(win)) // XXX: use QWidgetPrivate::isOpaque() + return false; + + return true; +} + +// ### copied from qwindowsurface_raster.cpp -- should be cross-platform +void QWSMemorySurface::beginPaint(const QRegion &rgn) +{ + if (!isWidgetOpaque(window())) { + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_Source); + const QVector<QRect> rects = rgn.rects(); + const QColor blank = Qt::transparent; + for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { + QRect r = *it; +#ifdef Q_BACKINGSTORE_SUBSURFACES + r.translate(painterOffset()); +#endif + p.fillRect(r, blank); + } + } + QWSWindowSurface::beginPaint(rgn); +} + +// from qwindowsurface.cpp +extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); + +bool QWSMemorySurface::scroll(const QRegion &area, int dx, int dy) +{ + const QVector<QRect> rects = area.rects(); + for (int i = 0; i < rects.size(); ++i) + qt_scrollRectInImage(img, rects.at(i), QPoint(dx, dy)); + + return true; +} + +QPoint QWSMemorySurface::painterOffset() const +{ + const QWidget *w = window(); + if (!w) + return QPoint(); + + if (w->mask().isEmpty()) + return QWSWindowSurface::painterOffset(); + + const QRegion region = w->mask() + & w->frameGeometry().translated(-w->geometry().topLeft()); + return -region.boundingRect().topLeft(); +} + +QWSLocalMemSurface::QWSLocalMemSurface() + : QWSMemorySurface(), mem(0), memsize(0) +{ +} + +QWSLocalMemSurface::QWSLocalMemSurface(QWidget *w) + : QWSMemorySurface(w), mem(0), memsize(0) +{ +} + +QWSLocalMemSurface::~QWSLocalMemSurface() +{ + if (memsize) + delete[] mem; +} + +void QWSLocalMemSurface::setGeometry(const QRect &rect) +{ + QSize size = rect.size(); + + QWidget *win = window(); + if (win && !win->mask().isEmpty()) { + const QRegion region = win->mask() + & rect.translated(-win->geometry().topLeft()); + size = region.boundingRect().size(); + } + + uchar *deleteLater = 0; + // In case of a Hide event we need to delete the memory after sending the + // event to the server in order to let the server animate the event. + if (size.isEmpty()) { + deleteLater = mem; + mem = 0; + } + + if (img.size() != size) { + delete[] mem; + if (size.isEmpty()) { + mem = 0; + img = QImage(); + } else { + const QImage::Format format = preferredImageFormat(win); + const int bpl = nextMulOf4(bytesPerPixel(format) * size.width()); + const int memsize = bpl * size.height(); + mem = new uchar[memsize]; + img = QImage(mem, size.width(), size.height(), bpl, format); + setImageMetrics(img, win); + } + } + + QWSWindowSurface::setGeometry(rect); + delete[] deleteLater; +} + +QByteArray QWSLocalMemSurface::permanentState() const +{ + QByteArray array; + array.resize(sizeof(uchar*) + 3 * sizeof(int) + + sizeof(SurfaceFlags)); + + char *ptr = array.data(); + + *reinterpret_cast<uchar**>(ptr) = mem; + ptr += sizeof(uchar*); + + reinterpret_cast<int*>(ptr)[0] = img.width(); + reinterpret_cast<int*>(ptr)[1] = img.height(); + ptr += 2 * sizeof(int); + + *reinterpret_cast<int *>(ptr) = img.format(); + ptr += sizeof(int); + + *reinterpret_cast<SurfaceFlags*>(ptr) = surfaceFlags(); + + return array; +} + +void QWSLocalMemSurface::setPermanentState(const QByteArray &data) +{ + int width; + int height; + QImage::Format format; + SurfaceFlags flags; + + const char *ptr = data.constData(); + + mem = *reinterpret_cast<uchar* const*>(ptr); + ptr += sizeof(uchar*); + + width = reinterpret_cast<const int*>(ptr)[0]; + height = reinterpret_cast<const int*>(ptr)[1]; + ptr += 2 * sizeof(int); + + format = QImage::Format(*reinterpret_cast<const int*>(ptr)); + ptr += sizeof(int); + + flags = *reinterpret_cast<const SurfaceFlags*>(ptr); + + const int bpl = nextMulOf4(bytesPerPixel(format) * width); + QWSMemorySurface::img = QImage(mem, width, height, bpl, format); + setSurfaceFlags(flags); +} + +void QWSLocalMemSurface::releaseSurface() +{ + mem = 0; + img = QImage(); +} + +#ifndef QT_NO_QWS_MULTIPROCESS + +QWSSharedMemSurface::QWSSharedMemSurface() + : QWSMemorySurface() +{ +} + +QWSSharedMemSurface::QWSSharedMemSurface(QWidget *widget) + : QWSMemorySurface(widget) +{ +} + +QWSSharedMemSurface::~QWSSharedMemSurface() +{ + // mem.detach() is done automatically by ~QSharedMemory +} + +bool QWSSharedMemSurface::setMemory(int memId) +{ + if (mem.id() == memId) + return true; + + mem.detach(); + if (!mem.attach(memId)) { + perror("QWSSharedMemSurface: attaching to shared memory"); + qCritical("QWSSharedMemSurface: Error attaching to" + " shared memory 0x%x", memId); + return false; + } + + return true; +} + +#ifdef QT_QWS_CLIENTBLIT +void QWSSharedMemSurface::setDirectRegion(const QRegion &r, int id) +{ + QWSMemorySurface::setDirectRegion(r, id); + if(mem.address()) + *(uint *)mem.address() = id; +} + +const QRegion QWSSharedMemSurface::directRegion() const +{ + QWSSharedMemory *cmem = const_cast<QWSSharedMemory *>(&mem); + if (cmem->address() && ((int*)cmem->address())[0] == directRegionId()) + return QWSMemorySurface::directRegion(); + else + return QRegion(); +} +#endif + +void QWSSharedMemSurface::setPermanentState(const QByteArray &data) +{ + int memId; + int width; + int height; + int lockId; + QImage::Format format; + SurfaceFlags flags; + + const int *ptr = reinterpret_cast<const int*>(data.constData()); + + memId = ptr[0]; + width = ptr[1]; + height = ptr[2]; + lockId = ptr[3]; + format = QImage::Format(ptr[4]); + flags = SurfaceFlags(ptr[5]); + + setSurfaceFlags(flags); + setMemory(memId); + setLock(lockId); + +#ifdef QT_QWS_CLIENTBLIT + uchar *base = static_cast<uchar*>(mem.address()) + sizeof(uint); +#else + uchar *base = static_cast<uchar*>(mem.address()); +#endif + const int bpl = nextMulOf4(bytesPerPixel(format) * width); + QWSMemorySurface::img = QImage(base, width, height, bpl, format); +} + +void QWSSharedMemSurface::setGeometry(const QRect &rect) +{ + const QSize size = rect.size(); + if (img.size() != size) { + if (size.isEmpty()) { + mem.detach(); + img = QImage(); + } else { + mem.detach(); + + QWidget *win = window(); + const QImage::Format format = preferredImageFormat(win); + const int bpl = nextMulOf4(bytesPerPixel(format) * size.width()); +#ifdef QT_QWS_CLIENTBLIT + const int imagesize = bpl * size.height() + sizeof(uint); +#else + const int imagesize = bpl * size.height(); +#endif + if (!mem.create(imagesize)) { + perror("QWSSharedMemSurface::setGeometry allocating shared memory"); + qFatal("Error creating shared memory of size %d", imagesize); + } +#ifdef QT_QWS_CLIENTBLIT + *((uint *)mem.address()) = 0; + uchar *base = static_cast<uchar*>(mem.address()) + sizeof(uint); +#else + uchar *base = static_cast<uchar*>(mem.address()); +#endif + img = QImage(base, size.width(), size.height(), bpl, format); + setImageMetrics(img, win); + } + } + + QWSWindowSurface::setGeometry(rect); +} + +QByteArray QWSSharedMemSurface::permanentState() const +{ + QByteArray array; + array.resize(6 * sizeof(int)); + + int *ptr = reinterpret_cast<int*>(array.data()); + + ptr[0] = mem.id(); + ptr[1] = img.width(); + ptr[2] = img.height(); + ptr[3] = (memlock ? memlock->id() : -1); + ptr[4] = int(img.format()); + ptr[5] = int(surfaceFlags()); + + return array; +} + +void QWSSharedMemSurface::releaseSurface() +{ + mem.detach(); + img = QImage(); +} + +#endif // QT_NO_QWS_MULTIPROCESS + +#ifndef QT_NO_PAINTONSCREEN + +QWSOnScreenSurface::QWSOnScreenSurface(QWidget *w) + : QWSMemorySurface(w) +{ + attachToScreen(getScreen(w)); + setSurfaceFlags(Opaque); +} + +QWSOnScreenSurface::QWSOnScreenSurface() + : QWSMemorySurface() +{ + setSurfaceFlags(Opaque); +} + +void QWSOnScreenSurface::attachToScreen(const QScreen *s) +{ + screen = s; + uchar *base = screen->base(); + QImage::Format format = screen->pixelFormat(); + + if (format == QImage::Format_Invalid || format == QImage::Format_Indexed8) { + //### currently we have no paint engine for indexed image formats + qFatal("QWSOnScreenSurface::attachToScreen(): screen depth %d " + "not implemented", screen->depth()); + return; + } + QWSMemorySurface::img = QImage(base, screen->width(), screen->height(), + screen->linestep(), format ); +} + +QWSOnScreenSurface::~QWSOnScreenSurface() +{ +} + +QPoint QWSOnScreenSurface::painterOffset() const +{ + return geometry().topLeft() + QWSWindowSurface::painterOffset(); +} + +bool QWSOnScreenSurface::isValid() const +{ + const QWidget *win = window(); + if (screen != getScreen(win)) + return false; + if (img.isNull()) + return false; + return QScreen::isWidgetPaintOnScreen(win); +} + +QByteArray QWSOnScreenSurface::permanentState() const +{ + QByteArray array; + array.resize(sizeof(int)); + int *ptr = reinterpret_cast<int*>(array.data()); + ptr[0] = QApplication::desktop()->screenNumber(window()); + return array; +} + +void QWSOnScreenSurface::setPermanentState(const QByteArray &data) +{ + const int *ptr = reinterpret_cast<const int*>(data.constData()); + const int screenNo = ptr[0]; + + QScreen *screen = qt_screen; + if (screenNo > 0) + screen = qt_screen->subScreens().at(screenNo); + attachToScreen(screen); +} + +#endif // QT_NO_PAINTONSCREEN + +#ifndef QT_NO_PAINT_DEBUG + +QWSYellowSurface::QWSYellowSurface(bool isClient) + : QWSWindowSurface(), delay(10) +{ + if (isClient) { + setWinId(QWidget::qwsDisplay()->takeId()); + QWidget::qwsDisplay()->nameRegion(winId(), + QLatin1String("Debug flush paint"), + QLatin1String("Silly yellow thing")); + QWidget::qwsDisplay()->setAltitude(winId(), 1, true); + } + setSurfaceFlags(Buffered); +} + +QWSYellowSurface::~QWSYellowSurface() +{ +} + +QByteArray QWSYellowSurface::permanentState() const +{ + QByteArray array; + array.resize(2 * sizeof(int)); + + int *ptr = reinterpret_cast<int*>(array.data()); + ptr[0] = surfaceSize.width(); + ptr[1] = surfaceSize.height(); + + return array; +} + +void QWSYellowSurface::setPermanentState(const QByteArray &data) +{ + const int *ptr = reinterpret_cast<const int*>(data.constData()); + + const int width = ptr[0]; + const int height = ptr[1]; + + img = QImage(width, height, QImage::Format_ARGB32); + img.fill(qRgba(255,255,31,127)); +} + +void QWSYellowSurface::flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset) +{ + Q_UNUSED(offset); + + QWSDisplay *display = QWidget::qwsDisplay(); + QRegion rgn = region; + + if (widget) + rgn.translate(widget->mapToGlobal(QPoint(0, 0))); + + surfaceSize = region.boundingRect().size(); + + const int id = winId(); + display->requestRegion(id, key(), permanentState(), rgn); + display->setAltitude(id, 1, true); + display->repaintRegion(id, 0, false, rgn); + + ::usleep(500 * delay); + display->requestRegion(id, key(), permanentState(), QRegion()); + ::usleep(500 * delay); +} + +#endif // QT_NO_PAINT_DEBUG + +#ifndef QT_NO_DIRECTPAINTER + +static inline QScreen *getPrimaryScreen() +{ + QScreen *screen = QScreen::instance(); + if (!screen->base()) { + QList<QScreen*> subScreens = screen->subScreens(); + if (subScreens.size() < 1) + return 0; + screen = subScreens.at(0); + } + return screen; +} + +QWSDirectPainterSurface::QWSDirectPainterSurface(bool isClient, + QDirectPainter::SurfaceFlag flags) + : QWSWindowSurface(), flushingRegionEvents(false), doLocking(false) +{ + setSurfaceFlags(Opaque); + synchronous = (flags == QDirectPainter::ReservedSynchronous); + + if (isClient) { + setWinId(QWidget::qwsDisplay()->takeId()); + QWidget::qwsDisplay()->nameRegion(winId(), + QLatin1String("QDirectPainter reserved space"), + QLatin1String("reserved")); + } else { + setWinId(0); + } + _screen = QScreen::instance(); + if (!_screen->base()) { + QList<QScreen*> subScreens = _screen->subScreens(); + if (subScreens.size() < 1) + _screen = 0; + else + _screen = subScreens.at(0); + } +} + +QWSDirectPainterSurface::~QWSDirectPainterSurface() +{ + if (winId() && QWSDisplay::instance()) // make sure not in QApplication destructor + QWidget::qwsDisplay()->destroyRegion(winId()); +} + +void QWSDirectPainterSurface::setRegion(const QRegion ®ion) +{ + const int id = winId(); + QWidget::qwsDisplay()->requestRegion(id, key(), permanentState(), region); +#ifndef QT_NO_QWS_MULTIPROCESS + if (synchronous) + QWSDisplay::instance()->d->waitForRegionAck(id); +#endif +} + +void QWSDirectPainterSurface::flush(QWidget *, const QRegion &r, const QPoint &) +{ + QWSDisplay::instance()->repaintRegion(winId(), 0, true, r); +} + +QByteArray QWSDirectPainterSurface::permanentState() const +{ + QByteArray res; + if (isRegionReserved()) + res.append( 'r'); + return res; +} + +void QWSDirectPainterSurface::setPermanentState(const QByteArray &ba) +{ + if (ba.size() > 0 && ba.at(0) == 'r') + setReserved(); + setSurfaceFlags(surfaceFlags() | Opaque); +} + +void QWSDirectPainterSurface::beginPaint(const QRegion ®ion) +{ + QWSWindowSurface::beginPaint(region); +#ifndef QT_NO_QWS_MULTIPROCESS + if (!synchronous) { + flushingRegionEvents = true; + QWSDisplay::instance()->d->waitForRegionEvents(winId(), doLocking); + flushingRegionEvents = false; + } +#endif +} + +bool QWSDirectPainterSurface::hasPendingRegionEvents() const +{ +#ifndef QT_NO_QWS_MULTIPROCESS + if (synchronous) + return false; + + return QWSDisplay::instance()->d->hasPendingRegionEvents(); +#else + return false; +#endif +} + +bool QWSDirectPainterSurface::lock(int timeout) +{ +#ifndef QT_NO_THREAD + threadLock.lock(); +#endif + Q_UNUSED(timeout); + if (doLocking) + QWSDisplay::grab(true); + return true; +} + +void QWSDirectPainterSurface::unlock() +{ + if (doLocking) + QWSDisplay::ungrab(); +#ifndef QT_NO_THREAD + threadLock.unlock(); +#endif +} + +#endif // QT_NO_DIRECTPAINTER + +QT_END_NAMESPACE diff --git a/src/gui/painting/qwindowsurface_qws_p.h b/src/gui/painting/qwindowsurface_qws_p.h new file mode 100644 index 0000000000..fd56c814a3 --- /dev/null +++ b/src/gui/painting/qwindowsurface_qws_p.h @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_QWS_P_H +#define QWINDOWSURFACE_QWS_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 "qwindowsurface_p.h" +#include <qregion.h> +#include <qimage.h> +#include <qdirectpainter_qws.h> +#include <qmutex.h> +#include <private/qwssharedmemory_p.h> + +QT_BEGIN_NAMESPACE + +class QScreen; +class QWSWindowSurfacePrivate; + +class Q_GUI_EXPORT QWSWindowSurface : public QWindowSurface +{ +public: + QWSWindowSurface(); + QWSWindowSurface(QWidget *widget); + ~QWSWindowSurface(); + + virtual bool isValid() const = 0; + + virtual void setGeometry(const QRect &rect); + virtual void setGeometry(const QRect &rect, const QRegion &mask); + virtual void flush(QWidget *widget, const QRegion ®ion, + const QPoint &offset); + + virtual bool move(const QPoint &offset); + virtual QRegion move(const QPoint &offset, const QRegion &newClip); + + virtual QPoint painterOffset() const; // remove!!! + + virtual void beginPaint(const QRegion &); + virtual void endPaint(const QRegion &); + + virtual bool lock(int timeout = -1); + virtual void unlock(); + + virtual QString key() const = 0; + + // XXX: not good enough + virtual QByteArray transientState() const; + virtual QByteArray permanentState() const; + virtual void setTransientState(const QByteArray &state); + virtual void setPermanentState(const QByteArray &state); + + virtual QImage image() const = 0; + virtual QPaintDevice *paintDevice() = 0; + + const QRegion clipRegion() const; + void setClipRegion(const QRegion &); + +#ifdef QT_QWS_CLIENTBLIT + virtual const QRegion directRegion() const; + virtual int directRegionId() const; + virtual void setDirectRegion(const QRegion &, int); +#endif + + enum SurfaceFlag { + RegionReserved = 0x1, + Buffered = 0x2, + Opaque = 0x4 + }; + Q_DECLARE_FLAGS(SurfaceFlags, SurfaceFlag) + + SurfaceFlags surfaceFlags() const; + + inline bool isRegionReserved() const { + return surfaceFlags() & RegionReserved; + } + inline bool isBuffered() const { return surfaceFlags() & Buffered; } + inline bool isOpaque() const { return surfaceFlags() & Opaque; } + + int winId() const; + virtual void releaseSurface(); + +protected: + void setSurfaceFlags(SurfaceFlags type); + void setWinId(int id); + +private: + friend class QWidgetPrivate; + + void invalidateBuffer(); + + QWSWindowSurfacePrivate *d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWSWindowSurface::SurfaceFlags) + +class QWSWindowSurfacePrivate +{ +public: + QWSWindowSurfacePrivate(); + + void setWinId(int id); + + QWSWindowSurface::SurfaceFlags flags; + QRegion clip; +#ifdef QT_QWS_CLIENTBLIT + QRegion direct; + int directId; +#endif + + int winId; +}; + +class QWSLock; + +class Q_GUI_EXPORT QWSMemorySurface : public QWSWindowSurface +{ +public: + QWSMemorySurface(); + QWSMemorySurface(QWidget *widget); + ~QWSMemorySurface(); + + bool isValid() const; + + QPaintDevice *paintDevice() { return &img; } + bool scroll(const QRegion &area, int dx, int dy); + + QImage image() const { return img; } + QPoint painterOffset() const; + + void beginPaint(const QRegion &rgn); + + bool lock(int timeout = -1); + void unlock(); + +protected: + QImage::Format preferredImageFormat(const QWidget *widget) const; + +#ifndef QT_NO_QWS_MULTIPROCESS + void setLock(int lockId); + QWSLock *memlock; +#endif +#ifndef QT_NO_THREAD + QMutex threadLock; +#endif + + QImage img; +}; + +class Q_GUI_EXPORT QWSLocalMemSurface : public QWSMemorySurface +{ +public: + QWSLocalMemSurface(); + QWSLocalMemSurface(QWidget *widget); + ~QWSLocalMemSurface(); + + void setGeometry(const QRect &rect); + + QString key() const { return QLatin1String("mem"); } + QByteArray permanentState() const; + + void setPermanentState(const QByteArray &data); + virtual void releaseSurface(); +protected: + uchar *mem; + int memsize; +}; + +#ifndef QT_NO_QWS_MULTIPROCESS +class Q_GUI_EXPORT QWSSharedMemSurface : public QWSMemorySurface +{ +public: + QWSSharedMemSurface(); + QWSSharedMemSurface(QWidget *widget); + ~QWSSharedMemSurface(); + + void setGeometry(const QRect &rect); + + QString key() const { return QLatin1String("shm"); } + QByteArray permanentState() const; + + void setPermanentState(const QByteArray &data); + +#ifdef QT_QWS_CLIENTBLIT + virtual void setDirectRegion(const QRegion &, int); + virtual const QRegion directRegion() const; +#endif + virtual void releaseSurface(); + +private: + bool setMemory(int memId); + + QWSSharedMemory mem; +}; +#endif // QT_NO_QWS_MULTIPROCESS + +#ifndef QT_NO_PAINTONSCREEN +class Q_GUI_EXPORT QWSOnScreenSurface : public QWSMemorySurface +{ +public: + QWSOnScreenSurface(); + QWSOnScreenSurface(QWidget *widget); + ~QWSOnScreenSurface(); + + bool isValid() const; + QPoint painterOffset() const; + + QString key() const { return QLatin1String("OnScreen"); } + QByteArray permanentState() const; + + void setPermanentState(const QByteArray &data); + +private: + void attachToScreen(const QScreen *screen); + + const QScreen *screen; +}; +#endif // QT_NO_PAINTONSCREEN + +#ifndef QT_NO_PAINT_DEBUG +class Q_GUI_EXPORT QWSYellowSurface : public QWSWindowSurface +{ +public: + QWSYellowSurface(bool isClient = false); + ~QWSYellowSurface(); + + void setDelay(int msec) { delay = msec; } + + bool isValid() const { return true; } + + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + + QString key() const { return QLatin1String("Yellow"); } + QByteArray permanentState() const; + + void setPermanentState(const QByteArray &data); + + QPaintDevice *paintDevice() { return &img; } + QImage image() const { return img; } + +private: + int delay; + QSize surfaceSize; // client side + QImage img; // server side +}; +#endif // QT_NO_PAINT_DEBUG + +#ifndef QT_NO_DIRECTPAINTER + +class QScreen; + +class Q_GUI_EXPORT QWSDirectPainterSurface : public QWSWindowSurface +{ +public: + QWSDirectPainterSurface(bool isClient = false, + QDirectPainter::SurfaceFlag flags = QDirectPainter::NonReserved); + ~QWSDirectPainterSurface(); + + void setReserved() { setSurfaceFlags(RegionReserved); } + + void setGeometry(const QRect &rect) { setRegion(rect); } + + void setRegion(const QRegion ®ion); + QRegion region() const { return clipRegion(); } + + void flush(QWidget*, const QRegion &, const QPoint &); + + bool isValid() const { return false; } + + QString key() const { return QLatin1String("DirectPainter"); } + QByteArray permanentState() const; + + void setPermanentState(const QByteArray &); + + QImage image() const { return QImage(); } + QPaintDevice *paintDevice() { return 0; } + + // hw: get rid of this + WId windowId() const { return static_cast<WId>(winId()); } + + QScreen *screen() const { return _screen; } + + void beginPaint(const QRegion &); + bool lock(int timeout = -1); + void unlock(); + + void setLocking(bool b) { doLocking = b; } + + bool hasPendingRegionEvents() const; + +private: + QScreen *_screen; +#ifndef QT_NO_THREAD + QMutex threadLock; +#endif + + friend void qt_directpainter_region(QDirectPainter*, const QRegion&, int); + bool flushingRegionEvents; + bool synchronous; + bool doLocking; +}; + +#endif // QT_NO_DIRECTPAINTER + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_QWS_P_H diff --git a/src/gui/painting/qwindowsurface_raster.cpp b/src/gui/painting/qwindowsurface_raster.cpp new file mode 100644 index 0000000000..9860841640 --- /dev/null +++ b/src/gui/painting/qwindowsurface_raster.cpp @@ -0,0 +1,487 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> + +#include <qglobal.h> // for Q_WS_WIN define (non-PCH) +#ifdef Q_WS_WIN +#include <qlibrary.h> +#include <qt_windows.h> +#endif + +#include <QtGui/qpaintdevice.h> +#include <QtGui/qwidget.h> + +#include "private/qwindowsurface_raster_p.h" +#include "private/qnativeimage_p.h" +#include "private/qwidget_p.h" + +#ifdef Q_WS_X11 +#include "private/qpixmap_x11_p.h" +#include "private/qt_x11_p.h" +#include "private/qwidget_p.h" +#include "qx11info_x11.h" +#endif +#include "private/qdrawhelper_p.h" + +#ifdef Q_WS_MAC +#include <private/qt_cocoa_helpers_mac_p.h> +#include <QMainWindow> +#include <private/qmainwindowlayout_p.h> +#include <QToolBar> +#endif + +QT_BEGIN_NAMESPACE + +class QRasterWindowSurfacePrivate +{ +public: + QNativeImage *image; + +#ifdef Q_WS_X11 + GC gc; +#ifndef QT_NO_MITSHM + uint needsSync : 1; +#endif +#ifndef QT_NO_XRENDER + uint translucentBackground : 1; +#endif +#endif + uint inSetGeometry : 1; +}; + +QRasterWindowSurface::QRasterWindowSurface(QWidget *window, bool setDefaultSurface) + : QWindowSurface(window, setDefaultSurface), d_ptr(new QRasterWindowSurfacePrivate) +{ +#ifdef Q_WS_X11 + d_ptr->gc = XCreateGC(X11->display, window->handle(), 0, 0); +#ifndef QT_NO_XRENDER + d_ptr->translucentBackground = X11->use_xrender + && window->x11Info().depth() == 32; +#endif +#ifndef QT_NO_MITHSM + d_ptr->needsSync = false; +#endif +#endif + d_ptr->image = 0; + d_ptr->inSetGeometry = false; + +#ifdef QT_MAC_USE_COCOA + needsFlush = false; + regionToFlush = QRegion(); +#endif // QT_MAC_USE_COCOA +} + + +QRasterWindowSurface::~QRasterWindowSurface() +{ +#ifdef Q_WS_X11 + XFreeGC(X11->display, d_ptr->gc); +#endif + if (d_ptr->image) + delete d_ptr->image; +} + + +QPaintDevice *QRasterWindowSurface::paintDevice() +{ + return &d_ptr->image->image; +} + +#if defined(Q_WS_X11) && !defined(QT_NO_MITSHM) +void QRasterWindowSurface::syncX() +{ + // delay writing to the backbuffer until we know for sure X is done reading from it + if (d_ptr->needsSync) { + XSync(X11->display, false); + d_ptr->needsSync = false; + } +} +#endif + +void QRasterWindowSurface::beginPaint(const QRegion &rgn) +{ +#if defined(Q_WS_X11) && !defined(QT_NO_MITSHM) + syncX(); +#endif + +#if (defined(Q_WS_X11) && !defined(QT_NO_XRENDER)) || (defined(Q_WS_WIN) && !defined(Q_WS_WINCE)) + if (!qt_widget_private(window())->isOpaque && window()->testAttribute(Qt::WA_TranslucentBackground)) { +#if defined(Q_WS_WIN) && !defined(Q_WS_WINCE) + if (d_ptr->image->image.format() != QImage::Format_ARGB32_Premultiplied) + prepareBuffer(QImage::Format_ARGB32_Premultiplied, window()); +#endif + QPainter p(&d_ptr->image->image); + p.setCompositionMode(QPainter::CompositionMode_Source); + const QVector<QRect> rects = rgn.rects(); + const QColor blank = Qt::transparent; + for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { + p.fillRect(*it, blank); + } + } +#else + Q_UNUSED(rgn); +#endif +} + +void QRasterWindowSurface::flush(QWidget *widget, const QRegion &rgn, const QPoint &offset) +{ + Q_D(QRasterWindowSurface); + + // Not ready for painting yet, bail out. This can happen in + // QWidget::create_sys() + if (!d->image || rgn.rectCount() == 0) + return; + +#ifdef Q_WS_WIN + QRect br = rgn.boundingRect(); + +#ifndef Q_WS_WINCE + if (!qt_widget_private(window())->isOpaque + && window()->testAttribute(Qt::WA_TranslucentBackground) + && (qt_widget_private(window())->data.window_flags & Qt::FramelessWindowHint)) + { + QRect r = window()->frameGeometry(); + QPoint frameOffset = qt_widget_private(window())->frameStrut().topLeft(); + QRect dirtyRect = br.translated(offset + frameOffset); + + SIZE size = {r.width(), r.height()}; + POINT ptDst = {r.x(), r.y()}; + POINT ptSrc = {0, 0}; + BLENDFUNCTION blend = {AC_SRC_OVER, 0, (int)(255.0 * window()->windowOpacity()), Q_AC_SRC_ALPHA}; + RECT dirty = {dirtyRect.x(), dirtyRect.y(), + dirtyRect.x() + dirtyRect.width(), dirtyRect.y() + dirtyRect.height()}; + Q_UPDATELAYEREDWINDOWINFO info = {sizeof(info), NULL, &ptDst, &size, d->image->hdc, &ptSrc, 0, &blend, Q_ULW_ALPHA, &dirty}; + ptrUpdateLayeredWindowIndirect(window()->internalWinId(), &info); + } else +#endif + { + QPoint wOffset = qt_qwidget_data(widget)->wrect.topLeft(); + + HDC widget_dc = widget->getDC(); + + QRect wbr = br.translated(-wOffset); + BitBlt(widget_dc, wbr.x(), wbr.y(), wbr.width(), wbr.height(), + d->image->hdc, br.x() + offset.x(), br.y() + offset.y(), SRCCOPY); + widget->releaseDC(widget_dc); + } + +#ifndef QT_NO_DEBUG + static bool flush = !qgetenv("QT_FLUSH_WINDOWSURFACE").isEmpty(); + if (flush) { + SelectObject(qt_win_display_dc(), GetStockObject(BLACK_BRUSH)); + Rectangle(qt_win_display_dc(), 0, 0, d->image->width() + 2, d->image->height() + 2); + BitBlt(qt_win_display_dc(), 1, 1, d->image->width(), d->image->height(), + d->image->hdc, 0, 0, SRCCOPY); + } +#endif + +#endif + +#ifdef Q_WS_X11 + extern void *qt_getClipRects(const QRegion &r, int &num); // in qpaintengine_x11.cpp + extern QWidgetData* qt_widget_data(QWidget *); + QPoint wOffset = qt_qwidget_data(widget)->wrect.topLeft(); + + if (widget->window() != window()) { + XFreeGC(X11->display, d_ptr->gc); + d_ptr->gc = XCreateGC(X11->display, widget->handle(), 0, 0); + } + + QRegion wrgn(rgn); + if (!wOffset.isNull()) + wrgn.translate(-wOffset); + QRect wbr = wrgn.boundingRect(); + + if (wrgn.rectCount() != 1) { + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(wrgn, num); + XSetClipRectangles(X11->display, d_ptr->gc, 0, 0, rects, num, YXBanded); + } + + QRect br = rgn.boundingRect().translated(offset); +#ifndef QT_NO_MITSHM + if (d_ptr->image->xshmpm) { + XCopyArea(X11->display, d_ptr->image->xshmpm, widget->handle(), d_ptr->gc, + br.x(), br.y(), br.width(), br.height(), wbr.x(), wbr.y()); + d_ptr->needsSync = true; + } else if (d_ptr->image->xshmimg) { + const QImage &src = d->image->image; + br = br.intersected(src.rect()); + XShmPutImage(X11->display, widget->handle(), d_ptr->gc, d_ptr->image->xshmimg, + br.x(), br.y(), wbr.x(), wbr.y(), br.width(), br.height(), False); + d_ptr->needsSync = true; + } else +#endif + { + const QImage &src = d->image->image; + br = br.intersected(src.rect()); + if (src.format() != QImage::Format_RGB32 || widget->x11Info().depth() < 24) { + Q_ASSERT(src.depth() >= 16); + const QImage sub_src(src.scanLine(br.y()) + br.x() * (uint(src.depth()) / 8), + br.width(), br.height(), src.bytesPerLine(), src.format()); + QX11PixmapData *data = new QX11PixmapData(QPixmapData::PixmapType); + data->xinfo = widget->x11Info(); + data->fromImage(sub_src, Qt::NoOpaqueDetection); + QPixmap pm = QPixmap(data); + XCopyArea(X11->display, pm.handle(), widget->handle(), d_ptr->gc, 0 , 0 , br.width(), br.height(), wbr.x(), wbr.y()); + } else { + // qpaintengine_x11.cpp + extern void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image, Drawable hd, GC gc, Display *dpy, Visual *visual, int depth); + qt_x11_drawImage(br, wbr.topLeft(), src, widget->handle(), d_ptr->gc, X11->display, (Visual *)widget->x11Info().visual(), widget->x11Info().depth()); + } + } + + if (wrgn.rectCount() != 1) + XSetClipMask(X11->display, d_ptr->gc, XNone); +#endif // FALCON + +#ifdef Q_WS_MAC + + Q_UNUSED(offset); + + // This is mainly done for native components like native "open file" dialog. + if (widget->testAttribute(Qt::WA_DontShowOnScreen)) { + return; + } + +#ifdef QT_MAC_USE_COCOA + + this->needsFlush = true; + this->regionToFlush += rgn; + + // The actual flushing will be processed in [view drawRect:rect] + qt_mac_setNeedsDisplay(widget); + +#else + // Get a context for the widget. + CGContextRef context; + CGrafPtr port = GetWindowPort(qt_mac_window_for(widget)); + QDBeginCGContext(port, &context); + CGContextRetain(context); + CGContextSaveGState(context); + + // Flip context. + CGContextTranslateCTM(context, 0, widget->height()); + CGContextScaleCTM(context, 1, -1); + + // Clip to region. + const QVector<QRect> &rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) { + const QRect &rect = rects.at(i); + CGContextAddRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height())); + } + CGContextClip(context); + + QRect r = rgn.boundingRect(); + const CGRect area = CGRectMake(r.x(), r.y(), r.width(), r.height()); + CGImageRef image = CGBitmapContextCreateImage(d->image->cg); + CGImageRef subImage = CGImageCreateWithImageInRect(image, area); + + qt_mac_drawCGImage(context, &area, subImage); + + CGImageRelease(subImage); + CGImageRelease(image); + + QDEndCGContext(port, &context); + + // Restore context. + CGContextRestoreGState(context); + CGContextRelease(context); +#endif // QT_MAC_USE_COCOA + +#endif // Q_WS_MAC + +#ifdef Q_OS_SYMBIAN + Q_UNUSED(widget); + Q_UNUSED(rgn); + Q_UNUSED(offset); +#endif +} + +void QRasterWindowSurface::setGeometry(const QRect &rect) +{ + QWindowSurface::setGeometry(rect); + Q_D(QRasterWindowSurface); + d->inSetGeometry = true; + if (d->image == 0 || d->image->width() < rect.width() || d->image->height() < rect.height()) { +#if (defined(Q_WS_X11) && !defined(QT_NO_XRENDER)) || (defined(Q_WS_WIN) && !defined(Q_WS_WINCE)) +#ifndef Q_WS_WIN + if (d_ptr->translucentBackground) +#else + if (!qt_widget_private(window())->isOpaque) +#endif + prepareBuffer(QImage::Format_ARGB32_Premultiplied, window()); + else +#endif + prepareBuffer(QNativeImage::systemFormat(), window()); + } + d->inSetGeometry = false; + +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + QMainWindow* mWindow = qobject_cast<QMainWindow*>(window()); + if (mWindow) { + QMainWindowLayout *mLayout = qobject_cast<QMainWindowLayout*>(mWindow->layout()); + QList<QToolBar *> toolbarList = mLayout->qtoolbarsInUnifiedToolbarList; + + for (int i = 0; i < toolbarList.size(); ++i) { + QToolBar* toolbar = toolbarList.at(i); + if (mLayout->toolBarArea(toolbar) == Qt::TopToolBarArea) { + QWidget* tbWidget = (QWidget*) toolbar; + if (tbWidget->d_func()->unifiedSurface) { + tbWidget->d_func()->unifiedSurface->setGeometry(rect); + } + } + } + } +#endif // Q_WS_MAC && QT_MAC_USE_COCOA + +} + +// from qwindowsurface.cpp +extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); + +bool QRasterWindowSurface::scroll(const QRegion &area, int dx, int dy) +{ +#ifdef Q_WS_WIN + Q_D(QRasterWindowSurface); + + if (!d->image || !d->image->hdc) + return false; + + QRect rect = area.boundingRect(); + BitBlt(d->image->hdc, rect.x()+dx, rect.y()+dy, rect.width(), rect.height(), + d->image->hdc, rect.x(), rect.y(), SRCCOPY); + + return true; +#else + Q_D(QRasterWindowSurface); + + if (!d->image || d->image->image.isNull()) + return false; + +#if defined(Q_WS_X11) && !defined(QT_NO_MITSHM) + syncX(); +#endif + + const QVector<QRect> rects = area.rects(); + for (int i = 0; i < rects.size(); ++i) + qt_scrollRectInImage(d->image->image, rects.at(i), QPoint(dx, dy)); + + return true; +#endif +} + +QWindowSurface::WindowSurfaceFeatures QRasterWindowSurface::features() const +{ + return QWindowSurface::AllFeatures; +} + +void QRasterWindowSurface::prepareBuffer(QImage::Format format, QWidget *widget) +{ + Q_D(QRasterWindowSurface); + + int width = window()->width(); + int height = window()->height(); + if (d->image) { + width = qMax(d->image->width(), width); + height = qMax(d->image->height(), height); + } + + if (width == 0 || height == 0) { + delete d->image; + d->image = 0; + return; + } + + QNativeImage *oldImage = d->image; + + d->image = new QNativeImage(width, height, format, false, widget); + + if (oldImage && d->inSetGeometry && hasStaticContents()) { + // Make sure we use the const version of bits() (no detach). + const uchar *src = const_cast<const QImage &>(oldImage->image).bits(); + uchar *dst = d->image->image.bits(); + + const int srcBytesPerLine = oldImage->image.bytesPerLine(); + const int dstBytesPerLine = d->image->image.bytesPerLine(); + const int bytesPerPixel = oldImage->image.depth() >> 3; + + QRegion staticRegion(staticContents()); + // Make sure we're inside the boundaries of the old image. + staticRegion &= QRect(0, 0, oldImage->image.width(), oldImage->image.height()); + const QVector<QRect> &rects = staticRegion.rects(); + const QRect *srcRect = rects.constData(); + + // Copy the static content of the old image into the new one. + int numRectsLeft = rects.size(); + do { + const int bytesOffset = srcRect->x() * bytesPerPixel; + const int dy = srcRect->y(); + + // Adjust src and dst to point to the right offset. + const uchar *s = src + dy * srcBytesPerLine + bytesOffset; + uchar *d = dst + dy * dstBytesPerLine + bytesOffset; + const int numBytes = srcRect->width() * bytesPerPixel; + + int numScanLinesLeft = srcRect->height(); + do { + ::memcpy(d, s, numBytes); + d += dstBytesPerLine; + s += srcBytesPerLine; + } while (--numScanLinesLeft); + + ++srcRect; + } while (--numRectsLeft); + } + + delete oldImage; +} + +#ifdef QT_MAC_USE_COCOA +CGContextRef QRasterWindowSurface::imageContext() +{ + Q_D(QRasterWindowSurface); + return d->image->cg; +} +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/painting/qwindowsurface_raster_p.h b/src/gui/painting/qwindowsurface_raster_p.h new file mode 100644 index 0000000000..06abcd3a4a --- /dev/null +++ b/src/gui/painting/qwindowsurface_raster_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_RASTER_P_H +#define QWINDOWSURFACE_RASTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include "private/qwindowsurface_p.h" + +#ifdef QT_MAC_USE_COCOA +# include <private/qt_cocoa_helpers_mac_p.h> +#endif // QT_MAC_USE_COCOA + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_WIN +#define Q_WS_EX_LAYERED 0x00080000 // copied from WS_EX_LAYERED in winuser.h +#define Q_LWA_ALPHA 0x00000002 // copied from LWA_ALPHA in winuser.h +#define Q_ULW_ALPHA 0x00000002 // copied from ULW_ALPHA in winuser.h +#define Q_AC_SRC_ALPHA 0x00000001 // copied from AC_SRC_ALPHA in winuser.h + +struct Q_UPDATELAYEREDWINDOWINFO { + DWORD cbSize; + HDC hdcDst; + const POINT *pptDst; + const SIZE *psize; + HDC hdcSrc; + const POINT *pptSrc; + COLORREF crKey; + const BLENDFUNCTION *pblend; + DWORD dwFlags; + const RECT *prcDirty; +}; + +typedef BOOL (WINAPI *PtrUpdateLayeredWindow)(HWND hwnd, HDC hdcDst, const POINT *pptDst, + const SIZE *psize, HDC hdcSrc, const POINT *pptSrc, COLORREF crKey, + const BLENDFUNCTION *pblend, DWORD dwflags); +typedef BOOL (WINAPI *PtrUpdateLayeredWindowIndirect)(HWND hwnd, const Q_UPDATELAYEREDWINDOWINFO *pULWInfo); +extern PtrUpdateLayeredWindow ptrUpdateLayeredWindow; +extern PtrUpdateLayeredWindowIndirect ptrUpdateLayeredWindowIndirect; +#endif + +class QPaintDevice; +class QPoint; +class QRegion; +class QRegion; +class QSize; +class QWidget; +class QRasterWindowSurfacePrivate; +class QNativeImage; + +class Q_GUI_EXPORT QRasterWindowSurface : public QWindowSurface +{ +public: + QRasterWindowSurface(QWidget *widget, bool setDefaultSurface = true); + ~QRasterWindowSurface(); + + QPaintDevice *paintDevice(); + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + void beginPaint(const QRegion &rgn); + void setGeometry(const QRect &rect); + bool scroll(const QRegion &area, int dx, int dy); + WindowSurfaceFeatures features() const; + +#ifdef QT_MAC_USE_COCOA + CGContextRef imageContext(); + + bool needsFlush; + QRegion regionToFlush; +#endif // QT_MAC_USE_COCOA + +private: +#if defined(Q_WS_X11) && !defined(QT_NO_MITSHM) + void syncX(); +#endif + void prepareBuffer(QImage::Format format, QWidget *widget); + Q_DECLARE_PRIVATE(QRasterWindowSurface) + QScopedPointer<QRasterWindowSurfacePrivate> d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_RASTER_P_H diff --git a/src/gui/painting/qwindowsurface_s60.cpp b/src/gui/painting/qwindowsurface_s60.cpp new file mode 100644 index 0000000000..9fa01edf7d --- /dev/null +++ b/src/gui/painting/qwindowsurface_s60.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> // for Q_WS_WIN define (non-PCH) + +#include <QtGui/qpaintdevice.h> +#include <private/qwidget_p.h> +#include <private/qwindowsurface_s60_p.h> +#include <private/qpixmap_s60_p.h> +#include <private/qt_s60_p.h> +#include <private/qapplication_p.h> +#include <private/qdrawhelper_p.h> + +#ifdef QT_GRAPHICSSYSTEM_RUNTIME +#include <private/qgraphicssystem_runtime_p.h> +#endif + +QT_BEGIN_NAMESPACE + +struct QS60WindowSurfacePrivate +{ + QPixmap device; + QList<QImage*> bufferImages; +}; + +TDisplayMode displayMode(bool opaque) +{ + TDisplayMode mode = S60->screenDevice()->DisplayMode(); + if (opaque) { + mode = EColor16MU; + } else { + if (QSysInfo::symbianVersion() >= QSysInfo::SV_SF_3) + mode = Q_SYMBIAN_ECOLOR16MAP; // Symbian^3 WServ has support for ARGB32_PRE + else + mode = EColor16MA; // Symbian prior to Symbian^3 sw accelerates EColor16MA + } + return mode; +} + +bool blitWriteAlpha(QWidgetPrivate *widgetPrivate) +{ + QWExtra *extra = widgetPrivate->extraData(); + return extra ? extra->nativePaintMode == QWExtra::BlitWriteAlpha : false; +} + +QS60WindowSurface::QS60WindowSurface(QWidget* widget) + : QWindowSurface(widget), d_ptr(new QS60WindowSurfacePrivate) +{ + QWidgetPrivate *widgetPrivate = qt_widget_private(widget); + const bool opaque = widgetPrivate->isOpaque && !blitWriteAlpha(widgetPrivate); + TDisplayMode mode = displayMode(opaque); + // We create empty CFbsBitmap here -> it will be resized in setGeometry + CFbsBitmap *bitmap = new CFbsBitmap; // CBase derived object needs check on new + Q_CHECK_PTR(bitmap); + qt_symbian_throwIfError( bitmap->Create( TSize(0, 0), mode ) ); + + QS60PixmapData *data = new QS60PixmapData(QPixmapData::PixmapType); + if (data) { + data->fromSymbianBitmap(bitmap, true); + d_ptr->device = QPixmap(data); + } +} + +QS60WindowSurface::~QS60WindowSurface() +{ +#if defined(QT_GRAPHICSSYSTEM_RUNTIME) && defined(Q_SYMBIAN_SUPPORTS_SURFACES) + if(QApplicationPrivate::runtime_graphics_system) { + QRuntimeGraphicsSystem *runtimeGraphicsSystem = + static_cast<QRuntimeGraphicsSystem*>(QApplicationPrivate::graphics_system); + if(runtimeGraphicsSystem->graphicsSystemName() == QLatin1String("openvg")) { + + // Graphics system has been switched from raster to openvg. + // Issue empty redraw to clear the UI surface + + QWidget *w = window(); + if (w->testAttribute(Qt::WA_WState_Created)) { + RWindow *const window = static_cast<RWindow *>(w->winId()->DrawableWindow()); + window->BeginRedraw(); + window->EndRedraw(); + } + } + } +#endif + + delete d_ptr; +} + +void QS60WindowSurface::beginPaint(const QRegion &rgn) +{ +#ifdef Q_SYMBIAN_SUPPORTS_SURFACES + S60->wsSession().Finish(); +#endif + + QWidgetPrivate *windowPrivate = qt_widget_private(window()); + if (!windowPrivate->isOpaque || blitWriteAlpha(windowPrivate)) { + QS60PixmapData *pixmapData = static_cast<QS60PixmapData *>(d_ptr->device.data_ptr().data()); + + TDisplayMode mode = displayMode(false); + if (pixmapData->cfbsBitmap->DisplayMode() != mode) + pixmapData->convertToDisplayMode(mode); + + pixmapData->beginDataAccess(); + + if (!windowPrivate->isOpaque) { + QPainter p(&pixmapData->image); + p.setCompositionMode(QPainter::CompositionMode_Source); + const QVector<QRect> rects = rgn.rects(); + const QColor blank = Qt::transparent; + for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) { + p.fillRect(*it, blank); + } + } + + pixmapData->endDataAccess(); + } +} + +void QS60WindowSurface::endPaint(const QRegion &) +{ + qDeleteAll(d_ptr->bufferImages); + d_ptr->bufferImages.clear(); +} + +QImage* QS60WindowSurface::buffer(const QWidget *widget) +{ + if (widget->window() != window()) + return 0; + + QPaintDevice *pdev = paintDevice(); + if (!pdev) + return 0; + + const QPoint off = offset(widget); + QImage *img = &(static_cast<QS60PixmapData *>(d_ptr->device.data_ptr().data())->image); + + QRect rect(off, widget->size()); + rect &= QRect(QPoint(), img->size()); + + if (rect.isEmpty()) + return 0; + + img = new QImage(img->scanLine(rect.y()) + rect.x() * img->depth() / 8, + rect.width(), rect.height(), + img->bytesPerLine(), img->format()); + d_ptr->bufferImages.append(img); + + return img; +} + +void QS60WindowSurface::flush(QWidget *widget, const QRegion ®ion, const QPoint &) +{ + QWidget *window = widget->window(); + Q_ASSERT(window); + QTLWExtra *topExtra = window->d_func()->maybeTopData(); + Q_ASSERT(topExtra); + QRect qr = region.boundingRect(); + if (!topExtra->inExpose) { + topExtra->inExpose = true; // Prevent DrawNow() from calling syncBackingStore() again + TRect tr = qt_QRect2TRect(qr); + widget->winId()->DrawNow(tr); + topExtra->inExpose = false; + } else { + // This handles the case when syncBackingStore updates content outside of the + // original drawing rectangle. This might happen if there are pending update() + // events at the same time as we get a Draw() from Symbian. + QRect drawRect = qt_TRect2QRect(widget->winId()->DrawableWindow()->GetDrawRect()); + if (!drawRect.contains(qr)) + widget->winId()->DrawDeferred(); + } +} + +bool QS60WindowSurface::scroll(const QRegion &area, int dx, int dy) +{ + QRect rect = area.boundingRect(); + + if (dx == 0 && dy == 0) + return false; + + if (d_ptr->device.isNull()) + return false; + + QS60PixmapData *data = static_cast<QS60PixmapData*>(d_ptr->device.data_ptr().data()); + data->scroll(dx, dy, rect); + + return true; +} + +QPaintDevice* QS60WindowSurface::paintDevice() +{ + return &d_ptr->device; +} + +void QS60WindowSurface::setGeometry(const QRect& rect) +{ + if (rect == geometry()) + return; + + QS60PixmapData *data = static_cast<QS60PixmapData*>(d_ptr->device.data_ptr().data()); + data->resize(rect.width(), rect.height()); + + QWindowSurface::setGeometry(rect); +} + +QWindowSurface::WindowSurfaceFeatures QS60WindowSurface::features() const +{ + return QWindowSurface::AllFeatures; +} + +CFbsBitmap* QS60WindowSurface::symbianBitmap() const +{ + QS60PixmapData *data = static_cast<QS60PixmapData*>(d_ptr->device.data_ptr().data()); + return data->cfbsBitmap; +} + +QT_END_NAMESPACE + diff --git a/src/gui/painting/qwindowsurface_s60_p.h b/src/gui/painting/qwindowsurface_s60_p.h new file mode 100644 index 0000000000..25018d86e2 --- /dev/null +++ b/src/gui/painting/qwindowsurface_s60_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_S60_P_H +#define QWINDOWSURFACE_S60_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include "private/qwindowsurface_p.h" + +class CFbsBitmap; + +QT_BEGIN_NAMESPACE + +struct QS60WindowSurfacePrivate; + +class QS60WindowSurface : public QWindowSurface +{ +public: + QS60WindowSurface(QWidget *widget); + ~QS60WindowSurface(); + + QPaintDevice *paintDevice(); + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + bool scroll(const QRegion &area, int dx, int dy); + + void beginPaint(const QRegion &); + void endPaint(const QRegion &); + + QImage* buffer(const QWidget *widget); + + void setGeometry(const QRect &rect); + + WindowSurfaceFeatures features() const; + + CFbsBitmap *symbianBitmap() const; + +private: + QS60WindowSurfacePrivate* d_ptr; + +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_S60_P_H diff --git a/src/gui/painting/qwindowsurface_x11.cpp b/src/gui/painting/qwindowsurface_x11.cpp new file mode 100644 index 0000000000..deb83b452c --- /dev/null +++ b/src/gui/painting/qwindowsurface_x11.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/QPaintDevice> +#include <QtGui/QPainter> +#include <QtGui/QPixmap> +#include <QtGui/QWidget> + +#include "private/qt_x11_p.h" +#include "private/qpixmap_x11_p.h" +#include "private/qwidget_p.h" +#include "qx11info_x11.h" +#include "qwindowsurface_x11_p.h" + +QT_BEGIN_NAMESPACE + +extern void *qt_getClipRects(const QRegion &r, int &num); // in qpaintengine_x11.cpp + +struct QX11WindowSurfacePrivate +{ + QWidget *widget; + QPixmap device; +#ifndef QT_NO_XRENDER + bool translucentBackground; +#endif +}; + +QX11WindowSurface::QX11WindowSurface(QWidget *widget) + : QWindowSurface(widget), d_ptr(new QX11WindowSurfacePrivate), gc(0) +{ + d_ptr->widget = widget; +#ifndef QT_NO_XRENDER + d_ptr->translucentBackground = X11->use_xrender + && widget->x11Info().depth() == 32; +#endif +} + + +QX11WindowSurface::~QX11WindowSurface() +{ + delete d_ptr; + if (gc) { + XFreeGC(X11->display, gc); + gc = 0; + } +} + +QPaintDevice *QX11WindowSurface::paintDevice() +{ + return &d_ptr->device; +} + +void QX11WindowSurface::beginPaint(const QRegion &rgn) +{ +#ifndef QT_NO_XRENDER + Q_ASSERT(!d_ptr->device.isNull()); + + if (d_ptr->translucentBackground) { + if (d_ptr->device.depth() != 32) + static_cast<QX11PixmapData *>(d_ptr->device.data_ptr().data())->convertToARGB32(); + ::Picture src = X11->getSolidFill(d_ptr->device.x11Info().screen(), Qt::transparent); + ::Picture dst = d_ptr->device.x11PictureHandle(); + const QVector<QRect> rects = rgn.rects(); + const int w = d_ptr->device.width(); + const int h = d_ptr->device.height(); + for (QVector<QRect>::const_iterator it = rects.begin(); it != rects.end(); ++it) + XRenderComposite(X11->display, PictOpSrc, src, 0, dst, + 0, 0, w, h, it->x(), it->y(), + it->width(), it->height()); + } +#endif +} + +void QX11WindowSurface::flush(QWidget *widget, const QRegion &rgn, const QPoint &offset) +{ + if (d_ptr->device.isNull()) + return; + + QPoint wOffset = qt_qwidget_data(widget)->wrect.topLeft(); + QRegion wrgn(rgn); + QRect br = rgn.boundingRect(); + if (!wOffset.isNull()) + wrgn.translate(-wOffset); + QRect wbr = wrgn.boundingRect(); + + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(wrgn, num); + if (num <= 0) + return; +// qDebug() << "XSetClipRectangles"; +// for (int i = 0; i < num; ++i) +// qDebug() << ' ' << i << rects[i].x << rects[i].x << rects[i].y << rects[i].width << rects[i].height; + if (num != 1) + XSetClipRectangles(X11->display, gc, 0, 0, rects, num, YXBanded); + XCopyArea(X11->display, d_ptr->device.handle(), widget->handle(), gc, + br.x() + offset.x(), br.y() + offset.y(), br.width(), br.height(), wbr.x(), wbr.y()); + if (num != 1) + XSetClipMask(X11->display, gc, XNone); +} + +void QX11WindowSurface::setGeometry(const QRect &rect) +{ + QWindowSurface::setGeometry(rect); + + const QSize size = rect.size(); + + if (d_ptr->device.size() == size || size.width() <= 0 || size.height() <= 0) + return; +#ifndef QT_NO_XRENDER + if (d_ptr->translucentBackground) { + QPixmap::x11SetDefaultScreen(d_ptr->widget->x11Info().screen()); + + QX11PixmapData *data = new QX11PixmapData(QPixmapData::PixmapType); + data->xinfo = d_ptr->widget->x11Info(); + data->resize(size.width(), size.height()); + d_ptr->device = QPixmap(data); + } else +#endif + { + QPixmap::x11SetDefaultScreen(d_ptr->widget->x11Info().screen()); + + QX11PixmapData *oldData = static_cast<QX11PixmapData *>(d_ptr->device.pixmapData()); + + if (oldData && !(oldData->flags & QX11PixmapData::Uninitialized) && hasStaticContents()) { + // Copy the content of the old pixmap into the new one. + QX11PixmapData *newData = new QX11PixmapData(QPixmapData::PixmapType); + newData->resize(size.width(), size.height()); + Q_ASSERT(oldData->d == newData->d); + + QRegion staticRegion(staticContents()); + // Make sure we're inside the boundaries of the old pixmap. + staticRegion &= QRect(0, 0, oldData->w, oldData->h); + const QRect boundingRect(staticRegion.boundingRect()); + const int dx = boundingRect.x(); + const int dy = boundingRect.y(); + + int num; + XRectangle *rects = (XRectangle *)qt_getClipRects(staticRegion, num); + GC tmpGc = XCreateGC(X11->display, oldData->hd, 0, 0); + XSetClipRectangles(X11->display, tmpGc, 0, 0, rects, num, YXBanded); + XCopyArea(X11->display, oldData->hd, newData->hd, tmpGc, + dx, dy, qMin(boundingRect.width(), size.width()), + qMin(boundingRect.height(), size.height()), dx, dy); + XFreeGC(X11->display, tmpGc); + newData->flags &= ~QX11PixmapData::Uninitialized; + + d_ptr->device = QPixmap(newData); + } else { + d_ptr->device = QPixmap(size); + } + } + + if (gc) { + XFreeGC(X11->display, gc); + gc = 0; + } + if (!d_ptr->device.isNull()) { + gc = XCreateGC(X11->display, d_ptr->device.handle(), 0, 0); + XSetGraphicsExposures(X11->display, gc, False); + } +} + +bool QX11WindowSurface::scroll(const QRegion &area, int dx, int dy) +{ + QRect rect = area.boundingRect(); + + if (d_ptr->device.isNull()) + return false; + + GC gc = XCreateGC(X11->display, d_ptr->device.handle(), 0, 0); + XCopyArea(X11->display, d_ptr->device.handle(), d_ptr->device.handle(), gc, + rect.x(), rect.y(), rect.width(), rect.height(), + rect.x()+dx, rect.y()+dy); + XFreeGC(X11->display, gc); + + return true; +} + +QPixmap QX11WindowSurface::grabWidget(const QWidget *widget, + const QRect& rect) const +{ + if (!widget || d_ptr->device.isNull()) + return QPixmap(); + + QRect srcRect; + + // make sure the rect is inside the widget & clip to widget's rect + if (!rect.isEmpty()) + srcRect = rect & widget->rect(); + else + srcRect = widget->rect(); + + if (srcRect.isEmpty()) + return QPixmap(); + + // If it's a child widget we have to translate the coordinates + if (widget != window()) + srcRect.translate(widget->mapTo(window(), QPoint(0, 0))); + + QPixmap::x11SetDefaultScreen(widget->x11Info().screen()); + QPixmap px(srcRect.width(), srcRect.height()); + + GC tmpGc = XCreateGC(X11->display, d_ptr->device.handle(), 0, 0); + + // Copy srcRect from the backing store to the new pixmap + XSetGraphicsExposures(X11->display, tmpGc, False); + XCopyArea(X11->display, d_ptr->device.handle(), px.handle(), tmpGc, + srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height(), 0, 0); + + XFreeGC(X11->display, tmpGc); + + return px; +} + +QWindowSurface::WindowSurfaceFeatures QX11WindowSurface::features() const +{ + WindowSurfaceFeatures features = QWindowSurface::PartialUpdates | QWindowSurface::PreservedContents; +#ifndef QT_NO_XRENDER + if (!d_ptr->translucentBackground) + features |= QWindowSurface::StaticContents; +#else + features |= QWindowSurface::StaticContents; +#endif + return features; +} + +QT_END_NAMESPACE diff --git a/src/gui/painting/qwindowsurface_x11_p.h b/src/gui/painting/qwindowsurface_x11_p.h new file mode 100644 index 0000000000..df76f986ff --- /dev/null +++ b/src/gui/painting/qwindowsurface_x11_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSURFACE_X11_P_H +#define QWINDOWSURFACE_X11_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qglobal.h> +#include "private/qwindowsurface_p.h" + +QT_BEGIN_NAMESPACE + +class QPaintDevice; +class QPoint; +class QRegion; +class QRegion; +class QSize; +class QWidget; +struct QX11WindowSurfacePrivate; + +class QX11WindowSurface : public QWindowSurface +{ +public: + QX11WindowSurface(QWidget *widget); + ~QX11WindowSurface(); + + QPaintDevice *paintDevice(); + void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); + + void beginPaint(const QRegion &rgn); + void setGeometry(const QRect &rect); + bool scroll(const QRegion &area, int dx, int dy); + QPixmap grabWidget(const QWidget *widget, + const QRect& rectangle = QRect()) const; + WindowSurfaceFeatures features() const; + +private: + QX11WindowSurfacePrivate *d_ptr; + GC gc; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSURFACE_X11_P_H diff --git a/src/gui/painting/qwmatrix.h b/src/gui/painting/qwmatrix.h new file mode 100644 index 0000000000..c4f4c75405 --- /dev/null +++ b/src/gui/painting/qwmatrix.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWMATRIX_H +#define QWMATRIX_H + +#include <QtGui/qmatrix.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if defined(QT3_SUPPORT) +typedef QMatrix QWMatrix; +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWMATRIX_H diff --git a/src/gui/s60framework/qs60mainapplication.cpp b/src/gui/s60framework/qs60mainapplication.cpp new file mode 100644 index 0000000000..289d81cf96 --- /dev/null +++ b/src/gui/s60framework/qs60mainapplication.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// INCLUDE FILES +#include <exception> +#include <private/qcore_symbian_p.h> +#include "qs60maindocument.h" +#include "qs60mainapplication_p.h" +#include "qs60mainapplication.h" +#include <bautils.h> +#include <coemain.h> +#ifndef Q_WS_S60 +# include <eikserverapp.h> +#endif + +QT_BEGIN_NAMESPACE + +/** + * factory function to create the QS60Main application class + */ +CApaApplication *newS60Application() +{ + return new QS60MainApplication; +} + + +/*! + \class QS60MainApplication + \since 4.6 + \brief The QS60MainApplication class provides support for migration from S60. + + \warning This class is provided only to get access to S60 specific + functionality in the application framework classes. It is not + portable. We strongly recommend against using it in new applications. + + The QS60MainApplication provides a helper class for use in migrating + from existing S60 based applications to Qt based applications. It is + used in the exact same way as the \c CEikApplication class from + Symbian, but internally provides extensions used by Qt. + + When modifying old S60 applications that rely on implementing + functions in \c CEikApplication, the class should be modified to + inherit from this class instead of \c CEikApplication. Then the + application can choose to override only certain functions. To make + Qt use the custom application objects, pass a factory function to + \c{QApplication::QApplication(QApplication::QS60MainApplicationFactory, int &, char **)}. + + For more information on \c CEikApplication, please see the S60 documentation. + + Unlike other Qt classes, QS60MainApplication behaves like an S60 class, and can throw Symbian + leaves. + + \sa QS60MainDocument, QS60MainAppUi, QApplication::QS60MainApplicationFactory + */ + +/*! + * \brief Contructs an instance of QS60MainApplication. + */ +QS60MainApplication::QS60MainApplication() +{ +} + +/*! + * \brief Destroys the QS60MainApplication. + */ +QS60MainApplication::~QS60MainApplication() +{ +} + +/*! + * \brief Creates an instance of QS60MainDocument. + * + * \sa QS60MainDocument + */ +CApaDocument *QS60MainApplication::CreateDocumentL() +{ + // Create an QtS60Main document, and return a pointer to it + return new (ELeave) QS60MainDocument(*this); +} + + +/*! + * \brief Returns the UID of the application. + */ +TUid QS60MainApplication::AppDllUid() const +{ + // Return the UID for the QtS60Main application + return RProcess().SecureId().operator TUid(); +} + +/*! + * \brief Returns the resource file name. + */ +TFileName QS60MainApplication::ResourceFileName() const +{ + return KNullDesC(); +} + +/*! + \internal +*/ +void QS60MainApplication::PreDocConstructL() +{ + QS60MainApplicationBase::PreDocConstructL(); +} + +/*! + \internal +*/ +CDictionaryStore *QS60MainApplication::OpenIniFileLC(RFs &aFs) const +{ + return QS60MainApplicationBase::OpenIniFileLC(aFs); +} + +/*! + \internal +*/ +void QS60MainApplication::NewAppServerL(CApaAppServer *&aAppServer) +{ +#ifdef Q_WS_S60 + QS60MainApplicationBase::NewAppServerL(aAppServer); +#else + aAppServer = new(ELeave) CEikAppServer; +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/s60framework/qs60mainapplication.h b/src/gui/s60framework/qs60mainapplication.h new file mode 100644 index 0000000000..133f69a83d --- /dev/null +++ b/src/gui/s60framework/qs60mainapplication.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60MAINAPPLICATION_H +#define QS60MAINAPPLICATION_H + +#include <QtCore/qglobal.h> + +#ifdef Q_OS_SYMBIAN + +#ifdef Q_WS_S60 +#include <aknapp.h> +typedef CAknApplication QS60MainApplicationBase; +#else +#include <eikapp.h> +typedef CEikApplication QS60MainApplicationBase; +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QS60MainApplication : public QS60MainApplicationBase +{ +public: + QS60MainApplication(); + // The virtuals are for qdoc. + virtual ~QS60MainApplication(); + + virtual TUid AppDllUid() const; + + virtual TFileName ResourceFileName() const; + +public: + + virtual void PreDocConstructL(); + + virtual CDictionaryStore *OpenIniFileLC(RFs &aFs) const; + + virtual void NewAppServerL(CApaAppServer *&aAppServer); + +protected: + + virtual CApaDocument *CreateDocumentL(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q_OS_SYMBIAN + +#endif // QS60MAINAPPLICATION_H diff --git a/src/gui/s60framework/qs60mainapplication_p.h b/src/gui/s60framework/qs60mainapplication_p.h new file mode 100644 index 0000000000..bb76676b13 --- /dev/null +++ b/src/gui/s60framework/qs60mainapplication_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60MAINAPPLICATION_P_H +#define QS60MAINAPPLICATION_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 <qglobal.h> + +#include <apparc.h> + +QT_BEGIN_NAMESPACE + +CApaApplication *newS60Application(); + +QT_END_NAMESPACE + +#endif // QS60MAINAPPLICATION_P_H diff --git a/src/gui/s60framework/qs60mainappui.cpp b/src/gui/s60framework/qs60mainappui.cpp new file mode 100644 index 0000000000..f591fcba36 --- /dev/null +++ b/src/gui/s60framework/qs60mainappui.cpp @@ -0,0 +1,430 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// INCLUDE FILES +#include <exception> +#include <qglobal.h> +#ifdef Q_WS_S60 +#include <avkon.hrh> +#include <eikmenub.h> +#include <eikmenup.h> +#include <avkon.rsg> +#endif +#include <barsread.h> +#include <coeutils.h> +#include <qconfig.h> + +#include "qs60mainappui.h" +#include <QtGui/qapplication.h> +#include <QtGui/qsymbianevent.h> +#include <QtGui/qmenu.h> +#include <private/qmenu_p.h> +#include <private/qt_s60_p.h> +#include <qdebug.h> + +//Animated wallpapers in Qt applications are not supported. +const TInt KAknDisableAnimationBackground = 0x02000000; +const TInt KAknSingleClickCompatible = 0x01000000; + +QT_BEGIN_NAMESPACE + +/*! + \class QS60MainAppUi + \since 4.6 + \brief The QS60MainAppUi class is a helper class for S60 migration. + + \warning This class is provided only to get access to S60 specific + functionality in the application framework classes. It is not + portable. We strongly recommend against using it in new applications. + + The QS60MainAppUi provides a helper class for use in migrating from + existing S60 based applications to Qt based applications. It is used + in the exact same way as the \c CAknAppUi class from Symbian, but + internally provides extensions used by Qt. + + When modifying old S60 applications that rely on implementing + functions in \c CAknAppUi, the class should be modified to inherit + from this class instead of \c CAknAppUi. Then the application can + choose to override only certain functions. + + For more information on \c CAknAppUi, please see the S60 + documentation. + + Unlike other Qt classes, QS60MainAppUi behaves like an S60 class, + and can throw Symbian leaves. + + \sa QS60MainDocument, QS60MainApplication + */ + +/*! + * \brief Second phase Symbian constructor. + * + * Constructs all the elements of the class that can cause a leave to happen. + * + * If you override this function, you should call the base class implementation as well. + */ +void QS60MainAppUi::ConstructL() +{ + // Cone's heap and handle checks on app destruction are not suitable for Qt apps, as many + // objects can still exist in static data at that point. Instead we will print relevant information + // so that comparative checks may be made for memory leaks, using ~SPrintExitInfo in corelib. + iEikonEnv->DisableExitChecks(ETrue); + + // Initialise app UI with standard value. + // ENoAppResourceFile and ENonStandardResourceFile makes UI to work without + // resource files in most SDKs. S60 3rd FP1 public seems to require resource file + // even these flags are defined + TInt flags = CEikAppUi::ENoScreenFurniture + | CEikAppUi::ENonStandardResourceFile; +#ifdef Q_WS_S60 + flags |= CAknAppUi::EAknEnableSkin; + // After 5th Edition S60, native side supports animated wallpapers. + // However, there is no support for that feature on Qt side, so indicate to + // native UI framework that this application will not support background animations. + + // Also, add support for single touch for post 5th edition platforms. + // This has only impact when launching native dialogs/menus from inside QApplication. + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + flags |= (KAknDisableAnimationBackground | KAknSingleClickCompatible); + } +#endif + BaseConstructL(flags); +} + +/*! + * \brief Contructs an instance of QS60MainAppUi. + */ +QS60MainAppUi::QS60MainAppUi() +{ + // No implementation required +} + +/*! + * \brief Destroys the QS60MainAppUi. + */ +QS60MainAppUi::~QS60MainAppUi() +{ +} + +/*! + * \brief Handles commands produced by the S60 framework. + * + * \a command holds the ID of the command to handle, and is S60 specific. + * + * If you override this function, you should call the base class implementation if you do not + * handle the command. + */ +void QS60MainAppUi::HandleCommandL(TInt command) +{ + if (qApp) { + QSymbianEvent event(QSymbianEvent::CommandEvent, command); + QT_TRYCATCH_LEAVING(qApp->symbianProcessEvent(&event)); + } +} + +/*! + * \brief Handles a resource change in the S60 framework. + * + * Resource changes include layout switches. \a type holds the type of resource change that + * occurred. + * + * If you override this function, you should call the base class implementation if you do not + * handle the resource change. + */ +void QS60MainAppUi::HandleResourceChangeL(TInt type) +{ + QS60MainAppUiBase::HandleResourceChangeL(type); + + if (qApp) { + QSymbianEvent event(QSymbianEvent::ResourceChangeEvent, type); + QT_TRYCATCH_LEAVING(qApp->symbianProcessEvent(&event)); + } +} + +/*! + * \brief Handles raw window server events. + * + * The event type and information is passed in \a wsEvent, while the receiving control is passed in + * \a destination. + * + * If you override this function, you should call the base class implementation if you do not + * handle the event. + */ +void QS60MainAppUi::HandleWsEventL(const TWsEvent &wsEvent, CCoeControl *destination) +{ + int result = 0; + if (qApp) { + QSymbianEvent event(&wsEvent); + QT_TRYCATCH_LEAVING( + result = qApp->symbianProcessEvent(&event) + ); + } + + if (result <= 0) + QS60MainAppUiBase::HandleWsEventL(wsEvent, destination); +} + + +/*! + * \brief Handles changes to the status pane size. + * + * Called by the framework when the application status pane size is changed. + * + * If you override this function, you should call the base class implementation if you do not + * handle the size change. + */ +void QS60MainAppUi::HandleStatusPaneSizeChange() +{ + TRAP_IGNORE(HandleResourceChangeL(KInternalStatusPaneChange)); + HandleStackedControlsResourceChange(KInternalStatusPaneChange); +} + +/*! + * \brief Dynamically initializes a menu bar. + * + * The resource associated with the menu is given in \a resourceId, and the actual menu bar is + * passed in \a menuBar. + * + * If you override this function, you should call the base class implementation as well. + */ +void QS60MainAppUi::DynInitMenuBarL(TInt /* resourceId */, CEikMenuBar * /* menuBar */) +{ +} + +/*! + * \brief Dynamically initializes a menu pane. + * + * The resource associated with the menu is given in \a resourceId, and the actual menu pane is + * passed in \a menuPane. + * + * If you override this function, you should call the base class implementation as well. + */ +void QS60MainAppUi::DynInitMenuPaneL(TInt resourceId, CEikMenuPane *menuPane) +{ +#ifdef Q_WS_S60 + if (resourceId == R_AVKON_MENUPANE_EMPTY) { + if (menuPane->NumberOfItemsInPane() <= 1) + QT_TRYCATCH_LEAVING(qt_symbian_show_toplevel(menuPane)); + + } else if (resourceId != R_AVKON_MENUPANE_FEP_DEFAULT + && resourceId != R_AVKON_MENUPANE_EDITTEXT_DEFAULT + && resourceId != R_AVKON_MENUPANE_LANGUAGE_DEFAULT) { + QT_TRYCATCH_LEAVING(qt_symbian_show_submenu(menuPane, resourceId)); + } +#else + QS60MainAppUiBase::DynInitMenuPaneL(resourceId, menuPane); +#endif +} + +/*! + * \brief Restores a menu window. + * + * The menu window to restore is given in \a menuWindow. The resource ID and type of menu is given + * in \a resourceId and \a menuType, respectively. + * + * If you override this function, you should call the base class implementation as well. + */ +void QS60MainAppUi::RestoreMenuL(CCoeControl *menuWindow, TInt resourceId, TMenuType menuType) +{ +#ifdef Q_WS_S60 + if (resourceId >= QT_SYMBIAN_FIRST_MENU_ITEM && resourceId <= QT_SYMBIAN_LAST_MENU_ITEM) { + if (menuType == EMenuPane) + DynInitMenuPaneL(resourceId, (CEikMenuPane*)menuWindow); + else + DynInitMenuBarL(resourceId, (CEikMenuBar*)menuWindow); + } else if(resourceId == R_AVKON_MENUPANE_EMPTY) { + CEikMenuBarTitle *title = new(ELeave) CEikMenuBarTitle; + CleanupStack::PushL(title); + + title->iData.iMenuPaneResourceId = R_AVKON_MENUPANE_EMPTY; + title->iTitleFlags = 0; + + S60->menuBar()->TitleArray()->AddTitleL(title); + CleanupStack::Pop( title ); + } + else +#endif + { + QS60MainAppUiBase::RestoreMenuL(menuWindow, resourceId, menuType); + } +} + +/*! + \internal +*/ +void QS60MainAppUi::Exit() +{ + QS60MainAppUiBase::Exit(); +} + +/*! + \internal +*/ +void QS60MainAppUi::SetFadedL(TBool aFaded) +{ + QS60MainAppUiBase::SetFadedL(aFaded); +} + +/*! + \internal +*/ +TRect QS60MainAppUi::ApplicationRect() const +{ + return QS60MainAppUiBase::ApplicationRect(); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleScreenDeviceChangedL() +{ + QS60MainAppUiBase::HandleScreenDeviceChangedL(); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleApplicationSpecificEventL(TInt aType, const TWsEvent &aEvent) +{ + QS60MainAppUiBase::HandleApplicationSpecificEventL(aType, aEvent); +} + +/*! + \internal +*/ +TTypeUid::Ptr QS60MainAppUi::MopSupplyObject(TTypeUid aId) +{ + return QS60MainAppUiBase::MopSupplyObject(aId); +} + +/*! + \internal +*/ +void QS60MainAppUi::ProcessCommandL(TInt aCommand) +{ + QS60MainAppUiBase::ProcessCommandL(aCommand); +} + +/*! + \internal +*/ +TErrorHandlerResponse QS60MainAppUi::HandleError (TInt aError, const SExtendedError &aExtErr, TDes &aErrorText, TDes &aContextText) +{ + return QS60MainAppUiBase::HandleError(aError, aExtErr, aErrorText, aContextText); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleViewDeactivation(const TVwsViewId &aViewIdToBeDeactivated, const TVwsViewId &aNewlyActivatedViewId) +{ + QS60MainAppUiBase::HandleViewDeactivation(aViewIdToBeDeactivated, aNewlyActivatedViewId); +} + +/*! + \internal +*/ +void QS60MainAppUi::PrepareToExit() +{ + QS60MainAppUiBase::PrepareToExit(); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleTouchPaneSizeChange() +{ + QS60MainAppUiBase::HandleTouchPaneSizeChange(); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleSystemEventL(const TWsEvent &aEvent) +{ + QS60MainAppUiBase::HandleSystemEventL(aEvent); +} + +/*! + \internal +*/ +void QS60MainAppUi::Reserved_MtsmPosition() +{ + QS60MainAppUiBase::Reserved_MtsmPosition(); +} + +/*! + \internal +*/ +void QS60MainAppUi::Reserved_MtsmObject() +{ + QS60MainAppUiBase::Reserved_MtsmObject(); +} + +/*! + \internal +*/ +void QS60MainAppUi::HandleForegroundEventL(TBool aForeground) +{ + QS60MainAppUiBase::HandleForegroundEventL(aForeground); +} + +/*! + \internal +*/ +TBool QS60MainAppUi::ProcessCommandParametersL(TApaCommand /*aCommand*/, TFileName &/*aDocumentName*/, const TDesC8 &/*aTail*/) +{ + // bypass CEikAppUi::ProcessCommandParametersL(..) which modifies aDocumentName, preventing apparc document opening from working. + // The return value is effectively unused in Qt apps (see QS60MainDocument::OpenFileL) + return EFalse; +} + +#ifndef Q_WS_S60 + +void QS60StubAknAppUi::HandleViewDeactivation(const TVwsViewId &, const TVwsViewId &) {} +void QS60StubAknAppUi::HandleTouchPaneSizeChange() {} +void QS60StubAknAppUi::HandleStatusPaneSizeChange() {} +void QS60StubAknAppUi::Reserved_MtsmPosition() {} +void QS60StubAknAppUi::Reserved_MtsmObject() {} + +#endif + +QT_END_NAMESPACE diff --git a/src/gui/s60framework/qs60mainappui.h b/src/gui/s60framework/qs60mainappui.h new file mode 100644 index 0000000000..bf118ffbe8 --- /dev/null +++ b/src/gui/s60framework/qs60mainappui.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60MAINAPPUI_H +#define QS60MAINAPPUI_H + +#include <QtCore/qglobal.h> + +#ifdef Q_OS_SYMBIAN + +#ifdef Q_WS_S60 +#include <aknappui.h> +typedef CAknAppUi QS60MainAppUiBase; +#else +#include <eikappui.h> +// these stub classes simulate the structure of CAknAppUi, to help binary compatibility between Qt configured with and without S60/Avkon +class QS60StubAknAppUiBase : public CEikAppUi +{ +private: + int qS60StubAknAppUiBaseSpace[4]; +}; + +class QS60StubMEikStatusPaneObserver +{ +public: + virtual void HandleStatusPaneSizeChange() = 0; +}; + +class QS60StubMAknTouchPaneObserver +{ +public: + virtual void HandleTouchPaneSizeChange() = 0; +}; + +class QS60StubAknAppUi : public QS60StubAknAppUiBase, QS60StubMEikStatusPaneObserver, + public MCoeViewDeactivationObserver, + public QS60StubMAknTouchPaneObserver +{ +public: // MCoeViewDeactivationObserver + virtual void HandleViewDeactivation(const TVwsViewId&, const TVwsViewId &); + +public: // from MAknTouchPaneObserver + virtual void HandleTouchPaneSizeChange(); + +protected: // from MEikStatusPaneObserver + virtual void HandleStatusPaneSizeChange(); + +protected: // from CAknAppUi + virtual void Reserved_MtsmPosition(); + virtual void Reserved_MtsmObject(); + +private: + int qS60StubAknAppUiSpace[4]; +}; + +typedef QS60StubAknAppUi QS60MainAppUiBase; +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QS60MainAppUi : public QS60MainAppUiBase +{ +public: + QS60MainAppUi(); + // The virtuals are for qdoc. + virtual ~QS60MainAppUi(); + + virtual void ConstructL(); + + virtual void RestoreMenuL(CCoeControl *menuWindow,TInt resourceId,TMenuType menuType); + virtual void DynInitMenuBarL(TInt resourceId, CEikMenuBar *menuBar); + virtual void DynInitMenuPaneL(TInt resourceId, CEikMenuPane *menuPane); + + virtual void HandleCommandL( TInt command ); + + virtual void HandleResourceChangeL(TInt type); + + virtual void HandleStatusPaneSizeChange(); + +protected: + virtual void HandleWsEventL(const TWsEvent &event, CCoeControl *destination); + +public: + virtual void Exit(); + virtual void SetFadedL(TBool aFaded); + virtual TRect ApplicationRect() const; + virtual void ProcessCommandL(TInt aCommand); + virtual TErrorHandlerResponse HandleError (TInt aError, const SExtendedError &aExtErr, TDes &aErrorText, TDes &aContextText); + virtual void HandleViewDeactivation(const TVwsViewId &aViewIdToBeDeactivated, const TVwsViewId &aNewlyActivatedViewId); + virtual void PrepareToExit(); + virtual void HandleTouchPaneSizeChange(); + virtual TBool ProcessCommandParametersL(TApaCommand aCommand, TFileName &aDocumentName, const TDesC8 &aTail); + +protected: + virtual void HandleScreenDeviceChangedL(); + virtual void HandleApplicationSpecificEventL(TInt aType, const TWsEvent &aEvent); + virtual TTypeUid::Ptr MopSupplyObject(TTypeUid aId); + virtual void HandleSystemEventL(const TWsEvent &aEvent); + virtual void Reserved_MtsmPosition(); + virtual void Reserved_MtsmObject(); + virtual void HandleForegroundEventL(TBool aForeground); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q_OS_SYMBIAN + +#endif // QS60MAINAPPUI_H diff --git a/src/gui/s60framework/qs60maindocument.cpp b/src/gui/s60framework/qs60maindocument.cpp new file mode 100644 index 0000000000..37bd55f22e --- /dev/null +++ b/src/gui/s60framework/qs60maindocument.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qs60mainappui.h" +#include "qs60maindocument.h" +#include "qcoreapplication.h" +#include "qevent.h" +#include "private/qcore_symbian_p.h" + +#include <exception> + +QT_BEGIN_NAMESPACE + +/*! + \class QS60MainDocument + \since 4.6 + \brief The QS60MainDocument class is a helper class for S60 migration. + + \warning This class is provided only to get access to S60 specific + functionality in the application framework classes. It is not + portable. We strongly recommend against using it in new applications. + + The QS60MainDocument provides a helper class for use in migrating + from existing S60 based applications to Qt based applications. It is + used in the exact same way as the \c CEikDocument class from + Symbian, but internally provides extensions used by Qt. + + When modifying old S60 applications that rely on implementing + functions in \c CEikDocument, the class should be modified to + inherit from this class instead of \c CEikDocument. Then the + application can choose to override only certain functions. + + For more information on \c CEikDocument, please see the S60 + documentation. + + Unlike other Qt classes, QS60MainDocument behaves like an S60 class, + and can throw Symbian leaves. + + \sa QS60MainApplication, QS60MainAppUi + */ + +/*! + * \brief Constructs an instance of QS60MainDocument. + * + * \a mainApplication should contain a pointer to a QS60MainApplication instance. + */ +QS60MainDocument::QS60MainDocument(CEikApplication &mainApplication) + : QS60MainDocumentBase(mainApplication) +{ + // No implementation required +} + +/*! + * \brief Destroys the QS60MainDocument. + */ +QS60MainDocument::~QS60MainDocument() +{ + // No implementation required +} + +/*! + * \brief Creates an instance of QS60MainAppUi. + * + * \sa QS60MainAppUi + */ +CEikAppUi *QS60MainDocument::CreateAppUiL() +{ + // Create the application user interface, and return a pointer to it; + // the framework takes ownership of this object + return (static_cast <CEikAppUi*>(new(ELeave)QS60MainAppUi)); +} + +/*! + \internal + */ +CFileStore *QS60MainDocument::OpenFileL(TBool /*aDoOpen*/, const TDesC &aFilename, RFs &/*aFs*/) +{ + QT_TRYCATCH_LEAVING( { + QCoreApplication* app = QCoreApplication::instance(); + QString qname = qt_TDesC2QString(aFilename); + QFileOpenEvent* event = new QFileOpenEvent(qname); + app->postEvent(app, event); + }) + return 0; +} + +/*! + \internal + */ +void QS60MainDocument::OpenFileL(CFileStore *&aFileStore, RFile &aFile) +{ + QT_TRYCATCH_LEAVING( { + QCoreApplication* app = QCoreApplication::instance(); + QFileOpenEvent* event = new QFileOpenEvent(aFile); + app->postEvent(app, event); + aFileStore = 0; + }) +} + +QT_END_NAMESPACE diff --git a/src/gui/s60framework/qs60maindocument.h b/src/gui/s60framework/qs60maindocument.h new file mode 100644 index 0000000000..16ea92281d --- /dev/null +++ b/src/gui/s60framework/qs60maindocument.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60MAINDOCUMENT_H +#define QS60MAINDOCUMENT_H + +#include <QtCore/qglobal.h> + +#ifdef Q_OS_SYMBIAN + +#ifdef Q_WS_S60 +#include <AknDoc.h> +typedef CAknDocument QS60MainDocumentBase; +#else +#include <eikdoc.h> +typedef CEikDocument QS60MainDocumentBase; +#endif + +class CEikApplication; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QS60MainAppUi; + +class Q_GUI_EXPORT QS60MainDocument : public QS60MainDocumentBase +{ +public: + + QS60MainDocument(CEikApplication &mainApplication); + // The virtuals are for qdoc. + virtual ~QS60MainDocument(); + +public: + + virtual CEikAppUi *CreateAppUiL(); + +public: + + virtual CFileStore *OpenFileL(TBool aDoOpen, const TDesC &aFilename, RFs &aFs); + + virtual void OpenFileL(CFileStore *&aFileStore, RFile &aFile); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q_OS_SYMBIAN + +#endif // QS60MAINDOCUMENT_H diff --git a/src/gui/s60framework/s60framework.pri b/src/gui/s60framework/s60framework.pri new file mode 100644 index 0000000000..19525b7fdb --- /dev/null +++ b/src/gui/s60framework/s60framework.pri @@ -0,0 +1,10 @@ +SOURCES += s60framework/qs60mainapplication.cpp \ + s60framework/qs60mainappui.cpp \ + s60framework/qs60maindocument.cpp + +HEADERS += s60framework/qs60mainapplication_p.h \ + s60framework/qs60mainapplication.h \ + s60framework/qs60mainappui.h \ + s60framework/qs60maindocument.h + +!isEmpty(QT_LIBINFIX): DEFINES += QT_LIBINFIX_UNQUOTED=$$QT_LIBINFIX \ No newline at end of file diff --git a/src/gui/s60framework/s60main.rss b/src/gui/s60framework/s60main.rss new file mode 100644 index 0000000000..a38087ddfc --- /dev/null +++ b/src/gui/s60framework/s60main.rss @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Symbian application wrapper of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Even S60 application have ENoAppResourceFile and ENonStandardResourceFile +// flags set, the S60 3rd Edition FP1 emulator requires applications to have +// very minimalistic application resource file, otherwise apps refures to start +// This file serves the minimalistic resource file for S60 3.1 platforms. + +// RESOURCE IDENTIFIER +NAME QTMA // 4 letter ID + +// INCLUDES +//#include <eikon.rh> +#include <appinfo.rh> +#include <eikon.rh> +#include <avkon.rsg> +#include <avkon.rh> +#include <avkon.mbg> + +// RESOURCE DEFINITIONS +RESOURCE RSS_SIGNATURE + { + } + +RESOURCE TBUF r_default_document_name + { + buf="QTMA"; + } + +RESOURCE EIK_APP_INFO + { + menubar = r_qt_wrapperapp_menubar; + cba = R_AVKON_SOFTKEYS_EXIT; + } + +RESOURCE MENU_BAR r_qt_wrapperapp_menubar + { + titles = + { + MENU_TITLE { menu_pane = r_qt_wrapperapp_menu; } + }; + } + +RESOURCE MENU_PANE r_qt_wrapperapp_menu + { + } +// End of File diff --git a/src/gui/statemachine/qbasickeyeventtransition.cpp b/src/gui/statemachine/qbasickeyeventtransition.cpp new file mode 100644 index 0000000000..2f1848144c --- /dev/null +++ b/src/gui/statemachine/qbasickeyeventtransition.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbasickeyeventtransition_p.h" + +#ifndef QT_NO_STATEMACHINE + +#include <QtGui/qevent.h> +#include <qdebug.h> +#include <private/qabstracttransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QBasicKeyEventTransition + \since 4.6 + \ingroup statemachine + + \brief The QBasicKeyEventTransition class provides a transition for Qt key events. +*/ + +class QBasicKeyEventTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QBasicKeyEventTransition) +public: + QBasicKeyEventTransitionPrivate(); + + static QBasicKeyEventTransitionPrivate *get(QBasicKeyEventTransition *q); + + QEvent::Type eventType; + int key; + Qt::KeyboardModifiers modifierMask; +}; + +QBasicKeyEventTransitionPrivate::QBasicKeyEventTransitionPrivate() +{ + eventType = QEvent::None; + key = 0; + modifierMask = Qt::NoModifier; +} + +QBasicKeyEventTransitionPrivate *QBasicKeyEventTransitionPrivate::get(QBasicKeyEventTransition *q) +{ + return q->d_func(); +} + +/*! + Constructs a new key event transition with the given \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new event transition for events of the given \a type for the + given \a key, with the given \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QEvent::Type type, int key, + QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; + d->key = key; +} + +/*! + Constructs a new event transition for events of the given \a type for the + given \a key, with the given \a modifierMask and \a sourceState. +*/ +QBasicKeyEventTransition::QBasicKeyEventTransition(QEvent::Type type, int key, + Qt::KeyboardModifiers modifierMask, + QState *sourceState) + : QAbstractTransition(*new QBasicKeyEventTransitionPrivate, sourceState) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; + d->key = key; + d->modifierMask = modifierMask; +} + +/*! + Destroys this event transition. +*/ +QBasicKeyEventTransition::~QBasicKeyEventTransition() +{ +} + +/*! + Returns the event type that this key event transition is associated with. +*/ +QEvent::Type QBasicKeyEventTransition::eventType() const +{ + Q_D(const QBasicKeyEventTransition); + return d->eventType; +} + +/*! + Sets the event \a type that this key event transition is associated with. +*/ +void QBasicKeyEventTransition::setEventType(QEvent::Type type) +{ + Q_D(QBasicKeyEventTransition); + d->eventType = type; +} + +/*! + Returns the key that this key event transition checks for. +*/ +int QBasicKeyEventTransition::key() const +{ + Q_D(const QBasicKeyEventTransition); + return d->key; +} + +/*! + Sets the key that this key event transition will check for. +*/ +void QBasicKeyEventTransition::setKey(int key) +{ + Q_D(QBasicKeyEventTransition); + d->key = key; +} + +/*! + Returns the keyboard modifier mask that this key event transition checks + for. +*/ +Qt::KeyboardModifiers QBasicKeyEventTransition::modifierMask() const +{ + Q_D(const QBasicKeyEventTransition); + return d->modifierMask; +} + +/*! + Sets the keyboard modifier mask that this key event transition will check + for. +*/ +void QBasicKeyEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QBasicKeyEventTransition); + d->modifierMask = modifierMask; +} + +/*! + \reimp +*/ +bool QBasicKeyEventTransition::eventTest(QEvent *event) +{ + Q_D(const QBasicKeyEventTransition); + if (event->type() == d->eventType) { + QKeyEvent *ke = static_cast<QKeyEvent*>(event); + return (ke->key() == d->key) + && ((ke->modifiers() & d->modifierMask) == d->modifierMask); + } + return false; +} + +/*! + \reimp +*/ +void QBasicKeyEventTransition::onTransition(QEvent *) +{ +} + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE diff --git a/src/gui/statemachine/qbasickeyeventtransition_p.h b/src/gui/statemachine/qbasickeyeventtransition_p.h new file mode 100644 index 0000000000..629cae7009 --- /dev/null +++ b/src/gui/statemachine/qbasickeyeventtransition_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBASICKEYEVENTTRANSITION_P_H +#define QBASICKEYEVENTTRANSITION_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 <QtCore/qabstracttransition.h> + +#ifndef QT_NO_STATEMACHINE + +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +class QBasicKeyEventTransitionPrivate; +class Q_AUTOTEST_EXPORT QBasicKeyEventTransition : public QAbstractTransition +{ + Q_OBJECT +public: + QBasicKeyEventTransition(QState *sourceState = 0); + QBasicKeyEventTransition(QEvent::Type type, int key, QState *sourceState = 0); + QBasicKeyEventTransition(QEvent::Type type, int key, + Qt::KeyboardModifiers modifierMask, + QState *sourceState = 0); + ~QBasicKeyEventTransition(); + + QEvent::Type eventType() const; + void setEventType(QEvent::Type type); + + int key() const; + void setKey(int key); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + +protected: + bool eventTest(QEvent *event); + void onTransition(QEvent *); + +private: + Q_DISABLE_COPY(QBasicKeyEventTransition) + Q_DECLARE_PRIVATE(QBasicKeyEventTransition) +}; + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE + +#endif diff --git a/src/gui/statemachine/qbasicmouseeventtransition.cpp b/src/gui/statemachine/qbasicmouseeventtransition.cpp new file mode 100644 index 0000000000..d11b537c42 --- /dev/null +++ b/src/gui/statemachine/qbasicmouseeventtransition.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbasicmouseeventtransition_p.h" + +#ifndef QT_NO_STATEMACHINE + +#include <QtGui/qevent.h> +#include <QtGui/qpainterpath.h> +#include <qdebug.h> +#include <private/qabstracttransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QBasicMouseEventTransition + \since 4.6 + \ingroup statemachine + + \brief The QBasicMouseEventTransition class provides a transition for Qt mouse events. +*/ + +class QBasicMouseEventTransitionPrivate : public QAbstractTransitionPrivate +{ + Q_DECLARE_PUBLIC(QBasicMouseEventTransition) +public: + QBasicMouseEventTransitionPrivate(); + + static QBasicMouseEventTransitionPrivate *get(QBasicMouseEventTransition *q); + + QEvent::Type eventType; + Qt::MouseButton button; + Qt::KeyboardModifiers modifierMask; + QPainterPath path; +}; + +QBasicMouseEventTransitionPrivate::QBasicMouseEventTransitionPrivate() +{ + eventType = QEvent::None; + button = Qt::NoButton; +} + +QBasicMouseEventTransitionPrivate *QBasicMouseEventTransitionPrivate::get(QBasicMouseEventTransition *q) +{ + return q->d_func(); +} + +/*! + Constructs a new mouse event transition with the given \a sourceState. +*/ +QBasicMouseEventTransition::QBasicMouseEventTransition(QState *sourceState) + : QAbstractTransition(*new QBasicMouseEventTransitionPrivate, sourceState) +{ +} + +/*! + Constructs a new mouse event transition for events of the given \a type. +*/ +QBasicMouseEventTransition::QBasicMouseEventTransition(QEvent::Type type, + Qt::MouseButton button, + QState *sourceState) + : QAbstractTransition(*new QBasicMouseEventTransitionPrivate, sourceState) +{ + Q_D(QBasicMouseEventTransition); + d->eventType = type; + d->button = button; +} + +/*! + Destroys this mouse event transition. +*/ +QBasicMouseEventTransition::~QBasicMouseEventTransition() +{ +} + +/*! + Returns the event type that this mouse event transition is associated with. +*/ +QEvent::Type QBasicMouseEventTransition::eventType() const +{ + Q_D(const QBasicMouseEventTransition); + return d->eventType; +} + +/*! + Sets the event \a type that this mouse event transition is associated with. +*/ +void QBasicMouseEventTransition::setEventType(QEvent::Type type) +{ + Q_D(QBasicMouseEventTransition); + d->eventType = type; +} + +/*! + Returns the button that this mouse event transition checks for. +*/ +Qt::MouseButton QBasicMouseEventTransition::button() const +{ + Q_D(const QBasicMouseEventTransition); + return d->button; +} + +/*! + Sets the button that this mouse event transition will check for. +*/ +void QBasicMouseEventTransition::setButton(Qt::MouseButton button) +{ + Q_D(QBasicMouseEventTransition); + d->button = button; +} + +/*! + Returns the keyboard modifier mask that this mouse event transition checks + for. +*/ +Qt::KeyboardModifiers QBasicMouseEventTransition::modifierMask() const +{ + Q_D(const QBasicMouseEventTransition); + return d->modifierMask; +} + +/*! + Sets the keyboard modifier mask that this mouse event transition will check + for. +*/ +void QBasicMouseEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QBasicMouseEventTransition); + d->modifierMask = modifierMask; +} + +/*! + Returns the hit test path for this mouse event transition. +*/ +QPainterPath QBasicMouseEventTransition::hitTestPath() const +{ + Q_D(const QBasicMouseEventTransition); + return d->path; +} + +/*! + Sets the hit test path for this mouse event transition. +*/ +void QBasicMouseEventTransition::setHitTestPath(const QPainterPath &path) +{ + Q_D(QBasicMouseEventTransition); + d->path = path; +} + +/*! + \reimp +*/ +bool QBasicMouseEventTransition::eventTest(QEvent *event) +{ + Q_D(const QBasicMouseEventTransition); + if (event->type() == d->eventType) { + QMouseEvent *me = static_cast<QMouseEvent*>(event); + return (me->button() == d->button) + && ((me->modifiers() & d->modifierMask) == d->modifierMask) + && (d->path.isEmpty() || d->path.contains(me->pos())); + } + return false; +} + +/*! + \reimp +*/ +void QBasicMouseEventTransition::onTransition(QEvent *) +{ +} + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE diff --git a/src/gui/statemachine/qbasicmouseeventtransition_p.h b/src/gui/statemachine/qbasicmouseeventtransition_p.h new file mode 100644 index 0000000000..61eefa6cfe --- /dev/null +++ b/src/gui/statemachine/qbasicmouseeventtransition_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBASICMOUSEEVENTTRANSITION_P_H +#define QBASICMOUSEEVENTTRANSITION_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 <QtCore/qabstracttransition.h> + +#ifndef QT_NO_STATEMACHINE + +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +class QPainterPath; + +class QBasicMouseEventTransitionPrivate; +class Q_AUTOTEST_EXPORT QBasicMouseEventTransition : public QAbstractTransition +{ + Q_OBJECT +public: + QBasicMouseEventTransition(QState *sourceState = 0); + QBasicMouseEventTransition(QEvent::Type type, Qt::MouseButton button, + QState *sourceState = 0); + ~QBasicMouseEventTransition(); + + QEvent::Type eventType() const; + void setEventType(QEvent::Type type); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + + QPainterPath hitTestPath() const; + void setHitTestPath(const QPainterPath &path); + +protected: + bool eventTest(QEvent *event); + void onTransition(QEvent *); + +private: + Q_DISABLE_COPY(QBasicMouseEventTransition) + Q_DECLARE_PRIVATE(QBasicMouseEventTransition) +}; + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE + +#endif diff --git a/src/gui/statemachine/qguistatemachine.cpp b/src/gui/statemachine/qguistatemachine.cpp new file mode 100644 index 0000000000..2a0de3c1c7 --- /dev/null +++ b/src/gui/statemachine/qguistatemachine.cpp @@ -0,0 +1,523 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qstatemachine.h> + +#ifndef QT_NO_STATEMACHINE + +#include <private/qstatemachine_p.h> +#include <QtGui/qevent.h> +#include <QtGui/qgraphicssceneevent.h> + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT const QStateMachinePrivate::Handler *qcoreStateMachineHandler(); + +static QEvent *cloneEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + return new QMouseEvent(*static_cast<QMouseEvent*>(e)); + case QEvent::KeyPress: + case QEvent::KeyRelease: + return new QKeyEvent(*static_cast<QKeyEvent*>(e)); + case QEvent::FocusIn: + case QEvent::FocusOut: + return new QFocusEvent(*static_cast<QFocusEvent*>(e)); + case QEvent::Enter: + return new QEvent(*e); + case QEvent::Leave: + return new QEvent(*e); + break; + case QEvent::Paint: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::Move: + return new QMoveEvent(*static_cast<QMoveEvent*>(e)); + case QEvent::Resize: + return new QResizeEvent(*static_cast<QResizeEvent*>(e)); + case QEvent::Create: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::Destroy: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::Show: + return new QShowEvent(*static_cast<QShowEvent*>(e)); + case QEvent::Hide: + return new QHideEvent(*static_cast<QHideEvent*>(e)); + case QEvent::Close: + return new QCloseEvent(*static_cast<QCloseEvent*>(e)); + case QEvent::Quit: + return new QEvent(*e); + case QEvent::ParentChange: + return new QEvent(*e); + case QEvent::ParentAboutToChange: + return new QEvent(*e); + case QEvent::ThreadChange: + return new QEvent(*e); + + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + return new QEvent(*e); + + case QEvent::ShowToParent: + return new QEvent(*e); + case QEvent::HideToParent: + return new QEvent(*e); +#ifndef QT_NO_WHEELEVENT + case QEvent::Wheel: + return new QWheelEvent(*static_cast<QWheelEvent*>(e)); +#endif //QT_NO_WHEELEVENT + case QEvent::WindowTitleChange: + return new QEvent(*e); + case QEvent::WindowIconChange: + return new QEvent(*e); + case QEvent::ApplicationWindowIconChange: + return new QEvent(*e); + case QEvent::ApplicationFontChange: + return new QEvent(*e); + case QEvent::ApplicationLayoutDirectionChange: + return new QEvent(*e); + case QEvent::ApplicationPaletteChange: + return new QEvent(*e); + case QEvent::PaletteChange: + return new QEvent(*e); + case QEvent::Clipboard: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::Speech: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::MetaCall: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::SockAct: + return new QEvent(*e); + case QEvent::WinEventAct: + return new QEvent(*e); + case QEvent::DeferredDelete: + return new QEvent(*e); +#ifndef QT_NO_DRAGANDDROP + case QEvent::DragEnter: + return new QDragEnterEvent(*static_cast<QDragEnterEvent*>(e)); + case QEvent::DragMove: + return new QDragMoveEvent(*static_cast<QDragMoveEvent*>(e)); + case QEvent::DragLeave: + return new QDragLeaveEvent(*static_cast<QDragLeaveEvent*>(e)); + case QEvent::Drop: + return new QDropEvent(*static_cast<QDragMoveEvent*>(e)); + case QEvent::DragResponse: + return new QDragResponseEvent(*static_cast<QDragResponseEvent*>(e)); +#endif + case QEvent::ChildAdded: + return new QChildEvent(*static_cast<QChildEvent*>(e)); + case QEvent::ChildPolished: + return new QChildEvent(*static_cast<QChildEvent*>(e)); +#ifdef QT3_SUPPORT + case QEvent::ChildInsertedRequest: + return new QEvent(*e); + case QEvent::ChildInserted: + return new QChildEvent(*static_cast<QChildEvent*>(e)); + case QEvent::LayoutHint: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; +#endif + case QEvent::ChildRemoved: + return new QChildEvent(*static_cast<QChildEvent*>(e)); + case QEvent::ShowWindowRequest: + return new QEvent(*e); + case QEvent::PolishRequest: + return new QEvent(*e); + case QEvent::Polish: + return new QEvent(*e); + case QEvent::LayoutRequest: + return new QEvent(*e); + case QEvent::UpdateRequest: + return new QEvent(*e); + case QEvent::UpdateLater: + return new QEvent(*e); + + case QEvent::EmbeddingControl: + return new QEvent(*e); + case QEvent::ActivateControl: + return new QEvent(*e); + case QEvent::DeactivateControl: + return new QEvent(*e); + +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: + return new QContextMenuEvent(*static_cast<QContextMenuEvent*>(e)); +#endif + case QEvent::InputMethod: + return new QInputMethodEvent(*static_cast<QInputMethodEvent*>(e)); + case QEvent::AccessibilityPrepare: + return new QEvent(*e); +#ifndef QT_NO_TABLETEVENT + case QEvent::TabletMove: + return new QTabletEvent(*static_cast<QTabletEvent*>(e)); +#endif //QT_NO_TABLETEVENT + case QEvent::LocaleChange: + return new QEvent(*e); + case QEvent::LanguageChange: + return new QEvent(*e); + case QEvent::LayoutDirectionChange: + return new QEvent(*e); + case QEvent::Style: + return new QEvent(*e); +#ifndef QT_NO_TABLETEVENT + case QEvent::TabletPress: + return new QTabletEvent(*static_cast<QTabletEvent*>(e)); + case QEvent::TabletRelease: + return new QTabletEvent(*static_cast<QTabletEvent*>(e)); +#endif //QT_NO_TABLETEVENT + case QEvent::OkRequest: + return new QEvent(*e); + case QEvent::HelpRequest: + return new QEvent(*e); + + case QEvent::IconDrag: + return new QIconDragEvent(*static_cast<QIconDragEvent*>(e)); + + case QEvent::FontChange: + return new QEvent(*e); + case QEvent::EnabledChange: + return new QEvent(*e); + case QEvent::ActivationChange: + return new QEvent(*e); + case QEvent::StyleChange: + return new QEvent(*e); + case QEvent::IconTextChange: + return new QEvent(*e); + case QEvent::ModifiedChange: + return new QEvent(*e); + case QEvent::MouseTrackingChange: + return new QEvent(*e); + + case QEvent::WindowBlocked: + return new QEvent(*e); + case QEvent::WindowUnblocked: + return new QEvent(*e); + case QEvent::WindowStateChange: + return new QWindowStateChangeEvent(*static_cast<QWindowStateChangeEvent*>(e)); + + case QEvent::ToolTip: + return new QHelpEvent(*static_cast<QHelpEvent*>(e)); + case QEvent::WhatsThis: + return new QHelpEvent(*static_cast<QHelpEvent*>(e)); +#ifndef QT_NO_STATUSTIP + case QEvent::StatusTip: + return new QStatusTipEvent(*static_cast<QStatusTipEvent*>(e)); +#endif //QT_NO_STATUSTIP +#ifndef QT_NO_ACTION + case QEvent::ActionChanged: + case QEvent::ActionAdded: + case QEvent::ActionRemoved: + return new QActionEvent(*static_cast<QActionEvent*>(e)); +#endif + case QEvent::FileOpen: + return new QFileOpenEvent(*static_cast<QFileOpenEvent*>(e)); + +#ifndef QT_NO_SHORTCUT + case QEvent::Shortcut: + return new QShortcutEvent(*static_cast<QShortcutEvent*>(e)); +#endif //QT_NO_SHORTCUT + case QEvent::ShortcutOverride: + return new QKeyEvent(*static_cast<QKeyEvent*>(e)); + +#ifdef QT3_SUPPORT + case QEvent::Accel: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::AccelAvailable: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; +#endif + +#ifndef QT_NO_WHATSTHIS + case QEvent::WhatsThisClicked: + return new QWhatsThisClickedEvent(*static_cast<QWhatsThisClickedEvent*>(e)); +#endif //QT_NO_WHATSTHIS + +#ifndef QT_NO_TOOLBAR + case QEvent::ToolBarChange: + return new QToolBarChangeEvent(*static_cast<QToolBarChangeEvent*>(e)); +#endif //QT_NO_TOOLBAR + + case QEvent::ApplicationActivate: + return new QEvent(*e); + case QEvent::ApplicationDeactivate: + return new QEvent(*e); + + case QEvent::QueryWhatsThis: + return new QHelpEvent(*static_cast<QHelpEvent*>(e)); + case QEvent::EnterWhatsThisMode: + return new QEvent(*e); + case QEvent::LeaveWhatsThisMode: + return new QEvent(*e); + + case QEvent::ZOrderChange: + return new QEvent(*e); + + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + return new QHoverEvent(*static_cast<QHoverEvent*>(e)); + + case QEvent::AccessibilityHelp: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + case QEvent::AccessibilityDescription: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + return new QEvent(*e); + case QEvent::LeaveEditFocus: + return new QEvent(*e); +#endif + case QEvent::AcceptDropsChange: + return new QEvent(*e); + +#ifdef QT3_SUPPORT + case QEvent::MenubarUpdated: + return new QMenubarUpdatedEvent(*static_cast<QMenubarUpdatedEvent*>(e)); +#endif + + case QEvent::ZeroTimerEvent: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent*>(e); + QGraphicsSceneMouseEvent *me2 = new QGraphicsSceneMouseEvent(me->type()); + me2->setWidget(me->widget()); + me2->setPos(me->pos()); + me2->setScenePos(me->scenePos()); + me2->setScreenPos(me->screenPos()); +// ### for all buttons + me2->setButtonDownPos(Qt::LeftButton, me->buttonDownPos(Qt::LeftButton)); + me2->setButtonDownPos(Qt::RightButton, me->buttonDownPos(Qt::RightButton)); + me2->setButtonDownScreenPos(Qt::LeftButton, me->buttonDownScreenPos(Qt::LeftButton)); + me2->setButtonDownScreenPos(Qt::RightButton, me->buttonDownScreenPos(Qt::RightButton)); + me2->setLastPos(me->lastPos()); + me2->setLastScenePos(me->lastScenePos()); + me2->setLastScreenPos(me->lastScreenPos()); + me2->setButtons(me->buttons()); + me2->setButton(me->button()); + me2->setModifiers(me->modifiers()); + return me2; + } + + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *me = static_cast<QGraphicsSceneContextMenuEvent*>(e); + QGraphicsSceneContextMenuEvent *me2 = new QGraphicsSceneContextMenuEvent(me->type()); + me2->setWidget(me->widget()); + me2->setPos(me->pos()); + me2->setScenePos(me->scenePos()); + me2->setScreenPos(me->screenPos()); + me2->setModifiers(me->modifiers()); + me2->setReason(me->reason()); + return me2; + } + + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: { + QGraphicsSceneHoverEvent *he = static_cast<QGraphicsSceneHoverEvent*>(e); + QGraphicsSceneHoverEvent *he2 = new QGraphicsSceneHoverEvent(he->type()); + he2->setPos(he->pos()); + he2->setScenePos(he->scenePos()); + he2->setScreenPos(he->screenPos()); + he2->setLastPos(he->lastPos()); + he2->setLastScenePos(he->lastScenePos()); + he2->setLastScreenPos(he->lastScreenPos()); + he2->setModifiers(he->modifiers()); + return he2; + } + case QEvent::GraphicsSceneHelp: + return new QHelpEvent(*static_cast<QHelpEvent*>(e)); + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneDragDropEvent *dde = static_cast<QGraphicsSceneDragDropEvent*>(e); + QGraphicsSceneDragDropEvent *dde2 = new QGraphicsSceneDragDropEvent(dde->type()); + dde2->setPos(dde->pos()); + dde2->setScenePos(dde->scenePos()); + dde2->setScreenPos(dde->screenPos()); + dde2->setButtons(dde->buttons()); + dde2->setModifiers(dde->modifiers()); + return dde2; + } + case QEvent::GraphicsSceneWheel: { + QGraphicsSceneWheelEvent *we = static_cast<QGraphicsSceneWheelEvent*>(e); + QGraphicsSceneWheelEvent *we2 = new QGraphicsSceneWheelEvent(we->type()); + we2->setPos(we->pos()); + we2->setScenePos(we->scenePos()); + we2->setScreenPos(we->screenPos()); + we2->setButtons(we->buttons()); + we2->setModifiers(we->modifiers()); + we2->setOrientation(we->orientation()); + we2->setDelta(we->delta()); + return we2; + } +#endif + case QEvent::KeyboardLayoutChange: + return new QEvent(*e); + + case QEvent::DynamicPropertyChange: + return new QDynamicPropertyChangeEvent(*static_cast<QDynamicPropertyChangeEvent*>(e)); + +#ifndef QT_NO_TABLETEVENT + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: + return new QTabletEvent(*static_cast<QTabletEvent*>(e)); +#endif //QT_NO_TABLETEVENT + + case QEvent::NonClientAreaMouseMove: + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::NonClientAreaMouseButtonRelease: + case QEvent::NonClientAreaMouseButtonDblClick: + return new QMouseEvent(*static_cast<QMouseEvent*>(e)); + + case QEvent::MacSizeChange: + return new QEvent(*e); + + case QEvent::ContentsRectChange: + return new QEvent(*e); + + case QEvent::MacGLWindowChange: + return new QEvent(*e); + + case QEvent::FutureCallOut: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneResize: { + QGraphicsSceneResizeEvent *re = static_cast<QGraphicsSceneResizeEvent*>(e); + QGraphicsSceneResizeEvent *re2 = new QGraphicsSceneResizeEvent(); + re2->setOldSize(re->oldSize()); + re2->setNewSize(re->newSize()); + return re2; + } + case QEvent::GraphicsSceneMove: { + QGraphicsSceneMoveEvent *me = static_cast<QGraphicsSceneMoveEvent*>(e); + QGraphicsSceneMoveEvent *me2 = new QGraphicsSceneMoveEvent(); + me2->setWidget(me->widget()); + me2->setNewPos(me->newPos()); + me2->setOldPos(me->oldPos()); + return me2; + } +#endif + case QEvent::CursorChange: + return new QEvent(*e); + case QEvent::ToolTipChange: + return new QEvent(*e); + + case QEvent::NetworkReplyUpdated: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + + case QEvent::GrabMouse: + case QEvent::UngrabMouse: + case QEvent::GrabKeyboard: + case QEvent::UngrabKeyboard: + return new QEvent(*e); + + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + return new QTouchEvent(*static_cast<QTouchEvent*>(e)); + +#ifndef QT_NO_GESTURES + case QEvent::NativeGesture: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; +#endif + + case QEvent::RequestSoftwareInputPanel: + case QEvent::CloseSoftwareInputPanel: + return new QEvent(*e); + + case QEvent::UpdateSoftKeys: + return new QEvent(*e); + + case QEvent::User: + case QEvent::MaxUser: + Q_ASSERT_X(false, "cloneEvent()", "not implemented"); + break; + default: + ; + } + return qcoreStateMachineHandler()->cloneEvent(e); +} + +const QStateMachinePrivate::Handler qt_gui_statemachine_handler = { + cloneEvent +}; + +static const QStateMachinePrivate::Handler *qt_guistatemachine_last_handler = 0; +int qRegisterGuiStateMachine() +{ + qt_guistatemachine_last_handler = QStateMachinePrivate::handler; + QStateMachinePrivate::handler = &qt_gui_statemachine_handler; + return 1; +} +Q_CONSTRUCTOR_FUNCTION(qRegisterGuiStateMachine) + +int qUnregisterGuiStateMachine() +{ + QStateMachinePrivate::handler = qt_guistatemachine_last_handler; + return 1; +} +Q_DESTRUCTOR_FUNCTION(qUnregisterGuiStateMachine) + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE diff --git a/src/gui/statemachine/qkeyeventtransition.cpp b/src/gui/statemachine/qkeyeventtransition.cpp new file mode 100644 index 0000000000..65af91b382 --- /dev/null +++ b/src/gui/statemachine/qkeyeventtransition.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qkeyeventtransition.h" + +#ifndef QT_NO_STATEMACHINE + +#include "qbasickeyeventtransition_p.h" +#include <QtCore/qstatemachine.h> +#include <private/qeventtransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QKeyEventTransition + + \brief The QKeyEventTransition class provides a transition for key events. + + \since 4.6 + \ingroup statemachine + + QKeyEventTransition is part of \l{The State Machine Framework}. + + \sa QState::addTransition() +*/ + +/*! + \property QKeyEventTransition::key + + \brief the key that this key event transition is associated with +*/ + +/*! + \property QKeyEventTransition::modifierMask + + \brief the keyboard modifier mask that this key event transition checks for +*/ + +class QKeyEventTransitionPrivate : public QEventTransitionPrivate +{ + Q_DECLARE_PUBLIC(QKeyEventTransition) +public: + QKeyEventTransitionPrivate() {} + + QBasicKeyEventTransition *transition; +}; + +/*! + Constructs a new key event transition with the given \a sourceState. +*/ +QKeyEventTransition::QKeyEventTransition(QState *sourceState) + : QEventTransition(*new QKeyEventTransitionPrivate, sourceState) +{ + Q_D(QKeyEventTransition); + d->transition = new QBasicKeyEventTransition(); +} + +/*! + Constructs a new key event transition for events of the given \a type for + the given \a object, with the given \a key and \a sourceState. +*/ +QKeyEventTransition::QKeyEventTransition(QObject *object, QEvent::Type type, + int key, QState *sourceState) + : QEventTransition(*new QKeyEventTransitionPrivate, object, type, sourceState) +{ + Q_D(QKeyEventTransition); + d->transition = new QBasicKeyEventTransition(type, key); +} + +/*! + Destroys this key event transition. +*/ +QKeyEventTransition::~QKeyEventTransition() +{ + Q_D(QKeyEventTransition); + delete d->transition; +} + +/*! + Returns the key that this key event transition checks for. +*/ +int QKeyEventTransition::key() const +{ + Q_D(const QKeyEventTransition); + return d->transition->key(); +} + +/*! + Sets the key that this key event transition will check for. +*/ +void QKeyEventTransition::setKey(int key) +{ + Q_D(QKeyEventTransition); + d->transition->setKey(key); +} + +/*! + Returns the keyboard modifier mask that this key event transition checks + for. +*/ +Qt::KeyboardModifiers QKeyEventTransition::modifierMask() const +{ + Q_D(const QKeyEventTransition); + return d->transition->modifierMask(); +} + +/*! + Sets the keyboard modifier mask that this key event transition will + check for to \a modifierMask. +*/ +void QKeyEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QKeyEventTransition); + d->transition->setModifierMask(modifierMask); +} + +/*! + \reimp +*/ +bool QKeyEventTransition::eventTest(QEvent *event) +{ + Q_D(const QKeyEventTransition); + if (!QEventTransition::eventTest(event)) + return false; + QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(event); + d->transition->setEventType(we->event()->type()); + return QAbstractTransitionPrivate::get(d->transition)->callEventTest(we->event()); +} + +/*! + \reimp +*/ +void QKeyEventTransition::onTransition(QEvent *event) +{ + QEventTransition::onTransition(event); +} + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE diff --git a/src/gui/statemachine/qkeyeventtransition.h b/src/gui/statemachine/qkeyeventtransition.h new file mode 100644 index 0000000000..2bc47e4178 --- /dev/null +++ b/src/gui/statemachine/qkeyeventtransition.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKEYEVENTTRANSITION_H +#define QKEYEVENTTRANSITION_H + +#include <QtCore/qeventtransition.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STATEMACHINE + +class QKeyEventTransitionPrivate; +class Q_GUI_EXPORT QKeyEventTransition : public QEventTransition +{ + Q_OBJECT + Q_PROPERTY(int key READ key WRITE setKey) + Q_PROPERTY(Qt::KeyboardModifiers modifierMask READ modifierMask WRITE setModifierMask) +public: + QKeyEventTransition(QState *sourceState = 0); + QKeyEventTransition(QObject *object, QEvent::Type type, int key, + QState *sourceState = 0); + ~QKeyEventTransition(); + + int key() const; + void setKey(int key); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + +protected: + void onTransition(QEvent *event); + bool eventTest(QEvent *event); + +private: + Q_DISABLE_COPY(QKeyEventTransition) + Q_DECLARE_PRIVATE(QKeyEventTransition) +}; + +#endif //QT_NO_STATEMACHINE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/statemachine/qmouseeventtransition.cpp b/src/gui/statemachine/qmouseeventtransition.cpp new file mode 100644 index 0000000000..3ecf1e2fa8 --- /dev/null +++ b/src/gui/statemachine/qmouseeventtransition.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmouseeventtransition.h" + +#ifndef QT_NO_STATEMACHINE + +#include "qbasicmouseeventtransition_p.h" +#include <QtCore/qstatemachine.h> +#include <QtGui/qpainterpath.h> +#include <private/qeventtransition_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QMouseEventTransition + + \brief The QMouseEventTransition class provides a transition for mouse events. + + \since 4.6 + \ingroup statemachine + + QMouseEventTransition is part of \l{The State Machine Framework}. + + \sa QState::addTransition() +*/ + +/*! + \property QMouseEventTransition::button + + \brief the button that this mouse event transition is associated with +*/ + +/*! + \property QMouseEventTransition::modifierMask + + \brief the keyboard modifier mask that this mouse event transition checks for +*/ + +class QMouseEventTransitionPrivate : public QEventTransitionPrivate +{ + Q_DECLARE_PUBLIC(QMouseEventTransition) +public: + QMouseEventTransitionPrivate(); + + QBasicMouseEventTransition *transition; +}; + +QMouseEventTransitionPrivate::QMouseEventTransitionPrivate() +{ +} + +/*! + Constructs a new mouse event transition with the given \a sourceState. +*/ +QMouseEventTransition::QMouseEventTransition(QState *sourceState) + : QEventTransition(*new QMouseEventTransitionPrivate, sourceState) +{ + Q_D(QMouseEventTransition); + d->transition = new QBasicMouseEventTransition(); +} + +/*! + Constructs a new mouse event transition for events of the given \a type for + the given \a object, with the given \a button and \a sourceState. +*/ +QMouseEventTransition::QMouseEventTransition(QObject *object, QEvent::Type type, + Qt::MouseButton button, + QState *sourceState) + : QEventTransition(*new QMouseEventTransitionPrivate, object, type, sourceState) +{ + Q_D(QMouseEventTransition); + d->transition = new QBasicMouseEventTransition(type, button); +} + +/*! + Destroys this mouse event transition. +*/ +QMouseEventTransition::~QMouseEventTransition() +{ + Q_D(QMouseEventTransition); + delete d->transition; +} + +/*! + Returns the button that this mouse event transition checks for. +*/ +Qt::MouseButton QMouseEventTransition::button() const +{ + Q_D(const QMouseEventTransition); + return d->transition->button(); +} + +/*! + Sets the \a button that this mouse event transition will check for. +*/ +void QMouseEventTransition::setButton(Qt::MouseButton button) +{ + Q_D(QMouseEventTransition); + d->transition->setButton(button); +} + +/*! + Returns the keyboard modifier mask that this mouse event transition checks + for. +*/ +Qt::KeyboardModifiers QMouseEventTransition::modifierMask() const +{ + Q_D(const QMouseEventTransition); + return d->transition->modifierMask(); +} + +/*! + Sets the keyboard modifier mask that this mouse event transition will + check for to \a modifierMask. +*/ +void QMouseEventTransition::setModifierMask(Qt::KeyboardModifiers modifierMask) +{ + Q_D(QMouseEventTransition); + d->transition->setModifierMask(modifierMask); +} + +/*! + Returns the hit test path for this mouse event transition. +*/ +QPainterPath QMouseEventTransition::hitTestPath() const +{ + Q_D(const QMouseEventTransition); + return d->transition->hitTestPath(); +} + +/*! + Sets the hit test path for this mouse event transition to \a path. + If a valid path has been set, the transition will only trigger if the mouse + event position (QMouseEvent::pos()) is inside the path. + + \sa QPainterPath::contains() +*/ +void QMouseEventTransition::setHitTestPath(const QPainterPath &path) +{ + Q_D(QMouseEventTransition); + d->transition->setHitTestPath(path); +} + +/*! + \reimp +*/ +bool QMouseEventTransition::eventTest(QEvent *event) +{ + Q_D(const QMouseEventTransition); + if (!QEventTransition::eventTest(event)) + return false; + QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(event); + d->transition->setEventType(we->event()->type()); + return QAbstractTransitionPrivate::get(d->transition)->callEventTest(we->event()); +} + +/*! + \reimp +*/ +void QMouseEventTransition::onTransition(QEvent *event) +{ + QEventTransition::onTransition(event); +} + +QT_END_NAMESPACE + +#endif //QT_NO_STATEMACHINE diff --git a/src/gui/statemachine/qmouseeventtransition.h b/src/gui/statemachine/qmouseeventtransition.h new file mode 100644 index 0000000000..8be10c8513 --- /dev/null +++ b/src/gui/statemachine/qmouseeventtransition.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOUSEEVENTTRANSITION_H +#define QMOUSEEVENTTRANSITION_H + +#include <QtCore/qeventtransition.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifndef QT_NO_STATEMACHINE + +class QMouseEventTransitionPrivate; +class QPainterPath; +class Q_GUI_EXPORT QMouseEventTransition : public QEventTransition +{ + Q_OBJECT + Q_PROPERTY(Qt::MouseButton button READ button WRITE setButton) + Q_PROPERTY(Qt::KeyboardModifiers modifierMask READ modifierMask WRITE setModifierMask) +public: + QMouseEventTransition(QState *sourceState = 0); + QMouseEventTransition(QObject *object, QEvent::Type type, + Qt::MouseButton button, QState *sourceState = 0); + ~QMouseEventTransition(); + + Qt::MouseButton button() const; + void setButton(Qt::MouseButton button); + + Qt::KeyboardModifiers modifierMask() const; + void setModifierMask(Qt::KeyboardModifiers modifiers); + + QPainterPath hitTestPath() const; + void setHitTestPath(const QPainterPath &path); + +protected: + void onTransition(QEvent *event); + bool eventTest(QEvent *event); + +private: + Q_DISABLE_COPY(QMouseEventTransition) + Q_DECLARE_PRIVATE(QMouseEventTransition) +}; + +#endif //QT_NO_STATEMACHINE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/statemachine/statemachine.pri b/src/gui/statemachine/statemachine.pri new file mode 100644 index 0000000000..2eb1e05be6 --- /dev/null +++ b/src/gui/statemachine/statemachine.pri @@ -0,0 +1,13 @@ +SOURCES += $$PWD/qguistatemachine.cpp +!contains(DEFINES, QT_NO_STATEMACHINE_EVENTFILTER) { + HEADERS += \ + $$PWD/qkeyeventtransition.h \ + $$PWD/qmouseeventtransition.h \ + $$PWD/qbasickeyeventtransition_p.h \ + $$PWD/qbasicmouseeventtransition_p.h + SOURCES += \ + $$PWD/qkeyeventtransition.cpp \ + $$PWD/qmouseeventtransition.cpp \ + $$PWD/qbasickeyeventtransition.cpp \ + $$PWD/qbasicmouseeventtransition.cpp +} diff --git a/src/gui/styles/images/cdr-128.png b/src/gui/styles/images/cdr-128.png new file mode 100644 index 0000000000..c5daa15fc8 Binary files /dev/null and b/src/gui/styles/images/cdr-128.png differ diff --git a/src/gui/styles/images/cdr-16.png b/src/gui/styles/images/cdr-16.png new file mode 100644 index 0000000000..82d7533bd1 Binary files /dev/null and b/src/gui/styles/images/cdr-16.png differ diff --git a/src/gui/styles/images/cdr-32.png b/src/gui/styles/images/cdr-32.png new file mode 100644 index 0000000000..dcfb085da5 Binary files /dev/null and b/src/gui/styles/images/cdr-32.png differ diff --git a/src/gui/styles/images/closedock-16.png b/src/gui/styles/images/closedock-16.png new file mode 100644 index 0000000000..ab9d669eee Binary files /dev/null and b/src/gui/styles/images/closedock-16.png differ diff --git a/src/gui/styles/images/closedock-down-16.png b/src/gui/styles/images/closedock-down-16.png new file mode 100644 index 0000000000..c1791dd2cc Binary files /dev/null and b/src/gui/styles/images/closedock-down-16.png differ diff --git a/src/gui/styles/images/computer-16.png b/src/gui/styles/images/computer-16.png new file mode 100644 index 0000000000..43fb0bb581 Binary files /dev/null and b/src/gui/styles/images/computer-16.png differ diff --git a/src/gui/styles/images/computer-32.png b/src/gui/styles/images/computer-32.png new file mode 100644 index 0000000000..6d750ce89b Binary files /dev/null and b/src/gui/styles/images/computer-32.png differ diff --git a/src/gui/styles/images/defaults60theme.blob b/src/gui/styles/images/defaults60theme.blob new file mode 100644 index 0000000000..f3a59528d6 Binary files /dev/null and b/src/gui/styles/images/defaults60theme.blob differ diff --git a/src/gui/styles/images/desktop-16.png b/src/gui/styles/images/desktop-16.png new file mode 100644 index 0000000000..d612dfb0fc Binary files /dev/null and b/src/gui/styles/images/desktop-16.png differ diff --git a/src/gui/styles/images/desktop-32.png b/src/gui/styles/images/desktop-32.png new file mode 100644 index 0000000000..ad85b48d3a Binary files /dev/null and b/src/gui/styles/images/desktop-32.png differ diff --git a/src/gui/styles/images/dirclosed-128.png b/src/gui/styles/images/dirclosed-128.png new file mode 100644 index 0000000000..e4fa843162 Binary files /dev/null and b/src/gui/styles/images/dirclosed-128.png differ diff --git a/src/gui/styles/images/dirclosed-16.png b/src/gui/styles/images/dirclosed-16.png new file mode 100644 index 0000000000..333fe8eaac Binary files /dev/null and b/src/gui/styles/images/dirclosed-16.png differ diff --git a/src/gui/styles/images/dirclosed-32.png b/src/gui/styles/images/dirclosed-32.png new file mode 100644 index 0000000000..3add907ed5 Binary files /dev/null and b/src/gui/styles/images/dirclosed-32.png differ diff --git a/src/gui/styles/images/dirlink-128.png b/src/gui/styles/images/dirlink-128.png new file mode 100644 index 0000000000..ec299f8e52 Binary files /dev/null and b/src/gui/styles/images/dirlink-128.png differ diff --git a/src/gui/styles/images/dirlink-16.png b/src/gui/styles/images/dirlink-16.png new file mode 100644 index 0000000000..9f16cd3520 Binary files /dev/null and b/src/gui/styles/images/dirlink-16.png differ diff --git a/src/gui/styles/images/dirlink-32.png b/src/gui/styles/images/dirlink-32.png new file mode 100644 index 0000000000..776536d131 Binary files /dev/null and b/src/gui/styles/images/dirlink-32.png differ diff --git a/src/gui/styles/images/diropen-128.png b/src/gui/styles/images/diropen-128.png new file mode 100644 index 0000000000..b91c4af72a Binary files /dev/null and b/src/gui/styles/images/diropen-128.png differ diff --git a/src/gui/styles/images/diropen-16.png b/src/gui/styles/images/diropen-16.png new file mode 100644 index 0000000000..95f0771d06 Binary files /dev/null and b/src/gui/styles/images/diropen-16.png differ diff --git a/src/gui/styles/images/diropen-32.png b/src/gui/styles/images/diropen-32.png new file mode 100644 index 0000000000..af5f7e7e81 Binary files /dev/null and b/src/gui/styles/images/diropen-32.png differ diff --git a/src/gui/styles/images/dockdock-16.png b/src/gui/styles/images/dockdock-16.png new file mode 100644 index 0000000000..4ac9483176 Binary files /dev/null and b/src/gui/styles/images/dockdock-16.png differ diff --git a/src/gui/styles/images/dockdock-down-16.png b/src/gui/styles/images/dockdock-down-16.png new file mode 100644 index 0000000000..2e85a679be Binary files /dev/null and b/src/gui/styles/images/dockdock-down-16.png differ diff --git a/src/gui/styles/images/down-128.png b/src/gui/styles/images/down-128.png new file mode 100644 index 0000000000..09dfe43a93 Binary files /dev/null and b/src/gui/styles/images/down-128.png differ diff --git a/src/gui/styles/images/down-16.png b/src/gui/styles/images/down-16.png new file mode 100644 index 0000000000..c60a174e25 Binary files /dev/null and b/src/gui/styles/images/down-16.png differ diff --git a/src/gui/styles/images/down-32.png b/src/gui/styles/images/down-32.png new file mode 100644 index 0000000000..46eadb8e12 Binary files /dev/null and b/src/gui/styles/images/down-32.png differ diff --git a/src/gui/styles/images/dvd-128.png b/src/gui/styles/images/dvd-128.png new file mode 100644 index 0000000000..9ed9dc1e55 Binary files /dev/null and b/src/gui/styles/images/dvd-128.png differ diff --git a/src/gui/styles/images/dvd-16.png b/src/gui/styles/images/dvd-16.png new file mode 100644 index 0000000000..623386d4ca Binary files /dev/null and b/src/gui/styles/images/dvd-16.png differ diff --git a/src/gui/styles/images/dvd-32.png b/src/gui/styles/images/dvd-32.png new file mode 100644 index 0000000000..089b72accb Binary files /dev/null and b/src/gui/styles/images/dvd-32.png differ diff --git a/src/gui/styles/images/file-128.png b/src/gui/styles/images/file-128.png new file mode 100644 index 0000000000..46e6ceb49a Binary files /dev/null and b/src/gui/styles/images/file-128.png differ diff --git a/src/gui/styles/images/file-16.png b/src/gui/styles/images/file-16.png new file mode 100644 index 0000000000..664b56356b Binary files /dev/null and b/src/gui/styles/images/file-16.png differ diff --git a/src/gui/styles/images/file-32.png b/src/gui/styles/images/file-32.png new file mode 100644 index 0000000000..83e5c3d311 Binary files /dev/null and b/src/gui/styles/images/file-32.png differ diff --git a/src/gui/styles/images/filecontents-128.png b/src/gui/styles/images/filecontents-128.png new file mode 100644 index 0000000000..50e0a838e2 Binary files /dev/null and b/src/gui/styles/images/filecontents-128.png differ diff --git a/src/gui/styles/images/filecontents-16.png b/src/gui/styles/images/filecontents-16.png new file mode 100644 index 0000000000..b9399ccf2e Binary files /dev/null and b/src/gui/styles/images/filecontents-16.png differ diff --git a/src/gui/styles/images/filecontents-32.png b/src/gui/styles/images/filecontents-32.png new file mode 100644 index 0000000000..3761f70690 Binary files /dev/null and b/src/gui/styles/images/filecontents-32.png differ diff --git a/src/gui/styles/images/fileinfo-128.png b/src/gui/styles/images/fileinfo-128.png new file mode 100644 index 0000000000..8c5b331876 Binary files /dev/null and b/src/gui/styles/images/fileinfo-128.png differ diff --git a/src/gui/styles/images/fileinfo-16.png b/src/gui/styles/images/fileinfo-16.png new file mode 100644 index 0000000000..729be4d5f2 Binary files /dev/null and b/src/gui/styles/images/fileinfo-16.png differ diff --git a/src/gui/styles/images/fileinfo-32.png b/src/gui/styles/images/fileinfo-32.png new file mode 100644 index 0000000000..ca795aa49b Binary files /dev/null and b/src/gui/styles/images/fileinfo-32.png differ diff --git a/src/gui/styles/images/filelink-128.png b/src/gui/styles/images/filelink-128.png new file mode 100644 index 0000000000..be86a82901 Binary files /dev/null and b/src/gui/styles/images/filelink-128.png differ diff --git a/src/gui/styles/images/filelink-16.png b/src/gui/styles/images/filelink-16.png new file mode 100644 index 0000000000..6643f2c428 Binary files /dev/null and b/src/gui/styles/images/filelink-16.png differ diff --git a/src/gui/styles/images/filelink-32.png b/src/gui/styles/images/filelink-32.png new file mode 100644 index 0000000000..1e46fdc13c Binary files /dev/null and b/src/gui/styles/images/filelink-32.png differ diff --git a/src/gui/styles/images/floppy-128.png b/src/gui/styles/images/floppy-128.png new file mode 100644 index 0000000000..fa7a3e1334 Binary files /dev/null and b/src/gui/styles/images/floppy-128.png differ diff --git a/src/gui/styles/images/floppy-16.png b/src/gui/styles/images/floppy-16.png new file mode 100644 index 0000000000..91c59c567d Binary files /dev/null and b/src/gui/styles/images/floppy-16.png differ diff --git a/src/gui/styles/images/floppy-32.png b/src/gui/styles/images/floppy-32.png new file mode 100644 index 0000000000..e63b3213bf Binary files /dev/null and b/src/gui/styles/images/floppy-32.png differ diff --git a/src/gui/styles/images/fontbitmap-16.png b/src/gui/styles/images/fontbitmap-16.png new file mode 100644 index 0000000000..03efc9cbab Binary files /dev/null and b/src/gui/styles/images/fontbitmap-16.png differ diff --git a/src/gui/styles/images/fonttruetype-16.png b/src/gui/styles/images/fonttruetype-16.png new file mode 100644 index 0000000000..25205021e9 Binary files /dev/null and b/src/gui/styles/images/fonttruetype-16.png differ diff --git a/src/gui/styles/images/harddrive-128.png b/src/gui/styles/images/harddrive-128.png new file mode 100644 index 0000000000..0b73d9de1e Binary files /dev/null and b/src/gui/styles/images/harddrive-128.png differ diff --git a/src/gui/styles/images/harddrive-16.png b/src/gui/styles/images/harddrive-16.png new file mode 100644 index 0000000000..45d592baa3 Binary files /dev/null and b/src/gui/styles/images/harddrive-16.png differ diff --git a/src/gui/styles/images/harddrive-32.png b/src/gui/styles/images/harddrive-32.png new file mode 100644 index 0000000000..7041452b68 Binary files /dev/null and b/src/gui/styles/images/harddrive-32.png differ diff --git a/src/gui/styles/images/left-128.png b/src/gui/styles/images/left-128.png new file mode 100644 index 0000000000..a26a5195f8 Binary files /dev/null and b/src/gui/styles/images/left-128.png differ diff --git a/src/gui/styles/images/left-16.png b/src/gui/styles/images/left-16.png new file mode 100644 index 0000000000..110dd90f2d Binary files /dev/null and b/src/gui/styles/images/left-16.png differ diff --git a/src/gui/styles/images/left-32.png b/src/gui/styles/images/left-32.png new file mode 100644 index 0000000000..ec4107b372 Binary files /dev/null and b/src/gui/styles/images/left-32.png differ diff --git a/src/gui/styles/images/media-pause-16.png b/src/gui/styles/images/media-pause-16.png new file mode 100644 index 0000000000..6cb1fd7f63 Binary files /dev/null and b/src/gui/styles/images/media-pause-16.png differ diff --git a/src/gui/styles/images/media-pause-32.png b/src/gui/styles/images/media-pause-32.png new file mode 100644 index 0000000000..3f172a04d6 Binary files /dev/null and b/src/gui/styles/images/media-pause-32.png differ diff --git a/src/gui/styles/images/media-play-16.png b/src/gui/styles/images/media-play-16.png new file mode 100644 index 0000000000..d7ee3ccbe3 Binary files /dev/null and b/src/gui/styles/images/media-play-16.png differ diff --git a/src/gui/styles/images/media-play-32.png b/src/gui/styles/images/media-play-32.png new file mode 100644 index 0000000000..af8d2f7ba5 Binary files /dev/null and b/src/gui/styles/images/media-play-32.png differ diff --git a/src/gui/styles/images/media-seek-backward-16.png b/src/gui/styles/images/media-seek-backward-16.png new file mode 100644 index 0000000000..b8a8ea42d1 Binary files /dev/null and b/src/gui/styles/images/media-seek-backward-16.png differ diff --git a/src/gui/styles/images/media-seek-backward-32.png b/src/gui/styles/images/media-seek-backward-32.png new file mode 100644 index 0000000000..a21d1372fe Binary files /dev/null and b/src/gui/styles/images/media-seek-backward-32.png differ diff --git a/src/gui/styles/images/media-seek-forward-16.png b/src/gui/styles/images/media-seek-forward-16.png new file mode 100644 index 0000000000..3c900dcb62 Binary files /dev/null and b/src/gui/styles/images/media-seek-forward-16.png differ diff --git a/src/gui/styles/images/media-seek-forward-32.png b/src/gui/styles/images/media-seek-forward-32.png new file mode 100644 index 0000000000..4f8d370fa1 Binary files /dev/null and b/src/gui/styles/images/media-seek-forward-32.png differ diff --git a/src/gui/styles/images/media-skip-backward-16.png b/src/gui/styles/images/media-skip-backward-16.png new file mode 100644 index 0000000000..f5b3f4f56d Binary files /dev/null and b/src/gui/styles/images/media-skip-backward-16.png differ diff --git a/src/gui/styles/images/media-skip-backward-32.png b/src/gui/styles/images/media-skip-backward-32.png new file mode 100644 index 0000000000..1d338035ef Binary files /dev/null and b/src/gui/styles/images/media-skip-backward-32.png differ diff --git a/src/gui/styles/images/media-skip-forward-16.png b/src/gui/styles/images/media-skip-forward-16.png new file mode 100644 index 0000000000..27e205b02f Binary files /dev/null and b/src/gui/styles/images/media-skip-forward-16.png differ diff --git a/src/gui/styles/images/media-skip-forward-32.png b/src/gui/styles/images/media-skip-forward-32.png new file mode 100644 index 0000000000..a583fa1b11 Binary files /dev/null and b/src/gui/styles/images/media-skip-forward-32.png differ diff --git a/src/gui/styles/images/media-stop-16.png b/src/gui/styles/images/media-stop-16.png new file mode 100644 index 0000000000..9ce035d696 Binary files /dev/null and b/src/gui/styles/images/media-stop-16.png differ diff --git a/src/gui/styles/images/media-stop-32.png b/src/gui/styles/images/media-stop-32.png new file mode 100644 index 0000000000..aae24ba925 Binary files /dev/null and b/src/gui/styles/images/media-stop-32.png differ diff --git a/src/gui/styles/images/media-volume-16.png b/src/gui/styles/images/media-volume-16.png new file mode 100644 index 0000000000..ad258340f2 Binary files /dev/null and b/src/gui/styles/images/media-volume-16.png differ diff --git a/src/gui/styles/images/media-volume-muted-16.png b/src/gui/styles/images/media-volume-muted-16.png new file mode 100644 index 0000000000..06bded21e7 Binary files /dev/null and b/src/gui/styles/images/media-volume-muted-16.png differ diff --git a/src/gui/styles/images/networkdrive-128.png b/src/gui/styles/images/networkdrive-128.png new file mode 100644 index 0000000000..fd4a59c6bd Binary files /dev/null and b/src/gui/styles/images/networkdrive-128.png differ diff --git a/src/gui/styles/images/networkdrive-16.png b/src/gui/styles/images/networkdrive-16.png new file mode 100644 index 0000000000..1bc62f766a Binary files /dev/null and b/src/gui/styles/images/networkdrive-16.png differ diff --git a/src/gui/styles/images/networkdrive-32.png b/src/gui/styles/images/networkdrive-32.png new file mode 100644 index 0000000000..6a389dcae4 Binary files /dev/null and b/src/gui/styles/images/networkdrive-32.png differ diff --git a/src/gui/styles/images/newdirectory-128.png b/src/gui/styles/images/newdirectory-128.png new file mode 100644 index 0000000000..fdbee27688 Binary files /dev/null and b/src/gui/styles/images/newdirectory-128.png differ diff --git a/src/gui/styles/images/newdirectory-16.png b/src/gui/styles/images/newdirectory-16.png new file mode 100644 index 0000000000..6c9f80318b Binary files /dev/null and b/src/gui/styles/images/newdirectory-16.png differ diff --git a/src/gui/styles/images/newdirectory-32.png b/src/gui/styles/images/newdirectory-32.png new file mode 100644 index 0000000000..4fd0329216 Binary files /dev/null and b/src/gui/styles/images/newdirectory-32.png differ diff --git a/src/gui/styles/images/parentdir-128.png b/src/gui/styles/images/parentdir-128.png new file mode 100644 index 0000000000..84d14ab079 Binary files /dev/null and b/src/gui/styles/images/parentdir-128.png differ diff --git a/src/gui/styles/images/parentdir-16.png b/src/gui/styles/images/parentdir-16.png new file mode 100644 index 0000000000..665f8280f2 Binary files /dev/null and b/src/gui/styles/images/parentdir-16.png differ diff --git a/src/gui/styles/images/parentdir-32.png b/src/gui/styles/images/parentdir-32.png new file mode 100644 index 0000000000..44f3c4876d Binary files /dev/null and b/src/gui/styles/images/parentdir-32.png differ diff --git a/src/gui/styles/images/refresh-24.png b/src/gui/styles/images/refresh-24.png new file mode 100644 index 0000000000..4c9b72c489 Binary files /dev/null and b/src/gui/styles/images/refresh-24.png differ diff --git a/src/gui/styles/images/refresh-32.png b/src/gui/styles/images/refresh-32.png new file mode 100644 index 0000000000..eecde4b8f9 Binary files /dev/null and b/src/gui/styles/images/refresh-32.png differ diff --git a/src/gui/styles/images/right-128.png b/src/gui/styles/images/right-128.png new file mode 100644 index 0000000000..14b1cfd8eb Binary files /dev/null and b/src/gui/styles/images/right-128.png differ diff --git a/src/gui/styles/images/right-16.png b/src/gui/styles/images/right-16.png new file mode 100644 index 0000000000..81ca628ff6 Binary files /dev/null and b/src/gui/styles/images/right-16.png differ diff --git a/src/gui/styles/images/right-32.png b/src/gui/styles/images/right-32.png new file mode 100644 index 0000000000..0f6ba8608b Binary files /dev/null and b/src/gui/styles/images/right-32.png differ diff --git a/src/gui/styles/images/standardbutton-apply-128.png b/src/gui/styles/images/standardbutton-apply-128.png new file mode 100644 index 0000000000..85f07a57ef Binary files /dev/null and b/src/gui/styles/images/standardbutton-apply-128.png differ diff --git a/src/gui/styles/images/standardbutton-apply-16.png b/src/gui/styles/images/standardbutton-apply-16.png new file mode 100644 index 0000000000..8f11ce6504 Binary files /dev/null and b/src/gui/styles/images/standardbutton-apply-16.png differ diff --git a/src/gui/styles/images/standardbutton-apply-32.png b/src/gui/styles/images/standardbutton-apply-32.png new file mode 100644 index 0000000000..e8f7853a1e Binary files /dev/null and b/src/gui/styles/images/standardbutton-apply-32.png differ diff --git a/src/gui/styles/images/standardbutton-cancel-128.png b/src/gui/styles/images/standardbutton-cancel-128.png new file mode 100644 index 0000000000..16d857030f Binary files /dev/null and b/src/gui/styles/images/standardbutton-cancel-128.png differ diff --git a/src/gui/styles/images/standardbutton-cancel-16.png b/src/gui/styles/images/standardbutton-cancel-16.png new file mode 100644 index 0000000000..7bd25bd7c7 Binary files /dev/null and b/src/gui/styles/images/standardbutton-cancel-16.png differ diff --git a/src/gui/styles/images/standardbutton-cancel-32.png b/src/gui/styles/images/standardbutton-cancel-32.png new file mode 100644 index 0000000000..64a78727a1 Binary files /dev/null and b/src/gui/styles/images/standardbutton-cancel-32.png differ diff --git a/src/gui/styles/images/standardbutton-clear-128.png b/src/gui/styles/images/standardbutton-clear-128.png new file mode 100644 index 0000000000..107aea2234 Binary files /dev/null and b/src/gui/styles/images/standardbutton-clear-128.png differ diff --git a/src/gui/styles/images/standardbutton-clear-16.png b/src/gui/styles/images/standardbutton-clear-16.png new file mode 100644 index 0000000000..5359134c72 Binary files /dev/null and b/src/gui/styles/images/standardbutton-clear-16.png differ diff --git a/src/gui/styles/images/standardbutton-clear-32.png b/src/gui/styles/images/standardbutton-clear-32.png new file mode 100644 index 0000000000..8b85d6b7b3 Binary files /dev/null and b/src/gui/styles/images/standardbutton-clear-32.png differ diff --git a/src/gui/styles/images/standardbutton-close-128.png b/src/gui/styles/images/standardbutton-close-128.png new file mode 100644 index 0000000000..571aeae2bd Binary files /dev/null and b/src/gui/styles/images/standardbutton-close-128.png differ diff --git a/src/gui/styles/images/standardbutton-close-16.png b/src/gui/styles/images/standardbutton-close-16.png new file mode 100644 index 0000000000..e9e481987a Binary files /dev/null and b/src/gui/styles/images/standardbutton-close-16.png differ diff --git a/src/gui/styles/images/standardbutton-close-32.png b/src/gui/styles/images/standardbutton-close-32.png new file mode 100644 index 0000000000..47e5733062 Binary files /dev/null and b/src/gui/styles/images/standardbutton-close-32.png differ diff --git a/src/gui/styles/images/standardbutton-closetab-16.png b/src/gui/styles/images/standardbutton-closetab-16.png new file mode 100644 index 0000000000..540694eae3 Binary files /dev/null and b/src/gui/styles/images/standardbutton-closetab-16.png differ diff --git a/src/gui/styles/images/standardbutton-closetab-down-16.png b/src/gui/styles/images/standardbutton-closetab-down-16.png new file mode 100644 index 0000000000..ccec241652 Binary files /dev/null and b/src/gui/styles/images/standardbutton-closetab-down-16.png differ diff --git a/src/gui/styles/images/standardbutton-closetab-hover-16.png b/src/gui/styles/images/standardbutton-closetab-hover-16.png new file mode 100644 index 0000000000..b22a0ffaf0 Binary files /dev/null and b/src/gui/styles/images/standardbutton-closetab-hover-16.png differ diff --git a/src/gui/styles/images/standardbutton-delete-128.png b/src/gui/styles/images/standardbutton-delete-128.png new file mode 100644 index 0000000000..11947ba681 Binary files /dev/null and b/src/gui/styles/images/standardbutton-delete-128.png differ diff --git a/src/gui/styles/images/standardbutton-delete-16.png b/src/gui/styles/images/standardbutton-delete-16.png new file mode 100644 index 0000000000..63fe93fe98 Binary files /dev/null and b/src/gui/styles/images/standardbutton-delete-16.png differ diff --git a/src/gui/styles/images/standardbutton-delete-32.png b/src/gui/styles/images/standardbutton-delete-32.png new file mode 100644 index 0000000000..336d965d1c Binary files /dev/null and b/src/gui/styles/images/standardbutton-delete-32.png differ diff --git a/src/gui/styles/images/standardbutton-help-128.png b/src/gui/styles/images/standardbutton-help-128.png new file mode 100644 index 0000000000..aa38e6fdfb Binary files /dev/null and b/src/gui/styles/images/standardbutton-help-128.png differ diff --git a/src/gui/styles/images/standardbutton-help-16.png b/src/gui/styles/images/standardbutton-help-16.png new file mode 100644 index 0000000000..e8299418da Binary files /dev/null and b/src/gui/styles/images/standardbutton-help-16.png differ diff --git a/src/gui/styles/images/standardbutton-help-32.png b/src/gui/styles/images/standardbutton-help-32.png new file mode 100644 index 0000000000..310056a632 Binary files /dev/null and b/src/gui/styles/images/standardbutton-help-32.png differ diff --git a/src/gui/styles/images/standardbutton-no-128.png b/src/gui/styles/images/standardbutton-no-128.png new file mode 100644 index 0000000000..491c048ebd Binary files /dev/null and b/src/gui/styles/images/standardbutton-no-128.png differ diff --git a/src/gui/styles/images/standardbutton-no-16.png b/src/gui/styles/images/standardbutton-no-16.png new file mode 100644 index 0000000000..812d3f57dd Binary files /dev/null and b/src/gui/styles/images/standardbutton-no-16.png differ diff --git a/src/gui/styles/images/standardbutton-no-32.png b/src/gui/styles/images/standardbutton-no-32.png new file mode 100644 index 0000000000..9548d59196 Binary files /dev/null and b/src/gui/styles/images/standardbutton-no-32.png differ diff --git a/src/gui/styles/images/standardbutton-ok-128.png b/src/gui/styles/images/standardbutton-ok-128.png new file mode 100644 index 0000000000..63cc5279ae Binary files /dev/null and b/src/gui/styles/images/standardbutton-ok-128.png differ diff --git a/src/gui/styles/images/standardbutton-ok-16.png b/src/gui/styles/images/standardbutton-ok-16.png new file mode 100644 index 0000000000..fb4b4dbf96 Binary files /dev/null and b/src/gui/styles/images/standardbutton-ok-16.png differ diff --git a/src/gui/styles/images/standardbutton-ok-32.png b/src/gui/styles/images/standardbutton-ok-32.png new file mode 100644 index 0000000000..2dadd7a690 Binary files /dev/null and b/src/gui/styles/images/standardbutton-ok-32.png differ diff --git a/src/gui/styles/images/standardbutton-open-128.png b/src/gui/styles/images/standardbutton-open-128.png new file mode 100644 index 0000000000..8a052e829d Binary files /dev/null and b/src/gui/styles/images/standardbutton-open-128.png differ diff --git a/src/gui/styles/images/standardbutton-open-16.png b/src/gui/styles/images/standardbutton-open-16.png new file mode 100644 index 0000000000..08cdc2b91f Binary files /dev/null and b/src/gui/styles/images/standardbutton-open-16.png differ diff --git a/src/gui/styles/images/standardbutton-open-32.png b/src/gui/styles/images/standardbutton-open-32.png new file mode 100644 index 0000000000..db33c79852 Binary files /dev/null and b/src/gui/styles/images/standardbutton-open-32.png differ diff --git a/src/gui/styles/images/standardbutton-save-128.png b/src/gui/styles/images/standardbutton-save-128.png new file mode 100644 index 0000000000..fc6fd7ce1d Binary files /dev/null and b/src/gui/styles/images/standardbutton-save-128.png differ diff --git a/src/gui/styles/images/standardbutton-save-16.png b/src/gui/styles/images/standardbutton-save-16.png new file mode 100644 index 0000000000..dd4e228280 Binary files /dev/null and b/src/gui/styles/images/standardbutton-save-16.png differ diff --git a/src/gui/styles/images/standardbutton-save-32.png b/src/gui/styles/images/standardbutton-save-32.png new file mode 100644 index 0000000000..177678c963 Binary files /dev/null and b/src/gui/styles/images/standardbutton-save-32.png differ diff --git a/src/gui/styles/images/standardbutton-yes-128.png b/src/gui/styles/images/standardbutton-yes-128.png new file mode 100644 index 0000000000..79c8296016 Binary files /dev/null and b/src/gui/styles/images/standardbutton-yes-128.png differ diff --git a/src/gui/styles/images/standardbutton-yes-16.png b/src/gui/styles/images/standardbutton-yes-16.png new file mode 100644 index 0000000000..cc16dbbec3 Binary files /dev/null and b/src/gui/styles/images/standardbutton-yes-16.png differ diff --git a/src/gui/styles/images/standardbutton-yes-32.png b/src/gui/styles/images/standardbutton-yes-32.png new file mode 100644 index 0000000000..e3340c6453 Binary files /dev/null and b/src/gui/styles/images/standardbutton-yes-32.png differ diff --git a/src/gui/styles/images/stop-24.png b/src/gui/styles/images/stop-24.png new file mode 100644 index 0000000000..99856e9640 Binary files /dev/null and b/src/gui/styles/images/stop-24.png differ diff --git a/src/gui/styles/images/stop-32.png b/src/gui/styles/images/stop-32.png new file mode 100644 index 0000000000..4f4952bb2a Binary files /dev/null and b/src/gui/styles/images/stop-32.png differ diff --git a/src/gui/styles/images/trash-128.png b/src/gui/styles/images/trash-128.png new file mode 100644 index 0000000000..334fe5b6f3 Binary files /dev/null and b/src/gui/styles/images/trash-128.png differ diff --git a/src/gui/styles/images/trash-16.png b/src/gui/styles/images/trash-16.png new file mode 100644 index 0000000000..c471791ee8 Binary files /dev/null and b/src/gui/styles/images/trash-16.png differ diff --git a/src/gui/styles/images/trash-32.png b/src/gui/styles/images/trash-32.png new file mode 100644 index 0000000000..68625cf698 Binary files /dev/null and b/src/gui/styles/images/trash-32.png differ diff --git a/src/gui/styles/images/up-128.png b/src/gui/styles/images/up-128.png new file mode 100644 index 0000000000..c10df10677 Binary files /dev/null and b/src/gui/styles/images/up-128.png differ diff --git a/src/gui/styles/images/up-16.png b/src/gui/styles/images/up-16.png new file mode 100644 index 0000000000..33e939db8f Binary files /dev/null and b/src/gui/styles/images/up-16.png differ diff --git a/src/gui/styles/images/up-32.png b/src/gui/styles/images/up-32.png new file mode 100644 index 0000000000..d7157c9476 Binary files /dev/null and b/src/gui/styles/images/up-32.png differ diff --git a/src/gui/styles/images/viewdetailed-128.png b/src/gui/styles/images/viewdetailed-128.png new file mode 100644 index 0000000000..363937a857 Binary files /dev/null and b/src/gui/styles/images/viewdetailed-128.png differ diff --git a/src/gui/styles/images/viewdetailed-16.png b/src/gui/styles/images/viewdetailed-16.png new file mode 100644 index 0000000000..44a14b923a Binary files /dev/null and b/src/gui/styles/images/viewdetailed-16.png differ diff --git a/src/gui/styles/images/viewdetailed-32.png b/src/gui/styles/images/viewdetailed-32.png new file mode 100644 index 0000000000..fac1a3e683 Binary files /dev/null and b/src/gui/styles/images/viewdetailed-32.png differ diff --git a/src/gui/styles/images/viewlist-128.png b/src/gui/styles/images/viewlist-128.png new file mode 100644 index 0000000000..cc301059c1 Binary files /dev/null and b/src/gui/styles/images/viewlist-128.png differ diff --git a/src/gui/styles/images/viewlist-16.png b/src/gui/styles/images/viewlist-16.png new file mode 100644 index 0000000000..9132877ff6 Binary files /dev/null and b/src/gui/styles/images/viewlist-16.png differ diff --git a/src/gui/styles/images/viewlist-32.png b/src/gui/styles/images/viewlist-32.png new file mode 100644 index 0000000000..fae3c24536 Binary files /dev/null and b/src/gui/styles/images/viewlist-32.png differ diff --git a/src/gui/styles/qcdestyle.cpp b/src/gui/styles/qcdestyle.cpp new file mode 100644 index 0000000000..5acf399396 --- /dev/null +++ b/src/gui/styles/qcdestyle.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcdestyle.h" + +#if !defined(QT_NO_STYLE_CDE) || defined(QT_PLUGIN) + +#include "qmenu.h" +#include "qapplication.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qpixmap.h" +#include "qpalette.h" +#include "qwidget.h" +#include "qpushbutton.h" +#include "qscrollbar.h" +#include "qtabbar.h" +#include "qtabwidget.h" +#include "qlistview.h" +#include "qsplitter.h" +#include "qslider.h" +#include "qcombobox.h" +#include "qlineedit.h" +#include "qprogressbar.h" +#include "qimage.h" +#include "qfocusframe.h" +#include "qpainterpath.h" +#include "qdebug.h" +#include <limits.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QCDEStyle + \brief The QCDEStyle class provides a CDE look and feel. + + \ingroup appearance + + This style provides a slightly improved Motif look similar to some + versions of the Common Desktop Environment (CDE). The main + differences are thinner frames and more modern radio buttons and + checkboxes. Together with a dark background and a bright + text/foreground color, the style looks quite attractive (at least + for Motif fans). + + Note that most of the functions provided by QCDEStyle are + reimplementations of QStyle functions; see QStyle for their + documentation. QCDEStyle provides overloads for drawControl() and + drawPrimitive() which are documented here. + + \img qcdestyle.png + \sa QWindowsXPStyle, QMacStyle, QWindowsStyle, QPlastiqueStyle, QMotifStyle +*/ + +/*! + Constructs a QCDEStyle. + + If \a useHighlightCols is false (the default), then the style will + polish the application's color palette to emulate the Motif way of + highlighting, which is a simple inversion between the base and the + text color. +*/ +QCDEStyle::QCDEStyle(bool useHighlightCols) + : QMotifStyle(useHighlightCols) +{ +} + +/*! + Destroys the style. +*/ +QCDEStyle::~QCDEStyle() +{ +} + + +/*!\reimp +*/ +int QCDEStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, + const QWidget *widget) const +/* +int QCDEStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, + const QWidget *widget) const + */ +{ + int ret = 0; + + switch(metric) { + case PM_MenuBarPanelWidth: + case PM_DefaultFrameWidth: + case PM_FocusFrameVMargin: + case PM_FocusFrameHMargin: + case PM_MenuPanelWidth: + case PM_SpinBoxFrameWidth: + case PM_MenuBarVMargin: + case PM_MenuBarHMargin: + case PM_DockWidgetFrameWidth: + ret = 1; + break; + case PM_ScrollBarExtent: + ret = 13; + break; + default: + ret = QMotifStyle::pixelMetric(metric, option, widget); + break; + } + return ret; +} + +/*! + \reimp +*/ +void QCDEStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *widget) const +{ + + switch(element) { + case CE_MenuBarItem: { + if (opt->state & State_Selected) // active item + qDrawShadePanel(p, opt->rect, opt->palette, true, 1, + &opt->palette.brush(QPalette::Button)); + else // other item + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + QCommonStyle::drawControl(element, opt, p, widget); + break; } + case CE_RubberBand: { + p->save(); + p->setClipping(false); + QPainterPath path; + path.addRect(opt->rect); + path.addRect(opt->rect.adjusted(2, 2, -2, -2)); + p->fillPath(path, opt->palette.color(QPalette::Active, QPalette::Text)); + p->restore(); + break; } + default: + QMotifStyle::drawControl(element, opt, p, widget); + break; + } +} + +/*! + \reimp +*/ +void QCDEStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *widget) const +{ + switch(pe) { + case PE_IndicatorCheckBox: { + bool down = opt->state & State_Sunken; + bool on = opt->state & State_On; + bool showUp = !(down ^ on); + QBrush fill = (showUp || (opt->state & State_NoChange)) ? opt->palette.brush(QPalette::Button) : opt->palette.brush(QPalette::Mid); + qDrawShadePanel(p, opt->rect, opt->palette, !showUp, pixelMetric(PM_DefaultFrameWidth), &opt->palette.brush(QPalette::Button)); + + if (on || (opt->state & State_NoChange)) { + QRect r = opt->rect; + QPolygon a(7 * 2); + int i, xx, yy; + xx = r.x() + 3; + yy = r.y() + 5; + if (opt->rect.width() <= 9) { + // When called from CE_MenuItem in QMotifStyle + xx -= 2; + yy -= 2; + } + + for (i = 0; i < 3; i++) { + a.setPoint(2 * i, xx, yy); + a.setPoint(2 * i + 1, xx, yy + 2); + xx++; yy++; + } + yy -= 2; + for (i = 3; i < 7; i++) { + a.setPoint(2 * i, xx, yy); + a.setPoint(2 * i + 1, xx, yy + 2); + xx++; yy--; + } + if (opt->state & State_NoChange) + p->setPen(opt->palette.dark().color()); + else + p->setPen(opt->palette.foreground().color()); + p->drawPolyline(a); + } + if (!(opt->state & State_Enabled) && styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + } break; + case PE_IndicatorRadioButton: + { + QRect r = opt->rect; +#define INTARRLEN(x) sizeof(x)/(sizeof(int)*2) + static const int pts1[] = { // up left lines + 1,9, 1,8, 0,7, 0,4, 1,3, 1,2, 2,1, 3,1, 4,0, 7,0, 8,1, 9,1 }; + static const int pts4[] = { // bottom right lines + 2,10, 3,10, 4,11, 7,11, 8,10, 9,10, 10,9, 10,8, 11,7, + 11,4, 10,3, 10,2 }; + static const int pts5[] = { // inner fill + 4,2, 7,2, 9,4, 9,7, 7,9, 4,9, 2,7, 2,4 }; + bool down = opt->state & State_Sunken; + bool on = opt->state & State_On; + QPolygon a(INTARRLEN(pts1), pts1); + + //center when rect is larger than indicator size + int xOffset = 0; + int yOffset = 0; + int indicatorWidth = pixelMetric(PM_ExclusiveIndicatorWidth); + int indicatorHeight = pixelMetric(PM_ExclusiveIndicatorWidth); + if (r.width() > indicatorWidth) + xOffset += (r.width() - indicatorWidth)/2; + if (r.height() > indicatorHeight) + yOffset += (r.height() - indicatorHeight)/2; + p->translate(xOffset, yOffset); + + a.translate(r.x(), r.y()); + QPen oldPen = p->pen(); + QBrush oldBrush = p->brush(); + p->setPen((down || on) ? opt->palette.dark().color() : opt->palette.light().color()); + p->drawPolyline(a); + a.setPoints(INTARRLEN(pts4), pts4); + a.translate(r.x(), r.y()); + p->setPen((down || on) ? opt->palette.light().color() : opt->palette.dark().color()); + p->drawPolyline(a); + a.setPoints(INTARRLEN(pts5), pts5); + a.translate(r.x(), r.y()); + QColor fillColor = on ? opt->palette.dark().color() : opt->palette.background().color(); + p->setPen(fillColor); + p->setBrush(on ? opt->palette.brush(QPalette::Dark) : + opt->palette.brush(QPalette::Window)); + p->drawPolygon(a); + if (!(opt->state & State_Enabled) && styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + p->setPen(oldPen); + p->setBrush(oldBrush); + + p->translate(-xOffset, -yOffset); + + } break; + default: + QMotifStyle::drawPrimitive(pe, opt, p, widget); + } +} + +/*!\reimp*/ +QPalette QCDEStyle::standardPalette() const +{ + QColor background(0xb6, 0xb6, 0xcf); + QColor light = background.lighter(); + QColor mid = background.darker(150); + QColor dark = background.darker(); + QPalette palette(Qt::black, background, light, dark, mid, Qt::black, Qt::white); + palette.setBrush(QPalette::Disabled, QPalette::WindowText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Text, dark); + palette.setBrush(QPalette::Disabled, QPalette::ButtonText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Base, background); + return palette; +} + +/*! + \internal +*/ +QIcon QCDEStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt, + const QWidget *widget) const +{ + return QMotifStyle::standardIconImplementation(standardIcon, opt, widget); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/styles/qcdestyle.h b/src/gui/styles/qcdestyle.h new file mode 100644 index 0000000000..ca43b6a530 --- /dev/null +++ b/src/gui/styles/qcdestyle.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QCDESTYLE_H +#define QCDESTYLE_H + +#include <QtGui/qmotifstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_CDE) + +class Q_GUI_EXPORT QCDEStyle : public QMotifStyle +{ + Q_OBJECT +public: + explicit QCDEStyle(bool useHighlightCols = false); + virtual ~QCDEStyle(); + + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QPalette standardPalette() const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; +}; + +#endif // QT_NO_STYLE_CDE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCDESTYLE_H diff --git a/src/gui/styles/qcleanlooksstyle.cpp b/src/gui/styles/qcleanlooksstyle.cpp new file mode 100644 index 0000000000..cc5fe10692 --- /dev/null +++ b/src/gui/styles/qcleanlooksstyle.cpp @@ -0,0 +1,4466 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcleanlooksstyle.h" +#include "qcleanlooksstyle_p.h" + +#if !defined(QT_NO_STYLE_CLEANLOOKS) || defined(QT_PLUGIN) + +#include "qwindowsstyle_p.h" +#include <qcombobox.h> +#include <qpushbutton.h> +#include <qpainter.h> +#include <qdir.h> +#include <qhash.h> +#include <qstyleoption.h> +#include <qapplication.h> +#include <qmainwindow.h> +#include <qfont.h> +#include <qgroupbox.h> +#include <qprocess.h> +#include <qpixmapcache.h> +#include <qdialogbuttonbox.h> +#include <qscrollbar.h> +#include <qspinbox.h> +#include <qslider.h> +#include <qsplitter.h> +#include <qprogressbar.h> +#include <qtoolbar.h> +#include <qwizard.h> +#include <qlibrary.h> +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +using namespace QStyleHelper; + +enum Direction { + TopDown, + FromLeft, + BottomUp, + FromRight +}; + +// from windows style +static const int windowsItemFrame = 2; // menu item frame width +static const int windowsItemHMargin = 3; // menu item hor text margin +static const int windowsItemVMargin = 8; // menu item ver text margin +static const int windowsRightBorder = 15; // right border on windows + +/* XPM */ +static const char * const dock_widget_close_xpm[] = { + "11 13 7 1", + " c None", + ". c #D5CFCB", + "+ c #8F8B88", + "@ c #6C6A67", + "# c #ABA6A3", + "$ c #B5B0AC", + "% c #A4A09D", + " ", + " +@@@@@@@+ ", + "+# #+", + "@ $@ @$ @", + "@ @@@ @@@ @", + "@ @@@@@ @", + "@ @@@ @", + "@ @@@@@ @", + "@ @@@ @@@ @", + "@ $@ @$ @", + "+% #+", + " +@@@@@@@+ ", + " "}; + +static const char * const qt_cleanlooks_arrow_down_xpm[] = { + "11 7 2 1", + " c None", + "x c #000000", + " ", + " x x ", + " xxx xxx ", + " xxxxxxx ", + " xxxxx ", + " xxx ", + " x "}; + +static const char * const qt_cleanlooks_arrow_up_xpm[] = { + "11 7 2 1", + " c None", + "x c #000000", + " x ", + " xxx ", + " xxxxx ", + " xxxxxxx ", + " xxx xxx ", + " x x ", + " "}; + +static const char * const dock_widget_restore_xpm[] = { + "11 13 7 1", + " c None", + ". c #D5CFCB", + "+ c #8F8B88", + "@ c #6C6A67", + "# c #ABA6A3", + "$ c #B5B0AC", + "% c #A4A09D", + " ", + " +@@@@@@@+ ", + "+# #+", + "@ #@@@# @", + "@ @ @ @", + "@ #@@@# @ @", + "@ @ @ @ @", + "@ @ @@@ @", + "@ @ @ @", + "@ #@@@# @", + "+% #+", + " +@@@@@@@+ ", + " "}; + +static const char * const workspace_minimize[] = { + "11 13 7 1", + " c None", + ". c #D5CFCB", + "+ c #8F8B88", + "@ c #6C6A67", + "# c #ABA6A3", + "$ c #B5B0AC", + "% c #A4A09D", + " ", + " +@@@@@@@+ ", + "+# #+", + "@ @", + "@ @", + "@ @", + "@ @@@@@@@ @", + "@ @@@@@@@ @", + "@ @", + "@ @", + "+% #+", + " +@@@@@@@+ ", + " "}; + + +static const char * const qt_titlebar_context_help[] = { + "10 10 3 1", + " c None", + "# c #000000", + "+ c #444444", + " +####+ ", + " ### ### ", + " ## ## ", + " +##+ ", + " +## ", + " ## ", + " ## ", + " ", + " ## ", + " ## "}; + +static const char * const qt_cleanlooks_radiobutton[] = { + "13 13 9 1", + " c None", + ". c #ABA094", + "+ c #B7ADA0", + "@ c #C4BBB2", + "# c #DDD4CD", + "$ c #E7E1E0", + "% c #F4EFED", + "& c #FFFAF9", + "* c #FCFEFB", + " #@...@# ", + " @+@#$$#+@ ", + " @+$%%***&@@ ", + "#+$%**&&**&+#", + "@@$&&******#@", + ".#**********.", + ".$&******&*&.", + ".$*&******&*.", + "+#********&#@", + "#+*********+#", + " @@*******@@ ", + " @+#%*%#+@ ", + " #@...+# "}; + +static const char * const qt_cleanlooks_radiobutton_checked[] = { + "13 13 20 1", + " c None", + ". c #A8ABAE", + "+ c #596066", + "@ c #283138", + "# c #A9ACAF", + "$ c #A6A9AB", + "% c #6B7378", + "& c #8C9296", + "* c #A2A6AA", + "= c #61696F", + "- c #596065", + "; c #93989C", + "> c #777E83", + ", c #60686E", + "' c #252D33", + ") c #535B62", + "! c #21292E", + "~ c #242B31", + "{ c #1F262B", + "] c #41484E", + " ", + " ", + " ", + " .+@+# ", + " $%&*&=# ", + " -&;>,'+ ", + " @*>,)!@ ", + " +&,)~{+ ", + " #='!{]# ", + " #+@+# ", + " ", + " ", + " "}; + + +static const char * const qt_scrollbar_button_arrow_left[] = { + "4 7 2 1", + " c None", + "* c #BFBFBF", + " *", + " **", + " ***", + "****", + " ***", + " **", + " *"}; + +static const char * const qt_scrollbar_button_arrow_right[] = { + "4 7 2 1", + " c None", + "* c #BFBFBF", + "* ", + "** ", + "*** ", + "****", + "*** ", + "** ", + "* "}; + +static const char * const qt_scrollbar_button_arrow_up[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + " * ", + " *** ", + " ***** ", + "*******"}; + +static const char * const qt_scrollbar_button_arrow_down[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + "*******", + " ***** ", + " *** ", + " * "}; + +static const char * const qt_spinbox_button_arrow_down[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + "*******", + " ***** ", + " *** ", + " * "}; + +static const char * const qt_spinbox_button_arrow_up[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + " * ", + " *** ", + " ***** ", + "*******"}; + +static const char * const qt_scrollbar_button_left[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + " .++++++++++++++", + ".+#############+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + ".+<<<<<<<<<<<<<+", + " .++++++++++++++"}; + +static const char * const qt_scrollbar_button_right[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + "++++++++++++++. ", + "+#############+.", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+<<<<<<<<<<<<<+.", + "++++++++++++++. "}; + +static const char * const qt_scrollbar_button_up[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + " .++++++++++++. ", + ".+############+.", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+<<<<<<<<<<<<<<+", + "++++++++++++++++"}; + +static const char * const qt_scrollbar_button_down[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + "++++++++++++++++", + "+##############+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + ".+<<<<<<<<<<<<+.", + " .++++++++++++. "}; + +static const char * const qt_cleanlooks_menuitem_checkbox_checked[] = { + "8 7 6 1", + " g None", + ". g #959595", + "+ g #676767", + "@ g #454545", + "# g #1D1D1D", + "0 g #101010", + " ..", + " .+ ", + " .+ ", + "0 .@ ", + "@#++. ", + " @# ", + " . "}; + +static const char * const qt_cleanlooks_checkbox_checked[] = { + "13 13 3 1", + " c None", + ". c #272D33", + "% c #666666", + + " ", + " % ", + " %. ", + " %.% ", + " %.. ", + " %.% %.. ", + " %..%..% ", + " %...% ", + " %..% ", + " %.% ", + " % ", + " ", + " "}; + +static void qt_cleanlooks_draw_gradient(QPainter *painter, const QRect &rect, const QColor &gradientStart, + const QColor &gradientStop, Direction direction = TopDown, QBrush bgBrush = QBrush()) +{ + int x = rect.center().x(); + int y = rect.center().y(); + QLinearGradient *gradient; + switch(direction) { + case FromLeft: + gradient = new QLinearGradient(rect.left(), y, rect.right(), y); + break; + case FromRight: + gradient = new QLinearGradient(rect.right(), y, rect.left(), y); + break; + case BottomUp: + gradient = new QLinearGradient(x, rect.bottom(), x, rect.top()); + break; + case TopDown: + default: + gradient = new QLinearGradient(x, rect.top(), x, rect.bottom()); + break; + } + if (bgBrush.gradient()) + gradient->setStops(bgBrush.gradient()->stops()); + else { + gradient->setColorAt(0, gradientStart); + gradient->setColorAt(1, gradientStop); + } + painter->fillRect(rect, *gradient); + delete gradient; +} + +static void qt_cleanlooks_draw_buttongradient(QPainter *painter, const QRect &rect, const QColor &gradientStart, + const QColor &gradientMid, const QColor &gradientStop, Direction direction = TopDown, + QBrush bgBrush = QBrush()) +{ + int x = rect.center().x(); + int y = rect.center().y(); + QLinearGradient *gradient; + bool horizontal = false; + switch(direction) { + case FromLeft: + horizontal = true; + gradient = new QLinearGradient(rect.left(), y, rect.right(), y); + break; + case FromRight: + horizontal = true; + gradient = new QLinearGradient(rect.right(), y, rect.left(), y); + break; + case BottomUp: + gradient = new QLinearGradient(x, rect.bottom(), x, rect.top()); + break; + case TopDown: + default: + gradient = new QLinearGradient(x, rect.top(), x, rect.bottom()); + break; + } + if (bgBrush.gradient()) + gradient->setStops(bgBrush.gradient()->stops()); + else { + int size = horizontal ? rect.width() : rect.height() ; + if (size > 4) { + float edge = 4.0/(float)size; + gradient->setColorAt(0, gradientStart); + gradient->setColorAt(edge, gradientMid.lighter(104)); + gradient->setColorAt(1.0 - edge, gradientMid.darker(100)); + gradient->setColorAt(1.0, gradientStop); + } + } + painter->fillRect(rect, *gradient); + delete gradient; +} + +static void qt_cleanlooks_draw_mdibutton(QPainter *painter, const QStyleOptionTitleBar *option, const QRect &tmp, bool hover, bool sunken) +{ + QColor dark; + dark.setHsv(option->palette.button().color().hue(), + qMin(255, (int)(option->palette.button().color().saturation()*1.9)), + qMin(255, (int)(option->palette.button().color().value()*0.7))); + + QColor highlight = option->palette.highlight().color(); + + bool active = (option->titleBarState & QStyle::State_Active); + QColor titleBarHighlight(255, 255, 255, 60); + + if (sunken) + painter->fillRect(tmp.adjusted(1, 1, -1, -1), option->palette.highlight().color().darker(120)); + else if (hover) + painter->fillRect(tmp.adjusted(1, 1, -1, -1), QColor(255, 255, 255, 20)); + + QColor mdiButtonGradientStartColor; + QColor mdiButtonGradientStopColor; + + mdiButtonGradientStartColor = QColor(0, 0, 0, 40); + mdiButtonGradientStopColor = QColor(255, 255, 255, 60); + + if (sunken) + titleBarHighlight = highlight.darker(130); + + QLinearGradient gradient(tmp.center().x(), tmp.top(), tmp.center().x(), tmp.bottom()); + gradient.setColorAt(0, mdiButtonGradientStartColor); + gradient.setColorAt(1, mdiButtonGradientStopColor); + QColor mdiButtonBorderColor(active ? option->palette.highlight().color().darker(180): dark.darker(110)); + + painter->setPen(QPen(mdiButtonBorderColor, 1)); + const QLine lines[4] = { + QLine(tmp.left() + 2, tmp.top(), tmp.right() - 2, tmp.top()), + QLine(tmp.left() + 2, tmp.bottom(), tmp.right() - 2, tmp.bottom()), + QLine(tmp.left(), tmp.top() + 2, tmp.left(), tmp.bottom() - 2), + QLine(tmp.right(), tmp.top() + 2, tmp.right(), tmp.bottom() - 2) + }; + painter->drawLines(lines, 4); + const QPoint points[4] = { + QPoint(tmp.left() + 1, tmp.top() + 1), + QPoint(tmp.right() - 1, tmp.top() + 1), + QPoint(tmp.left() + 1, tmp.bottom() - 1), + QPoint(tmp.right() - 1, tmp.bottom() - 1) + }; + painter->drawPoints(points, 4); + + painter->setPen(titleBarHighlight); + painter->drawLine(tmp.left() + 2, tmp.top() + 1, tmp.right() - 2, tmp.top() + 1); + painter->drawLine(tmp.left() + 1, tmp.top() + 2, tmp.left() + 1, tmp.bottom() - 2); + + painter->setPen(QPen(gradient, 1)); + painter->drawLine(tmp.right() + 1, tmp.top() + 2, tmp.right() + 1, tmp.bottom() - 2); + painter->drawPoint(tmp.right() , tmp.top() + 1); + + painter->drawLine(tmp.left() + 2, tmp.bottom() + 1, tmp.right() - 2, tmp.bottom() + 1); + painter->drawPoint(tmp.left() + 1, tmp.bottom()); + painter->drawPoint(tmp.right() - 1, tmp.bottom()); + painter->drawPoint(tmp.right() , tmp.bottom() - 1); +} + +/*! + \class QCleanlooksStyle + \brief The QCleanlooksStyle class provides a widget style similar to the + Clearlooks style available in GNOME. + \since 4.2 + + The Cleanlooks style provides a look and feel for widgets + that closely resembles the Clearlooks style, introduced by Richard + Stellingwerff and Daniel Borgmann. + + \sa {Cleanlooks Style Widget Gallery}, QWindowsXPStyle, QMacStyle, QWindowsStyle, + QCDEStyle, QMotifStyle, QPlastiqueStyle +*/ + +/*! + Constructs a QCleanlooksStyle object. +*/ +QCleanlooksStyle::QCleanlooksStyle() : QWindowsStyle(*new QCleanlooksStylePrivate) +{ + setObjectName(QLatin1String("CleanLooks")); +} + +/*! + \internal + + Constructs a QCleanlooksStyle object. +*/ +QCleanlooksStyle::QCleanlooksStyle(QCleanlooksStylePrivate &dd) : QWindowsStyle(dd) +{ +} + +/*! + Destroys the QCleanlooksStyle object. +*/ +QCleanlooksStyle::~QCleanlooksStyle() +{ +} + +/*! + \fn void QCleanlooksStyle::drawItemText(QPainter *painter, const QRect &rectangle, int alignment, const QPalette &palette, + bool enabled, const QString& text, QPalette::ColorRole textRole) const + + Draws the given \a text in the specified \a rectangle using the + provided \a painter and \a palette. + + Text is drawn using the painter's pen. If an explicit \a textRole + is specified, then the text is drawn using the \a palette's color + for the specified role. The \a enabled value indicates whether or + not the item is enabled; when reimplementing, this value should + influence how the item is drawn. + + The text is aligned and wrapped according to the specified \a + alignment. + + \sa Qt::Alignment +*/ +void QCleanlooksStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const +{ + if (text.isEmpty()) + return; + + QPen savedPen = painter->pen(); + if (textRole != QPalette::NoRole) { + painter->setPen(QPen(pal.brush(textRole), savedPen.widthF())); + } + if (!enabled) { + QPen pen = painter->pen(); + painter->setPen(pen); + } + painter->drawText(rect, alignment, text); + painter->setPen(savedPen); +} + +static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50) +{ + const int maxFactor = 100; + QColor tmp = colorA; + tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor); + tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor); + tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor); + return tmp; +} + +/*! + \reimp +*/ +void QCleanlooksStyle::drawPrimitive(PrimitiveElement elem, + const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + Q_ASSERT(option); + QRect rect = option->rect; + int state = option->state; + QColor button = option->palette.button().color(); + QColor buttonShadow = option->palette.button().color().darker(110); + QColor buttonShadowAlpha = buttonShadow; + buttonShadowAlpha.setAlpha(128); + QColor darkOutline; + QColor dark; + darkOutline.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*3.0)), + qMin(255, (int)(button.value()*0.6))); + dark.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*1.9)), + qMin(255, (int)(button.value()*0.7))); + QColor tabFrameColor = mergedColors(option->palette.background().color(), + dark.lighter(135), 60); + + switch(elem) { +#ifndef QT_NO_TABBAR + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb + = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) { + painter->save(); + painter->setPen(QPen(darkOutline.lighter(110), 0)); + switch (tbb->shape) { + case QTabBar::RoundedNorth: { + QRegion region(tbb->rect); + region -= tbb->selectedTabRect; + painter->drawLine(tbb->rect.topLeft(), tbb->rect.topRight()); + painter->setClipRegion(region); + painter->setPen(option->palette.light().color()); + painter->drawLine(tbb->rect.topLeft() + QPoint(0, 1), + tbb->rect.topRight() + QPoint(0, 1)); + } + break; + case QTabBar::RoundedWest: + painter->drawLine(tbb->rect.left(), tbb->rect.top(), tbb->rect.left(), tbb->rect.bottom()); + break; + case QTabBar::RoundedSouth: + painter->drawLine(tbb->rect.left(), tbb->rect.bottom(), + tbb->rect.right(), tbb->rect.bottom()); + break; + case QTabBar::RoundedEast: + painter->drawLine(tbb->rect.topRight(), tbb->rect.bottomRight()); + break; + case QTabBar::TriangularNorth: + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: + case QTabBar::TriangularSouth: + painter->restore(); + QWindowsStyle::drawPrimitive(elem, option, painter, widget); + return; + } + painter->restore(); + } + return; +#endif // QT_NO_TABBAR + case PE_IndicatorViewItemCheck: + { + QStyleOptionButton button; + button.QStyleOption::operator=(*option); + button.state &= ~State_MouseOver; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget); + } + return; + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + QRect r = header->rect; + QImage arrow; + if (header->sortIndicator & QStyleOptionHeader::SortUp) + arrow = QImage(qt_cleanlooks_arrow_up_xpm); + else if (header->sortIndicator & QStyleOptionHeader::SortDown) + arrow = QImage(qt_cleanlooks_arrow_down_xpm); + if (!arrow.isNull()) { + r.setSize(arrow.size()); + r.moveCenter(header->rect.center()); + arrow.setColor(1, header->palette.foreground().color().rgba()); + painter->drawImage(r, arrow); + } + } + break; + case PE_IndicatorButtonDropDown: + proxy()->drawPrimitive(PE_PanelButtonCommand, option, painter, widget); + break; + case PE_IndicatorToolBarSeparator: + { + QRect rect = option->rect; + const int margin = 6; + if (option->state & State_Horizontal) { + const int offset = rect.width()/2; + painter->setPen(QPen(option->palette.background().color().darker(110))); + painter->drawLine(rect.bottomLeft().x() + offset, + rect.bottomLeft().y() - margin, + rect.topLeft().x() + offset, + rect.topLeft().y() + margin); + painter->setPen(QPen(option->palette.background().color().lighter(110))); + painter->drawLine(rect.bottomLeft().x() + offset + 1, + rect.bottomLeft().y() - margin, + rect.topLeft().x() + offset + 1, + rect.topLeft().y() + margin); + } else { //Draw vertical separator + const int offset = rect.height()/2; + painter->setPen(QPen(option->palette.background().color().darker(110))); + painter->drawLine(rect.topLeft().x() + margin , + rect.topLeft().y() + offset, + rect.topRight().x() - margin, + rect.topRight().y() + offset); + painter->setPen(QPen(option->palette.background().color().lighter(110))); + painter->drawLine(rect.topLeft().x() + margin , + rect.topLeft().y() + offset + 1, + rect.topRight().x() - margin, + rect.topRight().y() + offset + 1); + } + } + break; + case PE_Frame: + painter->save(); + painter->setPen(dark.lighter(108)); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->restore(); + break; + case PE_FrameMenu: + painter->save(); + { + painter->setPen(QPen(darkOutline, 1)); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + QColor frameLight = option->palette.background().color().lighter(160); + QColor frameShadow = option->palette.background().color().darker(110); + + //paint beveleffect + QRect frame = option->rect.adjusted(1, 1, -1, -1); + painter->setPen(frameLight); + painter->drawLine(frame.topLeft(), frame.bottomLeft()); + painter->drawLine(frame.topLeft(), frame.topRight()); + + painter->setPen(frameShadow); + painter->drawLine(frame.topRight(), frame.bottomRight()); + painter->drawLine(frame.bottomLeft(), frame.bottomRight()); + } + painter->restore(); + break; + case PE_FrameDockWidget: + + painter->save(); + { + QColor softshadow = option->palette.background().color().darker(120); + + QRect rect= option->rect; + painter->setPen(softshadow); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->setPen(QPen(option->palette.light(), 0)); + painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), QPoint(rect.left() + 1, rect.bottom() - 1)); + painter->setPen(QPen(option->palette.background().color().darker(120), 0)); + painter->drawLine(QPoint(rect.left() + 1, rect.bottom() - 1), QPoint(rect.right() - 2, rect.bottom() - 1)); + painter->drawLine(QPoint(rect.right() - 1, rect.top() + 1), QPoint(rect.right() - 1, rect.bottom() - 1)); + + } + painter->restore(); + break; + case PE_PanelButtonTool: + painter->save(); + if ((option->state & State_Enabled || option->state & State_On) || !(option->state & State_AutoRaise)) { + QRect rect = option->rect; + QPen oldPen = painter->pen(); + + if (widget && widget->inherits("QDockWidgetTitleButton")) { + if (option->state & State_MouseOver) + proxy()->drawPrimitive(PE_PanelButtonCommand, option, painter, widget); + } else { + proxy()->drawPrimitive(PE_PanelButtonCommand, option, painter, widget); + } + } + painter->restore(); + break; + case PE_IndicatorDockWidgetResizeHandle: + { + QStyleOption dockWidgetHandle = *option; + bool horizontal = option->state & State_Horizontal; + if (horizontal) + dockWidgetHandle.state &= ~State_Horizontal; + else + dockWidgetHandle.state |= State_Horizontal; + proxy()->drawControl(CE_Splitter, &dockWidgetHandle, painter, widget); + } + break; + case PE_FrameWindow: + painter->save(); + { + QRect rect= option->rect; + painter->setPen(QPen(dark.darker(150), 0)); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->setPen(QPen(option->palette.light(), 0)); + painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), + QPoint(rect.left() + 1, rect.bottom() - 1)); + painter->setPen(QPen(option->palette.background().color().darker(120), 0)); + painter->drawLine(QPoint(rect.left() + 1, rect.bottom() - 1), + QPoint(rect.right() - 2, rect.bottom() - 1)); + painter->drawLine(QPoint(rect.right() - 1, rect.top() + 1), + QPoint(rect.right() - 1, rect.bottom() - 1)); + } + painter->restore(); + break; +#ifndef QT_NO_LINEEDIT + case PE_FrameLineEdit: + // fall through +#endif // QT_NO_LINEEDIT +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3ToolBar")) { + proxy()->drawPrimitive(PE_Q3Separator, option, painter, widget); + break; + } +#endif + { + QPen oldPen = painter->pen(); + if (option->state & State_Enabled) { + painter->setPen(QPen(option->palette.background(), 0)); + painter->drawRect(rect.adjusted(0, 0, 0, 0)); + painter->drawRect(rect.adjusted(1, 1, -1, -1)); + } else { + painter->fillRect(rect, option->palette.background()); + } + QRect r = rect.adjusted(0, 1, 0, -1); + painter->setPen(buttonShadowAlpha); + painter->drawLine(QPoint(r.left() + 2, r.top() - 1), QPoint(r.right() - 2, r.top() - 1)); + const QPoint points[8] = { + QPoint(r.right() - 1, r.top()), + QPoint(r.right(), r.top() + 1), + QPoint(r.right() - 1, r.bottom()), + QPoint(r.right(), r.bottom() - 1), + QPoint(r.left() + 1, r.top() ), + QPoint(r.left(), r.top() + 1), + QPoint(r.left() + 1, r.bottom() ), + QPoint(r.left(), r.bottom() - 1) + }; + painter->drawPoints(points, 8); + painter->setPen(QPen(option->palette.background().color(), 1)); + painter->drawLine(QPoint(r.left() + 2, r.top() + 1), QPoint(r.right() - 2, r.top() + 1)); + + if (option->state & State_HasFocus) { + QColor darkoutline = option->palette.highlight().color().darker(150); + QColor innerline = mergedColors(option->palette.highlight().color(), Qt::white); + painter->setPen(QPen(innerline, 0)); + painter->drawRect(rect.adjusted(1, 2, -2, -3)); + painter->setPen(QPen(darkoutline, 0)); + } + else { + QColor highlight = Qt::white; + highlight.setAlpha(130); + painter->setPen(option->palette.base().color().darker(120)); + painter->drawLine(QPoint(r.left() + 1, r.top() + 1), + QPoint(r.right() - 1, r.top() + 1)); + painter->drawLine(QPoint(r.left() + 1, r.top() + 1), + QPoint(r.left() + 1, r.bottom() - 1)); + painter->setPen(option->palette.base().color()); + painter->drawLine(QPoint(r.right() - 1, r.top() + 1), + QPoint(r.right() - 1, r.bottom() - 1)); + painter->setPen(highlight); + painter->drawLine(QPoint(r.left() + 1, r.bottom() + 1), + QPoint(r.right() - 1, r.bottom() + 1)); + painter->drawPoint(QPoint(r.left(), r.bottom())); + painter->drawPoint(QPoint(r.right(), r.bottom() )); + painter->setPen(QPen(darkOutline.lighter(115), 1)); + } + painter->drawLine(QPoint(r.left(), r.top() + 2), QPoint(r.left(), r.bottom() - 2)); + painter->drawLine(QPoint(r.right(), r.top() + 2), QPoint(r.right(), r.bottom() - 2)); + painter->drawLine(QPoint(r.left() + 2, r.bottom()), QPoint(r.right() - 2, r.bottom())); + const QPoint points2[4] = { + QPoint(r.right() - 1, r.bottom() - 1), + QPoint(r.right() - 1, r.top() + 1), + QPoint(r.left() + 1, r.bottom() - 1), + QPoint(r.left() + 1, r.top() + 1) + }; + painter->drawPoints(points2, 4); + painter->drawLine(QPoint(r.left() + 2, r.top()), QPoint(r.right() - 2, r.top())); + painter->setPen(oldPen); + } + break; + case PE_IndicatorCheckBox: + painter->save(); + if (const QStyleOptionButton *checkbox = qstyleoption_cast<const QStyleOptionButton*>(option)) { + QRect checkRect; + checkRect.setX(rect.left() ); + checkRect.setY(rect.top() ); + checkRect.setWidth(rect.width() - 1); + checkRect.setHeight(rect.height() - 1); + if (state & State_Sunken) + painter->setBrush(dark.lighter(130)); + else + painter->setBrush(option->palette.base()); + painter->setPen(QPen(dark.lighter(110), 0)); + painter->drawRect(checkRect); + if (checkbox->state & (State_On | State_Sunken | State_NoChange)) { + QImage image(qt_cleanlooks_checkbox_checked); + QColor fillColor = option->palette.text().color(); + image.setColor(1, fillColor.rgba()); + fillColor.setAlpha(100); + image.setColor(2, fillColor.rgba()); + painter->drawImage(rect, image); + if (checkbox->state & State_NoChange) { + QColor bgc = option->palette.background().color(); + bgc.setAlpha(127); + painter->fillRect(checkRect.adjusted(1, 1, -1, -1), bgc); + } + } + } + painter->restore(); + break; + case PE_IndicatorRadioButton: + painter->save(); + { + painter->setRenderHint(QPainter::SmoothPixmapTransform); + QRect checkRect = rect.adjusted(0, 0, 0, 0); + if (state & (State_On )) { + painter->drawImage(rect, QImage(qt_cleanlooks_radiobutton)); + painter->drawImage(checkRect, QImage(qt_cleanlooks_radiobutton_checked)); + } + else if (state & State_Sunken) { + painter->drawImage(rect, QImage(qt_cleanlooks_radiobutton)); + QColor bgc = buttonShadow; + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(bgc); + painter->setPen(Qt::NoPen); + painter->drawEllipse(rect.adjusted(1, 1, -1, -1)); } + else { + painter->drawImage(rect, QImage(qt_cleanlooks_radiobutton)); + } + } + painter->restore(); + break; + case PE_IndicatorToolBarHandle: + painter->save(); + if (option->state & State_Horizontal) { + for (int i = rect.height()/5; i <= 4*(rect.height()/5) ; ++i) { + int y = rect.topLeft().y() + i + 1; + int x1 = rect.topLeft().x() + 3; + int x2 = rect.topRight().x() - 2; + + if (i % 2 == 0) + painter->setPen(QPen(option->palette.light(), 0)); + else + painter->setPen(QPen(dark.lighter(110), 0)); + painter->drawLine(x1, y, x2, y); + } + } + else { //vertical toolbar + for (int i = rect.width()/5; i <= 4*(rect.width()/5) ; ++i) { + int x = rect.topLeft().x() + i + 1; + int y1 = rect.topLeft().y() + 3; + int y2 = rect.topLeft().y() + 5; + + if (i % 2 == 0) + painter->setPen(QPen(option->palette.light(), 0)); + else + painter->setPen(QPen(dark.lighter(110), 0)); + painter->drawLine(x, y1, x, y2); + } + } + painter->restore(); + break; + case PE_FrameDefaultButton: + case PE_FrameFocusRect: + if (const QStyleOptionFocusRect *focusFrame = qstyleoption_cast<const QStyleOptionFocusRect *>(option)) { + if (!(focusFrame->state & State_KeyboardFocusChange)) + return; + QRect rect = focusFrame->rect; + painter->save(); + painter->setBackgroundMode(Qt::TransparentMode); + painter->setBrush(QBrush(dark.darker(120), Qt::Dense4Pattern)); + painter->setBrushOrigin(rect.topLeft()); + painter->setPen(Qt::NoPen); + const QRect rects[4] = { + QRect(rect.left(), rect.top(), rect.width(), 1), // Top + QRect(rect.left(), rect.bottom(), rect.width(), 1), // Bottom + QRect(rect.left(), rect.top(), 1, rect.height()), // Left + QRect(rect.right(), rect.top(), 1, rect.height()) // Right + }; + painter->drawRects(rects, 4); + painter->restore(); + } + break; + case PE_PanelButtonCommand: + { + bool isDefault = false; + bool isFlat = false; + bool isDown = (option->state & State_Sunken) || (option->state & State_On); + QPen oldPen = painter->pen(); + QBrush oldBrush = painter->brush(); + QRect r; + + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option)) { + isDefault = (button->features & QStyleOptionButton::DefaultButton) && (button->state & State_Enabled); + isFlat = (button->features & QStyleOptionButton::Flat); + } + + if (isFlat && !isDown) { + if (isDefault) { + r = option->rect.adjusted(0, 1, 0, -1); + painter->setPen(QPen(Qt::black, 0)); + const QLine lines[4] = { + QLine(QPoint(r.left() + 2, r.top()), + QPoint(r.right() - 2, r.top())), + QLine(QPoint(r.left(), r.top() + 2), + QPoint(r.left(), r.bottom() - 2)), + QLine(QPoint(r.right(), r.top() + 2), + QPoint(r.right(), r.bottom() - 2)), + QLine(QPoint(r.left() + 2, r.bottom()), + QPoint(r.right() - 2, r.bottom())) + }; + painter->drawLines(lines, 4); + const QPoint points[4] = { + QPoint(r.right() - 1, r.bottom() - 1), + QPoint(r.right() - 1, r.top() + 1), + QPoint(r.left() + 1, r.bottom() - 1), + QPoint(r.left() + 1, r.top() + 1) + }; + painter->drawPoints(points, 4); + painter->setPen(oldPen); + } + return; + } + + BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("pushbutton-%1").arg(isDefault)) + r = rect.adjusted(0, 1, 0, -1); + + bool isEnabled = (option->state & State_Enabled); + + QColor highlightedGradientStartColor = option->palette.button().color().lighter(107); + QColor highlightedGradientMidColor = option->palette.button().color().lighter(105); + QColor highlightedGradientStopColor = buttonShadow.lighter(107); + QColor gradientStartColor = option->palette.button().color().lighter(108); + + QColor buttonColor = option->palette.button().color(); + QColor gradientMidColor = option->palette.button().color(); + QColor gradientStopColor; + gradientStopColor.setHsv(buttonColor.hue(), + qMin(255, (int)(buttonColor.saturation()*1.9)), + qMin(255, (int)(buttonColor.value()*0.96))); + + QRect gradRect = rect.adjusted(1, 2, -1, -2); + // gradient fill + QRect innerBorder = r.adjusted(1, 1, -1, 0); + + if (isDown) { + QBrush fillColor = gradientStopColor.darker(110); + if (option->palette.button().gradient()) + fillColor = option->palette.button(); + p->fillRect(gradRect, fillColor); + p->setPen(gradientStopColor.darker(125)); + p->drawLine(innerBorder.topLeft(), innerBorder.topRight()); + p->drawLine(innerBorder.topLeft(), innerBorder.bottomLeft()); + } else { + if (isEnabled && option->state & State_MouseOver ) { + qt_cleanlooks_draw_buttongradient(p, gradRect, + highlightedGradientStartColor, + highlightedGradientMidColor, + highlightedGradientStopColor, TopDown, option->palette.button()); + } else { + qt_cleanlooks_draw_buttongradient(p, gradRect, + gradientStartColor, + gradientMidColor, + gradientStopColor, TopDown, option->palette.button()); + } + } + + bool hasFocus = option->state & State_HasFocus; + + if (!isEnabled) + p->setPen(QPen(dark.lighter(115))); + else if (isDefault) + p->setPen(QPen(Qt::black, 1)); + else + p->setPen(QPen(darkOutline, 1)); + + p->drawLine(QPoint(r.left(), r.top() + 2), + QPoint(r.left(), r.bottom() - 2)); + p->drawLine(QPoint(r.right(), r.top() + 2), + QPoint(r.right(), r.bottom() - 2)); + p->drawLine(QPoint(r.left() + 2, r.bottom()), + QPoint(r.right() - 2, r.bottom())); + const QPoint points[4] = { + QPoint(r.right() - 1, r.bottom() - 1), + QPoint(r.right() - 1, r.top() + 1), + QPoint(r.left() + 1, r.bottom() - 1), + QPoint(r.left() + 1, r.top() + 1) + }; + p->drawPoints(points, 4); + + if (!isDefault && !hasFocus && isEnabled) + p->setPen(QPen(darkOutline.darker(110), 0)); + + p->drawLine(QPoint(r.left() + 2, r.top()), + QPoint(r.right() - 2, r.top())); + + QColor highlight = Qt::white; + highlight.setAlpha(110); + p->setPen(highlight); + p->drawLine(QPoint(r.left() + 1, r.top() + 2), + QPoint(r.left() + 1, r.bottom() - 2)); + p->drawLine(QPoint(r.left() + 3, r.bottom() + 1), + QPoint(r.right() - 3, r.bottom() + 1)); + + QColor topShadow = darkOutline; + topShadow.setAlpha(60); + + p->setPen(topShadow); + const QPoint points2[8] = { + QPoint(r.right(), r.top() + 1), + QPoint(r.right() - 1, r.top() ), + QPoint(r.right(), r.bottom() - 1), + QPoint(r.right() - 1, r.bottom() ), + QPoint(r.left() + 1, r.bottom()), + QPoint(r.left(), r.bottom() - 1), + QPoint(r.left() + 1, r.top()), + QPoint(r.left(), r.top() + 1) + }; + p->drawPoints(points2, 8); + + topShadow.setAlpha(30); + p->setPen(topShadow); + + p->drawLine(QPoint(r.right() - 1, r.top() + 2), + QPoint(r.right() - 1, r.bottom() - 2)); + p->drawLine(QPoint(r.left() + 2, r.top() - 1), + QPoint(r.right() - 2, r.top() - 1)); + + if (isDefault) { + r.adjust(-1, -1, 1, 1); + p->setPen(buttonShadowAlpha.darker(120)); + const QLine lines[4] = { + QLine(r.topLeft() + QPoint(3, 0), r.topRight() - QPoint(3, 0)), + QLine(r.bottomLeft() + QPoint(3, 0), r.bottomRight() - QPoint(3, 0)), + QLine(r.topLeft() + QPoint(0, 3), r.bottomLeft() - QPoint(0, 3)), + QLine(r.topRight() + QPoint(0, 3), r.bottomRight() - QPoint(0, 3)) + }; + p->drawLines(lines, 4); + const QPoint points3[8] = { + r.topRight() + QPoint(-2, 1), + r.topRight() + QPoint(-1, 2), + r.bottomRight() + QPoint(-1, -2), + r.bottomRight() + QPoint(-2, -1), + r.topLeft() + QPoint(1, 2), + r.topLeft() + QPoint(2, 1), + r.bottomLeft() + QPoint(1, -2), + r.bottomLeft() + QPoint(2, -1) + }; + p->drawPoints(points3, 8); + } + painter->setPen(oldPen); + painter->setBrush(oldBrush); + END_STYLE_PIXMAPCACHE + } + break; +#ifndef QT_NO_TABBAR + case PE_FrameTabWidget: + painter->save(); + { + painter->fillRect(option->rect, tabFrameColor); + } +#ifndef QT_NO_TABWIDGET + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) { + QColor borderColor = darkOutline.lighter(110); + QColor alphaCornerColor = mergedColors(borderColor, option->palette.background().color()); + QColor innerShadow = mergedColors(borderColor, option->palette.base().color()); + + int borderThickness = proxy()->pixelMetric(PM_TabBarBaseOverlap, twf, widget); + bool reverse = (twf->direction == Qt::RightToLeft); + QRect tabBarRect; + + switch (twf->shape) { + case QTabBar::RoundedNorth: + if (reverse) { + tabBarRect = QRect(twf->rect.right() - twf->leftCornerWidgetSize.width() + - twf->tabBarSize.width() + 1, + twf->rect.top(), + twf->tabBarSize.width(), borderThickness); + } else { + tabBarRect = QRect(twf->rect.left() + twf->leftCornerWidgetSize.width(), + twf->rect.top(), + twf->tabBarSize.width(), borderThickness); + } + break ; + case QTabBar::RoundedWest: + tabBarRect = QRect(twf->rect.left(), + twf->rect.top() + twf->leftCornerWidgetSize.height(), + borderThickness, + twf->tabBarSize.height()); + tabBarRect = tabBarRect; //adjust + break ; + case QTabBar::RoundedEast: + tabBarRect = QRect(twf->rect.right() - borderThickness + 1, + twf->rect.top() + twf->leftCornerWidgetSize.height(), + 0, + twf->tabBarSize.height()); + break ; + case QTabBar::RoundedSouth: + if (reverse) { + tabBarRect = QRect(twf->rect.right() - twf->leftCornerWidgetSize.width() - twf->tabBarSize.width() + 1, + twf->rect.bottom() + 1, + twf->tabBarSize.width(), + borderThickness); + } else { + tabBarRect = QRect(twf->rect.left() + twf->leftCornerWidgetSize.width(), + twf->rect.bottom() + 1, + twf->tabBarSize.width(), + borderThickness); + } + break; + default: + break; + } + + QRegion region(twf->rect); + region -= tabBarRect; + painter->setClipRegion(region); + + // Outer border + QLine leftLine = QLine(twf->rect.topLeft() + QPoint(0, 2), twf->rect.bottomLeft() - QPoint(0, 2)); + QLine rightLine = QLine(twf->rect.topRight(), twf->rect.bottomRight() - QPoint(0, 2)); + QLine bottomLine = QLine(twf->rect.bottomLeft() + QPoint(2, 0), twf->rect.bottomRight() - QPoint(2, 0)); + QLine topLine = QLine(twf->rect.topLeft(), twf->rect.topRight()); + + painter->setPen(borderColor); + painter->drawLine(topLine); + + // Inner border + QLine innerLeftLine = QLine(leftLine.p1() + QPoint(1, 0), leftLine.p2() + QPoint(1, 0)); + QLine innerRightLine = QLine(rightLine.p1() - QPoint(1, -1), rightLine.p2() - QPoint(1, 0)); + QLine innerBottomLine = QLine(bottomLine.p1() - QPoint(0, 1), bottomLine.p2() - QPoint(0, 1)); + QLine innerTopLine = QLine(topLine.p1() + QPoint(0, 1), topLine.p2() + QPoint(-1, 1)); + + // Rounded Corner + QPoint leftBottomOuterCorner = QPoint(innerLeftLine.p2() + QPoint(0, 1)); + QPoint leftBottomInnerCorner1 = QPoint(leftLine.p2() + QPoint(0, 1)); + QPoint leftBottomInnerCorner2 = QPoint(bottomLine.p1() - QPoint(1, 0)); + QPoint rightBottomOuterCorner = QPoint(innerRightLine.p2() + QPoint(0, 1)); + QPoint rightBottomInnerCorner1 = QPoint(rightLine.p2() + QPoint(0, 1)); + QPoint rightBottomInnerCorner2 = QPoint(bottomLine.p2() + QPoint(1, 0)); + QPoint leftTopOuterCorner = QPoint(innerLeftLine.p1() - QPoint(0, 1)); + QPoint leftTopInnerCorner1 = QPoint(leftLine.p1() - QPoint(0, 1)); + QPoint leftTopInnerCorner2 = QPoint(topLine.p1() - QPoint(1, 0)); + + painter->setPen(borderColor); + painter->drawLine(leftLine); + painter->drawLine(rightLine); + painter->drawLine(bottomLine); + painter->drawPoint(leftBottomOuterCorner); + painter->drawPoint(rightBottomOuterCorner); + painter->drawPoint(leftTopOuterCorner); + + painter->setPen(option->palette.light().color()); + painter->drawLine(innerLeftLine); + painter->drawLine(innerTopLine); + + painter->setPen(buttonShadowAlpha); + painter->drawLine(innerRightLine); + painter->drawLine(innerBottomLine); + + painter->setPen(alphaCornerColor); + const QPoint points[6] = { + leftBottomInnerCorner1, + leftBottomInnerCorner2, + rightBottomInnerCorner1, + rightBottomInnerCorner2, + leftTopInnerCorner1, + leftTopInnerCorner2 + }; + painter->drawPoints(points, 6); + } +#endif // QT_NO_TABWIDGET + painter->restore(); + break ; + + case PE_FrameStatusBarItem: + break; + case PE_IndicatorTabClose: + { + Q_D(const QCleanlooksStyle); + if (d->tabBarcloseButtonIcon.isNull()) + d->tabBarcloseButtonIcon = standardIcon(SP_DialogCloseButton, option, widget); + if ((option->state & State_Enabled) && (option->state & State_MouseOver)) + proxy()->drawPrimitive(PE_PanelButtonCommand, option, painter, widget); + QPixmap pixmap = d->tabBarcloseButtonIcon.pixmap(QSize(16, 16), QIcon::Normal, QIcon::On); + proxy()->drawItemPixmap(painter, option->rect, Qt::AlignCenter, pixmap); + } + break; + +#endif // QT_NO_TABBAR + default: + QWindowsStyle::drawPrimitive(elem, option, painter, widget); + break; + } +} + +/*! + \reimp +*/ +void QCleanlooksStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, + const QWidget *widget) const +{ + QColor button = option->palette.button().color(); + QColor dark; + dark.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*1.9)), + qMin(255, (int)(button.value()*0.7))); + QColor darkOutline; + darkOutline.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*2.0)), + qMin(255, (int)(button.value()*0.6))); + QRect rect = option->rect; + QColor shadow = mergedColors(option->palette.background().color().darker(120), + dark.lighter(130), 60); + QColor tabFrameColor = mergedColors(option->palette.background().color(), + dark.lighter(135), 60); + + QColor highlight = option->palette.highlight().color(); + + switch(element) { + case CE_RadioButton: //fall through + case CE_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool hover = (btn->state & State_MouseOver && btn->state & State_Enabled); + if (hover) + painter->fillRect(rect, btn->palette.background().color().lighter(104)); + QStyleOptionButton copy = *btn; + copy.rect.adjust(2, 0, -2, 0); + QWindowsStyle::drawControl(element, ©, painter, widget); + } + break; + case CE_Splitter: + painter->save(); + { + // hover appearance + QBrush fillColor = option->palette.background().color(); + if (option->state & State_MouseOver && option->state & State_Enabled) + fillColor = fillColor.color().lighter(106); + + painter->fillRect(option->rect, fillColor); + + QColor grooveColor = mergedColors(dark.lighter(110), option->palette.button().color(),40); + QColor gripShadow = grooveColor.darker(110); + QPalette palette = option->palette; + bool vertical = !(option->state & State_Horizontal); + QRect scrollBarSlider = option->rect; + int gripMargin = 4; + //draw grips + if (vertical) { + for( int i = -20; i< 20 ; i += 2) { + painter->setPen(QPen(gripShadow, 1)); + painter->drawLine( + QPoint(scrollBarSlider.center().x() + i , + scrollBarSlider.top() + gripMargin), + QPoint(scrollBarSlider.center().x() + i, + scrollBarSlider.bottom() - gripMargin)); + painter->setPen(QPen(palette.light(), 1)); + painter->drawLine( + QPoint(scrollBarSlider.center().x() + i + 1, + scrollBarSlider.top() + gripMargin ), + QPoint(scrollBarSlider.center().x() + i + 1, + scrollBarSlider.bottom() - gripMargin)); + } + } else { + for (int i = -20; i < 20 ; i += 2) { + painter->setPen(QPen(gripShadow, 1)); + painter->drawLine( + QPoint(scrollBarSlider.left() + gripMargin , + scrollBarSlider.center().y()+ i), + QPoint(scrollBarSlider.right() - gripMargin, + scrollBarSlider.center().y()+ i)); + painter->setPen(QPen(palette.light(), 1)); + painter->drawLine( + QPoint(scrollBarSlider.left() + gripMargin, + scrollBarSlider.center().y() + 1 + i), + QPoint(scrollBarSlider.right() - gripMargin, + scrollBarSlider.center().y() + 1 + i)); + + } + } + } + painter->restore(); + break; +#ifndef QT_NO_SIZEGRIP + case CE_SizeGrip: + painter->save(); + { + int x, y, w, h; + option->rect.getRect(&x, &y, &w, &h); + int sw = qMin(h, w); + if (h > w) + painter->translate(0, h - w); + else + painter->translate(w - h, 0); + + int sx = x; + int sy = y; + int s = 4; + if (option->direction == Qt::RightToLeft) { + sx = x + sw; + for (int i = 0; i < 4; ++i) { + painter->setPen(QPen(option->palette.light().color(), 1)); + painter->drawLine(x, sy - 1 , sx + 1, sw); + painter->setPen(QPen(dark.lighter(120), 1)); + painter->drawLine(x, sy, sx, sw); + sx -= s; + sy += s; + } + } else { + for (int i = 0; i < 4; ++i) { + painter->setPen(QPen(option->palette.light().color(), 1)); + painter->drawLine(sx - 1, sw, sw, sy - 1); + painter->setPen(QPen(dark.lighter(120), 1)); + painter->drawLine(sx, sw, sw, sy); + sx += s; + sy += s; + } + } + } + painter->restore(); + break; +#endif // QT_NO_SIZEGRIP +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + painter->save(); + if (const QStyleOptionToolBar *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + QRect rect = option->rect; + + bool paintLeftBorder = true; + bool paintRightBorder = true; + bool paintBottomBorder = true; + + switch (toolbar->toolBarArea) { + case Qt::BottomToolBarArea: + switch(toolbar->positionOfLine) { + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintBottomBorder = false; + default: + break; + } + case Qt::TopToolBarArea: + switch (toolbar->positionWithinLine) { + case QStyleOptionToolBar::Beginning: + paintLeftBorder = false; + break; + case QStyleOptionToolBar::End: + paintRightBorder = false; + break; + case QStyleOptionToolBar::OnlyOne: + paintRightBorder = false; + paintLeftBorder = false; + default: + break; + } + if (toolbar->direction == Qt::RightToLeft) { //reverse layout changes the order of Beginning/end + bool tmp = paintLeftBorder; + paintRightBorder=paintLeftBorder; + paintLeftBorder=tmp; + } + break; + case Qt::RightToolBarArea: + switch (toolbar->positionOfLine) { + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintRightBorder = false; + break; + default: + break; + } + break; + case Qt::LeftToolBarArea: + switch (toolbar->positionOfLine) { + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintLeftBorder = false; + break; + default: + break; + } + break; + default: + break; + } + + QColor light = option->palette.background().color().lighter(110); + + //draw top border + painter->setPen(QPen(light)); + painter->drawLine(rect.topLeft().x(), + rect.topLeft().y(), + rect.topRight().x(), + rect.topRight().y()); + + if (paintLeftBorder) { + painter->setPen(QPen(light)); + painter->drawLine(rect.topLeft().x(), + rect.topLeft().y(), + rect.bottomLeft().x(), + rect.bottomLeft().y()); + } + + if (paintRightBorder) { + painter->setPen(QPen(shadow)); + painter->drawLine(rect.topRight().x(), + rect.topRight().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + } + + if (paintBottomBorder) { + painter->setPen(QPen(shadow)); + painter->drawLine(rect.bottomLeft().x(), + rect.bottomLeft().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + } + } + painter->restore(); + break; +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + painter->save(); + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + QRect rect = dwOpt->rect; + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, option, widget); + QRect r = rect.adjusted(0, 0, -1, 0); + if (verticalTitleBar) + r.adjust(0, 0, 0, -1); + painter->setPen(option->palette.light().color()); + painter->drawRect(r.adjusted(1, 1, 1, 1)); + painter->setPen(shadow); + painter->drawRect(r); + + if (verticalTitleBar) { + QRect r = rect; + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + + painter->translate(r.left(), r.top() + r.width()); + painter->rotate(-90); + painter->translate(-r.left(), -r.top()); + + rect = r; + } + + if (!dwOpt->title.isEmpty()) { + QString titleText + = painter->fontMetrics().elidedText(dwOpt->title, + Qt::ElideRight, titleRect.width()); + proxy()->drawItemText(painter, + titleRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + } + painter->restore(); + break; +#endif // QT_NO_DOCKWIDGET + case CE_HeaderSection: + painter->save(); + // Draws the header in tables. + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + QPixmap cache; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("headersection"), option, option->rect.size()); + pixmapName += QString::number(- int(header->position)); + pixmapName += QString::number(- int(header->orientation)); + QRect r = option->rect; + QColor gradientStopColor; + QColor gradientStartColor = option->palette.button().color(); + gradientStopColor.setHsv(gradientStartColor.hue(), + qMin(255, (int)(gradientStartColor.saturation()*2)), + qMin(255, (int)(gradientStartColor.value()*0.96))); + QLinearGradient gradient(rect.topLeft(), rect.bottomLeft()); + if (option->palette.background().gradient()) { + gradient.setStops(option->palette.background().gradient()->stops()); + } else { + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(0.8, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + } + painter->fillRect(r, gradient); + + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(r.size()); + cache.fill(Qt::transparent); + QRect pixmapRect(0, 0, r.width(), r.height()); + QPainter cachePainter(&cache); + if (header->orientation == Qt::Vertical) { + cachePainter.setPen(QPen(dark)); + cachePainter.drawLine(pixmapRect.topRight(), pixmapRect.bottomRight()); + if (header->position != QStyleOptionHeader::End) { + cachePainter.setPen(QPen(shadow)); + cachePainter.drawLine(pixmapRect.bottomLeft() + QPoint(3, -1), pixmapRect.bottomRight() + QPoint(-3, -1)); cachePainter.setPen(QPen(option->palette.light().color())); + cachePainter.drawLine(pixmapRect.bottomLeft() + QPoint(3, 0), pixmapRect.bottomRight() + QPoint(-3, 0)); } + } else { + cachePainter.setPen(QPen(dark)); + cachePainter.drawLine(pixmapRect.bottomLeft(), pixmapRect.bottomRight()); + cachePainter.setPen(QPen(shadow)); + cachePainter.drawLine(pixmapRect.topRight() + QPoint(-1, 3), pixmapRect.bottomRight() + QPoint(-1, -3)); cachePainter.setPen(QPen(option->palette.light().color())); + cachePainter.drawLine(pixmapRect.topRight() + QPoint(0, 3), pixmapRect.bottomRight() + QPoint(0, -3)); } + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(r.topLeft(), cache); + } + painter->restore(); + break; + case CE_ProgressBarGroove: + painter->save(); + { + painter->fillRect(rect, option->palette.base()); + QColor borderColor = dark.lighter(110); + painter->setPen(QPen(borderColor, 0)); + const QLine lines[4] = { + QLine(QPoint(rect.left() + 1, rect.top()), QPoint(rect.right() - 1, rect.top())), + QLine(QPoint(rect.left() + 1, rect.bottom()), QPoint(rect.right() - 1, rect.bottom())), + QLine(QPoint(rect.left(), rect.top() + 1), QPoint(rect.left(), rect.bottom() - 1)), + QLine(QPoint(rect.right(), rect.top() + 1), QPoint(rect.right(), rect.bottom() - 1)) + }; + painter->drawLines(lines, 4); + QColor alphaCorner = mergedColors(borderColor, option->palette.background().color()); + QColor innerShadow = mergedColors(borderColor, option->palette.base().color()); + + //corner smoothing + painter->setPen(alphaCorner); + const QPoint points[4] = { + rect.topRight(), + rect.topLeft(), + rect.bottomRight(), + rect.bottomLeft() + }; + painter->drawPoints(points, 4); + + //inner shadow + painter->setPen(innerShadow); + painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), + QPoint(rect.right() - 1, rect.top() + 1)); + painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), + QPoint(rect.left() + 1, rect.bottom() + 1)); + + } + painter->restore(); + break; + case CE_ProgressBarContents: + painter->save(); + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + QRect rect = bar->rect; + bool vertical = false; + bool inverted = false; + bool indeterminate = (bar->minimum == 0 && bar->maximum == 0); + + // Get extra style options if version 2 + if (const QStyleOptionProgressBarV2 *bar2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (bar2->orientation == Qt::Vertical); + inverted = bar2->invertedAppearance; + } + + // If the orientation is vertical, we use a transform to rotate + // the progress bar 90 degrees clockwise. This way we can use the + // same rendering code for both orientations. + if (vertical) { + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + QTransform m = QTransform::fromTranslate(rect.height()-1, -1.0); + m.rotate(90.0); + painter->setTransform(m, true); + } + + int maxWidth = rect.width() - 4; + int minWidth = 4; + qreal progress = qMax(bar->progress, bar->minimum); // workaround for bug in QProgressBar + int progressBarWidth = (progress - bar->minimum) * qreal(maxWidth) / qMax(qreal(1.0), qreal(bar->maximum) - bar->minimum); + int width = indeterminate ? maxWidth : qMax(minWidth, progressBarWidth); + + bool reverse = (!vertical && (bar->direction == Qt::RightToLeft)) || vertical; + if (inverted) + reverse = !reverse; + + QRect progressBar; + if (!indeterminate) { + if (!reverse) { + progressBar.setRect(rect.left() + 1, rect.top() + 1, width + 1, rect.height() - 3); + } else { + progressBar.setRect(rect.right() - 1 - width, rect.top() + 1, width + 1, rect.height() - 3); + } + } else { + Q_D(const QCleanlooksStyle); + int slideWidth = ((rect.width() - 4) * 2) / 3; + int step = ((d->animateStep * slideWidth) / d->animationFps) % slideWidth; + if ((((d->animateStep * slideWidth) / d->animationFps) % (2 * slideWidth)) >= slideWidth) + step = slideWidth - step; + progressBar.setRect(rect.left() + 1 + step, rect.top() + 1, + slideWidth / 2, rect.height() - 3); + } + QColor highlight = option->palette.color(QPalette::Normal, QPalette::Highlight); + painter->setPen(QPen(highlight.darker(140), 0)); + + QColor highlightedGradientStartColor = highlight.lighter(100); + QColor highlightedGradientStopColor = highlight.lighter(130); + + QLinearGradient gradient(rect.topLeft(), QPoint(rect.bottomLeft().x(), + rect.bottomLeft().y()*2)); + + gradient.setColorAt(0, highlightedGradientStartColor); + gradient.setColorAt(1, highlightedGradientStopColor); + + painter->setBrush(gradient); + painter->drawRect(progressBar); + + painter->setPen(QPen(highlight.lighter(120), 0)); + painter->drawLine(QPoint(progressBar.left() + 1, progressBar.top() + 1), + QPoint(progressBar.right(), progressBar.top() + 1)); + painter->drawLine(QPoint(progressBar.left() + 1, progressBar.top() + 1), + QPoint(progressBar.left() + 1, progressBar.bottom() - 1)); + + painter->setPen(QPen(highlightedGradientStartColor, 7.0));//QPen(option->palette.highlight(), 3)); + + painter->save(); + painter->setClipRect(progressBar.adjusted(2, 2, -1, -1)); + for (int x = progressBar.left() - 32; x < rect.right() ; x+=18) { + painter->drawLine(x, progressBar.bottom() + 1, x + 23, progressBar.top() - 2); + } + painter->restore(); + + } + painter->restore(); + break; + case CE_MenuBarItem: + painter->save(); + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + QStyleOptionMenuItem item = *mbi; + item.rect = mbi->rect.adjusted(0, 3, 0, -1); + QColor highlightOutline = highlight.darker(125); + QLinearGradient gradient(rect.topLeft(), QPoint(rect.bottomLeft().x(), rect.bottomLeft().y()*2)); + + if (option->palette.button().gradient()) { + gradient.setStops(option->palette.button().gradient()->stops()); + } else { + gradient.setColorAt(0, option->palette.button().color()); + gradient.setColorAt(1, option->palette.button().color().darker(110)); + } + painter->fillRect(rect, gradient); + + QCommonStyle::drawControl(element, &item, painter, widget); + + bool act = mbi->state & State_Selected && mbi->state & State_Sunken; + bool dis = !(mbi->state & State_Enabled); + + QRect r = option->rect; + if (act) { + qt_cleanlooks_draw_gradient(painter, r.adjusted(1, 1, -1, -1), + highlight, + highlightOutline, TopDown, + option->palette.highlight()); + + painter->setPen(QPen(highlightOutline, 0)); + const QLine lines[4] = { + QLine(QPoint(r.left(), r.top() + 1), QPoint(r.left(), r.bottom())), + QLine(QPoint(r.right(), r.top() + 1), QPoint(r.right(), r.bottom())), + QLine(QPoint(r.left() + 1, r.bottom()), QPoint(r.right() - 1, r.bottom())), + QLine(QPoint(r.left() + 1, r.top()), QPoint(r.right() - 1, r.top())) + }; + painter->drawLines(lines, 4); + + //draw text + QPalette::ColorRole textRole = dis ? QPalette::Text : QPalette::HighlightedText; + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + proxy()->drawItemText(painter, item.rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole); + } + + } + painter->restore(); + break; + case CE_MenuItem: + painter->save(); + // Draws one item in a popup menu. + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + QColor highlightOutline = highlight.darker(125); + QColor menuBackground = option->palette.background().color().lighter(104); + QColor borderColor = option->palette.background().color().darker(160); + QColor alphaCornerColor; + + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + QColor alphaTextColor = mergedColors(option->palette.background().color(), option->palette.text().color()); + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + painter->fillRect(menuItem->rect, menuBackground); + int w = 0; + if (!menuItem->text.isEmpty()) { + painter->setFont(menuItem->font); + proxy()->drawItemText(painter, menuItem->rect.adjusted(5, 0, -5, 0), Qt::AlignLeft | Qt::AlignVCenter, + menuItem->palette, menuItem->state & State_Enabled, menuItem->text, + QPalette::Text); + w = menuItem->fontMetrics.width(menuItem->text) + 5; + } + painter->setPen(shadow.lighter(106)); + bool reverse = menuItem->direction == Qt::RightToLeft; + painter->drawLine(menuItem->rect.left() + 5 + (reverse ? 0 : w), menuItem->rect.center().y(), + menuItem->rect.right() - 5 - (reverse ? w : 0), menuItem->rect.center().y()); + painter->restore(); + break; + } + bool selected = menuItem->state & State_Selected && menuItem->state & State_Enabled; + if (selected) { + QRect r = option->rect.adjusted(1, 0, -2, -1); + qt_cleanlooks_draw_gradient(painter, r, highlight, + highlightOutline, TopDown, + highlight); + r = r.adjusted(-1, 0, 1, 0); + painter->setPen(QPen(highlightOutline, 0)); + const QLine lines[4] = { + QLine(QPoint(r.left(), r.top() + 1), QPoint(r.left(), r.bottom() - 1)), + QLine(QPoint(r.right(), r.top() + 1), QPoint(r.right(), r.bottom() - 1)), + QLine(QPoint(r.left() + 1, r.bottom()), QPoint(r.right() - 1, r.bottom())), + QLine(QPoint(r.left() + 1, r.top()), QPoint(r.right() - 1, r.top())) + }; + painter->drawLines(lines, 4); + } else { + painter->fillRect(option->rect, menuBackground); + } + + bool checkable = menuItem->checkType != QStyleOptionMenuItem::NotCheckable; + bool checked = menuItem->checked; + bool sunken = menuItem->state & State_Sunken; + bool enabled = menuItem->state & State_Enabled; + + bool ignoreCheckMark = false; + int checkcol = qMax(menuItem->maxIconWidth, 20); + +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox*>(widget)) + ignoreCheckMark = true; //ignore the checkmarks provided by the QComboMenuDelegate +#endif + + if (!ignoreCheckMark) { + // Check + QRect checkRect(option->rect.left() + 7, option->rect.center().y() - 6, 13, 13); + checkRect = visualRect(menuItem->direction, menuItem->rect, checkRect); + if (checkable) { + if (menuItem->checkType & QStyleOptionMenuItem::Exclusive) { + // Radio button + if (checked || sunken) { + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + + QPalette::ColorRole textRole = !enabled ? QPalette::Text: + selected ? QPalette::HighlightedText : QPalette::ButtonText; + painter->setBrush(option->palette.brush( option->palette.currentColorGroup(), textRole)); + painter->drawEllipse(checkRect.adjusted(4, 4, -4, -4)); + } + } else { + // Check box + if (menuItem->icon.isNull()) { + if (checked || sunken) { + QImage image(qt_cleanlooks_menuitem_checkbox_checked); + if (enabled && (menuItem->state & State_Selected)) { + image.setColor(1, 0x55ffffff); + image.setColor(2, 0xAAffffff); + image.setColor(3, 0xBBffffff); + image.setColor(4, 0xFFffffff); + image.setColor(5, 0x33ffffff); + } else { + image.setColor(1, 0x55000000); + image.setColor(2, 0xAA000000); + image.setColor(3, 0xBB000000); + image.setColor(4, 0xFF000000); + image.setColor(5, 0x33000000); + } + painter->drawImage(QPoint(checkRect.center().x() - image.width() / 2, + checkRect.center().y() - image.height() / 2), image); + } + } + } + } + } else { //ignore checkmark + if (menuItem->icon.isNull()) + checkcol = 0; + else + checkcol = menuItem->maxIconWidth; + } + + // Text and icon, ripped from windows style + bool dis = !(menuItem->state & State_Enabled); + bool act = menuItem->state & State_Selected; + const QStyleOption *opt = option; + const QStyleOptionMenuItem *menuitem = menuItem; + + QPainter *p = painter; + QRect vCheckRect = visualRect(opt->direction, menuitem->rect, + QRect(menuitem->rect.x(), menuitem->rect.y(), + checkcol, menuitem->rect.height())); + if (!menuItem->icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + if (act && !dis) + mode = QIcon::Active; + QPixmap pixmap; + + int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize, option, widget); + QSize iconSize(smallIconSize, smallIconSize); +#ifndef QT_NO_COMBOBOX + if (const QComboBox *combo = qobject_cast<const QComboBox*>(widget)) + iconSize = combo->iconSize(); +#endif // QT_NO_COMBOBOX + if (checked) + pixmap = menuItem->icon.pixmap(iconSize, mode, QIcon::On); + else + pixmap = menuItem->icon.pixmap(iconSize, mode); + + int pixw = pixmap.width(); + int pixh = pixmap.height(); + + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center()); + painter->setPen(menuItem->palette.text().color()); + if (checkable && checked) { + QStyleOption opt = *option; + if (act) { + QColor activeColor = mergedColors(option->palette.background().color(), + option->palette.highlight().color()); + opt.palette.setBrush(QPalette::Button, activeColor); + } + opt.state |= State_Sunken; + opt.rect = vCheckRect; + proxy()->drawPrimitive(PE_PanelButtonCommand, &opt, painter, widget); + } + painter->drawPixmap(pmr.topLeft(), pixmap); + } + if (selected) { + painter->setPen(menuItem->palette.highlightedText().color()); + } else { + painter->setPen(menuItem->palette.text().color()); + } + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + QColor discol; + if (dis) { + discol = menuitem->palette.text().color(); + p->setPen(discol); + } + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + int xpos = menuitem->rect.x() + xm; + + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(opt->direction, menuitem->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { // draw text + p->save(); + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!styleHint(SH_UnderlineShortcut, menuitem, widget)) + text_flags |= Qt::TextHideMnemonic; + text_flags |= Qt::AlignLeft; + if (t >= 0) { + QRect vShortcutRect = visualRect(opt->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom()))); + 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(discol); + } + p->drawText(vShortcutRect, text_flags, s.mid(t + 1)); + s = s.left(t); + } + QFont font = menuitem->font; + // font may not have any "hard" flags set. We override + // the point size so that when it is resolved against the device, this font will win. + // This is mainly to handle cases where someone sets the font on the window + // and then the combo inherits it and passes it onward. At that point the resolve mask + // is very, very weak. This makes it stonger. + font.setPointSizeF(QFontInfo(menuItem->font).pointSizeF()); + + 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(discol); + } + p->drawText(vTextRect, text_flags, s.left(t)); + p->restore(); + } + + // Arrow + if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + int dim = (menuItem->rect.height() - 4) / 2; + PrimitiveElement arrow; + arrow = QApplication::isRightToLeft() ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + int xpos = menuItem->rect.left() + menuItem->rect.width() - 3 - dim; + QRect vSubMenuRect = visualRect(option->direction, menuItem->rect, + QRect(xpos, menuItem->rect.top() + menuItem->rect.height() / 2 - dim / 2, dim, dim)); + QStyleOptionMenuItem newMI = *menuItem; + newMI.rect = vSubMenuRect; + newMI.state = !enabled ? State_None : State_Enabled; + if (selected) + newMI.palette.setColor(QPalette::ButtonText, + newMI.palette.highlightedText().color()); + proxy()->drawPrimitive(arrow, &newMI, painter, widget); + } + } + painter->restore(); + break; + case CE_MenuHMargin: + case CE_MenuVMargin: + break; + case CE_MenuEmptyArea: + break; + case CE_PushButtonLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + QRect ir = button->rect; + uint tf = Qt::AlignVCenter; + if (styleHint(SH_UnderlineShortcut, button, widget)) + tf |= Qt::TextShowMnemonic; + else + tf |= Qt::TextHideMnemonic; + + if (!button->icon.isNull()) { + //Center both icon and text + QPoint point; + + QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + if (mode == QIcon::Normal && button->state & State_HasFocus) + mode = QIcon::Active; + QIcon::State state = QIcon::Off; + if (button->state & State_On) + state = QIcon::On; + + QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state); + int w = pixmap.width(); + int h = pixmap.height(); + + if (!button->text.isEmpty()) + w += button->fontMetrics.boundingRect(option->rect, tf, button->text).width() + 2; + + point = QPoint(ir.x() + ir.width() / 2 - w / 2, + ir.y() + ir.height() / 2 - h / 2); + + if (button->direction == Qt::RightToLeft) + point.rx() += pixmap.width(); + + painter->drawPixmap(visualPos(button->direction, button->rect, point), pixmap); + + if (button->direction == Qt::RightToLeft) + ir.translate(-point.x() - 2, 0); + else + ir.translate(point.x() + pixmap.width(), 0); + + // left-align text if there is + if (!button->text.isEmpty()) + tf |= Qt::AlignLeft; + + } else { + tf |= Qt::AlignHCenter; + } + + if (button->features & QStyleOptionButton::HasMenu) + ir = ir.adjusted(0, 0, -proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget), 0); + proxy()->drawItemText(painter, ir, tf, button->palette, (button->state & State_Enabled), + button->text, QPalette::ButtonText); + } + break; + case CE_MenuBarEmptyArea: + painter->save(); + { + QColor shadow = mergedColors(option->palette.background().color().darker(120), + dark.lighter(140), 60); + + QLinearGradient gradient(rect.topLeft(), QPoint(rect.bottomLeft().x(), rect.bottomLeft().y()*2)); + gradient.setColorAt(0, option->palette.button().color()); + gradient.setColorAt(1, option->palette.button().color().darker(110)); + painter->fillRect(rect, gradient); + +#ifndef QT_NO_MAINWINDOW + if (widget && qobject_cast<const QMainWindow *>(widget->parentWidget())) { + QPen oldPen = painter->pen(); + painter->setPen(QPen(shadow)); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + } +#endif // QT_NO_MAINWINDOW + } + painter->restore(); + break; +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + painter->save(); + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + + bool rtlHorTabs = (tab->direction == Qt::RightToLeft + && (tab->shape == QTabBar::RoundedNorth + || tab->shape == QTabBar::RoundedSouth)); + bool selected = tab->state & State_Selected; + bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End) + || (rtlHorTabs + && tab->position == QStyleOptionTab::Beginning)); + bool onlyTab = tab->position == QStyleOptionTab::OnlyOneTab; + bool leftCornerWidget = (tab->cornerWidgets & QStyleOptionTab::LeftCornerWidget); + + bool atBeginning = ((tab->position == (tab->direction == Qt::LeftToRight ? + QStyleOptionTab::Beginning : QStyleOptionTab::End)) || onlyTab); + + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + bool previousSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::PreviousIsSelected) + || (rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected)); + bool nextSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected) + || (rtlHorTabs + && tab->selectedPosition + == QStyleOptionTab::PreviousIsSelected)); + int tabBarAlignment = proxy()->styleHint(SH_TabBar_Alignment, tab, widget); + bool leftAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignLeft) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignRight); + + bool rightAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignRight) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignLeft); + + QColor light = tab->palette.light().color(); + QColor midlight = tab->palette.midlight().color(); + + QColor background = tab->palette.background().color(); + int borderThinkness = proxy()->pixelMetric(PM_TabBarBaseOverlap, tab, widget); + if (selected) + borderThinkness /= 2; + QRect r2(option->rect); + int x1 = r2.left(); + int x2 = r2.right(); + int y1 = r2.top(); + int y2 = r2.bottom(); + + QTransform rotMatrix; + bool flip = false; + painter->setPen(shadow); + QColor activeHighlight = option->palette.color(QPalette::Normal, QPalette::Highlight); + switch (tab->shape) { + case QTabBar::RoundedNorth: + break; + case QTabBar::RoundedSouth: + rotMatrix.rotate(180); + rotMatrix.translate(0, -rect.height() + 1); + rotMatrix.scale(-1, 1); + painter->setTransform(rotMatrix, true); + break; + case QTabBar::RoundedWest: + rotMatrix.rotate(180 + 90); + rotMatrix.scale(-1, 1); + flip = true; + painter->setTransform(rotMatrix, true); + break; + case QTabBar::RoundedEast: + rotMatrix.rotate(90); + rotMatrix.translate(0, - rect.width() + 1); + flip = true; + painter->setTransform(rotMatrix, true); + break; + default: + painter->restore(); + QWindowsStyle::drawControl(element, tab, painter, widget); + return; + } + + if (flip) { + QRect tmp = rect; + rect = QRect(tmp.y(), tmp.x(), tmp.height(), tmp.width()); + int temp = x1; + x1 = y1; + y1 = temp; + temp = x2; + x2 = y2; + y2 = temp; + } + + QLinearGradient gradient(rect.topLeft(), rect.bottomLeft()); + if (option->palette.button().gradient()) { + if (selected) + gradient.setStops(option->palette.background().gradient()->stops()); + else + gradient.setStops(option->palette.background().gradient()->stops()); + } + else if (selected) { + gradient.setColorAt(0, option->palette.background().color().lighter(104)); + gradient.setColorAt(1, tabFrameColor); + painter->fillRect(rect.adjusted(0, 2, 0, -1), gradient); + } else { + y1 += 2; + gradient.setColorAt(0, option->palette.background().color()); + gradient.setColorAt(1, dark.lighter(120)); + painter->fillRect(rect.adjusted(0, 2, 0, -2), gradient); + } + + // Delete border + if (selected) { + painter->setPen(QPen(activeHighlight, 0)); + painter->drawLine(x1 + 1, y1 + 1, x2 - 1, y1 + 1); + painter->drawLine(x1 , y1 + 2, x2 , y1 + 2); + } else { + painter->setPen(dark); + painter->drawLine(x1, y2 - 1, x2 + 2, y2 - 1 ); + if (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedWest) { + painter->setPen(light); + painter->drawLine(x1, y2 , x2, y2 ); + } + } + // Left + if (atBeginning || selected ) { + painter->setPen(light); + painter->drawLine(x1 + 1, y1 + 2 + 1, x1 + 1, y2 - ((onlyOne || atBeginning) && selected && leftAligned ? 0 : borderThinkness) - (atBeginning && leftCornerWidget ? 1 : 0)); + painter->drawPoint(x1 + 1, y1 + 1); + painter->setPen(dark); + painter->drawLine(x1, y1 + 2, x1, y2 - ((onlyOne || atBeginning) && leftAligned ? 0 : borderThinkness) - (atBeginning && leftCornerWidget ? 1 : 0)); + } + // Top + { + int beg = x1 + (previousSelected ? 0 : 2); + int end = x2 - (nextSelected ? 0 : 2); + painter->setPen(light); + + if (!selected)painter->drawLine(beg - 2, y1 + 1, end, y1 + 1); + + if (selected) + painter->setPen(QPen(activeHighlight.darker(150), 0)); + else + painter->setPen(darkOutline); + painter->drawLine(beg, y1 , end, y1); + + if (atBeginning|| selected) { + painter->drawPoint(beg - 1, y1 + 1); + } else if (!atBeginning) { + painter->drawPoint(beg - 1, y1); + painter->drawPoint(beg - 2, y1); + if (!lastTab) { + painter->setPen(dark.lighter(130)); + painter->drawPoint(end + 1, y1); + painter->drawPoint(end + 2 , y1); + painter->drawPoint(end + 2, y1 + 1); + } + } + } + // Right + if (lastTab || selected || onlyOne || !nextSelected) { + painter->setPen(darkOutline); + painter->drawLine(x2, y1 + 2, x2, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + if (selected) + painter->setPen(QPen(activeHighlight.darker(150), 0)); + else + painter->setPen(darkOutline); + painter->drawPoint(x2 - 1, y1 + 1); + + if (selected) { + painter->setPen(background.darker(110)); + painter->drawLine(x2 - 1, y1 + 3, x2 - 1, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + } + } + } + painter->restore(); + break; + +#endif // QT_NO_TABBAR + default: + QWindowsStyle::drawControl(element,option,painter,widget); + break; + } +} + +/*! + \reimp +*/ +QPalette QCleanlooksStyle::standardPalette () const +{ + QPalette palette = QWindowsStyle::standardPalette(); + palette.setBrush(QPalette::Active, QPalette::Highlight, QColor(98, 140, 178)); + palette.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(145, 141, 126)); + palette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 141, 126)); + + QColor backGround(239, 235, 231); + + QColor light = backGround.lighter(150); + QColor base = Qt::white; + QColor dark = QColor(170, 156, 143).darker(110); + dark = backGround.darker(150); + QColor darkDisabled = QColor(209, 200, 191).darker(110); + + //### Find the correct disabled text color + palette.setBrush(QPalette::Disabled, QPalette::Text, QColor(190, 190, 190)); + + palette.setBrush(QPalette::Window, backGround); + palette.setBrush(QPalette::Mid, backGround.darker(130)); + palette.setBrush(QPalette::Light, light); + + palette.setBrush(QPalette::Active, QPalette::Base, base); + palette.setBrush(QPalette::Inactive, QPalette::Base, base); + palette.setBrush(QPalette::Disabled, QPalette::Base, backGround); + + palette.setBrush(QPalette::Midlight, palette.mid().color().lighter(110)); + + palette.setBrush(QPalette::All, QPalette::Dark, dark); + palette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled); + + QColor button = backGround; + + palette.setBrush(QPalette::Button, button); + + QColor shadow = dark.darker(135); + palette.setBrush(QPalette::Shadow, shadow); + palette.setBrush(QPalette::Disabled, QPalette::Shadow, shadow.lighter(150)); + palette.setBrush(QPalette::HighlightedText, QColor(QRgb(0xffffffff))); + return palette; +} + +/*! + \reimp +*/ +void QCleanlooksStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const +{ + QColor button = option->palette.button().color(); + QColor dark; + QColor grooveColor; + QColor darkOutline; + dark.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*1.9)), + qMin(255, (int)(button.value()*0.7))); + grooveColor.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*2.6)), + qMin(255, (int)(button.value()*0.9))); + darkOutline.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*3.0)), + qMin(255, (int)(button.value()*0.6))); + + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), darkOutline); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), darkOutline); + } + QColor gripShadow = grooveColor.darker(110); + QColor buttonShadow = option->palette.button().color().darker(110); + + QColor gradientStartColor = option->palette.button().color().lighter(108); + QColor gradientStopColor = mergedColors(option->palette.button().color().darker(108), dark.lighter(150), 70); + + QColor highlightedGradientStartColor = option->palette.button().color(); + QColor highlightedGradientStopColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 85); + + QColor highlightedDarkInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 35); + QColor highlightedLightInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 58); + + QColor buttonShadowAlpha = option->palette.background().color().darker(105); + + QPalette palette = option->palette; + + switch (control) { +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QPixmap cache; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("spinbox"), spinBox, spinBox->rect.size()); + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(spinBox->rect.size()); + cache.fill(Qt::transparent); + QRect pixmapRect(0, 0, spinBox->rect.width(), spinBox->rect.height()); + QPainter cachePainter(&cache); + + bool isEnabled = (spinBox->state & State_Enabled); + //bool focus = isEnabled && (spinBox->state & State_HasFocus); + bool hover = isEnabled && (spinBox->state & State_MouseOver); + bool sunken = (spinBox->state & State_Sunken); + bool upIsActive = (spinBox->activeSubControls == SC_SpinBoxUp); + bool downIsActive = (spinBox->activeSubControls == SC_SpinBoxDown); + + QRect rect = pixmapRect; + QStyleOptionSpinBox spinBoxCopy = *spinBox; + spinBoxCopy.rect = pixmapRect; + QRect upRect = proxy()->subControlRect(CC_SpinBox, &spinBoxCopy, SC_SpinBoxUp, widget); + QRect downRect = proxy()->subControlRect(CC_SpinBox, &spinBoxCopy, SC_SpinBoxDown, widget); + + int fw = spinBoxCopy.frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, &spinBoxCopy, widget) : 0; + cachePainter.fillRect(rect.adjusted(1, qMax(fw - 1, 0), -1, -fw), + option->palette.base()); + + QRect r = rect.adjusted(0, 1, 0, -1); + if (spinBox->frame) { + + QColor topShadow = darkOutline; + topShadow.setAlpha(60); + cachePainter.setPen(topShadow); + + // antialias corners + const QPoint points[8] = { + QPoint(r.right(), r.top() + 1), + QPoint(r.right() - 1, r.top() ), + QPoint(r.right(), r.bottom() - 1), + QPoint(r.right() - 1, r.bottom() ), + QPoint(r.left() + 1, r.bottom()), + QPoint(r.left(), r.bottom() - 1), + QPoint(r.left() + 1, r.top()), + QPoint(r.left(), r.top() + 1) + }; + cachePainter.drawPoints(points, 8); + + // draw frame + topShadow.setAlpha(30); + cachePainter.setPen(topShadow); + cachePainter.drawLine(QPoint(r.left() + 2, r.top() - 1), QPoint(r.right() - 2, r.top() - 1)); + + cachePainter.setPen(QPen(option->palette.background().color(), 1)); + cachePainter.drawLine(QPoint(r.left() + 2, r.top() + 1), QPoint(r.right() - 2, r.top() + 1)); + QColor highlight = Qt::white; + highlight.setAlpha(130); + cachePainter.setPen(option->palette.base().color().darker(120)); + cachePainter.drawLine(QPoint(r.left() + 1, r.top() + 1), + QPoint(r.right() - 1, r.top() + 1)); + cachePainter.drawLine(QPoint(r.left() + 1, r.top() + 1), + QPoint(r.left() + 1, r.bottom() - 1)); + cachePainter.setPen(option->palette.base().color()); + cachePainter.drawLine(QPoint(r.right() - 1, r.top() + 1), + QPoint(r.right() - 1, r.bottom() - 1)); + cachePainter.drawLine(QPoint(r.left() + 1, r.bottom() - 1), + QPoint(r.right() - 1, r.bottom() - 1)); + cachePainter.setPen(highlight); + cachePainter.drawLine(QPoint(r.left() + 3, r.bottom() + 1), + QPoint(r.right() - 3, r.bottom() + 1)); + + cachePainter.setPen(QPen(darkOutline, 1)); + + // top and bottom lines + const QLine lines[4] = { + QLine(QPoint(r.left() + 2, r.bottom()), QPoint(r.right()- 2, r.bottom())), + QLine(QPoint(r.left() + 2, r.top()), QPoint(r.right() - 2, r.top())), + QLine(QPoint(r.right(), r.top() + 2), QPoint(r.right(), r.bottom() - 2)), + QLine(QPoint(r.left(), r.top() + 2), QPoint(r.left(), r.bottom() - 2)) + }; + cachePainter.drawLines(lines, 4); + } + + // gradients + qt_cleanlooks_draw_gradient(&cachePainter, upRect, + gradientStartColor.darker(106), + gradientStopColor, TopDown, option->palette.button()); + qt_cleanlooks_draw_gradient(&cachePainter, downRect.adjusted(0, 0, 0, 1), + gradientStartColor.darker(106), + gradientStopColor, TopDown, option->palette.button()); + if (isEnabled) { + if(upIsActive) { + if (sunken) { + cachePainter.fillRect(upRect.adjusted(1, 0, 0, 0), gradientStopColor.darker(110)); + } else if (hover) { + qt_cleanlooks_draw_gradient(&cachePainter, upRect.adjusted(1, 0, 0, 0), + gradientStartColor.lighter(110), + gradientStopColor.lighter(110), TopDown, option->palette.button()); + } + } + if(downIsActive) { + if (sunken) { + cachePainter.fillRect(downRect.adjusted(1, 0, 0, 1), gradientStopColor.darker(110)); + + } else if (hover) { + qt_cleanlooks_draw_gradient(&cachePainter, downRect.adjusted(1, 0, 0, 1), + gradientStartColor.lighter(110), + gradientStopColor.lighter(110), TopDown, option->palette.button()); + } + } + } + + if (spinBox->frame) { + // rounded corners + const QPoint points[4] = { + QPoint(r.left() + 1, r.bottom() - 1), + QPoint(r.left() + 1, r.top() + 1), + QPoint(r.right() - 1, r.bottom() - 1), + QPoint(r.right() - 1, r.top() + 1) + }; + cachePainter.drawPoints(points, 4); + + if (option->state & State_HasFocus) { + QColor darkoutline = option->palette.highlight().color().darker(150); + QColor innerline = mergedColors(option->palette.highlight().color(), Qt::white); + cachePainter.setPen(QPen(innerline, 0)); + if (spinBox->direction == Qt::LeftToRight) { + cachePainter.drawRect(rect.adjusted(1, 2, -3 -downRect.width(), -3)); + cachePainter.setPen(QPen(darkoutline, 0)); + const QLine lines[4] = { + QLine(QPoint(r.left() + 2, r.bottom()), QPoint(r.right()- downRect.width() - 1, r.bottom())), + QLine(QPoint(r.left() + 2, r.top()), QPoint(r.right() - downRect.width() - 1, r.top())), + QLine(QPoint(r.right() - downRect.width() - 1, r.top() + 1), QPoint(r.right()- downRect.width() - 1, r.bottom() - 1)), + QLine(QPoint(r.left(), r.top() + 2), QPoint(r.left(), r.bottom() - 2)) + }; + cachePainter.drawLines(lines, 4); + cachePainter.drawPoint(QPoint(r.left() + 1, r.bottom() - 1)); + cachePainter.drawPoint(QPoint(r.left() + 1, r.top() + 1)); + cachePainter.drawLine(QPoint(r.left(), r.top() + 2), QPoint(r.left(), r.bottom() - 2)); + } else { + cachePainter.drawRect(rect.adjusted(downRect.width() + 2, 2, -2, -3)); + cachePainter.setPen(QPen(darkoutline, 0)); + cachePainter.drawLine(QPoint(r.left() + downRect.width(), r.bottom()), QPoint(r.right()- 2 - 1, r.bottom())); + cachePainter.drawLine(QPoint(r.left() + downRect.width(), r.top()), QPoint(r.right() - 2 - 1, r.top())); + + cachePainter.drawLine(QPoint(r.right(), r.top() + 2), QPoint(r.right(), r.bottom() - 2)); + cachePainter.drawPoint(QPoint(r.right() - 1, r.bottom() - 1)); + cachePainter.drawPoint(QPoint(r.right() - 1, r.top() + 1)); + cachePainter.drawLine(QPoint(r.left() + downRect.width() + 1, r.top()), + QPoint(r.left() + downRect.width() + 1, r.bottom())); + } + } + } + + // outline the up/down buttons + cachePainter.setPen(darkOutline); + QColor light = option->palette.light().color().lighter(); + + if (spinBox->direction == Qt::RightToLeft) { + cachePainter.drawLine(upRect.right(), upRect.top() - 1, upRect.right(), downRect.bottom() + 1); + cachePainter.setPen(light); + cachePainter.drawLine(upRect.right() - 1, upRect.top() + 3, upRect.right() - 1, downRect.bottom() ); + } else { + cachePainter.drawLine(upRect.left(), upRect.top() - 1, upRect.left(), downRect.bottom() + 1); + cachePainter.setPen(light); + cachePainter.drawLine(upRect.left() + 1, upRect.top() , upRect.left() + 1, downRect.bottom() ); + } + if (upIsActive && sunken) { + cachePainter.setPen(gradientStopColor.darker(130)); + cachePainter.drawLine(upRect.left() + 1, upRect.top(), upRect.left() + 1, upRect.bottom()); + cachePainter.drawLine(upRect.left(), upRect.top() - 1, upRect.right(), upRect.top() - 1); + } else { + cachePainter.setPen(light); + cachePainter.drawLine(upRect.topLeft() + QPoint(1, -1), upRect.topRight() + QPoint(-1, -1)); + cachePainter.setPen(darkOutline); + cachePainter.drawLine(upRect.bottomLeft(), upRect.bottomRight()); + } + if (downIsActive && sunken) { + cachePainter.setPen(gradientStopColor.darker(130)); + cachePainter.drawLine(downRect.left() + 1, downRect.top(), downRect.left() + 1, downRect.bottom() + 1); + cachePainter.drawLine(downRect.left(), downRect.top(), downRect.right(), downRect.top()); + cachePainter.setPen(gradientStopColor.darker(110)); + cachePainter.drawLine(downRect.left(), downRect.bottom() + 1, downRect.right(), downRect.bottom() + 1); + } else { + cachePainter.setPen(light); + cachePainter.drawLine(downRect.topLeft() + QPoint(2,0), downRect.topRight()); + } + + if (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) { + int centerX = upRect.center().x(); + int centerY = upRect.center().y(); + cachePainter.setPen(spinBox->palette.foreground().color()); + + // plus/minus + if (spinBox->activeSubControls == SC_SpinBoxUp && sunken) { + cachePainter.drawLine(1 + centerX - 2, 1 + centerY, 1 + centerX + 2, 1 + centerY); + cachePainter.drawLine(1 + centerX, 1 + centerY - 2, 1 + centerX, 1 + centerY + 2); + } else { + cachePainter.drawLine(centerX - 2, centerY, centerX + 2, centerY); + cachePainter.drawLine(centerX, centerY - 2, centerX, centerY + 2); + } + + centerX = downRect.center().x(); + centerY = downRect.center().y(); + if (spinBox->activeSubControls == SC_SpinBoxDown && sunken) { + cachePainter.drawLine(1 + centerX - 2, 1 + centerY, 1 + centerX + 2, 1 + centerY); + } else { + cachePainter.drawLine(centerX - 2, centerY, centerX + 2, centerY); + } + } else if (spinBox->buttonSymbols == QAbstractSpinBox::UpDownArrows){ + // arrows + QImage upArrow(qt_spinbox_button_arrow_up); + upArrow.setColor(1, spinBox->palette.foreground().color().rgba()); + + cachePainter.drawImage(upRect.center().x() - upArrow.width() / 2, + upRect.center().y() - upArrow.height() / 2, + upArrow); + + QImage downArrow(qt_spinbox_button_arrow_down); + downArrow.setColor(1, spinBox->palette.foreground().color().rgba()); + + cachePainter.drawImage(downRect.center().x() - downArrow.width() / 2, + downRect.center().y() - downArrow.height() / 2 + 1, + downArrow); + } + + QColor disabledColor = option->palette.background().color(); + disabledColor.setAlpha(150); + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled)) + cachePainter.fillRect(upRect.adjusted(1, 0, 0, 0), disabledColor); + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + cachePainter.fillRect(downRect.adjusted(1, 0, 0, 0), disabledColor); + } + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(spinBox->rect.topLeft(), cache); + } + break; +#endif // QT_NO_SPINBOX + case CC_TitleBar: + painter->save(); + if (const QStyleOptionTitleBar *titleBar = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + const int buttonMargin = 5; + bool active = (titleBar->titleBarState & State_Active); + QRect fullRect = titleBar->rect; + QPalette palette = option->palette; + QColor highlight = option->palette.highlight().color(); + + QColor titleBarFrameBorder(active ? highlight.darker(180): dark.darker(110)); + QColor titleBarHighlight(active ? highlight.lighter(120): palette.background().color().lighter(120)); + QColor textColor(active ? 0xffffff : 0xff000000); + QColor textAlphaColor(active ? 0xffffff : 0xff000000 ); + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + QStyleOptionDockWidgetV2 dockwidget; + dockwidget.QStyleOption::operator=(*option); + proxy()->drawControl(CE_DockWidgetTitle, &dockwidget, painter, widget); + } else +#endif // QT3_SUPPORT + { + // Fill title bar gradient + QColor titlebarColor = QColor(active ? highlight: palette.background().color()); + QLinearGradient gradient(option->rect.center().x(), option->rect.top(), + option->rect.center().x(), option->rect.bottom()); + + gradient.setColorAt(0, titlebarColor.lighter(114)); + gradient.setColorAt(0.5, titlebarColor.lighter(102)); + gradient.setColorAt(0.51, titlebarColor.darker(104)); + gradient.setColorAt(1, titlebarColor); + painter->fillRect(option->rect.adjusted(1, 1, -1, 0), gradient); + + // Frame and rounded corners + painter->setPen(titleBarFrameBorder); + + // top outline + painter->drawLine(fullRect.left() + 5, fullRect.top(), fullRect.right() - 5, fullRect.top()); + painter->drawLine(fullRect.left(), fullRect.top() + 4, fullRect.left(), fullRect.bottom()); + const QPoint points[5] = { + QPoint(fullRect.left() + 4, fullRect.top() + 1), + QPoint(fullRect.left() + 3, fullRect.top() + 1), + QPoint(fullRect.left() + 2, fullRect.top() + 2), + QPoint(fullRect.left() + 1, fullRect.top() + 3), + QPoint(fullRect.left() + 1, fullRect.top() + 4) + }; + painter->drawPoints(points, 5); + + painter->drawLine(fullRect.right(), fullRect.top() + 4, fullRect.right(), fullRect.bottom()); + const QPoint points2[5] = { + QPoint(fullRect.right() - 3, fullRect.top() + 1), + QPoint(fullRect.right() - 4, fullRect.top() + 1), + QPoint(fullRect.right() - 2, fullRect.top() + 2), + QPoint(fullRect.right() - 1, fullRect.top() + 3), + QPoint(fullRect.right() - 1, fullRect.top() + 4) + }; + painter->drawPoints(points2, 5); + + // draw bottomline + painter->drawLine(fullRect.right(), fullRect.bottom(), fullRect.left(), fullRect.bottom()); + + // top highlight + painter->setPen(titleBarHighlight); + painter->drawLine(fullRect.left() + 6, fullRect.top() + 1, fullRect.right() - 6, fullRect.top() + 1); + } + // draw title + QRect textRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarLabel, widget); + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + painter->setPen(active? (titleBar->palette.text().color().lighter(120)) : + titleBar->palette.text().color() ); + // Note workspace also does elliding but it does not use the correct font + QString title = QFontMetrics(font).elidedText(titleBar->text, Qt::ElideRight, textRect.width() - 14); + painter->drawText(textRect.adjusted(1, 1, 1, 1), title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + painter->setPen(Qt::white); + if (active) + painter->drawText(textRect, title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + // min button + if ((titleBar->subControls & SC_TitleBarMinButton) && (titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint) && + !(titleBar->titleBarState& Qt::WindowMinimized)) { + QRect minButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMinButton, widget); + if (minButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, minButtonRect, hover, sunken); + QRect minButtonIconRect = minButtonRect.adjusted(buttonMargin ,buttonMargin , -buttonMargin, -buttonMargin); + painter->setPen(textColor); + painter->drawLine(minButtonIconRect.center().x() - 2, minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() + 3, minButtonIconRect.center().y() + 3); + painter->drawLine(minButtonIconRect.center().x() - 2, minButtonIconRect.center().y() + 4, + minButtonIconRect.center().x() + 3, minButtonIconRect.center().y() + 4); + painter->setPen(textAlphaColor); + painter->drawLine(minButtonIconRect.center().x() - 3, minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() - 3, minButtonIconRect.center().y() + 4); + painter->drawLine(minButtonIconRect.center().x() + 4, minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() + 4, minButtonIconRect.center().y() + 4); + } + } + // max button + if ((titleBar->subControls & SC_TitleBarMaxButton) && (titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint) && + !(titleBar->titleBarState & Qt::WindowMaximized)) { + QRect maxButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMaxButton, widget); + if (maxButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, maxButtonRect, hover, sunken); + + QRect maxButtonIconRect = maxButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin); + + painter->setPen(textColor); + painter->drawRect(maxButtonIconRect.adjusted(0, 0, -1, -1)); + painter->drawLine(maxButtonIconRect.left() + 1, maxButtonIconRect.top() + 1, + maxButtonIconRect.right() - 1, maxButtonIconRect.top() + 1); + painter->setPen(textAlphaColor); + const QPoint points[4] = { + maxButtonIconRect.topLeft(), + maxButtonIconRect.topRight(), + maxButtonIconRect.bottomLeft(), + maxButtonIconRect.bottomRight() + }; + painter->drawPoints(points, 4); + } + } + + // close button + if ((titleBar->subControls & SC_TitleBarCloseButton) && (titleBar->titleBarFlags & Qt::WindowSystemMenuHint)) { + QRect closeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarCloseButton, widget); + if (closeButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, closeButtonRect, hover, sunken); + QRect closeIconRect = closeButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin); + painter->setPen(textAlphaColor); + const QLine lines[4] = { + QLine(closeIconRect.left() + 1, closeIconRect.top(), + closeIconRect.right(), closeIconRect.bottom() - 1), + QLine(closeIconRect.left(), closeIconRect.top() + 1, + closeIconRect.right() - 1, closeIconRect.bottom()), + QLine(closeIconRect.right() - 1, closeIconRect.top(), + closeIconRect.left(), closeIconRect.bottom() - 1), + QLine(closeIconRect.right(), closeIconRect.top() + 1, + closeIconRect.left() + 1, closeIconRect.bottom()) + }; + painter->drawLines(lines, 4); + const QPoint points[4] = { + closeIconRect.topLeft(), + closeIconRect.topRight(), + closeIconRect.bottomLeft(), + closeIconRect.bottomRight() + }; + painter->drawPoints(points, 4); + + painter->setPen(textColor); + painter->drawLine(closeIconRect.left() + 1, closeIconRect.top() + 1, + closeIconRect.right() - 1, closeIconRect.bottom() - 1); + painter->drawLine(closeIconRect.left() + 1, closeIconRect.bottom() - 1, + closeIconRect.right() - 1, closeIconRect.top() + 1); + } + } + + // normalize button + if ((titleBar->subControls & SC_TitleBarNormalButton) && + (((titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint) && + (titleBar->titleBarState & Qt::WindowMinimized)) || + ((titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint) && + (titleBar->titleBarState & Qt::WindowMaximized)))) { + QRect normalButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarNormalButton, widget); + if (normalButtonRect.isValid()) { + + bool hover = (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_Sunken); + QRect normalButtonIconRect = normalButtonRect.adjusted(buttonMargin, buttonMargin, -buttonMargin, -buttonMargin); + qt_cleanlooks_draw_mdibutton(painter, titleBar, normalButtonRect, hover, sunken); + + QRect frontWindowRect = normalButtonIconRect.adjusted(0, 3, -3, 0); + painter->setPen(textColor); + painter->drawRect(frontWindowRect.adjusted(0, 0, -1, -1)); + painter->drawLine(frontWindowRect.left() + 1, frontWindowRect.top() + 1, + frontWindowRect.right() - 1, frontWindowRect.top() + 1); + painter->setPen(textAlphaColor); + const QPoint points[4] = { + frontWindowRect.topLeft(), + frontWindowRect.topRight(), + frontWindowRect.bottomLeft(), + frontWindowRect.bottomRight() + }; + painter->drawPoints(points, 4); + + QRect backWindowRect = normalButtonIconRect.adjusted(3, 0, 0, -3); + QRegion clipRegion = backWindowRect; + clipRegion -= frontWindowRect; + painter->save(); + painter->setClipRegion(clipRegion); + painter->setPen(textColor); + painter->drawRect(backWindowRect.adjusted(0, 0, -1, -1)); + painter->drawLine(backWindowRect.left() + 1, backWindowRect.top() + 1, + backWindowRect.right() - 1, backWindowRect.top() + 1); + painter->setPen(textAlphaColor); + const QPoint points2[4] = { + backWindowRect.topLeft(), + backWindowRect.topRight(), + backWindowRect.bottomLeft(), + backWindowRect.bottomRight() + }; + painter->drawPoints(points2, 4); + painter->restore(); + } + } + + // context help button + if (titleBar->subControls & SC_TitleBarContextHelpButton + && (titleBar->titleBarFlags & Qt::WindowContextHelpButtonHint)) { + QRect contextHelpButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarContextHelpButton, widget); + if (contextHelpButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, contextHelpButtonRect, hover, sunken); + + QColor blend; + QImage image(qt_titlebar_context_help); + QColor alpha = textColor; + alpha.setAlpha(128); + image.setColor(1, textColor.rgba()); + image.setColor(2, alpha.rgba()); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->drawImage(contextHelpButtonRect.adjusted(4, 4, -4, -4), image); + } + } + + // shade button + if (titleBar->subControls & SC_TitleBarShadeButton && (titleBar->titleBarFlags & Qt::WindowShadeButtonHint)) { + QRect shadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarShadeButton, widget); + if (shadeButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, shadeButtonRect, hover, sunken); + QImage image(qt_scrollbar_button_arrow_up); + image.setColor(1, textColor.rgba()); + painter->drawImage(shadeButtonRect.adjusted(5, 7, -5, -7), image); + } + } + + // unshade button + if (titleBar->subControls & SC_TitleBarUnshadeButton && (titleBar->titleBarFlags & Qt::WindowShadeButtonHint)) { + QRect unshadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarUnshadeButton, widget); + if (unshadeButtonRect.isValid()) { + bool hover = (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_Sunken); + qt_cleanlooks_draw_mdibutton(painter, titleBar, unshadeButtonRect, hover, sunken); + QImage image(qt_scrollbar_button_arrow_down); + image.setColor(1, textColor.rgba()); + painter->drawImage(unshadeButtonRect.adjusted(5, 7, -5, -7), image); + } + } + + if ((titleBar->subControls & SC_TitleBarSysMenu) && (titleBar->titleBarFlags & Qt::WindowSystemMenuHint)) { + 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(0); + tool.palette = titleBar->palette; + QPixmap pm = standardIcon(SP_TitleBarMenuButton, &tool, widget).pixmap(16, 16); + tool.rect = iconRect; + painter->save(); + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pm); + painter->restore(); + } + } + } + } + painter->restore(); + break; +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + painter->save(); + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + bool isEnabled = scrollBar->state & State_Enabled; + bool reverse = scrollBar->direction == Qt::RightToLeft; + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool sunken = scrollBar->state & State_Sunken; + + painter->fillRect(option->rect, option->palette.background()); + + QRect scrollBarSubLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSubLine, widget); + QRect scrollBarAddLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarAddLine, widget); + QRect scrollBarSlider = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSlider, widget); + QRect grooveRect = proxy()->subControlRect(control, scrollBar, SC_ScrollBarGroove, widget); + + // paint groove + if (scrollBar->subControls & SC_ScrollBarGroove) { + painter->setBrush(grooveColor); + painter->setPen(Qt::NoPen); + if (horizontal) { + painter->drawRect(grooveRect); + painter->setPen(darkOutline); + painter->drawLine(grooveRect.topLeft(), grooveRect.topRight()); + painter->drawLine(grooveRect.bottomLeft(), grooveRect.bottomRight()); + } else { + painter->drawRect(grooveRect); + painter->setPen(darkOutline); + painter->drawLine(grooveRect.topLeft(), grooveRect.bottomLeft()); + painter->drawLine(grooveRect.topRight(), grooveRect.bottomRight()); + } + } + //paint slider + if (scrollBar->subControls & SC_ScrollBarSlider) { + QRect pixmapRect = scrollBarSlider; + if (horizontal) + pixmapRect.adjust(-1, 0, 0, -1); + else + pixmapRect.adjust(0, -1, -1, 0); + + if (isEnabled) { + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + if (!horizontal) + gradient = QLinearGradient(pixmapRect.left(), pixmapRect.center().y(), + pixmapRect.right(), pixmapRect.center().y()); + + if (option->palette.button().gradient()) { + gradient.setStops(option->palette.button().gradient()->stops()); + } else { + if (sunken || (option->state & State_MouseOver && + (scrollBar->activeSubControls & SC_ScrollBarSlider))) { + gradient.setColorAt(0, gradientStartColor.lighter(110)); + gradient.setColorAt(1, gradientStopColor.lighter(110)); + } else { + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + } + } + painter->setPen(QPen(darkOutline, 0)); + painter->setBrush(gradient); + painter->drawRect(pixmapRect); + + + //calculate offsets used by highlight and shadow + int yoffset, xoffset; + if (option->state & State_Horizontal) { + xoffset = 0; + yoffset = 1; + } else { + xoffset = 1; + yoffset = 0; + } + //draw slider highlights + painter->setPen(QPen(gradientStopColor, 0)); + painter->drawLine(scrollBarSlider.left() + xoffset, + scrollBarSlider.bottom() - yoffset, + scrollBarSlider.right() - xoffset, + scrollBarSlider.bottom() - yoffset); + painter->drawLine(scrollBarSlider.right() - xoffset, + scrollBarSlider.top() + yoffset, + scrollBarSlider.right() - xoffset, + scrollBarSlider.bottom() - yoffset); + + //draw slider shadow + painter->setPen(QPen(gradientStartColor, 0)); + painter->drawLine(scrollBarSlider.left() + xoffset, + scrollBarSlider.top() + yoffset, + scrollBarSlider.right() - xoffset, + scrollBarSlider.top() + yoffset); + painter->drawLine(scrollBarSlider.left() + xoffset, + scrollBarSlider.top() + yoffset, + scrollBarSlider.left() + xoffset, + scrollBarSlider.bottom() - yoffset); + } else { + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + if (!horizontal) { + gradient = QLinearGradient(pixmapRect.left(), pixmapRect.center().y(), + pixmapRect.right(), pixmapRect.center().y()); + } + if (sunken) { + gradient.setColorAt(0, gradientStartColor.lighter(110)); + gradient.setColorAt(1, gradientStopColor.lighter(110)); + } else { + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + } + painter->setPen(darkOutline); + painter->setBrush(gradient); + painter->drawRect(pixmapRect); + } + int gripMargin = 4; + //draw grips + if (horizontal) { + for (int i = -3; i< 6 ; i += 3) { + painter->setPen(QPen(gripShadow, 1)); + painter->drawLine( + QPoint(scrollBarSlider.center().x() + i , + scrollBarSlider.top() + gripMargin), + QPoint(scrollBarSlider.center().x() + i, + scrollBarSlider.bottom() - gripMargin)); + painter->setPen(QPen(palette.light(), 1)); + painter->drawLine( + QPoint(scrollBarSlider.center().x() + i + 1, + scrollBarSlider.top() + gripMargin ), + QPoint(scrollBarSlider.center().x() + i + 1, + scrollBarSlider.bottom() - gripMargin)); + } + } else { + for (int i = -3; i < 6 ; i += 3) { + painter->setPen(QPen(gripShadow, 1)); + painter->drawLine( + QPoint(scrollBarSlider.left() + gripMargin , + scrollBarSlider.center().y()+ i), + QPoint(scrollBarSlider.right() - gripMargin, + scrollBarSlider.center().y()+ i)); + painter->setPen(QPen(palette.light(), 1)); + painter->drawLine( + QPoint(scrollBarSlider.left() + gripMargin, + scrollBarSlider.center().y() + 1 + i), + QPoint(scrollBarSlider.right() - gripMargin, + scrollBarSlider.center().y() + 1 + i)); + } + } + } + + // The SubLine (up/left) buttons + if (scrollBar->subControls & SC_ScrollBarSubLine) { + //int scrollBarExtent = proxy()->pixelMetric(PM_ScrollBarExtent, option, widget); + QRect pixmapRect = scrollBarSubLine; + if (isEnabled ) { + QRect fillRect = pixmapRect.adjusted(1, 1, -1, -1); + // Gradients + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) { + qt_cleanlooks_draw_gradient(painter, + QRect(fillRect), + gradientStopColor.darker(120), + gradientStopColor.darker(120), + horizontal ? TopDown : FromLeft, option->palette.button()); + } else { + qt_cleanlooks_draw_gradient(painter, + QRect(fillRect), + gradientStartColor.lighter(105), + gradientStopColor, + horizontal ? TopDown : FromLeft, option->palette.button()); + } + } + // Details + QImage subButton; + if (horizontal) { + subButton = QImage(reverse ? qt_scrollbar_button_right : qt_scrollbar_button_left); + } else { + subButton = QImage(qt_scrollbar_button_up); + } + subButton.setColor(1, alphaCornerColor.rgba()); + subButton.setColor(2, darkOutline.rgba()); + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) { + subButton.setColor(3, gradientStopColor.darker(140).rgba()); + subButton.setColor(4, gradientStopColor.darker(120).rgba()); + } else { + subButton.setColor(3, gradientStartColor.lighter(105).rgba()); + subButton.setColor(4, gradientStopColor.rgba()); + } + subButton.setColor(5, scrollBar->palette.text().color().rgba()); + painter->drawImage(pixmapRect, subButton); + + // Arrows + PrimitiveElement arrow; + if (option->state & State_Horizontal) + arrow = option->direction == Qt::LeftToRight ? PE_IndicatorArrowLeft: PE_IndicatorArrowRight; + else + arrow = PE_IndicatorArrowUp; + QStyleOption arrowOpt = *option; + arrowOpt.rect = scrollBarSubLine.adjusted(3, 3, -2, -2); + proxy()->drawPrimitive(arrow, &arrowOpt, painter, widget); + + + // The AddLine (down/right) button + if (scrollBar->subControls & SC_ScrollBarAddLine) { + QString addLinePixmapName = QStyleHelper::uniqueName(QLatin1String("scrollbar_addline"), option, QSize(16, 16)); + QRect pixmapRect = scrollBarAddLine; + if (isEnabled) { + QRect fillRect = pixmapRect.adjusted(1, 1, -1, -1); + // Gradients + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) { + qt_cleanlooks_draw_gradient(painter, + fillRect, + gradientStopColor.darker(120), + gradientStopColor.darker(120), + horizontal ? TopDown: FromLeft, option->palette.button()); + } else { + qt_cleanlooks_draw_gradient(painter, + fillRect, + gradientStartColor.lighter(105), + gradientStopColor, + horizontal ? TopDown : FromLeft, option->palette.button()); + } + } + // Details + QImage addButton; + if (horizontal) { + addButton = QImage(reverse ? qt_scrollbar_button_left : qt_scrollbar_button_right); + } else { + addButton = QImage(qt_scrollbar_button_down); + } + addButton.setColor(1, alphaCornerColor.rgba()); + addButton.setColor(2, darkOutline.rgba()); + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) { + addButton.setColor(3, gradientStopColor.darker(140).rgba()); + addButton.setColor(4, gradientStopColor.darker(120).rgba()); + } else { + addButton.setColor(3, gradientStartColor.lighter(105).rgba()); + addButton.setColor(4, gradientStopColor.rgba()); + } + addButton.setColor(5, scrollBar->palette.text().color().rgba()); + painter->drawImage(pixmapRect, addButton); + + PrimitiveElement arrow; + if (option->state & State_Horizontal) + arrow = option->direction == Qt::LeftToRight ? PE_IndicatorArrowRight : PE_IndicatorArrowLeft; + else + arrow = PE_IndicatorArrowDown; + + QStyleOption arrowOpt = *option; + arrowOpt.rect = scrollBarAddLine.adjusted(3, 3, -2, -2); + proxy()->drawPrimitive(arrow, &arrowOpt, painter, widget); + } + } + } + painter->restore(); + break;; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + painter->save(); + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + bool sunken = comboBox->state & State_On; // play dead, if combobox has no items + bool isEnabled = (comboBox->state & State_Enabled); + bool focus = isEnabled && (comboBox->state & State_HasFocus); + QPixmap cache; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("combobox"), option, comboBox->rect.size()); + if (sunken) + pixmapName += QLatin1String("-sunken"); + if (comboBox->editable) + pixmapName += QLatin1String("-editable"); + if (isEnabled) + pixmapName += QLatin1String("-enabled"); + + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(comboBox->rect.size()); + cache.fill(Qt::transparent); + QPainter cachePainter(&cache); + QRect pixmapRect(0, 0, comboBox->rect.width(), comboBox->rect.height()); + QStyleOptionComboBox comboBoxCopy = *comboBox; + comboBoxCopy.rect = pixmapRect; + + QRect rect = pixmapRect; + QRect downArrowRect = proxy()->subControlRect(CC_ComboBox, &comboBoxCopy, + SC_ComboBoxArrow, widget); + QRect editRect = proxy()->subControlRect(CC_ComboBox, &comboBoxCopy, + SC_ComboBoxEditField, widget); + // Draw a push button + if (comboBox->editable) { + QStyleOptionFrame buttonOption; + buttonOption.QStyleOption::operator=(*comboBox); + buttonOption.rect = rect; + buttonOption.state = comboBox->state & (State_Enabled | State_MouseOver); + + if (sunken) { + buttonOption.state |= State_Sunken; + buttonOption.state &= ~State_MouseOver; + } + + proxy()->drawPrimitive(PE_PanelButtonCommand, &buttonOption, &cachePainter, widget); + + //remove shadow from left side of edit field when pressed: + if (comboBox->direction != Qt::RightToLeft) + cachePainter.fillRect(editRect.left() - 1, editRect.top() + 1, editRect.left(), + editRect.bottom() - 3, option->palette.base()); + + cachePainter.setPen(dark.lighter(110)); + if (!sunken) { + int borderSize = 2; + if (comboBox->direction == Qt::RightToLeft) { + cachePainter.drawLine(QPoint(downArrowRect.right() - 1, downArrowRect.top() + borderSize ), + QPoint(downArrowRect.right() - 1, downArrowRect.bottom() - borderSize)); + cachePainter.setPen(option->palette.light().color()); + cachePainter.drawLine(QPoint(downArrowRect.right(), downArrowRect.top() + borderSize), + QPoint(downArrowRect.right(), downArrowRect.bottom() - borderSize)); + } else { + cachePainter.drawLine(QPoint(downArrowRect.left() , downArrowRect.top() + borderSize), + QPoint(downArrowRect.left() , downArrowRect.bottom() - borderSize)); + cachePainter.setPen(option->palette.light().color()); + cachePainter.drawLine(QPoint(downArrowRect.left() + 1, downArrowRect.top() + borderSize), + QPoint(downArrowRect.left() + 1, downArrowRect.bottom() - borderSize)); + } + } else { + if (comboBox->direction == Qt::RightToLeft) { + cachePainter.drawLine(QPoint(downArrowRect.right(), downArrowRect.top() + 2), + QPoint(downArrowRect.right(), downArrowRect.bottom() - 2)); + + } else { + cachePainter.drawLine(QPoint(downArrowRect.left(), downArrowRect.top() + 2), + QPoint(downArrowRect.left(), downArrowRect.bottom() - 2)); + } + } + } else { + QStyleOptionButton buttonOption; + buttonOption.QStyleOption::operator=(*comboBox); + buttonOption.rect = rect; + buttonOption.state = comboBox->state & (State_Enabled | State_MouseOver); + if (sunken) { + buttonOption.state |= State_Sunken; + buttonOption.state &= ~State_MouseOver; + } + proxy()->drawPrimitive(PE_PanelButtonCommand, &buttonOption, &cachePainter, widget); + + cachePainter.setPen(buttonShadow.darker(102)); + int borderSize = 4; + + if (!sunken) { + if (comboBox->direction == Qt::RightToLeft) { + cachePainter.drawLine(QPoint(downArrowRect.right() + 1, downArrowRect.top() + borderSize), + QPoint(downArrowRect.right() + 1, downArrowRect.bottom() - borderSize)); + cachePainter.setPen(option->palette.light().color()); + cachePainter.drawLine(QPoint(downArrowRect.right(), downArrowRect.top() + borderSize), + QPoint(downArrowRect.right(), downArrowRect.bottom() - borderSize)); + } else { + cachePainter.drawLine(QPoint(downArrowRect.left() - 1, downArrowRect.top() + borderSize), + QPoint(downArrowRect.left() - 1, downArrowRect.bottom() - borderSize)); + cachePainter.setPen(option->palette.light().color()); + cachePainter.drawLine(QPoint(downArrowRect.left() , downArrowRect.top() + borderSize), + QPoint(downArrowRect.left() , downArrowRect.bottom() - borderSize)); + } + } else { + cachePainter.setPen(dark.lighter(110)); + if (comboBox->direction == Qt::RightToLeft) { + cachePainter.drawLine(QPoint(downArrowRect.right() + 1, downArrowRect.top() + borderSize), + QPoint(downArrowRect.right() + 1, downArrowRect.bottom() - borderSize)); + + } else { + cachePainter.drawLine(QPoint(downArrowRect.left() - 1, downArrowRect.top() + borderSize), + QPoint(downArrowRect.left() - 1, downArrowRect.bottom() - borderSize)); + } + } + } + + + if (comboBox->subControls & SC_ComboBoxArrow) { + if (comboBox->editable) { + // Draw the down arrow + QImage downArrow(qt_cleanlooks_arrow_down_xpm); + downArrow.setColor(1, comboBox->palette.foreground().color().rgba()); + cachePainter.drawImage(downArrowRect.center().x() - downArrow.width() / 2, + downArrowRect.center().y() - downArrow.height() / 2 + 1, downArrow); + } else { + // Draw the up/down arrow + QImage upArrow(qt_scrollbar_button_arrow_up); + upArrow.setColor(1, comboBox->palette.foreground().color().rgba()); + QImage downArrow(qt_scrollbar_button_arrow_down); + downArrow.setColor(1, comboBox->palette.foreground().color().rgba()); + cachePainter.drawImage(downArrowRect.center().x() - downArrow.width() / 2, + downArrowRect.center().y() - upArrow.height() - 1 , upArrow); + cachePainter.drawImage(downArrowRect.center().x() - downArrow.width() / 2, + downArrowRect.center().y() + 2, downArrow); + } + } + // Draw the focus rect + if (focus && !comboBox->editable + && ((option->state & State_KeyboardFocusChange) || styleHint(SH_UnderlineShortcut, option, widget))) { + QStyleOptionFocusRect focus; + focus.rect = proxy()->subControlRect(CC_ComboBox, &comboBoxCopy, SC_ComboBoxEditField, widget) + .adjusted(0, 2, option->direction == Qt::RightToLeft ? 1 : -1, -2); + proxy()->drawPrimitive(PE_FrameFocusRect, &focus, &cachePainter, widget); + } + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(comboBox->rect.topLeft(), cache); + } + painter->restore(); + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + painter->save(); + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + QRect textRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxLabel, widget); + QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxCheckBox, widget); + bool flat = groupBox->features & QStyleOptionFrameV2::Flat; + + if(!flat) { + if (groupBox->subControls & QStyle::SC_GroupBoxFrame) { + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget); + + painter->save(); + QRegion region(groupBox->rect); + bool ltr = groupBox->direction == Qt::LeftToRight; + region -= checkBoxRect.united(textRect).adjusted(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0); + if (!groupBox->text.isEmpty() || groupBox->subControls & SC_GroupBoxCheckBox) + painter->setClipRegion(region); + frame.palette.setBrush(QPalette::Dark, option->palette.mid().color().lighter(110)); + proxy()->drawPrimitive(PE_FrameGroupBox, &frame, painter); + painter->restore(); + } + } + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + if (!groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) + painter->setPen(textColor); + int alignment = int(groupBox->textAlignment); + if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + if (flat) { + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + if (groupBox->subControls & SC_GroupBoxCheckBox) { + textRect.adjust(checkBoxRect.right() + 4, 0, checkBoxRect.right() + 4, 0); + } + } + painter->drawText(textRect, Qt::TextShowMnemonic | Qt::AlignLeft| alignment, groupBox->text); + } + } + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + } + painter->restore(); + break; +#endif // QT_NO_GROUPBOX +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect groove = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); + QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + QRect ticks = proxy()->subControlRect(CC_Slider, option, SC_SliderTickmarks, widget); + + bool horizontal = slider->orientation == Qt::Horizontal; + bool ticksAbove = slider->tickPosition & QSlider::TicksAbove; + bool ticksBelow = slider->tickPosition & QSlider::TicksBelow; + QColor activeHighlight = option->palette.color(QPalette::Normal, QPalette::Highlight); + QPixmap cache; + + QBrush oldBrush = painter->brush(); + QPen oldPen = painter->pen(); + + QColor shadowAlpha(Qt::black); + shadowAlpha.setAlpha(10); + QColor highlightAlpha(Qt::white); + highlightAlpha.setAlpha(80); + + if ((option->subControls & SC_SliderGroove) && groove.isValid()) { + QString groovePixmapName = QStyleHelper::uniqueName(QLatin1String("slider_groove"), option, groove.size()); + QRect pixmapRect(0, 0, groove.width(), groove.height()); + + // draw background groove + if (!QPixmapCache::find(groovePixmapName, cache)) { + cache = QPixmap(pixmapRect.size()); + cache.fill(Qt::transparent); + QPainter groovePainter(&cache); + + groovePainter.setPen(shadowAlpha); + groovePainter.drawLine(1, 0, groove.width(), 0); + groovePainter.drawLine(0, 0, 0, groove.height() - 1); + + groovePainter.setPen(highlightAlpha); + groovePainter.drawLine(1, groove.height() - 1, groove.width() - 1, groove.height() - 1); + groovePainter.drawLine(groove.width() - 1, 1, groove.width() - 1, groove.height() - 1); + QLinearGradient gradient; + if (horizontal) { + gradient.setStart(pixmapRect.center().x(), pixmapRect.top()); + gradient.setFinalStop(pixmapRect.center().x(), pixmapRect.bottom()); + } + else { + gradient.setStart(pixmapRect.left(), pixmapRect.center().y()); + gradient.setFinalStop(pixmapRect.right(), pixmapRect.center().y()); + } + groovePainter.setPen(QPen(darkOutline.darker(110), 0)); + gradient.setColorAt(0, grooveColor.darker(110));//dark.lighter(120)); + gradient.setColorAt(1, grooveColor.lighter(110));//palette.button().color().darker(115)); + groovePainter.setBrush(gradient); + groovePainter.drawRect(pixmapRect.adjusted(1, 1, -2, -2)); + groovePainter.end(); + QPixmapCache::insert(groovePixmapName, cache); + } + painter->drawPixmap(groove.topLeft(), cache); + + // draw blue groove highlight + QRect clipRect; + groovePixmapName += QLatin1String("_blue"); + if (!QPixmapCache::find(groovePixmapName, cache)) { + cache = QPixmap(pixmapRect.size()); + cache.fill(Qt::transparent); + QPainter groovePainter(&cache); + QLinearGradient gradient; + if (horizontal) { + gradient.setStart(pixmapRect.center().x(), pixmapRect.top()); + gradient.setFinalStop(pixmapRect.center().x(), pixmapRect.bottom()); + } + else { + gradient.setStart(pixmapRect.left(), pixmapRect.center().y()); + gradient.setFinalStop(pixmapRect.right(), pixmapRect.center().y()); + } + groovePainter.setPen(QPen(activeHighlight.darker(150), 0)); + gradient.setColorAt(0, activeHighlight.darker(120)); + gradient.setColorAt(1, activeHighlight.lighter(160)); + groovePainter.setBrush(gradient); + groovePainter.drawRect(pixmapRect.adjusted(1, 1, -2, -2)); + groovePainter.end(); + QPixmapCache::insert(groovePixmapName, cache); + } + if (horizontal) { + if (slider->upsideDown) + clipRect = QRect(handle.right(), groove.top(), groove.right() - handle.right(), groove.height()); + else + clipRect = QRect(groove.left(), groove.top(), handle.left(), groove.height()); + } else { + if (slider->upsideDown) + clipRect = QRect(groove.left(), handle.bottom(), groove.width(), groove.height() - handle.bottom()); + else + clipRect = QRect(groove.left(), groove.top(), groove.width(), handle.top() - groove.top()); + } + painter->save(); + painter->setClipRect(clipRect.adjusted(0, 0, 1, 1)); + painter->drawPixmap(groove.topLeft(), cache); + painter->restore(); + } + + // draw handle + if ((option->subControls & SC_SliderHandle) ) { + QString handlePixmapName = QStyleHelper::uniqueName(QLatin1String("slider_handle"), option, handle.size()); + if (!QPixmapCache::find(handlePixmapName, cache)) { + cache = QPixmap(handle.size()); + cache.fill(Qt::transparent); + QRect pixmapRect(0, 0, handle.width(), handle.height()); + QPainter handlePainter(&cache); + + QColor highlightedGradientStartColor = option->palette.button().color(); + QColor highlightedGradientStopColor = option->palette.light().color(); + QColor gradientStartColor = mergedColors(option->palette.button().color().lighter(155), + dark.lighter(155), 50); + QColor gradientStopColor = gradientStartColor.darker(108); + QRect gradRect = pixmapRect.adjusted(2, 2, -2, -2); + + QColor gradientBgStartColor = gradientStartColor; + QColor gradientBgStopColor = gradientStopColor; + + QColor outline = option->state & State_Enabled ? dark : dark.lighter(130); + if (option->state & State_Enabled && option->activeSubControls & SC_SliderHandle) { + gradientBgStartColor = option->palette.highlight().color().lighter(180); + gradientBgStopColor = option->palette.highlight().color().lighter(110); + outline = option->palette.highlight().color().darker(130); + } + + // gradient fill + QRect innerBorder = gradRect; + QRect r = pixmapRect.adjusted(1, 1, -1, -1); + + qt_cleanlooks_draw_gradient(&handlePainter, gradRect, + gradientBgStartColor, + gradientBgStopColor, + horizontal ? TopDown : FromLeft, option->palette.button()); + + handlePainter.setPen(QPen(outline.darker(110), 1)); + handlePainter.drawLine(QPoint(r.left(), r.top() + 3), QPoint(r.left(), r.bottom() - 3)); + handlePainter.drawLine(QPoint(r.right(), r.top() + 3), QPoint(r.right(), r.bottom() - 3)); + handlePainter.drawLine(QPoint(r.left() + 3, r.bottom()), QPoint(r.right() - 3, r.bottom())); + + handlePainter.save(); + handlePainter.setRenderHint(QPainter::Antialiasing); + handlePainter.translate(0.5, 0.5); + const QLine lines[4] = { + QLine(QPoint(r.left(), r.bottom() - 2), QPoint(r.left() + 2, r.bottom())), + QLine(QPoint(r.left(), r.top() + 2), QPoint(r.left() + 2, r.top())), + QLine(QPoint(r.right(), r.bottom() - 2), QPoint(r.right() - 2, r.bottom())), + QLine(QPoint(r.right(), r.top() + 2), QPoint(r.right() - 2, r.top())) + }; + handlePainter.drawLines(lines, 4); + handlePainter.restore();; + handlePainter.setPen(QPen(outline.darker(130), 1)); + handlePainter.drawLine(QPoint(r.left() + 3, r.top()), QPoint(r.right() - 3, r.top())); + QColor cornerAlpha = outline.darker(120); + cornerAlpha.setAlpha(80); + + handlePainter.setPen(cornerAlpha); + if (horizontal) { + handlePainter.drawLine(QPoint(r.left() + 6, r.top()), QPoint(r.left() + 6, r.bottom())); + handlePainter.drawLine(QPoint(r.right() - 6, r.top()), QPoint(r.right() - 6, r.bottom())); + } else { + handlePainter.drawLine(QPoint(r.left(), r.top() + 6), QPoint(r.right(), r.top() + 6)); + handlePainter.drawLine(QPoint(r.left(), r.bottom() - 6), QPoint(r.right(), r.bottom() - 6)); + } + + //handle shadow + handlePainter.setPen(shadowAlpha); + handlePainter.drawLine(QPoint(r.left() + 2, r.bottom() + 1), QPoint(r.right() - 2, r.bottom() + 1)); + handlePainter.drawLine(QPoint(r.right() + 1, r.bottom() - 3), QPoint(r.right() + 1, r.top() + 4)); + handlePainter.drawLine(QPoint(r.right() - 1, r.bottom()), QPoint(r.right() + 1, r.bottom() - 2)); + + qt_cleanlooks_draw_gradient(&handlePainter, horizontal ? + gradRect.adjusted(6, 0, -6, 0) : gradRect.adjusted(0, 6, 0, -6), + gradientStartColor, + gradientStopColor.darker(106), + horizontal ? TopDown : FromLeft, + option->palette.button()); + + //draw grips + for (int i = -3; i< 6 ; i += 3) { + for (int j = -3; j< 6 ; j += 3) { + handlePainter.fillRect(r.center().x() + i, r.center().y() + j, 2, 2, highlightAlpha); + handlePainter.setPen(gripShadow); + handlePainter.drawPoint(r.center().x() + i, r.center().y() + j ); + } + } + handlePainter.end(); + QPixmapCache::insert(handlePixmapName, cache); + } + + painter->drawPixmap(handle.topLeft(), cache); + + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = slider->rect; + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + if (option->subControls & SC_SliderTickmarks) { + painter->setPen(darkOutline); + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, 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 <= 0) + interval = 1; + + int v = slider->minimum; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int pos = sliderPositionFromValue(slider->minimum, slider->maximum, + v_, (horizontal + ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown) + len / 2; + int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0); + + if (horizontal) { + if (ticksAbove) { + painter->drawLine(pos, slider->rect.top() + extra, + pos, slider->rect.top() + tickSize); + } + if (ticksBelow) { + painter->drawLine(pos, slider->rect.bottom() - extra, + pos, slider->rect.bottom() - tickSize); + } + } else { + if (ticksAbove) { + painter->drawLine(slider->rect.left() + extra, pos, + slider->rect.left() + tickSize, pos); + } + if (ticksBelow) { + painter->drawLine(slider->rect.right() - extra, pos, + slider->rect.right() - tickSize, pos); + } + } + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + } + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(option)) + QStyleHelper::drawDial(dial, painter); + break; +#endif // QT_NO_DIAL + default: + QWindowsStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +/*! + \reimp +*/ +int QCleanlooksStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + int ret = -1; + switch (metric) { + case PM_ToolTipLabelFrameWidth: + ret = 2; + break; + case PM_ButtonDefaultIndicator: + ret = 0; + break; + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 0; + break; + case PM_MessageBoxIconSize: + ret = 48; + break; + case PM_ListViewIconSize: + ret = 24; + break; + case PM_DialogButtonsSeparator: + case PM_SplitterWidth: + ret = 6; + break; + case PM_ScrollBarSliderMin: + ret = 26; + break; + case PM_MenuPanelWidth: //menu framewidth + ret = 2; + break; + case PM_TitleBarHeight: + ret = 24; + break; + case PM_ScrollBarExtent: + ret = 15; + break; + case PM_SliderThickness: + ret = 15; + break; + case PM_SliderLength: + ret = 27; + break; + case PM_DockWidgetTitleMargin: + ret = 1; + break; + case PM_MenuBarVMargin: + ret = 1; + break; + case PM_DefaultFrameWidth: + ret = 2; + break; + case PM_SpinBoxFrameWidth: + ret = 3; + break; + case PM_MenuBarItemSpacing: + ret = 6; + break; + case PM_MenuBarHMargin: + ret = 0; + break; + case PM_ToolBarHandleExtent: + ret = 9; + break; + case PM_ToolBarItemSpacing: + ret = 2; + break; + case PM_ToolBarFrameWidth: + ret = 0; + break; + case PM_ToolBarItemMargin: + ret = 1; + break; + case PM_SmallIconSize: + ret = 16; + break; + case PM_ButtonIconSize: + ret = 24; + break; + case PM_MenuVMargin: + case PM_MenuHMargin: + ret = 0; + break; + case PM_DockWidgetTitleBarButtonMargin: + ret = 4; + break; + case PM_MaximumDragDistance: + return -1; + case PM_TabCloseIndicatorWidth: + case PM_TabCloseIndicatorHeight: + return 20; + default: + break; + } + + return ret != -1 ? ret : QWindowsStyle::pixelMetric(metric, option, widget); +} + +/*! + \reimp +*/ +QSize QCleanlooksStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const +{ + QSize newSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + switch (type) { + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + if (!btn->text.isEmpty() && newSize.width() < 80) + newSize.setWidth(80); + if (!btn->icon.isNull() && btn->iconSize.height() > 16) + newSize -= QSize(0, 2); + newSize += QSize(0, 1); + } + if (const QPushButton *button = qobject_cast<const QPushButton *>(widget)) { + if (qobject_cast<const QDialogButtonBox *>(button->parentWidget())) { + if (newSize.height() < 32) + newSize.setHeight(32); + } + } + break; +#ifndef QT_NO_GROUPBOX + case CT_GroupBox: + // Since we use a bold font we have to recalculate base width + if (const QGroupBox *gb = qobject_cast<const QGroupBox*>(widget)) { + QFont font = gb->font(); + font.setBold(true); + QFontMetrics metrics(font); + int baseWidth = metrics.width(gb->title()) + metrics.width(QLatin1Char(' ')); + if (gb->isCheckable()) { + baseWidth += proxy()->pixelMetric(QStyle::PM_IndicatorWidth, option, widget); + baseWidth += proxy()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, option, widget); + } + newSize.setWidth(qMax(baseWidth, newSize.width())); + } + newSize += QSize(0, 1); + break; +#endif //QT_NO_GROUPBOX + case CT_RadioButton: + case CT_CheckBox: + newSize += QSize(0, 1); + break; + case CT_ToolButton: +#ifndef QT_NO_TOOLBAR + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) + newSize += QSize(4, 6); +#endif // QT_NO_TOOLBAR + break; + case CT_SpinBox: + newSize += QSize(0, -2); + break; + case CT_ComboBox: + newSize += QSize(2, 4); + break; + case CT_LineEdit: + newSize += QSize(0, 4); + break; + case CT_MenuBarItem: + newSize += QSize(0, 2); + break; + case CT_MenuItem: + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + if (!menuItem->text.isEmpty()) { + newSize.setHeight(menuItem->fontMetrics.height()); + } + } +#ifndef QT_NO_COMBOBOX + else if (!menuItem->icon.isNull()) { + if (const QComboBox *combo = qobject_cast<const QComboBox*>(widget)) { + newSize.setHeight(qMax(combo->iconSize().height() + 2, newSize.height())); + } + } +#endif // QT_NO_COMBOBOX + } + break; + case CT_SizeGrip: + newSize += QSize(4, 4); + break; + case CT_MdiControls: + if (const QStyleOptionComplex *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(option)) { + int width = 0; + if (styleOpt->subControls & SC_MdiMinButton) + width += 19 + 1; + if (styleOpt->subControls & SC_MdiNormalButton) + width += 19 + 1; + if (styleOpt->subControls & SC_MdiCloseButton) + width += 19 + 1; + newSize = QSize(width, 19); + } else { + newSize = QSize(60, 19); + } + break; + default: + break; + } + return newSize; +} + +/*! + \reimp +*/ +void QCleanlooksStyle::polish(QApplication *app) +{ + QWindowsStyle::polish(app); +} + +/*! + \reimp +*/ +void QCleanlooksStyle::polish(QWidget *widget) +{ + QWindowsStyle::polish(widget); + if (qobject_cast<QAbstractButton*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox *>(widget) +#endif +#ifndef QT_NO_PROGRESSBAR + || qobject_cast<QProgressBar *>(widget) +#endif +#ifndef QT_NO_SCROLLBAR + || qobject_cast<QScrollBar *>(widget) +#endif +#ifndef QT_NO_SPLITTER + || qobject_cast<QSplitterHandle *>(widget) +#endif + || qobject_cast<QAbstractSlider *>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox *>(widget) +#endif + || (widget->inherits("QWorkspaceChild")) + || (widget->inherits("QDockSeparator")) + || (widget->inherits("QDockWidgetSeparator")) + ) { + widget->setAttribute(Qt::WA_Hover, true); + } +} + +/*! + \reimp +*/ +void QCleanlooksStyle::polish(QPalette &pal) +{ + QWindowsStyle::polish(pal); + //this is a workaround for some themes such as Human, where the contrast + //between text and background is too low. + QColor highlight = pal.highlight().color(); + QColor highlightText = pal.highlightedText().color(); + if (qAbs(qGray(highlight.rgb()) - qGray(highlightText.rgb())) < 150) { + if (qGray(highlightText.rgb()) < 128) + pal.setBrush(QPalette::Highlight, highlight.lighter(145)); + } +} + +/*! + \reimp +*/ +void QCleanlooksStyle::unpolish(QWidget *widget) +{ + QWindowsStyle::unpolish(widget); + if (qobject_cast<QAbstractButton*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox *>(widget) +#endif +#ifndef QT_NO_PROGRESSBAR + || qobject_cast<QProgressBar *>(widget) +#endif +#ifndef QT_NO_SCROLLBAR + || qobject_cast<QScrollBar *>(widget) +#endif +#ifndef QT_NO_SPLITTER + || qobject_cast<QSplitterHandle *>(widget) +#endif + || qobject_cast<QAbstractSlider *>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox *>(widget) +#endif + || (widget->inherits("QWorkspaceChild")) + || (widget->inherits("QDockSeparator")) + || (widget->inherits("QDockWidgetSeparator")) + ) { + widget->setAttribute(Qt::WA_Hover, false); + } +} + +/*! + \reimp +*/ +void QCleanlooksStyle::unpolish(QApplication *app) +{ + QWindowsStyle::unpolish(app); +} + +/*! + \reimp +*/ +QRect QCleanlooksStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + QRect rect = QWindowsStyle::subControlRect(control, option, subControl, widget); + + switch (control) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); + switch (subControl) { + case SC_SliderHandle: { + if (slider->orientation == Qt::Horizontal) { + rect.setHeight(proxy()->pixelMetric(PM_SliderThickness)); + rect.setWidth(proxy()->pixelMetric(PM_SliderLength)); + int centerY = slider->rect.center().y() - rect.height() / 2; + if (slider->tickPosition & QSlider::TicksAbove) + centerY += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerY -= tickSize; + rect.moveTop(centerY); + } else { + rect.setWidth(proxy()->pixelMetric(PM_SliderThickness)); + rect.setHeight(proxy()->pixelMetric(PM_SliderLength)); + int centerX = slider->rect.center().x() - rect.width() / 2; + if (slider->tickPosition & QSlider::TicksAbove) + centerX += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerX -= tickSize; + rect.moveLeft(centerX); + } + } + break; + case SC_SliderGroove: { + QPoint grooveCenter = slider->rect.center(); + if (slider->orientation == Qt::Horizontal) { + rect.setHeight(7); + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.ry() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.ry() -= tickSize; + } else { + rect.setWidth(7); + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.rx() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.rx() -= tickSize; + } + rect.moveCenter(grooveCenter); + break; + } + default: + break; + } + } + break; +#endif // QT_NO_SLIDER + case CC_ScrollBar: + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QSize bs; + int center = spinbox->rect.height() / 2; + int fw = spinbox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0; + int y = fw; + bs.setHeight(qMax(8, spinbox->rect.height()/2 - y)); + bs.setWidth(15); + int x, lx, rx; + x = spinbox->rect.width() - y - bs.width() + 2; + lx = fw; + rx = x - fw; + switch (subControl) { + case SC_SpinBoxUp: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + rect = QRect(x, fw, bs.width(), center - fw); + break; + case SC_SpinBoxDown: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + + rect = QRect(x, center, bs.width(), spinbox->rect.bottom() - center - fw + 1); + break; + case SC_SpinBoxEditField: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) { + rect = QRect(lx, fw, spinbox->rect.width() - 2*fw, spinbox->rect.height() - 2*fw); + } else { + rect = QRect(lx, fw, rx - qMax(fw - 1, 0), spinbox->rect.height() - 2*fw); + } + break; + case SC_SpinBoxFrame: + rect = spinbox->rect; + default: + break; + } + rect = visualRect(spinbox->direction, spinbox->rect, rect); + } + break; +#endif // Qt_NO_SPINBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + int topMargin = 0; + int topHeight = 0; + int verticalAlignment = proxy()->styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget); + bool flat = groupBox->features & QStyleOptionFrameV2::Flat; + if (!groupBox->text.isEmpty()) { + topHeight = groupBox->fontMetrics.height(); + if (verticalAlignment & Qt::AlignVCenter) + topMargin = topHeight / 2; + else if (verticalAlignment & Qt::AlignTop) + topMargin = topHeight; + } + QRect frameRect = groupBox->rect; + frameRect.setTop(topMargin); + if (subControl == SC_GroupBoxFrame) { + return rect; + } + else if (subControl == SC_GroupBoxContents) { + if( flat ) { + int margin = 0; + int leftMarginExtension = 16; + rect = frameRect.adjusted(leftMarginExtension + margin, margin + topHeight, -margin, -margin); + } + break; + } + if(flat) { + if (const QGroupBox *groupBoxWidget = qobject_cast<const QGroupBox *>(widget)) { + //Prepare metrics for a bold font + QFont font = widget->font(); + font.setBold(true); + QFontMetrics fontMetrics(font); + + QSize textRect = fontMetrics.boundingRect(groupBoxWidget->title()).size() + QSize(2, 2); + if (subControl == SC_GroupBoxCheckBox) { + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget); + int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget); + rect.setWidth(indicatorWidth); + rect.setHeight(indicatorHeight); + rect.moveTop((fontMetrics.height() - indicatorHeight) / 2 + 2); + } else if (subControl == SC_GroupBoxLabel) { + rect.setSize(textRect); + } + } + } + } + return rect; +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + switch (subControl) { + case SC_ComboBoxArrow: + rect = visualRect(option->direction, option->rect, rect); + rect.setRect(rect.right() - 18, rect.top() - 2, + 19, rect.height() + 4); + rect = visualRect(option->direction, option->rect, rect); + break; + case SC_ComboBoxEditField: { + int frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth); + rect = visualRect(option->direction, option->rect, rect); + rect.setRect(option->rect.left() + frameWidth, option->rect.top() + frameWidth, + option->rect.width() - 19 - 2 * frameWidth, + option->rect.height() - 2 * frameWidth); + if (const QStyleOptionComboBox *box = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + if (!box->editable) { + rect.adjust(2, 0, 0, 0); + if (box->state & (State_Sunken | State_On)) + rect.translate(1, 1); + } + } + rect = visualRect(option->direction, option->rect, rect); + break; + } + default: + break; + } + break; +#endif // QT_NO_COMBOBOX +#endif //QT_NO_GROUPBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + SubControl sc = subControl; + QRect &ret = rect; + const int indent = 3; + const int controlTopMargin = 3; + const int controlBottomMargin = 3; + const int controlWidthMargin = 2; + const int controlHeight = tb->rect.height() - controlTopMargin - controlBottomMargin ; + const int delta = controlHeight + controlWidthMargin; + int offset = 0; + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + + switch (sc) { + case SC_TitleBarLabel: + if (tb->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) { + ret = tb->rect; + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + ret.adjust(delta, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowShadeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + ret.adjust(0, 0, -delta, 0); + } + break; + case SC_TitleBarContextHelpButton: + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + offset += delta; + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMinButton) + break; + case SC_TitleBarNormalButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarNormalButton) + break; + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMaxButton) + break; + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarShadeButton) + break; + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarUnshadeButton) + break; + case SC_TitleBarCloseButton: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + offset += delta; + else if (sc == SC_TitleBarCloseButton) + break; + ret.setRect(tb->rect.right() - indent - offset, tb->rect.top() + controlTopMargin, + controlHeight, controlHeight); + break; + case SC_TitleBarSysMenu: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) { + ret.setRect(tb->rect.left() + controlWidthMargin + indent, tb->rect.top() + controlTopMargin, + controlHeight, controlHeight); + } + break; + default: + break; + } + ret = visualRect(tb->direction, tb->rect, ret); + } + break; + default: + break; + } + + return rect; +} + + +/*! + \reimp +*/ +QRect QCleanlooksStyle::itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const +{ + return QWindowsStyle::itemPixmapRect(r, flags, pixmap); +} + +/*! + \reimp +*/ +void QCleanlooksStyle::drawItemPixmap(QPainter *painter, const QRect &rect, + int alignment, const QPixmap &pixmap) const +{ + QWindowsStyle::drawItemPixmap(painter, rect, alignment, pixmap); +} + +/*! + \reimp +*/ +QStyle::SubControl QCleanlooksStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w) const +{ + return QWindowsStyle::hitTestComplexControl(cc, opt, pt, w); +} + +/*! + \reimp +*/ +QPixmap QCleanlooksStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + return QWindowsStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + +/*! + \reimp +*/ +int QCleanlooksStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + int ret = 0; + switch (hint) { + case SH_ScrollBar_MiddleClickAbsolutePosition: + ret = true; + break; + case SH_EtchDisabledText: + ret = 1; + break; + case SH_Menu_AllowActiveAndDisabled: + ret = false; + break; + case SH_MainWindow_SpaceBelowMenuBar: + ret = 0; + break; + case SH_MenuBar_MouseTracking: + ret = 1; + break; + case SH_TitleBar_AutoRaise: + ret = 1; + break; + case SH_TitleBar_NoBorder: + ret = 1; + break; + case SH_ItemView_ShowDecorationSelected: + ret = true; + break; + case SH_Table_GridLineColor: + if (option) { + ret = option->palette.background().color().darker(120).rgb(); + break; + } + case SH_ComboBox_Popup: +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3ComboBox")) + return 0; +#endif + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) + ret = !cmb->editable; + else + ret = 0; + break; + case SH_WindowFrame_Mask: + ret = 1; + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData)) { + //left rounded corner + mask->region = option->rect; + mask->region -= QRect(option->rect.left(), option->rect.top(), 5, 1); + mask->region -= QRect(option->rect.left(), option->rect.top() + 1, 3, 1); + mask->region -= QRect(option->rect.left(), option->rect.top() + 2, 2, 1); + mask->region -= QRect(option->rect.left(), option->rect.top() + 3, 1, 2); + + //right rounded corner + mask->region -= QRect(option->rect.right() - 4, option->rect.top(), 5, 1); + mask->region -= QRect(option->rect.right() - 2, option->rect.top() + 1, 3, 1); + mask->region -= QRect(option->rect.right() - 1, option->rect.top() + 2, 2, 1); + mask->region -= QRect(option->rect.right() , option->rect.top() + 3, 1, 2); + } + break; + case SH_MessageBox_TextInteractionFlags: + ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse; + break; + case SH_DialogButtonBox_ButtonsHaveIcons: + ret = true; + break; + case SH_MessageBox_CenterButtons: + ret = false; + break; +#ifndef QT_NO_WIZARD + case SH_WizardStyle: + ret = QWizard::ClassicStyle; + break; +#endif + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = false; + break; + case SH_Menu_SubMenuPopupDelay: + ret = 225; // default from GtkMenu + break; + default: + ret = QWindowsStyle::styleHint(hint, option, widget, returnData); + break; + } + return ret; +} + +/*! \reimp */ +QRect QCleanlooksStyle::subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *w) const +{ + QRect r = QWindowsStyle::subElementRect(sr, opt, w); + switch (sr) { + case SE_PushButtonFocusRect: + r.adjust(0, 1, 0, -1); + break; + case SE_DockWidgetTitleBarText: { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(opt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + if (verticalTitleBar) { + r.adjust(0, 0, 0, -4); + } else { + if (opt->direction == Qt::LeftToRight) + r.adjust(4, 0, 0, 0); + else + r.adjust(0, 0, -4, 0); + } + + break; + } + case SE_ProgressBarContents: + r = subElementRect(SE_ProgressBarGroove, opt, w); + break; + default: + break; + } + return r; +} + +/*! + \internal +*/ +QIcon QCleanlooksStyle::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + return QWindowsStyle::standardIconImplementation(standardIcon, option, widget); +} + +/*! + \reimp + */ +QPixmap QCleanlooksStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ + QPixmap pixmap; + +#ifndef QT_NO_IMAGEFORMAT_XPM + switch (standardPixmap) { + case SP_TitleBarNormalButton: + return QPixmap((const char **)dock_widget_restore_xpm); + case SP_TitleBarMinButton: + return QPixmap((const char **)workspace_minimize); + case SP_TitleBarCloseButton: + case SP_DockWidgetCloseButton: + return QPixmap((const char **)dock_widget_close_xpm); + + default: + break; + } +#endif //QT_NO_IMAGEFORMAT_XPM + + return QWindowsStyle::standardPixmap(standardPixmap, opt, widget); +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_CLEANLOOKS || QT_PLUGIN diff --git a/src/gui/styles/qcleanlooksstyle.h b/src/gui/styles/qcleanlooksstyle.h new file mode 100644 index 0000000000..9ffa5789c0 --- /dev/null +++ b/src/gui/styles/qcleanlooksstyle.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCLEANLOOKSSTYLE_H +#define QCLEANLOOKSSTYLE_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_CLEANLOOKS) + +class QCleanlooksStylePrivate; +class Q_GUI_EXPORT QCleanlooksStyle : public QWindowsStyle +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QCleanlooksStyle) + +public: + QCleanlooksStyle(); + ~QCleanlooksStyle(); + + QPalette standardPalette () const; + void drawPrimitive(PrimitiveElement elem, + const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement ce, const QStyleOption *option, QPainter *painter, + const QWidget *widget) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + QRect itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const; + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + void drawItemPixmap(QPainter *painter, const QRect &rect, + int alignment, const QPixmap &pixmap) const; + void drawItemText(QPainter *painter, const QRect &rect, + int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + void polish(QWidget *widget); + void polish(QApplication *app); + void polish(QPalette &pal); + void unpolish(QWidget *widget); + void unpolish(QApplication *app); + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + +protected: + QCleanlooksStyle(QCleanlooksStylePrivate &dd); + +}; + +#endif // QT_NO_STYLE_CLEANLOOKS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCLEANLOOKSSTYLE_H diff --git a/src/gui/styles/qcleanlooksstyle_p.h b/src/gui/styles/qcleanlooksstyle_p.h new file mode 100644 index 0000000000..52cc282578 --- /dev/null +++ b/src/gui/styles/qcleanlooksstyle_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCLEANLOOKSSTYLE_P_H +#define QCLEANLOOKSSTYLE_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 "qwindowsstyle.h" +#include "qwindowsstyle_p.h" + +#ifndef QT_NO_STYLE_CLEANLOOKS + +QT_BEGIN_NAMESPACE + +class QCleanlooksStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QCleanlooksStyle) +public: + QCleanlooksStylePrivate() + : QWindowsStylePrivate() { + animationFps = 24; + } + + ~QCleanlooksStylePrivate() { + } +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_CLEANLOOKS + +#endif //QCLEANLOOKSSTYLE_P_H diff --git a/src/gui/styles/qcommonstyle.cpp b/src/gui/styles/qcommonstyle.cpp new file mode 100644 index 0000000000..8f99d6ad26 --- /dev/null +++ b/src/gui/styles/qcommonstyle.cpp @@ -0,0 +1,6085 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcommonstyle.h" +#include "qcommonstyle_p.h" + +#include <qfile.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qcache.h> +#include <qdockwidget.h> +#include <qdrawutil.h> +#include <qdialogbuttonbox.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qmath.h> +#include <qmenu.h> +#include <qpainter.h> +#include <qpaintengine.h> +#include <qpainterpath.h> +#include <qslider.h> +#include <qstyleoption.h> +#include <qtabbar.h> +#include <qtabwidget.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qrubberband.h> +#include <private/qcommonstylepixmaps_p.h> +#include <private/qmath_p.h> +#include <qdebug.h> +#include <qtextformat.h> +#include <qwizard.h> +#include <qtabbar.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qsettings.h> +#include <qpixmapcache.h> +#include <private/qguiplatformplugin_p.h> + +#include <limits.h> + +#ifndef QT_NO_ITEMVIEWS +# include "private/qtextengine_p.h" +#endif + +#ifdef Q_WS_X11 +# include <private/qt_x11_p.h> +#elif defined(Q_WS_MAC) +# include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QCommonStyle + \brief The QCommonStyle class encapsulates the common Look and Feel of a GUI. + + \ingroup appearance + + This abstract class implements some of the widget's look and feel + that is common to all GUI styles provided and shipped as part of + Qt. + + Since QCommonStyle inherits QStyle, all of its functions are fully documented + in the QStyle documentation. + \omit + , although the + extra functions that QCommonStyle provides, e.g. + drawComplexControl(), drawControl(), drawPrimitive(), + hitTestComplexControl(), subControlRect(), sizeFromContents(), and + subElementRect() are documented here. + \endomit + + \sa QStyle, QMotifStyle, QWindowsStyle +*/ + +/*! + Constructs a QCommonStyle. +*/ +QCommonStyle::QCommonStyle() + : QStyle(*new QCommonStylePrivate) +{ } + +/*! \internal +*/ +QCommonStyle::QCommonStyle(QCommonStylePrivate &dd) + : QStyle(dd) +{ } + +/*! + Destroys the style. +*/ +QCommonStyle::~QCommonStyle() +{ } + + +/*! + \reimp +*/ +void QCommonStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *widget) const +{ + Q_D(const QCommonStyle); + switch (pe) { + case PE_FrameButtonBevel: + case PE_FrameButtonTool: + qDrawShadeRect(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), 1, 0); + break; + case PE_PanelButtonCommand: + case PE_PanelButtonBevel: + case PE_PanelButtonTool: + case PE_IndicatorButtonDropDown: + qDrawShadePanel(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), 1, + &opt->palette.brush(QPalette::Button)); + break; + case PE_IndicatorViewItemCheck: + proxy()->drawPrimitive(PE_IndicatorCheckBox, opt, p, widget); + break; + case PE_IndicatorCheckBox: + if (opt->state & State_NoChange) { + p->setPen(opt->palette.foreground().color()); + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + p->drawRect(opt->rect); + p->drawLine(opt->rect.topLeft(), opt->rect.bottomRight()); + } else { + qDrawShadePanel(p, opt->rect.x(), opt->rect.y(), opt->rect.width(), opt->rect.height(), + opt->palette, opt->state & (State_Sunken | State_On), 1, + &opt->palette.brush(QPalette::Button)); + } + break; + case PE_IndicatorRadioButton: { + QRect ir = opt->rect; + p->setPen(opt->palette.dark().color()); + p->drawArc(opt->rect, 0, 5760); + if (opt->state & (State_Sunken | State_On)) { + ir.adjust(2, 2, -2, -2); + p->setBrush(opt->palette.foreground()); + p->drawEllipse(ir); + } + break; } + case PE_FrameFocusRect: + if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(opt)) { + QColor bg = fropt->backgroundColor; + QPen oldPen = p->pen(); + if (bg.isValid()) { + int h, s, v; + bg.getHsv(&h, &s, &v); + if (v >= 128) + p->setPen(Qt::black); + else + p->setPen(Qt::white); + } else { + p->setPen(opt->palette.foreground().color()); + } + QRect focusRect = opt->rect.adjusted(1, 1, -1, -1); + p->drawRect(focusRect.adjusted(0, 0, -1, -1)); //draw pen inclusive + p->setPen(oldPen); + } + break; + case PE_IndicatorMenuCheckMark: { + const int markW = opt->rect.width() > 7 ? 7 : opt->rect.width(); + const int markH = markW; + int posX = opt->rect.x() + (opt->rect.width() - markW)/2 + 1; + int posY = opt->rect.y() + (opt->rect.height() - markH)/2; + + QVector<QLineF> a; + a.reserve(markH); + + int i, xx, yy; + xx = posX; + yy = 3 + posY; + for (i = 0; i < markW/2; ++i) { + a << QLineF(xx, yy, xx, yy + 2); + ++xx; + ++yy; + } + yy -= 2; + for (; i < markH; ++i) { + a << QLineF(xx, yy, xx, yy + 2); + ++xx; + --yy; + } + if (!(opt->state & State_Enabled) && !(opt->state & State_On)) { + int pnt; + p->setPen(opt->palette.highlightedText().color()); + QPoint offset(1, 1); + for (pnt = 0; pnt < a.size(); ++pnt) + a[pnt].translate(offset.x(), offset.y()); + p->drawLines(a); + for (pnt = 0; pnt < a.size(); ++pnt) + a[pnt].translate(offset.x(), offset.y()); + } + p->setPen(opt->palette.text().color()); + p->drawLines(a); + break; } + case PE_Frame: + case PE_FrameMenu: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (pe == PE_FrameMenu || (frame->state & State_Sunken) || (frame->state & State_Raised)) { + qDrawShadePanel(p, frame->rect, frame->palette, frame->state & State_Sunken, + frame->lineWidth); + } else { + qDrawPlainRect(p, frame->rect, frame->palette.foreground().color(), frame->lineWidth); + } + } + break; +#ifndef QT_NO_TOOLBAR + case PE_PanelMenuBar: + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) + break; + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)){ + qDrawShadePanel(p, frame->rect, frame->palette, false, frame->lineWidth, + &frame->palette.brush(QPalette::Button)); + + } + else if (const QStyleOptionToolBar *frame = qstyleoption_cast<const QStyleOptionToolBar *>(opt)){ + qDrawShadePanel(p, frame->rect, frame->palette, false, frame->lineWidth, + &frame->palette.brush(QPalette::Button)); + } + + break; + case PE_PanelMenu: + break; + case PE_PanelToolBar: + break; +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_PROGRESSBAR + case PE_IndicatorProgressChunk: + { + bool vertical = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) + vertical = (pb2->orientation == Qt::Vertical); + if (!vertical) { + p->fillRect(opt->rect.x(), opt->rect.y() + 3, opt->rect.width() -2, opt->rect.height() - 6, + opt->palette.brush(QPalette::Highlight)); + } else { + p->fillRect(opt->rect.x() + 2, opt->rect.y(), opt->rect.width() -6, opt->rect.height() - 2, + opt->palette.brush(QPalette::Highlight)); + } + } + break; +#endif // QT_NO_PROGRESSBAR +#ifdef QT3_SUPPORT + case PE_Q3CheckListController: +#ifndef QT_NO_IMAGEFORMAT_XPM + p->drawPixmap(opt->rect.topLeft(), QPixmap(check_list_controller_xpm)); +#endif + break; + case PE_Q3CheckListExclusiveIndicator: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if (lv->items.isEmpty()) + return; + int x = lv->rect.x(), + y = lv->rect.y(); +#define INTARRLEN(x) sizeof(x)/(sizeof(int)*2) + static const int pts1[] = { // dark lines + 1,9, 1,8, 0,7, 0,4, 1,3, 1,2, 2,1, 3,1, 4,0, 7,0, 8,1, 9,1 }; + static const int pts2[] = { // black lines + 2,8, 1,7, 1,4, 2,3, 2,2, 3,2, 4,1, 7,1, 8,2, 9,2 }; + static const int pts3[] = { // background lines + 2,9, 3,9, 4,10, 7,10, 8,9, 9,9, 9,8, 10,7, 10,4, 9,3 }; + static const int pts4[] = { // white lines + 2,10, 3,10, 4,11, 7,11, 8,10, 9,10, 10,9, 10,8, 11,7, + 11,4, 10,3, 10,2 }; + // static const int pts5[] = { // inner fill + // 4,2, 7,2, 9,4, 9,7, 7,9, 4,9, 2,7, 2,4 }; + //QPolygon a; + + if (lv->state & State_Enabled) + p->setPen(lv->palette.text().color()); + else + p->setPen(QPen(lv->viewportPalette.color(QPalette::Disabled, QPalette::Text))); + QPolygon a(INTARRLEN(pts1), pts1); + a.translate(x, y); + //p->setPen(pal.dark()); + p->drawPolyline(a); + a.setPoints(INTARRLEN(pts2), pts2); + a.translate(x, y); + p->drawPolyline(a); + a.setPoints(INTARRLEN(pts3), pts3); + a.translate(x, y); + // p->setPen(black); + p->drawPolyline(a); + a.setPoints(INTARRLEN(pts4), pts4); + a.translate(x, y); + // p->setPen(blue); + p->drawPolyline(a); + // a.setPoints(INTARRLEN(pts5), pts5); + // a.translate(x, y); + // QColor fillColor = isDown() ? g.background() : g.base(); + // p->setPen(fillColor); + // p->setBrush(fillColor); + // p->drawPolygon(a); + if (opt->state & State_On) { + p->setPen(Qt::NoPen); + p->setBrush(opt->palette.text()); + p->drawRect(x + 5, y + 4, 2, 4); + p->drawRect(x + 4, y + 5, 4, 2); + } +#undef INTARRLEN + } + break; + case PE_Q3CheckListIndicator: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if(lv->items.isEmpty()) + break; + QStyleOptionQ3ListViewItem item = lv->items.at(0); + int x = lv->rect.x(), + y = lv->rect.y(), + w = lv->rect.width(), + h = lv->rect.width(), + marg = lv->itemMargin; + + if (lv->state & State_Enabled) + p->setPen(QPen(lv->palette.text().color(), 2)); + else + p->setPen(QPen(lv->viewportPalette.color(QPalette::Disabled, QPalette::Text), 2)); + if (opt->state & State_Selected && !lv->rootIsDecorated + && !(item.features & QStyleOptionQ3ListViewItem::ParentControl)) { + p->fillRect(0, 0, x + marg + w + 4, item.height, + lv->palette.brush(QPalette::Highlight)); + if (item.state & State_Enabled) + p->setPen(QPen(lv->palette.highlightedText().color(), 2)); + } + + if (lv->state & State_NoChange) + p->setBrush(lv->palette.brush(QPalette::Button)); + p->drawRect(x + marg, y + 2, w - 4, h - 4); + ///////////////////// + ++x; + ++y; + if (lv->state & State_On || lv->state & State_NoChange) { + QLineF lines[7]; + int i, + xx = x + 1 + marg, + yy = y + 5; + for (i = 0; i < 3; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + 2); + ++xx; + ++yy; + } + yy -= 2; + for (i = 3; i < 7; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + 2); + ++xx; + --yy; + } + p->drawLines(lines, 7); + } + } + break; +#endif // QT3_SUPPORT + case PE_IndicatorBranch: { + int mid_h = opt->rect.x() + opt->rect.width() / 2; + int mid_v = opt->rect.y() + opt->rect.height() / 2; + int bef_h = mid_h; + int bef_v = mid_v; + int aft_h = mid_h; + int aft_v = mid_v; +#ifndef QT_NO_IMAGEFORMAT_XPM + static const int decoration_size = 9; + static QPixmap open(tree_branch_open_xpm); + static QPixmap closed(tree_branch_closed_xpm); + if (opt->state & State_Children) { + int delta = decoration_size / 2; + bef_h -= delta; + bef_v -= delta; + aft_h += delta; + aft_v += delta; + p->drawPixmap(bef_h, bef_v, opt->state & State_Open ? open : closed); + } +#endif // QT_NO_IMAGEFORMAT_XPM + if (opt->state & State_Item) { + if (opt->direction == Qt::RightToLeft) + p->drawLine(opt->rect.left(), mid_v, bef_h, mid_v); + else + p->drawLine(aft_h, mid_v, opt->rect.right(), mid_v); + } + if (opt->state & State_Sibling) + p->drawLine(mid_h, aft_v, mid_h, opt->rect.bottom()); + if (opt->state & (State_Open | State_Children | State_Item | State_Sibling)) + p->drawLine(mid_h, opt->rect.y(), mid_h, bef_v); + break; } +#ifdef QT3_SUPPORT + case PE_Q3Separator: + qDrawShadeLine(p, opt->rect.left(), opt->rect.top(), opt->rect.right(), opt->rect.bottom(), + opt->palette, opt->state & State_Sunken, 1, 0); + break; +#endif // QT3_SUPPORT + case PE_FrameStatusBarItem: + qDrawShadeRect(p, opt->rect, opt->palette, true, 1, 0, 0); + break; + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QPen oldPen = p->pen(); + if (header->sortIndicator & QStyleOptionHeader::SortUp) { + QPolygon pa(3); + p->setPen(QPen(opt->palette.light(), 0)); + p->drawLine(opt->rect.x() + opt->rect.width(), opt->rect.y(), + opt->rect.x() + opt->rect.width() / 2, opt->rect.y() + opt->rect.height()); + p->setPen(QPen(opt->palette.dark(), 0)); + pa.setPoint(0, opt->rect.x() + opt->rect.width() / 2, opt->rect.y() + opt->rect.height()); + pa.setPoint(1, opt->rect.x(), opt->rect.y()); + pa.setPoint(2, opt->rect.x() + opt->rect.width(), opt->rect.y()); + p->drawPolyline(pa); + } else if (header->sortIndicator & QStyleOptionHeader::SortDown) { + QPolygon pa(3); + p->setPen(QPen(opt->palette.light(), 0)); + pa.setPoint(0, opt->rect.x(), opt->rect.y() + opt->rect.height()); + pa.setPoint(1, opt->rect.x() + opt->rect.width(), opt->rect.y() + opt->rect.height()); + pa.setPoint(2, opt->rect.x() + opt->rect.width() / 2, opt->rect.y()); + p->drawPolyline(pa); + p->setPen(QPen(opt->palette.dark(), 0)); + p->drawLine(opt->rect.x(), opt->rect.y() + opt->rect.height(), + opt->rect.x() + opt->rect.width() / 2, opt->rect.y()); + } + p->setPen(oldPen); + } + break; +#ifndef QT_NO_TABBAR + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb + = qstyleoption_cast<const QStyleOptionTabBarBase *>(opt)) { + p->save(); + switch (tbb->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + p->setPen(QPen(tbb->palette.light(), 0)); + p->drawLine(tbb->rect.topLeft(), tbb->rect.topRight()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + p->setPen(QPen(tbb->palette.light(), 0)); + p->drawLine(tbb->rect.topLeft(), tbb->rect.bottomLeft()); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + p->setPen(QPen(tbb->palette.shadow(), 0)); + p->drawLine(tbb->rect.left(), tbb->rect.bottom(), + tbb->rect.right(), tbb->rect.bottom()); + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.left(), tbb->rect.bottom() - 1, + tbb->rect.right() - 1, tbb->rect.bottom() - 1); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + p->setPen(QPen(tbb->palette.dark(), 0)); + p->drawLine(tbb->rect.topRight(), tbb->rect.bottomRight()); + break; + } + p->restore(); + } + break; + case PE_IndicatorTabClose: { + if (d->tabBarcloseButtonIcon.isNull()) { + d->tabBarcloseButtonIcon.addPixmap(QPixmap( + QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-closetab-16.png")), + QIcon::Normal, QIcon::Off); + d->tabBarcloseButtonIcon.addPixmap(QPixmap( + QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-closetab-down-16.png")), + QIcon::Normal, QIcon::On); + d->tabBarcloseButtonIcon.addPixmap(QPixmap( + QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-closetab-hover-16.png")), + QIcon::Active, QIcon::Off); + } + + int size = proxy()->pixelMetric(QStyle::PM_SmallIconSize); + QIcon::Mode mode = opt->state & State_Enabled ? + (opt->state & State_Raised ? QIcon::Active : QIcon::Normal) + : QIcon::Disabled; + if (!(opt->state & State_Raised) + && !(opt->state & State_Sunken) + && !(opt->state & QStyle::State_Selected)) + mode = QIcon::Disabled; + + QIcon::State state = opt->state & State_Sunken ? QIcon::On : QIcon::Off; + QPixmap pixmap = d->tabBarcloseButtonIcon.pixmap(size, mode, state); + proxy()->drawItemPixmap(p, opt->rect, Qt::AlignCenter, pixmap); + break; + } +#endif // QT_NO_TABBAR + case PE_FrameTabWidget: + case PE_FrameWindow: + qDrawWinPanel(p, opt->rect, opt->palette, false, 0); + break; + case PE_FrameLineEdit: + proxy()->drawPrimitive(PE_Frame, opt, p, widget); + break; +#ifndef QT_NO_GROUPBOX + case PE_FrameGroupBox: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + const QStyleOptionFrameV2 *frame2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(opt); + if (frame2 && (frame2->features & QStyleOptionFrameV2::Flat)) { + QRect fr = frame->rect; + QPoint p1(fr.x(), fr.y() + 1); + QPoint p2(fr.x() + fr.width(), p1.y()); + qDrawShadeLine(p, p1, p2, frame->palette, true, + frame->lineWidth, frame->midLineWidth); + } else { + qDrawShadeRect(p, frame->rect.x(), frame->rect.y(), frame->rect.width(), + frame->rect.height(), frame->palette, true, + frame->lineWidth, frame->midLineWidth); + } + } + break; +#endif // QT_NO_GROUPBOX +#ifndef QT_NO_DOCKWIDGET + case PE_FrameDockWidget: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + int lw = frame->lineWidth; + if (lw <= 0) + lw = proxy()->pixelMetric(PM_DockWidgetFrameWidth); + + qDrawShadePanel(p, frame->rect, frame->palette, false, lw); + } + break; +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarHandle: + p->save(); + p->translate(opt->rect.x(), opt->rect.y()); + if (opt->state & State_Horizontal) { + int x = opt->rect.width() / 3; + if (opt->direction == Qt::RightToLeft) + x -= 2; + if (opt->rect.height() > 4) { + qDrawShadePanel(p, x, 2, 3, opt->rect.height() - 4, + opt->palette, false, 1, 0); + qDrawShadePanel(p, x+3, 2, 3, opt->rect.height() - 4, + opt->palette, false, 1, 0); + } + } else { + if (opt->rect.width() > 4) { + int y = opt->rect.height() / 3; + qDrawShadePanel(p, 2, y, opt->rect.width() - 4, 3, + opt->palette, false, 1, 0); + qDrawShadePanel(p, 2, y+3, opt->rect.width() - 4, 3, + opt->palette, false, 1, 0); + } + } + p->restore(); + break; + case PE_Q3DockWindowSeparator: + proxy()->drawPrimitive(PE_IndicatorToolBarSeparator, opt, p, widget); + break; + case PE_IndicatorToolBarSeparator: + { + QPoint p1, p2; + if (opt->state & State_Horizontal) { + p1 = QPoint(opt->rect.width()/2, 0); + p2 = QPoint(p1.x(), opt->rect.height()); + } else { + p1 = QPoint(0, opt->rect.height()/2); + p2 = QPoint(opt->rect.width(), p1.y()); + } + qDrawShadeLine(p, p1, p2, opt->palette, 1, 1, 0); + break; + } +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_SPINBOX + case PE_IndicatorSpinPlus: + case PE_IndicatorSpinMinus: { + QRect r = opt->rect; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + QRect br = r.adjusted(fw, fw, -fw, -fw); + + int offset = (opt->state & State_Sunken) ? 1 : 0; + int step = (br.width() + 4) / 5; + p->fillRect(br.x() + offset, br.y() + offset +br.height() / 2 - step / 2, + br.width(), step, + opt->palette.buttonText()); + if (pe == PE_IndicatorSpinPlus) + p->fillRect(br.x() + br.width() / 2 - step / 2 + offset, br.y() + offset, + step, br.height(), + opt->palette.buttonText()); + + break; } + case PE_IndicatorSpinUp: + case PE_IndicatorSpinDown: { + QRect r = opt->rect; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + // QRect br = r.adjusted(fw, fw, -fw, -fw); + int x = r.x(), y = r.y(), w = r.width(), h = r.height(); + int sw = w-4; + if (sw < 3) + break; + else if (!(sw & 1)) + sw--; + sw -= (sw / 7) * 2; // Empty border + int sh = sw/2 + 2; // Must have empty row at foot of arrow + + int sx = x + w / 2 - sw / 2; + int sy = y + h / 2 - sh / 2; + + if (pe == PE_IndicatorSpinUp && fw) + --sy; + + QPolygon a; + if (pe == PE_IndicatorSpinDown) + a.setPoints(3, 0, 1, sw-1, 1, sh-2, sh-1); + else + a.setPoints(3, 0, sh-1, sw-1, sh-1, sh-2, 1); + int bsx = 0; + int bsy = 0; + if (opt->state & State_Sunken) { + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical); + } + p->save(); + p->translate(sx + bsx, sy + bsy); + p->setPen(opt->palette.buttonText().color()); + p->setBrush(opt->palette.buttonText()); + p->drawPolygon(a); + p->restore(); + break; } +#endif // QT_NO_SPINBOX + case PE_PanelTipLabel: { + QBrush oldBrush = p->brush(); + QPen oldPen = p->pen(); + p->setPen(QPen(opt->palette.toolTipText(), 0)); + p->setBrush(opt->palette.toolTipBase()); + p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + p->setBrush(oldBrush); + break; + } +#ifndef QT_NO_TABBAR + case PE_IndicatorTabTear: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + bool rtl = tab->direction == Qt::RightToLeft; + QRect rect = tab->rect; + QPainterPath path; + + rect.setTop(rect.top() + ((tab->state & State_Selected) ? 1 : 3)); + rect.setBottom(rect.bottom() - ((tab->state & State_Selected) ? 0 : 2)); + + path.moveTo(QPoint(rtl ? rect.right() : rect.left(), rect.top())); + int count = 4; + for(int jags = 1; jags <= count; ++jags, rtl = !rtl) + path.lineTo(QPoint(rtl ? rect.left() : rect.right(), rect.top() + jags * rect.height()/count)); + + p->setPen(QPen(tab->palette.light(), qreal(.8))); + p->setBrush(tab->palette.background()); + p->setRenderHint(QPainter::Antialiasing); + p->drawPath(path); + } + break; +#endif // QT_NO_TABBAR +#ifndef QT_NO_LINEEDIT + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + p->fillRect(panel->rect.adjusted(panel->lineWidth, panel->lineWidth, -panel->lineWidth, -panel->lineWidth), + panel->palette.brush(QPalette::Base)); + + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, panel, p, widget); + } + break; +#endif // QT_NO_LINEEDIT +#ifndef QT_NO_COLUMNVIEW + case PE_IndicatorColumnViewArrow: { + if (const QStyleOptionViewItem *viewOpt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { + bool reverse = (viewOpt->direction == Qt::RightToLeft); + p->save(); + QPainterPath path; + int x = viewOpt->rect.x() + 1; + int offset = (viewOpt->rect.height() / 3); + int height = (viewOpt->rect.height()) - offset * 2; + if (height % 2 == 1) + --height; + int x2 = x + height - 1; + if (reverse) { + x = viewOpt->rect.x() + viewOpt->rect.width() - 1; + x2 = x - height + 1; + } + path.moveTo(x, viewOpt->rect.y() + offset); + path.lineTo(x, viewOpt->rect.y() + offset + height); + path.lineTo(x2, viewOpt->rect.y() + offset+height/2); + path.closeSubpath(); + if (viewOpt->state & QStyle::State_Selected ) { + if (viewOpt->showDecorationSelected) { + QColor color = viewOpt->palette.color(QPalette::Active, QPalette::HighlightedText); + p->setPen(color); + p->setBrush(color); + } else { + QColor color = viewOpt->palette.color(QPalette::Active, QPalette::WindowText); + p->setPen(color); + p->setBrush(color); + } + + } else { + QColor color = viewOpt->palette.color(QPalette::Active, QPalette::Mid); + p->setPen(color); + p->setBrush(color); + } + p->drawPath(path); + + // draw the vertical and top triangle line + if (!(viewOpt->state & QStyle::State_Selected)) { + QPainterPath lines; + lines.moveTo(x, viewOpt->rect.y() + offset); + lines.lineTo(x, viewOpt->rect.y() + offset + height); + lines.moveTo(x, viewOpt->rect.y() + offset); + lines.lineTo(x2, viewOpt->rect.y() + offset+height/2); + QColor color = viewOpt->palette.color(QPalette::Active, QPalette::Dark); + p->setPen(color); + p->drawPath(lines); + } + p->restore(); + } + break; } +#endif //QT_NO_COLUMNVIEW + case PE_IndicatorItemViewItemDrop: { + QRect rect = opt->rect; + if (opt->rect.height() == 0) + p->drawLine(rect.topLeft(), rect.topRight()); + else + p->drawRect(rect); + break; } +#ifndef QT_NO_ITEMVIEWS + case PE_PanelItemViewRow: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + QPalette::ColorGroup cg = (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)) + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if ((vopt->state & QStyle::State_Selected) && proxy()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, opt, widget)) + p->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::Highlight)); + else if (vopt->features & QStyleOptionViewItemV2::Alternate) + p->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::AlternateBase)); + } + break; + case PE_PanelItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + QPalette::ColorGroup cg = (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)) + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if (vopt->showDecorationSelected && (vopt->state & QStyle::State_Selected)) { + p->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::Highlight)); + } else { + if (vopt->backgroundBrush.style() != Qt::NoBrush) { + QPointF oldBO = p->brushOrigin(); + p->setBrushOrigin(vopt->rect.topLeft()); + p->fillRect(vopt->rect, vopt->backgroundBrush); + p->setBrushOrigin(oldBO); + } + + if (vopt->state & QStyle::State_Selected) { + QRect textRect = subElementRect(QStyle::SE_ItemViewItemText, opt, widget); + p->fillRect(textRect, vopt->palette.brush(cg, QPalette::Highlight)); + } + } + } + break; +#endif //QT_NO_ITEMVIEWS + case PE_PanelScrollAreaCorner: { + const QBrush brush(opt->palette.brush(QPalette::Window)); + p->fillRect(opt->rect, brush); + } break; + default: + break; + } +} + +#ifndef QT_NO_TOOLBUTTON +static void drawArrow(const QStyle *style, const QStyleOptionToolButton *toolbutton, + const QRect &rect, QPainter *painter, const QWidget *widget = 0) +{ + 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; + arrowOpt.rect = rect; + arrowOpt.palette = toolbutton->palette; + arrowOpt.state = toolbutton->state; + style->drawPrimitive(pe, &arrowOpt, painter, widget); +} +#endif // QT_NO_TOOLBUTTON + +#ifndef QT_NO_ITEMVIEWS + +QSize QCommonStylePrivate::viewItemSize(const QStyleOptionViewItemV4 *option, int role) const +{ + Q_Q(const QCommonStyle); + + const QWidget *widget = option->widget; + switch (role) { + case Qt::CheckStateRole: + if (option->features & QStyleOptionViewItemV2::HasCheckIndicator) + return QSize(q->pixelMetric(QStyle::PM_IndicatorWidth, option, widget), + q->pixelMetric(QStyle::PM_IndicatorHeight, option, widget)); + break; + case Qt::DisplayRole: + if (option->features & QStyleOptionViewItemV2::HasDisplay) { + QTextOption textOption; + textOption.setWrapMode(QTextOption::WordWrap); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + const bool wrapText = option->features & QStyleOptionViewItemV2::WrapText; + const int textMargin = q->pixelMetric(QStyle::PM_FocusFrameHMargin, option, widget) + 1; + QRect bounds = option->rect; + switch (option->decorationPosition) { + case QStyleOptionViewItem::Left: + case QStyleOptionViewItem::Right: + bounds.setWidth(wrapText && bounds.isValid() ? bounds.width() - 2 * textMargin : QFIXED_MAX); + break; + case QStyleOptionViewItem::Top: + case QStyleOptionViewItem::Bottom: + bounds.setWidth(wrapText ? option->decorationSize.width() : QFIXED_MAX); + break; + default: + break; + } + + qreal height = 0, widthUsed = 0; + textLayout.beginLayout(); + while (true) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(bounds.width()); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + const QSize size(qCeil(widthUsed), qCeil(height)); + return QSize(size.width() + 2 * textMargin, size.height()); + } + break; + case Qt::DecorationRole: + if (option->features & QStyleOptionViewItemV2::HasDecoration) { + return option->decorationSize; + } + break; + default: + break; + } + + return QSize(0, 0); +} + +static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth) +{ + qreal height = 0; + qreal widthUsed = 0; + textLayout.beginLayout(); + while (true) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout.endLayout(); + return QSizeF(widthUsed, height); +} + +void QCommonStylePrivate::viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect) const +{ + Q_Q(const QCommonStyle); + const QWidget *widget = option->widget; + const int textMargin = q->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; + + QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding + const bool wrapText = option->features & QStyleOptionViewItemV2::WrapText; + QTextOption textOption; + textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap); + textOption.setTextDirection(option->direction); + textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment)); + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option->font); + textLayout.setText(option->text); + + QSizeF textLayoutSize = viewItemTextLayout(textLayout, textRect.width()); + + QString elidedText; + qreal height = 0; + qreal width = 0; + int elidedIndex = -1; + const int lineCount = textLayout.lineCount(); + for (int j = 0; j < lineCount; ++j) { + const QTextLine line = textLayout.lineAt(j); + if (j + 1 <= lineCount - 1) { + const QTextLine nextLine = textLayout.lineAt(j + 1); + if ((nextLine.y() + nextLine.height()) > textRect.height()) { + int start = line.textStart(); + int length = line.textLength() + nextLine.textLength(); + const QStackTextEngine engine(textLayout.text().mid(start, length), option->font); + elidedText = engine.elidedText(option->textElideMode, textRect.width()); + height += line.height(); + width = textRect.width(); + elidedIndex = j; + break; + } + } + if (line.naturalTextWidth() > textRect.width()) { + int start = line.textStart(); + int length = line.textLength(); + const QStackTextEngine engine(textLayout.text().mid(start, length), option->font); + elidedText = engine.elidedText(option->textElideMode, textRect.width()); + height += line.height(); + width = textRect.width(); + elidedIndex = j; + break; + } + width = qMax<qreal>(width, line.width()); + height += line.height(); + } + + const QRect layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, + QSize(int(width), int(height)), textRect); + const QPointF position = layoutRect.topLeft(); + for (int i = 0; i < lineCount; ++i) { + const QTextLine line = textLayout.lineAt(i); + if (i == elidedIndex) { + qreal x = position.x() + line.x(); + qreal y = position.y() + line.y() + line.ascent(); + p->save(); + p->setFont(option->font); + p->drawText(QPointF(x, y), elidedText); + p->restore(); + break; + } + line.draw(p, position); + } +} + +/*! \internal + compute the position for the different component of an item (pixmap, text, checkbox) + + Set sizehint to false to layout the elements inside opt->rect. Set sizehint to true to ignore + opt->rect and return rectangles in infinite space + + Code duplicated in QItemDelegate::doLayout +*/ +void QCommonStylePrivate::viewItemLayout(const QStyleOptionViewItemV4 *opt, QRect *checkRect, + QRect *pixmapRect, QRect *textRect, bool sizehint) const +{ + Q_Q(const QCommonStyle); + Q_ASSERT(checkRect && pixmapRect && textRect); + *pixmapRect = QRect(QPoint(0, 0), viewItemSize(opt, Qt::DecorationRole)); + *textRect = QRect(QPoint(0, 0), viewItemSize(opt, Qt::DisplayRole)); + *checkRect = QRect(QPoint(0, 0), viewItemSize(opt, Qt::CheckStateRole)); + + const QWidget *widget = opt->widget; + const bool hasCheck = checkRect->isValid(); + const bool hasPixmap = pixmapRect->isValid(); + const bool hasText = textRect->isValid(); + const int textMargin = hasText ? q->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, widget) + 1 : 0; + const int pixmapMargin = hasPixmap ? q->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, widget) + 1 : 0; + const int checkMargin = hasCheck ? q->pixelMetric(QStyle::PM_FocusFrameHMargin, opt, widget) + 1 : 0; + int x = opt->rect.left(); + int y = opt->rect.top(); + int w, h; + + if (textRect->height() == 0 && (!hasPixmap || !sizehint)) { + //if there is no text, we still want to have a decent height for the item sizeHint and the editor size + textRect->setHeight(opt->fontMetrics.height()); + } + + QSize pm(0, 0); + if (hasPixmap) { + pm = pixmapRect->size(); + pm.rwidth() += 2 * pixmapMargin; + } + if (sizehint) { + h = qMax(checkRect->height(), qMax(textRect->height(), pm.height())); + if (opt->decorationPosition == QStyleOptionViewItem::Left + || opt->decorationPosition == QStyleOptionViewItem::Right) { + w = textRect->width() + pm.width(); + } else { + w = qMax(textRect->width(), pm.width()); + } + } else { + w = opt->rect.width(); + h = opt->rect.height(); + } + + int cw = 0; + QRect check; + if (hasCheck) { + cw = checkRect->width() + 2 * checkMargin; + if (sizehint) w += cw; + if (opt->direction == Qt::RightToLeft) { + check.setRect(x + w - cw, y, cw, h); + } else { + check.setRect(x, y, cw, h); + } + } + + QRect display; + QRect decoration; + switch (opt->decorationPosition) { + case QStyleOptionViewItem::Top: { + if (hasPixmap) + pm.setHeight(pm.height() + pixmapMargin); // add space + h = sizehint ? textRect->height() : h - pm.height(); + + if (opt->direction == Qt::RightToLeft) { + decoration.setRect(x, y, w - cw, pm.height()); + display.setRect(x, y + pm.height(), w - cw, h); + } else { + decoration.setRect(x + cw, y, w - cw, pm.height()); + display.setRect(x + cw, y + pm.height(), w - cw, h); + } + break; } + case QStyleOptionViewItem::Bottom: { + if (hasText) + textRect->setHeight(textRect->height() + textMargin); // add space + h = sizehint ? textRect->height() + pm.height() : h; + + if (opt->direction == Qt::RightToLeft) { + display.setRect(x, y, w - cw, textRect->height()); + decoration.setRect(x, y + textRect->height(), w - cw, h - textRect->height()); + } else { + display.setRect(x + cw, y, w - cw, textRect->height()); + decoration.setRect(x + cw, y + textRect->height(), w - cw, h - textRect->height()); + } + break; } + case QStyleOptionViewItem::Left: { + if (opt->direction == Qt::LeftToRight) { + decoration.setRect(x + cw, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } else { + display.setRect(x, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } + break; } + case QStyleOptionViewItem::Right: { + if (opt->direction == Qt::LeftToRight) { + display.setRect(x + cw, y, w - pm.width() - cw, h); + decoration.setRect(display.right() + 1, y, pm.width(), h); + } else { + decoration.setRect(x, y, pm.width(), h); + display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h); + } + break; } + default: + qWarning("doLayout: decoration position is invalid"); + decoration = *pixmapRect; + break; + } + + if (!sizehint) { // we only need to do the internal layout if we are going to paint + *checkRect = QStyle::alignedRect(opt->direction, Qt::AlignCenter, + checkRect->size(), check); + *pixmapRect = QStyle::alignedRect(opt->direction, opt->decorationAlignment, + pixmapRect->size(), decoration); + // the text takes up all available space, unless the decoration is not shown as selected + if (opt->showDecorationSelected) + *textRect = display; + else + *textRect = QStyle::alignedRect(opt->direction, opt->displayAlignment, + textRect->size().boundedTo(display.size()), display); + } else { + *checkRect = check; + *pixmapRect = decoration; + *textRect = display; + } +} +#endif // QT_NO_ITEMVIEWS + + +#ifndef QT_NO_TABBAR +/*! \internal + Compute the textRect and the pixmapRect from the opt rect + + Uses the same computation than in QTabBar::tabSizeHint + */ +void QCommonStylePrivate::tabLayout(const QStyleOptionTabV3 *opt, const QWidget *widget, QRect *textRect, QRect *iconRect) const +{ + Q_ASSERT(textRect); + Q_ASSERT(iconRect); + QRect tr = opt->rect; + bool verticalTabs = opt->shape == QTabBar::RoundedEast + || opt->shape == QTabBar::RoundedWest + || opt->shape == QTabBar::TriangularEast + || opt->shape == QTabBar::TriangularWest; + if (verticalTabs) + tr.setRect(0, 0, tr.height(), tr.width()); //0, 0 as we will have a translate transform + + int verticalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftVertical, opt, widget); + int horizontalShift = proxyStyle->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, opt, widget); + int hpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2; + int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2; + if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth) + verticalShift = -verticalShift; + tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding); + bool selected = opt->state & QStyle::State_Selected; + if (selected) { + tr.setTop(tr.top() - verticalShift); + tr.setRight(tr.right() - horizontalShift); + } + + // left widget + if (!opt->leftButtonSize.isEmpty()) { + tr.setLeft(tr.left() + 4 + + (verticalTabs ? opt->leftButtonSize.height() : opt->leftButtonSize.width())); + } + // right widget + if (!opt->rightButtonSize.isEmpty()) { + tr.setRight(tr.right() - 4 - + (verticalTabs ? opt->rightButtonSize.height() : opt->rightButtonSize.width())); + } + + // icon + if (!opt->icon.isNull()) { + QSize iconSize = opt->iconSize; + if (!iconSize.isValid()) { + int iconExtent = proxyStyle->pixelMetric(QStyle::PM_SmallIconSize); + iconSize = QSize(iconExtent, iconExtent); + } + QSize tabIconSize = opt->icon.actualSize(iconSize, + (opt->state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled, + (opt->state & QStyle::State_Selected) ? QIcon::On : QIcon::Off ); + + *iconRect = QRect(tr.left(), tr.center().y() - tabIconSize.height() / 2, + tabIconSize.width(), tabIconSize .height()); + if (!verticalTabs) + *iconRect = proxyStyle->visualRect(opt->direction, opt->rect, *iconRect); + tr.setLeft(tr.left() + tabIconSize.width() + 4); + } + + if (!verticalTabs) + tr = proxyStyle->visualRect(opt->direction, opt->rect, tr); + + *textRect = tr; +} +#endif //QT_NO_TABBAR + + +/*! + \reimp +*/ +void QCommonStyle::drawControl(ControlElement element, const QStyleOption *opt, + QPainter *p, const QWidget *widget) const +{ + Q_D(const QCommonStyle); + switch (element) { + + case CE_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + proxy()->drawControl(CE_PushButtonBevel, btn, p, widget); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_PushButtonContents, btn, widget); + proxy()->drawControl(CE_PushButtonLabel, &subopt, p, widget); + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(SE_PushButtonFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QRect br = btn->rect; + int dbi = proxy()->pixelMetric(PM_ButtonDefaultIndicator, btn, widget); + if (btn->features & QStyleOptionButton::DefaultButton) + proxy()->drawPrimitive(PE_FrameDefaultButton, opt, p, widget); + if (btn->features & QStyleOptionButton::AutoDefaultButton) + br.setCoords(br.left() + dbi, br.top() + dbi, br.right() - dbi, br.bottom() - dbi); + if (!(btn->features & (QStyleOptionButton::Flat | QStyleOptionButton::CommandLinkButton)) + || btn->state & (State_Sunken | State_On) + || (btn->features & QStyleOptionButton::CommandLinkButton && btn->state & State_MouseOver)) { + QStyleOptionButton tmpBtn = *btn; + tmpBtn.rect = br; + proxy()->drawPrimitive(PE_PanelButtonCommand, &tmpBtn, p, widget); + } + if (btn->features & QStyleOptionButton::HasMenu) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, btn, widget); + QRect ir = btn->rect; + QStyleOptionButton newBtn = *btn; + newBtn.rect = QRect(ir.right() - mbi + 2, ir.height()/2 - mbi/2 + 3, mbi - 6, mbi - 6); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + } + break; + case CE_PushButtonLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QRect textRect = button->rect; + uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic; + if (!proxy()->styleHint(SH_UnderlineShortcut, button, widget)) + tf |= Qt::TextHideMnemonic; + + if (!button->icon.isNull()) { + //Center both icon and text + QRect iconRect; + QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + if (mode == QIcon::Normal && button->state & State_HasFocus) + mode = QIcon::Active; + QIcon::State state = QIcon::Off; + if (button->state & State_On) + state = QIcon::On; + + QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state); + int labelWidth = pixmap.width(); + int labelHeight = pixmap.height(); + int iconSpacing = 4;//### 4 is currently hardcoded in QPushButton::sizeHint() + int textWidth = button->fontMetrics.boundingRect(opt->rect, tf, button->text).width(); + if (!button->text.isEmpty()) + labelWidth += (textWidth + iconSpacing); + + iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2, + textRect.y() + (textRect.height() - labelHeight) / 2, + pixmap.width(), pixmap.height()); + + iconRect = visualRect(button->direction, textRect, iconRect); + + tf |= Qt::AlignLeft; //left align, we adjust the text-rect instead + + if (button->direction == Qt::RightToLeft) + textRect.setRight(iconRect.left() - iconSpacing); + else + textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing); + + if (button->state & (State_On | State_Sunken)) + iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget)); + p->drawPixmap(iconRect, pixmap); + } else { + tf |= Qt::AlignHCenter; + } + if (button->state & (State_On | State_Sunken)) + textRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget)); + + if (button->features & QStyleOptionButton::HasMenu) { + int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget); + if (button->direction == Qt::LeftToRight) + textRect = textRect.adjusted(0, 0, -indicatorSize, 0); + else + textRect = textRect.adjusted(indicatorSize, 0, 0, 0); + } + proxy()->drawItemText(p, textRect, tf, button->palette, (button->state & State_Enabled), + button->text, QPalette::ButtonText); + } + break; + case CE_RadioButton: + case CE_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, btn, widget); + proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, + &subopt, p, widget); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, widget); + proxy()->drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, widget); + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect + : SE_CheckBoxFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; + case CE_RadioButtonLabel: + case CE_CheckBoxLabel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + uint alignment = visualAlignment(btn->direction, Qt::AlignLeft | Qt::AlignVCenter); + + if (!proxy()->styleHint(SH_UnderlineShortcut, btn, widget)) + alignment |= Qt::TextHideMnemonic; + QPixmap pix; + QRect textRect = btn->rect; + if (!btn->icon.isNull()) { + pix = btn->icon.pixmap(btn->iconSize, btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled); + proxy()->drawItemPixmap(p, btn->rect, alignment, pix); + if (btn->direction == Qt::RightToLeft) + textRect.setRight(textRect.right() - btn->iconSize.width() - 4); + else + textRect.setLeft(textRect.left() + btn->iconSize.width() + 4); + } + if (!btn->text.isEmpty()){ + proxy()->drawItemText(p, textRect, alignment | Qt::TextShowMnemonic, + btn->palette, btn->state & State_Enabled, btn->text, QPalette::WindowText); + } + } + break; +#ifndef QT_NO_MENU + case CE_MenuScroller: { + p->fillRect(opt->rect, opt->palette.background()); + QStyleOption arrowOpt = *opt; + arrowOpt.state |= State_Enabled; + proxy()->drawPrimitive(((opt->state & State_DownArrow) ? PE_IndicatorArrowDown : PE_IndicatorArrowUp), + &arrowOpt, p, widget); + break; } + case CE_MenuTearoff: + if (opt->state & State_Selected) + p->fillRect(opt->rect, opt->palette.brush(QPalette::Highlight)); + else + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + p->setPen(QPen(opt->palette.dark().color(), 1, Qt::DashLine)); + p->drawLine(opt->rect.x() + 2, opt->rect.y() + opt->rect.height() / 2 - 1, + opt->rect.x() + opt->rect.width() - 4, + opt->rect.y() + opt->rect.height() / 2 - 1); + p->setPen(QPen(opt->palette.light().color(), 1, Qt::DashLine)); + p->drawLine(opt->rect.x() + 2, opt->rect.y() + opt->rect.height() / 2, + opt->rect.x() + opt->rect.width() - 4, opt->rect.y() + opt->rect.height() / 2); + break; +#endif // QT_NO_MENU +#ifndef QT_NO_MENUBAR + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip + | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), (mbi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + if (!pix.isNull()) + proxy()->drawItemPixmap(p,mbi->rect, alignment, pix); + else + proxy()->drawItemText(p, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, + mbi->text, QPalette::ButtonText); + } + break; + case CE_MenuBarEmptyArea: + if (widget && !widget->testAttribute(Qt::WA_NoSystemBackground)) + p->eraseRect(opt->rect); + break; +#endif // QT_NO_MENUBAR +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBar: + if (const QStyleOptionProgressBar *pb + = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + QStyleOptionProgressBarV2 subopt = *pb; + subopt.rect = subElementRect(SE_ProgressBarGroove, pb, widget); + proxy()->drawControl(CE_ProgressBarGroove, &subopt, p, widget); + subopt.rect = subElementRect(SE_ProgressBarContents, pb, widget); + proxy()->drawControl(CE_ProgressBarContents, &subopt, p, widget); + if (pb->textVisible) { + subopt.rect = subElementRect(SE_ProgressBarLabel, pb, widget); + proxy()->drawControl(CE_ProgressBarLabel, &subopt, p, widget); + } + } + break; + case CE_ProgressBarGroove: + if (opt->rect.isValid()) + qDrawShadePanel(p, opt->rect, opt->palette, true, 1, + &opt->palette.brush(QPalette::Window)); + break; + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + bool vertical = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + vertical = (pb2->orientation == Qt::Vertical); + } + if (!vertical) { + QPalette::ColorRole textRole = QPalette::NoRole; + if ((pb->textAlignment & Qt::AlignCenter) && pb->textVisible + && ((qint64(pb->progress) - qint64(pb->minimum)) * 2 >= (qint64(pb->maximum) - qint64(pb->minimum)))) { + textRole = QPalette::HighlightedText; + //Draw text shadow, This will increase readability when the background of same color + QRect shadowRect(pb->rect); + shadowRect.translate(1,1); + QColor shadowColor = (pb->palette.color(textRole).value() <= 128) + ? QColor(255,255,255,160) : QColor(0,0,0,160); + QPalette shadowPalette = pb->palette; + shadowPalette.setColor(textRole, shadowColor); + proxy()->drawItemText(p, shadowRect, Qt::AlignCenter | Qt::TextSingleLine, shadowPalette, + pb->state & State_Enabled, pb->text, textRole); + } + proxy()->drawItemText(p, pb->rect, Qt::AlignCenter | Qt::TextSingleLine, pb->palette, + pb->state & State_Enabled, pb->text, textRole); + } + } + break; + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + + QRect rect = pb->rect; + bool vertical = false; + bool inverted = false; + qint64 minimum = qint64(pb->minimum); + qint64 maximum = qint64(pb->maximum); + qint64 progress = qint64(pb->progress); + + // Get extra style options if version 2 + const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt); + if (pb2) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + QMatrix m; + + if (vertical) { + rect = QRect(rect.y(), rect.x(), rect.height(), rect.width()); // flip width and height + m.rotate(90); + m.translate(0, -(rect.height() + rect.y()*2)); + } + + QPalette pal2 = pb->palette; + // Correct the highlight color if it is the same as the background + if (pal2.highlight() == pal2.background()) + pal2.setColor(QPalette::Highlight, pb->palette.color(QPalette::Active, + QPalette::Highlight)); + bool reverse = ((!vertical && (pb->direction == Qt::RightToLeft)) || vertical); + if (inverted) + reverse = !reverse; + int w = rect.width(); + if (pb->minimum == 0 && pb->maximum == 0) { + // draw busy indicator + int x = (progress - minimum) % (w * 2); + if (x > w) + x = 2 * w - x; + x = reverse ? rect.right() - x : x + rect.x(); + p->setPen(QPen(pal2.highlight().color(), 4)); + p->drawLine(x, rect.y(), x, rect.height()); + } else { + const int unit_width = proxy()->pixelMetric(PM_ProgressBarChunkWidth, pb, widget); + if (!unit_width) + return; + + int u; + if (unit_width > 1) + u = ((rect.width() + unit_width) / unit_width); + else + u = w / unit_width; + qint64 p_v = progress - minimum; + qint64 t_s = (maximum - minimum) ? (maximum - minimum) : qint64(1); + + if (u > 0 && p_v >= INT_MAX / u && t_s >= u) { + // scale down to something usable. + p_v /= u; + t_s /= u; + } + + // nu < tnu, if last chunk is only a partial chunk + int tnu, nu; + tnu = nu = p_v * u / t_s; + + if (nu * unit_width > w) + --nu; + + // Draw nu units out of a possible u of unit_width + // width, each a rectangle bordered by background + // color, all in a sunken panel with a percentage text + // display at the end. + int x = 0; + int x0 = reverse ? rect.right() - ((unit_width > 1) ? unit_width : 0) + : rect.x(); + + QStyleOptionProgressBarV2 pbBits = *pb; + pbBits.rect = rect; + pbBits.palette = pal2; + int myY = pbBits.rect.y(); + int myHeight = pbBits.rect.height(); + pbBits.state = State_None; + for (int i = 0; i < nu; ++i) { + pbBits.rect.setRect(x0 + x, myY, unit_width, myHeight); + pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect(); + proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget); + x += reverse ? -unit_width : unit_width; + } + + // Draw the last partial chunk to fill up the + // progress bar entirely + if (nu < tnu) { + int pixels_left = w - (nu * unit_width); + int offset = reverse ? x0 + x + unit_width-pixels_left : x0 + x; + pbBits.rect.setRect(offset, myY, pixels_left, myHeight); + pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect(); + proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget); + } + } + } + break; +#endif // QT_NO_PROGRESSBAR + case CE_HeaderLabel: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QRect rect = header->rect; + if (!header->icon.isNull()) { + QPixmap pixmap + = header->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), (header->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + int pixw = pixmap.width(); + + QRect aligned = alignedRect(header->direction, QFlag(header->iconAlignment), pixmap.size(), rect); + QRect inter = aligned.intersected(rect); + p->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width(), inter.height()); + + if (header->direction == Qt::LeftToRight) + rect.setLeft(rect.left() + pixw + 2); + else + rect.setRight(rect.right() - pixw - 2); + } + if (header->state & QStyle::State_On) { + QFont fnt = p->font(); + fnt.setBold(true); + p->setFont(fnt); + } + proxy()->drawItemText(p, rect, header->textAlignment, header->palette, + (header->state & State_Enabled), header->text, QPalette::ButtonText); + } + break; +#ifndef QT_NO_TOOLBUTTON + case CE_ToolButtonLabel: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + 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, opt, widget)) + alignment |= Qt::TextHideMnemonic; + rect.translate(shiftX, shiftY); + p->setFont(toolbutton->font); + proxy()->drawItemText(p, rect, alignment, toolbutton->palette, + opt->state & State_Enabled, toolbutton->text, + QPalette::ButtonText); + } 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 ((opt->state & State_MouseOver) && (opt->state & State_AutoRaise)) + mode = QIcon::Active; + else + mode = QIcon::Normal; + pm = toolbutton->icon.pixmap(toolbutton->rect.size().boundedTo(toolbutton->iconSize), + mode, state); + pmSize = pm.size(); + } + + if (toolbutton->toolButtonStyle != Qt::ToolButtonIconOnly) { + p->setFont(toolbutton->font); + QRect pr = rect, + tr = rect; + int alignment = Qt::TextShowMnemonic; + if (!proxy()->styleHint(SH_UnderlineShortcut, opt, widget)) + alignment |= Qt::TextHideMnemonic; + + if (toolbutton->toolButtonStyle == Qt::ToolButtonTextUnderIcon) { + pr.setHeight(pmSize.height() + 6); + tr.adjust(0, pr.height() - 1, 0, -2); + pr.translate(shiftX, shiftY); + if (!hasArrow) { + proxy()->drawItemPixmap(p, pr, Qt::AlignCenter, pm); + } else { + drawArrow(this, toolbutton, pr, p, widget); + } + alignment |= Qt::AlignCenter; + } else { + pr.setWidth(pmSize.width() + 8); + tr.adjust(pr.width(), 0, 0, 0); + pr.translate(shiftX, shiftY); + if (!hasArrow) { + proxy()->drawItemPixmap(p, QStyle::visualRect(opt->direction, rect, pr), Qt::AlignCenter, pm); + } else { + drawArrow(this, toolbutton, pr, p, widget); + } + alignment |= Qt::AlignLeft | Qt::AlignVCenter; + } + tr.translate(shiftX, shiftY); + proxy()->drawItemText(p, QStyle::visualRect(opt->direction, rect, tr), alignment, toolbutton->palette, + toolbutton->state & State_Enabled, toolbutton->text, + QPalette::ButtonText); + } else { + rect.translate(shiftX, shiftY); + if (hasArrow) { + drawArrow(this, toolbutton, rect, p, widget); + } else { + proxy()->drawItemPixmap(p, rect, Qt::AlignCenter, pm); + } + } + } + } + break; +#endif // QT_NO_TOOLBUTTON +#ifndef QT_NO_TOOLBOX + case CE_ToolBoxTab: + if (const QStyleOptionToolBox *tb = qstyleoption_cast<const QStyleOptionToolBox *>(opt)) { + proxy()->drawControl(CE_ToolBoxTabShape, tb, p, widget); + proxy()->drawControl(CE_ToolBoxTabLabel, tb, p, widget); + } + break; + case CE_ToolBoxTabShape: + if (const QStyleOptionToolBox *tb = qstyleoption_cast<const QStyleOptionToolBox *>(opt)) { + int d = 20 + tb->rect.height() - 3; + QPolygon a(7); + if (tb->direction != Qt::RightToLeft) { + a.setPoint(0, -1, tb->rect.height() + 1); + a.setPoint(1, -1, 1); + a.setPoint(2, tb->rect.width() - d, 1); + a.setPoint(3, tb->rect.width() - 20, tb->rect.height() - 2); + a.setPoint(4, tb->rect.width() - 1, tb->rect.height() - 2); + a.setPoint(5, tb->rect.width() - 1, tb->rect.height() + 1); + a.setPoint(6, -1, tb->rect.height() + 1); + } else { + a.setPoint(0, tb->rect.width(), tb->rect.height() + 1); + a.setPoint(1, tb->rect.width(), 1); + a.setPoint(2, d - 1, 1); + a.setPoint(3, 20 - 1, tb->rect.height() - 2); + a.setPoint(4, 0, tb->rect.height() - 2); + a.setPoint(5, 0, tb->rect.height() + 1); + a.setPoint(6, tb->rect.width(), tb->rect.height() + 1); + } + + p->setPen(tb->palette.mid().color().darker(150)); + p->drawPolygon(a); + p->setPen(tb->palette.light().color()); + if (tb->direction != Qt::RightToLeft) { + p->drawLine(0, 2, tb->rect.width() - d, 2); + p->drawLine(tb->rect.width() - d - 1, 2, tb->rect.width() - 21, tb->rect.height() - 1); + p->drawLine(tb->rect.width() - 20, tb->rect.height() - 1, + tb->rect.width(), tb->rect.height() - 1); + } else { + p->drawLine(tb->rect.width() - 1, 2, d - 1, 2); + p->drawLine(d, 2, 20, tb->rect.height() - 1); + p->drawLine(19, tb->rect.height() - 1, + -1, tb->rect.height() - 1); + } + p->setBrush(Qt::NoBrush); + } + break; +#endif // QT_NO_TOOLBOX +#ifndef QT_NO_TABBAR + case CE_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + proxy()->drawControl(CE_TabBarTabShape, tab, p, widget); + proxy()->drawControl(CE_TabBarTabLabel, tab, p, widget); + } + break; + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + p->save(); + + QRect rect(tab->rect); + bool selected = tab->state & State_Selected; + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + int tabOverlap = onlyOne ? 0 : proxy()->pixelMetric(PM_TabBarTabOverlap, opt, widget); + + if (!selected) { + switch (tab->shape) { + case QTabBar::TriangularNorth: + rect.adjust(0, 0, 0, -tabOverlap); + if(!selected) + rect.adjust(1, 1, -1, 0); + break; + case QTabBar::TriangularSouth: + rect.adjust(0, tabOverlap, 0, 0); + if(!selected) + rect.adjust(1, 0, -1, -1); + break; + case QTabBar::TriangularEast: + rect.adjust(tabOverlap, 0, 0, 0); + if(!selected) + rect.adjust(0, 1, -1, -1); + break; + case QTabBar::TriangularWest: + rect.adjust(0, 0, -tabOverlap, 0); + if(!selected) + rect.adjust(1, 1, 0, -1); + break; + default: + break; + } + } + + p->setPen(QPen(tab->palette.foreground(), 0)); + if (selected) { + p->setBrush(tab->palette.base()); + } else { + if (widget && widget->parentWidget()) + p->setBrush(widget->parentWidget()->palette().background()); + else + p->setBrush(tab->palette.background()); + } + + int y; + int x; + QPolygon a(10); + switch (tab->shape) { + case QTabBar::TriangularNorth: + case QTabBar::TriangularSouth: { + a.setPoint(0, 0, -1); + a.setPoint(1, 0, 0); + y = rect.height() - 2; + x = y / 3; + a.setPoint(2, x++, y - 1); + ++x; + a.setPoint(3, x++, y++); + a.setPoint(4, x, y); + + int i; + int right = rect.width() - 1; + for (i = 0; i < 5; ++i) + a.setPoint(9 - i, right - a.point(i).x(), a.point(i).y()); + if (tab->shape == QTabBar::TriangularNorth) + for (i = 0; i < 10; ++i) + a.setPoint(i, a.point(i).x(), rect.height() - 1 - a.point(i).y()); + + a.translate(rect.left(), rect.top()); + p->setRenderHint(QPainter::Antialiasing); + p->translate(0, 0.5); + + QPainterPath path; + path.addPolygon(a); + p->drawPath(path); + break; } + case QTabBar::TriangularEast: + case QTabBar::TriangularWest: { + a.setPoint(0, -1, 0); + a.setPoint(1, 0, 0); + x = rect.width() - 2; + y = x / 3; + a.setPoint(2, x - 1, y++); + ++y; + a.setPoint(3, x++, y++); + a.setPoint(4, x, y); + int i; + int bottom = rect.height() - 1; + for (i = 0; i < 5; ++i) + a.setPoint(9 - i, a.point(i).x(), bottom - a.point(i).y()); + if (tab->shape == QTabBar::TriangularWest) + for (i = 0; i < 10; ++i) + a.setPoint(i, rect.width() - 1 - a.point(i).x(), a.point(i).y()); + a.translate(rect.left(), rect.top()); + p->setRenderHint(QPainter::Antialiasing); + p->translate(0.5, 0); + QPainterPath path; + path.addPolygon(a); + p->drawPath(path); + break; } + default: + break; + } + p->restore(); + } + break; + case CE_ToolBoxTabLabel: + if (const QStyleOptionToolBox *tb = qstyleoption_cast<const QStyleOptionToolBox *>(opt)) { + bool enabled = tb->state & State_Enabled; + bool selected = tb->state & State_Selected; + QPixmap pm = tb->icon.pixmap(proxy()->pixelMetric(QStyle::PM_SmallIconSize, tb, widget), + enabled ? QIcon::Normal : QIcon::Disabled); + + QRect cr = subElementRect(QStyle::SE_ToolBoxTabContents, tb, widget); + QRect tr, ir; + int ih = 0; + if (pm.isNull()) { + tr = cr; + tr.adjust(4, 0, -8, 0); + } else { + int iw = pm.width() + 4; + ih = pm.height(); + ir = QRect(cr.left() + 4, cr.top(), iw + 2, ih); + tr = QRect(ir.right(), cr.top(), cr.width() - ir.right() - 4, cr.height()); + } + + if (selected && proxy()->styleHint(QStyle::SH_ToolBox_SelectedPageTitleBold, tb, widget)) { + QFont f(p->font()); + f.setBold(true); + p->setFont(f); + } + + QString txt = tb->fontMetrics.elidedText(tb->text, Qt::ElideRight, tr.width()); + + if (ih) + p->drawPixmap(ir.left(), (tb->rect.height() - ih) / 2, pm); + + int alignment = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic; + if (!proxy()->styleHint(QStyle::SH_UnderlineShortcut, tb, widget)) + alignment |= Qt::TextHideMnemonic; + proxy()->drawItemText(p, tr, alignment, tb->palette, enabled, txt, QPalette::ButtonText); + + if (!txt.isEmpty() && opt->state & State_HasFocus) { + QStyleOptionFocusRect opt; + opt.rect = tr; + opt.palette = tb->palette; + opt.state = QStyle::State_None; + proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, p, widget); + } + } + break; + case CE_TabBarTabLabel: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + QStyleOptionTabV3 tabV2(*tab); + QRect tr = tabV2.rect; + bool verticalTabs = tabV2.shape == QTabBar::RoundedEast + || tabV2.shape == QTabBar::RoundedWest + || tabV2.shape == QTabBar::TriangularEast + || tabV2.shape == QTabBar::TriangularWest; + + int alignment = Qt::AlignCenter | Qt::TextShowMnemonic; + if (!proxy()->styleHint(SH_UnderlineShortcut, opt, widget)) + alignment |= Qt::TextHideMnemonic; + + if (verticalTabs) { + p->save(); + int newX, newY, newRot; + if (tabV2.shape == QTabBar::RoundedEast || tabV2.shape == QTabBar::TriangularEast) { + newX = tr.width() + tr.x(); + newY = tr.y(); + newRot = 90; + } else { + newX = tr.x(); + newY = tr.y() + tr.height(); + newRot = -90; + } + QTransform m = QTransform::fromTranslate(newX, newY); + m.rotate(newRot); + p->setTransform(m, true); + } + QRect iconRect; + d->tabLayout(&tabV2, widget, &tr, &iconRect); + tr = proxy()->subElementRect(SE_TabBarTabText, opt, widget); //we compute tr twice because the style may override subElementRect + + if (!tabV2.icon.isNull()) { + QPixmap tabIcon = tabV2.icon.pixmap(tabV2.iconSize, + (tabV2.state & State_Enabled) ? QIcon::Normal + : QIcon::Disabled, + (tabV2.state & State_Selected) ? QIcon::On + : QIcon::Off); + p->drawPixmap(iconRect.x(), iconRect.y(), tabIcon); + } + + proxy()->drawItemText(p, tr, alignment, tab->palette, tab->state & State_Enabled, tab->text, QPalette::WindowText); + if (verticalTabs) + p->restore(); + + if (tabV2.state & State_HasFocus) { + const int OFFSET = 1 + pixelMetric(PM_DefaultFrameWidth); + + int x1, x2; + x1 = tabV2.rect.left(); + x2 = tabV2.rect.right() - 1; + + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*tab); + fropt.rect.setRect(x1 + 1 + OFFSET, tabV2.rect.y() + OFFSET, + x2 - x1 - 2*OFFSET, tabV2.rect.height() - 2*OFFSET); + drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; +#endif // QT_NO_TABBAR +#ifndef QT_NO_SIZEGRIP + case CE_SizeGrip: { + p->save(); + int x, y, w, h; + opt->rect.getRect(&x, &y, &w, &h); + + int sw = qMin(h, w); + if (h > w) + p->translate(0, h - w); + else + p->translate(w - h, 0); + + int sx = x; + int sy = y; + int s = sw / 3; + + Qt::Corner corner; + if (const QStyleOptionSizeGrip *sgOpt = qstyleoption_cast<const QStyleOptionSizeGrip *>(opt)) + corner = sgOpt->corner; + else if (opt->direction == Qt::RightToLeft) + corner = Qt::BottomLeftCorner; + else + corner = Qt::BottomRightCorner; + + if (corner == Qt::BottomLeftCorner) { + sx = x + sw; + for (int i = 0; i < 4; ++i) { + p->setPen(QPen(opt->palette.light().color(), 1)); + p->drawLine(x, sy - 1 , sx + 1, sw); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(x, sy, sx, sw); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(x, sy + 1, sx - 1, sw); + sx -= s; + sy += s; + } + } else if (corner == Qt::BottomRightCorner) { + for (int i = 0; i < 4; ++i) { + p->setPen(QPen(opt->palette.light().color(), 1)); + p->drawLine(sx - 1, sw, sw, sy - 1); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(sx, sw, sw, sy); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(sx + 1, sw, sw, sy + 1); + sx += s; + sy += s; + } + } else if (corner == Qt::TopRightCorner) { + sy = y + sw; + for (int i = 0; i < 4; ++i) { + p->setPen(QPen(opt->palette.light().color(), 1)); + p->drawLine(sx - 1, y, sw, sy + 1); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(sx, y, sw, sy); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(sx + 1, y, sw, sy - 1); + sx += s; + sy -= s; + } + } else if (corner == Qt::TopLeftCorner) { + for (int i = 0; i < 4; ++i) { + p->setPen(QPen(opt->palette.light().color(), 1)); + p->drawLine(x, sy - 1, sx - 1, y); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(x, sy, sx, y); + p->setPen(QPen(opt->palette.dark().color(), 1)); + p->drawLine(x, sy + 1, sx + 1, y); + sx += s; + sy += s; + } + } + p->restore(); + break; } +#endif // QT_NO_SIZEGRIP +#ifndef QT_NO_RUBBERBAND + case CE_RubberBand: { + if (const QStyleOptionRubberBand *rbOpt = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + QPixmap tiledPixmap(16, 16); + QPainter pixmapPainter(&tiledPixmap); + pixmapPainter.setPen(Qt::NoPen); + pixmapPainter.setBrush(Qt::Dense4Pattern); + pixmapPainter.setBackground(QBrush(opt->palette.base())); + pixmapPainter.setBackgroundMode(Qt::OpaqueMode); + pixmapPainter.drawRect(0, 0, tiledPixmap.width(), tiledPixmap.height()); + pixmapPainter.end(); + // ### workaround for borked XRENDER + tiledPixmap = QPixmap::fromImage(tiledPixmap.toImage()); + + p->save(); + QRect r = opt->rect; + QStyleHintReturnMask mask; + if (proxy()->styleHint(QStyle::SH_RubberBand_Mask, opt, widget, &mask)) + p->setClipRegion(mask.region); + p->drawTiledPixmap(r.x(), r.y(), r.width(), r.height(), tiledPixmap); + p->setPen(opt->palette.color(QPalette::Active, QPalette::WindowText)); + p->setBrush(Qt::NoBrush); + p->drawRect(r.adjusted(0, 0, -1, -1)); + if (rbOpt->shape == QRubberBand::Rectangle) + p->drawRect(r.adjusted(3, 3, -4, -4)); + p->restore(); + } + break; } +#endif // QT_NO_RUBBERBAND +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) { + QRect r = dwOpt->rect.adjusted(0, 0, -1, -1); + if (dwOpt->movable) { + p->setPen(dwOpt->palette.color(QPalette::Dark)); + p->drawRect(r); + } + + if (!dwOpt->title.isEmpty()) { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(opt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + if (verticalTitleBar) { + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + p->save(); + p->translate(r.left(), r.top() + r.width()); + p->rotate(-90); + p->translate(-r.left(), -r.top()); + } + + const int indent = p->fontMetrics().descent(); + proxy()->drawItemText(p, r.adjusted(indent + 1, 1, -indent - 1, -1), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, dwOpt->title, + QPalette::WindowText); + + if (verticalTitleBar) + p->restore(); + } + } + break; +#endif // QT_NO_DOCKWIDGET + case CE_Header: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QRegion clipRegion = p->clipRegion(); + p->setClipRect(opt->rect); + proxy()->drawControl(CE_HeaderSection, header, p, widget); + QStyleOptionHeader subopt = *header; + subopt.rect = subElementRect(SE_HeaderLabel, header, widget); + if (subopt.rect.isValid()) + proxy()->drawControl(CE_HeaderLabel, &subopt, p, widget); + if (header->sortIndicator != QStyleOptionHeader::None) { + subopt.rect = subElementRect(SE_HeaderArrow, opt, widget); + proxy()->drawPrimitive(PE_IndicatorHeaderArrow, &subopt, p, widget); + } + p->setClipRegion(clipRegion); + } + break; + case CE_FocusFrame: + p->fillRect(opt->rect, opt->palette.foreground()); + break; + case CE_HeaderSection: + qDrawShadePanel(p, opt->rect, opt->palette, + opt->state & State_Sunken, 1, + &opt->palette.brush(QPalette::Button)); + break; + case CE_HeaderEmptyArea: + p->fillRect(opt->rect, opt->palette.background()); + break; +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget); + p->save(); + p->setClipRect(editRect); + if (!cb->currentIcon.isNull()) { + QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(cb->iconSize.width() + 4); + iconRect = alignedRect(cb->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + if (cb->editable) + p->fillRect(iconRect, opt->palette.brush(QPalette::Base)); + proxy()->drawItemPixmap(p, iconRect, Qt::AlignCenter, pixmap); + + if (cb->direction == Qt::RightToLeft) + editRect.translate(-4 - cb->iconSize.width(), 0); + else + editRect.translate(cb->iconSize.width() + 4, 0); + } + if (!cb->currentText.isEmpty() && !cb->editable) { + proxy()->drawItemText(p, editRect.adjusted(1, 0, -1, 0), + visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter), + cb->palette, cb->state & State_Enabled, cb->currentText); + } + p->restore(); + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + if (const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) { + // Compatibility with styles that use PE_PanelToolBar + QStyleOptionFrame frame; + frame.QStyleOption::operator=(*toolBar); + frame.lineWidth = toolBar->lineWidth; + frame.midLineWidth = toolBar->midLineWidth; + proxy()->drawPrimitive(PE_PanelToolBar, opt, p, widget); + + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) + break; + qDrawShadePanel(p, toolBar->rect, toolBar->palette, false, toolBar->lineWidth, + &toolBar->palette.brush(QPalette::Button)); + } + break; +#endif // QT_NO_TOOLBAR + case CE_ColumnViewGrip: { + // draw background gradients + QLinearGradient g(0, 0, opt->rect.width(), 0); + g.setColorAt(0, opt->palette.color(QPalette::Active, QPalette::Mid)); + g.setColorAt(0.5, Qt::white); + p->fillRect(QRect(0, 0, opt->rect.width(), opt->rect.height()), g); + + // draw the two lines + QPen pen(p->pen()); + pen.setWidth(opt->rect.width()/20); + pen.setColor(opt->palette.color(QPalette::Active, QPalette::Dark)); + p->setPen(pen); + + int line1starting = opt->rect.width()*8 / 20; + int line2starting = opt->rect.width()*13 / 20; + int top = opt->rect.height()*20/75; + int bottom = opt->rect.height() - 1 - top; + p->drawLine(line1starting, top, line1starting, bottom); + p->drawLine(line2starting, top, line2starting, bottom); + } + break; + +#ifndef QT_NO_ITEMVIEWS + case CE_ItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + p->save(); + p->setClipRect(opt->rect); + + QRect checkRect = subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget); + QRect iconRect = subElementRect(SE_ItemViewItemDecoration, vopt, widget); + QRect textRect = subElementRect(SE_ItemViewItemText, vopt, widget); + + // draw the background + proxy()->drawPrimitive(PE_PanelItemViewItem, opt, p, widget); + + // draw the check mark + if (vopt->features & QStyleOptionViewItemV2::HasCheckIndicator) { + QStyleOptionViewItemV4 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_IndicatorViewItemCheck, &option, p, 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(p, iconRect, vopt->decorationAlignment, mode, state); + + // draw the text + if (!vopt->text.isEmpty()) { + QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if (vopt->state & QStyle::State_Selected) { + p->setPen(vopt->palette.color(cg, QPalette::HighlightedText)); + } else { + p->setPen(vopt->palette.color(cg, QPalette::Text)); + } + if (vopt->state & QStyle::State_Editing) { + p->setPen(vopt->palette.color(cg, QPalette::Text)); + p->drawRect(textRect.adjusted(0, 0, -1, -1)); + } + + d->viewItemDrawText(p, vopt, textRect); + } + + // draw the focus rect + if (vopt->state & QStyle::State_HasFocus) { + QStyleOptionFocusRect o; + o.QStyleOption::operator=(*vopt); + o.rect = proxy()->subElementRect(SE_ItemViewItemFocusRect, vopt, widget); + o.state |= QStyle::State_KeyboardFocusChange; + o.state |= QStyle::State_Item; + QPalette::ColorGroup cg = (vopt->state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = vopt->palette.color(cg, (vopt->state & QStyle::State_Selected) + ? QPalette::Highlight : QPalette::Window); + proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, p, widget); + } + + p->restore(); + } + break; + +#endif // QT_NO_ITEMVIEWS +#ifndef QT_NO_FRAME + case CE_ShapedFrame: + if (const QStyleOptionFrameV3 *f = qstyleoption_cast<const QStyleOptionFrameV3 *>(opt)) { + 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; + QPalette::ColorRole foregroundRole = QPalette::WindowText; + if (widget) + foregroundRole = widget->foregroundRole(); + + switch (frameShape) { + case QFrame::Box: + if (frameShadow == QFrame::Plain) { + qDrawPlainRect(p, f->rect, f->palette.color(foregroundRole), lw); + } else { + qDrawShadeRect(p, f->rect, f->palette, frameShadow == QFrame::Sunken, lw, mlw); + } + break; + case QFrame::StyledPanel: + //keep the compatibility with Qt 4.4 if there is a proxy style. + //be sure to call drawPrimitive(QStyle::PE_Frame) on the proxy style + if (widget) { + widget->style()->drawPrimitive(QStyle::PE_Frame, opt, p, widget); + } else { + proxy()->drawPrimitive(QStyle::PE_Frame, opt, p, widget); + } + break; + case QFrame::Panel: + if (frameShadow == QFrame::Plain) { + qDrawPlainRect(p, f->rect, f->palette.color(foregroundRole), lw); + } else { + qDrawShadePanel(p, f->rect, f->palette, frameShadow == QFrame::Sunken, lw); + } + break; + case QFrame::WinPanel: + if (frameShadow == QFrame::Plain) { + qDrawPlainRect(p, f->rect, f->palette.color(foregroundRole), lw); + } else { + qDrawWinPanel(p, f->rect, f->palette, frameShadow == QFrame::Sunken); + } + break; + case QFrame::HLine: + case QFrame::VLine: { + QPoint p1, p2; + if (frameShape == QFrame::HLine) { + p1 = QPoint(opt->rect.x(), opt->rect.height() / 2); + p2 = QPoint(opt->rect.x() + opt->rect.width(), p1.y()); + } else { + p1 = QPoint(opt->rect.x()+opt->rect.width() / 2, 0); + p2 = QPoint(p1.x(), opt->rect.height()); + } + if (frameShadow == QFrame::Plain) { + QPen oldPen = p->pen(); + p->setPen(QPen(opt->palette.brush(foregroundRole), lw)); + p->drawLine(p1, p2); + p->setPen(oldPen); + } else { + qDrawShadeLine(p, p1, p2, f->palette, frameShadow == QFrame::Sunken, lw, mlw); + } + break; + } + } + } + break; +#endif + default: + break; + } +} + +/*! + \reimp +*/ +QRect QCommonStyle::subElementRect(SubElement sr, const QStyleOption *opt, + const QWidget *widget) const +{ + Q_D(const QCommonStyle); + QRect r; + switch (sr) { + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + int dx1, dx2; + dx1 = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget); + if (btn->features & QStyleOptionButton::AutoDefaultButton) + dx1 += proxy()->pixelMetric(PM_ButtonDefaultIndicator, btn, widget); + dx2 = dx1 * 2; + r.setRect(opt->rect.x() + dx1, opt->rect.y() + dx1, opt->rect.width() - dx2, + opt->rect.height() - dx2); + r = visualRect(opt->direction, opt->rect, r); + } + break; + case SE_PushButtonFocusRect: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + int dbw1 = 0, dbw2 = 0; + if (btn->features & QStyleOptionButton::AutoDefaultButton){ + dbw1 = proxy()->pixelMetric(PM_ButtonDefaultIndicator, btn, widget); + dbw2 = dbw1 * 2; + } + + int dfw1 = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget) + 1, + dfw2 = dfw1 * 2; + + r.setRect(btn->rect.x() + dfw1 + dbw1, btn->rect.y() + dfw1 + dbw1, + btn->rect.width() - dfw2 - dbw2, btn->rect.height()- dfw2 - dbw2); + r = visualRect(opt->direction, opt->rect, r); + } + break; + case SE_CheckBoxIndicator: + { + int h = proxy()->pixelMetric(PM_IndicatorHeight, opt, widget); + r.setRect(opt->rect.x(), opt->rect.y() + ((opt->rect.height() - h) / 2), + proxy()->pixelMetric(PM_IndicatorWidth, opt, widget), h); + r = visualRect(opt->direction, opt->rect, r); + } + break; + + case SE_CheckBoxContents: + { + // Deal with the logical first, then convert it back to screen coords. + QRect ir = visualRect(opt->direction, opt->rect, + subElementRect(SE_CheckBoxIndicator, opt, widget)); + int spacing = proxy()->pixelMetric(PM_CheckBoxLabelSpacing, opt, widget); + r.setRect(ir.right() + spacing, opt->rect.y(), opt->rect.width() - ir.width() - spacing, + opt->rect.height()); + r = visualRect(opt->direction, opt->rect, r); + } + break; + + case SE_CheckBoxFocusRect: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (btn->icon.isNull() && btn->text.isEmpty()) { + r = subElementRect(SE_CheckBoxIndicator, opt, widget); + r.adjust(1, 1, -1, -1); + break; + } + // As above, deal with the logical first, then convert it back to screen coords. + QRect cr = visualRect(btn->direction, btn->rect, + subElementRect(SE_CheckBoxContents, btn, widget)); + + QRect iconRect, textRect; + if (!btn->text.isEmpty()) { + textRect = itemTextRect(opt->fontMetrics, cr, Qt::AlignAbsolute | Qt::AlignLeft + | Qt::AlignVCenter | Qt::TextShowMnemonic, + btn->state & State_Enabled, btn->text); + } + if (!btn->icon.isNull()) { + iconRect = itemPixmapRect(cr, Qt::AlignAbsolute | Qt::AlignLeft | Qt::AlignVCenter + | Qt::TextShowMnemonic, + btn->icon.pixmap(btn->iconSize, QIcon::Normal)); + if (!textRect.isEmpty()) + textRect.translate(iconRect.right() + 4, 0); + } + r = iconRect | textRect; + r.adjust(-3, -2, 3, 2); + r = r.intersected(btn->rect); + r = visualRect(btn->direction, btn->rect, r); + } + break; + + case SE_RadioButtonIndicator: + { + int h = proxy()->pixelMetric(PM_ExclusiveIndicatorHeight, opt, widget); + r.setRect(opt->rect.x(), opt->rect.y() + ((opt->rect.height() - h) / 2), + proxy()->pixelMetric(PM_ExclusiveIndicatorWidth, opt, widget), h); + r = visualRect(opt->direction, opt->rect, r); + } + break; + + case SE_RadioButtonContents: + { + QRect ir = visualRect(opt->direction, opt->rect, + subElementRect(SE_RadioButtonIndicator, opt, widget)); + int spacing = proxy()->pixelMetric(PM_RadioButtonLabelSpacing, opt, widget); + r.setRect(ir.left() + ir.width() + spacing, opt->rect.y(), opt->rect.width() - ir.width() - spacing, + opt->rect.height()); + r = visualRect(opt->direction, opt->rect, r); + break; + } + + case SE_RadioButtonFocusRect: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (btn->icon.isNull() && btn->text.isEmpty()) { + r = subElementRect(SE_RadioButtonIndicator, opt, widget); + r.adjust(1, 1, -1, -1); + break; + } + QRect cr = visualRect(btn->direction, btn->rect, + subElementRect(SE_RadioButtonContents, opt, widget)); + + QRect iconRect, textRect; + if (!btn->text.isEmpty()){ + textRect = itemTextRect(opt->fontMetrics, cr, Qt::AlignAbsolute | Qt::AlignLeft | Qt::AlignVCenter + | Qt::TextShowMnemonic, btn->state & State_Enabled, btn->text); + } + if (!btn->icon.isNull()) { + iconRect = itemPixmapRect(cr, Qt::AlignAbsolute | Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, + btn->icon.pixmap(btn->iconSize, QIcon::Normal)); + if (!textRect.isEmpty()) + textRect.translate(iconRect.right() + 4, 0); + } + r = iconRect | textRect; + r.adjust(-3, -2, 3, 2); + r = r.intersected(btn->rect); + r = visualRect(btn->direction, btn->rect, r); + } + break; +#ifndef QT_NO_SLIDER + case SE_SliderFocusRect: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + if (slider->orientation == Qt::Horizontal) + r.setRect(0, tickOffset - 1, slider->rect.width(), thickness + 2); + else + r.setRect(tickOffset - 1, 0, thickness + 2, slider->rect.height()); + r = r.intersected(slider->rect); + r = visualRect(opt->direction, opt->rect, r); + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_PROGRESSBAR + case SE_ProgressBarGroove: + case SE_ProgressBarContents: + case SE_ProgressBarLabel: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + int textw = 0; + bool vertical = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + vertical = (pb2->orientation == Qt::Vertical); + } + if (!vertical) { + if (pb->textVisible) + textw = qMax(pb->fontMetrics.width(pb->text), pb->fontMetrics.width(QLatin1String("100%"))) + 6; + } + + if ((pb->textAlignment & Qt::AlignCenter) == 0) { + if (sr != SE_ProgressBarLabel) + r.setCoords(pb->rect.left(), pb->rect.top(), + pb->rect.right() - textw, pb->rect.bottom()); + else + r.setCoords(pb->rect.right() - textw, pb->rect.top(), + pb->rect.right(), pb->rect.bottom()); + } else { + r = pb->rect; + } + r = visualRect(pb->direction, pb->rect, r); + } + break; +#endif // QT_NO_PROGRESSBAR +#ifdef QT3_SUPPORT + case SE_Q3DockWindowHandleRect: + if (const QStyleOptionQ3DockWindow *dw = qstyleoption_cast<const QStyleOptionQ3DockWindow *>(opt)) { + if (!dw->docked || !dw->closeEnabled) + r.setRect(0, 0, dw->rect.width(), dw->rect.height()); + else { + if (dw->state & State_Horizontal) + r.setRect(0, 15, dw->rect.width(), dw->rect.height() - 15); + else + r.setRect(0, 1, dw->rect.width() - 15, dw->rect.height() - 1); + } + r = visualRect(opt->direction, opt->rect, r); + } + break; +#endif // QT3_SUPPORT +#ifndef QT_NO_COMBOBOX + case SE_ComboBoxFocusRect: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + int margin = cb->frame ? 3 : 0; + r.setRect(opt->rect.left() + margin, opt->rect.top() + margin, + opt->rect.width() - 2*margin - 16, opt->rect.height() - 2*margin); + r = visualRect(opt->direction, opt->rect, r); + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_TOOLBOX + case SE_ToolBoxTabContents: + r = opt->rect; + r.adjust(0, 0, -30, 0); + break; +#endif // QT_NO_TOOLBOX + case SE_HeaderLabel: { + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); + r.setRect(opt->rect.x() + margin, opt->rect.y() + margin, + opt->rect.width() - margin * 2, opt->rect.height() - margin * 2); + + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // Subtract width needed for arrow, if there is one + if (header->sortIndicator != QStyleOptionHeader::None) { + if (opt->state & State_Horizontal) + r.setWidth(r.width() - (opt->rect.height() / 2) - (margin * 2)); + else + r.setHeight(r.height() - (opt->rect.width() / 2) - (margin * 2)); + } + } + r = visualRect(opt->direction, opt->rect, r); + break; } + case SE_HeaderArrow: { + int h = opt->rect.height(); + int w = opt->rect.width(); + int x = opt->rect.x(); + int y = opt->rect.y(); + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); + + if (opt->state & State_Horizontal) { + int horiz_size = h / 2; + r.setRect(x + w - margin * 2 - horiz_size, y + 5, + horiz_size, h - margin * 2 - 5); + } else { + int vert_size = w / 2; + r.setRect(x + 5, y + h - margin * 2 - vert_size, + w - margin * 2 - 5, vert_size); + } + r = visualRect(opt->direction, opt->rect, r); + break; } + + case SE_RadioButtonClickRect: + r = subElementRect(SE_RadioButtonFocusRect, opt, widget); + r |= subElementRect(SE_RadioButtonIndicator, opt, widget); + break; + case SE_CheckBoxClickRect: + r = subElementRect(SE_CheckBoxFocusRect, opt, widget); + r |= subElementRect(SE_CheckBoxIndicator, opt, widget); + break; +#ifndef QT_NO_TABWIDGET + case SE_TabWidgetTabBar: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + r.setSize(twf->tabBarSize); + const uint alingMask = Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter; + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + // Constrain the size now, otherwise, center could get off the page + // This of course repeated for all the other directions + r.setWidth(qMin(r.width(), twf->rect.width() + - twf->leftCornerWidgetSize.width() + - twf->rightCornerWidgetSize.width())); + switch (proxy()->styleHint(SH_TabBar_Alignment, twf, widget) & alingMask) { + default: + case Qt::AlignLeft: + r.moveTopLeft(QPoint(twf->leftCornerWidgetSize.width(), 0)); + break; + case Qt::AlignHCenter: + r.moveTopLeft(QPoint(twf->rect.center().x() - qRound(r.width() / 2.0f) + + (twf->leftCornerWidgetSize.width() / 2) + - (twf->rightCornerWidgetSize.width() / 2), 0)); + break; + case Qt::AlignRight: + r.moveTopLeft(QPoint(twf->rect.width() - twf->tabBarSize.width() + - twf->rightCornerWidgetSize.width(), 0)); + break; + } + r = visualRect(twf->direction, twf->rect, r); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r.setWidth(qMin(r.width(), twf->rect.width() + - twf->leftCornerWidgetSize.width() + - twf->rightCornerWidgetSize.width())); + switch (proxy()->styleHint(SH_TabBar_Alignment, twf, widget) & alingMask) { + default: + case Qt::AlignLeft: + r.moveTopLeft(QPoint(twf->leftCornerWidgetSize.width(), + twf->rect.height() - twf->tabBarSize.height())); + break; + case Qt::AlignHCenter: + r.moveTopLeft(QPoint(twf->rect.center().x() - qRound(r.width() / 2.0f) + + (twf->leftCornerWidgetSize.width() / 2) + - (twf->rightCornerWidgetSize.width() / 2), + twf->rect.height() - twf->tabBarSize.height())); + break; + case Qt::AlignRight: + r.moveTopLeft(QPoint(twf->rect.width() - twf->tabBarSize.width() + - twf->rightCornerWidgetSize.width(), + twf->rect.height() - twf->tabBarSize.height())); + break; + } + r = visualRect(twf->direction, twf->rect, r); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + r.setHeight(qMin(r.height(), twf->rect.height() + - twf->leftCornerWidgetSize.height() + - twf->rightCornerWidgetSize.height())); + switch (proxy()->styleHint(SH_TabBar_Alignment, twf, widget) & alingMask) { + default: + case Qt::AlignLeft: + r.moveTopLeft(QPoint(twf->rect.width() - twf->tabBarSize.width(), + twf->leftCornerWidgetSize.height())); + break; + case Qt::AlignHCenter: + r.moveTopLeft(QPoint(twf->rect.width() - twf->tabBarSize.width(), + twf->rect.center().y() - r.height() / 2)); + break; + case Qt::AlignRight: + r.moveTopLeft(QPoint(twf->rect.width() - twf->tabBarSize.width(), + twf->rect.height() - twf->tabBarSize.height() + - twf->rightCornerWidgetSize.height())); + break; + } + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + r.setHeight(qMin(r.height(), twf->rect.height() + - twf->leftCornerWidgetSize.height() + - twf->rightCornerWidgetSize.height())); + switch (proxy()->styleHint(SH_TabBar_Alignment, twf, widget) & alingMask) { + default: + case Qt::AlignLeft: + r.moveTopLeft(QPoint(0, twf->leftCornerWidgetSize.height())); + break; + case Qt::AlignHCenter: + r.moveTopLeft(QPoint(0, twf->rect.center().y() - r.height() / 2)); + break; + case Qt::AlignRight: + r.moveTopLeft(QPoint(0, twf->rect.height() - twf->tabBarSize.height() + - twf->rightCornerWidgetSize.height())); + break; + } + break; + } + } + break; + case SE_TabWidgetTabPane: + case SE_TabWidgetTabContents: + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QStyleOptionTab tabopt; + tabopt.shape = twf->shape; + int overlap = proxy()->pixelMetric(PM_TabBarBaseOverlap, &tabopt, widget); + if (twf->lineWidth == 0) + overlap = 0; + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + r = QRect(QPoint(0,qMax(twf->tabBarSize.height() - overlap, 0)), + QSize(twf->rect.width(), qMin(twf->rect.height() - twf->tabBarSize.height() + overlap, twf->rect.height()))); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r = QRect(QPoint(0,0), QSize(twf->rect.width(), qMin(twf->rect.height() - twf->tabBarSize.height() + overlap, twf->rect.height()))); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + r = QRect(QPoint(0, 0), QSize(qMin(twf->rect.width() - twf->tabBarSize.width() + overlap, twf->rect.width()), twf->rect.height())); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + r = QRect(QPoint(qMax(twf->tabBarSize.width() - overlap, 0), 0), + QSize(qMin(twf->rect.width() - twf->tabBarSize.width() + overlap, twf->rect.width()), twf->rect.height())); + break; + } + if (sr == SE_TabWidgetTabContents && twf->lineWidth > 0) + r.adjust(2, 2, -2, -2); + } + break; + case SE_TabWidgetLeftCorner: + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QRect paneRect = subElementRect(SE_TabWidgetTabPane, twf, widget); + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + r = QRect(QPoint(paneRect.x(), paneRect.y() - twf->leftCornerWidgetSize.height()), + twf->leftCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r = QRect(QPoint(paneRect.x(), paneRect.height()), twf->leftCornerWidgetSize); + break; + default: + break; + } + r = visualRect(twf->direction, twf->rect, r); + } + break; + case SE_TabWidgetRightCorner: + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QRect paneRect = subElementRect(SE_TabWidgetTabPane, twf, widget); + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + r = QRect(QPoint(paneRect.width() - twf->rightCornerWidgetSize.width(), + paneRect.y() - twf->rightCornerWidgetSize.height()), + twf->rightCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r = QRect(QPoint(paneRect.width() - twf->rightCornerWidgetSize.width(), + paneRect.height()), twf->rightCornerWidgetSize); + break; + default: + break; + } + r = visualRect(twf->direction, twf->rect, r); + } + break; + case SE_TabBarTabText: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + QStyleOptionTabV3 tabV3(*tab); + QRect dummyIconRect; + d->tabLayout(&tabV3, widget, &r, &dummyIconRect); + } + break; + case SE_TabBarTabLeftButton: + case SE_TabBarTabRightButton: + if (const QStyleOptionTabV3 *tab = qstyleoption_cast<const QStyleOptionTabV3 *>(opt)) { + bool selected = tab->state & State_Selected; + int verticalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, tab, widget); + int horizontalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, tab, widget); + int hpadding = proxy()->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2; + hpadding = qMax(hpadding, 4); //workaround KStyle returning 0 because they workaround an old bug in Qt + + bool verticalTabs = tab->shape == QTabBar::RoundedEast + || tab->shape == QTabBar::RoundedWest + || tab->shape == QTabBar::TriangularEast + || tab->shape == QTabBar::TriangularWest; + + QRect tr = tab->rect; + if (tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::TriangularSouth) + verticalShift = -verticalShift; + if (verticalTabs) { + qSwap(horizontalShift, verticalShift); + horizontalShift *= -1; + verticalShift *= -1; + } + if (tab->shape == QTabBar::RoundedWest || tab->shape == QTabBar::TriangularWest) + horizontalShift = -horizontalShift; + + tr.adjust(0, 0, horizontalShift, verticalShift); + if (selected) + { + tr.setBottom(tr.bottom() - verticalShift); + tr.setRight(tr.right() - horizontalShift); + } + + QSize size = (sr == SE_TabBarTabLeftButton) ? tab->leftButtonSize : tab->rightButtonSize; + int w = size.width(); + int h = size.height(); + int midHeight = static_cast<int>(qCeil(float(tr.height() - h) / 2)); + int midWidth = ((tr.width() - w) / 2); + + bool atTheTop = true; + switch (tab->shape) { + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + atTheTop = (sr == SE_TabBarTabLeftButton); + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + atTheTop = (sr == SE_TabBarTabRightButton); + break; + default: + if (sr == SE_TabBarTabLeftButton) + r = QRect(tab->rect.x() + hpadding, midHeight, w, h); + else + r = QRect(tab->rect.right() - w - hpadding, midHeight, w, h); + r = visualRect(tab->direction, tab->rect, r); + } + if (verticalTabs) { + if (atTheTop) + r = QRect(midWidth, tr.y() + tab->rect.height() - hpadding - h, w, h); + else + r = QRect(midWidth, tr.y() + hpadding, w, h); + } + } + + break; +#endif // QT_NO_TABWIDGET +#ifndef QT_NO_TABBAR + case SE_TabBarTearIndicator: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r.setRect(tab->rect.left(), tab->rect.top(), 4, opt->rect.height()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + r.setRect(tab->rect.left(), tab->rect.top(), opt->rect.width(), 4); + break; + default: + break; + } + r = visualRect(opt->direction, opt->rect, r); + } + break; +#endif + case SE_TreeViewDisclosureItem: + r = opt->rect; + break; + case SE_LineEditContents: + if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + r = f->rect.adjusted(f->lineWidth, f->lineWidth, -f->lineWidth, -f->lineWidth); + r = visualRect(opt->direction, opt->rect, r); + } + break; + case SE_FrameContents: + if (const QStyleOptionFrameV2 *f = qstyleoption_cast<const QStyleOptionFrameV2 *>(opt)) { + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, f, widget); + r = opt->rect.adjusted(fw, fw, -fw, -fw); + r = visualRect(opt->direction, opt->rect, r); + } + break; + case SE_ShapedFrameContents: + if (const QStyleOptionFrameV3 *f = qstyleoption_cast<const QStyleOptionFrameV3 *>(opt)) { + 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 frameWidth = 0; + + switch (frameShape) { + case QFrame::NoFrame: + frameWidth = 0; + break; + + case QFrame::Box: + case QFrame::HLine: + case QFrame::VLine: + switch (frameShadow) { + case QFrame::Plain: + frameWidth = f->lineWidth; + break; + case QFrame::Raised: + case QFrame::Sunken: + frameWidth = (short)(f->lineWidth*2 + f->midLineWidth); + break; + } + break; + + case QFrame::StyledPanel: + //keep the compatibility with Qt 4.4 if there is a proxy style. + //be sure to call drawPrimitive(QStyle::SE_FrameContents) on the proxy style + if (widget) + return widget->style()->subElementRect(QStyle::SE_FrameContents, opt, widget); + else + return subElementRect(QStyle::SE_FrameContents, opt, widget); + break; + + case QFrame::WinPanel: + frameWidth = 2; + break; + + case QFrame::Panel: + switch (frameShadow) { + case QFrame::Plain: + case QFrame::Raised: + case QFrame::Sunken: + frameWidth = f->lineWidth; + break; + } + break; + } + r = f->rect.adjusted(frameWidth, frameWidth, -frameWidth, -frameWidth); + } + break; +#ifndef QT_NO_DOCKWIDGET + case SE_DockWidgetCloseButton: + case SE_DockWidgetFloatButton: + case SE_DockWidgetTitleBarText: + case SE_DockWidgetIcon: { + int iconSize = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + int buttonMargin = proxy()->pixelMetric(PM_DockWidgetTitleBarButtonMargin, opt, widget); + int margin = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, opt, widget); + QRect rect = opt->rect; + + const QStyleOptionDockWidget *dwOpt + = qstyleoption_cast<const QStyleOptionDockWidget*>(opt); + bool canClose = dwOpt == 0 ? true : dwOpt->closable; + bool canFloat = dwOpt == 0 ? false : dwOpt->floatable; + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(opt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + // If this is a vertical titlebar, we transpose and work as if it was + // horizontal, then transpose again. + + if (verticalTitleBar) { + QSize size = rect.size(); + size.transpose(); + rect.setSize(size); + } + + do { + + int right = rect.right(); + int left = rect.left(); + + QRect closeRect; + if (canClose) { + QSize sz = standardIcon(QStyle::SP_TitleBarCloseButton, + opt, widget).actualSize(QSize(iconSize, iconSize)); + sz += QSize(buttonMargin, buttonMargin); + if (verticalTitleBar) + sz.transpose(); + closeRect = QRect(right - sz.width(), + rect.center().y() - sz.height()/2, + sz.width(), sz.height()); + right = closeRect.left() - 1; + } + if (sr == SE_DockWidgetCloseButton) { + r = closeRect; + break; + } + + QRect floatRect; + if (canFloat) { + QSize sz = standardIcon(QStyle::SP_TitleBarNormalButton, + opt, widget).actualSize(QSize(iconSize, iconSize)); + sz += QSize(buttonMargin, buttonMargin); + if (verticalTitleBar) + sz.transpose(); + floatRect = QRect(right - sz.width(), + rect.center().y() - sz.height()/2, + sz.width(), sz.height()); + right = floatRect.left() - 1; + } + if (sr == SE_DockWidgetFloatButton) { + r = floatRect; + break; + } + + QRect iconRect; + if (const QDockWidget *dw = qobject_cast<const QDockWidget*>(widget)) { + QIcon icon; + if (dw->isFloating()) + icon = dw->windowIcon(); + if (!icon.isNull() + && icon.cacheKey() != QApplication::windowIcon().cacheKey()) { + QSize sz = icon.actualSize(QSize(r.height(), r.height())); + if (verticalTitleBar) + sz.transpose(); + iconRect = QRect(left, rect.center().y() - sz.height()/2, + sz.width(), sz.height()); + left = iconRect.right() + margin; + } + } + if (sr == SE_DockWidgetIcon) { + r = iconRect; + break; + } + + QRect textRect = QRect(left, rect.top(), + right - left, rect.height()); + if (sr == SE_DockWidgetTitleBarText) { + r = textRect; + break; + } + + } while (false); + + if (verticalTitleBar) { + r = QRect(rect.left() + r.top() - rect.top(), + rect.top() + rect.right() - r.right(), + r.height(), r.width()); + } else { + r = visualRect(opt->direction, rect, r); + } + break; + } +#endif +#ifndef QT_NO_ITEMVIEWS + case SE_ItemViewItemCheckIndicator: + if (!qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + r = subElementRect(SE_CheckBoxIndicator, opt, widget); + break; + } + case SE_ItemViewItemDecoration: + case SE_ItemViewItemText: + case SE_ItemViewItemFocusRect: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + if (!d->isViewItemCached(*vopt)) { + d->viewItemLayout(vopt, &d->checkRect, &d->decorationRect, &d->displayRect, false); + if (d->cachedOption) { + delete d->cachedOption; + d->cachedOption = 0; + } + d->cachedOption = new QStyleOptionViewItemV4(*vopt); + } + if (sr == SE_ViewItemCheckIndicator) + r = d->checkRect; + else if (sr == SE_ItemViewItemDecoration) + r = d->decorationRect; + else if (sr == SE_ItemViewItemText || sr == SE_ItemViewItemFocusRect) + r = d->displayRect; + } + break; +#endif //QT_NO_ITEMVIEWS +#ifndef QT_NO_TOOLBAR + case SE_ToolBarHandle: + if (const QStyleOptionToolBar *tbopt = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) { + if (tbopt->features & QStyleOptionToolBar::Movable) { + ///we need to access the widget here because the style option doesn't + //have all the information we need (ie. the layout's margin) + const QToolBar *tb = qobject_cast<const QToolBar*>(widget); + const int margin = tb && tb->layout() ? tb->layout()->margin() : 2; + const int handleExtent = pixelMetric(QStyle::PM_ToolBarHandleExtent, opt, tb); + if (tbopt->state & QStyle::State_Horizontal) { + r = QRect(margin, margin, handleExtent, tbopt->rect.height() - 2*margin); + r = QStyle::visualRect(tbopt->direction, tbopt->rect, r); + } else { + r = QRect(margin, margin, tbopt->rect.width() - 2*margin, handleExtent); + } + } + } + break; +#endif //QT_NO_TOOLBAR + default: + break; + } + return r; +} + +#ifndef QT_NO_DIAL + +static QPolygonF calcArrow(const QStyleOptionSlider *dial, qreal &a) +{ + int width = dial->rect.width(); + int height = dial->rect.height(); + int r = qMin(width, height) / 2; + int currentSliderPosition = dial->upsideDown ? dial->sliderPosition : (dial->maximum - dial->sliderPosition); + + if (dial->maximum == dial->minimum) + a = Q_PI / 2; + else if (dial->dialWrapping) + a = Q_PI * 3 / 2 - (currentSliderPosition - dial->minimum) * 2 * Q_PI + / (dial->maximum - dial->minimum); + else + a = (Q_PI * 8 - (currentSliderPosition - dial->minimum) * 10 * Q_PI + / (dial->maximum - dial->minimum)) / 6; + + int xc = width / 2; + int yc = height / 2; + + int len = r - QStyleHelper::calcBigLineSize(r) - 5; + if (len < 5) + len = 5; + int back = len / 2; + + QPolygonF arrow(3); + arrow[0] = QPointF(0.5 + xc + len * qCos(a), + 0.5 + yc - len * qSin(a)); + arrow[1] = QPointF(0.5 + xc + back * qCos(a + Q_PI * 5 / 6), + 0.5 + yc - back * qSin(a + Q_PI * 5 / 6)); + arrow[2] = QPointF(0.5 + xc + back * qCos(a - Q_PI * 5 / 6), + 0.5 + yc - back * qSin(a - Q_PI * 5 / 6)); + return arrow; +} + +#endif // QT_NO_DIAL + +/*! + \reimp +*/ +void QCommonStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + QPainter *p, const QWidget *widget) const +{ + switch (cc) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + if (slider->subControls == 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; + // Since there is no subrect for tickmarks do a translation here. + p->save(); + p->translate(slider->rect.x(), slider->rect.y()); + p->setPen(slider->palette.foreground().color()); + int v = slider->minimum; + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + v_, available) + fudge; + if (slider->orientation == Qt::Horizontal) { + if (ticks & QSlider::TicksAbove) + p->drawLine(pos, 0, pos, tickOffset - 2); + if (ticks & QSlider::TicksBelow) + p->drawLine(pos, tickOffset + thickness + 1, pos, + slider->rect.height()-1); + } else { + if (ticks & QSlider::TicksAbove) + p->drawLine(0, pos, tickOffset - 2, pos); + if (ticks & QSlider::TicksBelow) + p->drawLine(tickOffset + thickness + 1, pos, + slider->rect.width()-1, pos); + } + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + p->restore(); + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + // Make a copy here and reset it for each primitive. + QStyleOptionSlider newScrollbar = *scrollbar; + State saveFlags = scrollbar->state; + + if (scrollbar->subControls & SC_ScrollBarSubLine) { + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarSubLine, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarSubLine)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarSubLine, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarAddLine) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarAddLine, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarAddLine)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarAddLine, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarSubPage) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarSubPage, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarSubPage)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarSubPage, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarAddPage) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarAddPage, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarAddPage)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarAddPage, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarFirst) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarFirst, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarFirst)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarFirst, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarLast) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarLast, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarLast)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarLast, &newScrollbar, p, widget); + } + } + if (scrollbar->subControls & SC_ScrollBarSlider) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(cc, &newScrollbar, SC_ScrollBarSlider, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarSlider)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + proxy()->drawControl(CE_ScrollBarSlider, &newScrollbar, p, widget); + + if (scrollbar->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(newScrollbar); + fropt.rect.setRect(newScrollbar.rect.x() + 2, newScrollbar.rect.y() + 2, + newScrollbar.rect.width() - 5, + newScrollbar.rect.height() - 5); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + } + } + break; +#endif // QT_NO_SCROLLBAR +#ifdef QT3_SUPPORT + case CC_Q3ListView: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if (lv->subControls & SC_Q3ListView) + p->fillRect(lv->rect, lv->viewportPalette.brush(lv->viewportBGRole)); + } + break; +#endif // QT3_SUPPORT +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox copy = *sb; + PrimitiveElement pe; + + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + QRect r = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxFrame, widget); + qDrawWinPanel(p, r, sb->palette, true); + } + + if (sb->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + + copy.palette = pal2; + + if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinPlus + : PE_IndicatorSpinUp); + + copy.rect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget); + proxy()->drawPrimitive(PE_PanelButtonBevel, ©, p, widget); + copy.rect.adjust(3, 0, -4, 0); + proxy()->drawPrimitive(pe, ©, p, widget); + } + + if (sb->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = sb->state; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + + if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinMinus + : PE_IndicatorSpinDown); + + copy.rect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); + proxy()->drawPrimitive(PE_PanelButtonBevel, ©, p, widget); + copy.rect.adjust(3, 0, -4, 0); + proxy()->drawPrimitive(pe, ©, p, widget); + } + } + break; +#endif // QT_NO_SPINBOX +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + 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; + + if (bflags & State_AutoRaise) { + if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) { + bflags &= ~State_Raised; + } + } + State mflags = bflags; + if (toolbutton->state & State_Sunken) { + if (toolbutton->activeSubControls & SC_ToolButton) + bflags |= State_Sunken; + mflags |= State_Sunken; + } + + QStyleOption tool(0); + tool.palette = toolbutton->palette; + if (toolbutton->subControls & SC_ToolButton) { + if (bflags & (State_Sunken | State_On | State_Raised)) { + tool.rect = button; + tool.state = bflags; + proxy()->drawPrimitive(PE_PanelButtonTool, &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 = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + 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 (mflags & (State_Sunken | State_On | State_Raised)) + proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget); + } 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() + 5 - mbi, ir.y() + ir.height() - mbi + 4, mbi - 6, mbi - 6); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + } + break; +#endif // QT_NO_TOOLBUTTON + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + QRect ir; + if (opt->subControls & 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; + } + + p->fillRect(opt->rect, fillBrush); + + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarLabel, widget); + + p->setPen(tb->palette.highlightedText().color()); + p->drawText(ir.x() + 2, ir.y(), ir.width() - 2, ir.height(), + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + + bool down = false; + QPixmap pm; + + QStyleOption tool(0); + tool.palette = tb->palette; + if (tb->subControls & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarCloseButton, widget); + down = tb->activeSubControls & SC_TitleBarCloseButton && (opt->state & State_Sunken); + if ((tb->titleBarFlags & Qt::WindowType_Mask) == Qt::Tool +#ifndef QT_NO_DOCKWIDGET + || qobject_cast<const QDockWidget *>(widget) +#endif + ) + pm = standardIcon(SP_DockWidgetCloseButton, &tool, widget).pixmap(10, 10); + else + pm = standardIcon(SP_TitleBarCloseButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + + if (tb->subControls & SC_TitleBarMaxButton + && tb->titleBarFlags & Qt::WindowMaximizeButtonHint + && !(tb->titleBarState & Qt::WindowMaximized)) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarMaxButton, widget); + + down = tb->activeSubControls & SC_TitleBarMaxButton && (opt->state & State_Sunken); + pm = standardIcon(SP_TitleBarMaxButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + + if (tb->subControls & SC_TitleBarMinButton + && tb->titleBarFlags & Qt::WindowMinimizeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarMinButton, widget); + down = tb->activeSubControls & SC_TitleBarMinButton && (opt->state & State_Sunken); + pm = standardIcon(SP_TitleBarMinButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + + bool drawNormalButton = (tb->subControls & SC_TitleBarNormalButton) + && (((tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + && (tb->titleBarState & Qt::WindowMinimized)) + || ((tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + && (tb->titleBarState & Qt::WindowMaximized))); + + if (drawNormalButton) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarNormalButton, widget); + down = tb->activeSubControls & SC_TitleBarNormalButton && (opt->state & State_Sunken); + pm = standardIcon(SP_TitleBarNormalButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + + if (tb->subControls & SC_TitleBarShadeButton + && tb->titleBarFlags & Qt::WindowShadeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarShadeButton, widget); + down = (tb->activeSubControls & SC_TitleBarShadeButton && (opt->state & State_Sunken)); + pm = standardIcon(SP_TitleBarShadeButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + + if (tb->subControls & SC_TitleBarUnshadeButton + && tb->titleBarFlags & Qt::WindowShadeButtonHint + && tb->titleBarState & Qt::WindowMinimized) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarUnshadeButton, widget); + + down = tb->activeSubControls & SC_TitleBarUnshadeButton && (opt->state & State_Sunken); + pm = standardIcon(SP_TitleBarUnshadeButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + if (tb->subControls & SC_TitleBarContextHelpButton + && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarContextHelpButton, widget); + + down = tb->activeSubControls & SC_TitleBarContextHelpButton && (opt->state & State_Sunken); + pm = standardIcon(SP_TitleBarContextHelpButton, &tool, widget).pixmap(10, 10); + tool.rect = ir; + tool.state = down ? State_Sunken : State_Raised; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + p->save(); + if (down) + p->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, tb, widget)); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + if (tb->subControls & SC_TitleBarSysMenu && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarSysMenu, widget); + if (!tb->icon.isNull()) { + tb->icon.paint(p, ir); + } else { + int iconSize = proxy()->pixelMetric(PM_SmallIconSize, tb, widget); + pm = standardIcon(SP_TitleBarMenuButton, &tool, widget).pixmap(iconSize, iconSize); + tool.rect = ir; + p->save(); + proxy()->drawItemPixmap(p, ir, Qt::AlignCenter, pm); + p->restore(); + } + } + } + break; +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + // OK, this is more a port of things over + p->save(); + + // avoid dithering + if (p->paintEngine()->hasFeature(QPaintEngine::Antialiasing)) + p->setRenderHint(QPainter::Antialiasing); + + int width = dial->rect.width(); + int height = dial->rect.height(); + qreal r = qMin(width, height) / 2; + qreal d_ = r / 6; + qreal dx = dial->rect.x() + d_ + (width - 2 * r) / 2 + 1; + qreal dy = dial->rect.y() + d_ + (height - 2 * r) / 2 + 1; + QRect br = QRect(int(dx), int(dy), int(r * 2 - 2 * d_ - 2), int(r * 2 - 2 * d_ - 2)); + + QPalette pal = opt->palette; + // draw notches + if (dial->subControls & QStyle::SC_DialTickmarks) { + p->setPen(pal.foreground().color()); + p->drawLines(QStyleHelper::calcLines(dial)); + } + + if (dial->state & State_Enabled) { + p->setBrush(pal.brush(QPalette::ColorRole(proxy()->styleHint(SH_Dial_BackgroundRole, + dial, widget)))); + p->setPen(Qt::NoPen); + p->drawEllipse(br); + p->setBrush(Qt::NoBrush); + } + p->setPen(QPen(pal.dark().color())); + p->drawArc(br, 60 * 16, 180 * 16); + p->setPen(QPen(pal.light().color())); + p->drawArc(br, 240 * 16, 180 * 16); + + qreal a; + QPolygonF arrow(calcArrow(dial, a)); + + p->setPen(Qt::NoPen); + p->setBrush(pal.button()); + p->drawPolygon(arrow); + + a = QStyleHelper::angle(QPointF(width / 2, height / 2), arrow[0]); + p->setBrush(Qt::NoBrush); + + if (a <= 0 || a > 200) { + p->setPen(pal.light().color()); + p->drawLine(arrow[2], arrow[0]); + p->drawLine(arrow[1], arrow[2]); + p->setPen(pal.dark().color()); + p->drawLine(arrow[0], arrow[1]); + } else if (a > 0 && a < 45) { + p->setPen(pal.light().color()); + p->drawLine(arrow[2], arrow[0]); + p->setPen(pal.dark().color()); + p->drawLine(arrow[1], arrow[2]); + p->drawLine(arrow[0], arrow[1]); + } else if (a >= 45 && a < 135) { + p->setPen(pal.dark().color()); + p->drawLine(arrow[2], arrow[0]); + p->drawLine(arrow[1], arrow[2]); + p->setPen(pal.light().color()); + p->drawLine(arrow[0], arrow[1]); + } else if (a >= 135 && a < 200) { + p->setPen(pal.dark().color()); + p->drawLine(arrow[2], arrow[0]); + p->setPen(pal.light().color()); + p->drawLine(arrow[0], arrow[1]); + p->drawLine(arrow[1], arrow[2]); + } + + // draw focus rect around the dial + QStyleOptionFocusRect fropt; + fropt.rect = dial->rect; + fropt.state = dial->state; + fropt.palette = dial->palette; + if (fropt.state & QStyle::State_HasFocus) { + br.adjust(0, 0, 2, 2); + if (dial->subControls & SC_DialTickmarks) { + int r = qMin(width, height) / 2; + br.translate(-r / 6, - r / 6); + br.setWidth(br.width() + r / 3); + br.setHeight(br.height() + r / 3); + } + fropt.rect = br.adjusted(-2, -2, 2, 2); + proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &fropt, p, widget); + } + p->restore(); + } + break; +#endif // QT_NO_DIAL +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + // Draw frame + QRect textRect = proxy()->subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, widget); + QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, opt, SC_GroupBoxCheckBox, widget); + if (groupBox->subControls & QStyle::SC_GroupBoxFrame) { + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = proxy()->subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, widget); + p->save(); + QRegion region(groupBox->rect); + if (!groupBox->text.isEmpty()) { + bool ltr = groupBox->direction == Qt::LeftToRight; + QRect finalRect; + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) { + finalRect = checkBoxRect.united(textRect); + finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0); + } else { + finalRect = textRect; + } + region -= finalRect; + } + p->setClipRegion(region); + proxy()->drawPrimitive(PE_FrameGroupBox, &frame, p, widget); + p->restore(); + } + + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) + p->setPen(textColor); + int alignment = int(groupBox->textAlignment); + if (!proxy()->styleHint(QStyle::SH_UnderlineShortcut, opt, widget)) + alignment |= Qt::TextHideMnemonic; + + proxy()->drawItemText(p, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment, + groupBox->palette, groupBox->state & State_Enabled, groupBox->text, + textColor.isValid() ? QPalette::NoRole : QPalette::WindowText); + + if (groupBox->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*groupBox); + fropt.rect = textRect; + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + + // Draw checkbox + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, p, widget); + } + } + break; +#endif // QT_NO_GROUPBOX +#ifndef QT_NO_WORKSPACE + case CC_MdiControls: + { + QStyleOptionButton btnOpt; + btnOpt.QStyleOption::operator=(*opt); + btnOpt.state &= ~State_MouseOver; + int bsx = 0; + int bsy = 0; + if (opt->subControls & QStyle::SC_MdiCloseButton) { + if (opt->activeSubControls & QStyle::SC_MdiCloseButton && (opt->state & State_Sunken)) { + btnOpt.state |= State_Sunken; + btnOpt.state &= ~State_Raised; + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical); + } else { + btnOpt.state |= State_Raised; + btnOpt.state &= ~State_Sunken; + bsx = 0; + bsy = 0; + } + btnOpt.rect = proxy()->subControlRect(CC_MdiControls, opt, SC_MdiCloseButton, widget); + proxy()->drawPrimitive(PE_PanelButtonCommand, &btnOpt, p, widget); + QPixmap pm = standardIcon(SP_TitleBarCloseButton).pixmap(16, 16); + proxy()->drawItemPixmap(p, btnOpt.rect.translated(bsx, bsy), Qt::AlignCenter, pm); + } + if (opt->subControls & QStyle::SC_MdiNormalButton) { + if (opt->activeSubControls & QStyle::SC_MdiNormalButton && (opt->state & State_Sunken)) { + btnOpt.state |= State_Sunken; + btnOpt.state &= ~State_Raised; + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical); + } else { + btnOpt.state |= State_Raised; + btnOpt.state &= ~State_Sunken; + bsx = 0; + bsy = 0; + } + btnOpt.rect = proxy()->subControlRect(CC_MdiControls, opt, SC_MdiNormalButton, widget); + proxy()->drawPrimitive(PE_PanelButtonCommand, &btnOpt, p, widget); + QPixmap pm = standardIcon(SP_TitleBarNormalButton).pixmap(16, 16); + proxy()->drawItemPixmap(p, btnOpt.rect.translated(bsx, bsy), Qt::AlignCenter, pm); + } + if (opt->subControls & QStyle::SC_MdiMinButton) { + if (opt->activeSubControls & QStyle::SC_MdiMinButton && (opt->state & State_Sunken)) { + btnOpt.state |= State_Sunken; + btnOpt.state &= ~State_Raised; + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical); + } else { + btnOpt.state |= State_Raised; + btnOpt.state &= ~State_Sunken; + bsx = 0; + bsy = 0; + } + btnOpt.rect = proxy()->subControlRect(CC_MdiControls, opt, SC_MdiMinButton, widget); + proxy()->drawPrimitive(PE_PanelButtonCommand, &btnOpt, p, widget); + QPixmap pm = standardIcon(SP_TitleBarMinButton).pixmap(16, 16); + proxy()->drawItemPixmap(p, btnOpt.rect.translated(bsx, bsy), Qt::AlignCenter, pm); + } + } + break; +#endif // QT_NO_WORKSPACE + + default: + qWarning("QCommonStyle::drawComplexControl: Control %d not handled", cc); + } +} + +/*! + \reimp +*/ +QStyle::SubControl QCommonStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *widget) const +{ + SubControl sc = SC_None; + switch (cc) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QRect r = proxy()->subControlRect(cc, slider, SC_SliderHandle, widget); + if (r.isValid() && r.contains(pt)) { + sc = SC_SliderHandle; + } else { + r = proxy()->subControlRect(cc, slider, SC_SliderGroove ,widget); + if (r.isValid() && r.contains(pt)) + sc = SC_SliderGroove; + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QRect r; + uint ctrl = SC_ScrollBarAddLine; + while (ctrl <= SC_ScrollBarGroove) { + r = proxy()->subControlRect(cc, scrollbar, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + QRect r; + uint ctrl = SC_ToolButton; + while (ctrl <= SC_ToolButtonMenu) { + r = proxy()->subControlRect(cc, toolbutton, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + } + break; +#endif // QT_NO_TOOLBUTTON +#ifdef QT3_SUPPORT + case CC_Q3ListView: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if (pt.x() >= 0 && pt.x() < lv->treeStepSize) + sc = SC_Q3ListViewExpand; + } + break; +#endif // QT3_SUPPORT +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QRect r; + uint ctrl = SC_SpinBoxUp; + while (ctrl <= SC_SpinBoxEditField) { + r = proxy()->subControlRect(cc, spinbox, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + } + break; +#endif // QT_NO_SPINBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + QRect r; + uint ctrl = SC_TitleBarSysMenu; + + while (ctrl <= SC_TitleBarLabel) { + r = proxy()->subControlRect(cc, tb, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + } + break; +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QRect r; + uint ctrl = SC_ComboBoxArrow; // Start here and go down. + while (ctrl > 0) { + r = proxy()->subControlRect(cc, cb, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl >>= 1; + } + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + QRect r; + uint ctrl = SC_GroupBoxCheckBox; + while (ctrl <= SC_GroupBoxFrame) { + r = proxy()->subControlRect(cc, groupBox, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + } + break; +#endif // QT_NO_GROUPBOX + case CC_MdiControls: + { + QRect r; + uint ctrl = SC_MdiMinButton; + while (ctrl <= SC_MdiCloseButton) { + r = proxy()->subControlRect(CC_MdiControls, opt, QStyle::SubControl(ctrl), widget); + if (r.isValid() && r.contains(pt) && (opt->subControls & ctrl)) { + sc = QStyle::SubControl(ctrl); + return sc; + } + ctrl <<= 1; + } + } + break; + default: + qWarning("QCommonStyle::hitTestComplexControl: Case %d not handled", cc); + } + return sc; +} + +/*! + \reimp +*/ +QRect QCommonStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const +{ + QRect ret; + switch (cc) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + + switch (sc) { + case SC_SliderHandle: { + int sliderPos = 0; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + bool horizontal = slider->orientation == Qt::Horizontal; + sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum, + slider->sliderPosition, + (horizontal ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown); + if (horizontal) + ret.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness); + else + ret.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len); + break; } + case SC_SliderGroove: + if (slider->orientation == Qt::Horizontal) + ret.setRect(slider->rect.x(), slider->rect.y() + tickOffset, + slider->rect.width(), thickness); + else + ret.setRect(slider->rect.x() + tickOffset, slider->rect.y(), + thickness, slider->rect.height()); + break; + default: + break; + } + ret = visualRect(slider->direction, slider->rect, ret); + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + const QRect scrollBarRect = scrollbar->rect; + int sbextent = proxy()->pixelMetric(PM_ScrollBarExtent, scrollbar, widget); + int maxlen = ((scrollbar->orientation == Qt::Horizontal) ? + scrollBarRect.width() : scrollBarRect.height()) - (sbextent * 2); + int sliderlen; + + // calculate slider length + if (scrollbar->maximum != scrollbar->minimum) { + uint range = scrollbar->maximum - scrollbar->minimum; + sliderlen = (qint64(scrollbar->pageStep) * maxlen) / (range + scrollbar->pageStep); + + int slidermin = proxy()->pixelMetric(PM_ScrollBarSliderMin, scrollbar, widget); + if (sliderlen < slidermin || range > INT_MAX / 2) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + + int sliderstart = sbextent + sliderPositionFromValue(scrollbar->minimum, + scrollbar->maximum, + scrollbar->sliderPosition, + maxlen - sliderlen, + scrollbar->upsideDown); + + switch (sc) { + case SC_ScrollBarSubLine: // top/left button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollBarRect.width() / 2, sbextent); + ret.setRect(0, 0, buttonWidth, scrollBarRect.height()); + } else { + int buttonHeight = qMin(scrollBarRect.height() / 2, sbextent); + ret.setRect(0, 0, scrollBarRect.width(), buttonHeight); + } + break; + case SC_ScrollBarAddLine: // bottom/right button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollBarRect.width()/2, sbextent); + ret.setRect(scrollBarRect.width() - buttonWidth, 0, buttonWidth, scrollBarRect.height()); + } else { + int buttonHeight = qMin(scrollBarRect.height()/2, sbextent); + ret.setRect(0, scrollBarRect.height() - buttonHeight, scrollBarRect.width(), buttonHeight); + } + break; + case SC_ScrollBarSubPage: // between top/left button and slider + if (scrollbar->orientation == Qt::Horizontal) + ret.setRect(sbextent, 0, sliderstart - sbextent, scrollBarRect.height()); + else + ret.setRect(0, sbextent, scrollBarRect.width(), sliderstart - sbextent); + break; + case SC_ScrollBarAddPage: // between bottom/right button and slider + if (scrollbar->orientation == Qt::Horizontal) + ret.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen + sbextent, scrollBarRect.height()); + else + ret.setRect(0, sliderstart + sliderlen, scrollBarRect.width(), + maxlen - sliderstart - sliderlen + sbextent); + break; + case SC_ScrollBarGroove: + if (scrollbar->orientation == Qt::Horizontal) + ret.setRect(sbextent, 0, scrollBarRect.width() - sbextent * 2, + scrollBarRect.height()); + else + ret.setRect(0, sbextent, scrollBarRect.width(), + scrollBarRect.height() - sbextent * 2); + break; + case SC_ScrollBarSlider: + if (scrollbar->orientation == Qt::Horizontal) + ret.setRect(sliderstart, 0, sliderlen, scrollBarRect.height()); + else + ret.setRect(0, sliderstart, scrollBarRect.width(), sliderlen); + break; + default: + break; + } + ret = visualRect(scrollbar->direction, scrollBarRect, ret); + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QSize bs; + int fw = spinbox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0; + bs.setHeight(qMax(8, spinbox->rect.height()/2 - fw)); + // 1.6 -approximate golden mean + bs.setWidth(qMax(16, qMin(bs.height() * 8 / 5, spinbox->rect.width() / 4))); + bs = bs.expandedTo(QApplication::globalStrut()); + int y = fw + spinbox->rect.y(); + int x, lx, rx; + x = spinbox->rect.x() + spinbox->rect.width() - fw - bs.width(); + lx = fw; + rx = x - fw; + switch (sc) { + 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, y + bs.height(), 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; +#endif // Qt_NO_SPINBOX +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, tb, widget); + ret = tb->rect; + switch (sc) { + case SC_ToolButton: + if ((tb->features + & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay)) + == QStyleOptionToolButton::MenuButtonPopup) + ret.adjust(0, 0, -mbi, 0); + break; + case SC_ToolButtonMenu: + if ((tb->features + & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay)) + == QStyleOptionToolButton::MenuButtonPopup) + ret.adjust(ret.width() - mbi, 0, 0, 0); + break; + default: + break; + } + ret = visualRect(tb->direction, tb->rect, ret); + } + break; +#endif // QT_NO_TOOLBUTTON +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + int x = cb->rect.x(), + y = cb->rect.y(), + wi = cb->rect.width(), + he = cb->rect.height(); + int xpos = x; + int margin = cb->frame ? 3 : 0; + int bmarg = cb->frame ? 2 : 0; + xpos += wi - bmarg - 16; + + + switch (sc) { + case SC_ComboBoxFrame: + ret = cb->rect; + break; + case SC_ComboBoxArrow: + ret.setRect(xpos, y + bmarg, 16, he - 2*bmarg); + break; + case SC_ComboBoxEditField: + ret.setRect(x + margin, y + margin, wi - 2 * margin - 16, he - 2 * margin); + break; + case SC_ComboBoxListBoxPopup: + ret = cb->rect; + break; + default: + break; + } + ret = visualRect(cb->direction, cb->rect, ret); + } + break; +#endif // QT_NO_COMBOBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + const int controlMargin = 2; + const int controlHeight = tb->rect.height() - controlMargin *2; + const int delta = controlHeight + controlMargin; + int offset = 0; + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + + switch (sc) { + case SC_TitleBarLabel: + if (tb->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) { + ret = tb->rect; + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + ret.adjust(delta, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowShadeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + ret.adjust(0, 0, -delta, 0); + } + break; + case SC_TitleBarContextHelpButton: + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + offset += delta; + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMinButton) + break; + case SC_TitleBarNormalButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarNormalButton) + break; + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMaxButton) + break; + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarShadeButton) + break; + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarUnshadeButton) + break; + case SC_TitleBarCloseButton: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + offset += delta; + else if (sc == SC_TitleBarCloseButton) + break; + ret.setRect(tb->rect.right() - offset, tb->rect.top() + controlMargin, + controlHeight, controlHeight); + break; + case SC_TitleBarSysMenu: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) { + ret.setRect(tb->rect.left() + controlMargin, tb->rect.top() + controlMargin, + controlHeight, controlHeight); + } + break; + default: + break; + } + ret = visualRect(tb->direction, tb->rect, ret); + } + break; +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + switch (sc) { + case SC_GroupBoxFrame: + // FALL THROUGH + case SC_GroupBoxContents: { + int topMargin = 0; + int topHeight = 0; + int verticalAlignment = proxy()->styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget); + if (groupBox->text.size() || (groupBox->subControls & QStyle::SC_GroupBoxCheckBox)) { + topHeight = groupBox->fontMetrics.height(); + if (verticalAlignment & Qt::AlignVCenter) + topMargin = topHeight / 2; + else if (verticalAlignment & Qt::AlignTop) + topMargin = topHeight; + } + + QRect frameRect = groupBox->rect; + frameRect.setTop(topMargin); + + if (sc == SC_GroupBoxFrame) { + ret = frameRect; + break; + } + + int frameWidth = 0; + if (!(widget && widget->inherits("Q3GroupBox")) + && ((groupBox->features & QStyleOptionFrameV2::Flat) == 0)) { + frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth, groupBox, widget); + } + ret = frameRect.adjusted(frameWidth, frameWidth + topHeight - topMargin, + -frameWidth, -frameWidth); + break; + } + case SC_GroupBoxCheckBox: + // FALL THROUGH + case SC_GroupBoxLabel: { + QFontMetrics fontMetrics = groupBox->fontMetrics; + int h = fontMetrics.height(); + int tw = fontMetrics.size(Qt::TextShowMnemonic, groupBox->text + QLatin1Char(' ')).width(); + int marg = (groupBox->features & QStyleOptionFrameV2::Flat) ? 0 : 8; + ret = groupBox->rect.adjusted(marg, 0, -marg, 0); + ret.setHeight(h); + + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt, widget); + int indicatorSpace = proxy()->pixelMetric(PM_CheckBoxLabelSpacing, opt, widget) - 1; + bool hasCheckBox = groupBox->subControls & QStyle::SC_GroupBoxCheckBox; + int checkBoxSize = hasCheckBox ? (indicatorWidth + indicatorSpace) : 0; + + // Adjusted rect for label + indicatorWidth + indicatorSpace + QRect totalRect = alignedRect(groupBox->direction, groupBox->textAlignment, + QSize(tw + checkBoxSize, h), ret); + + // Adjust totalRect if checkbox is set + if (hasCheckBox) { + bool ltr = groupBox->direction == Qt::LeftToRight; + int left = 0; + // Adjust for check box + if (sc == SC_GroupBoxCheckBox) { + int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, opt, widget); + left = ltr ? totalRect.left() : (totalRect.right() - indicatorWidth); + int top = totalRect.top() + (fontMetrics.height() - indicatorHeight) / 2; + totalRect.setRect(left, top, indicatorWidth, indicatorHeight); + // Adjust for label + } else { + left = ltr ? (totalRect.left() + checkBoxSize - 2) : totalRect.left(); + totalRect.setRect(left, totalRect.top(), + totalRect.width() - checkBoxSize, totalRect.height()); + } + } + ret = totalRect; + break; + } + default: + break; + } + } + break; + } +#endif // QT_NO_GROUPBOX +#ifndef QT_NO_WORKSPACE + case CC_MdiControls: + { + int numSubControls = 0; + if (opt->subControls & SC_MdiCloseButton) + ++numSubControls; + if (opt->subControls & SC_MdiMinButton) + ++numSubControls; + if (opt->subControls & SC_MdiNormalButton) + ++numSubControls; + if (numSubControls == 0) + break; + + int buttonWidth = opt->rect.width()/ numSubControls - 1; + int offset = 0; + switch (sc) { + case SC_MdiCloseButton: + // Only one sub control, no offset needed. + if (numSubControls == 1) + break; + offset += buttonWidth + 2; + //FALL THROUGH + 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 && !(opt->subControls & SC_MdiMinButton))) + break; + if (opt->subControls & SC_MdiNormalButton) + offset += buttonWidth; + break; + default: + break; + } + + // Subtract one pixel if we only have one sub control. At this point + // buttonWidth is the actual width + 1 pixel margin, but we don't want the + // margin when there are no other controllers. + if (numSubControls == 1) + --buttonWidth; + ret = QRect(offset, 0, buttonWidth, opt->rect.height()); + break; + } +#endif // QT_NO_WORKSPACE + default: + qWarning("QCommonStyle::subControlRect: Case %d not handled", cc); + } + return ret; +} + +/*! \reimp */ +int QCommonStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWidget *widget) const +{ + int ret; + + switch (m) { + case PM_FocusFrameVMargin: + case PM_FocusFrameHMargin: + ret = 2; + break; + case PM_MenuBarVMargin: + case PM_MenuBarHMargin: + ret = 0; + break; + case PM_DialogButtonsSeparator: + ret = int(QStyleHelper::dpiScaled(5.)); + break; + case PM_DialogButtonsButtonWidth: + ret = int(QStyleHelper::dpiScaled(70.)); + break; + case PM_DialogButtonsButtonHeight: + ret = int(QStyleHelper::dpiScaled(30.)); + break; + case PM_CheckListControllerSize: + case PM_CheckListButtonSize: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + case PM_TitleBarHeight: { + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + if ((tb->titleBarFlags & Qt::WindowType_Mask) == Qt::Tool) { + ret = qMax(widget ? widget->fontMetrics().height() : opt->fontMetrics.height(), 16); +#ifndef QT_NO_DOCKWIDGET + } else if (qobject_cast<const QDockWidget*>(widget)) { + ret = qMax(widget->fontMetrics().height(), int(QStyleHelper::dpiScaled(13))); +#endif + } else { + ret = qMax(widget ? widget->fontMetrics().height() : opt->fontMetrics.height(), 18); + } + } else { + ret = int(QStyleHelper::dpiScaled(18.)); + } + + break; } + case PM_ScrollBarSliderMin: + ret = int(QStyleHelper::dpiScaled(9.)); + break; + + case PM_ButtonMargin: + ret = int(QStyleHelper::dpiScaled(6.)); + break; + + case PM_DockWidgetTitleBarButtonMargin: + ret = int(QStyleHelper::dpiScaled(2.)); + break; + + case PM_ButtonDefaultIndicator: + ret = 0; + break; + + case PM_MenuButtonIndicator: + ret = int(QStyleHelper::dpiScaled(12.)); + break; + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + + case PM_DefaultFrameWidth: + ret = 2; + break; + + case PM_ComboBoxFrameWidth: + case PM_SpinBoxFrameWidth: + case PM_MenuPanelWidth: + case PM_TabBarBaseOverlap: + case PM_TabBarBaseHeight: + ret = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + break; + + case PM_MdiSubWindowFrameWidth: + ret = int(QStyleHelper::dpiScaled(4.)); + break; + + case PM_MdiSubWindowMinimizedWidth: + ret = int(QStyleHelper::dpiScaled(196.)); + break; + +#ifndef QT_NO_SCROLLBAR + case PM_ScrollBarExtent: + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int s = sb->orientation == Qt::Horizontal ? + QApplication::globalStrut().height() + : QApplication::globalStrut().width(); + ret = qMax(16, s); + } else { + ret = int(QStyleHelper::dpiScaled(16.)); + } + break; +#endif + case PM_MaximumDragDistance: + ret = -1; + break; + +#ifndef QT_NO_SLIDER + case PM_SliderThickness: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + + case PM_SliderTickmarkOffset: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() + : sl->rect.width(); + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, sl, widget); + int ticks = sl->tickPosition; + + if (ticks == QSlider::TicksBothSides) + ret = (space - thickness) / 2; + else if (ticks == QSlider::TicksAbove) + ret = space - thickness; + else + ret = 0; + } else { + ret = 0; + } + break; + + case PM_SliderSpaceAvailable: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + if (sl->orientation == Qt::Horizontal) + ret = sl->rect.width() - proxy()->pixelMetric(PM_SliderLength, sl, widget); + else + ret = sl->rect.height() - proxy()->pixelMetric(PM_SliderLength, sl, widget); + } else { + ret = 0; + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_DOCKWIDGET + case PM_DockWidgetSeparatorExtent: + ret = int(QStyleHelper::dpiScaled(6.)); + break; + + case PM_DockWidgetHandleExtent: + ret = int(QStyleHelper::dpiScaled(8.)); + break; + case PM_DockWidgetTitleMargin: + ret = 0; + break; + case PM_DockWidgetFrameWidth: + ret = 1; + break; +#endif // QT_NO_DOCKWIDGET + + case PM_SpinBoxSliderHeight: + case PM_MenuBarPanelWidth: + ret = 2; + break; + + case PM_MenuBarItemSpacing: + ret = 0; + break; + +#ifndef QT_NO_TOOLBAR + case PM_ToolBarFrameWidth: + ret = 1; + break; + + case PM_ToolBarItemMargin: + ret = 0; + break; + + case PM_ToolBarItemSpacing: + ret = int(QStyleHelper::dpiScaled(4.)); + break; + + case PM_ToolBarHandleExtent: + ret = int(QStyleHelper::dpiScaled(8.)); + break; + + case PM_ToolBarSeparatorExtent: + ret = int(QStyleHelper::dpiScaled(6.)); + break; + + case PM_ToolBarExtensionExtent: + ret = int(QStyleHelper::dpiScaled(12.)); + break; +#endif // QT_NO_TOOLBAR + +#ifndef QT_NO_TABBAR + case PM_TabBarTabOverlap: + ret = 3; + break; + + case PM_TabBarTabHSpace: + ret = int(QStyleHelper::dpiScaled(24.)); + break; + + case PM_TabBarTabShiftHorizontal: + ret = 0; + break; + + case PM_TabBarTabShiftVertical: + ret = 2; + break; + + case PM_TabBarTabVSpace: { + const QStyleOptionTab *tb = qstyleoption_cast<const QStyleOptionTab *>(opt); + if (tb && (tb->shape == QTabBar::RoundedNorth || tb->shape == QTabBar::RoundedSouth + || tb->shape == QTabBar::RoundedWest || tb->shape == QTabBar::RoundedEast)) + ret = 8; + else + if(tb && (tb->shape == QTabBar::TriangularWest || tb->shape == QTabBar::TriangularEast)) + ret = 3; + else + ret = 2; + break; } +#endif + + case PM_ProgressBarChunkWidth: + ret = 9; + break; + + case PM_IndicatorWidth: + ret = int(QStyleHelper::dpiScaled(13.)); + break; + + case PM_IndicatorHeight: + ret = int(QStyleHelper::dpiScaled(13.)); + break; + + case PM_ExclusiveIndicatorWidth: + ret = int(QStyleHelper::dpiScaled(12.)); + break; + + case PM_ExclusiveIndicatorHeight: + ret = int(QStyleHelper::dpiScaled(12.)); + break; + + case PM_MenuTearoffHeight: + ret = int(QStyleHelper::dpiScaled(10.)); + break; + + case PM_MenuScrollerHeight: + ret = int(QStyleHelper::dpiScaled(10.)); + break; + + case PM_MenuDesktopFrameWidth: + case PM_MenuHMargin: + case PM_MenuVMargin: + ret = 0; + break; + + case PM_HeaderMargin: + ret = int(QStyleHelper::dpiScaled(4.)); + break; + case PM_HeaderMarkSize: + ret = int(QStyleHelper::dpiScaled(32.)); + break; + case PM_HeaderGripMargin: + ret = int(QStyleHelper::dpiScaled(4.)); + break; + case PM_TabBarScrollButtonWidth: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + case PM_LayoutLeftMargin: + case PM_LayoutTopMargin: + case PM_LayoutRightMargin: + case PM_LayoutBottomMargin: + { + bool isWindow = false; + if (opt) { + isWindow = (opt->state & State_Window); + } else if (widget) { + isWindow = widget->isWindow(); + } + ret = proxy()->pixelMetric(isWindow ? PM_DefaultTopLevelMargin : PM_DefaultChildMargin); + } + break; + case PM_LayoutHorizontalSpacing: + case PM_LayoutVerticalSpacing: + ret = proxy()->pixelMetric(PM_DefaultLayoutSpacing); + break; + + case PM_DefaultTopLevelMargin: + ret = int(QStyleHelper::dpiScaled(11.)); + break; + case PM_DefaultChildMargin: + ret = int(QStyleHelper::dpiScaled(9.)); + break; + case PM_DefaultLayoutSpacing: + ret = int(QStyleHelper::dpiScaled(6.)); + break; + + case PM_ToolBarIconSize: + ret = qt_guiPlatformPlugin()->platformHint(QGuiPlatformPlugin::PH_ToolBarIconSize); + if (!ret) + ret = int(QStyleHelper::dpiScaled(24.)); + break; + + case PM_TabBarIconSize: + case PM_ListViewIconSize: + ret = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + break; + + case PM_ButtonIconSize: + case PM_SmallIconSize: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + case PM_IconViewIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget); + break; + + case PM_LargeIconSize: + ret = int(QStyleHelper::dpiScaled(32.)); + break; + + case PM_ToolTipLabelFrameWidth: + ret = 1; + break; + case PM_CheckBoxLabelSpacing: + case PM_RadioButtonLabelSpacing: + ret = int(QStyleHelper::dpiScaled(6.)); + break; + case PM_SizeGripSize: + ret = int(QStyleHelper::dpiScaled(13.)); + break; + case PM_MessageBoxIconSize: +#ifdef Q_WS_MAC + if (QApplication::desktopSettingsAware()) { + ret = 64; // No DPI scaling, it's handled elsewhere. + } else +#endif + { + ret = int(QStyleHelper::dpiScaled(32.)); + } + break; + case PM_TextCursorWidth: + ret = 1; + break; + case PM_TabBar_ScrollButtonOverlap: + ret = 1; + break; + case PM_TabCloseIndicatorWidth: + case PM_TabCloseIndicatorHeight: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + case PM_ScrollView_ScrollBarSpacing: + ret = 2 * proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + break; + case PM_SubMenuOverlap: + ret = -proxy()->pixelMetric(QStyle::PM_MenuPanelWidth, opt, widget); + break; + default: + ret = 0; + break; + } + + return ret; +} + +/*! + \reimp +*/ +QSize QCommonStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *widget) const +{ + Q_D(const QCommonStyle); + QSize sz(csz); + switch (ct) { + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + int w = csz.width(), + h = csz.height(), + bm = proxy()->pixelMetric(PM_ButtonMargin, btn, widget), + fw = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget) * 2; + w += bm + fw; + h += bm + fw; + if (btn->features & QStyleOptionButton::AutoDefaultButton){ + int dbw = proxy()->pixelMetric(PM_ButtonDefaultIndicator, btn, widget) * 2; + w += dbw; + h += dbw; + } + sz = QSize(w, h); + } + break; + case CT_RadioButton: + case CT_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + bool isRadio = (ct == CT_RadioButton); + + int w = proxy()->pixelMetric(isRadio ? PM_ExclusiveIndicatorWidth + : PM_IndicatorWidth, btn, widget); + int h = proxy()->pixelMetric(isRadio ? PM_ExclusiveIndicatorHeight + : PM_IndicatorHeight, btn, widget); + + int margins = 0; + // we add 4 pixels for label margins + if (!btn->icon.isNull() || !btn->text.isEmpty()) + margins = 4 + proxy()->pixelMetric(isRadio ? PM_RadioButtonLabelSpacing + : PM_CheckBoxLabelSpacing, opt, widget); + sz += QSize(w + margins, 4); + sz.setHeight(qMax(sz.height(), h)); + } + break; +#ifndef QT_NO_MENU + case CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + bool checkable = mi->menuHasCheckableItems; + int maxpmw = mi->maxIconWidth; + int w = sz.width(), h = sz.height(); + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + w = 10; + h = 2; + } else { + h = mi->fontMetrics.height() + 8; + if (!mi->icon.isNull()) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4); + } + } + if (mi->text.contains(QLatin1Char('\t'))) + w += 12; + if (maxpmw > 0) + w += maxpmw + 6; + if (checkable && maxpmw < 20) + w += 20 - maxpmw; + if (checkable || maxpmw > 0) + w += 2; + w += 12; + sz = QSize(w, h); + } + break; +#endif // QT_NO_MENU +#ifndef QT_NO_TOOLBUTTON + case CT_ToolButton: + sz = QSize(sz.width() + 6, sz.height() + 5); + break; +#endif // QT_NO_TOOLBUTTON +#ifndef QT_NO_COMBOBOX + case CT_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + int fw = cmb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, opt, widget) * 2 : 0; + const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin) + 1); + // QItemDelegate::sizeHint expands the textMargins two times, thus the 2*textMargins... + int other = qMax(23, 2*textMargins + proxy()->pixelMetric(QStyle::PM_ScrollBarExtent, opt, widget)); + sz = QSize(sz.width() + fw + other, sz.height() + fw); + } + break; +#endif // QT_NO_COMBOBOX + case CT_HeaderSection: + if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + bool nullIcon = hdr->icon.isNull(); + int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, hdr, widget); + int iconSize = nullIcon ? 0 : proxy()->pixelMetric(QStyle::PM_SmallIconSize, hdr, widget); + QSize txt = hdr->fontMetrics.size(0, hdr->text); + sz.setHeight(margin + qMax(iconSize, txt.height()) + margin); + sz.setWidth((nullIcon ? 0 : margin) + iconSize + + (hdr->text.isNull() ? 0 : margin) + txt.width() + margin); + } + break; + case CT_TabWidget: + sz += QSize(4, 4); + break; + case CT_LineEdit: + if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(opt)) + sz += QSize(2*f->lineWidth, 2*f->lineWidth); + break; +#ifndef QT_NO_GROUPBOX + case CT_GroupBox: + if (const QGroupBox *grb = static_cast<const QGroupBox *>(widget)) + sz += QSize(!grb->isFlat() ? 16 : 0, 0); + break; +#endif // QT_NO_GROUPBOX + case CT_MdiControls: + if (const QStyleOptionComplex *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(opt)) { + int width = 1; + if (styleOpt->subControls & SC_MdiMinButton) + width += 16 + 1; + if (styleOpt->subControls & SC_MdiNormalButton) + width += 16 + 1; + if (styleOpt->subControls & SC_MdiCloseButton) + width += 16 + 1; + sz = QSize(width, 16); + } else { + sz = QSize(52, 16); + } + break; +#ifndef QT_NO_ITEMVIEWS + case CT_ItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + QRect decorationRect, displayRect, checkRect; + d->viewItemLayout(vopt, &checkRect, &decorationRect, &displayRect, true); + sz = (decorationRect|displayRect|checkRect).size(); + } + break; +#endif // QT_NO_ITEMVIEWS + case CT_ScrollBar: + case CT_MenuBar: + case CT_Menu: + case CT_MenuBarItem: + case CT_Q3Header: + case CT_Slider: + case CT_ProgressBar: + case CT_TabBarTab: + // just return the contentsSize for now + // fall through intended + default: + break; + } + return sz; +} + + +/*! \reimp */ +int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *hret) const +{ + int ret = 0; + + switch (sh) { + case SH_Menu_KeyboardSearch: + ret = false; + break; + case SH_Slider_AbsoluteSetButtons: + ret = Qt::MidButton; + break; + case SH_Slider_PageSetButtons: + ret = Qt::LeftButton; + break; + case SH_ScrollBar_ContextMenu: + ret = true; + break; + case SH_DialogButtons_DefaultButton: // This value not used anywhere. + ret = QDialogButtonBox::AcceptRole; + break; +#ifndef QT_NO_GROUPBOX + case SH_GroupBox_TextLabelVerticalAlignment: + ret = Qt::AlignVCenter; + break; + + case SH_GroupBox_TextLabelColor: + ret = opt ? int(opt->palette.color(QPalette::Text).rgba()) : 0; + break; +#endif // QT_NO_GROUPBOX + + case SH_Q3ListViewExpand_SelectMouseType: + case SH_TabBar_SelectMouseType: + ret = QEvent::MouseButtonPress; + break; + +#ifdef QT3_SUPPORT + case SH_GUIStyle: + ret = Qt::WindowsStyle; + break; +#endif + + case SH_TabBar_Alignment: + case SH_Header_ArrowAlignment: + ret = Qt::AlignLeft; + break; + + case SH_TitleBar_AutoRaise: + ret = false; + break; + + case SH_Menu_SubMenuPopupDelay: + ret = 256; + break; + + case SH_ProgressDialog_TextLabelAlignment: + ret = Qt::AlignCenter; + break; + + case SH_BlinkCursorWhenTextSelected: + ret = 1; + break; + + case SH_Table_GridLineColor: + if (opt) + ret = opt->palette.color(QPalette::Mid).rgb(); + else + ret = -1; + break; + case SH_LineEdit_PasswordCharacter: { + const QFontMetrics &fm = opt ? opt->fontMetrics + : (widget ? widget->fontMetrics() : QFontMetrics(QFont())); + ret = 0; + if (fm.inFont(QChar(0x25CF))) { + ret = 0x25CF; + } else if (fm.inFont(QChar(0x2022))) { + ret = 0x2022; + } else { + ret = '*'; + } + break; + } + + + case SH_ToolBox_SelectedPageTitleBold: + ret = 1; + break; + + case SH_UnderlineShortcut: + ret = 1; + break; + + case SH_SpinBox_ClickAutoRepeatRate: + ret = 150; + break; + + case SH_SpinBox_ClickAutoRepeatThreshold: + ret = 500; + break; + + case SH_SpinBox_KeyPressAutoRepeatRate: + ret = 75; + break; + + case SH_Menu_SelectionWrap: + ret = true; + break; + + case SH_Menu_FillScreenWithScroll: + ret = true; + break; + + case SH_ToolTipLabel_Opacity: + ret = 255; + break; + + case SH_Button_FocusPolicy: + ret = Qt::StrongFocus; + break; + + case SH_MenuBar_DismissOnSecondClick: + ret = 1; + break; + + case SH_MessageBox_UseBorderForButtonSpacing: + ret = 0; + break; + + case SH_ToolButton_PopupDelay: + ret = 600; + break; + + case SH_FocusFrame_Mask: + ret = 1; + if (widget) { + if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + mask->region = widget->rect(); + int vmargin = proxy()->pixelMetric(QStyle::PM_FocusFrameVMargin), + hmargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin); + mask->region -= QRect(widget->rect().adjusted(hmargin, vmargin, -hmargin, -vmargin)); + } + } + break; +#ifndef QT_NO_RUBBERBAND + case SH_RubberBand_Mask: + if (const QStyleOptionRubberBand *rbOpt = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + ret = 0; + if (rbOpt->shape == QRubberBand::Rectangle) { + ret = true; + if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + mask->region = opt->rect; + int margin = proxy()->pixelMetric(PM_DefaultFrameWidth) * 2; + mask->region -= opt->rect.adjusted(margin, margin, -margin, -margin); + } + } + } + break; +#endif // QT_NO_RUBBERBAND + case SH_SpinControls_DisableOnBounds: + ret = 1; + break; + + case SH_Dial_BackgroundRole: + ret = QPalette::Window; + break; + + case SH_ComboBox_LayoutDirection: + ret = opt ? opt->direction : Qt::LeftToRight; + break; + + case SH_ItemView_EllipsisLocation: + ret = Qt::AlignTrailing; + break; + + case SH_ItemView_ShowDecorationSelected: + ret = false; + break; + + case SH_ItemView_ActivateItemOnSingleClick: + ret = qt_guiPlatformPlugin()->platformHint(QGuiPlatformPlugin::PH_ItemView_ActivateItemOnSingleClick); + break; + + case SH_TitleBar_ModifyNotification: + ret = true; + break; + case SH_ScrollBar_RollBetweenButtons: + ret = false; + break; + case SH_TabBar_ElideMode: + ret = Qt::ElideNone; + break; + case SH_DialogButtonLayout: + ret = QDialogButtonBox::WinLayout; +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE) + ret = QDialogButtonBox::KdeLayout; + else if (X11->desktopEnvironment == DE_GNOME) + ret = QDialogButtonBox::GnomeLayout; +#endif + break; + case SH_ComboBox_PopupFrameStyle: + ret = QFrame::StyledPanel | QFrame::Plain; + break; + case SH_MessageBox_TextInteractionFlags: + ret = Qt::LinksAccessibleByMouse; + break; + case SH_DialogButtonBox_ButtonsHaveIcons: +#ifdef Q_WS_X11 + return true; +#endif + ret = 0; + break; + case SH_SpellCheckUnderlineStyle: + ret = QTextCharFormat::WaveUnderline; + break; + case SH_MessageBox_CenterButtons: + ret = true; + break; + case SH_ItemView_MovementWithoutUpdatingSelection: + ret = true; + break; + case SH_FocusFrame_AboveWidget: + ret = false; + break; +#ifndef QT_NO_TABWIDGET + case SH_TabWidget_DefaultTabPosition: + ret = QTabWidget::North; + break; +#endif + case SH_ToolBar_Movable: + ret = true; + break; + case SH_TextControl_FocusIndicatorTextCharFormat: + ret = true; + if (QStyleHintReturnVariant *vret = qstyleoption_cast<QStyleHintReturnVariant*>(hret)) { + QPen outline(opt->palette.color(QPalette::Text), 1, Qt::DotLine); + QTextCharFormat fmt; + fmt.setProperty(QTextFormat::OutlinePen, outline); + vret->variant = fmt; + } + break; +#ifndef QT_NO_WIZARD + case SH_WizardStyle: + ret = QWizard::ClassicStyle; + break; +#endif + case SH_FormLayoutWrapPolicy: + ret = QFormLayout::DontWrapRows; + break; + case SH_FormLayoutFieldGrowthPolicy: + ret = QFormLayout::AllNonFixedFieldsGrow; + break; + case SH_FormLayoutFormAlignment: + ret = Qt::AlignLeft | Qt::AlignTop; + break; + case SH_FormLayoutLabelAlignment: + ret = Qt::AlignLeft; + break; + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = false; + break; + case SH_ItemView_DrawDelegateFrame: + ret = 0; + break; +#ifndef QT_NO_TABBAR + case SH_TabBar_CloseButtonPosition: + ret = QTabBar::RightSide; + break; +#endif + case SH_DockWidget_ButtonsHaveFrame: + ret = true; + break; + case SH_ToolButtonStyle: + ret = qt_guiPlatformPlugin()->platformHint(QGuiPlatformPlugin::PH_ToolButtonStyle); + break; + case SH_RequestSoftwareInputPanel: + ret = RSIP_OnMouseClickAndAlreadyFocused; + break; + default: + ret = 0; + break; + } + + return ret; +} + +/*! \reimp */ +QPixmap QCommonStyle::standardPixmap(StandardPixmap sp, const QStyleOption *option, + const QWidget *widget) const +{ + const bool rtl = (option && option->direction == Qt::RightToLeft) || (!option && QApplication::isRightToLeft()); +#ifdef QT_NO_IMAGEFORMAT_PNG + Q_UNUSED(widget); + Q_UNUSED(sp); +#else + QPixmap pixmap; + + if (QApplication::desktopSettingsAware() && !QIcon::themeName().isEmpty()) { + switch (sp) { + case SP_DialogYesButton: + case SP_DialogOkButton: + pixmap = QIcon::fromTheme(QLatin1String("dialog-ok")).pixmap(16); + break; + case SP_DialogApplyButton: + pixmap = QIcon::fromTheme(QLatin1String("dialog-ok-apply")).pixmap(16); + break; + case SP_DialogDiscardButton: + pixmap = QIcon::fromTheme(QLatin1String("edit-delete")).pixmap(16); + break; + case SP_DialogCloseButton: + pixmap = QIcon::fromTheme(QLatin1String("dialog-close")).pixmap(16); + break; + case SP_DirHomeIcon: + pixmap = QIcon::fromTheme(QLatin1String("user-home")).pixmap(16); + break; + case SP_MessageBoxInformation: + pixmap = QIcon::fromTheme(QLatin1String("messagebox_info")).pixmap(16); + break; + case SP_MessageBoxWarning: + pixmap = QIcon::fromTheme(QLatin1String("messagebox_warning")).pixmap(16); + break; + case SP_MessageBoxCritical: + pixmap = QIcon::fromTheme(QLatin1String("messagebox_critical")).pixmap(16); + break; + case SP_MessageBoxQuestion: + pixmap = QIcon::fromTheme(QLatin1String("help")).pixmap(16); + break; + case SP_DialogOpenButton: + case SP_DirOpenIcon: + pixmap = QIcon::fromTheme(QLatin1String("folder-open")).pixmap(16); + break; + case SP_FileIcon: + pixmap = QIcon::fromTheme(QLatin1String("text-x-generic"), + QIcon::fromTheme(QLatin1String("empty"))).pixmap(16); + break; + case SP_DirClosedIcon: + case SP_DirIcon: + pixmap = QIcon::fromTheme(QLatin1String("folder")).pixmap(16); + break; + case SP_DriveFDIcon: + pixmap = QIcon::fromTheme(QLatin1String("media-floppy"), + QIcon::fromTheme(QLatin1String("3floppy_unmount"))).pixmap(16); + break; + case SP_ComputerIcon: + pixmap = QIcon::fromTheme(QLatin1String("computer"), + QIcon::fromTheme(QLatin1String("system"))).pixmap(16); + break; + case SP_DesktopIcon: + pixmap = QIcon::fromTheme(QLatin1String("user-desktop"), + QIcon::fromTheme(QLatin1String("desktop"))).pixmap(16); + break; + case SP_TrashIcon: + pixmap = QIcon::fromTheme(QLatin1String("user-trash"), + QIcon::fromTheme(QLatin1String("trashcan_empty"))).pixmap(16); + break; + case SP_DriveCDIcon: + case SP_DriveDVDIcon: + pixmap = QIcon::fromTheme(QLatin1String("media-optical"), + QIcon::fromTheme(QLatin1String("cdrom_unmount"))).pixmap(16); + break; + case SP_DriveHDIcon: + pixmap = QIcon::fromTheme(QLatin1String("drive-harddisk"), + QIcon::fromTheme(QLatin1String("hdd_unmount"))).pixmap(16); + break; + case SP_FileDialogToParent: + pixmap = QIcon::fromTheme(QLatin1String("go-up"), + QIcon::fromTheme(QLatin1String("up"))).pixmap(16); + break; + case SP_FileDialogNewFolder: + pixmap = QIcon::fromTheme(QLatin1String("folder_new")).pixmap(16); + break; + case SP_ArrowUp: + pixmap = QIcon::fromTheme(QLatin1String("go-up"), + QIcon::fromTheme(QLatin1String("up"))).pixmap(16); + break; + case SP_ArrowDown: + pixmap = QIcon::fromTheme(QLatin1String("go-down"), + QIcon::fromTheme(QLatin1String("down"))).pixmap(16); + break; + case SP_ArrowRight: + pixmap = QIcon::fromTheme(QLatin1String("go-next"), + QIcon::fromTheme(QLatin1String("forward"))).pixmap(16); + break; + case SP_ArrowLeft: + pixmap = QIcon::fromTheme(QLatin1String("go-previous"), + QIcon::fromTheme(QLatin1String("back"))).pixmap(16); + break; + case SP_FileDialogDetailedView: + pixmap = QIcon::fromTheme(QLatin1String("view_detailed")).pixmap(16); + break; + case SP_FileDialogListView: + pixmap = QIcon::fromTheme(QLatin1String("view_icon")).pixmap(16); + break; + case SP_BrowserReload: + pixmap = QIcon::fromTheme(QLatin1String("reload")).pixmap(16); + break; + case SP_BrowserStop: + pixmap = QIcon::fromTheme(QLatin1String("process-stop")).pixmap(16); + break; + case SP_MediaPlay: + pixmap = QIcon::fromTheme(QLatin1String("media-playback-start")).pixmap(16); + break; + case SP_MediaPause: + pixmap = QIcon::fromTheme(QLatin1String("media-playback-pause")).pixmap(16); + break; + case SP_MediaStop: + pixmap = QIcon::fromTheme(QLatin1String("media-playback-stop")).pixmap(16); + break; + case SP_MediaSeekForward: + pixmap = QIcon::fromTheme(QLatin1String("media-seek-forward")).pixmap(16); + break; + case SP_MediaSeekBackward: + pixmap = QIcon::fromTheme(QLatin1String("media-seek-backward")).pixmap(16); + break; + case SP_MediaSkipForward: + pixmap = QIcon::fromTheme(QLatin1String("media-skip-backward")).pixmap(16); + break; + case SP_MediaSkipBackward: + pixmap = QIcon::fromTheme(QLatin1String("media-skip-backward")).pixmap(16); + break; + case SP_DialogResetButton: + pixmap = QIcon::fromTheme(QLatin1String("edit-clear")).pixmap(24); + break; + case SP_DialogHelpButton: + pixmap = QIcon::fromTheme(QLatin1String("help-contents")).pixmap(24); + break; + case SP_DialogNoButton: + case SP_DialogCancelButton: + pixmap = QIcon::fromTheme(QLatin1String("dialog-cancel"), + QIcon::fromTheme(QLatin1String("process-stop"))).pixmap(24); + break; + case SP_DialogSaveButton: + pixmap = QIcon::fromTheme(QLatin1String("document-save")).pixmap(24); + break; + case SP_FileLinkIcon: + pixmap = QIcon::fromTheme(QLatin1String("emblem-symbolic-link")).pixmap(16); + if (!pixmap.isNull()) { + QPixmap fileIcon = QIcon::fromTheme(QLatin1String("text-x-generic")).pixmap(16); + if (fileIcon.isNull()) + fileIcon = QIcon::fromTheme(QLatin1String("empty")).pixmap(16); + if (!fileIcon.isNull()) { + QPainter painter(&fileIcon); + painter.drawPixmap(0, 0, 16, 16, pixmap); + return fileIcon; + } + } + break; + case SP_DirLinkIcon: + pixmap = QIcon::fromTheme(QLatin1String("emblem-symbolic-link")).pixmap(16); + if (!pixmap.isNull()) { + QPixmap dirIcon = QIcon::fromTheme(QLatin1String("folder")).pixmap(16); + if (!dirIcon.isNull()) { + QPainter painter(&dirIcon); + painter.drawPixmap(0, 0, 16, 16, pixmap); + return dirIcon; + } + } + break; + default: + break; + } + } + + if (!pixmap.isNull()) + return pixmap; +#endif //QT_NO_IMAGEFORMAT_PNG + switch (sp) { +#ifndef QT_NO_IMAGEFORMAT_XPM + case SP_ToolBarHorizontalExtensionButton: + if (rtl) { + QImage im(tb_extension_arrow_h_xpm); + im = im.convertToFormat(QImage::Format_ARGB32).mirrored(true, false); + return QPixmap::fromImage(im); + } + return QPixmap(tb_extension_arrow_h_xpm); + case SP_ToolBarVerticalExtensionButton: + return QPixmap(tb_extension_arrow_v_xpm); + case SP_FileDialogStart: + return QPixmap(filedialog_start_xpm); + case SP_FileDialogEnd: + return QPixmap(filedialog_end_xpm); +#endif +#ifndef QT_NO_IMAGEFORMAT_PNG + case SP_CommandLink: + case SP_ArrowForward: + if (rtl) + return proxy()->standardPixmap(SP_ArrowLeft, option, widget); + return proxy()->standardPixmap(SP_ArrowRight, option, widget); + case SP_ArrowBack: + if (rtl) + return proxy()->standardPixmap(SP_ArrowRight, option, widget); + return proxy()->standardPixmap(SP_ArrowLeft, option, widget); + case SP_ArrowLeft: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/left-16.png")); + case SP_ArrowRight: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/right-16.png")); + case SP_ArrowUp: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/up-16.png")); + case SP_ArrowDown: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/down-16.png")); + case SP_FileDialogToParent: + return proxy()->standardPixmap(SP_ArrowUp, option, widget); + case SP_FileDialogNewFolder: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/newdirectory-16.png")); + case SP_FileDialogDetailedView: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/viewdetailed-16.png")); + case SP_FileDialogInfoView: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/fileinfo-16.png")); + case SP_FileDialogContentsView: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/filecontents-16.png")); + case SP_FileDialogListView: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/viewlist-16.png")); + case SP_FileDialogBack: + return proxy()->standardPixmap(SP_ArrowBack, option, widget); + case SP_DriveHDIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/harddrive-16.png")); + case SP_TrashIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/trash-16.png")); + case SP_DriveFDIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/floppy-16.png")); + case SP_DriveNetIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/networkdrive-16.png")); + case SP_DesktopIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/desktop-16.png")); + case SP_ComputerIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/computer-16.png")); + case SP_DriveCDIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/cdr-16.png")); + case SP_DriveDVDIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/dvd-16.png")); + case SP_DirHomeIcon: + case SP_DirOpenIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/diropen-16.png")); + case SP_DirIcon: + case SP_DirClosedIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/dirclosed-16.png")); + case SP_DirLinkIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/dirlink-16.png")); + case SP_FileIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/file-16.png")); + case SP_FileLinkIcon: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/filelink-16.png")); + case SP_DialogOkButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-ok-16.png")); + case SP_DialogCancelButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-cancel-16.png")); + case SP_DialogHelpButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-help-16.png")); + case SP_DialogOpenButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-open-16.png")); + case SP_DialogSaveButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-save-16.png")); + case SP_DialogCloseButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-close-16.png")); + case SP_DialogApplyButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-apply-16.png")); + case SP_DialogResetButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-clear-16.png")); + case SP_DialogDiscardButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-delete-16.png")); + case SP_DialogYesButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-yes-16.png")); + case SP_DialogNoButton: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-no-16.png")); + case SP_BrowserReload: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/refresh-24.png")); + case SP_BrowserStop: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/stop-24.png")); + case SP_MediaPlay: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-play-32.png")); + case SP_MediaPause: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-pause-32.png")); + case SP_MediaStop: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-stop-32.png")); + case SP_MediaSeekForward: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-forward-32.png")); + case SP_MediaSeekBackward: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-backward-32.png")); + case SP_MediaSkipForward: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-forward-32.png")); + case SP_MediaSkipBackward: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-backward-32.png")); + case SP_MediaVolume: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-volume-16.png")); + case SP_MediaVolumeMuted: + return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/media-volume-muted-16.png")); +#endif // QT_NO_IMAGEFORMAT_PNG + default: + break; + } + return QPixmap(); +} + +/*! + \internal +*/ +QIcon QCommonStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget) const +{ + QIcon icon; + const bool rtl = (option && option->direction == Qt::RightToLeft) || (!option && QApplication::isRightToLeft()); + if (QApplication::desktopSettingsAware() && !QIcon::themeName().isEmpty()) { + switch (standardIcon) { + case SP_DirHomeIcon: + icon = QIcon::fromTheme(QLatin1String("user-home")); + break; + case SP_MessageBoxInformation: + icon = QIcon::fromTheme(QLatin1String("dialog-information")); + break; + case SP_MessageBoxWarning: + icon = QIcon::fromTheme(QLatin1String("dialog-warning")); + break; + case SP_MessageBoxCritical: + icon = QIcon::fromTheme(QLatin1String("dialog-error")); + break; + case SP_MessageBoxQuestion: + icon = QIcon::fromTheme(QLatin1String("dialog-question")); + break; + case SP_DialogOpenButton: + case SP_DirOpenIcon: + icon = QIcon::fromTheme(QLatin1String("folder-open")); + break; + case SP_DialogSaveButton: + icon = QIcon::fromTheme(QLatin1String("document-save")); + break; + case SP_DialogApplyButton: + icon = QIcon::fromTheme(QLatin1String("dialog-ok-apply")); + break; + case SP_DialogYesButton: + case SP_DialogOkButton: + icon = QIcon::fromTheme(QLatin1String("dialog-ok")); + break; + case SP_DialogDiscardButton: + icon = QIcon::fromTheme(QLatin1String("edit-delete")); + break; + case SP_DialogResetButton: + icon = QIcon::fromTheme(QLatin1String("edit-clear")); + break; + case SP_DialogHelpButton: + icon = QIcon::fromTheme(QLatin1String("help-contents")); + break; + case SP_FileIcon: + icon = QIcon::fromTheme(QLatin1String("text-x-generic")); + break; + case SP_DirClosedIcon: + case SP_DirIcon: + icon = QIcon::fromTheme(QLatin1String("folder")); + break; + case SP_DriveFDIcon: + icon = QIcon::fromTheme(QLatin1String("floppy_unmount")); + break; + case SP_ComputerIcon: + icon = QIcon::fromTheme(QLatin1String("computer"), + QIcon::fromTheme(QLatin1String("system"))); + break; + case SP_DesktopIcon: + icon = QIcon::fromTheme(QLatin1String("user-desktop")); + break; + case SP_TrashIcon: + icon = QIcon::fromTheme(QLatin1String("user-trash")); + break; + case SP_DriveCDIcon: + case SP_DriveDVDIcon: + icon = QIcon::fromTheme(QLatin1String("media-optical")); + break; + case SP_DriveHDIcon: + icon = QIcon::fromTheme(QLatin1String("drive-harddisk")); + break; + case SP_FileDialogToParent: + icon = QIcon::fromTheme(QLatin1String("go-up")); + break; + case SP_FileDialogNewFolder: + icon = QIcon::fromTheme(QLatin1String("folder-new")); + break; + case SP_ArrowUp: + icon = QIcon::fromTheme(QLatin1String("go-up")); + break; + case SP_ArrowDown: + icon = QIcon::fromTheme(QLatin1String("go-down")); + break; + case SP_ArrowRight: + icon = QIcon::fromTheme(QLatin1String("go-next")); + break; + case SP_ArrowLeft: + icon = QIcon::fromTheme(QLatin1String("go-previous")); + break; + case SP_DialogCancelButton: + icon = QIcon::fromTheme(QLatin1String("dialog-cancel"), + QIcon::fromTheme(QLatin1String("process-stop"))); + break; + case SP_DialogCloseButton: + icon = QIcon::fromTheme(QLatin1String("window-close")); + break; + case SP_FileDialogDetailedView: + icon = QIcon::fromTheme(QLatin1String("view-list-details")); + break; + case SP_FileDialogListView: + icon = QIcon::fromTheme(QLatin1String("view-list-icons")); + break; + case SP_BrowserReload: + icon = QIcon::fromTheme(QLatin1String("view-refresh")); + break; + case SP_BrowserStop: + icon = QIcon::fromTheme(QLatin1String("process-stop")); + break; + case SP_MediaPlay: + icon = QIcon::fromTheme(QLatin1String("media-playback-start")); + break; + case SP_MediaPause: + icon = QIcon::fromTheme(QLatin1String("media-playback-pause")); + break; + case SP_MediaStop: + icon = QIcon::fromTheme(QLatin1String("media-playback-stop")); + break; + case SP_MediaSeekForward: + icon = QIcon::fromTheme(QLatin1String("media-seek-forward")); + break; + case SP_MediaSeekBackward: + icon = QIcon::fromTheme(QLatin1String("media-seek-backward")); + break; + case SP_MediaSkipForward: + icon = QIcon::fromTheme(QLatin1String("media-skip-forward")); + break; + case SP_MediaSkipBackward: + icon = QIcon::fromTheme(QLatin1String("media-skip-backward")); + break; + case SP_MediaVolume: + icon = QIcon::fromTheme(QLatin1String("audio-volume-medium")); + break; + case SP_MediaVolumeMuted: + icon = QIcon::fromTheme(QLatin1String("audio-volume-muted")); + break; + case SP_ArrowForward: + if (rtl) + return standardIconImplementation(SP_ArrowLeft, option, widget); + return standardIconImplementation(SP_ArrowRight, option, widget); + case SP_ArrowBack: + if (rtl) + return standardIconImplementation(SP_ArrowRight, option, widget); + return standardIconImplementation(SP_ArrowLeft, option, widget); + case SP_FileLinkIcon: + { + QIcon linkIcon = QIcon::fromTheme(QLatin1String("emblem-symbolic-link")); + if (!linkIcon.isNull()) { + QIcon baseIcon = standardIconImplementation(SP_FileIcon, option, widget); + const QList<QSize> sizes = baseIcon.availableSizes(QIcon::Normal, QIcon::Off); + for (int i = 0 ; i < sizes.size() ; ++i) { + int size = sizes[i].width(); + QPixmap basePixmap = baseIcon.pixmap(size); + QPixmap linkPixmap = linkIcon.pixmap(size/2); + QPainter painter(&basePixmap); + painter.drawPixmap(size/2, size/2, linkPixmap); + icon.addPixmap(basePixmap); + } + } + } + break; + case SP_DirLinkIcon: + { + QIcon linkIcon = QIcon::fromTheme(QLatin1String("emblem-symbolic-link")); + if (!linkIcon.isNull()) { + QIcon baseIcon = standardIconImplementation(SP_DirIcon, option, widget); + const QList<QSize> sizes = baseIcon.availableSizes(QIcon::Normal, QIcon::Off); + for (int i = 0 ; i < sizes.size() ; ++i) { + int size = sizes[i].width(); + QPixmap basePixmap = baseIcon.pixmap(size); + QPixmap linkPixmap = linkIcon.pixmap(size/2); + QPainter painter(&basePixmap); + painter.drawPixmap(size/2, size/2, linkPixmap); + icon.addPixmap(basePixmap); + } + } + } + break; + default: + break; + } + } // if (QApplication::desktopSettingsAware() && !QIcon::themeName().isEmpty()) + if (!icon.isNull()) + return icon; +#if defined(Q_WS_MAC) + if (QApplication::desktopSettingsAware()) { + OSType iconType = 0; + switch (standardIcon) { + case QStyle::SP_MessageBoxQuestion: + iconType = kQuestionMarkIcon; + break; + case QStyle::SP_MessageBoxInformation: + iconType = kAlertNoteIcon; + break; + case QStyle::SP_MessageBoxWarning: + iconType = kAlertCautionIcon; + break; + case QStyle::SP_MessageBoxCritical: + iconType = kAlertStopIcon; + break; + case SP_DesktopIcon: + iconType = kDesktopIcon; + break; + case SP_TrashIcon: + iconType = kTrashIcon; + break; + case SP_ComputerIcon: + iconType = kComputerIcon; + break; + case SP_DriveFDIcon: + iconType = kGenericFloppyIcon; + break; + case SP_DriveHDIcon: + iconType = kGenericHardDiskIcon; + break; + case SP_DriveCDIcon: + case SP_DriveDVDIcon: + iconType = kGenericCDROMIcon; + break; + case SP_DriveNetIcon: + iconType = kGenericNetworkIcon; + break; + case SP_DirOpenIcon: + iconType = kOpenFolderIcon; + break; + case SP_DirClosedIcon: + case SP_DirLinkIcon: + iconType = kGenericFolderIcon; + break; + case SP_FileLinkIcon: + case SP_FileIcon: + iconType = kGenericDocumentIcon; + break; + case SP_DirIcon: { + // A rather special case + QIcon closeIcon = QStyle::standardIcon(SP_DirClosedIcon, option, widget); + QIcon openIcon = QStyle::standardIcon(SP_DirOpenIcon, option, widget); + closeIcon.addPixmap(openIcon.pixmap(16, 16), QIcon::Normal, QIcon::On); + closeIcon.addPixmap(openIcon.pixmap(32, 32), QIcon::Normal, QIcon::On); + closeIcon.addPixmap(openIcon.pixmap(64, 64), QIcon::Normal, QIcon::On); + closeIcon.addPixmap(openIcon.pixmap(128, 128), QIcon::Normal, QIcon::On); + return closeIcon; + } + case SP_TitleBarNormalButton: + case SP_TitleBarCloseButton: { + QIcon titleBarIcon; + if (standardIcon == SP_TitleBarCloseButton) { + titleBarIcon.addFile(QLatin1String(":/trolltech/styles/macstyle/images/closedock-16.png")); + titleBarIcon.addFile(QLatin1String(":/trolltech/styles/macstyle/images/closedock-down-16.png"), QSize(16, 16), QIcon::Normal, QIcon::On); + } else { + titleBarIcon.addFile(QLatin1String(":/trolltech/styles/macstyle/images/dockdock-16.png")); + titleBarIcon.addFile(QLatin1String(":/trolltech/styles/macstyle/images/dockdock-down-16.png"), QSize(16, 16), QIcon::Normal, QIcon::On); + } + return titleBarIcon; + } + default: + break; + } + if (iconType != 0) { + QIcon retIcon; + IconRef icon; + IconRef overlayIcon = 0; + if (iconType != kGenericApplicationIcon) { + GetIconRef(kOnSystemDisk, kSystemIconsCreator, iconType, &icon); + } else { + FSRef fsRef; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + GetProcessBundleLocation(&psn, &fsRef); + GetIconRefFromFileInfo(&fsRef, 0, 0, 0, 0, kIconServicesNormalUsageFlag, &icon, 0); + if (standardIcon == SP_MessageBoxCritical) { + overlayIcon = icon; + GetIconRef(kOnSystemDisk, kSystemIconsCreator, kAlertCautionIcon, &icon); + } + } + if (icon) { + qt_mac_constructQIconFromIconRef(icon, overlayIcon, &retIcon, standardIcon); + ReleaseIconRef(icon); + } + if (overlayIcon) + ReleaseIconRef(overlayIcon); + return retIcon; + } + } // if (QApplication::desktopSettingsAware()) +#endif // Q_WS_MAC + + switch (standardIcon) { +#ifndef QT_NO_IMAGEFORMAT_PNG + case SP_FileDialogNewFolder: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/newdirectory-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/newdirectory-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/newdirectory-128.png"), QSize(128, 128)); + break; + case SP_FileDialogBack: + return standardIconImplementation(SP_ArrowBack, option, widget); + case SP_FileDialogToParent: + return standardIconImplementation(SP_ArrowUp, option, widget); + case SP_FileDialogDetailedView: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewdetailed-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewdetailed-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewdetailed-128.png"), QSize(128, 128)); + break; + case SP_FileDialogInfoView: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/fileinfo-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/fileinfo-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/fileinfo-128.png"), QSize(128, 128)); + break; + case SP_FileDialogContentsView: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filecontents-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filecontents-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filecontents-128.png"), QSize(128, 128)); + break; + case SP_FileDialogListView: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewlist-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewlist-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/viewlist-128.png"), QSize(128, 128)); + break; + case SP_DialogOkButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-ok-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-ok-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-ok-128.png"), QSize(128, 128)); + break; + case SP_DialogCancelButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-cancel-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-cancel-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-cancel-128.png"), QSize(128, 128)); + break; + case SP_DialogHelpButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-help-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-help-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-help-128.png"), QSize(128, 128)); + break; + case SP_DialogOpenButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-open-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-open-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-open-128.png"), QSize(128, 128)); + break; + case SP_DialogSaveButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-save-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-save-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-save-128.png"), QSize(128, 128)); + break; + case SP_DialogCloseButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-close-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-close-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-close-128.png"), QSize(128, 128)); + break; + case SP_DialogApplyButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-apply-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-apply-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-apply-128.png"), QSize(128, 128)); + break; + case SP_DialogResetButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-clear-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-clear-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-clear-128.png"), QSize(128, 128)); + break; + case SP_DialogDiscardButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-delete-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-delete-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-delete-128.png"), QSize(128, 128)); + break; + case SP_DialogYesButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-yes-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-yes-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-yes-128.png"), QSize(128, 128)); + break; + case SP_DialogNoButton: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-no-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-no-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/standardbutton-no-128.png"), QSize(128, 128)); + break; + case SP_ArrowForward: + if (rtl) + return standardIconImplementation(SP_ArrowLeft, option, widget); + return standardIconImplementation(SP_ArrowRight, option, widget); + case SP_ArrowBack: + if (rtl) + return standardIconImplementation(SP_ArrowRight, option, widget); + return standardIconImplementation(SP_ArrowLeft, option, widget); + case SP_ArrowLeft: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/left-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/left-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/left-128.png"), QSize(128, 128)); + break; + case SP_ArrowRight: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/right-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/right-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/right-128.png"), QSize(128, 128)); + break; + case SP_ArrowUp: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/up-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/up-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/up-128.png"), QSize(128, 128)); + break; + case SP_ArrowDown: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/down-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/down-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/down-128.png"), QSize(128, 128)); + break; + case SP_DirHomeIcon: + case SP_DirIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dirclosed-16.png"), + QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/diropen-16.png"), + QSize(), QIcon::Normal, QIcon::On); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dirclosed-32.png"), + QSize(32, 32), QIcon::Normal, QIcon::Off); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/diropen-32.png"), + QSize(32, 32), QIcon::Normal, QIcon::On); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dirclosed-128.png"), + QSize(128, 128), QIcon::Normal, QIcon::Off); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/diropen-128.png"), + QSize(128, 128), QIcon::Normal, QIcon::On); + break; + case SP_DriveCDIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/cdr-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/cdr-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/cdr-128.png"), QSize(128, 128)); + break; + case SP_DriveDVDIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dvd-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dvd-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/dvd-128.png"), QSize(128, 128)); + break; + case SP_FileIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/file-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/file-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/file-128.png"), QSize(128, 128)); + break; + case SP_FileLinkIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filelink-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filelink-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/filelink-128.png"), QSize(128, 128)); + break; + case SP_TrashIcon: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/trash-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/trash-32.png"), QSize(32, 32)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/trash-128.png"), QSize(128, 128)); + break; + case SP_BrowserReload: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/refresh-24.png"), QSize(24, 24)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/refresh-32.png"), QSize(32, 32)); + break; + case SP_BrowserStop: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/stop-24.png"), QSize(24, 24)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/stop-32.png"), QSize(32, 32)); + break; + case SP_MediaPlay: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-play-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-play-32.png"), QSize(32, 32)); + break; + case SP_MediaPause: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-pause-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-pause-32.png"), QSize(32, 32)); + break; + case SP_MediaStop: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-stop-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-stop-32.png"), QSize(32, 32)); + break; + case SP_MediaSeekForward: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-forward-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-forward-32.png"), QSize(32, 32)); + break; + case SP_MediaSeekBackward: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-backward-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-seek-backward-32.png"), QSize(32, 32)); + break; + case SP_MediaSkipForward: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-forward-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-forward-32.png"), QSize(32, 32)); + break; + case SP_MediaSkipBackward: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-backward-16.png"), QSize(16, 16)); + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-skip-backward-32.png"), QSize(32, 32)); + break; + case SP_MediaVolume: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-volume-16.png"), QSize(16, 16)); + break; + case SP_MediaVolumeMuted: + icon.addFile(QLatin1String(":/trolltech/styles/commonstyle/images/media-volume-muted-16.png"), QSize(16, 16)); + break; +#endif // QT_NO_IMAGEFORMAT_PNG + default: + icon.addPixmap(proxy()->standardPixmap(standardIcon, option, widget)); + break; + } + return icon; +} + +static inline uint qt_intensity(uint r, uint g, uint b) +{ + // 30% red, 59% green, 11% blue + return (77 * r + 150 * g + 28 * b) / 255; +} + +/*! \reimp */ +QPixmap QCommonStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + switch (iconMode) { + case QIcon::Disabled: { + QImage im = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + + // Create a colortable based on the background (black -> bg -> white) + QColor bg = opt->palette.color(QPalette::Disabled, QPalette::Window); + int red = bg.red(); + int green = bg.green(); + int blue = bg.blue(); + uchar reds[256], greens[256], blues[256]; + for (int i=0; i<128; ++i) { + reds[i] = uchar((red * (i<<1)) >> 8); + greens[i] = uchar((green * (i<<1)) >> 8); + blues[i] = uchar((blue * (i<<1)) >> 8); + } + for (int i=0; i<128; ++i) { + reds[i+128] = uchar(qMin(red + (i << 1), 255)); + greens[i+128] = uchar(qMin(green + (i << 1), 255)); + blues[i+128] = uchar(qMin(blue + (i << 1), 255)); + } + + int intensity = qt_intensity(red, green, blue); + const int factor = 191; + + // High intensity colors needs dark shifting in the color table, while + // low intensity colors needs light shifting. This is to increase the + // percieved contrast. + if ((red - factor > green && red - factor > blue) + || (green - factor > red && green - factor > blue) + || (blue - factor > red && blue - factor > green)) + intensity = qMin(255, intensity + 91); + else if (intensity <= 128) + intensity -= 51; + + for (int y=0; y<im.height(); ++y) { + QRgb *scanLine = (QRgb*)im.scanLine(y); + for (int x=0; x<im.width(); ++x) { + QRgb pixel = *scanLine; + // Calculate color table index, taking intensity adjustment + // and a magic offset into account. + uint ci = uint(qGray(pixel)/3 + (130 - intensity / 3)); + *scanLine = qRgba(reds[ci], greens[ci], blues[ci], qAlpha(pixel)); + ++scanLine; + } + } + + return QPixmap::fromImage(im); + } + case QIcon::Selected: { + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); + QColor color = opt->palette.color(QPalette::Normal, QPalette::Highlight); + color.setAlphaF(qreal(0.3)); + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter.fillRect(0, 0, img.width(), img.height(), color); + painter.end(); + return QPixmap::fromImage(img); } + case QIcon::Active: + return pixmap; + default: + break; + } + return pixmap; +} + +/*! + \reimp +*/ +void QCommonStyle::polish(QPalette &pal) +{ + QStyle::polish(pal); +} + +/*! + \reimp + */ +void QCommonStyle::polish(QWidget *widget) +{ + QStyle::polish(widget); +} + +/*! + \reimp + */ +void QCommonStyle::unpolish(QWidget *widget) +{ + QStyle::unpolish(widget); +} + +/*! + \reimp +*/ +void QCommonStyle::polish(QApplication *app) +{ + QStyle::polish(app); +} + +/*! + \reimp + */ +void QCommonStyle::unpolish(QApplication *application) +{ + Q_D(const QCommonStyle); + d->tabBarcloseButtonIcon = QIcon(); + QStyle::unpolish(application); +} + + +QT_END_NAMESPACE diff --git a/src/gui/styles/qcommonstyle.h b/src/gui/styles/qcommonstyle.h new file mode 100644 index 0000000000..2b42367aac --- /dev/null +++ b/src/gui/styles/qcommonstyle.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMMONSTYLE_H +#define QCOMMONSTYLE_H + +#include <QtGui/qstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE +QT_MODULE(Gui) + +class QCommonStylePrivate; + +class Q_GUI_EXPORT QCommonStyle: public QStyle +{ + Q_OBJECT + +public: + QCommonStyle(); + ~QCommonStyle(); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *w = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *widget = 0) const; + + int pixelMetric(PixelMetric m, const QStyleOption *opt = 0, const QWidget *widget = 0) const; + + int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, + QStyleHintReturn *shret = 0) const; + + QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + + void polish(QPalette &); + void polish(QApplication *app); + void polish(QWidget *widget); + void unpolish(QWidget *widget); + void unpolish(QApplication *application); + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + +protected: + QCommonStyle(QCommonStylePrivate &dd); + +private: + Q_DECLARE_PRIVATE(QCommonStyle) + Q_DISABLE_COPY(QCommonStyle) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QCOMMONSTYLE_H diff --git a/src/gui/styles/qcommonstyle_p.h b/src/gui/styles/qcommonstyle_p.h new file mode 100644 index 0000000000..a02bd44259 --- /dev/null +++ b/src/gui/styles/qcommonstyle_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMMONSTYLE_P_H +#define QCOMMONSTYLE_P_H + +#include "qcommonstyle.h" +#include "qstyle_p.h" + +#include "qstyleoption.h" + +QT_BEGIN_NAMESPACE + +// +// 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. +// + +class QStringList; + +// Private class +class QCommonStylePrivate : public QStylePrivate +{ + Q_DECLARE_PUBLIC(QCommonStyle) +public: + inline QCommonStylePrivate() +#ifndef QT_NO_ITEMVIEWS + : cachedOption(0) +#endif + { } + +#ifndef QT_NO_ITEMVIEWS + ~QCommonStylePrivate() + { + delete cachedOption; + } + void viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect) const; + void viewItemLayout(const QStyleOptionViewItemV4 *opt, QRect *checkRect, + QRect *pixmapRect, QRect *textRect, bool sizehint) const; + QSize viewItemSize(const QStyleOptionViewItemV4 *option, int role) const; + + mutable QRect decorationRect, displayRect, checkRect; + mutable QStyleOptionViewItemV4 *cachedOption; + bool isViewItemCached(const QStyleOptionViewItemV4 &option) const { + return cachedOption && (option.rect == cachedOption->rect + && option.direction == cachedOption->direction + && option.state == cachedOption->state + && option.displayAlignment == cachedOption->displayAlignment + && option.decorationAlignment == cachedOption->decorationAlignment + && option.decorationPosition == cachedOption->decorationPosition + && option.decorationSize == cachedOption->decorationSize + && option.font == cachedOption->font + && option.features == cachedOption->features + && option.widget == cachedOption->widget + && option.index == cachedOption->index + && option.icon.isNull() == cachedOption->icon.isNull() + && option.text == cachedOption->text + && option.viewItemPosition == cachedOption->viewItemPosition); + } +#endif + mutable QIcon tabBarcloseButtonIcon; +#ifndef QT_NO_TABBAR + void tabLayout(const QStyleOptionTabV3 *opt, const QWidget *widget, QRect *textRect, QRect *pixmapRect) const; +#endif +}; + +QT_END_NAMESPACE + +#endif //QCOMMONSTYLE_P_H diff --git a/src/gui/styles/qcommonstylepixmaps_p.h b/src/gui/styles/qcommonstylepixmaps_p.h new file mode 100644 index 0000000000..fb87c0e7a7 --- /dev/null +++ b/src/gui/styles/qcommonstylepixmaps_p.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_IMAGEFORMAT_XPM + +// +// 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. +// + +static const char * const check_list_controller_xpm[] = { +"16 16 4 1", +" c None", +". c #000000000000", +"X c #FFFFFFFF0000", +"o c #C71BC30BC71B", +" ", +" ", +" .......... ", +" .XXXXXXXX. ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" .XXXXXXXX.oo ", +" ..........oo ", +" oooooooooo ", +" oooooooooo ", +" ", +" "}; + +static const char * const tree_branch_open_xpm[] = { +"9 9 2 1", +" c None", +"# c #000000", +"#########", +"# #", +"# ##### #", +"# ### #", +"# ### #", +"# # #", +"# # #", +"# #", +"#########"}; + +static const char * const tree_branch_closed_xpm[] = { +"9 9 2 1", +" c None", +"# c #000000", +"#########", +"# #", +"# # #", +"# ### #", +"# ##### #", +"# ### #", +"# # #", +"# #", +"#########"}; + +static const char * const tb_extension_arrow_v_xpm[] = { + "5 8 3 1", + " c None", + ". c #000000", + "+ c none", + ".+++.", + "..+..", + "+...+", + "++.++", + ".+++.", + "..+..", + "+...+", + "++.++" +}; + +static const char * const tb_extension_arrow_h_xpm[] = { + "8 5 3 1", + " c None", + ". c #000000", + "+ c none", + "..++..++", + "+..++..+", + "++..++..", + "+..++..+", + "..++..++", +}; + +static const char * const filedialog_start_xpm[]={ + "16 15 8 1", + "a c #cec6bd", + "# c #000000", + "e c #ffff00", + "b c #999999", + "f c #cccccc", + "d c #dcdcdc", + "c c #ffffff", + ". c None", + ".....######aaaaa", + "...bb#cccc##aaaa", + "..bcc#cccc#d#aaa", + ".bcef#cccc#dd#aa", + ".bcfe#cccc#####a", + ".bcef#ccccccccc#", + "bbbbbbbbbbbbccc#", + "bccccccccccbbcc#", + "bcefefefefee#bc#", + ".bcefefefefef#c#", + ".bcfefefefefe#c#", + "..bcfefefefeeb##", + "..bbbbbbbbbbbbb#", + "...#############", + "................"}; + +static const char * const filedialog_end_xpm[]={ + "16 15 9 1", + "d c #a0a0a0", + "c c #c3c3c3", + "# c #cec6bd", + ". c #000000", + "f c #ffff00", + "e c #999999", + "g c #cccccc", + "b c #ffffff", + "a c None", + "......####aaaaaa", + ".bbbb..###aaaaaa", + ".bbbb.c.##aaaaaa", + ".bbbb....ddeeeea", + ".bbbbbbb.bbbbbe.", + ".bbbbbbb.bcfgfe.", + "eeeeeeeeeeeeefe.", + "ebbbbbbbbbbeege.", + "ebfgfgfgfgff.ee.", + "aebfgfgfgfgfg.e.", + "aebgfgfgfgfgf.e.", + "aaebgfgfgfgffe..", + "aaeeeeeeeeeeeee.", + "aaa.............", + "aaaaaaaaaaaaaaaa"}; + +#endif // QT_NO_IMAGEFORMAT_XPM diff --git a/src/gui/styles/qgtkpainter.cpp b/src/gui/styles/qgtkpainter.cpp new file mode 100644 index 0000000000..68ade04984 --- /dev/null +++ b/src/gui/styles/qgtkpainter.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgtkpainter_p.h" + +#include <QtCore/qglobal.h> +#if !defined(QT_NO_STYLE_GTK) + +// This class is primarily a wrapper around the gtk painter functions +// and takes care of converting all such calls into cached Qt pixmaps. + +#include <private/qstylehelper_p.h> +#include <QtGui/QWidget> +#include <QtGui/QStyleOption> +#include <QtGui/QPixmapCache> + +QT_BEGIN_NAMESPACE + +#undef GTK_OBJECT_FLAGS +#define GTK_OBJECT_FLAGS(obj)(((GtkObject*)(obj))->flags) + +#if Q_BYTE_ORDER == Q_BIG_ENDIAN +# define QT_RED 3 +# define QT_GREEN 2 +# define QT_BLUE 1 +# define QT_ALPHA 0 +#else +# define QT_RED 0 +# define QT_GREEN 1 +# define QT_BLUE 2 +# define QT_ALPHA 3 +#endif +# define GTK_RED 2 +# define GTK_GREEN 1 +# define GTK_BLUE 0 +# define GTK_ALPHA 3 + +// To recover alpha we apply the gtk painting function two times to +// white, and black window backgrounds. This can be used to +// recover the premultiplied alpha channel +QPixmap QGtkPainter::renderTheme(uchar *bdata, uchar *wdata, const QRect &rect) +{ + const int bytecount = rect.width() * rect.height() * 4; + for (int index = 0; index < bytecount ; index += 4) { + uchar val = bdata[index + GTK_BLUE]; + if (m_alpha) { + int alphaval = qMax(bdata[index + GTK_BLUE] - wdata[index + GTK_BLUE], + bdata[index + GTK_GREEN] - wdata[index + GTK_GREEN]); + alphaval = qMax(alphaval, bdata[index + GTK_RED] - wdata[index + GTK_RED]) + 255; + bdata[index + QT_ALPHA] = alphaval; + } + bdata[index + QT_RED] = bdata[index + GTK_RED]; + bdata[index + QT_GREEN] = bdata[index + GTK_GREEN]; + bdata[index + QT_BLUE] = val; + } + QImage converted((const uchar*)bdata, rect.width(), rect.height(), m_alpha ? + QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32); + + if (m_hflipped || m_vflipped) { + return QPixmap::fromImage(converted.mirrored(m_hflipped, m_vflipped)); + } else { + // on raster graphicssystem we need to do a copy here, because + // we intend to deallocate the qimage bits shortly after... + return QPixmap::fromImage(converted.copy()); + } +} + +// This macro is responsible for painting any GtkStyle painting function onto a QPixmap +#define DRAW_TO_CACHE(draw_func) \ + if (rect.width() > QWIDGETSIZE_MAX || rect.height() > QWIDGETSIZE_MAX) \ + return; \ + QRect pixmapRect(0, 0, rect.width(), rect.height()); \ + { \ + GdkPixmap *pixmap = QGtkStylePrivate::gdk_pixmap_new((GdkDrawable*)(m_window->window), \ + rect.width(), rect.height(), -1); \ + if (!pixmap) \ + return; \ + style = QGtkStylePrivate::gtk_style_attach (style, m_window->window); \ + QGtkStylePrivate::gdk_draw_rectangle(pixmap, m_alpha ? style->black_gc : *style->bg_gc, true, \ + 0, 0, rect.width(), rect.height()); \ + draw_func; \ + GdkPixbuf *imgb = QGtkStylePrivate::gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, rect.width(), rect.height());\ + if (!imgb) \ + return; \ + imgb = QGtkStylePrivate::gdk_pixbuf_get_from_drawable(imgb, pixmap, NULL, 0, 0, 0, 0, \ + rect.width(), rect.height()); \ + uchar* bdata = (uchar*)QGtkStylePrivate::gdk_pixbuf_get_pixels(imgb); \ + if (m_alpha) { \ + QGtkStylePrivate::gdk_draw_rectangle(pixmap, style->white_gc, true, 0, 0, rect.width(), rect.height()); \ + draw_func; \ + GdkPixbuf *imgw = QGtkStylePrivate::gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, rect. \ + width(), rect.height()); \ + if (!imgw) \ + return; \ + imgw = QGtkStylePrivate::gdk_pixbuf_get_from_drawable(imgw, pixmap, NULL, 0, 0, 0, 0, \ + rect.width(), rect.height()); \ + uchar* wdata = (uchar*)QGtkStylePrivate::gdk_pixbuf_get_pixels(imgw); \ + cache = renderTheme(bdata, wdata, rect); \ + QGtkStylePrivate::gdk_pixbuf_unref(imgw); \ + } else { \ + cache = renderTheme(bdata, 0, rect); \ + } \ + QGtkStylePrivate::gdk_drawable_unref(pixmap); \ + QGtkStylePrivate::gdk_pixbuf_unref(imgb); \ + } + +QGtkPainter::QGtkPainter(QPainter *_painter) + : m_window(QGtkStylePrivate::gtkWidget("GtkWindow")) + , m_painter(_painter) + , m_alpha(true) + , m_hflipped(false) + , m_vflipped(false) + , m_usePixmapCache(true) +{} + + +static QString uniqueName(const QString &key, GtkStateType state, GtkShadowType shadow, + const QSize &size, GtkWidget *widget = 0) +{ + // Note the widget arg should ideally use the widget path, though would compromise performance + QString tmp = key + % HexString<uint>(state) + % HexString<uint>(shadow) + % HexString<uint>(size.width()) + % HexString<uint>(size.height()) + % HexString<quint64>(quint64(widget)); + return tmp; +} + + +GtkStateType QGtkPainter::gtkState(const QStyleOption *option) + +{ + GtkStateType state = GTK_STATE_NORMAL; + if (!(option->state & QStyle::State_Enabled)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & QStyle::State_MouseOver) + state = GTK_STATE_PRELIGHT; + + return state; +} + + +GtkStyle* QGtkPainter::getStyle(GtkWidget *gtkWidget) + +{ + Q_ASSERT(gtkWidget); + GtkStyle* style = gtkWidget->style; + Q_ASSERT(style); + return style; +} + +QPixmap QGtkPainter::getIcon(const char* iconName, GtkIconSize size) +{ + GtkStyle *style = QGtkStylePrivate::gtkStyle(); + GtkIconSet* iconSet = QGtkStylePrivate::gtk_icon_factory_lookup_default (iconName); + GdkPixbuf* icon = QGtkStylePrivate::gtk_icon_set_render_icon(iconSet, + style, + GTK_TEXT_DIR_LTR, + GTK_STATE_NORMAL, + size, + NULL, + "button"); + uchar* data = (uchar*)QGtkStylePrivate::gdk_pixbuf_get_pixels(icon); + int width = QGtkStylePrivate::gdk_pixbuf_get_width(icon); + int height = QGtkStylePrivate::gdk_pixbuf_get_height(icon); + QImage converted(width, height, QImage::Format_ARGB32); + uchar* tdata = (uchar*)converted.bits(); + + for ( int index = 0 ; index < height * width*4 ; index +=4 ) { + //int index = y * rowstride + x; + tdata[index + QT_RED] = data[index + GTK_RED]; + tdata[index + QT_GREEN] = data[index + GTK_GREEN]; + tdata[index + QT_BLUE] = data[index + GTK_BLUE]; + tdata[index + QT_ALPHA] = data[index + GTK_ALPHA]; + } + + QGtkStylePrivate::gdk_pixbuf_unref(icon); + + // should we free iconset? + return QPixmap::fromImage(converted); + +} + +// Note currently painted without alpha for performance reasons +void QGtkPainter::paintBoxGap(GtkWidget *gtkWidget, const gchar* part, + const QRect &paintRect, GtkStateType state, + GtkShadowType shadow, GtkPositionType gap_side, + gint x, gint width, + GtkStyle *style) +{ + if (!paintRect.isValid()) + return; + + QPixmap cache; + QRect rect = paintRect; + + // To avoid exhausting cache on large tabframes we cheat a bit by + // tiling the center part. + + const int maxHeight = 256; + const int border = 16; + if (rect.height() > maxHeight && (gap_side == GTK_POS_TOP || gap_side == GTK_POS_BOTTOM)) + rect.setHeight(2 * border + 1); + + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size(), gtkWidget) + % HexString<uchar>(gap_side) + % HexString<gint>(width) + % HexString<gint>(x); + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_box_gap (style, + pixmap, + state, + shadow, + NULL, + gtkWidget, + (gchar*)part, + 0, 0, + rect.width(), + rect.height(), + gap_side, + x, + width)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + if (rect.size() != paintRect.size()) { + // We assume we can stretch the middle tab part + // Note: the side effect of this is that pinstripe patterns will get fuzzy + const QSize size = cache.size(); + // top part + m_painter->drawPixmap(QRect(paintRect.left(), paintRect.top(), + paintRect.width(), border), cache, + QRect(0, 0, size.width(), border)); + + // tiled center part + QPixmap tilePart(cache.width(), 1); + QPainter scanLinePainter(&tilePart); + scanLinePainter.drawPixmap(QRect(0, 0, tilePart.width(), tilePart.height()), cache, QRect(0, border, size.width(), 1)); + scanLinePainter.end(); + m_painter->drawTiledPixmap(QRect(paintRect.left(), paintRect.top() + border, + paintRect.width(), paintRect.height() - 2*border), tilePart); + + // bottom part + m_painter->drawPixmap(QRect(paintRect.left(), paintRect.top() + paintRect.height() - border, + paintRect.width(), border), cache, + QRect(0, size.height() - border, size.width(), border)); + } else + m_painter->drawPixmap(paintRect.topLeft(), cache); +} + +void QGtkPainter::paintBox(GtkWidget *gtkWidget, const gchar* part, + const QRect &paintRect, GtkStateType state, + GtkShadowType shadow, GtkStyle *style, + const QString &pmKey) +{ + if (!paintRect.isValid()) + return; + + QPixmap cache; + QRect rect = paintRect; + + // To avoid exhausting cache on large tabframes we cheat a bit by + // tiling the center part. + + const int maxHeight = 256; + const int maxArea = 256*512; + const int border = 32; + if (rect.height() > maxHeight && (rect.width()*rect.height() > maxArea)) + rect.setHeight(2 * border + 1); + + QString pixmapName = uniqueName(QLS(part), state, shadow, + rect.size(), gtkWidget) % pmKey; + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_box (style, + pixmap, + state, + shadow, + NULL, + gtkWidget, + part, + 0, 0, + rect.width(), + rect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + if (rect.size() != paintRect.size()) { + // We assume we can stretch the middle tab part + // Note: the side effect of this is that pinstripe patterns will get fuzzy + const QSize size = cache.size(); + // top part + m_painter->drawPixmap(QRect(paintRect.left(), paintRect.top(), + paintRect.width(), border), cache, + QRect(0, 0, size.width(), border)); + + // tiled center part + QPixmap tilePart(cache.width(), 1); + QPainter scanLinePainter(&tilePart); + scanLinePainter.drawPixmap(QRect(0, 0, tilePart.width(), tilePart.height()), cache, QRect(0, border, size.width(), 1)); + scanLinePainter.end(); + m_painter->drawTiledPixmap(QRect(paintRect.left(), paintRect.top() + border, + paintRect.width(), paintRect.height() - 2*border), tilePart); + + // bottom part + m_painter->drawPixmap(QRect(paintRect.left(), paintRect.top() + paintRect.height() - border, + paintRect.width(), border), cache, + QRect(0, size.height() - border, size.width(), border)); + } else + m_painter->drawPixmap(paintRect.topLeft(), cache); +} + +void QGtkPainter::paintHline(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkStyle *style, int x1, int x2, int y, + const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, GTK_SHADOW_NONE, rect.size(), gtkWidget) + % HexString<int>(x1) + % HexString<int>(x2) + % HexString<int>(y) + % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_hline (style, + pixmap, + state, + NULL, + gtkWidget, + part, + x1, x2, y)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintVline(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkStyle *style, int y1, int y2, int x, + const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, GTK_SHADOW_NONE, rect.size(), gtkWidget) + % HexString<int>(y1) + % HexString<int>(y2) + % HexString<int>(x) + % pmKey; + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_vline (style, + pixmap, + state, + NULL, + gtkWidget, + part, + y1, y2, + x)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintExpander(GtkWidget *gtkWidget, + const gchar* part, const QRect &rect, + GtkStateType state, GtkExpanderStyle expander_state, + GtkStyle *style, const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, GTK_SHADOW_NONE, rect.size(), gtkWidget) + % HexString<uchar>(expander_state) + % pmKey; + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_expander (style, pixmap, + state, NULL, + gtkWidget, part, + rect.width()/2, + rect.height()/2, + expander_state)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintFocus(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkStyle *style, const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, GTK_SHADOW_NONE, rect.size(), gtkWidget) % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_focus (style, pixmap, state, NULL, + gtkWidget, + part, + 0, 0, + rect.width(), + rect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintResizeGrip(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkShadowType shadow, GdkWindowEdge edge, + GtkStyle *style, const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size(), gtkWidget) % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_resize_grip (style, pixmap, state, + NULL, gtkWidget, + part, edge, 0, 0, + rect.width(), + rect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintArrow(GtkWidget *gtkWidget, const gchar* part, + const QRect &arrowrect, GtkArrowType arrow_type, + GtkStateType state, GtkShadowType shadow, + gboolean fill, GtkStyle *style, const QString &pmKey) +{ + QRect rect = m_cliprect.isValid() ? m_cliprect : arrowrect; + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size()) + % HexString<uchar>(arrow_type) + % pmKey; + + GdkRectangle gtkCliprect = {0, 0, rect.width(), rect.height()}; + int xOffset = m_cliprect.isValid() ? arrowrect.x() - m_cliprect.x() : 0; + int yOffset = m_cliprect.isValid() ? arrowrect.y() - m_cliprect.y() : 0; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_arrow (style, pixmap, state, shadow, + >kCliprect, + gtkWidget, + part, + arrow_type, fill, + xOffset, yOffset, + arrowrect.width(), + arrowrect.height())) + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintHandle(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, + GtkStateType state, GtkShadowType shadow, + GtkOrientation orientation, GtkStyle *style) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size()) + % HexString<uchar>(orientation); + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_handle (style, + pixmap, + state, + shadow, + NULL, + gtkWidget, + part, 0, 0, + rect.width(), + rect.height(), + orientation)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintSlider(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, + GtkStateType state, GtkShadowType shadow, + GtkStyle *style, GtkOrientation orientation, + const QString &pmKey) +{ + if (!rect.isValid()) + return; + + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size(), gtkWidget) % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_slider (style, + pixmap, + state, + shadow, + NULL, + gtkWidget, + part, + 0, 0, + rect.width(), + rect.height(), + orientation)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + m_painter->drawPixmap(rect.topLeft(), cache); +} + + +void QGtkPainter::paintShadow(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkShadowType shadow, GtkStyle *style, + const QString &pmKey) + +{ + if (!rect.isValid()) + return; + + QRect r = rect; + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size()) % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_shadow(style, pixmap, state, shadow, NULL, + gtkWidget, part, 0, 0, rect.width(), rect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintFlatBox(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, + GtkShadowType shadow, GtkStyle *style, + const QString &pmKey) +{ + if (!rect.isValid()) + return; + QRect r = rect; + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size()) % pmKey; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_flat_box (style, + pixmap, + state, + shadow, + NULL, + gtkWidget, + part, 0, 0, + rect.width(), + rect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintExtention(GtkWidget *gtkWidget, + const gchar *part, const QRect &rect, + GtkStateType state, GtkShadowType shadow, + GtkPositionType gap_pos, GtkStyle *style) +{ + if (!rect.isValid()) + return; + + QRect r = rect; + QPixmap cache; + QString pixmapName = uniqueName(QLS(part), state, shadow, rect.size(), gtkWidget) + % HexString<uchar>(gap_pos); + + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_extension (style, pixmap, state, shadow, + NULL, gtkWidget, + (gchar*)part, 0, 0, + rect.width(), + rect.height(), + gap_pos)); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintOption(GtkWidget *gtkWidget, const QRect &radiorect, + GtkStateType state, GtkShadowType shadow, + GtkStyle *style, const QString &detail) + +{ + QRect rect = m_cliprect.isValid() ? m_cliprect : radiorect; + if (!rect.isValid()) + return; + + QRect r = rect; + QPixmap cache; + QString pixmapName = uniqueName(detail, state, shadow, rect.size()); + GdkRectangle gtkCliprect = {0, 0, rect.width(), rect.height()}; + int xOffset = m_cliprect.isValid() ? radiorect.x() - m_cliprect.x() : 0; + int yOffset = m_cliprect.isValid() ? radiorect.y() - m_cliprect.y() : 0; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_option(style, pixmap, + state, shadow, + >kCliprect, + gtkWidget, + detail.toLatin1(), + xOffset, yOffset, + radiorect.width(), + radiorect.height())); + + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + +void QGtkPainter::paintCheckbox(GtkWidget *gtkWidget, const QRect &checkrect, + GtkStateType state, GtkShadowType shadow, + GtkStyle *style, const QString &detail) + +{ + QRect rect = m_cliprect.isValid() ? m_cliprect : checkrect; + if (!rect.isValid()) + return; + + QRect r = rect; + QPixmap cache; + QString pixmapName = uniqueName(detail, state, shadow, rect.size()); + GdkRectangle gtkCliprect = {0, 0, rect.width(), rect.height()}; + int xOffset = m_cliprect.isValid() ? checkrect.x() - m_cliprect.x() : 0; + int yOffset = m_cliprect.isValid() ? checkrect.y() - m_cliprect.y() : 0; + if (!m_usePixmapCache || !QPixmapCache::find(pixmapName, cache)) { + DRAW_TO_CACHE(QGtkStylePrivate::gtk_paint_check (style, + pixmap, + state, + shadow, + >kCliprect, + gtkWidget, + detail.toLatin1(), + xOffset, yOffset, + checkrect.width(), + checkrect.height())); + if (m_usePixmapCache) + QPixmapCache::insert(pixmapName, cache); + } + + m_painter->drawPixmap(rect.topLeft(), cache); +} + +QT_END_NAMESPACE + +#endif //!defined(QT_NO_STYLE_GTK) diff --git a/src/gui/styles/qgtkpainter_p.h b/src/gui/styles/qgtkpainter_p.h new file mode 100644 index 0000000000..1c253798a8 --- /dev/null +++ b/src/gui/styles/qgtkpainter_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGTKPAINTER_H +#define QGTKPAINTER_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/qglobal.h> +#if !defined(QT_NO_STYLE_GTK) + +#include <QtGui/QCleanlooksStyle> +#include <QtGui/QPainter> +#include <QtGui/QPalette> +#include <QtGui/QFont> +#include <private/qgtkstyle_p.h> + +QT_BEGIN_NAMESPACE + +class QGtkPainter +{ + +public: + QGtkPainter(QPainter *painter); + GtkStyle *getStyle(GtkWidget *gtkWidget); + GtkStateType gtkState(const QStyleOption *option); + + void setAlphaSupport(bool value) { m_alpha = value; } + void setClipRect(const QRect &rect) { m_cliprect = rect; } + void setFlipHorizontal(bool value) { m_hflipped = value; } + void setFlipVertical(bool value) { m_vflipped = value; } + void setUsePixmapCache(bool value) { m_usePixmapCache = value; } + + void paintBoxGap(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, + GtkStateType state, GtkShadowType shadow, GtkPositionType gap_side, gint x, + gint width, GtkStyle *style); + void paintBox(GtkWidget *gtkWidget, const gchar* part, + const QRect &rect, GtkStateType state, GtkShadowType shadow, GtkStyle *style, + const QString &pmKey = QString()); + void paintHline(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkStyle *style, + int x1, int x2, int y, const QString &pmKey = QString()); + void paintVline(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkStyle *style, + int y1, int y2, int x, const QString &pmKey = QString()); + void paintExpander(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, + GtkExpanderStyle expander_state, GtkStyle *style, const QString &pmKey = QString()); + void paintFocus(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkStyle *style, + const QString &pmKey = QString()); + void paintResizeGrip(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkShadowType shadow, + GdkWindowEdge edge, GtkStyle *style, const QString &pmKey = QString()); + void paintArrow(GtkWidget *gtkWidget, const gchar* part, const QRect &arrowrect, GtkArrowType arrow_type, GtkStateType state, GtkShadowType shadow, + gboolean fill, GtkStyle *style, const QString &pmKey = QString()); + void paintHandle(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, + GtkStateType state, GtkShadowType shadow, GtkOrientation orientation, GtkStyle *style); + void paintSlider(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkShadowType shadow, + GtkStyle *style, GtkOrientation orientation, const QString &pmKey = QString()); + void paintShadow(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkShadowType shadow, + GtkStyle *style, const QString &pmKey = QString()); + void paintFlatBox(GtkWidget *gtkWidget, const gchar* part, const QRect &rect, GtkStateType state, GtkShadowType shadow, GtkStyle *style, const QString & = QString()); + void paintExtention(GtkWidget *gtkWidget, const gchar *part, const QRect &rect, GtkStateType state, GtkShadowType shadow, + GtkPositionType gap_pos, GtkStyle *style); + void paintOption(GtkWidget *gtkWidget, const QRect &rect, GtkStateType state, GtkShadowType shadow, GtkStyle *style, const QString &detail); + void paintCheckbox(GtkWidget *gtkWidget, const QRect &rect, GtkStateType state, GtkShadowType shadow, GtkStyle *style, const QString &detail); + + static QPixmap getIcon(const char* iconName, GtkIconSize size = GTK_ICON_SIZE_BUTTON); +private: + QPixmap renderTheme(uchar *bdata, uchar *wdata, const QRect&); + + GtkWidget *m_window; + QPainter *m_painter; + bool m_alpha; + bool m_hflipped; + bool m_vflipped; + bool m_usePixmapCache; + QRect m_cliprect; + +}; + +QT_END_NAMESPACE + +#endif //!defined(QT_NO_STYLE_QGTK) + +#endif // QGTKPAINTER_H diff --git a/src/gui/styles/qgtkstyle.cpp b/src/gui/styles/qgtkstyle.cpp new file mode 100644 index 0000000000..277e3025b5 --- /dev/null +++ b/src/gui/styles/qgtkstyle.cpp @@ -0,0 +1,3563 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qgtkstyle.h" + +#if !defined(QT_NO_STYLE_GTK) + +#include <private/qapplication_p.h> +#include <QtCore/QLibrary> +#include <QtCore/QSettings> +#include <QtGui/QDialogButtonBox> +#include <QtGui/QStatusBar> +#include <QtGui/QLineEdit> +#include <QtGui/QWidget> +#include <QtGui/QListView> +#include <QtGui/QApplication> +#include <QtGui/QStyleOption> +#include <QtGui/QPushButton> +#include <QtGui/QPainter> +#include <QtGui/QMainWindow> +#include <QtGui/QToolBar> +#include <QtGui/QHeaderView> +#include <QtGui/QMenuBar> +#include <QtGui/QComboBox> +#include <QtGui/QSpinBox> +#include <QtGui/QScrollBar> +#include <QtGui/QAbstractButton> +#include <QtGui/QToolButton> +#include <QtGui/QGroupBox> +#include <QtGui/QRadioButton> +#include <QtGui/QCheckBox> +#include <QtGui/QTreeView> +#include <QtGui/QStyledItemDelegate> +#include <qpixmapcache.h> +#undef signals // Collides with GTK stymbols +#include <private/qgtkpainter_p.h> +#include <private/qstylehelper_p.h> +#include <private/qgtkstyle_p.h> +#include <private/qcleanlooksstyle_p.h> + + +QT_BEGIN_NAMESPACE + +static const char * const dock_widget_close_xpm[] = + { + "11 13 5 1", + " c None", + ". c #D5CFCB", + "+ c #6C6A67", + "@ c #6C6A67", + "$ c #B5B0AC", + " ", + " @@@@@@@@@ ", + "@+ +@", + "@ +@ @+ @", + "@ @@@ @@@ @", + "@ @@@@@ @", + "@ @@@ @", + "@ @@@@@ @", + "@ @@@ @@@ @", + "@ +@ @+ @", + "@+ +@", + " @@@@@@@@@ ", + " " + }; + +static const char * const dock_widget_restore_xpm[] = + { + "11 13 5 1", + " c None", + ". c #D5CFCB", + "+ c #6C6A67", + "@ c #6C6A67", + "# c #6C6A67", + " ", + " @@@@@@@@@ ", + "@+ +@", + "@ #@@@# @", + "@ @ @ @", + "@ #@@@# @ @", + "@ @ @ @ @", + "@ @ @@@ @", + "@ @ @ @", + "@ #@@@@ @", + "@+ +@", + " @@@@@@@@@ ", + " " + }; + +static const int groupBoxBottomMargin = 2; // space below the groupbox +static const int groupBoxTitleMargin = 6; // space between contents and title +static const int groupBoxTopMargin = 2; + +/*! + Returns the configuration string for \a value. + Returns \a fallback if \a value is not found. + */ +QString QGtkStyle::getGConfString(const QString &value, const QString &fallback) +{ + return QGtkStylePrivate::getGConfString(value, fallback); +} + +/*! + Returns the configuration boolean for \a key. + Returns \a fallback if \a key is not found. + */ +bool QGtkStyle::getGConfBool(const QString &key, bool fallback) +{ + return QGtkStylePrivate::getGConfBool(key, fallback); +} + +static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50) +{ + const int maxFactor = 100; + QColor tmp = colorA; + tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor); + tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor); + tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor); + return tmp; +} + +static GdkColor fromQColor(const QColor &color) +{ + GdkColor retval; + retval.red = color.red() * 255; + retval.green = color.green() * 255; + retval.blue = color.blue() * 255; + return retval; +} + +/*! + \class QGtkStyle + \brief The QGtkStyle class provides a widget style rendered by GTK+ + \since 4.5 + + The QGtkStyle style provides a look and feel that integrates well + into GTK-based desktop environments such as the XFCe and GNOME. + + It does this by making use of the GTK+ theme engine, ensuring + that Qt applications look and feel native on these platforms. + + Note: The style requires GTK+ version 2.10 or later. + The Qt3-based "Qt" GTK+ theme engine will not work with QGtkStyle. + + \sa {Cleanlooks Style Widget Gallery}, QWindowsXPStyle, QMacStyle, QWindowsStyle, + QCDEStyle, QMotifStyle, QPlastiqueStyle, QCleanlooksStyle +*/ + +/*! + Constructs a QGtkStyle object. +*/ +QGtkStyle::QGtkStyle() + : QCleanlooksStyle(*new QGtkStylePrivate) +{ + Q_D(QGtkStyle); + d->init(); +} + +/*! + \internal + + Constructs a QGtkStyle object. +*/ +QGtkStyle::QGtkStyle(QGtkStylePrivate &dd) + : QCleanlooksStyle(dd) +{ + Q_D(QGtkStyle); + d->init(); +} + + +/*! + Destroys the QGtkStyle object. +*/ +QGtkStyle::~QGtkStyle() +{ +} + +/*! + \reimp +*/ +QPalette QGtkStyle::standardPalette() const +{ + Q_D(const QGtkStyle); + + QPalette palette = QCleanlooksStyle::standardPalette(); + if (d->isThemeAvailable()) { + GtkStyle *style = d->gtkStyle(); + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + GtkWidget *gtkEntry = d->getTextColorWidget(); + GdkColor gdkBg, gdkBase, gdkText, gdkForeground, gdkSbg, gdkSfg, gdkaSbg, gdkaSfg; + QColor bg, base, text, fg, highlight, highlightText, inactiveHighlight, inactiveHighlightedTExt; + gdkBg = style->bg[GTK_STATE_NORMAL]; + gdkForeground = gtkButton->style->fg[GTK_STATE_NORMAL]; + + // Our base and selected color is primarily used for text + // so we assume a gtkEntry will have the most correct value + gdkBase = gtkEntry->style->base[GTK_STATE_NORMAL]; + gdkText = gtkEntry->style->text[GTK_STATE_NORMAL]; + gdkSbg = gtkEntry->style->base[GTK_STATE_SELECTED]; + gdkSfg = gtkEntry->style->text[GTK_STATE_SELECTED]; + + // The ACTIVE base color is really used for inactive windows + gdkaSbg = gtkEntry->style->base[GTK_STATE_ACTIVE]; + gdkaSfg = gtkEntry->style->text[GTK_STATE_ACTIVE]; + + bg = QColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8); + text = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + fg = QColor(gdkForeground.red>>8, gdkForeground.green>>8, gdkForeground.blue>>8); + base = QColor(gdkBase.red>>8, gdkBase.green>>8, gdkBase.blue>>8); + highlight = QColor(gdkSbg.red>>8, gdkSbg.green>>8, gdkSbg.blue>>8); + highlightText = QColor(gdkSfg.red>>8, gdkSfg.green>>8, gdkSfg.blue>>8); + inactiveHighlight = QColor(gdkaSbg.red>>8, gdkaSbg.green>>8, gdkaSbg.blue>>8); + inactiveHighlightedTExt = QColor(gdkaSfg.red>>8, gdkaSfg.green>>8, gdkaSfg.blue>>8); + + palette.setColor(QPalette::HighlightedText, highlightText); + + + palette.setColor(QPalette::Light, bg.lighter(125)); + palette.setColor(QPalette::Shadow, bg.darker(130)); + palette.setColor(QPalette::Dark, bg.darker(120)); + palette.setColor(QPalette::Text, text); + palette.setColor(QPalette::WindowText, fg); + palette.setColor(QPalette::ButtonText, fg); + palette.setColor(QPalette::Base, base); + + QColor alternateRowColor = palette.base().color().lighter(93); // ref gtkstyle.c draw_flat_box + GtkWidget *gtkTreeView = d->gtkWidget("GtkTreeView"); + GdkColor *gtkAltBase = NULL; + d->gtk_widget_style_get(gtkTreeView, "odd-row-color", >kAltBase, NULL); + if (gtkAltBase) { + alternateRowColor = QColor(gtkAltBase->red>>8, gtkAltBase->green>>8, gtkAltBase->blue>>8); + d->gdk_color_free(gtkAltBase); + } + palette.setColor(QPalette::AlternateBase, alternateRowColor); + + palette.setColor(QPalette::Window, bg); + palette.setColor(QPalette::Button, bg); + palette.setColor(QPalette::Background, bg); + QColor disabled((fg.red() + bg.red()) / 2, + (fg.green() + bg.green())/ 2, + (fg.blue() + bg.blue()) / 2); + palette.setColor(QPalette::Disabled, QPalette::Text, disabled); + palette.setColor(QPalette::Disabled, QPalette::WindowText, disabled); + palette.setColor(QPalette::Disabled, QPalette::Foreground, disabled); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, disabled); + palette.setColor(QPalette::Highlight, highlight); + // calculate disabled colors by removing saturation + highlight.setHsv(highlight.hue(), 0, highlight.value(), highlight.alpha()); + highlightText.setHsv(highlightText.hue(), 0, highlightText.value(), highlightText.alpha()); + palette.setColor(QPalette::Disabled, QPalette::Highlight, highlight); + palette.setColor(QPalette::Disabled, QPalette::HighlightedText, highlightText); + + palette.setColor(QPalette::Inactive, QPalette::HighlightedText, inactiveHighlightedTExt); + palette.setColor(QPalette::Inactive, QPalette::Highlight, inactiveHighlight); + + style = d->gtk_rc_get_style_by_paths(d->gtk_settings_get_default(), "gtk-tooltips", "GtkWindow", + d->gtk_window_get_type()); + if (style) { + gdkText = style->fg[GTK_STATE_NORMAL]; + text = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + palette.setColor(QPalette::ToolTipText, text); + } + } + return palette; +} + +/*! + \reimp +*/ +void QGtkStyle::polish(QPalette &palette) +{ + Q_D(QGtkStyle); + + // QCleanlooksStyle will alter the palette, hence we do + // not want to polish the palette unless we are using it as + // the fallback + if (!d->isThemeAvailable()) + QCleanlooksStyle::polish(palette); + else + palette = palette.resolve(standardPalette()); +} + +/*! + \reimp +*/ +void QGtkStyle::polish(QApplication *app) +{ + Q_D(QGtkStyle); + + QCleanlooksStyle::polish(app); + // Custom fonts and palettes with QtConfig are intentionally + // not supported as these should be entirely determined by + // current Gtk settings + if (app->desktopSettingsAware() && d->isThemeAvailable()) { + QApplicationPrivate::setSystemPalette(standardPalette()); + QApplicationPrivate::setSystemFont(d->getThemeFont()); + d->applyCustomPaletteHash(); + if (!d->isKDE4Session()) { + qt_filedialog_open_filename_hook = &QGtkStylePrivate::openFilename; + qt_filedialog_save_filename_hook = &QGtkStylePrivate::saveFilename; + qt_filedialog_open_filenames_hook = &QGtkStylePrivate::openFilenames; + qt_filedialog_existing_directory_hook = &QGtkStylePrivate::openDirectory; + qApp->installEventFilter(&d->filter); + } + } +} + +/*! + \reimp +*/ +void QGtkStyle::unpolish(QApplication *app) +{ + Q_D(QGtkStyle); + + QCleanlooksStyle::unpolish(app); + QPixmapCache::clear(); + + if (app->desktopSettingsAware() && d->isThemeAvailable() + && !d->isKDE4Session()) { + qt_filedialog_open_filename_hook = 0; + qt_filedialog_save_filename_hook = 0; + qt_filedialog_open_filenames_hook = 0; + qt_filedialog_existing_directory_hook = 0; + qApp->removeEventFilter(&d->filter); + } +} + +/*! + \reimp +*/ + +void QGtkStyle::polish(QWidget *widget) +{ + Q_D(QGtkStyle); + + QCleanlooksStyle::polish(widget); + if (!d->isThemeAvailable()) + return; + if (qobject_cast<QAbstractButton*>(widget) + || qobject_cast<QToolButton*>(widget) + || qobject_cast<QComboBox*>(widget) + || qobject_cast<QGroupBox*>(widget) + || qobject_cast<QScrollBar*>(widget) + || qobject_cast<QSlider*>(widget) + || qobject_cast<QAbstractSpinBox*>(widget) + || qobject_cast<QSpinBox*>(widget) + || qobject_cast<QHeaderView*>(widget)) + widget->setAttribute(Qt::WA_Hover); + else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) + tree->viewport()->setAttribute(Qt::WA_Hover); +} + +/*! + \reimp +*/ +void QGtkStyle::unpolish(QWidget *widget) +{ + QCleanlooksStyle::unpolish(widget); +} + +/*! + \reimp +*/ +int QGtkStyle::pixelMetric(PixelMetric metric, + const QStyleOption *option, + const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) + return QCleanlooksStyle::pixelMetric(metric, option, widget); + + switch (metric) { + case PM_DefaultFrameWidth: + if (qobject_cast<const QFrame*>(widget)) { + if (GtkStyle *style = + d->gtk_rc_get_style_by_paths(d->gtk_settings_get_default(), + "*.GtkScrolledWindow", + "*.GtkScrolledWindow", + d->gtk_window_get_type())) + return qMax(style->xthickness, style->ythickness); + } + return 2; + + case PM_MenuButtonIndicator: + return 20; + + case PM_TabBarBaseOverlap: + return 1; + + case PM_ToolBarSeparatorExtent: + return 11; + + case PM_ToolBarFrameWidth: + return 1; + + case PM_ToolBarItemSpacing: + return 0; + + case PM_ButtonShiftHorizontal: { + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + guint horizontal_shift; + d->gtk_widget_style_get(gtkButton, "child-displacement-x", &horizontal_shift, NULL); + return horizontal_shift; + } + + case PM_ButtonShiftVertical: { + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + guint vertical_shift; + d->gtk_widget_style_get(gtkButton, "child-displacement-y", &vertical_shift, NULL); + return vertical_shift; + } + + case PM_MenuBarPanelWidth: + return 0; + + case PM_MenuPanelWidth: { + GtkWidget *gtkMenu = d->gtkWidget("GtkMenu"); + guint horizontal_padding = 0; + // horizontal-padding is used by Maemo to get thicker borders + if (!d->gtk_check_version(2, 10, 0)) + d->gtk_widget_style_get(gtkMenu, "horizontal-padding", &horizontal_padding, NULL); + int padding = qMax<int>(gtkMenu->style->xthickness, horizontal_padding); + return padding; + } + + case PM_ButtonIconSize: { + int retVal = 24; + GtkSettings *settings = d->gtk_settings_get_default(); + gchararray icon_sizes; + g_object_get(settings, "gtk-icon-sizes", &icon_sizes, NULL); + QStringList values = QString(QLS(icon_sizes)).split(QLatin1Char(':')); + g_free(icon_sizes); + QChar splitChar(QLatin1Char(',')); + foreach (const QString &value, values) { + if (value.startsWith(QLS("gtk-button="))) { + QString iconSize = value.right(value.size() - 11); + + if (iconSize.contains(splitChar)) + retVal = iconSize.split(splitChar)[0].toInt(); + break; + } + } + return retVal; + } + + case PM_MenuVMargin: + + case PM_MenuHMargin: + return 0; + + case PM_DockWidgetTitleMargin: + return 0; + + case PM_DockWidgetTitleBarButtonMargin: + return 5; + + case PM_TabBarTabVSpace: + return 12; + + case PM_TabBarTabHSpace: + return 14; + + case PM_TabBarTabShiftVertical: + return 2; + + case PM_ToolBarHandleExtent: + return 9; + + case PM_SplitterWidth: + return 6; + + case PM_SliderThickness: + case PM_SliderControlThickness: { + GtkWidget *gtkScale = d->gtkWidget("GtkHScale"); + gint val; + d->gtk_widget_style_get(gtkScale, "slider-width", &val, NULL); + if (metric == PM_SliderControlThickness) + return val + 2*gtkScale->style->ythickness; + return val; + } + + case PM_ScrollBarExtent: { + gint sliderLength; + gint trough_border; + GtkWidget *hScrollbar = d->gtkWidget("GtkHScrollbar"); + d->gtk_widget_style_get(hScrollbar, + "trough-border", &trough_border, + "slider-width", &sliderLength, + NULL); + return sliderLength + trough_border*2; + } + + case PM_ScrollBarSliderMin: + return 34; + + case PM_SliderLength: + gint val; + d->gtk_widget_style_get(d->gtkWidget("GtkHScale"), "slider-length", &val, NULL); + return val; + + case PM_ExclusiveIndicatorWidth: + case PM_ExclusiveIndicatorHeight: + case PM_IndicatorWidth: + case PM_IndicatorHeight: { + GtkWidget *gtkCheckButton = d->gtkWidget("GtkCheckButton"); + gint size, spacing; + d->gtk_widget_style_get(gtkCheckButton, "indicator-spacing", &spacing, "indicator-size", &size, NULL); + return size + 2 * spacing; + } + + case PM_MenuBarVMargin: { + GtkWidget *gtkMenubar = d->gtkWidget("GtkMenuBar"); + return qMax(0, gtkMenubar->style->ythickness); + } + case PM_ScrollView_ScrollBarSpacing: + { + gint spacing = 3; + GtkWidget *gtkScrollWindow = d->gtkWidget("GtkScrolledWindow"); + Q_ASSERT(gtkScrollWindow); + d->gtk_widget_style_get(gtkScrollWindow, "scrollbar-spacing", &spacing, NULL); + return spacing; + } + case PM_SubMenuOverlap: { + gint offset = 0; + GtkWidget *gtkMenu = d->gtkWidget("GtkMenu"); + d->gtk_widget_style_get(gtkMenu, "horizontal-offset", &offset, NULL); + return offset; + } + default: + return QCleanlooksStyle::pixelMetric(metric, option, widget); + } +} + +/*! + \reimp +*/ +int QGtkStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + + QStyleHintReturn *returnData = 0) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) + return QCleanlooksStyle::styleHint(hint, option, widget, returnData); + + switch (hint) { + + case SH_DialogButtonLayout: { + int ret = QDialogButtonBox::GnomeLayout; + gboolean alternateOrder = 0; + GtkSettings *settings = d->gtk_settings_get_default(); + g_object_get(settings, "gtk-alternative-button-order", &alternateOrder, NULL); + + if (alternateOrder) + ret = QDialogButtonBox::WinLayout; + + return ret; + } + + break; + + case SH_ToolButtonStyle: + { + if (d->isKDE4Session()) + return QCleanlooksStyle::styleHint(hint, option, widget, returnData); + GtkWidget *gtkToolbar = d->gtkWidget("GtkToolbar"); + GtkToolbarStyle toolbar_style = GTK_TOOLBAR_ICONS; + g_object_get(gtkToolbar, "toolbar-style", &toolbar_style, NULL); + switch (toolbar_style) { + case GTK_TOOLBAR_TEXT: + return Qt::ToolButtonTextOnly; + case GTK_TOOLBAR_BOTH: + return Qt::ToolButtonTextUnderIcon; + case GTK_TOOLBAR_BOTH_HORIZ: + return Qt::ToolButtonTextBesideIcon; + case GTK_TOOLBAR_ICONS: + default: + return Qt::ToolButtonIconOnly; + } + } + break; + case SH_SpinControls_DisableOnBounds: + return int(true); + + case SH_DitherDisabledText: + return int(false); + + case SH_ComboBox_Popup: { + GtkWidget *gtkComboBox = d->gtkWidget("GtkComboBox"); + gboolean appears_as_list; + d->gtk_widget_style_get((GtkWidget*)gtkComboBox, "appears-as-list", &appears_as_list, NULL); + return appears_as_list ? 0 : 1; + } + + case SH_MenuBar_AltKeyNavigation: + return int(false); + + case SH_EtchDisabledText: + return int(false); + + case SH_Menu_SubMenuPopupDelay: { + gint delay = 225; + GtkSettings *settings = d->gtk_settings_get_default(); + g_object_get(settings, "gtk-menu-popup-delay", &delay, NULL); + return delay; + } + + case SH_ScrollView_FrameOnlyAroundContents: { + gboolean scrollbars_within_bevel = false; + if (widget && widget->isWindow()) + scrollbars_within_bevel = true; + else if (!d->gtk_check_version(2, 12, 0)) { + GtkWidget *gtkScrollWindow = d->gtkWidget("GtkScrolledWindow"); + d->gtk_widget_style_get(gtkScrollWindow, "scrollbars-within-bevel", &scrollbars_within_bevel, NULL); + } + return !scrollbars_within_bevel; + } + + case SH_DialogButtonBox_ButtonsHaveIcons: { + static bool buttonsHaveIcons = d->getGConfBool(QLS("/desktop/gnome/interface/buttons_have_icons")); + return buttonsHaveIcons; + } + + case SH_UnderlineShortcut: { + gboolean underlineShortcut = true; + if (!d->gtk_check_version(2, 12, 0)) { + GtkSettings *settings = d->gtk_settings_get_default(); + g_object_get(settings, "gtk-enable-mnemonics", &underlineShortcut, NULL); + } + return underlineShortcut; + } + + default: + return QCleanlooksStyle::styleHint(hint, option, widget, returnData); + } +} + +/*! + \reimp +*/ +void QGtkStyle::drawPrimitive(PrimitiveElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) { + QCleanlooksStyle::drawPrimitive(element, option, painter, widget); + return; + } + + GtkStyle* style = d->gtkStyle(); + QGtkPainter gtkPainter(painter); + + switch (element) { + case PE_Frame: { + if (widget && widget->inherits("QComboBoxPrivateContainer")){ + QStyleOption copy = *option; + copy.state |= State_Raised; + proxy()->drawPrimitive(PE_PanelMenu, ©, painter, widget); + break; + } + // Drawing the entire itemview frame is very expensive, especially on the native X11 engine + // Instead we cheat a bit and draw a border image without the center part, hence only scaling + // thin rectangular images + const int pmSize = 64; + const int border = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + const QString pmKey = QLatin1Literal("windowframe") % HexString<uint>(option->state); + + QPixmap pixmap; + QRect pmRect(QPoint(0,0), QSize(pmSize, pmSize)); + + // Only draw through style once + if (!QPixmapCache::find(pmKey, pixmap)) { + pixmap = QPixmap(pmSize, pmSize); + pixmap.fill(Qt::transparent); + QPainter pmPainter(&pixmap); + QGtkPainter gtkFramePainter(&pmPainter); + gtkFramePainter.setUsePixmapCache(false); // Don't cache twice + + GtkShadowType shadow_type = GTK_SHADOW_NONE; + if (option->state & State_Sunken) + shadow_type = GTK_SHADOW_IN; + else if (option->state & State_Raised) + shadow_type = GTK_SHADOW_OUT; + + GtkStyle *style = d->gtk_rc_get_style_by_paths(d->gtk_settings_get_default(), + "*.GtkScrolledWindow", "*.GtkScrolledWindow", d->gtk_window_get_type()); + if (style) + gtkFramePainter.paintShadow(d->gtkWidget("GtkFrame"), "viewport", pmRect, + option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, + shadow_type, style); + QPixmapCache::insert(pmKey, pixmap); + } + + QRect rect = option->rect; + const int rw = rect.width() - border; + const int rh = rect.height() - border; + const int pw = pmRect.width() - border; + const int ph = pmRect.height() - border; + + // Sidelines + painter->drawPixmap(rect.adjusted(border, 0, -border, -rh), pixmap, pmRect.adjusted(border, 0, -border,-ph)); + painter->drawPixmap(rect.adjusted(border, rh, -border, 0), pixmap, pmRect.adjusted(border, ph,-border,0)); + painter->drawPixmap(rect.adjusted(0, border, -rw, -border), pixmap, pmRect.adjusted(0, border, -pw, -border)); + painter->drawPixmap(rect.adjusted(rw, border, 0, -border), pixmap, pmRect.adjusted(pw, border, 0, -border)); + + // Corners + painter->drawPixmap(rect.adjusted(0, 0, -rw, -rh), pixmap, pmRect.adjusted(0, 0, -pw,-ph)); + painter->drawPixmap(rect.adjusted(rw, 0, 0, -rh), pixmap, pmRect.adjusted(pw, 0, 0,-ph)); + painter->drawPixmap(rect.adjusted(0, rh, -rw, 0), pixmap, pmRect.adjusted(0, ph, -pw,0)); + painter->drawPixmap(rect.adjusted(rw, rh, 0, 0), pixmap, pmRect.adjusted(pw, ph, 0,0)); + } + break; + + case PE_PanelTipLabel: { + GtkWidget *gtkWindow = d->gtkWidget("GtkWindow"); // The Murrine Engine currently assumes a widget is passed + style = d->gtk_rc_get_style_by_paths(d->gtk_settings_get_default(), "gtk-tooltips", "GtkWindow", + d->gtk_window_get_type()); + gtkPainter.paintFlatBox(gtkWindow, "tooltip", option->rect, GTK_STATE_NORMAL, GTK_SHADOW_NONE, style); + } + break; + + case PE_PanelStatusBar: { + if (widget && widget->testAttribute(Qt::WA_SetPalette) && + option->palette.resolve() & (1 << QPalette::Window)) { + // Respect custom palette + painter->fillRect(option->rect, option->palette.window()); + break; + } + GtkShadowType shadow_type; + GtkWidget *gtkStatusbarFrame = d->gtkWidget("GtkStatusbar.GtkFrame"); + d->gtk_widget_style_get(gtkStatusbarFrame->parent, "shadow-type", &shadow_type, NULL); + gtkPainter.paintShadow(gtkStatusbarFrame, "frame", option->rect, GTK_STATE_NORMAL, + shadow_type, gtkStatusbarFrame->style); + } + break; + + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + GtkWidget *gtkTreeHeader = d->gtkWidget("GtkTreeView.GtkButton"); + GtkStateType state = gtkPainter.gtkState(option); + style = gtkTreeHeader->style; + GtkArrowType type = GTK_ARROW_UP; + QRect r = header->rect; + QImage arrow; + // This sorting indicator inversion is intentional, and follows the GNOME HIG. + // See http://library.gnome.org/devel/hig-book/stable/controls-lists.html.en#controls-lists-sortable + if (header->sortIndicator & QStyleOptionHeader::SortUp) + type = GTK_ARROW_UP; + else if (header->sortIndicator & QStyleOptionHeader::SortDown) + type = GTK_ARROW_DOWN; + + gtkPainter.paintArrow(gtkTreeHeader, "button", option->rect.adjusted(1, 1, -1, -1), type, state, + GTK_SHADOW_NONE, FALSE, style); + } + break; + + case PE_FrameFocusRect: + if (!widget || qobject_cast<const QAbstractItemView*>(widget)) + QCleanlooksStyle::drawPrimitive(element, option, painter, widget); + else { + // ### this mess should move to subcontrolrect + QRect frameRect = option->rect.adjusted(1, 1, -1, -2); + + if (qobject_cast<const QTabBar*>(widget)) { + GtkWidget *gtkNotebook = d->gtkWidget("GtkNotebook"); + style = gtkPainter.getStyle(gtkNotebook); + gtkPainter.paintFocus(gtkNotebook, "tab", frameRect.adjusted(-1, 1, 1, 1), GTK_STATE_ACTIVE, style); + } else { + gtkPainter.paintFocus(NULL, "tab", frameRect, GTK_STATE_ACTIVE, style); + } + } + break; + + case PE_IndicatorBranch: + if (option->state & State_Children) { + QRect rect = option->rect; + rect = QRect(0, 0, 12, 12); + rect.moveCenter(option->rect.center()); + rect.translate(2, 0); + GtkExpanderStyle openState = GTK_EXPANDER_EXPANDED; + GtkExpanderStyle closedState = GTK_EXPANDER_COLLAPSED; + GtkWidget *gtkTreeView = d->gtkWidget("GtkTreeView"); + + GtkStateType state = GTK_STATE_NORMAL; + if (!(option->state & State_Enabled)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & State_MouseOver) + state = GTK_STATE_PRELIGHT; + + gtkPainter.paintExpander(gtkTreeView, "treeview", rect, state, + option->state & State_Open ? openState : closedState , gtkTreeView->style); + } + break; + + case PE_PanelItemViewRow: + // This primitive is only used to draw selection behind selected expander arrows. + // We try not to decorate the tree branch background unless you inherit from StyledItemDelegate + // The reason for this is that a lot of code that relies on custom item delegates will look odd having + // a gradient on the branch but a flat shaded color on the item itself. + QCommonStyle::drawPrimitive(element, option, painter, widget); + if (!option->state & State_Selected) { + break; + } else { + if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(widget)) { + if (!qobject_cast<QStyledItemDelegate*>(view->itemDelegate())) + break; + } + } // fall through + + case PE_PanelItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + uint resolve_mask = vopt->palette.resolve(); + if (vopt->backgroundBrush.style() != Qt::NoBrush + || (resolve_mask & (1 << QPalette::Base))) + { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(vopt->rect.topLeft()); + painter->fillRect(vopt->rect, vopt->backgroundBrush); + painter->setBrushOrigin(oldBO); + if (!(option->state & State_Selected)) + break; + } + if (GtkWidget *gtkTreeView = d->gtkWidget("GtkTreeView")) { + const char *detail = "cell_even_ruled"; + if (vopt && vopt->features & QStyleOptionViewItemV2::Alternate) + detail = "cell_odd_ruled"; + bool isActive = option->state & State_Active; + QString key; + if (isActive ) { + // Required for active/non-active window appearance + key = QLS("a"); + GTK_WIDGET_SET_FLAGS(gtkTreeView, GTK_HAS_FOCUS); + } + bool isEnabled = (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)); + gtkPainter.paintFlatBox(gtkTreeView, detail, option->rect, + option->state & State_Selected ? GTK_STATE_SELECTED : + isEnabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, + GTK_SHADOW_OUT, gtkTreeView->style, key); + if (isActive ) + GTK_WIDGET_UNSET_FLAGS(gtkTreeView, GTK_HAS_FOCUS); + } + } + break; + case PE_IndicatorToolBarSeparator: + { + const int margin = 6; + GtkWidget *gtkSeparator = d->gtkWidget("GtkToolbar.GtkSeparatorToolItem"); + if (option->state & State_Horizontal) { + const int offset = option->rect.width()/2; + QRect rect = option->rect.adjusted(offset, margin, 0, -margin); + painter->setPen(QPen(option->palette.background().color().darker(110))); + gtkPainter.paintVline( gtkSeparator, "vseparator", + rect, GTK_STATE_NORMAL, gtkSeparator->style, + 0, rect.height(), 0); + } else { //Draw vertical separator + const int offset = option->rect.height()/2; + QRect rect = option->rect.adjusted(margin, offset, -margin, 0); + painter->setPen(QPen(option->palette.background().color().darker(110))); + gtkPainter.paintHline( gtkSeparator, "hseparator", + rect, GTK_STATE_NORMAL, gtkSeparator->style, + 0, rect.width(), 0); + } + } + break; + + case PE_IndicatorToolBarHandle: { + GtkWidget *gtkToolbar = d->gtkWidget("GtkToolbar"); + GtkShadowType shadow_type; + d->gtk_widget_style_get(gtkToolbar, "shadow-type", &shadow_type, NULL); + //Note when the toolbar is horizontal, the handle is vertical + painter->setClipRect(option->rect); + gtkPainter.paintHandle(gtkToolbar, "toolbar", option->rect.adjusted(-1, -1 ,0 ,1), + GTK_STATE_NORMAL, shadow_type, !(option->state & State_Horizontal) ? + GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, gtkToolbar->style); + } + break; + + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowLeft: + case PE_IndicatorArrowRight: { + + + GtkArrowType type = GTK_ARROW_UP; + + switch (element) { + + case PE_IndicatorArrowDown: + type = GTK_ARROW_DOWN; + break; + + case PE_IndicatorArrowLeft: + type = GTK_ARROW_LEFT; + break; + + case PE_IndicatorArrowRight: + type = GTK_ARROW_RIGHT; + break; + + default: + break; + } + int size = qMin(option->rect.height(), option->rect.width()); + int border = (size > 9) ? (size/4) : 0; //Allow small arrows to have exact dimensions + int bsx = 0, bsy = 0; + if (option->state & State_Sunken) { + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical); + } + QRect arrowRect = option->rect.adjusted(border + bsx, border + bsy, -border + bsx, -border + bsy); + GtkShadowType shadow = option->state & State_Sunken ? GTK_SHADOW_IN : GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + QColor arrowColor = option->palette.buttonText().color(); + GtkWidget *gtkArrow = d->gtkWidget("GtkArrow"); + GdkColor color = fromQColor(arrowColor); + d->gtk_widget_modify_fg (gtkArrow, state, &color); + gtkPainter.paintArrow(gtkArrow, "button", arrowRect, + type, state, shadow, FALSE, gtkArrow->style, + QString::number(arrowColor.rgba(), 16)); + // Passing NULL will revert the color change + d->gtk_widget_modify_fg (gtkArrow, state, NULL); + } + break; + + case PE_FrameGroupBox: + // Do nothing here, the GNOME groupboxes are flat + break; + + case PE_PanelMenu: { + GtkWidget *gtkMenu = d->gtkWidget("GtkMenu"); + gtkPainter.setAlphaSupport(false); // Note, alpha disabled for performance reasons + gtkPainter.paintBox(gtkMenu, "menu", option->rect, GTK_STATE_NORMAL, GTK_SHADOW_OUT, gtkMenu->style, QString()); + } + break; + + case PE_FrameMenu: + //This is actually done by PE_Widget due to a clipping issue + //Otherwise Menu items will not be able to span the entire menu width + + // This is only used by floating tool bars + if (qobject_cast<const QToolBar *>(widget)) { + GtkWidget *gtkMenubar = d->gtkWidget("GtkMenuBar"); + gtkPainter.paintBox( gtkMenubar, "toolbar", option->rect, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, style); + gtkPainter.paintBox( gtkMenubar, "menu", option->rect, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, style); + } + break; + + case PE_FrameLineEdit: { + GtkWidget *gtkEntry = d->gtkWidget("GtkEntry"); + + + gboolean interior_focus; + gint focus_line_width; + QRect rect = option->rect; + d->gtk_widget_style_get(gtkEntry, + "interior-focus", &interior_focus, + "focus-line-width", &focus_line_width, NULL); + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=405421 for info about this hack + g_object_set_data(G_OBJECT(gtkEntry), "transparent-bg-hint", GINT_TO_POINTER(TRUE)); + + if (!interior_focus && option->state & State_HasFocus) + rect.adjust(focus_line_width, focus_line_width, -focus_line_width, -focus_line_width); + + if (option->state & State_HasFocus) + GTK_WIDGET_SET_FLAGS(gtkEntry, GTK_HAS_FOCUS); + gtkPainter.paintShadow(gtkEntry, "entry", rect, option->state & State_Enabled ? + GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, gtkEntry->style, + option->state & State_HasFocus ? QLS("focus") : QString()); + if (!interior_focus && option->state & State_HasFocus) + gtkPainter.paintShadow(gtkEntry, "entry", option->rect, option->state & State_Enabled ? + GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, gtkEntry->style, QLS("GtkEntryShadowIn")); + + if (option->state & State_HasFocus) + GTK_WIDGET_UNSET_FLAGS(gtkEntry, GTK_HAS_FOCUS); + } + break; + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + GtkWidget *gtkEntry = d->gtkWidget("GtkEntry"); + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, option, painter, widget); + uint resolve_mask = option->palette.resolve(); + QRect textRect = option->rect.adjusted(gtkEntry->style->xthickness, gtkEntry->style->ythickness, + -gtkEntry->style->xthickness, -gtkEntry->style->ythickness); + + if (widget && widget->testAttribute(Qt::WA_SetPalette) && + resolve_mask & (1 << QPalette::Base)) // Palette overridden by user + painter->fillRect(textRect, option->palette.base()); + else + gtkPainter.paintFlatBox( gtkEntry, "entry_bg", textRect, + option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, GTK_SHADOW_NONE, gtkEntry->style); + } + break; + + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *frame = qstyleoption_cast<const QStyleOptionTabWidgetFrame*>(option)) { + GtkWidget *gtkNotebook = d->gtkWidget("GtkNotebook"); + style = gtkPainter.getStyle(gtkNotebook); + gtkPainter.setAlphaSupport(false); + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = GTK_STATE_NORMAL; // Only state supported by gtknotebook + bool reverse = (option->direction == Qt::RightToLeft); + QGtkStylePrivate::gtk_widget_set_direction(gtkNotebook, reverse ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + if (const QStyleOptionTabWidgetFrameV2 *tabframe = qstyleoption_cast<const QStyleOptionTabWidgetFrameV2*>(option)) { + GtkPositionType frameType = GTK_POS_TOP; + QTabBar::Shape shape = frame->shape; + int gapStart = 0; + int gapSize = 0; + if (shape == QTabBar::RoundedNorth || shape == QTabBar::RoundedSouth) { + frameType = (shape == QTabBar::RoundedNorth) ? GTK_POS_TOP : GTK_POS_BOTTOM; + gapStart = tabframe->selectedTabRect.left(); + gapSize = tabframe->selectedTabRect.width(); + } else { + frameType = (shape == QTabBar::RoundedWest) ? GTK_POS_LEFT : GTK_POS_RIGHT; + gapStart = tabframe->selectedTabRect.y(); + gapSize = tabframe->selectedTabRect.height(); + } + gtkPainter.paintBoxGap(gtkNotebook, "notebook", option->rect, state, shadow, frameType, + gapStart, gapSize, style); + break; // done + } + + // Note this is only the fallback option + gtkPainter.paintBox(gtkNotebook, "notebook", option->rect, state, shadow, style); + } + break; + + case PE_PanelButtonCommand: + case PE_PanelButtonTool: { + bool isDefault = false; + bool isTool = (element == PE_PanelButtonTool); + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton*>(option)) + isDefault = btn->features & QStyleOptionButton::DefaultButton; + + // don't draw a frame for tool buttons that have the autoRaise flag and are not enabled or on + if (isTool && !(option->state & State_Enabled || option->state & State_On) && (option->state & State_AutoRaise)) + break; + // don't draw a frame for dock widget buttons, unless we are hovering + if (widget && widget->inherits("QDockWidgetTitleButton") && !(option->state & State_MouseOver)) + break; + + GtkStateType state = gtkPainter.gtkState(option); + if (option->state & State_On || option->state & State_Sunken) + state = GTK_STATE_ACTIVE; + GtkWidget *gtkButton = isTool ? d->gtkWidget("GtkToolButton.GtkButton") : d->gtkWidget("GtkButton"); + gint focusWidth, focusPad; + gboolean interiorFocus = false; + d->gtk_widget_style_get (gtkButton, + "focus-line-width", &focusWidth, + "focus-padding", &focusPad, + "interior-focus", &interiorFocus, NULL); + + style = gtkButton->style; + + QRect buttonRect = option->rect; + + QString key; + if (isDefault) { + key += QLS("def"); + GTK_WIDGET_SET_FLAGS(gtkButton, GTK_HAS_DEFAULT); + gtkPainter.paintBox(gtkButton, "buttondefault", buttonRect, state, GTK_SHADOW_IN, + style, isDefault ? QLS("d") : QString()); + } + + bool hasFocus = option->state & State_HasFocus; + + if (hasFocus) { + key += QLS("def"); + GTK_WIDGET_SET_FLAGS(gtkButton, GTK_HAS_FOCUS); + } + + if (!interiorFocus) + buttonRect = buttonRect.adjusted(focusWidth, focusWidth, -focusWidth, -focusWidth); + + GtkShadowType shadow = (option->state & State_Sunken || option->state & State_On ) ? + GTK_SHADOW_IN : GTK_SHADOW_OUT; + + gtkPainter.paintBox(gtkButton, "button", buttonRect, state, shadow, + style, key); + if (isDefault) + GTK_WIDGET_UNSET_FLAGS(gtkButton, GTK_HAS_DEFAULT); + if (hasFocus) + GTK_WIDGET_UNSET_FLAGS(gtkButton, GTK_HAS_FOCUS); + } + break; + + case PE_IndicatorRadioButton: { + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + if (option->state & State_Sunken) + state = GTK_STATE_ACTIVE; + + if (option->state & State_NoChange) + shadow = GTK_SHADOW_ETCHED_IN; + else if (option->state & State_On) + shadow = GTK_SHADOW_IN; + else + shadow = GTK_SHADOW_OUT; + + GtkWidget *gtkRadioButton = d->gtkWidget("GtkRadioButton"); + gint spacing; + d->gtk_widget_style_get(gtkRadioButton, "indicator-spacing", &spacing, NULL); + QRect buttonRect = option->rect.adjusted(spacing, spacing, -spacing, -spacing); + gtkPainter.setClipRect(option->rect); + // ### Note: Ubuntulooks breaks when the proper widget is passed + // Murrine engine requires a widget not to get RGBA check - warnings + GtkWidget *gtkCheckButton = d->gtkWidget("GtkCheckButton"); + QString key(QLS("radiobutton")); + if (option->state & State_HasFocus) { // Themes such as Nodoka check this flag + key += QLatin1Char('f'); + GTK_WIDGET_SET_FLAGS(gtkCheckButton, GTK_HAS_FOCUS); + } + gtkPainter.paintOption(gtkCheckButton , buttonRect, state, shadow, gtkRadioButton->style, key); + if (option->state & State_HasFocus) + GTK_WIDGET_UNSET_FLAGS(gtkCheckButton, GTK_HAS_FOCUS); + } + break; + + case PE_IndicatorCheckBox: { + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + if (option->state & State_Sunken) + state = GTK_STATE_ACTIVE; + + if (option->state & State_NoChange) + shadow = GTK_SHADOW_ETCHED_IN; + else if (option->state & State_On) + shadow = GTK_SHADOW_IN; + else + shadow = GTK_SHADOW_OUT; + + int spacing; + + GtkWidget *gtkCheckButton = d->gtkWidget("GtkCheckButton"); + QString key(QLS("checkbutton")); + if (option->state & State_HasFocus) { // Themes such as Nodoka checks this flag + key += QLatin1Char('f'); + GTK_WIDGET_SET_FLAGS(gtkCheckButton, GTK_HAS_FOCUS); + } + + // Some styles such as aero-clone assume they can paint in the spacing area + gtkPainter.setClipRect(option->rect); + + d->gtk_widget_style_get(gtkCheckButton, "indicator-spacing", &spacing, NULL); + + QRect checkRect = option->rect.adjusted(spacing, spacing, -spacing, -spacing); + + gtkPainter.paintCheckbox(gtkCheckButton, checkRect, state, shadow, gtkCheckButton->style, + key); + if (option->state & State_HasFocus) + GTK_WIDGET_UNSET_FLAGS(gtkCheckButton, GTK_HAS_FOCUS); + + } + break; + +#ifndef QT_NO_TABBAR + + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb + = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) { + QRect tabRect = tbb->rect; + painter->save(); + painter->setPen(QPen(option->palette.dark().color().dark(110), 0)); + switch (tbb->shape) { + + case QTabBar::RoundedNorth: + painter->drawLine(tabRect.topLeft(), tabRect.topRight()); + break; + + case QTabBar::RoundedWest: + painter->drawLine(tabRect.left(), tabRect.top(), tabRect.left(), tabRect.bottom()); + break; + + case QTabBar::RoundedSouth: + painter->drawLine(tbb->rect.left(), tbb->rect.bottom(), + tabRect.right(), tabRect.bottom()); + break; + + case QTabBar::RoundedEast: + painter->drawLine(tabRect.topRight(), tabRect.bottomRight()); + 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; + +#endif // QT_NO_TABBAR + + case PE_Widget: + break; + + default: + QCleanlooksStyle::drawPrimitive(element, option, painter, widget); + } +} + +/*! + \reimp +*/ +void QGtkStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + + QPainter *painter, const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) { + QCleanlooksStyle::drawComplexControl(control, option, painter, widget); + return; + } + + GtkStyle* style = d->gtkStyle(); + QGtkPainter gtkPainter(painter); + QColor button = option->palette.button().color(); + QColor dark; + QColor grooveColor; + QColor darkOutline; + dark.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*1.9)), + qMin(255, (int)(button.value()*0.7))); + grooveColor.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*2.6)), + qMin(255, (int)(button.value()*0.9))); + darkOutline.setHsv(button.hue(), + qMin(255, (int)(button.saturation()*3.0)), + qMin(255, (int)(button.value()*0.6))); + + QColor alphaCornerColor; + + if (widget) + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), darkOutline); + else + alphaCornerColor = mergedColors(option->palette.background().color(), darkOutline); + + switch (control) { + + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + // Since this is drawn by metacity and not Gtk we + // have to rely on Cleanlooks for a fallback + QStyleOptionTitleBar copyOpt = *tb; + QPalette pal = copyOpt.palette; + // Bg color is closer to the window selection than + // the base selection color + GdkColor gdkBg = style->bg[GTK_STATE_SELECTED]; + QColor bgColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8); + pal.setBrush(QPalette::Active, QPalette::Highlight, bgColor); + copyOpt.palette = pal; + QCleanlooksStyle::drawComplexControl(control, ©Opt, painter, widget); + } + break; + +#ifndef QT_NO_GROUPBOX + + case CC_GroupBox: + painter->save(); + + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + QRect textRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxLabel, widget); + QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxCheckBox, widget); + // Draw title + + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + // Draw prelight background + GtkWidget *gtkCheckButton = d->gtkWidget("GtkCheckButton"); + + if (option->state & State_MouseOver) { + QRect bgRect = textRect | checkBoxRect; + gtkPainter.paintFlatBox(gtkCheckButton, "checkbutton", bgRect.adjusted(0, 0, 0, -2), + GTK_STATE_PRELIGHT, GTK_SHADOW_ETCHED_OUT, gtkCheckButton->style); + } + + if (!groupBox->text.isEmpty()) { + int alignment = int(groupBox->textAlignment); + if (!proxy()->styleHint(QStyle::SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + QColor textColor = groupBox->textColor; // Note: custom textColor is currently ignored + int labelState = GTK_STATE_INSENSITIVE; + + if (option->state & State_Enabled) + labelState = (option->state & State_MouseOver) ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + GdkColor gdkText = gtkCheckButton->style->fg[labelState]; + textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + painter->setPen(textColor); + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + painter->drawText(textRect, Qt::TextShowMnemonic | Qt::AlignLeft| alignment, groupBox->text); + + if (option->state & State_HasFocus) + gtkPainter.paintFocus( NULL, "tab", textRect.adjusted(-4, -1, 0, -3), GTK_STATE_ACTIVE, style); + } + } + + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + } + + painter->restore(); + break; +#endif // QT_NO_GROUPBOX + +#ifndef QT_NO_COMBOBOX + + case CC_ComboBox: + // See: http://live.gnome.org/GnomeArt/Tutorials/GtkThemes/GtkComboBox + // and http://live.gnome.org/GnomeArt/Tutorials/GtkThemes/GtkComboBoxEntry + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + bool sunken = comboBox->state & State_On; // play dead, if combobox has no items + BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("cb-%0-%1").arg(sunken).arg(comboBox->editable)); + QGtkPainter gtkCachedPainter(p); + gtkCachedPainter.setUsePixmapCache(false); // cached externally + + bool isEnabled = (comboBox->state & State_Enabled); + bool focus = isEnabled && (comboBox->state & State_HasFocus); + GtkStateType state = gtkPainter.gtkState(option); + int appears_as_list = !proxy()->styleHint(QStyle::SH_ComboBox_Popup, comboBox, widget); + QStyleOptionComboBox comboBoxCopy = *comboBox; + comboBoxCopy.rect = option->rect; + + bool reverse = (option->direction == Qt::RightToLeft); + QRect rect = option->rect; + QRect arrowButtonRect = proxy()->subControlRect(CC_ComboBox, &comboBoxCopy, + SC_ComboBoxArrow, widget); + + GtkShadowType shadow = (option->state & State_Sunken || option->state & State_On ) ? + GTK_SHADOW_IN : GTK_SHADOW_OUT; + const QHashableLatin1Literal comboBoxPath = comboBox->editable ? QHashableLatin1Literal("GtkComboBoxEntry") : QHashableLatin1Literal("GtkComboBox"); + + // We use the gtk widget to position arrows and separators for us + GtkWidget *gtkCombo = d->gtkWidget(comboBoxPath); + GtkAllocation geometry = {0, 0, option->rect.width(), option->rect.height()}; + d->gtk_widget_set_direction(gtkCombo, reverse ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + d->gtk_widget_size_allocate(gtkCombo, &geometry); + + QHashableLatin1Literal buttonPath = comboBox->editable ? QHashableLatin1Literal("GtkComboBoxEntry.GtkToggleButton") + : QHashableLatin1Literal("GtkComboBox.GtkToggleButton"); + GtkWidget *gtkToggleButton = d->gtkWidget(buttonPath); + d->gtk_widget_set_direction(gtkToggleButton, reverse ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + if (gtkToggleButton && (appears_as_list || comboBox->editable)) { + if (focus) + GTK_WIDGET_SET_FLAGS(gtkToggleButton, GTK_HAS_FOCUS); + // Draw the combo box as a line edit with a button next to it + if (comboBox->editable || appears_as_list) { + GtkStateType frameState = (state == GTK_STATE_PRELIGHT) ? GTK_STATE_NORMAL : state; + QHashableLatin1Literal entryPath = comboBox->editable ? QHashableLatin1Literal("GtkComboBoxEntry.GtkEntry") : QHashableLatin1Literal("GtkComboBox.GtkFrame"); + GtkWidget *gtkEntry = d->gtkWidget(entryPath); + d->gtk_widget_set_direction(gtkEntry, reverse ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + QRect frameRect = option->rect; + + if (reverse) + frameRect.setLeft(arrowButtonRect.right()); + else + frameRect.setRight(arrowButtonRect.left()); + + // Fill the line edit background + // We could have used flat_box with "entry_bg" but that is probably not worth the overhead + uint resolve_mask = option->palette.resolve(); + int xt = gtkEntry->style->xthickness; + int yt = gtkEntry->style->ythickness; + QRect contentRect = frameRect.adjusted(xt, yt, -xt, -yt); + // Required for inner blue highlight with clearlooks + if (focus) + GTK_WIDGET_SET_FLAGS(gtkEntry, GTK_HAS_FOCUS); + + if (widget && widget->testAttribute(Qt::WA_SetPalette) && + resolve_mask & (1 << QPalette::Base)) // Palette overridden by user + p->fillRect(contentRect, option->palette.base().color()); + else { + gtkCachedPainter.paintFlatBox(gtkEntry, "entry_bg", contentRect, + option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, + GTK_SHADOW_NONE, gtkEntry->style, entryPath.toString() + QString::number(focus)); + } + + gtkCachedPainter.paintShadow(gtkEntry, comboBox->editable ? "entry" : "frame", frameRect, frameState, + GTK_SHADOW_IN, gtkEntry->style, entryPath.toString() + + QString::number(focus) + QString::number(comboBox->editable) + + QString::number(option->direction)); + if (focus) + GTK_WIDGET_UNSET_FLAGS(gtkEntry, GTK_HAS_FOCUS); + } + + GtkStateType buttonState = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled)) + buttonState = GTK_STATE_INSENSITIVE; + else if (option->state & State_Sunken || option->state & State_On) + buttonState = GTK_STATE_ACTIVE; + else if (option->state & State_MouseOver && comboBox->activeSubControls & SC_ComboBoxArrow) + buttonState = GTK_STATE_PRELIGHT; + + Q_ASSERT(gtkToggleButton); + gtkCachedPainter.paintBox( gtkToggleButton, "button", arrowButtonRect, buttonState, + shadow, gtkToggleButton->style, buttonPath.toString() + + QString::number(focus) + QString::number(option->direction)); + if (focus) + GTK_WIDGET_UNSET_FLAGS(gtkToggleButton, GTK_HAS_FOCUS); + } else { + // Draw combo box as a button + QRect buttonRect = option->rect; + + if (focus) // Clearlooks actually check the widget for the default state + GTK_WIDGET_SET_FLAGS(gtkToggleButton, GTK_HAS_FOCUS); + gtkCachedPainter.paintBox(gtkToggleButton, "button", + buttonRect, state, + shadow, gtkToggleButton->style, + buttonPath.toString() + QString::number(focus)); + if (focus) + GTK_WIDGET_UNSET_FLAGS(gtkToggleButton, GTK_HAS_FOCUS); + + + // Draw the separator between label and arrows + QHashableLatin1Literal vSeparatorPath = comboBox->editable + ? QHashableLatin1Literal("GtkComboBoxEntry.GtkToggleButton.GtkHBox.GtkVSeparator") + : QHashableLatin1Literal("GtkComboBox.GtkToggleButton.GtkHBox.GtkVSeparator"); + + if (GtkWidget *gtkVSeparator = d->gtkWidget(vSeparatorPath)) { + QRect vLineRect(gtkVSeparator->allocation.x, + gtkVSeparator->allocation.y, + gtkVSeparator->allocation.width, + gtkVSeparator->allocation.height); + + gtkCachedPainter.paintVline( gtkVSeparator, "vseparator", + vLineRect, state, gtkVSeparator->style, + 0, vLineRect.height(), 0, vSeparatorPath.toString()); + + + gint interiorFocus = true; + d->gtk_widget_style_get(gtkToggleButton, "interior-focus", &interiorFocus, NULL); + int xt = interiorFocus ? gtkToggleButton->style->xthickness : 0; + int yt = interiorFocus ? gtkToggleButton->style->ythickness : 0; + if (focus && ((option->state & State_KeyboardFocusChange) || styleHint(SH_UnderlineShortcut, option, widget))) + gtkCachedPainter.paintFocus(gtkToggleButton, "button", + option->rect.adjusted(xt, yt, -xt, -yt), + option->state & State_Sunken ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL, + gtkToggleButton->style); + } + } + + if (comboBox->subControls & SC_ComboBoxArrow) { + if (!isEnabled) + state = GTK_STATE_INSENSITIVE; + else if (sunken) + state = GTK_STATE_ACTIVE; + else if (option->state & State_MouseOver) + state = GTK_STATE_PRELIGHT; + else + state = GTK_STATE_NORMAL; + + QHashableLatin1Literal arrowPath(""); + if (comboBox->editable) { + if (appears_as_list) + arrowPath = QHashableLatin1Literal("GtkComboBoxEntry.GtkToggleButton.GtkArrow"); + else + arrowPath = QHashableLatin1Literal("GtkComboBoxEntry.GtkToggleButton.GtkHBox.GtkArrow"); + } else { + if (appears_as_list) + arrowPath = QHashableLatin1Literal("GtkComboBox.GtkToggleButton.GtkArrow"); + else + arrowPath = QHashableLatin1Literal("GtkComboBox.GtkToggleButton.GtkHBox.GtkArrow"); + } + + GtkWidget *gtkArrow = d->gtkWidget(arrowPath); + gfloat scale = 0.7; + gint minSize = 15; + QRect arrowWidgetRect; + + if (gtkArrow && !d->gtk_check_version(2, 12, 0)) { + d->gtk_widget_style_get(gtkArrow, "arrow-scaling", &scale, NULL); + d->gtk_widget_style_get(gtkCombo, "arrow-size", &minSize, NULL); + } + if (gtkArrow) { + arrowWidgetRect = QRect(gtkArrow->allocation.x, gtkArrow->allocation.y, + gtkArrow->allocation.width, gtkArrow->allocation.height); + style = gtkArrow->style; + } + + // Note that for some reason the arrow-size is not properly respected with Hildon + // Hence we enforce the minimum "arrow-size" ourselves + int arrowSize = qMax(qMin(rect.height() - gtkCombo->style->ythickness * 2, minSize), + qMin(arrowWidgetRect.width(), arrowWidgetRect.height())); + QRect arrowRect(0, 0, static_cast<int>(arrowSize * scale), static_cast<int>(arrowSize * scale)); + + arrowRect.moveCenter(arrowWidgetRect.center()); + + if (sunken) { + int xoff, yoff; + const QHashableLatin1Literal toggleButtonPath = comboBox->editable + ? QHashableLatin1Literal("GtkComboBoxEntry.GtkToggleButton") + : QHashableLatin1Literal("GtkComboBox.GtkToggleButton"); + + GtkWidget *gtkButton = d->gtkWidget(toggleButtonPath); + d->gtk_widget_style_get(gtkButton, "child-displacement-x", &xoff, NULL); + d->gtk_widget_style_get(gtkButton, "child-displacement-y", &yoff, NULL); + arrowRect = arrowRect.adjusted(xoff, yoff, xoff, yoff); + } + + // Some styles such as Nimbus paint outside the arrowRect + // hence we have provide the whole widget as the cliprect + if (gtkArrow) { + gtkCachedPainter.setClipRect(option->rect); + gtkCachedPainter.paintArrow( gtkArrow, "arrow", arrowRect, + GTK_ARROW_DOWN, state, GTK_SHADOW_NONE, TRUE, + style, arrowPath.toString() + QString::number(option->direction)); + } + } + END_STYLE_PIXMAPCACHE; + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_TOOLBUTTON + + case CC_ToolButton: + if (const QStyleOptionToolButton *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_MouseOver); + + if (bflags & State_AutoRaise) + if (!(bflags & State_MouseOver)) + bflags &= ~State_Raised; + + State mflags = bflags; + + if (toolbutton->state & State_Sunken) { + if (toolbutton->activeSubControls & SC_ToolButton) + bflags |= State_Sunken; + if (toolbutton->activeSubControls & SC_ToolButtonMenu) + mflags |= State_Sunken; + } else if (toolbutton->state & State_MouseOver) { + if (toolbutton->activeSubControls & SC_ToolButton) + bflags |= State_MouseOver; + if (toolbutton->activeSubControls & SC_ToolButtonMenu) + mflags |= State_MouseOver; + } + + QStyleOption tool(0); + + tool.palette = toolbutton->palette; + + if (toolbutton->subControls & SC_ToolButton) { + if (bflags & (State_Sunken | State_On | State_Raised | State_MouseOver)) { + tool.rect = button; + tool.state = bflags; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget); + } + } + + bool drawMenuArrow = toolbutton->features & QStyleOptionToolButton::HasMenu && + !(toolbutton->features & QStyleOptionToolButton::MenuButtonPopup); + int popupArrowSize = drawMenuArrow ? 7 : 0; + + if (toolbutton->state & State_HasFocus) { + QStyleOptionFocusRect fr; + fr.QStyleOption::operator=(*toolbutton); + fr.rect = proxy()->subControlRect(CC_ToolButton, toolbutton, SC_ToolButton, widget); + fr.rect.adjust(1, 1, -1, -1); + proxy()->drawPrimitive(PE_FrameFocusRect, &fr, painter, widget); + } + + QStyleOptionToolButton label = *toolbutton; + label.state = bflags; + GtkWidget *gtkButton = d->gtkWidget("GtkToolButton.GtkButton"); + QPalette pal = toolbutton->palette; + if (option->state & State_Enabled && + option->state & State_MouseOver && !(widget && widget->testAttribute(Qt::WA_SetPalette))) { + GdkColor gdkText = gtkButton->style->fg[GTK_STATE_PRELIGHT]; + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + pal.setBrush(QPalette::All, QPalette::ButtonText, textColor); + label.palette = pal; + } + label.rect = button.adjusted(style->xthickness, style->ythickness, + -style->xthickness - popupArrowSize, -style->ythickness); + proxy()->drawControl(CE_ToolButtonLabel, &label, painter, widget); + + if (toolbutton->subControls & SC_ToolButtonMenu) { + tool.rect = menuarea; + tool.state = mflags; + if ((mflags & State_Enabled && (mflags & (State_Sunken | State_Raised | State_MouseOver))) || !(mflags & State_AutoRaise)) + proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, painter, widget); + + proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, painter, widget); + + } else if (drawMenuArrow) { + QRect ir = toolbutton->rect; + QStyleOptionToolButton newBtn = *toolbutton; + newBtn.rect = QRect(ir.right() - popupArrowSize - style->xthickness - 3, ir.height()/2 - 1, popupArrowSize, popupArrowSize); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget); + } + } + break; + +#endif // QT_NO_TOOLBUTTON +#ifndef QT_NO_SCROLLBAR + + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + GtkWidget *gtkHScrollBar = d->gtkWidget("GtkHScrollbar"); + GtkWidget *gtkVScrollBar = d->gtkWidget("GtkVScrollbar"); + + // Fill background in case the scrollbar is partially transparent + painter->fillRect(option->rect, option->palette.background()); + + QRect rect = scrollBar->rect; + QRect scrollBarSubLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSubLine, widget); + QRect scrollBarAddLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarAddLine, widget); + QRect scrollBarSlider = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSlider, widget); + QRect grooveRect = proxy()->subControlRect(control, scrollBar, SC_ScrollBarGroove, widget); + bool horizontal = scrollBar->orientation == Qt::Horizontal; + GtkWidget * scrollbarWidget = horizontal ? gtkHScrollBar : gtkVScrollBar; + style = scrollbarWidget->style; + gboolean trough_under_steppers = true; + gboolean trough_side_details = false; + gboolean activate_slider = false; + gboolean stepper_size = 14; + gint trough_border = 1; + if (!d->gtk_check_version(2, 10, 0)) { + d->gtk_widget_style_get((GtkWidget*)(scrollbarWidget), + "trough-border", &trough_border, + "trough-side-details", &trough_side_details, + "trough-under-steppers", &trough_under_steppers, + "activate-slider", &activate_slider, + "stepper-size", &stepper_size, NULL); + } + if (trough_under_steppers) { + scrollBarAddLine.adjust(trough_border, trough_border, -trough_border, -trough_border); + scrollBarSubLine.adjust(trough_border, trough_border, -trough_border, -trough_border); + scrollBarSlider.adjust(horizontal ? -trough_border : 0, horizontal ? 0 : -trough_border, + horizontal ? trough_border : 0, horizontal ? 0 : trough_border); + } + + // Some styles check the position of scrollbars in order to determine + // if lines should be painted when the scrollbar is in max or min positions. + int maximum = 2; + int fakePos = 0; + bool reverse = (option->direction == Qt::RightToLeft); + if (scrollBar->minimum == scrollBar->maximum) + maximum = 0; + if (scrollBar->sliderPosition == scrollBar->maximum) + fakePos = maximum; + else if (scrollBar->sliderPosition > scrollBar->minimum) + fakePos = maximum - 1; + + + GtkRange *range = (GtkRange*)(horizontal ? gtkHScrollBar : gtkVScrollBar); + GtkAdjustment *adjustment = d->gtk_range_get_adjustment(range); + + if (adjustment) { + d->gtk_adjustment_configure(adjustment, fakePos, 0, maximum, 0, 0, 0); + } else { + adjustment = (GtkAdjustment*)d->gtk_adjustment_new(fakePos, 0, maximum, 0, 0, 0); + d->gtk_range_set_adjustment(range, adjustment); + } + + if (scrollBar->subControls & SC_ScrollBarGroove) { + GtkStateType state = GTK_STATE_ACTIVE; + + if (!(option->state & State_Enabled)) + state = GTK_STATE_INSENSITIVE; + + if (trough_under_steppers) + grooveRect = option->rect; + + gtkPainter.paintBox( scrollbarWidget, "trough", grooveRect, state, GTK_SHADOW_IN, style); + } + + //paint slider + if (scrollBar->subControls & SC_ScrollBarSlider) { + GtkStateType state = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled)) + state = GTK_STATE_INSENSITIVE; + else if (activate_slider && + option->state & State_Sunken && (scrollBar->activeSubControls & SC_ScrollBarSlider)) + state = GTK_STATE_ACTIVE; + else if (option->state & State_MouseOver && (scrollBar->activeSubControls & SC_ScrollBarSlider)) + state = GTK_STATE_PRELIGHT; + + GtkShadowType shadow = GTK_SHADOW_OUT; + + if (trough_under_steppers) { + if (!horizontal) + scrollBarSlider.adjust(trough_border, 0, -trough_border, 0); + else + scrollBarSlider.adjust(0, trough_border, 0, -trough_border); + } + + gtkPainter.paintSlider( scrollbarWidget, "slider", scrollBarSlider, state, shadow, style, + + horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, QString(QLS("%0%1")).arg(fakePos).arg(maximum)); + } + + if (scrollBar->subControls & SC_ScrollBarAddLine) { + gtkVScrollBar->allocation.y = scrollBarAddLine.top(); + gtkVScrollBar->allocation.height = scrollBarAddLine.height() - rect.height() + 6; + gtkHScrollBar->allocation.x = scrollBarAddLine.right(); + gtkHScrollBar->allocation.width = scrollBarAddLine.width() - rect.width(); + + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled) || (fakePos == maximum)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & State_Sunken && (scrollBar->activeSubControls & SC_ScrollBarAddLine)) { + state = GTK_STATE_ACTIVE; + shadow = GTK_SHADOW_IN; + + } else if (option->state & State_MouseOver && (scrollBar->activeSubControls & SC_ScrollBarAddLine)) + state = GTK_STATE_PRELIGHT; + + gtkPainter.paintBox( scrollbarWidget, + horizontal ? "hscrollbar" : "vscrollbar", scrollBarAddLine, + state, shadow, style, QLS("add")); + + gtkPainter.paintArrow( scrollbarWidget, horizontal ? "hscrollbar" : "vscrollbar", scrollBarAddLine.adjusted(4, 4, -4, -4), + horizontal ? (reverse ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT) : + GTK_ARROW_DOWN, state, GTK_SHADOW_NONE, FALSE, style); + } + + if (scrollBar->subControls & SC_ScrollBarSubLine) { + gtkVScrollBar->allocation.y = 0; + gtkVScrollBar->allocation.height = scrollBarSubLine.height(); + gtkHScrollBar->allocation.x = 0; + gtkHScrollBar->allocation.width = scrollBarSubLine.width(); + + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled) || (fakePos == 0)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & State_Sunken && (scrollBar->activeSubControls & SC_ScrollBarSubLine)) { + shadow = GTK_SHADOW_IN; + state = GTK_STATE_ACTIVE; + + } else if (option->state & State_MouseOver && (scrollBar->activeSubControls & SC_ScrollBarSubLine)) + state = GTK_STATE_PRELIGHT; + + gtkPainter.paintBox(scrollbarWidget, horizontal ? "hscrollbar" : "vscrollbar", scrollBarSubLine, + state, shadow, style, QLS("sub")); + + gtkPainter.paintArrow(scrollbarWidget, horizontal ? "hscrollbar" : "vscrollbar", scrollBarSubLine.adjusted(4, 4, -4, -4), + horizontal ? (reverse ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT) : + GTK_ARROW_UP, state, GTK_SHADOW_NONE, FALSE, style); + } + } + break; + +#endif //QT_NO_SCROLLBAR +#ifndef QT_NO_SPINBOX + + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + + GtkWidget *gtkSpinButton = spinBox->buttonSymbols == QAbstractSpinBox::NoButtons + ? d->gtkWidget("GtkEntry") + : d->gtkWidget("GtkSpinButton"); + bool isEnabled = (spinBox->state & State_Enabled); + bool hover = isEnabled && (spinBox->state & State_MouseOver); + bool sunken = (spinBox->state & State_Sunken); + bool upIsActive = (spinBox->activeSubControls == SC_SpinBoxUp); + bool downIsActive = (spinBox->activeSubControls == SC_SpinBoxDown); + bool reverse = (spinBox->direction == Qt::RightToLeft); + + QRect editArea = option->rect; + QRect editRect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxEditField, widget); + QRect upRect, downRect, buttonRect; + if (spinBox->buttonSymbols != QAbstractSpinBox::NoButtons) { + upRect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget); + downRect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget); + + //### Move this to subControlRect + upRect.setTop(option->rect.top()); + + if (reverse) + upRect.setLeft(option->rect.left()); + else + upRect.setRight(option->rect.right()); + + downRect.setBottom(option->rect.bottom()); + + if (reverse) + downRect.setLeft(option->rect.left()); + else + downRect.setRight(option->rect.right()); + + buttonRect = upRect | downRect; + + if (reverse) + editArea.setLeft(upRect.right()); + else + editArea.setRight(upRect.left()); + } + if (spinBox->frame) { + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + if (!(option->state & State_Enabled)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & State_HasFocus) + state = GTK_STATE_NORMAL; + else if (state == GTK_STATE_PRELIGHT) + state = GTK_STATE_NORMAL; + + shadow = GTK_SHADOW_IN; + style = gtkPainter.getStyle(gtkSpinButton); + + + QString key; + + if (option->state & State_HasFocus) { + key += QLatin1Char('f'); + GTK_WIDGET_SET_FLAGS(gtkSpinButton, GTK_HAS_FOCUS); + } + + uint resolve_mask = option->palette.resolve(); + + if (resolve_mask & (1 << QPalette::Base)) // Palette overridden by user + painter->fillRect(editRect, option->palette.base().color()); + else + gtkPainter.paintFlatBox(gtkSpinButton, "entry_bg", editArea.adjusted(style->xthickness, style->ythickness, + -style->xthickness, -style->ythickness), + option->state & State_Enabled ? + GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE, GTK_SHADOW_NONE, style, key); + + gtkPainter.paintShadow(gtkSpinButton, "entry", editArea, state, GTK_SHADOW_IN, gtkSpinButton->style, key); + if (spinBox->buttonSymbols != QAbstractSpinBox::NoButtons) { + gtkPainter.paintBox(gtkSpinButton, "spinbutton", buttonRect, state, GTK_SHADOW_IN, style, key); + + upRect.setSize(downRect.size()); + if (!(option->state & State_Enabled)) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_up", upRect, GTK_STATE_INSENSITIVE, GTK_SHADOW_IN, style, key); + else if (upIsActive && sunken) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_up", upRect, GTK_STATE_ACTIVE, GTK_SHADOW_IN, style, key); + else if (upIsActive && hover) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_up", upRect, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, style, key); + else + gtkPainter.paintBox( gtkSpinButton, "spinbutton_up", upRect, GTK_STATE_NORMAL, GTK_SHADOW_OUT, style, key); + + if (!(option->state & State_Enabled)) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_down", downRect, GTK_STATE_INSENSITIVE, GTK_SHADOW_IN, style, key); + else if (downIsActive && sunken) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_down", downRect, GTK_STATE_ACTIVE, GTK_SHADOW_IN, style, key); + else if (downIsActive && hover) + gtkPainter.paintBox( gtkSpinButton, "spinbutton_down", downRect, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, style, key); + else + gtkPainter.paintBox( gtkSpinButton, "spinbutton_down", downRect, GTK_STATE_NORMAL, GTK_SHADOW_OUT, style, key); + + if (option->state & State_HasFocus) + GTK_WIDGET_UNSET_FLAGS(gtkSpinButton, GTK_HAS_FOCUS); + } + } + + if (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) { + int centerX = upRect.center().x(); + int centerY = upRect.center().y(); + // plus/minus + + if (spinBox->activeSubControls == SC_SpinBoxUp && sunken) { + painter->drawLine(1 + centerX - 2, 1 + centerY, 1 + centerX + 2, 1 + centerY); + painter->drawLine(1 + centerX, 1 + centerY - 2, 1 + centerX, 1 + centerY + 2); + + } else { + painter->drawLine(centerX - 2, centerY, centerX + 2, centerY); + painter->drawLine(centerX, centerY - 2, centerX, centerY + 2); + } + centerX = downRect.center().x(); + centerY = downRect.center().y(); + + if (spinBox->activeSubControls == SC_SpinBoxDown && sunken) { + painter->drawLine(1 + centerX - 2, 1 + centerY, 1 + centerX + 2, 1 + centerY); + } else { + painter->drawLine(centerX - 2, centerY, centerX + 2, centerY); + } + + } else if (spinBox->buttonSymbols == QAbstractSpinBox::UpDownArrows) { + int size = d->getSpinboxArrowSize(); + int w = size / 2 - 1; + w -= w % 2 - 1; // force odd + int h = (w + 1)/2; + QRect arrowRect(0, 0, w, h); + arrowRect.moveCenter(upRect.center()); + // arrows + GtkStateType state = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled) || !(spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled)) + state = GTK_STATE_INSENSITIVE; + + gtkPainter.paintArrow( gtkSpinButton, "spinbutton", arrowRect, GTK_ARROW_UP, state, + GTK_SHADOW_NONE, FALSE, style); + + arrowRect.moveCenter(downRect.center()); + + if (!(option->state & State_Enabled) || !(spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled)) + state = GTK_STATE_INSENSITIVE; + + gtkPainter.paintArrow( gtkSpinButton, "spinbutton", arrowRect, GTK_ARROW_DOWN, state, + GTK_SHADOW_NONE, FALSE, style); + } + } + break; + +#endif // QT_NO_SPINBOX + +#ifndef QT_NO_SLIDER + + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + GtkWidget *hScaleWidget = d->gtkWidget("GtkHScale"); + GtkWidget *vScaleWidget = d->gtkWidget("GtkVScale"); + + QRect groove = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); + QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + + bool horizontal = slider->orientation == Qt::Horizontal; + bool ticksAbove = slider->tickPosition & QSlider::TicksAbove; + bool ticksBelow = slider->tickPosition & QSlider::TicksBelow; + + QBrush oldBrush = painter->brush(); + QPen oldPen = painter->pen(); + + QColor shadowAlpha(Qt::black); + shadowAlpha.setAlpha(10); + QColor highlightAlpha(Qt::white); + highlightAlpha.setAlpha(80); + + QGtkStylePrivate::gtk_widget_set_direction(hScaleWidget, slider->upsideDown ? + GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + GtkWidget *scaleWidget = horizontal ? hScaleWidget : vScaleWidget; + style = scaleWidget->style; + + if ((option->subControls & SC_SliderGroove) && groove.isValid()) { + + GtkRange *range = (GtkRange*)scaleWidget; + GtkAdjustment *adjustment = d->gtk_range_get_adjustment(range); + if (adjustment) { + d->gtk_adjustment_configure(adjustment, + slider->sliderPosition, + slider->minimum, + slider->maximum, + slider->singleStep, + slider->singleStep, + slider->pageStep); + } else { + adjustment = (GtkAdjustment*)d->gtk_adjustment_new(slider->sliderPosition, + slider->minimum, + slider->maximum, + slider->singleStep, + slider->singleStep, + slider->pageStep); + d->gtk_range_set_adjustment(range, adjustment); + } + + int outerSize; + d->gtk_range_set_inverted(range, !horizontal); + d->gtk_widget_style_get(scaleWidget, "trough-border", &outerSize, NULL); + outerSize++; + + GtkStateType state = gtkPainter.gtkState(option); + int focusFrameMargin = 2; + QRect grooveRect = option->rect.adjusted(focusFrameMargin, outerSize + focusFrameMargin, + -focusFrameMargin, -outerSize - focusFrameMargin); + + gboolean trough_side_details = false; // Indicates if the upper or lower scale background differs + if (!d->gtk_check_version(2, 10, 0)) + d->gtk_widget_style_get((GtkWidget*)(scaleWidget), "trough-side-details", &trough_side_details, NULL); + + if (!trough_side_details) { + gtkPainter.paintBox( scaleWidget, "trough", grooveRect, state, + GTK_SHADOW_IN, style, QString(QLS("p%0")).arg(slider->sliderPosition)); + } else { + QRect upperGroove = grooveRect; + QRect lowerGroove = grooveRect; + + if (horizontal) { + if (slider->upsideDown) { + lowerGroove.setLeft(handle.center().x()); + upperGroove.setRight(handle.center().x()); + } else { + upperGroove.setLeft(handle.center().x()); + lowerGroove.setRight(handle.center().x()); + } + } else { + if (!slider->upsideDown) { + lowerGroove.setBottom(handle.center().y()); + upperGroove.setTop(handle.center().y()); + } else { + upperGroove.setBottom(handle.center().y()); + lowerGroove.setTop(handle.center().y()); + } + } + + gtkPainter.paintBox( scaleWidget, "trough-upper", upperGroove, state, + GTK_SHADOW_IN, style, QString(QLS("p%0")).arg(slider->sliderPosition)); + gtkPainter.paintBox( scaleWidget, "trough-lower", lowerGroove, state, + GTK_SHADOW_IN, style, QString(QLS("p%0")).arg(slider->sliderPosition)); + } + } + + if (option->subControls & SC_SliderTickmarks) { + painter->setPen(darkOutline); + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, 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 <= 0) + interval = 1; + + int v = slider->minimum; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int pos = sliderPositionFromValue(slider->minimum, slider->maximum, + v_, (horizontal + ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown) + len / 2; + int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0); + if (horizontal) { + if (ticksAbove) + painter->drawLine(pos, slider->rect.top() + extra, + pos, slider->rect.top() + tickSize); + if (ticksBelow) + painter->drawLine(pos, slider->rect.bottom() - extra, + pos, slider->rect.bottom() - tickSize); + + } else { + if (ticksAbove) + painter->drawLine(slider->rect.left() + extra, pos, + slider->rect.left() + tickSize, pos); + if (ticksBelow) + painter->drawLine(slider->rect.right() - extra, pos, + slider->rect.right() - tickSize, pos); + } + + // In the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + } + + // Draw slider handle + if (option->subControls & SC_SliderHandle) { + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = GTK_STATE_NORMAL; + + if (!(option->state & State_Enabled)) + state = GTK_STATE_INSENSITIVE; + else if (option->state & State_MouseOver && option->activeSubControls & SC_SliderHandle) + state = GTK_STATE_PRELIGHT; + + bool horizontal = option->state & State_Horizontal; + + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = slider->rect.adjusted(-1, -1 ,1, 1); + + if (horizontal) { + fropt.rect.setTop(handle.top() - 3); + fropt.rect.setBottom(handle.bottom() + 4); + + } else { + fropt.rect.setLeft(handle.left() - 3); + fropt.rect.setRight(handle.right() + 3); + } + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + gtkPainter.paintSlider( scaleWidget, horizontal ? "hscale" : "vscale", handle, state, shadow, style, + + horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); + } + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + break; + +#endif // QT_NO_SLIDER + + default: + QCleanlooksStyle::drawComplexControl(control, option, painter, widget); + + break; + } +} + + +/*! + \reimp +*/ +void QGtkStyle::drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) { + QCleanlooksStyle::drawControl(element, option, painter, widget); + return; + } + + GtkStyle* style = d->gtkStyle(); + QGtkPainter gtkPainter(painter); + + switch (element) { + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + GtkWidget *gtkProgressBar = d->gtkWidget("GtkProgressBar"); + if (!gtkProgressBar) + return; + + QRect leftRect; + QRect rect = bar->rect; + GdkColor gdkText = gtkProgressBar->style->fg[GTK_STATE_NORMAL]; + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + gdkText = gtkProgressBar->style->fg[GTK_STATE_PRELIGHT]; + QColor alternateTextColor= QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + + painter->save(); + bool vertical = false, inverted = false; + if (const QStyleOptionProgressBarV2 *bar2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (bar2->orientation == Qt::Vertical); + inverted = bar2->invertedAppearance; + } + if (vertical) + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + const int progressIndicatorPos = (bar->progress - qreal(bar->minimum)) * rect.width() / + qMax(qreal(1.0), qreal(bar->maximum) - bar->minimum); + if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) + leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); + if (vertical) + leftRect.translate(rect.width() - progressIndicatorPos, 0); + + bool flip = (!vertical && (((bar->direction == Qt::RightToLeft) && !inverted) || + ((bar->direction == Qt::LeftToRight) && inverted))); + + QRegion rightRect = rect; + rightRect = rightRect.subtracted(leftRect); + painter->setClipRegion(rightRect); + painter->setPen(flip ? alternateTextColor : textColor); + painter->drawText(rect, bar->text, QTextOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter)); + if (!leftRect.isNull()) { + painter->setPen(flip ? textColor : alternateTextColor); + painter->setClipRect(leftRect); + painter->drawText(rect, bar->text, QTextOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter)); + } + painter->restore(); + } + break; + case CE_PushButtonLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + QRect ir = button->rect; + uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic; + QPoint buttonShift; + + if (option->state & State_Sunken) + buttonShift = QPoint(pixelMetric(PM_ButtonShiftHorizontal, option, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget)); + + if (proxy()->styleHint(SH_UnderlineShortcut, button, widget)) + tf |= Qt::TextShowMnemonic; + else + tf |= Qt::TextHideMnemonic; + + if (!button->icon.isNull()) { + //Center both icon and text + QPoint point; + + QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + if (mode == QIcon::Normal && button->state & State_HasFocus) + mode = QIcon::Active; + + QIcon::State state = QIcon::Off; + + if (button->state & State_On) + state = QIcon::On; + + QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state); + int w = pixmap.width(); + int h = pixmap.height(); + + if (!button->text.isEmpty()) + w += button->fontMetrics.boundingRect(option->rect, tf, button->text).width() + 4; + + point = QPoint(ir.x() + ir.width() / 2 - w / 2, + ir.y() + ir.height() / 2 - h / 2); + + if (button->direction == Qt::RightToLeft) + point.rx() += pixmap.width(); + + painter->drawPixmap(visualPos(button->direction, button->rect, point + buttonShift), pixmap); + + if (button->direction == Qt::RightToLeft) + ir.translate(-point.x() - 2, 0); + else + ir.translate(point.x() + pixmap.width() + 2, 0); + + // left-align text if there is + if (!button->text.isEmpty()) + tf |= Qt::AlignLeft; + + } else { + tf |= Qt::AlignHCenter; + } + + ir.translate(buttonShift); + + if (button->features & QStyleOptionButton::HasMenu) + ir = ir.adjusted(0, 0, -pixelMetric(PM_MenuButtonIndicator, button, widget), 0); + + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + QPalette pal = button->palette; + int labelState = GTK_STATE_INSENSITIVE; + if (option->state & State_Enabled) + labelState = (option->state & State_MouseOver && !(option->state & State_Sunken)) ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + GdkColor gdkText = gtkButton->style->fg[labelState]; + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + pal.setBrush(QPalette::ButtonText, textColor); + proxy()->drawItemText(painter, ir, tf, pal, (button->state & State_Enabled), + button->text, QPalette::ButtonText); + } + break; + + case CE_RadioButton: // Fall through + case CE_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool isRadio = (element == CE_RadioButton); + + // Draw prelight background + GtkWidget *gtkRadioButton = d->gtkWidget("GtkRadioButton"); + + if (option->state & State_MouseOver) { + gtkPainter.paintFlatBox(gtkRadioButton, "checkbutton", option->rect, + GTK_STATE_PRELIGHT, GTK_SHADOW_ETCHED_OUT, gtkRadioButton->style); + } + + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, btn, widget); + proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, + &subopt, painter, widget); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, widget); + // Get label text color + QPalette pal = subopt.palette; + int labelState = GTK_STATE_INSENSITIVE; + if (option->state & State_Enabled) + labelState = (option->state & State_MouseOver) ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + GdkColor gdkText = gtkRadioButton->style->fg[labelState]; + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + pal.setBrush(QPalette::WindowText, textColor); + subopt.palette = pal; + proxy()->drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, painter, widget); + + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect + : SE_CheckBoxFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + break; + +#ifndef QT_NO_COMBOBOX + + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget); + bool appearsAsList = !proxy()->styleHint(QStyle::SH_ComboBox_Popup, cb, widget); + painter->save(); + painter->setClipRect(editRect); + + if (!cb->currentIcon.isNull()) { + QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(cb->iconSize.width() + 4); + + iconRect = alignedRect(cb->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + + if (cb->editable) + painter->fillRect(iconRect, option->palette.brush(QPalette::Base)); + + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + + if (cb->direction == Qt::RightToLeft) + editRect.translate(-4 - cb->iconSize.width(), 0); + else + editRect.translate(cb->iconSize.width() + 4, 0); + } + + if (!cb->currentText.isEmpty() && !cb->editable) { + GtkWidget *gtkCombo = d->gtkWidget("GtkComboBox"); + QPalette pal = cb->palette; + int labelState = GTK_STATE_INSENSITIVE; + + if (option->state & State_Enabled) + labelState = (option->state & State_MouseOver && !appearsAsList) ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + GdkColor gdkText = gtkCombo->style->fg[labelState]; + + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + + pal.setBrush(QPalette::ButtonText, textColor); + + proxy()->drawItemText(painter, editRect.adjusted(1, 0, -1, 0), + visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter), + pal, cb->state & State_Enabled, cb->currentText, QPalette::ButtonText); + } + + painter->restore(); + } + break; + +#endif // QT_NO_COMBOBOX + + case CE_DockWidgetTitle: + painter->save(); + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + QRect rect = dwOpt->rect; + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, option, widget).adjusted(-2, 0, -2, 0); + QRect r = rect.adjusted(0, 0, -1, -1); + if (verticalTitleBar) + r.adjust(0, 0, 0, -1); + + if (verticalTitleBar) { + QRect r = rect; + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + + painter->translate(r.left(), r.top() + r.width()); + painter->rotate(-90); + painter->translate(-r.left(), -r.top()); + + rect = r; + } + + if (!dwOpt->title.isEmpty()) { + QString titleText + = painter->fontMetrics().elidedText(dwOpt->title, + Qt::ElideRight, titleRect.width()); + proxy()->drawItemText(painter, + titleRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + } + painter->restore(); + break; + + + + case CE_HeaderSection: + painter->save(); + + // Draws the header in tables. + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + Q_UNUSED(header); + GtkWidget *gtkTreeView = d->gtkWidget("GtkTreeView"); + // Get the middle column + GtkTreeViewColumn *column = d->gtk_tree_view_get_column((GtkTreeView*)gtkTreeView, 1); + Q_ASSERT(column); + + GtkWidget *gtkTreeHeader = column->button; + GtkStateType state = gtkPainter.gtkState(option); + GtkShadowType shadow = GTK_SHADOW_OUT; + + if (option->state & State_Sunken) + shadow = GTK_SHADOW_IN; + + gtkPainter.paintBox(gtkTreeHeader, "button", option->rect.adjusted(-1, 0, 0, 0), state, shadow, gtkTreeHeader->style); + } + + painter->restore(); + break; + +#ifndef QT_NO_SIZEGRIP + + case CE_SizeGrip: { + GtkWidget *gtkStatusbar = d->gtkWidget("GtkStatusbar.GtkFrame"); + QRect gripRect = option->rect.adjusted(0, 0, -gtkStatusbar->style->xthickness, -gtkStatusbar->style->ythickness); + gtkPainter.paintResizeGrip( gtkStatusbar, "statusbar", gripRect, GTK_STATE_NORMAL, + GTK_SHADOW_OUT, QApplication::isRightToLeft() ? + GDK_WINDOW_EDGE_SOUTH_WEST : GDK_WINDOW_EDGE_SOUTH_EAST, + gtkStatusbar->style); + } + break; + +#endif // QT_NO_SIZEGRIP + + case CE_MenuBarEmptyArea: { + GtkWidget *gtkMenubar = d->gtkWidget("GtkMenuBar"); + GdkColor gdkBg = gtkMenubar->style->bg[GTK_STATE_NORMAL]; // Theme can depend on transparency + painter->fillRect(option->rect, QColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8)); + if (widget) { // See CE_MenuBarItem + QRect menuBarRect = widget->rect(); + QPixmap pixmap(menuBarRect.size()); + pixmap.fill(Qt::transparent); + QPainter pmPainter(&pixmap); + QGtkPainter gtkMenuBarPainter(&pmPainter); + GtkShadowType shadow_type; + d->gtk_widget_style_get(gtkMenubar, "shadow-type", &shadow_type, NULL); + gtkMenuBarPainter.paintBox( gtkMenubar, "menubar", menuBarRect, + GTK_STATE_NORMAL, shadow_type, gtkMenubar->style); + pmPainter.end(); + painter->drawPixmap(option->rect, pixmap, option->rect); + } + } + break; + + case CE_MenuBarItem: + painter->save(); + + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + GtkWidget *gtkMenubarItem = d->gtkWidget("GtkMenuBar.GtkMenuItem"); + GtkWidget *gtkMenubar = d->gtkWidget("GtkMenuBar"); + + style = gtkMenubarItem->style; + + if (widget) { + // Since Qt does not currently allow filling the entire background + // we use a hack for this by making a complete menubar each time and + // paint with the correct offset inside it. Pixmap caching should resolve + // most of the performance penalty. + QRect menuBarRect = widget->rect(); + QPixmap pixmap(menuBarRect.size()); + pixmap.fill(Qt::transparent); + QPainter pmPainter(&pixmap); + QGtkPainter menubarPainter(&pmPainter); + GtkShadowType shadow_type; + d->gtk_widget_style_get(gtkMenubar, "shadow-type", &shadow_type, NULL); + GdkColor gdkBg = gtkMenubar->style->bg[GTK_STATE_NORMAL]; // Theme can depend on transparency + painter->fillRect(option->rect, QColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8)); + menubarPainter.paintBox(gtkMenubar, "menubar", menuBarRect, + GTK_STATE_NORMAL, shadow_type, gtkMenubar->style); + pmPainter.end(); + painter->drawPixmap(option->rect, pixmap, option->rect); + } + + QStyleOptionMenuItem item = *mbi; + bool act = mbi->state & State_Selected && mbi->state & State_Sunken; + bool dis = !(mbi->state & State_Enabled); + item.rect = mbi->rect; + GdkColor gdkText = gtkMenubarItem->style->fg[dis ? GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL]; + GdkColor gdkHText = gtkMenubarItem->style->fg[GTK_STATE_PRELIGHT]; + QColor normalTextColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + QColor highlightedTextColor = QColor(gdkHText.red>>8, gdkHText.green>>8, gdkHText.blue>>8); + item.palette.setBrush(QPalette::HighlightedText, highlightedTextColor); + item.palette.setBrush(QPalette::Text, normalTextColor); + item.palette.setBrush(QPalette::ButtonText, normalTextColor); + QCommonStyle::drawControl(element, &item, painter, widget); + + if (act) { + GtkShadowType shadowType = GTK_SHADOW_NONE; + d->gtk_widget_style_get (gtkMenubarItem, "selected-shadow-type", &shadowType, NULL); + gtkPainter.paintBox(gtkMenubarItem, "menuitem", option->rect.adjusted(0, 0, 0, 3), + GTK_STATE_PRELIGHT, shadowType, gtkMenubarItem->style); + //draw text + QPalette::ColorRole textRole = dis ? QPalette::Text : QPalette::HighlightedText; + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + proxy()->drawItemText(painter, item.rect, alignment, item.palette, mbi->state & State_Enabled, mbi->text, textRole); + } + } + painter->restore(); + break; + + case CE_Splitter: { + GtkWidget *gtkWindow = d->gtkWidget("GtkWindow"); // The Murrine Engine currently assumes a widget is passed + gtkPainter.paintHandle(gtkWindow, "splitter", option->rect, gtkPainter.gtkState(option), GTK_SHADOW_NONE, + !(option->state & State_Horizontal) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL, + style); + } + break; + +#ifndef QT_NO_TOOLBAR + + case CE_ToolBar: + if (const QStyleOptionToolBar *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + // Reserve the beveled appearance only for mainwindow toolbars + if (!(widget && qobject_cast<const QMainWindow*> (widget->parentWidget()))) + break; + + QRect rect = option->rect; + // There is a 1 pixel gap between toolbar lines in some styles (i.e Human) + if (toolbar->positionWithinLine != QStyleOptionToolBar::End) + rect.adjust(0, 0, 1, 0); + + GtkWidget *gtkToolbar = d->gtkWidget("GtkToolbar"); + GtkShadowType shadow_type = GTK_SHADOW_NONE; + d->gtk_widget_style_get(gtkToolbar, "shadow-type", &shadow_type, NULL); + gtkPainter.paintBox( gtkToolbar, "toolbar", rect, + GTK_STATE_NORMAL, shadow_type, gtkToolbar->style); + } + break; + +#endif // QT_NO_TOOLBAR + + case CE_MenuItem: + painter->save(); + + // Draws one item in a popup menu. + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + const int windowsItemFrame = 2; // menu item frame width + const int windowsItemHMargin = 3; // menu item hor text margin + const int windowsItemVMargin = 26; // menu item ver text margin + const int windowsRightBorder = 15; // right border on windows + GtkWidget *gtkMenuItem = menuItem->checked ? d->gtkWidget("GtkMenu.GtkCheckMenuItem") : + d->gtkWidget("GtkMenu.GtkMenuItem"); + + style = gtkPainter.getStyle(gtkMenuItem); + QColor shadow = option->palette.dark().color(); + + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + GtkWidget *gtkMenuSeparator = d->gtkWidget("GtkMenu.GtkSeparatorMenuItem"); + painter->setPen(shadow.lighter(106)); + gboolean wide_separators = 0; + gint separator_height = 0; + guint horizontal_padding = 3; + QRect separatorRect = option->rect; + if (!d->gtk_check_version(2, 10, 0)) { + d->gtk_widget_style_get(gtkMenuSeparator, + "wide-separators", &wide_separators, + "separator-height", &separator_height, + "horizontal-padding", &horizontal_padding, + NULL); + } + separatorRect.setHeight(option->rect.height() - 2 * gtkMenuSeparator->style->ythickness); + separatorRect.setWidth(option->rect.width() - 2 * (horizontal_padding + gtkMenuSeparator->style->xthickness)); + separatorRect.moveCenter(option->rect.center()); + if (wide_separators) + gtkPainter.paintBox( gtkMenuSeparator, "hseparator", + separatorRect, GTK_STATE_NORMAL, GTK_SHADOW_NONE, gtkMenuSeparator->style); + else + gtkPainter.paintHline( gtkMenuSeparator, "hseparator", + separatorRect, GTK_STATE_NORMAL, gtkMenuSeparator->style, + 0, option->rect.right() - 1, 1); + painter->restore(); + break; + } + + bool selected = menuItem->state & State_Selected && menuItem->state & State_Enabled; + + if (selected) { + QRect rect = option->rect; +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox*>(widget)) + rect = option->rect; +#endif + gtkPainter.paintBox( gtkMenuItem, "menuitem", rect, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, style); + } + + bool checkable = menuItem->checkType != QStyleOptionMenuItem::NotCheckable; + bool checked = menuItem->checked; + bool enabled = menuItem->state & State_Enabled; + bool ignoreCheckMark = false; + + gint checkSize; + d->gtk_widget_style_get(d->gtkWidget("GtkMenu.GtkCheckMenuItem"), "indicator-size", &checkSize, NULL); + + int checkcol = qMax(menuItem->maxIconWidth, qMax(20, checkSize)); + +#ifndef QT_NO_COMBOBOX + + if (qobject_cast<const QComboBox*>(widget)) + ignoreCheckMark = true; // Ignore the checkmarks provided by the QComboMenuDelegate + +#endif + if (!ignoreCheckMark) { + // Check + QRect checkRect(option->rect.left() + 7, option->rect.center().y() - checkSize/2 + 1, checkSize, checkSize); + checkRect = visualRect(menuItem->direction, menuItem->rect, checkRect); + + if (checkable && menuItem->icon.isNull()) { + // Some themes such as aero-clone draw slightly outside the paint rect + int spacing = 1; // ### Consider using gtkCheckBox : "indicator-spacing" instead + + if (menuItem->checkType & QStyleOptionMenuItem::Exclusive) { + // Radio button + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + if (selected) + state = GTK_STATE_PRELIGHT; + if (checked) + shadow = GTK_SHADOW_IN; + + gtkPainter.setClipRect(checkRect.adjusted(-spacing, -spacing, spacing, spacing)); + gtkPainter.paintOption(gtkMenuItem, checkRect.translated(-spacing, -spacing), state, shadow, + gtkMenuItem->style, QLS("option")); + gtkPainter.setClipRect(QRect()); + + } else { + // Check box + if (menuItem->icon.isNull()) { + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = gtkPainter.gtkState(option); + + if (selected) + state = GTK_STATE_PRELIGHT; + if (checked) + shadow = GTK_SHADOW_IN; + + gtkPainter.setClipRect(checkRect.adjusted(-spacing, -spacing, -spacing, -spacing)); + gtkPainter.paintCheckbox(gtkMenuItem, checkRect.translated(-spacing, -spacing), state, shadow, + gtkMenuItem->style, QLS("check")); + gtkPainter.setClipRect(QRect()); + } + } + } + + } else { + // Ignore checkmark + if (menuItem->icon.isNull()) + checkcol = 0; + else + checkcol = menuItem->maxIconWidth; + } + + bool dis = !(menuItem->state & State_Enabled); + bool act = menuItem->state & State_Selected; + const QStyleOption *opt = option; + const QStyleOptionMenuItem *menuitem = menuItem; + QPainter *p = painter; + QRect vCheckRect = visualRect(opt->direction, menuitem->rect, + QRect(menuitem->rect.x() + 3, menuitem->rect.y(), + checkcol, menuitem->rect.height())); + + if (!menuItem->icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + + if (act && !dis) + mode = QIcon::Active; + + QPixmap pixmap; + int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize, option, widget); + QSize iconSize(smallIconSize, smallIconSize); + +#ifndef QT_NO_COMBOBOX + if (const QComboBox *combo = qobject_cast<const QComboBox*>(widget)) + iconSize = combo->iconSize(); + +#endif // QT_NO_COMBOBOX + if (checked) + pixmap = menuItem->icon.pixmap(iconSize, mode, QIcon::On); + else + pixmap = menuItem->icon.pixmap(iconSize, mode); + + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center() - QPoint(0, 1)); + painter->setPen(menuItem->palette.text().color()); + if (!ignoreCheckMark && checkable && checked) { + QStyleOption opt = *option; + + if (act) { + QColor activeColor = mergedColors(option->palette.background().color(), + option->palette.highlight().color()); + opt.palette.setBrush(QPalette::Button, activeColor); + } + opt.state |= State_Sunken; + opt.rect = vCheckRect; + proxy()->drawPrimitive(PE_PanelButtonCommand, &opt, painter, widget); + } + painter->drawPixmap(pmr.topLeft(), pixmap); + } + + GdkColor gdkText = gtkMenuItem->style->fg[GTK_STATE_NORMAL]; + GdkColor gdkDText = gtkMenuItem->style->fg[GTK_STATE_INSENSITIVE]; + GdkColor gdkHText = gtkMenuItem->style->fg[GTK_STATE_PRELIGHT]; + uint resolve_mask = option->palette.resolve(); + QColor textColor = QColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + QColor disabledTextColor = QColor(gdkDText.red>>8, gdkDText.green>>8, gdkDText.blue>>8); + if (resolve_mask & (1 << QPalette::ButtonText)) { + textColor = option->palette.buttonText().color(); + disabledTextColor = option->palette.brush(QPalette::Disabled, QPalette::ButtonText).color(); + } + + QColor highlightedTextColor = QColor(gdkHText.red>>8, gdkHText.green>>8, gdkHText.blue>>8); + if (resolve_mask & (1 << QPalette::HighlightedText)) { + highlightedTextColor = option->palette.highlightedText().color(); + } + + if (selected) + painter->setPen(highlightedTextColor); + else + painter->setPen(textColor); + + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + int xpos = menuitem->rect.x() + xm + 1; + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(opt->direction, menuitem->rect, textRect); + QString s = menuitem->text; + + if (!s.isEmpty()) { // Draw text + p->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; + + // Draw shortcut right aligned + text_flags |= Qt::AlignRight; + + if (t >= 0) { + int rightMargin = 12; // Hardcode for now + QRect vShortcutRect = visualRect(opt->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right() - rightMargin, textRect.bottom()))); + + if (dis) + p->setPen(disabledTextColor); + p->drawText(vShortcutRect, text_flags , s.mid(t + 1)); + s = s.left(t); + } + + text_flags &= ~Qt::AlignRight; + text_flags |= Qt::AlignLeft; + QFont font = menuitem->font; + if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem) + font.setBold(true); + p->setFont(font); + + if (dis) + p->setPen(disabledTextColor); + p->drawText(vTextRect, text_flags, s.left(t)); + p->restore(); + } + + // Arrow + if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + + QFontMetrics fm(menuitem->font); + int arrow_size = fm.ascent() + fm.descent() - 2 * gtkMenuItem->style->ythickness; + gfloat arrow_scaling = 0.8; + int extra = 0; + if (!d->gtk_check_version(2, 16, 0)) { + // "arrow-scaling" is actually hardcoded and fails on hardy (see gtk+-2.12/gtkmenuitem.c) + // though the current documentation states otherwise + d->gtk_widget_style_get(gtkMenuItem, "arrow-scaling", &arrow_scaling, NULL); + // in versions < 2.16 ythickness was previously subtracted from the arrow_size + extra = 2 * gtkMenuItem->style->ythickness; + } + + int horizontal_padding; + d->gtk_widget_style_get(gtkMenuItem, "horizontal-padding", &horizontal_padding, NULL); + + const int dim = static_cast<int>(arrow_size * arrow_scaling) + extra; + int xpos = menuItem->rect.left() + menuItem->rect.width() - horizontal_padding - dim; + QRect vSubMenuRect = visualRect(option->direction, menuItem->rect, + QRect(xpos, menuItem->rect.top() + + menuItem->rect.height() / 2 - dim / 2, dim, dim)); + GtkStateType state = enabled ? (act ? GTK_STATE_PRELIGHT: GTK_STATE_NORMAL) : GTK_STATE_INSENSITIVE; + GtkShadowType shadowType = (state == GTK_STATE_PRELIGHT) ? GTK_SHADOW_OUT : GTK_SHADOW_IN; + gtkPainter.paintArrow(gtkMenuItem, "menuitem", vSubMenuRect, QApplication::isRightToLeft() ? GTK_ARROW_LEFT : GTK_ARROW_RIGHT, state, + shadowType, FALSE, style); + } + } + painter->restore(); + break; + + case CE_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + proxy()->drawControl(CE_PushButtonBevel, btn, painter, widget); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_PushButtonContents, btn, widget); + gint interiorFocus = true; + d->gtk_widget_style_get(gtkButton, "interior-focus", &interiorFocus, NULL); + int xt = interiorFocus ? gtkButton->style->xthickness : 0; + int yt = interiorFocus ? gtkButton->style->ythickness : 0; + + if (btn->features & QStyleOptionButton::Flat && btn->state & State_HasFocus) + // The normal button focus rect does not work well for flat buttons in Clearlooks + proxy()->drawPrimitive(PE_FrameFocusRect, option, painter, widget); + else if (btn->state & State_HasFocus) + gtkPainter.paintFocus(gtkButton, "button", + option->rect.adjusted(xt, yt, -xt, -yt), + btn->state & State_Sunken ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL, + gtkButton->style); + + proxy()->drawControl(CE_PushButtonLabel, &subopt, painter, widget); + } + break; + +#ifndef QT_NO_TABBAR + + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + GtkWidget *gtkNotebook = d->gtkWidget("GtkNotebook"); + style = gtkPainter.getStyle(gtkNotebook); + + QRect rect = option->rect; + GtkShadowType shadow = GTK_SHADOW_OUT; + GtkStateType state = GTK_STATE_ACTIVE; + if (tab->state & State_Selected) + state = GTK_STATE_NORMAL; + + bool selected = (tab->state & State_Selected); + bool first = false, last = false; + if (widget) { + // This is most accurate and avoids resizing tabs while moving + first = tab->rect.left() == widget->rect().left(); + last = tab->rect.right() == widget->rect().right(); + } else if (option->direction == Qt::RightToLeft) { + bool tmp = first; + first = last; + last = tmp; + } + int topIndent = 3; + int bottomIndent = 1; + int tabOverlap = 1; + painter->save(); + + switch (tab->shape) { + case QTabBar::RoundedNorth: + if (!selected) + rect.adjust(first ? 0 : -tabOverlap, topIndent, last ? 0 : tabOverlap, -bottomIndent); + gtkPainter.paintExtention( gtkNotebook, "tab", rect, + state, shadow, GTK_POS_BOTTOM, style); + break; + + case QTabBar::RoundedSouth: + if (!selected) + rect.adjust(first ? 0 : -tabOverlap, 0, last ? 0 : tabOverlap, -topIndent); + gtkPainter.paintExtention( gtkNotebook, "tab", rect.adjusted(0, 1, 0, 0), + state, shadow, GTK_POS_TOP, style); + break; + + case QTabBar::RoundedWest: + if (!selected) + rect.adjust(topIndent, 0, -bottomIndent, 0); + gtkPainter.paintExtention( gtkNotebook, "tab", rect, state, shadow, GTK_POS_RIGHT, style); + break; + + case QTabBar::RoundedEast: + if (!selected) + rect.adjust(bottomIndent, 0, -topIndent, 0); + gtkPainter.paintExtention( gtkNotebook, "tab", rect, state, shadow, GTK_POS_LEFT, style); + break; + + default: + QCleanlooksStyle::drawControl(element, option, painter, widget); + break; + } + + painter->restore(); + } + + break; + +#endif //QT_NO_TABBAR + + case CE_ProgressBarGroove: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + Q_UNUSED(bar); + GtkWidget *gtkProgressBar = d->gtkWidget("GtkProgressBar"); + GtkStateType state = gtkPainter.gtkState(option); + gtkPainter.paintBox( gtkProgressBar, "trough", option->rect, state, GTK_SHADOW_IN, gtkProgressBar->style); + } + + break; + + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + GtkStateType state = option->state & State_Enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE; + GtkWidget *gtkProgressBar = d->gtkWidget("GtkProgressBar"); + style = gtkProgressBar->style; + gtkPainter.paintBox( gtkProgressBar, "trough", option->rect, state, GTK_SHADOW_IN, style); + int xt = style->xthickness; + int yt = style->ythickness; + QRect rect = bar->rect.adjusted(xt, yt, -xt, -yt); + bool vertical = false; + bool inverted = false; + bool indeterminate = (bar->minimum == 0 && bar->maximum == 0); + // Get extra style options if version 2 + + if (const QStyleOptionProgressBarV2 *bar2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (bar2->orientation == Qt::Vertical); + inverted = bar2->invertedAppearance; + } + + // If the orientation is vertical, we use a transform to rotate + // the progress bar 90 degrees clockwise. This way we can use the + // same rendering code for both orientations. + if (vertical) { + rect.translate(xt, -yt * 2); + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // Flip width and height + QTransform m = QTransform::fromTranslate(rect.height(), 0); + m.rotate(90.0); + painter->setTransform(m); + } + + int maxWidth = rect.width(); + int minWidth = 4; + + qint64 progress = (qint64)qMax(bar->progress, bar->minimum); // Workaround for bug in QProgressBar + double vc6_workaround = ((progress - qint64(bar->minimum)) / double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth; + int progressBarWidth = (int(vc6_workaround) > minWidth ) ? int(vc6_workaround) : minWidth; + int width = indeterminate ? maxWidth : progressBarWidth; + bool reverse = (!vertical && (bar->direction == Qt::RightToLeft)) || vertical; + + if (inverted) + reverse = !reverse; + + int maximum = 2; + int fakePos = 0; + if (bar->minimum == bar->maximum) + maximum = 0; + if (bar->progress == bar->maximum) + fakePos = maximum; + else if (bar->progress > bar->minimum) + fakePos = maximum - 1; + + d->gtk_progress_configure((GtkProgress*)gtkProgressBar, fakePos, 0, maximum); + + QRect progressBar; + + if (!indeterminate) { + if (!reverse) + progressBar.setRect(rect.left(), rect.top(), width, rect.height()); + else + progressBar.setRect(rect.right() - width, rect.top(), width, rect.height()); + + } else { + Q_D(const QGtkStyle); + int slideWidth = ((rect.width() - 4) * 2) / 3; + int step = ((d->animateStep * slideWidth) / d->animationFps) % slideWidth; + if ((((d->animateStep * slideWidth) / d->animationFps) % (2 * slideWidth)) >= slideWidth) + step = slideWidth - step; + progressBar.setRect(rect.left() + step, rect.top(), slideWidth / 2, rect.height()); + } + + QString key = QString(QLS("%0")).arg(fakePos); + if (inverted) { + key += QLatin1String("inv"); + gtkPainter.setFlipHorizontal(true); + } + gtkPainter.paintBox( gtkProgressBar, "bar", progressBar, GTK_STATE_SELECTED, GTK_SHADOW_OUT, style, key); + } + + break; + + default: + QCleanlooksStyle::drawControl(element, option, painter, widget); + } +} + +/*! + \reimp +*/ +QRect QGtkStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + QRect rect = QWindowsStyle::subControlRect(control, option, subControl, widget); + if (!d->isThemeAvailable()) + return QCleanlooksStyle::subControlRect(control, option, subControl, widget); + + switch (control) { + case CC_TitleBar: + return QCleanlooksStyle::subControlRect(control, option, subControl, widget); + + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + // Reserve space for outside focus rect + QStyleOptionSlider sliderCopy = *slider; + sliderCopy.rect = option->rect.adjusted(2, 2, -2, -2); + return QCleanlooksStyle::subControlRect(control, &sliderCopy, subControl, widget); + } + + break; + +#ifndef QT_NO_GROUPBOX + + case CC_GroupBox: + if (qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + rect = option->rect.adjusted(0, groupBoxTopMargin, 0, -groupBoxBottomMargin); + int topMargin = 0; + int topHeight = 0; + topHeight = 10; + QRect frameRect = rect; + frameRect.setTop(topMargin); + + if (subControl == SC_GroupBoxFrame) + return rect; + else if (subControl == SC_GroupBoxContents) { + int margin = 0; + int leftMarginExtension = 8; + return frameRect.adjusted(leftMarginExtension + margin, margin + topHeight + groupBoxTitleMargin, -margin, -margin); + } + + if (const QGroupBox *groupBoxWidget = qobject_cast<const QGroupBox *>(widget)) { + //Prepare metrics for a bold font + QFont font = widget->font(); + font.setBold(true); + QFontMetrics fontMetrics(font); + QSize textRect = fontMetrics.boundingRect(groupBoxWidget->title()).size() + QSize(4, 4); + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget); + int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget); + + if (subControl == SC_GroupBoxCheckBox) { + rect.setWidth(indicatorWidth); + rect.setHeight(indicatorHeight); + rect.moveTop((textRect.height() - indicatorHeight) / 2); + + } else if (subControl == SC_GroupBoxLabel) { + if (groupBoxWidget->isCheckable()) + rect.adjust(indicatorWidth + 4, 0, 0, 0); + rect.setSize(textRect); + } + rect = visualRect(option->direction, option->rect, rect); + } + } + + return rect; + +#endif +#ifndef QT_NO_SPINBOX + + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + GtkWidget *gtkSpinButton = d->gtkWidget("GtkSpinButton"); + int center = spinbox->rect.height() / 2; + int xt = spinbox->frame ? gtkSpinButton->style->xthickness : 0; + int yt = spinbox->frame ? gtkSpinButton->style->ythickness : 0; + int y = yt; + + QSize bs; + bs.setHeight(qMax(8, spinbox->rect.height()/2 - y)); + bs.setWidth(d->getSpinboxArrowSize()); + int x, lx, rx; + x = spinbox->rect.width() - y - bs.width() + 2; + lx = xt; + rx = x - xt; + + switch (subControl) { + + case SC_SpinBoxUp: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + rect = QRect(x, xt, bs.width(), center - yt); + break; + + case SC_SpinBoxDown: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + rect = QRect(x, center, bs.width(), spinbox->rect.bottom() - center - yt + 1); + break; + + case SC_SpinBoxEditField: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + rect = QRect(lx, yt, spinbox->rect.width() - 2*xt, spinbox->rect.height() - 2*yt); + else + rect = QRect(lx, yt, rx - qMax(xt - 1, 0), spinbox->rect.height() - 2*yt); + break; + + case SC_SpinBoxFrame: + rect = spinbox->rect; + + default: + break; + } + + rect = visualRect(spinbox->direction, spinbox->rect, rect); + } + + break; + +#endif // Qt_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + + case CC_ComboBox: + if (const QStyleOptionComboBox *box = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + // We employ the gtk widget to position arrows and separators for us + GtkWidget *gtkCombo = box->editable ? d->gtkWidget("GtkComboBoxEntry") + : d->gtkWidget("GtkComboBox"); + d->gtk_widget_set_direction(gtkCombo, (option->direction == Qt::RightToLeft) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); + GtkAllocation geometry = {0, 0, qMax(0, option->rect.width()), qMax(0, option->rect.height())}; + d->gtk_widget_size_allocate(gtkCombo, &geometry); + int appears_as_list = !proxy()->styleHint(QStyle::SH_ComboBox_Popup, option, widget); + QHashableLatin1Literal arrowPath("GtkComboBoxEntry.GtkToggleButton"); + if (!box->editable) { + if (appears_as_list) + arrowPath = "GtkComboBox.GtkToggleButton"; + else + arrowPath = "GtkComboBox.GtkToggleButton.GtkHBox.GtkArrow"; + } + + GtkWidget *arrowWidget = d->gtkWidget(arrowPath); + if (!arrowWidget) + return QCleanlooksStyle::subControlRect(control, option, subControl, widget); + + QRect buttonRect(option->rect.left() + arrowWidget->allocation.x, + option->rect.top() + arrowWidget->allocation.y, + arrowWidget->allocation.width, arrowWidget->allocation.height); + + switch (subControl) { + + case SC_ComboBoxArrow: // Note: this indicates the arrowbutton for editable combos + rect = buttonRect; + break; + + case SC_ComboBoxEditField: { + rect = visualRect(option->direction, option->rect, rect); + int xMargin = box->editable ? 1 : 4, yMargin = 2; + rect.setRect(option->rect.left() + gtkCombo->style->xthickness + xMargin, + option->rect.top() + gtkCombo->style->ythickness + yMargin, + option->rect.width() - buttonRect.width() - 2*(gtkCombo->style->xthickness + xMargin), + option->rect.height() - 2*(gtkCombo->style->ythickness + yMargin)); + rect = visualRect(option->direction, option->rect, rect); + break; + } + + default: + break; + } + } + + break; + +#endif // QT_NO_COMBOBOX + + default: + break; + } + + return rect; +} + +/*! + \reimp +*/ +QSize QGtkStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + QSize newSize = QCleanlooksStyle::sizeFromContents(type, option, size, widget); + if (!d->isThemeAvailable()) + return newSize; + + switch (type) { + + case CT_ToolButton: + if (const QStyleOptionToolButton *toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + GtkWidget *gtkButton = d->gtkWidget("GtkToolButton.GtkButton"); + newSize = size + QSize(2 * gtkButton->style->xthickness, 2 + 2 * gtkButton->style->ythickness); + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) { + QSize minSize(0, 25); + if (toolbutton->toolButtonStyle != Qt::ToolButtonTextOnly) + minSize = toolbutton->iconSize + QSize(12, 12); + newSize = newSize.expandedTo(minSize); + } + + if (toolbutton->features & QStyleOptionToolButton::HasMenu) + newSize += QSize(6, 0); + } + break; + case CT_MenuItem: + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + int textMargin = 8; + + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + GtkWidget *gtkMenuSeparator = d->gtkWidget("GtkMenu.GtkSeparatorMenuItem"); + GtkRequisition sizeReq = {0, 0}; + d->gtk_widget_size_request(gtkMenuSeparator, &sizeReq); + newSize = QSize(size.width(), sizeReq.height); + break; + } + + GtkWidget *gtkMenuItem = d->gtkWidget("GtkMenu.GtkCheckMenuItem"); + GtkStyle* style = gtkMenuItem->style; + + // Note we get the perfect height for the default font since we + // set a fake text label on the gtkMenuItem + // But if custom fonts are used on the widget we need a minimum size + GtkRequisition sizeReq = {0, 0}; + d->gtk_widget_size_request(gtkMenuItem, &sizeReq); + newSize.setHeight(qMax(newSize.height() - 4, sizeReq.height)); + newSize += QSize(textMargin + style->xthickness - 1, 0); + + // Cleanlooks assumes a check column of 20 pixels so we need to + // expand it a bit + gint checkSize; + d->gtk_widget_style_get(gtkMenuItem, "indicator-size", &checkSize, NULL); + newSize.setWidth(newSize.width() + qMax(0, checkSize - 20)); + } + + break; + + case CT_SpinBox: + // QSpinBox does some nasty things that depends on CT_LineEdit + newSize = size + QSize(0, -d->gtkWidget("GtkSpinButton")->style->ythickness * 2); + break; + + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + gint focusPadding, focusWidth; + d->gtk_widget_style_get(gtkButton, "focus-padding", &focusPadding, NULL); + d->gtk_widget_style_get(gtkButton, "focus-line-width", &focusWidth, NULL); + newSize = size; + newSize += QSize(2*gtkButton->style->xthickness + 4, 2*gtkButton->style->ythickness); + newSize += QSize(2*(focusWidth + focusPadding + 2), 2*(focusWidth + focusPadding)); + + GtkWidget *gtkButtonBox = d->gtkWidget("GtkHButtonBox"); + gint minWidth = 85, minHeight = 0; + d->gtk_widget_style_get(gtkButtonBox, "child-min-width", &minWidth, + "child-min-height", &minHeight, NULL); + if (!btn->text.isEmpty() && newSize.width() < minWidth) + newSize.setWidth(minWidth); + if (newSize.height() < minHeight) + newSize.setHeight(minHeight); + } + + break; + + case CT_Slider: { + GtkWidget *gtkSlider = d->gtkWidget("GtkHScale"); + newSize = size + QSize(2*gtkSlider->style->xthickness, 2*gtkSlider->style->ythickness); + } + break; + + case CT_LineEdit: { + GtkWidget *gtkEntry = d->gtkWidget("GtkEntry"); + newSize = size + QSize(2*gtkEntry->style->xthickness, 2 + 2*gtkEntry->style->ythickness); + } + break; + + case CT_ItemViewItem: + newSize += QSize(0, 2); + break; + + case CT_ComboBox: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + GtkWidget *gtkCombo = d->gtkWidget("GtkComboBox"); + QRect arrowButtonRect = proxy()->subControlRect(CC_ComboBox, combo, SC_ComboBoxArrow, widget); + newSize = size + QSize(12 + arrowButtonRect.width() + 2*gtkCombo->style->xthickness, 4 + 2*gtkCombo->style->ythickness); + + if (!(widget && qobject_cast<QToolBar *>(widget->parentWidget()))) + newSize += QSize(0, 2); + } + break; + + case CT_GroupBox: + newSize += QSize(4, groupBoxBottomMargin + groupBoxTopMargin + groupBoxTitleMargin); // Add some space below the groupbox + break; + + case CT_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + if (!tab->icon.isNull()) + newSize += QSize(6, 0); + } + newSize += QSize(1, 1); + break; + + default: + break; + } + + return newSize; +} + + +/*! \reimp */ +QPixmap QGtkStyle::standardPixmap(StandardPixmap sp, const QStyleOption *option, + const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) + return QCleanlooksStyle::standardPixmap(sp, option, widget); + + QPixmap pixmap; + switch (sp) { + + case SP_TitleBarNormalButton: { + QImage restoreButton((const char **)dock_widget_restore_xpm); + QColor alphaCorner = restoreButton.color(2); + alphaCorner.setAlpha(80); + restoreButton.setColor(2, alphaCorner.rgba()); + alphaCorner.setAlpha(180); + restoreButton.setColor(4, alphaCorner.rgba()); + return QPixmap::fromImage(restoreButton); + } + break; + + case SP_TitleBarCloseButton: // Fall through + case SP_DockWidgetCloseButton: { + + QImage closeButton((const char **)dock_widget_close_xpm); + QColor alphaCorner = closeButton.color(2); + alphaCorner.setAlpha(80); + closeButton.setColor(2, alphaCorner.rgba()); + return QPixmap::fromImage(closeButton); + } + break; + + case SP_DialogDiscardButton: + return QGtkPainter::getIcon(GTK_STOCK_DELETE); + case SP_DialogOkButton: + return QGtkPainter::getIcon(GTK_STOCK_OK); + case SP_DialogCancelButton: + return QGtkPainter::getIcon(GTK_STOCK_CANCEL); + case SP_DialogYesButton: + return QGtkPainter::getIcon(GTK_STOCK_YES); + case SP_DialogNoButton: + return QGtkPainter::getIcon(GTK_STOCK_NO); + case SP_DialogOpenButton: + return QGtkPainter::getIcon(GTK_STOCK_OPEN); + case SP_DialogCloseButton: + return QGtkPainter::getIcon(GTK_STOCK_CLOSE); + case SP_DialogApplyButton: + return QGtkPainter::getIcon(GTK_STOCK_APPLY); + case SP_DialogSaveButton: + return QGtkPainter::getIcon(GTK_STOCK_SAVE); + case SP_MessageBoxWarning: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxQuestion: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxInformation: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxCritical: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG); + default: + return QCleanlooksStyle::standardPixmap(sp, option, widget); + } + return pixmap; +} + +/*! + \internal +*/ +QIcon QGtkStyle::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + if (!d->isThemeAvailable()) + return QCleanlooksStyle::standardIconImplementation(standardIcon, option, widget); + switch (standardIcon) { + case SP_DialogDiscardButton: + return QGtkPainter::getIcon(GTK_STOCK_DELETE); + case SP_DialogOkButton: + return QGtkPainter::getIcon(GTK_STOCK_OK); + case SP_DialogCancelButton: + return QGtkPainter::getIcon(GTK_STOCK_CANCEL); + case SP_DialogYesButton: + return QGtkPainter::getIcon(GTK_STOCK_YES); + case SP_DialogNoButton: + return QGtkPainter::getIcon(GTK_STOCK_NO); + case SP_DialogOpenButton: + return QGtkPainter::getIcon(GTK_STOCK_OPEN); + case SP_DialogCloseButton: + return QGtkPainter::getIcon(GTK_STOCK_CLOSE); + case SP_DialogApplyButton: + return QGtkPainter::getIcon(GTK_STOCK_APPLY); + case SP_DialogSaveButton: + return QGtkPainter::getIcon(GTK_STOCK_SAVE); + case SP_MessageBoxWarning: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxQuestion: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxInformation: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG); + case SP_MessageBoxCritical: + return QGtkPainter::getIcon(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG); + default: + return QCleanlooksStyle::standardIconImplementation(standardIcon, option, widget); + } +} + + +/*! \reimp */ +QRect QGtkStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + Q_D(const QGtkStyle); + + QRect r = QCleanlooksStyle::subElementRect(element, option, widget); + if (!d->isThemeAvailable()) + return r; + + switch (element) { + case SE_ProgressBarLabel: + case SE_ProgressBarContents: + case SE_ProgressBarGroove: + return option->rect; + case SE_PushButtonContents: + if (!d->gtk_check_version(2, 10, 0)) { + GtkWidget *gtkButton = d->gtkWidget("GtkButton"); + GtkBorder *border = 0; + d->gtk_widget_style_get(gtkButton, "inner-border", &border, NULL); + if (border) { + r = option->rect.adjusted(border->left, border->top, -border->right, -border->bottom); + d->gtk_border_free(border); + } else { + r = option->rect.adjusted(1, 1, -1, -1); + } + r = visualRect(option->direction, option->rect, r); + } + break; + default: + break; + } + + return r; +} + +/*! + \reimp +*/ +QRect QGtkStyle::itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const +{ + return QCleanlooksStyle::itemPixmapRect(r, flags, pixmap); +} + +/*! + \reimp +*/ +void QGtkStyle::drawItemPixmap(QPainter *painter, const QRect &rect, + int alignment, const QPixmap &pixmap) const +{ + QCleanlooksStyle::drawItemPixmap(painter, rect, alignment, pixmap); +} + +/*! + \reimp +*/ +QStyle::SubControl QGtkStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w) const +{ + return QCleanlooksStyle::hitTestComplexControl(cc, opt, pt, w); +} + +/*! + \reimp +*/ +QPixmap QGtkStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + return QCleanlooksStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + +/*! + \reimp +*/ +void QGtkStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const +{ + return QCleanlooksStyle::drawItemText(painter, rect, alignment, pal, enabled, text, textRole); +} + +QT_END_NAMESPACE + +#endif //!defined(QT_NO_STYLE_QGTK) diff --git a/src/gui/styles/qgtkstyle.h b/src/gui/styles/qgtkstyle.h new file mode 100644 index 0000000000..616ce24509 --- /dev/null +++ b/src/gui/styles/qgtkstyle.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGTKSTYLE_H +#define QGTKSTYLE_H + +#include <QtGui/QCleanlooksStyle> +#include <QtGui/QPalette> +#include <QtGui/QFont> +#include <QtGui/QFileDialog> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_GTK) + +class QPainterPath; +class QGtkStylePrivate; + +class Q_GUI_EXPORT QGtkStyle : public QCleanlooksStyle +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGtkStyle) + +public: + QGtkStyle(); + QGtkStyle(QGtkStylePrivate &dd); + + ~QGtkStyle(); + + QPalette standardPalette() const; + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + void drawControl(ControlElement control, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, + const QPixmap &pixmap) const; + void drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const; + + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *option, + const QWidget *widget, QStyleHintReturn *returnData) const; + + QStyle::SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w) const; + + QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const; + QRect subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *w) const; + QRect itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const; + + + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *option, + const QWidget *widget) const; + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + + void polish(QWidget *widget); + void polish(QApplication *app); + void polish(QPalette &palette); + + void unpolish(QWidget *widget); + void unpolish(QApplication *app); + + static bool getGConfBool(const QString &key, bool fallback = 0); + static QString getGConfString(const QString &key, const QString &fallback = QString()); + + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; +}; + +#endif //!defined(QT_NO_STYLE_QGTK) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QGTKSTYLE_H diff --git a/src/gui/styles/qgtkstyle_p.cpp b/src/gui/styles/qgtkstyle_p.cpp new file mode 100644 index 0000000000..d7c53c9a91 --- /dev/null +++ b/src/gui/styles/qgtkstyle_p.cpp @@ -0,0 +1,1146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgtkstyle_p.h" + +// This file is responsible for resolving all GTK functions we use +// dynamically. This is done to avoid link-time dependancy on GTK +// as well as crashes occurring due to usage of the GTK_QT engines +// +// Additionally we create a map of common GTK widgets that we can pass +// to the GTK theme engine as many engines resort to querying the +// actual widget pointers for details that are not covered by the +// state flags + +#include <QtCore/qglobal.h> +#if !defined(QT_NO_STYLE_GTK) + +#include <QtCore/QEvent> +#include <QtCore/QFile> +#include <QtCore/QStringList> +#include <QtCore/QTextStream> +#include <QtCore/QHash> +#include <QtCore/QUrl> +#include <QtCore/QLibrary> +#include <QtCore/QDebug> + +#include <private/qapplication_p.h> +#include <private/qiconloader_p.h> + +#include <QtGui/QMenu> +#include <QtGui/QStyle> +#include <QtGui/QApplication> +#include <QtGui/QPixmapCache> +#include <QtGui/QStatusBar> +#include <QtGui/QMenuBar> +#include <QtGui/QToolBar> +#include <QtGui/QToolButton> +#include <QtGui/QX11Info> + +#include <private/qt_x11_p.h> + +QT_BEGIN_NAMESPACE + +static bool displayDepth = -1; +Q_GLOBAL_STATIC(QGtkStyleUpdateScheduler, styleScheduler) + +Ptr_gtk_container_forall QGtkStylePrivate::gtk_container_forall = 0; +Ptr_gtk_init QGtkStylePrivate::gtk_init = 0; +Ptr_gtk_style_attach QGtkStylePrivate::gtk_style_attach = 0; +Ptr_gtk_window_new QGtkStylePrivate::gtk_window_new = 0; +Ptr_gtk_widget_destroy QGtkStylePrivate::gtk_widget_destroy = 0; +Ptr_gtk_widget_realize QGtkStylePrivate::gtk_widget_realize = 0; +Ptr_gtk_widget_set_default_direction QGtkStylePrivate::gtk_widget_set_default_direction = 0; +Ptr_gtk_widget_modify_color QGtkStylePrivate::gtk_widget_modify_fg = 0; +Ptr_gtk_widget_modify_color QGtkStylePrivate::gtk_widget_modify_bg = 0; +Ptr_gtk_arrow_new QGtkStylePrivate::gtk_arrow_new = 0; +Ptr_gtk_menu_item_new_with_label QGtkStylePrivate::gtk_menu_item_new_with_label = 0; +Ptr_gtk_check_menu_item_new_with_label QGtkStylePrivate::gtk_check_menu_item_new_with_label = 0; +Ptr_gtk_menu_bar_new QGtkStylePrivate::gtk_menu_bar_new = 0; +Ptr_gtk_menu_new QGtkStylePrivate::gtk_menu_new = 0; +Ptr_gtk_button_new QGtkStylePrivate::gtk_button_new = 0; +Ptr_gtk_tool_button_new QGtkStylePrivate::gtk_tool_button_new = 0; +Ptr_gtk_hbutton_box_new QGtkStylePrivate::gtk_hbutton_box_new = 0; +Ptr_gtk_check_button_new QGtkStylePrivate::gtk_check_button_new = 0; +Ptr_gtk_radio_button_new QGtkStylePrivate::gtk_radio_button_new = 0; +Ptr_gtk_spin_button_new QGtkStylePrivate::gtk_spin_button_new = 0; +Ptr_gtk_frame_new QGtkStylePrivate::gtk_frame_new = 0; +Ptr_gtk_expander_new QGtkStylePrivate::gtk_expander_new = 0; +Ptr_gtk_statusbar_new QGtkStylePrivate::gtk_statusbar_new = 0; +Ptr_gtk_entry_new QGtkStylePrivate::gtk_entry_new = 0; +Ptr_gtk_hscale_new QGtkStylePrivate::gtk_hscale_new = 0; +Ptr_gtk_vscale_new QGtkStylePrivate::gtk_vscale_new = 0; +Ptr_gtk_hscrollbar_new QGtkStylePrivate::gtk_hscrollbar_new = 0; +Ptr_gtk_vscrollbar_new QGtkStylePrivate::gtk_vscrollbar_new = 0; +Ptr_gtk_scrolled_window_new QGtkStylePrivate::gtk_scrolled_window_new = 0; +Ptr_gtk_notebook_new QGtkStylePrivate::gtk_notebook_new = 0; +Ptr_gtk_toolbar_new QGtkStylePrivate::gtk_toolbar_new = 0; +Ptr_gtk_toolbar_insert QGtkStylePrivate::gtk_toolbar_insert = 0; +Ptr_gtk_separator_tool_item_new QGtkStylePrivate::gtk_separator_tool_item_new = 0; +Ptr_gtk_tree_view_new QGtkStylePrivate::gtk_tree_view_new = 0; +Ptr_gtk_combo_box_new QGtkStylePrivate::gtk_combo_box_new = 0; +Ptr_gtk_combo_box_entry_new QGtkStylePrivate::gtk_combo_box_entry_new = 0; +Ptr_gtk_progress_bar_new QGtkStylePrivate::gtk_progress_bar_new = 0; +Ptr_gtk_container_add QGtkStylePrivate::gtk_container_add = 0; +Ptr_gtk_menu_shell_append QGtkStylePrivate::gtk_menu_shell_append = 0; +Ptr_gtk_progress_configure QGtkStylePrivate::gtk_progress_configure = 0; +Ptr_gtk_range_get_adjustment QGtkStylePrivate::gtk_range_get_adjustment = 0; +Ptr_gtk_range_set_adjustment QGtkStylePrivate::gtk_range_set_adjustment = 0; +Ptr_gtk_range_set_inverted QGtkStylePrivate::gtk_range_set_inverted = 0; +Ptr_gtk_icon_factory_lookup_default QGtkStylePrivate::gtk_icon_factory_lookup_default = 0; +Ptr_gtk_icon_theme_get_default QGtkStylePrivate::gtk_icon_theme_get_default = 0; +Ptr_gtk_widget_style_get QGtkStylePrivate::gtk_widget_style_get = 0; +Ptr_gtk_icon_set_render_icon QGtkStylePrivate::gtk_icon_set_render_icon = 0; +Ptr_gtk_fixed_new QGtkStylePrivate::gtk_fixed_new = 0; +Ptr_gtk_tree_view_column_new QGtkStylePrivate::gtk_tree_view_column_new = 0; +Ptr_gtk_tree_view_get_column QGtkStylePrivate::gtk_tree_view_get_column = 0; +Ptr_gtk_tree_view_append_column QGtkStylePrivate::gtk_tree_view_append_column = 0; +Ptr_gtk_paint_check QGtkStylePrivate::gtk_paint_check = 0; +Ptr_gtk_paint_box QGtkStylePrivate::gtk_paint_box = 0; +Ptr_gtk_paint_box_gap QGtkStylePrivate::gtk_paint_box_gap = 0; +Ptr_gtk_paint_flat_box QGtkStylePrivate::gtk_paint_flat_box = 0; +Ptr_gtk_paint_option QGtkStylePrivate::gtk_paint_option = 0; +Ptr_gtk_paint_extension QGtkStylePrivate::gtk_paint_extension = 0; +Ptr_gtk_paint_slider QGtkStylePrivate::gtk_paint_slider = 0; +Ptr_gtk_paint_shadow QGtkStylePrivate::gtk_paint_shadow = 0; +Ptr_gtk_paint_resize_grip QGtkStylePrivate::gtk_paint_resize_grip = 0; +Ptr_gtk_paint_focus QGtkStylePrivate::gtk_paint_focus = 0; +Ptr_gtk_paint_arrow QGtkStylePrivate::gtk_paint_arrow = 0; +Ptr_gtk_paint_handle QGtkStylePrivate::gtk_paint_handle = 0; +Ptr_gtk_paint_expander QGtkStylePrivate::gtk_paint_expander = 0; +Ptr_gtk_adjustment_configure QGtkStylePrivate::gtk_adjustment_configure = 0; +Ptr_gtk_adjustment_new QGtkStylePrivate::gtk_adjustment_new = 0; +Ptr_gtk_paint_hline QGtkStylePrivate::gtk_paint_hline = 0; +Ptr_gtk_paint_vline QGtkStylePrivate::gtk_paint_vline = 0; +Ptr_gtk_menu_item_set_submenu QGtkStylePrivate::gtk_menu_item_set_submenu = 0; +Ptr_gtk_settings_get_default QGtkStylePrivate::gtk_settings_get_default = 0; +Ptr_gtk_separator_menu_item_new QGtkStylePrivate::gtk_separator_menu_item_new = 0; +Ptr_gtk_widget_size_allocate QGtkStylePrivate::gtk_widget_size_allocate = 0; +Ptr_gtk_widget_size_request QGtkStylePrivate::gtk_widget_size_request = 0; +Ptr_gtk_widget_set_direction QGtkStylePrivate::gtk_widget_set_direction = 0; +Ptr_gtk_widget_path QGtkStylePrivate::gtk_widget_path = 0; +Ptr_gtk_container_get_type QGtkStylePrivate::gtk_container_get_type = 0; +Ptr_gtk_window_get_type QGtkStylePrivate::gtk_window_get_type = 0; +Ptr_gtk_widget_get_type QGtkStylePrivate::gtk_widget_get_type = 0; +Ptr_gtk_rc_get_style_by_paths QGtkStylePrivate::gtk_rc_get_style_by_paths = 0; +Ptr_gtk_check_version QGtkStylePrivate::gtk_check_version = 0; +Ptr_gtk_border_free QGtkStylePrivate::gtk_border_free = 0; +Ptr_pango_font_description_get_size QGtkStylePrivate::pango_font_description_get_size = 0; +Ptr_pango_font_description_get_weight QGtkStylePrivate::pango_font_description_get_weight = 0; +Ptr_pango_font_description_get_family QGtkStylePrivate::pango_font_description_get_family = 0; +Ptr_pango_font_description_get_style QGtkStylePrivate::pango_font_description_get_style = 0; + +Ptr_gtk_file_filter_new QGtkStylePrivate::gtk_file_filter_new = 0; +Ptr_gtk_file_filter_set_name QGtkStylePrivate::gtk_file_filter_set_name = 0; +Ptr_gtk_file_filter_add_pattern QGtkStylePrivate::gtk_file_filter_add_pattern = 0; +Ptr_gtk_file_chooser_add_filter QGtkStylePrivate::gtk_file_chooser_add_filter = 0; +Ptr_gtk_file_chooser_set_filter QGtkStylePrivate::gtk_file_chooser_set_filter = 0; +Ptr_gtk_file_chooser_get_filter QGtkStylePrivate::gtk_file_chooser_get_filter = 0; +Ptr_gtk_file_chooser_dialog_new QGtkStylePrivate::gtk_file_chooser_dialog_new = 0; +Ptr_gtk_file_chooser_set_current_folder QGtkStylePrivate::gtk_file_chooser_set_current_folder = 0; +Ptr_gtk_file_chooser_get_filename QGtkStylePrivate::gtk_file_chooser_get_filename = 0; +Ptr_gtk_file_chooser_get_filenames QGtkStylePrivate::gtk_file_chooser_get_filenames = 0; +Ptr_gtk_file_chooser_set_current_name QGtkStylePrivate::gtk_file_chooser_set_current_name = 0; +Ptr_gtk_dialog_run QGtkStylePrivate::gtk_dialog_run = 0; +Ptr_gtk_file_chooser_set_filename QGtkStylePrivate::gtk_file_chooser_set_filename = 0; + +Ptr_gdk_pixbuf_get_pixels QGtkStylePrivate::gdk_pixbuf_get_pixels = 0; +Ptr_gdk_pixbuf_get_width QGtkStylePrivate::gdk_pixbuf_get_width = 0; +Ptr_gdk_pixbuf_get_height QGtkStylePrivate::gdk_pixbuf_get_height = 0; +Ptr_gdk_pixmap_new QGtkStylePrivate::gdk_pixmap_new = 0; +Ptr_gdk_pixbuf_new QGtkStylePrivate::gdk_pixbuf_new = 0; +Ptr_gdk_pixbuf_get_from_drawable QGtkStylePrivate::gdk_pixbuf_get_from_drawable = 0; +Ptr_gdk_draw_rectangle QGtkStylePrivate::gdk_draw_rectangle = 0; +Ptr_gdk_pixbuf_unref QGtkStylePrivate::gdk_pixbuf_unref = 0; +Ptr_gdk_drawable_unref QGtkStylePrivate::gdk_drawable_unref = 0; +Ptr_gdk_drawable_get_depth QGtkStylePrivate::gdk_drawable_get_depth = 0; +Ptr_gdk_color_free QGtkStylePrivate::gdk_color_free = 0; +Ptr_gdk_x11_window_set_user_time QGtkStylePrivate::gdk_x11_window_set_user_time = 0; +Ptr_gdk_x11_drawable_get_xid QGtkStylePrivate::gdk_x11_drawable_get_xid = 0; +Ptr_gdk_x11_drawable_get_xdisplay QGtkStylePrivate::gdk_x11_drawable_get_xdisplay = 0; + +Ptr_gconf_client_get_default QGtkStylePrivate::gconf_client_get_default = 0; +Ptr_gconf_client_get_string QGtkStylePrivate::gconf_client_get_string = 0; +Ptr_gconf_client_get_bool QGtkStylePrivate::gconf_client_get_bool = 0; + +Ptr_gnome_icon_lookup_sync QGtkStylePrivate::gnome_icon_lookup_sync = 0; +Ptr_gnome_vfs_init QGtkStylePrivate::gnome_vfs_init = 0; + +typedef int (*x11ErrorHandler)(Display*, XErrorEvent*); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QGtkStylePrivate*); + +QT_BEGIN_NAMESPACE + +static void gtkStyleSetCallback(GtkWidget*) +{ + qRegisterMetaType<QGtkStylePrivate *>(); + + // We have to let this function return and complete the event + // loop to ensure that all gtk widgets have been styled before + // updating + QMetaObject::invokeMethod(styleScheduler(), "updateTheme", Qt::QueuedConnection); +} + +static void update_toolbar_style(GtkWidget *gtkToolBar, GParamSpec *, gpointer) +{ + GtkToolbarStyle toolbar_style = GTK_TOOLBAR_ICONS; + g_object_get(gtkToolBar, "toolbar-style", &toolbar_style, NULL); + QWidgetList widgets = QApplication::allWidgets(); + for (int i = 0; i < widgets.size(); ++i) { + QWidget *widget = widgets.at(i); + if (qobject_cast<QToolButton*>(widget)) { + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(widget, &event); + } + } +} + +static QHashableLatin1Literal classPath(GtkWidget *widget) +{ + char *class_path; + QGtkStylePrivate::gtk_widget_path (widget, NULL, &class_path, NULL); + + char *copy = class_path; + if (strncmp(copy, "GtkWindow.", 10) == 0) + copy += 10; + if (strncmp(copy, "GtkFixed.", 9) == 0) + copy += 9; + + copy = strdup(copy); + + g_free(class_path); + + return QHashableLatin1Literal::fromData(copy); +} + + + +bool QGtkStyleFilter::eventFilter(QObject *obj, QEvent *e) +{ + if (e->type() == QEvent::ApplicationPaletteChange) { + // Only do this the first time since this will also + // generate applicationPaletteChange events + if (!qt_app_palettes_hash() || qt_app_palettes_hash()->isEmpty()) { + stylePrivate->applyCustomPaletteHash(); + } + } + return QObject::eventFilter(obj, e); +} + +QList<QGtkStylePrivate *> QGtkStylePrivate::instances; +QGtkStylePrivate::WidgetMap *QGtkStylePrivate::widgetMap = 0; + +QGtkStylePrivate::QGtkStylePrivate() + : QCleanlooksStylePrivate() + , filter(this) +{ + instances.append(this); +} + +QGtkStylePrivate::~QGtkStylePrivate() +{ + instances.removeOne(this); +} + +void QGtkStylePrivate::init() +{ + resolveGtk(); + initGtkWidgets(); +} + +GtkWidget* QGtkStylePrivate::gtkWidget(const QHashableLatin1Literal &path) +{ + GtkWidget *widget = gtkWidgetMap()->value(path); + if (!widget) { + // Theme might have rearranged widget internals + widget = gtkWidgetMap()->value(path); + } + return widget; +} + +GtkStyle* QGtkStylePrivate::gtkStyle(const QHashableLatin1Literal &path) +{ + if (GtkWidget *w = gtkWidgetMap()->value(path)) + return w->style; + return 0; +} + +/*! \internal + * Get references to gtk functions after we dynamically load the library. + */ +void QGtkStylePrivate::resolveGtk() const +{ + // enforce the "0" suffix, so we'll open libgtk-x11-2.0.so.0 + QLibrary libgtk(QLS("gtk-x11-2.0"), 0, 0); + + gtk_init = (Ptr_gtk_init)libgtk.resolve("gtk_init"); + gtk_window_new = (Ptr_gtk_window_new)libgtk.resolve("gtk_window_new"); + gtk_style_attach = (Ptr_gtk_style_attach)libgtk.resolve("gtk_style_attach"); + gtk_widget_destroy = (Ptr_gtk_widget_destroy)libgtk.resolve("gtk_widget_destroy"); + gtk_widget_realize = (Ptr_gtk_widget_realize)libgtk.resolve("gtk_widget_realize"); + + gtk_file_chooser_set_current_folder = (Ptr_gtk_file_chooser_set_current_folder)libgtk.resolve("gtk_file_chooser_set_current_folder"); + gtk_file_filter_new = (Ptr_gtk_file_filter_new)libgtk.resolve("gtk_file_filter_new"); + gtk_file_filter_set_name = (Ptr_gtk_file_filter_set_name)libgtk.resolve("gtk_file_filter_set_name"); + gtk_file_filter_add_pattern = (Ptr_gtk_file_filter_add_pattern)libgtk.resolve("gtk_file_filter_add_pattern"); + gtk_file_chooser_add_filter = (Ptr_gtk_file_chooser_add_filter)libgtk.resolve("gtk_file_chooser_add_filter"); + gtk_file_chooser_set_filter = (Ptr_gtk_file_chooser_set_filter)libgtk.resolve("gtk_file_chooser_set_filter"); + gtk_file_chooser_get_filter = (Ptr_gtk_file_chooser_get_filter)libgtk.resolve("gtk_file_chooser_get_filter"); + gtk_file_chooser_dialog_new = (Ptr_gtk_file_chooser_dialog_new)libgtk.resolve("gtk_file_chooser_dialog_new"); + gtk_file_chooser_set_current_folder = (Ptr_gtk_file_chooser_set_current_folder)libgtk.resolve("gtk_file_chooser_set_current_folder"); + gtk_file_chooser_get_filename = (Ptr_gtk_file_chooser_get_filename)libgtk.resolve("gtk_file_chooser_get_filename"); + gtk_file_chooser_get_filenames = (Ptr_gtk_file_chooser_get_filenames)libgtk.resolve("gtk_file_chooser_get_filenames"); + gtk_file_chooser_set_current_name = (Ptr_gtk_file_chooser_set_current_name)libgtk.resolve("gtk_file_chooser_set_current_name"); + gtk_dialog_run = (Ptr_gtk_dialog_run)libgtk.resolve("gtk_dialog_run"); + gtk_file_chooser_set_filename = (Ptr_gtk_file_chooser_set_filename)libgtk.resolve("gtk_file_chooser_set_filename"); + + gdk_pixbuf_get_pixels = (Ptr_gdk_pixbuf_get_pixels)libgtk.resolve("gdk_pixbuf_get_pixels"); + gdk_pixbuf_get_width = (Ptr_gdk_pixbuf_get_width)libgtk.resolve("gdk_pixbuf_get_width"); + gdk_pixbuf_get_height = (Ptr_gdk_pixbuf_get_height)libgtk.resolve("gdk_pixbuf_get_height"); + gdk_pixmap_new = (Ptr_gdk_pixmap_new)libgtk.resolve("gdk_pixmap_new"); + gdk_pixbuf_new = (Ptr_gdk_pixbuf_new)libgtk.resolve("gdk_pixbuf_new"); + gdk_pixbuf_get_from_drawable = (Ptr_gdk_pixbuf_get_from_drawable)libgtk.resolve("gdk_pixbuf_get_from_drawable"); + gdk_draw_rectangle = (Ptr_gdk_draw_rectangle)libgtk.resolve("gdk_draw_rectangle"); + gdk_pixbuf_unref = (Ptr_gdk_pixbuf_unref)libgtk.resolve("gdk_pixbuf_unref"); + gdk_drawable_unref = (Ptr_gdk_drawable_unref)libgtk.resolve("gdk_drawable_unref"); + gdk_drawable_get_depth = (Ptr_gdk_drawable_get_depth)libgtk.resolve("gdk_drawable_get_depth"); + gdk_color_free = (Ptr_gdk_color_free)libgtk.resolve("gdk_color_free"); + gdk_x11_window_set_user_time = (Ptr_gdk_x11_window_set_user_time)libgtk.resolve("gdk_x11_window_set_user_time"); + gdk_x11_drawable_get_xid = (Ptr_gdk_x11_drawable_get_xid)libgtk.resolve("gdk_x11_drawable_get_xid"); + gdk_x11_drawable_get_xdisplay = (Ptr_gdk_x11_drawable_get_xdisplay)libgtk.resolve("gdk_x11_drawable_get_xdisplay"); + + gtk_widget_set_default_direction = (Ptr_gtk_widget_set_default_direction)libgtk.resolve("gtk_widget_set_default_direction"); + gtk_widget_modify_fg = (Ptr_gtk_widget_modify_color)libgtk.resolve("gtk_widget_modify_fg"); + gtk_widget_modify_bg = (Ptr_gtk_widget_modify_color)libgtk.resolve("gtk_widget_modify_bg"); + gtk_arrow_new = (Ptr_gtk_arrow_new)libgtk.resolve("gtk_arrow_new"); + gtk_menu_item_new_with_label = (Ptr_gtk_menu_item_new_with_label)libgtk.resolve("gtk_menu_item_new_with_label"); + gtk_check_menu_item_new_with_label = (Ptr_gtk_check_menu_item_new_with_label)libgtk.resolve("gtk_check_menu_item_new_with_label"); + gtk_menu_bar_new = (Ptr_gtk_menu_bar_new)libgtk.resolve("gtk_menu_bar_new"); + gtk_menu_new = (Ptr_gtk_menu_new)libgtk.resolve("gtk_menu_new"); + gtk_toolbar_new = (Ptr_gtk_toolbar_new)libgtk.resolve("gtk_toolbar_new"); + gtk_separator_tool_item_new = (Ptr_gtk_separator_tool_item_new)libgtk.resolve("gtk_separator_tool_item_new"); + gtk_toolbar_insert = (Ptr_gtk_toolbar_insert)libgtk.resolve("gtk_toolbar_insert"); + gtk_button_new = (Ptr_gtk_button_new)libgtk.resolve("gtk_button_new"); + gtk_tool_button_new = (Ptr_gtk_tool_button_new)libgtk.resolve("gtk_tool_button_new"); + gtk_hbutton_box_new = (Ptr_gtk_hbutton_box_new)libgtk.resolve("gtk_hbutton_box_new"); + gtk_check_button_new = (Ptr_gtk_check_button_new)libgtk.resolve("gtk_check_button_new"); + gtk_radio_button_new = (Ptr_gtk_radio_button_new)libgtk.resolve("gtk_radio_button_new"); + gtk_notebook_new = (Ptr_gtk_notebook_new)libgtk.resolve("gtk_notebook_new"); + gtk_progress_bar_new = (Ptr_gtk_progress_bar_new)libgtk.resolve("gtk_progress_bar_new"); + gtk_spin_button_new = (Ptr_gtk_spin_button_new)libgtk.resolve("gtk_spin_button_new"); + gtk_hscale_new = (Ptr_gtk_hscale_new)libgtk.resolve("gtk_hscale_new"); + gtk_vscale_new = (Ptr_gtk_vscale_new)libgtk.resolve("gtk_vscale_new"); + gtk_hscrollbar_new = (Ptr_gtk_hscrollbar_new)libgtk.resolve("gtk_hscrollbar_new"); + gtk_vscrollbar_new = (Ptr_gtk_vscrollbar_new)libgtk.resolve("gtk_vscrollbar_new"); + gtk_scrolled_window_new = (Ptr_gtk_scrolled_window_new)libgtk.resolve("gtk_scrolled_window_new"); + gtk_menu_shell_append = (Ptr_gtk_menu_shell_append)libgtk.resolve("gtk_menu_shell_append"); + gtk_entry_new = (Ptr_gtk_entry_new)libgtk.resolve("gtk_entry_new"); + gtk_tree_view_new = (Ptr_gtk_tree_view_new)libgtk.resolve("gtk_tree_view_new"); + gtk_combo_box_new = (Ptr_gtk_combo_box_new)libgtk.resolve("gtk_combo_box_new"); + gtk_progress_configure = (Ptr_gtk_progress_configure)libgtk.resolve("gtk_progress_configure"); + gtk_range_get_adjustment = (Ptr_gtk_range_get_adjustment)libgtk.resolve("gtk_range_get_adjustment"); + gtk_range_set_adjustment = (Ptr_gtk_range_set_adjustment)libgtk.resolve("gtk_range_set_adjustment"); + gtk_range_set_inverted = (Ptr_gtk_range_set_inverted)libgtk.resolve("gtk_range_set_inverted"); + gtk_container_add = (Ptr_gtk_container_add)libgtk.resolve("gtk_container_add"); + gtk_icon_factory_lookup_default = (Ptr_gtk_icon_factory_lookup_default)libgtk.resolve("gtk_icon_factory_lookup_default"); + gtk_icon_theme_get_default = (Ptr_gtk_icon_theme_get_default)libgtk.resolve("gtk_icon_theme_get_default"); + gtk_widget_style_get = (Ptr_gtk_widget_style_get)libgtk.resolve("gtk_widget_style_get"); + gtk_icon_set_render_icon = (Ptr_gtk_icon_set_render_icon)libgtk.resolve("gtk_icon_set_render_icon"); + gtk_fixed_new = (Ptr_gtk_fixed_new)libgtk.resolve("gtk_fixed_new"); + gtk_tree_view_column_new = (Ptr_gtk_tree_view_column_new)libgtk.resolve("gtk_tree_view_column_new"); + gtk_tree_view_append_column= (Ptr_gtk_tree_view_append_column )libgtk.resolve("gtk_tree_view_append_column"); + gtk_tree_view_get_column = (Ptr_gtk_tree_view_get_column )libgtk.resolve("gtk_tree_view_get_column"); + gtk_paint_check = (Ptr_gtk_paint_check)libgtk.resolve("gtk_paint_check"); + gtk_paint_box = (Ptr_gtk_paint_box)libgtk.resolve("gtk_paint_box"); + gtk_paint_flat_box = (Ptr_gtk_paint_flat_box)libgtk.resolve("gtk_paint_flat_box"); + gtk_paint_check = (Ptr_gtk_paint_check)libgtk.resolve("gtk_paint_check"); + gtk_paint_box = (Ptr_gtk_paint_box)libgtk.resolve("gtk_paint_box"); + gtk_paint_resize_grip = (Ptr_gtk_paint_resize_grip)libgtk.resolve("gtk_paint_resize_grip"); + gtk_paint_focus = (Ptr_gtk_paint_focus)libgtk.resolve("gtk_paint_focus"); + gtk_paint_shadow = (Ptr_gtk_paint_shadow)libgtk.resolve("gtk_paint_shadow"); + gtk_paint_slider = (Ptr_gtk_paint_slider)libgtk.resolve("gtk_paint_slider"); + gtk_paint_expander = (Ptr_gtk_paint_expander)libgtk.resolve("gtk_paint_expander"); + gtk_paint_handle = (Ptr_gtk_paint_handle)libgtk.resolve("gtk_paint_handle"); + gtk_paint_option = (Ptr_gtk_paint_option)libgtk.resolve("gtk_paint_option"); + gtk_paint_arrow = (Ptr_gtk_paint_arrow)libgtk.resolve("gtk_paint_arrow"); + gtk_paint_box_gap = (Ptr_gtk_paint_box_gap)libgtk.resolve("gtk_paint_box_gap"); + gtk_paint_extension = (Ptr_gtk_paint_extension)libgtk.resolve("gtk_paint_extension"); + gtk_paint_hline = (Ptr_gtk_paint_hline)libgtk.resolve("gtk_paint_hline"); + gtk_paint_vline = (Ptr_gtk_paint_vline)libgtk.resolve("gtk_paint_vline"); + gtk_adjustment_configure = (Ptr_gtk_adjustment_configure)libgtk.resolve("gtk_adjustment_configure"); + gtk_adjustment_new = (Ptr_gtk_adjustment_new)libgtk.resolve("gtk_adjustment_new"); + gtk_menu_item_set_submenu = (Ptr_gtk_menu_item_set_submenu)libgtk.resolve("gtk_menu_item_set_submenu"); + gtk_settings_get_default = (Ptr_gtk_settings_get_default)libgtk.resolve("gtk_settings_get_default"); + gtk_separator_menu_item_new = (Ptr_gtk_separator_menu_item_new)libgtk.resolve("gtk_separator_menu_item_new"); + gtk_frame_new = (Ptr_gtk_frame_new)libgtk.resolve("gtk_frame_new"); + gtk_expander_new = (Ptr_gtk_expander_new)libgtk.resolve("gtk_expander_new"); + gtk_statusbar_new = (Ptr_gtk_statusbar_new)libgtk.resolve("gtk_statusbar_new"); + gtk_combo_box_entry_new = (Ptr_gtk_combo_box_entry_new)libgtk.resolve("gtk_combo_box_entry_new"); + gtk_container_forall = (Ptr_gtk_container_forall)libgtk.resolve("gtk_container_forall"); + gtk_widget_size_allocate =(Ptr_gtk_widget_size_allocate)libgtk.resolve("gtk_widget_size_allocate"); + gtk_widget_size_request =(Ptr_gtk_widget_size_request)libgtk.resolve("gtk_widget_size_request"); + gtk_widget_set_direction =(Ptr_gtk_widget_set_direction)libgtk.resolve("gtk_widget_set_direction"); + gtk_widget_path =(Ptr_gtk_widget_path)libgtk.resolve("gtk_widget_path"); + gtk_container_get_type =(Ptr_gtk_container_get_type)libgtk.resolve("gtk_container_get_type"); + gtk_window_get_type =(Ptr_gtk_window_get_type)libgtk.resolve("gtk_window_get_type"); + gtk_widget_get_type =(Ptr_gtk_widget_get_type)libgtk.resolve("gtk_widget_get_type"); + + gtk_rc_get_style_by_paths =(Ptr_gtk_rc_get_style_by_paths)libgtk.resolve("gtk_rc_get_style_by_paths"); + gtk_check_version =(Ptr_gtk_check_version)libgtk.resolve("gtk_check_version"); + gtk_border_free =(Ptr_gtk_border_free)libgtk.resolve("gtk_border_free"); + pango_font_description_get_size = (Ptr_pango_font_description_get_size)libgtk.resolve("pango_font_description_get_size"); + pango_font_description_get_weight = (Ptr_pango_font_description_get_weight)libgtk.resolve("pango_font_description_get_weight"); + pango_font_description_get_family = (Ptr_pango_font_description_get_family)libgtk.resolve("pango_font_description_get_family"); + pango_font_description_get_style = (Ptr_pango_font_description_get_style)libgtk.resolve("pango_font_description_get_style"); + + gnome_icon_lookup_sync = (Ptr_gnome_icon_lookup_sync)QLibrary::resolve(QLS("gnomeui-2"), 0, "gnome_icon_lookup_sync"); + gnome_vfs_init= (Ptr_gnome_vfs_init)QLibrary::resolve(QLS("gnomevfs-2"), 0, "gnome_vfs_init"); +} + +/* \internal + * Initializes a number of gtk menu widgets. + * The widgets are cached. + */ +void QGtkStylePrivate::initGtkMenu() const +{ + // Create menubar + GtkWidget *gtkMenuBar = QGtkStylePrivate::gtk_menu_bar_new(); + setupGtkWidget(gtkMenuBar); + + GtkWidget *gtkMenuBarItem = QGtkStylePrivate::gtk_menu_item_new_with_label("X"); + gtk_menu_shell_append((GtkMenuShell*)(gtkMenuBar), gtkMenuBarItem); + gtk_widget_realize(gtkMenuBarItem); + + // Create menu + GtkWidget *gtkMenu = QGtkStylePrivate::gtk_menu_new(); + gtk_menu_item_set_submenu((GtkMenuItem*)(gtkMenuBarItem), gtkMenu); + gtk_widget_realize(gtkMenu); + + GtkWidget *gtkMenuItem = QGtkStylePrivate::gtk_menu_item_new_with_label("X"); + gtk_menu_shell_append((GtkMenuShell*)gtkMenu, gtkMenuItem); + gtk_widget_realize(gtkMenuItem); + + GtkWidget *gtkCheckMenuItem = QGtkStylePrivate::gtk_check_menu_item_new_with_label("X"); + gtk_menu_shell_append((GtkMenuShell*)gtkMenu, gtkCheckMenuItem); + gtk_widget_realize(gtkCheckMenuItem); + + GtkWidget *gtkMenuSeparator = QGtkStylePrivate::gtk_separator_menu_item_new(); + gtk_menu_shell_append((GtkMenuShell*)gtkMenu, gtkMenuSeparator); + + addAllSubWidgets(gtkMenuBar); + addAllSubWidgets(gtkMenu); +} + + +void QGtkStylePrivate::initGtkTreeview() const +{ + GtkWidget *gtkTreeView = gtk_tree_view_new(); + gtk_tree_view_append_column((GtkTreeView*)gtkTreeView, gtk_tree_view_column_new()); + gtk_tree_view_append_column((GtkTreeView*)gtkTreeView, gtk_tree_view_column_new()); + gtk_tree_view_append_column((GtkTreeView*)gtkTreeView, gtk_tree_view_column_new()); + addWidget(gtkTreeView); +} + + +/* \internal + * Initializes a number of gtk widgets that we can later on use to determine some of our styles. + * The widgets are cached. + */ +void QGtkStylePrivate::initGtkWidgets() const +{ + // From gtkmain.c + uid_t ruid = getuid (); + uid_t rgid = getgid (); + uid_t euid = geteuid (); + uid_t egid = getegid (); + if (ruid != euid || rgid != egid) { + qWarning("\nThis process is currently running setuid or setgid.\nGTK+ does not allow this " + "therefore Qt cannot use the GTK+ integration.\nTry launching your app using \'gksudo\', " + "\'kdesudo\' or a similar tool.\n\n" + "See http://www.gtk.org/setuid.html for more information.\n"); + return; + } + + static QString themeName; + if (!gtkWidgetMap()->contains("GtkWindow") && themeName.isEmpty()) { + themeName = getThemeName(); + + if (themeName.isEmpty()) { + qWarning("QGtkStyle was unable to detect the current GTK+ theme."); + return; + } else if (themeName == QLS("Qt") || themeName == QLS("Qt4")) { + // Due to namespace conflicts with Qt3 and obvious recursion with Qt4, + // we cannot support the GTK_Qt Gtk engine + qWarning("QGtkStyle cannot be used together with the GTK_Qt engine."); + return; + } + } + + if (QGtkStylePrivate::gtk_init) { + // Gtk will set the Qt error handler so we have to reset it afterwards + x11ErrorHandler qt_x_errhandler = XSetErrorHandler(0); + QGtkStylePrivate::gtk_init (NULL, NULL); + XSetErrorHandler(qt_x_errhandler); + + // make a window + GtkWidget* gtkWindow = QGtkStylePrivate::gtk_window_new(GTK_WINDOW_POPUP); + QGtkStylePrivate::gtk_widget_realize(gtkWindow); + if (displayDepth == -1) + displayDepth = QGtkStylePrivate::gdk_drawable_get_depth(gtkWindow->window); + QHashableLatin1Literal widgetPath = QHashableLatin1Literal::fromData(strdup("GtkWindow")); + removeWidgetFromMap(widgetPath); + gtkWidgetMap()->insert(widgetPath, gtkWindow); + + + // Make all other widgets. respect the text direction + if (qApp->layoutDirection() == Qt::RightToLeft) + QGtkStylePrivate::gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL); + + if (!gtkWidgetMap()->contains("GtkButton")) { + GtkWidget *gtkButton = QGtkStylePrivate::gtk_button_new(); + addWidget(gtkButton); + g_signal_connect(gtkButton, "style-set", G_CALLBACK(gtkStyleSetCallback), 0); + addWidget(QGtkStylePrivate::gtk_tool_button_new(NULL, "Qt")); + addWidget(QGtkStylePrivate::gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE)); + addWidget(QGtkStylePrivate::gtk_hbutton_box_new()); + addWidget(QGtkStylePrivate::gtk_check_button_new()); + addWidget(QGtkStylePrivate::gtk_radio_button_new(NULL)); + addWidget(QGtkStylePrivate::gtk_combo_box_new()); + addWidget(QGtkStylePrivate::gtk_combo_box_entry_new()); + addWidget(QGtkStylePrivate::gtk_entry_new()); + addWidget(QGtkStylePrivate::gtk_frame_new(NULL)); + addWidget(QGtkStylePrivate::gtk_expander_new("")); + addWidget(QGtkStylePrivate::gtk_statusbar_new()); + addWidget(QGtkStylePrivate::gtk_hscale_new((GtkAdjustment*)(QGtkStylePrivate::gtk_adjustment_new(1, 0, 1, 0, 0, 0)))); + addWidget(QGtkStylePrivate::gtk_hscrollbar_new(NULL)); + addWidget(QGtkStylePrivate::gtk_scrolled_window_new(NULL, NULL)); + + initGtkMenu(); + addWidget(QGtkStylePrivate::gtk_notebook_new()); + addWidget(QGtkStylePrivate::gtk_progress_bar_new()); + addWidget(QGtkStylePrivate::gtk_spin_button_new((GtkAdjustment*) + (QGtkStylePrivate::gtk_adjustment_new(1, 0, 1, 0, 0, 0)), 0.1, 3)); + GtkWidget *toolbar = gtk_toolbar_new(); + g_signal_connect (toolbar, "notify::toolbar-style", G_CALLBACK (update_toolbar_style), toolbar); + gtk_toolbar_insert((GtkToolbar*)toolbar, gtk_separator_tool_item_new(), -1); + addWidget(toolbar); + initGtkTreeview(); + addWidget(gtk_vscale_new((GtkAdjustment*)(QGtkStylePrivate::gtk_adjustment_new(1, 0, 1, 0, 0, 0)))); + addWidget(gtk_vscrollbar_new(NULL)); + } + else // Rebuild map + { + // When styles change subwidgets can get rearranged + // as with the combo box. We need to update the widget map + // to reflect this; + QHash<QHashableLatin1Literal, GtkWidget*> oldMap = *gtkWidgetMap(); + gtkWidgetMap()->clear(); + QHashIterator<QHashableLatin1Literal, GtkWidget*> it(oldMap); + while (it.hasNext()) { + it.next(); + if (!strchr(it.key().data(), '.')) { + addAllSubWidgets(it.value()); + } + free(const_cast<char *>(it.key().data())); + } + } + } else { + qWarning("QGtkStyle could not resolve GTK. Make sure you have installed the proper libraries."); + } +} + +/*! \internal + * destroys all previously buffered widgets. + */ +void QGtkStylePrivate::cleanupGtkWidgets() +{ + if (!widgetMap) + return; + if (widgetMap->contains("GtkWindow")) // Gtk will destroy all children + gtk_widget_destroy(widgetMap->value("GtkWindow")); + for (QHash<QHashableLatin1Literal, GtkWidget *>::const_iterator it = widgetMap->constBegin(); + it != widgetMap->constEnd(); ++it) + free(const_cast<char *>(it.key().data())); +} + +static bool resolveGConf() +{ + if (!QGtkStylePrivate::gconf_client_get_default) { + QGtkStylePrivate::gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLS("gconf-2"), 4, "gconf_client_get_default"); + QGtkStylePrivate::gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLS("gconf-2"), 4, "gconf_client_get_string"); + QGtkStylePrivate::gconf_client_get_bool = (Ptr_gconf_client_get_bool)QLibrary::resolve(QLS("gconf-2"), 4, "gconf_client_get_bool"); + } + return (QGtkStylePrivate::gconf_client_get_default !=0); +} + +QString QGtkStylePrivate::getGConfString(const QString &value, const QString &fallback) +{ + QString retVal = fallback; + if (resolveGConf()) { + g_type_init(); + GConfClient* client = gconf_client_get_default(); + GError *err = 0; + char *str = gconf_client_get_string(client, qPrintable(value), &err); + if (!err) { + retVal = QString::fromUtf8(str); + g_free(str); + } + g_object_unref(client); + if (err) + g_error_free (err); + } + return retVal; +} + +bool QGtkStylePrivate::getGConfBool(const QString &key, bool fallback) +{ + bool retVal = fallback; + if (resolveGConf()) { + g_type_init(); + GConfClient* client = gconf_client_get_default(); + GError *err = 0; + bool result = gconf_client_get_bool(client, qPrintable(key), &err); + g_object_unref(client); + if (!err) + retVal = result; + else + g_error_free (err); + } + return retVal; +} + +QString QGtkStylePrivate::getThemeName() +{ + QString themeName; + // We try to parse the gtkrc file first + // primarily to avoid resolving Gtk functions if + // the KDE 3 "Qt" style is currently in use + QString rcPaths = QString::fromLocal8Bit(qgetenv("GTK2_RC_FILES")); + if (!rcPaths.isEmpty()) { + QStringList paths = rcPaths.split(QLS(":")); + foreach (const QString &rcPath, paths) { + if (!rcPath.isEmpty()) { + QFile rcFile(rcPath); + if (rcFile.exists() && rcFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&rcFile); + while(!in.atEnd()) { + QString line = in.readLine(); + if (line.contains(QLS("gtk-theme-name"))) { + line = line.right(line.length() - line.indexOf(QLatin1Char('=')) - 1); + line.remove(QLatin1Char('\"')); + line = line.trimmed(); + themeName = line; + break; + } + } + } + } + if (!themeName.isEmpty()) + break; + } + } + + // Fall back to gconf + if (themeName.isEmpty() && resolveGConf()) + themeName = getGConfString(QLS("/desktop/gnome/interface/gtk_theme")); + + return themeName; +} + +// Get size of the arrow controls in a GtkSpinButton +int QGtkStylePrivate::getSpinboxArrowSize() const +{ + const int MIN_ARROW_WIDTH = 6; + GtkWidget *spinButton = gtkWidget("GtkSpinButton"); + GtkStyle *style = spinButton->style; + gint size = pango_font_description_get_size (style->font_desc); + gint arrow_size; + arrow_size = qMax(PANGO_PIXELS (size), MIN_ARROW_WIDTH) + style->xthickness; + arrow_size += arrow_size%2 + 1; + return arrow_size; +} + + +bool QGtkStylePrivate::isKDE4Session() +{ + static int version = -1; + if (version == -1) + version = qgetenv("KDE_SESSION_VERSION").toInt(); + return (version == 4); +} + +void QGtkStylePrivate::applyCustomPaletteHash() +{ + QPalette menuPal = gtkWidgetPalette("GtkMenu"); + GdkColor gdkBg = gtkWidget("GtkMenu")->style->bg[GTK_STATE_NORMAL]; + QColor bgColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8); + menuPal.setBrush(QPalette::Base, bgColor); + menuPal.setBrush(QPalette::Window, bgColor); + qApp->setPalette(menuPal, "QMenu"); + + QPalette toolbarPal = gtkWidgetPalette("GtkToolbar"); + qApp->setPalette(toolbarPal, "QToolBar"); + + QPalette menuBarPal = gtkWidgetPalette("GtkMenuBar"); + qApp->setPalette(menuBarPal, "QMenuBar"); +} + +/*! \internal + * Returns the gtk Widget that should be used to determine text foreground and background colors. +*/ +GtkWidget* QGtkStylePrivate::getTextColorWidget() const +{ + return gtkWidget("GtkEntry"); +} + +void QGtkStylePrivate::setupGtkWidget(GtkWidget* widget) +{ + if (Q_GTK_IS_WIDGET(widget)) { + static GtkWidget* protoLayout = 0; + if (!protoLayout) { + protoLayout = QGtkStylePrivate::gtk_fixed_new(); + QGtkStylePrivate::gtk_container_add((GtkContainer*)(gtkWidgetMap()->value("GtkWindow")), protoLayout); + } + Q_ASSERT(protoLayout); + + if (!widget->parent && !GTK_WIDGET_TOPLEVEL(widget)) + QGtkStylePrivate::gtk_container_add((GtkContainer*)(protoLayout), widget); + QGtkStylePrivate::gtk_widget_realize(widget); + } +} + +void QGtkStylePrivate::removeWidgetFromMap(const QHashableLatin1Literal &path) +{ + WidgetMap *map = gtkWidgetMap(); + WidgetMap::iterator it = map->find(path); + if (it != map->end()) { + free(const_cast<char *>(it.key().data())); + map->erase(it); + } +} + +void QGtkStylePrivate::addWidgetToMap(GtkWidget *widget) +{ + if (Q_GTK_IS_WIDGET(widget)) { + gtk_widget_realize(widget); + QHashableLatin1Literal widgetPath = classPath(widget); + + removeWidgetFromMap(widgetPath); + gtkWidgetMap()->insert(widgetPath, widget); +#ifdef DUMP_GTK_WIDGET_TREE + qWarning("Inserted Gtk Widget: %s", widgetPath.data()); +#endif + } + } + +void QGtkStylePrivate::addAllSubWidgets(GtkWidget *widget, gpointer v) +{ + Q_UNUSED(v); + addWidgetToMap(widget); + if (GTK_CHECK_TYPE ((widget), gtk_container_get_type())) + gtk_container_forall((GtkContainer*)widget, addAllSubWidgets, NULL); +} + +// Updates window/windowtext palette based on the indicated gtk widget +QPalette QGtkStylePrivate::gtkWidgetPalette(const QHashableLatin1Literal >kWidgetName) const +{ + GtkWidget *gtkWidget = QGtkStylePrivate::gtkWidget(gtkWidgetName); + Q_ASSERT(gtkWidget); + QPalette pal = QApplication::palette(); + GdkColor gdkBg = gtkWidget->style->bg[GTK_STATE_NORMAL]; + GdkColor gdkText = gtkWidget->style->fg[GTK_STATE_NORMAL]; + GdkColor gdkDisabledText = gtkWidget->style->fg[GTK_STATE_INSENSITIVE]; + QColor bgColor(gdkBg.red>>8, gdkBg.green>>8, gdkBg.blue>>8); + QColor textColor(gdkText.red>>8, gdkText.green>>8, gdkText.blue>>8); + QColor disabledTextColor(gdkDisabledText.red>>8, gdkDisabledText.green>>8, gdkDisabledText.blue>>8); + pal.setBrush(QPalette::Window, bgColor); + pal.setBrush(QPalette::Button, bgColor); + pal.setBrush(QPalette::All, QPalette::WindowText, textColor); + pal.setBrush(QPalette::Disabled, QPalette::WindowText, disabledTextColor); + pal.setBrush(QPalette::All, QPalette::ButtonText, textColor); + pal.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledTextColor); + return pal; +} + + +void QGtkStyleUpdateScheduler::updateTheme() +{ + static QString oldTheme(QLS("qt_not_set")); + QPixmapCache::clear(); + + QFont font = QGtkStylePrivate::getThemeFont(); + if (QApplication::font() != font) + qApp->setFont(font); + + if (oldTheme != QGtkStylePrivate::getThemeName()) { + oldTheme = QGtkStylePrivate::getThemeName(); + QPalette newPalette = qApp->style()->standardPalette(); + QApplicationPrivate::setSystemPalette(newPalette); + QApplication::setPalette(newPalette); + if (!QGtkStylePrivate::instances.isEmpty()) { + QGtkStylePrivate::instances.last()->initGtkWidgets(); + QGtkStylePrivate::instances.last()->applyCustomPaletteHash(); + } + QList<QWidget*> widgets = QApplication::allWidgets(); + // Notify all widgets that size metrics might have changed + foreach (QWidget *widget, widgets) { + QEvent e(QEvent::StyleChange); + QApplication::sendEvent(widget, &e); + } + } + QIconLoader::instance()->updateSystemTheme(); +} + +void QGtkStylePrivate::addWidget(GtkWidget *widget) +{ + if (widget) { + setupGtkWidget(widget); + addAllSubWidgets(widget); + } +} + + +// Fetch the application font from the pango font description +// contained in the theme. +QFont QGtkStylePrivate::getThemeFont() +{ + QFont font; + GtkStyle *style = gtkStyle(); + if (style && qApp->desktopSettingsAware()) + { + PangoFontDescription *gtk_font = style->font_desc; + font.setPointSizeF((float)(pango_font_description_get_size(gtk_font))/PANGO_SCALE); + + QString family = QString::fromLatin1(pango_font_description_get_family(gtk_font)); + if (!family.isEmpty()) + font.setFamily(family); + + int weight = pango_font_description_get_weight(gtk_font); + if (weight >= PANGO_WEIGHT_HEAVY) + font.setWeight(QFont::Black); + else if (weight >= PANGO_WEIGHT_BOLD) + font.setWeight(QFont::Bold); + else if (weight >= PANGO_WEIGHT_SEMIBOLD) + font.setWeight(QFont::DemiBold); + else if (weight >= PANGO_WEIGHT_NORMAL) + font.setWeight(QFont::Normal); + else + font.setWeight(QFont::Light); + + PangoStyle fontstyle = pango_font_description_get_style(gtk_font); + if (fontstyle == PANGO_STYLE_ITALIC) + font.setStyle(QFont::StyleItalic); + else if (fontstyle == PANGO_STYLE_OBLIQUE) + font.setStyle(QFont::StyleOblique); + else + font.setStyle(QFont::StyleNormal); + } + return font; +} + + +// ----------- Native file dialogs ----------- + +// Extract filter list from expressions of type: foo (*.a *.b *.c)" +QStringList QGtkStylePrivate::extract_filter(const QString &rawFilter) +{ + QString result = rawFilter; + QRegExp r(QString::fromLatin1("^([^()]*)\\(([a-zA-Z0-9_.*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$")); + int index = r.indexIn(result); + if (index >= 0) + result = r.cap(2); + return result.split(QLatin1Char(' ')); +} + +extern QStringList qt_make_filter_list(const QString &filter); + +void QGtkStylePrivate::setupGtkFileChooser(GtkWidget* gtkFileChooser, QWidget *parent, + const QString &dir, const QString &filter, QString *selectedFilter, + QFileDialog::Options options, bool isSaveDialog, + QMap<GtkFileFilter *, QString> *filterMap) +{ + g_object_set(gtkFileChooser, "do-overwrite-confirmation", gboolean(!(options & QFileDialog::DontConfirmOverwrite)), NULL); + g_object_set(gtkFileChooser, "local_only", gboolean(true), NULL); + if (!filter.isEmpty()) { + QStringList filters = qt_make_filter_list(filter); + foreach (const QString &rawfilter, filters) { + GtkFileFilter *gtkFilter = QGtkStylePrivate::gtk_file_filter_new (); + QString name = rawfilter.left(rawfilter.indexOf(QLatin1Char('('))); + QStringList extensions = extract_filter(rawfilter); + QGtkStylePrivate::gtk_file_filter_set_name(gtkFilter, qPrintable(name.isEmpty() ? extensions.join(QLS(", ")) : name)); + + foreach (const QString &fileExtension, extensions) { + // Note Gtk file dialogs are by default case sensitive + // and only supports basic glob syntax so we + // rewrite .xyz to .[xX][yY][zZ] + QString caseInsensitive; + for (int i = 0 ; i < fileExtension.length() ; ++i) { + QChar ch = fileExtension.at(i); + if (ch.isLetter()) { + caseInsensitive.append( + QLatin1Char('[') + + ch.toLower() + + ch.toUpper() + + QLatin1Char(']')); + } else { + caseInsensitive.append(ch); + } + } + QGtkStylePrivate::gtk_file_filter_add_pattern (gtkFilter, qPrintable(caseInsensitive)); + + } + if (filterMap) + filterMap->insert(gtkFilter, rawfilter); + QGtkStylePrivate::gtk_file_chooser_add_filter((GtkFileChooser*)gtkFileChooser, gtkFilter); + if (selectedFilter && (rawfilter == *selectedFilter)) + QGtkStylePrivate::gtk_file_chooser_set_filter((GtkFileChooser*)gtkFileChooser, gtkFilter); + } + } + + // Using the currently active window is not entirely correct, however + // it gives more sensible behavior for applications that do not provide a + // parent + QWidget *modalFor = parent ? parent->window() : qApp->activeWindow(); + if (modalFor) { + QGtkStylePrivate::gtk_widget_realize(gtkFileChooser); // Creates X window + XSetTransientForHint(QGtkStylePrivate::gdk_x11_drawable_get_xdisplay(gtkFileChooser->window), + QGtkStylePrivate::gdk_x11_drawable_get_xid(gtkFileChooser->window), + modalFor->winId()); + QGtkStylePrivate::gdk_x11_window_set_user_time (gtkFileChooser->window, QX11Info::appUserTime()); + + } + + QFileInfo fileinfo(dir); + if (dir.isEmpty()) + fileinfo.setFile(QDir::currentPath()); + fileinfo.makeAbsolute(); + if (fileinfo.isDir()) { + QGtkStylePrivate::gtk_file_chooser_set_current_folder((GtkFileChooser*)gtkFileChooser, qPrintable(dir)); + } else if (isSaveDialog) { + QGtkStylePrivate::gtk_file_chooser_set_current_folder((GtkFileChooser*)gtkFileChooser, qPrintable(fileinfo.absolutePath())); + QGtkStylePrivate::gtk_file_chooser_set_current_name((GtkFileChooser*)gtkFileChooser, qPrintable(fileinfo.fileName())); + } else { + QGtkStylePrivate::gtk_file_chooser_set_filename((GtkFileChooser*)gtkFileChooser, qPrintable(dir)); + } +} + +QString QGtkStylePrivate::openFilename(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) +{ + QMap<GtkFileFilter *, QString> filterMap; + GtkWidget *gtkFileChooser = QGtkStylePrivate::gtk_file_chooser_dialog_new (qPrintable(caption), + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + setupGtkFileChooser(gtkFileChooser, parent, dir, filter, selectedFilter, options, false, &filterMap); + + QWidget modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + QString filename; + if (QGtkStylePrivate::gtk_dialog_run ((GtkDialog*)gtkFileChooser) == GTK_RESPONSE_ACCEPT) { + char *gtk_filename = QGtkStylePrivate::gtk_file_chooser_get_filename ((GtkFileChooser*)gtkFileChooser); + filename = QString::fromUtf8(gtk_filename); + g_free (gtk_filename); + if (selectedFilter) { + GtkFileFilter *gtkFilter = QGtkStylePrivate::gtk_file_chooser_get_filter ((GtkFileChooser*)gtkFileChooser); + *selectedFilter = filterMap.value(gtkFilter); + } + } + + QApplicationPrivate::leaveModal(&modal_widget); + gtk_widget_destroy (gtkFileChooser); + return filename; +} + + +QString QGtkStylePrivate::openDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) +{ + QMap<GtkFileFilter *, QString> filterMap; + GtkWidget *gtkFileChooser = QGtkStylePrivate::gtk_file_chooser_dialog_new (qPrintable(caption), + NULL, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + setupGtkFileChooser(gtkFileChooser, parent, dir, QString(), 0, options); + QWidget modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + QString filename; + if (QGtkStylePrivate::gtk_dialog_run ((GtkDialog*)gtkFileChooser) == GTK_RESPONSE_ACCEPT) { + char *gtk_filename = QGtkStylePrivate::gtk_file_chooser_get_filename ((GtkFileChooser*)gtkFileChooser); + filename = QString::fromUtf8(gtk_filename); + g_free (gtk_filename); + } + + QApplicationPrivate::leaveModal(&modal_widget); + gtk_widget_destroy (gtkFileChooser); + return filename; +} + +QStringList QGtkStylePrivate::openFilenames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) +{ + QStringList filenames; + QMap<GtkFileFilter *, QString> filterMap; + GtkWidget *gtkFileChooser = QGtkStylePrivate::gtk_file_chooser_dialog_new (qPrintable(caption), + NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + setupGtkFileChooser(gtkFileChooser, parent, dir, filter, selectedFilter, options, false, &filterMap); + g_object_set(gtkFileChooser, "select-multiple", gboolean(true), NULL); + + QWidget modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + if (gtk_dialog_run ((GtkDialog*)gtkFileChooser) == GTK_RESPONSE_ACCEPT) { + GSList *gtk_file_names = QGtkStylePrivate::gtk_file_chooser_get_filenames((GtkFileChooser*)gtkFileChooser); + for (GSList *iterator = gtk_file_names ; iterator; iterator = iterator->next) + filenames << QString::fromUtf8((const char*)iterator->data); + g_slist_free(gtk_file_names); + if (selectedFilter) { + GtkFileFilter *gtkFilter = QGtkStylePrivate::gtk_file_chooser_get_filter ((GtkFileChooser*)gtkFileChooser); + *selectedFilter = filterMap.value(gtkFilter); + } + } + + QApplicationPrivate::leaveModal(&modal_widget); + gtk_widget_destroy (gtkFileChooser); + return filenames; +} + +QString QGtkStylePrivate::saveFilename(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) +{ + QMap<GtkFileFilter *, QString> filterMap; + GtkWidget *gtkFileChooser = QGtkStylePrivate::gtk_file_chooser_dialog_new (qPrintable(caption), + NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + setupGtkFileChooser(gtkFileChooser, parent, dir, filter, selectedFilter, options, true, &filterMap); + + QWidget modal_widget; + modal_widget.setAttribute(Qt::WA_NoChildEventsForParent, true); + modal_widget.setParent(parent, Qt::Window); + QApplicationPrivate::enterModal(&modal_widget); + + QString filename; + if (QGtkStylePrivate::gtk_dialog_run ((GtkDialog*)gtkFileChooser) == GTK_RESPONSE_ACCEPT) { + char *gtk_filename = QGtkStylePrivate::gtk_file_chooser_get_filename ((GtkFileChooser*)gtkFileChooser); + filename = QString::fromUtf8(gtk_filename); + g_free (gtk_filename); + if (selectedFilter) { + GtkFileFilter *gtkFilter = QGtkStylePrivate::gtk_file_chooser_get_filter ((GtkFileChooser*)gtkFileChooser); + *selectedFilter = filterMap.value(gtkFilter); + } + } + + QApplicationPrivate::leaveModal(&modal_widget); + gtk_widget_destroy (gtkFileChooser); + return filename; +} + +QIcon QGtkStylePrivate::getFilesystemIcon(const QFileInfo &info) +{ + QIcon icon; + if (gnome_vfs_init && gnome_icon_lookup_sync) { + gnome_vfs_init(); + GtkIconTheme *theme = gtk_icon_theme_get_default(); + QByteArray fileurl = QUrl::fromLocalFile(info.absoluteFilePath()).toEncoded(); + char * icon_name = gnome_icon_lookup_sync(theme, + NULL, + fileurl.data(), + NULL, + GNOME_ICON_LOOKUP_FLAGS_NONE, + NULL); + QString iconName = QString::fromUtf8(icon_name); + g_free(icon_name); + if (iconName.startsWith(QLatin1Char('/'))) + return QIcon(iconName); + return QIcon::fromTheme(iconName); + } + return icon; +} + +bool operator==(const QHashableLatin1Literal &l1, const QHashableLatin1Literal &l2) +{ + return l1.size() == l2.size() || qstrcmp(l1.data(), l2.data()) == 0; +} + +// copied from qHash.cpp +uint qHash(const QHashableLatin1Literal &key) +{ + int n = key.size(); + const uchar *p = reinterpret_cast<const uchar *>(key.data()); + uint h = 0; + uint g; + + while (n--) { + h = (h << 4) + *p++; + if ((g = (h & 0xf0000000)) != 0) + h ^= g >> 23; + h &= ~g; + } + return h; +} + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_STYLE_GTK) diff --git a/src/gui/styles/qgtkstyle_p.h b/src/gui/styles/qgtkstyle_p.h new file mode 100644 index 0000000000..15a98c8257 --- /dev/null +++ b/src/gui/styles/qgtkstyle_p.h @@ -0,0 +1,531 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGTKSTYLE_P_H +#define QGTKSTYLE_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 <QtCore/qglobal.h> +#if !defined(QT_NO_STYLE_GTK) + +#include <QtCore/qstring.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/qcoreapplication.h> + +#include <QtGui/QFileDialog> + +#include <QtGui/QGtkStyle> +#include <private/qcleanlooksstyle_p.h> + +#undef signals // Collides with GTK stymbols +#include <gtk/gtk.h> + +typedef unsigned long XID; + +#undef GTK_OBJECT_FLAGS +#define GTK_OBJECT_FLAGS(obj)(((GtkObject*)(obj))->flags) +#define Q_GTK_IS_WIDGET(widget) widget && GTK_CHECK_TYPE ((widget), QGtkStylePrivate::gtk_widget_get_type()) + +#define QLS(x) QLatin1String(x) + +QT_BEGIN_NAMESPACE + +// ### Qt 4.7 - merge with QLatin1Literal +class QHashableLatin1Literal +{ +public: + int size() const { return m_size; } + const char *data() const { return m_data; } + +#ifdef __SUNPRO_CC + QHashableLatin1Literal(const char* str) + : m_size(strlen(str)), m_data(str) {} +#else + template <int N> + QHashableLatin1Literal(const char (&str)[N]) + : m_size(N - 1), m_data(str) {} +#endif + + QHashableLatin1Literal(const QHashableLatin1Literal &other) + : m_size(other.m_size), m_data(other.m_data) + {} + + QHashableLatin1Literal &operator=(const QHashableLatin1Literal &other) + { + if (this == &other) + return *this; + *const_cast<int *>(&m_size) = other.m_size; + *const_cast<char **>(&m_data) = const_cast<char *>(other.m_data); + return *this; + } + + QString toString() const { return QString::fromLatin1(m_data, m_size); } + + static QHashableLatin1Literal fromData(const char *str) + { + return QHashableLatin1Literal(str, qstrlen(str)); + } + +private: + QHashableLatin1Literal(const char *str, int length) + : m_size(length), m_data(str) + {} + + const int m_size; + const char *m_data; +}; + +bool operator==(const QHashableLatin1Literal &l1, const QHashableLatin1Literal &l2); +inline bool operator!=(const QHashableLatin1Literal &l1, const QHashableLatin1Literal &l2) { return !operator==(l1, l2); } +uint qHash(const QHashableLatin1Literal &key); + +QT_END_NAMESPACE + +class GConf; +class GConfClient; + +typedef GConfClient* (*Ptr_gconf_client_get_default)(); +typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **); +typedef bool (*Ptr_gconf_client_get_bool)(GConfClient*, const char*, GError **); + +typedef void (*Ptr_gtk_init)(int *, char ***); +typedef GtkWidget* (*Ptr_gtk_window_new) (GtkWindowType); +typedef GtkStyle* (*Ptr_gtk_style_attach)(GtkStyle *, GdkWindow *); +typedef void (*Ptr_gtk_widget_destroy) (GtkWidget *); +typedef void (*Ptr_gtk_widget_realize) (GtkWidget *); +typedef void (*Ptr_gtk_widget_set_default_direction) (GtkTextDirection); +typedef void (*Ptr_gtk_widget_modify_color)(GtkWidget *widget, GtkStateType state, const GdkColor *color); +typedef GtkWidget* (*Ptr_gtk_arrow_new)(GtkArrowType, GtkShadowType); +typedef GtkWidget* (*Ptr_gtk_menu_item_new_with_label)(const gchar *); +typedef GtkWidget* (*Ptr_gtk_separator_menu_item_new)(void); +typedef GtkWidget* (*Ptr_gtk_check_menu_item_new_with_label)(const gchar *); +typedef GtkWidget* (*Ptr_gtk_menu_bar_new)(void); +typedef GtkWidget* (*Ptr_gtk_menu_new)(void); +typedef GtkWidget* (*Ptr_gtk_combo_box_entry_new)(void); +typedef GtkWidget* (*Ptr_gtk_toolbar_new)(void); +typedef GtkWidget* (*Ptr_gtk_spin_button_new)(GtkAdjustment*, double, int); +typedef GtkWidget* (*Ptr_gtk_button_new)(void); +typedef GtkWidget* (*Ptr_gtk_tool_button_new)(GtkWidget *, const gchar *); +typedef GtkWidget* (*Ptr_gtk_hbutton_box_new)(void); +typedef GtkWidget* (*Ptr_gtk_check_button_new)(void); +typedef GtkWidget* (*Ptr_gtk_radio_button_new)(GSList *); +typedef GtkWidget* (*Ptr_gtk_notebook_new)(void); +typedef GtkWidget* (*Ptr_gtk_progress_bar_new)(void); +typedef GtkWidget* (*Ptr_gtk_hscale_new)(GtkAdjustment*); +typedef GtkWidget* (*Ptr_gtk_vscale_new)(GtkAdjustment*); +typedef GtkWidget* (*Ptr_gtk_hscrollbar_new)(GtkAdjustment*); +typedef GtkWidget* (*Ptr_gtk_vscrollbar_new)(GtkAdjustment*); +typedef GtkWidget* (*Ptr_gtk_scrolled_window_new)(GtkAdjustment*, GtkAdjustment*); +typedef gchar* (*Ptr_gtk_check_version)(guint, guint, guint); +typedef GtkToolItem* (*Ptr_gtk_separator_tool_item_new) (void); +typedef GtkWidget* (*Ptr_gtk_entry_new)(void); +typedef GtkWidget* (*Ptr_gtk_tree_view_new)(void); +typedef GtkTreeViewColumn* (*Ptr_gtk_tree_view_get_column)(GtkTreeView *, gint); +typedef GtkWidget* (*Ptr_gtk_combo_box_new)(void); +typedef GtkWidget* (*Ptr_gtk_frame_new)(const gchar *); +typedef GtkWidget* (*Ptr_gtk_expander_new)(const gchar*); +typedef GtkWidget* (*Ptr_gtk_statusbar_new)(void); +typedef GtkSettings* (*Ptr_gtk_settings_get_default)(void); +typedef GtkAdjustment* (*Ptr_gtk_range_get_adjustment)(GtkRange *); +typedef void (*Ptr_gtk_range_set_adjustment)(GtkRange *, GtkAdjustment *); +typedef void (*Ptr_gtk_progress_configure)(GtkProgress *, double, double, double); +typedef void (*Ptr_gtk_range_set_inverted)(GtkRange*, bool); +typedef void (*Ptr_gtk_container_add)(GtkContainer *container, GtkWidget *widget); +typedef GtkIconSet* (*Ptr_gtk_icon_factory_lookup_default) (const gchar*); +typedef GtkIconTheme* (*Ptr_gtk_icon_theme_get_default) (void); +typedef void (*Ptr_gtk_widget_style_get)(GtkWidget *, const gchar *first_property_name, ...); +typedef GtkTreeViewColumn* (*Ptr_gtk_tree_view_column_new)(void); +typedef GtkWidget* (*Ptr_gtk_fixed_new)(void); +typedef GdkPixbuf* (*Ptr_gtk_icon_set_render_icon)(GtkIconSet *, GtkStyle *, GtkTextDirection, GtkStateType, GtkIconSize, GtkWidget *,const char *); +typedef void (*Ptr_gtk_tree_view_append_column) (GtkTreeView*, GtkTreeViewColumn*); +typedef void (*Ptr_gtk_paint_check) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_box) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_box_gap) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint, gint, gint , gint, GtkPositionType, gint gap_x, gint gap_width); +typedef void (*Ptr_gtk_paint_resize_grip) (GtkStyle*,GdkWindow*, GtkStateType, const GdkRectangle *, GtkWidget *, const gchar *, GdkWindowEdge, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_focus) (GtkStyle*,GdkWindow*, GtkStateType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_shadow) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_slider) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint, GtkOrientation); +typedef void (*Ptr_gtk_paint_expander) (GtkStyle*,GdkWindow*, GtkStateType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , GtkExpanderStyle ); +typedef void (*Ptr_gtk_paint_handle) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint, GtkOrientation); +typedef void (*Ptr_gtk_paint_arrow) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, GtkArrowType, gboolean, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_option) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_flat_box) (GtkStyle*,GdkWindow*, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint , gint , gint , gint); +typedef void (*Ptr_gtk_paint_extension) (GtkStyle *, GdkWindow *, GtkStateType, GtkShadowType, const GdkRectangle *, GtkWidget *, const gchar *, gint, gint, gint, gint, GtkPositionType); +typedef void (*Ptr_gtk_adjustment_configure) (GtkAdjustment *, double, double, double, double, double, double); +typedef GtkObject* (*Ptr_gtk_adjustment_new) (double, double, double, double, double, double); +typedef void (*Ptr_gtk_paint_hline) (GtkStyle *, GdkWindow *, GtkStateType, const GdkRectangle *, GtkWidget *, const gchar *, gint, gint, gint y); +typedef void (*Ptr_gtk_paint_vline) (GtkStyle *, GdkWindow *, GtkStateType, const GdkRectangle *, GtkWidget *, const gchar *, gint, gint, gint); +typedef void (*Ptr_gtk_menu_item_set_submenu) (GtkMenuItem *, GtkWidget *); +typedef void (*Ptr_gtk_container_forall) (GtkContainer *, GtkCallback, gpointer); +typedef void (*Ptr_gtk_widget_size_allocate) (GtkWidget *, GtkAllocation*); +typedef void (*Ptr_gtk_widget_size_request) (GtkWidget *widget, GtkRequisition *requisition); +typedef void (*Ptr_gtk_widget_set_direction) (GtkWidget *, GtkTextDirection); +typedef void (*Ptr_gtk_widget_path) (GtkWidget *, guint *, gchar **, gchar**); + +typedef void (*Ptr_gtk_toolbar_insert) (GtkToolbar *toolbar, GtkToolItem *item, int pos); +typedef void (*Ptr_gtk_menu_shell_append)(GtkMenuShell *, GtkWidget *); +typedef GtkType (*Ptr_gtk_container_get_type) (void); +typedef GtkType (*Ptr_gtk_window_get_type) (void); +typedef GtkType (*Ptr_gtk_widget_get_type) (void); +typedef GtkStyle* (*Ptr_gtk_rc_get_style_by_paths) (GtkSettings *, const char *, const char *, GType); +typedef gint (*Ptr_pango_font_description_get_size) (const PangoFontDescription *); +typedef PangoWeight (*Ptr_pango_font_description_get_weight) (const PangoFontDescription *); +typedef const char* (*Ptr_pango_font_description_get_family) (const PangoFontDescription *); +typedef PangoStyle (*Ptr_pango_font_description_get_style) (const PangoFontDescription *desc); +typedef gboolean (*Ptr_gtk_file_chooser_set_current_folder)(GtkFileChooser *, const gchar *); +typedef GtkFileFilter* (*Ptr_gtk_file_filter_new)(void); +typedef void (*Ptr_gtk_file_filter_set_name)(GtkFileFilter *, const gchar *); +typedef void (*Ptr_gtk_file_filter_add_pattern)(GtkFileFilter *filter, const gchar *pattern); +typedef void (*Ptr_gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); +typedef void (*Ptr_gtk_file_chooser_set_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); +typedef GtkFileFilter* (*Ptr_gtk_file_chooser_get_filter)(GtkFileChooser *chooser); +typedef gchar* (*Ptr_gtk_file_chooser_get_filename)(GtkFileChooser *chooser); +typedef GSList* (*Ptr_gtk_file_chooser_get_filenames)(GtkFileChooser *chooser); +typedef GtkWidget* (*Ptr_gtk_file_chooser_dialog_new)(const gchar *title, + GtkWindow *parent, + GtkFileChooserAction action, + const gchar *first_button_text, + ...); +typedef void (*Ptr_gtk_file_chooser_set_current_name) (GtkFileChooser *, const gchar *); +typedef gboolean (*Ptr_gtk_file_chooser_set_filename) (GtkFileChooser *chooser, const gchar *name); +typedef gint (*Ptr_gtk_dialog_run) (GtkDialog*); +typedef void (*Ptr_gtk_border_free)(GtkBorder *); + +typedef guchar* (*Ptr_gdk_pixbuf_get_pixels) (const GdkPixbuf *pixbuf); +typedef int (*Ptr_gdk_pixbuf_get_width) (const GdkPixbuf *pixbuf); +typedef void (*Ptr_gdk_color_free) (const GdkColor *); +typedef int (*Ptr_gdk_pixbuf_get_height) (const GdkPixbuf *pixbuf); +typedef GdkPixbuf* (*Ptr_gdk_pixbuf_get_from_drawable) (GdkPixbuf *dest, GdkDrawable *src, + GdkColormap *cmap, int src_x, + int src_y, int dest_x, int dest_y, + int width, int height); +typedef GdkPixmap* (*Ptr_gdk_pixmap_new) (GdkDrawable *drawable, gint width, gint height, gint depth); +typedef GdkPixbuf* (*Ptr_gdk_pixbuf_new) (GdkColorspace colorspace, gboolean has_alpha, + int bits_per_sample, int width, int height); +typedef void (*Ptr_gdk_draw_rectangle) (GdkDrawable *drawable, GdkGC *gc, + gboolean filled, gint x, gint y, gint width, gint height); +typedef void (*Ptr_gdk_pixbuf_unref)(GdkPixbuf *); +typedef void (*Ptr_gdk_drawable_unref)(GdkDrawable *); +typedef gint (*Ptr_gdk_drawable_get_depth)(GdkDrawable *); +typedef void (*Ptr_gdk_x11_window_set_user_time) (GdkWindow *window, guint32); +typedef XID (*Ptr_gdk_x11_drawable_get_xid) (GdkDrawable *); +typedef Display* (*Ptr_gdk_x11_drawable_get_xdisplay) ( GdkDrawable *); + + +QT_BEGIN_NAMESPACE + +typedef QStringList (*_qt_filedialog_open_filenames_hook)(QWidget * parent, const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, QFileDialog::Options options); +typedef QString (*_qt_filedialog_open_filename_hook) (QWidget * parent, const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, QFileDialog::Options options); +typedef QString (*_qt_filedialog_save_filename_hook) (QWidget * parent, const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, QFileDialog::Options options); +typedef QString (*_qt_filedialog_existing_directory_hook)(QWidget *parent, const QString &caption, const QString &dir, + QFileDialog::Options options); + +extern Q_GUI_EXPORT _qt_filedialog_open_filename_hook qt_filedialog_open_filename_hook; +extern Q_GUI_EXPORT _qt_filedialog_open_filenames_hook qt_filedialog_open_filenames_hook; +extern Q_GUI_EXPORT _qt_filedialog_save_filename_hook qt_filedialog_save_filename_hook; +extern Q_GUI_EXPORT _qt_filedialog_existing_directory_hook qt_filedialog_existing_directory_hook; + +class QGtkStylePrivate; + +class QGtkStyleFilter : public QObject +{ +public: + QGtkStyleFilter(QGtkStylePrivate* sp) + : stylePrivate(sp) + {} +private: + QGtkStylePrivate* stylePrivate; + bool eventFilter(QObject *obj, QEvent *e); +}; + +typedef enum { + GNOME_ICON_LOOKUP_FLAGS_NONE = 0, + GNOME_ICON_LOOKUP_FLAGS_EMBEDDING_TEXT = 1<<0, + GNOME_ICON_LOOKUP_FLAGS_SHOW_SMALL_IMAGES_AS_THEMSELVES = 1<<1, + GNOME_ICON_LOOKUP_FLAGS_ALLOW_SVG_AS_THEMSELVES = 1<<2 +} GnomeIconLookupFlags; + +typedef enum { + GNOME_ICON_LOOKUP_RESULT_FLAGS_NONE = 0, + GNOME_ICON_LOOKUP_RESULT_FLAGS_THUMBNAIL = 1<<0 +} GnomeIconLookupResultFlags; + +struct GnomeThumbnailFactory; +typedef gboolean (*Ptr_gnome_vfs_init) (void); +typedef char* (*Ptr_gnome_icon_lookup_sync) ( + GtkIconTheme *icon_theme, + GnomeThumbnailFactory *, + const char *file_uri, + const char *custom_icon, + GnomeIconLookupFlags flags, + GnomeIconLookupResultFlags *result); + +class QGtkStylePrivate : public QCleanlooksStylePrivate +{ + Q_DECLARE_PUBLIC(QGtkStyle) +public: + QGtkStylePrivate(); + ~QGtkStylePrivate(); + + QGtkStyleFilter filter; + + static GtkWidget* gtkWidget(const QHashableLatin1Literal &path); + static GtkStyle* gtkStyle(const QHashableLatin1Literal &path = QHashableLatin1Literal("GtkWindow")); + + virtual void resolveGtk() const; + virtual void initGtkMenu() const; + virtual void initGtkTreeview() const; + virtual void initGtkWidgets() const; + + static void cleanupGtkWidgets(); + + static bool isKDE4Session(); + void applyCustomPaletteHash(); + static QFont getThemeFont(); + static bool isThemeAvailable() { return gtkStyle() != 0; } + + static bool getGConfBool(const QString &key, bool fallback = 0); + static QString getGConfString(const QString &key, const QString &fallback = QString()); + + static QString getThemeName(); + virtual int getSpinboxArrowSize() const; + + static void setupGtkFileChooser(GtkWidget* gtkFileChooser, QWidget *parent, + const QString &dir, const QString &filter, QString *selectedFilter, + QFileDialog::Options options, bool isSaveDialog = false, + QMap<GtkFileFilter *, QString> *filterMap = 0); + + static QString openFilename(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options); + static QString saveFilename(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options); + static QString openDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options); + static QStringList openFilenames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options); + static QIcon getFilesystemIcon(const QFileInfo &); + + static Ptr_gtk_container_forall gtk_container_forall; + static Ptr_gtk_init gtk_init; + static Ptr_gtk_style_attach gtk_style_attach; + static Ptr_gtk_window_new gtk_window_new; + static Ptr_gtk_widget_destroy gtk_widget_destroy; + static Ptr_gtk_widget_realize gtk_widget_realize; + static Ptr_gtk_widget_set_default_direction gtk_widget_set_default_direction; + static Ptr_gtk_widget_modify_color gtk_widget_modify_fg; + static Ptr_gtk_widget_modify_color gtk_widget_modify_bg; + static Ptr_gtk_menu_item_new_with_label gtk_menu_item_new_with_label; + static Ptr_gtk_arrow_new gtk_arrow_new; + static Ptr_gtk_check_menu_item_new_with_label gtk_check_menu_item_new_with_label; + static Ptr_gtk_menu_bar_new gtk_menu_bar_new; + static Ptr_gtk_menu_new gtk_menu_new; + static Ptr_gtk_expander_new gtk_expander_new; + static Ptr_gtk_button_new gtk_button_new; + static Ptr_gtk_tool_button_new gtk_tool_button_new; + static Ptr_gtk_hbutton_box_new gtk_hbutton_box_new; + static Ptr_gtk_check_button_new gtk_check_button_new; + static Ptr_gtk_radio_button_new gtk_radio_button_new; + static Ptr_gtk_spin_button_new gtk_spin_button_new; + static Ptr_gtk_separator_tool_item_new gtk_separator_tool_item_new; + static Ptr_gtk_toolbar_insert gtk_toolbar_insert; + static Ptr_gtk_frame_new gtk_frame_new; + static Ptr_gtk_statusbar_new gtk_statusbar_new; + static Ptr_gtk_entry_new gtk_entry_new; + static Ptr_gtk_hscale_new gtk_hscale_new; + static Ptr_gtk_vscale_new gtk_vscale_new; + static Ptr_gtk_hscrollbar_new gtk_hscrollbar_new; + static Ptr_gtk_vscrollbar_new gtk_vscrollbar_new; + static Ptr_gtk_scrolled_window_new gtk_scrolled_window_new; + static Ptr_gtk_notebook_new gtk_notebook_new; + static Ptr_gtk_toolbar_new gtk_toolbar_new; + static Ptr_gtk_tree_view_new gtk_tree_view_new; + static Ptr_gtk_tree_view_get_column gtk_tree_view_get_column; + static Ptr_gtk_combo_box_new gtk_combo_box_new; + static Ptr_gtk_combo_box_entry_new gtk_combo_box_entry_new; + static Ptr_gtk_progress_bar_new gtk_progress_bar_new; + static Ptr_gtk_container_add gtk_container_add; + static Ptr_gtk_menu_shell_append gtk_menu_shell_append; + static Ptr_gtk_progress_configure gtk_progress_configure; + static Ptr_gtk_range_get_adjustment gtk_range_get_adjustment; + static Ptr_gtk_range_set_adjustment gtk_range_set_adjustment; + static Ptr_gtk_range_set_inverted gtk_range_set_inverted; + static Ptr_gtk_icon_factory_lookup_default gtk_icon_factory_lookup_default; + static Ptr_gtk_icon_theme_get_default gtk_icon_theme_get_default; + static Ptr_gtk_widget_style_get gtk_widget_style_get; + static Ptr_gtk_icon_set_render_icon gtk_icon_set_render_icon; + static Ptr_gtk_fixed_new gtk_fixed_new; + static Ptr_gtk_tree_view_column_new gtk_tree_view_column_new; + static Ptr_gtk_tree_view_append_column gtk_tree_view_append_column; + static Ptr_gtk_paint_check gtk_paint_check; + static Ptr_gtk_paint_box gtk_paint_box; + static Ptr_gtk_paint_box_gap gtk_paint_box_gap; + static Ptr_gtk_paint_flat_box gtk_paint_flat_box; + static Ptr_gtk_paint_option gtk_paint_option; + static Ptr_gtk_paint_extension gtk_paint_extension; + static Ptr_gtk_paint_slider gtk_paint_slider; + static Ptr_gtk_paint_shadow gtk_paint_shadow; + static Ptr_gtk_paint_resize_grip gtk_paint_resize_grip; + static Ptr_gtk_paint_focus gtk_paint_focus; + static Ptr_gtk_paint_arrow gtk_paint_arrow; + static Ptr_gtk_paint_handle gtk_paint_handle; + static Ptr_gtk_paint_expander gtk_paint_expander; + static Ptr_gtk_adjustment_configure gtk_adjustment_configure; + static Ptr_gtk_adjustment_new gtk_adjustment_new; + static Ptr_gtk_paint_vline gtk_paint_vline; + static Ptr_gtk_paint_hline gtk_paint_hline; + static Ptr_gtk_menu_item_set_submenu gtk_menu_item_set_submenu; + static Ptr_gtk_settings_get_default gtk_settings_get_default; + static Ptr_gtk_separator_menu_item_new gtk_separator_menu_item_new; + static Ptr_gtk_widget_size_allocate gtk_widget_size_allocate; + static Ptr_gtk_widget_size_request gtk_widget_size_request; + static Ptr_gtk_widget_set_direction gtk_widget_set_direction; + static Ptr_gtk_widget_path gtk_widget_path; + static Ptr_gtk_container_get_type gtk_container_get_type; + static Ptr_gtk_window_get_type gtk_window_get_type; + static Ptr_gtk_widget_get_type gtk_widget_get_type; + static Ptr_gtk_rc_get_style_by_paths gtk_rc_get_style_by_paths; + static Ptr_gtk_check_version gtk_check_version; + static Ptr_gtk_border_free gtk_border_free; + + static Ptr_pango_font_description_get_size pango_font_description_get_size; + static Ptr_pango_font_description_get_weight pango_font_description_get_weight; + static Ptr_pango_font_description_get_family pango_font_description_get_family; + static Ptr_pango_font_description_get_style pango_font_description_get_style; + + static Ptr_gtk_file_filter_new gtk_file_filter_new; + static Ptr_gtk_file_filter_set_name gtk_file_filter_set_name; + static Ptr_gtk_file_filter_add_pattern gtk_file_filter_add_pattern; + static Ptr_gtk_file_chooser_add_filter gtk_file_chooser_add_filter; + static Ptr_gtk_file_chooser_set_filter gtk_file_chooser_set_filter; + static Ptr_gtk_file_chooser_get_filter gtk_file_chooser_get_filter; + static Ptr_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new; + static Ptr_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder; + static Ptr_gtk_file_chooser_get_filename gtk_file_chooser_get_filename; + static Ptr_gtk_file_chooser_get_filenames gtk_file_chooser_get_filenames; + static Ptr_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name; + static Ptr_gtk_dialog_run gtk_dialog_run; + static Ptr_gtk_file_chooser_set_filename gtk_file_chooser_set_filename; + + static Ptr_gdk_pixbuf_get_pixels gdk_pixbuf_get_pixels; + static Ptr_gdk_pixbuf_get_width gdk_pixbuf_get_width; + static Ptr_gdk_pixbuf_get_height gdk_pixbuf_get_height; + static Ptr_gdk_pixmap_new gdk_pixmap_new; + static Ptr_gdk_pixbuf_new gdk_pixbuf_new; + static Ptr_gdk_pixbuf_get_from_drawable gdk_pixbuf_get_from_drawable; + static Ptr_gdk_draw_rectangle gdk_draw_rectangle; + static Ptr_gdk_pixbuf_unref gdk_pixbuf_unref; + static Ptr_gdk_drawable_unref gdk_drawable_unref; + static Ptr_gdk_drawable_get_depth gdk_drawable_get_depth; + static Ptr_gdk_color_free gdk_color_free; + static Ptr_gdk_x11_window_set_user_time gdk_x11_window_set_user_time; + static Ptr_gdk_x11_drawable_get_xid gdk_x11_drawable_get_xid; + static Ptr_gdk_x11_drawable_get_xdisplay gdk_x11_drawable_get_xdisplay; + + static Ptr_gconf_client_get_default gconf_client_get_default; + static Ptr_gconf_client_get_string gconf_client_get_string; + static Ptr_gconf_client_get_bool gconf_client_get_bool; + + static Ptr_gnome_icon_lookup_sync gnome_icon_lookup_sync; + static Ptr_gnome_vfs_init gnome_vfs_init; + + virtual QPalette gtkWidgetPalette(const QHashableLatin1Literal >kWidgetName) const; + +protected: + typedef QHash<QHashableLatin1Literal, GtkWidget*> WidgetMap; + + static inline void destroyWidgetMap() + { + cleanupGtkWidgets(); + delete widgetMap; + widgetMap = 0; + } + + static inline WidgetMap *gtkWidgetMap() + { + if (!widgetMap) { + widgetMap = new WidgetMap(); + qAddPostRoutine(destroyWidgetMap); + } + return widgetMap; + } + + static QStringList extract_filter(const QString &rawFilter); + + virtual GtkWidget* getTextColorWidget() const; + static void setupGtkWidget(GtkWidget* widget); + static void addWidgetToMap(GtkWidget* widget); + static void addAllSubWidgets(GtkWidget *widget, gpointer v = 0); + static void addWidget(GtkWidget *widget); + static void removeWidgetFromMap(const QHashableLatin1Literal &path); + + virtual void init(); + +private: + static QList<QGtkStylePrivate *> instances; + static WidgetMap *widgetMap; + friend class QGtkStyleUpdateScheduler; +}; + +// Helper to ensure that we have polished all our gtk widgets +// before updating our own palettes +class QGtkStyleUpdateScheduler : public QObject +{ + Q_OBJECT +public slots: + void updateTheme(); +}; + +QT_END_NAMESPACE + +#endif // !QT_NO_STYLE_GTK +#endif // QGTKSTYLE_P_H diff --git a/src/gui/styles/qmacstyle.qdoc b/src/gui/styles/qmacstyle.qdoc new file mode 100644 index 0000000000..2f42d7156b --- /dev/null +++ b/src/gui/styles/qmacstyle.qdoc @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/*! + \class QMacStyle + \brief The QMacStyle class provides a Mac OS X style using the Apple Appearance Manager. + + \ingroup appearance + + This class is implemented as a wrapper to the HITheme + APIs, allowing applications to be styled according to the current + theme in use on Mac OS X. This is done by having primitives + in QStyle implemented in terms of what Mac OS X would normally theme. + + \warning This style is only available on Mac OS X because it relies on the + HITheme APIs. + + There are additional issues that should be taken + into consideration to make an application compatible with the + \link http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/index.html + Apple Human Interface Guidelines \endlink. Some of these issues are outlined + below. + + \list + + \i Layout - The restrictions on window layout are such that some + aspects of layout that are style-dependent cannot be achieved + using QLayout. Changes are being considered (and feedback would be + appreciated) to make layouts QStyle-able. Some of the restrictions + involve horizontal and vertical widget alignment and widget size + (covered below). + + \i Widget size - Mac OS X allows widgets to have specific fixed sizes. Qt + does not fully implement this behavior so as to maintain cross-platform + compatibility. As a result some widgets sizes may be inappropriate (and + subsequently not rendered correctly by the HITheme APIs).The + QWidget::sizeHint() will return the appropriate size for many + managed widgets (widgets enumerated in \l QStyle::ContentsType). + + \i Effects - QMacStyle uses HITheme for performing most of the drawing, but + also uses emulation in a few cases where HITheme does not provide the + required functionality (for example, tab bars on Panther, the toolbar + separator, etc). We tried to make the emulation as close to the original as + possible. Please report any issues you see in effects or non-standard + widgets. + + \endlist + + There are other issues that need to be considered in the feel of + your application (including the general color scheme to match the + Aqua colors). The Guidelines mentioned above will remain current + with new advances and design suggestions for Mac OS X. + + Note that the functions provided by QMacStyle are + reimplementations of QStyle functions; see QStyle for their + documentation. + + \img qmacstyle.png + \sa QWindowsXPStyle, QWindowsStyle, QPlastiqueStyle, QCDEStyle, QMotifStyle +*/ + + +/*! + \enum QMacStyle::WidgetSizePolicy + + \value SizeSmall + \value SizeLarge + \value SizeMini + \value SizeDefault + \omitvalue SizeNone +*/ + +/*! \fn QMacStyle::QMacStyle() + Constructs a QMacStyle object. +*/ + +/*! \fn QMacStyle::~QMacStyle() + Destructs a QMacStyle object. +*/ + +/*! \fn void QMacStyle::polish(QPalette &pal) + \reimp +*/ + +/*! \fn void QMacStyle::polish(QApplication *) + \reimp +*/ + +/*! \fn void QMacStyle::unpolish(QApplication *) + \reimp +*/ + +/*! \fn void QMacStyle::polish(QWidget* w) + \reimp +*/ + +/*! \fn void QMacStyle::unpolish(QWidget* w) + \reimp +*/ + +/*! \fn int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const + \reimp +*/ + +/*! \fn QPalette QMacStyle::standardPalette() const + \reimp +*/ + +/*! \fn int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, QStyleHintReturn *hret) const + \reimp +*/ + +/*! \fn QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, const QStyleOption *opt) const + \reimp +*/ + +/*! \fn QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, const QWidget *widget) const + \reimp +*/ + +/*! + \enum QMacStyle::FocusRectPolicy + + This type is used to signify a widget's focus rectangle policy. + + \value FocusEnabled show a focus rectangle when the widget has focus. + \value FocusDisabled never show a focus rectangle for the widget. + \value FocusDefault show a focus rectangle when the widget has + focus and the widget is a QSpinWidget, QDateTimeEdit, QLineEdit, + QListBox, QListView, editable QTextEdit, or one of their + subclasses. +*/ + +/*! \fn void QMacStyle::setFocusRectPolicy(QWidget *w, FocusRectPolicy policy) + \obsolete + Sets the focus rectangle policy of \a w. The \a policy can be one of + \l{QMacStyle::FocusRectPolicy}. + + This is now simply an interface to the Qt::WA_MacShowFocusRect attribute and the + FocusDefault value does nothing anymore. If you want to set a widget back + to its default value, you must save the old value of the attribute before + you change it. + + \sa focusRectPolicy() QWidget::setAttribute() +*/ + +/*! \fn QMacStyle::FocusRectPolicy QMacStyle::focusRectPolicy(const QWidget *w) + \obsolete + Returns the focus rectangle policy for the widget \a w. + + The focus rectangle policy can be one of \l{QMacStyle::FocusRectPolicy}. + + In 4.3 and up this function will simply test for the + Qt::WA_MacShowFocusRect attribute and will never return + QMacStyle::FocusDefault. + + \sa setFocusRectPolicy(), QWidget::testAttribute() +*/ + +/*! \fn void QMacStyle::setWidgetSizePolicy(const QWidget *widget, WidgetSizePolicy policy) + + \obsolete + + Call QWidget::setAttribute() with Qt::WA_MacMiniSize, Qt::WA_MacSmallSize, + or Qt::WA_MacNormalSize instead. +*/ + +/*! \fn QMacStyle::WidgetSizePolicy QMacStyle::widgetSizePolicy(const QWidget *widget) + \obsolete + + Call QWidget::testAttribute() with Qt::WA_MacMiniSize, Qt::WA_MacSmallSize, + or Qt::WA_MacNormalSize instead. +*/ + +/*! \fn void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const + + \reimp +*/ + +/*! \fn void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, const QWidget *w) const + + \reimp +*/ + +/*! \fn QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *widget) const + + \reimp +*/ + +/*! \fn void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const + \reimp +*/ + +/*! \fn QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, const QPoint &pt, const QWidget *widget) const + \reimp +*/ + +/*! \fn QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const + \reimp +*/ + +/*! \fn QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &csz, const QWidget *widget) const + \reimp +*/ + +/*! \fn void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const + \reimp +*/ + +/*! \fn bool QMacStyle::event(QEvent *e) + \reimp +*/ + +/*! \fn QIcon QMacStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt, const QWidget *widget) const + \internal +*/ + +/*! \fn int QMacStyle::layoutSpacingImplementation(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation, const QStyleOption *option, const QWidget *widget) const + + \internal +*/ + diff --git a/src/gui/styles/qmacstyle_mac.h b/src/gui/styles/qmacstyle_mac.h new file mode 100644 index 0000000000..78a25ce9b2 --- /dev/null +++ b/src/gui/styles/qmacstyle_mac.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACSTYLE_MAC_H +#define QMACSTYLE_MAC_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) + +class QPalette; + +#if defined(QT_PLUGIN) +#define Q_GUI_EXPORT_STYLE_MAC +#else +#define Q_GUI_EXPORT_STYLE_MAC Q_GUI_EXPORT +#endif + +class QPushButton; +class QStyleOptionButton; +class QMacStylePrivate; +class Q_GUI_EXPORT_STYLE_MAC QMacStyle : public QWindowsStyle +{ + Q_OBJECT +public: + QMacStyle(); + virtual ~QMacStyle(); + + void polish(QWidget *w); + void unpolish(QWidget *w); + + void polish(QApplication*); + void unpolish(QApplication*); + + void polish(QPalette &pal); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *w = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *w = 0) const; + + int pixelMetric(PixelMetric pm, const QStyleOption *opt = 0, const QWidget *widget = 0) const; + + QPalette standardPalette() const; + + virtual int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, + QStyleHintReturn *shret = 0) const; + + enum FocusRectPolicy { FocusEnabled, FocusDisabled, FocusDefault }; + static void setFocusRectPolicy(QWidget *w, FocusRectPolicy policy); + static FocusRectPolicy focusRectPolicy(const QWidget *w); + + enum WidgetSizePolicy { SizeSmall, SizeLarge, SizeMini, SizeDefault +#ifdef QT3_SUPPORT + , SizeNone = SizeDefault +#endif + }; + static void setWidgetSizePolicy(const QWidget *w, WidgetSizePolicy policy); + static WidgetSizePolicy widgetSizePolicy(const QWidget *w); + + QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *opt, + const QWidget *widget = 0) const; + + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const; + + virtual void drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, + bool enabled, const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + + bool event(QEvent *e); + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + int layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QMacStyle) + + QMacStylePrivate *d; + + friend bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option); +}; + +#endif // Q_WS_MAC + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMACSTYLE_H diff --git a/src/gui/styles/qmacstyle_mac.mm b/src/gui/styles/qmacstyle_mac.mm new file mode 100644 index 0000000000..2d21628488 --- /dev/null +++ b/src/gui/styles/qmacstyle_mac.mm @@ -0,0 +1,6042 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + Note: The qdoc comments for QMacStyle are contained in + .../doc/src/qstyles.qdoc. +*/ + +#include "qmacstyle_mac.h" + +#if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) +#define QMAC_QAQUASTYLE_SIZE_CONSTRAIN +//#define DEBUG_SIZE_CONSTRAINT + +#include <private/qapplication_p.h> +#include <private/qcombobox_p.h> +#include <private/qmacstylepixmaps_mac_p.h> +#include <private/qpaintengine_mac_p.h> +#include <private/qpainter_p.h> +#include <private/qprintengine_mac_p.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qdialogbuttonbox.h> +#include <qdockwidget.h> +#include <qevent.h> +#include <qfocusframe.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qhash.h> +#include <qheaderview.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistview.h> +#include <qmainwindow.h> +#include <qmap.h> +#include <qmenubar.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <qpixmapcache.h> +#include <qpointer.h> +#include <qprogressbar.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qrubberband.h> +#include <qsizegrip.h> +#include <qspinbox.h> +#include <qsplitter.h> +#include <qstyleoption.h> +#include <qtextedit.h> +#include <qtextstream.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qtreeview.h> +#include <qtableview.h> +#include <qwizard.h> +#include <qdebug.h> +#include <qlibrary.h> +#include <qdatetimeedit.h> +#include <qmath.h> +#include <QtGui/qgraphicsproxywidget.h> +#include <QtGui/qgraphicsview.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include "qmacstyle_mac_p.h" +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +// The following constants are used for adjusting the size +// of push buttons so that they are drawn inside their bounds. +const int QMacStylePrivate::PushButtonLeftOffset = 6; +const int QMacStylePrivate::PushButtonTopOffset = 4; +const int QMacStylePrivate::PushButtonRightOffset = 12; +const int QMacStylePrivate::PushButtonBottomOffset = 12; +const int QMacStylePrivate::MiniButtonH = 26; +const int QMacStylePrivate::SmallButtonH = 30; +const int QMacStylePrivate::BevelButtonW = 50; +const int QMacStylePrivate::BevelButtonH = 22; +const int QMacStylePrivate::PushButtonContentPadding = 6; + +// These colors specify the titlebar gradient colors on +// Leopard. Ideally we should get them from the system. +static const QColor titlebarGradientActiveBegin(220, 220, 220); +static const QColor titlebarGradientActiveEnd(151, 151, 151); +static const QColor titlebarSeparatorLineActive(111, 111, 111); +static const QColor titlebarGradientInactiveBegin(241, 241, 241); +static const QColor titlebarGradientInactiveEnd(207, 207, 207); +static const QColor titlebarSeparatorLineInactive(131, 131, 131); + +// Gradient colors used for the dock widget title bar and +// non-unifed tool bar bacground. +static const QColor mainWindowGradientBegin(240, 240, 240); +static const QColor mainWindowGradientEnd(200, 200, 200); + +static const int DisclosureOffset = 4; + +// Resolve these at run-time, since the functions was moved in Leopard. +typedef HIRect * (*PtrHIShapeGetBounds)(HIShapeRef, HIRect *); +static PtrHIShapeGetBounds ptrHIShapeGetBounds = 0; + +static int closeButtonSize = 12; + +extern QRegion qt_mac_convert_mac_region(RgnHandle); //qregion_mac.cpp + +static bool isVerticalTabs(const QTabBar::Shape shape) { + return (shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularEast + || shape == QTabBar::RoundedWest + || shape == QTabBar::TriangularWest); +} + +void drawTabCloseButton(QPainter *p, bool hover, bool active, bool selected) +{ + // draw background circle + p->setRenderHints(QPainter::Antialiasing); + QRect rect(0, 0, closeButtonSize, closeButtonSize); + QColor background; + if (hover) { + background = QColor(124, 124, 124); + } else { + if (active) { + if (selected) + background = QColor(104, 104, 104); + else + background = QColor(83, 83, 83); + } else { + if (selected) + background = QColor(144, 144, 144); + else + background = QColor(114, 114, 114); + } + } + p->setPen(Qt::transparent); + p->setBrush(background); + p->drawEllipse(rect); + + // draw cross + int min = 3; + int max = 9; + QPen crossPen; + crossPen.setColor(QColor(194, 194, 194)); + crossPen.setWidthF(1.3); + crossPen.setCapStyle(Qt::FlatCap); + p->setPen(crossPen); + p->drawLine(min, min, max, max); + p->drawLine(min, max, max, min); +} + +QRect rotateTabPainter(QPainter *p, QTabBar::Shape shape, QRect tabRect) +{ + if (isVerticalTabs(shape)) { + int newX, newY, newRot; + if (shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularEast) { + newX = tabRect.width(); + newY = tabRect.y(); + newRot = 90; + } else { + newX = 0; + newY = tabRect.y() + tabRect.height(); + newRot = -90; + } + tabRect.setRect(0, 0, tabRect.height(), tabRect.width()); + QMatrix m; + m.translate(newX, newY); + m.rotate(newRot); + p->setMatrix(m, true); + } + return tabRect; +} + +void drawTabShape(QPainter *p, const QStyleOptionTabV3 *tabOpt) +{ + QRect r = tabOpt->rect; + p->translate(tabOpt->rect.x(), tabOpt->rect.y()); + r.moveLeft(0); + r.moveTop(0); + QRect tabRect = rotateTabPainter(p, tabOpt->shape, r); + + int width = tabRect.width(); + int height = 20; + bool active = (tabOpt->state & QStyle::State_Active); + bool selected = (tabOpt->state & QStyle::State_Selected); + + if (selected) { + QRect rect(1, 0, width - 2, height); + + // fill body + if (active) { + int d = (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_6) ? 16 : 0; + p->fillRect(rect, QColor(151 + d, 151 + d, 151 + d)); + } else { + int d = (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_6) ? 9 : 0; + QLinearGradient gradient(rect.topLeft(), rect.bottomLeft()); + gradient.setColorAt(0, QColor(207 + d, 207 + d, 207 + d)); + gradient.setColorAt(0.5, QColor(206 + d, 206 + d, 206 + d)); + gradient.setColorAt(1, QColor(201 + d, 201 + d, 201 + d)); + p->fillRect(rect, gradient); + } + + // draw border + QColor borderSides; + QColor borderBottom; + if (active) { + borderSides = QColor(88, 88, 88); + borderBottom = QColor(88, 88, 88); + } else { + borderSides = QColor(121, 121, 121); + borderBottom = QColor(116, 116, 116); + } + + p->setPen(borderSides); + + int bottom = height; + // left line + p->drawLine(0, 1, 0, bottom-2); + // right line + p->drawLine(width-1, 1, width-1, bottom-2); + + // bottom line + if (active) { + p->setPen(QColor(168, 168, 168)); + p->drawLine(3, bottom-1, width-3, bottom-1); + } + p->setPen(borderBottom); + p->drawLine(2, bottom, width-2, bottom); + + int w = 3; + QRectF rectangleLeft(1, height - w, w, w); + QRectF rectangleRight(width - 2, height - 1, w, w); + int startAngle = 180 * 16; + int spanAngle = 90 * 16; + p->setRenderHint(QPainter::Antialiasing); + p->drawArc(rectangleLeft, startAngle, spanAngle); + p->drawArc(rectangleRight, startAngle, -spanAngle); + } else { + // when the mouse is over non selected tabs they get a new color + bool hover = (tabOpt->state & QStyle::State_MouseOver); + if (hover) { + QRect rect(1, 2, width - 1, height - 1); + p->fillRect(rect, QColor(110, 110, 110)); + } + + // seperator lines between tabs + bool west = (tabOpt->shape == QTabBar::RoundedWest || tabOpt->shape == QTabBar::TriangularWest); + bool drawOnRight = !west; + if ((!drawOnRight && tabOpt->selectedPosition != QStyleOptionTab::NextIsSelected) + || (drawOnRight && tabOpt->selectedPosition != QStyleOptionTab::NextIsSelected)) { + QColor borderColor; + QColor borderHighlightColor; + if (active) { + borderColor = QColor(64, 64, 64); + borderHighlightColor = QColor(140, 140, 140); + } else { + borderColor = QColor(135, 135, 135); + borderHighlightColor = QColor(178, 178, 178); + } + + int x = drawOnRight ? width : 0; + + // tab seperator line + p->setPen(borderColor); + p->drawLine(x, 2, x, height + 1); + + // tab seperator highlight + p->setPen(borderHighlightColor); + p->drawLine(x-1, 2, x-1, height + 1); + p->drawLine(x+1, 2, x+1, height + 1); + } + } +} + +void drawTabBase(QPainter *p, const QStyleOptionTabBarBaseV2 *tbb, const QWidget *w) +{ + QRect r = tbb->rect; + if (isVerticalTabs(tbb->shape)) { + r.setWidth(w->width()); + } else { + r.setHeight(w->height()); + } + QRect tabRect = rotateTabPainter(p, tbb->shape, r); + int width = tabRect.width(); + int height = tabRect.height(); + bool active = (tbb->state & QStyle::State_Active); + + // top border lines + QColor borderHighlightTop; + QColor borderTop; + if (active) { + borderTop = QColor(64, 64, 64); + borderHighlightTop = QColor(174, 174, 174); + } else { + borderTop = QColor(135, 135, 135); + borderHighlightTop = QColor(207, 207, 207); + } + p->setPen(borderHighlightTop); + p->drawLine(tabRect.x(), 0, width, 0); + p->setPen(borderTop); + p->drawLine(tabRect.x(), 1, width, 1); + + // center block + QRect centralRect(tabRect.x(), 2, width, height - 2); + if (active) { + QColor mainColor = QColor(120, 120, 120); + p->fillRect(centralRect, mainColor); + } else { + QLinearGradient gradient(centralRect.topLeft(), centralRect.bottomLeft()); + gradient.setColorAt(0, QColor(165, 165, 165)); + gradient.setColorAt(0.5, QColor(164, 164, 164)); + gradient.setColorAt(1, QColor(158, 158, 158)); + p->fillRect(centralRect, gradient); + } + + // bottom border lines + QColor borderHighlightBottom; + QColor borderBottom; + if (active) { + borderHighlightBottom = QColor(153, 153, 153); + borderBottom = QColor(64, 64, 64); + } else { + borderHighlightBottom = QColor(177, 177, 177); + borderBottom = QColor(127, 127, 127); + } + p->setPen(borderHighlightBottom); + p->drawLine(tabRect.x(), height - 2, width, height - 2); + p->setPen(borderBottom); + p->drawLine(tabRect.x(), height - 1, width, height - 1); +} + +static int getControlSize(const QStyleOption *option, const QWidget *widget) +{ + if (option) { + if (option->state & (QStyle::State_Small | QStyle::State_Mini)) + return (option->state & QStyle::State_Mini) ? QAquaSizeMini : QAquaSizeSmall; + } else if (widget) { + switch (QMacStyle::widgetSizePolicy(widget)) { + case QMacStyle::SizeSmall: + return QAquaSizeSmall; + case QMacStyle::SizeMini: + return QAquaSizeMini; + default: + break; + } + } + return QAquaSizeLarge; +} + + +static inline bool isTreeView(const QWidget *widget) +{ + return (widget && widget->parentWidget() && + (qobject_cast<const QTreeView *>(widget->parentWidget()) +#ifdef QT3_SUPPORT + || widget->parentWidget()->inherits("Q3ListView") +#endif + )); +} + +QString qt_mac_removeMnemonics(const QString &original) +{ + QString returnText(original.size(), 0); + int finalDest = 0; + int currPos = 0; + int l = original.length(); + while (l) { + if (original.at(currPos) == QLatin1Char('&') + && (l == 1 || original.at(currPos + 1) != QLatin1Char('&'))) { + ++currPos; + --l; + if (l == 0) + break; + } + returnText[finalDest] = original.at(currPos); + ++currPos; + ++finalDest; + --l; + } + returnText.truncate(finalDest); + return returnText; +} + +static inline ThemeTabDirection getTabDirection(QTabBar::Shape shape) +{ + ThemeTabDirection ttd; + switch (shape) { + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + ttd = kThemeTabSouth; + break; + default: // Added to remove the warning, since all values are taken care of, really! + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + ttd = kThemeTabNorth; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + ttd = kThemeTabWest; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + ttd = kThemeTabEast; + break; + } + return ttd; +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include "moc_qmacstyle_mac.cpp" +#include "moc_qmacstyle_mac_p.cpp" +QT_END_INCLUDE_NAMESPACE + +/***************************************************************************** + External functions + *****************************************************************************/ +extern CGContextRef qt_mac_cg_context(const QPaintDevice *); //qpaintdevice_mac.cpp +extern QRegion qt_mac_convert_mac_region(HIShapeRef); //qregion_mac.cpp +void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp +extern QPaintDevice *qt_mac_safe_pdev; //qapplication_mac.cpp + +/***************************************************************************** + QMacCGStyle globals + *****************************************************************************/ +const int qt_mac_hitheme_version = 0; //the HITheme version we speak +const int macItemFrame = 2; // menu item frame width +const int macItemHMargin = 3; // menu item hor text margin +const int macItemVMargin = 2; // menu item ver text margin +const int macRightBorder = 12; // right border on mac +const ThemeWindowType QtWinType = kThemeDocumentWindow; // Window type we use for QTitleBar. +QPixmap *qt_mac_backgroundPattern = 0; // stores the standard widget background. + +/***************************************************************************** + QMacCGStyle utility functions + *****************************************************************************/ +static inline int qt_mac_hitheme_tab_version() +{ + return 1; +} + +static inline HIRect qt_hirectForQRect(const QRect &convertRect, const QRect &rect = QRect()) +{ + return CGRectMake(convertRect.x() + rect.x(), convertRect.y() + rect.y(), + convertRect.width() - rect.width(), convertRect.height() - rect.height()); +} + +static inline const QRect qt_qrectForHIRect(const HIRect &hirect) +{ + return QRect(QPoint(int(hirect.origin.x), int(hirect.origin.y)), + QSize(int(hirect.size.width), int(hirect.size.height))); +} + +inline bool qt_mac_is_metal(const QWidget *w) +{ + for (; w; w = w->parentWidget()) { + if (w->testAttribute(Qt::WA_MacBrushedMetal)) + return true; + if (w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) { // If not created will fall through to the opaque check and be fine anyway. + return macWindowIsTextured(qt_mac_window_for(w)); + } + if (w->d_func()->isOpaque) + break; + } + return false; +} + +static int qt_mac_aqua_get_metric(ThemeMetric met) +{ + SInt32 ret; + GetThemeMetric(met, &ret); + return ret; +} + +static QSize qt_aqua_get_known_size(QStyle::ContentsType ct, const QWidget *widg, QSize szHint, + QAquaWidgetSize sz) +{ + QSize ret(-1, -1); + if (sz != QAquaSizeSmall && sz != QAquaSizeLarge && sz != QAquaSizeMini) { + qDebug("Not sure how to return this..."); + return ret; + } + if ((widg && widg->testAttribute(Qt::WA_SetFont)) || !QApplication::desktopSettingsAware()) { + // If you're using a custom font and it's bigger than the default font, + // then no constraints for you. If you are smaller, we can try to help you out + QFont font = qt_app_fonts_hash()->value(widg->metaObject()->className(), QFont()); + if (widg->font().pointSize() > font.pointSize()) + return ret; + } + + if (ct == QStyle::CT_CustomBase && widg) { + if (qobject_cast<const QPushButton *>(widg)) + ct = QStyle::CT_PushButton; + else if (qobject_cast<const QRadioButton *>(widg)) + ct = QStyle::CT_RadioButton; + else if (qobject_cast<const QCheckBox *>(widg)) + ct = QStyle::CT_CheckBox; + else if (qobject_cast<const QComboBox *>(widg)) + ct = QStyle::CT_ComboBox; + else if (qobject_cast<const QToolButton *>(widg)) + ct = QStyle::CT_ToolButton; + else if (qobject_cast<const QSlider *>(widg)) + ct = QStyle::CT_Slider; + else if (qobject_cast<const QProgressBar *>(widg)) + ct = QStyle::CT_ProgressBar; + else if (qobject_cast<const QLineEdit *>(widg)) + ct = QStyle::CT_LineEdit; + else if (qobject_cast<const QHeaderView *>(widg) +#ifdef QT3_SUPPORT + || widg->inherits("Q3Header") +#endif + ) + ct = QStyle::CT_HeaderSection; + else if (qobject_cast<const QMenuBar *>(widg) +#ifdef QT3_SUPPORT + || widg->inherits("Q3MenuBar") +#endif + ) + ct = QStyle::CT_MenuBar; + else if (qobject_cast<const QSizeGrip *>(widg)) + ct = QStyle::CT_SizeGrip; + else + return ret; + } + + switch (ct) { + case QStyle::CT_PushButton: { + const QPushButton *psh = qobject_cast<const QPushButton *>(widg); + // If this comparison is false, then the widget was not a push button. + // This is bad and there's very little we can do since we were requested to find a + // sensible size for a widget that pretends to be a QPushButton but is not. + if(psh) { + QString buttonText = qt_mac_removeMnemonics(psh->text()); + if (buttonText.contains(QLatin1Char('\n'))) + ret = QSize(-1, -1); + else if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPushButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPushButtonHeight)); + + if (!psh->icon().isNull()){ + // If the button got an icon, and the icon is larger than the + // button, we can't decide on a default size + ret.setWidth(-1); + if (ret.height() < psh->iconSize().height()) + ret.setHeight(-1); + } + else if (buttonText == QLatin1String("OK") || buttonText == QLatin1String("Cancel")){ + // Aqua Style guidelines restrict the size of OK and Cancel buttons to 68 pixels. + // However, this doesn't work for German, therefore only do it for English, + // I suppose it would be better to do some sort of lookups for languages + // that like to have really long words. + ret.setWidth(77 - 8); + } + } else { + // The only sensible thing to do is to return whatever the style suggests... + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPushButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPushButtonHeight)); + else + // Since there's no default size we return the large size... + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPushButtonHeight)); + } +#if 0 //Not sure we are applying the rules correctly for RadioButtons/CheckBoxes --Sam + } else if (ct == QStyle::CT_RadioButton) { + QRadioButton *rdo = static_cast<QRadioButton *>(widg); + // Exception for case where multiline radio button text requires no size constrainment + if (rdo->text().find('\n') != -1) + return ret; + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricRadioButtonHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallRadioButtonHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniRadioButtonHeight)); + } else if (ct == QStyle::CT_CheckBox) { + if (sz == QAquaSizeLarge) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricCheckBoxHeight)); + else if (sz == QAquaSizeSmall) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallCheckBoxHeight)); + else if (sz == QAquaSizeMini) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniCheckBoxHeight)); +#endif + break; + } + case QStyle::CT_SizeGrip: + if (sz == QAquaSizeLarge || sz == QAquaSizeSmall) { + HIRect r; + HIPoint p = { 0, 0 }; + HIThemeGrowBoxDrawInfo gbi; + gbi.version = 0; + gbi.state = kThemeStateActive; + gbi.kind = kHIThemeGrowBoxKindNormal; + gbi.direction = QApplication::isRightToLeft() ? kThemeGrowLeft | kThemeGrowDown + : kThemeGrowRight | kThemeGrowDown; + gbi.size = sz == QAquaSizeSmall ? kHIThemeGrowBoxSizeSmall : kHIThemeGrowBoxSizeNormal; + if (HIThemeGetGrowBoxBounds(&p, &gbi, &r) == noErr) + ret = QSize(r.size.width, r.size.height); + } + break; + case QStyle::CT_ComboBox: + switch (sz) { + case QAquaSizeLarge: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricPopupButtonHeight)); + break; + case QAquaSizeSmall: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricSmallPopupButtonHeight)); + break; + case QAquaSizeMini: + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricMiniPopupButtonHeight)); + break; + default: + break; + } + break; + case QStyle::CT_ToolButton: + if (sz == QAquaSizeSmall) { + int width = 0, height = 0; + if (szHint == QSize(-1, -1)) { //just 'guess'.. + const QToolButton *bt = qobject_cast<const QToolButton *>(widg); + // If this conversion fails then the widget was not what it claimed to be. + if(bt) { + if (!bt->icon().isNull()) { + QSize iconSize = bt->iconSize(); + QSize pmSize = bt->icon().actualSize(QSize(32, 32), QIcon::Normal); + width = qMax(width, qMax(iconSize.width(), pmSize.width())); + height = qMax(height, qMax(iconSize.height(), pmSize.height())); + } + if (!bt->text().isNull() && bt->toolButtonStyle() != Qt::ToolButtonIconOnly) { + int text_width = bt->fontMetrics().width(bt->text()), + text_height = bt->fontMetrics().height(); + if (bt->toolButtonStyle() == Qt::ToolButtonTextUnderIcon) { + width = qMax(width, text_width); + height += text_height; + } else { + width += text_width; + width = qMax(height, text_height); + } + } + } else { + // Let's return the size hint... + width = szHint.width(); + height = szHint.height(); + } + } else { + width = szHint.width(); + height = szHint.height(); + } + width = qMax(20, width + 5); //border + height = qMax(20, height + 5); //border + ret = QSize(width, height); + } + break; + case QStyle::CT_Slider: { + int w = -1; + const QSlider *sld = qobject_cast<const QSlider *>(widg); + // If this conversion fails then the widget was not what it claimed to be. + if(sld) { + if (sz == QAquaSizeLarge) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricVSliderTickWidth); + } + } else if (sz == QAquaSizeSmall) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricSmallHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricSmallHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricSmallVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricSmallVSliderTickWidth); + } + } else if (sz == QAquaSizeMini) { + if (sld->orientation() == Qt::Horizontal) { + w = qt_mac_aqua_get_metric(kThemeMetricMiniHSliderHeight); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricMiniHSliderTickHeight); + } else { + w = qt_mac_aqua_get_metric(kThemeMetricMiniVSliderWidth); + if (sld->tickPosition() != QSlider::NoTicks) + w += qt_mac_aqua_get_metric(kThemeMetricMiniVSliderTickWidth); + } + } + } else { + // This is tricky, we were requested to find a size for a slider which is not + // a slider. We don't know if this is vertical or horizontal or if we need to + // have tick marks or not. + // For this case we will return an horizontal slider without tick marks. + w = qt_mac_aqua_get_metric(kThemeMetricHSliderHeight); + w += qt_mac_aqua_get_metric(kThemeMetricHSliderTickHeight); + } + if (sld->orientation() == Qt::Horizontal) + ret.setHeight(w); + else + ret.setWidth(w); + break; + } + case QStyle::CT_ProgressBar: { + int finalValue = -1; + Qt::Orientation orient = Qt::Horizontal; + if (const QProgressBar *pb = qobject_cast<const QProgressBar *>(widg)) + orient = pb->orientation(); + + if (sz == QAquaSizeLarge) + finalValue = qt_mac_aqua_get_metric(kThemeMetricLargeProgressBarThickness) + + qt_mac_aqua_get_metric(kThemeMetricProgressBarShadowOutset); + else + finalValue = qt_mac_aqua_get_metric(kThemeMetricNormalProgressBarThickness) + + qt_mac_aqua_get_metric(kThemeMetricSmallProgressBarShadowOutset); + if (orient == Qt::Horizontal) + ret.setHeight(finalValue); + else + ret.setWidth(finalValue); + break; + } + case QStyle::CT_LineEdit: + if (!widg || !qobject_cast<QComboBox *>(widg->parentWidget())) { + //should I take into account the font dimentions of the lineedit? -Sam + if (sz == QAquaSizeLarge) + ret = QSize(-1, 22); + else + ret = QSize(-1, 19); + } + break; + case QStyle::CT_HeaderSection: + if (isTreeView(widg)) + ret = QSize(-1, qt_mac_aqua_get_metric(kThemeMetricListHeaderHeight)); + break; + case QStyle::CT_MenuBar: + if (sz == QAquaSizeLarge) { +#ifndef QT_MAC_USE_COCOA + SInt16 size; + if (!GetThemeMenuBarHeight(&size)) + ret = QSize(-1, size); +#else + ret = QSize(-1, [[NSApp mainMenu] menuBarHeight]); + // In the qt_mac_set_native_menubar(false) case, + // we come it here with a zero-height main menu, + // preventing the in-window menu from displaying. + // Use 22 pixels for the height, by observation. + if (ret.height() <= 0) + ret.setHeight(22); +#endif + } + break; + default: + break; + } + return ret; +} + + +#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) +static QAquaWidgetSize qt_aqua_guess_size(const QWidget *widg, QSize large, QSize small, QSize mini) +{ + if (large == QSize(-1, -1)) { + if (small != QSize(-1, -1)) + return QAquaSizeSmall; + if (mini != QSize(-1, -1)) + return QAquaSizeMini; + return QAquaSizeUnknown; + } else if (small == QSize(-1, -1)) { + if (mini != QSize(-1, -1)) + return QAquaSizeMini; + return QAquaSizeLarge; + } else if (mini == QSize(-1, -1)) { + return QAquaSizeLarge; + } + +#ifndef QT_NO_MAINWINDOW + if (qobject_cast<QDockWidget *>(widg->window()) || !qgetenv("QWIDGET_ALL_SMALL").isNull()) { + //if (small.width() != -1 || small.height() != -1) + return QAquaSizeSmall; + } else if (!qgetenv("QWIDGET_ALL_MINI").isNull()) { + return QAquaSizeMini; + } +#endif + +#if 0 + /* Figure out which size we're closer to, I just hacked this in, I haven't + tested it as it would probably look pretty strange to have some widgets + big and some widgets small in the same window?? -Sam */ + int large_delta=0; + if (large.width() != -1) { + int delta = large.width() - widg->width(); + large_delta += delta * delta; + } + if (large.height() != -1) { + int delta = large.height() - widg->height(); + large_delta += delta * delta; + } + int small_delta=0; + if (small.width() != -1) { + int delta = small.width() - widg->width(); + small_delta += delta * delta; + } + if (small.height() != -1) { + int delta = small.height() - widg->height(); + small_delta += delta * delta; + } + int mini_delta=0; + if (mini.width() != -1) { + int delta = mini.width() - widg->width(); + mini_delta += delta * delta; + } + if (mini.height() != -1) { + int delta = mini.height() - widg->height(); + mini_delta += delta * delta; + } + if (mini_delta < small_delta && mini_delta < large_delta) + return QAquaSizeMini; + else if (small_delta < large_delta) + return QAquaSizeSmall; +#endif + return QAquaSizeLarge; +} +#endif + +QAquaWidgetSize QMacStylePrivate::aquaSizeConstrain(const QStyleOption *option, const QWidget *widg, + QStyle::ContentsType ct, QSize szHint, QSize *insz) const +{ +#if defined(QMAC_QAQUASTYLE_SIZE_CONSTRAIN) || defined(DEBUG_SIZE_CONSTRAINT) + if (option) { + if (option->state & QStyle::State_Small) + return QAquaSizeSmall; + if (option->state & QStyle::State_Mini) + return QAquaSizeMini; + } + + if (!widg) { + if (insz) + *insz = QSize(); + if (!qgetenv("QWIDGET_ALL_SMALL").isNull()) + return QAquaSizeSmall; + if (!qgetenv("QWIDGET_ALL_MINI").isNull()) + return QAquaSizeMini; + return QAquaSizeUnknown; + } + QSize large = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeLarge), + small = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeSmall), + mini = qt_aqua_get_known_size(ct, widg, szHint, QAquaSizeMini); + bool guess_size = false; + QAquaWidgetSize ret = QAquaSizeUnknown; + QMacStyle::WidgetSizePolicy wsp = q->widgetSizePolicy(widg); + if (wsp == QMacStyle::SizeDefault) + guess_size = true; + else if (wsp == QMacStyle::SizeMini) + ret = QAquaSizeMini; + else if (wsp == QMacStyle::SizeSmall) + ret = QAquaSizeSmall; + else if (wsp == QMacStyle::SizeLarge) + ret = QAquaSizeLarge; + if (guess_size) + ret = qt_aqua_guess_size(widg, large, small, mini); + + QSize *sz = 0; + if (ret == QAquaSizeSmall) + sz = &small; + else if (ret == QAquaSizeLarge) + sz = &large; + else if (ret == QAquaSizeMini) + sz = &mini; + if (insz) + *insz = sz ? *sz : QSize(-1, -1); +#ifdef DEBUG_SIZE_CONSTRAINT + if (sz) { + const char *size_desc = "Unknown"; + if (sz == &small) + size_desc = "Small"; + else if (sz == &large) + size_desc = "Large"; + else if (sz == &mini) + size_desc = "Mini"; + qDebug("%s - %s: %s taken (%d, %d) [%d, %d]", + widg ? widg->objectName().toLatin1().constData() : "*Unknown*", + widg ? widg->metaObject()->className() : "*Unknown*", size_desc, widg->width(), widg->height(), + sz->width(), sz->height()); + } +#endif + return ret; +#else + if (insz) + *insz = QSize(); + Q_UNUSED(widg); + Q_UNUSED(ct); + Q_UNUSED(szHint); + return QAquaSizeUnknown; +#endif +} + +/** + Returns the free space awailable for contents inside the + button (and not the size of the contents itself) +*/ +HIRect QMacStylePrivate::pushButtonContentBounds(const QStyleOptionButton *btn, + const HIThemeButtonDrawInfo *bdi) const +{ + HIRect outerBounds = qt_hirectForQRect(btn->rect); + // Adjust the bounds to correct for + // carbon not calculating the content bounds fully correct + if (bdi->kind == kThemePushButton || bdi->kind == kThemePushButtonSmall){ + outerBounds.origin.y += QMacStylePrivate::PushButtonTopOffset; + outerBounds.size.height -= QMacStylePrivate::PushButtonBottomOffset; + } else if (bdi->kind == kThemePushButtonMini) { + outerBounds.origin.y += QMacStylePrivate::PushButtonTopOffset; + } + + HIRect contentBounds; + HIThemeGetButtonContentBounds(&outerBounds, bdi, &contentBounds); + return contentBounds; +} + +/** + Calculates the size of the button contents. + This includes both the text and the icon. +*/ +QSize QMacStylePrivate::pushButtonSizeFromContents(const QStyleOptionButton *btn) const +{ + QSize csz(0, 0); + QSize iconSize = btn->icon.isNull() ? QSize(0, 0) + : (btn->iconSize + QSize(QMacStylePrivate::PushButtonContentPadding, 0)); + QRect textRect = btn->text.isEmpty() ? QRect(0, 0, 1, 1) + : btn->fontMetrics.boundingRect(QRect(), Qt::AlignCenter, btn->text); + csz.setWidth(iconSize.width() + textRect.width() + + ((btn->features & QStyleOptionButton::HasMenu) + ? q->proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, 0) : 0)); + csz.setHeight(qMax(iconSize.height(), textRect.height())); + return csz; +} + +/** + Checks if the actual contents of btn fits inside the free content bounds of + 'buttonKindToCheck'. Meant as a helper function for 'initHIThemePushButton' + for determining which button kind to use for drawing. +*/ +bool QMacStylePrivate::contentFitsInPushButton(const QStyleOptionButton *btn, + HIThemeButtonDrawInfo *bdi, + ThemeButtonKind buttonKindToCheck) const +{ + ThemeButtonKind tmp = bdi->kind; + bdi->kind = buttonKindToCheck; + QSize contentSize = pushButtonSizeFromContents(btn); + QRect freeContentRect = qt_qrectForHIRect(pushButtonContentBounds(btn, bdi)); + bdi->kind = tmp; + return freeContentRect.contains(QRect(freeContentRect.x(), freeContentRect.y(), + contentSize.width(), contentSize.height())); +} + +/** + Creates a HIThemeButtonDrawInfo structure that specifies the correct button + kind and other details to use for drawing the given push button. Which + button kind depends on the size of the button, the size of the contents, + explicit user style settings, etc. +*/ +void QMacStylePrivate::initHIThemePushButton(const QStyleOptionButton *btn, + const QWidget *widget, + const ThemeDrawState tds, + HIThemeButtonDrawInfo *bdi) const +{ + bool drawColorless = btn->palette.currentColorGroup() == QPalette::Active; + ThemeDrawState tdsModified = tds; + if (btn->state & QStyle::State_On) + tdsModified = kThemeStatePressed; + bdi->version = qt_mac_hitheme_version; + bdi->state = tdsModified; + bdi->value = kThemeButtonOff; + + if (drawColorless && tdsModified == kThemeStateInactive) + bdi->state = kThemeStateActive; + if (btn->state & QStyle::State_HasFocus) + bdi->adornment = kThemeAdornmentFocus; + else + bdi->adornment = kThemeAdornmentNone; + + + if (btn->features & (QStyleOptionButton::Flat)) { + bdi->kind = kThemeBevelButton; + } else { + switch (aquaSizeConstrain(btn, widget)) { + case QAquaSizeSmall: + bdi->kind = kThemePushButtonSmall; + break; + case QAquaSizeMini: + bdi->kind = kThemePushButtonMini; + break; + case QAquaSizeLarge: + // ... We should honor if the user is explicit about using the + // large button. But right now Qt will specify the large button + // as default rather than QAquaSizeUnknown. + // So we treat it like QAquaSizeUnknown + // to get the dynamic choosing of button kind. + case QAquaSizeUnknown: + // Choose the button kind that closest match the button rect, but at the + // same time displays the button contents without clipping. + bdi->kind = kThemeBevelButton; + if (btn->rect.width() >= QMacStylePrivate::BevelButtonW && btn->rect.height() >= QMacStylePrivate::BevelButtonH){ + if (widget && widget->testAttribute(Qt::WA_MacVariableSize)) { + if (btn->rect.height() <= QMacStylePrivate::MiniButtonH){ + if (contentFitsInPushButton(btn, bdi, kThemePushButtonMini)) + bdi->kind = kThemePushButtonMini; + } else if (btn->rect.height() <= QMacStylePrivate::SmallButtonH){ + if (contentFitsInPushButton(btn, bdi, kThemePushButtonSmall)) + bdi->kind = kThemePushButtonSmall; + } else if (contentFitsInPushButton(btn, bdi, kThemePushButton)) { + bdi->kind = kThemePushButton; + } + } else { + bdi->kind = kThemePushButton; + } + } + } + } +} + +bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option) +{ + QMacStyle *macStyle = qobject_cast<QMacStyle *>(pushButton->style()); + if (!macStyle) + return false; + HIThemeButtonDrawInfo bdi; + macStyle->d->initHIThemePushButton(option, pushButton, kThemeStateActive, &bdi); + return bdi.kind == kThemeBevelButton; +} + +/** + Creates a HIThemeButtonDrawInfo structure that specifies the correct button + kind and other details to use for drawing the given combobox. Which button + kind depends on the size of the combo, wether or not it is editable, + explicit user style settings, etc. +*/ +void QMacStylePrivate::initComboboxBdi(const QStyleOptionComboBox *combo, HIThemeButtonDrawInfo *bdi, + const QWidget *widget, const ThemeDrawState &tds) +{ + bdi->version = qt_mac_hitheme_version; + bdi->adornment = kThemeAdornmentArrowLeftArrow; + bdi->value = kThemeButtonOff; + if (combo->state & QStyle::State_HasFocus) + bdi->adornment = kThemeAdornmentFocus; + bool drawColorless = combo->palette.currentColorGroup() == QPalette::Active && tds == kThemeStateInactive; + if (combo->activeSubControls & QStyle::SC_ComboBoxArrow) + bdi->state = kThemeStatePressed; + else if (drawColorless) + bdi->state = kThemeStateActive; + else + bdi->state = tds; + + QAquaWidgetSize aSize = aquaSizeConstrain(combo, widget); + switch (aSize) { + case QAquaSizeMini: + bdi->kind = combo->editable ? ThemeButtonKind(kThemeComboBoxMini) + : ThemeButtonKind(kThemePopupButtonMini); + break; + case QAquaSizeSmall: + bdi->kind = combo->editable ? ThemeButtonKind(kThemeComboBoxSmall) + : ThemeButtonKind(kThemePopupButtonSmall); + break; + case QAquaSizeUnknown: + case QAquaSizeLarge: + // Unless the user explicitly specified large buttons, determine the + // kind by looking at the combox size. + // ... specifying small and mini-buttons it not a current feature of + // Qt (e.g. QWidget::getAttribute(WA_ButtonSize)). But when it is, add + // an extra check here before using the mini and small buttons. + int h = combo->rect.size().height(); + if (combo->editable){ + if (h < 21) + bdi->kind = kThemeComboBoxMini; + else if (h < 26) + bdi->kind = kThemeComboBoxSmall; + else + bdi->kind = kThemeComboBox; + } else { + // Even if we specify that we want the kThemePopupButton, Carbon + // will use the kThemePopupButtonSmall if the size matches. So we + // do the same size check explicit to have the size of the inner + // text field be correct. Therefore, do this even if the user specifies + // the use of LargeButtons explicit. + if (h < 21) + bdi->kind = kThemePopupButtonMini; + else if (h < 26) + bdi->kind = kThemePopupButtonSmall; + else + bdi->kind = kThemePopupButton; + } + break; + } +} + +/** + Carbon draws comboboxes (and other views) outside the rect given as argument. Use this function to obtain + the corresponding inner rect for drawing the same combobox so that it stays inside the given outerBounds. +*/ +HIRect QMacStylePrivate::comboboxInnerBounds(const HIRect &outerBounds, int buttonKind) +{ + HIRect innerBounds = outerBounds; + // Carbon draw parts of the view outside the rect. + // So make the rect a bit smaller to compensate + // (I wish HIThemeGetButtonBackgroundBounds worked) + switch (buttonKind){ + case kThemePopupButton: + innerBounds.origin.x += 2; + innerBounds.origin.y += 3; + innerBounds.size.width -= 5; + innerBounds.size.height -= 6; + break; + case kThemePopupButtonSmall: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 6; + innerBounds.size.height -= 7; + break; + case kThemePopupButtonMini: + innerBounds.origin.x += 2; + innerBounds.origin.y += 2; + innerBounds.size.width -= 5; + innerBounds.size.height -= 6; + break; + case kThemeComboBox: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 6; + innerBounds.size.height -= 6; + break; + case kThemeComboBoxSmall: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 7; + innerBounds.size.height -= 8; + break; + case kThemeComboBoxMini: + innerBounds.origin.x += 3; + innerBounds.origin.y += 3; + innerBounds.size.width -= 4; + innerBounds.size.height -= 8; + break; + default: + break; + } + return innerBounds; +} + +/** + Inside a combobox Qt places a line edit widget. The size of this widget should depend on the kind + of combobox we choose to draw. This function calculates and returns this size. +*/ +QRect QMacStylePrivate::comboboxEditBounds(const QRect &outerBounds, const HIThemeButtonDrawInfo &bdi) +{ + QRect ret = outerBounds; + switch (bdi.kind){ + case kThemeComboBox: + ret.adjust(5, 8, -21, -4); + break; + case kThemeComboBoxSmall: + ret.adjust(4, 5, -18, 0); + ret.setHeight(16); + break; + case kThemeComboBoxMini: + ret.adjust(4, 5, -16, 0); + ret.setHeight(13); + break; + case kThemePopupButton: + ret.adjust(10, 3, -23, -3); + break; + case kThemePopupButtonSmall: + ret.adjust(9, 3, -20, -3); + break; + case kThemePopupButtonMini: + ret.adjust(8, 3, -19, 0); + ret.setHeight(13); + break; + } + return ret; +} + +/** + Carbon comboboxes don't scale (sight). If the size of the combo suggest a scaled version, + create it manually by drawing a small Carbon combo onto a pixmap (use pixmap cache), chop + it up, and copy it back onto the widget. Othervise, draw the combobox supplied by Carbon directly. +*/ +void QMacStylePrivate::drawCombobox(const HIRect &outerBounds, const HIThemeButtonDrawInfo &bdi, QPainter *p) +{ + if (!(bdi.kind == kThemeComboBox && outerBounds.size.height > 28)){ + // We have an unscaled combobox, or popup-button; use Carbon directly. + HIRect innerBounds = QMacStylePrivate::comboboxInnerBounds(outerBounds, bdi.kind); + HIThemeDrawButton(&innerBounds, &bdi, QMacCGContext(p), kHIThemeOrientationNormal, 0); + } else { + QPixmap buffer; + QString key = QString(QLatin1String("$qt_cbox%1-%2")).arg(int(bdi.state)).arg(int(bdi.adornment)); + if (!QPixmapCache::find(key, buffer)) { + HIRect innerBoundsSmallCombo = {{3, 3}, {29, 25}}; + buffer = QPixmap(35, 28); + buffer.fill(Qt::transparent); + QPainter buffPainter(&buffer); + HIThemeDrawButton(&innerBoundsSmallCombo, &bdi, QMacCGContext(&buffPainter), kHIThemeOrientationNormal, 0); + buffPainter.end(); + QPixmapCache::insert(key, buffer); + } + + const int bwidth = 20; + const int fwidth = 10; + const int fheight = 10; + int w = qRound(outerBounds.size.width); + int h = qRound(outerBounds.size.height); + int bstart = w - bwidth; + int blower = fheight + 1; + int flower = h - fheight; + int sheight = flower - fheight; + int center = qRound(outerBounds.size.height + outerBounds.origin.y) / 2; + + // Draw upper and lower gap + p->drawPixmap(fwidth, 0, bstart - fwidth, fheight, buffer, fwidth, 0, 1, fheight); + p->drawPixmap(fwidth, flower, bstart - fwidth, fheight, buffer, fwidth, buffer.height() - fheight, 1, fheight); + // Draw left and right gap. Right gap is drawn top and bottom separatly + p->drawPixmap(0, fheight, fwidth, sheight, buffer, 0, fheight, fwidth, 1); + p->drawPixmap(bstart, fheight, bwidth, center - fheight, buffer, buffer.width() - bwidth, fheight - 1, bwidth, 1); + p->drawPixmap(bstart, center, bwidth, sheight / 2, buffer, buffer.width() - bwidth, fheight + 6, bwidth, 1); + // Draw arrow + p->drawPixmap(bstart, center - 4, bwidth - 3, 6, buffer, buffer.width() - bwidth, fheight, bwidth - 3, 6); + // Draw corners + p->drawPixmap(0, 0, fwidth, fheight, buffer, 0, 0, fwidth, fheight); + p->drawPixmap(bstart, 0, bwidth, fheight, buffer, buffer.width() - bwidth, 0, bwidth, fheight); + p->drawPixmap(0, flower, fwidth, fheight, buffer, 0, buffer.height() - fheight, fwidth, fheight); + p->drawPixmap(bstart, h - blower, bwidth, blower, buffer, buffer.width() - bwidth, buffer.height() - blower, bwidth, blower); + } +} + +/** + Carbon tableheaders don't scale (sight). So create it manually by drawing a small Carbon header + onto a pixmap (use pixmap cache), chop it up, and copy it back onto the widget. +*/ +void QMacStylePrivate::drawTableHeader(const HIRect &outerBounds, + bool drawTopBorder, bool drawLeftBorder, const HIThemeButtonDrawInfo &bdi, QPainter *p) +{ + static SInt32 headerHeight = 0; + static OSStatus err = GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight); + Q_UNUSED(err); + + QPixmap buffer; + QString key = QString(QLatin1String("$qt_tableh%1-%2-%3")).arg(int(bdi.state)).arg(int(bdi.adornment)).arg(int(bdi.value)); + if (!QPixmapCache::find(key, buffer)) { + HIRect headerNormalRect = {{0., 0.}, {16., CGFloat(headerHeight)}}; + buffer = QPixmap(headerNormalRect.size.width, headerNormalRect.size.height); + buffer.fill(Qt::transparent); + QPainter buffPainter(&buffer); + HIThemeDrawButton(&headerNormalRect, &bdi, QMacCGContext(&buffPainter), kHIThemeOrientationNormal, 0); + buffPainter.end(); + QPixmapCache::insert(key, buffer); + } + const int buttonw = qRound(outerBounds.size.width); + const int buttonh = qRound(outerBounds.size.height); + const int framew = 1; + const int frameh_n = 4; + const int frameh_s = 3; + const int transh = buffer.height() - frameh_n - frameh_s; + int center = buttonh - frameh_s - int(transh / 2.0f) + 1; // Align bottom; + + int skipTopBorder = 0; + if (!drawTopBorder) + skipTopBorder = 1; + + p->translate(outerBounds.origin.x, outerBounds.origin.y); + + p->drawPixmap(QRect(QRect(0, -skipTopBorder, buttonw - framew , frameh_n)), buffer, QRect(framew, 0, 1, frameh_n)); + p->drawPixmap(QRect(0, buttonh - frameh_s, buttonw - framew, frameh_s), buffer, QRect(framew, buffer.height() - frameh_s, 1, frameh_s)); + // Draw upper and lower center blocks + p->drawPixmap(QRect(0, frameh_n - skipTopBorder, buttonw - framew, center - frameh_n + skipTopBorder), buffer, QRect(framew, frameh_n, 1, 1)); + p->drawPixmap(QRect(0, center, buttonw - framew, buttonh - center - frameh_s), buffer, QRect(framew, buffer.height() - frameh_s, 1, 1)); + // Draw right center block borders + p->drawPixmap(QRect(buttonw - framew, frameh_n - skipTopBorder, framew, center - frameh_n), buffer, QRect(buffer.width() - framew, frameh_n, framew, 1)); + p->drawPixmap(QRect(buttonw - framew, center, framew, buttonh - center - 1), buffer, QRect(buffer.width() - framew, buffer.height() - frameh_s, framew, 1)); + // Draw right corners + p->drawPixmap(QRect(buttonw - framew, -skipTopBorder, framew, frameh_n), buffer, QRect(buffer.width() - framew, 0, framew, frameh_n)); + p->drawPixmap(QRect(buttonw - framew, buttonh - frameh_s, framew, frameh_s), buffer, QRect(buffer.width() - framew, buffer.height() - frameh_s, framew, frameh_s)); + // Draw center transition block + p->drawPixmap(QRect(0, center - qRound(transh / 2.0f), buttonw - framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(framew, frameh_n + 1, 1, transh)); + // Draw right center transition block border + p->drawPixmap(QRect(buttonw - framew, center - qRound(transh / 2.0f), framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(buffer.width() - framew, frameh_n + 1, framew, transh)); + if (drawLeftBorder){ + // Draw left center block borders + p->drawPixmap(QRect(0, frameh_n - skipTopBorder, framew, center - frameh_n + skipTopBorder), buffer, QRect(0, frameh_n, framew, 1)); + p->drawPixmap(QRect(0, center, framew, buttonh - center - 1), buffer, QRect(0, buffer.height() - frameh_s, framew, 1)); + // Draw left corners + p->drawPixmap(QRect(0, -skipTopBorder, framew, frameh_n), buffer, QRect(0, 0, framew, frameh_n)); + p->drawPixmap(QRect(0, buttonh - frameh_s, framew, frameh_s), buffer, QRect(0, buffer.height() - frameh_s, framew, frameh_s)); + // Draw left center transition block border + p->drawPixmap(QRect(0, center - qRound(transh / 2.0f), framew, buffer.height() - frameh_n - frameh_s), buffer, QRect(0, frameh_n + 1, framew, transh)); + } + + p->translate(-outerBounds.origin.x, -outerBounds.origin.y); +} + +/* + Returns cutoff sizes for scroll bars. + thumbIndicatorCutoff is the smallest size where the thumb indicator is drawn. + scrollButtonsCutoff is the smallest size where the up/down buttons is drawn. +*/ +enum ScrollBarCutoffType { thumbIndicatorCutoff = 0, scrollButtonsCutoff = 1 }; +static int scrollButtonsCutoffSize(ScrollBarCutoffType cutoffType, QMacStyle::WidgetSizePolicy widgetSize) +{ + // Mini scroll bars do not exist as of version 10.4. + if (widgetSize == QMacStyle::SizeMini) + return 0; + + const int sizeIndex = (widgetSize == QMacStyle::SizeSmall) ? 1 : 0; + static const int sizeTable[2][2] = { { 61, 56 }, { 49, 44 } }; + return sizeTable[sizeIndex][cutoffType]; +} + +void QMacStylePrivate::getSliderInfo(QStyle::ComplexControl cc, const QStyleOptionSlider *slider, + HIThemeTrackDrawInfo *tdi, const QWidget *needToRemoveMe) +{ + memset(tdi, 0, sizeof(HIThemeTrackDrawInfo)); // We don't get it all for some reason or another... + tdi->version = qt_mac_hitheme_version; + tdi->reserved = 0; + tdi->filler1 = 0; + bool isScrollbar = (cc == QStyle::CC_ScrollBar); + switch (aquaSizeConstrain(0, needToRemoveMe)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (isScrollbar) + tdi->kind = kThemeMediumScrollBar; + else + tdi->kind = kThemeMediumSlider; + break; + case QAquaSizeMini: + if (isScrollbar) + tdi->kind = kThemeSmallScrollBar; // should be kThemeMiniScrollBar, but not implemented + else + tdi->kind = kThemeMiniSlider; + break; + case QAquaSizeSmall: + if (isScrollbar) + tdi->kind = kThemeSmallScrollBar; + else + tdi->kind = kThemeSmallSlider; + break; + } + tdi->bounds = qt_hirectForQRect(slider->rect); + tdi->min = slider->minimum; + tdi->max = slider->maximum; + tdi->value = slider->sliderPosition; + tdi->attributes = kThemeTrackShowThumb; + if (slider->upsideDown) + tdi->attributes |= kThemeTrackRightToLeft; + if (slider->orientation == Qt::Horizontal) { + tdi->attributes |= kThemeTrackHorizontal; + if (isScrollbar && slider->direction == Qt::RightToLeft) { + if (!slider->upsideDown) + tdi->attributes |= kThemeTrackRightToLeft; + else + tdi->attributes &= ~kThemeTrackRightToLeft; + } + } + + // Tiger broke reverse scroll bars so put them back and "fake it" + if (isScrollbar && (tdi->attributes & kThemeTrackRightToLeft)) { + tdi->attributes &= ~kThemeTrackRightToLeft; + tdi->value = tdi->max - slider->sliderPosition; + } + + tdi->enableState = (slider->state & QStyle::State_Enabled) ? kThemeTrackActive + : kThemeTrackDisabled; + if (!(slider->state & QStyle::State_Active)) + tdi->enableState = kThemeTrackInactive; + if (!isScrollbar) { + if (slider->state & QStyle::QStyle::State_HasFocus) + tdi->attributes |= kThemeTrackHasFocus; + if (slider->tickPosition == QSlider::NoTicks || slider->tickPosition == QSlider::TicksBothSides) + tdi->trackInfo.slider.thumbDir = kThemeThumbPlain; + else if (slider->tickPosition == QSlider::TicksAbove) + tdi->trackInfo.slider.thumbDir = kThemeThumbUpward; + else + tdi->trackInfo.slider.thumbDir = kThemeThumbDownward; + } else { + tdi->trackInfo.scrollbar.viewsize = slider->pageStep; + } +} +#endif + +QMacStylePrivate::QMacStylePrivate(QMacStyle *style) + : timerID(-1), progressFrame(0), q(style), mouseDown(false) +{ + defaultButtonStart = CFAbsoluteTimeGetCurrent(); + memset(&buttonState, 0, sizeof(ButtonState)); + + if (ptrHIShapeGetBounds == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/Carbon.framework/Carbon")); + library.setLoadHints(QLibrary::ExportExternalSymbolsHint); + ptrHIShapeGetBounds = reinterpret_cast<PtrHIShapeGetBounds>(library.resolve("HIShapeGetBounds")); + } + +} + +bool QMacStylePrivate::animatable(QMacStylePrivate::Animates as, const QWidget *w) const +{ + if (!w) + return false; + + if (as == AquaPushButton) { + QPushButton *pb = const_cast<QPushButton *>(static_cast<const QPushButton *>(w)); + if (w->window()->isActiveWindow() && pb && !mouseDown) { + if (static_cast<const QPushButton *>(w) != defaultButton) { + // Changed on its own, update the value. + const_cast<QMacStylePrivate *>(this)->stopAnimate(as, defaultButton); + const_cast<QMacStylePrivate *>(this)->startAnimate(as, pb); + } + return true; + } + } else if (as == AquaProgressBar) { + if (progressBars.contains((const_cast<QWidget *>(w)))) + return true; + } + return false; +} + +void QMacStylePrivate::stopAnimate(QMacStylePrivate::Animates as, QWidget *w) +{ + if (as == AquaPushButton && defaultButton) { + QPushButton *tmp = defaultButton; + defaultButton = 0; + tmp->update(); + } else if (as == AquaProgressBar) { + progressBars.removeAll(w); + } +} + +void QMacStylePrivate::startAnimate(QMacStylePrivate::Animates as, QWidget *w) +{ + if (as == AquaPushButton) + defaultButton = static_cast<QPushButton *>(w); + else if (as == AquaProgressBar) + progressBars.append(w); + startAnimationTimer(); +} + +void QMacStylePrivate::startAnimationTimer() +{ + if ((defaultButton || !progressBars.isEmpty()) && timerID <= -1) + timerID = startTimer(animateSpeed(AquaListViewItemOpen)); +} + +bool QMacStylePrivate::addWidget(QWidget *w) +{ + //already knew of it + if (static_cast<QPushButton*>(w) == defaultButton + || progressBars.contains(static_cast<QProgressBar*>(w))) + return false; + + if (QPushButton *btn = qobject_cast<QPushButton *>(w)) { + btn->installEventFilter(this); + if (btn->isDefault() || (btn->autoDefault() && btn->hasFocus())) + startAnimate(AquaPushButton, btn); + return true; + } else { + bool isProgressBar = (qobject_cast<QProgressBar *>(w) +#ifdef QT3_SUPPORT + || w->inherits("Q3ProgressBar") +#endif + ); + if (isProgressBar) { + w->installEventFilter(this); + startAnimate(AquaProgressBar, w); + return true; + } + } + if (w->isWindow()) { + w->installEventFilter(this); + return true; + } + return false; +} + +void QMacStylePrivate::removeWidget(QWidget *w) +{ + QPushButton *btn = qobject_cast<QPushButton *>(w); + if (btn && btn == defaultButton) { + stopAnimate(AquaPushButton, btn); + } else if (qobject_cast<QProgressBar *>(w) +#ifdef QT3_SUPPORT + || w->inherits("Q3ProgressBar") +#endif + ) { + stopAnimate(AquaProgressBar, w); + } +} + +ThemeDrawState QMacStylePrivate::getDrawState(QStyle::State flags) +{ + ThemeDrawState tds = kThemeStateActive; + if (flags & QStyle::State_Sunken) { + tds = kThemeStatePressed; + } else if (flags & QStyle::State_Active) { + if (!(flags & QStyle::State_Enabled)) + tds = kThemeStateUnavailable; + } else { + if (flags & QStyle::State_Enabled) + tds = kThemeStateInactive; + else + tds = kThemeStateUnavailableInactive; + } + return tds; +} + +void QMacStylePrivate::timerEvent(QTimerEvent *) +{ + int animated = 0; + if (defaultButton && defaultButton->isEnabled() && defaultButton->window()->isActiveWindow() + && defaultButton->isVisibleTo(0) && (defaultButton->isDefault() + || (defaultButton->autoDefault() && defaultButton->hasFocus())) + && doAnimate(AquaPushButton)) { + ++animated; + defaultButton->update(); + } + if (!progressBars.isEmpty()) { + int i = 0; + while (i < progressBars.size()) { + QWidget *maybeProgress = progressBars.at(i); + if (!maybeProgress) { + progressBars.removeAt(i); + } else { + if (QProgressBar *pb = qobject_cast<QProgressBar *>(maybeProgress)) { + if (pb->maximum() == 0 || (pb->value() > 0 && pb->value() < pb->maximum())) { + if (doAnimate(AquaProgressBar)) + pb->update(); + } + } +#ifdef QT3_SUPPORT + else { + // Watch me now... + QVariant progress = maybeProgress->property("progress"); + QVariant totalSteps = maybeProgress->property("totalSteps"); + if (progress.isValid() && totalSteps.isValid()) { + int intProgress = progress.toInt(); + int intTotalSteps = totalSteps.toInt(); + if (intTotalSteps == 0 || intProgress > 0 && intProgress < intTotalSteps) { + if (doAnimate(AquaProgressBar)) + maybeProgress->update(); + } + } + } +#endif + ++i; + } + } + if (i > 0) { + ++progressFrame; + animated += i; + } + } + if (animated <= 0) { + killTimer(timerID); + timerID = -1; + } +} + +bool QMacStylePrivate::eventFilter(QObject *o, QEvent *e) +{ + //animate + if (QProgressBar *pb = qobject_cast<QProgressBar *>(o)) { + switch (e->type()) { + default: + break; + case QEvent::Show: + if (!progressBars.contains(pb)) + startAnimate(AquaProgressBar, pb); + break; + case QEvent::Destroy: + case QEvent::Hide: + progressBars.removeAll(pb); + } + } else if (QPushButton *btn = qobject_cast<QPushButton *>(o)) { + switch (e->type()) { + default: + break; + case QEvent::FocusIn: + if (btn->autoDefault()) + startAnimate(AquaPushButton, btn); + break; + case QEvent::Destroy: + case QEvent::Hide: + if (btn == defaultButton) + stopAnimate(AquaPushButton, btn); + break; + case QEvent::MouseButtonPress: + // It is very confusing to keep the button pulsing, so just stop the animation. + if (static_cast<QMouseEvent *>(e)->button() == Qt::LeftButton) + mouseDown = true; + stopAnimate(AquaPushButton, btn); + break; + case QEvent::MouseButtonRelease: + if (static_cast<QMouseEvent *>(e)->button() == Qt::LeftButton) + mouseDown = false; + // fall through + case QEvent::FocusOut: + case QEvent::Show: + case QEvent::WindowActivate: { + QList<QPushButton *> list = btn->window()->findChildren<QPushButton *>(); + for (int i = 0; i < list.size(); ++i) { + QPushButton *pBtn = list.at(i); + if ((e->type() == QEvent::FocusOut + && (pBtn->isDefault() || (pBtn->autoDefault() && pBtn->hasFocus())) + && pBtn != btn) + || ((e->type() == QEvent::Show || e->type() == QEvent::MouseButtonRelease + || e->type() == QEvent::WindowActivate) + && pBtn->isDefault())) { + if (pBtn->window()->isActiveWindow()) { + startAnimate(AquaPushButton, pBtn); + } + break; + } + } + break; } + } + } + return false; +} + +bool QMacStylePrivate::doAnimate(QMacStylePrivate::Animates as) +{ + if (as == AquaPushButton) { + } else if (as == AquaProgressBar) { + // something for later... + } else if (as == AquaListViewItemOpen) { + // To be revived later... + } + return true; +} + +void QMacStylePrivate::drawColorlessButton(const HIRect &macRect, HIThemeButtonDrawInfo *bdi, + QPainter *p, const QStyleOption *opt) const +{ + int xoff = 0, + yoff = 0, + extraWidth = 0, + extraHeight = 0, + finalyoff = 0; + + const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt); + int width = int(macRect.size.width) + extraWidth; + int height = int(macRect.size.height) + extraHeight; + + if (width <= 0 || height <= 0) + return; // nothing to draw + + QString key = QLatin1String("$qt_mac_style_ctb_") + QString::number(bdi->kind) + QLatin1Char('_') + + QString::number(bdi->value) + QLatin1Char('_') + QString::number(width) + + QLatin1Char('_') + QString::number(height); + QPixmap pm; + if (!QPixmapCache::find(key, pm)) { + QPixmap activePixmap(width, height); + activePixmap.fill(Qt::transparent); + { + if (combo){ + // Carbon combos don't scale. Therefore we draw it + // ourselves, if a scaled version is needed. + QPainter tmpPainter(&activePixmap); + QMacStylePrivate::drawCombobox(macRect, *bdi, &tmpPainter); + } + else { + QMacCGContext cg(&activePixmap); + HIRect newRect = CGRectMake(xoff, yoff, macRect.size.width, macRect.size.height); + HIThemeDrawButton(&newRect, bdi, cg, kHIThemeOrientationNormal, 0); + } + } + + if (!combo && bdi->value == kThemeButtonOff) { + pm = activePixmap; + } else if (combo) { + QImage image = activePixmap.toImage(); + + for (int y = 0; y < height; ++y) { + QRgb *scanLine = reinterpret_cast<QRgb *>(image.scanLine(y)); + + for (int x = 0; x < width; ++x) { + QRgb &pixel = scanLine[x]; + + int darkest = qRed(pixel); + int mid = qGreen(pixel); + int lightest = qBlue(pixel); + + if (darkest > mid) + qSwap(darkest, mid); + if (mid > lightest) + qSwap(mid, lightest); + if (darkest > mid) + qSwap(darkest, mid); + + int gray = (mid + 2 * lightest) / 3; + pixel = qRgba(gray, gray, gray, qAlpha(pixel)); + } + } + pm = QPixmap::fromImage(image); + } else { + QImage activeImage = activePixmap.toImage(); + QImage colorlessImage; + { + QPixmap colorlessPixmap(width, height); + colorlessPixmap.fill(Qt::transparent); + + QMacCGContext cg(&colorlessPixmap); + HIRect newRect = CGRectMake(xoff, yoff, macRect.size.width, macRect.size.height); + int oldValue = bdi->value; + bdi->value = kThemeButtonOff; + HIThemeDrawButton(&newRect, bdi, cg, kHIThemeOrientationNormal, 0); + bdi->value = oldValue; + colorlessImage = colorlessPixmap.toImage(); + } + + for (int y = 0; y < height; ++y) { + QRgb *colorlessScanLine = reinterpret_cast<QRgb *>(colorlessImage.scanLine(y)); + const QRgb *activeScanLine = reinterpret_cast<const QRgb *>(activeImage.scanLine(y)); + + for (int x = 0; x < width; ++x) { + QRgb &colorlessPixel = colorlessScanLine[x]; + QRgb activePixel = activeScanLine[x]; + + if (activePixel != colorlessPixel) { + int max = qMax(qMax(qRed(activePixel), qGreen(activePixel)), + qBlue(activePixel)); + QRgb newPixel = qRgba(max, max, max, qAlpha(activePixel)); + if (qGray(newPixel) < qGray(colorlessPixel) + || qAlpha(newPixel) > qAlpha(colorlessPixel)) + colorlessPixel = newPixel; + } + } + } + pm = QPixmap::fromImage(colorlessImage); + } + QPixmapCache::insert(key, pm); + } + p->drawPixmap(int(macRect.origin.x), int(macRect.origin.y) + finalyoff, width, height, pm); +} + +QMacStyle::QMacStyle() + : QWindowsStyle() +{ + d = new QMacStylePrivate(this); +} + +QMacStyle::~QMacStyle() +{ + delete qt_mac_backgroundPattern; + qt_mac_backgroundPattern = 0; + delete d; +} + +/*! \internal + Generates the standard widget background pattern. +*/ +QPixmap QMacStylePrivate::generateBackgroundPattern() const +{ + QPixmap px(4, 4); + QMacCGContext cg(&px); + HIThemeSetFill(kThemeBrushDialogBackgroundActive, 0, cg, kHIThemeOrientationNormal); + const CGRect cgRect = CGRectMake(0, 0, px.width(), px.height()); + CGContextFillRect(cg, cgRect); + return px; +} + +/*! \internal + Fills the given \a rect with the pattern stored in \a brush. As an optimization, + HIThemeSetFill us used directly if we are filling with the standard background. +*/ +void qt_mac_fill_background(QPainter *painter, const QRegion &rgn, const QBrush &brush) +{ + QPoint dummy; + const QPaintDevice *target = painter->device(); + const QPaintDevice *redirected = QPainter::redirected(target, &dummy); + const bool usePainter = redirected && redirected != target; + + if (!usePainter && qt_mac_backgroundPattern + && qt_mac_backgroundPattern->cacheKey() == brush.texture().cacheKey()) { + + painter->setClipRegion(rgn); + + QCFType<CGContextRef> cg = qt_mac_cg_context(target); + CGContextSaveGState(cg); + HIThemeSetFill(kThemeBrushDialogBackgroundActive, 0, cg, kHIThemeOrientationInverted); + + const QVector<QRect> &rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) { + const QRect rect(rects.at(i)); + // Anchor the pattern to the top so it stays put when the window is resized. + CGContextSetPatternPhase(cg, CGSizeMake(rect.width(), rect.height())); + CGRect mac_rect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + CGContextFillRect(cg, mac_rect); + } + + CGContextRestoreGState(cg); + } else { + const QRect rect(rgn.boundingRect()); + painter->setClipRegion(rgn); + painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft()); + } +} + +void QMacStyle::polish(QPalette &pal) +{ + if (!qt_mac_backgroundPattern) { + if (!qApp) + return; + qt_mac_backgroundPattern = new QPixmap(d->generateBackgroundPattern()); + } + + QColor pc(Qt::black); + pc = qcolorForTheme(kThemeBrushDialogBackgroundActive); + QBrush background(pc, *qt_mac_backgroundPattern); + pal.setBrush(QPalette::All, QPalette::Window, background); + pal.setBrush(QPalette::All, QPalette::Button, background); + + QCFString theme; + const OSErr err = CopyThemeIdentifier(&theme); + if (err == noErr && CFStringCompare(theme, kThemeAppearanceAquaGraphite, 0) == kCFCompareEqualTo) { + pal.setBrush(QPalette::All, QPalette::AlternateBase, QColor(240, 240, 240)); + } else { + pal.setBrush(QPalette::All, QPalette::AlternateBase, QColor(237, 243, 254)); + } +} + +void QMacStyle::polish(QApplication *) +{ +} + +void QMacStyle::unpolish(QApplication *) +{ +} + +void QMacStyle::polish(QWidget* w) +{ + d->addWidget(w); + if (qt_mac_is_metal(w) && !w->testAttribute(Qt::WA_SetPalette)) { + // Set a clear brush so that the metal shines through. + QPalette pal = w->palette(); + QBrush background(Qt::transparent); + pal.setBrush(QPalette::All, QPalette::Window, background); + pal.setBrush(QPalette::All, QPalette::Button, background); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + } + + if (qobject_cast<QMenu*>(w) || qobject_cast<QComboBoxPrivateContainer *>(w)) { + w->setWindowOpacity(QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 ? 0.985 : 0.94); + if (!w->testAttribute(Qt::WA_SetPalette)) { + QPixmap px(64, 64); + px.fill(Qt::white); + HIThemeMenuDrawInfo mtinfo; + mtinfo.version = qt_mac_hitheme_version; + mtinfo.menuType = kThemeMenuTypePopUp; + HIRect rect = CGRectMake(0, 0, px.width(), px.height()); + HIThemeDrawMenuBackground(&rect, &mtinfo, QCFType<CGContextRef>(qt_mac_cg_context(&px)), + kHIThemeOrientationNormal); + QPalette pal = w->palette(); + QBrush background(px); + pal.setBrush(QPalette::All, QPalette::Window, background); + pal.setBrush(QPalette::All, QPalette::Button, background); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + } + } + + if (QTabBar *tb = qobject_cast<QTabBar*>(w)) { + if (tb->documentMode()) { + w->setAttribute(Qt::WA_Hover); + w->setFont(qt_app_fonts_hash()->value("QSmallFont", QFont())); + QPalette p = w->palette(); + p.setColor(QPalette::WindowText, QColor(17, 17, 17)); + w->setPalette(p); + } + } + + QWindowsStyle::polish(w); + + if (QRubberBand *rubber = qobject_cast<QRubberBand*>(w)) { + rubber->setWindowOpacity(0.25); + rubber->setAttribute(Qt::WA_PaintOnScreen, false); + rubber->setAttribute(Qt::WA_NoSystemBackground, false); + } +} + +void QMacStyle::unpolish(QWidget* w) +{ + d->removeWidget(w); + if ((qobject_cast<QMenu*>(w) || qt_mac_is_metal(w)) && !w->testAttribute(Qt::WA_SetPalette)) { + QPalette pal = qApp->palette(w); + w->setPalette(pal); + w->setAttribute(Qt::WA_SetPalette, false); + w->setWindowOpacity(1.0); + } + + if (QComboBox *combo = qobject_cast<QComboBox *>(w)) { + if (!combo->isEditable()) { + if (QWidget *widget = combo->findChild<QComboBoxPrivateContainer *>()) + widget->setWindowOpacity(1.0); + } + } + + if (QRubberBand *rubber = ::qobject_cast<QRubberBand*>(w)) { + rubber->setWindowOpacity(1.0); + rubber->setAttribute(Qt::WA_PaintOnScreen, true); + rubber->setAttribute(Qt::WA_NoSystemBackground, true); + } + + if (QFocusFrame *frame = qobject_cast<QFocusFrame *>(w)) + frame->setAttribute(Qt::WA_NoSystemBackground, true); + + QWindowsStyle::unpolish(w); +} + +int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QWidget *widget) const +{ + int controlSize = getControlSize(opt, widget); + SInt32 ret = 0; + + switch (metric) { + case PM_TabCloseIndicatorWidth: + case PM_TabCloseIndicatorHeight: + ret = closeButtonSize; + break; + case PM_ToolBarIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize); + break; + case PM_FocusFrameVMargin: + case PM_FocusFrameHMargin: + GetThemeMetric(kThemeMetricFocusRectOutset, &ret); + break; + case PM_DialogButtonsSeparator: + ret = -5; + break; + case PM_DialogButtonsButtonHeight: { + QSize sz; + ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); + if (sz == QSize(-1, -1)) + ret = 32; + else + ret = sz.height(); + break; } + case PM_CheckListButtonSize: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricCheckBoxWidth, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniCheckBoxWidth, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallCheckBoxWidth, &ret); + break; + } + break; } + case PM_DialogButtonsButtonWidth: { + QSize sz; + ret = d->aquaSizeConstrain(opt, 0, QStyle::CT_PushButton, QSize(-1, -1), &sz); + if (sz == QSize(-1, -1)) + ret = 70; + else + ret = sz.width(); + break; } + + case PM_MenuBarHMargin: + ret = 8; + break; + + case PM_MenuBarVMargin: + ret = 0; + break; + + case QStyle::PM_MenuDesktopFrameWidth: + ret = 5; + break; + + case PM_CheckBoxLabelSpacing: + case PM_RadioButtonLabelSpacing: + ret = 2; + break; + case PM_MenuScrollerHeight: +#if 0 + SInt16 ash, asw; + GetThemeMenuItemExtra(kThemeMenuItemScrollUpArrow, &ash, &asw); + ret = ash; +#else + ret = 15; // I hate having magic numbers in here... +#endif + break; + case PM_DefaultFrameWidth: +#ifndef QT_NO_MAINWINDOW + if (widget && (widget->isWindow() || !widget->parentWidget() + || (qobject_cast<const QMainWindow*>(widget->parentWidget()) + && static_cast<QMainWindow *>(widget->parentWidget())->centralWidget() == widget)) + && (qobject_cast<const QAbstractScrollArea *>(widget) +#ifdef QT3_SUPPORT + || widget->inherits("QScrollView") +#endif + || widget->inherits("QWorkspaceChild"))) + ret = 0; + else +#endif + // The combo box popup has no frame. + if (qstyleoption_cast<const QStyleOptionComboBox *>(opt) != 0) + ret = 0; + // Frame of mac style line edits is two pixels on top and one on the bottom + else if (qobject_cast<const QLineEdit *>(widget) != 0) + ret = 2; + else + ret = 1; + break; + case PM_MaximumDragDistance: + ret = -1; + break; + case PM_ScrollBarSliderMin: + ret = 24; + break; + case PM_SpinBoxFrameWidth: + GetThemeMetric(kThemeMetricEditTextFrameOutset, &ret); + switch (d->aquaSizeConstrain(opt, widget)) { + default: + ret += 2; + break; + case QAquaSizeMini: + ret += 1; + break; + } + break; + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 0; + break; + case PM_SliderLength: + ret = 17; + break; + case PM_ButtonDefaultIndicator: + ret = 0; + break; + case PM_TitleBarHeight: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + HIThemeWindowDrawInfo wdi; + wdi.version = qt_mac_hitheme_version; + wdi.state = kThemeStateActive; + wdi.windowType = QtWinType; + if (tb->titleBarState) + wdi.attributes = kThemeWindowHasFullZoom | kThemeWindowHasCloseBox + | kThemeWindowHasCollapseBox; + else if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + wdi.attributes = kThemeWindowHasCloseBox; + else + wdi.attributes = 0; + wdi.titleHeight = tb->rect.height(); + wdi.titleWidth = tb->rect.width(); + QCFType<HIShapeRef> region; + HIRect hirect = qt_hirectForQRect(tb->rect); + if (hirect.size.width <= 0) + hirect.size.width = 100; + if (hirect.size.height <= 0) + hirect.size.height = 30; + + HIThemeGetWindowShape(&hirect, &wdi, kWindowTitleBarRgn, ®ion); + HIRect rect; + ptrHIShapeGetBounds(region, &rect); + ret = int(rect.size.height); + ret += 4; + } + break; + case PM_TabBarTabVSpace: + ret = 4; + break; + case PM_TabBarTabShiftHorizontal: + case PM_TabBarTabShiftVertical: + ret = 0; + break; + case PM_TabBarBaseHeight: + ret = 0; + break; + case PM_TabBarTabOverlap: + ret = 0; + break; + case PM_TabBarBaseOverlap: + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + ret = 11; + break; + case QAquaSizeSmall: + ret = 8; + break; + case QAquaSizeMini: + ret = 7; + break; + } + break; + case PM_ScrollBarExtent: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricScrollBarWidth, &ret); + break; + case QAquaSizeMini: + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallScrollBarWidth, &ret); + break; + } + break; } + case PM_IndicatorHeight: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricCheckBoxHeight, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniCheckBoxHeight, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallCheckBoxHeight, &ret); + break; + } + break; } + case PM_IndicatorWidth: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricCheckBoxWidth, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniCheckBoxWidth, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallCheckBoxWidth, &ret); + break; + } + ++ret; + break; } + case PM_ExclusiveIndicatorHeight: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricRadioButtonHeight, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniRadioButtonHeight, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallRadioButtonHeight, &ret); + break; + } + break; } + case PM_ExclusiveIndicatorWidth: { + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + GetThemeMetric(kThemeMetricRadioButtonWidth, &ret); + break; + case QAquaSizeMini: + GetThemeMetric(kThemeMetricMiniRadioButtonWidth, &ret); + break; + case QAquaSizeSmall: + GetThemeMetric(kThemeMetricSmallRadioButtonWidth, &ret); + break; + } + ++ret; + break; } + case PM_MenuVMargin: + ret = 4; + break; + case PM_MenuPanelWidth: + ret = 0; + break; + case PM_ToolTipLabelFrameWidth: + ret = 0; + break; + case PM_SizeGripSize: { + QAquaWidgetSize aSize; + if (widget && widget->window()->windowType() == Qt::Tool) + aSize = QAquaSizeSmall; + else + aSize = QAquaSizeLarge; + const QSize size = qt_aqua_get_known_size(CT_SizeGrip, widget, QSize(), aSize); + ret = size.width(); + break; } + case PM_MdiSubWindowFrameWidth: + ret = 1; + break; + case PM_DockWidgetFrameWidth: + ret = 2; + break; + case PM_DockWidgetTitleMargin: + ret = 0; + break; + case PM_DockWidgetSeparatorExtent: + ret = 1; + break; + case PM_ToolBarHandleExtent: + ret = 11; + break; + case PM_ToolBarItemMargin: + ret = 0; + break; + case PM_ToolBarItemSpacing: + ret = 4; + break; + case PM_SplitterWidth: + ret = qMax(7, QApplication::globalStrut().width()); + break; + case PM_LayoutLeftMargin: + case PM_LayoutTopMargin: + case PM_LayoutRightMargin: + case PM_LayoutBottomMargin: + { + bool isWindow = false; + if (opt) { + isWindow = (opt->state & State_Window); + } else if (widget) { + isWindow = widget->isWindow(); + } + + if (isWindow) { + bool isMetal = widget && widget->testAttribute(Qt::WA_MacBrushedMetal); + if (isMetal) { + if (metric == PM_LayoutTopMargin) { + return_SIZE(9 /* AHIG */, 6 /* guess */, 6 /* guess */); + } else if (metric == PM_LayoutBottomMargin) { + return_SIZE(18 /* AHIG */, 15 /* guess */, 13 /* guess */); + } else { + return_SIZE(14 /* AHIG */, 11 /* guess */, 9 /* guess */); + } + } else { + /* + AHIG would have (20, 8, 10) here but that makes + no sense. It would also have 14 for the top margin + but this contradicts both Builder and most + applications. + */ + return_SIZE(20, 10, 10); // AHIG + } + } else { + // hack to detect QTabWidget + if (widget && widget->parentWidget() + && widget->parentWidget()->sizePolicy().controlType() == QSizePolicy::TabWidget) { + if (metric == PM_LayoutTopMargin) { + /* + Builder would have 14 (= 20 - 6) instead of 12, + but that makes the tab look disproportionate. + */ + return_SIZE(12, 6, 6); // guess + } else { + return_SIZE(20 /* Builder */, 8 /* guess */, 8 /* guess */); + } + } else { + /* + Child margins are highly inconsistent in AHIG and Builder. + */ + return_SIZE(12, 8, 6); // guess + } + } + } + case PM_LayoutHorizontalSpacing: + case PM_LayoutVerticalSpacing: + return -1; + case QStyle::PM_TabBarTabHSpace: + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeLarge: + case QAquaSizeUnknown: + ret = QWindowsStyle::pixelMetric(metric, opt, widget); + break; + case QAquaSizeSmall: + ret = 20; + break; + case QAquaSizeMini: + ret = 16; + break; + } + break; + case PM_MenuHMargin: + ret = 0; + break; + case PM_ToolBarFrameWidth: + ret = 1; + if (widget) { + if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(widget->parent())) + if (mainWindow->unifiedTitleAndToolBarOnMac()) + ret = 0; + } + break; + default: + ret = QWindowsStyle::pixelMetric(metric, opt, widget); + break; + } + return ret; +} + +QPalette QMacStyle::standardPalette() const +{ + QPalette pal = QWindowsStyle::standardPalette(); + pal.setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); + pal.setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); + pal.setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); + return pal; +} + +int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, + QStyleHintReturn *hret) const +{ + SInt32 ret = 0; + switch (sh) { + case SH_Menu_SelectionWrap: + ret = false; + break; + case SH_Menu_KeyboardSearch: + ret = true; + break; + case SH_Menu_SpaceActivatesItem: + ret = true; + break; + case SH_Slider_AbsoluteSetButtons: + ret = Qt::LeftButton|Qt::MidButton; + break; + case SH_Slider_PageSetButtons: + ret = 0; + break; + case SH_ScrollBar_ContextMenu: + ret = false; + break; + case SH_TitleBar_AutoRaise: + ret = true; + break; + case SH_Menu_AllowActiveAndDisabled: + ret = false; + break; + case SH_Menu_SubMenuPopupDelay: + ret = 100; + break; + case SH_ScrollBar_LeftClickAbsolutePosition: { + extern bool qt_scrollbar_jump_to_pos; //qapplication_mac.cpp + if(QApplication::keyboardModifiers() & Qt::AltModifier) + ret = !qt_scrollbar_jump_to_pos; + else + ret = qt_scrollbar_jump_to_pos; + break; } + case SH_TabBar_PreferNoArrows: + ret = true; + break; + case SH_LineEdit_PasswordCharacter: + ret = kBulletUnicode; + break; + /* + case SH_DialogButtons_DefaultButton: + ret = QDialogButtons::Reject; + break; + */ + case SH_Menu_SloppySubMenus: + ret = true; + break; + case SH_GroupBox_TextLabelVerticalAlignment: + ret = Qt::AlignTop; + break; + case SH_ScrollView_FrameOnlyAroundContents: + if (w && (w->isWindow() || !w->parentWidget() || w->parentWidget()->isWindow()) + && (w->inherits("QWorkspaceChild") +#ifdef QT3_SUPPORT + || w->inherits("QScrollView") +#endif + )) + ret = true; + else + ret = QWindowsStyle::styleHint(sh, opt, w, hret); + break; + case SH_Menu_FillScreenWithScroll: + ret = false; + break; + case SH_Menu_Scrollable: + ret = true; + break; + case SH_RichText_FullWidthSelection: + ret = true; + break; + case SH_BlinkCursorWhenTextSelected: + ret = false; + break; + case SH_ScrollBar_StopMouseOverSlider: + ret = true; + break; + case SH_Q3ListViewExpand_SelectMouseType: + ret = QEvent::MouseButtonRelease; + break; + case SH_TabBar_SelectMouseType: + if (const QStyleOptionTabBarBaseV2 *opt2 = qstyleoption_cast<const QStyleOptionTabBarBaseV2 *>(opt)) { + ret = opt2->documentMode ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; + } else { + ret = QEvent::MouseButtonRelease; + } + break; + case SH_ComboBox_Popup: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) + ret = !cmb->editable; + else + ret = 0; + break; + case SH_Workspace_FillSpaceOnMaximize: + ret = true; + break; + case SH_Widget_ShareActivation: + ret = true; + break; + case SH_Header_ArrowAlignment: + ret = Qt::AlignRight; + break; + case SH_TabBar_Alignment: { + if (const QTabWidget *tab = qobject_cast<const QTabWidget*>(w)) { + if (tab->documentMode()) { + ret = Qt::AlignLeft; + break; + } + } + if (const QTabBar *tab = qobject_cast<const QTabBar*>(w)) { + if (tab->documentMode()) { + ret = Qt::AlignLeft; + break; + } + } + ret = Qt::AlignCenter; + } break; + case SH_UnderlineShortcut: + ret = false; + break; + case SH_ToolTipLabel_Opacity: + ret = 242; // About 95% + break; + case SH_Button_FocusPolicy: + ret = Qt::TabFocus; + break; + case SH_EtchDisabledText: + ret = false; + break; + case SH_FocusFrame_Mask: { + ret = true; + if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + const uchar fillR = 192, fillG = 191, fillB = 190; + QImage img; + + QSize pixmapSize = opt->rect.size(); + if (pixmapSize.isValid()) { + QPixmap pix(pixmapSize); + pix.fill(QColor(fillR, fillG, fillB)); + QPainter pix_paint(&pix); + proxy()->drawControl(CE_FocusFrame, opt, &pix_paint, w); + pix_paint.end(); + img = pix.toImage(); + } + + const QRgb *sptr = (QRgb*)img.bits(), *srow; + const int sbpl = img.bytesPerLine(); + const int w = sbpl/4, h = img.height(); + + QImage img_mask(img.width(), img.height(), QImage::Format_ARGB32); + QRgb *dptr = (QRgb*)img_mask.bits(), *drow; + const int dbpl = img_mask.bytesPerLine(); + + for (int y = 0; y < h; ++y) { + srow = sptr+((y*sbpl)/4); + drow = dptr+((y*dbpl)/4); + for (int x = 0; x < w; ++x) { + const int diff = (((qRed(*srow)-fillR)*(qRed(*srow)-fillR)) + + ((qGreen(*srow)-fillG)*((qGreen(*srow)-fillG))) + + ((qBlue(*srow)-fillB)*((qBlue(*srow)-fillB)))); + (*drow++) = (diff < 100) ? 0xffffffff : 0xff000000; + ++srow; + } + } + QBitmap qmask = QBitmap::fromImage(img_mask); + mask->region = QRegion(qmask); + } + break; } + case SH_TitleBar_NoBorder: + ret = 1; + break; + case SH_RubberBand_Mask: + ret = 0; + break; + case SH_ComboBox_LayoutDirection: + ret = Qt::LeftToRight; + break; + case SH_ItemView_EllipsisLocation: + ret = Qt::AlignHCenter; + break; + case SH_ItemView_ShowDecorationSelected: + ret = true; + break; + case SH_TitleBar_ModifyNotification: + ret = false; + break; + case SH_ScrollBar_RollBetweenButtons: + ret = true; + break; + case SH_WindowFrame_Mask: + ret = 1; + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(hret)) { + mask->region = opt->rect; + mask->region -= QRect(opt->rect.left(), opt->rect.top(), 5, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 1, 3, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 2, 2, 1); + mask->region -= QRect(opt->rect.left(), opt->rect.top() + 3, 1, 2); + + mask->region -= QRect(opt->rect.right() - 4, opt->rect.top(), 5, 1); + mask->region -= QRect(opt->rect.right() - 2, opt->rect.top() + 1, 3, 1); + mask->region -= QRect(opt->rect.right() - 1, opt->rect.top() + 2, 2, 1); + mask->region -= QRect(opt->rect.right() , opt->rect.top() + 3, 1, 2); + } + break; + case SH_TabBar_ElideMode: + ret = Qt::ElideRight; + break; + case SH_DialogButtonLayout: + ret = QDialogButtonBox::MacLayout; + break; + case SH_FormLayoutWrapPolicy: + ret = QFormLayout::DontWrapRows; + break; + case SH_FormLayoutFieldGrowthPolicy: + ret = QFormLayout::FieldsStayAtSizeHint; + break; + case SH_FormLayoutFormAlignment: + ret = Qt::AlignHCenter | Qt::AlignTop; + break; + case SH_FormLayoutLabelAlignment: + ret = Qt::AlignRight; + break; + case SH_ComboBox_PopupFrameStyle: + ret = QFrame::NoFrame | QFrame::Plain; + break; + case SH_MessageBox_TextInteractionFlags: + ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard; + break; + case SH_SpellCheckUnderlineStyle: + ret = QTextCharFormat::DashUnderline; + break; + case SH_MessageBox_CenterButtons: + ret = false; + break; + case SH_MenuBar_AltKeyNavigation: + ret = false; + break; + case SH_ItemView_MovementWithoutUpdatingSelection: + ret = false; + break; + case SH_FocusFrame_AboveWidget: + ret = true; + break; + case SH_WizardStyle: + ret = QWizard::MacStyle; + break; + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = false; + break; + case SH_Menu_FlashTriggeredItem: + ret = true; + break; + case SH_Menu_FadeOutOnHide: + ret = true; + break; + case SH_Menu_Mask: + if (opt) { + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(hret)) { + ret = true; + HIRect menuRect = CGRectMake(opt->rect.x(), opt->rect.y() + 4, + opt->rect.width(), opt->rect.height() - 8); + HIThemeMenuDrawInfo mdi; + mdi.version = 0; + if (w && qobject_cast<QMenu *>(w->parentWidget())) + mdi.menuType = kThemeMenuTypeHierarchical; + else + mdi.menuType = kThemeMenuTypePopUp; + QCFType<HIShapeRef> shape; + HIThemeGetMenuBackgroundShape(&menuRect, &mdi, &shape); + mask->region = QRegion::fromHIShapeRef(shape); + } + } + break; + case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: + ret = true; + break; + case SH_TabBar_CloseButtonPosition: + ret = QTabBar::LeftSide; + break; + case SH_DockWidget_ButtonsHaveFrame: + ret = false; + break; + default: + ret = QWindowsStyle::styleHint(sh, opt, w, hret); + break; + } + return ret; +} + +QPixmap QMacStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const +{ + switch (iconMode) { + case QIcon::Disabled: { + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int imgh = img.height(); + int imgw = img.width(); + QRgb pixel; + for (int y = 0; y < imgh; ++y) { + for (int x = 0; x < imgw; ++x) { + pixel = img.pixel(x, y); + img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), + qAlpha(pixel) / 2)); + } + } + return QPixmap::fromImage(img); + } + default: + ; + } + return QWindowsStyle::generatedIconPixmap(iconMode, pixmap, opt); +} + + +QPixmap QMacStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ + // The default implementation of QStyle::standardIconImplementation() is to call standardPixmap() + // I don't want infinite recursion so if we do get in that situation, just return the Window's + // standard pixmap instead (since there is no mac-specific icon then). This should be fine until + // someone changes how Windows standard + // pixmap works. + static bool recursionGuard = false; + + if (recursionGuard) + return QWindowsStyle::standardPixmap(standardPixmap, opt, widget); + + recursionGuard = true; + QIcon icon = standardIconImplementation(standardPixmap, opt, widget); + recursionGuard = false; + int size; + switch (standardPixmap) { + default: + size = 32; + break; + case SP_MessageBoxCritical: + case SP_MessageBoxQuestion: + case SP_MessageBoxInformation: + case SP_MessageBoxWarning: + size = 64; + break; + } + return icon.pixmap(size, size); +} + +void QMacStyle::setFocusRectPolicy(QWidget *w, FocusRectPolicy policy) +{ + switch (policy) { + case FocusDefault: + break; + case FocusEnabled: + case FocusDisabled: + w->setAttribute(Qt::WA_MacShowFocusRect, policy == FocusEnabled); + break; + } +} + +QMacStyle::FocusRectPolicy QMacStyle::focusRectPolicy(const QWidget *w) +{ + return w->testAttribute(Qt::WA_MacShowFocusRect) ? FocusEnabled : FocusDisabled; +} + +void QMacStyle::setWidgetSizePolicy(const QWidget *widget, WidgetSizePolicy policy) +{ + QWidget *wadget = const_cast<QWidget *>(widget); + wadget->setAttribute(Qt::WA_MacNormalSize, policy == SizeLarge); + wadget->setAttribute(Qt::WA_MacSmallSize, policy == SizeSmall); + wadget->setAttribute(Qt::WA_MacMiniSize, policy == SizeMini); +} + +QMacStyle::WidgetSizePolicy QMacStyle::widgetSizePolicy(const QWidget *widget) +{ + while (widget) { + if (widget->testAttribute(Qt::WA_MacMiniSize)) { + return SizeMini; + } else if (widget->testAttribute(Qt::WA_MacSmallSize)) { + return SizeSmall; + } else if (widget->testAttribute(Qt::WA_MacNormalSize)) { + return SizeLarge; + } + widget = widget->parentWidget(); + } + return SizeDefault; +} + +void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + switch (pe) { + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: { + p->save(); + p->setRenderHint(QPainter::Antialiasing); + int xOffset = opt->direction == Qt::LeftToRight ? 2 : -1; + QMatrix matrix; + matrix.translate(opt->rect.center().x() + xOffset, opt->rect.center().y() + 2); + QPainterPath path; + switch(pe) { + default: + case PE_IndicatorArrowDown: + break; + case PE_IndicatorArrowUp: + matrix.rotate(180); + break; + case PE_IndicatorArrowLeft: + matrix.rotate(90); + break; + case PE_IndicatorArrowRight: + matrix.rotate(-90); + break; + } + path.moveTo(0, 5); + path.lineTo(-4, -3); + path.lineTo(4, -3); + p->setMatrix(matrix); + p->setPen(Qt::NoPen); + p->setBrush(QColor(0, 0, 0, 135)); + p->drawPath(path); + p->restore(); + break; } + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBaseV2 *tbb + = qstyleoption_cast<const QStyleOptionTabBarBaseV2 *>(opt)) { + if (tbb->documentMode) { + p->save(); + drawTabBase(p, tbb, w); + p->restore(); + return; + } + + QRegion region(tbb->rect); + region -= tbb->tabBarRect; + p->save(); + p->setClipRegion(region); + QStyleOptionTabWidgetFrame twf; + twf.QStyleOption::operator=(*tbb); + twf.shape = tbb->shape; + switch (getTabDirection(twf.shape)) { + case kThemeTabNorth: + twf.rect = twf.rect.adjusted(0, 0, 0, 10); + break; + case kThemeTabSouth: + twf.rect = twf.rect.adjusted(0, -10, 0, 0); + break; + case kThemeTabWest: + twf.rect = twf.rect.adjusted(0, 0, 10, 0); + break; + case kThemeTabEast: + twf.rect = twf.rect.adjusted(0, -10, 0, 0); + break; + } + proxy()->drawPrimitive(PE_FrameTabWidget, &twf, p, w); + p->restore(); + } + break; + case PE_PanelTipLabel: + p->fillRect(opt->rect, opt->palette.brush(QPalette::ToolTipBase)); + break; + case PE_FrameGroupBox: + if (const QStyleOptionFrame *groupBox = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + const QStyleOptionFrameV2 *frame2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(opt); + if (frame2 && frame2->features & QStyleOptionFrameV2::Flat) { + QWindowsStyle::drawPrimitive(pe, groupBox, p, w); + } else { + HIThemeGroupBoxDrawInfo gdi; + gdi.version = qt_mac_hitheme_version; + gdi.state = tds; + if (w && qobject_cast<QGroupBox *>(w->parentWidget())) + gdi.kind = kHIThemeGroupBoxKindSecondary; + else + gdi.kind = kHIThemeGroupBoxKindPrimary; + HIRect hirect = qt_hirectForQRect(opt->rect); + HIThemeDrawGroupBox(&hirect, &gdi, cg, kHIThemeOrientationNormal); + } + } + break; + case PE_IndicatorToolBarSeparator: { + QPainterPath path; + if (opt->state & State_Horizontal) { + int xpoint = opt->rect.center().x(); + path.moveTo(xpoint + 0.5, opt->rect.top() + 1); + path.lineTo(xpoint + 0.5, opt->rect.bottom()); + } else { + int ypoint = opt->rect.center().y(); + path.moveTo(opt->rect.left() + 2 , ypoint + 0.5); + path.lineTo(opt->rect.right() + 1, ypoint + 0.5); + } + QPainterPathStroker theStroker; + theStroker.setCapStyle(Qt::FlatCap); + theStroker.setDashPattern(QVector<qreal>() << 1 << 2); + path = theStroker.createStroke(path); + p->fillPath(path, QColor(0, 0, 0, 119)); + } + break; + case PE_FrameWindow: + break; + case PE_IndicatorDockWidgetResizeHandle: { + // The docwidget resize handle is drawn as a one-pixel wide line. + p->save(); + if (opt->state & State_Horizontal) { + p->setPen(QColor(160, 160, 160)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + } else { + p->setPen(QColor(145, 145, 145)); + p->drawLine(opt->rect.topRight(), opt->rect.bottomRight()); + } + p->restore(); + } break; + case PE_IndicatorToolBarHandle: { + p->save(); + QPainterPath path; + int x = opt->rect.x() + 6; + int y = opt->rect.y() + 5; + static const int RectHeight = 2; + if (opt->state & State_Horizontal) { + while (y < opt->rect.height() - RectHeight - 6) { + path.moveTo(x, y); + path.addRect(x, y, RectHeight, RectHeight); + y += 6; + } + } else { + while (x < opt->rect.width() - RectHeight - 6) { + path.moveTo(x, y); + path.addRect(x, y, RectHeight, RectHeight); + x += 6; + } + } + p->setPen(Qt::NoPen); + QColor dark = opt->palette.dark().color(); + dark.setAlphaF(0.75); + QColor light = opt->palette.light().color(); + light.setAlphaF(0.6); + p->fillPath(path, light); + p->save(); + p->translate(1, 1); + p->fillPath(path, dark); + p->restore(); + p->translate(3, 3); + p->fillPath(path, light); + p->translate(1, 1); + p->fillPath(path, dark); + p->restore(); + + break; + } + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // In HITheme, up is down, down is up and hamburgers eat people. + if (header->sortIndicator != QStyleOptionHeader::None) + proxy()->drawPrimitive( + (header->sortIndicator == QStyleOptionHeader::SortDown) ? + PE_IndicatorArrowUp : PE_IndicatorArrowDown, header, p, w); + } + break; + case PE_IndicatorMenuCheckMark: { + const int checkw = 8; + const int checkh = 8; + const int xoff = qMax(0, (opt->rect.width() - checkw) / 2); + const int yoff = qMax(0, (opt->rect.width() - checkh) / 2); + const int x1 = xoff + opt->rect.x(); + const int y1 = yoff + opt->rect.y() + checkw/2; + const int x2 = xoff + opt->rect.x() + checkw/4; + const int y2 = yoff + opt->rect.y() + checkh; + const int x3 = xoff + opt->rect.x() + checkw; + const int y3 = yoff + opt->rect.y(); + + QVector<QLineF> a(2); + a << QLineF(x1, y1, x2, y2); + a << QLineF(x2, y2, x3, y3); + if (opt->palette.currentColorGroup() == QPalette::Active) + p->setPen(QPen(Qt::white, 3)); + else + p->setPen(QPen(QColor(100, 100, 100), 3)); + p->save(); + p->setRenderHint(QPainter::Antialiasing); + p->drawLines(a); + p->restore(); + break; } + case PE_IndicatorViewItemCheck: + case PE_Q3CheckListExclusiveIndicator: + case PE_Q3CheckListIndicator: + case PE_IndicatorRadioButton: + case PE_IndicatorCheckBox: { + bool drawColorless = (!(opt->state & State_Active)) + && opt->palette.currentColorGroup() == QPalette::Active; + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + if (drawColorless && tds == kThemeStateInactive) + bdi.state = kThemeStateActive; + bdi.adornment = kThemeDrawIndicatorOnly; + if (opt->state & State_HasFocus) + bdi.adornment |= kThemeAdornmentFocus; + bool isRadioButton = (pe == PE_Q3CheckListExclusiveIndicator + || pe == PE_IndicatorRadioButton); + switch (d->aquaSizeConstrain(opt, w)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (isRadioButton) + bdi.kind = kThemeRadioButton; + else + bdi.kind = kThemeCheckBox; + break; + case QAquaSizeMini: + if (isRadioButton) + bdi.kind = kThemeMiniRadioButton; + else + bdi.kind = kThemeMiniCheckBox; + break; + case QAquaSizeSmall: + if (isRadioButton) + bdi.kind = kThemeSmallRadioButton; + else + bdi.kind = kThemeSmallCheckBox; + break; + } + if (opt->state & State_NoChange) + bdi.value = kThemeButtonMixed; + else if (opt->state & State_On) + bdi.value = kThemeButtonOn; + else + bdi.value = kThemeButtonOff; + HIRect macRect; + if (pe == PE_Q3CheckListExclusiveIndicator || pe == PE_Q3CheckListIndicator) + macRect = qt_hirectForQRect(opt->rect); + else + macRect = qt_hirectForQRect(opt->rect); + if (!drawColorless) + HIThemeDrawButton(&macRect, &bdi, cg, kHIThemeOrientationNormal, 0); + else + d->drawColorlessButton(macRect, &bdi, p, opt); + break; } + case PE_FrameFocusRect: + // Use the our own focus widget stuff. + break; + case PE_IndicatorBranch: { + if (!(opt->state & State_Children)) + break; + HIThemeButtonDrawInfo bi; + bi.version = qt_mac_hitheme_version; + bi.state = tds; + if (tds == kThemeStateInactive && opt->palette.currentColorGroup() == QPalette::Active) + bi.state = kThemeStateActive; + if (opt->state & State_Sunken) + bi.state |= kThemeStatePressed; + bi.kind = kThemeDisclosureButton; + if (opt->state & State_Open) + bi.value = kThemeDisclosureDown; + else + bi.value = opt->direction == Qt::LeftToRight ? kThemeDisclosureRight : kThemeDisclosureLeft; + bi.adornment = kThemeAdornmentNone; + HIRect hirect = qt_hirectForQRect(opt->rect.adjusted(DisclosureOffset,0,-DisclosureOffset,0)); + HIThemeDrawButton(&hirect, &bi, cg, kHIThemeOrientationNormal, 0); + break; } + + case PE_Frame: { + QPen oldPen = p->pen(); + p->setPen(opt->palette.base().color().darker(140)); + p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); + p->setPen(opt->palette.base().color().darker(180)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(oldPen); + break; } + + case PE_FrameLineEdit: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (frame->state & State_Sunken) { + QColor baseColor(frame->palette.background().color()); + HIThemeFrameDrawInfo fdi; + fdi.version = qt_mac_hitheme_version; + fdi.state = tds; + SInt32 frame_size; + if (pe == PE_FrameLineEdit) { + fdi.kind = kHIThemeFrameTextFieldSquare; + GetThemeMetric(kThemeMetricEditTextFrameOutset, &frame_size); + if ((frame->state & State_ReadOnly) || !(frame->state & State_Enabled)) + fdi.state = kThemeStateInactive; + } else { + baseColor = QColor(150, 150, 150); //hardcoded since no query function --Sam + fdi.kind = kHIThemeFrameListBox; + GetThemeMetric(kThemeMetricListBoxFrameOutset, &frame_size); + } + fdi.isFocused = (frame->state & State_HasFocus); + int lw = frame->lineWidth; + if (lw <= 0) + lw = proxy()->pixelMetric(PM_DefaultFrameWidth, frame, w); + { //clear to base color + p->save(); + p->setPen(QPen(baseColor, lw)); + p->setBrush(Qt::NoBrush); + p->drawRect(frame->rect); + p->restore(); + } + HIRect hirect = qt_hirectForQRect(frame->rect, + QRect(frame_size, frame_size, + frame_size * 2, frame_size * 2)); + + HIThemeDrawFrame(&hirect, &fdi, cg, kHIThemeOrientationNormal); + } else { + QWindowsStyle::drawPrimitive(pe, opt, p, w); + } + } + break; + case PE_PanelLineEdit: + QWindowsStyle::drawPrimitive(pe, opt, p, w); + // Draw the focus frame for widgets other than QLineEdit (e.g. for line edits in Webkit). + // Focus frame is drawn outside the rectangle passed in the option-rect. + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if ((opt->state & State_HasFocus) && !qobject_cast<const QLineEdit*>(w)) { + int vmargin = pixelMetric(QStyle::PM_FocusFrameVMargin); + int hmargin = pixelMetric(QStyle::PM_FocusFrameHMargin); + QStyleOptionFrame focusFrame = *panel; + focusFrame.rect = panel->rect.adjusted(-hmargin, -vmargin, hmargin, vmargin); + drawControl(CE_FocusFrame, &focusFrame, p, w); + } + } + + break; + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + HIRect hirect = qt_hirectForQRect(twf->rect); + HIThemeTabPaneDrawInfo tpdi; + tpdi.version = qt_mac_hitheme_tab_version(); + tpdi.state = tds; + tpdi.direction = getTabDirection(twf->shape); + tpdi.size = kHIThemeTabSizeNormal; + tpdi.kind = kHIThemeTabKindNormal; + tpdi.adornment = kHIThemeTabPaneAdornmentNormal; + HIThemeDrawTabPane(&hirect, &tpdi, cg, kHIThemeOrientationNormal); + } + break; + case PE_PanelScrollAreaCorner: { + const QBrush brush(opt->palette.brush(QPalette::Base)); + p->fillRect(opt->rect, brush); + p->setPen(QPen(QColor(217, 217, 217))); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); + } break; + case PE_FrameStatusBarItem: + break; + case PE_IndicatorTabClose: { + bool hover = (opt->state & State_MouseOver); + bool selected = (opt->state & State_Selected); + bool active = (opt->state & State_Active); + drawTabCloseButton(p, hover, active, selected); + } break; + case PE_PanelStatusBar: { + if (QSysInfo::MacintoshVersion <= QSysInfo::MV_10_4) { + QWindowsStyle::drawPrimitive(pe, opt, p, w); + break; + } + // Use the Leopard style only if the status bar is the status bar for a + // QMainWindow with a unifed toolbar. + if (w == 0 || w->parent() == 0 || qobject_cast<QMainWindow *>(w->parent()) == 0 || + qobject_cast<QMainWindow *>(w->parent())->unifiedTitleAndToolBarOnMac() == false ) { + QWindowsStyle::drawPrimitive(pe, opt, p, w); + break; + } + + // Fill the status bar with the titlebar gradient. + QLinearGradient linearGrad(0, opt->rect.top(), 0, opt->rect.bottom()); + if (opt->state & QStyle::State_Active) { + linearGrad.setColorAt(0, titlebarGradientActiveBegin); + linearGrad.setColorAt(1, titlebarGradientActiveEnd); + } else { + linearGrad.setColorAt(0, titlebarGradientInactiveBegin); + linearGrad.setColorAt(1, titlebarGradientInactiveEnd); + } + p->fillRect(opt->rect, linearGrad); + + // Draw the black separator line at the top of the status bar. + if (opt->state & QStyle::State_Active) + p->setPen(titlebarSeparatorLineActive); + else + p->setPen(titlebarSeparatorLineInactive); + p->drawLine(opt->rect.left(), opt->rect.top(), opt->rect.right(), opt->rect.top()); + + break; + } + + default: + QWindowsStyle::drawPrimitive(pe, opt, p, w); + break; + } +} + +static inline QPixmap darkenPixmap(const QPixmap &pixmap) +{ + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int imgh = img.height(); + int imgw = img.width(); + int h, s, v, a; + QRgb pixel; + for (int y = 0; y < imgh; ++y) { + for (int x = 0; x < imgw; ++x) { + pixel = img.pixel(x, y); + a = qAlpha(pixel); + QColor hsvColor(pixel); + hsvColor.getHsv(&h, &s, &v); + s = qMin(100, s * 2); + v = v / 2; + hsvColor.setHsv(h, s, v); + pixel = hsvColor.rgb(); + img.setPixel(x, y, qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), a)); + } + } + return QPixmap::fromImage(img); +} + + + +void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + switch (ce) { + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + State flags = header->state; + QRect ir = header->rect; + bdi.kind = kThemeListHeaderButton; + bdi.adornment = kThemeAdornmentNone; + bdi.state = kThemeStateActive; + + if (flags & State_On) + bdi.value = kThemeButtonOn; + else + bdi.value = kThemeButtonOff; + + if (header->orientation == Qt::Horizontal){ + switch (header->position) { + case QStyleOptionHeader::Beginning: + ir.adjust(-1, -1, 0, 0); + break; + case QStyleOptionHeader::Middle: + ir.adjust(-1, -1, 0, 0); + break; + case QStyleOptionHeader::OnlyOneSection: + case QStyleOptionHeader::End: + ir.adjust(-1, -1, 1, 0); + break; + default: + break; + } + + if (header->position != QStyleOptionHeader::Beginning + && header->position != QStyleOptionHeader::OnlyOneSection) { + bdi.adornment = header->direction == Qt::LeftToRight + ? kThemeAdornmentHeaderButtonLeftNeighborSelected + : kThemeAdornmentHeaderButtonRightNeighborSelected; + } + } + + if (flags & State_Active) { + if (!(flags & State_Enabled)) + bdi.state = kThemeStateUnavailable; + else if (flags & State_Sunken) + bdi.state = kThemeStatePressed; + } else { + if (flags & State_Enabled) + bdi.state = kThemeStateInactive; + else + bdi.state = kThemeStateUnavailableInactive; + } + + if (header->sortIndicator != QStyleOptionHeader::None) { + bdi.value = kThemeButtonOn; + if (header->sortIndicator == QStyleOptionHeader::SortDown) + bdi.adornment = kThemeAdornmentHeaderButtonSortUp; + } + if (flags & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + + ir = visualRect(header->direction, header->rect, ir); + HIRect bounds = qt_hirectForQRect(ir); + + bool noVerticalHeader = true; + if (w) + if (const QTableView *table = qobject_cast<const QTableView *>(w->parentWidget())) + noVerticalHeader = !table->verticalHeader()->isVisible(); + + bool drawTopBorder = header->orientation == Qt::Horizontal; + bool drawLeftBorder = header->orientation == Qt::Vertical + || header->position == QStyleOptionHeader::OnlyOneSection + || (header->position == QStyleOptionHeader::Beginning && noVerticalHeader); + d->drawTableHeader(bounds, drawTopBorder, drawLeftBorder, bdi, p); + } + break; + case CE_HeaderLabel: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QRect textr = header->rect; + if (!header->icon.isNull()) { + QIcon::Mode mode = QIcon::Disabled; + if (opt->state & State_Enabled) + mode = QIcon::Normal; + QPixmap pixmap = header->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), mode); + + QRect pixr = header->rect; + pixr.setY(header->rect.center().y() - (pixmap.height() - 1) / 2); + proxy()->drawItemPixmap(p, pixr, Qt::AlignVCenter, pixmap); + textr.translate(pixmap.width() + 2, 0); + } + + proxy()->drawItemText(p, textr, header->textAlignment | Qt::AlignVCenter, header->palette, + header->state & State_Enabled, header->text, QPalette::ButtonText); + } + break; + case CE_ToolButtonLabel: + if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + QStyleOptionToolButton myTb = *tb; + myTb.state &= ~State_AutoRaise; + if (w && qobject_cast<QToolBar *>(w->parentWidget())) { + QRect cr = tb->rect; + int shiftX = 0; + int shiftY = 0; + bool needText = false; + int alignment = 0; + bool down = tb->state & (State_Sunken | State_On); + if (down) { + shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, tb, w); + shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, tb, w); + } + // The down state is special for QToolButtons in a toolbar on the Mac + // The text is a bit bolder and gets a drop shadow and the icons are also darkened. + // This doesn't really fit into any particular case in QIcon, so we + // do the majority of the work ourselves. + if (!(tb->features & QStyleOptionToolButton::Arrow)) { + Qt::ToolButtonStyle tbstyle = tb->toolButtonStyle; + if (tb->icon.isNull() && !tb->text.isEmpty()) + tbstyle = Qt::ToolButtonTextOnly; + + switch (tbstyle) { + case Qt::ToolButtonTextOnly: { + needText = true; + alignment = Qt::AlignCenter; + break; } + case Qt::ToolButtonIconOnly: + case Qt::ToolButtonTextBesideIcon: + case Qt::ToolButtonTextUnderIcon: { + QRect pr = cr; + QIcon::Mode iconMode = (tb->state & State_Enabled) ? QIcon::Normal + : QIcon::Disabled; + QIcon::State iconState = (tb->state & State_On) ? QIcon::On + : QIcon::Off; + QPixmap pixmap = tb->icon.pixmap(tb->rect.size().boundedTo(tb->iconSize), iconMode, iconState); + + // Draw the text if it's needed. + if (tb->toolButtonStyle != Qt::ToolButtonIconOnly) { + needText = true; + if (tb->toolButtonStyle == Qt::ToolButtonTextUnderIcon) { + QMainWindow *mw = qobject_cast<QMainWindow *>(w->window()); + if (mw && mw->unifiedTitleAndToolBarOnMac()) { + pr.setHeight(pixmap.size().height()); + cr.adjust(0, pr.bottom() + 1, 0, 1); + } else { + pr.setHeight(pixmap.size().height() + 6); + cr.adjust(0, pr.bottom(), 0, -3); + } + alignment |= Qt::AlignCenter; + } else { + pr.setWidth(pixmap.width() + 8); + cr.adjust(pr.right(), 0, 0, 0); + alignment |= Qt::AlignLeft | Qt::AlignVCenter; + } + } + if (opt->state & State_Sunken) { + pr.translate(shiftX, shiftY); + pixmap = darkenPixmap(pixmap); + } + proxy()->drawItemPixmap(p, pr, Qt::AlignCenter, pixmap); + break; } + default: + Q_ASSERT(false); + break; + } + + if (needText) { + QPalette pal = tb->palette; + QPalette::ColorRole role = QPalette::NoRole; + if (!proxy()->styleHint(SH_UnderlineShortcut, tb, w)) + alignment |= Qt::TextHideMnemonic; + if (down) + cr.translate(shiftX, shiftY); + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 + && (tbstyle == Qt::ToolButtonTextOnly + || (tbstyle != Qt::ToolButtonTextOnly && !down))) { + QPen pen = p->pen(); + QColor light = down ? Qt::black : Qt::white; + light.setAlphaF(0.375f); + p->setPen(light); + p->drawText(cr.adjusted(0, 1, 0, 1), alignment, tb->text); + p->setPen(pen); + if (down && tbstyle == Qt::ToolButtonTextOnly) { + pal = QApplication::palette("QMenu"); + pal.setCurrentColorGroup(tb->palette.currentColorGroup()); + role = QPalette::HighlightedText; + } + } + proxy()->drawItemText(p, cr, alignment, pal, + tb->state & State_Enabled, tb->text, role); + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5 && + (tb->state & State_Sunken)) { + // Draw a "drop shadow" in earlier versions. + proxy()->drawItemText(p, cr.adjusted(0, 1, 0, 1), alignment, + tb->palette, tb->state & State_Enabled, tb->text); + } + } + } else { + QWindowsStyle::drawControl(ce, &myTb, p, w); + } + } else { + QWindowsStyle::drawControl(ce, &myTb, p, w); + } + } + break; + case CE_ToolBoxTabShape: + QCommonStyle::drawControl(ce, opt, p, w); + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = ::qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (!(btn->state & (State_Raised | State_Sunken | State_On))) + break; + + if (btn->features & QStyleOptionButton::CommandLinkButton) { + QWindowsStyle::drawControl(ce, opt, p, w); + break; + } + + HIThemeButtonDrawInfo bdi; + d->initHIThemePushButton(btn, w, tds, &bdi); + if (btn->features & QStyleOptionButton::DefaultButton + && d->animatable(QMacStylePrivate::AquaPushButton, w)) { + bdi.adornment |= kThemeAdornmentDefault; + bdi.animation.time.start = d->defaultButtonStart; + bdi.animation.time.current = CFAbsoluteTimeGetCurrent(); + if (d->timerID <= -1) + QMetaObject::invokeMethod(d, "startAnimationTimer", Qt::QueuedConnection); + } + // Unlike Carbon, we want the button to always be drawn inside its bounds. + // Therefore, make the button a bit smaller, so that even if it got focus, + // the focus 'shadow' will be inside. + HIRect newRect = qt_hirectForQRect(btn->rect); + if (bdi.kind == kThemePushButton || bdi.kind == kThemePushButtonSmall) { + newRect.origin.x += QMacStylePrivate::PushButtonLeftOffset; + newRect.origin.y += QMacStylePrivate::PushButtonTopOffset; + newRect.size.width -= QMacStylePrivate::PushButtonRightOffset; + newRect.size.height -= QMacStylePrivate::PushButtonBottomOffset; + } else if (bdi.kind == kThemePushButtonMini) { + newRect.origin.x += QMacStylePrivate::PushButtonLeftOffset - 2; + newRect.origin.y += QMacStylePrivate::PushButtonTopOffset; + newRect.size.width -= QMacStylePrivate::PushButtonRightOffset - 4; + } + HIThemeDrawButton(&newRect, &bdi, cg, kHIThemeOrientationNormal, 0); + + if (btn->features & QStyleOptionButton::HasMenu) { + int mbi = proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, btn, w); + QRect ir = btn->rect; + HIRect arrowRect = CGRectMake(ir.right() - mbi - QMacStylePrivate::PushButtonRightOffset, + ir.height() / 2 - 4, mbi, ir.height() / 2); + bool drawColorless = btn->palette.currentColorGroup() == QPalette::Active; + if (drawColorless && tds == kThemeStateInactive) + tds = kThemeStateActive; + + HIThemePopupArrowDrawInfo pdi; + pdi.version = qt_mac_hitheme_version; + pdi.state = tds; + pdi.orientation = kThemeArrowDown; + if (arrowRect.size.width < 8.) + pdi.size = kThemeArrow5pt; + else + pdi.size = kThemeArrow9pt; + HIThemeDrawPopupArrow(&arrowRect, &pdi, cg, kHIThemeOrientationNormal); + } + } + break; + case CE_PushButtonLabel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + // We really don't want the label to be drawn the same as on + // windows style if it has an icon and text, then it should be more like a + // tab. So, cheat a little here. However, if it *is* only an icon + // the windows style works great, so just use that implementation. + bool hasMenu = btn->features & QStyleOptionButton::HasMenu; + bool hasIcon = !btn->icon.isNull(); + bool hasText = !btn->text.isEmpty(); + if (!hasIcon && !hasMenu) { + // ### this is really overly difficult, simplify. + // It basically tries to get the right font for "small" and "mini" icons. + QFont oldFont = p->font(); + QFont newFont = qt_app_fonts_hash()->value("QPushButton", QFont()); + ThemeFontID themeId = kThemePushButtonFont; + if (oldFont == newFont) { // Yes, use HITheme to draw the text for small sizes. + switch (d->aquaSizeConstrain(opt, w)) { + default: + break; + case QAquaSizeSmall: + themeId = kThemeSmallSystemFont; + break; + case QAquaSizeMini: + themeId = kThemeMiniSystemFont; + break; + } + } + if (themeId == kThemePushButtonFont) { + QWindowsStyle::drawControl(ce, btn, p, w); + } else { + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor = btn->palette.buttonText().color(); + CGFloat colorComp[] = { textColor.redF(), textColor.greenF(), + textColor.blueF(), textColor.alphaF() }; + CGContextSetFillColorSpace(cg, QCoreGraphicsPaintEngine::macGenericColorSpace()); + CGContextSetFillColor(cg, colorComp); + tti.fontID = themeId; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + btn->text.count(QLatin1Char('\n')); + QCFString buttonText = qt_mac_removeMnemonics(btn->text); + QRect r = btn->rect; + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(buttonText, &bounds, &tti, + cg, kHIThemeOrientationNormal); + p->restore(); + } + } else { + if (hasIcon && !hasText) { + QWindowsStyle::drawControl(ce, btn, p, w); + } else { + QRect freeContentRect = btn->rect; + QRect textRect = itemTextRect( + btn->fontMetrics, freeContentRect, Qt::AlignCenter, btn->state & State_Enabled, btn->text); + if (hasMenu) + textRect.adjust(-1, 0, -1, 0); + // Draw the icon: + if (hasIcon) { + int contentW = textRect.width(); + if (hasMenu) + contentW += proxy()->pixelMetric(PM_MenuButtonIndicator) + 4; + QIcon::Mode mode = btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + if (mode == QIcon::Normal && btn->state & State_HasFocus) + mode = QIcon::Active; + // Decide if the icon is should be on or off: + QIcon::State state = QIcon::Off; + if (btn->state & State_On) + state = QIcon::On; + QPixmap pixmap = btn->icon.pixmap(btn->iconSize, mode, state); + contentW += pixmap.width() + QMacStylePrivate::PushButtonContentPadding; + int iconLeftOffset = freeContentRect.x() + (freeContentRect.width() - contentW) / 2; + int iconTopOffset = freeContentRect.y() + (freeContentRect.height() - pixmap.height()) / 2; + QRect iconDestRect(iconLeftOffset, iconTopOffset, pixmap.width(), pixmap.height()); + QRect visualIconDestRect = visualRect(btn->direction, freeContentRect, iconDestRect); + proxy()->drawItemPixmap(p, visualIconDestRect, Qt::AlignLeft | Qt::AlignVCenter, pixmap); + int newOffset = iconDestRect.x() + iconDestRect.width() + + QMacStylePrivate::PushButtonContentPadding - textRect.x(); + textRect.adjust(newOffset, 0, newOffset, 0); + } + // Draw the text: + if (hasText) { + textRect = visualRect(btn->direction, freeContentRect, textRect); + proxy()->drawItemText(p, textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, btn->palette, + (btn->state & State_Enabled), btn->text, QPalette::ButtonText); + } + } + } + } + break; + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QStyleOptionComboBox comboCopy = *cb; + comboCopy.direction = Qt::LeftToRight; + QWindowsStyle::drawControl(CE_ComboBoxLabel, &comboCopy, p, w); + } + break; + case CE_TabBarTabShape: + if (const QStyleOptionTab *tabOpt = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + + if (const QStyleOptionTabV3 *tabOptV3 = qstyleoption_cast<const QStyleOptionTabV3 *>(opt)) { + if (tabOptV3->documentMode) { + p->save(); + QRect tabRect = tabOptV3->rect; + drawTabShape(p, tabOptV3); + p->restore(); + return; + } + } + HIThemeTabDrawInfo tdi; + tdi.version = 1; + tdi.style = kThemeTabNonFront; + tdi.direction = getTabDirection(tabOpt->shape); + switch (d->aquaSizeConstrain(opt, w)) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + tdi.size = kHIThemeTabSizeNormal; + break; + case QAquaSizeSmall: + tdi.size = kHIThemeTabSizeSmall; + break; + case QAquaSizeMini: + tdi.size = kHIThemeTabSizeMini; + break; + } + bool verticalTabs = tdi.direction == kThemeTabWest || tdi.direction == kThemeTabEast; + QRect tabRect = tabOpt->rect; + + bool selected = tabOpt->state & State_Selected; + if (selected) { + if (!(tabOpt->state & State_Active)) + tdi.style = kThemeTabFrontUnavailable; + else if (!(tabOpt->state & State_Enabled)) + tdi.style = kThemeTabFrontInactive; + else + tdi.style = kThemeTabFront; + } else if (!(tabOpt->state & State_Active)) { + tdi.style = kThemeTabNonFrontUnavailable; + } else if (!(tabOpt->state & State_Enabled)) { + tdi.style = kThemeTabNonFrontInactive; + } else if (tabOpt->state & State_Sunken) { + tdi.style = kThemeTabFrontInactive; // (should be kThemeTabNonFrontPressed) + } + if (tabOpt->state & State_HasFocus) + tdi.adornment = kHIThemeTabAdornmentFocus; + else + tdi.adornment = kHIThemeTabAdornmentNone; + tdi.kind = kHIThemeTabKindNormal; + if (!verticalTabs) + tabRect.setY(tabRect.y() - 1); + else + tabRect.setX(tabRect.x() - 1); + QStyleOptionTab::TabPosition tp = tabOpt->position; + QStyleOptionTab::SelectedPosition sp = tabOpt->selectedPosition; + if (tabOpt->direction == Qt::RightToLeft && !verticalTabs) { + if (sp == QStyleOptionTab::NextIsSelected) + sp = QStyleOptionTab::PreviousIsSelected; + else if (sp == QStyleOptionTab::PreviousIsSelected) + sp = QStyleOptionTab::NextIsSelected; + switch (tp) { + case QStyleOptionTab::Beginning: + tp = QStyleOptionTab::End; + break; + case QStyleOptionTab::End: + tp = QStyleOptionTab::Beginning; + break; + default: + break; + } + } + bool stretchTabs = (!verticalTabs && tabRect.height() > 22) || (verticalTabs && tabRect.width() > 22); + + switch (tp) { + case QStyleOptionTab::Beginning: + tdi.position = kHIThemeTabPositionFirst; + if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) + tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + break; + case QStyleOptionTab::Middle: + tdi.position = kHIThemeTabPositionMiddle; + if (selected) + tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; + if (sp != QStyleOptionTab::NextIsSelected || stretchTabs) // Also when we're selected. + tdi.adornment |= kHIThemeTabAdornmentTrailingSeparator; + break; + case QStyleOptionTab::End: + tdi.position = kHIThemeTabPositionLast; + if (selected) + tdi.adornment |= kHIThemeTabAdornmentLeadingSeparator; + break; + case QStyleOptionTab::OnlyOneTab: + tdi.position = kHIThemeTabPositionOnly; + break; + } + // HITheme doesn't stretch its tabs. Therefore we have to cheat and do the job ourselves. + if (stretchTabs) { + HIRect hirect = CGRectMake(0, 0, 23, 23); + QPixmap pm(23, 23); + pm.fill(Qt::transparent); + { + QMacCGContext pmcg(&pm); + HIThemeDrawTab(&hirect, &tdi, pmcg, kHIThemeOrientationNormal, 0); + } + QStyleHelper::drawBorderPixmap(pm, p, tabRect, 7, 7, 7, 7); + } else { + HIRect hirect = qt_hirectForQRect(tabRect); + HIThemeDrawTab(&hirect, &tdi, cg, kHIThemeOrientationNormal, 0); + } + } + break; + case CE_TabBarTabLabel: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + QStyleOptionTabV3 myTab = *tab; + ThemeTabDirection ttd = getTabDirection(myTab.shape); + bool verticalTabs = ttd == kThemeTabWest || ttd == kThemeTabEast; + + // Check to see if we use have the same as the system font + // (QComboMenuItem is internal and should never be seen by the + // outside world, unless they read the source, in which case, it's + // their own fault). + bool nonDefaultFont = p->font() != qt_app_fonts_hash()->value("QComboMenuItem"); + if (verticalTabs || nonDefaultFont || !tab->icon.isNull() + || !myTab.leftButtonSize.isNull() || !myTab.rightButtonSize.isNull()) { + int heightOffset = 0; + if (verticalTabs) { + heightOffset = -1; + } else if (nonDefaultFont) { + if (p->fontMetrics().height() == myTab.rect.height()) + heightOffset = 2; + } + myTab.rect.setHeight(myTab.rect.height() + heightOffset); + + if (myTab.documentMode) { + p->save(); + rotateTabPainter(p, myTab.shape, myTab.rect); + + QPalette np = tab->palette; + np.setColor(QPalette::WindowText, QColor(255, 255, 255, 75)); + QRect nr = subElementRect(SE_TabBarTabText, opt, w); + nr.moveTop(-1); + int alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextHideMnemonic; + proxy()->drawItemText(p, nr, alignment, np, tab->state & State_Enabled, + tab->text, QPalette::WindowText); + p->restore(); + } + + QCommonStyle::drawControl(ce, &myTab, p, w); + } else { + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor = myTab.palette.windowText().color(); + CGFloat colorComp[] = { textColor.redF(), textColor.greenF(), + textColor.blueF(), textColor.alphaF() }; + CGContextSetFillColorSpace(cg, QCoreGraphicsPaintEngine::macGenericColorSpace()); + CGContextSetFillColor(cg, colorComp); + switch (d->aquaSizeConstrain(opt, w)) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + tti.fontID = kThemeSystemFont; + break; + case QAquaSizeSmall: + tti.fontID = kThemeSmallSystemFont; + break; + case QAquaSizeMini: + tti.fontID = kThemeMiniSystemFont; + break; + } + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = verticalTabs ? kHIThemeTextBoxOptionStronglyVertical : kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + myTab.text.count(QLatin1Char('\n')); + QCFString tabText = qt_mac_removeMnemonics(myTab.text); + QRect r = myTab.rect.adjusted(0, 0, 0, -1); + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(tabText, &bounds, &tti, cg, kHIThemeOrientationNormal); + p->restore(); + } + } + break; + case CE_DockWidgetTitle: + if (const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(w)) { + bool floating = dockWidget->isFloating(); + if (floating) { + ThemeDrawState tds = d->getDrawState(opt->state); + HIThemeWindowDrawInfo wdi; + wdi.version = qt_mac_hitheme_version; + wdi.state = tds; + wdi.windowType = kThemeMovableDialogWindow; + wdi.titleHeight = opt->rect.height(); + wdi.titleWidth = opt->rect.width(); + wdi.attributes = 0; + + HIRect titleBarRect; + HIRect tmpRect = qt_hirectForQRect(opt->rect); + { + QCFType<HIShapeRef> titleRegion; + QRect newr = opt->rect.adjusted(0, 0, 2, 0); + HIThemeGetWindowShape(&tmpRect, &wdi, kWindowTitleBarRgn, &titleRegion); + ptrHIShapeGetBounds(titleRegion, &tmpRect); + newr.translate(newr.x() - int(tmpRect.origin.x), newr.y() - int(tmpRect.origin.y)); + titleBarRect = qt_hirectForQRect(newr); + } + QMacCGContext cg(p); + HIThemeDrawWindowFrame(&titleBarRect, &wdi, cg, kHIThemeOrientationNormal, 0); + } else { + // fill title bar background + QLinearGradient linearGrad(0, opt->rect.top(), 0, opt->rect.bottom()); + linearGrad.setColorAt(0, mainWindowGradientBegin); + linearGrad.setColorAt(1, mainWindowGradientEnd); + p->fillRect(opt->rect, linearGrad); + + // draw horizontal lines at top and bottom + p->save(); + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); + p->restore(); + } + } + + // Draw the text... + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) { + if (!dwOpt->title.isEmpty()) { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, opt, w); + if (verticalTitleBar) { + QRect rect = dwOpt->rect; + QRect r = rect; + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + + p->translate(r.left(), r.top() + r.width()); + p->rotate(-90); + p->translate(-r.left(), -r.top()); + } + + QFont oldFont = p->font(); + p->setFont(qt_app_fonts_hash()->value("QToolButton", p->font())); + QString text = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, + titleRect.width()); + drawItemText(p, titleRect, + Qt::AlignCenter | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, text, + QPalette::WindowText); + p->setFont(oldFont); + } + } + break; + case CE_FocusFrame: { + int xOff = proxy()->pixelMetric(PM_FocusFrameHMargin, opt, w) + 1; + int yOff = proxy()->pixelMetric(PM_FocusFrameVMargin, opt, w) + 1; + HIRect hirect = CGRectMake(xOff+opt->rect.x(), yOff+opt->rect.y(), opt->rect.width() - 2 * xOff, + opt->rect.height() - 2 * yOff); + HIThemeDrawFocusRect(&hirect, true, QMacCGContext(p), kHIThemeOrientationNormal); + break; } + case CE_MenuItem: + case CE_MenuEmptyArea: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + p->fillRect(mi->rect, opt->palette.background()); + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, w); + int tabwidth = mi->tabWidth; + int maxpmw = mi->maxIconWidth; + bool active = mi->state & State_Selected; + bool enabled = mi->state & State_Enabled; + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + mdi.itemType = kThemeMenuItemPlain; + if (!mi->icon.isNull()) + mdi.itemType |= kThemeMenuItemHasIcon; + if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + mdi.itemType |= kThemeMenuItemHierarchical | kThemeMenuItemHierBackground; + else + mdi.itemType |= kThemeMenuItemPopUpBackground; + if (enabled) + mdi.state = kThemeMenuActive; + else + mdi.state = kThemeMenuDisabled; + if (active) + mdi.state |= kThemeMenuSelected; + QRect contentRect; + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + // First arg should be &menurect, but wacky stuff happens then. + HIThemeDrawMenuSeparator(&itemRect, &itemRect, &mdi, + cg, kHIThemeOrientationNormal); + break; + } else { + HIRect cr; + bool needAlpha = mi->palette.color(QPalette::Button) == Qt::transparent; + if (needAlpha) { + needAlpha = true; + CGContextSaveGState(cg); + CGContextSetAlpha(cg, 0.0); + } + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, + cg, kHIThemeOrientationNormal, &cr); + if (needAlpha) + CGContextRestoreGState(cg); + if (ce == CE_MenuEmptyArea) + break; + contentRect = qt_qrectForHIRect(cr); + } + int xpos = contentRect.x() + 18; + int checkcol = maxpmw; + if (!enabled) + p->setPen(mi->palette.text().color()); + else if (active) + p->setPen(mi->palette.highlightedText().color()); + else + p->setPen(mi->palette.buttonText().color()); + + if (mi->checked) { + // Use the HIThemeTextInfo foo to draw the check mark correctly, if we do it, + // we somehow need to use a special encoding as it doesn't look right with our + // drawText(). + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + QColor textColor = p->pen().color(); + CGFloat colorComp[] = { textColor.redF(), textColor.greenF(), + textColor.blueF(), textColor.alphaF() }; + CGContextSetFillColorSpace(cg, QCoreGraphicsPaintEngine::macGenericColorSpace()); + CGContextSetFillColor(cg, colorComp); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + if (active && enabled) + tti.state = kThemeStatePressed; + switch (widgetSize) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + tti.fontID = kThemeMenuItemMarkFont; + break; + case QAquaSizeSmall: + tti.fontID = kThemeSmallSystemFont; + break; + case QAquaSizeMini: + tti.fontID = kThemeMiniSystemFont; + break; + } + tti.horizontalFlushness = kHIThemeTextHorizontalFlushLeft; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1; + QCFString checkmark; +#if 0 + if (mi->checkType == QStyleOptionMenuItem::Exclusive) + checkmark = QString(QChar(kDiamondUnicode)); + else +#endif + checkmark = QString(QChar(kCheckUnicode)); + int mw = checkcol + macItemFrame; + int mh = contentRect.height() - 2 * macItemFrame; + int xp = contentRect.x(); + xp += macItemFrame; + CGFloat outWidth, outHeight, outBaseline; + HIThemeGetTextDimensions(checkmark, 0, &tti, &outWidth, &outHeight, + &outBaseline); + if (widgetSize == QAquaSizeMini) + outBaseline += 1; + QRect r(xp, contentRect.y(), mw, mh); + r.translate(0, p->fontMetrics().ascent() - int(outBaseline) + 1); + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(checkmark, &bounds, &tti, + cg, kHIThemeOrientationNormal); + p->restore(); + } + if (!mi->icon.isNull()) { + QIcon::Mode mode = (mi->state & State_Enabled) ? QIcon::Normal + : QIcon::Disabled; + // Always be normal or disabled to follow the Mac style. + int smallIconSize = proxy()->pixelMetric(PM_SmallIconSize); + QSize iconSize(smallIconSize, smallIconSize); + if (const QComboBox *comboBox = qobject_cast<const QComboBox *>(w)) { + iconSize = comboBox->iconSize(); + } + QPixmap pixmap = mi->icon.pixmap(iconSize, mode); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRect cr(xpos, contentRect.y(), checkcol, contentRect.height()); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(cr.center()); + p->drawPixmap(pmr.topLeft(), pixmap); + xpos += pixw + 6; + } + + QString s = mi->text; + if (!s.isEmpty()) { + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignRight | Qt::AlignVCenter | Qt::TextHideMnemonic + | Qt::TextSingleLine | Qt::AlignAbsolute; + int yPos = contentRect.y(); + if (widgetSize == QAquaSizeMini) + yPos += 1; + p->save(); + if (t >= 0) { + p->setFont(qt_app_fonts_hash()->value("QMenuItem", p->font())); + int xp = contentRect.right() - tabwidth - macRightBorder + - macItemHMargin - macItemFrame + 1; + p->drawText(xp, yPos, tabwidth, contentRect.height(), text_flags, + s.mid(t + 1)); + s = s.left(t); + } + + const int xm = macItemFrame + maxpmw + macItemHMargin; + QFont myFont = mi->font; + // myFont may not have any "hard" flags set. We override + // the point size so that when it is resolved against the device, this font will win. + // This is mainly to handle cases where someone sets the font on the window + // and then the combo inherits it and passes it onward. At that point the resolve mask + // is very, very weak. This makes it stonger. + myFont.setPointSizeF(QFontInfo(mi->font).pointSizeF()); + p->setFont(myFont); + p->drawText(xpos, yPos, contentRect.width() - xm - tabwidth + 1, + contentRect.height(), text_flags ^ Qt::AlignRight, s); + p->restore(); + } + } + break; + case CE_MenuHMargin: + case CE_MenuVMargin: + case CE_MenuTearoff: + case CE_MenuScroller: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + p->fillRect(mi->rect, opt->palette.background()); + + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + if (!(opt->state & State_Enabled)) + mdi.state = kThemeMenuDisabled; + else if (opt->state & State_Selected) + mdi.state = kThemeMenuSelected; + else + mdi.state = kThemeMenuActive; + if (ce == CE_MenuScroller) { + if (opt->state & State_DownArrow) + mdi.itemType = kThemeMenuItemScrollDownArrow; + else + mdi.itemType = kThemeMenuItemScrollUpArrow; + } else { + mdi.itemType = kThemeMenuItemPlain; + } + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, + cg, + kHIThemeOrientationNormal, 0); + if (ce == CE_MenuTearoff) { + p->setPen(QPen(mi->palette.dark().color(), 1, Qt::DashLine)); + p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2 - 1, + mi->rect.x() + mi->rect.width() - 4, + mi->rect.y() + mi->rect.height() / 2 - 1); + p->setPen(QPen(mi->palette.light().color(), 1, Qt::DashLine)); + p->drawLine(mi->rect.x() + 2, mi->rect.y() + mi->rect.height() / 2, + mi->rect.x() + mi->rect.width() - 4, + mi->rect.y() + mi->rect.height() / 2); + } + } + break; + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + HIRect menuRect = qt_hirectForQRect(mi->menuRect); + HIRect itemRect = qt_hirectForQRect(mi->rect); + + if ((opt->state & State_Selected) && (opt->state & State_Enabled) && (opt->state & State_Sunken)){ + // Draw a selected menu item background: + HIThemeMenuItemDrawInfo mdi; + mdi.version = qt_mac_hitheme_version; + mdi.state = kThemeMenuSelected; + mdi.itemType = kThemeMenuItemPlain; + HIThemeDrawMenuItem(&menuRect, &itemRect, &mdi, cg, kHIThemeOrientationNormal, 0); + } else { + // Draw the toolbar background: + HIThemeMenuBarDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeMenuBarNormal; + bdi.attributes = 0; + HIThemeDrawMenuBarBackground(&menuRect, &bdi, cg, kHIThemeOrientationNormal); + } + + if (!mi->icon.isNull()) { + drawItemPixmap(p, mi->rect, + Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip + | Qt::TextSingleLine, + mi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), + (mi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled)); + } else { + drawItemText(p, mi->rect, + Qt::AlignCenter | Qt::TextHideMnemonic | Qt::TextDontClip + | Qt::TextSingleLine, + mi->palette, mi->state & State_Enabled, + mi->text, QPalette::ButtonText); + } + } + break; + case CE_MenuBarEmptyArea: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + HIThemeMenuBarDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeMenuBarNormal; + bdi.attributes = 0; + HIRect hirect = qt_hirectForQRect(mi->rect); + HIThemeDrawMenuBarBackground(&hirect, &bdi, cg, + kHIThemeOrientationNormal); + break; + } + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + HIThemeTrackDrawInfo tdi; + tdi.version = qt_mac_hitheme_version; + tdi.reserved = 0; + bool isIndeterminate = (pb->minimum == 0 && pb->maximum == 0); + bool vertical = false; + bool inverted = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + bool reverse = (!vertical && (pb->direction == Qt::RightToLeft)); + if (inverted) + reverse = !reverse; + switch (d->aquaSizeConstrain(opt, w)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + tdi.kind = !isIndeterminate ? kThemeLargeProgressBar + : kThemeLargeIndeterminateBar; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + tdi.kind = !isIndeterminate ? kThemeProgressBar : kThemeIndeterminateBar; + break; + } + tdi.bounds = qt_hirectForQRect(pb->rect); + tdi.max = pb->maximum; + tdi.min = pb->minimum; + tdi.value = pb->progress; + tdi.attributes = vertical ? 0 : kThemeTrackHorizontal; + tdi.trackInfo.progress.phase = d->progressFrame; + if (!(pb->state & State_Active)) + tdi.enableState = kThemeTrackInactive; + else if (!(pb->state & State_Enabled)) + tdi.enableState = kThemeTrackDisabled; + else + tdi.enableState = kThemeTrackActive; + HIThemeOrientation drawOrientation = kHIThemeOrientationNormal; + if (reverse) { + if (vertical) { + drawOrientation = kHIThemeOrientationInverted; + } else { + CGContextSaveGState(cg); + CGContextTranslateCTM(cg, pb->rect.width(), 0); + CGContextScaleCTM(cg, -1, 1); + } + } + HIThemeDrawTrack(&tdi, 0, cg, drawOrientation); + if (reverse && !vertical) + CGContextRestoreGState(cg); + } + break; + case CE_ProgressBarLabel: + case CE_ProgressBarGroove: + break; + case CE_SizeGrip: { + if (w && w->testAttribute(Qt::WA_MacOpaqueSizeGrip)) { + HIThemeGrowBoxDrawInfo gdi; + gdi.version = qt_mac_hitheme_version; + gdi.state = tds; + gdi.kind = kHIThemeGrowBoxKindNormal; + gdi.direction = kThemeGrowRight | kThemeGrowDown; + gdi.size = kHIThemeGrowBoxSizeNormal; + HIPoint pt = CGPointMake(opt->rect.x(), opt->rect.y()); + HIThemeDrawGrowBox(&pt, &gdi, cg, kHIThemeOrientationNormal); + } else { + // It isn't possible to draw a transparent size grip with the + // native API, so we do it ourselves here. + const bool metal = qt_mac_is_metal(w); + QPen lineColor = metal ? QColor(236, 236, 236) : QColor(82, 82, 82, 192); + QPen metalHighlight = QColor(5, 5, 5, 192); + lineColor.setWidth(1); + p->save(); + p->setRenderHint(QPainter::Antialiasing); + p->setPen(lineColor); + const Qt::LayoutDirection layoutDirection = w ? w->layoutDirection() : qApp->layoutDirection(); + const int NumLines = metal ? 4 : 3; + for (int l = 0; l < NumLines; ++l) { + const int offset = (l * 4 + (metal ? 2 : 3)); + QPoint start, end; + if (layoutDirection == Qt::LeftToRight) { + start = QPoint(opt->rect.width() - offset, opt->rect.height() - 1); + end = QPoint(opt->rect.width() - 1, opt->rect.height() - offset); + } else { + start = QPoint(offset, opt->rect.height() - 1); + end = QPoint(1, opt->rect.height() - offset); + } + p->drawLine(start, end); + if (metal) { + p->setPen(metalHighlight); + p->setRenderHint(QPainter::Antialiasing, false); + p->drawLine(start + QPoint(0, -1), end + QPoint(0, -1)); + p->setRenderHint(QPainter::Antialiasing, true); + p->setPen(lineColor); + } + } + p->restore(); + } + break; + } + case CE_Splitter: { + HIThemeSplitterDrawInfo sdi; + sdi.version = qt_mac_hitheme_version; + sdi.state = tds; + sdi.adornment = qt_mac_is_metal(w) ? kHIThemeSplitterAdornmentMetal + : kHIThemeSplitterAdornmentNone; + HIRect hirect = qt_hirectForQRect(opt->rect); + HIThemeDrawPaneSplitter(&hirect, &sdi, cg, kHIThemeOrientationNormal); + break; } + case CE_RubberBand: + if (const QStyleOptionRubberBand *rubber = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + QColor fillColor(opt->palette.color(QPalette::Disabled, QPalette::Highlight)); + if (!rubber->opaque) { + QColor strokeColor; + // I retrieved these colors from the Carbon-Dev mailing list + strokeColor.setHsvF(0, 0, 0.86, 1.0); + fillColor.setHsvF(0, 0, 0.53, 0.25); + if (opt->rect.width() * opt->rect.height() <= 3) { + p->fillRect(opt->rect, strokeColor); + } else { + QPen oldPen = p->pen(); + QBrush oldBrush = p->brush(); + QPen pen(strokeColor); + p->setPen(pen); + p->setBrush(fillColor); + p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + p->setBrush(oldBrush); + } + } else { + p->fillRect(opt->rect, fillColor); + } + } + break; + case CE_ToolBar: { + // For unified tool bars, draw nothing. + if (w) { + if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(w->window())) { + if (mainWindow->unifiedTitleAndToolBarOnMac()) + break; + } + } + + // draw background gradient + QLinearGradient linearGrad; + if (opt->state & State_Horizontal) + linearGrad = QLinearGradient(0, opt->rect.top(), 0, opt->rect.bottom()); + else + linearGrad = QLinearGradient(opt->rect.left(), 0, opt->rect.right(), 0); + + linearGrad.setColorAt(0, mainWindowGradientBegin); + linearGrad.setColorAt(1, mainWindowGradientEnd); + p->fillRect(opt->rect, linearGrad); + + p->save(); + if (opt->state & State_Horizontal) { + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.topRight()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); + + } else { + p->setPen(mainWindowGradientBegin.lighter(114)); + p->drawLine(opt->rect.topLeft(), opt->rect.bottomLeft()); + p->setPen(mainWindowGradientEnd.darker(114)); + p->drawLine(opt->rect.topRight(), opt->rect.bottomRight()); + } + p->restore(); + + + } break; + default: + QWindowsStyle::drawControl(ce, opt, p, w); + break; + } +} + +static void setLayoutItemMargins(int left, int top, int right, int bottom, QRect *rect, Qt::LayoutDirection dir) +{ + if (dir == Qt::RightToLeft) { + rect->adjust(-right, top, -left, bottom); + } else { + rect->adjust(left, top, right, bottom); + } +} + +QRect QMacStyle::subElementRect(SubElement sr, const QStyleOption *opt, + const QWidget *widget) const +{ + QRect rect; + int controlSize = getControlSize(opt, widget); + + switch (sr) { + case SE_ItemViewItemText: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + int fw = proxy()->pixelMetric(PM_FocusFrameHMargin, opt, widget); + // We add the focusframeargin between icon and text in commonstyle + rect = QCommonStyle::subElementRect(sr, opt, widget); + if (vopt->features & QStyleOptionViewItemV2::HasDecoration) + rect.adjust(-fw, 0, 0, 0); + } + break; + case SE_ToolBoxTabContents: + rect = QCommonStyle::subElementRect(sr, opt, widget); + break; + case SE_PushButtonContents: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + // Unlike Carbon, we want the button to always be drawn inside its bounds. + // Therefore, the button is a bit smaller, so that even if it got focus, + // the focus 'shadow' will be inside. Adjust the content rect likewise. + HIThemeButtonDrawInfo bdi; + d->initHIThemePushButton(btn, widget, d->getDrawState(opt->state), &bdi); + HIRect contentRect = d->pushButtonContentBounds(btn, &bdi); + rect = qt_qrectForHIRect(contentRect); + } + break; + case SE_HeaderLabel: + if (qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + rect = QWindowsStyle::subElementRect(sr, opt, widget); + if (widget && widget->height() <= 22){ + // We need to allow the text a bit more space when the header is + // small, otherwise it gets clipped: + rect.setY(0); + rect.setHeight(widget->height()); + } + } + break; + case SE_ProgressBarGroove: + case SE_ProgressBarLabel: + break; + case SE_ProgressBarContents: + rect = opt->rect; + break; + case SE_TreeViewDisclosureItem: { + HIRect inRect = CGRectMake(opt->rect.x(), opt->rect.y(), + opt->rect.width(), opt->rect.height()); + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeStateActive; + bdi.kind = kThemeDisclosureButton; + bdi.value = kThemeDisclosureRight; + bdi.adornment = kThemeAdornmentNone; + HIRect contentRect; + HIThemeGetButtonContentBounds(&inRect, &bdi, &contentRect); + QCFType<HIShapeRef> shape; + HIRect outRect; + HIThemeGetButtonShape(&inRect, &bdi, &shape); + ptrHIShapeGetBounds(shape, &outRect); + rect = QRect(int(outRect.origin.x + DisclosureOffset), int(outRect.origin.y), + int(contentRect.origin.x - outRect.origin.x + DisclosureOffset), + int(outRect.size.height)); + break; + } + case SE_TabWidgetLeftCorner: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect = QRect(QPoint(0, 0), twf->leftCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect = QRect(QPoint(0, twf->rect.height() - twf->leftCornerWidgetSize.height()), + twf->leftCornerWidgetSize); + break; + default: + break; + } + rect = visualRect(twf->direction, twf->rect, rect); + } + break; + case SE_TabWidgetRightCorner: + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), 0), + twf->rightCornerWidgetSize); + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + rect = QRect(QPoint(twf->rect.width() - twf->rightCornerWidgetSize.width(), + twf->rect.height() - twf->rightCornerWidgetSize.height()), + twf->rightCornerWidgetSize); + break; + default: + break; + } + rect = visualRect(twf->direction, twf->rect, rect); + } + break; + case SE_TabWidgetTabContents: + rect = QWindowsStyle::subElementRect(sr, opt, widget); + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + if (twf->lineWidth != 0) { + switch (getTabDirection(twf->shape)) { + case kThemeTabNorth: + rect.adjust(+1, +14, -1, -1); + break; + case kThemeTabSouth: + rect.adjust(+1, +1, -1, -14); + break; + case kThemeTabWest: + rect.adjust(+14, +1, -1, -1); + break; + case kThemeTabEast: + rect.adjust(+1, +1, -14, -1); + } + } + } + break; + case SE_LineEditContents: + rect = QWindowsStyle::subElementRect(sr, opt, widget); + if(widget->parentWidget() && qobject_cast<const QComboBox*>(widget->parentWidget())) + rect.adjust(-1, -2, 0, 0); + else + rect.adjust(-1, 0, 0, +1); + break; + case SE_CheckBoxLayoutItem: + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + setLayoutItemMargins(+2, +3, -9, -4, &rect, opt->direction); + } else if (controlSize == QAquaSizeSmall) { + setLayoutItemMargins(+1, +5, 0 /* fix */, -6, &rect, opt->direction); + } else { + setLayoutItemMargins(0, +7, 0 /* fix */, -6, &rect, opt->direction); + } + break; + case SE_ComboBoxLayoutItem: + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) { + // Do nothing, because QToolbar needs the entire widget rect. + // Otherwise it will be clipped. Equivalent to + // widget->setAttribute(Qt::WA_LayoutUsesWidgetRect), but without + // all the hassle. + } else { + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + rect.adjust(+3, +2, -3, -4); + } else if (controlSize == QAquaSizeSmall) { + setLayoutItemMargins(+2, +1, -3, -4, &rect, opt->direction); + } else { + setLayoutItemMargins(+1, 0, -2, 0, &rect, opt->direction); + } + } + break; + case SE_LabelLayoutItem: + rect = opt->rect; + setLayoutItemMargins(+1, 0 /* SHOULD be -1, done for alignment */, 0, 0 /* SHOULD be -1, done for alignment */, &rect, opt->direction); + break; + case SE_ProgressBarLayoutItem: { + rect = opt->rect; + int bottom = SIZE(3, 8, 8); + if (opt->state & State_Horizontal) { + rect.adjust(0, +1, 0, -bottom); + } else { + setLayoutItemMargins(+1, 0, -bottom, 0, &rect, opt->direction); + } + break; + } + case SE_PushButtonLayoutItem: + if (const QStyleOptionButton *buttonOpt + = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if ((buttonOpt->features & QStyleOptionButton::Flat)) + break; // leave rect alone + } + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + rect.adjust(+6, +4, -6, -8); + } else if (controlSize == QAquaSizeSmall) { + rect.adjust(+5, +4, -5, -6); + } else { + rect.adjust(+1, 0, -1, -2); + } + break; + case SE_RadioButtonLayoutItem: + rect = opt->rect; + if (controlSize == QAquaSizeLarge) { + setLayoutItemMargins(+2, +2 /* SHOULD BE +3, done for alignment */, + 0, -4 /* SHOULD BE -3, done for alignment */, &rect, opt->direction); + } else if (controlSize == QAquaSizeSmall) { + rect.adjust(0, +6, 0 /* fix */, -5); + } else { + rect.adjust(0, +6, 0 /* fix */, -7); + } + break; + case SE_SliderLayoutItem: + if (const QStyleOptionSlider *sliderOpt + = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + rect = opt->rect; + if (sliderOpt->tickPosition == QSlider::NoTicks) { + int above = SIZE(3, 0, 2); + int below = SIZE(4, 3, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.adjust(0, +above, 0, -below); + } else { + rect.adjust(+above, 0, -below, 0); //### Seems that QSlider flip the position of the ticks in reverse mode. + } + } else if (sliderOpt->tickPosition == QSlider::TicksAbove) { + int below = SIZE(3, 2, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.setHeight(rect.height() - below); + } else { + rect.setWidth(rect.width() - below); + } + } else if (sliderOpt->tickPosition == QSlider::TicksBelow) { + int above = SIZE(3, 2, 0); + if (sliderOpt->orientation == Qt::Horizontal) { + rect.setTop(rect.top() + above); + } else { + rect.setLeft(rect.left() + above); + } + } + } + break; + case SE_FrameLayoutItem: + // hack because QStyleOptionFrameV2 doesn't have a frameStyle member + if (const QFrame *frame = qobject_cast<const QFrame *>(widget)) { + rect = opt->rect; + switch (frame->frameStyle() & QFrame::Shape_Mask) { + case QFrame::HLine: + rect.adjust(0, +1, 0, -1); + break; + case QFrame::VLine: + rect.adjust(+1, 0, -1, 0); + break; + default: + ; + } + } + break; + case SE_GroupBoxLayoutItem: + rect = opt->rect; + if (const QStyleOptionGroupBox *groupBoxOpt = + qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + /* + AHIG is very inconsistent when it comes to group boxes. + Basically, we make sure that (non-checkable) group boxes + and tab widgets look good when laid out side by side. + */ + if (groupBoxOpt->subControls & (QStyle::SC_GroupBoxCheckBox + | QStyle::SC_GroupBoxLabel)) { + int delta; + if (groupBoxOpt->subControls & QStyle::SC_GroupBoxCheckBox) { + delta = SIZE(8, 4, 4); // guess + } else { + delta = SIZE(15, 12, 12); // guess + } + rect.setTop(rect.top() + delta); + } + } + rect.setBottom(rect.bottom() - 1); + break; + case SE_TabWidgetLayoutItem: + if (const QStyleOptionTabWidgetFrame *tabWidgetOpt = + qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + /* + AHIG specifies "12 or 14" as the distance from the window + edge. We choose 14 and since the default top margin is 20, + the overlap is 6. + */ + rect = tabWidgetOpt->rect; + if (tabWidgetOpt->shape == QTabBar::RoundedNorth) + rect.setTop(rect.top() + SIZE(6 /* AHIG */, 3 /* guess */, 2 /* AHIG */)); + } + break; + default: + rect = QWindowsStyle::subElementRect(sr, opt, widget); + break; + } + return rect; +} + +static inline void drawToolbarButtonArrow(const QRect &toolButtonRect, ThemeDrawState tds, CGContextRef cg) +{ + QRect arrowRect = QRect(toolButtonRect.right() - 9, toolButtonRect.bottom() - 9, 7, 5); + HIThemePopupArrowDrawInfo padi; + padi.version = qt_mac_hitheme_version; + padi.state = tds; + padi.orientation = kThemeArrowDown; + padi.size = kThemeArrow7pt; + HIRect hirect = qt_hirectForQRect(arrowRect); + HIThemeDrawPopupArrow(&hirect, &padi, cg, kHIThemeOrientationNormal); +} + +void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *widget) const +{ + ThemeDrawState tds = d->getDrawState(opt->state); + QMacCGContext cg(p); + switch (cc) { + case CC_Slider: + case CC_ScrollBar: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + if (slider->state & State_Sunken) { + if (cc == CC_Slider) { + if (slider->activeSubControls == SC_SliderHandle) + tdi.trackInfo.slider.pressState = kThemeThumbPressed; + else if (slider->activeSubControls == SC_SliderGroove) + tdi.trackInfo.slider.pressState = kThemeLeftTrackPressed; + } else { + if (slider->activeSubControls == SC_ScrollBarSubLine + || slider->activeSubControls == SC_ScrollBarAddLine) { + // This test looks complex but it basically boils down + // to the following: The "RTL look" on the mac also + // changed the directions of the controls, that's not + // what people expect (an arrow is an arrow), so we + // kind of fake and say the opposite button is hit. + // This works great, up until 10.4 which broke the + // scroll bars, so I also have actually do something + // similar when I have an upside down scroll bar + // because on Tiger I only "fake" the reverse stuff. + bool reverseHorizontal = (slider->direction == Qt::RightToLeft + && slider->orientation == Qt::Horizontal); + if ((reverseHorizontal + && slider->activeSubControls == SC_ScrollBarAddLine) + || (!reverseHorizontal + && slider->activeSubControls == SC_ScrollBarSubLine)) { + tdi.trackInfo.scrollbar.pressState = kThemeRightInsideArrowPressed + | kThemeLeftOutsideArrowPressed; + } else { + tdi.trackInfo.scrollbar.pressState = kThemeLeftInsideArrowPressed + | kThemeRightOutsideArrowPressed; + } + } else if (slider->activeSubControls == SC_ScrollBarAddPage) { + tdi.trackInfo.scrollbar.pressState = kThemeRightTrackPressed; + } else if (slider->activeSubControls == SC_ScrollBarSubPage) { + tdi.trackInfo.scrollbar.pressState = kThemeLeftTrackPressed; + } else if (slider->activeSubControls == SC_ScrollBarSlider) { + tdi.trackInfo.scrollbar.pressState = kThemeThumbPressed; + } + } + } + HIRect macRect; + bool tracking = slider->sliderPosition == slider->sliderValue; + if (!tracking) { + // Small optimization, the same as q->subControlRect + QCFType<HIShapeRef> shape; + HIThemeGetTrackThumbShape(&tdi, &shape); + ptrHIShapeGetBounds(shape, &macRect); + tdi.value = slider->sliderValue; + } + + // Remove controls from the scroll bar if it is to short to draw them correctly. + // This is done in two stages: first the thumb indicator is removed when it is + // no longer possible to move it, second the up/down buttons are removed when + // there is not enough space for them. + if (cc == CC_ScrollBar) { + const int scrollBarLength = (slider->orientation == Qt::Horizontal) + ? slider->rect.width() : slider->rect.height(); + const QMacStyle::WidgetSizePolicy sizePolicy = widgetSizePolicy(widget); + if (scrollBarLength < scrollButtonsCutoffSize(thumbIndicatorCutoff, sizePolicy)) + tdi.attributes &= ~kThemeTrackShowThumb; + if (scrollBarLength < scrollButtonsCutoffSize(scrollButtonsCutoff, sizePolicy)) + tdi.enableState = kThemeTrackNothingToScroll; + } else { + if (!(slider->subControls & SC_SliderHandle)) + tdi.attributes &= ~kThemeTrackShowThumb; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (!(slider->subControls & SC_SliderGroove)) + tdi.attributes |= kThemeTrackHideTrack; +#endif + } + + HIThemeDrawTrack(&tdi, tracking ? 0 : &macRect, cg, + kHIThemeOrientationNormal); + if (cc == CC_Slider && slider->subControls & SC_SliderTickmarks) { + if (qt_mac_is_metal(widget)) { + if (tdi.enableState == kThemeTrackInactive) + tdi.enableState = kThemeTrackActive; // Looks more Cocoa-like + } + int interval = slider->tickInterval; + if (interval == 0) { + interval = slider->pageStep; + if (interval == 0) + interval = slider->singleStep; + if (interval == 0) + interval = 1; + } + int numMarks = 1 + ((slider->maximum - slider->minimum) / interval); + + if (tdi.trackInfo.slider.thumbDir == kThemeThumbPlain) { + // They asked for both, so we'll give it to them. + tdi.trackInfo.slider.thumbDir = kThemeThumbDownward; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + tdi.trackInfo.slider.thumbDir = kThemeThumbUpward; + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + } else { + HIThemeDrawTrackTickMarks(&tdi, numMarks, + cg, + kHIThemeOrientationNormal); + + } + } + } + break; + case CC_Q3ListView: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if (lv->subControls & SC_Q3ListView) + QWindowsStyle::drawComplexControl(cc, lv, p, widget); + if (lv->subControls & (SC_Q3ListViewBranch | SC_Q3ListViewExpand)) { + int y = lv->rect.y(); + int h = lv->rect.height(); + int x = lv->rect.right() - 10; + for (int i = 1; i < lv->items.size() && y < h; ++i) { + QStyleOptionQ3ListViewItem item = lv->items.at(i); + if (y + item.height > 0 && (item.childCount > 0 + || (item.features & (QStyleOptionQ3ListViewItem::Expandable + | QStyleOptionQ3ListViewItem::Visible)) + == (QStyleOptionQ3ListViewItem::Expandable + | QStyleOptionQ3ListViewItem::Visible))) { + QStyleOption treeOpt(0); + treeOpt.rect.setRect(x, y + item.height / 2 - 4, 9, 9); + treeOpt.palette = lv->palette; + treeOpt.state = lv->state; + treeOpt.state |= State_Children; + if (item.state & State_Open) + treeOpt.state |= State_Open; + proxy()->drawPrimitive(PE_IndicatorBranch, &treeOpt, p, widget); + } + y += item.totalHeight; + } + } + } + break; + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox newSB = *sb; + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + SInt32 frame_size; + GetThemeMetric(kThemeMetricEditTextFrameOutset, &frame_size); + + QRect lineeditRect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxEditField, widget); + lineeditRect.adjust(-frame_size, -frame_size, +frame_size, +frame_size); + + HIThemeFrameDrawInfo fdi; + fdi.version = qt_mac_hitheme_version; + fdi.state = kThemeStateInactive; + fdi.kind = kHIThemeFrameTextFieldSquare; + fdi.isFocused = false; + HIRect hirect = qt_hirectForQRect(lineeditRect); + HIThemeDrawFrame(&hirect, &fdi, cg, kHIThemeOrientationNormal); + } + if (sb->subControls & (SC_SpinBoxUp | SC_SpinBoxDown)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + QAquaWidgetSize aquaSize = d->aquaSizeConstrain(opt, widget); + switch (aquaSize) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + bdi.kind = kThemeIncDecButton; + break; + case QAquaSizeMini: + bdi.kind = kThemeIncDecButtonMini; + break; + case QAquaSizeSmall: + bdi.kind = kThemeIncDecButtonSmall; + break; + } + if (!(sb->stepEnabled & (QAbstractSpinBox::StepUpEnabled + | QAbstractSpinBox::StepDownEnabled))) + tds = kThemeStateUnavailable; + if (sb->activeSubControls == SC_SpinBoxDown + && (sb->state & State_Sunken)) + tds = kThemeStatePressedDown; + else if (sb->activeSubControls == SC_SpinBoxUp + && (sb->state & State_Sunken)) + tds = kThemeStatePressedUp; + bdi.state = tds; + if (!(sb->state & State_Active) + && sb->palette.currentColorGroup() == QPalette::Active + && tds == kThemeStateInactive) + bdi.state = kThemeStateActive; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + + QRect updown = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget); + + updown |= proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); + HIRect newRect = qt_hirectForQRect(updown); + QRect off_rct; + HIRect outRect; + HIThemeGetButtonBackgroundBounds(&newRect, &bdi, &outRect); + off_rct.setRect(int(newRect.origin.x - outRect.origin.x), + int(newRect.origin.y - outRect.origin.y), + int(outRect.size.width - newRect.size.width), + int(outRect.size.height - newRect.size.height)); + + newRect = qt_hirectForQRect(updown, off_rct); + HIThemeDrawButton(&newRect, &bdi, cg, kHIThemeOrientationNormal, 0); + } + } + break; + case CC_ComboBox: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){ + HIThemeButtonDrawInfo bdi; + d->initComboboxBdi(combo, &bdi, widget, d->getDrawState(opt->state)); + bool drawColorless = combo->palette.currentColorGroup() == QPalette::Active && tds == kThemeStateInactive; + if (!drawColorless) + QMacStylePrivate::drawCombobox(qt_hirectForQRect(combo->rect), bdi, p); + else + d->drawColorlessButton(qt_hirectForQRect(combo->rect), &bdi, p, opt); + } + break; + case CC_TitleBar: + if (const QStyleOptionTitleBar *titlebar + = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + if (titlebar->state & State_Active) { + if (titlebar->titleBarState & State_Active) + tds = kThemeStateActive; + else + tds = kThemeStateInactive; + } else { + tds = kThemeStateInactive; + } + + HIThemeWindowDrawInfo wdi; + wdi.version = qt_mac_hitheme_version; + wdi.state = tds; + wdi.windowType = QtWinType; + wdi.titleHeight = titlebar->rect.height(); + wdi.titleWidth = titlebar->rect.width(); + wdi.attributes = kThemeWindowHasTitleText; + // It seems HIThemeDrawTitleBarWidget is not able to draw a dirty + // close button, so use HIThemeDrawWindowFrame instead. + if (widget && widget->isWindowModified() && titlebar->subControls & SC_TitleBarCloseButton) + wdi.attributes |= kThemeWindowHasCloseBox | kThemeWindowHasDirty; + + HIRect titleBarRect; + HIRect tmpRect = qt_hirectForQRect(titlebar->rect); + { + QCFType<HIShapeRef> titleRegion; + QRect newr = titlebar->rect.adjusted(0, 0, 2, 0); + HIThemeGetWindowShape(&tmpRect, &wdi, kWindowTitleBarRgn, &titleRegion); + ptrHIShapeGetBounds(titleRegion, &tmpRect); + newr.translate(newr.x() - int(tmpRect.origin.x), newr.y() - int(tmpRect.origin.y)); + titleBarRect = qt_hirectForQRect(newr); + } + HIThemeDrawWindowFrame(&titleBarRect, &wdi, cg, kHIThemeOrientationNormal, 0); + if (titlebar->subControls & (SC_TitleBarCloseButton + | SC_TitleBarMaxButton + | SC_TitleBarMinButton + | SC_TitleBarNormalButton)) { + HIThemeWindowWidgetDrawInfo wwdi; + wwdi.version = qt_mac_hitheme_version; + wwdi.widgetState = tds; + if (titlebar->state & State_MouseOver) + wwdi.widgetState = kThemeStateRollover; + wwdi.windowType = QtWinType; + wwdi.attributes = wdi.attributes | kThemeWindowHasFullZoom | kThemeWindowHasCloseBox | kThemeWindowHasCollapseBox; + wwdi.windowState = wdi.state; + wwdi.titleHeight = wdi.titleHeight; + wwdi.titleWidth = wdi.titleWidth; + ThemeDrawState savedControlState = wwdi.widgetState; + uint sc = SC_TitleBarMinButton; + ThemeTitleBarWidget tbw = kThemeWidgetCollapseBox; + bool active = titlebar->state & State_Active; + if (qMacVersion() < QSysInfo::MV_10_6) { + int border = 2; + titleBarRect.origin.x += border; + titleBarRect.origin.y -= border; + } + + while (sc <= SC_TitleBarCloseButton) { + if (sc & titlebar->subControls) { + uint tmp = sc; + wwdi.widgetState = savedControlState; + wwdi.widgetType = tbw; + if (sc == SC_TitleBarMinButton) + tmp |= SC_TitleBarNormalButton; + if (active && (titlebar->activeSubControls & tmp) + && (titlebar->state & State_Sunken)) + wwdi.widgetState = kThemeStatePressed; + // Draw all sub controllers except the dirty close button + // (it is already handled by HIThemeDrawWindowFrame). + if (!(widget && widget->isWindowModified() && tbw == kThemeWidgetCloseBox)) { + HIThemeDrawTitleBarWidget(&titleBarRect, &wwdi, cg, kHIThemeOrientationNormal); + p->paintEngine()->syncState(); + } + } + sc = sc << 1; + tbw = tbw >> 1; + } + } + p->paintEngine()->syncState(); + if (titlebar->subControls & SC_TitleBarLabel) { + int iw = 0; + if (!titlebar->icon.isNull()) { + QCFType<HIShapeRef> titleRegion2; + HIThemeGetWindowShape(&titleBarRect, &wdi, kWindowTitleProxyIconRgn, + &titleRegion2); + ptrHIShapeGetBounds(titleRegion2, &tmpRect); + if (tmpRect.size.width != 1) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + iw = titlebar->icon.actualSize(QSize(iconExtent, iconExtent)).width(); + } + } + if (!titlebar->text.isEmpty()) { + p->save(); + QCFType<HIShapeRef> titleRegion3; + HIThemeGetWindowShape(&titleBarRect, &wdi, kWindowTitleTextRgn, &titleRegion3); + ptrHIShapeGetBounds(titleRegion3, &tmpRect); + p->setClipRect(qt_qrectForHIRect(tmpRect)); + QRect br = p->clipRegion().boundingRect(); + int x = br.x(), + y = br.y() + (titlebar->rect.height() / 2 - p->fontMetrics().height() / 2); + if (br.width() <= (p->fontMetrics().width(titlebar->text) + iw * 2)) + x += iw; + else + x += br.width() / 2 - p->fontMetrics().width(titlebar->text) / 2; + if (iw) + p->drawPixmap(x - iw, y, + titlebar->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize), QIcon::Normal)); + drawItemText(p, br, Qt::AlignCenter, opt->palette, tds == kThemeStateActive, + titlebar->text, QPalette::Text); + p->restore(); + } + } + } + break; + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox + = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + + QStyleOptionGroupBox groupBoxCopy(*groupBox); + if ((widget && !widget->testAttribute(Qt::WA_SetFont)) + && QApplication::desktopSettingsAware()) + groupBoxCopy.subControls = groupBoxCopy.subControls & ~SC_GroupBoxLabel; + QWindowsStyle::drawComplexControl(cc, &groupBoxCopy, p, widget); + if (groupBoxCopy.subControls != groupBox->subControls) { + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + p->save(); + CGContextSetShouldAntialias(cg, true); + CGContextSetShouldSmoothFonts(cg, true); + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = tds; + QColor textColor = groupBox->palette.windowText().color(); + CGFloat colorComp[] = { textColor.redF(), textColor.greenF(), + textColor.blueF(), textColor.alphaF() }; + CGContextSetFillColorSpace(cg, QCoreGraphicsPaintEngine::macGenericColorSpace()); + CGContextSetFillColor(cg, colorComp); + tti.fontID = checkable ? kThemeSystemFont : kThemeSmallSystemFont; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + groupBox->text.count(QLatin1Char('\n')); + QCFString groupText = qt_mac_removeMnemonics(groupBox->text); + QRect r = proxy()->subControlRect(CC_GroupBox, groupBox, SC_GroupBoxLabel, widget); + HIRect bounds = qt_hirectForQRect(r); + HIThemeDrawTextBox(groupText, &bounds, &tti, cg, kHIThemeOrientationNormal); + p->restore(); + } + } + break; + case CC_ToolButton: + if (const QStyleOptionToolButton *tb + = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + if (widget && qobject_cast<QToolBar *>(widget->parentWidget())) { + if (tb->subControls & SC_ToolButtonMenu) { + QStyleOption arrowOpt(0); + arrowOpt.rect = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); + arrowOpt.rect.setY(arrowOpt.rect.y() + arrowOpt.rect.height() / 2); + arrowOpt.rect.setHeight(arrowOpt.rect.height() / 2); + arrowOpt.state = tb->state; + arrowOpt.palette = tb->palette; + proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); + } else if ((tb->features & QStyleOptionToolButton::HasMenu) + && (tb->toolButtonStyle != Qt::ToolButtonTextOnly && !tb->icon.isNull())) { + drawToolbarButtonArrow(tb->rect, tds, cg); + } + if (tb->state & State_On) { + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + static QPixmap pm(QLatin1String(":/trolltech/mac/style/images/leopard-unified-toolbar-on.png")); + p->setRenderHint(QPainter::SmoothPixmapTransform); + QStyleHelper::drawBorderPixmap(pm, p, tb->rect, 2, 2, 2, 2); + } else { + QPen oldPen = p->pen(); + p->setPen(QColor(0, 0, 0, 0x3a)); + p->fillRect(tb->rect.adjusted(1, 1, -1, -1), QColor(0, 0, 0, 0x12)); + p->drawLine(tb->rect.left() + 1, tb->rect.top(), + tb->rect.right() - 1, tb->rect.top()); + p->drawLine(tb->rect.left() + 1, tb->rect.bottom(), + tb->rect.right() - 1, tb->rect.bottom()); + p->drawLine(tb->rect.topLeft(), tb->rect.bottomLeft()); + p->drawLine(tb->rect.topRight(), tb->rect.bottomRight()); + p->setPen(oldPen); + } + } + proxy()->drawControl(CE_ToolButtonLabel, opt, p, widget); + } else { + ThemeButtonKind bkind = kThemeBevelButton; + switch (d->aquaSizeConstrain(opt, widget)) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + bkind = kThemeBevelButton; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + bkind = kThemeSmallBevelButton; + break; + } + + QRect button, menuarea; + button = proxy()->subControlRect(cc, tb, SC_ToolButton, widget); + menuarea = proxy()->subControlRect(cc, tb, SC_ToolButtonMenu, widget); + State bflags = tb->state, + mflags = tb->state; + if (tb->subControls & SC_ToolButton) + bflags |= State_Sunken; + if (tb->subControls & SC_ToolButtonMenu) + mflags |= State_Sunken; + + if (tb->subControls & SC_ToolButton) { + if (bflags & (State_Sunken | State_On | State_Raised)) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + bdi.adornment = kThemeAdornmentNone; + bdi.kind = bkind; + bdi.value = kThemeButtonOff; + if (tb->state & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + if (tb->state & State_Sunken) + bdi.state = kThemeStatePressed; + if (tb->state & State_On) + bdi.value = kThemeButtonOn; + + QRect off_rct(0, 0, 0, 0); + HIRect myRect, macRect; + myRect = CGRectMake(tb->rect.x(), tb->rect.y(), + tb->rect.width(), tb->rect.height()); + HIThemeGetButtonBackgroundBounds(&myRect, &bdi, &macRect); + off_rct.setRect(int(myRect.origin.x - macRect.origin.x), + int(myRect.origin.y - macRect.origin.y), + int(macRect.size.width - myRect.size.width), + int(macRect.size.height - myRect.size.height)); + + myRect = qt_hirectForQRect(button, off_rct); + HIThemeDrawButton(&myRect, &bdi, cg, kHIThemeOrientationNormal, 0); + } + } + + if (tb->subControls & SC_ToolButtonMenu) { + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = tds; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + bdi.kind = bkind; + if (tb->state & State_HasFocus) + bdi.adornment = kThemeAdornmentFocus; + if (tb->state & (State_On | State_Sunken) + || (tb->activeSubControls & SC_ToolButtonMenu)) + bdi.state = kThemeStatePressed; + HIRect hirect = qt_hirectForQRect(menuarea); + HIThemeDrawButton(&hirect, &bdi, cg, kHIThemeOrientationNormal, 0); + QRect r(menuarea.x() + ((menuarea.width() / 2) - 3), menuarea.height() - 8, 8, 8); + HIThemePopupArrowDrawInfo padi; + padi.version = qt_mac_hitheme_version; + padi.state = tds; + padi.orientation = kThemeArrowDown; + padi.size = kThemeArrow7pt; + hirect = qt_hirectForQRect(r); + HIThemeDrawPopupArrow(&hirect, &padi, cg, kHIThemeOrientationNormal); + } else if (tb->features & QStyleOptionToolButton::HasMenu) { + drawToolbarButtonArrow(tb->rect, tds, cg); + } + QRect buttonRect = proxy()->subControlRect(CC_ToolButton, tb, SC_ToolButton, widget); + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + QStyleOptionToolButton label = *tb; + label.rect = buttonRect.adjusted(fw, fw, -fw, -fw); + proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget); + } + } + break; + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(opt)) + QStyleHelper::drawDial(dial, p); + break; + default: + QWindowsStyle::drawComplexControl(cc, opt, p, widget); + break; + } +} + +QStyle::SubControl QMacStyle::hitTestComplexControl(ComplexControl cc, + const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *widget) const +{ + SubControl sc = QStyle::SC_None; + switch (cc) { + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + sc = QWindowsStyle::hitTestComplexControl(cc, cmb, pt, widget); + if (!cmb->editable && sc != QStyle::SC_None) + sc = SC_ComboBoxArrow; // A bit of a lie, but what we want + } + break; + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + ControlPartCode part; + HIPoint pos = CGPointMake(pt.x(), pt.y()); + if (HIThemeHitTestTrack(&tdi, &pos, &part)) { + if (part == kControlPageUpPart || part == kControlPageDownPart) + sc = SC_SliderGroove; + else + sc = SC_SliderHandle; + } + } + break; + case CC_ScrollBar: + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIScrollBarTrackInfo sbi; + sbi.version = qt_mac_hitheme_version; + if (!(sb->state & State_Active)) + sbi.enableState = kThemeTrackInactive; + else if (sb->state & State_Enabled) + sbi.enableState = kThemeTrackActive; + else + sbi.enableState = kThemeTrackDisabled; + + // The arrow buttons are not drawn if the scroll bar is to short, + // exclude them from the hit test. + const int scrollBarLength = (sb->orientation == Qt::Horizontal) + ? sb->rect.width() : sb->rect.height(); + if (scrollBarLength < scrollButtonsCutoffSize(scrollButtonsCutoff, widgetSizePolicy(widget))) + sbi.enableState = kThemeTrackNothingToScroll; + + sbi.viewsize = sb->pageStep; + HIPoint pos = CGPointMake(pt.x(), pt.y()); + + HIRect macSBRect = qt_hirectForQRect(sb->rect); + ControlPartCode part; + bool reverseHorizontal = (sb->direction == Qt::RightToLeft + && sb->orientation == Qt::Horizontal + && (!sb->upsideDown || + (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4 + && sb->upsideDown))); + if (HIThemeHitTestScrollBarArrows(&macSBRect, &sbi, sb->orientation == Qt::Horizontal, + &pos, 0, &part)) { + if (part == kControlUpButtonPart) + sc = reverseHorizontal ? SC_ScrollBarAddLine : SC_ScrollBarSubLine; + else if (part == kControlDownButtonPart) + sc = reverseHorizontal ? SC_ScrollBarSubLine : SC_ScrollBarAddLine; + } else { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, sb, &tdi, widget); + if(tdi.enableState == kThemeTrackInactive) + tdi.enableState = kThemeTrackActive; + if (HIThemeHitTestTrack(&tdi, &pos, &part)) { + if (part == kControlPageUpPart) + sc = reverseHorizontal ? SC_ScrollBarAddPage + : SC_ScrollBarSubPage; + else if (part == kControlPageDownPart) + sc = reverseHorizontal ? SC_ScrollBarSubPage + : SC_ScrollBarAddPage; + else + sc = SC_ScrollBarSlider; + } + } + } + break; +/* + I don't know why, but we only get kWindowContentRgn here, which isn't what we want at all. + It would be very nice if this would work. + case QStyle::CC_TitleBar: + if (const QStyleOptionTitleBar *tbar = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + HIThemeWindowDrawInfo wdi; + memset(&wdi, 0, sizeof(wdi)); + wdi.version = qt_mac_hitheme_version; + wdi.state = kThemeStateActive; + wdi.windowType = QtWinType; + wdi.titleWidth = tbar->rect.width(); + wdi.titleHeight = tbar->rect.height(); + if (tbar->titleBarState) + wdi.attributes |= kThemeWindowHasFullZoom | kThemeWindowHasCloseBox + | kThemeWindowHasCollapseBox; + else if (tbar->titleBarFlags & Qt::WindowSystemMenuHint) + wdi.attributes |= kThemeWindowHasCloseBox; + QRect tmpRect = tbar->rect; + tmpRect.setHeight(tmpRect.height() + 100); + HIRect hirect = qt_hirectForQRect(tmpRect); + WindowRegionCode hit; + HIPoint hipt = CGPointMake(pt.x(), pt.y()); + if (HIThemeGetWindowRegionHit(&hirect, &wdi, &hipt, &hit)) { + switch (hit) { + case kWindowCloseBoxRgn: + sc = QStyle::SC_TitleBarCloseButton; + break; + case kWindowCollapseBoxRgn: + sc = QStyle::SC_TitleBarMinButton; + break; + case kWindowZoomBoxRgn: + sc = QStyle::SC_TitleBarMaxButton; + break; + case kWindowTitleTextRgn: + sc = QStyle::SC_TitleBarLabel; + break; + default: + qDebug("got something else %d", hit); + break; + } + } + } + break; +*/ + default: + sc = QWindowsStyle::hitTestComplexControl(cc, opt, pt, widget); + break; + } + return sc; +} + +QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *widget) const +{ + QRect ret; + switch (cc) { + case CC_Slider: + case CC_ScrollBar: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + HIThemeTrackDrawInfo tdi; + d->getSliderInfo(cc, slider, &tdi, widget); + HIRect macRect; + QCFType<HIShapeRef> shape; + bool scrollBar = cc == CC_ScrollBar; + if ((scrollBar && sc == SC_ScrollBarSlider) + || (!scrollBar && sc == SC_SliderHandle)) { + HIThemeGetTrackThumbShape(&tdi, &shape); + ptrHIShapeGetBounds(shape, &macRect); + } else if (!scrollBar && sc == SC_SliderGroove) { + HIThemeGetTrackBounds(&tdi, &macRect); + } else if (sc == SC_ScrollBarGroove) { // Only scroll bar parts available... + HIThemeGetTrackDragRect(&tdi, &macRect); + } else { + ControlPartCode cpc; + if (sc == SC_ScrollBarSubPage || sc == SC_ScrollBarAddPage) { + cpc = sc == SC_ScrollBarSubPage ? kControlPageDownPart + : kControlPageUpPart; + } else { + cpc = sc == SC_ScrollBarSubLine ? kControlUpButtonPart + : kControlDownButtonPart; + if (slider->direction == Qt::RightToLeft + && slider->orientation == Qt::Horizontal) { + if (cpc == kControlDownButtonPart) + cpc = kControlUpButtonPart; + else if (cpc == kControlUpButtonPart) + cpc = kControlDownButtonPart; + } + } + HIThemeGetTrackPartBounds(&tdi, cpc, &macRect); + } + ret = qt_qrectForHIRect(macRect); + + // Tweak: the dark line between the sub/add line buttons belong to only one of the buttons + // when doing hit-testing, but both of them have to repaint it. Extend the rect to cover + // the line in the cases where HIThemeGetTrackPartBounds returns a rect that doesn't. + if (slider->orientation == Qt::Horizontal) { + if (slider->direction == Qt::LeftToRight && sc == SC_ScrollBarSubLine) + ret.adjust(0, 0, 1, 0); + else if (slider->direction == Qt::RightToLeft && sc == SC_ScrollBarAddLine) + ret.adjust(-1, 0, 1, 0); + } else if (sc == SC_ScrollBarAddLine) { + ret.adjust(0, -1, 0, 1); + } + } + break; + case CC_TitleBar: + if (const QStyleOptionTitleBar *titlebar + = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + HIThemeWindowDrawInfo wdi; + memset(&wdi, 0, sizeof(wdi)); + wdi.version = qt_mac_hitheme_version; + wdi.state = kThemeStateActive; + wdi.windowType = QtWinType; + wdi.titleHeight = titlebar->rect.height(); + wdi.titleWidth = titlebar->rect.width(); + wdi.attributes = kThemeWindowHasTitleText; + if (titlebar->subControls & SC_TitleBarCloseButton) + wdi.attributes |= kThemeWindowHasCloseBox; + if (titlebar->subControls & SC_TitleBarMaxButton + | SC_TitleBarNormalButton) + wdi.attributes |= kThemeWindowHasFullZoom; + if (titlebar->subControls & SC_TitleBarMinButton) + wdi.attributes |= kThemeWindowHasCollapseBox; + WindowRegionCode wrc = kWindowGlobalPortRgn; + + if (sc == SC_TitleBarCloseButton) + wrc = kWindowCloseBoxRgn; + else if (sc == SC_TitleBarMinButton) + wrc = kWindowCollapseBoxRgn; + else if (sc == SC_TitleBarMaxButton) + wrc = kWindowZoomBoxRgn; + else if (sc == SC_TitleBarLabel) + wrc = kWindowTitleTextRgn; + else if (sc == SC_TitleBarSysMenu) + ret.setRect(-1024, -1024, 10, proxy()->pixelMetric(PM_TitleBarHeight, + titlebar, widget)); + if (wrc != kWindowGlobalPortRgn) { + QCFType<HIShapeRef> region; + QRect tmpRect = titlebar->rect; + HIRect titleRect = qt_hirectForQRect(tmpRect); + HIThemeGetWindowShape(&titleRect, &wdi, kWindowTitleBarRgn, ®ion); + ptrHIShapeGetBounds(region, &titleRect); + CFRelease(region); + tmpRect.translate(tmpRect.x() - int(titleRect.origin.x), + tmpRect.y() - int(titleRect.origin.y)); + titleRect = qt_hirectForQRect(tmpRect); + HIThemeGetWindowShape(&titleRect, &wdi, wrc, ®ion); + ptrHIShapeGetBounds(region, &titleRect); + ret = qt_qrectForHIRect(titleRect); + } + } + break; + case CC_ComboBox: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + HIThemeButtonDrawInfo bdi; + d->initComboboxBdi(combo, &bdi, widget, d->getDrawState(opt->state)); + + switch (sc) { + case SC_ComboBoxEditField:{ + ret = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + // hack to posistion the edit feld correctly for QDateTimeEdits + // in calendarPopup mode. + if (qobject_cast<const QDateTimeEdit *>(widget)) { + ret.moveTop(ret.top() - 2); + ret.setHeight(ret.height() +1); + } + break; } + case SC_ComboBoxArrow:{ + ret = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + ret.setX(ret.x() + ret.width()); + ret.setWidth(combo->rect.right() - ret.right()); + break; } + case SC_ComboBoxListBoxPopup:{ + if (combo->editable) { + HIRect inner = QMacStylePrivate::comboboxInnerBounds(qt_hirectForQRect(combo->rect), bdi.kind); + QRect editRect = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + const int comboTop = combo->rect.top(); + ret = QRect(qRound(inner.origin.x), + comboTop, + qRound(inner.origin.x - combo->rect.left() + inner.size.width), + editRect.bottom() - comboTop + 2); + } else { + QRect editRect = QMacStylePrivate::comboboxEditBounds(combo->rect, bdi); + ret = QRect(combo->rect.x() + 4 - 11, + combo->rect.y() + 1, + editRect.width() + 10 + 11, + 1); + } + break; } + default: + break; + } + } + break; + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + bool flat = (groupBox->features & QStyleOptionFrameV2::Flat); + bool hasNoText = !checkable && groupBox->text.isEmpty(); + switch (sc) { + case SC_GroupBoxLabel: + case SC_GroupBoxCheckBox: { + // Cheat and use the smaller font if we need to + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + bool fontIsSet = (widget && widget->testAttribute(Qt::WA_SetFont)) + || !QApplication::desktopSettingsAware(); + int tw; + int h; + int margin = flat || hasNoText ? 0 : 12; + ret = groupBox->rect.adjusted(margin, 0, -margin, 0); + + if (!fontIsSet) { + HIThemeTextInfo tti; + tti.version = qt_mac_hitheme_version; + tti.state = kThemeStateActive; + tti.fontID = checkable ? kThemeSystemFont : kThemeSmallSystemFont; + tti.horizontalFlushness = kHIThemeTextHorizontalFlushCenter; + tti.verticalFlushness = kHIThemeTextVerticalFlushCenter; + tti.options = kHIThemeTextBoxOptionNone; + tti.truncationPosition = kHIThemeTextTruncationNone; + tti.truncationMaxLines = 1 + groupBox->text.count(QLatin1Char('\n')); + CGFloat width; + CGFloat height; + QCFString groupText = qt_mac_removeMnemonics(groupBox->text); + HIThemeGetTextDimensions(groupText, 0, &tti, &width, &height, 0); + tw = qRound(width); + h = qCeil(height); + } else { + QFontMetricsF fm = QFontMetricsF(groupBox->fontMetrics); + h = qCeil(fm.height()); + tw = qCeil(fm.size(Qt::TextShowMnemonic, groupBox->text).width()); + } + ret.setHeight(h); + + QRect labelRect = alignedRect(groupBox->direction, groupBox->textAlignment, + QSize(tw, h), ret); + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, opt, widget); + bool rtl = groupBox->direction == Qt::RightToLeft; + if (sc == SC_GroupBoxLabel) { + if (checkable) { + int newSum = indicatorWidth + 1; + int newLeft = labelRect.left() + (rtl ? -newSum : newSum); + labelRect.moveLeft(newLeft); + } else if (flat) { + int newLeft = labelRect.left() - (rtl ? 3 : -3); + labelRect.moveLeft(newLeft); + labelRect.moveTop(labelRect.top() + 3); + } else { + int newLeft = labelRect.left() - (rtl ? 3 : 2); + labelRect.moveLeft(newLeft); + labelRect.moveTop(labelRect.top() + 5); + } + ret = labelRect; + } + + if (sc == SC_GroupBoxCheckBox) { + int left = rtl ? labelRect.right() - indicatorWidth : labelRect.left(); + ret.setRect(left, ret.top(), + indicatorWidth, proxy()->pixelMetric(PM_IndicatorHeight, opt, widget)); + } + break; + } + case SC_GroupBoxContents: + case SC_GroupBoxFrame: { + if (flat) { + ret = QWindowsStyle::subControlRect(cc, groupBox, sc, widget); + break; + } + QFontMetrics fm = groupBox->fontMetrics; + bool checkable = groupBox->subControls & SC_GroupBoxCheckBox; + int yOffset = 3; + if (!checkable) { + if (widget && !widget->testAttribute(Qt::WA_SetFont) + && QApplication::desktopSettingsAware()) + fm = QFontMetrics(qt_app_fonts_hash()->value("QSmallFont", QFont())); + yOffset = 5; + if (hasNoText) + yOffset = -qCeil(QFontMetricsF(fm).height()); + } + + ret = opt->rect.adjusted(0, qCeil(QFontMetricsF(fm).height()) + yOffset, 0, 0); + if (sc == SC_GroupBoxContents) + ret.adjust(3, 3, -3, -4); // guess + } + break; + default: + ret = QWindowsStyle::subControlRect(cc, groupBox, sc, widget); + break; + } + } + break; + case CC_SpinBox: + if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QAquaWidgetSize aquaSize = d->aquaSizeConstrain(spin, widget); + int spinner_w; + int spinBoxSep; + int fw = proxy()->pixelMetric(PM_SpinBoxFrameWidth, spin, widget); + switch (aquaSize) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + spinner_w = 14; + spinBoxSep = 2; + break; + case QAquaSizeSmall: + spinner_w = 12; + spinBoxSep = 2; + break; + case QAquaSizeMini: + spinner_w = 10; + spinBoxSep = 1; + break; + } + + switch (sc) { + case SC_SpinBoxUp: + case SC_SpinBoxDown: { + if (spin->buttonSymbols == QAbstractSpinBox::NoButtons) + break; + + const int y = fw; + const int x = spin->rect.width() - spinner_w; + ret.setRect(x + spin->rect.x(), y + spin->rect.y(), spinner_w, spin->rect.height() - y * 2); + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.kind = kThemeIncDecButton; + int hackTranslateX; + switch (aquaSize) { + default: + case QAquaSizeUnknown: + case QAquaSizeLarge: + bdi.kind = kThemeIncDecButton; + hackTranslateX = 0; + break; + case QAquaSizeSmall: + bdi.kind = kThemeIncDecButtonSmall; + hackTranslateX = -2; + break; + case QAquaSizeMini: + bdi.kind = kThemeIncDecButtonMini; + hackTranslateX = -1; + break; + } + bdi.state = kThemeStateActive; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + HIRect hirect = qt_hirectForQRect(ret); + + HIRect outRect; + HIThemeGetButtonBackgroundBounds(&hirect, &bdi, &outRect); + ret = qt_qrectForHIRect(outRect); + switch (sc) { + case SC_SpinBoxUp: + ret.setHeight(ret.height() / 2); + break; + case SC_SpinBoxDown: + ret.setY(ret.y() + ret.height() / 2); + break; + default: + Q_ASSERT(0); + break; + } + ret.translate(hackTranslateX, 0); // hack: position the buttons correctly (weird that we need this) + ret = visualRect(spin->direction, spin->rect, ret); + break; + } + case SC_SpinBoxEditField: + if (spin->buttonSymbols == QAbstractSpinBox::NoButtons) { + ret.setRect(fw, fw, + spin->rect.width() - fw * 2, + spin->rect.height() - fw * 2); + } else { + ret.setRect(fw, fw, + spin->rect.width() - fw * 2 - spinBoxSep - spinner_w, + spin->rect.height() - fw * 2); + } + ret = visualRect(spin->direction, spin->rect, ret); + break; + default: + ret = QWindowsStyle::subControlRect(cc, spin, sc, widget); + break; + } + } + break; + case CC_ToolButton: + ret = QWindowsStyle::subControlRect(cc, opt, sc, widget); + if (sc == SC_ToolButtonMenu && widget && !qobject_cast<QToolBar*>(widget->parentWidget())) { + ret.adjust(-1, 0, 0, 0); + } + break; + default: + ret = QWindowsStyle::subControlRect(cc, opt, sc, widget); + break; + } + return ret; +} + +QSize QMacStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *widget) const +{ + QSize sz(csz); + bool useAquaGuideline = true; + + switch (ct) { + case QStyle::CT_SpinBox: + // hack to work around horrible sizeHint() code in QAbstractSpinBox + sz.setHeight(sz.height() - 3); + break; + case QStyle::CT_TabWidget: + // the size between the pane and the "contentsRect" (+4,+4) + // (the "contentsRect" is on the inside of the pane) + sz = QWindowsStyle::sizeFromContents(ct, opt, csz, widget); + /** + This is supposed to show the relationship between the tabBar and + the stack widget of a QTabWidget. + Unfortunately ascii is not a good way of representing graphics..... + PS: The '=' line is the painted frame. + + top ---+ + | + | + | + | vvv just outside the painted frame is the "pane" + - -|- - - - - - - - - - <-+ + TAB BAR +=====^============ | +2 pixels + - - -|- - -|- - - - - - - <-+ + | | ^ ^^^ just inside the painted frame is the "contentsRect" + | | | + | overlap | + | | | + bottom ------+ <-+ +14 pixels + | + v + ------------------------------ <- top of stack widget + + + To summarize: + * 2 is the distance between the pane and the contentsRect + * The 14 and the 1's are the distance from the contentsRect to the stack widget. + (same value as used in SE_TabWidgetTabContents) + * overlap is how much the pane should overlap the tab bar + */ + // then add the size between the stackwidget and the "contentsRect" + + if (const QStyleOptionTabWidgetFrame *twf + = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QSize extra(0,0); + const int overlap = pixelMetric(PM_TabBarBaseOverlap, opt, widget); + const int gapBetweenTabbarAndStackWidget = 2 + 14 - overlap; + + if (getTabDirection(twf->shape) == kThemeTabNorth || getTabDirection(twf->shape) == kThemeTabSouth) { + extra = QSize(2, gapBetweenTabbarAndStackWidget + 1); + } else { + extra = QSize(gapBetweenTabbarAndStackWidget + 1, 2); + } + sz+= extra; + } + + break; + case QStyle::CT_TabBarTab: + if (const QStyleOptionTabV3 *tab = qstyleoption_cast<const QStyleOptionTabV3 *>(opt)) { + const QAquaWidgetSize AquaSize = d->aquaSizeConstrain(opt, widget); + const bool differentFont = (widget && widget->testAttribute(Qt::WA_SetFont)) + || !QApplication::desktopSettingsAware(); + ThemeTabDirection ttd = getTabDirection(tab->shape); + bool vertTabs = ttd == kThemeTabWest || ttd == kThemeTabEast; + if (vertTabs) + sz.transpose(); + int defaultTabHeight; + int defaultExtraSpace = proxy()->pixelMetric(PM_TabBarTabHSpace, tab, widget); // Remove spurious gcc warning (AFAIK) + QFontMetrics fm = opt->fontMetrics; + switch (AquaSize) { + case QAquaSizeUnknown: + case QAquaSizeLarge: + if (tab->documentMode) + defaultTabHeight = 23; + else + defaultTabHeight = 21; + break; + case QAquaSizeSmall: + defaultTabHeight = 18; + break; + case QAquaSizeMini: + defaultTabHeight = 16; + break; + } + bool setWidth = false; + if (differentFont || !tab->icon.isNull()) { + sz.rheight() = qMax(defaultTabHeight, sz.height()); + } else { + QSize textSize = fm.size(Qt::TextShowMnemonic, tab->text); + sz.rheight() = qMax(defaultTabHeight, textSize.height()); + sz.rwidth() = textSize.width() + defaultExtraSpace; + setWidth = true; + } + + if (vertTabs) + sz.transpose(); + + int maxWidgetHeight = qMax(tab->leftButtonSize.height(), tab->rightButtonSize.height()); + int maxWidgetWidth = qMax(tab->leftButtonSize.width(), tab->rightButtonSize.width()); + + int widgetWidth = 0; + int widgetHeight = 0; + int padding = 0; + if (tab->leftButtonSize.isValid()) { + padding += 8; + widgetWidth += tab->leftButtonSize.width(); + widgetHeight += tab->leftButtonSize.height(); + } + if (tab->rightButtonSize.isValid()) { + padding += 8; + widgetWidth += tab->rightButtonSize.width(); + widgetHeight += tab->rightButtonSize.height(); + } + + if (vertTabs) { + sz.setHeight(sz.height() + widgetHeight + padding); + sz.setWidth(qMax(sz.width(), maxWidgetWidth)); + } else { + if (setWidth) + sz.setWidth(sz.width() + widgetWidth + padding); + sz.setHeight(qMax(sz.height(), maxWidgetHeight)); + } + } + break; + case QStyle::CT_PushButton: + // By default, we fit the contents inside a normal rounded push button. + // Do this by add enough space around the contents so that rounded + // borders (including highlighting when active) will show. + sz.rwidth() += QMacStylePrivate::PushButtonLeftOffset + QMacStylePrivate::PushButtonRightOffset + 12; + sz.rheight() += QMacStylePrivate::PushButtonTopOffset + QMacStylePrivate::PushButtonBottomOffset; + break; + case QStyle::CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + int maxpmw = mi->maxIconWidth; + const QComboBox *comboBox = qobject_cast<const QComboBox *>(widget); + int w = sz.width(), + h = sz.height(); + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + w = 10; + SInt16 ash; + GetThemeMenuSeparatorHeight(&ash); + h = ash; + } else { + h = mi->fontMetrics.height() + 2; + if (!mi->icon.isNull()) { + if (comboBox) { + const QSize &iconSize = comboBox->iconSize(); + h = qMax(h, iconSize.height() + 4); + maxpmw = qMax(maxpmw, iconSize.width()); + } else { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + h = qMax(h, mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + 4); + } + } + } + if (mi->text.contains(QLatin1Char('\t'))) + w += 12; + if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + w += 20; + if (maxpmw) + w += maxpmw + 6; + // add space for a check. All items have place for a check too. + w += 20; + if (comboBox && comboBox->isVisible()) { + QStyleOptionComboBox cmb; + cmb.initFrom(comboBox); + cmb.editable = false; + cmb.subControls = QStyle::SC_ComboBoxEditField; + cmb.activeSubControls = QStyle::SC_None; + w = qMax(w, subControlRect(QStyle::CC_ComboBox, &cmb, + QStyle::SC_ComboBoxEditField, + comboBox).width()); + } else { + w += 12; + } + sz = QSize(w, h); + } + break; + case CT_ToolButton: + if (widget && qobject_cast<const QToolBar *>(widget->parentWidget())) { + if (QMainWindow * mainWindow = qobject_cast<QMainWindow *>(widget->parent())) { + if (mainWindow->unifiedTitleAndToolBarOnMac()) { + sz.rwidth() += 4; + if (sz.height() <= 32) { + // Workaround strange HIToolBar bug when getting constraints. + sz.rheight() += 1; + } + return sz; + } + } + } + sz.rwidth() += 10; + sz.rheight() += 10; + return sz; + case CT_ComboBox: + sz.rwidth() += 50; + break; + case CT_Menu: { + QStyleHintReturnMask menuMask; + QStyleOption myOption = *opt; + myOption.rect.setSize(sz); + if (proxy()->styleHint(SH_Menu_Mask, &myOption, widget, &menuMask)) { + sz = menuMask.region.boundingRect().size(); + } + break; } + case CT_HeaderSection:{ + const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt); + sz = QWindowsStyle::sizeFromContents(ct, opt, csz, widget); + if (header->text.contains(QLatin1Char('\n'))) + useAquaGuideline = false; + break; } + case CT_ScrollBar : + // Make sure that the scroll bar is large enough to display the thumb indicator. + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + const int minimumSize = scrollButtonsCutoffSize(thumbIndicatorCutoff, widgetSizePolicy(widget)); + if (slider->orientation == Qt::Horizontal) + sz = sz.expandedTo(QSize(minimumSize, sz.height())); + else + sz = sz.expandedTo(QSize(sz.width(), minimumSize)); + } + break; + case CT_ItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + sz = QCommonStyle::sizeFromContents(ct, vopt, csz, widget); + sz.setHeight(sz.height() + 2); + } + break; + + default: + sz = QWindowsStyle::sizeFromContents(ct, opt, csz, widget); + } + + if (useAquaGuideline){ + QSize macsz; + if (d->aquaSizeConstrain(opt, widget, ct, sz, &macsz) != QAquaSizeUnknown) { + if (macsz.width() != -1) + sz.setWidth(macsz.width()); + if (macsz.height() != -1) + sz.setHeight(macsz.height()); + } + } + + // The sizes that Carbon and the guidelines gives us excludes the focus frame. + // We compensate for this by adding some extra space here to make room for the frame when drawing: + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)){ + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, widget); + int bkind = 0; + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = combo->editable ? kThemeComboBox : kThemePopupButton; + break; + case QAquaSizeSmall: + bkind = combo->editable ? int(kThemeComboBoxSmall) : int(kThemePopupButtonSmall); + break; + case QAquaSizeMini: + bkind = combo->editable ? kThemeComboBoxMini : kThemePopupButtonMini; + break; + } + HIRect tmpRect = {{0, 0}, {0, 0}}; + HIRect diffRect = QMacStylePrivate::comboboxInnerBounds(tmpRect, bkind); + sz.rwidth() -= qRound(diffRect.size.width); + sz.rheight() -= qRound(diffRect.size.height); + } else if (ct == CT_PushButton || ct == CT_ToolButton){ + ThemeButtonKind bkind; + QAquaWidgetSize widgetSize = d->aquaSizeConstrain(opt, widget); + switch (ct) { + default: + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (btn->features & QStyleOptionButton::CommandLinkButton) { + return QWindowsStyle::sizeFromContents(ct, opt, sz, widget); + } + } + + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = kThemePushButton; + break; + case QAquaSizeSmall: + bkind = kThemePushButtonSmall; + break; + case QAquaSizeMini: + bkind = kThemePushButtonMini; + break; + } + break; + case CT_ToolButton: + switch (widgetSize) { + default: + case QAquaSizeLarge: + bkind = kThemeLargeBevelButton; + break; + case QAquaSizeMini: + case QAquaSizeSmall: + bkind = kThemeSmallBevelButton; + } + break; + } + + HIThemeButtonDrawInfo bdi; + bdi.version = qt_mac_hitheme_version; + bdi.state = kThemeStateActive; + bdi.kind = bkind; + bdi.value = kThemeButtonOff; + bdi.adornment = kThemeAdornmentNone; + HIRect macRect, myRect; + myRect = CGRectMake(0, 0, sz.width(), sz.height()); + HIThemeGetButtonBackgroundBounds(&myRect, &bdi, &macRect); + // Mini buttons only return their actual size in HIThemeGetButtonBackgroundBounds, so help them out a bit (guess), + if (bkind == kThemePushButtonMini) + macRect.size.height += 8.; + else if (bkind == kThemePushButtonSmall) + macRect.size.height -= 10; + sz.setWidth(sz.width() + int(macRect.size.width - myRect.size.width)); + sz.setHeight(sz.height() + int(macRect.size.height - myRect.size.height)); + } + return sz; +} + +void QMacStyle::drawItemText(QPainter *p, const QRect &r, int flags, const QPalette &pal, + bool enabled, const QString &text, QPalette::ColorRole textRole) const +{ + if(flags & Qt::TextShowMnemonic) + flags |= Qt::TextHideMnemonic; + QWindowsStyle::drawItemText(p, r, flags, pal, enabled, text, textRole); +} + +bool QMacStyle::event(QEvent *e) +{ + if(e->type() == QEvent::FocusIn) { + QWidget *f = 0; + QWidget *focusWidget = QApplication::focusWidget(); +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) { + QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : 0; + if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) { + QGraphicsProxyWidget *proxy = static_cast<QGraphicsProxyWidget *>(focusItem); + if (proxy->widget()) + focusWidget = proxy->widget()->focusWidget(); + } + } +#endif + if (focusWidget && focusWidget->testAttribute(Qt::WA_MacShowFocusRect)) { + f = focusWidget; + QWidget *top = f->parentWidget(); + while (top && !top->isWindow() && !(top->windowType() == Qt::SubWindow)) + top = top->parentWidget(); +#ifndef QT_NO_MAINWINDOW + if (qobject_cast<QMainWindow *>(top)) { + QWidget *central = static_cast<QMainWindow *>(top)->centralWidget(); + for (const QWidget *par = f; par; par = par->parentWidget()) { + if (par == central) { + top = central; + break; + } + if (par->isWindow()) + break; + } + } +#endif + } + if (f) { + if(!d->focusWidget) + d->focusWidget = new QFocusFrame(f); + d->focusWidget->setWidget(f); + } else if(d->focusWidget) { + d->focusWidget->setWidget(0); + } + } else if(e->type() == QEvent::FocusOut) { + if(d->focusWidget) + d->focusWidget->setWidget(0); + } + return false; +} + +QIcon QMacStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt, + const QWidget *widget) const +{ + switch (standardIcon) { + default: + return QWindowsStyle::standardIconImplementation(standardIcon, opt, widget); + case SP_ToolBarHorizontalExtensionButton: + case SP_ToolBarVerticalExtensionButton: { + QPixmap pixmap(qt_mac_toolbar_ext); + if (standardIcon == SP_ToolBarVerticalExtensionButton) { + QPixmap pix2(pixmap.height(), pixmap.width()); + pix2.fill(Qt::transparent); + QPainter p(&pix2); + p.translate(pix2.width(), 0); + p.rotate(90); + p.drawPixmap(0, 0, pixmap); + return pix2; + } + return pixmap; + } + } +} + +int QMacStyle::layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option, + const QWidget *widget) const +{ + const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; + bool isMetal = (widget && widget->testAttribute(Qt::WA_MacBrushedMetal)); + int controlSize = getControlSize(option, widget); + + if (control2 == QSizePolicy::ButtonBox) { + /* + AHIG seems to prefer a 12-pixel margin between group + boxes and the row of buttons. The 20 pixel comes from + Builder. + */ + if (isMetal // (AHIG, guess, guess) + || (control1 & (QSizePolicy::Frame // guess + | QSizePolicy::GroupBox // (AHIG, guess, guess) + | QSizePolicy::TabWidget // guess + | ButtonMask))) { // AHIG + return_SIZE(14, 8, 8); + } else if (control1 == QSizePolicy::LineEdit) { + return_SIZE(8, 8, 8); // Interface Builder + } else { + return_SIZE(20, 7, 7); // Interface Builder + } + } + + if ((control1 | control2) & ButtonMask) { + if (control1 == QSizePolicy::LineEdit) + return_SIZE(8, 8, 8); // Interface Builder + else if (control2 == QSizePolicy::LineEdit) { + if (orientation == Qt::Vertical) + return_SIZE(20, 7, 7); // Interface Builder + else + return_SIZE(20, 8, 8); + } + return_SIZE(14, 8, 8); // Interface Builder + } + + switch (CT2(control1, control2)) { + case CT1(QSizePolicy::Label): // guess + case CT2(QSizePolicy::Label, QSizePolicy::DefaultType): // guess + case CT2(QSizePolicy::Label, QSizePolicy::CheckBox): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::ComboBox): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::LineEdit): // guess + case CT2(QSizePolicy::Label, QSizePolicy::RadioButton): // AHIG + case CT2(QSizePolicy::Label, QSizePolicy::Slider): // guess + case CT2(QSizePolicy::Label, QSizePolicy::SpinBox): // guess + case CT2(QSizePolicy::Label, QSizePolicy::ToolButton): // guess + return_SIZE(8, 6, 5); + case CT1(QSizePolicy::ToolButton): + return 8; // AHIG + case CT1(QSizePolicy::CheckBox): + case CT2(QSizePolicy::CheckBox, QSizePolicy::RadioButton): + case CT2(QSizePolicy::RadioButton, QSizePolicy::CheckBox): + if (orientation == Qt::Vertical) + return_SIZE(8, 8, 7); // AHIG and Builder + break; + case CT1(QSizePolicy::RadioButton): + if (orientation == Qt::Vertical) + return 5; // (Builder, guess, AHIG) + } + + if (orientation == Qt::Horizontal + && (control2 & (QSizePolicy::CheckBox | QSizePolicy::RadioButton))) + return_SIZE(12, 10, 8); // guess + + if ((control1 | control2) & (QSizePolicy::Frame + | QSizePolicy::GroupBox + | QSizePolicy::TabWidget)) { + /* + These values were chosen so that nested container widgets + look good side by side. Builder uses 8, which looks way + too small, and AHIG doesn't say anything. + */ + return_SIZE(16, 10, 10); // guess + } + + if ((control1 | control2) & (QSizePolicy::Line | QSizePolicy::Slider)) + return_SIZE(12, 10, 8); // AHIG + + if ((control1 | control2) & QSizePolicy::LineEdit) + return_SIZE(10, 8, 8); // AHIG + + /* + AHIG and Builder differ by up to 4 pixels for stacked editable + comboboxes. We use some values that work fairly well in all + cases. + */ + if ((control1 | control2) & QSizePolicy::ComboBox) + return_SIZE(10, 8, 7); // guess + + /* + Builder defaults to 8, 6, 5 in lots of cases, but most of the time the + result looks too cramped. + */ + return_SIZE(10, 8, 6); // guess +} + +QT_END_NAMESPACE diff --git a/src/gui/styles/qmacstyle_mac_p.h b/src/gui/styles/qmacstyle_mac_p.h new file mode 100644 index 0000000000..fbd6d57e09 --- /dev/null +++ b/src/gui/styles/qmacstyle_mac_p.h @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMACSTYLE_MAC_P_H +#define QMACSTYLE_MAC_P_H + +#include <qmacstyle_mac.h> +#include <private/qapplication_p.h> +#include <private/qcombobox_p.h> +#include <private/qmacstylepixmaps_mac_p.h> +#include <private/qpaintengine_mac_p.h> +#include <private/qpainter_p.h> +#include <private/qprintengine_mac_p.h> +#include <private/qstylehelper_p.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qdialogbuttonbox.h> +#include <qdockwidget.h> +#include <qevent.h> +#include <qfocusframe.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qhash.h> +#include <qheaderview.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistview.h> +#include <qmainwindow.h> +#include <qmap.h> +#include <qmenubar.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <qpixmapcache.h> +#include <qpointer.h> +#include <qprogressbar.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qrubberband.h> +#include <qsizegrip.h> +#include <qspinbox.h> +#include <qsplitter.h> +#include <qstyleoption.h> +#include <qtextedit.h> +#include <qtextstream.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qtreeview.h> +#include <qtableview.h> +#include <qwizard.h> +#include <qdebug.h> +#include <qlibrary.h> +#include <qdatetimeedit.h> +#include <qmath.h> +#include <QtGui/qgraphicsproxywidget.h> +#include <QtGui/qgraphicsview.h> +#include <private/qt_cocoa_helpers_mac_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 + +#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5) +enum { + kThemePushButtonTextured = 31, + kThemePushButtonTexturedSmall = 32, + kThemePushButtonTexturedMini = 33 +}; + +/* Search fields */ +enum { + kHIThemeFrameTextFieldRound = 1000, + kHIThemeFrameTextFieldRoundSmall = 1001, + kHIThemeFrameTextFieldRoundMini = 1002 +}; +#endif + +/* + AHIG: + Apple Human Interface Guidelines + http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/ + + Builder: + Apple Interface Builder v. 3.1.1 +*/ + +// this works as long as we have at most 16 different control types +#define CT1(c) CT2(c, c) +#define CT2(c1, c2) ((uint(c1) << 16) | uint(c2)) + +enum QAquaWidgetSize { QAquaSizeLarge = 0, QAquaSizeSmall = 1, QAquaSizeMini = 2, + QAquaSizeUnknown = -1 }; + +#define SIZE(large, small, mini) \ + (controlSize == QAquaSizeLarge ? (large) : controlSize == QAquaSizeSmall ? (small) : (mini)) + +// same as return SIZE(...) but optimized +#define return_SIZE(large, small, mini) \ + do { \ + static const int sizes[] = { (large), (small), (mini) }; \ + return sizes[controlSize]; \ + } while (0) + +bool qt_mac_buttonIsRenderedFlat(const QPushButton *pushButton, const QStyleOptionButton *option); + +class QMacStylePrivate : public QObject +{ + Q_OBJECT + +public: + QMacStylePrivate(QMacStyle *style); + + // Ideally these wouldn't exist, but since they already exist we need some accessors. + static const int PushButtonLeftOffset; + static const int PushButtonTopOffset; + static const int PushButtonRightOffset; + static const int PushButtonBottomOffset; + static const int MiniButtonH; + static const int SmallButtonH; + static const int BevelButtonW; + static const int BevelButtonH; + static const int PushButtonContentPadding; + + + // Stuff from QAquaAnimate: + bool addWidget(QWidget *); + void removeWidget(QWidget *); + + enum Animates { AquaPushButton, AquaProgressBar, AquaListViewItemOpen }; + bool animatable(Animates, const QWidget *) const; + void stopAnimate(Animates, QWidget *); + void startAnimate(Animates, QWidget *); + static ThemeDrawState getDrawState(QStyle::State flags); + QAquaWidgetSize aquaSizeConstrain(const QStyleOption *option, const QWidget *widg, + QStyle::ContentsType ct = QStyle::CT_CustomBase, + QSize szHint=QSize(-1, -1), QSize *insz = 0) const; + void getSliderInfo(QStyle::ComplexControl cc, const QStyleOptionSlider *slider, + HIThemeTrackDrawInfo *tdi, const QWidget *needToRemoveMe); + bool doAnimate(Animates); + inline int animateSpeed(Animates) const { return 33; } + + // Utility functions + void drawColorlessButton(const HIRect &macRect, HIThemeButtonDrawInfo *bdi, + QPainter *p, const QStyleOption *opt) const; + + QSize pushButtonSizeFromContents(const QStyleOptionButton *btn) const; + + HIRect pushButtonContentBounds(const QStyleOptionButton *btn, + const HIThemeButtonDrawInfo *bdi) const; + + void initComboboxBdi(const QStyleOptionComboBox *combo, HIThemeButtonDrawInfo *bdi, + const QWidget *widget, const ThemeDrawState &tds); + + static HIRect comboboxInnerBounds(const HIRect &outerBounds, int buttonKind); + + static QRect comboboxEditBounds(const QRect &outerBounds, const HIThemeButtonDrawInfo &bdi); + + static void drawCombobox(const HIRect &outerBounds, const HIThemeButtonDrawInfo &bdi, QPainter *p); + static void drawTableHeader(const HIRect &outerBounds, bool drawTopBorder, bool drawLeftBorder, + const HIThemeButtonDrawInfo &bdi, QPainter *p); + bool contentFitsInPushButton(const QStyleOptionButton *btn, HIThemeButtonDrawInfo *bdi, + ThemeButtonKind buttonKindToCheck) const; + void initHIThemePushButton(const QStyleOptionButton *btn, const QWidget *widget, + const ThemeDrawState tds, + HIThemeButtonDrawInfo *bdi) const; + QPixmap generateBackgroundPattern() const; +protected: + bool eventFilter(QObject *, QEvent *); + void timerEvent(QTimerEvent *); + +private slots: + void startAnimationTimer(); + +public: + QPointer<QPushButton> defaultButton; //default push buttons + int timerID; + QList<QPointer<QWidget> > progressBars; //existing progress bars that need animation + + struct ButtonState { + int frame; + enum { ButtonDark, ButtonLight } dir; + } buttonState; + UInt8 progressFrame; + QPointer<QFocusFrame> focusWidget; + CFAbsoluteTime defaultButtonStart; + QMacStyle *q; + bool mouseDown; +}; + +QT_END_NAMESPACE + +#endif // QMACSTYLE_MAC_P_H diff --git a/src/gui/styles/qmacstylepixmaps_mac_p.h b/src/gui/styles/qmacstylepixmaps_mac_p.h new file mode 100644 index 0000000000..41794387ea --- /dev/null +++ b/src/gui/styles/qmacstylepixmaps_mac_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMACSTYLEPIXMAPS_MAC_P_H +#define QMACSTYLEPIXMAPS_MAC_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. +// + +static const char * const qt_mac_toolbar_ext[]={ + "14 9 4 1", + "# c #858585", + "b c #d9d9d9", + ". c #dbdbdb", + "a c None", + ".###..###.aaaa", + "a.###..###.aaa", + "aab###bb###baa", + "aaab###bb###ba", + "aaaa.###..###.", + "aaa.###..###.a", + "aab###bb###baa", + "ab###bb###baaa", + ".###..###.aaaa"}; + +#endif // QMACSTYLEPIXMAPS_MAC_P_H diff --git a/src/gui/styles/qmotifstyle.cpp b/src/gui/styles/qmotifstyle.cpp new file mode 100644 index 0000000000..3bf8996650 --- /dev/null +++ b/src/gui/styles/qmotifstyle.cpp @@ -0,0 +1,2721 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmotifstyle.h" +#include "qcdestyle.h" + +#if !defined(QT_NO_STYLE_MOTIF) || defined(QT_PLUGIN) + +#include "qmenu.h" +#include "qapplication.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qpixmap.h" +#include "qpalette.h" +#include "qwidget.h" +#include "qpushbutton.h" +#include "qscrollbar.h" +#include "qtabbar.h" +#include "qtabwidget.h" +#include "qlistview.h" +#include "qsplitter.h" +#include "qslider.h" +#include "qcombobox.h" +#include "qlineedit.h" +#include "qprogressbar.h" +#include "qimage.h" +#include "qfocusframe.h" +#include "qdebug.h" +#include "qpainterpath.h" +#include "qmotifstyle_p.h" +#include "qdialogbuttonbox.h" +#include "qformlayout.h" +#include <limits.h> +#include <QtGui/qgraphicsproxywidget.h> +#include <QtGui/qgraphicsview.h> + +#ifdef Q_WS_X11 +#include "qx11info_x11.h" +#endif + +QT_BEGIN_NAMESPACE + +// old constants that might still be useful... +static const int motifItemFrame = 2; // menu item frame width +static const int motifSepHeight = 2; // separator item height +static const int motifItemHMargin = 3; // menu item hor text margin +static const int motifItemVMargin = 2; // menu item ver text margin +static const int motifArrowHMargin = 6; // arrow horizontal margin +static const int motifTabSpacing = 12; // space between text and tab +static const int motifCheckMarkHMargin = 2; // horiz. margins of check mark +static const int motifCheckMarkSpace = 16; + + +/*! + \class QMotifStyle + \brief The QMotifStyle class provides Motif look and feel. + + \ingroup appearance + + This class implements the Motif look and feel. It closely + resembles the original Motif look as defined by the Open Group, + but with some minor improvements. The Motif style is Qt's default + GUI style on Unix platforms. + + \img qmotifstyle.png + \sa QWindowsXPStyle, QMacStyle, QWindowsStyle, QPlastiqueStyle, QCDEStyle +*/ + +/*! + \variable QMotifStyle::focus + \internal +*/ + +/*! + Constructs a QMotifStyle. + + If \a useHighlightCols is false (the default), the style will + polish the application's color palette to emulate the Motif way of + highlighting, which is a simple inversion between the base and the + text color. +*/ +QMotifStyle::QMotifStyle(bool useHighlightCols) + : QCommonStyle(*new QMotifStylePrivate) +{ + focus = 0; + highlightCols = useHighlightCols; +} + + +/*! + \internal +*/ +QMotifStyle::QMotifStyle(QMotifStylePrivate &dd, bool useHighlightColors) + : QCommonStyle(dd) +{ + focus = 0; + highlightCols = useHighlightColors; +} + + +/*! + \overload + + Destroys the style. +*/ +QMotifStyle::~QMotifStyle() +{ + delete focus; +} + +/*! + \internal + Animate indeterminate progress bars only when visible +*/ +bool QMotifStyle::eventFilter(QObject *o, QEvent *e) +{ +#ifndef QT_NO_PROGRESSBAR + Q_D(QMotifStyle); + switch(e->type()) { + case QEvent::StyleChange: + case QEvent::Show: + if (QProgressBar *bar = qobject_cast<QProgressBar *>(o)) { + d->bars << bar; + if (d->bars.size() == 1) { + Q_ASSERT(d->animationFps> 0); + d->animateTimer = startTimer(1000 / d->animationFps); + } + } + break; + case QEvent::Destroy: + case QEvent::Hide: + // reinterpret_cast because there is no type info when getting + // the destroy event. We know that it is a QProgressBar. + if (QProgressBar *bar = reinterpret_cast<QProgressBar *>(o)) { + d->bars.removeAll(bar); + if (d->bars.isEmpty() && d->animateTimer) { + killTimer(d->animateTimer); + d->animateTimer = 0; + } + } + default: + break; + } +#endif // QT_NO_PROGRESSBAR + return QStyle::eventFilter(o, e); +} + +/*! + \internal +*/ +QIcon QMotifStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt, + const QWidget *widget) const +{ + return QCommonStyle::standardIconImplementation(standardIcon, opt, widget); +} + +/*! + \reimp +*/ +void QMotifStyle::timerEvent(QTimerEvent *event) +{ +#ifndef QT_NO_PROGRESSBAR + Q_D(QMotifStyle); + if (event->timerId() == d->animateTimer) { + Q_ASSERT(d->animationFps > 0); + d->animateStep = d->startTime.elapsed() / (1000 / d->animationFps); + foreach (QProgressBar *bar, d->bars) { + if ((bar->minimum() == 0 && bar->maximum() == 0)) + bar->update(); + } + } +#endif // QT_NO_PROGRESSBAR + event->ignore(); +} + + +QMotifStylePrivate::QMotifStylePrivate() +#ifndef QT_NO_PROGRESSBAR + : animationFps(25), animateTimer(0), animateStep(0) +#endif +{ +} + +/*! + If \a arg is false, the style will polish the application's color + palette to emulate the Motif way of highlighting, which is a + simple inversion between the base and the text color. + + The effect will show up the next time an application palette is + set via QApplication::setPalette(). The current color palette of + the application remains unchanged. + + \sa QStyle::polish() +*/ +void QMotifStyle::setUseHighlightColors(bool arg) +{ + highlightCols = arg; +} + +/*! + Returns true if the style treats the highlight colors of the + palette in a Motif-like manner, which is a simple inversion + between the base and the text color; otherwise returns false. The + default is false. +*/ +bool QMotifStyle::useHighlightColors() const +{ + return highlightCols; +} + +/*! \reimp */ + +void QMotifStyle::polish(QPalette& pal) +{ + if (pal.brush(QPalette::Active, QPalette::Light) == pal.brush(QPalette::Active, QPalette::Base)) { + QColor nlight = pal.color(QPalette::Active, QPalette::Light).darker(108); + pal.setColor(QPalette::Active, QPalette::Light, nlight) ; + pal.setColor(QPalette::Disabled, QPalette::Light, nlight) ; + pal.setColor(QPalette::Inactive, QPalette::Light, nlight) ; + } + + if (highlightCols) + return; + + // force the ugly motif way of highlighting *sigh* + pal.setColor(QPalette::Active, QPalette::Highlight, + pal.color(QPalette::Active, QPalette::Text)); + pal.setColor(QPalette::Active, QPalette::HighlightedText, + pal.color(QPalette::Active, QPalette::Base)); + pal.setColor(QPalette::Disabled, QPalette::Highlight, + pal.color(QPalette::Disabled, QPalette::Text)); + pal.setColor(QPalette::Disabled, QPalette::HighlightedText, + pal.color(QPalette::Disabled, QPalette::Base)); + pal.setColor(QPalette::Inactive, QPalette::Highlight, + pal.color(QPalette::Active, QPalette::Text)); + pal.setColor(QPalette::Inactive, QPalette::HighlightedText, + pal.color(QPalette::Active, QPalette::Base)); +} + +/*! + \reimp + \internal + Keep QStyle::polish() visible. +*/ +void QMotifStyle::polish(QWidget* widget) +{ + QStyle::polish(widget); +#ifndef QT_NO_PROGRESSBAR + if (qobject_cast<QProgressBar *>(widget)) + widget->installEventFilter(this); +#endif +} + +/*! + \reimp + \internal + Keep QStyle::polish() visible. +*/ +void QMotifStyle::unpolish(QWidget* widget) +{ + QCommonStyle::unpolish(widget); +#ifndef QT_NO_PROGRESSBAR + if (qobject_cast<QProgressBar *>(widget)) { + Q_D(QMotifStyle); + widget->removeEventFilter(this); + d->bars.removeAll(static_cast<QProgressBar*>(widget)); + } +#endif +} + + +/*! + \reimp + \internal + Keep QStyle::polish() visible. +*/ +void QMotifStyle::polish(QApplication* a) +{ + QCommonStyle::polish(a); +} + + +/*! + \reimp + \internal + Keep QStyle::polish() visible. +*/ +void QMotifStyle::unpolish(QApplication* a) +{ + QCommonStyle::unpolish(a); +} + +static void rot(QPolygon& a, int n) +{ + QPolygon r(a.size()); + for (int i = 0; i < (int)a.size(); i++) { + switch (n) { + case 1: r.setPoint(i,-a[i].y(),a[i].x()); break; + case 2: r.setPoint(i,-a[i].x(),-a[i].y()); break; + case 3: r.setPoint(i,a[i].y(),-a[i].x()); break; + } + } + a = r; +} + + +/*! + \reimp +*/ +void QMotifStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + switch(pe) { + case PE_Q3CheckListExclusiveIndicator: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + if (lv->items.isEmpty()) + return; + + if (lv->state & State_Enabled) + p->setPen(QPen(opt->palette.text().color())); + else + p->setPen(QPen(lv->palette.color(QPalette::Disabled, QPalette::Text))); + QPolygon a; + + int cx = opt->rect.width()/2 - 1; + int cy = opt->rect.height()/2; + int e = opt->rect.width()/2 - 1; + for (int i = 0; i < 3; i++) { //penWidth 2 doesn't quite work + a.setPoints(4, cx-e, cy, cx, cy-e, cx+e, cy, cx, cy+e); + p->drawPolygon(a); + e--; + } + if (opt->state & State_On) { + if (lv->state & State_Enabled) + p->setPen(QPen(opt->palette.text().color())); + else + p->setPen(QPen(lv->palette.color(QPalette::Disabled, + QPalette::Text))); + QBrush saveBrush = p->brush(); + p->setBrush(opt->palette.text()); + e = e - 2; + a.setPoints(4, cx-e, cy, cx, cy-e, cx+e, cy, cx, cy+e); + p->drawPolygon(a); + p->setBrush(saveBrush); + } + } + break; + + case PE_FrameTabWidget: + case PE_FrameWindow: + qDrawShadePanel(p, opt->rect, opt->palette, QStyle::State_None, proxy()->pixelMetric(PM_DefaultFrameWidth)); + break; + case PE_FrameFocusRect: + if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(opt)) { + if ((fropt->state & State_HasFocus) && focus && focus->isVisible() + && !(fropt->state & QStyle::State_Item)) + break; + QCommonStyle::drawPrimitive(pe, opt, p, w); + } + break; + + case PE_IndicatorToolBarHandle: { + p->save(); + p->translate(opt->rect.x(), opt->rect.y()); + + QColor dark(opt->palette.dark().color()); + QColor light(opt->palette.light().color()); + int i; + if (opt->state & State_Horizontal) { + int h = opt->rect.height(); + if (h > 6) { + if (opt->state & State_On) + p->fillRect(1, 1, 8, h - 2, opt->palette.highlight()); + QPolygon a(2 * ((h-6)/3)); + int y = 3 + (h%3)/2; + p->setPen(dark); + p->drawLine(8, 1, 8, h-2); + for (i=0; 2*i < a.size(); ++i) { + a.setPoint(2*i, 5, y+1+3*i); + a.setPoint(2*i+1, 2, y+2+3*i); + } + p->drawPoints(a); + p->setPen(light); + p->drawLine(9, 1, 9, h-2); + for (i=0; 2*i < a.size(); i++) { + a.setPoint(2*i, 4, y+3*i); + a.setPoint(2*i+1, 1, y+1+3*i); + } + p->drawPoints(a); + // if (drawBorder) { + // p->setPen(QPen(Qt::darkGray)); + // p->drawLine(0, opt->rect.height() - 1, + // tbExtent, opt->rect.height() - 1); + // } + } + } else { + int w = opt->rect.width(); + if (w > 6) { + if (opt->state & State_On) + p->fillRect(1, 1, w - 2, 9, opt->palette.highlight()); + QPolygon a(2 * ((w-6)/3)); + + int x = 3 + (w%3)/2; + p->setPen(dark); + p->drawLine(1, 8, w-2, 8); + for (i=0; 2*i < a.size(); ++i) { + a.setPoint(2*i, x+1+3*i, 6); + a.setPoint(2*i+1, x+2+3*i, 3); + } + p->drawPoints(a); + p->setPen(light); + p->drawLine(1, 9, w-2, 9); + for (i=0; 2*i < a.size(); ++i) { + a.setPoint(2*i, x+3*i, 5); + a.setPoint(2*i+1, x+1+3*i, 2); + } + p->drawPoints(a); + // if (drawBorder) { + // p->setPen(QPen(Qt::darkGray)); + // p->drawLine(opt->rect.width() - 1, 0, + // opt->rect.width() - 1, tbExtent); + // } + } + } + p->restore(); + break; } + + case PE_PanelButtonCommand: + case PE_PanelButtonBevel: + case PE_PanelButtonTool: { + QBrush fill; + if (opt->state & State_Sunken) + fill = opt->palette.brush(QPalette::Mid); + else if ((opt->state & State_On) && (opt->state & State_Enabled)) + fill = QBrush(opt->palette.mid().color(), Qt::Dense4Pattern); + else + fill = opt->palette.brush(QPalette::Button); + if ((opt->state & State_Enabled || opt->state & State_On) || !(opt->state & State_AutoRaise)) + qDrawShadePanel(p, opt->rect, opt->palette, bool(opt->state & (State_Sunken | State_On)), + proxy()->pixelMetric(PM_DefaultFrameWidth), &fill); + break; } + + case PE_IndicatorCheckBox: { + bool on = opt->state & State_On; + bool down = opt->state & State_Sunken; + bool showUp = !(down ^ on); + QBrush fill = opt->palette.brush((showUp || opt->state & State_NoChange) ?QPalette::Button : QPalette::Mid); + if (opt->state & State_NoChange) { + qDrawPlainRect(p, opt->rect, opt->palette.text().color(), + 1, &fill); + p->drawLine(opt->rect.x() + opt->rect.width() - 1, opt->rect.y(), + opt->rect.x(), opt->rect.y() + opt->rect.height() - 1); + } else { + qDrawShadePanel(p, opt->rect, opt->palette, !showUp, + proxy()->pixelMetric(PM_DefaultFrameWidth), &fill); + } + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + break; } + + case PE_IndicatorRadioButton: { +#define INTARRLEN(x) sizeof(x)/(sizeof(int)*2) + int inner_pts[] = { // used for filling diamond + 2,opt->rect.height()/2, + opt->rect.width()/2,2, + opt->rect.width()-3,opt->rect.height()/2, + opt->rect.width()/2,opt->rect.height()-3 + }; + int top_pts[] = { // top (^) of diamond + 0,opt->rect.height()/2, + opt->rect.width()/2,0, + opt->rect.width()-2,opt->rect.height()/2-1, + opt->rect.width()-3,opt->rect.height()/2-1, + opt->rect.width()/2,1, + 1,opt->rect.height()/2, + 2,opt->rect.height()/2, + opt->rect.width()/2,2, + opt->rect.width()-4,opt->rect.height()/2-1 + }; + int bottom_pts[] = { // bottom (v) of diamond + 1,opt->rect.height()/2+1, + opt->rect.width()/2,opt->rect.height()-1, + opt->rect.width()-1,opt->rect.height()/2, + opt->rect.width()-2,opt->rect.height()/2, + opt->rect.width()/2,opt->rect.height()-2, + 2,opt->rect.height()/2+1, + 3,opt->rect.height()/2+1, + opt->rect.width()/2,opt->rect.height()-3, + opt->rect.width()-3,opt->rect.height()/2 + }; + bool on = opt->state & State_On; + bool down = opt->state & State_Sunken; + bool showUp = !(down ^ on); + QPen oldPen = p->pen(); + QBrush oldBrush = p->brush(); + QPolygon a(INTARRLEN(inner_pts), inner_pts); + p->setPen(Qt::NoPen); + p->setBrush(opt->palette.brush(showUp ? QPalette::Button : QPalette::Mid)); + a.translate(opt->rect.x(), opt->rect.y()); + p->drawPolygon(a); + p->setPen(showUp ? opt->palette.light().color() : opt->palette.dark().color()); + p->setBrush(Qt::NoBrush); + a.setPoints(INTARRLEN(top_pts), top_pts); + a.translate(opt->rect.x(), opt->rect.y()); + p->drawPolyline(a); + p->setPen(showUp ? opt->palette.dark().color() : opt->palette.light().color()); + a.setPoints(INTARRLEN(bottom_pts), bottom_pts); + a.translate(opt->rect.x(), opt->rect.y()); + p->drawPolyline(a); + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + p->setPen(oldPen); + p->setBrush(oldBrush); + break; } + + case PE_IndicatorSpinUp: + case PE_IndicatorSpinPlus: + case PE_IndicatorSpinDown: + case PE_IndicatorSpinMinus: + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: { + QRect rect = opt->rect; + QPolygon bFill; + QPolygon bTop; + QPolygon bBot; + QPolygon bLeft; + if (pe == PE_IndicatorSpinPlus || pe == PE_IndicatorSpinUp) + pe = PE_IndicatorArrowUp; + else if (pe == PE_IndicatorSpinMinus || pe == PE_IndicatorSpinDown) + pe = PE_IndicatorArrowDown; + bool vertical = pe == PE_IndicatorArrowUp || pe == PE_IndicatorArrowDown; + bool horizontal = !vertical; + int dim = rect.width() < rect.height() ? rect.width() : rect.height(); + int colspec = 0x0000; + + if (!(opt->state & State_Enabled)) + dim -= 2; + if(dim < 2) + break; + + // adjust size and center (to fix rotation below) + if (rect.width() > dim) { + rect.setX(rect.x() + ((rect.width() - dim) / 2)); + rect.setWidth(dim); + } + if (rect.height() > dim) { + rect.setY(rect.y() + ((rect.height() - dim) / 2)); + rect.setHeight(dim); + } + + if (dim > 3) { + if (pixelMetric(PM_DefaultFrameWidth) < 2) { // thin style + bFill.resize( dim & 1 ? 3 : 4 ); + bTop.resize( 2 ); + bBot.resize( 2 ); + bLeft.resize( 2 ); + bLeft.putPoints( 0, 2, 0, 0, 0, dim-1 ); + bTop.putPoints( 0, 2, 1, 0, dim-1, dim/2 ); + bBot.putPoints( 0, 2, 1, dim-1, dim-1, dim/2 ); + + if ( dim > 6 ) { // dim>6: must fill interior + bFill.putPoints( 0, 2, 0, dim-1, 0, 0 ); + if ( dim & 1 ) // if size is an odd number + bFill.setPoint( 2, dim - 1, dim / 2 ); + else + bFill.putPoints( 2, 2, dim-1, dim/2-1, dim-1, dim/2 ); + } + } else { + if (dim > 6) + bFill.resize(dim & 1 ? 3 : 4); + bTop.resize((dim/2)*2); + bBot.resize(dim & 1 ? dim + 1 : dim); + bLeft.resize(dim > 4 ? 4 : 2); + bLeft.putPoints(0, 2, 0,0, 0,dim-1); + if (dim > 4) + bLeft.putPoints(2, 2, 1,2, 1,dim-3); + bTop.putPoints(0, 4, 1,0, 1,1, 2,1, 3,1); + bBot.putPoints(0, 4, 1,dim-1, 1,dim-2, 2,dim-2, 3,dim-2); + + for(int i=0; i<dim/2-2 ; i++) { + bTop.putPoints(i*2+4, 2, 2+i*2,2+i, 5+i*2, 2+i); + bBot.putPoints(i*2+4, 2, 2+i*2,dim-3-i, 5+i*2,dim-3-i); + } + if (dim & 1) // odd number size: extra line + bBot.putPoints(dim-1, 2, dim-3,dim/2, dim-1,dim/2); + if (dim > 6) { // dim>6: must fill interior + bFill.putPoints(0, 2, 1,dim-3, 1,2); + if (dim & 1) // if size is an odd number + bFill.setPoint(2, dim - 3, dim / 2); + else + bFill.putPoints(2, 2, dim-4,dim/2-1, dim-4,dim/2); + } + } + } else { + if (dim == 3) { // 3x3 arrow pattern + bLeft.setPoints(4, 0,0, 0,2, 1,1, 1,1); + bTop .setPoints(2, 1,0, 1,0); + bBot .setPoints(2, 1,2, 2,1); + } + else { // 2x2 arrow pattern + bLeft.setPoints(2, 0,0, 0,1); + bTop .setPoints(2, 1,0, 1,0); + bBot .setPoints(2, 1,1, 1,1); + } + } + + // We use rot() and translate() as it is more efficient that + // matrix transformations on the painter, and because it still + // works with QT_NO_TRANSFORMATIONS defined. + + if (pe == PE_IndicatorArrowUp || pe == PE_IndicatorArrowLeft) { + if (vertical) { + rot(bFill,3); + rot(bLeft,3); + rot(bTop,3); + rot(bBot,3); + bFill.translate(0, rect.height() - 1); + bLeft.translate(0, rect.height() - 1); + bTop.translate(0, rect.height() - 1); + bBot.translate(0, rect.height() - 1); + } else { + rot(bFill,2); + rot(bLeft,2); + rot(bTop,2); + rot(bBot,2); + bFill.translate(rect.width() - 1, rect.height() - 1); + bLeft.translate(rect.width() - 1, rect.height() - 1); + bTop.translate(rect.width() - 1, rect.height() - 1); + bBot.translate(rect.width() - 1, rect.height() - 1); + } + if (opt->state & State_Sunken) + colspec = horizontal ? 0x2334 : 0x2343; + else + colspec = horizontal ? 0x1443 : 0x1434; + } else { + if (vertical) { + rot(bFill,1); + rot(bLeft,1); + rot(bTop,1); + rot(bBot,1); + bFill.translate(rect.width() - 1, 0); + bLeft.translate(rect.width() - 1, 0); + bTop.translate(rect.width() - 1, 0); + bBot.translate(rect.width() - 1, 0); + } + if (opt->state & State_Sunken) + colspec = horizontal ? 0x2443 : 0x2434; + else + colspec = horizontal ? 0x1334 : 0x1343; + } + bFill.translate(rect.x(), rect.y()); + bLeft.translate(rect.x(), rect.y()); + bTop.translate(rect.x(), rect.y()); + bBot.translate(rect.x(), rect.y()); + + const QColor *cols[5]; + if (opt->state & State_Enabled) { + cols[0] = 0; + cols[1] = &opt->palette.button().color(); + cols[2] = &opt->palette.mid().color(); + cols[3] = &opt->palette.light().color(); + cols[4] = &opt->palette.dark().color(); + } else { + cols[0] = 0; + cols[1] = &opt->palette.mid().color(); + cols[2] = &opt->palette.mid().color(); + cols[3] = &opt->palette.mid().color(); + cols[4] = &opt->palette.mid().color(); + } + +#define CMID *cols[(colspec>>12) & 0xf] +#define CLEFT *cols[(colspec>>8) & 0xf] +#define CTOP *cols[(colspec>>4) & 0xf] +#define CBOT *cols[colspec & 0xf] + + QPen savePen = p->pen(); + QBrush saveBrush = p->brush(); + QPen pen(Qt::NoPen); + QBrush brush = opt->palette.brush((opt->state & State_Enabled) ? + QPalette::Button : QPalette::Mid); + p->setPen(pen); + p->setBrush(brush); + p->drawPolygon(bFill); + p->setBrush(Qt::NoBrush); + + p->setPen(CLEFT); + p->drawPolyline(bLeft); + p->setPen(CTOP); + p->drawPolyline(bTop); + p->setPen(CBOT); + p->drawPolyline(bBot); + + p->setBrush(saveBrush); + p->setPen(savePen); +#undef CMID +#undef CLEFT +#undef CTOP +#undef CBOT + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + break; } + + case PE_IndicatorDockWidgetResizeHandle: { + const int motifOffset = 10; + int sw = proxy()->pixelMetric(PM_SplitterWidth); + if (opt->state & State_Horizontal) { + int yPos = opt->rect.y() + opt->rect.height() / 2; + int kPos = opt->rect.right() - motifOffset - sw; + int kSize = sw - 2; + + qDrawShadeLine(p, opt->rect.left(), yPos, kPos, yPos, opt->palette); + qDrawShadePanel(p, kPos, yPos - sw / 2 + 1, kSize, kSize, + opt->palette, false, 1, &opt->palette.brush(QPalette::Button)); + qDrawShadeLine(p, kPos + kSize - 1, yPos, opt->rect.right(), yPos, opt->palette); + } else { + int xPos = opt->rect.x() + opt->rect.width() / 2; + int kPos = motifOffset; + int kSize = sw - 2; + + qDrawShadeLine(p, xPos, opt->rect.top() + kPos + kSize - 1, xPos, opt->rect.bottom(), opt->palette); + qDrawShadePanel(p, xPos - sw / 2 + 1, opt->rect.top() + kPos, kSize, kSize, opt->palette, + false, 1, &opt->palette.brush(QPalette::Button)); + qDrawShadeLine(p, xPos, opt->rect.top(), xPos, opt->rect.top() + kPos, opt->palette); + } + break; } + + case PE_IndicatorMenuCheckMark: { + const int markW = 6; + const int markH = 6; + int posX = opt->rect.x() + (opt->rect.width() - markW) / 2 - 1; + int posY = opt->rect.y() + (opt->rect.height() - markH) / 2; + int dfw = proxy()->pixelMetric(PM_DefaultFrameWidth); + + if (dfw < 2) { + // Could do with some optimizing/caching... + QPolygon a(7*2); + int i, xx, yy; + xx = posX; + yy = 3 + posY; + for (i=0; i<3; i++) { + a.setPoint(2*i, xx, yy); + a.setPoint(2*i+1, xx, yy+2); + xx++; yy++; + } + yy -= 2; + for (i=3; i<7; i++) { + a.setPoint(2*i, xx, yy); + a.setPoint(2*i+1, xx, yy+2); + xx++; yy--; + } + if (! (opt->state & State_Enabled) && ! (opt->state & State_On)) { + int pnt; + p->setPen(opt->palette.highlightedText().color()); + QPoint offset(1,1); + for (pnt = 0; pnt < (int)a.size(); pnt++) + a[pnt] += offset; + p->drawPolyline(a); + for (pnt = 0; pnt < (int)a.size(); pnt++) + a[pnt] -= offset; + } + p->setPen(opt->palette.text().color()); + p->drawPolyline(a); + + qDrawShadePanel(p, posX-2, posY-2, markW+4, markH+6, opt->palette, true, dfw); + } else + qDrawShadePanel(p, posX, posY, markW, markH, opt->palette, true, dfw, + &opt->palette.brush(QPalette::Mid)); + + break; } + + case PE_IndicatorProgressChunk: + { + bool vertical = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) + vertical = (pb2->orientation == Qt::Vertical); + if (!vertical) { + p->fillRect(opt->rect.x(), opt->rect.y(), opt->rect.width(), + opt->rect.height(), opt->palette.brush(QPalette::Highlight)); + } else { + p->fillRect(opt->rect.x(), opt->rect.y(), opt->rect.width(), opt->rect.height(), + opt->palette.brush(QPalette::Highlight)); + } + } + break; + + default: + QCommonStyle::drawPrimitive(pe, opt, p, w); + break; + } +} + + +/*! + \reimp +*/ +void QMotifStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *widget) const +{ + switch(element) { + case CE_Splitter: { + QStyleOption handleOpt = *opt; + if (handleOpt.state & State_Horizontal) + handleOpt.state &= ~State_Horizontal; + else + handleOpt.state |= State_Horizontal; + proxy()->drawPrimitive(PE_IndicatorDockWidgetResizeHandle, &handleOpt, p, widget); + break; } + + case CE_ScrollBarSubLine: + case CE_ScrollBarAddLine:{ + PrimitiveElement pe; + if (element == CE_ScrollBarAddLine) + pe = (opt->state & State_Horizontal) ? (opt->direction == Qt::LeftToRight ? PE_IndicatorArrowRight : PE_IndicatorArrowLeft) : PE_IndicatorArrowDown; + else + pe = (opt->state & State_Horizontal) ? (opt->direction == Qt::LeftToRight ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight) : PE_IndicatorArrowUp; + QStyleOption arrowOpt = *opt; + arrowOpt.state |= State_Enabled; + proxy()->drawPrimitive(pe, &arrowOpt, p, widget); + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) { + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth); + p->fillRect(opt->rect.adjusted(fw, fw, -fw, -fw), QBrush(p->background().color(), Qt::Dense5Pattern)); + } + }break; + + case CE_ScrollBarSubPage: + case CE_ScrollBarAddPage: + p->fillRect(opt->rect, opt->palette.brush((opt->state & State_Enabled) ? QPalette::Mid : QPalette::Window)); + break; + + case CE_ScrollBarSlider: { + QStyleOption bevelOpt = *opt; + bevelOpt.state |= State_Raised; + bevelOpt.state &= ~(State_Sunken | State_On); + p->save(); + p->setBrushOrigin(bevelOpt.rect.topLeft()); + proxy()->drawPrimitive(PE_PanelButtonBevel, &bevelOpt, p, widget); + p->restore(); + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(opt->rect, QBrush(p->background().color(), Qt::Dense5Pattern)); + break; } + + case CE_RadioButton: + case CE_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, btn, widget); + proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, + &subopt, p, widget); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, widget); + proxy()->drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, p, widget); + if ((btn->state & State_HasFocus) && (!focus || !focus->isVisible())) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect + : SE_CheckBoxFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; + case CE_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + proxy()->drawControl(CE_PushButtonBevel, btn, p, widget); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_PushButtonContents, btn, widget); + proxy()->drawControl(CE_PushButtonLabel, &subopt, p, widget); + if ((btn->state & State_HasFocus) && (!focus || !focus->isVisible())) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(SE_PushButtonFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + int diw, x1, y1, x2, y2; + p->setPen(opt->palette.foreground().color()); + p->setBrush(QBrush(opt->palette.button().color(), Qt::NoBrush)); + diw = proxy()->pixelMetric(PM_ButtonDefaultIndicator); + opt->rect.getCoords(&x1, &y1, &x2, &y2); + if (btn->features & (QStyleOptionButton::AutoDefaultButton|QStyleOptionButton::DefaultButton)) { + x1 += diw; + y1 += diw; + x2 -= diw; + y2 -= diw; + } + if (btn->features & QStyleOptionButton::DefaultButton) { + if (diw == 0) { + QPolygon a; + a.setPoints(9, + x1, y1, x2, y1, x2, y2, x1, y2, x1, y1+1, + x2-1, y1+1, x2-1, y2-1, x1+1, y2-1, x1+1, y1+1); + p->setPen(opt->palette.shadow().color()); + p->drawPolygon(a); + x1 += 2; + y1 += 2; + x2 -= 2; + y2 -= 2; + } else { + qDrawShadePanel(p, opt->rect.adjusted(1, 1, -1, -1), opt->palette, true); + } + } + if (!(btn->features & QStyleOptionButton::Flat) || + (btn->state & (State_Sunken | State_On))) { + QStyleOptionButton newOpt = *btn; + newOpt.rect = QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + p->setBrushOrigin(p->brushOrigin()); + proxy()->drawPrimitive(PE_PanelButtonCommand, &newOpt, p, widget); + } + if (btn->features & QStyleOptionButton::HasMenu) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, btn, widget); + QRect ir = btn->rect; + QStyleOptionButton newBtn = *btn; + newBtn.rect = QRect(ir.right() - mbi - 3, ir.y() + 4, mbi, ir.height() - 8); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + break; + } + +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + const int default_frame = proxy()->pixelMetric(PM_DefaultFrameWidth, tab, widget); + const int frame_offset = (default_frame > 1) ? 1 : 0; + + if (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedEast || + tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::RoundedWest) { + p->save(); + QRect tabRect = opt->rect; + QColor tabLight = opt->palette.light().color(); + QColor tabDark = opt->palette.dark().color(); + + p->fillRect(opt->rect.adjusted(default_frame, default_frame, + -default_frame, -default_frame), + tab->palette.background()); + + if(tab->shape == QTabBar::RoundedWest) { + tabDark = opt->palette.light().color(); + tabLight = opt->palette.dark().color(); + tabRect = QRect(0, 0, tabRect.height(), tabRect.width()); + p->translate(opt->rect.left(), opt->rect.bottom()); + p->rotate(-90); + } else if(tab->shape == QTabBar::RoundedSouth) { + tabDark = opt->palette.light().color(); + tabLight = opt->palette.dark().color(); + tabRect = QRect(0, 0, tabRect.width(), tabRect.height()); + p->translate(opt->rect.right(), opt->rect.bottom()); + p->rotate(180); + } else if(tab->shape == QTabBar::RoundedEast) { + tabRect = QRect(0, 0, tabRect.height(), tabRect.width()); + p->translate(opt->rect.right(), opt->rect.top()); + p->rotate(90); + } + + if (default_frame > 1) { + p->setPen(tabLight); + p->drawLine(tabRect.left(), tabRect.bottom(), + tabRect.right(), tabRect.bottom()); + p->setPen(tabLight); + p->drawLine(tabRect.left(), tabRect.bottom()-1, + tabRect.right(), tabRect.bottom()-1); + if (tabRect.left() == 0) + p->drawPoint(tabRect.bottomLeft()); + } else { + p->setPen(tabLight); + p->drawLine(tabRect.left(), tabRect.bottom(), + tabRect.right(), tabRect.bottom()); + } + + if (opt->state & State_Selected) { + p->fillRect(QRect(tabRect.left()+1, tabRect.bottom()-frame_offset, + tabRect.width()-3, 2), + tab->palette.brush(QPalette::Active, QPalette::Background)); + p->setPen(tab->palette.background().color()); + p->drawLine(tabRect.left()+1, tabRect.bottom(), + tabRect.left()+1, tabRect.top()+2); + p->setPen(tabLight); + } else { + p->setPen(tabLight); + } + p->drawLine(tabRect.left(), tabRect.bottom()-1, + tabRect.left(), tabRect.top() + 2); + p->drawPoint(tabRect.left()+1, tabRect.top() + 1); + p->drawLine(tabRect.left()+2, tabRect.top(), + tabRect.right() - 2, tabRect.top()); + p->drawPoint(tabRect.left(), tabRect.bottom()); + + if (default_frame > 1) { + p->drawLine(tabRect.left()+1, tabRect.bottom(), + tabRect.left()+1, tabRect.top() + 2); + p->drawLine(tabRect.left()+2, tabRect.top()+1, + tabRect.right() - 2, tabRect.top()+1); + } + + p->setPen(tabDark); + p->drawLine(tabRect.right() - 1, tabRect.top() + 2, + tabRect.right() - 1, tabRect.bottom() - 1 + + ((opt->state & State_Selected) ? frame_offset : -frame_offset)); + if (default_frame > 1) { + p->drawPoint(tabRect.right() - 1, tabRect.top() + 1); + p->drawLine(tabRect.right(), tabRect.top() + 2, tabRect.right(), + tabRect.bottom() - + ((opt->state & State_Selected) ? + ((tab->position == QStyleOptionTab::End) ? 0:1):1+frame_offset)); + p->drawPoint(tabRect.right() - 1, tabRect.top() + 1); + } + p->restore(); + } else { + QCommonStyle::drawControl(element, opt, p, widget); + } + break; } +#endif // QT_NO_TABBAR + case CE_ProgressBarGroove: + qDrawShadePanel(p, opt->rect, opt->palette, true, 2); + break; + + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + QTransform oldMatrix = p->transform(); + QRect rect = pb->rect; + bool vertical = false; + bool invert = false; + bool bottomToTop = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + vertical = (pb2->orientation == Qt::Vertical); + invert = pb2->invertedAppearance; + bottomToTop = pb2->bottomToTop; + } + if (vertical) { + QTransform m; + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + if (bottomToTop) { + m.translate(0.0, rect.width()); + m.rotate(-90); + } else { + m.translate(rect.height(), 0.0); + m.rotate(90); + } + p->setTransform(m, true); + } + const int unit_width = proxy()->pixelMetric(PM_ProgressBarChunkWidth, opt, widget); + int u = rect.width() / unit_width; + int p_v = pb->progress - pb->minimum; + int t_s = qMax(0, pb->maximum - pb->minimum); + if (u > 0 && pb->progress >= INT_MAX / u && t_s >= u) { + // scale down to something usable. + p_v /= u; + t_s /= u; + } + if (pb->textVisible && t_s) { + int nu = (u * p_v + t_s/2) / t_s; + int x = unit_width * nu; + QRect left(rect.x(), rect.y(), x, rect.height()); + QRect right(rect.x() + x, rect.y(), rect.width() - x, rect.height()); + Qt::LayoutDirection dir; + dir = vertical ? (bottomToTop ? Qt::LeftToRight : Qt::RightToLeft) : pb->direction; + if (invert) + dir = (dir == Qt::LeftToRight) ? Qt::RightToLeft : Qt::LeftToRight; + const QRect highlighted = visualRect(dir, rect, left); + const QRect background = visualRect(dir, rect, right); + p->setPen(opt->palette.highlightedText().color()); + p->setClipRect(highlighted); + p->drawText(rect, Qt::AlignCenter | Qt::TextSingleLine, pb->text); + + if (pb->progress != pb->maximum) { + p->setClipRect(background); + p->setPen(opt->palette.highlight().color()); + p->drawText(rect, Qt::AlignCenter | Qt::TextSingleLine, pb->text); + } + } + p->setTransform(oldMatrix, false); + break; + } + + case CE_MenuTearoff: { + if(opt->state & State_Selected) { + if(pixelMetric(PM_MenuPanelWidth, opt, widget) > 1) + qDrawShadePanel(p, opt->rect.x(), opt->rect.y(), opt->rect.width(), + opt->rect.height(), opt->palette, false, motifItemFrame, + &opt->palette.brush(QPalette::Button)); + else + qDrawShadePanel(p, opt->rect.x()+1, opt->rect.y()+1, opt->rect.width()-2, + opt->rect.height()-2, opt->palette, true, 1, &opt->palette.brush(QPalette::Button)); + } else { + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + } + p->setPen(QPen(opt->palette.dark().color(), 1, Qt::DashLine)); + p->drawLine(opt->rect.x()+2, opt->rect.y()+opt->rect.height()/2-1, opt->rect.x()+opt->rect.width()-4, + opt->rect.y()+opt->rect.height()/2-1); + p->setPen(QPen(opt->palette.light().color(), 1, Qt::DashLine)); + p->drawLine(opt->rect.x()+2, opt->rect.y()+opt->rect.height()/2, opt->rect.x()+opt->rect.width()-4, + opt->rect.y()+opt->rect.height()/2); + break; } + + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + int maxpmw = menuitem->maxIconWidth; + if(menuitem->menuHasCheckableItems) + maxpmw = qMax(maxpmw, motifCheckMarkSpace); + + int x, y, w, h; + opt->rect.getRect(&x, &y, &w, &h); + + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) { // draw separator + int textWidth = 0; + if (!menuitem->text.isEmpty()) { + QFont oldFont = p->font(); + p->setFont(menuitem->font); + p->fillRect(x, y, w, h, opt->palette.brush(QPalette::Button)); + proxy()->drawItemText(p, menuitem->rect.adjusted(10, 0, -5, 0), Qt::AlignLeft | Qt::AlignVCenter, + menuitem->palette, menuitem->state & State_Enabled, menuitem->text, + QPalette::Text); + textWidth = menuitem->fontMetrics.width(menuitem->text) + 10; + y += menuitem->fontMetrics.height() / 2; + p->setFont(oldFont); + } + p->setPen(opt->palette.dark().color()); + p->drawLine(x, y, x + 5, y); + p->drawLine(x + 5 + textWidth, y, x+w, y); + p->setPen(opt->palette.light().color()); + p->drawLine(x, y + 1, x + 5, y + 1); + p->drawLine(x + 5 + textWidth, y + 1, x+w, y + 1); + return; + } + + int pw = motifItemFrame; + if((opt->state & State_Selected) && (opt->state & State_Enabled)) { // active item frame + if(pixelMetric(PM_MenuPanelWidth, opt) > 1) + qDrawShadePanel(p, x, y, w, h, opt->palette, false, pw, + &opt->palette.brush(QPalette::Button)); + else + qDrawShadePanel(p, x+1, y+1, w-2, h-2, opt->palette, true, 1, + &opt->palette.brush(QPalette::Button)); + } else { // incognito frame + p->fillRect(x, y, w, h, opt->palette.brush(QPalette::Button)); + } + + QRect vrect = visualRect(opt->direction, opt->rect, + QRect(x+motifItemFrame, y+motifItemFrame, maxpmw, + h-2*motifItemFrame)); + int xvis = vrect.x(); + if (menuitem->checked) { + if(!menuitem->icon.isNull()) + qDrawShadePanel(p, xvis, y+motifItemFrame, maxpmw, h-2*motifItemFrame, + opt->palette, true, 1, &opt->palette.brush(QPalette::Midlight)); + } else if (!(opt->state & State_Selected)) { + p->fillRect(xvis, y+motifItemFrame, maxpmw, h-2*motifItemFrame, + opt->palette.brush(QPalette::Button)); + } + + if(!menuitem->icon.isNull()) { // draw icon + QIcon::Mode mode = QIcon::Normal; // no disabled icons in Motif + if ((opt->state & State_Selected) && !!(opt->state & State_Enabled)) + mode = QIcon::Active; + QPixmap pixmap; + if (menuitem->checkType != QStyleOptionMenuItem::NotCheckable && menuitem->checked) + pixmap = menuitem->icon.pixmap(pixelMetric(PM_SmallIconSize, opt, widget), mode, QIcon::On); + else + pixmap = menuitem->icon.pixmap(pixelMetric(PM_SmallIconSize, opt, widget), mode); + + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vrect.center()); + p->setPen(opt->palette.text().color()); + p->drawPixmap(pmr.topLeft(), pixmap); + + } else if (menuitem->checkType != QStyleOptionMenuItem::NotCheckable) { // just "checking"... + int mh = h - 2*motifItemFrame; + + QStyleOptionButton newMenuItem; + newMenuItem.state = menuitem->checked ? State_On : State_None; + if (opt->state & State_Enabled) { + newMenuItem.state |= State_Enabled; + if (menuitem->state & State_Sunken) + newMenuItem.state |= State_Sunken; + } + if (menuitem->checkType & QStyleOptionMenuItem::Exclusive) { + newMenuItem.rect.setRect(xvis + 2, y + motifItemFrame + mh / 4, 11, 11); + proxy()->drawPrimitive(PE_IndicatorRadioButton, &newMenuItem, p, widget); + } else { + newMenuItem.rect.setRect(xvis + 5, y + motifItemFrame + mh / 4, 9, 9); + proxy()->drawPrimitive(PE_IndicatorCheckBox, &newMenuItem, p, widget); + } + } + + p->setPen(opt->palette.buttonText().color()); + + QColor discol; + if (!(opt->state & State_Enabled)) { + discol = opt->palette.text().color(); + p->setPen(discol); + } + + int xm = motifItemFrame + maxpmw + motifItemHMargin; + + vrect = visualRect(opt->direction, opt->rect, + QRect(x+xm, y+motifItemVMargin, w-xm-menuitem->tabWidth, + h-2*motifItemVMargin)); + xvis = vrect.x(); + + QString s = menuitem->text; + if (!s.isNull()) { // draw text + int t = s.indexOf(QLatin1Char('\t')); + int m = motifItemVMargin; + int text_flags = Qt::AlignVCenter|Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + text_flags |= Qt::AlignLeft; + QFont oldFont = p->font(); + p->setFont(menuitem->font); + if (t >= 0) { // draw tab text + QRect vr = visualRect(opt->direction, opt->rect, + QRect(x+w-menuitem->tabWidth-motifItemHMargin-motifItemFrame, + y+motifItemVMargin, menuitem->tabWidth, + h-2*motifItemVMargin)); + int xv = vr.x(); + QRect tr(xv, y+m, menuitem->tabWidth, h-2*m); + p->drawText(tr, text_flags, s.mid(t+1)); + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(tr, QBrush(p->background().color(), Qt::Dense5Pattern)); + s = s.left(t); + } + QRect tr(xvis, y+m, w - xm - menuitem->tabWidth + 1, h-2*m); + p->drawText(tr, text_flags, s.left(t)); + p->setFont(oldFont); + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(tr, QBrush(p->background().color(), Qt::Dense5Pattern)); + } + if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) { // draw sub menu arrow + int dim = (h-2*motifItemFrame) / 2; + QStyle::PrimitiveElement arrow = (opt->direction == Qt::RightToLeft ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight); + QStyleOption arrowOpt = *opt; + arrowOpt.rect = visualRect(opt->direction, opt->rect, + QRect(x+w - motifArrowHMargin - motifItemFrame - dim, + y+h/2-dim/2, dim, dim)); + if ((opt->state & State_Selected)) + arrowOpt.state = (State_Sunken | ((opt->state & State_Enabled) ? State_Enabled : State_None)); + else + arrowOpt.state = ((opt->state & State_Enabled) ? State_Enabled : State_None); + proxy()->drawPrimitive(arrow, &arrowOpt, p, widget); + } + break; } + + case CE_MenuBarItem: + if (opt->state & State_Selected) // active item + qDrawShadePanel(p, opt->rect, opt->palette, false, motifItemFrame, + &opt->palette.brush(QPalette::Button)); + else // other item + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + QCommonStyle::drawControl(element, opt, p, widget); + break; + + case CE_HeaderSection: + p->save(); + p->setBrushOrigin(opt->rect.topLeft()); + qDrawShadePanel(p, opt->rect, opt->palette, bool(opt->state & (State_Sunken|State_On)), + proxy()->pixelMetric(PM_DefaultFrameWidth), + &opt->palette.brush((opt->state & State_Sunken) ? QPalette::Mid : QPalette::Button)); + p->restore(); + break; + case CE_RubberBand: { + QPixmap tiledPixmap(16, 16); + QPainter pixmapPainter(&tiledPixmap); + pixmapPainter.setPen(Qt::NoPen); + pixmapPainter.setBrush(Qt::Dense4Pattern); + pixmapPainter.setBackground(QBrush(opt->palette.base())); + pixmapPainter.setBackgroundMode(Qt::OpaqueMode); + pixmapPainter.drawRect(0, 0, tiledPixmap.width(), tiledPixmap.height()); + pixmapPainter.end(); + // ### workaround for borked XRENDER + tiledPixmap = QPixmap::fromImage(tiledPixmap.toImage()); + + p->save(); + QRect r = opt->rect; + QStyleHintReturnMask mask; + if (styleHint(QStyle::SH_RubberBand_Mask, opt, widget, &mask)) + p->setClipRegion(mask.region); + p->drawTiledPixmap(r.x(), r.y(), r.width(), r.height(), tiledPixmap); + p->restore(); + } + break; +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + QRect rect = pb->rect; + bool vertical = false; + bool inverted = false; + + // Get extra style options if version 2 + const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt); + if (pb2) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + + QTransform m; + if (vertical) { + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + m.rotate(90); + m.translate(0, -(rect.height() + rect.y()*2)); + } + + QPalette pal2 = pb->palette; + // Correct the highlight color if it is the same as the background + if (pal2.highlight() == pal2.background()) + pal2.setColor(QPalette::Highlight, pb->palette.color(QPalette::Active, + QPalette::Highlight)); + bool reverse = ((!vertical && (pb->direction == Qt::RightToLeft)) || vertical); + if (inverted) + reverse = !reverse; + int w = rect.width(); + if (pb->minimum == 0 && pb->maximum == 0) { + QRect progressBar; + Q_D(const QMotifStyle); + // draw busy indicator + int x = (d->animateStep*8)% (w * 2); + if (x > w) + x = 2 * w - x; + x = reverse ? rect.right() - x : x + rect.x(); + p->setTransform(m, true); + p->setPen(QPen(pal2.highlight().color(), 4)); + p->drawLine(x, rect.y(), x, rect.height()); + + } else + QCommonStyle::drawControl(element, opt, p, widget); + } + break; +#endif // QT_NO_PROGRESSBAR + default: + QCommonStyle::drawControl(element, opt, p, widget); + break; } +} + +static int get_combo_extra_width(int h, int w, int *return_awh=0) +{ + int awh, + tmp; + if (h < 8) { + awh = 6; + } else if (h < 14) { + awh = h - 2; + } else { + awh = h/2; + } + tmp = (awh * 3) / 2; + if (tmp > w / 2) { + awh = w / 2 - 3; + tmp = w / 2 + 3; + } + + if (return_awh) + *return_awh = awh; + + return tmp; +} + +static void get_combo_parameters(const QRect &r, + int &ew, int &awh, int &ax, + int &ay, int &sh, int &dh, + int &sy) +{ + ew = get_combo_extra_width(r.height(), r.width(), &awh); + + sh = (awh+3)/4; + if (sh < 3) + sh = 3; + dh = sh/2 + 1; + + ay = r.y() + (r.height()-awh-sh-dh)/2; + if (ay < 0) { + //panic mode + ay = 0; + sy = r.height(); + } else { + sy = ay+awh+dh; + } + ax = r.x() + r.width() - ew; + ax += (ew-awh)/2; +} + +/*! + \reimp +*/ +void QMotifStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *widget) const +{ + switch (cc) { + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + 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; + if (bflags & State_AutoRaise) { + if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) { + bflags &= ~State_Raised; + } + } + State mflags = bflags; + if (toolbutton->state & State_Sunken) { + if (toolbutton->activeSubControls & SC_ToolButton) + bflags |= State_Sunken; + mflags |= State_Sunken; + } + + QStyleOption tool(0); + tool.palette = toolbutton->palette; + if (toolbutton->subControls & SC_ToolButton) { + if (bflags & (State_Sunken | State_On | State_Raised)) { + tool.rect = button; + tool.state = bflags; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget); + } + } + + if ((toolbutton->state & State_HasFocus) && (!focus || !focus->isVisible())) { + QStyleOptionFocusRect fr; + fr.QStyleOption::operator=(*toolbutton); + fr.rect = toolbutton->rect.adjusted(3, 3, -3, -3); + proxy()->drawPrimitive(PE_FrameFocusRect, &fr, p, widget); + } + QStyleOptionToolButton label = *toolbutton; + label.state = bflags; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + 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 (mflags & (State_Sunken | State_On | State_Raised)) + proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget); + } 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() + 5 - mbi, ir.height() - mbi + 4, mbi - 6, mbi - 6); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + } + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox copy = *spinbox; + PrimitiveElement pe; + + if (spinbox->frame && (spinbox->subControls & SC_SpinBoxFrame)) { + QRect r = proxy()->subControlRect(CC_SpinBox, spinbox, SC_SpinBoxFrame, widget); + qDrawShadePanel(p, r, opt->palette, false, proxy()->pixelMetric(PM_SpinBoxFrameWidth)); + + int fw = proxy()->pixelMetric(QStyle::PM_DefaultFrameWidth); + r = proxy()->subControlRect(CC_SpinBox, spinbox, SC_SpinBoxEditField, widget).adjusted(-fw,-fw,fw,fw); + QStyleOptionFrame lineOpt; + lineOpt.QStyleOption::operator=(*opt); + lineOpt.rect = r; + lineOpt.lineWidth = fw; + lineOpt.midLineWidth = 0; + lineOpt.state |= QStyle::State_Sunken; + proxy()->drawPrimitive(QStyle::PE_FrameLineEdit, &lineOpt, p, widget); + } + + if (spinbox->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette pal2 = spinbox->palette; + if (!(spinbox->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + + copy.palette = pal2; + + if (spinbox->activeSubControls == SC_SpinBoxUp && (spinbox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (spinbox->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinPlus + : PE_IndicatorSpinUp); + + copy.rect = proxy()->subControlRect(CC_SpinBox, spinbox, SC_SpinBoxUp, widget); + proxy()->drawPrimitive(pe, ©, p, widget); + } + + if (spinbox->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = spinbox->state; + QPalette pal2 = spinbox->palette; + if (!(spinbox->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + + if (spinbox->activeSubControls == SC_SpinBoxDown && (spinbox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (spinbox->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinMinus + : PE_IndicatorSpinDown); + + copy.rect = proxy()->subControlRect(CC_SpinBox, spinbox, SC_SpinBoxDown, widget); + proxy()->drawPrimitive(pe, ©, p, widget); + } + } + break; +#endif // QT_NO_SPINBOX +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QRect groove = proxy()->subControlRect(CC_Slider, opt, SC_SliderGroove, widget), + handle = proxy()->subControlRect(CC_Slider, opt, SC_SliderHandle, widget); + + if ((opt->subControls & SC_SliderGroove) && groove.isValid()) { + qDrawShadePanel(p, groove, opt->palette, true, proxy()->pixelMetric(PM_DefaultFrameWidth), + &opt->palette.brush((opt->state & State_Enabled) ? QPalette::Mid : QPalette::Window)); + if ((opt->state & State_HasFocus) && (!focus || !focus->isVisible())) { + QStyleOption focusOpt = *opt; + focusOpt.rect = subElementRect(SE_SliderFocusRect, opt, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &focusOpt, p, widget); + } + } + + if ((opt->subControls & SC_SliderHandle) && handle.isValid()) { + QStyleOption bevelOpt = *opt; + bevelOpt.state = (opt->state | State_Raised) & ~State_Sunken; + bevelOpt.rect = handle; + p->save(); + p->setBrushOrigin(bevelOpt.rect.topLeft()); + proxy()->drawPrimitive(PE_PanelButtonBevel, &bevelOpt, p, widget); + p->restore(); + + if (slider->orientation == Qt::Horizontal) { + int mid = handle.x() + handle.width() / 2; + qDrawShadeLine(p, mid, handle.y(), mid, handle.y() + handle.height() - 2, + opt->palette, true, 1); + } else { + int mid = handle.y() + handle.height() / 2; + qDrawShadeLine(p, handle.x(), mid, handle.x() + handle.width() - 2, mid, opt->palette, + true, 1); + } + if (!(opt->state & State_Enabled) && proxy()->styleHint(SH_DitherDisabledText)) + p->fillRect(handle, QBrush(p->background().color(), Qt::Dense5Pattern)); + } + + if (slider->subControls & SC_SliderTickmarks) { + QStyleOptionSlider tmpSlider = *slider; + tmpSlider.subControls = SC_SliderTickmarks; + int frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth); + tmpSlider.rect.translate(frameWidth - 1, 0); + QCommonStyle::drawComplexControl(cc, &tmpSlider, p, widget); + } + } + break; +#endif // QT_NO_SLIDER + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + if (opt->subControls & SC_ComboBoxArrow) { + int awh, ax, ay, sh, sy, dh, ew; + int fw = cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, opt, widget) : 0; + + if (cb->frame) { + QStyleOptionButton btn; + btn.QStyleOption::operator=(*cb); + btn.state |= QStyle::State_Raised; + proxy()->drawPrimitive(PE_PanelButtonCommand, &btn, p, widget); + } else { + p->fillRect(opt->rect, opt->palette.brush(QPalette::Button)); + } + + QRect tr = opt->rect; + tr.adjust(fw, fw, -fw, -fw); + get_combo_parameters(tr, ew, awh, ax, ay, sh, dh, sy); + + QRect ar = QStyle::visualRect(opt->direction, opt->rect, QRect(ax,ay,awh,awh)); + + QStyleOption arrowOpt = *opt; + arrowOpt.rect = ar; + arrowOpt.state |= State_Enabled; + proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); + + + // draws the shaded line under the arrow + p->setPen(opt->palette.light().color()); + p->drawLine(ar.x(), sy, ar.x()+awh-1, sy); + p->drawLine(ar.x(), sy, ar.x(), sy+sh-1); + p->setPen(opt->palette.dark().color()); + p->drawLine(ar.x()+1, sy+sh-1, ar.x()+awh-1, sy+sh-1); + p->drawLine(ar.x()+awh-1, sy+1, ar.x()+awh-1, sy+sh-1); + + if ((cb->state & State_HasFocus) && (!focus || !focus->isVisible())) { + QStyleOptionFocusRect focus; + focus.QStyleOption::operator=(*opt); + focus.rect = subElementRect(SE_ComboBoxFocusRect, opt, widget); + focus.backgroundColor = opt->palette.button().color(); + proxy()->drawPrimitive(PE_FrameFocusRect, &focus, p, widget); + } + } + + if (opt->subControls & SC_ComboBoxEditField) { + if (cb->editable) { + QRect er = proxy()->subControlRect(CC_ComboBox, opt, SC_ComboBoxEditField, widget); + er.adjust(-1, -1, 1, 1); + qDrawShadePanel(p, er, opt->palette, true, 1, + &opt->palette.brush(QPalette::Base)); + } + } + p->setPen(opt->palette.buttonText().color()); + } + break; + +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: { + if (opt->subControls & SC_ScrollBarGroove) + qDrawShadePanel(p, opt->rect, opt->palette, true, + proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget), + &opt->palette.brush((opt->state & State_Enabled) ? QPalette::Mid : QPalette::Window)); + + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QStyleOptionSlider newScrollbar = *scrollbar; + if (scrollbar->minimum == scrollbar->maximum) + newScrollbar.state |= State_Enabled; // make sure that the slider is drawn. + QCommonStyle::drawComplexControl(cc, &newScrollbar, p, widget); + } + break; } +#endif + + case CC_Q3ListView: + if (opt->subControls & (SC_Q3ListViewBranch | SC_Q3ListViewExpand)) { + int i; + if (opt->subControls & SC_Q3ListView) + QCommonStyle::drawComplexControl(cc, opt, p, widget); + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + QStyleOptionQ3ListViewItem item = lv->items.at(0); + int y = opt->rect.y(); + int c; + QPolygon dotlines; + if ((opt->activeSubControls & SC_All) && (opt->subControls & SC_Q3ListViewExpand)) { + c = 2; + dotlines.resize(2); + dotlines[0] = QPoint(opt->rect.right(), opt->rect.top()); + dotlines[1] = QPoint(opt->rect.right(), opt->rect.bottom()); + } else { + int linetop = 0, linebot = 0; + // each branch needs at most two lines, ie. four end points + dotlines.resize(item.childCount * 4); + c = 0; + + // skip the stuff above the exposed rectangle + for (i = 1; i < lv->items.size(); ++i) { + QStyleOptionQ3ListViewItem child = lv->items.at(i); + if (child.height + y > 0) + break; + y += child.totalHeight; + } + + int bx = opt->rect.width() / 2; + + // paint stuff in the magical area + while (i < lv->items.size() && y < lv->rect.height()) { + QStyleOptionQ3ListViewItem child = lv->items.at(i); + if (child.features & QStyleOptionQ3ListViewItem::Visible) { + int lh; + if (!(item.features & QStyleOptionQ3ListViewItem::MultiLine)) + lh = child.height; + else + lh = p->fontMetrics().height() + 2 * lv->itemMargin; + lh = qMax(lh, QApplication::globalStrut().height()); + if (lh % 2 > 0) + lh++; + linebot = y + lh/2; + if ((child.features & QStyleOptionQ3ListViewItem::Expandable || child.childCount > 0) && + child.height > 0) { + // needs a box + p->setPen(opt->palette.text().color()); + p->drawRect(bx-4, linebot-4, 9, 9); + QPolygon a; + if ((child.state & State_Open)) + a.setPoints(3, bx-2, linebot-2, + bx, linebot+2, + bx+2, linebot-2); //Qt::RightArrow + else + a.setPoints(3, bx-2, linebot-2, + bx+2, linebot, + bx-2, linebot+2); //Qt::DownArrow + p->setBrush(opt->palette.text()); + p->drawPolygon(a); + p->setBrush(Qt::NoBrush); + // dotlinery + dotlines[c++] = QPoint(bx, linetop); + dotlines[c++] = QPoint(bx, linebot - 5); + dotlines[c++] = QPoint(bx + 5, linebot); + dotlines[c++] = QPoint(opt->rect.width(), linebot); + linetop = linebot + 5; + } else { + // just dotlinery + dotlines[c++] = QPoint(bx+1, linebot); + dotlines[c++] = QPoint(opt->rect.width(), linebot); + } + y += child.totalHeight; + } + ++i; + } + + // Expand line height to edge of rectangle if there's any + // visible child below + while (i < lv->items.size() && lv->items.at(i).height <= 0) + ++i; + if (i < lv->items.size()) + linebot = opt->rect.height(); + + if (linetop < linebot) { + dotlines[c++] = QPoint(bx, linetop); + dotlines[c++] = QPoint(bx, linebot); + } + } + + int line; // index into dotlines + p->setPen(opt->palette.text().color()); + if (opt->subControls & SC_Q3ListViewBranch) for(line = 0; line < c; line += 2) { + p->drawLine(dotlines[line].x(), dotlines[line].y(), + dotlines[line+1].x(), dotlines[line+1].y()); + } + } + break; } + + default: + QCommonStyle::drawComplexControl(cc, opt, p, widget); + break; + } +} + + +/*! \reimp */ +int QMotifStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, + const QWidget *widget) const +{ + int ret = 0; + + switch(pm) { + case PM_ButtonDefaultIndicator: + ret = 5; + break; + + case PM_CheckBoxLabelSpacing: + case PM_RadioButtonLabelSpacing: + ret = 10; + break; + + case PM_ToolBarFrameWidth: + ret = proxy()->pixelMetric(PM_DefaultFrameWidth); + break; + + case PM_ToolBarItemMargin: + ret = 1; + break; + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 0; + break; + + case PM_SplitterWidth: + ret = qMax(10, QApplication::globalStrut().width()); + break; + + case PM_SliderLength: + ret = 30; + break; + + case PM_SliderThickness: + ret = 16 + 4 * proxy()->pixelMetric(PM_DefaultFrameWidth); + break; +#ifndef QT_NO_SLIDER + case PM_SliderControlThickness: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); + int ticks = sl->tickPosition; + int n = 0; + if (ticks & QSlider::TicksAbove) + n++; + if (ticks & QSlider::TicksBelow) + n++; + if (!n) { + ret = space; + break; + } + + int thick = 6; // Magic constant to get 5 + 16 + 5 + + space -= thick; + //### the two sides may be unequal in size + if (space > 0) + thick += (space * 2) / (n + 2); + ret = thick; + } + break; + + case PM_SliderSpaceAvailable: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + if (sl->orientation == Qt::Horizontal) + ret = sl->rect.width() - proxy()->pixelMetric(PM_SliderLength, opt, widget) - 2 * proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + else + ret = sl->rect.height() - proxy()->pixelMetric(PM_SliderLength, opt, widget) - 2 * proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + } + break; +#endif // QT_NO_SLIDER + case PM_DockWidgetFrameWidth: + ret = 2; + break; + + case PM_DockWidgetHandleExtent: + ret = 9; + break; + + case PM_ProgressBarChunkWidth: + ret = 1; + break; + + case PM_ExclusiveIndicatorWidth: + case PM_ExclusiveIndicatorHeight: + ret = 13; + break; + + case PM_MenuBarHMargin: + ret = 2; // really ugly, but Motif + break; + + case PM_MenuButtonIndicator: + if (!opt) + ret = 12; + else + ret = qMax(12, (opt->rect.height() - 4) / 3); + break; + default: + ret = QCommonStyle::pixelMetric(pm, opt, widget); + break; + } + return ret; +} + + +/*! + \reimp +*/ +QRect +QMotifStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const +{ + switch (cc) { +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + int fw = spinbox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0; + QSize bs; + bs.setHeight(opt->rect.height()/2 - fw); + bs.setWidth(qMin(bs.height() * 8 / 5, opt->rect.width() / 4)); // 1.6 -approximate golden mean + bs = bs.expandedTo(QApplication::globalStrut()); + int y = fw + spinbox->rect.y(); + int x, lx, rx; + x = spinbox->rect.x() + opt->rect.width() - fw - bs.width(); + lx = fw; + rx = x - fw * 2; + const int margin = spinbox->frame ? 4 : 0; + switch (sc) { + case SC_SpinBoxUp: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + return visualRect(spinbox->direction, spinbox->rect, + QRect(x, y, bs.width(), bs.height() - 1)); + case SC_SpinBoxDown: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + return visualRect(spinbox->direction, spinbox->rect, + QRect(x, y + bs.height() + 1, bs.width(), bs.height() - 1)); + case SC_SpinBoxEditField: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return visualRect(spinbox->direction, spinbox->rect, + QRect(lx + margin, y + margin, + spinbox->rect.width() - 2*fw - 2*margin, + spinbox->rect.height() - 2*fw - 2*margin)); + + return visualRect(spinbox->direction, spinbox->rect, + QRect(lx + margin, y + margin, rx - margin, + spinbox->rect.height() - 2*fw - 2 * margin)); + case SC_SpinBoxFrame: + return visualRect(spinbox->direction, spinbox->rect, spinbox->rect); + default: + break; + } + break; } +#endif // QT_NO_SPINBOX +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + if (sc == SC_SliderHandle) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, opt, widget); + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, opt, widget); + bool horizontal = slider->orientation == Qt::Horizontal; + int len = proxy()->pixelMetric(PM_SliderLength, opt, widget); + int motifBorder = proxy()->pixelMetric(PM_DefaultFrameWidth); + int sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum, slider->sliderPosition, + horizontal ? slider->rect.width() - len - 2 * motifBorder + : slider->rect.height() - len - 2 * motifBorder, + slider->upsideDown); + if (horizontal) + return visualRect(slider->direction, slider->rect, + QRect(sliderPos + motifBorder, tickOffset + motifBorder, len, + thickness - 2 * motifBorder)); + return visualRect(slider->direction, slider->rect, + QRect(tickOffset + motifBorder, sliderPos + motifBorder, + thickness - 2 * motifBorder, len)); + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int dfw = proxy()->pixelMetric(PM_DefaultFrameWidth); + QRect rect = visualRect(scrollbar->direction, scrollbar->rect, + QCommonStyle::subControlRect(cc, scrollbar, sc, widget)); + if (sc == SC_ScrollBarSlider) { + if (scrollbar->orientation == Qt::Horizontal) + rect.adjust(-dfw, dfw, dfw, -dfw); + else + rect.adjust(dfw, -dfw, -dfw, dfw); + } else if (sc != SC_ScrollBarGroove) { + if (scrollbar->orientation == Qt::Horizontal) + rect.adjust(0, dfw, 0, -dfw); + else + rect.adjust(dfw, 0, -dfw, 0); + } + return visualRect(scrollbar->direction, scrollbar->rect, rect); + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + switch (sc) { + case SC_ComboBoxArrow: { + int ew, awh, sh, dh, ax, ay, sy; + int fw = cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, opt, widget) : 0; + QRect cr = opt->rect; + cr.adjust(fw, fw, -fw, -fw); + get_combo_parameters(cr, ew, awh, ax, ay, sh, dh, sy); + return visualRect(cb->direction, cb->rect, QRect(QPoint(ax, ay), cr.bottomRight())); + } + + case SC_ComboBoxEditField: { + int fw = cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, opt, widget) : 0; + QRect rect = opt->rect; + rect.adjust(fw, fw, -fw, -fw); + int ew = get_combo_extra_width(rect.height(), rect.width()); + rect.adjust(1, 1, -1-ew, -1); + return visualRect(cb->direction, cb->rect, rect); + } + + default: + break; + } + } + break; +#endif // QT_NO_SCROLLBAR + default: + break; + } + return QCommonStyle::subControlRect(cc, opt, sc, widget); +} + +/*! + \reimp +*/ +QSize +QMotifStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *widget) const +{ + QSize sz(contentsSize); + + switch(ct) { + case CT_RadioButton: + case CT_CheckBox: + sz = QCommonStyle::sizeFromContents(ct, opt, contentsSize, widget); + sz.rwidth() += motifItemFrame; + break; + + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + sz = QCommonStyle::sizeFromContents(ct, opt, contentsSize, widget); + if (!btn->text.isEmpty() && (btn->features & (QStyleOptionButton::AutoDefaultButton|QStyleOptionButton::DefaultButton))) + sz.setWidth(qMax(75, sz.width())); + sz += QSize(0, 1); // magical extra pixel + } + break; + + case CT_MenuBarItem: { + if(!sz.isEmpty()) + sz += QSize(5*motifItemHMargin+1, 2*motifItemVMargin + motifItemFrame); + break; } + + case CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + sz = QCommonStyle::sizeFromContents(ct, opt, sz, widget); + int w = sz.width(), h = sz.height(); + + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + w = 10; + h = (mi->text.isEmpty()) ? motifSepHeight : mi->fontMetrics.height(); + } + + // a little bit of border can never harm + w += 2*motifItemHMargin + 2*motifItemFrame; + + if (!mi->text.isNull() && mi->text.indexOf(QLatin1Char('\t')) >= 0) + // string contains tab + w += motifTabSpacing; + else if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + // submenu indicator needs some room if we don't have a tab column + w += motifArrowHMargin + 4*motifItemFrame; + + int checkColumn = mi->maxIconWidth; + if (mi->menuHasCheckableItems) + checkColumn = qMax(checkColumn, motifCheckMarkSpace); + if (checkColumn > 0) + w += checkColumn + motifCheckMarkHMargin; + + sz = QSize(w, h); + } + break; + + + default: + sz = QCommonStyle::sizeFromContents(ct, opt, contentsSize, widget); + break; + } + + return sz; +} + +/*! + \reimp +*/ +QRect +QMotifStyle::subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *widget) const +{ + QRect rect; + + switch (sr) { + case SE_SliderFocusRect: + rect = QCommonStyle::subElementRect(sr, opt, widget); + rect.adjust(2, 2, -2, -2); + break; + + case SE_CheckBoxIndicator: + case SE_RadioButtonIndicator: + { + rect = visualRect(opt->direction, opt->rect, + QCommonStyle::subElementRect(sr, opt, widget)); + rect.adjust(motifItemFrame,0, motifItemFrame,0); + rect = visualRect(opt->direction, opt->rect, rect); + } + break; + + case SE_ComboBoxFocusRect: + { + int awh, ax, ay, sh, sy, dh, ew; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, opt, widget); + QRect tr = opt->rect; + + tr.adjust(fw, fw, -fw, -fw); + get_combo_parameters(tr, ew, awh, ax, ay, sh, dh, sy); + rect.setRect(ax-2, ay-2, awh+4, awh+sh+dh+4); + break; + } + + case SE_Q3DockWindowHandleRect: + if (const QStyleOptionQ3DockWindow *dw = qstyleoption_cast<const QStyleOptionQ3DockWindow *>(opt)) { + if (!dw->docked || !dw->closeEnabled) + rect.setRect(0, 0, opt->rect.width(), opt->rect.height()); + else { + if (dw->state == State_Horizontal) + rect.setRect(2, 15, opt->rect.width()-2, opt->rect.height() - 15); + else + rect.setRect(0, 2, opt->rect.width() - 15, opt->rect.height() - 2); + } + rect = visualRect(dw->direction, dw->rect, rect); + } + break; + + case SE_ProgressBarLabel: + case SE_ProgressBarGroove: + case SE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + int textw = 0; + if (pb->textVisible) + textw = pb->fontMetrics.width(QLatin1String("100%")) + 6; + + if (pb->textAlignment == Qt::AlignLeft || pb->textAlignment == Qt::AlignCenter) { + rect = opt->rect; + } else { + if(sr == SE_ProgressBarLabel) + rect.setCoords(opt->rect.right() - textw, opt->rect.top(), + opt->rect.right(), opt->rect.bottom()); + else + rect.setCoords(opt->rect.left(), opt->rect.top(), + opt->rect.right() - textw, opt->rect.bottom()); + } + if (sr == SE_ProgressBarContents) + rect.adjust(2, 2, -2, -2); + rect = visualRect(pb->direction, pb->rect, rect); + } + break; + case SE_CheckBoxClickRect: + case SE_RadioButtonClickRect: + rect = visualRect(opt->direction, opt->rect, opt->rect); + break; + + default: + rect = QCommonStyle::subElementRect(sr, opt, widget); + } + return rect; +} + +#ifndef QT_NO_IMAGEFORMAT_XPM +static const char * const qt_menu_xpm[] = { +"16 16 11 1", +" c #000000", +", c #336600", +". c #99CC00", +"X c #666600", +"o c #999933", +"+ c #333300", +"@ c #669900", +"# c #999900", +"$ c #336633", +"% c #666633", +"& c #99CC33", +"................", +"................", +".....#,++X#.....", +"....X X....", +"...X Xo#% X&..", +"..# o..&@o o..", +".., X..#+ @X X..", +"..+ o.o+ +o# +..", +"..+ #o+ +## +..", +".., %@ ++ +, X..", +"..# o@oo+ #..", +"...X X##$ o..", +"....X X..", +"....&oX++X#oX...", +"................", +"................"}; + + +static const char * const qt_close_xpm[] = { + "12 12 2 1", + " s None c None", + ". c black", + " ", + " ", + " . . ", + " ... ... ", + " ...... ", + " .... ", + " .... ", + " ...... ", + " ... ... ", + " . . ", + " ", + " "}; + +static const char * const qt_maximize_xpm[] = { + "12 12 2 1", + " s None c None", + ". c black", + " ", + " ", + " ", + " . ", + " ... ", + " ..... ", + " ....... ", + " ......... ", + " ", + " ", + " ", + " "}; + +static const char * const qt_minimize_xpm[] = { + "12 12 2 1", + " s None c None", + ". c black", + " ", + " ", + " ", + " ", + " ......... ", + " ....... ", + " ..... ", + " ... ", + " . ", + " ", + " ", + " "}; + +#if 0 // ### not used??? +static const char * const qt_normalize_xpm[] = { + "12 12 2 1", + " s None c None", + ". c black", + " ", + " ", + " . ", + " .. ", + " ... ", + " .... ", + " ..... ", + " ...... ", + " ....... ", + " ", + " ", + " "}; +#endif + +static const char * const qt_normalizeup_xpm[] = { + "12 12 2 1", + " s None c None", + ". c black", + " ", + " ", + " ", + " ....... ", + " ...... ", + " ..... ", + " .... ", + " ... ", + " .. ", + " . ", + " ", + " "}; + +static const char * const qt_shade_xpm[] = { + "12 12 2 1", "# c #000000", + ". c None", + "............", + "............", + ".#########..", + ".#########..", + "............", + "............", + "............", + "............", + "............", + "............", + "............", + "............"}; + + +static const char * const qt_unshade_xpm[] = { + "12 12 2 1", + "# c #000000", + ". c None", + "............", + "............", + ".#########..", + ".#########..", + ".#.......#..", + ".#.......#..", + ".#.......#..", + ".#.......#..", + ".#.......#..", + ".#########..", + "............", + "............"}; + + +static const char * dock_window_close_xpm[] = { + "8 8 2 1", + "# c #000000", + ". c None", + "##....##", + ".##..##.", + "..####..", + "...##...", + "..####..", + ".##..##.", + "##....##", + "........"}; + +// Message box icons, from page 210 of the Windows style guide. + +// Hand-drawn to resemble Microsoft's icons, but in the Mac/Netscape palette. +// Thanks to TrueColor displays, it is slightly more efficient to have +// them duplicated. +/* XPM */ +static const char * const information_xpm[]={ + "32 32 5 1", + ". c None", + "c c #000000", + "* c #999999", + "a c #ffffff", + "b c #0000ff", + "...........********.............", + "........***aaaaaaaa***..........", + "......**aaaaaaaaaaaaaa**........", + ".....*aaaaaaaaaaaaaaaaaa*.......", + "....*aaaaaaaabbbbaaaaaaaac......", + "...*aaaaaaaabbbbbbaaaaaaaac.....", + "..*aaaaaaaaabbbbbbaaaaaaaaac....", + ".*aaaaaaaaaaabbbbaaaaaaaaaaac...", + ".*aaaaaaaaaaaaaaaaaaaaaaaaaac*..", + "*aaaaaaaaaaaaaaaaaaaaaaaaaaaac*.", + "*aaaaaaaaaabbbbbbbaaaaaaaaaaac*.", + "*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", + "*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", + "*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", + "*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", + "*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", + ".*aaaaaaaaaaabbbbbaaaaaaaaaac***", + ".*aaaaaaaaaaabbbbbaaaaaaaaaac***", + "..*aaaaaaaaaabbbbbaaaaaaaaac***.", + "...caaaaaaabbbbbbbbbaaaaaac****.", + "....caaaaaaaaaaaaaaaaaaaac****..", + ".....caaaaaaaaaaaaaaaaaac****...", + "......ccaaaaaaaaaaaaaacc****....", + ".......*cccaaaaaaaaccc*****.....", + "........***cccaaaac*******......", + "..........****caaac*****........", + ".............*caaac**...........", + "...............caac**...........", + "................cac**...........", + ".................cc**...........", + "..................***...........", + "...................**..........."}; +/* XPM */ +static const char* const warning_xpm[]={ + "32 32 4 1", + ". c None", + "a c #ffff00", + "* c #000000", + "b c #999999", + ".............***................", + "............*aaa*...............", + "...........*aaaaa*b.............", + "...........*aaaaa*bb............", + "..........*aaaaaaa*bb...........", + "..........*aaaaaaa*bb...........", + ".........*aaaaaaaaa*bb..........", + ".........*aaaaaaaaa*bb..........", + "........*aaaaaaaaaaa*bb.........", + "........*aaaa***aaaa*bb.........", + ".......*aaaa*****aaaa*bb........", + ".......*aaaa*****aaaa*bb........", + "......*aaaaa*****aaaaa*bb.......", + "......*aaaaa*****aaaaa*bb.......", + ".....*aaaaaa*****aaaaaa*bb......", + ".....*aaaaaa*****aaaaaa*bb......", + "....*aaaaaaaa***aaaaaaaa*bb.....", + "....*aaaaaaaa***aaaaaaaa*bb.....", + "...*aaaaaaaaa***aaaaaaaaa*bb....", + "...*aaaaaaaaaa*aaaaaaaaaa*bb....", + "..*aaaaaaaaaaa*aaaaaaaaaaa*bb...", + "..*aaaaaaaaaaaaaaaaaaaaaaa*bb...", + ".*aaaaaaaaaaaa**aaaaaaaaaaa*bb..", + ".*aaaaaaaaaaa****aaaaaaaaaa*bb..", + "*aaaaaaaaaaaa****aaaaaaaaaaa*bb.", + "*aaaaaaaaaaaaa**aaaaaaaaaaaa*bb.", + "*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", + "*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", + ".*aaaaaaaaaaaaaaaaaaaaaaaaa*bbbb", + "..*************************bbbbb", + "....bbbbbbbbbbbbbbbbbbbbbbbbbbb.", + ".....bbbbbbbbbbbbbbbbbbbbbbbbb.."}; +/* XPM */ +static const char* const critical_xpm[]={ + "32 32 4 1", + ". c None", + "a c #999999", + "* c #ff0000", + "b c #ffffff", + "...........********.............", + ".........************...........", + ".......****************.........", + "......******************........", + ".....********************a......", + "....**********************a.....", + "...************************a....", + "..*******b**********b*******a...", + "..******bbb********bbb******a...", + ".******bbbbb******bbbbb******a..", + ".*******bbbbb****bbbbb*******a..", + "*********bbbbb**bbbbb*********a.", + "**********bbbbbbbbbb**********a.", + "***********bbbbbbbb***********aa", + "************bbbbbb************aa", + "************bbbbbb************aa", + "***********bbbbbbbb***********aa", + "**********bbbbbbbbbb**********aa", + "*********bbbbb**bbbbb*********aa", + ".*******bbbbb****bbbbb*******aa.", + ".******bbbbb******bbbbb******aa.", + "..******bbb********bbb******aaa.", + "..*******b**********b*******aa..", + "...************************aaa..", + "....**********************aaa...", + "....a********************aaa....", + ".....a******************aaa.....", + "......a****************aaa......", + ".......aa************aaaa.......", + ".........aa********aaaaa........", + "...........aaaaaaaaaaa..........", + ".............aaaaaaa............"}; +/* XPM */ +static const char *const question_xpm[] = { + "32 32 5 1", + ". c None", + "c c #000000", + "* c #999999", + "a c #ffffff", + "b c #0000ff", + "...........********.............", + "........***aaaaaaaa***..........", + "......**aaaaaaaaaaaaaa**........", + ".....*aaaaaaaaaaaaaaaaaa*.......", + "....*aaaaaaaaaaaaaaaaaaaac......", + "...*aaaaaaaabbbbbbaaaaaaaac.....", + "..*aaaaaaaabaaabbbbaaaaaaaac....", + ".*aaaaaaaabbaaaabbbbaaaaaaaac...", + ".*aaaaaaaabbbbaabbbbaaaaaaaac*..", + "*aaaaaaaaabbbbaabbbbaaaaaaaaac*.", + "*aaaaaaaaaabbaabbbbaaaaaaaaaac*.", + "*aaaaaaaaaaaaabbbbaaaaaaaaaaac**", + "*aaaaaaaaaaaaabbbaaaaaaaaaaaac**", + "*aaaaaaaaaaaaabbaaaaaaaaaaaaac**", + "*aaaaaaaaaaaaabbaaaaaaaaaaaaac**", + "*aaaaaaaaaaaaaaaaaaaaaaaaaaaac**", + ".*aaaaaaaaaaaabbaaaaaaaaaaaac***", + ".*aaaaaaaaaaabbbbaaaaaaaaaaac***", + "..*aaaaaaaaaabbbbaaaaaaaaaac***.", + "...caaaaaaaaaabbaaaaaaaaaac****.", + "....caaaaaaaaaaaaaaaaaaaac****..", + ".....caaaaaaaaaaaaaaaaaac****...", + "......ccaaaaaaaaaaaaaacc****....", + ".......*cccaaaaaaaaccc*****.....", + "........***cccaaaac*******......", + "..........****caaac*****........", + ".............*caaac**...........", + "...............caac**...........", + "................cac**...........", + ".................cc**...........", + "..................***...........", + "...................**...........", +}; +#endif + +/*! + \reimp +*/ +QPixmap +QMotifStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ +#ifndef QT_NO_IMAGEFORMAT_XPM + switch (standardPixmap) { + case SP_TitleBarMenuButton: + return QPixmap(qt_menu_xpm); + case SP_TitleBarShadeButton: + return QPixmap(qt_shade_xpm); + case SP_TitleBarUnshadeButton: + return QPixmap(qt_unshade_xpm); + case SP_TitleBarNormalButton: + return QPixmap(qt_normalizeup_xpm); + case SP_TitleBarMinButton: + return QPixmap(qt_minimize_xpm); + case SP_TitleBarMaxButton: + return QPixmap(qt_maximize_xpm); + case SP_TitleBarCloseButton: + return QPixmap(qt_close_xpm); + case SP_DockWidgetCloseButton: + return QPixmap(dock_window_close_xpm); + + case SP_MessageBoxInformation: + case SP_MessageBoxWarning: + case SP_MessageBoxCritical: + case SP_MessageBoxQuestion: + { + const char * const * xpm_data; + switch (standardPixmap) { + case SP_MessageBoxInformation: + xpm_data = information_xpm; + break; + case SP_MessageBoxWarning: + xpm_data = warning_xpm; + break; + case SP_MessageBoxCritical: + xpm_data = critical_xpm; + break; + case SP_MessageBoxQuestion: + xpm_data = question_xpm; + break; + default: + xpm_data = 0; + break; + } + QPixmap pm; + if (xpm_data) { + QImage image((const char **) xpm_data); + // All that color looks ugly in Motif + const QPalette &pal = QApplication::palette(); + switch (standardPixmap) { + case SP_MessageBoxInformation: + case SP_MessageBoxQuestion: + image.setColor(2, 0xff000000 | + pal.color(QPalette::Active, QPalette::Dark).rgb()); + image.setColor(3, 0xff000000 | + pal.color(QPalette::Active, QPalette::Base).rgb()); + image.setColor(4, 0xff000000 | + pal.color(QPalette::Active, QPalette::Text).rgb()); + break; + case SP_MessageBoxWarning: + image.setColor(1, 0xff000000 | + pal.color(QPalette::Active, QPalette::Base).rgb()); + image.setColor(2, 0xff000000 | + pal.color(QPalette::Active, QPalette::Text).rgb()); + image.setColor(3, 0xff000000 | + pal.color(QPalette::Active, QPalette::Dark).rgb()); + break; + case SP_MessageBoxCritical: + image.setColor(1, 0xff000000 | + pal.color(QPalette::Active, QPalette::Dark).rgb()); + image.setColor(2, 0xff000000 | + pal.color(QPalette::Active, QPalette::Text).rgb()); + image.setColor(3, 0xff000000 | + pal.color(QPalette::Active, QPalette::Base).rgb()); + break; + default: + break; + } + pm = QPixmap::fromImage(image); + } + return pm; + } + + default: + break; + } +#endif + + return QCommonStyle::standardPixmap(standardPixmap, opt, widget); +} + +/*! \reimp */ +bool QMotifStyle::event(QEvent *e) +{ + if(e->type() == QEvent::FocusIn) { + if (QWidget *focusWidget = QApplication::focusWidget()) { +#ifndef QT_NO_GRAPHICSVIEW + if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(focusWidget)) { + QGraphicsItem *focusItem = graphicsView->scene() ? graphicsView->scene()->focusItem() : 0; + if (focusItem && focusItem->type() == QGraphicsProxyWidget::Type) { + QGraphicsProxyWidget *proxy = static_cast<QGraphicsProxyWidget *>(focusItem); + if (proxy->widget()) + focusWidget = proxy->widget()->focusWidget(); + } + } +#endif + if(!focus) + focus = new QFocusFrame(focusWidget); + focus->setWidget(focusWidget); + } else { + if(focus) + focus->setWidget(0); + } + } else if(e->type() == QEvent::FocusOut) { + if(focus) + focus->setWidget(0); + } + return QCommonStyle::event(e); +} + + +/*! \reimp */ +int +QMotifStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + int ret; + + switch (hint) { +#ifdef QT3_SUPPORT + case SH_GUIStyle: + ret = Qt::MotifStyle; + break; +#endif + case SH_DrawMenuBarSeparator: + ret = true; + break; + + case SH_ScrollBar_MiddleClickAbsolutePosition: + case SH_Slider_SloppyKeyEvents: + case SH_ProgressDialog_CenterCancelButton: + case SH_Menu_SpaceActivatesItem: + case SH_ScrollView_FrameOnlyAroundContents: + case SH_DitherDisabledText: + ret = 1; + break; + + case SH_Menu_SubMenuPopupDelay: + ret = 96; + break; + + case SH_ProgressDialog_TextLabelAlignment: + ret = Qt::AlignLeft | Qt::AlignVCenter; + break; + + case SH_ItemView_ChangeHighlightOnFocus: + ret = 0; + break; + + case SH_MessageBox_UseBorderForButtonSpacing: + ret = 1; + break; + + case SH_Dial_BackgroundRole: + ret = QPalette::Mid; + break; + + case SH_DialogButtonLayout: + ret = QDialogButtonBox::KdeLayout; + break; + case SH_LineEdit_PasswordCharacter: + ret = '*'; + break; + case SH_DialogButtonBox_ButtonsHaveIcons: + ret = 0; + break; + default: + ret = QCommonStyle::styleHint(hint, opt, widget, returnData); + break; + } + + return ret; +} + +/*! \reimp */ +QPalette QMotifStyle::standardPalette() const +{ +#ifdef Q_WS_X11 + QColor background(0xcf, 0xcf, 0xcf); + if (QX11Info::appDepth() <= 8) + background = QColor(0xc0, 0xc0, 0xc0); +#else + QColor background = QColor(0xcf, 0xcf, 0xcf); +#endif + + QColor light = background.lighter(); + QColor mid = QColor(0xa6, 0xa6, 0xa6); + QColor dark = QColor(0x79, 0x7d, 0x79); + QPalette palette(Qt::black, background, light, dark, mid, Qt::black, Qt::white); + palette.setBrush(QPalette::Disabled, QPalette::WindowText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Text, dark); + palette.setBrush(QPalette::Disabled, QPalette::ButtonText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Base, background); + return palette; +} + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_STYLE_MOTIF) || defined(QT_PLUGIN) diff --git a/src/gui/styles/qmotifstyle.h b/src/gui/styles/qmotifstyle.h new file mode 100644 index 0000000000..5ca0795216 --- /dev/null +++ b/src/gui/styles/qmotifstyle.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOTIFSTYLE_H +#define QMOTIFSTYLE_H + +#include <QtGui/qcommonstyle.h> +#include <QtCore/qpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_MOTIF) + +class QPalette; +class QFocusFrame; + +class QMotifStylePrivate; +class Q_GUI_EXPORT QMotifStyle : public QCommonStyle +{ + Q_OBJECT +public: + explicit QMotifStyle(bool useHighlightCols=false); + virtual ~QMotifStyle(); + + void setUseHighlightColors(bool); + bool useHighlightColors() const; + + void polish(QPalette&); + void polish(QWidget*); + void unpolish(QWidget*); + void polish(QApplication*); + void unpolish(QApplication*); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget = 0) const; + + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *widget = 0) const; + + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + bool event(QEvent *); + QPalette standardPalette() const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + +protected: + QPointer<QFocusFrame> focus; + QMotifStyle(QMotifStylePrivate &dd, bool useHighlightCols = false); + void timerEvent(QTimerEvent *event); + bool eventFilter(QObject *o, QEvent *e); + +private: + Q_DECLARE_PRIVATE(QMotifStyle) + Q_DISABLE_COPY(QMotifStyle) + + bool highlightCols; +}; + +#endif // QT_NO_STYLE_MOTIF + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMOTIFSTYLE_H diff --git a/src/gui/styles/qmotifstyle_p.h b/src/gui/styles/qmotifstyle_p.h new file mode 100644 index 0000000000..47043b582b --- /dev/null +++ b/src/gui/styles/qmotifstyle_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMOTIFSTYLE_P_H +#define QMOTIFSTYLE_P_H +#include <qlist.h> +#include <qdatetime.h> +#include <qprogressbar.h> +#include "qmotifstyle.h" +#include "qcommonstyle_p.h" + +QT_BEGIN_NAMESPACE + +// +// 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. +// + +// Private class +class QMotifStylePrivate : public QCommonStylePrivate +{ + Q_DECLARE_PUBLIC(QMotifStyle) +public: + QMotifStylePrivate(); + +public: +#ifndef QT_NO_PROGRESSBAR + QList<QProgressBar *> bars; + int animationFps; + int animateTimer; + QTime startTime; + int animateStep; +#endif // QT_NO_PROGRESSBAR +}; + +QT_END_NAMESPACE + +#endif //QMOTIFSTYLE_P_H diff --git a/src/gui/styles/qplastiquestyle.cpp b/src/gui/styles/qplastiquestyle.cpp new file mode 100644 index 0000000000..02ce60efab --- /dev/null +++ b/src/gui/styles/qplastiquestyle.cpp @@ -0,0 +1,6011 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplastiquestyle.h" + +#if !defined(QT_NO_STYLE_PLASTIQUE) || defined(QT_PLUGIN) + +static const bool AnimateBusyProgressBar = true; +static const bool AnimateProgressBar = false; +// #define QPlastique_MaskButtons +static const int ProgressBarFps = 25; +static const int blueFrameWidth = 2; // with of line edit focus frame + +#include "qwindowsstyle_p.h" +#include <qapplication.h> +#include <qbitmap.h> +#include <qabstractitemview.h> +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qdebug.h> +#include <qdialogbuttonbox.h> +#include <qformlayout.h> +#include <qgroupbox.h> +#include <qimage.h> +#include <qlineedit.h> +#include <qmainwindow.h> +#include <qmenu.h> +#include <qmenubar.h> +#include <qpainter.h> +#include <qpaintengine.h> +#include <qpainterpath.h> +#include <qpalette.h> +#include <qpen.h> +#include <qpixmap.h> +#include <qpixmapcache.h> +#include <qprogressbar.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qscrollbar.h> +#include <qspinbox.h> +#include <qsplitter.h> +#include <qstyleoption.h> +#include <qtextedit.h> +#include <qelapsedtimer.h> +#include <qtoolbar.h> +#include <qtoolbox.h> +#include <qtoolbutton.h> +#include <qworkspace.h> +#include <qprocess.h> +#include <qvarlengtharray.h> +#include <limits.h> +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +// from windows style +static const int windowsItemFrame = 2; // menu item frame width +static const int windowsSepHeight = 2; // separator item height +static const int windowsItemHMargin = 3; // menu item hor text margin +static const int windowsItemVMargin = 2; // menu item ver text margin +static const int windowsArrowHMargin = 6; // arrow horizontal margin +static const int windowsTabSpacing = 12; // space between text and tab +static const int windowsRightBorder = 15; // right border on windows +static const int windowsCheckMarkWidth = 12; // checkmarks width on windows + +static const char * const qt_plastique_slider_verticalhandle[] = { + "15 11 6 1", + " c None", + "+ c #979797", + "@ c #C9C9C9", + "$ c #C1C1C1", + "b c None", + "d c None", + " $++++++++$ ", + "$+bbbbbbbb+$ ", + "+b $$ +$ ", + "+b $@ +$ ", + "+b +$", + "+b d+", + "+b d+$", + "+b $$ d+$ ", + "+b $@ d+$ ", + "$+dddddddd+$ ", + " $++++++++$ "}; + +static const char * const qt_plastique_slider_verticalhandle_left[] = { + "15 11 6 1", + " c None", + "+ c #979797", + "@ c #C9C9C9", + "$ c #C1C1C1", + "b c None", + "d c None", + " $++++++++$ ", + " $+bbbbbbbb+$", + " $+b $$ d+", + " $+b $@ d+", + "$+b d+", + "+b d+", + "$+ d+", + " $+ $$ d+", + " $+ $@ d+", + " $+dddddddd+$", + " $++++++++$ "}; + +static const char * const qt_plastique_slider_horizontalhandle[] = { + "11 15 6 1", + " c None", + "+ c #979797", + "@ c #C9C9C9", + "$ c #C1C1C1", + "b c None", + "d c None", + " $+++++++$ ", + "$+bbbbbbb+$", + "+b d+", + "+b$$ $$d+", + "+b$@ $@d+", + "+b d+", + "+b d+", + "+b d+", + "+b d+", + "+b d+", + "$+ d+$", + " $+ d+$ ", + " $+ d+$ ", + " $+d+$ ", + " $+$ "}; + +static const char * const qt_plastique_slider_horizontalhandle_up[] = { + "11 15 6 1", + " c None", + "+ c #979797", + "@ c #C9C9C9", + "$ c #C1C1C1", + "b c None", + "d c None", + " $+$ ", + " $+b+$ ", + " $+b +$ ", + " $+b +$ ", + "$+b +$", + "+b d+", + "+b d+", + "+b d+", + "+b d+", + "+b d+", + "+b$$ $$d+", + "+b$@ $@d+", + "+b d+", + "$+ddddddd+$", + " $+++++++$ "}; + +static const char * const qt_scrollbar_button_arrow_left[] = { + "4 7 2 1", + " c None", + "* c #BFBFBF", + " *", + " **", + " ***", + "****", + " ***", + " **", + " *"}; + +static const char * const qt_scrollbar_button_arrow_right[] = { + "4 7 2 1", + " c None", + "* c #BFBFBF", + "* ", + "** ", + "*** ", + "****", + "*** ", + "** ", + "* "}; + +static const char * const qt_scrollbar_button_arrow_up[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + " * ", + " *** ", + " ***** ", + "*******"}; + +static const char * const qt_scrollbar_button_arrow_down[] = { + "7 4 2 1", + " c None", + "* c #BFBFBF", + "*******", + " ***** ", + " *** ", + " * "}; + +static const char * const qt_scrollbar_button_left[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + " .+++++++++++++.", + ".+#############+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + ".+<<<<<<<<<<<<<+", + " .+++++++++++++."}; + +static const char * const qt_scrollbar_button_right[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + ".+++++++++++++. ", + "+#############+.", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+<<<<<<<<<<<<<+.", + ".+++++++++++++. "}; + +static const char * const qt_scrollbar_button_up[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + " .++++++++++++. ", + ".+############+.", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+<<<<<<<<<<<<<<+", + ".++++++++++++++."}; + +static const char * const qt_scrollbar_button_down[] = { + "16 16 6 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + "# c #FAFAFA", + "< c #FAFAFA", + "* c #FAFAFA", + "++++++++++++++++", + "+##############+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + "+# <+", + ".+<<<<<<<<<<<<+.", + " .++++++++++++. "}; + +static const char * const qt_scrollbar_slider_pattern_vertical[] = { + "10 18 3 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + ".. .. ..", + ".+ .+ .+", + " ", + " ", + ".. .. ..", + ".+ .+ .+", + " ", + " ", + ".. .. ..", + ".+ .+ .+", + " ", + " ", + ".. .. ..", + ".+ .+ .+", + " ", + " ", + ".. .. ..", + ".+ .+ .+"}; + +static const char * const qt_scrollbar_slider_pattern_horizontal[] = { + "18 10 3 1", + " c None", + ". c #BFBFBF", + "+ c #979797", + ".. .. .. .. ..", + ".+ .+ .+ .+ .+", + " ", + " ", + ".. .. .. .. ..", + ".+ .+ .+ .+ .+", + " ", + " ", + ".. .. .. .. ..", + ".+ .+ .+ .+ .+"}; + +static const char * const qt_toolbarhandle[] = { + "6 6 4 1", + " c None", + ". c #C5C5C5", + "+ c #EEEEEE", + "@ c #FAFAFA", + ".. ", + ".+@ ", + " @@ ", + " .. ", + " .+@", + " @@"}; + +static const char * const qt_simple_toolbarhandle[] = { + "3 3 4 1", + " c None", + ". c #C5C5C5", + "+ c #EEEEEE", + "@ c #FAFAFA", + ".. ", + ".+@", + " @@"}; + +static const char * const qt_titlebar_context_help[] = { +"27 27 5 1", +" c None", +". c #0A0C12", +"+ c #1B202D", +"@ c #293144", +"# c #3C435D", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" +@##@+ ", +" .@@@.+@@.. ", +" .##+ +@@+. ", +" .##@ @#@+. ", +" .... +@+.. ", +" .@+@@.. ", +" +#@@+ ", +" .##. ", +" .++. ", +" .++. ", +" +##+ ", +" .@@. ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; + +static QLinearGradient qMapGradientToRect(const QLinearGradient &gradient, const QRectF &rect) +{ + QLinearGradient tmpGrad(rect.center().x(), rect.top(), + rect.center().x(), rect.bottom()); + tmpGrad.setStops(gradient.stops()); + return tmpGrad; +} + +static QBrush qMapBrushToRect(const QBrush &brush, const QRectF &rect) +{ + if (!brush.gradient()) + return brush; + + // ### Ugly assumption that it's a linear gradient + QBrush tmp(qMapGradientToRect(*static_cast<const QLinearGradient *>(brush.gradient()), rect)); + return tmp; +} + +static void qBrushSetAlphaF(QBrush *brush, qreal alpha) +{ + if (const QGradient *gradient = brush->gradient()) { + // Use the gradient. Call QColor::setAlphaF() on all color stops. + QGradientStops stops = gradient->stops(); + QMutableVectorIterator<QGradientStop> it(stops); + QColor tmpColor; + while (it.hasNext()) { + it.next(); + tmpColor = it.value().second; + tmpColor.setAlphaF(alpha * tmpColor.alphaF()); + it.setValue(QPair<qreal, QColor>(it.value().first, tmpColor)); + } + + switch (gradient->type()) { + case QGradient::RadialGradient: { + QRadialGradient grad = *static_cast<const QRadialGradient *>(gradient); + grad.setStops(stops); + *brush = QBrush(grad); + break; + } + case QGradient::ConicalGradient: { + QConicalGradient grad = *static_cast<const QConicalGradient *>(gradient); + grad.setStops(stops); + *brush = QBrush(grad); + break; + } + default: + qWarning("QPlastiqueStyle::qBrushLight() - unknown gradient type" + " - falling back to QLinearGradient"); + case QGradient::LinearGradient: { + QLinearGradient grad = *static_cast<const QLinearGradient *>(gradient); + grad.setStops(stops); + *brush = QBrush(grad); + break; + } + } + } else if (!brush->texture().isNull()) { + // Modify the texture - ridiculously expensive. + QPixmap texture = brush->texture(); + QPixmap pixmap; + QString name = QLatin1Literal("qbrushtexture-alpha") + % HexString<qreal>(alpha) + % HexString<qint64>(texture.cacheKey()); + if (!QPixmapCache::find(name, pixmap)) { + QImage image = texture.toImage(); + QRgb *rgb = reinterpret_cast<QRgb *>(image.bits()); + int pixels = image.width() * image.height(); + QColor tmpColor; + while (pixels--) { + tmpColor.setRgb(*rgb); + tmpColor.setAlphaF(alpha * tmpColor.alphaF()); + *rgb++ = tmpColor.rgba(); + } + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(name, pixmap); + } + brush->setTexture(pixmap); + } else { + // Use the color + QColor tmpColor = brush->color(); + tmpColor.setAlphaF(alpha * tmpColor.alphaF()); + brush->setColor(tmpColor); + } +} + +static QBrush qBrushLight(QBrush brush, int light) +{ + if (const QGradient *gradient = brush.gradient()) { + // Use the gradient. Call QColor::lighter() on all color stops. + QGradientStops stops = gradient->stops(); + QMutableVectorIterator<QGradientStop> it(stops); + while (it.hasNext()) { + it.next(); + it.setValue(QPair<qreal, QColor>(it.value().first, it.value().second.lighter(light))); + } + + switch (gradient->type()) { + case QGradient::RadialGradient: { + QRadialGradient grad = *static_cast<const QRadialGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + case QGradient::ConicalGradient: { + QConicalGradient grad = *static_cast<const QConicalGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + default: + qWarning("QPlastiqueStyle::qBrushLight() - unknown gradient type" + " - falling back to QLinearGradient"); + case QGradient::LinearGradient: { + QLinearGradient grad = *static_cast<const QLinearGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + } + } else if (!brush.texture().isNull()) { + // Modify the texture - ridiculously expensive. + QPixmap texture = brush.texture(); + QPixmap pixmap; + QString name = QLatin1Literal("qbrushtexture-light") + % HexString<int>(light) + % HexString<qint64>(texture.cacheKey()); + + if (!QPixmapCache::find(name, pixmap)) { + QImage image = texture.toImage(); + QRgb *rgb = reinterpret_cast<QRgb *>(image.bits()); + int pixels = image.width() * image.height(); + QColor tmpColor; + while (pixels--) { + tmpColor.setRgb(*rgb); + *rgb++ = tmpColor.lighter(light).rgba(); + } + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(name, pixmap); + } + brush.setTexture(pixmap); + } else { + // Use the color + brush.setColor(brush.color().lighter(light)); + } + return brush; +} + +static QBrush qBrushDark(QBrush brush, int dark) +{ + if (const QGradient *gradient = brush.gradient()) { + // Use the gradient. Call QColor::darker() on all color stops. + QGradientStops stops = gradient->stops(); + QMutableVectorIterator<QGradientStop> it(stops); + while (it.hasNext()) { + it.next(); + it.setValue(QPair<qreal, QColor>(it.value().first, it.value().second.darker(dark))); + } + + switch (gradient->type()) { + case QGradient::RadialGradient: { + QRadialGradient grad = *static_cast<const QRadialGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + case QGradient::ConicalGradient: { + QConicalGradient grad = *static_cast<const QConicalGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + default: + qWarning("QPlastiqueStyle::qBrushDark() - unknown gradient type" + " - falling back to QLinearGradient"); + case QGradient::LinearGradient: { + QLinearGradient grad = *static_cast<const QLinearGradient *>(gradient); + grad.setStops(stops); + brush = QBrush(grad); + break; + } + } + } else if (!brush.texture().isNull()) { + // Modify the texture - ridiculously expensive. + QPixmap texture = brush.texture(); + QPixmap pixmap; + QString name = QLatin1Literal("qbrushtexture-dark") + % HexString<int>(dark) + % HexString<qint64>(texture.cacheKey()); + + if (!QPixmapCache::find(name, pixmap)) { + QImage image = texture.toImage(); + QRgb *rgb = reinterpret_cast<QRgb *>(image.bits()); + int pixels = image.width() * image.height(); + QColor tmpColor; + while (pixels--) { + tmpColor.setRgb(*rgb); + *rgb++ = tmpColor.darker(dark).rgba(); + } + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(name, pixmap); + } + brush.setTexture(pixmap); + } else { + // Use the color + brush.setColor(brush.color().darker(dark)); + } + return brush; +} + +/* + Draws a rounded frame using the provided brush for 1, and adds 0.5 alpha + for 0. + + 0111111110 + 01 10 + 1 1 + 1 1 + 1 1 + 01 10 + 0111111110 +*/ +static void qt_plastique_draw_frame(QPainter *painter, const QRect &rect, const QStyleOption *option, + QFrame::Shadow shadow = QFrame::Plain) +{ + QPen oldPen = painter->pen(); + QBrush border; + QBrush corner; + QBrush innerTopLeft; + QBrush innerBottomRight; + + if (shadow != QFrame::Plain && (option->state & QStyle::State_HasFocus)) { + border = option->palette.highlight(); + qBrushSetAlphaF(&border, qreal(0.8)); + corner = option->palette.highlight(); + qBrushSetAlphaF(&corner, 0.5); + innerTopLeft = qBrushDark(option->palette.highlight(), 125); + innerBottomRight = option->palette.highlight(); + qBrushSetAlphaF(&innerBottomRight, qreal(0.65)); + } else { + border = option->palette.shadow(); + qBrushSetAlphaF(&border, qreal(0.4)); + corner = option->palette.shadow(); + qBrushSetAlphaF(&corner, 0.25); + innerTopLeft = option->palette.shadow(); + innerBottomRight = option->palette.shadow(); + if (shadow == QFrame::Sunken) { + qBrushSetAlphaF(&innerTopLeft, qreal(0.23)); + qBrushSetAlphaF(&innerBottomRight, qreal(0.075)); + } else { + qBrushSetAlphaF(&innerTopLeft, qreal(0.075)); + qBrushSetAlphaF(&innerBottomRight, qreal(0.23)); + } + } + + QLine lines[4]; + QPoint points[8]; + + // Opaque corner lines + painter->setPen(QPen(border, 0)); + lines[0] = QLine(rect.left() + 2, rect.top(), rect.right() - 2, rect.top()); + lines[1] = QLine(rect.left() + 2, rect.bottom(), rect.right() - 2, rect.bottom()); + lines[2] = QLine(rect.left(), rect.top() + 2, rect.left(), rect.bottom() - 2); + lines[3] = QLine(rect.right(), rect.top() + 2, rect.right(), rect.bottom() - 2); + painter->drawLines(lines, 4); + + // Opaque corner dots + points[0] = QPoint(rect.left() + 1, rect.top() + 1); + points[1] = QPoint(rect.left() + 1, rect.bottom() - 1); + points[2] = QPoint(rect.right() - 1, rect.top() + 1); + points[3] = QPoint(rect.right() - 1, rect.bottom() - 1); + painter->drawPoints(points, 4); + + // Shaded corner dots + painter->setPen(QPen(corner, 0)); + points[0] = QPoint(rect.left(), rect.top() + 1); + points[1] = QPoint(rect.left(), rect.bottom() - 1); + points[2] = QPoint(rect.left() + 1, rect.top()); + points[3] = QPoint(rect.left() + 1, rect.bottom()); + points[4] = QPoint(rect.right(), rect.top() + 1); + points[5] = QPoint(rect.right(), rect.bottom() - 1); + points[6] = QPoint(rect.right() - 1, rect.top()); + points[7] = QPoint(rect.right() - 1, rect.bottom()); + painter->drawPoints(points, 8); + + // Shadows + if (shadow != QFrame::Plain) { + painter->setPen(QPen(innerTopLeft, 0)); + lines[0] = QLine(rect.left() + 2, rect.top() + 1, rect.right() - 2, rect.top() + 1); + lines[1] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, rect.bottom() - 2); + painter->drawLines(lines, 2); + painter->setPen(QPen(innerBottomRight, 0)); + lines[0] = QLine(rect.left() + 2, rect.bottom() - 1, rect.right() - 2, rect.bottom() - 1); + lines[1] = QLine(rect.right() - 1, rect.top() + 2, rect.right() - 1, rect.bottom() - 2); + painter->drawLines(lines, 2); + } + + painter->setPen(oldPen); +} + +static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50) +{ + const int maxFactor = 100; + QColor tmp = colorA; + tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor); + tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor); + tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor); + return tmp; +} + +static void qt_plastique_draw_gradient(QPainter *painter, const QRect &rect, const QColor &gradientStart, + const QColor &gradientStop) +{ + QString gradientName = QLatin1Literal("qplastique-g") + % HexString<int>(rect.width()) + % HexString<int>(rect.height()) + % HexString<QRgb>(gradientStart.rgba()) + % HexString<QRgb>(gradientStop.rgba()); + + QPixmap cache; + QPainter *p = painter; + QRect r = rect; + + bool doPixmapCache = painter->deviceTransform().isIdentity() + && painter->worldMatrix().isIdentity(); + if (doPixmapCache && QPixmapCache::find(gradientName, cache)) { + painter->drawPixmap(rect, cache); + } else { + if (doPixmapCache) { + cache = QPixmap(rect.size()); + cache.fill(Qt::transparent); + p = new QPainter(&cache); + r = QRect(0, 0, rect.width(), rect.height()); + } + + int x = r.center().x(); + QLinearGradient gradient(x, r.top(), x, r.bottom()); + gradient.setColorAt(0, gradientStart); + gradient.setColorAt(1, gradientStop); + p->fillRect(r, gradient); + + if (doPixmapCache) { + p->end(); + delete p; + painter->drawPixmap(rect, cache); + QPixmapCache::insert(gradientName, cache); + } + } +} + +static void qt_plastique_drawFrame(QPainter *painter, const QStyleOption *option, const QWidget *widget) +{ + QRect rect = option->rect; + QPen oldPen = painter->pen(); + + QColor borderColor = option->palette.background().color().darker(178); + QColor gradientStartColor = option->palette.button().color().lighter(104); + QColor gradientStopColor = option->palette.button().color().darker(105); + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + + QLine lines[4]; + QPoint points[8]; + + // outline / border + painter->setPen(borderColor); + lines[0] = QLine(rect.left() + 2, rect.top(), rect.right() - 2, rect.top()); + lines[1] = QLine(rect.left() + 2, rect.bottom(), rect.right() - 2, rect.bottom()); + lines[2] = QLine(rect.left(), rect.top() + 2, rect.left(), rect.bottom() - 2); + lines[3] = QLine(rect.right(), rect.top() + 2, rect.right(), rect.bottom() - 2); + painter->drawLines(lines, 4); + + points[0] = QPoint(rect.left() + 1, rect.top() + 1); + points[1] = QPoint(rect.right() - 1, rect.top() + 1); + points[2] = QPoint(rect.left() + 1, rect.bottom() - 1); + points[3] = QPoint(rect.right() - 1, rect.bottom() - 1); + painter->drawPoints(points, 4); + + painter->setPen(alphaCornerColor); + + points[0] = QPoint(rect.left() + 1, rect.top()); + points[1] = QPoint(rect.right() - 1, rect.top()); + points[2] = QPoint(rect.left() + 1, rect.bottom()); + points[3] = QPoint(rect.right() - 1, rect.bottom()); + points[4] = QPoint(rect.left(), rect.top() + 1); + points[5] = QPoint(rect.right(), rect.top() + 1); + points[6] = QPoint(rect.left(), rect.bottom() - 1); + points[7] = QPoint(rect.right(), rect.bottom() - 1); + painter->drawPoints(points, 8); + + // inner border + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) + painter->setPen(option->palette.button().color().darker(118)); + else + painter->setPen(gradientStartColor); + + lines[0] = QLine(rect.left() + 2, rect.top() + 1, rect.right() - 2, option->rect.top() + 1); + lines[1] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, option->rect.bottom() - 2); + painter->drawLines(lines, 2); + + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) + painter->setPen(option->palette.button().color().darker(110)); + else + painter->setPen(gradientStopColor.darker(102)); + + lines[0] = QLine(rect.left() + 2, rect.bottom() - 1, rect.right() - 2, rect.bottom() - 1); + lines[1] = QLine(rect.right() - 1, rect.top() + 2, rect.right() - 1, rect.bottom() - 2); + painter->drawLines(lines, 2); + + painter->setPen(oldPen); +} + +static void qt_plastique_drawShadedPanel(QPainter *painter, const QStyleOption *option, bool base, + const QWidget *widget) +{ + QRect rect = option->rect; + QPen oldPen = painter->pen(); + + QColor gradientStartColor = option->palette.button().color().lighter(104); + QColor gradientStopColor = option->palette.button().color().darker(105); + + // gradient fill + if ((option->state & QStyle::State_Enabled) || !(option->state & QStyle::State_AutoRaise)) { + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) { + qt_plastique_draw_gradient(painter, rect.adjusted(1, 1, -1, -1), + option->palette.button().color().darker(114), + option->palette.button().color().darker(106)); + } else { + qt_plastique_draw_gradient(painter, rect.adjusted(1, 1, -1, -1), + base ? option->palette.background().color().lighter(105) : gradientStartColor, + base ? option->palette.background().color().darker(102) : gradientStopColor); + } + } + + qt_plastique_drawFrame(painter, option, widget); + + painter->setPen(oldPen); +} + +static void qt_plastique_draw_mdibutton(QPainter *painter, const QStyleOptionTitleBar *option, const QRect &tmp, bool hover, bool sunken) +{ + if (tmp.isNull()) + return; + bool active = (option->titleBarState & QStyle::State_Active); + + // ### use palette colors instead + QColor mdiButtonGradientStartColor; + QColor mdiButtonGradientStopColor; + if (active) { + mdiButtonGradientStartColor = QColor((hover || sunken) ? 0x7d8bb1 : 0x55689a); + mdiButtonGradientStopColor = QColor((hover || sunken) ? 0x939ebe : 0x7381ab); + } else { + mdiButtonGradientStartColor = QColor((hover || sunken) ? 0x9e9e9e : 0x818181); + mdiButtonGradientStopColor = QColor((hover || sunken) ? 0xababab : 0x929292); + } + + qt_plastique_draw_gradient(painter, tmp.adjusted(1, 1, -1, -1), + mdiButtonGradientStartColor, mdiButtonGradientStopColor); + + QColor mdiButtonBorderColor; + if (active) { + mdiButtonBorderColor = (hover || sunken) ? QColor(0x627097) : QColor(0x324577); + } else { + mdiButtonBorderColor = (hover || sunken) ? QColor(0x838383) : QColor(0x5e5e5e); + } + painter->setPen(QPen(mdiButtonBorderColor, 1)); + + const QLine lines[4] = { + QLine(tmp.left() + 2, tmp.top(), tmp.right() - 2, tmp.top()), + QLine(tmp.left() + 2, tmp.bottom(), tmp.right() - 2, tmp.bottom()), + QLine(tmp.left(), tmp.top() + 2, tmp.left(), tmp.bottom() - 2), + QLine(tmp.right(), tmp.top() + 2, tmp.right(), tmp.bottom() - 2) }; + painter->drawLines(lines, 4); + + const QPoint points[4] = { + QPoint(tmp.left() + 1, tmp.top() + 1), + QPoint(tmp.right() - 1, tmp.top() + 1), + QPoint(tmp.left() + 1, tmp.bottom() - 1), + QPoint(tmp.right() - 1, tmp.bottom() - 1) }; + painter->drawPoints(points, 4); +} + +#ifndef QT_NO_DOCKWIDGET +static QString elliditide(const QString &text, const QFontMetrics &fontMetrics, const QRect &rect, int *textWidth = 0) +{ + // Chop and insert ellide into title if text is too wide + QString title = text; + int width = textWidth ? *textWidth : fontMetrics.width(text); + QString ellipsis = QLatin1String("..."); + if (width > rect.width()) { + QString leftHalf = title.left(title.size() / 2); + QString rightHalf = title.mid(leftHalf.size() + 1); + while (!leftHalf.isEmpty() && !rightHalf.isEmpty()) { + leftHalf.chop(1); + int width = fontMetrics.width(leftHalf + ellipsis + rightHalf); + if (width < rect.width()) { + title = leftHalf + ellipsis + rightHalf; + width = width; + break; + } + rightHalf.remove(0, 1); + width = fontMetrics.width(leftHalf + ellipsis + rightHalf); + if (width < rect.width()) { + title = leftHalf + ellipsis + rightHalf; + width = width; + break; + } + } + } + if (textWidth) + *textWidth = width; + return title; +} +#endif + +#if !defined(QT_NO_DOCKWIDGET) || !defined(QT_NO_SPLITTER) +static void qt_plastique_draw_handle(QPainter *painter, const QStyleOption *option, + const QRect &rect, Qt::Orientation orientation, + const QWidget *widget) +{ + QColor borderColor = option->palette.background().color().darker(178); + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + QImage handle(qt_simple_toolbarhandle); + alphaCornerColor.setAlpha(170); + handle.setColor(1, alphaCornerColor.rgba()); + handle.setColor(2, mergedColors(alphaCornerColor, option->palette.light().color()).rgba()); + handle.setColor(3, option->palette.light().color().rgba()); + + const int spacing = 2; + + if (orientation == Qt::Vertical) { + int nchunks = rect.width() / (handle.width() + spacing); + for (int i = 0; i < nchunks; ++i) + painter->drawImage(QPoint(rect.left() + i * (handle.width() + spacing), rect.top()), handle); + } else { + int nchunks = rect.height() / (handle.height() + spacing); + for (int i = 0; i < nchunks; ++i) + painter->drawImage(QPoint(rect.left(), rect.top() + i * (handle.height() + spacing)), handle); + } +} +#endif + +class QPlastiqueStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QPlastiqueStyle) +public: + QPlastiqueStylePrivate(); + virtual ~QPlastiqueStylePrivate(); + void drawPartialFrame(QPainter *painter, const QStyleOptionComplex *option, + const QRect &rect, const QWidget *widget) const; + +#ifndef QT_NO_PROGRESSBAR + QList<QProgressBar *> bars; + int progressBarAnimateTimer; + QElapsedTimer timer; +#endif +}; + +/*! + \internal + */ +QPlastiqueStylePrivate::QPlastiqueStylePrivate() : + QWindowsStylePrivate() +#ifndef QT_NO_PROGRESSBAR + , progressBarAnimateTimer(0) +#endif +{ +} + +/*! + \internal + */ +QPlastiqueStylePrivate::~QPlastiqueStylePrivate() +{ +} + +/*! + \class QPlastiqueStyle + \brief The QPlastiqueStyle class provides a widget style similar to the + Plastik style available in KDE. + + The Plastique style provides a default look and feel for widgets on X11 + that closely resembles the Plastik style, introduced by Sandro Giessl in + KDE 3.2. + + \img qplastiquestyle.png + \sa QWindowsXPStyle, QMacStyle, QWindowsStyle, QCDEStyle, QMotifStyle +*/ + +/*! + Constructs a QPlastiqueStyle object. +*/ +QPlastiqueStyle::QPlastiqueStyle() + : QWindowsStyle(*new QPlastiqueStylePrivate) +{ + setObjectName(QLatin1String("Plastique")); +} + +/*! + Destructs the QPlastiqueStyle object. +*/ +QPlastiqueStyle::~QPlastiqueStyle() +{ +} + +/* + Used by spin- and combo box. + Draws a rounded frame around rect but omits the right hand edge +*/ +void QPlastiqueStylePrivate::drawPartialFrame(QPainter *painter, const QStyleOptionComplex *option, + const QRect &rect, const QWidget *widget) const +{ + Q_Q(const QPlastiqueStyle); + bool reverse = option->direction == Qt::RightToLeft; + QStyleOptionFrame frameOpt; +#ifndef QT_NO_LINEEDIT + if (QLineEdit *lineedit = widget->findChild<QLineEdit *>()) + frameOpt.initFrom(lineedit); +#else + Q_UNUSED(widget) +#endif // QT_NO_LINEEDIT + + frameOpt.rect = rect; + painter->save(); + frameOpt.rect.adjust(-blueFrameWidth + (reverse ? 1 : 0), -blueFrameWidth, + blueFrameWidth + (reverse ? 0 : -1), blueFrameWidth); + painter->setClipRect(frameOpt.rect); + frameOpt.rect.adjust(reverse ? -2 : 0, 0, reverse ? 0 : 2, 0); + frameOpt.lineWidth = q->pixelMetric(QStyle::PM_DefaultFrameWidth); + frameOpt.midLineWidth = 0; + frameOpt.state = option->state | QStyle::State_Sunken; + frameOpt.palette = option->palette; + q->drawPrimitive(QStyle::PE_PanelLineEdit, &frameOpt, painter, widget); + painter->restore(); + + // Draw a two pixel highlight on the flat edge + if (option->state & QStyle::State_HasFocus) { + painter->setPen(QPen(option->palette.highlight(), 0)); + QBrush focusBorder = option->palette.highlight(); + qBrushSetAlphaF(&focusBorder, qreal(0.65)); + if (!reverse) { + painter->drawLine(rect.topRight() + QPoint(1, -1), + rect.bottomRight() + QPoint(1, 1)); + painter->setPen(QPen(focusBorder, 0)); + painter->drawLine(rect.topRight(), + rect.bottomRight()); + } + else { + painter->drawLine(rect.topLeft() + QPoint(-1, -1), + rect.bottomLeft() + QPoint(-1, 1)); + painter->setPen(QPen(focusBorder, 0)); + painter->drawLine(rect.topLeft(), + rect.bottomLeft()); + } + } +} + +/*! + \reimp +*/ +void QPlastiqueStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + Q_ASSERT(option); + + QColor borderColor = option->palette.background().color().darker(178); + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + QColor alphaTextColor = mergedColors(option->palette.background().color(), option->palette.text().color()); + + switch (element) { + case PE_IndicatorButtonDropDown: + proxy()->drawPrimitive(PE_PanelButtonTool, option, painter, widget); + break; + case PE_FrameDefaultButton: { + if (!(option->state & QStyle::State_Enabled)) + break; + painter->setPen(QPen(QColor(0, 0, 0, 127), 0)); + const QLine lines[4] = { + QLine(option->rect.left() + 2, option->rect.top(), + option->rect.right() - 2, option->rect.top()), + QLine(option->rect.left() + 2, option->rect.bottom(), + option->rect.right() - 2, option->rect.bottom()), + QLine(option->rect.left(), option->rect.top() + 2, + option->rect.left(), option->rect.bottom() - 2), + QLine(option->rect.right(), option->rect.top() + 2, + option->rect.right(), option->rect.bottom() - 2) }; + painter->drawLines(lines, 4); + + QPoint points[8]; + points[0] = QPoint(option->rect.left() + 1, option->rect.top() + 1); + points[1] = QPoint(option->rect.right() - 1, option->rect.top() + 1); + points[2] = QPoint(option->rect.left() + 1, option->rect.bottom() - 1); + points[3] = QPoint(option->rect.right() - 1, option->rect.bottom() - 1); + painter->drawPoints(points, 4); + + painter->setPen(QPen(QColor(0, 0, 0, 63), 0)); + points[0] = QPoint(option->rect.left() + 1, option->rect.top()); + points[1] = QPoint(option->rect.right() - 1, option->rect.top()); + points[2] = QPoint(option->rect.left(), option->rect.top() + 1); + points[3] = QPoint(option->rect.right(), option->rect.top() + 1); + points[4] = QPoint(option->rect.left() + 1, option->rect.bottom()); + points[5] = QPoint(option->rect.right() - 1, option->rect.bottom()); + points[6] = QPoint(option->rect.left(), option->rect.bottom() - 1); + points[7] = QPoint(option->rect.right(), option->rect.bottom() - 1); + painter->drawPoints(points, 8); + + break; + } +#ifndef QT_NO_TABWIDGET + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) { + if (twf->shape != QTabBar::RoundedNorth && twf->shape != QTabBar::RoundedWest && + twf->shape != QTabBar::RoundedSouth && twf->shape != QTabBar::RoundedEast) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + break; + } + + int borderThickness = proxy()->pixelMetric(PM_TabBarBaseOverlap, twf, widget); + bool reverse = (twf->direction == Qt::RightToLeft); + + painter->save(); + + // Start by filling the contents of the tab widget frame (which is + // actually a panel). + painter->fillRect(option->rect.adjusted(1, 1, -1, -1), option->palette.window()); + + QRect tabBarRect; + switch (twf->shape) { + case QTabBar::RoundedNorth: + if (reverse) + tabBarRect = QRect(twf->rect.right() - twf->leftCornerWidgetSize.width() - twf->tabBarSize.width() + 1, twf->rect.top(), twf->tabBarSize.width(), borderThickness); + else + tabBarRect = QRect(twf->rect.left() + twf->leftCornerWidgetSize.width(), twf->rect.top(), twf->tabBarSize.width(), borderThickness); + break ; + case QTabBar::RoundedWest: + tabBarRect = QRect(twf->rect.left(), twf->rect.top() + twf->leftCornerWidgetSize.height(), borderThickness, twf->tabBarSize.height()); + break ; + case QTabBar::RoundedEast: + tabBarRect = QRect(twf->rect.right() - borderThickness + 1, twf->rect.top() + twf->leftCornerWidgetSize.height(), + borderThickness, twf->tabBarSize.height()); + break ; + case QTabBar::RoundedSouth: + if (reverse) + tabBarRect = QRect(twf->rect.right() - twf->leftCornerWidgetSize.width() - twf->tabBarSize.width() + 1, + twf->rect.bottom() - borderThickness + 1, twf->tabBarSize.width(), borderThickness); + else + tabBarRect = QRect(twf->rect.left() + twf->leftCornerWidgetSize.width(), + twf->rect.bottom() - borderThickness + 1, twf->tabBarSize.width(), borderThickness); + break ; + default: + break; + } + + QRegion region(twf->rect); + region -= tabBarRect; + painter->setClipRegion(region); + + // Outer border + QLine leftLine = QLine(twf->rect.topLeft() + QPoint(0, 2), twf->rect.bottomLeft() - QPoint(0, 2)); + QLine rightLine = QLine(twf->rect.topRight() + QPoint(0, 2), twf->rect.bottomRight() - QPoint(0, 2)); + QLine bottomLine = QLine(twf->rect.bottomLeft() + QPoint(2, 0), twf->rect.bottomRight() - QPoint(2, 0)); + QLine topLine = QLine(twf->rect.topLeft() + QPoint(2, 0), twf->rect.topRight() - QPoint(2, 0)); + + QBrush border = option->palette.shadow(); + qBrushSetAlphaF(&border, qreal(0.4)); + painter->setPen(QPen(border, 0)); + + QVarLengthArray<QLine, 4> lines; + QVarLengthArray<QPoint, 8> points; + + lines.append(topLine); + + // Inner border + QLine innerLeftLine = QLine(leftLine.p1() + QPoint(1, 0), leftLine.p2() + QPoint(1, 0)); + QLine innerRightLine = QLine(rightLine.p1() - QPoint(1, 0), rightLine.p2() - QPoint(1, 0)); + QLine innerBottomLine = QLine(bottomLine.p1() - QPoint(0, 1), bottomLine.p2() - QPoint(0, 1)); + QLine innerTopLine = QLine(topLine.p1() + QPoint(0, 1), topLine.p2() + QPoint(0, 1)); + + // Rounded Corner + QPoint leftBottomOuterCorner = QPoint(innerLeftLine.p2() + QPoint(0, 1)); + QPoint leftBottomInnerCorner1 = QPoint(leftLine.p2() + QPoint(0, 1)); + QPoint leftBottomInnerCorner2 = QPoint(bottomLine.p1() - QPoint(1, 0)); + QPoint rightBottomOuterCorner = QPoint(innerRightLine.p2() + QPoint(0, 1)); + QPoint rightBottomInnerCorner1 = QPoint(rightLine.p2() + QPoint(0, 1)); + QPoint rightBottomInnerCorner2 = QPoint(bottomLine.p2() + QPoint(1, 0)); + QPoint rightTopOuterCorner = QPoint(innerRightLine.p1() - QPoint(0, 1)); + QPoint rightTopInnerCorner1 = QPoint(rightLine.p1() - QPoint(0, 1)); + QPoint rightTopInnerCorner2 = QPoint(topLine.p2() + QPoint(1, 0)); + QPoint leftTopOuterCorner = QPoint(innerLeftLine.p1() - QPoint(0, 1)); + QPoint leftTopInnerCorner1 = QPoint(leftLine.p1() - QPoint(0, 1)); + QPoint leftTopInnerCorner2 = QPoint(topLine.p1() - QPoint(1, 0)); + + lines.append(leftLine); + lines.append(rightLine); + lines.append(bottomLine); + + painter->drawLines(lines.constData(), lines.size()); + lines.clear(); + + points.append(leftBottomOuterCorner); + points.append(rightBottomOuterCorner); + points.append(rightTopOuterCorner); + points.append(leftTopOuterCorner); + + painter->drawPoints(points.constData(), points.size()); + points.clear(); + + QBrush innerTopLeft = option->palette.shadow(); + qBrushSetAlphaF(&innerTopLeft, qreal(0.075)); + painter->setPen(QPen(innerTopLeft, 0)); + + lines.append(innerLeftLine); + lines.append(innerTopLine); + painter->drawLines(lines.constData(), lines.size()); + lines.clear(); + + QBrush innerBottomRight = option->palette.shadow(); + qBrushSetAlphaF(&innerBottomRight, qreal(0.23)); + painter->setPen(QPen(innerBottomRight, 0)); + lines.append(innerRightLine); + lines.append(innerBottomLine); + painter->drawLines(lines.constData(), lines.size()); + lines.clear(); + + QBrush corner = option->palette.shadow(); + qBrushSetAlphaF(&corner, 0.25); + painter->setPen(QPen(corner, 0)); + points.append(leftBottomInnerCorner1); + points.append(leftBottomInnerCorner2); + points.append(rightBottomInnerCorner1); + points.append(rightBottomInnerCorner2); + points.append(rightTopInnerCorner1); + points.append(rightTopInnerCorner2); + points.append(leftTopInnerCorner1); + points.append(leftTopInnerCorner2); + painter->drawPoints(points.constData(), points.size()); + points.clear(); + + painter->restore(); + } + break ; +#endif // QT_NO_TABWIDGET +#ifndef QT_NO_TABBAR + case PE_FrameTabBarBase: + if (const QStyleOptionTabBarBase *tbb = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) { + if (tbb->shape != QTabBar::RoundedNorth && tbb->shape != QTabBar::RoundedWest && + tbb->shape != QTabBar::RoundedSouth && tbb->shape != QTabBar::RoundedEast) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + break; + } + + painter->save(); + + QRegion region(tbb->rect); + region -= tbb->tabBarRect; + painter->setClipRegion(region); + + QLine topLine = QLine(tbb->rect.bottomLeft() - QPoint(0, 1), tbb->rect.bottomRight() - QPoint(0, 1)); + QLine bottomLine = QLine(tbb->rect.bottomLeft(), tbb->rect.bottomRight()); + + QBrush border = option->palette.shadow(); + qBrushSetAlphaF(&border, qreal(0.4)); + QBrush innerTopLeft = option->palette.shadow(); + qBrushSetAlphaF(&innerTopLeft, qreal(0.075)); + QBrush innerBottomRight = option->palette.shadow(); + qBrushSetAlphaF(&innerBottomRight, qreal(0.23)); + QBrush corner = option->palette.shadow(); + qBrushSetAlphaF(&corner, 0.25); + + if (tbb->shape == QTabBar::RoundedSouth) + painter->setPen(QPen(corner, 0)); + else + painter->setPen(QPen(border, 0)); + painter->drawLine(topLine); + + if (tbb->shape != QTabBar::RoundedSouth) + painter->setPen(QPen(innerTopLeft, 0)); + else + painter->setPen(QPen(border, 0)); + painter->drawLine(bottomLine); + + painter->restore(); + } + break ; +#endif // QT_NO_TABBAR +#ifndef QT_NO_GROUPBOX + case PE_FrameGroupBox: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + QStyleOptionFrameV2 frameV2(*frame); + if (frameV2.features & QStyleOptionFrameV2::Flat) { + QPen oldPen = painter->pen(); + painter->setPen(borderColor); + painter->drawLine(frameV2.rect.topLeft(), frameV2.rect.topRight()); + painter->setPen(oldPen); + } else { + frameV2.state &= ~(State_Sunken | State_HasFocus); + proxy()->drawPrimitive(PE_Frame, &frameV2, painter, widget); + } + } + break; +#endif // QT_NO_GROUPBOX + case PE_Frame: { + QFrame::Shadow shadow = QFrame::Plain; + if (option->state & State_Sunken) + shadow = QFrame::Sunken; + else if (option->state & State_Raised) + shadow = QFrame::Raised; + qt_plastique_draw_frame(painter, option->rect, option, shadow); + break; + } +#ifndef QT_NO_LINEEDIT + case PE_FrameLineEdit: + qt_plastique_draw_frame(painter, option->rect, option, QFrame::Sunken); + break; + case PE_PanelLineEdit: + if (const QStyleOptionFrame *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + // Panel of a line edit inside combo box or spin box is drawn in CC_ComboBox and CC_SpinBox + if (widget) { +#ifndef QT_NO_SPINBOX + // Spinbox doesn't need a separate palette for the lineedit + if (qobject_cast<const QAbstractSpinBox *>(widget->parentWidget())) + break; +#endif + } + + painter->save(); + + // Fill the line edit insides + QRect filledRect = lineEdit->rect.adjusted(1, 1, -1, -1); + QBrush baseBrush = qMapBrushToRect(lineEdit->palette.base(), filledRect); + painter->setBrushOrigin(filledRect.topLeft()); + painter->fillRect(filledRect.adjusted(1, 1, -1, -1), baseBrush); + + painter->setPen(QPen(baseBrush, 0)); + const QLine lines[4] = { + QLine(filledRect.left(), filledRect.top() + 1, + filledRect.left(), filledRect.bottom() - 1), + QLine(filledRect.right(), filledRect.top() + 1, + filledRect.right(), filledRect.bottom() - 1), + QLine(filledRect.left() + 1, filledRect.top(), + filledRect.right() - 1, filledRect.top()), + QLine(filledRect.left() + 1, filledRect.bottom(), + filledRect.right() - 1, filledRect.bottom()) }; + painter->drawLines(lines, 4); + + if (lineEdit->lineWidth != 0) + qt_plastique_draw_frame(painter, option->rect, option, QFrame::Sunken); + + painter->restore(); + break; + } +#endif // QT_NO_LINEEDIT + case PE_FrameDockWidget: + case PE_FrameMenu: + case PE_FrameStatusBarItem: { + // Draws the frame around a popup menu. + QPen oldPen = painter->pen(); + painter->setPen(borderColor); + painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); + painter->setPen(alphaCornerColor); + const QPoint points[4] = { + QPoint(option->rect.topLeft()), + QPoint(option->rect.topRight()), + QPoint(option->rect.bottomLeft()), + QPoint(option->rect.bottomRight()) }; + painter->drawPoints(points, 4); + painter->setPen(oldPen); + break; + } +#ifdef QT3_SUPPORT + case PE_Q3DockWindowSeparator: { + QPen oldPen = painter->pen(); + painter->setPen(alphaCornerColor); + QRect rect = option->rect; + if (option->state & State_Horizontal) { + painter->drawLine(rect.right(), rect.top() + 2, rect.right(), rect.bottom() - 1); + } else { + painter->drawLine(rect.left() + 2, rect.bottom(), rect.right() - 1, rect.bottom()); + } + painter->setPen(oldPen); + break; + } + case PE_Q3Separator: { + QPen oldPen = painter->pen(); + painter->setPen(alphaCornerColor); + if ((option->state & State_Horizontal) == 0) + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + else + painter->drawLine(option->rect.topRight(), option->rect.bottomRight()); + painter->setPen(option->palette.background().color().lighter(104)); + if ((option->state & State_Horizontal) == 0) + painter->drawLine(option->rect.topLeft(), option->rect.topRight()); + else + painter->drawLine(option->rect.topLeft(), option->rect.bottomLeft()); + painter->setPen(oldPen); + break; + } +#endif // QT3_SUPPORT +#ifndef QT_NO_MAINWINDOW + case PE_PanelMenuBar: + if ((widget && qobject_cast<const QMainWindow *>(widget->parentWidget())) +#ifdef QT3_SUPPORT + || (widget && widget->parentWidget() && widget->parentWidget()->inherits("Q3MainWindow")) +#endif + ) { + // Draws the light line above and the dark line below menu bars and + // tool bars. + QPen oldPen = painter->pen(); + if (element == PE_PanelMenuBar || (option->state & State_Horizontal)) { + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.left(), option->rect.bottom(), + option->rect.right(), option->rect.bottom()); + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.left(), option->rect.top(), + option->rect.right(), option->rect.top()); + } else { + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.left(), option->rect.top(), + option->rect.left(), option->rect.bottom()); + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.right(), option->rect.top(), + option->rect.right(), option->rect.bottom()); + } + painter->setPen(oldPen); + } + break; +#endif // QT_NO_MAINWINDOW + case PE_IndicatorHeaderArrow: { + bool usedAntialiasing = painter->renderHints() & QPainter::Antialiasing; + if (!usedAntialiasing) + painter->setRenderHint(QPainter::Antialiasing); + QWindowsStyle::drawPrimitive(element, option, painter, widget); + if (!usedAntialiasing) + painter->setRenderHint(QPainter::Antialiasing, false); + break; + } + case PE_PanelButtonTool: + // Draws a tool button (f.ex., in QToolBar and QTabBar) + if ((option->state & State_Enabled || option->state & State_On) || !(option->state & State_AutoRaise)) + qt_plastique_drawShadedPanel(painter, option, true, widget); + break; +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarHandle: { + QPixmap cache; + QRect rect = option->rect; +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowHandle") && widget->parentWidget()->inherits("Q3DockWindow")) { + if (!(option->state & State_Horizontal)) + rect.adjust(2, 0, -2, 0); + } +#endif + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("toolbarhandle"), option, rect.size()); + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(rect.size()); + cache.fill(Qt::transparent); + QPainter cachePainter(&cache); + QRect cacheRect(QPoint(0, 0), rect.size()); + if (widget) + cachePainter.fillRect(cacheRect, option->palette.brush(widget->backgroundRole())); + else + cachePainter.fillRect(cacheRect, option->palette.background()); + + QImage handle(qt_toolbarhandle); + alphaCornerColor.setAlpha(170); + handle.setColor(1, alphaCornerColor.rgba()); + handle.setColor(2, mergedColors(alphaCornerColor, option->palette.light().color()).rgba()); + handle.setColor(3, option->palette.light().color().rgba()); + + if (option->state & State_Horizontal) { + int nchunks = cacheRect.height() / handle.height(); + int indent = (cacheRect.height() - (nchunks * handle.height())) / 2; + for (int i = 0; i < nchunks; ++i) + cachePainter.drawImage(QPoint(cacheRect.left() + 3, cacheRect.top() + indent + i * handle.height()), + handle); + } else { + int nchunks = cacheRect.width() / handle.width(); + int indent = (cacheRect.width() - (nchunks * handle.width())) / 2; + for (int i = 0; i < nchunks; ++i) + cachePainter.drawImage(QPoint(cacheRect.left() + indent + i * handle.width(), cacheRect.top() + 3), + handle); + } + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(rect.topLeft(), cache); + break; + } + case PE_IndicatorToolBarSeparator: { + QPen oldPen = painter->pen(); + painter->setPen(alphaCornerColor); + if (option->state & State_Horizontal) { + painter->drawLine(option->rect.left(), option->rect.top() + 1, option->rect.left(), option->rect.bottom() - 2); + painter->setPen(option->palette.base().color()); + painter->drawLine(option->rect.right(), option->rect.top() + 1, option->rect.right(), option->rect.bottom() - 2); + } else { + painter->drawLine(option->rect.left() + 1, option->rect.top(), option->rect.right() - 2, option->rect.top()); + painter->setPen(option->palette.base().color()); + painter->drawLine(option->rect.left() + 1, option->rect.bottom(), option->rect.right() - 2, option->rect.bottom()); + } + painter->setPen(oldPen); + break; + } +#endif // QT_NO_TOOLBAR + case PE_PanelButtonCommand: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool sunken = (button->state & State_Sunken) || (button->state & State_On); + if ((button->features & QStyleOptionButton::Flat) && !sunken) + break; + + bool defaultButton = (button->features & (QStyleOptionButton::DefaultButton + | QStyleOptionButton::AutoDefaultButton)); + + BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("pushbutton-%1").arg(defaultButton)) + + QPen oldPen = p->pen(); + bool hover = (button->state & State_Enabled) && (button->state & State_MouseOver); + + // Give the painter a different brush origin for sunken buttons + if (sunken) { + // ### No such function + // p->setPenOrigin(rect.left() + 1, rect.top() + 1); + p->setBrushOrigin(rect.left() + 1, rect.top() + 1); + } + + // Draw border + qt_plastique_draw_frame(p, rect, option); + + // Fill the panel + QRectF fillRect = rect.adjusted(2, 2, -2, -2); + + // Button colors + QBrush alphaCornerBrush = qMapBrushToRect(qBrushDark(option->palette.button(), 165), rect); + qBrushSetAlphaF(&alphaCornerBrush, 0.5); + QBrush buttonGradientBrush; + QBrush leftLineGradientBrush; + QBrush rightLineGradientBrush; + QBrush sunkenButtonGradientBrush; + QBrush sunkenLeftLineGradientBrush; + QBrush sunkenRightLineGradientBrush; + QBrush buttonBrush = qMapBrushToRect(option->palette.button(), rect); + if (buttonBrush.gradient() || !buttonBrush.texture().isNull()) { + buttonGradientBrush = buttonBrush; + sunkenButtonGradientBrush = qBrushDark(buttonBrush, 108); + leftLineGradientBrush = qBrushLight(buttonBrush, 105); + rightLineGradientBrush = qBrushDark(buttonBrush, 105); + sunkenLeftLineGradientBrush = qBrushDark(buttonBrush, 110); + sunkenRightLineGradientBrush = qBrushDark(buttonBrush, 106); + } else { + // Generate gradients + QLinearGradient buttonGradient(rect.topLeft(), rect.bottomLeft()); + if (hover) { + buttonGradient.setColorAt(0.0, mergedColors(option->palette.highlight().color(), + buttonBrush.color().lighter(104), 6)); + buttonGradient.setColorAt(1.0, mergedColors(option->palette.highlight().color(), + buttonBrush.color().darker(110), 6)); + } else { + buttonGradient.setColorAt(0.0, buttonBrush.color().lighter(104)); + buttonGradient.setColorAt(1.0, buttonBrush.color().darker(110)); + } + buttonGradientBrush = QBrush(buttonGradient); + + QLinearGradient buttonGradient2(rect.topLeft(), rect.bottomLeft()); + buttonGradient2.setColorAt(0.0, buttonBrush.color().darker(113)); + buttonGradient2.setColorAt(1.0, buttonBrush.color().darker(103)); + sunkenButtonGradientBrush = QBrush(buttonGradient2); + + QLinearGradient buttonGradient3(rect.topLeft(), rect.bottomLeft()); + buttonGradient3.setColorAt(0.0, buttonBrush.color().lighter(105)); + buttonGradient3.setColorAt(1.0, buttonBrush.color()); + leftLineGradientBrush = QBrush(buttonGradient3); + + QLinearGradient buttonGradient4(rect.topLeft(), rect.bottomLeft()); + buttonGradient4.setColorAt(0.0, buttonBrush.color()); + buttonGradient4.setColorAt(1.0, buttonBrush.color().darker(110)); + rightLineGradientBrush = QBrush(buttonGradient4); + + QLinearGradient buttonGradient5(rect.topLeft(), rect.bottomLeft()); + buttonGradient5.setColorAt(0.0, buttonBrush.color().darker(113)); + buttonGradient5.setColorAt(1.0, buttonBrush.color().darker(107)); + sunkenLeftLineGradientBrush = QBrush(buttonGradient5); + + QLinearGradient buttonGradient6(rect.topLeft(), rect.bottomLeft()); + buttonGradient6.setColorAt(0.0, buttonBrush.color().darker(108)); + buttonGradient6.setColorAt(1.0, buttonBrush.color().darker(103)); + sunkenRightLineGradientBrush = QBrush(buttonGradient6); + } + + // Main fill + p->fillRect(fillRect, + qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, rect)); + + // Top line + p->setPen(QPen(qBrushLight(qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, rect), 105), 0)); + p->drawLine(QPointF(rect.left() + 2, rect.top() + 1), + QPointF(rect.right() - 2, rect.top() + 1)); + + // Bottom line + p->setPen(QPen(qBrushDark(qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, rect), 105), 0)); + p->drawLine(QPointF(rect.left() + 2, rect.bottom() - 1), + QPointF(rect.right() - 2, rect.bottom() - 1)); + + // Left line + p->setPen(QPen(qMapBrushToRect(sunken ? sunkenLeftLineGradientBrush + : leftLineGradientBrush, rect), 1)); + p->drawLine(QPointF(rect.left() + 1, rect.top() + 2), + QPointF(rect.left() + 1, rect.bottom() - 2)); + + // Right line + p->setPen(QPen(qMapBrushToRect(sunken ? sunkenRightLineGradientBrush + : rightLineGradientBrush, rect), 1)); + p->drawLine(QPointF(rect.right() - 1, rect.top() + 2), + QPointF(rect.right() - 1, rect.bottom() - 2)); + + // Hovering + if (hover && !sunken) { + QBrush hover = qMapBrushToRect(option->palette.highlight(), rect); + QBrush hoverOuter = hover; + qBrushSetAlphaF(&hoverOuter, qreal(0.7)); + + QLine lines[2]; + + p->setPen(QPen(hoverOuter, 0)); + lines[0] = QLine(rect.left() + 1, rect.top() + 1, rect.right() - 1, rect.top() + 1); + lines[1] = QLine(rect.left() + 1, rect.bottom() - 1, rect.right() - 1, rect.bottom() - 1); + p->drawLines(lines, 2); + + QBrush hoverInner = hover; + qBrushSetAlphaF(&hoverInner, qreal(0.45)); + p->setPen(QPen(hoverInner, 0)); + lines[0] = QLine(rect.left() + 1, rect.top() + 2, rect.right() - 1, rect.top() + 2); + lines[1] = QLine(rect.left() + 1, rect.bottom() - 2, rect.right() - 1, rect.bottom() - 2); + p->drawLines(lines, 2); + + QBrush hoverSide = hover; + qBrushSetAlphaF(&hoverSide, qreal(0.075)); + p->setPen(QPen(hoverSide, 0)); + lines[0] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, rect.bottom() - 2); + lines[1] = QLine(rect.right() - 1, rect.top() + 2, rect.right() - 1, rect.bottom() - 2); + p->drawLines(lines, 2); + } + + p->setPen(oldPen); + + END_STYLE_PIXMAPCACHE + } + break; + case PE_IndicatorCheckBox: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + BEGIN_STYLE_PIXMAPCACHE(QLatin1String("checkbox")) + + p->save(); + + // Outline + QBrush border = option->palette.shadow(); + qBrushSetAlphaF(&border, qreal(0.4)); + p->setPen(QPen(border, 0)); + const QLine lines[4] = { + QLine(rect.left() + 1, rect.top(), rect.right() - 1, rect.top()), + QLine(rect.left() + 1, rect.bottom(), rect.right() - 1, rect.bottom()), + QLine(rect.left(), rect.top() + 1, rect.left(), rect.bottom() - 1), + QLine(rect.right(), rect.top() + 1, rect.right(), rect.bottom() - 1) }; + p->drawLines(lines, 4); + + QBrush corner = option->palette.shadow(); + qBrushSetAlphaF(&corner, qreal(0.2)); + p->setPen(QPen(corner, 0)); + const QPoint points[4] = { + rect.topLeft(), rect.topRight(), + rect.bottomLeft(), rect.bottomRight() }; + p->drawPoints(points, 4); + + // Fill + QBrush baseBrush = qMapBrushToRect(button->palette.base(), rect); + if (!baseBrush.gradient() && baseBrush.texture().isNull()) { + QLinearGradient gradient(rect.center().x(), rect.top(), rect.center().x(), rect.bottom()); + gradient.setColorAt(0, baseBrush.color()); + gradient.setColorAt(1, baseBrush.color().darker(105)); + baseBrush = gradient; + } + p->fillRect(rect.adjusted(1, 1, -1, -1), baseBrush); + + // Hover + if ((button->state & State_Enabled) && (button->state & State_MouseOver)) { + QBrush pen = qMapBrushToRect(button->palette.highlight(), rect); + qBrushSetAlphaF(&pen, qreal(0.8)); + p->setPen(QPen(pen, 0)); + p->drawRect(rect.adjusted(1, 1, -2, -2)); + qBrushSetAlphaF(&pen, 0.5); + p->setPen(QPen(pen, 0)); + p->drawRect(rect.adjusted(2, 2, -3, -3)); + + qBrushSetAlphaF(&pen, qreal(0.2)); + p->setBrush(pen); + p->drawRect(rect.adjusted(2, 2, -3, -3)); + } + + // Indicator + bool on = button->state & State_On; + bool sunken = button->state & State_Sunken; + bool unchanged = button->state & State_NoChange; + bool enabled = button->state & State_Enabled; + if (on || (enabled && sunken) || unchanged) { + p->setRenderHint(QPainter::Antialiasing); + QBrush pointBrush = qMapBrushToRect(button->palette.text(), rect); + if (sunken) + qBrushSetAlphaF(&pointBrush, qreal(0.5)); + else if (unchanged) + qBrushSetAlphaF(&pointBrush, qreal(0.3)); + p->setPen(QPen(pointBrush, 3)); + const QLine lines[2] = { + QLine(rect.left() + 4, rect.top() + 4, rect.right() - 3, rect.bottom() - 3), + QLine(rect.right() - 3, rect.top() + 4, rect.left() + 4, rect.bottom() - 3) }; + p->drawLines(lines, 2); + } + + p->restore(); + END_STYLE_PIXMAPCACHE + } + break; + case PE_IndicatorRadioButton: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + BEGIN_STYLE_PIXMAPCACHE(QLatin1String("radiobutton")) + + p->save(); + p->setRenderHint(QPainter::Antialiasing); + + // The the filled ellipse + QBrush border = qMapBrushToRect(option->palette.shadow(), rect); + qBrushSetAlphaF(&border, qreal(0.51)); + p->setPen(QPen(border, 0)); + + QBrush baseBrush = qMapBrushToRect(button->palette.base(), rect); + if (!baseBrush.gradient() && baseBrush.texture().isNull()) { + QLinearGradient gradient(rect.center().x(), rect.top(), rect.center().x(), rect.bottom()); + gradient.setColorAt(0, baseBrush.color()); + gradient.setColorAt(1, baseBrush.color().darker(105)); + baseBrush = gradient; + } + p->setBrush(baseBrush); + p->drawEllipse(QRectF(rect).adjusted(1, 1, -1, -1)); + + // Hover + if ((button->state & State_Enabled) && (button->state & State_MouseOver)) { + QBrush pen = qMapBrushToRect(button->palette.highlight(), rect); + qBrushSetAlphaF(&pen, qreal(0.8)); + p->setPen(QPen(pen, 0)); + qBrushSetAlphaF(&pen, qreal(0.2)); + p->setBrush(pen); + p->drawEllipse(QRectF(rect).adjusted(2, 2, -2, -2)); + } + + // Indicator + bool on = button->state & State_On; + bool sunken = button->state & State_Sunken; + bool enabled = button->state & State_Enabled; + if (on || (enabled && sunken)) { + p->setPen(Qt::NoPen); + QBrush pointBrush = qMapBrushToRect(button->palette.text(), rect); + if (sunken) + qBrushSetAlphaF(&pointBrush, 0.5); + p->setBrush(pointBrush); + p->drawEllipse(QRectF(rect).adjusted(3, 3, -3, -3)); + } + + p->restore(); + END_STYLE_PIXMAPCACHE + } + break; +#ifndef QT_NO_DOCKWIDGET + case PE_IndicatorDockWidgetResizeHandle: + if ((option->state & State_Enabled) && (option->state & State_MouseOver)) + painter->fillRect(option->rect, QColor(255, 255, 255, 128)); + if (option->state & State_Horizontal) { + int width = option->rect.width() / 3; + QRect rect(option->rect.center().x() - width / 2, + option->rect.top() + (option->rect.height() / 2) - 1, width, 3); + qt_plastique_draw_handle(painter, option, rect, Qt::Vertical, widget); + } else { + int height = option->rect.height() / 3; + QRect rect(option->rect.left() + (option->rect.width() / 2 - 1), + option->rect.center().y() - height / 2, 3, height); + qt_plastique_draw_handle(painter, option, rect, Qt::Horizontal, widget); + } + break; +#endif // QT_NO_DOCKWIDGET + case PE_IndicatorViewItemCheck: { + QStyleOptionButton button; + button.QStyleOption::operator=(*option); + button.state &= ~State_MouseOver; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget); + break; + } + case PE_FrameWindow: { + painter->save(); + bool active = (option->state & State_Active); + int titleBarStop = option->rect.top() + proxy()->pixelMetric(PM_TitleBarHeight, option, widget); + + QPalette palette = option->palette; + if (!active) + palette.setCurrentColorGroup(QPalette::Disabled); + + // Frame and rounded corners + painter->setPen(mergedColors(palette.highlight().color(), Qt::black, 50)); + + QLine lines[3]; + QPoint points[4]; + + // bottom border line + lines[0] = QLine(option->rect.left() + 1, option->rect.bottom(), option->rect.right() - 1, option->rect.bottom()); + + // bottom left and right side border lines + lines[1] = QLine(option->rect.left(), titleBarStop, option->rect.left(), option->rect.bottom() - 1); + lines[2] = QLine(option->rect.right(), titleBarStop, option->rect.right(), option->rect.bottom() - 1); + painter->drawLines(lines, 3); + points[0] = QPoint(option->rect.left() + 1, option->rect.bottom() - 1); + points[1] = QPoint(option->rect.right() - 1, option->rect.bottom() - 1); + painter->drawPoints(points, 2); + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindow")) { + // also draw the frame on the title bar + lines[0] = QLine(option->rect.left() + 1, option->rect.top(), + option->rect.right() - 1, option->rect.top()); + lines[1] = QLine(option->rect.left(), option->rect.top() + 1, + option->rect.left(), titleBarStop); + lines[2] = QLine(option->rect.right(), option->rect.top() + 1, + option->rect.right(), titleBarStop); + painter->drawLines(lines, 3); + } +#endif + + // alpha corners + painter->setPen(mergedColors(palette.highlight().color(), palette.background().color(), 55)); + points[0] = QPoint(option->rect.left() + 2, option->rect.bottom() - 1); + points[1] = QPoint(option->rect.left() + 1, option->rect.bottom() - 2); + points[2] = QPoint(option->rect.right() - 2, option->rect.bottom() - 1); + points[3] = QPoint(option->rect.right() - 1, option->rect.bottom() - 2); + painter->drawPoints(points, 4); + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindow")) { + // also draw the frame on the title bar + points[0] = option->rect.topLeft(); + points[1] = option->rect.topRight(); + painter->drawPoints(points, 2); + } +#endif + + // upper and lower left inner + painter->setPen(active ? mergedColors(palette.highlight().color(), palette.background().color()) : palette.background().color().darker(120)); + painter->drawLine(option->rect.left() + 1, titleBarStop, option->rect.left() + 1, option->rect.bottom() - 2); + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindow")) { + // also draw the frame on the title bar + lines[0] = QLine(option->rect.left() + 1, option->rect.top() + 1, + option->rect.left() + 1, titleBarStop); + lines[1] = QLine(option->rect.right() - 1, option->rect.top() + 1, + option->rect.right() - 1, titleBarStop); + lines[2] = QLine(option->rect.left() + 1, option->rect.top() + 1, + option->rect.right() - 1, option->rect.top() + 1); + painter->drawLines(lines, 3); + } +#endif + + painter->setPen(active ? mergedColors(palette.highlight().color(), palette.background().color(), 57) : palette.background().color().darker(130)); + lines[0] = QLine(option->rect.right() - 1, titleBarStop, option->rect.right() - 1, option->rect.bottom() - 2); + lines[1] = QLine(option->rect.left() + 1, option->rect.bottom() - 1, option->rect.right() - 1, option->rect.bottom() - 1); + painter->drawLines(lines, 2); + + painter->restore(); + } + break; + case PE_IndicatorBranch: { + 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; + 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) + 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)) + painter->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush); + + if (option->state & State_Children) { + painter->save(); + QPoint center = option->rect.center(); + // border + QRect fullRect(center.x() - 4, center.y() - 4, 9, 9); + painter->setPen(borderColor); + + const QLine lines[4] = { + QLine(fullRect.left() + 1, fullRect.top(), + fullRect.right() - 1, fullRect.top()), + QLine(fullRect.left() + 1, fullRect.bottom(), + fullRect.right() - 1, fullRect.bottom()), + QLine(fullRect.left(), fullRect.top() + 1, + fullRect.left(), fullRect.bottom() - 1), + QLine(fullRect.right(), fullRect.top() + 1, + fullRect.right(), fullRect.bottom() - 1) }; + painter->drawLines(lines, 4); + + // "antialiased" corners + painter->setPen(alphaCornerColor); + const QPoint points[4] = { + fullRect.topLeft(), + fullRect.topRight(), + fullRect.bottomLeft(), + fullRect.bottomRight() }; + painter->drawPoints(points, 4); + + // fill + QRect adjustedRect = fullRect; + QRect gradientRect(adjustedRect.left() + 1, adjustedRect.top() + 1, + adjustedRect.right() - adjustedRect.left() - 1, + adjustedRect.bottom() - adjustedRect.top() - 1); + if (option->palette.base().style() == Qt::SolidPattern) { + QColor baseGradientStartColor = option->palette.base().color().darker(101); + QColor baseGradientStopColor = option->palette.base().color().darker(106); + qt_plastique_draw_gradient(painter, gradientRect, baseGradientStartColor, baseGradientStopColor); + } else { + painter->fillRect(gradientRect, option->palette.base()); + } + // draw "+" or "-" + painter->setPen(alphaTextColor); + painter->drawLine(center.x() - 2, center.y(), center.x() + 2, center.y()); + if (!(option->state & State_Open)) + painter->drawLine(center.x(), center.y() - 2, center.x(), center.y() + 2); + painter->restore(); + } + } + break; + default: + QWindowsStyle::drawPrimitive(element, option, painter, widget); + break; + } +} + +/*! + \reimp +*/ +void QPlastiqueStyle::drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + QColor borderColor = option->palette.background().color().darker(178); + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + QColor alphaTextColor = mergedColors(option->palette.background().color(), option->palette.text().color()); + + QColor gradientStartColor = option->palette.button().color().lighter(104); + QColor gradientStopColor = option->palette.button().color().darker(105); + + QColor shadowGradientStartColor = option->palette.button().color().darker(115); + QColor shadowGradientStopColor = option->palette.button().color().darker(120); + + QColor highlightedGradientStartColor = option->palette.button().color().lighter(101); + QColor highlightedGradientStopColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 85); + + QColor lightShadowGradientStartColor = highlightedGradientStartColor.lighter(105); + QColor lightShadowGradientStopColor = highlightedGradientStopColor.lighter(105); + + QColor highlightedDarkInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 35); + QColor highlightedLightInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 58); + + QColor alphaInnerColor = mergedColors(highlightedDarkInnerBorderColor, option->palette.base().color()); + QColor lightShadow = lightShadowGradientStartColor; + QColor shadow = shadowGradientStartColor; + + switch (element) { +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + + if (tab->shape != QTabBar::RoundedNorth && tab->shape != QTabBar::RoundedWest && + tab->shape != QTabBar::RoundedSouth && tab->shape != QTabBar::RoundedEast) { + QWindowsStyle::drawControl(element, option, painter, widget); + break; + } + + painter->save(); + + // Set up some convenience variables + bool disabled = !(tab->state & State_Enabled); + bool onlyTab = tab->position == QStyleOptionTab::OnlyOneTab; + bool selected = tab->state & State_Selected; + bool mouseOver = (tab->state & State_MouseOver) && !selected && !disabled; + bool previousSelected = tab->selectedPosition == QStyleOptionTab::PreviousIsSelected; + bool nextSelected = tab->selectedPosition == QStyleOptionTab::NextIsSelected; + bool leftCornerWidget = (tab->cornerWidgets & QStyleOptionTab::LeftCornerWidget); + bool reverse = (tab->direction == Qt::RightToLeft); + + int lowerTop = selected ? 0 : 3; // to make the selected tab bigger than the rest + bool atEnd = (tab->position == QStyleOptionTab::End) || onlyTab; + bool atBeginning = ((tab->position == QStyleOptionTab::Beginning) || onlyTab) + && !leftCornerWidget; + bool reverseShadow = false; + + int borderThickness = proxy()->pixelMetric(PM_TabBarBaseOverlap, tab, widget); + int marginLeft = 0; + if ((atBeginning && !selected) || (selected && leftCornerWidget && ((tab->position == QStyleOptionTab::Beginning) || onlyTab))) { + marginLeft = 1; + } + + // I've set the names based on the natural coordinate system. Vectors are used to rotate everything + // if the orientation of the tab bare is different than north. + { + // Coordinates of corners of rectangle for transformation + QPoint topLeft; + QPoint topRight; + QPoint bottomLeft; + QPoint bottomRight; + + // Fill with normalized vectors in the direction of the coordinate system + // (down and right should be complement of up and left, or it will look odd) + QPoint vectorUp; + QPoint vectorDown; + QPoint vectorLeft; + QPoint vectorRight; + + QBrush border = option->palette.shadow(); + qBrushSetAlphaF(&border, qreal(0.4)); + QBrush innerTopLeft = option->palette.shadow(); + qBrushSetAlphaF(&innerTopLeft, qreal(0.075)); + QBrush innerBottomRight = option->palette.shadow(); + qBrushSetAlphaF(&innerBottomRight, qreal(0.23)); + QBrush corner = option->palette.shadow(); + qBrushSetAlphaF(&corner, qreal(0.25)); + + QBrush baseColor1; + QBrush baseColor2; + + switch (tab->shape) { + case QTabBar::RoundedNorth: + vectorUp = QPoint(0, -1); + vectorDown = QPoint(0, 1); + + if (reverse) { + vectorLeft = QPoint(1, 0); + vectorRight = QPoint(-1, 0); + reverseShadow = true; + } else { + vectorLeft = QPoint(-1, 0); + vectorRight = QPoint(1, 0); + } + + if (reverse) { + topLeft = tab->rect.topRight(); + topRight = tab->rect.topLeft(); + bottomLeft = tab->rect.bottomRight(); + bottomRight = tab->rect.bottomLeft(); + } else { + topLeft = tab->rect.topLeft(); + topRight = tab->rect.topRight(); + bottomLeft = tab->rect.bottomLeft(); + bottomRight = tab->rect.bottomRight(); + } + + + baseColor1 = border; + baseColor2 = innerTopLeft; + break ; + case QTabBar::RoundedWest: + vectorUp = QPoint(-1, 0); + vectorDown = QPoint(1, 0); + vectorLeft = QPoint(0, -1); + vectorRight = QPoint(0, 1); + + topLeft = tab->rect.topLeft(); + topRight = tab->rect.bottomLeft(); + bottomLeft = tab->rect.topRight(); + bottomRight = tab->rect.bottomRight(); + + baseColor1 = border; + baseColor2 = innerTopLeft; + break ; + case QTabBar::RoundedEast: + vectorUp = QPoint(1, 0); + vectorDown = QPoint(-1, 0); + vectorLeft = QPoint(0, -1); + vectorRight = QPoint(0, 1); + + topLeft = tab->rect.topRight(); + topRight = tab->rect.bottomRight(); + bottomLeft = tab->rect.topLeft(); + bottomRight = tab->rect.bottomLeft(); + + baseColor1 = border; + baseColor2 = innerBottomRight; + break ; + case QTabBar::RoundedSouth: + vectorUp = QPoint(0, 1); + vectorDown = QPoint(0, -1); + + if (reverse) { + vectorLeft = QPoint(1, 0); + vectorRight = QPoint(-1, 0); + reverseShadow = true; + + topLeft = tab->rect.bottomRight(); + topRight = tab->rect.bottomLeft(); + bottomLeft = tab->rect.topRight(); + bottomRight = tab->rect.topLeft(); + } else { + vectorLeft = QPoint(-1, 0); + vectorRight = QPoint(1, 0); + + topLeft = tab->rect.bottomLeft(); + topRight = tab->rect.bottomRight(); + bottomLeft = tab->rect.topLeft(); + bottomRight = tab->rect.topRight(); + } + + baseColor1 = border; + baseColor2 = innerBottomRight; + break ; + default: + break; + } + + // Make the tab smaller when it's at the end, so that we are able to draw the corner + if (atEnd) { + topRight += vectorLeft; + bottomRight += vectorLeft; + } + + { + // Outer border + QLine topLine; + { + QPoint adjustTopLineLeft = (vectorRight * (marginLeft + (previousSelected ? 0 : 1))) + + (vectorDown * lowerTop); + QPoint adjustTopLineRight = (vectorDown * lowerTop); + if (atBeginning || selected) + adjustTopLineLeft += vectorRight; + if (atEnd || selected) + adjustTopLineRight += 2 * vectorLeft; + + topLine = QLine(topLeft + adjustTopLineLeft, topRight + adjustTopLineRight); + } + + QLine leftLine; + { + QPoint adjustLeftLineTop = (vectorRight * marginLeft) + (vectorDown * (lowerTop + 1)); + QPoint adjustLeftLineBottom = (vectorRight * marginLeft) + (vectorUp * borderThickness); + if (atBeginning || selected) + adjustLeftLineTop += vectorDown; // Make place for rounded corner + if (atBeginning && selected) + adjustLeftLineBottom += borderThickness * vectorDown; + else if (selected) + adjustLeftLineBottom += vectorUp; + + leftLine = QLine(topLeft + adjustLeftLineTop, bottomLeft + adjustLeftLineBottom); + } + + QLine rightLine; + { + QPoint adjustRightLineTop = vectorDown * (2 + lowerTop); + QPoint adjustRightLineBottom = vectorUp * borderThickness; + if (selected) + adjustRightLineBottom += vectorUp; + + rightLine = QLine(topRight + adjustRightLineTop, bottomRight + adjustRightLineBottom); + } + + // Background + QPoint startPoint = topLine.p1() + vectorDown + vectorLeft; + if (mouseOver) + startPoint += vectorDown; + QPoint endPoint = rightLine.p2(); + + if (tab->state & State_Enabled) { + QRect fillRect = QRect(startPoint, endPoint).normalized(); + if (fillRect.isValid()) { + if (selected) { + fillRect = QRect(startPoint, endPoint + vectorLeft + vectorDown * 3).normalized(); + painter->fillRect(fillRect, option->palette.window()); + + // Connect to the base + painter->setPen(QPen(option->palette.window(), 0)); + QVarLengthArray<QPoint, 6> points; + points.append(rightLine.p2() + vectorDown); + points.append(rightLine.p2() + vectorDown + vectorDown); + points.append(rightLine.p2() + vectorDown + vectorDown + vectorRight); + if (tab->position != QStyleOptionTab::Beginning) { + points.append(leftLine.p2() + vectorDown); + points.append(leftLine.p2() + vectorDown + vectorDown); + points.append(leftLine.p2() + vectorDown + vectorDown + vectorLeft); + } + painter->drawPoints(points.constData(), points.size()); + } else { + QBrush buttonGradientBrush; + QBrush buttonBrush = qMapBrushToRect(option->palette.button(), fillRect); + if (buttonBrush.gradient() || !buttonBrush.texture().isNull()) { + buttonGradientBrush = buttonBrush; + } else { + // Generate gradients + QLinearGradient buttonGradient(fillRect.topLeft(), fillRect.bottomLeft()); + buttonGradient.setColorAt(0.0, buttonBrush.color().lighter(104)); + buttonGradient.setColorAt(1.0, buttonBrush.color().darker(110)); + buttonGradientBrush = QBrush(buttonGradient); + } + + painter->fillRect(fillRect, buttonGradientBrush); + } + } + } + + QPoint rightCornerDot = topRight + vectorLeft + (lowerTop + 1)*vectorDown; + QPoint leftCornerDot = topLeft + (marginLeft + 1)*vectorRight + (lowerTop + 1)*vectorDown; + QPoint bottomRightConnectToBase = rightLine.p2() + vectorRight + vectorDown; + QPoint bottomLeftConnectToBase = leftLine.p2() + vectorLeft + vectorDown; + + painter->setPen(QPen(border, 0)); + + QVarLengthArray<QLine, 3> lines; + QVarLengthArray<QPoint, 7> points; + + lines.append(topLine); + + if (mouseOver) { + painter->drawLines(lines.constData(), lines.count()); + lines.clear(); + + QLine secondHoverLine = QLine(topLine.p1() + vectorDown * 2 + vectorLeft, topLine.p2() + vectorDown * 2 + vectorRight); + painter->setPen(highlightedLightInnerBorderColor); + painter->drawLine(secondHoverLine); + } + + if (mouseOver) + painter->setPen(QPen(border, 0)); + + if (!previousSelected) + lines.append(leftLine); + if (atEnd || selected) { + lines.append(rightLine); + points.append(rightCornerDot); + } + if (atBeginning || selected) + points.append(leftCornerDot); + if (selected) { + points.append(bottomRightConnectToBase); + points.append(bottomLeftConnectToBase); + } + if (lines.size() > 0) { + painter->drawLines(lines.constData(), lines.size()); + lines.clear(); + } + if (points.size() > 0) { + painter->drawPoints(points.constData(), points.size()); + points.clear(); + } + + // Antialiasing + painter->setPen(QPen(corner, 0)); + if (atBeginning || selected) + points.append(topLine.p1() + vectorLeft); + if (!previousSelected) + points.append(leftLine.p1() + vectorUp); + if (atEnd || selected) { + points.append(topLine.p2() + vectorRight); + points.append(rightLine.p1() + vectorUp); + } + + if (selected) { + points.append(bottomRightConnectToBase + vectorLeft); + if (!atBeginning) { + points.append(bottomLeftConnectToBase + vectorRight); + + if (((tab->position == QStyleOptionTab::Beginning) || onlyTab) && leftCornerWidget) { + // A special case: When the first tab is selected and + // has a left corner widget, it needs to do more work + // to connect to the base + QPoint p1 = bottomLeftConnectToBase + vectorDown; + + points.append(p1); + } + } + } + if (points.size() > 0) { + painter->drawPoints(points.constData(), points.size()); + points.clear(); + } + + // Inner border + QLine innerTopLine = QLine(topLine.p1() + vectorDown, topLine.p2() + vectorDown); + if (!selected) { + QLinearGradient topLineGradient(innerTopLine.p1(),innerTopLine.p2()); + topLineGradient.setColorAt(0, lightShadowGradientStartColor); + topLineGradient.setColorAt(1, lightShadowGradientStopColor); + painter->setPen(QPen(mouseOver ? QBrush(highlightedDarkInnerBorderColor) : QBrush(topLineGradient), 1)); + } else { + painter->setPen(QPen(innerTopLeft, 0)); + } + painter->drawLine(innerTopLine); + + QLine innerLeftLine = QLine(leftLine.p1() + vectorRight + vectorDown, leftLine.p2() + vectorRight); + QLine innerRightLine = QLine(rightLine.p1() + vectorLeft + vectorDown, rightLine.p2() + vectorLeft); + + if (selected) { + innerRightLine = QLine(innerRightLine.p1() + vectorUp, innerRightLine.p2()); + innerLeftLine = QLine(innerLeftLine.p1() + vectorUp, innerLeftLine.p2()); + } + + if (selected || atBeginning) { + QBrush leftLineGradientBrush; + QRect rect = QRect(innerLeftLine.p1(), innerLeftLine.p2()).normalized(); + QBrush buttonBrush = qMapBrushToRect(option->palette.button(), rect); + if (buttonBrush.gradient() || !buttonBrush.texture().isNull()) { + leftLineGradientBrush = qBrushLight(buttonBrush, 105); + } else { + QLinearGradient buttonGradient3(rect.topLeft(), rect.bottomLeft()); + buttonGradient3.setColorAt(0.0, buttonBrush.color().lighter(105)); + buttonGradient3.setColorAt(1.0, buttonBrush.color()); + leftLineGradientBrush = QBrush(buttonGradient3); + } + + if (!selected) + painter->setPen(QPen(leftLineGradientBrush, 0)); + + // Assume the sun is on the same side in Right-To-Left layouts and draw the + // light shadow on the left side always (the right line is on the left side in + // reverse layouts for north and south) + if (reverseShadow) + painter->drawLine(innerRightLine); + else + painter->drawLine(innerLeftLine); + } + + if (atEnd || selected) { + if (!selected) { + QBrush rightLineGradientBrush; + QRect rect = QRect(innerRightLine.p1(), innerRightLine.p2()).normalized(); + QBrush buttonBrush = qMapBrushToRect(option->palette.button(), rect); + if (buttonBrush.gradient() || !buttonBrush.texture().isNull()) { + rightLineGradientBrush = qBrushDark(buttonBrush, 105); + } else { + QLinearGradient buttonGradient4(rect.topLeft(), rect.bottomLeft()); + buttonGradient4.setColorAt(0.0, buttonBrush.color()); + buttonGradient4.setColorAt(1.0, buttonBrush.color().darker(110)); + rightLineGradientBrush = QBrush(buttonGradient4); + } + + painter->setPen(QPen(rightLineGradientBrush, 0)); + } else { + painter->setPen(QPen(innerBottomRight, 0)); + } + + if (reverseShadow) + painter->drawLine(innerLeftLine); + else + painter->drawLine(innerRightLine); + } + + + // Base + QLine baseLine = QLine(bottomLeft + marginLeft * 2 * vectorRight, bottomRight); + { + + QPoint adjustedLeft; + QPoint adjustedRight; + + if (atEnd && !selected) { + baseLine = QLine(baseLine.p1(), baseLine.p2() + vectorRight); + } + + if (nextSelected) { + adjustedRight += vectorLeft; + baseLine = QLine(baseLine.p1(), baseLine.p2() + vectorLeft); + } + if (previousSelected) { + adjustedLeft += vectorRight; + baseLine = QLine(baseLine.p1() + vectorRight, baseLine.p2()); + } + if (atBeginning) + adjustedLeft += vectorRight; + + painter->setPen(QPen(baseColor2, 0)); + if (!selected) + painter->drawLine(baseLine); + + if (atEnd && !selected) + painter->drawPoint(baseLine.p2() + vectorRight); + + if (atBeginning && !selected) + adjustedLeft = vectorRight; + else + adjustedLeft = QPoint(0, 0); + painter->setPen(QPen(baseColor1, 0)); + if (!selected) + painter->drawLine(bottomLeft + vectorUp + adjustedLeft, baseLine.p2() + vectorUp); + + QPoint endPoint = bottomRight + vectorUp; + if (atEnd && !selected) + painter->drawPoint(endPoint); + + // For drawing a lower left "fake" corner on the base when the first tab is unselected + if (atBeginning && !selected) { + painter->drawPoint(baseLine.p1() + vectorLeft); + } + + painter->setPen(QPen(corner, 0)); + if (nextSelected) + painter->drawPoint(endPoint); + else if (selected) + painter->drawPoint(endPoint + vectorRight); + + // For drawing a lower left "fake" corner on the base when the first tab is unselected + if (atBeginning && !selected) { + painter->drawPoint(baseLine.p1() + 2 * vectorLeft); + } + } + } + } + + // Yay we're done + + painter->restore(); + } + break; +#endif // QT_NO_TABBAR +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarGroove: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + QRect rect = bar->rect; + QPen oldPen = painter->pen(); + + QLine lines[4]; + + // outline + painter->setPen(borderColor); + lines[0] = QLine(rect.left() + 2, rect.top(), rect.right() - 2, rect.top()); + lines[1] = QLine(rect.left() + 2, rect.bottom(), rect.right() - 2, rect.bottom()); + lines[2] = QLine(rect.left(), rect.top() + 2, rect.left(), rect.bottom() - 2); + lines[3] = QLine(rect.right(), rect.top() + 2, rect.right(), rect.bottom() - 2); + painter->drawLines(lines, 4); + + QPoint points[8]; + points[0] = QPoint(rect.left() + 1, rect.top() + 1); + points[1] = QPoint(rect.right() - 1, rect.top() + 1); + points[2] = QPoint(rect.left() + 1, rect.bottom() - 1); + points[3] = QPoint(rect.right() - 1, rect.bottom() - 1); + painter->drawPoints(points, 4); + + // alpha corners + painter->setPen(alphaCornerColor); + points[0] = QPoint(rect.left(), rect.top() + 1); + points[1] = QPoint(rect.left() + 1, rect.top()); + points[2] = QPoint(rect.right(), rect.top() + 1); + points[3] = QPoint(rect.right() - 1, rect.top()); + points[4] = QPoint(rect.left(), rect.bottom() - 1); + points[5] = QPoint(rect.left() + 1, rect.bottom()); + points[6] = QPoint(rect.right(), rect.bottom() - 1); + points[7] = QPoint(rect.right() - 1, rect.bottom()); + painter->drawPoints(points, 8); + + // inner outline, north-west + painter->setPen(gradientStartColor.darker(105)); + lines[0] = QLine(rect.left() + 2, rect.top() + 1, rect.right() - 2, rect.top() + 1); + lines[1] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, rect.bottom() - 2); + painter->drawLines(lines, 2); + + // base of the groove + painter->setPen(QPen()); + painter->fillRect(rect.adjusted(2, 2, -2, -1), QBrush(bar->palette.base().color())); + painter->setPen(bar->palette.base().color()); + painter->drawLine(rect.right() - 1, rect.top() + 2, rect.right() - 1, rect.bottom() - 2); + + painter->setPen(oldPen); + } + break; + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + // The busy indicator doesn't draw a label + if (bar->minimum == 0 && bar->maximum == 0) + return; + + painter->save(); + + QRect rect = bar->rect; + QRect leftRect; + + QFont font; + font.setBold(true); + painter->setFont(font); + painter->setPen(bar->palette.text().color()); + + bool vertical = false; + bool inverted = false; + bool bottomToTop = false; + // Get extra style options if version 2 + if (const QStyleOptionProgressBarV2 *bar2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (bar2->orientation == Qt::Vertical); + inverted = bar2->invertedAppearance; + bottomToTop = bar2->bottomToTop; + } + + if (vertical) { + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + QTransform m; + if (bottomToTop) { + m.translate(0.0, rect.width()); + m.rotate(-90); + } else { + m.translate(rect.height(), 0.0); + m.rotate(90); + } + painter->setTransform(m, true); + } + + int progressIndicatorPos = (bar->progress - qreal(bar->minimum)) / qMax(qreal(1.0), qreal(bar->maximum) - bar->minimum) * rect.width(); + + bool flip = (!vertical && (((bar->direction == Qt::RightToLeft) && !inverted) + || ((bar->direction == Qt::LeftToRight) && inverted))) || (vertical && ((!inverted && !bottomToTop) || (inverted && bottomToTop))); + if (flip) { + int indicatorPos = rect.width() - progressIndicatorPos; + if (indicatorPos >= 0 && indicatorPos <= rect.width()) { + painter->setPen(bar->palette.base().color()); + leftRect = QRect(rect.left(), rect.top(), indicatorPos, rect.height()); + } else if (indicatorPos > rect.width()) { + painter->setPen(bar->palette.text().color()); + } else { + painter->setPen(bar->palette.base().color()); + } + } else { + if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { + leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); + } else if (progressIndicatorPos > rect.width()) { + painter->setPen(bar->palette.base().color()); + } else { + painter->setPen(bar->palette.text().color()); + } + } + + QRegion rightRect = rect; + rightRect = rightRect.subtracted(leftRect); + painter->setClipRegion(rightRect); + painter->drawText(rect, bar->text, QTextOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter)); + if (!leftRect.isNull()) { + painter->setPen(flip ? bar->palette.text().color() : bar->palette.base().color()); + painter->setClipRect(leftRect); + painter->drawText(rect, bar->text, QTextOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter)); + } + + painter->restore(); + } + break; + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + Q_D(const QPlastiqueStyle); + QRect rect = bar->rect; + bool vertical = false; + bool inverted = false; + bool indeterminate = (bar->minimum == 0 && bar->maximum == 0); + if (!indeterminate && bar->progress == -1) + break; + + painter->save(); + + // Get extra style options if version 2 + if (const QStyleOptionProgressBarV2 *bar2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (bar2->orientation == Qt::Vertical); + inverted = bar2->invertedAppearance; + } + + // If the orientation is vertical, we use a transform to rotate + // the progress bar 90 degrees clockwise. This way we can use the + // same rendering code for both orientations. + if (vertical) { + rect = QRect(rect.left(), rect.top(), rect.height(), rect.width()); // flip width and height + QTransform m = QTransform::fromTranslate(rect.height()-1, 0); + m.rotate(90.0); + painter->setTransform(m, true); + } + + int maxWidth = rect.width() - 4; + int minWidth = 4; + qint64 progress = qMax<qint64>(bar->progress, bar->minimum); // workaround for bug in QProgressBar + double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth); + int width = indeterminate ? maxWidth : qMax(int(vc6_workaround), minWidth); + bool reverse = (!vertical && (bar->direction == Qt::RightToLeft)) || vertical; + if (inverted) + reverse = !reverse; + + QRect progressBar; + if (!indeterminate) { + if (!reverse) { + progressBar.setRect(rect.left() + 2, rect.top() + 2, width, rect.height() - 4); + } else { + progressBar.setRect(rect.right() - 1 - width, rect.top() + 2, width, rect.height() - 4); + } + } else { + int slideWidth = ((rect.width() - 4) * 2) / 3; + int step = ((d->animateStep * slideWidth) / ProgressBarFps) % slideWidth; + if ((((d->animateStep * slideWidth) / ProgressBarFps) % (2 * slideWidth)) >= slideWidth) + step = slideWidth - step; + progressBar.setRect(rect.left() + 2 + step, rect.top() + 2, + slideWidth / 2, rect.height() - 4); + } + + // outline + painter->setPen(highlightedDarkInnerBorderColor); + + QVarLengthArray<QLine, 4> lines; + QVarLengthArray<QPoint, 8> points; + if (!reverse) { + if (width == minWidth) { + points.append(QPoint(progressBar.left() + 1, progressBar.top())); + points.append(QPoint(progressBar.left() + 1, progressBar.bottom())); + } else { + if (indeterminate) { + lines.append(QLine(progressBar.left() + 2, progressBar.top(), + progressBar.right() - 2, progressBar.top())); + lines.append(QLine(progressBar.left() + 2, progressBar.bottom(), + progressBar.right() - 2, progressBar.bottom())); + } else { + lines.append(QLine(progressBar.left() + 1, progressBar.top(), + progressBar.right() - 2, progressBar.top())); + lines.append(QLine(progressBar.left() + 1, progressBar.bottom(), + progressBar.right() - 2, progressBar.bottom())); + } + } + + if (indeterminate) { + lines.append(QLine(progressBar.left(), progressBar.top() + 2, + progressBar.left(), progressBar.bottom() - 2)); + } else { + lines.append(QLine(progressBar.left(), progressBar.top() + 1, + progressBar.left(), progressBar.bottom() - 1)); + } + lines.append(QLine(progressBar.right(), progressBar.top() + 2, + progressBar.right(), progressBar.bottom() - 2)); + } else { + if (width == minWidth) { + points.append(QPoint(progressBar.right() - 1, progressBar.top())); + points.append(QPoint(progressBar.right() - 1, progressBar.bottom())); + } else { + if (indeterminate) { + lines.append(QLine(progressBar.right() - 2, progressBar.top(), + progressBar.left() + 2, progressBar.top())); + lines.append(QLine(progressBar.right() - 2, progressBar.bottom(), + progressBar.left() + 2, progressBar.bottom())); + } else { + lines.append(QLine(progressBar.right() - 1, progressBar.top(), + progressBar.left() + 2, progressBar.top())); + lines.append(QLine(progressBar.right() - 1, progressBar.bottom(), + progressBar.left() + 2, progressBar.bottom())); + } + } + if (indeterminate) { + lines.append(QLine(progressBar.right(), progressBar.top() + 2, + progressBar.right(), progressBar.bottom() - 2)); + } else { + lines.append(QLine(progressBar.right(), progressBar.top() + 1, + progressBar.right(), progressBar.bottom() - 1)); + } + lines.append(QLine(progressBar.left(), progressBar.top() + 2, + progressBar.left(), progressBar.bottom() - 2)); + } + + if (points.size() > 0) { + painter->drawPoints(points.constData(), points.size()); + points.clear(); + } + painter->drawLines(lines.constData(), lines.size()); + lines.clear(); + + // alpha corners + painter->setPen(alphaInnerColor); + if (!reverse) { + if (indeterminate) { + points.append(QPoint(progressBar.left() + 1, progressBar.top())); + points.append(QPoint(progressBar.left(), progressBar.top() + 1)); + points.append(QPoint(progressBar.left() + 1, progressBar.bottom())); + points.append(QPoint(progressBar.left(), progressBar.bottom() - 1)); + } else { + points.append(QPoint(progressBar.left(), progressBar.top())); + points.append(QPoint(progressBar.left(), progressBar.bottom())); + } + points.append(QPoint(progressBar.right() - 1, progressBar.top())); + points.append(QPoint(progressBar.right(), progressBar.top() + 1)); + points.append(QPoint(progressBar.right() - 1, progressBar.bottom())); + points.append(QPoint(progressBar.right(), progressBar.bottom() - 1)); + } else { + if (indeterminate) { + points.append(QPoint(progressBar.right() - 1, progressBar.top())); + points.append(QPoint(progressBar.right(), progressBar.top() + 1)); + points.append(QPoint(progressBar.right() - 1, progressBar.bottom())); + points.append(QPoint(progressBar.right(), progressBar.bottom() - 1)); + } else { + points.append(QPoint(progressBar.right(), progressBar.top())); + points.append(QPoint(progressBar.right(), progressBar.bottom())); + } + points.append(QPoint(progressBar.left() + 1, progressBar.top())); + points.append(QPoint(progressBar.left(), progressBar.top() + 1)); + points.append(QPoint(progressBar.left() + 1, progressBar.bottom())); + points.append(QPoint(progressBar.left(), progressBar.bottom() - 1)); + } + + painter->drawPoints(points.constData(), points.size()); + points.clear(); + + // contents + painter->setPen(QPen()); + + QString progressBarName = QStyleHelper::uniqueName(QLatin1String("progressBarContents"), + option, rect.size()); + QPixmap cache; + if (!QPixmapCache::find(progressBarName, cache) && rect.height() > 7) { + QSize size = rect.size(); + cache = QPixmap(QSize(size.width() - 6 + 30, size.height() - 6)); + cache.fill(Qt::white); + QPainter cachePainter(&cache); + QRect pixmapRect(0, 0, cache.width(), cache.height()); + + int leftEdge = 0; + bool flip = false; + while (leftEdge < cache.width() + 1) { + QColor rectColor = option->palette.highlight().color(); + QColor lineColor = option->palette.highlight().color(); + if (flip) { + flip = false; + rectColor = rectColor.lighter(105); + lineColor = lineColor.lighter(105); + } else { + flip = true; + } + + cachePainter.setPen(lineColor); + const QLine cacheLines[2] = { + QLine(pixmapRect.left() + leftEdge - 1, pixmapRect.top(), + pixmapRect.left() + leftEdge + 9, pixmapRect.top()), + QLine(pixmapRect.left() + leftEdge - 1, pixmapRect.bottom(), + pixmapRect.left() + leftEdge + 9, pixmapRect.bottom()) }; + cachePainter.drawLines(cacheLines, 2); + cachePainter.fillRect(QRect(pixmapRect.left() + leftEdge, pixmapRect.top(), + 10, pixmapRect.height()), rectColor); + + leftEdge += 10; + } + + QPixmapCache::insert(progressBarName, cache); + } + painter->setClipRect(progressBar.adjusted(1, 0, -1, -1)); + + if (!vertical) + progressBar.adjust(0, 1, 0, 1); + if (!indeterminate) { + int step = (AnimateProgressBar || (indeterminate && AnimateBusyProgressBar)) ? (d->animateStep % 20) : 0; + if (reverse) + painter->drawPixmap(progressBar.left() - 25 + step, progressBar.top(), cache); + else + painter->drawPixmap(progressBar.left() - 25 - step + width % 20, progressBar.top(), cache); + } else { + painter->drawPixmap(progressBar.left(), progressBar.top(), cache); + } + + painter->restore(); + } + break; +#endif // QT_NO_PROGRESSBAR + case CE_HeaderSection: + // Draws the header in tables. + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + QPixmap cache; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("headersection"), option, option->rect.size()); + pixmapName += QString::number(- int(header->position)); + pixmapName += QString::number(- int(header->orientation)); + + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(option->rect.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, option->rect.width(), option->rect.height()); + QPainter cachePainter(&cache); + + bool sunken = (header->state & State_Enabled) && (header->state & State_Sunken); + + QColor headerGradientStart = sunken ? option->palette.background().color().darker(114) : gradientStartColor; + QColor headerGradientStop = sunken ? option->palette.background().color().darker(106) : gradientStopColor; + + QColor lightLine = sunken ? option->palette.background().color().darker(118) : gradientStartColor; + QColor darkLine = sunken ? option->palette.background().color().darker(110) : gradientStopColor.darker(105); + + qt_plastique_draw_gradient(&cachePainter, pixmapRect, + headerGradientStart, headerGradientStop); + + cachePainter.setPen(borderColor); + cachePainter.drawRect(pixmapRect.adjusted(0, 0, -1, -1)); + cachePainter.setPen(alphaCornerColor); + + const QPoint points[4] = { + pixmapRect.topLeft(), pixmapRect.topRight(), + pixmapRect.bottomLeft(), pixmapRect.bottomRight() }; + cachePainter.drawPoints(points, 4); + + QLine lines[2]; + + // inner lines + cachePainter.setPen(lightLine); + lines[0] = QLine(pixmapRect.left() + 2, pixmapRect.top() + 1, + pixmapRect.right() - 2, pixmapRect.top() + 1); + lines[1] = QLine(pixmapRect.left() + 1, pixmapRect.top() + 2, + pixmapRect.left() + 1, pixmapRect.bottom() - 2); + cachePainter.drawLines(lines, 2); + + cachePainter.setPen(darkLine); + lines[0] = QLine(pixmapRect.left() + 2, pixmapRect.bottom() - 1, + pixmapRect.right() - 2, pixmapRect.bottom() - 1); + lines[1] = QLine(pixmapRect.right() - 1, pixmapRect.bottom() - 2, + pixmapRect.right() - 1, pixmapRect.top() + 2); + cachePainter.drawLines(lines, 2); + + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(option->rect.topLeft(), cache); + + } + break; +#ifndef QT_NO_MENU + case CE_MenuItem: + // Draws one item in a popup menu. + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + painter->save(); + QBrush textBrush; + if (option->palette.resolve() & (1 << QPalette::ButtonText)) + textBrush = option->palette.buttonText(); + else + textBrush = option->palette.windowText(); // KDE uses windowText rather than buttonText for menus + + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + painter->fillRect(menuItem->rect, option->palette.background().color().lighter(103)); + + int w = 0; + if (!menuItem->text.isEmpty()) { + painter->setFont(menuItem->font); + proxy()->drawItemText(painter, menuItem->rect.adjusted(5, 0, -5, 0), Qt::AlignLeft | Qt::AlignVCenter, + menuItem->palette, menuItem->state & State_Enabled, menuItem->text, + QPalette::Text); + w = menuItem->fontMetrics.width(menuItem->text) + 5; + } + + painter->setPen(alphaCornerColor); + bool reverse = menuItem->direction == Qt::RightToLeft; + painter->drawLine(menuItem->rect.left() + 5 + (reverse ? 0 : w), menuItem->rect.center().y(), + menuItem->rect.right() - 5 - (reverse ? w : 0), menuItem->rect.center().y()); + + painter->restore(); + break; + } + + bool selected = menuItem->state & State_Selected; + bool checkable = menuItem->checkType != QStyleOptionMenuItem::NotCheckable; + bool checked = menuItem->checked; + + if (selected) { + qt_plastique_draw_gradient(painter, menuItem->rect, + option->palette.highlight().color().lighter(105), + option->palette.highlight().color().darker(110)); + + painter->setPen(option->palette.highlight().color().lighter(110)); + painter->drawLine(option->rect.topLeft(), option->rect.topRight()); + painter->setPen(option->palette.highlight().color().darker(115)); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + } else { + painter->fillRect(option->rect, option->palette.background().color().lighter(103)); + } + + // Check + QRect checkRect(option->rect.left() + 7, option->rect.center().y() - 6, 13, 13); + checkRect = visualRect(menuItem->direction, menuItem->rect, checkRect); + if (checkable) { + if ((menuItem->checkType & QStyleOptionMenuItem::Exclusive) && menuItem->icon.isNull()) { + QStyleOptionButton button; + button.rect = checkRect; + button.state = menuItem->state; + if (checked) + button.state |= State_On; + button.palette = menuItem->palette; + proxy()->drawPrimitive(PE_IndicatorRadioButton, &button, painter, widget); + } else { + if (menuItem->icon.isNull()) { + QStyleOptionButton button; + button.rect = checkRect; + button.state = menuItem->state; + if (checked) + button.state |= State_On; + button.palette = menuItem->palette; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget); + } else if (checked) { + int iconSize = qMax(menuItem->maxIconWidth, 20); + QRect sunkenRect(option->rect.left() + 1, + option->rect.top() + (option->rect.height() - iconSize) / 2 + 1, + iconSize, iconSize); + sunkenRect = visualRect(menuItem->direction, menuItem->rect, sunkenRect); + + QStyleOption opt = *option; + opt.state |= State_Sunken; + opt.rect = sunkenRect; + qt_plastique_drawShadedPanel(painter, &opt, false, widget); + } + } + } + + // Text and icon, ripped from windows style + bool dis = !(menuItem->state & State_Enabled); + bool act = menuItem->state & State_Selected; + const QStyleOption *opt = option; + const QStyleOptionMenuItem *menuitem = menuItem; + int checkcol = qMax(menuitem->maxIconWidth, 20); + QPainter *p = painter; + QRect vCheckRect = visualRect(opt->direction, menuitem->rect, + QRect(menuitem->rect.x(), menuitem->rect.y(), + checkcol, menuitem->rect.height())); + 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(pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On); + else + pixmap = menuItem->icon.pixmap(pixelMetric(PM_SmallIconSize, option, widget), mode); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center()); + painter->setPen(textBrush.color()); + if (checkable && checked) + painter->drawPixmap(QPoint(pmr.left() + 1, pmr.top() + 1), pixmap); + else + painter->drawPixmap(pmr.topLeft(), pixmap); + } + + if (selected) { + painter->setPen(menuItem->palette.highlightedText().color()); + } else { + painter->setPen(textBrush.color()); + } + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + QColor discol; + if (dis) { + discol = textBrush.color(); + p->setPen(discol); + } + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + int xpos = menuitem->rect.x() + xm; + QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin); + QRect vTextRect = visualRect(opt->direction, menuitem->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { // draw text + p->save(); + int t = s.indexOf(QLatin1Char('\t')); + int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!styleHint(SH_UnderlineShortcut, menuitem, widget)) + text_flags |= Qt::TextHideMnemonic; + text_flags |= Qt::AlignLeft; + if (t >= 0) { + QRect vShortcutRect = visualRect(opt->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom()))); + if (dis && !act && 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(discol); + } + 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 && 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(discol); + } + p->drawText(vTextRect, text_flags, s.left(t)); + p->restore(); + } + + // Arrow + if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + int dim = (menuItem->rect.height() - 4) / 2; + PrimitiveElement arrow; + arrow = (opt->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + int xpos = menuItem->rect.left() + menuItem->rect.width() - 6 - 2 - dim; + QRect vSubMenuRect = visualRect(option->direction, menuItem->rect, + QRect(xpos, menuItem->rect.top() + menuItem->rect.height() / 2 - dim / 2, dim, dim)); + QStyleOptionMenuItem newMI = *menuItem; + newMI.rect = vSubMenuRect; + newMI.state = option->state & State_Enabled; + if (selected) + newMI.palette.setColor(QPalette::ButtonText, + newMI.palette.highlightedText().color()); + else + newMI.palette.setColor(QPalette::ButtonText, textBrush.color()); + proxy()->drawPrimitive(arrow, &newMI, painter, widget); + } + + painter->restore(); + } + break; +#endif // QT_NO_MENU +#ifndef QT_NO_MENUBAR + case CE_MenuBarItem: + // Draws a menu bar item; File, Edit, Help etc.. + if ((option->state & State_Selected)) { + QPixmap cache; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("menubaritem"), option, option->rect.size()); + if (!QPixmapCache::find(pixmapName, cache)) { + cache = QPixmap(option->rect.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, option->rect.width(), option->rect.height()); + QPainter cachePainter(&cache); + + QRect rect = pixmapRect; + + // gradient fill + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) { + qt_plastique_draw_gradient(&cachePainter, rect.adjusted(1, 1, -1, -1), + option->palette.button().color().darker(114), + option->palette.button().color().darker(106)); + } else { + qt_plastique_draw_gradient(&cachePainter, rect.adjusted(1, 1, -1, -1), + option->palette.background().color().lighter(105), + option->palette.background().color().darker(102)); + } + + // outer border and corners + cachePainter.setPen(borderColor); + cachePainter.drawRect(rect.adjusted(0, 0, -1, -1)); + cachePainter.setPen(alphaCornerColor); + + const QPoint points[4] = { + rect.topLeft(), + rect.topRight(), + rect.bottomLeft(), + rect.bottomRight() }; + cachePainter.drawPoints(points, 4); + + // inner border + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) + cachePainter.setPen(option->palette.button().color().darker(118)); + else + cachePainter.setPen(gradientStartColor); + + QLine lines[2]; + lines[0] = QLine(rect.left() + 1, rect.top() + 1, rect.right() - 1, rect.top() + 1); + lines[1] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, rect.bottom() - 2); + cachePainter.drawLines(lines, 2); + + if ((option->state & QStyle::State_Sunken) || (option->state & QStyle::State_On)) + cachePainter.setPen(option->palette.button().color().darker(114)); + else + cachePainter.setPen(gradientStopColor.darker(102)); + lines[0] = QLine(rect.left() + 1, rect.bottom() - 1, rect.right() - 1, rect.bottom() - 1); + lines[1] = QLine(rect.right() - 1, rect.top() + 1, rect.right() - 1, rect.bottom() - 2); + cachePainter.drawLines(lines, 2); + cachePainter.end(); + QPixmapCache::insert(pixmapName, cache); + } + painter->drawPixmap(option->rect.topLeft(), cache); + } else { + painter->fillRect(option->rect, option->palette.background()); + } + + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + QStyleOptionMenuItem newMI = *mbi; + if (!(option->palette.resolve() & (1 << QPalette::ButtonText))) //KDE uses windowText rather than buttonText for menus + newMI.palette.setColor(QPalette::ButtonText, newMI.palette.windowText().color()); + QCommonStyle::drawControl(element, &newMI, painter, widget); + } + break; + +#ifndef QT_NO_MAINWINDOW + case CE_MenuBarEmptyArea: + if (widget && qobject_cast<const QMainWindow *>(widget->parentWidget())) { + painter->fillRect(option->rect, option->palette.window()); + QPen oldPen = painter->pen(); + painter->setPen(QPen(option->palette.dark().color())); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + painter->setPen(oldPen); + } + break; +#endif // QT_NO_MAINWINDOW + +#endif // QT_NO_MENUBAR + +#ifndef QT_NO_TOOLBOX + case CE_ToolBoxTabShape: + if (const QStyleOptionToolBox *toolBox = qstyleoption_cast<const QStyleOptionToolBox *>(option)) { + painter->save(); + + int width = toolBox->rect.width(); + int diag = toolBox->rect.height() - 2; + + // The essential points + QPoint rightMost; + QPoint rightEdge; + QPoint leftEdge; + QPoint leftMost; + QPoint leftOne; + QPoint rightOne; + QPoint upOne(0, -1); + QPoint downOne(0, 1); + + if (toolBox->direction != Qt::RightToLeft) { + rightMost = QPoint(toolBox->rect.right(), toolBox->rect.bottom() - 2); + rightEdge = QPoint(toolBox->rect.right() - width / 10, toolBox->rect.bottom() - 2); + leftEdge = QPoint(toolBox->rect.right() - width / 10 - diag, toolBox->rect.top()); + leftMost = QPoint(toolBox->rect.left(), toolBox->rect.top()); + leftOne = QPoint(-1, 0); + rightOne = QPoint(1, 0); + } else { + rightMost = QPoint(toolBox->rect.left(), toolBox->rect.bottom() - 2); + rightEdge = QPoint(toolBox->rect.left() + width / 10, toolBox->rect.bottom() - 2); + leftEdge = QPoint(toolBox->rect.left() + width / 10 + diag, toolBox->rect.top()); + leftMost = QPoint(toolBox->rect.right(), toolBox->rect.top()); + leftOne = QPoint(1, 0); + rightOne = QPoint(-1, 0); + } + + QLine lines[3]; + + // Draw the outline + painter->setPen(borderColor); + lines[0] = QLine(rightMost, rightEdge); + lines[1] = QLine(rightEdge + leftOne, leftEdge); + lines[2] = QLine(leftEdge + leftOne, leftMost); + painter->drawLines(lines, 3); + painter->setPen(toolBox->palette.base().color()); + lines[0] = QLine(rightMost + downOne, rightEdge + downOne); + lines[1] = QLine(rightEdge + leftOne + downOne, leftEdge + downOne); + lines[2] = QLine(leftEdge + leftOne + downOne, leftMost + downOne); + painter->drawLines(lines, 3); + + painter->restore(); + } + break; +#endif // QT_NO_TOOLBOX +#ifndef QT_NO_SPLITTER + case CE_Splitter: + if ((option->state & State_Enabled) && (option->state & State_MouseOver)) + painter->fillRect(option->rect, QColor(255, 255, 255, 128)); + if (option->state & State_Horizontal) { + int height = option->rect.height() / 3; + QRect rect(option->rect.left() + (option->rect.width() / 2 - 1), + option->rect.center().y() - height / 2, 3, height); + qt_plastique_draw_handle(painter, option, rect, Qt::Horizontal, widget); + } else { + int width = option->rect.width() / 3; + QRect rect(option->rect.center().x() - width / 2, + option->rect.top() + (option->rect.height() / 2) - 1, width, 3); + qt_plastique_draw_handle(painter, option, rect, Qt::Vertical, widget); + } + break; +#endif // QT_NO_SPLITTER +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dockWidget = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + painter->save(); + + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dockWidget); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + // Find text width and title rect + int textWidth = option->fontMetrics.width(dockWidget->title); + int margin = 4; + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, option, widget); + QRect rect = dockWidget->rect; + + if (verticalTitleBar) { + QRect r = rect; + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + + painter->translate(r.left(), r.top() + r.width()); + painter->rotate(-90); + painter->translate(-r.left(), -r.top()); + + rect = r; + } + + // Chop and insert ellide into title if text is too wide + QString title = elliditide(dockWidget->title, dockWidget->fontMetrics, titleRect, &textWidth); + + // Draw the toolbar handle pattern to the left and right of the text + QImage handle(qt_toolbarhandle); + alphaCornerColor.setAlpha(170); + handle.setColor(1, alphaCornerColor.rgba()); + handle.setColor(2, mergedColors(alphaCornerColor, option->palette.light().color()).rgba()); + handle.setColor(3, option->palette.light().color().rgba()); + + if (title.isEmpty()) { + // Joint handle if there's no title + QRect r; +#ifdef QT3_SUPPORT + // Q3DockWindow doesn't need space for buttons + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + r = rect; + } else +#endif + r.setRect(titleRect.left(), titleRect.top(), titleRect.width(), titleRect.bottom()); + int nchunks = (r.width() / handle.width()) - 1; + int indent = (r.width() - (nchunks * handle.width())) / 2; + for (int i = 0; i < nchunks; ++i) { + painter->drawImage(QPoint(r.left() + indent + i * handle.width(), + r.center().y() - handle.height() / 2), + handle); + } + } else { + // Handle pattern to the left of the title + QRect leftSide(titleRect.left(), titleRect.top(), + titleRect.width() / 2 - textWidth / 2 - margin, titleRect.bottom()); + int nchunks = leftSide.width() / handle.width(); + int indent = (leftSide.width() - (nchunks * handle.width())) / 2; + for (int i = 0; i < nchunks; ++i) { + painter->drawImage(QPoint(leftSide.left() + indent + + i * handle.width(), + leftSide.center().y() + - handle.height() / 2), + handle); + } + + // Handle pattern to the right of the title + QRect rightSide = titleRect.adjusted(titleRect.width() / 2 + textWidth / 2 + margin, 0, 0, 0); + nchunks = rightSide.width() / handle.width(); + indent = (rightSide.width() - (nchunks * handle.width())) / 2; + for (int j = 0; j < nchunks; ++j) { + painter->drawImage(QPoint(rightSide.left() + indent + j * handle.width(), + rightSide.center().y() - handle.height() / 2), + handle); + } + } + + // Draw the text centered + QFont font = painter->font(); + font.setPointSize(QFontInfo(font).pointSize() - 1); + painter->setFont(font); + painter->setPen(dockWidget->palette.windowText().color()); + painter->drawText(titleRect, + int(Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextShowMnemonic), + title); + + painter->restore(); + } + + break; +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + if (const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + // Draws the light line above and the dark line below menu bars and + // tool bars. + QPen oldPen = painter->pen(); + if (toolBar->toolBarArea == Qt::TopToolBarArea) { + if (toolBar->positionOfLine == QStyleOptionToolBar::End + || toolBar->positionOfLine == QStyleOptionToolBar::OnlyOne) { + // The end and onlyone top toolbar lines draw a double + // line at the bottom to blend with the central + // widget. + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.left(), option->rect.bottom() - 1, + option->rect.right(), option->rect.bottom() - 1); + } else { + // All others draw a single dark line at the bottom. + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + } + // All top toolbar lines draw a light line at the top. + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.topLeft(), option->rect.topRight()); + } else if (toolBar->toolBarArea == Qt::BottomToolBarArea) { + if (toolBar->positionOfLine == QStyleOptionToolBar::End + || toolBar->positionOfLine == QStyleOptionToolBar::Middle) { + // The end and middle bottom tool bar lines draw a dark + // line at the bottom. + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + } + if (toolBar->positionOfLine == QStyleOptionToolBar::Beginning + || toolBar->positionOfLine == QStyleOptionToolBar::OnlyOne) { + // The beginning and only one tool bar lines draw a + // double line at the bottom to blend with the + // status bar. + // ### The styleoption could contain whether the + // main window has a menu bar and a status bar, and + // possibly dock widgets. + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.left(), option->rect.bottom() - 1, + option->rect.right(), option->rect.bottom() - 1); + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.bottomLeft(), option->rect.bottomRight()); + } + if (toolBar->positionOfLine == QStyleOptionToolBar::End) { + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.topLeft(), option->rect.topRight()); + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.left(), option->rect.top() + 1, + option->rect.right(), option->rect.top() + 1); + + } else { + // All other bottom toolbars draw a light line at the top. + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.topLeft(), option->rect.topRight()); + } + } + if (toolBar->toolBarArea == Qt::LeftToolBarArea) { + if (toolBar->positionOfLine == QStyleOptionToolBar::Middle + || toolBar->positionOfLine == QStyleOptionToolBar::End) { + // The middle and left end toolbar lines draw a light + // line to the left. + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.topLeft(), option->rect.bottomLeft()); + } + if (toolBar->positionOfLine == QStyleOptionToolBar::End) { + // All other left toolbar lines draw a dark line to the right + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.right() - 1, option->rect.top(), + option->rect.right() - 1, option->rect.bottom()); + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.topRight(), option->rect.bottomRight()); + } else { + // All other left toolbar lines draw a dark line to the right + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.topRight(), option->rect.bottomRight()); + } + } else if (toolBar->toolBarArea == Qt::RightToolBarArea) { + if (toolBar->positionOfLine == QStyleOptionToolBar::Middle + || toolBar->positionOfLine == QStyleOptionToolBar::End) { + // Right middle and end toolbar lines draw the dark right line + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.topRight(), option->rect.bottomRight()); + } + if (toolBar->positionOfLine == QStyleOptionToolBar::End + || toolBar->positionOfLine == QStyleOptionToolBar::OnlyOne) { + // The right end and single toolbar draws the dark + // line on its left edge + painter->setPen(alphaCornerColor); + painter->drawLine(option->rect.topLeft(), option->rect.bottomLeft()); + // And a light line next to it + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.left() + 1, option->rect.top(), + option->rect.left() + 1, option->rect.bottom()); + } else { + // Other right toolbars draw a light line on its left edge + painter->setPen(option->palette.background().color().lighter(104)); + painter->drawLine(option->rect.topLeft(), option->rect.bottomLeft()); + } + } + painter->setPen(oldPen); + } + break; +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_SCROLLBAR + case CE_ScrollBarAddLine: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool reverse = scrollBar->direction == Qt::RightToLeft; + bool sunken = scrollBar->state & State_Sunken; + + QString addLinePixmapName = QStyleHelper::uniqueName(QLatin1String("scrollbar_addline"), option, option->rect.size()); + QPixmap cache; + if (!QPixmapCache::find(addLinePixmapName, cache)) { + cache = QPixmap(option->rect.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, cache.width(), cache.height()); + QPainter addLinePainter(&cache); + addLinePainter.fillRect(pixmapRect, option->palette.background()); + + if (option->state & State_Enabled) { + // Gradient + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top() + 2, + pixmapRect.center().x(), pixmapRect.bottom() - 2); + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) { + gradient.setColorAt(0, gradientStopColor); + gradient.setColorAt(1, gradientStopColor); + } else { + gradient.setColorAt(0, gradientStartColor.lighter(105)); + gradient.setColorAt(1, gradientStopColor); + } + addLinePainter.fillRect(pixmapRect.left() + 2, pixmapRect.top() + 2, + pixmapRect.right() - 3, pixmapRect.bottom() - 3, + gradient); + } + + // Details + QImage addButton; + if (horizontal) { + addButton = QImage(reverse ? qt_scrollbar_button_left : qt_scrollbar_button_right); + } else { + addButton = QImage(qt_scrollbar_button_down); + } + addButton.setColor(1, alphaCornerColor.rgba()); + addButton.setColor(2, borderColor.rgba()); + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) { + addButton.setColor(3, gradientStopColor.rgba()); + addButton.setColor(4, gradientStopColor.rgba()); + } else { + addButton.setColor(3, gradientStartColor.lighter(105).rgba()); + addButton.setColor(4, gradientStopColor.rgba()); + } + addButton.setColor(5, scrollBar->palette.text().color().rgba()); + addLinePainter.drawImage(pixmapRect, addButton); + + // Arrow + if (horizontal) { + QImage arrow(reverse ? qt_scrollbar_button_arrow_left : qt_scrollbar_button_arrow_right); + arrow.setColor(1, scrollBar->palette.foreground().color().rgba()); + + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) + addLinePainter.translate(1, 1); + addLinePainter.drawImage(QPoint(pixmapRect.center().x() - 2, pixmapRect.center().y() - 3), arrow); + } else { + QImage arrow(qt_scrollbar_button_arrow_down); + arrow.setColor(1, scrollBar->palette.foreground().color().rgba()); + + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) + addLinePainter.translate(1, 1); + addLinePainter.drawImage(QPoint(pixmapRect.center().x() - 3, pixmapRect.center().y() - 2), arrow); + } + addLinePainter.end(); + QPixmapCache::insert(addLinePixmapName, cache); + } + painter->drawPixmap(option->rect.topLeft(), cache); + } + break; + case CE_ScrollBarSubPage: + case CE_ScrollBarAddPage: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + bool sunken = scrollBar->state & State_Sunken; + bool horizontal = scrollBar->orientation == Qt::Horizontal; + + QString groovePixmapName = QStyleHelper::uniqueName(QLatin1String("scrollbar_groove"), option, option->rect.size()); + if (sunken) + groovePixmapName += QLatin1String("-sunken"); + if (element == CE_ScrollBarAddPage) + groovePixmapName += QLatin1String("-addpage"); + + QPixmap cache; + if (!QPixmapCache::find(groovePixmapName, cache)) { + cache = QPixmap(option->rect.size()); + cache.fill(option->palette.background().color()); + QPainter groovePainter(&cache); + QRect pixmapRect = QRect(0, 0, option->rect.width(), option->rect.height()); + QColor color = scrollBar->palette.base().color().darker(sunken ? 125 : 100); + groovePainter.setBrushOrigin((element == CE_ScrollBarAddPage) ? pixmapRect.width() : 0, + (element == CE_ScrollBarAddPage) ? pixmapRect.height() : 0); + groovePainter.fillRect(pixmapRect, QBrush(color, Qt::Dense4Pattern)); + + QColor edgeColor = scrollBar->palette.base().color().darker(125); + if (horizontal) { + groovePainter.setBrushOrigin((element == CE_ScrollBarAddPage) ? pixmapRect.width() : 1, 0); + groovePainter.fillRect(QRect(pixmapRect.topLeft(), QSize(pixmapRect.width(), 1)), + QBrush(edgeColor, Qt::Dense4Pattern)); + groovePainter.fillRect(QRect(pixmapRect.bottomLeft(), QSize(pixmapRect.width(), 1)), + QBrush(edgeColor, Qt::Dense4Pattern)); + } else { + groovePainter.setBrushOrigin(0, (element == CE_ScrollBarAddPage) ? pixmapRect.height() : 1); + groovePainter.fillRect(QRect(pixmapRect.topLeft(), QSize(1, pixmapRect.height())), + QBrush(edgeColor, Qt::Dense4Pattern)); + groovePainter.fillRect(QRect(pixmapRect.topRight(), QSize(1, pixmapRect.height())), + QBrush(edgeColor, Qt::Dense4Pattern)); + } + + groovePainter.end(); + QPixmapCache::insert(groovePixmapName, cache); + } + painter->drawPixmap(option->rect.topLeft(), cache); + } + break; + case CE_ScrollBarSubLine: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect scrollBarSubLine = scrollBar->rect; + + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool isEnabled = scrollBar->state & State_Enabled; + bool reverse = scrollBar->direction == Qt::RightToLeft; + bool sunken = scrollBar->state & State_Sunken; + + // The SubLine (up/left) buttons + QRect button1; + QRect button2; + int scrollBarExtent = proxy()->pixelMetric(PM_ScrollBarExtent, option, widget); + if (horizontal) { + button1.setRect(scrollBarSubLine.left(), scrollBarSubLine.top(), scrollBarExtent, scrollBarSubLine.height()); + button2.setRect(scrollBarSubLine.right() - (scrollBarExtent - 1), scrollBarSubLine.top(), scrollBarExtent, scrollBarSubLine.height()); + } else { + button1.setRect(scrollBarSubLine.left(), scrollBarSubLine.top(), scrollBarSubLine.width(), scrollBarExtent); + button2.setRect(scrollBarSubLine.left(), scrollBarSubLine.bottom() - (scrollBarExtent - 1), scrollBarSubLine.width(), scrollBarExtent); + } + + QString subLinePixmapName = QStyleHelper::uniqueName(QLatin1String("scrollbar_subline"), option, button1.size()); + QPixmap cache; + if (!QPixmapCache::find(subLinePixmapName, cache)) { + cache = QPixmap(button1.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, cache.width(), cache.height()); + QPainter subLinePainter(&cache); + subLinePainter.fillRect(pixmapRect, option->palette.background()); + + if (isEnabled) { + // Gradients + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) { + qt_plastique_draw_gradient(&subLinePainter, + QRect(pixmapRect.left() + 2, pixmapRect.top() + 2, + pixmapRect.right() - 3, pixmapRect.bottom() - 3), + gradientStopColor, + gradientStopColor); + } else { + qt_plastique_draw_gradient(&subLinePainter, + QRect(pixmapRect.left() + 2, pixmapRect.top() + 2, + pixmapRect.right() - 3, pixmapRect.bottom() - 3), + gradientStartColor.lighter(105), + gradientStopColor); + } + } + + // Details + QImage subButton; + if (horizontal) { + subButton = QImage(reverse ? qt_scrollbar_button_right : qt_scrollbar_button_left); + } else { + subButton = QImage(qt_scrollbar_button_up); + } + subButton.setColor(1, alphaCornerColor.rgba()); + subButton.setColor(2, borderColor.rgba()); + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) { + subButton.setColor(3, gradientStopColor.rgba()); + subButton.setColor(4, gradientStopColor.rgba()); + } else { + subButton.setColor(3, gradientStartColor.lighter(105).rgba()); + subButton.setColor(4, gradientStopColor.rgba()); + } + subButton.setColor(5, scrollBar->palette.text().color().rgba()); + subLinePainter.drawImage(pixmapRect, subButton); + + // Arrows + if (horizontal) { + QImage arrow(reverse ? qt_scrollbar_button_arrow_right : qt_scrollbar_button_arrow_left); + arrow.setColor(1, scrollBar->palette.foreground().color().rgba()); + + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) + subLinePainter.translate(1, 1); + subLinePainter.drawImage(QPoint(pixmapRect.center().x() - 2, pixmapRect.center().y() - 3), arrow); + } else { + QImage arrow(qt_scrollbar_button_arrow_up); + arrow.setColor(1, scrollBar->palette.foreground().color().rgba()); + + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) + subLinePainter.translate(1, 1); + subLinePainter.drawImage(QPoint(pixmapRect.center().x() - 3, pixmapRect.center().y() - 2), arrow); + } + subLinePainter.end(); + QPixmapCache::insert(subLinePixmapName, cache); + } + painter->drawPixmap(button1.topLeft(), cache); + painter->drawPixmap(button2.topLeft(), cache); + } + break; + case CE_ScrollBarSlider: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool isEnabled = scrollBar->state & State_Enabled; + + // The slider + if (option->rect.isValid()) { + QString sliderPixmapName = QStyleHelper::uniqueName(QLatin1String("scrollbar_slider"), option, option->rect.size()); + if (horizontal) + sliderPixmapName += QLatin1String("-horizontal"); + + QPixmap cache; + if (!QPixmapCache::find(sliderPixmapName, cache)) { + cache = QPixmap(option->rect.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, cache.width(), cache.height()); + QPainter sliderPainter(&cache); + bool sunken = (scrollBar->state & State_Sunken); + + if (isEnabled) { + QLinearGradient gradient(pixmapRect.left(), pixmapRect.center().y(), + pixmapRect.right(), pixmapRect.center().y()); + if (horizontal) + gradient = QLinearGradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + + if (sunken) { + gradient.setColorAt(0, gradientStartColor.lighter(110)); + gradient.setColorAt(1, gradientStopColor.lighter(105)); + } else { + gradient.setColorAt(0, gradientStartColor.lighter(105)); + gradient.setColorAt(1, gradientStopColor); + } + sliderPainter.fillRect(pixmapRect.adjusted(2, 2, -2, -2), gradient); + } else { + sliderPainter.fillRect(pixmapRect.adjusted(2, 2, -2, -2), option->palette.background()); + } + + sliderPainter.setPen(borderColor); + sliderPainter.drawRect(pixmapRect.adjusted(0, 0, -1, -1)); + sliderPainter.setPen(alphaCornerColor); + QPoint points[4] = { + QPoint(pixmapRect.left(), pixmapRect.top()), + QPoint(pixmapRect.left(), pixmapRect.bottom()), + QPoint(pixmapRect.right(), pixmapRect.top()), + QPoint(pixmapRect.right(), pixmapRect.bottom()) }; + sliderPainter.drawPoints(points, 4); + + QLine lines[2]; + sliderPainter.setPen(sunken ? gradientStartColor.lighter(110) : gradientStartColor.lighter(105)); + lines[0] = QLine(pixmapRect.left() + 1, pixmapRect.top() + 1, + pixmapRect.right() - 1, pixmapRect.top() + 1); + lines[1] = QLine(pixmapRect.left() + 1, pixmapRect.top() + 2, + pixmapRect.left() + 1, pixmapRect.bottom() - 2); + sliderPainter.drawLines(lines, 2); + + sliderPainter.setPen(sunken ? gradientStopColor.lighter(105) : gradientStopColor); + lines[0] = QLine(pixmapRect.left() + 1, pixmapRect.bottom() - 1, + pixmapRect.right() - 1, pixmapRect.bottom() - 1); + lines[1] = QLine(pixmapRect.right() - 1, pixmapRect.top() + 2, + pixmapRect.right() - 1, pixmapRect.bottom() - 1); + sliderPainter.drawLines(lines, 2); + + int sliderMinLength = proxy()->pixelMetric(PM_ScrollBarSliderMin, scrollBar, widget); + if ((horizontal && scrollBar->rect.width() > sliderMinLength) + || (!horizontal && scrollBar->rect.height() > sliderMinLength)) { + QImage pattern(horizontal ? qt_scrollbar_slider_pattern_horizontal + : qt_scrollbar_slider_pattern_vertical); + pattern.setColor(1, alphaCornerColor.rgba()); + pattern.setColor(2, (sunken ? gradientStartColor.lighter(110) : gradientStartColor.lighter(105)).rgba()); + + if (horizontal) { + sliderPainter.drawImage(pixmapRect.center().x() - pattern.width() / 2 + 1, + pixmapRect.center().y() - 4, + pattern); + } else { + sliderPainter.drawImage(pixmapRect.center().x() - 4, + pixmapRect.center().y() - pattern.height() / 2 + 1, + pattern); + } + } + sliderPainter.end(); + // insert the slider into the cache + QPixmapCache::insert(sliderPixmapName, cache); + } + painter->drawPixmap(option->rect.topLeft(), cache); + } + } + break; +#endif +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + painter->save(); + if (!comboBox->editable) { + // Plastique's non-editable combo box is drawn as a button, so + // we need the label to be drawn using ButtonText where it + // would usually use Text. + painter->setPen(QPen(comboBox->palette.buttonText(), 0)); + QWindowsStyle::drawControl(element, option, painter, widget); + } else if (!comboBox->currentIcon.isNull()) { + { + QRect editRect = proxy()->subControlRect(CC_ComboBox, comboBox, SC_ComboBoxEditField, widget); + if (comboBox->direction == Qt::RightToLeft) + editRect.adjust(0, 2, -2, -2); + else + editRect.adjust(2, 2, 0, -2); + painter->save(); + painter->setClipRect(editRect); + if (!comboBox->currentIcon.isNull()) { + QIcon::Mode mode = comboBox->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + QPixmap pixmap = comboBox->currentIcon.pixmap(comboBox->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(comboBox->iconSize.width() + 5); + iconRect = alignedRect(comboBox->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + painter->fillRect(iconRect, option->palette.brush(QPalette::Base)); + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + } + painter->restore(); + } + } else { + QWindowsStyle::drawControl(element, option, painter, widget); + } + + painter->restore(); + } + break; +#endif + default: + QWindowsStyle::drawControl(element, option, painter, widget); + break; + } +} + +/*! + \reimp +*/ +void QPlastiqueStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const +{ + QColor borderColor = option->palette.background().color().darker(178); + QColor alphaCornerColor; + if (widget) { + // ### backgroundrole/foregroundrole should be part of the style option + alphaCornerColor = mergedColors(option->palette.color(widget->backgroundRole()), borderColor); + } else { + alphaCornerColor = mergedColors(option->palette.background().color(), borderColor); + } + QColor gradientStartColor = option->palette.button().color().lighter(104); + QColor gradientStopColor = option->palette.button().color().darker(105); + QColor highlightedGradientStartColor = option->palette.button().color().lighter(101); + QColor highlightedGradientStopColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 85); + QColor highlightedDarkInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 35); + QColor highlightedLightInnerBorderColor = mergedColors(option->palette.button().color(), option->palette.highlight().color(), 58); + + switch (control) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect grooveRegion = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget); + QRect handle = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget); + QRect ticks = proxy()->subControlRect(CC_Slider, option, SC_SliderTickmarks, widget); + bool horizontal = slider->orientation == Qt::Horizontal; + bool ticksAbove = slider->tickPosition & QSlider::TicksAbove; + bool ticksBelow = slider->tickPosition & QSlider::TicksBelow; + + QRect groove; + //The clickable region is 5 px wider than the visible groove for improved usability + if (grooveRegion.isValid()) + groove = horizontal ? grooveRegion.adjusted(0, 5, 0, -5) : grooveRegion.adjusted(5, 0, -5, 0); + + + QPixmap cache; + + if ((option->subControls & SC_SliderGroove) && groove.isValid()) { + BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("slider_groove-%0-%1").arg(ticksAbove).arg(ticksBelow)) + p->fillRect(groove, option->palette.background()); + + // draw groove + if (horizontal) { + p->setPen(borderColor); + const QLine lines[4] = { + QLine(groove.left() + 1, groove.top(), + groove.right() - 1, groove.top()), + QLine(groove.left() + 1, groove.bottom(), + groove.right() - 1, groove.bottom()), + QLine(groove.left(), groove.top() + 1, + groove.left(), groove.bottom() - 1), + QLine(groove.right(), groove.top() + 1, + groove.right(), groove.bottom() - 1) }; + p->drawLines(lines, 4); + + p->setPen(alphaCornerColor); + const QPoint points[4] = { + QPoint(groove.left(), groove.top()), + QPoint(groove.left(), groove.bottom()), + QPoint(groove.right(), groove.top()), + QPoint(groove.right(), groove.bottom()) }; + p->drawPoints(points, 4); + } else { + p->setPen(borderColor); + const QLine lines[4] = { + QLine(groove.left() + 1, groove.top(), + groove.right() - 1, groove.top()), + QLine(groove.left() + 1, groove.bottom(), + groove.right() - 1, groove.bottom()), + QLine(groove.left(), groove.top() + 1, + groove.left(), groove.bottom() - 1), + QLine(groove.right(), groove.top() + 1, + groove.right(), groove.bottom() - 1) }; + p->drawLines(lines, 4); + + p->setPen(alphaCornerColor); + const QPoint points[4] = { + QPoint(groove.left(), groove.top()), + QPoint(groove.right(), groove.top()), + QPoint(groove.left(), groove.bottom()), + QPoint(groove.right(), groove.bottom()) }; + p->drawPoints(points, 4); + } + END_STYLE_PIXMAPCACHE + } + + if ((option->subControls & SC_SliderHandle) && handle.isValid()) { + QString handlePixmapName = QStyleHelper::uniqueName(QLatin1String("slider_handle"), option, handle.size()); + if (ticksAbove && !ticksBelow) + handlePixmapName += QLatin1String("-flipped"); + if ((option->activeSubControls & SC_SliderHandle) && (option->state & State_Sunken)) + handlePixmapName += QLatin1String("-sunken"); + + if (!QPixmapCache::find(handlePixmapName, cache)) { + cache = QPixmap(handle.size()); + cache.fill(Qt::white); + QRect pixmapRect(0, 0, handle.width(), handle.height()); + QPainter handlePainter(&cache); + handlePainter.fillRect(pixmapRect, option->palette.background()); + + // draw handle + if (horizontal) { + QPainterPath path; + if (ticksAbove && !ticksBelow) { + path.moveTo(QPoint(pixmapRect.right(), pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.bottom() - 10)); + path.lineTo(QPoint(pixmapRect.right() - 5, pixmapRect.bottom() - 14)); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.bottom() - 10)); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.bottom())); + } else { + path.moveTo(QPoint(pixmapRect.right(), pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.top() + 10)); + path.lineTo(QPoint(pixmapRect.right() - 5, pixmapRect.top() + 14)); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.top() + 10)); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.top() + 1)); + } + if (slider->state & State_Enabled) { + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + if ((option->activeSubControls & SC_SliderHandle) && (option->state & State_Sunken)) { + gradient.setColorAt(0, gradientStartColor.lighter(110)); + gradient.setColorAt(1, gradientStopColor.lighter(110)); + } else { + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + } + handlePainter.fillPath(path, gradient); + } else { + handlePainter.fillPath(path, slider->palette.background()); + } + } else { + QPainterPath path; + if (ticksAbove && !ticksBelow) { + path.moveTo(QPoint(pixmapRect.right(), pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.right() - 10, pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.right() - 14, pixmapRect.top() + 5)); + path.lineTo(QPoint(pixmapRect.right() - 10, pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.right(), pixmapRect.top() + 1)); + } else { + path.moveTo(QPoint(pixmapRect.left() + 1, pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.left() + 10, pixmapRect.top() + 1)); + path.lineTo(QPoint(pixmapRect.left() + 14, pixmapRect.top() + 5)); + path.lineTo(QPoint(pixmapRect.left() + 10, pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.bottom())); + path.lineTo(QPoint(pixmapRect.left() + 1, pixmapRect.top() + 1)); + } + if (slider->state & State_Enabled) { + QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + pixmapRect.center().x(), pixmapRect.bottom()); + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + handlePainter.fillPath(path, gradient); + } else { + handlePainter.fillPath(path, slider->palette.background()); + } + } + + QImage image; + if (horizontal) { + image = QImage((ticksAbove && !ticksBelow) ? qt_plastique_slider_horizontalhandle_up : qt_plastique_slider_horizontalhandle); + } else { + image = QImage((ticksAbove && !ticksBelow) ? qt_plastique_slider_verticalhandle_left : qt_plastique_slider_verticalhandle); + } + + image.setColor(1, borderColor.rgba()); + image.setColor(2, gradientStartColor.rgba()); + image.setColor(3, alphaCornerColor.rgba()); + if (option->state & State_Enabled) { + image.setColor(4, 0x80ffffff); + image.setColor(5, 0x25000000); + } + handlePainter.drawImage(pixmapRect, image); + handlePainter.end(); + QPixmapCache::insert(handlePixmapName, cache); + } + + painter->drawPixmap(handle.topLeft(), cache); + + 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); + } + } + + if (option->subControls & SC_SliderTickmarks) { + QPen oldPen = painter->pen(); + painter->setPen(borderColor); + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, 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 <= 0) + interval = 1; + + int v = slider->minimum; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + QVarLengthArray<QLine, 32> lines; + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int pos = sliderPositionFromValue(slider->minimum, slider->maximum, + v_, (horizontal + ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown) + len / 2; + + int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0); + + if (horizontal) { + if (ticksAbove) { + lines.append(QLine(pos, slider->rect.top() + extra, + pos, slider->rect.top() + tickSize)); + } + if (ticksBelow) { + lines.append(QLine(pos, slider->rect.bottom() - extra, + pos, slider->rect.bottom() - tickSize)); + } + } else { + if (ticksAbove) { + lines.append(QLine(slider->rect.left() + extra, pos, + slider->rect.left() + tickSize, pos)); + } + if (ticksBelow) { + lines.append(QLine(slider->rect.right() - extra, pos, + slider->rect.right() - tickSize, pos)); + } + } + + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + painter->drawLines(lines.constData(), lines.size()); + painter->setPen(oldPen); + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + painter->save(); + bool upSunken = (spinBox->activeSubControls & SC_SpinBoxUp) && (spinBox->state & (State_Sunken | State_On)); + bool downSunken = (spinBox->activeSubControls & SC_SpinBoxDown) && (spinBox->state & (State_Sunken | State_On)); + bool reverse = (spinBox->direction == Qt::RightToLeft); + + // Rects + QRect upRect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget); + QRect downRect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget); + QRect buttonRect = upRect | downRect; + + // Brushes + QBrush corner = qMapBrushToRect(option->palette.shadow(), buttonRect); + qBrushSetAlphaF(&corner, qreal(0.25)); + QBrush border = qMapBrushToRect(option->palette.shadow(), buttonRect); + qBrushSetAlphaF(&border, qreal(0.4)); + + QVarLengthArray<QPoint, 4> points; + + Q_D(const QPlastiqueStyle); + if (spinBox->buttonSymbols == QAbstractSpinBox::NoButtons) { + QRect filledRect = option->rect.adjusted(1, 1, -1, -1); + QBrush baseBrush = qMapBrushToRect(option->palette.base(), filledRect); + painter->setBrushOrigin(filledRect.topLeft()); + painter->fillRect(filledRect.adjusted(1, 1, -1, -1), baseBrush); + qt_plastique_draw_frame(painter, option->rect, option, QFrame::Sunken); + } else { + d->drawPartialFrame(painter, + option, + proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxEditField, widget), + widget); + } + // Paint buttons + if (spinBox->buttonSymbols == QAbstractSpinBox::NoButtons) { + painter->restore(); + break; + } + // Button outlines + painter->setPen(QPen(border, 0)); + if (!reverse) + painter->drawLine(buttonRect.topLeft() + QPoint(0, 1), buttonRect.bottomLeft() + QPoint(0, -1)); + else + painter->drawLine(buttonRect.topRight() + QPoint(0, -1), buttonRect.bottomRight() + QPoint(0, 1)); + + if (!reverse) { + const QLine lines[4] = { + QLine(upRect.left(), upRect.top(), upRect.right() - 2, upRect.top()), + QLine(upRect.left() + 1, upRect.bottom(), upRect.right() - 1, upRect.bottom()), + QLine(downRect.left(), downRect.bottom(), downRect.right() - 2, downRect.bottom()), + QLine(buttonRect.right(), buttonRect.top() + 2, buttonRect.right(), buttonRect.bottom() - 2) }; + painter->drawLines(lines, 4); + + points.append(QPoint(upRect.right() - 1, upRect.top() + 1)); + points.append(QPoint(downRect.right() - 1, downRect.bottom() - 1)); + painter->drawPoints(points.constData(), points.size()); + points.clear(); + painter->setPen(QPen(corner, 0)); + points.append(QPoint(upRect.right() - 1, upRect.top())); + points.append(QPoint(upRect.right(), upRect.top() + 1)); + points.append(QPoint(upRect.right(), downRect.bottom() - 1)); + points.append(QPoint(upRect.right() - 1, downRect.bottom())); + } else { + const QLine lines[4] = { + QLine(upRect.right(), upRect.top(), upRect.left() + 2, upRect.top()), + QLine(upRect.right() - 1, upRect.bottom(), upRect.left() + 1, upRect.bottom()), + QLine(downRect.right(), downRect.bottom(), downRect.left() + 2, downRect.bottom()), + QLine(buttonRect.left(), buttonRect.top() + 2, buttonRect.left(), buttonRect.bottom() - 2) }; + painter->drawLines(lines, 4); + + points.append(QPoint(upRect.left() + 1, upRect.top() + 1)); + points.append(QPoint(downRect.left() + 1, downRect.bottom() - 1)); + painter->drawPoints(points.constData(), points.size()); + points.clear(); + painter->setPen(QPen(corner, 0)); + points.append(QPoint(upRect.left() + 1, upRect.top())); + points.append(QPoint(upRect.left(), upRect.top() + 1)); + points.append(QPoint(upRect.left(), downRect.bottom() - 1)); + points.append(QPoint(upRect.left() + 1, downRect.bottom())); + } + painter->drawPoints(points.constData(), points.size()); + points.clear(); + + // Button colors + QBrush buttonGradientBrush; + QBrush leftLineGradientBrush; + QBrush rightLineGradientBrush; + QBrush sunkenButtonGradientBrush; + QBrush sunkenLeftLineGradientBrush; + QBrush sunkenRightLineGradientBrush; + QBrush buttonBrush = qMapBrushToRect(option->palette.button(), buttonRect); + if (buttonBrush.gradient() || !buttonBrush.texture().isNull()) { + buttonGradientBrush = buttonBrush; + sunkenButtonGradientBrush = qBrushDark(buttonBrush, 108); + leftLineGradientBrush = qBrushLight(buttonBrush, 105); + rightLineGradientBrush = qBrushDark(buttonBrush, 105); + sunkenLeftLineGradientBrush = qBrushDark(buttonBrush, 110); + sunkenRightLineGradientBrush = qBrushDark(buttonBrush, 106); + } else { + // Generate gradients + QLinearGradient buttonGradient(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient.setColorAt(0.0, buttonBrush.color().lighter(104)); + buttonGradient.setColorAt(1.0, buttonBrush.color().darker(110)); + buttonGradientBrush = QBrush(buttonGradient); + + QLinearGradient buttonGradient2(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient2.setColorAt(0.0, buttonBrush.color().darker(113)); + buttonGradient2.setColorAt(1.0, buttonBrush.color().darker(103)); + sunkenButtonGradientBrush = QBrush(buttonGradient2); + + QLinearGradient buttonGradient3(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient3.setColorAt(0.0, buttonBrush.color().lighter(105)); + buttonGradient3.setColorAt(1.0, buttonBrush.color()); + leftLineGradientBrush = QBrush(buttonGradient3); + + QLinearGradient buttonGradient4(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient4.setColorAt(0.0, buttonBrush.color()); + buttonGradient4.setColorAt(1.0, buttonBrush.color().darker(110)); + rightLineGradientBrush = QBrush(buttonGradient4); + + QLinearGradient buttonGradient5(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient5.setColorAt(0.0, buttonBrush.color().darker(113)); + buttonGradient5.setColorAt(1.0, buttonBrush.color().darker(107)); + sunkenLeftLineGradientBrush = QBrush(buttonGradient5); + + QLinearGradient buttonGradient6(buttonRect.topLeft(), buttonRect.bottomLeft()); + buttonGradient6.setColorAt(0.0, buttonBrush.color().darker(108)); + buttonGradient6.setColorAt(1.0, buttonBrush.color().darker(103)); + sunkenRightLineGradientBrush = QBrush(buttonGradient6); + } + + // Main fill + painter->fillRect(upRect.adjusted(2, 2, -2, -2), + qMapBrushToRect(upSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, upRect)); + painter->fillRect(downRect.adjusted(2, 2, -2, -2), + qMapBrushToRect(downSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, downRect)); + + // Top line + painter->setPen(QPen(qBrushLight(qMapBrushToRect(upSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, upRect), 105), 0)); + if (!reverse) { + painter->drawLine(upRect.left() + 1, upRect.top() + 1, + upRect.right() - 2, upRect.top() + 1); + } else { + painter->drawLine(upRect.right() - 1, upRect.top() + 1, + upRect.left() + 2, upRect.top() + 1); + } + painter->setPen(QPen(qBrushLight(qMapBrushToRect(downSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, downRect), 105), 0)); + if (!reverse) { + painter->drawLine(downRect.left() + 1, downRect.top() + 1, + downRect.right() - 1, downRect.top() + 1); + } else { + painter->drawLine(downRect.right() - 1, downRect.top() + 1, + downRect.left() + 1, downRect.top() + 1); + } + + // Left line + painter->setPen(QPen(qMapBrushToRect(upSunken ? sunkenLeftLineGradientBrush + : leftLineGradientBrush, upRect), 1)); + if (!reverse) { + painter->drawLine(upRect.left() + 1, upRect.top() + 2, + upRect.left() + 1, upRect.bottom() - 1); + } else { + painter->drawLine(upRect.left() + 1, upRect.top() + 2, + upRect.left() + 1, upRect.bottom() - 1); + } + painter->setPen(QPen(qMapBrushToRect(downSunken ? sunkenLeftLineGradientBrush + : leftLineGradientBrush, downRect), 1)); + if (!reverse) { + painter->drawLine(downRect.left() + 1, downRect.top() + 2, + downRect.left() + 1, downRect.bottom() - 1); + } else { + painter->drawLine(downRect.left() + 1, downRect.top() + 1, + downRect.left() + 1, downRect.bottom() - 2); + } + + // Bottom line + painter->setPen(QPen(qBrushDark(qMapBrushToRect(upSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, upRect), 105), 0)); + if (!reverse) { + painter->drawLine(upRect.left() + 2, upRect.bottom() - 1, + upRect.right() - 1, upRect.bottom() - 1); + } else { + painter->drawLine(upRect.right() - 2, upRect.bottom() - 1, + upRect.left() + 1, upRect.bottom() - 1); + } + painter->setPen(QPen(qBrushDark(qMapBrushToRect(downSunken ? sunkenButtonGradientBrush + : buttonGradientBrush, downRect), 105), 0)); + if (!reverse) { + painter->drawLine(downRect.left() + 2, downRect.bottom() - 1, + downRect.right() - 2, downRect.bottom() - 1); + } else { + painter->drawLine(downRect.right() - 2, downRect.bottom() - 1, + downRect.left() + 2, downRect.bottom() - 1); + } + + // Right line + painter->setPen(QPen(qMapBrushToRect(upSunken ? sunkenRightLineGradientBrush + : rightLineGradientBrush, upRect), 1)); + if (!reverse) { + painter->drawLine(upRect.right() - 1, upRect.top() + 2, + upRect.right() - 1, upRect.bottom() - 1); + } else { + painter->drawLine(upRect.right() - 1, upRect.top() + 2, + upRect.right() - 1, upRect.bottom() - 1); + } + painter->setPen(QPen(qMapBrushToRect(downSunken ? sunkenRightLineGradientBrush + : rightLineGradientBrush, downRect), 1)); + if (!reverse) { + painter->drawLine(downRect.right() - 1, downRect.top() + 1, + downRect.right() - 1, downRect.bottom() - 2); + } else { + painter->drawLine(downRect.right() - 1, downRect.top() + 2, + downRect.right() - 1, downRect.bottom() - 1); + } + + QBrush indicatorBrush = qMapBrushToRect(option->palette.buttonText(), buttonRect); + painter->setPen(QPen(indicatorBrush, 0)); + if (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) { + QPoint center; + if (spinBox->subControls & SC_SpinBoxUp) { + // ....... + // ...X... + // ...X... + // .XXXXX. + // ...X... + // ...X... + // ....... + center = upRect.center(); + if (upSunken) { + ++center.rx(); + ++center.ry(); + } + painter->drawLine(center.x(), center.y() - 2, center.x(), center.y() + 2); + painter->drawLine(center.x() - 2, center.y(), center.x() + 2, center.y()); + } + if (spinBox->subControls & SC_SpinBoxDown) { + // ....... + // ....... + // ....... + // .XXXXX. + // ....... + // ....... + // ....... + center = downRect.center(); + if (downSunken) { + ++center.rx(); + ++center.ry(); + } + painter->drawLine(center.x() - 2, center.y(), center.x() + 2, center.y()); + } + } else { + int offset; + int centerX; + if (spinBox->subControls & SC_SpinBoxUp) { + // ........... + // .....X..... + // ....XXX.... + // ...XXXXX... + // ..XXXXXXX.. + // ........... + offset = upSunken ? 1 : 0; + QRect upArrowRect(upRect.center().x() - 3 + offset, upRect.center().y() - 2 + offset, 7, 4); + centerX = upArrowRect.center().x(); + painter->drawPoint(centerX, upArrowRect.top()); + const QLine lines[3] = { + QLine(centerX - 1, upArrowRect.top() + 1, centerX + 1, upArrowRect.top() + 1), + QLine(centerX - 2, upArrowRect.top() + 2, centerX + 2, upArrowRect.top() + 2), + QLine(centerX - 3, upArrowRect.top() + 3, centerX + 3, upArrowRect.top() + 3) }; + painter->drawLines(lines, 3); + } + if (spinBox->subControls & SC_SpinBoxDown) { + // ........... + // ..XXXXXXX.. + // ...XXXXX... + // ....XXX.... + // .....X..... + // ........... + offset = downSunken ? 1 : 0; + QRect downArrowRect(downRect.center().x() - 3 + offset, downRect.center().y() - 2 + offset + 1, 7, 4); + centerX = downArrowRect.center().x(); + const QLine lines[3] = { + QLine(centerX - 3, downArrowRect.top(), centerX + 3, downArrowRect.top()), + QLine(centerX - 2, downArrowRect.top() + 1, centerX + 2, downArrowRect.top() + 1), + QLine(centerX - 1, downArrowRect.top() + 2, centerX + 1, downArrowRect.top() + 2) }; + painter->drawLines(lines, 3); + painter->drawPoint(centerX, downArrowRect.top() + 3); + } + } + painter->restore(); + } + break; +#endif // QT_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + bool sunken = comboBox->state & State_On; // play dead if combobox has no items + bool reverse = comboBox->direction == Qt::RightToLeft; + int menuButtonWidth = 16; + int xoffset = sunken ? (reverse ? -1 : 1) : 0; + int yoffset = sunken ? 1 : 0; + QRect rect = comboBox->rect; + QPen oldPen = painter->pen(); + + // Fill + if (comboBox->editable) { + // Button colors + QBrush alphaCornerBrush = qBrushDark(option->palette.button(), 165); + qBrushSetAlphaF(&alphaCornerBrush, 0.5); + QBrush buttonGradientBrush; + QBrush leftLineGradientBrush; + QBrush rightLineGradientBrush; + QBrush sunkenButtonGradientBrush; + QBrush sunkenLeftLineGradientBrush; + QBrush sunkenRightLineGradientBrush; + QBrush button = option->palette.button(); + if (button.gradient() || !button.texture().isNull()) { + buttonGradientBrush = button; + sunkenButtonGradientBrush = qBrushDark(button, 108); + leftLineGradientBrush = qBrushLight(button, 105); + rightLineGradientBrush = qBrushDark(button, 105); + sunkenLeftLineGradientBrush = qBrushDark(button, 110); + sunkenRightLineGradientBrush = qBrushDark(button, 106); + } else { + // Generate gradients + QLinearGradient buttonGradient(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient.setColorAt(0.0, button.color().lighter(104)); + buttonGradient.setColorAt(1.0, button.color().darker(110)); + buttonGradientBrush = QBrush(buttonGradient); + + QLinearGradient buttonGradient2(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient2.setColorAt(0.0, button.color().darker(113)); + buttonGradient2.setColorAt(1.0, button.color().darker(103)); + sunkenButtonGradientBrush = QBrush(buttonGradient2); + + QLinearGradient buttonGradient3(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient3.setColorAt(0.0, button.color().lighter(105)); + buttonGradient3.setColorAt(1.0, button.color()); + leftLineGradientBrush = QBrush(buttonGradient3); + + QLinearGradient buttonGradient4(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient4.setColorAt(0.0, button.color()); + buttonGradient4.setColorAt(1.0, button.color().darker(110)); + rightLineGradientBrush = QBrush(buttonGradient4); + + QLinearGradient buttonGradient5(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient5.setColorAt(0.0, button.color().darker(113)); + buttonGradient5.setColorAt(1.0, button.color().darker(107)); + sunkenLeftLineGradientBrush = QBrush(buttonGradient5); + + QLinearGradient buttonGradient6(option->rect.topLeft(), option->rect.bottomLeft()); + buttonGradient6.setColorAt(0.0, button.color().darker(108)); + buttonGradient6.setColorAt(1.0, button.color().darker(103)); + sunkenRightLineGradientBrush = QBrush(buttonGradient6); + } + + // ComboBox starts with a lineedit in place already. + QRect buttonRect; + if (!reverse) { + buttonRect.setRect(rect.right() - menuButtonWidth, rect.top(), menuButtonWidth + 1, rect.height()); + } else { + buttonRect.setRect(rect.left(), rect.top(), menuButtonWidth + 1, rect.height()); + } + + Q_D(const QPlastiqueStyle); + d->drawPartialFrame(painter, + option, + proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget), + widget); + + QBrush border = qMapBrushToRect(option->palette.shadow(), buttonRect); + qBrushSetAlphaF(&border, qreal(0.4)); + painter->setPen(QPen(border, 0)); + if (!reverse) + painter->drawLine(buttonRect.topLeft() + QPoint(0, 1), buttonRect.bottomLeft() + QPoint(0, -1)); + else + painter->drawLine(buttonRect.topRight() + QPoint(0, -1), buttonRect.bottomRight() + QPoint(0, 1)); + + // Outline the button border + if (!reverse) { + const QLine lines[3] = { + QLine(buttonRect.left(), buttonRect.top(), + buttonRect.right() - 2, buttonRect.top()), + QLine(buttonRect.right(), buttonRect.top() + 2, + buttonRect.right(), buttonRect.bottom() - 2), + QLine(buttonRect.left(), buttonRect.bottom(), + buttonRect.right() - 2, buttonRect.bottom()) }; + painter->drawLines(lines, 3); + { + const QPoint points[2] = { + QPoint(buttonRect.right() - 1, buttonRect.top() + 1), + QPoint(buttonRect.right() - 1, buttonRect.bottom() - 1) }; + painter->drawPoints(points, 2); + } + + QBrush corner = qMapBrushToRect(option->palette.shadow(), buttonRect); + qBrushSetAlphaF(&corner, qreal(0.16)); + painter->setPen(QPen(corner, 0)); + { + const QPoint points[4] = { + QPoint(buttonRect.right() - 1, buttonRect.top()), + QPoint(buttonRect.right() - 1, buttonRect.bottom()), + QPoint(buttonRect.right(), buttonRect.top() + 1), + QPoint(buttonRect.right(), buttonRect.bottom() - 1) }; + painter->drawPoints(points, 4); + } + } else { + const QLine lines[3] = { + QLine(buttonRect.right(), buttonRect.top(), + buttonRect.left() + 2, buttonRect.top()), + QLine(buttonRect.left(), buttonRect.top() + 2, + buttonRect.left(), buttonRect.bottom() - 2), + QLine(buttonRect.right(), buttonRect.bottom(), + buttonRect.left() + 2, buttonRect.bottom()) }; + painter->drawLines(lines, 3); + { + const QPoint points[2] = { + QPoint(buttonRect.left() + 1, buttonRect.top() + 1), + QPoint(buttonRect.left() + 1, buttonRect.bottom() - 1) }; + painter->drawPoints(points, 2); + } + + QBrush corner = qMapBrushToRect(option->palette.shadow(), buttonRect); + qBrushSetAlphaF(&corner, qreal(0.16)); + painter->setPen(QPen(corner, 0)); + { + const QPoint points[4] = { + QPoint(buttonRect.left() + 1, buttonRect.top()), + QPoint(buttonRect.left() + 1, buttonRect.bottom()), + QPoint(buttonRect.left(), buttonRect.top() + 1), + QPoint(buttonRect.left(), buttonRect.bottom() - 1) }; + painter->drawPoints(points, 4); + } + } + + QRect fillRect = buttonRect.adjusted(2, 2, -2, -2); + // Main fill + painter->fillRect(fillRect, + qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, option->rect)); + + // Top line + painter->setPen(QPen(qBrushLight(qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, option->rect), 105), 0)); + if (!reverse) { + painter->drawLine(QPointF(buttonRect.left() + 1, buttonRect.top() + 1), + QPointF(buttonRect.right() - 2, buttonRect.top() + 1)); + } else { + painter->drawLine(QPointF(buttonRect.right() - 1, buttonRect.top() + 1), + QPointF(buttonRect.left() + 2, buttonRect.top() + 1)); + } + + // Bottom line + painter->setPen(QPen(qBrushDark(qMapBrushToRect(sunken ? sunkenButtonGradientBrush + : buttonGradientBrush, option->rect), 105), 0)); + if (!reverse) { + painter->drawLine(QPointF(buttonRect.left() + 1, buttonRect.bottom() - 1), + QPointF(buttonRect.right() - 2, buttonRect.bottom() - 1)); + } else { + painter->drawLine(QPointF(buttonRect.right() - 1, buttonRect.bottom() - 1), + QPointF(buttonRect.left() + 2, buttonRect.bottom() - 1)); + } + + // Left line + painter->setPen(QPen(qMapBrushToRect(sunken ? sunkenLeftLineGradientBrush + : leftLineGradientBrush, option->rect), 1)); + if (!reverse) { + painter->drawLine(QPointF(buttonRect.left() + 1, buttonRect.top() + 2), + QPointF(buttonRect.left() + 1, buttonRect.bottom() - 2)); + } else { + painter->drawLine(QPointF(buttonRect.left() + 1, buttonRect.top() + 2), + QPointF(buttonRect.left() + 1, buttonRect.bottom() - 2)); + } + + // Right line + painter->setPen(QPen(qMapBrushToRect(sunken ? sunkenRightLineGradientBrush + : rightLineGradientBrush, option->rect), 1)); + if (!reverse) { + painter->drawLine(QPointF(buttonRect.right() - 1, buttonRect.top() + 2), + QPointF(buttonRect.right() - 1, buttonRect.bottom() - 2)); + } else { + painter->drawLine(QPointF(buttonRect.right() - 1, buttonRect.top() + 2), + QPointF(buttonRect.right() - 1, buttonRect.bottom() - 2)); + } + } else { + // Start with a standard panel button fill + QStyleOptionButton buttonOption; + buttonOption.QStyleOption::operator=(*comboBox); + if (!sunken) { + buttonOption.state &= ~State_Sunken; + } + proxy()->drawPrimitive(PE_PanelButtonCommand, &buttonOption, painter, widget); + + // Draw the menu button separator line + QBrush border = qMapBrushToRect(option->palette.shadow(), rect); + qBrushSetAlphaF(&border, qreal(0.35)); + painter->setPen(QPen(border, 0)); + if (!reverse) { + painter->drawLine(rect.right() - menuButtonWidth + xoffset, rect.top() + 1, + rect.right() - menuButtonWidth + xoffset, rect.bottom() - 1); + } else { + painter->drawLine(rect.left() + menuButtonWidth + xoffset, rect.top() + 1, + rect.left() + menuButtonWidth + xoffset, rect.bottom() - 1); + } + } + + // Draw the little arrow + if (comboBox->subControls & SC_ComboBoxArrow) { + int left = !reverse ? rect.right() - menuButtonWidth : rect.left(); + int right = !reverse ? rect.right() : rect.left() + menuButtonWidth; + QRect arrowRect((left + right) / 2 - 3 + xoffset, + rect.center().y() - 1 + yoffset, 7, 4); + painter->setPen(QPen(qMapBrushToRect(option->palette.buttonText(), rect), 0)); + const QLine lines[3] = { + QLine(arrowRect.topLeft(), arrowRect.topRight()), + QLine(arrowRect.left() + 1, arrowRect.top() + 1, + arrowRect.right() - 1, arrowRect.top() + 1), + QLine(arrowRect.left() + 2, arrowRect.top() + 2, + arrowRect.right() - 2, arrowRect.top() + 2) }; + painter->drawLines(lines, 3); + painter->drawPoint(arrowRect.center().x(), arrowRect.bottom()); + } + + // Draw the focus rect + if ((option->state & State_HasFocus) && !comboBox->editable + && ((option->state & State_KeyboardFocusChange) || styleHint(SH_UnderlineShortcut, option, widget))) { + QStyleOptionFocusRect focus; + focus.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget) + .adjusted(-2, 0, 2, 0); + proxy()->drawPrimitive(PE_FrameFocusRect, &focus, painter, widget); + } + + painter->setPen(oldPen); + } + break; +#endif // QT_NO_COMBOBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *titleBar = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + painter->save(); + bool active = (titleBar->titleBarState & State_Active); + QRect fullRect = titleBar->rect; + + // ### use palette colors instead + QColor titleBarGradientStart(active ? 0x3b508a : 0x6e6e6e); + QColor titleBarGradientStop(active ? 0x5d6e9e : 0x818181); + QColor titleBarFrameBorder(0x393939); + QColor titleBarAlphaCorner(active ? 0x4b5e7f : 0x6a6a6a); + QColor titleBarInnerTopLine(active ? 0x8e98ba : 0xa4a4a4); + QColor titleBarInnerInnerTopLine(active ? 0x57699b : 0x808080); + QColor leftCorner(active ? 0x6f7ea8 : 0x8e8e8e); + QColor rightCorner(active ? 0x44537d : 0x676767); + QColor textColor(active ? 0x282e40 : 0x282e40); + QColor textAlphaColor(active ? 0x3f4862 : 0x3f4862); + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + QStyleOptionDockWidgetV2 dockwidget; + dockwidget.QStyleOption::operator=(*option); + dockwidget.title = titleBar->text; + proxy()->drawControl(CE_DockWidgetTitle, &dockwidget, painter, widget); + } else +#endif // QT3_SUPPORT + + { + // Fill title bar gradient + qt_plastique_draw_gradient(painter, option->rect.adjusted(1, 1, -1, 0), + titleBarGradientStart, + titleBarGradientStop); + + // Frame and rounded corners + painter->setPen(titleBarFrameBorder); + + // top border line + { + const QLine lines[3] = { + QLine(fullRect.left() + 2, fullRect.top(), fullRect.right() - 2, fullRect.top()), + QLine(fullRect.left(), fullRect.top() + 2, fullRect.left(), fullRect.bottom()), + QLine(fullRect.right(), fullRect.top() + 2, fullRect.right(), fullRect.bottom()) }; + painter->drawLines(lines, 3); + const QPoint points[2] = { + QPoint(fullRect.left() + 1, fullRect.top() + 1), + QPoint(fullRect.right() - 1, fullRect.top() + 1) }; + painter->drawPoints(points, 2); + } + + // alpha corners + painter->setPen(titleBarAlphaCorner); + { + const QPoint points[4] = { + QPoint(fullRect.left() + 2, fullRect.top() + 1), + QPoint(fullRect.left() + 1, fullRect.top() + 2), + QPoint(fullRect.right() - 2, fullRect.top() + 1), + QPoint(fullRect.right() - 1, fullRect.top() + 2) }; + painter->drawPoints(points, 4); + } + + // inner top line + painter->setPen(titleBarInnerTopLine); + painter->drawLine(fullRect.left() + 3, fullRect.top() + 1, fullRect.right() - 3, fullRect.top() + 1); + + // inner inner top line + painter->setPen(titleBarInnerInnerTopLine); + painter->drawLine(fullRect.left() + 2, fullRect.top() + 2, fullRect.right() - 2, fullRect.top() + 2); + + // left and right inner + painter->setPen(leftCorner); + painter->drawLine(fullRect.left() + 1, fullRect.top() + 3, fullRect.left() + 1, fullRect.bottom()); + painter->setPen(rightCorner); + painter->drawLine(fullRect.right() - 1, fullRect.top() + 3, fullRect.right() - 1, fullRect.bottom()); + + if (titleBar->titleBarState & Qt::WindowMinimized) { + painter->setPen(titleBarFrameBorder); + painter->drawLine(fullRect.left() + 2, fullRect.bottom(), fullRect.right() - 2, fullRect.bottom()); + { + const QPoint points[2] = { + QPoint(fullRect.left() + 1, fullRect.bottom() - 1), + QPoint(fullRect.right() - 1, fullRect.bottom() - 1) }; + painter->drawPoints(points, 2); + } + painter->setPen(rightCorner); + painter->drawLine(fullRect.left() + 2, fullRect.bottom() - 1, fullRect.right() - 2, fullRect.bottom() - 1); + painter->setPen(titleBarAlphaCorner); + { + const QPoint points[4] = { + QPoint(fullRect.left() + 1, fullRect.bottom() - 2), + QPoint(fullRect.left() + 2, fullRect.bottom() - 1), + QPoint(fullRect.right() - 1, fullRect.bottom() - 2), + QPoint(fullRect.right() - 2, fullRect.bottom() - 1) }; + painter->drawPoints(points, 4); + } + } + // draw title + QRect textRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarLabel, widget); + + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + painter->setPen(titleBar->palette.text().color()); + + // Attempt to align left if there is not enough room for the title + // text. Otherwise, align center. QWorkspace does elliding for us, + // and it doesn't know about the bold title, so we need to work + // around some of the width mismatches. + bool tooWide = (QFontMetrics(font).width(titleBar->text) > textRect.width()); + QTextOption option((tooWide ? Qt::AlignLeft : Qt::AlignHCenter) | Qt::AlignVCenter); + option.setWrapMode(QTextOption::NoWrap); + + painter->drawText(textRect.adjusted(1, 1, 1, 1), titleBar->text, option); + painter->setPen(titleBar->palette.highlightedText().color()); + painter->drawText(textRect, titleBar->text, option); + } + + // min button + if ((titleBar->subControls & SC_TitleBarMinButton) + && (titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint) + && !(titleBar->titleBarState & Qt::WindowMinimized)) { + bool hover = (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarMinButton) && (titleBar->state & State_Sunken); + + QRect minButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMinButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, minButtonRect, hover, sunken); + + int xoffset = minButtonRect.width() / 3; + int yoffset = minButtonRect.height() / 3; + + QRect minButtonIconRect(minButtonRect.left() + xoffset, minButtonRect.top() + yoffset, + minButtonRect.width() - xoffset * 2, minButtonRect.height() - yoffset * 2); + + painter->setPen(textColor); + { + const QLine lines[2] = { + QLine(minButtonIconRect.center().x() - 2, + minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() + 3, + minButtonIconRect.center().y() + 3), + QLine(minButtonIconRect.center().x() - 2, + minButtonIconRect.center().y() + 4, + minButtonIconRect.center().x() + 3, + minButtonIconRect.center().y() + 4) }; + painter->drawLines(lines, 2); + } + painter->setPen(textAlphaColor); + { + const QLine lines[2] = { + QLine(minButtonIconRect.center().x() - 3, + minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() - 3, + minButtonIconRect.center().y() + 4), + QLine(minButtonIconRect.center().x() + 4, + minButtonIconRect.center().y() + 3, + minButtonIconRect.center().x() + 4, + minButtonIconRect.center().y() + 4) }; + painter->drawLines(lines, 2); + } + } + + // max button + if ((titleBar->subControls & SC_TitleBarMaxButton) + && (titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint) + && !(titleBar->titleBarState & Qt::WindowMaximized)) { + bool hover = (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarMaxButton) && (titleBar->state & State_Sunken); + + QRect maxButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarMaxButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, maxButtonRect, hover, sunken); + + int xoffset = maxButtonRect.width() / 3; + int yoffset = maxButtonRect.height() / 3; + + QRect maxButtonIconRect(maxButtonRect.left() + xoffset, maxButtonRect.top() + yoffset, + maxButtonRect.width() - xoffset * 2, maxButtonRect.height() - yoffset * 2); + + painter->setPen(textColor); + painter->drawRect(maxButtonIconRect.adjusted(0, 0, -1, -1)); + painter->drawLine(maxButtonIconRect.left() + 1, maxButtonIconRect.top() + 1, + maxButtonIconRect.right() - 1, maxButtonIconRect.top() + 1); + painter->setPen(textAlphaColor); + const QPoint points[4] = { + maxButtonIconRect.topLeft(), maxButtonIconRect.topRight(), + maxButtonIconRect.bottomLeft(), maxButtonIconRect.bottomRight() }; + painter->drawPoints(points, 4); + } + + // close button + if (titleBar->subControls & SC_TitleBarCloseButton && titleBar->titleBarFlags & Qt::WindowSystemMenuHint) { + bool hover = (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarCloseButton) && (titleBar->state & State_Sunken); + + QRect closeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarCloseButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, closeButtonRect, hover, sunken); + + int xoffset = closeButtonRect.width() / 3; + int yoffset = closeButtonRect.height() / 3; + + QRect closeIconRect(closeButtonRect.left() + xoffset, closeButtonRect.top() + yoffset, + closeButtonRect.width() - xoffset * 2, closeButtonRect.height() - yoffset * 2); + + painter->setPen(textAlphaColor); + { + const QLine lines[4] = { + QLine(closeIconRect.left() + 1, closeIconRect.top(), + closeIconRect.right(), closeIconRect.bottom() - 1), + QLine(closeIconRect.left(), closeIconRect.top() + 1, + closeIconRect.right() - 1, closeIconRect.bottom()), + QLine(closeIconRect.right() - 1, closeIconRect.top(), + closeIconRect.left(), closeIconRect.bottom() - 1), + QLine(closeIconRect.right(), closeIconRect.top() + 1, + closeIconRect.left() + 1, closeIconRect.bottom()) }; + painter->drawLines(lines, 4); + const QPoint points[4] = { + closeIconRect.topLeft(), closeIconRect.topRight(), + closeIconRect.bottomLeft(), closeIconRect.bottomRight() }; + painter->drawPoints(points, 4); + } + painter->setPen(textColor); + { + const QLine lines[2] = { + QLine(closeIconRect.left() + 1, closeIconRect.top() + 1, + closeIconRect.right() - 1, closeIconRect.bottom() - 1), + QLine(closeIconRect.left() + 1, closeIconRect.bottom() - 1, + closeIconRect.right() - 1, closeIconRect.top() + 1) }; + painter->drawLines(lines, 2); + } + } + + // normalize button + if ((titleBar->subControls & SC_TitleBarNormalButton) && + (((titleBar->titleBarFlags & Qt::WindowMinimizeButtonHint) && + (titleBar->titleBarState & Qt::WindowMinimized)) || + ((titleBar->titleBarFlags & Qt::WindowMaximizeButtonHint) && + (titleBar->titleBarState & Qt::WindowMaximized)))) { + bool hover = (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarNormalButton) && (titleBar->state & State_Sunken); + + QRect normalButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarNormalButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, normalButtonRect, hover, sunken); + int xoffset = int(normalButtonRect.width() / 3.5); + int yoffset = int(normalButtonRect.height() / 3.5); + + QRect normalButtonIconRect(normalButtonRect.left() + xoffset, normalButtonRect.top() + yoffset, + normalButtonRect.width() - xoffset * 2, normalButtonRect.height() - yoffset * 2); + + QRect frontWindowRect = normalButtonIconRect.adjusted(0, 3, -3, 0); + painter->setPen(textColor); + painter->drawRect(frontWindowRect.adjusted(0, 0, -1, -1)); + painter->drawLine(frontWindowRect.left() + 1, frontWindowRect.top() + 1, + frontWindowRect.right() - 1, frontWindowRect.top() + 1); + painter->setPen(textAlphaColor); + { + const QPoint points[4] = { + frontWindowRect.topLeft(), frontWindowRect.topRight(), + frontWindowRect.bottomLeft(), frontWindowRect.bottomRight() }; + painter->drawPoints(points, 4); + } + + QRect backWindowRect = normalButtonIconRect.adjusted(3, 0, 0, -3); + QRegion clipRegion = backWindowRect; + clipRegion -= frontWindowRect; + painter->save(); + painter->setClipRegion(clipRegion); + painter->setPen(textColor); + painter->drawRect(backWindowRect.adjusted(0, 0, -1, -1)); + painter->drawLine(backWindowRect.left() + 1, backWindowRect.top() + 1, + backWindowRect.right() - 1, backWindowRect.top() + 1); + painter->setPen(textAlphaColor); + { + const QPoint points[4] = { + backWindowRect.topLeft(), backWindowRect.topRight(), + backWindowRect.bottomLeft(), backWindowRect.bottomRight() }; + painter->drawPoints(points, 4); + } + painter->restore(); + } + + // context help button + if (titleBar->subControls & SC_TitleBarContextHelpButton + && (titleBar->titleBarFlags & Qt::WindowContextHelpButtonHint)) { + bool hover = (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarContextHelpButton) && (titleBar->state & State_Sunken); + + QRect contextHelpButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarContextHelpButton, widget); + + qt_plastique_draw_mdibutton(painter, titleBar, contextHelpButtonRect, hover, sunken); + + QColor blend; + // ### Use palette colors + if (active) { + blend = mergedColors(QColor(hover ? 0x7d8bb1 : 0x55689a), + QColor(hover ? 0x939ebe : 0x7381ab)); + } else { + blend = mergedColors(QColor(hover ? 0x9e9e9e : 0x818181), + QColor(hover ? 0xababab : 0x929292)); + } + QImage image(qt_titlebar_context_help); + image.setColor(4, textColor.rgba()); + image.setColor(3, mergedColors(blend, textColor, 30).rgba()); + image.setColor(2, mergedColors(blend, textColor, 70).rgba()); + image.setColor(1, mergedColors(blend, textColor, 90).rgba()); + + painter->drawImage(contextHelpButtonRect, image); + } + + // shade button + if (titleBar->subControls & SC_TitleBarShadeButton) { + bool hover = (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarShadeButton) && (titleBar->state & State_Sunken); + + QRect shadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarShadeButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, shadeButtonRect, hover, sunken); + + int xoffset = shadeButtonRect.width() / 3; + int yoffset = shadeButtonRect.height() / 3; + + QRect shadeButtonIconRect(shadeButtonRect.left() + xoffset, shadeButtonRect.top() + yoffset, + shadeButtonRect.width() - xoffset * 2, shadeButtonRect.height() - yoffset * 2); + + QPainterPath path(shadeButtonIconRect.bottomLeft()); + path.lineTo(shadeButtonIconRect.center().x(), shadeButtonIconRect.bottom() - shadeButtonIconRect.height() / 2); + path.lineTo(shadeButtonIconRect.bottomRight()); + path.lineTo(shadeButtonIconRect.bottomLeft()); + + painter->setPen(textAlphaColor); + painter->setBrush(textColor); + painter->drawPath(path); + } + + // unshade button + if (titleBar->subControls & SC_TitleBarUnshadeButton) { + bool hover = (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarUnshadeButton) && (titleBar->state & State_Sunken); + + QRect unshadeButtonRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarUnshadeButton, widget); + qt_plastique_draw_mdibutton(painter, titleBar, unshadeButtonRect, hover, sunken); + + int xoffset = unshadeButtonRect.width() / 3; + int yoffset = unshadeButtonRect.height() / 3; + + QRect unshadeButtonIconRect(unshadeButtonRect.left() + xoffset, unshadeButtonRect.top() + yoffset, + unshadeButtonRect.width() - xoffset * 2, unshadeButtonRect.height() - yoffset * 2); + + int midY = unshadeButtonIconRect.bottom() - unshadeButtonIconRect.height() / 2; + QPainterPath path(QPoint(unshadeButtonIconRect.left(), midY)); + path.lineTo(unshadeButtonIconRect.right(), midY); + path.lineTo(unshadeButtonIconRect.center().x(), unshadeButtonIconRect.bottom()); + path.lineTo(unshadeButtonIconRect.left(), midY); + + painter->setPen(textAlphaColor); + painter->setBrush(textColor); + painter->drawPath(path); + } + + // from qwindowsstyle.cpp + if ((titleBar->subControls & SC_TitleBarSysMenu) && (titleBar->titleBarFlags & Qt::WindowSystemMenuHint)) { + bool hover = (titleBar->activeSubControls & SC_TitleBarSysMenu) && (titleBar->state & State_MouseOver); + bool sunken = (titleBar->activeSubControls & SC_TitleBarSysMenu) && (titleBar->state & State_Sunken); + + QRect iconRect = proxy()->subControlRect(CC_TitleBar, titleBar, SC_TitleBarSysMenu, widget); + if (hover) + qt_plastique_draw_mdibutton(painter, titleBar, iconRect, hover, sunken); + + if (!titleBar->icon.isNull()) { + titleBar->icon.paint(painter, iconRect); + } else { + QStyleOption tool(0); + tool.palette = titleBar->palette; + QPixmap pm = standardPixmap(SP_TitleBarMenuButton, &tool, widget); + tool.rect = iconRect; + painter->save(); + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pm); + painter->restore(); + } + } + painter->restore(); + } + break; +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(option)) + QStyleHelper::drawDial(dial, painter); + break; +#endif // QT_NO_DIAL + default: + QWindowsStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +/*! + \reimp +*/ +QSize QPlastiqueStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const +{ + QSize newSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + + switch (type) { + case CT_RadioButton: + ++newSize.rheight(); + ++newSize.rwidth(); + break; +#ifndef QT_NO_SLIDER + case CT_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); + if (slider->tickPosition & QSlider::TicksBelow) { + if (slider->orientation == Qt::Horizontal) + newSize.rheight() += tickSize; + else + newSize.rwidth() += tickSize; + } + if (slider->tickPosition & QSlider::TicksAbove) { + if (slider->orientation == Qt::Horizontal) + newSize.rheight() += tickSize; + else + newSize.rwidth() += tickSize; + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CT_ScrollBar: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int scrollBarExtent = proxy()->pixelMetric(PM_ScrollBarExtent, option, widget); + int scrollBarSliderMinimum = proxy()->pixelMetric(PM_ScrollBarSliderMin, option, widget); + if (scrollBar->orientation == Qt::Horizontal) { + newSize = QSize(scrollBarExtent * 3 + scrollBarSliderMinimum, scrollBarExtent); + } else { + newSize = QSize(scrollBarExtent, scrollBarExtent * 3 + scrollBarSliderMinimum); + } + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_SPINBOX + case CT_SpinBox: + // Make sure the size is odd + newSize.setHeight(sizeFromContents(CT_LineEdit, option, size, widget).height()); + newSize.rheight() -= ((1 - newSize.rheight()) & 1); + break; +#endif +#ifndef QT_NO_TOOLBUTTON + case CT_ToolButton: + newSize.rheight() += 3; + newSize.rwidth() += 3; + break; +#endif +#ifndef QT_NO_COMBOBOX + case CT_ComboBox: + newSize = sizeFromContents(CT_PushButton, option, size, widget); + newSize.rwidth() += 30; // Make room for drop-down indicator + newSize.rheight() += 4; + break; +#endif + case CT_MenuItem: + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) + newSize.setHeight(menuItem->text.isEmpty() ? 2 : menuItem->fontMetrics.height()); + } + break; + case CT_MenuBarItem: + newSize.setHeight(newSize.height()); + break; + default: + break; + } + + return newSize; +} + +/*! + \reimp +*/ +QRect QPlastiqueStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + QRect rect; + switch (element) { + case SE_RadioButtonIndicator: + rect = visualRect(option->direction, option->rect, + QWindowsStyle::subElementRect(element, option, widget)).adjusted(0, 0, 1, 1); + break; +#ifndef QT_NO_PROGRESSBAR + case SE_ProgressBarLabel: + case SE_ProgressBarContents: + case SE_ProgressBarGroove: + return option->rect; +#endif // QT_NO_PROGRESSBAR + default: + return QWindowsStyle::subElementRect(element, option, widget); + } + + return visualRect(option->direction, option->rect, rect); +} + +/*! + \reimp +*/ +QRect QPlastiqueStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const +{ + QRect rect = QWindowsStyle::subControlRect(control, option, subControl, widget); + + switch (control) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); + + switch (subControl) { + case SC_SliderHandle: + if (slider->orientation == Qt::Horizontal) { + rect.setWidth(11); + rect.setHeight(15); + int centerY = slider->rect.center().y() - rect.height() / 2; + if (slider->tickPosition & QSlider::TicksAbove) + centerY += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerY -= tickSize; + rect.moveTop(centerY); + } else { + rect.setWidth(15); + rect.setHeight(11); + int centerX = slider->rect.center().x() - rect.width() / 2; + if (slider->tickPosition & QSlider::TicksAbove) + centerX += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerX -= tickSize; + rect.moveLeft(centerX); + } + break; + case SC_SliderGroove: { + QPoint grooveCenter = slider->rect.center(); + if (slider->orientation == Qt::Horizontal) { + rect.setHeight(14); + --grooveCenter.ry(); + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.ry() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.ry() -= tickSize; + } else { + rect.setWidth(14); + --grooveCenter.rx(); + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.rx() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.rx() -= tickSize; + } + rect.moveCenter(grooveCenter); + break; + } + default: + break; + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int scrollBarExtent = proxy()->pixelMetric(PM_ScrollBarExtent, scrollBar, widget); + int sliderMaxLength = ((scrollBar->orientation == Qt::Horizontal) ? + scrollBar->rect.width() : scrollBar->rect.height()) - (scrollBarExtent * 3); + int sliderMinLength = proxy()->pixelMetric(PM_ScrollBarSliderMin, scrollBar, widget); + int sliderLength; + + // calculate slider length + if (scrollBar->maximum != scrollBar->minimum) { + uint valueRange = scrollBar->maximum - scrollBar->minimum; + sliderLength = (scrollBar->pageStep * sliderMaxLength) / (valueRange + scrollBar->pageStep); + + if (sliderLength < sliderMinLength || valueRange > INT_MAX / 2) + sliderLength = sliderMinLength; + if (sliderLength > sliderMaxLength) + sliderLength = sliderMaxLength; + } else { + sliderLength = sliderMaxLength; + } + + int sliderStart = scrollBarExtent + sliderPositionFromValue(scrollBar->minimum, + scrollBar->maximum, + scrollBar->sliderPosition, + sliderMaxLength - sliderLength, + scrollBar->upsideDown); + + QRect scrollBarRect = scrollBar->rect; + + switch (subControl) { + case SC_ScrollBarSubLine: // top/left button + if (scrollBar->orientation == Qt::Horizontal) { + rect.setRect(scrollBarRect.left(), scrollBarRect.top(), scrollBarRect.width() - scrollBarExtent, scrollBarRect.height()); + } else { + rect.setRect(scrollBarRect.left(), scrollBarRect.top(), scrollBarRect.width(), scrollBarRect.height() - scrollBarExtent); + } + break; + case SC_ScrollBarAddLine: // bottom/right button + if (scrollBar->orientation == Qt::Horizontal) { + rect.setRect(scrollBarRect.right() - (scrollBarExtent - 1), scrollBarRect.top(), scrollBarExtent, scrollBarRect.height()); + } else { + rect.setRect(scrollBarRect.left(), scrollBarRect.bottom() - (scrollBarExtent - 1), scrollBarRect.width(), scrollBarExtent); + } + break; + case SC_ScrollBarSubPage: + if (scrollBar->orientation == Qt::Horizontal) { + rect.setRect(scrollBarRect.left() + scrollBarExtent, scrollBarRect.top(), + sliderStart - (scrollBarRect.left() + scrollBarExtent), scrollBarRect.height()); + } else { + rect.setRect(scrollBarRect.left(), scrollBarRect.top() + scrollBarExtent, + scrollBarRect.width(), sliderStart - (scrollBarRect.left() + scrollBarExtent)); + } + break; + case SC_ScrollBarAddPage: + if (scrollBar->orientation == Qt::Horizontal) + rect.setRect(sliderStart + sliderLength, 0, + sliderMaxLength - sliderStart - sliderLength + scrollBarExtent, scrollBarRect.height()); + else + rect.setRect(0, sliderStart + sliderLength, + scrollBarRect.width(), sliderMaxLength - sliderStart - sliderLength + scrollBarExtent); + break; + case SC_ScrollBarGroove: + if (scrollBar->orientation == Qt::Horizontal) { + rect = scrollBarRect.adjusted(scrollBarExtent, 0, -2 * scrollBarExtent, 0); + } else { + rect = scrollBarRect.adjusted(0, scrollBarExtent, 0, -2 * scrollBarExtent); + } + break; + case SC_ScrollBarSlider: + if (scrollBar->orientation == Qt::Horizontal) { + rect.setRect(sliderStart, 0, sliderLength, scrollBarRect.height()); + } else { + rect.setRect(0, sliderStart, scrollBarRect.width(), sliderLength); + } + break; + default: + break; + } + rect = visualRect(scrollBar->direction, scrollBarRect, rect); + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + int center = spinBox->rect.height() / 2; + switch (subControl) { + case SC_SpinBoxUp: + if (spinBox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + rect = visualRect(spinBox->direction, spinBox->rect, rect); + rect.setRect(spinBox->rect.right() - 16, spinBox->rect.top(), 17, center + 1); + rect = visualRect(spinBox->direction, spinBox->rect, rect); + break; + case SC_SpinBoxDown: + if (spinBox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + rect = visualRect(spinBox->direction, spinBox->rect, rect); + rect.setRect(spinBox->rect.right() - 16, spinBox->rect.top() + center, 17, spinBox->rect.height() - center); + rect = visualRect(spinBox->direction, spinBox->rect, rect); + break; + case SC_SpinBoxEditField: + if (spinBox->buttonSymbols != QAbstractSpinBox::NoButtons) { + rect = spinBox->rect.adjusted(0, 0, -16, 0); + } else { + rect = spinBox->rect; + } + rect.adjust(blueFrameWidth, blueFrameWidth, -blueFrameWidth, -blueFrameWidth); + rect = visualRect(spinBox->direction, spinBox->rect, rect); + break; + default: + break; + } + } + break; +#endif // QT_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + switch (subControl) { + case SC_ComboBoxArrow: + rect = visualRect(option->direction, option->rect, rect); + rect.setRect(rect.right() - 17, rect.top() - 2, + 19, rect.height() + 4); + rect = visualRect(option->direction, option->rect, rect); + break; + case SC_ComboBoxEditField: { + if (const QStyleOptionComboBox *box = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth); + rect = visualRect(option->direction, option->rect, rect); + + if (box->editable) { + rect = box->rect.adjusted(blueFrameWidth, blueFrameWidth, -blueFrameWidth, -blueFrameWidth); + rect.setRight(rect.right() - 16); // Overlaps the combobox button by 2 pixels + } else { + rect.setRect(option->rect.left() + frameWidth, option->rect.top() + frameWidth, + option->rect.width() - 16 - 2 * frameWidth, + option->rect.height() - 2 * frameWidth); + rect.setLeft(rect.left() + 2); + rect.setRight(rect.right() - 2); + if (box->state & (State_Sunken | State_On)) + rect.translate(1, 1); + } + rect = visualRect(option->direction, option->rect, rect); + } + break; + } + default: + break; + } + break; +#endif // QT_NO_COMBOBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) { + SubControl sc = subControl; + QRect &ret = rect; + const int indent = 3; + const int controlTopMargin = 4; + const int controlBottomMargin = 3; + const int controlWidthMargin = 1; + const int controlHeight = tb->rect.height() - controlTopMargin - controlBottomMargin; + const int delta = controlHeight + controlWidthMargin; + int offset = 0; + + bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + + switch (sc) { + case SC_TitleBarLabel: + if (tb->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) { + ret = tb->rect; + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + ret.adjust(delta, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowShadeButtonHint) + ret.adjust(0, 0, -delta, 0); + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + ret.adjust(0, 0, -delta, 0); + ret.adjusted(indent, 0, -indent, 0); + } + break; + case SC_TitleBarContextHelpButton: + if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) + offset += delta; + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMinButton) + break; + case SC_TitleBarNormalButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarNormalButton) + break; + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarMaxButton) + break; + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarShadeButton) + break; + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (sc == SC_TitleBarUnshadeButton) + break; + case SC_TitleBarCloseButton: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) + offset += delta; + else if (sc == SC_TitleBarCloseButton) + break; + ret.setRect(tb->rect.right() - indent - offset, tb->rect.top() + controlTopMargin, + controlHeight, controlHeight); + break; + case SC_TitleBarSysMenu: + if (tb->titleBarFlags & Qt::WindowSystemMenuHint) { + ret.setRect(tb->rect.left() + controlWidthMargin + indent, tb->rect.top() + controlTopMargin, + controlHeight, controlHeight); + } + break; + default: + break; + } + ret = visualRect(tb->direction, tb->rect, ret); + } + break; + default: + break; + } + + return rect; +} + +/*! + \reimp +*/ +int QPlastiqueStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + int ret = 0; + switch (hint) { + case SH_WindowFrame_Mask: + ret = 1; + if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData)) { + mask->region = option->rect; + mask->region -= QRect(option->rect.left(), option->rect.top(), 2, 1); + mask->region -= QRect(option->rect.right() - 1, option->rect.top(), 2, 1); + mask->region -= QRect(option->rect.left(), option->rect.top() + 1, 1, 1); + mask->region -= QRect(option->rect.right(), option->rect.top() + 1, 1, 1); + + const QStyleOptionTitleBar *titleBar = qstyleoption_cast<const QStyleOptionTitleBar *>(option); + if (titleBar && (titleBar->titleBarState & Qt::WindowMinimized)) { + mask->region -= QRect(option->rect.left(), option->rect.bottom(), 2, 1); + mask->region -= QRect(option->rect.right() - 1, option->rect.bottom(), 2, 1); + mask->region -= QRect(option->rect.left(), option->rect.bottom() - 1, 1, 1); + mask->region -= QRect(option->rect.right(), option->rect.bottom() - 1, 1, 1); + } else { + mask->region -= QRect(option->rect.bottomLeft(), QSize(1, 1)); + mask->region -= QRect(option->rect.bottomRight(), QSize(1, 1)); + } + } + break; + case SH_TitleBar_NoBorder: + ret = 1; + break; + case SH_TitleBar_AutoRaise: + ret = 1; + break; + case SH_ItemView_ShowDecorationSelected: + ret = true; + break; + case SH_ToolBox_SelectedPageTitleBold: + case SH_ScrollBar_MiddleClickAbsolutePosition: + ret = true; + break; + case SH_MainWindow_SpaceBelowMenuBar: + ret = 0; + break; + case SH_FormLayoutWrapPolicy: + ret = QFormLayout::DontWrapRows; + break; + case SH_FormLayoutFieldGrowthPolicy: + ret = QFormLayout::ExpandingFieldsGrow; + break; + case SH_FormLayoutFormAlignment: + ret = Qt::AlignLeft | Qt::AlignTop; + break; + case SH_FormLayoutLabelAlignment: + ret = Qt::AlignRight; + break; + case SH_MessageBox_TextInteractionFlags: + ret = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse; + break; + case SH_LineEdit_PasswordCharacter: + ret = QCommonStyle::styleHint(hint, option, widget, returnData); + break; + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = true; + break; + case SH_Menu_SubMenuPopupDelay: + ret = 96; // from Plastik + break; +#ifdef Q_WS_X11 + case SH_DialogButtonBox_ButtonsHaveIcons: + ret = true; + break; +#endif +#ifndef Q_OS_WIN + case SH_Menu_AllowActiveAndDisabled: + ret = false; + break; +#endif + default: + ret = QWindowsStyle::styleHint(hint, option, widget, returnData); + break; + } + return ret; +} + +/*! + \reimp +*/ +QStyle::SubControl QPlastiqueStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget) const +{ + SubControl ret = SC_None; + switch (control) { +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect slider = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSlider, widget); + if (slider.contains(pos)) { + ret = SC_ScrollBarSlider; + break; + } + + QRect scrollBarAddLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarAddLine, widget); + if (scrollBarAddLine.contains(pos)) { + ret = SC_ScrollBarAddLine; + break; + } + + QRect scrollBarSubPage = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSubPage, widget); + if (scrollBarSubPage.contains(pos)) { + ret = SC_ScrollBarSubPage; + break; + } + + QRect scrollBarAddPage = proxy()->subControlRect(control, scrollBar, SC_ScrollBarAddPage, widget); + if (scrollBarAddPage.contains(pos)) { + ret = SC_ScrollBarAddPage; + break; + } + + QRect scrollBarSubLine = proxy()->subControlRect(control, scrollBar, SC_ScrollBarSubLine, widget); + if (scrollBarSubLine.contains(pos)) { + ret = SC_ScrollBarSubLine; + break; + } + } + break; +#endif // QT_NO_SCROLLBAR + default: + break; + } + + return ret != SC_None ? ret : QWindowsStyle::hitTestComplexControl(control, option, pos, widget); +} + +/*! + \reimp +*/ +int QPlastiqueStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + int ret = -1; + switch (metric) { + case PM_MenuVMargin: + case PM_MenuHMargin: + ret = 0; + break; + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 1; + break; + case PM_ButtonDefaultIndicator: + ret = 0; + break; +#ifndef QT_NO_SLIDER + case PM_SliderThickness: + ret = 15; + break; + case PM_SliderLength: + case PM_SliderControlThickness: + ret = 11; + break; + case PM_SliderTickmarkOffset: + ret = 5; + break; + case PM_SliderSpaceAvailable: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int size = 15; + if (slider->tickPosition & QSlider::TicksBelow) + ++size; + if (slider->tickPosition & QSlider::TicksAbove) + ++size; + ret = size; + break; + } +#endif // QT_NO_SLIDER + case PM_ScrollBarExtent: + ret = 16; + break; + case PM_ScrollBarSliderMin: + ret = 26; + break; + case PM_ProgressBarChunkWidth: + ret = 1; + break; + case PM_MenuBarItemSpacing: + ret = 3; + break; + case PM_MenuBarVMargin: + ret = 2; + break; + case PM_MenuBarHMargin: + ret = 0; + break; + case PM_MenuBarPanelWidth: + ret = 1; + break; + case PM_ToolBarHandleExtent: + ret = 9; + break; + case PM_ToolBarSeparatorExtent: + ret = 2; + break; + case PM_ToolBarItemSpacing: + ret = 1; + break; + case PM_ToolBarItemMargin: + ret = 1; + break; + case PM_ToolBarFrameWidth: + ret = 2; + break; + case PM_SplitterWidth: + ret = 6; + break; + case PM_DockWidgetSeparatorExtent: + ret = 6; + break; + case PM_DockWidgetHandleExtent: + ret = 20; + break; + case PM_DefaultFrameWidth: +#ifndef QT_NO_MENU + if (qobject_cast<const QMenu *>(widget)) { + ret = 1; + break; + } +#endif + ret = 2; + break; + case PM_MdiSubWindowFrameWidth: + ret = 4; + break; + case PM_TitleBarHeight: +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + // Q3DockWindow has smaller title bars than QDockWidget + ret = qMax(widget->fontMetrics().height(), 20); + } else +#endif + ret = qMax(widget ? widget->fontMetrics().height() : + (option ? option->fontMetrics.height() : 0), 30); + break; + case PM_MaximumDragDistance: + return -1; + case PM_DockWidgetTitleMargin: + return 2; + case PM_LayoutHorizontalSpacing: + case PM_LayoutVerticalSpacing: + return -1; // rely on layoutHorizontalSpacing() + case PM_LayoutLeftMargin: + case PM_LayoutTopMargin: + case PM_LayoutRightMargin: + case PM_LayoutBottomMargin: + { + bool isWindow = false; + if (option) { + isWindow = (option->state & State_Window); + } else if (widget) { + isWindow = widget->isWindow(); + } + + if (isWindow) { + ret = 11; + } else { + ret = 9; + } + } + default: + break; + } + + return ret != -1 ? ret : QWindowsStyle::pixelMetric(metric, option, widget); +} + +/*! + \reimp +*/ +QPalette QPlastiqueStyle::standardPalette() const +{ + QPalette palette; + + palette.setBrush(QPalette::Disabled, QPalette::WindowText, QColor(QRgb(0xff808080))); + palette.setBrush(QPalette::Disabled, QPalette::Button, QColor(QRgb(0xffdddfe4))); + palette.setBrush(QPalette::Disabled, QPalette::Light, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Disabled, QPalette::Midlight, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Disabled, QPalette::Dark, QColor(QRgb(0xff555555))); + palette.setBrush(QPalette::Disabled, QPalette::Mid, QColor(QRgb(0xffc7c7c7))); + palette.setBrush(QPalette::Disabled, QPalette::Text, QColor(QRgb(0xffc7c7c7))); + palette.setBrush(QPalette::Disabled, QPalette::BrightText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Disabled, QPalette::ButtonText, QColor(QRgb(0xff808080))); + palette.setBrush(QPalette::Disabled, QPalette::Base, QColor(QRgb(0xffefefef))); + palette.setBrush(QPalette::Disabled, QPalette::AlternateBase, palette.color(QPalette::Disabled, QPalette::Base).darker(110)); + palette.setBrush(QPalette::Disabled, QPalette::Window, QColor(QRgb(0xffefefef))); + palette.setBrush(QPalette::Disabled, QPalette::Shadow, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(QRgb(0xff567594))); + palette.setBrush(QPalette::Disabled, QPalette::HighlightedText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Disabled, QPalette::Link, QColor(QRgb(0xff0000ee))); + palette.setBrush(QPalette::Disabled, QPalette::LinkVisited, QColor(QRgb(0xff52188b))); + palette.setBrush(QPalette::Active, QPalette::WindowText, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Active, QPalette::Button, QColor(QRgb(0xffdddfe4))); + palette.setBrush(QPalette::Active, QPalette::Light, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Active, QPalette::Midlight, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Active, QPalette::Dark, QColor(QRgb(0xff555555))); + palette.setBrush(QPalette::Active, QPalette::Mid, QColor(QRgb(0xffc7c7c7))); + palette.setBrush(QPalette::Active, QPalette::Text, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Active, QPalette::BrightText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Active, QPalette::ButtonText, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Active, QPalette::Base, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Active, QPalette::AlternateBase, palette.color(QPalette::Active, QPalette::Base).darker(110)); + palette.setBrush(QPalette::Active, QPalette::Window, QColor(QRgb(0xffefefef))); + palette.setBrush(QPalette::Active, QPalette::Shadow, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Active, QPalette::Highlight, QColor(QRgb(0xff678db2))); + palette.setBrush(QPalette::Active, QPalette::HighlightedText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Active, QPalette::Link, QColor(QRgb(0xff0000ee))); + palette.setBrush(QPalette::Active, QPalette::LinkVisited, QColor(QRgb(0xff52188b))); + palette.setBrush(QPalette::Inactive, QPalette::WindowText, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Inactive, QPalette::Button, QColor(QRgb(0xffdddfe4))); + palette.setBrush(QPalette::Inactive, QPalette::Light, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Inactive, QPalette::Midlight, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Inactive, QPalette::Dark, QColor(QRgb(0xff555555))); + palette.setBrush(QPalette::Inactive, QPalette::Mid, QColor(QRgb(0xffc7c7c7))); + palette.setBrush(QPalette::Inactive, QPalette::Text, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Inactive, QPalette::BrightText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Inactive, QPalette::ButtonText, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Inactive, QPalette::Base, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Inactive, QPalette::AlternateBase, palette.color(QPalette::Inactive, QPalette::Base).darker(110)); + palette.setBrush(QPalette::Inactive, QPalette::Window, QColor(QRgb(0xffefefef))); + palette.setBrush(QPalette::Inactive, QPalette::Shadow, QColor(QRgb(0xff000000))); + palette.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(QRgb(0xff678db2))); + palette.setBrush(QPalette::Inactive, QPalette::HighlightedText, QColor(QRgb(0xffffffff))); + palette.setBrush(QPalette::Inactive, QPalette::Link, QColor(QRgb(0xff0000ee))); + palette.setBrush(QPalette::Inactive, QPalette::LinkVisited, QColor(QRgb(0xff52188b))); + return palette; +} + +/*! + \reimp +*/ +void QPlastiqueStyle::polish(QWidget *widget) +{ + if (qobject_cast<QPushButton *>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox *>(widget) +#endif +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox *>(widget) +#endif + || qobject_cast<QCheckBox *>(widget) +#ifndef QT_NO_GROUPBOX + || qobject_cast<QGroupBox *>(widget) +#endif + || qobject_cast<QRadioButton *>(widget) +#ifndef QT_NO_SPLITTER + || qobject_cast<QSplitterHandle *>(widget) +#endif +#ifndef QT_NO_TABBAR + || qobject_cast<QTabBar *>(widget) +#endif + ) { + widget->setAttribute(Qt::WA_Hover); + } + + if (widget->inherits("QWorkspaceTitleBar") + || widget->inherits("QDockSeparator") + || widget->inherits("QDockWidgetSeparator") + || widget->inherits("Q3DockWindowResizeHandle")) { + widget->setAttribute(Qt::WA_Hover); + } + + if (false // to simplify the #ifdefs +#ifndef QT_NO_MENUBAR + || qobject_cast<QMenuBar *>(widget) +#endif +#ifdef QT3_SUPPORT + || widget->inherits("Q3ToolBar") +#endif +#ifndef QT_NO_TOOLBAR + || qobject_cast<QToolBar *>(widget) + || (widget && qobject_cast<QToolBar *>(widget->parent())) +#endif + ) { + widget->setBackgroundRole(QPalette::Window); + } + +#ifndef QT_NO_PROGRESSBAR + if (AnimateBusyProgressBar && qobject_cast<QProgressBar *>(widget)) + widget->installEventFilter(this); +#endif + +#if defined QPlastique_MaskButtons + if (qobject_cast<QPushButton *>(widget) || qobject_cast<QToolButton *>(widget)) + widget->installEventFilter(this); +#endif +} + +/*! + \reimp +*/ +void QPlastiqueStyle::unpolish(QWidget *widget) +{ + if (qobject_cast<QPushButton *>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox *>(widget) +#endif +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox *>(widget) +#endif + || qobject_cast<QCheckBox *>(widget) +#ifndef QT_NO_GROUPBOX + || qobject_cast<QGroupBox *>(widget) +#endif +#ifndef QT_NO_SPLITTER + || qobject_cast<QSplitterHandle *>(widget) +#endif +#ifndef QT_NO_TABBAR + || qobject_cast<QTabBar *>(widget) +#endif + || qobject_cast<QRadioButton *>(widget)) { + widget->setAttribute(Qt::WA_Hover, false); + } + + if (widget->inherits("QWorkspaceTitleBar") + || widget->inherits("QDockSeparator") + || widget->inherits("QDockWidgetSeparator") + || widget->inherits("Q3DockWindowResizeHandle")) { + widget->setAttribute(Qt::WA_Hover, false); + } + + if (false // to simplify the #ifdefs +#ifndef QT_NO_MENUBAR + || qobject_cast<QMenuBar *>(widget) +#endif +#ifndef QT_NO_TOOLBOX + || qobject_cast<QToolBox *>(widget) +#endif +#ifdef QT3_SUPPORT + || widget->inherits("Q3ToolBar") +#endif +#ifndef QT_NO_TOOLBAR + || qobject_cast<QToolBar *>(widget) + || (widget && qobject_cast<QToolBar *>(widget->parent())) +#endif + ) { + widget->setBackgroundRole(QPalette::Button); + } + +#ifndef QT_NO_PROGRESSBAR + if (AnimateBusyProgressBar && qobject_cast<QProgressBar *>(widget)) { + Q_D(QPlastiqueStyle); + widget->removeEventFilter(this); + d->bars.removeAll(static_cast<QProgressBar*>(widget)); + } +#endif + +#if defined QPlastique_MaskButtons + if (qobject_cast<QPushButton *>(widget) || qobject_cast<QToolButton *>(widget)) + widget->removeEventFilter(this); +#endif +} + +/*! + \reimp +*/ +void QPlastiqueStyle::polish(QApplication *app) +{ + QWindowsStyle::polish(app); +} + +/*! + \reimp +*/ +void QPlastiqueStyle::polish(QPalette &pal) +{ + QWindowsStyle::polish(pal); +#ifdef Q_WS_MAC + pal.setBrush(QPalette::Shadow, Qt::black); +#endif +} + +/*! + \reimp +*/ +void QPlastiqueStyle::unpolish(QApplication *app) +{ + QWindowsStyle::unpolish(app); +} + +/*! + \internal +*/ +QIcon QPlastiqueStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget) const +{ + return QWindowsStyle::standardIconImplementation(standardIcon, option, widget); +} + +/*! + \reimp +*/ +QPixmap QPlastiqueStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ + return QWindowsStyle::standardPixmap(standardPixmap, opt, widget); +} + +// this works as long as we have at most 16 different control types +#define CT1(c) CT2(c, c) +#define CT2(c1, c2) (((uint)c1 << 16) | (uint)c2) + +/*! + \internal +*/ +int QPlastiqueStyle::layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption * /* option */, + const QWidget * /* widget */) const +{ + const int ButtonMask = QSizePolicy::ButtonBox | QSizePolicy::PushButton; + + if (control2 == QSizePolicy::ButtonBox) + return 11; + + if ((control1 | control2) & ButtonMask) + return (orientation == Qt::Horizontal) ? 10 : 9; + + switch (CT2(control1, control2)) { + case CT1(QSizePolicy::Label): + case CT2(QSizePolicy::Label, QSizePolicy::DefaultType): + case CT2(QSizePolicy::Label, QSizePolicy::CheckBox): + case CT2(QSizePolicy::Label, QSizePolicy::ComboBox): + case CT2(QSizePolicy::Label, QSizePolicy::LineEdit): + case CT2(QSizePolicy::Label, QSizePolicy::RadioButton): + case CT2(QSizePolicy::Label, QSizePolicy::Slider): + case CT2(QSizePolicy::Label, QSizePolicy::SpinBox): + case CT2(QSizePolicy::Label, QSizePolicy::ToolButton): + return 5; + case CT2(QSizePolicy::CheckBox, QSizePolicy::RadioButton): + case CT2(QSizePolicy::RadioButton, QSizePolicy::CheckBox): + case CT1(QSizePolicy::CheckBox): + if (orientation == Qt::Vertical) + return 2; + case CT1(QSizePolicy::RadioButton): + if (orientation == Qt::Vertical) + return 1; + } + + if (orientation == Qt::Horizontal + && (control2 & (QSizePolicy::CheckBox | QSizePolicy::RadioButton))) + return 8; + + if ((control1 | control2) & (QSizePolicy::Frame + | QSizePolicy::GroupBox + | QSizePolicy::TabWidget)) { + return 11; + } + + if ((control1 | control2) & (QSizePolicy::Line | QSizePolicy::Slider + | QSizePolicy::LineEdit | QSizePolicy::ComboBox + | QSizePolicy::SpinBox)) + return 7; + + return 6; +} + +/*! + \reimp +*/ +bool QPlastiqueStyle::eventFilter(QObject *watched, QEvent *event) +{ +#ifndef QT_NO_PROGRESSBAR + Q_D(QPlastiqueStyle); + + switch (event->type()) { + case QEvent::Show: + if (QProgressBar *bar = qobject_cast<QProgressBar *>(watched)) { + d->bars.append(bar); + if (d->bars.size() == 1) { + Q_ASSERT(ProgressBarFps > 0); + d->timer.start(); + d->progressBarAnimateTimer = startTimer(1000 / ProgressBarFps); + } + } + break; + case QEvent::Destroy: + case QEvent::Hide: + if(!d->bars.isEmpty()) { + d->bars.removeAll(reinterpret_cast<QProgressBar*>(watched)); + if (d->bars.isEmpty()) { + killTimer(d->progressBarAnimateTimer); + d->progressBarAnimateTimer = 0; + } + } + break; +#if defined QPlastique_MaskButtons + case QEvent::Resize: + if (qobject_cast<QPushButton *>(watched) || qobject_cast<QToolButton *>(watched)) { + QWidget *widget = qobject_cast<QWidget *>(watched); + QRect rect = widget->rect(); + QRegion region(rect); + region -= QRect(rect.left(), rect.top(), 2, 1); + region -= QRect(rect.left(), rect.top() + 1, 1, 1); + region -= QRect(rect.left(), rect.bottom(), 2, 1); + region -= QRect(rect.left(), rect.bottom() - 1, 1, 1); + region -= QRect(rect.right() - 1, rect.top(), 2, 1); + region -= QRect(rect.right(), rect.top() + 1, 1, 1); + region -= QRect(rect.right() - 1, rect.bottom(), 2, 1); + region -= QRect(rect.right(), rect.bottom() - 1, 1, 1); + widget->setMask(region); + } + break; +#endif + default: + break; + } +#endif // QT_NO_PROGRESSBAR + + return QWindowsStyle::eventFilter(watched, event); +} + +/*! + \reimp +*/ +void QPlastiqueStyle::timerEvent(QTimerEvent *event) +{ +#ifndef QT_NO_PROGRESSBAR + Q_D(QPlastiqueStyle); + + if (event->timerId() == d->progressBarAnimateTimer) { + Q_ASSERT(ProgressBarFps > 0); + d->animateStep = d->timer.elapsed() / (1000 / ProgressBarFps); + foreach (QProgressBar *bar, d->bars) { + if (AnimateProgressBar || (bar->minimum() == 0 && bar->maximum() == 0)) + bar->update(); + } + } +#endif // QT_NO_PROGRESSBAR + event->ignore(); +} + +QT_END_NAMESPACE + +#endif // !defined(QT_NO_STYLE_PLASTIQUE) || defined(QT_PLUGIN) diff --git a/src/gui/styles/qplastiquestyle.h b/src/gui/styles/qplastiquestyle.h new file mode 100644 index 0000000000..20d2f490a6 --- /dev/null +++ b/src/gui/styles/qplastiquestyle.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLASTIQUESTYLE_H +#define QPLASTIQUESTYLE_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_PLASTIQUE) + +class QPlastiqueStylePrivate; +class Q_GUI_EXPORT QPlastiqueStyle : public QWindowsStyle +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPlastiqueStyle) +public: + QPlastiqueStyle(); + ~QPlastiqueStyle(); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget = 0) const; + + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + + void polish(QWidget *widget); + void polish(QApplication *app); + void polish(QPalette &pal); + void unpolish(QWidget *widget); + void unpolish(QApplication *app); + + QPalette standardPalette() const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + int layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +protected: + bool eventFilter(QObject *watched, QEvent *event); + void timerEvent(QTimerEvent *event); + +private: + Q_DISABLE_COPY(QPlastiqueStyle) + void *reserved; +}; + +#endif // QT_NO_STYLE_PLASTIQUE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLASTIQUESTYLE_H diff --git a/src/gui/styles/qproxystyle.cpp b/src/gui/styles/qproxystyle.cpp new file mode 100644 index 0000000000..516d17fa69 --- /dev/null +++ b/src/gui/styles/qproxystyle.cpp @@ -0,0 +1,420 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qstyle.h> +#include <private/qproxystyle_p.h> +#include <private/qapplication_p.h> +#include "qproxystyle.h" +#include "qstylefactory.h" +#include <private/qstyle_p.h> + +#if !defined(QT_NO_STYLE_PROXY) || defined(QT_PLUGIN) + +QT_BEGIN_NAMESPACE + +/*! + \class QProxyStyle + + \brief The QProxyStyle class is a convenience class that simplifies + dynamically overriding QStyle elements. + + \since 4.6 + + A QProxyStyle wraps a QStyle (usually the default system style) for the + purpose of dynamically overriding painting or other specific style behavior. + + The following example shows how to override the shortcut underline + behavior on any platform: + + \snippet doc/src/snippets/code/src_gui_qproxystyle.cpp 1 + + Warning: The \l {QCommonStyle} {common styles} provided by Qt will + respect this hint, because they call QStyle::proxy(), but there is + no guarantee that QStyle::proxy() will be called for user defined + or system controlled styles. It would not work on a Mac, for + example, where menus are handled by the operating system. + + \sa QStyle +*/ + +void QProxyStylePrivate::ensureBaseStyle() const +{ + Q_Q(const QProxyStyle); + + if (baseStyle) + return; + + if (!baseStyle && !QApplicationPrivate::styleOverride.isEmpty()) { + baseStyle = QStyleFactory::create(QApplicationPrivate::styleOverride); + if (baseStyle) { + // If baseStyle is an instance of the same proxyStyle + // we destroy it and fall back to the desktop style + if (qstrcmp(baseStyle->metaObject()->className(), + q->metaObject()->className()) == 0) { + delete baseStyle; + baseStyle = 0; + } + } + } + + if (!baseStyle) // Use application desktop style + baseStyle = QStyleFactory::create(QApplicationPrivate::desktopStyleKey()); + + if (!baseStyle) // Fallback to windows style + baseStyle = QStyleFactory::create(QLatin1String("windows")); + + baseStyle->setProxy(const_cast<QProxyStyle*>(q)); + baseStyle->setParent(const_cast<QProxyStyle*>(q)); // Take ownership +} + +/*! + Constructs a QProxyStyle object for overriding behavior in \a style + or in the current application \l{QStyle}{style} if \a style is 0 + (default). Normally \a style is 0, because you want to override + behavior in the system style. + + Ownership of \a style is transferred to QProxyStyle. +*/ +QProxyStyle::QProxyStyle(QStyle *style) : + QCommonStyle(*new QProxyStylePrivate()) +{ + Q_D(QProxyStyle); + if (style) { + style->setProxy(this); + style->setParent(this); // Take ownership + d->baseStyle = style; + } +} + +/*! + Destroys the QProxyStyle object. +*/ +QProxyStyle::~QProxyStyle() +{ +} + +/*! + Returns the proxy base style object. If no base style + is set on the proxy style, QProxyStyle will create + an instance of the application style instead. + + \sa setBaseStyle(), QStyle +*/ +QStyle *QProxyStyle::baseStyle() const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle; +} + +/*! + Sets the base style that should be proxied. + + Ownership of \a style is transferred to QProxyStyle. + + If style is zero, a desktop-dependant style will be + assigned automatically. +*/ +void QProxyStyle::setBaseStyle(QStyle *style) +{ + Q_D (QProxyStyle); + + if (d->baseStyle && d->baseStyle->parent() == this) + d->baseStyle->deleteLater(); + + d->baseStyle = style; + + if (d->baseStyle) { + d->baseStyle->setProxy(this); + d->baseStyle->setParent(this); + } +} + +/*! \reimp + */ +void QProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->drawPrimitive(element, option, painter, widget); +} + +/*! + \reimp + */ +void QProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->drawControl(element, option, painter, widget); +} + +/*! \reimp + */ +void QProxyStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->drawComplexControl(control, option, painter, widget); +} + +/*! \reimp + */ +void QProxyStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->drawItemText(painter, rect, flags, pal, enabled, text, textRole); +} + +/*! \reimp + */ +void QProxyStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->drawItemPixmap(painter, rect, alignment, pixmap); +} + +/*! \reimp + */ +QSize QProxyStyle::sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->sizeFromContents(type, option, size, widget); +} + +/*! \reimp + */ +QRect QProxyStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->subElementRect(element, option, widget); +} + +/*! \reimp + */ +QRect QProxyStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->subControlRect(cc, option, sc, widget); +} + +/*! \reimp + */ +QRect QProxyStyle::itemTextRect(const QFontMetrics &fm, const QRect &r, int flags, bool enabled, const QString &text) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->itemTextRect(fm, r, flags, enabled, text); +} + +/*! \reimp + */ +QRect QProxyStyle::itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->itemPixmapRect(r, flags, pixmap); +} + +/*! \reimp + */ +QStyle::SubControl QProxyStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, const QPoint &pos, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->hitTestComplexControl(control, option, pos, widget); +} + +/*! \reimp + */ +int QProxyStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->styleHint(hint, option, widget, returnData); +} + +/*! \reimp + */ +int QProxyStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->pixelMetric(metric, option, widget); +} + +/*! \reimp + */ +QPixmap QProxyStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->standardPixmap(standardPixmap, opt, widget); +} + +/*! \reimp + */ +QPixmap QProxyStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, const QStyleOption *opt) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->generatedIconPixmap(iconMode, pixmap, opt); +} + +/*! \reimp + */ +QPalette QProxyStyle::standardPalette() const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->standardPalette(); +} + +/*! \reimp + */ +void QProxyStyle::polish(QWidget *widget) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->polish(widget); +} + +/*! \reimp + */ +void QProxyStyle::polish(QPalette &pal) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->polish(pal); +} + +/*! \reimp + */ +void QProxyStyle::polish(QApplication *app) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->polish(app); +} + +/*! \reimp + */ +void QProxyStyle::unpolish(QWidget *widget) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->unpolish(widget); +} + +/*! \reimp + */ +void QProxyStyle::unpolish(QApplication *app) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + d->baseStyle->unpolish(app); +} + +/*! \reimp + */ +bool QProxyStyle::event(QEvent *e) +{ + Q_D (QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->event(e); +} + +/*! + Returns an icon for the given \a standardIcon. + + Reimplement this slot to provide your own icons in a QStyle + subclass. The \a option argument can be used to pass extra + information required to find the appropriate icon. The \a widget + argument is optional and can also be used to help find the icon. + + \note Because of binary compatibility constraints, standardIcon() + introduced in Qt 4.1 is not virtual. Therefore it must dynamically + detect and call \e this slot. This default implementation simply + calls standardIcon() with the given parameters. + + \sa standardIcon() + */ +QIcon QProxyStyle::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->standardIcon(standardIcon, option, widget); +} + +/*! + This slot is called by layoutSpacing() to determine the spacing that + should be used between \a control1 and \a control2 in a layout. \a + orientation specifies whether the controls are laid out side by side + or stacked vertically. The \a option parameter can be used to pass + extra information about the parent widget. The \a widget parameter + is optional and can also be used if \a option is 0. + + The default implementation returns -1. + + \sa layoutSpacing(), combinedLayoutSpacing() + */ +int QProxyStyle::layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option, + const QWidget *widget) const +{ + Q_D (const QProxyStyle); + d->ensureBaseStyle(); + return d->baseStyle->layoutSpacing(control1, control2, orientation, option, widget); +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_PROXY diff --git a/src/gui/styles/qproxystyle.h b/src/gui/styles/qproxystyle.h new file mode 100644 index 0000000000..9d4a5cc56c --- /dev/null +++ b/src/gui/styles/qproxystyle.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYSTYLE_H +#define QPROXYSTYLE_H + +#include <QtGui/QCommonStyle> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_PROXY) + +class QProxyStylePrivate; +class Q_GUI_EXPORT QProxyStyle : public QCommonStyle +{ + Q_OBJECT + +public: + QProxyStyle(QStyle *baseStyle = 0); + ~QProxyStyle(); + + QStyle *baseStyle() const; + void setBaseStyle(QStyle *style); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = 0) const; + void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + virtual void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const; + + QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const; + QRect itemTextRect(const QFontMetrics &fm, const QRect &r, int flags, bool enabled, const QString &text) const; + QRect itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const; + + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, const QPoint &pos, const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, const QWidget *widget = 0) const; + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, const QStyleOption *opt) const; + QPalette standardPalette() const; + + void polish(QWidget *widget); + void polish(QPalette &pal); + void polish(QApplication *app); + + void unpolish(QWidget *widget); + void unpolish(QApplication *app); + +protected: + bool event(QEvent *e); + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const; + int layoutSpacingImplementation(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, + Qt::Orientation orientation, const QStyleOption *option = 0, const QWidget *widget = 0) const; +private: + Q_DISABLE_COPY(QProxyStyle) + Q_DECLARE_PRIVATE(QProxyStyle) +}; + +#endif // QT_NO_STYLE_PROXY + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPROXYSTYLE_H diff --git a/src/gui/styles/qproxystyle_p.h b/src/gui/styles/qproxystyle_p.h new file mode 100644 index 0000000000..8c330d0330 --- /dev/null +++ b/src/gui/styles/qproxystyle_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPROXYSTYLE_P_H +#define QPROXYSTYLE_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 "qcommonstyle.h" +#include "qcommonstyle_p.h" +#include "qproxystyle.h" + +#ifndef QT_NO_STYLE_PROXY + +QT_BEGIN_NAMESPACE + +class QProxyStylePrivate : public QCommonStylePrivate +{ + Q_DECLARE_PUBLIC(QProxyStyle) +public: + void ensureBaseStyle() const; +private: + QProxyStylePrivate() : + QCommonStylePrivate(), baseStyle(0) {} + mutable QPointer <QStyle> baseStyle; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_PROXY + +#endif //QPROXYSTYLE_P_H diff --git a/src/gui/styles/qs60style.cpp b/src/gui/styles/qs60style.cpp new file mode 100644 index 0000000000..fa6eeb7b36 --- /dev/null +++ b/src/gui/styles/qs60style.cpp @@ -0,0 +1,3618 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qs60style_p.h" + +#include "qapplication.h" +#include "qpainter.h" +#include "qstyleoption.h" +#include "qevent.h" +#include "qpixmapcache.h" + +#include "qcalendarwidget.h" +#include "qdial.h" +#include "qdialog.h" +#include "qmessagebox.h" +#include "qgroupbox.h" +#include "qheaderview.h" +#include "qlist.h" +#include "qlistwidget.h" +#include "qlistview.h" +#include "qmenu.h" +#include "qmenubar.h" +#include "qpushbutton.h" +#include "qscrollarea.h" +#include "qscrollbar.h" +#include "qtabbar.h" +#include "qtableview.h" +#include "qtextedit.h" +#include "qtoolbar.h" +#include "qtoolbutton.h" +#include "qfocusframe.h" +#include "qformlayout.h" +#include "qradiobutton.h" +#include "qcheckbox.h" +#include "qdesktopwidget.h" +#include "qprogressbar.h" +#include "qlabel.h" + +#include "private/qtoolbarextension_p.h" +#include "private/qcombobox_p.h" +#include "private/qwidget_p.h" +#include "private/qapplication_p.h" +#include "private/qfont_p.h" + +#if !defined(QT_NO_STYLE_S60) || defined(QT_PLUGIN) + +QT_BEGIN_NAMESPACE + +const QS60StylePrivate::SkinElementFlags QS60StylePrivate::KDefaultSkinElementFlags = + SkinElementFlags(SF_PointNorth | SF_StateEnabled); + +static const qreal goldenRatio = 1.618; + +const layoutHeader QS60StylePrivate::m_layoutHeaders[] = { +// *** generated layout data *** +{240,320,1,19,"QVGA Landscape"}, +{320,240,1,19,"QVGA Portrait"}, +{360,640,1,19,"NHD Landscape"}, +{640,360,1,19,"NHD Portrait"}, +{352,800,1,12,"E90 Landscape"}, +{480,640,1,19,"VGA Landscape"} +// *** End of generated data *** +}; +const int QS60StylePrivate::m_numberOfLayouts = + (int)sizeof(QS60StylePrivate::m_layoutHeaders)/sizeof(QS60StylePrivate::m_layoutHeaders[0]); + +const short QS60StylePrivate::data[][MAX_PIXELMETRICS] = { +// *** generated pixel metrics *** +{5,0,-909,0,0,2,0,2,-1,7,12,22,15,15,7,198,-909,-909,-909,20,13,2,0,0,21,7,18,30,3,3,1,-909,-909,0,1,0,0,12,20,15,15,18,18,1,115,18,0,-909,-909,-909,-909,0,0,16,2,-909,0,0,-909,16,-909,-909,-909,-909,32,18,55,24,55,4,4,4,9,13,-909,5,51,11,5,0,3,3,6,8,3,3,-909,2,-909,-909,-909,-909,5,5,3,1,106}, +{5,0,-909,0,0,1,0,2,-1,8,14,22,15,15,7,164,-909,-909,-909,19,15,2,0,0,21,8,27,28,4,4,1,-909,-909,0,7,6,0,13,23,17,17,21,21,7,115,21,0,-909,-909,-909,-909,0,0,15,1,-909,0,0,-909,15,-909,-909,-909,-909,32,21,65,27,65,3,3,5,10,15,-909,5,58,13,5,0,4,4,7,9,4,4,-909,2,-909,-909,-909,-909,6,6,3,1,106}, +{7,0,-909,0,0,2,0,5,-1,25,69,46,37,37,9,258,-909,-909,-909,23,19,11,0,0,32,25,72,44,5,5,2,-909,-909,0,7,21,0,17,29,22,22,27,27,7,173,29,0,-909,-909,-909,-909,0,0,25,2,-909,0,0,-909,25,-909,-909,-909,-909,87,27,77,35,77,13,3,6,8,19,-909,7,74,19,7,0,5,5,8,12,5,5,-909,3,-909,-909,-909,-909,7,7,3,1,135}, +{7,0,-909,0,0,2,0,5,-1,25,68,46,37,37,9,258,-909,-909,-909,31,19,13,0,0,32,25,60,52,5,5,2,-909,-909,0,7,32,0,17,29,22,22,27,27,7,173,29,0,-909,-909,-909,-909,0,0,26,2,-909,0,0,-909,26,-909,-909,-909,-909,87,27,96,35,96,12,3,6,8,19,-909,7,74,22,7,0,5,5,8,12,5,5,-909,3,-909,-909,-909,-909,7,7,3,1,135}, +{7,0,-909,0,0,2,0,2,-1,10,20,27,18,18,9,301,-909,-909,-909,29,18,5,0,0,35,7,32,30,5,5,2,-909,-909,0,2,8,0,16,28,21,21,26,26,2,170,26,0,-909,-909,-909,-909,0,0,21,6,-909,0,0,-909,-909,-909,-909,-909,-909,54,26,265,34,265,5,5,6,3,18,-909,7,72,19,7,0,5,6,8,11,6,5,-909,2,-909,-909,-909,-909,5,5,3,1,106}, +{9,0,-909,0,0,2,0,5,-1,34,99,76,51,51,25,352,-909,-909,-909,29,25,7,0,0,43,34,42,76,7,7,2,-909,-909,0,9,14,0,23,39,30,30,37,37,9,391,40,0,-909,-909,-909,-909,0,0,29,2,-909,0,0,-909,29,-909,-909,-909,-909,115,37,96,48,96,19,19,9,1,25,-909,9,101,24,9,0,7,7,7,16,7,7,-909,3,-909,-909,-909,-909,9,9,3,1,184} +// *** End of generated data *** +}; + +const short *QS60StylePrivate::m_pmPointer = QS60StylePrivate::data[0]; + +// theme background texture +QPixmap *QS60StylePrivate::m_background = 0; +QPixmap *QS60StylePrivate::m_placeHolderTexture = 0; + +// theme palette +QPalette *QS60StylePrivate::m_themePalette = 0; + +qint64 QS60StylePrivate::m_webPaletteKey = 0; + +QPointer<QWidget> QS60StylePrivate::m_pressedWidget = 0; + +const struct QS60StylePrivate::frameElementCenter QS60StylePrivate::m_frameElementsData[] = { + {SE_ButtonNormal, QS60StyleEnums::SP_QsnFrButtonTbCenter}, + {SE_ButtonPressed, QS60StyleEnums::SP_QsnFrButtonTbCenterPressed}, + {SE_FrameLineEdit, QS60StyleEnums::SP_QsnFrInputCenter}, + {SE_ListHighlight, QS60StyleEnums::SP_QsnFrListCenter}, + {SE_PopupBackground, QS60StyleEnums::SP_QsnFrPopupCenter}, + {SE_SettingsList, QS60StyleEnums::SP_QsnFrSetOptCenter}, + {SE_TableItem, QS60StyleEnums::SP_QsnFrCaleCenter}, + {SE_TableHeaderItem, QS60StyleEnums::SP_QsnFrCaleHeadingCenter}, + {SE_ToolTip, QS60StyleEnums::SP_QsnFrPopupPreviewCenter}, + {SE_ToolBar, QS60StyleEnums::SP_QsnFrPopupSubCenter}, + {SE_ToolBarButton, QS60StyleEnums::SP_QgnFrSctrlButtonCenter}, + {SE_ToolBarButtonPressed, QS60StyleEnums::SP_QgnFrSctrlButtonCenterPressed}, + {SE_PanelBackground, QS60StyleEnums::SP_QsnFrSetOptCenter}, + {SE_ButtonInactive, QS60StyleEnums::SP_QsnFrButtonCenterInactive}, + {SE_Editor, QS60StyleEnums::SP_QsnFrInputCenter}, + {SE_TableItemPressed, QS60StyleEnums::SP_QsnFrGridCenterPressed}, + {SE_ListItemPressed, QS60StyleEnums::SP_QsnFrListCenterPressed}, +}; + +static const int frameElementsCount = + int(sizeof(QS60StylePrivate::m_frameElementsData)/sizeof(QS60StylePrivate::m_frameElementsData[0])); + +const int KNotFound = -909; +const double KTabFontMul = 0.72; + +QS60StylePrivate::~QS60StylePrivate() +{ + clearCaches(); //deletes also background image + if (m_placeHolderTexture) { + delete m_placeHolderTexture; + m_placeHolderTexture = 0; + } + deleteThemePalette(); +#ifdef Q_WS_S60 + removeAnimations(); +#endif +} + +void QS60StylePrivate::drawSkinElement(SkinElements element, QPainter *painter, + const QRect &rect, SkinElementFlags flags) +{ + switch (element) { + case SE_ButtonNormal: + drawFrame(SF_ButtonNormal, painter, rect, flags | SF_PointNorth); + break; + case SE_ButtonPressed: + drawFrame(SF_ButtonPressed, painter, rect, flags | SF_PointNorth); + break; + case SE_FrameLineEdit: + drawFrame(SF_FrameLineEdit, painter, rect, flags | SF_PointNorth); + break; + case SE_ProgressBarGrooveHorizontal: + drawRow(QS60StyleEnums::SP_QgnGrafBarFrameSideL, QS60StyleEnums::SP_QgnGrafBarFrameCenter, + QS60StyleEnums::SP_QgnGrafBarFrameSideR, Qt::Horizontal, painter, rect, flags | SF_PointNorth); + break; + case SE_ProgressBarGrooveVertical: + drawRow(QS60StyleEnums::SP_QgnGrafBarFrameSideL, QS60StyleEnums::SP_QgnGrafBarFrameCenter, + QS60StyleEnums::SP_QgnGrafBarFrameSideR, Qt::Vertical, painter, rect, flags | SF_PointEast); + break; + case SE_ProgressBarIndicatorHorizontal: + drawPart(QS60StyleEnums::SP_QgnGrafBarProgress, painter, rect, flags | SF_PointNorth); + break; + case SE_ProgressBarIndicatorVertical: + drawPart(QS60StyleEnums::SP_QgnGrafBarProgress, painter, rect, flags | SF_PointWest); + break; + case SE_ScrollBarGrooveHorizontal: + drawRow(QS60StyleEnums::SP_QsnCpScrollBgBottom, QS60StyleEnums::SP_QsnCpScrollBgMiddle, + QS60StyleEnums::SP_QsnCpScrollBgTop, Qt::Horizontal, painter, rect, flags | SF_PointEast); + break; + case SE_ScrollBarGrooveVertical: + drawRow(QS60StyleEnums::SP_QsnCpScrollBgTop, QS60StyleEnums::SP_QsnCpScrollBgMiddle, + QS60StyleEnums::SP_QsnCpScrollBgBottom, Qt::Vertical, painter, rect, flags | SF_PointNorth); + break; + case SE_ScrollBarHandleHorizontal: + drawRow(QS60StyleEnums::SP_QsnCpScrollHandleBottom, QS60StyleEnums::SP_QsnCpScrollHandleMiddle, + QS60StyleEnums::SP_QsnCpScrollHandleTop, Qt::Horizontal, painter, rect, flags | SF_PointEast); + break; + case SE_ScrollBarHandleVertical: + drawRow(QS60StyleEnums::SP_QsnCpScrollHandleTop, QS60StyleEnums::SP_QsnCpScrollHandleMiddle, + QS60StyleEnums::SP_QsnCpScrollHandleBottom, Qt::Vertical, painter, rect, flags | SF_PointNorth); + break; + case SE_SliderHandleHorizontal: + drawPart(QS60StyleEnums::SP_QgnGrafNsliderMarker, painter, rect, flags | SF_PointNorth); + break; + case SE_SliderHandleVertical: + drawPart(QS60StyleEnums::SP_QgnGrafNsliderMarker, painter, rect, flags | SF_PointEast); + break; + case SE_SliderHandleSelectedHorizontal: + drawPart(QS60StyleEnums::SP_QgnGrafNsliderMarkerSelected, painter, rect, flags | SF_PointNorth); + break; + case SE_SliderHandleSelectedVertical: + drawPart(QS60StyleEnums::SP_QgnGrafNsliderMarkerSelected, painter, rect, flags | SF_PointEast); + break; + case SE_SliderGrooveVertical: + drawRow(QS60StyleEnums::SP_QgnGrafNsliderEndLeft, QS60StyleEnums::SP_QgnGrafNsliderMiddle, + QS60StyleEnums::SP_QgnGrafNsliderEndRight, Qt::Vertical, painter, rect, flags | SF_PointEast); + break; + case SE_SliderGrooveHorizontal: + drawRow(QS60StyleEnums::SP_QgnGrafNsliderEndLeft, QS60StyleEnums::SP_QgnGrafNsliderMiddle, + QS60StyleEnums::SP_QgnGrafNsliderEndRight, Qt::Horizontal, painter, rect, flags | SF_PointNorth); + break; + case SE_TabBarTabEastActive: + drawRow(QS60StyleEnums::SP_QgnGrafTabActiveL, QS60StyleEnums::SP_QgnGrafTabActiveM, + QS60StyleEnums::SP_QgnGrafTabActiveR, Qt::Vertical, painter, rect, flags | SF_PointEast); + break; + case SE_TabBarTabEastInactive: + drawRow(QS60StyleEnums::SP_QgnGrafTabPassiveL, QS60StyleEnums::SP_QgnGrafTabPassiveM, + QS60StyleEnums::SP_QgnGrafTabPassiveR, Qt::Vertical, painter, rect, flags | SF_PointEast); + break; + case SE_TabBarTabNorthActive: + drawRow(QS60StyleEnums::SP_QgnGrafTabActiveL, QS60StyleEnums::SP_QgnGrafTabActiveM, + QS60StyleEnums::SP_QgnGrafTabActiveR, Qt::Horizontal, painter, rect, flags | SF_PointNorth); + break; + case SE_TabBarTabNorthInactive: + drawRow(QS60StyleEnums::SP_QgnGrafTabPassiveL, QS60StyleEnums::SP_QgnGrafTabPassiveM, + QS60StyleEnums::SP_QgnGrafTabPassiveR, Qt::Horizontal, painter, rect, flags | SF_PointNorth); + break; + case SE_TabBarTabSouthActive: + drawRow(QS60StyleEnums::SP_QgnGrafTabActiveR, QS60StyleEnums::SP_QgnGrafTabActiveM, + QS60StyleEnums::SP_QgnGrafTabActiveL, Qt::Horizontal, painter, rect, flags | SF_PointSouth); + break; + case SE_TabBarTabSouthInactive: + drawRow(QS60StyleEnums::SP_QgnGrafTabPassiveR, QS60StyleEnums::SP_QgnGrafTabPassiveM, + QS60StyleEnums::SP_QgnGrafTabPassiveL, Qt::Horizontal, painter, rect, flags | SF_PointSouth); + break; + case SE_TabBarTabWestActive: + drawRow(QS60StyleEnums::SP_QgnGrafTabActiveR, QS60StyleEnums::SP_QgnGrafTabActiveM, + QS60StyleEnums::SP_QgnGrafTabActiveL, Qt::Vertical, painter, rect, flags | SF_PointWest); + break; + case SE_TabBarTabWestInactive: + drawRow(QS60StyleEnums::SP_QgnGrafTabPassiveR, QS60StyleEnums::SP_QgnGrafTabPassiveM, + QS60StyleEnums::SP_QgnGrafTabPassiveL, Qt::Vertical, painter, rect, flags | SF_PointWest); + break; + case SE_ListHighlight: + drawFrame(SF_ListHighlight, painter, rect, flags | SF_PointNorth); + break; + case SE_PopupBackground: + drawFrame(SF_PopupBackground, painter, rect, flags | SF_PointNorth); + break; + case SE_SettingsList: + drawFrame(SF_SettingsList, painter, rect, flags | SF_PointNorth); + break; + case SE_TableItem: + drawFrame(SF_TableItem, painter, rect, flags | SF_PointNorth); + break; + case SE_TableHeaderItem: + drawFrame(SF_TableHeaderItem, painter, rect, flags | SF_PointNorth); + break; + case SE_ToolTip: + drawFrame(SF_ToolTip, painter, rect, flags | SF_PointNorth); + break; + case SE_ToolBar: + drawFrame(SF_ToolBar, painter, rect, flags | SF_PointNorth); + break; + case SE_ToolBarButton: + drawFrame(SF_ToolBarButton, painter, rect, flags | SF_PointNorth); + break; + case SE_ToolBarButtonPressed: + drawFrame(SF_ToolBarButtonPressed, painter, rect, flags | SF_PointNorth); + break; + case SE_PanelBackground: + drawFrame(SF_PanelBackground, painter, rect, flags | SF_PointNorth); + break; + case SE_ScrollBarHandlePressedHorizontal: + drawRow(QS60StyleEnums::SP_QsnCpScrollHandleBottomPressed, QS60StyleEnums::SP_QsnCpScrollHandleMiddlePressed, + QS60StyleEnums::SP_QsnCpScrollHandleTopPressed, Qt::Horizontal, painter, rect, flags | SF_PointEast); + break; + case SE_ScrollBarHandlePressedVertical: + drawRow(QS60StyleEnums::SP_QsnCpScrollHandleTopPressed, QS60StyleEnums::SP_QsnCpScrollHandleMiddlePressed, + QS60StyleEnums::SP_QsnCpScrollHandleBottomPressed, Qt::Vertical, painter, rect, flags | SF_PointNorth); + break; + case SE_ButtonInactive: + drawFrame(SF_ButtonInactive, painter, rect, flags | SF_PointNorth); + break; + case SE_Editor: + drawFrame(SF_FrameLineEdit, painter, rect, flags | SF_PointNorth); + break; + case SE_DropArea: + drawPart(QS60StyleEnums::SP_QgnGrafOrgBgGrid, painter, rect, flags | SF_PointNorth); + break; + case SE_TableItemPressed: + drawFrame(SF_TableItemPressed, painter, rect, flags | SF_PointNorth); + break; + case SE_ListItemPressed: + drawFrame(SF_ListItemPressed, painter, rect, flags | SF_PointNorth); + break; + default: + break; + } +} + +void QS60StylePrivate::drawSkinPart(QS60StyleEnums::SkinParts part, + QPainter *painter, const QRect &rect, SkinElementFlags flags) +{ + drawPart(part, painter, rect, flags); +} + +short QS60StylePrivate::pixelMetric(int metric) +{ + //If it is a custom value, need to strip away the base to map to internal + //pixel metric value table + if (metric & QStyle::PM_CustomBase) { + metric -= QStyle::PM_CustomBase; + metric += MAX_NON_CUSTOM_PIXELMETRICS - 1; + } + + Q_ASSERT(metric < MAX_PIXELMETRICS); + const short returnValue = m_pmPointer[metric]; + return returnValue; +} + +QColor QS60StylePrivate::stateColor(const QColor &color, const QStyleOption *option) +{ + QColor retColor (color); + if (option && !(option->state & QStyle::State_Enabled)) { + QColor hsvColor = retColor.toHsv(); + int colorSat = hsvColor.saturation(); + int colorVal = hsvColor.value(); + colorSat = (colorSat != 0) ? (colorSat >> 1) : 128; + colorVal = (colorVal != 0) ? (colorVal >> 1) : 128; + hsvColor.setHsv(hsvColor.hue(), colorSat, colorVal); + retColor = hsvColor.toRgb(); + } + return retColor; +} + +QColor QS60StylePrivate::lighterColor(const QColor &baseColor) +{ + QColor result(baseColor); + bool modifyColor = false; + if (result.saturation() == 0) { + result.setHsv(result.hue(), 128, result.value()); + modifyColor = true; + } + if (result.value() == 0) { + result.setHsv(result.hue(), result.saturation(), 128); + modifyColor = true; + } + if (modifyColor) + result = result.lighter(175); + else + result = result.lighter(225); + return result; +} + +bool QS60StylePrivate::drawsOwnThemeBackground(const QWidget *widget) +{ + return (widget ? (widget->windowType() == Qt::Dialog) : false); +} + +QFont QS60StylePrivate::s60Font( + QS60StyleEnums::FontCategories fontCategory, + int pointSize, bool resolveFontSize) const +{ + QFont result; + int actualPointSize = pointSize; + if (actualPointSize <= 0) { + const QFont appFont = QApplication::font(); + actualPointSize = appFont.pointSize(); + if (actualPointSize <= 0) + actualPointSize = appFont.pixelSize() * 72 / qt_defaultDpiY(); + } + Q_ASSERT(actualPointSize > 0); + const QPair<QS60StyleEnums::FontCategories, int> key(fontCategory, actualPointSize); + if (!m_mappedFontsCache.contains(key)) { + result = s60Font_specific(fontCategory, actualPointSize, resolveFontSize); + m_mappedFontsCache.insert(key, result); + } else { + result = m_mappedFontsCache.value(key); + if (result.pointSize() != actualPointSize) + result.setPointSize(actualPointSize); + } + return result; +} + +void QS60StylePrivate::clearCaches(CacheClearReason reason) +{ + switch(reason){ + case CC_LayoutChange: + // when layout changes, the colors remain in cache, but graphics and fonts can change + m_mappedFontsCache.clear(); + QPixmapCache::clear(); + break; + case CC_ThemeChange: + QPixmapCache::clear(); +#ifdef Q_WS_S60 + deleteStoredSettings(); +#endif + deleteBackground(); + break; + case CC_UndefinedChange: + default: + m_mappedFontsCache.clear(); + QPixmapCache::clear(); + deleteBackground(); + break; + } +} + +QColor QS60StylePrivate::calculatedColor(SkinFrameElements frame) const +{ + const int frameCornerWidth = pixelMetric(PM_FrameCornerWidth); + const int frameCornerHeight = pixelMetric(PM_FrameCornerHeight); + Q_ASSERT(2 * frameCornerWidth < 32); + Q_ASSERT(2 * frameCornerHeight < 32); + + const QImage frameImage = QS60StylePrivate::frame(frame, QSize(32, 32)).toImage(); + Q_ASSERT(frameImage.bytesPerLine() > 0); + if (frameImage.isNull()) + return Qt::black; + + const QRgb *pixelRgb = (const QRgb*)frameImage.constBits(); + const int pixels = frameImage.byteCount() / sizeof(QRgb); + + int estimatedRed = 0; + int estimatedGreen = 0; + int estimatedBlue = 0; + + int skips = 0; + int estimations = 0; + + const int topBorderLastPixel = frameCornerHeight * frameImage.width() - 1; + const int bottomBorderFirstPixel = frameImage.width() * frameImage.height() - topBorderLastPixel; + const int rightBorderFirstPixel = frameImage.width() - frameCornerWidth; + const int leftBorderLastPixel = frameCornerWidth; + + while ((skips + estimations) < pixels) { + if ((skips + estimations) > topBorderLastPixel && + (skips + estimations) < bottomBorderFirstPixel) { + for (int rowIndex = 0; rowIndex < frameImage.width(); rowIndex++) { + if (rowIndex > leftBorderLastPixel && + rowIndex < rightBorderFirstPixel) { + estimatedRed += qRed(*pixelRgb); + estimatedGreen += qGreen(*pixelRgb); + estimatedBlue += qBlue(*pixelRgb); + } + pixelRgb++; + estimations++; + } + } else { + pixelRgb++; + skips++; + } + } + QColor frameColor(estimatedRed/estimations, estimatedGreen/estimations, estimatedBlue/estimations); + return !estimations ? Qt::black : frameColor; +} + +void QS60StylePrivate::setThemePalette(QApplication *app) const +{ + Q_UNUSED(app) + QPalette widgetPalette = QPalette(Qt::white); + setThemePalette(&widgetPalette); +} + +QPalette* QS60StylePrivate::themePalette() +{ + return m_themePalette; +} + +bool QS60StylePrivate::equalToThemePalette(QColor color, QPalette::ColorRole role) +{ + if (!m_themePalette) + return false; + if (color == m_themePalette->color(role)) + return true; + return false; +} + +bool QS60StylePrivate::equalToThemePalette(qint64 cacheKey, QPalette::ColorRole role) +{ + if (!m_themePalette) + return false; + if (cacheKey == m_themePalette->brush(role).texture().cacheKey()) + return true; + return false; +} + +void QS60StylePrivate::setBackgroundTexture(QApplication *app) const +{ + Q_UNUSED(app) + QPalette applicationPalette = QApplication::palette(); + // The initial QPalette::Window is just a placeHolder QPixmap to save RAM + // if the actual texture is not needed. The real texture is created just before + // painting it in qt_s60_fill_background(). + applicationPalette.setBrush(QPalette::Window, placeHolderTexture()); + setThemePalette(&applicationPalette); +} + +void QS60StylePrivate::deleteBackground() +{ + if (m_background) { + delete m_background; + m_background = 0; + } +} + +void QS60StylePrivate::setCurrentLayout(int index) +{ + m_pmPointer = data[index]; +} + +void QS60StylePrivate::drawPart(QS60StyleEnums::SkinParts skinPart, + QPainter *painter, const QRect &rect, SkinElementFlags flags) +{ + static const bool doCache = +#if defined(Q_WS_S60) + // Freezes on 3.1. Anyways, caching is only really needed on touch UI + !(QSysInfo::s60Version() == QSysInfo::SV_S60_3_1 || QSysInfo::s60Version() == QSysInfo::SV_S60_3_2); +#else + true; +#endif + + const QPixmap skinPartPixMap((doCache ? cachedPart : part)(skinPart, rect.size(), painter, flags)); + if (!skinPartPixMap.isNull()) + painter->drawPixmap(rect.topLeft(), skinPartPixMap); +} + +void QS60StylePrivate::drawFrame(SkinFrameElements frameElement, QPainter *painter, const QRect &rect, SkinElementFlags flags) +{ + static const bool doCache = +#if defined(Q_WS_S60) + // Freezes on 3.1. Anyways, caching is only really needed on touch UI + !(QSysInfo::s60Version() == QSysInfo::SV_S60_3_1 || QSysInfo::s60Version() == QSysInfo::SV_S60_3_2); +#else + true; +#endif + const QPixmap frameElementPixMap((doCache ? cachedFrame : frame)(frameElement, rect.size(), flags)); + if (!frameElementPixMap.isNull()) + painter->drawPixmap(rect.topLeft(), frameElementPixMap); +} + +void QS60StylePrivate::drawRow(QS60StyleEnums::SkinParts start, + QS60StyleEnums::SkinParts middle, QS60StyleEnums::SkinParts end, + Qt::Orientation orientation, QPainter *painter, const QRect &rect, + SkinElementFlags flags) +{ + QSize startEndSize(partSize(start, flags)); + startEndSize.scale(rect.size(), Qt::KeepAspectRatio); + + QRect startRect = QRect(rect.topLeft(), startEndSize); + QRect middleRect = rect; + QRect endRect; + + if (orientation == Qt::Horizontal) { + startRect.setHeight(rect.height()); + startRect.setWidth(qMin((rect.width() >> 1) - 1, startRect.width())); + endRect = startRect.translated(rect.width() - startRect.width(), 0); + middleRect.adjust(startRect.width(), 0, -startRect.width(), 0); + if (startRect.bottomRight().x() > endRect.topLeft().x()) { + const int overlap = (startRect.bottomRight().x() - endRect.topLeft().x()) >> 1; + startRect.setWidth(startRect.width() - overlap); + endRect.adjust(overlap, 0, 0, 0); + } + } else { + startRect.setWidth(rect.width()); + startRect.setHeight(qMin((rect.height() >> 1) - 1, startRect.height())); + endRect = startRect.translated(0, rect.height() - startRect.height()); + middleRect.adjust(0, startRect.height(), 0, -startRect.height()); + if (startRect.topRight().y() > endRect.bottomLeft().y()) { + const int overlap = (startRect.topRight().y() - endRect.bottomLeft().y()) >> 1; + startRect.setHeight(startRect.height() - overlap); + endRect.adjust(0, overlap, 0, 0); + } + } + +#if 0 + painter->save(); + painter->setOpacity(.3); + painter->fillRect(startRect, Qt::red); + painter->fillRect(middleRect, Qt::green); + painter->fillRect(endRect, Qt::blue); + painter->restore(); +#else + drawPart(start, painter, startRect, flags); + if (middleRect.isValid()) + drawPart(middle, painter, middleRect, flags); + drawPart(end, painter, endRect, flags); +#endif +} + +QPixmap QS60StylePrivate::cachedPart(QS60StyleEnums::SkinParts part, + const QSize &size, QPainter *painter, SkinElementFlags flags) +{ + QPixmap result; + const int animationFrame = (flags & SF_Animation) ? currentAnimationFrame(part) : 0; + + const QString cacheKey = + QString::fromLatin1("S60Style: SkinParts=%1 QSize=%2|%3 SkinPartFlags=%4 AnimationFrame=%5") + .arg((int)part).arg(size.width()).arg(size.height()).arg((int)flags).arg(animationFrame); + if (!QPixmapCache::find(cacheKey, result)) { + result = QS60StylePrivate::part(part, size, painter, flags); + QPixmapCache::insert(cacheKey, result); + } + return result; +} + +QPixmap QS60StylePrivate::cachedFrame(SkinFrameElements frame, const QSize &size, SkinElementFlags flags) +{ + QPixmap result; + const QString cacheKey = + QString::fromLatin1("S60Style: SkinFrameElements=%1 QSize=%2|%3 SkinElementFlags=%4") + .arg((int)frame).arg(size.width()).arg(size.height()).arg((int)flags); + if (!QPixmapCache::find(cacheKey, result)) { + result = QS60StylePrivate::frame(frame, size, flags); + QPixmapCache::insert(cacheKey, result); + } + return result; +} + +void QS60StylePrivate::setFont(QWidget *widget) const +{ + QS60StyleEnums::FontCategories fontCategory = QS60StyleEnums::FC_Undefined; + if (!widget) + return; + if (qobject_cast<QPushButton *>(widget)){ + fontCategory = QS60StyleEnums::FC_Primary; + } else if (qobject_cast<QToolButton *>(widget)){ + fontCategory = QS60StyleEnums::FC_Primary; + } else if (qobject_cast<QHeaderView *>(widget)){ + fontCategory = QS60StyleEnums::FC_Secondary; + } else if (qobject_cast<QGroupBox *>(widget)){ + fontCategory = QS60StyleEnums::FC_Title; + } else if (qobject_cast<QMessageBox *>(widget)){ + fontCategory = QS60StyleEnums::FC_Primary; + } else if (qobject_cast<QMenu *>(widget)){ + fontCategory = QS60StyleEnums::FC_Primary; + } else if (qobject_cast<QCalendarWidget *>(widget)){ + fontCategory = QS60StyleEnums::FC_Secondary; + } + if (fontCategory != QS60StyleEnums::FC_Undefined) { + const bool resolveFontSize = widget->testAttribute(Qt::WA_SetFont) + && (widget->font().resolve() & QFont::SizeResolved); + const QFont suggestedFont = + s60Font(fontCategory, widget->font().pointSizeF(), resolveFontSize); + widget->setFont(suggestedFont); + } +} + +void QS60StylePrivate::setThemePalette(QWidget *widget) +{ + if(!widget) + return; + + //header view and its viewport need to be set 100% transparent button color, since drawing code will + //draw transparent theme graphics to table column and row headers. + if (qobject_cast<QHeaderView *>(widget)){ + QPalette widgetPalette = QApplication::palette(widget); + widgetPalette.setColor(QPalette::Active, QPalette::ButtonText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 23, 0)); + QHeaderView* header = qobject_cast<QHeaderView *>(widget); + widgetPalette.setColor(QPalette::Button, Qt::transparent ); + if (header->viewport()) + header->viewport()->setPalette(widgetPalette); + QApplication::setPalette(widgetPalette, "QHeaderView"); + } else if (qobject_cast<QLabel *>(widget)) { + if (widget->window() && widget->window()->windowType() == Qt::Dialog) { + QPalette widgetPalette = widget->palette(); + widgetPalette.setColor(QPalette::WindowText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 19, 0)); + widget->setPalette(widgetPalette); + } + } +} + +void QS60StylePrivate::setThemePalette(QPalette *palette) const +{ + if (!palette) + return; + + // basic colors + palette->setColor(QPalette::WindowText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 6, 0)); + palette->setColor(QPalette::ButtonText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 20, 0)); + palette->setColor(QPalette::Text, + s60Color(QS60StyleEnums::CL_QsnTextColors, 6, 0)); + palette->setColor(QPalette::ToolTipText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 55, 0)); + palette->setColor(QPalette::BrightText, palette->color(QPalette::WindowText).lighter()); + palette->setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 24, 0)); + palette->setColor(QPalette::Link, + s60Color(QS60StyleEnums::CL_QsnHighlightColors, 3, 0)); + palette->setColor(QPalette::LinkVisited, palette->color(QPalette::Link).darker()); + palette->setColor(QPalette::Highlight, + s60Color(QS60StyleEnums::CL_QsnHighlightColors, 2, 0)); + // The initial QPalette::Window is just a placeHolder QPixmap to save RAM + // if the actual texture is not needed. The real texture is created just before + // painting it in qt_s60_fill_background(). + palette->setBrush(QPalette::Window, placeHolderTexture()); + // set as transparent so that styled full screen theme background is visible + palette->setBrush(QPalette::Base, Qt::transparent); + // set button color based on pixel colors +#ifndef Q_WS_S60 + //For emulated style, just calculate the color every time + const QColor buttonColor = calculatedColor(SF_ButtonNormal); +#else + const QColor buttonColor = colorFromFrameGraphics(SF_ButtonNormal); +#endif + palette->setColor(QPalette::Button, buttonColor); + palette->setColor(QPalette::Light, palette->color(QPalette::Button).lighter()); + palette->setColor(QPalette::Dark, palette->color(QPalette::Button).darker()); + palette->setColor(QPalette::Midlight, palette->color(QPalette::Button).lighter(125)); + palette->setColor(QPalette::Mid, palette->color(QPalette::Button).darker(150)); + palette->setColor(QPalette::Shadow, Qt::black); + QColor alternateBase = palette->light().color(); + alternateBase.setAlphaF(0.8); + palette->setColor(QPalette::AlternateBase, alternateBase); + + QApplication::setPalette(*palette); //calling QApplication::setPalette clears palette hash + setThemePaletteHash(palette); + storeThemePalette(palette); +} + +void QS60StylePrivate::deleteThemePalette() +{ + if (m_themePalette) { + delete m_themePalette; + m_themePalette = 0; + } +} + +void QS60StylePrivate::storeThemePalette(QPalette *palette) +{ + deleteThemePalette(); + //store specified palette for latter use. + m_themePalette = new QPalette(*palette); +} + +// set widget specific palettes +void QS60StylePrivate::setThemePaletteHash(QPalette *palette) +{ + if (!palette) + return; + + //store the original palette + QPalette widgetPalette = *palette; + const QColor mainAreaTextColor = + s60Color(QS60StyleEnums::CL_QsnTextColors, 6, 0); + + widgetPalette.setColor(QPalette::WindowText, + s60Color(QS60StyleEnums::CL_QsnLineColors, 8, 0)); + QApplication::setPalette(widgetPalette, "QSlider"); + // return to original palette after each widget + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Active, QPalette::ButtonText, mainAreaTextColor); + widgetPalette.setColor(QPalette::Inactive, QPalette::ButtonText, mainAreaTextColor); + const QStyleOption opt; + widgetPalette.setColor(QPalette::Disabled, QPalette::ButtonText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 6, &opt)); + QApplication::setPalette(widgetPalette, "QPushButton"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Active, QPalette::ButtonText, mainAreaTextColor); + widgetPalette.setColor(QPalette::Inactive, QPalette::ButtonText, mainAreaTextColor); + QApplication::setPalette(widgetPalette, "QToolButton"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Active, QPalette::ButtonText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 23, 0)); + QApplication::setPalette(widgetPalette, "QHeaderView"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::ButtonText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 8, 0)); + QApplication::setPalette(widgetPalette, "QMenuBar"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Text, + s60Color(QS60StyleEnums::CL_QsnTextColors, 22, 0)); + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 11, 0)); + QApplication::setPalette(widgetPalette, "QMenu"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::WindowText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 4, 0)); + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 3, 0)); + QApplication::setPalette(widgetPalette, "QTabBar"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 10, 0)); + QApplication::setPalette(widgetPalette, "QListView"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Text, + s60Color(QS60StyleEnums::CL_QsnTextColors, 22, 0)); + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 11, 0)); + QApplication::setPalette(widgetPalette, "QTableView"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::Text, + s60Color(QS60StyleEnums::CL_QsnTextColors, 27, 0)); + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 24, 0)); + QApplication::setPalette(widgetPalette, "QLineEdit"); + QApplication::setPalette(widgetPalette, "QTextEdit"); + QApplication::setPalette(widgetPalette, "QComboBox"); + QApplication::setPalette(widgetPalette, "QSpinBox"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::WindowText, s60Color(QS60StyleEnums::CL_QsnTextColors, 7, 0)); + widgetPalette.setColor(QPalette::HighlightedText, + s60Color(QS60StyleEnums::CL_QsnTextColors, 11, 0)); + QApplication::setPalette(widgetPalette, "QRadioButton"); + QApplication::setPalette(widgetPalette, "QCheckBox"); + widgetPalette = *palette; + + widgetPalette.setColor(QPalette::WindowText, mainAreaTextColor); + widgetPalette.setColor(QPalette::Button, QApplication::palette().color(QPalette::Button)); + widgetPalette.setColor(QPalette::Dark, mainAreaTextColor.darker()); + widgetPalette.setColor(QPalette::Light, mainAreaTextColor.lighter()); + QApplication::setPalette(widgetPalette, "QDial"); + widgetPalette = *palette; + + widgetPalette.setBrush(QPalette::Window, QBrush()); + QApplication::setPalette(widgetPalette, "QScrollArea"); + widgetPalette = *palette; + + //Webpages should not use S60 theme colors as they are designed to work + //with themeBackground and do not generally mesh well with web page backgrounds. + QPalette webPalette = *palette; + webPalette.setColor(QPalette::WindowText, Qt::black); + webPalette.setColor(QPalette::Text, Qt::black); + webPalette.setBrush(QPalette::Base, Qt::white); + + QApplication::setPalette(webPalette, "QWebView"); + QApplication::setPalette(webPalette, "QGraphicsWebView"); + + m_webPaletteKey = webPalette.cacheKey(); +} + +QSize QS60StylePrivate::partSize(QS60StyleEnums::SkinParts part, SkinElementFlags flags) +{ + QSize result(20, 20); + switch (part) + { + case QS60StyleEnums::SP_QgnGrafBarProgress: + result.setWidth(pixelMetric(QStyle::PM_ProgressBarChunkWidth)); + break; + case QS60StyleEnums::SP_QgnGrafTabActiveM: + case QS60StyleEnums::SP_QgnGrafTabPassiveM: + case QS60StyleEnums::SP_QgnGrafTabActiveR: + case QS60StyleEnums::SP_QgnGrafTabPassiveR: + case QS60StyleEnums::SP_QgnGrafTabPassiveL: + case QS60StyleEnums::SP_QgnGrafTabActiveL: + //Returned QSize for tabs must not be square, but narrow rectangle with width:height + //ratio of 1:2 for horizontal tab bars (and 2:1 for vertical ones). + result.setWidth(result.height() >> 1); + break; + + case QS60StyleEnums::SP_QgnGrafNsliderEndLeft: + case QS60StyleEnums::SP_QgnGrafNsliderEndRight: + case QS60StyleEnums::SP_QgnGrafNsliderMiddle: + break; + + case QS60StyleEnums::SP_QgnGrafNsliderMarker: + case QS60StyleEnums::SP_QgnGrafNsliderMarkerSelected: + result.scale(pixelMetric(QStyle::PM_SliderLength), + pixelMetric(QStyle::PM_SliderControlThickness), Qt::IgnoreAspectRatio); + break; + + case QS60StyleEnums::SP_QgnGrafBarFrameSideL: + case QS60StyleEnums::SP_QgnGrafBarFrameSideR: + result.setWidth(pixelMetric(PM_FrameCornerWidth)); + break; + + case QS60StyleEnums::SP_QsnCpScrollHandleTopPressed: + case QS60StyleEnums::SP_QsnCpScrollBgBottom: + case QS60StyleEnums::SP_QsnCpScrollBgTop: + case QS60StyleEnums::SP_QsnCpScrollHandleBottom: + case QS60StyleEnums::SP_QsnCpScrollHandleTop: + case QS60StyleEnums::SP_QsnCpScrollHandleBottomPressed: + result.setHeight(pixelMetric(QStyle::PM_ScrollBarExtent)); + result.setWidth(pixelMetric(QStyle::PM_ScrollBarExtent)); + break; + case QS60StyleEnums::SP_QsnCpScrollHandleMiddlePressed: + case QS60StyleEnums::SP_QsnCpScrollBgMiddle: + case QS60StyleEnums::SP_QsnCpScrollHandleMiddle: + result.setHeight(pixelMetric(QStyle::PM_ScrollBarExtent)); + result.setWidth(pixelMetric(QStyle::PM_ScrollBarSliderMin)); + break; + default: + // Generic frame part size gathering. + for (int i = 0; i < frameElementsCount; ++i) + { + switch (m_frameElementsData[i].center - part) { + case 8: /* CornerTl */ + case 7: /* CornerTr */ + case 6: /* CornerBl */ + case 5: /* CornerBr */ + result.setWidth(pixelMetric(PM_FrameCornerWidth)); + // Falltrough intended... + case 4: /* SideT */ + case 3: /* SideB */ + result.setHeight(pixelMetric(PM_FrameCornerHeight)); + break; + case 2: /* SideL */ + case 1: /* SideR */ + result.setWidth(pixelMetric(PM_FrameCornerWidth)); + break; + case 0: /* center */ + default: + break; + } + } + break; + } + if (flags & (SF_PointEast | SF_PointWest)) { + const int temp = result.width(); + result.setWidth(result.height()); + result.setHeight(temp); + } + return result; +} + +bool QS60StylePrivate::canDrawThemeBackground(const QBrush &backgroundBrush, const QWidget *widget) +{ + // Always return true for web pages. + if (widget && m_webPaletteKey == QApplication::palette(widget).cacheKey()) + return true; + //If brush is not changed from style's default values, draw theme graphics. + return (backgroundBrush.color() == Qt::transparent || + backgroundBrush.style() == Qt::NoBrush) ? true : false; +} + +bool QS60StylePrivate::isWidgetPressed(const QWidget *widget) +{ + return (widget && widget == m_pressedWidget); +} + +// Generates 1*1 white pixmap as a placeholder for real texture. +// The actual theme texture is drawn in qt_s60_fill_background(). +QPixmap QS60StylePrivate::placeHolderTexture() +{ + if (!m_placeHolderTexture) { + m_placeHolderTexture = new QPixmap(1,1); + m_placeHolderTexture->fill(Qt::green); + } + return *m_placeHolderTexture; +} + +/*! + \class QS60Style + \brief The QS60Style class provides a look and feel suitable for applications on S60. + \since 4.6 + \ingroup appearance + + \sa QMacStyle, QWindowsStyle, QWindowsXPStyle, QWindowsVistaStyle, QPlastiqueStyle, QCleanlooksStyle, QMotifStyle +*/ + + +/*! + Destroys the style. +*/ +QS60Style::~QS60Style() +{ +} + +/*! + \reimp +*/ +void QS60Style::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const +{ + const QS60StylePrivate::SkinElementFlags flags = (option->state & State_Enabled) ? QS60StylePrivate::SF_StateEnabled : QS60StylePrivate::SF_StateDisabled; + SubControls sub = option->subControls; + + switch (control) { +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *optionSlider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + const bool horizontal = optionSlider->orientation == Qt::Horizontal; + + const QRect scrollBarSlider = subControlRect(control, optionSlider, SC_ScrollBarSlider, widget); + const QRect grooveRect = subControlRect(control, optionSlider, SC_ScrollBarGroove, widget); + + const QS60StylePrivate::SkinElements grooveElement = + horizontal ? QS60StylePrivate::SE_ScrollBarGrooveHorizontal : QS60StylePrivate::SE_ScrollBarGrooveVertical; + QS60StylePrivate::drawSkinElement(grooveElement, painter, grooveRect, flags); + + const SubControls subControls = optionSlider->subControls; + + // select correct slider (horizontal/vertical/pressed) + const bool sliderPressed = ((optionSlider->state & State_Sunken) && (subControls & SC_ScrollBarSlider)); + const QS60StylePrivate::SkinElements handleElement = + horizontal ? + ( sliderPressed ? + QS60StylePrivate::SE_ScrollBarHandlePressedHorizontal : + QS60StylePrivate::SE_ScrollBarHandleHorizontal ) : + ( sliderPressed ? + QS60StylePrivate::SE_ScrollBarHandlePressedVertical : + QS60StylePrivate::SE_ScrollBarHandleVertical); + QS60StylePrivate::drawSkinElement(handleElement, painter, scrollBarSlider, flags); + } + break; +#endif // QT_NO_SCROLLBAR +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *optionSlider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + + const QRect sliderGroove = subControlRect(control, optionSlider, SC_SliderGroove, widget); + const bool horizontal = optionSlider->orientation == Qt::Horizontal; + + //Highlight +/* if (optionSlider->state & State_HasFocus) + drawPrimitive(PE_FrameFocusRect, optionSlider, painter, widget);*/ + + //Groove graphics + if (QS60StylePrivate::hasSliderGrooveGraphic()) { + const QS60StylePrivate::SkinElements grooveElement = horizontal ? + QS60StylePrivate::SE_SliderGrooveHorizontal : + QS60StylePrivate::SE_SliderGrooveVertical; + QS60StylePrivate::drawSkinElement(grooveElement, painter, sliderGroove, flags); + } else { + const QPoint sliderGrooveCenter = sliderGroove.center(); + const bool horizontal = optionSlider->orientation == Qt::Horizontal; + painter->save(); + if (widget) + painter->setPen(widget->palette().windowText().color()); + if (horizontal) + painter->drawLine(0, sliderGrooveCenter.y(), sliderGroove.right(), sliderGrooveCenter.y()); + else + painter->drawLine(sliderGrooveCenter.x(), 0, sliderGrooveCenter.x(), sliderGroove.bottom()); + painter->restore(); + } + + //Handle graphics + const QRect sliderHandle = subControlRect(control, optionSlider, SC_SliderHandle, widget); + QS60StylePrivate::SkinElements handleElement; + if (optionSlider->state & State_Sunken) + handleElement = + horizontal ? QS60StylePrivate::SE_SliderHandleSelectedHorizontal : QS60StylePrivate::SE_SliderHandleSelectedVertical; + else + handleElement = + horizontal ? QS60StylePrivate::SE_SliderHandleHorizontal : QS60StylePrivate::SE_SliderHandleVertical; + QS60StylePrivate::drawSkinElement(handleElement, painter, sliderHandle, flags); + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + const QRect cmbxEditField = subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget); + const QRect cmbxFrame = subControlRect(CC_ComboBox, option, SC_ComboBoxFrame, widget); + const bool direction = cmb->direction == Qt::LeftToRight; + + // Button frame + QStyleOptionFrame buttonOption; + buttonOption.QStyleOption::operator=(*cmb); + const int maxButtonSide = cmbxFrame.width() - cmbxEditField.width(); + const int newTop = cmbxEditField.center().y() - maxButtonSide / 2; + const int topLeftPoint = direction ? + (cmbxEditField.right() + 1) : (cmbxEditField.left() + 1 - maxButtonSide); + const QRect buttonRect(topLeftPoint, newTop, maxButtonSide, maxButtonSide); + buttonOption.rect = buttonRect; + buttonOption.state = cmb->state; + drawPrimitive(PE_PanelButtonCommand, &buttonOption, painter, widget); + + // draw label background - label itself is drawn separately + const QS60StylePrivate::SkinElements skinElement = QS60StylePrivate::SE_FrameLineEdit; + QS60StylePrivate::drawSkinElement(skinElement, painter, cmbxEditField, flags); + + // Draw the combobox arrow + if (sub & SC_ComboBoxArrow) { + // Make rect slightly smaller + buttonOption.rect.adjust(1, 1, -1, -1); + painter->save(); + painter->setPen(option->palette.buttonText().color()); + drawPrimitive(PE_IndicatorSpinDown, &buttonOption, painter, widget); + painter->restore(); + } + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *toolBtn = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + State bflags = toolBtn->state & ~State_Sunken; + + if (bflags & State_AutoRaise) { + if (!(bflags & State_MouseOver) || !(bflags & State_Enabled)) { + bflags &= ~State_Raised; + } + } + State mflags = bflags; + if (toolBtn->state & State_Sunken) { + bflags |= State_Sunken; + mflags |= State_Sunken; + } + + const QRect button(subControlRect(control, toolBtn, SC_ToolButton, widget)); + QRect menuRect = QRect(); + if (toolBtn->subControls & SC_ToolButtonMenu) + menuRect = subControlRect(control, toolBtn, SC_ToolButtonMenu, widget); + + if (toolBtn->subControls & SC_ToolButton) { + QStyleOption tool(0); + tool.palette = toolBtn->palette; + + if (bflags & (State_Sunken | State_On | State_Raised | State_Enabled)) { + tool.rect = button.unite(menuRect); + tool.state = bflags; + drawPrimitive(PE_PanelButtonTool, &tool, painter, widget); + } + if (toolBtn->subControls & SC_ToolButtonMenu) { + tool.rect = menuRect; + tool.state = mflags; + drawPrimitive(PE_IndicatorArrowDown, &tool, painter, widget); + } + } + QStyleOptionToolButton toolButton = *toolBtn; + if (toolBtn->features & QStyleOptionToolButton::Arrow) { + PrimitiveElement pe; + switch (toolBtn->arrowType) { + case Qt::LeftArrow: + pe = PE_IndicatorArrowLeft; + break; + case Qt::RightArrow: + pe = PE_IndicatorArrowRight; + break; + case Qt::UpArrow: + pe = PE_IndicatorArrowUp; + break; + case Qt::DownArrow: + pe = PE_IndicatorArrowDown; + break; + default: + break; } + toolButton.rect = button; + drawPrimitive(pe, &toolButton, painter, widget); + } + + if (toolBtn->text.length() > 0 || + !toolBtn->icon.isNull()) { + const int frameWidth = pixelMetric(PM_DefaultFrameWidth, option, widget); + toolButton.rect = button.adjusted(frameWidth, frameWidth, -frameWidth, -frameWidth); + drawControl(CE_ToolButtonLabel, &toolButton, painter, widget); + } + } + break; +#endif //QT_NO_TOOLBUTTON +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QStyleOptionSpinBox copy = *spinBox; + PrimitiveElement pe; + + if (spinBox->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette spinBoxPal = spinBox->palette; + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + spinBoxPal.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + copy.palette = spinBoxPal; + } + + if (spinBox->activeSubControls == SC_SpinBoxUp && (spinBox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) ? + PE_IndicatorSpinPlus : + PE_IndicatorSpinUp; + + copy.rect = subControlRect(CC_SpinBox, spinBox, SC_SpinBoxUp, widget); + drawPrimitive(PE_PanelButtonBevel, ©, painter, widget); + copy.rect.adjust(1, 1, -1, -1); + drawPrimitive(pe, ©, painter, widget); + } + + if (spinBox->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = spinBox->state; + QPalette spinBoxPal = spinBox->palette; + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + spinBoxPal.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + copy.palette = spinBoxPal; + } + + if (spinBox->activeSubControls == SC_SpinBoxDown && (spinBox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus) ? + PE_IndicatorSpinMinus : + PE_IndicatorSpinDown; + + copy.rect = subControlRect(CC_SpinBox, spinBox, SC_SpinBoxDown, widget); + drawPrimitive(PE_PanelButtonBevel, ©, painter, widget); + copy.rect.adjust(1, 1, -1, -1); + drawPrimitive(pe, ©, painter, widget); + } + } + break; +#endif //QT_NO_SPINBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + // Draw frame + const QRect textRect = subControlRect(CC_GroupBox, option, SC_GroupBoxLabel, widget); + const QRect checkBoxRect = subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget); + if (groupBox->subControls & SC_GroupBoxFrame) { + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget); + drawPrimitive(PE_FrameGroupBox, &frame, painter, widget); + } + + // Draw title + if ((groupBox->subControls & SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + const QColor textColor = groupBox->textColor; + painter->save(); + + if (textColor.isValid()) + painter->setPen(textColor); + int alignment = int(groupBox->textAlignment); + if (!styleHint(SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + + drawItemText(painter, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | Qt::AlignVCenter | alignment, + groupBox->palette, groupBox->state & State_Enabled, groupBox->text, + textColor.isValid() ? QPalette::NoRole : QPalette::WindowText); + painter->restore(); + } + + // Draw checkbox + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + } + break; +#endif //QT_NO_GROUPBOX + default: + QCommonStyle::drawComplexControl(control, option, painter, widget); + } +} + +/*! + \reimp +*/ +void QS60Style::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + Q_D(const QS60Style); + const QS60StylePrivate::SkinElementFlags flags = (option->state & State_Enabled) ? QS60StylePrivate::SF_StateEnabled : QS60StylePrivate::SF_StateDisabled; + switch (element) { + case CE_CheckBox: + case CE_RadioButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt = *btn; + + // Highlight needs to be drawn first, as it goes "underneath" the text and indicator. + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(isRadio ? SE_RadioButtonFocusRect + : SE_CheckBoxFocusRect, btn, widget); + drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + + subopt.palette.setColor(QPalette::Active, QPalette::WindowText, + subopt.palette.highlightedText().color()); + } + + subopt.rect = subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, btn, widget); + drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, + &subopt, painter, widget); + subopt.rect = subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, btn, widget); + + drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, painter, widget); + } + break; + + case CE_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + + drawControl(CE_PushButtonBevel, btn, painter, widget); + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_PushButtonContents, btn, widget); + + drawControl(CE_PushButtonLabel, &subopt, painter, widget); + } + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + const bool isDisabled = !(option->state & State_Enabled); + const bool isFlat = button->features & QStyleOptionButton::Flat; + QS60StyleEnums::SkinParts skinPart; + QS60StylePrivate::SkinElements skinElement; + if (!isDisabled) { + const bool isPressed = (option->state & State_Sunken) || + (option->state & State_On); + if (isFlat) { + skinPart = + isPressed ? QS60StyleEnums::SP_QsnFrButtonTbCenterPressed : QS60StyleEnums::SP_QsnFrButtonTbCenter; + } else { + skinElement = + isPressed ? QS60StylePrivate::SE_ButtonPressed : QS60StylePrivate::SE_ButtonNormal; + } + } else { + if (isFlat) + skinPart =QS60StyleEnums::SP_QsnFrButtonCenterInactive; + else + skinElement = QS60StylePrivate::SE_ButtonInactive; + } + if (isFlat) + QS60StylePrivate::drawSkinPart(skinPart, painter, option->rect, flags); + else + QS60StylePrivate::drawSkinElement(skinElement, painter, option->rect, flags); + } + break; +#ifndef QT_NO_TOOLBUTTON + case CE_ToolButtonLabel: + if (const QStyleOptionToolButton *toolBtn = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + QStyleOptionToolButton optionToolButton = *toolBtn; + + if (!optionToolButton.icon.isNull() && (optionToolButton.state & State_Sunken) + && (optionToolButton.state & State_Enabled)) { + + const QIcon::State state = optionToolButton.state & State_On ? QIcon::On : QIcon::Off; + const QPixmap pm(optionToolButton.icon.pixmap(optionToolButton.rect.size().boundedTo(optionToolButton.iconSize), + QIcon::Normal, state)); + optionToolButton.icon = generatedIconPixmap(QIcon::Selected, pm, &optionToolButton); + } + + QCommonStyle::drawControl(element, &optionToolButton, painter, widget); + } + break; +#endif //QT_NO_TOOLBUTTON +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + QStyleOption optionComboBox = *comboBox; + optionComboBox.palette.setColor(QPalette::Active, QPalette::WindowText, + optionComboBox.palette.text().color() ); + optionComboBox.palette.setColor(QPalette::Inactive, QPalette::WindowText, + optionComboBox.palette.text().color() ); + QRect editRect = subControlRect(CC_ComboBox, comboBox, SC_ComboBoxEditField, widget); + const int frameW = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + + if (!comboBox->currentIcon.isNull()) { + const QIcon::Mode mode = comboBox->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + const QPixmap pixmap = comboBox->currentIcon.pixmap(comboBox->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(comboBox->iconSize.width() + frameW); + iconRect = alignedRect(comboBox->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + if (comboBox->editable) + painter->fillRect(iconRect, optionComboBox.palette.brush(QPalette::Base)); + drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + + if (comboBox->direction == Qt::RightToLeft) + editRect.setRight(editRect.right() - frameW - comboBox->iconSize.width()); + else + editRect.setLeft(comboBox->iconSize.width() + frameW); + } + if (!comboBox->currentText.isEmpty() && !comboBox->editable) { + const Qt::TextElideMode elideMode = (comboBox->direction == Qt::LeftToRight) ? Qt::ElideRight : Qt::ElideLeft; + const QString text = comboBox->fontMetrics.elidedText(comboBox->currentText, elideMode, editRect.width()); + + QCommonStyle::drawItemText(painter, + editRect.adjusted(QS60StylePrivate::pixelMetric(PM_FrameCornerWidth), 0, -1, 0), + visualAlignment(comboBox->direction, Qt::AlignLeft | Qt::AlignVCenter), + comboBox->palette, comboBox->state & State_Enabled, text); + } + } + break; +#endif //QT_NO_COMBOBOX +#ifndef QT_NO_ITEMVIEWS + case CE_ItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + QStyleOptionViewItemV4 voptAdj = *vopt; + painter->save(); + + painter->setClipRect(voptAdj.rect); + const bool isSelected = (vopt->state & State_Selected); + const bool hasFocus = (vopt->state & State_HasFocus); + + bool isScrollBarVisible = false; + int scrollBarWidth = 0; + QList<QScrollBar *> scrollBars = widget->findChildren<QScrollBar *>(); + for (int i = 0; i < scrollBars.size(); ++i) { + QScrollBar *scrollBar = scrollBars.at(i); + if (scrollBar && scrollBar->orientation() == Qt::Vertical) { + isScrollBarVisible = scrollBar->isVisible(); + scrollBarWidth = scrollBar->size().width(); + break; + } + } + + int rightValue = widget ? widget->contentsRect().right() : voptAdj.rect.right(); + + if (isScrollBarVisible) + rightValue -= scrollBarWidth; + + if (voptAdj.rect.right() > rightValue) + voptAdj.rect.setRight(rightValue); + + const QRect iconRect = subElementRect(SE_ItemViewItemDecoration, &voptAdj, widget); + QRect textRect = subElementRect(SE_ItemViewItemText, &voptAdj, widget); + const QAbstractItemView *itemView = qobject_cast<const QAbstractItemView *>(widget); + + const bool singleSelection = itemView && + ((itemView->selectionMode() == QAbstractItemView::SingleSelection || + itemView->selectionMode() == QAbstractItemView::NoSelection)); + const bool selectItems = itemView && (itemView->selectionBehavior() == QAbstractItemView::SelectItems); + + // draw themed background for itemview unless background brush has been defined. + if (vopt->backgroundBrush == Qt::NoBrush) { + if (itemView) { + //With single item selection, use highlight focus as selection indicator. + if (singleSelection && isSelected){ + voptAdj.state = voptAdj.state | State_HasFocus; + if (!hasFocus && selectItems) { + painter->save(); + painter->setOpacity(0.5); + } + } + drawPrimitive(PE_PanelItemViewItem, &voptAdj, painter, widget); + if (singleSelection && isSelected && !hasFocus && selectItems) + painter->restore(); + } + } else { QCommonStyle::drawPrimitive(PE_PanelItemViewItem, &voptAdj, painter, widget);} + + // draw the icon + const QIcon::Mode mode = (voptAdj.state & State_Enabled) ? QIcon::Normal : QIcon::Disabled; + const QIcon::State state = (voptAdj.state & State_Open) ? QIcon::On : QIcon::Off; + voptAdj.icon.paint(painter, iconRect, voptAdj.decorationAlignment, mode, state); + + // Draw selection check mark or checkbox + if (itemView && (!singleSelection || (vopt->features & QStyleOptionViewItemV2::HasCheckIndicator))) { + const QRect selectionRect = subElementRect(SE_ItemViewItemCheckIndicator, &voptAdj, widget); + + QStyleOptionViewItemV4 checkMarkOption(voptAdj); + if (selectionRect.isValid()) + checkMarkOption.rect = selectionRect; + // Draw selection mark. + if (isSelected && selectItems) { + proxy()->drawPrimitive(PE_IndicatorViewItemCheck, &checkMarkOption, painter, widget); + // @todo: this should happen in the rect retrievel i.e. subElementRect() + if (textRect.right() > selectionRect.left()) + textRect.setRight(selectionRect.left()); + } else if (voptAdj.features & QStyleOptionViewItemV2::HasCheckIndicator) { + checkMarkOption.state = checkMarkOption.state & ~State_HasFocus; + + switch (vopt->checkState) { + case Qt::Unchecked: + checkMarkOption.state |= State_Off; + break; + case Qt::PartiallyChecked: + checkMarkOption.state |= State_NoChange; + break; + case Qt::Checked: + checkMarkOption.state |= State_On; + break; + } + drawPrimitive(PE_IndicatorViewItemCheck, &checkMarkOption, painter, widget); + } + } + + // draw the text + if (!voptAdj.text.isEmpty()) { + if (hasFocus) + painter->setPen(voptAdj.palette.highlightedText().color()); + else + painter->setPen(voptAdj.palette.text().color()); + d->viewItemDrawText(painter, &voptAdj, textRect); + } + painter->restore(); + } + break; +#endif // QT_NO_ITEMVIEWS +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + if (const QStyleOptionTabV3 *optionTab = qstyleoption_cast<const QStyleOptionTabV3 *>(option)) { + QStyleOptionTabV3 optionTabAdj = *optionTab; + const bool isSelected = optionTab->state & State_Selected; + const bool directionMirrored = (optionTab->direction == Qt::RightToLeft); + QS60StylePrivate::SkinElements skinElement; + switch (optionTab->shape) { + case QTabBar::TriangularEast: + case QTabBar::RoundedEast: + skinElement = isSelected ? QS60StylePrivate::SE_TabBarTabEastActive: + QS60StylePrivate::SE_TabBarTabEastInactive; + break; + case QTabBar::TriangularSouth: + case QTabBar::RoundedSouth: + skinElement = isSelected ? QS60StylePrivate::SE_TabBarTabSouthActive: + QS60StylePrivate::SE_TabBarTabSouthInactive; + break; + case QTabBar::TriangularWest: + case QTabBar::RoundedWest: + skinElement = isSelected ? QS60StylePrivate::SE_TabBarTabWestActive: + QS60StylePrivate::SE_TabBarTabWestInactive; + break; + case QTabBar::TriangularNorth: + case QTabBar::RoundedNorth: + default: + skinElement = isSelected ? QS60StylePrivate::SE_TabBarTabNorthActive: + QS60StylePrivate::SE_TabBarTabNorthInactive; + break; + } + if (skinElement == QS60StylePrivate::SE_TabBarTabEastInactive || + skinElement == QS60StylePrivate::SE_TabBarTabNorthInactive || + skinElement == QS60StylePrivate::SE_TabBarTabSouthInactive || + skinElement == QS60StylePrivate::SE_TabBarTabWestInactive || + skinElement == QS60StylePrivate::SE_TabBarTabEastActive || + skinElement == QS60StylePrivate::SE_TabBarTabNorthActive || + skinElement == QS60StylePrivate::SE_TabBarTabSouthActive || + skinElement==QS60StylePrivate::SE_TabBarTabWestActive) { + const int borderThickness = + QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + int tabOverlap = pixelMetric(PM_TabBarTabOverlap); + if (tabOverlap > borderThickness) + tabOverlap -= borderThickness; + + const bool usesScrollButtons = + (widget) ? (qobject_cast<const QTabBar*>(widget))->usesScrollButtons() : false; + const int roomForScrollButton = + usesScrollButtons ? QS60StylePrivate::pixelMetric(PM_TabBarScrollButtonWidth) : 0; + + // adjust for overlapping tabs and scrollbuttons, if necessary + if (skinElement == QS60StylePrivate::SE_TabBarTabEastInactive || + skinElement == QS60StylePrivate::SE_TabBarTabEastActive || + skinElement == QS60StylePrivate::SE_TabBarTabWestInactive || + skinElement == QS60StylePrivate::SE_TabBarTabWestActive){ + if (optionTabAdj.position == QStyleOptionTabV3::Beginning) + optionTabAdj.rect.adjust(0, roomForScrollButton, 0, tabOverlap); + else if (optionTabAdj.position == QStyleOptionTabV3::End) + optionTabAdj.rect.adjust(0, 0, 0, tabOverlap); + else + optionTabAdj.rect.adjust(0, 0, 0, tabOverlap); + } else { + if (directionMirrored) { + if (optionTabAdj.position == QStyleOptionTabV3::Beginning) + optionTabAdj.rect.adjust(-tabOverlap, 0, -roomForScrollButton, 0); + else + optionTabAdj.rect.adjust(-tabOverlap, 0, 0, 0); + } else { + if (optionTabAdj.position == QStyleOptionTabV3::Beginning) + optionTabAdj.rect.adjust(roomForScrollButton, 0, tabOverlap, 0); + else + optionTabAdj.rect.adjust(0, 0, tabOverlap, 0); + } + } + } + QS60StylePrivate::drawSkinElement(skinElement, painter, optionTabAdj.rect, flags); + } + break; + case CE_TabBarTabLabel: + if (const QStyleOptionTabV3 *tab = qstyleoption_cast<const QStyleOptionTabV3 *>(option)) { + QStyleOptionTabV3 optionTab = *tab; + QRect tr = optionTab.rect; + const bool directionMirrored = (optionTab.direction == Qt::RightToLeft); + const int borderThickness = + QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + int tabOverlap = pixelMetric(PM_TabBarTabOverlap); + if (tabOverlap > borderThickness) + tabOverlap -= borderThickness; + const bool usesScrollButtons = + (widget) ? (qobject_cast<const QTabBar*>(widget))->usesScrollButtons() : false; + const int roomForScrollButton = + usesScrollButtons ? QS60StylePrivate::pixelMetric(PM_TabBarScrollButtonWidth) : 0; + + switch (tab->shape) { + case QTabBar::TriangularWest: + case QTabBar::RoundedWest: + case QTabBar::TriangularEast: + case QTabBar::RoundedEast: + tr.adjust(0, 0, 0, tabOverlap); + break; + case QTabBar::TriangularSouth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedNorth: + default: + if (directionMirrored) + tr.adjust(-tabOverlap, 0, 0, 0); + else + tr.adjust(0, 0, tabOverlap, 0); + break; + } + painter->save(); + QFont f = painter->font(); + f.setPointSizeF(f.pointSizeF() * KTabFontMul); + painter->setFont(f); + + const bool selected = optionTab.state & State_Selected; + if (selected) + optionTab.palette.setColor(QPalette::Active, QPalette::WindowText, + optionTab.palette.highlightedText().color()); + + const bool verticalTabs = optionTab.shape == QTabBar::RoundedEast + || optionTab.shape == QTabBar::RoundedWest + || optionTab.shape == QTabBar::TriangularEast + || optionTab.shape == QTabBar::TriangularWest; + + //make room for scrollbuttons + if (!verticalTabs) { + if ((tab->position == QStyleOptionTabV3::Beginning && !directionMirrored)) + tr.adjust(roomForScrollButton, 0, 0, 0); + else if ((tab->position == QStyleOptionTabV3::Beginning && directionMirrored)) + tr.adjust(0, 0, -roomForScrollButton, 0); + } else { + if (tab->position == QStyleOptionTabV3::Beginning) + tr.adjust(0, roomForScrollButton, 0, 0); + } + + if (verticalTabs) { + painter->save(); + int newX, newY, newRotation; + if (optionTab.shape == QTabBar::RoundedEast || optionTab.shape == QTabBar::TriangularEast) { + newX = tr.width(); + newY = tr.y(); + newRotation = 90; + } else { + newX = 0; + newY = tr.y() + tr.height(); + newRotation = -90; + } + tr.setRect(0, 0, tr.height(), tr.width()); + QTransform m; + m.translate(newX, newY); + m.rotate(newRotation); + painter->setTransform(m, true); + } + tr.adjust(0, 0, pixelMetric(PM_TabBarTabShiftHorizontal, tab, widget), + pixelMetric(PM_TabBarTabShiftVertical, tab, widget)); + + if (selected) { + tr.setBottom(tr.bottom() - pixelMetric(PM_TabBarTabShiftVertical, tab, widget)); + tr.setRight(tr.right() - pixelMetric(PM_TabBarTabShiftHorizontal, tab, widget)); + } + + int alignment = Qt::AlignCenter | Qt::TextShowMnemonic; + if (!styleHint(SH_UnderlineShortcut, &optionTab, widget)) + alignment |= Qt::TextHideMnemonic; + if (!optionTab.icon.isNull()) { + QSize iconSize = optionTab.iconSize; + if (!iconSize.isValid()) { + const int iconExtent = pixelMetric(PM_TabBarIconSize); + iconSize = QSize(iconExtent, iconExtent); + } + QPixmap tabIcon = optionTab.icon.pixmap(iconSize, + (optionTab.state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + if (tab->text.isEmpty()) + painter->drawPixmap(tr.center().x() - (tabIcon.height() >> 1), + tr.center().y() - (tabIcon.height() >> 1), + tabIcon); + else + painter->drawPixmap(tr.left() + tabOverlap, + tr.center().y() - (tabIcon.height() >> 1), + tabIcon); + tr.setLeft(tr.left() + iconSize.width() + 4); //todo: magic four + } + + QCommonStyle::drawItemText(painter, tr, alignment, optionTab.palette, tab->state & State_Enabled, tab->text, QPalette::WindowText); + if (verticalTabs) + painter->restore(); + + painter->restore(); + } + break; +#endif // QT_NO_TABBAR +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarContents: + if (const QStyleOptionProgressBarV2 *optionProgressBar = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + QRect progressRect = optionProgressBar->rect; + + if (optionProgressBar->minimum == optionProgressBar->maximum && optionProgressBar->minimum == 0) { + // busy indicator + const QS60StylePrivate::SkinElementFlag orientationFlag = optionProgressBar->orientation == Qt::Horizontal ? + QS60StylePrivate::SF_PointNorth : QS60StylePrivate::SF_PointWest; + + QS60StylePrivate::drawSkinPart(QS60StyleEnums::SP_QgnGrafBarWaitAnim, + painter, progressRect, flags | orientationFlag | QS60StylePrivate::SF_Animation ); + } else { + const qreal progressFactor = (optionProgressBar->minimum == optionProgressBar->maximum) ? 1.0 + : (qreal)optionProgressBar->progress / optionProgressBar->maximum; + const int frameWidth = pixelMetric(PM_DefaultFrameWidth, option, widget); + if (optionProgressBar->orientation == Qt::Horizontal) { + progressRect.setWidth(int(progressRect.width() * progressFactor)); + if(optionProgressBar->direction == Qt::RightToLeft) + progressRect.translate(optionProgressBar->rect.width() - progressRect.width(), 0); + progressRect.adjust(frameWidth, 0, -frameWidth, 0); + } else { + progressRect.adjust(0, frameWidth, 0, -frameWidth); + progressRect.setTop(progressRect.bottom() - int(progressRect.height() * progressFactor)); + } + + const QS60StylePrivate::SkinElements skinElement = optionProgressBar->orientation == Qt::Horizontal ? + QS60StylePrivate::SE_ProgressBarIndicatorHorizontal : QS60StylePrivate::SE_ProgressBarIndicatorVertical; + QS60StylePrivate::drawSkinElement(skinElement, painter, progressRect, flags); + } + } + break; + case CE_ProgressBarGroove: + if (const QStyleOptionProgressBarV2 *optionProgressBar = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + const QS60StylePrivate::SkinElements skinElement = optionProgressBar->orientation == Qt::Horizontal ? + QS60StylePrivate::SE_ProgressBarGrooveHorizontal : QS60StylePrivate::SE_ProgressBarGrooveVertical; + QS60StylePrivate::drawSkinElement(skinElement, painter, option->rect, flags); + } + break; + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBarV2 *progressbar = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + QStyleOptionProgressBarV2 optionProgressBar = *progressbar; + QCommonStyle::drawItemText(painter, progressbar->rect, flags | Qt::AlignCenter | Qt::TextSingleLine, optionProgressBar.palette, + progressbar->state & State_Enabled, progressbar->text, QPalette::WindowText); + } + break; +#endif // QT_NO_PROGRESSBAR +#ifndef QT_NO_MENU + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + QStyleOptionMenuItem optionMenuItem = *menuItem; + + bool drawSubMenuIndicator = false; + bool drawSeparator = false; + switch(menuItem->menuItemType) { + case QStyleOptionMenuItem::Separator: + drawSeparator = true; + break; + case QStyleOptionMenuItem::Scroller: + return; // no scrollers in S60 menus + case QStyleOptionMenuItem::SubMenu: + drawSubMenuIndicator = true; + break; + default: + break; + } + if (drawSeparator) { + painter->save(); + painter->setPen(QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnLineColors, 10, 0)); + painter->drawLine(optionMenuItem.rect.topLeft(), optionMenuItem.rect.bottomRight()); + painter->restore(); + return; + } + const bool enabled = optionMenuItem.state & State_Enabled; + const bool checkable = optionMenuItem.checkType != QStyleOptionMenuItem::NotCheckable; + bool ignoreCheckMark = false; + +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox*>(widget)) + ignoreCheckMark = true; //ignore the checkmarks provided by the QComboMenuDelegate +#endif + + uint text_flags = Qt::AlignLeading | Qt::TextShowMnemonic | Qt::TextDontClip + | Qt::TextSingleLine | Qt::AlignVCenter; + if (!styleHint(SH_UnderlineShortcut, menuItem, widget)) + text_flags |= Qt::TextHideMnemonic; + + QRect iconRect = subElementRect(SE_ItemViewItemDecoration, &optionMenuItem, widget); + QRect textRect = subElementRect(SE_ItemViewItemText, &optionMenuItem, widget); + + QStyleOptionMenuItem optionCheckBox; + + //Regardless of checkbox visibility, make room for it, this mirrors native implementation, + //where text and icon placement is static regardless of content of menu item. + optionCheckBox.QStyleOptionMenuItem::operator=(*menuItem); + optionCheckBox.rect.setWidth(pixelMetric(PM_IndicatorWidth)); + optionCheckBox.rect.setHeight(pixelMetric(PM_IndicatorHeight)); + + const int vSpacing = QS60StylePrivate::pixelMetric(PM_LayoutVerticalSpacing); + //The vertical spacing is doubled; it needs one spacing to separate checkbox from + //highlight and then it needs one to separate it whatever is shown after it (text/icon/both). + const int moveByX = optionCheckBox.rect.width() + 2 * vSpacing; + optionCheckBox.rect.moveCenter(QPoint( + optionCheckBox.rect.center().x() + moveByX >> 1, + menuItem->rect.center().y())); + + if (optionMenuItem.direction != Qt::LeftToRight) + optionCheckBox.rect.translate(textRect.width() + iconRect.width(), 0); + + + const bool selected = (option->state & State_Selected) && (option->state & State_Enabled); + if (selected) { + const int spacing = ignoreCheckMark ? (vSpacing + QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth)) : 0; + const int start = optionMenuItem.rect.left() + spacing; + const int end = optionMenuItem.rect.right() - spacing; + //-1 adjustment to avoid highlight being on top of possible separator item + const QRect highlightRect = QRect( + QPoint(start, option->rect.top()), + QPoint(end, option->rect.bottom() - 1)); + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_ListHighlight, painter, highlightRect, flags); + } + + if (checkable && !ignoreCheckMark) + drawPrimitive(PE_IndicatorMenuCheckMark, &optionCheckBox, painter, widget); + + //draw icon and/or checkState + QPixmap pix = menuItem->icon.pixmap(pixelMetric(PM_SmallIconSize), + enabled ? QIcon::Normal : QIcon::Disabled); + const bool itemWithIcon = !pix.isNull(); + if (itemWithIcon) { + drawItemPixmap(painter, iconRect, text_flags, pix); + if (optionMenuItem.direction == Qt::LeftToRight) + textRect.translate(vSpacing, 0); + else + textRect.translate(-vSpacing, 0); + textRect.setWidth(textRect.width() - vSpacing); + } + + //draw indicators + if (drawSubMenuIndicator) { + QStyleOptionMenuItem arrowOptions; + arrowOptions.QStyleOption::operator=(*menuItem); + const int indicatorWidth = (pixelMetric(PM_ListViewIconSize, option, widget) >> 1) + + pixelMetric(PM_LayoutVerticalSpacing, option, widget); + if (optionMenuItem.direction == Qt::LeftToRight) + arrowOptions.rect.setLeft(textRect.right()); + arrowOptions.rect.setWidth(indicatorWidth); + //by default sub menu indicator in S60 points to east,so here icon + // direction is set to north (and south when in RightToLeft) + const QS60StylePrivate::SkinElementFlag arrowDirection = (arrowOptions.direction == Qt::LeftToRight) ? + QS60StylePrivate::SF_PointNorth : QS60StylePrivate::SF_PointSouth; + painter->save(); + painter->setPen(option->palette.windowText().color()); + QS60StylePrivate::drawSkinPart(QS60StyleEnums::SP_QgnIndiSubmenu, painter, arrowOptions.rect, + (flags | QS60StylePrivate::SF_ColorSkinned | arrowDirection)); + painter->restore(); + } + + //draw text + if (!enabled){ + //In s60, if something becomes disabled, it is removed from menu, so no native look-alike available. + optionMenuItem.palette.setColor(QPalette::Disabled, QPalette::Text, QS60StylePrivate::lighterColor( + optionMenuItem.palette.color(QPalette::Disabled, QPalette::Text))); + painter->save(); + painter->setOpacity(0.5); + } + if (selected) + optionMenuItem.palette.setColor( + QPalette::Active, QPalette::Text, optionMenuItem.palette.highlightedText().color()); + + QCommonStyle::drawItemText(painter, textRect, text_flags, + optionMenuItem.palette, enabled, + optionMenuItem.text, QPalette::Text); + + //In Sym^3, native menu items have "lines" between them + if (QS60StylePrivate::isSingleClickUi()) { + int diff = widget->geometry().bottom() - optionMenuItem.rect.bottom(); + if (const QComboBox *cb = qobject_cast<const QComboBox*>(widget)) + diff = cb->view()->geometry().bottom() - optionMenuItem.rect.bottom(); + + // Skip drawing the horizontal line for the last menu item. + if (diff > optionMenuItem.rect.height()) { + const QColor lineColorAlpha = QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnLineColors, 15, 0); + //native platform sets each color byte to same value for "line 16" which just defines alpha for + //menuitem lines; lets use first byte "red". + QColor lineColor = optionMenuItem.palette.text().color(); + if (lineColorAlpha.isValid()) + lineColor.setAlpha(lineColorAlpha.red()); + painter->save(); + painter->setPen(lineColor); + const int horizontalMargin = 2 * QS60StylePrivate::pixelMetric(PM_FrameCornerWidth) - QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + const int lineStartX = optionMenuItem.rect.left() + horizontalMargin; + const int lineEndX = optionMenuItem.rect.right() - horizontalMargin; + painter->drawLine(QPoint(lineStartX, optionMenuItem.rect.bottom()), QPoint(lineEndX, optionMenuItem.rect.bottom())); + painter->restore(); + } + } + if (!enabled) + painter->restore(); + } + break; + case CE_MenuEmptyArea: + break; +#endif //QT_NO_MENU + +#ifndef QT_NO_MENUBAR + case CE_MenuBarEmptyArea: + break; +#endif //QT_NO_MENUBAR + + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + painter->save(); + QPen linePen = QPen(QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnLineColors, 1, header)); + const int penWidth = (header->orientation == Qt::Horizontal) ? + linePen.width() + QS60StylePrivate::pixelMetric(PM_BoldLineWidth) + : linePen.width() + QS60StylePrivate::pixelMetric(PM_ThinLineWidth); + linePen.setWidth(penWidth); + painter->setPen(linePen); + if (header->orientation == Qt::Horizontal){ + painter->drawLine(header->rect.bottomLeft(), header->rect.bottomRight()); + } else { + if ( header->direction == Qt::LeftToRight ) { + painter->drawLine(header->rect.topRight(), header->rect.bottomRight()); + } else { + painter->drawLine(header->rect.topLeft(), header->rect.bottomLeft()); + } + } + painter->restore(); + + //Draw corner button as normal pushButton. + if (qobject_cast<const QAbstractButton *>(widget)) { + //Make cornerButton slightly smaller so that it is not on top of table border graphic. + QStyleOptionHeader subopt = *header; + const int borderTweak = + QS60StylePrivate::pixelMetric(PM_FrameCornerWidth) >> 1; + if (subopt.direction == Qt::LeftToRight) + subopt.rect.adjust(borderTweak, borderTweak, 0, -borderTweak); + else + subopt.rect.adjust(0, borderTweak, -borderTweak, -borderTweak); + drawPrimitive(PE_PanelButtonBevel, &subopt, painter, widget); + } else if ((header->palette.brush(QPalette::Button) != Qt::transparent)) { + //Draw non-themed background. Background for theme is drawn in CE_ShapedFrame + //to get continuous theme graphic across all the header cells. + qDrawShadePanel(painter, header->rect, header->palette, + header->state & (State_Sunken | State_On), penWidth, + &header->palette.brush(QPalette::Button)); + } + } + break; + case CE_HeaderEmptyArea: // no need to draw this + break; + case CE_Header: + if ( const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + drawControl(CE_HeaderSection, header, painter, widget); + QStyleOptionHeader subopt = *header; + subopt.rect = subElementRect(SE_HeaderLabel, header, widget); + if (subopt.rect.isValid()) + drawControl(CE_HeaderLabel, &subopt, painter, widget); + if (header->sortIndicator != QStyleOptionHeader::None) { + subopt.rect = subElementRect(SE_HeaderArrow, option, widget); + drawPrimitive(PE_IndicatorHeaderArrow, &subopt, painter, widget); + } + } + break; +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + if (const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + const QToolBar *tbWidget = qobject_cast<const QToolBar *>(widget); + + //toolbar within a toolbar, skip + if (!tbWidget || (widget && qobject_cast<QToolBar *>(widget->parentWidget()))) + break; + + // Normally in S60 5.0+ there is no background for toolbar, but in some cases with versatile QToolBar, + // it looks a bit strange. So, lets fillRect with Button. + if (!QS60StylePrivate::isToolBarBackground()) { + QList<QAction *> actions = tbWidget->actions(); + bool justToolButtonsInToolBar = true; + for (int i = 0; i < actions.size(); ++i) { + QWidget *childWidget = tbWidget->widgetForAction(actions.at(i)); + const QToolButton *button = qobject_cast<const QToolButton *>(childWidget); + if (!button){ + justToolButtonsInToolBar = false; + } + } + + // Draw frame background + // for vertical toolbars with text only and + // for toolbars with extension buttons and + // for toolbars with widgets in them. + if (!justToolButtonsInToolBar || + (tbWidget && + (tbWidget->orientation() == Qt::Vertical) && + (tbWidget->toolButtonStyle() == Qt::ToolButtonTextOnly))) { + painter->save(); + if (widget) + painter->setBrush(widget->palette().button()); + painter->setOpacity(0.3); + painter->fillRect(toolBar->rect, painter->brush()); + painter->restore(); + } + } else { + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_ToolBar, painter, toolBar->rect, flags); + } + } + break; +#endif //QT_NO_TOOLBAR + case CE_ShapedFrame: + if (const QTextEdit *textEdit = qobject_cast<const QTextEdit *>(widget)) { + const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option); + if (frame && QS60StylePrivate::canDrawThemeBackground(frame->palette.base(), widget)) + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_Editor, painter, option->rect, flags); + else + QCommonStyle::drawControl(element, option, painter, widget); + } else if (qobject_cast<const QTableView *>(widget)) { + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_TableItem, painter, option->rect, flags); + } else if (const QHeaderView *header = qobject_cast<const QHeaderView *>(widget)) { + //QS60style draws header background here instead of in each headersection, to get + //continuous graphic from section to section. + QS60StylePrivate::SkinElementFlags adjustableFlags = flags; + QRect headerRect = option->rect; + if (header->orientation() != Qt::Horizontal) { + //todo: update to horizontal table graphic + adjustableFlags = (adjustableFlags | QS60StylePrivate::SF_PointWest); + } else { + const int frameWidth = QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + if (option->direction == Qt::LeftToRight) + headerRect.adjust(-2 * frameWidth, 0, 0, 0); + else + headerRect.adjust(0, 0, 2 * frameWidth, 0); + } + if (option->palette.brush(QPalette::Button).color() == Qt::transparent) + QS60StylePrivate::drawSkinElement( + QS60StylePrivate::SE_TableHeaderItem, painter, headerRect, adjustableFlags); + + } else if (qobject_cast<const QFrame *>(widget)) { + QCommonStyle::drawControl(element, option, painter, widget); + } + break; + case CE_MenuScroller: + break; + case CE_FocusFrame: { +#ifdef QT_KEYPAD_NAVIGATION + bool editFocus = false; + if (const QFocusFrame *focusFrame = qobject_cast<const QFocusFrame*>(widget)) { + if (focusFrame->widget() && focusFrame->widget()->hasEditFocus()) + editFocus = true; + } + const qreal opacity = editFocus ? 1 : 0.75; // Trial and error factors. Feel free to improve. +#else + const qreal opacity = 0.85; +#endif + // We need to reduce the focus frame size if LayoutSpacing is smaller than FocusFrameMargin + // Otherwise, we would overlay adjacent widgets. + const int frameHeightReduction = + qMin(0, pixelMetric(PM_LayoutVerticalSpacing) + - pixelMetric(PM_FocusFrameVMargin)); + const int frameWidthReduction = + qMin(0, pixelMetric(PM_LayoutHorizontalSpacing) + - pixelMetric(PM_FocusFrameHMargin)); + const int rounding = + qMin(pixelMetric(PM_FocusFrameVMargin), + pixelMetric(PM_LayoutVerticalSpacing)); + const QRect frameRect = + option->rect.adjusted(-frameWidthReduction, -frameHeightReduction, + frameWidthReduction, frameHeightReduction); + QPainterPath framePath; + framePath.addRoundedRect(frameRect, rounding, rounding); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setOpacity(opacity); + painter->fillPath(framePath, option->palette.color(QPalette::Text)); + painter->restore(); + } + break; + case CE_Splitter: + if (option->state & State_Sunken && option->state & State_Enabled && QS60StylePrivate::themePalette()) { + painter->save(); + painter->setOpacity(0.5); + painter->setBrush(QS60StylePrivate::themePalette()->light()); + painter->setRenderHint(QPainter::Antialiasing); + const qreal roundRectRadius = 4 * goldenRatio; + painter->drawRoundedRect(option->rect, roundRectRadius, roundRectRadius); + painter->restore(); + } + break; + default: + QCommonStyle::drawControl(element, option, painter, widget); + } +} + +/*! + \reimp +*/ +void QS60Style::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + const QS60StylePrivate::SkinElementFlags flags = (option->state & State_Enabled) ? QS60StylePrivate::SF_StateEnabled : QS60StylePrivate::SF_StateDisabled; + bool commonStyleDraws = false; + + switch (element) { + case PE_FrameFocusRect: { + //Draw themed highlight to radiobuttons and checkboxes. + //For other widgets skip, unless palette has been modified. In that case, draw with commonstyle. + if (QS60StylePrivate::equalToThemePalette(option->palette.highlight().color(), QPalette::Highlight)) { + if ((qstyleoption_cast<const QStyleOptionFocusRect *>(option) && + (qobject_cast<const QRadioButton *>(widget) || qobject_cast<const QCheckBox *>(widget)))) + QS60StylePrivate::drawSkinElement( + QS60StylePrivate::isWidgetPressed(widget) ? + QS60StylePrivate::SE_ListItemPressed : + QS60StylePrivate::SE_ListHighlight, painter, option->rect, flags); + } else { + commonStyleDraws = true; + } + } + break; +#ifndef QT_NO_LINEEDIT + case PE_PanelLineEdit: + if (const QStyleOptionFrame *lineEdit = qstyleoption_cast<const QStyleOptionFrame *>(option)) { +#ifndef QT_NO_COMBOBOX + if (widget && qobject_cast<const QComboBox *>(widget->parentWidget())) + break; +#endif + if (QS60StylePrivate::canDrawThemeBackground(option->palette.base(), widget)) + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_FrameLineEdit, painter, option->rect, flags); + else + commonStyleDraws = true; + } + break; +#endif // QT_NO_LINEEDIT + case PE_IndicatorCheckBox: { + // Draw checkbox indicator as color skinned graphics. + const QS60StyleEnums::SkinParts skinPart = (option->state & State_On) ? + QS60StyleEnums::SP_QgnIndiCheckboxOn : QS60StyleEnums::SP_QgnIndiCheckboxOff; + painter->save(); + + if (QS60StylePrivate::equalToThemePalette(option->palette.windowText().color(), QPalette::WindowText)) + painter->setPen(option->palette.windowText().color()); + + QS60StylePrivate::drawSkinPart(skinPart, painter, option->rect, flags | QS60StylePrivate::SF_ColorSkinned ); + painter->restore(); + } + break; + case PE_IndicatorViewItemCheck: +#ifndef QT_NO_ITEMVIEWS + if (const QAbstractItemView *itemView = (qobject_cast<const QAbstractItemView *>(widget))) { + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + const bool checkBoxVisible = vopt->features & QStyleOptionViewItemV2::HasCheckIndicator; + const bool singleSelection = itemView->selectionMode() == + QAbstractItemView::SingleSelection || itemView->selectionMode() == QAbstractItemView::NoSelection; + // draw either checkbox at the beginning + if (checkBoxVisible && singleSelection) { + drawPrimitive(PE_IndicatorCheckBox, option, painter, widget); + // ... or normal "tick" selection at the end. + } else if (option->state & State_Selected) { + QRect tickRect = option->rect; + const int frameBorderWidth = QS60StylePrivate::pixelMetric(PM_FrameCornerWidth); + // adjust tickmark rect to exclude frame border + tickRect.adjust(0, -frameBorderWidth, 0, -frameBorderWidth); + QS60StyleEnums::SkinParts skinPart = QS60StyleEnums::SP_QgnIndiMarkedAdd; + QS60StylePrivate::drawSkinPart(skinPart, painter, tickRect, + (flags | QS60StylePrivate::SF_ColorSkinned)); + } + } + } +#endif //QT_NO_ITEMVIEWS + break; + case PE_IndicatorRadioButton: { + QRect buttonRect = option->rect; + //there is empty (a. 33%) space in svg graphics for radiobutton + const qreal reduceWidth = (qreal)buttonRect.width() / 3.0; + const qreal rectWidth = (qreal)option->rect.width() != 0 ? option->rect.width() : 1.0; + // Try to occupy the full area + const qreal scaler = 1 + (reduceWidth/rectWidth); + buttonRect.setWidth((int)((buttonRect.width()-reduceWidth) * scaler)); + buttonRect.setHeight((int)(buttonRect.height() * scaler)); + // move the rect up for half of the new height-gain + const int newY = (buttonRect.bottomRight().y() - option->rect.bottomRight().y()) >> 1 ; + buttonRect.adjust(0, -newY, -1, -newY); + + painter->save(); + const QColor themeColor = QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnTextColors, 6, option); + const QColor buttonTextColor = option->palette.buttonText().color(); + if (themeColor != buttonTextColor) + painter->setPen(buttonTextColor); + else + painter->setPen(themeColor); + + // Draw radiobutton indicator as color skinned graphics. + QS60StyleEnums::SkinParts skinPart = (option->state & State_On) ? + QS60StyleEnums::SP_QgnIndiRadiobuttOn : QS60StyleEnums::SP_QgnIndiRadiobuttOff; + QS60StylePrivate::drawSkinPart(skinPart, painter, buttonRect, + (flags | QS60StylePrivate::SF_ColorSkinned)); + painter->restore(); + } + break; + case PE_PanelButtonCommand: + case PE_PanelButtonTool: + case PE_PanelButtonBevel: + case PE_FrameButtonBevel: + if (QS60StylePrivate::canDrawThemeBackground(option->palette.base(), widget)) { + const bool isPressed = (option->state & State_Sunken) || (option->state & State_On); + QS60StylePrivate::SkinElements skinElement; + if (element == PE_PanelButtonTool) + skinElement = isPressed ? QS60StylePrivate::SE_ToolBarButtonPressed : QS60StylePrivate::SE_ToolBarButton; + else + skinElement = isPressed ? QS60StylePrivate::SE_ButtonPressed : QS60StylePrivate::SE_ButtonNormal; + QS60StylePrivate::drawSkinElement(skinElement, painter, option->rect, flags); + } else { + commonStyleDraws = true; + } + break; +#ifndef QT_NO_TOOLBUTTON + case PE_IndicatorArrowDown: + case PE_IndicatorArrowLeft: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowUp: { + QS60StyleEnums::SkinParts skinPart; + if (element==PE_IndicatorArrowDown) + skinPart = QS60StyleEnums::SP_QgnGrafScrollArrowDown; + else if (element==PE_IndicatorArrowLeft) + skinPart = QS60StyleEnums::SP_QgnGrafScrollArrowLeft; + else if (element==PE_IndicatorArrowRight) + skinPart = QS60StyleEnums::SP_QgnGrafScrollArrowRight; + else if (element==PE_IndicatorArrowUp) + skinPart = QS60StyleEnums::SP_QgnGrafScrollArrowUp; + + QS60StylePrivate::drawSkinPart(skinPart, painter, option->rect, flags); + } + break; +#endif //QT_NO_TOOLBUTTON +#ifndef QT_NO_SPINBOX + case PE_IndicatorSpinDown: + case PE_IndicatorSpinUp: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + if (QS60StylePrivate::canDrawThemeBackground(spinBox->palette.base(), widget)) { + QStyleOptionSpinBox optionSpinBox = *spinBox; + const QS60StyleEnums::SkinParts part = (element == PE_IndicatorSpinUp) ? + QS60StyleEnums::SP_QgnGrafScrollArrowUp : + QS60StyleEnums::SP_QgnGrafScrollArrowDown; + const int iconMargin = QS60StylePrivate::pixelMetric(PM_FrameCornerWidth) >> 1; + optionSpinBox.rect.translate(0, (element == PE_IndicatorSpinDown) ? iconMargin : -iconMargin ); + QS60StylePrivate::drawSkinPart(part, painter, optionSpinBox.rect, flags); + } else { + commonStyleDraws = true; + } + } +#endif //QT_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + if (const QStyleOptionFrame *cmb = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + if (QS60StylePrivate::canDrawThemeBackground( option->palette.base(), widget)) { + // We want to draw down arrow here for comboboxes as well. + QStyleOptionFrame optionsComboBox = *cmb; + const QS60StyleEnums::SkinParts part = QS60StyleEnums::SP_QgnGrafScrollArrowDown; + const int iconMargin = QS60StylePrivate::pixelMetric(PM_FrameCornerWidth) >> 1; + optionsComboBox.rect.translate(0, (element == PE_IndicatorSpinDown) ? iconMargin : -iconMargin ); + QS60StylePrivate::drawSkinPart(part, painter, optionsComboBox.rect, flags); + } else { + commonStyleDraws = true; + } + } +#endif //QT_NO_COMBOBOX + break; + case PE_IndicatorSpinMinus: + case PE_IndicatorSpinPlus: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QStyleOptionSpinBox optionSpinBox = *spinBox; + QCommonStyle::drawPrimitive(element, &optionSpinBox, painter, widget); + } +#ifndef QT_NO_COMBOBOX + else if (const QStyleOptionFrame *cmb = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + // We want to draw down arrow here for comboboxes as well. + QStyleOptionFrame comboBox = *cmb; + const int frameWidth = QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + comboBox.rect.adjust(0, frameWidth, 0, -frameWidth); + QCommonStyle::drawPrimitive(element, &comboBox, painter, widget); + } +#endif //QT_NO_COMBOBOX + break; + case PE_Widget: + if (QS60StylePrivate::drawsOwnThemeBackground(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<const QComboBoxListView *>(widget) +#endif //QT_NO_COMBOBOX +#ifndef QT_NO_MENU + || qobject_cast<const QMenu *> (widget) +#endif //QT_NO_MENU + ) { + //Need extra check since dialogs have their own theme background + if (QS60StylePrivate::canDrawThemeBackground(option->palette.base(), widget) + && QS60StylePrivate::equalToThemePalette(option->palette.window().texture().cacheKey(), QPalette::Window)) { + const bool comboMenu = qobject_cast<const QComboBoxListView *>(widget); + // Add margin area to the background, to avoid background being cut for first and last item. + const int verticalMenuAdjustment = comboMenu ? QS60StylePrivate::pixelMetric(PM_MenuVMargin) : 0; + const QRect adjustedMenuRect = option->rect.adjusted(0, -verticalMenuAdjustment, 0, verticalMenuAdjustment); + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_PopupBackground, painter, adjustedMenuRect, flags); + } else { + commonStyleDraws = true; + } + } + break; + case PE_FrameWindow: + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *tabFrame = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) { + QStyleOptionTabWidgetFrame optionTabFrame = *tabFrame; + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_PanelBackground, painter, optionTabFrame.rect, flags); + } + break; + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + if (header->sortIndicator & QStyleOptionHeader::SortUp) + drawPrimitive(PE_IndicatorArrowUp, header, painter, widget); + else if (header->sortIndicator & QStyleOptionHeader::SortDown) + drawPrimitive(PE_IndicatorArrowDown, header, painter, widget); + } // QStyleOptionHeader::None is not drawn => not needed + break; +#ifndef QT_NO_GROUPBOX + case PE_FrameGroupBox: + if (const QStyleOptionFrameV2 *frame = qstyleoption_cast<const QStyleOptionFrameV2 *>(option)) + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_SettingsList, painter, frame->rect, flags); + break; +#endif //QT_NO_GROUPBOX + + // Qt3 primitives are not supported + case PE_Q3CheckListController: + case PE_Q3CheckListExclusiveIndicator: + case PE_Q3CheckListIndicator: + case PE_Q3DockWindowSeparator: + case PE_Q3Separator: + Q_ASSERT(false); + break; + case PE_Frame: + break; +#ifndef QT_NO_ITEMVIEWS + case PE_PanelItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + const bool isSelected = (vopt->state & State_Selected); + const bool hasFocus = (vopt->state & State_HasFocus); + const bool isPressed = QS60StylePrivate::isWidgetPressed(widget); + + if (QS60StylePrivate::equalToThemePalette(option->palette.highlight().color(), QPalette::Highlight)) { + QRect highlightRect = vopt->rect.adjusted(1,1,-1,-1); + const QAbstractItemView *itemView = qobject_cast<const QAbstractItemView *>(widget); + QAbstractItemView::SelectionBehavior selectionBehavior = + itemView ? itemView->selectionBehavior() : QAbstractItemView::SelectItems; + // Set the draw area for highlights (focus, select rect or pressed rect) + if (hasFocus || isPressed) { + if (selectionBehavior != QAbstractItemView::SelectItems) { + // set highlight rect so that it is continuous from cell to cell, yet sligthly + // smaller than cell rect + int xBeginning = 0, yBeginning = 0, xEnd = 0, yEnd = 0; + if (selectionBehavior == QAbstractItemView::SelectRows) { + yBeginning = 1; yEnd = -1; + if (vopt->viewItemPosition == QStyleOptionViewItemV4::Beginning) + xBeginning = 1; + else if (vopt->viewItemPosition == QStyleOptionViewItemV4::End) + xEnd = -1; + } else if (selectionBehavior == QAbstractItemView::SelectColumns) { + xBeginning = 1; xEnd = -1; + if (vopt->viewItemPosition == QStyleOptionViewItemV4::Beginning) + yBeginning = 1; + else if (vopt->viewItemPosition == QStyleOptionViewItemV4::End) + yEnd = -1; + } + highlightRect = option->rect.adjusted(xBeginning, yBeginning, xEnd, yEnd); + } + } + bool tableView = false; + if (itemView && qobject_cast<const QTableView *>(widget)) + tableView = true; + + QS60StylePrivate::SkinElements element; + bool themeGraphicDefined = false; + QRect elementRect = option->rect; + + //draw item is drawn as pressed, if it already has focus. + if (isPressed && hasFocus) { + themeGraphicDefined = true; + element = tableView ? QS60StylePrivate::SE_TableItemPressed : QS60StylePrivate::SE_ListItemPressed; + } else if (hasFocus || (isSelected && selectionBehavior != QAbstractItemView::SelectItems)) { + element = QS60StylePrivate::SE_ListHighlight; + elementRect = highlightRect; + themeGraphicDefined = true; + } + if (themeGraphicDefined) + QS60StylePrivate::drawSkinElement(element, painter, elementRect, flags); + } else { + QCommonStyle::drawPrimitive(element, option, painter, widget); + } + } + break; +#endif //QT_NO_ITEMVIEWS + + case PE_IndicatorMenuCheckMark: + if (const QStyleOptionMenuItem *checkBox = qstyleoption_cast<const QStyleOptionMenuItem *>(option)){ + QStyleOptionMenuItem optionCheckBox = *checkBox; + if (optionCheckBox.checked) + optionCheckBox.state = (optionCheckBox.state | State_On); + drawPrimitive(PE_IndicatorCheckBox, &optionCheckBox, painter, widget); + } + break; +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarHandle: + // no toolbar handles in S60/AVKON UI + case PE_IndicatorToolBarSeparator: + // no separators in S60/AVKON UI + break; +#endif //QT_NO_TOOLBAR + + case PE_PanelMenuBar: + case PE_FrameMenu: + break; //disable frame in menu + + case PE_IndicatorBranch: +#if defined(Q_WS_S60) + // 3.1 AVKON UI does not have tree view component, use common style for drawing there + if (QSysInfo::s60Version() == QSysInfo::SV_S60_3_1) { +#else + if (true) { +#endif + QCommonStyle::drawPrimitive(element, option, painter, widget); + } else { + if (const QStyleOptionViewItemV2 *vopt = qstyleoption_cast<const QStyleOptionViewItemV2 *>(option)) { + const bool rightLine = option->state & State_Item; + const bool downLine = option->state & State_Sibling; + const bool upLine = option->state & (State_Open | State_Children | State_Item | State_Sibling); + QS60StylePrivate::SkinElementFlags adjustedFlags = flags; + + QS60StyleEnums::SkinParts skinPart; + bool drawSkinPart = false; + if (rightLine && downLine && upLine) { + skinPart = QS60StyleEnums::SP_QgnIndiHlLineBranch; + drawSkinPart = true; + } else if (rightLine && upLine) { + skinPart = QS60StyleEnums::SP_QgnIndiHlLineEnd; + drawSkinPart = true; + } else if (upLine && downLine) { + skinPart = QS60StyleEnums::SP_QgnIndiHlLineStraight; + drawSkinPart = true; + } + + if (option->direction == Qt::RightToLeft) + adjustedFlags |= QS60StylePrivate::SF_Mirrored_X_Axis; + + if (drawSkinPart) + QS60StylePrivate::drawSkinPart(skinPart, painter, option->rect, adjustedFlags); + + if (option->state & State_Children) { + QS60StyleEnums::SkinParts skinPart = + (option->state & State_Open) ? QS60StyleEnums::SP_QgnIndiHlColSuper : QS60StyleEnums::SP_QgnIndiHlExpSuper; + const QRect selectionRect = subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget); + const int minDimension = qMin(option->rect.width(), option->rect.height()); + const int magicTweak = (option->direction == Qt::RightToLeft) ? -3 : 3; //@todo: magic + //The branch indicator icon in S60 is supposed to be superimposed on top of branch lines. + QRect iconRect(QPoint(option->rect.left() + magicTweak, selectionRect.top() + 1), QSize(minDimension, minDimension)); + if (!QS60StylePrivate::isTouchSupported()) + iconRect.translate(0, -4); //@todo: magic + QS60StylePrivate::drawSkinPart(skinPart, painter, iconRect, adjustedFlags); + } + } + } + break; + case PE_PanelItemViewRow: // ### Qt 5: remove +#ifndef QT_NO_ITEMVIEWS + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + if (!QS60StylePrivate::equalToThemePalette(vopt->palette.base().texture().cacheKey(), QPalette::Base)) { + //QPalette::Base has been changed, let commonstyle draw the item + commonStyleDraws = true; + } else { + QPalette::ColorGroup cg = vopt->state & State_Enabled ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & State_Active)) + cg = QPalette::Inactive; + if (vopt->features & QStyleOptionViewItemV2::Alternate) + painter->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::AlternateBase)); + //apart from alternate base, no background for list item is drawn for S60Style + } + } +#endif + break; + case PE_PanelScrollAreaCorner: + break; + case PE_IndicatorItemViewItemDrop: + if (QS60StylePrivate::isTouchSupported()) + QS60StylePrivate::drawSkinElement(QS60StylePrivate::SE_DropArea, painter, option->rect, flags); + else + commonStyleDraws = true; + break; + // todo: items are below with #ifdefs "just in case". in final version, remove all non-required cases + case PE_FrameLineEdit: + case PE_IndicatorDockWidgetResizeHandle: + case PE_PanelTipLabel: + +#ifndef QT_NO_TABBAR + case PE_IndicatorTabTear: // No tab tear in S60 +#endif // QT_NO_TABBAR + case PE_FrameDefaultButton: +#ifndef QT_NO_DOCKWIDGET + case PE_FrameDockWidget: +#endif //QT_NO_DOCKWIDGET +#ifndef QT_NO_PROGRESSBAR + case PE_IndicatorProgressChunk: +#endif //QT_NO_PROGRESSBAR +#ifndef QT_NO_TOOLBAR + case PE_PanelToolBar: +#endif //QT_NO_TOOLBAR +#ifndef QT_NO_COLUMNVIEW + case PE_IndicatorColumnViewArrow: +#endif //QT_NO_COLUMNVIEW + case PE_FrameTabBarBase: // since tabs are in S60 always in navipane, let's use common style for tab base in Qt. + default: + commonStyleDraws = true; + } + if (commonStyleDraws) { + QCommonStyle::drawPrimitive(element, option, painter, widget); + } +} + +/*! \reimp */ +int QS60Style::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + int metricValue = QS60StylePrivate::pixelMetric(metric); + if (metricValue == KNotFound) + metricValue = QCommonStyle::pixelMetric(metric, option, widget); + + // Menu scrollers should be set to zero height for combobox popups + if (metric == PM_MenuScrollerHeight && !qobject_cast<const QMenu *>(widget)) + metricValue = 0; + + //if layout direction is mirrored, switch left and right border margins + if (option && option->direction == Qt::RightToLeft) { + if (metric == PM_LayoutLeftMargin) + metricValue = QS60StylePrivate::pixelMetric(PM_LayoutRightMargin); + else if (metric == PM_LayoutRightMargin) + metricValue = QS60StylePrivate::pixelMetric(PM_LayoutLeftMargin); + } + + if (widget && (metric == PM_LayoutTopMargin || metric == PM_LayoutLeftMargin || metric == PM_LayoutRightMargin)) + if (widget->windowType() == Qt::Dialog) + //double the layout margins (except bottom) for dialogs, it is very close to real value + //without having to define custom pixel metric + metricValue *= 2; + +#if defined(Q_WS_S60) + if (metric == PM_TabBarTabOverlap && (QSysInfo::s60Version() > QSysInfo::SV_S60_5_2)) + metricValue = 0; +#endif + + return metricValue; +} + +/*! \reimp */ +QSize QS60Style::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *widget) const +{ + QSize sz(csz); + switch (ct) { + case CT_ToolButton: + sz = QCommonStyle::sizeFromContents( ct, opt, csz, widget); + //FIXME properly - style should calculate the location of border frame-part + sz += QSize(2 * pixelMetric(PM_ButtonMargin), 2 * pixelMetric(PM_ButtonMargin)); + if (const QStyleOptionToolButton *toolBtn = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) + if (toolBtn->subControls & SC_ToolButtonMenu) + sz += QSize(pixelMetric(PM_MenuButtonIndicator), 0); + + //Make toolbuttons in toolbar stretch the whole screen area + if (widget && qobject_cast<const QToolBar *>(widget->parentWidget())) { + const QToolBar *tb = qobject_cast<const QToolBar *>(widget->parentWidget()); + const bool parentCanGrowHorizontally = !(tb->sizePolicy().horizontalPolicy() == QSizePolicy::Fixed || + tb->sizePolicy().horizontalPolicy() == QSizePolicy::Maximum) && tb->orientation() == Qt::Horizontal; + + if (parentCanGrowHorizontally) { + int buttons = 0; + //Make the auto-stretch to happen only for horizontal orientation + if (tb && tb->orientation() == Qt::Horizontal) { + QList<QAction*> actionList = tb->actions(); + for (int i = 0; i < actionList.count(); i++) { + buttons++; + } + } + + if (widget->parentWidget() && buttons > 0) { + QWidget *w = const_cast<QWidget *>(widget); + int toolBarMaxWidth = 0; + int totalMargin = 0; + while (w) { + //honor fixed width parents + if (w->maximumWidth() == w->minimumWidth()) + toolBarMaxWidth = qMax(toolBarMaxWidth, w->maximumWidth()); + if (w->layout() && w->windowType() == Qt::Widget) { + totalMargin += w->layout()->contentsMargins().left() + + w->layout()->contentsMargins().right(); + } + w = w->parentWidget(); + } + totalMargin += 2 * pixelMetric(QStyle::PM_ToolBarFrameWidth); + + if (toolBarMaxWidth == 0) + toolBarMaxWidth = + QApplication::desktop()->availableGeometry(widget->parentWidget()).width(); + //Reduce the margins, toolbar frame, item spacing and internal margin from available area + toolBarMaxWidth -= totalMargin; + + //ensure that buttons are side-by-side and not on top of each other + const int toolButtonWidth = (toolBarMaxWidth / buttons) + - pixelMetric(QStyle::PM_ToolBarItemSpacing) + - pixelMetric(QStyle::PM_ToolBarItemMargin) + //toolbar frame needs to be reduced again, since QToolBarLayout adds it for each toolbar action + - 2 * pixelMetric(QStyle::PM_ToolBarFrameWidth) - 1; + sz.setWidth(qMax(toolButtonWidth, sz.width())); + } + } + } + break; + case CT_PushButton: + sz = QCommonStyle::sizeFromContents( ct, opt, csz, widget); + //FIXME properly - style should calculate the location of border frame-part + if (const QAbstractButton *buttonWidget = (qobject_cast<const QAbstractButton *>(widget))) { + if (buttonWidget->isCheckable()) + sz += QSize(pixelMetric(PM_IndicatorWidth) + pixelMetric(PM_CheckBoxLabelSpacing), 0); + const int iconHeight = (!buttonWidget->icon().isNull()) ? buttonWidget->iconSize().height() : 0; + const int textHeight = (buttonWidget->text().length() > 0) ? + buttonWidget->fontMetrics().size(Qt::TextSingleLine, buttonWidget->text()).height() : opt->fontMetrics.height(); + const int decoratorHeight = (buttonWidget->isCheckable()) ? pixelMetric(PM_IndicatorHeight) : 0; + + const int contentHeight = + qMax(qMax(iconHeight, decoratorHeight) + pixelMetric(PM_ButtonMargin), + textHeight + 2*pixelMetric(PM_ButtonMargin)); + sz.setHeight(qMax(sz.height(), contentHeight)); + sz += QSize(2 * pixelMetric(PM_ButtonMargin), 0); + } + break; + case CT_LineEdit: + if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(opt)) + sz += QSize(2 * f->lineWidth, 4 * f->lineWidth); + break; + case CT_TabBarTab: { + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + // Adjust beginning tabbar item size, if scrollbuttons are used. This is to ensure that the + // tabbar item content fits, since scrollbuttons are making beginning tabbar item smaller. + int scrollButtonSize = 0; + if (const QTabBar *tabBar = qobject_cast<const QTabBar *>(widget)) + scrollButtonSize = tabBar->usesScrollButtons() ? pixelMetric(PM_TabBarScrollButtonWidth) : 0; + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + const bool verticalTabs = tab->shape == QTabBar::RoundedEast + || tab->shape == QTabBar::RoundedWest + || tab->shape == QTabBar::TriangularEast + || tab->shape == QTabBar::TriangularWest; + if (tab->position == QStyleOptionTab::Beginning) + sz += QSize(verticalTabs ? 0 : scrollButtonSize, !verticalTabs ? 0 : scrollButtonSize); + } + } + break; + case CT_MenuItem: + case CT_ItemViewItem: + if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) { + sz = QSize(menuItem->rect.width() - 2 * pixelMetric(PM_MenuHMargin) - 2 * QS60StylePrivate::pixelMetric(PM_FrameCornerWidth), 1); + break; + } + } + sz = QCommonStyle::sizeFromContents( ct, opt, csz, widget); + if (QS60StylePrivate::isTouchSupported()) { + //Make itemview easier to use in touch devices + sz.setHeight(sz.height() + 2 * pixelMetric(PM_FocusFrameVMargin)); + //QCommonStyle does not adjust height with horizontal margin, it only adjusts width + if (ct == CT_MenuItem) + sz.setHeight(sz.height() - 8); //QCommonstyle adds 8 to height that this style handles through PM values + } + break; +#ifndef QT_NO_COMBOBOX + case CT_ComboBox: { + // Fixing Ui design issues with too wide QComboBoxes and greedy SizeHints + // Make sure, that the combobox stays within the screen. + const QSize desktopContentSize = QApplication::desktop()->availableGeometry().size() + - QSize(pixelMetric(PM_LayoutLeftMargin) + pixelMetric(PM_LayoutRightMargin), 0); + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget). + boundedTo(desktopContentSize); + } + break; +#endif + default: + sz = QCommonStyle::sizeFromContents( ct, opt, csz, widget); + break; + } + if (!sz.isValid()) + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + return sz; +} + +/*! \reimp */ +int QS60Style::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *hret) const +{ + int retValue = 0; + switch (sh) { + case SH_RequestSoftwareInputPanel: + if (QS60StylePrivate::isSingleClickUi()) + retValue = RSIP_OnMouseClick; + else + retValue = RSIP_OnMouseClickAndAlreadyFocused; + break; + case SH_ComboBox_Popup: + retValue = true; + break; + case SH_Table_GridLineColor: + retValue = int(QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnLineColors, 2, 0).rgba()); + break; + case SH_GroupBox_TextLabelColor: + retValue = int(QS60StylePrivate::s60Color(QS60StyleEnums::CL_QsnTextColors, 6, 0).rgba()); + break; + case SH_ScrollBar_ScrollWhenPointerLeavesControl: + retValue = true; + break; + case SH_Slider_SnapToValue: + retValue = true; + break; + case SH_Slider_StopMouseOverSlider: + retValue = true; + break; + case SH_LineEdit_PasswordCharacter: + retValue = '*'; + break; + case SH_ComboBox_PopupFrameStyle: + retValue = QFrame::NoFrame | QFrame::Plain; + break; + case SH_Dial_BackgroundRole: + retValue = QPalette::Base; + break; + case SH_ItemView_ActivateItemOnSingleClick: { + if (QS60StylePrivate::isSingleClickUi()) + retValue = true; + else if (opt && opt->state & QStyle::State_Selected) + retValue = true; + break; + } + case SH_ProgressDialog_TextLabelAlignment: + retValue = (QApplication::layoutDirection() == Qt::LeftToRight) ? + Qt::AlignLeft : + Qt::AlignRight; + break; + case SH_Menu_SubMenuPopupDelay: + retValue = 300; + break; + case SH_Menu_Scrollable: + retValue = true; + break; + case SH_Menu_SelectionWrap: + retValue = true; + break; + case SH_Menu_MouseTracking: + retValue = true; + break; + case SH_ItemView_ShowDecorationSelected: + retValue = true; + break; + case SH_ToolBar_Movable: + retValue = false; + break; + case SH_BlinkCursorWhenTextSelected: + retValue = true; + break; + case SH_UnderlineShortcut: + retValue = 0; + break; + case SH_FormLayoutWrapPolicy: + retValue = QFormLayout::WrapLongRows; + break; + case SH_ScrollBar_ContextMenu: + retValue = false; + break; + default: + retValue = QCommonStyle::styleHint(sh, opt, widget, hret); + break; + } + return retValue; +} + +/*! \reimp */ +QRect QS60Style::subControlRect(ComplexControl control, const QStyleOptionComplex *option, SubControl scontrol, const QWidget *widget) const +{ + QRect ret; + switch (control) { +#ifndef QT_NO_SCROLLBAR + // This implementation of subControlRect(CC_ScrollBar..) basically just removes the SC_ScrollBarSubLine and SC_ScrollBarAddLine + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbarOption = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + const QRect scrollBarRect = scrollbarOption->rect; + const bool isHorizontal = scrollbarOption->orientation == Qt::Horizontal; + const int maxlen = isHorizontal ? scrollBarRect.width() : scrollBarRect.height(); + int sliderlen; + + // calculate slider length + if (scrollbarOption->maximum != scrollbarOption->minimum) { + const uint range = scrollbarOption->maximum - scrollbarOption->minimum; + sliderlen = (qint64(scrollbarOption->pageStep) * maxlen) / (range + scrollbarOption->pageStep); + + const int slidermin = pixelMetric(PM_ScrollBarSliderMin, scrollbarOption, widget); + if (sliderlen < slidermin || range > (INT_MAX >> 1)) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + + const int sliderstart = sliderPositionFromValue(scrollbarOption->minimum, + scrollbarOption->maximum, + scrollbarOption->sliderPosition, + maxlen - sliderlen, + scrollbarOption->upsideDown); + + switch (scontrol) { + case SC_ScrollBarSubPage: // between top/left button and slider + if (isHorizontal) + ret.setRect(0, 0, sliderstart, scrollBarRect.height()); + else + ret.setRect(0, 0, scrollBarRect.width(), sliderstart); + break; + case SC_ScrollBarAddPage: { // between bottom/right button and slider + const int addPageLength = sliderstart + sliderlen; + if (isHorizontal) + ret = scrollBarRect.adjusted(addPageLength, 0, 0, 0); + else + ret = scrollBarRect.adjusted(0, addPageLength, 0, 0); + } + break; + case SC_ScrollBarGroove: + ret = scrollBarRect; + break; + case SC_ScrollBarSlider: + if (scrollbarOption->orientation == Qt::Horizontal) + ret.setRect(sliderstart, 0, sliderlen, scrollBarRect.height()); + else + ret.setRect(0, sliderstart, scrollBarRect.width(), sliderlen); + break; + case SC_ScrollBarSubLine: // top/left button + case SC_ScrollBarAddLine: // bottom/right button + default: + break; + } + ret = visualRect(scrollbarOption->direction, scrollBarRect, ret); + } + break; +#endif // QT_NO_SCROLLBAR + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + const int frameThickness = spinbox->frame ? pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0; + const int buttonMargin = spinbox->frame ? 2 : 0; + const int buttonContentWidth = QS60StylePrivate::pixelMetric(PM_ButtonIconSize) + 2 * buttonMargin; + // Spinbox buttons should be no larger than one fourth of total width. + // Thus, side-by-side buttons would take half of the total width. + const int maxSize = qMax(spinbox->rect.width() / 4, buttonContentWidth); + QSize buttonSize; + buttonSize.setHeight(qMin(maxSize, qMax(8, spinbox->rect.height() - frameThickness))); + //width should at least be equal to height + buttonSize.setWidth(qMax(buttonSize.height(), buttonContentWidth)); + buttonSize = buttonSize.expandedTo(QApplication::globalStrut()); + + // Normally spinbuttons should be side-by-side, but if spinbox grows very big + // and spinbuttons reach their maximum size, they can be deployed one top of the other. + const bool sideBySide = (buttonSize.height() * 2 < spinbox->rect.height()) ? false : true; + const int y = frameThickness + spinbox->rect.y() + + (spinbox->rect.height() - (sideBySide ? 1 : 2) * buttonSize.height()) / 2; + const int x = spinbox->rect.x() + + spinbox->rect.width() - frameThickness - (sideBySide ? 2 : 1) * buttonSize.width(); + + switch (scontrol) { + case SC_SpinBoxUp: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + ret = QRect(x, y, buttonSize.width(), buttonSize.height()); + break; + case SC_SpinBoxDown: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + return QRect(); + ret = QRect(x + (sideBySide ? buttonSize.width() : 0), + y + (sideBySide ? 0 : buttonSize.height()), + buttonSize.width(), buttonSize.height()); + break; + case SC_SpinBoxEditField: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) + ret = QRect( + frameThickness, + frameThickness, + spinbox->rect.width() - 2 * frameThickness, + spinbox->rect.height() - 2 * frameThickness); + else + ret = QRect( + frameThickness, + frameThickness, + x - frameThickness, + spinbox->rect.height() - 2 * frameThickness); + break; + case SC_SpinBoxFrame: + ret = spinbox->rect; + break; + default: + break; + } + ret = visualRect(spinbox->direction, spinbox->rect, ret); + } + break; + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + ret = cmb->rect; + const int width = cmb->rect.width(); + const int height = cmb->rect.height(); + const int buttonMargin = cmb->frame ? 2 : 0; + // lets use spinbox frame here as well, as no combobox specific value available. + const int frameThickness = cmb->frame ? pixelMetric(PM_SpinBoxFrameWidth, cmb, widget) : 0; + const int buttonMinSize = QS60StylePrivate::pixelMetric(PM_ButtonIconSize) + 2 * buttonMargin; + QSize buttonSize; + //allow button to grow to one fourth of the frame height, if the frame is really tall + buttonSize.setHeight(qMin(height, qMax(width / 4, buttonMinSize))); + buttonSize.setWidth(buttonSize.height()); + buttonSize = buttonSize.expandedTo(QApplication::globalStrut()); + switch (scontrol) { + case SC_ComboBoxArrow: { + const int xposMod = cmb->rect.x() + width - buttonMargin - buttonSize.width(); + const int ypos = cmb->rect.y(); + ret.setRect(xposMod, ypos + buttonMargin, buttonSize.width(), height - 2 * buttonMargin); + } + break; + case SC_ComboBoxEditField: { + ret = QRect(0, 0, cmb->rect.x() + width - buttonSize.width(), height); + } + break; + case SC_ComboBoxListBoxPopup: { + ret = QApplication::desktop()->availableGeometry(); + } + break; + default: + break; + } + ret = visualRect(cmb->direction, cmb->rect, ret); + } + break; + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + ret = QCommonStyle::subControlRect(control, option, scontrol, widget); + switch (scontrol) { + case SC_GroupBoxCheckBox: //fallthrough + case SC_GroupBoxLabel: { + //slightly indent text and boxes, so that dialog border does not mess with them. + const int horizontalSpacing = + QS60StylePrivate::pixelMetric(PM_LayoutHorizontalSpacing); + ret.adjust(2, horizontalSpacing - 3, 0, 0); + } + break; + case SC_GroupBoxFrame: { + const QRect textBox = subControlRect(control, option, SC_GroupBoxLabel, widget); + const int tbHeight = textBox.height(); + ret.translate(0, -ret.y()); + // include title to within the groupBox frame + ret.setHeight(ret.height() + tbHeight); + if (widget && ret.bottom() > widget->rect().bottom()) + ret.setBottom(widget->rect().bottom()); + } + break; + default: + break; + } + } + break; + case CC_ToolButton: + if (const QStyleOptionToolButton *toolButton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + const int indicatorRect = pixelMetric(PM_MenuButtonIndicator) + 2 * pixelMetric(PM_ButtonMargin); + const int border = pixelMetric(PM_ButtonMargin) + pixelMetric(PM_DefaultFrameWidth); + ret = toolButton->rect; + const bool popup = (toolButton->features & + (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay)) + == QStyleOptionToolButton::MenuButtonPopup; + switch (scontrol) { + case SC_ToolButton: + if (popup) + ret.adjust(0, 0, -indicatorRect, 0); + break; + case SC_ToolButtonMenu: + if (popup) + ret.adjust(ret.width() - indicatorRect, border, -pixelMetric(PM_ButtonMargin), -border); + break; + default: + break; + } + ret = visualRect(toolButton->direction, toolButton->rect, ret); + } + break; + default: + ret = QCommonStyle::subControlRect(control, option, scontrol, widget); + } + return ret; +} + +/*! + \reimp +*/ +QRect QS60Style::subElementRect(SubElement element, const QStyleOption *opt, const QWidget *widget) const +{ + QRect ret; + switch (element) { + case SE_RadioButtonFocusRect: + ret = opt->rect; + break; + case SE_LineEditContents: { + // in S60 the input text box doesn't start from line Edit's TL, but + // a bit indented (8 pixels). + const int KLineEditDefaultIndention = 8; + ret = visualRect( + opt->direction, opt->rect, opt->rect.adjusted(KLineEditDefaultIndention, 0, 0, 0)); + } + break; + case SE_TabBarTearIndicator: + ret = QRect(0, 0, 0, 0); + break; + case SE_TabWidgetTabBar: + if (const QStyleOptionTabWidgetFrame *optionTab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + ret = QCommonStyle::subElementRect(element, opt, widget); + + if (const QStyleOptionTabWidgetFrame *twf = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + const int borderThickness = + QS60StylePrivate::pixelMetric(PM_DefaultFrameWidth); + int tabOverlap = pixelMetric(PM_TabBarTabOverlap); + if (tabOverlap > borderThickness) + tabOverlap -= borderThickness; + const QTabWidget *tab = qobject_cast<const QTabWidget *>(widget); + int gain = (tab) ? tabOverlap * tab->count() : 0; + switch (twf->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: { + if (widget) { + // make sure that gain does not set the rect outside of widget boundaries + if (twf->direction == Qt::RightToLeft) { + if ((ret.left() - gain) < widget->rect().left()) + gain = widget->rect().left() - ret.left(); + ret.adjust(-gain, 0, 0, 0); + } else { + if ((ret.right() + gain) > widget->rect().right()) + gain = widget->rect().right() - ret.right(); + ret.adjust(0, 0, gain, 0); + } + } + break; + } + default: { + if (widget) { + if ((ret.bottom() + gain) > widget->rect().bottom()) + gain = widget->rect().bottom() - ret.bottom(); + ret.adjust(0, 0, 0, gain); + } + break; + } + } + } + } + break; + case SE_ItemViewItemText: + case SE_ItemViewItemDecoration: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + const QAbstractItemView *listItem = qobject_cast<const QAbstractItemView *>(widget); + const bool multiSelection = !listItem ? false : + listItem->selectionMode() == QAbstractItemView::MultiSelection || + listItem->selectionMode() == QAbstractItemView::ExtendedSelection || + listItem->selectionMode() == QAbstractItemView::ContiguousSelection; + ret = QCommonStyle::subElementRect(element, opt, widget); + // If both multiselect & check-state, then remove checkbox and move + // text and decoration towards the beginning + if (listItem && + multiSelection && + (vopt->features & QStyleOptionViewItemV2::HasCheckIndicator)) { + const int verticalSpacing = + QS60StylePrivate::pixelMetric(PM_LayoutVerticalSpacing); + //const int horizontalSpacing = QS60StylePrivate::pixelMetric(PM_LayoutHorizontalSpacing); + const int checkBoxRectWidth = subElementRect(SE_ItemViewItemCheckIndicator, opt, widget).width(); + ret.adjust(-checkBoxRectWidth - verticalSpacing, 0, -checkBoxRectWidth - verticalSpacing, 0); + } + } else if (const QStyleOptionMenuItem *menuItem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + const bool checkable = menuItem->checkType != QStyleOptionMenuItem::NotCheckable; + const int indicatorWidth = checkable ? + pixelMetric(PM_ListViewIconSize, opt, widget) : + pixelMetric(PM_SmallIconSize, opt, widget); + ret = menuItem->rect; + + QRect checkBoxRect = checkable ? menuItem->rect : QRect(); + if (checkable) { + checkBoxRect.setWidth(pixelMetric(PM_IndicatorWidth)); + checkBoxRect.setHeight(pixelMetric(PM_IndicatorHeight)); + } + + const int vSpacing = QS60StylePrivate::pixelMetric(PM_LayoutVerticalSpacing); + //The vertical spacing is doubled; it needs one spacing to separate checkbox from + //highlight and then it needs one to separate it whatever is shown after it (text/icon/both). + const int moveByX = checkBoxRect.width() + 2 * vSpacing; + + if (element == SE_ItemViewItemDecoration) { + if (menuItem->icon.isNull()) { + ret = QRect(); + } else { + if (menuItem->direction == Qt::RightToLeft) + ret.translate(ret.width() - indicatorWidth - moveByX, 0); + else + ret.translate(moveByX, 0); + ret.setWidth(indicatorWidth); + } + } else { + if (!menuItem->icon.isNull()) { + if (menuItem->direction == Qt::LeftToRight) + ret.adjust(indicatorWidth, 0, 0, 0); + else + ret.adjust(0, 0, -indicatorWidth, 0); + } + if (menuItem->direction == Qt::LeftToRight) + ret.adjust(moveByX, 0, 0, 0); + else + ret.adjust(0, 0, -moveByX, 0); + + // Make room for submenu indicator + if (menuItem->menuItemType == QStyleOptionMenuItem::SubMenu){ + // submenu indicator is very small, so lets halve the rect + if (menuItem->direction == Qt::LeftToRight) + ret.adjust(0, 0, -(indicatorWidth >> 1), 0); + else + ret.adjust((indicatorWidth >> 1), 0, 0, 0); + } + } + } + break; + case SE_ItemViewItemCheckIndicator: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + const QAbstractItemView *listItem = qobject_cast<const QAbstractItemView *>(widget); + + const bool singleSelection = listItem && + (listItem->selectionMode() == QAbstractItemView::SingleSelection || + listItem->selectionMode() == QAbstractItemView::NoSelection); + const bool checkBoxOnly = (vopt->features & QStyleOptionViewItemV2::HasCheckIndicator) && + listItem && + singleSelection && vopt->text.isEmpty() && vopt->icon.isNull(); + + // Selection check mark rect. + const int indicatorWidth = QS60StylePrivate::pixelMetric(PM_IndicatorWidth); + const int indicatorHeight = QS60StylePrivate::pixelMetric(PM_IndicatorHeight); + const int spacing = QS60StylePrivate::pixelMetric(PM_CheckBoxLabelSpacing); + + const int itemHeight = opt->rect.height(); + int heightOffset = 0; + if (indicatorHeight < itemHeight) + heightOffset = ((itemHeight - indicatorHeight) >> 1); + if (checkBoxOnly) { + // Move rect and make it slightly smaller, so that + // a) highlight border does not cross the rect + // b) in s60 list checkbox is smaller than normal checkbox + //todo; magic three + ret.setRect(opt->rect.left() + 3, opt->rect.top() + heightOffset, + indicatorWidth - 3, indicatorHeight - 3); + } else { + ret.setRect(opt->rect.right() - indicatorWidth - spacing, opt->rect.top() + heightOffset, + indicatorWidth, indicatorHeight); + } + } else { + ret = QCommonStyle::subElementRect(element, opt, widget); + } + break; + case SE_HeaderLabel: + ret = QCommonStyle::subElementRect(element, opt, widget); + if (qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // Subtract area needed for line + if (opt->state & State_Horizontal) + ret.setHeight(ret.height() - QS60StylePrivate::pixelMetric(PM_BoldLineWidth)); + else + ret.setWidth(ret.width() - QS60StylePrivate::pixelMetric(PM_ThinLineWidth)); + } + ret = visualRect(opt->direction, opt->rect, ret); + break; + case SE_RadioButtonIndicator: { + const int height = pixelMetric(PM_ExclusiveIndicatorHeight, opt, widget); + ret.setRect(opt->rect.x(), opt->rect.y() + ((opt->rect.height() - height) >> 1), + pixelMetric(PM_ExclusiveIndicatorWidth, opt, widget), height); + ret.translate(2, 0); //move indicator slightly to avoid highlight crossing over it + ret = visualRect(opt->direction, opt->rect, ret); + } + break; + case SE_CheckBoxIndicator: { + const int height = pixelMetric(PM_IndicatorHeight, opt, widget); + ret.setRect(opt->rect.x(), opt->rect.y() + ((opt->rect.height() - height) >> 1), + pixelMetric(PM_IndicatorWidth, opt, widget), height); + ret.translate(2, 0); //move indicator slightly to avoid highlight crossing over it + ret = visualRect(opt->direction, opt->rect, ret); + } + break; + case SE_CheckBoxFocusRect: + ret = opt->rect; + break; + case SE_ProgressBarLabel: + case SE_ProgressBarContents: + case SE_ProgressBarGroove: + ret = opt->rect; + break; + default: + ret = QCommonStyle::subElementRect(element, opt, widget); + } + return ret; +} + +/*! + \reimp + */ +void QS60Style::polish(QWidget *widget) +{ + Q_D(const QS60Style); + QCommonStyle::polish(widget); + + if (!widget) + return; + + //Currently we only support animations in QProgressBar. +#ifndef QT_NO_PROGRESSBAR + if (qobject_cast<QProgressBar *>(widget)) + widget->installEventFilter(this); +#endif + + if (false +#ifndef QT_NO_SCROLLBAR + || qobject_cast<QScrollBar *>(widget) +#endif + ) { + widget->setAttribute(Qt::WA_OpaquePaintEvent, false); + } + + if (QS60StylePrivate::drawsOwnThemeBackground(widget)) { + widget->setAttribute(Qt::WA_StyledBackground); + } else if (false +#ifndef QT_NO_MENU + || qobject_cast<const QMenu *> (widget) +#endif // QT_NO_MENU + ) { + widget->setAttribute(Qt::WA_StyledBackground); + } else if (false +#ifndef QT_NO_COMBOBOX + || qobject_cast<const QComboBoxListView *>(widget) +#endif //QT_NO_COMBOBOX + ) { + widget->setAttribute(Qt::WA_StyledBackground); + } + d->setThemePalette(widget); + d->setFont(widget); +} + +/*! + \reimp + */ +void QS60Style::unpolish(QWidget *widget) +{ + Q_D(QS60Style); + + if (false + #ifndef QT_NO_SCROLLBAR + || qobject_cast<QScrollBar *>(widget) + #endif + ) + widget->setAttribute(Qt::WA_OpaquePaintEvent); + + if (QS60StylePrivate::drawsOwnThemeBackground(widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); + } else if (false +#ifndef QT_NO_MENU + || qobject_cast<const QMenu *> (widget) +#endif // QT_NO_MENU + ) { + widget->setAttribute(Qt::WA_StyledBackground, false); + } else if (false +#ifndef QT_NO_COMBOBOX + || qobject_cast<const QComboBoxListView *>(widget) +#endif //QT_NO_COMBOBOX + ) { + widget->setAttribute(Qt::WA_StyledBackground, false); + } + + if (widget) + widget->setPalette(QPalette()); + +#if defined(Q_WS_S60) && !defined(QT_NO_PROGRESSBAR) + if (QProgressBar *bar = qobject_cast<QProgressBar *>(widget)) { + widget->removeEventFilter(this); + d->m_bars.removeAll(bar); + } +#else + Q_UNUSED(d) +#endif + QCommonStyle::unpolish(widget); +} + +/*! + \reimp + */ +void QS60Style::polish(QApplication *application) +{ + Q_D(QS60Style); + QCommonStyle::polish(qApp); + d->m_originalPalette = application->palette(); + d->setThemePalette(application); + if (QS60StylePrivate::isTouchSupported()) + qApp->installEventFilter(this); +} + +/*! + \reimp + */ +void QS60Style::unpolish(QApplication *application) +{ + Q_UNUSED(application) + + Q_D(QS60Style); + QCommonStyle::unpolish(qApp); + const QPalette newPalette = QApplication::style()->standardPalette(); + QApplication::setPalette(newPalette); + QApplicationPrivate::setSystemPalette(d->m_originalPalette); + if (QS60StylePrivate::isTouchSupported()) + qApp->removeEventFilter(this); +} + +/*! + \reimp + */ +bool QS60Style::event(QEvent *e) +{ +#ifdef QT_KEYPAD_NAVIGATION + Q_D(QS60Style); + const QEvent::Type eventType = e->type(); + if ((eventType == QEvent::FocusIn || + eventType == QEvent::FocusOut || + eventType == QEvent::EnterEditFocus || + eventType == QEvent::LeaveEditFocus) && + QS60StylePrivate::isTouchSupported()) + return false; +#endif + + switch (e->type()) { + case QEvent::Timer: { + QTimerEvent *te = static_cast<QTimerEvent*>(e); + timerEvent(te); + } + break; +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::FocusIn: + if (QWidget *focusWidget = QApplication::focusWidget()) { + + // Menus and combobox popups do not draw focus frame around them + if (qobject_cast<QComboBoxListView *>(focusWidget) || + qobject_cast<QMenu *>(focusWidget)) + break; + + if (!d->m_focusFrame) + d->m_focusFrame = new QFocusFrame(focusWidget); + d->m_focusFrame->setWidget(focusWidget); + } else if (d->m_focusFrame) { + d->m_focusFrame->setWidget(0); + } + break; + case QEvent::FocusOut: + if (d->m_focusFrame) + d->m_focusFrame->setWidget(0); + break; + case QEvent::EnterEditFocus: + case QEvent::LeaveEditFocus: + if (d->m_focusFrame) + d->m_focusFrame->update(); + break; +#endif + default: + break; + } + return false; +} + +/*! + \internal + */ +QIcon QS60Style::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, const QWidget *widget) const +{ + QS60StyleEnums::SkinParts part; + qreal iconHeightMultiplier = 1.0; + qreal iconWidthMultiplier = 1.0; + QS60StylePrivate::SkinElementFlags adjustedFlags; + if (option) + adjustedFlags = (option->state & State_Enabled || option->state == 0) ? + QS60StylePrivate::SF_StateEnabled : + QS60StylePrivate::SF_StateDisabled; + + switch(standardIcon) { + case SP_MessageBoxWarning: + // By default, S60 messagebox icons have 4:3 ratio. Value is from S60 LAF documentation. + iconHeightMultiplier = 1.33; + part = QS60StyleEnums::SP_QgnNoteWarning; + break; + case SP_MessageBoxInformation: + iconHeightMultiplier = 1.33; + part = QS60StyleEnums::SP_QgnNoteInfo; + break; + case SP_MessageBoxCritical: + iconHeightMultiplier = 1.33; + part = QS60StyleEnums::SP_QgnNoteError; + break; + case SP_MessageBoxQuestion: + iconHeightMultiplier = 1.33; + part = QS60StyleEnums::SP_QgnNoteQuery; + break; + case SP_ArrowRight: + part = QS60StyleEnums::SP_QgnIndiNaviArrowRight; + break; + case SP_ArrowLeft: + part = QS60StyleEnums::SP_QgnIndiNaviArrowLeft; + break; + case SP_ArrowUp: + part = QS60StyleEnums::SP_QgnIndiNaviArrowLeft; + adjustedFlags |= QS60StylePrivate::SF_PointEast; + break; + case SP_ArrowDown: + part = QS60StyleEnums::SP_QgnIndiNaviArrowLeft; + adjustedFlags |= QS60StylePrivate::SF_PointWest; + break; + case SP_ArrowBack: + if (QApplication::layoutDirection() == Qt::RightToLeft) + return QS60Style::standardIcon(SP_ArrowRight, option, widget); + return QS60Style::standardIcon(SP_ArrowLeft, option, widget); + case SP_ArrowForward: + if (QApplication::layoutDirection() == Qt::RightToLeft) + return QS60Style::standardIcon(SP_ArrowLeft, option, widget); + return QS60Style::standardIcon(SP_ArrowRight, option, widget); + case SP_ComputerIcon: + part = QS60StyleEnums::SP_QgnPropPhoneMemcLarge; + break; + case SP_DirClosedIcon: + part = QS60StyleEnums::SP_QgnPropFolderSmall; + break; + case SP_DirOpenIcon: + part = QS60StyleEnums::SP_QgnPropFolderCurrent; + break; + case SP_DirIcon: + part = QS60StyleEnums::SP_QgnPropFolderSmall; + break; + case SP_FileDialogNewFolder: + part = QS60StyleEnums::SP_QgnPropFolderSmallNew; + break; + case SP_FileIcon: + part = QS60StyleEnums::SP_QgnPropFileSmall; + break; + case SP_TrashIcon: + part = QS60StyleEnums::SP_QgnNoteErased; + break; + case SP_ToolBarHorizontalExtensionButton: + part = QS60StyleEnums::SP_QgnIndiSubmenu; + if (QApplication::layoutDirection() == Qt::RightToLeft) + adjustedFlags |= QS60StylePrivate::SF_PointSouth; + break; + case SP_ToolBarVerticalExtensionButton: + adjustedFlags |= QS60StylePrivate::SF_PointEast; + part = QS60StyleEnums::SP_QgnIndiSubmenu; + break; + default: + return QCommonStyle::standardIconImplementation(standardIcon, option, widget); + } + const QS60StylePrivate::SkinElementFlags flags = adjustedFlags; + const int iconDimension = QS60StylePrivate::pixelMetric(PM_ToolBarIconSize); + const QRect iconSize = (!option) ? + QRect(0, 0, iconDimension * iconWidthMultiplier, iconDimension * iconHeightMultiplier) : option->rect; + const QPixmap cachedPixMap(QS60StylePrivate::cachedPart(part, iconSize.size(), 0, flags)); + return cachedPixMap.isNull() ? + QCommonStyle::standardIconImplementation(standardIcon, option, widget) : QIcon(cachedPixMap); +} + +/*! + \internal + Animate indeterminate progress bars only when visible +*/ +bool QS60Style::eventFilter(QObject *object, QEvent *event) +{ + Q_D(QS60Style); + switch(event->type()) { + case QEvent::MouseButtonPress: { + QWidget *w = QApplication::widgetAt(QCursor::pos()); + if (w) { + QWidget *focusW = w->focusProxy(); + if (qobject_cast<QAbstractItemView *>(focusW) || + qobject_cast<QRadioButton *>(focusW) || + qobject_cast<QCheckBox *>(focusW)) + d->m_pressedWidget = focusW; + else if (qobject_cast<QAbstractItemView *>(w)|| + qobject_cast<QRadioButton *>(w) || + qobject_cast<QCheckBox *>(w)) + d->m_pressedWidget = w; + + if (d->m_pressedWidget) + d->m_pressedWidget->update(); + } + break; + } + case QEvent::MouseButtonRelease: { + if (d->m_pressedWidget) { + d->m_pressedWidget->update(); + d->m_pressedWidget = 0; + } + break; + } + default: + break; + } + +#ifdef Q_WS_S60 +#ifndef QT_NO_PROGRESSBAR + switch(event->type()) { + case QEvent::StyleChange: + case QEvent::Show: + if (QProgressBar *bar = qobject_cast<QProgressBar *>(object)) { + if (!d->m_bars.contains(bar)) + d->m_bars << bar; + if (d->m_bars.size() == 1) //only start with first animated progressbar + d->startAnimation(QS60StyleEnums::SP_QgnGrafBarWaitAnim); + } + break; + case QEvent::Destroy: + case QEvent::Hide: + if (QProgressBar *bar = reinterpret_cast<QProgressBar *>(object)) { + d->stopAnimation(QS60StyleEnums::SP_QgnGrafBarWaitAnim); + d->m_bars.removeAll(bar); + } + break; + default: + break; + } +#endif // QT_NO_PROGRESSBAR +#endif // Q_WS_S60 + return QCommonStyle::eventFilter(object, event); +} + +/*! + \internal + Handle the timer \a event. +*/ +void QS60Style::timerEvent(QTimerEvent *event) +{ +#ifdef Q_WS_S60 +#ifndef QT_NO_PROGRESSBAR + Q_D(QS60Style); + + QS60StyleAnimation *progressBarAnimation = + QS60StylePrivate::animationDefinition(QS60StyleEnums::SP_QgnGrafBarWaitAnim); + + if (event->timerId() == progressBarAnimation->timerId()) { + + Q_ASSERT(progressBarAnimation->interval() > 0); + + if (progressBarAnimation->currentFrame() == progressBarAnimation->frameCount() ) + if (progressBarAnimation->playMode() == QS60StyleEnums::AM_Looping) + progressBarAnimation->setCurrentFrame(0); + else + d->stopAnimation(progressBarAnimation->animationId()); + + foreach (QProgressBar *bar, d->m_bars) { + if ((bar->minimum() == 0 && bar->maximum() == 0)) + bar->update(); + } + progressBarAnimation->setCurrentFrame(progressBarAnimation->currentFrame() + 1); + } +#endif // QT_NO_PROGRESSBAR +#endif // Q_WS_S60 + event->ignore(); +} + +extern QPoint qt_s60_fill_background_offset(const QWidget *targetWidget); + +bool qt_s60_fill_background(QPainter *painter, const QRegion &rgn, const QBrush &brush) +{ + // Check if the widget's palette matches placeholder or actual background texture. + // When accessing backgroundTexture, use parameter value 'true' to avoid creating + // the texture, if it is not already created. + + const QPixmap placeHolder(QS60StylePrivate::placeHolderTexture()); + const QPixmap bg(QS60StylePrivate::backgroundTexture(true)); + if (placeHolder.cacheKey() != brush.texture().cacheKey() + && bg.cacheKey() != brush.texture().cacheKey()) + return false; + + const QPixmap backgroundTexture(QS60StylePrivate::backgroundTexture()); + + const QPaintDevice *target = painter->device(); + if (target->devType() == QInternal::Widget) { + const QWidget *widget = static_cast<const QWidget *>(target); + if (!widget->testAttribute(Qt::WA_TranslucentBackground)) { + const QVector<QRect> &rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) { + const QRect rect(rects.at(i)); + painter->drawPixmap(rect.topLeft(), backgroundTexture, + rect.translated(qt_s60_fill_background_offset(widget))); + } + } + } + return true; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_S60 || QT_PLUGIN diff --git a/src/gui/styles/qs60style.h b/src/gui/styles/qs60style.h new file mode 100644 index 0000000000..5f44ca7b9b --- /dev/null +++ b/src/gui/styles/qs60style.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60STYLE_H +#define QS60STYLE_H + +#include <QtGui/qcommonstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +//Public custom pixel metrics values. +//These can be used to fetch custom pixel metric value from outside QS60Style. +enum { + PM_FrameCornerWidth = QStyle::PM_CustomBase + 1, + PM_FrameCornerHeight, + PM_BoldLineWidth, + PM_ThinLineWidth, + PM_MessageBoxHeight + }; + +class QS60StylePrivate; + +class Q_GUI_EXPORT QS60Style : public QCommonStyle +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QS60Style) + +public: + QS60Style(); + ~QS60Style(); + + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w = 0) const; + int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, + QStyleHintReturn *shret = 0) const; + QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, SubControl scontrol, const QWidget *widget = 0) const; + QRect subElementRect(SubElement element, const QStyleOption *opt, const QWidget *widget = 0) const; + void polish(QWidget *widget); + void unpolish(QWidget *widget); + void polish(QApplication *application); + void unpolish(QApplication *application); +#ifndef Q_NO_USING_KEYWORD + using QCommonStyle::polish; +#endif + bool event(QEvent *e); + +#ifndef Q_OS_SYMBIAN + static QStringList partKeys(); + static QStringList colorListKeys(); + void setS60Theme(const QHash<QString, QPicture> &parts, + const QHash<QPair<QString , int>, QColor> &colors); + bool loadS60ThemeFromBlob(const QString &blobFile); + bool saveS60ThemeToBlob(const QString &blobFile) const; +#endif // !Q_OS_SYMBIAN + +protected Q_SLOTS: + QIcon standardIconImplementation( + StandardPixmap standardIcon, const QStyleOption * option = 0, const QWidget * widget = 0 ) const; + +protected: + void timerEvent(QTimerEvent *event); + bool eventFilter(QObject *o, QEvent *e); +private: + Q_DISABLE_COPY(QS60Style) + friend class QStyleFactory; + friend class QApplicationPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QS60STYLE_H diff --git a/src/gui/styles/qs60style_p.h b/src/gui/styles/qs60style_p.h new file mode 100644 index 0000000000..e146a4e3ac --- /dev/null +++ b/src/gui/styles/qs60style_p.h @@ -0,0 +1,638 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60STYLE_P_H +#define QS60STYLE_P_H + +#include "qs60style.h" +#include "qcommonstyle_p.h" +#include <QtCore/qhash.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 + +const int MAX_NON_CUSTOM_PIXELMETRICS = 92; +const int CUSTOMVALUESCOUNT = 5; + +const int MAX_PIXELMETRICS = MAX_NON_CUSTOM_PIXELMETRICS + CUSTOMVALUESCOUNT; + +typedef struct { + unsigned short height; + unsigned short width; + int major_version; + int minor_version; + const char* layoutName; +} layoutHeader; + +#ifdef Q_OS_SYMBIAN +NONSHARABLE_CLASS (QS60StyleEnums) +#else +class QS60StyleEnums +#endif +: public QObject +{ +#ifndef Q_WS_S60 + Q_OBJECT + Q_ENUMS(FontCategories) + Q_ENUMS(SkinParts) + Q_ENUMS(ColorLists) +#endif // !Q_WS_S60 + +public: + + // S60 definitions within theme + enum ThemeDefinitions { + TD_AnimationData, + }; + + //Defines which values are contained within animation data (retrieved using TD_AnimationData). + //Additionally defines the order in which the items are given out in QList<QVariant>. + enum AnimationData { + AD_Interval = 0, + AD_NumberOfFrames, + AD_AnimationPlayMode, //currently not used as themes seem to contain invalid data + }; + + // Animation modes + enum AnimationMode { + AM_PlayOnce = 0, //animation is played exactly once + AM_Looping, //animation is repeated until stopped + AM_Bounce //animation is played repeatedly until stopped, + //but frames are played in reverse order every second time + //(no support yet) + }; + + // S60 look-and-feel font categories + enum FontCategories { + FC_Undefined, + FC_Primary, + FC_Secondary, + FC_Title, + FC_PrimarySmall, + FC_Digital + }; + + enum SkinParts { + SP_QgnGrafBarWaitAnim, + SP_QgnGrafBarFrameCenter, + SP_QgnGrafBarFrameSideL, + SP_QgnGrafBarFrameSideR, + SP_QgnGrafBarProgress, + SP_QgnGrafOrgBgGrid, + SP_QgnGrafScrollArrowDown, + SP_QgnGrafScrollArrowLeft, + SP_QgnGrafScrollArrowRight, + SP_QgnGrafScrollArrowUp, + SP_QgnGrafTabActiveL, + SP_QgnGrafTabActiveM, + SP_QgnGrafTabActiveR, + SP_QgnGrafTabPassiveL, + SP_QgnGrafTabPassiveM, + SP_QgnGrafTabPassiveR, + SP_QgnGrafNsliderEndLeft, + SP_QgnGrafNsliderEndRight, + SP_QgnGrafNsliderMiddle, + SP_QgnIndiCheckboxOff, + SP_QgnIndiCheckboxOn, + SP_QgnIndiHlColSuper, // Available in S60 release 3.2 and later. + SP_QgnIndiHlExpSuper, // Available in S60 release 3.2 and later. + SP_QgnIndiHlLineBranch, // Available in S60 release 3.2 and later. + SP_QgnIndiHlLineEnd, // Available in S60 release 3.2 and later. + SP_QgnIndiHlLineStraight, // Available in S60 release 3.2 and later. + SP_QgnIndiMarkedAdd, + SP_QgnIndiNaviArrowLeft, + SP_QgnIndiNaviArrowRight, + SP_QgnIndiRadiobuttOff, + SP_QgnIndiRadiobuttOn, + SP_QgnGrafNsliderMarker, + SP_QgnGrafNsliderMarkerSelected, + SP_QgnIndiSubmenu, + SP_QgnNoteErased, + SP_QgnNoteError, + SP_QgnNoteInfo, + SP_QgnNoteOk, + SP_QgnNoteQuery, + SP_QgnNoteWarning, + SP_QgnPropFileSmall, + SP_QgnPropFolderCurrent, + SP_QgnPropFolderSmall, + SP_QgnPropFolderSmallNew, + SP_QgnPropPhoneMemcLarge, + SP_QgnFrSctrlButtonCornerTl, // Toolbar button + SP_QgnFrSctrlButtonCornerTr, + SP_QgnFrSctrlButtonCornerBl, + SP_QgnFrSctrlButtonCornerBr, + SP_QgnFrSctrlButtonSideT, + SP_QgnFrSctrlButtonSideB, + SP_QgnFrSctrlButtonSideL, + SP_QgnFrSctrlButtonSideR, + SP_QgnFrSctrlButtonCenter, + SP_QgnFrSctrlButtonCornerTlPressed, // Toolbar button, pressed + SP_QgnFrSctrlButtonCornerTrPressed, + SP_QgnFrSctrlButtonCornerBlPressed, + SP_QgnFrSctrlButtonCornerBrPressed, + SP_QgnFrSctrlButtonSideTPressed, + SP_QgnFrSctrlButtonSideBPressed, + SP_QgnFrSctrlButtonSideLPressed, + SP_QgnFrSctrlButtonSideRPressed, + SP_QgnFrSctrlButtonCenterPressed, + SP_QsnCpScrollHandleBottomPressed, //ScrollBar handle, pressed state + SP_QsnCpScrollHandleMiddlePressed, + SP_QsnCpScrollHandleTopPressed, + SP_QsnBgScreen, + SP_QsnCpScrollBgBottom, + SP_QsnCpScrollBgMiddle, + SP_QsnCpScrollBgTop, + SP_QsnCpScrollHandleBottom, + SP_QsnCpScrollHandleMiddle, + SP_QsnCpScrollHandleTop, + SP_QsnFrButtonTbCornerTl, // Button, normal state + SP_QsnFrButtonTbCornerTr, + SP_QsnFrButtonTbCornerBl, + SP_QsnFrButtonTbCornerBr, + SP_QsnFrButtonTbSideT, + SP_QsnFrButtonTbSideB, + SP_QsnFrButtonTbSideL, + SP_QsnFrButtonTbSideR, + SP_QsnFrButtonTbCenter, + SP_QsnFrButtonTbCornerTlPressed, // Button, pressed state + SP_QsnFrButtonTbCornerTrPressed, + SP_QsnFrButtonTbCornerBlPressed, + SP_QsnFrButtonTbCornerBrPressed, + SP_QsnFrButtonTbSideTPressed, + SP_QsnFrButtonTbSideBPressed, + SP_QsnFrButtonTbSideLPressed, + SP_QsnFrButtonTbSideRPressed, + SP_QsnFrButtonTbCenterPressed, + SP_QsnFrCaleCornerTl, // calendar grid item + SP_QsnFrCaleCornerTr, + SP_QsnFrCaleCornerBl, + SP_QsnFrCaleCornerBr, + SP_QsnFrCaleSideT, + SP_QsnFrCaleSideB, + SP_QsnFrCaleSideL, + SP_QsnFrCaleSideR, + SP_QsnFrCaleCenter, + SP_QsnFrCaleHeadingCornerTl, // calendar grid header + SP_QsnFrCaleHeadingCornerTr, + SP_QsnFrCaleHeadingCornerBl, + SP_QsnFrCaleHeadingCornerBr, + SP_QsnFrCaleHeadingSideT, + SP_QsnFrCaleHeadingSideB, + SP_QsnFrCaleHeadingSideL, + SP_QsnFrCaleHeadingSideR, + SP_QsnFrCaleHeadingCenter, + SP_QsnFrInputCornerTl, // Text input field + SP_QsnFrInputCornerTr, + SP_QsnFrInputCornerBl, + SP_QsnFrInputCornerBr, + SP_QsnFrInputSideT, + SP_QsnFrInputSideB, + SP_QsnFrInputSideL, + SP_QsnFrInputSideR, + SP_QsnFrInputCenter, + SP_QsnFrListCornerTl, // List background + SP_QsnFrListCornerTr, + SP_QsnFrListCornerBl, + SP_QsnFrListCornerBr, + SP_QsnFrListSideT, + SP_QsnFrListSideB, + SP_QsnFrListSideL, + SP_QsnFrListSideR, + SP_QsnFrListCenter, + SP_QsnFrPopupCornerTl, // Option menu background + SP_QsnFrPopupCornerTr, + SP_QsnFrPopupCornerBl, + SP_QsnFrPopupCornerBr, + SP_QsnFrPopupSideT, + SP_QsnFrPopupSideB, + SP_QsnFrPopupSideL, + SP_QsnFrPopupSideR, + SP_QsnFrPopupCenter, + SP_QsnFrPopupPreviewCornerTl, // tool tip background + SP_QsnFrPopupPreviewCornerTr, + SP_QsnFrPopupPreviewCornerBl, + SP_QsnFrPopupPreviewCornerBr, + SP_QsnFrPopupPreviewSideT, + SP_QsnFrPopupPreviewSideB, + SP_QsnFrPopupPreviewSideL, + SP_QsnFrPopupPreviewSideR, + SP_QsnFrPopupPreviewCenter, + SP_QsnFrSetOptCornerTl, // Settings list + SP_QsnFrSetOptCornerTr, + SP_QsnFrSetOptCornerBl, + SP_QsnFrSetOptCornerBr, + SP_QsnFrSetOptSideT, + SP_QsnFrSetOptSideB, + SP_QsnFrSetOptSideL, + SP_QsnFrSetOptSideR, + SP_QsnFrSetOptCenter, + SP_QsnFrPopupSubCornerTl, // Toolbar background + SP_QsnFrPopupSubCornerTr, + SP_QsnFrPopupSubCornerBl, + SP_QsnFrPopupSubCornerBr, + SP_QsnFrPopupSubSideT, + SP_QsnFrPopupSubSideB, + SP_QsnFrPopupSubSideL, + SP_QsnFrPopupSubSideR, + SP_QsnFrPopupSubCenter, + SP_QsnFrButtonCornerTlInactive, // Inactive button + SP_QsnFrButtonCornerTrInactive, + SP_QsnFrButtonCornerBlInactive, + SP_QsnFrButtonCornerBrInactive, + SP_QsnFrButtonSideTInactive, + SP_QsnFrButtonSideBInactive, + SP_QsnFrButtonSideLInactive, + SP_QsnFrButtonSideRInactive, + SP_QsnFrButtonCenterInactive, + SP_QsnFrGridCornerTlPressed, // Pressed table item + SP_QsnFrGridCornerTrPressed, + SP_QsnFrGridCornerBlPressed, + SP_QsnFrGridCornerBrPressed, + SP_QsnFrGridSideTPressed, + SP_QsnFrGridSideBPressed, + SP_QsnFrGridSideLPressed, + SP_QsnFrGridSideRPressed, + SP_QsnFrGridCenterPressed, + SP_QsnFrListCornerTlPressed, // Pressed list item + SP_QsnFrListCornerTrPressed, + SP_QsnFrListCornerBlPressed, + SP_QsnFrListCornerBrPressed, + SP_QsnFrListSideTPressed, + SP_QsnFrListSideBPressed, + SP_QsnFrListSideLPressed, + SP_QsnFrListSideRPressed, + SP_QsnFrListCenterPressed, + }; + + enum ColorLists { + CL_QsnHighlightColors, + CL_QsnIconColors, + CL_QsnLineColors, + CL_QsnOtherColors, + CL_QsnParentColors, + CL_QsnTextColors + }; +}; + +#ifdef Q_WS_S60 +class CAknBitmapAnimation; +NONSHARABLE_CLASS (AnimationData) : public QObject +{ +public: + AnimationData(const QS60StyleEnums::SkinParts part, int frames, int interval); + + const QS60StyleEnums::SkinParts m_id; + int m_frames; + int m_interval; + QS60StyleEnums::AnimationMode m_mode; +}; + + +NONSHARABLE_CLASS (AnimationDataV2) : public AnimationData +{ +public: + AnimationDataV2(const AnimationData &data); + ~AnimationDataV2(); + + CAknBitmapAnimation *m_animation; + int m_currentFrame; + bool m_resourceBased; + int m_timerId; +}; + + +class QS60StyleAnimation : public QObject +{ +public: + QS60StyleAnimation(const QS60StyleEnums::SkinParts part, int frames, int interval); + ~QS60StyleAnimation(); + +public: + QS60StyleEnums::SkinParts animationId() const {return m_currentData->m_id;} + int frameCount() const { return m_currentData->m_frames;} + int interval() const {return m_currentData->m_interval;} + QS60StyleEnums::AnimationMode playMode() const {return m_currentData->m_mode;} + CAknBitmapAnimation* animationObject() const {return m_currentData->m_animation;} + bool isResourceBased() const {return m_currentData->m_resourceBased;} + int timerId() const {return m_currentData->m_timerId;} + int currentFrame() const {return m_currentData->m_currentFrame;} + + void setFrameCount(int frameCount) {m_currentData->m_frames = frameCount;} + void setInterval(int interval) {m_currentData->m_interval = interval;} + void setAnimationObject(CAknBitmapAnimation* animation); + void setResourceBased(bool resourceBased) {m_currentData->m_resourceBased = resourceBased;} + void setTimerId(int timerId) {m_currentData->m_timerId = timerId;} + void setCurrentFrame(int currentFrame) {m_currentData->m_currentFrame = currentFrame;} + + void resetToDefaults(); + +private: //data members + //TODO: consider changing these to non-pointers as the classes are rather small anyway + AnimationData *m_defaultData; + AnimationDataV2 *m_currentData; +}; + +#endif //Q_WS_S60 + + +class QFocusFrame; +class QProgressBar; +class QS60StyleAnimation; + +// Private class +#ifdef Q_OS_SYMBIAN +NONSHARABLE_CLASS (QS60StylePrivate) +#else +class QS60StylePrivate +#endif +: public QCommonStylePrivate +{ + Q_DECLARE_PUBLIC(QS60Style) + +public: + QS60StylePrivate(); + ~QS60StylePrivate(); + + enum SkinElements { + SE_ButtonNormal, + SE_ButtonPressed, + SE_FrameLineEdit, + SE_ProgressBarGrooveHorizontal, + SE_ProgressBarIndicatorHorizontal, + SE_ProgressBarGrooveVertical, + SE_ProgressBarIndicatorVertical, + SE_ScrollBarGrooveHorizontal, + SE_ScrollBarGrooveVertical, + SE_ScrollBarHandleHorizontal, + SE_ScrollBarHandleVertical, + SE_SliderHandleHorizontal, + SE_SliderHandleVertical, + SE_SliderHandleSelectedHorizontal, + SE_SliderHandleSelectedVertical, + SE_SliderGrooveVertical, + SE_SliderGrooveHorizontal, + SE_TabBarTabEastActive, + SE_TabBarTabEastInactive, + SE_TabBarTabNorthActive, + SE_TabBarTabNorthInactive, + SE_TabBarTabSouthActive, + SE_TabBarTabSouthInactive, + SE_TabBarTabWestActive, + SE_TabBarTabWestInactive, + SE_ListHighlight, + SE_PopupBackground, + SE_SettingsList, + SE_TableItem, + SE_TableHeaderItem, + SE_ToolTip, //own graphic available on 3.2+ releases, + SE_ToolBar, + SE_ToolBarButton, + SE_ToolBarButtonPressed, + SE_PanelBackground, + SE_ScrollBarHandlePressedHorizontal, + SE_ScrollBarHandlePressedVertical, + SE_ButtonInactive, + SE_Editor, + SE_DropArea, + SE_TableItemPressed, + SE_ListItemPressed, + }; + + enum SkinFrameElements { + SF_ButtonNormal, + SF_ButtonPressed, + SF_FrameLineEdit, + SF_ListHighlight, + SF_PopupBackground, + SF_SettingsList, + SF_TableItem, + SF_TableHeaderItem, + SF_ToolTip, + SF_ToolBar, + SF_ToolBarButton, + SF_ToolBarButtonPressed, + SF_PanelBackground, + SF_ButtonInactive, + SF_TableItemPressed, + SF_ListItemPressed, + }; + + enum SkinElementFlag { + SF_PointNorth = 0x0001, // North = the default + SF_PointEast = 0x0002, + SF_PointSouth = 0x0004, + SF_PointWest = 0x0008, + + SF_StateEnabled = 0x0010, // Enabled = the default + SF_StateDisabled = 0x0020, + SF_ColorSkinned = 0x0040, // pixmap is colored with foreground pen color + SF_Animation = 0x0080, + SF_Mirrored_X_Axis = 0x0100, + SF_Mirrored_Y_Axis = 0x0200 + }; + + enum CacheClearReason { + CC_UndefinedChange = 0, + CC_LayoutChange, + CC_ThemeChange + }; + + Q_DECLARE_FLAGS(SkinElementFlags, SkinElementFlag) + + // draws skin element + static void drawSkinElement(SkinElements element, QPainter *painter, + const QRect &rect, SkinElementFlags flags = KDefaultSkinElementFlags); + // draws a specific skin part + static void drawSkinPart(QS60StyleEnums::SkinParts part, QPainter *painter, + const QRect &rect, SkinElementFlags flags = KDefaultSkinElementFlags); + // gets pixel metrics value + static short pixelMetric(int metric); + // gets color. 'index' is NOT 0-based. + // It corresponds to the enum key 1-based numbers of TAknsQsnXYZColorsIndex, not the values. + static QColor s60Color(QS60StyleEnums::ColorLists list, + int index, const QStyleOption *option); + // gets state specific color + static QColor stateColor(const QColor &color, const QStyleOption *option); + // gets lighter color than base color + static QColor lighterColor(const QColor &baseColor); + //deduces if the given widget should have separately themeable background + static bool drawsOwnThemeBackground(const QWidget *widget); + + QFont s60Font(QS60StyleEnums::FontCategories fontCategory, + int pointSize = -1, bool resolveFontSize = true) const; + // clears all style caches (fonts, colors, pixmaps) + void clearCaches(CacheClearReason reason = CC_UndefinedChange); + + // themed main background oprations + void setBackgroundTexture(QApplication *application) const; + static void deleteBackground(); + + static bool isTouchSupported(); + static bool isToolBarBackground(); + static bool hasSliderGrooveGraphic(); + static bool isSingleClickUi(); + static bool isWidgetPressed(const QWidget *widget); + +#ifdef Q_WS_S60 + static void deleteStoredSettings(); + // calculates average color based on theme graphics (minus borders). + QColor colorFromFrameGraphics(SkinFrameElements frame) const; +#endif + QColor calculatedColor(SkinFrameElements frame) const; + + //set theme palette for application + void setThemePalette(QApplication *application) const; + //access to theme palette + static QPalette* themePalette(); + + static const layoutHeader m_layoutHeaders[]; + static const short data[][MAX_PIXELMETRICS]; + + void setCurrentLayout(int layoutIndex); + void setActiveLayout(); + // Pointer + static short const *m_pmPointer; + // number of layouts supported by the style + static const int m_numberOfLayouts; + + mutable QHash<QPair<QS60StyleEnums::FontCategories , int>, QFont> m_mappedFontsCache; + + // Has one entry per SkinFrameElements + static const struct frameElementCenter { + SkinElements element; + QS60StyleEnums::SkinParts center; + } m_frameElementsData[]; + + static QPixmap frame(SkinFrameElements frame, const QSize &size, + SkinElementFlags flags = KDefaultSkinElementFlags); + static QPixmap backgroundTexture(bool skipCreation = false); + static QPixmap placeHolderTexture(); + +#ifdef Q_WS_S60 + void handleDynamicLayoutVariantSwitch(); + void handleSkinChange(); +#endif // Q_WS_S60 + + //Checks that the current brush is transparent or has BrushStyle NoBrush, + //so that theme graphic background can be drawn. + static bool canDrawThemeBackground(const QBrush &backgroundBrush, const QWidget *widget); + + static int currentAnimationFrame(QS60StyleEnums::SkinParts part); +#ifdef Q_WS_S60 + + //No support for animations on emulated style + void startAnimation(QS60StyleEnums::SkinParts animation); + void stopAnimation(QS60StyleEnums::SkinParts animation); + static QS60StyleAnimation* animationDefinition(QS60StyleEnums::SkinParts part); + static void removeAnimations(); + +#endif + +private: + static void drawPart(QS60StyleEnums::SkinParts part, QPainter *painter, + const QRect &rect, SkinElementFlags flags = KDefaultSkinElementFlags); + static void drawRow(QS60StyleEnums::SkinParts start, QS60StyleEnums::SkinParts middle, + QS60StyleEnums::SkinParts end, Qt::Orientation orientation, QPainter *painter, + const QRect &rect, SkinElementFlags flags = KDefaultSkinElementFlags); + static void drawFrame(SkinFrameElements frame, QPainter *painter, + const QRect &rect, SkinElementFlags flags = KDefaultSkinElementFlags); + + static QPixmap cachedPart(QS60StyleEnums::SkinParts part, const QSize &size, + QPainter *painter, SkinElementFlags flags = KDefaultSkinElementFlags); + static QPixmap cachedFrame(SkinFrameElements frame, const QSize &size, + SkinElementFlags flags = KDefaultSkinElementFlags); + + // set S60 font for widget + void setFont(QWidget *widget) const; + static void setThemePalette(QWidget *widget); + void setThemePalette(QPalette *palette) const; + static void setThemePaletteHash(QPalette *palette); + static void storeThemePalette(QPalette *palette); + static void deleteThemePalette(); + static bool equalToThemePalette(QColor color, QPalette::ColorRole role); + static bool equalToThemePalette(qint64 cacheKey, QPalette::ColorRole role); + + static QSize partSize(QS60StyleEnums::SkinParts part, + SkinElementFlags flags = KDefaultSkinElementFlags); + static QPixmap part(QS60StyleEnums::SkinParts part, const QSize &size, + QPainter *painter, SkinElementFlags flags = KDefaultSkinElementFlags); + + static QFont s60Font_specific(QS60StyleEnums::FontCategories fontCategory, + int pointSize, bool resolveFontSize); + + static QSize screenSize(); + + // Contains background texture. + static QPixmap *m_background; + // Placeholder pixmap for the real background texture. + static QPixmap *m_placeHolderTexture; + + const static SkinElementFlags KDefaultSkinElementFlags; + // defined theme palette + static QPalette *m_themePalette; + QPalette m_originalPalette; + + QPointer<QFocusFrame> m_focusFrame; + static qint64 m_webPaletteKey; + + static QPointer<QWidget> m_pressedWidget; + +#ifdef Q_WS_S60 + //list of progress bars having animation running + QList<QProgressBar *> m_bars; +#endif + +}; + +QT_END_NAMESPACE + +#endif // QS60STYLE_P_H diff --git a/src/gui/styles/qs60style_s60.cpp b/src/gui/styles/qs60style_s60.cpp new file mode 100644 index 0000000000..1e374cbc09 --- /dev/null +++ b/src/gui/styles/qs60style_s60.cpp @@ -0,0 +1,1591 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qs60style.h" +#include "qs60style_p.h" +#include "qpainter.h" +#include "qstyleoption.h" +#include "qstyle.h" +#include "private/qt_s60_p.h" +#include "private/qpixmap_s60_p.h" +#include "private/qcore_symbian_p.h" +#include "private/qvolatileimage_p.h" +#include "qapplication.h" +#include "qsettings.h" + +#include <w32std.h> +#include <AknsConstants.h> +#include <aknconsts.h> +#include <AknsItemID.h> +#include <AknsUtils.h> +#include <AknsDrawUtils.h> +#include <AknsSkinInstance.h> +#include <AknsBasicBackgroundControlContext.h> +#include <avkon.mbg> +#include <AknFontAccess.h> +#include <AknLayoutFont.h> +#include <AknUtils.h> +#include <aknnavi.h> +#include <gulicon.h> +#include <AknBitmapAnimation.h> +#include <centralrepository.h> + +#if !defined(QT_NO_STYLE_S60) || defined(QT_PLUGIN) + +QT_BEGIN_NAMESPACE + +enum TDrawType { + EDrawIcon, + EDrawGulIcon, + EDrawBackground, + EDrawAnimation, + ENoDraw +}; + +const TUid personalisationUID = { 0x101F876F }; + +enum TSupportRelease { + ES60_None = 0x0000, //indicates that the commonstyle should draw the graphics + ES60_3_1 = 0x0001, + ES60_3_2 = 0x0002, + ES60_5_0 = 0x0004, + ES60_5_1 = 0x0008, + ES60_5_2 = 0x0010, + ES60_5_3 = 0x0020, + ES60_3_X = ES60_3_1 | ES60_3_2, + // Releases before Symbian Foundation + ES60_PreSF = ES60_3_1 | ES60_3_2 | ES60_5_0, + // Releases before the S60 5.2 + ES60_Pre52 = ES60_3_1 | ES60_3_2 | ES60_5_0 | ES60_5_1, + // Releases before S60 5.3 + ES60_Pre53 = ES60_3_1 | ES60_3_2 | ES60_5_0 | ES60_5_1 | ES60_5_2, + // Add all new releases here + ES60_All = ES60_3_1 | ES60_3_2 | ES60_5_0 | ES60_5_1 | ES60_5_2 | ES60_5_3 +}; + +typedef struct { + const TAknsItemID &skinID; // Determines default theme graphics ID. + TDrawType drawType; // Determines which native drawing routine is used to draw this item. + int supportInfo; // Defines the S60 versions that use the default graphics. + // These two, define new graphics that are used in releases other than partMapEntry.supportInfo defined releases. + // In general, these are given in numeric form to allow style compilation in earlier + // native releases that do not contain the new graphics. + int newMajorSkinId; + int newMinorSkinId; +} partMapEntry; + +AnimationData::AnimationData(const QS60StyleEnums::SkinParts part, int frames, int interval) : m_id(part), + m_frames(frames), m_interval(interval), m_mode(QS60StyleEnums::AM_Looping) +{ +} + +AnimationDataV2::AnimationDataV2(const AnimationData &data) : AnimationData(data.m_id, data.m_frames, data.m_interval), + m_animation(0), m_currentFrame(0), m_resourceBased(false), m_timerId(0) +{ +} +AnimationDataV2::~AnimationDataV2() +{ + delete m_animation; +} + +QS60StyleAnimation::QS60StyleAnimation(const QS60StyleEnums::SkinParts part, int frames, int interval) +{ + QT_TRAP_THROWING(m_defaultData = new (ELeave) AnimationData(part, frames, interval)); + QT_TRAP_THROWING(m_currentData = new (ELeave) AnimationDataV2(*m_defaultData)); +} + +QS60StyleAnimation::~QS60StyleAnimation() +{ + delete m_currentData; + delete m_defaultData; +} + +void QS60StyleAnimation::setAnimationObject(CAknBitmapAnimation* animation) +{ + Q_ASSERT(animation); + if (m_currentData->m_animation) + delete m_currentData->m_animation; + m_currentData->m_animation = animation; +} + +void QS60StyleAnimation::resetToDefaults() +{ + delete m_currentData; + m_currentData = 0; + QT_TRAP_THROWING(m_currentData = new (ELeave) AnimationDataV2(*m_defaultData)); +} + +class QS60StyleModeSpecifics +{ +public: + static QPixmap skinnedGraphics(QS60StyleEnums::SkinParts stylepart, + const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static QPixmap skinnedGraphics(QS60StylePrivate::SkinFrameElements frameElement, const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static QPixmap colorSkinnedGraphics(const QS60StyleEnums::SkinParts &stylepart, + const QSize &size, QPainter *painter, QS60StylePrivate::SkinElementFlags flags); + static QColor colorValue(const TAknsItemID &colorGroup, int colorIndex); + static QPixmap fromFbsBitmap(CFbsBitmap *icon, CFbsBitmap *mask, QS60StylePrivate::SkinElementFlags flags, const TSize& targetSize); + static bool disabledPartGraphic(QS60StyleEnums::SkinParts &part); + static bool disabledFrameGraphic(QS60StylePrivate::SkinFrameElements &frame); + static QPixmap generateMissingThemeGraphic(QS60StyleEnums::SkinParts &part, const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static TAknsItemID partSpecificThemeId(int part); + + static QVariant themeDefinition(QS60StyleEnums::ThemeDefinitions definition, QS60StyleEnums::SkinParts part); + +private: + static QPixmap createSkinnedGraphicsLX(QS60StyleEnums::SkinParts part, + const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static QPixmap createSkinnedGraphicsLX(QS60StylePrivate::SkinFrameElements frameElement, const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static QPixmap colorSkinnedGraphicsLX(const QS60StyleEnums::SkinParts &stylepart, + const QSize &size, QPainter *painter, QS60StylePrivate::SkinElementFlags flags); + static void frameIdAndCenterId(QS60StylePrivate::SkinFrameElements frameElement, TAknsItemID &frameId, TAknsItemID ¢erId); + static TRect innerRectFromElement(QS60StylePrivate::SkinFrameElements frameElement, const TRect &outerRect); + static void fallbackInfo(const QS60StyleEnums::SkinParts &stylePart, TInt &fallbackIndex); + static bool checkSupport(const int supportedRelease); + // Array to match the skin ID, fallback graphics and Qt widget graphics. + static const partMapEntry m_partMap[]; +}; + +const partMapEntry QS60StyleModeSpecifics::m_partMap[] = { + /* SP_QgnGrafBarWaitAnim */ {KAknsIIDQgnGrafBarWaitAnim, EDrawAnimation, ES60_All, -1,-1}, + /* SP_QgnGrafBarFrameCenter */ {KAknsIIDQgnGrafBarFrameCenter, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnGrafBarFrameSideL */ {KAknsIIDQgnGrafBarFrameSideL, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnGrafBarFrameSideR */ {KAknsIIDQgnGrafBarFrameSideR, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnGrafBarProgress */ {KAknsIIDQgnGrafBarProgress, EDrawIcon, ES60_All, -1,-1}, + // No drop area for 3.x non-touch devices + /* SP_QgnGrafOrgBgGrid */ {KAknsIIDNone, EDrawIcon, ES60_3_X, EAknsMajorGeneric ,0x1eba}, //KAknsIIDQgnGrafOrgBgGrid + /* SP_QgnGrafScrollArrowDown */ {KAknsIIDQgnGrafScrollArrowDown, EDrawGulIcon, ES60_All, -1,-1}, + /* SP_QgnGrafScrollArrowLeft */ {KAknsIIDQgnGrafScrollArrowLeft, EDrawGulIcon, ES60_All, -1,-1}, + /* SP_QgnGrafScrollArrowRight */ {KAknsIIDQgnGrafScrollArrowRight, EDrawGulIcon, ES60_All, -1,-1}, + /* SP_QgnGrafScrollArrowUp */ {KAknsIIDQgnGrafScrollArrowUp, EDrawGulIcon, ES60_All, -1,-1}, + + // In S60 5.3 there is a new tab graphic + /* SP_QgnGrafTabActiveL */ {KAknsIIDQgnGrafTabActiveL, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x2219}, //KAknsIIDQtgFrTabActiveNormalL + /* SP_QgnGrafTabActiveM */ {KAknsIIDQgnGrafTabActiveM, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x221b}, //KAknsIIDQtgFrTabActiveNormalC + /* SP_QgnGrafTabActiveR */ {KAknsIIDQgnGrafTabActiveR, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x221a}, //KAknsIIDQtgFrTabActiveNormalR + /* SP_QgnGrafTabPassiveL */ {KAknsIIDQgnGrafTabPassiveL, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x2221}, //KAknsIIDQtgFrTabPassiveNormalL + /* SP_QgnGrafTabPassiveM */ {KAknsIIDQgnGrafTabPassiveM, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x2223}, //KAknsIIDQtgFrTabPassiveNormalC + /* SP_QgnGrafTabPassiveR */ {KAknsIIDQgnGrafTabPassiveR, EDrawIcon, ES60_Pre53, EAknsMajorSkin, 0x2222}, //KAknsIIDQtgFrTabPassiveNormalR + + // In 3.1 there is no slider groove. + /* SP_QgnGrafNsliderEndLeft */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x19cf /* KAknsIIDQgnGrafNsliderEndLeft */}, + /* SP_QgnGrafNsliderEndRight */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x19d0 /* KAknsIIDQgnGrafNsliderEndRight */}, + /* SP_QgnGrafNsliderMiddle */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x19d2 /* KAknsIIDQgnGrafNsliderMiddle */}, + /* SP_QgnIndiCheckboxOff */ {KAknsIIDQgnIndiCheckboxOff, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnIndiCheckboxOn */ {KAknsIIDQgnIndiCheckboxOn, EDrawIcon, ES60_All, -1,-1}, + + // Following 5 items (SP_QgnIndiHlColSuper - SP_QgnIndiHlLineStraight) are available starting from S60 release 3.2. + // In 3.1 CommonStyle drawing is used for these QTreeView elements, since no similar icons in AVKON UI. + /* SP_QgnIndiHlColSuper */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x17d5 /* KAknsIIDQgnIndiHlColSuper */}, + /* SP_QgnIndiHlExpSuper */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x17d6 /* KAknsIIDQgnIndiHlExpSuper */}, + /* SP_QgnIndiHlLineBranch */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x17d7 /* KAknsIIDQgnIndiHlLineBranch */}, + /* SP_QgnIndiHlLineEnd */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x17d8 /* KAknsIIDQgnIndiHlLineEnd */}, + /* SP_QgnIndiHlLineStraight */ {KAknsIIDNone, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x17d9 /* KAknsIIDQgnIndiHlLineStraight */}, + /* SP_QgnIndiMarkedAdd */ {KAknsIIDQgnIndiMarkedAdd, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnIndiNaviArrowLeft */ {KAknsIIDQgnIndiNaviArrowLeft, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnIndiNaviArrowRight */ {KAknsIIDQgnIndiNaviArrowRight, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnIndiRadiobuttOff */ {KAknsIIDQgnIndiRadiobuttOff, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnIndiRadiobuttOn */ {KAknsIIDQgnIndiRadiobuttOn, EDrawIcon, ES60_All, -1,-1}, + + // In 3.1 there different slider graphic and no pressed state. + /* SP_QgnGrafNsliderMarker */ {KAknsIIDQgnIndiSliderEdit, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x19d1 /* KAknsIIDQgnGrafNsliderMarker */}, + /* SP_QgnGrafNsliderMarkerSelected */ {KAknsIIDQgnIndiSliderEdit, EDrawIcon, ES60_3_1, EAknsMajorGeneric, 0x1a4a /* KAknsIIDQgnGrafNsliderMarkerSelected */}, + /* SP_QgnIndiSubmenu */ {KAknsIIDQgnIndiSubmenu, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteErased */ {KAknsIIDQgnNoteErased, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteError */ {KAknsIIDQgnNoteError, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteInfo */ {KAknsIIDQgnNoteInfo, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteOk */ {KAknsIIDQgnNoteOk, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteQuery */ {KAknsIIDQgnNoteQuery, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnNoteWarning */ {KAknsIIDQgnNoteWarning, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnPropFileSmall */ {KAknsIIDQgnPropFileSmall, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnPropFolderCurrent */ {KAknsIIDQgnPropFolderCurrent, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnPropFolderSmall */ {KAknsIIDQgnPropFolderSmall, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnPropFolderSmallNew */ {KAknsIIDQgnPropFolderSmallNew, EDrawIcon, ES60_All, -1,-1}, + /* SP_QgnPropPhoneMemcLarge */ {KAknsIIDQgnPropPhoneMemcLarge, EDrawIcon, ES60_All, -1,-1}, + + // Toolbar graphics is different in 3.1/3.2 vs. 5.0 + /* SP_QgnFrSctrlButtonCornerTl */ {KAknsIIDQsnFrButtonTbCornerTl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2301}, /* KAknsIIDQgnFrSctrlButtonCornerTl*/ + /* SP_QgnFrSctrlButtonCornerTr */ {KAknsIIDQsnFrButtonTbCornerTr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2302}, + /* SP_QgnFrSctrlButtonCornerBl */ {KAknsIIDQsnFrButtonTbCornerBl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2303}, + /* SP_QgnFrSctrlButtonCornerBr */ {KAknsIIDQsnFrButtonTbCornerBr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2304}, + /* SP_QgnFrSctrlButtonSideT */ {KAknsIIDQsnFrButtonTbSideT, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2305}, + /* SP_QgnFrSctrlButtonSideB */ {KAknsIIDQsnFrButtonTbSideB, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2306}, + /* SP_QgnFrSctrlButtonSideL */ {KAknsIIDQsnFrButtonTbSideL, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2307}, + /* SP_QgnFrSctrlButtonSideR */ {KAknsIIDQsnFrButtonTbSideR, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2308}, + /* SP_QgnFrSctrlButtonCenter */ {KAknsIIDQsnFrButtonTbCenter, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2309}, /*KAknsIIDQgnFrSctrlButtonCenter*/ + + // No pressed state for toolbar button in 3.1/3.2. + /* SP_QgnFrSctrlButtonCornerTlPressed */ {KAknsIIDQsnFrButtonTbCornerTl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2621}, /*KAknsIIDQgnFrSctrlButtonCornerTlPressed*/ + /* SP_QgnFrSctrlButtonCornerTrPressed */ {KAknsIIDQsnFrButtonTbCornerTr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2622}, + /* SP_QgnFrSctrlButtonCornerBlPressed */ {KAknsIIDQsnFrButtonTbCornerBl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2623}, + /* SP_QgnFrSctrlButtonCornerBrPressed */ {KAknsIIDQsnFrButtonTbCornerBr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2624}, + /* SP_QgnFrSctrlButtonSideTPressed */ {KAknsIIDQsnFrButtonTbSideT, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2625}, + /* SP_QgnFrSctrlButtonSideBPressed */ {KAknsIIDQsnFrButtonTbSideB, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2626}, + /* SP_QgnFrSctrlButtonSideLPressed */ {KAknsIIDQsnFrButtonTbSideL, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2627}, + /* SP_QgnFrSctrlButtonSideRPressed */ {KAknsIIDQsnFrButtonTbSideR, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2628}, + /* SP_QgnFrSctrlButtonCenterPressed */ {KAknsIIDQsnFrButtonTbCenter, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2629}, + + // 3.1 & 3.2 do not have pressed state for scrollbar, so use normal scrollbar graphics instead. + /* SP_QsnCpScrollHandleBottomPressed*/ {KAknsIIDQsnCpScrollHandleBottom, EDrawIcon, ES60_3_X, EAknsMajorGeneric, 0x20f8}, /*KAknsIIDQsnCpScrollHandleBottomPressed*/ + /* SP_QsnCpScrollHandleMiddlePressed*/ {KAknsIIDQsnCpScrollHandleMiddle, EDrawIcon, ES60_3_X, EAknsMajorGeneric, 0x20f9}, /*KAknsIIDQsnCpScrollHandleMiddlePressed*/ + /* SP_QsnCpScrollHandleTopPressed*/ {KAknsIIDQsnCpScrollHandleTop, EDrawIcon, ES60_3_X, EAknsMajorGeneric, 0x20fa}, /*KAknsIIDQsnCpScrollHandleTopPressed*/ + + /* SP_QsnBgScreen */ {KAknsIIDQsnBgScreen, EDrawBackground, ES60_All, -1,-1}, + + /* SP_QsnCpScrollBgBottom */ {KAknsIIDQsnCpScrollBgBottom, EDrawIcon, ES60_All, -1,-1}, + /* SP_QsnCpScrollBgMiddle */ {KAknsIIDQsnCpScrollBgMiddle, EDrawIcon, ES60_All, -1,-1}, + /* SP_QsnCpScrollBgTop */ {KAknsIIDQsnCpScrollBgTop, EDrawIcon, ES60_All, -1,-1}, + + /* SP_QsnCpScrollHandleBottom */ {KAknsIIDQsnCpScrollHandleBottom, EDrawIcon, ES60_All, -1,-1}, + /* SP_QsnCpScrollHandleMiddle */ {KAknsIIDQsnCpScrollHandleMiddle, EDrawIcon, ES60_All, -1,-1}, + /* SP_QsnCpScrollHandleTop */ {KAknsIIDQsnCpScrollHandleTop, EDrawIcon, ES60_All, -1,-1}, + + /* SP_QsnFrButtonTbCornerTl */ {KAknsIIDQsnFrButtonTbCornerTl, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbCornerTr */ {KAknsIIDQsnFrButtonTbCornerTr, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbCornerBl */ {KAknsIIDQsnFrButtonTbCornerBl, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbCornerBr */ {KAknsIIDQsnFrButtonTbCornerBr, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbSideT */ {KAknsIIDQsnFrButtonTbSideT, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbSideB */ {KAknsIIDQsnFrButtonTbSideB, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbSideL */ {KAknsIIDQsnFrButtonTbSideL, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbSideR */ {KAknsIIDQsnFrButtonTbSideR, ENoDraw, ES60_All, -1, -1}, + /* SP_QsnFrButtonTbCenter */ {KAknsIIDQsnFrButtonTbCenter, EDrawIcon, ES60_All, -1, -1}, + + /* SP_QsnFrButtonTbCornerTlPressed */{KAknsIIDQsnFrButtonTbCornerTlPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbCornerTrPressed */{KAknsIIDQsnFrButtonTbCornerTrPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbCornerBlPressed */{KAknsIIDQsnFrButtonTbCornerBlPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbCornerBrPressed */{KAknsIIDQsnFrButtonTbCornerBrPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbSideTPressed */ {KAknsIIDQsnFrButtonTbSideTPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbSideBPressed */ {KAknsIIDQsnFrButtonTbSideBPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbSideLPressed */ {KAknsIIDQsnFrButtonTbSideLPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbSideRPressed */ {KAknsIIDQsnFrButtonTbSideRPressed, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrButtonTbCenterPressed */ {KAknsIIDQsnFrButtonTbCenterPressed, EDrawIcon, ES60_All, -1,-1}, + + /* SP_QsnFrCaleCornerTl */ {KAknsIIDQsnFrCaleCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleCornerTr */ {KAknsIIDQsnFrCaleCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleCornerBl */ {KAknsIIDQsnFrCaleCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleCornerBr */ {KAknsIIDQsnFrCaleCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleSideT */ {KAknsIIDQsnFrCaleSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleSideB */ {KAknsIIDQsnFrCaleSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleSideL */ {KAknsIIDQsnFrCaleSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleSideR */ {KAknsIIDQsnFrCaleSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleCenter */ {KAknsIIDQsnFrCaleCenter, ENoDraw, ES60_All, -1,-1}, + + /* SP_QsnFrCaleHeadingCornerTl */ {KAknsIIDQsnFrCaleHeadingCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingCornerTr */ {KAknsIIDQsnFrCaleHeadingCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingCornerBl */ {KAknsIIDQsnFrCaleHeadingCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingCornerBr */ {KAknsIIDQsnFrCaleHeadingCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingSideT */ {KAknsIIDQsnFrCaleHeadingSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingSideB */ {KAknsIIDQsnFrCaleHeadingSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingSideL */ {KAknsIIDQsnFrCaleHeadingSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingSideR */ {KAknsIIDQsnFrCaleHeadingSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrCaleHeadingCenter */ {KAknsIIDQsnFrCaleHeadingCenter, ENoDraw, ES60_All, -1,-1}, + + /* SP_QsnFrInputCornerTl */ {KAknsIIDQsnFrInputCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputCornerTr */ {KAknsIIDQsnFrInputCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputCornerBl */ {KAknsIIDQsnFrInputCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputCornerBr */ {KAknsIIDQsnFrInputCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputSideT */ {KAknsIIDQsnFrInputSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputSideB */ {KAknsIIDQsnFrInputSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputSideL */ {KAknsIIDQsnFrInputSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputSideR */ {KAknsIIDQsnFrInputSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrInputCenter */ {KAknsIIDQsnFrInputCenter, ENoDraw, ES60_All, -1,-1}, + + /* SP_QsnFrListCornerTl */ {KAknsIIDQsnFrListCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListCornerTr */ {KAknsIIDQsnFrListCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListCornerBl */ {KAknsIIDQsnFrListCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListCornerBr */ {KAknsIIDQsnFrListCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListSideT */ {KAknsIIDQsnFrListSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListSideB */ {KAknsIIDQsnFrListSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListSideL */ {KAknsIIDQsnFrListSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListSideR */ {KAknsIIDQsnFrListSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrListCenter */ {KAknsIIDQsnFrListCenter, ENoDraw, ES60_All, -1,-1}, + + /* SP_QsnFrPopupCornerTl */ {KAknsIIDQsnFrPopupCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupCornerTr */ {KAknsIIDQsnFrPopupCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupCornerBl */ {KAknsIIDQsnFrPopupCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupCornerBr */ {KAknsIIDQsnFrPopupCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupSideT */ {KAknsIIDQsnFrPopupSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupSideB */ {KAknsIIDQsnFrPopupSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupSideL */ {KAknsIIDQsnFrPopupSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupSideR */ {KAknsIIDQsnFrPopupSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrPopupCenter */ {KAknsIIDQsnFrPopupCenterSubmenu, ENoDraw, ES60_All, -1,-1}, + + // ToolTip graphics different in 3.1 vs. 3.2+. + /* SP_QsnFrPopupPreviewCornerTl */ {KAknsIIDQsnFrPopupCornerTl, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c5}, /* KAknsIIDQsnFrPopupPreviewCornerTl */ + /* SP_QsnFrPopupPreviewCornerTr */ {KAknsIIDQsnFrPopupCornerTr, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c6}, + /* SP_QsnFrPopupPreviewCornerBl */ {KAknsIIDQsnFrPopupCornerBl, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c3}, + /* SP_QsnFrPopupPreviewCornerBr */ {KAknsIIDQsnFrPopupCornerBr, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c4}, + /* SP_QsnFrPopupPreviewSideT */ {KAknsIIDQsnFrPopupSideT, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19ca}, + /* SP_QsnFrPopupPreviewSideB */ {KAknsIIDQsnFrPopupSideB, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c7}, + /* SP_QsnFrPopupPreviewSideL */ {KAknsIIDQsnFrPopupSideL, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c8}, + /* SP_QsnFrPopupPreviewSideR */ {KAknsIIDQsnFrPopupSideR, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c9}, + /* SP_QsnFrPopupPreviewCenter */ {KAknsIIDQsnFrPopupCenter, ENoDraw, ES60_3_1, EAknsMajorSkin, 0x19c2}, + + /* SP_QsnFrSetOptCornerTl */ {KAknsIIDQsnFrSetOptCornerTl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptCornerTr */ {KAknsIIDQsnFrSetOptCornerTr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptCornerBl */ {KAknsIIDQsnFrSetOptCornerBl, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptCornerBr */ {KAknsIIDQsnFrSetOptCornerBr, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptSideT */ {KAknsIIDQsnFrSetOptSideT, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptSideB */ {KAknsIIDQsnFrSetOptSideB, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptSideL */ {KAknsIIDQsnFrSetOptSideL, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptSideR */ {KAknsIIDQsnFrSetOptSideR, ENoDraw, ES60_All, -1,-1}, + /* SP_QsnFrSetOptCenter */ {KAknsIIDQsnFrSetOptCenter, ENoDraw, ES60_All, -1,-1}, + + // No toolbar frame for 5.0+ releases. + /* SP_QsnFrPopupSubCornerTl */ {KAknsIIDQsnFrPopupSubCornerTl, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubCornerTr */ {KAknsIIDQsnFrPopupSubCornerTr, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubCornerBl */ {KAknsIIDQsnFrPopupSubCornerBl, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubCornerBr */ {KAknsIIDQsnFrPopupSubCornerBr, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubSideT */ {KAknsIIDQsnFrPopupSubSideT, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubSideB */ {KAknsIIDQsnFrPopupSubSideB, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubSideL */ {KAknsIIDQsnFrPopupSubSideL, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubSideR */ {KAknsIIDQsnFrPopupSubSideR, ENoDraw, ES60_3_X, -1,-1}, + /* SP_QsnFrPopupSubCenter */ {KAknsIIDQsnFrPopupCenterSubmenu, ENoDraw, ES60_3_X, -1,-1}, + + // No inactive button graphics in 3.1/3.2 + /* SP_QsnFrButtonCornerTlInactive */ {KAknsIIDQsnFrButtonTbCornerTl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b1}, /*KAknsIIDQsnFrButtonCornerTlInactive*/ + /* SP_QsnFrButtonCornerTrInactive */ {KAknsIIDQsnFrButtonTbCornerTr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b2}, + /* SP_QsnFrButtonCornerBlInactive */ {KAknsIIDQsnFrButtonTbCornerBl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b3}, + /* SP_QsnFrButtonCornerTrInactive */ {KAknsIIDQsnFrButtonTbCornerBr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b4}, + /* SP_QsnFrButtonSideTInactive */ {KAknsIIDQsnFrButtonTbSideT, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b5}, + /* SP_QsnFrButtonSideBInactive */ {KAknsIIDQsnFrButtonTbSideB, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b6}, + /* SP_QsnFrButtonSideLInactive */ {KAknsIIDQsnFrButtonTbSideL, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b7}, + /* SP_QsnFrButtonSideRInactive */ {KAknsIIDQsnFrButtonTbSideR, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x21b8}, + /* SP_QsnFrButtonCenterInactive */ {KAknsIIDQsnFrButtonTbCenter, EDrawIcon, ES60_3_X, EAknsMajorSkin, 0x21b9}, + + // No pressed down grid in 3.1/3.2 + /* SP_QsnFrGridCornerTlPressed */ {KAknsIIDQsnFrGridCornerTl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2681}, /*KAknsIIDQsnFrGridCornerTlPressed*/ + /* SP_QsnFrGridCornerTrPressed */ {KAknsIIDQsnFrGridCornerTr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2682}, + /* SP_QsnFrGridCornerBlPressed */ {KAknsIIDQsnFrGridCornerBl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2683}, + /* SP_QsnFrGridCornerBrPressed */ {KAknsIIDQsnFrGridCornerBr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2684}, + /* SP_QsnFrGridSideTPressed */ {KAknsIIDQsnFrGridSideT, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2685}, + /* SP_QsnFrGridSideBPressed */ {KAknsIIDQsnFrGridSideB, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2686}, + /* SP_QsnFrGridSideLPressed */ {KAknsIIDQsnFrGridSideL, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2687}, + /* SP_QsnFrGridSideRPressed */ {KAknsIIDQsnFrGridSideR, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2688}, + /* SP_QsnFrGridCenterPressed */ {KAknsIIDQsnFrGridCenter, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2689}, + + // No pressed down list in 3.1/3.2 + /* SP_QsnFrListCornerTlPressed */ {KAknsIIDQsnFrListCornerTl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x268b}, /*KAknsIIDQsnFrListCornerTlPressed*/ + /* SP_QsnFrListCornerTrPressed */ {KAknsIIDQsnFrListCornerTr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x268c}, + /* SP_QsnFrListCornerBlPressed */ {KAknsIIDQsnFrListCornerBl, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x268d}, + /* SP_QsnFrListCornerBrPressed */ {KAknsIIDQsnFrListCornerBr, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x268e}, + /* SP_QsnFrListSideTPressed */ {KAknsIIDQsnFrListSideT, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x268f}, + /* SP_QsnFrListSideBPressed */ {KAknsIIDQsnFrListSideB, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2690}, + /* SP_QsnFrListSideLPressed */ {KAknsIIDQsnFrListSideL, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2691}, + /* SP_QsnFrListSideRPressed */ {KAknsIIDQsnFrListSideR, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2692}, + /* SP_QsnFrListCenterPressed */ {KAknsIIDQsnFrListCenter, ENoDraw, ES60_3_X, EAknsMajorSkin, 0x2693}, +}; + +QPixmap QS60StyleModeSpecifics::skinnedGraphics( + QS60StyleEnums::SkinParts stylepart, const QSize &size, + QS60StylePrivate::SkinElementFlags flags) +{ + QPixmap themedImage; + TRAPD( error, QT_TRYCATCH_LEAVING({ + const QPixmap skinnedImage = createSkinnedGraphicsLX(stylepart, size, flags); + themedImage = skinnedImage; + })); + if (error) + return themedImage = QPixmap(); + return themedImage; +} + +QPixmap QS60StyleModeSpecifics::skinnedGraphics( + QS60StylePrivate::SkinFrameElements frame, const QSize &size, QS60StylePrivate::SkinElementFlags flags) +{ + QPixmap themedImage; + TRAPD( error, QT_TRYCATCH_LEAVING({ + const QPixmap skinnedImage = createSkinnedGraphicsLX(frame, size, flags); + themedImage = skinnedImage; + })); + if (error) + return themedImage = QPixmap(); + return themedImage; +} + +QPixmap QS60StyleModeSpecifics::colorSkinnedGraphics( + const QS60StyleEnums::SkinParts &stylepart, const QSize &size, QPainter *painter, + QS60StylePrivate::SkinElementFlags flags) +{ + QPixmap colorGraphics; + TRAPD(error, QT_TRYCATCH_LEAVING(colorGraphics = colorSkinnedGraphicsLX(stylepart, size, painter, flags))); + return error ? QPixmap() : colorGraphics; +} + +void QS60StyleModeSpecifics::fallbackInfo(const QS60StyleEnums::SkinParts &stylePart, TInt &fallbackIndex) +{ + switch(stylePart) { + case QS60StyleEnums::SP_QgnGrafBarWaitAnim: + fallbackIndex = EMbmAvkonQgn_graf_bar_wait_1; + break; + case QS60StyleEnums::SP_QgnGrafBarFrameCenter: + fallbackIndex = EMbmAvkonQgn_graf_bar_frame_center; + break; + case QS60StyleEnums::SP_QgnGrafBarFrameSideL: + fallbackIndex = EMbmAvkonQgn_graf_bar_frame_side_l; + break; + case QS60StyleEnums::SP_QgnGrafBarFrameSideR: + fallbackIndex = EMbmAvkonQgn_graf_bar_frame_side_r; + break; + case QS60StyleEnums::SP_QgnGrafBarProgress: + fallbackIndex = EMbmAvkonQgn_graf_bar_progress; + break; + case QS60StyleEnums::SP_QgnGrafTabActiveL: + fallbackIndex = EMbmAvkonQgn_graf_tab_active_l; + break; + case QS60StyleEnums::SP_QgnGrafTabActiveM: + fallbackIndex = EMbmAvkonQgn_graf_tab_active_m; + break; + case QS60StyleEnums::SP_QgnGrafTabActiveR: + fallbackIndex = EMbmAvkonQgn_graf_tab_active_r; + break; + case QS60StyleEnums::SP_QgnGrafTabPassiveL: + fallbackIndex = EMbmAvkonQgn_graf_tab_passive_l; + break; + case QS60StyleEnums::SP_QgnGrafTabPassiveM: + fallbackIndex = EMbmAvkonQgn_graf_tab_passive_m; + break; + case QS60StyleEnums::SP_QgnGrafTabPassiveR: + fallbackIndex = EMbmAvkonQgn_graf_tab_passive_r; + break; + case QS60StyleEnums::SP_QgnIndiCheckboxOff: + fallbackIndex = EMbmAvkonQgn_indi_checkbox_off; + break; + case QS60StyleEnums::SP_QgnIndiCheckboxOn: + fallbackIndex = EMbmAvkonQgn_indi_checkbox_on; + break; + case QS60StyleEnums::SP_QgnIndiHlColSuper: + fallbackIndex = 0x4456; /* EMbmAvkonQgn_indi_hl_col_super */ + break; + case QS60StyleEnums::SP_QgnIndiHlExpSuper: + fallbackIndex = 0x4458; /* EMbmAvkonQgn_indi_hl_exp_super */ + break; + case QS60StyleEnums::SP_QgnIndiHlLineBranch: + fallbackIndex = 0x445A; /* EMbmAvkonQgn_indi_hl_line_branch */ + break; + case QS60StyleEnums::SP_QgnIndiHlLineEnd: + fallbackIndex = 0x445C; /* EMbmAvkonQgn_indi_hl_line_end */ + break; + case QS60StyleEnums::SP_QgnIndiHlLineStraight: + fallbackIndex = 0x445E; /* EMbmAvkonQgn_indi_hl_line_straight */ + break; + case QS60StyleEnums::SP_QgnIndiMarkedAdd: + fallbackIndex = EMbmAvkonQgn_indi_marked_add; + break; + case QS60StyleEnums::SP_QgnIndiNaviArrowLeft: + fallbackIndex = EMbmAvkonQgn_indi_navi_arrow_left; + break; + case QS60StyleEnums::SP_QgnIndiNaviArrowRight: + fallbackIndex = EMbmAvkonQgn_indi_navi_arrow_right; + break; + case QS60StyleEnums::SP_QgnIndiRadiobuttOff: + fallbackIndex = EMbmAvkonQgn_indi_radiobutt_off; + break; + case QS60StyleEnums::SP_QgnIndiRadiobuttOn: + fallbackIndex = EMbmAvkonQgn_indi_radiobutt_on; + break; + case QS60StyleEnums::SP_QgnGrafNsliderMarker: + fallbackIndex = 17572; /* EMbmAvkonQgn_graf_nslider_marker */ + break; + case QS60StyleEnums::SP_QgnGrafNsliderMarkerSelected: + fallbackIndex = 17574; /* EMbmAvkonQgn_graf_nslider_marker_selected */ + break; + case QS60StyleEnums::SP_QgnIndiSubmenu: + fallbackIndex = EMbmAvkonQgn_indi_submenu; + break; + case QS60StyleEnums::SP_QgnNoteErased: + fallbackIndex = EMbmAvkonQgn_note_erased; + break; + case QS60StyleEnums::SP_QgnNoteError: + fallbackIndex = EMbmAvkonQgn_note_error; + break; + case QS60StyleEnums::SP_QgnNoteInfo: + fallbackIndex = EMbmAvkonQgn_note_info; + break; + case QS60StyleEnums::SP_QgnNoteOk: + fallbackIndex = EMbmAvkonQgn_note_ok; + break; + case QS60StyleEnums::SP_QgnNoteQuery: + fallbackIndex = EMbmAvkonQgn_note_query; + break; + case QS60StyleEnums::SP_QgnNoteWarning: + fallbackIndex = EMbmAvkonQgn_note_warning; + break; + case QS60StyleEnums::SP_QgnPropFileSmall: + fallbackIndex = EMbmAvkonQgn_prop_file_small; + break; + case QS60StyleEnums::SP_QgnPropFolderCurrent: + fallbackIndex = EMbmAvkonQgn_prop_folder_current; + break; + case QS60StyleEnums::SP_QgnPropFolderSmall: + fallbackIndex = EMbmAvkonQgn_prop_folder_small; + break; + case QS60StyleEnums::SP_QgnPropFolderSmallNew: + fallbackIndex = EMbmAvkonQgn_prop_folder_small_new; + break; + case QS60StyleEnums::SP_QgnPropPhoneMemcLarge: + fallbackIndex = EMbmAvkonQgn_prop_phone_memc_large; + break; + default: + fallbackIndex = -1; + break; + } +} + +QPixmap QS60StyleModeSpecifics::colorSkinnedGraphicsLX( + const QS60StyleEnums::SkinParts &stylepart, + const QSize &size, QPainter *painter, QS60StylePrivate::SkinElementFlags flags) +{ + // this function can throw both exceptions and leaves. There are no cleanup dependencies between Qt and Symbian parts. + const int stylepartIndex = (int)stylepart; + const TAknsItemID skinId = m_partMap[stylepartIndex].skinID; + + TInt fallbackGraphicID = -1; + HBufC* iconFile = HBufC::NewLC( KMaxFileName ); + fallbackInfo(stylepart, fallbackGraphicID); + + TAknsItemID colorGroup = KAknsIIDQsnIconColors; + TRgb defaultColor = KRgbBlack; + int colorIndex = -1; //set a bogus value to color index to ensure that painter color is used + //to color the icon + if (painter) { + QRgb widgetColor = painter->pen().color().rgb(); + defaultColor = TRgb(qRed(widgetColor), qGreen(widgetColor), qBlue(widgetColor)); + } + + const bool rotatedBy90or270 = + (flags & (QS60StylePrivate::SF_PointEast | QS60StylePrivate::SF_PointWest)); + const TSize targetSize = + rotatedBy90or270?TSize(size.height(), size.width()):TSize(size.width(), size.height()); + CFbsBitmap *icon = 0; + CFbsBitmap *iconMask = 0; + const TInt fallbackGraphicsMaskID = + fallbackGraphicID == KErrNotFound?KErrNotFound:fallbackGraphicID+1; //masks are auto-generated as next in mif files + MAknsSkinInstance* skinInstance = AknsUtils::SkinInstance(); + AknsUtils::CreateColorIconLC( + skinInstance, + skinId, + colorGroup, + colorIndex, + icon, + iconMask, + AknIconUtils::AvkonIconFileName(), + fallbackGraphicID, + fallbackGraphicsMaskID, + defaultColor); + + QPixmap result = fromFbsBitmap(icon, iconMask, flags, targetSize); + CleanupStack::PopAndDestroy(3); //icon, iconMask, iconFile + return result; +} + +QColor QS60StyleModeSpecifics::colorValue(const TAknsItemID &colorGroup, int colorIndex) +{ + TRgb skinnedColor; + MAknsSkinInstance* skin = AknsUtils::SkinInstance(); + AknsUtils::GetCachedColor(skin, skinnedColor, colorGroup, colorIndex); + return QColor(skinnedColor.Red(),skinnedColor.Green(),skinnedColor.Blue()); +} + +struct QAutoFbsBitmapHeapLock +{ + QAutoFbsBitmapHeapLock(CFbsBitmap* aBmp) : mBmp(aBmp) { mBmp->LockHeap(); } + ~QAutoFbsBitmapHeapLock() { mBmp->UnlockHeap(); } + CFbsBitmap* mBmp; +}; + +QPixmap QS60StyleModeSpecifics::fromFbsBitmap(CFbsBitmap *icon, CFbsBitmap *mask, QS60StylePrivate::SkinElementFlags flags, const TSize &targetSize) +{ + Q_ASSERT(icon); + + AknIconUtils::DisableCompression(icon); + TInt error = AknIconUtils::SetSize(icon, targetSize, EAspectRatioNotPreserved); + + if (mask && !error) { + AknIconUtils::DisableCompression(mask); + error = AknIconUtils::SetSize(mask, targetSize, EAspectRatioNotPreserved); + } + if (error) + return QPixmap(); + + QPixmap pixmap; + QScopedPointer<QPixmapData> pd(QPixmapData::create(0, 0, QPixmapData::PixmapType)); + if (mask) { + // Try the efficient path with less copying and conversion. + QVolatileImage img(icon, mask); + pd->fromNativeType(&img, QPixmapData::VolatileImage); + if (!pd->isNull()) + pixmap = QPixmap(pd.take()); + } + if (pixmap.isNull()) { + // Potentially more expensive path. + pd->fromNativeType(icon, QPixmapData::FbsBitmap); + pixmap = QPixmap(pd.take()); + if (mask) { + pixmap.setAlphaChannel(QPixmap::fromSymbianCFbsBitmap(mask)); + } + } + + if ((flags & QS60StylePrivate::SF_PointEast) || + (flags & QS60StylePrivate::SF_PointSouth) || + (flags & QS60StylePrivate::SF_PointWest)) { + QImage iconImage = pixmap.toImage(); + QTransform imageTransform; + if (flags & QS60StylePrivate::SF_PointEast) { + imageTransform.rotate(90); + } else if (flags & QS60StylePrivate::SF_PointSouth) { + imageTransform.rotate(180); + iconImage = iconImage.transformed(imageTransform); + } else if (flags & QS60StylePrivate::SF_PointWest) { + imageTransform.rotate(270); + } + if (imageTransform.isRotating()) + iconImage = iconImage.transformed(imageTransform); + + pixmap = QPixmap::fromImage(iconImage); + } + if ((flags & QS60StylePrivate::SF_Mirrored_X_Axis) || + (flags & QS60StylePrivate::SF_Mirrored_Y_Axis)) { + QImage iconImage = pixmap.toImage().mirrored( + flags & QS60StylePrivate::SF_Mirrored_X_Axis, + flags & QS60StylePrivate::SF_Mirrored_Y_Axis); + pixmap = QPixmap::fromImage(iconImage); + } + + return pixmap; +} + +bool QS60StylePrivate::isTouchSupported() +{ + return bool(AknLayoutUtils::PenEnabled()); +} + +bool QS60StylePrivate::isToolBarBackground() +{ + return (QSysInfo::s60Version() == QSysInfo::SV_S60_3_1 || QSysInfo::s60Version() == QSysInfo::SV_S60_3_2); +} + +bool QS60StylePrivate::hasSliderGrooveGraphic() +{ + return QSysInfo::s60Version() != QSysInfo::SV_S60_3_1; +} + +bool QS60StylePrivate::isSingleClickUi() +{ + return (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0); +} + +void QS60StylePrivate::deleteStoredSettings() +{ + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("QS60Style")); + settings.remove(QString()); + settings.endGroup(); +} + +// Since S60Style has 'button' as a graphic, we don't have any native color which to use +// for QPalette::Button. Therefore S60Style needs to guesstimate palette color by calculating +// average rgb values for button pixels. +// Returns Qt::black if there is an issue with the graphics (image is NULL, or no constBits() found). +QColor QS60StylePrivate::colorFromFrameGraphics(SkinFrameElements frame) const +{ +#ifndef QT_NO_SETTINGS + TInt themeID = 0; + //First we need to fetch active theme ID. We need to store the themeID at the same time + //as color, so that we can later check if the stored color is still from the same theme. + //Native side stores active theme UID/Timestamp into central repository. + int error = 0; + QT_TRAP_THROWING( + CRepository *themeRepository = CRepository::NewLC(personalisationUID); + if (themeRepository) { + TBuf<32> value; //themeID is currently max of 8 + 1 + 8 characters, but lets have some extra space + const TUint32 key = 0x00000002; //active theme key in the repository + error = themeRepository->Get(key, value); + if (error == KErrNone) { + TLex lex(value); + TPtrC numberToken(lex.NextToken()); + if (numberToken.Length()) + error = TLex(numberToken).Val(themeID); + else + error = KErrArgument; + } + } + CleanupStack::PopAndDestroy(themeRepository); + ); + + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("QS60Style")); + if (themeID != 0) { + QVariant buttonColor = settings.value(QLatin1String("ButtonColor")); + if (!buttonColor.isNull()) { + //there is a stored color value, lets see if the theme ID matches + if (error == KErrNone) { + QVariant themeUID = settings.value(QLatin1String("ThemeUID")); + if (!themeUID.isNull() && themeUID.toInt() == themeID) { + QColor storedColor(buttonColor.value<QColor>()); + if (storedColor.isValid()) + return storedColor; + } + } + settings.remove(QString()); //if color was invalid, or theme has been changed, just delete all stored settings + } + } +#endif + + QColor color = calculatedColor(frame); + +#ifndef QT_NO_SETTINGS + settings.setValue(QLatin1String("ThemeUID"), QVariant(themeID)); + if (frame == SF_ButtonNormal) //other colors are not currently calculated from graphics + settings.setValue(QLatin1String("ButtonColor"), QVariant(color)); + settings.endGroup(); +#endif + + return color; +} + +QPoint qt_s60_fill_background_offset(const QWidget *targetWidget) +{ + CCoeControl *control = targetWidget->effectiveWinId(); + TPoint pos(0,0); + if (control) + pos = control->PositionRelativeToScreen(); + return QPoint(pos.iX, pos.iY); +} + +QPixmap QS60StyleModeSpecifics::createSkinnedGraphicsLX( + QS60StyleEnums::SkinParts part, const QSize &size, + QS60StylePrivate::SkinElementFlags flags) +{ + // this function can throw both exceptions and leaves. There are no cleanup dependencies between Qt and Symbian parts. + if (!size.isValid()) + return QPixmap(); + + // Check release support and change part, if necessary. + const TAknsItemID skinId = partSpecificThemeId((int)part); + const int stylepartIndex = (int)part; + const TDrawType drawType = m_partMap[stylepartIndex].drawType; + Q_ASSERT(drawType != ENoDraw); + const bool rotatedBy90or270 = + (flags & (QS60StylePrivate::SF_PointEast | QS60StylePrivate::SF_PointWest)); + const TSize targetSize = + rotatedBy90or270 ? TSize(size.height(), size.width()) : qt_QSize2TSize(size); + + MAknsSkinInstance* skinInstance = AknsUtils::SkinInstance(); + static const TDisplayMode displayMode = S60->supportsPremultipliedAlpha ? Q_SYMBIAN_ECOLOR16MAP : EColor16MA; + static const TInt drawParam = S60->supportsPremultipliedAlpha ? KAknsDrawParamDefault : KAknsDrawParamRGBOnly; + + QPixmap result; + + switch (drawType) { + case EDrawGulIcon: { + CGulIcon* icon = AknsUtils::CreateGulIconL( AknsUtils::SkinInstance(), skinId, EFalse ); + if (icon) + result = fromFbsBitmap(icon->Bitmap(), icon->Mask(), flags, targetSize); + delete icon; + break; + } + case EDrawIcon: { + TInt fallbackGraphicID = -1; + fallbackInfo(part, fallbackGraphicID); + + CFbsBitmap *icon = 0; + CFbsBitmap *iconMask = 0; + const TInt fallbackGraphicsMaskID = + fallbackGraphicID == KErrNotFound?KErrNotFound:fallbackGraphicID+1; //masks are auto-generated as next in mif files + + AknsUtils::CreateIconL( + skinInstance, + skinId, + icon, + iconMask, + AknIconUtils::AvkonIconFileName(), + fallbackGraphicID , + fallbackGraphicsMaskID); + + result = fromFbsBitmap(icon, iconMask, flags, targetSize); + delete icon; + delete iconMask; + break; + } + case EDrawBackground: { + // QS60WindowSurface::unlockBitmapHeap(); + CFbsBitmap *background = new (ELeave) CFbsBitmap(); //offscreen + CleanupStack::PushL(background); + User::LeaveIfError(background->Create(targetSize, displayMode)); + + CFbsBitmapDevice *dev = CFbsBitmapDevice::NewL(background); + CleanupStack::PushL(dev); + CFbsBitGc *gc = NULL; + User::LeaveIfError(dev->CreateContext(gc)); + CleanupStack::PushL(gc); + + CAknsBasicBackgroundControlContext *bgContext = CAknsBasicBackgroundControlContext::NewL( + skinId, + targetSize, + EFalse); + CleanupStack::PushL(bgContext); + + const TBool drawn = AknsDrawUtils::DrawBackground( + skinInstance, + bgContext, + NULL, + *gc, + TPoint(), + targetSize, + drawParam); + + if (drawn) + result = fromFbsBitmap(background, NULL, flags, targetSize); + // if drawing fails in skin server, just ignore the background (probably OOM case) + + CleanupStack::PopAndDestroy(4, background); //background, dev, gc, bgContext + // QS60WindowSurface::lockBitmapHeap(); + break; + } + case EDrawAnimation: { + CFbsBitmap* animationFrame; + CFbsBitmap* frameMask; + CAknBitmapAnimation* aknAnimation = 0; + TBool constructedFromTheme = ETrue; + + QS60StyleAnimation* animation = QS60StylePrivate::animationDefinition(part); //ownership is not passed + if (animation) { + if (!animation->animationObject() && !animation->isResourceBased()) {// no pre-made item exists, create new animation + CAknBitmapAnimation* newAnimation = CAknBitmapAnimation::NewL(); + CleanupStack::PushL(newAnimation); + if (newAnimation) + constructedFromTheme = newAnimation->ConstructFromSkinL(skinId); + if (constructedFromTheme && newAnimation->BitmapAnimData()->FrameArray().Count() > 0) { + animation->setResourceBased(false); + animation->setAnimationObject(newAnimation); //animation takes ownership + } + CleanupStack::Pop(newAnimation); + } + //fill-in stored information + aknAnimation = animation->animationObject(); + constructedFromTheme = !animation->isResourceBased(); + } + + const int currentFrame = QS60StylePrivate::currentAnimationFrame(part); + if (constructedFromTheme && aknAnimation && aknAnimation->BitmapAnimData()->FrameArray().Count() > 0) { + //Animation was created successfully and contains frames, just fetch current frame + if(currentFrame >= aknAnimation->BitmapAnimData()->FrameArray().Count()) + User::Leave(KErrOverflow); + const CBitmapFrameData* frameData = aknAnimation->BitmapAnimData()->FrameArray().At(currentFrame); + if (frameData) { + animationFrame = frameData->Bitmap(); + frameMask = frameData->Mask(); + } + } else { + //Theme does not contain animation theming, create frames from resource file + TInt fallbackGraphicID = -1; + fallbackInfo(part, fallbackGraphicID); + fallbackGraphicID = fallbackGraphicID + (currentFrame * 2); //skip masks + TInt fallbackGraphicsMaskID = + (fallbackGraphicID == KErrNotFound) ? KErrNotFound : fallbackGraphicID + 1; //masks are auto-generated as next in mif files + if (fallbackGraphicsMaskID != KErrNotFound) + fallbackGraphicsMaskID = fallbackGraphicsMaskID + (currentFrame * 2); //skip actual graphics + + //Then draw animation frame + AknsUtils::CreateIconL( + skinInstance, + KAknsIIDDefault, //animation is not themed, lets force fallback graphics + animationFrame, + frameMask, + AknIconUtils::AvkonIconFileName(), + fallbackGraphicID , + fallbackGraphicsMaskID); + } + result = fromFbsBitmap(animationFrame, frameMask, flags, targetSize); + if (!constructedFromTheme) { + delete animationFrame; + animationFrame = 0; + delete frameMask; + frameMask = 0; + } + break; + } + } + if (!result) + result = QPixmap(); + + return result; +} + +QPixmap QS60StyleModeSpecifics::createSkinnedGraphicsLX(QS60StylePrivate::SkinFrameElements frameElement, + const QSize &size, QS60StylePrivate::SkinElementFlags flags) +{ + // this function can throw both exceptions and leaves. There are no cleanup dependencies between Qt and Symbian parts. + if (!size.isValid()) + return QPixmap(); + + const bool rotatedBy90or270 = + (flags & (QS60StylePrivate::SF_PointEast | QS60StylePrivate::SF_PointWest)); + const TSize targetSize = + rotatedBy90or270 ? TSize(size.height(), size.width()) : qt_QSize2TSize(size); + + MAknsSkinInstance* skinInstance = AknsUtils::SkinInstance(); + QPixmap result; + + static const TDisplayMode displayMode = S60->supportsPremultipliedAlpha ? Q_SYMBIAN_ECOLOR16MAP : EColor16MA; + static const TInt drawParam = S60->supportsPremultipliedAlpha ? KAknsDrawParamDefault : KAknsDrawParamNoClearUnderImage|KAknsDrawParamRGBOnly; + + CFbsBitmap *frame = new (ELeave) CFbsBitmap(); //offscreen + CleanupStack::PushL(frame); + User::LeaveIfError(frame->Create(targetSize, displayMode)); + + CFbsBitmapDevice* bitmapDev = CFbsBitmapDevice::NewL(frame); + CleanupStack::PushL(bitmapDev); + CFbsBitGc* bitmapGc = NULL; + User::LeaveIfError(bitmapDev->CreateContext(bitmapGc)); + CleanupStack::PushL(bitmapGc); + + frame->LockHeap(); + memset(frame->DataAddress(), 0, frame->SizeInPixels().iWidth * frame->SizeInPixels().iHeight * 4); // 4: argb bytes + frame->UnlockHeap(); + + const TRect outerRect(TPoint(0, 0), targetSize); + const TRect innerRect = innerRectFromElement(frameElement, outerRect); + + TAknsItemID frameSkinID, centerSkinID; + frameSkinID = centerSkinID = partSpecificThemeId(QS60StylePrivate::m_frameElementsData[frameElement].center); + frameIdAndCenterId(frameElement, frameSkinID, centerSkinID); + + TBool drawn = AknsDrawUtils::DrawFrame( + skinInstance, + *bitmapGc, + outerRect, + innerRect, + frameSkinID, + centerSkinID, + drawParam ); + + if (S60->supportsPremultipliedAlpha) { + if (drawn) { + result = fromFbsBitmap(frame, NULL, flags, targetSize); + } else { + // Drawing might fail due to OOM (we can do nothing about that), + // or due to skin item not being available. + // If the latter occurs, lets try switch to non-release specific items (if available) + // and re-try the drawing. + frameSkinID = centerSkinID = m_partMap[(int)QS60StylePrivate::m_frameElementsData[frameElement].center].skinID; + frameIdAndCenterId(frameElement, frameSkinID, centerSkinID); + drawn = AknsDrawUtils::DrawFrame( skinInstance, + *bitmapGc, outerRect, innerRect, + frameSkinID, centerSkinID, + drawParam ); + // in case drawing fails, even after using default graphics, ignore the error + if (drawn) + result = fromFbsBitmap(frame, NULL, flags, targetSize); + } + } else { + TDisplayMode maskDepth = EGray256; + // Query the skin item for possible frame graphics mask details. + if (skinInstance) { + CAknsMaskedBitmapItemData* skinMaskedBmp = static_cast<CAknsMaskedBitmapItemData*>( + skinInstance->GetCachedItemData(frameSkinID,EAknsITMaskedBitmap)); + if (skinMaskedBmp && skinMaskedBmp->Mask()) + maskDepth = skinMaskedBmp->Mask()->DisplayMode(); + } + if (maskDepth != ENone) { + CFbsBitmap *frameMask = new (ELeave) CFbsBitmap(); //offscreen + CleanupStack::PushL(frameMask); + User::LeaveIfError(frameMask->Create(targetSize, maskDepth)); + + CFbsBitmapDevice* maskBitmapDevice = CFbsBitmapDevice::NewL(frameMask); + CleanupStack::PushL(maskBitmapDevice); + CFbsBitGc* maskBitGc = NULL; + User::LeaveIfError(maskBitmapDevice->CreateContext(maskBitGc)); + CleanupStack::PushL(maskBitGc); + + if (drawn) { + //ensure that the mask is really transparent + maskBitGc->Activate( maskBitmapDevice ); + maskBitGc->SetPenStyle(CGraphicsContext::ENullPen); + maskBitGc->SetBrushStyle(CGraphicsContext::ESolidBrush); + maskBitGc->SetBrushColor(KRgbWhite); + maskBitGc->Clear(); + maskBitGc->SetBrushStyle(CGraphicsContext::ENullBrush); + + drawn = AknsDrawUtils::DrawFrame(skinInstance, + *maskBitGc, outerRect, innerRect, + frameSkinID, centerSkinID, + KAknsSDMAlphaOnly |KAknsDrawParamNoClearUnderImage); + if (drawn) + result = fromFbsBitmap(frame, frameMask, flags, targetSize); + } + CleanupStack::PopAndDestroy(3, frameMask); + } + } + CleanupStack::PopAndDestroy(3, frame); //frame, bitmapDev, bitmapGc + return result; +} + +void QS60StyleModeSpecifics::frameIdAndCenterId(QS60StylePrivate::SkinFrameElements frameElement, TAknsItemID &frameId, TAknsItemID ¢erId) +{ +// There are some major mix-ups in skin declarations for some frames. +// First, the frames are not declared in sequence. +// Second, the parts use different major than the frame-master. + + switch(frameElement) { + case QS60StylePrivate::SF_ToolTip: + if (QSysInfo::s60Version() != QSysInfo::SV_S60_3_1) { + centerId.Set(EAknsMajorGeneric, 0x19c2); + frameId.Set(EAknsMajorSkin, 0x5300); + } else { + centerId.Set(KAknsIIDQsnFrPopupCenter); + frameId.iMinor = centerId.iMinor - 9; + } + break; + case QS60StylePrivate::SF_ToolBar: + if (QSysInfo::s60Version() == QSysInfo::SV_S60_3_1 || + QSysInfo::s60Version() == QSysInfo::SV_S60_3_2) { + centerId.Set(KAknsIIDQsnFrPopupCenterSubmenu); + frameId.Set(KAknsIIDQsnFrPopupSub); + } + break; + case QS60StylePrivate::SF_PopupBackground: + centerId.Set(KAknsIIDQsnFrPopupCenterSubmenu); + frameId.Set(KAknsIIDQsnFrPopupSub); + break; + case QS60StylePrivate::SF_PanelBackground: + // remove center piece for panel graphics, so that only border is drawn + centerId.Set(KAknsIIDNone); + frameId.Set(KAknsIIDQsnFrSetOpt); + break; + default: + // center should be correct here + frameId.iMinor = centerId.iMinor - 9; + break; + } +} + +TRect QS60StyleModeSpecifics::innerRectFromElement(QS60StylePrivate::SkinFrameElements frameElement, const TRect &outerRect) +{ + TInt widthShrink = QS60StylePrivate::pixelMetric(PM_FrameCornerWidth); + TInt heightShrink = QS60StylePrivate::pixelMetric(PM_FrameCornerHeight); + switch(frameElement) { + case QS60StylePrivate::SF_PanelBackground: + // panel should have slightly slimmer border to enable thin line of background graphics between closest component + widthShrink = widthShrink - 2; + heightShrink = heightShrink - 2; + break; + case QS60StylePrivate::SF_ToolTip: + widthShrink = widthShrink >> 1; + heightShrink = heightShrink >> 1; + break; + case QS60StylePrivate::SF_ListHighlight: + //In Sym^3 devices highlights are less blocky + if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) { + widthShrink += 2; + heightShrink += 2; + } else { + widthShrink -= 2; + heightShrink -= 2; + } + break; + case QS60StylePrivate::SF_PopupBackground: + widthShrink = widthShrink + 5; + heightShrink = heightShrink + 5; + break; + default: + break; + } + TRect innerRect(outerRect); + innerRect.Shrink(widthShrink, heightShrink); + return innerRect; +} + +bool QS60StyleModeSpecifics::checkSupport(const int supportedRelease) +{ + const QSysInfo::S60Version currentRelease = QSysInfo::s60Version(); + return ( (currentRelease == QSysInfo::SV_S60_3_1 && supportedRelease & ES60_3_1) || + (currentRelease == QSysInfo::SV_S60_3_2 && supportedRelease & ES60_3_2) || + (currentRelease == QSysInfo::SV_S60_5_0 && supportedRelease & ES60_5_0) || + (currentRelease == QSysInfo::SV_S60_5_1 && supportedRelease & ES60_5_1) || + (currentRelease == QSysInfo::SV_S60_5_2 && supportedRelease & ES60_5_2) || + (currentRelease == QSysInfo::SV_S60_5_3 && supportedRelease & ES60_5_3) ); +} + +TAknsItemID QS60StyleModeSpecifics::partSpecificThemeId(int part) +{ + TAknsItemID newSkinId; + if (!checkSupport(m_partMap[(int)part].supportInfo)) + newSkinId.Set(m_partMap[(int)part].newMajorSkinId, m_partMap[(int)part].newMinorSkinId); + else + newSkinId.Set(m_partMap[(int)part].skinID); + return newSkinId; +} + +QFont QS60StylePrivate::s60Font_specific( + QS60StyleEnums::FontCategories fontCategory, + int pointSize, bool resolveFontSize) +{ + Q_UNUSED(resolveFontSize); + + TAknFontCategory aknFontCategory = EAknFontCategoryUndefined; + switch (fontCategory) { + case QS60StyleEnums::FC_Primary: + aknFontCategory = EAknFontCategoryPrimary; + break; + case QS60StyleEnums::FC_Secondary: + aknFontCategory = EAknFontCategorySecondary; + break; + case QS60StyleEnums::FC_Title: + aknFontCategory = EAknFontCategoryTitle; + break; + case QS60StyleEnums::FC_PrimarySmall: + aknFontCategory = EAknFontCategoryPrimarySmall; + break; + case QS60StyleEnums::FC_Digital: + aknFontCategory = EAknFontCategoryDigital; + break; + case QS60StyleEnums::FC_Undefined: + default: + break; + } + + // Create AVKON font according the given parameters + CWsScreenDevice* dev = CCoeEnv::Static()->ScreenDevice(); + TAknFontSpecification spec(aknFontCategory, TFontSpec(), NULL); + if (pointSize > 0) { + const TInt pixelSize = dev->VerticalTwipsToPixels(pointSize * KTwipsPerPoint); + spec.SetTextPaneHeight(pixelSize + 4); // TODO: Is 4 a reasonable top+bottom margin? + } + + QFont result; + TRAPD( error, QT_TRYCATCH_LEAVING({ + const CAknLayoutFont* aknFont = + AknFontAccess::CreateLayoutFontFromSpecificationL(*dev, spec); + + result = qt_TFontSpec2QFontL(aknFont->DoFontSpecInTwips()); + if (result.pointSize() != pointSize) + result.setPointSize(pointSize); // Correct the font size returned by CreateLayoutFontFromSpecificationL() + + delete aknFont; + })); + if (error) result = QFont(); + return result; +} + +void QS60StylePrivate::setActiveLayout() +{ + const QSize activeScreenSize(screenSize()); + int activeLayoutIndex = -1; + const short screenHeight = (short)activeScreenSize.height(); + const short screenWidth = (short)activeScreenSize.width(); + for (int i=0; i<m_numberOfLayouts; i++) { + if (screenHeight==m_layoutHeaders[i].height && + screenWidth==m_layoutHeaders[i].width) { + activeLayoutIndex = i; + break; + } + } + + //not found, lets try with either of dimensions + if (activeLayoutIndex==-1){ + const QSysInfo::S60Version currentRelease = QSysInfo::s60Version(); + const bool landscape = screenHeight < screenWidth; + + activeLayoutIndex = (currentRelease == QSysInfo::SV_S60_3_1 || currentRelease == QSysInfo::SV_S60_3_2) ? 0 : 2; + activeLayoutIndex += (!landscape) ? 1 : 0; + } + + setCurrentLayout(activeLayoutIndex); +} + +Q_GLOBAL_STATIC(QList<QS60StyleAnimation *>, m_animations) + +QS60StylePrivate::QS60StylePrivate() +{ + //Animation defaults need to be created when style is instantiated + QS60StyleAnimation* progressBarAnimation = new QS60StyleAnimation(QS60StyleEnums::SP_QgnGrafBarWaitAnim, 7, 100); + m_animations()->append(progressBarAnimation); + // No need to set active layout, if dynamic metrics API is available + setActiveLayout(); +} + +void QS60StylePrivate::removeAnimations() +{ + //currently only one animation in the list. + m_animations()->removeFirst(); +} + +QColor QS60StylePrivate::s60Color(QS60StyleEnums::ColorLists list, + int index, const QStyleOption *option) +{ + static const TAknsItemID *idMap[] = { + &KAknsIIDQsnHighlightColors, + &KAknsIIDQsnIconColors, + &KAknsIIDQsnLineColors, + &KAknsIIDQsnOtherColors, + &KAknsIIDQsnParentColors, + &KAknsIIDQsnTextColors + }; + Q_ASSERT((int)list < (int)sizeof(idMap)/sizeof(idMap[0])); + const QColor color = QS60StyleModeSpecifics::colorValue(*idMap[(int) list], index - 1); + return option ? QS60StylePrivate::stateColor(color, option) : color; +} + +// In some cases, the AVKON UI themegraphic is already in 'disabled state'. +// If so, return true for these parts. +bool QS60StyleModeSpecifics::disabledPartGraphic(QS60StyleEnums::SkinParts &part) +{ + bool disabledGraphic = false; + switch(part){ + // inactive button graphics are available from 5.0 onwards + case QS60StyleEnums::SP_QsnFrButtonCornerTlInactive: + case QS60StyleEnums::SP_QsnFrButtonCornerTrInactive: + case QS60StyleEnums::SP_QsnFrButtonCornerBlInactive: + case QS60StyleEnums::SP_QsnFrButtonCornerBrInactive: + case QS60StyleEnums::SP_QsnFrButtonSideTInactive: + case QS60StyleEnums::SP_QsnFrButtonSideBInactive: + case QS60StyleEnums::SP_QsnFrButtonSideLInactive: + case QS60StyleEnums::SP_QsnFrButtonSideRInactive: + case QS60StyleEnums::SP_QsnFrButtonCenterInactive: + if (!(QSysInfo::s60Version()==QSysInfo::SV_S60_3_1 || + QSysInfo::s60Version()==QSysInfo::SV_S60_3_2)) + disabledGraphic = true; + break; + default: + break; + } + return disabledGraphic; +} + +// In some cases, the AVKON UI themegraphic is already in 'disabled state'. +// If so, return true for these frames. +bool QS60StyleModeSpecifics::disabledFrameGraphic(QS60StylePrivate::SkinFrameElements &frame) +{ + bool disabledGraphic = false; + switch(frame){ + // inactive button graphics are available from 5.0 onwards + case QS60StylePrivate::SF_ButtonInactive: + if (!(QSysInfo::s60Version()==QSysInfo::SV_S60_3_1 || + QSysInfo::s60Version()==QSysInfo::SV_S60_3_2)) + disabledGraphic = true; + break; + default: + break; + } + return disabledGraphic; +} + +QPixmap QS60StyleModeSpecifics::generateMissingThemeGraphic(QS60StyleEnums::SkinParts &part, + const QSize &size, QS60StylePrivate::SkinElementFlags flags) +{ + if (!QS60StylePrivate::isTouchSupported()) + return QPixmap(); + + QS60StyleEnums::SkinParts updatedPart = part; + switch(part){ + // AVKON UI has a abnormal handling for scrollbar graphics. It is possible that the root + // skin does not contain mandatory graphics for scrollbar pressed states. Therefore, AVKON UI + // creates dynamically these graphics by modifying the normal state scrollbar graphics slightly. + // S60Style needs to work similarly. Therefore if skingraphics call provides to be a miss + // (i.e. result is not valid), style needs to draw normal graphics instead and apply some + // modifications (similar to generatedIconPixmap()) to the result. + case QS60StyleEnums::SP_QsnCpScrollHandleBottomPressed: + updatedPart = QS60StyleEnums::SP_QsnCpScrollHandleBottom; + break; + case QS60StyleEnums::SP_QsnCpScrollHandleMiddlePressed: + updatedPart = QS60StyleEnums::SP_QsnCpScrollHandleMiddle; + break; + case QS60StyleEnums::SP_QsnCpScrollHandleTopPressed: + updatedPart = QS60StyleEnums::SP_QsnCpScrollHandleTop; + break; + default: + break; + } + if (part==updatedPart) { + return QPixmap(); + } else { + QPixmap result = skinnedGraphics(updatedPart, size, flags); + QStyleOption opt; + QPalette *themePalette = QS60StylePrivate::themePalette(); + if (themePalette) + opt.palette = *themePalette; + + // For now, always generate new icon based on "selected". In the future possibly, expand + // this to consist other possibilities as well. + result = QApplication::style()->generatedIconPixmap(QIcon::Selected, result, &opt); + return result; + } +} + +QPixmap QS60StylePrivate::part(QS60StyleEnums::SkinParts part, + const QSize &size, QPainter *painter, SkinElementFlags flags) +{ + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + QPixmap result = (flags & SF_ColorSkinned)? + QS60StyleModeSpecifics::colorSkinnedGraphics(part, size, painter, flags) + : QS60StyleModeSpecifics::skinnedGraphics(part, size, flags); + + lock.relock(); + + if (flags & SF_StateDisabled && !QS60StyleModeSpecifics::disabledPartGraphic(part)) { + QStyleOption opt; + QPalette *themePalette = QS60StylePrivate::themePalette(); + if (themePalette) + opt.palette = *themePalette; + result = QApplication::style()->generatedIconPixmap(QIcon::Disabled, result, &opt); + } + + if (!result) + result = QS60StyleModeSpecifics::generateMissingThemeGraphic(part, size, flags); + + return result; +} + +QPixmap QS60StylePrivate::frame(SkinFrameElements frame, const QSize &size, SkinElementFlags flags) +{ + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + QPixmap result = QS60StyleModeSpecifics::skinnedGraphics(frame, size, flags); + lock.relock(); + + if (flags & SF_StateDisabled && !QS60StyleModeSpecifics::disabledFrameGraphic(frame)) { + QStyleOption opt; + QPalette *themePalette = QS60StylePrivate::themePalette(); + if (themePalette) + opt.palette = *themePalette; + result = QApplication::style()->generatedIconPixmap(QIcon::Disabled, result, &opt); + } + return result; +} + +QPixmap QS60StylePrivate::backgroundTexture(bool skipCreation) +{ + bool createNewBackground = false; + TRect applicationRect = (static_cast<CEikAppUi*>(S60->appUi())->ApplicationRect()); + if (!m_background) { + createNewBackground = true; + } else { + //if background brush does not match screensize, re-create it + if (m_background->width() != applicationRect.Width() || + m_background->height() != applicationRect.Height()) { + delete m_background; + m_background = 0; + createNewBackground = true; + } + } + + if (createNewBackground && !skipCreation) { + QPixmap background = part(QS60StyleEnums::SP_QsnBgScreen, + QSize(applicationRect.Width(), applicationRect.Height()), 0, SkinElementFlags()); + m_background = new QPixmap(background); + + // Notify all widgets that palette is updated with the actual background texture. + QPalette pal = QApplication::palette(); + pal.setBrush(QPalette::Window, *m_background); + QApplication::setPalette(pal); + setThemePaletteHash(&pal); + storeThemePalette(&pal); + foreach (QWidget *widget, QApplication::allWidgets()){ + QEvent e(QEvent::PaletteChange); + QApplication::sendEvent(widget, &e); + setThemePalette(widget); + widget->ensurePolished(); + } + } + if (!m_background) + return QPixmap(); + return *m_background; +} + +QSize QS60StylePrivate::screenSize() +{ + return QSize(S60->screenWidthInPixels, S60->screenHeightInPixels); +} + +QS60Style::QS60Style() + : QCommonStyle(*new QS60StylePrivate) +{ +} + +#ifdef Q_WS_S60 +void QS60StylePrivate::handleDynamicLayoutVariantSwitch() +{ + clearCaches(QS60StylePrivate::CC_LayoutChange); + setBackgroundTexture(qApp); + setActiveLayout(); + foreach (QWidget *widget, QApplication::allWidgets()) + widget->ensurePolished(); +} + +void QS60StylePrivate::handleSkinChange() +{ + clearCaches(QS60StylePrivate::CC_ThemeChange); + setThemePalette(qApp); + foreach (QWidget *topLevelWidget, QApplication::allWidgets()){ + QEvent e(QEvent::StyleChange); + QApplication::sendEvent(topLevelWidget, &e); + setThemePalette(topLevelWidget); + topLevelWidget->ensurePolished(); + } +#ifndef QT_NO_PROGRESSBAR + //re-start animation timer + stopAnimation(QS60StyleEnums::SP_QgnGrafBarWaitAnim); //todo: once we have more animations, we could say "stop all running ones" + startAnimation(QS60StyleEnums::SP_QgnGrafBarWaitAnim); //and "re-start all previously running ones" +#endif +} + +int QS60StylePrivate::currentAnimationFrame(QS60StyleEnums::SkinParts part) +{ + QS60StyleAnimation *animation = animationDefinition(part); + // todo: looping could be done in QS60Style::timerEvent + if (animation->frameCount() == animation->currentFrame()) + animation->setCurrentFrame(0); + return animation->currentFrame(); +} + +QS60StyleAnimation* QS60StylePrivate::animationDefinition(QS60StyleEnums::SkinParts part) +{ + int i = 0; + const int animationsCount = m_animations()->isEmpty() ? 0 : m_animations()->count(); + for(; i < animationsCount; i++) { + if (part == m_animations()->at(i)->animationId()) + break; + } + return m_animations()->at(i); +} + +void QS60StylePrivate::startAnimation(QS60StyleEnums::SkinParts animationPart) +{ + Q_Q(QS60Style); + + //Query animation data from theme and store values to local struct. + QVariant themeAnimationDataVariant = QS60StyleModeSpecifics::themeDefinition( + QS60StyleEnums::TD_AnimationData, animationPart); + QList<QVariant> themeAnimationData = themeAnimationDataVariant.toList(); + + QS60StyleAnimation *animation = QS60StylePrivate::animationDefinition(animationPart); + if (animation) { + if (themeAnimationData.at(QS60StyleEnums::AD_Interval).toInt() != 0) + animation->setInterval(themeAnimationData.at(QS60StyleEnums::AD_Interval).toInt()); + + if (themeAnimationData.at(QS60StyleEnums::AD_NumberOfFrames).toInt() != 0) + animation->setFrameCount(themeAnimationData.at(QS60StyleEnums::AD_NumberOfFrames).toInt()); + + //todo: playmode is ignored for now, since it seems to return invalid data on some themes + //lets use the table values for play mode + + animation->setCurrentFrame(0); //always initialize + const int timerId = q->startTimer(animation->interval()); + animation->setTimerId(timerId); + } +} + +void QS60StylePrivate::stopAnimation(QS60StyleEnums::SkinParts animationPart) +{ + Q_Q(QS60Style); + + QS60StyleAnimation *animation = QS60StylePrivate::animationDefinition(animationPart); + if (animation) { + animation->setCurrentFrame(0); + if (animation->timerId() != 0) { + q->killTimer(animation->timerId()); + animation->setTimerId(0); + } + animation->resetToDefaults(); + } +} + +QVariant QS60StyleModeSpecifics::themeDefinition( + QS60StyleEnums::ThemeDefinitions definition, QS60StyleEnums::SkinParts part) +{ + MAknsSkinInstance* skinInstance = AknsUtils::SkinInstance(); + + Q_ASSERT(skinInstance); + + switch(definition) { + //Animation definitions + case QS60StyleEnums::TD_AnimationData: + { + CAknsBmpAnimItemData *animationData; + TAknsItemID animationSkinId = partSpecificThemeId(part); + QList<QVariant> list; + + TRAPD( error, QT_TRYCATCH_LEAVING( + animationData = static_cast<CAknsBmpAnimItemData*>(skinInstance->CreateUncachedItemDataL( + animationSkinId, EAknsITBmpAnim)))); + if (error) + return list; + + if (animationData) { + list.append((int)animationData->FrameInterval()); + list.append((int)animationData->NumberOfImages()); + + QS60StyleEnums::AnimationMode playMode; + switch(animationData->PlayMode()) { + case CBitmapAnimClientData::EPlay: + playMode = QS60StyleEnums::AM_PlayOnce; + break; + case CBitmapAnimClientData::ECycle: + playMode = QS60StyleEnums::AM_Looping; + break; + case CBitmapAnimClientData::EBounce: + playMode = QS60StyleEnums::AM_Bounce; + break; + default: + break; + } + list.append(QVariant((int)playMode)); + delete animationData; + } else { + list.append(0); + list.append(0); + } + return list; + } + break; + default: + break; + } + return QVariant(); +} + +#endif // Q_WS_S60 + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_S60 || QT_PLUGIN diff --git a/src/gui/styles/qs60style_simulated.cpp b/src/gui/styles/qs60style_simulated.cpp new file mode 100644 index 0000000000..a5aeac3fb9 --- /dev/null +++ b/src/gui/styles/qs60style_simulated.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qs60style.h" +#include "qs60style_p.h" +#include "qfile.h" +#include "qhash.h" +#include "qapplication.h" +#include "qpainter.h" +#include "qpicture.h" +#include "qstyleoption.h" +#include "qtransform.h" +#include "qlayout.h" +#include "qpixmapcache.h" +#include "qmetaobject.h" +#include "qdebug.h" +#include "qbuffer.h" +#include "qdesktopwidget.h" + +#if !defined(QT_NO_STYLE_S60) || defined(QT_PLUGIN) + +QT_BEGIN_NAMESPACE + +static const quint32 blobVersion = 1; +static const int pictureSize = 256; + +#if defined(Q_CC_GNU) +#if __GNUC__ >= 2 +#define __FUNCTION__ __func__ +#endif +#endif + + +bool saveThemeToBlob(const QString &themeBlob, + const QHash<QString, QPicture> &partPictures, + const QHash<QPair<QString, int>, QColor> &colors) +{ + QFile blob(themeBlob); + if (!blob.open(QIODevice::WriteOnly)) { + qWarning() << __FUNCTION__ << ": Could not create blob: " << themeBlob; + return false; + } + + QByteArray data; + QBuffer dataBuffer(&data); + dataBuffer.open(QIODevice::WriteOnly); + QDataStream dataOut(&dataBuffer); + + const int colorsCount = colors.count(); + dataOut << colorsCount; + const QList<QPair<QString, int> > colorKeys = colors.keys(); + for (int i = 0; i < colorsCount; ++i) { + const QPair<QString, int> &key = colorKeys.at(i); + dataOut << key; + const QColor color = colors.value(key); + dataOut << color; + } + + dataOut << partPictures.count(); + QHashIterator<QString, QPicture> i(partPictures); + while (i.hasNext()) { + i.next(); + dataOut << i.key(); + dataOut << i.value(); // the QPicture + } + + QDataStream blobOut(&blob); + blobOut << blobVersion; + blobOut << qCompress(data); + return blobOut.status() == QDataStream::Ok; +} + +bool loadThemeFromBlob(const QString &themeBlob, + QHash<QString, QPicture> &partPictures, + QHash<QPair<QString, int>, QColor> &colors) +{ + QFile blob(themeBlob); + if (!blob.open(QIODevice::ReadOnly)) { + qWarning() << __FUNCTION__ << ": Could not read blob: " << themeBlob; + return false; + } + QDataStream blobIn(&blob); + + quint32 version; + blobIn >> version; + + if (version != blobVersion) { + qWarning() << __FUNCTION__ << ": Invalid blob version: " << version << " ...expected: " << blobVersion; + return false; + } + + QByteArray data; + blobIn >> data; + data = qUncompress(data); + QBuffer dataBuffer(&data); + dataBuffer.open(QIODevice::ReadOnly); + QDataStream dataIn(&dataBuffer); + + int colorsCount; + dataIn >> colorsCount; + for (int i = 0; i < colorsCount; ++i) { + QPair<QString, int> key; + dataIn >> key; + QColor value; + dataIn >> value; + colors.insert(key, value); + } + + int picturesCount; + dataIn >> picturesCount; + for (int i = 0; i < picturesCount; ++i) { + QString key; + dataIn >> key; + QPicture value; + dataIn >> value; + value.setBoundingRect(QRect(0, 0, pictureSize, pictureSize)); // Bug? The forced bounding rect was not deserialized. + partPictures.insert(key, value); + } + + if (dataIn.status() != QDataStream::Ok) { + qWarning() << __FUNCTION__ << ": Invalid data blob: " << themeBlob; + return false; + } + return true; +} + +class QS60StyleModeSpecifics +{ +public: + static QPixmap skinnedGraphics(QS60StyleEnums::SkinParts stylepart, + const QSize &size, QS60StylePrivate::SkinElementFlags flags); + static QHash<QString, QPicture> m_partPictures; + static QHash<QPair<QString , int>, QColor> m_colors; +}; +QHash<QString, QPicture> QS60StyleModeSpecifics::m_partPictures; +QHash<QPair<QString , int>, QColor> QS60StyleModeSpecifics::m_colors; + +QS60StylePrivate::QS60StylePrivate() +{ + setCurrentLayout(0); +} + +QColor QS60StylePrivate::s60Color(QS60StyleEnums::ColorLists list, + int index, const QStyleOption *option) +{ + const QString listKey = QS60Style::colorListKeys().at(list); + return QS60StylePrivate::stateColor( + QS60StyleModeSpecifics::m_colors.value(QPair<QString, int>(listKey, index)), + option + ); +} + +QPixmap QS60StylePrivate::part(QS60StyleEnums::SkinParts part, const QSize &size, + QPainter *painter, QS60StylePrivate::SkinElementFlags flags) +{ + Q_UNUSED(painter); + + const QString partKey = QS60Style::partKeys().at(part); + const QPicture partPicture = QS60StyleModeSpecifics::m_partPictures.value(partKey); + QSize partSize(partPicture.boundingRect().size()); + if (flags & (SF_PointEast | SF_PointWest)) { + const int temp = partSize.width(); + partSize.setWidth(partSize.height()); + partSize.setHeight(temp); + } + const qreal scaleX = size.width() / (qreal)partSize.width(); + const qreal scaleY = size.height() / (qreal)partSize.height(); + + QImage partImage(size, QImage::Format_ARGB32); + partImage.fill(Qt::transparent); + QPainter resultPainter(&partImage); + QTransform t; + + if (flags & SF_PointEast) + t.translate(size.width(), 0); + else if (flags & SF_PointSouth) + t.translate(size.width(), size.height()); + else if (flags & SF_PointWest) + t.translate(0, size.height()); + + t.scale(scaleX, scaleY); + + if (flags & SF_PointEast) + t.rotate(90); + else if (flags & SF_PointSouth) + t.rotate(180); + else if (flags & SF_PointWest) + t.rotate(270); + + resultPainter.setTransform(t, true); + const_cast<QPicture *>(&partPicture)->play(&resultPainter); + resultPainter.end(); + + QPixmap result = QPixmap::fromImage(partImage); + if (flags & SF_StateDisabled) { + QStyleOption opt; + QPalette *themePalette = QS60StylePrivate::themePalette(); + if (themePalette) + opt.palette = *themePalette; + result = QApplication::style()->generatedIconPixmap(QIcon::Disabled, result, &opt); + } + + return result; +} + +QPixmap QS60StylePrivate::frame(SkinFrameElements frame, const QSize &size, + SkinElementFlags flags) +{ + const QS60StyleEnums::SkinParts center = m_frameElementsData[frame].center; + const QS60StyleEnums::SkinParts topLeft = QS60StyleEnums::SkinParts(center - 8); + const QS60StyleEnums::SkinParts topRight = QS60StyleEnums::SkinParts(center - 7); + const QS60StyleEnums::SkinParts bottomLeft = QS60StyleEnums::SkinParts(center - 6); + const QS60StyleEnums::SkinParts bottomRight = QS60StyleEnums::SkinParts(center - 5); + const QS60StyleEnums::SkinParts top = QS60StyleEnums::SkinParts(center - 4); + const QS60StyleEnums::SkinParts bottom = QS60StyleEnums::SkinParts(center - 3); + const QS60StyleEnums::SkinParts left = QS60StyleEnums::SkinParts(center - 2); + const QS60StyleEnums::SkinParts right = QS60StyleEnums::SkinParts(center - 1); + + // The size of topLeft defines all other sizes + const QSize cornerSize(partSize(topLeft)); + // if frame is so small that corners would cover it completely, draw only center piece + const bool drawOnlyCenter = + 2 * cornerSize.width() + 1 >= size.width() || 2 * cornerSize.height() + 1 >= size.height(); + + const int cornerWidth = cornerSize.width(); + const int cornerHeight = cornerSize.height(); + const int rectWidth = size.width(); + const int rectHeight = size.height(); + const QRect rect(QPoint(), size); + + const QRect topLeftRect = QRect(rect.topLeft(), cornerSize); + const QRect topRect = rect.adjusted(cornerWidth, 0, -cornerWidth, -(rectHeight - cornerHeight)); + const QRect topRightRect = topLeftRect.translated(rectWidth - cornerWidth, 0); + const QRect rightRect = rect.adjusted(rectWidth - cornerWidth, cornerHeight, 0, -cornerHeight); + const QRect bottomRightRect = topRightRect.translated(0, rectHeight - cornerHeight); + const QRect bottomRect = topRect.translated(0, rectHeight - cornerHeight); + const QRect bottomLeftRect = topLeftRect.translated(0, rectHeight - cornerHeight); + const QRect leftRect = rightRect.translated(cornerWidth - rectWidth, 0); + const QRect centerRect = drawOnlyCenter ? rect : rect.adjusted(cornerWidth, cornerWidth, -cornerWidth, -cornerWidth); + + QPixmap result(size); + result.fill(Qt::transparent); + QPainter painter(&result); + +#if 0 + painter.save(); + painter.setOpacity(.3); + painter.fillRect(topLeftRect, Qt::red); + painter.fillRect(topRect, Qt::green); + painter.fillRect(topRightRect, Qt::blue); + painter.fillRect(rightRect, Qt::green); + painter.fillRect(bottomRightRect, Qt::red); + painter.fillRect(bottomRect, Qt::blue); + painter.fillRect(bottomLeftRect, Qt::green); + painter.fillRect(leftRect, Qt::blue); + painter.fillRect(centerRect, Qt::red); + painter.restore(); +#else + drawPart(topLeft, &painter, topLeftRect, flags); + drawPart(top, &painter, topRect, flags); + drawPart(topRight, &painter, topRightRect, flags); + drawPart(right, &painter, rightRect, flags); + drawPart(bottomRight, &painter, bottomRightRect, flags); + drawPart(bottom, &painter, bottomRect, flags); + drawPart(bottomLeft, &painter, bottomLeftRect, flags); + drawPart(left, &painter, leftRect, flags); + drawPart(center, &painter, centerRect, flags); +#endif + + return result; +} + +QPixmap QS60StylePrivate::backgroundTexture(bool /*skipCreation*/) +{ + if (!m_background) { + const QSize size = QApplication::desktop()->screen()->size(); + QPixmap background = part(QS60StyleEnums::SP_QsnBgScreen, size, 0); + m_background = new QPixmap(background); + } + return *m_background; +} + +bool QS60StylePrivate::isTouchSupported() +{ +#ifdef QT_KEYPAD_NAVIGATION + return !QApplication::keypadNavigationEnabled(); +#else + return true; +#endif +} + +bool QS60StylePrivate::isToolBarBackground() +{ + return true; +} + +bool QS60StylePrivate::hasSliderGrooveGraphic() +{ + return false; +} + +bool QS60StylePrivate::isSingleClickUi() +{ + return false; +} + +QFont QS60StylePrivate::s60Font_specific( + QS60StyleEnums::FontCategories fontCategory, + int pointSize, bool resolveFontSize) +{ + QFont result; + if (resolveFontSize) + result.setPointSize(pointSize); + switch (fontCategory) { + case QS60StyleEnums::FC_Primary: + result.setBold(true); + break; + case QS60StyleEnums::FC_Secondary: + case QS60StyleEnums::FC_Title: + case QS60StyleEnums::FC_PrimarySmall: + case QS60StyleEnums::FC_Digital: + case QS60StyleEnums::FC_Undefined: + default: + break; + } + return result; +} + +int QS60StylePrivate::currentAnimationFrame(QS60StyleEnums::SkinParts part) +{ + return 0; +} + +/*! + Constructs a QS60Style object. +*/ +QS60Style::QS60Style() + : QCommonStyle(*new QS60StylePrivate) +{ + const QString defaultBlob = QString::fromLatin1(":/trolltech/styles/s60style/images/defaults60theme.blob"); + if (QFile::exists(defaultBlob)) + loadS60ThemeFromBlob(defaultBlob); +} + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QStringList, enumPartKeys, { + const int enumIndex = QS60StyleEnums::staticMetaObject.indexOfEnumerator("SkinParts"); + Q_ASSERT(enumIndex >= 0); + const QMetaEnum metaEnum = QS60StyleEnums::staticMetaObject.enumerator(enumIndex); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + const QString enumKey = QString::fromLatin1(metaEnum.key(i)); + QString partKey; + // Following loop does following conversions: "SP_QgnNoteInfo" to "qgn_note_info"... + for (int charPosition = 3; charPosition < enumKey.length(); charPosition++) { + if (charPosition > 3 && enumKey[charPosition].isUpper()) + partKey.append(QChar::fromLatin1('_')); + partKey.append(enumKey[charPosition].toLower()); + } + x->append(partKey); + } +}) + +QStringList QS60Style::partKeys() +{ + return *enumPartKeys(); +} + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QStringList, enumColorListKeys, { + const int enumIndex = QS60StyleEnums::staticMetaObject.indexOfEnumerator("ColorLists"); + Q_ASSERT(enumIndex >= 0); + const QMetaEnum metaEnum = QS60StyleEnums::staticMetaObject.enumerator(enumIndex); + for (int i = 0; i < metaEnum.keyCount(); i++) { + const QString enumKey = QString::fromLatin1(metaEnum.key(i)); + // Following line does following conversions: CL_QsnTextColors to "text"... + x->append(enumKey.mid(6, enumKey.length() - 12).toLower()); + } +}) + +QStringList QS60Style::colorListKeys() +{ + return *enumColorListKeys(); +} + +void QS60Style::setS60Theme(const QHash<QString, QPicture> &parts, + const QHash<QPair<QString , int>, QColor> &colors) +{ + Q_D(QS60Style); + QS60StyleModeSpecifics::m_partPictures = parts; + QS60StyleModeSpecifics::m_colors = colors; + d->clearCaches(QS60StylePrivate::CC_ThemeChange); + d->setBackgroundTexture(qApp); + d->setThemePalette(qApp); +} + +bool QS60Style::loadS60ThemeFromBlob(const QString &blobFile) +{ + QHash<QString, QPicture> partPictures; + QHash<QPair<QString, int>, QColor> colors; + + if (!loadThemeFromBlob(blobFile, partPictures, colors)) + return false; + setS60Theme(partPictures, colors); + return true; +} + +bool QS60Style::saveS60ThemeToBlob(const QString &blobFile) const +{ + return saveThemeToBlob(blobFile, + QS60StyleModeSpecifics::m_partPictures, QS60StyleModeSpecifics::m_colors); +} + +QPoint qt_s60_fill_background_offset(const QWidget *targetWidget) +{ + Q_UNUSED(targetWidget) + return QPoint(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_S60 || QT_PLUGIN diff --git a/src/gui/styles/qs60style_stub.cpp b/src/gui/styles/qs60style_stub.cpp new file mode 100644 index 0000000000..a3a5b9d0ec --- /dev/null +++ b/src/gui/styles/qs60style_stub.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qs60style.h" +#include "qdebug.h" + +#if defined(QT_NO_STYLE_S60) +QT_BEGIN_NAMESPACE + +QS60Style::QS60Style() +{ + qWarning() << "QS60Style stub created"; +} + +QS60Style::~QS60Style() +{ +} + +void QS60Style::drawComplexControl(ComplexControl , const QStyleOptionComplex *, QPainter *, const QWidget *) const +{ +} + +void QS60Style::drawControl(ControlElement , const QStyleOption *, QPainter *, const QWidget *) const +{ +} + +void QS60Style::drawPrimitive(PrimitiveElement , const QStyleOption *, QPainter *, const QWidget *) const +{ +} + +int QS60Style::pixelMetric(PixelMetric , const QStyleOption *, const QWidget *) const +{ + return 0; +} + +QSize QS60Style::sizeFromContents(ContentsType , const QStyleOption *, const QSize &, const QWidget *) const +{ + return QSize(); +} + +int QS60Style::styleHint(StyleHint , const QStyleOption *, const QWidget *, QStyleHintReturn *) const +{ + return 0; +} + +QRect QS60Style::subControlRect(ComplexControl , const QStyleOptionComplex *, SubControl , const QWidget *) const +{ + return QRect(); +} + +QRect QS60Style::subElementRect(SubElement , const QStyleOption *, const QWidget *) const +{ + return QRect(); +} + +void QS60Style::polish(QWidget *) +{ +} + +void QS60Style::unpolish(QWidget *) +{ +} + +void QS60Style::polish(QApplication *) +{ +} + +void QS60Style::unpolish(QApplication *) +{ +} + +bool QS60Style::event(QEvent *) +{ + return false; +} + +QIcon QS60Style::standardIconImplementation(StandardPixmap , const QStyleOption *, const QWidget *) const +{ + return QIcon(); +} + +void QS60Style::timerEvent(QTimerEvent *) +{ +} + +bool QS60Style::eventFilter(QObject *, QEvent *) +{ + return false; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_S60 diff --git a/src/gui/styles/qstyle.cpp b/src/gui/styles/qstyle.cpp new file mode 100644 index 0000000000..dd816d379c --- /dev/null +++ b/src/gui/styles/qstyle.cpp @@ -0,0 +1,2493 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstyle.h" +#include "qapplication.h" +#include "qpainter.h" +#include "qwidget.h" +#include "qbitmap.h" +#include "qpixmapcache.h" +#include "qstyleoption.h" +#include "private/qstyle_p.h" +#ifndef QT_NO_DEBUG +#include "qdebug.h" +#endif + +#ifdef Q_WS_X11 +#include <qx11info_x11.h> +#endif + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +static const int MaxBits = 8 * sizeof(QSizePolicy::ControlType); + +static int unpackControlTypes(QSizePolicy::ControlTypes controls, QSizePolicy::ControlType *array) +{ + if (!controls) + return 0; + + // optimization: exactly one bit is set + if ((controls & (controls - 1)) == 0) { + array[0] = QSizePolicy::ControlType(uint(controls)); + return 1; + } + + int count = 0; + for (int i = 0; i < MaxBits; ++i) { + if (uint bit = (controls & (0x1 << i))) + array[count++] = QSizePolicy::ControlType(bit); + } + return count; +} + +/*! + \class QStyle + \brief The QStyle class is an abstract base class that encapsulates the look and feel of a GUI. + + \ingroup appearance + + Qt contains a set of QStyle subclasses that emulate the styles of + the different platforms supported by Qt (QWindowsStyle, + QMacStyle, QMotifStyle, etc.). By default, these styles are built + into the QtGui library. Styles can also be made available as + plugins. + + Qt's built-in widgets use QStyle to perform nearly all of their + drawing, ensuring that they look exactly like the equivalent + native widgets. The diagram below shows a QComboBox in eight + different styles. + + \img qstyle-comboboxes.png Eight combo boxes + + Topics: + + \tableofcontents + + \section1 Setting a Style + + The style of the entire application can be set using the + QApplication::setStyle() function. It can also be specified by the + user of the application, using the \c -style command-line option: + + \snippet doc/src/snippets/code/src_gui_styles_qstyle.cpp 0 + + If no style is specified, Qt will choose the most appropriate + style for the user's platform or desktop environment. + + A style can also be set on an individual widget using the + QWidget::setStyle() function. + + \section1 Developing Style-Aware Custom Widgets + + If you are developing custom widgets and want them to look good on + all platforms, you can use QStyle functions to perform parts of + the widget drawing, such as drawItemText(), drawItemPixmap(), + drawPrimitive(), drawControl(), and drawComplexControl(). + + Most QStyle draw functions take four arguments: + \list + \o an enum value specifying which graphical element to draw + \o a QStyleOption specifying how and where to render that element + \o a QPainter that should be used to draw the element + \o a QWidget on which the drawing is performed (optional) + \endlist + + For example, if you want to draw a focus rectangle on your + widget, you can write: + + \snippet doc/src/snippets/styles/styles.cpp 1 + + QStyle gets all the information it needs to render the graphical + element from QStyleOption. The widget is passed as the last + argument in case the style needs it to perform special effects + (such as animated default buttons on Mac OS X), but it isn't + mandatory. In fact, you can use QStyle to draw on any paint + device, not just widgets, by setting the QPainter properly. + + QStyleOption has various subclasses for the various types of + graphical elements that can be drawn. For example, + PE_FrameFocusRect expects a QStyleOptionFocusRect argument. + + To ensure that drawing operations are as fast as possible, + QStyleOption and its subclasses have public data members. See the + QStyleOption class documentation for details on how to use it. + + For convenience, Qt provides the QStylePainter class, which + combines a QStyle, a QPainter, and a QWidget. This makes it + possible to write + + \snippet doc/src/snippets/styles/styles.cpp 5 + \dots + \snippet doc/src/snippets/styles/styles.cpp 7 + + instead of + + \snippet doc/src/snippets/styles/styles.cpp 2 + \dots + \snippet doc/src/snippets/styles/styles.cpp 3 + + \section1 Creating a Custom Style + + You can create a custom look and feel for your application by + creating a custom style. There are two approaches to creating a + custom style. In the static approach, you either choose an + existing QStyle class, subclass it, and reimplement virtual + functions to provide the custom behavior, or you create an entire + QStyle class from scratch. In the dynamic approach, you modify the + behavior of your system style at runtime. The static approach is + described below. The dynamic approach is described in QProxyStyle. + + The first step in the static approach is to pick one of the styles + provided by Qt from which you will build your custom style. Your + choice of QStyle class will depend on which style resembles your + desired style the most. The most general class that you can use as + a base is QCommonStyle (not QStyle). This is because Qt requires + its styles to be \l{QCommonStyle}s. + + Depending on which parts of the base style you want to change, + you must reimplement the functions that are used to draw those + parts of the interface. To illustrate this, we will modify the + look of the spin box arrows drawn by QWindowsStyle. The arrows + are \e{primitive elements} that are drawn by the drawPrimitive() + function, so we need to reimplement that function. We need the + following class declaration: + + \snippet doc/src/snippets/customstyle/customstyle.h 0 + + To draw its up and down arrows, QSpinBox uses the + PE_IndicatorSpinUp and PE_IndicatorSpinDown primitive elements. + Here's how to reimplement the drawPrimitive() function to draw + them differently: + + \snippet doc/src/snippets/customstyle/customstyle.cpp 2 + \snippet doc/src/snippets/customstyle/customstyle.cpp 3 + \snippet doc/src/snippets/customstyle/customstyle.cpp 4 + + Notice that we don't use the \c widget argument, except to pass it + on to the QWindowStyle::drawPrimitive() function. As mentioned + earlier, the information about what is to be drawn and how it + should be drawn is specified by a QStyleOption object, so there is + no need to ask the widget. + + If you need to use the \c widget argument to obtain additional + information, be careful to ensure that it isn't 0 and that it is + of the correct type before using it. For example: + + \snippet doc/src/snippets/customstyle/customstyle.cpp 0 + \dots + \snippet doc/src/snippets/customstyle/customstyle.cpp 1 + + When implementing a custom style, you cannot assume that the + widget is a QSpinBox just because the enum value is called + PE_IndicatorSpinUp or PE_IndicatorSpinDown. + + The documentation for the \l{widgets/styles}{Styles} example + covers this topic in more detail. + + \warning Qt style sheets are currently not supported for custom QStyle + subclasses. We plan to address this in some future release. + + + \section1 Using a Custom Style + + There are several ways of using a custom style in a Qt + application. The simplest way is to pass the custom style to the + QApplication::setStyle() static function before creating the + QApplication object: + + \snippet snippets/customstyle/main.cpp using a custom style + + You can call QApplication::setStyle() at any time, but by calling + it before the constructor, you ensure that the user's preference, + set using the \c -style command-line option, is respected. + + You may want to make your custom style available for use in other + applications, which may not be yours and hence not available for + you to recompile. The Qt Plugin system makes it possible to create + styles as plugins. Styles created as plugins are loaded as shared + objects at runtime by Qt itself. Please refer to the \link + plugins-howto.html Qt Plugin\endlink documentation for more + information on how to go about creating a style plugin. + + Compile your plugin and put it into Qt's \c plugins/styles + directory. We now have a pluggable style that Qt can load + automatically. To use your new style with existing applications, + simply start the application with the following argument: + + \snippet doc/src/snippets/code/src_gui_styles_qstyle.cpp 1 + + The application will use the look and feel from the custom style you + implemented. + + \section1 Right-to-Left Desktops + + Languages written from right to left (such as Arabic and Hebrew) + usually also mirror the whole layout of widgets, and require the + light to come from the screen's top-right corner instead of + top-left. + + If you create a custom style, you should take special care when + drawing asymmetric elements to make sure that they also look + correct in a mirrored layout. An easy way to test your styles is + to run applications with the \c -reverse command-line option or + to call QApplication::setLayoutDirection() in your \c main() + function. + + Here are some things to keep in mind when making a style work well in a + right-to-left environment: + + \list + \o subControlRect() and subElementRect() return rectangles in screen coordinates + \o QStyleOption::direction indicates in which direction the item should be drawn in + \o If a style is not right-to-left aware it will display items as if it were left-to-right + \o visualRect(), visualPos(), and visualAlignment() are helpful functions that will + translate from logical to screen representations. + \o alignedRect() will return a logical rect aligned for the current direction + \endlist + + \section1 Styles in Item Views + + The painting of items in views is performed by a delegate. Qt's + default delegate, QStyledItemDelegate, is also used for for calculating bounding + rectangles of items, and their sub-elements for the various kind + of item \l{Qt::ItemDataRole}{data roles} + QStyledItemDelegate supports. See the QStyledItemDelegate class + description to find out which datatypes and roles are supported. You + can read more about item data roles in \l{Model/View Programming}. + + When QStyledItemDelegate paints its items, it draws + CE_ItemViewItem, and calculates their size with CT_ItemViewItem. + Note also that it uses SE_ItemViewItemText to set the size of + editors. When implementing a style to customize drawing of item + views, you need to check the implementation of QCommonStyle (and + any other subclasses from which your style + inherits). This way, you find out which and how + other style elements are painted, and you can then reimplement the + painting of elements that should be drawn differently. + + We include a small example where we customize the drawing of item + backgrounds. + + \snippet doc/src/snippets/customviewstyle.cpp 0 + + The primitive element PE_PanelItemViewItem is responsible for + painting the background of items, and is called from + \l{QCommonStyle}'s implementation of CE_ItemViewItem. + + To add support for drawing of new datatypes and item data roles, + it is necessary to create a custom delegate. But if you only + need to support the datatypes implemented by the default + delegate, a custom style does not need an accompanying + delegate. The QStyledItemDelegate class description gives more + information on custom delegates. + + The drawing of item view headers is also done by the style, giving + control over size of header items and row and column sizes. + + \sa QStyleOption, QStylePainter, {Styles Example}, + {Styles and Style Aware Widgets}, QStyledItemDelegate +*/ + +/*! + Constructs a style object. +*/ +QStyle::QStyle() + : QObject(*new QStylePrivate) +{ + Q_D(QStyle); + d->proxyStyle = this; +} + +/*! + \internal + + Constructs a style object. +*/ +QStyle::QStyle(QStylePrivate &dd) + : QObject(dd) +{ + Q_D(QStyle); + d->proxyStyle = this; +} + +/*! + Destroys the style object. +*/ +QStyle::~QStyle() +{ +} + +/*! + Initializes the appearance of the given \a widget. + + This function is called for every widget at some point after it + has been fully created but just \e before it is shown for the very + first time. + + Note that the default implementation does nothing. Reasonable + actions in this function might be to call the + QWidget::setBackgroundMode() function for the widget. Do not use + the function to set, for example, the geometry. Reimplementing + this function provides a back-door through which the appearance + of a widget can be changed, but with Qt's style engine it is + rarely necessary to implement this function; reimplement + drawItemPixmap(), drawItemText(), drawPrimitive(), etc. instead. + + The QWidget::inherits() function may provide enough information to + allow class-specific customizations. But because new QStyle + subclasses are expected to work reasonably with all current and \e + future widgets, limited use of hard-coded customization is + recommended. + + \sa unpolish() +*/ +void QStyle::polish(QWidget * /* widget */) +{ +} + +/*! + Uninitialize the given \a{widget}'s appearance. + + This function is the counterpart to polish(). It is called for + every polished widget whenever the style is dynamically changed; + the former style has to unpolish its settings before the new style + can polish them again. + + Note that unpolish() will only be called if the widget is + destroyed. This can cause problems in some cases, e.g, if you + remove a widget from the UI, cache it, and then reinsert it after + the style has changed; some of Qt's classes cache their widgets. + + \sa polish() +*/ +void QStyle::unpolish(QWidget * /* widget */) +{ +} + +/*! + \fn void QStyle::polish(QApplication * application) + \overload + + Late initialization of the given \a application object. +*/ +void QStyle::polish(QApplication * /* app */) +{ +} + +/*! + \fn void QStyle::unpolish(QApplication * application) + \overload + + Uninitialize the given \a application. +*/ +void QStyle::unpolish(QApplication * /* app */) +{ +} + +/*! + \fn void QStyle::polish(QPalette & palette) + \overload + + Changes the \a palette according to style specific requirements + for color palettes (if any). + + \sa QPalette, QApplication::setPalette() +*/ +void QStyle::polish(QPalette & /* pal */) +{ +} + +/*! + \fn QRect QStyle::itemTextRect(const QFontMetrics &metrics, const QRect &rectangle, int alignment, bool enabled, const QString &text) const + + Returns the area within the given \a rectangle in which to draw + the provided \a text according to the specified font \a metrics + and \a alignment. The \a enabled parameter indicates whether or + not the associated item is enabled. + + If the given \a rectangle is larger than the area needed to render + the \a text, the rectangle that is returned will be offset within + \a rectangle according to the specified \a alignment. For + example, if \a alignment is Qt::AlignCenter, the returned + rectangle will be centered within \a rectangle. If the given \a + rectangle is smaller than the area needed, the returned rectangle + will be the smallest rectangle large enough to render the \a text. + + \sa Qt::Alignment +*/ +QRect QStyle::itemTextRect(const QFontMetrics &metrics, const QRect &rect, int alignment, bool enabled, + const QString &text) const +{ + QRect result; + int x, y, w, h; + rect.getRect(&x, &y, &w, &h); + if (!text.isEmpty()) { + result = metrics.boundingRect(x, y, w, h, alignment, text); + if (!enabled && proxy()->styleHint(SH_EtchDisabledText)) { + result.setWidth(result.width()+1); + result.setHeight(result.height()+1); + } + } else { + result = QRect(x, y, w, h); + } + return result; +} + +/*! + \fn QRect QStyle::itemPixmapRect(const QRect &rectangle, int alignment, const QPixmap &pixmap) const + + Returns the area within the given \a rectangle in which to draw + the specified \a pixmap according to the defined \a alignment. +*/ +QRect QStyle::itemPixmapRect(const QRect &rect, int alignment, const QPixmap &pixmap) const +{ + QRect result; + int x, y, w, h; + rect.getRect(&x, &y, &w, &h); + if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter) + y += h/2 - pixmap.height()/2; + else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) + y += h - pixmap.height(); + if ((alignment & Qt::AlignRight) == Qt::AlignRight) + x += w - pixmap.width(); + else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) + x += w/2 - pixmap.width()/2; + else if ((alignment & Qt::AlignLeft) != Qt::AlignLeft && QApplication::isRightToLeft()) + x += w - pixmap.width(); + result = QRect(x, y, pixmap.width(), pixmap.height()); + return result; +} + +/*! + \fn void QStyle::drawItemText(QPainter *painter, const QRect &rectangle, int alignment, const QPalette &palette, bool enabled, const QString& text, QPalette::ColorRole textRole) const + + Draws the given \a text in the specified \a rectangle using the + provided \a painter and \a palette. + + The text is drawn using the painter's pen, and aligned and wrapped + according to the specified \a alignment. If an explicit \a + textRole is specified, the text is drawn using the \a palette's + color for the given role. The \a enabled parameter indicates + whether or not the item is enabled; when reimplementing this + function, the \a enabled parameter should influence how the item is + drawn. + + \sa Qt::Alignment, drawItemPixmap() +*/ +void QStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const +{ + if (text.isEmpty()) + return; + QPen savedPen; + if (textRole != QPalette::NoRole) { + savedPen = painter->pen(); + painter->setPen(QPen(pal.brush(textRole), savedPen.widthF())); + } + if (!enabled) { + if (proxy()->styleHint(SH_DitherDisabledText)) { + QRect br; + painter->drawText(rect, alignment, text, &br); + painter->fillRect(br, QBrush(painter->background().color(), Qt::Dense5Pattern)); + return; + } else if (proxy()->styleHint(SH_EtchDisabledText)) { + QPen pen = painter->pen(); + painter->setPen(pal.light().color()); + painter->drawText(rect.adjusted(1, 1, 1, 1), alignment, text); + painter->setPen(pen); + } + } + painter->drawText(rect, alignment, text); + if (textRole != QPalette::NoRole) + painter->setPen(savedPen); +} + +/*! + \fn void QStyle::drawItemPixmap(QPainter *painter, const QRect &rectangle, int alignment, + const QPixmap &pixmap) const + + Draws the given \a pixmap in the specified \a rectangle, according + to the specified \a alignment, using the provided \a painter. + + \sa drawItemText() +*/ + +void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, + const QPixmap &pixmap) const +{ + QRect aligned = alignedRect(QApplication::layoutDirection(), QFlag(alignment), pixmap.size(), rect); + QRect inter = aligned.intersected(rect); + + painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width(), inter.height()); +} + +/*! + \enum QStyle::PrimitiveElement + + This enum describes the various primitive elements. A + primitive element is a common GUI element, such as a checkbox + indicator or button bevel. + + \omitvalue PE_IndicatorViewItemCheck + \value PE_FrameStatusBar Frame + + \value PE_PanelButtonCommand Button used to initiate an action, for + example, a QPushButton. + + \value PE_FrameDefaultButton This frame around a default button, e.g. in a dialog. + \value PE_PanelButtonBevel Generic panel with a button bevel. + \value PE_PanelButtonTool Panel for a Tool button, used with QToolButton. + \value PE_PanelLineEdit Panel for a QLineEdit. + \value PE_IndicatorButtonDropDown Indicator for a drop down button, for example, a tool + button that displays a menu. + + \value PE_FrameFocusRect Generic focus indicator. + + \value PE_IndicatorArrowUp Generic Up arrow. + \value PE_IndicatorArrowDown Generic Down arrow. + \value PE_IndicatorArrowRight Generic Right arrow. + \value PE_IndicatorArrowLeft Generic Left arrow. + + \value PE_IndicatorSpinUp Up symbol for a spin widget, for example a QSpinBox. + \value PE_IndicatorSpinDown Down symbol for a spin widget. + \value PE_IndicatorSpinPlus Increase symbol for a spin widget. + \value PE_IndicatorSpinMinus Decrease symbol for a spin widget. + + \value PE_IndicatorItemViewItemCheck On/off indicator for a view item. + + \value PE_IndicatorCheckBox On/off indicator, for example, a QCheckBox. + \value PE_IndicatorRadioButton Exclusive on/off indicator, for example, a QRadioButton. + + \value PE_Q3DockWindowSeparator Item separator for Qt 3 compatible dock window + and toolbar contents. + \value PE_IndicatorDockWidgetResizeHandle Resize handle for dock windows. + + \value PE_Frame Generic frame + \value PE_FrameMenu Frame for popup windows/menus; see also QMenu. + \value PE_PanelMenuBar Panel for menu bars. + \value PE_PanelScrollAreaCorner Panel at the bottom-right (or + bottom-left) corner of a scroll area. + + \value PE_FrameDockWidget Panel frame for dock windows and toolbars. + \value PE_FrameTabWidget Frame for tab widgets. + \value PE_FrameLineEdit Panel frame for line edits. + \value PE_FrameGroupBox Panel frame around group boxes. + \value PE_FrameButtonBevel Panel frame for a button bevel. + \value PE_FrameButtonTool Panel frame for a tool button. + + \value PE_IndicatorHeaderArrow Arrow used to indicate sorting on a list or table + header. + \value PE_FrameStatusBarItem Frame for an item of a status bar; see also QStatusBar. + + \value PE_FrameWindow Frame around a MDI window or a docking window. + + \value PE_Q3Separator Qt 3 compatible generic separator. + + \value PE_IndicatorMenuCheckMark Check mark used in a menu. + + \value PE_IndicatorProgressChunk Section of a progress bar indicator; see also QProgressBar. + + \value PE_Q3CheckListController Qt 3 compatible controller part of a list view item. + \value PE_Q3CheckListIndicator Qt 3 compatible checkbox part of a list view item. + \value PE_Q3CheckListExclusiveIndicator Qt 3 compatible radio button part of a list view item. + + \value PE_IndicatorBranch Lines used to represent the branch of a tree in a tree view. + \value PE_IndicatorToolBarHandle The handle of a toolbar. + \value PE_IndicatorToolBarSeparator The separator in a toolbar. + \value PE_PanelToolBar The panel for a toolbar. + \value PE_PanelTipLabel The panel for a tip label. + \value PE_FrameTabBarBase The frame that is drawn for a tab bar, ususally drawn for a tab bar that isn't part of a tab widget. + \value PE_IndicatorTabTear An indicator that a tab is partially scrolled out of the visible tab bar when there are many tabs. + \value PE_IndicatorColumnViewArrow An arrow in a QColumnView. + + \value PE_Widget A plain QWidget. + + \value PE_CustomBase Base value for custom primitive elements. + All values above this are reserved for custom use. Custom values + must be greater than this value. + + \value PE_IndicatorItemViewItemDrop An indicator that is drawn to show where an item in an item view is about to be dropped + during a drag-and-drop operation in an item view. + \value PE_PanelItemViewItem The background for an item in an item view. + \value PE_PanelItemViewRow The background of a row in an item view. + + \value PE_PanelStatusBar The panel for a status bar. + + \value PE_IndicatorTabClose The close button on a tab bar. + \value PE_PanelMenu The panel for a menu. + + \sa drawPrimitive() +*/ + +/*! + \typedef QStyle::SFlags + \internal +*/ + +/*! + \typedef QStyle::SCFlags + \internal +*/ + +/*! + \enum QStyle::StateFlag + + This enum describes flags that are used when drawing primitive + elements. + + Note that not all primitives use all of these flags, and that the + flags may mean different things to different items. + + \value State_None Indicates that the widget does not have a state. + \value State_Active Indicates that the widget is active. + \value State_AutoRaise Used to indicate if auto-raise appearance should be usd on a tool button. + \value State_Children Used to indicate if an item view branch has children. + \value State_DownArrow Used to indicate if a down arrow should be visible on the widget. + \value State_Editing Used to indicate if an editor is opened on the widget. + \value State_Enabled Used to indicate if the widget is enabled. + \value State_HasEditFocus Used to indicate if the widget currently has edit focus. + \value State_HasFocus Used to indicate if the widget has focus. + \value State_Horizontal Used to indicate if the widget is laid out horizontally, for example. a tool bar. + \value State_KeyboardFocusChange Used to indicate if the focus was changed with the keyboard, e.g., tab, backtab or shortcut. + \value State_MouseOver Used to indicate if the widget is under the mouse. + \value State_NoChange Used to indicate a tri-state checkbox. + \value State_Off Used to indicate if the widget is not checked. + \value State_On Used to indicate if the widget is checked. + \value State_Raised Used to indicate if a button is raised. + \value State_ReadOnly Used to indicate if a widget is read-only. + \value State_Selected Used to indicate if a widget is selected. + \value State_Item Used by item views to indicate if a horizontal branch should be drawn. + \value State_Open Used by item views to indicate if the tree branch is open. + \value State_Sibling Used by item views to indicate if a vertical line needs to be drawn (for siblings). + \value State_Sunken Used to indicate if the widget is sunken or pressed. + \value State_UpArrow Used to indicate if an up arrow should be visible on the widget. + \value State_Mini Used to indicate a mini style Mac widget or button. + \value State_Small Used to indicate a small style Mac widget or button. + \omitvalue State_Window + \omitvalue State_Bottom + \omitvalue State_Default + \omitvalue State_FocusAtBorder + \omitvalue State_Top + + \sa drawPrimitive() +*/ + +/*! + \fn void QStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, \ + QPainter *painter, const QWidget *widget) const + + Draws the given primitive \a element with the provided \a painter using the style + options specified by \a option. + + The \a widget argument is optional and may contain a widget that may + aid in drawing the primitive element. + + The table below is listing the primitive elements and their + associated style option subclasses. The style options contain all + the parameters required to draw the elements, including + QStyleOption::state which holds the style flags that are used when + drawing. The table also describes which flags that are set when + casting the given option to the appropriate subclass. + + Note that if a primitive element is not listed here, it is because + it uses a plain QStyleOption object. + + \table + \header \o Primitive Element \o QStyleOption Subclass \o Style Flag \o Remark + \row \o \l PE_FrameFocusRect \o \l QStyleOptionFocusRect + \o \l State_FocusAtBorder + \o Whether the focus is is at the border or inside the widget. + \row \o{1,2} \l PE_IndicatorCheckBox \o{1,2} \l QStyleOptionButton + \o \l State_NoChange \o Indicates a "tri-state" checkbox. + \row \o \l State_On \o Indicates the indicator is checked. + \row \o \l PE_IndicatorRadioButton \o \l QStyleOptionButton + \o \l State_On \o Indicates that a radio button is selected. + \row \o{1,3} \l PE_Q3CheckListExclusiveIndicator, \l PE_Q3CheckListIndicator + \o{1,3} \l QStyleOptionQ3ListView \o \l State_On + \o Indicates whether or not the controller is selected. + \row \o \l State_NoChange \o Indicates a "tri-state" controller. + \row \o \l State_Enabled \o Indicates the controller is enabled. + \row \o{1,4} \l PE_IndicatorBranch \o{1,4} \l QStyleOption + \o \l State_Children \o Indicates that the control for expanding the tree to show child items, should be drawn. + \row \o \l State_Item \o Indicates that a horizontal branch (to show a child item), should be drawn. + \row \o \l State_Open \o Indicates that the tree branch is expanded. + \row \o \l State_Sibling \o Indicates that a vertical line (to show a sibling item), should be drawn. + \row \o \l PE_IndicatorHeaderArrow \o \l QStyleOptionHeader + \o \l State_UpArrow \o Indicates that the arrow should be drawn up; + otherwise it should be down. + \row \o \l PE_FrameGroupBox, \l PE_Frame, \l PE_FrameLineEdit, + \l PE_FrameMenu, \l PE_FrameDockWidget, \l PE_FrameWindow + \o \l QStyleOptionFrame \o \l State_Sunken + \o Indicates that the Frame should be sunken. + \row \o \l PE_IndicatorToolBarHandle \o \l QStyleOption + \o \l State_Horizontal \o Indicates that the window handle is horizontal + instead of vertical. + \row \o \l PE_Q3DockWindowSeparator \o \l QStyleOption + \o \l State_Horizontal \o Indicates that the separator is horizontal + instead of vertical. + \row \o \l PE_IndicatorSpinPlus, \l PE_IndicatorSpinMinus, \l PE_IndicatorSpinUp, + \l PE_IndicatorSpinDown, + \o \l QStyleOptionSpinBox + \o \l State_Sunken \o Indicates that the button is pressed. + \row \o{1,5} \l PE_PanelButtonCommand + \o{1,5} \l QStyleOptionButton + \o \l State_Enabled \o Set if the button is enabled. + \row \o \l State_HasFocus \o Set if the button has input focus. + \row \o \l State_Raised \o Set if the button is not down, not on and not flat. + \row \o \l State_On \o Set if the button is a toggle button and is toggled on. + \row \o \l State_Sunken + \o Set if the button is down (i.e., the mouse button or the + space bar is pressed on the button). + \endtable + + \sa drawComplexControl(), drawControl() +*/ + +/*! + \enum QStyle::ControlElement + + This enum represents a control element. A control element is a + part of a widget that performs some action or displays information + to the user. + + \value CE_PushButton A QPushButton, draws CE_PushButtonBevel, CE_PushButtonLabel and PE_FrameFocusRect. + \value CE_PushButtonBevel The bevel and default indicator of a QPushButton. + \value CE_PushButtonLabel The label (an icon with text or pixmap) of a QPushButton. + + \value CE_DockWidgetTitle Dock window title. + \value CE_Splitter Splitter handle; see also QSplitter. + + + \value CE_CheckBox A QCheckBox, draws a PE_IndicatorCheckBox, a CE_CheckBoxLabel and a PE_FrameFocusRect. + \value CE_CheckBoxLabel The label (text or pixmap) of a QCheckBox. + + \value CE_RadioButton A QRadioButton, draws a PE_IndicatorRadioButton, a CE_RadioButtonLabel and a PE_FrameFocusRect. + \value CE_RadioButtonLabel The label (text or pixmap) of a QRadioButton. + + \value CE_TabBarTab The tab and label within a QTabBar. + \value CE_TabBarTabShape The tab shape within a tab bar. + \value CE_TabBarTabLabel The label within a tab. + + \value CE_ProgressBar A QProgressBar, draws CE_ProgressBarGroove, CE_ProgressBarContents and CE_ProgressBarLabel. + \value CE_ProgressBarGroove The groove where the progress + indicator is drawn in a QProgressBar. + \value CE_ProgressBarContents The progress indicator of a QProgressBar. + \value CE_ProgressBarLabel The text label of a QProgressBar. + + \value CE_ToolButtonLabel A tool button's label. + + \value CE_MenuBarItem A menu item in a QMenuBar. + \value CE_MenuBarEmptyArea The empty area of a QMenuBar. + + \value CE_MenuItem A menu item in a QMenu. + \value CE_MenuScroller Scrolling areas in a QMenu when the + style supports scrolling. + \value CE_MenuTearoff A menu item representing the tear off section of + a QMenu. + \value CE_MenuEmptyArea The area in a menu without menu items. + \value CE_MenuHMargin The horizontal extra space on the left/right of a menu. + \value CE_MenuVMargin The vertical extra space on the top/bottom of a menu. + + \value CE_Q3DockWindowEmptyArea The empty area of a QDockWidget. + + \value CE_ToolBoxTab The toolbox's tab and label within a QToolBox. + \value CE_SizeGrip Window resize handle; see also QSizeGrip. + + \value CE_Header A header. + \value CE_HeaderSection A header section. + \value CE_HeaderLabel The header's label. + + \value CE_ScrollBarAddLine Scroll bar line increase indicator. + (i.e., scroll down); see also QScrollBar. + \value CE_ScrollBarSubLine Scroll bar line decrease indicator (i.e., scroll up). + \value CE_ScrollBarAddPage Scolllbar page increase indicator (i.e., page down). + \value CE_ScrollBarSubPage Scroll bar page decrease indicator (i.e., page up). + \value CE_ScrollBarSlider Scroll bar slider. + \value CE_ScrollBarFirst Scroll bar first line indicator (i.e., home). + \value CE_ScrollBarLast Scroll bar last line indicator (i.e., end). + + \value CE_RubberBand Rubber band used in for example an icon view. + + \value CE_FocusFrame Focus frame that is style controlled. + + \value CE_ItemViewItem An item inside an item view. + + \value CE_CustomBase Base value for custom control elements; + custom values must be greater than this value. + \value CE_ComboBoxLabel The label of a non-editable QComboBox. + \value CE_ToolBar A toolbar like QToolBar. + \value CE_ToolBoxTabShape The toolbox's tab shape. + \value CE_ToolBoxTabLabel The toolbox's tab label. + \value CE_HeaderEmptyArea The area of a header view where there are no header sections. + + \value CE_ShapedFrame The frame with the shape specified in the QStyleOptionFrameV3; see QFrame. + + \omitvalue CE_ColumnViewGrip + + \sa drawControl() +*/ + +/*! + \fn void QStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const + + Draws the given \a element with the provided \a painter with the + style options specified by \a option. + + The \a widget argument is optional and can be used as aid in + drawing the control. The \a option parameter is a pointer to a + QStyleOption object that can be cast to the correct subclass + using the qstyleoption_cast() function. + + The table below is listing the control elements and their + associated style option subclass. The style options contain all + the parameters required to draw the controls, including + QStyleOption::state which holds the style flags that are used when + drawing. The table also describes which flags that are set when + casting the given option to the appropriate subclass. + + Note that if a control element is not listed here, it is because + it uses a plain QStyleOption object. + + \table + \header \o Control Element \o QStyleOption Subclass \o Style Flag \o Remark + \row \o{1,5} \l CE_MenuItem, \l CE_MenuBarItem + \o{1,5} \l QStyleOptionMenuItem + \o \l State_Selected \o The menu item is currently selected item. + \row \o \l State_Enabled \o The item is enabled. + \row \o \l State_DownArrow \o Indicates that a scroll down arrow should be drawn. + \row \o \l State_UpArrow \o Indicates that a scroll up arrow should be drawn + \row \o \l State_HasFocus \o Set if the menu bar has input focus. + + \row \o{1,5} \l CE_PushButton, \l CE_PushButtonBevel, \l CE_PushButtonLabel + \o{1,5} \l QStyleOptionButton + \o \l State_Enabled \o Set if the button is enabled. + \row \o \l State_HasFocus \o Set if the button has input focus. + \row \o \l State_Raised \o Set if the button is not down, not on and not flat. + \row \o \l State_On \o Set if the button is a toggle button and is toggled on. + \row \o \l State_Sunken + \o Set if the button is down (i.e., the mouse button or the + space bar is pressed on the button). + + \row \o{1,6} \l CE_RadioButton, \l CE_RadioButtonLabel, + \l CE_CheckBox, \l CE_CheckBoxLabel + \o{1,6} \l QStyleOptionButton + \o \l State_Enabled \o Set if the button is enabled. + \row \o \l State_HasFocus \o Set if the button has input focus. + \row \o \l State_On \o Set if the button is checked. + \row \o \l State_Off \o Set if the button is not checked. + \row \o \l State_NoChange \o Set if the button is in the NoChange state. + \row \o \l State_Sunken + \o Set if the button is down (i.e., the mouse button or + the space bar is pressed on the button). + + \row \o{1,2} \l CE_ProgressBarContents, \l CE_ProgressBarLabel, + \l CE_ProgressBarGroove + \o{1,2} \l QStyleOptionProgressBar + \o \l State_Enabled \o Set if the progress bar is enabled. + \row \o \l State_HasFocus \o Set if the progress bar has input focus. + + \row \o \l CE_Header, \l CE_HeaderSection, \l CE_HeaderLabel \o \l QStyleOptionHeader \o \o + + \row \o{1,3} \l CE_TabBarTab, CE_TabBarTabShape, CE_TabBarTabLabel + \o{1,3} \l QStyleOptionTab + \o \l State_Enabled \o Set if the tab bar is enabled. + \row \o \l State_Selected \o The tab bar is the currently selected tab bar. + \row \o \l State_HasFocus \o Set if the tab bar tab has input focus. + + \row \o{1,7} \l CE_ToolButtonLabel + \o{1,7} \l QStyleOptionToolButton + \o \l State_Enabled \o Set if the tool button is enabled. + \row \o \l State_HasFocus \o Set if the tool button has input focus. + \row \o \l State_Sunken + \o Set if the tool button is down (i.e., a mouse button or + the space bar is pressed). + \row \o \l State_On \o Set if the tool button is a toggle button and is toggled on. + \row \o \l State_AutoRaise \o Set if the tool button has auto-raise enabled. + \row \o \l State_MouseOver \o Set if the mouse pointer is over the tool button. + \row \o \l State_Raised \o Set if the button is not down and is not on. + + \row \o \l CE_ToolBoxTab \o \l QStyleOptionToolBox + \o \l State_Selected \o The tab is the currently selected tab. + \row \o{1,3} \l CE_HeaderSection \o{1,3} \l QStyleOptionHeader + \o \l State_Sunken \o Indicates that the section is pressed. + \row \o \l State_UpArrow \o Indicates that the sort indicator should be pointing up. + \row \o \l State_DownArrow \o Indicates that the sort indicator should be pointing down. + \endtable + + \sa drawPrimitive(), drawComplexControl() +*/ + +/*! + \enum QStyle::SubElement + + This enum represents a sub-area of a widget. Style implementations + use these areas to draw the different parts of a widget. + + \value SE_PushButtonContents Area containing the label (icon + with text or pixmap). + \value SE_PushButtonFocusRect Area for the focus rect (usually + larger than the contents rect). + \value SE_PushButtonLayoutItem Area that counts for the parent layout. + + \value SE_CheckBoxIndicator Area for the state indicator (e.g., check mark). + \value SE_CheckBoxContents Area for the label (text or pixmap). + \value SE_CheckBoxFocusRect Area for the focus indicator. + \value SE_CheckBoxClickRect Clickable area, defaults to SE_CheckBoxFocusRect. + \value SE_CheckBoxLayoutItem Area that counts for the parent layout. + + \value SE_DateTimeEditLayoutItem Area that counts for the parent layout. + + \value SE_RadioButtonIndicator Area for the state indicator. + \value SE_RadioButtonContents Area for the label. + \value SE_RadioButtonFocusRect Area for the focus indicator. + \value SE_RadioButtonClickRect Clickable area, defaults to SE_RadioButtonFocusRect. + \value SE_RadioButtonLayoutItem Area that counts for the parent layout. + + \value SE_ComboBoxFocusRect Area for the focus indicator. + + \value SE_SliderFocusRect Area for the focus indicator. + \value SE_SliderLayoutItem Area that counts for the parent layout. + + \value SE_SpinBoxLayoutItem Area that counts for the parent layout. + + \value SE_Q3DockWindowHandleRect Area for the tear-off handle. + + \value SE_ProgressBarGroove Area for the groove. + \value SE_ProgressBarContents Area for the progress indicator. + \value SE_ProgressBarLabel Area for the text label. + \value SE_ProgressBarLayoutItem Area that counts for the parent layout. + + \omitvalue SE_DialogButtonAccept + \omitvalue SE_DialogButtonReject + \omitvalue SE_DialogButtonApply + \omitvalue SE_DialogButtonHelp + \omitvalue SE_DialogButtonAll + \omitvalue SE_DialogButtonRetry + \omitvalue SE_DialogButtonAbort + \omitvalue SE_DialogButtonIgnore + \omitvalue SE_DialogButtonCustom + \omitvalue SE_ViewItemCheckIndicator + + \value SE_FrameContents Area for a frame's contents. + \value SE_ShapedFrameContents Area for a frame's contents using the shape in QStyleOptionFrameV3; see QFrame + \value SE_FrameLayoutItem Area that counts for the parent layout. + + \value SE_HeaderArrow Area for the sort indicator for a header. + \value SE_HeaderLabel Area for the label in a header. + + \value SE_LabelLayoutItem Area that counts for the parent layout. + + \value SE_LineEditContents Area for a line edit's contents. + + \value SE_TabWidgetLeftCorner Area for the left corner widget in a tab widget. + \value SE_TabWidgetRightCorner Area for the right corner widget in a tab widget. + \value SE_TabWidgetTabBar Area for the tab bar widget in a tab widget. + \value SE_TabWidgetTabContents Area for the contents of the tab widget. + \value SE_TabWidgetTabPane Area for the pane of a tab widget. + \value SE_TabWidgetLayoutItem Area that counts for the parent layout. + + \value SE_ToolBoxTabContents Area for a toolbox tab's icon and label. + + \value SE_ToolButtonLayoutItem Area that counts for the parent layout. + + \value SE_ItemViewItemCheckIndicator Area for a view item's check mark. + + \value SE_TabBarTearIndicator Area for the tear indicator on a tab bar with scroll arrows. + + \value SE_TreeViewDisclosureItem Area for the actual disclosure item in a tree branch. + + \value SE_DialogButtonBoxLayoutItem Area that counts for the parent layout. + + \value SE_GroupBoxLayoutItem Area that counts for the parent layout. + + \value SE_CustomBase Base value for custom sub-elements. + Custom values must be greater than this value. + + \value SE_DockWidgetFloatButton The float button of a dock + widget. + \value SE_DockWidgetTitleBarText The text bounds of the dock + widgets title. + \value SE_DockWidgetCloseButton The close button of a dock + widget. + \value SE_DockWidgetIcon The icon of a dock widget. + \value SE_ComboBoxLayoutItem Area that counts for the parent layout. + + + \value SE_ItemViewItemDecoration Area for a view item's decoration (icon). + \value SE_ItemViewItemText Area for a view item's text. + \value SE_ItemViewItemFocusRect Area for a view item's focus rect. + + \value SE_TabBarTabLeftButton Area for a widget on the left side of a tab in a tab bar. + \value SE_TabBarTabRightButton Area for a widget on the right side of a tab in a tab bar. + \value SE_TabBarTabText Area for the text on a tab in a tab bar. + + \value SE_ToolBarHandle Area for the handle of a tool bar. + + \sa subElementRect() +*/ + +/*! + \fn QRect QStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const + + Returns the sub-area for the given \a element as described in the + provided style \a option. The returned rectangle is defined in + screen coordinates. + + The \a widget argument is optional and can be used to aid + determining the area. The QStyleOption object can be cast to the + appropriate type using the qstyleoption_cast() function. See the + table below for the appropriate \a option casts: + + \table + \header \o Sub Element \o QStyleOption Subclass + \row \o \l SE_PushButtonContents \o \l QStyleOptionButton + \row \o \l SE_PushButtonFocusRect \o \l QStyleOptionButton + \row \o \l SE_CheckBoxIndicator \o \l QStyleOptionButton + \row \o \l SE_CheckBoxContents \o \l QStyleOptionButton + \row \o \l SE_CheckBoxFocusRect \o \l QStyleOptionButton + \row \o \l SE_RadioButtonIndicator \o \l QStyleOptionButton + \row \o \l SE_RadioButtonContents \o \l QStyleOptionButton + \row \o \l SE_RadioButtonFocusRect \o \l QStyleOptionButton + \row \o \l SE_ComboBoxFocusRect \o \l QStyleOptionComboBox + \row \o \l SE_Q3DockWindowHandleRect \o \l QStyleOptionQ3DockWindow + \row \o \l SE_ProgressBarGroove \o \l QStyleOptionProgressBar + \row \o \l SE_ProgressBarContents \o \l QStyleOptionProgressBar + \row \o \l SE_ProgressBarLabel \o \l QStyleOptionProgressBar + \endtable +*/ + +/*! + \enum QStyle::ComplexControl + + This enum describes the available complex controls. Complex + controls have different behavior depending upon where the user + clicks on them or which keys are pressed. + + \value CC_SpinBox A spinbox, like QSpinBox. + \value CC_ComboBox A combobox, like QComboBox. + \value CC_ScrollBar A scroll bar, like QScrollBar. + \value CC_Slider A slider, like QSlider. + \value CC_ToolButton A tool button, like QToolButton. + \value CC_TitleBar A Title bar, like those used in QMdiSubWindow. + \value CC_Q3ListView Used for drawing the Q3ListView class. + \value CC_GroupBox A group box, like QGroupBox. + \value CC_Dial A dial, like QDial. + \value CC_MdiControls The minimize, close, and normal + button in the menu bar for a + maximized MDI subwindow. + + \value CC_CustomBase Base value for custom complex controls. Custom + values must be greater than this value. + + \sa SubControl drawComplexControl() +*/ + +/*! + \enum QStyle::SubControl + + This enum describes the available sub controls. A subcontrol is a + control element within a complex control (ComplexControl). + + \value SC_None Special value that matches no other sub control. + + \value SC_ScrollBarAddLine Scroll bar add line (i.e., down/right + arrow); see also QScrollBar. + \value SC_ScrollBarSubLine Scroll bar sub line (i.e., up/left arrow). + \value SC_ScrollBarAddPage Scroll bar add page (i.e., page down). + \value SC_ScrollBarSubPage Scroll bar sub page (i.e., page up). + \value SC_ScrollBarFirst Scroll bar first line (i.e., home). + \value SC_ScrollBarLast Scroll bar last line (i.e., end). + \value SC_ScrollBarSlider Scroll bar slider handle. + \value SC_ScrollBarGroove Special sub-control which contains the + area in which the slider handle may move. + + \value SC_SpinBoxUp Spin widget up/increase; see also QSpinBox. + \value SC_SpinBoxDown Spin widget down/decrease. + \value SC_SpinBoxFrame Spin widget frame. + \value SC_SpinBoxEditField Spin widget edit field. + + \value SC_ComboBoxEditField Combobox edit field; see also QComboBox. + \value SC_ComboBoxArrow Combobox arrow button. + \value SC_ComboBoxFrame Combobox frame. + \value SC_ComboBoxListBoxPopup The reference rectangle for the combobox popup. + Used to calculate the position of the popup. + + \value SC_SliderGroove Special sub-control which contains the area + in which the slider handle may move. + \value SC_SliderHandle Slider handle. + \value SC_SliderTickmarks Slider tickmarks. + + \value SC_ToolButton Tool button (see also QToolButton). + \value SC_ToolButtonMenu Sub-control for opening a popup menu in a + tool button; see also Q3PopupMenu. + + \value SC_TitleBarSysMenu System menu button (i.e., restore, close, etc.). + \value SC_TitleBarMinButton Minimize button. + \value SC_TitleBarMaxButton Maximize button. + \value SC_TitleBarCloseButton Close button. + \value SC_TitleBarLabel Window title label. + \value SC_TitleBarNormalButton Normal (restore) button. + \value SC_TitleBarShadeButton Shade button. + \value SC_TitleBarUnshadeButton Unshade button. + \value SC_TitleBarContextHelpButton Context Help button. + + \value SC_Q3ListView The list view area. + \value SC_Q3ListViewExpand Expand item (i.e., show/hide child items). + + \value SC_DialHandle The handle of the dial (i.e. what you use to control the dial). + \value SC_DialGroove The groove for the dial. + \value SC_DialTickmarks The tickmarks for the dial. + + \value SC_GroupBoxFrame The frame of a group box. + \value SC_GroupBoxLabel The title of a group box. + \value SC_GroupBoxCheckBox The optional check box of a group box. + \value SC_GroupBoxContents The group box contents. + + \value SC_MdiNormalButton The normal button for a MDI + subwindow in the menu bar. + \value SC_MdiMinButton The minimize button for a MDI + subwindow in the menu bar. + \value SC_MdiCloseButton The close button for a MDI subwindow + in the menu bar. + + \value SC_All Special value that matches all sub-controls. + \omitvalue SC_Q3ListViewBranch + \omitvalue SC_CustomBase + + \sa ComplexControl +*/ + +/*! + \fn void QStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const + + Draws the given \a control using the provided \a painter with the + style options specified by \a option. + + The \a widget argument is optional and can be used as aid in + drawing the control. + + The \a option parameter is a pointer to a QStyleOptionComplex + object that can be cast to the correct subclass using the + qstyleoption_cast() function. Note that the \c rect member of the + specified \a option must be in logical + coordinates. Reimplementations of this function should use + visualRect() to change the logical coordinates into screen + coordinates before calling the drawPrimitive() or drawControl() + function. + + The table below is listing the complex control elements and their + associated style option subclass. The style options contain all + the parameters required to draw the controls, including + QStyleOption::state which holds the \l {QStyle::StateFlag}{style + flags} that are used when drawing. The table also describes which + flags that are set when casting the given \a option to the + appropriate subclass. + + \table + \header \o Complex Control \o QStyleOptionComplex Subclass \o Style Flag \o Remark + \row \o{1,2} \l{CC_SpinBox} \o{1,2} \l QStyleOptionSpinBox + \o \l State_Enabled \o Set if the spin box is enabled. + \row \o \l State_HasFocus \o Set if the spin box has input focus. + + \row \o{1,2} \l {CC_ComboBox} \o{1,2} \l QStyleOptionComboBox + \o \l State_Enabled \o Set if the combobox is enabled. + \row \o \l State_HasFocus \o Set if the combobox has input focus. + + \row \o{1,2} \l {CC_ScrollBar} \o{1,2} \l QStyleOptionSlider + \o \l State_Enabled \o Set if the scroll bar is enabled. + \row \o \l State_HasFocus \o Set if the scroll bar has input focus. + + \row \o{1,2} \l {CC_Slider} \o{1,2} \l QStyleOptionSlider + \o \l State_Enabled \o Set if the slider is enabled. + \row \o \l State_HasFocus \o Set if the slider has input focus. + + \row \o{1,2} \l {CC_Dial} \o{1,2} \l QStyleOptionSlider + \o \l State_Enabled \o Set if the dial is enabled. + \row \o \l State_HasFocus \o Set if the dial has input focus. + + \row \o{1,6} \l {CC_ToolButton} \o{1,6} \l QStyleOptionToolButton + \o \l State_Enabled \o Set if the tool button is enabled. + \row \o \l State_HasFocus \o Set if the tool button has input focus. + \row \o \l State_DownArrow \o Set if the tool button is down (i.e., a mouse + button or the space bar is pressed). + \row \o \l State_On \o Set if the tool button is a toggle button + and is toggled on. + \row \o \l State_AutoRaise \o Set if the tool button has auto-raise enabled. + \row \o \l State_Raised \o Set if the button is not down, not on, and doesn't + contain the mouse when auto-raise is enabled. + + \row \o \l{CC_TitleBar} \o \l QStyleOptionTitleBar + \o \l State_Enabled \o Set if the title bar is enabled. + + \row \o \l{CC_Q3ListView} \o \l QStyleOptionQ3ListView + \o \l State_Enabled \o Set if the list view is enabled. + + \endtable + + \sa drawPrimitive(), drawControl() +*/ + + +/*! + \fn QRect QStyle::subControlRect(ComplexControl control, + const QStyleOptionComplex *option, SubControl subControl, + const QWidget *widget) const = 0 + + Returns the rectangle containing the specified \a subControl of + the given complex \a control (with the style specified by \a + option). The rectangle is defined in screen coordinates. + + The \a option argument is a pointer to QStyleOptionComplex or + one of its subclasses, and can be cast to the appropriate type + using the qstyleoption_cast() function. See drawComplexControl() + for details. The \a widget is optional and can contain additional + information for the function. + + \sa drawComplexControl() +*/ + +/*! + \fn QStyle::SubControl QStyle::hitTestComplexControl(ComplexControl control, + const QStyleOptionComplex *option, const QPoint &position, + const QWidget *widget) const = 0 + + Returns the sub control at the given \a position in the given + complex \a control (with the style options specified by \a + option). + + Note that the \a position is expressed in screen coordinates. + + The \a option argument is a pointer to a QStyleOptionComplex + object (or one of its subclasses). The object can be cast to the + appropriate type using the qstyleoption_cast() function. See + drawComplexControl() for details. The \a widget argument is + optional and can contain additional information for the function. + + \sa drawComplexControl(), subControlRect() +*/ + +/*! + \enum QStyle::PixelMetric + + This enum describes the various available pixel metrics. A pixel + metric is a style dependent size represented by a single pixel + value. + + \value PM_ButtonMargin Amount of whitespace between push button + labels and the frame. + \value PM_DockWidgetTitleBarButtonMargin Amount of whitespace between dock widget's + title bar button labels and the frame. + \value PM_ButtonDefaultIndicator Width of the default-button indicator frame. + \value PM_MenuButtonIndicator Width of the menu button indicator + proportional to the widget height. + \value PM_ButtonShiftHorizontal Horizontal contents shift of a + button when the button is down. + \value PM_ButtonShiftVertical Vertical contents shift of a button when the + button is down. + + \value PM_DefaultFrameWidth Default frame width (usually 2). + \value PM_SpinBoxFrameWidth Frame width of a spin box, defaults to PM_DefaultFrameWidth. + \value PM_ComboBoxFrameWidth Frame width of a combo box, defaults to PM_DefaultFrameWidth. + + \value PM_MDIFrameWidth Obsolete. Use PM_MdiSubWindowFrameWidth instead. + \value PM_MdiSubWindowFrameWidth Frame width of an MDI window. + \value PM_MDIMinimizedWidth Obsolete. Use PM_MdiSubWindowMinimizedWidth instead. + \value PM_MdiSubWindowMinimizedWidth Width of a minimized MDI window. + + \value PM_LayoutLeftMargin Default \l{QLayout::setContentsMargins()}{left margin} for a + QLayout. + \value PM_LayoutTopMargin Default \l{QLayout::setContentsMargins()}{top margin} for a QLayout. + \value PM_LayoutRightMargin Default \l{QLayout::setContentsMargins()}{right margin} for a + QLayout. + \value PM_LayoutBottomMargin Default \l{QLayout::setContentsMargins()}{bottom margin} for a + QLayout. + \value PM_LayoutHorizontalSpacing Default \l{QLayout::spacing}{horizontal spacing} for a + QLayout. + \value PM_LayoutVerticalSpacing Default \l{QLayout::spacing}{vertical spacing} for a QLayout. + + \value PM_MaximumDragDistance The maximum allowed distance between + the mouse and a scrollbar when dragging. Exceeding the specified + distance will cause the slider to jump back to the original + position; a value of -1 disables this behavior. + + \value PM_ScrollBarExtent Width of a vertical scroll bar and the + height of a horizontal scroll bar. + \value PM_ScrollBarSliderMin The minimum height of a vertical + scroll bar's slider and the minimum width of a horizontal + scroll bar's slider. + + \value PM_SliderThickness Total slider thickness. + \value PM_SliderControlThickness Thickness of the slider handle. + \value PM_SliderLength Length of the slider. + \value PM_SliderTickmarkOffset The offset between the tickmarks + and the slider. + \value PM_SliderSpaceAvailable The available space for the slider to move. + + \value PM_DockWidgetSeparatorExtent Width of a separator in a + horizontal dock window and the height of a separator in a + vertical dock window. + \value PM_DockWidgetHandleExtent Width of the handle in a + horizontal dock window and the height of the handle in a + vertical dock window. + \value PM_DockWidgetFrameWidth Frame width of a dock window. + \value PM_DockWidgetTitleMargin Margin of the dock window title. + + \value PM_MenuBarPanelWidth Frame width of a menu bar, defaults to PM_DefaultFrameWidth. + \value PM_MenuBarItemSpacing Spacing between menu bar items. + \value PM_MenuBarHMargin Spacing between menu bar items and left/right of bar. + \value PM_MenuBarVMargin Spacing between menu bar items and top/bottom of bar. + + \value PM_ToolBarFrameWidth Width of the frame around toolbars. + \value PM_ToolBarHandleExtent Width of a toolbar handle in a + horizontal toolbar and the height of the handle in a vertical toolbar. + \value PM_ToolBarItemMargin Spacing between the toolbar frame and the items. + \value PM_ToolBarItemSpacing Spacing between toolbar items. + \value PM_ToolBarSeparatorExtent Width of a toolbar separator in a + horizontal toolbar and the height of a separator in a vertical toolbar. + \value PM_ToolBarExtensionExtent Width of a toolbar extension + button in a horizontal toolbar and the height of the button in a + vertical toolbar. + + \value PM_TabBarTabOverlap Number of pixels the tabs should overlap. + (Currently only used in styles, not inside of QTabBar) + \value PM_TabBarTabHSpace Extra space added to the tab width. + \value PM_TabBarTabVSpace Extra space added to the tab height. + \value PM_TabBarBaseHeight Height of the area between the tab bar + and the tab pages. + \value PM_TabBarBaseOverlap Number of pixels the tab bar overlaps + the tab bar base. + \value PM_TabBarScrollButtonWidth + \value PM_TabBarTabShiftHorizontal Horizontal pixel shift when a + tab is selected. + \value PM_TabBarTabShiftVertical Vertical pixel shift when a + tab is selected. + + \value PM_ProgressBarChunkWidth Width of a chunk in a progress bar indicator. + + \value PM_SplitterWidth Width of a splitter. + + \value PM_TitleBarHeight Height of the title bar. + + \value PM_IndicatorWidth Width of a check box indicator. + \value PM_IndicatorHeight Height of a checkbox indicator. + \value PM_ExclusiveIndicatorWidth Width of a radio button indicator. + \value PM_ExclusiveIndicatorHeight Height of a radio button indicator. + + \value PM_MenuPanelWidth Border width (applied on all sides) for a QMenu. + \value PM_MenuHMargin Additional border (used on left and right) for a QMenu. + \value PM_MenuVMargin Additional border (used for bottom and top) for a QMenu. + \value PM_MenuScrollerHeight Height of the scroller area in a QMenu. + \value PM_MenuTearoffHeight Height of a tear off area in a QMenu. + \value PM_MenuDesktopFrameWidth The frame width for the menu on the desktop. + + \value PM_CheckListButtonSize Area (width/height) of the + checkbox/radio button in a Q3CheckListItem. + \value PM_CheckListControllerSize Area (width/height) of the + controller in a Q3CheckListItem. + + \omitvalue PM_DialogButtonsSeparator + \omitvalue PM_DialogButtonsButtonWidth + \omitvalue PM_DialogButtonsButtonHeight + + \value PM_HeaderMarkSize The size of the sort indicator in a header. + \value PM_HeaderGripMargin The size of the resize grip in a header. + \value PM_HeaderMargin The size of the margin between the sort indicator and the text. + \value PM_SpinBoxSliderHeight The height of the optional spin box slider. + + \value PM_ToolBarIconSize Default tool bar icon size + \value PM_SmallIconSize Default small icon size + \value PM_LargeIconSize Default large icon size + + \value PM_FocusFrameHMargin Horizontal margin that the focus frame will outset the widget by. + \value PM_FocusFrameVMargin Vertical margin that the focus frame will outset the widget by. + \value PM_IconViewIconSize The default size for icons in an icon view. + \value PM_ListViewIconSize The default size for icons in a list view. + + \value PM_ToolTipLabelFrameWidth The frame width for a tool tip label. + \value PM_CheckBoxLabelSpacing The spacing between a check box indicator and its label. + \value PM_RadioButtonLabelSpacing The spacing between a radio button indicator and its label. + \value PM_TabBarIconSize The default icon size for a tab bar. + \value PM_SizeGripSize The size of a size grip. + \value PM_MessageBoxIconSize The size of the standard icons in a message box + \value PM_ButtonIconSize The default size of button icons + \value PM_TextCursorWidth The width of the cursor in a line edit or text edit + \value PM_TabBar_ScrollButtonOverlap The distance between the left and right buttons in a tab bar. + + \value PM_TabCloseIndicatorWidth The default width of a close button on a tab in a tab bar. + \value PM_TabCloseIndicatorHeight The default height of a close button on a tab in a tab bar. + + \value PM_CustomBase Base value for custom pixel metrics. Custom + values must be greater than this value. + + The following values are obsolete: + + \value PM_DefaultTopLevelMargin Use PM_LayoutLeftMargin, + PM_LayoutTopMargin, + PM_LayoutRightMargin, and + PM_LayoutBottomMargin instead. + \value PM_DefaultChildMargin Use PM_LayoutLeftMargin, + PM_LayoutTopMargin, + PM_LayoutRightMargin, and + PM_LayoutBottomMargin instead. + \value PM_DefaultLayoutSpacing Use PM_LayoutHorizontalSpacing + and PM_LayoutVerticalSpacing + instead. + + \value PM_ScrollView_ScrollBarSpacing Distance between frame and scrollbar + with SH_ScrollView_FrameOnlyAroundContents set. + \value PM_SubMenuOverlap The horizontal overlap between a submenu and its parent. + + + \sa pixelMetric() +*/ + +/*! + \fn int QStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const; + + Returns the value of the given pixel \a metric. + + The specified \a option and \a widget can be used for calculating + the metric. In general, the \a widget argument is not used. The \a + option can be cast to the appropriate type using the + qstyleoption_cast() function. Note that the \a option may be zero + even for PixelMetrics that can make use of it. See the table below + for the appropriate \a option casts: + + \table + \header \o Pixel Metric \o QStyleOption Subclass + \row \o \l PM_SliderControlThickness \o \l QStyleOptionSlider + \row \o \l PM_SliderLength \o \l QStyleOptionSlider + \row \o \l PM_SliderTickmarkOffset \o \l QStyleOptionSlider + \row \o \l PM_SliderSpaceAvailable \o \l QStyleOptionSlider + \row \o \l PM_ScrollBarExtent \o \l QStyleOptionSlider + \row \o \l PM_TabBarTabOverlap \o \l QStyleOptionTab + \row \o \l PM_TabBarTabHSpace \o \l QStyleOptionTab + \row \o \l PM_TabBarTabVSpace \o \l QStyleOptionTab + \row \o \l PM_TabBarBaseHeight \o \l QStyleOptionTab + \row \o \l PM_TabBarBaseOverlap \o \l QStyleOptionTab + \endtable + + Some pixel metrics are called from widgets and some are only called + internally by the style. If the metric is not called by a widget, it is the + discretion of the style author to make use of it. For some styles, this + may not be appropriate. +*/ + +/*! + \enum QStyle::ContentsType + + This enum describes the available contents types. These are used to + calculate sizes for the contents of various widgets. + + \value CT_CheckBox A check box, like QCheckBox. + \value CT_ComboBox A combo box, like QComboBox. + \omitvalue CT_DialogButtons + \value CT_Q3DockWindow A Q3DockWindow. + \value CT_HeaderSection A header section, like QHeader. + \value CT_LineEdit A line edit, like QLineEdit. + \value CT_Menu A menu, like QMenu. + \value CT_Q3Header A Qt 3 header section, like Q3Header. + \value CT_MenuBar A menu bar, like QMenuBar. + \value CT_MenuBarItem A menu bar item, like the buttons in a QMenuBar. + \value CT_MenuItem A menu item, like QMenuItem. + \value CT_ProgressBar A progress bar, like QProgressBar. + \value CT_PushButton A push button, like QPushButton. + \value CT_RadioButton A radio button, like QRadioButton. + \value CT_SizeGrip A size grip, like QSizeGrip. + \value CT_Slider A slider, like QSlider. + \value CT_ScrollBar A scroll bar, like QScrollBar. + \value CT_SpinBox A spin box, like QSpinBox. + \value CT_Splitter A splitter, like QSplitter. + \value CT_TabBarTab A tab on a tab bar, like QTabBar. + \value CT_TabWidget A tab widget, like QTabWidget. + \value CT_ToolButton A tool button, like QToolButton. + \value CT_GroupBox A group box, like QGroupBox. + \value CT_ItemViewItem An item inside an item view. + + \value CT_CustomBase Base value for custom contents types. + Custom values must be greater than this value. + + \value CT_MdiControls The minimize, normal, and close button + in the menu bar for a maximized MDI + subwindow. + + \sa sizeFromContents() +*/ + +/*! + \fn QSize QStyle::sizeFromContents(ContentsType type, const QStyleOption *option, \ + const QSize &contentsSize, const QWidget *widget) const + + Returns the size of the element described by the specified + \a option and \a type, based on the provided \a contentsSize. + + The \a option argument is a pointer to a QStyleOption or one of + its subclasses. The \a option can be cast to the appropriate type + using the qstyleoption_cast() function. The \a widget is an + optional argument and can contain extra information used for + calculating the size. + + See the table below for the appropriate \a option casts: + + \table + \header \o Contents Type \o QStyleOption Subclass + \row \o \l CT_PushButton \o \l QStyleOptionButton + \row \o \l CT_CheckBox \o \l QStyleOptionButton + \row \o \l CT_RadioButton \o \l QStyleOptionButton + \row \o \l CT_ToolButton \o \l QStyleOptionToolButton + \row \o \l CT_ComboBox \o \l QStyleOptionComboBox + \row \o \l CT_Splitter \o \l QStyleOption + \row \o \l CT_Q3DockWindow \o \l QStyleOptionQ3DockWindow + \row \o \l CT_ProgressBar \o \l QStyleOptionProgressBar + \row \o \l CT_MenuItem \o \l QStyleOptionMenuItem + \endtable + + \sa ContentsType QStyleOption +*/ + +/*! + \enum QStyle::RequestSoftwareInputPanel + + This enum describes under what circumstances a software input panel will be + requested by input capable widgets. + + \value RSIP_OnMouseClickAndAlreadyFocused Requests an input panel if the user + clicks on the widget, but only if it is already focused. + \value RSIP_OnMouseClick Requests an input panel if the user clicks on the + widget. + + \sa QEvent::RequestSoftwareInputPanel, QInputContext +*/ + +/*! + \enum QStyle::StyleHint + + This enum describes the available style hints. A style hint is a general look + and/or feel hint. + + \value SH_EtchDisabledText Disabled text is "etched" as it is on Windows. + + \value SH_DitherDisabledText Disabled text is dithered as it is on Motif. + + \value SH_GUIStyle The GUI style to use. + + \value SH_ScrollBar_ContextMenu Whether or not a scroll bar has a context menu. + + \value SH_ScrollBar_MiddleClickAbsolutePosition A boolean value. + If true, middle clicking on a scroll bar causes the slider to + jump to that position. If false, middle clicking is + ignored. + + \value SH_ScrollBar_LeftClickAbsolutePosition A boolean value. + If true, left clicking on a scroll bar causes the slider to + jump to that position. If false, left clicking will + behave as appropriate for each control. + + \value SH_ScrollBar_ScrollWhenPointerLeavesControl A boolean + value. If true, when clicking a scroll bar SubControl, holding + the mouse button down and moving the pointer outside the + SubControl, the scroll bar continues to scroll. If false, the + scollbar stops scrolling when the pointer leaves the + SubControl. + + \value SH_ScrollBar_RollBetweenButtons A boolean value. + If true, when clicking a scroll bar button (SC_ScrollBarAddLine or + SC_ScrollBarSubLine) and dragging over to the opposite button (rolling) + will press the new button and release the old one. When it is false, the + original button is released and nothing happens (like a push button). + + \value SH_TabBar_Alignment The alignment for tabs in a + QTabWidget. Possible values are Qt::AlignLeft, + Qt::AlignCenter and Qt::AlignRight. + + \value SH_Header_ArrowAlignment The placement of the sorting + indicator may appear in list or table headers. Possible values + are Qt::Left or Qt::Right. + + \value SH_Slider_SnapToValue Sliders snap to values while moving, + as they do on Windows. + + \value SH_Slider_SloppyKeyEvents Key presses handled in a sloppy + manner, i.e., left on a vertical slider subtracts a line. + + \value SH_ProgressDialog_CenterCancelButton Center button on + progress dialogs, like Motif, otherwise right aligned. + + \value SH_ProgressDialog_TextLabelAlignment The alignment for text + labels in progress dialogs; Qt::AlignCenter on Windows, + Qt::AlignVCenter otherwise. + + \value SH_PrintDialog_RightAlignButtons Right align buttons in + the print dialog, as done on Windows. + + \value SH_MainWindow_SpaceBelowMenuBar One or two pixel space between + the menu bar and the dockarea, as done on Windows. + + \value SH_FontDialog_SelectAssociatedText Select the text in the + line edit, or when selecting an item from the listbox, or when + the line edit receives focus, as done on Windows. + + \value SH_Menu_KeyboardSearch Typing causes a menu to be search + for relevant items, otherwise only mnemnonic is considered. + + \value SH_Menu_AllowActiveAndDisabled Allows disabled menu + items to be active. + + \value SH_Menu_SpaceActivatesItem Pressing the space bar activates + the item, as done on Motif. + + \value SH_Menu_SubMenuPopupDelay The number of milliseconds + to wait before opening a submenu (256 on Windows, 96 on Motif). + + \value SH_Menu_Scrollable Whether popup menus must support scrolling. + + \value SH_Menu_SloppySubMenus Whether popupmenu's must support + sloppy submenu; as implemented on Mac OS. + + \value SH_ScrollView_FrameOnlyAroundContents Whether scrollviews + draw their frame only around contents (like Motif), or around + contents, scroll bars and corner widgets (like Windows). + + \value SH_MenuBar_AltKeyNavigation Menu bars items are navigable + by pressing Alt, followed by using the arrow keys to select + the desired item. + + \value SH_ComboBox_ListMouseTracking Mouse tracking in combobox + drop-down lists. + + \value SH_Menu_MouseTracking Mouse tracking in popup menus. + + \value SH_MenuBar_MouseTracking Mouse tracking in menu bars. + + \value SH_Menu_FillScreenWithScroll Whether scrolling popups + should fill the screen as they are scrolled. + + \value SH_Menu_SelectionWrap Whether popups should allow the selections + to wrap, that is when selection should the next item be the first item. + + \value SH_ItemView_ChangeHighlightOnFocus Gray out selected items + when losing focus. + + \value SH_Widget_ShareActivation Turn on sharing activation with + floating modeless dialogs. + + \value SH_TabBar_SelectMouseType Which type of mouse event should + cause a tab to be selected. + + \value SH_Q3ListViewExpand_SelectMouseType Which type of mouse event should + cause a list view expansion to be selected. + + \value SH_TabBar_PreferNoArrows Whether a tab bar should suggest a size + to prevent scoll arrows. + + \value SH_ComboBox_Popup Allows popups as a combobox drop-down + menu. + + \value SH_Workspace_FillSpaceOnMaximize The workspace should + maximize the client area. + + \value SH_TitleBar_NoBorder The title bar has no border. + + \value SH_ScrollBar_StopMouseOverSlider Obsolete. Use + SH_Slider_StopMouseOverSlider instead. + + \value SH_Slider_StopMouseOverSlider Stops auto-repeat when + the slider reaches the mouse position. + + \value SH_BlinkCursorWhenTextSelected Whether cursor should blink + when text is selected. + + \value SH_RichText_FullWidthSelection Whether richtext selections + should extend to the full width of the document. + + \value SH_GroupBox_TextLabelVerticalAlignment How to vertically align a + group box's text label. + + \value SH_GroupBox_TextLabelColor How to paint a group box's text label. + + \value SH_DialogButtons_DefaultButton Which button gets the + default status in a dialog's button widget. + + \value SH_ToolBox_SelectedPageTitleBold Boldness of the selected + page title in a QToolBox. + + \value SH_LineEdit_PasswordCharacter The Unicode character to be + used for passwords. + + \value SH_Table_GridLineColor The RGB value of the grid for a table. + + \value SH_UnderlineShortcut Whether shortcuts are underlined. + + \value SH_SpellCheckUnderlineStyle A + QTextCharFormat::UnderlineStyle value that specifies the way + misspelled words should be underlined. + + \value SH_SpinBox_AnimateButton Animate a click when up or down is + pressed in a spin box. + \value SH_SpinBox_KeyPressAutoRepeatRate Auto-repeat interval for + spinbox key presses. + \value SH_SpinBox_ClickAutoRepeatRate Auto-repeat interval for + spinbox mouse clicks. + \value SH_SpinBox_ClickAutoRepeatThreshold Auto-repeat threshold for + spinbox mouse clicks. + \value SH_ToolTipLabel_Opacity An integer indicating the opacity for + the tip label, 0 is completely transparent, 255 is completely + opaque. + \value SH_DrawMenuBarSeparator Indicates whether or not the menu bar draws separators. + \value SH_TitleBar_ModifyNotification Indicates if the title bar should show + a '*' for windows that are modified. + + \value SH_Button_FocusPolicy The default focus policy for buttons. + + \value SH_CustomBase Base value for custom style hints. + Custom values must be greater than this value. + + \value SH_MenuBar_DismissOnSecondClick A boolean indicating if a menu in + the menu bar should be dismissed when it is clicked on a second time. (Example: + Clicking and releasing on the File Menu in a menu bar and then + immediately clicking on the File Menu again.) + + \value SH_MessageBox_UseBorderForButtonSpacing A boolean indicating what the to + use the border of the buttons (computed as half the button height) for the spacing + of the button in a message box. + + \value SH_MessageBox_CenterButtons A boolean indicating whether the buttons in the + message box should be centered or not (see QDialogButtonBox::setCentered()). + + \value SH_MessageBox_TextInteractionFlags A boolean indicating if + the text in a message box should allow user interfactions (e.g. + selection) or not. + + \value SH_TitleBar_AutoRaise A boolean indicating whether + controls on a title bar ought to update when the mouse is over them. + + \value SH_ToolButton_PopupDelay An int indicating the popup delay in milliseconds + for menus attached to tool buttons. + + \value SH_FocusFrame_Mask The mask of the focus frame. + + \value SH_RubberBand_Mask The mask of the rubber band. + + \value SH_WindowFrame_Mask The mask of the window frame. + + \value SH_SpinControls_DisableOnBounds Determines if the spin controls will shown + as disabled when reaching the spin range boundary. + + \value SH_Dial_BackgroundRole Defines the style's preferred + background role (as QPalette::ColorRole) for a dial widget. + + \value SH_ScrollBar_BackgroundMode The background mode for a scroll bar. + + \value SH_ComboBox_LayoutDirection The layout direction for the + combo box. By default it should be the same as indicated by the + QStyleOption::direction variable. + + \value SH_ItemView_EllipsisLocation The location where ellipses should be + added for item text that is too long to fit in an view item. + + \value SH_ItemView_ShowDecorationSelected When an item in an item + view is selected, also highlight the branch or other decoration. + + \value SH_ItemView_ActivateItemOnSingleClick Emit the activated signal + when the user single clicks on an item in an item in an item view. + Otherwise the signal is emitted when the user double clicks on an item. + + \value SH_Slider_AbsoluteSetButtons Which mouse buttons cause a slider + to set the value to the position clicked on. + + \value SH_Slider_PageSetButtons Which mouse buttons cause a slider + to page step the value. + + \value SH_TabBar_ElideMode The default eliding style for a tab bar. + + \value SH_DialogButtonLayout Controls how buttons are laid out in a QDialogButtonBox, returns a QDialogButtonBox::ButtonLayout enum. + + \value SH_WizardStyle Controls the look and feel of a QWizard. Returns a QWizard::WizardStyle enum. + + \value SH_FormLayoutWrapPolicy Provides a default for how rows are wrapped in a QFormLayout. Returns a QFormLayout::RowWrapPolicy enum. + \value SH_FormLayoutFieldGrowthPolicy Provides a default for how fields can grow in a QFormLayout. Returns a QFormLayout::FieldGrowthPolicy enum. + \value SH_FormLayoutFormAlignment Provides a default for how a QFormLayout aligns its contents within the available space. Returns a Qt::Alignment enum. + \value SH_FormLayoutLabelAlignment Provides a default for how a QFormLayout aligns labels within the available space. Returns a Qt::Alignment enum. + + \value SH_ItemView_ArrowKeysNavigateIntoChildren Controls whether the tree view will select the first child when it is exapanded and the right arrow key is pressed. + \value SH_ComboBox_PopupFrameStyle The frame style used when drawing a combobox popup menu. + + \value SH_DialogButtonBox_ButtonsHaveIcons Indicates whether or not StandardButtons in QDialogButtonBox should have icons or not. + \value SH_ItemView_MovementWithoutUpdatingSelection The item view is able to indicate a current item without changing the selection. + \value SH_ToolTip_Mask The mask of a tool tip. + + \value SH_FocusFrame_AboveWidget The FocusFrame is stacked above the widget that it is "focusing on". + + \value SH_TextControl_FocusIndicatorTextCharFormat Specifies the text format used to highlight focused anchors in rich text + documents displayed for example in QTextBrowser. The format has to be a QTextCharFormat returned in the variant of the + QStyleHintReturnVariant return value. The QTextFormat::OutlinePen property is used for the outline and QTextFormat::BackgroundBrush + for the background of the highlighted area. + + \value SH_Menu_FlashTriggeredItem Flash triggered item. + \value SH_Menu_FadeOutOnHide Fade out the menu instead of hiding it immediately. + + \value SH_TabWidget_DefaultTabPosition Default position of the tab bar in a tab widget. + + \value SH_ToolBar_Movable Determines if the tool bar is movable by default. + + \value SH_ItemView_PaintAlternatingRowColorsForEmptyArea Whether QTreeView paints alternating row colors for the area that does not have any items. + + \value SH_Menu_Mask The mask for a popup menu. + + \value SH_ItemView_DrawDelegateFrame Determines if there should be a frame for a delegate widget. + + \value SH_TabBar_CloseButtonPosition Determines the position of the close button on a tab in a tab bar. + + \value SH_DockWidget_ButtonsHaveFrame Determines if dockwidget buttons should have frames. Default is true. + + \value SH_ToolButtonStyle Determines the default system style for tool buttons that uses Qt::ToolButtonFollowStyle. + + \value SH_RequestSoftwareInputPanel Determines when a software input panel should + be requested by input widgets. Returns an enum of type QStyle::RequestSoftwareInputPanel. + + \omitvalue SH_UnderlineAccelerator + + \sa styleHint() +*/ + +/*! + \fn int QStyle::styleHint(StyleHint hint, const QStyleOption *option, \ + const QWidget *widget, QStyleHintReturn *returnData) const + + Returns an integer representing the specified style \a hint for + the given \a widget described by the provided style \a option. + + \a returnData is used when the querying widget needs more detailed data than + the integer that styleHint() returns. See the QStyleHintReturn class + description for details. +*/ + +/*! + \enum QStyle::StandardPixmap + + This enum describes the available standard pixmaps. A standard pixmap is a pixmap that + can follow some existing GUI style or guideline. + + \value SP_TitleBarMinButton Minimize button on title bars (e.g., + in QMdiSubWindow). + \value SP_TitleBarMenuButton Menu button on a title bar. + \value SP_TitleBarMaxButton Maximize button on title bars. + \value SP_TitleBarCloseButton Close button on title bars. + \value SP_TitleBarNormalButton Normal (restore) button on title bars. + \value SP_TitleBarShadeButton Shade button on title bars. + \value SP_TitleBarUnshadeButton Unshade button on title bars. + \value SP_TitleBarContextHelpButton The Context help button on title bars. + \value SP_MessageBoxInformation The "information" icon. + \value SP_MessageBoxWarning The "warning" icon. + \value SP_MessageBoxCritical The "critical" icon. + \value SP_MessageBoxQuestion The "question" icon. + \value SP_DesktopIcon The "desktop" icon. + \value SP_TrashIcon The "trash" icon. + \value SP_ComputerIcon The "My computer" icon. + \value SP_DriveFDIcon The floppy icon. + \value SP_DriveHDIcon The harddrive icon. + \value SP_DriveCDIcon The CD icon. + \value SP_DriveDVDIcon The DVD icon. + \value SP_DriveNetIcon The network icon. + \value SP_DirHomeIcon The home directory icon. + \value SP_DirOpenIcon The open directory icon. + \value SP_DirClosedIcon The closed directory icon. + \value SP_DirIcon The directory icon. + \value SP_DirLinkIcon The link to directory icon. + \value SP_FileIcon The file icon. + \value SP_FileLinkIcon The link to file icon. + \value SP_FileDialogStart The "start" icon in a file dialog. + \value SP_FileDialogEnd The "end" icon in a file dialog. + \value SP_FileDialogToParent The "parent directory" icon in a file dialog. + \value SP_FileDialogNewFolder The "create new folder" icon in a file dialog. + \value SP_FileDialogDetailedView The detailed view icon in a file dialog. + \value SP_FileDialogInfoView The file info icon in a file dialog. + \value SP_FileDialogContentsView The contents view icon in a file dialog. + \value SP_FileDialogListView The list view icon in a file dialog. + \value SP_FileDialogBack The back arrow in a file dialog. + \value SP_DockWidgetCloseButton Close button on dock windows (see also QDockWidget). + \value SP_ToolBarHorizontalExtensionButton Extension button for horizontal toolbars. + \value SP_ToolBarVerticalExtensionButton Extension button for vertical toolbars. + \value SP_DialogOkButton Icon for a standard OK button in a QDialogButtonBox. + \value SP_DialogCancelButton Icon for a standard Cancel button in a QDialogButtonBox. + \value SP_DialogHelpButton Icon for a standard Help button in a QDialogButtonBox. + \value SP_DialogOpenButton Icon for a standard Open button in a QDialogButtonBox. + \value SP_DialogSaveButton Icon for a standard Save button in a QDialogButtonBox. + \value SP_DialogCloseButton Icon for a standard Close button in a QDialogButtonBox. + \value SP_DialogApplyButton Icon for a standard Apply button in a QDialogButtonBox. + \value SP_DialogResetButton Icon for a standard Reset button in a QDialogButtonBox. + \value SP_DialogDiscardButton Icon for a standard Discard button in a QDialogButtonBox. + \value SP_DialogYesButton Icon for a standard Yes button in a QDialogButtonBox. + \value SP_DialogNoButton Icon for a standard No button in a QDialogButtonBox. + \value SP_ArrowUp Icon arrow pointing up. + \value SP_ArrowDown Icon arrow pointing down. + \value SP_ArrowLeft Icon arrow pointing left. + \value SP_ArrowRight Icon arrow pointing right. + \value SP_ArrowBack Equivalent to SP_ArrowLeft when the current layout direction is Qt::LeftToRight, otherwise SP_ArrowRight. + \value SP_ArrowForward Equivalent to SP_ArrowRight when the current layout direction is Qt::LeftToRight, otherwise SP_ArrowLeft. + \value SP_CommandLink Icon used to indicate a Vista style command link glyph. + \value SP_VistaShield Icon used to indicate UAC prompts on Windows Vista. This will return a null pixmap or icon on all other platforms. + \value SP_BrowserReload Icon indicating that the current page should be reloaded. + \value SP_BrowserStop Icon indicating that the page loading should stop. + \value SP_MediaPlay Icon indicating that media should begin playback. + \value SP_MediaStop Icon indicating that media should stop playback. + \value SP_MediaPause Icon indicating that media should pause playback. + \value SP_MediaSkipForward Icon indicating that media should skip forward. + \value SP_MediaSkipBackward Icon indicating that media should skip backward. + \value SP_MediaSeekForward Icon indicating that media should seek forward. + \value SP_MediaSeekBackward Icon indicating that media should seek backward. + \value SP_MediaVolume Icon indicating a volume control. + \value SP_MediaVolumeMuted Icon indicating a muted volume control. + \value SP_CustomBase Base value for custom standard pixmaps; + custom values must be greater than this value. + + \sa standardIcon() +*/ + +/*! + \fn QPixmap QStyle::generatedIconPixmap(QIcon::Mode iconMode, + const QPixmap &pixmap, const QStyleOption *option) const + + Returns a copy of the given \a pixmap, styled to conform to the + specified \a iconMode and taking into account the palette + specified by \a option. + + The \a option parameter can pass extra information, but + it must contain a palette. + + Note that not all pixmaps will conform, in which case the returned + pixmap is a plain copy. + + \sa QIcon +*/ + +/*! + \fn QPixmap QStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option, \ + const QWidget *widget) const + + \obsolete + Returns a pixmap for the given \a standardPixmap. + + A standard pixmap is a pixmap that can follow some existing GUI + style or guideline. The \a option argument can be used to pass + extra information required when defining the appropriate + pixmap. The \a widget argument is optional and can also be used to + aid the determination of the pixmap. + + Developers calling standardPixmap() should instead call standardIcon() + Developers who re-implemented standardPixmap() should instead re-implement + the slot standardIconImplementation(). + + \sa standardIcon() +*/ + + +/*! + \fn QRect QStyle::visualRect(Qt::LayoutDirection direction, const QRect &boundingRectangle, const QRect &logicalRectangle) + + Returns the given \a logicalRectangle converted to screen + coordinates based on the specified \a direction. The \a + boundingRectangle is used when performing the translation. + + This function is provided to support right-to-left desktops, and + is typically used in implementations of the subControlRect() + function. + + \sa QWidget::layoutDirection +*/ +QRect QStyle::visualRect(Qt::LayoutDirection direction, const QRect &boundingRect, const QRect &logicalRect) +{ + if (direction == Qt::LeftToRight) + return logicalRect; + QRect rect = logicalRect; + rect.translate(2 * (boundingRect.right() - logicalRect.right()) + + logicalRect.width() - boundingRect.width(), 0); + return rect; +} + +/*! + \fn QPoint QStyle::visualPos(Qt::LayoutDirection direction, const QRect &boundingRectangle, const QPoint &logicalPosition) + + Returns the given \a logicalPosition converted to screen + coordinates based on the specified \a direction. The \a + boundingRectangle is used when performing the translation. + + \sa QWidget::layoutDirection +*/ +QPoint QStyle::visualPos(Qt::LayoutDirection direction, const QRect &boundingRect, const QPoint &logicalPos) +{ + if (direction == Qt::LeftToRight) + return logicalPos; + return QPoint(boundingRect.right() - logicalPos.x(), logicalPos.y()); +} + +/*! + Returns a new rectangle of the specified \a size that is aligned to the given \a + rectangle according to the specified \a alignment and \a direction. + */ +QRect QStyle::alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSize &size, const QRect &rectangle) +{ + alignment = visualAlignment(direction, alignment); + int x = rectangle.x(); + int y = rectangle.y(); + int w = size.width(); + int h = size.height(); + if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter) + y += rectangle.size().height()/2 - h/2; + else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom) + y += rectangle.size().height() - h; + if ((alignment & Qt::AlignRight) == Qt::AlignRight) + x += rectangle.size().width() - w; + else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter) + x += rectangle.size().width()/2 - w/2; + return QRect(x, y, w, h); +} + +/*! + Transforms an \a alignment of Qt::AlignLeft or Qt::AlignRight + without Qt::AlignAbsolute into Qt::AlignLeft or Qt::AlignRight with + Qt::AlignAbsolute according to the layout \a direction. The other + alignment flags are left untouched. + + If no horizontal alignment was specified, the function returns the + default alignment for the given layout \a direction. + + QWidget::layoutDirection +*/ +Qt::Alignment QStyle::visualAlignment(Qt::LayoutDirection direction, Qt::Alignment alignment) +{ + if (!(alignment & Qt::AlignHorizontal_Mask)) + alignment |= Qt::AlignLeft; + if ((alignment & Qt::AlignAbsolute) == 0 && (alignment & (Qt::AlignLeft | Qt::AlignRight))) { + if (direction == Qt::RightToLeft) + alignment ^= (Qt::AlignLeft | Qt::AlignRight); + alignment |= Qt::AlignAbsolute; + } + return alignment; +} + +/*! + Converts the given \a logicalValue to a pixel position. The \a min + parameter maps to 0, \a max maps to \a span and other values are + distributed evenly in-between. + + This function can handle the entire integer range without + overflow, providing that \a span is less than 4096. + + By default, this function assumes that the maximum value is on the + right for horizontal items and on the bottom for vertical items. + Set the \a upsideDown parameter to true to reverse this behavior. + + \sa sliderValueFromPosition() +*/ + +int QStyle::sliderPositionFromValue(int min, int max, int logicalValue, int span, bool upsideDown) +{ + if (span <= 0 || logicalValue < min || max <= min) + return 0; + if (logicalValue > max) + return upsideDown ? span : min; + + uint range = max - min; + uint p = upsideDown ? max - logicalValue : logicalValue - min; + + if (range > (uint)INT_MAX/4096) { + double dpos = (double(p))/(double(range)/span); + return int(dpos); + } else if (range > (uint)span) { + return (2 * p * span + range) / (2*range); + } else { + uint div = span / range; + uint mod = span % range; + return p * div + (2 * p * mod + range) / (2 * range); + } + // equiv. to (p * span) / range + 0.5 + // no overflow because of this implicit assumption: + // span <= 4096 +} + +/*! + \fn int QStyle::sliderValueFromPosition(int min, int max, int position, int span, bool upsideDown) + + Converts the given pixel \a position to a logical value. 0 maps to + the \a min parameter, \a span maps to \a max and other values are + distributed evenly in-between. + + This function can handle the entire integer range without + overflow. + + By default, this function assumes that the maximum value is on the + right for horizontal items and on the bottom for vertical + items. Set the \a upsideDown parameter to true to reverse this + behavior. + + \sa sliderPositionFromValue() +*/ + +int QStyle::sliderValueFromPosition(int min, int max, int pos, int span, bool upsideDown) +{ + if (span <= 0 || pos <= 0) + return upsideDown ? max : min; + if (pos >= span) + return upsideDown ? min : max; + + uint range = max - min; + + if ((uint)span > range) { + int tmp = (2 * pos * range + span) / (2 * span); + return upsideDown ? max - tmp : tmp + min; + } else { + uint div = range / span; + uint mod = range % span; + int tmp = pos * div + (2 * pos * mod + span) / (2 * span); + return upsideDown ? max - tmp : tmp + min; + } + // equiv. to min + (pos*range)/span + 0.5 + // no overflow because of this implicit assumption: + // pos <= span < sqrt(INT_MAX+0.0625)+0.25 ~ sqrt(INT_MAX) +} + +/*### \fn void QStyle::drawItem(QPainter *p, const QRect &r, + int flags, const QColorGroup &colorgroup, bool enabled, + const QString &text, int len = -1, + const QColor *penColor = 0) const + + Use one of the drawItem() overloads that takes a QPalette instead + of a QColorGroup. +*/ + +/*### \fn void QStyle::drawItem(QPainter *p, const QRect &r, + int flags, const QColorGroup colorgroup, bool enabled, + const QPixmap &pixmap, + const QColor *penColor = 0) const + + Use one of the drawItem() overloads that takes a QPalette instead + of a QColorGroup. +*/ + +/*### \fn void QStyle::drawItem(QPainter *p, const QRect &r, + int flags, const QColorGroup colorgroup, bool enabled, + const QPixmap *pixmap, + const QString &text, int len = -1, + const QColor *penColor = 0) const + + Use one of the drawItem() overloads that takes a QPalette instead + of a QColorGroup. +*/ + +/*! + Returns the style's standard palette. + + Note that on systems that support system colors, the style's + standard palette is not used. In particular, the Windows XP, + Vista, and Mac styles do not use the standard palette, but make + use of native theme engines. With these styles, you should not set + the palette with QApplication::setStandardPalette(). + + */ +QPalette QStyle::standardPalette() const +{ +#ifdef Q_WS_X11 + QColor background; + if (QX11Info::appDepth() > 8) + background = QColor(0xd4, 0xd0, 0xc8); // win 2000 grey + else + background = QColor(192, 192, 192); +#else + QColor background(0xd4, 0xd0, 0xc8); // win 2000 grey +#endif + QColor light(background.lighter()); + QColor dark(background.darker()); + QColor mid(Qt::gray); + QPalette palette(Qt::black, background, light, dark, mid, Qt::black, Qt::white); + palette.setBrush(QPalette::Disabled, QPalette::WindowText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Text, dark); + palette.setBrush(QPalette::Disabled, QPalette::ButtonText, dark); + palette.setBrush(QPalette::Disabled, QPalette::Base, background); + return palette; +} + +/*! + \since 4.1 + + Returns an icon for the given \a standardIcon. + + The \a standardIcon is a standard pixmap which can follow some + existing GUI style or guideline. The \a option argument can be + used to pass extra information required when defining the + appropriate icon. The \a widget argument is optional and can also + be used to aid the determination of the icon. + + \warning Because of binary compatibility constraints, this + function is not virtual. If you want to provide your own icons in + a QStyle subclass, reimplement the standardIconImplementation() + slot in your subclass instead. The standardIcon() function will + dynamically detect the slot and call it. + + \sa standardIconImplementation() +*/ +QIcon QStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget) const +{ + QIcon result; + // ### Qt 4.1: invokeMethod should accept const functions, to avoid this dirty cast + QMetaObject::invokeMethod(const_cast<QStyle*>(this), + "standardIconImplementation", Qt::DirectConnection, + Q_RETURN_ARG(QIcon, result), + Q_ARG(StandardPixmap, standardIcon), + Q_ARG(const QStyleOption*, option), + Q_ARG(const QWidget*, widget)); + return result; +} + +/*! + \since 4.1 + + Returns an icon for the given \a standardIcon. + + Reimplement this slot to provide your own icons in a QStyle + subclass; because of binary compatibility constraints, the + standardIcon() function (introduced in Qt 4.1) is not + virtual. Instead, standardIcon() will dynamically detect and call + \e this slot. + + The \a standardIcon is a standard pixmap which can follow some + existing GUI style or guideline. The \a option argument can be + used to pass extra information required when defining the + appropriate icon. The \a widget argument is optional and can also + be used to aid the determination of the icon. + + \sa standardIcon() +*/ +QIcon QStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget) const +{ + return QIcon(standardPixmap(standardIcon, option, widget)); +} + +/*! + \since 4.3 + + Returns the spacing that should be used between \a control1 and + \a control2 in a layout. \a orientation specifies whether the + controls are laid out side by side or stacked vertically. The \a + option parameter can be used to pass extra information about the + parent widget. The \a widget parameter is optional and can also + be used if \a option is 0. + + This function is called by the layout system. It is used only if + PM_LayoutHorizontalSpacing or PM_LayoutVerticalSpacing returns a + negative value. + + For binary compatibility reasons, this function is not virtual. + If you want to specify custom layout spacings in a QStyle + subclass, implement a slot called layoutSpacingImplementation(). + QStyle will discover the slot at run-time (using Qt's + \l{meta-object system}) and direct all calls to layoutSpacing() + to layoutSpacingImplementation(). + + \sa combinedLayoutSpacing(), layoutSpacingImplementation() +*/ +int QStyle::layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, + Qt::Orientation orientation, const QStyleOption *option, + const QWidget *widget) const +{ + Q_D(const QStyle); + if (d->layoutSpacingIndex == -1) { + d->layoutSpacingIndex = metaObject()->indexOfMethod( + "layoutSpacingImplementation(QSizePolicy::ControlType,QSizePolicy::ControlType," + "Qt::Orientation,const QStyleOption*,const QWidget*)" + ); + } + if (d->layoutSpacingIndex < 0) + return -1; + int result = -1; + void *param[] = {&result, &control1, &control2, &orientation, &option, &widget}; + + const_cast<QStyle *>(this)->qt_metacall(QMetaObject::InvokeMetaMethod, + d->layoutSpacingIndex, param); + return result; +} + +/*! + \since 4.3 + + Returns the spacing that should be used between \a controls1 and + \a controls2 in a layout. \a orientation specifies whether the + controls are laid out side by side or stacked vertically. The \a + option parameter can be used to pass extra information about the + parent widget. The \a widget parameter is optional and can also + be used if \a option is 0. + + \a controls1 and \a controls2 are OR-combination of zero or more + \l{QSizePolicy::ControlTypes}{control types}. + + This function is called by the layout system. It is used only if + PM_LayoutHorizontalSpacing or PM_LayoutVerticalSpacing returns a + negative value. + + \sa layoutSpacing(), layoutSpacingImplementation() +*/ +int QStyle::combinedLayoutSpacing(QSizePolicy::ControlTypes controls1, + QSizePolicy::ControlTypes controls2, Qt::Orientation orientation, + QStyleOption *option, QWidget *widget) const +{ + QSizePolicy::ControlType array1[MaxBits]; + QSizePolicy::ControlType array2[MaxBits]; + int count1 = unpackControlTypes(controls1, array1); + int count2 = unpackControlTypes(controls2, array2); + int result = -1; + + for (int i = 0; i < count1; ++i) { + for (int j = 0; j < count2; ++j) { + int spacing = layoutSpacing(array1[i], array2[j], orientation, option, widget); + result = qMax(spacing, result); + } + } + return result; +} + +/*! + \since 4.3 + + This slot is called by layoutSpacing() to determine the spacing + that should be used between \a control1 and \a control2 in a + layout. \a orientation specifies whether the controls are laid + out side by side or stacked vertically. The \a option parameter + can be used to pass extra information about the parent widget. + The \a widget parameter is optional and can also be used if \a + option is 0. + + If you want to provide custom layout spacings in a QStyle + subclass, implement a slot called layoutSpacingImplementation() + in your subclass. Be aware that this slot will only be called if + PM_LayoutHorizontalSpacing or PM_LayoutVerticalSpacing returns a + negative value. + + The default implementation returns -1. + + \sa layoutSpacing(), combinedLayoutSpacing() +*/ +int QStyle::layoutSpacingImplementation(QSizePolicy::ControlType /* control1 */, + QSizePolicy::ControlType /* control2 */, + Qt::Orientation /*orientation*/, + const QStyleOption * /* option */, + const QWidget * /* widget */) const +{ + return -1; +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include <QDebug> +QT_END_INCLUDE_NAMESPACE + +#if !defined(QT_NO_DEBUG_STREAM) +QDebug operator<<(QDebug debug, QStyle::State state) +{ +#if !defined(QT_NO_DEBUG) + debug << "QStyle::State("; + + QStringList states; + if (state & QStyle::State_Active) states << QLatin1String("Active"); + if (state & QStyle::State_AutoRaise) states << QLatin1String("AutoRaise"); + if (state & QStyle::State_Bottom) states << QLatin1String("Bottom"); + if (state & QStyle::State_Children) states << QLatin1String("Children"); + if (state & QStyle::State_DownArrow) states << QLatin1String("DownArrow"); + if (state & QStyle::State_Editing) states << QLatin1String("Editing"); + if (state & QStyle::State_Enabled) states << QLatin1String("Enabled"); + if (state & QStyle::State_FocusAtBorder) states << QLatin1String("FocusAtBorder"); + if (state & QStyle::State_HasFocus) states << QLatin1String("HasFocus"); + if (state & QStyle::State_Horizontal) states << QLatin1String("Horizontal"); + if (state & QStyle::State_Item) states << QLatin1String("Item"); + if (state & QStyle::State_KeyboardFocusChange) states << QLatin1String("KeyboardFocusChange"); + if (state & QStyle::State_MouseOver) states << QLatin1String("MouseOver"); + if (state & QStyle::State_NoChange) states << QLatin1String("NoChange"); + if (state & QStyle::State_Off) states << QLatin1String("Off"); + if (state & QStyle::State_On) states << QLatin1String("On"); + if (state & QStyle::State_Open) states << QLatin1String("Open"); + if (state & QStyle::State_Raised) states << QLatin1String("Raised"); + if (state & QStyle::State_ReadOnly) states << QLatin1String("ReadOnly"); + if (state & QStyle::State_Selected) states << QLatin1String("Selected"); + if (state & QStyle::State_Sibling) states << QLatin1String("Sibling"); + if (state & QStyle::State_Sunken) states << QLatin1String("Sunken"); + if (state & QStyle::State_Top) states << QLatin1String("Top"); + if (state & QStyle::State_UpArrow) states << QLatin1String("UpArrow"); + + qSort(states); + debug << states.join(QLatin1String(" | ")); + debug << ')'; +#else + Q_UNUSED(state); +#endif + return debug; +} +#endif + +/*! + \since 4.6 + + \fn const QStyle *QStyle::proxy() const + + This function returns the current proxy for this style. + By default most styles will return themselves. However + when a proxy style is in use, it will allow the style to + call back into its proxy. +*/ +const QStyle * QStyle::proxy() const +{ + Q_D(const QStyle); + return d->proxyStyle; +} + +/* \internal + + This function sets the base style that style calls will be + redirected to. Note that ownership is not transferred. +*/ +void QStyle::setProxy(QStyle *style) +{ + Q_D(QStyle); + d->proxyStyle = style; +} + +QT_END_NAMESPACE diff --git a/src/gui/styles/qstyle.h b/src/gui/styles/qstyle.h new file mode 100644 index 0000000000..94f2ce102f --- /dev/null +++ b/src/gui/styles/qstyle.h @@ -0,0 +1,889 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLE_H +#define QSTYLE_H + +#include <QtCore/qobject.h> +#include <QtCore/qrect.h> +#include <QtCore/qsize.h> +#include <QtGui/qicon.h> +#include <QtGui/qpixmap.h> +#include <QtGui/qpalette.h> +#include <QtGui/qsizepolicy.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAction; +class QDebug; +class QTab; +class QFontMetrics; +class QStyleHintReturn; +class QStyleOption; +class QStyleOptionComplex; +class QStylePrivate; + +class Q_GUI_EXPORT QStyle : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QStyle) + +protected: + QStyle(QStylePrivate &dd); + +public: + QStyle(); + virtual ~QStyle(); + + virtual void polish(QWidget *); + virtual void unpolish(QWidget *); + + virtual void polish(QApplication *); + virtual void unpolish(QApplication *); + + virtual void polish(QPalette &); + + virtual QRect itemTextRect(const QFontMetrics &fm, const QRect &r, + int flags, bool enabled, + const QString &text) const; + + virtual QRect itemPixmapRect(const QRect &r, int flags, const QPixmap &pixmap) const; + + virtual void drawItemText(QPainter *painter, const QRect &rect, + int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + + virtual void drawItemPixmap(QPainter *painter, const QRect &rect, + int alignment, const QPixmap &pixmap) const; + + virtual QPalette standardPalette() const; + + enum StateFlag { + State_None = 0x00000000, +#ifdef QT3_SUPPORT + State_Default = State_None, +#endif + State_Enabled = 0x00000001, + State_Raised = 0x00000002, + State_Sunken = 0x00000004, + State_Off = 0x00000008, + State_NoChange = 0x00000010, + State_On = 0x00000020, + State_DownArrow = 0x00000040, + State_Horizontal = 0x00000080, + State_HasFocus = 0x00000100, + State_Top = 0x00000200, + State_Bottom = 0x00000400, + State_FocusAtBorder = 0x00000800, + State_AutoRaise = 0x00001000, + State_MouseOver = 0x00002000, + State_UpArrow = 0x00004000, + State_Selected = 0x00008000, + State_Active = 0x00010000, + State_Window = 0x00020000, + State_Open = 0x00040000, + State_Children = 0x00080000, + State_Item = 0x00100000, + State_Sibling = 0x00200000, + State_Editing = 0x00400000, + State_KeyboardFocusChange = 0x00800000, +#ifdef QT_KEYPAD_NAVIGATION + State_HasEditFocus = 0x01000000, +#endif + State_ReadOnly = 0x02000000, + State_Small = 0x04000000, + State_Mini = 0x08000000 + }; + Q_DECLARE_FLAGS(State, StateFlag) + +#ifdef QT3_SUPPORT + typedef State SFlags; +#endif + + enum PrimitiveElement { + PE_Q3CheckListController, + PE_Q3CheckListExclusiveIndicator, + PE_Q3CheckListIndicator, + PE_Q3DockWindowSeparator, + PE_Q3Separator, + + PE_Frame, + PE_FrameDefaultButton, + PE_FrameDockWidget, + PE_FrameFocusRect, + PE_FrameGroupBox, + PE_FrameLineEdit, + PE_FrameMenu, + PE_FrameStatusBar, // obsolete + PE_FrameStatusBarItem = PE_FrameStatusBar, + PE_FrameTabWidget, + PE_FrameWindow, + PE_FrameButtonBevel, + PE_FrameButtonTool, + PE_FrameTabBarBase, + + PE_PanelButtonCommand, + PE_PanelButtonBevel, + PE_PanelButtonTool, + PE_PanelMenuBar, + PE_PanelToolBar, + PE_PanelLineEdit, + + PE_IndicatorArrowDown, + PE_IndicatorArrowLeft, + PE_IndicatorArrowRight, + PE_IndicatorArrowUp, + PE_IndicatorBranch, + PE_IndicatorButtonDropDown, + PE_IndicatorViewItemCheck, + PE_IndicatorItemViewItemCheck = PE_IndicatorViewItemCheck, + PE_IndicatorCheckBox, + PE_IndicatorDockWidgetResizeHandle, + PE_IndicatorHeaderArrow, + PE_IndicatorMenuCheckMark, + PE_IndicatorProgressChunk, + PE_IndicatorRadioButton, + PE_IndicatorSpinDown, + PE_IndicatorSpinMinus, + PE_IndicatorSpinPlus, + PE_IndicatorSpinUp, + PE_IndicatorToolBarHandle, + PE_IndicatorToolBarSeparator, + PE_PanelTipLabel, + PE_IndicatorTabTear, + PE_PanelScrollAreaCorner, + + PE_Widget, + + PE_IndicatorColumnViewArrow, + PE_IndicatorItemViewItemDrop, + + PE_PanelItemViewItem, + PE_PanelItemViewRow, // ### Qt 5: remove + + PE_PanelStatusBar, + + PE_IndicatorTabClose, + PE_PanelMenu, + + // do not add any values below/greater this + PE_CustomBase = 0xf000000 + }; + + virtual void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const = 0; + enum ControlElement { + CE_PushButton, + CE_PushButtonBevel, + CE_PushButtonLabel, + + CE_CheckBox, + CE_CheckBoxLabel, + + CE_RadioButton, + CE_RadioButtonLabel, + + CE_TabBarTab, + CE_TabBarTabShape, + CE_TabBarTabLabel, + + CE_ProgressBar, + CE_ProgressBarGroove, + CE_ProgressBarContents, + CE_ProgressBarLabel, + + CE_MenuItem, + CE_MenuScroller, + CE_MenuVMargin, + CE_MenuHMargin, + CE_MenuTearoff, + CE_MenuEmptyArea, + + CE_MenuBarItem, + CE_MenuBarEmptyArea, + + CE_ToolButtonLabel, + + CE_Header, + CE_HeaderSection, + CE_HeaderLabel, + + CE_Q3DockWindowEmptyArea, + CE_ToolBoxTab, + CE_SizeGrip, + CE_Splitter, + CE_RubberBand, + CE_DockWidgetTitle, + + CE_ScrollBarAddLine, + CE_ScrollBarSubLine, + CE_ScrollBarAddPage, + CE_ScrollBarSubPage, + CE_ScrollBarSlider, + CE_ScrollBarFirst, + CE_ScrollBarLast, + + CE_FocusFrame, + CE_ComboBoxLabel, + + CE_ToolBar, + CE_ToolBoxTabShape, + CE_ToolBoxTabLabel, + CE_HeaderEmptyArea, + + CE_ColumnViewGrip, + + CE_ItemViewItem, + + CE_ShapedFrame, + + // do not add any values below/greater than this + CE_CustomBase = 0xf0000000 + }; + + virtual void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const = 0; + + enum SubElement { + SE_PushButtonContents, + SE_PushButtonFocusRect, + + SE_CheckBoxIndicator, + SE_CheckBoxContents, + SE_CheckBoxFocusRect, + SE_CheckBoxClickRect, + + SE_RadioButtonIndicator, + SE_RadioButtonContents, + SE_RadioButtonFocusRect, + SE_RadioButtonClickRect, + + SE_ComboBoxFocusRect, + + SE_SliderFocusRect, + + SE_Q3DockWindowHandleRect, + + SE_ProgressBarGroove, + SE_ProgressBarContents, + SE_ProgressBarLabel, + + // ### Qt 5: These values are unused; eliminate them + SE_DialogButtonAccept, + SE_DialogButtonReject, + SE_DialogButtonApply, + SE_DialogButtonHelp, + SE_DialogButtonAll, + SE_DialogButtonAbort, + SE_DialogButtonIgnore, + SE_DialogButtonRetry, + SE_DialogButtonCustom, + + SE_ToolBoxTabContents, + + SE_HeaderLabel, + SE_HeaderArrow, + + SE_TabWidgetTabBar, + SE_TabWidgetTabPane, + SE_TabWidgetTabContents, + SE_TabWidgetLeftCorner, + SE_TabWidgetRightCorner, + + SE_ViewItemCheckIndicator, + SE_ItemViewItemCheckIndicator = SE_ViewItemCheckIndicator, + + SE_TabBarTearIndicator, + + SE_TreeViewDisclosureItem, + + SE_LineEditContents, + SE_FrameContents, + + SE_DockWidgetCloseButton, + SE_DockWidgetFloatButton, + SE_DockWidgetTitleBarText, + SE_DockWidgetIcon, + + SE_CheckBoxLayoutItem, + SE_ComboBoxLayoutItem, + SE_DateTimeEditLayoutItem, + SE_DialogButtonBoxLayoutItem, // ### remove + SE_LabelLayoutItem, + SE_ProgressBarLayoutItem, + SE_PushButtonLayoutItem, + SE_RadioButtonLayoutItem, + SE_SliderLayoutItem, + SE_SpinBoxLayoutItem, + SE_ToolButtonLayoutItem, + + SE_FrameLayoutItem, + SE_GroupBoxLayoutItem, + SE_TabWidgetLayoutItem, + + SE_ItemViewItemDecoration, + SE_ItemViewItemText, + SE_ItemViewItemFocusRect, + + SE_TabBarTabLeftButton, + SE_TabBarTabRightButton, + SE_TabBarTabText, + + SE_ShapedFrameContents, + + SE_ToolBarHandle, + + // do not add any values below/greater than this + SE_CustomBase = 0xf0000000 + }; + + virtual QRect subElementRect(SubElement subElement, const QStyleOption *option, + const QWidget *widget = 0) const = 0; + + + enum ComplexControl { + CC_SpinBox, + CC_ComboBox, + CC_ScrollBar, + CC_Slider, + CC_ToolButton, + CC_TitleBar, + CC_Q3ListView, + CC_Dial, + CC_GroupBox, + CC_MdiControls, + + // do not add any values below/greater than this + CC_CustomBase = 0xf0000000 + }; + + enum SubControl { + SC_None = 0x00000000, + + SC_ScrollBarAddLine = 0x00000001, + SC_ScrollBarSubLine = 0x00000002, + SC_ScrollBarAddPage = 0x00000004, + SC_ScrollBarSubPage = 0x00000008, + SC_ScrollBarFirst = 0x00000010, + SC_ScrollBarLast = 0x00000020, + SC_ScrollBarSlider = 0x00000040, + SC_ScrollBarGroove = 0x00000080, + + SC_SpinBoxUp = 0x00000001, + SC_SpinBoxDown = 0x00000002, + SC_SpinBoxFrame = 0x00000004, + SC_SpinBoxEditField = 0x00000008, + + SC_ComboBoxFrame = 0x00000001, + SC_ComboBoxEditField = 0x00000002, + SC_ComboBoxArrow = 0x00000004, + SC_ComboBoxListBoxPopup = 0x00000008, + + SC_SliderGroove = 0x00000001, + SC_SliderHandle = 0x00000002, + SC_SliderTickmarks = 0x00000004, + + SC_ToolButton = 0x00000001, + SC_ToolButtonMenu = 0x00000002, + + SC_TitleBarSysMenu = 0x00000001, + SC_TitleBarMinButton = 0x00000002, + SC_TitleBarMaxButton = 0x00000004, + SC_TitleBarCloseButton = 0x00000008, + SC_TitleBarNormalButton = 0x00000010, + SC_TitleBarShadeButton = 0x00000020, + SC_TitleBarUnshadeButton = 0x00000040, + SC_TitleBarContextHelpButton = 0x00000080, + SC_TitleBarLabel = 0x00000100, + + SC_Q3ListView = 0x00000001, + SC_Q3ListViewBranch = 0x00000002, + SC_Q3ListViewExpand = 0x00000004, + + SC_DialGroove = 0x00000001, + SC_DialHandle = 0x00000002, + SC_DialTickmarks = 0x00000004, + + SC_GroupBoxCheckBox = 0x00000001, + SC_GroupBoxLabel = 0x00000002, + SC_GroupBoxContents = 0x00000004, + SC_GroupBoxFrame = 0x00000008, + + SC_MdiMinButton = 0x00000001, + SC_MdiNormalButton = 0x00000002, + SC_MdiCloseButton = 0x00000004, + + SC_CustomBase = 0xf0000000, + SC_All = 0xffffffff + }; + Q_DECLARE_FLAGS(SubControls, SubControl) + +#ifdef QT3_SUPPORT + typedef SubControls SCFlags; +#endif + + virtual void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *widget = 0) const = 0; + virtual SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *widget = 0) const = 0; + virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget = 0) const = 0; + + enum PixelMetric { + PM_ButtonMargin, + PM_ButtonDefaultIndicator, + PM_MenuButtonIndicator, + PM_ButtonShiftHorizontal, + PM_ButtonShiftVertical, + + PM_DefaultFrameWidth, + PM_SpinBoxFrameWidth, + PM_ComboBoxFrameWidth, + + PM_MaximumDragDistance, + + PM_ScrollBarExtent, + PM_ScrollBarSliderMin, + + PM_SliderThickness, // total slider thickness + PM_SliderControlThickness, // thickness of the business part + PM_SliderLength, // total length of slider + PM_SliderTickmarkOffset, // + PM_SliderSpaceAvailable, // available space for slider to move + + PM_DockWidgetSeparatorExtent, + PM_DockWidgetHandleExtent, + PM_DockWidgetFrameWidth, + + PM_TabBarTabOverlap, + PM_TabBarTabHSpace, + PM_TabBarTabVSpace, + PM_TabBarBaseHeight, + PM_TabBarBaseOverlap, + + PM_ProgressBarChunkWidth, + + PM_SplitterWidth, + PM_TitleBarHeight, + + PM_MenuScrollerHeight, + PM_MenuHMargin, + PM_MenuVMargin, + PM_MenuPanelWidth, + PM_MenuTearoffHeight, + PM_MenuDesktopFrameWidth, + + PM_MenuBarPanelWidth, + PM_MenuBarItemSpacing, + PM_MenuBarVMargin, + PM_MenuBarHMargin, + + PM_IndicatorWidth, + PM_IndicatorHeight, + PM_ExclusiveIndicatorWidth, + PM_ExclusiveIndicatorHeight, + PM_CheckListButtonSize, + PM_CheckListControllerSize, + + PM_DialogButtonsSeparator, + PM_DialogButtonsButtonWidth, + PM_DialogButtonsButtonHeight, + + PM_MdiSubWindowFrameWidth, + PM_MDIFrameWidth = PM_MdiSubWindowFrameWidth, //obsolete + PM_MdiSubWindowMinimizedWidth, + PM_MDIMinimizedWidth = PM_MdiSubWindowMinimizedWidth, //obsolete + + PM_HeaderMargin, + PM_HeaderMarkSize, + PM_HeaderGripMargin, + PM_TabBarTabShiftHorizontal, + PM_TabBarTabShiftVertical, + PM_TabBarScrollButtonWidth, + + PM_ToolBarFrameWidth, + PM_ToolBarHandleExtent, + PM_ToolBarItemSpacing, + PM_ToolBarItemMargin, + PM_ToolBarSeparatorExtent, + PM_ToolBarExtensionExtent, + + PM_SpinBoxSliderHeight, + + PM_DefaultTopLevelMargin, + PM_DefaultChildMargin, + PM_DefaultLayoutSpacing, + + PM_ToolBarIconSize, + PM_ListViewIconSize, + PM_IconViewIconSize, + PM_SmallIconSize, + PM_LargeIconSize, + + PM_FocusFrameVMargin, + PM_FocusFrameHMargin, + + PM_ToolTipLabelFrameWidth, + PM_CheckBoxLabelSpacing, + PM_TabBarIconSize, + PM_SizeGripSize, + PM_DockWidgetTitleMargin, + PM_MessageBoxIconSize, + PM_ButtonIconSize, + + PM_DockWidgetTitleBarButtonMargin, + + PM_RadioButtonLabelSpacing, + PM_LayoutLeftMargin, + PM_LayoutTopMargin, + PM_LayoutRightMargin, + PM_LayoutBottomMargin, + PM_LayoutHorizontalSpacing, + PM_LayoutVerticalSpacing, + PM_TabBar_ScrollButtonOverlap, + + PM_TextCursorWidth, + + PM_TabCloseIndicatorWidth, + PM_TabCloseIndicatorHeight, + + PM_ScrollView_ScrollBarSpacing, + PM_SubMenuOverlap, + + // do not add any values below/greater than this + PM_CustomBase = 0xf0000000 + }; + + virtual int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, + const QWidget *widget = 0) const = 0; + + enum ContentsType { + CT_PushButton, + CT_CheckBox, + CT_RadioButton, + CT_ToolButton, + CT_ComboBox, + CT_Splitter, + CT_Q3DockWindow, + CT_ProgressBar, + CT_MenuItem, + CT_MenuBarItem, + CT_MenuBar, + CT_Menu, + CT_TabBarTab, + CT_Slider, + CT_ScrollBar, + CT_Q3Header, + CT_LineEdit, + CT_SpinBox, + CT_SizeGrip, + CT_TabWidget, + CT_DialogButtons, + CT_HeaderSection, + CT_GroupBox, + CT_MdiControls, + CT_ItemViewItem, + // do not add any values below/greater than this + CT_CustomBase = 0xf0000000 + }; + + virtual QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *w = 0) const = 0; + + enum RequestSoftwareInputPanel { + RSIP_OnMouseClickAndAlreadyFocused, + RSIP_OnMouseClick + }; + + enum StyleHint { + SH_EtchDisabledText, + SH_DitherDisabledText, + SH_ScrollBar_MiddleClickAbsolutePosition, + SH_ScrollBar_ScrollWhenPointerLeavesControl, + SH_TabBar_SelectMouseType, + SH_TabBar_Alignment, + SH_Header_ArrowAlignment, + SH_Slider_SnapToValue, + SH_Slider_SloppyKeyEvents, + SH_ProgressDialog_CenterCancelButton, + SH_ProgressDialog_TextLabelAlignment, + SH_PrintDialog_RightAlignButtons, + SH_MainWindow_SpaceBelowMenuBar, + SH_FontDialog_SelectAssociatedText, + SH_Menu_AllowActiveAndDisabled, + SH_Menu_SpaceActivatesItem, + SH_Menu_SubMenuPopupDelay, + SH_ScrollView_FrameOnlyAroundContents, + SH_MenuBar_AltKeyNavigation, + SH_ComboBox_ListMouseTracking, + SH_Menu_MouseTracking, + SH_MenuBar_MouseTracking, + SH_ItemView_ChangeHighlightOnFocus, + SH_Widget_ShareActivation, + SH_Workspace_FillSpaceOnMaximize, + SH_ComboBox_Popup, + SH_TitleBar_NoBorder, + SH_Slider_StopMouseOverSlider, + SH_ScrollBar_StopMouseOverSlider = SH_Slider_StopMouseOverSlider, // obsolete + SH_BlinkCursorWhenTextSelected, + SH_RichText_FullWidthSelection, + SH_Menu_Scrollable, + SH_GroupBox_TextLabelVerticalAlignment, + SH_GroupBox_TextLabelColor, + SH_Menu_SloppySubMenus, + SH_Table_GridLineColor, + SH_LineEdit_PasswordCharacter, + SH_DialogButtons_DefaultButton, + SH_ToolBox_SelectedPageTitleBold, + SH_TabBar_PreferNoArrows, + SH_ScrollBar_LeftClickAbsolutePosition, + SH_Q3ListViewExpand_SelectMouseType, + SH_UnderlineShortcut, + SH_SpinBox_AnimateButton, + SH_SpinBox_KeyPressAutoRepeatRate, + SH_SpinBox_ClickAutoRepeatRate, + SH_Menu_FillScreenWithScroll, + SH_ToolTipLabel_Opacity, + SH_DrawMenuBarSeparator, + SH_TitleBar_ModifyNotification, + SH_Button_FocusPolicy, + SH_MenuBar_DismissOnSecondClick, + SH_MessageBox_UseBorderForButtonSpacing, + SH_TitleBar_AutoRaise, + SH_ToolButton_PopupDelay, + SH_FocusFrame_Mask, + SH_RubberBand_Mask, + SH_WindowFrame_Mask, + SH_SpinControls_DisableOnBounds, + SH_Dial_BackgroundRole, + SH_ComboBox_LayoutDirection, + SH_ItemView_EllipsisLocation, + SH_ItemView_ShowDecorationSelected, + SH_ItemView_ActivateItemOnSingleClick, + SH_ScrollBar_ContextMenu, + SH_ScrollBar_RollBetweenButtons, + SH_Slider_AbsoluteSetButtons, + SH_Slider_PageSetButtons, + SH_Menu_KeyboardSearch, + SH_TabBar_ElideMode, + SH_DialogButtonLayout, + SH_ComboBox_PopupFrameStyle, + SH_MessageBox_TextInteractionFlags, + SH_DialogButtonBox_ButtonsHaveIcons, + SH_SpellCheckUnderlineStyle, + SH_MessageBox_CenterButtons, + SH_Menu_SelectionWrap, + SH_ItemView_MovementWithoutUpdatingSelection, + SH_ToolTip_Mask, + SH_FocusFrame_AboveWidget, + SH_TextControl_FocusIndicatorTextCharFormat, + SH_WizardStyle, + SH_ItemView_ArrowKeysNavigateIntoChildren, + SH_Menu_Mask, + SH_Menu_FlashTriggeredItem, + SH_Menu_FadeOutOnHide, + SH_SpinBox_ClickAutoRepeatThreshold, + SH_ItemView_PaintAlternatingRowColorsForEmptyArea, + SH_FormLayoutWrapPolicy, + SH_TabWidget_DefaultTabPosition, + SH_ToolBar_Movable, + SH_FormLayoutFieldGrowthPolicy, + SH_FormLayoutFormAlignment, + SH_FormLayoutLabelAlignment, + SH_ItemView_DrawDelegateFrame, + SH_TabBar_CloseButtonPosition, + SH_DockWidget_ButtonsHaveFrame, + SH_ToolButtonStyle, + SH_RequestSoftwareInputPanel, + // Add new style hint values here + +#ifdef QT3_SUPPORT + SH_GUIStyle = 0x00000100, + SH_ScrollBar_BackgroundMode, + // Add other compat values here + + SH_UnderlineAccelerator = SH_UnderlineShortcut, +#endif + SH_CustomBase = 0xf0000000 + }; + + virtual int styleHint(StyleHint stylehint, const QStyleOption *opt = 0, + const QWidget *widget = 0, QStyleHintReturn* returnData = 0) const = 0; + + enum StandardPixmap { + SP_TitleBarMenuButton, + SP_TitleBarMinButton, + SP_TitleBarMaxButton, + SP_TitleBarCloseButton, + SP_TitleBarNormalButton, + SP_TitleBarShadeButton, + SP_TitleBarUnshadeButton, + SP_TitleBarContextHelpButton, + SP_DockWidgetCloseButton, + SP_MessageBoxInformation, + SP_MessageBoxWarning, + SP_MessageBoxCritical, + SP_MessageBoxQuestion, + SP_DesktopIcon, + SP_TrashIcon, + SP_ComputerIcon, + SP_DriveFDIcon, + SP_DriveHDIcon, + SP_DriveCDIcon, + SP_DriveDVDIcon, + SP_DriveNetIcon, + SP_DirOpenIcon, + SP_DirClosedIcon, + SP_DirLinkIcon, + SP_FileIcon, + SP_FileLinkIcon, + SP_ToolBarHorizontalExtensionButton, + SP_ToolBarVerticalExtensionButton, + SP_FileDialogStart, + SP_FileDialogEnd, + SP_FileDialogToParent, + SP_FileDialogNewFolder, + SP_FileDialogDetailedView, + SP_FileDialogInfoView, + SP_FileDialogContentsView, + SP_FileDialogListView, + SP_FileDialogBack, + SP_DirIcon, + SP_DialogOkButton, + SP_DialogCancelButton, + SP_DialogHelpButton, + SP_DialogOpenButton, + SP_DialogSaveButton, + SP_DialogCloseButton, + SP_DialogApplyButton, + SP_DialogResetButton, + SP_DialogDiscardButton, + SP_DialogYesButton, + SP_DialogNoButton, + SP_ArrowUp, + SP_ArrowDown, + SP_ArrowLeft, + SP_ArrowRight, + SP_ArrowBack, + SP_ArrowForward, + SP_DirHomeIcon, + SP_CommandLink, + SP_VistaShield, + SP_BrowserReload, + SP_BrowserStop, + SP_MediaPlay, + SP_MediaStop, + SP_MediaPause, + SP_MediaSkipForward, + SP_MediaSkipBackward, + SP_MediaSeekForward, + SP_MediaSeekBackward, + SP_MediaVolume, + SP_MediaVolumeMuted, + // do not add any values below/greater than this + SP_CustomBase = 0xf0000000 + }; + + virtual QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt = 0, + const QWidget *widget = 0) const = 0; + + QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + + virtual QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *opt) const = 0; + + static QRect visualRect(Qt::LayoutDirection direction, const QRect &boundingRect, + const QRect &logicalRect); + static QPoint visualPos(Qt::LayoutDirection direction, const QRect &boundingRect, + const QPoint &logicalPos); + static int sliderPositionFromValue(int min, int max, int val, int space, + bool upsideDown = false); + static int sliderValueFromPosition(int min, int max, int pos, int space, + bool upsideDown = false); + static Qt::Alignment visualAlignment(Qt::LayoutDirection direction, Qt::Alignment alignment); + static QRect alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, + const QSize &size, const QRect &rectangle); + + int layoutSpacing(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, Qt::Orientation orientation, + const QStyleOption *option = 0, const QWidget *widget = 0) const; + int combinedLayoutSpacing(QSizePolicy::ControlTypes controls1, + QSizePolicy::ControlTypes controls2, Qt::Orientation orientation, + QStyleOption *option = 0, QWidget *widget = 0) const; + + const QStyle * proxy() const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + int layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QStyle) + friend class QWidget; + friend class QWidgetPrivate; + friend class QApplication; + friend class QProxyStyle; + friend class QProxyStylePrivate; + void setProxy(QStyle *style); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyle::State) +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyle::SubControls) + +#if !defined(QT_NO_DEBUG_STREAM) +Q_GUI_EXPORT QDebug operator<<(QDebug debug, QStyle::State state); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLE_H diff --git a/src/gui/styles/qstyle.qrc b/src/gui/styles/qstyle.qrc new file mode 100644 index 0000000000..8654e66a37 --- /dev/null +++ b/src/gui/styles/qstyle.qrc @@ -0,0 +1,135 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/styles/commonstyle"> +<file>images/filelink-16.png</file> +<file>images/filelink-32.png</file> +<file>images/filelink-128.png</file> +<file>images/file-16.png</file> +<file>images/file-32.png</file> +<file>images/file-128.png</file> +<file>images/newdirectory-16.png</file> +<file>images/newdirectory-32.png</file> +<file>images/newdirectory-128.png</file> +<file>images/parentdir-16.png</file> +<file>images/parentdir-32.png</file> +<file>images/parentdir-128.png</file> +<file>images/dvd-16.png</file> +<file>images/dvd-32.png</file> +<file>images/dvd-128.png</file> +<file>images/cdr-16.png</file> +<file>images/cdr-32.png</file> +<file>images/cdr-128.png</file> +<file>images/floppy-16.png</file> +<file>images/floppy-32.png</file> +<file>images/floppy-128.png</file> +<file>images/harddrive-16.png</file> +<file>images/harddrive-32.png</file> +<file>images/harddrive-128.png</file> +<file>images/trash-16.png</file> +<file>images/trash-32.png</file> +<file>images/trash-128.png</file> +<file>images/networkdrive-16.png</file> +<file>images/networkdrive-32.png</file> +<file>images/networkdrive-128.png</file> +<file>images/computer-16.png</file> +<file>images/computer-32.png</file> +<file>images/desktop-16.png</file> +<file>images/desktop-32.png</file> +<file>images/dirclosed-16.png</file> +<file>images/dirclosed-32.png</file> +<file>images/dirclosed-128.png</file> +<file>images/dirlink-16.png</file> +<file>images/dirlink-32.png</file> +<file>images/dirlink-128.png</file> +<file>images/diropen-16.png</file> +<file>images/diropen-32.png</file> +<file>images/diropen-128.png</file> +<file>images/left-16.png</file> +<file>images/left-32.png</file> +<file>images/left-128.png</file> +<file>images/right-16.png</file> +<file>images/right-32.png</file> +<file>images/right-128.png</file> +<file>images/up-16.png</file> +<file>images/up-32.png</file> +<file>images/up-128.png</file> +<file>images/down-16.png</file> +<file>images/down-32.png</file> +<file>images/down-128.png</file> +<file>images/filecontents-16.png</file> +<file>images/filecontents-32.png</file> +<file>images/filecontents-128.png</file> +<file>images/fileinfo-16.png</file> +<file>images/fileinfo-32.png</file> +<file>images/fileinfo-128.png</file> +<file>images/viewdetailed-16.png</file> +<file>images/viewdetailed-32.png</file> +<file>images/viewdetailed-128.png</file> +<file>images/viewlist-16.png</file> +<file>images/viewlist-32.png</file> +<file>images/viewlist-128.png</file> +<file>images/fontbitmap-16.png</file> +<file>images/fonttruetype-16.png</file> +<file>images/standardbutton-apply-128.png</file> +<file>images/standardbutton-apply-16.png</file> +<file>images/standardbutton-apply-32.png</file> +<file>images/standardbutton-cancel-128.png</file> +<file>images/standardbutton-cancel-16.png</file> +<file>images/standardbutton-cancel-32.png</file> +<file>images/standardbutton-clear-128.png</file> +<file>images/standardbutton-clear-16.png</file> +<file>images/standardbutton-clear-32.png</file> +<file>images/standardbutton-close-128.png</file> +<file>images/standardbutton-close-16.png</file> +<file>images/standardbutton-close-32.png</file> +<file>images/standardbutton-delete-128.png</file> +<file>images/standardbutton-delete-16.png</file> +<file>images/standardbutton-delete-32.png</file> +<file>images/standardbutton-help-128.png</file> +<file>images/standardbutton-help-16.png</file> +<file>images/standardbutton-help-32.png</file> +<file>images/standardbutton-no-128.png</file> +<file>images/standardbutton-no-16.png</file> +<file>images/standardbutton-no-32.png</file> +<file>images/standardbutton-ok-128.png</file> +<file>images/standardbutton-ok-16.png</file> +<file>images/standardbutton-ok-32.png</file> +<file>images/standardbutton-open-128.png</file> +<file>images/standardbutton-open-16.png</file> +<file>images/standardbutton-open-32.png</file> +<file>images/standardbutton-save-128.png</file> +<file>images/standardbutton-save-16.png</file> +<file>images/standardbutton-save-32.png</file> +<file>images/standardbutton-yes-128.png</file> +<file>images/standardbutton-yes-16.png</file> +<file>images/standardbutton-yes-32.png</file> +<file>images/standardbutton-closetab-16.png</file> +<file>images/standardbutton-closetab-down-16.png</file> +<file>images/standardbutton-closetab-hover-16.png</file> +<file>images/refresh-24.png</file> +<file>images/refresh-32.png</file> +<file>images/stop-24.png</file> +<file>images/stop-32.png</file> +<file>images/media-stop-16.png</file> +<file>images/media-stop-32.png</file> +<file>images/media-play-16.png</file> +<file>images/media-play-32.png</file> +<file>images/media-pause-16.png</file> +<file>images/media-pause-32.png</file> +<file>images/media-seek-forward-16.png</file> +<file>images/media-seek-forward-32.png</file> +<file>images/media-seek-backward-16.png</file> +<file>images/media-seek-backward-32.png</file> +<file>images/media-skip-forward-16.png</file> +<file>images/media-skip-forward-32.png</file> +<file>images/media-skip-backward-16.png</file> +<file>images/media-skip-backward-32.png</file> +<file>images/media-volume-16.png</file> +<file>images/media-volume-muted-16.png</file> +</qresource> +<qresource prefix="/trolltech/styles/macstyle"> +<file>images/closedock-16.png</file> +<file>images/closedock-down-16.png</file> +<file>images/dockdock-16.png</file> +<file>images/dockdock-down-16.png</file> +</qresource> +</RCC> diff --git a/src/gui/styles/qstyle_p.h b/src/gui/styles/qstyle_p.h new file mode 100644 index 0000000000..1fcd2eaf2a --- /dev/null +++ b/src/gui/styles/qstyle_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLE_P_H +#define QSTYLE_P_H + +#include "private/qobject_p.h" +#include <QtGui/qstyle.h> + +QT_BEGIN_NAMESPACE + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qstyle_*.cpp. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +// Private class + +class QStyle; + +class QStylePrivate: public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QStyle) +public: + inline QStylePrivate() + : layoutSpacingIndex(-1), proxyStyle(0) {} + mutable int layoutSpacingIndex; + QStyle *proxyStyle; +}; + + +#define BEGIN_STYLE_PIXMAPCACHE(a) \ + QRect rect = option->rect; \ + QPixmap internalPixmapCache; \ + QImage imageCache; \ + QPainter *p = painter; \ + QString unique = QStyleHelper::uniqueName((a), option, option->rect.size()); \ + int txType = painter->deviceTransform().type() | painter->worldTransform().type(); \ + bool doPixmapCache = txType <= QTransform::TxTranslate; \ + if (doPixmapCache && QPixmapCache::find(unique, internalPixmapCache)) { \ + painter->drawPixmap(option->rect.topLeft(), internalPixmapCache); \ + } else { \ + if (doPixmapCache) { \ + rect.setRect(0, 0, option->rect.width(), option->rect.height()); \ + imageCache = QImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); \ + imageCache.fill(0); \ + p = new QPainter(&imageCache); \ + } + + + +#define END_STYLE_PIXMAPCACHE \ + if (doPixmapCache) { \ + p->end(); \ + delete p; \ + internalPixmapCache = QPixmap::fromImage(imageCache); \ + painter->drawPixmap(option->rect.topLeft(), internalPixmapCache); \ + QPixmapCache::insert(unique, internalPixmapCache); \ + } \ + } + +QT_END_NAMESPACE + +#endif //QSTYLE_P_H diff --git a/src/gui/styles/qstyle_s60.qrc b/src/gui/styles/qstyle_s60.qrc new file mode 100644 index 0000000000..dbee38b266 --- /dev/null +++ b/src/gui/styles/qstyle_s60.qrc @@ -0,0 +1,137 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/styles/commonstyle"> +<!-- <file>images/filelink-16.png</file> --> +<file>images/filelink-32.png</file> +<!-- <file>images/filelink-128.png</file> --> +<!-- <file>images/file-16.png</file> --> +<file>images/file-32.png</file> +<!-- <file>images/file-128.png</file> --> +<!-- <file>images/newdirectory-16.png</file> --> +<file>images/newdirectory-32.png</file> +<!-- <file>images/newdirectory-128.png</file> --> +<!-- <file>images/parentdir-16.png</file> --> +<file>images/parentdir-32.png</file> +<!-- <file>images/parentdir-128.png</file> --> +<!-- <file>images/dvd-16.png</file> --> +<!-- <file>images/dvd-32.png</file> --> +<!-- <file>images/dvd-128.png</file> --> +<!-- <file>images/cdr-16.png</file> --> +<!-- <file>images/cdr-32.png</file> --> +<!-- <file>images/cdr-128.png</file> --> +<!-- <file>images/floppy-16.png</file> --> +<!-- <file>images/floppy-32.png</file> --> +<!-- <file>images/floppy-128.png</file> --> +<!-- <file>images/harddrive-16.png</file> --> +<file>images/harddrive-32.png</file> +<!-- <file>images/harddrive-128.png</file> --> +<!-- <file>images/trash-16.png</file> --> +<!-- <file>images/trash-32.png</file> --> +<!-- <file>images/trash-128.png</file> --> +<!-- <file>images/networkdrive-16.png</file> --> +<!-- <file>images/networkdrive-32.png</file> --> +<!-- <file>images/networkdrive-128.png</file> --> +<!-- <file>images/computer-16.png</file> --> +<!-- <file>images/computer-32.png</file> --> +<!-- <file>images/desktop-16.png</file> --> +<file>images/desktop-32.png</file> +<!-- <file>images/dirclosed-16.png</file> --> +<file>images/dirclosed-32.png</file> +<!-- <file>images/dirclosed-128.png</file> --> +<!-- <file>images/dirlink-16.png</file> --> +<file>images/dirlink-32.png</file> +<!-- <file>images/dirlink-128.png</file> --> +<!-- <file>images/diropen-16.png</file> --> +<file>images/diropen-32.png</file> +<!-- <file>images/diropen-128.png</file> --> +<!-- <file>images/left-16.png</file> --> +<file>images/left-32.png</file> +<!-- <file>images/left-128.png</file> --> +<!-- <file>images/right-16.png</file> --> +<file>images/right-32.png</file> +<!-- <file>images/right-128.png</file> --> +<!-- <file>images/up-16.png</file> --> +<file>images/up-32.png</file> +<!-- <file>images/up-128.png</file> --> +<!-- <file>images/down-16.png</file> --> +<file>images/down-32.png</file> +<!-- <file>images/down-128.png</file> --> +<!-- <file>images/filecontents-16.png</file> --> +<file>images/filecontents-32.png</file> +<!-- <file>images/filecontents-128.png</file> --> +<!-- <file>images/fileinfo-16.png</file> --> +<file>images/fileinfo-32.png</file> +<!-- <file>images/fileinfo-128.png</file> --> +<!-- <file>images/viewdetailed-16.png</file> --> +<file>images/viewdetailed-32.png</file> +<!-- <file>images/viewdetailed-128.png</file> --> +<!-- <file>images/viewlist-16.png</file> --> +<file>images/viewlist-32.png</file> +<!-- <file>images/viewlist-128.png</file> --> +<file>images/fontbitmap-16.png</file> +<file>images/fonttruetype-16.png</file> +<!-- <file>images/standardbutton-apply-128.png</file> --> +<!-- <file>images/standardbutton-apply-16.png</file> --> +<file>images/standardbutton-apply-32.png</file> +<!-- <file>images/standardbutton-cancel-128.png</file> --> +<!-- <file>images/standardbutton-cancel-16.png</file> --> +<file>images/standardbutton-cancel-32.png</file> +<!-- <file>images/standardbutton-clear-128.png</file> --> +<!-- <file>images/standardbutton-clear-16.png</file> --> +<file>images/standardbutton-clear-32.png</file> +<!-- <file>images/standardbutton-close-128.png</file> --> +<!-- <file>images/standardbutton-close-16.png</file> --> +<file>images/standardbutton-close-32.png</file> +<!-- <file>images/standardbutton-delete-128.png</file> --> +<!-- <file>images/standardbutton-delete-16.png</file> --> +<file>images/standardbutton-delete-32.png</file> +<!-- <file>images/standardbutton-help-128.png</file> --> +<!-- <file>images/standardbutton-help-16.png</file> --> +<file>images/standardbutton-help-32.png</file> +<!-- <file>images/standardbutton-no-128.png</file> --> +<!-- <file>images/standardbutton-no-16.png</file> --> +<file>images/standardbutton-no-32.png</file> +<!-- <file>images/standardbutton-ok-128.png</file> --> +<!-- <file>images/standardbutton-ok-16.png</file> --> +<file>images/standardbutton-ok-32.png</file> +<!-- <file>images/standardbutton-open-128.png</file> --> +<!-- <file>images/standardbutton-open-16.png</file> --> +<file>images/standardbutton-open-32.png</file> +<!-- <file>images/standardbutton-save-128.png</file> --> +<!-- <file>images/standardbutton-save-16.png</file> --> +<file>images/standardbutton-save-32.png</file> +<!-- <file>images/standardbutton-yes-128.png</file> --> +<!-- <file>images/standardbutton-yes-16.png</file> --> +<file>images/standardbutton-yes-32.png</file> +<file>images/standardbutton-closetab-16.png</file> +<file>images/standardbutton-closetab-down-16.png</file> +<file>images/standardbutton-closetab-hover-16.png</file> +<!-- <file>images/refresh-24.png</file> --> +<file>images/refresh-32.png</file> +<!-- <file>images/stop-24.png</file> --> +<file>images/stop-32.png</file> +<!-- <file>images/media-stop-16.png</file> --> +<file>images/media-stop-32.png</file> +<!-- <file>images/media-play-16.png</file> --> +<file>images/media-play-32.png</file> +<!-- <file>images/media-pause-16.png</file> --> +<file>images/media-pause-32.png</file> +<!-- <file>images/media-seek-forward-16.png</file> --> +<file>images/media-seek-forward-32.png</file> +<!-- <file>images/media-seek-backward-16.png</file> --> +<file>images/media-seek-backward-32.png</file> +<!-- <file>images/media-skip-forward-16.png</file> --> +<file>images/media-skip-forward-32.png</file> +<!-- <file>images/media-skip-backward-16.png</file> --> +<file>images/media-skip-backward-32.png</file> +<file>images/media-volume-16.png</file> +<file>images/media-volume-muted-16.png</file> +</qresource> +<!-- +<qresource prefix="/trolltech/styles/macstyle"> +<file>images/closedock-16.png</file> +<file>images/closedock-down-16.png</file> +<file>images/dockdock-16.png</file> +<file>images/dockdock-down-16.png</file> +</qresource> +--> +</RCC> diff --git a/src/gui/styles/qstyle_s60_simulated.qrc b/src/gui/styles/qstyle_s60_simulated.qrc new file mode 100644 index 0000000000..969732e1e0 --- /dev/null +++ b/src/gui/styles/qstyle_s60_simulated.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource prefix="/trolltech/styles/s60style"> + <file>images/defaults60theme.blob</file> + </qresource> +</RCC> diff --git a/src/gui/styles/qstyle_wince.qrc b/src/gui/styles/qstyle_wince.qrc new file mode 100644 index 0000000000..bdcf604625 --- /dev/null +++ b/src/gui/styles/qstyle_wince.qrc @@ -0,0 +1,97 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/styles/commonstyle"> +<file>images/filelink-16.png</file> +<file>images/filelink-32.png</file> +<file>images/file-16.png</file> +<file>images/file-32.png</file> +<file>images/newdirectory-16.png</file> +<file>images/newdirectory-32.png</file> +<file>images/parentdir-16.png</file> +<file>images/parentdir-32.png</file> +<file>images/dvd-16.png</file> +<file>images/dvd-32.png</file> +<file>images/cdr-16.png</file> +<file>images/cdr-32.png</file> +<file>images/floppy-16.png</file> +<file>images/floppy-32.png</file> +<file>images/harddrive-16.png</file> +<file>images/harddrive-32.png</file> +<file>images/trash-16.png</file> +<file>images/trash-32.png</file> +<file>images/networkdrive-16.png</file> +<file>images/networkdrive-32.png</file> +<file>images/computer-16.png</file> +<file>images/computer-32.png</file> +<file>images/desktop-16.png</file> +<file>images/desktop-32.png</file> +<file>images/dirclosed-16.png</file> +<file>images/dirclosed-32.png</file> +<file>images/dirlink-16.png</file> +<file>images/dirlink-32.png</file> +<file>images/diropen-16.png</file> +<file>images/diropen-32.png</file> +<file>images/left-16.png</file> +<file>images/left-32.png</file> +<file>images/right-16.png</file> +<file>images/right-32.png</file> +<file>images/up-16.png</file> +<file>images/up-32.png</file> +<file>images/down-16.png</file> +<file>images/down-32.png</file> +<file>images/filecontents-16.png</file> +<file>images/filecontents-32.png</file> +<file>images/fileinfo-16.png</file> +<file>images/fileinfo-32.png</file> +<file>images/viewdetailed-16.png</file> +<file>images/viewdetailed-32.png</file> +<file>images/viewlist-16.png</file> +<file>images/viewlist-32.png</file> +<file>images/fontbitmap-16.png</file> +<file>images/fonttruetype-16.png</file> +<file>images/standardbutton-apply-16.png</file> +<file>images/standardbutton-apply-32.png</file> +<file>images/standardbutton-cancel-16.png</file> +<file>images/standardbutton-cancel-32.png</file> +<file>images/standardbutton-clear-16.png</file> +<file>images/standardbutton-clear-32.png</file> +<file>images/standardbutton-close-16.png</file> +<file>images/standardbutton-close-32.png</file> +<file>images/standardbutton-delete-16.png</file> +<file>images/standardbutton-delete-32.png</file> +<file>images/standardbutton-help-16.png</file> +<file>images/standardbutton-help-32.png</file> +<file>images/standardbutton-no-16.png</file> +<file>images/standardbutton-no-32.png</file> +<file>images/standardbutton-ok-16.png</file> +<file>images/standardbutton-ok-32.png</file> +<file>images/standardbutton-open-16.png</file> +<file>images/standardbutton-open-32.png</file> +<file>images/standardbutton-save-16.png</file> +<file>images/standardbutton-save-32.png</file> +<file>images/standardbutton-yes-16.png</file> +<file>images/standardbutton-yes-32.png</file> +<file>images/standardbutton-closetab-16.png</file> +<file>images/standardbutton-closetab-down-16.png</file> +<file>images/standardbutton-closetab-hover-16.png</file> +<file>images/refresh-24.png</file> +<file>images/refresh-32.png</file> +<file>images/stop-24.png</file> +<file>images/stop-32.png</file> +<file>images/media-stop-16.png</file> +<file>images/media-stop-32.png</file> +<file>images/media-play-16.png</file> +<file>images/media-play-32.png</file> +<file>images/media-pause-16.png</file> +<file>images/media-pause-32.png</file> +<file>images/media-seek-forward-16.png</file> +<file>images/media-seek-forward-32.png</file> +<file>images/media-seek-backward-16.png</file> +<file>images/media-seek-backward-32.png</file> +<file>images/media-skip-forward-16.png</file> +<file>images/media-skip-forward-32.png</file> +<file>images/media-skip-backward-16.png</file> +<file>images/media-skip-backward-32.png</file> +<file>images/media-volume-16.png</file> +<file>images/media-volume-muted-16.png</file> +</qresource> +</RCC> diff --git a/src/gui/styles/qstylefactory.cpp b/src/gui/styles/qstylefactory.cpp new file mode 100644 index 0000000000..1a9682a831 --- /dev/null +++ b/src/gui/styles/qstylefactory.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstylefactory.h" +#include "qstyleplugin.h" +#include "private/qfactoryloader_p.h" +#include "qmutex.h" + +#include "qapplication.h" +#include "qwindowsstyle.h" +#include "qmotifstyle.h" +#include "qcdestyle.h" +#ifndef QT_NO_STYLE_PLASTIQUE +#include "qplastiquestyle.h" +#endif +#ifndef QT_NO_STYLE_CLEANLOOKS +#include "qcleanlooksstyle.h" +#endif +#ifndef QT_NO_STYLE_GTK +#include "qgtkstyle.h" +#endif +#ifndef QT_NO_STYLE_WINDOWSXP +#include "qwindowsxpstyle.h" +#endif +#ifndef QT_NO_STYLE_WINDOWSVISTA +#include "qwindowsvistastyle.h" +#endif +#ifndef QT_NO_STYLE_WINDOWSCE +#include "qwindowscestyle.h" +#endif +#ifndef QT_NO_STYLE_WINDOWSMOBILE +#include "qwindowsmobilestyle.h" +#endif +#ifndef QT_NO_STYLE_S60 +#include "qs60style.h" +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_STYLE_MAC) && defined(Q_WS_MAC) +QT_BEGIN_INCLUDE_NAMESPACE +# include "qmacstyle_mac.h" +QT_END_INCLUDE_NAMESPACE +#endif + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QStyleFactoryInterface_iid, QLatin1String("/styles"), Qt::CaseInsensitive)) +#endif + +/*! + \class QStyleFactory + \brief The QStyleFactory class creates QStyle objects. + + \ingroup appearance + + The QStyle class is an abstract base class that encapsulates the + look and feel of a GUI. QStyleFactory creates a QStyle object + using the create() function and a key identifying the style. The + styles are either built-in or dynamically loaded from a style + plugin (see QStylePlugin). + + The valid keys can be retrieved using the keys() + function. Typically they include "windows", "motif", "cde", + "plastique" and "cleanlooks". Depending on the platform, + "windowsxp", "windowsvista" and "macintosh" may be available. + Note that keys are case insensitive. + + \sa QStyle +*/ + +/*! + Creates and returns a QStyle object that matches the given \a key, or + returns 0 if no matching style is found. + + Both built-in styles and styles from style plugins are queried for a + matching style. + + \note The keys used are case insensitive. + + \sa keys() +*/ +QStyle *QStyleFactory::create(const QString& key) +{ + QStyle *ret = 0; + QString style = key.toLower(); +#ifndef QT_NO_STYLE_WINDOWS + if (style == QLatin1String("windows")) + ret = new QWindowsStyle; + else +#endif +#ifndef QT_NO_STYLE_WINDOWSCE + if (style == QLatin1String("windowsce")) + ret = new QWindowsCEStyle; + else +#endif +#ifndef QT_NO_STYLE_WINDOWSMOBILE + if (style == QLatin1String("windowsmobile")) + ret = new QWindowsMobileStyle; + else +#endif +#ifndef QT_NO_STYLE_WINDOWSXP + if (style == QLatin1String("windowsxp")) + ret = new QWindowsXPStyle; + else +#endif +#ifndef QT_NO_STYLE_WINDOWSVISTA + if (style == QLatin1String("windowsvista")) + ret = new QWindowsVistaStyle; + else +#endif +#ifndef QT_NO_STYLE_MOTIF + if (style == QLatin1String("motif")) + ret = new QMotifStyle; + else +#endif +#ifndef QT_NO_STYLE_CDE + if (style == QLatin1String("cde")) + ret = new QCDEStyle; + else +#endif +#ifndef QT_NO_STYLE_S60 + if (style == QLatin1String("s60")) + ret = new QS60Style; + else +#endif +#ifndef QT_NO_STYLE_PLASTIQUE + if (style == QLatin1String("plastique")) + ret = new QPlastiqueStyle; + else +#endif +#ifndef QT_NO_STYLE_CLEANLOOKS + if (style == QLatin1String("cleanlooks")) + ret = new QCleanlooksStyle; + else +#endif +#ifndef QT_NO_STYLE_GTK + if (style == QLatin1String("gtk") || style == QLatin1String("gtk+")) + ret = new QGtkStyle; + else +#endif +#ifndef QT_NO_STYLE_MAC + if (style.startsWith(QLatin1String("macintosh"))) { + ret = new QMacStyle; +# ifdef Q_WS_MAC + if (style == QLatin1String("macintosh")) + style += QLatin1String(" (aqua)"); +# endif + } else +#endif + { } // Keep these here - they make the #ifdefery above work +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + if(!ret) { + if (QStyleFactoryInterface *factory = qobject_cast<QStyleFactoryInterface*>(loader()->instance(style))) + ret = factory->create(style); + } +#endif + if(ret) + ret->setObjectName(style); + return ret; +} + +/*! + Returns the list of valid keys, i.e. the keys this factory can + create styles for. + + \sa create() +*/ +QStringList QStyleFactory::keys() +{ +#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) + QStringList list = loader()->keys(); +#else + QStringList list; +#endif +#ifndef QT_NO_STYLE_WINDOWS + if (!list.contains(QLatin1String("Windows"))) + list << QLatin1String("Windows"); +#endif +#ifndef QT_NO_STYLE_WINDOWSCE + if (!list.contains(QLatin1String("WindowsCE"))) + list << QLatin1String("WindowsCE"); +#endif +#ifndef QT_NO_STYLE_WINDOWSMOBILE + if (!list.contains(QLatin1String("WindowsMobile"))) + list << QLatin1String("WindowsMobile"); +#endif +#ifndef QT_NO_STYLE_WINDOWSXP + if (!list.contains(QLatin1String("WindowsXP")) && + (QSysInfo::WindowsVersion >= QSysInfo::WV_XP && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + list << QLatin1String("WindowsXP"); +#endif +#ifndef QT_NO_STYLE_WINDOWSVISTA + if (!list.contains(QLatin1String("WindowsVista")) && + (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) + list << QLatin1String("WindowsVista"); +#endif +#ifndef QT_NO_STYLE_MOTIF + if (!list.contains(QLatin1String("Motif"))) + list << QLatin1String("Motif"); +#endif +#ifndef QT_NO_STYLE_CDE + if (!list.contains(QLatin1String("CDE"))) + list << QLatin1String("CDE"); +#endif +#ifndef QT_NO_STYLE_S60 + if (!list.contains(QLatin1String("S60"))) + list << QLatin1String("S60"); +#endif +#ifndef QT_NO_STYLE_PLASTIQUE + if (!list.contains(QLatin1String("Plastique"))) + list << QLatin1String("Plastique"); +#endif +#ifndef QT_NO_STYLE_GTK + if (!list.contains(QLatin1String("GTK+"))) + list << QLatin1String("GTK+"); +#endif +#ifndef QT_NO_STYLE_CLEANLOOKS + if (!list.contains(QLatin1String("Cleanlooks"))) + list << QLatin1String("Cleanlooks"); +#endif +#ifndef QT_NO_STYLE_MAC + QString mstyle = QLatin1String("Macintosh"); +# ifdef Q_WS_MAC + mstyle += QLatin1String(" (aqua)"); +# endif + if (!list.contains(mstyle)) + list << mstyle; +#endif + return list; +} + +QT_END_NAMESPACE diff --git a/src/gui/styles/qstylefactory.h b/src/gui/styles/qstylefactory.h new file mode 100644 index 0000000000..b665e5fce4 --- /dev/null +++ b/src/gui/styles/qstylefactory.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEFACTORY_H +#define QSTYLEFACTORY_H + +#include <QtCore/qstringlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyle; + +class Q_GUI_EXPORT QStyleFactory +{ +public: + static QStringList keys(); + static QStyle *create(const QString&); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEFACTORY_H diff --git a/src/gui/styles/qstylehelper.cpp b/src/gui/styles/qstylehelper.cpp new file mode 100644 index 0000000000..59766cae0f --- /dev/null +++ b/src/gui/styles/qstylehelper.cpp @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qstyleoption.h> +#include <qpainter.h> +#include <qpixmapcache.h> +#include <private/qmath_p.h> +#include <private/qstyle_p.h> +#include <qmath.h> + +#if defined(Q_WS_WIN) +#include "qt_windows.h" +#elif defined(Q_WS_MAC) +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#include "qstylehelper_p.h" +#include <qstringbuilder.h> + +QT_BEGIN_NAMESPACE + +namespace QStyleHelper { + +QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size) +{ + const QStyleOptionComplex *complexOption = qstyleoption_cast<const QStyleOptionComplex *>(option); + QString tmp = key % HexString<uint>(option->state) + % HexString<uint>(option->direction) + % HexString<uint>(complexOption ? uint(complexOption->activeSubControls) : 0u) + % HexString<quint64>(option->palette.cacheKey()) + % HexString<uint>(size.width()) + % HexString<uint>(size.height()); + +#ifndef QT_NO_SPINBOX + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + tmp = tmp % HexString<uint>(spinBox->buttonSymbols) + % HexString<uint>(spinBox->stepEnabled) + % QLatin1Char(spinBox->frame ? '1' : '0'); ; + } +#endif // QT_NO_SPINBOX + return tmp; +} + +qreal dpiScaled(qreal value) +{ + static qreal scale = -1; + if (scale < 0) { + scale = 1.0; +#if defined(Q_WS_WIN) + { + HDC hdcScreen = GetDC(0); + int dpi = GetDeviceCaps(hdcScreen, LOGPIXELSX); + ReleaseDC(0, hdcScreen); + scale = dpi/96.0; + } +#elif defined(Q_WS_MAC) + scale = qt_mac_get_scalefactor(); +#endif + } + return value * scale; +} + + +#ifndef QT_NO_DIAL + +int calcBigLineSize(int radius) +{ + int bigLineSize = radius / 6; + if (bigLineSize < 4) + bigLineSize = 4; + if (bigLineSize > radius / 2) + bigLineSize = radius / 2; + return bigLineSize; +} + +static QPointF calcRadialPos(const QStyleOptionSlider *dial, qreal offset) +{ + const int width = dial->rect.width(); + const int height = dial->rect.height(); + const int r = qMin(width, height) / 2; + const int currentSliderPosition = dial->upsideDown ? dial->sliderPosition : (dial->maximum - dial->sliderPosition); + qreal a = 0; + if (dial->maximum == dial->minimum) + a = Q_PI / 2; + else if (dial->dialWrapping) + a = Q_PI * 3 / 2 - (currentSliderPosition - dial->minimum) * 2 * Q_PI + / (dial->maximum - dial->minimum); + else + a = (Q_PI * 8 - (currentSliderPosition - dial->minimum) * 10 * Q_PI + / (dial->maximum - dial->minimum)) / 6; + qreal xc = width / 2.0; + qreal yc = height / 2.0; + qreal len = r - QStyleHelper::calcBigLineSize(r) - 3; + qreal back = offset * len; + QPointF pos(QPointF(xc + back * qCos(a), yc - back * qSin(a))); + return pos; +} + +qreal angle(const QPointF &p1, const QPointF &p2) +{ + static const qreal rad_factor = 180 / Q_PI; + qreal _angle = 0; + + if (p1.x() == p2.x()) { + if (p1.y() < p2.y()) + _angle = 270; + else + _angle = 90; + } else { + qreal x1, x2, y1, y2; + + if (p1.x() <= p2.x()) { + x1 = p1.x(); y1 = p1.y(); + x2 = p2.x(); y2 = p2.y(); + } else { + x2 = p1.x(); y2 = p1.y(); + x1 = p2.x(); y1 = p2.y(); + } + + qreal m = -(y2 - y1) / (x2 - x1); + _angle = qAtan(m) * rad_factor; + + if (p1.x() < p2.x()) + _angle = 180 - _angle; + else + _angle = -_angle; + } + return _angle; +} + +QPolygonF calcLines(const QStyleOptionSlider *dial) +{ + QPolygonF poly; + int width = dial->rect.width(); + int height = dial->rect.height(); + qreal r = qMin(width, height) / 2; + int bigLineSize = calcBigLineSize(int(r)); + + qreal xc = width / 2 + 0.5; + qreal yc = height / 2 + 0.5; + int ns = dial->tickInterval; + int notches = (dial->maximum + ns - 1 - dial->minimum) / ns; + if (notches <= 0) + return poly; + if (dial->maximum < dial->minimum || dial->maximum - dial->minimum > 1000) { + int maximum = dial->minimum + 1000; + notches = (maximum + ns - 1 - dial->minimum) / ns; + } + + poly.resize(2 + 2 * notches); + int smallLineSize = bigLineSize / 2; + for (int i = 0; i <= notches; ++i) { + qreal angle = dial->dialWrapping ? Q_PI * 3 / 2 - i * 2 * Q_PI / notches + : (Q_PI * 8 - i * 10 * Q_PI / notches) / 6; + qreal s = qSin(angle); + qreal c = qCos(angle); + if (i == 0 || (((ns * i) % (dial->pageStep ? dial->pageStep : 1)) == 0)) { + poly[2 * i] = QPointF(xc + (r - bigLineSize) * c, + yc - (r - bigLineSize) * s); + poly[2 * i + 1] = QPointF(xc + r * c, yc - r * s); + } else { + poly[2 * i] = QPointF(xc + (r - 1 - smallLineSize) * c, + yc - (r - 1 - smallLineSize) * s); + poly[2 * i + 1] = QPointF(xc + (r - 1) * c, yc -(r - 1) * s); + } + } + return poly; +} + +// This will draw a nice and shiny QDial for us. We don't want +// all the shinyness in QWindowsStyle, hence we place it here + +void drawDial(const QStyleOptionSlider *option, QPainter *painter) +{ + QPalette pal = option->palette; + QColor buttonColor = pal.button().color(); + const int width = option->rect.width(); + const int height = option->rect.height(); + const bool enabled = option->state & QStyle::State_Enabled; + qreal r = qMin(width, height) / 2; + r -= r/50; + const qreal penSize = r/20.0; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + // Draw notches + if (option->subControls & QStyle::SC_DialTickmarks) { + painter->setPen(option->palette.dark().color().darker(120)); + painter->drawLines(QStyleHelper::calcLines(option)); + } + + // Cache dial background + BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("qdial")); + p->setRenderHint(QPainter::Antialiasing); + + const qreal d_ = r / 6; + const qreal dx = option->rect.x() + d_ + (width - 2 * r) / 2 + 1; + const qreal dy = option->rect.y() + d_ + (height - 2 * r) / 2 + 1; + + QRectF br = QRectF(dx + 0.5, dy + 0.5, + int(r * 2 - 2 * d_ - 2), + int(r * 2 - 2 * d_ - 2)); + buttonColor.setHsv(buttonColor .hue(), + qMin(140, buttonColor .saturation()), + qMax(180, buttonColor.value())); + QColor shadowColor(0, 0, 0, 20); + + if (enabled) { + // Drop shadow + qreal shadowSize = qMax(1.0, penSize/2.0); + QRectF shadowRect= br.adjusted(-2*shadowSize, -2*shadowSize, + 2*shadowSize, 2*shadowSize); + QRadialGradient shadowGradient(shadowRect.center().x(), + shadowRect.center().y(), shadowRect.width()/2.0, + shadowRect.center().x(), shadowRect.center().y()); + shadowGradient.setColorAt(qreal(0.91), QColor(0, 0, 0, 40)); + shadowGradient.setColorAt(qreal(1.0), Qt::transparent); + p->setBrush(shadowGradient); + p->setPen(Qt::NoPen); + p->translate(shadowSize, shadowSize); + p->drawEllipse(shadowRect); + p->translate(-shadowSize, -shadowSize); + + // Main gradient + QRadialGradient gradient(br.center().x() - br.width()/3, dy, + br.width()*1.3, br.center().x(), + br.center().y() - br.height()/2); + gradient.setColorAt(0, buttonColor.lighter(110)); + gradient.setColorAt(qreal(0.5), buttonColor); + gradient.setColorAt(qreal(0.501), buttonColor.darker(102)); + gradient.setColorAt(1, buttonColor.darker(115)); + p->setBrush(gradient); + } else { + p->setBrush(Qt::NoBrush); + } + + p->setPen(QPen(buttonColor.darker(280))); + p->drawEllipse(br); + p->setBrush(Qt::NoBrush); + p->setPen(buttonColor.lighter(110)); + p->drawEllipse(br.adjusted(1, 1, -1, -1)); + + if (option->state & QStyle::State_HasFocus) { + QColor highlight = pal.highlight().color(); + highlight.setHsv(highlight.hue(), + qMin(160, highlight.saturation()), + qMax(230, highlight.value())); + highlight.setAlpha(127); + p->setPen(QPen(highlight, 2.0)); + p->setBrush(Qt::NoBrush); + p->drawEllipse(br.adjusted(-1, -1, 1, 1)); + } + + END_STYLE_PIXMAPCACHE + + QPointF dp = calcRadialPos(option, qreal(0.70)); + buttonColor = buttonColor.lighter(104); + buttonColor.setAlphaF(qreal(0.8)); + const qreal ds = r/qreal(7.0); + QRectF dialRect(dp.x() - ds, dp.y() - ds, 2*ds, 2*ds); + QRadialGradient dialGradient(dialRect.center().x() + dialRect.width()/2, + dialRect.center().y() + dialRect.width(), + dialRect.width()*2, + dialRect.center().x(), dialRect.center().y()); + dialGradient.setColorAt(1, buttonColor.darker(140)); + dialGradient.setColorAt(qreal(0.4), buttonColor.darker(120)); + dialGradient.setColorAt(0, buttonColor.darker(110)); + if (penSize > 3.0) { + painter->setPen(QPen(QColor(0, 0, 0, 25), penSize)); + painter->drawLine(calcRadialPos(option, qreal(0.90)), calcRadialPos(option, qreal(0.96))); + } + + painter->setBrush(dialGradient); + painter->setPen(QColor(255, 255, 255, 150)); + painter->drawEllipse(dialRect.adjusted(-1, -1, 1, 1)); + painter->setPen(QColor(0, 0, 0, 80)); + painter->drawEllipse(dialRect); + painter->restore(); +} +#endif //QT_NO_DIAL + +void drawBorderPixmap(const QPixmap &pixmap, QPainter *painter, const QRect &rect, + int left, int top, int right, + int bottom) +{ + QSize size = pixmap.size(); + //painter->setRenderHint(QPainter::SmoothPixmapTransform); + + //top + if (top > 0) { + painter->drawPixmap(QRect(rect.left() + left, rect.top(), rect.width() -right - left, top), pixmap, + QRect(left, 0, size.width() -right - left, top)); + + //top-left + if(left > 0) + painter->drawPixmap(QRect(rect.left(), rect.top(), left, top), pixmap, + QRect(0, 0, left, top)); + + //top-right + if (right > 0) + painter->drawPixmap(QRect(rect.left() + rect.width() - right, rect.top(), right, top), pixmap, + QRect(size.width() - right, 0, right, top)); + } + + //left + if (left > 0) + painter->drawPixmap(QRect(rect.left(), rect.top()+top, left, rect.height() - top - bottom), pixmap, + QRect(0, top, left, size.height() - bottom - top)); + + //center + painter->drawPixmap(QRect(rect.left() + left, rect.top()+top, rect.width() -right - left, + rect.height() - bottom - top), pixmap, + QRect(left, top, size.width() -right -left, + size.height() - bottom - top)); + //right + if (right > 0) + painter->drawPixmap(QRect(rect.left() +rect.width() - right, rect.top()+top, right, rect.height() - top - bottom), pixmap, + QRect(size.width() - right, top, right, size.height() - bottom - top)); + + //bottom + if (bottom > 0) { + painter->drawPixmap(QRect(rect.left() +left, rect.top() + rect.height() - bottom, + rect.width() - right - left, bottom), pixmap, + QRect(left, size.height() - bottom, + size.width() - right - left, bottom)); + //bottom-left + if (left > 0) + painter->drawPixmap(QRect(rect.left(), rect.top() + rect.height() - bottom, left, bottom), pixmap, + QRect(0, size.height() - bottom, left, bottom)); + + //bottom-right + if (right > 0) + painter->drawPixmap(QRect(rect.left() + rect.width() - right, rect.top() + rect.height() - bottom, right, bottom), pixmap, + QRect(size.width() - right, size.height() - bottom, right, bottom)); + + } +} +} +QT_END_NAMESPACE diff --git a/src/gui/styles/qstylehelper_p.h b/src/gui/styles/qstylehelper_p.h new file mode 100644 index 0000000000..27587e30ac --- /dev/null +++ b/src/gui/styles/qstylehelper_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> +#include <QtCore/qpoint.h> +#include <QtCore/qstring.h> +#include <QtGui/qpolygon.h> +#include <QtCore/qstringbuilder.h> + +#ifndef QSTYLEHELPER_P_H +#define QSTYLEHELPER_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 + +class QPainter; +class QPixmap; +class QStyleOptionSlider; +class QStyleOption; + +namespace QStyleHelper +{ + QString uniqueName(const QString &key, const QStyleOption *option, const QSize &size); + qreal dpiScaled(qreal value); +#ifndef QT_NO_DIAL + qreal angle(const QPointF &p1, const QPointF &p2); + QPolygonF calcLines(const QStyleOptionSlider *dial); + int calcBigLineSize(int radius); + void drawDial(const QStyleOptionSlider *dial, QPainter *painter); +#endif //QT_NO_DIAL + void drawBorderPixmap(const QPixmap &pixmap, QPainter *painter, const QRect &rect, + int left = 0, int top = 0, int right = 0, + int bottom = 0); +} + +// internal helper. Converts an integer value to an unique string token +template <typename T> + struct HexString +{ + inline HexString(const T t) + : val(t) + {} + + inline void write(QChar *&dest) const + { + const ushort hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const char *c = reinterpret_cast<const char *>(&val); + for (uint i = 0; i < sizeof(T); ++i) { + *dest++ = hexChars[*c & 0xf]; + *dest++ = hexChars[(*c & 0xf0) >> 4]; + ++c; + } + } + const T val; +}; + +// specialization to enable fast concatenating of our string tokens to a string +template <typename T> + struct QConcatenable<HexString<T> > +{ + typedef HexString<T> type; + enum { ExactSize = true }; + static int size(const HexString<T> &) { return sizeof(T) * 2; } + static inline void appendTo(const HexString<T> &str, QChar *&out) { str.write(out); } + typedef QString ConvertTo; +}; + +QT_END_NAMESPACE + +#endif // QSTYLEHELPER_P_H diff --git a/src/gui/styles/qstyleoption.cpp b/src/gui/styles/qstyleoption.cpp new file mode 100644 index 0000000000..ee8e177546 --- /dev/null +++ b/src/gui/styles/qstyleoption.cpp @@ -0,0 +1,5508 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstyleoption.h" +#include "qapplication.h" +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "qmacstyle_mac.h" +#endif +#include <qdebug.h> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QStyleOption + \brief The QStyleOption class stores the parameters used by QStyle functions. + + \ingroup appearance + + QStyleOption and its subclasses contain all the information that + QStyle functions need to draw a graphical element. + + For performance reasons, there are few member functions and the + access to the member variables is direct (i.e., using the \c . or + \c -> operator). This low-level feel makes the structures + straightforward to use and emphasizes that these are simply + parameters used by the style functions. + + The caller of a QStyle function usually creates QStyleOption + objects on the stack. This combined with Qt's extensive use of + \l{implicit sharing} for types such as QString, QPalette, and + QColor ensures that no memory allocation needlessly takes place. + + The following code snippet shows how to use a specific + QStyleOption subclass to paint a push button: + + \snippet doc/src/snippets/qstyleoption/main.cpp 0 + + In our example, the control is a QStyle::CE_PushButton, and + according to the QStyle::drawControl() documentation the + corresponding class is QStyleOptionButton. + + When reimplementing QStyle functions that take a QStyleOption + parameter, you often need to cast the QStyleOption to a subclass. + For safety, you can use qstyleoption_cast() to ensure that the + pointer type is correct. For example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 4 + + The qstyleoption_cast() function will return 0 if the object to + which \c option points is not of the correct type. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyle, QStylePainter +*/ + +/*! + \enum QStyleOption::OptionType + + This enum is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \value SO_Button \l QStyleOptionButton + \value SO_ComboBox \l QStyleOptionComboBox + \value SO_Complex \l QStyleOptionComplex + \value SO_Default QStyleOption + \value SO_DockWidget \l QStyleOptionDockWidget + \value SO_FocusRect \l QStyleOptionFocusRect + \value SO_Frame \l QStyleOptionFrame \l QStyleOptionFrameV2 + \value SO_GraphicsItem \l QStyleOptionGraphicsItem + \value SO_GroupBox \l QStyleOptionGroupBox + \value SO_Header \l QStyleOptionHeader + \value SO_MenuItem \l QStyleOptionMenuItem + \value SO_ProgressBar \l QStyleOptionProgressBar \l QStyleOptionProgressBarV2 + \value SO_RubberBand \l QStyleOptionRubberBand + \value SO_SizeGrip \l QStyleOptionSizeGrip + \value SO_Slider \l QStyleOptionSlider + \value SO_SpinBox \l QStyleOptionSpinBox + \value SO_Tab \l QStyleOptionTab + \value SO_TabBarBase \l QStyleOptionTabBarBase + \value SO_TabWidgetFrame \l QStyleOptionTabWidgetFrame + \value SO_TitleBar \l QStyleOptionTitleBar + \value SO_ToolBar \l QStyleOptionToolBar + \value SO_ToolBox \l QStyleOptionToolBox + \value SO_ToolButton \l QStyleOptionToolButton + \value SO_ViewItem \l QStyleOptionViewItem (used in Interviews) + + The following values are used for custom controls: + + \value SO_CustomBase Reserved for custom QStyleOptions; + all custom controls values must be above this value + \value SO_ComplexCustomBase Reserved for custom QStyleOptions; + all custom complex controls values must be above this value + + Some style options are defined for various Qt3Support controls: + + \value SO_Q3DockWindow \l QStyleOptionQ3DockWindow + \value SO_Q3ListView \l QStyleOptionQ3ListView + \value SO_Q3ListViewItem \l QStyleOptionQ3ListViewItem + + \sa type +*/ + +/*! + Constructs a QStyleOption with the specified \a version and \a + type. + + The version has no special meaning for QStyleOption; it can be + used by subclasses to distinguish between different version of + the same option type. + + The \l state member variable is initialized to + QStyle::State_None. + + \sa version, type +*/ + +QStyleOption::QStyleOption(int version, int type) + : version(version), type(type), state(QStyle::State_None), + direction(QApplication::layoutDirection()), fontMetrics(QFont()) +{ +} + + +/*! + Destroys this style option object. +*/ +QStyleOption::~QStyleOption() +{ +} + +/*! + \fn void QStyleOption::initFrom(const QWidget *widget) + \since 4.1 + + Initializes the \l state, \l direction, \l rect, \l palette, and + \l fontMetrics member variables based on the specified \a widget. + + This is a convenience function; the member variables can also be + initialized manually. + + \sa QWidget::layoutDirection(), QWidget::rect(), + QWidget::palette(), QWidget::fontMetrics() +*/ + +/*! + \obsolete + + Use initFrom(\a widget) instead. +*/ +void QStyleOption::init(const QWidget *widget) +{ + QWidget *window = widget->window(); + state = QStyle::State_None; + if (widget->isEnabled()) + state |= QStyle::State_Enabled; + if (widget->hasFocus()) + state |= QStyle::State_HasFocus; + if (window->testAttribute(Qt::WA_KeyboardFocusChange)) + state |= QStyle::State_KeyboardFocusChange; + if (widget->underMouse()) + state |= QStyle::State_MouseOver; + if (window->isActiveWindow()) + state |= QStyle::State_Active; + if (widget->isWindow()) + state |= QStyle::State_Window; +#ifdef Q_WS_MAC + extern bool qt_mac_can_clickThrough(const QWidget *w); //qwidget_mac.cpp + if (!(state & QStyle::State_Active) && !qt_mac_can_clickThrough(widget)) + state &= ~QStyle::State_Enabled; + + switch (QMacStyle::widgetSizePolicy(widget)) { + case QMacStyle::SizeSmall: + state |= QStyle::State_Small; + break; + case QMacStyle::SizeMini: + state |= QStyle::State_Mini; + break; + default: + ; + } +#endif +#ifdef QT_KEYPAD_NAVIGATION + if (widget->hasEditFocus()) + state |= QStyle::State_HasEditFocus; +#endif + + direction = widget->layoutDirection(); + rect = widget->rect(); + palette = widget->palette(); + fontMetrics = widget->fontMetrics(); +} + +/*! + Constructs a copy of \a other. +*/ +QStyleOption::QStyleOption(const QStyleOption &other) + : version(Version), type(Type), state(other.state), + direction(other.direction), rect(other.rect), fontMetrics(other.fontMetrics), + palette(other.palette) +{ +} + +/*! + Assign \a other to this QStyleOption. +*/ +QStyleOption &QStyleOption::operator=(const QStyleOption &other) +{ + state = other.state; + direction = other.direction; + rect = other.rect; + fontMetrics = other.fontMetrics; + palette = other.palette; + return *this; +} + +/*! + \enum QStyleOption::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Default} for + this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOption::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOption::palette + \brief the palette that should be used when painting the control + + By default, the application's default palette is used. + + \sa initFrom() +*/ + +/*! + \variable QStyleOption::direction + \brief the text layout direction that should be used when drawing text in the control + + By default, the layout direction is Qt::LeftToRight. + + \sa initFrom() +*/ + +/*! + \variable QStyleOption::fontMetrics + \brief the font metrics that should be used when drawing text in the control + + By default, the application's default font is used. + + \sa initFrom() +*/ + +/*! + \variable QStyleOption::rect + \brief the area that should be used for various calculations and painting + + This can have different meanings for different types of elements. + For example, for a \l QStyle::CE_PushButton element it would be + the rectangle for the entire button, while for a \l + QStyle::CE_PushButtonLabel element it would be just the area for + the push button label. + + The default value is a null rectangle, i.e. a rectangle with both + the width and the height set to 0. + + \sa initFrom() +*/ + +/*! + \variable QStyleOption::state + \brief the style flags that are used when drawing the control + + The default value is QStyle::State_None. + + \sa initFrom(), QStyle::drawPrimitive(), QStyle::drawControl(), + QStyle::drawComplexControl(), QStyle::State +*/ + +/*! + \variable QStyleOption::type + \brief the option type of the style option + + The default value is SO_Default. + + \sa OptionType +*/ + +/*! + \variable QStyleOption::version + \brief the version of the style option + + This value can be used by subclasses to implement extensions + without breaking compatibility. If you use the qstyleoption_cast() + function, you normally do not need to check it. + + The default value is 1. +*/ + +/*! + \class QStyleOptionFocusRect + \brief The QStyleOptionFocusRect class is used to describe the + parameters for drawing a focus rectangle with QStyle. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionFocusRect, initializing the members + variables to their default values. +*/ + +QStyleOptionFocusRect::QStyleOptionFocusRect() + : QStyleOption(Version, SO_FocusRect) +{ + state |= QStyle::State_KeyboardFocusChange; // assume we had one, will be corrected in initFrom() +} + +/*! + \internal +*/ +QStyleOptionFocusRect::QStyleOptionFocusRect(int version) + : QStyleOption(version, SO_FocusRect) +{ + state |= QStyle::State_KeyboardFocusChange; // assume we had one, will be corrected in initFrom() +} + +/*! + \enum QStyleOptionFocusRect::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_FocusRect} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionFocusRect::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \fn QStyleOptionFocusRect::QStyleOptionFocusRect(const QStyleOptionFocusRect &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \variable QStyleOptionFocusRect::backgroundColor + \brief the background color on which the focus rectangle is being drawn + + The default value is an invalid color with the RGB value (0, 0, + 0). An invalid color is a color that is not properly set up for + the underlying window system. +*/ + +/*! + \class QStyleOptionFrame + \brief The QStyleOptionFrame class is used to describe the + parameters for drawing a frame. + + QStyleOptionFrame is used for drawing several built-in Qt widgets, + including QFrame, QGroupBox, QLineEdit, and QMenu. Note that to + describe the parameters necessary for drawing a frame in Qt 4.1 or + above, you must use the QStyleOptionFrameV2 subclass. + + An instance of the QStyleOptionFrame class has + \l{QStyleOption::type} {type} SO_Frame and \l{QStyleOption::version} + {version} 1. + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionFrame and QStyleOptionFrameV2. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionFrameV2, QStyleOption +*/ + +/*! + Constructs a QStyleOptionFrame, initializing the members + variables to their default values. +*/ + +QStyleOptionFrame::QStyleOptionFrame() + : QStyleOption(Version, SO_Frame), lineWidth(0), midLineWidth(0) +{ +} + +/*! + \internal +*/ +QStyleOptionFrame::QStyleOptionFrame(int version) + : QStyleOption(version, SO_Frame), lineWidth(0), midLineWidth(0) +{ +} + +/*! + \fn QStyleOptionFrame::QStyleOptionFrame(const QStyleOptionFrame &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionFrame::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Frame} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionFrame::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionFrame::lineWidth + \brief the line width for drawing the frame + + The default value is 0. + + \sa QFrame::lineWidth +*/ + +/*! + \variable QStyleOptionFrame::midLineWidth + \brief the mid-line width for drawing the frame + + This is usually used in drawing sunken or raised frames. + + The default value is 0. + + \sa QFrame::midLineWidth +*/ + +/*! + \class QStyleOptionFrameV2 + \brief The QStyleOptionFrameV2 class is used to describe the + parameters necessary for drawing a frame in Qt 4.1 or above. + + \since 4.1 + + QStyleOptionFrameV2 inherits QStyleOptionFrame which is used for + drawing several built-in Qt widgets, including QFrame, QGroupBox, + QLineEdit, and QMenu. + + An instance of the QStyleOptionFrameV2 class has + \l{QStyleOption::type} {type} SO_Frame and + \l{QStyleOption::version} {version} 2. The type is used + internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionFrame and QStyleOptionFrameV2. One way to achieve this + is to use the QStyleOptionFrameV2 copy constructor. For example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 1 + + In the example above: If the \c frameOption's version is 1, \l + FrameFeature is set to \l None for \c frameOptionV2. If \c + frameOption's version is 2, the constructor will simply copy the + \c frameOption's \l FrameFeature value. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionFrame, QStyleOption +*/ + +/*! + Constructs a QStyleOptionFrameV2 object. +*/ +QStyleOptionFrameV2::QStyleOptionFrameV2() + : QStyleOptionFrame(Version), features(None) +{ +} + +/*! + \fn QStyleOptionFrameV2::QStyleOptionFrameV2(const QStyleOptionFrameV2 &other) + + Constructs a QStyleOptionFrameV2 copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionFrameV2::QStyleOptionFrameV2(int version) + : QStyleOptionFrame(version), features(None) +{ +} + +/*! + Constructs a QStyleOptionFrameV2 copy of the \a other style option + which can be either of the QStyleOptionFrameV2 or + QStyleOptionFrame types. + + If the \a other style option's version is 1, the new style option's \l + FrameFeature value is set to \l QStyleOptionFrameV2::None. If its + version is 2, its \l FrameFeature value is simply copied to the + new style option. + + \sa version +*/ +QStyleOptionFrameV2::QStyleOptionFrameV2(const QStyleOptionFrame &other) +{ + QStyleOptionFrame::operator=(other); + + const QStyleOptionFrameV2 *f2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(&other); + features = f2 ? f2->features : FrameFeatures(QStyleOptionFrameV2::None); + version = Version; +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionFrameV2 or + QStyleOptionFrame types. + + If the \a{other} style option's version is 1, this style option's + \l FrameFeature value is set to \l QStyleOptionFrameV2::None. If + its version is 2, its \l FrameFeature value is simply copied to + this style option. +*/ +QStyleOptionFrameV2 &QStyleOptionFrameV2::operator=(const QStyleOptionFrame &other) +{ + QStyleOptionFrame::operator=(other); + + const QStyleOptionFrameV2 *f2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(&other); + features = f2 ? f2->features : FrameFeatures(QStyleOptionFrameV2::None); + version = Version; + return *this; +} + +/*! + \enum QStyleOptionFrameV2::FrameFeature + + This enum describes the different types of features a frame can have. + + \value None Indicates a normal frame. + \value Flat Indicates a flat frame. +*/ + +/*! + \variable QStyleOptionFrameV2::features + \brief a bitwise OR of the features that describe this frame. + + \sa FrameFeature +*/ + +/*! + \enum QStyleOptionFrameV2::StyleOptionVersion + + This enum is used to hold information about the version of the + style option, and is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \class QStyleOptionFrameV3 + \brief The QStyleOptionFrameV3 class is used to describe the + parameters necessary for drawing a frame in Qt 4.1 or above. + + \since 4.5 + + QStyleOptionFrameV3 inherits QStyleOptionFrameV2 + + An instance of the QStyleOptionFrameV3 class has + \l{QStyleOption::type} {type} SO_Frame and + \l{QStyleOption::version} {version} 3. The type is used + internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + \sa QStyleOptionFrameV2, QStyleOption +*/ + +/*! + Constructs a QStyleOptionFrameV3 object. +*/ +QStyleOptionFrameV3::QStyleOptionFrameV3() + : QStyleOptionFrameV2(Version), frameShape(QFrame::NoFrame), unused(0) +{ +} + +/*! + \fn QStyleOptionFrameV3::QStyleOptionFrameV3(const QStyleOptionFrameV3 &other) + + Constructs a QStyleOptionFrameV3 copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionFrameV3::QStyleOptionFrameV3(int version) + : QStyleOptionFrameV2(version), frameShape(QFrame::NoFrame), unused(0) +{ +} + +/*! + Constructs a QStyleOptionFrameV3 copy of the \a other style option + which can be either of the QStyleOptionFrameV3 or + QStyleOptionFrame types. + + If the \a other style option's version is 1, the new style + option's \l FrameFeature value is set to + \l{QStyleOptionFrameV2::None}. If its version is 2 or lower, + \l{QStyleOptionFrameV3::frameShape} value is QFrame::NoFrame + + \sa version +*/ +QStyleOptionFrameV3::QStyleOptionFrameV3(const QStyleOptionFrame &other) +{ + operator=(other); +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionFrameV3, + QStyleOptionFrameV2 or QStyleOptionFrame types. + + If the \a other style option's version is 1, the new style + option's \l FrameFeature value is set to + \l{QStyleOptionFrameV2::None}. If its version is 2 or lower, + \l{QStyleOptionFrameV3::frameShape} value is QFrame::NoFrame +*/ +QStyleOptionFrameV3 &QStyleOptionFrameV3::operator=(const QStyleOptionFrame &other) +{ + QStyleOptionFrameV2::operator=(other); + + const QStyleOptionFrameV3 *f3 = qstyleoption_cast<const QStyleOptionFrameV3 *>(&other); + frameShape = f3 ? f3->frameShape : QFrame::NoFrame; + version = Version; + return *this; +} + + +/*! + \variable QStyleOptionFrameV3::frameShape + \brief This property holds the frame shape value of the frame. + + \sa QFrame::frameShape +*/ + +/*! + \enum QStyleOptionFrameV3::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 3 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \class QStyleOptionViewItemV2 + \brief The QStyleOptionViewItemV2 class is used to describe the + parameters necessary for drawing a frame in Qt 4.2 or above. + \since 4.2 + + QStyleOptionViewItemV2 inherits QStyleOptionViewItem. + + An instance of the QStyleOptionViewItemV2 class has + \l{QStyleOption::type} {type} SO_ViewItem and + \l{QStyleOption::version} {version} 2. The type is used internally + by QStyleOption, its subclasses, and qstyleoption_cast() to + determine the type of style option. In general you do not need to + worry about this unless you want to create your own QStyleOption + subclass and your own styles. The version is used by QStyleOption + subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not + need to check it. + + See QStyleOptionFrameV2's detailed description for a discussion + of how to handle "V2" classes. + + \sa QStyleOptionViewItem, QStyleOption +*/ + +/*! + \enum QStyleOptionViewItemV2::StyleOptionVersion + + This enum is used to hold information about the version of the + style option, and is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionViewItemV2::features + \brief a bitwise OR of the features that describe this view item + + \sa ViewItemFeature +*/ + +/*! + Constructs a QStyleOptionViewItemV2 object. +*/ +QStyleOptionViewItemV2::QStyleOptionViewItemV2() + : QStyleOptionViewItem(Version), features(None) +{ +} + +/*! + \fn QStyleOptionViewItemV2::QStyleOptionViewItemV2(const QStyleOptionViewItemV2 &other) + + Constructs a copy of \a other. +*/ + +/*! + Constructs a QStyleOptionViewItemV2 copy of the \a other style option + which can be either of the QStyleOptionViewItemV2 or + QStyleOptionViewItem types. + + If the \a other style option's version is 1, the new style option's \l + ViewItemFeature value is set to \l QStyleOptionViewItemV2::None. If its + version is 2, its \l ViewItemFeature value is simply copied to the + new style option. + + \sa version +*/ +QStyleOptionViewItemV2::QStyleOptionViewItemV2(const QStyleOptionViewItem &other) + : QStyleOptionViewItem(Version) +{ + (void)QStyleOptionViewItemV2::operator=(other); +} + +/*! + \internal +*/ +QStyleOptionViewItemV2::QStyleOptionViewItemV2(int version) + : QStyleOptionViewItem(version) +{ + +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionViewItemV2 or + QStyleOptionViewItem types. + + If the \a{other} style option's version is 1, this style option's + \l ViewItemFeature value is set to \l QStyleOptionViewItemV2::None. + If its version is 2, its \l ViewItemFeature value is simply copied + to this style option. +*/ +QStyleOptionViewItemV2 &QStyleOptionViewItemV2::operator=(const QStyleOptionViewItem &other) +{ + QStyleOptionViewItem::operator=(other); + const QStyleOptionViewItemV2 *v2 = qstyleoption_cast<const QStyleOptionViewItemV2 *>(&other); + features = v2 ? v2->features : ViewItemFeatures(QStyleOptionViewItemV2::None); + return *this; +} + +/*! + \enum QStyleOptionViewItemV2::ViewItemFeature + + This enum describes the different types of features an item can have. + + \value None Indicates a normal item. + \value WrapText Indicates an item with wrapped text. + \value Alternate Indicates that the item's background is rendered using alternateBase. + \value HasCheckIndicator Indicates that the item has a check state indicator. + \value HasDisplay Indicates that the item has a display role. + \value HasDecoration Indicates that the item has a decoration role. +*/ + + + +/*! + \class QStyleOptionViewItemV3 + \brief The QStyleOptionViewItemV3 class is used to describe the + parameters necessary for drawing a frame in Qt 4.3 or above. + \since 4.3 + + QStyleOptionViewItemV3 inherits QStyleOptionViewItem. + + An instance of the QStyleOptionViewItemV3 class has + \l{QStyleOption::type} {type} SO_ViewItem and \l{QStyleOption::version} + {version} 3. The type is used internally by QStyleOption, its subclasses, + and qstyleoption_cast() to determine the type of style option. In general + you do not need to worry about this unless you want to create your own + QStyleOption subclass and your own styles. The version is used by + QStyleOption subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not need to + check it. + + See QStyleOptionFrameV2's detailed description for a discussion + of how to handle "V2" and other versioned classes. + + \sa QStyleOptionViewItem, QStyleOption +*/ + +/*! + \enum QStyleOptionViewItemV3::StyleOptionVersion + + This enum is used to hold information about the version of the + style option, and is defined for each QStyleOption subclass. + + \value Version 3 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + Constructs a QStyleOptionViewItemV3 object. +*/ +QStyleOptionViewItemV3::QStyleOptionViewItemV3() + : QStyleOptionViewItemV2(Version), widget(0) +{ +} + +/*! + Constructs a copy of \a other. +*/ +QStyleOptionViewItemV3::QStyleOptionViewItemV3(const QStyleOptionViewItem &other) + : QStyleOptionViewItemV2(Version), widget(0) +{ + (void)QStyleOptionViewItemV3::operator=(other); +} + +/*! + \fn QStyleOptionViewItemV3::QStyleOptionViewItemV3(const QStyleOptionViewItemV3 &other) + + Constructs a copy of \a other. +*/ + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be an instance of the QStyleOptionViewItemV2, + QStyleOptionViewItemV3 or QStyleOptionViewItem types. +*/ +QStyleOptionViewItemV3 &QStyleOptionViewItemV3::operator = (const QStyleOptionViewItem &other) +{ + QStyleOptionViewItemV2::operator=(other); + const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3*>(&other); + locale = v3 ? v3->locale : QLocale(); + widget = v3 ? v3->widget : 0; + return *this; +} + +/*! + \internal +*/ +QStyleOptionViewItemV3::QStyleOptionViewItemV3(int version) + : QStyleOptionViewItemV2(version), widget(0) +{ +} + +#ifndef QT_NO_ITEMVIEWS + +/*! + \class QStyleOptionViewItemV4 + \brief The QStyleOptionViewItemV4 class is used to describe the + parameters necessary for drawing a frame in Qt 4.4 or above. + \since 4.4 + + QStyleOptionViewItemV4 inherits QStyleOptionViewItemV3. + + An instance of the QStyleOptionViewItemV4 class has + \l{QStyleOption::type} {type} SO_ViewItem and + \l{QStyleOption::version} {version} 4. The type is used internally + by QStyleOption, its subclasses, and qstyleoption_cast() to + determine the type of style option. In general you do not need to + worry about this unless you want to create your own QStyleOption + subclass and your own styles. The version is used by QStyleOption + subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not + need to check it. + + See QStyleOptionViewItemV3's detailed description for a discussion + of how to handle "V3" classes. + + \sa QStyleOptionViewItem, QStyleOption +*/ + +/*! + \variable QStyleOptionViewItemV4::index + + The model index that is to be drawn. +*/ + +/*! + \variable QStyleOptionViewItemV4::checkState + + If this view item is checkable, i.e., + ViewItemFeature::HasCheckIndicator is true, \c checkState is true + if the item is checked; otherwise, it is false. + +*/ + +/*! + \variable QStyleOptionViewItemV4::icon + + The icon (if any) to be drawn in the view item. +*/ + + +/*! + \variable QStyleOptionViewItemV4::text + + The text (if any) to be drawn in the view item. +*/ + +/*! + \variable QStyleOptionViewItemV4::backgroundBrush + + The QBrush that should be used to paint the view items + background. +*/ + +/*! + \variable QStyleOptionViewItemV4::viewItemPosition + + Gives the position of this view item relative to other items. See + the \l{QStyleOptionViewItemV4::}{ViewItemPosition} enum for the + details. +*/ + +/*! + \enum QStyleOptionViewItemV4::StyleOptionVersion + + This enum is used to hold information about the version of the + style option, and is defined for each QStyleOption subclass. + + \value Version 4 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \enum QStyleOptionViewItemV4::ViewItemPosition + + This enum is used to represent the placement of the item on + a row. This can be used to draw items differently depending + on their placement, for example by putting rounded edges at + the beginning and end, and straight edges in between. + + \value Invalid The ViewItemPosition is unknown and should be + disregarded. + \value Beginning The item appears at the beginning of the row. + \value Middle The item appears in the middle of the row. + \value End The item appears at the end of the row. + \value OnlyOne The item is the only one on the row, and is + therefore both at the beginning and the end. +*/ + + +/*! + Constructs a QStyleOptionViewItemV4 object. +*/ +QStyleOptionViewItemV4::QStyleOptionViewItemV4() +: QStyleOptionViewItemV3(Version), checkState(Qt::Unchecked), viewItemPosition(QStyleOptionViewItemV4::Invalid) +{ +} + +/*! + \fn QStyleOptionViewItemV4::QStyleOptionViewItemV4(const QStyleOptionViewItemV4 &other) + + Constructs a copy of \a other. +*/ + +/*! + Constructs a QStyleOptionViewItemV4 copy of the \a other style option + which can be either of the QStyleOptionViewItemV3 or + QStyleOptionViewItem types. + + \sa version +*/ +QStyleOptionViewItemV4::QStyleOptionViewItemV4(const QStyleOptionViewItem &other) + : QStyleOptionViewItemV3(Version) +{ + (void)QStyleOptionViewItemV4::operator=(other); +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionViewItemV3 or + QStyleOptionViewItem types. +*/ +QStyleOptionViewItemV4 &QStyleOptionViewItemV4::operator = (const QStyleOptionViewItem &other) +{ + QStyleOptionViewItemV3::operator=(other); + if (const QStyleOptionViewItemV4 *v4 = qstyleoption_cast<const QStyleOptionViewItemV4*>(&other)) { + index = v4->index; + checkState = v4->checkState; + text = v4->text; + viewItemPosition = v4->viewItemPosition; + backgroundBrush = v4->backgroundBrush; + icon = v4->icon; + } else { + viewItemPosition = QStyleOptionViewItemV4::Invalid; + checkState = Qt::Unchecked; + } + return *this; +} + +/*! + \internal +*/ +QStyleOptionViewItemV4::QStyleOptionViewItemV4(int version) + : QStyleOptionViewItemV3(version) +{ +} +#endif // QT_NO_ITEMVIEWS + +/*! + \class QStyleOptionGroupBox + \brief The QStyleOptionGroupBox class describes the parameters for + drawing a group box. + + \since 4.1 + + QStyleOptionButton contains all the information that QStyle + functions need the various graphical elements of a group box. + + It holds the lineWidth and the midLineWidth for drawing the panel, + the group box's \l {text}{title} and the title's \l + {textAlignment}{alignment} and \l {textColor}{color}. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex, QGroupBox +*/ + +/*! + \enum QStyleOptionGroupBox::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_GroupBox} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionGroupBox::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionGroupBox::lineWidth + \brief the line width for drawing the panel + + The value of this variable is, currently, always 1. + + \sa QFrame::lineWidth +*/ + +/*! + \variable QStyleOptionGroupBox::midLineWidth + \brief the mid-line width for drawing the panel + + The mid-line width is usually used when drawing sunken or raised + group box frames. The value of this variable is, currently, always 0. + + \sa QFrame::midLineWidth +*/ + +/*! + \variable QStyleOptionGroupBox::text + \brief the text of the group box + + The default value is an empty string. + + \sa QGroupBox::title +*/ + +/*! + \variable QStyleOptionGroupBox::textAlignment + \brief the alignment of the group box title + + The default value is Qt::AlignLeft. + + \sa QGroupBox::alignment +*/ + +/*! + \variable QStyleOptionGroupBox::features + \brief the features of the group box frame + + The frame is flat by default. + + \sa QStyleOptionFrameV2::FrameFeature +*/ + +/*! + \variable QStyleOptionGroupBox::textColor + \brief the color of the group box title + + The default value is an invalid color with the RGB value (0, 0, + 0). An invalid color is a color that is not properly set up for + the underlying window system. +*/ + +/*! + Constructs a QStyleOptionGroupBox, initializing the members + variables to their default values. +*/ +QStyleOptionGroupBox::QStyleOptionGroupBox() + : QStyleOptionComplex(Version, Type), features(QStyleOptionFrameV2::None), + textAlignment(Qt::AlignLeft), lineWidth(0), midLineWidth(0) +{ +} + +/*! + \fn QStyleOptionGroupBox::QStyleOptionGroupBox(const QStyleOptionGroupBox &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionGroupBox::QStyleOptionGroupBox(int version) + : QStyleOptionComplex(version, Type), features(QStyleOptionFrameV2::None), + textAlignment(Qt::AlignLeft), lineWidth(0), midLineWidth(0) +{ +} + +/*! + \class QStyleOptionHeader + \brief The QStyleOptionHeader class is used to describe the + parameters for drawing a header. + + QStyleOptionHeader contains all the information that QStyle + functions need to draw the item views' header pane, header sort + arrow, and header label. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionHeader, initializing the members + variables to their default values. +*/ + +QStyleOptionHeader::QStyleOptionHeader() + : QStyleOption(QStyleOptionHeader::Version, SO_Header), + section(0), textAlignment(Qt::AlignLeft), iconAlignment(Qt::AlignLeft), + position(QStyleOptionHeader::Beginning), + selectedPosition(QStyleOptionHeader::NotAdjacent), sortIndicator(None), + orientation(Qt::Horizontal) +{ +} + +/*! + \internal +*/ +QStyleOptionHeader::QStyleOptionHeader(int version) + : QStyleOption(version, SO_Header), + section(0), textAlignment(Qt::AlignLeft), iconAlignment(Qt::AlignLeft), + position(QStyleOptionHeader::Beginning), + selectedPosition(QStyleOptionHeader::NotAdjacent), sortIndicator(None), + orientation(Qt::Horizontal) +{ +} + +/*! + \variable QStyleOptionHeader::orientation + \brief the header's orientation (horizontal or vertical) + + The default orientation is Qt::Horizontal +*/ + +/*! + \fn QStyleOptionHeader::QStyleOptionHeader(const QStyleOptionHeader &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionHeader::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Header} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionHeader::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionHeader::section + \brief which section of the header is being painted + + The default value is 0. +*/ + +/*! + \variable QStyleOptionHeader::text + \brief the text of the header + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionHeader::textAlignment + \brief the alignment flags for the text of the header + + The default value is Qt::AlignLeft. +*/ + +/*! + \variable QStyleOptionHeader::icon + \brief the icon of the header + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionHeader::iconAlignment + \brief the alignment flags for the icon of the header + + The default value is Qt::AlignLeft. +*/ + +/*! + \variable QStyleOptionHeader::position + \brief the section's position in relation to the other sections + + The default value is QStyleOptionHeader::Beginning. +*/ + +/*! + \variable QStyleOptionHeader::selectedPosition + \brief the section's position in relation to the selected section + + The default value is QStyleOptionHeader::NotAdjacent +*/ + +/*! + \variable QStyleOptionHeader::sortIndicator + \brief the direction the sort indicator should be drawn + + The default value is QStyleOptionHeader::None. +*/ + +/*! + \enum QStyleOptionHeader::SectionPosition + + This enum lets you know where the section's position is in relation to the other sections. + + \value Beginning At the beginining of the header + \value Middle In the middle of the header + \value End At the end of the header + \value OnlyOneSection Only one header section + + \sa position +*/ + +/*! + \enum QStyleOptionHeader::SelectedPosition + + This enum lets you know where the section's position is in relation to the selected section. + + \value NotAdjacent Not adjacent to the selected section + \value NextIsSelected The next section is selected + \value PreviousIsSelected The previous section is selected + \value NextAndPreviousAreSelected Both the next and previous section are selected + + \sa selectedPosition +*/ + +/*! + \enum QStyleOptionHeader::SortIndicator + + Indicates which direction the sort indicator should be drawn + + \value None No sort indicator is needed + \value SortUp Draw an up indicator + \value SortDown Draw a down indicator + + \sa sortIndicator +*/ + +/*! + \class QStyleOptionButton + \brief The QStyleOptionButton class is used to describe the + parameters for drawing buttons. + + QStyleOptionButton contains all the information that QStyle + functions need to draw graphical elements like QPushButton, + QCheckBox, and QRadioButton. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionToolButton +*/ + +/*! + \enum QStyleOptionButton::ButtonFeature + + This enum describes the different types of features a push button can have. + + \value None Indicates a normal push button. + \value Flat Indicates a flat push button. + \value HasMenu Indicates that the button has a drop down menu. + \value DefaultButton Indicates that the button is a default button. + \value AutoDefaultButton Indicates that the button is an auto default button. + \value CommandLinkButton Indicates that the button is a Windows Vista type command link. + + \sa features +*/ + +/*! + Constructs a QStyleOptionButton, initializing the members + variables to their default values. +*/ + +QStyleOptionButton::QStyleOptionButton() + : QStyleOption(QStyleOptionButton::Version, SO_Button), features(None) +{ +} + +/*! + \internal +*/ +QStyleOptionButton::QStyleOptionButton(int version) + : QStyleOption(version, SO_Button), features(None) +{ +} + +/*! + \fn QStyleOptionButton::QStyleOptionButton(const QStyleOptionButton &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionButton::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Button} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionButton::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionButton::features + \brief a bitwise OR of the features that describe this button + + \sa ButtonFeature +*/ + +/*! + \variable QStyleOptionButton::text + \brief the text of the button + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionButton::icon + \brief the icon of the button + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. + + \sa iconSize +*/ + +/*! + \variable QStyleOptionButton::iconSize + \brief the size of the icon for the button + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + + +#ifndef QT_NO_TOOLBAR +/*! + \class QStyleOptionToolBar + \brief The QStyleOptionToolBar class is used to describe the + parameters for drawing a toolbar. + + \since 4.1 + + QStyleOptionToolBar contains all the information that QStyle + functions need to draw QToolBar. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + The QStyleOptionToolBar class holds the lineWidth and the + midLineWidth for drawing the widget. It also stores information + about which \l {toolBarArea}{area} the toolbar should be located + in, whether it is movable or not, which position the toolbar line + should have (positionOfLine), and the toolbar's position within + the line (positionWithinLine). + + In addition, the class provides a couple of enums: The + ToolBarFeature enum is used to describe whether a toolbar is + movable or not, and the ToolBarPosition enum is used to describe + the position of a toolbar line, as well as the toolbar's position + within the line. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionToolBar, initializing the members + variables to their default values. +*/ + +QStyleOptionToolBar::QStyleOptionToolBar() + : QStyleOption(Version, SO_ToolBar), positionOfLine(OnlyOne), positionWithinLine(OnlyOne), + toolBarArea(Qt::TopToolBarArea), features(None), lineWidth(0), midLineWidth(0) +{ +} + +/*! + \fn QStyleOptionToolBar::QStyleOptionToolBar(const QStyleOptionToolBar &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionToolBar::QStyleOptionToolBar(int version) +: QStyleOption(version, SO_ToolBar), positionOfLine(OnlyOne), positionWithinLine(OnlyOne), + toolBarArea(Qt::TopToolBarArea), features(None), lineWidth(0), midLineWidth(0) +{ + +} + +/*! + \enum QStyleOptionToolBar::ToolBarPosition + + \image qstyleoptiontoolbar-position.png + + This enum is used to describe the position of a toolbar line, as + well as the toolbar's position within the line. + + The order of the positions within a line starts at the top of a + vertical line, and from the left within a horizontal line. The + order of the positions for the lines is always from the the + parent widget's boundary edges. + + \value Beginning The toolbar is located at the beginning of the line, + or the toolbar line is the first of several lines. There can + only be one toolbar (and only one line) with this position. + \value Middle The toolbar is located in the middle of the line, + or the toolbar line is in the middle of several lines. There can + several toolbars (and lines) with this position. + \value End The toolbar is located at the end of the line, + or the toolbar line is the last of several lines. There can + only be one toolbar (and only one line) with this position. + \value OnlyOne There is only one toolbar or line. This is the default value + of the positionOfLine and positionWithinLine variables. + + \sa positionWithinLine, positionOfLine +*/ + +/*! + \enum QStyleOptionToolBar::ToolBarFeature + + This enum is used to describe whether a toolbar is movable or not. + + \value None The toolbar cannot be moved. The default value. + \value Movable The toolbar is movable, and a handle will appear when + holding the cursor over the toolbar's boundary. + + \sa features, QToolBar::isMovable() +*/ + +/*! + \variable QStyleOptionToolBar::positionOfLine + + This variable holds the position of the toolbar line. + + The default value is QStyleOptionToolBar::OnlyOne. +*/ + +/*! + \variable QStyleOptionToolBar::positionWithinLine + + This variable holds the position of the toolbar within a line. + + The default value is QStyleOptionToolBar::OnlyOne. +*/ + +/*! + \variable QStyleOptionToolBar::toolBarArea + + This variable holds the location for drawing the toolbar. + + The default value is Qt::TopToolBarArea. + + \sa Qt::ToolBarArea +*/ + +/*! + \variable QStyleOptionToolBar::features + + This variable holds whether the toolbar is movable or not. + + The default value is \l None. +*/ + +/*! + \variable QStyleOptionToolBar::lineWidth + + This variable holds the line width for drawing the toolbar. + + The default value is 0. +*/ + +/*! + \variable QStyleOptionToolBar::midLineWidth + + This variable holds the mid-line width for drawing the toolbar. + + The default value is 0. +*/ + +/*! + \enum QStyleOptionToolBar::StyleOptionType + + This enum is used to hold information about the type of the style + option, and is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ToolBar} for + this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionToolBar::StyleOptionVersion + + This enum is used to hold information about the version of the + style option, and is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +#endif + +#ifndef QT_NO_TABBAR +/*! + \class QStyleOptionTab + \brief The QStyleOptionTab class is used to describe the + parameters for drawing a tab bar. + + The QStyleOptionTab class is used for drawing several built-in Qt + widgets including \l QTabBar and the panel for \l QTabWidget. Note + that to describe the parameters necessary for drawing a frame in + Qt 4.1 or above, you must use the QStyleOptionFrameV2 subclass. + + An instance of the QStyleOptiontabV2 class has + \l{QStyleOption::type} {type} \l SO_Tab and + \l{QStyleOption::version} {version} 1. The type is used internally + by QStyleOption, its subclasses, and qstyleoption_cast() to + determine the type of style option. In general you do not need to + worry about this unless you want to create your own QStyleOption + subclass and your own styles. The version is used by QStyleOption + subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not + need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionTab and QStyleOptionTabV2. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionTabV2, QStyleOption +*/ + +/*! + Constructs a QStyleOptionTab object, initializing the members + variables to their default values. +*/ + +QStyleOptionTab::QStyleOptionTab() + : QStyleOption(QStyleOptionTab::Version, SO_Tab), + shape(QTabBar::RoundedNorth), + row(0), + position(Beginning), + selectedPosition(NotAdjacent), cornerWidgets(QStyleOptionTab::NoCornerWidgets) +{ +} + +/*! + \internal +*/ +QStyleOptionTab::QStyleOptionTab(int version) + : QStyleOption(version, SO_Tab), + shape(QTabBar::RoundedNorth), + row(0), + position(Beginning), + selectedPosition(NotAdjacent), cornerWidgets(QStyleOptionTab::NoCornerWidgets) +{ +} + +/*! + \fn QStyleOptionTab::QStyleOptionTab(const QStyleOptionTab &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionTab::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Tab} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionTab::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \enum QStyleOptionTab::TabPosition + + This enum describes the position of the tab. + + \value Beginning The tab is the first tab in the tab bar. + \value Middle The tab is neither the first nor the last tab in the tab bar. + \value End The tab is the last tab in the tab bar. + \value OnlyOneTab The tab is both the first and the last tab in the tab bar. + + \sa position +*/ + +/*! + \enum QStyleOptionTab::CornerWidget + + These flags indicate the corner widgets in a tab. + + \value NoCornerWidgets There are no corner widgets + \value LeftCornerWidget Left corner widget + \value RightCornerWidget Right corner widget + + \sa cornerWidgets +*/ + +/*! \enum QStyleOptionTab::SelectedPosition + + This enum describes the position of the selected tab. Some styles + need to draw a tab differently depending on whether or not it is + adjacent to the selected tab. + + \value NotAdjacent The tab is not adjacent to a selected tab (or is the selected tab). + \value NextIsSelected The next tab (typically the tab on the right) is selected. + \value PreviousIsSelected The previous tab (typically the tab on the left) is selected. + + \sa selectedPosition +*/ + +/*! + \variable QStyleOptionTab::selectedPosition + \brief the position of the selected tab in relation to this tab + + The default value is NotAdjacent, i.e. the tab is not adjacent to + a selected tab nor is it the selected tab. +*/ + +/*! + \variable QStyleOptionTab::cornerWidgets + \brief an OR combination of CornerWidget values indicating the + corner widgets of the tab bar + + The default value is NoCornerWidgets. + + \sa CornerWidget +*/ + + +/*! + \variable QStyleOptionTab::shape + \brief the tab shape used to draw the tab; by default + QTabBar::RoundedNorth + + \sa QTabBar::Shape +*/ + +/*! + \variable QStyleOptionTab::text + \brief the text of the tab + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionTab::icon + \brief the icon for the tab + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionTab::row + \brief which row the tab is currently in + + The default value is 0, indicating the front row. Currently this + property can only be 0. +*/ + +/*! + \variable QStyleOptionTab::position + \brief the position of the tab in the tab bar + + The default value is \l Beginning, i.e. the tab is the first tab + in the tab bar. +*/ + +/*! + \class QStyleOptionTabV2 + \brief The QStyleOptionTabV2 class is used to describe the + parameters necessary for drawing a tabs in Qt 4.1 or above. + + \since 4.1 + + An instance of the QStyleOptionTabV2 class has + \l{QStyleOption::type} {type} \l SO_Tab and + \l{QStyleOption::version} {version} 2. The type is used internally + by QStyleOption, its subclasses, and qstyleoption_cast() to + determine the type of style option. In general you do not need to + worry about this unless you want to create your own QStyleOption + subclass and your own styles. The version is used by QStyleOption + subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not + need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionTab and QStyleOptionTabV2. One way to achieve this is + to use the QStyleOptionTabV2 copy constructor. For example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 3 + + In the example above: If \c tabOption's version is 1, the extra + member (\l iconSize) will be set to an invalid size for \c tabV2. + If \c tabOption's version is 2, the constructor will simply copy + the \c tab's iconSize. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionTab, QStyleOption +*/ + +/*! + \enum QStyleOptionTabV2::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionTabV2::iconSize + \brief the size for the icons + + The default value is QSize(-1, -1), i.e. an invalid size; use + QStyle::pixelMetric() to find the default icon size for tab bars. + + \sa QTabBar::iconSize() +*/ + +/*! + Constructs a QStyleOptionTabV2. +*/ +QStyleOptionTabV2::QStyleOptionTabV2() + : QStyleOptionTab(Version) +{ +} + +/*! + \internal +*/ +QStyleOptionTabV2::QStyleOptionTabV2(int version) + : QStyleOptionTab(version) +{ +} + +/*! + \fn QStyleOptionTabV2::QStyleOptionTabV2(const QStyleOptionTabV2 &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + Constructs a QStyleOptionTabV2 copy of the \a other style option + which can be either of the QStyleOptionTabV2 or QStyleOptionTab + types. + + If the other style option's version is 1, the new style option's + \c iconSize is set to an invalid value. If its version is 2, its + \c iconSize value is simply copied to the new style option. +*/ +QStyleOptionTabV2::QStyleOptionTabV2(const QStyleOptionTab &other) + : QStyleOptionTab(Version) +{ + if (const QStyleOptionTabV2 *tab = qstyleoption_cast<const QStyleOptionTabV2 *>(&other)) { + *this = *tab; + } else { + *((QStyleOptionTab *)this) = other; + version = Version; + } +} + +/*! + Assigns the \a other style option to this QStyleOptionTabV2. The + \a other style option can be either of the QStyleOptionTabV2 or + QStyleOptionTab types. + + If the other style option's version is 1, this style option's \c + iconSize is set to an invalid size. If its version is 2, its \c + iconSize value is simply copied to this style option. +*/ +QStyleOptionTabV2 &QStyleOptionTabV2::operator=(const QStyleOptionTab &other) +{ + QStyleOptionTab::operator=(other); + + if (const QStyleOptionTabV2 *tab = qstyleoption_cast<const QStyleOptionTabV2 *>(&other)) + iconSize = tab->iconSize; + else + iconSize = QSize(); + return *this; +} + +/*! + \class QStyleOptionTabV3 + \brief The QStyleOptionTabV3 class is used to describe the + parameters necessary for drawing a tabs in Qt 4.5 or above. + + \since 4.5 + + An instance of the QStyleOptionTabV3 class has + \l{QStyleOption::type} {type} \l SO_Tab and + \l{QStyleOption::version} {version} 3. The type is used internally + by QStyleOption, its subclasses, and qstyleoption_cast() to + determine the type of style option. In general you do not need to + worry about this unless you want to create your own QStyleOption + subclass and your own styles. The version is used by QStyleOption + subclasses to implement extensions without breaking + compatibility. If you use qstyleoption_cast(), you normally do not + need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionTab, QStyleOptionTabV2 and QStyleOptionTabV3. + One way to achieve this is to use the QStyleOptionTabV3 copy + constructor. For example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 3 + + In the example above: If \c tabOption's version is 1, the extra + member (\l{QStyleOptionTabV2::iconSize}{iconSize}) will be set to + an invalid size for \c tabV2. If \c tabOption's version is 2, the + constructor will simply copy the \c tab's iconSize. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionTab, QStyleOption +*/ + +/*! + \enum QStyleOptionTabV3::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 3 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionTabV3::documentMode + \brief whether the tabbar is in document mode. + + The default value is false; +*/ + +/*! + \variable QStyleOptionTabV3::leftButtonSize + \brief the size for the left widget on the tab. + + The default value is QSize(-1, -1), i.e. an invalid size; +*/ + +/*! + \variable QStyleOptionTabV3::rightButtonSize + \brief the size for the right widget on the tab. + + The default value is QSize(-1, -1), i.e. an invalid size; +*/ + +/*! + Constructs a QStyleOptionTabV3. +*/ + +QStyleOptionTabV3::QStyleOptionTabV3() + : QStyleOptionTabV2(Version) + , documentMode(false) +{ +} + +/*! + \internal +*/ +QStyleOptionTabV3::QStyleOptionTabV3(int version) + : QStyleOptionTabV2(version) +{ +} + +/*! + \fn QStyleOptionTabV3::QStyleOptionTabV3(const QStyleOptionTabV3 &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \fn QStyleOptionTabV3::QStyleOptionTabV3(const QStyleOptionTabV2 &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + Constructs a QStyleOptionTabV3 copy of the \a other style option + which can be either of the QStyleOptionTabV3, QStyleOptionTabV2 + or QStyleOptionTab types. + + If the other style option's version is 1 or 2, the new style option's + \c leftButtonSize and \c rightButtonSize is set to an invalid value. If + its version is 3, its \c leftButtonSize and \c rightButtonSize values + are simply copied to the new style option. +*/ +QStyleOptionTabV3::QStyleOptionTabV3(const QStyleOptionTab &other) + : QStyleOptionTabV2(Version) +{ + if (const QStyleOptionTabV3 *tab = qstyleoption_cast<const QStyleOptionTabV3 *>(&other)) { + *this = *tab; + } else { + *((QStyleOptionTabV2 *)this) = other; + version = Version; + } +} + +/*! + Assigns the \a other style option to this QStyleOptionTabV3. The + \a other style option can be either of the QStyleOptionTabV3, + QStyleOptionTabV2 or QStyleOptionTab types. + + If the other style option's version is 1 or 2, the new style option's + \c leftButtonSize and \c rightButtonSize is set to an invalid value. If + its version is 3, its \c leftButtonSize and \c rightButtonSize values + are simply copied to the new style option. +*/ +QStyleOptionTabV3 &QStyleOptionTabV3::operator=(const QStyleOptionTab &other) +{ + QStyleOptionTabV2::operator=(other); + + if (const QStyleOptionTabV3 *tab = qstyleoption_cast<const QStyleOptionTabV3 *>(&other)) { + leftButtonSize = tab->leftButtonSize; + rightButtonSize = tab->rightButtonSize; + } else { + leftButtonSize = QSize(); + rightButtonSize = QSize(); + documentMode = false; + } + return *this; +} + +#endif // QT_NO_TABBAR + +/*! + \class QStyleOptionProgressBar + \brief The QStyleOptionProgressBar class is used to describe the + parameters necessary for drawing a progress bar. + + Since Qt 4.1, Qt uses the QStyleOptionProgressBarV2 subclass for + drawing QProgressBar. + + An instance of the QStyleOptionProgressBar class has type + SO_ProgressBar and version 1. + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionProgressBar and QStyleOptionProgressBarV2. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionProgressBarV2, QStyleOption +*/ + +/*! + Constructs a QStyleOptionProgressBar, initializing the members + variables to their default values. +*/ + +QStyleOptionProgressBar::QStyleOptionProgressBar() + : QStyleOption(QStyleOptionProgressBar::Version, SO_ProgressBar), + minimum(0), maximum(0), progress(0), textAlignment(Qt::AlignLeft), textVisible(false) +{ +} + +/*! + \internal +*/ +QStyleOptionProgressBar::QStyleOptionProgressBar(int version) + : QStyleOption(version, SO_ProgressBar), + minimum(0), maximum(0), progress(0), textAlignment(Qt::AlignLeft), textVisible(false) +{ +} + +/*! + \fn QStyleOptionProgressBar::QStyleOptionProgressBar(const QStyleOptionProgressBar &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionProgressBar::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ProgressBar} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionProgressBar::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionProgressBar::minimum + \brief the minimum value for the progress bar + + This is the minimum value in the progress bar. The default value + is 0. + + \sa QProgressBar::minimum +*/ + +/*! + \variable QStyleOptionProgressBar::maximum + \brief the maximum value for the progress bar + + This is the maximum value in the progress bar. The default value + is 0. + + \sa QProgressBar::maximum +*/ + +/*! + \variable QStyleOptionProgressBar::text + \brief the text for the progress bar + + The progress bar text is usually just the progress expressed as a + string. An empty string indicates that the progress bar has not + started yet. The default value is an empty string. + + \sa QProgressBar::text +*/ + +/*! + \variable QStyleOptionProgressBar::textVisible + \brief a flag indicating whether or not text is visible + + If this flag is true then the text is visible. Otherwise, the text + is not visible. The default value is false. + + \sa QProgressBar::textVisible +*/ + + +/*! + \variable QStyleOptionProgressBar::textAlignment + \brief the text alignment for the text in the QProgressBar + + This can be used as a guide on where the text should be in the + progress bar. The default value is Qt::AlignLeft. +*/ + +/*! + \variable QStyleOptionProgressBar::progress + \brief the current progress for the progress bar + + The current progress. A value of QStyleOptionProgressBar::minimum + - 1 indicates that the progress hasn't started yet. The default + value is 0. + + \sa QProgressBar::value +*/ + +/*! + \class QStyleOptionProgressBarV2 + \brief The QStyleOptionProgressBarV2 class is used to describe the + parameters necessary for drawing a progress bar in Qt 4.1 or above. + + \since 4.1 + + An instance of this class has \l{QStyleOption::type} {type} + SO_ProgressBar and \l{QStyleOption::version} {version} 2. + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionProgressBar and QStyleOptionProgressBarV2. One way + to achieve this is to use the QStyleOptionProgressBarV2 copy + constructor. For example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 2 + + In the example above: If the \c progressBarOption's version is 1, + the extra members (\l orientation, \l invertedAppearance, and \l + bottomToTop) are set to default values for \c progressBarV2. If + the \c progressBarOption's version is 2, the constructor will + simply copy the extra members to progressBarV2. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionProgressBar, QStyleOption +*/ + +/*! + Constructs a QStyleOptionProgressBarV2, initializing he members + variables to their default values. +*/ + +QStyleOptionProgressBarV2::QStyleOptionProgressBarV2() + : QStyleOptionProgressBar(2), + orientation(Qt::Horizontal), invertedAppearance(false), bottomToTop(false) +{ +} + +/*! + \internal +*/ +QStyleOptionProgressBarV2::QStyleOptionProgressBarV2(int version) + : QStyleOptionProgressBar(version), + orientation(Qt::Horizontal), invertedAppearance(false), bottomToTop(false) +{ +} + +/*! + Constructs a copy of the \a other style option which can be either + of the QStyleOptionProgressBar and QStyleOptionProgressBarV2 + types. + + If the \a{other} style option's version is 1, the extra members (\l + orientation, \l invertedAppearance, and \l bottomToTop) are set + to default values for the new style option. If \a{other}'s version + is 2, the extra members are simply copied. + + \sa version +*/ +QStyleOptionProgressBarV2::QStyleOptionProgressBarV2(const QStyleOptionProgressBar &other) + : QStyleOptionProgressBar(2), orientation(Qt::Horizontal), invertedAppearance(false), bottomToTop(false) +{ + const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(&other); + if (pb2) + *this = *pb2; + else + *((QStyleOptionProgressBar *)this) = other; +} + +/*! + Constructs a copy of the \a other style option. +*/ +QStyleOptionProgressBarV2::QStyleOptionProgressBarV2(const QStyleOptionProgressBarV2 &other) + : QStyleOptionProgressBar(2), orientation(Qt::Horizontal), invertedAppearance(false), bottomToTop(false) +{ + *this = other; +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionProgressBarV2 + or QStyleOptionProgressBar types. + + If the \a{other} style option's version is 1, the extra members + (\l orientation, \l invertedAppearance, and \l bottomToTop) are + set to default values for this style option. If \a{other}'s + version is 2, the extra members are simply copied to this style + option. +*/ +QStyleOptionProgressBarV2 &QStyleOptionProgressBarV2::operator=(const QStyleOptionProgressBar &other) +{ + QStyleOptionProgressBar::operator=(other); + + const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(&other); + orientation = pb2 ? pb2->orientation : Qt::Horizontal; + invertedAppearance = pb2 ? pb2->invertedAppearance : false; + bottomToTop = pb2 ? pb2->bottomToTop : false; + return *this; +} + +/*! + \variable QStyleOptionProgressBarV2::orientation + \brief the progress bar's orientation (horizontal or vertical); + the default orentation is Qt::Horizontal + + \sa QProgressBar::orientation +*/ + +/*! + \variable QStyleOptionProgressBarV2::invertedAppearance + \brief whether the progress bar's appearance is inverted + + The default value is false. + + \sa QProgressBar::invertedAppearance +*/ + +/*! + \variable QStyleOptionProgressBarV2::bottomToTop + \brief whether the text reads from bottom to top when the progress + bar is vertical + + The default value is false. + + \sa QProgressBar::textDirection +*/ + +/*! + \enum QStyleOptionProgressBarV2::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ProgressBar} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionProgressBarV2::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + + +/*! + \class QStyleOptionMenuItem + \brief The QStyleOptionMenuItem class is used to describe the + parameter necessary for drawing a menu item. + + QStyleOptionMenuItem contains all the information that QStyle + functions need to draw the menu items from \l QMenu. It is also + used for drawing other menu-related widgets. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionMenuItem, initializing the members + variables to their default values. +*/ + +QStyleOptionMenuItem::QStyleOptionMenuItem() + : QStyleOption(QStyleOptionMenuItem::Version, SO_MenuItem), menuItemType(Normal), + checkType(NotCheckable), checked(false), menuHasCheckableItems(true), maxIconWidth(0), tabWidth(0) +{ +} + +/*! + \internal +*/ +QStyleOptionMenuItem::QStyleOptionMenuItem(int version) + : QStyleOption(version, SO_MenuItem), menuItemType(Normal), + checkType(NotCheckable), checked(false), menuHasCheckableItems(true), maxIconWidth(0), tabWidth(0) +{ +} + +/*! + \fn QStyleOptionMenuItem::QStyleOptionMenuItem(const QStyleOptionMenuItem &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionMenuItem::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_MenuItem} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionMenuItem::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \enum QStyleOptionMenuItem::MenuItemType + + This enum indicates the type of menu item that the structure describes. + + \value Normal A normal menu item. + \value DefaultItem A menu item that is the default action as specified with \l QMenu::defaultAction(). + \value Separator A menu separator. + \value SubMenu Indicates the menu item points to a sub-menu. + \value Scroller A popup menu scroller (currently only used on Mac OS X). + \value TearOff A tear-off handle for the menu. + \value Margin The margin of the menu. + \value EmptyArea The empty area of the menu. + + \sa menuItemType +*/ + +/*! + \enum QStyleOptionMenuItem::CheckType + + This enum is used to indicate whether or not a check mark should be + drawn for the item, or even if it should be drawn at all. + + \value NotCheckable The item is not checkable. + \value Exclusive The item is an exclusive check item (like a radio button). + \value NonExclusive The item is a non-exclusive check item (like a check box). + + \sa checkType, QAction::checkable, QAction::checked, QActionGroup::exclusive +*/ + +/*! + \variable QStyleOptionMenuItem::menuItemType + \brief the type of menu item + + The default value is \l Normal. + + \sa MenuItemType +*/ + +/*! + \variable QStyleOptionMenuItem::checkType + \brief the type of checkmark of the menu item + + The default value is \l NotCheckable. + + \sa CheckType +*/ + +/*! + \variable QStyleOptionMenuItem::checked + \brief whether the menu item is checked or not + + The default value is false. +*/ + +/*! + \variable QStyleOptionMenuItem::menuHasCheckableItems + \brief whether the menu as a whole has checkable items or not + + The default value is true. + + If this option is set to false, then the menu has no checkable + items. This makes it possible for GUI styles to save some + horizontal space that would normally be used for the check column. +*/ + +/*! + \variable QStyleOptionMenuItem::menuRect + \brief the rectangle for the entire menu + + The default value is a null rectangle, i.e. a rectangle with both + the width and the height set to 0. +*/ + +/*! + \variable QStyleOptionMenuItem::text + \brief the text for the menu item + + Note that the text format is something like this "Menu + text\bold{\\t}Shortcut". + + If the menu item doesn't have a shortcut, it will just contain the + menu item's text. The default value is an empty string. +*/ + +/*! + \variable QStyleOptionMenuItem::icon + \brief the icon for the menu item + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionMenuItem::maxIconWidth + \brief the maximum icon width for the icon in the menu item + + This can be used for drawing the icon into the correct place or + properly aligning items. The variable must be set regardless of + whether or not the menu item has an icon. The default value is 0. +*/ + +/*! + \variable QStyleOptionMenuItem::tabWidth + \brief the tab width for the menu item + + The tab width is the distance between the text of the menu item + and the shortcut. The default value is 0. +*/ + + +/*! + \variable QStyleOptionMenuItem::font + \brief the font used for the menu item text + + This is the font that should be used for drawing the menu text + minus the shortcut. The shortcut is usually drawn using the + painter's font. By default, the application's default font is + used. +*/ + +/*! + \class QStyleOptionComplex + \brief The QStyleOptionComplex class is used to hold parameters that are + common to all complex controls. + + This class is not used on its own. Instead it is used to derive + other complex control options, for example QStyleOptionSlider and + QStyleOptionSpinBox. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionComplex of the specified \a type and \a + version, initializing the member variables to their default + values. This constructor is usually called by subclasses. +*/ + +QStyleOptionComplex::QStyleOptionComplex(int version, int type) + : QStyleOption(version, type), subControls(QStyle::SC_All), activeSubControls(QStyle::SC_None) +{ +} + +/*! + \fn QStyleOptionComplex::QStyleOptionComplex(const QStyleOptionComplex &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionComplex::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Complex} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionComplex::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionComplex::subControls + + This variable holds a bitwise OR of the \l{QStyle::SubControl} + {sub-controls} to be drawn for the complex control. + + The default value is QStyle::SC_All. + + \sa QStyle::SubControl +*/ + +/*! + \variable QStyleOptionComplex::activeSubControls + + This variable holds a bitwise OR of the \l{QStyle::SubControl} + {sub-controls} that are active for the complex control. + + The default value is QStyle::SC_None. + + \sa QStyle::SubControl +*/ + +#ifndef QT_NO_SLIDER +/*! + \class QStyleOptionSlider + \brief The QStyleOptionSlider class is used to describe the + parameters needed for drawing a slider. + + QStyleOptionSlider contains all the information that QStyle + functions need to draw QSlider and QScrollBar. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionComplex, QSlider, QScrollBar +*/ + +/*! + Constructs a QStyleOptionSlider, initializing the members + variables to their default values. +*/ + +QStyleOptionSlider::QStyleOptionSlider() + : QStyleOptionComplex(Version, SO_Slider), orientation(Qt::Horizontal), minimum(0), maximum(0), + tickPosition(QSlider::NoTicks), tickInterval(0), upsideDown(false), + sliderPosition(0), sliderValue(0), singleStep(0), pageStep(0), notchTarget(0.0), + dialWrapping(false) +{ +} + +/*! + \internal +*/ +QStyleOptionSlider::QStyleOptionSlider(int version) + : QStyleOptionComplex(version, SO_Slider), orientation(Qt::Horizontal), minimum(0), maximum(0), + tickPosition(QSlider::NoTicks), tickInterval(0), upsideDown(false), + sliderPosition(0), sliderValue(0), singleStep(0), pageStep(0), notchTarget(0.0), + dialWrapping(false) +{ +} + +/*! + \fn QStyleOptionSlider::QStyleOptionSlider(const QStyleOptionSlider &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionSlider::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Slider} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionSlider::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionSlider::orientation + \brief the slider's orientation (horizontal or vertical) + + The default orientation is Qt::Horizontal. + + \sa Qt::Orientation +*/ + +/*! + \variable QStyleOptionSlider::minimum + \brief the minimum value for the slider + + The default value is 0. +*/ + +/*! + \variable QStyleOptionSlider::maximum + \brief the maximum value for the slider + + The default value is 0. +*/ + +/*! + \variable QStyleOptionSlider::tickPosition + \brief the position of the slider's tick marks, if any + + The default value is QSlider::NoTicks. + + \sa QSlider::TickPosition +*/ + +/*! + \variable QStyleOptionSlider::tickInterval + \brief the interval that should be drawn between tick marks + + The default value is 0. +*/ + +/*! + \variable QStyleOptionSlider::notchTarget + \brief the number of pixel between notches + + The default value is 0.0. + + \sa QDial::notchTarget() +*/ + +/*! + \variable QStyleOptionSlider::dialWrapping + \brief whether the dial should wrap or not + + The default value is false, i.e. the dial is not wrapped. + + \sa QDial::wrapping() +*/ + +/*! + \variable QStyleOptionSlider::upsideDown + \brief the slider control orientation + + Normally a slider increases as it moves up or to the right; + upsideDown indicates that it should do the opposite (increase as + it moves down or to the left). The default value is false, + i.e. the slider increases as it moves up or to the right. + + \sa QStyle::sliderPositionFromValue(), + QStyle::sliderValueFromPosition(), + QAbstractSlider::invertedAppearance +*/ + +/*! + \variable QStyleOptionSlider::sliderPosition + \brief the position of the slider handle + + If the slider has active feedback (i.e., + QAbstractSlider::tracking is true), this value will be the same as + \l sliderValue. Otherwise, it will have the current position of + the handle. The default value is 0. + + \sa QAbstractSlider::tracking, sliderValue +*/ + +/*! + \variable QStyleOptionSlider::sliderValue + \brief the value of the slider + + If the slider has active feedback (i.e., + QAbstractSlider::tracking is true), this value will be the same + as \l sliderPosition. Otherwise, it will have the value the + slider had before the mouse was pressed. + + The default value is 0. + + \sa QAbstractSlider::tracking sliderPosition +*/ + +/*! + \variable QStyleOptionSlider::singleStep + \brief the size of the single step of the slider + + The default value is 0. + + \sa QAbstractSlider::singleStep +*/ + +/*! + \variable QStyleOptionSlider::pageStep + \brief the size of the page step of the slider + + The default value is 0. + + \sa QAbstractSlider::pageStep +*/ +#endif // QT_NO_SLIDER + +#ifndef QT_NO_SPINBOX +/*! + \class QStyleOptionSpinBox + \brief The QStyleOptionSpinBox class is used to describe the + parameters necessary for drawing a spin box. + + QStyleOptionSpinBox contains all the information that QStyle + functions need to draw QSpinBox and QDateTimeEdit. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex +*/ + +/*! + Constructs a QStyleOptionSpinBox, initializing the members + variables to their default values. +*/ + +QStyleOptionSpinBox::QStyleOptionSpinBox() + : QStyleOptionComplex(Version, SO_SpinBox), buttonSymbols(QAbstractSpinBox::UpDownArrows), + stepEnabled(QAbstractSpinBox::StepNone), frame(false) +{ +} + +/*! + \internal +*/ +QStyleOptionSpinBox::QStyleOptionSpinBox(int version) + : QStyleOptionComplex(version, SO_SpinBox), buttonSymbols(QAbstractSpinBox::UpDownArrows), + stepEnabled(QAbstractSpinBox::StepNone), frame(false) +{ +} + +/*! + \fn QStyleOptionSpinBox::QStyleOptionSpinBox(const QStyleOptionSpinBox &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionSpinBox::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_SpinBox} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionSpinBox::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionSpinBox::buttonSymbols + \brief the type of button symbols to draw for the spin box + + The default value is QAbstractSpinBox::UpDownArrows specufying + little arrows in the classic style. + + \sa QAbstractSpinBox::ButtonSymbols +*/ + +/*! + \variable QStyleOptionSpinBox::stepEnabled + \brief which buttons of the spin box that are enabled + + The default value is QAbstractSpinBox::StepNone. + + \sa QAbstractSpinBox::StepEnabled +*/ + +/*! + \variable QStyleOptionSpinBox::frame + \brief whether the spin box has a frame + + The default value is false, i.e. the spin box has no frame. +*/ +#endif // QT_NO_SPINBOX + +/*! + \class QStyleOptionQ3ListViewItem + \brief The QStyleOptionQ3ListViewItem class is used to describe an + item drawn in a Q3ListView. + + This class is used for drawing the compatibility Q3ListView's + items. \bold {It is not recommended for new classes}. + + QStyleOptionQ3ListViewItem contains all the information that + QStyle functions need to draw the Q3ListView items. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionQ3ListView, Q3ListViewItem +*/ + +/*! + \enum QStyleOptionQ3ListViewItem::Q3ListViewItemFeature + + This enum describes the features a list view item can have. + + \value None A standard item. + \value Expandable The item has children that can be shown. + \value MultiLine The item is more than one line tall. + \value Visible The item is visible. + \value ParentControl The item's parent is a type of item control (Q3CheckListItem::Controller). + + \sa features, Q3ListViewItem::isVisible(), Q3ListViewItem::multiLinesEnabled(), + Q3ListViewItem::isExpandable() +*/ + +/*! + Constructs a QStyleOptionQ3ListViewItem, initializing the members + variables to their default values. +*/ + +QStyleOptionQ3ListViewItem::QStyleOptionQ3ListViewItem() + : QStyleOption(Version, SO_Q3ListViewItem), features(None), height(0), totalHeight(0), + itemY(0), childCount(0) +{ +} + +/*! + \internal +*/ +QStyleOptionQ3ListViewItem::QStyleOptionQ3ListViewItem(int version) + : QStyleOption(version, SO_Q3ListViewItem), features(None), height(0), totalHeight(0), + itemY(0), childCount(0) +{ +} + +/*! + \fn QStyleOptionQ3ListViewItem::QStyleOptionQ3ListViewItem(const QStyleOptionQ3ListViewItem &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionQ3ListViewItem::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Q3ListViewItem} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionQ3ListViewItem::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionQ3ListViewItem::features + \brief the features for this item + + This variable is a bitwise OR of the features of the item. The deafult value is \l None. + + \sa Q3ListViewItemFeature +*/ + +/*! + \variable QStyleOptionQ3ListViewItem::height + \brief the height of the item + + This doesn't include the height of the item's children. The default height is 0. + + \sa Q3ListViewItem::height() +*/ + +/*! + \variable QStyleOptionQ3ListViewItem::totalHeight + \brief the total height of the item, including its children + + The default total height is 0. + + \sa Q3ListViewItem::totalHeight() +*/ + +/*! + \variable QStyleOptionQ3ListViewItem::itemY + \brief the Y-coordinate for the item + + The default value is 0. + + \sa Q3ListViewItem::itemPos() +*/ + +/*! + \variable QStyleOptionQ3ListViewItem::childCount + \brief the number of children the item has +*/ + +/*! + \class QStyleOptionQ3ListView + \brief The QStyleOptionQ3ListView class is used to describe the + parameters for drawing a Q3ListView. + + This class is used for drawing the compatibility Q3ListView. \bold + {It is not recommended for new classes}. + + QStyleOptionQ3ListView contains all the information that QStyle + functions need to draw Q3ListView. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOptionComplex, Q3ListView, QStyleOptionQ3ListViewItem +*/ + +/*! + Creates a QStyleOptionQ3ListView, initializing the members + variables to their default values. +*/ + +QStyleOptionQ3ListView::QStyleOptionQ3ListView() + : QStyleOptionComplex(Version, SO_Q3ListView), viewportBGRole(QPalette::Base), + sortColumn(0), itemMargin(0), treeStepSize(0), rootIsDecorated(false) +{ +} + +/*! + \internal +*/ +QStyleOptionQ3ListView::QStyleOptionQ3ListView(int version) + : QStyleOptionComplex(version, SO_Q3ListView), viewportBGRole(QPalette::Base), + sortColumn(0), itemMargin(0), treeStepSize(0), rootIsDecorated(false) +{ +} + +/*! + \fn QStyleOptionQ3ListView::QStyleOptionQ3ListView(const QStyleOptionQ3ListView &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionQ3ListView::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Q3ListView} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionQ3ListView::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionQ3ListView::items + \brief a list of items in the Q3ListView + + This is a list of \l {QStyleOptionQ3ListViewItem}s. The first item + can be used for most of the calculation that are needed for + drawing a list view. Any additional items are the children of + this first item, which may be used for additional information. + + \sa QStyleOptionQ3ListViewItem +*/ + +/*! + \variable QStyleOptionQ3ListView::viewportPalette + \brief the palette of Q3ListView's viewport + + By default, the application's default palette is used. +*/ + +/*! + \variable QStyleOptionQ3ListView::viewportBGRole + \brief the background role of Q3ListView's viewport + + The default value is QPalette::Base. + + \sa QWidget::backgroundRole() +*/ + +/*! + \variable QStyleOptionQ3ListView::sortColumn + \brief the sort column of the list view + + The default value is 0. + + \sa Q3ListView::sortColumn() +*/ + +/*! + \variable QStyleOptionQ3ListView::itemMargin + \brief the margin for items in the list view + + The default value is 0. + + \sa Q3ListView::itemMargin() +*/ + +/*! + \variable QStyleOptionQ3ListView::treeStepSize + \brief the number of pixel to offset children items from their + parents + + The default value is 0. + + \sa Q3ListView::treeStepSize() +*/ + +/*! + \variable QStyleOptionQ3ListView::rootIsDecorated + \brief whether root items are decorated + + The default value is false. + + \sa Q3ListView::rootIsDecorated() +*/ + +/*! + \class QStyleOptionQ3DockWindow + \brief The QStyleOptionQ3DockWindow class is used to describe the + parameters for drawing various parts of a Q3DockWindow. + + This class is used for drawing the old Q3DockWindow and its + parts. \bold {It is not recommended for new classes}. + + QStyleOptionQ3DockWindow contains all the information that QStyle + functions need to draw Q3DockWindow and its parts. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, Q3DockWindow +*/ + +/*! + Constructs a QStyleOptionQ3DockWindow, initializing the member + variables to their default values. +*/ + +QStyleOptionQ3DockWindow::QStyleOptionQ3DockWindow() + : QStyleOption(Version, SO_Q3DockWindow), docked(false), closeEnabled(false) +{ +} + +/*! + \internal +*/ +QStyleOptionQ3DockWindow::QStyleOptionQ3DockWindow(int version) + : QStyleOption(version, SO_Q3DockWindow), docked(false), closeEnabled(false) +{ +} + +/*! + \fn QStyleOptionQ3DockWindow::QStyleOptionQ3DockWindow(const QStyleOptionQ3DockWindow &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionQ3DockWindow::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_Q3DockWindow} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionQ3DockWindow::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionQ3DockWindow::docked + \brief whether the dock window is currently docked + + The default value is false. +*/ + +/*! + \variable QStyleOptionQ3DockWindow::closeEnabled + \brief whether the dock window has a close button + + The default value is false. +*/ + +/*! + \class QStyleOptionDockWidget + \brief The QStyleOptionDockWidget class is used to describe the + parameters for drawing a dock widget. + + QStyleOptionDockWidget contains all the information that QStyle + functions need to draw graphical elements like QDockWidget. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption +*/ + +/*! + Constructs a QStyleOptionDockWidget, initializing the member + variables to their default values. +*/ + +QStyleOptionDockWidget::QStyleOptionDockWidget() + : QStyleOption(Version, SO_DockWidget), closable(false), + movable(false), floatable(false) +{ +} + +/*! + \internal +*/ +QStyleOptionDockWidget::QStyleOptionDockWidget(int version) + : QStyleOption(version, SO_DockWidget), closable(false), + movable(false), floatable(false) +{ +} + +QStyleOptionDockWidgetV2::QStyleOptionDockWidgetV2() + : QStyleOptionDockWidget(Version), verticalTitleBar(false) +{ +} + +QStyleOptionDockWidgetV2::QStyleOptionDockWidgetV2( + const QStyleOptionDockWidget &other) + : QStyleOptionDockWidget(Version) +{ + (void)QStyleOptionDockWidgetV2::operator=(other); +} + +QStyleOptionDockWidgetV2 &QStyleOptionDockWidgetV2::operator = ( + const QStyleOptionDockWidget &other) +{ + QStyleOptionDockWidget::operator=(other); + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(&other); + verticalTitleBar = v2 ? v2->verticalTitleBar : false; + return *this; +} + +QStyleOptionDockWidgetV2::QStyleOptionDockWidgetV2(int version) + : QStyleOptionDockWidget(version), verticalTitleBar(false) +{ +} + +/*! + \fn QStyleOptionDockWidget::QStyleOptionDockWidget(const QStyleOptionDockWidget &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionDockWidget::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_DockWidget} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionDockWidget::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionDockWidget::title + \brief the title of the dock window + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionDockWidget::closable + \brief whether the dock window is closable + + The default value is true. +*/ + +/*! + \variable QStyleOptionDockWidget::movable + \brief whether the dock window is movable + + The default value is false. +*/ + +/*! + \variable QStyleOptionDockWidget::floatable + \brief whether the dock window is floatable + + The default value is true. +*/ + +/*! + \class QStyleOptionToolButton + \brief The QStyleOptionToolButton class is used to describe the + parameters for drawing a tool button. + + QStyleOptionToolButton contains all the information that QStyle + functions need to draw QToolButton. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex, QStyleOptionButton +*/ + +/*! + \enum QStyleOptionToolButton::ToolButtonFeature + Describes the various features that a tool button can have. + + \value None A normal tool button. + \value Arrow The tool button is an arrow. + \value Menu The tool button has a menu. + \value PopupDelay There is a delay to showing the menu. + \value HasMenu The button has a popup menu. + \value MenuButtonPopup The button should display an arrow to + indicate that a menu is present. + + \sa features, QToolButton::toolButtonStyle(), QToolButton::popupMode() +*/ + +/*! + Constructs a QStyleOptionToolButton, initializing the members + variables to their default values. +*/ + +QStyleOptionToolButton::QStyleOptionToolButton() + : QStyleOptionComplex(Version, SO_ToolButton), features(None), arrowType(Qt::DownArrow) + , toolButtonStyle(Qt::ToolButtonIconOnly) +{ +} + +/*! + \internal +*/ +QStyleOptionToolButton::QStyleOptionToolButton(int version) + : QStyleOptionComplex(version, SO_ToolButton), features(None), arrowType(Qt::DownArrow) + , toolButtonStyle(Qt::ToolButtonIconOnly) + +{ +} + +/*! + \fn QStyleOptionToolButton::QStyleOptionToolButton(const QStyleOptionToolButton &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionToolButton::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ToolButton} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionToolButton::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionToolButton::features + \brief an OR combination of the tool button's features + + The default value is \l None. + + \sa ToolButtonFeature +*/ + +/*! + \variable QStyleOptionToolButton::icon + \brief the icon for the tool button + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. + + \sa iconSize +*/ + +/*! + \variable QStyleOptionToolButton::iconSize + \brief the size of the icon for the tool button + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + +/*! + \variable QStyleOptionToolButton::text + \brief the text of the tool button + + This value is only used if toolButtonStyle is + Qt::ToolButtonTextUnderIcon, Qt::ToolButtonTextBesideIcon, or + Qt::ToolButtonTextOnly. The default value is an empty string. +*/ + +/*! + \variable QStyleOptionToolButton::arrowType + \brief the direction of the arrow for the tool button + + This value is only used if \l features includes \l Arrow. The + default value is Qt::DownArrow. +*/ + +/*! + \variable QStyleOptionToolButton::toolButtonStyle + \brief a Qt::ToolButtonStyle value describing the appearance of + the tool button + + The default value is Qt::ToolButtonIconOnly. + + \sa QToolButton::toolButtonStyle() +*/ + +/*! + \variable QStyleOptionToolButton::pos + \brief the position of the tool button + + The default value is a null point, i.e. (0, 0) +*/ + +/*! + \variable QStyleOptionToolButton::font + \brief the font that is used for the text + + This value is only used if toolButtonStyle is + Qt::ToolButtonTextUnderIcon, Qt::ToolButtonTextBesideIcon, or + Qt::ToolButtonTextOnly. By default, the application's default font + is used. +*/ + +/*! + \class QStyleOptionComboBox + \brief The QStyleOptionComboBox class is used to describe the + parameter for drawing a combobox. + + QStyleOptionButton contains all the information that QStyle + functions need to draw QComboBox. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex, QComboBox +*/ + +/*! + Creates a QStyleOptionComboBox, initializing the members variables + to their default values. +*/ + +QStyleOptionComboBox::QStyleOptionComboBox() + : QStyleOptionComplex(Version, SO_ComboBox), editable(false), frame(true) +{ +} + +/*! + \internal +*/ +QStyleOptionComboBox::QStyleOptionComboBox(int version) + : QStyleOptionComplex(version, SO_ComboBox), editable(false), frame(true) +{ +} + +/*! + \fn QStyleOptionComboBox::QStyleOptionComboBox(const QStyleOptionComboBox &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionComboBox::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ComboBox} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionComboBox::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionComboBox::editable + \brief whether or not the combobox is editable or not + + the default + value is false + + \sa QComboBox::isEditable() +*/ + + +/*! + \variable QStyleOptionComboBox::frame + \brief whether the combo box has a frame + + The default value is true. +*/ + +/*! + \variable QStyleOptionComboBox::currentText + \brief the text for the current item of the combo box + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionComboBox::currentIcon + \brief the icon for the current item of the combo box + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionComboBox::iconSize + \brief the icon size for the current item of the combo box + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + +/*! + \variable QStyleOptionComboBox::popupRect + \brief the popup rectangle for the combobox + + The default value is a null rectangle, i.e. a rectangle with both + the width and the height set to 0. + + This variable is currently unused. You can safely ignore it. + + \sa QStyle::SC_ComboBoxListBoxPopup +*/ + +/*! + \class QStyleOptionToolBox + \brief The QStyleOptionToolBox class is used to describe the + parameters needed for drawing a tool box. + + QStyleOptionToolBox contains all the information that QStyle + functions need to draw QToolBox. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QToolBox +*/ + +/*! + Creates a QStyleOptionToolBox, initializing the members variables + to their default values. +*/ + +QStyleOptionToolBox::QStyleOptionToolBox() + : QStyleOption(Version, SO_ToolBox) +{ +} + +/*! + \internal +*/ +QStyleOptionToolBox::QStyleOptionToolBox(int version) + : QStyleOption(version, SO_ToolBox) +{ +} + +/*! + \fn QStyleOptionToolBox::QStyleOptionToolBox(const QStyleOptionToolBox &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionToolBox::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ToolBox} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionToolBox::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionToolBox::icon + \brief the icon for the tool box tab + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionToolBox::text + \brief the text for the tool box tab + + The default value is an empty string. +*/ + +/*! + \class QStyleOptionToolBoxV2 + \brief The QStyleOptionToolBoxV2 class is used to describe the parameters necessary for drawing a frame in Qt 4.3 or above. + + \since 4.3 + QStyleOptionToolBoxV2 inherits QStyleOptionToolBox which is used for + drawing the tabs in a QToolBox. + + An instance of the QStyleOptionToolBoxV2 class has + \l{QStyleOption::type} {type} SO_ToolBox and + \l{QStyleOption::version} {version} 2. The type is used + internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. The + version is used by QStyleOption subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast(), + you normally do not need to check it. + + If you create your own QStyle subclass, you should handle both + QStyleOptionToolBox and QStyleOptionToolBoxV2. + + \sa QStyleOptionToolBox, QStyleOption +*/ + +/*! + Contsructs a QStyleOptionToolBoxV2 object. +*/ +QStyleOptionToolBoxV2::QStyleOptionToolBoxV2() + : QStyleOptionToolBox(Version), position(Beginning), selectedPosition(NotAdjacent) +{ +} + +/*! + \fn QStyleOptionToolBoxV2::QStyleOptionToolBoxV2(const QStyleOptionToolBoxV2 &other) + + Constructs a QStyleOptionToolBoxV2 copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionToolBoxV2::QStyleOptionToolBoxV2(int version) + : QStyleOptionToolBox(version), position(Beginning), selectedPosition(NotAdjacent) +{ +} + +/*! + Constructs a QStyleOptionToolBoxV2 copy of the \a other style option + which can be either of the QStyleOptionToolBoxV2 or + QStyleOptionToolBox types. + + If the \a other style option's version is 1, the new style + option's \l{QStyleOptionTab::position} {position} value is set to + \l QStyleOptionToolBoxV2::Beginning and \l selectedPosition is set + to \l QStyleOptionToolBoxV2::NotAdjacent. If its version is 2, the + \l{QStyleOptionTab::position} {position} selectedPosition values + are simply copied to the new style option. + + \sa version +*/ +QStyleOptionToolBoxV2::QStyleOptionToolBoxV2(const QStyleOptionToolBox &other) +{ + QStyleOptionToolBox::operator=(other); + + const QStyleOptionToolBoxV2 *f2 = qstyleoption_cast<const QStyleOptionToolBoxV2 *>(&other); + position = f2 ? f2->position : Beginning; + selectedPosition = f2 ? f2->selectedPosition : NotAdjacent; + version = Version; +} + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionToolBoxV2 or + QStyleOptionToolBox types. + + If the \a{other} style option's version is 1, this style option's + \l{QStyleOptionTab::position} {position} and \l selectedPosition + values are set to \l QStyleOptionToolBoxV2::Beginning and \l + QStyleOptionToolBoxV2::NotAdjacent respectively. If its + \l{QStyleOption::version} {version} is 2, these values are simply + copied to this style option. +*/ +QStyleOptionToolBoxV2 &QStyleOptionToolBoxV2::operator=(const QStyleOptionToolBox &other) +{ + QStyleOptionToolBox::operator=(other); + + const QStyleOptionToolBoxV2 *f2 = qstyleoption_cast<const QStyleOptionToolBoxV2 *>(&other); + position = f2 ? f2->position : Beginning; + selectedPosition = f2 ? f2->selectedPosition : NotAdjacent; + version = Version; + return *this; +} + + +/*! + \enum QStyleOptionToolBoxV2::SelectedPosition + + This enum describes the position of the selected tab. Some styles + need to draw a tab differently depending on whether or not it is + adjacent to the selected tab. + + \value NotAdjacent The tab is not adjacent to a selected tab (or is the selected tab). + \value NextIsSelected The next tab (typically the tab on the right) is selected. + \value PreviousIsSelected The previous tab (typically the tab on the left) is selected. + + \sa selectedPosition +*/ + +/*! + \enum QStyleOptionToolBoxV2::StyleOptionVersion + + This enum holds the version of this style option + + \value Version 2 +*/ + +/*! + \enum QStyleOptionToolBoxV2::TabPosition + + This enum describes tab positions relative to other tabs. + + \value Beginning The tab is the first (i.e., top-most) tab in + the toolbox. + \value Middle The tab is placed in the middle of the toolbox. + \value End The tab is placed at the bottom of the toolbox. + \value OnlyOneTab There is only one tab in the toolbox. +*/ + +/*! + \variable QStyleOptionToolBoxV2::selectedPosition + \brief the position of the selected tab in relation to this tab + + The default value is NotAdjacent, i.e. the tab is not adjacent to + a selected tab nor is it the selected tab. +*/ + +#ifndef QT_NO_RUBBERBAND +/*! + \class QStyleOptionRubberBand + \brief The QStyleOptionRubberBand class is used to describe the + parameters needed for drawing a rubber band. + + QStyleOptionRubberBand contains all the information that + QStyle functions need to draw QRubberBand. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QRubberBand +*/ + +/*! + Creates a QStyleOptionRubberBand, initializing the members + variables to their default values. +*/ + +QStyleOptionRubberBand::QStyleOptionRubberBand() + : QStyleOption(Version, SO_RubberBand), shape(QRubberBand::Line), opaque(false) +{ +} + +/*! + \internal +*/ +QStyleOptionRubberBand::QStyleOptionRubberBand(int version) + : QStyleOption(version, SO_RubberBand), shape(QRubberBand::Line), opaque(false) +{ +} + +/*! + \fn QStyleOptionRubberBand::QStyleOptionRubberBand(const QStyleOptionRubberBand &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionRubberBand::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_RubberBand} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionRubberBand::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionRubberBand::shape + \brief the shape of the rubber band + + The default shape is QRubberBand::Line. +*/ + +/*! + \variable QStyleOptionRubberBand::opaque + \brief whether the rubber band is required to be drawn in an opaque style + + The default value is true. +*/ +#endif // QT_NO_RUBBERBAND + +/*! + \class QStyleOptionTitleBar + \brief The QStyleOptionTitleBar class is used to describe the + parameters for drawing a title bar. + + QStyleOptionTitleBar contains all the information that QStyle + functions need to draw the title bar of a QMdiSubWindow. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex, QMdiSubWindow +*/ + +/*! + Constructs a QStyleOptionTitleBar, initializing the members + variables to their default values. +*/ + +QStyleOptionTitleBar::QStyleOptionTitleBar() + : QStyleOptionComplex(Version, SO_TitleBar), titleBarState(0), titleBarFlags(0) +{ +} + +/*! + \fn QStyleOptionTitleBar::QStyleOptionTitleBar(const QStyleOptionTitleBar &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionTitleBar::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_TitleBar} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionTitleBar::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \internal +*/ +QStyleOptionTitleBar::QStyleOptionTitleBar(int version) + : QStyleOptionComplex(version, SO_TitleBar), titleBarState(0), titleBarFlags(0) +{ +} + + +/*! + \variable QStyleOptionTitleBar::text + \brief the text of the title bar + + The default value is an empty string. +*/ + +/*! + \variable QStyleOptionTitleBar::icon + \brief the icon for the title bar + + The default value is an empty icon, i.e. an icon with neither a + pixmap nor a filename. +*/ + +/*! + \variable QStyleOptionTitleBar::titleBarState + \brief the state of the title bar + + This is basically the window state of the underlying widget. The + default value is 0. + + \sa QWidget::windowState() +*/ + +/*! + \variable QStyleOptionTitleBar::titleBarFlags + \brief the widget flags for the title bar + + The default value is Qt::Widget. + + \sa Qt::WindowFlags +*/ + +/*! + \class QStyleOptionViewItem + \brief The QStyleOptionViewItem class is used to describe the + parameters used to draw an item in a view widget. + + QStyleOptionViewItem contains all the information that QStyle + functions need to draw the items for Qt's model/view classes. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, {model-view-programming.html}{Model/View + Programming} +*/ + +/*! + \enum QStyleOptionViewItem::Position + + This enum describes the position of the item's decoration. + + \value Left On the left of the text. + \value Right On the right of the text. + \value Top Above the text. + \value Bottom Below the text. + + \sa decorationPosition +*/ + +/*! + \variable QStyleOptionViewItem::showDecorationSelected + \brief whether the decoration should be highlighted on selected + items + + If this option is true, the branch and any decorations on selected items + should be highlighted, indicating that the item is selected; otherwise, no + highlighting is required. The default value is false. + + \sa QStyle::SH_ItemView_ShowDecorationSelected, QAbstractItemView +*/ + +/*! + \variable QStyleOptionViewItem::textElideMode + \brief where ellipsis should be added for text that is too long to fit + into an item + + The default value is Qt::ElideMiddle, i.e. the ellipsis appears in + the middle of the text. + + \sa Qt::TextElideMode, QStyle::SH_ItemView_EllipsisLocation +*/ + +/*! + Constructs a QStyleOptionViewItem, initializing the members + variables to their default values. +*/ + +QStyleOptionViewItem::QStyleOptionViewItem() + : QStyleOption(Version, SO_ViewItem), + displayAlignment(Qt::AlignLeft), decorationAlignment(Qt::AlignLeft), + textElideMode(Qt::ElideMiddle), decorationPosition(Left), + showDecorationSelected(false) +{ +} + +/*! + \internal +*/ +QStyleOptionViewItem::QStyleOptionViewItem(int version) + : QStyleOption(version, SO_ViewItem), + displayAlignment(Qt::AlignLeft), decorationAlignment(Qt::AlignLeft), + textElideMode(Qt::ElideMiddle), decorationPosition(Left), + showDecorationSelected(false) +{ +} + +/*! + \fn QStyleOptionViewItem::QStyleOptionViewItem(const QStyleOptionViewItem &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \enum QStyleOptionViewItem::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_ViewItem} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionViewItem::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionViewItem::displayAlignment + \brief the alignment of the display value for the item + + The default value is Qt::AlignLeft. +*/ + +/*! + \variable QStyleOptionViewItem::decorationAlignment + \brief the alignment of the decoration for the item + + The default value is Qt::AlignLeft. +*/ + +/*! + \variable QStyleOptionViewItem::decorationPosition + \brief the position of the decoration for the item + + The default value is \l Left. + + \sa Position +*/ + +/*! + \variable QStyleOptionViewItem::decorationSize + \brief the size of the decoration for the item + + The default value is QSize(-1, -1), i.e. an invalid size. + + \sa decorationAlignment, decorationPosition +*/ + +/*! + \variable QStyleOptionViewItem::font + \brief the font used for the item + + By default, the application's default font is used. + + \sa QFont +*/ + +/*! + \fn T qstyleoption_cast<T>(const QStyleOption *option) + \relates QStyleOption + + Returns a T or 0 depending on the \l{QStyleOption::type}{type} and + \l{QStyleOption::version}{version} of the given \a option. + + Example: + + \snippet doc/src/snippets/qstyleoption/main.cpp 4 + + \sa QStyleOption::type, QStyleOption::version +*/ + +/*! + \fn T qstyleoption_cast<T>(QStyleOption *option) + \overload + \relates QStyleOption + + Returns a T or 0 depending on the type of the given \a option. +*/ + +#ifndef QT_NO_TABWIDGET +/*! + \class QStyleOptionTabWidgetFrame + \brief The QStyleOptionTabWidgetFrame class is used to describe the + parameters for drawing the frame around a tab widget. + + QStyleOptionTabWidgetFrame contains all the information that + QStyle functions need to draw the frame around QTabWidget. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QTabWidget +*/ + +/*! + Constructs a QStyleOptionTabWidgetFrame, initializing the members + variables to their default values. +*/ +QStyleOptionTabWidgetFrame::QStyleOptionTabWidgetFrame() + : QStyleOption(Version, SO_TabWidgetFrame), lineWidth(0), midLineWidth(0), + shape(QTabBar::RoundedNorth) +{ +} + +/*! + \fn QStyleOptionTabWidgetFrame::QStyleOptionTabWidgetFrame(const QStyleOptionTabWidgetFrame &other) + + Constructs a copy of \a other. +*/ + +/*! \internal */ +QStyleOptionTabWidgetFrame::QStyleOptionTabWidgetFrame(int version) + : QStyleOption(version, SO_TabWidgetFrame), lineWidth(0), midLineWidth(0), + shape(QTabBar::RoundedNorth) +{ +} + +/*! + \enum QStyleOptionTabWidgetFrame::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_TabWidgetFrame} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionTabWidgetFrame::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionTabWidgetFrame::lineWidth + \brief the line width for drawing the panel + + The default value is 0. +*/ + +/*! + \variable QStyleOptionTabWidgetFrame::midLineWidth + \brief the mid-line width for drawing the panel + + The mid line width is usually used in drawing sunken or raised + frames. The default value is 0. +*/ + +/*! + \variable QStyleOptionTabWidgetFrame::shape + \brief the tab shape used to draw the tabs + + The default value is QTabBar::RoundedNorth. +*/ + +/*! + \variable QStyleOptionTabWidgetFrame::tabBarSize + \brief the size of the tab bar + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + +/*! + \variable QStyleOptionTabWidgetFrame::rightCornerWidgetSize + \brief the size of the right-corner widget + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + +/*! \variable QStyleOptionTabWidgetFrame::leftCornerWidgetSize + \brief the size of the left-corner widget + + The default value is QSize(-1, -1), i.e. an invalid size. +*/ + + +/*! + + \class QStyleOptionTabWidgetFrameV2 + \brief The QStyleOptionTabWidgetFrameV2 class is used to describe the + parameters for drawing the frame around a tab widget. + + QStyleOptionTabWidgetFrameV2 contains all the information that + QStyle functions need to draw the frame around QTabWidget. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QTabWidget +*/ + + +/*! + \variable QStyleOptionTabWidgetFrameV2::tabBarRect + \brief the rectangle containing all the tabs + + The default value is a null rectangle, i.e. a rectangle with both + the width and the height set to 0. +*/ + +/*! + \variable QStyleOptionTabWidgetFrameV2::selectedTabRect + \brief the rectangle containing the selected tab + + This rectangle is contained within the tabBarRect. The default + value is a null rectangle, i.e. a rectangle with both the width + and the height set to 0. +*/ + + +/*! + Constructs a QStyleOptionTabWidgetFrameV2, initializing the members + variables to their default values. +*/ + +QStyleOptionTabWidgetFrameV2::QStyleOptionTabWidgetFrameV2() + : QStyleOptionTabWidgetFrame(Version) +{ +} + + +/*! \internal */ +QStyleOptionTabWidgetFrameV2::QStyleOptionTabWidgetFrameV2(int version) + : QStyleOptionTabWidgetFrame(version) +{ +} + + +/*! \fn QStyleOptionTabWidgetFrameV2::QStyleOptionTabWidgetFrameV2(const QStyleOptionTabWidgetFrameV2 &other) + Constructs a QStyleOptionTabWidgetFrameV2 copy of the \a other style option. + + If the \a other style option's version is 1, the new style option's \l + selectedTabRect and tabBarRect will contain null rects + + \sa version +*/ + +/*! + Constructs a QStyleOptionTabWidgetFrameV2 copy of the \a other style option. + + If the \a other style option's version is 1, the new style option's \l + selectedTabRect and tabBarRect will contain null rects + + \sa version +*/ +QStyleOptionTabWidgetFrameV2::QStyleOptionTabWidgetFrameV2(const QStyleOptionTabWidgetFrame &other) +{ + QStyleOptionTabWidgetFrameV2::operator=(other); + +} + + +/*! + Assigns the \a other style option to this style option. The \a + other style option can be either of the QStyleOptionFrameV2 or + QStyleOptionFrame types. + + If the \a{other} style option's version is 1, this style option's + QStyleOptionFrameV2::FrameFeature value is set to + QStyleOptionFrameV2::None. If its version is 2, its + \l{QStyleOptionFrameV2::}{FrameFeature} value is simply copied to + this style option. +*/ +QStyleOptionTabWidgetFrameV2 &QStyleOptionTabWidgetFrameV2::operator=(const QStyleOptionTabWidgetFrame &other) +{ + QStyleOptionTabWidgetFrame::operator=(other); + if (const QStyleOptionTabWidgetFrameV2 *f2 = qstyleoption_cast<const QStyleOptionTabWidgetFrameV2 *>(&other)) { + selectedTabRect = f2->selectedTabRect; + tabBarRect = f2->tabBarRect; + } + return *this; +} + + +/*! + \enum QStyleOptionTabWidgetFrameV2::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + + +#endif // QT_NO_TABWIDGET + +#ifndef QT_NO_TABBAR + +/*! + \class QStyleOptionTabBarBase + \brief The QStyleOptionTabBarBase class is used to describe + the base of a tab bar, i.e. the part that the tab bar usually + overlaps with. + + QStyleOptionTabBarBase contains all the information that QStyle + functions need to draw the tab bar base. Note that this is only + drawn for a standalone QTabBar (one that isn't part of a + QTabWidget). + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QTabBar::drawBase() +*/ + +/*! + Construct a QStyleOptionTabBarBase, initializing the members + vaiables to their default values. +*/ +QStyleOptionTabBarBase::QStyleOptionTabBarBase() + : QStyleOption(Version, SO_TabBarBase), shape(QTabBar::RoundedNorth) +{ +} + +/*! \internal */ +QStyleOptionTabBarBase::QStyleOptionTabBarBase(int version) + : QStyleOption(version, SO_TabBarBase), shape(QTabBar::RoundedNorth) +{ +} + +/*! + \fn QStyleOptionTabBarBase::QStyleOptionTabBarBase(const QStyleOptionTabBarBase &other) + + Constructs a copy of \a other. +*/ + +/*! + \enum QStyleOptionTabBarBase::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_TabBarBase} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionTabBarBase::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionTabBarBase::shape + \brief the shape of the tab bar + + The default value is QTabBar::RoundedNorth. +*/ + +/*! + \variable QStyleOptionTabBarBase::tabBarRect + \brief the rectangle containing all the tabs + + The default value is a null rectangle, i.e. a rectangle with both + the width and the height set to 0. +*/ + +/*! + \variable QStyleOptionTabBarBase::selectedTabRect + \brief the rectangle containing the selected tab + + This rectangle is contained within the tabBarRect. The default + value is a null rectangle, i.e. a rectangle with both the width + and the height set to 0. +*/ + + +/*! + \class QStyleOptionTabBarBaseV2 + \brief The QStyleOptionTabBarBaseV2 class is used to describe + the base of a tab bar, i.e. the part that the tab bar usually + overlaps with. + \since 4.5 + + QStyleOptionTabBarBase contains all the information that QStyle + functions need to draw the tab bar base. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QTabBar::drawBase() +*/ + +/*! + \enum QStyleOptionTabBarBaseV2::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 2 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleOptionTabBarBaseV2::documentMode + \brief whether the tabbar is in document mode. + + The default value is false; +*/ + +/*! + Construct a QStyleOptionTabBarBaseV2, initializing the members + vaiables to their default values. +*/ +QStyleOptionTabBarBaseV2::QStyleOptionTabBarBaseV2() + : QStyleOptionTabBarBase(Version) + , documentMode(false) +{ +} + +/*! + \fn QStyleOptionTabBarBaseV2::QStyleOptionTabBarBaseV2(const QStyleOptionTabBarBaseV2 &other) + + Constructs a copy of \a other. +*/ + +/*! + Constructs a copy of \a other. +*/ +QStyleOptionTabBarBaseV2::QStyleOptionTabBarBaseV2(const QStyleOptionTabBarBase &other) + : QStyleOptionTabBarBase(Version) +{ + (void)QStyleOptionTabBarBaseV2::operator=(other); +} + +/*! + Constructs a QStyleOptionTabBarBaseV2 copy of the \a other style option + which can be either of the QStyleOptionTabBarBaseV2, or QStyleOptionTabBarBase types. + + If the other style option's version is not 1, the new style option's + \c documentMode is set to false. If its version is 2, its \c documentMode value + is simply copied to the new style option. +*/ +QStyleOptionTabBarBaseV2 &QStyleOptionTabBarBaseV2::operator = (const QStyleOptionTabBarBase &other) +{ + QStyleOptionTabBarBase::operator=(other); + const QStyleOptionTabBarBaseV2 *v2 = qstyleoption_cast<const QStyleOptionTabBarBaseV2*>(&other); + documentMode = v2 ? v2->documentMode : false; + return *this; +} + +/*! \internal */ +QStyleOptionTabBarBaseV2::QStyleOptionTabBarBaseV2(int version) + : QStyleOptionTabBarBase(version), documentMode(false) +{ +} + +#endif // QT_NO_TABBAR + +#ifndef QT_NO_SIZEGRIP +/*! + \class QStyleOptionSizeGrip + \brief The QStyleOptionSizeGrip class is used to describe the + parameter for drawing a size grip. + \since 4.2 + + QStyleOptionButton contains all the information that QStyle + functions need to draw QSizeGrip. + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters used by the style functions. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QStyleOptionComplex, QSizeGrip +*/ + +/*! + Constructs a QStyleOptionSizeGrip. +*/ +QStyleOptionSizeGrip::QStyleOptionSizeGrip() + : QStyleOptionComplex(Version, Type), corner(Qt::BottomRightCorner) +{ +} + +/*! + \fn QStyleOptionSizeGrip::QStyleOptionSizeGrip(const QStyleOptionSizeGrip &other) + + Constructs a copy of the \a other style option. +*/ + +/*! + \internal +*/ +QStyleOptionSizeGrip::QStyleOptionSizeGrip(int version) + : QStyleOptionComplex(version, Type), corner(Qt::BottomRightCorner) +{ +} + +/*! + \variable QStyleOptionSizeGrip::corner + + The corner in which the size grip is located. +*/ + +/*! + \enum QStyleOptionSizeGrip::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l{SO_TabBarBase} for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionSizeGrip::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ +#endif // QT_NO_SIZEGRIP + +/*! + \class QStyleOptionGraphicsItem + \brief The QStyleOptionGraphicsItem class is used to describe + the parameters needed to draw a QGraphicsItem. + \since 4.2 + \ingroup graphicsview-api + + For performance reasons, the access to the member variables is + direct (i.e., using the \c . or \c -> operator). This low-level feel + makes the structures straightforward to use and emphasizes that + these are simply parameters. + + For an example demonstrating how style options can be used, see + the \l {widgets/styles}{Styles} example. + + \sa QStyleOption, QGraphicsItem::paint() +*/ + +/*! + \enum QStyleOptionGraphicsItem::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleOption subclass. + + \value Type The type of style option provided (\l SO_GraphicsItem for this class). + + The type is used internally by QStyleOption, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleOption subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleOptionGraphicsItem::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleOption subclass. + + \value Version 1 + + The version is used by QStyleOption subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + Constructs a QStyleOptionGraphicsItem. +*/ +QStyleOptionGraphicsItem::QStyleOptionGraphicsItem() + : QStyleOption(Version, Type), levelOfDetail(1) +{ +} + +/*! + \internal +*/ +QStyleOptionGraphicsItem::QStyleOptionGraphicsItem(int version) + : QStyleOption(version, Type), levelOfDetail(1) +{ +} + +/*! + \since 4.6 + + Returns the level of detail from the \a worldTransform. + + Its value represents the maximum value of the height and + width of a unity rectangle, mapped using the \a worldTransform + of the painter used to draw the item. By default, if no + transformations are applied, its value is 1. If zoomed out 1:2, the level + of detail will be 0.5, and if zoomed in 2:1, its value is 2. +*/ +qreal QStyleOptionGraphicsItem::levelOfDetailFromTransform(const QTransform &worldTransform) +{ + if (worldTransform.type() <= QTransform::TxTranslate) + return 1; // Translation only? The LOD is 1. + + // Two unit vectors. + QLineF v1(0, 0, 1, 0); + QLineF v2(0, 0, 0, 1); + // LOD is the transformed area of a 1x1 rectangle. + return qSqrt(worldTransform.map(v1).length() * worldTransform.map(v2).length()); +} + +/*! + \fn QStyleOptionGraphicsItem::QStyleOptionGraphicsItem(const QStyleOptionGraphicsItem &other) + + Constructs a copy of \a other. +*/ + +/*! + \variable QStyleOptionGraphicsItem::exposedRect + \brief the exposed rectangle, in item coordinates + + Make use of this rectangle to speed up item drawing when only parts of the + item are exposed. If the whole item is exposed, this rectangle will be the + same as QGraphicsItem::boundingRect(). + + This member is only initialized for items that have the + QGraphicsItem::ItemUsesExtendedStyleOption flag set. +*/ + +/*! + \variable QStyleOptionGraphicsItem::matrix + \brief the complete transformation matrix for the item + \obsolete + + The QMatrix provided through this member does include information about + any perspective transformations applied to the view or item. To get the + correct transformation matrix, use QPainter::transform() on the painter + passed into the QGraphicsItem::paint() implementation. + + This matrix is the combination of the item's scene matrix and the matrix + of the painter used for drawing the item. It is provided for convenience, + allowing anvanced level-of-detail metrics that can be used to speed up + item drawing. + + To find the dimensions of an item in screen coordinates (i.e., pixels), + you can use the mapping functions of QMatrix, such as QMatrix::map(). + + This member is only initialized for items that have the + QGraphicsItem::ItemUsesExtendedStyleOption flag set. + + \sa QStyleOptionGraphicsItem::levelOfDetailFromTransform() +*/ + +/*! + \variable QStyleOptionGraphicsItem::levelOfDetail + \obsolete + + Use QStyleOptionGraphicsItem::levelOfDetailFromTransform() + together with QPainter::worldTransform() instead. +*/ + +/*! + \class QStyleHintReturn + \brief The QStyleHintReturn class provides style hints that return more + than basic data types. + + \ingroup appearance + + QStyleHintReturn and its subclasses are used to pass information + from a style back to the querying widget. This is most useful + when the return value from QStyle::styleHint() does not provide enough + detail; for example, when a mask is to be returned. + + \omit + ### --Sam + \endomit +*/ + +/*! + \enum QStyleHintReturn::HintReturnType + + \value SH_Default QStyleHintReturn + \value SH_Mask \l QStyle::SH_RubberBand_Mask QStyle::SH_FocusFrame_Mask + \value SH_Variant \l QStyle::SH_TextControl_FocusIndicatorTextCharFormat +*/ + +/*! + \enum QStyleHintReturn::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Type The type of style option provided (\l SH_Default for + this class). + + The type is used internally by QStyleHintReturn, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleHintReturn subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleHintReturn::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Version 1 + + The version is used by QStyleHintReturn subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \variable QStyleHintReturn::type + \brief the type of the style hint container + + \sa HintReturnType +*/ + +/*! + \variable QStyleHintReturn::version + \brief the version of the style hint return container + + This value can be used by subclasses to implement extensions + without breaking compatibility. If you use qstyleoption_cast<T>(), you + normally do not need to check it. +*/ + +/*! + Constructs a QStyleHintReturn with version \a version and type \a + type. + + The version has no special meaning for QStyleHintReturn; it can be + used by subclasses to distinguish between different version of + the same hint type. + + \sa QStyleOption::version, QStyleOption::type +*/ + +QStyleHintReturn::QStyleHintReturn(int version, int type) + : version(version), type(type) +{ +} + +/*! + \internal +*/ + +QStyleHintReturn::~QStyleHintReturn() +{ + +} + +/*! + \class QStyleHintReturnMask + \brief The QStyleHintReturnMask class provides style hints that return a QRegion. + + \ingroup appearance + + \omit + ### --Sam + \endomit +*/ + +/*! + \variable QStyleHintReturnMask::region + \brief the region for style hints that return a QRegion +*/ + +/*! + Constructs a QStyleHintReturnMask. The member variables are + initialized to default values. +*/ +QStyleHintReturnMask::QStyleHintReturnMask() : QStyleHintReturn(Version, Type) +{ +} + +/*! + \enum QStyleHintReturnMask::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Type The type of style option provided (\l{SH_Mask} for + this class). + + The type is used internally by QStyleHintReturn, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleHintReturn subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleHintReturnMask::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Version 1 + + The version is used by QStyleHintReturn subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \class QStyleHintReturnVariant + \brief The QStyleHintReturnVariant class provides style hints that return a QVariant. + \since 4.3 + \ingroup appearance +*/ + +/*! + \variable QStyleHintReturnVariant::variant + \brief the variant for style hints that return a QVariant +*/ + +/*! + Constructs a QStyleHintReturnVariant. The member variables are + initialized to default values. +*/ +QStyleHintReturnVariant::QStyleHintReturnVariant() : QStyleHintReturn(Version, Type) +{ +} + +/*! + \enum QStyleHintReturnVariant::StyleOptionType + + This enum is used to hold information about the type of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Type The type of style option provided (\l{SH_Variant} for + this class). + + The type is used internally by QStyleHintReturn, its subclasses, and + qstyleoption_cast() to determine the type of style option. In + general you do not need to worry about this unless you want to + create your own QStyleHintReturn subclass and your own styles. + + \sa StyleOptionVersion +*/ + +/*! + \enum QStyleHintReturnVariant::StyleOptionVersion + + This enum is used to hold information about the version of the style option, and + is defined for each QStyleHintReturn subclass. + + \value Version 1 + + The version is used by QStyleHintReturn subclasses to implement + extensions without breaking compatibility. If you use + qstyleoption_cast(), you normally do not need to check it. + + \sa StyleOptionType +*/ + +/*! + \fn T qstyleoption_cast<T>(const QStyleHintReturn *hint) + \relates QStyleHintReturn + + Returns a T or 0 depending on the \l{QStyleHintReturn::type}{type} + and \l{QStyleHintReturn::version}{version} of \a hint. + + Example: + + \snippet doc/src/snippets/code/src_gui_styles_qstyleoption.cpp 0 + + \sa QStyleHintReturn::type, QStyleHintReturn::version +*/ + +/*! + \fn T qstyleoption_cast<T>(QStyleHintReturn *hint) + \overload + \relates QStyleHintReturn + + Returns a T or 0 depending on the type of \a hint. +*/ + +#if !defined(QT_NO_DEBUG_STREAM) +QDebug operator<<(QDebug debug, const QStyleOption::OptionType &optionType) +{ +#if !defined(QT_NO_DEBUG) + switch (optionType) { + case QStyleOption::SO_Default: + debug << "SO_Default"; break; + case QStyleOption::SO_FocusRect: + debug << "SO_FocusRect"; break; + case QStyleOption::SO_Button: + debug << "SO_Button"; break; + case QStyleOption::SO_Tab: + debug << "SO_Tab"; break; + case QStyleOption::SO_MenuItem: + debug << "SO_MenuItem"; break; + case QStyleOption::SO_Frame: + debug << "SO_Frame"; break; + case QStyleOption::SO_ProgressBar: + debug << "SO_ProgressBar"; break; + case QStyleOption::SO_ToolBox: + debug << "SO_ToolBox"; break; + case QStyleOption::SO_Header: + debug << "SO_Header"; break; + case QStyleOption::SO_Q3DockWindow: + debug << "SO_Q3DockWindow"; break; + case QStyleOption::SO_DockWidget: + debug << "SO_DockWidget"; break; + case QStyleOption::SO_Q3ListViewItem: + debug << "SO_Q3ListViewItem"; break; + case QStyleOption::SO_ViewItem: + debug << "SO_ViewItem"; break; + case QStyleOption::SO_TabWidgetFrame: + debug << "SO_TabWidgetFrame"; break; + case QStyleOption::SO_TabBarBase: + debug << "SO_TabBarBase"; break; + case QStyleOption::SO_RubberBand: + debug << "SO_RubberBand"; break; + case QStyleOption::SO_Complex: + debug << "SO_Complex"; break; + case QStyleOption::SO_Slider: + debug << "SO_Slider"; break; + case QStyleOption::SO_SpinBox: + debug << "SO_SpinBox"; break; + case QStyleOption::SO_ToolButton: + debug << "SO_ToolButton"; break; + case QStyleOption::SO_ComboBox: + debug << "SO_ComboBox"; break; + case QStyleOption::SO_Q3ListView: + debug << "SO_Q3ListView"; break; + case QStyleOption::SO_TitleBar: + debug << "SO_TitleBar"; break; + case QStyleOption::SO_CustomBase: + debug << "SO_CustomBase"; break; + case QStyleOption::SO_GroupBox: + debug << "SO_GroupBox"; break; + case QStyleOption::SO_ToolBar: + debug << "SO_ToolBar"; break; + case QStyleOption::SO_ComplexCustomBase: + debug << "SO_ComplexCustomBase"; break; + case QStyleOption::SO_SizeGrip: + debug << "SO_SizeGrip"; break; + case QStyleOption::SO_GraphicsItem: + debug << "SO_GraphicsItem"; break; + } +#else + Q_UNUSED(optionType); +#endif + return debug; +} + +QDebug operator<<(QDebug debug, const QStyleOption &option) +{ +#if !defined(QT_NO_DEBUG) + debug << "QStyleOption("; + debug << QStyleOption::OptionType(option.type); + debug << ',' << (option.direction == Qt::RightToLeft ? "RightToLeft" : "LeftToRight"); + debug << ',' << option.state; + debug << ',' << option.rect; + debug << ')'; +#else + Q_UNUSED(option); +#endif + return debug; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/styles/qstyleoption.h b/src/gui/styles/qstyleoption.h new file mode 100644 index 0000000000..1b1de9cdf2 --- /dev/null +++ b/src/gui/styles/qstyleoption.h @@ -0,0 +1,970 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEOPTION_H +#define QSTYLEOPTION_H + +#include <QtCore/qvariant.h> +#include <QtGui/qabstractspinbox.h> +#include <QtGui/qicon.h> +#include <QtGui/qmatrix.h> +#include <QtGui/qslider.h> +#include <QtGui/qstyle.h> +#include <QtGui/qtabbar.h> +#include <QtGui/qtabwidget.h> +#include <QtGui/qrubberband.h> +#include <QtGui/qframe.h> +#ifndef QT_NO_ITEMVIEWS +# include <QtCore/qabstractitemmodel.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QDebug; + +class Q_GUI_EXPORT QStyleOption +{ +public: + enum OptionType { + SO_Default, SO_FocusRect, SO_Button, SO_Tab, SO_MenuItem, + SO_Frame, SO_ProgressBar, SO_ToolBox, SO_Header, SO_Q3DockWindow, + SO_DockWidget, SO_Q3ListViewItem, SO_ViewItem, SO_TabWidgetFrame, + SO_TabBarBase, SO_RubberBand, SO_ToolBar, SO_GraphicsItem, + + SO_Complex = 0xf0000, SO_Slider, SO_SpinBox, SO_ToolButton, SO_ComboBox, + SO_Q3ListView, SO_TitleBar, SO_GroupBox, SO_SizeGrip, + + SO_CustomBase = 0xf00, + SO_ComplexCustomBase = 0xf000000 + }; + + enum StyleOptionType { Type = SO_Default }; + enum StyleOptionVersion { Version = 1 }; + + int version; + int type; + QStyle::State state; + Qt::LayoutDirection direction; + QRect rect; + QFontMetrics fontMetrics; + QPalette palette; + + QStyleOption(int version = QStyleOption::Version, int type = SO_Default); + QStyleOption(const QStyleOption &other); + ~QStyleOption(); + + void init(const QWidget *w); + inline void initFrom(const QWidget *w) { init(w); } + QStyleOption &operator=(const QStyleOption &other); +}; + +class Q_GUI_EXPORT QStyleOptionFocusRect : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_FocusRect }; + enum StyleOptionVersion { Version = 1 }; + + QColor backgroundColor; + + QStyleOptionFocusRect(); + QStyleOptionFocusRect(const QStyleOptionFocusRect &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionFocusRect(int version); +}; + +class Q_GUI_EXPORT QStyleOptionFrame : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Frame }; + enum StyleOptionVersion { Version = 1 }; + + int lineWidth; + int midLineWidth; + + QStyleOptionFrame(); + QStyleOptionFrame(const QStyleOptionFrame &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionFrame(int version); +}; + +class Q_GUI_EXPORT QStyleOptionFrameV2 : public QStyleOptionFrame +{ +public: + enum StyleOptionVersion { Version = 2 }; + enum FrameFeature { + None = 0x00, + Flat = 0x01 + }; + Q_DECLARE_FLAGS(FrameFeatures, FrameFeature) + FrameFeatures features; + + QStyleOptionFrameV2(); + QStyleOptionFrameV2(const QStyleOptionFrameV2 &other) : QStyleOptionFrame(Version) { *this = other; } + QStyleOptionFrameV2(const QStyleOptionFrame &other); + QStyleOptionFrameV2 &operator=(const QStyleOptionFrame &other); + +protected: + QStyleOptionFrameV2(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionFrameV2::FrameFeatures) + + +class Q_GUI_EXPORT QStyleOptionFrameV3 : public QStyleOptionFrameV2 +{ +public: + enum StyleOptionVersion { Version = 3 }; + QFrame::Shape frameShape : 4; + uint unused : 28; + + QStyleOptionFrameV3(); + QStyleOptionFrameV3(const QStyleOptionFrameV3 &other) : QStyleOptionFrameV2(Version) { *this = other; } + QStyleOptionFrameV3(const QStyleOptionFrame &other); + QStyleOptionFrameV3 &operator=(const QStyleOptionFrame &other); + +protected: + QStyleOptionFrameV3(int version); +}; + + +#ifndef QT_NO_TABWIDGET +class Q_GUI_EXPORT QStyleOptionTabWidgetFrame : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_TabWidgetFrame }; + enum StyleOptionVersion { Version = 1 }; + + int lineWidth; + int midLineWidth; + QTabBar::Shape shape; + QSize tabBarSize; + QSize rightCornerWidgetSize; + QSize leftCornerWidgetSize; + + QStyleOptionTabWidgetFrame(); + inline QStyleOptionTabWidgetFrame(const QStyleOptionTabWidgetFrame &other) + : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionTabWidgetFrame(int version); +}; + +class Q_GUI_EXPORT QStyleOptionTabWidgetFrameV2 : public QStyleOptionTabWidgetFrame +{ +public: + enum StyleOptionVersion { Version = 2 }; + + QRect tabBarRect; + QRect selectedTabRect; + + QStyleOptionTabWidgetFrameV2(); + QStyleOptionTabWidgetFrameV2(const QStyleOptionTabWidgetFrameV2 &other) : + QStyleOptionTabWidgetFrame(Version) { *this = other; } + QStyleOptionTabWidgetFrameV2(const QStyleOptionTabWidgetFrame &other); + QStyleOptionTabWidgetFrameV2 &operator=(const QStyleOptionTabWidgetFrame &other); + +protected: + QStyleOptionTabWidgetFrameV2(int version); +}; + +#endif + + +#ifndef QT_NO_TABBAR +class Q_GUI_EXPORT QStyleOptionTabBarBase : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_TabBarBase }; + enum StyleOptionVersion { Version = 1 }; + + QTabBar::Shape shape; + QRect tabBarRect; + QRect selectedTabRect; + + QStyleOptionTabBarBase(); + QStyleOptionTabBarBase(const QStyleOptionTabBarBase &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionTabBarBase(int version); +}; + +class Q_GUI_EXPORT QStyleOptionTabBarBaseV2 : public QStyleOptionTabBarBase +{ +public: + enum StyleOptionVersion { Version = 2 }; + bool documentMode; + QStyleOptionTabBarBaseV2(); + QStyleOptionTabBarBaseV2(const QStyleOptionTabBarBaseV2 &other) : QStyleOptionTabBarBase(Version) { *this = other; } + QStyleOptionTabBarBaseV2(const QStyleOptionTabBarBase &other); + QStyleOptionTabBarBaseV2 &operator=(const QStyleOptionTabBarBase &other); + +protected: + QStyleOptionTabBarBaseV2(int version); +}; + +#endif + +class Q_GUI_EXPORT QStyleOptionHeader : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Header }; + enum StyleOptionVersion { Version = 1 }; + + enum SectionPosition { Beginning, Middle, End, OnlyOneSection }; + enum SelectedPosition { NotAdjacent, NextIsSelected, PreviousIsSelected, + NextAndPreviousAreSelected }; + enum SortIndicator { None, SortUp, SortDown }; + + int section; + QString text; + Qt::Alignment textAlignment; + QIcon icon; + Qt::Alignment iconAlignment; + SectionPosition position; + SelectedPosition selectedPosition; + SortIndicator sortIndicator; + Qt::Orientation orientation; + + QStyleOptionHeader(); + QStyleOptionHeader(const QStyleOptionHeader &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionHeader(int version); +}; + +class Q_GUI_EXPORT QStyleOptionButton : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Button }; + enum StyleOptionVersion { Version = 1 }; + + enum ButtonFeature { None = 0x00, Flat = 0x01, HasMenu = 0x02, DefaultButton = 0x04, + AutoDefaultButton = 0x08, CommandLinkButton = 0x10 }; + Q_DECLARE_FLAGS(ButtonFeatures, ButtonFeature) + + ButtonFeatures features; + QString text; + QIcon icon; + QSize iconSize; + + QStyleOptionButton(); + QStyleOptionButton(const QStyleOptionButton &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionButton(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionButton::ButtonFeatures) + +#ifndef QT_NO_TABBAR +class Q_GUI_EXPORT QStyleOptionTab : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Tab }; + enum StyleOptionVersion { Version = 1 }; + + enum TabPosition { Beginning, Middle, End, OnlyOneTab }; + enum SelectedPosition { NotAdjacent, NextIsSelected, PreviousIsSelected }; + enum CornerWidget { NoCornerWidgets = 0x00, LeftCornerWidget = 0x01, + RightCornerWidget = 0x02 }; + Q_DECLARE_FLAGS(CornerWidgets, CornerWidget) + + QTabBar::Shape shape; + QString text; + QIcon icon; + int row; + TabPosition position; + SelectedPosition selectedPosition; + CornerWidgets cornerWidgets; + + QStyleOptionTab(); + QStyleOptionTab(const QStyleOptionTab &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionTab(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionTab::CornerWidgets) + +class Q_GUI_EXPORT QStyleOptionTabV2 : public QStyleOptionTab +{ +public: + enum StyleOptionVersion { Version = 2 }; + QSize iconSize; + QStyleOptionTabV2(); + QStyleOptionTabV2(const QStyleOptionTabV2 &other) : QStyleOptionTab(Version) { *this = other; } + QStyleOptionTabV2(const QStyleOptionTab &other); + QStyleOptionTabV2 &operator=(const QStyleOptionTab &other); + +protected: + QStyleOptionTabV2(int version); +}; + +class Q_GUI_EXPORT QStyleOptionTabV3 : public QStyleOptionTabV2 +{ +public: + enum StyleOptionVersion { Version = 3 }; + bool documentMode; + QSize leftButtonSize; + QSize rightButtonSize; + QStyleOptionTabV3(); + QStyleOptionTabV3(const QStyleOptionTabV3 &other) : QStyleOptionTabV2(Version) { *this = other; } + QStyleOptionTabV3(const QStyleOptionTabV2 &other) : QStyleOptionTabV2(Version) { *this = other; } + QStyleOptionTabV3(const QStyleOptionTab &other); + QStyleOptionTabV3 &operator=(const QStyleOptionTab &other); + +protected: + QStyleOptionTabV3(int version); +}; + +#endif + + +#ifndef QT_NO_TOOLBAR + +class Q_GUI_EXPORT QStyleOptionToolBar : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_ToolBar }; + enum StyleOptionVersion { Version = 1 }; + enum ToolBarPosition { Beginning, Middle, End, OnlyOne }; + enum ToolBarFeature { None = 0x0, Movable = 0x1 }; + Q_DECLARE_FLAGS(ToolBarFeatures, ToolBarFeature) + ToolBarPosition positionOfLine; // The toolbar line position + ToolBarPosition positionWithinLine; // The position within a toolbar + Qt::ToolBarArea toolBarArea; // The toolbar docking area + ToolBarFeatures features; + int lineWidth; + int midLineWidth; + QStyleOptionToolBar(); + QStyleOptionToolBar(const QStyleOptionToolBar &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionToolBar(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionToolBar::ToolBarFeatures) + +#endif + + + +class Q_GUI_EXPORT QStyleOptionProgressBar : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_ProgressBar }; + enum StyleOptionVersion { Version = 1 }; + + int minimum; + int maximum; + int progress; + QString text; + Qt::Alignment textAlignment; + bool textVisible; + + QStyleOptionProgressBar(); + QStyleOptionProgressBar(const QStyleOptionProgressBar &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionProgressBar(int version); +}; + +// Adds style info for vertical progress bars +class Q_GUI_EXPORT QStyleOptionProgressBarV2 : public QStyleOptionProgressBar +{ +public: + enum StyleOptionType { Type = SO_ProgressBar }; + enum StyleOptionVersion { Version = 2 }; + Qt::Orientation orientation; + bool invertedAppearance; + bool bottomToTop; + + QStyleOptionProgressBarV2(); + QStyleOptionProgressBarV2(const QStyleOptionProgressBar &other); + QStyleOptionProgressBarV2(const QStyleOptionProgressBarV2 &other); + QStyleOptionProgressBarV2 &operator=(const QStyleOptionProgressBar &other); + +protected: + QStyleOptionProgressBarV2(int version); +}; + +class Q_GUI_EXPORT QStyleOptionMenuItem : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_MenuItem }; + enum StyleOptionVersion { Version = 1 }; + + enum MenuItemType { Normal, DefaultItem, Separator, SubMenu, Scroller, TearOff, Margin, + EmptyArea }; + enum CheckType { NotCheckable, Exclusive, NonExclusive }; + + MenuItemType menuItemType; + CheckType checkType; + bool checked; + bool menuHasCheckableItems; + QRect menuRect; + QString text; + QIcon icon; + int maxIconWidth; + int tabWidth; + QFont font; + + QStyleOptionMenuItem(); + QStyleOptionMenuItem(const QStyleOptionMenuItem &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionMenuItem(int version); +}; + +class Q_GUI_EXPORT QStyleOptionQ3ListViewItem : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Q3ListViewItem }; + enum StyleOptionVersion { Version = 1 }; + + enum Q3ListViewItemFeature { None = 0x00, Expandable = 0x01, MultiLine = 0x02, Visible = 0x04, + ParentControl = 0x08 }; + Q_DECLARE_FLAGS(Q3ListViewItemFeatures, Q3ListViewItemFeature) + + Q3ListViewItemFeatures features; + int height; + int totalHeight; + int itemY; + int childCount; + + QStyleOptionQ3ListViewItem(); + QStyleOptionQ3ListViewItem(const QStyleOptionQ3ListViewItem &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionQ3ListViewItem(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionQ3ListViewItem::Q3ListViewItemFeatures) + +class Q_GUI_EXPORT QStyleOptionQ3DockWindow : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Q3DockWindow }; + enum StyleOptionVersion { Version = 1 }; + + bool docked; + bool closeEnabled; + + QStyleOptionQ3DockWindow(); + QStyleOptionQ3DockWindow(const QStyleOptionQ3DockWindow &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionQ3DockWindow(int version); +}; + +class Q_GUI_EXPORT QStyleOptionDockWidget : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_DockWidget }; + enum StyleOptionVersion { Version = 1 }; + + QString title; + bool closable; + bool movable; + bool floatable; + + QStyleOptionDockWidget(); + QStyleOptionDockWidget(const QStyleOptionDockWidget &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionDockWidget(int version); +}; + +class Q_GUI_EXPORT QStyleOptionDockWidgetV2 : public QStyleOptionDockWidget +{ +public: + enum StyleOptionVersion { Version = 2 }; + + bool verticalTitleBar; + + QStyleOptionDockWidgetV2(); + QStyleOptionDockWidgetV2(const QStyleOptionDockWidgetV2 &other) + : QStyleOptionDockWidget(Version) { *this = other; } + QStyleOptionDockWidgetV2(const QStyleOptionDockWidget &other); + QStyleOptionDockWidgetV2 &operator = (const QStyleOptionDockWidget &other); + +protected: + QStyleOptionDockWidgetV2(int version); +}; + +class Q_GUI_EXPORT QStyleOptionViewItem : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_ViewItem }; + enum StyleOptionVersion { Version = 1 }; + + enum Position { Left, Right, Top, Bottom }; + + Qt::Alignment displayAlignment; + Qt::Alignment decorationAlignment; + Qt::TextElideMode textElideMode; + Position decorationPosition; + QSize decorationSize; + QFont font; + bool showDecorationSelected; + + QStyleOptionViewItem(); + QStyleOptionViewItem(const QStyleOptionViewItem &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionViewItem(int version); +}; + +class Q_GUI_EXPORT QStyleOptionViewItemV2 : public QStyleOptionViewItem +{ +public: + enum StyleOptionVersion { Version = 2 }; + + enum ViewItemFeature { + None = 0x00, + WrapText = 0x01, + Alternate = 0x02, + HasCheckIndicator = 0x04, + HasDisplay = 0x08, + HasDecoration = 0x10 + }; + Q_DECLARE_FLAGS(ViewItemFeatures, ViewItemFeature) + + ViewItemFeatures features; + + QStyleOptionViewItemV2(); + QStyleOptionViewItemV2(const QStyleOptionViewItemV2 &other) : QStyleOptionViewItem(Version) { *this = other; } + QStyleOptionViewItemV2(const QStyleOptionViewItem &other); + QStyleOptionViewItemV2 &operator=(const QStyleOptionViewItem &other); + +protected: + QStyleOptionViewItemV2(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionViewItemV2::ViewItemFeatures) + +class Q_GUI_EXPORT QStyleOptionViewItemV3 : public QStyleOptionViewItemV2 +{ +public: + enum StyleOptionVersion { Version = 3 }; + + QLocale locale; + const QWidget *widget; + + QStyleOptionViewItemV3(); + QStyleOptionViewItemV3(const QStyleOptionViewItemV3 &other) + : QStyleOptionViewItemV2(Version) { *this = other; } + QStyleOptionViewItemV3(const QStyleOptionViewItem &other); + QStyleOptionViewItemV3 &operator = (const QStyleOptionViewItem &other); + +protected: + QStyleOptionViewItemV3(int version); +}; + +#ifndef QT_NO_ITEMVIEWS +class Q_GUI_EXPORT QStyleOptionViewItemV4 : public QStyleOptionViewItemV3 +{ +public: + enum StyleOptionVersion { Version = 4 }; + enum ViewItemPosition { Invalid, Beginning, Middle, End, OnlyOne }; + + QModelIndex index; + Qt::CheckState checkState; + QIcon icon; + QString text; + ViewItemPosition viewItemPosition; + QBrush backgroundBrush; + + QStyleOptionViewItemV4(); + QStyleOptionViewItemV4(const QStyleOptionViewItemV4 &other) + : QStyleOptionViewItemV3(Version) { *this = other; } + QStyleOptionViewItemV4(const QStyleOptionViewItem &other); + QStyleOptionViewItemV4 &operator = (const QStyleOptionViewItem &other); + +protected: + QStyleOptionViewItemV4(int version); +}; +#endif + +class Q_GUI_EXPORT QStyleOptionToolBox : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_ToolBox }; + enum StyleOptionVersion { Version = 1 }; + + QString text; + QIcon icon; + + QStyleOptionToolBox(); + QStyleOptionToolBox(const QStyleOptionToolBox &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionToolBox(int version); +}; + +class Q_GUI_EXPORT QStyleOptionToolBoxV2 : public QStyleOptionToolBox +{ +public: + enum StyleOptionVersion { Version = 2 }; + enum TabPosition { Beginning, Middle, End, OnlyOneTab }; + enum SelectedPosition { NotAdjacent, NextIsSelected, PreviousIsSelected }; + + TabPosition position; + SelectedPosition selectedPosition; + + QStyleOptionToolBoxV2(); + QStyleOptionToolBoxV2(const QStyleOptionToolBoxV2 &other) : QStyleOptionToolBox(Version) { *this = other; } + QStyleOptionToolBoxV2(const QStyleOptionToolBox &other); + QStyleOptionToolBoxV2 &operator=(const QStyleOptionToolBox &other); + +protected: + QStyleOptionToolBoxV2(int version); +}; + +#ifndef QT_NO_RUBBERBAND +class Q_GUI_EXPORT QStyleOptionRubberBand : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_RubberBand }; + enum StyleOptionVersion { Version = 1 }; + + QRubberBand::Shape shape; + bool opaque; + + QStyleOptionRubberBand(); + QStyleOptionRubberBand(const QStyleOptionRubberBand &other) : QStyleOption(Version, Type) { *this = other; } + +protected: + QStyleOptionRubberBand(int version); +}; +#endif // QT_NO_RUBBERBAND + +// -------------------------- Complex style options ------------------------------- +class Q_GUI_EXPORT QStyleOptionComplex : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_Complex }; + enum StyleOptionVersion { Version = 1 }; + + QStyle::SubControls subControls; + QStyle::SubControls activeSubControls; + + QStyleOptionComplex(int version = QStyleOptionComplex::Version, int type = SO_Complex); + QStyleOptionComplex(const QStyleOptionComplex &other) : QStyleOption(Version, Type) { *this = other; } +}; + +#ifndef QT_NO_SLIDER +class Q_GUI_EXPORT QStyleOptionSlider : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_Slider }; + enum StyleOptionVersion { Version = 1 }; + + Qt::Orientation orientation; + int minimum; + int maximum; + QSlider::TickPosition tickPosition; + int tickInterval; + bool upsideDown; + int sliderPosition; + int sliderValue; + int singleStep; + int pageStep; + qreal notchTarget; + bool dialWrapping; + + QStyleOptionSlider(); + QStyleOptionSlider(const QStyleOptionSlider &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionSlider(int version); +}; +#endif // QT_NO_SLIDER + +#ifndef QT_NO_SPINBOX +class Q_GUI_EXPORT QStyleOptionSpinBox : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_SpinBox }; + enum StyleOptionVersion { Version = 1 }; + + QAbstractSpinBox::ButtonSymbols buttonSymbols; + QAbstractSpinBox::StepEnabled stepEnabled; + bool frame; + + QStyleOptionSpinBox(); + QStyleOptionSpinBox(const QStyleOptionSpinBox &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionSpinBox(int version); +}; +#endif // QT_NO_SPINBOX + +class Q_GUI_EXPORT QStyleOptionQ3ListView : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_Q3ListView }; + enum StyleOptionVersion { Version = 1 }; + + QList<QStyleOptionQ3ListViewItem> items; + QPalette viewportPalette; + QPalette::ColorRole viewportBGRole; + int sortColumn; + int itemMargin; + int treeStepSize; + bool rootIsDecorated; + + QStyleOptionQ3ListView(); + QStyleOptionQ3ListView(const QStyleOptionQ3ListView &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionQ3ListView(int version); +}; + +class Q_GUI_EXPORT QStyleOptionToolButton : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_ToolButton }; + enum StyleOptionVersion { Version = 1 }; + + enum ToolButtonFeature { None = 0x00, Arrow = 0x01, Menu = 0x04, MenuButtonPopup = Menu, PopupDelay = 0x08, + HasMenu = 0x10 }; + Q_DECLARE_FLAGS(ToolButtonFeatures, ToolButtonFeature) + + ToolButtonFeatures features; + QIcon icon; + QSize iconSize; + QString text; + Qt::ArrowType arrowType; + Qt::ToolButtonStyle toolButtonStyle; + QPoint pos; + QFont font; + + QStyleOptionToolButton(); + QStyleOptionToolButton(const QStyleOptionToolButton &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionToolButton(int version); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QStyleOptionToolButton::ToolButtonFeatures) + +class Q_GUI_EXPORT QStyleOptionComboBox : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_ComboBox }; + enum StyleOptionVersion { Version = 1 }; + + bool editable; + QRect popupRect; + bool frame; + QString currentText; + QIcon currentIcon; + QSize iconSize; + + QStyleOptionComboBox(); + QStyleOptionComboBox(const QStyleOptionComboBox &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionComboBox(int version); +}; + +class Q_GUI_EXPORT QStyleOptionTitleBar : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_TitleBar }; + enum StyleOptionVersion { Version = 1 }; + + QString text; + QIcon icon; + int titleBarState; + Qt::WindowFlags titleBarFlags; + + QStyleOptionTitleBar(); + QStyleOptionTitleBar(const QStyleOptionTitleBar &other) : QStyleOptionComplex(Version, Type) { *this = other; } + +protected: + QStyleOptionTitleBar(int version); +}; + +class Q_GUI_EXPORT QStyleOptionGroupBox : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_GroupBox }; + enum StyleOptionVersion { Version = 1 }; + + QStyleOptionFrameV2::FrameFeatures features; + QString text; + Qt::Alignment textAlignment; + QColor textColor; + int lineWidth; + int midLineWidth; + + QStyleOptionGroupBox(); + QStyleOptionGroupBox(const QStyleOptionGroupBox &other) : QStyleOptionComplex(Version, Type) { *this = other; } +protected: + QStyleOptionGroupBox(int version); +}; + +class Q_GUI_EXPORT QStyleOptionSizeGrip : public QStyleOptionComplex +{ +public: + enum StyleOptionType { Type = SO_SizeGrip }; + enum StyleOptionVersion { Version = 1 }; + + Qt::Corner corner; + + QStyleOptionSizeGrip(); + QStyleOptionSizeGrip(const QStyleOptionSizeGrip &other) : QStyleOptionComplex(Version, Type) { *this = other; } +protected: + QStyleOptionSizeGrip(int version); +}; + +class Q_GUI_EXPORT QStyleOptionGraphicsItem : public QStyleOption +{ +public: + enum StyleOptionType { Type = SO_GraphicsItem }; + enum StyleOptionVersion { Version = 1 }; + + QRectF exposedRect; + QMatrix matrix; + qreal levelOfDetail; + + QStyleOptionGraphicsItem(); + QStyleOptionGraphicsItem(const QStyleOptionGraphicsItem &other) : QStyleOption(Version, Type) { *this = other; } + static qreal levelOfDetailFromTransform(const QTransform &worldTransform); +protected: + QStyleOptionGraphicsItem(int version); +}; + +template <typename T> +T qstyleoption_cast(const QStyleOption *opt) +{ + if (opt && opt->version >= static_cast<T>(0)->Version && (opt->type == static_cast<T>(0)->Type + || int(static_cast<T>(0)->Type) == QStyleOption::SO_Default + || (int(static_cast<T>(0)->Type) == QStyleOption::SO_Complex + && opt->type > QStyleOption::SO_Complex))) + return static_cast<T>(opt); + return 0; +} + +template <typename T> +T qstyleoption_cast(QStyleOption *opt) +{ + if (opt && opt->version >= static_cast<T>(0)->Version && (opt->type == static_cast<T>(0)->Type + || int(static_cast<T>(0)->Type) == QStyleOption::SO_Default + || (int(static_cast<T>(0)->Type) == QStyleOption::SO_Complex + && opt->type > QStyleOption::SO_Complex))) + return static_cast<T>(opt); + return 0; +} + +// -------------------------- QStyleHintReturn ------------------------------- +class Q_GUI_EXPORT QStyleHintReturn { +public: + enum HintReturnType { + SH_Default=0xf000, SH_Mask, SH_Variant + }; + + enum StyleOptionType { Type = SH_Default }; + enum StyleOptionVersion { Version = 1 }; + + QStyleHintReturn(int version = QStyleOption::Version, int type = SH_Default); + ~QStyleHintReturn(); + + int version; + int type; +}; + +class Q_GUI_EXPORT QStyleHintReturnMask : public QStyleHintReturn { +public: + enum StyleOptionType { Type = SH_Mask }; + enum StyleOptionVersion { Version = 1 }; + + QStyleHintReturnMask(); + + QRegion region; +}; + +class Q_GUI_EXPORT QStyleHintReturnVariant : public QStyleHintReturn { +public: + enum StyleOptionType { Type = SH_Variant }; + enum StyleOptionVersion { Version = 1 }; + + QStyleHintReturnVariant(); + + QVariant variant; +}; + +template <typename T> +T qstyleoption_cast(const QStyleHintReturn *hint) +{ + if (hint && hint->version <= static_cast<T>(0)->Version && + (hint->type == static_cast<T>(0)->Type || int(static_cast<T>(0)->Type) == QStyleHintReturn::SH_Default)) + return static_cast<T>(hint); + return 0; +} + +template <typename T> +T qstyleoption_cast(QStyleHintReturn *hint) +{ + if (hint && hint->version <= static_cast<T>(0)->Version && + (hint->type == static_cast<T>(0)->Type || int(static_cast<T>(0)->Type) == QStyleHintReturn::SH_Default)) + return static_cast<T>(hint); + return 0; +} + +#if !defined(QT_NO_DEBUG_STREAM) +Q_GUI_EXPORT QDebug operator<<(QDebug debug, const QStyleOption::OptionType &optionType); +Q_GUI_EXPORT QDebug operator<<(QDebug debug, const QStyleOption &option); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEOPTION_H diff --git a/src/gui/styles/qstyleplugin.cpp b/src/gui/styles/qstyleplugin.cpp new file mode 100644 index 0000000000..f2143e307a --- /dev/null +++ b/src/gui/styles/qstyleplugin.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstyleplugin.h" +#include "qstyle.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QStylePlugin + \brief The QStylePlugin class provides an abstract base for custom QStyle plugins. + + \ingroup plugins + + QStylePlugin is a simple plugin interface that makes it easy + to create custom styles that can be loaded dynamically into + applications using the QStyleFactory class. + + Writing a style plugin is achieved by subclassing this base class, + reimplementing the pure virtual keys() and create() functions, and + exporting the class using the Q_EXPORT_PLUGIN2() macro. See \l + {How to Create Qt Plugins} for details. + + \sa QStyleFactory, QStyle +*/ + +/*! + \fn QStringList QStylePlugin::keys() const + + Returns the list of style keys this plugin supports. + + These keys are usually the class names of the custom styles that + are implemented in the plugin. + + \sa create() +*/ + +/*! + \fn QStyle *QStylePlugin::create(const QString& key) + + Creates and returns a QStyle object for the given style \a key. + If a plugin cannot create a style, it should return 0 instead. + + The style key is usually the class name of the required + style. Note that the keys are case insensitive. For example: + + \snippet doc/src/snippets/qstyleplugin/main.cpp 0 + \codeline + \snippet doc/src/snippets/qstyleplugin/main.cpp 1 + \snippet doc/src/snippets/qstyleplugin/main.cpp 2 + + \sa keys() +*/ + +/*! + Constructs a style plugin with the given \a parent. + + Note that this constructor is invoked automatically by the + Q_EXPORT_PLUGIN2() macro, so there is no need for calling it + explicitly. +*/ +QStylePlugin::QStylePlugin(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the style plugin. + + Note that Qt destroys a plugin automatically when it is no longer + used, so there is no need for calling the destructor explicitly. +*/ +QStylePlugin::~QStylePlugin() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/styles/qstyleplugin.h b/src/gui/styles/qstyleplugin.h new file mode 100644 index 0000000000..56d690f6ff --- /dev/null +++ b/src/gui/styles/qstyleplugin.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLEPLUGIN_H +#define QSTYLEPLUGIN_H + +#include <QtCore/qplugin.h> +#include <QtCore/qfactoryinterface.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyle; + +struct Q_GUI_EXPORT QStyleFactoryInterface : public QFactoryInterface +{ + virtual QStyle *create(const QString &key) = 0; +}; + +#define QStyleFactoryInterface_iid "com.trolltech.Qt.QStyleFactoryInterface" + +Q_DECLARE_INTERFACE(QStyleFactoryInterface, QStyleFactoryInterface_iid) + +class Q_GUI_EXPORT QStylePlugin : public QObject, public QStyleFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QStyleFactoryInterface:QFactoryInterface) +public: + explicit QStylePlugin(QObject *parent = 0); + ~QStylePlugin(); + + virtual QStringList keys() const = 0; + virtual QStyle *create(const QString &key) = 0; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSTYLEPLUGIN_H diff --git a/src/gui/styles/qstylesheetstyle.cpp b/src/gui/styles/qstylesheetstyle.cpp new file mode 100644 index 0000000000..faa929ebea --- /dev/null +++ b/src/gui/styles/qstylesheetstyle.cpp @@ -0,0 +1,5898 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qglobal.h> + +#ifndef QT_NO_STYLE_STYLESHEET + +#include "qstylesheetstyle_p.h" +#include "private/qcssutil_p.h" +#include <qdebug.h> +#include <qapplication.h> +#include <qmenu.h> +#include <qmenubar.h> +#include <qpainter.h> +#include <qstyleoption.h> +#include <qlineedit.h> +#include <qwindowsstyle.h> +#include <qcombobox.h> +#include <qwindowsstyle.h> +#include <qplastiquestyle.h> +#include "private/qcssparser_p.h" +#include "private/qmath_p.h" +#include <qabstractscrollarea.h> +#include "private/qabstractscrollarea_p.h" +#include <qtooltip.h> +#include <qshareddata.h> +#include <qradiobutton.h> +#include <qtoolbutton.h> +#include <qscrollbar.h> +#include <qstring.h> +#include <qfile.h> +#include <qcheckbox.h> +#include <qstatusbar.h> +#include <qheaderview.h> +#include <qprogressbar.h> +#include <private/qwindowsstyle_p.h> +#include <qtabbar.h> +#include <QMetaProperty> +#include <qmainwindow.h> +#include <qdockwidget.h> +#include <qmdisubwindow.h> +#include <qdialog.h> +#include <private/qwidget_p.h> +#include <QAbstractSpinBox> +#include <QLabel> +#include "qdrawutil.h" + +#include <limits.h> +#include <QtGui/qtoolbar.h> + +QT_BEGIN_NAMESPACE + +using namespace QCss; + + +class QStyleSheetStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QStyleSheetStyle) +public: + QStyleSheetStylePrivate() { } +}; + + +static QStyleSheetStyleCaches *styleSheetCaches = 0; + +/* RECURSION_GUARD: + * the QStyleSheetStyle is a proxy. If used with others proxy style, we may end up with something like: + * QStyleSheetStyle -> ProxyStyle -> QStyleSheetStyle -> OriginalStyle + * Recursion may happen if the style call the widget()->style() again. + * Not to mention the performence penalty of having two lookup of rules. + * + * The first instance of QStyleSheetStyle will set globalStyleSheetStyle to itself. The second one + * will notice the globalStyleSheetStyle is not istelf and call its base style directly. + */ +static const QStyleSheetStyle *globalStyleSheetStyle = 0; +class QStyleSheetStyleRecursionGuard +{ + public: + QStyleSheetStyleRecursionGuard(const QStyleSheetStyle *that) + : guarded(globalStyleSheetStyle == 0) + { + if (guarded) globalStyleSheetStyle = that; + } + ~QStyleSheetStyleRecursionGuard() { if (guarded) globalStyleSheetStyle = 0; } + bool guarded; +}; +#define RECURSION_GUARD(RETURN) \ + if (globalStyleSheetStyle != 0 && globalStyleSheetStyle != this) { RETURN; } \ + QStyleSheetStyleRecursionGuard recursion_guard(this); + +#define ceil(x) ((int)(x) + ((x) > 0 && (x) != (int)(x))) + +enum PseudoElement { + PseudoElement_None, + PseudoElement_DownArrow, + PseudoElement_UpArrow, + PseudoElement_LeftArrow, + PseudoElement_RightArrow, + PseudoElement_Indicator, + PseudoElement_ExclusiveIndicator, + PseudoElement_PushButtonMenuIndicator, + PseudoElement_ComboBoxDropDown, + PseudoElement_ComboBoxArrow, + PseudoElement_Item, + PseudoElement_SpinBoxUpButton, + PseudoElement_SpinBoxUpArrow, + PseudoElement_SpinBoxDownButton, + PseudoElement_SpinBoxDownArrow, + PseudoElement_GroupBoxTitle, + PseudoElement_GroupBoxIndicator, + PseudoElement_ToolButtonMenu, + PseudoElement_ToolButtonMenuArrow, + PseudoElement_ToolButtonDownArrow, + PseudoElement_ToolBoxTab, + PseudoElement_ScrollBarSlider, + PseudoElement_ScrollBarAddPage, + PseudoElement_ScrollBarSubPage, + PseudoElement_ScrollBarAddLine, + PseudoElement_ScrollBarSubLine, + PseudoElement_ScrollBarFirst, + PseudoElement_ScrollBarLast, + PseudoElement_ScrollBarUpArrow, + PseudoElement_ScrollBarDownArrow, + PseudoElement_ScrollBarLeftArrow, + PseudoElement_ScrollBarRightArrow, + PseudoElement_SplitterHandle, + PseudoElement_ToolBarHandle, + PseudoElement_ToolBarSeparator, + PseudoElement_MenuScroller, + PseudoElement_MenuTearoff, + PseudoElement_MenuCheckMark, + PseudoElement_MenuSeparator, + PseudoElement_MenuIcon, + PseudoElement_MenuRightArrow, + PseudoElement_TreeViewBranch, + PseudoElement_HeaderViewSection, + PseudoElement_HeaderViewUpArrow, + PseudoElement_HeaderViewDownArrow, + PseudoElement_ProgressBarChunk, + PseudoElement_TabBarTab, + PseudoElement_TabBarScroller, + PseudoElement_TabBarTear, + PseudoElement_SliderGroove, + PseudoElement_SliderHandle, + PseudoElement_SliderAddPage, + PseudoElement_SliderSubPage, + PseudoElement_SliderTickmark, + PseudoElement_TabWidgetPane, + PseudoElement_TabWidgetTabBar, + PseudoElement_TabWidgetLeftCorner, + PseudoElement_TabWidgetRightCorner, + PseudoElement_DockWidgetTitle, + PseudoElement_DockWidgetCloseButton, + PseudoElement_DockWidgetFloatButton, + PseudoElement_DockWidgetSeparator, + PseudoElement_MdiCloseButton, + PseudoElement_MdiMinButton, + PseudoElement_MdiNormalButton, + PseudoElement_TitleBar, + PseudoElement_TitleBarCloseButton, + PseudoElement_TitleBarMinButton, + PseudoElement_TitleBarMaxButton, + PseudoElement_TitleBarShadeButton, + PseudoElement_TitleBarUnshadeButton, + PseudoElement_TitleBarNormalButton, + PseudoElement_TitleBarContextHelpButton, + PseudoElement_TitleBarSysMenu, + PseudoElement_ViewItem, + PseudoElement_ViewItemIcon, + PseudoElement_ViewItemText, + PseudoElement_ViewItemIndicator, + PseudoElement_ScrollAreaCorner, + PseudoElement_TabBarTabCloseButton, + NumPseudoElements +}; + +struct PseudoElementInfo { + QStyle::SubControl subControl; + const char *name; +}; + +static const PseudoElementInfo knownPseudoElements[NumPseudoElements] = { + { QStyle::SC_None, "" }, + { QStyle::SC_None, "down-arrow" }, + { QStyle::SC_None, "up-arrow" }, + { QStyle::SC_None, "left-arrow" }, + { QStyle::SC_None, "right-arrow" }, + { QStyle::SC_None, "indicator" }, + { QStyle::SC_None, "indicator" }, + { QStyle::SC_None, "menu-indicator" }, + { QStyle::SC_ComboBoxArrow, "drop-down" }, + { QStyle::SC_ComboBoxArrow, "down-arrow" }, + { QStyle::SC_None, "item" }, + { QStyle::SC_SpinBoxUp, "up-button" }, + { QStyle::SC_SpinBoxUp, "up-arrow" }, + { QStyle::SC_SpinBoxDown, "down-button" }, + { QStyle::SC_SpinBoxDown, "down-arrow" }, + { QStyle::SC_GroupBoxLabel, "title" }, + { QStyle::SC_GroupBoxCheckBox, "indicator" }, + { QStyle::SC_ToolButtonMenu, "menu-button" }, + { QStyle::SC_ToolButtonMenu, "menu-arrow" }, + { QStyle::SC_None, "menu-indicator" }, + { QStyle::SC_None, "tab" }, + { QStyle::SC_ScrollBarSlider, "handle" }, + { QStyle::SC_ScrollBarAddPage, "add-page" }, + { QStyle::SC_ScrollBarSubPage, "sub-page" }, + { QStyle::SC_ScrollBarAddLine, "add-line" }, + { QStyle::SC_ScrollBarSubLine, "sub-line" }, + { QStyle::SC_ScrollBarFirst, "first" }, + { QStyle::SC_ScrollBarLast, "last" }, + { QStyle::SC_ScrollBarSubLine, "up-arrow" }, + { QStyle::SC_ScrollBarAddLine, "down-arrow" }, + { QStyle::SC_ScrollBarSubLine, "left-arrow" }, + { QStyle::SC_ScrollBarAddLine, "right-arrow" }, + { QStyle::SC_None, "handle" }, + { QStyle::SC_None, "handle" }, + { QStyle::SC_None, "separator" }, + { QStyle::SC_None, "scroller" }, + { QStyle::SC_None, "tearoff" }, + { QStyle::SC_None, "indicator" }, + { QStyle::SC_None, "separator" }, + { QStyle::SC_None, "icon" }, + { QStyle::SC_None, "right-arrow" }, + { QStyle::SC_None, "branch" }, + { QStyle::SC_None, "section" }, + { QStyle::SC_None, "down-arrow" }, + { QStyle::SC_None, "up-arrow" }, + { QStyle::SC_None, "chunk" }, + { QStyle::SC_None, "tab" }, + { QStyle::SC_None, "scroller" }, + { QStyle::SC_None, "tear" }, + { QStyle::SC_SliderGroove, "groove" }, + { QStyle::SC_SliderHandle, "handle" }, + { QStyle::SC_None, "add-page" }, + { QStyle::SC_None, "sub-page"}, + { QStyle::SC_SliderTickmarks, "tick-mark" }, + { QStyle::SC_None, "pane" }, + { QStyle::SC_None, "tab-bar" }, + { QStyle::SC_None, "left-corner" }, + { QStyle::SC_None, "right-corner" }, + { QStyle::SC_None, "title" }, + { QStyle::SC_None, "close-button" }, + { QStyle::SC_None, "float-button" }, + { QStyle::SC_None, "separator" }, + { QStyle::SC_MdiCloseButton, "close-button" }, + { QStyle::SC_MdiMinButton, "minimize-button" }, + { QStyle::SC_MdiNormalButton, "normal-button" }, + { QStyle::SC_TitleBarLabel, "title" }, + { QStyle::SC_TitleBarCloseButton, "close-button" }, + { QStyle::SC_TitleBarMinButton, "minimize-button" }, + { QStyle::SC_TitleBarMaxButton, "maximize-button" }, + { QStyle::SC_TitleBarShadeButton, "shade-button" }, + { QStyle::SC_TitleBarUnshadeButton, "unshade-button" }, + { QStyle::SC_TitleBarNormalButton, "normal-button" }, + { QStyle::SC_TitleBarContextHelpButton, "contexthelp-button" }, + { QStyle::SC_TitleBarSysMenu, "sys-menu" }, + { QStyle::SC_None, "item" }, + { QStyle::SC_None, "icon" }, + { QStyle::SC_None, "text" }, + { QStyle::SC_None, "indicator" }, + { QStyle::SC_None, "corner" }, + { QStyle::SC_None, "close-button" }, +}; + + +struct QStyleSheetBorderImageData : public QSharedData +{ + QStyleSheetBorderImageData() + : horizStretch(QCss::TileMode_Unknown), vertStretch(QCss::TileMode_Unknown) + { + for (int i = 0; i < 4; i++) + cuts[i] = -1; + } + int cuts[4]; + QPixmap pixmap; + QImage image; + QCss::TileMode horizStretch, vertStretch; +}; + +struct QStyleSheetBackgroundData : public QSharedData +{ + QStyleSheetBackgroundData(const QBrush& b, const QPixmap& p, QCss::Repeat r, + Qt::Alignment a, QCss::Origin o, Attachment t, QCss::Origin c) + : brush(b), pixmap(p), repeat(r), position(a), origin(o), attachment(t), clip(c) { } + + bool isTransparent() const { + if (brush.style() != Qt::NoBrush) + return !brush.isOpaque(); + return pixmap.isNull() ? false : pixmap.hasAlpha(); + } + QBrush brush; + QPixmap pixmap; + QCss::Repeat repeat; + Qt::Alignment position; + QCss::Origin origin; + QCss::Attachment attachment; + QCss::Origin clip; +}; + +struct QStyleSheetBorderData : public QSharedData +{ + QStyleSheetBorderData() : bi(0) + { + for (int i = 0; i < 4; i++) { + borders[i] = 0; + styles[i] = QCss::BorderStyle_None; + } + } + + QStyleSheetBorderData(int *b, QBrush *c, QCss::BorderStyle *s, QSize *r) : bi(0) + { + for (int i = 0; i < 4; i++) { + borders[i] = b[i]; + styles[i] = s[i]; + colors[i] = c[i]; + radii[i] = r[i]; + } + } + + int borders[4]; + QBrush colors[4]; + QCss::BorderStyle styles[4]; + QSize radii[4]; // topleft, topright, bottomleft, bottomright + + const QStyleSheetBorderImageData *borderImage() const + { return bi; } + bool hasBorderImage() const { return bi!=0; } + + QSharedDataPointer<QStyleSheetBorderImageData> bi; + + bool isOpaque() const + { + for (int i = 0; i < 4; i++) { + if (styles[i] == QCss::BorderStyle_Native || styles[i] == QCss::BorderStyle_None) + continue; + if (styles[i] >= QCss::BorderStyle_Dotted && styles[i] <= QCss::BorderStyle_DotDotDash + && styles[i] != BorderStyle_Solid) + return false; + if (!colors[i].isOpaque()) + return false; + if (!radii[i].isEmpty()) + return false; + } + if (bi != 0 && bi->pixmap.hasAlpha()) + return false; + return true; + } +}; + + +struct QStyleSheetOutlineData : public QStyleSheetBorderData +{ + QStyleSheetOutlineData() + { + for (int i = 0; i < 4; i++) { + offsets[i] = 0; + } + } + + QStyleSheetOutlineData(int *b, QBrush *c, QCss::BorderStyle *s, QSize *r, int *o) + : QStyleSheetBorderData(b, c, s, r) + { + for (int i = 0; i < 4; i++) { + offsets[i] = o[i]; + } + } + + int offsets[4]; +}; + +struct QStyleSheetBoxData : public QSharedData +{ + QStyleSheetBoxData(int *m, int *p, int s) : spacing(s) + { + for (int i = 0; i < 4; i++) { + margins[i] = m[i]; + paddings[i] = p[i]; + } + } + + int margins[4]; + int paddings[4]; + + int spacing; +}; + +struct QStyleSheetPaletteData : public QSharedData +{ + QStyleSheetPaletteData(const QBrush &fg, const QBrush &sfg, const QBrush &sbg, + const QBrush &abg) + : foreground(fg), selectionForeground(sfg), selectionBackground(sbg), + alternateBackground(abg) { } + + QBrush foreground; + QBrush selectionForeground; + QBrush selectionBackground; + QBrush alternateBackground; +}; + +struct QStyleSheetGeometryData : public QSharedData +{ + QStyleSheetGeometryData(int w, int h, int minw, int minh, int maxw, int maxh) + : minWidth(minw), minHeight(minh), width(w), height(h), maxWidth(maxw), maxHeight(maxh) { } + + int minWidth, minHeight, width, height, maxWidth, maxHeight; +}; + +struct QStyleSheetPositionData : public QSharedData +{ + QStyleSheetPositionData(int l, int t, int r, int b, Origin o, Qt::Alignment p, QCss::PositionMode m, Qt::Alignment a = 0) + : left(l), top(t), bottom(b), right(r), origin(o), position(p), mode(m), textAlignment(a) { } + + int left, top, bottom, right; + Origin origin; + Qt::Alignment position; + QCss::PositionMode mode; + Qt::Alignment textAlignment; +}; + +struct QStyleSheetImageData : public QSharedData +{ + QStyleSheetImageData(const QIcon &i, Qt::Alignment a, const QSize &sz) + : icon(i), alignment(a), size(sz) { } + + QIcon icon; + Qt::Alignment alignment; + QSize size; +}; + +class QRenderRule +{ +public: + QRenderRule() : features(0), hasFont(false), pal(0), b(0), bg(0), bd(0), ou(0), geo(0), p(0), img(0), clipset(0) { } + QRenderRule(const QVector<QCss::Declaration> &, const QWidget *); + ~QRenderRule() { } + + QRect borderRect(const QRect &r) const; + QRect outlineRect(const QRect &r) const; + QRect paddingRect(const QRect &r) const; + QRect contentsRect(const QRect &r) const; + + enum { Margin = 1, Border = 2, Padding = 4, All=Margin|Border|Padding }; + QRect boxRect(const QRect &r, int flags = All) const; + QSize boxSize(const QSize &s, int flags = All) const; + QRect originRect(const QRect &rect, Origin origin) const; + + QPainterPath borderClip(QRect rect); + void drawBorder(QPainter *, const QRect&); + void drawOutline(QPainter *, const QRect&); + void drawBorderImage(QPainter *, const QRect&); + void drawBackground(QPainter *, const QRect&, const QPoint& = QPoint(0, 0)); + void drawBackgroundImage(QPainter *, const QRect&, QPoint = QPoint(0, 0)); + void drawFrame(QPainter *, const QRect&); + void drawImage(QPainter *p, const QRect &rect); + void drawRule(QPainter *, const QRect&); + void configurePalette(QPalette *, QPalette::ColorGroup, const QWidget *, bool); + void configurePalette(QPalette *p, QPalette::ColorRole fr, QPalette::ColorRole br); + + const QStyleSheetPaletteData *palette() const { return pal; } + const QStyleSheetBoxData *box() const { return b; } + const QStyleSheetBackgroundData *background() const { return bg; } + const QStyleSheetBorderData *border() const { return bd; } + const QStyleSheetOutlineData *outline() const { return ou; } + const QStyleSheetGeometryData *geometry() const { return geo; } + const QStyleSheetPositionData *position() const { return p; } + + bool hasPalette() const { return pal != 0; } + bool hasBackground() const { return bg != 0 && (!bg->pixmap.isNull() || bg->brush.style() != Qt::NoBrush); } + bool hasGradientBackground() const { return bg && bg->brush.style() >= Qt::LinearGradientPattern + && bg->brush.style() <= Qt::ConicalGradientPattern; } + + bool hasNativeBorder() const { + return bd == 0 + || (!bd->hasBorderImage() && bd->styles[0] == BorderStyle_Native); + } + + bool hasNativeOutline() const { + return (ou == 0 + || (!ou->hasBorderImage() && ou->styles[0] == BorderStyle_Native)); + } + + bool baseStyleCanDraw() const { + if (!hasBackground() || (background()->brush.style() == Qt::NoBrush && bg->pixmap.isNull())) + return true; + if (bg && !bg->pixmap.isNull()) + return false; + if (hasGradientBackground()) + return features & StyleFeature_BackgroundGradient; + return features & StyleFeature_BackgroundColor; + } + + bool hasBox() const { return b != 0; } + bool hasBorder() const { return bd != 0; } + bool hasOutline() const { return ou != 0; } + bool hasPosition() const { return p != 0; } + bool hasGeometry() const { return geo != 0; } + bool hasDrawable() const { return !hasNativeBorder() || hasBackground() || hasImage(); } + bool hasImage() const { return img != 0; } + + QSize minimumContentsSize() const + { return geo ? QSize(geo->minWidth, geo->minHeight) : QSize(0, 0); } + QSize minimumSize() const + { return boxSize(minimumContentsSize()); } + + QSize contentsSize() const + { return geo ? QSize(geo->width, geo->height) + : ((img && img->size.isValid()) ? img->size : QSize()); } + QSize contentsSize(const QSize &sz) const + { + QSize csz = contentsSize(); + if (csz.width() == -1) csz.setWidth(sz.width()); + if (csz.height() == -1) csz.setHeight(sz.height()); + return csz; + } + bool hasContentsSize() const + { return (geo && (geo->width != -1 || geo->height != -1)) || (img && img->size.isValid()); } + + QSize size() const { return boxSize(contentsSize()); } + QSize size(const QSize &sz) const { return boxSize(contentsSize(sz)); } + QSize adjustSize(const QSize &sz) + { + if (!geo) + return sz; + QSize csz = contentsSize(); + if (csz.width() == -1) csz.setWidth(sz.width()); + if (csz.height() == -1) csz.setHeight(sz.height()); + if (geo->maxWidth != -1 && csz.width() > geo->maxWidth) csz.setWidth(geo->maxWidth); + if (geo->maxHeight != -1 && csz.height() > geo->maxHeight) csz.setHeight(geo->maxHeight); + csz=csz.expandedTo(QSize(geo->minWidth, geo->minHeight)); + return csz; + } + + int features; + QBrush defaultBackground; + QFont font; + bool hasFont; + + QHash<QString, QVariant> styleHints; + bool hasStyleHint(const QString& sh) const { return styleHints.contains(sh); } + QVariant styleHint(const QString& sh) const { return styleHints.value(sh); } + + void fixupBorder(int); + + QSharedDataPointer<QStyleSheetPaletteData> pal; + QSharedDataPointer<QStyleSheetBoxData> b; + QSharedDataPointer<QStyleSheetBackgroundData> bg; + QSharedDataPointer<QStyleSheetBorderData> bd; + QSharedDataPointer<QStyleSheetOutlineData> ou; + QSharedDataPointer<QStyleSheetGeometryData> geo; + QSharedDataPointer<QStyleSheetPositionData> p; + QSharedDataPointer<QStyleSheetImageData> img; + + // Shouldn't be here + void setClip(QPainter *p, const QRect &rect); + void unsetClip(QPainter *); + int clipset; + QPainterPath clipPath; +}; + +/////////////////////////////////////////////////////////////////////////////////////////// +static const char *knownStyleHints[] = { + "activate-on-singleclick", + "alignment", + "arrow-keys-navigate-into-children", + "backward-icon", + "button-layout", + "cd-icon", + "combobox-list-mousetracking", + "combobox-popup", + "computer-icon", + "desktop-icon", + "dialog-apply-icon", + "dialog-cancel-icon", + "dialog-close-icon", + "dialog-discard-icon", + "dialog-help-icon", + "dialog-no-icon", + "dialog-ok-icon", + "dialog-open-icon", + "dialog-reset-icon", + "dialog-save-icon", + "dialog-yes-icon", + "dialogbuttonbox-buttons-have-icons", + "directory-closed-icon", + "directory-icon", + "directory-link-icon", + "directory-open-icon", + "dither-disable-text", + "dockwidget-close-icon", + "downarrow-icon", + "dvd-icon", + "etch-disabled-text", + "file-icon", + "file-link-icon", + "filedialog-backward-icon", // unused + "filedialog-contentsview-icon", + "filedialog-detailedview-icon", + "filedialog-end-icon", + "filedialog-infoview-icon", + "filedialog-listview-icon", + "filedialog-new-directory-icon", + "filedialog-parent-directory-icon", + "filedialog-start-icon", + "floppy-icon", + "forward-icon", + "gridline-color", + "harddisk-icon", + "home-icon", + "icon-size", + "leftarrow-icon", + "lineedit-password-character", + "mdi-fill-space-on-maximize", + "menu-scrollable", + "menubar-altkey-navigation", + "menubar-separator", + "messagebox-critical-icon", + "messagebox-information-icon", + "messagebox-question-icon", + "messagebox-text-interaction-flags", + "messagebox-warning-icon", + "mouse-tracking", + "network-icon", + "opacity", + "paint-alternating-row-colors-for-empty-area", + "rightarrow-icon", + "scrollbar-contextmenu", + "scrollbar-leftclick-absolute-position", + "scrollbar-middleclick-absolute-position", + "scrollbar-roll-between-buttons", + "scrollbar-scroll-when-pointer-leaves-control", + "scrollview-frame-around-contents", + "show-decoration-selected", + "spinbox-click-autorepeat-rate", + "spincontrol-disable-on-bounds", + "tabbar-elide-mode", + "tabbar-prefer-no-arrows", + "titlebar-close-icon", + "titlebar-contexthelp-icon", + "titlebar-maximize-icon", + "titlebar-menu-icon", + "titlebar-minimize-icon", + "titlebar-normal-icon", + "titlebar-shade-icon", + "titlebar-unshade-icon", + "toolbutton-popup-delay", + "trash-icon", + "uparrow-icon" +}; + +static const int numKnownStyleHints = sizeof(knownStyleHints)/sizeof(knownStyleHints[0]); + +static QList<QVariant> subControlLayout(const QString& layout) +{ + QList<QVariant> buttons; + for (int i = 0; i < layout.count(); i++) { + int button = layout[i].toAscii(); + switch (button) { + case 'm': + buttons.append(PseudoElement_MdiMinButton); + buttons.append(PseudoElement_TitleBarMinButton); + break; + case 'M': + buttons.append(PseudoElement_TitleBarMaxButton); + break; + case 'X': + buttons.append(PseudoElement_MdiCloseButton); + buttons.append(PseudoElement_TitleBarCloseButton); + break; + case 'N': + buttons.append(PseudoElement_MdiNormalButton); + buttons.append(PseudoElement_TitleBarNormalButton); + break; + case 'I': + buttons.append(PseudoElement_TitleBarSysMenu); + break; + case 'T': + buttons.append(PseudoElement_TitleBar); + break; + case 'H': + buttons.append(PseudoElement_TitleBarContextHelpButton); + break; + case 'S': + buttons.append(PseudoElement_TitleBarShadeButton); + break; + default: + buttons.append(button); + break; + } + } + return buttons; +} + +namespace { + struct ButtonInfo { + QRenderRule rule; + int element; + int offset; + int where; + int width; + }; +} + +QHash<QStyle::SubControl, QRect> QStyleSheetStyle::titleBarLayout(const QWidget *w, const QStyleOptionTitleBar *tb) const +{ + QHash<QStyle::SubControl, QRect> layoutRects; + const bool isMinimized = tb->titleBarState & Qt::WindowMinimized; + const bool isMaximized = tb->titleBarState & Qt::WindowMaximized; + QRenderRule subRule = renderRule(w, tb); + QRect cr = subRule.contentsRect(tb->rect); + QList<QVariant> layout = subRule.styleHint(QLatin1String("button-layout")).toList(); + if (layout.isEmpty()) + layout = subControlLayout(QLatin1String("I(T)HSmMX")); + + int offsets[3] = { 0, 0, 0 }; + enum Where { Left, Right, Center, NoWhere } where = Left; + QList<ButtonInfo> infos; + for (int i = 0; i < layout.count(); i++) { + ButtonInfo info; + info.element = layout[i].toInt(); + if (info.element == '(') { + where = Center; + } else if (info.element == ')') { + where = Right; + } else { + switch (info.element) { + case PseudoElement_TitleBar: + if (!(tb->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint))) + continue; + break; + case PseudoElement_TitleBarContextHelpButton: + if (!(tb->titleBarFlags & Qt::WindowContextHelpButtonHint)) + continue; + break; + case PseudoElement_TitleBarMinButton: + if (!(tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + continue; + if (isMinimized) + info.element = PseudoElement_TitleBarNormalButton; + break; + case PseudoElement_TitleBarMaxButton: + if (!(tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + continue; + if (isMaximized) + info.element = PseudoElement_TitleBarNormalButton; + break; + case PseudoElement_TitleBarShadeButton: + if (!(tb->titleBarFlags & Qt::WindowShadeButtonHint)) + continue; + if (isMinimized) + info.element = PseudoElement_TitleBarUnshadeButton; + break; + case PseudoElement_TitleBarCloseButton: + case PseudoElement_TitleBarSysMenu: + if (!(tb->titleBarFlags & Qt::WindowSystemMenuHint)) + continue; + break; + default: + continue; + } + if (info.element == PseudoElement_TitleBar) { + info.width = tb->fontMetrics.width(tb->text) + 6; + subRule.geo = new QStyleSheetGeometryData(info.width, tb->fontMetrics.height(), -1, -1, -1, -1); + } else { + subRule = renderRule(w, tb, info.element); + info.width = subRule.size().width(); + } + info.rule = subRule; + info.offset = offsets[where]; + info.where = where; + infos.append(info); + + offsets[where] += info.width; + } + } + + for (int i = 0; i < infos.count(); i++) { + ButtonInfo info = infos[i]; + QRect lr = cr; + switch (info.where) { + case Center: { + lr.setLeft(cr.left() + offsets[Left]); + lr.setRight(cr.right() - offsets[Right]); + QRect r(0, 0, offsets[Center], lr.height()); + r.moveCenter(lr.center()); + r.setLeft(r.left()+info.offset); + r.setWidth(info.width); + lr = r; + break; } + case Left: + lr.translate(info.offset, 0); + lr.setWidth(info.width); + break; + case Right: + lr.moveLeft(cr.right() + 1 - offsets[Right] + info.offset); + lr.setWidth(info.width); + break; + default: + break; + } + QStyle::SubControl control = knownPseudoElements[info.element].subControl; + layoutRects[control] = positionRect(w, info.rule, info.element, lr, tb->direction); + } + + return layoutRects; +} + +static QStyle::StandardPixmap subControlIcon(int pe) +{ + switch (pe) { + case PseudoElement_MdiCloseButton: return QStyle::SP_TitleBarCloseButton; + case PseudoElement_MdiMinButton: return QStyle::SP_TitleBarMinButton; + case PseudoElement_MdiNormalButton: return QStyle::SP_TitleBarNormalButton; + case PseudoElement_TitleBarCloseButton: return QStyle::SP_TitleBarCloseButton; + case PseudoElement_TitleBarMinButton: return QStyle::SP_TitleBarMinButton; + case PseudoElement_TitleBarMaxButton: return QStyle::SP_TitleBarMaxButton; + case PseudoElement_TitleBarShadeButton: return QStyle::SP_TitleBarShadeButton; + case PseudoElement_TitleBarUnshadeButton: return QStyle::SP_TitleBarUnshadeButton; + case PseudoElement_TitleBarNormalButton: return QStyle::SP_TitleBarNormalButton; + case PseudoElement_TitleBarContextHelpButton: return QStyle::SP_TitleBarContextHelpButton; + default: break; + } + return QStyle::SP_CustomBase; +} + +QRenderRule::QRenderRule(const QVector<Declaration> &declarations, const QWidget *widget) +: features(0), hasFont(false), pal(0), b(0), bg(0), bd(0), ou(0), geo(0), p(0), img(0), clipset(0) +{ + QPalette palette = QApplication::palette(); // ###: ideally widget's palette + ValueExtractor v(declarations, palette); + features = v.extractStyleFeatures(); + + int w = -1, h = -1, minw = -1, minh = -1, maxw = -1, maxh = -1; + if (v.extractGeometry(&w, &h, &minw, &minh, &maxw, &maxh)) + geo = new QStyleSheetGeometryData(w, h, minw, minh, maxw, maxh); + + int left = 0, top = 0, right = 0, bottom = 0; + Origin origin = Origin_Unknown; + Qt::Alignment position = 0; + QCss::PositionMode mode = PositionMode_Unknown; + Qt::Alignment textAlignment = 0; + if (v.extractPosition(&left, &top, &right, &bottom, &origin, &position, &mode, &textAlignment)) + p = new QStyleSheetPositionData(left, top, right, bottom, origin, position, mode, textAlignment); + + int margins[4], paddings[4], spacing = -1; + for (int i = 0; i < 4; i++) + margins[i] = paddings[i] = 0; + if (v.extractBox(margins, paddings, &spacing)) + b = new QStyleSheetBoxData(margins, paddings, spacing); + + int borders[4]; + QBrush colors[4]; + QCss::BorderStyle styles[4]; + QSize radii[4]; + for (int i = 0; i < 4; i++) { + borders[i] = 0; + styles[i] = BorderStyle_None; + } + if (v.extractBorder(borders, colors, styles, radii)) + bd = new QStyleSheetBorderData(borders, colors, styles, radii); + + int offsets[4]; + for (int i = 0; i < 4; i++) { + borders[i] = offsets[i] = 0; + styles[i] = BorderStyle_None; + } + if (v.extractOutline(borders, colors, styles, radii, offsets)) + ou = new QStyleSheetOutlineData(borders, colors, styles, radii, offsets); + + QBrush brush; + QString uri; + Repeat repeat = Repeat_XY; + Qt::Alignment alignment = Qt::AlignTop | Qt::AlignLeft; + Attachment attachment = Attachment_Scroll; + origin = Origin_Padding; + Origin clip = Origin_Border; + if (v.extractBackground(&brush, &uri, &repeat, &alignment, &origin, &attachment, &clip)) + bg = new QStyleSheetBackgroundData(brush, QPixmap(uri), repeat, alignment, origin, attachment, clip); + + QBrush sfg, fg; + QBrush sbg, abg; + if (v.extractPalette(&fg, &sfg, &sbg, &abg)) + pal = new QStyleSheetPaletteData(fg, sfg, sbg, abg); + + QIcon icon; + alignment = Qt::AlignCenter; + QSize size; + if (v.extractImage(&icon, &alignment, &size)) + img = new QStyleSheetImageData(icon, alignment, size); + + int adj = -255; + hasFont = v.extractFont(&font, &adj); + +#ifndef QT_NO_TOOLTIP + if (widget && qstrcmp(widget->metaObject()->className(), "QTipLabel") == 0) + palette = QToolTip::palette(); +#endif + + for (int i = 0; i < declarations.count(); i++) { + const Declaration& decl = declarations.at(i); + if (decl.d->propertyId == BorderImage) { + QString uri; + QCss::TileMode horizStretch, vertStretch; + int cuts[4]; + + decl.borderImageValue(&uri, cuts, &horizStretch, &vertStretch); + if (uri.isEmpty() || uri == QLatin1String("none")) { + if (bd && bd->bi) + bd->bi->pixmap = QPixmap(); + } else { + if (!bd) + bd = new QStyleSheetBorderData; + if (!bd->bi) + bd->bi = new QStyleSheetBorderImageData; + + QStyleSheetBorderImageData *bi = bd->bi; + bi->pixmap = QPixmap(uri); + for (int i = 0; i < 4; i++) + bi->cuts[i] = cuts[i]; + bi->horizStretch = horizStretch; + bi->vertStretch = vertStretch; + } + } else if (decl.d->propertyId == QtBackgroundRole) { + if (bg && bg->brush.style() != Qt::NoBrush) + continue; + int role = decl.d->values.at(0).variant.toInt(); + if (role >= Value_FirstColorRole && role <= Value_LastColorRole) + defaultBackground = palette.color((QPalette::ColorRole)(role-Value_FirstColorRole)); + } else if (decl.d->property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive)) { + // intentionally left blank... + } else if (decl.d->propertyId == UnknownProperty) { + bool knownStyleHint = false; + for (int i = 0; i < numKnownStyleHints; i++) { + QLatin1String styleHint(knownStyleHints[i]); + if (decl.d->property.compare(styleHint) == 0) { + QString hintName = QString(styleHint); + QVariant hintValue; + if (hintName.endsWith(QLatin1String("alignment"))) { + hintValue = (int) decl.alignmentValue(); + } else if (hintName.endsWith(QLatin1String("color"))) { + hintValue = (int) decl.colorValue().rgba(); + } else if (hintName.endsWith(QLatin1String("size"))) { + hintValue = decl.sizeValue(); + } else if (hintName.endsWith(QLatin1String("icon"))) { + hintValue = decl.iconValue(); + } else if (hintName == QLatin1String("button-layout") + && decl.d->values.count() != 0 && decl.d->values.at(0).type == Value::String) { + hintValue = subControlLayout(decl.d->values.at(0).variant.toString()); + } else { + int integer; + decl.intValue(&integer); + hintValue = integer; + } + styleHints[decl.d->property] = hintValue; + knownStyleHint = true; + break; + } + } + if (!knownStyleHint) + qDebug("Unknown property %s", qPrintable(decl.d->property)); + } + } + + if (widget) { + QStyleSheetStyle *style = const_cast<QStyleSheetStyle *>(globalStyleSheetStyle); + if (!style) + style = qobject_cast<QStyleSheetStyle *>(widget->style()); + if (style) + fixupBorder(style->nativeFrameWidth(widget)); + + } + if (hasBorder() && border()->hasBorderImage()) + defaultBackground = QBrush(); +} + +QRect QRenderRule::borderRect(const QRect& r) const +{ + if (!hasBox()) + return r; + const int* m = box()->margins; + return r.adjusted(m[LeftEdge], m[TopEdge], -m[RightEdge], -m[BottomEdge]); +} + +QRect QRenderRule::outlineRect(const QRect& r) const +{ + QRect br = borderRect(r); + if (!hasOutline()) + return br; + const int *b = outline()->borders; + return r.adjusted(b[LeftEdge], b[TopEdge], -b[RightEdge], -b[BottomEdge]); +} + +QRect QRenderRule::paddingRect(const QRect& r) const +{ + QRect br = borderRect(r); + if (!hasBorder()) + return br; + const int *b = border()->borders; + return br.adjusted(b[LeftEdge], b[TopEdge], -b[RightEdge], -b[BottomEdge]); +} + +QRect QRenderRule::contentsRect(const QRect& r) const +{ + QRect pr = paddingRect(r); + if (!hasBox()) + return pr; + const int *p = box()->paddings; + return pr.adjusted(p[LeftEdge], p[TopEdge], -p[RightEdge], -p[BottomEdge]); +} + +QRect QRenderRule::boxRect(const QRect& cr, int flags) const +{ + QRect r = cr; + if (hasBox()) { + if (flags & Margin) { + const int *m = box()->margins; + r.adjust(-m[LeftEdge], -m[TopEdge], m[RightEdge], m[BottomEdge]); + } + if (flags & Padding) { + const int *p = box()->paddings; + r.adjust(-p[LeftEdge], -p[TopEdge], p[RightEdge], p[BottomEdge]); + } + } + if (hasBorder() && (flags & Border)) { + const int *b = border()->borders; + r.adjust(-b[LeftEdge], -b[TopEdge], b[RightEdge], b[BottomEdge]); + } + return r; +} + +QSize QRenderRule::boxSize(const QSize &cs, int flags) const +{ + QSize bs = boxRect(QRect(QPoint(0, 0), cs), flags).size(); + if (cs.width() < 0) bs.setWidth(-1); + if (cs.height() < 0) bs.setHeight(-1); + return bs; +} + +void QRenderRule::fixupBorder(int nativeWidth) +{ + if (bd == 0) + return; + + if (!bd->hasBorderImage() || bd->bi->pixmap.isNull()) { + bd->bi = 0; + // ignore the color, border of edges that have none border-style + QBrush color = pal ? pal->foreground : QBrush(); + const bool hasRadius = bd->radii[0].isValid() || bd->radii[1].isValid() + || bd->radii[2].isValid() || bd->radii[3].isValid(); + for (int i = 0; i < 4; i++) { + if ((bd->styles[i] == BorderStyle_Native) && hasRadius) + bd->styles[i] = BorderStyle_None; + + switch (bd->styles[i]) { + case BorderStyle_None: + // border-style: none forces width to be 0 + bd->colors[i] = QBrush(); + bd->borders[i] = 0; + break; + case BorderStyle_Native: + if (bd->borders[i] == 0) + bd->borders[i] = nativeWidth; + // intentional fall through + default: + if (!bd->colors[i].style() != Qt::NoBrush) // auto-acquire 'color' + bd->colors[i] = color; + break; + } + } + + return; + } + + // inspect the border image + QStyleSheetBorderImageData *bi = bd->bi; + if (bi->cuts[0] == -1) { + for (int i = 0; i < 4; i++) // assume, cut = border + bi->cuts[i] = int(border()->borders[i]); + } +} + +void QRenderRule::drawBorderImage(QPainter *p, const QRect& rect) +{ + setClip(p, rect); + static const Qt::TileRule tileMode2TileRule[] = { + Qt::StretchTile, Qt::RoundTile, Qt::StretchTile, Qt::RepeatTile, Qt::StretchTile }; + + const QStyleSheetBorderImageData *borderImageData = border()->borderImage(); + const int *targetBorders = border()->borders; + const int *sourceBorders = borderImageData->cuts; + QMargins sourceMargins(sourceBorders[LeftEdge], sourceBorders[TopEdge], + sourceBorders[RightEdge], sourceBorders[BottomEdge]); + QMargins targetMargins(targetBorders[LeftEdge], targetBorders[TopEdge], + targetBorders[RightEdge], targetBorders[BottomEdge]); + + bool wasSmoothPixmapTransform = p->renderHints() & QPainter::SmoothPixmapTransform; + p->setRenderHint(QPainter::SmoothPixmapTransform); + qDrawBorderPixmap(p, rect, targetMargins, borderImageData->pixmap, + QRect(QPoint(), borderImageData->pixmap.size()), sourceMargins, + QTileRules(tileMode2TileRule[borderImageData->horizStretch], tileMode2TileRule[borderImageData->vertStretch])); + p->setRenderHint(QPainter::SmoothPixmapTransform, wasSmoothPixmapTransform); + unsetClip(p); +} + +QRect QRenderRule::originRect(const QRect &rect, Origin origin) const +{ + switch (origin) { + case Origin_Padding: + return paddingRect(rect); + case Origin_Border: + return borderRect(rect); + case Origin_Content: + return contentsRect(rect); + case Origin_Margin: + default: + return rect; + } +} + +void QRenderRule::drawBackgroundImage(QPainter *p, const QRect &rect, QPoint off) +{ + if (!hasBackground()) + return; + + const QPixmap& bgp = background()->pixmap; + if (bgp.isNull()) + return; + + setClip(p, borderRect(rect)); + + if (background()->origin != background()->clip) { + p->save(); + p->setClipRect(originRect(rect, background()->clip), Qt::IntersectClip); + } + + if (background()->attachment == Attachment_Fixed) + off = QPoint(0, 0); + + QRect r = originRect(rect, background()->origin); + QRect aligned = QStyle::alignedRect(Qt::LeftToRight, background()->position, bgp.size(), r); + QRect inter = aligned.translated(-off).intersected(r); + + switch (background()->repeat) { + case Repeat_Y: + p->drawTiledPixmap(inter.x(), r.y(), inter.width(), r.height(), bgp, + inter.x() - aligned.x() + off.x(), + bgp.height() - int(aligned.y() - r.y()) % bgp.height() + off.y()); + break; + case Repeat_X: + p->drawTiledPixmap(r.x(), inter.y(), r.width(), inter.height(), bgp, + bgp.width() - int(aligned.x() - r.x())%bgp.width() + off.x(), + inter.y() - aligned.y() + off.y()); + break; + case Repeat_XY: + p->drawTiledPixmap(r, bgp, + QPoint(bgp.width() - int(aligned.x() - r.x())% bgp.width() + off.x(), + bgp.height() - int(aligned.y() - r.y())%bgp.height() + off.y())); + break; + case Repeat_None: + default: + p->drawPixmap(inter.x(), inter.y(), bgp, inter.x() - aligned.x() + off.x(), + inter.y() - aligned.y() + off.y(), inter.width(), inter.height()); + break; + } + + + if (background()->origin != background()->clip) + p->restore(); + + unsetClip(p); +} + +void QRenderRule::drawOutline(QPainter *p, const QRect &rect) +{ + if (!hasOutline()) + return; + + bool wasAntialiased = p->renderHints() & QPainter::Antialiasing; + p->setRenderHint(QPainter::Antialiasing); + qDrawBorder(p, rect, ou->styles, ou->borders, ou->colors, ou->radii); + p->setRenderHint(QPainter::Antialiasing, wasAntialiased); +} + +void QRenderRule::drawBorder(QPainter *p, const QRect& rect) +{ + if (!hasBorder()) + return; + + if (border()->hasBorderImage()) { + drawBorderImage(p, rect); + return; + } + + bool wasAntialiased = p->renderHints() & QPainter::Antialiasing; + p->setRenderHint(QPainter::Antialiasing); + qDrawBorder(p, rect, bd->styles, bd->borders, bd->colors, bd->radii); + p->setRenderHint(QPainter::Antialiasing, wasAntialiased); +} + +QPainterPath QRenderRule::borderClip(QRect r) +{ + if (!hasBorder()) + return QPainterPath(); + + QSize tlr, trr, blr, brr; + qNormalizeRadii(r, bd->radii, &tlr, &trr, &blr, &brr); + if (tlr.isNull() && trr.isNull() && blr.isNull() && brr.isNull()) + return QPainterPath(); + + const QRectF rect(r); + const int *borders = border()->borders; + QPainterPath path; + qreal curY = rect.y() + borders[TopEdge]/2.0; + path.moveTo(rect.x() + tlr.width(), curY); + path.lineTo(rect.right() - trr.width(), curY); + qreal curX = rect.right() - borders[RightEdge]/2.0; + path.arcTo(curX - 2*trr.width() + borders[RightEdge], curY, + trr.width()*2 - borders[RightEdge], trr.height()*2 - borders[TopEdge], 90, -90); + + path.lineTo(curX, rect.bottom() - brr.height()); + curY = rect.bottom() - borders[BottomEdge]/2.0; + path.arcTo(curX - 2*brr.width() + borders[RightEdge], curY - 2*brr.height() + borders[BottomEdge], + brr.width()*2 - borders[RightEdge], brr.height()*2 - borders[BottomEdge], 0, -90); + + path.lineTo(rect.x() + blr.width(), curY); + curX = rect.left() + borders[LeftEdge]/2.0; + path.arcTo(curX, rect.bottom() - 2*blr.height() + borders[BottomEdge]/2, + blr.width()*2 - borders[LeftEdge], blr.height()*2 - borders[BottomEdge], 270, -90); + + path.lineTo(curX, rect.top() + tlr.height()); + path.arcTo(curX, rect.top() + borders[TopEdge]/2, + tlr.width()*2 - borders[LeftEdge], tlr.height()*2 - borders[TopEdge], 180, -90); + + path.closeSubpath(); + return path; +} + +/*! \internal + Clip the painter to the border (in case we are using radius border) + */ +void QRenderRule::setClip(QPainter *p, const QRect &rect) +{ + if (clipset++) + return; + clipPath = borderClip(rect); + if (!clipPath.isEmpty()) { + p->save(); + p->setClipPath(clipPath, Qt::IntersectClip); + } +} + +void QRenderRule::unsetClip(QPainter *p) +{ + if (--clipset) + return; + if (!clipPath.isEmpty()) + p->restore(); +} + +void QRenderRule::drawBackground(QPainter *p, const QRect& rect, const QPoint& off) +{ + QBrush brush = hasBackground() ? background()->brush : QBrush(); + if (brush.style() == Qt::NoBrush) + brush = defaultBackground; + + if (brush.style() != Qt::NoBrush) { + Origin origin = hasBackground() ? background()->clip : Origin_Border; + // ### fix for gradients + const QPainterPath &borderPath = borderClip(originRect(rect, origin)); + if (!borderPath.isEmpty()) { + // Drawn intead of being used as clipping path for better visual quality + bool wasAntialiased = p->renderHints() & QPainter::Antialiasing; + p->setRenderHint(QPainter::Antialiasing); + p->fillPath(borderPath, brush); + p->setRenderHint(QPainter::Antialiasing, wasAntialiased); + } else { + p->fillRect(originRect(rect, origin), brush); + } + } + + drawBackgroundImage(p, rect, off); +} + +void QRenderRule::drawFrame(QPainter *p, const QRect& rect) +{ + drawBackground(p, rect); + if (hasBorder()) + drawBorder(p, borderRect(rect)); +} + +void QRenderRule::drawImage(QPainter *p, const QRect &rect) +{ + if (!hasImage()) + return; + img->icon.paint(p, rect, img->alignment); +} + +void QRenderRule::drawRule(QPainter *p, const QRect& rect) +{ + drawFrame(p, rect); + drawImage(p, contentsRect(rect)); +} + +// *shudder* , *horror*, *whoa* <-- what you might feel when you see the functions below +void QRenderRule::configurePalette(QPalette *p, QPalette::ColorRole fr, QPalette::ColorRole br) +{ + if (bg && bg->brush.style() != Qt::NoBrush) { + if (br != QPalette::NoRole) + p->setBrush(br, bg->brush); + p->setBrush(QPalette::Window, bg->brush); + if (bg->brush.style() == Qt::SolidPattern) { + p->setBrush(QPalette::Light, bg->brush.color().lighter(115)); + p->setBrush(QPalette::Midlight, bg->brush.color().lighter(107)); + p->setBrush(QPalette::Dark, bg->brush.color().darker(150)); + p->setBrush(QPalette::Shadow, bg->brush.color().darker(300)); + } + } + + if (!hasPalette()) + return; + + if (pal->foreground.style() != Qt::NoBrush) { + if (fr != QPalette::NoRole) + p->setBrush(fr, pal->foreground); + p->setBrush(QPalette::WindowText, pal->foreground); + p->setBrush(QPalette::Text, pal->foreground); + } + if (pal->selectionBackground.style() != Qt::NoBrush) + p->setBrush(QPalette::Highlight, pal->selectionBackground); + if (pal->selectionForeground.style() != Qt::NoBrush) + p->setBrush(QPalette::HighlightedText, pal->selectionForeground); + if (pal->alternateBackground.style() != Qt::NoBrush) + p->setBrush(QPalette::AlternateBase, pal->alternateBackground); +} + +void QRenderRule::configurePalette(QPalette *p, QPalette::ColorGroup cg, const QWidget *w, bool embedded) +{ + if (bg && bg->brush.style() != Qt::NoBrush) { + p->setBrush(cg, QPalette::Base, bg->brush); // for windows, windowxp + p->setBrush(cg, QPalette::Button, bg->brush); // for plastique + p->setBrush(cg, w->backgroundRole(), bg->brush); + p->setBrush(cg, QPalette::Window, bg->brush); + } + + if (embedded) { + /* For embedded widgets (ComboBox, SpinBox and ScrollArea) we want the embedded widget + * to be transparent when we have a transparent background or border image */ + if ((hasBackground() && background()->isTransparent()) + || (hasBorder() && border()->hasBorderImage() && !border()->borderImage()->pixmap.isNull())) + p->setBrush(cg, w->backgroundRole(), Qt::NoBrush); + } + + if (!hasPalette()) + return; + + if (pal->foreground.style() != Qt::NoBrush) { + p->setBrush(cg, QPalette::ButtonText, pal->foreground); + p->setBrush(cg, w->foregroundRole(), pal->foreground); + p->setBrush(cg, QPalette::WindowText, pal->foreground); + p->setBrush(cg, QPalette::Text, pal->foreground); + } + if (pal->selectionBackground.style() != Qt::NoBrush) + p->setBrush(cg, QPalette::Highlight, pal->selectionBackground); + if (pal->selectionForeground.style() != Qt::NoBrush) + p->setBrush(cg, QPalette::HighlightedText, pal->selectionForeground); + if (pal->alternateBackground.style() != Qt::NoBrush) + p->setBrush(cg, QPalette::AlternateBase, pal->alternateBackground); +} + +/////////////////////////////////////////////////////////////////////////////// +// Style rules +#define WIDGET(x) (static_cast<QWidget *>(x.ptr)) + +static inline QWidget *parentWidget(const QWidget *w) +{ + if(qobject_cast<const QLabel *>(w) && qstrcmp(w->metaObject()->className(), "QTipLabel") == 0) { + QWidget *p = qvariant_cast<QWidget *>(w->property("_q_stylesheet_parent")); + if (p) + return p; + } + return w->parentWidget(); +} + +class QStyleSheetStyleSelector : public StyleSelector +{ +public: + QStyleSheetStyleSelector() { } + + QStringList nodeNames(NodePtr node) const + { + if (isNullNode(node)) + return QStringList(); + const QMetaObject *metaObject = WIDGET(node)->metaObject(); +#ifndef QT_NO_TOOLTIP + if (qstrcmp(metaObject->className(), "QTipLabel") == 0) + return QStringList(QLatin1String("QToolTip")); +#endif + QStringList result; + do { + result += QString::fromLatin1(metaObject->className()).replace(QLatin1Char(':'), QLatin1Char('-')); + metaObject = metaObject->superClass(); + } while (metaObject != 0); + return result; + } + QString attribute(NodePtr node, const QString& name) const + { + if (isNullNode(node)) + return QString(); + + QHash<QString, QString> &cache = m_attributeCache[WIDGET(node)]; + QHash<QString, QString>::const_iterator cacheIt = cache.constFind(name); + if (cacheIt != cache.constEnd()) + return cacheIt.value(); + + QVariant value = WIDGET(node)->property(name.toLatin1()); + if (!value.isValid()) { + if (name == QLatin1String("class")) { + QString className = QString::fromLatin1(WIDGET(node)->metaObject()->className()); + if (className.contains(QLatin1Char(':'))) + className.replace(QLatin1Char(':'), QLatin1Char('-')); + cache[name] = className; + return className; + } else if (name == QLatin1String("style")) { + QStyleSheetStyle *proxy = qobject_cast<QStyleSheetStyle *>(WIDGET(node)->style()); + if (proxy) { + QString styleName = QString::fromLatin1(proxy->baseStyle()->metaObject()->className()); + cache[name] = styleName; + return styleName; + } + } + } + QString valueStr; + if(value.type() == QVariant::StringList || value.type() == QVariant::List) + valueStr = value.toStringList().join(QLatin1String(" ")); + else + valueStr = value.toString(); + cache[name] = valueStr; + return valueStr; + } + bool nodeNameEquals(NodePtr node, const QString& nodeName) const + { + if (isNullNode(node)) + return false; + const QMetaObject *metaObject = WIDGET(node)->metaObject(); +#ifndef QT_NO_TOOLTIP + if (qstrcmp(metaObject->className(), "QTipLabel") == 0) + return nodeName == QLatin1String("QToolTip"); +#endif + do { + const ushort *uc = (const ushort *)nodeName.constData(); + const ushort *e = uc + nodeName.length(); + const uchar *c = (uchar *)metaObject->className(); + while (*c && uc != e && (*uc == *c || (*c == ':' && *uc == '-'))) { + ++uc; + ++c; + } + if (uc == e && !*c) + return true; + metaObject = metaObject->superClass(); + } while (metaObject != 0); + return false; + } + bool hasAttributes(NodePtr) const + { return true; } + QStringList nodeIds(NodePtr node) const + { return isNullNode(node) ? QStringList() : QStringList(WIDGET(node)->objectName()); } + bool isNullNode(NodePtr node) const + { return node.ptr == 0; } + NodePtr parentNode(NodePtr node) const + { NodePtr n; n.ptr = isNullNode(node) ? 0 : parentWidget(WIDGET(node)); return n; } + NodePtr previousSiblingNode(NodePtr) const + { NodePtr n; n.ptr = 0; return n; } + NodePtr duplicateNode(NodePtr node) const + { return node; } + void freeNode(NodePtr) const + { } + +private: + mutable QHash<const QWidget *, QHash<QString, QString> > m_attributeCache; +}; + +QVector<QCss::StyleRule> QStyleSheetStyle::styleRules(const QWidget *w) const +{ + QHash<const QWidget *, QVector<StyleRule> >::const_iterator cacheIt = styleSheetCaches->styleRulesCache.constFind(w); + if (cacheIt != styleSheetCaches->styleRulesCache.constEnd()) + return cacheIt.value(); + + if (!initWidget(w)) { + return QVector<StyleRule>(); + } + + QStyleSheetStyleSelector styleSelector; + + StyleSheet defaultSs; + QHash<const void *, StyleSheet>::const_iterator defaultCacheIt = styleSheetCaches->styleSheetCache.constFind(baseStyle()); + if (defaultCacheIt == styleSheetCaches->styleSheetCache.constEnd()) { + defaultSs = getDefaultStyleSheet(); + QStyle *bs = baseStyle(); + styleSheetCaches->styleSheetCache.insert(bs, defaultSs); + QObject::connect(bs, SIGNAL(destroyed(QObject*)), styleSheetCaches, SLOT(styleDestroyed(QObject*)), Qt::UniqueConnection); + } else { + defaultSs = defaultCacheIt.value(); + } + styleSelector.styleSheets += defaultSs; + + if (!qApp->styleSheet().isEmpty()) { + StyleSheet appSs; + QHash<const void *, StyleSheet>::const_iterator appCacheIt = styleSheetCaches->styleSheetCache.constFind(qApp); + if (appCacheIt == styleSheetCaches->styleSheetCache.constEnd()) { + QString ss = qApp->styleSheet(); + if (ss.startsWith(QLatin1String("file:///"))) + ss.remove(0, 8); + parser.init(ss, qApp->styleSheet() != ss); + if (!parser.parse(&appSs)) + qWarning("Could not parse application stylesheet"); + appSs.origin = StyleSheetOrigin_Inline; + appSs.depth = 1; + styleSheetCaches->styleSheetCache.insert(qApp, appSs); + } else { + appSs = appCacheIt.value(); + } + styleSelector.styleSheets += appSs; + } + + QVector<QCss::StyleSheet> widgetSs; + for (const QWidget *wid = w; wid; wid = parentWidget(wid)) { + if (wid->styleSheet().isEmpty()) + continue; + StyleSheet ss; + QHash<const void *, StyleSheet>::const_iterator widCacheIt = styleSheetCaches->styleSheetCache.constFind(wid); + if (widCacheIt == styleSheetCaches->styleSheetCache.constEnd()) { + parser.init(wid->styleSheet()); + if (!parser.parse(&ss)) { + parser.init(QLatin1String("* {") + wid->styleSheet() + QLatin1Char('}')); + if (!parser.parse(&ss)) + qWarning("Could not parse stylesheet of widget %p", wid); + } + ss.origin = StyleSheetOrigin_Inline; + styleSheetCaches->styleSheetCache.insert(wid, ss); + } else { + ss = widCacheIt.value(); + } + widgetSs.append(ss); + } + + for (int i = 0; i < widgetSs.count(); i++) + widgetSs[i].depth = widgetSs.count() - i + 2; + + styleSelector.styleSheets += widgetSs; + + StyleSelector::NodePtr n; + n.ptr = (void *)w; + QVector<QCss::StyleRule> rules = styleSelector.styleRulesForNode(n); + styleSheetCaches->styleRulesCache.insert(w, rules); + return rules; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Rendering rules +static QVector<Declaration> declarations(const QVector<StyleRule> &styleRules, const QString &part, quint64 pseudoClass = PseudoClass_Unspecified) +{ + QVector<Declaration> decls; + for (int i = 0; i < styleRules.count(); i++) { + const Selector& selector = styleRules.at(i).selectors.at(0); + // Rules with pseudo elements don't cascade. This is an intentional + // diversion for CSS + if (part.compare(selector.pseudoElement(), Qt::CaseInsensitive) != 0) + continue; + quint64 negated = 0; + quint64 cssClass = selector.pseudoClass(&negated); + if ((pseudoClass == PseudoClass_Any) || (cssClass == PseudoClass_Unspecified) + || ((((cssClass & pseudoClass) == cssClass)) && ((negated & pseudoClass) == 0))) + decls += styleRules.at(i).declarations; + } + return decls; +} + +int QStyleSheetStyle::nativeFrameWidth(const QWidget *w) +{ + QStyle *base = baseStyle(); + +#ifndef QT_NO_SPINBOX + if (qobject_cast<const QAbstractSpinBox *>(w)) + return base->pixelMetric(QStyle::PM_SpinBoxFrameWidth, 0, w); +#endif + +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox *>(w)) + return base->pixelMetric(QStyle::PM_ComboBoxFrameWidth, 0, w); +#endif + +#ifndef QT_NO_MENU + if (qobject_cast<const QMenu *>(w)) + return base->pixelMetric(QStyle::PM_MenuPanelWidth, 0, w); +#endif + +#ifndef QT_NO_MENUBAR + if (qobject_cast<const QMenuBar *>(w)) + return base->pixelMetric(QStyle::PM_MenuBarPanelWidth, 0, w); +#endif +#ifndef QT_NO_FRAME + if (const QFrame *frame = qobject_cast<const QFrame *>(w)) { + if (frame->frameShape() == QFrame::NoFrame) + return 0; + } +#endif + + if (qstrcmp(w->metaObject()->className(), "QTipLabel") == 0) + return base->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, 0, w); + + return base->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, w); +} + +static quint64 pseudoClass(QStyle::State state) +{ + quint64 pc = 0; + if (state & QStyle::State_Enabled) { + pc |= PseudoClass_Enabled; + if (state & QStyle::State_MouseOver) + pc |= PseudoClass_Hover; + } else { + pc |= PseudoClass_Disabled; + } + if (state & QStyle::State_Active) + pc |= PseudoClass_Active; + if (state & QStyle::State_Window) + pc |= PseudoClass_Window; + if (state & QStyle::State_Sunken) + pc |= PseudoClass_Pressed; + if (state & QStyle::State_HasFocus) + pc |= PseudoClass_Focus; + if (state & QStyle::State_On) + pc |= (PseudoClass_On | PseudoClass_Checked); + if (state & QStyle::State_Off) + pc |= (PseudoClass_Off | PseudoClass_Unchecked); + if (state & QStyle::State_NoChange) + pc |= PseudoClass_Indeterminate; + if (state & QStyle::State_Selected) + pc |= PseudoClass_Selected; + if (state & QStyle::State_Horizontal) + pc |= PseudoClass_Horizontal; + else + pc |= PseudoClass_Vertical; + if (state & (QStyle::State_Open | QStyle::State_On | QStyle::State_Sunken)) + pc |= PseudoClass_Open; + else + pc |= PseudoClass_Closed; + if (state & QStyle::State_Children) + pc |= PseudoClass_Children; + if (state & QStyle::State_Sibling) + pc |= PseudoClass_Sibling; + if (state & QStyle::State_ReadOnly) + pc |= PseudoClass_ReadOnly; + if (state & QStyle::State_Item) + pc |= PseudoClass_Item; +#ifdef QT_KEYPAD_NAVIGATION + if (state & QStyle::State_HasEditFocus) + pc |= PseudoClass_EditFocus; +#endif + return pc; +} + +static void qt_check_if_internal_widget(const QWidget **w, int *element) +{ +#ifdef QT_NO_DOCKWIDGET + Q_UNUSED(w); + Q_UNUSED(element); +#else + if (*w && qstrcmp((*w)->metaObject()->className(), "QDockWidgetTitleButton") == 0) { + if ((*w)->objectName() == QLatin1String("qt_dockwidget_closebutton")) { + *element = PseudoElement_DockWidgetCloseButton; + } else if ((*w)->objectName() == QLatin1String("qt_dockwidget_floatbutton")) { + *element = PseudoElement_DockWidgetFloatButton; + } + *w = (*w)->parentWidget(); + } +#endif +} + +QRenderRule QStyleSheetStyle::renderRule(const QWidget *w, int element, quint64 state) const +{ + qt_check_if_internal_widget(&w, &element); + QHash<quint64, QRenderRule> &cache = styleSheetCaches->renderRulesCache[w][element]; + QHash<quint64, QRenderRule>::const_iterator cacheIt = cache.constFind(state); + if (cacheIt != cache.constEnd()) + return cacheIt.value(); + + if (!initWidget(w)) + return QRenderRule(); + + quint64 stateMask = 0; + const QVector<StyleRule> rules = styleRules(w); + for (int i = 0; i < rules.count(); i++) { + const Selector& selector = rules.at(i).selectors.at(0); + quint64 negated = 0; + stateMask |= selector.pseudoClass(&negated); + stateMask |= negated; + } + + cacheIt = cache.constFind(state & stateMask); + if (cacheIt != cache.constEnd()) { + const QRenderRule &newRule = cacheIt.value(); + cache[state] = newRule; + return newRule; + } + + + const QString part = QLatin1String(knownPseudoElements[element].name); + QVector<Declaration> decls = declarations(rules, part, state); + QRenderRule newRule(decls, w); + cache[state] = newRule; + if ((state & stateMask) != state) + cache[state&stateMask] = newRule; + return newRule; +} + +QRenderRule QStyleSheetStyle::renderRule(const QWidget *w, const QStyleOption *opt, int pseudoElement) const +{ + quint64 extraClass = 0; + QStyle::State state = opt ? opt->state : QStyle::State(QStyle::State_None); + + if (const QStyleOptionComplex *complex = qstyleoption_cast<const QStyleOptionComplex *>(opt)) { + if (pseudoElement != PseudoElement_None) { + // if not an active subcontrol, just pass enabled/disabled + QStyle::SubControl subControl = knownPseudoElements[pseudoElement].subControl; + + if (!(complex->activeSubControls & subControl)) + state &= (QStyle::State_Enabled | QStyle::State_Horizontal | QStyle::State_HasFocus); + } + + switch (pseudoElement) { + case PseudoElement_ComboBoxDropDown: + case PseudoElement_ComboBoxArrow: + state |= (complex->state & (QStyle::State_On|QStyle::State_ReadOnly)); + break; + case PseudoElement_SpinBoxUpButton: + case PseudoElement_SpinBoxDownButton: + case PseudoElement_SpinBoxUpArrow: + case PseudoElement_SpinBoxDownArrow: +#ifndef QT_NO_SPINBOX + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + bool on = false; + bool up = pseudoElement == PseudoElement_SpinBoxUpButton + || pseudoElement == PseudoElement_SpinBoxUpArrow; + if ((sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) && up) + on = true; + else if ((sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) && !up) + on = true; + state |= (on ? QStyle::State_On : QStyle::State_Off); + } +#endif // QT_NO_SPINBOX + break; + case PseudoElement_GroupBoxTitle: + state |= (complex->state & (QStyle::State_MouseOver | QStyle::State_Sunken)); + break; + case PseudoElement_ToolButtonMenu: + case PseudoElement_ToolButtonMenuArrow: + case PseudoElement_ToolButtonDownArrow: + state |= complex->state & QStyle::State_MouseOver; + if (complex->state & QStyle::State_Sunken || + complex->activeSubControls & QStyle::SC_ToolButtonMenu) + state |= QStyle::State_Sunken; + break; + case PseudoElement_SliderGroove: + state |= complex->state & QStyle::State_MouseOver; + break; + default: + break; + } + + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + // QStyle::State_On is set when the popup is being shown + // Propagate EditField Pressed state + if (pseudoElement == PseudoElement_None + && (complex->activeSubControls & QStyle::SC_ComboBoxEditField) + && (!(state & QStyle::State_MouseOver))) { + state |= QStyle::State_Sunken; + } + + if (!combo->frame) + extraClass |= PseudoClass_Frameless; + if (!combo->editable) + extraClass |= PseudoClass_ReadOnly; + else + extraClass |= PseudoClass_Editable; +#ifndef QT_NO_SPINBOX + } else if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + if (!spin->frame) + extraClass |= PseudoClass_Frameless; +#endif // QT_NO_SPINBOX + } else if (const QStyleOptionGroupBox *gb = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + if (gb->features & QStyleOptionFrameV2::Flat) + extraClass |= PseudoClass_Flat; + if (gb->lineWidth == 0) + extraClass |= PseudoClass_Frameless; + } else if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + if (tb->titleBarState & Qt::WindowMinimized) { + extraClass |= PseudoClass_Minimized; + } + else if (tb->titleBarState & Qt::WindowMaximized) + extraClass |= PseudoClass_Maximized; + } + } else { + // handle simple style options + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + if (mi->menuItemType == QStyleOptionMenuItem::DefaultItem) + extraClass |= PseudoClass_Default; + if (mi->checkType == QStyleOptionMenuItem::Exclusive) + extraClass |= PseudoClass_Exclusive; + else if (mi->checkType == QStyleOptionMenuItem::NonExclusive) + extraClass |= PseudoClass_NonExclusive; + if (mi->checkType != QStyleOptionMenuItem::NotCheckable) + extraClass |= (mi->checked) ? (PseudoClass_On|PseudoClass_Checked) + : (PseudoClass_Off|PseudoClass_Unchecked); + } else if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + if (hdr->position == QStyleOptionHeader::OnlyOneSection) + extraClass |= PseudoClass_OnlyOne; + else if (hdr->position == QStyleOptionHeader::Beginning) + extraClass |= PseudoClass_First; + else if (hdr->position == QStyleOptionHeader::End) + extraClass |= PseudoClass_Last; + else if (hdr->position == QStyleOptionHeader::Middle) + extraClass |= PseudoClass_Middle; + + if (hdr->selectedPosition == QStyleOptionHeader::NextAndPreviousAreSelected) + extraClass |= (PseudoClass_NextSelected | PseudoClass_PreviousSelected); + else if (hdr->selectedPosition == QStyleOptionHeader::NextIsSelected) + extraClass |= PseudoClass_NextSelected; + else if (hdr->selectedPosition == QStyleOptionHeader::PreviousIsSelected) + extraClass |= PseudoClass_PreviousSelected; +#ifndef QT_NO_TABWIDGET + } else if (const QStyleOptionTabWidgetFrame *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + extraClass |= PseudoClass_Top; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + extraClass |= PseudoClass_Bottom; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + extraClass |= PseudoClass_Left; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + extraClass |= PseudoClass_Right; + break; + default: + break; + } +#endif +#ifndef QT_NO_TABBAR + } else if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + if (tab->position == QStyleOptionTab::OnlyOneTab) + extraClass |= PseudoClass_OnlyOne; + else if (tab->position == QStyleOptionTab::Beginning) + extraClass |= PseudoClass_First; + else if (tab->position == QStyleOptionTab::End) + extraClass |= PseudoClass_Last; + else if (tab->position == QStyleOptionTab::Middle) + extraClass |= PseudoClass_Middle; + + if (tab->selectedPosition == QStyleOptionTab::NextIsSelected) + extraClass |= PseudoClass_NextSelected; + else if (tab->selectedPosition == QStyleOptionTab::PreviousIsSelected) + extraClass |= PseudoClass_PreviousSelected; + + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + extraClass |= PseudoClass_Top; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + extraClass |= PseudoClass_Bottom; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + extraClass |= PseudoClass_Left; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + extraClass |= PseudoClass_Right; + break; + default: + break; + } +#endif // QT_NO_TABBAR + } else if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (btn->features & QStyleOptionButton::Flat) + extraClass |= PseudoClass_Flat; + if (btn->features & QStyleOptionButton::DefaultButton) + extraClass |= PseudoClass_Default; + } else if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (frm->lineWidth == 0) + extraClass |= PseudoClass_Frameless; + if (const QStyleOptionFrameV2 *frame2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(opt)) { + if (frame2->features & QStyleOptionFrameV2::Flat) + extraClass |= PseudoClass_Flat; + } + } +#ifndef QT_NO_TOOLBAR + else if (const QStyleOptionToolBar *tb = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) { + if (tb->toolBarArea == Qt::LeftToolBarArea) + extraClass |= PseudoClass_Left; + else if (tb->toolBarArea == Qt::RightToolBarArea) + extraClass |= PseudoClass_Right; + else if (tb->toolBarArea == Qt::TopToolBarArea) + extraClass |= PseudoClass_Top; + else if (tb->toolBarArea == Qt::BottomToolBarArea) + extraClass |= PseudoClass_Bottom; + + if (tb->positionWithinLine == QStyleOptionToolBar::Beginning) + extraClass |= PseudoClass_First; + else if (tb->positionWithinLine == QStyleOptionToolBar::Middle) + extraClass |= PseudoClass_Middle; + else if (tb->positionWithinLine == QStyleOptionToolBar::End) + extraClass |= PseudoClass_Last; + else if (tb->positionWithinLine == QStyleOptionToolBar::OnlyOne) + extraClass |= PseudoClass_OnlyOne; + } +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_TOOLBOX + else if (const QStyleOptionToolBoxV2 *tab = qstyleoption_cast<const QStyleOptionToolBoxV2 *>(opt)) { + if (tab->position == QStyleOptionToolBoxV2::OnlyOneTab) + extraClass |= PseudoClass_OnlyOne; + else if (tab->position == QStyleOptionToolBoxV2::Beginning) + extraClass |= PseudoClass_First; + else if (tab->position == QStyleOptionToolBoxV2::End) + extraClass |= PseudoClass_Last; + else if (tab->position == QStyleOptionToolBoxV2::Middle) + extraClass |= PseudoClass_Middle; + + if (tab->selectedPosition == QStyleOptionToolBoxV2::NextIsSelected) + extraClass |= PseudoClass_NextSelected; + else if (tab->selectedPosition == QStyleOptionToolBoxV2::PreviousIsSelected) + extraClass |= PseudoClass_PreviousSelected; + } +#endif // QT_NO_TOOLBOX +#ifndef QT_NO_DOCKWIDGET + else if (const QStyleOptionDockWidgetV2 *dw = qstyleoption_cast<const QStyleOptionDockWidgetV2 *>(opt)) { + if (dw->verticalTitleBar) + extraClass |= PseudoClass_Vertical; + else + extraClass |= PseudoClass_Horizontal; + if (dw->closable) + extraClass |= PseudoClass_Closable; + if (dw->floatable) + extraClass |= PseudoClass_Floatable; + if (dw->movable) + extraClass |= PseudoClass_Movable; + } +#endif // QT_NO_DOCKWIDGET +#ifndef QT_NO_ITEMVIEWS + else if (const QStyleOptionViewItemV2 *v2 = qstyleoption_cast<const QStyleOptionViewItemV2 *>(opt)) { + if (v2->features & QStyleOptionViewItemV2::Alternate) + extraClass |= PseudoClass_Alternate; + if (const QStyleOptionViewItemV4 *v4 = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + if (v4->viewItemPosition == QStyleOptionViewItemV4::OnlyOne) + extraClass |= PseudoClass_OnlyOne; + else if (v4->viewItemPosition == QStyleOptionViewItemV4::Beginning) + extraClass |= PseudoClass_First; + else if (v4->viewItemPosition == QStyleOptionViewItemV4::End) + extraClass |= PseudoClass_Last; + else if (v4->viewItemPosition == QStyleOptionViewItemV4::Middle) + extraClass |= PseudoClass_Middle; + } + } +#endif +#ifndef QT_NO_LINEEDIT + // LineEdit sets Sunken flag to indicate Sunken frame (argh) + if (const QLineEdit *lineEdit = qobject_cast<const QLineEdit *>(w)) { + state &= ~QStyle::State_Sunken; + if (lineEdit->hasFrame()) { + extraClass &= ~PseudoClass_Frameless; + } else { + extraClass |= PseudoClass_Frameless; + } + } else +#endif + if (const QFrame *frm = qobject_cast<const QFrame *>(w)) { + if (frm->lineWidth() == 0) + extraClass |= PseudoClass_Frameless; + } + } + + return renderRule(w, pseudoElement, pseudoClass(state) | extraClass); +} + +bool QStyleSheetStyle::hasStyleRule(const QWidget *w, int part) const +{ + QHash<int, bool> &cache = styleSheetCaches->hasStyleRuleCache[w]; + QHash<int, bool>::const_iterator cacheIt = cache.constFind(part); + if (cacheIt != cache.constEnd()) + return cacheIt.value(); + + if (!initWidget(w)) + return false; + + + const QVector<StyleRule> &rules = styleRules(w); + if (part == PseudoElement_None) { + bool result = w && !rules.isEmpty(); + cache[part] = result; + return result; + } + + QString pseudoElement = QLatin1String(knownPseudoElements[part].name); + QVector<Declaration> declarations; + for (int i = 0; i < rules.count(); i++) { + const Selector& selector = rules.at(i).selectors.at(0); + if (pseudoElement.compare(selector.pseudoElement(), Qt::CaseInsensitive) == 0) { + cache[part] = true; + return true; + } + } + + cache[part] = false; + return false; +} + +static Origin defaultOrigin(int pe) +{ + switch (pe) { + case PseudoElement_ScrollBarAddPage: + case PseudoElement_ScrollBarSubPage: + case PseudoElement_ScrollBarAddLine: + case PseudoElement_ScrollBarSubLine: + case PseudoElement_ScrollBarFirst: + case PseudoElement_ScrollBarLast: + case PseudoElement_GroupBoxTitle: + case PseudoElement_GroupBoxIndicator: // never used + case PseudoElement_ToolButtonMenu: + case PseudoElement_SliderAddPage: + case PseudoElement_SliderSubPage: + return Origin_Border; + + case PseudoElement_SpinBoxUpButton: + case PseudoElement_SpinBoxDownButton: + case PseudoElement_PushButtonMenuIndicator: + case PseudoElement_ComboBoxDropDown: + case PseudoElement_ToolButtonDownArrow: + case PseudoElement_MenuCheckMark: + case PseudoElement_MenuIcon: + case PseudoElement_MenuRightArrow: + return Origin_Padding; + + case PseudoElement_Indicator: + case PseudoElement_ExclusiveIndicator: + case PseudoElement_ComboBoxArrow: + case PseudoElement_ScrollBarSlider: + case PseudoElement_ScrollBarUpArrow: + case PseudoElement_ScrollBarDownArrow: + case PseudoElement_ScrollBarLeftArrow: + case PseudoElement_ScrollBarRightArrow: + case PseudoElement_SpinBoxUpArrow: + case PseudoElement_SpinBoxDownArrow: + case PseudoElement_ToolButtonMenuArrow: + case PseudoElement_HeaderViewUpArrow: + case PseudoElement_HeaderViewDownArrow: + case PseudoElement_SliderGroove: + case PseudoElement_SliderHandle: + return Origin_Content; + + default: + return Origin_Margin; + } +} + +static Qt::Alignment defaultPosition(int pe) +{ + switch (pe) { + case PseudoElement_Indicator: + case PseudoElement_ExclusiveIndicator: + case PseudoElement_MenuCheckMark: + case PseudoElement_MenuIcon: + return Qt::AlignLeft | Qt::AlignVCenter; + + case PseudoElement_ScrollBarAddLine: + case PseudoElement_ScrollBarLast: + case PseudoElement_SpinBoxDownButton: + case PseudoElement_PushButtonMenuIndicator: + case PseudoElement_ToolButtonDownArrow: + return Qt::AlignRight | Qt::AlignBottom; + + case PseudoElement_ScrollBarSubLine: + case PseudoElement_ScrollBarFirst: + case PseudoElement_SpinBoxUpButton: + case PseudoElement_ComboBoxDropDown: + case PseudoElement_ToolButtonMenu: + case PseudoElement_DockWidgetCloseButton: + case PseudoElement_DockWidgetFloatButton: + return Qt::AlignRight | Qt::AlignTop; + + case PseudoElement_ScrollBarUpArrow: + case PseudoElement_ScrollBarDownArrow: + case PseudoElement_ScrollBarLeftArrow: + case PseudoElement_ScrollBarRightArrow: + case PseudoElement_SpinBoxUpArrow: + case PseudoElement_SpinBoxDownArrow: + case PseudoElement_ComboBoxArrow: + case PseudoElement_DownArrow: + case PseudoElement_ToolButtonMenuArrow: + case PseudoElement_SliderGroove: + return Qt::AlignCenter; + + case PseudoElement_GroupBoxTitle: + case PseudoElement_GroupBoxIndicator: // never used + return Qt::AlignLeft | Qt::AlignTop; + + case PseudoElement_HeaderViewUpArrow: + case PseudoElement_HeaderViewDownArrow: + case PseudoElement_MenuRightArrow: + return Qt::AlignRight | Qt::AlignVCenter; + + default: + return 0; + } +} + +QSize QStyleSheetStyle::defaultSize(const QWidget *w, QSize sz, const QRect& rect, int pe) const +{ + QStyle *base = baseStyle(); + + switch (pe) { + case PseudoElement_Indicator: + case PseudoElement_MenuCheckMark: + if (sz.width() == -1) + sz.setWidth(base->pixelMetric(PM_IndicatorWidth, 0, w)); + if (sz.height() == -1) + sz.setHeight(base->pixelMetric(PM_IndicatorHeight, 0, w)); + break; + + case PseudoElement_ExclusiveIndicator: + case PseudoElement_GroupBoxIndicator: + if (sz.width() == -1) + sz.setWidth(base->pixelMetric(PM_ExclusiveIndicatorWidth, 0, w)); + if (sz.height() == -1) + sz.setHeight(base->pixelMetric(PM_ExclusiveIndicatorHeight, 0, w)); + break; + + case PseudoElement_PushButtonMenuIndicator: { + int pm = base->pixelMetric(PM_MenuButtonIndicator, 0, w); + if (sz.width() == -1) + sz.setWidth(pm); + if (sz.height() == -1) + sz.setHeight(pm); + } + break; + + case PseudoElement_ComboBoxDropDown: + if (sz.width() == -1) + sz.setWidth(16); + break; + + case PseudoElement_ComboBoxArrow: + case PseudoElement_DownArrow: + case PseudoElement_ToolButtonMenuArrow: + case PseudoElement_ToolButtonDownArrow: + case PseudoElement_MenuRightArrow: + if (sz.width() == -1) + sz.setWidth(13); + if (sz.height() == -1) + sz.setHeight(13); + break; + + case PseudoElement_SpinBoxUpButton: + case PseudoElement_SpinBoxDownButton: + if (sz.width() == -1) + sz.setWidth(16); + if (sz.height() == -1) + sz.setHeight(rect.height()/2); + break; + + case PseudoElement_ToolButtonMenu: + if (sz.width() == -1) + sz.setWidth(base->pixelMetric(PM_MenuButtonIndicator, 0, w)); + break; + + case PseudoElement_HeaderViewUpArrow: + case PseudoElement_HeaderViewDownArrow: { + int pm = base->pixelMetric(PM_HeaderMargin, 0, w); + if (sz.width() == -1) + sz.setWidth(pm); + if (sz.height() == 1) + sz.setHeight(pm); + break; + } + + case PseudoElement_ScrollBarFirst: + case PseudoElement_ScrollBarLast: + case PseudoElement_ScrollBarAddLine: + case PseudoElement_ScrollBarSubLine: + case PseudoElement_ScrollBarSlider: { + int pm = pixelMetric(QStyle::PM_ScrollBarExtent, 0, w); + if (sz.width() == -1) + sz.setWidth(pm); + if (sz.height() == -1) + sz.setHeight(pm); + break; + } + + case PseudoElement_DockWidgetCloseButton: + case PseudoElement_DockWidgetFloatButton: { + int iconSize = pixelMetric(PM_SmallIconSize, 0, w); + return QSize(iconSize, iconSize); + } + + default: + break; + } + + // expand to rectangle + if (sz.height() == -1) + sz.setHeight(rect.height()); + if (sz.width() == -1) + sz.setWidth(rect.width()); + + return sz; +} + +static PositionMode defaultPositionMode(int pe) +{ + switch (pe) { + case PseudoElement_ScrollBarFirst: + case PseudoElement_ScrollBarLast: + case PseudoElement_ScrollBarAddLine: + case PseudoElement_ScrollBarSubLine: + case PseudoElement_ScrollBarAddPage: + case PseudoElement_ScrollBarSubPage: + case PseudoElement_ScrollBarSlider: + case PseudoElement_SliderGroove: + case PseudoElement_SliderHandle: + case PseudoElement_TabWidgetPane: + return PositionMode_Absolute; + default: + return PositionMode_Static; + } +} + +QRect QStyleSheetStyle::positionRect(const QWidget *w, const QRenderRule &rule2, int pe, + const QRect &originRect, Qt::LayoutDirection dir) const +{ + const QStyleSheetPositionData *p = rule2.position(); + PositionMode mode = (p && p->mode != PositionMode_Unknown) ? p->mode : defaultPositionMode(pe); + Qt::Alignment position = (p && p->position != 0) ? p->position : defaultPosition(pe); + QRect r; + + if (mode != PositionMode_Absolute) { + QSize sz = defaultSize(w, rule2.size(), originRect, pe); + sz = sz.expandedTo(rule2.minimumContentsSize()); + r = QStyle::alignedRect(dir, position, sz, originRect); + if (p) { + int left = p->left ? p->left : -p->right; + int top = p->top ? p->top : -p->bottom; + r.translate(dir == Qt::LeftToRight ? left : -left, top); + } + } else { + r = p ? originRect.adjusted(dir == Qt::LeftToRight ? p->left : p->right, p->top, + dir == Qt::LeftToRight ? -p->right : -p->left, -p->bottom) + : originRect; + if (rule2.hasContentsSize()) { + QSize sz = rule2.size().expandedTo(rule2.minimumContentsSize()); + if (sz.width() == -1) sz.setWidth(r.width()); + if (sz.height() == -1) sz.setHeight(r.height()); + r = QStyle::alignedRect(dir, position, sz, r); + } + } + return r; +} + +QRect QStyleSheetStyle::positionRect(const QWidget *w, const QRenderRule& rule1, const QRenderRule& rule2, int pe, + const QRect& rect, Qt::LayoutDirection dir) const +{ + const QStyleSheetPositionData *p = rule2.position(); + Origin origin = (p && p->origin != Origin_Unknown) ? p->origin : defaultOrigin(pe); + QRect originRect = rule1.originRect(rect, origin); + return positionRect(w, rule2, pe, originRect, dir); +} + + +/** \internal + For widget that have an embedded widget (such as combobox) return that embedded widget. + otherwise return the widget itself + */ +static QWidget *embeddedWidget(QWidget *w) +{ +#ifndef QT_NO_COMBOBOX + if (QComboBox *cmb = qobject_cast<QComboBox *>(w)) { + if (cmb->isEditable()) + return cmb->lineEdit(); + else + return cmb; + } +#endif + +#ifndef QT_NO_SPINBOX + if (QAbstractSpinBox *sb = qobject_cast<QAbstractSpinBox *>(w)) + return sb->findChild<QLineEdit *>(); +#endif + +#ifndef QT_NO_SCROLLAREA + if (QAbstractScrollArea *sa = qobject_cast<QAbstractScrollArea *>(w)) + return sa->viewport(); +#endif + + return w; +} + +/** \internal + in case w is an embedded widget, return the container widget + (i.e, the widget for which the rules actualy apply) + (exemple, if w is a lineedit embedded in a combobox, return the combobox) + + if w is not embedded, return w itself +*/ +static QWidget *containerWidget(const QWidget *w) +{ +#ifndef QT_NO_LINEEDIT + if (qobject_cast<const QLineEdit *>(w)) { + //if the QLineEdit is an embeddedWidget, we need the rule of the real widget +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox *>(w->parentWidget())) + return w->parentWidget(); +#endif +#ifndef QT_NO_SPINBOX + if (qobject_cast<const QAbstractSpinBox *>(w->parentWidget())) + return w->parentWidget(); +#endif + } +#endif // QT_NO_LINEEDIT + +#ifndef QT_NO_SCROLLAREA + if (const QAbstractScrollArea *sa = qobject_cast<const QAbstractScrollArea *>(w->parentWidget())) { + if (sa->viewport() == w) + return w->parentWidget(); + } +#endif + + return const_cast<QWidget *>(w); +} + +/** \internal + returns true if the widget can NOT be styled directly + */ +static bool unstylable(const QWidget *w) +{ + if (w->windowType() == Qt::Desktop) + return true; + + if (!w->styleSheet().isEmpty()) + return false; + + if (containerWidget(w) != w) + return true; + +#ifndef QT_NO_FRAME + // detect QComboBoxPrivateContainer + else if (qobject_cast<const QFrame *>(w)) { + if (0 +#ifndef QT_NO_COMBOBOX + || qobject_cast<const QComboBox *>(w->parentWidget()) +#endif + ) + return true; + } +#endif + return false; +} + +static quint64 extendedPseudoClass(const QWidget *w) +{ + quint64 pc = w->isWindow() ? quint64(PseudoClass_Window) : 0; + if (const QAbstractSlider *slider = qobject_cast<const QAbstractSlider *>(w)) { + pc |= ((slider->orientation() == Qt::Vertical) ? PseudoClass_Vertical : PseudoClass_Horizontal); + } else +#ifndef QT_NO_COMBOBOX + if (const QComboBox *combo = qobject_cast<const QComboBox *>(w)) { + if (combo->isEditable()) + pc |= (combo->isEditable() ? PseudoClass_Editable : PseudoClass_ReadOnly); + } else +#endif +#ifndef QT_NO_LINEEDIT + if (const QLineEdit *edit = qobject_cast<const QLineEdit *>(w)) { + pc |= (edit->isReadOnly() ? PseudoClass_ReadOnly : PseudoClass_Editable); + } else +#endif + { } // required for the above ifdef'ery to work + return pc; +} + +// sets up the geometry of the widget. We set a dynamic property when +// we modify the min/max size of the widget. The min/max size is restored +// to their original value when a new stylesheet that does not contain +// the CSS properties is set and when the widget has this dynamic property set. +// This way we don't trample on users who had setup a min/max size in code and +// don't use stylesheets at all. +void QStyleSheetStyle::setGeometry(QWidget *w) +{ + QRenderRule rule = renderRule(w, PseudoElement_None, PseudoClass_Enabled | extendedPseudoClass(w)); + const QStyleSheetGeometryData *geo = rule.geometry(); + if (w->property("_q_stylesheet_minw").toBool() + && ((!rule.hasGeometry() || geo->minWidth == -1))) { + w->setMinimumWidth(0); + w->setProperty("_q_stylesheet_minw", QVariant()); + } + if (w->property("_q_stylesheet_minh").toBool() + && ((!rule.hasGeometry() || geo->minHeight == -1))) { + w->setMinimumHeight(0); + w->setProperty("_q_stylesheet_minh", QVariant()); + } + if (w->property("_q_stylesheet_maxw").toBool() + && ((!rule.hasGeometry() || geo->maxWidth == -1))) { + w->setMaximumWidth(QWIDGETSIZE_MAX); + w->setProperty("_q_stylesheet_maxw", QVariant()); + } + if (w->property("_q_stylesheet_maxh").toBool() + && ((!rule.hasGeometry() || geo->maxHeight == -1))) { + w->setMaximumHeight(QWIDGETSIZE_MAX); + w->setProperty("_q_stylesheet_maxh", QVariant()); + } + + + if (rule.hasGeometry()) { + if (geo->minWidth != -1) { + w->setProperty("_q_stylesheet_minw", true); + w->setMinimumWidth(rule.boxSize(QSize(qMax(geo->width, geo->minWidth), 0)).width()); + } + if (geo->minHeight != -1) { + w->setProperty("_q_stylesheet_minh", true); + w->setMinimumHeight(rule.boxSize(QSize(0, qMax(geo->height, geo->minHeight))).height()); + } + if (geo->maxWidth != -1) { + w->setProperty("_q_stylesheet_maxw", true); + w->setMaximumWidth(rule.boxSize(QSize(qMin(geo->width == -1 ? QWIDGETSIZE_MAX : geo->width, + geo->maxWidth == -1 ? QWIDGETSIZE_MAX : geo->maxWidth), 0)).width()); + } + if (geo->maxHeight != -1) { + w->setProperty("_q_stylesheet_maxh", true); + w->setMaximumHeight(rule.boxSize(QSize(0, qMin(geo->height == -1 ? QWIDGETSIZE_MAX : geo->height, + geo->maxHeight == -1 ? QWIDGETSIZE_MAX : geo->maxHeight))).height()); + } + } +} + +void QStyleSheetStyle::setProperties(QWidget *w) +{ + QHash<QString, QVariant> propertyHash; + QVector<Declaration> decls = declarations(styleRules(w), QString()); + + // run through the declarations in order + for (int i = 0; i < decls.count(); i++) { + const Declaration &decl = decls.at(i); + QString property = decl.d->property; + if (!property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive)) + continue; + property.remove(0, 10); // strip "qproperty-" + const QVariant value = w->property(property.toLatin1()); + const QMetaObject *metaObject = w->metaObject(); + int index = metaObject->indexOfProperty(property.toLatin1()); + if (index == -1) { + qWarning() << w << " does not have a property named " << property; + continue; + } + QMetaProperty metaProperty = metaObject->property(index); + if (!metaProperty.isWritable() || !metaProperty.isDesignable()) { + qWarning() << w << " cannot design property named " << property; + continue; + } + QVariant v; + switch (value.type()) { + case QVariant::Icon: v = decl.iconValue(); break; + case QVariant::Image: v = QImage(decl.uriValue()); break; + case QVariant::Pixmap: v = QPixmap(decl.uriValue()); break; + case QVariant::Rect: v = decl.rectValue(); break; + case QVariant::Size: v = decl.sizeValue(); break; + case QVariant::Color: v = decl.colorValue(); break; + case QVariant::Brush: v = decl.brushValue(); break; +#ifndef QT_NO_SHORTCUT + case QVariant::KeySequence: v = QKeySequence(decl.d->values.at(0).variant.toString()); break; +#endif + default: v = decl.d->values.at(0).variant; break; + } + propertyHash[property] = v; + } + // apply the values + const QList<QString> properties = propertyHash.keys(); + for (int i = 0; i < properties.count(); i++) { + const QString &property = properties.at(i); + w->setProperty(property.toLatin1(), propertyHash[property]); + } +} + +void QStyleSheetStyle::setPalette(QWidget *w) +{ + struct RuleRoleMap { + int state; + QPalette::ColorGroup group; + } map[3] = { + { int(PseudoClass_Active | PseudoClass_Enabled), QPalette::Active }, + { PseudoClass_Disabled, QPalette::Disabled }, + { PseudoClass_Enabled, QPalette::Inactive } + }; + + QPalette p = w->palette(); + QWidget *ew = embeddedWidget(w); + + for (int i = 0; i < 3; i++) { + QRenderRule rule = renderRule(w, PseudoElement_None, map[i].state | extendedPseudoClass(w)); + if (i == 0) { + if (!w->property("_q_styleSheetWidgetFont").isValid()) { + saveWidgetFont(w, w->font()); + } + updateStyleSheetFont(w); + if (ew != w) + updateStyleSheetFont(ew); + } + + rule.configurePalette(&p, map[i].group, ew, ew != w); + } + + styleSheetCaches->customPaletteWidgets.insert(w, w->palette()); + w->setPalette(p); + if (ew != w) + ew->setPalette(p); +} + +void QStyleSheetStyle::unsetPalette(QWidget *w) +{ + if (styleSheetCaches->customPaletteWidgets.contains(w)) { + QPalette p = styleSheetCaches->customPaletteWidgets.value(w); + w->setPalette(p); + QWidget *ew = embeddedWidget(w); + if (ew != w) + ew->setPalette(p); + styleSheetCaches->customPaletteWidgets.remove(w); + } + QVariant oldFont = w->property("_q_styleSheetWidgetFont"); + if (oldFont.isValid()) { + w->setFont(qvariant_cast<QFont>(oldFont)); + } + if (styleSheetCaches->autoFillDisabledWidgets.contains(w)) { + embeddedWidget(w)->setAutoFillBackground(true); + styleSheetCaches->autoFillDisabledWidgets.remove(w); + } +} + +static void updateWidgets(const QList<const QWidget *>& widgets) +{ + if (!styleSheetCaches->styleRulesCache.isEmpty() || !styleSheetCaches->hasStyleRuleCache.isEmpty() || !styleSheetCaches->renderRulesCache.isEmpty()) { + for (int i = 0; i < widgets.size(); ++i) { + const QWidget *widget = widgets.at(i); + styleSheetCaches->styleRulesCache.remove(widget); + styleSheetCaches->hasStyleRuleCache.remove(widget); + styleSheetCaches->renderRulesCache.remove(widget); + } + } + for (int i = 0; i < widgets.size(); ++i) { + QWidget *widget = const_cast<QWidget *>(widgets.at(i)); + if (widget == 0) + continue; + widget->style()->polish(widget); + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(widget, &event); + widget->update(); + widget->updateGeometry(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// The stylesheet style +int QStyleSheetStyle::numinstances = 0; + +QStyleSheetStyle::QStyleSheetStyle(QStyle *base) + : QWindowsStyle(*new QStyleSheetStylePrivate), base(base), refcount(1) +{ + ++numinstances; + if (numinstances == 1) { + styleSheetCaches = new QStyleSheetStyleCaches; + } +} + +QStyleSheetStyle::~QStyleSheetStyle() +{ + --numinstances; + if (numinstances == 0) { + delete styleSheetCaches; + } +} +QStyle *QStyleSheetStyle::baseStyle() const +{ + if (base) + return base; + if (QStyleSheetStyle *me = qobject_cast<QStyleSheetStyle *>(QApplication::style())) + return me->base; + return QApplication::style(); +} + +void QStyleSheetStyleCaches::widgetDestroyed(QObject *o) +{ + styleRulesCache.remove((const QWidget *)o); + hasStyleRuleCache.remove((const QWidget *)o); + renderRulesCache.remove((const QWidget *)o); + customPaletteWidgets.remove((const QWidget *)o); + styleSheetCache.remove((const QWidget *)o); + autoFillDisabledWidgets.remove((const QWidget *)o); +} + +void QStyleSheetStyleCaches::styleDestroyed(QObject *o) +{ + styleSheetCache.remove(o); +} + +/*! + * Make sure that the cache will be clean by connecting destroyed if needed. + * return false if the widget is not stylable; + */ +bool QStyleSheetStyle::initWidget(const QWidget *w) const +{ + if (!w) + return false; + if(w->testAttribute(Qt::WA_StyleSheet)) + return true; + + if(unstylable(w)) + return false; + + const_cast<QWidget *>(w)->setAttribute(Qt::WA_StyleSheet, true); + QObject::connect(w, SIGNAL(destroyed(QObject*)), styleSheetCaches, SLOT(widgetDestroyed(QObject*)), Qt::UniqueConnection); + return true; +} + +void QStyleSheetStyle::polish(QWidget *w) +{ + baseStyle()->polish(w); + RECURSION_GUARD(return) + + if (!initWidget(w)) + return; + + if (styleSheetCaches->styleRulesCache.contains(w)) { + // the widget accessed its style pointer before polish (or repolish) + // (exemple: the QAbstractSpinBox constructor ask for the stylehint) + styleSheetCaches->styleRulesCache.remove(w); + styleSheetCaches->hasStyleRuleCache.remove(w); + styleSheetCaches->renderRulesCache.remove(w); + } + setGeometry(w); + setProperties(w); + unsetPalette(w); + setPalette(w); + + //set the WA_Hover attribute if one of the selector depends of the hover state + QVector<StyleRule> rules = styleRules(w); + for (int i = 0; i < rules.count(); i++) { + const Selector& selector = rules.at(i).selectors.at(0); + quint64 negated = 0; + quint64 cssClass = selector.pseudoClass(&negated); + if ( cssClass & PseudoClass_Hover || negated & PseudoClass_Hover) { + w->setAttribute(Qt::WA_Hover); + embeddedWidget(w)->setAttribute(Qt::WA_Hover); + } + } + + +#ifndef QT_NO_SCROLLAREA + if (QAbstractScrollArea *sa = qobject_cast<QAbstractScrollArea *>(w)) { + QRenderRule rule = renderRule(sa, PseudoElement_None, PseudoClass_Enabled); + if ((rule.hasBorder() && rule.border()->hasBorderImage()) + || (rule.hasBackground() && !rule.background()->pixmap.isNull())) { + QObject::connect(sa->horizontalScrollBar(), SIGNAL(valueChanged(int)), + sa, SLOT(update()), Qt::UniqueConnection); + QObject::connect(sa->verticalScrollBar(), SIGNAL(valueChanged(int)), + sa, SLOT(update()), Qt::UniqueConnection); + } + } +#endif + +#ifndef QT_NO_PROGRESSBAR + if (QProgressBar *pb = qobject_cast<QProgressBar *>(w)) { + QWindowsStyle::polish(pb); + } +#endif + + QRenderRule rule = renderRule(w, PseudoElement_None, PseudoClass_Any); + if (rule.hasDrawable() || rule.hasBox()) { + if (w->metaObject() == &QWidget::staticMetaObject +#ifndef QT_NO_ITEMVIEWS + || qobject_cast<QHeaderView *>(w) +#endif +#ifndef QT_NO_TABBAR + || qobject_cast<QTabBar *>(w) +#endif +#ifndef QT_NO_FRAME + || qobject_cast<QFrame *>(w) +#endif +#ifndef QT_NO_MAINWINDOW + || qobject_cast<QMainWindow *>(w) +#endif +#ifndef QT_NO_MDIAREA + || qobject_cast<QMdiSubWindow *>(w) +#endif +#ifndef QT_NO_MENUBAR + || qobject_cast<QMenuBar *>(w) +#endif + || qobject_cast<QDialog *>(w)) { + w->setAttribute(Qt::WA_StyledBackground, true); + } + QWidget *ew = embeddedWidget(w); + if (ew->autoFillBackground()) { + ew->setAutoFillBackground(false); + styleSheetCaches->autoFillDisabledWidgets.insert(w); + if (ew != w) { //eg. viewport of a scrollarea + //(in order to draw the background anyway in case we don't.) + ew->setAttribute(Qt::WA_StyledBackground, true); + } + } + if (!rule.hasBackground() || rule.background()->isTransparent() || rule.hasBox() + || (!rule.hasNativeBorder() && !rule.border()->isOpaque())) + w->setAttribute(Qt::WA_OpaquePaintEvent, false); + } +} + +void QStyleSheetStyle::polish(QApplication *app) +{ + baseStyle()->polish(app); +} + +void QStyleSheetStyle::polish(QPalette &pal) +{ + baseStyle()->polish(pal); +} + +void QStyleSheetStyle::repolish(QWidget *w) +{ + QList<const QWidget *> children = w->findChildren<const QWidget *>(QString()); + children.append(w); + styleSheetCaches->styleSheetCache.remove(w); + updateWidgets(children); +} + +void QStyleSheetStyle::repolish(QApplication *app) +{ + Q_UNUSED(app); + const QList<const QWidget*> allWidgets = styleSheetCaches->styleRulesCache.keys(); + styleSheetCaches->styleSheetCache.remove(qApp); + styleSheetCaches->styleRulesCache.clear(); + styleSheetCaches->hasStyleRuleCache.clear(); + styleSheetCaches->renderRulesCache.clear(); + updateWidgets(allWidgets); +} + +void QStyleSheetStyle::unpolish(QWidget *w) +{ + if (!w || !w->testAttribute(Qt::WA_StyleSheet)) { + baseStyle()->unpolish(w); + return; + } + + styleSheetCaches->styleRulesCache.remove(w); + styleSheetCaches->hasStyleRuleCache.remove(w); + styleSheetCaches->renderRulesCache.remove(w); + styleSheetCaches->styleSheetCache.remove(w); + unsetPalette(w); + w->setProperty("_q_stylesheet_minw", QVariant()); + w->setProperty("_q_stylesheet_minh", QVariant()); + w->setProperty("_q_stylesheet_maxw", QVariant()); + w->setProperty("_q_stylesheet_maxh", QVariant()); + w->setAttribute(Qt::WA_StyleSheet, false); + QObject::disconnect(w, 0, this, 0); +#ifndef QT_NO_SCROLLAREA + if (QAbstractScrollArea *sa = qobject_cast<QAbstractScrollArea *>(w)) { + QObject::disconnect(sa->horizontalScrollBar(), SIGNAL(valueChanged(int)), + sa, SLOT(update())); + QObject::disconnect(sa->verticalScrollBar(), SIGNAL(valueChanged(int)), + sa, SLOT(update())); + } +#endif +#ifndef QT_NO_PROGRESSBAR + if (QProgressBar *pb = qobject_cast<QProgressBar *>(w)) + QWindowsStyle::unpolish(pb); +#endif + baseStyle()->unpolish(w); +} + +void QStyleSheetStyle::unpolish(QApplication *app) +{ + baseStyle()->unpolish(app); + RECURSION_GUARD(return) + styleSheetCaches->styleRulesCache.clear(); + styleSheetCaches->hasStyleRuleCache.clear(); + styleSheetCaches->renderRulesCache.clear(); + styleSheetCaches->styleSheetCache.remove(qApp); +} + +#ifndef QT_NO_TABBAR +inline static bool verticalTabs(QTabBar::Shape shape) +{ + return shape == QTabBar::RoundedWest + || shape == QTabBar::RoundedEast + || shape == QTabBar::TriangularWest + || shape == QTabBar::TriangularEast; +} +#endif // QT_NO_TABBAR + +void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w) const +{ + RECURSION_GUARD(baseStyle()->drawComplexControl(cc, opt, p, w); return) + + QRenderRule rule = renderRule(w, opt); + + switch (cc) { + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QStyleOptionComboBox cmbOpt(*cmb); + cmbOpt.rect = rule.borderRect(opt->rect); + if (rule.hasNativeBorder()) { + rule.drawBackgroundImage(p, cmbOpt.rect); + rule.configurePalette(&cmbOpt.palette, QPalette::ButtonText, QPalette::Button); + bool customDropDown = (opt->subControls & QStyle::SC_ComboBoxArrow) + && (hasStyleRule(w, PseudoElement_ComboBoxDropDown) || hasStyleRule(w, PseudoElement_ComboBoxArrow)); + if (customDropDown) + cmbOpt.subControls &= ~QStyle::SC_ComboBoxArrow; + if (rule.baseStyleCanDraw()) { + baseStyle()->drawComplexControl(cc, &cmbOpt, p, w); + } else { + QWindowsStyle::drawComplexControl(cc, &cmbOpt, p, w); + } + if (!customDropDown) + return; + } else { + rule.drawRule(p, opt->rect); + } + + if (opt->subControls & QStyle::SC_ComboBoxArrow) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ComboBoxDropDown); + if (subRule.hasDrawable()) { + QRect r = subControlRect(CC_ComboBox, opt, SC_ComboBoxArrow, w); + subRule.drawRule(p, r); + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_ComboBoxArrow); + r = positionRect(w, subRule, subRule2, PseudoElement_ComboBoxArrow, r, opt->direction); + subRule2.drawRule(p, r); + } else { + cmbOpt.subControls = QStyle::SC_ComboBoxArrow; + QWindowsStyle::drawComplexControl(cc, &cmbOpt, p, w); + } + } + + return; + } + break; + +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox spinOpt(*spin); + rule.configurePalette(&spinOpt.palette, QPalette::ButtonText, QPalette::Button); + rule.configurePalette(&spinOpt.palette, QPalette::Text, QPalette::Base); + spinOpt.rect = rule.borderRect(opt->rect); + bool customUp = true, customDown = true; + QRenderRule upRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton); + QRenderRule downRule = renderRule(w, opt, PseudoElement_SpinBoxDownButton); + bool upRuleMatch = upRule.hasGeometry() || upRule.hasPosition(); + bool downRuleMatch = downRule.hasGeometry() || downRule.hasPosition(); + if (rule.hasNativeBorder() && !upRuleMatch && !downRuleMatch) { + rule.drawBackgroundImage(p, spinOpt.rect); + customUp = (opt->subControls & QStyle::SC_SpinBoxUp) + && (hasStyleRule(w, PseudoElement_SpinBoxUpButton) || hasStyleRule(w, PseudoElement_UpArrow)); + if (customUp) + spinOpt.subControls &= ~QStyle::SC_SpinBoxUp; + customDown = (opt->subControls & QStyle::SC_SpinBoxDown) + && (hasStyleRule(w, PseudoElement_SpinBoxDownButton) || hasStyleRule(w, PseudoElement_DownArrow)); + if (customDown) + spinOpt.subControls &= ~QStyle::SC_SpinBoxDown; + if (rule.baseStyleCanDraw()) { + baseStyle()->drawComplexControl(cc, &spinOpt, p, w); + } else { + QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + } + if (!customUp && !customDown) + return; + } else { + rule.drawRule(p, opt->rect); + } + + if ((opt->subControls & QStyle::SC_SpinBoxUp) && customUp) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton); + if (subRule.hasDrawable()) { + QRect r = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w); + subRule.drawRule(p, r); + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_SpinBoxUpArrow); + r = positionRect(w, subRule, subRule2, PseudoElement_SpinBoxUpArrow, r, opt->direction); + subRule2.drawRule(p, r); + } else { + spinOpt.subControls = QStyle::SC_SpinBoxUp; + QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + } + } + + if ((opt->subControls & QStyle::SC_SpinBoxDown) && customDown) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_SpinBoxDownButton); + if (subRule.hasDrawable()) { + QRect r = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w); + subRule.drawRule(p, r); + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_SpinBoxDownArrow); + r = positionRect(w, subRule, subRule2, PseudoElement_SpinBoxDownArrow, r, opt->direction); + subRule2.drawRule(p, r); + } else { + spinOpt.subControls = QStyle::SC_SpinBoxDown; + QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + } + } + return; + } + break; +#endif // QT_NO_SPINBOX + + case CC_GroupBox: + if (const QStyleOptionGroupBox *gb = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + + QRect labelRect, checkBoxRect, titleRect, frameRect; + bool hasTitle = (gb->subControls & QStyle::SC_GroupBoxCheckBox) || !gb->text.isEmpty(); + + if (!rule.hasDrawable() && (!hasTitle || !hasStyleRule(w, PseudoElement_GroupBoxTitle)) + && !hasStyleRule(w, PseudoElement_Indicator) && !rule.hasBox() && !rule.hasFont && !rule.hasPalette()) { + // let the native style draw the combobox if there is no style for it. + break; + } + rule.drawBackground(p, opt->rect); + + QRenderRule titleRule = renderRule(w, opt, PseudoElement_GroupBoxTitle); + bool clipSet = false; + + if (hasTitle) { + labelRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w); + //Some native style (such as mac) may return a too small rectangle (because they use smaller fonts), so we may need to expand it a little bit. + labelRect.setSize(labelRect.size().expandedTo(ParentStyle::subControlRect(CC_GroupBox, opt, SC_GroupBoxLabel, w).size())); + if (gb->subControls & QStyle::SC_GroupBoxCheckBox) { + checkBoxRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxCheckBox, w); + titleRect = titleRule.boxRect(checkBoxRect.united(labelRect)); + } else { + titleRect = titleRule.boxRect(labelRect); + } + if (!titleRule.hasBackground() || !titleRule.background()->isTransparent()) { + clipSet = true; + p->save(); + p->setClipRegion(QRegion(opt->rect) - titleRect); + } + } + + frameRect = subControlRect(CC_GroupBox, opt, SC_GroupBoxFrame, w); + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*gb); + frame.features = gb->features; + frame.lineWidth = gb->lineWidth; + frame.midLineWidth = gb->midLineWidth; + frame.rect = frameRect; + drawPrimitive(PE_FrameGroupBox, &frame, p, w); + + if (clipSet) + p->restore(); + + // draw background and frame of the title + if (hasTitle) + titleRule.drawRule(p, titleRect); + + // draw the indicator + if (gb->subControls & QStyle::SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*gb); + box.rect = checkBoxRect; + drawPrimitive(PE_IndicatorCheckBox, &box, p, w); + } + + // draw the text + if (!gb->text.isEmpty()) { + int alignment = int(Qt::AlignCenter | Qt::TextShowMnemonic); + if (!styleHint(QStyle::SH_UnderlineShortcut, opt, w)) { + alignment |= Qt::TextHideMnemonic; + } + + QPalette pal = gb->palette; + if (gb->textColor.isValid()) + pal.setColor(QPalette::WindowText, gb->textColor); + titleRule.configurePalette(&pal, QPalette::WindowText, QPalette::Window); + drawItemText(p, labelRect, alignment, pal, gb->state & State_Enabled, + gb->text, QPalette::WindowText); + + if (gb->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*gb); + fropt.rect = labelRect; + drawPrimitive(PE_FrameFocusRect, &fropt, p, w); + } + } + + return; + } + break; + + case CC_ToolButton: + if (const QStyleOptionToolButton *tool = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + QStyleOptionToolButton toolOpt(*tool); + rule.configurePalette(&toolOpt.palette, QPalette::ButtonText, QPalette::Button); + toolOpt.font = rule.font.resolve(toolOpt.font); + toolOpt.rect = rule.borderRect(opt->rect); + bool customArrow = (tool->features & (QStyleOptionToolButton::HasMenu | QStyleOptionToolButton::MenuButtonPopup)); + bool customDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup; + if (rule.hasNativeBorder()) { + if (tool->subControls & SC_ToolButton) { + //in some case (eg. the button is "auto raised") the style doesn't draw the background + //so we need to draw the background. + // use the same condition as in QCommonStyle + State bflags = tool->state & ~State_Sunken; + if (bflags & State_AutoRaise && (!(bflags & State_MouseOver) || !(bflags & State_Enabled))) + bflags &= ~State_Raised; + if (tool->state & State_Sunken && tool->activeSubControls & SC_ToolButton) + bflags |= State_Sunken; + if (!(bflags & (State_Sunken | State_On | State_Raised))) + rule.drawBackground(p, toolOpt.rect); + } + customArrow = customArrow && hasStyleRule(w, PseudoElement_ToolButtonDownArrow); + if (customArrow) + toolOpt.features &= ~QStyleOptionToolButton::HasMenu; + customDropDown = customDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu); + if (customDropDown) + toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu; + + if (rule.baseStyleCanDraw() && !(tool->features & QStyleOptionToolButton::Arrow)) { + baseStyle()->drawComplexControl(cc, &toolOpt, p, w); + } else { + QWindowsStyle::drawComplexControl(cc, &toolOpt, p, w); + } + + if (!customArrow && !customDropDown) + return; + } else { + rule.drawRule(p, opt->rect); + toolOpt.rect = rule.contentsRect(opt->rect); + if (rule.hasFont) + toolOpt.font = rule.font; + drawControl(CE_ToolButtonLabel, &toolOpt, p, w); + } + + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); + QRect r = subControlRect(CC_ToolButton, opt, QStyle::SC_ToolButtonMenu, w); + if (customDropDown) { + if (opt->subControls & QStyle::SC_ToolButtonMenu) { + if (subRule.hasDrawable()) { + subRule.drawRule(p, r); + } else { + toolOpt.rect = r; + baseStyle()->drawPrimitive(PE_IndicatorButtonDropDown, &toolOpt, p, w); + } + } + } + + if (customArrow) { + QRenderRule subRule2 = customDropDown ? renderRule(w, opt, PseudoElement_ToolButtonMenuArrow) + : renderRule(w, opt, PseudoElement_ToolButtonDownArrow); + QRect r2 = customDropDown + ? positionRect(w, subRule, subRule2, PseudoElement_ToolButtonMenuArrow, r, opt->direction) + : positionRect(w, rule, subRule2, PseudoElement_ToolButtonDownArrow, opt->rect, opt->direction); + if (subRule2.hasDrawable()) { + subRule2.drawRule(p, r2); + } else { + toolOpt.rect = r2; + baseStyle()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &toolOpt, p, w); + } + } + + return; + } + break; + +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QStyleOptionSlider sbOpt(*sb); + if (!rule.hasDrawable()) { + sbOpt.rect = rule.borderRect(opt->rect); + rule.drawBackgroundImage(p, opt->rect); + baseStyle()->drawComplexControl(cc, &sbOpt, p, w); + } else { + rule.drawRule(p, opt->rect); + QWindowsStyle::drawComplexControl(cc, opt, p, w); + } + return; + } + break; +#endif // QT_NO_SCROLLBAR + +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + rule.drawRule(p, opt->rect); + + QRenderRule grooveSubRule = renderRule(w, opt, PseudoElement_SliderGroove); + QRenderRule handleSubRule = renderRule(w, opt, PseudoElement_SliderHandle); + if (!grooveSubRule.hasDrawable()) { + QStyleOptionSlider slOpt(*slider); + bool handleHasRule = handleSubRule.hasDrawable(); + // If the style specifies a different handler rule, draw the groove without the handler. + if (handleHasRule) + slOpt.subControls &= ~SC_SliderHandle; + baseStyle()->drawComplexControl(cc, &slOpt, p, w); + if (!handleHasRule) + return; + } + + QRect gr = subControlRect(cc, opt, SC_SliderGroove, w); + if (slider->subControls & SC_SliderGroove) { + grooveSubRule.drawRule(p, gr); + } + + if (slider->subControls & SC_SliderHandle) { + QRect hr = subControlRect(cc, opt, SC_SliderHandle, w); + + QRenderRule subRule1 = renderRule(w, opt, PseudoElement_SliderSubPage); + if (subRule1.hasDrawable()) { + QRect r(gr.topLeft(), + slider->orientation == Qt::Horizontal + ? QPoint(hr.x()+hr.width()/2, gr.y()+gr.height() - 1) + : QPoint(gr.x()+gr.width() - 1, hr.y()+hr.height()/2)); + subRule1.drawRule(p, r); + } + + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_SliderAddPage); + if (subRule2.hasDrawable()) { + QRect r(slider->orientation == Qt::Horizontal + ? QPoint(hr.x()+hr.width()/2+1, gr.y()) + : QPoint(gr.x(), hr.y()+hr.height()/2+1), + gr.bottomRight()); + subRule2.drawRule(p, r); + } + + handleSubRule.drawRule(p, handleSubRule.boxRect(hr, Margin)); + } + + if (slider->subControls & SC_SliderTickmarks) { + // TODO... + } + + return; + } + break; +#endif // QT_NO_SLIDER + + case CC_MdiControls: + if (hasStyleRule(w, PseudoElement_MdiCloseButton) + || hasStyleRule(w, PseudoElement_MdiNormalButton) + || hasStyleRule(w, PseudoElement_MdiMinButton)) { + QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + if (layout.isEmpty()) + layout = subControlLayout(QLatin1String("mNX")); + + QStyleOptionComplex optCopy(*opt); + optCopy.subControls = 0; + for (int i = 0; i < layout.count(); i++) { + int layoutButton = layout[i].toInt(); + if (layoutButton < PseudoElement_MdiCloseButton + || layoutButton > PseudoElement_MdiNormalButton) + continue; + QStyle::SubControl control = knownPseudoElements[layoutButton].subControl; + if (!(opt->subControls & control)) + continue; + QRenderRule subRule = renderRule(w, opt, layoutButton); + if (subRule.hasDrawable()) { + QRect rect = subRule.boxRect(subControlRect(CC_MdiControls, opt, control, w), Margin); + subRule.drawRule(p, rect); + QIcon icon = standardIcon(subControlIcon(layoutButton), opt); + icon.paint(p, subRule.contentsRect(rect), Qt::AlignCenter); + } else { + optCopy.subControls |= control; + } + } + + if (optCopy.subControls) + baseStyle()->drawComplexControl(CC_MdiControls, &optCopy, p, w); + return; + } + break; + + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TitleBar); + if (!subRule.hasDrawable() && !subRule.hasBox() && !subRule.hasBorder()) + break; + subRule.drawRule(p, opt->rect); + QHash<QStyle::SubControl, QRect> layout = titleBarLayout(w, tb); + + QRect ir; + ir = layout[SC_TitleBarLabel]; + if (ir.isValid()) { + if (subRule.hasPalette()) + p->setPen(subRule.palette()->foreground.color()); + p->fillRect(ir, Qt::white); + p->drawText(ir.x(), ir.y(), ir.width(), ir.height(), Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); + } + + QPixmap pm; + + ir = layout[SC_TitleBarSysMenu]; + if (ir.isValid()) { + QRenderRule subSubRule = renderRule(w, opt, PseudoElement_TitleBarSysMenu); + subSubRule.drawRule(p, ir); + ir = subSubRule.contentsRect(ir); + if (!tb->icon.isNull()) { + tb->icon.paint(p, ir); + } else { + int iconSize = pixelMetric(PM_SmallIconSize, tb, w); + pm = standardIcon(SP_TitleBarMenuButton, 0, w).pixmap(iconSize, iconSize); + drawItemPixmap(p, ir, Qt::AlignCenter, pm); + } + } + + ir = layout[SC_TitleBarCloseButton]; + if (ir.isValid()) { + QRenderRule subSubRule = renderRule(w, opt, PseudoElement_TitleBarCloseButton); + subSubRule.drawRule(p, ir); + + QSize sz = subSubRule.contentsRect(ir).size(); + if ((tb->titleBarFlags & Qt::WindowType_Mask) == Qt::Tool) + pm = standardIcon(SP_DockWidgetCloseButton, 0, w).pixmap(sz); + else + pm = standardIcon(SP_TitleBarCloseButton, 0, w).pixmap(sz); + drawItemPixmap(p, ir, Qt::AlignCenter, pm); + } + + int pes[] = { + PseudoElement_TitleBarMaxButton, + PseudoElement_TitleBarMinButton, + PseudoElement_TitleBarNormalButton, + PseudoElement_TitleBarShadeButton, + PseudoElement_TitleBarUnshadeButton, + PseudoElement_TitleBarContextHelpButton + }; + + for (unsigned int i = 0; i < sizeof(pes)/sizeof(int); i++) { + int pe = pes[i]; + QStyle::SubControl sc = knownPseudoElements[pe].subControl; + ir = layout[sc]; + if (!ir.isValid()) + continue; + QRenderRule subSubRule = renderRule(w, opt, pe); + subSubRule.drawRule(p, ir); + pm = standardIcon(subControlIcon(pe), 0, w).pixmap(subSubRule.contentsRect(ir).size()); + drawItemPixmap(p, ir, Qt::AlignCenter, pm); + } + + return; + } + break; + + + default: + break; + } + + baseStyle()->drawComplexControl(cc, opt, p, w); +} + +void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + RECURSION_GUARD(baseStyle()->drawControl(ce, opt, p, w); return) + + QRenderRule rule = renderRule(w, opt); + int pe1 = PseudoElement_None, pe2 = PseudoElement_None; + bool fallback = false; + + switch (ce) { + case CE_ToolButtonLabel: + if (const QStyleOptionToolButton *btn = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + if (rule.hasBox() || btn->features & QStyleOptionToolButton::Arrow) { + QCommonStyle::drawControl(ce, opt, p, w); + } else { + QStyleOptionToolButton butOpt(*btn); + rule.configurePalette(&butOpt.palette, QPalette::ButtonText, QPalette::Button); + baseStyle()->drawControl(ce, &butOpt, p, w); + } + return; + } + break; + + case CE_FocusFrame: + if (!rule.hasNativeBorder()) { + rule.drawBorder(p, opt->rect); + return; + } + break; + + case CE_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (rule.hasDrawable() || rule.hasBox() || rule.hasPosition() || rule.hasPalette() || + ((btn->features & QStyleOptionButton::HasMenu) && hasStyleRule(w, PseudoElement_PushButtonMenuIndicator))) { + ParentStyle::drawControl(ce, opt, p, w); + return; + } + } + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton btnOpt(*btn); + btnOpt.rect = rule.borderRect(opt->rect); + if (rule.hasNativeBorder()) { + rule.drawBackgroundImage(p, btnOpt.rect); + rule.configurePalette(&btnOpt.palette, QPalette::ButtonText, QPalette::Button); + bool customMenu = (btn->features & QStyleOptionButton::HasMenu + && hasStyleRule(w, PseudoElement_PushButtonMenuIndicator)); + if (customMenu) + btnOpt.features &= ~QStyleOptionButton::HasMenu; + if (rule.baseStyleCanDraw()) { + baseStyle()->drawControl(ce, &btnOpt, p, w); + } else { + QWindowsStyle::drawControl(ce, &btnOpt, p, w); + } + if (!customMenu) + return; + } else { + rule.drawRule(p, opt->rect); + } + + if (btn->features & QStyleOptionButton::HasMenu) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_PushButtonMenuIndicator); + QRect ir = positionRect(w, rule, subRule, PseudoElement_PushButtonMenuIndicator, opt->rect, opt->direction); + if (subRule.hasDrawable()) { + subRule.drawRule(p, ir); + } else { + btnOpt.rect = ir; + baseStyle()->drawPrimitive(PE_IndicatorArrowDown, &btnOpt, p, w); + } + } + } + return; + + case CE_PushButtonLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton butOpt(*button); + rule.configurePalette(&butOpt.palette, QPalette::ButtonText, QPalette::Button); + if (rule.hasPosition() && rule.position()->textAlignment != 0) { + Qt::Alignment textAlignment = rule.position()->textAlignment; + QRect textRect = button->rect; + uint tf = Qt::TextShowMnemonic; + const uint verticalAlignMask = Qt::AlignVCenter | Qt::AlignTop | Qt::AlignLeft; + tf |= (textAlignment & verticalAlignMask) ? (textAlignment & verticalAlignMask) : Qt::AlignVCenter; + if (!styleHint(SH_UnderlineShortcut, button, w)) + tf |= Qt::TextHideMnemonic; + if (!button->icon.isNull()) { + //Group both icon and text + QRect iconRect; + QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + if (mode == QIcon::Normal && button->state & State_HasFocus) + mode = QIcon::Active; + QIcon::State state = QIcon::Off; + if (button->state & State_On) + state = QIcon::On; + + QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state); + int labelWidth = pixmap.width(); + int labelHeight = pixmap.height(); + int iconSpacing = 4;//### 4 is currently hardcoded in QPushButton::sizeHint() + int textWidth = button->fontMetrics.boundingRect(opt->rect, tf, button->text).width(); + if (!button->text.isEmpty()) + labelWidth += (textWidth + iconSpacing); + + //Determine label alignment: + if (textAlignment & Qt::AlignLeft) { /*left*/ + iconRect = QRect(textRect.x(), textRect.y() + (textRect.height() - labelHeight) / 2, + pixmap.width(), pixmap.height()); + } else if (textAlignment & Qt::AlignHCenter) { /* center */ + iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2, + textRect.y() + (textRect.height() - labelHeight) / 2, + pixmap.width(), pixmap.height()); + } else { /*right*/ + iconRect = QRect(textRect.x() + textRect.width() - labelWidth, + textRect.y() + (textRect.height() - labelHeight) / 2, + pixmap.width(), pixmap.height()); + } + + iconRect = visualRect(button->direction, textRect, iconRect); + + tf |= Qt::AlignLeft; //left align, we adjust the text-rect instead + + if (button->direction == Qt::RightToLeft) + textRect.setRight(iconRect.left() - iconSpacing); + else + textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing); + + if (button->state & (State_On | State_Sunken)) + iconRect.translate(pixelMetric(PM_ButtonShiftHorizontal, opt, w), + pixelMetric(PM_ButtonShiftVertical, opt, w)); + p->drawPixmap(iconRect, pixmap); + } else { + tf |= textAlignment; + } + if (button->state & (State_On | State_Sunken)) + textRect.translate(pixelMetric(PM_ButtonShiftHorizontal, opt, w), + pixelMetric(PM_ButtonShiftVertical, opt, w)); + + if (button->features & QStyleOptionButton::HasMenu) { + int indicatorSize = pixelMetric(PM_MenuButtonIndicator, button, w); + if (button->direction == Qt::LeftToRight) + textRect = textRect.adjusted(0, 0, -indicatorSize, 0); + else + textRect = textRect.adjusted(indicatorSize, 0, 0, 0); + } + drawItemText(p, textRect, tf, butOpt.palette, (button->state & State_Enabled), + button->text, QPalette::ButtonText); + } else { + ParentStyle::drawControl(ce, &butOpt, p, w); + } + } + return; + + case CE_RadioButton: + case CE_CheckBox: + if (rule.hasBox() || !rule.hasNativeBorder() || rule.hasDrawable() || hasStyleRule(w, PseudoElement_Indicator)) { + rule.drawRule(p, opt->rect); + ParentStyle::drawControl(ce, opt, p, w); + return; + } else if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton butOpt(*btn); + rule.configurePalette(&butOpt.palette, QPalette::ButtonText, QPalette::Button); + baseStyle()->drawControl(ce, &butOpt, p, w); + return; + } + break; + case CE_RadioButtonLabel: + case CE_CheckBoxLabel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton butOpt(*btn); + rule.configurePalette(&butOpt.palette, QPalette::ButtonText, QPalette::Button); + ParentStyle::drawControl(ce, &butOpt, p, w); + } + return; + + case CE_Splitter: + pe1 = PseudoElement_SplitterHandle; + break; + + case CE_ToolBar: + if (rule.hasBackground()) { + rule.drawBackground(p, opt->rect); + } + if (rule.hasBorder()) { + rule.drawBorder(p, rule.borderRect(opt->rect)); + } else { +#ifndef QT_NO_TOOLBAR + if (const QStyleOptionToolBar *tb = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) { + QStyleOptionToolBar newTb(*tb); + newTb.rect = rule.borderRect(opt->rect); + baseStyle()->drawControl(ce, &newTb, p, w); + } +#endif // QT_NO_TOOLBAR + } + return; + + case CE_MenuEmptyArea: + case CE_MenuBarEmptyArea: + if (rule.hasDrawable()) { + // Drawn by PE_Widget + return; + } + break; + + case CE_MenuTearoff: + case CE_MenuScroller: + if (const QStyleOptionMenuItem *m = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + QStyleOptionMenuItem mi(*m); + int pe = ce == CE_MenuTearoff ? PseudoElement_MenuTearoff : PseudoElement_MenuScroller; + QRenderRule subRule = renderRule(w, opt, pe); + mi.rect = subRule.contentsRect(opt->rect); + rule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + subRule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + } else { + baseStyle()->drawControl(ce, &mi, p, w); + } + } + return; + + case CE_MenuItem: + if (const QStyleOptionMenuItem *m = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + QStyleOptionMenuItem mi(*m); + + int pseudo = (mi.menuItemType == QStyleOptionMenuItem::Separator) ? PseudoElement_MenuSeparator : PseudoElement_Item; + QRenderRule subRule = renderRule(w, opt, pseudo); + mi.rect = subRule.contentsRect(opt->rect); + rule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + rule.configurePalette(&mi.palette, QPalette::HighlightedText, QPalette::Highlight); + subRule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + subRule.configurePalette(&mi.palette, QPalette::HighlightedText, QPalette::Highlight); + QFont oldFont = p->font(); + if (subRule.hasFont) + p->setFont(subRule.font.resolve(p->font())); + + // We fall back to drawing with the style sheet code whenever at least one of the + // items are styled in an incompatible way, such as having a background image. + QRenderRule allRules = renderRule(w, PseudoElement_Item, PseudoClass_Any); + + if ((pseudo == PseudoElement_MenuSeparator) && subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + } else if ((pseudo == PseudoElement_Item) + && (allRules.hasBox() || allRules.hasBorder() + || (allRules.background() && !allRules.background()->pixmap.isNull()))) { + subRule.drawRule(p, opt->rect); + if (subRule.hasBackground()) { + mi.palette.setBrush(QPalette::Highlight, Qt::NoBrush); + mi.palette.setBrush(QPalette::Button, Qt::NoBrush); + } else { + mi.palette.setBrush(QPalette::Highlight, mi.palette.brush(QPalette::Button)); + } + mi.palette.setBrush(QPalette::HighlightedText, mi.palette.brush(QPalette::ButtonText)); + + bool checkable = mi.checkType != QStyleOptionMenuItem::NotCheckable; + bool checked = checkable ? mi.checked : false; + + bool dis = !(opt->state & QStyle::State_Enabled), + act = opt->state & QStyle::State_Selected; + + if (!mi.icon.isNull()) { + QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; + if (act && !dis) + mode = QIcon::Active; + QPixmap pixmap; + if (checked) + pixmap = mi.icon.pixmap(pixelMetric(PM_SmallIconSize), mode, QIcon::On); + else + pixmap = mi.icon.pixmap(pixelMetric(PM_SmallIconSize), mode); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRenderRule iconRule = renderRule(w, opt, PseudoElement_MenuIcon); + if (!iconRule.hasGeometry()) { + iconRule.geo = new QStyleSheetGeometryData(pixw, pixh, pixw, pixh, -1, -1); + } else { + iconRule.geo->width = pixw; + iconRule.geo->height = pixh; + } + QRect iconRect = positionRect(w, subRule, iconRule, PseudoElement_MenuIcon, opt->rect, opt->direction); + iconRule.drawRule(p, iconRect); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(iconRect.center()); + p->drawPixmap(pmr.topLeft(), pixmap); + } else if (checkable) { + QRenderRule subSubRule = renderRule(w, opt, PseudoElement_MenuCheckMark); + if (subSubRule.hasDrawable() || checked) { + QStyleOptionMenuItem newMi = mi; + newMi.rect = positionRect(w, subRule, subSubRule, PseudoElement_MenuCheckMark, opt->rect, opt->direction); + drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, p, w); + } + } + + QRect textRect = subRule.contentsRect(opt->rect); + textRect.setWidth(textRect.width() - mi.tabWidth); + QString s = mi.text; + p->setPen(mi.palette.buttonText().color()); + if (!s.isEmpty()) { + int text_flags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!styleHint(SH_UnderlineShortcut, &mi, w)) + text_flags |= Qt::TextHideMnemonic; + int t = s.indexOf(QLatin1Char('\t')); + if (t >= 0) { + QRect vShortcutRect = visualRect(opt->direction, mi.rect, + QRect(textRect.topRight(), QPoint(mi.rect.right(), textRect.bottom()))); + p->drawText(vShortcutRect, text_flags, s.mid(t + 1)); + s = s.left(t); + } + p->drawText(textRect, text_flags, s.left(t)); + } + + if (mi.menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + PrimitiveElement arrow = (opt->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_MenuRightArrow); + mi.rect = positionRect(w, subRule, subRule2, PseudoElement_MenuRightArrow, opt->rect, mi.direction); + drawPrimitive(arrow, &mi, p, w); + } + } else if (hasStyleRule(w, PseudoElement_MenuCheckMark) || hasStyleRule(w, PseudoElement_MenuRightArrow)) { + QWindowsStyle::drawControl(ce, &mi, p, w); + if (mi.checkType != QStyleOptionMenuItem::NotCheckable && !mi.checked) { + // We have a style defined, but QWindowsStyle won't draw anything if not checked. + // So we mimick what QWindowsStyle would do. + int checkcol = qMax<int>(mi.maxIconWidth, QWindowsStylePrivate::windowsCheckMarkWidth); + QRect vCheckRect = visualRect(opt->direction, mi.rect, QRect(mi.rect.x(), mi.rect.y(), checkcol, mi.rect.height())); + if (mi.state.testFlag(State_Enabled) && mi.state.testFlag(State_Selected)) { + qDrawShadePanel(p, vCheckRect, mi.palette, true, 1, &mi.palette.brush(QPalette::Button)); + } else { + QBrush fill(mi.palette.light().color(), Qt::Dense4Pattern); + qDrawShadePanel(p, vCheckRect, mi.palette, true, 1, &fill); + } + QRenderRule subSubRule = renderRule(w, opt, PseudoElement_MenuCheckMark); + if (subSubRule.hasDrawable()) { + QStyleOptionMenuItem newMi(mi); + newMi.rect = visualRect(opt->direction, mi.rect, QRect(mi.rect.x() + QWindowsStylePrivate::windowsItemFrame, + mi.rect.y() + QWindowsStylePrivate::windowsItemFrame, + checkcol - 2 * QWindowsStylePrivate::windowsItemFrame, + mi.rect.height() - 2 * QWindowsStylePrivate::windowsItemFrame)); + drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, p, w); + } + } + } else { + if (rule.hasDrawable() && !subRule.hasDrawable() && !(opt->state & QStyle::State_Selected)) { + mi.palette.setColor(QPalette::Window, Qt::transparent); + mi.palette.setColor(QPalette::Button, Qt::transparent); + } + if (rule.baseStyleCanDraw() && subRule.baseStyleCanDraw()) { + baseStyle()->drawControl(ce, &mi, p, w); + } else { + ParentStyle::drawControl(ce, &mi, p, w); + } + } + + if (subRule.hasFont) + p->setFont(oldFont); + + return; + } + return; + + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *m = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + QStyleOptionMenuItem mi(*m); + QRenderRule subRule = renderRule(w, opt, PseudoElement_Item); + mi.rect = subRule.contentsRect(opt->rect); + rule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + subRule.configurePalette(&mi.palette, QPalette::ButtonText, QPalette::Button); + + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + QCommonStyle::drawControl(ce, &mi, p, w); + } else { + if (rule.hasDrawable() && !(opt->state & QStyle::State_Selected)) { + // So that the menu bar background is not hidden by the items + mi.palette.setColor(QPalette::Window, Qt::transparent); + mi.palette.setColor(QPalette::Button, Qt::transparent); + } + baseStyle()->drawControl(ce, &mi, p, w); + } + } + return; + +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + if (!rule.hasBox()) + break; + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QRect editRect = subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, w); + p->save(); + p->setClipRect(editRect); + if (!cb->currentIcon.isNull()) { + int spacing = rule.hasBox() ? rule.box()->spacing : -1; + if (spacing == -1) + spacing = 6; + QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; + QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(cb->iconSize.width()); + iconRect = alignedRect(cb->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + drawItemPixmap(p, iconRect, Qt::AlignCenter, pixmap); + + if (cb->direction == Qt::RightToLeft) + editRect.translate(-spacing - cb->iconSize.width(), 0); + else + editRect.translate(cb->iconSize.width() + spacing, 0); + } + if (!cb->currentText.isEmpty() && !cb->editable) { + QPalette styledPalette(cb->palette); + rule.configurePalette(&styledPalette, QPalette::Text, QPalette::Base); + drawItemText(p, editRect.adjusted(0, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, styledPalette, + cb->state & State_Enabled, cb->currentText, QPalette::Text); + } + p->restore(); + return; + } + break; +#endif // QT_NO_COMBOBOX + + case CE_Header: + if (hasStyleRule(w, PseudoElement_HeaderViewUpArrow) + || hasStyleRule(w, PseudoElement_HeaderViewDownArrow)) { + ParentStyle::drawControl(ce, opt, p, w); + return; + } + if(hasStyleRule(w, PseudoElement_HeaderViewSection)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + if (!subRule.hasNativeBorder() || !subRule.baseStyleCanDraw() + || subRule.hasBackground() || subRule.hasPalette()) { + ParentStyle::drawControl(ce, opt, p, w); + return; + } + } + break; + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + if (subRule.hasNativeBorder()) { + QStyleOptionHeader hdr(*header); + subRule.configurePalette(&hdr.palette, QPalette::ButtonText, QPalette::Button); + + if (subRule.baseStyleCanDraw()) { + baseStyle()->drawControl(CE_HeaderSection, &hdr, p, w); + } else { + QWindowsStyle::drawControl(CE_HeaderSection, &hdr, p, w); + } + } else { + subRule.drawRule(p, opt->rect); + } + return; + } + break; + + case CE_HeaderLabel: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QStyleOptionHeader hdr(*header); + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + subRule.configurePalette(&hdr.palette, QPalette::ButtonText, QPalette::Button); + QFont oldFont = p->font(); + if (subRule.hasFont) + p->setFont(subRule.font.resolve(p->font())); + baseStyle()->drawControl(ce, &hdr, p, w); + if (subRule.hasFont) + p->setFont(oldFont); + return; + } + break; + + case CE_HeaderEmptyArea: + if (rule.hasDrawable()) { + return; + } + break; + + case CE_ProgressBar: + QWindowsStyle::drawControl(ce, opt, p, w); + return; + + case CE_ProgressBarGroove: + if (!rule.hasNativeBorder()) { + rule.drawRule(p, rule.boxRect(opt->rect, Margin)); + return; + } + break; + + case CE_ProgressBarContents: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ProgressBarChunk); + if (subRule.hasDrawable()) { + if (const QStyleOptionProgressBarV2 *pb = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + p->save(); + p->setClipRect(pb->rect); + + qint64 minimum = qint64(pb->minimum); + qint64 maximum = qint64(pb->maximum); + qint64 progress = qint64(pb->progress); + bool vertical = (pb->orientation == Qt::Vertical); + bool inverted = pb->invertedAppearance; + + QTransform m; + QRect rect = pb->rect; + if (vertical) { + rect = QRect(rect.y(), rect.x(), rect.height(), rect.width()); + m.rotate(90); + m.translate(0, -(rect.height() + rect.y()*2)); + } + + bool reverse = ((!vertical && (pb->direction == Qt::RightToLeft)) || vertical); + if (inverted) + reverse = !reverse; + const bool indeterminate = pb->minimum == pb->maximum; + qreal fillRatio = indeterminate ? 0.50 : qreal(progress - minimum)/(maximum - minimum); + int fillWidth = int(rect.width() * fillRatio); + int chunkWidth = fillWidth; + if (subRule.hasContentsSize()) { + QSize sz = subRule.size(); + chunkWidth = (opt->state & QStyle::State_Horizontal) ? sz.width() : sz.height(); + } + + QRect r = rect; + if (pb->minimum == 0 && pb->maximum == 0) { + Q_D(const QWindowsStyle); + int chunkCount = fillWidth/chunkWidth; + int offset = (d->animateStep*8%rect.width()); + int x = reverse ? r.left() + r.width() - offset - chunkWidth : r.x() + offset; + while (chunkCount > 0) { + r.setRect(x, rect.y(), chunkWidth, rect.height()); + r = m.mapRect(QRectF(r)).toRect(); + subRule.drawRule(p, r); + x += reverse ? -chunkWidth : chunkWidth; + if (reverse ? x < rect.left() : x > rect.right()) + break; + --chunkCount; + } + + r = rect; + x = reverse ? r.right() - (r.left() - x - chunkWidth) + : r.left() + (x - r.right() - chunkWidth); + while (chunkCount > 0) { + r.setRect(x, rect.y(), chunkWidth, rect.height()); + r = m.mapRect(QRectF(r)).toRect(); + subRule.drawRule(p, r); + x += reverse ? -chunkWidth : chunkWidth; + --chunkCount; + }; + } else { + int x = reverse ? r.left() + r.width() - chunkWidth : r.x(); + + for (int i = 0; i < ceil(qreal(fillWidth)/chunkWidth); ++i) { + r.setRect(x, rect.y(), chunkWidth, rect.height()); + r = m.mapRect(QRectF(r)).toRect(); + subRule.drawRule(p, r); + x += reverse ? -chunkWidth : chunkWidth; + } + } + + p->restore(); + return; + } + } + } + break; + + case CE_ProgressBarLabel: + if (const QStyleOptionProgressBarV2 *pb = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + if (rule.hasBox() || rule.hasBorder() || hasStyleRule(w, PseudoElement_ProgressBarChunk)) { + drawItemText(p, pb->rect, pb->textAlignment | Qt::TextSingleLine, pb->palette, + pb->state & State_Enabled, pb->text, QPalette::Text); + } else { + QStyleOptionProgressBarV2 pbCopy(*pb); + rule.configurePalette(&pbCopy.palette, QPalette::HighlightedText, QPalette::Highlight); + baseStyle()->drawControl(ce, &pbCopy, p, w); + } + return; + } + break; + + case CE_SizeGrip: + if (const QStyleOptionSizeGrip *sgOpt = qstyleoption_cast<const QStyleOptionSizeGrip *>(opt)) { + if (rule.hasDrawable()) { + rule.drawFrame(p, opt->rect); + p->save(); + switch (sgOpt->corner) { + case Qt::BottomRightCorner: break; + case Qt::BottomLeftCorner: p->rotate(90); break; + case Qt::TopLeftCorner: p->rotate(180); break; + case Qt::TopRightCorner: p->rotate(270); break; + default: break; + } + rule.drawImage(p, opt->rect); + p->restore(); + } else { + QStyleOptionSizeGrip sg(*sgOpt); + sg.rect = rule.contentsRect(opt->rect); + baseStyle()->drawControl(CE_SizeGrip, &sg, p, w); + } + return; + } + break; + + case CE_ToolBoxTab: + QWindowsStyle::drawControl(ce, opt, p, w); + return; + + case CE_ToolBoxTabShape: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolBoxTab); + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + return; + } + } + break; + + case CE_ToolBoxTabLabel: + if (const QStyleOptionToolBox *box = qstyleoption_cast<const QStyleOptionToolBox *>(opt)) { + QStyleOptionToolBox boxCopy(*box); + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolBoxTab); + subRule.configurePalette(&boxCopy.palette, QPalette::ButtonText, QPalette::Button); + QFont oldFont = p->font(); + if (subRule.hasFont) + p->setFont(subRule.font); + boxCopy.rect = subRule.contentsRect(opt->rect); + QWindowsStyle::drawControl(ce, &boxCopy, p , w); + if (subRule.hasFont) + p->setFont(oldFont); + return; + } + break; + + case CE_ScrollBarAddPage: + pe1 = PseudoElement_ScrollBarAddPage; + break; + + case CE_ScrollBarSubPage: + pe1 = PseudoElement_ScrollBarSubPage; + break; + + case CE_ScrollBarAddLine: + pe1 = PseudoElement_ScrollBarAddLine; + pe2 = (opt->state & QStyle::State_Horizontal) ? PseudoElement_ScrollBarRightArrow : PseudoElement_ScrollBarDownArrow; + fallback = true; + break; + + case CE_ScrollBarSubLine: + pe1 = PseudoElement_ScrollBarSubLine; + pe2 = (opt->state & QStyle::State_Horizontal) ? PseudoElement_ScrollBarLeftArrow : PseudoElement_ScrollBarUpArrow; + fallback = true; + break; + + case CE_ScrollBarFirst: + pe1 = PseudoElement_ScrollBarFirst; + break; + + case CE_ScrollBarLast: + pe1 = PseudoElement_ScrollBarLast; + break; + + case CE_ScrollBarSlider: + pe1 = PseudoElement_ScrollBarSlider; + fallback = true; + break; + +#ifndef QT_NO_ITEMVIEWS + case CE_ItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ViewItem); + if (subRule.hasDrawable() || hasStyleRule(w, PseudoElement_Indicator)) { + QStyleOptionViewItemV4 optCopy(*vopt); + subRule.configurePalette(&optCopy.palette, vopt->state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text, + vopt->state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base); + QWindowsStyle::drawControl(ce, &optCopy, p, w); + } else { + QStyleOptionViewItemV4 voptCopy(*vopt); + subRule.configurePalette(&voptCopy.palette, QPalette::Text, QPalette::NoRole); + baseStyle()->drawControl(ce, &voptCopy, p, w); + } + return; + } + break; +#endif // QT_NO_ITEMVIEWS + +#ifndef QT_NO_TABBAR + case CE_TabBarTab: + if (hasStyleRule(w, PseudoElement_TabBarTab)) { + QWindowsStyle::drawControl(ce, opt, p, w); + return; + } + break; + + case CE_TabBarTabLabel: + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + QStyleOptionTabV3 tabCopy(*tab); + QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTab); + QRect r = positionRect(w, subRule, PseudoElement_TabBarTab, opt->rect, opt->direction); + if (ce == CE_TabBarTabShape && subRule.hasDrawable()) { + subRule.drawRule(p, r); + return; + } + subRule.configurePalette(&tabCopy.palette, QPalette::WindowText, QPalette::Window); + QFont oldFont = p->font(); + if (subRule.hasFont) + p->setFont(subRule.font); + if (subRule.hasBox() || !subRule.hasNativeBorder()) { + tabCopy.rect = ce == CE_TabBarTabShape ? subRule.borderRect(r) + : subRule.contentsRect(r); + QWindowsStyle::drawControl(ce, &tabCopy, p, w); + } else { + baseStyle()->drawControl(ce, &tabCopy, p, w); + } + if (subRule.hasFont) + p->setFont(oldFont); + + return; + } + break; +#endif // QT_NO_TABBAR + + case CE_ColumnViewGrip: + if (rule.hasDrawable()) { + rule.drawRule(p, opt->rect); + return; + } + break; + + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidgetV2 *dwOpt = qstyleoption_cast<const QStyleOptionDockWidgetV2 *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_DockWidgetTitle); + if (!subRule.hasDrawable() && !subRule.hasPosition()) + break; + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + } else { + QStyleOptionDockWidgetV2 dwCopy(*dwOpt); + dwCopy.title = QString(); + baseStyle()->drawControl(ce, &dwCopy, p, w); + } + + if (!dwOpt->title.isEmpty()) { + QRect r = opt->rect; + if (dwOpt->verticalTitleBar) { + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + p->save(); + p->translate(r.left(), r.top() + r.width()); + p->rotate(-90); + p->translate(-r.left(), -r.top()); + } + + Qt::Alignment alignment = 0; + if (subRule.hasPosition()) + alignment = subRule.position()->textAlignment; + if (alignment == 0) + alignment = Qt::AlignLeft; + drawItemText(p, subRule.contentsRect(opt->rect), + alignment | Qt::TextShowMnemonic, dwOpt->palette, + dwOpt->state & State_Enabled, dwOpt->title, + QPalette::WindowText); + + if (dwOpt->verticalTitleBar) + p->restore(); + } + + return; + } + break; + case CE_ShapedFrame: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (rule.hasNativeBorder()) { + QStyleOptionFrameV3 frmOpt(*frm); + rule.configurePalette(&frmOpt.palette, QPalette::Text, QPalette::Base); + frmOpt.rect = rule.borderRect(frmOpt.rect); + baseStyle()->drawControl(ce, &frmOpt, p, w); + } + // else, borders are already drawn in PE_Widget + } + return; + + + default: + break; + } + + if (pe1 != PseudoElement_None) { + QRenderRule subRule = renderRule(w, opt, pe1); + if (subRule.bg != 0 || subRule.hasDrawable()) { + //We test subRule.bg directly because hasBackground() would return false for background:none. + //But we still don't want the default drawning in that case (example for QScrollBar::add-page) (task 198926) + subRule.drawRule(p, opt->rect); + } else if (fallback) { + QWindowsStyle::drawControl(ce, opt, p, w); + pe2 = PseudoElement_None; + } else { + baseStyle()->drawControl(ce, opt, p, w); + } + if (pe2 != PseudoElement_None) { + QRenderRule subSubRule = renderRule(w, opt, pe2); + QRect r = positionRect(w, subRule, subSubRule, pe2, opt->rect, opt->direction); + subSubRule.drawRule(p, r); + } + return; + } + + baseStyle()->drawControl(ce, opt, p, w); +} + +void QStyleSheetStyle::drawItemPixmap(QPainter *p, const QRect &rect, int alignment, const + QPixmap &pixmap) const +{ + baseStyle()->drawItemPixmap(p, rect, alignment, pixmap); +} + +void QStyleSheetStyle::drawItemText(QPainter *painter, const QRect& rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const +{ + baseStyle()->drawItemText(painter, rect, alignment, pal, enabled, text, textRole); +} + +void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + RECURSION_GUARD(baseStyle()->drawPrimitive(pe, opt, p, w); return) + + int pseudoElement = PseudoElement_None; + QRenderRule rule = renderRule(w, opt); + QRect rect = opt->rect; + + switch (pe) { + + case PE_FrameStatusBar: { + QRenderRule subRule = renderRule(w->parentWidget(), opt, PseudoElement_Item); + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + return; + } + break; + } + + case PE_IndicatorArrowDown: + pseudoElement = PseudoElement_DownArrow; + break; + + case PE_IndicatorArrowUp: + pseudoElement = PseudoElement_UpArrow; + break; + + case PE_IndicatorRadioButton: + pseudoElement = PseudoElement_ExclusiveIndicator; + break; + + case PE_IndicatorViewItemCheck: + pseudoElement = PseudoElement_ViewItemIndicator; + break; + + case PE_IndicatorCheckBox: + pseudoElement = PseudoElement_Indicator; + break; + + case PE_IndicatorHeaderArrow: + if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + pseudoElement = hdr->sortIndicator == QStyleOptionHeader::SortUp + ? PseudoElement_HeaderViewUpArrow + : PseudoElement_HeaderViewDownArrow; + } + break; + + case PE_PanelButtonTool: + case PE_PanelButtonCommand: + if (qobject_cast<const QAbstractButton *>(w) && rule.hasBackground() && rule.hasNativeBorder()) { + //the window style will draw the borders + ParentStyle::drawPrimitive(pe, opt, p, w); + if (!rule.background()->pixmap.isNull() || rule.hasImage()) { + rule.drawRule(p, rule.boxRect(opt->rect, QRenderRule::Margin).adjusted(1,1,-1,-1)); + } + return; + } + if (!rule.hasNativeBorder()) { + rule.drawRule(p, rule.boxRect(opt->rect, QRenderRule::Margin)); + return; + } + break; + + case PE_IndicatorButtonDropDown: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); + if (!subRule.hasNativeBorder()) { + rule.drawBorder(p, opt->rect); + return; + } + break; + } + + case PE_FrameDefaultButton: + if (rule.hasNativeBorder()) { + if (rule.baseStyleCanDraw()) + break; + QWindowsStyle::drawPrimitive(pe, opt, p, w); + } + return; + + case PE_FrameWindow: + case PE_FrameDockWidget: + case PE_Frame: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (rule.hasNativeBorder()) { + QStyleOptionFrameV2 frmOpt(*frm); + rule.configurePalette(&frmOpt.palette, QPalette::Text, QPalette::Base); + if (!qstyleoption_cast<const QStyleOptionFrameV3 *>(opt)) //if it comes from CE_ShapedFrame, the margins are already sustracted + frmOpt.rect = rule.borderRect(frmOpt.rect); + baseStyle()->drawPrimitive(pe, &frmOpt, p, w); + } else { + rule.drawBorder(p, rule.borderRect(opt->rect)); + } + } + return; + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { +#ifndef QT_NO_SPINBOX + if (w && qobject_cast<const QAbstractSpinBox *>(w->parentWidget())) { + QRenderRule spinboxRule = renderRule(w->parentWidget(), opt); + if (!spinboxRule.hasNativeBorder() || !spinboxRule.baseStyleCanDraw()) + return; + rule = spinboxRule; + } +#endif + if (rule.hasNativeBorder()) { + QStyleOptionFrame frmOpt(*frm); + rule.configurePalette(&frmOpt.palette, QPalette::Text, QPalette::Base); + frmOpt.rect = rule.borderRect(frmOpt.rect); + if (rule.baseStyleCanDraw()) { + rule.drawBackgroundImage(p, opt->rect); + baseStyle()->drawPrimitive(pe, &frmOpt, p, w); + } else { + rule.drawBackground(p, opt->rect); + if (frmOpt.lineWidth > 0) + baseStyle()->drawPrimitive(PE_FrameLineEdit, &frmOpt, p, w); + } + } else { + rule.drawRule(p, opt->rect); + } + } + return; + + case PE_Widget: + if (w && !rule.hasDrawable()) { + QWidget *container = containerWidget(w); + if (styleSheetCaches->autoFillDisabledWidgets.contains(container) + && (container == w || !renderRule(container, opt).hasBackground())) { + //we do not have a background, but we disabled the autofillbackground anyway. so fill the background now. + // (this may happen if we have rules like :focus) + p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole())); + } + break; + } +#ifndef QT_NO_SCROLLAREA + if (const QAbstractScrollArea *sa = qobject_cast<const QAbstractScrollArea *>(w)) { + const QAbstractScrollAreaPrivate *sap = sa->d_func(); + rule.drawBackground(p, opt->rect, sap->contentsOffset()); + if (rule.hasBorder()) { + QRect brect = rule.borderRect(opt->rect); + if (styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, opt, w)) { + QRect r = brect.adjusted(0, 0, sa->verticalScrollBar()->isVisible() ? -sa->verticalScrollBar()->width() : 0, + sa->horizontalScrollBar()->isVisible() ? -sa->horizontalScrollBar()->height() : 0); + brect = QStyle::visualRect(opt->direction, brect, r); + } + rule.drawBorder(p, brect); + } + break; + } +#endif + //fall tghought + case PE_PanelMenu: + case PE_PanelStatusBar: + if(rule.hasDrawable()) { + rule.drawRule(p, opt->rect); + return; + } + break; + + case PE_FrameMenu: + if (rule.hasDrawable()) { + // Drawn by PE_PanelMenu + return; + } + break; + + case PE_PanelMenuBar: + if (rule.hasDrawable()) { + // Drawn by PE_Widget + return; + } + break; + + case PE_IndicatorToolBarSeparator: + case PE_IndicatorToolBarHandle: { + PseudoElement ps = pe == PE_IndicatorToolBarHandle ? PseudoElement_ToolBarHandle : PseudoElement_ToolBarSeparator; + QRenderRule subRule = renderRule(w, opt, ps); + if (subRule.hasDrawable()) { + subRule.drawRule(p, opt->rect); + return; + } + } + break; + + case PE_IndicatorMenuCheckMark: + pseudoElement = PseudoElement_MenuCheckMark; + break; + + case PE_IndicatorArrowLeft: + pseudoElement = PseudoElement_LeftArrow; + break; + + case PE_IndicatorArrowRight: + pseudoElement = PseudoElement_RightArrow; + break; + + case PE_IndicatorColumnViewArrow: + if (const QStyleOptionViewItem *viewOpt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { + bool reverse = (viewOpt->direction == Qt::RightToLeft); + pseudoElement = reverse ? PseudoElement_LeftArrow : PseudoElement_RightArrow; + } else { + pseudoElement = PseudoElement_RightArrow; + } + break; + + case PE_IndicatorBranch: + if (const QStyleOptionViewItemV2 *v2 = qstyleoption_cast<const QStyleOptionViewItemV2 *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TreeViewBranch); + if (subRule.hasDrawable()) { + if ((v2->state & QStyle::State_Selected) && v2->showDecorationSelected) + p->fillRect(v2->rect, v2->palette.highlight()); + else if (v2->features & QStyleOptionViewItemV2::Alternate) + p->fillRect(v2->rect, v2->palette.alternateBase()); + subRule.drawRule(p, opt->rect); + } else { + baseStyle()->drawPrimitive(pe, v2, p, w); + } + } + return; + + case PE_PanelTipLabel: + if (!rule.hasDrawable()) + break; + + if (const QStyleOptionFrame *frmOpt = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (rule.hasNativeBorder()) { + rule.drawBackground(p, opt->rect); + QStyleOptionFrame optCopy(*frmOpt); + optCopy.rect = rule.borderRect(opt->rect); + optCopy.palette.setBrush(QPalette::Window, Qt::NoBrush); // oh dear + baseStyle()->drawPrimitive(pe, &optCopy, p, w); + } else { + rule.drawRule(p, opt->rect); + } + } + return; + + case PE_FrameGroupBox: + if (rule.hasNativeBorder()) + break; + rule.drawBorder(p, opt->rect); + return; + +#ifndef QT_NO_TABWIDGET + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *frm = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TabWidgetPane); + if (subRule.hasNativeBorder()) { + subRule.drawBackground(p, opt->rect); + QStyleOptionTabWidgetFrameV2 frmCopy(*frm); + subRule.configurePalette(&frmCopy.palette, QPalette::WindowText, QPalette::Window); + baseStyle()->drawPrimitive(pe, &frmCopy, p, w); + } else { + subRule.drawRule(p, opt->rect); + } + return; + } + break; +#endif // QT_NO_TABWIDGET + + case PE_IndicatorProgressChunk: + pseudoElement = PseudoElement_ProgressBarChunk; + break; + + case PE_IndicatorTabTear: + pseudoElement = PseudoElement_TabBarTear; + break; + + case PE_FrameFocusRect: + if (!rule.hasNativeOutline()) { + rule.drawOutline(p, opt->rect); + return; + } + break; + + case PE_IndicatorDockWidgetResizeHandle: + pseudoElement = PseudoElement_DockWidgetSeparator; + break; + + case PE_PanelItemViewItem: + pseudoElement = PseudoElement_ViewItem; + break; + + case PE_PanelScrollAreaCorner: + pseudoElement = PseudoElement_ScrollAreaCorner; + break; + + case PE_IndicatorSpinDown: + case PE_IndicatorSpinMinus: + pseudoElement = PseudoElement_SpinBoxDownArrow; + break; + + case PE_IndicatorSpinUp: + case PE_IndicatorSpinPlus: + pseudoElement = PseudoElement_SpinBoxUpArrow; + break; +#ifndef QT_NO_TABBAR + case PE_IndicatorTabClose: + if (w) + w = w->parentWidget(); //match on the QTabBar instead of the CloseButton + pseudoElement = PseudoElement_TabBarTabCloseButton; +#endif + + default: + break; + } + + if (pseudoElement != PseudoElement_None) { + QRenderRule subRule = renderRule(w, opt, pseudoElement); + if (subRule.hasDrawable()) { + subRule.drawRule(p, rect); + } else { + baseStyle()->drawPrimitive(pe, opt, p, w); + } + } else { + baseStyle()->drawPrimitive(pe, opt, p, w); + } +} + +QPixmap QStyleSheetStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, + const QStyleOption *option) const +{ + return baseStyle()->generatedIconPixmap(iconMode, pixmap, option); +} + +QStyle::SubControl QStyleSheetStyle::hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->hitTestComplexControl(cc, opt, pt, w)) + switch (cc) { + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + QRenderRule rule = renderRule(w, opt, PseudoElement_TitleBar); + if (rule.hasDrawable() || rule.hasBox() || rule.hasBorder()) { + QHash<QStyle::SubControl, QRect> layout = titleBarLayout(w, tb); + QRect r; + QStyle::SubControl sc = QStyle::SC_None; + uint ctrl = SC_TitleBarSysMenu; + while (ctrl <= SC_TitleBarLabel) { + r = layout[QStyle::SubControl(ctrl)]; + if (r.isValid() && r.contains(pt)) { + sc = QStyle::SubControl(ctrl); + break; + } + ctrl <<= 1; + } + return sc; + } + } + break; + + case CC_MdiControls: + if (hasStyleRule(w, PseudoElement_MdiCloseButton) + || hasStyleRule(w, PseudoElement_MdiNormalButton) + || hasStyleRule(w, PseudoElement_MdiMinButton)) + return QWindowsStyle::hitTestComplexControl(cc, opt, pt, w); + break; + + case CC_ScrollBar: { + QRenderRule rule = renderRule(w, opt); + if (!rule.hasDrawable() && !rule.hasBox()) + break; + } + // intentionally falls through + case CC_SpinBox: + case CC_GroupBox: + case CC_ComboBox: + case CC_Slider: + case CC_ToolButton: + return QWindowsStyle::hitTestComplexControl(cc, opt, pt, w); + default: + break; + } + + return baseStyle()->hitTestComplexControl(cc, opt, pt, w); +} + +QRect QStyleSheetStyle::itemPixmapRect(const QRect &rect, int alignment, const QPixmap &pixmap) const +{ + return baseStyle()->itemPixmapRect(rect, alignment, pixmap); +} + +QRect QStyleSheetStyle::itemTextRect(const QFontMetrics &metrics, const QRect& rect, int alignment, + bool enabled, const QString& text) const +{ + return baseStyle()->itemTextRect(metrics, rect, alignment, enabled, text); +} + +int QStyleSheetStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->pixelMetric(m, opt, w)) + + QRenderRule rule = renderRule(w, opt); + QRenderRule subRule; + + switch (m) { + case PM_MenuButtonIndicator: +#ifndef QT_NO_TOOLBUTTON + // QToolButton adds this directly to the width + if (qobject_cast<const QToolButton *>(w) && (rule.hasBox() || !rule.hasNativeBorder())) + return 0; +#endif + subRule = renderRule(w, opt, PseudoElement_PushButtonMenuIndicator); + if (subRule.hasContentsSize()) + return subRule.size().width(); + break; + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + case PM_ButtonMargin: + case PM_ButtonDefaultIndicator: + if (rule.hasBox()) + return 0; + break; + + case PM_DefaultFrameWidth: + if (!rule.hasNativeBorder()) + return rule.border()->borders[LeftEdge]; + break; + + case PM_ExclusiveIndicatorWidth: + case PM_IndicatorWidth: + case PM_ExclusiveIndicatorHeight: + case PM_IndicatorHeight: + subRule = renderRule(w, opt, PseudoElement_Indicator); + if (subRule.hasContentsSize()) { + return (m == PM_ExclusiveIndicatorWidth) || (m == PM_IndicatorWidth) + ? subRule.size().width() : subRule.size().height(); + } + break; + + case PM_DockWidgetFrameWidth: + case PM_ToolTipLabelFrameWidth: // border + margin + padding (support only one width) + if (!rule.hasDrawable()) + break; + + return (rule.border() ? rule.border()->borders[LeftEdge] : 0) + + (rule.hasBox() ? rule.box()->margins[LeftEdge] + rule.box()->paddings[LeftEdge]: 0); + + case PM_ToolBarFrameWidth: + if (rule.hasBorder() || rule.hasBox()) + return (rule.border() ? rule.border()->borders[LeftEdge] : 0) + + (rule.hasBox() ? rule.box()->paddings[LeftEdge]: 0); + break; + + case PM_MenuPanelWidth: + case PM_MenuBarPanelWidth: + if (rule.hasBorder() || rule.hasBox()) + return (rule.border() ? rule.border()->borders[LeftEdge] : 0) + + (rule.hasBox() ? rule.box()->margins[LeftEdge]: 0); + break; + + + case PM_MenuHMargin: + case PM_MenuBarHMargin: + if (rule.hasBox()) + return rule.box()->paddings[LeftEdge]; + break; + + case PM_MenuVMargin: + case PM_MenuBarVMargin: + if (rule.hasBox()) + return rule.box()->paddings[TopEdge]; + break; + + case PM_DockWidgetTitleBarButtonMargin: + case PM_ToolBarItemMargin: + if (rule.hasBox()) + return rule.box()->margins[TopEdge]; + break; + + case PM_ToolBarItemSpacing: + case PM_MenuBarItemSpacing: + if (rule.hasBox() && rule.box()->spacing != -1) + return rule.box()->spacing; + break; + + case PM_MenuTearoffHeight: + case PM_MenuScrollerHeight: { + PseudoElement ps = m == PM_MenuTearoffHeight ? PseudoElement_MenuTearoff : PseudoElement_MenuScroller; + subRule = renderRule(w, opt, ps); + if (subRule.hasContentsSize()) + return subRule.size().height(); + break; + } + + case PM_ToolBarExtensionExtent: + break; + + case PM_SplitterWidth: + case PM_ToolBarSeparatorExtent: + case PM_ToolBarHandleExtent: { + PseudoElement ps; + if (m == PM_ToolBarHandleExtent) ps = PseudoElement_ToolBarHandle; + else if (m == PM_SplitterWidth) ps = PseudoElement_SplitterHandle; + else ps = PseudoElement_ToolBarSeparator; + subRule = renderRule(w, opt, ps); + if (subRule.hasContentsSize()) { + QSize sz = subRule.size(); + return (opt && opt->state & QStyle::State_Horizontal) ? sz.width() : sz.height(); + } + break; + } + + case PM_RadioButtonLabelSpacing: + if (rule.hasBox() && rule.box()->spacing != -1) + return rule.box()->spacing; + break; + case PM_CheckBoxLabelSpacing: + if (qobject_cast<const QCheckBox *>(w)) { + if (rule.hasBox() && rule.box()->spacing != -1) + return rule.box()->spacing; + } + // assume group box + subRule = renderRule(w, opt, PseudoElement_GroupBoxTitle); + if (subRule.hasBox() && subRule.box()->spacing != -1) + return subRule.box()->spacing; + break; + +#ifndef QT_NO_SCROLLBAR + case PM_ScrollBarExtent: + if (rule.hasContentsSize()) { + QSize sz = rule.size(); + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) + return sb->orientation == Qt::Horizontal ? sz.height() : sz.width(); + return sz.width() == -1 ? sz.height() : sz.width(); + } + break; + + case PM_ScrollBarSliderMin: + if (hasStyleRule(w, PseudoElement_ScrollBarSlider)) { + subRule = renderRule(w, opt, PseudoElement_ScrollBarSlider); + QSize msz = subRule.minimumSize(); + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) + return sb->orientation == Qt::Horizontal ? msz.width() : msz.height(); + return msz.width() == -1 ? msz.height() : msz.width(); + } + break; + + case PM_ScrollView_ScrollBarSpacing: + if(!rule.hasNativeBorder() || rule.hasBox()) + return 0; + break; +#endif // QT_NO_SCROLLBAR + + case PM_ProgressBarChunkWidth: + subRule = renderRule(w, opt, PseudoElement_ProgressBarChunk); + if (subRule.hasContentsSize()) { + QSize sz = subRule.size(); + return (opt->state & QStyle::State_Horizontal) + ? sz.width() : sz.height(); + } + break; + +#ifndef QT_NO_TABWIDGET + case PM_TabBarTabHSpace: + case PM_TabBarTabVSpace: + subRule = renderRule(w, opt, PseudoElement_TabBarTab); + if (subRule.hasBox() || subRule.hasBorder()) + return 0; + break; + + case PM_TabBarScrollButtonWidth: { + subRule = renderRule(w, opt, PseudoElement_TabBarScroller); + if (subRule.hasContentsSize()) { + QSize sz = subRule.size(); + return sz.width() != -1 ? sz.width() : sz.height(); + } + } + break; + + case PM_TabBarTabShiftHorizontal: + case PM_TabBarTabShiftVertical: + subRule = renderRule(w, opt, PseudoElement_TabBarTab); + if (subRule.hasBox()) + return 0; + break; + + case PM_TabBarBaseOverlap: { + const QWidget *tabWidget = qobject_cast<const QTabWidget *>(w) ? w : w->parentWidget(); + if (hasStyleRule(tabWidget, PseudoElement_TabWidgetPane)) { + return 0; + } + break; + } +#endif // QT_NO_TABWIDGET + + case PM_SliderThickness: // horizontal slider's height (sizeHint) + case PM_SliderLength: // minimum length of slider + if (rule.hasContentsSize()) { + bool horizontal = opt->state & QStyle::State_Horizontal; + if (m == PM_SliderThickness) { + QSize sz = rule.size(); + return horizontal ? sz.height() : sz.width(); + } else { + QSize msz = rule.minimumContentsSize(); + return horizontal ? msz.width() : msz.height(); + } + } + break; + + case PM_SliderControlThickness: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_SliderHandle); + if (!subRule.hasContentsSize()) + break; + QSize size = subRule.size(); + return (opt->state & QStyle::State_Horizontal) ? size.height() : size.width(); + } + + case PM_ToolBarIconSize: + case PM_ListViewIconSize: + case PM_IconViewIconSize: + case PM_TabBarIconSize: + case PM_MessageBoxIconSize: + case PM_ButtonIconSize: + case PM_SmallIconSize: + if (rule.hasStyleHint(QLatin1String("icon-size"))) { + return rule.styleHint(QLatin1String("icon-size")).toSize().width(); + } + break; + + case PM_DockWidgetTitleMargin: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_DockWidgetTitle); + if (!subRule.hasBox()) + break; + return (subRule.border() ? subRule.border()->borders[TopEdge] : 0) + + (subRule.hasBox() ? subRule.box()->margins[TopEdge] + subRule.box()->paddings[TopEdge]: 0); + } + + case PM_DockWidgetSeparatorExtent: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_DockWidgetSeparator); + if (!subRule.hasContentsSize()) + break; + QSize sz = subRule.size(); + return qMax(sz.width(), sz.height()); + } + + case PM_TitleBarHeight: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TitleBar); + if (subRule.hasContentsSize()) + return subRule.size().height(); + else if (subRule.hasBox() || subRule.hasBorder()) { + QFontMetrics fm = opt ? opt->fontMetrics : w->fontMetrics(); + return subRule.size(QSize(0, fm.height())).height(); + } + break; + } + + case PM_MdiSubWindowFrameWidth: + if (rule.hasBox() || rule.hasBorder()) { + return (rule.border() ? rule.border()->borders[LeftEdge] : 0) + + (rule.hasBox() ? rule.box()->paddings[LeftEdge]+rule.box()->margins[LeftEdge]: 0); + } + break; + + case PM_MdiSubWindowMinimizedWidth: { + QRenderRule subRule = renderRule(w, PseudoElement_None, PseudoClass_Minimized); + int width = subRule.size().width(); + if (width != -1) + return width; + break; + } + default: + break; + } + + return baseStyle()->pixelMetric(m, opt, w); +} + +QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->sizeFromContents(ct, opt, csz, w)) + + QRenderRule rule = renderRule(w, opt); + QSize sz = rule.adjustSize(csz); + + switch (ct) { + case CT_SpinBox: // ### hopelessly broken QAbstractSpinBox (part 1) + if (rule.hasBox() || !rule.hasNativeBorder()) + return csz; + return rule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + case CT_ToolButton: + if (rule.hasBox() || !rule.hasNativeBorder() || !rule.baseStyleCanDraw()) + sz += QSize(3, 3); // ### broken QToolButton + //fall thought + case CT_ComboBox: + case CT_PushButton: + if (rule.hasBox() || !rule.hasNativeBorder()) { + if(ct == CT_ComboBox) { + //add some space for the drop down. + QRenderRule subRule = renderRule(w, opt, PseudoElement_ComboBoxDropDown); + QRect comboRect = positionRect(w, rule, subRule, PseudoElement_ComboBoxDropDown, opt->rect, opt->direction); + //+2 because there is hardcoded margins in QCommonStyle::drawControl(CE_ComboBoxLabel) + sz += QSize(comboRect.width() + 2, 0); + } + return rule.boxSize(sz); + } + sz = rule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + return rule.boxSize(sz, Margin); + + case CT_HeaderSection: { + if (const QStyleOptionHeader *hdr = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + if (subRule.hasGeometry() || subRule.hasBox() || !subRule.hasNativeBorder()) { + sz = subRule.adjustSize(csz); + if (!subRule.hasGeometry()) { + QSize nativeContentsSize; + bool nullIcon = hdr->icon.isNull(); + int iconSize = nullIcon ? 0 : pixelMetric(QStyle::PM_SmallIconSize, hdr, w); + QSize txt = hdr->fontMetrics.size(0, hdr->text); + nativeContentsSize.setHeight(qMax(iconSize, txt.height())); + nativeContentsSize.setWidth(iconSize + txt.width()); + sz = sz.expandedTo(nativeContentsSize); + } + return subRule.size(sz); + } + return subRule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + } + } + break; + case CT_GroupBox: + case CT_LineEdit: +#ifndef QT_NO_SPINBOX + // ### hopelessly broken QAbstractSpinBox (part 2) + if (QAbstractSpinBox *spinBox = qobject_cast<QAbstractSpinBox *>(w ? w->parentWidget() : 0)) { + QRenderRule rule = renderRule(spinBox, opt); + if (rule.hasBox() || !rule.hasNativeBorder()) + return csz; + return rule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + } +#endif + if (rule.hasBox() || !rule.hasNativeBorder()) { + return rule.boxSize(sz); + } + break; + + case CT_CheckBox: + case CT_RadioButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + if (rule.hasBox() || rule.hasBorder() || hasStyleRule(w, PseudoElement_Indicator)) { + bool isRadio = (ct == CT_RadioButton); + int iw = pixelMetric(isRadio ? PM_ExclusiveIndicatorWidth + : PM_IndicatorWidth, btn, w); + int ih = pixelMetric(isRadio ? PM_ExclusiveIndicatorHeight + : PM_IndicatorHeight, btn, w); + + int spacing = pixelMetric(isRadio ? PM_RadioButtonLabelSpacing + : PM_CheckBoxLabelSpacing, btn, w); + sz.setWidth(sz.width() + iw + spacing); + sz.setHeight(qMax(sz.height(), ih)); + return rule.boxSize(sz); + } + } + break; + + case CT_Menu: + case CT_MenuBar: // already has everything! + case CT_ScrollBar: + if (rule.hasBox() || rule.hasBorder()) + return sz; + break; + + case CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + PseudoElement pe = (mi->menuItemType == QStyleOptionMenuItem::Separator) + ? PseudoElement_MenuSeparator : PseudoElement_Item; + QRenderRule subRule = renderRule(w, opt, pe); + if ((pe == PseudoElement_MenuSeparator) && subRule.hasContentsSize()) { + return QSize(sz.width(), subRule.size().height()); + } else if ((pe == PseudoElement_Item) && (subRule.hasBox() || subRule.hasBorder())) { + int width = csz.width(); + if (mi->text.contains(QLatin1Char('\t'))) + width += 12; //as in QCommonStyle + return subRule.boxSize(subRule.adjustSize(QSize(width, csz.height()))); + } + } + break; + + case CT_Splitter: + case CT_MenuBarItem: { + PseudoElement pe = (ct == CT_Splitter) ? PseudoElement_SplitterHandle : PseudoElement_Item; + QRenderRule subRule = renderRule(w, opt, pe); + if (subRule.hasBox() || subRule.hasBorder()) + return subRule.boxSize(sz); + break; + } + + case CT_ProgressBar: + case CT_SizeGrip: + return (rule.hasContentsSize()) + ? rule.size(sz) + : rule.boxSize(baseStyle()->sizeFromContents(ct, opt, sz, w)); + break; + + case CT_Slider: + if (rule.hasBorder() || rule.hasBox() || rule.hasGeometry()) + return rule.boxSize(sz); + break; + +#ifndef QT_NO_TABBAR + case CT_TabBarTab: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTab); + if (subRule.hasBox() || !subRule.hasNativeBorder()) { + int spaceForIcon = 0; + bool vertical = false; + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + if (!tab->icon.isNull()) + spaceForIcon = 6 /* icon offset */ + 4 /* spacing */ + 2 /* magic */; // ###: hardcoded to match with common style + vertical = verticalTabs(tab->shape); + } + sz = csz + QSize(vertical ? 0 : spaceForIcon, vertical ? spaceForIcon : 0); + return subRule.boxSize(subRule.adjustSize(sz)); + } +#ifdef Q_WS_MAC + if (baseStyle()->inherits("QMacStyle")) { + //adjust the size after the call to the style because the mac style ignore the size arguments anyway. + //this might cause the (max-){width,height} property to include the native style border while they should not. + return subRule.adjustSize(baseStyle()->sizeFromContents(ct, opt, csz, w)); + } +#endif + sz = subRule.adjustSize(csz); + break; + } +#endif // QT_NO_TABBAR + + case CT_MdiControls: + if (const QStyleOptionComplex *ccOpt = qstyleoption_cast<const QStyleOptionComplex *>(opt)) { + if (!hasStyleRule(w, PseudoElement_MdiCloseButton) + && !hasStyleRule(w, PseudoElement_MdiNormalButton) + && !hasStyleRule(w, PseudoElement_MdiMinButton)) + break; + + QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + if (layout.isEmpty()) + layout = subControlLayout(QLatin1String("mNX")); + + int width = 0, height = 0; + for (int i = 0; i < layout.count(); i++) { + int layoutButton = layout[i].toInt(); + if (layoutButton < PseudoElement_MdiCloseButton + || layoutButton > PseudoElement_MdiNormalButton) + continue; + QStyle::SubControl sc = knownPseudoElements[layoutButton].subControl; + if (!(ccOpt->subControls & sc)) + continue; + QRenderRule subRule = renderRule(w, opt, layoutButton); + QSize sz = subRule.size(); + width += sz.width(); + height = qMax(height, sz.height()); + } + + return QSize(width, height); + } + break; + +#ifndef QT_NO_ITEMVIEWS + case CT_ItemViewItem: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ViewItem); + sz = baseStyle()->sizeFromContents(ct, opt, csz, w); + sz = subRule.adjustSize(sz); + if (subRule.hasBox() || subRule.hasBorder()) + sz = subRule.boxSize(sz); + return sz; + } +#endif // QT_NO_ITEMVIEWS + + default: + break; + } + + return baseStyle()->sizeFromContents(ct, opt, sz, w); +} + +/*! + \internal +*/ +static QLatin1String propertyNameForStandardPixmap(QStyle::StandardPixmap sp) +{ + switch (sp) { + case QStyle::SP_TitleBarMenuButton: return QLatin1String("titlebar-menu-icon"); + case QStyle::SP_TitleBarMinButton: return QLatin1String("titlebar-minimize-icon"); + case QStyle::SP_TitleBarMaxButton: return QLatin1String("titlebar-maximize-icon"); + case QStyle::SP_TitleBarCloseButton: return QLatin1String("titlebar-close-icon"); + case QStyle::SP_TitleBarNormalButton: return QLatin1String("titlebar-normal-icon"); + case QStyle::SP_TitleBarShadeButton: return QLatin1String("titlebar-shade-icon"); + case QStyle::SP_TitleBarUnshadeButton: return QLatin1String("titlebar-unshade-icon"); + case QStyle::SP_TitleBarContextHelpButton: return QLatin1String("titlebar-contexthelp-icon"); + case QStyle::SP_DockWidgetCloseButton: return QLatin1String("dockwidget-close-icon"); + case QStyle::SP_MessageBoxInformation: return QLatin1String("messagebox-information-icon"); + case QStyle::SP_MessageBoxWarning: return QLatin1String("messagebox-warning-icon"); + case QStyle::SP_MessageBoxCritical: return QLatin1String("messagebox-critical-icon"); + case QStyle::SP_MessageBoxQuestion: return QLatin1String("messagebox-question-icon"); + case QStyle::SP_DesktopIcon: return QLatin1String("desktop-icon"); + case QStyle::SP_TrashIcon: return QLatin1String("trash-icon"); + case QStyle::SP_ComputerIcon: return QLatin1String("computer-icon"); + case QStyle::SP_DriveFDIcon: return QLatin1String("floppy-icon"); + case QStyle::SP_DriveHDIcon: return QLatin1String("harddisk-icon"); + case QStyle::SP_DriveCDIcon: return QLatin1String("cd-icon"); + case QStyle::SP_DriveDVDIcon: return QLatin1String("dvd-icon"); + case QStyle::SP_DriveNetIcon: return QLatin1String("network-icon"); + case QStyle::SP_DirOpenIcon: return QLatin1String("directory-open-icon"); + case QStyle::SP_DirClosedIcon: return QLatin1String("directory-closed-icon"); + case QStyle::SP_DirLinkIcon: return QLatin1String("directory-link-icon"); + case QStyle::SP_FileIcon: return QLatin1String("file-icon"); + case QStyle::SP_FileLinkIcon: return QLatin1String("file-link-icon"); + case QStyle::SP_FileDialogStart: return QLatin1String("filedialog-start-icon"); + case QStyle::SP_FileDialogEnd: return QLatin1String("filedialog-end-icon"); + case QStyle::SP_FileDialogToParent: return QLatin1String("filedialog-parent-directory-icon"); + case QStyle::SP_FileDialogNewFolder: return QLatin1String("filedialog-new-directory-icon"); + case QStyle::SP_FileDialogDetailedView: return QLatin1String("filedialog-detailedview-icon"); + case QStyle::SP_FileDialogInfoView: return QLatin1String("filedialog-infoview-icon"); + case QStyle::SP_FileDialogContentsView: return QLatin1String("filedialog-contentsview-icon"); + case QStyle::SP_FileDialogListView: return QLatin1String("filedialog-listview-icon"); + case QStyle::SP_FileDialogBack: return QLatin1String("filedialog-backward-icon"); + case QStyle::SP_DirIcon: return QLatin1String("directory-icon"); + case QStyle::SP_DialogOkButton: return QLatin1String("dialog-ok-icon"); + case QStyle::SP_DialogCancelButton: return QLatin1String("dialog-cancel-icon"); + case QStyle::SP_DialogHelpButton: return QLatin1String("dialog-help-icon"); + case QStyle::SP_DialogOpenButton: return QLatin1String("dialog-open-icon"); + case QStyle::SP_DialogSaveButton: return QLatin1String("dialog-save-icon"); + case QStyle::SP_DialogCloseButton: return QLatin1String("dialog-close-icon"); + case QStyle::SP_DialogApplyButton: return QLatin1String("dialog-apply-icon"); + case QStyle::SP_DialogResetButton: return QLatin1String("dialog-reset-icon"); + case QStyle::SP_DialogDiscardButton: return QLatin1String("discard-icon"); + case QStyle::SP_DialogYesButton: return QLatin1String("dialog-yes-icon"); + case QStyle::SP_DialogNoButton: return QLatin1String("dialog-no-icon"); + case QStyle::SP_ArrowUp: return QLatin1String("uparrow-icon"); + case QStyle::SP_ArrowDown: return QLatin1String("downarrow-icon"); + case QStyle::SP_ArrowLeft: return QLatin1String("leftarrow-icon"); + case QStyle::SP_ArrowRight: return QLatin1String("rightarrow-icon"); + case QStyle::SP_ArrowBack: return QLatin1String("backward-icon"); + case QStyle::SP_ArrowForward: return QLatin1String("forward-icon"); + case QStyle::SP_DirHomeIcon: return QLatin1String("home-icon"); + default: return QLatin1String(""); + } +} + +QIcon QStyleSheetStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt, + const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->standardIcon(standardIcon, opt, w)) + QString s = propertyNameForStandardPixmap(standardIcon); + if (!s.isEmpty()) { + QRenderRule rule = renderRule(w, opt); + if (rule.hasStyleHint(s)) + return qvariant_cast<QIcon>(rule.styleHint(s)); + } + return baseStyle()->standardIcon(standardIcon, opt, w); +} + +QPalette QStyleSheetStyle::standardPalette() const +{ + return baseStyle()->standardPalette(); +} + +QPixmap QStyleSheetStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->standardPixmap(standardPixmap, opt, w)) + QString s = propertyNameForStandardPixmap(standardPixmap); + if (!s.isEmpty()) { + QRenderRule rule = renderRule(w, opt); + if (rule.hasStyleHint(s)) { + QIcon icon = qvariant_cast<QIcon>(rule.styleHint(s)); + return icon.pixmap(16, 16); // ###: unhard-code this if someone complains + } + } + return baseStyle()->standardPixmap(standardPixmap, opt, w); +} + +int QStyleSheetStyle::layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, + Qt::Orientation orientation, const QStyleOption *option, + const QWidget *widget) const +{ + return baseStyle()->layoutSpacing(control1, control2, orientation, option, widget); +} + +int QStyleSheetStyle::layoutSpacingImplementation(QSizePolicy::ControlType control1 , + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption * option , + const QWidget * widget) const +{ + return baseStyle()->layoutSpacing(control1, control2, orientation, option, widget); +} + +int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, + QStyleHintReturn *shret) const +{ + RECURSION_GUARD(return baseStyle()->styleHint(sh, opt, w, shret)) + // Prevent endless loop if somebody use isActiveWindow property as selector. + // QWidget::isActiveWindow uses this styleHint to determine if the window is active or not + if (sh == SH_Widget_ShareActivation) + return baseStyle()->styleHint(sh, opt, w, shret); + + QRenderRule rule = renderRule(w, opt); + QString s; + switch (sh) { + case SH_LineEdit_PasswordCharacter: s = QLatin1String("lineedit-password-character"); break; + case SH_DitherDisabledText: s = QLatin1String("dither-disabled-text"); break; + case SH_EtchDisabledText: s = QLatin1String("etch-disabled-text"); break; + case SH_ItemView_ActivateItemOnSingleClick: s = QLatin1String("activate-on-singleclick"); break; + case SH_ItemView_ShowDecorationSelected: s = QLatin1String("show-decoration-selected"); break; + case SH_Table_GridLineColor: s = QLatin1String("gridline-color"); break; + case SH_DialogButtonLayout: s = QLatin1String("button-layout"); break; + case SH_ToolTipLabel_Opacity: s = QLatin1String("opacity"); break; + case SH_ComboBox_Popup: s = QLatin1String("combobox-popup"); break; + case SH_ComboBox_ListMouseTracking: s = QLatin1String("combobox-list-mousetracking"); break; + case SH_MenuBar_AltKeyNavigation: s = QLatin1String("menubar-altkey-navigation"); break; + case SH_Menu_Scrollable: s = QLatin1String("menu-scrollable"); break; + case SH_DrawMenuBarSeparator: s = QLatin1String("menubar-separator"); break; + case SH_MenuBar_MouseTracking: s = QLatin1String("mouse-tracking"); break; + case SH_SpinBox_ClickAutoRepeatRate: s = QLatin1String("spinbox-click-autorepeat-rate"); break; + case SH_SpinControls_DisableOnBounds: s = QLatin1String("spincontrol-disable-on-bounds"); break; + case SH_MessageBox_TextInteractionFlags: s = QLatin1String("messagebox-text-interaction-flags"); break; + case SH_ToolButton_PopupDelay: s = QLatin1String("toolbutton-popup-delay"); break; + case SH_ToolBox_SelectedPageTitleBold: + if (renderRule(w, opt, PseudoElement_ToolBoxTab).hasFont) + return 0; + break; + case SH_GroupBox_TextLabelColor: + if (rule.hasPalette() && rule.palette()->foreground.style() != Qt::NoBrush) + return rule.palette()->foreground.color().rgba(); + break; + case SH_ScrollView_FrameOnlyAroundContents: s = QLatin1String("scrollview-frame-around-contents"); break; + case SH_ScrollBar_ContextMenu: s = QLatin1String("scrollbar-contextmenu"); break; + case SH_ScrollBar_LeftClickAbsolutePosition: s = QLatin1String("scrollbar-leftclick-absolute-position"); break; + case SH_ScrollBar_MiddleClickAbsolutePosition: s = QLatin1String("scrollbar-middleclick-absolute-position"); break; + case SH_ScrollBar_RollBetweenButtons: s = QLatin1String("scrollbar-roll-between-buttons"); break; + case SH_ScrollBar_ScrollWhenPointerLeavesControl: s = QLatin1String("scrollbar-scroll-when-pointer-leaves-control"); break; + case SH_TabBar_Alignment: +#ifndef QT_NO_TABWIDGET + if (qobject_cast<const QTabWidget *>(w)) { + rule = renderRule(w, opt, PseudoElement_TabWidgetTabBar); + if (rule.hasPosition()) + return rule.position()->position; + } +#endif // QT_NO_TABWIDGET + s = QLatin1String("alignment"); + break; +#ifndef QT_NO_TABBAR + case SH_TabBar_CloseButtonPosition: + rule = renderRule(w, opt, PseudoElement_TabBarTabCloseButton); + if (rule.hasPosition()) { + Qt::Alignment align = rule.position()->position; + if (align & Qt::AlignLeft || align & Qt::AlignTop) + return QTabBar::LeftSide; + if (align & Qt::AlignRight || align & Qt::AlignBottom) + return QTabBar::RightSide; + } + break; +#endif + case SH_TabBar_ElideMode: s = QLatin1String("tabbar-elide-mode"); break; + case SH_TabBar_PreferNoArrows: s = QLatin1String("tabbar-prefer-no-arrows"); break; + case SH_ComboBox_PopupFrameStyle: +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox *>(w)) { + QAbstractItemView *view = w->findChild<QAbstractItemView *>(); + if (view) { + view->ensurePolished(); + QRenderRule subRule = renderRule(view, PseudoElement_None); + if (subRule.hasBox() || !subRule.hasNativeBorder()) + return QFrame::NoFrame; + } + } +#endif // QT_NO_COMBOBOX + break; + case SH_DialogButtonBox_ButtonsHaveIcons: s = QLatin1String("dialogbuttonbox-buttons-have-icons"); break; + case SH_Workspace_FillSpaceOnMaximize: s = QLatin1String("mdi-fill-space-on-maximize"); break; + case SH_TitleBar_NoBorder: + if (rule.hasBorder()) + return !rule.border()->borders[LeftEdge]; + break; + case SH_TitleBar_AutoRaise: { // plain absurd + QRenderRule subRule = renderRule(w, opt, PseudoElement_TitleBar); + if (subRule.hasDrawable()) + return 1; + break; + } + case SH_ItemView_ArrowKeysNavigateIntoChildren: s = QLatin1String("arrow-keys-navigate-into-children"); break; + case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: s = QLatin1String("paint-alternating-row-colors-for-empty-area"); break; + default: break; + } + if (!s.isEmpty() && rule.hasStyleHint(s)) { + return rule.styleHint(s).toInt(); + } + + return baseStyle()->styleHint(sh, opt, w, shret); +} + +QRect QStyleSheetStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->subControlRect(cc, opt, sc, w)) + + QRenderRule rule = renderRule(w, opt); + switch (cc) { + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + if (rule.hasBox() || !rule.hasNativeBorder()) { + switch (sc) { + case SC_ComboBoxFrame: return rule.borderRect(opt->rect); + case SC_ComboBoxEditField: + { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ComboBoxDropDown); + QRect r = rule.contentsRect(opt->rect); + QRect r2 = positionRect(w, rule, subRule, PseudoElement_ComboBoxDropDown, + opt->rect, opt->direction); + if (subRule.hasPosition() && subRule.position()->position & Qt::AlignLeft) { + return visualRect(opt->direction, r, r.adjusted(r2.width(),0,0,0)); + } else { + return visualRect(opt->direction, r, r.adjusted(0,0,-r2.width(),0)); + } + } + case SC_ComboBoxArrow: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ComboBoxDropDown); + return positionRect(w, rule, subRule, PseudoElement_ComboBoxDropDown, opt->rect, opt->direction); + } + case SC_ComboBoxListBoxPopup: + default: + return baseStyle()->subControlRect(cc, opt, sc, w); + } + } + + QStyleOptionComboBox comboBox(*cb); + comboBox.rect = rule.borderRect(opt->rect); + return rule.baseStyleCanDraw() ? baseStyle()->subControlRect(cc, &comboBox, sc, w) + : QWindowsStyle::subControlRect(cc, &comboBox, sc, w); + } + break; + +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spin = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QRenderRule upRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton); + QRenderRule downRule = renderRule(w, opt, PseudoElement_SpinBoxDownButton); + bool ruleMatch = rule.hasBox() || !rule.hasNativeBorder(); + bool upRuleMatch = upRule.hasGeometry() || upRule.hasPosition(); + bool downRuleMatch = downRule.hasGeometry() || upRule.hasPosition(); + if (ruleMatch || upRuleMatch || downRuleMatch) { + switch (sc) { + case SC_SpinBoxFrame: + return rule.borderRect(opt->rect); + case SC_SpinBoxEditField: + { + QRect r = rule.contentsRect(opt->rect); + // Use the widest button on each side to determine edit field size. + Qt::Alignment upAlign, downAlign; + + upAlign = upRule.hasPosition() ? upRule.position()->position + : Qt::Alignment(Qt::AlignRight); + upAlign = resolveAlignment(opt->direction, upAlign); + + downAlign = downRule.hasPosition() ? downRule.position()->position + : Qt::Alignment(Qt::AlignRight); + downAlign = resolveAlignment(opt->direction, downAlign); + + int upSize = subControlRect(CC_SpinBox, opt, SC_SpinBoxUp, w).width(); + int downSize = subControlRect(CC_SpinBox, opt, SC_SpinBoxDown, w).width(); + int widestL = qMax((upAlign & Qt::AlignLeft) ? upSize : 0, + (downAlign & Qt::AlignLeft) ? downSize : 0); + int widestR = qMax((upAlign & Qt::AlignRight) ? upSize : 0, + (downAlign & Qt::AlignRight) ? downSize : 0); + r.setRight(r.right() - widestR); + r.setLeft(r.left() + widestL); + return r; + } + case SC_SpinBoxDown: + if (downRuleMatch) + return positionRect(w, rule, downRule, PseudoElement_SpinBoxDownButton, + opt->rect, opt->direction); + break; + case SC_SpinBoxUp: + if (upRuleMatch) + return positionRect(w, rule, upRule, PseudoElement_SpinBoxUpButton, + opt->rect, opt->direction); + break; + default: + break; + } + + return baseStyle()->subControlRect(cc, opt, sc, w); + } + + QStyleOptionSpinBox spinBox(*spin); + spinBox.rect = rule.borderRect(opt->rect); + return rule.baseStyleCanDraw() ? baseStyle()->subControlRect(cc, &spinBox, sc, w) + : QWindowsStyle::subControlRect(cc, &spinBox, sc, w); + } + break; +#endif // QT_NO_SPINBOX + + case CC_GroupBox: + if (const QStyleOptionGroupBox *gb = qstyleoption_cast<const QStyleOptionGroupBox *>(opt)) { + switch (sc) { + case SC_GroupBoxFrame: + case SC_GroupBoxContents: { + if (rule.hasBox() || !rule.hasNativeBorder()) { + return sc == SC_GroupBoxFrame ? rule.borderRect(opt->rect) + : rule.contentsRect(opt->rect); + } + QStyleOptionGroupBox groupBox(*gb); + groupBox.rect = rule.borderRect(opt->rect); + return baseStyle()->subControlRect(cc, &groupBox, sc, w); + } + default: + case SC_GroupBoxLabel: + case SC_GroupBoxCheckBox: { + QRenderRule indRule = renderRule(w, opt, PseudoElement_GroupBoxIndicator); + QRenderRule labelRule = renderRule(w, opt, PseudoElement_GroupBoxTitle); + if (!labelRule.hasPosition() && !labelRule.hasGeometry() && !labelRule.hasBox() + && !labelRule.hasBorder() && !indRule.hasContentsSize()) { + QStyleOptionGroupBox groupBox(*gb); + groupBox.rect = rule.borderRect(opt->rect); + return baseStyle()->subControlRect(cc, &groupBox, sc, w); + } + int tw = opt->fontMetrics.width(gb->text); + int th = opt->fontMetrics.height(); + int spacing = pixelMetric(QStyle::PM_CheckBoxLabelSpacing, opt, w); + int iw = pixelMetric(QStyle::PM_IndicatorWidth, opt, w); + int ih = pixelMetric(QStyle::PM_IndicatorHeight, opt, w); + + if (gb->subControls & QStyle::SC_GroupBoxCheckBox) { + tw = tw + iw + spacing; + th = qMax(th, ih); + } + if (!labelRule.hasGeometry()) { + labelRule.geo = new QStyleSheetGeometryData(tw, th, tw, th, -1, -1); + } else { + labelRule.geo->width = tw; + labelRule.geo->height = th; + } + if (!labelRule.hasPosition()) { + labelRule.p = new QStyleSheetPositionData(0, 0, 0, 0, defaultOrigin(PseudoElement_GroupBoxTitle), + gb->textAlignment, PositionMode_Static); + } + QRect r = positionRect(w, rule, labelRule, PseudoElement_GroupBoxTitle, + opt->rect, opt->direction); + if (gb->subControls & SC_GroupBoxCheckBox) { + r = labelRule.contentsRect(r); + if (sc == SC_GroupBoxLabel) { + r.setLeft(r.left() + iw + spacing); + r.setTop(r.center().y() - th/2); + } else { + r = QRect(r.left(), r.center().y() - ih/2, iw, ih); + } + return r; + } else { + return labelRule.contentsRect(r); + } + } + } // switch + } + break; + + case CC_ToolButton: + if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { + if (rule.hasBox() || !rule.hasNativeBorder()) { + switch (sc) { + case SC_ToolButton: return rule.borderRect(opt->rect); + case SC_ToolButtonMenu: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); + return positionRect(w, rule, subRule, PseudoElement_ToolButtonMenu, opt->rect, opt->direction); + } + default: + break; + } + } + + QStyleOptionToolButton tool(*tb); + tool.rect = rule.borderRect(opt->rect); + return rule.baseStyleCanDraw() ? baseStyle()->subControlRect(cc, &tool, sc, w) + : QWindowsStyle::subControlRect(cc, &tool, sc, w); + } + break; + +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *sb = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QStyleOptionSlider styleOptionSlider(*sb); + styleOptionSlider.rect = rule.borderRect(opt->rect); + if (rule.hasDrawable() || rule.hasBox()) { + QRect grooveRect; + if (!rule.hasBox()) { + grooveRect = rule.baseStyleCanDraw() ? baseStyle()->subControlRect(cc, sb, SC_ScrollBarGroove, w) + : QWindowsStyle::subControlRect(cc, sb, SC_ScrollBarGroove, w); + } else { + grooveRect = rule.contentsRect(opt->rect); + } + + PseudoElement pe = PseudoElement_None; + + switch (sc) { + case SC_ScrollBarGroove: + return grooveRect; + case SC_ScrollBarAddPage: + case SC_ScrollBarSubPage: + case SC_ScrollBarSlider: { + QRect contentRect = grooveRect; + if (hasStyleRule(w, PseudoElement_ScrollBarSlider)) { + QRenderRule sliderRule = renderRule(w, opt, PseudoElement_ScrollBarSlider); + Origin origin = sliderRule.hasPosition() ? sliderRule.position()->origin : defaultOrigin(PseudoElement_ScrollBarSlider); + contentRect = rule.originRect(opt->rect, origin); + } + int maxlen = (styleOptionSlider.orientation == Qt::Horizontal) ? contentRect.width() : contentRect.height(); + int sliderlen; + if (sb->maximum != sb->minimum) { + uint range = sb->maximum - sb->minimum; + sliderlen = (qint64(sb->pageStep) * maxlen) / (range + sb->pageStep); + + int slidermin = pixelMetric(PM_ScrollBarSliderMin, sb, w); + if (sliderlen < slidermin || range > INT_MAX / 2) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + + int sliderstart = (styleOptionSlider.orientation == Qt::Horizontal ? contentRect.left() : contentRect.top()) + + sliderPositionFromValue(sb->minimum, sb->maximum, sb->sliderPosition, + maxlen - sliderlen, sb->upsideDown); + + QRect sr = (sb->orientation == Qt::Horizontal) + ? QRect(sliderstart, contentRect.top(), sliderlen, contentRect.height()) + : QRect(contentRect.left(), sliderstart, contentRect.width(), sliderlen); + if (sc == SC_ScrollBarSlider) { + return sr; + } else if (sc == SC_ScrollBarSubPage) { + return QRect(contentRect.topLeft(), sb->orientation == Qt::Horizontal ? sr.bottomLeft() : sr.topRight()); + } else { // SC_ScrollBarAddPage + return QRect(sb->orientation == Qt::Horizontal ? sr.topRight() : sr.bottomLeft(), contentRect.bottomRight()); + } + break; + } + case SC_ScrollBarAddLine: pe = PseudoElement_ScrollBarAddLine; break; + case SC_ScrollBarSubLine: pe = PseudoElement_ScrollBarSubLine; break; + case SC_ScrollBarFirst: pe = PseudoElement_ScrollBarFirst; break; + case SC_ScrollBarLast: pe = PseudoElement_ScrollBarLast; break; + default: break; + } + if (hasStyleRule(w,pe)) { + QRenderRule subRule = renderRule(w, opt, pe); + if (subRule.hasPosition() || subRule.hasGeometry() || subRule.hasBox()) { + const QStyleSheetPositionData *pos = subRule.position(); + QRect originRect = grooveRect; + if (rule.hasBox()) { + Origin origin = (pos && pos->origin != Origin_Unknown) ? pos->origin : defaultOrigin(pe); + originRect = rule.originRect(opt->rect, origin); + } + return positionRect(w, subRule, pe, originRect, styleOptionSlider.direction); + } + } + } + return rule.baseStyleCanDraw() ? baseStyle()->subControlRect(cc, &styleOptionSlider, sc, w) + : QWindowsStyle::subControlRect(cc, &styleOptionSlider, sc, w); + } + break; +#endif // QT_NO_SCROLLBAR + +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_SliderGroove); + if (!subRule.hasDrawable()) + break; + subRule.img = 0; + QRect gr = positionRect(w, rule, subRule, PseudoElement_SliderGroove, opt->rect, opt->direction); + switch (sc) { + case SC_SliderGroove: + return gr; + case SC_SliderHandle: { + bool horizontal = slider->orientation & Qt::Horizontal; + QRect cr = subRule.contentsRect(gr); + QRenderRule subRule2 = renderRule(w, opt, PseudoElement_SliderHandle); + int len = horizontal ? subRule2.size().width() : subRule2.size().height(); + subRule2.img = 0; + subRule2.geo = 0; + cr = positionRect(w, subRule2, PseudoElement_SliderHandle, cr, opt->direction); + int thickness = horizontal ? cr.height() : cr.width(); + int sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum, slider->sliderPosition, + (horizontal ? cr.width() : cr.height()) - len, slider->upsideDown); + cr = horizontal ? QRect(cr.x() + sliderPos, cr.y(), len, thickness) + : QRect(cr.x(), cr.y() + sliderPos, thickness, len); + return subRule2.borderRect(cr); + break; } + case SC_SliderTickmarks: + // TODO... + default: + break; + } + } + break; +#endif // QT_NO_SLIDER + + case CC_MdiControls: + if (hasStyleRule(w, PseudoElement_MdiCloseButton) + || hasStyleRule(w, PseudoElement_MdiNormalButton) + || hasStyleRule(w, PseudoElement_MdiMinButton)) { + QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + if (layout.isEmpty()) + layout = subControlLayout(QLatin1String("mNX")); + + int x = 0, width = 0; + QRenderRule subRule; + for (int i = 0; i < layout.count(); i++) { + int layoutButton = layout[i].toInt(); + if (layoutButton < PseudoElement_MdiCloseButton + || layoutButton > PseudoElement_MdiNormalButton) + continue; + QStyle::SubControl control = knownPseudoElements[layoutButton].subControl; + if (!(opt->subControls & control)) + continue; + subRule = renderRule(w, opt, layoutButton); + width = subRule.size().width(); + if (sc == control) + break; + x += width; + } + + return subRule.borderRect(QRect(x, opt->rect.top(), width, opt->rect.height())); + } + break; + + case CC_TitleBar: + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TitleBar); + if (!subRule.hasDrawable() && !subRule.hasBox() && !subRule.hasBorder()) + break; + QHash<QStyle::SubControl, QRect> layoutRects = titleBarLayout(w, tb); + return layoutRects.value(sc); + } + break; + + default: + break; + } + + return baseStyle()->subControlRect(cc, opt, sc, w); +} + +QRect QStyleSheetStyle::subElementRect(SubElement se, const QStyleOption *opt, const QWidget *w) const +{ + RECURSION_GUARD(return baseStyle()->subElementRect(se, opt, w)) + + QRenderRule rule = renderRule(w, opt); +#ifndef QT_NO_TABBAR + int pe = PseudoElement_None; +#endif + + switch (se) { + case SE_PushButtonContents: + case SE_PushButtonFocusRect: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QStyleOptionButton btnOpt(*btn); + if (rule.hasBox() || !rule.hasNativeBorder()) + return visualRect(opt->direction, opt->rect, rule.contentsRect(opt->rect)); + return rule.baseStyleCanDraw() ? baseStyle()->subElementRect(se, &btnOpt, w) + : QWindowsStyle::subElementRect(se, &btnOpt, w); + } + break; + + case SE_LineEditContents: + case SE_FrameContents: + case SE_ShapedFrameContents: + if (rule.hasBox() || !rule.hasNativeBorder()) { + return visualRect(opt->direction, opt->rect, rule.contentsRect(opt->rect)); + } + break; + + case SE_CheckBoxIndicator: + case SE_RadioButtonIndicator: + if (rule.hasBox() || rule.hasBorder() || hasStyleRule(w, PseudoElement_Indicator)) { + PseudoElement pe = se == SE_CheckBoxIndicator ? PseudoElement_Indicator : PseudoElement_ExclusiveIndicator; + QRenderRule subRule = renderRule(w, opt, pe); + return positionRect(w, rule, subRule, pe, opt->rect, opt->direction); + } + break; + + case SE_CheckBoxContents: + case SE_RadioButtonContents: + if (rule.hasBox() || rule.hasBorder() || hasStyleRule(w, PseudoElement_Indicator)) { + bool isRadio = se == SE_RadioButtonContents; + QRect ir = subElementRect(isRadio ? SE_RadioButtonIndicator : SE_CheckBoxIndicator, + opt, w); + ir = visualRect(opt->direction, opt->rect, ir); + int spacing = pixelMetric(isRadio ? PM_RadioButtonLabelSpacing : PM_CheckBoxLabelSpacing, 0, w); + QRect cr = rule.contentsRect(opt->rect); + ir.setRect(ir.left() + ir.width() + spacing, cr.y(), + cr.width() - ir.width() - spacing, cr.height()); + return visualRect(opt->direction, opt->rect, ir); + } + break; + + case SE_ToolBoxTabContents: + if (w && hasStyleRule(w->parentWidget(), PseudoElement_ToolBoxTab)) { + QRenderRule subRule = renderRule(w->parentWidget(), opt, PseudoElement_ToolBoxTab); + return visualRect(opt->direction, opt->rect, subRule.contentsRect(opt->rect)); + } + break; + + case SE_RadioButtonFocusRect: + case SE_RadioButtonClickRect: // focusrect | indicator + if (rule.hasBox() || rule.hasBorder() || hasStyleRule(w, PseudoElement_Indicator)) { + return opt->rect; + } + break; + + case SE_CheckBoxFocusRect: + case SE_CheckBoxClickRect: // relies on indicator and contents + return ParentStyle::subElementRect(se, opt, w); + +#ifndef QT_NO_ITEMVIEWS + case SE_ViewItemCheckIndicator: + if (!qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + return subElementRect(SE_CheckBoxIndicator, opt, w); + } + // intentionally falls through + case SE_ItemViewItemText: + case SE_ItemViewItemDecoration: + case SE_ItemViewItemFocusRect: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(opt)) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ViewItem); + PseudoElement pe = PseudoElement_None; + if (se == SE_ItemViewItemText || se == SE_ItemViewItemFocusRect) + pe = PseudoElement_ViewItemText; + else if (se == SE_ItemViewItemDecoration && vopt->features & QStyleOptionViewItemV2::HasDecoration) + pe = PseudoElement_ViewItemIcon; + else if (se == SE_ItemViewItemCheckIndicator && vopt->features & QStyleOptionViewItemV2::HasCheckIndicator) + pe = PseudoElement_ViewItemIndicator; + else + break; + if (subRule.hasGeometry() || subRule.hasBox() || !subRule.hasNativeBorder() || hasStyleRule(w, pe)) { + QRenderRule subRule2 = renderRule(w, opt, pe); + QStyleOptionViewItemV4 optCopy(*vopt); + optCopy.rect = subRule.contentsRect(vopt->rect); + QRect rect = ParentStyle::subElementRect(se, &optCopy, w); + return positionRect(w, subRule2, pe, rect, opt->direction); + } + } + break; +#endif // QT_NO_ITEMVIEWS + + case SE_HeaderArrow: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewUpArrow); + if (subRule.hasPosition() || subRule.hasGeometry()) + return positionRect(w, rule, subRule, PseudoElement_HeaderViewUpArrow, opt->rect, opt->direction); + } + break; + + case SE_HeaderLabel: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + if (subRule.hasBox() || !subRule.hasNativeBorder()) + return subRule.contentsRect(opt->rect); + } + break; + + case SE_ProgressBarGroove: + case SE_ProgressBarContents: + case SE_ProgressBarLabel: + if (const QStyleOptionProgressBarV2 *pb = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + if (rule.hasBox() || !rule.hasNativeBorder() || rule.hasPosition() || hasStyleRule(w, PseudoElement_ProgressBarChunk)) { + if (se == SE_ProgressBarGroove) + return rule.borderRect(pb->rect); + else if (se == SE_ProgressBarContents) + return rule.contentsRect(pb->rect); + + QSize sz = pb->fontMetrics.size(0, pb->text); + return QStyle::alignedRect(Qt::LeftToRight, rule.hasPosition() ? rule.position()->textAlignment : pb->textAlignment, + sz, pb->rect); + } + } + break; + +#ifndef QT_NO_TABBAR + case SE_TabWidgetLeftCorner: + pe = PseudoElement_TabWidgetLeftCorner; + // intentionally falls through + case SE_TabWidgetRightCorner: + if (pe == PseudoElement_None) + pe = PseudoElement_TabWidgetRightCorner; + // intentionally falls through + case SE_TabWidgetTabBar: + if (pe == PseudoElement_None) + pe = PseudoElement_TabWidgetTabBar; + // intentionally falls through + case SE_TabWidgetTabPane: + case SE_TabWidgetTabContents: + if (pe == PseudoElement_None) + pe = PseudoElement_TabWidgetPane; + + if (hasStyleRule(w, pe)) { + QRect r = QWindowsStyle::subElementRect(pe == PseudoElement_TabWidgetPane ? SE_TabWidgetTabPane : se, opt, w); + QRenderRule subRule = renderRule(w, opt, pe); + r = positionRect(w, subRule, pe, r, opt->direction); + if (pe == PseudoElement_TabWidgetTabBar) { + Q_ASSERT(opt); + r = opt->rect.intersected(r); + } + if (se == SE_TabWidgetTabContents) + r = subRule.contentsRect(r); + return r; + } + break; + + case SE_TabBarTearIndicator: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTear); + if (subRule.hasContentsSize()) { + QRect r; + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + r.setRect(tab->rect.left(), tab->rect.top(), subRule.size().width(), opt->rect.height()); + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + r.setRect(tab->rect.left(), tab->rect.top(), opt->rect.width(), subRule.size().height()); + break; + default: + break; + } + r = visualRect(opt->direction, opt->rect, r); + } + return r; + } + break; + } + case SE_TabBarTabText: + case SE_TabBarTabLeftButton: + case SE_TabBarTabRightButton: { + QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTab); + if (subRule.hasBox() || !subRule.hasNativeBorder()) { + return ParentStyle::subElementRect(se, opt, w); + } + break; + } +#endif // QT_NO_TABBAR + + case SE_DockWidgetCloseButton: + case SE_DockWidgetFloatButton: { + PseudoElement pe = (se == SE_DockWidgetCloseButton) ? PseudoElement_DockWidgetCloseButton : PseudoElement_DockWidgetFloatButton; + QRenderRule subRule2 = renderRule(w, opt, pe); + if (!subRule2.hasPosition()) + break; + QRenderRule subRule = renderRule(w, opt, PseudoElement_DockWidgetTitle); + return positionRect(w, subRule, subRule2, pe, opt->rect, opt->direction); + } + +#ifndef QT_NO_TOOLBAR + case SE_ToolBarHandle: + if (hasStyleRule(w, PseudoElement_ToolBarHandle)) + return ParentStyle::subElementRect(se, opt, w); + break; +#endif //QT_NO_TOOLBAR + + default: + break; + } + + return baseStyle()->subElementRect(se, opt, w); +} + +bool QStyleSheetStyle::event(QEvent *e) +{ + return (baseStyle()->event(e) && e->isAccepted()) || ParentStyle::event(e); +} + +void QStyleSheetStyle::updateStyleSheetFont(QWidget* w) const +{ + QWidget *container = containerWidget(w); + QRenderRule rule = renderRule(container, PseudoElement_None, + PseudoClass_Active | PseudoClass_Enabled | extendedPseudoClass(container)); + QFont font = rule.font.resolve(w->font()); + + if ((!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + && isNaturalChild(w) && qobject_cast<QWidget *>(w->parent())) { + + font = font.resolve(static_cast<QWidget *>(w->parent())->font()); + } + + if (w->data->fnt == font) + return; + +#ifdef QT3_SUPPORT + QFont old = w->data->fnt; +#endif + w->data->fnt = font; +#if defined(Q_WS_X11) + // make sure the font set on this widget is associated with the correct screen + //w->data->fnt.x11SetScreen(w->d_func()->xinfo.screen()); +#endif + + QEvent e(QEvent::FontChange); + QApplication::sendEvent(w, &e); +#ifdef QT3_SUPPORT + w->fontChange(old); +#endif +} + +void QStyleSheetStyle::saveWidgetFont(QWidget* w, const QFont& font) const +{ + w->setProperty("_q_styleSheetWidgetFont", font); +} + +void QStyleSheetStyle::clearWidgetFont(QWidget* w) const +{ + w->setProperty("_q_styleSheetWidgetFont", QVariant(QVariant::Invalid)); +} + +// Polish palette that should be used for a particular widget, with particular states +// (eg. :focus, :hover, ...) +// this is called by widgets that paint themself in their paint event +// Returns true if there is a new palette in pal. +bool QStyleSheetStyle::styleSheetPalette(const QWidget* w, const QStyleOption* opt, QPalette* pal) +{ + if (!w || !opt || !pal) + return false; + + RECURSION_GUARD(return false) + + w = containerWidget(w); + + QRenderRule rule = renderRule(w, PseudoElement_None, pseudoClass(opt->state) | extendedPseudoClass(w)); + if (!rule.hasPalette()) + return false; + + rule.configurePalette(pal, QPalette::NoRole, QPalette::NoRole); + return true; +} + +Qt::Alignment QStyleSheetStyle::resolveAlignment(Qt::LayoutDirection layDir, Qt::Alignment src) +{ + if (layDir == Qt::LeftToRight || src & Qt::AlignAbsolute) + return src; + + if (src & Qt::AlignLeft) { + src &= ~Qt::AlignLeft; + src |= Qt::AlignRight; + } else if (src & Qt::AlignRight) { + src &= ~Qt::AlignRight; + src |= Qt::AlignLeft; + } + src |= Qt::AlignAbsolute; + return src; +} + +// Returns whether the given QWidget has a "natural" parent, meaning that +// the parent contains this child as part of its normal operation. +// An example is the QTabBar inside a QTabWidget. +// This does not mean that any QTabBar which is a child of QTabWidget will +// match, only the one that was created by the QTabWidget initialization +// (and hence has the correct object name). +bool QStyleSheetStyle::isNaturalChild(const QWidget *w) +{ + if (w->objectName().startsWith(QLatin1String("qt_"))) + return true; + + return false; +} + +QT_END_NAMESPACE + +#include "moc_qstylesheetstyle_p.cpp" + +#endif // QT_NO_STYLE_STYLESHEET diff --git a/src/gui/styles/qstylesheetstyle_default.cpp b/src/gui/styles/qstylesheetstyle_default.cpp new file mode 100644 index 0000000000..76ffac8187 --- /dev/null +++ b/src/gui/styles/qstylesheetstyle_default.cpp @@ -0,0 +1,512 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* This is the default Qt style sheet. + + IMPORTANT: This style sheet is primarily meant for defining feature + capablities of styles. Do NOT add default styling rules here. When in + doubt ask the stylesheet maintainer. + + The stylesheet in here used to be in a CSS file, but was moved here to + avoid parsing overhead. +*/ + +#include "private/qcssparser_p.h" +#include "qstylesheetstyle_p.h" + +#ifndef QT_NO_STYLE_STYLESHEET + +QT_BEGIN_NAMESPACE + +using namespace QCss; + +// This is the class name of the selector. +// Use an empty string where you would use '*' in CSS. +// Ex. QHeaderView + +#define SET_ELEMENT_NAME(x) \ + bSelector.elementName = (x) + +// This acts as both pseudo state and sub control. The first parameter is the +// string name, and the second is the PseudoClass_* constant. +// The sub control specifier is always the first, and has the type +// PseudoClass_Unknown. +// If there is no PseudoClass_Unknown as the first pseudo, it is assumed to be +// a pseudo state. +// Ex. QComboBox::drop-down:enabled +// ^ ^ + +#define ADD_PSEUDO(x, y) \ + pseudo.type = (y); \ + pseudo.name = (x); \ + bSelector.pseudos << pseudo + +// This is attributes. The third parameter is AttributeSelector::* +// Ex. QComboBox[style="QWindowsXPStyle"] +// ^ ^ + +#define ADD_ATTRIBUTE_SELECTOR(x, y, z) \ + attr.name = (x); \ + attr.value = (y); \ + attr.valueMatchCriterium = (z); \ + bSelector.attributeSelectors << attr + +// Adds the current basic selector to the rule. +// Several basic selectors behave as AND (space in CSS). + +#define ADD_BASIC_SELECTOR \ + selector.basicSelectors << bSelector; \ + bSelector.ids.clear(); \ + bSelector.pseudos.clear(); \ + bSelector.attributeSelectors.clear() + +// Adds the current selector to the rule. +// Several selectors behave as OR (comma in CSS). + +#define ADD_SELECTOR \ + styleRule.selectors << selector; \ + selector.basicSelectors.clear() + +// Sets the name of a property. +// Ex. background: red; +// ^ + +#define SET_PROPERTY(x, y) \ + decl.d->property = (x); \ + decl.d->propertyId = (y) + +// Adds a value to the current property. +// The first parameter should be Value::KnownIdentifier if the value can be +// found among the Value_* constants, in which case the second should be that +// constant. Otherwise the first parameter is Value::Identifier and the second +// is a string. +// Adding more values is the same as seperating by spaces in CSS. +// Ex. border: 2px solid black; +// ^ ^ ^ + +#define ADD_VALUE(x, y) \ + value.type = (x); \ + value.variant = (y); \ + decl.d->values << value + +// Adds the current declaration to the rule. +// Ex. border: 2px solid black; +// \----------------------/ + +#define ADD_DECLARATION \ + styleRule.declarations << decl; \ + decl.d.detach(); \ + decl.d->values.clear() + +// Adds the rule to the stylesheet. +// Use at the end of every CSS block. + +#define ADD_STYLE_RULE \ + sheet.styleRules << styleRule; \ + styleRule.selectors.clear(); \ + styleRule.declarations.clear() + +StyleSheet QStyleSheetStyle::getDefaultStyleSheet() const +{ + StyleSheet sheet; + StyleRule styleRule; + BasicSelector bSelector; + Selector selector; + Declaration decl; + QCss::Value value; + Pseudo pseudo; + AttributeSelector attr; + + // pixmap based style doesn't support any features + bool styleIsPixmapBased = baseStyle()->inherits("QMacStyle") + || baseStyle()->inherits("QWindowsXPStyle") + || baseStyle()->inherits("QGtkStyle") + || baseStyle()->inherits("QS60Style"); + + + /*QLineEdit { + -qt-background-role: base; + border: native; + -qt-style-features: background-color; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QLineEdit")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Base); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QLineEdit:no-frame { + border: none; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QLineEdit")); + ADD_PSEUDO(QLatin1String("no-frame"), PseudoClass_Frameless); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_None); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QFrame { + border: native; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QFrame")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QLabel, QToolBox { + background: none; + border-image: none; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QLabel")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_ELEMENT_NAME(QLatin1String("QToolBox")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("background"), Background); + ADD_VALUE(Value::KnownIdentifier, Value_None); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("border-image"), BorderImage); + ADD_VALUE(Value::KnownIdentifier, Value_None); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QGroupBox { + border: native; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QGroupBox")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + + /*QToolTip { + -qt-background-role: window; + border: native; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QToolTip")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Window); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QPushButton, QToolButton { + border-style: native; + -qt-style-features: background-color; //only for not pixmap based styles + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QPushButton")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_ELEMENT_NAME(QLatin1String("QToolButton")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border-style"), BorderStyles); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + if (!styleIsPixmapBased) { + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_DECLARATION; + } + + + ADD_STYLE_RULE; + } + + + /*QComboBox { + border: native; + -qt-style-features: background-color background-gradient; //only for not pixmap based styles + -qt-background-role: base; + }*/ + + { + SET_ELEMENT_NAME(QLatin1String("QComboBox")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + if (!styleIsPixmapBased) { + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-gradient")); + ADD_DECLARATION; + } + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Base); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QComboBox[style="QPlastiqueStyle"][readOnly="true"], + QComboBox[style="QCleanlooksStyle"][readOnly="true"] + { + -qt-background-role: button; + }*/ + if (baseStyle()->inherits("QPlastiqueStyle") || baseStyle()->inherits("QCleanlooksStyle")) + { + SET_ELEMENT_NAME(QLatin1String("QComboBox")); + ADD_ATTRIBUTE_SELECTOR(QLatin1String("readOnly"), QLatin1String("true"), AttributeSelector::MatchEqual); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Button); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QAbstractSpinBox { + border: native; + -qt-style-features: background-color; + -qt-background-role: base; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QAbstractSpinBox")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_DECLARATION; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Base); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QMenu { + -qt-background-role: window; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QMenu")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Window); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + /*QMenu::item { + -qt-style-features: background-color; + }*/ + if (!styleIsPixmapBased) { + SET_ELEMENT_NAME(QLatin1String("QMenu")); + ADD_PSEUDO(QLatin1String("item"), PseudoClass_Unknown); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QHeaderView { + -qt-background-role: window; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QHeaderView")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Window); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QTableCornerButton::section, QHeaderView::section { + -qt-background-role: button; + -qt-style-features: background-color; //if style is not pixmap based + border: native; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QTableCornerButton")); + ADD_PSEUDO(QLatin1String("section"), PseudoClass_Unknown); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_ELEMENT_NAME(QLatin1String("QHeaderView")); + ADD_PSEUDO(QLatin1String("section"), PseudoClass_Unknown); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Button); + ADD_DECLARATION; + + if (!styleIsPixmapBased) { + SET_PROPERTY(QLatin1String("-qt-style-features"), QtStyleFeatures); + ADD_VALUE(Value::Identifier, QString::fromLatin1("background-color")); + ADD_DECLARATION; + } + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QProgressBar { + -qt-background-role: base; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QProgressBar")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Base); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QScrollBar { + -qt-background-role: window; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QScrollBar")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("-qt-background-role"), QtBackgroundRole); + ADD_VALUE(Value::KnownIdentifier, Value_Window); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + /*QDockWidget { + border: native; + }*/ + { + SET_ELEMENT_NAME(QLatin1String("QDockWidget")); + ADD_BASIC_SELECTOR; + ADD_SELECTOR; + + SET_PROPERTY(QLatin1String("border"), Border); + ADD_VALUE(Value::KnownIdentifier, Value_Native); + ADD_DECLARATION; + + ADD_STYLE_RULE; + } + + sheet.origin = StyleSheetOrigin_UserAgent; + sheet.buildIndexes(); + return sheet; +} + +#endif // #ifndef QT_NO_STYLE_STYLESHEET + +QT_END_NAMESPACE diff --git a/src/gui/styles/qstylesheetstyle_p.h b/src/gui/styles/qstylesheetstyle_p.h new file mode 100644 index 0000000000..1f331b2642 --- /dev/null +++ b/src/gui/styles/qstylesheetstyle_p.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTYLESHEETSTYLE_P_H +#define QSTYLESHEETSTYLE_P_H + +#include "QtGui/qwindowsstyle.h" + +#ifndef QT_NO_STYLE_STYLESHEET + +#include "QtGui/qstyleoption.h" +#include "QtCore/qhash.h" +#include "QtGui/qevent.h" +#include "QtCore/qvector.h" +#include "QtGui/qapplication.h" +#include "private/qcssparser_p.h" +#include "QtGui/qbrush.h" + +QT_BEGIN_NAMESPACE + +// +// 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. +// + +class QRenderRule; +class QAbstractScrollArea; +class QStyleSheetStylePrivate; +class QStyleOptionTitleBar; + +class Q_AUTOTEST_EXPORT QStyleSheetStyle : public QWindowsStyle +{ + typedef QWindowsStyle ParentStyle; + + Q_OBJECT +public: + QStyleSheetStyle(QStyle *baseStyle); + ~QStyleSheetStyle(); + + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const; + void drawItemText(QPainter *painter, const QRect& rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole = QPalette::NoRole) const; + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *option) const; + SubControl hitTestComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + const QPoint &pt, const QWidget *w = 0) const; + QRect itemPixmapRect(const QRect &rect, int alignment, const QPixmap &pixmap) const; + QRect itemTextRect(const QFontMetrics &metrics, const QRect &rect, int alignment, bool enabled, + const QString &text) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + void polish(QWidget *widget); + void polish(QApplication *app); + void polish(QPalette &pal); + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *widget = 0) const; + QPalette standardPalette() const; + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option = 0, + const QWidget *w = 0 ) const; + int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, + Qt::Orientation orientation, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, + QStyleHintReturn *shret = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, + const QWidget *w = 0) const; + + // These functions are called from QApplication/QWidget. Be careful. + QStyle *baseStyle() const; + void repolish(QWidget *widget); + void repolish(QApplication *app); + + void unpolish(QWidget *widget); + void unpolish(QApplication *app); + + QStyle *base; + void ref() { ++refcount; } + void deref() { Q_ASSERT(refcount > 0); if (!--refcount) delete this; } + + void updateStyleSheetFont(QWidget* w) const; + void saveWidgetFont(QWidget* w, const QFont& font) const; + void clearWidgetFont(QWidget* w) const; + + bool styleSheetPalette(const QWidget* w, const QStyleOption* opt, QPalette* pal); + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *opt = 0, + const QWidget *widget = 0) const; + int layoutSpacingImplementation(QSizePolicy::ControlType control1, + QSizePolicy::ControlType control2, + Qt::Orientation orientation, + const QStyleOption *option = 0, + const QWidget *widget = 0) const; + +protected: + bool event(QEvent *e); + +private: + int refcount; + + friend class QRenderRule; + int nativeFrameWidth(const QWidget *); + QRenderRule renderRule(const QWidget *, int, quint64 = 0) const; + QRenderRule renderRule(const QWidget *, const QStyleOption *, int = 0) const; + QSize defaultSize(const QWidget *, QSize, const QRect&, int) const; + QRect positionRect(const QWidget *, const QRenderRule&, const QRenderRule&, int, + const QRect&, Qt::LayoutDirection) const; + QRect positionRect(const QWidget *w, const QRenderRule &rule2, int pe, + const QRect &originRect, Qt::LayoutDirection dir) const; + + mutable QCss::Parser parser; + + void setPalette(QWidget *); + void unsetPalette(QWidget *); + void setProperties(QWidget *); + void setGeometry(QWidget *); + QVector<QCss::StyleRule> styleRules(const QWidget *w) const; + bool hasStyleRule(const QWidget *w, int part) const; + + QHash<QStyle::SubControl, QRect> titleBarLayout(const QWidget *w, const QStyleOptionTitleBar *tb) const; + + QCss::StyleSheet getDefaultStyleSheet() const; + + static Qt::Alignment resolveAlignment(Qt::LayoutDirection, Qt::Alignment); + static bool isNaturalChild(const QWidget *w); + bool initWidget(const QWidget *w) const; +public: + static int numinstances; + +private: + Q_DISABLE_COPY(QStyleSheetStyle) + Q_DECLARE_PRIVATE(QStyleSheetStyle) +}; + +class QStyleSheetStyleCaches : public QObject +{ + Q_OBJECT +public Q_SLOTS: + void widgetDestroyed(QObject *); + void styleDestroyed(QObject *); +public: + QHash<const QWidget *, QVector<QCss::StyleRule> > styleRulesCache; + QHash<const QWidget *, QHash<int, bool> > hasStyleRuleCache; + typedef QHash<int, QHash<quint64, QRenderRule> > QRenderRules; + QHash<const QWidget *, QRenderRules> renderRulesCache; + QHash<const QWidget *, QPalette> customPaletteWidgets; // widgets whose palette we tampered + QHash<const void *, QCss::StyleSheet> styleSheetCache; // parsed style sheets + QSet<const QWidget *> autoFillDisabledWidgets; +}; + + +QT_END_NAMESPACE +#endif // QT_NO_STYLE_STYLESHEET +#endif // QSTYLESHEETSTYLE_P_H diff --git a/src/gui/styles/qwindowscestyle.cpp b/src/gui/styles/qwindowscestyle.cpp new file mode 100644 index 0000000000..d7947bc653 --- /dev/null +++ b/src/gui/styles/qwindowscestyle.cpp @@ -0,0 +1,2429 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowscestyle.h" + +#if !defined(QT_NO_STYLE_WINDOWSCE) || defined(QT_PLUGIN) + +#include "qpainterpath.h" +#include "qapplication.h" +#include "qdockwidget.h" +#include "qtoolbar.h" +#include "qpaintengine.h" +#include "qpainter.h" +#include "qstyleoption.h" +#include "qwindowscestyle_p.h" +#include "qdebug.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 = 2; // menu item ver text margin +static const int windowsArrowHMargin = 6; // arrow horizontal margin +static const int windowsRightBorder = 15; // right border on windows +static const int windowsCheckMarkWidth = 14; // checkmarks width on windows + +static const int windowsCEitemViewCheckBoxSize = 14; +static const int windowsCEFrameGroupBoxOffset = 9; +static const int windowsCEIndicatorSize = 14; +static const int windowsCEExclusiveIndicatorSize = 14; +static const int windowsCESliderThickness = 24; +static const int windowsCEIconSize = 16; + +static const QColor windowsCECheckBoxGradientColorBegin = QColor(222, 224, 214); +static const QColor windowsCECheckBoxGradientColorEnd = QColor(255, 255, 255); + +enum QSliderDirection { SlUp, SlDown, SlLeft, SlRight }; + +QWindowsCEStyle::QWindowsCEStyle() : QWindowsStyle() { + qApp->setEffectEnabled(Qt::UI_FadeMenu, false); + qApp->setEffectEnabled(Qt::UI_AnimateMenu, false); +} + +void QWindowsCEStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const { + + bool doRestore = false; + QRect rect = option->rect; + + switch (element) { + case PE_PanelButtonTool: { + if ( +#ifndef QT_NO_TOOLBAR + (widget && qobject_cast<QToolBar*>(widget->parentWidget())) || +#endif +#ifndef QT_NO_DOCKWIDGET + (widget && widget->inherits("QDockWidgetTitleButton")) || +#endif + (option->state & (State_Sunken | State_On))) + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect.adjusted(0, 0, 0, 0), + option->palette, option->state & (State_Sunken | State_On), + &option->palette.button()); + if (option->state & (State_On)){ + QBrush fill = QBrush(option->palette.midlight().color(), Qt::Dense4Pattern); + painter->fillRect(option->rect.adjusted(windowsItemFrame , windowsItemFrame , + -windowsItemFrame , -windowsItemFrame ), fill); + } + break; } + case PE_IndicatorButtonDropDown: + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect, option->palette, + option->state & (State_Sunken | State_On), + &option->palette.brush(QPalette::Button)); + break; +#ifndef QT_NO_TABBAR + case PE_IndicatorTabTear: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + bool rtl = tab->direction == Qt::RightToLeft; + QRect rect = tab->rect; + QPainterPath path; + rect.setTop(rect.top() + ((tab->state & State_Selected) ? 1 : 3)); + rect.setBottom(rect.bottom() - ((tab->state & State_Selected) ? 0 : 2)); + path.moveTo(QPoint(rtl ? rect.right() : rect.left(), rect.top())); + int count = 3; + for(int jags = 1; jags <= count; ++jags, rtl = !rtl) + path.lineTo(QPoint(rtl ? rect.left() : rect.right(), rect.top() + jags * rect.height()/count)); + + painter->setPen(QPen(tab->palette.light(), qreal(.8))); + painter->setBrush(tab->palette.background()); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawPath(path); + } + break; +#endif //QT_NO_TABBAR +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarSeparator: + //nothing to draw on WindowsCE + break; + case PE_IndicatorToolBarHandle: + painter->save(); + painter->translate(option->rect.x(), option->rect.y()); + if (option->state & State_Horizontal) { + int x = option->rect.width() / 2 - 4; + if (QApplication::layoutDirection() == Qt::RightToLeft) + x -= 2; + if (option->rect.height() > 4) { + QWindowsCEStylePrivate::drawWinCEButton(painter,x - 1, 0, 7, option->rect.height(), + option->palette, false, 0); + QWindowsCEStylePrivate::drawWinCEPanel(painter, x, 1, 3, option->rect.height() - 1, + option->palette, false, 0); + QWindowsCEStylePrivate::drawWinCEPanel(painter, x + 3, 1, 3, option->rect.height() - 1, + option->palette, false, 0); + painter->setPen(option->palette.button().color()); + painter->drawLine(x + 4, 2, x + 4,option->rect.height() - 2); + } + } else { + if (option->rect.width() > 4) { + int y = option->rect.height() / 2 - 4; + QWindowsCEStylePrivate::drawWinCEPanel(painter, 2, y, option->rect.width() - 2, 3, + option->palette, false, 0); + QWindowsCEStylePrivate::drawWinCEPanel(painter, 2, y + 3, option->rect.width() - 2, 3, + option->palette, false, 0); + } + } + painter->restore(); + break; + +#endif // QT_NO_TOOLBAR + case PE_FrameButtonTool: { +#ifndef QT_NO_DOCKWIDGET + if (widget && widget->inherits("QDockWidgetTitleButton")) { + if (const QDockWidget *dw = qobject_cast<const QDockWidget *>(widget->parent())) + if (dw->isFloating()){ + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect.adjusted(1, 1, 0, 0), + option->palette, option->state & (State_Sunken | State_On), + &option->palette.button()); + return; + } + } +#endif // QT_NO_DOCKWIDGET + QBrush fill; + bool stippled; + bool panel = (element == PE_PanelButtonTool); + if ((!(option->state & State_Sunken )) + && (!(option->state & State_Enabled) + || ((option->state & State_Enabled ) && !(option->state & State_MouseOver))) + && (option->state & State_On)) { + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + stippled = true; + } else { + fill = option->palette.brush(QPalette::Button); + stippled = false; + } + if (option->state & (State_Raised | State_Sunken | State_On)) { + if (option->state & State_AutoRaise) { + if(option->state & (State_Enabled | State_Sunken | State_On)){ + if (panel) + QWindowsCEStylePrivate::drawWinCEPanel(painter, option->rect, option->palette, + option->state & (State_Sunken | State_On), &fill); + else + qDrawShadeRect(painter, option->rect, option->palette, + option->state & (State_Sunken | State_On), 1); + } + if (stippled) { + painter->setPen(option->palette.button().color()); + painter->drawRect(option->rect.adjusted(1, 1, -2, -2)); + } + } else { + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect, option->palette, + option->state & (State_Sunken | State_On), panel ? &fill : 0); + } + } else { + painter->fillRect(option->rect, fill); + } + break; } + + case PE_PanelButtonBevel: { + QBrush fill; + bool panel = element != PE_FrameButtonBevel; + painter->setBrushOrigin(option->rect.topLeft()); + if (!(option->state & State_Sunken) && (option->state & State_On)) + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + else + fill = option->palette.brush(QPalette::Button); + + if (option->state & (State_Raised | State_On | State_Sunken)) { + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect, option->palette, + option->state & (State_Sunken | State_On), + panel ? &fill : 0); ; + } else { + if (panel) + painter->fillRect(option->rect, fill); + else + painter->drawRect(option->rect); + } + break; } + + case PE_FrameGroupBox: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + QRect fr = frame->rect; + painter->setPen(frame->palette.shadow().color()); + painter->drawRect(fr.x(), fr.y(), fr.x() + fr.width() - 1, + fr.y() + fr.height() - windowsCEFrameGroupBoxOffset); + } + break; + + case PE_IndicatorCheckBox: { + QBrush fill; + if (option->state & State_NoChange) + fill = QBrush(option->palette.base().color(), Qt::Dense4Pattern); + else if (option->state & State_Sunken) + fill = option->palette.button(); + else if (option->state & State_Enabled) + fill = option->palette.base(); + else + fill = option->palette.background(); + painter->save(); + doRestore = true; + painter->fillRect(option->rect,fill); + painter->setPen(option->palette.dark().color()); + painter->drawRect(option->rect); + painter->setPen(option->palette.shadow().color()); + painter->drawLine(option->rect.x() + 1,option->rect.y() + 1, + option->rect.x() + option->rect.width() - 1, option->rect.y() + 1); + painter->drawLine(option->rect.x() + 1,option->rect.y() + 1, + option->rect.x() + 1, option->rect.y() + option->rect.height() - 1); + //fall through... + } + case PE_IndicatorViewItemCheck: + case PE_Q3CheckListIndicator: { + if (!doRestore) { + painter->save(); + doRestore = true; + } + int arrowSize= 2; + if (element == PE_Q3CheckListIndicator || element == PE_IndicatorViewItemCheck) { + QLinearGradient linearGradient(QPoint(option->rect.x(),option->rect.y()), QPoint(option->rect.x()+option->rect.width(), + option->rect.y()+option->rect.height())); + linearGradient.setColorAt(0, windowsCECheckBoxGradientColorBegin); + linearGradient.setColorAt(1, windowsCECheckBoxGradientColorEnd); + painter->setBrush(linearGradient); + painter->setPen(Qt::NoPen); + if (option->state & State_NoChange) + painter->setBrush(option->palette.brush(QPalette::Button)); + painter->setPen(option->palette.link().color()); + painter->drawRect(option->rect.x(), option->rect.y(), windowsCEitemViewCheckBoxSize, windowsCEitemViewCheckBoxSize); + painter->setPen(option->palette.brightText().color()); + arrowSize= 3; + } + if (!(option->state & State_Off)) { + QLineF lines[9]; + int i, xx, yy; + xx = option->rect.x() + 4; + yy = option->rect.y() + 6; + for (i = 0; i < 4; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + arrowSize); + ++xx; + ++yy; + } + yy -= 2; + for (i = 4; i < 9; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + arrowSize); + ++xx; + --yy; + } + painter->drawLines(lines, 9); + } + if (doRestore) + painter->restore(); + + break; } + case PE_IndicatorRadioButton: { + QRect ir = option->rect; + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(option->palette.light()); + painter->drawEllipse(option->rect); + painter->setPen(option->palette.shadow().color()); + painter->setBrush(option->palette.shadow().color()); + painter->drawArc(option->rect, 0, 360 * 16); + painter->drawArc(option->rect.x() + 1, option->rect.y() + 1, option->rect.width() - 2, + option->rect.height() - 2, 40 * 16, 180 * 16); + painter->setPen(option->palette.light().color()); + painter->drawPoint(option->rect.x() + 11, option->rect.y() + 3); + painter->drawPoint(option->rect.x() + 3,option->rect.y() + 3); + painter->setPen(option->palette.shadow().color()); + painter->drawPoint(option->rect.x() +3,option->rect.y() + 12); + if (option->state & (State_Sunken | State_On)) { + painter->setPen(Qt::NoPen); + painter->setBrush(option->palette.text()); + painter->drawEllipse(option->rect.x() +3,option->rect.y()+ 2,9,10); + } + painter->restore(); + break; } + case PE_PanelMenuBar: + painter->save(); + painter->setPen(option->palette.shadow().color()); + painter->drawRect(option->rect); + painter->restore(); + break; + case PE_PanelButtonCommand: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + QBrush fill; + State flags = option->state; + QPalette pal = option->palette; + QRect r = option->rect; + if (! (flags & State_Sunken) && (flags & State_On)) + fill = QBrush(pal.light().color(), Qt::Dense4Pattern); + else + fill = pal.brush(QPalette::Button); + if (btn->features & QStyleOptionButton::DefaultButton && flags & State_Sunken) { + painter->setPen(pal.dark().color()); + painter->setBrush(fill); + painter->drawRect(r.adjusted(0, 0, -1, -1)); + } else if (flags & (State_Raised | State_Sunken | State_On | State_Sunken)) { + QWindowsCEStylePrivate::drawWinCEButton(painter, r, pal, flags & (State_Sunken | State_On), + &fill); + } else { + painter->fillRect(r, fill); + } + + } + break; + case PE_FrameDefaultButton: { + painter->setPen(option->palette.shadow().color()); + QRect rect = option->rect; + rect.adjust(0, 0, -1, -1); + painter->drawRect(rect); + break; } + case PE_IndicatorSpinPlus: + case PE_IndicatorSpinMinus: { + QRect r = option->rect; + int fw = pixelMetric(PM_DefaultFrameWidth, option, widget)+2; + QRect br = r.adjusted(fw, fw, -fw, -fw); + int offset = (option->state & State_Sunken) ? 1 : 0; + int step = (br.width() + 4) / 5; + painter->fillRect(br.x() + offset, br.y() + offset +br.height() / 2 - step / 2, + br.width(), step, + option->palette.buttonText()); + if (element == PE_IndicatorSpinPlus) + painter->fillRect(br.x() + br.width() / 2 - step / 2 + offset, br.y() + offset+4, + step, br.height()-7, + option->palette.buttonText()); + break; } + case PE_IndicatorSpinUp: + case PE_IndicatorSpinDown: { + painter->save(); + QPoint points[7]; + switch (element) { + case PE_IndicatorSpinUp: + points[0] = QPoint(-2, -4); + points[1] = QPoint(-2, 2); + points[2] = QPoint(-1, -3); + points[3] = QPoint(-1, 1); + points[4] = QPoint(0, -2); + points[5] = QPoint(0, 0); + points[6] = QPoint(1, -1); + break; + case PE_IndicatorSpinDown: + points[0] = QPoint(0, -4); + points[1] = QPoint(0, 2); + points[2] = QPoint(-1, -3); + points[3] = QPoint(-1, 1); + points[4] = QPoint(-2, -2); + points[5] = QPoint(-2, 0); + points[6] = QPoint(-3, -1); + break; + default: + break; + } + if (option->state & State_Sunken) + painter->translate(pixelMetric(PM_ButtonShiftHorizontal), + pixelMetric(PM_ButtonShiftVertical)); + if (option->state & State_Enabled) { + painter->translate(option->rect.x() + option->rect.width() / 2, + option->rect.y() + option->rect.height() / 2); + painter->setPen(option->palette.buttonText().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } else { + painter->translate(option->rect.x() + option->rect.width() / 2 + 1, + option->rect.y() + option->rect.height() / 2 + 1); + painter->setPen(option->palette.light().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + painter->translate(-1, -1); + painter->setPen(option->palette.mid().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } + + painter->restore(); + break; } + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: { + painter->save(); + QPoint points[9]; + switch (element) { + case PE_IndicatorArrowUp: + + points[0] = QPoint(-4, 2); + points[1] = QPoint(4, 2); + points[2] = QPoint(-3, 1); + points[3] = QPoint(3, 1); + points[4] = QPoint(-2, 0); + points[5] = QPoint(2, 0); + points[6] = QPoint(-1, -1); + points[7] = QPoint(1, -1); + points[8] = QPoint(0, -2); + break; + case PE_IndicatorArrowDown: + + points[0] = QPoint(-4, -2); + points[1] = QPoint(4, -2); + points[2] = QPoint(-3, -1); + points[3] = QPoint(3, -1); + points[4] = QPoint(-2, 0); + points[5] = QPoint(2, 0); + points[6] = QPoint(-1, 1); + points[7] = QPoint(1, 1); + points[8] = QPoint(0, 2); + break; + case PE_IndicatorArrowRight: + points[0] = QPoint(-3, -4); + points[1] = QPoint(-3, 4); + points[2] = QPoint(-2, -3); + points[3] = QPoint(-2, 3); + points[4] = QPoint(-1, -2); + points[5] = QPoint(-1, 2); + points[6] = QPoint(0, -1); + points[7] = QPoint(0, 1); + points[8] = QPoint(1, 0); + break; + case PE_IndicatorArrowLeft: + points[0] = QPoint(1, -4); + points[1] = QPoint(1, 4); + points[2] = QPoint(0, -3); + points[3] = QPoint(0, 3); + points[4] = QPoint(-1, -2); + points[5] = QPoint(-1, 2); + points[6] = QPoint(-2, -1); + points[7] = QPoint(-2, 1); + points[8] = QPoint(-3, 0); + break; + default: + break; + } + if (option->state & State_Sunken) + painter->translate(pixelMetric(PM_ButtonShiftHorizontal), + pixelMetric(PM_ButtonShiftVertical)); + if (option->state & State_Enabled) { + painter->translate(option->rect.x() + option->rect.width() / 2, + option->rect.y() + option->rect.height() / 2); + painter->setPen(option->palette.buttonText().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawLine(points[6], points[7]); + painter->drawPoint(points[8]); + } else { + painter->translate(option->rect.x() + option->rect.width() / 2 + 1, + option->rect.y() + option->rect.height() / 2 + 1); + painter->setPen(option->palette.light().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawLine(points[6], points[7]); + painter->drawPoint(points[8]); + painter->translate(-1, -1); + painter->setPen(option->palette.mid().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawLine(points[6], points[7]); + painter->drawPoint(points[8]); + } + painter->restore(); + break; } + + case PE_FrameWindow: { + QPalette popupPal = option->palette; + popupPal.setColor(QPalette::Light, option->palette.background().color()); + popupPal.setColor(QPalette::Midlight, option->palette.light().color()); + QWindowsCEStylePrivate::drawWinCEPanel(painter, option->rect, popupPal, option->state & State_Sunken); + break; } + + case PE_Frame: + case PE_FrameMenu: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + QPalette popupPal = frame->palette; + QRect r = frame->rect; + qDrawPlainRect(painter, r, frame->palette.shadow().color(),1); + } + break; + case PE_FrameStatusBar: + QWindowsCEStylePrivate::drawWinCEPanel(painter, option->rect, option->palette, true, 0); + break; + + case PE_FrameTabWidget: { + QRect rect = option->rect; + QPalette pal = option->palette; + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect, option->palette, false, 0); + break; } + default: + QWindowsStyle::drawPrimitive(element, option, painter, widget); + break; + } +} + +void QWindowsCEStyle::drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const { + switch (element) { + #ifndef QT_NO_MENU + case CE_MenuTearoff: { + if(option->state & State_Selected) { + if(pixelMetric(PM_MenuPanelWidth, option, widget) > 1) + qDrawShadePanel(painter, option->rect.x(), option->rect.y(), option->rect.width(), + option->rect.height(), option->palette, false, 2, + &option->palette.brush(QPalette::Button)); + else + qDrawShadePanel(painter, option->rect.x() + 1, option->rect.y() + 1, option->rect.width() - 2, + option->rect.height() - 2, option->palette, true, 1, &option->palette.brush(QPalette::Button)); + } else { + painter->fillRect(option->rect, option->palette.brush(QPalette::Button)); + } + painter->setPen(QPen(option->palette.dark().color(), 1, Qt::DashLine)); + painter->drawLine(option->rect.x()+2, option->rect.y()+option->rect.height()/2-1, option->rect.x()+option->rect.width()-4, + option->rect.y()+option->rect.height()/2-1); + painter->setPen(QPen(option->palette.light().color(), 1, Qt::DashLine)); + painter->drawLine(option->rect.x()+2, option->rect.y()+option->rect.height()/2, option->rect.x()+option->rect.width()-4, + option->rect.y()+option->rect.height()/2); + break; } + + + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + bool active = mbi->state & State_Selected; + bool hasFocus = mbi->state & State_HasFocus; + bool down = mbi->state & State_Sunken; + QStyleOptionMenuItem newMbi = *mbi; + if (active || hasFocus) { + QBrush b = mbi->palette.brush(QPalette::Highlight); + if (active && down) { + painter->fillRect(mbi->rect.adjusted(0, 1, 0, -1), b); + } + } + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip + | Qt::TextSingleLine; + if (!styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + painter->save(); + QFont f = painter->font(); + f.setBold(true); + painter->setFont(f); + QPixmap pix = mbi->icon.pixmap(pixelMetric(PM_SmallIconSize), + (mbi->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); + if (!pix.isNull()) + drawItemPixmap(painter,mbi->rect, alignment, pix); + else + if (active && down) + drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, + mbi->text, QPalette::Light); + else + drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, + mbi->text, QPalette::ButtonText); + painter->restore(); + } + break; + + case CE_MenuBarEmptyArea: + painter->save(); + painter->setPen(option->palette.shadow().color()); + if (widget && !widget->testAttribute(Qt::WA_NoSystemBackground)) { + painter->eraseRect(option->rect); + QRect r = option->rect; + painter->drawLine(r.x() + 1, r.y() + 1, r.x()+ 1, r.y()+ r.height() - 2); + painter->drawLine(r.x() - 2 + r.width(), r.y() + 1, r.x() - 2 + r.width(), r.y() + r.height() - 2); + painter->drawLine(r.x() + 1, r.y() +1, r.x() - 1 + r.width(), r.y() + 1); + painter->drawLine(r.x() + 1, r.y() + r.height()-2 , r.x() - 2 + r.width(), r.y() + r.height() - 2); + } + painter->restore(); + break; + + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + 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(menuitem->maxIconWidth, windowsCheckMarkWidth); + QBrush fill = menuitem->palette.brush(act ? QPalette::Highlight : QPalette::Button); + painter->fillRect(menuitem->rect.adjusted(1, 1, 0, 0), fill); + + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) { + int yoff = y-1 + h / 2; + painter->setPen(menuitem->palette.shadow().color()); + painter->drawLine(x + 4, yoff + 1, x + w - 8, yoff + 1); + return; + } + + QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(), + menuitem->rect.y(), checkcol, menuitem->rect.height())); + if (checked) { + if (act && !dis) { + qDrawPlainRect(painter, vCheckRect, + menuitem->palette.button().color(), 1, + &menuitem->palette.brush(QPalette::Button)); + } else { + QBrush fill(menuitem->palette.button().color(), Qt::Dense4Pattern); + qDrawPlainRect(painter, vCheckRect,menuitem->palette.button().color(), 1, &fill); + } + } else if (!act) { + painter->fillRect(vCheckRect, menuitem->palette.brush(QPalette::Button)); + } + // 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(pixelMetric(PM_SmallIconSize), mode, QIcon::On); + else + pixmap = menuitem->icon.pixmap(pixelMetric(PM_SmallIconSize), mode); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + if (act && !dis && !checked) + qDrawPlainRect(painter, vCheckRect, menuitem->palette.button().color(), 1, + &menuitem->palette.brush(QPalette::Button)); + QRect pmr(0, 0, pixw, pixh); + 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; + newMi.rect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x() + + windowsItemFrame, menuitem->rect.y() + windowsItemFrame, + checkcol - 2 * windowsItemFrame, menuitem->rect.height() - 2*windowsItemFrame)); + drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, painter, widget); + } + painter->setPen(act ? menuitem->palette.highlightedText().color() : menuitem->palette.buttonText().color()); + + QColor discol; + if (dis) { + discol = menuitem->palette.text().color(); + painter->setPen(discol); + } + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + 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 (!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()))); + if (dis && !act) + painter->setPen(discol); + 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); + if (dis && !act) + painter->setPen(discol); + 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_IndicatorSpinDown : PE_IndicatorSpinUp; + 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; + if (act) + newMI.palette.setColor(QPalette::ButtonText, + newMI.palette.highlightedText().color()); + drawPrimitive(arrow, &newMI, painter, widget); + } + } + break; +#endif // QT_NO_MENU + case CE_MenuVMargin: + painter->fillRect(option->rect, Qt::white); + break; + case CE_MenuEmptyArea: + QWindowsStyle::drawControl(element,option, painter, widget); + break; + +#ifndef QT_NO_TABBAR + case CE_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + drawControl(CE_TabBarTabShape, tab, painter, widget); + drawControl(CE_TabBarTabLabel, tab, painter, widget); + } + break; + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + bool rtlHorTabs = (tab->direction == Qt::RightToLeft + && (tab->shape == QTabBar::RoundedNorth + || tab->shape == QTabBar::RoundedSouth)); + bool selected = tab->state & State_Selected; + bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End) + || (rtlHorTabs + && tab->position == QStyleOptionTab::Beginning)); + bool firstTab = ((!rtlHorTabs + && tab->position == QStyleOptionTab::Beginning) + || (rtlHorTabs + && tab->position == QStyleOptionTab::End)); + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + bool previousSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::PreviousIsSelected) + || (rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected)); + bool nextSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected) + || (rtlHorTabs + && tab->selectedPosition + == QStyleOptionTab::PreviousIsSelected)); + int tabBarAlignment = styleHint(SH_TabBar_Alignment, tab, widget); + bool leftAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignLeft) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignRight); + + bool rightAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignRight) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignLeft); + QColor light = tab->palette.light().color(); + QColor midlight = tab->palette.midlight().color(); + QColor dark = tab->palette.dark().color(); + QColor shadow = tab->palette.shadow().color(); + QColor background = tab->palette.background().color(); + int borderThinkness = pixelMetric(PM_TabBarBaseOverlap, tab, widget); + if (selected) + borderThinkness /= 2; + QRect r2(option->rect); + int x1 = r2.left(); + int x2 = r2.right(); + int y1 = r2.top(); + int y2 = r2.bottom(); + switch (tab->shape) { + default: + QCommonStyle::drawControl(element, tab, painter, widget); + break; + case QTabBar::RoundedNorth: { + if (!selected) { + y1 += 2; + x1 += firstTab ? borderThinkness : 0; + x2 -= lastTab ? borderThinkness : 0; + } + + painter->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 2), tab->palette.background()); + + // Delete border + if (selected) { + painter->setPen(background); + painter->drawLine(x1, y2 - 1, x2, y2 - 1); + painter->drawLine(x1, y2 + 1, x2, y2 + 1); + painter->drawLine(x1, y2, x2, y2); + } + // Left + if (firstTab || selected || onlyOne || !previousSelected) { + painter->setPen(dark); + painter->drawLine(x1, y1 + 2, x1, y2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + painter->drawPoint(x1 + 1, y1 + 1); + painter->setPen(midlight); + painter->drawLine(x1 + 1, y1 + 2, x1 + 1, y2 - + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + + } + // Top + { + int beg = x1 + (previousSelected ? 0 : 2); + int end = x2 - (nextSelected ? 0 : 2); + painter->setPen(dark); + painter->drawLine(beg, y1, end, y1); + + painter->setPen(midlight); + painter->drawLine(beg, y1 + 1, end, y1 + 1); + + } + // Right + if (lastTab || selected || onlyOne || !nextSelected) { + painter->setPen(shadow); + painter->drawLine(x2, y1 + 2, x2, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + painter->drawPoint(x2 - 1, y1 + 1); + painter->setPen(dark); + painter->drawLine(x2 - 1, y1 + 2, x2 - 1, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + } + break; } + case QTabBar::RoundedSouth: { + if (!selected) { + y2 -= 2; + x1 += firstTab ? borderThinkness : 0; + x2 -= lastTab ? borderThinkness : 0; + } + + painter->fillRect(QRect(x1 + 1, y1 + 2, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + painter->setPen(background); + painter->drawLine(x1, y1 + 1, x2 - 1, y1 + 1); + painter->drawLine(x1, y1 - 1, x2 - 1, y1 - 1); + painter->drawLine(x1, y1, x2 - 1, y1); + } + // Left + if (firstTab || selected || onlyOne || !previousSelected) { + painter->setPen(dark); + painter->drawLine(x1, y2 - 2, x1, y1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + painter->drawPoint(x1 + 1, y2 - 1); + painter->setPen(midlight); + painter->drawLine(x1 + 1, y2 - 2, x1 + 1, y1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + } + // Bottom + { + int beg = x1 + (previousSelected ? 0 : 2); + int end = x2 - (nextSelected ? 0 : 2); + painter->setPen(shadow); + painter->drawLine(beg, y2, end, y2); + painter->setPen(dark); + painter->drawLine(beg, y2 - 1, end, y2 - 1); + } + // Right + if (lastTab || selected || onlyOne || !nextSelected) { + painter->setPen(shadow); + painter->drawLine(x2, y2 - 2, x2, y1 + ((onlyOne || lastTab) && selected && + rightAligned ? 0 : borderThinkness)); + painter->drawPoint(x2 - 1, y2 - 1); + painter->setPen(dark); + painter->drawLine(x2 - 1, y2 - 2, x2 - 1, y1 + ((onlyOne || lastTab) && selected && + rightAligned ? 0 : borderThinkness)); + } + break; } + case QTabBar::RoundedWest: { + if (!selected) { + x1 += 2; + y1 += firstTab ? borderThinkness : 0; + y2 -= lastTab ? borderThinkness : 0; + } + + painter->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 2, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + painter->setPen(background); + painter->drawLine(x2 - 1, y1, x2 - 1, y2); + painter->drawLine(x2, y1, x2, y2); + } + // Top + if (firstTab || selected || onlyOne || !previousSelected) { + painter->setPen(dark); + painter->drawLine(x1 + 2, y1, x2 - ((onlyOne || firstTab) && selected && + leftAligned ? 0 : borderThinkness), y1); + painter->drawPoint(x1 + 1, y1 + 1); + painter->setPen(midlight); + painter->drawLine(x1 + 2, y1 + 1, x2 - ((onlyOne || firstTab) && selected && + leftAligned ? 0 : borderThinkness), y1 + 1); + } + // Left + { + int beg = y1 + (previousSelected ? 0 : 2); + int end = y2 - (nextSelected ? 0 : 2); + painter->setPen(dark); + painter->drawLine(x1, beg, x1, end); + painter->setPen(midlight); + painter->drawLine(x1 + 1, beg, x1 + 1, end); + } + // Bottom + if (lastTab || selected || onlyOne || !nextSelected) { + painter->setPen(shadow); + painter->drawLine(x1 + 3, y2, x2 - ((onlyOne || lastTab) && selected && + rightAligned ? 0 : borderThinkness), y2); + painter->drawPoint(x1 + 2, y2 - 1); + painter->setPen(dark); + painter->drawLine(x1 + 3, y2 - 1, x2 - ((onlyOne || lastTab) && selected && + rightAligned ? 0 : borderThinkness), y2 - 1); + painter->drawPoint(x1 + 1, y2 - 1); + painter->drawPoint(x1 + 2, y2); + } + break; } + case QTabBar::RoundedEast: { + if (!selected) { + x2 -= 2; + y1 += firstTab ? borderThinkness : 0; + y2 -= lastTab ? borderThinkness : 0; + } + + painter->fillRect(QRect(x1 + 2, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + painter->setPen(background); + painter->drawLine(x1 + 1, y1, x1 + 1, y2 - 1); + painter->drawLine(x1, y1, x1, y2 - 1); + } + // Top + if (firstTab || selected || onlyOne || !previousSelected) { + painter->setPen(dark); + painter->drawLine(x2 - 2, y1, x1 + ((onlyOne || firstTab) && selected && + leftAligned ? 0 : borderThinkness), y1); + painter->drawPoint(x2 - 1, y1 + 1); + painter->setPen(midlight); + painter->drawLine(x2 - 3, y1 + 1, x1 + ((onlyOne || firstTab) && + selected && leftAligned ? 0 : borderThinkness), y1 + 1); + painter->drawPoint(x2 - 1, y1); + + } + // Right + { + int beg = y1 + (previousSelected ? 0 : 2); + int end = y2 - (nextSelected ? 0 : 2); + painter->setPen(shadow); + painter->drawLine(x2, beg, x2, end); + painter->setPen(dark); + painter->drawLine(x2 - 1, beg, x2 - 1, end); + } + // Bottom + if (lastTab || selected || onlyOne || !nextSelected) { + painter->setPen(shadow); + painter->drawLine(x2 - 2, y2, x1 + ((onlyOne || lastTab) && + selected && rightAligned ? 0 : borderThinkness), y2); + painter->drawPoint(x2 - 1, y2 - 1); + painter->setPen(dark); + painter->drawLine(x2 - 2, y2 - 1, x1 + ((onlyOne || lastTab) && + selected && rightAligned ? 0 : borderThinkness), y2 - 1); + } + break; } + } + } + break; +#endif // QT_NO_TABBAR + + case CE_ToolBar: { + QRect rect = option->rect; + painter->setPen(QPen(option->palette.dark().color())); + painter->drawLine(rect.topRight().x()-1, + rect.topRight().y(), + rect.bottomRight().x()-1, + rect.bottomRight().y()); + painter->drawLine(rect.bottomLeft().x(), + rect.bottomLeft().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + painter->setPen(QPen(option->palette.light().color())); + painter->drawLine(rect.topRight().x(), + rect.topRight().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + painter->drawLine(rect.topLeft().x(), + rect.topLeft().y(), + rect.topRight().x(), + rect.topRight().y()); + + break; } +#ifndef QT_NO_SCROLLBAR + case CE_ScrollBarSubLine: + case CE_ScrollBarAddLine: { + if (option->state & State_Sunken) { + QStyleOption buttonOpt = *option; + + drawPrimitive(PE_PanelButtonBevel, &buttonOpt, painter, widget); + } else { + QStyleOption buttonOpt = *option; + if (!(buttonOpt.state & State_Sunken)) + buttonOpt.state |= State_Raised; + drawPrimitive(PE_PanelButtonBevel, &buttonOpt, painter, widget); + } + PrimitiveElement arrow; + if (option->state & State_Horizontal) { + if (element == CE_ScrollBarAddLine) + arrow = option->direction == Qt::LeftToRight ? PE_IndicatorArrowRight : PE_IndicatorArrowLeft; + else + arrow = option->direction == Qt::LeftToRight ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + } else { + if (element == CE_ScrollBarAddLine) + arrow = PE_IndicatorArrowDown; + else + arrow = PE_IndicatorArrowUp; + } + drawPrimitive(arrow, option, painter, widget); + break; } + case CE_ScrollBarAddPage: + case CE_ScrollBarSubPage: { + QBrush br; + QBrush bg = painter->background(); + Qt::BGMode bg_mode = painter->backgroundMode(); + painter->setPen(Qt::NoPen); + painter->setBackgroundMode(Qt::OpaqueMode); + + if (option->state & State_Sunken) { + br = QBrush(option->palette.shadow().color(), Qt::Dense4Pattern); + painter->setBackground(option->palette.dark().color()); + painter->setBrush(br); + } else { + QPixmap pm = option->palette.brush(QPalette::Light).texture(); + if (option->state & State_Enabled) + br = !pm.isNull() ? QBrush(pm) : QBrush(option->palette.button().color(), Qt::Dense4Pattern); + else + br = !pm.isNull() ? QBrush(pm) : QBrush(option->palette.light().color(), Qt::Dense4Pattern); + painter->setBackground(option->palette.base().color()); + painter->setBrush(br); + } + painter->drawRect(option->rect); + painter->setBackground(bg); + painter->setBackgroundMode(bg_mode); + break; } + case CE_ScrollBarSlider: + if (!(option->state & State_Enabled)) { + QStyleOptionButton buttonOpt; + buttonOpt.QStyleOption::operator=(*option); + buttonOpt.state = State_Enabled | State_Raised; + drawPrimitive(PE_PanelButtonBevel, &buttonOpt, painter, widget); + QPixmap pm = option->palette.brush(QPalette::Light).texture(); + QBrush br = !pm.isNull() ? QBrush(pm) : QBrush(option->palette.light().color(), Qt::Dense4Pattern); + painter->setPen(Qt::NoPen); + painter->setBrush(br); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawRect(option->rect.adjusted(2, 2, -2, -2)); + } else { + QStyleOptionButton buttonOpt; + buttonOpt.QStyleOption::operator=(*option); + buttonOpt.state = State_Enabled | State_Raised; + drawPrimitive(PE_PanelButtonBevel, &buttonOpt, painter, widget); + } + break; +#endif // QT_NO_SCROLLBAR + case CE_HeaderSection: { + QBrush fill; + if (option->state & State_On) + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + else + fill = option->palette.brush(QPalette::Button); + + if (option->state & (State_Raised | State_Sunken)) { + QWindowsCEStylePrivate::drawWinCEButton(painter, option->rect, option->palette, + option->state & State_Sunken, &fill); + } else { + painter->fillRect(option->rect, fill); + } + break; } + + case CE_DockWidgetTitle: + QWindowsStyle::drawControl(element,option, painter, widget); + break; + + case CE_PushButtonLabel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + painter->save(); + QFont f = painter->font(); + f.setBold(true); + painter->setFont(f); + QRect ir = btn->rect; + uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic; + if (!styleHint(SH_UnderlineShortcut, btn, widget)) + tf |= Qt::TextHideMnemonic; + + if (btn->state & (State_On | State_Sunken)) + ir.translate(pixelMetric(PM_ButtonShiftHorizontal, option, widget), + pixelMetric(PM_ButtonShiftVertical, option, widget)); + if (!btn->icon.isNull()) { + 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, mode, state); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + //Center the icon if there is no text + + QPoint point; + if (btn->text.isEmpty()) { + point = QPoint(ir.x() + ir.width() / 2 - pixw / 2, + ir.y() + ir.height() / 2 - pixh / 2); + } else { + point = QPoint(ir.x() + 2, ir.y() + ir.height() / 2 - pixh / 2); + } + if (btn->direction == Qt::RightToLeft) + point.rx() += pixw; + + if ((btn->state & (State_On | State_Sunken)) && btn->direction == Qt::RightToLeft) + point.rx() -= pixelMetric(PM_ButtonShiftHorizontal, option, widget) * 2; + + painter->drawPixmap(visualPos(btn->direction, btn->rect, point), pixmap); + + if (btn->direction == Qt::RightToLeft) + ir.translate(-4, 0); + else + ir.translate(pixw + 4, 0); + ir.setWidth(ir.width() - (pixw + 4)); + // left-align text if there is + if (!btn->text.isEmpty()) + tf |= Qt::AlignLeft; + } else { + tf |= Qt::AlignHCenter; + } + drawItemText(painter, ir, tf, btn->palette, (btn->state & State_Enabled), + btn->text, QPalette::ButtonText); + painter->restore(); + } + break; + default: + QWindowsStyle::drawControl(element, option, painter, widget); + break; + } +} + +void QWindowsCEStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const { + switch (control) { + #ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int thickness = pixelMetric(PM_SliderControlThickness, slider, widget); + int len = pixelMetric(PM_SliderLength, slider, widget); + int ticks = slider->tickPosition; + QRect groove = subControlRect(CC_Slider, slider, SC_SliderGroove, widget); + QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, widget); + + if ((slider->subControls & SC_SliderGroove) && groove.isValid()) { + int mid = thickness / 2; + if (ticks & QSlider::TicksAbove) + mid += len / 8; + if (ticks & QSlider::TicksBelow) + mid -= len / 8; + + painter->setPen(slider->palette.shadow().color()); + if (slider->orientation == Qt::Horizontal) { + QWindowsCEStylePrivate::drawWinCEPanel(painter, groove.x(), groove.y() + mid - 2, + groove.width(), 4, option->palette, true); + painter->drawLine(groove.x() + 1, groove.y() + mid - 1, + groove.x() + groove.width() - 3, groove.y() + mid - 1); + } else { + QWindowsCEStylePrivate::drawWinCEPanel(painter, groove.x() + mid - 2, groove.y(), + 4, groove.height(), option->palette, true); + painter->drawLine(groove.x() + mid - 1, groove.y() + 1, + groove.x() + mid - 1, groove.y() + groove.height() - 3); + } + } + if (slider->subControls & SC_SliderTickmarks) { + QStyleOptionSlider tmpSlider = *slider; + tmpSlider.subControls = SC_SliderTickmarks; + QCommonStyle::drawComplexControl(control, &tmpSlider, painter, widget); + } + + if (slider->subControls & SC_SliderHandle) { + // 4444440 + // 4333310 + // 4322210 + // 4322210 + // 4322210 + // 4322210 + // *43210* + // **440** + // ***0*** + const QColor c0 = slider->palette.shadow().color(); + const QColor c1 = slider->palette.dark().color(); + // const QColor c2 = g.button(); + const QColor c3 = slider->palette.midlight().color(); + const QColor c4 = slider->palette.dark().color(); + QBrush handleBrush; + + if (slider->state & State_Enabled) { + handleBrush = slider->palette.color(QPalette::Button); + } else { + handleBrush = QBrush(slider->palette.color(QPalette::Button), + Qt::Dense4Pattern); + } + + int x = handle.x(), y = handle.y(), + wi = handle.width(), he = handle.height(); + + int x1 = x; + int x2 = x + wi - 1; + int y1 = y; + int y2 = y + he - 1; + + Qt::Orientation orient = slider->orientation; + bool tickAbove = slider->tickPosition == QSlider::TicksAbove; + bool tickBelow = slider->tickPosition == QSlider::TicksBelow; + + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget); + drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + if ((tickAbove && tickBelow) || (!tickAbove && !tickBelow)) { + Qt::BGMode oldMode = painter->backgroundMode(); + painter->setBackgroundMode(Qt::OpaqueMode); + QWindowsCEStylePrivate::drawWinCEButton(painter, QRect(x, y, wi, he), slider->palette, false, + &handleBrush); + painter->setBackgroundMode(oldMode); + QBrush fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 + 2, y1 + 2, x2 - x1 - 3, y2 - y1 - 3),fill); + return; + } + QSliderDirection dir; + if (orient == Qt::Horizontal) + if (tickAbove) + dir = SlUp; + else + dir = SlDown; + else + if (tickAbove) + dir = SlLeft; + else + dir = SlRight; + QPolygon a; + int d = 0; + switch (dir) { + case SlUp: + x2++; + y1 = y1 + wi / 2; + d = (wi + 1) / 2 - 1; + a.setPoints(5, x1, y1, x1, y2, x2, y2, x2, y1, x1 + d, y1 - d); + break; + case SlDown: + x2++; + y2 = y2 - wi / 2; + d = (wi + 1) / 2 - 1; + a.setPoints(5, x1, y1, x1, y2, x1 + d, y2+d, x2, y2, x2, y1); + break; + case SlLeft: + d = (he + 1) / 2 - 1; + x1 = x1 + he / 2; + a.setPoints(5, x1, y1, x1 - d, y1 + d, x1, y2, x2, y2, x2, y1); + y1--; + break; + case SlRight: + d = (he + 1) / 2 - 1; + x2 = x2 - he / 2; + a.setPoints(5, x1, y1, x1, y2, x2, y2, x2 + d, y1 + d, x2, y1); + y1--; + break; + } + QBrush oldBrush = painter->brush(); + painter->setPen(Qt::NoPen); + painter->setBrush(handleBrush); + Qt::BGMode oldMode = painter->backgroundMode(); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + painter->drawPolygon(a); + QBrush fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1, y1, x2 - x1 + 1, y2 - y1 + 1),fill); + painter->setBrush(oldBrush); + painter->setBackgroundMode(oldMode); + + if (dir != SlUp) { + painter->setPen(c4); + painter->drawLine(x1, y1, x2, y1); + painter->setPen(c3); + painter->drawLine(x1, y1 + 1, x2, y1 + 1); + } + if (dir != SlLeft) { + painter->setPen(c3); + painter->drawLine(x1 + 1, y1 + 1, x1 + 1, y2); + painter->setPen(c4); + painter->drawLine(x1, y1, x1, y2); + } + if (dir != SlRight) { + painter->setPen(c0); + painter->drawLine(x2, y1, x2, y2); + painter->setPen(c1); + painter->drawLine(x2 - 1, y1 + 1, x2 - 1, y2 - 1); + } + if (dir != SlDown) { + painter->setPen(c0); + painter->drawLine(x1, y2, x2, y2); + painter->setPen(c1); + painter->drawLine(x1+1, y2 - 1, x2 - 1, y2 - 1); + } + + switch (dir) { + case SlUp: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 + 3, y1 - d + 2, x2 - x1 - 4,y1), fill); + painter->setPen(c4); + painter->drawLine(x1, y1, x1 + d, y1 - d); + painter->setPen(c0); + d = wi - d - 1; + painter->drawLine(x2, y1, x2 - d, y1 - d); + d--; + painter->setPen(c3); + painter->drawLine(x1 + 1, y1, x1 + 1 + d-1, y1 - d + 1); + painter->setPen(c1); + painter->drawLine(x2 - 1, y1, x2-1 - d, y1 - d); + break; + case SlDown: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 + 3, y2 - d, x2 - x1 - 4,y2 - 8), fill); + painter->setPen(c4); + painter->drawLine(x1, y2, x1 + d, y2 + d); + painter->setPen(c0); + d = wi - d - 1; + painter->drawLine(x2, y2, x2 - d, y2 + d); + d--; + painter->setPen(c3); + painter->drawLine(x1 + 1, y2, x1 + 1 + d - 1, y2 + d - 1); + painter->setPen(c1); + painter->drawLine(x2 - 1, y2, x2 - 1 - d, y2 + d); + break; + case SlLeft: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 - d + 2, y1 + 2, x1,y2 - y1 - 3), fill); + painter->setPen(c4); + painter->drawLine(x1, y1, x1 - d, y1 + d); + painter->setPen(c0); + d = he - d - 1; + painter->drawLine(x1, y2, x1 - d, y2 - d); + d--; + painter->setPen(c3); + painter->drawLine(x1, y1 + 1, x1 - d + 1, y1 + 1 + d - 1); + painter->setPen(c1); + painter->drawLine(x1, y2 - 1, x1 - d, y2 - 1 - d); + break; + case SlRight: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x2 - d - 4, y1 + 2, x2 - 4, y2 - y1 - 3), fill); + painter->setPen(c4); + painter->drawLine(x2, y1, x2 + d, y1 + d); + painter->setPen(c0); + d = he - d - 1; + painter->drawLine(x2, y2, x2 + d, y2 - d); + d--; + painter->setPen(c3); + painter->drawLine(x2, y1 + 1, x2 + d - 1, y1 + 1 + d - 1); + painter->setPen(c1); + painter->drawLine(x2, y2 - 1, x2 + d, y2 - 1 - d); + break; + } + } + } + break; +#endif // QT_NO_SLIDER + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + QRect button, menuarea; + +#ifndef QT_NO_TOOLBAR + bool flat = !(widget ? qobject_cast<QToolBar*>(widget->parentWidget()) : 0); +#else + bool flat = true; +#endif + + button = subControlRect(control, toolbutton, SC_ToolButton, widget); + menuarea = subControlRect(control, toolbutton, SC_ToolButtonMenu, widget); + + if (flat && (toolbutton->subControls & SC_ToolButtonMenu)) { + menuarea.setLeft(menuarea.left() - 4); + button.setRight(button.right() - 4); + } + + State bflags = toolbutton->state; + + if (bflags & State_AutoRaise) + if (!(bflags & State_MouseOver)) { + bflags &= ~State_Raised; + } + State mflags = bflags; + + if (toolbutton->activeSubControls & SC_ToolButton) + bflags |= State_Sunken; + if (toolbutton->activeSubControls & SC_ToolButtonMenu) + mflags |= State_Sunken; + + QStyleOption tool(0); + tool.palette = toolbutton->palette; + if (toolbutton->subControls & SC_ToolButton) { + tool.rect = button; + tool.state = bflags; + drawPrimitive(PE_PanelButtonTool, &tool, painter, widget); + } + + if (toolbutton->subControls & SC_ToolButtonMenu) { + tool.rect = menuarea; + tool.state = mflags; + tool.state = bflags; + drawPrimitive(PE_IndicatorButtonDropDown, &tool, painter, widget); + + if (!flat) { + + //connect buttons + painter->save(); + painter->setPen(tool.palette.button().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y(), tool.rect.x() - 2, tool.rect.y() + tool.rect.height()); + painter->drawLine(tool.rect.x() - 1, tool.rect.y(), tool.rect.x() - 1, tool.rect.y() + tool.rect.height()); + painter->drawLine(tool.rect.x(), tool.rect.y(), tool.rect.x(), tool.rect.y() + tool.rect.height()); + painter->drawLine(tool.rect.x() + 1, tool.rect.y(), tool.rect.x() + 1, tool.rect.y() + tool.rect.height()); + + if (tool.state & State_Sunken) + { + painter->setPen(tool.palette.midlight().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y() + tool.rect.height() - 2, + tool.rect.x() + 1, tool.rect.y() + tool.rect.height() -2 ); + painter->setPen(tool.palette.shadow().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y() + 1,tool.rect.x() + 1, tool.rect.y() + 1); + painter->drawLine(tool.rect.x() - 2, tool.rect.y(), tool.rect.x() + 1, tool.rect.y()); + painter->setPen(tool.palette.light().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y() + tool.rect.height() - 1, + tool.rect.x() + 1, tool.rect.y() + tool.rect.height() - 1); + } + else + { + painter->setPen(tool.palette.dark().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y(),tool.rect.x() + 1, tool.rect.y()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y()+tool.rect.height() - 2,tool.rect.x() + 1, + tool.rect.y() + tool.rect.height() - 2); + painter->setPen(tool.palette.midlight().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y() + 1,tool.rect.x() + 1, tool.rect.y() + 1); + painter->setPen(tool.palette.shadow().color()); + painter->drawLine(tool.rect.x() - 2, tool.rect.y() + tool.rect.height() - 1, + tool.rect.x() + 1, tool.rect.y() + tool.rect.height() - 1); + } + painter->restore(); + } + + + if (!flat) { + tool.rect.adjust(-3,0,-3,0); + painter->save(); + painter->setPen(tool.palette.button().color()); + if (tool.state & State_Sunken) + painter->drawLine(tool.rect.x() + 2, tool.rect.y() + 10, + tool.rect.x() + tool.rect.width(), tool.rect.y() + 10); + else + painter->drawLine(tool.rect.x() + 1, tool.rect.y() + 9, tool.rect.x() + + tool.rect.width() - 1, tool.rect.y() + 9); + painter->restore(); + } else { + tool.rect.adjust(-1,0,-1,0); + } + + drawPrimitive(PE_IndicatorArrowDown, &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::Menu) + fr.rect.adjust(0, 0, -pixelMetric(QStyle::PM_MenuButtonIndicator, + toolbutton, widget), 0); + drawPrimitive(PE_FrameFocusRect, &fr, painter, widget); + } + QStyleOptionToolButton label = *toolbutton; + int fw = pixelMetric(PM_DefaultFrameWidth, option, widget); + label.rect = button.adjusted(fw, fw, -fw, -fw); + drawControl(CE_ToolButtonLabel, &label, painter, widget); + } + break; + +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + // Draw frame + painter->save(); + QFont f = painter->font(); + f.setBold(true); + painter->setFont(f); + QStyleOptionGroupBox groupBoxFont = *groupBox; + groupBoxFont.fontMetrics = QFontMetrics(f); + QRect textRect = subControlRect(CC_GroupBox, &groupBoxFont, SC_GroupBoxLabel, widget); + QRect checkBoxRect = subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget); + if (groupBox->subControls & QStyle::SC_GroupBoxFrame) { + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget); + painter->save(); + + QRegion region(groupBox->rect); + if (!groupBox->text.isEmpty()) { + bool ltr = groupBox->direction == Qt::LeftToRight; + QRect finalRect = checkBoxRect.united(textRect); + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) + finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0); + region -= finalRect; + } + painter->setClipRegion(region); + drawPrimitive(PE_FrameGroupBox, &frame, painter, widget); + painter->restore(); + } + + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) + painter->setPen(textColor); + int alignment = int(groupBox->textAlignment); + if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + + drawItemText(painter, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment, + groupBox->palette, groupBox->state & State_Enabled, groupBox->text, + textColor.isValid() ? QPalette::NoRole : QPalette::WindowText); + + if (groupBox->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*groupBox); + fropt.rect = textRect; + drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + // Draw checkbox + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + painter->restore(); + } + break; +#endif //QT_NO_GROUPBOX +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + QBrush editBrush = cmb->palette.brush(QPalette::Base); + if ((cmb->subControls & SC_ComboBoxFrame) && cmb->frame) + QWindowsCEStylePrivate::drawWinCEPanel(painter, option->rect, option->palette, true, &editBrush); + else + painter->fillRect(option->rect, editBrush); + + if (cmb->subControls & SC_ComboBoxArrow) { + State flags = State_None; + + QRect ar = subControlRect(CC_ComboBox, cmb, SC_ComboBoxArrow, widget); + if (cmb->activeSubControls == SC_ComboBoxArrow) { + painter->setPen(cmb->palette.dark().color()); + painter->setBrush(cmb->palette.brush(QPalette::Button)); + painter->drawRect(ar.adjusted(0, 0, -1, -1)); + QWindowsCEStylePrivate::drawWinCEButton(painter, ar.adjusted(0, 0, -1, -1), option->palette, true, + &cmb->palette.brush(QPalette::Button)); + } else { + // Make qDrawWinButton use the right colors for drawing the shade of the button + + QWindowsCEStylePrivate::drawWinCEButton(painter, ar, option->palette, false, + &cmb->palette.brush(QPalette::Button)); + } + + ar.adjust(2, 2, -2, -2); + if (option->state & State_Enabled) + flags |= State_Enabled; + + if (cmb->activeSubControls == SC_ComboBoxArrow) + flags |= State_Sunken; + QStyleOption arrowOpt(0); + arrowOpt.rect = ar; + arrowOpt.palette = cmb->palette; + arrowOpt.state = flags; + drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, painter, widget); + } + if (cmb->subControls & SC_ComboBoxEditField) { + QRect re = subControlRect(CC_ComboBox, cmb, SC_ComboBoxEditField, widget); + if (cmb->state & State_HasFocus && !cmb->editable) + painter->fillRect(re.x(), re.y(), re.width(), re.height(), + cmb->palette.brush(QPalette::Highlight)); + if (cmb->state & State_HasFocus) { + painter->setPen(cmb->palette.highlightedText().color()); + painter->setBackground(cmb->palette.highlight()); + } else { + painter->setPen(cmb->palette.text().color()); + painter->setBackground(cmb->palette.background()); + } + if (cmb->state & State_HasFocus && !cmb->editable) { + QStyleOptionFocusRect focus; + focus.QStyleOption::operator=(*cmb); + focus.rect = subElementRect(SE_ComboBoxFocusRect, cmb, widget); + focus.state |= State_FocusAtBorder; + focus.backgroundColor = cmb->palette.highlight().color(); + drawPrimitive(PE_FrameFocusRect, &focus, painter, widget); + } + } + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QStyleOptionSpinBox copy = *sb; + PrimitiveElement pe; + + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + QRect r = subControlRect(CC_SpinBox, sb, SC_SpinBoxFrame, widget); + QWindowsCEStylePrivate::drawWinCEPanel(painter, r, option->palette, true); + } + QPalette shadePal(option->palette); + shadePal.setColor(QPalette::Button, option->palette.light().color()); + shadePal.setColor(QPalette::Light, option->palette.button().color()); + + bool reverse = QApplication::layoutDirection() == Qt::RightToLeft; + + if (sb->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + if (reverse) + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinMinus + : PE_IndicatorSpinDown); + else + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinPlus + : PE_IndicatorSpinUp); + copy.rect = subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget); + QWindowsCEStylePrivate::drawWinCEButton(painter, copy.rect, option->palette, copy.state & (State_Sunken | State_On), + ©.palette.brush(QPalette::Button)); + copy.rect.adjust(3, 0, -4, 0); + drawPrimitive(pe, ©, painter, widget); + } + if (sb->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = sb->state; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + + if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + if (reverse) + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinPlus + : PE_IndicatorSpinUp); + else + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinMinus + : PE_IndicatorSpinDown); + copy.rect = subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); + QWindowsCEStylePrivate::drawWinCEButton(painter, copy.rect, shadePal, copy.state & (State_Sunken | State_On), + ©.palette.brush(QPalette::Button)); + + copy.rect.adjust(3, 0, -4, 0); + if (pe == PE_IndicatorArrowUp || pe == PE_IndicatorArrowDown) { + copy.rect = copy.rect.adjusted(1, 1, -1, -1); + drawPrimitive(pe, ©, painter, widget); + } + else { + drawPrimitive(pe, ©, painter, widget); + } + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + QRect r = subControlRect(CC_SpinBox, sb, SC_SpinBoxEditField, widget); + painter->save(); + painter->setPen(option->palette.light().color()); + painter->drawLine(r.x() + 1 + r.width(), r.y() - 2, r.x() + 1 + r.width(), r.y() + r.height() + 1); + painter->setPen(option->palette.midlight().color()); + painter->drawLine(r.x() + r.width(), r.y() - 1, r.x() + r.width(), r.y() + r.height()); + painter->restore(); + } + } + } + break; +#endif // QT_NO_SPINBOX + + default: + QWindowsStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +void QWindowsCEStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, const QPalette &pal, + bool enabled, const QString& text, QPalette::ColorRole textRole) const { + if (text.isEmpty()) + return; + QPen savedPen; + if (textRole != QPalette::NoRole) { + savedPen = painter->pen(); + painter->setPen(pal.color(textRole)); + } + if (!enabled) { + QPen pen = painter->pen(); + painter->setPen(pal.light().color()); + //painter->drawText(rect.adjusted(1, 1, 1, 1), alignment, text); + painter->setPen(pen); + } + painter->drawText(rect, alignment, text); + if (textRole != QPalette::NoRole) + painter->setPen(savedPen); +} + + +QSize QWindowsCEStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const { + QSize newSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + switch (type) { + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + newSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + int w = newSize.width(), + h = newSize.height(); + int defwidth = 0; + if (btn->features & QStyleOptionButton::AutoDefaultButton) + defwidth = 2 * pixelMetric(PM_ButtonDefaultIndicator, btn, widget); + if (w < 75 + defwidth && btn->icon.isNull()) + w = 75 + defwidth; + if (h < 23 + defwidth) + h = 23 + defwidth; + newSize = QSize(w+14, h); + } + break; + + case CT_RadioButton: + case CT_CheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool isRadio = (type == CT_RadioButton); + QRect irect = visualRect(btn->direction, btn->rect, + subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, btn, widget)); + int h = pixelMetric(isRadio ? PM_ExclusiveIndicatorHeight + : PM_IndicatorHeight, btn, widget); + int margins = (!btn->icon.isNull() && btn->text.isEmpty()) ? 0 : 10; + newSize += QSize(irect.right() + margins, 4); + newSize.setHeight(qMax(newSize.height(), h)); + } + break; + case CT_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int fw = cmb->frame ? pixelMetric(PM_ComboBoxFrameWidth, option, widget) * 2 : 0; + newSize = QSize(newSize.width() + fw -1, qMax(24, newSize.height() + fw-1)); + } + break; +#ifndef QT_NO_SPINBOX + case CT_SpinBox: + if (const QStyleOptionSpinBox *spnb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + int fw = spnb->frame ? pixelMetric(PM_SpinBoxFrameWidth, option, widget) * 2 : 0; + newSize = QSize(newSize.width() + fw - 5, newSize.height() + fw - 6); + } + break; +#endif + case CT_LineEdit: + newSize += QSize(0,1); + break; + case CT_MenuBarItem: + newSize += QSize(5, 1); + break; + case CT_MenuItem: + newSize += QSize(0, -2); + break; + case CT_MenuBar: + newSize += QSize(0, -1); + break; + case CT_ToolButton: + if (const QStyleOptionToolButton *b = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + if (b->toolButtonStyle != Qt::ToolButtonIconOnly) + newSize = QSize(newSize.width() + 1, newSize.height() - 1); + else + newSize = QSize(newSize.width() + 1, newSize.height()); + } + break; + + default: + break; + } + return newSize; +} + +QRect QWindowsCEStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const { + QRect rect = QWindowsStyle::subElementRect(element, option, widget); + switch (element) { +#ifndef QT_NO_COMBOBOX + case SE_ComboBoxFocusRect: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int margin = cb->frame ? 3 : 0; + rect.setRect(margin, margin, option->rect.width() - 2*margin - 20, option->rect.height() - 2*margin); + rect = visualRect(option->direction, option->rect, rect); + } + break; +#endif // QT_NO_COMBOBOX + default: + break; + } + return rect; +} + +QRect QWindowsCEStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const { + QRect rect = QWindowsStyle::subControlRect(control, option, subControl, widget); + switch (control) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int tickOffset = pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int thickness = pixelMetric(PM_SliderControlThickness, slider, widget); + + switch (subControl) { + case SC_SliderHandle: { + int sliderPos = 0; + int len = pixelMetric(PM_SliderLength, slider, widget); + bool horizontal = slider->orientation == Qt::Horizontal; + sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum, + slider->sliderPosition, + (horizontal ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown); + if (horizontal) + rect.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness); + else + rect.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len); + break; } + default: + break; + } + rect = visualRect(slider->direction, slider->rect, rect); + } + break; +#endif //QT_NO_SLIDER +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = cb->rect.x(), + y = cb->rect.y(), + wi = cb->rect.width(), + he = cb->rect.height(); + int xpos = x; + int margin = cb->frame ? 3 : 0; + int bmarg = cb->frame ? 2 : 0; + xpos += wi - (he - 2*bmarg) - bmarg; + switch (subControl) { + case SC_ComboBoxArrow: + rect.setRect(xpos, y + bmarg, he - 2*bmarg, he - 2*bmarg); + break; + case SC_ComboBoxEditField: + rect.setRect(x + margin, y + margin, wi - 2 * margin - (he - 2*bmarg), he - 2 * margin); + break; + case SC_ComboBoxListBoxPopup: + rect = cb->rect; + break; + case SC_ComboBoxFrame: + rect = cb->rect; + break; + default: + break; + } + rect = visualRect(cb->direction, cb->rect, rect); + } +#endif //QT_NO_COMBOBOX +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QSize bs; + int fw = spinbox->frame ? pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0; + bs.setWidth(qMax(18, (spinbox->rect.height() / 2 - fw + 1))); + // 1.6 -approximate golden mean + bs.setHeight(qMax(18, qMin((bs.height() * 8 / 5), (spinbox->rect.width() / 4)))); + bs = bs.expandedTo(QApplication::globalStrut()); + int y = fw; + int x, lx, rx; + x = spinbox->rect.width() - y - bs.width() * 2; + lx = fw; + rx = x - fw; + switch (subControl) { + case SC_SpinBoxUp: + rect = QRect(x + bs.width(), y, bs.width(), bs.height()); + break; + case SC_SpinBoxDown: + rect = QRect(x, y , bs.width(), bs.height()); + break; + case SC_SpinBoxEditField: + if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) { + rect = QRect(lx, fw, spinbox->rect.width() - 2*fw - 2, spinbox->rect.height() - 2*fw); + } else { + rect = QRect(lx, fw, rx-2, spinbox->rect.height() - 2*fw); + } + break; + case SC_SpinBoxFrame: + rect = spinbox->rect; + default: + break; + } + rect = visualRect(spinbox->direction, spinbox->rect, rect); + } + break; +#endif // Qt_NO_SPINBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + switch (subControl) { + case SC_GroupBoxFrame: + // FALL THROUGH + case SC_GroupBoxContents: { + int topMargin = 0; + int topHeight = 0; + int bottomMargin = 0; + int noLabelMargin = 0; + QRect frameRect = groupBox->rect; + int verticalAlignment = styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget); + if (groupBox->text.size()) { + topHeight = groupBox->fontMetrics.height(); + if (verticalAlignment & Qt::AlignVCenter) + topMargin = topHeight / 2; + else if (verticalAlignment & Qt::AlignTop) + topMargin = -topHeight/2; + } + else { + topHeight = groupBox->fontMetrics.height(); + noLabelMargin = topHeight / 2; + if (verticalAlignment & Qt::AlignVCenter) { + topMargin = topHeight / 4 - 4; + bottomMargin = topHeight / 4 - 4; + } + else if (verticalAlignment & Qt::AlignTop) { + topMargin = topHeight/2 - 4; + bottomMargin = topHeight/2 - 4; + } + } + + if (subControl == SC_GroupBoxFrame) { + frameRect.setTop(topMargin); + frameRect.setBottom(frameRect.height() + bottomMargin); + rect = frameRect; + break; + } + + int frameWidth = 0; + if ((groupBox->features & QStyleOptionFrameV2::Flat) == 0) + frameWidth = pixelMetric(PM_DefaultFrameWidth, groupBox, widget); + rect = frameRect.adjusted(frameWidth, frameWidth + topHeight, -frameWidth, -frameWidth - noLabelMargin); + break; + } + case SC_GroupBoxCheckBox: + // FALL THROUGH + case SC_GroupBoxLabel: { + QFontMetrics fontMetrics = groupBox->fontMetrics; + int h = fontMetrics.height(); + int tw = fontMetrics.size(Qt::TextShowMnemonic, groupBox->text + QLatin1Char(' ')).width(); + int marg = (groupBox->features & QStyleOptionFrameV2::Flat) ? 0 : 8; + rect = groupBox->rect.adjusted(marg, 0, -marg, 0); + rect.setHeight(h); + + int indicatorWidth = pixelMetric(PM_IndicatorWidth, option, widget); + int indicatorSpace = pixelMetric(PM_CheckBoxLabelSpacing, option, widget) - 1; + bool hasCheckBox = groupBox->subControls & QStyle::SC_GroupBoxCheckBox; + int checkBoxSize = hasCheckBox ? (indicatorWidth + indicatorSpace) : 0; + + // Adjusted rect for label + indicatorWidth + indicatorSpace + QRect totalRect = alignedRect(groupBox->direction, groupBox->textAlignment, + QSize(tw + checkBoxSize, h), rect); + + // Adjust totalRect if checkbox is set + if (hasCheckBox) { + bool ltr = groupBox->direction == Qt::LeftToRight; + int left = 0; + // Adjust for check box + if (subControl == SC_GroupBoxCheckBox) { + int indicatorHeight = pixelMetric(PM_IndicatorHeight, option, widget); + left = ltr ? totalRect.left() : (totalRect.right() - indicatorWidth); + int top = totalRect.top() + (fontMetrics.height() - indicatorHeight) / 2; + totalRect.setRect(left, top, indicatorWidth, indicatorHeight); + // Adjust for label + } else { + left = ltr ? (totalRect.left() + checkBoxSize - 2) : totalRect.left(); + totalRect.setRect(left, totalRect.top(), + totalRect.width() - checkBoxSize, totalRect.height()); + } + } + rect = totalRect; + break; + } + default: + break; + } + } + break; + } +#endif // QT_NO_GROUPBOX + default: + break; + } + return rect; +} + +QStyle::SubControl QWindowsCEStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget) const { + /*switch (control) { + default: + break; + }*/ + return QWindowsStyle::hitTestComplexControl(control, option, pos, widget); +} + + +QPalette QWindowsCEStyle::standardPalette() const { + QPalette palette (Qt::black,QColor(198, 195, 198), QColor(222, 223, 222 ), + QColor(132, 130, 132), QColor(198, 195, 198) , Qt::black, Qt::white, Qt::white, QColor(198, 195, 198)); + palette.setColor(QPalette::Window, QColor(198, 195, 198)); + palette.setColor(QPalette::Base, Qt::white); + palette.setColor(QPalette::Button, QColor(198, 195, 198)); + palette.setColor(QPalette::Highlight, QColor(0, 0, 132)); + palette.setColor(QPalette::Light, Qt::white); + palette.setColor(QPalette::Midlight, QColor(222, 223, 222 )); + palette.setColor(QPalette::Dark, QColor(132, 130, 132)); + palette.setColor(QPalette::Mid, QColor(132, 130, 132)); + palette.setColor(QPalette::Shadow, QColor(0, 0, 0)); + palette.setColor(QPalette::BrightText, QColor(33, 162, 33)); //color for ItemView checked indicator (arrow) + palette.setColor(QPalette::Link, QColor(24,81,132)); // color for the box around the ItemView indicator + + return palette; +} + +void QWindowsCEStyle::polish(QApplication *app) { + QWindowsStyle::polish(app); +} + +void QWindowsCEStyle::polish(QWidget *widget) { + QWindowsStyle::polish(widget); +} + +void QWindowsCEStyle::polish(QPalette &palette) { + QWindowsStyle::polish(palette); +} + +int QWindowsCEStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QWidget *widget) const { + int ret; + + switch (pm) { + case PM_DefaultFrameWidth: + ret = 1; + break; + + case PM_MenuBarHMargin: + ret = 2; + break; + case PM_MenuBarVMargin: + ret = 2; + break; + /*case PM_MenuBarItemSpacing: + ret = 2; + break;*/ + + case PM_MenuButtonIndicator: + ret = 10; + break; + + case PM_SpinBoxFrameWidth: + ret = 2; + break; + case PM_ButtonDefaultIndicator: + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + ret = 1; + break; +#ifndef QT_NO_TABBAR + case PM_TabBarTabShiftHorizontal: + ret = 0; + break; + case PM_TabBarTabShiftVertical: + ret = 6; + break; +#endif + case PM_MaximumDragDistance: + ret = 60; + break; + + case PM_IndicatorWidth: + ret = windowsCEIndicatorSize; + break; + + case PM_IndicatorHeight: + ret = windowsCEIndicatorSize; + break; + + case PM_ExclusiveIndicatorWidth: + ret = windowsCEExclusiveIndicatorSize; + break; + + case PM_ExclusiveIndicatorHeight: + ret = windowsCEExclusiveIndicatorSize;; + break; + +#ifndef QT_NO_SLIDER + case PM_SliderLength: + ret = 12; + break; + case PM_SliderThickness: + ret = windowsCESliderThickness; + break; + + case PM_TabBarScrollButtonWidth: + ret = 18; + break; + + // Returns the number of pixels to use for the business part of the + // slider (i.e., the non-tickmark portion). The remaining space is shared + // equally between the tickmark regions. + case PM_SliderControlThickness: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); + int ticks = sl->tickPosition; + int n = 0; + if (ticks & QSlider::TicksAbove) + ++n; + if (ticks & QSlider::TicksBelow) + ++n; + if (!n) { + ret = space; + break; + } + int thick = 12; + if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks) + thick += pixelMetric(PM_SliderLength, sl, widget) / 4; + + space -= thick; + if (space > 0) + thick += (space * 2) / (n + 2); + ret = thick; + } else { + ret = 0; + } + break; +#endif // QT_NO_SLIDER + +#ifndef QT_NO_MENU + + case PM_SmallIconSize: + ret = windowsCEIconSize; + break; + case PM_ButtonMargin: + ret = 6; + break; + + case PM_LargeIconSize: + ret = 32; + break; + + case PM_IconViewIconSize: + ret = pixelMetric(PM_LargeIconSize, opt, widget); + break; + + case PM_ToolBarIconSize: + ret = windowsCEIconSize; + break; + case PM_DockWidgetTitleMargin: + ret = 2; + break; +#if defined(Q_WS_WIN) +// case PM_DockWidgetFrameWidth: +// ret = GetSystemMetrics(SM_CXFRAME); +// break; +#else + case PM_DockWidgetFrameWidth: + ret = 4; + break; +#endif // Q_WS_WIN + break; + +#endif // QT_NO_MENU + + case PM_TitleBarHeight: + ret = 30; + break; + case PM_ScrollBarExtent: + ret = 19; + break; + case PM_SplitterWidth: + ret = qMax(4, QApplication::globalStrut().width()); + break; + +#if defined(Q_WS_WIN) + case PM_MDIFrameWidth: + ret = 3; + break; +#endif + case PM_ToolBarItemMargin: + ret = 1; + break; + case PM_ToolBarItemSpacing: + ret = 0; + break; + case PM_ToolBarHandleExtent: + ret = 10; + break; + case PM_ButtonIconSize: + ret = 22; + break; + default: + ret = QWindowsStyle::pixelMetric(pm, opt, widget); + break; + } + return ret; +} + +QPixmap QWindowsCEStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const { +#ifndef QT_NO_IMAGEFORMAT_XPM + /*switch (standardPixmap) { + + default: + break; + }*/ +#endif //QT_NO_IMAGEFORMAT_XPM + return QWindowsStyle::standardPixmap(standardPixmap, opt, widget); +} + +int QWindowsCEStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *returnData) const { + int ret; + switch (hint) { + case SH_TabBar_ElideMode: + ret = Qt::ElideMiddle; + break; + case SH_EtchDisabledText: + ret = false; + break; + case SH_RequestSoftwareInputPanel: + ret = RSIP_OnMouseClick; + break; + default: + ret = QWindowsStyle::styleHint(hint, opt, widget, returnData); + break; + } + return ret; +} + +void QWindowsCEStylePrivate::drawWinShades(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill) { + if (w < 2 || h < 2) // can't do anything with that + return; + QPen oldPen = p->pen(); + QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) }; + p->setPen(c1); + p->drawPolyline(a, 3); + QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; + p->setPen(c2); + p->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) }; + p->setPen(c3); + p->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) }; + p->setPen(c4); + p->drawPolyline(d, 3); + if (fill) + p->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); + } + p->setPen(oldPen); +} + +void QWindowsCEStylePrivate::drawWinCEShades(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill) { + if (w < 2 || h < 2) // can't do anything with that + return; + QPen oldPen = p->pen(); + QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; + p->setPen(c2); + p->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) }; + p->setPen(c3); + p->drawPolyline(c, 3); + QPoint d[5] = { QPoint(x, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y), QPoint(x, y), QPoint(x, y+h-2) }; + p->setPen(c4); + p->drawPolyline(d, 5); + if (fill) + p->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); + } + QPoint a[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) }; + p->setPen(c1); + p->drawPolyline(a, 3); + p->setPen(oldPen); +} + +void QWindowsCEStylePrivate::drawWinCEShadesSunken(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill) { + if (w < 2 || h < 2) // can't do anything with that + return; + QPen oldPen = p->pen(); + + QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) }; + p->setPen(c2); + p->drawPolyline(b, 3); + if (w > 4 && h > 4) { + QPoint d[3] = { QPoint(x, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y) }; + p->setPen(c4); + p->drawPolyline(d, 3); + QPoint c[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) }; + p->setPen(c3); + p->drawPolyline(c, 3); + if (fill) + p->fillRect(QRect(x+2, y+2, w-4, h-4), *fill); + } + QPoint a[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) }; + p->setPen(c1); + p->drawPolyline(a, 3); + p->setPen(oldPen); +} + + +void QWindowsCEStylePrivate::drawWinCEButton(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + const QBrush *fill) { + if (sunken) + drawWinCEShadesSunken(p, x, y, w, h, + pal.shadow().color(), pal.light().color(), pal.shadow().color(), + pal.midlight().color(), fill); + else + drawWinCEShades(p, x, y, w, h, + pal.midlight().color(), pal.shadow().color(), pal.button().color(), + pal.dark().color(), fill); +} + +void QWindowsCEStylePrivate::drawWinCEPanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken, + const QBrush *fill) { + if (sunken) + drawWinShades(p, x, y, w, h, + pal.dark().color(), pal.light().color(), pal.shadow().color(), + pal.midlight().color(), fill); + else + drawWinShades(p, x, y, w, h, + pal.light().color(), pal.shadow().color(), pal.button().color(), + pal.midlight().color(), fill); +} + +void QWindowsCEStylePrivate::drawWinCEButton(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, const QBrush *fill) { + drawWinCEButton(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, fill); +} + +void QWindowsCEStylePrivate::drawWinCEPanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken, const QBrush *fill) { + drawWinCEPanel(p, r.x(), r.y(), r.width(), r.height(), pal, sunken, fill); +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSCE diff --git a/src/gui/styles/qwindowscestyle.h b/src/gui/styles/qwindowscestyle.h new file mode 100644 index 0000000000..3fd83bc764 --- /dev/null +++ b/src/gui/styles/qwindowscestyle.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSCESTYLE_H +#define QWINDOWSCESTYLE_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_WINDOWSCE) + +class Q_GUI_EXPORT QWindowsCEStyle : public QWindowsStyle +{ + Q_OBJECT +public: + QWindowsCEStyle(); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + + void drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + + virtual void drawItemText(QPainter *painter, const QRect &rect, + int flags, const QPalette &pal, bool enabled, + const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; + + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + void polish(QWidget *widget); + void polish(QPalette &palette); + void polish(QApplication *app); + QPalette standardPalette() const; +}; + +#endif // QT_NO_STYLE_WINDOWSCE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWINDOWSCESTYLE_H diff --git a/src/gui/styles/qwindowscestyle_p.h b/src/gui/styles/qwindowscestyle_p.h new file mode 100644 index 0000000000..c2d9c68ea7 --- /dev/null +++ b/src/gui/styles/qwindowscestyle_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSCE_P_H +#define QWINDOWSCE_P_H + +#include "qwindowscestyle.h" +#include <private/qwindowsstyle_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. +// + +QT_BEGIN_NAMESPACE + +class QPainter; +class QPalette; +class QPoint; +class QColor; +class QBrush; +class QRect; + +// Private class +class QWindowsCEStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsCEStyle) +public: + inline QWindowsCEStylePrivate() + { } + + +static void drawWinCEButton(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +static void drawWinCEButton(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +static void drawWinCEPanel(QPainter *p, int x, int y, int w, int h, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +static void drawWinCEPanel(QPainter *p, const QRect &r, + const QPalette &pal, bool sunken = false, + const QBrush *fill = 0); + +static void drawWinShades(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill); + +static void drawWinCEShades(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill); + +static void drawWinCEShadesSunken(QPainter *p, + int x, int y, int w, int h, + const QColor &c1, const QColor &c2, + const QColor &c3, const QColor &c4, + const QBrush *fill); + + + + +}; + +QT_END_NAMESPACE + +#endif //QWINDOWSCE_P_H diff --git a/src/gui/styles/qwindowsmobilestyle.cpp b/src/gui/styles/qwindowsmobilestyle.cpp new file mode 100644 index 0000000000..32cc2e63b8 --- /dev/null +++ b/src/gui/styles/qwindowsmobilestyle.cpp @@ -0,0 +1,7283 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsmobilestyle.h" +#include "qwindowsmobilestyle_p.h" + +#if !defined(QT_NO_STYLE_WINDOWSMOBILE) || defined(QT_PLUGIN) + +#include "qpainterpath.h" +#include "qapplication.h" +#include "qdesktopwidget.h" +#include "qwidget.h" +#include "qdockwidget.h" +#include "qframe.h" +#include "qmenu.h" +#include "qpaintengine.h" +#include "qpainter.h" +#include "qgroupbox.h" +#include "qstyleoption.h" +#include "qlistview.h" +#include "qdrawutil.h" +#include "qtoolbar.h" +#include "qabstractscrollarea.h" +#include "qabstractbutton.h" +#include "qcombobox.h" +#include "qabstractscrollarea.h" +#include "qframe.h" +#include "qscrollbar.h" +#include "qabstractitemview.h" +#include "qmenubar.h" +#include "qtoolbutton.h" +#include "qtextedit.h" +#include "qdialog.h" +#include "qdebug.h" +#include "qtabwidget.h" + +#ifdef Q_WS_WINCE +#include "qt_windows.h" +#include "qguifunctions_wince.h" +extern bool qt_wince_is_high_dpi(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_smartphone(); //defined in qguifunctions_wince.cpp +extern bool qt_wince_is_windows_mobile_65(); //defined in qguifunctions_wince.cpp +#endif // Q_WS_WINCE + +#include "qstylehelper_p.h" + +QT_BEGIN_NAMESPACE + +static const int windowsItemFrame = 1; // menu item frame width + +static const int windowsMobileitemViewCheckBoxSize = 13; +static const int windowsMobileFrameGroupBoxOffset = 9; +static const int windowsMobileIndicatorSize = 14; +static const int windowsMobileExclusiveIndicatorSize = 14; +static const int windowsMobileSliderThickness = 6; +static const int windowsMobileIconSize = 16; +static const int PE_IndicatorArrowUpBig = 0xf000101; +static const int PE_IndicatorArrowDownBig = 0xf000102; +static const int PE_IndicatorArrowLeftBig = 0xf000103; +static const int PE_IndicatorArrowRightBig = 0xf000104; + +/* XPM */ +static const char *const radiobutton_xpm[] = { + "30 30 2 1", + " c None", + ". c #000000", + " ........ ", + " .............. ", + " .... .... ", + " .... .... ", + " ... ... ", + " ... ... ", + " .. .. ", + " .. .. ", + " ... ... ", + " .. .. ", + " .. .. ", + ".. ..", + ".. ..", + ".. ..", + ".. ..", + ".. ..", + ".. ..", + ".. ..", + ".. ..", + " .. .. ", + " .. .. ", + " ... ... ", + " .. .. ", + " .. .. ", + " ... ... ", + " ... ... ", + " .... .... ", + " .... .... ", + " .............. ", + " ........ "}; + +/* XPM */ +static const char * const radiobutton_low_xpm[] = { + "15 15 2 1", + " c None", + ". c #000000", + " ..... ", + " .. .. ", + " . . ", + " . . ", + " . . ", + ". .", + ". .", + ". .", + ". .", + ". .", + " . . ", + " . . ", + " . . ", + " .. .. ", + " ..... "}; + +/* XPM */ + static const char * const arrowleft_big_xpm[] = { + "9 17 2 1", + " c None", + ". c #000000", + " .", + " ..", + " ...", + " ....", + " .....", + " ......", + " .......", + " ........", + ".........", + " ........", + " .......", + " ......", + " .....", + " ....", + " ...", + " ..", + " ."}; + +/* XPM */ + static const char * const arrowleft_xpm[] = { + "8 15 2 1", + " c None", + ". c #000000", + " .", + " ..", + " ...", + " ....", + " .....", + " ......", + " .......", + "........", + " .......", + " ......", + " .....", + " ....", + " ...", + " ..", + " ."}; + + + +/* XPM */ +static const char *const horlines_xpm[] = { + "2 2 2 1", + " c None", + ". c #000000", + " ", + ".."}; + +/* XPM */ +static const char *const vertlines_xpm[] = { + "2 2 2 1", + " c None", + ". c #000000", + ". ", + ". "}; + +/* XPM */ +static const char *const radiochecked_xpm[] = { + "18 18 2 1", + " c None", + ". c #000000", + " ...... ", + " .......... ", + " .............. ", + " .............. ", + " ................ ", + " ................ ", + "..................", + "..................", + "..................", + "..................", + "..................", + "..................", + " ................ ", + " ................ ", + " .............. ", + " .............. ", + " .......... ", + " ...... "}; + +/* XPM */ +static const char * const radiochecked_low_xpm[] = { + "9 9 2 1", + " c None", + ". c #000000", + " ... ", + " ....... ", + " ....... ", + ".........", + ".........", + ".........", + " ....... ", + " ....... ", + " ... "}; + +static const char *const arrowdown_xpm[] = { + "15 8 2 1", + " c None", + ". c #000000", + "...............", + " ............. ", + " ........... ", + " ......... ", + " ....... ", + " ..... ", + " ... ", + " . "}; + + +static const char *const arrowdown_big_xpm[] = { + "17 9 2 1", + " c None", + ". c #000000", + ".................", + " ............... ", + " ............. ", + " ........... ", + " ......... ", + " ....... ", + " ..... ", + " ... ", + " . "}; + + +/* XPM */ +static const char *const checkedlight_xpm[] = { + "24 24 2 1", + " c None", + ". c #000000", + " ", + " ", + " ", + " ", + " ", + " . ", + " .. ", + " ... ", + " .... ", + " ..... ", + " ...... ", + " . ...... ", + " .. ...... ", + " ... ...... ", + " .... ...... ", + " .......... ", + " ......... ", + " ....... ", + " ..... ", + " ... ", + " . ", + " ", + " ", + " "}; + + +/* XPM */ +static const char *const checkedbold_xpm[] = { + "26 26 2 1", + " c None", + ". c #000000", + " ", + " ", + " ", + " ", + " ", + " ", + " .. ", + " ... ", + " .... ", + " ..... ", + " .. ...... ", + " ... ....... ", + " .... ....... ", + " ..... ....... ", + " ...... ....... ", + " .............. ", + " ............ ", + " .......... ", + " ........ ", + " ...... ", + " .... ", + " .. ", + " ", + " ", + " ", + " "}; + +/* XPM */ +static const char * const checkedbold_low_xpm[] = { + "9 8 2 1", + " c None", + ". c #000000", + " .", + " ..", + ". ...", + ".. ... ", + "... ... ", + " ..... ", + " ... ", + " . "}; + +/* XPM */ +static const char * const checkedlight_low_xpm[] = { + "8 8 2 1", + " c None", + ". c #000000", + " .", + " ..", + " ...", + ". ... ", + ".. ... ", + "..... ", + " ... ", + " . "}; + +/* XPM */ +static const char * const highlightedradiobutton_xpm[] = { + "30 30 3 1", + " c None", + ". c #000000", + "+ c #0078CC", + " ........ ", + " .............. ", + " ....++++++++.... ", + " ....++++++++++++.... ", + " ...++++ ++++... ", + " ...+++ +++... ", + " ..++ ++.. ", + " ..++ ++.. ", + " ...++ ++... ", + " ..++ ++.. ", + " ..++ ++.. ", + "..++ ++..", + "..++ ++..", + "..++ ++..", + "..++ ++..", + "..++ ++..", + "..++ ++..", + "..++ ++..", + "..++ ++..", + " ..++ ++.. ", + " ..++ ++.. ", + " ...++ ++... ", + " ..++ ++.. ", + " ..++ ++.. ", + " ...+++ +++... ", + " ...++++ ++++... ", + " ....++++++++++++.... ", + " ....++++++++.... ", + " .............. ", + " ........ "}; + +/* XPM */ +static const char * const highlightedradiobutton_low_xpm[] = { + "15 15 3 1", + " c None", + ". c #000000", + "+ c #3192D6", + " ..... ", + " ..+++++.. ", + " .++ ++. ", + " .+ +. ", + " .+ +. ", + ".+ +.", + ".+ +.", + ".+ +.", + ".+ +.", + ".+ +.", + " .+ +. ", + " .+ +. ", + " .++ ++. ", + " ..+++++.. ", + " ..... "}; + +/* XPM */ +static const char * const cross_big_xpm[] = { +"28 28 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FDFFFC", +" ", +" ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ++....................++ ", +" ++....................++ ", +" ++..@@@..........@@@..++ ", +" ++..@@@@........@@@@..++ ", +" ++..@@@@@......@@@@@..++ ", +" ++...@@@@@....@@@@@...++ ", +" ++....@@@@@..@@@@@....++ ", +" ++.....@@@@@@@@@@.....++ ", +" ++......@@@@@@@@......++ ", +" ++.......@@@@@@.......++ ", +" ++.......@@@@@@.......++ ", +" ++......@@@@@@@@......++ ", +" ++.....@@@@@@@@@@.....++ ", +" ++....@@@@@..@@@@@....++ ", +" ++...@@@@@....@@@@@...++ ", +" ++..@@@@@......@@@@@..++ ", +" ++..@@@@........@@@@..++ ", +" ++..@@@..........@@@..++ ", +" ++....................++ ", +" ++....................++ ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ", +" "}; + +/* XPM */ +static const char * const cross_small_xpm[] = { +"14 14 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FCFFFC", +" ", +" ++++++++++++ ", +" +..........+ ", +" +.@@....@@.+ ", +" +.@@@..@@@.+ ", +" +..@@@@@@..+ ", +" +...@@@@...+ ", +" +...@@@@...+ ", +" +..@@@@@@..+ ", +" +.@@@..@@@.+ ", +" +.@@....@@.+ ", +" +..........+ ", +" ++++++++++++ ", +" "}; + +/* XPM */ +static const char * const max_big_xpm[] = { +"28 28 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FDFFFC", +" ", +" ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ", +" "}; + +/* XPM */ +static const char * const max_small_xpm[] = { +"14 14 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FCFFFC", +" ", +" ++++++++++++ ", +" +..........+ ", +" +..........+ ", +" +.@@@@@@@@.+ ", +" +.@@@@@@@@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@@@@@@@@.+ ", +" +..........+ ", +" +..........+ ", +" ++++++++++++ ", +" "}; + +/* XPM */ +static const char * const normal_big_xpm[] = { +"28 28 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FDFFFC", +" ", +" ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ++....................++ ", +" ++....................++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@............@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++....................++ ", +" ++....................++ ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ", +" "}; + +/* XPM */ +static const char * const normal_small_xpm[] = { +"14 14 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FCFFFC", +" ", +" ++++++++++++ ", +" +..........+ ", +" +.@@@@@@@@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@......@.+ ", +" +.@@@@@@@@.+ ", +" +..........+ ", +" ++++++++++++ ", +" "}; + + +/* XPM */ +static const char * const min_big_xpm[] = { +"28 28 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FDFFFC", +" ", +" ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++..@@@@@@@@@@@@@@@@..++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++....................++ ", +" ++++++++++++++++++++++++ ", +" ++++++++++++++++++++++++ ", +" ", +" "}; + +/* XPM */ +static const char * const min_small_xpm[] = { +"14 14 4 1", +" c #09454A", +". c #218C98", +"+ c #47D8E5", +"@ c #FCFFFC", +" ", +" ++++++++++++ ", +" +..........+ ", +" +..........+ ", +" +..........+ ", +" +..........+ ", +" +..........+ ", +" +..........+ ", +" +..........+ ", +" +.@@@@@@@@.+ ", +" +..........+ ", +" +..........+ ", +" ++++++++++++ ", +" "}; + +#ifdef Q_WS_WINCE_WM + +static char * sbhandleup_xpm[] = { +"26 41 45 1", +" c None", +". c #000000", +"+ c #E7E7E7", +"@ c #D6D7D6", +"# c #949294", +"$ c #737573", +"% c #636563", +"& c #636163", +"* c #5A5D5A", +"= c #5A595A", +"- c #525552", +"; c #525152", +"> c #4A4D4A", +", c #7B797B", +"' c #CECFCE", +") c #CED3CE", +"! c #6B6D6B", +"~ c #6B696B", +"{ c #737173", +"] c #7B7D7B", +"^ c #848684", +"/ c #848284", +"( c #8C8A8C", +"_ c #8C8E8C", +": c #B5B2B5", +"< c #FFFFFF", +"[ c #949694", +"} c #B5B6B5", +"| c #9C9A9C", +"1 c #ADAEAD", +"2 c #9C9E9C", +"3 c #BDBABD", +"4 c #BDBEBD", +"5 c #F7F3F7", +"6 c #C6C3C6", +"7 c #C6C7C6", +"8 c #A5A2A5", +"9 c #CECBCE", +"0 c #FFFBFF", +"a c #ADAAAD", +"b c #A5A6A5", +"c c #D6D3D6", +"d c #B5BAB5", +"e c #DEDFDE", +"f c #DEDBDE", +"..........................", +"+@#$%%&&&**===---;;;;>=,'+", +"+@#$%%&&&**===---;;;;>=$'+", +")$!!~~%%&&&**===---;;;;>;'", +"#{$]],,$${{{!!~~%%%&&&*-;]", +"#{$]],,$${{{!!~~%%%&&&*-;]", +",$^//]],,$${{{!!~~%%%&&*;*", +",,(^^//]],$${!!!!!~~%%%&-*", +",,(^^//]],$${!!!!!~~%%%&-*", +"]]_((^^//]$!%%~!{{!!~~%%-*", +"//#__((^^]{:<<:~!{{{!!~~=*", +"//#__((^^]{:<<:~!{{{!!~~=&", +"//###__(/$:<<<<:~{${{!!~*&", +"^^[[##_^]:<<<<<<}!{$${{!*%", +"^^[[##_^]:<<<<<<}!{$${{!*%", +"((|[[#_/:<<<<<<<<}!$$${{&~", +"((||[#^1<<<<1:<<<<}!$$$$&~", +"((||[#^1<<<<1:<<<<}!$$$$&~", +"__2|#(1<<<<}],}<<<<}{$,$%~", +"##2|_1<<<<}^((]3<<<<}{$,~!", +"##2|_1<<<<}^((]3<<<<}{$,~!", +"##2#1<<<<3^###(/4<<<<}{,~{", +"##2#1<<<<3^###(/4<<<<}{,~!", +"[[2_5<<<4(#|[[#_/6<<<<,,!{", +"[|2_5<<4_[||||[[_/7<<<,]{$", +"[|2_5<<4_[||||[[_/7<<<,]{$", +"||8_5<6#|2222|||[_/9<<,]{$", +"228#06[28888222||[_/'<,/$,", +"228#06[28888222||[_/'<,/$,", +"22a|6[8bbbb88822||[(/c](,]", +"881b8baaabbbb88222|[(^(_,]", +"881b8baaabbbb88222|[(^(_,]", +"88111111aaabbb88822|[###]/", +"bb:::11111aaabbb8822||[[/^", +"bb:::11111aaabbb8822||[[//", +"bb:::::1111aaabbb8822||[/(", +"3a1::::::1111aaabb8822|_^8", +"da1::::::1111aaabb8822|_^8", +"e1aaabbbb888822||[[##__((@", +"+e4:aaabbbb88822||[[[#[b@+", +"+e4:aaabbbb88822||[[[#[bf+"}; + +static char * sbhandledown_xpm[] = { +"26 40 46 1", +" c None", +". c #E7E7E7", +"+ c #DEDFDE", +"@ c #BDBEBD", +"# c #B5B2B5", +"$ c #ADAAAD", +"% c #A5A6A5", +"& c #A5A2A5", +"* c #9C9E9C", +"= c #9C9A9C", +"- c #949694", +"; c #949294", +"> c #D6D7D6", +", c #DEDBDE", +"' c #D6DBD6", +") c #ADAEAD", +"! c #8C8E8C", +"~ c #8C8A8C", +"{ c #BDBABD", +"] c #848684", +"^ c #B5BAB5", +"/ c #848284", +"( c #848A84", +"_ c #7B7D7B", +": c #7B797B", +"< c #C6C3C6", +"[ c #D6D3D6", +"} c #FFFBFF", +"| c #CECFCE", +"1 c #FFFFFF", +"2 c #737573", +"3 c #F7F3F7", +"4 c #CECBCE", +"5 c #737173", +"6 c #C6C7C6", +"7 c #6B6D6B", +"8 c #B5B6B5", +"9 c #6B696B", +"0 c #636563", +"a c #636163", +"b c #5A5D5A", +"c c #5A595A", +"d c #525552", +"e c #525152", +"f c #4A4D4A", +"g c #C6CBC6", +".+@#$$$%%%%&&&**==---;-%>.", +".+@#$$$%%%%&&&**==---;-%,.", +"')$$$%%%%&&&&**==--;;!!~~>", +"{$)######))))$$$%%&&**=!]&", +"^$)######))))$$$%%&&**=!]&", +"%%#####))))$$$%%%&&**==-/(", +"%%###)))))$$$%%%&&**==--/]", +"%%###)))))$$$%%%&&**==--//", +"&&))))))$$$%%%&&&**=-;;;_/", +"&&)%&%$$$%%%%&&***=-~]~!:_", +"&&)%&%$$$%%%%&&***=-~]~!:_", +"**$=<-&%%%%&&&**==-~/[_~:_", +"**&;}<-*&&&&***==-!/|1:/2:", +"**&;}<-*&&&&***==-!/|1:/2:", +"==&!31<;=****===-!/411:_5:", +"-=*!311@!-====--!/6111:_52", +"-=*!311@!-====--!/6111:_52", +"--*!3111@~;=--;!/<1111::75", +";;*;)1111{];;;~/@111185:95", +";;*;)1111{];;;~/@111185:97", +";;*=!)11118]~~_{1111852:97", +";;*=!)11118]~~_{1111852:97", +"!!*=;~)11118_:81111852:207", +"~~==-;])1111)#1111872222a9", +"~~==-;])1111)#1111872222a9", +"~~=--;!/#111111118722255a0", +"]]--;;!]_#11111187522557b0", +"]]--;;!]_#11111187522557b0", +"//;;;!!~/2#1111#95255779ba", +"//;!!~~]]_5#11#975557799cb", +"//;!!~~]]_5#11#975557799ca", +"__!~~]]//_27009755779900db", +"::~]]//__:2257777799000adb", +"::~]]//__:2257777799000adb", +":2]//__::225557799000aabeb", +";52__::225557799000aaabde_", +";52__::225557799000aaabde_", +"[2779900aaabbcccdddeeeefeg", +".>;200aaabbcccdddeeeefc:|.", +".>;200aaabbcccdddeeeefc2|."}; + +static char * sbgripdown_xpm[] = { +"26 34 39 1", +" c None", +". c #949294", +"+ c #9C9E9C", +"@ c #9C9A9C", +"# c #949694", +"$ c #8C8E8C", +"% c #8C8A8C", +"& c #848684", +"* c #848284", +"= c #7B7D7B", +"- c #7B797B", +"; c #6B696B", +"> c #636563", +", c #737573", +"' c #636163", +") c #737173", +"! c #5A5D5A", +"~ c #6B6D6B", +"{ c #5A595A", +"] c #B5B6B5", +"^ c #BDBEBD", +"/ c #ADAEAD", +"( c #BDBABD", +"_ c #525552", +": c #313031", +"< c #525152", +"[ c #ADAAAD", +"} c #BDBAB5", +"| c #4A4D4A", +"1 c #4A494A", +"2 c #C6C3C6", +"3 c #C6CBC6", +"4 c #E7E7E7", +"5 c #DEDFDE", +"6 c #E7E3E7", +"7 c #DEE3DE", +"8 c #CECBCE", +"9 c #8C928C", +"0 c #CECFCE", +"..+++@@@###...$$%&&**==-;>", +"$.++@@@@##...$$%%&**==-->>", +"$$+@@@@###..$$%%&&*==--,>>", +"$$@@@@###..$$%%&&**==-,,>'", +"%%@@@###..$$$%&&**==--,,''", +"%%@@###..$$$%&&**==--,,)''", +"%%@###...$$%%&&*==--,,))'!", +"&&###...$$%%&&**==--,)))!!", +"&&##...$$%%&&**==--,,))~!!", +"&&#...$$%%&&**==--,,))~~!{", +"**...$$%%&&**==--,,))~~;!{", +"**..$$%%&&**===--,)))~~;{{", +"**.$$%%&]^&===//,,))~~;;{{", +"==$$%%&&]^*==-((,))~~;;>{_", +"==$%%&&***::--,,::~~;;;>__", +"==%%&&&**=::-,,)::~~;;>>__", +"--%&&&**==--,,)))~~;;>>>__", +"--&&&**==--,,)))~~;;>>>'_<", +",-&&**==]^-,))[[~;;>>>''<<", +",,&**==-]^-)))}};;>>>'''<<", +",,**==--,,::)~~;::>>'''!<<", +"))*==--,,)::~~;;::>'''!!<|", +"))==--,,)))~~;;;>>'''!!!||", +"))=--,,)))~~;;;>>'''!!!{||", +"~~--,,)))~~;;;>>'''!!!{{||", +"~~-,,)))~~;;>>>'''!!!{{{|1", +";;,,)))~~;;>>>'''!!!{{{_1<", +"~;,)))~~;;>>>'''!!!{{{__1'", +"%>~))~~;;>>>'''!!!{{{__|1$", +"2>>~~~;;>>>''!!!{{{{__<113", +"4%'';;;>>>''!!!{{{{__<11%4", +"45-!!'>>>''!!!{{{{_<|11)64", +"447+!{{___<<<||||1111|+444", +"444489~__<<<||||111>$04444"}; + +static char * sbgripup_xpm[] = { +"26 34 38 1", +" c None", +". c #E7E7E7", +"+ c #D6DBD6", +"@ c #C6C7C6", +"# c #B5B6B5", +"$ c #ADAEAD", +"% c #ADAAAD", +"& c #A5A6A5", +"* c #A5A2A5", +"= c #BDBEBD", +"- c #DEDFDE", +"; c #C6CBC6", +"> c #9C9E9C", +", c #E7E3E7", +"' c #BDBABD", +") c #B5B2B5", +"! c #9C9A9C", +"~ c #DEE3DE", +"{ c #949694", +"] c #D6D7D6", +"^ c #949294", +"/ c #DEDBDE", +"( c #8C8E8C", +"_ c #8C8A8C", +": c #848684", +"< c #D6D3CE", +"[ c #CECBCE", +"} c #D6D3D6", +"| c #848284", +"1 c #313031", +"2 c #7B7D7B", +"3 c #CECFCE", +"4 c #CECBC6", +"5 c #7B797B", +"6 c #737573", +"7 c #737173", +"8 c #6B6D6B", +"9 c #6B696B", +"....+@#$$%%%%&&&***$=-....", +"...;$$$$$%%%&&&&**>>>>@...", +".,'$$)#'#####)))$$$%*!!$~.", +".=$)#'''####))))$$$%%*!{'.", +"]$$''''#####)))$$$%%%&*{^/", +"=$#'''#####)))$$$$%%&&&!^#", +"$$'''#####))))$$$%%%&&*>(!", +"$$''#####))))$$$%%%&&&*>(^", +"$$######))))$$$$%%&&&**>(_", +"%$#####))))$$$$%%%&&***>__", +"%$####))))$$$$%%%&&&**>>__", +"%%###)))))$$$%%%&&&**>>>_:", +"%%##))))<])$$%[[&&***>>!::", +"%%#)))))<]$$%%}<&&**>>!!:|", +"&%)))))$$$11%%&&11*>>>!!:|", +"&&))))$$$$11%&&&11*>>!!{||", +"&&)))$$$$$%%%&&&**>>!!!{|2", +"&&))$$$$$%%%&&&**>>>!!{{|2", +"*&)$$$$$3]%&&&4@*>>!!{{{22", +"**$$$$$%3]%&&&<<>>!!!{{^25", +"**$$$$%%%%11&**>11!!{{^^25", +"**$$$%%%%&11***>11!!{{^^55", +"**$$%%%%&&&***>>!!!{{^^(55", +">>$%%%%&&&***>>>!!{{^^((56", +">>%%%%&&&&***>>!!!{{^^((66", +">>%%%&&&&***>>!!!{{^^((_67", +"!>%%&&&&***>>>!!{{{^^(__67", +"!!%&&&&***>>>!!!{{^^((_:77", +"!!&&&&***>>>!!!{{^^((__:77", +"!!&&&****>>!!!{{^^^(__::78", +"{!&&****>>>!!{{{^^((_::|88", +"{{&****>>>!!!{{^^((__:||88", +"{{****>>>!!!{{^^^(__::|289", +"{{***>>>!!!{{{^^((_::||289"}; + +static char * sbgripmiddle_xpm[] = { +"26 2 12 1", +" c None", +". c #949294", +"+ c #A5A2A5", +"@ c #9C9E9C", +"# c #9C9A9C", +"$ c #949694", +"% c #8C8E8C", +"& c #8C8A8C", +"* c #848684", +"= c #848284", +"- c #7B7D7B", +"; c #6B696B", +"..++@@@###$$$..%%&&*==--;;", +"..++@@@###$$$..%%&&*==--;;"}; + + +static char * listviewhighmiddle_xpm[] = { +"8 46 197 2", +" c None", +". c #66759E", +"+ c #6C789D", +"@ c #6A789E", +"# c #6B789E", +"$ c #6A779D", +"% c #6C789C", +"& c #6F7D9B", +"* c #6F7D9A", +"= c #9DB6EE", +"- c #9DB6ED", +"; c #9CB6ED", +"> c #A1B6EF", +", c #A2B6F0", +"' c #93AAE9", +") c #95ABEA", +"! c #94ABEA", +"~ c #94A9E8", +"{ c #8BA8EA", +"] c #8BA7EA", +"^ c #8AA7EA", +"/ c #8EAAE8", +"( c #8FAAE8", +"_ c #88A2E7", +": c #8CA3E8", +"< c #8BA3E7", +"[ c #8BA3E8", +"} c #8BA2E7", +"| c #8CA2E7", +"1 c #8DA2E7", +"2 c #87A1E8", +"3 c #87A1E9", +"4 c #86A0E8", +"5 c #86A1E7", +"6 c #87A2E7", +"7 c #859EE9", +"8 c #849DE9", +"9 c #869EE9", +"0 c #869FE9", +"a c #7C9BEA", +"b c #7C9CEA", +"c c #7B9CEA", +"d c #7C9BE9", +"e c #7E9CE9", +"f c #7B9AEA", +"g c #7C99E9", +"h c #7C9AEA", +"i c #7B9AE8", +"j c #7A9AEA", +"k c #7996E1", +"l c #7C96E4", +"m c #7B96E3", +"n c #7B95E3", +"o c #7E95E5", +"p c #7E95E6", +"q c #7292E1", +"r c #7490DF", +"s c #7591E0", +"t c #7590DF", +"u c #7392E1", +"v c #6D8CDE", +"w c #6F8EDD", +"x c #6E8DDD", +"y c #6E8DDE", +"z c #6F8EDE", +"A c #6E8EDE", +"B c #718EDD", +"C c #728EDD", +"D c #6B89E0", +"E c #6C89DF", +"F c #6D89E0", +"G c #6D89DF", +"H c #6C88DF", +"I c #6D88DF", +"J c #6D86DD", +"K c #6086E0", +"L c #6686E0", +"M c #6586E0", +"N c #6486E0", +"O c #6485E0", +"P c #6786DF", +"Q c #5F85E0", +"R c #6583DE", +"S c #6683DE", +"T c #6682DD", +"U c #6086DF", +"V c #5F86E0", +"W c #567ED7", +"X c #567ED8", +"Y c #557DD7", +"Z c #5A7FD8", +"` c #6281DA", +" . c #5379D9", +".. c #5278D9", +"+. c #547BD8", +"@. c #4C73D7", +"#. c #4B72D2", +"$. c #4C73D4", +"%. c #4C73D3", +"&. c #4B72D4", +"*. c #4F75D3", +"=. c #5074D2", +"-. c #4971D0", +";. c #4871D0", +">. c #335ECF", +",. c #325ECB", +"'. c #335ECD", +"). c #335ECE", +"!. c #325DCD", +"~. c #2E59C9", +"{. c #3059C9", +"]. c #2F59C9", +"^. c #2F59C8", +"/. c #2B59CA", +"(. c #3355C6", +"_. c #3354C5", +":. c #3156C7", +"<. c #3056C7", +"[. c #3355C7", +"}. c #3355C5", +"|. c #254EBF", +"1. c #1F51C1", +"2. c #234FC0", +"3. c #234FBF", +"4. c #2350C0", +"5. c #1E50BE", +"6. c #1D50C0", +"7. c #264DBE", +"8. c #264CBD", +"9. c #254DBE", +"0. c #244EBF", +"a. c #254DBF", +"b. c #234CBF", +"c. c #244CC0", +"d. c #244BC0", +"e. c #234BC0", +"f. c #234BBF", +"g. c #234CBE", +"h. c #2049B7", +"i. c #2A49B5", +"j. c #2749B5", +"k. c #2749B6", +"l. c #2D49B4", +"m. c #2649B6", +"n. c #2946B5", +"o. c #2A48B6", +"p. c #2947B5", +"q. c #2946B6", +"r. c #2848B6", +"s. c #2549B5", +"t. c #2648B6", +"u. c #2744B5", +"v. c #2744B4", +"w. c #2744AF", +"x. c #2543B4", +"y. c #2543B2", +"z. c #2442B2", +"A. c #2442B3", +"B. c #2442B5", +"C. c #2543B3", +"D. c #1F40B1", +"E. c #1E40B1", +"F. c #243EAE", +"G. c #273BAC", +"H. c #263DAC", +"I. c #253CAB", +"J. c #273CAB", +"K. c #273CAC", +"L. c #263BAA", +"M. c #253CAE", +"N. c #263BA6", +"O. c #253BA5", +"P. c #253AA5", +"Q. c #253BA6", +"R. c #253CA7", +"S. c #263AA6", +"T. c #243CA6", +"U. c #253CA5", +"V. c #273BA8", +"W. c #2F4DA4", +"X. c #2F4DA3", +"Y. c #1B2F85", +"Z. c #B5B5B6", +"`. c #B5B5B5", +" + c #B5B6B6", +".+ c #B5B4B6", +"++ c #C2C3C5", +"@+ c #C0C3C3", +"#+ c #C1C3C4", +"$+ c #E3E3E3", +"%+ c #E3E3E4", +"&+ c #E4E3E4", +"*+ c #E2E3E4", +"=+ c #ECEEEB", +"-+ c #EBEDEA", +";+ c #EEF0ED", +">+ c #EFF0EE", +". + @ @ # # $ % ", +"& & * & & & & & ", +"= = - = = ; > , ", +"' ) ! ! ! ) ' ~ ", +"{ ] { { { ^ / ( ", +"_ : < [ : } | 1 ", +"2 2 2 3 2 4 5 6 ", +"7 7 7 7 7 8 9 0 ", +"a b a a a c d e ", +"f g h h h h i j ", +"k l m m m n o p ", +"q q q q q q q q ", +"r r s s s t q u ", +"v w x y z A B C ", +"D E F F G F H I ", +"J K L M N O P Q ", +"R R S S S T U V ", +"W W X X X Y Z ` ", +" . . . . ...+.W ", +" . . . . ..... .", +"@.#.$.$.%.&.*.=.", +"-.-.;.-.-.-.-.-.", +">.,.'.).).!.!.>.", +"~.{.].^.].^././.", +"(.(.(.(.(._.:.<.", +"(.(.[.[.[.[.(.}.", +"|.1.2.3.3.4.5.6.", +"7.7.7.7.7.8.9.0.", +"a.b.c.d.c.e.f.g.", +"h.i.j.k.j.k.l.m.", +"n.o.p.q.r.p.s.t.", +"u.u.v.u.u.u.u.u.", +"w.x.y.z.A.y.B.C.", +"D.D.E.D.D.D.D.D.", +"D.D.E.D.D.D.D.D.", +"F.G.H.I.J.K.L.M.", +"N.N.O.N.N.P.Q.R.", +"N.N.S.N.N.N.N.N.", +"T.N.T.T.T.U.N.V.", +"W.W.X.W.W.W.W.W.", +"W.W.W.W.W.W.W.W.", +"Y.Y.Y.Y.Y.Y.Y.Y.", +"Z.`. + +.+Z.`.`.", +"++@+#+#+#+#+@+@+", +"$+%+&+&+*+%+%+%+", +"=+-+;+-+-+>+-+-+"}; + + + +static char * listviewhighcornerleft_xpm[] = { +"100 46 1475 2", +" c None", +". c #FBFBFC", +"+ c #E8EAE7", +"@ c #758DC3", +"# c #42599E", +"$ c #28418A", +"% c #19418F", +"& c #3F5695", +"* c #415896", +"= c #435A98", +"- c #445C99", +"; c #465E9B", +"> c #48609B", +", c #49629C", +"' c #4A639D", +") c #49639D", +"! c #4A629D", +"~ c #4B639D", +"{ c #4B649D", +"] c #4C659D", +"^ c #4D669D", +"/ c #4E689D", +"( c #506A9D", +"_ c #516A9D", +": c #536B9C", +"< c #546C9C", +"[ c #566D9B", +"} c #576D9B", +"| c #586E9C", +"1 c #5B6F9D", +"2 c #61739D", +"3 c #63749E", +"4 c #64749E", +"5 c #68769E", +"6 c #6A779E", +"7 c #6B789E", +"8 c #66759E", +"9 c #6C789D", +"0 c #EEF0ED", +"a c #D0D3DC", +"b c #3E51A3", +"c c #28428B", +"d c #29428C", +"e c #425996", +"f c #455C99", +"g c #485F9C", +"h c #49619E", +"i c #4A63A0", +"j c #4B64A1", +"k c #4B65A1", +"l c #4C66A2", +"m c #4D67A2", +"n c #4F69A1", +"o c #516AA1", +"p c #536CA0", +"q c #556DA1", +"r c #576EA0", +"s c #586F9F", +"t c #586E9F", +"u c #596F9E", +"v c #5A6F9E", +"w c #5C709E", +"x c #5E719E", +"y c #5F729F", +"z c #62739F", +"A c #63739E", +"B c #64749D", +"C c #65749E", +"D c #69769D", +"E c #6C799E", +"F c #6D799F", +"G c #707D9F", +"H c #717F9E", +"I c #6E7AA1", +"J c #6C789E", +"K c #6F7C9C", +"L c #6F7D9B", +"M c #2A4AA0", +"N c #4971D0", +"O c #4C72D8", +"P c #5472C0", +"Q c #5573BF", +"R c #5774BF", +"S c #5875BF", +"T c #5976C1", +"U c #5A76C1", +"V c #5C78C2", +"W c #5E7AC2", +"X c #607CC3", +"Y c #627EC3", +"Z c #637FC4", +"` c #6581C5", +" . c #6682C6", +".. c #6783C7", +"+. c #6984C8", +"@. c #6B85C9", +"#. c #6D87CA", +"$. c #6F89CB", +"%. c #718CCD", +"&. c #748ECF", +"*. c #7690D0", +"=. c #7992D2", +"-. c #7A93D3", +";. c #7C95D5", +">. c #7F98D7", +",. c #8099D8", +"'. c #859CDB", +"). c #8AA0DD", +"!. c #8DA3DF", +"~. c #8FA5E0", +"{. c #90A5E0", +"]. c #91A6E1", +"^. c #91A5E1", +"/. c #90A4E0", +"(. c #8EA3DE", +"_. c #92A6E2", +":. c #8FA4DF", +"<. c #90A5DE", +"[. c #90A5DC", +"}. c #90A6DB", +"|. c #91A6E0", +"1. c #93A7E2", +"2. c #95AAE6", +"3. c #99AEEA", +"4. c #9AB2EA", +"5. c #99B1E9", +"6. c #99B1E7", +"7. c #98AFE6", +"8. c #93A8E2", +"9. c #97ACE7", +"0. c #9AB3EB", +"a. c #9DB5ED", +"b. c #9DB6EE", +"c. c #375095", +"d. c #4056AD", +"e. c #506DCD", +"f. c #4360CC", +"g. c #345ED6", +"h. c #335ECF", +"i. c #355ED6", +"j. c #355FD6", +"k. c #365FD6", +"l. c #355FD0", +"m. c #3760D5", +"n. c #3A63D4", +"o. c #3C63D1", +"p. c #3B63CD", +"q. c #3B63C9", +"r. c #3B62C9", +"s. c #3D63C8", +"t. c #4065C5", +"u. c #4567C5", +"v. c #496BC5", +"w. c #4F70C7", +"x. c #5273C8", +"y. c #5475CA", +"z. c #5777CB", +"A. c #5879CD", +"B. c #5A7BCE", +"C. c #5D7DCF", +"D. c #5F7ECF", +"E. c #617FD0", +"F. c #6381D1", +"G. c #6583D2", +"H. c #6785D2", +"I. c #6886D3", +"J. c #6A88D4", +"K. c #6C89D5", +"L. c #6E8BD6", +"M. c #708CD7", +"N. c #718DD8", +"O. c #738EDA", +"P. c #748FDB", +"Q. c #7691DC", +"R. c #7893DD", +"S. c #7994DD", +"T. c #7A96DE", +"U. c #7B97DF", +"V. c #7C98E0", +"W. c #7E9AE2", +"X. c #7F9BE3", +"Y. c #829DE4", +"Z. c #849FE5", +"`. c #87A0E6", +" + c #88A1E7", +".+ c #89A2E6", +"++ c #8CA3E7", +"@+ c #8EA5E9", +"#+ c #8EA6E9", +"$+ c #8FA7E9", +"%+ c #8FA8E8", +"&+ c #8FA9E8", +"*+ c #91A9E8", +"=+ c #90A7E8", +"-+ c #8FA8EA", +";+ c #90AAEA", +">+ c #93ABEA", +",+ c #95ABEA", +"'+ c #93ABE9", +")+ c #94ABEA", +"!+ c #90A9EA", +"~+ c #93AAE9", +"{+ c #273E7E", +"]+ c #345ED5", +"^+ c #3D60CE", +"/+ c #3D60CF", +"(+ c #345ECF", +"_+ c #335ED0", +":+ c #355FD3", +"<+ c #3A60CE", +"[+ c #3A5FCB", +"}+ c #385FC9", +"|+ c #3B60C8", +"1+ c #3C63CB", +"2+ c #3E64CB", +"3+ c #4166CA", +"4+ c #4568C9", +"5+ c #4A6CC7", +"6+ c #4F71C8", +"7+ c #5172CA", +"8+ c #5475CE", +"9+ c #5678D3", +"0+ c #597CD6", +"a+ c #5C7ED7", +"b+ c #5E7FD8", +"c+ c #6181D9", +"d+ c #6383DA", +"e+ c #6585DA", +"f+ c #6786DB", +"g+ c #6988DC", +"h+ c #6B8ADD", +"i+ c #6D8BDE", +"j+ c #6F8DDE", +"k+ c #718EDF", +"l+ c #728FE0", +"m+ c #7390E1", +"n+ c #7390E2", +"o+ c #7491E3", +"p+ c #7592E4", +"q+ c #7693E4", +"r+ c #7794E5", +"s+ c #7894E5", +"t+ c #7995E6", +"u+ c #7B96E6", +"v+ c #7C97E7", +"w+ c #7D9AE8", +"x+ c #7F9CE9", +"y+ c #829DE9", +"z+ c #849EE9", +"A+ c #859EE9", +"B+ c #87A0E7", +"C+ c #8AA2E7", +"D+ c #8BA3E8", +"E+ c #89A2E7", +"F+ c #8CA6EA", +"G+ c #8BA6EA", +"H+ c #8BA7EA", +"I+ c #8CA3E8", +"J+ c #8BA8EA", +"K+ c #8CA7EA", +"L+ c #8CA8EA", +"M+ c #4659C7", +"N+ c #355ECF", +"O+ c #3660CF", +"P+ c #3860CE", +"Q+ c #3961CD", +"R+ c #3B61CB", +"S+ c #3B61CA", +"T+ c #3D62CA", +"U+ c #3D63CA", +"V+ c #4165CB", +"W+ c #456ACB", +"X+ c #4B6FCD", +"Y+ c #5174CE", +"Z+ c #5275D1", +"`+ c #5477D4", +" @ c #5678D9", +".@ c #587ADB", +"+@ c #597BDB", +"@@ c #5B7DDC", +"#@ c #5E7FDC", +"$@ c #6081DD", +"%@ c #6283DE", +"&@ c #6484DF", +"*@ c #6787E0", +"=@ c #6989E1", +"-@ c #6B8BE1", +";@ c #6D8DE2", +">@ c #6F8EE3", +",@ c #718FE4", +"'@ c #7290E4", +")@ c #7491E5", +"!@ c #7692E6", +"~@ c #7793E5", +"{@ c #7894E6", +"]@ c #7895E7", +"^@ c #7996E8", +"/@ c #7A97E8", +"(@ c #7B98E9", +"_@ c #7D99E8", +":@ c #7F9AE8", +"<@ c #7F9BE9", +"[@ c #7F9CEA", +"}@ c #859EE8", +"|@ c #859FE8", +"1@ c #85A0E9", +"2@ c #869FE9", +"3@ c #86A1E7", +"4@ c #86A0E9", +"5@ c #87A1E7", +"6@ c #88A2E7", +"7@ c #87A1E9", +"8@ c #5A6FCA", +"9@ c #365FCF", +"0@ c #345ED0", +"a@ c #385FCC", +"b@ c #385FCE", +"c@ c #3A61CC", +"d@ c #3B62CD", +"e@ c #3E64CD", +"f@ c #4167CF", +"g@ c #4469CF", +"h@ c #486CD1", +"i@ c #4D71D2", +"j@ c #5175D4", +"k@ c #5376D6", +"l@ c #5578DA", +"m@ c #5679DC", +"n@ c #587BDD", +"o@ c #5A7DDE", +"p@ c #5D80DE", +"q@ c #5F82DF", +"r@ c #6284DF", +"s@ c #6585E0", +"t@ c #6787E1", +"u@ c #6988E2", +"v@ c #6B8AE2", +"w@ c #6D8CE3", +"x@ c #6E8DE3", +"y@ c #708EE4", +"z@ c #718FE3", +"A@ c #7391E4", +"B@ c #7592E5", +"C@ c #7895E5", +"D@ c #7996E6", +"E@ c #7A97E6", +"F@ c #7B98E7", +"G@ c #7A98E8", +"H@ c #7B99E9", +"I@ c #7E9AE9", +"J@ c #7D9AE9", +"K@ c #7E9AEA", +"L@ c #809CE9", +"M@ c #819DE8", +"N@ c #7F9BEA", +"O@ c #819DE9", +"P@ c #819CE9", +"Q@ c #839EE9", +"R@ c #839EE8", +"S@ c #839DEA", +"T@ c #859FE9", +"U@ c #87A0E8", +"V@ c #86A0E8", +"W@ c #87A1E8", +"X@ c #3760CF", +"Y@ c #3A61CE", +"Z@ c #3A62CD", +"`@ c #3F66CE", +" # c #4368D0", +".# c #466CD2", +"+# c #496DD5", +"@# c #4E72D6", +"## c #5175D8", +"$# c #5276DA", +"%# c #5578DC", +"&# c #577ADC", +"*# c #597CDD", +"=# c #5B7DDD", +"-# c #5D7FDE", +";# c #5E81DE", +"># c #6183DF", +",# c #6386DF", +"'# c #6687E0", +")# c #6888E0", +"!# c #6A89E1", +"~# c #6C8AE1", +"{# c #6E8CE2", +"]# c #6F8DE2", +"^# c #7390E4", +"/# c #7390E3", +"(# c #7491E4", +"_# c #7693E5", +":# c #7895E6", +"<# c #7896E6", +"[# c #7997E7", +"}# c #7B97E7", +"|# c #7B98E8", +"1# c #7C98E8", +"2# c #7E9BE9", +"3# c #809CEA", +"4# c #819CEA", +"5# c #839DE9", +"6# c #365FD0", +"7# c #3660D0", +"8# c #3961CF", +"9# c #3B63CF", +"0# c #3D64D0", +"a# c #4067D0", +"b# c #4469D2", +"c# c #466BD3", +"d# c #496ED5", +"e# c #4C71D6", +"f# c #4E72D8", +"g# c #5074D9", +"h# c #5376DB", +"i# c #5578DB", +"j# c #587ADC", +"k# c #5B7CDC", +"l# c #5D7EDD", +"m# c #5F80DD", +"n# c #6081DE", +"o# c #6383DE", +"p# c #6686DF", +"q# c #6887E0", +"r# c #6988E0", +"s# c #6B89E1", +"t# c #6C8AE0", +"u# c #6E8CE1", +"v# c #708EE2", +"w# c #718FE2", +"x# c #7290E3", +"y# c #7391E2", +"z# c #7492E1", +"A# c #7592E2", +"B# c #7691E3", +"C# c #7591E3", +"D# c #7692E3", +"E# c #7693E3", +"F# c #7793E4", +"G# c #7893E4", +"H# c #7994E5", +"I# c #7D97E8", +"J# c #7E98E8", +"K# c #7D98E8", +"L# c #7D99E9", +"M# c #7D9BEA", +"N# c #7D9CEA", +"O# c #7E99E8", +"P# c #7D9AEA", +"Q# c #7C9BEA", +"R# c #7C9CEA", +"S# c #355FCF", +"T# c #3860D0", +"U# c #3A62D0", +"V# c #3C64D1", +"W# c #4167D1", +"X# c #4369D3", +"Y# c #466BD4", +"Z# c #486DD5", +"`# c #4A6ED7", +" $ c #4C70D8", +".$ c #5478D9", +"+$ c #577BDA", +"@$ c #597DDB", +"#$ c #5B7EDB", +"$$ c #5D7FDC", +"%$ c #6182DE", +"&$ c #6284DE", +"*$ c #6485DF", +"=$ c #6586DF", +"-$ c #6787DF", +";$ c #6888DF", +">$ c #6A8ADF", +",$ c #6C8BE0", +"'$ c #6D8CE0", +")$ c #6E8DE1", +"!$ c #6F8DE1", +"~$ c #708EE1", +"{$ c #718FE0", +"]$ c #728FE1", +"^$ c #7390E0", +"/$ c #738FE0", +"($ c #7490E1", +"_$ c #7590E1", +":$ c #7591E1", +"<$ c #7592E1", +"[$ c #7692E2", +"}$ c #7794E2", +"|$ c #7894E3", +"1$ c #7996E3", +"2$ c #7A96E5", +"3$ c #7B98E6", +"4$ c #7B9AE8", +"5$ c #7C99E8", +"6$ c #7C96E5", +"7$ c #7D97E7", +"8$ c #7C99E9", +"9$ c #7B9AE9", +"0$ c #7B9AEA", +"a$ c #5B6DCF", +"b$ c #305EC8", +"c$ c #335ECE", +"d$ c #305ECA", +"e$ c #345FCF", +"f$ c #3761D0", +"g$ c #3A62D1", +"h$ c #3C64D2", +"i$ c #4066D3", +"j$ c #466BD5", +"k$ c #486ED6", +"l$ c #4A6ED6", +"m$ c #4D71D8", +"n$ c #4F72D9", +"o$ c #5073D9", +"p$ c #4F72D8", +"q$ c #5074D8", +"r$ c #5276D9", +"s$ c #587ADA", +"t$ c #5B7CDB", +"u$ c #5D7EDC", +"v$ c #5F7FDD", +"w$ c #6081DC", +"x$ c #6182DD", +"y$ c #6283DD", +"z$ c #6484DE", +"A$ c #6585DD", +"B$ c #6787DE", +"C$ c #6988DF", +"D$ c #6A89DE", +"E$ c #6C8ADF", +"F$ c #6D8BDF", +"G$ c #6E8CE0", +"H$ c #6F8DE0", +"I$ c #718EE0", +"J$ c #728FDF", +"K$ c #728FDE", +"L$ c #7290E0", +"M$ c #7190E0", +"N$ c #7291E0", +"O$ c #7191E0", +"P$ c #7392E1", +"Q$ c #7493E1", +"R$ c #7594E1", +"S$ c #7594E2", +"T$ c #7694E2", +"U$ c #7695E2", +"V$ c #7A96E4", +"W$ c #7895E2", +"X$ c #7A96E2", +"Y$ c #7A96E3", +"Z$ c #7B96E3", +"`$ c #7996E1", +" % c #7C96E4", +".% c #305EC9", +"+% c #315ECC", +"@% c #325ECE", +"#% c #3760D0", +"$% c #3962D1", +"%% c #3E66D3", +"&% c #4268D4", +"*% c #446BD5", +"=% c #476CD6", +"-% c #496ED7", +";% c #4B6FD7", +">% c #4C70D7", +",% c #4E71D7", +"'% c #5074D7", +")% c #5276D8", +"!% c #5376D8", +"~% c #5779DA", +"{% c #597ADA", +"]% c #5A7BDB", +"^% c #5B7CDA", +"/% c #5D7EDB", +"(% c #5E7FDB", +"_% c #6182DB", +":% c #6384DC", +"<% c #6586DD", +"[% c #6686DC", +"}% c #6887DD", +"|% c #6988DD", +"1% c #6A8ADE", +"2% c #6B8BDE", +"3% c #6C8CDE", +"4% c #6E8DDF", +"5% c #6E8CDF", +"6% c #6D8DDF", +"7% c #6C8BDF", +"8% c #6F8DDF", +"9% c #718FDF", +"0% c #7290DF", +"a% c #7391E0", +"b% c #7491E0", +"c% c #7292E1", +"d% c #3959C5", +"e% c #345BC5", +"f% c #315EC8", +"g% c #355BC5", +"h% c #325EC8", +"i% c #315ECB", +"j% c #345DCC", +"k% c #335ECD", +"l% c #345ECD", +"m% c #355FCE", +"n% c #3862D0", +"o% c #3E66D2", +"p% c #456BD5", +"q% c #476CD5", +"r% c #4B6ED7", +"s% c #4B6FD6", +"t% c #4B6FD5", +"u% c #4D71D6", +"v% c #5073D7", +"w% c #5174D7", +"x% c #5275D8", +"y% c #5577D8", +"z% c #5678D8", +"A% c #5779D9", +"B% c #587AD8", +"C% c #597CD9", +"D% c #5B7DD9", +"E% c #5D7FDA", +"F% c #5F80DB", +"G% c #6182DC", +"H% c #6484DC", +"I% c #6585DC", +"J% c #6787DD", +"K% c #6988DE", +"L% c #6B8ADE", +"M% c #6B8ADF", +"N% c #6989DE", +"O% c #6B89DE", +"P% c #6E8BDF", +"Q% c #708CDE", +"R% c #708DDF", +"S% c #708FDF", +"T% c #728EDF", +"U% c #6F8EDD", +"V% c #728EDD", +"W% c #7390DF", +"X% c #7490DF", +"Y% c #335DC8", +"Z% c #3759C5", +"`% c #3859C5", +" & c #335EC8", +".& c #325DCA", +"+& c #345CCB", +"@& c #335DCC", +"#& c #345DCD", +"$& c #355FCD", +"%& c #3861D0", +"&& c #3B64D1", +"*& c #3E65D2", +"=& c #4168D3", +"-& c #456AD5", +";& c #4B6ED5", +">& c #4C6FD4", +",& c #4D70D5", +"'& c #4F72D6", +")& c #5173D6", +"!& c #5375D7", +"~& c #5476D8", +"{& c #5577D7", +"]& c #5477D8", +"^& c #5677D8", +"/& c #5879D9", +"(& c #597AD9", +"_& c #5C7DDA", +":& c #6080DC", +"<& c #6080DB", +"[& c #6181DC", +"}& c #6282DC", +"|& c #6383DD", +"1& c #6484DD", +"2& c #6686DE", +"3& c #6685DE", +"4& c #6786DE", +"5& c #6687DE", +"6& c #6887DE", +"7& c #6987DE", +"8& c #6788DF", +"9& c #6785DF", +"0& c #6B89DF", +"a& c #6C89DF", +"b& c #6F8DDD", +"c& c #6D8CDE", +"d& c #445BBB", +"e& c #3759BE", +"f& c #375AC6", +"g& c #355CC8", +"h& c #345CCA", +"i& c #355ECC", +"j& c #365FCD", +"k& c #3761CE", +"l& c #3A63D0", +"m& c #3D65D1", +"n& c #466AD4", +"o& c #476BD4", +"p& c #486CD3", +"q& c #4A6ED4", +"r& c #4B6ED4", +"s& c #4E71D6", +"t& c #4F71D5", +"u& c #5072D6", +"v& c #5274D7", +"w& c #5273D7", +"x& c #5274D6", +"y& c #5476D7", +"z& c #5779D8", +"A& c #587AD9", +"B& c #5A7CDA", +"C& c #5C7DDB", +"D& c #5D7EDA", +"E& c #6081DA", +"F& c #6181DB", +"G& c #6283DC", +"H& c #6483DD", +"I& c #6483DE", +"J& c #6585DE", +"K& c #6786DF", +"L& c #6886DE", +"M& c #6887DF", +"N& c #6987DF", +"O& c #6A88DF", +"P& c #6786E0", +"Q& c #6A86DE", +"R& c #6B89E0", +"S& c #365BC8", +"T& c #365CC8", +"U& c #375DCA", +"V& c #375FCB", +"W& c #3860CD", +"X& c #3C63D0", +"Y& c #4167D2", +"Z& c #4268D2", +"`& c #4368D2", +" * c #4367D2", +".* c #4568D2", +"+* c #466AD2", +"@* c #496CD3", +"#* c #4A6DD3", +"$* c #4A6DD4", +"%* c #4D70D4", +"&* c #4F72D5", +"** c #4C70D4", +"=* c #4E72D5", +"-* c #5173D5", +";* c #5375D6", +">* c #597BDA", +",* c #5B7DDA", +"'* c #5C7EDB", +")* c #5D7FDB", +"!* c #5E80DB", +"~* c #5E81DA", +"{* c #5F81DB", +"]* c #5F82DB", +"^* c #6384DD", +"/* c #6384DE", +"(* c #6585DF", +"_* c #6486E0", +":* c #6583DD", +"<* c #6386E0", +"[* c #6686E0", +"}* c #6B86DD", +"|* c #6D86DD", +"1* c #6086E0", +"2* c #5573CD", +"3* c #3959C3", +"4* c #3959C4", +"5* c #3759C0", +"6* c #375BC7", +"7* c #365CC7", +"8* c #395FCC", +"9* c #3B62CE", +"0* c #3E64D0", +"a* c #4066D1", +"b* c #4166D1", +"c* c #4064CF", +"d* c #4065CF", +"e* c #4266D0", +"f* c #4468D1", +"g* c #4569D1", +"h* c #476BD2", +"i* c #466AD1", +"j* c #476AD2", +"k* c #456AD1", +"l* c #496DD2", +"m* c #4A6FD3", +"n* c #496ED2", +"o* c #4B70D4", +"p* c #4D71D4", +"q* c #4E72D4", +"r* c #5073D4", +"s* c #5174D5", +"t* c #5175D5", +"u* c #5276D6", +"v* c #5377D6", +"w* c #5478D7", +"x* c #5579D7", +"y* c #567AD8", +"z* c #577BD9", +"A* c #597CD8", +"B* c #5A7DD9", +"C* c #5A7ED9", +"D* c #5B7FDA", +"E* c #5C80DA", +"F* c #5D80DA", +"G* c #5E81DB", +"H* c #5D80DB", +"I* c #6082DC", +"J* c #6183DD", +"K* c #6183DE", +"L* c #6082DB", +"M* c #6282DE", +"N* c #6682DE", +"O* c #6583DE", +"P* c #3759BF", +"Q* c #375AC2", +"R* c #375AC1", +"S* c #375AC4", +"T* c #395DCA", +"U* c #3A5ECA", +"V* c #3C60CC", +"W* c #3D61CD", +"X* c #3D61CC", +"Y* c #3C61CD", +"Z* c #3E62CD", +"`* c #3F64CE", +" = c #4266CF", +".= c #4468D0", +"+= c #4267CF", +"@= c #4166CE", +"#= c #4065CE", +"$= c #4166CD", +"%= c #4267CE", +"&= c #456AD0", +"*= c #4368CE", +"== c #4468CF", +"-= c #4569D0", +";= c #486BD1", +">= c #4B6FD3", +",= c #4C70D3", +"'= c #4F73D4", +")= c #5275D5", +"!= c #5477D6", +"~= c #577BD7", +"{= c #587CD8", +"]= c #577CD8", +"^= c #597DD9", +"/= c #5A7DDA", +"(= c #597DDA", +"_= c #587CDA", +":= c #5A7EDA", +"<= c #567BD8", +"[= c #557AD9", +"}= c #567BD9", +"|= c #577CD9", +"1= c #587DD9", +"2= c #587ED9", +"3= c #577ED8", +"4= c #587DD8", +"5= c #587ED8", +"6= c #567ED7", +"7= c #526ABD", +"8= c #3759C1", +"9= c #385BC7", +"0= c #395CC8", +"a= c #3B5DC9", +"b= c #3B5ECA", +"c= c #3A5FCA", +"d= c #3B60CC", +"e= c #3C61CC", +"f= c #3D62CD", +"g= c #3E63CD", +"h= c #3C61CB", +"i= c #3C61CA", +"j= c #3D62CB", +"k= c #3F64CC", +"l= c #4065CD", +"m= c #4669D0", +"n= c #476AD0", +"o= c #496BD1", +"p= c #4A6DD2", +"q= c #4B6ED2", +"r= c #4D71D3", +"s= c #4E73D4", +"t= c #4F74D4", +"u= c #5075D5", +"v= c #5276D5", +"w= c #5377D7", +"x= c #5278D7", +"y= c #5277D6", +"z= c #5378D7", +"A= c #5379D8", +"B= c #5379D9", +"C= c #5278D8", +"D= c #5178D7", +"E= c #3355C0", +"F= c #3556C1", +"G= c #395AC6", +"H= c #385AC7", +"I= c #395BC7", +"J= c #395EC9", +"K= c #395FCA", +"L= c #3B60CA", +"M= c #3B60CB", +"N= c #375DC7", +"O= c #385EC8", +"P= c #395FC9", +"Q= c #3A60CA", +"R= c #3D63CC", +"S= c #4367CF", +"T= c #476BD1", +"U= c #4A6ED2", +"V= c #4B6FD2", +"W= c #4C6FD2", +"X= c #4D70D1", +"Y= c #4E71D2", +"Z= c #4E72D2", +"`= c #4E74D4", +" - c #4E75D5", +".- c #4E75D4", +"+- c #4F75D3", +"@- c #5075D2", +"#- c #5075D3", +"$- c #5177D7", +"%- c #5178D8", +"&- c #4F75D5", +"*- c #5076D5", +"=- c #4F76D6", +"-- c #5279D9", +";- c #3C52B1", +">- c #3656C3", +",- c #3757C5", +"'- c #3758C6", +")- c #3759C6", +"!- c #375BC6", +"~- c #385CC7", +"{- c #385DC8", +"]- c #365CC6", +"^- c #355BC6", +"/- c #355CC6", +"(- c #365DC7", +"_- c #375EC8", +":- c #375CC6", +"<- c #385EC6", +"[- c #3A5FC7", +"}- c #3C60C8", +"|- c #3D61C9", +"1- c #3E62CA", +"2- c #4063CC", +"3- c #4165CE", +"4- c #4268D0", +"5- c #4269D1", +"6- c #436AD2", +"7- c #446AD2", +"8- c #456BD2", +"9- c #496CD1", +"0- c #4C6CD0", +"a- c #4D6CCF", +"b- c #4E6DD0", +"c- c #4F6ECF", +"d- c #4E6FCF", +"e- c #4C70CF", +"f- c #4A71D0", +"g- c #4F6FCF", +"h- c #4B71D0", +"i- c #4A72D1", +"j- c #4B73D4", +"k- c #4F70D0", +"l- c #4C73D3", +"m- c #4C73D6", +"n- c #4B72D2", +"o- c #4B71D1", +"p- c #4C73D7", +"q- c #3354C0", +"r- c #3152BE", +"s- c #3052BE", +"t- c #3051BF", +"u- c #2E4FBF", +"v- c #2E4FBE", +"w- c #2E50BF", +"x- c #2F50BF", +"y- c #3156C4", +"z- c #2F56C5", +"A- c #2E57C5", +"B- c #2F57C5", +"C- c #3057C6", +"D- c #3258C6", +"E- c #3459C7", +"F- c #365AC7", +"G- c #385BC8", +"H- c #3B5DCA", +"I- c #3B5DCB", +"J- c #3C5ECC", +"K- c #3C60CD", +"L- c #3C62CE", +"M- c #3D65D0", +"N- c #3D66D1", +"O- c #4166D2", +"P- c #4667D2", +"Q- c #4A67D1", +"R- c #4C68D0", +"S- c #4C69CF", +"T- c #4D6BCE", +"U- c #4E6DCD", +"V- c #4E6ECE", +"W- c #4E6DCE", +"X- c #4970D0", +"Y- c #4770D0", +"Z- c #4B6BCE", +"`- c #4A6CCE", +" ; c #496DCF", +".; c #476FD0", +"+; c #4870D0", +"@; c #486DCF", +"#; c #242F79", +"$; c #2F41AC", +"%; c #2040B8", +"&; c #2041B8", +"*; c #2243B3", +"=; c #2243B8", +"-; c #2343B8", +";; c #2444B8", +">; c #2445B8", +",; c #2445B6", +"'; c #2445B7", +"); c #2444B9", +"!; c #2949BE", +"~; c #2649BF", +"{; c #234BBF", +"]; c #224CBF", +"^; c #224AC0", +"/; c #244CC0", +"(; c #254DC0", +"_; c #254DC1", +":; c #264DC2", +"<; c #274EC3", +"[; c #274CC3", +"}; c #274DC4", +"|; c #254DC5", +"1; c #214EC5", +"2; c #204FC6", +"3; c #1F50C8", +"4; c #2151C9", +"5; c #2B53C8", +"6; c #3154C7", +"7; c #3255C6", +"8; c #2F57C7", +"9; c #2C58C9", +"0; c #2D59CA", +"a; c #2D58C9", +"b; c #2E5BCC", +"c; c #325ECC", +"d; c #325ECB", +"e; c #1F40B1", +"f; c #1F40B2", +"g; c #1F40B3", +"h; c #2A44BD", +"i; c #2845BE", +"j; c #2745BE", +"k; c #2646BF", +"l; c #2546BE", +"m; c #2347BF", +"n; c #2147BF", +"o; c #2048C0", +"p; c #1D48C0", +"q; c #1C48C0", +"r; c #1B47C0", +"s; c #1C48BF", +"t; c #1E49BE", +"u; c #214ABD", +"v; c #244CBD", +"w; c #264DBE", +"x; c #254EC0", +"y; c #214FC2", +"z; c #1B51C5", +"A; c #1C51C7", +"B; c #2250C8", +"C; c #2A52C8", +"D; c #3254C6", +"E; c #3355C5", +"F; c #3154C8", +"G; c #3355C6", +"H; c #2F57C8", +"I; c #2E58C9", +"J; c #2E59C9", +"K; c #3059C9", +"L; c #2040B6", +"M; c #2743BB", +"N; c #2844BC", +"O; c #2743BD", +"P; c #2844BE", +"Q; c #2844BD", +"R; c #2346BE", +"S; c #2047BF", +"T; c #1E48C0", +"U; c #1D47C0", +"V; c #1D49BF", +"W; c #1F49BF", +"X; c #204ABE", +"Y; c #254DBF", +"Z; c #234EC0", +"`; c #2050C1", +" > c #1C51C3", +".> c #1F51C6", +"+> c #2651C8", +"@> c #2D53C7", +"#> c #3155C6", +"$> c #3155C7", +"%> c #3355C7", +"&> c #3254C7", +"*> c #1E40B1", +"=> c #2141B8", +"-> c #2442B9", +";> c #2744BB", +">> c #2945BB", +",> c #2A45BB", +"'> c #2944BA", +")> c #2745BB", +"!> c #2545BC", +"~> c #2246BD", +"{> c #2047BE", +"]> c #1F47BD", +"^> c #1D48BE", +"/> c #1E49C0", +"(> c #1F4AC0", +"_> c #214BBF", +":> c #244CBE", +"<> c #254DBE", +"[> c #244DBE", +"}> c #224FBF", +"|> c #2051C1", +"1> c #2151C3", +"2> c #2252C5", +"3> c #2151C1", +"4> c #2851C6", +"5> c #2A50C6", +"6> c #2E54C6", +"7> c #1F51C2", +"8> c #1D52C5", +"9> c #2651C9", +"0> c #2950C7", +"a> c #2D40A5", +"b> c #2040B0", +"c> c #1F40B0", +"d> c #223CAE", +"e> c #233CAE", +"f> c #253BAC", +"g> c #253BAD", +"h> c #233CB0", +"i> c #213EB2", +"j> c #1F3FB4", +"k> c #1E40B6", +"l> c #1F3FB7", +"m> c #1E3EB8", +"n> c #1F3FB8", +"o> c #2040B7", +"p> c #2141B6", +"q> c #2140B7", +"r> c #2241B6", +"s> c #2342B5", +"t> c #2442B6", +"u> c #2543B5", +"v> c #2643B4", +"w> c #2544B6", +"x> c #2346B8", +"y> c #2247B9", +"z> c #2048BC", +"A> c #1F48BF", +"B> c #2049C0", +"C> c #214AC0", +"D> c #224BBF", +"E> c #234CBE", +"F> c #244DBF", +"G> c #234CBF", +"H> c #264DC0", +"I> c #274EBF", +"J> c #264DBF", +"K> c #254EBF", +"L> c #2050C0", +"M> c #1F51C1", +"N> c #1E42A4", +"O> c #263BA6", +"P> c #253BA7", +"Q> c #253CA7", +"R> c #1E41A5", +"S> c #1F40AF", +"T> c #273AAC", +"U> c #1E40B0", +"V> c #1F40B5", +"W> c #1F40B6", +"X> c #1F40B8", +"Y> c #1E40B8", +"Z> c #1F3EB8", +"`> c #203FB7", +" , c #2240B6", +"., c #2341B7", +"+, c #2345B9", +"@, c #2147BB", +"#, c #2148BA", +"$, c #2049BB", +"%, c #2049BD", +"&, c #2049BF", +"*, c #224BBE", +"=, c #244DBD", +"-, c #244CBF", +";, c #182969", +">, c #273BAD", +",, c #2739AB", +"', c #263AAC", +"), c #243CAE", +"!, c #233DAE", +"~, c #213EAF", +"{, c #1F3FB0", +"], c #2040B4", +"^, c #1F3FB6", +"/, c #1E3EB7", +"(, c #2240B7", +"_, c #2341B6", +":, c #2543B4", +"<, c #2644B3", +"[, c #2544B5", +"}, c #2545B5", +"|, c #2547B6", +"1, c #2548B7", +"2, c #2349BA", +"3, c #1F49BE", +"4, c #2149BD", +"5, c #2049BE", +"6, c #214BBE", +"7, c #2249BE", +"8, c #234CBD", +"9, c #2149BE", +"0, c #1E49BF", +"a, c #253BA9", +"b, c #253BAB", +"c, c #263AAB", +"d, c #213DAF", +"e, c #203EAF", +"f, c #1D40AF", +"g, c #1D40B0", +"h, c #1E40B4", +"i, c #2241B7", +"j, c #2643B6", +"k, c #2744B5", +"l, c #2643B5", +"m, c #2346B6", +"n, c #2147B7", +"o, c #2644B6", +"p, c #2247B7", +"q, c #2248B8", +"r, c #2647B7", +"s, c #2549B7", +"t, c #2645B7", +"u, c #2148B8", +"v, c #2847B6", +"w, c #2549B6", +"x, c #2849B6", +"y, c #2049B7", +"z, c #2A49B5", +"A, c #243BA4", +"B, c #253BA5", +"C, c #253BA6", +"D, c #263AA7", +"E, c #263AA8", +"F, c #2739AA", +"G, c #243CAD", +"H, c #223DAE", +"I, c #1F3EAF", +"J, c #1E3FB0", +"K, c #1D40B1", +"L, c #1E3FB1", +"M, c #1F3FB3", +"N, c #1F3FB5", +"O, c #2140B6", +"P, c #2140B8", +"Q, c #2744B4", +"R, c #2746B6", +"S, c #2947B6", +"T, c #2946B5", +"U, c #2A48B6", +"V, c #3551A8", +"W, c #1F399C", +"X, c #143D9F", +"Y, c #263BA5", +"Z, c #273BA8", +"`, c #273BAA", +" ' c #263AAD", +".' c #233CAD", +"+' c #213DAE", +"@' c #203FB2", +"#' c #2342B6", +"$' c #2443B6", +"%' c #2543B6", +"&' c #2644B5", +"*' c #133D9E", +"=' c #263BA7", +"-' c #263BA9", +";' c #273BA9", +">' c #263AAA", +",' c #2539AB", +"'' c #2639AB", +")' c #253AAC", +"!' c #243BAD", +"~' c #223DAF", +"{' c #203FB0", +"]' c #2040B1", +"^' c #2140B3", +"/' c #2543B1", +"(' c #2744AF", +"_' c #1A3CA0", +":' c #1D3BA2", +"<' c #233BA4", +"[' c #263AA5", +"}' c #253AA5", +"|' c #263AA6", +"1' c #263BA4", +"2' c #243BA5", +"3' c #263BA8", +"4' c #223EAF", +"5' c #3B4CA5", +"6' c #1D379A", +"7' c #1E389C", +"8' c #1E399F", +"9' c #1F3BA2", +"0' c #1F3BA3", +"a' c #213BA4", +"b' c #233AA3", +"c' c #243AA3", +"d' c #2539A4", +"e' c #253AA6", +"f' c #243BA7", +"g' c #253CAA", +"h' c #253CAC", +"i' c #253CAD", +"j' c #253CAE", +"k' c #243DAE", +"l' c #213FAF", +"m' c #223FAF", +"n' c #2040AF", +"o' c #253D93", +"p' c #1D3894", +"q' c #1F379A", +"r' c #1E389B", +"s' c #1D399C", +"t' c #1C3A9D", +"u' c #1B3A9D", +"v' c #183B9E", +"w' c #163C9E", +"x' c #153C9E", +"y' c #163B9D", +"z' c #173B9D", +"A' c #193A9D", +"B' c #1C3A9E", +"C' c #1F3AA1", +"D' c #223AA4", +"E' c #253BA8", +"F' c #273BA7", +"G' c #263CAB", +"H' c #263CAC", +"I' c #243EAE", +"J' c #273BAC", +"K' c #2A3795", +"L' c #1F389B", +"M' c #1D389B", +"N' c #1C399C", +"O' c #1B399C", +"P' c #1A3A9D", +"Q' c #1D399B", +"R' c #1B399B", +"S' c #1A3A9C", +"T' c #1B3A9F", +"U' c #1D3AA0", +"V' c #203BA2", +"W' c #203BA3", +"X' c #2639A6", +"Y' c #1B3692", +"Z' c #1C3794", +"`' c #1D3796", +" ) c #1E3898", +".) c #1E389A", +"+) c #1F399B", +"@) c #1A399C", +"#) c #193A9E", +"$) c #1A3BA0", +"%) c #1C3BA2", +"&) c #1D3CA3", +"*) c #203CA4", +"=) c #223BA5", +"-) c #3C4699", +";) c #2B4595", +">) c #1C3793", +",) c #1D3895", +"') c #1E3897", +")) c #1F3998", +"!) c #1F3999", +"~) c #1F399A", +"{) c #1E399C", +"]) c #1C3B9E", +"^) c #1D3BA0", +"/) c #1E3CA2", +"() c #223CA5", +"_) c #243CA6", +":) c #596FA9", +"<) c #3B4894", +"[) c #314993", +"}) c #29499F", +"|) c #28489E", +"1) c #2B4BA1", +"2) c #2C4BA1", +"3) c #2D4CA2", +"4) c #2E4CA3", +"5) c #2F4CA4", +"6) c #2E4CA4", +"7) c #2F4DA3", +"8) c #2F4DA4", +"9) c #D3D5D2", +"0) c #3B4794", +"a) c #314791", +"b) c #304892", +"c) c #304893", +"d) c #2F4995", +"e) c #2F4997", +"f) c #2D4A9A", +"g) c #2A4A9D", +"h) c #294A9F", +"i) c #284AA0", +"j) c #294AA0", +"k) c #2B4AA1", +"l) c #2D4CA3", +"m) c #C9CAC9", +"n) c #455D9B", +"o) c #242F78", +"p) c #1B2F85", +"q) c #C6C3C8", +"r) c #B5B2B6", +"s) c #B5B7B4", +"t) c #B5B7B3", +"u) c #B5B2B5", +"v) c #B5B3B4", +"w) c #B5B5B4", +"x) c #B5B6B3", +"y) c #B5B4B4", +"z) c #B5B3B5", +"A) c #B5B4B5", +"B) c #B5B5B5", +"C) c #B5B5B3", +"D) c #B5B5B6", +"E) c #BAC3BE", +"F) c #B9C3BD", +"G) c #C1C3C4", +"H) c #BFC3C2", +"I) c #B9C3BE", +"J) c #BBC3BF", +"K) c #BDC3C1", +"L) c #C0C3C3", +"M) c #BEC3C1", +"N) c #C2C3C5", +"O) c #E6E3E8", +"P) c #E0E2DF", +"Q) c #E1E1E1", +"R) c #E2E1E3", +"S) c #E4E1E6", +"T) c #E4E2E7", +"U) c #E4E2E6", +"V) c #E3E3E4", +"W) c #E2E3E3", +"X) c #E1E3E2", +"Y) c #E3E3E3", +"Z) c #E3E3E2", +"`) c #EBEDEA", +" ! c #EAECE9", +".! c #E9EBE8", +"+! c #ECEEEB", +". . + @ # $ $ $ $ $ $ $ % $ $ $ $ $ % $ $ $ $ $ $ % $ $ $ $ $ % $ $ $ $ $ $ $ $ $ % $ $ & * = - ; > , , ' ) ! ! ~ { ] ^ / ( _ : < [ } | | 1 2 3 3 4 4 4 4 4 4 4 5 6 4 4 4 5 6 7 8 9 4 5 6 7 8 9 6 7 8 9 ", +"0 a b % $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ c d d d d $ $ $ $ $ c d e f g h i i i i j k l m n o p q r s t u v w x y z 4 A B C D 9 9 E 9 E F G H I F J K L L L L J K L L L L L L L L ", +"@ % M N O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O P Q R S T U V W X Y Z ` ...+.@.#.$.%.&.*.=.-.;.>.,.'.).!.~.{.].^./.(._.:.<.[.}.|.1.2.3.4.5.6.7.8.9.0.a.b.b.b.b.b.b.", +"c.$ d.O e.f.g.g.g.h.g.g.g.g.g.h.h.g.g.g.g.g.h.h.g.g.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.`. +.+++@+#+$+@+$+%+&+*+=+$+-+;+>+,+'+)+!+;+>+,+~+,+>+,+~+,+", +"$ {+N N f.f.f.f.h.h.h.g.f.f.h.h.h.h.g.f.f.h.h.h.h.]+^+/+(+h._+:+<+[+}+|+1+2+3+4+5+6+7+8+9+0+a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z+A+B+.+C+D+E+D+F+G+H+C+I+F+G+J+K+L+H+F+G+J+K+L+H+J+H+J+H+", +"{+{+N N M+M+h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.N+N+h.h.(+O+P+P+Q+R+S+T+U+V+W+X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@-@;@>@,@'@)@!@~@{@]@^@/@(@_@:@<@[@[@y+}@|@1@A+1@2@3@ +2@4@2@5@C+D+6@D+7@5@C+D+6@I+C+D+6@I+", +"{+{+8@N M+M+h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.9@9@0@N+a@b@c@d@e@f@g@h@i@j@k@l@m@n@o@p@q@r@s@t@u@v@w@x@y@z@A@B@q+r+C@D@E@F@G@H@_@I@J@K@<@L@M@N@O@P@Q@R@S@T@A+A+U@V@W@W@A+2@U@V@W@W@U@V@W@W@", +"{+{+8@N f.M+h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.(+(+(+9@9@X@Y@Z@e@`@ #.#+#@###$#%#&#*#=#-#;#>#,#'#)#!#~#{#]#z@^#/#(#p+_#r+:#s+t+<#[#}#|#|#1#_@|#_@_@2#L@3#4#y+y+5#z+z+z+5#z+z+z+z+A+A+A+A+A+", +"{+{+8@8@f.f.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.(+6#7#8#9#0#a#b#c#d#e#f#g#h#i#j#k#l#m#n#o#&@p#q#r#s#t#u#v#w#x#x#y#y#z#A#B#C#D#E#E#F#G#H#F#H#H#u+v+I#J#K#L#J@J@M#N#O#P#M#M#M#N#M#Q#Q#R#", +"$ {+8@e.f.f.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.h.S#l.7#T#U#V#W#X#Y#Z#`# $f#g###.$+$@$#$$$$@%$&$*$=$-$;$>$,$'$)$!$~$~${$]$^$/$($($_$_$:$<$_$<$[$}$|$|$1$2$2$3$}#4$5$6$7$8$8$9$8$8$8$0$8$", +"$ {+a$e.f.f.h.h.h.h.h.h.h.h.h.b$h.c$c$c$c$c$d$c$c$c$c$c$c$c$c$c$c$e$e$7#f$g$h$i$X#j$k$l$m$n$o$p$q$r$l@s$t$u$v$w$x$y$z$A$B$C$D$E$F$G$G$H$I$J$J$K$K$J$L$L$L$L$L$M$N$O$P$Q$R$S$T$U$1$V$T$W$X$Y$1$V$Y$Z$`$ %", +"$ $ a$a$f.f.b$b$b$b$b$b$b$b$b$b$b$b$b$b$b$b$b$b$.%b$b$b$.%d$+%+%@%h.e$l.#%$%h$%%&%*%=%-%;%>%,%'%)%!% @ @~%{%]%^%/%(%w$_%:%<%[%}%|%D$1%2%3%4%5%4%4%6%5%5%4%4%4%5%7%5%8%9%L$0%a%a%a%P$b%P$P$z#z#z#P$c%c%c%", +"$ $ 8@e.f.f.d%b$b$b$b$b$d%b$b$b$b$b$b$e%f%b$b$b$b$b$g%h%b$.%i%i%j%k%l%m%X@n%h$o%&%p%q%`#r%s%t%u%v%w%x%y% @z%A%B%C%D%E%F%G%:%H%I%[%J%}%K%|%D$K%D$D$L%M%M%M%M%M%D$N%O%i+P%j+Q%R%S%T%0%U%V%W%W%W%W%X%X%X%X%", +"$ $ 8@8@f.f.d%d%b$b$b$b$d%d%b$b$b$h%Y%Z%Z%h%f%f%h%Y%`%`% &h%h%.&+&@&#&$&X@%&&&*&=&-&j$Z#+#;&>&,&'&)&)&!&~&{&]&^&/&(&^%_&(%:&<&[&}&|&1&A$A$2&3&4&4&5&B$6&7&B$7&8&9&6&7&0&a&a&i+i+i+b&a&a&j+U%c&U%j+U%c&U%", +"$ $ 8@8@d&e&d%d%d%d%d%d%d%d%d%d%d%`%d%d%d%d%`%`%`%d%d%d%d%`%`%f&g&h&j%i&j&k&l&m&=&X#Y#n&o&p&q&r&>&s&t&t&u&v&w&x&y&{&z&A&B&C&D&(%(%F%F%E&F&}&}&|&G&|&H&1&I%I&A$1&}&z$z$J&K&L&M&N&O&0&P&Q&0&a&R&a&a&a&R&a&", +"{+$ 8@8@e&e&d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%`%f&S&T&U&V&W&Y@X&Y&Z&`& *.*+*@*#*@*r&$*#*r&%*&***=*-*;*y&z%A%z&A&A&>*B&,*,*'*)*!*!*~*{*F&}&{*}&{*]*G%G%y$^*/*J&(*2&_*:*<*=$[*}*<*=$<*|*1*", +"{+{+8@2*e&e&d%d%d%d%d%d%d%d%d%e&3*4*4*4*4*4*5*4*4*4*4*4*4*4*4*4*`%f&6*6*7*8*9*0*a*b*c*d*e*f*g*h*i*j*+*k*h*l*m*n*m*o*p*q*r*s*t*u*v*w*x*y*y*z*A*B*C*D*E*F*G*E*G*F*H*G*F*~*]*{*I*x$J*K*L*G%K*M*o#o#I&N*O*O*", +"{+{+8@2*e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&P*e&e&e&e&e&e&P*P*e&e&e&P*P*5*Q*R*S*T*U*V*W*X*Y*Z*`*d* =.=+=@=#=$=%=g@&=*===-=i*;=l*>=,=q*'=s*)=k@!=x*~={=]=^=/=(=_=:=(=<=<=]=[=}=|=]=]=1=2=3=|=4=5=2=2=2=3=6=6=6=", +"{+{+7=e.e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&e&P*P*8=9=0=a=b=U*c=d=e=f=e@#=g=h=i=i=j=k=k=l=%===m=n=o=p=q=,=r=s=t=u=v=v*w=x=x=y=z=z=A=z=A=B=C=B=D=C=B=x=B=B=B=B=B=B=B=B=B=B=B=B=B=B=", +"{+{+7=7=e&e&e&e&E=E=e&e&e&e&E=E=E=e&e&e&e&E=E=E=e&e&e&e&E=E=e&e&e&e&E=E=E=F=d%G=G=H=I=J=K=L=M=R+}+N=O=P=Q=j=i=h=R=e@@=S=-=T=h@l*U=V=W=X=Y=Z=`= - - -.-+-@- -#-$-%-$-&-*-$-=-%-----C=$-%---------B=B=B=B=", +"{+{+7=7=;-;-E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=E=>-,-'-)-!-6*~-{-{-]-^-/-/-(-_-:-N=<-[-}-|-1-2-3- =4-5-6-7-8-9-0-0-a-b-c-d-e-f-g-h-h-i-j-k-h-h-i-j-l-m-n-o-i-j-l-m-n-j-l-p-n-", +"{+{+7=7=;-;-E=E=E=E=E=E=E=E=q-r-s-t-t-u-u-v-v-v-u-w-x-u-u-u-u-u-u-u-u-v-v-u-u-u-u-u-v-v-u-u-u-u-v-v-u-y-z-A-B-C-D-E-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-U-V-W-V-e-X-Y-Z-`- ;.;Y-N N +;@;.;Y-N N N N N N N ", +"#;#;d&d&$;$;%;%;%;%;%;%;%;%;&;*;=;-;-;-;;;>;,;>;>;>;;;>;>;>;>;>;>;>;>;>;';);>;>;>;>;>;';>;>;>;>;>;';);!;~;{;];^;/;(;_;_;:;<;[;};};|;1;2;3;4;5;6;7;8;9;9;0;a;0;0;b;h.a;0;0;b;h.c;h.d;0;b;h.c;h.d;h.c;h.d;", +"#;#;;-;-$;$;e;e;e;e;e;e;e;e;e;e;e;f;f;f;f;e;e;e;f;f;f;f;f;f;f;f;f;f;f;f;g;%;f;f;f;f;f;g;f;f;f;f;f;g;%;h;i;j;k;l;m;n;o;p;q;r;r;s;t;u;v;w;x;y;z;A;B;C;6;D;E;F;G;G;H;I;F;G;G;H;I;J;J;K;G;H;I;J;J;K;I;J;J;K;", +"#;#;;-;-$;$;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;L;e;e;e;e;e;e;e;e;e;e;e;e;L;M;N;O;P;Q;i;i;k;R;S;T;U;q;q;V;W;X;{;Y;Z;`; >.>+>@>#>+>$>6;#>#>+>%>&>G;G;G;G;G;&>G;G;G;G;G;G;G;G;G;", +"#;#;d.;-$;$;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;*>e;e;e;e;e;e;e;e;e;e;e;e;*>=>->;>>>,>'>'>)>!>~>{>]>^>^>V;V;/>(>_>:><>[>}>|>1>2>3>2>4>5>6>7>8>9>0>G;G;G;G;9>0>G;G;G;G;G;G;G;G;", +"#;#;d.d.a>a>e;e;e;e;e;e;e;e;e;e;b>b>c>c>c>c>c>b>e;e;e;e;e;e;e;e;e;e;e;e;e;e;d>e>f>g>h>i>j>k>l>l>m>m>n>n>o>o>p>q>r>r>s>t>u>v>v>u>w>';x>y>z>t;A>B>C>D>E>E>F>G>F>H>H>I>F>Y;J>w;K>L>K>M>J>w;K>L>K>M>K>L>K>M>", +"#;#;d.d.a>a>N>e;N>O>O>O>N>e;N>O>O>P>Q>R>S>R>Q>O>O>O>N>e;N>O>O>O>N>e;N>N>O>T>e;e;e;U>U>U>U>f;V>W>o>o>o>o>X>X>Y>Y>n>n>Z>Z>`> ,.,t>t>u>u>w>+,@,#,$,%,A>&,*,=,B>[>-,w;<>C>[>-,w;w;w;w;w;-,w;w;w;w;w;w;w;w;w;", +"#;;,;-;-a>a>N>N>N>O>O>O>N>N>N>O>O>O>O>N>N>N>O>O>O>O>N>N>N>O>O>O>N>N>N>N>O>>,,,,,,,',g>),!,~,{,{,*>U>e;f;],o>%;o>^,^,/,/,l>q>(,_,t>u>:,<,v>[,},|,1,2,%,%,3,4,5,6,7,8,9,5,6,0,G>G>Y;G>6,0,G>G>Y;G>G>G>Y;G>", +";,;,;-;-O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>P>a,b,',',c,c,f>),e>d,e,{,{,U>U>f,f,U>U>g,g,*>g;h,^,^,`>`>q>i,t>j,k,k,l,w>m,n,o,p,q,r,s,t,p,u,v,w,x,y,z,u,v,w,x,y,z,w,x,y,z,", +";,;,b b O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>A,A,A,B,C,D,E,F,c,',g>G,!,H,~,e,{,I,J,J,K,K,U>f,f,J,L,M,N,L;O,i,P,.,l,Q,k,k,k,k,k,k,R,v,k,k,k,R,v,S,T,U,k,R,v,S,T,U,v,S,T,U,", +";,;,b V,W,W,X,X,O>X,X,X,X,X,O>X,X,X,X,X,X,O>X,X,X,X,X,X,O>X,X,X,X,X,O>X,X,O>O>O>O>B,B,B,B,Y,O>O>Z,`,T>T> '',g>.'+'e,{,{,e,+'+'e,e,{,J,K,e;@'N,O,#'$'%'%'j,%'j,&'k,k,%'j,&'k,k,k,k,k,&'k,k,k,k,k,k,k,k,k,", +";,;,b V,W,W,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,*'O>O>O>O>O>O>O>O>B,B,A,A,B,C,='-'`,;'>'>',''')'!'!'e>e>~'~'~,~,{'{,*>*>e;]']']']']']'^'/']']']'^'/':,(':,]'^'/':,(':,/':,(':,", +";,;,V,V,W,W,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,_':'<'['}'|'|'O>O>O>O>O>O>O>Y,Y,1'1'B,B,2'2'C,3'-'>'c,)')'!'),4'{'e;]'e;*>*>e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;e;", +";,;,5'5'W,W,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,X,W,6'6'6'7'8'9'0'a'b'c'd'd'}'}'O>O>O>O>O>O>O>O>Y,1'1'['['e'e'f'g'h'i'j'k'G,),!,l'j'm'n'b>b>),m'b>e;e;e;e;e;b>e;e;e;e;e;e;e;e;e;", +";,;,b b o'o'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'W,q'q'q'r's't'u'v'w'x'y'z'A'B'C'D'2'2'B,B,O>O>O>O>O>O>O>O>O>O>O>Y,Y,C,C,='='='E'F'3'3'3'G'Z,='F'F'G'H'I'J'F'F'G'H'I'J'G'H'I'J'", +";,;,b b K'K'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'W,W,W,W,W,L'L'q'r'M'N'O'P'u'N's'Q'R'S'A'T'U'C'V'9'0'W'D'}'X'|'O>O>B,B,O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>", +";,;,b b K'K'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'p'Y'Y'Y'Z'`' ).)+)+)+)W,W,W,W,L'L'q'q'r'r's'M'N'P'@)A'#)$)%)&)*)=)B,|'|'O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>O>", +"{+;,$ -);)K'p'p'o'p'p'p'p'p'o'p'p'p'p'p'p'o'p'p'p'p'p'p'o'p'p'p'p'p'o'o'p'p'p'p'p'p'p'p'p'p'>)>)Y'Y'>)Z',)')))!)~)+)W,W,W,W,W,W,W,W,W,W,W,L'L'{)s't'])^)/)])/)/)O>()])/)/)O>()O>_)O>/)O>()O>_)O>()O>_)O>", +":);,;,;)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)[)M M M M M M M M M M M M M M M M M M })})|)|)})M M 1)2)3)4)5)6)6)6)7)7)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)", +"9)#;;,;,$ -)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)<)0)a)a)a)b)c)d)e)f)g)h)i)i)j)j)M M M M M M M M M M M })})})})M k)k)M M k)l)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)8)", +"+ 9)m)n)$ #;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;#;o)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)p)", +"+ + 9)a m)q)r)s)r)s)r)s)r)s)r)r)s)r)s)r)s)r)r)s)r)s)r)s)r)s)r)s)r)s)r)s)r)t)u)v)w)x)x)w)y)z)A)A)B)B)B)B)w)w)C)C)w)w)B)B)B)B)B)w)w)w)w)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)D)B)B)B)B)B)D)B)B)B)D)B)", +". + + 9)9)9)q)E)q)E)q)E)q)E)q)q)E)q)E)q)E)q)q)E)q)E)q)E)q)E)q)E)q)E)q)E)q)F)G)H)E)I)J)K)H)L)L)L)L)L)L)L)H)H)M)M)H)H)L)L)G)L)L)H)H)H)H)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)L)N)L)L)L)L)L)N)L)L)L)N)L)", +". . 0 . + O)P)O)P)O)P)O)P)O)P)P)O)P)O)P)O)P)P)O)P)O)P)O)P)O)P)O)P)O)P)O)P)O)Q)R)S)T)U)V)W)X)W)W)V)V)V)V)V)V)V)V)Y)Y)Z)Z)Y)Z)Z)Y)Y)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)V)Y)V)V)V)V)V)Y)V)V)V)Y)V)", +". . . 0 0 0 . 0 0 0 + 0 + 0 + 0 + 0 + 0 + 0 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 0 `) !+ + + .! !`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)`)+!`)`)`)`)`)+!`)`)`)+!`)"}; + + +static char * listviewhighcornerright_xpm[] = { +"100 46 780 2", +" c None", +". c #6A779D", +"+ c #6C789C", +"@ c #6C789D", +"# c #6B789D", +"$ c #6A779E", +"% c #66759E", +"& c #64749E", +"* c #63749E", +"= c #61739D", +"- c #576D9B", +"; c #556C9C", +"> c #4D679D", +", c #4A649D", +"' c #49629D", +") c #465E9C", +"! c #40579C", +"~ c #3B5394", +"{ c #2C4E97", +"] c #314993", +"^ c #2B4595", +"/ c #1B4296", +"( c #253D93", +"_ c #19418F", +": c #0F3C96", +"< c #42599E", +"[ c #758DC3", +"} c #E8EAE7", +"| c #EEF0ED", +"1 c #FBFBFC", +"2 c #6F7D9B", +"3 c #6F7D9A", +"4 c #6E7B9C", +"5 c #67759E", +"6 c #63739E", +"7 c #62739D", +"8 c #596F9C", +"9 c #4A639D", +"0 c #47609C", +"a c #445B9F", +"b c #3E5697", +"c c #2E509A", +"d c #2D509A", +"e c #2D4F99", +"f c #2D4F98", +"g c #28418A", +"h c #3E51A3", +"i c #D0D3DC", +"j c #A1B6EF", +"k c #A2B6F0", +"l c #A1B6F0", +"m c #A3B6F0", +"n c #A0B6EF", +"o c #9DB6EE", +"p c #9CB5EF", +"q c #9CB2F0", +"r c #9FB5EE", +"s c #9CB4EB", +"t c #9AB3EC", +"u c #9AB0EC", +"v c #9DB3EB", +"w c #9BB4EC", +"x c #9BB4EE", +"y c #9BB1EF", +"z c #9BB0F0", +"A c #90ACF0", +"B c #93ABEE", +"C c #91A8EB", +"D c #8BA3E8", +"E c #88A1E7", +"F c #809DE9", +"G c #7A99E8", +"H c #7491E5", +"I c #698AE4", +"J c #6184E3", +"K c #507EDC", +"L c #4E7CDB", +"M c #4F7DDC", +"N c #5479DA", +"O c #567BDC", +"P c #577CDD", +"Q c #5074DA", +"R c #5174DB", +"S c #5175DC", +"T c #5276DD", +"U c #4D71DE", +"V c #4C72D8", +"W c #3A6CE0", +"X c #2B49A6", +"Y c #E0E2DF", +"Z c #93AAE9", +"` c #94A9E8", +" . c #94AAE9", +".. c #93A9E9", +"+. c #92AAE9", +"@. c #8DA9E8", +"#. c #8CA7E9", +"$. c #92ABE9", +"%. c #8EAAE9", +"&. c #8EA9E9", +"*. c #8FAAE9", +"=. c #8CA8E9", +"-. c #8CA2E7", +";. c #86A1E6", +">. c #839EE9", +",. c #7F9CE9", +"'. c #7A97E8", +"). c #7693E7", +"!. c #6E8EE8", +"~. c #678AE9", +"{. c #5D84E3", +"]. c #577CDF", +"^. c #4E77DF", +"/. c #4A70DB", +"(. c #4870DB", +"_. c #4870DC", +":. c #4770E3", +"<. c #496FDC", +"[. c #486EDB", +"}. c #466FE4", +"|. c #466EE3", +"1. c #4167D9", +"2. c #4066D8", +"3. c #3F66D8", +"4. c #3D64D7", +"5. c #3960DA", +"6. c #476DD9", +"7. c #446EE5", +"8. c #305EC8", +"9. c #8EAAE8", +"0. c #8FAAE8", +"a. c #91AAE9", +"b. c #8FA9E8", +"c. c #8BA8E8", +"d. c #8AA7E9", +"e. c #8BA5EA", +"f. c #8AA7E8", +"g. c #87A2E6", +"h. c #859FE8", +"i. c #7F9DE8", +"j. c #7C9AE8", +"k. c #7B95E7", +"l. c #7090E8", +"m. c #6B8BE9", +"n. c #6386E6", +"o. c #5881E1", +"p. c #5479DE", +"q. c #4D74DE", +"r. c #476EDB", +"s. c #446EE1", +"t. c #446EE0", +"u. c #446EDF", +"v. c #446DE0", +"w. c #426ADF", +"x. c #3C64DA", +"y. c #4360CC", +"z. c #D3D5D2", +"A. c #E6E3E8", +"B. c #8DA2E7", +"C. c #8CA6EA", +"D. c #8DA3E9", +"E. c #88A2E7", +"F. c #87A1E7", +"G. c #8AA1E7", +"H. c #849EE9", +"I. c #7D9AE9", +"J. c #7B98E8", +"K. c #7796E5", +"L. c #7191E7", +"M. c #688CE9", +"N. c #6687E5", +"O. c #5C83E1", +"P. c #557BDE", +"Q. c #4F76DE", +"R. c #4C72DE", +"S. c #456EDF", +"T. c #426AD9", +"U. c #4269D9", +"V. c #4269D8", +"W. c #3D64D9", +"X. c #3A61DA", +"Y. c #345ED6", +"Z. c #335ECF", +"`. c #C6C3C8", +" + c #86A1E7", +".+ c #87A2E7", +"++ c #87A0E7", +"@+ c #859EE8", +"#+ c #849DE9", +"$+ c #7E9BE9", +"%+ c #7A99E9", +"&+ c #7A95E5", +"*+ c #7593E7", +"=+ c #6F8EE9", +"-+ c #668AE5", +";+ c #6386E0", +">+ c #5B82DF", +",+ c #5379DE", +"'+ c #5075DE", +")+ c #4B6FDC", +"!+ c #446AD7", +"~+ c #4269D6", +"{+ c #4269D5", +"]+ c #3E65D7", +"^+ c #C9CAC9", +"/+ c #869EE9", +"(+ c #859FE9", +"_+ c #849FE9", +":+ c #829DE8", +"<+ c #819DE8", +"[+ c #7B9AE9", +"}+ c #7A96E6", +"|+ c #7290E8", +"1+ c #698CE6", +"2+ c #6689E0", +"3+ c #5D84E0", +"4+ c #587FDF", +"5+ c #5377DD", +"6+ c #4B74DE", +"7+ c #496BD8", +"8+ c #7C9BE9", +"9+ c #7E9CE9", +"0+ c #7D9AEA", +"a+ c #7D9BEA", +"b+ c #7D98E8", +"c+ c #7C98E8", +"d+ c #7796E4", +"e+ c #7592E6", +"f+ c #7390E1", +"g+ c #698DE0", +"h+ c #6588DE", +"i+ c #5E84E0", +"j+ c #5880DF", +"k+ c #5479DC", +"l+ c #4F75DE", +"m+ c #4A6FDB", +"n+ c #436AD7", +"o+ c #3F65D7", +"p+ c #BAC3BE", +"q+ c #7B9AE8", +"r+ c #7B9AEA", +"s+ c #7A9AEA", +"t+ c #7B99E9", +"u+ c #7D97E7", +"v+ c #7D95E6", +"w+ c #7D95E5", +"x+ c #7C95E6", +"y+ c #7493E3", +"z+ c #7290DF", +"A+ c #6C8DDE", +"B+ c #6B89E1", +"C+ c #6486DF", +"D+ c #5D81DF", +"E+ c #567DDE", +"F+ c #4F73DE", +"G+ c #496EDA", +"H+ c #355ED6", +"I+ c #345ED5", +"J+ c #7E95E5", +"K+ c #7C97E8", +"L+ c #7C97E7", +"M+ c #7B94E6", +"N+ c #7A95E4", +"O+ c #7695E5", +"P+ c #7694E4", +"Q+ c #7994E6", +"R+ c #7995E4", +"S+ c #7594E4", +"T+ c #7391E2", +"U+ c #6E8EDE", +"V+ c #6B8ADE", +"W+ c #6688DF", +"X+ c #5F84E0", +"Y+ c #5980E0", +"Z+ c #4D72DD", +"`+ c #456BD7", +" @ c #4168D6", +".@ c #3C64D7", +"+@ c #335ED0", +"@@ c #4659C7", +"#@ c #7292E1", +"$@ c #7392E1", +"%@ c #7492E1", +"&@ c #718FDF", +"*@ c #6F8EDE", +"=@ c #6D8BDE", +"-@ c #6B88DF", +";@ c #597FDF", +">@ c #557ADD", +",@ c #5176DC", +"'@ c #4D74DD", +")@ c #496DDA", +"!@ c #3860D8", +"~@ c #7391E0", +"{@ c #7290DE", +"]@ c #6D8EDD", +"^@ c #6D8DDD", +"/@ c #7190E0", +"(@ c #6C8DDD", +"_@ c #6B89DF", +":@ c #6487E0", +"<@ c #6085DF", +"[@ c #5F81DE", +"}@ c #567EDE", +"|@ c #4F74D9", +"1@ c #466BD7", +"2@ c #4067D5", +"3@ c #3C63D7", +"4@ c #335ED3", +"5@ c #335ED1", +"6@ c #718EDD", +"7@ c #728EDD", +"8@ c #748EDD", +"9@ c #708EDD", +"0@ c #6F8DDD", +"a@ c #6E8DDD", +"b@ c #6C8ADE", +"c@ c #6C89DF", +"d@ c #6988DF", +"e@ c #6387DF", +"f@ c #6282DE", +"g@ c #5681E0", +"h@ c #577BDD", +"i@ c #5277DB", +"j@ c #4D73D8", +"k@ c #4A70D8", +"l@ c #436AD5", +"m@ c #3F66D6", +"n@ c #3C63D8", +"o@ c #3960D8", +"p@ c #3860D7", +"q@ c #335ED2", +"r@ c #345ED4", +"s@ c #6C88DF", +"t@ c #6D88DF", +"u@ c #6B89DE", +"v@ c #6888DF", +"w@ c #6587E0", +"x@ c #6989DF", +"y@ c #6687E0", +"z@ c #6287E0", +"A@ c #6281DD", +"B@ c #5881E0", +"C@ c #557ADB", +"D@ c #5176D9", +"E@ c #4E75D7", +"F@ c #4A6FD8", +"G@ c #476BD6", +"H@ c #4067D6", +"I@ c #3C62D7", +"J@ c #3C60D4", +"K@ c #365ED1", +"L@ c #345ED3", +"M@ c #6786DF", +"N@ c #5F85E0", +"O@ c #5F86E0", +"P@ c #6186DF", +"Q@ c #6286E0", +"R@ c #6284DF", +"S@ c #6384DF", +"T@ c #5B7FDE", +"U@ c #577DDC", +"V@ c #557BDA", +"W@ c #5278D8", +"X@ c #4E76D6", +"Y@ c #4C72D7", +"Z@ c #486DD8", +"`@ c #4469D6", +" # c #3F62D2", +".# c #3C60CF", +"+# c #345ECF", +"@# c #6086DF", +"## c #6085E0", +"$# c #6285DF", +"%# c #6383DD", +"&# c #6481DC", +"*# c #6380DD", +"=# c #6183DE", +"-# c #6083DD", +";# c #6081DC", +"># c #6080DD", +",# c #6083DE", +"'# c #6181DC", +")# c #6280DD", +"!# c #577EDB", +"~# c #557CD7", +"{# c #4F76D6", +"]# c #4E74D7", +"^# c #466CD7", +"/# c #3B64D6", +"(# c #4261CD", +"_# c #375FCE", +":# c #5A7FD8", +"<# c #6281DA", +"[# c #5F81D8", +"}# c #5C80D8", +"|# c #557DD7", +"1# c #577ED8", +"2# c #567ED7", +"3# c #587DD8", +"4# c #577DD8", +"5# c #587ED8", +"6# c #567DD8", +"7# c #5379D9", +"8# c #5177D7", +"9# c #4D74D5", +"0# c #486ED9", +"a# c #4068D4", +"b# c #3D65D2", +"c# c #4361CC", +"d# c #345ECE", +"e# c #325DCF", +"f# c #2C5AD1", +"g# c #3959C5", +"h# c #547BD8", +"i# c #567DD7", +"j# c #557BD8", +"k# c #5279D9", +"l# c #5278D9", +"m# c #4D74D6", +"n# c #4B71D8", +"o# c #496CD8", +"p# c #4669D7", +"q# c #3D66D3", +"r# c #3F62CF", +"s# c #4260CC", +"t# c #5379D8", +"u# c #4E75D4", +"v# c #4C73D7", +"w# c #476CD7", +"x# c #4869D0", +"y# c #4067D2", +"z# c #3D64D1", +"A# c #4261CC", +"B# c #395FCE", +"C# c #4F75D3", +"D# c #5074D2", +"E# c #5174D1", +"F# c #5175D1", +"G# c #4F74D3", +"H# c #4C73D5", +"I# c #4C73D4", +"J# c #4A72D1", +"K# c #4B70CF", +"L# c #506CCC", +"M# c #4D6BCE", +"N# c #4167D0", +"O# c #3D65D1", +"P# c #3F63CF", +"Q# c #3B5FCD", +"R# c #3159CD", +"S# c #4971D0", +"T# c #4870CF", +"U# c #4C6FCF", +"V# c #4E6CCE", +"W# c #4E6BCE", +"X# c #4769CF", +"Y# c #3D66D0", +"Z# c #3C65D1", +"`# c #4062CE", +" $ c #3D5FCD", +".$ c #365FCF", +"+$ c #325DCD", +"@$ c #2D5AD0", +"#$ c #3859C5", +"$$ c #355FCF", +"%$ c #355ECF", +"&$ c #335ECE", +"*$ c #305CCD", +"=$ c #2B5ACE", +"-$ c #3056C9", +";$ c #2553C6", +">$ c #2153C8", +",$ c #1F4FC7", +"'$ c #274CC5", +")$ c #214AC7", +"!$ c #1C48C8", +"~$ c #1244C9", +"{$ c #1043C9", +"]$ c #1144C9", +"^$ c #2A45BE", +"/$ c #2744B5", +"($ c #1D49C0", +"_$ c #2B58DE", +":$ c #002D94", +"<$ c #2B59CA", +"[$ c #2A59CA", +"}$ c #2E57C8", +"|$ c #3255C6", +"1$ c #3355C5", +"2$ c #1C52C8", +"3$ c #1D50C7", +"4$ c #234FC6", +"5$ c #264CC5", +"6$ c #1D48C7", +"7$ c #1245C8", +"8$ c #1F44C2", +"9$ c #2945BE", +"0$ c #2A45BD", +"a$ c #2040BF", +"b$ c #3156C7", +"c$ c #3056C7", +"d$ c #3354C5", +"e$ c #3355C6", +"f$ c #3255C5", +"g$ c #3254C5", +"h$ c #1952C7", +"i$ c #1951C8", +"j$ c #2050C7", +"k$ c #274CC4", +"l$ c #244CC6", +"m$ c #1F49C7", +"n$ c #1E47C5", +"o$ c #2045C3", +"p$ c #1C44BF", +"q$ c #2045BE", +"r$ c #2040B8", +"s$ c #3254C6", +"t$ c #3055C6", +"u$ c #2A54C6", +"v$ c #2353C7", +"w$ c #3054C5", +"x$ c #2F55C5", +"y$ c #2A54C5", +"z$ c #2553C5", +"A$ c #2F54C5", +"B$ c #3155C6", +"C$ c #2A54C7", +"D$ c #1A52C8", +"E$ c #204FC2", +"F$ c #264DC6", +"G$ c #234BC5", +"H$ c #1D48C1", +"I$ c #1E48BF", +"J$ c #2646BE", +"K$ c #2B45BD", +"L$ c #1E43BE", +"M$ c #2643BF", +"N$ c #2243BF", +"O$ c #3049BC", +"P$ c #1E50BE", +"Q$ c #1D50C0", +"R$ c #1D50BF", +"S$ c #1852C1", +"T$ c #1E51C0", +"U$ c #214FBF", +"V$ c #2050C0", +"W$ c #244EBF", +"X$ c #2151C0", +"Y$ c #234FBF", +"Z$ c #2350C0", +"`$ c #2351C0", +" % c #244FBF", +".% c #2250C0", +"+% c #2051C0", +"@% c #1E50C0", +"#% c #244DBE", +"$% c #274DBF", +"%% c #244CBF", +"&% c #1C48C0", +"*% c #2247BF", +"=% c #2C44BD", +"-% c #1C44BE", +";% c #1444BF", +">% c #1841BF", +",% c #1F40BF", +"'% c #254DBE", +")% c #224FBE", +"!% c #224FBF", +"~% c #234EBF", +"{% c #254CBD", +"]% c #244DBD", +"^% c #244CBD", +"/% c #264DBE", +"(% c #264DBD", +"_% c #214BC0", +":% c #1D48C0", +"<% c #2347BF", +"[% c #2B44BD", +"}% c #2444BE", +"|% c #0F42BF", +"1% c #0641BF", +"2% c #0F41BF", +"3% c #1741BE", +"4% c #1F40BD", +"5% c #234BBF", +"6% c #234CBE", +"7% c #214BBE", +"8% c #244CBE", +"9% c #214ABE", +"0% c #214ABF", +"a% c #1F48C0", +"b% c #2746BE", +"c% c #1F43BE", +"d% c #0941BE", +"e% c #0342BA", +"f% c #0242BC", +"g% c #1241B8", +"h% c #1F40B7", +"i% c #2F41AC", +"j% c #2644AE", +"k% c #2D49B4", +"l% c #2649B6", +"m% c #2949B7", +"n% c #2849B5", +"o% c #2149B8", +"p% c #1E49B9", +"q% c #1F48B8", +"r% c #1F49B9", +"s% c #2545B6", +"t% c #2744B7", +"u% c #2844B7", +"v% c #2043B8", +"w% c #1241B7", +"x% c #1340B8", +"y% c #0D41B8", +"z% c #1941B8", +"A% c #1F40B8", +"B% c #203FB8", +"C% c #2549B5", +"D% c #2648B6", +"E% c #2547B7", +"F% c #2248B7", +"G% c #2048B7", +"H% c #2346B6", +"I% c #2146B6", +"J% c #2247B7", +"K% c #2148B7", +"L% c #2743B4", +"M% c #2643B5", +"N% c #2542B6", +"O% c #1D42B7", +"P% c #0E42B8", +"Q% c #0C41B8", +"R% c #1341B8", +"S% c #1740B8", +"T% c #1C41B8", +"U% c #1F40B1", +"V% c #2644B5", +"W% c #2544B5", +"X% c #2544B4", +"Y% c #2444B5", +"Z% c #2444B4", +"`% c #2744B4", +" & c #2241B7", +".& c #1D41B8", +"+& c #0B42B8", +"@& c #0942B8", +"#& c #0C42B8", +"$& c #0F41B8", +"%& c #1641B8", +"&& c #2442B5", +"*& c #2543B3", +"=& c #2342B2", +"-& c #2341B4", +";& c #2141B3", +">& c #2141B5", +",& c #2140B5", +"'& c #2040B5", +")& c #1C40B7", +"!& c #1B41B3", +"~& c #0142B6", +"{& c #0E41B7", +"]& c #1141B7", +"^& c #1440B2", +"/& c #113FB0", +"(& c #1440B0", +"_& c #213EAF", +":& c #233DAE", +"<& c #223EAF", +"[& c #1E40B1", +"}& c #173EAD", +"|& c #1440AF", +"1& c #0D40AF", +"2& c #0941B0", +"3& c #0D3FAE", +"4& c #1B3CAC", +"5& c #233CAD", +"6& c #203FB0", +"7& c #273BAD", +"8& c #1D40B0", +"9& c #2040B1", +"0& c #1E40B0", +"a& c #1C40B0", +"b& c #1B3DAC", +"c& c #143DAC", +"d& c #193DAD", +"e& c #1B3DAD", +"f& c #173DAD", +"g& c #153DAC", +"h& c #1C3CAC", +"i& c #243CAD", +"j& c #213FB0", +"k& c #263BAA", +"l& c #253CAE", +"m& c #273AAC", +"n& c #273AAD", +"o& c #253BAD", +"p& c #1D3CAC", +"q& c #243BAD", +"r& c #1E3CAC", +"s& c #263BAD", +"t& c #1A3DAC", +"u& c #143DAB", +"v& c #163DAC", +"w& c #1A3CAC", +"x& c #1F3CAD", +"y& c #263BAB", +"z& c #263BA6", +"A& c #1E42A4", +"B& c #2D40A5", +"C& c #253BA6", +"D& c #253CA7", +"E& c #263AA5", +"F& c #253BA7", +"G& c #1E3BA6", +"H& c #193DA6", +"I& c #173DA5", +"J& c #143DA6", +"K& c #1A3DA7", +"L& c #133DA6", +"M& c #123DA5", +"N& c #1A3CA7", +"O& c #243BA6", +"P& c #263AA7", +"Q& c #273BA7", +"R& c #263AA6", +"S& c #223BA6", +"T& c #1D3BA6", +"U& c #173CA6", +"V& c #133DA5", +"W& c #1B3DA6", +"X& c #193DA5", +"Y& c #123DA4", +"Z& c #163CA5", +"`& c #213CA6", +" * c #273BA8", +".* c #263BA7", +"+* c #253BA5", +"@* c #263BA5", +"#* c #1C3BA6", +"$* c #1B3BA9", +"%* c #133BA8", +"&* c #0A3BA7", +"** c #083AA6", +"=* c #123CA5", +"-* c #0839A8", +";* c #0239A6", +">* c #123AA8", +",* c #1F49C8", +"'* c #2F4DA4", +")* c #2E4DA3", +"!* c #384CA4", +"~* c #3C4DA7", +"{* c #394EA7", +"]* c #3B4CA5", +"^* c #3C52B1", +"/* c #3551A8", +"(* c #3759BE", +"_* c #4161C7", +":* c #0033A8", +"<* c #596FA9", +"[* c #2F4DA3", +"}* c #2D4BA5", +"|* c #2E4CA4", +"1* c #2C4AA5", +"2* c #2D4BA4", +"3* c #354DA4", +"4* c #3A4BA4", +"5* c #394DA6", +"6* c #4056AD", +"7* c #445BBB", +"8* c #B5B7B4", +"9* c #1B2F85", +"0* c #242F79", +"a* c #B5B5B5", +"b* c #B5B2B6", +"c* c #C0C3C3", +"d* c #E3E3E4", +"e* c #EBEDEA", +". + @ + # $ % & # $ % & # $ % & # $ % & & * = - ; > , ' ) ! ~ { { { { { { { ] ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ / / / ( / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / _ _ / / : / < [ } | | | 1 1 ", +"2 2 2 2 3 2 4 @ 3 2 4 @ 3 2 4 @ 3 2 4 @ # 5 6 7 8 ; > 9 0 a b c d e f { { { ] ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ( ( ( ( ( ( ( ( ( / / / / / / / / / / / / / / / / / _ _ _ _ _ _ _ _ _ _ _ g g _ / / : : : h i } 1 | 1 ", +"j k l m n o p q n o p q r s t u v w x y z A B C D E F G H I J K L M N O P O O Q R S T T T T T T T T T T T T T T T T T T U U U U U U U U U U U U U U U U U U U U U U U U U U U U V V V U U W X : [ Y | | ", +"Z ` . ...+.@.#...+.@.#.Z $.%.&.Z $.*.=.-.;.>.,.'.).!.~.{.].^./.(._.:.<.[.}.|.1.2.3.4.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.6.7.8.: h Y } 1 ", +"9.0.a.b.c.c.d.e.f.c.d.e.f.c.d.e.f.c.d.e.g.h.i.j.k.l.m.n.o.p.q.r.s.s.t.u.u.v.w.x.4.4.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.y.5.7.6.: / z.A.} ", +"-.B.C.D.-.E.g.F.G.E.g.F.G.E.g.F.G.E.g.F.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.V.U.U.W.X.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Z.y.Y.7.7.: : `.z.} ", +" +.+g.;.++F.@+#+++F.@+#+++F.@+#+++F.@+#+$+%+&+*+=+-+;+>+,+'+)+!+~+{+]+{+{+4.4.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.Y.Y.5.5.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Z.Z.Z.y.y.5.7.7.: : ^+z.Y ", +"/+(+_+#+H.H.>.:+H.H.>.:+H.H.>.:+H.H.>.<+[+}+*+|+1+2+3+4+5+6+7+{+{+4.4.4.4.4.4.5.5.5.5.5.5.5.5.5.5.5.5.5.5.5.Y.Y.Y.Y.Y.Y.Y.5.Y.Y.Y.Y.Y.Y.Y.Y.5.Y.Y.5.5.5.5.Y.Y.Y.Y.Y.Y.Z.Z.Z.Z.y.y.y.y.y.y.7.7.: : ^+i } ", +"8+9+0+0+a+0+0+b+a+0+0+b+a+0+0+b+a+0+0+c+d+e+f+g+h+i+j+k+l+m+n+o+4.4.4.4.5.5.5.5.5.5.Y.Y.5.5.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Y.Y.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.7.7.: : p+z.Y ", +"q+r+r+s+t+u+v+w+t+u+v+w+t+u+v+w+t+u+x+&+y+z+A+B+C+D+E+5+F+G+~+4.4.4.4.5.5.5.5.5.H+Y.Y.Y.Y.Y.Y.Y.Y.I+Y.Z.Y.Y.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.7.7.: : `.z.A.", +"J+v+K+L+M+N+O+P+Q+R+O+P+Q+R+O+P+Q+R+O+S+T+U+V+W+X+Y+P.T Z+`+ @4.4..@5.5.5.5.5.5.Y.Y.Y.I+I+I+I+I++@+@Z.Z.Y.Y.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.@@Z.7.7.: : p+z.Y ", +"#@$@$@%@%@$@#@&@#@#@#@&@#@#@#@&@#@#@#@*@=@-@;+i+;@>@,@'@)@ @4.X.5.5.H+Y.Y.Y.!@Y.Y.I++@+@Z.Z.+@Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.y.Z.6.6.: : `.z.A.", +"#@$@~@~@~@{@]@^@/@{@]@^@/@{@]@^@/@{@]@(@_@:@<@[@}@k+|@V 1@2@3@5.5.5.Y.Y.I+4@I+5@+@Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.y.Z.6.6.: : p+z.Y ", +"6@7@8@9@0@a@b@c@a@a@b@c@a@a@b@c@a@a@b@d@e@<@f@g@h@i@j@k@l@m@n@o@o@p@Y.I+q@q@r@+@Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.y.Z.6.6.: : `.z.A.", +"s@t@u@_@_@v@w@w@x@v@w@w@x@v@y@y@x@v@:@z@A@B@P C@D@E@F@G@H@I@J@K@5@+@+@+@r@I+L@Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.@@Z.W W : : p+z.Y ", +"M@N@O@P@C+Q@Q@R@C+;+Q@R@C+;+;+S@C+Q@Q@R@T@U@V@W@X@Y@Z@`@4. #.#+#Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.8.Z.Z.Z.Z.8.8.Z.Z.y.@@@@W W : : `.z.A.", +"@#O@O@##$#%#&#*#=#-#;#>#,#-#;#>#,#-#'#)#!#~#W@{#]#k@^#H@/#(#_#Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.8.8.Z.Z.Z.Z.Z.Z.Z.8.8.8.8.8.8.8.8.8.8.8.Z.Z.y.y.@@W W : : p+z.Y ", +":#<#[#}#|#1#2#3#4#5#1#4#4#1#1#4#4#1#1#6#7#8#9#V 0#`+a#b#c#d#e#Z.Z.Z.f#Z.Z.Z.f#f#f#f#f#f#f#f#f#f#g#g#g#g#g#8.8.8.8.8.8.8.8.8.g#g#g#g#8.g#8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.y.y.@@W W : : `.z.A.", +"h#2#i#6#|#j#7#k#|#j#7#7#|#j#7#7#|#j#7#l#8#m#n#n#o#p#q#r#s#d#e#Z.Z.Z.f#f#f#f#Z.f#f#g#g#g#g#g#g#g#g#g#g#g#g#8.8.8.g#g#8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.y.y.y.y.8.8.8.y.y.@@W W : : p+z.Y ", +"l#7#7#l#7#7#7#W@7#7#7#W@7#7#k#W@t#7#7#W@u#v#n#w#x#y#z#A#B#Z.e#f#f#Z.f#f#f#Z.Z.g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#8.8.8.g#g#g#g#8.8.g#g#g#g#g#g#8.8.g#8.8.y.8.8.y.y.8.y.y.y.y.@@W W : : `.z.A.", +"C#D#E#F#G#H#I#J#G#H#I#J#G#H#I#J#G#H#I#J#K#L#M#N#O#P#s#Q#+#f#R#f#f#f#f#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#@@@@y.y.@@@@y.y.W W : : p+z.Y ", +"S#S#S#S#S#T#S#U#S#T#S#U#S#T#S#U#S#T#S#U#V#W#X#Y#Z#`# $.$+$@$#$g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#g#@@@@@@@@@@@@@@@@@@y.y.W W : : `.z.A.", +"+$Z..$$$%$+$&$*$%$+$&$*$%$+$&$*$%$+$&$*$=$-$;$>$,$'$)$!$~${$]$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$/$/$/$/$($($_$_$:$:$p+z.Y ", +"<$<$<$<$<$[$}$|$<$[$}$|$<$[$}$|$<$[$}$|$1$2$3$4$5$)$6$7$8$9$0$a$a$a$a$a$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$/$/$/$^$^$^$/$/$/$/$/$/$/$/$/$/$/$/$/$/$($($_$_$:$:$`.z.A.", +"b$c$c$c$d$e$e$f$g$|$|$1$d$e$e$1$d$e$e$1$h$i$j$k$l$m$n$o$p$9$q$a$a$a$a$a$a$a$a$^$a$a$^$^$^$^$^$^$a$r$r$r$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$($($_$_$:$:$p+z.Y ", +"e$1$s$s$1$t$u$v$w$x$y$z$A$x$u$v$g$B$C$>$D$E$F$G$H$I$J$K$L$M$N$a$a$a$a$a$a$a$a$^$r$r$a$^$^$^$a$r$r$r$r$r$/$^$r$^$^$^$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$O$($_$_$:$:$`.z.A.", +"P$Q$R$S$T$U$V$W$X$Y$Z$W$`$ %.%W$+%U$@%#%$%%%&%($*%=%-%;%>%>%,%r$r$r$r$r$a$a$a$/$/$/$r$r$r$r$r$r$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$O$($_$_$:$:$p+z.Y ", +"'%W$)%!%~%{%'%]%~%^%'%]%~%^%'%]%~%^%/%(%_%&%:%<%[%}%|%1%2%3%4%r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$/$r$/$/$r$r$r$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$/$r$/$/$/$/$/$O$($_$_$:$:$`.z.A.", +"5%6%'%'%6%7%8%9%6%7%8%9%6%7%8%9%6%7%8%0%&%a%<%b%[%c%d%e%f%g%h%r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$/$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$/$/$/$/$/$/$/$/$/$/$/$/$/$/$r$r$/$/$r$r$/$r$i%j%O$($_$_$:$:$p+z.Y ", +"k%l%m%n%o%o%p%q%o%o%r%q%o%o%r%q%o%o%p%q%s%t%/$u%v%w%x%y%z%A%B%r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$/$/$/$/$/$/$/$r$r$i%i%i%r$r$i%i%i%i%i%i%i%i%i%i%i%i%r$/$/$j%j%j%j%j%j%j%j%j%O$($_$_$:$:$`.z.A.", +"C%D%E%F%G%H%I%J%K%H%I%J%K%H%I%J%K%H%I%J%L%M%N%O%P%Q%R%S%T%A%B%r$r$r$r$r$r$r$r$r$r$r$r$r$r$r$U%U%r$r$i%i%/$/$r$r$/$/$/$/$r$r$i%i%i%i%i%i%i%i%i%i%i%i%i%i%j%i%j%j%j%j%j%j%j%j%j%j%j%j%j%O$($_$_$:$:$p+z.Y ", +"/$/$/$/$V%V%W%X%W%Y%Y%Z%W%W%Y%Z%W%W%W%`%`% &B%.&+&@&#&$&%&A%B%r$r$r$U%U%U%U%r$U%U%U%U%U%U%U%U%U%U%i%i%i%i%i%i%i%i%/$/$/$i%i%i%i%i%i%i%i%i%j%j%j%j%i%i%i%i%i%j%j%j%i%i%j%j%j%j%j%j%j%j%O$($_$_$:$:$`.z.A.", +"&&*&=&-&=&;&>&,&=&;&>&,&=&;&>&,&=&;&>&'&)&!&~&{&]&^&/&(&_&:&<&U%U%U%U%U%U%U%U%U%U%U%U%U%i%i%U%U%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%O$($_$_$:$:$p+z.Y ", +"U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%U%[&}&|&1&2&3&4&5&_&6&U%7&U%U%U%U%U%U%U%U%i%i%U%U%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%O$O$_$_$:$:$`.z.A.", +"U%U%U%U%U%U%[&8&U%9&[&0&U%9&[&0&U%9&[&a&:&b&c&d&e&f&g&h&i&<&j&U%U%U%U%U%U%U%U%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%O$O$_$_$:$:$p+z.Y ", +"k&l&m&7&7&n&o&p&7&n&q&r&s&s&q&r&s&n&o&p&t&u&u&g&v&w&x&q&n&m&y&7&7&U%U%7&z&7&z&U%A&B&i%i%B&B&i%i%B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&i%B&O$O$_$_$:$:$`.z.A.", +"C&D&E&z&z&E&F&G&z&E&F&G&z&E&F&G&z&E&F&G&H&I&J&K&L&M&N&O&P&Q&z&z&z&z&z&z&z&z&z&z&z&z&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&O$O$_$_$:$:$p+z.Y ", +"z&z&z&z&R&S&T&U&R&S&T&U&R&S&T&U&R&S&T&U&V&V&W&X&Y&Z&`&C&R&z&z&z&z&z&z&z&z&z&z&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&O$O$_$_$:$:$^+z.A.", +"z& *.*+*@*#*$*%*@*#*$*%*@*#*$*%*@*#*$*%*&***=*-*;*>*k&P&+*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&B&B&B&B&z&z&z&B&B&B&z&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&B&/$O$O$@@_$,*:$/ ^+z.Y ", +"'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*'*)*'*!*~*{*]*^*^*^*/*/*/*/*/*/*/*^*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*^*/*/*/*/*/*h h ^*h h ^*^*h h ^*^*^*^*h ^*^*^*^*h ^*^*^*(*_*_*_*_*_$:*:$<*`.z.} ", +"'*'*'*'*'*[*}*|*'*[*}*|*'*[*}*|*'*[*}*|*1*1*2*}*}*2*[*)*3*4*5*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*/*h h h h h h h h h h h h h h h h 6*7*_*_*_*_*^*:*:$: 8*z.Y } ", +"9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*9*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*0*( <*8*^+z.Y } 1 ", +"a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*8*b*8*b*8*b*8*b*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*b*8*8*8*8*b*8*`.z.A.Y | | ", +"c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*c*p+`.p+`.p+`.p+`.`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+`.p+^+`.^+^+z.z.Y Y | | 1 ", +"d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*A.Y A.Y A.Y A.Y Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y A.Y } } | | | | 1 1 ", +"e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*e*} | } | } | } | | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | } | | | | 1 | | | 1 1 1 "}; + + +static char * tabmiddle_xpm[] = { +"33 42 32 1", +" c None", +". c #CECFEF", +"+ c #CECBE7", +"@ c #C6C7E7", +"# c #C6CBE7", +"$ c #BDBEDE", +"% c #BDC3DE", +"& c #CECBEF", +"* c #B5B6D6", +"= c #ADAECE", +"- c #ADB2CE", +"; c #BDBAD6", +"> c #B5BAD6", +", c #C6C3DE", +"' c #ADAAC6", +") c #B5B2CE", +"! c #B5B6CE", +"~ c #A5A2BD", +"{ c #A5A6BD", +"] c #9C9EB5", +"^ c #9CA2BD", +"/ c #ADAEC6", +"( c #C6C3E7", +"_ c #9C9AB5", +": c #A5A6C6", +"< c #949AAD", +"[ c #A5AAC6", +"} c #9496AD", +"| c #BDBADE", +"1 c #BDBED6", +"2 c #9CA2B5", +"3 c #A5AABD", +"..........................+@.#.#.", +"........................$@%&#.#..", +"......................**$$@@&#.#.", +".....................=-;>,%+@.#..", +"....................'')!$$@@&#.#.", +"...................~{=)$$@@&#.#..", +"..................]^'/;;(%&#.#...", +"................._]:/*>,%&@.#.#..", +".................<{[)!$%+@.#.#...", +"................}~{=!$%@@.#......", +"................]^/-|$@@.#.......", +"................]'/*;@@&#........", +"...............<~[)>,%&#.#.......", +"...............]~=)$%+#.#........", +"...............]'/;1@@.#.........", +"...............~{)*,%&#..........", +"...............2/-$$@#...........", +"...............~[*>(@&#..........", +"...............^=)$%+#...........", +"...............{'*>(@.#..........", +"...............^=)$%+#...........", +"...............{'*>(@.#..........", +"...............^=)$%+#...........", +"...............{'*>(@.#..........", +"...............^=)$%+#...........", +"...............{'*>(@.#..........", +"...............^=)$%+#...........", +"...............{'*>@@.#..........", +"...............^=!$%&#...........", +"...............{/*;@@.#..........", +"...............{)!$%&#...........", +"..............]'/;1@@.#..........", +"..............23)>,%&#...........", +"..............~=-$$@@.#..........", +".............]{/*;@@.#...........", +"............<^[)>,%&#............", +"............]{/!$%@@.#...........", +"..........]^[-!$%@@.#............", +".........]^3/!>$@@.#.............", +".......<]^3/!>$@@&#..............", +".....<]2{[/!>$%@&#.#.............", +"}<<_]2{3/-!>$%@&#.#.............."}; + + +static char * tabselectedbeginn_xpm[] = { +"33 39 28 1", +" c None", +". c #CECFEF", +"+ c #EFF3EF", +"@ c #FFFBFF", +"# c #F7FBF7", +"$ c #FFFFFF", +"% c #EFEFEF", +"& c #F7F7F7", +"* c #DEDFDE", +"= c #E7E7E7", +"- c #D6D3D6", +"; c #DEE3DE", +"> c #EFEBEF", +", c #F7F3F7", +"' c #CECBCE", +") c #CECFCE", +"! c #D6D7D6", +"~ c #DEDBDE", +"{ c #E7EBE7", +"] c #C6C7C6", +"^ c #E7E3E7", +"/ c #BDC3BD", +"( c #CED3CE", +"_ c #BDBABD", +": c #C6C3C6", +"< c #C6CBC6", +"[ c #D6DBD6", +"} c #BDBEBD", +"..........................+@#$#$$", +"........................%%&&@#$#$", +"......................*==%%&&@#$$", +"....................--*;>%,&@#$#$", +"...................')!~={,+@#$#$$", +"...................]-!^=%%&&@#$#$", +"................../'(~;>%&&@#$#$$", +"................._])!*={,&@#$#$$$", +"................_])~*>%&&$#$$$$$$", +"................:<![={&&@#$$$$$$$", +"................:)!^=,+@#$$$$$$$$", +"...............}'(*^%+@#$#$$$$$$$", +"...............:<!*>%&&$#$$$$$$$$", +".............../)!^{,&@#$$$$$$$$$", +"...............](*^%+@#$$$$$$$$$$", +"...............]!~=%&&$$$$$$$$$$$", +"...............'(*=,+@#$$$$$$$$$$", +"...............<!*>%&&$$$$$$$$$$$", +"...............'-^=,+@#$$$$$$$$$$", +"...............<!*>%&#$$$$$$$$$$$", +"...............'-^=,+@#$$$$$$$$$$", +"...............<!*>%&#$$$$$$$$$$$", +"...............'-^=,+@#$$$$$$$$$$", +"...............<!*>%&#$$$$$$$$$$$", +"...............'-^=,+@#$$$$$$$$$$", +"...............<!*>%&#$$$$$$$$$$$", +"...............'!^=,&@#$$$$$$$$$$", +"...............<~*>%&#$$$$$$$$$$$", +"...............)!^{,&@#$$$$$$$$$$", +"..............])~;%+@#$$$$$$$$$$$", +"..............]-[={&&$#$$$$$$$$$$", +".............])!^=,&@#$$$$$$$$$$$", +"............:'-*^%+@#$$$$$$$$$$$$", +"............])~*>%&&$#$$$$$$$$$$$", +"...........:'!*={,&@#$$$$$$$$$$$$", +"..........:'-~^=,+@#$$$$$$$$$$$$$", +".......}]'-~^=%,&@#$$$$$$$$$$$$$$", +".....}:])-~^=%,+@#$#$$$$$$$$$$$$$", +"}}}:]')-!*^=%,&@#$#$$$$$$$$$$$$$$"}; + + +static char * tabselectedend_xpm[] = { +"33 42 33 1", +" c None", +". c #FFFFFF", +"+ c #CECBE7", +"@ c #C6C7E7", +"# c #CECFEF", +"$ c #C6CBE7", +"% c #BDBEDE", +"& c #BDC3DE", +"* c #CECBEF", +"= c #B5B6D6", +"- c #ADAECE", +"; c #ADB2CE", +"> c #BDBAD6", +", c #B5BAD6", +"' c #C6C3DE", +") c #ADAAC6", +"! c #B5B2CE", +"~ c #B5B6CE", +"{ c #A5A2BD", +"] c #A5A6BD", +"^ c #9C9EB5", +"/ c #9CA2BD", +"( c #ADAEC6", +"_ c #C6C3E7", +": c #9C9AB5", +"< c #A5A6C6", +"[ c #949AAD", +"} c #A5AAC6", +"| c #9496AD", +"1 c #BDBADE", +"2 c #BDBED6", +"3 c #9CA2B5", +"4 c #A5AABD", +"..........................+@#$#$#", +"........................%@&*$#$##", +"......................==%%@@*$#$#", +".....................-;>,'&+@#$##", +"....................))!~%%@@*$#$#", +"...................{]-!%%@@*$#$##", +"..................^/)(>>_&*$#$###", +".................:^<(=,'&*@#$#$##", +".................[]}!~%&+@#$#$###", +"................|{]-~%&@@#$######", +"................^/(;1%@@#$#######", +"................^)(=>@@*$########", +"...............[{}!,'&*$#$#######", +"...............^{-!%&+$#$########", +"...............^)(>2@@#$#########", +"...............{]!='&*$##########", +"...............3(;%%@$###########", +"...............{}=,_@*$##########", +".............../-!%&+$###########", +"...............])=,_@#$##########", +".............../-!%&+$###########", +"...............])=,_@#$##########", +".............../-!%&+$###########", +"...............])=,_@#$##########", +".............../-!%&+$###########", +"...............])=,_@#$##########", +".............../-!%&+$###########", +"...............])=,@@#$##########", +".............../-~%&*$###########", +"...............](=>@@#$##########", +"...............]!~%&*$###########", +"..............^)(>2@@#$##########", +"..............34!,'&*$###########", +"..............{-;%%@@#$##########", +".............^](=>@@#$###########", +"............[/}!,'&*$############", +"............^](~%&@@#$###########", +"..........^/};~%&@@#$############", +".........^/4(~,%@@#$#############", +".......[^/4(~,%@@*$##############", +".....[^3]}(~,%&@*$#$#############", +"|[[:^3]4(;~,%&@*$#$##############"}; + + +static char * tabend_xpm[] = { +"33 42 3 1", +" c None", +". c #CECFEF", +"+ c #FFFFFF", +"..........................+++++++", +"........................+++++++++", +"......................+++++++++++", +".....................++++++++++++", +"....................+++++++++++++", +"...................++++++++++++++", +"..................+++++++++++++++", +".................++++++++++++++++", +".................++++++++++++++++", +"................+++++++++++++++++", +"................+++++++++++++++++", +"................+++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"...............++++++++++++++++++", +"..............+++++++++++++++++++", +"..............+++++++++++++++++++", +"..............+++++++++++++++++++", +".............++++++++++++++++++++", +"............+++++++++++++++++++++", +"............+++++++++++++++++++++", +"..........+++++++++++++++++++++++", +".........++++++++++++++++++++++++", +".......++++++++++++++++++++++++++", +".....++++++++++++++++++++++++++++", +"+++++++++++++++++++++++++++++++++"}; + + + + +QColor fromHsl(QColor c) +{ + const qreal h = c.hueF(); + const qreal s = c.saturationF(); + const qreal l = c.valueF(); + + qreal ca[3] = {0, 0, 0}; + + if (s == 0 || h == 1) { + // achromatic case + ca[0] = ca[1] = ca[2] = l; + } else { + // chromatic case + qreal temp2; + if (l < qreal(0.5)) + temp2 = l * (qreal(1.0) + s); + else + temp2 = l + s - (l * s); + + const qreal temp1 = (qreal(2.0) * l) - temp2; + qreal temp3[3] = { h + (qreal(1.0) / qreal(3.0)), + h, + h - (qreal(1.0) / qreal(3.0)) }; + + for (int i = 0; i != 3; ++i) { + if (temp3[i] < qreal(0.0)) + temp3[i] += qreal(1.0); + else if (temp3[i] > qreal(1.0)) + temp3[i] -= qreal(1.0); + + const qreal sixtemp3 = temp3[i] * qreal(6.0); + + if (sixtemp3 < qreal(1.0)) + ca[i] = ((temp1 + (temp2 - temp1) * sixtemp3)); + else if ((temp3[i] * qreal(2.0)) < qreal(1.0)) + ca[i] = (temp2); + else if ((temp3[i] * qreal(3.0)) < qreal(2.0)) + ca[i] = temp1 + (temp2 -temp1) * (qreal(2.0) /qreal(3.0) - temp3[i]) * qreal(6.0); + else ca[i] = temp1; + } + } + + return QColor::fromRgbF(ca[0], ca[1], ca[2]); +} + +#define Q_MAX_3(a, b, c) ( ( a > b && a > c) ? a : (b > c ? b : c) ) +#define Q_MIN_3(a, b, c) ( ( a < b && a < c) ? a : (b < c ? b : c) ) + +QColor toHsl(QColor c) +{ + QColor color; + qreal h; + qreal s; + qreal l; + + const qreal r = c.redF(); + const qreal g = c.greenF(); + const qreal b = c.blueF(); + const qreal max = Q_MAX_3(r, g, b); + const qreal min = Q_MIN_3(r, g, b); + const qreal delta = max - min; + const qreal delta2 = max + min; + const qreal lightness = qreal(0.5) * delta2; + l = (lightness); + if (qFuzzyIsNull(delta)) { + // achromatic case, hue is undefined + h = 0; + s = 0; + } else { + // chromatic case + qreal hue = 0; + if (lightness < qreal(0.5)) + s = ((delta / delta2)); + else + s = ((delta / (qreal(2.0) - delta2))); + if (qFuzzyCompare(r, max)) { + hue = ((g - b) /delta); + } else if (qFuzzyCompare(g, max)) { + hue = (2.0 + (b - r) / delta); + } else if (qFuzzyCompare(b, max)) { + hue = (4.0 + (r - g) / delta); + } else { + Q_ASSERT_X(false, "QColor::toHsv", "internal error"); + } + hue *= 60.0; + if (hue < 0.0) + hue += 360.0; + h = (hue * 100); + } + + h = h / 36000; + + return QColor::fromHsvF(h, s, l); +} + +void tintColor(QColor &color, QColor tintColor, qreal _saturation) +{ + tintColor = toHsl(tintColor); + color = toHsl(color); + qreal hue = tintColor.hueF(); + + qreal saturation = color.saturationF(); + if (_saturation) + saturation = _saturation; + qreal lightness = color.valueF(); + color.setHsvF(hue, saturation, lightness); + + color = fromHsl(color); + color.toRgb(); +} + +void tintImagePal(QImage *image, QColor color, qreal saturation) +{ + QVector<QRgb> colorTable = image->colorTable(); + for (int i=2;i< colorTable.size();i++) { + QColor c(toHsl(colorTable.at(i))); + tintColor(c, color, saturation); + colorTable[i] = c.rgb(); + } + image->setColorTable(colorTable); +} + + +void tintImage(QImage *image, QColor color, qreal saturation) +{ + *image = image->convertToFormat(QImage::Format_RGB32); + + for (int x = 0; x < image->width(); x++) + for (int y = 0; y < image->height(); y++) { + QColor c(image->pixel(x,y)); + tintColor(c, color, saturation); + image->setPixel(x, y, c.rgb()); + } +} + +#endif //Q_WS_WINCE_WM + + +enum QSliderDirection { SliderUp, SliderDown, SliderLeft, SliderRight }; + +#ifdef Q_WS_WINCE_WM + +void QWindowsMobileStylePrivate::tintImagesButton(QColor color) +{ + if (currentTintButton == color) + return; + currentTintButton = color; + + imageTabEnd = QImage(tabend_xpm); + imageTabSelectedEnd = QImage(tabselectedend_xpm); + imageTabSelectedBegin = QImage(tabselectedbeginn_xpm); + imageTabMiddle = QImage(tabmiddle_xpm); + tintImage(&imageTabEnd, color, 0.0); + tintImage(&imageTabSelectedEnd, color, 0.0); + tintImage(&imageTabSelectedBegin, color, 0.0); + tintImage(&imageTabMiddle, color, 0.0); + + if (!doubleControls) { + int height = imageTabMiddle.height() / 2 + 1; + imageTabEnd = imageTabEnd.scaledToHeight(height); + imageTabMiddle = imageTabMiddle.scaledToHeight(height); + imageTabSelectedEnd = imageTabSelectedEnd.scaledToHeight(height); + imageTabSelectedBegin = imageTabSelectedBegin.scaledToHeight(height); + } +} + +void QWindowsMobileStylePrivate::tintImagesHigh(QColor color) +{ + if (currentTintHigh == color) + return; + currentTintHigh = color; + tintListViewHighlight(color); + imageScrollbarHandleUpHigh = imageScrollbarHandleUp; + imageScrollbarHandleDownHigh = imageScrollbarHandleDown; + tintImagePal(&imageScrollbarHandleDownHigh, color, qreal(0.8)); + tintImagePal(&imageScrollbarHandleUpHigh, color, qreal(0.8)); +} + +void QWindowsMobileStylePrivate::tintListViewHighlight(QColor color) +{ + imageListViewHighlightCornerRight = QImage(listviewhighcornerright_xpm); + tintImage(&imageListViewHighlightCornerRight, color, qreal(0.0)); + + imageListViewHighlightCornerLeft = QImage(listviewhighcornerleft_xpm); + tintImage(&imageListViewHighlightCornerLeft, color, qreal(0.0)); + + imageListViewHighlightMiddle = QImage(listviewhighmiddle_xpm); + tintImage(&imageListViewHighlightMiddle, color, qreal(0.0)); + + int height = imageListViewHighlightMiddle.height(); + if (!doubleControls) { + height = height / 2; + imageListViewHighlightCornerRight = imageListViewHighlightCornerRight.scaledToHeight(height); + imageListViewHighlightCornerLeft = imageListViewHighlightCornerLeft.scaledToHeight(height); + imageListViewHighlightMiddle = imageListViewHighlightMiddle.scaledToHeight(height); + } +} + +#endif //Q_WS_WINCE_WM + +void QWindowsMobileStylePrivate::setupWindowsMobileStyle65() +{ +#ifdef Q_WS_WINCE_WM + wm65 = qt_wince_is_windows_mobile_65(); + if (wm65) { + imageScrollbarHandleUp = QImage(sbhandleup_xpm); + imageScrollbarHandleDown = QImage(sbhandledown_xpm); + imageScrollbarGripUp = QImage(sbgripup_xpm); + imageScrollbarGripDown = QImage(sbgripdown_xpm); + imageScrollbarGripMiddle = QImage(sbgripmiddle_xpm); + + if (!doubleControls) { + imageScrollbarHandleUp = imageScrollbarHandleUp.scaledToHeight(imageScrollbarHandleUp.height() / 2); + imageScrollbarHandleDown = imageScrollbarHandleDown.scaledToHeight(imageScrollbarHandleDown.height() / 2); + imageScrollbarGripMiddle = imageScrollbarGripMiddle.scaledToHeight(imageScrollbarGripMiddle.height() / 2); + imageScrollbarGripUp = imageScrollbarGripUp.scaledToHeight(imageScrollbarGripUp.height() / 2); + imageScrollbarGripDown = imageScrollbarGripDown.scaledToHeight(imageScrollbarGripDown.height() / 2); + } else { + } + tintImagesHigh(Qt::blue); + } +#endif //Q_WS_WINCE_WM +} + +void QWindowsMobileStylePrivate::drawTabBarTab(QPainter *painter, const QStyleOptionTab *tab) +{ +#ifndef QT_NO_TABBAR +#ifdef Q_WS_WINCE_WM + if (wm65) { + tintImagesButton(tab->palette.button().color()); + QRect r; + r.setTopLeft(tab->rect.topRight() - QPoint(imageTabMiddle.width(), 0)); + r.setBottomRight(tab->rect.bottomRight()); + if (tab->state & QStyle::State_Selected) { + painter->fillRect(tab->rect, tab->palette.window()); + } else { + painter->fillRect(tab->rect, QColor(imageTabMiddle.pixel(0,0))); + } + if (tab->selectedPosition == QStyleOptionTab::NextIsSelected) { + painter->drawImage(r, imageTabSelectedBegin); + } else if (tab->position == QStyleOptionTab::End || + tab->position == QStyleOptionTab::OnlyOneTab) { + if (!(tab->state & QStyle::State_Selected)) { + painter->drawImage(r, imageTabEnd); + } + } else if (tab->state & QStyle::State_Selected) { + painter->drawImage(r, imageTabSelectedEnd); + } else { + painter->drawImage(r, imageTabMiddle); + } + if (tab->position == QStyleOptionTab::Beginning && ! (tab->state & QStyle::State_Selected)) { + painter->drawImage(tab->rect.topLeft() - QPoint(imageTabMiddle.width() * 0.60, 0), imageTabSelectedEnd); + } + //imageTabBarBig + return; + } +#endif //Q_WS_WINCE_WM + painter->save(); + painter->setPen(tab->palette.shadow().color()); + if (doubleControls) { + QPen pen = painter->pen(); + pen.setWidth(2); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); + } + if(tab->shape == QTabBar::RoundedNorth) { + if (tab->state & QStyle::State_Selected) { + painter->fillRect(tab->rect, tab->palette.light()); + painter->drawLine(tab->rect.topRight(), tab->rect.bottomRight()); + } + else { + painter->fillRect(tab->rect, tab->palette.button()); + painter->drawLine(tab->rect.bottomLeft() , tab->rect.bottomRight()); + painter->drawLine(tab->rect.topRight(), tab->rect.bottomRight()); + } + } + else if(tab->shape == QTabBar::RoundedSouth) { + if (tab->state & QStyle::State_Selected) { + painter->fillRect(tab->rect.adjusted(0,-2,0,0), tab->palette.light()); + painter->drawLine(tab->rect.topRight(), tab->rect.bottomRight()); + } + else { + painter->fillRect(tab->rect, tab->palette.button()); + if (doubleControls) + painter->drawLine(tab->rect.topLeft() + QPoint(0,1), tab->rect.topRight() + QPoint(0,1)); + else + painter->drawLine(tab->rect.topLeft(), tab->rect.topRight()); + painter->drawLine(tab->rect.topRight(), tab->rect.bottomRight()); + } + } + else if(tab->shape == QTabBar::RoundedEast) { + if (tab->state & QStyle::State_Selected) { + painter->fillRect(tab->rect, tab->palette.light()); + painter->drawLine(tab->rect.topLeft(), tab->rect.topRight()); + } + else { + painter->fillRect(tab->rect, tab->palette.button()); + painter->drawLine(tab->rect.topLeft(), tab->rect.bottomLeft()); + painter->drawLine(tab->rect.topLeft(), tab->rect.topRight()); + } + } + else if(tab->shape == QTabBar::RoundedWest) { + if (tab->state & QStyle::State_Selected) { + painter->fillRect(tab->rect, tab->palette.light()); + painter->drawLine(tab->rect.bottomLeft(), tab->rect.bottomRight()); + } + else { + painter->fillRect(tab->rect, tab->palette.button()); + painter->drawLine(tab->rect.topRight(), tab->rect.bottomRight()); + painter->drawLine(tab->rect.bottomLeft(), tab->rect.bottomRight()); + } + } + painter->restore(); +#endif //QT_NO_TABBAR +} + +void QWindowsMobileStylePrivate::drawPanelItemViewSelected(QPainter *painter, const QStyleOptionViewItemV4 *option, QRect rect) +{ +#ifdef Q_WS_WINCE_WM + if (wm65) { + QRect r; + if (rect.isValid()) + r = rect; + else + r = option->rect; + tintImagesHigh(option->palette.highlight().color()); + + painter->setPen(QColor(Qt::lightGray)); + + if (option->viewItemPosition == QStyleOptionViewItemV4::Middle) { + painter->drawImage(r, imageListViewHighlightMiddle); + } else if (option->viewItemPosition == QStyleOptionViewItemV4::Beginning) { + painter->drawImage(r.adjusted(10, 0, 0, 0), imageListViewHighlightMiddle); + } else if (option->viewItemPosition == QStyleOptionViewItemV4::End) { + painter->drawImage(r.adjusted(0, 0, -10, 0), imageListViewHighlightMiddle); + } else { + painter->drawImage(r.adjusted(10, 0, -10, 0), imageListViewHighlightMiddle); + } + + QImage cornerLeft = imageListViewHighlightCornerLeft; + QImage cornerRight = imageListViewHighlightCornerRight; + + int width = r.width() > cornerRight.width() ? r.width() : cornerRight.width(); + + if ((width * 2) > r.width()) { + width = (r.width() - 5) / 2; + } + + cornerLeft = cornerLeft.scaled(width, r.height()); + cornerRight = cornerRight.scaled(width, r.height()); + + if ((option->viewItemPosition == QStyleOptionViewItemV4::Beginning) || (option->viewItemPosition == QStyleOptionViewItemV4::OnlyOne) || !option->viewItemPosition) { + painter->drawImage(r.topLeft(), cornerLeft); + } + if ((option->viewItemPosition == QStyleOptionViewItemV4::End) || (option->viewItemPosition == QStyleOptionViewItemV4::OnlyOne) || !option->viewItemPosition) { + painter->drawImage(r.topRight() - QPoint(cornerRight.width(),0), cornerRight); + } + return; + } +#endif //Q_WS_WINCE_WM + QPalette::ColorGroup cg = option->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + + if (rect.isValid()) + painter->fillRect(rect, option->palette.brush(cg, QPalette::Highlight)); + else + painter->fillRect(option->rect, option->palette.brush(cg, QPalette::Highlight)); +} + +void QWindowsMobileStylePrivate::drawScrollbarGrip(QPainter *p, QStyleOptionSlider *newScrollbar, const QStyleOptionComplex *option, bool drawCompleteFrame) +{ +#ifdef Q_WS_WINCE_WM + if (wm65) { + if (newScrollbar->orientation == Qt::Horizontal) { + QTransform transform; + transform.rotate(-90); + QRect r = newScrollbar->rect; + p->drawImage(r.adjusted(10, 0, -10, 0), imageScrollbarGripMiddle.transformed(transform)); + p->drawImage(r.topLeft(), imageScrollbarGripUp.transformed(transform)); + p->drawImage(r.topRight() - QPoint(imageScrollbarGripDown.height() - 1, 0), imageScrollbarGripDown.transformed(transform)); + } else { + QRect r = newScrollbar->rect; + p->drawImage(r.adjusted(0, 10, 0, -10), imageScrollbarGripMiddle); + p->drawImage(r.topLeft(), imageScrollbarGripUp); + p->drawImage(r.bottomLeft() - QPoint(0, imageScrollbarGripDown.height() - 1), imageScrollbarGripDown); + } + return ; + } +#endif + if (newScrollbar->orientation == Qt::Horizontal) { + p->fillRect(newScrollbar->rect,option->palette.button()); + QRect r = newScrollbar->rect; + p->drawLine(r.topLeft(), r.bottomLeft()); + p->drawLine(r.topRight(), r.bottomRight()); + if (smartphone) { + p->drawLine(r.topLeft(), r.topRight()); + p->drawLine(r.bottomLeft(), r.bottomRight()); + } + } + else { + p->fillRect(newScrollbar->rect,option->palette.button()); + QRect r = newScrollbar->rect; + p->drawLine(r.topLeft(), r.topRight()); + p->drawLine(r.bottomLeft(), r.bottomRight()); + if (smartphone) { + p->drawLine(r.topLeft(), r.bottomLeft()); + p->drawLine(r.topRight(), r.bottomRight()); + } + } + if (newScrollbar->state & QStyle::State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*newScrollbar); + fropt.rect.setRect(newScrollbar->rect.x() + 2, newScrollbar->rect.y() + 2, + newScrollbar->rect.width() - 5, + newScrollbar->rect.height() - 5); + } + int gripMargin = doubleControls ? 4 : 2; + int doubleLines = doubleControls ? 2 : 1; + //If there is a frame around the scrollbar (abstractScrollArea), + //then the margin is different, because of the missing frame + int gripMarginFrame = doubleControls ? 3 : 1; + if (drawCompleteFrame) + gripMarginFrame = 0; + //draw grips + if (!smartphone) + if (newScrollbar->orientation == Qt::Horizontal) { + for (int i = -3; i < 3; i += 2) { + p->drawLine( + QPoint(newScrollbar->rect.center().x() + i * doubleLines + 1, + newScrollbar->rect.top() + gripMargin +gripMarginFrame), + QPoint(newScrollbar->rect.center().x() + i * doubleLines + 1, + newScrollbar->rect.bottom() - gripMargin)); + } + } else { + for (int i = -2; i < 4 ; i += 2) { + p->drawLine( + QPoint(newScrollbar->rect.left() + gripMargin + gripMarginFrame , + newScrollbar->rect.center().y() + 1 + i * doubleLines - 1), + QPoint(newScrollbar->rect.right() - gripMargin, + newScrollbar->rect.center().y() + 1 + i * doubleLines - 1)); + } + } + if (!smartphone) { + QRect r; + if (doubleControls) + r = option->rect.adjusted(1, 1, -1, 0); + else + r = option->rect.adjusted(0, 0, -1, 0); + if (drawCompleteFrame && doubleControls) + r.adjust(0, 0, 0, -1); + //Check if the scrollbar is part of an abstractItemView and draw the frame according + if (drawCompleteFrame) + p->drawRect(r); + else + if (newScrollbar->orientation == Qt::Horizontal) + p->drawLine(r.topLeft(), r.topRight()); + else + p->drawLine(r.topLeft(), r.bottomLeft()); + } +} + +void QWindowsMobileStylePrivate::drawScrollbarHandleUp(QPainter *p, QStyleOptionSlider *opt, bool completeFrame, bool ) +{ +#ifdef Q_WS_WINCE_WM + if (wm65) { + tintImagesHigh(opt->palette.highlight().color()); + QRect r = opt->rect; + if (opt->orientation == Qt::Horizontal) { + QTransform transform; + transform.rotate(-90); + if (opt->state & QStyle::State_Sunken) + p->drawImage(r.topLeft(), imageScrollbarHandleUpHigh.transformed(transform)); + else + p->drawImage(r.topLeft(), imageScrollbarHandleUp.transformed(transform)); + } else { + if (opt->state & QStyle::State_Sunken) + p->drawImage(r.topLeft(), imageScrollbarHandleUpHigh); + else + p->drawImage(r.topLeft(), imageScrollbarHandleUp); + } + return ; + } +#endif //Q_WS_WINCE_WM + + QBrush fill = opt->palette.button(); + if (opt->state & QStyle::State_Sunken) + fill = opt->palette.shadow(); + + QStyleOption arrowOpt = *opt; + if (doubleControls) + arrowOpt.rect = opt->rect.adjusted(4, 6, -5, -3); + else + arrowOpt.rect = opt->rect.adjusted(5, 6, -4, -3); + + bool horizontal = (opt->orientation == Qt::Horizontal); + + if (horizontal) { + p->fillRect(opt->rect,fill); + QRect r = opt->rect.adjusted(0,0,1,0); + p->drawLine(r.topRight(), r.bottomRight()); + if (doubleControls) + arrowOpt.rect.adjust(0, -2 ,0, -2); + q_func()->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &arrowOpt, p, 0); + } else { + p->fillRect(opt->rect,fill); + QRect r = opt->rect.adjusted(0, 0, 0, 1); + p->drawLine(r.bottomLeft(), r.bottomRight()); + if (completeFrame) + arrowOpt.rect.adjust(-2, 0, -2, 0); + if (doubleControls) + arrowOpt.rect.adjust(0, -4 , 0, -4); + if (completeFrame && doubleControls) + arrowOpt.rect.adjust(2, 0, 2, 0); + q_func()->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &arrowOpt, p, 0); + } +} + +void QWindowsMobileStylePrivate::drawScrollbarHandleDown(QPainter *p, QStyleOptionSlider *opt, bool completeFrame, bool secondScrollBar) +{ +#ifndef QT_NO_SCROLLBAR +#ifdef Q_WS_WINCE_WM + if (wm65) { + tintImagesHigh(opt->palette.highlight().color()); + QRect r = opt->rect; + if (opt->orientation == Qt::Horizontal) { + QTransform transform; + transform.rotate(-90); + if (opt->state & QStyle::State_Sunken) + p->drawImage(r.topLeft(), imageScrollbarHandleDownHigh.transformed(transform)); + else + p->drawImage(r.topLeft(), imageScrollbarHandleDown.transformed(transform)); + } else { + if (opt->state & QStyle::State_Sunken) + p->drawImage(r.topLeft(), imageScrollbarHandleDownHigh); + else + p->drawImage(r.topLeft(), imageScrollbarHandleDown); + } + return ; + } +#endif //Q_WS_WINCE_WM + + QBrush fill = opt->palette.button(); + if (opt->state & QStyle::State_Sunken) + fill = opt->palette.shadow(); + + QStyleOption arrowOpt = *opt; + if (doubleControls) + arrowOpt.rect = opt->rect.adjusted(4, 0, -5, 3); + else + arrowOpt.rect = opt->rect.adjusted(5, 6, -4, -3); + + bool horizontal = (opt->orientation == Qt::Horizontal); + + if (horizontal) { + p->fillRect(opt->rect,fill); + QRect r = opt->rect.adjusted(0, 0, 0, 0); + p->drawLine(r.topLeft(), r.bottomLeft()); + if (secondScrollBar) + p->drawLine(r.topRight(), r.bottomRight()); + if (doubleControls) + arrowOpt.rect.adjust(0, 4, 0, 4 ); + q_func()->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &arrowOpt, p, 0); + } else { + p->fillRect(opt->rect,fill); + QRect r = opt->rect.adjusted(0, -1, 0, -1); + p->drawLine(r.topLeft(), r.topRight()); + if (secondScrollBar) + p->drawLine(r.bottomLeft() + QPoint(0,1), r.bottomRight() + QPoint(0, 1)); + if (completeFrame) + arrowOpt.rect.adjust(-2, 0, -2, 0); + if (doubleControls) + arrowOpt.rect.adjust(1, 0, 1, 0 ); + if (completeFrame && doubleControls) + arrowOpt.rect.adjust(1, 0, 1, 0); + q_func()->proxy()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &arrowOpt, p, 0); + } +#endif //QT_NO_SCROLLBAR +} + +void QWindowsMobileStylePrivate::drawScrollbarGroove(QPainter *p,const QStyleOptionSlider *opt) +{ +#ifndef QT_NO_SCROLLBAR +#ifdef Q_OS_WINCE_WM + if (wm65) { + p->fillRect(opt->rect, QColor(231, 231, 231)); + return ; + } +#endif + QBrush fill; + if (smartphone) { + fill = opt->palette.light(); + p->fillRect(opt->rect, fill); + fill = opt->palette.button(); + QImage image; +#ifndef QT_NO_IMAGEFORMAT_XPM + if (opt->orientation == Qt::Horizontal) + image = QImage(vertlines_xpm); + else + image = QImage(horlines_xpm); +#endif + image.setColor(1, opt->palette.button().color().rgb()); + fill.setTextureImage(image); + } + else { + fill = opt->palette.light(); + } + p->fillRect(opt->rect, fill); +#endif //QT_NO_SCROLLBAR +} + +QWindowsMobileStyle::QWindowsMobileStyle(QWindowsMobileStylePrivate &dd) : QWindowsStyle(dd) { + qApp->setEffectEnabled(Qt::UI_FadeMenu, false); + qApp->setEffectEnabled(Qt::UI_AnimateMenu, false); +} + +QWindowsMobileStyle::QWindowsMobileStyle() : QWindowsStyle(*new QWindowsMobileStylePrivate) { + qApp->setEffectEnabled(Qt::UI_FadeMenu, false); + qApp->setEffectEnabled(Qt::UI_AnimateMenu, false); +} + +QWindowsMobileStylePrivate::QWindowsMobileStylePrivate() :QWindowsStylePrivate() { + +#ifdef Q_WS_WINCE + doubleControls = qt_wince_is_high_dpi(); + smartphone = qt_wince_is_smartphone(); +#else + doubleControls = false; + smartphone = false; +#endif //Q_WS_WINCE + +#ifndef QT_NO_IMAGEFORMAT_XPM + + imageArrowDown = QImage(arrowdown_xpm); + imageArrowUp = QImage(arrowdown_xpm).mirrored(); + imageArrowLeft = QImage(arrowleft_xpm); + imageArrowRight = QImage(arrowleft_xpm).mirrored(true, false); + if (doubleControls) { + imageRadioButton = QImage(radiobutton_xpm); + imageRadioButtonChecked = QImage(radiochecked_xpm); + imageChecked = QImage(checkedlight_xpm); + imageCheckedBold = QImage(checkedbold_xpm); + imageRadioButtonHighlighted = QImage(highlightedradiobutton_xpm); + imageClose = QImage(cross_big_xpm); + imageMaximize = QImage(max_big_xpm); + imageMinimize = QImage(min_big_xpm); + imageNormalize = QImage(normal_big_xpm); + } else { + imageRadioButton = QImage(radiobutton_low_xpm); + imageRadioButtonChecked = QImage(radiochecked_low_xpm); + imageChecked = QImage(checkedlight_low_xpm); + imageCheckedBold = QImage(checkedbold_low_xpm); + imageRadioButtonHighlighted = QImage(highlightedradiobutton_low_xpm); + imageClose = QImage(cross_small_xpm); + imageMaximize = QImage(max_small_xpm); + imageMinimize = QImage(min_small_xpm); + imageNormalize = QImage(normal_small_xpm); + } + + setupWindowsMobileStyle65(); + + + imageArrowDownBig = QImage(arrowdown_big_xpm); + imageArrowUpBig = QImage(arrowdown_big_xpm).mirrored(); + imageArrowLeftBig = QImage(arrowleft_big_xpm); + imageArrowRightBig = QImage(arrowleft_big_xpm).mirrored(true, false); + +#endif +} + +void QWindowsMobileStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + bool doRestore = false; + QRect rect = option->rect; + painter->setClipping(false); + + switch (element) { + case PE_PanelButtonTool: { + int penSize = 1; + if (d->doubleControls) + penSize = 2; + if (widget) + if (QWidget *parent = widget->parentWidget()) +#ifndef QT_NO_TABWIDGET + if (qobject_cast<QTabWidget *>(parent->parentWidget())) { +#else + if (false) { +#endif //QT_NO_TABBAR + rect.adjust(0,2*penSize,0,-1*penSize); + qDrawPlainRect(painter, rect, option->palette.shadow().color(), penSize, &option->palette.light()); + if (option->state & (State_Sunken)) + qDrawPlainRect(painter, rect, option->palette.shadow().color(), penSize, &option->palette.shadow()); + } + else { + if (!(option->state & State_AutoRaise) || (option->state & (State_Sunken | State_On))) + qDrawPlainRect(painter,option->rect.adjusted(0, penSize, 0, -1 * penSize) , + option->palette.button().color(), 0, &option->palette.button()); + if (option->state & (State_Sunken)) { + qDrawPlainRect(painter, rect, option->palette.shadow().color(), penSize, &option->palette.light()); + } + if (option->state & (State_On)){ + QBrush fill = QBrush(option->palette.light().color()); + painter->fillRect(rect.adjusted(windowsItemFrame , windowsItemFrame , + -windowsItemFrame , -windowsItemFrame ), fill); + qDrawPlainRect(painter, rect, option->palette.shadow().color(), penSize, &option->palette.light()); + } + } + break; } + case PE_IndicatorButtonDropDown: + if (d->doubleControls) + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), 2, &option->palette.button()); + else + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), 1, &option->palette.button()); + break; +#ifndef QT_NO_TABBAR + case PE_IndicatorTabTear: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + bool rtl = tab->direction == Qt::RightToLeft; + QRect rect = tab->rect; + QPainterPath path; + rect.setTop(rect.top() + ((tab->state & State_Selected) ? 1 : 3)); + rect.setBottom(rect.bottom() - ((tab->state & State_Selected) ? 0 : 2)); + path.moveTo(QPoint(rtl ? rect.right() : rect.left(), rect.top())); + int count = 3; + for(int jags = 1; jags <= count; ++jags, rtl = !rtl) + path.lineTo(QPoint(rtl ? rect.left() : rect.right(), rect.top() + jags * rect.height()/count)); + painter->setPen(QPen(tab->palette.light(), qreal(.8))); + painter->setBrush(tab->palette.background()); + painter->setRenderHint(QPainter::Antialiasing); + painter->drawPath(path); + } + break; +#endif //QT_NO_TABBAR + +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarSeparator: { + painter->save(); + QPoint p1, p2; + if (option->state & State_Horizontal) { + p1 = QPoint(option->rect.width()/2, 0); + p2 = QPoint(p1.x(), option->rect.height()); + } else { + p1 = QPoint(0, option->rect.height()/2); + p2 = QPoint(option->rect.width(), p1.y()); + } + + + painter->setPen(option->palette.mid().color()); + if (d->doubleControls) { + QPen pen = painter->pen(); + pen.setWidth(2); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); + } + painter->drawLine(p1, p2); + painter->restore(); + break; } +#endif // QT_NO_TOOLBAR + case PE_IndicatorToolBarHandle: + painter->save(); + painter->translate(option->rect.x(), option->rect.y()); + if (option->state & State_Horizontal) { + int x = option->rect.width() / 2 - 4; + if (QApplication::layoutDirection() == Qt::RightToLeft) + x -= 2; + if (option->rect.height() > 4) { + qDrawWinButton(painter,x-1,0,7,option->rect.height(), option->palette, false, 0); + + qDrawShadePanel(painter, x, 1, 3, option->rect.height() - 1, + option->palette, false, 0); + qDrawShadePanel(painter, x + 3, 1, 3, option->rect.height() - 1, + option->palette, false, 0); + painter->setPen(option->palette.button().color()); + } + } else { + if (option->rect.width() > 4) { + int y = option->rect.height() / 2 - 4; + qDrawShadePanel(painter, 2, y, option->rect.width() - 2, 3, + option->palette, false, 0); + qDrawShadePanel(painter, 2, y + 3, option->rect.width() - 2, 3, + option->palette, false, 0); + } + } + painter->restore(); + break; + +#ifndef QT_NO_PROGRESSBAR + case PE_IndicatorProgressChunk: { + bool vertical = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) + vertical = (pb2->orientation == Qt::Vertical); + if (!vertical) { + painter->fillRect(option->rect.x(), option->rect.y()+2, option->rect.width(), option->rect.height()-4, + option->palette.brush(QPalette::Highlight)); + } else { + painter->fillRect(option->rect.x()+2, option->rect.y(), option->rect.width()-4, option->rect.height(), + option->palette.brush(QPalette::Highlight)); + } + } + break; +#endif // QT_NO_PROGRESSBAR + + case PE_FrameButtonTool: { +#ifndef QT_NO_DOCKWIDGET + if (widget && widget->inherits("QDockWidgetTitleButton")) { + if (const QDockWidget *dw = qobject_cast<const QDockWidget *>(widget->parent())) + if (dw->isFloating()){ + qDrawPlainRect(painter,option->rect.adjusted(1, 1, 0, 0), + option->palette.shadow().color(),1,&option->palette.button()); + return; + } + } +#endif // QT_NO_DOCKWIDGET + QBrush fill; + bool stippled; + bool panel = (element == PE_PanelButtonTool); + if ((!(option->state & State_Sunken )) + && (!(option->state & State_Enabled) + || ((option->state & State_Enabled ) && !(option->state & State_MouseOver))) + && (option->state & State_On)) { + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + stippled = true; + } else { + fill = option->palette.brush(QPalette::Button); + stippled = false; + } + if (option->state & (State_Raised | State_Sunken | State_On)) { + if (option->state & State_AutoRaise) { + if(option->state & (State_Enabled | State_Sunken | State_On)){ + if (panel) + qDrawPlainRect(painter, option->rect,option->palette.shadow().color(),d->doubleControls, &fill); + else + qDrawPlainRect(painter, option->rect,option->palette.shadow().color(),d->doubleControls, &fill); + } + if (stippled) { + painter->setPen(option->palette.button().color()); + painter->drawRect(option->rect.adjusted(1, 1, -2, -2)); + } + } else { + qDrawPlainRect(painter, option->rect,option->palette.shadow().color(),d->doubleControls, &fill); + } + } else { + painter->fillRect(option->rect, fill); + } + break; } + + case PE_FrameFocusRect: + if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(option)) { + //### check for d->alt_down + int penSize; + d->doubleControls ? penSize = 2 : penSize = 1; + bool alternateFocusStyle = false; + if (!widget) + alternateFocusStyle = true; +#ifndef QT_NO_COMBOBOX + if (qobject_cast<const QComboBox*>(widget)) + alternateFocusStyle = true; +#endif + if (!(fropt->state & State_KeyboardFocusChange) && !styleHint(SH_UnderlineShortcut, option)) + return; + QRect r = option->rect; + painter->save(); + painter->setBackgroundMode(Qt::TransparentMode); + if (alternateFocusStyle) { + QColor bg_col = fropt->backgroundColor; + if (!bg_col.isValid()) + bg_col = painter->background().color(); + // Create an "XOR" color. + QColor patternCol((bg_col.red() ^ 0xff) & 0xff, + (bg_col.green() ^ 0xff) & 0xff, + (bg_col.blue() ^ 0xff) & 0xff); + painter->setBrush(QBrush(patternCol, Qt::Dense4Pattern)); + painter->setBrushOrigin(r.topLeft()); + } + else { + painter->setPen(option->palette.highlight().color()); + painter->setBrush(option->palette.highlight()); + } + painter->setPen(Qt::NoPen); + painter->setBrushOrigin(r.topLeft()); + painter->drawRect(r.left(), r.top(), r.width(), penSize); // Top + painter->drawRect(r.left(), r.bottom(), r.width() + penSize - 1, penSize); // Bottom + painter->drawRect(r.left(), r.top(), penSize, r.height()); // Left + painter->drawRect(r.right(), r.top(), penSize, r.height()); // Right + painter->restore(); + } + break; + + case PE_PanelButtonBevel: { + QBrush fill; + bool panel = element != PE_FrameButtonBevel; + painter->setBrushOrigin(option->rect.topLeft()); + if (!(option->state & State_Sunken) && (option->state & State_On)) + fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + else + fill = option->palette.brush(QPalette::Button); + + if (option->state & (State_Raised | State_On | State_Sunken)) { + if (d->doubleControls) + qDrawPlainRect(painter, option->rect,option->palette.shadow().color(),2,&fill); + else + qDrawPlainRect(painter, option->rect,option->palette.shadow().color(),1,&fill); + } else { + if (panel) + painter->fillRect(option->rect, fill); + else + painter->drawRect(option->rect); + } + break; } + + case PE_FrameGroupBox: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + + const QStyleOptionFrameV2 *frame2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(option); + if (frame2 && !(frame2->features & QStyleOptionFrameV2::Flat)) { + QPen oldPen = painter->pen(); + QRect r = frame->rect; + painter->setPen(frame->palette.shadow().color()); + painter->fillRect(r.x(), r.y(), r.x() + r.width()-1, + r.y() + r.height() - windowsMobileFrameGroupBoxOffset, + frame->palette.light()); + painter ->drawLine(r.topLeft() + QPoint(-2, 1), r.topRight()+ QPoint(0, 1)); + if (d->doubleControls) + painter ->drawLine(r.topLeft() + QPoint(-2, 2), r.topRight()+ QPoint(0, 2)); + painter->setPen(oldPen); + } + } + break; + + case PE_IndicatorCheckBox: { + QBrush fill; + QRect r = d->doubleControls ? option->rect.adjusted(0,1,0,-1) : option->rect; + if (option->state & State_NoChange) + fill = QBrush(option->palette.shadow().color(), Qt::Dense4Pattern); + else if (option->state & State_Sunken) + fill = option->palette.button(); + else if (option->state & State_Enabled) + fill = option->palette.base(); + else + fill = option->palette.background(); + painter->save(); + doRestore = true; + if (d->doubleControls && (option->state & State_NoChange)) + painter->fillRect(r, fill); + else + painter->fillRect(option->rect, fill); + painter->setPen(option->palette.shadow().color()); + painter->drawLine(r.topLeft(), r.topRight()); + painter->drawLine(r.topRight(), r.bottomRight()); + painter->drawLine(r.bottomLeft(), r.bottomRight()); + painter->drawLine(r.bottomLeft(), r.topLeft()); + if (d->doubleControls) { + QRect r0 = r.adjusted(1, 1, -1, -1); + painter->drawLine(r0.topLeft(), r0.topRight()); + painter->drawLine(r0.topRight(), r0.bottomRight()); + painter->drawLine(r0.bottomLeft(), r0.bottomRight()); + painter->drawLine(r0.bottomLeft(), r0.topLeft()); + } + if (option->state & State_HasFocus) { + painter->setPen(option->palette.highlight().color()); + QRect r2 = d->doubleControls ? r.adjusted(2, 2, -2, -2) : r.adjusted(1, 1, -1, -1); + painter->drawLine(r2.topLeft(), r2.topRight()); + painter->drawLine(r2.topRight(), r2.bottomRight()); + painter->drawLine(r2.bottomLeft(), r2.bottomRight()); + painter->drawLine(r2.bottomLeft(), r2.topLeft()); + if (d->doubleControls) { + QRect r3 = r2.adjusted(1, 1, -1, -1); + painter->drawLine(r3.topLeft(), r3.topRight()); + painter->drawLine(r3.topRight(), r3.bottomRight()); + painter->drawLine(r3.bottomLeft(), r3.bottomRight()); + painter->drawLine(r3.bottomLeft(), r3.topLeft()); + } + painter->setPen(option->palette.shadow().color()); + } + //fall through... + } + case PE_IndicatorViewItemCheck: + case PE_Q3CheckListIndicator: { + if (!doRestore) { + painter->save(); + doRestore = true; + } + if (element == PE_Q3CheckListIndicator || element == PE_IndicatorViewItemCheck) { + painter->setPen(option->palette.shadow().color()); + if (option->state & State_NoChange) + painter->setBrush(option->palette.brush(QPalette::Button)); + if (d->doubleControls) { + QRect r = QRect(option->rect.x(), option->rect.y(), windowsMobileitemViewCheckBoxSize * 2, windowsMobileitemViewCheckBoxSize * 2); + qDrawPlainRect(painter, r, option->palette.shadow().color(), 2); + } else { + QRect r = QRect(option->rect.x(), option->rect.y(), windowsMobileitemViewCheckBoxSize, windowsMobileitemViewCheckBoxSize); + qDrawPlainRect(painter, r, option->palette.shadow().color(), 1); + } + if (option->state & State_Enabled) + d->imageChecked.setColor(1, option->palette.shadow().color().rgba()); + else + d->imageChecked.setColor(1, option->palette.dark().color().rgba()); + if (!(option->state & State_Off)) { + if (d->doubleControls) + painter->drawImage(option->rect.x(), option->rect.y(), d->imageChecked); + else + painter->drawImage(option->rect.x() + 3, option->rect.y() + 3, d->imageChecked); + } + } + else { + if (option->state & State_NoChange) + d->imageCheckedBold.setColor(1, option->palette.dark().color().rgba()); + else if (option->state & State_Enabled) + d->imageCheckedBold.setColor(1, option->palette.shadow().color().rgba()); + else + d->imageCheckedBold.setColor(1, option->palette.dark().color().rgba()); + if (!(option->state & State_Off)) { + if (d->doubleControls) + painter->drawImage(option->rect.x() + 2, option->rect.y(), d->imageCheckedBold); + else + painter->drawImage(option->rect.x() + 3, option->rect.y() + 3, d->imageCheckedBold); + } + } + if (doRestore) + painter->restore(); + break; } + case PE_IndicatorRadioButton: { + painter->save(); + + if (option->state & State_HasFocus) { + d->imageRadioButtonHighlighted.setColor(1, option->palette.shadow().color().rgba()); + d->imageRadioButtonHighlighted.setColor(2, option->palette.highlight().color().rgba()); + painter->drawImage(option->rect.x(), option->rect.y(), d->imageRadioButtonHighlighted); + } + else { + d->imageRadioButton.setColor(1, option->palette.shadow().color().rgba()); + painter->drawImage(option->rect.x(), option->rect.y(), d->imageRadioButton); + } + if (option->state & (State_Sunken | State_On)) { + if (option->state & State_Enabled) + d->imageRadioButtonChecked.setColor(1, option->palette.shadow().color().rgba()); + else + d->imageRadioButtonChecked.setColor(1, option->palette.dark().color().rgba()); + + static const int offset = d->doubleControls ? 6 : 3; + painter->drawImage(option->rect.x() + offset, option->rect.y() + offset, d->imageRadioButtonChecked); + } + painter->restore(); + break; } + case PE_PanelButtonCommand: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + QBrush fill; + State flags = option->state; + QPalette pal = option->palette; + QRect r = option->rect; + if ((flags & State_Sunken || flags & State_On) ) + fill = pal.brush(QPalette::Shadow); + else + fill = pal.brush(QPalette::Button); + int singleLine = 1; + int doubleLine = 2; + if (d->doubleControls) { + singleLine = 2; + doubleLine = 4; + } + if (button->features & QStyleOptionButton::DefaultButton && flags & State_Sunken) { + if (d->doubleControls) { + qDrawPlainRect(painter, r, pal.shadow().color(), 1, &fill); + qDrawPlainRect(painter, r.adjusted(1, 1, -1, 1), pal.shadow().color(), 1, &fill); + } + else { + qDrawPlainRect(painter, r, pal.shadow().color(), 1, &fill); + } + } else if (flags & (State_Raised | State_Sunken | State_On | State_Sunken)) { + qDrawPlainRect(painter, r, pal.shadow().color(), singleLine, &fill); + } else { + painter->fillRect(r, fill); + } + } + break; + case PE_FrameDefaultButton: { + painter->save(); + painter->setPen(option->palette.shadow().color()); + QRect rect = option->rect; + if (d->doubleControls) { + rect.adjust(1, 1, -2, -2); + painter->drawRect(rect); + painter->drawRect(rect.adjusted(1, 1, -1, -1)); + } + + else { + rect.adjust(2, 2, -3, -3); + painter->drawRect(rect); + } + painter->restore(); + break; } + case PE_IndicatorSpinPlus: + case PE_IndicatorSpinMinus: { + QRect r = option->rect; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget)+2; + QRect br = r.adjusted(fw, fw, -fw, -fw); + int offset = (option->state & State_Sunken) ? 1 : 0; + int step = (br.width() + 4) / 5; + painter->fillRect(br.x() + offset, br.y() + offset +br.height() / 2 - step / 2, + br.width(), step, option->palette.buttonText()); + if (element == PE_IndicatorSpinPlus) + painter->fillRect(br.x() + br.width() / 2 - step / 2 + offset, br.y() + offset+4, + step, br.height() - 7, option->palette.buttonText()); + break; } + case PE_IndicatorSpinUp: + case PE_IndicatorSpinDown: { + painter->save(); + QPoint points[7]; + switch (element) { + case PE_IndicatorSpinUp: + points[0] = QPoint(-2, -4); + points[1] = QPoint(-2, 2); + points[2] = QPoint(-1, -3); + points[3] = QPoint(-1, 1); + points[4] = QPoint(0, -2); + points[5] = QPoint(0, 0); + points[6] = QPoint(1, -1); + break; + case PE_IndicatorSpinDown: + points[0] = QPoint(0, -4); + points[1] = QPoint(0, 2); + points[2] = QPoint(-1, -3); + points[3] = QPoint(-1, 1); + points[4] = QPoint(-2, -2); + points[5] = QPoint(-2, 0); + points[6] = QPoint(-3, -1); + break; + default: + break; + } + if (option->state & State_Sunken) + painter->translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal), + proxy()->pixelMetric(PM_ButtonShiftVertical)); + if (option->state & State_Enabled) { + painter->translate(option->rect.x() + option->rect.width() / 2, + option->rect.y() + option->rect.height() / 2); + painter->setPen(option->palette.buttonText().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } else { + painter->translate(option->rect.x() + option->rect.width() / 2 + 1, + option->rect.y() + option->rect.height() / 2 + 1); + painter->setPen(option->palette.light().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + painter->translate(-1, -1); + painter->setPen(option->palette.mid().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } + painter->restore(); + break; } + + case PE_IndicatorArrowUpBig: + case PE_IndicatorArrowDownBig: + case PE_IndicatorArrowLeftBig: + case PE_IndicatorArrowRightBig: + + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: { + painter->save(); + + if (d->doubleControls) { + QColor color; + if (option->state & State_Sunken) + color = option->palette.light().color(); + else + color = option->palette.buttonText().color(); + QImage image; + int xoffset, yoffset; + bool isTabBarArrow = widget && widget->parent() + && widget->inherits("QToolButton") + && widget->parent()->inherits("QTabBar"); + + switch (element) { + case PE_IndicatorArrowUp: + image = d->imageArrowUp; + xoffset = 1; + yoffset = 12; + break; + case PE_IndicatorArrowDown: + image = d->imageArrowDown; + xoffset = 1; + yoffset =12; + break; + case PE_IndicatorArrowLeft: + image = d->imageArrowLeft; + xoffset = 8; + yoffset = isTabBarArrow ? 12 : 2; + break; + case PE_IndicatorArrowRight: + image = d->imageArrowRight; + xoffset = 8; + yoffset = isTabBarArrow ? 12 : 2; + break; + case PE_IndicatorArrowUpBig: + image = d->imageArrowUpBig; + xoffset = 3; + yoffset = 12; + break; + case PE_IndicatorArrowDownBig: + image = d->imageArrowDownBig; + xoffset = 2; + yoffset =12; + break; + case PE_IndicatorArrowLeftBig: + image = d->imageArrowLeftBig; + xoffset = 8; + yoffset = 2; + break; + case PE_IndicatorArrowRightBig: + image = d->imageArrowRightBig; + xoffset = 8; + yoffset = 2; + break; + default: + break; + } + image.setColor(1, color.rgba()); + painter->drawImage(option->rect.x() + xoffset, option->rect.y() + yoffset, image); + } + else { + QPoint points[7]; + switch (element) { + case PE_IndicatorArrowUp: + case PE_IndicatorArrowUpBig: + points[0] = QPoint(-3, 1); + points[1] = QPoint(3, 1); + points[2] = QPoint(-2, 0); + points[3] = QPoint(2, 0); + points[4] = QPoint(-1, -1); + points[5] = QPoint(1, -1); + points[6] = QPoint(0, -2); + break; + case PE_IndicatorArrowDown: + case PE_IndicatorArrowDownBig: + points[0] = QPoint(-3, -1); + points[1] = QPoint(3, -1); + points[2] = QPoint(-2, 0); + points[3] = QPoint(2, 0); + points[4] = QPoint(-1, 1); + points[5] = QPoint(1, 1); + points[6] = QPoint(0, 2); + break; + case PE_IndicatorArrowRight: + case PE_IndicatorArrowRightBig: + points[0] = QPoint(-2, -3); + points[1] = QPoint(-2, 3); + points[2] = QPoint(-1, -2); + points[3] = QPoint(-1, 2); + points[4] = QPoint(0, -1); + points[5] = QPoint(0, 1); + points[6] = QPoint(1, 0); + break; + case PE_IndicatorArrowLeft: + case PE_IndicatorArrowLeftBig: + points[0] = QPoint(0, -3); + points[1] = QPoint(0, 3); + points[2] = QPoint(-1, -2); + points[3] = QPoint(-1, 2); + points[4] = QPoint(-2, -1); + points[5] = QPoint(-2, 1); + points[6] = QPoint(-3, 0); + break; + default: + break; + } + if (option->state & State_Sunken) + painter->setPen(option->palette.light().color()); + else + painter->setPen(option->palette.buttonText().color()); + if (option->state & State_Enabled) { + painter->translate(option->rect.x() + option->rect.width() / 2, + option->rect.y() + option->rect.height() / 2 - 1); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } else { + painter->translate(option->rect.x() + option->rect.width() / 2, + option->rect.y() + option->rect.height() / 2 - 1); + painter->setPen(option->palette.mid().color()); + painter->drawLine(points[0], points[1]); + painter->drawLine(points[2], points[3]); + painter->drawLine(points[4], points[5]); + painter->drawPoint(points[6]); + } + } + painter->restore(); + break; } +#ifndef QT_NO_TABWIDGET + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) { + QRect rect = option->rect; + QPalette pal = option->palette; + painter->save(); + QBrush fill = pal.light(); + painter->fillRect(rect, fill); + painter->setPen(pal.shadow().color()); + if (d->doubleControls) { + QPen pen = painter->pen(); + pen.setWidth(2); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); + } + switch (tab->shape) { + case QTabBar::RoundedNorth: +#ifdef Q_WS_WINCE_WM + if (!d->wm65) +#endif + { + if (d->doubleControls) + painter->drawLine(rect.topLeft() + QPoint(0, 1), rect.topRight() + QPoint(0, 1)); + else + painter->drawLine(rect.topLeft(), rect.topRight()); + } + break; + case QTabBar::RoundedSouth: +#ifdef Q_WS_WINCE_WM + if (!d->wm65) +#endif + { + if (d->doubleControls) + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + else + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + } + break; + case QTabBar::RoundedEast: +#ifdef Q_WS_WINCE_WM + if (!d->wm65) +#endif + painter->drawLine(rect.topRight(), rect.bottomRight()); + break; + case QTabBar::RoundedWest: +#ifdef Q_WS_WINCE_WM + if (!d->wm65) +#endif + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + break; + case QTabBar::TriangularWest: + case QTabBar::TriangularEast: + case QTabBar::TriangularSouth: + case QTabBar::TriangularNorth: + if (d->doubleControls) + qDrawPlainRect(painter, rect.adjusted(0,-2,0,0), option->palette.shadow().color(),2,&pal.light()); + else + qDrawPlainRect(painter, rect, option->palette.shadow().color(),1,&pal.light()); + break; + default: + break; + } + painter->restore(); + } + break; +#endif //QT_NO_TABBAR +#ifndef QT_NO_ITEMVIEWS + case PE_PanelItemViewRow: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if ((vopt->state & QStyle::State_Selected) && proxy()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, option, widget)) + d->drawPanelItemViewSelected(painter, vopt); + else if (vopt->features & QStyleOptionViewItemV2::Alternate) + painter->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::AlternateBase)); + else if (!(vopt->state & QStyle::State_Enabled)) + painter->fillRect(vopt->rect, vopt->palette.brush(cg, QPalette::Base)); + } + break; + case PE_PanelItemViewItem: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(option)) { + QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if (vopt->showDecorationSelected && (vopt->state & QStyle::State_Selected)) { + d->drawPanelItemViewSelected(painter, vopt); + } else { + if (vopt->backgroundBrush.style() != Qt::NoBrush) { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(vopt->rect.topLeft()); + painter->fillRect(vopt->rect, vopt->backgroundBrush); + painter->setBrushOrigin(oldBO); + } + + if (vopt->state & QStyle::State_Selected) { + QRect textRect = proxy()->subElementRect(QStyle::SE_ItemViewItemText, option, widget); + d->drawPanelItemViewSelected(painter, vopt, textRect); + } + } + } + break; +#endif //QT_NO_ITEMVIEWS + + case PE_FrameWindow: { + QPalette popupPal = option->palette; + popupPal.setColor(QPalette::Light, option->palette.background().color()); + popupPal.setColor(QPalette::Midlight, option->palette.light().color()); + if (d->doubleControls) + qDrawPlainRect(painter, option->rect, popupPal.shadow().color(),2,0); + else + qDrawPlainRect(painter, option->rect, popupPal.shadow().color(),1,0); + break; } + case PE_FrameTabBarBase: { + break; } + case PE_Widget: + break; + case PE_IndicatorMenuCheckMark: { + int markW = option->rect.width() > 7 ? 7 : option->rect.width(); + int markH = markW; + if (d->doubleControls) + markW*=2; + markH*=2; + int posX = option->rect.x() + (option->rect.width() - markW)/2 + 1; + int posY = option->rect.y() + (option->rect.height() - markH)/2; + + QVector<QLineF> a; + a.reserve(markH); + + int i, xx, yy; + xx = posX; + yy = 3 + posY; + for (i = 0; i < markW/2; ++i) { + a << QLineF(xx, yy, xx, yy + 2); + ++xx; + ++yy; + } + yy -= 2; + for (; i < markH; ++i) { + a << QLineF(xx, yy, xx, yy + 2); + ++xx; + --yy; + } + if (!(option->state & State_Enabled) && !(option->state & State_On)) { + int pnt; + painter->setPen(option->palette.highlightedText().color()); + QPoint offset(1, 1); + for (pnt = 0; pnt < a.size(); ++pnt) + a[pnt].translate(offset.x(), offset.y()); + painter->drawLines(a); + for (pnt = 0; pnt < a.size(); ++pnt) + a[pnt].translate(offset.x(), offset.y()); + } + painter->setPen(option->palette.text().color()); + painter->drawLines(a); + break; } + case PE_IndicatorBranch: { + // Copied from the Windows style. + static const int decoration_size = d->doubleControls ? 18 : 9; + static const int ofsA = d->doubleControls ? 4 : 2; + static const int ofsB = d->doubleControls ? 8 : 4; + static const int ofsC = d->doubleControls ? 12 : 6; + static const int ofsD = d->doubleControls ? 1 : 0; + 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; + bef_h -= delta; + bef_v -= delta; + aft_h += delta; + aft_v += delta; + QPen oldPen = painter->pen(); + QPen crossPen = oldPen; + crossPen.setWidth(2); + painter->setPen(crossPen); + painter->drawLine(bef_h + ofsA + ofsD, bef_v + ofsB + ofsD, bef_h + ofsC + ofsD, bef_v + ofsB + ofsD); + if (!(option->state & State_Open)) + painter->drawLine(bef_h + ofsB + ofsD, bef_v + ofsA + ofsD, bef_h + ofsB + ofsD, bef_v + ofsC + ofsD); + painter->setPen(option->palette.dark().color()); + painter->drawRect(bef_h, bef_v, decoration_size - 1, decoration_size - 1); + if (d->doubleControls) + painter->drawRect(bef_h + 1, bef_v + 1, decoration_size - 3, decoration_size - 3); + painter->setPen(oldPen); + } + 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) + 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)) + painter->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush); + break; } + case PE_Frame: + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), + d->doubleControls ? 2 : 1, &option->palette.background()); + break; + case PE_FrameLineEdit: + case PE_FrameMenu: + if (d->doubleControls) + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(),2); + else + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(),1); + break; + case PE_FrameStatusBar: + if (d->doubleControls) + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(),2,0); + else + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(),1,0); + break; + + default: + QWindowsStyle::drawPrimitive(element, option, painter, widget); + break; + } +} + +void QWindowsMobileStyle::drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const { + + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + + painter->setClipping(false); + switch (element) { + case CE_MenuBarEmptyArea: + painter->setClipping(true); + QWindowsStyle::drawControl(element, option, painter, widget); + break; + case CE_PushButtonBevel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + QRect br = button->rect; + int dbi = proxy()->pixelMetric(PM_ButtonDefaultIndicator, button, widget); + + if (button->features & QStyleOptionButton::AutoDefaultButton) + br.setCoords(br.left() + dbi, br.top() + dbi, br.right() - dbi, br.bottom() - dbi); + QStyleOptionButton tmpBtn = *button; + tmpBtn.rect = br; + proxy()->drawPrimitive(PE_PanelButtonCommand, &tmpBtn, painter, widget); + if (button->features & QStyleOptionButton::HasMenu) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget); + QRect ir = button->rect; + QStyleOptionButton newButton = *button; + if (d->doubleControls) + newButton.rect = QRect(ir.right() - mbi, ir.height() - 30, mbi, ir.height() - 4); + else + newButton.rect = QRect(ir.right() - mbi, ir.height() - 20, mbi, ir.height() - 4); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newButton, painter, widget); + } + if (button->features & QStyleOptionButton::DefaultButton) + proxy()->drawPrimitive(PE_FrameDefaultButton, option, painter, widget); + } + break; + case CE_RadioButton: + case CE_CheckBox: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool isRadio = (element == CE_RadioButton); + QStyleOptionButton subopt = *button; + subopt.rect = proxy()->subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, button, widget); + proxy()->drawPrimitive(isRadio ? PE_IndicatorRadioButton : PE_IndicatorCheckBox, + &subopt, painter, widget); + subopt.rect = proxy()->subElementRect(isRadio ? SE_RadioButtonContents + : SE_CheckBoxContents, button, widget); + proxy()->drawControl(isRadio ? CE_RadioButtonLabel : CE_CheckBoxLabel, &subopt, painter, widget); + if (button->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*button); + fropt.rect = proxy()->subElementRect(isRadio ? SE_RadioButtonFocusRect + : SE_CheckBoxFocusRect, button, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + break; + case CE_RadioButtonLabel: + case CE_CheckBoxLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + uint alignment = visualAlignment(button->direction, Qt::AlignLeft | Qt::AlignVCenter); + if (!styleHint(SH_UnderlineShortcut, button, widget)) + alignment |= Qt::TextHideMnemonic; + QPixmap pix; + QRect textRect = button->rect; + if (!button->icon.isNull()) { + pix = button->icon.pixmap(button->iconSize, button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled); + proxy()->drawItemPixmap(painter, button->rect, alignment, pix); + if (button->direction == Qt::RightToLeft) + textRect.setRight(textRect.right() - button->iconSize.width() - 4); + else + textRect.setLeft(textRect.left() + button->iconSize.width() + 4); + } + if (!button->text.isEmpty()){ + if (button->state & State_Enabled) + proxy()->drawItemText(painter, textRect, alignment | Qt::TextShowMnemonic, + button->palette, false, button->text, QPalette::WindowText); + else + proxy()->drawItemText(painter, textRect, alignment | Qt::TextShowMnemonic, + button->palette, false, button->text, QPalette::Mid); + } + } + break; +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarGroove: + if (d->doubleControls) + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), 2, &option->palette.brush(QPalette::Window)); + else + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), 1, &option->palette.brush(QPalette::Window)); + break; +#endif //QT_NO_PROGRESSBAR +#ifndef QT_NO_TABBAR + case CE_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + proxy()->drawControl(CE_TabBarTabShape, tab, painter, widget); + proxy()->drawControl(CE_TabBarTabLabel, tab, painter, widget); + } + break; + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + + if (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedEast || + tab->shape == QTabBar::RoundedSouth || tab->shape == QTabBar::RoundedWest) { + d->drawTabBarTab(painter, tab); + } else { + QCommonStyle::drawControl(element, option, painter, widget); + } + break; } + +#endif // QT_NO_TABBAR + +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + if (const QStyleOptionToolBar *toolBar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) { + QRect rect = option->rect; + painter->save(); + painter->setPen(option->palette.dark().color()); + painter->fillRect(rect,option->palette.button()); + if (d->doubleControls) { + QPen pen = painter->pen(); + pen.setWidth(4); + painter->setPen(pen); + } + if (toolBar->toolBarArea == Qt::TopToolBarArea) + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + else + painter->drawLine(rect.topLeft(), rect.topRight()); + painter->restore(); + break; } +#endif //QT_NO_TOOLBAR + case CE_Header: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + QRegion clipRegion = painter->clipRegion(); + painter->setClipRect(option->rect); + proxy()->drawControl(CE_HeaderSection, header, painter, widget); + QStyleOptionHeader subopt = *header; + subopt.rect = proxy()->subElementRect(SE_HeaderLabel, header, widget); + if (header->state & State_Sunken) + subopt.palette.setColor(QPalette::ButtonText, header->palette.brightText().color()); + subopt.state |= QStyle::State_On; + if (subopt.rect.isValid()) + proxy()->drawControl(CE_HeaderLabel, &subopt, painter, widget); + if (header->sortIndicator != QStyleOptionHeader::None) { + subopt.rect = proxy()->subElementRect(SE_HeaderArrow, option, widget); + proxy()->drawPrimitive(PE_IndicatorHeaderArrow, &subopt, painter, widget); + } + painter->setClipRegion(clipRegion); + } + break; + + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + QBrush fill; + QColor color; + QRect rect = option->rect; + painter->setPen(option->palette.shadow().color()); + + int penSize = 1; + + if (d->doubleControls) { + penSize = 2; + QPen pen = painter->pen(); + pen.setWidth(2); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); + } + + //fix Frame + + if (header->position == QStyleOptionHeader::End + || (header->position == QStyleOptionHeader::OnlyOneSection + && !header->text.isEmpty())) + if (Qt::Horizontal == header->orientation ) + rect.adjust(0, 0, penSize, 0); + else + rect.adjust(0, 0, 0, penSize); + + if (option->state & State_Sunken) { + fill = option->palette.brush(QPalette::Shadow); + color = option->palette.light().color(); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->drawLine(rect.topRight(), rect.bottomRight()); + rect.adjust(0, 0, -penSize, -penSize); + } + else { + fill = option->palette.brush(QPalette::Button); + color = option->palette.shadow().color(); + if (Qt::Horizontal == header->orientation ) + rect.adjust(-penSize, 0, 0, 0); + else + rect.adjust(0, -penSize, 0, 0); + } + if (Qt::Horizontal == header->orientation ) + rect.adjust(0,-penSize,0,0); + else + rect.adjust(-penSize, 0, 0, 0); + + if (option->state & State_Sunken) { + qDrawPlainRect(painter, rect, color, penSize, &fill); + } else { + //Corner + rect.adjust(-penSize, 0, 0, 0); + qDrawPlainRect(painter, rect, color, penSize, &fill); + } + + //Hack to get rid of some double lines... StyleOptions need a clean flag for that + rect = option->rect; +#ifndef QT_NO_SCROLLAREA + if (const QAbstractScrollArea *abstractScrollArea = qobject_cast<const QAbstractScrollArea *> (widget) ) { + QRect rectScrollArea = abstractScrollArea->geometry(); + if (Qt::Horizontal == header->orientation ) + if ((rectScrollArea.right() - rect.right() ) > 1) + painter->drawLine(rect.topRight(), rect.bottomRight()); + else ; + else + if ((rectScrollArea.bottom() - rect.bottom() ) > 1) + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + } +#endif // QT_NO_SCROLLAREA + break; + } +#ifndef QT_NO_COMBOBOX + case CE_ComboBoxLabel: + // This is copied from qcommonstyle.cpp with the difference, that + // the editRect isn't adjusted when calling drawItemText. + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget); + painter->save(); + painter->setClipRect(editRect); + if (!cb->currentIcon.isNull()) { + QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode); + QRect iconRect(editRect); + iconRect.setWidth(cb->iconSize.width() + 4); + iconRect = alignedRect(cb->direction, + Qt::AlignLeft | Qt::AlignVCenter, + iconRect.size(), editRect); + if (cb->editable) + painter->fillRect(iconRect, option->palette.brush(QPalette::Base)); + proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pixmap); + + if (cb->direction == Qt::RightToLeft) + editRect.translate(-4 - cb->iconSize.width(), 0); + else + editRect.translate(cb->iconSize.width() + 4, 0); + } + if (!cb->currentText.isEmpty() && !cb->editable) { + proxy()->drawItemText(painter, editRect, + visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter), + cb->palette, cb->state & State_Enabled, cb->currentText); + } + painter->restore(); + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(option); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + QRect rect = dwOpt->rect; + QRect r = rect; + + if (verticalTitleBar) { + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + painter->save(); + painter->translate(r.left(), r.top() + r.width()); + painter->rotate(-90); + painter->translate(-r.left(), -r.top()); + } + + bool floating = false; + bool active = dwOpt->state & State_Active; + int menuOffset = 0; //used to center text when floated + QColor inactiveCaptionTextColor = option->palette.highlightedText().color(); + if (dwOpt->movable) { + QColor left, right; + + //Titlebar gradient + if (widget && widget->isWindow()) { + floating = true; + if (active) { + right = option->palette.highlight().color(); + left = right.lighter(125); + } else { + left = option->palette.highlight().color().lighter(125); + right = QColor(0xff, 0xff, 0xff); + } + menuOffset = 2; + QBrush fillBrush(left); + if (left != right) { + QPoint p1(r.x(), r.top() + r.height()/2); + QPoint p2(rect.right(), r.top() + r.height()/2); + QLinearGradient lg(p1, p2); + lg.setColorAt(0, left); + lg.setColorAt(1, right); + fillBrush = lg; + } + painter->fillRect(r.adjusted(0, 0, 0, -3), fillBrush); + } else { + painter->fillRect(r.adjusted(0, 0, 0, -3), option->palette.button().color()); + } + painter->setPen(dwOpt->palette.color(QPalette::Light)); + if (!widget || !widget->isWindow()) { + painter->drawLine(r.topLeft(), r.topRight()); + painter->setPen(dwOpt->palette.color(QPalette::Dark)); + painter->drawLine(r.bottomLeft(), r.bottomRight()); } + } + if (!dwOpt->title.isEmpty()) { + QFont oldFont = painter->font(); + QFont newFont = oldFont; + if (newFont.pointSize() > 2) + newFont.setPointSize(newFont.pointSize() - 2); + if (floating) + newFont.setBold(true); + painter->setFont(newFont); + QPalette palette = dwOpt->palette; + palette.setColor(QPalette::Window, inactiveCaptionTextColor); + QRect titleRect = proxy()->subElementRect(SE_DockWidgetTitleBarText, option, widget); + if (verticalTitleBar) { + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + } + proxy()->drawItemText(painter, titleRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, palette, + dwOpt->state & State_Enabled, dwOpt->title, + floating ? (active ? QPalette::BrightText : QPalette::Window) : QPalette::WindowText); + painter->setFont(oldFont); + } + if (verticalTitleBar) + painter->restore(); + } + return; +#endif // QT_NO_DOCKWIDGET + + case CE_PushButtonLabel: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + painter->save(); + QRect ir = button->rect; + QPalette::ColorRole colorRole; + uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic; + if (!styleHint(SH_UnderlineShortcut, button, widget)) + tf |= Qt::TextHideMnemonic; + + if (button->state & (State_On | State_Sunken)) + colorRole = QPalette::Light; + else + colorRole = QPalette::ButtonText; + + if (!button->icon.isNull()) { + QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal + : QIcon::Disabled; + if (mode == QIcon::Normal && button->state & State_HasFocus) + mode = QIcon::Active; + QIcon::State state = QIcon::Off; + if (button->state & State_On) + state = QIcon::On; + QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + //Center the icon if there is no text + + QPoint point; + if (button->text.isEmpty()) { + point = QPoint(ir.x() + ir.width() / 2 - pixw / 2, + ir.y() + ir.height() / 2 - pixh / 2); + } else { + point = QPoint(ir.x() + 2, ir.y() + ir.height() / 2 - pixh / 2); + } + if (button->direction == Qt::RightToLeft) + point.rx() += pixw; + + if ((button->state & (State_On | State_Sunken)) && button->direction == Qt::RightToLeft) + point.rx() -= proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget) * 2; + + painter->drawPixmap(visualPos(button->direction, button->rect, point), pixmap); + + if (button->direction == Qt::RightToLeft) + ir.translate(-4, 0); + else + ir.translate(pixw + 4, 0); + ir.setWidth(ir.width() - (pixw + 4)); + // left-align text if there is + if (!button->text.isEmpty()) + tf |= Qt::AlignLeft; + } else { + tf |= Qt::AlignHCenter; + } + if (button->state & State_Enabled) + proxy()->drawItemText(painter, ir, tf, button->palette, true, button->text, colorRole); + else + proxy()->drawItemText(painter, ir, tf, button->palette, true, button->text, QPalette::Mid); + painter->restore(); + } + break; + default: + QWindowsStyle::drawControl(element, option, painter, widget); + break; + } +} + +void QWindowsMobileStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const { + + painter->setClipping(false); + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + switch (control) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + int ticks = slider->tickPosition; + QRect groove = proxy()->subControlRect(CC_Slider, slider, SC_SliderGroove, widget); + QRect handle = proxy()->subControlRect(CC_Slider, slider, SC_SliderHandle, widget); + + if ((slider->subControls & SC_SliderGroove) && groove.isValid()) { + int mid = thickness / 2; + if (ticks & QSlider::TicksAbove) + mid += len / 8; + if (ticks & QSlider::TicksBelow) + mid -= len / 8; + + painter->setPen(slider->palette.shadow().color()); + if (slider->orientation == Qt::Horizontal) { + qDrawPlainRect(painter, groove.x(), groove.y() + mid - 2, + groove.width(), 4, option->palette.shadow().color(),1,0); + } else { + qDrawPlainRect(painter, groove.x()+mid-2, groove.y(), + 4, groove.height(), option->palette.shadow().color(),1,0); + } + } + if (slider->subControls & SC_SliderTickmarks) { + QStyleOptionSlider tmpSlider = *slider; + tmpSlider.subControls = SC_SliderTickmarks; + QCommonStyle::drawComplexControl(control, &tmpSlider, painter, widget); + } + + if (slider->subControls & SC_SliderHandle) { + const QColor c0 = slider->palette.shadow().color(); + const QColor c1 = slider->palette.dark().color(); + const QColor c3 = slider->palette.midlight().color(); + const QColor c4 = slider->palette.dark().color(); + QBrush handleBrush; + + if (slider->state & State_Enabled) { + handleBrush = slider->palette.color(QPalette::Light); + } else { + handleBrush = QBrush(slider->palette.color(QPalette::Shadow), + Qt::Dense4Pattern); + } + int x = handle.x(), y = handle.y(), + wi = handle.width(), he = handle.height(); + int x1 = x; + int x2 = x+wi-1; + int y1 = y; + int y2 = y+he-1; + + Qt::Orientation orient = slider->orientation; + bool tickAbove = slider->tickPosition == QSlider::TicksAbove; + bool tickBelow = slider->tickPosition == QSlider::TicksBelow; + + if (slider->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*slider); + fropt.rect = proxy()->subElementRect(SE_SliderFocusRect, slider, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + if ((tickAbove && tickBelow) || (!tickAbove && !tickBelow)) { + Qt::BGMode oldMode = painter->backgroundMode(); + painter->setBackgroundMode(Qt::OpaqueMode); + qDrawPlainRect(painter, QRect(x, y, wi, he) + ,slider->palette.shadow().color(),1,&handleBrush); + painter->setBackgroundMode(oldMode); + QBrush fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern); + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 + 2, y1 + 2, x2 - x1 - 3, y2 - y1 - 3),fill); + return; + } + QSliderDirection dir; + if (orient == Qt::Horizontal) + if (tickAbove) + dir = SliderUp; + else + dir = SliderDown; + else + if (tickAbove) + dir = SliderLeft; + else + dir = SliderRight; + QPolygon polygon; + int d = 0; + switch (dir) { + case SliderUp: + x2++; + y1 = y1 + wi / 2; + d = (wi + 1) / 2 - 1; + polygon.setPoints(5, x1, y1, x1, y2, x2, y2, x2, y1, x1 + d,y1 - d); + break; + case SliderDown: + x2++; + y2 = y2 - wi/2; + d = (wi + 1) / 2 - 1; + polygon.setPoints(5, x1, y1, x1, y2, x1 + d,y2 + d, x2, y2, x2, y1); + break; + case SliderLeft: + d = (he + 1) / 2 - 1; + x1 = x1 + he/2; + polygon.setPoints(5, x1, y1, x1 - d, y1 + d, x1,y2, x2, y2, x2, y1); + y1--; + break; + case SliderRight: + d = (he + 1) / 2 - 1; + x2 = x2 - he/2; + polygon.setPoints(5, x1, y1, x1, y2, x2,y2, x2 + d, y1 + d, x2, y1); + y1--; + break; + } + QBrush oldBrush = painter->brush(); + painter->setPen(Qt::NoPen); + painter->setBrush(handleBrush); + Qt::BGMode oldMode = painter->backgroundMode(); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawRect(x1, y1, x2-x1+1, y2-y1+1); + painter->drawPolygon(polygon); + QBrush fill = QBrush(option->palette.button().color(), Qt::Dense4Pattern); + painter->setBrush(oldBrush); + painter->setBackgroundMode(oldMode); + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1, y1, x2 - x1 + 1, y2 - y1 + 1),fill); + + if (dir != SliderUp) { + painter->setPen(c0); + painter->drawLine(x1, y1, x2, y1); + } + if (dir != SliderLeft) { + painter->setPen(c0); + painter->drawLine(x1, y1, x1, y2); + } + if (dir != SliderRight) { + painter->setPen(c0); + painter->drawLine(x2, y1, x2, y2); + } + if (dir != SliderDown) { + painter->setPen(c0); + painter->drawLine(x1, y2, x2, y2); + } + switch (dir) { + case SliderUp: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 + 3, y1 - d + 2, x2 - x1 - 4, y1),fill); + painter->setPen(c0); + painter->drawLine(x1, y1, x1 + d, y1 - d); + d = wi - d - 1; + painter->drawLine(x2, y1, x2 -d , y1 -d ); + d--; + break; + case SliderDown: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1+3, y2 - d, x2 - x1 -4,y2 - 8),fill); + painter->setPen(c0); + painter->drawLine(x1, y2, x1 + d, y2 + d); + d = wi - d - 1; + painter->drawLine(x2, y2, x2 - d, y2 + d); + d--; + break; + case SliderLeft: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x1 - d + 2, y1 + 2, x1, y2 - y1 - 3),fill); + painter->setPen(c0); + painter->drawLine(x1, y1, x1 - d, y1 + d); + d = he - d - 1; + painter->drawLine(x1, y2, x1 - d, y2 - d); + d--; + break; + case SliderRight: + if (slider->state & State_Sunken) + painter->fillRect(QRectF(x2 - d - 4, y1 + 2, x2 - 4, y2 - y1 - 3),fill); + painter->setPen(c0); + painter->drawLine(x2, y1, x2 + d, y1 + d); + painter->setPen(c0); + d = he - d - 1; + painter->drawLine(x2, y2, x2 + d, y2 - d); + d--; + break; + } + } + } + break; +#endif //QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + painter->save(); + painter->setPen(option->palette.shadow().color()); + if (d->doubleControls) { + QPen pen = painter->pen(); + pen.setWidth(2); + pen.setCapStyle(Qt::SquareCap); + painter->setPen(pen); + } + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + d->drawScrollbarGroove(painter, scrollbar); + // Make a copy here and reset it for each primitive. + QStyleOptionSlider newScrollbar = *scrollbar; + State saveFlags = scrollbar->state; + //Check if the scrollbar is part of an abstractItemView and draw the frame according + bool drawCompleteFrame = true; + bool secondScrollBar = false; + if (widget) + if (QWidget *parent = widget->parentWidget()) { + if (QAbstractScrollArea *abstractScrollArea = qobject_cast<QAbstractScrollArea *>(parent->parentWidget())) { + drawCompleteFrame = (abstractScrollArea->frameStyle() == QFrame::NoFrame) || (abstractScrollArea->frameStyle() == QFrame::StyledPanel); + secondScrollBar = (abstractScrollArea->horizontalScrollBar()->isVisible() + && abstractScrollArea->verticalScrollBar()->isVisible()) ; + } +#ifndef QT_NO_LISTVIEW + if (QListView *listView = qobject_cast<QListView *>(parent->parentWidget())) + drawCompleteFrame = false; +#endif + } + if (scrollbar->minimum == scrollbar->maximum) + saveFlags |= State_Enabled; + if (scrollbar->subControls & SC_ScrollBarSubLine) { + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(control, &newScrollbar, SC_ScrollBarSubLine, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarSubLine)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + d->drawScrollbarHandleUp(painter, &newScrollbar, drawCompleteFrame, secondScrollBar); + } + } + if (scrollbar->subControls & SC_ScrollBarAddLine) { + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(control, &newScrollbar, SC_ScrollBarAddLine, widget); + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarAddLine)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + d->drawScrollbarHandleDown(painter, &newScrollbar, drawCompleteFrame, secondScrollBar); + } + } + if (scrollbar->subControls & SC_ScrollBarSlider) { + + newScrollbar.rect = scrollbar->rect; + newScrollbar.state = saveFlags; + newScrollbar.rect = proxy()->subControlRect(control, &newScrollbar, SC_ScrollBarSlider, widget); + + if (newScrollbar.rect.isValid()) { + if (!(scrollbar->activeSubControls & SC_ScrollBarSlider)) + newScrollbar.state &= ~(State_Sunken | State_MouseOver); + d->drawScrollbarGrip(painter, &newScrollbar, option, drawCompleteFrame); + } + } + } + painter->restore(); + break; +#endif // QT_NO_SCROLLBAR + case CC_ToolButton: + if (const QStyleOptionToolButton *toolbutton + = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + QRect button, menuarea; + bool isTabWidget = false; +#ifndef QT_NO_TABWIDGET + if (widget) + if (QWidget *parent = widget->parentWidget()) + isTabWidget = (qobject_cast<QTabWidget *>(parent->parentWidget())); +#endif //QT_NO_TABWIDGET + + button = proxy()->subControlRect(control, toolbutton, SC_ToolButton, widget); + menuarea = proxy()->subControlRect(control, toolbutton, SC_ToolButtonMenu, widget); + State buttonFlags = toolbutton->state; + if (buttonFlags & State_AutoRaise) { + if (!(buttonFlags & State_MouseOver)) { + buttonFlags &= ~State_Raised; + } + } + State menuFlags = buttonFlags; + if (toolbutton->activeSubControls & SC_ToolButton) + buttonFlags |= State_Sunken; + if (toolbutton->activeSubControls & SC_ToolButtonMenu) + menuFlags |= State_On; + QStyleOption tool(0); + tool.palette = toolbutton->palette; + if (toolbutton->subControls & SC_ToolButton) { + tool.rect = button; + tool.state = buttonFlags; + proxy()->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget); + } + if (toolbutton->subControls & SC_ToolButtonMenu) { + tool.rect = menuarea; + tool.state = buttonFlags & State_Enabled; + QStyleOption toolMenu(0); + toolMenu = *toolbutton; + toolMenu.state = menuFlags; + if (buttonFlags & State_Sunken) + proxy()->drawPrimitive(PE_PanelButtonTool, &toolMenu, painter, widget); + QStyleOption arrowOpt(0); + arrowOpt.rect = tool.rect; + arrowOpt.palette = tool.palette; + State flags = State_None; + if (menuFlags & State_Enabled) + flags |= State_Enabled; + if ((menuFlags & State_On) && !(buttonFlags & State_Sunken)) { + flags |= State_Sunken; + painter->fillRect(menuarea, option->palette.shadow()); + } + arrowOpt.state = flags; + proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, painter, widget); + } + if (toolbutton->state & State_HasFocus) { + QStyleOptionFocusRect focusRect; + focusRect.QStyleOption::operator=(*toolbutton); + focusRect.rect.adjust(3, 3, -3, -3); + if (toolbutton->features & QStyleOptionToolButton::Menu) + focusRect.rect.adjust(0, 0, -proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator, + toolbutton, widget), 0); + proxy()->drawPrimitive(PE_FrameFocusRect, &focusRect, painter, widget); + } + QStyleOptionToolButton label = *toolbutton; + if (isTabWidget) + label.state = toolbutton->state; + else + label.state = toolbutton->state & State_Enabled; + int fw = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + label.rect = button.adjusted(fw, fw, -fw, -fw); + proxy()->drawControl(CE_ToolButtonLabel, &label, painter, widget); + } + break; + +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + // Draw frame + painter->save(); + QFont font = painter->font(); + font.setBold(true); + painter->setFont(font); + QStyleOptionGroupBox groupBoxFont = *groupBox; + groupBoxFont.fontMetrics = QFontMetrics(font); + QRect textRect = proxy()->subControlRect(CC_GroupBox, &groupBoxFont, SC_GroupBoxLabel, widget); + QRect checkBoxRect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxCheckBox, widget).adjusted(0,0,0,0); + if (groupBox->subControls & QStyle::SC_GroupBoxFrame) { + QStyleOptionFrameV2 frame; + frame.QStyleOption::operator=(*groupBox); + frame.features = groupBox->features; + frame.lineWidth = groupBox->lineWidth; + frame.midLineWidth = groupBox->midLineWidth; + frame.rect = proxy()->subControlRect(CC_GroupBox, option, SC_GroupBoxFrame, widget); + painter->save(); + QRegion region(groupBox->rect); + if (!groupBox->text.isEmpty()) { + bool ltr = groupBox->direction == Qt::LeftToRight; + QRect finalRect = checkBoxRect.united(textRect); + if (groupBox->subControls & QStyle::SC_GroupBoxCheckBox) + finalRect.adjust(ltr ? -4 : 0, 0, ltr ? 0 : 4, 0); + region -= finalRect; + } + proxy()->drawPrimitive(PE_FrameGroupBox, &frame, painter, widget); + painter->restore(); + } + // Draw checkbox + if (groupBox->subControls & SC_GroupBoxCheckBox) { + QStyleOptionButton box; + box.QStyleOption::operator=(*groupBox); + box.rect = checkBoxRect; + proxy()->drawPrimitive(PE_IndicatorCheckBox, &box, painter, widget); + } + // Draw title + if ((groupBox->subControls & QStyle::SC_GroupBoxLabel) && !groupBox->text.isEmpty()) { + QColor textColor = groupBox->textColor; + if (textColor.isValid()) + painter->setPen(textColor); + else + painter->setPen(groupBox->palette.link().color()); + painter->setPen(groupBox->palette.link().color()); + + int alignment = int(groupBox->textAlignment); + if (!styleHint(QStyle::SH_UnderlineShortcut, option, widget)) + alignment |= Qt::TextHideMnemonic; + + if (groupBox->state & State_Enabled) + proxy()->drawItemText(painter, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment, + groupBox->palette, true, groupBox->text, + textColor.isValid() ? QPalette::NoRole : QPalette::Link); + else + proxy()->drawItemText(painter, textRect, Qt::TextShowMnemonic | Qt::AlignHCenter | alignment, + groupBox->palette, true, groupBox->text, QPalette::Mid); + if (groupBox->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*groupBox); + fropt.rect = textRect; + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); + } + } + painter->restore(); + } + break; +#endif //QT_NO_GROUPBOX + +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + QBrush editBrush = cmb->palette.brush(QPalette::Base); + if ((cmb->subControls & SC_ComboBoxFrame) && cmb->frame) + qDrawPlainRect(painter, option->rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_ComboBoxFrameWidth, option, widget), &editBrush); + else + painter->fillRect(option->rect, editBrush); + State flags = State_None; + QRect ar = proxy()->subControlRect(CC_ComboBox, cmb, SC_ComboBoxArrow, widget); + if ((option->state & State_On)) { + painter->fillRect(ar.adjusted(0, 0, 1, 1),cmb->palette.brush(QPalette::Shadow)); + } + if (d->doubleControls) + ar.adjust(5, 0, 5, 0); + else + ar.adjust(2, 0, -2, 0); + if (option->state & State_Enabled) + flags |= State_Enabled; + if (option->state & State_On) + flags |= State_Sunken; + QStyleOption arrowOpt(0); + arrowOpt.rect = ar; + arrowOpt.palette = cmb->palette; + arrowOpt.state = flags; + proxy()->drawPrimitive(PrimitiveElement(PE_IndicatorArrowDownBig), &arrowOpt, painter, widget); + if (cmb->subControls & SC_ComboBoxEditField) { + QRect re = proxy()->subControlRect(CC_ComboBox, cmb, SC_ComboBoxEditField, widget); + if (cmb->state & State_HasFocus && !cmb->editable) + painter->fillRect(re.x(), re.y(), re.width(), re.height(), + cmb->palette.brush(QPalette::Highlight)); + if (cmb->state & State_HasFocus) { + painter->setPen(cmb->palette.highlightedText().color()); + painter->setBackground(cmb->palette.highlight()); + } else { + painter->setPen(cmb->palette.text().color()); + painter->setBackground(cmb->palette.background()); + } + if (cmb->state & State_HasFocus && !cmb->editable) { + QStyleOptionFocusRect focus; + focus.QStyleOption::operator=(*cmb); + focus.rect = proxy()->subElementRect(SE_ComboBoxFocusRect, cmb, widget); + focus.state |= State_FocusAtBorder; + focus.backgroundColor = cmb->palette.highlight().color(); + if ((option->state & State_On)) + proxy()->drawPrimitive(PE_FrameFocusRect, &focus, painter, widget); + } + } + } + break; +#endif // QT_NO_COMBOBOX + + +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + QStyleOptionSpinBox copy = *spinBox; + //PrimitiveElement primitiveElement; + int primitiveElement; + + if (spinBox->frame && (spinBox->subControls & SC_SpinBoxFrame)) { + QRect r = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxFrame, widget); + qDrawPlainRect(painter, r, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget),0); + } + QPalette shadePal(option->palette); + shadePal.setColor(QPalette::Button, option->palette.light().color()); + shadePal.setColor(QPalette::Light, option->palette.base().color()); + if (spinBox->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette pal2 = spinBox->palette; + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + if (spinBox->activeSubControls == SC_SpinBoxUp && (spinBox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + primitiveElement = (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorArrowUpBig + : PE_IndicatorArrowUpBig); + copy.rect = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxUp, widget); + if (copy.state & (State_Sunken | State_On)) + qDrawPlainRect(painter, copy.rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), ©.palette.brush(QPalette::Shadow)); + else + qDrawPlainRect(painter, copy.rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), ©.palette.brush(QPalette::Base)); + copy.rect.adjust(proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), 0, -pixelMetric(PM_SpinBoxFrameWidth, option, widget), 0); + proxy()->drawPrimitive(PrimitiveElement(primitiveElement), ©, painter, widget); + } + if (spinBox->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = spinBox->state; + QPalette pal2 = spinBox->palette; + if (!(spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + if (spinBox->activeSubControls == SC_SpinBoxDown && (spinBox->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + primitiveElement = (spinBox->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorArrowDownBig + : PE_IndicatorArrowDownBig); + copy.rect = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxDown, widget); + qDrawPlainRect(painter, copy.rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), ©.palette.brush(QPalette::Base)); + if (copy.state & (State_Sunken | State_On)) + qDrawPlainRect(painter, copy.rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), ©.palette.brush(QPalette::Shadow)); + else + qDrawPlainRect(painter, copy.rect, option->palette.shadow().color(), proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), ©.palette.brush(QPalette::Base)); + copy.rect.adjust(3, 0, -4, 0); + if (primitiveElement == PE_IndicatorArrowUp || primitiveElement == PE_IndicatorArrowDown) { + int frameWidth = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget); + copy.rect = copy.rect.adjusted(frameWidth, frameWidth, -frameWidth, -frameWidth); + proxy()->drawPrimitive(PrimitiveElement(primitiveElement), ©, painter, widget); + } + else { + proxy()->drawPrimitive(PrimitiveElement(primitiveElement), ©, painter, widget); + } + if (spinBox->frame && (spinBox->subControls & SC_SpinBoxFrame)) { + QRect r = proxy()->subControlRect(CC_SpinBox, spinBox, SC_SpinBoxEditField, widget); + } + } + } + break; +#endif // QT_NO_SPINBOX + + default: + QWindowsStyle::drawComplexControl(control, option, painter, widget); + break; + } +} + +QSize QWindowsMobileStyle::sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const { + + QSize newSize = QWindowsStyle::sizeFromContents(type, option, size, widget); + switch (type) { + case CT_PushButton: + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + newSize = QCommonStyle::sizeFromContents(type, option, size, widget); + int w = newSize.width(), + h = newSize.height(); + int defwidth = 0; + if (button->features & QStyleOptionButton::AutoDefaultButton) + defwidth = 2 * proxy()->pixelMetric(PM_ButtonDefaultIndicator, button, widget); + + int minwidth = int(QStyleHelper::dpiScaled(55.0f)); + int minheight = int(QStyleHelper::dpiScaled(19.0f)); + + if (w < minwidth + defwidth && button->icon.isNull()) + w = minwidth + defwidth; + if (h < minheight + defwidth) + h = minheight + defwidth; + newSize = QSize(w + 4, h + 4); + } + break; + +#ifndef QT_NO_GROUPBOX + case CT_GroupBox: + if (const QGroupBox *grb = static_cast<const QGroupBox *>(widget)) { + newSize = size + QSize(!grb->isFlat() ? 16 : 0, !grb->isFlat() ? 16 : 0); + } + break; +#endif // QT_NO_GROUPBOX + + case CT_RadioButton: + case CT_CheckBox: + newSize = size; + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + bool isRadio = (type == CT_RadioButton); + QRect irect = visualRect(button->direction, button->rect, + proxy()->subElementRect(isRadio ? SE_RadioButtonIndicator + : SE_CheckBoxIndicator, button, widget)); + int h = proxy()->pixelMetric(isRadio ? PM_ExclusiveIndicatorHeight + : PM_IndicatorHeight, button, widget); + int margins = (!button->icon.isNull() && button->text.isEmpty()) ? 0 : 10; + if (d_func()->doubleControls) + margins *= 2; + newSize += QSize(irect.right() + margins, 1); + newSize.setHeight(qMax(newSize.height(), h)); + } + break; +#ifndef QT_NO_COMBOBOX + case CT_ComboBox: + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int fw = comboBox->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, option, widget) * 2 : 0; + newSize = QSize(newSize.width() + fw + 9, newSize.height() + fw); //Nine is a magic Number - See CommonStyle for real magic (23) + } + break; +#endif +#ifndef QT_NO_SPINBOX + case CT_SpinBox: + if (const QStyleOptionSpinBox *spinBox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) { + int fw = spinBox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget) * 2 : 0; + newSize = QSize(newSize.width() + fw-5, newSize.height() + fw-6); + } + break; +#endif +#ifndef QT_NO_LINEEDIT + case CT_LineEdit: + newSize += QSize(0,1); + break; +#endif + case CT_ToolButton: + newSize = QSize(newSize.width() + 1, newSize.height()); + break; + case CT_TabBarTab: + if (d_func()->doubleControls) + newSize = QSize(newSize.width(), 42); + else + newSize = QSize(newSize.width(), 21); + break; + case CT_HeaderSection: + newSize += QSize(4, 2); + break; +#ifndef QT_NO_ITEMVIEWS +#ifdef Q_WS_WINCE_WM + case CT_ItemViewItem: + if (d_func()->wm65) + if (d_func()->doubleControls) + newSize.setHeight(46); + else + newSize.setHeight(23); + break; +#endif //Q_WS_WINCE_WM +#endif //QT_NO_ITEMVIEWS + default: + break; + } + return newSize; +} + +QRect QWindowsMobileStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + QRect rect = QWindowsStyle::subElementRect(element, option, widget); + switch (element) { +#ifndef QT_NO_TABWIDGET + case SE_TabWidgetTabBar: + if (d->doubleControls) + rect.adjust(-2, 0, 2, 0); + else + rect.adjust(-2, 0, 2, 0); + break; +#endif //QT_NO_TABWIDGET + case SE_CheckBoxFocusRect: + rect.adjust(1,0,-2,-1); + break; + case SE_RadioButtonFocusRect: + rect.adjust(1,1,-2,-2); + break; + default: + break; +#ifndef QT_NO_SLIDER + case SE_SliderFocusRect: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + rect = slider->rect; + } + break; + case SE_PushButtonFocusRect: + if (d->doubleControls) + rect.adjust(-1, -1, 0, 0); + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_ITEMVIEWS + case SE_ItemViewItemFocusRect: +#ifdef Q_WS_WINCE_WM + if (d->wm65) + rect = QRect(); +#endif + break; +#endif //QT_NO_ITEMVIEWS + } + return rect; +} + +QRect QWindowsMobileStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option, + SubControl subControl, const QWidget *widget) const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + QRect rect = QCommonStyle::subControlRect(control, option, subControl, widget); + switch (control) { + +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int sliderButtonExtent = proxy()->pixelMetric(PM_ScrollBarExtent, scrollbar, widget); + float stretchFactor = 1.4f; + int sliderButtonExtentDir = int (sliderButtonExtent * stretchFactor); + +#ifdef Q_WS_WINCE_WM + if (d->wm65) + { + sliderButtonExtent = d->imageScrollbarHandleUp.width(); + sliderButtonExtentDir = d->imageScrollbarHandleUp.height(); + } +#endif //Q_WS_WINCE_WM + + int sliderlen; + int maxlen = ((scrollbar->orientation == Qt::Horizontal) ? + scrollbar->rect.width() : scrollbar->rect.height()) - (sliderButtonExtentDir * 2); + // calculate slider length + if (scrollbar->maximum != scrollbar->minimum) { + uint range = scrollbar->maximum - scrollbar->minimum; + sliderlen = (qint64(scrollbar->pageStep) * maxlen) / (range + scrollbar->pageStep); + + int slidermin = proxy()->pixelMetric(PM_ScrollBarSliderMin, scrollbar, widget); + if (sliderlen < slidermin || range > INT_MAX / 2) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + int sliderstart = sliderButtonExtentDir + sliderPositionFromValue(scrollbar->minimum, + scrollbar->maximum, + scrollbar->sliderPosition, + maxlen - sliderlen, + scrollbar->upsideDown); + if (d->smartphone) { + sliderstart -= sliderButtonExtentDir; + sliderlen += 2*sliderButtonExtent; + } + switch (subControl) { + case SC_ScrollBarSubLine: // top/left button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollbar->rect.width() / 2, sliderButtonExtentDir ); + rect.setRect(0, 0, buttonWidth, sliderButtonExtent); + } else { + int buttonHeight = qMin(scrollbar->rect.height() / 2, sliderButtonExtentDir); + rect.setRect(0, 0, sliderButtonExtent, buttonHeight); + } + if (d->smartphone) + rect.setRect(0, 0, 0, 0); + break; + case SC_ScrollBarAddLine: // bottom/right button + if (scrollbar->orientation == Qt::Horizontal) { + int buttonWidth = qMin(scrollbar->rect.width()/2, sliderButtonExtentDir); + rect.setRect(scrollbar->rect.width() - buttonWidth, 0, buttonWidth, sliderButtonExtent); + } else { + int buttonHeight = qMin(scrollbar->rect.height()/2, sliderButtonExtentDir ); + rect.setRect(0, scrollbar->rect.height() - buttonHeight, sliderButtonExtent, buttonHeight); + } + if (d->smartphone) + rect.setRect(0, 0, 0, 0); + break; + case SC_ScrollBarSubPage: // between top/left button and slider + if (scrollbar->orientation == Qt::Horizontal) + if (d->smartphone) + rect.setRect(0, 0, sliderstart, sliderButtonExtent); + else + rect.setRect(sliderButtonExtent, 0, sliderstart - sliderButtonExtent, sliderButtonExtent); + else + if (d->smartphone) + rect.setRect(0, 0, sliderButtonExtent, sliderstart); + else + rect.setRect(0, sliderButtonExtent, sliderButtonExtent, sliderstart - sliderButtonExtent); + break; + case SC_ScrollBarAddPage: // between bottom/right button and slider + if (scrollbar->orientation == Qt::Horizontal) + if (d->smartphone) + rect.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen + 2*sliderButtonExtent, sliderButtonExtent); + else + rect.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen + sliderButtonExtent, sliderButtonExtent); + else + if (d->smartphone) + rect.setRect(0, sliderstart + sliderlen, sliderButtonExtent, + maxlen - sliderstart - sliderlen + 2*sliderButtonExtent); + else + rect.setRect(0, sliderstart + sliderlen, sliderButtonExtent, + maxlen - sliderstart - sliderlen + sliderButtonExtent); + break; + case SC_ScrollBarGroove: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderButtonExtent, 0, scrollbar->rect.width() - sliderButtonExtent * 2, + scrollbar->rect.height()); + else + rect.setRect(0, sliderButtonExtent, scrollbar->rect.width(), + scrollbar->rect.height() - sliderButtonExtent * 2); + break; + case SC_ScrollBarSlider: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderstart, 0, sliderlen, sliderButtonExtent); + else + rect.setRect(0, sliderstart, sliderButtonExtent, sliderlen); + break; + default: + break; + } + rect = visualRect(scrollbar->direction, scrollbar->rect, rect); + } + break; +#endif // QT_NO_SCROLLBAR + + + +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *toolButton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) { + int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, toolButton, widget); + rect = toolButton->rect; + switch (subControl) { + case SC_ToolButton: + if ((toolButton->features + & (QStyleOptionToolButton::Menu | QStyleOptionToolButton::PopupDelay)) + == QStyleOptionToolButton::Menu) + rect.adjust(0, 0, -mbi, 0); + break; + case SC_ToolButtonMenu: + if ((toolButton->features + & (QStyleOptionToolButton::Menu | QStyleOptionToolButton::PopupDelay)) + == QStyleOptionToolButton::Menu) + rect.adjust(rect.width() - mbi, 1, 0, 1); + break; + default: + break; + } + rect = visualRect(toolButton->direction, toolButton->rect, rect); + } + break; +#endif // QT_NO_TOOLBUTTON + +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) { + int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + switch (subControl) { + case SC_SliderHandle: { + int sliderPos = 0; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + bool horizontal = slider->orientation == Qt::Horizontal; + sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum, + slider->sliderPosition, + (horizontal ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown); + if (horizontal) + rect.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness); + else + rect.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len); + break; } + default: + break; + } + rect = visualRect(slider->direction, slider->rect, rect); + } + break; +#endif //QT_NO_SLIDER +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *comboBox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = comboBox->rect.x(), + y = comboBox->rect.y(), + wi = comboBox->rect.width(), + he = comboBox->rect.height(); + int xpos = x; + int margin = comboBox->frame ? (d->doubleControls ? 2 : 1) : 0; + int bmarg = comboBox->frame ? (d->doubleControls ? 2 : 1) : 0; + if (subControl == SC_ComboBoxArrow) + xpos += wi - int((he - 2*bmarg)*0.9) - bmarg; + else + xpos += wi - (he - 2*bmarg) - bmarg; + switch (subControl) { + case SC_ComboBoxArrow: + rect.setRect(xpos, y + bmarg, he - 2*bmarg, he - 2*bmarg); + break; + case SC_ComboBoxEditField: + rect.setRect(x + margin, y + margin, wi - 2 * margin - int((he - 2*bmarg) * 0.84f), he - 2 * margin); + if (d->doubleControls) { + if (comboBox->editable) + rect.adjust(2, 0, 0, 0); + else + rect.adjust(4, 2, 0, -2); + } else if (!comboBox->editable) { + rect.adjust(2, 1, 0, -1); + } + break; + case SC_ComboBoxFrame: + rect = comboBox->rect; + break; + default: + break; + } + } +#endif //QT_NO_COMBOBOX +#ifndef QT_NO_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(d->doubleControls ? 28 : 14, (spinBox->rect.height()))); + // 1.6 -approximate golden mean + bs.setWidth(qMax(d->doubleControls ? 28 : 14, qMin((bs.height()*7/8), (spinBox->rect.width() / 8)))); + bs = bs.expandedTo(QApplication::globalStrut()); + int x, lx, rx; + x = spinBox->rect.width() - bs.width()*2; + lx = fw; + rx = x - fw; + switch (subControl) { + case SC_SpinBoxUp: + rect = QRect(x + proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget), 0 , bs.width(), bs.height()); + break; + case SC_SpinBoxDown: + rect = QRect(x + bs.width(), 0, bs.width(), bs.height()); + break; + case SC_SpinBoxEditField: + if (spinBox->buttonSymbols == QAbstractSpinBox::NoButtons) { + rect = QRect(lx, fw, spinBox->rect.width() - 2*fw - 2, spinBox->rect.height() - 2*fw); + } else { + rect = QRect(lx, fw, rx-2, spinBox->rect.height() - 2*fw); + } + break; + case SC_SpinBoxFrame: + rect = spinBox->rect; + default: + break; + } + rect = visualRect(spinBox->direction, spinBox->rect, rect); + } + break; +#endif // Qt_NO_SPINBOX +#ifndef QT_NO_GROUPBOX + case CC_GroupBox: { + if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox *>(option)) { + switch (subControl) { + case SC_GroupBoxFrame: + // FALL THROUGH + case SC_GroupBoxContents: { + int topMargin = 0; + int topHeight = 0; + int bottomMargin = 0; + int labelMargin = 2; + + QRect frameRect = groupBox->rect; + int verticalAlignment = styleHint(SH_GroupBox_TextLabelVerticalAlignment, groupBox, widget); + if (groupBox->text.size()) { + topHeight = groupBox->fontMetrics.height(); + if (verticalAlignment & Qt::AlignVCenter) + topMargin = topHeight+5; + else if (verticalAlignment & Qt::AlignTop) + topMargin = -topHeight+5; + } + if (subControl == SC_GroupBoxFrame) { + frameRect.setTop(topMargin); + frameRect.setBottom(frameRect.height() + bottomMargin); + rect = frameRect; + break; + } + int frameWidth = 0; + if (groupBox->text.size()) { + frameWidth = proxy()->pixelMetric(PM_DefaultFrameWidth, groupBox, widget); + rect = frameRect.adjusted(frameWidth, frameWidth + topHeight + labelMargin, -frameWidth, -frameWidth); + } + else { + rect = groupBox->rect; + } + break; + } + case SC_GroupBoxCheckBox: + // FALL THROUGH + case SC_GroupBoxLabel: { + QFontMetrics fontMetrics = groupBox->fontMetrics; + int h = fontMetrics.height(); + int textWidth = fontMetrics.size(Qt::TextShowMnemonic, groupBox->text + QLatin1Char(' ')).width(); + int margX = (groupBox->features & QStyleOptionFrameV2::Flat) ? 0 : 2; + int margY = (groupBox->features & QStyleOptionFrameV2::Flat) ? 0 : 2; + rect = groupBox->rect.adjusted(margX, margY, -margX, 0); + if (groupBox->text.size()) + rect.setHeight(h); + else + rect.setHeight(0); + int indicatorWidth = proxy()->pixelMetric(PM_IndicatorWidth, option, widget); + int indicatorSpace = proxy()->pixelMetric(PM_CheckBoxLabelSpacing, option, widget) - 1; + bool hasCheckBox = groupBox->subControls & QStyle::SC_GroupBoxCheckBox; + int checkBoxSize = hasCheckBox ? (indicatorWidth + indicatorSpace) : 0; + + // Adjusted rect for label + indicatorWidth + indicatorSpace + QRect totalRect = alignedRect(groupBox->direction, groupBox->textAlignment, + QSize(textWidth + checkBoxSize, h), rect); + + // Adjust totalRect if checkbox is set + if (hasCheckBox) { + bool ltr = groupBox->direction == Qt::LeftToRight; + int left = 2; + // Adjust for check box + if (subControl == SC_GroupBoxCheckBox) { + int indicatorHeight = proxy()->pixelMetric(PM_IndicatorHeight, option, widget); + left = ltr ? totalRect.left() : (totalRect.right() - indicatorWidth); + int top = totalRect.top() + (fontMetrics.height() - indicatorHeight) / 2; + totalRect.setRect(left, top, indicatorWidth, indicatorHeight); + // Adjust for label + } else { + left = ltr ? (totalRect.left() + checkBoxSize - 2) : totalRect.left(); + totalRect.setRect(left, totalRect.top(), + totalRect.width() - checkBoxSize, totalRect.height()); + } + } + if ((subControl== SC_GroupBoxLabel)) + totalRect.adjust(-2,0,6,0); + rect = totalRect; + break; + } + default: + break; + } + } + break; + } +#endif // QT_NO_GROUPBOX + default: + break; + } + return rect; +} + +QPalette QWindowsMobileStyle::standardPalette() const { + QPalette palette (Qt::black,QColor(198, 195, 198), QColor(222, 223, 222 ), + QColor(132, 130, 132), QColor(198, 195, 198), Qt::black, Qt::white, Qt::white, QColor(198, 195, 198)); + palette.setColor(QPalette::Window, QColor(206, 223, 239)); + palette.setColor(QPalette::Link, QColor(8,77,123)); //Alternate TextColor for labels... + palette.setColor(QPalette::Base, Qt::white); + palette.setColor(QPalette::Button, QColor(206, 223, 239)); + palette.setColor(QPalette::Highlight, QColor(49, 146, 214)); + palette.setColor(QPalette::Light, Qt::white); + palette.setColor(QPalette::Text, Qt::black); + palette.setColor(QPalette::ButtonText, Qt::black); + palette.setColor(QPalette::Midlight, QColor(222, 223, 222 )); + palette.setColor(QPalette::Dark, QColor(132, 130, 132)); + palette.setColor(QPalette::Mid, QColor(189, 190, 189)); + palette.setColor(QPalette::Shadow, QColor(0, 0, 0)); + palette.setColor(QPalette::BrightText, QColor(33, 162, 33)); //color for ItemView checked indicator (arrow) + return palette; +} + + +/*! \reimp */ +void QWindowsMobileStyle::polish(QApplication *application) { + QWindowsStyle::polish(application); +} + +/*! \reimp */ +void QWindowsMobileStyle::polish(QWidget *widget) { + +#ifndef QT_NO_TOOLBAR + if (QToolBar *toolBar = qobject_cast<QToolBar*>(widget)) { + QPalette pal = toolBar->palette(); + pal.setColor(QPalette::Background, pal.button().color()); + toolBar->setPalette(pal); + } + else +#endif //QT_NO_TOOLBAR + + QWindowsStyle::polish(widget); +} + +void QWindowsMobileStyle::unpolish(QWidget *widget) +{ + QWindowsStyle::unpolish(widget); +} + +void QWindowsMobileStyle::unpolish(QApplication *app) +{ + QWindowsStyle::unpolish(app); +} + +/*! \reimp */ +void QWindowsMobileStyle::polish(QPalette &palette) { + QWindowsStyle::polish(palette); +} + +int QWindowsMobileStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QWidget *widget) const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + int ret; + + switch (pm) { + case PM_DefaultTopLevelMargin: + ret =0; + break; + case PM_DefaultLayoutSpacing: + d->doubleControls ? ret = 8 : ret = 4; + break; + case PM_HeaderMargin: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_DefaultChildMargin: + d->doubleControls ? ret = 10 : ret = 5; + break; + case PM_ToolBarSeparatorExtent: + d->doubleControls ? ret = 6 : ret = 3; + break; + case PM_DefaultFrameWidth: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_MenuVMargin: + ret = 1; + break; + case PM_MenuHMargin: + ret = 1; + break; + case PM_MenuButtonIndicator: + ret = d->doubleControls ? 24 : 14; + break; + case PM_ComboBoxFrameWidth: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_SpinBoxFrameWidth: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_ButtonDefaultIndicator: + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + d->doubleControls ? ret = 2 : ret = 1; + break; +#ifndef QT_NO_TABBAR + case PM_TabBarTabShiftHorizontal: + ret = 0; + break; + case PM_TabBarTabShiftVertical: + ret = 0; + break; +#endif + case PM_MaximumDragDistance: + ret = 60; + break; + case PM_TabBarTabVSpace: + ret = d->doubleControls ? 12 : 6; + break; + case PM_TabBarBaseHeight: + ret = 0; + break; + case PM_IndicatorWidth: + ret = d->doubleControls ? windowsMobileIndicatorSize * 2 : windowsMobileIndicatorSize; + break; + case PM_IndicatorHeight: + ret = d->doubleControls ? windowsMobileIndicatorSize * 2 : windowsMobileIndicatorSize; + break; + case PM_ExclusiveIndicatorWidth: + ret = d->doubleControls ? windowsMobileExclusiveIndicatorSize * 2 + 4: windowsMobileExclusiveIndicatorSize + 2; + break; + case PM_ExclusiveIndicatorHeight: + ret = d->doubleControls ? windowsMobileExclusiveIndicatorSize * 2 + 4: windowsMobileExclusiveIndicatorSize + 2; + break; +#ifndef QT_NO_SLIDER + case PM_SliderLength: + ret = d->doubleControls ? 16 : 8; + break; + case PM_FocusFrameHMargin: + ret = d->doubleControls ? 1 : 2; + break; + case PM_SliderThickness: + ret = d->doubleControls ? windowsMobileSliderThickness * 2: windowsMobileSliderThickness; + break; + case PM_TabBarScrollButtonWidth: + ret = d->doubleControls ? 14 * 2 : 18; + break; + case PM_CheckBoxLabelSpacing: + case PM_RadioButtonLabelSpacing: + ret = d->doubleControls ? 6 * 2 : 6; + break; + // Returns the number of pixels to use for the business part of the + // slider (i.e., the non-tickmark portion). The remaining space is shared + // equally between the tickmark regions. + case PM_SliderControlThickness: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); + int ticks = sl->tickPosition; + int n = 0; + if (ticks & QSlider::TicksAbove) + ++n; + if (ticks & QSlider::TicksBelow) + ++n; + if (!n) { + ret = space; + break; + } + int thick = 8; + if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks) + thick += proxy()->pixelMetric(PM_SliderLength, sl, widget) / 4; + + space -= thick; + if (space > 0) + thick += (space * 2) / (n + 2); + ret = thick; + } else { + ret = 0; + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_MENU + case PM_SmallIconSize: + d->doubleControls ? ret = windowsMobileIconSize * 2 : ret = windowsMobileIconSize; + break; + case PM_ButtonMargin: + d->doubleControls ? ret = 8 : ret = 4; + break; + case PM_LargeIconSize: + d->doubleControls ? ret = 64 : ret = 32; + break; + case PM_IconViewIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget); + break; + case PM_ToolBarIconSize: + d->doubleControls ? ret = 2 * windowsMobileIconSize : ret = windowsMobileIconSize; + break; + case PM_DockWidgetTitleMargin: + ret = 2; + break; +#if defined(Q_WS_WIN) +#else + case PM_DockWidgetFrameWidth: + ret = 4; + break; +#endif // Q_WS_WIN + break; +#endif // QT_NO_MENU + + case PM_TitleBarHeight: + d->doubleControls ? ret = 42 : ret = 21; + break; + case PM_ScrollBarSliderMin: +#ifdef Q_WS_WINCE_WM + if (d->wm65) +#else + if (false) +#endif + { + d->doubleControls ? ret = 68 : ret = 34; + } else { + d->doubleControls ? ret = 36 : ret = 18; + } + break; + case PM_ScrollBarExtent: { + + if (d->smartphone) + ret = 9; + else + d->doubleControls ? ret = 25 : ret = 13; + +#ifdef Q_WS_WINCE_WM + if (d->wm65) +#else + if (false) +#endif + { + d->doubleControls ? ret = 26 : ret = 13; + break; + } + +#ifndef QT_NO_SCROLLAREA + //Check if the scrollbar is part of an abstractItemView and set size according + if (widget) + if (QWidget *parent = widget->parentWidget()) + if (qobject_cast<QAbstractScrollArea *>(parent->parentWidget())) + if (d->smartphone) + ret = 8; + else + d->doubleControls ? ret = 24 : ret = 12; +#endif + } + break; + case PM_SplitterWidth: + ret = qMax(4, QApplication::globalStrut().width()); + break; + +#if defined(Q_WS_WIN) + case PM_MDIFrameWidth: + ret = 1; + break; +#endif + case PM_ToolBarExtensionExtent: + d->doubleControls ? ret = 32 : ret = 16; + break; + case PM_ToolBarItemMargin: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_ToolBarItemSpacing: + d->doubleControls ? ret = 2 : ret = 1; + break; + case PM_ToolBarHandleExtent: + d->doubleControls ? ret = 16 : ret = 8; + break; + case PM_ButtonIconSize: + d->doubleControls ? ret = 32 : ret = 16; + break; + case PM_TextCursorWidth: + ret = 2; + break; + case PM_TabBar_ScrollButtonOverlap: + ret = 0; + break; + default: + ret = QWindowsStyle::pixelMetric(pm, opt, widget); + break; + } + return ret; +} + +int QWindowsMobileStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *returnData) const { + + int ret; + switch (hint) { + case SH_Menu_MouseTracking: + case SH_ComboBox_ListMouseTracking: + case SH_EtchDisabledText: + ret = 0; + break; + case SH_DitherDisabledText: + ret = 0; + break; + case SH_ItemView_ShowDecorationSelected: + ret = 0; + break; +#ifndef QT_NO_TABWIDGET + case SH_TabWidget_DefaultTabPosition: + ret = QTabWidget::South; + break; +#endif + case SH_ToolBar_Movable: + ret = false; + break; + case SH_ScrollBar_ContextMenu: + ret = false; + break; + case SH_MenuBar_AltKeyNavigation: + ret = false; + break; + case SH_RequestSoftwareInputPanel: + ret = RSIP_OnMouseClick; + break; + default: + ret = QWindowsStyle::styleHint(hint, opt, widget, returnData); + break; + } + return ret; +} + +QPixmap QWindowsMobileStyle::standardPixmap(StandardPixmap sp, const QStyleOption *option, + const QWidget *widget) const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + switch (sp) { +#ifndef QT_NO_IMAGEFORMAT_XPM + case SP_ToolBarHorizontalExtensionButton: { + QPixmap pixmap = QCommonStyle::standardPixmap(sp, option, widget); + if (d->doubleControls) + return pixmap.scaledToHeight(pixmap.height() * 2); + else + return pixmap; + } + case SP_TitleBarMaxButton: + case SP_TitleBarCloseButton: + case SP_TitleBarNormalButton: + case SP_TitleBarMinButton: { + QImage image; + switch (sp) { + case SP_TitleBarMaxButton: + image = d->imageMaximize; + break; + case SP_TitleBarCloseButton: + image = d->imageClose; + break; + case SP_TitleBarNormalButton: + image = d->imageNormalize; + break; + case SP_TitleBarMinButton: + image = d->imageMinimize; + break; + default: + break; + } + if (option) { + image.setColor(0, option->palette.shadow().color().rgba()); + image.setColor(1, option->palette.highlight().color().rgba()); + image.setColor(2, option->palette.highlight().color().lighter(150).rgba()); + image.setColor(3, option->palette.highlightedText().color().rgba()); + } + + return QPixmap::fromImage(image); + } + +#endif + default: + return QWindowsStyle::standardPixmap(sp, option, widget); + } +} + +QPixmap QWindowsMobileStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *option) const { + + switch (iconMode) { + case QIcon::Selected: { +#ifdef Q_WS_WINCE_WM + if (d_func()->wm65) + return pixmap; +#endif //Q_WS_WINCE_WM + QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + int imgh = img.height(); + int imgw = img.width(); + for (int y = 0; y < imgh; y += 2) { + for (int x = 0; x < imgw; x += 2) { + QColor c = option->palette.highlight().color().rgb(); + c.setAlpha( qAlpha(img.pixel(x, y))); + QRgb pixel = c.rgba(); + img.setPixel(x, y, pixel); + } + } + return QPixmap::fromImage(img); + } + default: + break; + } + return QWindowsStyle::generatedIconPixmap(iconMode, pixmap, option); +} + + +bool QWindowsMobileStyle::doubleControls() const { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + return d->doubleControls; +} + +void QWindowsMobileStyle::setDoubleControls(bool doubleControls) { + + QWindowsMobileStylePrivate *d = const_cast<QWindowsMobileStylePrivate*>(d_func()); + + d->doubleControls = doubleControls; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSMOBILE + diff --git a/src/gui/styles/qwindowsmobilestyle.h b/src/gui/styles/qwindowsmobilestyle.h new file mode 100644 index 0000000000..6ac1af1c35 --- /dev/null +++ b/src/gui/styles/qwindowsmobilestyle.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSMOBILESTYLE_H +#define QWINDOWSMOBILESTYLE_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_WINDOWSMOBILE) + +class QWindowsMobileStylePrivate; + +class Q_GUI_EXPORT QWindowsMobileStyle : public QWindowsStyle +{ + Q_OBJECT +public: + QWindowsMobileStyle(); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + + void drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + + QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &pixmap, + const QStyleOption *option) const; + + QPixmap standardPixmap(StandardPixmap sp, const QStyleOption *option, + const QWidget *widget) const; + + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + void polish(QApplication*); + void unpolish(QApplication*); + void polish(QWidget *widget); + void unpolish(QWidget *widget); + void polish(QPalette &); + + QPalette standardPalette() const; + + bool doubleControls() const; + + void setDoubleControls(bool); + +protected: + QWindowsMobileStyle(QWindowsMobileStylePrivate &dd); + +private: + Q_DECLARE_PRIVATE(QWindowsMobileStyle) +}; + +#endif // QT_NO_STYLE_WINDOWSMOBILE + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QWINDOWSMOBILESTYLE_H diff --git a/src/gui/styles/qwindowsmobilestyle_p.h b/src/gui/styles/qwindowsmobilestyle_p.h new file mode 100644 index 0000000000..cfc6aea1c6 --- /dev/null +++ b/src/gui/styles/qwindowsmobilestyle_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSMOBILESTYLE_P_H +#define QWINDOWSMOBILESTYLE_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 "qwindowsmobilestyle.h" +#include "qwindowsstyle_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_STYLE_WINDOWSMOBILE + +class QStyleOptionTab; +class QStyleOptionSlider; +class QStyleOptionViewItemV4; + +class QWindowsMobileStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsMobileStyle) +public: + QWindowsMobileStylePrivate(); + bool doubleControls; + bool smartphone; +#ifdef Q_WS_WINCE_WM + bool wm65; +#endif + + QImage imageRadioButton; + QImage imageRadioButtonChecked; + QImage imageRadioButtonHighlighted; + QImage imageChecked; + QImage imageCheckedBold; + QImage imageArrowDown; + QImage imageArrowUp; + QImage imageArrowLeft; + QImage imageArrowRight; + QImage imageArrowDownBig; + QImage imageArrowUpBig; + QImage imageArrowLeftBig; + QImage imageArrowRightBig; + QImage imageClose; + QImage imageMaximize; + QImage imageNormalize; + QImage imageMinimize; + + void setupWindowsMobileStyle65(); + +#ifdef Q_WS_WINCE_WM + //Windows Mobile 6.5 images + QImage imageScrollbarHandleUp; + QImage imageScrollbarHandleDown; + QImage imageScrollbarHandleUpHigh; + QImage imageScrollbarHandleDownHigh; + QImage imageScrollbarGripUp; + QImage imageScrollbarGripDown; + QImage imageScrollbarGripMiddle; + QImage imageListViewHighlightCornerLeft; + QImage imageListViewHighlightCornerRight; + QImage imageListViewHighlightMiddle; + QImage imageTabEnd; + QImage imageTabSelectedEnd; + QImage imageTabSelectedBegin; + QImage imageTabMiddle; + + QColor currentTintHigh; + QColor currentTintButton; + + void tintImagesHigh(QColor color); + void tintImagesButton(QColor color); + void tintListViewHighlight(QColor color); + +#endif //Q_WS_WINCE_WM + + void drawScrollbarHandleUp(QPainter *p, QStyleOptionSlider *opt, bool completeFrame = false, bool secondScrollBar = false); + void drawScrollbarHandleDown(QPainter *p, QStyleOptionSlider *opt, bool completeFrame = false, bool secondScrollBar = false); + void drawScrollbarGroove(QPainter *p, const QStyleOptionSlider *opt); + void drawScrollbarGrip(QPainter *p, QStyleOptionSlider *newScrollbar, const QStyleOptionComplex *option, bool drawCompleteFrame); + void drawTabBarTab(QPainter *p, const QStyleOptionTab *tab); + void drawPanelItemViewSelected(QPainter *painter, const QStyleOptionViewItemV4 *option, QRect rect = QRect()); + +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSMOBILE +#endif //QWINDOWSMOBILESTYLE_P_H diff --git a/src/gui/styles/qwindowsstyle.cpp b/src/gui/styles/qwindowsstyle.cpp new file mode 100644 index 0000000000..44f3f92d8b --- /dev/null +++ b/src/gui/styles/qwindowsstyle.cpp @@ -0,0 +1,3392 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsstyle.h" +#include "qwindowsstyle_p.h" + +#if !defined(QT_NO_STYLE_WINDOWS) || defined(QT_PLUGIN) + +#include <private/qsystemlibrary_p.h> +#include "qapplication.h" +#include "qbitmap.h" +#include "qdrawutil.h" // for now +#include "qevent.h" +#include "qmenu.h" +#include "qmenubar.h" +#include <private/qmenubar_p.h> +#include "qpaintengine.h" +#include "qpainter.h" +#include "qprogressbar.h" +#include "qrubberband.h" +#include "qstyleoption.h" +#include "qtabbar.h" +#include "qwidget.h" +#include "qdebug.h" +#include "qmainwindow.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qpixmapcache.h" +#include "qwizard.h" +#include "qlistview.h" +#include <private/qmath_p.h> +#include <qmath.h> + +#ifdef Q_WS_X11 +#include "qfileinfo.h" +#include "qdir.h" +#include <private/qt_x11_p.h> +#endif + +#include <private/qstylehelper_p.h> + +QT_BEGIN_NAMESPACE + +#if defined(Q_WS_WIN) + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qt_windows.h" +QT_END_INCLUDE_NAMESPACE +# ifndef COLOR_GRADIENTACTIVECAPTION +# define COLOR_GRADIENTACTIVECAPTION 27 +# endif +# ifndef COLOR_GRADIENTINACTIVECAPTION +# define COLOR_GRADIENTINACTIVECAPTION 28 +# endif + + +typedef struct +{ + DWORD cbSize; + HICON hIcon; + int iSysImageIndex; + int iIcon; + WCHAR szPath[MAX_PATH]; +} QSHSTOCKICONINFO; + +#define _SHGFI_SMALLICON 0x000000001 +#define _SHGFI_LARGEICON 0x000000000 +#define _SHGFI_ICON 0x000000100 +#define _SIID_SHIELD 77 + +typedef HRESULT (WINAPI *PtrSHGetStockIconInfo)(int siid, int uFlags, QSHSTOCKICONINFO *psii); +static PtrSHGetStockIconInfo pSHGetStockIconInfo = 0; + +#endif //Q_WS_WIN + +QT_BEGIN_INCLUDE_NAMESPACE +#include <limits.h> +QT_END_INCLUDE_NAMESPACE + +enum QSliderDirection { SlUp, SlDown, SlLeft, SlRight }; + +/* + \internal +*/ +QWindowsStylePrivate::QWindowsStylePrivate() + : alt_down(false), menuBarTimer(0), animationFps(10), animateTimer(0), animateStep(0) +{ +#if defined(Q_WS_WIN) && !defined(Q_OS_WINCE) + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) { + QSystemLibrary shellLib(QLatin1String("shell32")); + pSHGetStockIconInfo = (PtrSHGetStockIconInfo)shellLib.resolve("SHGetStockIconInfo"); + } +#endif + startTime.start(); +} + +// Returns true if the toplevel parent of \a widget has seen the Alt-key +bool QWindowsStylePrivate::hasSeenAlt(const QWidget *widget) const +{ + widget = widget->window(); + return seenAlt.contains(widget); +} + +/*! + \reimp +*/ +void QWindowsStyle::timerEvent(QTimerEvent *event) +{ +#ifndef QT_NO_PROGRESSBAR + Q_D(QWindowsStyle); + if (event->timerId() == d->animateTimer) { + Q_ASSERT(d->animationFps> 0); + d->animateStep = d->startTime.elapsed() / (1000 / d->animationFps); + foreach (QProgressBar *bar, d->bars) { + if ((bar->minimum() == 0 && bar->maximum() == 0)) + bar->update(); + } + } +#endif // QT_NO_PROGRESSBAR + event->ignore(); +} + +/*! + \reimp +*/ +bool QWindowsStyle::eventFilter(QObject *o, QEvent *e) +{ + // Records Alt- and Focus events + if (!o->isWidgetType()) + return QObject::eventFilter(o, e); + + QWidget *widget = qobject_cast<QWidget*>(o); + Q_D(QWindowsStyle); + switch(e->type()) { + case QEvent::KeyPress: + if (static_cast<QKeyEvent *>(e)->key() == Qt::Key_Alt) { + widget = widget->window(); + + // Alt has been pressed - find all widgets that care + QList<QWidget *> l = widget->findChildren<QWidget *>(); + for (int pos=0 ; pos < l.size() ; ++pos) { + QWidget *w = l.at(pos); + if (w->isWindow() || !w->isVisible() || + w->style()->styleHint(SH_UnderlineShortcut, 0, w)) + l.removeAt(pos); + } + // Update states before repainting + d->seenAlt.append(widget); + d->alt_down = true; + + // Repaint all relevant widgets + for (int pos = 0; pos < l.size(); ++pos) + l.at(pos)->update(); + } + break; + case QEvent::KeyRelease: + if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Alt) { + widget = widget->window(); + + // Update state and repaint the menu bars. + d->alt_down = false; +#ifndef QT_NO_MENUBAR + QList<QMenuBar *> l = widget->findChildren<QMenuBar *>(); + for (int i = 0; i < l.size(); ++i) + l.at(i)->update(); +#endif + } + break; + case QEvent::Close: + // Reset widget when closing + d->seenAlt.removeAll(widget); + d->seenAlt.removeAll(widget->window()); + break; +#ifndef QT_NO_PROGRESSBAR + case QEvent::StyleChange: + case QEvent::Show: + if (QProgressBar *bar = qobject_cast<QProgressBar *>(o)) { + if (!d->bars.contains(bar)) { + d->bars << bar; + if (d->bars.size() == 1) { + Q_ASSERT(d->animationFps> 0); + d->animateTimer = startTimer(1000 / d->animationFps); + } + } + } + break; + case QEvent::Destroy: + case QEvent::Hide: + // reinterpret_cast because there is no type info when getting + // the destroy event. We know that it is a QProgressBar. + if (QProgressBar *bar = reinterpret_cast<QProgressBar *>(o)) { + d->bars.removeAll(bar); + if (d->bars.isEmpty() && d->animateTimer) { + killTimer(d->animateTimer); + d->animateTimer = 0; + } + } + break; +#endif // QT_NO_PROGRESSBAR + default: + break; + } + return QCommonStyle::eventFilter(o, e); +} + +/*! + \class QWindowsStyle + \brief The QWindowsStyle class provides a Microsoft Windows-like look and feel. + + \ingroup appearance + + This style is Qt's default GUI style on Windows. + + \img qwindowsstyle.png + \sa QWindowsXPStyle, QMacStyle, QPlastiqueStyle, QCDEStyle, QMotifStyle +*/ + +/*! + Constructs a QWindowsStyle object. +*/ +QWindowsStyle::QWindowsStyle() : QCommonStyle(*new QWindowsStylePrivate) +{ +} + +/*! + \internal + + Constructs a QWindowsStyle object. +*/ +QWindowsStyle::QWindowsStyle(QWindowsStylePrivate &dd) : QCommonStyle(dd) +{ +} + + +/*! Destroys the QWindowsStyle object. */ +QWindowsStyle::~QWindowsStyle() +{ +} + +#ifdef Q_WS_WIN +static inline QRgb colorref2qrgb(COLORREF col) +{ + return qRgb(GetRValue(col), GetGValue(col), GetBValue(col)); +} +#endif + +/*! \reimp */ +void QWindowsStyle::polish(QApplication *app) +{ + QCommonStyle::polish(app); + QWindowsStylePrivate *d = const_cast<QWindowsStylePrivate*>(d_func()); + // We only need the overhead when shortcuts are sometimes hidden + if (!proxy()->styleHint(SH_UnderlineShortcut, 0) && app) + app->installEventFilter(this); + + d->activeCaptionColor = app->palette().highlight().color(); + d->activeGradientCaptionColor = app->palette().highlight() .color(); + d->inactiveCaptionColor = app->palette().dark().color(); + d->inactiveGradientCaptionColor = app->palette().dark().color(); + d->inactiveCaptionText = app->palette().background().color(); + +#if defined(Q_WS_WIN) //fetch native title bar colors + if(app->desktopSettingsAware()){ + DWORD activeCaption = GetSysColor(COLOR_ACTIVECAPTION); + DWORD gradientActiveCaption = GetSysColor(COLOR_GRADIENTACTIVECAPTION); + DWORD inactiveCaption = GetSysColor(COLOR_INACTIVECAPTION); + DWORD gradientInactiveCaption = GetSysColor(COLOR_GRADIENTINACTIVECAPTION); + DWORD inactiveCaptionText = GetSysColor(COLOR_INACTIVECAPTIONTEXT); + d->activeCaptionColor = colorref2qrgb(activeCaption); + d->activeGradientCaptionColor = colorref2qrgb(gradientActiveCaption); + d->inactiveCaptionColor = colorref2qrgb(inactiveCaption); + d->inactiveGradientCaptionColor = colorref2qrgb(gradientInactiveCaption); + d->inactiveCaptionText = colorref2qrgb(inactiveCaptionText); + } +#endif +} + +/*! \reimp */ +void QWindowsStyle::unpolish(QApplication *app) +{ + QCommonStyle::unpolish(app); + app->removeEventFilter(this); +} + +/*! \reimp */ +void QWindowsStyle::polish(QWidget *widget) +{ + QCommonStyle::polish(widget); +#ifndef QT_NO_PROGRESSBAR + if (qobject_cast<QProgressBar *>(widget)) + widget->installEventFilter(this); +#endif +} + +/*! \reimp */ +void QWindowsStyle::unpolish(QWidget *widget) +{ + QCommonStyle::unpolish(widget); +#ifndef QT_NO_PROGRESSBAR + if (QProgressBar *bar=qobject_cast<QProgressBar *>(widget)) { + Q_D(QWindowsStyle); + widget->removeEventFilter(this); + d->bars.removeAll(bar); + } +#endif +} + +/*! + \reimp +*/ +void QWindowsStyle::polish(QPalette &pal) +{ + QCommonStyle::polish(pal); +} + +/*! + \reimp +*/ +int QWindowsStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QWidget *widget) const +{ + int ret; + + switch (pm) { + case PM_ButtonDefaultIndicator: + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + case PM_MenuHMargin: + case PM_MenuVMargin: + ret = 1; + break; +#ifndef QT_NO_TABBAR + case PM_TabBarTabShiftHorizontal: + ret = 0; + break; + case PM_TabBarTabShiftVertical: + ret = 2; + break; +#endif + case PM_MaximumDragDistance: +#if defined(Q_WS_WIN) + { + HDC hdcScreen = GetDC(0); + int dpi = GetDeviceCaps(hdcScreen, LOGPIXELSX); + ReleaseDC(0, hdcScreen); + ret = (int)(dpi * 1.375); + } +#else + ret = 60; +#endif + break; + +#ifndef QT_NO_SLIDER + case PM_SliderLength: + ret = int(QStyleHelper::dpiScaled(11.)); + break; + + // Returns the number of pixels to use for the business part of the + // slider (i.e., the non-tickmark portion). The remaining space is shared + // equally between the tickmark regions. + case PM_SliderControlThickness: + if (const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int space = (sl->orientation == Qt::Horizontal) ? sl->rect.height() : sl->rect.width(); + int ticks = sl->tickPosition; + int n = 0; + if (ticks & QSlider::TicksAbove) + ++n; + if (ticks & QSlider::TicksBelow) + ++n; + if (!n) { + ret = space; + break; + } + + int thick = 6; // Magic constant to get 5 + 16 + 5 + if (ticks != QSlider::TicksBothSides && ticks != QSlider::NoTicks) + thick += proxy()->pixelMetric(PM_SliderLength, sl, widget) / 4; + + space -= thick; + if (space > 0) + thick += (space * 2) / (n + 2); + ret = thick; + } else { + ret = 0; + } + break; +#endif // QT_NO_SLIDER + +#ifndef QT_NO_MENU + case PM_MenuBarHMargin: + ret = 0; + break; + + case PM_MenuBarVMargin: + ret = 0; + break; + + case PM_MenuBarPanelWidth: + ret = 0; + break; + + case PM_SmallIconSize: + ret = int(QStyleHelper::dpiScaled(16.)); + break; + + case PM_LargeIconSize: + ret = int(QStyleHelper::dpiScaled(32.)); + break; + + case PM_IconViewIconSize: + ret = proxy()->pixelMetric(PM_LargeIconSize, opt, widget); + break; + + case PM_DockWidgetTitleMargin: + ret = int(QStyleHelper::dpiScaled(2.)); + break; + case PM_DockWidgetTitleBarButtonMargin: + ret = int(QStyleHelper::dpiScaled(4.)); + break; +#if defined(Q_WS_WIN) + case PM_DockWidgetFrameWidth: +#if defined(Q_OS_WINCE) + ret = GetSystemMetrics(SM_CXDLGFRAME); +#else + ret = GetSystemMetrics(SM_CXFRAME); +#endif + break; +#else + case PM_DockWidgetFrameWidth: + ret = 4; + break; +#endif // Q_WS_WIN + break; + +#endif // QT_NO_MENU + + +#if defined(Q_WS_WIN) + case PM_TitleBarHeight: +#ifdef QT3_SUPPORT + // qt3 dockwindow height should be equal to tool windows + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + ret = GetSystemMetrics(SM_CYSMCAPTION) - 1; + } else +#endif + if (widget && (widget->windowType() == Qt::Tool)) { + // MS always use one less than they say +#if defined(Q_OS_WINCE) + ret = GetSystemMetrics(SM_CYCAPTION) - 1; +#else + ret = GetSystemMetrics(SM_CYSMCAPTION) - 1; +#endif + } else { + ret = GetSystemMetrics(SM_CYCAPTION) - 1; + } + + break; + + case PM_ScrollBarExtent: + { +#ifndef Q_OS_WINCE + NONCLIENTMETRICS ncm; + ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT); + if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0)) + ret = qMax(ncm.iScrollHeight, ncm.iScrollWidth); + else +#endif + ret = QCommonStyle::pixelMetric(pm, opt, widget); + } + break; +#endif // Q_WS_WIN + + case PM_SplitterWidth: + ret = qMax(4, QApplication::globalStrut().width()); + break; + +#if defined(Q_WS_WIN) + case PM_MdiSubWindowFrameWidth: +#if defined(Q_OS_WINCE) + ret = GetSystemMetrics(SM_CYDLGFRAME); +#else + ret = GetSystemMetrics(SM_CYFRAME); +#endif + break; + case PM_TextCursorWidth: { + DWORD caretWidth = 1; +#if defined(SPI_GETCARETWIDTH) + SystemParametersInfo(SPI_GETCARETWIDTH, 0, &caretWidth, 0); +#endif + ret = (int)caretWidth; + break; } +#endif + case PM_ToolBarItemMargin: + ret = 1; + break; + case PM_ToolBarItemSpacing: + ret = 0; + break; + case PM_ToolBarHandleExtent: + ret = int(QStyleHelper::dpiScaled(10.)); + break; + default: + ret = QCommonStyle::pixelMetric(pm, opt, widget); + break; + } + + return ret; +} + +#ifndef QT_NO_IMAGEFORMAT_XPM + +/* 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_minimize_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"..........", +"..........", +"..........", +"..........", +"..........", +"..........", +"..........", +".#######..", +".#######..", +".........."}; + +static const char * const qt_normalizeup_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"...######.", +"...######.", +"...#....#.", +".######.#.", +".######.#.", +".#....###.", +".#....#...", +".#....#...", +".######...", +".........."}; + +static const char * const qt_help_xpm[] = { +"10 10 2 1", +". c None", +"# c #000000", +"..........", +"..######..", +".##....##.", +"......##..", +".....##...", +"....##....", +"....##....", +"..........", +"....##....", +".........."}; + +static const char * const qt_shade_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"..........", +"..........", +"..........", +"..........", +"....#.....", +"...###....", +"..#####...", +".#######..", +"..........", +".........."}; + +static const char * const qt_unshade_xpm[] = { +"10 10 2 1", +"# c #000000", +". c None", +"..........", +"..........", +"..........", +".#######..", +"..#####...", +"...###....", +"....#.....", +"..........", +"..........", +".........."}; + +static const char * dock_widget_close_xpm[] = { +"8 8 2 1", +"# c #000000", +". c None", +"........", +".##..##.", +"..####..", +"...##...", +"..####..", +".##..##.", +"........", +"........"}; + +/* XPM */ +static const char * const information_xpm[]={ +"32 32 5 1", +". c None", +"c c #000000", +"* c #999999", +"a c #ffffff", +"b c #0000ff", +"...........********.............", +"........***aaaaaaaa***..........", +"......**aaaaaaaaaaaaaa**........", +".....*aaaaaaaaaaaaaaaaaa*.......", +"....*aaaaaaaabbbbaaaaaaaac......", +"...*aaaaaaaabbbbbbaaaaaaaac.....", +"..*aaaaaaaaabbbbbbaaaaaaaaac....", +".*aaaaaaaaaaabbbbaaaaaaaaaaac...", +".*aaaaaaaaaaaaaaaaaaaaaaaaaac*..", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaaac*.", +"*aaaaaaaaaabbbbbbbaaaaaaaaaaac*.", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaabbbbbaaaaaaaaaaac**", +".*aaaaaaaaaaabbbbbaaaaaaaaaac***", +".*aaaaaaaaaaabbbbbaaaaaaaaaac***", +"..*aaaaaaaaaabbbbbaaaaaaaaac***.", +"...caaaaaaabbbbbbbbbaaaaaac****.", +"....caaaaaaaaaaaaaaaaaaaac****..", +".....caaaaaaaaaaaaaaaaaac****...", +"......ccaaaaaaaaaaaaaacc****....", +".......*cccaaaaaaaaccc*****.....", +"........***cccaaaac*******......", +"..........****caaac*****........", +".............*caaac**...........", +"...............caac**...........", +"................cac**...........", +".................cc**...........", +"..................***...........", +"...................**..........."}; +/* XPM */ +static const char* const warning_xpm[]={ +"32 32 4 1", +". c None", +"a c #ffff00", +"* c #000000", +"b c #999999", +".............***................", +"............*aaa*...............", +"...........*aaaaa*b.............", +"...........*aaaaa*bb............", +"..........*aaaaaaa*bb...........", +"..........*aaaaaaa*bb...........", +".........*aaaaaaaaa*bb..........", +".........*aaaaaaaaa*bb..........", +"........*aaaaaaaaaaa*bb.........", +"........*aaaa***aaaa*bb.........", +".......*aaaa*****aaaa*bb........", +".......*aaaa*****aaaa*bb........", +"......*aaaaa*****aaaaa*bb.......", +"......*aaaaa*****aaaaa*bb.......", +".....*aaaaaa*****aaaaaa*bb......", +".....*aaaaaa*****aaaaaa*bb......", +"....*aaaaaaaa***aaaaaaaa*bb.....", +"....*aaaaaaaa***aaaaaaaa*bb.....", +"...*aaaaaaaaa***aaaaaaaaa*bb....", +"...*aaaaaaaaaa*aaaaaaaaaa*bb....", +"..*aaaaaaaaaaa*aaaaaaaaaaa*bb...", +"..*aaaaaaaaaaaaaaaaaaaaaaa*bb...", +".*aaaaaaaaaaaa**aaaaaaaaaaa*bb..", +".*aaaaaaaaaaa****aaaaaaaaaa*bb..", +"*aaaaaaaaaaaa****aaaaaaaaaaa*bb.", +"*aaaaaaaaaaaaa**aaaaaaaaaaaa*bb.", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaa*bbb", +".*aaaaaaaaaaaaaaaaaaaaaaaaa*bbbb", +"..*************************bbbbb", +"....bbbbbbbbbbbbbbbbbbbbbbbbbbb.", +".....bbbbbbbbbbbbbbbbbbbbbbbbb.."}; +/* XPM */ +static const char* const critical_xpm[]={ +"32 32 4 1", +". c None", +"a c #999999", +"* c #ff0000", +"b c #ffffff", +"...........********.............", +".........************...........", +".......****************.........", +"......******************........", +".....********************a......", +"....**********************a.....", +"...************************a....", +"..*******b**********b*******a...", +"..******bbb********bbb******a...", +".******bbbbb******bbbbb******a..", +".*******bbbbb****bbbbb*******a..", +"*********bbbbb**bbbbb*********a.", +"**********bbbbbbbbbb**********a.", +"***********bbbbbbbb***********aa", +"************bbbbbb************aa", +"************bbbbbb************aa", +"***********bbbbbbbb***********aa", +"**********bbbbbbbbbb**********aa", +"*********bbbbb**bbbbb*********aa", +".*******bbbbb****bbbbb*******aa.", +".******bbbbb******bbbbb******aa.", +"..******bbb********bbb******aaa.", +"..*******b**********b*******aa..", +"...************************aaa..", +"....**********************aaa...", +"....a********************aaa....", +".....a******************aaa.....", +"......a****************aaa......", +".......aa************aaaa.......", +".........aa********aaaaa........", +"...........aaaaaaaaaaa..........", +".............aaaaaaa............"}; +/* XPM */ +static const char *const question_xpm[] = { +"32 32 5 1", +". c None", +"c c #000000", +"* c #999999", +"a c #ffffff", +"b c #0000ff", +"...........********.............", +"........***aaaaaaaa***..........", +"......**aaaaaaaaaaaaaa**........", +".....*aaaaaaaaaaaaaaaaaa*.......", +"....*aaaaaaaaaaaaaaaaaaaac......", +"...*aaaaaaaabbbbbbaaaaaaaac.....", +"..*aaaaaaaabaaabbbbaaaaaaaac....", +".*aaaaaaaabbaaaabbbbaaaaaaaac...", +".*aaaaaaaabbbbaabbbbaaaaaaaac*..", +"*aaaaaaaaabbbbaabbbbaaaaaaaaac*.", +"*aaaaaaaaaabbaabbbbaaaaaaaaaac*.", +"*aaaaaaaaaaaaabbbbaaaaaaaaaaac**", +"*aaaaaaaaaaaaabbbaaaaaaaaaaaac**", +"*aaaaaaaaaaaaabbaaaaaaaaaaaaac**", +"*aaaaaaaaaaaaabbaaaaaaaaaaaaac**", +"*aaaaaaaaaaaaaaaaaaaaaaaaaaaac**", +".*aaaaaaaaaaaabbaaaaaaaaaaaac***", +".*aaaaaaaaaaabbbbaaaaaaaaaaac***", +"..*aaaaaaaaaabbbbaaaaaaaaaac***.", +"...caaaaaaaaaabbaaaaaaaaaac****.", +"....caaaaaaaaaaaaaaaaaaaac****..", +".....caaaaaaaaaaaaaaaaaac****...", +"......ccaaaaaaaaaaaaaacc****....", +".......*cccaaaaaaaaccc*****.....", +"........***cccaaaac*******......", +"..........****caaac*****........", +".............*caaac**...........", +"...............caac**...........", +"................cac**...........", +".................cc**...........", +"..................***...........", +"...................**..........."}; + +#endif //QT_NO_IMAGEFORMAT_XPM + +#ifdef Q_OS_WIN +static QPixmap loadIconFromShell32( int resourceId, int size ) +{ +#ifdef Q_OS_WINCE + HMODULE hmod = LoadLibrary(L"ceshell"); +#else + HMODULE hmod = QSystemLibrary::load(L"shell32"); +#endif + if( hmod ) { + HICON iconHandle = (HICON)LoadImage(hmod, MAKEINTRESOURCE(resourceId), IMAGE_ICON, size, size, 0); + if( iconHandle ) { + QPixmap iconpixmap = QPixmap::fromWinHICON( iconHandle ); + DestroyIcon(iconHandle); + return iconpixmap; + } + } + return QPixmap(); +} +#endif + +/*! + \reimp + */ +QPixmap QWindowsStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget) const +{ +#if defined(Q_WS_WIN) && !defined(Q_OS_WINCE) + QPixmap desktopIcon; + switch(standardPixmap) { + case SP_DriveCDIcon: + case SP_DriveDVDIcon: + { + desktopIcon = loadIconFromShell32(12, 16); + break; + } + case SP_DriveNetIcon: + { + desktopIcon = loadIconFromShell32(10, 16); + break; + } + case SP_DriveHDIcon: + { + desktopIcon = loadIconFromShell32(9, 16); + break; + } + case SP_DriveFDIcon: + { + desktopIcon = loadIconFromShell32(7, 16); + break; + } + case SP_FileIcon: + { + desktopIcon = loadIconFromShell32(1, 16); + break; + } + case SP_FileLinkIcon: + { + desktopIcon = loadIconFromShell32(1, 16); + QPainter painter(&desktopIcon); + QPixmap link = loadIconFromShell32(30, 16); + painter.drawPixmap(0, 0, 16, 16, link); + break; + } + case SP_DirLinkIcon: + { + desktopIcon = loadIconFromShell32(4, 16); + QPainter painter(&desktopIcon); + QPixmap link = loadIconFromShell32(30, 16); + painter.drawPixmap(0, 0, 16, 16, link); + break; + } + case SP_DirClosedIcon: + { + desktopIcon = loadIconFromShell32(4, 16); + break; + } + case SP_DesktopIcon: + { + desktopIcon = loadIconFromShell32(35, 16); + break; + } + case SP_ComputerIcon: + { + desktopIcon = loadIconFromShell32(16, 16); + break; + } + case SP_DirOpenIcon: + { + desktopIcon = loadIconFromShell32(5, 16); + break; + } + case SP_FileDialogNewFolder: + { + desktopIcon = loadIconFromShell32(319, 16); + break; + } + case SP_DirHomeIcon: + { + desktopIcon = loadIconFromShell32(235, 16); + break; + } + case SP_TrashIcon: + { + desktopIcon = loadIconFromShell32(191, 16); + break; + } + case SP_MessageBoxInformation: + { + HICON iconHandle = LoadIcon(NULL, IDI_INFORMATION); + desktopIcon = QPixmap::fromWinHICON( iconHandle ); + DestroyIcon(iconHandle); + break; + } + case SP_MessageBoxWarning: + { + HICON iconHandle = LoadIcon(NULL, IDI_WARNING); + desktopIcon = QPixmap::fromWinHICON( iconHandle ); + DestroyIcon(iconHandle); + break; + } + case SP_MessageBoxCritical: + { + HICON iconHandle = LoadIcon(NULL, IDI_ERROR); + desktopIcon = QPixmap::fromWinHICON( iconHandle ); + DestroyIcon(iconHandle); + break; + } + case SP_MessageBoxQuestion: + { + HICON iconHandle = LoadIcon(NULL, IDI_QUESTION); + desktopIcon = QPixmap::fromWinHICON( iconHandle ); + DestroyIcon(iconHandle); + break; + } + case SP_VistaShield: + { + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based + && pSHGetStockIconInfo) + { + QPixmap pixmap; + QSHSTOCKICONINFO iconInfo; + memset(&iconInfo, 0, sizeof(iconInfo)); + iconInfo.cbSize = sizeof(iconInfo); + if (pSHGetStockIconInfo(_SIID_SHIELD, _SHGFI_ICON | _SHGFI_SMALLICON, &iconInfo) == S_OK) { + pixmap = QPixmap::fromWinHICON(iconInfo.hIcon); + DestroyIcon(iconInfo.hIcon); + return pixmap; + } + } + } + break; + default: + break; + } + if (!desktopIcon.isNull()) { + return desktopIcon; + } +#endif +#ifndef QT_NO_IMAGEFORMAT_XPM + switch (standardPixmap) { + case SP_TitleBarMenuButton: + return QPixmap(qt_menu_xpm); + case SP_TitleBarShadeButton: + return QPixmap(qt_shade_xpm); + case SP_TitleBarUnshadeButton: + return QPixmap(qt_unshade_xpm); + case SP_TitleBarNormalButton: + return QPixmap(qt_normalizeup_xpm); + case SP_TitleBarMinButton: + return QPixmap(qt_minimize_xpm); + case SP_TitleBarMaxButton: + return QPixmap(qt_maximize_xpm); + case SP_TitleBarCloseButton: + return QPixmap(qt_close_xpm); + case SP_TitleBarContextHelpButton: + return QPixmap(qt_help_xpm); + case SP_DockWidgetCloseButton: + return QPixmap(dock_widget_close_xpm); + case SP_MessageBoxInformation: + return QPixmap(information_xpm); + case SP_MessageBoxWarning: + return QPixmap(warning_xpm); + case SP_MessageBoxCritical: + return QPixmap(critical_xpm); + case SP_MessageBoxQuestion: + return QPixmap(question_xpm); + default: + break; + } +#endif //QT_NO_IMAGEFORMAT_XPM + return QCommonStyle::standardPixmap(standardPixmap, opt, widget); +} + +/*! \reimp */ +int QWindowsStyle::styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + int ret = 0; + + switch (hint) { + case SH_EtchDisabledText: + case SH_Slider_SnapToValue: + case SH_PrintDialog_RightAlignButtons: + case SH_FontDialog_SelectAssociatedText: + case SH_Menu_AllowActiveAndDisabled: + case SH_MenuBar_AltKeyNavigation: + case SH_MenuBar_MouseTracking: + case SH_Menu_MouseTracking: + case SH_ComboBox_ListMouseTracking: + case SH_ScrollBar_StopMouseOverSlider: + case SH_MainWindow_SpaceBelowMenuBar: + ret = 1; + + break; + case SH_ItemView_ShowDecorationSelected: +#ifndef QT_NO_LISTVIEW + if (qobject_cast<const QListView*>(widget)) + ret = 1; +#endif + break; + case SH_ItemView_ChangeHighlightOnFocus: + ret = 1; + break; + case SH_ToolBox_SelectedPageTitleBold: + ret = 0; + break; + +#if defined(Q_WS_WIN) + case SH_UnderlineShortcut: + { + ret = 1; + BOOL cues = false; + SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &cues, 0); + ret = int(cues); + // Do nothing if we always paint underlines + Q_D(const QWindowsStyle); + if (!ret && widget && d) { +#ifndef QT_NO_MENUBAR + const QMenuBar *menuBar = qobject_cast<const QMenuBar *>(widget); + if (!menuBar && qobject_cast<const QMenu *>(widget)) { + QWidget *w = QApplication::activeWindow(); + if (w && w != widget) + menuBar = w->findChild<QMenuBar *>(); + } + // If we paint a menu bar draw underlines if is in the keyboardState + if (menuBar) { + if (menuBar->d_func()->keyboardState || d->altDown()) + ret = 1; + // Otherwise draw underlines if the toplevel widget has seen an alt-press + } else +#endif // QT_NO_MENUBAR + if (d->hasSeenAlt(widget)) { + ret = 1; + } + } + break; + } +#endif +#ifndef QT_NO_RUBBERBAND + case SH_RubberBand_Mask: + if (const QStyleOptionRubberBand *rbOpt = qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + ret = 0; + if (rbOpt->shape == QRubberBand::Rectangle) { + ret = true; + if(QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(returnData)) { + mask->region = opt->rect; + int size = 1; + if (widget && widget->isWindow()) + size = 4; + mask->region -= opt->rect.adjusted(size, size, -size, -size); + } + } + } + break; +#endif // QT_NO_RUBBERBAND + case SH_LineEdit_PasswordCharacter: + { +#ifdef Q_WS_WIN + if (widget && (QSysInfo::WindowsVersion >= QSysInfo::WV_XP && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) { + const QFontMetrics &fm = widget->fontMetrics(); + if (fm.inFont(QChar(0x25CF))) + ret = 0x25CF; + else if (fm.inFont(QChar(0x2022))) + ret = 0x2022; + } +#endif + if (!ret) + ret = '*'; + } + break; +#ifndef QT_NO_WIZARD + case SH_WizardStyle: + ret = QWizard::ModernStyle; + break; +#endif + case SH_ItemView_ArrowKeysNavigateIntoChildren: + ret = true; + break; + case SH_DialogButtonBox_ButtonsHaveIcons: + ret = 0; + break; + default: + ret = QCommonStyle::styleHint(hint, opt, widget, returnData); + break; + } + return ret; +} + +/*! \reimp */ +void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w) const +{ + // Used to restore across fallthrough cases. Currently only used in PE_IndicatorCheckBox + bool doRestore = false; + + switch (pe) { +#ifndef QT_NO_TOOLBAR + case PE_IndicatorToolBarSeparator: + { + QRect rect = opt->rect; + const int margin = 2; + QPen oldPen = p->pen(); + if(opt->state & State_Horizontal){ + const int offset = rect.width()/2; + p->setPen(QPen(opt->palette.dark().color())); + p->drawLine(rect.bottomLeft().x() + offset, + rect.bottomLeft().y() - margin, + rect.topLeft().x() + offset, + rect.topLeft().y() + margin); + p->setPen(QPen(opt->palette.light().color())); + p->drawLine(rect.bottomLeft().x() + offset + 1, + rect.bottomLeft().y() - margin, + rect.topLeft().x() + offset + 1, + rect.topLeft().y() + margin); + } + else{ //Draw vertical separator + const int offset = rect.height()/2; + p->setPen(QPen(opt->palette.dark().color())); + p->drawLine(rect.topLeft().x() + margin , + rect.topLeft().y() + offset, + rect.topRight().x() - margin, + rect.topRight().y() + offset); + p->setPen(QPen(opt->palette.light().color())); + p->drawLine(rect.topLeft().x() + margin , + rect.topLeft().y() + offset + 1, + rect.topRight().x() - margin, + rect.topRight().y() + offset + 1); + } + p->setPen(oldPen); + } + break; + case PE_IndicatorToolBarHandle: + p->save(); + p->translate(opt->rect.x(), opt->rect.y()); + if (opt->state & State_Horizontal) { + int x = opt->rect.width() / 2 - 4; + if (opt->direction == Qt::RightToLeft) + x -= 2; + if (opt->rect.height() > 4) { + qDrawShadePanel(p, x, 2, 3, opt->rect.height() - 4, + opt->palette, false, 1, 0); + qDrawShadePanel(p, x + 3, 2, 3, opt->rect.height() - 4, + opt->palette, false, 1, 0); + } + } else { + if (opt->rect.width() > 4) { + int y = opt->rect.height() / 2 - 4; + qDrawShadePanel(p, 2, y, opt->rect.width() - 4, 3, + opt->palette, false, 1, 0); + qDrawShadePanel(p, 2, y + 3, opt->rect.width() - 4, 3, + opt->palette, false, 1, 0); + } + } + p->restore(); + break; + +#endif // QT_NO_TOOLBAR + case PE_FrameButtonTool: + case PE_PanelButtonTool: { + QPen oldPen = p->pen(); +#ifndef QT_NO_DOCKWIDGET + if (w && w->inherits("QDockWidgetTitleButton")) { + if (const QWidget *dw = w->parentWidget()) + if (dw->isWindow()){ + qDrawWinButton(p, opt->rect.adjusted(1, 1, 0, 0), opt->palette, opt->state & (State_Sunken | State_On), + &opt->palette.button()); + + return; + } + } +#endif // QT_NO_DOCKWIDGET + QBrush fill; + bool stippled; + bool panel = (pe == PE_PanelButtonTool); + if ((!(opt->state & State_Sunken )) + && (!(opt->state & State_Enabled) + || !(opt->state & State_MouseOver && opt->state & State_AutoRaise)) + && (opt->state & State_On)) { + fill = QBrush(opt->palette.light().color(), Qt::Dense4Pattern); + stippled = true; + } else { + fill = opt->palette.brush(QPalette::Button); + stippled = false; + } + + if (opt->state & (State_Raised | State_Sunken | State_On)) { + if (opt->state & State_AutoRaise) { + if(opt->state & (State_Enabled | State_Sunken | State_On)){ + if (panel) + qDrawShadePanel(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), 1, &fill); + else + qDrawShadeRect(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), 1); + } + if (stippled) { + p->setPen(opt->palette.button().color()); + p->drawRect(opt->rect.adjusted(1,1,-2,-2)); + } + } else { + qDrawWinButton(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), panel ? &fill : 0); + } + } else { + p->fillRect(opt->rect, fill); + } + p->setPen(oldPen); + break; } + case PE_PanelButtonCommand: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + QBrush fill; + State flags = opt->state; + QPalette pal = opt->palette; + QRect r = opt->rect; + if (! (flags & State_Sunken) && (flags & State_On)) + fill = QBrush(pal.light().color(), Qt::Dense4Pattern); + else + fill = pal.brush(QPalette::Button); + + if (btn->features & QStyleOptionButton::DefaultButton && flags & State_Sunken) { + p->setPen(pal.dark().color()); + p->setBrush(fill); + p->drawRect(r.adjusted(0, 0, -1, -1)); + } else if (flags & (State_Raised | State_Sunken | State_On | State_Sunken)) { + qDrawWinButton(p, r, pal, flags & (State_Sunken | State_On), + &fill); + } else { + p->fillRect(r, fill); + } + } + break; + case PE_FrameDefaultButton: { + QPen oldPen = p->pen(); + p->setPen(opt->palette.shadow().color()); + QRect rect = opt->rect; + rect.adjust(0, 0, -1, -1); + p->drawRect(rect); + p->setPen(oldPen); + break; + } + case PE_IndicatorArrowUp: + case PE_IndicatorArrowDown: + case PE_IndicatorArrowRight: + case PE_IndicatorArrowLeft: + { + if (opt->rect.width() <= 1 || opt->rect.height() <= 1) + break; + QRect r = opt->rect; + int size = qMin(r.height(), r.width()); + QPixmap pixmap; + QString pixmapName = QStyleHelper::uniqueName(QLatin1String("$qt_ia-") + % QLatin1String(metaObject()->className()), opt, QSize(size, size)) + % HexString<uint>(pe); + if (!QPixmapCache::find(pixmapName, pixmap)) { + int border = size/5; + int sqsize = 2*(size/2); + QImage image(sqsize, sqsize, QImage::Format_ARGB32_Premultiplied); + image.fill(0); + QPainter imagePainter(&image); + + QPolygon a; + switch (pe) { + case PE_IndicatorArrowUp: + a.setPoints(3, border, sqsize/2, sqsize/2, border, sqsize - border, sqsize/2); + break; + case PE_IndicatorArrowDown: + a.setPoints(3, border, sqsize/2, sqsize/2, sqsize - border, sqsize - border, sqsize/2); + break; + case PE_IndicatorArrowRight: + a.setPoints(3, sqsize - border, sqsize/2, sqsize/2, border, sqsize/2, sqsize - border); + break; + case PE_IndicatorArrowLeft: + a.setPoints(3, border, sqsize/2, sqsize/2, border, sqsize/2, sqsize - border); + break; + default: + break; + } + + int bsx = 0; + int bsy = 0; + + if (opt->state & State_Sunken) { + bsx = proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, w); + bsy = proxy()->pixelMetric(PM_ButtonShiftVertical, opt, w); + } + + QRect bounds = a.boundingRect(); + int sx = sqsize / 2 - bounds.center().x() - 1; + int sy = sqsize / 2 - bounds.center().y() - 1; + imagePainter.translate(sx + bsx, sy + bsy); + imagePainter.setPen(opt->palette.buttonText().color()); + imagePainter.setBrush(opt->palette.buttonText()); + + if (!(opt->state & State_Enabled)) { + imagePainter.translate(1, 1); + imagePainter.setBrush(opt->palette.light().color()); + imagePainter.setPen(opt->palette.light().color()); + imagePainter.drawPolygon(a); + imagePainter.translate(-1, -1); + imagePainter.setBrush(opt->palette.mid().color()); + imagePainter.setPen(opt->palette.mid().color()); + } + + imagePainter.drawPolygon(a); + imagePainter.end(); + pixmap = QPixmap::fromImage(image); + QPixmapCache::insert(pixmapName, pixmap); + } + int xOffset = r.x() + (r.width() - size)/2; + int yOffset = r.y() + (r.height() - size)/2; + p->drawPixmap(xOffset, yOffset, pixmap); + } + break; + case PE_IndicatorCheckBox: { + QBrush fill; + if (opt->state & State_NoChange) + fill = QBrush(opt->palette.base().color(), Qt::Dense4Pattern); + else if (opt->state & State_Sunken) + fill = opt->palette.button(); + else if (opt->state & State_Enabled) + fill = opt->palette.base(); + else + fill = opt->palette.background(); + p->save(); + doRestore = true; + qDrawWinPanel(p, opt->rect, opt->palette, true, &fill); + if (opt->state & State_NoChange) + p->setPen(opt->palette.dark().color()); + else + p->setPen(opt->palette.text().color()); + } // Fall through! + case PE_IndicatorViewItemCheck: + case PE_Q3CheckListIndicator: + if (!doRestore) { + p->save(); + doRestore = true; + } + if (pe == PE_Q3CheckListIndicator || pe == PE_IndicatorViewItemCheck) { + const QStyleOptionViewItem *itemViewOpt = qstyleoption_cast<const QStyleOptionViewItem *>(opt); + p->setPen(itemViewOpt + && itemViewOpt->showDecorationSelected + && opt->state & State_Selected + ? opt->palette.highlightedText().color() + : opt->palette.text().color()); + if (opt->state & State_NoChange) + p->setBrush(opt->palette.brush(QPalette::Button)); + p->drawRect(opt->rect.x() + 1, opt->rect.y() + 1, 11, 11); + } + if (!(opt->state & State_Off)) { + QLineF lines[7]; + int i, xx, yy; + xx = opt->rect.x() + 3; + yy = opt->rect.y() + 5; + for (i = 0; i < 3; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + 2); + ++xx; + ++yy; + } + yy -= 2; + for (i = 3; i < 7; ++i) { + lines[i] = QLineF(xx, yy, xx, yy + 2); + ++xx; + --yy; + } + p->drawLines(lines, 7); + } + if (doRestore) + p->restore(); + break; + case PE_FrameFocusRect: + if (const QStyleOptionFocusRect *fropt = qstyleoption_cast<const QStyleOptionFocusRect *>(opt)) { + //### check for d->alt_down + if (!(fropt->state & State_KeyboardFocusChange) && !proxy()->styleHint(SH_UnderlineShortcut, opt)) + return; + QRect r = opt->rect; + p->save(); + p->setBackgroundMode(Qt::TransparentMode); + QColor bg_col = fropt->backgroundColor; + if (!bg_col.isValid()) + bg_col = p->background().color(); + // Create an "XOR" color. + QColor patternCol((bg_col.red() ^ 0xff) & 0xff, + (bg_col.green() ^ 0xff) & 0xff, + (bg_col.blue() ^ 0xff) & 0xff); + p->setBrush(QBrush(patternCol, Qt::Dense4Pattern)); + p->setBrushOrigin(r.topLeft()); + p->setPen(Qt::NoPen); + p->drawRect(r.left(), r.top(), r.width(), 1); // Top + p->drawRect(r.left(), r.bottom(), r.width(), 1); // Bottom + p->drawRect(r.left(), r.top(), 1, r.height()); // Left + p->drawRect(r.right(), r.top(), 1, r.height()); // Right + p->restore(); + } + break; + case PE_IndicatorRadioButton: + { +#define PTSARRLEN(x) sizeof(x)/(sizeof(QPoint)) + static const QPoint pts1[] = { // dark lines + QPoint(1, 9), QPoint(1, 8), QPoint(0, 7), QPoint(0, 4), QPoint(1, 3), QPoint(1, 2), + QPoint(2, 1), QPoint(3, 1), QPoint(4, 0), QPoint(7, 0), QPoint(8, 1), QPoint(9, 1) + }; + static const QPoint pts2[] = { // black lines + QPoint(2, 8), QPoint(1, 7), QPoint(1, 4), QPoint(2, 3), QPoint(2, 2), QPoint(3, 2), + QPoint(4, 1), QPoint(7, 1), QPoint(8, 2), QPoint(9, 2) + }; + static const QPoint pts3[] = { // background lines + QPoint(2, 9), QPoint(3, 9), QPoint(4, 10), QPoint(7, 10), QPoint(8, 9), QPoint(9, 9), + QPoint(9, 8), QPoint(10, 7), QPoint(10, 4), QPoint(9, 3) + }; + static const QPoint pts4[] = { // white lines + QPoint(2, 10), QPoint(3, 10), QPoint(4, 11), QPoint(7, 11), QPoint(8, 10), + QPoint(9, 10), QPoint(10, 9), QPoint(10, 8), QPoint(11, 7), QPoint(11, 4), + QPoint(10, 3), QPoint(10, 2) + }; + static const QPoint pts5[] = { // inner fill + QPoint(4, 2), QPoint(7, 2), QPoint(9, 4), QPoint(9, 7), QPoint(7, 9), QPoint(4, 9), + QPoint(2, 7), QPoint(2, 4) + }; + + // make sure the indicator is square + QRect ir = opt->rect; + + if (opt->rect.width() < opt->rect.height()) { + ir.setTop(opt->rect.top() + (opt->rect.height() - opt->rect.width()) / 2); + ir.setHeight(opt->rect.width()); + } else if (opt->rect.height() < opt->rect.width()) { + ir.setLeft(opt->rect.left() + (opt->rect.width() - opt->rect.height()) / 2); + ir.setWidth(opt->rect.height()); + } + + p->save(); + bool down = opt->state & State_Sunken; + bool enabled = opt->state & State_Enabled; + bool on = opt->state & State_On; + QPolygon a; + + //center when rect is larger than indicator size + int xOffset = 0; + int yOffset = 0; + int indicatorWidth = proxy()->pixelMetric(PM_ExclusiveIndicatorWidth); + int indicatorHeight = proxy()->pixelMetric(PM_ExclusiveIndicatorWidth); + if (ir.width() > indicatorWidth) + xOffset += (ir.width() - indicatorWidth)/2; + if (ir.height() > indicatorHeight) + yOffset += (ir.height() - indicatorHeight)/2; + p->translate(xOffset, yOffset); + + p->translate(ir.x(), ir.y()); + + p->setPen(opt->palette.dark().color()); + p->drawPolyline(pts1, PTSARRLEN(pts1)); + + p->setPen(opt->palette.shadow().color()); + p->drawPolyline(pts2, PTSARRLEN(pts2)); + + p->setPen(opt->palette.midlight().color()); + p->drawPolyline(pts3, PTSARRLEN(pts3)); + + p->setPen(opt->palette.light().color()); + p->drawPolyline(pts4, PTSARRLEN(pts4)); + + QColor fillColor = (down || !enabled) + ? opt->palette.button().color() + : opt->palette.base().color(); + p->setPen(fillColor); + p->setBrush(fillColor) ; + p->drawPolygon(pts5, PTSARRLEN(pts5)); + + p->translate(-ir.x(), -ir.y()); // restore translate + + if (on) { + p->setPen(Qt::NoPen); + p->setBrush(opt->palette.text()); + p->drawRect(ir.x() + 5, ir.y() + 4, 2, 4); + p->drawRect(ir.x() + 4, ir.y() + 5, 4, 2); + } + p->restore(); + break; + } +#ifndef QT_NO_FRAME + case PE_Frame: + case PE_FrameMenu: + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + if (frame->lineWidth == 2 || pe == PE_Frame) { + QPalette popupPal = frame->palette; + if (pe == PE_FrameMenu) { + popupPal.setColor(QPalette::Light, frame->palette.background().color()); + popupPal.setColor(QPalette::Midlight, frame->palette.light().color()); + } + if (pe == PE_Frame && (frame->state & State_Raised)) + qDrawWinButton(p, frame->rect, popupPal, frame->state & State_Sunken); + else if (pe == PE_Frame && (frame->state & State_Sunken)) + { + popupPal.setColor(QPalette::Midlight, frame->palette.background().color()); + qDrawWinPanel(p, frame->rect, popupPal, frame->state & State_Sunken); + } + else + qDrawWinPanel(p, frame->rect, popupPal, frame->state & State_Sunken); + } else { + QCommonStyle::drawPrimitive(pe, opt, p, w); + } + } else { + QPalette popupPal = opt->palette; + popupPal.setColor(QPalette::Light, opt->palette.background().color()); + popupPal.setColor(QPalette::Midlight, opt->palette.light().color()); + qDrawWinPanel(p, opt->rect, popupPal, opt->state & State_Sunken); + } + break; +#endif // QT_NO_FRAME + case PE_IndicatorBranch: { + // This is _way_ too similar to the common style. + static const int decoration_size = 9; + int mid_h = opt->rect.x() + opt->rect.width() / 2; + int mid_v = opt->rect.y() + opt->rect.height() / 2; + int bef_h = mid_h; + int bef_v = mid_v; + int aft_h = mid_h; + int aft_v = mid_v; + if (opt->state & State_Children) { + int delta = decoration_size / 2; + bef_h -= delta; + bef_v -= delta; + aft_h += delta; + aft_v += delta; + p->drawLine(bef_h + 2, bef_v + 4, bef_h + 6, bef_v + 4); + if (!(opt->state & State_Open)) + p->drawLine(bef_h + 4, bef_v + 2, bef_h + 4, bef_v + 6); + QPen oldPen = p->pen(); + p->setPen(opt->palette.dark().color()); + p->drawRect(bef_h, bef_v, decoration_size - 1, decoration_size - 1); + p->setPen(oldPen); + } + QBrush brush(opt->palette.dark().color(), Qt::Dense4Pattern); + if (opt->state & State_Item) { + if (opt->direction == Qt::RightToLeft) + p->fillRect(opt->rect.left(), mid_v, bef_h - opt->rect.left(), 1, brush); + else + p->fillRect(aft_h, mid_v, opt->rect.right() - aft_h + 1, 1, brush); + } + if (opt->state & State_Sibling) + p->fillRect(mid_h, aft_v, 1, opt->rect.bottom() - aft_v + 1, brush); + if (opt->state & (State_Open | State_Children | State_Item | State_Sibling)) + p->fillRect(mid_h, opt->rect.y(), 1, bef_v - opt->rect.y(), brush); + break; } + case PE_FrameButtonBevel: + case PE_PanelButtonBevel: { + QBrush fill; + bool panel = pe != PE_FrameButtonBevel; + p->setBrushOrigin(opt->rect.topLeft()); + if (!(opt->state & State_Sunken) && (opt->state & State_On)) + fill = QBrush(opt->palette.light().color(), Qt::Dense4Pattern); + else + fill = opt->palette.brush(QPalette::Button); + + if (opt->state & (State_Raised | State_On | State_Sunken)) { + qDrawWinButton(p, opt->rect, opt->palette, opt->state & (State_Sunken | State_On), + panel ? &fill : 0); + } else { + if (panel) + p->fillRect(opt->rect, fill); + else + p->drawRect(opt->rect); + } + break; } + case PE_FrameWindow: { + QPalette popupPal = opt->palette; + popupPal.setColor(QPalette::Light, opt->palette.background().color()); + popupPal.setColor(QPalette::Midlight, opt->palette.light().color()); + qDrawWinPanel(p, opt->rect, popupPal, opt->state & State_Sunken); + break; } +#ifndef QT_NO_DOCKWIDGET + case PE_IndicatorDockWidgetResizeHandle: { + QPen oldPen = p->pen(); + p->setPen(opt->palette.light().color()); + if (opt->state & State_Horizontal) { + p->drawLine(opt->rect.left(), opt->rect.top(), + opt->rect.right(), opt->rect.top()); + p->setPen(opt->palette.dark().color()); + p->drawLine(opt->rect.left(), opt->rect.bottom() - 1, + opt->rect.right(), opt->rect.bottom() - 1); + p->setPen(opt->palette.shadow().color()); + p->drawLine(opt->rect.left(), opt->rect.bottom(), + opt->rect.right(), opt->rect.bottom()); + } else { + p->drawLine(opt->rect.left(), opt->rect.top(), + opt->rect.left(), opt->rect.bottom()); + p->setPen(opt->palette.dark().color()); + p->drawLine(opt->rect.right() - 1, opt->rect.top(), + opt->rect.right() - 1, opt->rect.bottom()); + p->setPen(opt->palette.shadow().color()); + p->drawLine(opt->rect.right(), opt->rect.top(), + opt->rect.right(), opt->rect.bottom()); + } + p->setPen(oldPen); + break; } +case PE_FrameDockWidget: + if (qstyleoption_cast<const QStyleOptionFrame *>(opt)) { + proxy()->drawPrimitive(QStyle::PE_FrameWindow, opt, p, w); + } + break; +#endif // QT_NO_DOCKWIDGET + + case PE_FrameStatusBarItem: + qDrawShadePanel(p, opt->rect, opt->palette, true, 1, 0); + break; + +#ifndef QT_NO_PROGRESSBAR + case PE_IndicatorProgressChunk: + { + bool vertical = false, inverted = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt)) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + + int space = 2; + int chunksize = proxy()->pixelMetric(PM_ProgressBarChunkWidth, opt, w) - space; + if (!vertical) { + if (opt->rect.width() <= chunksize) + space = 0; + + if (inverted) + p->fillRect(opt->rect.x() + space, opt->rect.y(), opt->rect.width() - space, opt->rect.height(), + opt->palette.brush(QPalette::Highlight)); + else + p->fillRect(opt->rect.x(), opt->rect.y(), opt->rect.width() - space, opt->rect.height(), + opt->palette.brush(QPalette::Highlight)); + } else { + if (opt->rect.height() <= chunksize) + space = 0; + + if (inverted) + p->fillRect(opt->rect.x(), opt->rect.y(), opt->rect.width(), opt->rect.height() - space, + opt->palette.brush(QPalette::Highlight)); + else + p->fillRect(opt->rect.x(), opt->rect.y() + space, opt->rect.width(), opt->rect.height() - space, + opt->palette.brush(QPalette::Highlight)); + } + } + break; +#endif // QT_NO_PROGRESSBAR + + case PE_FrameTabWidget: { + qDrawWinButton(p, opt->rect, opt->palette, false, 0); + break; + } + default: + QCommonStyle::drawPrimitive(pe, opt, p, w); + } +} + +/*! \reimp */ +void QWindowsStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, + const QWidget *widget) const +{ + switch (ce) { +#ifndef QT_NO_RUBBERBAND + case CE_RubberBand: + if (qstyleoption_cast<const QStyleOptionRubberBand *>(opt)) { + // ### workaround for slow general painter path + QPixmap tiledPixmap(16, 16); + QPainter pixmapPainter(&tiledPixmap); + pixmapPainter.setPen(Qt::NoPen); + pixmapPainter.setBrush(Qt::Dense4Pattern); + pixmapPainter.setBackground(Qt::white); + pixmapPainter.setBackgroundMode(Qt::OpaqueMode); + pixmapPainter.drawRect(0, 0, tiledPixmap.width(), tiledPixmap.height()); + pixmapPainter.end(); + tiledPixmap = QPixmap::fromImage(tiledPixmap.toImage()); + p->save(); + QRect r = opt->rect; + QStyleHintReturnMask mask; + if (proxy()->styleHint(QStyle::SH_RubberBand_Mask, opt, widget, &mask)) + p->setClipRegion(mask.region); + p->drawTiledPixmap(r.x(), r.y(), r.width(), r.height(), tiledPixmap); + p->restore(); + return; + } + break; +#endif // QT_NO_RUBBERBAND + +#if !defined(QT_NO_MENU) && !defined(QT_NO_MAINWINDOW) + case CE_MenuBarEmptyArea: + if (widget && qobject_cast<const QMainWindow *>(widget->parentWidget())) { + p->fillRect(opt->rect, opt->palette.button()); + QPen oldPen = p->pen(); + p->setPen(QPen(opt->palette.dark().color())); + p->drawLine(opt->rect.bottomLeft(), opt->rect.bottomRight()); + p->setPen(oldPen); + } + break; +#endif +#ifndef QT_NO_MENU + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + 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, QWindowsStylePrivate::windowsCheckMarkWidth); + + QBrush fill = menuitem->palette.brush(act ? QPalette::Highlight : QPalette::Button); + p->fillRect(menuitem->rect.adjusted(0, 0, -1, 0), fill); + + if (menuitem->menuItemType == QStyleOptionMenuItem::Separator){ + int yoff = y-1 + h / 2; + p->setPen(menuitem->palette.dark().color()); + p->drawLine(x + 2, yoff, x + w - 4, yoff); + p->setPen(menuitem->palette.light().color()); + p->drawLine(x + 2, yoff + 1, x + w - 4, yoff + 1); + return; + } + + QRect vCheckRect = visualRect(opt->direction, menuitem->rect, QRect(menuitem->rect.x(), menuitem->rect.y(), checkcol, menuitem->rect.height())); + if (checked) { + if (act && !dis) { + qDrawShadePanel(p, vCheckRect, + menuitem->palette, true, 1, + &menuitem->palette.brush(QPalette::Button)); + } else { + QBrush fill(menuitem->palette.light().color(), Qt::Dense4Pattern); + qDrawShadePanel(p, vCheckRect, menuitem->palette, true, 1, &fill); + } + } else if (!act) { + p->fillRect(vCheckRect, menuitem->palette.brush(QPalette::Button)); + } + + // 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, opt, widget), mode, QIcon::On); + else + pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, opt, widget), mode); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + if (act && !dis && !checked) + qDrawShadePanel(p, vCheckRect, menuitem->palette, false, 1, + &menuitem->palette.brush(QPalette::Button)); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center()); + p->setPen(menuitem->palette.text().color()); + p->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; + newMi.rect = visualRect(opt->direction, menuitem->rect, QRect(menuitem->rect.x() + QWindowsStylePrivate::windowsItemFrame, + menuitem->rect.y() + QWindowsStylePrivate::windowsItemFrame, + checkcol - 2 * QWindowsStylePrivate::windowsItemFrame, + menuitem->rect.height() - 2 * QWindowsStylePrivate::windowsItemFrame)); + proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, p, widget); + } + p->setPen(act ? menuitem->palette.highlightedText().color() : menuitem->palette.buttonText().color()); + + QColor discol; + if (dis) { + discol = menuitem->palette.text().color(); + p->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(opt->direction, menuitem->rect, textRect); + QString s = menuitem->text; + if (!s.isEmpty()) { // draw text + p->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(opt->direction, menuitem->rect, + QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom()))); + if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, opt, widget)) { + p->setPen(menuitem->palette.light().color()); + p->drawText(vShortcutRect.adjusted(1,1,1,1), text_flags, s.mid(t + 1)); + p->setPen(discol); + } + 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, opt, widget)) { + p->setPen(menuitem->palette.light().color()); + p->drawText(vTextRect.adjusted(1,1,1,1), text_flags, s.left(t)); + p->setPen(discol); + } + p->drawText(vTextRect, text_flags, s.left(t)); + p->restore(); + } + if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow + int dim = (h - 2 * QWindowsStylePrivate::windowsItemFrame) / 2; + PrimitiveElement arrow; + arrow = (opt->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + xpos = x + w - QWindowsStylePrivate::windowsArrowHMargin - QWindowsStylePrivate::windowsItemFrame - dim; + QRect vSubMenuRect = visualRect(opt->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()); + proxy()->drawPrimitive(arrow, &newMI, p, widget); + } + + } + break; +#endif // QT_NO_MENU +#ifndef QT_NO_MENUBAR + case CE_MenuBarItem: + if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + bool active = mbi->state & State_Selected; + bool hasFocus = mbi->state & State_HasFocus; + bool down = mbi->state & State_Sunken; + QStyleOptionMenuItem newMbi = *mbi; + p->fillRect(mbi->rect, mbi->palette.brush(QPalette::Button)); + if (active || hasFocus) { + QBrush b = mbi->palette.brush(QPalette::Button); + if (active && down) + p->setBrushOrigin(p->brushOrigin() + QPoint(1, 1)); + if (active && hasFocus) + qDrawShadeRect(p, mbi->rect.x(), mbi->rect.y(), mbi->rect.width(), + mbi->rect.height(), mbi->palette, active && down, 1, 0, &b); + if (active && down) { + newMbi.rect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, mbi, widget), + proxy()->pixelMetric(PM_ButtonShiftVertical, mbi, widget)); + p->setBrushOrigin(p->brushOrigin() - QPoint(1, 1)); + } + } + QCommonStyle::drawControl(ce, &newMbi, p, widget); + } + break; +#endif // QT_NO_MENUBAR +#ifndef QT_NO_TABBAR + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + bool rtlHorTabs = (tab->direction == Qt::RightToLeft + && (tab->shape == QTabBar::RoundedNorth + || tab->shape == QTabBar::RoundedSouth)); + bool selected = tab->state & State_Selected; + bool lastTab = ((!rtlHorTabs && tab->position == QStyleOptionTab::End) + || (rtlHorTabs + && tab->position == QStyleOptionTab::Beginning)); + bool firstTab = ((!rtlHorTabs + && tab->position == QStyleOptionTab::Beginning) + || (rtlHorTabs + && tab->position == QStyleOptionTab::End)); + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + bool previousSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::PreviousIsSelected) + || (rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected)); + bool nextSelected = + ((!rtlHorTabs + && tab->selectedPosition == QStyleOptionTab::NextIsSelected) + || (rtlHorTabs + && tab->selectedPosition + == QStyleOptionTab::PreviousIsSelected)); + int tabBarAlignment = proxy()->styleHint(SH_TabBar_Alignment, tab, widget); + bool leftAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignLeft) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignRight); + + bool rightAligned = (!rtlHorTabs && tabBarAlignment == Qt::AlignRight) + || (rtlHorTabs + && tabBarAlignment == Qt::AlignLeft); + + QColor light = tab->palette.light().color(); + QColor midlight = tab->palette.midlight().color(); + QColor dark = tab->palette.dark().color(); + QColor shadow = tab->palette.shadow().color(); + QColor background = tab->palette.background().color(); + int borderThinkness = proxy()->pixelMetric(PM_TabBarBaseOverlap, tab, widget); + if (selected) + borderThinkness /= 2; + QRect r2(opt->rect); + int x1 = r2.left(); + int x2 = r2.right(); + int y1 = r2.top(); + int y2 = r2.bottom(); + switch (tab->shape) { + default: + QCommonStyle::drawControl(ce, tab, p, widget); + break; + case QTabBar::RoundedNorth: { + if (!selected) { + y1 += 2; + x1 += onlyOne || firstTab ? borderThinkness : 0; + x2 -= onlyOne || lastTab ? borderThinkness : 0; + } + + p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 2), tab->palette.background()); + + // Delete border + if (selected) { + p->fillRect(QRect(x1,y2-1,x2-x1,1), tab->palette.background()); + p->fillRect(QRect(x1,y2,x2-x1,1), tab->palette.background()); + } + // Left + if (firstTab || selected || onlyOne || !previousSelected) { + p->setPen(light); + p->drawLine(x1, y1 + 2, x1, y2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + p->drawPoint(x1 + 1, y1 + 1); + } + // Top + { + int beg = x1 + (previousSelected ? 0 : 2); + int end = x2 - (nextSelected ? 0 : 2); + p->setPen(light); + p->drawLine(beg, y1, end, y1); + } + // Right + if (lastTab || selected || onlyOne || !nextSelected) { + p->setPen(shadow); + p->drawLine(x2, y1 + 2, x2, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + p->drawPoint(x2 - 1, y1 + 1); + p->setPen(dark); + p->drawLine(x2 - 1, y1 + 2, x2 - 1, y2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + } + break; } + case QTabBar::RoundedSouth: { + if (!selected) { + y2 -= 2; + x1 += firstTab ? borderThinkness : 0; + x2 -= lastTab ? borderThinkness : 0; + } + + p->fillRect(QRect(x1 + 1, y1 + 2, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + p->fillRect(QRect(x1, y1 + 1, (x2 - 1)-x1, 1), tab->palette.background()); + p->fillRect(QRect(x1, y1, (x2 - 1)-x1, 1), tab->palette.background()); + } + // Left + if (firstTab || selected || onlyOne || !previousSelected) { + p->setPen(light); + p->drawLine(x1, y2 - 2, x1, y1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness)); + p->drawPoint(x1 + 1, y2 - 1); + } + // Bottom + { + int beg = x1 + (previousSelected ? 0 : 2); + int end = x2 - (nextSelected ? 0 : 2); + p->setPen(shadow); + p->drawLine(beg, y2, end, y2); + p->setPen(dark); + p->drawLine(beg, y2 - 1, end, y2 - 1); + } + // Right + if (lastTab || selected || onlyOne || !nextSelected) { + p->setPen(shadow); + p->drawLine(x2, y2 - 2, x2, y1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + p->drawPoint(x2 - 1, y2 - 1); + p->setPen(dark); + p->drawLine(x2 - 1, y2 - 2, x2 - 1, y1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness)); + } + break; } + case QTabBar::RoundedWest: { + if (!selected) { + x1 += 2; + y1 += firstTab ? borderThinkness : 0; + y2 -= lastTab ? borderThinkness : 0; + } + + p->fillRect(QRect(x1 + 1, y1 + 1, (x2 - x1) - 2, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + p->fillRect(QRect(x2 - 1, y1, 1, y2-y1), tab->palette.background()); + p->fillRect(QRect(x2, y1, 1, y2-y1), tab->palette.background()); + } + // Top + if (firstTab || selected || onlyOne || !previousSelected) { + p->setPen(light); + p->drawLine(x1 + 2, y1, x2 - ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1); + p->drawPoint(x1 + 1, y1 + 1); + } + // Left + { + int beg = y1 + (previousSelected ? 0 : 2); + int end = y2 - (nextSelected ? 0 : 2); + p->setPen(light); + p->drawLine(x1, beg, x1, end); + } + // Bottom + if (lastTab || selected || onlyOne || !nextSelected) { + p->setPen(shadow); + p->drawLine(x1 + 3, y2, x2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2); + p->drawPoint(x1 + 2, y2 - 1); + p->setPen(dark); + p->drawLine(x1 + 3, y2 - 1, x2 - ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2 - 1); + p->drawPoint(x1 + 1, y2 - 1); + p->drawPoint(x1 + 2, y2); + } + break; } + case QTabBar::RoundedEast: { + if (!selected) { + x2 -= 2; + y1 += firstTab ? borderThinkness : 0; + y2 -= lastTab ? borderThinkness : 0; + } + + p->fillRect(QRect(x1 + 2, y1 + 1, (x2 - x1) - 1, (y2 - y1) - 1), tab->palette.background()); + + // Delete border + if (selected) { + p->fillRect(QRect(x1 + 1, y1, 1, (y2 - 1)-y1),tab->palette.background()); + p->fillRect(QRect(x1, y1, 1, (y2-1)-y1), tab->palette.background()); + } + // Top + if (firstTab || selected || onlyOne || !previousSelected) { + p->setPen(light); + p->drawLine(x2 - 2, y1, x1 + ((onlyOne || firstTab) && selected && leftAligned ? 0 : borderThinkness), y1); + p->drawPoint(x2 - 1, y1 + 1); + } + // Right + { + int beg = y1 + (previousSelected ? 0 : 2); + int end = y2 - (nextSelected ? 0 : 2); + p->setPen(shadow); + p->drawLine(x2, beg, x2, end); + p->setPen(dark); + p->drawLine(x2 - 1, beg, x2 - 1, end); + } + // Bottom + if (lastTab || selected || onlyOne || !nextSelected) { + p->setPen(shadow); + p->drawLine(x2 - 2, y2, x1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2); + p->drawPoint(x2 - 1, y2 - 1); + p->setPen(dark); + p->drawLine(x2 - 2, y2 - 1, x1 + ((onlyOne || lastTab) && selected && rightAligned ? 0 : borderThinkness), y2 - 1); + } + break; } + } + } + break; +#endif // QT_NO_TABBAR + case CE_ToolBoxTabShape: + qDrawShadePanel(p, opt->rect, opt->palette, + opt->state & (State_Sunken | State_On), 1, + &opt->palette.brush(QPalette::Button)); + break; +#ifndef QT_NO_SPLITTER + case CE_Splitter: + p->eraseRect(opt->rect); + break; +#endif // QT_NO_SPLITTER +#ifndef QT_NO_SCROLLBAR + case CE_ScrollBarSubLine: + case CE_ScrollBarAddLine: { + if ((opt->state & State_Sunken)) { + p->setPen(opt->palette.dark().color()); + p->setBrush(opt->palette.brush(QPalette::Button)); + p->drawRect(opt->rect.adjusted(0, 0, -1, -1)); + } else { + QStyleOption buttonOpt = *opt; + if (!(buttonOpt.state & State_Sunken)) + buttonOpt.state |= State_Raised; + QPalette pal(opt->palette); + pal.setColor(QPalette::Button, opt->palette.light().color()); + pal.setColor(QPalette::Light, opt->palette.button().color()); + qDrawWinButton(p, opt->rect, pal, opt->state & (State_Sunken | State_On), + &opt->palette.brush(QPalette::Button)); + } + PrimitiveElement arrow; + if (opt->state & State_Horizontal) { + if (ce == CE_ScrollBarAddLine) + arrow = opt->direction == Qt::LeftToRight ? PE_IndicatorArrowRight : PE_IndicatorArrowLeft; + else + arrow = opt->direction == Qt::LeftToRight ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight; + } else { + if (ce == CE_ScrollBarAddLine) + arrow = PE_IndicatorArrowDown; + else + arrow = PE_IndicatorArrowUp; + } + QStyleOption arrowOpt = *opt; + arrowOpt.rect = opt->rect.adjusted(4, 4, -4, -4); + proxy()->drawPrimitive(arrow, &arrowOpt, p, widget); + break; } + case CE_ScrollBarAddPage: + case CE_ScrollBarSubPage: { + QBrush br; + QBrush bg = p->background(); + Qt::BGMode bg_mode = p->backgroundMode(); + p->setPen(Qt::NoPen); + p->setBackgroundMode(Qt::OpaqueMode); + + if (opt->state & State_Sunken) { + br = QBrush(opt->palette.shadow().color(), Qt::Dense4Pattern); + p->setBackground(opt->palette.dark().color()); + p->setBrush(br); + } else { + QPixmap pm = opt->palette.brush(QPalette::Light).texture(); + br = !pm.isNull() ? QBrush(pm) : QBrush(opt->palette.light().color(), Qt::Dense4Pattern); + p->setBackground(opt->palette.background().color()); + p->setBrush(br); + } + p->drawRect(opt->rect); + p->setBackground(bg); + p->setBackgroundMode(bg_mode); + break; } + case CE_ScrollBarSlider: + if (!(opt->state & State_Enabled)) { + QPixmap pm = opt->palette.brush(QPalette::Light).texture(); + QBrush br = !pm.isNull() ? QBrush(pm) : QBrush(opt->palette.light().color(), Qt::Dense4Pattern); + p->setPen(Qt::NoPen); + p->setBrush(br); + p->setBackgroundMode(Qt::OpaqueMode); + p->drawRect(opt->rect); + } else { + QStyleOptionButton buttonOpt; + buttonOpt.QStyleOption::operator=(*opt); + buttonOpt.state = State_Enabled | State_Raised; + + QPalette pal(opt->palette); + pal.setColor(QPalette::Button, opt->palette.light().color()); + pal.setColor(QPalette::Light, opt->palette.button().color()); + qDrawWinButton(p, opt->rect, pal, false, &opt->palette.brush(QPalette::Button)); + } + break; +#endif // QT_NO_SCROLLBAR + case CE_HeaderSection: { + QBrush fill; + if (opt->state & State_On) + fill = QBrush(opt->palette.light().color(), Qt::Dense4Pattern); + else + fill = opt->palette.brush(QPalette::Button); + + if (opt->state & (State_Raised | State_Sunken)) { + qDrawWinButton(p, opt->rect, opt->palette, opt->state & State_Sunken, &fill); + } else { + p->fillRect(opt->rect, fill); + } + break; } +#ifndef QT_NO_TOOLBAR + case CE_ToolBar: + if (const QStyleOptionToolBar *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(opt)) { + QRect rect = opt->rect; + + bool paintLeftBorder = true; + bool paintRightBorder = true; + bool paintBottomBorder = true; + + switch (toolbar->toolBarArea){ + case Qt::BottomToolBarArea : + switch(toolbar->positionOfLine){ + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintBottomBorder = false; + default: + break; + } + case Qt::TopToolBarArea : + switch(toolbar->positionWithinLine){ + case QStyleOptionToolBar::Beginning: + paintLeftBorder = false; + break; + case QStyleOptionToolBar::End: + paintRightBorder = false; + break; + case QStyleOptionToolBar::OnlyOne: + paintRightBorder = false; + paintLeftBorder = false; + default: + break; + } + if(opt->direction == Qt::RightToLeft){ //reverse layout changes the order of Beginning/end + bool tmp = paintLeftBorder; + paintRightBorder=paintLeftBorder; + paintLeftBorder=tmp; + } + break; + case Qt::RightToolBarArea : + switch (toolbar->positionOfLine){ + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintRightBorder = false; + break; + default: + break; + } + break; + case Qt::LeftToolBarArea : + switch (toolbar->positionOfLine){ + case QStyleOptionToolBar::Beginning: + case QStyleOptionToolBar::OnlyOne: + paintLeftBorder = false; + break; + default: + break; + } + break; + default: + break; + } + + + //draw top border + p->setPen(QPen(opt->palette.light().color())); + p->drawLine(rect.topLeft().x(), + rect.topLeft().y(), + rect.topRight().x(), + rect.topRight().y()); + + if (paintLeftBorder){ + p->setPen(QPen(opt->palette.light().color())); + p->drawLine(rect.topLeft().x(), + rect.topLeft().y(), + rect.bottomLeft().x(), + rect.bottomLeft().y()); + } + + if (paintRightBorder){ + p->setPen(QPen(opt->palette.dark().color())); + p->drawLine(rect.topRight().x(), + rect.topRight().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + } + + if (paintBottomBorder){ + p->setPen(QPen(opt->palette.dark().color())); + p->drawLine(rect.bottomLeft().x(), + rect.bottomLeft().y(), + rect.bottomRight().x(), + rect.bottomRight().y()); + } + } + break; + + +#endif // QT_NO_TOOLBAR +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(opt)) { + QRect rect = pb->rect; + if (!rect.isValid()) + return; + + bool vertical = false; + bool inverted = false; + + // Get extra style options if version 2 + const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(opt); + if (pb2) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + QMatrix m; + if (vertical) { + rect = QRect(rect.y(), rect.x(), rect.height(), rect.width()); // flip width and height + m.rotate(90); + m.translate(0, -(rect.height() + rect.y()*2)); + } + QPalette pal2 = pb->palette; + // Correct the highlight color if it is the same as the background + if (pal2.highlight() == pal2.background()) + pal2.setColor(QPalette::Highlight, pb->palette.color(QPalette::Active, + QPalette::Highlight)); + bool reverse = ((!vertical && (pb->direction == Qt::RightToLeft)) || vertical); + if (inverted) + reverse = !reverse; + int w = rect.width(); + if (pb->minimum == 0 && pb->maximum == 0) { + Q_D(const QWindowsStyle); + const int unit_width = proxy()->pixelMetric(PM_ProgressBarChunkWidth, pb, widget); + QStyleOptionProgressBarV2 pbBits = *pb; + Q_ASSERT(unit_width >0); + + pbBits.rect = rect; + pbBits.palette = pal2; + + int chunkCount = w / unit_width + 1; + int step = d->animateStep%chunkCount; + int chunksInRow = 5; + int myY = pbBits.rect.y(); + int myHeight = pbBits.rect.height(); + int chunksToDraw = chunksInRow; + + if(step > chunkCount - 5)chunksToDraw = (chunkCount - step); + p->save(); + p->setClipRect(m.mapRect(QRectF(rect)).toRect()); + + int x0 = reverse ? rect.left() + rect.width() - unit_width*(step) - unit_width : rect.left() + unit_width * step; + int x = 0; + + for (int i = 0; i < chunksToDraw ; ++i) { + pbBits.rect.setRect(x0 + x, myY, unit_width, myHeight); + pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect(); + proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget); + x += reverse ? -unit_width : unit_width; + } + //Draw wrap-around chunks + if( step > chunkCount-5){ + x0 = reverse ? rect.left() + rect.width() - unit_width : rect.left() ; + x = 0; + int chunksToDraw = step - (chunkCount - chunksInRow); + for (int i = 0; i < chunksToDraw ; ++i) { + pbBits.rect.setRect(x0 + x, myY, unit_width, myHeight); + pbBits.rect = m.mapRect(QRectF(pbBits.rect)).toRect(); + proxy()->drawPrimitive(PE_IndicatorProgressChunk, &pbBits, p, widget); + x += reverse ? -unit_width : unit_width; + } + } + p->restore(); //restore state + } + else { + QCommonStyle::drawControl(ce, opt, p, widget); + } + } + break; +#endif // QT_NO_PROGRESSBAR + +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(opt)) { + Q_D(const QWindowsStyle); + + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(opt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + QRect rect = dwOpt->rect; + QRect r = rect; + + if (verticalTitleBar) { + QSize s = r.size(); + s.transpose(); + r.setSize(s); + + p->save(); + p->translate(r.left(), r.top() + r.width()); + p->rotate(-90); + p->translate(-r.left(), -r.top()); + } + + bool floating = false; + bool active = dwOpt->state & State_Active; + QColor inactiveCaptionTextColor = d->inactiveCaptionText; + if (dwOpt->movable) { + QColor left, right; + + //Titlebar gradient + if (widget && widget->isWindow()) { + floating = true; + if (active) { + left = d->activeCaptionColor; + right = d->activeGradientCaptionColor; + } else { + left = d->inactiveCaptionColor; + right = d->inactiveGradientCaptionColor; + } + QBrush fillBrush(left); + if (left != right) { + QPoint p1(r.x(), r.top() + r.height()/2); + QPoint p2(rect.right(), r.top() + r.height()/2); + QLinearGradient lg(p1, p2); + lg.setColorAt(0, left); + lg.setColorAt(1, right); + fillBrush = lg; + } + p->fillRect(r.adjusted(0, 0, 0, -3), fillBrush); + } + p->setPen(dwOpt->palette.color(QPalette::Light)); + if (!widget || !widget->isWindow()) { + p->drawLine(r.topLeft(), r.topRight()); + p->setPen(dwOpt->palette.color(QPalette::Dark)); + p->drawLine(r.bottomLeft(), r.bottomRight()); } + } + if (!dwOpt->title.isEmpty()) { + QFont oldFont = p->font(); + if (floating) { + QFont font = oldFont; + font.setBold(true); + p->setFont(font); + } + QPalette palette = dwOpt->palette; + palette.setColor(QPalette::Window, inactiveCaptionTextColor); + QRect titleRect = subElementRect(SE_DockWidgetTitleBarText, opt, widget); + if (verticalTitleBar) { + titleRect = QRect(r.left() + rect.bottom() + - titleRect.bottom(), + r.top() + titleRect.left() - rect.left(), + titleRect.height(), titleRect.width()); + } + proxy()->drawItemText(p, titleRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, palette, + dwOpt->state & State_Enabled, dwOpt->title, + floating ? (active ? QPalette::BrightText : QPalette::Window) : QPalette::WindowText); + p->setFont(oldFont); + } + if (verticalTitleBar) + p->restore(); + } + return; +#endif // QT_NO_DOCKWIDGET + default: + QCommonStyle::drawControl(ce, opt, p, widget); + } +} + +/*! \reimp */ +QRect QWindowsStyle::subElementRect(SubElement sr, const QStyleOption *opt, const QWidget *w) const +{ + QRect r; + switch (sr) { + case SE_SliderFocusRect: + case SE_ToolBoxTabContents: + r = visualRect(opt->direction, opt->rect, opt->rect); + break; + case SE_DockWidgetTitleBarText: { + r = QCommonStyle::subElementRect(sr, opt, w); + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(opt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + int m = proxy()->pixelMetric(PM_DockWidgetTitleMargin, opt, w); + if (verticalTitleBar) { + r.adjust(0, 0, 0, -m); + } else { + if (opt->direction == Qt::LeftToRight) + r.adjust(m, 0, 0, 0); + else + r.adjust(0, 0, -m, 0); + } + break; + } + case SE_ProgressBarContents: + r = QCommonStyle::subElementRect(SE_ProgressBarGroove, opt, w); + r.adjust(3, 3, -3, -3); + break; + default: + r = QCommonStyle::subElementRect(sr, opt, w); + } + return r; +} + +#ifdef QT3_SUPPORT +Q_GLOBAL_STATIC_WITH_ARGS(QBitmap, globalVerticalLine, (1, 129)) +Q_GLOBAL_STATIC_WITH_ARGS(QBitmap, globalHorizontalLine, (128, 1)) +#endif + +/*! \reimp */ +void QWindowsStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + QPainter *p, const QWidget *widget) const +{ + switch (cc) { +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + int ticks = slider->tickPosition; + QRect groove = proxy()->subControlRect(CC_Slider, slider, SC_SliderGroove, widget); + QRect handle = proxy()->subControlRect(CC_Slider, slider, SC_SliderHandle, widget); + + if ((slider->subControls & SC_SliderGroove) && groove.isValid()) { + int mid = thickness / 2; + + if (ticks & QSlider::TicksAbove) + mid += len / 8; + if (ticks & QSlider::TicksBelow) + mid -= len / 8; + + p->setPen(slider->palette.shadow().color()); + if (slider->orientation == Qt::Horizontal) { + qDrawWinPanel(p, groove.x(), groove.y() + mid - 2, + groove.width(), 4, slider->palette, true); + p->drawLine(groove.x() + 1, groove.y() + mid - 1, + groove.x() + groove.width() - 3, groove.y() + mid - 1); + } else { + qDrawWinPanel(p, groove.x() + mid - 2, groove.y(), + 4, groove.height(), slider->palette, true); + p->drawLine(groove.x() + mid - 1, groove.y() + 1, + groove.x() + mid - 1, groove.y() + groove.height() - 3); + } + } + + if (slider->subControls & SC_SliderTickmarks) { + QStyleOptionSlider tmpSlider = *slider; + tmpSlider.subControls = SC_SliderTickmarks; + QCommonStyle::drawComplexControl(cc, &tmpSlider, p, widget); + } + + if (slider->subControls & SC_SliderHandle) { + // 4444440 + // 4333310 + // 4322210 + // 4322210 + // 4322210 + // 4322210 + // *43210* + // **410** + // ***0*** + const QColor c0 = slider->palette.shadow().color(); + const QColor c1 = slider->palette.dark().color(); + // const QColor c2 = g.button(); + const QColor c3 = slider->palette.midlight().color(); + const QColor c4 = slider->palette.light().color(); + QBrush handleBrush; + + if (slider->state & State_Enabled) { + handleBrush = slider->palette.color(QPalette::Button); + } else { + handleBrush = QBrush(slider->palette.color(QPalette::Button), + Qt::Dense4Pattern); + } + + + int x = handle.x(), y = handle.y(), + wi = handle.width(), he = handle.height(); + + int x1 = x; + int x2 = x+wi-1; + int y1 = y; + int y2 = y+he-1; + + Qt::Orientation orient = slider->orientation; + bool tickAbove = slider->tickPosition == QSlider::TicksAbove; + bool tickBelow = slider->tickPosition == QSlider::TicksBelow; + + 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); + } + + if ((tickAbove && tickBelow) || (!tickAbove && !tickBelow)) { + Qt::BGMode oldMode = p->backgroundMode(); + p->setBackgroundMode(Qt::OpaqueMode); + qDrawWinButton(p, QRect(x, y, wi, he), slider->palette, false, + &handleBrush); + p->setBackgroundMode(oldMode); + return; + } + + QSliderDirection dir; + + if (orient == Qt::Horizontal) + if (tickAbove) + dir = SlUp; + else + dir = SlDown; + else + if (tickAbove) + dir = SlLeft; + else + dir = SlRight; + + QPolygon a; + + int d = 0; + switch (dir) { + case SlUp: + y1 = y1 + wi/2; + d = (wi + 1) / 2 - 1; + a.setPoints(5, x1,y1, x1,y2, x2,y2, x2,y1, x1+d,y1-d); + break; + case SlDown: + y2 = y2 - wi/2; + d = (wi + 1) / 2 - 1; + a.setPoints(5, x1,y1, x1,y2, x1+d,y2+d, x2,y2, x2,y1); + break; + case SlLeft: + d = (he + 1) / 2 - 1; + x1 = x1 + he/2; + a.setPoints(5, x1,y1, x1-d,y1+d, x1,y2, x2,y2, x2,y1); + break; + case SlRight: + d = (he + 1) / 2 - 1; + x2 = x2 - he/2; + a.setPoints(5, x1,y1, x1,y2, x2,y2, x2+d,y1+d, x2,y1); + break; + } + + QBrush oldBrush = p->brush(); + p->setPen(Qt::NoPen); + p->setBrush(handleBrush); + Qt::BGMode oldMode = p->backgroundMode(); + p->setBackgroundMode(Qt::OpaqueMode); + p->drawRect(x1, y1, x2-x1+1, y2-y1+1); + p->drawPolygon(a); + p->setBrush(oldBrush); + p->setBackgroundMode(oldMode); + + if (dir != SlUp) { + p->setPen(c4); + p->drawLine(x1, y1, x2, y1); + p->setPen(c3); + p->drawLine(x1, y1+1, x2, y1+1); + } + if (dir != SlLeft) { + p->setPen(c3); + p->drawLine(x1+1, y1+1, x1+1, y2); + p->setPen(c4); + p->drawLine(x1, y1, x1, y2); + } + if (dir != SlRight) { + p->setPen(c0); + p->drawLine(x2, y1, x2, y2); + p->setPen(c1); + p->drawLine(x2-1, y1+1, x2-1, y2-1); + } + if (dir != SlDown) { + p->setPen(c0); + p->drawLine(x1, y2, x2, y2); + p->setPen(c1); + p->drawLine(x1+1, y2-1, x2-1, y2-1); + } + + switch (dir) { + case SlUp: + p->setPen(c4); + p->drawLine(x1, y1, x1+d, y1-d); + p->setPen(c0); + d = wi - d - 1; + p->drawLine(x2, y1, x2-d, y1-d); + d--; + p->setPen(c3); + p->drawLine(x1+1, y1, x1+1+d, y1-d); + p->setPen(c1); + p->drawLine(x2-1, y1, x2-1-d, y1-d); + break; + case SlDown: + p->setPen(c4); + p->drawLine(x1, y2, x1+d, y2+d); + p->setPen(c0); + d = wi - d - 1; + p->drawLine(x2, y2, x2-d, y2+d); + d--; + p->setPen(c3); + p->drawLine(x1+1, y2, x1+1+d, y2+d); + p->setPen(c1); + p->drawLine(x2-1, y2, x2-1-d, y2+d); + break; + case SlLeft: + p->setPen(c4); + p->drawLine(x1, y1, x1-d, y1+d); + p->setPen(c0); + d = he - d - 1; + p->drawLine(x1, y2, x1-d, y2-d); + d--; + p->setPen(c3); + p->drawLine(x1, y1+1, x1-d, y1+1+d); + p->setPen(c1); + p->drawLine(x1, y2-1, x1-d, y2-1-d); + break; + case SlRight: + p->setPen(c4); + p->drawLine(x2, y1, x2+d, y1+d); + p->setPen(c0); + d = he - d - 1; + p->drawLine(x2, y2, x2+d, y2-d); + d--; + p->setPen(c3); + p->drawLine(x2, y1+1, x2+d, y1+1+d); + p->setPen(c1); + p->drawLine(x2, y2-1, x2+d, y2-1-d); + break; + } + } + } + break; +#endif // QT_NO_SLIDER +#ifndef QT_NO_SCROLLBAR + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(opt)) { + QStyleOptionSlider newScrollbar = *scrollbar; + if (scrollbar->minimum == scrollbar->maximum) + newScrollbar.state &= ~State_Enabled; //do not draw the slider. + QCommonStyle::drawComplexControl(cc, &newScrollbar, p, widget); + } + break; +#endif // QT_NO_SCROLLBAR +#ifdef QT3_SUPPORT + case CC_Q3ListView: + if (const QStyleOptionQ3ListView *lv = qstyleoption_cast<const QStyleOptionQ3ListView *>(opt)) { + int i; + if (lv->subControls & SC_Q3ListView) + QCommonStyle::drawComplexControl(cc, lv, p, widget); + if (lv->subControls & (SC_Q3ListViewBranch | SC_Q3ListViewExpand)) { + if (lv->items.isEmpty()) + break; + QStyleOptionQ3ListViewItem item = lv->items.at(0); + int y = lv->rect.y(); + int c; + int dotoffset = 0; + QPolygon dotlines; + if ((lv->activeSubControls & SC_All) && (lv->subControls & SC_Q3ListViewExpand)) { + c = 2; + dotlines.resize(2); + dotlines[0] = QPoint(lv->rect.right(), lv->rect.top()); + dotlines[1] = QPoint(lv->rect.right(), lv->rect.bottom()); + } else { + int linetop = 0, linebot = 0; + // each branch needs at most two lines, ie. four end points + dotoffset = (item.itemY + item.height - y) % 2; + dotlines.resize(item.childCount * 4); + c = 0; + + // skip the stuff above the exposed rectangle + for (i = 1; i < lv->items.size(); ++i) { + QStyleOptionQ3ListViewItem child = lv->items.at(i); + if (child.height + y > 0) + break; + y += child.totalHeight; + } + int bx = lv->rect.width() / 2; + + // paint stuff in the magical area + while (i < lv->items.size() && y < lv->rect.height()) { + QStyleOptionQ3ListViewItem child = lv->items.at(i); + if (child.features & QStyleOptionQ3ListViewItem::Visible) { + int lh; + if (!(item.features & QStyleOptionQ3ListViewItem::MultiLine)) + lh = child.height; + else + lh = p->fontMetrics().height() + 2 * lv->itemMargin; + lh = qMax(lh, QApplication::globalStrut().height()); + if (lh % 2 > 0) + ++lh; + linebot = y + lh / 2; + if (child.features & QStyleOptionQ3ListViewItem::Expandable + || (child.childCount > 0 && child.height > 0)) { + // needs a box + p->setPen(lv->palette.mid().color()); + p->drawRect(bx - 4, linebot - 4, 8, 8); + // plus or minus + p->setPen(lv->palette.text().color()); + p->drawLine(bx - 2, linebot, bx + 2, linebot); + if (!(child.state & State_Open)) + p->drawLine(bx, linebot - 2, bx, linebot + 2); + // dotlinery + p->setPen(lv->palette.mid().color()); + dotlines[c++] = QPoint(bx, linetop); + dotlines[c++] = QPoint(bx, linebot - 4); + dotlines[c++] = QPoint(bx + 5, linebot); + dotlines[c++] = QPoint(lv->rect.width(), linebot); + linetop = linebot + 5; + } else { + // just dotlinery + dotlines[c++] = QPoint(bx+1, linebot -1); + dotlines[c++] = QPoint(lv->rect.width(), linebot -1); + } + y += child.totalHeight; + } + ++i; + } + + // Expand line height to edge of rectangle if there's any + // visible child below + while (i < lv->items.size() && lv->items.at(i).height <= 0) + ++i; + if (i < lv->items.size()) + linebot = lv->rect.height(); + + if (linetop < linebot) { + dotlines[c++] = QPoint(bx, linetop); + dotlines[c++] = QPoint(bx, linebot); + } + } + p->setPen(lv->palette.text().color()); + QBitmap *verticalLine = globalVerticalLine(); + QBitmap *horizontalLine = globalHorizontalLine(); + static bool isInit = false; + if (!isInit) { + isInit = true; + // make 128*1 and 1*128 bitmaps that can be used for + // drawing the right sort of lines. + verticalLine->clear(); + horizontalLine->clear(); + QPolygon a(64); + QPainter p; + p.begin(verticalLine); + for(i = 0; i < 64; ++i) + a.setPoint(i, 0, i * 2 + 1); + p.setPen(Qt::color1); + p.drawPoints(a); + p.end(); + QApplication::flush(); + verticalLine->setMask(*verticalLine); + p.begin(horizontalLine); + for(i = 0; i < 64; ++i) + a.setPoint(i, i * 2 + 1, 0); + p.setPen(Qt::color1); + p.drawPoints(a); + p.end(); + QApplication::flush(); + horizontalLine->setMask(*horizontalLine); + } + + int line; // index into dotlines + if (lv->subControls & SC_Q3ListViewBranch) for(line = 0; line < c; line += 2) { + // assumptions here: lines are horizontal or vertical. + // lines always start with the numerically lowest + // coordinate. + + // point ... relevant coordinate of current point + // end ..... same coordinate of the end of the current line + // other ... the other coordinate of the current point/line + if (dotlines[line].y() == dotlines[line+1].y()) { + int end = dotlines[line + 1].x(); + int point = dotlines[line].x(); + int other = dotlines[line].y(); + while (point < end) { + int i = 128; + if (i + point > end) + i = end-point; + p->drawPixmap(point, other, *horizontalLine, 0, 0, i, 1); + point += i; + } + } else { + int end = dotlines[line + 1].y(); + int point = dotlines[line].y(); + int other = dotlines[line].x(); + int pixmapoffset = ((point & 1) != dotoffset) ? 1 : 0; + while(point < end) { + int i = 128; + if (i + point > end) + i = end-point; + p->drawPixmap(other, point, *verticalLine, 0, pixmapoffset, 1, i); + point += i; + } + } + } + } + } + break; +#endif // QT3_SUPPORT +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) { + QBrush editBrush = cmb->palette.brush(QPalette::Base); + if ((cmb->subControls & SC_ComboBoxFrame)) { + if (cmb->frame) { + QPalette shadePal = opt->palette; + shadePal.setColor(QPalette::Midlight, shadePal.button().color()); + qDrawWinPanel(p, opt->rect, shadePal, true, &editBrush); + } + else { + p->fillRect(opt->rect, editBrush); + } + } + if (cmb->subControls & SC_ComboBoxArrow) { + State flags = State_None; + + QRect ar = proxy()->subControlRect(CC_ComboBox, cmb, SC_ComboBoxArrow, widget); + bool sunkenArrow = cmb->activeSubControls == SC_ComboBoxArrow + && cmb->state & State_Sunken; + if (sunkenArrow) { + p->setPen(cmb->palette.dark().color()); + p->setBrush(cmb->palette.brush(QPalette::Button)); + p->drawRect(ar.adjusted(0,0,-1,-1)); + } else { + // Make qDrawWinButton use the right colors for drawing the shade of the button + QPalette pal(cmb->palette); + pal.setColor(QPalette::Button, cmb->palette.light().color()); + pal.setColor(QPalette::Light, cmb->palette.button().color()); + qDrawWinButton(p, ar, pal, false, + &cmb->palette.brush(QPalette::Button)); + } + + ar.adjust(2, 2, -2, -2); + if (opt->state & State_Enabled) + flags |= State_Enabled; + if (opt->state & State_HasFocus) + flags |= State_HasFocus; + + if (sunkenArrow) + flags |= State_Sunken; + QStyleOption arrowOpt(0); + arrowOpt.rect = ar.adjusted(1, 1, -1, -1); + arrowOpt.palette = cmb->palette; + arrowOpt.state = flags; + proxy()->drawPrimitive(PE_IndicatorArrowDown, &arrowOpt, p, widget); + } + + if (cmb->subControls & SC_ComboBoxEditField) { + QRect re = proxy()->subControlRect(CC_ComboBox, cmb, SC_ComboBoxEditField, widget); + if (cmb->state & State_HasFocus && !cmb->editable) + p->fillRect(re.x(), re.y(), re.width(), re.height(), + cmb->palette.brush(QPalette::Highlight)); + + if (cmb->state & State_HasFocus) { + p->setPen(cmb->palette.highlightedText().color()); + p->setBackground(cmb->palette.highlight()); + + } else { + p->setPen(cmb->palette.text().color()); + p->setBackground(cmb->palette.background()); + } + + if (cmb->state & State_HasFocus && !cmb->editable) { + QStyleOptionFocusRect focus; + focus.QStyleOption::operator=(*cmb); + focus.rect = subElementRect(SE_ComboBoxFocusRect, cmb, widget); + focus.state |= State_FocusAtBorder; + focus.backgroundColor = cmb->palette.highlight().color(); + proxy()->drawPrimitive(PE_FrameFocusRect, &focus, p, widget); + } + } + } + break; +#endif // QT_NO_COMBOBOX +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) { + QStyleOptionSpinBox copy = *sb; + PrimitiveElement pe; + bool enabled = opt->state & State_Enabled; + if (sb->frame && (sb->subControls & SC_SpinBoxFrame)) { + QBrush editBrush = sb->palette.brush(QPalette::Base); + QRect r = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxFrame, widget); + QPalette shadePal = sb->palette; + shadePal.setColor(QPalette::Midlight, shadePal.button().color()); + qDrawWinPanel(p, r, shadePal, true, &editBrush); + } + + QPalette shadePal(opt->palette); + shadePal.setColor(QPalette::Button, opt->palette.light().color()); + shadePal.setColor(QPalette::Light, opt->palette.button().color()); + + if (sb->subControls & SC_SpinBoxUp) { + copy.subControls = SC_SpinBoxUp; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + + copy.palette = pal2; + + if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinPlus + : PE_IndicatorSpinUp); + + copy.rect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxUp, widget); + qDrawWinButton(p, copy.rect, shadePal, copy.state & (State_Sunken | State_On), + ©.palette.brush(QPalette::Button)); + copy.rect.adjust(4, 1, -5, -1); + if ((!enabled || !(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled)) + && proxy()->styleHint(SH_EtchDisabledText, opt, widget) ) + { + QStyleOptionSpinBox lightCopy = copy; + lightCopy.rect.adjust(1, 1, 1, 1); + lightCopy.palette.setBrush(QPalette::ButtonText, copy.palette.light()); + proxy()->drawPrimitive(pe, &lightCopy, p, widget); + } + proxy()->drawPrimitive(pe, ©, p, widget); + } + + if (sb->subControls & SC_SpinBoxDown) { + copy.subControls = SC_SpinBoxDown; + copy.state = sb->state; + QPalette pal2 = sb->palette; + if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled)) { + pal2.setCurrentColorGroup(QPalette::Disabled); + copy.state &= ~State_Enabled; + } + copy.palette = pal2; + + if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken)) { + copy.state |= State_On; + copy.state |= State_Sunken; + } else { + copy.state |= State_Raised; + copy.state &= ~State_Sunken; + } + pe = (sb->buttonSymbols == QAbstractSpinBox::PlusMinus ? PE_IndicatorSpinMinus + : PE_IndicatorSpinDown); + + copy.rect = proxy()->subControlRect(CC_SpinBox, sb, SC_SpinBoxDown, widget); + qDrawWinButton(p, copy.rect, shadePal, copy.state & (State_Sunken | State_On), + ©.palette.brush(QPalette::Button)); + copy.rect.adjust(4, 0, -5, -1); + if ((!enabled || !(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled)) + && proxy()->styleHint(SH_EtchDisabledText, opt, widget) ) + { + QStyleOptionSpinBox lightCopy = copy; + lightCopy.rect.adjust(1, 1, 1, 1); + lightCopy.palette.setBrush(QPalette::ButtonText, copy.palette.light()); + proxy()->drawPrimitive(pe, &lightCopy, p, widget); + } + proxy()->drawPrimitive(pe, ©, p, widget); + } + } + break; +#endif // QT_NO_SPINBOX + + default: + QCommonStyle::drawComplexControl(cc, opt, p, widget); + } +} + +/*! \reimp */ +QSize QWindowsStyle::sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &csz, const QWidget *widget) const +{ + QSize sz(csz); + switch (ct) { + case CT_PushButton: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) { + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + int w = sz.width(), + h = sz.height(); + int defwidth = 0; + if (btn->features & QStyleOptionButton::AutoDefaultButton) + defwidth = 2 * proxy()->pixelMetric(PM_ButtonDefaultIndicator, btn, widget); + int minwidth = int(QStyleHelper::dpiScaled(75.)); + int minheight = int(QStyleHelper::dpiScaled(23.)); + +#ifndef QT_QWS_SMALL_PUSHBUTTON + if (w < minwidth + defwidth && !btn->text.isEmpty()) + w = minwidth + defwidth; + if (h < minheight + defwidth) + h = minheight + defwidth; +#endif + sz = QSize(w, h); + } + break; +#ifndef QT_NO_MENU + case CT_MenuItem: + if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)) { + int w = sz.width(); + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + + if (mi->menuItemType == QStyleOptionMenuItem::Separator) { + sz = QSize(10, QWindowsStylePrivate::windowsSepHeight); + } + else if (mi->icon.isNull()) { + sz.setHeight(sz.height() - 2); + w -= 6; + } + + if (mi->menuItemType != QStyleOptionMenuItem::Separator && !mi->icon.isNull()) { + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + sz.setHeight(qMax(sz.height(), + mi->icon.actualSize(QSize(iconExtent, iconExtent)).height() + + 2 * QWindowsStylePrivate::windowsItemFrame)); + } + int maxpmw = mi->maxIconWidth; + int tabSpacing = 20; + if (mi->text.contains(QLatin1Char('\t'))) + w += tabSpacing; + else if (mi->menuItemType == QStyleOptionMenuItem::SubMenu) + w += 2 * QWindowsStylePrivate::windowsArrowHMargin; + else if (mi->menuItemType == QStyleOptionMenuItem::DefaultItem) { + // adjust the font and add the difference in size. + // it would be better if the font could be adjusted in the initStyleOption qmenu func!! + QFontMetrics fm(mi->font); + QFont fontBold = mi->font; + fontBold.setBold(true); + QFontMetrics fmBold(fontBold); + w += fmBold.width(mi->text) - fm.width(mi->text); + } + + int checkcol = qMax<int>(maxpmw, QWindowsStylePrivate::windowsCheckMarkWidth); // Windows always shows a check column + w += checkcol; + w += int(QWindowsStylePrivate::windowsRightBorder) + 10; + sz.setWidth(w); + } + break; +#endif // QT_NO_MENU +#ifndef QT_NO_MENUBAR + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(QWindowsStylePrivate::windowsItemHMargin * 4, QWindowsStylePrivate::windowsItemVMargin * 2); + break; +#endif + // Otherwise, fall through + case CT_ToolButton: + if (qstyleoption_cast<const QStyleOptionToolButton *>(opt)) + return sz += QSize(7, 6); + // Otherwise, fall through + + default: + sz = QCommonStyle::sizeFromContents(ct, opt, csz, widget); + } + return sz; +} + +/*! + \internal +*/ +QIcon QWindowsStyle::standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget) const +{ + QIcon icon; + QPixmap pixmap; +#ifdef Q_OS_WIN + switch (standardIcon) { + case SP_FileDialogNewFolder: + { + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(319, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + } + case SP_DirHomeIcon: + { + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(235, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + } + case SP_DirIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(4, size); + icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off); + pixmap = loadIconFromShell32(5, size); + icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); + } + break; + case SP_DirLinkIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + QPixmap link = loadIconFromShell32(30, size); + pixmap = loadIconFromShell32(4, size); + if (!pixmap.isNull() && !link.isNull()) { + QPainter painter(&pixmap); + painter.drawPixmap(0, 0, size, size, link); + icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off); + } + link = loadIconFromShell32(30, size); + pixmap = loadIconFromShell32(5, size); + if (!pixmap.isNull() && !link.isNull()) { + QPainter painter(&pixmap); + painter.drawPixmap(0, 0, size, size, link); + icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); + } + } + break; + case SP_FileIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(1, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_ComputerIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(16, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + + case SP_DesktopIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(35, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_DriveCDIcon: + case SP_DriveDVDIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(12, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_DriveNetIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(10, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_DriveHDIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(9, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_DriveFDIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + pixmap = loadIconFromShell32(7, size); + icon.addPixmap(pixmap, QIcon::Normal); + } + break; + case SP_FileLinkIcon: + for (int size = 16 ; size <= 32 ; size += 16) { + QPixmap link; + link = loadIconFromShell32(30, size); + pixmap = loadIconFromShell32(1, size); + if (!pixmap.isNull() && !link.isNull()) { + QPainter painter(&pixmap); + painter.drawPixmap(0, 0, size, size, link); + icon.addPixmap(pixmap, QIcon::Normal); + } + } + break; + case SP_VistaShield: + { + if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA + && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based + && pSHGetStockIconInfo) + { + icon.addPixmap(proxy()->standardPixmap(SP_VistaShield, option, widget)); //fetches small icon + QSHSTOCKICONINFO iconInfo; //append large icon + memset(&iconInfo, 0, sizeof(iconInfo)); + iconInfo.cbSize = sizeof(iconInfo); + if (pSHGetStockIconInfo(_SIID_SHIELD, _SHGFI_ICON | _SHGFI_LARGEICON, &iconInfo) == S_OK) { + icon.addPixmap(QPixmap::fromWinHICON(iconInfo.hIcon)); + DestroyIcon(iconInfo.hIcon); + } + } + } + break; + default: + break; + } +#endif + + if (icon.isNull()) + icon = QCommonStyle::standardIconImplementation(standardIcon, option, widget); + return icon; +} + + + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWS diff --git a/src/gui/styles/qwindowsstyle.h b/src/gui/styles/qwindowsstyle.h new file mode 100644 index 0000000000..99f64fcb05 --- /dev/null +++ b/src/gui/styles/qwindowsstyle.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSSTYLE_H +#define QWINDOWSSTYLE_H + +#include <QtGui/qcommonstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_WINDOWS) + +class QWindowsStylePrivate; + +class Q_GUI_EXPORT QWindowsStyle : public QCommonStyle +{ + Q_OBJECT +public: + QWindowsStyle(); + ~QWindowsStyle(); + + void polish(QApplication*); + void unpolish(QApplication*); + + void polish(QWidget*); + void unpolish(QWidget*); + + void polish(QPalette &); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, + const QWidget *w = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *opt, const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, + const QWidget *w = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, + const QSize &contentsSize, const QWidget *widget = 0) const; + + int pixelMetric(PixelMetric pm, const QStyleOption *option = 0, const QWidget *widget = 0) const; + + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + +protected: + bool eventFilter(QObject *o, QEvent *e); + void timerEvent(QTimerEvent *event); + QWindowsStyle(QWindowsStylePrivate &dd); + +private: + Q_DISABLE_COPY(QWindowsStyle) + Q_DECLARE_PRIVATE(QWindowsStyle) + void *reserved; +}; + +#endif // QT_NO_STYLE_WINDOWS + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWINDOWSSTYLE_H diff --git a/src/gui/styles/qwindowsstyle_p.h b/src/gui/styles/qwindowsstyle_p.h new file mode 100644 index 0000000000..a10f2baffd --- /dev/null +++ b/src/gui/styles/qwindowsstyle_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSSTYLE_P_H +#define QWINDOWSSTYLE_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 "qwindowsstyle.h" +#include "qcommonstyle_p.h" + +#ifndef QT_NO_STYLE_WINDOWS +#include <qlist.h> +#include <qhash.h> +#include <qelapsedtimer.h> + +QT_BEGIN_NAMESPACE + +class QTime; +class QProgressBar; + +class QWindowsStylePrivate : public QCommonStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsStyle) +public: + QWindowsStylePrivate(); + bool hasSeenAlt(const QWidget *widget) const; + bool altDown() const { return alt_down; } + bool alt_down; + QList<const QWidget *> seenAlt; + int menuBarTimer; + + QList<QProgressBar *> bars; + int animationFps; + int animateTimer; + QElapsedTimer startTime; + int animateStep; + QColor inactiveCaptionText; + QColor activeCaptionColor; + QColor activeGradientCaptionColor; + QColor inactiveCaptionColor; + QColor inactiveGradientCaptionColor; + + enum { + windowsItemFrame = 2, // menu item frame width + windowsSepHeight = 9, // separator item height + windowsItemHMargin = 3, // menu item hor text margin + windowsItemVMargin = 2, // menu item ver text margin + windowsArrowHMargin = 6, // arrow horizontal margin + windowsRightBorder = 15, // right border on windows + windowsCheckMarkWidth = 12 // checkmarks width on windows + }; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWS + +#endif //QWINDOWSSTYLE_P_H diff --git a/src/gui/styles/qwindowsvistastyle.cpp b/src/gui/styles/qwindowsvistastyle.cpp new file mode 100644 index 0000000000..7f1a3ab678 --- /dev/null +++ b/src/gui/styles/qwindowsvistastyle.cpp @@ -0,0 +1,2670 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsvistastyle.h" +#include "qwindowsvistastyle_p.h" +#include <private/qstylehelper_p.h> +#include <private/qsystemlibrary_p.h> + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) || defined(QT_PLUGIN) + +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 + +// Runtime resolved theme engine function calls + + +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HTHEME (WINAPI *PtrOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); +typedef HTHEME (WINAPI *PtrOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI *PtrCloseThemeData)(HTHEME hTheme); +typedef HRESULT (WINAPI *PtrDrawThemeBackground)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect); +typedef HRESULT (WINAPI *PtrDrawThemeBackgroundEx)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const DTBGOPTS *pOptions); +typedef HRESULT (WINAPI *PtrGetCurrentThemeName)(OUT LPWSTR pszThemeFileName, int cchMaxNameChars, OUT OPTIONAL LPWSTR pszColorBuff, int cchMaxColorChars, OUT OPTIONAL LPWSTR pszSizeBuff, int cchMaxSizeChars); +typedef HRESULT (WINAPI *PtrGetThemeBool)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT BOOL *pfVal); +typedef HRESULT (WINAPI *PtrGetThemeColor)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT COLORREF *pColor); +typedef HRESULT (WINAPI *PtrGetThemeEnumValue)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemeFilename)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT LPWSTR pszThemeFileName, int cchMaxBuffChars); +typedef HRESULT (WINAPI *PtrGetThemeFont)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OUT LOGFONT *pFont); +typedef HRESULT (WINAPI *PtrGetThemeInt)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemeIntList)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT INTLIST *pIntList); +typedef HRESULT (WINAPI *PtrGetThemeMargins)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OPTIONAL RECT *prc, OUT MARGINS *pMargins); +typedef HRESULT (WINAPI *PtrGetThemeMetric)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HRESULT (WINAPI *PtrGetThemePosition)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT POINT *pPoint); +typedef HRESULT (WINAPI *PtrGetThemeRect)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT RECT *pRect); +typedef HRESULT (WINAPI *PtrGetThemeString)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT LPWSTR pszBuff, int cchMaxBuffChars); +typedef HRESULT (WINAPI *PtrGetThemeTransitionDuration)(HTHEME hTheme, int iPartId, int iStateFromId, int iStateToId, int iPropId, int *pDuration); +typedef HRESULT (WINAPI *PtrIsThemePartDefined)(HTHEME hTheme, int iPartId, int iStateId); +typedef HRESULT (WINAPI *PtrSetWindowTheme)(HWND hwnd, LPCWSTR pszSubAppName, LPCWSTR pszSubIdList); +typedef HRESULT (WINAPI *PtrGetThemePropertyOrigin)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT enum PROPERTYORIGIN *pOrigin); + +static PtrIsThemePartDefined pIsThemePartDefined = 0; +static PtrOpenThemeData pOpenThemeData = 0; +static PtrCloseThemeData pCloseThemeData = 0; +static PtrDrawThemeBackground pDrawThemeBackground = 0; +static PtrDrawThemeBackgroundEx pDrawThemeBackgroundEx = 0; +static PtrGetCurrentThemeName pGetCurrentThemeName = 0; +static PtrGetThemeBool pGetThemeBool = 0; +static PtrGetThemeColor pGetThemeColor = 0; +static PtrGetThemeEnumValue pGetThemeEnumValue = 0; +static PtrGetThemeFilename pGetThemeFilename = 0; +static PtrGetThemeFont pGetThemeFont = 0; +static PtrGetThemeInt pGetThemeInt = 0; +static PtrGetThemeIntList pGetThemeIntList = 0; +static PtrGetThemeMargins pGetThemeMargins = 0; +static PtrGetThemeMetric pGetThemeMetric = 0; +static PtrGetThemePartSize pGetThemePartSize = 0; +static PtrGetThemePosition pGetThemePosition = 0; +static PtrGetThemeRect pGetThemeRect = 0; +static PtrGetThemeString pGetThemeString = 0; +static PtrGetThemeTransitionDuration pGetThemeTransitionDuration= 0; +static PtrSetWindowTheme pSetWindowTheme = 0; +static PtrGetThemePropertyOrigin pGetThemePropertyOrigin = 0; + +/* \internal + Checks if we should use Vista style , or if we should + fall back to Windows style. +*/ +bool QWindowsVistaStylePrivate::useVista() +{ + return (QWindowsVistaStylePrivate::useXP() && + (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && + QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)); +} + +/*! + \class QWindowsVistaStyle + \brief The QWindowsVistaStyle class provides a look and feel suitable for applications on Microsoft Windows Vista. + \since 4.3 + \ingroup appearance + + \warning This style is only available on the Windows Vista platform + because it makes use of Windows Vista's style engine. + + \sa QMacStyle, QWindowsXPStyle, QPlastiqueStyle, QCleanlooksStyle, QMotifStyle +*/ + +/*! + Constructs a QWindowsVistaStyle object. +*/ +QWindowsVistaStyle::QWindowsVistaStyle() + : QWindowsXPStyle(*new QWindowsVistaStylePrivate) +{ +} + +//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; +} + +void QWindowsVistaAnimation::paint(QPainter *painter, const QStyleOption *option) +{ + Q_UNUSED(option); + Q_UNUSED(painter); +} + +/*! \internal + + Helperfunction to paint the current transition state between two + animation frames. + + The result is a blended image consisting of ((alpha)*_primaryImage) + + ((1-alpha)*_secondaryImage) + +*/ +void QWindowsVistaAnimation::drawBlendedImage(QPainter *painter, QRect rect, float alpha) { + if (_secondaryImage.isNull() || _primaryImage.isNull()) + return; + + if (_tempImage.isNull()) + _tempImage = _secondaryImage; + + const int a = qRound(alpha*256); + const int ia = 256 - a; + const int sw = _primaryImage.width(); + const int sh = _primaryImage.height(); + const int bpl = _primaryImage.bytesPerLine(); + switch(_primaryImage.depth()) { + case 32: + { + uchar *mixed_data = _tempImage.bits(); + const uchar *back_data = _primaryImage.bits(); + const uchar *front_data = _secondaryImage.bits(); + for (int sy = 0; sy < sh; sy++) { + quint32* mixed = (quint32*)mixed_data; + const quint32* back = (const quint32*)back_data; + const quint32* front = (const quint32*)front_data; + for (int sx = 0; sx < sw; sx++) { + quint32 bp = back[sx]; + quint32 fp = front[sx]; + mixed[sx] = qRgba ((qRed(bp)*ia + qRed(fp)*a)>>8, + (qGreen(bp)*ia + qGreen(fp)*a)>>8, + (qBlue(bp)*ia + qBlue(fp)*a)>>8, + (qAlpha(bp)*ia + qAlpha(fp)*a)>>8); + } + mixed_data += bpl; + back_data += bpl; + front_data += bpl; + } + } + default: + break; + } + painter->drawImage(rect, _tempImage); +} + +/*! \internal + Paints a transition state. The result will be a mix between the + initial and final state of the transition, depending on the time + difference between _startTime and current time. +*/ +void QWindowsVistaTransition::paint(QPainter *painter, const QStyleOption *option) +{ + float alpha = 1.0; + if (_duration > 0) { + QTime current = QTime::currentTime(); + + if (_startTime > current) + _startTime = current; + + int timeDiff = _startTime.msecsTo(current); + alpha = timeDiff/(float)_duration; + if (timeDiff > _duration) { + _running = false; + alpha = 1.0; + } + } + else { + _running = false; + } + drawBlendedImage(painter, option->rect, alpha); +} + +/*! \internal + Paints a pulse. The result will be a mix between the primary and + secondary pulse images depending on the time difference between + _startTime and current time. +*/ +void QWindowsVistaPulse::paint(QPainter *painter, const QStyleOption *option) +{ + float alpha = 1.0; + if (_duration > 0) { + QTime current = QTime::currentTime(); + + if (_startTime > current) + _startTime = current; + + int timeDiff = _startTime.msecsTo(current) % _duration*2; + if (timeDiff > _duration) + timeDiff = _duration*2 - timeDiff; + alpha = timeDiff/(float)_duration; + } else { + _running = false; + } + drawBlendedImage(painter, option->rect, alpha); +} + + +/*! + \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; + } + + QRect oldRect; + QRect newRect; + + if (widget && d->transitionsEnabled()) + { + /* all widgets that supports state transitions : */ + if ( +#ifndef QT_NO_LINEEDIT + (qobject_cast<const QLineEdit*>(widget) && element == PE_FrameLineEdit) || +#endif // QT_NO_LINEEDIT + (qobject_cast<const QRadioButton*>(widget)&& element == PE_IndicatorRadioButton) || + (qobject_cast<const QCheckBox*>(widget) && element == PE_IndicatorCheckBox) || + (qobject_cast<const QGroupBox *>(widget)&& element == PE_IndicatorCheckBox) || + (qobject_cast<const QToolButton*>(widget) && element == PE_PanelButtonBevel) + ) + { + // Retrieve and update the dynamic properties tracking + // the previous state of the widget: + QWidget *w = const_cast<QWidget *> (widget); + int oldState = w->property("_q_stylestate").toInt(); + oldRect = w->property("_q_stylerect").toRect(); + newRect = w->rect(); + w->setProperty("_q_stylestate", (int)option->state); + w->setProperty("_q_stylerect", w->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(widget); + +#ifndef QT_NO_LINEEDIT + if (const QLineEdit *edit = qobject_cast<const QLineEdit *>(widget)) + if (edit->isReadOnly() && element == PE_FrameLineEdit) // Do not animate read only line edits + doTransition = false; +#endif // QT_NO_LINEEDIT + + if (doTransition) { + + // We create separate images for the initial and final transition states and store them in the + // Transition object. + QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QImage endImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QStyleOption opt = *option; + + opt.rect.setRect(0, 0, option->rect.width(), option->rect.height()); + opt.state = (QStyle::State)oldState; + startImage.fill(0); + QPainter startPainter(&startImage); + + QWindowsVistaAnimation *anim = d->widgetAnimation(widget); + QWindowsVistaTransition *t = new QWindowsVistaTransition; + t->setWidget(w); + + // 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, &opt, &startPainter, 0); // Note that the widget pointer is intentionally 0 + else // this ensures that we do not recurse in the animation logic above + anim->paint(&startPainter, &opt); + + d->startAnimation(t); + t->setStartImage(startImage); + + // The end state of the transition is simply the result we would have painted + // if the style was not animated. + + QPainter endPainter(&endImage); + endImage.fill(0); + QStyleOption opt2 = opt; + opt2.state = option->state; + proxy()->drawPrimitive(element, &opt2, &endPainter, 0); // Note that the widget pointer is intentionally 0 + // this ensures that we do not recurse in the animation logic above + t->setEndImage(endImage); + + HTHEME theme; + int partId; + int duration; + int fromState = 0; + int toState = 0; + + //translate state flags to UXTHEME states : + if (element == PE_FrameLineEdit) { + theme = pOpenThemeData(0, 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 = pOpenThemeData(0, 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 && pGetThemeTransitionDuration(theme, partId, fromState, toState, + TMT_TRANSITIONDURATIONS, &duration) == S_OK) + { + t->setDuration(duration); + } + t->setStartTime(QTime::currentTime()); + } + } + } // 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, QLatin1String("HEADER"), HP_HEADERSORTARROW, stateId, option->rect); + d->drawBackground(theme); + } + break; + + case PE_IndicatorBranch: + { + XPThemeData theme(d->treeViewHelper(), painter, QLatin1String("TREEVIEW")); + static int decoration_size = 0; + if (theme.isValid() && !decoration_size) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, TVP_HOTGLYPH, GLPS_OPENED, 0, TS_TRUE, &size); + decoration_size = qMax(size.cx, size.cy); + } + 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 = d->widgetAnimation(widget)) { + 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, QLatin1String("MENU"), MENU_POPUPBORDERS, stateId, option->rect); + d->drawBackground(theme); + } + break; + case PE_Frame: +#ifndef QT_NO_TEXTEDIT + if (const QTextEdit *edit = qobject_cast<const QTextEdit*>(widget)) { + painter->save(); + int stateId = ETS_NORMAL; + if (!(state & State_Enabled)) + stateId = ETS_DISABLED; + else if (edit->isReadOnly()) + stateId = ETS_READONLY; + else if (state & State_HasFocus) + stateId = ETS_SELECTED; + XPThemeData theme(widget, painter, QLatin1String("EDIT"), EP_EDITBORDER_HVSCROLL, stateId, option->rect); + uint resolve_mask = option->palette.resolve(); + if (resolve_mask & (1 << QPalette::Base)) { + // Since EP_EDITBORDER_HVSCROLL does not us borderfill, theme.noContent cannot be used for clipping + int borderSize = 1; + pGetThemeInt(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 +#endif // QT_NO_TEXTEDIT + QWindowsXPStyle::drawPrimitive(element, option, painter, widget); + break; + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + QBrush bg; + bool usePalette = false; + bool isEnabled = option->state & State_Enabled; + uint resolve_mask = panel->palette.resolve(); + if (widget) { + //Since spin box and combo box includes a line edit we need to resolve the palette on the parent instead +#ifndef QT_NO_SPINBOX + if (QAbstractSpinBox *spinbox = qobject_cast<QAbstractSpinBox*>(widget->parentWidget())) + resolve_mask = spinbox->palette().resolve(); +#endif // QT_NO_SPINBOX + } + if (resolve_mask & (1 << QPalette::Base)) { + // Base color is set for this widget, so use it + bg = panel->palette.brush(QPalette::Base); + usePalette = true; + } + if (usePalette) { + painter->fillRect(panel->rect, bg); + } 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(0, painter, QLatin1String("EDIT"), partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(element, option, painter, widget); + return; + } + int bgType; + pGetThemeEnumValue( 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; + pGetThemePropertyOrigin(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; + pGetThemeColor(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 = d->widgetAnimation(widget)) { + anim->paint(painter, option); + } else { + QPainter *p = painter; + QWidget *parentWidget = 0; + if (widget) { + parentWidget = widget->parentWidget(); + if (parentWidget) + parentWidget = parentWidget->parentWidget(); + } + if (widget && widget->inherits("QLineEdit") + && parentWidget && parentWidget->inherits("QAbstractItemView")) { + // 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; + } else { + 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, QLatin1String("EDIT"), EP_EDITBORDER_NOSCROLL, stateId, option->rect); + 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, QLatin1String("REBAR"), 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, QLatin1String("REBAR"), 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.background().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, QLatin1String("TOOLTIP"), TTP_STANDARD, TTSS_NORMAL, option->rect); + d->drawBackground(theme); + break; + } + + case PE_PanelItemViewItem: + { + const QStyleOptionViewItemV4 *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 QStyleOptionViewItemV4 *>(option))) { + bool selected = vopt->state & QStyle::State_Selected; + bool hover = vopt->state & QStyle::State_MouseOver; + bool active = vopt->state & QStyle::State_Active; + + if (vopt->features & QStyleOptionViewItemV2::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 textRect = subElementRect(QStyle::SE_ItemViewItemText, option, widget); + 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 (view->selectionBehavior() == QAbstractItemView::SelectRows) + sectionSize.setWidth(vopt->rect.width()); + if (view->selectionMode() == QAbstractItemView::NoSelection) + hover = false; + QPixmap pixmap; + + if (vopt->backgroundBrush.style() != Qt::NoBrush) { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(vopt->rect.topLeft()); + painter->fillRect(vopt->rect, vopt->backgroundBrush); + } + + if (hover || selected) { + 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(d->treeViewHelper(), &pixmapPainter, QLatin1String("TREEVIEW"), + 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 == QStyleOptionViewItemV4::Beginning; + bool rightSection = vopt->viewItemPosition == QStyleOptionViewItemV4::End; + if (vopt->viewItemPosition == QStyleOptionViewItemV4::OnlyOne + || vopt->viewItemPosition == QStyleOptionViewItemV4::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 == QStyleOptionViewItemV4::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: + { + const QDialogButtonBox *buttonBox = 0; + + if (qobject_cast<const QMessageBox *> (widget)) + buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<const QInputDialog *> (widget)) + buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); +#endif // QT_NO_INPUTDIALOG + + if (buttonBox) { + //draw white panel part + XPThemeData theme(widget, painter, QLatin1String("TASKDIALOG"), 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); + } + } + 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; + QString name; + + QRect rect(option->rect); + State flags = option->state; + int partId = 0; + int stateId = 0; + + QRect oldRect; + QRect newRect; + + if (d->transitionsEnabled() && widget) { + if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) { + if ((qobject_cast<const QPushButton*>(widget) && element == CE_PushButtonBevel)) + { + QWidget *w = const_cast<QWidget *> (widget); + int oldState = w->property("_q_stylestate").toInt(); + oldRect = w->property("_q_stylerect").toRect(); + newRect = w->rect(); + w->setProperty("_q_stylestate", (int)option->state); + w->setProperty("_q_stylerect", w->rect()); + + bool wasDefault = w->property("_q_isdefault").toBool(); + bool isDefault = button->features & QStyleOptionButton::DefaultButton; + w->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(widget); + } + + if (doTransition) { + QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QImage endImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QWindowsVistaAnimation *anim = d->widgetAnimation(widget); + + QStyleOptionButton opt = *button; + opt.state = (QStyle::State)oldState; + + startImage.fill(0); + QWindowsVistaTransition *t = new QWindowsVistaTransition; + t->setWidget(w); + QPainter startPainter(&startImage); + + if (!anim) { + proxy()->drawControl(element, &opt, &startPainter, 0 /* Intentional */); + } else { + anim->paint(&startPainter, &opt); + d->stopAnimation(widget); + } + + t->setStartImage(startImage); + d->startAnimation(t); + + endImage.fill(0); + QPainter endPainter(&endImage); + proxy()->drawControl(element, option, &endPainter, 0 /* Intentional */); + t->setEndImage(endImage); + int duration = 0; + HTHEME theme = pOpenThemeData(0, L"Button"); + + int fromState = buttonStateId(oldState, BP_PUSHBUTTON); + int toState = buttonStateId(option->state, BP_PUSHBUTTON); + if (pGetThemeTransitionDuration(theme, BP_PUSHBUTTON, fromState, toState, TMT_TRANSITIONDURATIONS, &duration) == S_OK) + t->setDuration(duration); + else + t->setDuration(0); + t->setStartTime(QTime::currentTime()); + } + } + } + } + switch (element) { + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) + { + + if (QWindowsVistaAnimation *anim = d->widgetAnimation(widget)) { + anim->paint(painter, option); + } else { + name = QLatin1String("BUTTON"); + 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 (widget && d->transitionsEnabled() && (btn->features & QStyleOptionButton::DefaultButton) && + !(state & (State_Sunken | State_On)) && !(state & State_MouseOver) && + (state & State_Enabled) && (state & State_Active)) + { + QWindowsVistaAnimation *anim = d->widgetAnimation(widget); + if (!anim && widget) { + QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + startImage.fill(0); + QImage alternateImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + alternateImage.fill(0); + + QWindowsVistaPulse *pulse = new QWindowsVistaPulse; + pulse->setWidget(const_cast<QWidget*>(widget)); + + QPainter startPainter(&startImage); + stateId = PBS_DEFAULTED; + XPThemeData theme(widget, &startPainter, name, partId, stateId, rect); + d->drawBackground(theme); + + QPainter alternatePainter(&alternateImage); + theme.stateId = PBS_DEFAULTED_ANIMATING; + theme.painter = &alternatePainter; + d->drawBackground(theme); + pulse->setPrimaryImage(startImage); + pulse->setAlternateImage(alternateImage); + pulse->setStartTime(QTime::currentTime()); + pulse->setDuration(2000); + d->startAnimation(pulse); + anim = pulse; + } + + if (anim) + anim->paint(painter, option); + else { + XPThemeData theme(widget, painter, name, partId, stateId, rect); + d->drawBackground(theme); + } + } + else { + d->stopAnimation(widget); + XPThemeData theme(widget, painter, name, partId, stateId, rect); + d->drawBackground(theme); + } + } + } + if (btn->features & QStyleOptionButton::HasMenu) { + int mbiw = 0, mbih = 0; + XPThemeData theme(widget, 0, QLatin1String("TOOLBAR"), TP_DROPDOWNBUTTON); + if (theme.isValid()) { + SIZE size; + if (pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size) == S_OK) { + mbiw = size.cx; + mbih = size.cy; + } + } + QRect ir = subElementRect(SE_PushButtonContents, option, 0); + 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; +#ifndef QT_NO_PROGRESSBAR + case CE_ProgressBarContents: + if (const QStyleOptionProgressBar *bar + = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) { + int stateId = MBI_NORMAL; + if (disabled) + stateId = MBI_DISABLED; + bool isIndeterminate = (bar->minimum == 0 && bar->maximum == 0); + bool vertical = false; + bool inverted = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + vertical = (pb2->orientation == Qt::Vertical); + inverted = pb2->invertedAppearance; + } + + if (const QProgressBar *progressbar = qobject_cast<const QProgressBar *>(widget)) { + if (((progressbar->value() > 0 && d->transitionsEnabled()) || isIndeterminate)) { + if (!d->widgetAnimation(progressbar) && progressbar->value() < progressbar->maximum()) { + QWindowsVistaAnimation *a = new QWindowsVistaAnimation; + a->setWidget(const_cast<QWidget*>(widget)); + a->setStartTime(QTime::currentTime()); + d->startAnimation(a); + } + } else { + d->stopAnimation(progressbar); + } + } + + XPThemeData theme(widget, painter, QLatin1String("PROGRESS"), vertical ? PP_FILLVERT : PP_FILL); + theme.rect = option->rect; + bool reverse = (bar->direction == Qt::LeftToRight && inverted) || (bar->direction == Qt::RightToLeft && !inverted); + QTime current = QTime::currentTime(); + + if (isIndeterminate) { + if (QWindowsVistaAnimation *a = d->widgetAnimation(widget)) { + 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(QTime::currentTime()); + 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), theme.rect.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 (QWindowsVistaAnimation *a = d->widgetAnimation(widget)) { + 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(QTime::currentTime()); + else + d->stopAnimation(widget); //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; +#endif // QT_NO_PROGRESSBAR + 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); + + uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; + if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget)) + alignment |= Qt::TextHideMnemonic; + + //The rect adjustment is a workaround for the menu not really filling its background. + XPThemeData theme(widget, painter, QLatin1String("MENU"), 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, QLatin1String("MENU"), 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; +#ifndef QT_NO_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 + int checkcol = 28; + { + SIZE size; + MARGINS margins; + XPThemeData theme(widget, 0, QLatin1String("MENU"), MENU_POPUPCHECKBACKGROUND, MBI_HOT); + pGetThemePartSize(theme.handle(), NULL, MENU_POPUPCHECK, 0, NULL,TS_TRUE, &size); + pGetThemeMargins(theme.handle(), NULL, MENU_POPUPCHECK, 0, TMT_CONTENTMARGINS, NULL, &margins); + checkcol = qMax(menuitem->maxIconWidth, int(6 + size.cx + margins.cxLeftWidth + margins.cxRightWidth)); + } + QColor darkLine = option->palette.background().color().darker(108); + QColor lightLine = option->palette.background().color().lighter(107); + QRect rect = option->rect; + QStyleOptionMenuItem mbiCopy = *menuitem; + + //draw vertical menu line + 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(), 3, p2.y() - p1.y() + 1); + XPThemeData theme2(widget, painter, QLatin1String("MENU"), MENU_POPUPGUTTER, stateId, gutterRect); + d->drawBackground(theme2); + + int x, y, w, h; + menuitem->rect.getRect(&x, &y, &w, &h); + int tab = menuitem->tabWidth; + 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; + QPoint p1 = QPoint(x + checkcol, yoff); + QPoint p2 = QPoint(x + w + 6 , yoff); + stateId = MBI_HOT; + QRect subRect(p1.x(), p1.y(), p2.x() - p1.x(), 6); + subRect = QStyle::visualRect(option->direction, option->rect, subRect ); + XPThemeData theme2(widget, painter, QLatin1String("MENU"), MENU_POPUPSEPARATOR, stateId, subRect); + d->drawBackground(theme2); + return; + } + + QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(), + menuitem->rect.y(), checkcol - 6, menuitem->rect.height())); + + if (act) { + stateId = MBI_HOT; + XPThemeData theme2(widget, painter, QLatin1String("MENU"), MENU_POPUPITEM, stateId, option->rect); + d->drawBackground(theme2); + } + + if (checked) { + XPThemeData theme(widget, painter, QLatin1String("MENU"), MENU_POPUPCHECKBACKGROUND, + menuitem->icon.isNull() ? MBI_HOT : MBI_PUSHED, vCheckRect); + SIZE size; + MARGINS margins; + pGetThemePartSize(theme.handle(), NULL, MENU_POPUPCHECK, 0, NULL,TS_TRUE, &size); + pGetThemeMargins(theme.handle(), NULL, MENU_POPUPCHECK, 0, + TMT_CONTENTMARGINS, NULL, &margins); + QRect checkRect(0, 0, size.cx + margins.cxLeftWidth + margins.cxRightWidth , + size.cy + margins.cyBottomHeight + margins.cyTopHeight); + checkRect.moveCenter(vCheckRect.center()); + theme.rect = checkRect; + + d->drawBackground(theme); + + if (menuitem->icon.isNull()) { + checkRect = QRect(0, 0, size.cx, size.cy); + 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); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(vCheckRect.center()); + painter->setPen(menuitem->palette.text().color()); + painter->drawPixmap(pmr.topLeft(), pixmap); + } + + painter->setPen(menuitem->palette.buttonText().color()); + + QColor discol; + if (dis) { + discol = menuitem->palette.text().color(); + painter->setPen(discol); + } + + int xm = windowsItemFrame + checkcol + windowsItemHMargin; + 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(discol); + 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_NO_MENU + case CE_HeaderSection: + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) { + name = QLatin1String("HEADER"); + 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, name, 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, QLatin1String("MENU"), 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.background().color().darker(130)); + QStyleOptionToolBar copyOpt = *toolbar; + copyOpt.palette = pal; + QWindowsStyle::drawControl(element, ©Opt, painter, widget); + } + break; + case CE_DockWidgetTitle: + if (const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(widget)) { + QRect rect = option->rect; + if (dockWidget->isFloating()) { + QWindowsXPStyle::drawControl(element, option, painter, widget); + break; //otherwise fall through + } + + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) { + + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + if (verticalTitleBar) { + QSize s = rect.size(); + s.transpose(); + rect.setSize(s); + + painter->translate(rect.left() - 1, rect.top() + rect.width()); + painter->rotate(-90); + painter->translate(-rect.left() + 1, -rect.top()); + } + + painter->setBrush(option->palette.background().color().darker(110)); + painter->setPen(option->palette.background().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 != 0 && dw->isFloating(); + + QRect r = option->rect.adjusted(0, 2, -1, -3); + QRect titleRect = r; + + if (dwOpt->closable) { + QSize sz = standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10)); + titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0); + } + + if (dwOpt->floatable) { + QSize sz = 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 != 0 && 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 = painter->fontMetrics().descent(); + drawItemText(painter, rect.adjusted(indent + 1, 1, -indent - 1, -1), + Qt::AlignLeft | Qt::AlignVCenter, dwOpt->palette, + dwOpt->state & State_Enabled, titleText, + QPalette::WindowText); + } + } + break; + } +#ifndef QT_NO_ITEMVIEWS + case CE_ItemViewItem: + { + const QStyleOptionViewItemV4 *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 QStyleOptionViewItemV4 *>(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 = pGetThemeColor(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)); + QStyleOptionViewItemV4 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_NO_ITEMVIEWS + + 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() && widget) { + if ((qobject_cast<const QScrollBar *>(widget) && control == CC_ScrollBar) +#ifndef QT_NO_SPINBOX + || (qobject_cast<const QAbstractSpinBox*>(widget) && control == CC_SpinBox) +#endif // QT_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + || (qobject_cast<const QComboBox*>(widget) && control == CC_ComboBox) +#endif // QT_NO_COMBOBOX + ) + { + QWidget *w = const_cast<QWidget *> (widget); + + int oldState = w->property("_q_stylestate").toInt(); + int oldActiveControls = w->property("_q_stylecontrols").toInt(); + QRect oldRect = w->property("_q_stylerect").toRect(); + w->setProperty("_q_stylestate", (int)option->state); + w->setProperty("_q_stylecontrols", (int)option->activeSubControls); + w->setProperty("_q_stylerect", w->rect()); + + bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) || + (state & State_On) != (oldState & State_On) || + (state & State_MouseOver) != (oldState & State_MouseOver) || + oldActiveControls != option->activeSubControls); + + + if (qstyleoption_cast<const QStyleOptionSlider *>(option)) { + QRect oldSliderPos = w->property("_q_stylesliderpos").toRect(); + QRect currentPos = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + w->setProperty("_q_stylesliderpos", currentPos); + if (oldSliderPos != currentPos) { + doTransition = false; + d->stopAnimation(widget); + } + } 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(widget); + } + + if (doTransition) { + QImage startImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QImage endImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); + QWindowsVistaAnimation *anim = d->widgetAnimation(widget); + QWindowsVistaTransition *t = new QWindowsVistaTransition; + t->setWidget(w); + if (!anim) { + if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option)) { + //Combo boxes are special cased to avoid cleartype issues + startImage.fill(0); + QPainter startPainter(&startImage); + QStyleOptionComboBox startCombo = *combo; + startCombo.state = (QStyle::State)oldState; + startCombo.activeSubControls = (QStyle::SubControl)oldActiveControls; + proxy()->drawComplexControl(control, &startCombo, &startPainter, 0 /* Intentional */); + t->setStartImage(startImage); + } else if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option)) { + //This is a workaround for the direct3d engine as it currently has some issues with grabWindow + startImage.fill(0); + QPainter startPainter(&startImage); + QStyleOptionSlider startSlider = *slider; + startSlider.state = (QStyle::State)oldState; + startSlider.activeSubControls = (QStyle::SubControl)oldActiveControls; + proxy()->drawComplexControl(control, &startSlider, &startPainter, 0 /* Intentional */); + t->setStartImage(startImage); + } else { + QPoint offset(0, 0); + if (!widget->internalWinId()) + offset = widget->mapTo(widget->nativeParentWidget(), offset); + t->setStartImage(QPixmap::grabWindow(widget->effectiveWinId(), offset.x(), offset.y(), + option->rect.width(), option->rect.height()).toImage()); + } + } else { + startImage.fill(0); + QPainter startPainter(&startImage); + anim->paint(&startPainter, option); + t->setStartImage(startImage); + } + d->startAnimation(t); + endImage.fill(0); + QPainter endPainter(&endImage); + proxy()->drawComplexControl(control, option, &endPainter, 0 /* Intentional */); + t->setEndImage(endImage); + t->setStartTime(QTime::currentTime()); + + if (option->state & State_MouseOver || option->state & State_Sunken) + t->setDuration(150); + else + t->setDuration(500); + } + + if (QWindowsVistaAnimation *anim = d->widgetAnimation(widget)) { + 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, QLatin1String("EDIT"), partId, stateId, r); + + d->drawBackground(theme); + } + if (sub & SC_ComboBoxArrow) { + QRect subRect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + XPThemeData theme(widget, painter, QLatin1String("COMBOBOX")); + 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) { + QStyleOptionButton btn; + btn.QStyleOption::operator=(*option); + btn.rect = option->rect.adjusted(-1, -1, 1, 1); + if (sub & SC_ComboBoxArrow) + btn.features = QStyleOptionButton::HasMenu; + proxy()->drawControl(QStyle::CE_PushButton, &btn, painter, widget); + } + } + } + break; + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, painter, QLatin1String("SCROLLBAR")); + + 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.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + theme.stateId = stateId; + d->drawBackground(theme); + + // Calculate rect of gripper + const int swidth = theme.rect.width(); + const int sheight = theme.rect.height(); + + MARGINS contentsMargin; + RECT rect = theme.toRECT(theme.rect); + pGetThemeMargins(theme.handle(), 0, theme.partId, theme.stateId, TMT_SIZINGMARGINS, &rect, &contentsMargin); + + SIZE size; + theme.partId = flags & State_Horizontal ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + int gw = size.cx, gh = size.cy; + + + QRect gripperBounds; + if (flags & State_Horizontal && ((swidth - contentsMargin.cxLeftWidth - contentsMargin.cxRightWidth) > gw)) { + gripperBounds.setLeft(theme.rect.left() + swidth/2 - gw/2); + gripperBounds.setTop(theme.rect.top() + sheight/2 - gh/2); + gripperBounds.setWidth(gw); + gripperBounds.setHeight(gh); + } else if ((sheight - contentsMargin.cyTopHeight - contentsMargin.cyBottomHeight) > gh) { + gripperBounds.setLeft(theme.rect.left() + swidth/2 - gw/2); + gripperBounds.setTop(theme.rect.top() + sheight/2 - gh/2); + gripperBounds.setWidth(gw); + gripperBounds.setHeight(gh); + } + + // Draw gripper if there is enough space + if (!gripperBounds.isEmpty() && flags & State_Enabled) { + painter->save(); + XPThemeData grippBackground = theme; + grippBackground.partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT; + theme.rect = gripperBounds; + painter->setClipRegion(d->region(theme));// Only change inside the region of the gripper + d->drawBackground(grippBackground);// The gutter is the grippers background + d->drawBackground(theme); // Transparent gripper ontop of background + painter->restore(); + } + } + } + } + break; +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) + { + XPThemeData theme(widget, painter, QLatin1String("SPIN")); + 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, QLatin1String("EDIT"), partId, stateId, r); + ftheme.noContent = true; + 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_NO_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; + { + SIZE size; + MARGINS margins; + XPThemeData theme(widget, 0, QLatin1String("MENU"), MENU_POPUPCHECKBACKGROUND, MBI_HOT); + pGetThemePartSize(theme.handle(), NULL, MENU_POPUPCHECK, 0, NULL,TS_TRUE, &size); + pGetThemeMargins(theme.handle(), NULL, MENU_POPUPCHECK, 0, TMT_CONTENTMARGINS, NULL, &margins); + minimumHeight = qMax<qint32>(size.cy + margins.cyBottomHeight+ margins.cyTopHeight, sz.height()); + sz.rwidth() += size.cx + margins.cxLeftWidth + margins.cxRightWidth; + } + + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) { + if (menuitem->menuItemType != QStyleOptionMenuItem::Separator) + sz.setHeight(minimumHeight); + } + return sz; +#ifndef QT_NO_MENUBAR + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(windowsItemHMargin * 5 + 1, 5); + return sz; + break; +#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; + 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; + HTHEME theme = pOpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : 0, 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); + + int result = pGetThemeMargins(theme, + NULL, + BP_PUSHBUTTON, + stateId, + TMT_CONTENTMARGINS, + NULL, + &borderSize); + + if (result == S_OK) { + 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, 0, QLatin1String("HEADER"), HP_HEADERSORTARROW, HSAS_SORTEDDOWN, option->rect); + + int arrowWidth = 13; + int arrowHeight = 5; + if (theme.isValid()) { + SIZE size; + if (pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size) == S_OK) { + arrowWidth = size.cx; + arrowHeight = size.cy; + } + } + 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 QStyleOptionViewItemV4 *>(option)) + rect.adjust(-2, 0, 2, 0); + break; + case SE_ItemViewItemFocusRect: + if (const QStyleOptionViewItemV4 *vopt = qstyleoption_cast<const QStyleOptionViewItemV4 *>(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 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; +} + + +/*! \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, 0, QLatin1String("TOOLTIP"), TTP_STANDARD, TTSS_NORMAL, option->rect); + mask->region = d->region(themeData); + } + } + break; + case SH_Table_GridLineColor: + if (option) + ret = option->palette.color(QPalette::Base).darker(118).rgb(); + else + ret = -1; + 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) { +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = cb->rect.x(), + y = cb->rect.y(), + wi = cb->rect.width(), + he = cb->rect.height(); + int xpos = x; + int margin = cb->frame ? 3 : 0; + int bmarg = cb->frame ? 2 : 0; + int arrowButtonWidth = bmarg + 16; + xpos += 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 - 16, he - 2 * margin); + break; + case SC_ComboBoxListBoxPopup: + rect = cb->rect; + break; + default: + break; + } + rect = visualRect(cb->direction, cb->rect, rect); + return rect; + } +#endif // QT_NO_COMBOBOX + case CC_TitleBar: + if (const QStyleOptionTitleBar *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(); + int buttonWidth = GetSystemMetrics(SM_CXSIZE) - 4; + + 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, -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, 4); + } + 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); + } + rect.translate(0, 2); + rect = visualRect(option->direction, option->rect, rect); + break; + case SC_TitleBarSysMenu: + { + const int controlTop = 6; + const int controlHeight = height - controlTop - 3; + int iconExtent = proxy()->pixelMetric(PM_SmallIconSize); + 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, 3); + rect = visualRect(option->direction, option->rect, rect); + } + break; + default: + break; + } + } + break; + default: + break; + } + return rect; +} + +/*! + \internal + */ +bool QWindowsVistaStyle::event(QEvent *e) +{ + Q_D(QWindowsVistaStyle); + switch (e->type()) { + case QEvent::Timer: + { + QTimerEvent *timerEvent = (QTimerEvent *)e; + if (d->animationTimer.timerId() == timerEvent->timerId()) { + d->timerEvent(); + e->accept(); + return true; + } + } + break; + default: + break; + } + return QWindowsXPStyle::event(e); +} + +/*! + \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); +} + +/*! + \internal + */ +int QWindowsVistaStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::pixelMetric(metric, option, widget); + } + switch (metric) { + + case PM_DockWidgetTitleBarButtonMargin: + return int(QStyleHelper::dpiScaled(5.)); + case PM_ScrollBarSliderMin: + return int(QStyleHelper::dpiScaled(18.)); + case PM_MenuHMargin: + case PM_MenuVMargin: + return 0; + case PM_MenuPanelWidth: + return 3; + default: + break; + } + return QWindowsXPStyle::pixelMetric(metric, option, widget); +} + +/*! + \internal + */ +QPalette QWindowsVistaStyle::standardPalette() const +{ + return QWindowsXPStyle::standardPalette(); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QApplication *app) +{ + QWindowsXPStyle::polish(app); +} + +/*! + \internal + */ +void QWindowsVistaStyle::polish(QWidget *widget) +{ + QWindowsXPStyle::polish(widget); +#ifndef QT_NO_LINEEDIT + if (qobject_cast<QLineEdit*>(widget)) + widget->setAttribute(Qt::WA_Hover); + else +#endif // QT_NO_LINEEDIT + if (qobject_cast<QGroupBox*>(widget)) + widget->setAttribute(Qt::WA_Hover); + else if (qobject_cast<QCommandLinkButton*>(widget)) { + QFont buttonFont = widget->font(); + buttonFont.setFamily(QLatin1String("Segoe UI")); + widget->setFont(buttonFont); + } + 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 = pOpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : 0, L"TOOLTIP"); + if (theme) { + if (pGetThemeColor(theme, TTP_STANDARD, TTSS_NORMAL, TMT_TEXTCOLOR, &bgRef) == S_OK) { + 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); + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 9, 0, 0); + } +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<QInputDialog *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground); + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 9, 0, 0); + } +#endif // QT_NO_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 = const_cast<QWindowsVistaStylePrivate*>(d_func()); + d->stopAnimation(widget); + +#ifndef QT_NO_LINEEDIT + if (qobject_cast<QLineEdit*>(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else +#endif // QT_NO_LINEEDIT + if (qobject_cast<QGroupBox*>(widget)) + widget->setAttribute(Qt::WA_Hover, false); + else if (qobject_cast<QMessageBox *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 0, 0, 0); + } +#ifndef QT_NO_INPUTDIALOG + else if (qobject_cast<QInputDialog *> (widget)) { + widget->setAttribute(Qt::WA_StyledBackground, false); + QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox")); + if (buttonBox) + buttonBox->setContentsMargins(0, 0, 0, 0); + } +#endif // QT_NO_INPUTDIALOG + else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) { + tree->viewport()->setAttribute(Qt::WA_Hover, false); + } else if (qobject_cast<QCommandLinkButton*>(widget)) { + QFont font = QApplication::font("QCommandLinkButton"); + QFont widgetFont = widget->font(); + widgetFont.setFamily(font.family()); //Only family set by polish + widget->setFont(widgetFont); + } +} + + +/*! + \internal + */ +void QWindowsVistaStyle::unpolish(QApplication *app) +{ + QWindowsXPStyle::unpolish(app); +} + +/*! + \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); +} + +QWindowsVistaStylePrivate::QWindowsVistaStylePrivate() : + QWindowsXPStylePrivate(), m_treeViewHelper(0) +{ + resolveSymbols(); +} + +QWindowsVistaStylePrivate::~QWindowsVistaStylePrivate() +{ + delete m_treeViewHelper; +} + +void QWindowsVistaStylePrivate::timerEvent() +{ + for (int i = animations.size() - 1 ; i >= 0 ; --i) { + + if (animations[i]->widget()) + animations[i]->widget()->update(); + + if (!animations[i]->widget() || + !animations[i]->widget()->isEnabled() || + !animations[i]->widget()->isVisible() || + animations[i]->widget()->window()->isMinimized() || + !animations[i]->running() || + !QWindowsVistaStylePrivate::useVista()) + { + QWindowsVistaAnimation *a = animations.takeAt(i); + delete a; + } + } + if (animations.size() == 0 && animationTimer.isActive()) { + animationTimer.stop(); + } +} + +void QWindowsVistaStylePrivate::stopAnimation(const QWidget *w) +{ + for (int i = animations.size() - 1 ; i >= 0 ; --i) { + if (animations[i]->widget() == w) { + QWindowsVistaAnimation *a = animations.takeAt(i); + delete a; + break; + } + } +} + +void QWindowsVistaStylePrivate::startAnimation(QWindowsVistaAnimation *t) +{ + Q_Q(QWindowsVistaStyle); + stopAnimation(t->widget()); + animations.append(t); + if (animations.size() > 0 && !animationTimer.isActive()) { + animationTimer.start(45, q); + } +} + +bool QWindowsVistaStylePrivate::transitionsEnabled() const +{ + BOOL animEnabled = false; + if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &animEnabled, 0)) + { + if (animEnabled) + return true; + } + return false; +} + + +QWindowsVistaAnimation * QWindowsVistaStylePrivate::widgetAnimation(const QWidget *widget) const +{ + if (!widget) + return 0; + foreach (QWindowsVistaAnimation *a, animations) { + if (a->widget() == widget) + return a; + } + return 0; +} + + +/*! \internal + Returns true if all the necessary theme engine symbols were + resolved. +*/ +bool QWindowsVistaStylePrivate::resolveSymbols() +{ + static bool tried = false; + if (!tried) { + tried = true; + QSystemLibrary themeLib(QLatin1String("uxtheme")); + pSetWindowTheme = (PtrSetWindowTheme )themeLib.resolve("SetWindowTheme"); + pIsThemePartDefined = (PtrIsThemePartDefined )themeLib.resolve("IsThemePartDefined"); + pGetThemePartSize = (PtrGetThemePartSize )themeLib.resolve("GetThemePartSize"); + pOpenThemeData = (PtrOpenThemeData )themeLib.resolve("OpenThemeData"); + pCloseThemeData = (PtrCloseThemeData )themeLib.resolve("CloseThemeData"); + pDrawThemeBackground = (PtrDrawThemeBackground )themeLib.resolve("DrawThemeBackground"); + pDrawThemeBackgroundEx = (PtrDrawThemeBackgroundEx )themeLib.resolve("DrawThemeBackgroundEx"); + pGetCurrentThemeName = (PtrGetCurrentThemeName )themeLib.resolve("GetCurrentThemeName"); + pGetThemeBool = (PtrGetThemeBool )themeLib.resolve("GetThemeBool"); + pGetThemeColor = (PtrGetThemeColor )themeLib.resolve("GetThemeColor"); + pGetThemeEnumValue = (PtrGetThemeEnumValue )themeLib.resolve("GetThemeEnumValue"); + pGetThemeFilename = (PtrGetThemeFilename )themeLib.resolve("GetThemeFilename"); + pGetThemeFont = (PtrGetThemeFont )themeLib.resolve("GetThemeFont"); + pGetThemeInt = (PtrGetThemeInt )themeLib.resolve("GetThemeInt"); + pGetThemeIntList = (PtrGetThemeIntList )themeLib.resolve("GetThemeIntList"); + pGetThemeMargins = (PtrGetThemeMargins )themeLib.resolve("GetThemeMargins"); + pGetThemeMetric = (PtrGetThemeMetric )themeLib.resolve("GetThemeMetric"); + pGetThemePartSize = (PtrGetThemePartSize )themeLib.resolve("GetThemePartSize"); + pGetThemePosition = (PtrGetThemePosition )themeLib.resolve("GetThemePosition"); + pGetThemeRect = (PtrGetThemeRect )themeLib.resolve("GetThemeRect"); + pGetThemeString = (PtrGetThemeString )themeLib.resolve("GetThemeString"); + pGetThemeTransitionDuration = (PtrGetThemeTransitionDuration)themeLib.resolve("GetThemeTransitionDuration"); + pGetThemePropertyOrigin = (PtrGetThemePropertyOrigin)themeLib.resolve("GetThemePropertyOrigin"); + } + return pGetThemeTransitionDuration != 0; +} + +/* + * We need to set the windows explorer theme explicitly on a native widget + * in order to get Vista-style item view themes + */ +QWidget *QWindowsVistaStylePrivate::treeViewHelper() +{ + if (!m_treeViewHelper) { + m_treeViewHelper = new QWidget(0); + pSetWindowTheme(m_treeViewHelper->winId(), L"explorer", NULL); + } + return m_treeViewHelper; +} + + +/*! +\internal +*/ +QIcon QWindowsVistaStyle::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsVistaStylePrivate::useVista()) { + return QWindowsStyle::standardIconImplementation(standardIcon, option, widget); + } + + QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate *>(d_func()); + switch(standardIcon) { + case SP_CommandLink: + { + XPThemeData theme(0, 0, QLatin1String("BUTTON"), BP_COMMANDLINKGLYPH, CMDLGS_NORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + QIcon linkGlyph; + QPixmap pm = QPixmap(size.cx, size.cy); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(0, 0, size.cx, size.cy); + 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::standardIconImplementation(standardIcon, option, widget); +} + +QT_END_NAMESPACE + +#endif //QT_NO_WINDOWSVISTA diff --git a/src/gui/styles/qwindowsvistastyle.h b/src/gui/styles/qwindowsvistastyle.h new file mode 100644 index 0000000000..c13dccf28b --- /dev/null +++ b/src/gui/styles/qwindowsvistastyle.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSVISTASTYLE_H +#define QWINDOWSVISTASTYLE_H + +#include <QtGui/qwindowsxpstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) + +class QWindowsVistaStylePrivate; +class Q_GUI_EXPORT QWindowsVistaStyle : public QWindowsXPStyle +{ + Q_OBJECT +public: + QWindowsVistaStyle(); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const; + void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const; + QSize sizeFromContents(ContentsType type, const QStyleOption *option, + const QSize &size, const QWidget *widget) const; + + QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, + SubControl sc, const QWidget *widget) const; + + SubControl hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option, + const QPoint &pos, const QWidget *widget = 0) const; + + QPixmap standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt, + const QWidget *widget = 0) const; + int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *opt = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + + void polish(QWidget *widget); + void unpolish(QWidget *widget); + void polish(QPalette &pal); + void polish(QApplication *app); + void unpolish(QApplication *app); + bool event(QEvent *event); + QPalette standardPalette() const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QWindowsVistaStyle) + Q_DECLARE_PRIVATE(QWindowsVistaStyle) + friend class QStyleFactory; +}; +#endif //QT_NO_STYLE_WINDOWSVISTA + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif //QWINDOWSVISTASTYLE_H diff --git a/src/gui/styles/qwindowsvistastyle_p.h b/src/gui/styles/qwindowsvistastyle_p.h new file mode 100644 index 0000000000..ba653880c9 --- /dev/null +++ b/src/gui/styles/qwindowsvistastyle_p.h @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSVISTASTYLE_P_H +#define QWINDOWSVISTASTYLE_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 "qwindowsvistastyle.h" + +#if !defined(QT_NO_STYLE_WINDOWSVISTA) +#include <private/qwindowsxpstyle_p.h> +#include <private/qpaintengine_raster_p.h> +#include <qlibrary.h> +#include <qpaintengine.h> +#include <qwidget.h> +#include <qapplication.h> +#include <qpixmapcache.h> +#include <qstyleoption.h> +#include <qpushbutton.h> +#include <qradiobutton.h> +#include <qcheckbox.h> +#include <qlineedit.h> +#include <qgroupbox.h> +#include <qtoolbutton.h> +#include <qspinbox.h> +#include <qtoolbar.h> +#include <qcombobox.h> +#include <qscrollbar.h> +#include <qprogressbar.h> +#include <qdockwidget.h> +#include <qlistview.h> +#include <qtreeview.h> +#include <qtextedit.h> +#include <qmessagebox.h> +#include <qdialogbuttonbox.h> +#include <qinputdialog.h> +#include <qtreeview.h> +#include <qlistview.h> +#include <qtableview.h> +#include <qbasictimer.h> +#include <qdatetime.h> +#include <qcommandlinkbutton.h> + +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 : + QWindowsVistaAnimation() : _running(true) { } + virtual ~QWindowsVistaAnimation() { } + QWidget * widget() const { return _widget; } + bool running() const { return _running; } + const QTime &startTime() const { return _startTime; } + void setRunning(bool val) { _running = val; } + void setWidget(QWidget *widget) { _widget = widget; } + void setStartTime(const QTime &startTime) { _startTime = startTime; } + virtual void paint(QPainter *painter, const QStyleOption *option); + +protected: + void drawBlendedImage(QPainter *painter, QRect rect, float value); + QTime _startTime; + QPointer<QWidget> _widget; + QImage _primaryImage; + QImage _secondaryImage; + QImage _tempImage; + bool _running; +}; + + +// Handles state transition animations +class QWindowsVistaTransition : public QWindowsVistaAnimation +{ +public : + QWindowsVistaTransition() : QWindowsVistaAnimation() {} + virtual ~QWindowsVistaTransition() { } + void setDuration(int duration) { _duration = duration; } + void setStartImage(const QImage &image) { _primaryImage = image; } + void setEndImage(const QImage &image) { _secondaryImage = image; } + virtual void paint(QPainter *painter, const QStyleOption *option); + int duration() const { return _duration; } + int _duration; //set time in ms to complete a state transition +}; + + +// Handles pulse animations (default buttons) +class QWindowsVistaPulse: public QWindowsVistaAnimation +{ +public : + QWindowsVistaPulse() : QWindowsVistaAnimation() {} + virtual ~QWindowsVistaPulse() { } + void setDuration(int duration) { _duration = duration; } + void setPrimaryImage(const QImage &image) { _primaryImage = image; } + void setAlternateImage(const QImage &image) { _secondaryImage = image; } + virtual void paint(QPainter *painter, const QStyleOption *option); + int duration() const { return _duration; } + int _duration; //time in ms to complete a pulse cycle +}; + + +class QWindowsVistaStylePrivate : public QWindowsXPStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsVistaStyle) + +public: + QWindowsVistaStylePrivate(); + ~QWindowsVistaStylePrivate(); + static bool resolveSymbols(); + static inline bool useVista(); + void startAnimation(QWindowsVistaAnimation *); + void stopAnimation(const QWidget *); + QWindowsVistaAnimation* widgetAnimation(const QWidget *) const; + void timerEvent(); + bool transitionsEnabled() const; + QWidget *treeViewHelper(); + +private: + QList <QWindowsVistaAnimation*> animations; + QBasicTimer animationTimer; + QWidget *m_treeViewHelper; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_STYLE_WINDOWSVISTA + +#endif // QWINDOWSVISTASTYLE_P_H diff --git a/src/gui/styles/qwindowsxpstyle.cpp b/src/gui/styles/qwindowsxpstyle.cpp new file mode 100644 index 0000000000..74a20fce29 --- /dev/null +++ b/src/gui/styles/qwindowsxpstyle.cpp @@ -0,0 +1,4271 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qwindowsxpstyle.h" +#include "qwindowsxpstyle_p.h" + +#if !defined(QT_NO_STYLE_WINDOWSXP) || defined(QT_PLUGIN) + +#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 <private/qsystemlibrary_p.h> +#include <qpainter.h> +#include <qpaintengine.h> +#include <qwidget.h> +#include <qapplication.h> +#include <qpixmapcache.h> + +#include <qdesktopwidget.h> +#include <qtoolbutton.h> +#include <qtabbar.h> +#include <qcombobox.h> +#include <qscrollbar.h> +#include <qheaderview.h> +#include <qspinbox.h> +#include <qlistview.h> +#include <qstackedwidget.h> +#include <qpushbutton.h> +#include <qtoolbar.h> +#include <qlabel.h> +#include <qvarlengtharray.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +// Runtime resolved theme engine function calls +typedef bool (WINAPI *PtrIsAppThemed)(); +typedef bool (WINAPI *PtrIsThemeActive)(); +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HTHEME (WINAPI *PtrOpenThemeData)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT (WINAPI *PtrCloseThemeData)(HTHEME hTheme); +typedef HRESULT (WINAPI *PtrDrawThemeBackground)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect); +typedef HRESULT (WINAPI *PtrDrawThemeBackgroundEx)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const DTBGOPTS *pOptions); +typedef HRESULT (WINAPI *PtrGetCurrentThemeName)(OUT LPWSTR pszThemeFileName, int cchMaxNameChars, OUT OPTIONAL LPWSTR pszColorBuff, int cchMaxColorChars, OUT OPTIONAL LPWSTR pszSizeBuff, int cchMaxSizeChars); +typedef HRESULT (WINAPI *PtrGetThemeDocumentationProperty)(LPCWSTR pszThemeName, LPCWSTR pszPropertyName, OUT LPWSTR pszValueBuff, int cchMaxValChars); +typedef HRESULT (WINAPI *PtrGetThemeBool)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT BOOL *pfVal); +typedef HRESULT (WINAPI *PtrGetThemeColor)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT COLORREF *pColor); +typedef HRESULT (WINAPI *PtrGetThemeEnumValue)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemeFilename)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT LPWSTR pszThemeFileName, int cchMaxBuffChars); +typedef HRESULT (WINAPI *PtrGetThemeFont)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OUT LOGFONT *pFont); +typedef HRESULT (WINAPI *PtrGetThemeInt)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemeIntList)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT INTLIST *pIntList); +typedef HRESULT (WINAPI *PtrGetThemeMargins)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OPTIONAL RECT *prc, OUT MARGINS *pMargins); +typedef HRESULT (WINAPI *PtrGetThemeMetric)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, int iPropId, OUT int *piVal); +typedef HRESULT (WINAPI *PtrGetThemePartSize)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, OPTIONAL RECT *prc, enum THEMESIZE eSize, OUT SIZE *psz); +typedef HRESULT (WINAPI *PtrGetThemePosition)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT POINT *pPoint); +typedef HRESULT (WINAPI *PtrGetThemePropertyOrigin)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT enum PROPERTYORIGIN *pOrigin); +typedef HRESULT (WINAPI *PtrGetThemeRect)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT RECT *pRect); +typedef HRESULT (WINAPI *PtrGetThemeString)(HTHEME hTheme, int iPartId, int iStateId, int iPropId, OUT LPWSTR pszBuff, int cchMaxBuffChars); +typedef HRESULT (WINAPI *PtrGetThemeBackgroundRegion)(HTHEME hTheme, OPTIONAL HDC hdc, int iPartId, int iStateId, const RECT *pRect, OUT HRGN *pRegion); +typedef BOOL (WINAPI *PtrIsThemeBackgroundPartiallyTransparent)(HTHEME hTheme, int iPartId, int iStateId); + +static PtrIsAppThemed pIsAppThemed = 0; +static PtrIsThemeActive pIsThemeActive = 0; +static PtrOpenThemeData pOpenThemeData = 0; +static PtrCloseThemeData pCloseThemeData = 0; +static PtrDrawThemeBackground pDrawThemeBackground = 0; +static PtrDrawThemeBackgroundEx pDrawThemeBackgroundEx = 0; +static PtrGetCurrentThemeName pGetCurrentThemeName = 0; +static PtrGetThemeBool pGetThemeBool = 0; +static PtrGetThemeColor pGetThemeColor = 0; +static PtrGetThemeEnumValue pGetThemeEnumValue = 0; +static PtrGetThemeFilename pGetThemeFilename = 0; +static PtrGetThemeFont pGetThemeFont = 0; +static PtrGetThemeInt pGetThemeInt = 0; +static PtrGetThemeIntList pGetThemeIntList = 0; +static PtrGetThemeMargins pGetThemeMargins = 0; +static PtrGetThemeMetric pGetThemeMetric = 0; +static PtrGetThemePartSize pGetThemePartSize = 0; +static PtrGetThemePosition pGetThemePosition = 0; +static PtrGetThemePropertyOrigin pGetThemePropertyOrigin = 0; +static PtrGetThemeRect pGetThemeRect = 0; +static PtrGetThemeString pGetThemeString = 0; +static PtrGetThemeBackgroundRegion pGetThemeBackgroundRegion = 0; +static PtrGetThemeDocumentationProperty pGetThemeDocumentationProperty = 0; +static PtrIsThemeBackgroundPartiallyTransparent pIsThemeBackgroundPartiallyTransparent = 0; + +// 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 + +// External function calls +extern Q_GUI_EXPORT HDC qt_win_display_dc(); +extern QRegion qt_region_from_HRGN(HRGN rgn); + + + +// Theme data helper ------------------------------------------------------------------------------ +/* \internal + Returns true if the themedata is valid for use. +*/ +bool XPThemeData::isValid() +{ + return QWindowsXPStylePrivate::useXP() && name.size() && 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 0; + + if (!htheme && QWindowsXPStylePrivate::handleMap) + htheme = QWindowsXPStylePrivate::handleMap->operator[](name); + + if (!htheme) { + htheme = pOpenThemeData(QWindowsXPStylePrivate::winId(widget), (wchar_t*)name.utf16()); + if (htheme) { + if (!QWindowsXPStylePrivate::handleMap) + QWindowsXPStylePrivate::handleMap = new QMap<QString, HTHEME>; + QWindowsXPStylePrivate::handleMap->operator[](name) = htheme; + } + } + + 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() +{ + if (!pIsThemeBackgroundPartiallyTransparent(handle(), partId, stateId)) + return 0; + + HRGN hrgn; + HDC dc = painter == 0 ? 0 : painter->paintEngine()->getDC(); + RECT nativeRect = toRECT(rect); + pGetThemeBackgroundRegion(handle(), dc, partId, stateId, &nativeRect, &hrgn); + if (dc) + painter->paintEngine()->releaseDC(dc); + return hrgn; +} + +// QWindowsXPStylePrivate ------------------------------------------------------------------------- +// Static initializations +QWidget *QWindowsXPStylePrivate::limboWidget = 0; +QPixmap *QWindowsXPStylePrivate::tabbody = 0; +QMap<QString,HTHEME> *QWindowsXPStylePrivate::handleMap = 0; +bool QWindowsXPStylePrivate::use_xp = false; +QBasicAtomicInt QWindowsXPStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting + +/* \internal + Checks if the theme engine can/should be used, or if we should + fall back to Windows style. +*/ +bool QWindowsXPStylePrivate::useXP(bool update) +{ + if (!update) + return use_xp; + return (use_xp = resolveSymbols() && pIsThemeActive() + && (pIsAppThemed() || !QApplication::instance())); +} + +/* \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); +} + +/* \internal + Cleans up all static data. +*/ +void QWindowsXPStylePrivate::cleanup(bool force) +{ + if(bufferBitmap) { + if (bufferDC && nullBitmap) + SelectObject(bufferDC, nullBitmap); + DeleteObject(bufferBitmap); + bufferBitmap = 0; + } + + if(bufferDC) + DeleteDC(bufferDC); + bufferDC = 0; + + if (ref.deref() && !force) + return; + if (!force) // -1 based atomic refcounting + ref.deref(); + + use_xp = false; + cleanupHandleMap(); + if (limboWidget) { + if (QApplication::closingDown()) + delete limboWidget; + else + limboWidget->deleteLater(); + } + delete tabbody; + limboWidget = 0; + tabbody = 0; +} + +/* \internal + Closes all open theme data handles to ensure that we don't leak + resources, and that we don't refere to old handles when for + example the user changes the theme style. +*/ +void QWindowsXPStylePrivate::cleanupHandleMap() +{ + if (!handleMap) + return; + + QMap<QString, HTHEME>::Iterator it; + for (it = handleMap->begin(); it != handleMap->end(); ++it) + pCloseThemeData(it.value()); + delete handleMap; + handleMap = 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 QWindowsXPStylePrivate::winId(const QWidget *widget) +{ + if (widget && widget->internalWinId()) + return widget->internalWinId(); + + if (!limboWidget) { + limboWidget = new QWidget(0); + limboWidget->createWinId(); + limboWidget->setObjectName(QLatin1String("xp_limbo_widget")); + // We don't need this internal widget to appear in QApplication::topLevelWidgets() + if (QWidgetPrivate::allWidgets) + QWidgetPrivate::allWidgets->remove(limboWidget); + } + + return limboWidget->winId(); +} + +/*! \internal + Returns the pointer to a tab widgets body pixmap, scaled to the + height of the screen. This way the theme engine doesn't need to + scale the body for every time we ask for it. (Speed optimization) +*/ +const QPixmap *QWindowsXPStylePrivate::tabBody(QWidget *) +{ + if (!tabbody) { + SIZE sz; + XPThemeData theme(0, 0, QLatin1String("TAB"), TABP_BODY); + pGetThemePartSize(theme.handle(), qt_win_display_dc(), TABP_BODY, 0, 0, TS_TRUE, &sz); + + tabbody = new QPixmap(sz.cx, QApplication::desktop()->screenGeometry().height()); + QPainter painter(tabbody); + theme.rect = QRect(0, 0, sz.cx, sz.cy); + drawBackground(theme); + // We fill with the last line of the themedata, that + // way we don't get a tiled pixmap inside big tabs + QPixmap temp(sz.cx, 1); + painter.drawPixmap(0, 0, temp, 0, sz.cy-1, -1, -1); + painter.drawTiledPixmap(0, sz.cy, sz.cx, tabbody->height()-sz.cy, temp); + } + return tabbody; +} + +/*! \internal + Returns true if all the necessary theme engine symbols were + resolved. +*/ +bool QWindowsXPStylePrivate::resolveSymbols() +{ + static bool tried = false; + if (!tried) { + tried = true; + QSystemLibrary themeLib(QLatin1String("uxtheme")); + pIsAppThemed = (PtrIsAppThemed)themeLib.resolve("IsAppThemed"); + if (pIsAppThemed) { + pIsThemeActive = (PtrIsThemeActive )themeLib.resolve("IsThemeActive"); + pGetThemePartSize = (PtrGetThemePartSize )themeLib.resolve("GetThemePartSize"); + pOpenThemeData = (PtrOpenThemeData )themeLib.resolve("OpenThemeData"); + pCloseThemeData = (PtrCloseThemeData )themeLib.resolve("CloseThemeData"); + pDrawThemeBackground = (PtrDrawThemeBackground )themeLib.resolve("DrawThemeBackground"); + pDrawThemeBackgroundEx = (PtrDrawThemeBackgroundEx )themeLib.resolve("DrawThemeBackgroundEx"); + pGetCurrentThemeName = (PtrGetCurrentThemeName )themeLib.resolve("GetCurrentThemeName"); + pGetThemeBool = (PtrGetThemeBool )themeLib.resolve("GetThemeBool"); + pGetThemeColor = (PtrGetThemeColor )themeLib.resolve("GetThemeColor"); + pGetThemeEnumValue = (PtrGetThemeEnumValue )themeLib.resolve("GetThemeEnumValue"); + pGetThemeFilename = (PtrGetThemeFilename )themeLib.resolve("GetThemeFilename"); + pGetThemeFont = (PtrGetThemeFont )themeLib.resolve("GetThemeFont"); + pGetThemeInt = (PtrGetThemeInt )themeLib.resolve("GetThemeInt"); + pGetThemeIntList = (PtrGetThemeIntList )themeLib.resolve("GetThemeIntList"); + pGetThemeMargins = (PtrGetThemeMargins )themeLib.resolve("GetThemeMargins"); + pGetThemeMetric = (PtrGetThemeMetric )themeLib.resolve("GetThemeMetric"); + pGetThemePartSize = (PtrGetThemePartSize )themeLib.resolve("GetThemePartSize"); + pGetThemePosition = (PtrGetThemePosition )themeLib.resolve("GetThemePosition"); + pGetThemePropertyOrigin = (PtrGetThemePropertyOrigin)themeLib.resolve("GetThemePropertyOrigin"); + pGetThemeRect = (PtrGetThemeRect )themeLib.resolve("GetThemeRect"); + pGetThemeString = (PtrGetThemeString )themeLib.resolve("GetThemeString"); + pGetThemeBackgroundRegion = (PtrGetThemeBackgroundRegion )themeLib.resolve("GetThemeBackgroundRegion"); + pGetThemeDocumentationProperty = (PtrGetThemeDocumentationProperty )themeLib.resolve("GetThemeDocumentationProperty"); + pIsThemeBackgroundPartiallyTransparent = (PtrIsThemeBackgroundPartiallyTransparent)themeLib.resolve("IsThemeBackgroundPartiallyTransparent"); + } + } + + return pIsAppThemed != 0; +} + +/*! \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 = 0; + } + + w = qMax(bufferW, w); + h = qMax(bufferH, h); + + if (!bufferDC) + bufferDC = CreateCompatibleDC(qt_win_display_dc()); + + // 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 = 0; + bufferBitmap = CreateDIBSection(bufferDC, &bmi, DIB_RGB_COLORS, (void **) &bufferPixels, 0, 0); + GdiFlush(); + nullBitmap = (HBITMAP)SelectObject(bufferDC, bufferBitmap); + + if (!bufferBitmap) { + qErrnoWarning("QWindowsXPStylePrivate::buffer(w,h), failed to create dibsection"); + bufferW = 0; + bufferH = 0; + return 0; + } + if (!bufferPixels) { + qErrnoWarning("QWindowsXPStylePrivate::buffer(w,h), did not allocate pixel data"); + bufferW = 0; + bufferH = 0; + return 0; + } + bufferW = w; + bufferH = h; +#ifdef DEBUG_XP_STYLE + qDebug("Creating new dib section (%d, %d)", w, h); +#endif + return bufferBitmap; +} + +/*! \internal + Returns 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 pIsThemeBackgroundPartiallyTransparent(themeData.handle(), themeData.partId, + themeData.stateId); +} + + +/*! \internal + Returns a QRegion of the region of the part +*/ +QRegion QWindowsXPStylePrivate::region(XPThemeData &themeData) +{ + HRGN hRgn = 0; + RECT rect = themeData.toRECT(themeData.rect); + if (!SUCCEEDED(pGetThemeBackgroundRegion(themeData.handle(), bufferHDC(), themeData.partId, + themeData.stateId, &rect, &hRgn))) + return QRegion(); + + HRGN dest = CreateRectRgn(0, 0, 0, 0); + const bool success = CombineRgn(dest, hRgn, 0, RGN_COPY) != ERROR; + + QRegion region; + + if (success) + region = qt_region_from_HRGN(dest); + + DeleteObject(hRgn); + DeleteObject(dest); + + return region; +} + +/*! \internal + Sets the parts region on a window. +*/ +void QWindowsXPStylePrivate::setTransparency(QWidget *widget, XPThemeData &themeData) +{ + HRGN hrgn = themeData.mask(); + if (hrgn && widget) + SetWindowRgn(winId(widget), hrgn, true); +} + +/*! \internal + Returns true if the native doublebuffer contains a pixel which + has a non-0xFF alpha value. Should only be use when its + guaranteed that data painted into the buffer wasn't a proper + alpha pixmap. +*/ +bool QWindowsXPStylePrivate::hasAnyData(const QRect &rect) +{ + const int startX = rect.left(); + const int startY = rect.top(); + const int w = rect.width(); + const int h = rect.height(); + + for (int y = startY; y < h; ++y) { + register DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (int x = startX; x < w; ++x, ++buffer) { + int alpha = (*buffer) >> 24; + if (alpha != 0xFF) // buffer has been touched + return true; + } + } + return false; +} + +/*! \internal + Returns 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) { + register DWORD *buffer = (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 occures. +*/ +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) { + register DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (register 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 determin 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) { + register DWORD *buffer = (DWORD*)bufferPixels + (y * bufferW); + for (register int x = startX; x < w; ++x, ++buffer) { + if (allPixels) { + *buffer |= 0xFF000000; + continue; + } + register 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(). +*/ +void QWindowsXPStylePrivate::drawBackground(XPThemeData &themeData) +{ + if (themeData.rect.isEmpty()) + return; + + QPainter *painter = themeData.painter; + Q_ASSERT_X(painter != 0, "QWindowsXPStylePrivate::drawBackground()", "Trying to draw a theme part without a painter"); + if (!painter || !painter->isActive()) + return; + + painter->save(); + + bool complexXForm = painter->deviceTransform().type() > QTransform::TxTranslate; + + bool translucentToplevel = false; + QPaintDevice *pdev = painter->device(); + if (pdev->devType() == QInternal::Widget) { + QWidget *win = ((QWidget *) pdev)->window(); + translucentToplevel = win->testAttribute(Qt::WA_TranslucentBackground); + } + + bool useFallback = painter->paintEngine()->getDC() == 0 + || painter->opacity() != 1.0 + || themeData.rotate + || complexXForm + || themeData.mirrorVertically + || (themeData.mirrorHorizontally && pDrawThemeBackgroundEx == 0) + || translucentToplevel; + + if (!useFallback) + drawBackgroundDirectly(themeData); + else + drawBackgroundThruNativeBuffer(themeData); + + painter->restore(); +} + +/*! \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. +*/ +void QWindowsXPStylePrivate::drawBackgroundDirectly(XPThemeData &themeData) +{ + QPainter *painter = themeData.painter; + HDC dc = painter->paintEngine()->getDC(); + + QPoint redirectionDelta(int(painter->deviceMatrix().dx()), + int(painter->deviceMatrix().dy())); + QRect area = themeData.rect.translated(redirectionDelta); + + QRegion sysRgn = painter->paintEngine()->systemClip(); + if (sysRgn.isEmpty()) + sysRgn = area; + else + sysRgn &= area; + if (painter->hasClipping()) + sysRgn &= painter->clipRegion().translated(redirectionDelta); + SelectClipRgn(dc, sysRgn.handle()); + +#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; + 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); + + if (pDrawThemeBackgroundEx != 0) { + pDrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &drawOptions); + } else { + // 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. All flips and mirrors uses the + // fallback implementation + + int borderSize = 0; + PROPERTYORIGIN origin = PO_NOTFOUND; + pGetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin); + pGetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize); + + // Clip away border region + QRegion extraClip = sysRgn; + if ((origin == PO_CLASS || origin == PO_PART || origin == PO_STATE) && borderSize > 0) { + if (themeData.noBorder) { + // extraClip &= area is already done + drawRECT = themeData.toRECT(area.adjusted(-borderSize, -borderSize, borderSize, borderSize)); + } + + // Clip away content region + if (themeData.noContent) { + QRegion content = area.adjusted(borderSize, borderSize, -borderSize, -borderSize); + extraClip ^= content; + } + + // Set the clip region, if used.. + if (themeData.noBorder || themeData.noContent) + SelectClipRgn(dc, extraClip.handle()); + } + + pDrawThemeBackground(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &(drawOptions.rcClip)); + } + SelectClipRgn(dc, 0); +} + +/*! \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 (horizonal mirroring only, vertical are handled by the theme + engine). +*/ +void QWindowsXPStylePrivate::drawBackgroundThruNativeBuffer(XPThemeData &themeData) +{ + QPainter *painter = themeData.painter; + QRect rect = themeData.rect; + + if ((themeData.rotate + 90) % 180 == 0) { // Catch 90,270,etc.. degree flips. + rect = QRect(0, 0, rect.height(), rect.width()); + } + rect.moveTo(0,0); + 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 inspectData; + bool potentialInvalidAlpha; + + QString pixmapCacheKey = QString::fromLatin1("$qt_xp_%1p%2s%3s%4b%5c%6w%7h").arg(themeData.name) + .arg(partId).arg(stateId).arg(!themeData.noBorder).arg(!themeData.noContent) + .arg(w).arg(h); + QPixmap cachedPixmap; + ThemeMapKey key(themeData); + ThemeMapData data = alphaCache.value(key); + + bool haveCachedPixmap = false; + bool isCached = data.dataValid; + if (isCached) { + if (!(stateHasData = data.hasAnyData)) + return; // Cached NOOP + inspectData = data.wasAlphaSwapped; + 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; + pGetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERONLY, &tmt_borderonly); + pGetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, TMT_TRANSPARENTCOLOR, &tmt_transparentcolor); + pGetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_CAPTIONMARGINS, &proporigin); + inspectData = (tmt_transparentcolor != 0 || tmt_borderonly || proporigin == PO_PART || proporigin == PO_STATE); + + // ### This is a vista-specific workaround for broken alpha in titlebar pixmaps + if ((QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA && QSysInfo::WindowsVersion < QSysInfo::WV_NT_based)) { + if (themeData.partId == WP_CAPTION || themeData.partId == WP_SMALLCAPTION) + inspectData = false; + } + + partIsTransparent = isTransparent(themeData); + + potentialInvalidAlpha = false; + pGetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &proporigin); + if (proporigin == PO_PART || proporigin == PO_STATE) { + int tmt_glyphtype = GT_NONE; + pGetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &tmt_glyphtype); + potentialInvalidAlpha = partIsTransparent && !inspectData && 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("-->inspectData = %d\n", inspectData); + 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 = rect; + 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; + pGetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin); + pGetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize); + + // 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! ------------------------- + buffer(w, h); // Ensure a buffer of at least (w, h) in size + HDC dc = bufferHDC(); + + // Clear the buffer + if (alphaType != NoAlpha) { + // Consider have separate "memset" function for small chunks for more speedup + memset(bufferPixels, inspectData ? 0xFF : 0x00, bufferW * h * 4); + } + + // Difference between area and rect + int dx = area.x() - rect.x(); + int dy = area.y() - rect.y(); + int dr = area.right() - rect.right(); + int db = area.bottom() - rect.bottom(); + + // 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 + if (pDrawThemeBackgroundEx != 0) { + RECT rect(themeData.toRECT(area)); + pDrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &rect, &drawOptions); + } else { + // Set the clip region, if used.. + if (addBorderContentClipping) { + SelectClipRgn(dc, extraClip.handle()); + // Compensate for the noBorder area difference (noContent has the same area) + drawOptions.rcClip = themeData.toRECT(rect.adjusted(dx, dy, dr, db)); + } + + pDrawThemeBackground(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawOptions.rcClip), 0); + + if (addBorderContentClipping) + SelectClipRgn(dc, 0); + } + + // If not cached, analyze the buffer data to figure + // out alpha type, and if it contains data + if (!isCached) { + if (inspectData) + stateHasData = hasAnyData(rect); + // SHORTCUT: If the part's state has no data, cache it for NOOP later + if (!stateHasData) { + memset(&data, 0, sizeof(data)); + data.dataValid = true; + alphaCache.insert(key, data); + return; + } + hasAlpha = hasAlphaChannel(rect); + if (!hasAlpha && partIsTransparent) + potentialInvalidAlpha = true; +#if defined(DEBUG_XP_STYLE) && 1 + dumpNativeDIB(w, h); +#endif + } + + // Swap alpha values, if needed + if (inspectData) + wasAlphaSwapped = swapAlphaChannel(rect); + + // Fix alpha values, if needed + if (potentialInvalidAlpha) + wasAlphaFixed = fixAlphaChannel(rect); + + 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); + } + + // 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"); + QVector<QRect> rects = newRegion.rects(); + for (int i = 0; i < rects.count(); ++i) { + const QRect &r = rects.at(i); + printf(" (%d, %d, %d, %d)\n", r.x(), r.y(), r.right(), r.bottom()); + } +#endif + } + + if (addBorderContentClipping) + painter->setClipRegion(extraClip, 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) { + QMatrix 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(&data, 0, sizeof(data)); + data.dataValid = true; + data.partIsTransparent = partIsTransparent; + data.alphaType = alphaType; + data.hasAlphaChannel = hasAlpha; + data.hasAnyData = stateHasData; + data.wasAlphaSwapped = wasAlphaSwapped; + data.hadInvalidAlpha = wasAlphaFixed; + alphaCache.insert(key, data); + } +} + + +// ------------------------------------------------------------------------------------------------ + +/*! + \class QWindowsXPStyle + \brief The QWindowsXPStyle class provides a Microsoft Windows XP-like look and feel. + + \ingroup appearance + + \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. + + \img qwindowsxpstyle.png + \sa QMacStyle, QWindowsStyle, QPlastiqueStyle, QCDEStyle, QMotifStyle +*/ + +/*! + Constructs a QWindowsStyle +*/ +QWindowsXPStyle::QWindowsXPStyle() + : QWindowsStyle(*new QWindowsXPStylePrivate) +{ +} + +/*! + Destroys the style. +*/ +QWindowsXPStyle::~QWindowsXPStyle() +{ +} + +/*! \reimp */ +void QWindowsXPStyle::unpolish(QApplication *app) +{ + QWindowsStyle::unpolish(app); +} + +/*! \reimp */ +void QWindowsXPStyle::polish(QApplication *app) +{ + QWindowsStyle::polish(app); + if (!QWindowsXPStylePrivate::useXP()) + return; +} + +/*! \reimp */ +void QWindowsXPStyle::polish(QWidget *widget) +{ + QWindowsStyle::polish(widget); + if (!QWindowsXPStylePrivate::useXP()) + return; + + if (qobject_cast<QAbstractButton*>(widget) + || qobject_cast<QToolButton*>(widget) + || qobject_cast<QTabBar*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox*>(widget) +#endif // QT_NO_COMBOBOX + || qobject_cast<QScrollBar*>(widget) + || qobject_cast<QSlider*>(widget) + || qobject_cast<QHeaderView*>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox*>(widget) + || qobject_cast<QSpinBox*>(widget) +#endif // QT_NO_SPINBOX + || widget->inherits("QWorkspaceChild") + || widget->inherits("Q3TitleBar")) + widget->setAttribute(Qt::WA_Hover); + +#ifndef QT_NO_RUBBERBAND + if (qobject_cast<QRubberBand*>(widget)) { + widget->setWindowOpacity(0.6); + } +#endif + if (qobject_cast<QStackedWidget*>(widget) && + qobject_cast<QTabWidget*>(widget->parent())) + widget->parentWidget()->setAttribute(Qt::WA_ContentsPropagated); + + Q_D(QWindowsXPStyle); + if (!d->hasInitColors) { + // Get text color for group box labels + COLORREF cref; + XPThemeData theme(0, 0, QLatin1String("BUTTON"), 0, 0); + pGetThemeColor(theme.handle(), BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColor = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + pGetThemeColor(theme.handle(), BP_GROUPBOX, GBS_DISABLED, TMT_TEXTCOLOR, &cref); + d->groupBoxTextColorDisabled = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref)); + // Where does this color come from? + //pGetThemeColor(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) +{ +#ifndef QT_NO_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 (qobject_cast<QAbstractButton*>(widget) + || qobject_cast<QToolButton*>(widget) + || qobject_cast<QTabBar*>(widget) +#ifndef QT_NO_COMBOBOX + || qobject_cast<QComboBox*>(widget) +#endif // QT_NO_COMBOBOX + || qobject_cast<QScrollBar*>(widget) + || qobject_cast<QSlider*>(widget) + || qobject_cast<QHeaderView*>(widget) +#ifndef QT_NO_SPINBOX + || qobject_cast<QAbstractSpinBox*>(widget) + || qobject_cast<QSpinBox*>(widget) +#endif // QT_NO_SPINBOX + || widget->inherits("QWorkspaceChild") + || widget->inherits("Q3TitleBar")) + 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) + rect.adjust(0, 0, -2, -2); + } + break; + case SE_TabWidgetTabBar: { + rect = QWindowsStyle::subElementRect(sr, option, widget); + const QStyleOptionTabWidgetFrame *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 QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) { + MARGINS borderSize; + if (widget) { + XPThemeData buttontheme(widget, 0, QLatin1String("Button")); + 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); + + int result = pGetThemeMargins(theme, + NULL, + BP_PUSHBUTTON, + stateId, + TMT_CONTENTMARGINS, + NULL, + &borderSize); + + if (result == S_OK) { + 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 +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + + QString name; + 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 QStyleOptionTabBarBase *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: + name = QLatin1String("BUTTON"); + 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; + } + name = QLatin1String("TOOLBAR"); + 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: + name = QLatin1String("TOOLBAR"); + 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: + name = QLatin1String("BUTTON"); + 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: + name = QLatin1String("BUTTON"); + 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; + name = QLatin1String("LISTVIEW"); + partId = LVP_LISTGROUP; + XPThemeData theme(0, 0, name, partId, 0); + + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else + stateId = ETS_NORMAL; + int fillType; + if (pGetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &fillType) == S_OK) { + if (fillType == BT_BORDERFILL) { + COLORREF bcRef; + pGetThemeColor(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &bcRef); + QColor bordercolor(qRgb(GetRValue(bcRef), GetGValue(bcRef), GetBValue(bcRef))); + QPen oldPen = p->pen(); + // int borderSize = 1; + // pGetThemeInt(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &borderSize); + + // 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(bordercolor, 1)); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->setPen(oldPen); + return; + } else if (fillType == BT_NONE) { + return; + } else { + break; + } + } + } + case PE_FrameLineEdit: { + // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class. + QWidget *parentWidget = 0; + if (widget) + parentWidget = widget->parentWidget(); + if (parentWidget) + parentWidget = parentWidget->parentWidget(); + if (widget && widget->inherits("QLineEdit") + && parentWidget && parentWidget->inherits("QAbstractItemView")) { + 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; + } else if (qstyleoption_cast<const QStyleOptionFrame *>(option)) { + name = QLatin1String("EDIT"); + partId = EP_EDITTEXT; + noContent = true; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else + stateId = ETS_NORMAL; + } + break; + } + + case PE_PanelLineEdit: + if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + name = QLatin1String("EDIT"); + partId = EP_EDITTEXT; + noBorder = true; + QBrush bg; + bool usePalette = false; + bool isEnabled = flags & State_Enabled; + uint resolve_mask = panel->palette.resolve(); + +#ifndef QT_NO_SPINBOX + //Since spin box includes a line edit we need to resolve the palette on the spin box instead + if (widget) { + if (QAbstractSpinBox *spinbox = qobject_cast<QAbstractSpinBox*>(widget->parentWidget())) + resolve_mask = spinbox->palette().resolve(); + } +#endif // QT_NO_SPINBOX + if (resolve_mask & (1 << QPalette::Base)) { + // Base color is set for this widget, so use it + bg = panel->palette.brush(QPalette::Base); + usePalette = true; + } + + stateId = isEnabled ? ETS_NORMAL : ETS_DISABLED; + + if (usePalette) { + p->fillRect(panel->rect, bg); + } else { + XPThemeData theme(0, p, name, partId, stateId, rect); + if (!theme.isValid()) { + QWindowsStyle::drawPrimitive(pe, option, p, widget); + return; + } + int bgType; + pGetThemeEnumValue( theme.handle(), + partId, + stateId, + TMT_BGTYPE, + &bgType); + if( bgType == BT_IMAGEFILE ) { + theme.mirrorHorizontally = hMirrored; + theme.mirrorVertically = vMirrored; + theme.noBorder = noBorder; + theme.noContent = noContent; + theme.rotate = rotate; + d->drawBackground(theme); + } else { + QBrush fillColor = option->palette.brush(QPalette::Base); + + if (!isEnabled) { + PROPERTYORIGIN origin = PO_NOTFOUND; + pGetThemePropertyOrigin(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; + pGetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef); + fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef))); + } + } + p->fillRect(option->rect, fillColor); + } + } + + if (panel->lineWidth > 0) + proxy()->drawPrimitive(PE_FrameLineEdit, panel, p, widget); + return; + } + break; + + case PE_FrameTabWidget: + if (const QStyleOptionTabWidgetFrame *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) + { + name = QLatin1String("TAB"); + 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 (pGetCurrentThemeName(themeFileName, maxlength, themeColor, maxlength, NULL, 0) == S_OK) { + wchar_t *offset = 0; + if ((offset = wcsrchr(themeFileName, QChar(QLatin1Char('\\')).unicode())) != NULL) { + 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) { + QStyleOptionTabWidgetFrameV2 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, name, 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 QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) + { + name = QLatin1String("WINDOW"); + if (flags & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = proxy()->pixelMetric(PM_DockWidgetFrameWidth, frm, widget); + + XPThemeData theme(widget, p, name, 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 = QLatin1String("HEADER"); + partId = HP_HEADERSORTARROW; + if (flags & State_Down) + stateId = HSAS_SORTEDDOWN; + else + stateId = HSAS_SORTEDUP; +#else + if (const QStyleOptionHeader *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: + name = QLatin1String("STATUS"); + partId = SP_PANE; + break; + + case PE_FrameGroupBox: + name = QLatin1String("BUTTON"); + partId = BP_GROUPBOX; + if (!(flags & State_Enabled)) + stateId = GBS_DISABLED; + else + stateId = GBS_NORMAL; + if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) { + const QStyleOptionFrameV2 *frame2 = qstyleoption_cast<const QStyleOptionFrameV2 *>(option); + if (frame2->features & QStyleOptionFrameV2::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); + name = QLatin1String(""); + } + } + break; + + case PE_IndicatorProgressChunk: + { + Qt::Orientation orient = Qt::Horizontal; + bool inverted = false; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) { + orient = pb2->orientation; + if (pb2->invertedAppearance) + inverted = true; + } + if (orient == Qt::Horizontal) { + partId = PP_CHUNK; + rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.height() ); + if (inverted && option->direction == Qt::LeftToRight) + hMirrored = true; + } else { + partId = PP_CHUNKVERT; + rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.height()); + } + name = QLatin1String("PROGRESS"); + stateId = 1; + } + break; + + case PE_Q3DockWindowSeparator: + name = QLatin1String("TOOLBAR"); + if (flags & State_Horizontal) + partId = TP_SEPARATOR; + else + partId = TP_SEPARATORVERT; + break; + + case PE_FrameWindow: + if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) + { + name = QLatin1String("WINDOW"); + if (flags & State_Active) + stateId = FS_ACTIVE; + else + stateId = FS_INACTIVE; + + int fwidth = frm->lineWidth + frm->midLineWidth; + + XPThemeData theme(0, p, name, 0, stateId); + if (!theme.isValid()) + break; + + theme.rect = QRect(option->rect.x(), option->rect.y()+fwidth, option->rect.x()+fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMELEFT; + d->drawBackground(theme); + theme.rect = QRect(option->rect.width()-fwidth, option->rect.y()+fwidth, fwidth, option->rect.height()-fwidth); + theme.partId = WP_FRAMERIGHT; + d->drawBackground(theme); + theme.rect = QRect(option->rect.x(), option->rect.height()-fwidth, option->rect.width(), fwidth); + theme.partId = WP_FRAMEBOTTOM; + d->drawBackground(theme); + theme.rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.y()+fwidth); + theme.partId = WP_CAPTION; + d->drawBackground(theme); + return; + } + break; + + case PE_IndicatorBranch: + { + static const int decoration_size = 9; + 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; + QBrush brush(option->palette.dark().color(), Qt::Dense4Pattern); + if (option->state & State_Item) { + if (option->direction == Qt::RightToLeft) + p->fillRect(option->rect.left(), mid_v, bef_h - option->rect.left(), 1, brush); + else + p->fillRect(aft_h, mid_v, option->rect.right() - aft_h + 1, 1, brush); + } + if (option->state & State_Sibling) + p->fillRect(mid_h, aft_v, 1, option->rect.bottom() - aft_v + 1, brush); + if (option->state & (State_Open | State_Children | State_Item | State_Sibling)) + p->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush); + if (option->state & State_Children) { + int delta = decoration_size / 2; + bef_h -= delta; + bef_v -= delta; + aft_h += delta; + aft_v += delta; + XPThemeData theme(0, p, QLatin1String("TREEVIEW")); + theme.rect = QRect(bef_h, bef_v, decoration_size, decoration_size); + theme.partId = TVP_GLYPH; + theme.stateId = flags & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED; + d->drawBackground(theme); + } + } + return; + + 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; + } + name = QLatin1String("TOOLBAR"); + partId = TP_SEPARATOR; + + if (option->state & State_Horizontal) + partId = TP_SEPARATOR; + else + partId = TP_SEPARATORVERT; + + break; + + case PE_IndicatorToolBarHandle: + + name = QLatin1String("REBAR"); + 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(0, p, name, 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 +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawControl(element, option, p, widget); + return; + } + + QRect rect(option->rect); + State flags = option->state; + + int rotate = 0; + bool hMirrored = false; + bool vMirrored = false; + + QString name; + int partId = 0; + int stateId = 0; + switch (element) { + case CE_SizeGrip: + { + name = QLatin1String("STATUS"); + partId = SP_GRIPPER; + SIZE sz; + XPThemeData theme(0, p, name, partId, 0); + pGetThemePartSize(theme.handle(), 0, partId, 0, 0, TS_TRUE, &sz); + --sz.cy; + if (const QStyleOptionSizeGrip *sg = qstyleoption_cast<const QStyleOptionSizeGrip *>(option)) { + switch (sg->corner) { + case Qt::BottomRightCorner: + rect = QRect(rect.right() - sz.cx, rect.bottom() - sz.cy, sz.cx, sz.cy); + break; + case Qt::BottomLeftCorner: + rect = QRect(rect.left() + 1, rect.bottom() - sz.cy, sz.cx, sz.cy); + hMirrored = true; + break; + case Qt::TopRightCorner: + rect = QRect(rect.right() - sz.cx, rect.top() + 1, sz.cx, sz.cy); + vMirrored = true; + break; + case Qt::TopLeftCorner: + rect = QRect(rect.left() + 1, rect.top() + 1, sz.cx, sz.cy); + hMirrored = vMirrored = true; + } + } + } + break; + + case CE_HeaderSection: + name = QLatin1String("HEADER"); + partId = HP_HEADERITEM; + if (flags & State_Sunken) + stateId = HIS_PRESSED; + else if (flags & State_MouseOver) + stateId = HIS_HOT; + else + stateId = HIS_NORMAL; + break; + + case CE_Splitter: + p->eraseRect(option->rect); + return; + + case CE_PushButtonBevel: + if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) + { + name = QLatin1String("BUTTON"); + partId = BP_PUSHBUTTON; + bool justFlat = ((btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken))) + || ((btn->features & QStyleOptionButton::CommandLinkButton) + && !(flags & State_MouseOver) + && !(btn->features & QStyleOptionButton::DefaultButton)); + 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) + stateId = PBS_DEFAULTED; + else + stateId = PBS_NORMAL; + + if (!justFlat) { + XPThemeData theme(widget, p, name, partId, stateId, rect); + d->drawBackground(theme); + } + + if (btn->features & QStyleOptionButton::HasMenu) { + int mbiw = 0, mbih = 0; + XPThemeData theme(widget, 0, QLatin1String("TOOLBAR"), TP_SPLITBUTTONDROPDOWN); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + mbiw = size.cx; + mbih = size.cy; + } + + QRect ir = btn->rect; + QStyleOptionButton newBtn = *btn; + newBtn.rect = QRect(ir.right() - mbiw - 1, 1 + (ir.height()/2) - (mbih/2), mbiw, mbih); + proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget); + } + return; + } + break; + case CE_TabBarTab: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) + { + stateId = tab->state & State_Enabled ? TIS_NORMAL : TIS_DISABLED; + } + break; + + case CE_TabBarTabShape: + if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) + { + name = QLatin1String("TAB"); + bool isDisabled = !(tab->state & State_Enabled); + bool hasFocus = tab->state & State_HasFocus; + bool isHot = tab->state & State_MouseOver; + bool selected = tab->state & State_Selected; + bool lastTab = tab->position == QStyleOptionTab::End; + bool firstTab = tab->position == QStyleOptionTab::Beginning; + bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab; + bool leftAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignLeft; + bool centerAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignCenter; + int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget); + 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; + } + bool begin = firstTab || onlyOne; + 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: + name = QLatin1String(""); // 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 QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) + orient = pb2->orientation; + partId = (orient == Qt::Horizontal) ? PP_BAR : PP_BARVERT; + name = QLatin1String("PROGRESS"); + stateId = 1; + } + break; + + case CE_MenuEmptyArea: + case CE_MenuItem: + if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) + { + int tab = menuitem->tabWidth; + bool dis = !(menuitem->state & State_Enabled); + bool act = menuitem->state & State_Selected; + bool checkable = menuitem->menuHasCheckableItems; + bool checked = checkable ? menuitem->checked : false; + + // windows always has a check column, regardless whether we have an icon or not + 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); + int pixw = pixmap.width(); + int pixh = pixmap.height(); + QRect iconRect(0, 0, pixw, pixh); + 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 QStyleOptionMenuItem *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; +#ifndef QT_NO_DOCKWIDGET + case CE_DockWidgetTitle: + if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + int buttonMargin = 4; + int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget); + int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget); + bool isFloating = widget && widget->isWindow(); + bool isActive = dwOpt->state & State_Active; + + const QStyleOptionDockWidgetV2 *v2 + = qstyleoption_cast<const QStyleOptionDockWidgetV2*>(dwOpt); + bool verticalTitleBar = v2 == 0 ? false : v2->verticalTitleBar; + + if (verticalTitleBar) { + QSize s = rect.size(); + s.transpose(); + rect.setSize(s); + + 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 != 0 && 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 { + name = QLatin1String("WINDOW"); + 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, name, 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; + pGetThemeEnumValue(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + pGetThemeColor(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_NO_DOCKWIDGET +#ifndef QT_NO_RUBBERBAND + case CE_RubberBand: + if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) { + QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight); + p->save(); + QRect r = option->rect; + 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->isTopLevel())? 255 : 127); + p->setBrush(dimHighlight); + p->drawRect(option->rect.adjusted(0, 0, -1, -1)); + p->restore(); + return; + } +#endif // QT_NO_RUBBERBAND + case CE_HeaderEmptyArea: + if (option->state & State_Horizontal) + { + name = QLatin1String("HEADER"); + stateId = HIS_NORMAL; + } + else { + QWindowsStyle::drawControl(CE_HeaderEmptyArea, option, p, widget); + return; + } + break; + default: + break; + } + + XPThemeData theme(widget, p, name, 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); +} + + +/*! + \reimp +*/ +void QWindowsXPStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, + QPainter *p, const QWidget *widget) const +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + + if (!QWindowsXPStylePrivate::useXP()) { + QWindowsStyle::drawComplexControl(cc, option, p, widget); + return; + } + + State flags = option->state; + SubControls sub = option->subControls; + QRect r = option->rect; + + int partId = 0; + int stateId = 0; + if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow()) + flags |= State_MouseOver; + + switch (cc) { +#ifndef QT_NO_SPINBOX + case CC_SpinBox: + if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) + { + XPThemeData theme(widget, p, QLatin1String("SPIN")); + + if (sb->frame && (sub & SC_SpinBoxFrame)) { + partId = EP_EDITTEXT; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + + XPThemeData ftheme(widget, p, QLatin1String("EDIT"), partId, stateId, r); + ftheme.noContent = true; + d->drawBackground(ftheme); + } + if (sub & SC_SpinBoxUp) { + theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget); + 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_NO_SPINBOX +#ifndef QT_NO_COMBOBOX + case CC_ComboBox: + if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) + { + if (sub & SC_ComboBoxEditField) { + if (cmb->frame) { + partId = EP_EDITTEXT; + if (!(flags & State_Enabled)) + stateId = ETS_DISABLED; + else if (flags & State_HasFocus) + stateId = ETS_FOCUSED; + else + stateId = ETS_NORMAL; + XPThemeData theme(widget, p, QLatin1String("EDIT"), partId, stateId, r); + d->drawBackground(theme); + } else { + QBrush editBrush = cmb->palette.brush(QPalette::Base); + p->fillRect(option->rect, editBrush); + } + if (!cmb->editable) { + QRect re = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget); + if (option->state & State_HasFocus) { + p->fillRect(re, option->palette.highlight()); + p->setPen(option->palette.highlightedText().color()); + p->setBackground(option->palette.highlight()); + } else { + p->fillRect(re, option->palette.base()); + p->setPen(option->palette.text().color()); + p->setBackground(option->palette.base()); + } + } + } + + if (sub & SC_ComboBoxArrow) { + XPThemeData theme(widget, p, QLatin1String("COMBOBOX")); + theme.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget); + partId = CP_DROPDOWNBUTTON; + if (!(flags & State_Enabled)) + stateId = CBXS_DISABLED; + else if (cmb->activeSubControls == SC_ComboBoxArrow && (cmb->state & State_Sunken)) + stateId = CBXS_PRESSED; + else if (cmb->activeSubControls == SC_ComboBoxArrow && (cmb->state & State_MouseOver)) + stateId = CBXS_HOT; + else + stateId = CBXS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + break; +#endif // QT_NO_COMBOBOX + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, p, QLatin1String("SCROLLBAR")); + 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 + 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 + 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 = scrollbar->orientation == Qt::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 + stateId = SCRBS_NORMAL; + + // Draw handle + theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget); + theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + theme.stateId = stateId; + d->drawBackground(theme); + + // Calculate rect of gripper + const int swidth = theme.rect.width(); + const int sheight = theme.rect.height(); + + MARGINS contentsMargin; + RECT rect = theme.toRECT(theme.rect); + pGetThemeMargins(theme.handle(), 0, theme.partId, theme.stateId, TMT_SIZINGMARGINS, &rect, &contentsMargin); + + SIZE size; + theme.partId = flags & State_Horizontal ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + int gw = size.cx, gh = size.cy; + + + QRect gripperBounds; + if (flags & State_Horizontal && ((swidth - contentsMargin.cxLeftWidth - contentsMargin.cxRightWidth) > gw)) { + gripperBounds.setLeft(theme.rect.left() + swidth/2 - gw/2); + gripperBounds.setTop(theme.rect.top() + sheight/2 - gh/2); + gripperBounds.setWidth(gw); + gripperBounds.setHeight(gh); + } else if ((sheight - contentsMargin.cyTopHeight - contentsMargin.cyBottomHeight) > gh) { + gripperBounds.setLeft(theme.rect.left() + swidth/2 - gw/2); + gripperBounds.setTop(theme.rect.top() + sheight/2 - gh/2); + gripperBounds.setWidth(gw); + gripperBounds.setHeight(gh); + } + + // Draw gripper if there is enough space + if (!gripperBounds.isEmpty()) { + p->save(); + theme.rect = gripperBounds; + p->setClipRegion(d->region(theme));// Only change inside the region of the gripper + d->drawBackground(theme); // Transparent gripper ontop of background + p->restore(); + } + } + } + } + break; + +#ifndef QT_NO_SLIDER + case CC_Slider: + if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) + { + XPThemeData theme(widget, p, QLatin1String("TRACKBAR")); + 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.size() > 0) { + 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 +#ifndef QT_NO_TOOLBUTTON + case CC_ToolButton: + if (const QStyleOptionToolButton *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(0); + tool.palette = toolbutton->palette; + if (toolbutton->subControls & SC_ToolButton) { + if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) { + if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup && autoRaise) { + XPThemeData theme(widget, p, QLatin1String("TOOLBAR")); + 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, 0); + 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_NO_TOOLBUTTON + + case CC_TitleBar: + { + if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) + { + bool isActive = tb->titleBarState & QStyle::State_Active; + XPThemeData theme(widget, p, QLatin1String("WINDOW")); + if (sub & SC_TitleBarLabel) { + +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + partId = WP_SMALLCAPTION; + } else +#endif + 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; + pGetThemeEnumValue(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result); + if (result != TST_NONE) { + COLORREF textShadowRef; + pGetThemeColor(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(ir.x() + 3, ir.y() + 2, ir.width() - 1, 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(ir.x() + 2, ir.y() + 1, ir.width() - 2, 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; + SIZE sz; + pGetThemePartSize(theme.handle(), qt_win_display_dc(), theme.partId, theme.stateId, 0, TS_TRUE, &sz); + if (sz.cx == 0 || sz.cy == 0) { + 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)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarMinButton, widget); + partId = WP_MINBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarMinButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarMinButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarMaxButton && tb->titleBarFlags & Qt::WindowMaximizeButtonHint + && !(tb->titleBarState & Qt::WindowMaximized)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarMaxButton, widget); + partId = WP_MAXBUTTON; + if (widget && !widget->isEnabled()) + stateId = MAXBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarMaxButton && (option->state & State_Sunken)) + stateId = MAXBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarMaxButton && (option->state & State_MouseOver)) + stateId = MAXBS_HOT; + else if (!isActive) + stateId = MAXBS_INACTIVE; + else + stateId = MAXBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarContextHelpButton + && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarContextHelpButton, widget); + partId = WP_HELPBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarContextHelpButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarContextHelpButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + 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) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarNormalButton, widget); + partId = WP_RESTOREBUTTON; + if (widget && !widget->isEnabled()) + stateId = RBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarNormalButton && (option->state & State_Sunken)) + stateId = RBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarNormalButton && (option->state & State_MouseOver)) + stateId = RBS_HOT; + else if (!isActive) + stateId = RBS_INACTIVE; + else + stateId = RBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarShadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && !(tb->titleBarState & Qt::WindowMinimized)) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarShadeButton, widget); + partId = WP_MINBUTTON; + if (widget && !widget->isEnabled()) + stateId = MINBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarShadeButton && (option->state & State_Sunken)) + stateId = MINBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarShadeButton && (option->state & State_MouseOver)) + stateId = MINBS_HOT; + else if (!isActive) + stateId = MINBS_INACTIVE; + else + stateId = MINBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarUnshadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint + && tb->titleBarState & Qt::WindowMinimized) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarUnshadeButton, widget); + partId = WP_RESTOREBUTTON; + if (widget && !widget->isEnabled()) + stateId = RBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarUnshadeButton && (option->state & State_Sunken)) + stateId = RBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarUnshadeButton && (option->state & State_MouseOver)) + stateId = RBS_HOT; + else if (!isActive) + stateId = RBS_INACTIVE; + else + stateId = RBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + if (sub & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) { + theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarCloseButton, widget); + //partId = titlebar->testWFlags(Qt::WA_WState_Tool) ? WP_SMALLCLOSEBUTTON : WP_CLOSEBUTTON; + partId = WP_CLOSEBUTTON; + if (widget && !widget->isEnabled()) + stateId = CBS_DISABLED; + else if (option->activeSubControls == SC_TitleBarCloseButton && (option->state & State_Sunken)) + stateId = CBS_PUSHED; + else if (option->activeSubControls == SC_TitleBarCloseButton && (option->state & State_MouseOver)) + stateId = CBS_HOT; + else if (!isActive) + stateId = CBS_INACTIVE; + else + stateId = CBS_NORMAL; + theme.partId = partId; + theme.stateId = stateId; + d->drawBackground(theme); + } + } + } + break; + +#ifndef QT_NO_WORKSPACE + case CC_MdiControls: + { + QRect buttonRect; + XPThemeData theme(widget, p, QLatin1String("WINDOW"), WP_MDICLOSEBUTTON, CBS_NORMAL); + + if (option->subControls & SC_MdiCloseButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiCloseButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDICLOSEBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiCloseButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiCloseButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + if (option->subControls & SC_MdiNormalButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiNormalButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDIRESTOREBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiNormalButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiNormalButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + if (option->subControls & QStyle::SC_MdiMinButton) { + buttonRect = proxy()->subControlRect(CC_MdiControls, option, SC_MdiMinButton, widget); + if (theme.isValid()) { + theme.partId = WP_MDIMINBUTTON; + theme.rect = buttonRect; + if (!(flags & State_Enabled)) + theme.stateId = CBS_INACTIVE; + else if (flags & State_Sunken && (option->activeSubControls & SC_MdiMinButton)) + theme.stateId = CBS_PUSHED; + else if (flags & State_MouseOver && (option->activeSubControls & SC_MdiMinButton)) + theme.stateId = CBS_HOT; + else + theme.stateId = CBS_NORMAL; + d->drawBackground(theme); + } + } + } + break; +#endif //QT_NO_WORKSPACE +#ifndef QT_NO_DIAL + case CC_Dial: + if (const QStyleOptionSlider *dial = qstyleoption_cast<const QStyleOptionSlider *>(option)) + QStyleHelper::drawDial(dial, p); + break; +#endif // QT_NO_DIAL + default: + QWindowsStyle::drawComplexControl(cc, option, p, widget); + break; + } +} + +/*! \reimp */ +int QWindowsXPStyle::pixelMetric(PixelMetric pm, const QStyleOption *option, const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::pixelMetric(pm, option, widget); + + int res = 0; + switch (pm) { + case PM_MenuBarPanelWidth: + res = 0; + break; + + case PM_DefaultFrameWidth: + if (qobject_cast<const QListView*>(widget)) + res = 2; + else + res = 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 QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) { + switch (tab->shape) { + case QTabBar::RoundedNorth: + case QTabBar::TriangularNorth: + res = 1; + break; + case QTabBar::RoundedSouth: + case QTabBar::TriangularSouth: + res = 2; + break; + case QTabBar::RoundedEast: + case QTabBar::TriangularEast: + res = 3; + break; + case QTabBar::RoundedWest: + case QTabBar::TriangularWest: + res = 1; + break; + } + } + break; + + case PM_SplitterWidth: + res = qMax(int(QStyleHelper::dpiScaled(5.)), QApplication::globalStrut().width()); + break; + + case PM_IndicatorWidth: + case PM_IndicatorHeight: + { + XPThemeData theme(widget, 0, QLatin1String("BUTTON"), BP_CHECKBOX, CBS_UNCHECKEDNORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + res = (pm == PM_IndicatorWidth) ? size.cx : size.cy; + } + } + break; + + case PM_ExclusiveIndicatorWidth: + case PM_ExclusiveIndicatorHeight: + { + XPThemeData theme(widget, 0, QLatin1String("BUTTON"), BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + res = (pm == PM_ExclusiveIndicatorWidth) ? size.cx : size.cy; + } + } + break; + + case PM_ProgressBarChunkWidth: + { + Qt::Orientation orient = Qt::Horizontal; + if (const QStyleOptionProgressBarV2 *pb2 = qstyleoption_cast<const QStyleOptionProgressBarV2 *>(option)) + orient = pb2->orientation; + XPThemeData theme(widget, 0, QLatin1String("PROGRESS"), (orient == Qt::Horizontal) ? PP_CHUNK : PP_CHUNKVERT); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + res = (orient == Qt::Horizontal) ? size.cx : size.cy; + } + } + break; + + case PM_SliderThickness: + { + XPThemeData theme(widget, 0, QLatin1String("TRACKBAR"), TKP_THUMB); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + res = size.cy; + } + } + break; + + case PM_TitleBarHeight: + { +#ifdef QT3_SUPPORT + if (widget && widget->inherits("Q3DockWindowTitleBar")) { + res = GetSystemMetrics(SM_CYSMCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME); + } else +#endif + if (widget && (widget->windowType() == Qt::Tool)) + res = GetSystemMetrics(SM_CYSMCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME); + else + res = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME); + } + break; + + case PM_MdiSubWindowFrameWidth: + { + XPThemeData theme(widget, 0, QLatin1String("WINDOW"), WP_FRAMELEFT, FS_ACTIVE); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, WP_FRAMELEFT, FS_ACTIVE, 0, TS_TRUE, &size); + res = size.cx-1; + } + } + break; + + case PM_MdiSubWindowMinimizedWidth: + res = 160; + break; + +#ifndef QT_NO_TOOLBAR + case PM_ToolBarHandleExtent: + res = int(QStyleHelper::dpiScaled(8.)); + break; + +#endif // QT_NO_TOOLBAR + case PM_DockWidgetFrameWidth: + { + XPThemeData theme(widget, 0, QLatin1String("WINDOW"), WP_SMALLFRAMERIGHT, FS_ACTIVE); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + res = size.cx; + } + } + break; + case PM_DockWidgetSeparatorExtent: + res = int(QStyleHelper::dpiScaled(4.)); + break; + case PM_DockWidgetTitleMargin: + res = int(QStyleHelper::dpiScaled(4.)); + break; + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + if (qstyleoption_cast<const QStyleOptionToolButton *>(option)) + res = 1; + else + res = 0; + break; + + case PM_ButtonDefaultIndicator: + res = 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 QStyleOptionTitleBar *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(); + int buttonHeight = GetSystemMetrics(SM_CYSIZE) - 4; + int buttonWidth = GetSystemMetrics(SM_CXSIZE) - 4; + 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; + //fall through + case SC_TitleBarMinButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMinButton) + break; + //fall through + 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; + //fall through + case SC_TitleBarMaxButton: + if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarMaxButton) + break; + //fall through + case SC_TitleBarShadeButton: + if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarShadeButton) + break; + //fall through + case SC_TitleBarUnshadeButton: + if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint)) + offset += delta; + else if (subControl == SC_TitleBarUnshadeButton) + break; + //fall through + 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); + 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 QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + int x = cmb->rect.x(), y = cmb->rect.y(), wi = cmb->rect.width(), he = cmb->rect.height(); + int xpos = x; + xpos += wi - 1 - 16; + + switch (subControl) { + case SC_ComboBoxFrame: + rect = cmb->rect; + break; + + case SC_ComboBoxArrow: + rect = QRect(xpos, y+1, 16, he-2); + break; + + case SC_ComboBoxEditField: + rect = QRect(x+2, y+2, wi-3-16, he-4); + break; + + case SC_ComboBoxListBoxPopup: + rect = cmb->rect; + break; + + default: + break; + } + } + break; +#ifndef QT_NO_WORKSPACE + 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; + //FALL THROUGH + 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_NO_WORKSPACE + + 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, 0, QLatin1String("Button")); + HTHEME theme = buttontheme.handle(); + MARGINS borderSize; + if (theme) { + int result = pGetThemeMargins(theme, + NULL, + BP_PUSHBUTTON, + PBS_NORMAL, + TMT_CONTENTMARGINS, + NULL, + &borderSize); + if (result == S_OK) { + sz += QSize(borderSize.cxLeftWidth + borderSize.cxRightWidth - 2, + borderSize.cyBottomHeight + borderSize.cyTopHeight - 2); + } + const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin) + 1); + sz += QSize(qMax(pixelMetric(QStyle::PM_ScrollBarExtent, option, widget) + + textMargins, 23), 0); //arrow button + } + } + break; + case CT_SpinBox: + { + //Spinbox adds frame twice + sz = QWindowsStyle::sizeFromContents(ct, option, contentsSize, widget); + int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget); + sz -= QSize(2*border, 2*border); + } + break; + case CT_TabWidget: + sz += QSize(6, 6); + break; + case CT_Menu: + sz += QSize(1, 0); + break; +#ifndef QT_NO_MENUBAR + case CT_MenuBarItem: + if (!sz.isEmpty()) + sz += QSize(windowsItemHMargin * 5 + 1, 6); + break; +#endif + case CT_MenuItem: + if (const QStyleOptionMenuItem *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: + if (const QStyleOptionComplex *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(option)) { + int 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 = QSize(width, 19); + } else { + sz = QSize(54, 19); + } + 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 +{ + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + if (!QWindowsXPStylePrivate::useXP()) + return QWindowsStyle::styleHint(hint, option, widget, returnData); + + 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 && widget->isEnabled())) + res = d->groupBoxTextColor; + else + res = d->groupBoxTextColorDisabled; + break; + + case SH_Table_GridLineColor: + res = 0xC0C0C0; + break; + + case SH_WindowFrame_Mask: + { + res = 1; + QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData); + const QStyleOptionTitleBar *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, 0, QLatin1String("WINDOW"), WP_MINCAPTION, CS_ACTIVE, titleBarRect); + } else + themeData = XPThemeData(widget, 0, QLatin1String("WINDOW"), WP_CAPTION, CS_ACTIVE, titleBarRect); + mask->region = d->region(themeData) + + QRect(0, tbHeight, option->rect.width(), option->rect.height() - tbHeight); + } + } + break; +#ifndef QT_NO_RUBBERBAND + case SH_RubberBand_Mask: + if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) { + res = 0; + break; + } +#endif // QT_NO_RUBBERBAND + + case SH_ItemView_DrawDelegateFrame: + res = 1; + break; + + default: + res =QWindowsStyle::styleHint(hint, option, widget, returnData); + } + + return res; +} + +/*! \reimp */ +QPalette QWindowsXPStyle::standardPalette() const +{ + if (QWindowsXPStylePrivate::useXP() && QApplicationPrivate::sys_pal) + return *QApplicationPrivate::sys_pal; + else + return 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, 0, QLatin1String("WINDOW"), WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + SIZE sz; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &sz); + return QIcon(QWindowsStyle::standardPixmap(standardPixmap, option, widget)).pixmap(QSize(sz.cx, sz.cy)); + } + } + } + break; + default: + break; + } + return QWindowsStyle::standardPixmap(standardPixmap, option, widget); +} + +/*! + \internal +*/ +QIcon QWindowsXPStyle::standardIconImplementation(StandardPixmap standardIcon, + const QStyleOption *option, + const QWidget *widget) const +{ + if (!QWindowsXPStylePrivate::useXP()) { + return QWindowsStyle::standardIconImplementation(standardIcon, option, widget); + } + + QWindowsXPStylePrivate *d = const_cast<QWindowsXPStylePrivate*>(d_func()); + switch(standardIcon) { + case SP_TitleBarMaxButton: + if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) + { + if (d->dockFloat.isNull()) { + XPThemeData themeSize(0, 0, QLatin1String("WINDOW"), WP_SMALLCLOSEBUTTON, CBS_NORMAL); + XPThemeData theme(0, 0, QLatin1String("WINDOW"), WP_MAXBUTTON, MAXBS_NORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(themeSize.handle(), 0, themeSize.partId, themeSize.stateId, 0, TS_TRUE, &size); + QPixmap pm = QPixmap(size.cx, size.cy); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(0, 0, size.cx, size.cy); + 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(0, 0, QLatin1String("WINDOW"), WP_SMALLCLOSEBUTTON, CBS_NORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(theme.handle(), 0, theme.partId, theme.stateId, 0, TS_TRUE, &size); + QPixmap pm = QPixmap(size.cx, size.cy); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.partId = WP_CLOSEBUTTON; // #### + theme.rect = QRect(0, 0, size.cx, size.cy); + 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(0, 0, QLatin1String("WINDOW"), WP_SMALLCLOSEBUTTON, CBS_NORMAL); + XPThemeData theme(0, 0, QLatin1String("WINDOW"), WP_RESTOREBUTTON, RBS_NORMAL); + if (theme.isValid()) { + SIZE size; + pGetThemePartSize(themeSize.handle(), 0, themeSize.partId, themeSize.stateId, 0, TS_TRUE, &size); + QPixmap pm = QPixmap(size.cx, size.cy); + pm.fill(Qt::transparent); + QPainter p(&pm); + theme.painter = &p; + theme.rect = QRect(0, 0, size.cx, size.cy); + 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::standardIconImplementation(standardIcon, option, widget); +} + +/*! + \internal + + Constructs a QWindowsXPStyle object. +*/ +QWindowsXPStyle::QWindowsXPStyle(QWindowsXPStylePrivate &dd) : QWindowsStyle(dd) +{ +} + + +// 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 determin 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; + pGetThemePropertyOrigin(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]; + pGetThemeString(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; + pGetThemeEnumValue(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; + pGetThemeInt(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; + pGetThemeBool(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; + pGetThemeColor(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)); + pGetThemeMargins(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]; + pGetThemeFilename(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)); + pGetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_MIN, &result1); + pGetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_TRUE, &result2); + pGetThemePartSize(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)); + pGetThemePosition(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)); + pGetThemeRect(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)); + pGetThemeFont(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)); + pGetThemeIntList(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); + } + } + } + qSort(all_props); + + {// 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]; + pGetThemeDocumentationProperty(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; + pGetThemePropertyOrigin(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; + pGetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin); + if (origin != PO_NOTFOUND) + { + showProperty(themeData, prop); + } + } +} +#endif +// Debugging code -----------------------------------------------------------------------[ END ]--- + + +QT_END_NAMESPACE + +#endif //QT_NO_WINDOWSXP diff --git a/src/gui/styles/qwindowsxpstyle.h b/src/gui/styles/qwindowsxpstyle.h new file mode 100644 index 0000000000..08582d13a1 --- /dev/null +++ b/src/gui/styles/qwindowsxpstyle.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSXPSTYLE_H +#define QWINDOWSXPSTYLE_H + +#include <QtGui/qwindowsstyle.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_STYLE_WINDOWSXP) + +class QWindowsXPStylePrivate; +class Q_GUI_EXPORT QWindowsXPStyle : public QWindowsStyle +{ + Q_OBJECT +public: + QWindowsXPStyle(); + QWindowsXPStyle(QWindowsXPStylePrivate &dd); + ~QWindowsXPStyle(); + + void unpolish(QApplication*); + void polish(QApplication*); + void polish(QWidget*); + void polish(QPalette&); + void unpolish(QWidget*); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p, + const QWidget *widget = 0) const; + void drawControl(ControlElement element, const QStyleOption *option, QPainter *p, + const QWidget *wwidget = 0) const; + QRect subElementRect(SubElement r, const QStyleOption *option, const QWidget *widget = 0) const; + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, + const QWidget *widget = 0) const; + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p, + const QWidget *widget = 0) const; + QSize sizeFromContents(ContentsType ct, const QStyleOption *option, const QSize &contentsSize, + const QWidget *widget = 0) const; + int pixelMetric(PixelMetric pm, const QStyleOption *option = 0, + const QWidget *widget = 0) const; + int styleHint(StyleHint hint, const QStyleOption *option = 0, const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const; + + QPalette standardPalette() const; + QPixmap standardPixmap(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + +protected Q_SLOTS: + QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, + const QWidget *widget = 0) const; + +private: + Q_DISABLE_COPY(QWindowsXPStyle) + Q_DECLARE_PRIVATE(QWindowsXPStyle) + friend class QStyleFactory; + void *reserved; +}; + +#endif // QT_NO_STYLE_WINDOWSXP + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QWINDOWSXPSTYLE_H diff --git a/src/gui/styles/qwindowsxpstyle_p.h b/src/gui/styles/qwindowsxpstyle_p.h new file mode 100644 index 0000000000..5509cba512 --- /dev/null +++ b/src/gui/styles/qwindowsxpstyle_p.h @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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 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 "qwindowsxpstyle.h" +#include "qwindowsstyle_p.h" +#include <qmap.h> +#include <qt_windows.h> + +// Note, these tests are duplicated in qwizard_win.cpp. +#ifdef Q_CC_GNU +# include <w32api.h> +# if (__W32API_MAJOR_VERSION >= 3 || (__W32API_MAJOR_VERSION == 2 && __W32API_MINOR_VERSION >= 5)) +# ifdef _WIN32_WINNT +# undef _WIN32_WINNT +# endif +# define _WIN32_WINNT 0x0501 +# include <commctrl.h> +# endif +#endif + +#include <uxtheme.h> + +#if WINVER >= 0x0600 +#include <vssym32.h> +#else +#include <tmschema.h> +#endif + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +// Older Platform SDKs do not have the extended DrawThemeBackgroundEx +// function. We add the needed parts here, and use the extended +// function dynamically, if available in uxtheme.dll. Else, we revert +// back to using the DrawThemeBackground function. +#ifndef DTBG_OMITBORDER +# ifndef DTBG_CLIPRECT +# define DTBG_CLIPRECT 0x00000001 +# endif +# ifndef DTBG_DRAWSOLID +# define DTBG_DRAWSOLID 0x00000002 +# endif +# ifndef DTBG_OMITBORDER +# define DTBG_OMITBORDER 0x00000004 +# endif +# ifndef DTBG_OMITCONTENT +# define DTBG_OMITCONTENT 0x00000008 +# endif +# ifndef DTBG_COMPUTINGREGION +# define DTBG_COMPUTINGREGION 0x00000010 +# endif +# ifndef DTBG_MIRRORDC +# define DTBG_MIRRORDC 0x00000020 +# endif + typedef struct _DTBGOPTS + { + DWORD dwSize; + DWORD dwFlags; + RECT rcClip; + } DTBGOPTS, *PDTBGOPTS; +#endif // _DTBGOPTS + +// Undefined for some compile environments +#ifndef TMT_TEXTCOLOR +# define TMT_TEXTCOLOR 3803 +#endif +#ifndef TMT_BORDERCOLORHINT +# define TMT_BORDERCOLORHINT 3822 +#endif +#ifndef TMT_BORDERSIZE +# define TMT_BORDERSIZE 2403 +#endif +#ifndef TMT_BORDERONLY +# define TMT_BORDERONLY 2203 +#endif +#ifndef TMT_TRANSPARENTCOLOR +# define TMT_TRANSPARENTCOLOR 3809 +#endif +#ifndef TMT_CAPTIONMARGINS +# define TMT_CAPTIONMARGINS 3603 +#endif +#ifndef TMT_CONTENTMARGINS +# define TMT_CONTENTMARGINS 3602 +#endif +#ifndef TMT_SIZINGMARGINS +# define TMT_SIZINGMARGINS 3601 +#endif +#ifndef TMT_GLYPHTYPE +# define TMT_GLYPHTYPE 4012 +#endif +#ifndef TMT_BGTYPE +# define TMT_BGTYPE 4001 +#endif +#ifndef TMT_TEXTSHADOWTYPE +# define TMT_TEXTSHADOWTYPE 4010 +#endif +#ifndef TMT_BORDERCOLOR +# define TMT_BORDERCOLOR 3801 +#endif +#ifndef BT_IMAGEFILE +# define BT_IMAGEFILE 0 +#endif +#ifndef BT_BORDERFILL +# define BT_BORDERFILL 1 +#endif +#ifndef BT_NONE +# define BT_NONE 2 +#endif +#ifndef TMT_FILLCOLOR +# define TMT_FILLCOLOR 3802 +#endif +#ifndef TMT_PROGRESSCHUNKSIZE +# define TMT_PROGRESSCHUNKSIZE 2411 +#endif + +// 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 + +#ifndef GT_NONE +# define GT_NONE 0 +#endif +#ifndef GT_IMAGEGLYPH +# define GT_IMAGEGLYPH 1 +#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 + +#if !defined(QT_NO_STYLE_WINDOWSXP) + +// Declarations ----------------------------------------------------------------------------------- +class XPThemeData +{ +public: + XPThemeData(const QWidget *w = 0, QPainter *p = 0, const QString &theme = QString(), + int part = 0, int state = 0, const QRect &r = QRect()) + : widget(w), painter(p), name(theme), htheme(0), partId(part), stateId(state), + mirrorHorizontally(false), mirrorVertically(false), noBorder(false), + noContent(false), rotate(0), rect(r) + {} + + HRGN mask(); + HTHEME handle(); + + RECT toRECT(const QRect &qr); + bool isValid(); + + const QWidget *widget; + QPainter *painter; + QString name; + HTHEME htheme; + int partId; + int stateId; + + uint mirrorHorizontally : 1; + uint mirrorVertically : 1; + uint noBorder : 1; + uint noContent : 1; + uint rotate; + QRect rect; +}; + +struct ThemeMapKey { + QString name; + int partId; + int stateId; + bool noBorder; + bool noContent; + + ThemeMapKey() : partId(-1), stateId(-1) {} + ThemeMapKey(const XPThemeData &data) + : name(data.name), partId(data.partId), stateId(data.stateId), + noBorder(data.noBorder), noContent(data.noContent) {} + +}; + +inline uint qHash(const ThemeMapKey &key) +{ return qHash(key.name) ^ key.partId ^ key.stateId; } + +inline bool operator==(const ThemeMapKey &k1, const ThemeMapKey &k2) +{ + return k1.name == k2.name + && 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; // Which type of alpha on part & state + + bool dataValid : 1; // Only used to detect if hash value is ok + bool partIsTransparent : 1; + bool hasAnyData : 1; // False = part & state has not data, NOP + 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), hasAnyData(false), + hasAlphaChannel(false), wasAlphaSwapped(false), hadInvalidAlpha(false) {} +}; + +class QWindowsXPStylePrivate : public QWindowsStylePrivate +{ + Q_DECLARE_PUBLIC(QWindowsXPStyle) +public: + QWindowsXPStylePrivate() + : QWindowsStylePrivate(), hasInitColors(false), bufferDC(0), bufferBitmap(0), nullBitmap(0), + bufferPixels(0), bufferW(0), bufferH(0) + { init(); } + + ~QWindowsXPStylePrivate() + { cleanup(); } + + static HWND winId(const QWidget *widget); + + void init(bool force = false); + void cleanup(bool force = false); + void cleanupHandleMap(); + const QPixmap *tabBody(QWidget *widget); + + HBITMAP buffer(int w = 0, int h = 0); + HDC bufferHDC() + { return bufferDC;} + + static bool resolveSymbols(); + static bool useXP(bool update = false); + + bool isTransparent(XPThemeData &themeData); + QRegion region(XPThemeData &themeData); + + void setTransparency(QWidget *widget, XPThemeData &themeData); + void drawBackground(XPThemeData &themeData); + void drawBackgroundThruNativeBuffer(XPThemeData &themeData); + void drawBackgroundDirectly(XPThemeData &themeData); + + bool hasAnyData(const QRect &rect); + bool hasAlphaChannel(const QRect &rect); + bool fixAlphaChannel(const QRect &rect); + bool swapAlphaChannel(const QRect &rect, bool allPixels = false); + + QRgb groupBoxTextColor; + QRgb groupBoxTextColorDisabled; + QRgb sliderTickColor; + bool hasInitColors; + + static QMap<QString,HTHEME> *handleMap; + + QIcon dockFloat, dockClose; + +private: +#ifdef DEBUG_XP_STYLE + void dumpNativeDIB(int w, int h); + void showProperties(XPThemeData &themeData); +#endif + + static QBasicAtomicInt ref; + static bool use_xp; + static QWidget *limboWidget; + static QPixmap *tabbody; + + QHash<ThemeMapKey, ThemeMapData> alphaCache; + HDC bufferDC; + HBITMAP bufferBitmap; + HBITMAP nullBitmap; + uchar *bufferPixels; + int bufferW, bufferH; +}; + +#endif // QT_NO_STYLE_WINDOWS + +QT_END_NAMESPACE + +#endif //QWINDOWSXPSTYLE_P_H diff --git a/src/gui/styles/styles.pri b/src/gui/styles/styles.pri new file mode 100644 index 0000000000..b6eeec9af8 --- /dev/null +++ b/src/gui/styles/styles.pri @@ -0,0 +1,190 @@ +# Qt styles module + +HEADERS += \ + styles/qstyle.h \ + styles/qstylefactory.h \ + styles/qstyleoption.h \ + styles/qstyleplugin.h \ + styles/qcommonstylepixmaps_p.h \ + styles/qcommonstyle.h \ + styles/qstylehelper_p.h \ + styles/qproxystyle.h \ + styles/qproxystyle_p.h \ + styles/qstylesheetstyle_p.h + +SOURCES += \ + styles/qstyle.cpp \ + styles/qstylefactory.cpp \ + styles/qstyleoption.cpp \ + styles/qstyleplugin.cpp \ + styles/qstylehelper.cpp \ + styles/qcommonstyle.cpp \ + styles/qproxystyle.cpp \ + styles/qstylesheetstyle.cpp \ + styles/qstylesheetstyle_default.cpp + +wince* { + RESOURCES += styles/qstyle_wince.qrc +} else:symbian { + RESOURCES += styles/qstyle_s60.qrc +} else { + RESOURCES += styles/qstyle.qrc +} + +contains( styles, all ) { + styles = mac windows windowsxp windowsvista +} + +x11|embedded|qpa|!macx-*:styles -= mac + +x11{ + QMAKE_CXXFLAGS += $$QT_CFLAGS_QGTKSTYLE + LIBS_PRIVATE += $$QT_LIBS_QGTKSTYLE + styles += gtk +} + +contains( styles, mac ) { + HEADERS += \ + styles/qmacstyle_mac.h \ + styles/qmacstylepixmaps_mac_p.h \ + styles/qmacstyle_mac_p.h + OBJECTIVE_SOURCES += styles/qmacstyle_mac.mm + + !contains( styles, windows ) { + message( mac requires windows ) + styles += windows + DEFINES+= QT_STYLE_WINDOWS + } +} else { + DEFINES += QT_NO_STYLE_MAC +} + +contains( styles, cde ) { + HEADERS += styles/qcdestyle.h + SOURCES += styles/qcdestyle.cpp + + !contains( styles, motif ) { + message( cde requires motif ) + styles += motif + DEFINES+= QT_STYLE_MOTIF + } +} else { + DEFINES += QT_NO_STYLE_CDE +} + +contains( styles, windowsvista ) { + HEADERS += styles/qwindowsvistastyle.h + HEADERS += styles/qwindowsvistastyle_p.h + SOURCES += styles/qwindowsvistastyle.cpp + !contains( styles, windowsxp ) { + message( windowsvista requires windowsxp ) + styles += windowsxp + DEFINES+= QT_STYLE_WINDOWSXP + } +} else { + DEFINES += QT_NO_STYLE_WINDOWSVISTA +} + +contains( styles, windowsxp ) { + HEADERS += styles/qwindowsxpstyle.h + SOURCES += styles/qwindowsxpstyle.cpp + !contains( styles, windows ) { + message( windowsxp requires windows ) + styles += windows + DEFINES+= QT_STYLE_WINDOWS + } +} else { + DEFINES += QT_NO_STYLE_WINDOWSXP +} + +contains( styles, plastique ) { + HEADERS += styles/qplastiquestyle.h + SOURCES += styles/qplastiquestyle.cpp + !contains( styles, windows ) { + message( plastique requires windows ) + styles += windows + DEFINES+= QT_STYLE_WINDOWS + } +} else { + DEFINES += QT_NO_STYLE_PLASTIQUE +} + +contains( styles, gtk ) { + HEADERS += styles/qgtkstyle.h + HEADERS += styles/qgtkpainter_p.h + HEADERS += styles/qgtkstyle_p.h + SOURCES += styles/qgtkstyle.cpp + SOURCES += styles/qgtkpainter.cpp + SOURCES += styles/qgtkstyle_p.cpp + !contains( styles, cleanlooks ) { + styles += cleanlooks + DEFINES+= QT_STYLE_CLEANLOOKS + } +} else { + DEFINES += QT_NO_STYLE_GTK +} + +contains( styles, cleanlooks ) { + HEADERS += styles/qcleanlooksstyle.h + HEADERS += styles/qcleanlooksstyle_p.h + SOURCES += styles/qcleanlooksstyle.cpp + !contains( styles, windows ) { + styles += windows + DEFINES+= QT_STYLE_WINDOWS + } +} else { + DEFINES += QT_NO_STYLE_CLEANLOOKS +} + +contains( styles, windows ) { + HEADERS += styles/qwindowsstyle.h + SOURCES += styles/qwindowsstyle.cpp +} else { + DEFINES += QT_NO_STYLE_WINDOWS +} + +contains( styles, motif ) { + HEADERS += styles/qmotifstyle.h + SOURCES += styles/qmotifstyle.cpp +} else { + DEFINES += QT_NO_STYLE_MOTIF +} + +contains( styles, windowsce ) { + HEADERS += styles/qwindowscestyle.h + SOURCES += styles/qwindowscestyle.cpp +} else { + DEFINES += QT_NO_STYLE_WINDOWSCE +} + +contains( styles, windowsmobile ) { + HEADERS += styles/qwindowsmobilestyle.h + SOURCES += styles/qwindowsmobilestyle.cpp +} else { + DEFINES += QT_NO_STYLE_WINDOWSMOBILE +} + +contains( styles, s60 ):contains(QT_CONFIG, s60) { + HEADERS += \ + styles/qs60style.h \ + styles/qs60style_p.h + SOURCES += styles/qs60style.cpp + symbian { + SOURCES += styles/qs60style_s60.cpp + LIBS += -legul -lbmpanim + contains(CONFIG, is_using_gnupoc) { + LIBS += -laknicon -laknskins -laknskinsrv -lfontutils + } else { + LIBS += -lAknIcon -lAKNSKINS -lAKNSKINSRV -lFontUtils + } + } else { + SOURCES += styles/qs60style_simulated.cpp + RESOURCES += styles/qstyle_s60_simulated.qrc + } +} else { + symbian { + HEADERS += styles/qs60style.h + SOURCES += styles/qs60style_stub.cpp + } + DEFINES += QT_NO_STYLE_S60 +} diff --git a/src/gui/symbian/images/blank.png b/src/gui/symbian/images/blank.png new file mode 100644 index 0000000000..bd396de62e Binary files /dev/null and b/src/gui/symbian/images/blank.png differ diff --git a/src/gui/symbian/images/busy12.png b/src/gui/symbian/images/busy12.png new file mode 100644 index 0000000000..909e70fa4f Binary files /dev/null and b/src/gui/symbian/images/busy12.png differ diff --git a/src/gui/symbian/images/busy3.png b/src/gui/symbian/images/busy3.png new file mode 100644 index 0000000000..983f5d8b8f Binary files /dev/null and b/src/gui/symbian/images/busy3.png differ diff --git a/src/gui/symbian/images/busy6.png b/src/gui/symbian/images/busy6.png new file mode 100644 index 0000000000..b2e878074f Binary files /dev/null and b/src/gui/symbian/images/busy6.png differ diff --git a/src/gui/symbian/images/busy9.png b/src/gui/symbian/images/busy9.png new file mode 100644 index 0000000000..e093d015c5 Binary files /dev/null and b/src/gui/symbian/images/busy9.png differ diff --git a/src/gui/symbian/images/closehand.png b/src/gui/symbian/images/closehand.png new file mode 100644 index 0000000000..05534f580d Binary files /dev/null and b/src/gui/symbian/images/closehand.png differ diff --git a/src/gui/symbian/images/cross.png b/src/gui/symbian/images/cross.png new file mode 100644 index 0000000000..50da7aafc3 Binary files /dev/null and b/src/gui/symbian/images/cross.png differ diff --git a/src/gui/symbian/images/forbidden.png b/src/gui/symbian/images/forbidden.png new file mode 100644 index 0000000000..a3a0fd61e1 Binary files /dev/null and b/src/gui/symbian/images/forbidden.png differ diff --git a/src/gui/symbian/images/handpoint.png b/src/gui/symbian/images/handpoint.png new file mode 100644 index 0000000000..a221548d22 Binary files /dev/null and b/src/gui/symbian/images/handpoint.png differ diff --git a/src/gui/symbian/images/ibeam.png b/src/gui/symbian/images/ibeam.png new file mode 100644 index 0000000000..ace2fada1c Binary files /dev/null and b/src/gui/symbian/images/ibeam.png differ diff --git a/src/gui/symbian/images/openhand.png b/src/gui/symbian/images/openhand.png new file mode 100644 index 0000000000..6f232f0d8f Binary files /dev/null and b/src/gui/symbian/images/openhand.png differ diff --git a/src/gui/symbian/images/pointer.png b/src/gui/symbian/images/pointer.png new file mode 100644 index 0000000000..677404e250 Binary files /dev/null and b/src/gui/symbian/images/pointer.png differ diff --git a/src/gui/symbian/images/sizeall.png b/src/gui/symbian/images/sizeall.png new file mode 100644 index 0000000000..29500677eb Binary files /dev/null and b/src/gui/symbian/images/sizeall.png differ diff --git a/src/gui/symbian/images/sizebdiag.png b/src/gui/symbian/images/sizebdiag.png new file mode 100644 index 0000000000..f565a3a7a1 Binary files /dev/null and b/src/gui/symbian/images/sizebdiag.png differ diff --git a/src/gui/symbian/images/sizefdiag.png b/src/gui/symbian/images/sizefdiag.png new file mode 100644 index 0000000000..9493f12723 Binary files /dev/null and b/src/gui/symbian/images/sizefdiag.png differ diff --git a/src/gui/symbian/images/sizehor.png b/src/gui/symbian/images/sizehor.png new file mode 100644 index 0000000000..217bf39fe2 Binary files /dev/null and b/src/gui/symbian/images/sizehor.png differ diff --git a/src/gui/symbian/images/sizever.png b/src/gui/symbian/images/sizever.png new file mode 100644 index 0000000000..2c99038c3f Binary files /dev/null and b/src/gui/symbian/images/sizever.png differ diff --git a/src/gui/symbian/images/splith.png b/src/gui/symbian/images/splith.png new file mode 100644 index 0000000000..343bed529a Binary files /dev/null and b/src/gui/symbian/images/splith.png differ diff --git a/src/gui/symbian/images/splitv.png b/src/gui/symbian/images/splitv.png new file mode 100644 index 0000000000..69ee4163e8 Binary files /dev/null and b/src/gui/symbian/images/splitv.png differ diff --git a/src/gui/symbian/images/uparrow.png b/src/gui/symbian/images/uparrow.png new file mode 100644 index 0000000000..92dd933856 Binary files /dev/null and b/src/gui/symbian/images/uparrow.png differ diff --git a/src/gui/symbian/images/wait1.png b/src/gui/symbian/images/wait1.png new file mode 100644 index 0000000000..5aebaab1f9 Binary files /dev/null and b/src/gui/symbian/images/wait1.png differ diff --git a/src/gui/symbian/images/wait10.png b/src/gui/symbian/images/wait10.png new file mode 100644 index 0000000000..3b549b08ed Binary files /dev/null and b/src/gui/symbian/images/wait10.png differ diff --git a/src/gui/symbian/images/wait11.png b/src/gui/symbian/images/wait11.png new file mode 100644 index 0000000000..24a943fa68 Binary files /dev/null and b/src/gui/symbian/images/wait11.png differ diff --git a/src/gui/symbian/images/wait12.png b/src/gui/symbian/images/wait12.png new file mode 100644 index 0000000000..15afd4daa2 Binary files /dev/null and b/src/gui/symbian/images/wait12.png differ diff --git a/src/gui/symbian/images/wait2.png b/src/gui/symbian/images/wait2.png new file mode 100644 index 0000000000..f2022b2853 Binary files /dev/null and b/src/gui/symbian/images/wait2.png differ diff --git a/src/gui/symbian/images/wait3.png b/src/gui/symbian/images/wait3.png new file mode 100644 index 0000000000..5b73e57124 Binary files /dev/null and b/src/gui/symbian/images/wait3.png differ diff --git a/src/gui/symbian/images/wait4.png b/src/gui/symbian/images/wait4.png new file mode 100644 index 0000000000..17a03396e0 Binary files /dev/null and b/src/gui/symbian/images/wait4.png differ diff --git a/src/gui/symbian/images/wait5.png b/src/gui/symbian/images/wait5.png new file mode 100644 index 0000000000..16a5c231f2 Binary files /dev/null and b/src/gui/symbian/images/wait5.png differ diff --git a/src/gui/symbian/images/wait6.png b/src/gui/symbian/images/wait6.png new file mode 100644 index 0000000000..2870093a30 Binary files /dev/null and b/src/gui/symbian/images/wait6.png differ diff --git a/src/gui/symbian/images/wait7.png b/src/gui/symbian/images/wait7.png new file mode 100644 index 0000000000..54f75a1db0 Binary files /dev/null and b/src/gui/symbian/images/wait7.png differ diff --git a/src/gui/symbian/images/wait8.png b/src/gui/symbian/images/wait8.png new file mode 100644 index 0000000000..1d370c752e Binary files /dev/null and b/src/gui/symbian/images/wait8.png differ diff --git a/src/gui/symbian/images/wait9.png b/src/gui/symbian/images/wait9.png new file mode 100644 index 0000000000..c28096fe63 Binary files /dev/null and b/src/gui/symbian/images/wait9.png differ diff --git a/src/gui/symbian/images/whatsthis.png b/src/gui/symbian/images/whatsthis.png new file mode 100644 index 0000000000..3386ef0c50 Binary files /dev/null and b/src/gui/symbian/images/whatsthis.png differ diff --git a/src/gui/symbian/qsymbianevent.cpp b/src/gui/symbian/qsymbianevent.cpp new file mode 100644 index 0000000000..e177d1702b --- /dev/null +++ b/src/gui/symbian/qsymbianevent.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsymbianevent.h" +#include <qdebug.h> + +#include <w32std.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QSymbianEvent + \brief The QSymbianEvent class contains a Symbian event of any type. + \since 4.6 + + The class is used as a generic container type for all types of Symbian + events. + + \note This class is only available on Symbian. + + \sa QApplication::symbianEventFilter() +*/ + +/*! + \enum QSymbianEvent::Type + + \value InvalidEvent The event is not valid. + \value WindowServerEvent Indicates an event of type \c TWsEvent. + \value CommandEvent Indicates that the event is a Symbian command. + \value ResourceChangeEvent Indicates that the event is a Symbian resource change type. +*/ + +/*! + \fn QSymbianEvent::type() const + + Returns the event type contained in the QSymbianEvent instance. +*/ + +/*! + \fn QSymbianEvent::isValid() const + + Returns whether this QSymbianEvent instance contains a valid event. +*/ + +/*! + Constructs a QSymbianEvent containing the given window server event + \a windowServerEvent. +*/ +QSymbianEvent::QSymbianEvent(const TWsEvent *windowServerEvent) + : m_type(WindowServerEvent) + , m_eventPtr(windowServerEvent) +{ +} + +/*! + Constructs a QSymbianEvent containing the given event value + \a value. The type of event is controlled by the \a eventType parameter. +*/ +QSymbianEvent::QSymbianEvent(QSymbianEvent::Type eventType, int value) +{ + switch (eventType) { + case CommandEvent: + case ResourceChangeEvent: + m_type = eventType; + m_eventValue = value; + break; + default: + m_type = InvalidEvent; + m_eventValue = 0; + break; + } +} + +/*! + Destroys the QSymbianEvent. +*/ +QSymbianEvent::~QSymbianEvent() +{ +} + +/*! + Returns the window server event contained in the class instance, or 0 if the event type + is not \c WindowServerEvent. +*/ +const TWsEvent *QSymbianEvent::windowServerEvent() const +{ + return (m_type == WindowServerEvent) ? static_cast<const TWsEvent *>(m_eventPtr) : 0; +} + +/*! + Returns the command contained in the class instance, or 0 if the event type + is not \c CommandEvent. +*/ +int QSymbianEvent::command() const +{ + return (m_type == CommandEvent) ? m_eventValue : 0; +} + +/*! + Returns the resource change type contained in the class instance, or 0 if the event type + is not \c ResourceChangeEvent. +*/ +int QSymbianEvent::resourceChangeType() const +{ + return (m_type == ResourceChangeEvent) ? m_eventValue : 0; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QSymbianEvent *o) +{ + if (!o) { + dbg << "QSymbianEvent(0x0)"; + return dbg; + } + dbg.nospace() << "QSymbianEvent("; + switch (o->type()) { + case QSymbianEvent::InvalidEvent: + dbg << "InvalidEvent"; + break; + case QSymbianEvent::WindowServerEvent: + dbg << "WindowServerEvent, Type = " << o->windowServerEvent()->Type(); + break; + case QSymbianEvent::CommandEvent: + dbg << "CommandEvent, command = " << o->command(); + break; + case QSymbianEvent::ResourceChangeEvent: + dbg << "ResourceChangeEvent, resourceChangeType = " << o->resourceChangeType(); + break; + default: + dbg << "Unknown event type"; + break; + } + dbg << ")"; + return dbg.space(); +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/symbian/qsymbianevent.h b/src/gui/symbian/qsymbianevent.h new file mode 100644 index 0000000000..cf05f63176 --- /dev/null +++ b/src/gui/symbian/qsymbianevent.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSYMBIANEVENT_H +#define QSYMBIANEVENT_H + +#include <QtCore/qglobal.h> + +#ifdef Q_OS_SYMBIAN + +class TWsEvent; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QSymbianEvent +{ +public: + enum Type { + InvalidEvent, + WindowServerEvent, + CommandEvent, + ResourceChangeEvent + }; + + QSymbianEvent(const TWsEvent *windowServerEvent); + QSymbianEvent(Type eventType, int value); + ~QSymbianEvent(); + + Type type() const; + bool isValid() const; + + const TWsEvent *windowServerEvent() const; + int command() const; + int resourceChangeType() const; + +private: + Type m_type; + union { + const void *m_eventPtr; + int m_eventValue; + + qint64 m_reserved; + }; +}; + +inline QSymbianEvent::Type QSymbianEvent::type() const +{ + return m_type; +} + +inline bool QSymbianEvent::isValid() const +{ + return m_type != InvalidEvent; +} + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QSymbianEvent *o); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q_OS_SYMBIAN + +#endif // QSYMBIANEVENT_H diff --git a/src/gui/symbian/symbianresources.qrc b/src/gui/symbian/symbianresources.qrc new file mode 100644 index 0000000000..0a4fc36c9a --- /dev/null +++ b/src/gui/symbian/symbianresources.qrc @@ -0,0 +1,37 @@ +<RCC> + <qresource prefix="/trolltech/symbian/cursors" > + <file>images/blank.png</file> + <file>images/busy3.png</file> + <file>images/busy6.png</file> + <file>images/busy9.png</file> + <file>images/busy12.png</file> + <file>images/closehand.png</file> + <file>images/cross.png</file> + <file>images/forbidden.png</file> + <file>images/handpoint.png</file> + <file>images/ibeam.png</file> + <file>images/openhand.png</file> + <file>images/pointer.png</file> + <file>images/sizeall.png</file> + <file>images/sizebdiag.png</file> + <file>images/sizefdiag.png</file> + <file>images/sizehor.png</file> + <file>images/sizever.png</file> + <file>images/splith.png</file> + <file>images/splitv.png</file> + <file>images/uparrow.png</file> + <file>images/wait1.png</file> + <file>images/wait2.png</file> + <file>images/wait3.png</file> + <file>images/wait4.png</file> + <file>images/wait5.png</file> + <file>images/wait6.png</file> + <file>images/wait7.png</file> + <file>images/wait8.png</file> + <file>images/wait9.png</file> + <file>images/wait10.png</file> + <file>images/wait11.png</file> + <file>images/wait12.png</file> + <file>images/whatsthis.png</file> + </qresource> +</RCC> diff --git a/src/gui/text/qabstractfontengine_p.h b/src/gui/text/qabstractfontengine_p.h new file mode 100644 index 0000000000..7a430b93bd --- /dev/null +++ b/src/gui/text/qabstractfontengine_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFONTENGINE_P_H +#define QABSTRACTFONTENGINE_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 "qfontengine_p.h" +#include "qabstractfontengine_qws.h" + +QT_BEGIN_NAMESPACE + +class QCustomFontEngine; + +class QProxyFontEngine : public QFontEngine +{ + Q_OBJECT +public: + QProxyFontEngine(QAbstractFontEngine *engine, const QFontDef &def); + virtual ~QProxyFontEngine(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual QImage alphaMapForGlyph(glyph_t); + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, QPainterPath *path, QTextItem::RenderFlags flags); + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual int glyphCount() const; + + virtual bool canRender(const QChar *string, int len); + + virtual Type type() const { return Proxy; } + virtual const char *name() const { return "proxy engine"; } + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) + virtual void draw(QPaintEngine *, qreal, qreal, const QTextItemInt &); +#endif + + inline QAbstractFontEngine::Capabilities capabilities() const + { return engineCapabilities; } + + bool drawAsOutline() const; + +private: + QAbstractFontEngine *engine; + QAbstractFontEngine::Capabilities engineCapabilities; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/text/qabstractfontengine_qws.cpp b/src/gui/text/qabstractfontengine_qws.cpp new file mode 100644 index 0000000000..7cb868f655 --- /dev/null +++ b/src/gui/text/qabstractfontengine_qws.cpp @@ -0,0 +1,776 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractfontengine_qws.h" +#include "qabstractfontengine_p.h" + +#include <private/qtextengine_p.h> +#include <private/qpaintengine_raster_p.h> + +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +class QFontEngineInfoPrivate +{ +public: + inline QFontEngineInfoPrivate() + : pixelSize(0), weight(QFont::Normal), style(QFont::StyleNormal) + {} + + QString family; + qreal pixelSize; + int weight; + QFont::Style style; + QList<QFontDatabase::WritingSystem> writingSystems; +}; + +/*! + \class QFontEngineInfo + \preliminary + \brief The QFontEngineInfo class describes a specific font provided by a font engine plugin. + \since 4.3 + \ingroup qws + + \tableofcontents + + QFontEngineInfo is used to describe a request of a font to a font engine plugin as well as to + describe the actual fonts a plugin provides. + + \sa QAbstractFontEngine, QFontEnginePlugin +*/ + +/*! + Constructs a new empty QFontEngineInfo. +*/ +QFontEngineInfo::QFontEngineInfo() +{ + d = new QFontEngineInfoPrivate; +} + +/*! + Constructs a new QFontEngineInfo with the specified \a family. + The resulting object represents a freely scalable font with normal + weight and style. +*/ +QFontEngineInfo::QFontEngineInfo(const QString &family) +{ + d = new QFontEngineInfoPrivate; + d->family = family; +} + +/*! + Creates a new font engine info object with the same attributes as \a other. +*/ +QFontEngineInfo::QFontEngineInfo(const QFontEngineInfo &other) + : d(new QFontEngineInfoPrivate(*other.d)) +{ +} + +/*! + Assigns \a other to this font engine info object, and returns a reference + to this. +*/ +QFontEngineInfo &QFontEngineInfo::operator=(const QFontEngineInfo &other) +{ + *d = *other.d; + return *this; +} + +/*! + Destroys this QFontEngineInfo object. +*/ +QFontEngineInfo::~QFontEngineInfo() +{ + delete d; +} + +/*! + \property QFontEngineInfo::family + the family name of the font +*/ + +void QFontEngineInfo::setFamily(const QString &family) +{ + d->family = family; +} + +QString QFontEngineInfo::family() const +{ + return d->family; +} + +/*! + \property QFontEngineInfo::pixelSize + the pixel size of the font + + A pixel size of 0 represents a freely scalable font. +*/ + +void QFontEngineInfo::setPixelSize(qreal size) +{ + d->pixelSize = size; +} + +qreal QFontEngineInfo::pixelSize() const +{ + return d->pixelSize; +} + +/*! + \property QFontEngineInfo::weight + the weight of the font + + The value should be from the \l{QFont::Weight} enumeration. +*/ + +void QFontEngineInfo::setWeight(int weight) +{ + d->weight = weight; +} + +int QFontEngineInfo::weight() const +{ + return d->weight; +} + +/*! + \property QFontEngineInfo::style + the style of the font +*/ + +void QFontEngineInfo::setStyle(QFont::Style style) +{ + d->style = style; +} + +QFont::Style QFontEngineInfo::style() const +{ + return d->style; +} + +/*! + \property QFontEngineInfo::writingSystems + the writing systems supported by the font + + An empty list means that any writing system is supported. +*/ + +QList<QFontDatabase::WritingSystem> QFontEngineInfo::writingSystems() const +{ + return d->writingSystems; +} + +void QFontEngineInfo::setWritingSystems(const QList<QFontDatabase::WritingSystem> &writingSystems) +{ + d->writingSystems = writingSystems; +} + +class QFontEnginePluginPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QFontEnginePlugin) + + QString foundry; +}; + +/*! + \class QFontEnginePlugin + \preliminary + \brief The QFontEnginePlugin class is the base class for font engine factory plugins in Qt for Embedded Linux. + \since 4.3 + \ingroup qws + \ingroup plugins + + \tableofcontents + + QFontEnginePlugin is provided by font engine plugins to create + instances of subclasses of QAbstractFontEngine. + + The member functions create() and availableFontEngines() must be + implemented. + + \sa QAbstractFontEngine, QFontEngineInfo +*/ + +/*! + Creates a font engine plugin that creates font engines with the + specified \a foundry and \a parent. +*/ +QFontEnginePlugin::QFontEnginePlugin(const QString &foundry, QObject *parent) + : QObject(*new QFontEnginePluginPrivate, parent) +{ + Q_D(QFontEnginePlugin); + d->foundry = foundry; +} + +/*! + Destroys this font engine plugin. +*/ +QFontEnginePlugin::~QFontEnginePlugin() +{ +} + +/*! + Returns a list of foundries the font engine plugin provides. + The default implementation returns the foundry specified with the constructor. +*/ +QStringList QFontEnginePlugin::keys() const +{ + Q_D(const QFontEnginePlugin); + return QStringList(d->foundry); +} + +/*! + \fn QAbstractFontEngine *QFontEnginePlugin::create(const QFontEngineInfo &info) + + Implemented in subclasses to create a new font engine that provides a font that + matches \a info. +*/ + +/*! + \fn QList<QFontEngineInfo> QFontEnginePlugin::availableFontEngines() const + + Implemented in subclasses to return a list of QFontEngineInfo objects that represents all font + engines the plugin can create. +*/ + +class QAbstractFontEnginePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractFontEngine) +public: +}; + +//The <classname> class is|provides|contains|specifies... +/*! + \class QAbstractFontEngine + \preliminary + \brief The QAbstractFontEngine class is the base class for font engine plugins in Qt for Embedded Linux. + \since 4.3 + \ingroup qws + + \tableofcontents + + QAbstractFontEngine is implemented by font engine plugins through QFontEnginePlugin. + + \sa QFontEnginePlugin, QFontEngineInfo +*/ + +/*! + \enum QAbstractFontEngine::Capability + + This enum describes the capabilities of a font engine. + + \value CanRenderGlyphs_Gray The font engine can render individual glyphs into 8 bpp images. + \value CanRenderGlyphs_Mono The font engine can render individual glyphs into 1 bpp images. + \value CanRenderGlyphs The font engine can render individual glyphs into images. + \value CanOutlineGlyphs The font engine can convert glyphs to painter paths. +*/ + +/*! + \enum QAbstractFontEngine::FontProperty + + This enum describes the properties of a font provided by a font engine. + + \value Ascent The ascent of the font, specified as a 26.6 fixed point value. + \value Descent The descent of the font, specified as a 26.6 fixed point value. + \value Leading The leading of the font, specified as a 26.6 fixed point value. + \value XHeight The 'x' height of the font, specified as a 26.6 fixed point value. + \value AverageCharWidth The average character width of the font, specified as a 26.6 fixed point value. + \value LineThickness The thickness of the underline and strikeout lines for the font, specified as a 26.6 fixed point value. + \value UnderlinePosition The distance from the base line to the underline position for the font, specified as a 26.6 fixed point value. + \value MaxCharWidth The width of the widest character in the font, specified as a 26.6 fixed point value. + \value MinLeftBearing The minimum left bearing of the font, specified as a 26.6 fixed point value. + \value MinRightBearing The maximum right bearing of the font, specified as a 26.6 fixed point value. + \value GlyphCount The number of glyphs in the font, specified as an integer value. + \value CacheGlyphsHint A boolean value specifying whether rendered glyphs should be cached by Qt. + \value OutlineGlyphsHint A boolean value specifying whether the font engine prefers outline drawing over image rendering for uncached glyphs. +*/ + +/*! + \enum QAbstractFontEngine::TextShapingFlag + + This enum describes flags controlling conversion of characters to glyphs and their metrics. + + \value RightToLeft The text is used in a right-to-left context. + \value ReturnDesignMetrics Return font design metrics instead of pixel metrics. +*/ + +/*! + \typedef QAbstractFontEngine::Fixed + + This type is \c int, interpreted as a 26.6 fixed point value. +*/ + +/*! + \class QAbstractFontEngine::GlyphMetrics + \brief QAbstractFontEngine::GlyphMetrics defines the metrics of a single glyph. + \preliminary + \since 4.3 +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::x + + The horizontal offset from the origin. +*/ + +/*! + \fn QAbstractFontEngine::GlyphMetrics::GlyphMetrics() + + Constructs an empty glyph metrics object with all values + set to zero. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::y + + The vertical offset from the origin (baseline). +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::width + + The width of the glyph. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::height + + The height of the glyph. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::advance + + The advance of the glyph. +*/ + +/*! + \class QAbstractFontEngine::FixedPoint + \brief QAbstractFontEngine::FixedPoint defines a point in the place using 26.6 fixed point precision. + \preliminary + \since 4.3 +*/ + +/*! + \variable QAbstractFontEngine::FixedPoint::x + + The x coordinate of this point. +*/ + +/*! + \variable QAbstractFontEngine::FixedPoint::y + + The y coordinate of this point. +*/ + +/*! + Constructs a new QAbstractFontEngine with the given \a parent. +*/ +QAbstractFontEngine::QAbstractFontEngine(QObject *parent) + : QObject(*new QAbstractFontEnginePrivate, parent) +{ +} + +/*! + Destroys this QAbstractFontEngine object. +*/ +QAbstractFontEngine::~QAbstractFontEngine() +{ +} + +/*! + \fn QAbstractFontEngine::Capabilities QAbstractFontEngine::capabilities() const + + Implemented in subclasses to specify the font engine's capabilities. The return value + may be cached by the caller and is expected not to change during the lifetime of the + font engine. +*/ + +/*! + \fn QVariant QAbstractFontEngine::fontProperty(FontProperty property) const + + Implemented in subclasses to return the value of the font attribute \a property. The return + value may be cached by the caller and is expected not to change during the lifetime of the font + engine. +*/ + +/*! + \fn bool QAbstractFontEngine::convertStringToGlyphIndices(const QChar *string, int length, uint *glyphs, int *numGlyphs, TextShapingFlags flags) const + + Implemented in subclasses to convert the characters specified by \a string and \a length to + glyph indicies, using \a flags. The glyph indicies should be returned in the \a glyphs array + provided by the caller. The maximum size of \a glyphs is specified by the value pointed to by \a + numGlyphs. If successful, the subclass implementation sets the value pointed to by \a numGlyphs + to the actual number of glyph indices generated, and returns true. Otherwise, e.g. if there is + not enough space in the provided \a glyphs array, it should set \a numGlyphs to the number of + glyphs needed for the conversion and return false. +*/ + +/*! + \fn void QAbstractFontEngine::getGlyphAdvances(const uint *glyphs, int numGlyphs, Fixed *advances, TextShapingFlags flags) const + + Implemented in subclasses to retrieve the advances of the array specified by \a glyphs and \a + numGlyphs, using \a flags. The result is returned in \a advances, which is allocated by the + caller and contains \a numGlyphs elements. +*/ + +/*! + \fn QAbstractFontEngine::GlyphMetrics QAbstractFontEngine::glyphMetrics(uint glyph) const + + Implemented in subclass to return the metrics for \a glyph. +*/ + +/*! + Implemented in subclasses to render the specified \a glyph into a \a buffer with the given \a depth , + \a bytesPerLine and \a height. + + Returns true if rendering succeeded, false otherwise. +*/ +bool QAbstractFontEngine::renderGlyph(uint glyph, int depth, int bytesPerLine, int height, uchar *buffer) +{ + Q_UNUSED(glyph) + Q_UNUSED(depth) + Q_UNUSED(bytesPerLine) + Q_UNUSED(height) + Q_UNUSED(buffer) + qWarning("QAbstractFontEngine: renderGlyph is not implemented in font plugin!"); + return false; +} + +/*! + Implemented in subclasses to add the outline of the glyphs specified by \a glyphs and \a + numGlyphs at the specified \a positions to the painter path \a path. +*/ +void QAbstractFontEngine::addGlyphOutlinesToPath(uint *glyphs, int numGlyphs, FixedPoint *positions, QPainterPath *path) +{ + Q_UNUSED(glyphs) + Q_UNUSED(numGlyphs) + Q_UNUSED(positions) + Q_UNUSED(path) + qWarning("QAbstractFontEngine: addGlyphOutlinesToPath is not implemented in font plugin!"); +} + +/* +bool QAbstractFontEngine::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension) + return false; +} + +QVariant QAbstractFontEngine::extension(Extension extension, const QVariant &argument) +{ + Q_UNUSED(argument) + Q_UNUSED(extension) + return QVariant(); +} +*/ + +QProxyFontEngine::QProxyFontEngine(QAbstractFontEngine *customEngine, const QFontDef &def) + : engine(customEngine) +{ + fontDef = def; + engineCapabilities = engine->capabilities(); +} + +QProxyFontEngine::~QProxyFontEngine() +{ + delete engine; +} + +bool QProxyFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + QVarLengthArray<uint> glyphIndicies(*nglyphs); + if (!engine->convertStringToGlyphIndices(str, len, glyphIndicies.data(), nglyphs, QAbstractFontEngine::TextShapingFlags(int(flags)))) + return false; + + // ### use memcopy instead + for (int i = 0; i < *nglyphs; ++i) { + glyphs->glyphs[i] = glyphIndicies[i]; + } + glyphs->numGlyphs = *nglyphs; + + recalcAdvances(glyphs, flags); + return true; +} + +void QProxyFontEngine::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + const int nglyphs = glyphs->numGlyphs; + + QVarLengthArray<QAbstractFontEngine::Fixed> advances(nglyphs); + engine->getGlyphAdvances(glyphs->glyphs, nglyphs, advances.data(), QAbstractFontEngine::TextShapingFlags(int(flags))); + + + // ### use memcopy instead + for (int i = 0; i < nglyphs; ++i) { + glyphs->advances_x[i] = QFixed::fromFixed(advances[i]); + glyphs->advances_y[i] = 0; + } +} + + +static QImage alphaMapFromPath(QFontEngine *fe, glyph_t glyph) +{ + glyph_metrics_t gm = fe->boundingBox(glyph); + int glyph_x = qFloor(gm.x.toReal()); + int glyph_y = qFloor(gm.y.toReal()); + int glyph_width = qCeil((gm.x + gm.width).toReal()) - glyph_x; + int glyph_height = qCeil((gm.y + gm.height).toReal()) - glyph_y; + + if (glyph_width <= 0 || glyph_height <= 0) + return QImage(); + QFixedPoint pt; + pt.x = 0; + pt.y = -glyph_y; // the baseline + QPainterPath path; + QImage im(glyph_width + qAbs(glyph_x) + 4, glyph_height, QImage::Format_ARGB32_Premultiplied); + im.fill(Qt::transparent); + QPainter p(&im); + p.setRenderHint(QPainter::Antialiasing); + fe->addGlyphsToPath(&glyph, &pt, 1, &path, 0); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawPath(path); + p.end(); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uchar *dst = (uchar *) indexed.scanLine(y); + uint *src = (uint *) im.scanLine(y); + for (int x=0; x<im.width(); ++x) + dst[x] = qAlpha(src[x]); + } + + return indexed; +} + + +QImage QProxyFontEngine::alphaMapForGlyph(glyph_t glyph) +{ + if (!(engineCapabilities & QAbstractFontEngine::CanRenderGlyphs_Gray)) + return alphaMapFromPath(this, glyph); + + QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(glyph); + if (metrics.width <= 0 || metrics.height <= 0) + return QImage(); + + QImage img(metrics.width >> 6, metrics.height >> 6, QImage::Format_Indexed8); + + // ### we should have QImage::Format_GrayScale8 + static QVector<QRgb> colorMap; + if (colorMap.isEmpty()) { + colorMap.resize(256); + for (int i=0; i<256; ++i) + colorMap[i] = qRgba(0, 0, 0, i); + } + + img.setColorTable(colorMap); + + engine->renderGlyph(glyph, /*depth*/8, img.bytesPerLine(), img.height(), img.bits()); + + return img; +} + +void QProxyFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (engineCapabilities & QAbstractFontEngine::CanOutlineGlyphs) + engine->addGlyphOutlinesToPath(glyphs, nglyphs, reinterpret_cast<QAbstractFontEngine::FixedPoint *>(positions), path); + else + QFontEngine::addGlyphsToPath(glyphs, positions, nglyphs, path, flags); +} + +glyph_metrics_t QProxyFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -ascent(), w, ascent() + descent(), w, 0); +} + +glyph_metrics_t QProxyFontEngine::boundingBox(glyph_t glyph) +{ + glyph_metrics_t m; + + QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(glyph); + m.x = QFixed::fromFixed(metrics.x); + m.y = QFixed::fromFixed(metrics.y); + m.width = QFixed::fromFixed(metrics.width); + m.height = QFixed::fromFixed(metrics.height); + m.xoff = QFixed::fromFixed(metrics.advance); + + return m; +} + +QFixed QProxyFontEngine::ascent() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Ascent).toInt()); +} + +QFixed QProxyFontEngine::descent() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Descent).toInt()); +} + +QFixed QProxyFontEngine::leading() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Leading).toInt()); +} + +QFixed QProxyFontEngine::xHeight() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::XHeight).toInt()); +} + +QFixed QProxyFontEngine::averageCharWidth() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::AverageCharWidth).toInt()); +} + +QFixed QProxyFontEngine::lineThickness() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::LineThickness).toInt()); +} + +QFixed QProxyFontEngine::underlinePosition() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::UnderlinePosition).toInt()); +} + +qreal QProxyFontEngine::maxCharWidth() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MaxCharWidth).toInt()).toReal(); +} + +qreal QProxyFontEngine::minLeftBearing() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MinLeftBearing).toInt()).toReal(); +} + +qreal QProxyFontEngine::minRightBearing() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MinRightBearing).toInt()).toReal(); +} + +int QProxyFontEngine::glyphCount() const +{ + return engine->fontProperty(QAbstractFontEngine::GlyphCount).toInt(); +} + +bool QProxyFontEngine::canRender(const QChar *string, int len) +{ + QVarLengthArray<uint> glyphs(len); + int numGlyphs = len; + + if (!engine->convertStringToGlyphIndices(string, len, glyphs.data(), &numGlyphs, /*flags*/0)) + return false; + + for (int i = 0; i < numGlyphs; ++i) + if (!glyphs[i]) + return false; + + return true; +} + +void QProxyFontEngine::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast<QRasterPaintEngine*>(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + for(int i = 0; i < glyphs.size(); i++) { + QImage glyph = alphaMapForGlyph(glyphs[i]); + if (glyph.isNull()) + continue; + + if (glyph.format() != QImage::Format_Indexed8 + && glyph.format() != QImage::Format_Mono) + continue; + + QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(glyphs[i]); + + int depth = glyph.format() == QImage::Format_Mono ? 1 : 8; + paintEngine->alphaPenBlt(glyph.bits(), glyph.bytesPerLine(), depth, + qRound(positions[i].x + QFixed::fromFixed(metrics.x)), + qRound(positions[i].y + QFixed::fromFixed(metrics.y)), + glyph.width(), glyph.height()); + } +} + +/* + * This is only called when we use the proxy fontengine directly (without sharing the rendered + * glyphs). So we prefer outline rendering over rendering of unshared glyphs. That decision is + * done in qfontdatabase_qws.cpp by looking at the ShareGlyphsHint and the pixel size of the font. + */ +bool QProxyFontEngine::drawAsOutline() const +{ + if (!(engineCapabilities & QAbstractFontEngine::CanOutlineGlyphs)) + return false; + + QVariant outlineHint = engine->fontProperty(QAbstractFontEngine::OutlineGlyphsHint); + return !outlineHint.isValid() || outlineHint.toBool(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qabstractfontengine_qws.h b/src/gui/text/qabstractfontengine_qws.h new file mode 100644 index 0000000000..dfc15dcf5d --- /dev/null +++ b/src/gui/text/qabstractfontengine_qws.h @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFONTENGINE_QWS_H +#define QABSTRACTFONTENGINE_QWS_H + +#include <QtCore/qobject.h> +#include <QtCore/qhash.h> +#include <QtCore/qvariant.h> +#include <QtCore/qfactoryinterface.h> +#include <QtGui/qpaintengine.h> +#include <QtGui/qfontdatabase.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontEngineInfoPrivate; + +class Q_GUI_EXPORT QFontEngineInfo +{ +public: + QDOC_PROPERTY(QString family READ family WRITE setFamily) + QDOC_PROPERTY(qreal pixelSize READ pixelSize WRITE setPixelSize) + QDOC_PROPERTY(int weight READ weight WRITE setWeight) + QDOC_PROPERTY(QFont::Style style READ style WRITE setStyle) + QDOC_PROPERTY(QList<QFontDatabase::WritingSystem> writingSystems READ writingSystems WRITE setWritingSystems) + + QFontEngineInfo(); + explicit QFontEngineInfo(const QString &family); + QFontEngineInfo(const QFontEngineInfo &other); + QFontEngineInfo &operator=(const QFontEngineInfo &other); + ~QFontEngineInfo(); + + void setFamily(const QString &name); + QString family() const; + + void setPixelSize(qreal size); + qreal pixelSize() const; + + void setWeight(int weight); + int weight() const; + + void setStyle(QFont::Style style); + QFont::Style style() const; + + QList<QFontDatabase::WritingSystem> writingSystems() const; + void setWritingSystems(const QList<QFontDatabase::WritingSystem> &writingSystems); + +private: + QFontEngineInfoPrivate *d; +}; + +class QAbstractFontEngine; + +struct Q_GUI_EXPORT QFontEngineFactoryInterface : public QFactoryInterface +{ + virtual QAbstractFontEngine *create(const QFontEngineInfo &info) = 0; + virtual QList<QFontEngineInfo> availableFontEngines() const = 0; +}; + +#define QFontEngineFactoryInterface_iid "com.trolltech.Qt.QFontEngineFactoryInterface" +Q_DECLARE_INTERFACE(QFontEngineFactoryInterface, QFontEngineFactoryInterface_iid) + +class QFontEnginePluginPrivate; + +class Q_GUI_EXPORT QFontEnginePlugin : public QObject, public QFontEngineFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QFontEngineFactoryInterface:QFactoryInterface) +public: + QFontEnginePlugin(const QString &foundry, QObject *parent = 0); + ~QFontEnginePlugin(); + + virtual QStringList keys() const; + + virtual QAbstractFontEngine *create(const QFontEngineInfo &info) = 0; + virtual QList<QFontEngineInfo> availableFontEngines() const = 0; + +private: + Q_DECLARE_PRIVATE(QFontEnginePlugin) + Q_DISABLE_COPY(QFontEnginePlugin) +}; + +class QAbstractFontEnginePrivate; + +class Q_GUI_EXPORT QAbstractFontEngine : public QObject +{ + Q_OBJECT +public: + enum Capability { + CanOutlineGlyphs = 1, + CanRenderGlyphs_Mono = 2, + CanRenderGlyphs_Gray = 4, + CanRenderGlyphs = CanRenderGlyphs_Mono | CanRenderGlyphs_Gray + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + explicit QAbstractFontEngine(QObject *parent = 0); + ~QAbstractFontEngine(); + + typedef int Fixed; // 26.6 + + struct FixedPoint + { + Fixed x; + Fixed y; + }; + + struct GlyphMetrics + { + inline GlyphMetrics() + : x(0), y(0), width(0), height(0), + advance(0) {} + Fixed x; + Fixed y; + Fixed width; + Fixed height; + Fixed advance; + }; + + enum FontProperty { + Ascent, + Descent, + Leading, + XHeight, + AverageCharWidth, + LineThickness, + UnderlinePosition, + MaxCharWidth, + MinLeftBearing, + MinRightBearing, + GlyphCount, + + // hints + CacheGlyphsHint, + OutlineGlyphsHint + }; + + // keep in sync with QTextEngine::ShaperFlag!! + enum TextShapingFlag { + RightToLeft = 0x0001, + ReturnDesignMetrics = 0x0002 + }; + Q_DECLARE_FLAGS(TextShapingFlags, TextShapingFlag) + + virtual Capabilities capabilities() const = 0; + virtual QVariant fontProperty(FontProperty property) const = 0; + + virtual bool convertStringToGlyphIndices(const QChar *string, int length, uint *glyphs, int *numGlyphs, TextShapingFlags flags) const = 0; + + virtual void getGlyphAdvances(const uint *glyphs, int numGlyphs, Fixed *advances, TextShapingFlags flags) const = 0; + + virtual GlyphMetrics glyphMetrics(uint glyph) const = 0; + + virtual bool renderGlyph(uint glyph, int depth, int bytesPerLine, int height, uchar *buffer); + + virtual void addGlyphOutlinesToPath(uint *glyphs, int numGlyphs, FixedPoint *positions, QPainterPath *path); + + /* + enum Extension { + GetTrueTypeTable + }; + + virtual bool supportsExtension(Extension extension) const; + virtual QVariant extension(Extension extension, const QVariant &argument = QVariant()); + */ + +private: + Q_DECLARE_PRIVATE(QAbstractFontEngine) + Q_DISABLE_COPY(QAbstractFontEngine) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFontEngine::Capabilities) +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFontEngine::TextShapingFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/text/qabstracttextdocumentlayout.cpp b/src/gui/text/qabstracttextdocumentlayout.cpp new file mode 100644 index 0000000000..53ffaa4c6d --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout.cpp @@ -0,0 +1,623 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qabstracttextdocumentlayout.h> +#include <qtextformat.h> +#include "qtextdocument_p.h" +#include "qtextengine_p.h" + +#include "qabstracttextdocumentlayout_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractTextDocumentLayout + \reentrant + + \brief The QAbstractTextDocumentLayout class is an abstract base + class used to implement custom layouts for QTextDocuments. + + \ingroup richtext-processing + + The standard layout provided by Qt can handle simple word processing + including inline images, lists and tables. + + Some applications, e.g., a word processor or a DTP application might need + more features than the ones provided by Qt's layout engine, in which case + you can subclass QAbstractTextDocumentLayout to provide custom layout + behavior for your text documents. + + An instance of the QAbstractTextDocumentLayout subclass can be installed + on a QTextDocument object with the + \l{QTextDocument::}{setDocumentLayout()} function. + + You can insert custom objects into a QTextDocument; see the + QTextObjectInterface class description for details. + + \sa QTextObjectInterface +*/ + +/*! + \class QTextObjectInterface + \brief The QTextObjectInterface class allows drawing of + custom text objects in \l{QTextDocument}s. + \since 4.5 + + A text object describes the structure of one or more elements in a + text document; for instance, images imported from HTML are + implemented using text objects. A text object knows how to lay out + and draw its elements when a document is being rendered. + + Qt allows custom text objects to be inserted into a document by + registering a custom \l{QTextCharFormat::objectType()}{object + type} with QTextCharFormat. A QTextObjectInterface must also be + implemented for this type and be + \l{QAbstractTextDocumentLayout::registerHandler()}{registered} + with the QAbstractTextDocumentLayout of the document. When the + object type is encountered while rendering a QTextDocument, the + intrinsicSize() and drawObject() functions of the interface are + called. + + The following list explains the required steps of inserting a + custom text object into a document: + + \list + \o Choose an \a objectType. The \a objectType is an integer with a + value greater or equal to QTextFormat::UserObject. + \o Create a QTextCharFormat object and set the object type to the + chosen type using the setObjectType() function. + \o Implement the QTextObjectInterface class. + \o Call QAbstractTextDocumentLayout::registerHandler() with an instance of your + QTextObjectInterface subclass to register your object type. + \o Insert QChar::ObjectReplacementCharacter with the aforementioned + QTextCharFormat of the chosen object type into the document. + As mentioned, the functions of QTextObjectInterface + \l{QTextObjectInterface::}{intrinsicSize()} and + \l{QTextObjectInterface::}{drawObject()} will then be called with the + QTextFormat as parameter whenever the replacement character is + encountered. + \endlist + + A class implementing a text object needs to inherit both QObject + and QTextObjectInterface. QObject must be the first class + inherited. For instance: + + \snippet examples/richtext/textobject/svgtextobject.h 1 + + The data of a text object is usually stored in the QTextCharFormat + using QTextCharFormat::setProperty(), and then retrieved with + QTextCharFormat::property(). + + \warning Copy and Paste operations ignore custom text objects. + + \sa {Text Object Example}, QTextCharFormat, QTextLayout +*/ + +/*! + \fn QTextObjectInterface::~QTextObjectInterface() + + Destroys this QTextObjectInterface. +*/ + +/*! + \fn virtual QSizeF QTextObjectInterface::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0 + + The intrinsicSize() function returns the size of the text object + represented by \a format in the given document (\a doc) at the + given position (\a posInDocument). + + The size calculated will be used for subsequent calls to + drawObject() for this \a format. + + \sa drawObject() +*/ + +/*! + \fn virtual void QTextObjectInterface::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0 + + Draws this text object using the specified \a painter. + + The size of the rectangle, \a rect, to draw in is the size + previously calculated by intrinsicSize(). The rectangles position + is relative to the \a painter. + + You also get the document (\a doc) and the position (\a + posInDocument) of the \a format in that document. + + \sa intrinsicSize() +*/ + +/*! + \fn void QAbstractTextDocumentLayout::update(const QRectF &rect) + + This signal is emitted when the rectangle \a rect has been updated. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the layout of the contents change in order to repaint. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::updateBlock(const QTextBlock &block) + \since 4.4 + + This signal is emitted when the specified \a block has been updated. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the layout of \a block has changed in order to repaint. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::documentSizeChanged(const QSizeF &newSize) + + This signal is emitted when the size of the document layout changes to + \a newSize. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when the + document's entire layout size changes. This signal is useful for widgets + that display text documents since it enables them to update their scroll + bars correctly. + + \sa documentSize() +*/ + +/*! + \fn void QAbstractTextDocumentLayout::pageCountChanged(int newPages) + + This signal is emitted when the number of pages in the layout changes; + \a newPages is the updated page count. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the number of pages in the layout has changed. Changes to the page count + are caused by changes to the layout or the document content itself. + + \sa pageCount() +*/ + +/*! + \fn int QAbstractTextDocumentLayout::pageCount() const + + Returns the number of pages contained in the layout. + + \sa pageCountChanged() +*/ + +/*! + \fn QSizeF QAbstractTextDocumentLayout::documentSize() const + + Returns the total size of the document's layout. + + This information can be used by display widgets to update their scroll bars + correctly. + + \sa documentSizeChanged(), QTextDocument::pageSize +*/ + +/*! + \fn void QAbstractTextDocumentLayout::draw(QPainter *painter, const PaintContext &context) + + Draws the layout with the given \a painter using the given \a context. +*/ + +/*! + \fn int QAbstractTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const + + Returns the cursor postion for the given \a point with the specified + \a accuracy. Returns -1 if no valid cursor position was found. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded) + + This function is called whenever the contents of the document change. A + change occurs when text is inserted, removed, or a combination of these + two. The change is specified by \a position, \a charsRemoved, and + \a charsAdded corresponding to the starting character position of the + change, the number of characters removed from the document, and the + number of characters added. + + For example, when inserting the text "Hello" into an empty document, + \a charsRemoved would be 0 and \a charsAdded would be 5 (the length of + the string). + + Replacing text is a combination of removing and inserting. For example, if + the text "Hello" gets replaced by "Hi", \a charsRemoved would be 5 and + \a charsAdded would be 2. + + For subclasses of QAbstractTextDocumentLayout, this is the central function + where a large portion of the work to lay out and position document contents + is done. + + For example, in a subclass that only arranges blocks of text, an + implementation of this function would have to do the following: + + \list + \o Determine the list of changed \l{QTextBlock}(s) using the parameters + provided. + \o Each QTextBlock object's corresponding QTextLayout object needs to + be processed. You can access the \l{QTextBlock}'s layout using the + QTextBlock::layout() function. This processing should take the + document's page size into consideration. + \o If the total number of pages changed, the pageCountChanged() signal + should be emitted. + \o If the total size changed, the documentSizeChanged() signal should + be emitted. + \o The update() signal should be emitted to schedule a repaint of areas + in the layout that require repainting. + \endlist + + \sa QTextLayout +*/ + +/*! + \class QAbstractTextDocumentLayout::PaintContext + \reentrant + + \brief The QAbstractTextDocumentLayout::PaintContext class is a convenience + class defining the parameters used when painting a document's layout. + + A paint context is used when rendering custom layouts for QTextDocuments + with the QAbstractTextDocumentLayout::draw() function. It is specified by + a \l {cursorPosition}{cursor position}, \l {palette}{default text color}, + \l clip rectangle and a collection of \l selections. + + \sa QAbstractTextDocumentLayout +*/ + +/*! + \fn QAbstractTextDocumentLayout::PaintContext::PaintContext() + \internal +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::cursorPosition + + \brief the position within the document, where the cursor line should be + drawn. + + The default value is -1. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::palette + + \brief the default color that is used for the text, when no color is + specified. + + The default value is the application's default palette. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::clip + + \brief a hint to the layout specifying the area around paragraphs, frames + or text require painting. + + Everything outside of this rectangle does not need to be painted. + + Specifying a clip rectangle can speed up drawing of large documents + significantly. Note that the clip rectangle is in document coordinates (not + in viewport coordinates). It is not a substitute for a clip region set on + the painter but merely a hint. + + The default value is a null rectangle indicating everything needs to be + painted. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::selections + + \brief the collection of selections that will be rendered when passing this + paint context to QAbstractTextDocumentLayout's draw() function. + + The default value is an empty vector indicating no selection. +*/ + +/*! + \class QAbstractTextDocumentLayout::Selection + \reentrant + + \brief The QAbstractTextDocumentLayout::Selection class is a convenience + class defining the parameters of a selection. + + A selection can be used to specify a part of a document that should be + highlighted when drawing custom layouts for QTextDocuments with the + QAbstractTextDocumentLayout::draw() function. It is specified using + \l cursor and a \l format. + + \sa QAbstractTextDocumentLayout, PaintContext +*/ + +/*! + \variable QAbstractTextDocumentLayout::Selection::format + + \brief the format of the selection + + The default value is QTextFormat::InvalidFormat. +*/ + +/*! + \variable QAbstractTextDocumentLayout::Selection::cursor + \brief the selection's cursor + + The default value is a null cursor. +*/ + +/*! + Creates a new text document layout for the given \a document. +*/ +QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QTextDocument *document) + : QObject(*new QAbstractTextDocumentLayoutPrivate, document) +{ + Q_D(QAbstractTextDocumentLayout); + d->setDocument(document); +} + +/*! + \internal +*/ +QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QAbstractTextDocumentLayoutPrivate &p, QTextDocument *document) + :QObject(p, document) +{ + Q_D(QAbstractTextDocumentLayout); + d->setDocument(document); +} + +/*! + \internal +*/ +QAbstractTextDocumentLayout::~QAbstractTextDocumentLayout() +{ +} + +/*! + \fn void QAbstractTextDocumentLayout::registerHandler(int objectType, QObject *component) + + Registers the given \a component as a handler for items of the given \a objectType. + + \note registerHandler() has to be called once for each object type. This + means that there is only one handler for multiple replacement characters + of the same object type. +*/ +void QAbstractTextDocumentLayout::registerHandler(int formatType, QObject *component) +{ + Q_D(QAbstractTextDocumentLayout); + + QTextObjectInterface *iface = qobject_cast<QTextObjectInterface *>(component); + if (!iface) + return; // ### print error message on terminal? + + connect(component, SIGNAL(destroyed(QObject*)), this, SLOT(_q_handlerDestroyed(QObject*))); + + QTextObjectHandler h; + h.iface = iface; + h.component = component; + d->handlers.insert(formatType, h); +} + +/*! + Returns a handler for objects of the given \a objectType. +*/ +QTextObjectInterface *QAbstractTextDocumentLayout::handlerForObject(int objectType) const +{ + Q_D(const QAbstractTextDocumentLayout); + + QTextObjectHandler handler = d->handlers.value(objectType); + if (!handler.component) + return 0; + + return handler.iface; +} + +/*! + Sets the size of the inline object \a item corresponding to the text + \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation resizes the \a item to the size returned by + the object handler's intrinsicSize() function. This function is called only + within Qt. Subclasses can reimplement this function to customize the + resizing of inline objects. +*/ +void QAbstractTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QAbstractTextDocumentLayout); + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QSizeF s = handler.iface->intrinsicSize(document(), posInDocument, format); + item.setWidth(s.width()); + item.setAscent(s.height() - 1); + item.setDescent(0); +} + +/*! + Lays out the inline object \a item using the given text \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation does nothing. This function is called only + within Qt. Subclasses can reimplement this function to customize the + position of inline objects. + + \sa drawInlineObject() +*/ +void QAbstractTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_UNUSED(item); + Q_UNUSED(posInDocument); + Q_UNUSED(format); +} + +/*! + \fn void QAbstractTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format) + + This function is called to draw the inline object, \a object, with the + given \a painter within the rectangle specified by \a rect using the + specified text \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation calls drawObject() on the object handlers. This + function is called only within Qt. Subclasses can reimplement this function + to customize the drawing of inline objects. + + \sa draw() +*/ +void QAbstractTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format) +{ + Q_UNUSED(item); + Q_D(QAbstractTextDocumentLayout); + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + handler.iface->drawObject(p, rect, document(), posInDocument, format); +} + +void QAbstractTextDocumentLayoutPrivate::_q_handlerDestroyed(QObject *obj) +{ + HandlerHash::Iterator it = handlers.begin(); + while (it != handlers.end()) + if ((*it).component == obj) + it = handlers.erase(it); + else + ++it; +} + +/*! + \internal + + Returns the index of the format at position \a pos. +*/ +int QAbstractTextDocumentLayout::formatIndex(int pos) +{ + QTextDocumentPrivate *pieceTable = qobject_cast<QTextDocument *>(parent())->docHandle(); + return pieceTable->find(pos).value()->format; +} + +/*! + \fn QTextCharFormat QAbstractTextDocumentLayout::format(int position) + + Returns the character format that is applicable at the given \a position. +*/ +QTextCharFormat QAbstractTextDocumentLayout::format(int pos) +{ + QTextDocumentPrivate *pieceTable = qobject_cast<QTextDocument *>(parent())->docHandle(); + int idx = pieceTable->find(pos).value()->format; + return pieceTable->formatCollection()->charFormat(idx); +} + + + +/*! + Returns the text document that this layout is operating on. +*/ +QTextDocument *QAbstractTextDocumentLayout::document() const +{ + Q_D(const QAbstractTextDocumentLayout); + return d->document; +} + +/*! + \fn QString QAbstractTextDocumentLayout::anchorAt(const QPointF &position) const + + Returns the reference of the anchor the given \a position, or an empty + string if no anchor exists at that point. +*/ +QString QAbstractTextDocumentLayout::anchorAt(const QPointF& pos) const +{ + int cursorPos = hitTest(pos, Qt::ExactHit); + if (cursorPos == -1) + return QString(); + + QTextDocumentPrivate *pieceTable = qobject_cast<const QTextDocument *>(parent())->docHandle(); + QTextDocumentPrivate::FragmentIterator it = pieceTable->find(cursorPos); + QTextCharFormat fmt = pieceTable->formatCollection()->charFormat(it->format); + return fmt.anchorHref(); +} + +/*! + \fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const + + Returns the bounding rectangle of \a frame. +*/ + +/*! + \fn QRectF QAbstractTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const + + Returns the bounding rectangle of \a block. +*/ + +/*! + Sets the paint device used for rendering the document's layout to the given + \a device. + + \sa paintDevice() +*/ +void QAbstractTextDocumentLayout::setPaintDevice(QPaintDevice *device) +{ + Q_D(QAbstractTextDocumentLayout); + d->paintDevice = device; +} + +/*! + Returns the paint device used to render the document's layout. + + \sa setPaintDevice() +*/ +QPaintDevice *QAbstractTextDocumentLayout::paintDevice() const +{ + Q_D(const QAbstractTextDocumentLayout); + return d->paintDevice; +} + +QT_END_NAMESPACE + +#include "moc_qabstracttextdocumentlayout.cpp" diff --git a/src/gui/text/qabstracttextdocumentlayout.h b/src/gui/text/qabstracttextdocumentlayout.h new file mode 100644 index 0000000000..a3cd27cb31 --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTEXTDOCUMENTLAYOUT_H +#define QABSTRACTTEXTDOCUMENTLAYOUT_H + +#include <QtCore/qobject.h> +#include <QtGui/qtextlayout.h> +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qpalette.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractTextDocumentLayoutPrivate; +class QTextBlock; +class QTextObjectInterface; +class QTextFrame; + +class Q_GUI_EXPORT QAbstractTextDocumentLayout : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QAbstractTextDocumentLayout) + +public: + explicit QAbstractTextDocumentLayout(QTextDocument *doc); + ~QAbstractTextDocumentLayout(); + + struct Selection + { + QTextCursor cursor; + QTextCharFormat format; + }; + + struct PaintContext + { + PaintContext() + : cursorPosition(-1) + {} + int cursorPosition; + QPalette palette; + QRectF clip; + QVector<Selection> selections; + }; + + virtual void draw(QPainter *painter, const PaintContext &context) = 0; + virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const = 0; + QString anchorAt(const QPointF& pos) const; + + virtual int pageCount() const = 0; + virtual QSizeF documentSize() const = 0; + + virtual QRectF frameBoundingRect(QTextFrame *frame) const = 0; + virtual QRectF blockBoundingRect(const QTextBlock &block) const = 0; + + void setPaintDevice(QPaintDevice *device); + QPaintDevice *paintDevice() const; + + QTextDocument *document() const; + + void registerHandler(int objectType, QObject *component); + QTextObjectInterface *handlerForObject(int objectType) const; + +Q_SIGNALS: + void update(const QRectF & = QRectF(0., 0., 1000000000., 1000000000.)); + void updateBlock(const QTextBlock &block); + void documentSizeChanged(const QSizeF &newSize); + void pageCountChanged(int newPages); + +protected: + QAbstractTextDocumentLayout(QAbstractTextDocumentLayoutPrivate &, QTextDocument *); + + virtual void documentChanged(int from, int charsRemoved, int charsAdded) = 0; + + virtual void resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + virtual void positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + virtual void drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format); + + int formatIndex(int pos); + QTextCharFormat format(int pos); + +private: + friend class QTextControl; + friend class QTextDocument; + friend class QTextDocumentPrivate; + friend class QTextEngine; + friend class QTextLayout; + friend class QTextLine; + Q_PRIVATE_SLOT(d_func(), void _q_handlerDestroyed(QObject *obj)) + Q_PRIVATE_SLOT(d_func(), int _q_dynamicPageCountSlot()) + Q_PRIVATE_SLOT(d_func(), QSizeF _q_dynamicDocumentSizeSlot()) +}; + +class Q_GUI_EXPORT QTextObjectInterface +{ +public: + virtual ~QTextObjectInterface() {} + virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0; + virtual void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0; +}; + +Q_DECLARE_INTERFACE(QTextObjectInterface, "com.trolltech.Qt.QTextObjectInterface") + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTTEXTDOCUMENTLAYOUT_H diff --git a/src/gui/text/qabstracttextdocumentlayout_p.h b/src/gui/text/qabstracttextdocumentlayout_p.h new file mode 100644 index 0000000000..e1f236d4ee --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTEXTDOCUMENTLAYOUT_P_H +#define QABSTRACTTEXTDOCUMENTLAYOUT_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/qobject_p.h" +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +struct QTextObjectHandler +{ + QTextObjectHandler() : iface(0) {} + QTextObjectInterface *iface; + QPointer<QObject> component; +}; +typedef QHash<int, QTextObjectHandler> HandlerHash; + +class QAbstractTextDocumentLayoutPrivate : public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QAbstractTextDocumentLayout) + + inline QAbstractTextDocumentLayoutPrivate() + : paintDevice(0) {} + + inline void setDocument(QTextDocument *doc) { + document = doc; + docPrivate = 0; + if (doc) + docPrivate = doc->docHandle(); + } + + inline int _q_dynamicPageCountSlot() const + { return q_func()->pageCount(); } + inline QSizeF _q_dynamicDocumentSizeSlot() const + { return q_func()->documentSize(); } + + HandlerHash handlers; + + void _q_handlerDestroyed(QObject *obj); + QPaintDevice *paintDevice; + + QTextDocument *document; + QTextDocumentPrivate *docPrivate; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTTEXTDOCUMENTLAYOUT_P_H diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp new file mode 100644 index 0000000000..052dc72b63 --- /dev/null +++ b/src/gui/text/qcssparser.cpp @@ -0,0 +1,2768 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcssparser_p.h" + +#include <qdebug.h> +#include <qcolor.h> +#include <qfont.h> +#include <qfileinfo.h> +#include <qfontmetrics.h> +#include <qbrush.h> +#include <qimagereader.h> +#include "private/qfunctions_p.h" + +#ifndef QT_NO_CSSPARSER + +QT_BEGIN_NAMESPACE + +#include "qcssscanner.cpp" + +using namespace QCss; + +struct QCssKnownValue +{ + const char *name; + quint64 id; +}; + +static const QCssKnownValue properties[NumProperties - 1] = { + { "-qt-background-role", QtBackgroundRole }, + { "-qt-block-indent", QtBlockIndent }, + { "-qt-list-indent", QtListIndent }, + { "-qt-list-number-prefix", QtListNumberPrefix }, + { "-qt-list-number-suffix", QtListNumberSuffix }, + { "-qt-paragraph-type", QtParagraphType }, + { "-qt-style-features", QtStyleFeatures }, + { "-qt-table-type", QtTableType }, + { "-qt-user-state", QtUserState }, + { "alternate-background-color", QtAlternateBackground }, + { "background", Background }, + { "background-attachment", BackgroundAttachment }, + { "background-clip", BackgroundClip }, + { "background-color", BackgroundColor }, + { "background-image", BackgroundImage }, + { "background-origin", BackgroundOrigin }, + { "background-position", BackgroundPosition }, + { "background-repeat", BackgroundRepeat }, + { "border", Border }, + { "border-bottom", BorderBottom }, + { "border-bottom-color", BorderBottomColor }, + { "border-bottom-left-radius", BorderBottomLeftRadius }, + { "border-bottom-right-radius", BorderBottomRightRadius }, + { "border-bottom-style", BorderBottomStyle }, + { "border-bottom-width", BorderBottomWidth }, + { "border-color", BorderColor }, + { "border-image", BorderImage }, + { "border-left", BorderLeft }, + { "border-left-color", BorderLeftColor }, + { "border-left-style", BorderLeftStyle }, + { "border-left-width", BorderLeftWidth }, + { "border-radius", BorderRadius }, + { "border-right", BorderRight }, + { "border-right-color", BorderRightColor }, + { "border-right-style", BorderRightStyle }, + { "border-right-width", BorderRightWidth }, + { "border-style", BorderStyles }, + { "border-top", BorderTop }, + { "border-top-color", BorderTopColor }, + { "border-top-left-radius", BorderTopLeftRadius }, + { "border-top-right-radius", BorderTopRightRadius }, + { "border-top-style", BorderTopStyle }, + { "border-top-width", BorderTopWidth }, + { "border-width", BorderWidth }, + { "bottom", Bottom }, + { "color", Color }, + { "float", Float }, + { "font", Font }, + { "font-family", FontFamily }, + { "font-size", FontSize }, + { "font-style", FontStyle }, + { "font-variant", FontVariant }, + { "font-weight", FontWeight }, + { "height", Height }, + { "image", QtImage }, + { "image-position", QtImageAlignment }, + { "left", Left }, + { "line-height", LineHeight }, + { "list-style", ListStyle }, + { "list-style-type", ListStyleType }, + { "margin" , Margin }, + { "margin-bottom", MarginBottom }, + { "margin-left", MarginLeft }, + { "margin-right", MarginRight }, + { "margin-top", MarginTop }, + { "max-height", MaximumHeight }, + { "max-width", MaximumWidth }, + { "min-height", MinimumHeight }, + { "min-width", MinimumWidth }, + { "outline", Outline }, + { "outline-bottom-left-radius", OutlineBottomLeftRadius }, + { "outline-bottom-right-radius", OutlineBottomRightRadius }, + { "outline-color", OutlineColor }, + { "outline-offset", OutlineOffset }, + { "outline-radius", OutlineRadius }, + { "outline-style", OutlineStyle }, + { "outline-top-left-radius", OutlineTopLeftRadius }, + { "outline-top-right-radius", OutlineTopRightRadius }, + { "outline-width", OutlineWidth }, + { "padding", Padding }, + { "padding-bottom", PaddingBottom }, + { "padding-left", PaddingLeft }, + { "padding-right", PaddingRight }, + { "padding-top", PaddingTop }, + { "page-break-after", PageBreakAfter }, + { "page-break-before", PageBreakBefore }, + { "position", Position }, + { "right", Right }, + { "selection-background-color", QtSelectionBackground }, + { "selection-color", QtSelectionForeground }, + { "spacing", QtSpacing }, + { "subcontrol-origin", QtOrigin }, + { "subcontrol-position", QtPosition }, + { "text-align", TextAlignment }, + { "text-decoration", TextDecoration }, + { "text-indent", TextIndent }, + { "text-transform", TextTransform }, + { "text-underline-style", TextUnderlineStyle }, + { "top", Top }, + { "vertical-align", VerticalAlignment }, + { "white-space", Whitespace }, + { "width", Width } +}; + +static const QCssKnownValue values[NumKnownValues - 1] = { + { "active", Value_Active }, + { "alternate-base", Value_AlternateBase }, + { "always", Value_Always }, + { "auto", Value_Auto }, + { "base", Value_Base }, + { "bold", Value_Bold }, + { "bottom", Value_Bottom }, + { "bright-text", Value_BrightText }, + { "button", Value_Button }, + { "button-text", Value_ButtonText }, + { "center", Value_Center }, + { "circle", Value_Circle }, + { "dark", Value_Dark }, + { "dashed", Value_Dashed }, + { "decimal", Value_Decimal }, + { "disabled", Value_Disabled }, + { "disc", Value_Disc }, + { "dot-dash", Value_DotDash }, + { "dot-dot-dash", Value_DotDotDash }, + { "dotted", Value_Dotted }, + { "double", Value_Double }, + { "groove", Value_Groove }, + { "highlight", Value_Highlight }, + { "highlighted-text", Value_HighlightedText }, + { "inset", Value_Inset }, + { "italic", Value_Italic }, + { "large", Value_Large }, + { "left", Value_Left }, + { "light", Value_Light }, + { "line-through", Value_LineThrough }, + { "link", Value_Link }, + { "link-visited", Value_LinkVisited }, + { "lower-alpha", Value_LowerAlpha }, + { "lower-roman", Value_LowerRoman }, + { "lowercase", Value_Lowercase }, + { "medium", Value_Medium }, + { "mid", Value_Mid }, + { "middle", Value_Middle }, + { "midlight", Value_Midlight }, + { "native", Value_Native }, + { "none", Value_None }, + { "normal", Value_Normal }, + { "nowrap", Value_NoWrap }, + { "oblique", Value_Oblique }, + { "off", Value_Off }, + { "on", Value_On }, + { "outset", Value_Outset }, + { "overline", Value_Overline }, + { "pre", Value_Pre }, + { "pre-wrap", Value_PreWrap }, + { "ridge", Value_Ridge }, + { "right", Value_Right }, + { "selected", Value_Selected }, + { "shadow", Value_Shadow }, + { "small" , Value_Small }, + { "small-caps", Value_SmallCaps }, + { "solid", Value_Solid }, + { "square", Value_Square }, + { "sub", Value_Sub }, + { "super", Value_Super }, + { "text", Value_Text }, + { "top", Value_Top }, + { "transparent", Value_Transparent }, + { "underline", Value_Underline }, + { "upper-alpha", Value_UpperAlpha }, + { "upper-roman", Value_UpperRoman }, + { "uppercase", Value_Uppercase }, + { "wave", Value_Wave }, + { "window", Value_Window }, + { "window-text", Value_WindowText }, + { "x-large", Value_XLarge }, + { "xx-large", Value_XXLarge } +}; + +//Map id to strings as they appears in the 'values' array above +static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47, + 29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16, + 11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31, + 1, 15, 0, 52, 45, 44 }; + +QString Value::toString() const +{ + if (type == KnownIdentifier) { + return QLatin1String(values[indexOfId[variant.toInt()]].name); + } else { + return variant.toString(); + } +} + +static const QCssKnownValue pseudos[NumPseudos - 1] = { + { "active", PseudoClass_Active }, + { "adjoins-item", PseudoClass_Item }, + { "alternate", PseudoClass_Alternate }, + { "bottom", PseudoClass_Bottom }, + { "checked", PseudoClass_Checked }, + { "closable", PseudoClass_Closable }, + { "closed", PseudoClass_Closed }, + { "default", PseudoClass_Default }, + { "disabled", PseudoClass_Disabled }, + { "edit-focus", PseudoClass_EditFocus }, + { "editable", PseudoClass_Editable }, + { "enabled", PseudoClass_Enabled }, + { "exclusive", PseudoClass_Exclusive }, + { "first", PseudoClass_First }, + { "flat", PseudoClass_Flat }, + { "floatable", PseudoClass_Floatable }, + { "focus", PseudoClass_Focus }, + { "has-children", PseudoClass_Children }, + { "has-siblings", PseudoClass_Sibling }, + { "horizontal", PseudoClass_Horizontal }, + { "hover", PseudoClass_Hover }, + { "indeterminate" , PseudoClass_Indeterminate }, + { "last", PseudoClass_Last }, + { "left", PseudoClass_Left }, + { "maximized", PseudoClass_Maximized }, + { "middle", PseudoClass_Middle }, + { "minimized", PseudoClass_Minimized }, + { "movable", PseudoClass_Movable }, + { "next-selected", PseudoClass_NextSelected }, + { "no-frame", PseudoClass_Frameless }, + { "non-exclusive", PseudoClass_NonExclusive }, + { "off", PseudoClass_Unchecked }, + { "on", PseudoClass_Checked }, + { "only-one", PseudoClass_OnlyOne }, + { "open", PseudoClass_Open }, + { "pressed", PseudoClass_Pressed }, + { "previous-selected", PseudoClass_PreviousSelected }, + { "read-only", PseudoClass_ReadOnly }, + { "right", PseudoClass_Right }, + { "selected", PseudoClass_Selected }, + { "top", PseudoClass_Top }, + { "unchecked" , PseudoClass_Unchecked }, + { "vertical", PseudoClass_Vertical }, + { "window", PseudoClass_Window } +}; + +static const QCssKnownValue origins[NumKnownOrigins - 1] = { + { "border", Origin_Border }, + { "content", Origin_Content }, + { "margin", Origin_Margin }, // not in css + { "padding", Origin_Padding } +}; + +static const QCssKnownValue repeats[NumKnownRepeats - 1] = { + { "no-repeat", Repeat_None }, + { "repeat-x", Repeat_X }, + { "repeat-xy", Repeat_XY }, + { "repeat-y", Repeat_Y } +}; + +static const QCssKnownValue tileModes[NumKnownTileModes - 1] = { + { "repeat", TileMode_Repeat }, + { "round", TileMode_Round }, + { "stretch", TileMode_Stretch }, +}; + +static const QCssKnownValue positions[NumKnownPositionModes - 1] = { + { "absolute", PositionMode_Absolute }, + { "fixed", PositionMode_Fixed }, + { "relative", PositionMode_Relative }, + { "static", PositionMode_Static } +}; + +static const QCssKnownValue attachments[NumKnownAttachments - 1] = { + { "fixed", Attachment_Fixed }, + { "scroll", Attachment_Scroll } +}; + +static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] = { + { "background-color", StyleFeature_BackgroundColor }, + { "background-gradient", StyleFeature_BackgroundGradient }, + { "none", StyleFeature_None } +}; + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &name, const QCssKnownValue &prop) +{ + return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue &prop, const QString &name) +{ + return QString::compare(QLatin1String(prop.name), name, Qt::CaseInsensitive) < 0; +} + +static quint64 findKnownValue(const QString &name, const QCssKnownValue *start, int numValues) +{ + const QCssKnownValue *end = &start[numValues - 1]; + const QCssKnownValue *prop = qBinaryFind(start, end, name); + if (prop == end) + return 0; + return prop->id; +} + +/////////////////////////////////////////////////////////////////////////////// +// Value Extractor +ValueExtractor::ValueExtractor(const QVector<Declaration> &decls, const QPalette &pal) +: declarations(decls), adjustment(0), fontExtracted(false), pal(pal) +{ +} + +LengthData ValueExtractor::lengthValue(const Value& v) +{ + QString s = v.variant.toString(); + s.reserve(s.length()); + LengthData data; + data.unit = LengthData::None; + if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) + data.unit = LengthData::Px; + else if (s.endsWith(QLatin1String("ex"), Qt::CaseInsensitive)) + data.unit = LengthData::Ex; + else if (s.endsWith(QLatin1String("em"), Qt::CaseInsensitive)) + data.unit = LengthData::Em; + + if (data.unit != LengthData::None) + s.chop(2); + + data.number = s.toDouble(); + return data; +} + +static int lengthValueFromData(const LengthData& data, const QFont& f) +{ + if (data.unit == LengthData::Ex) + return qRound(QFontMetrics(f).xHeight() * data.number); + else if (data.unit == LengthData::Em) + return qRound(QFontMetrics(f).height() * data.number); + return qRound(data.number); +} + +int ValueExtractor::lengthValue(const Declaration &decl) +{ + if (decl.d->parsed.isValid()) + return lengthValueFromData(qvariant_cast<LengthData>(decl.d->parsed), f); + if (decl.d->values.count() < 1) + return 0; + LengthData data = lengthValue(decl.d->values.at(0)); + decl.d->parsed = QVariant::fromValue<LengthData>(data); + return lengthValueFromData(data,f); +} + +void ValueExtractor::lengthValues(const Declaration &decl, int *m) +{ + if (decl.d->parsed.isValid()) { + QList<QVariant> v = decl.d->parsed.toList(); + for (int i = 0; i < 4; i++) + m[i] = lengthValueFromData(qvariant_cast<LengthData>(v.at(i)), f); + return; + } + + LengthData datas[4]; + int i; + for (i = 0; i < qMin(decl.d->values.count(), 4); i++) + datas[i] = lengthValue(decl.d->values[i]); + + if (i == 0) { + LengthData zero = {0.0, LengthData::None}; + datas[0] = datas[1] = datas[2] = datas[3] = zero; + } else if (i == 1) { + datas[3] = datas[2] = datas[1] = datas[0]; + } else if (i == 2) { + datas[2] = datas[0]; + datas[3] = datas[1]; + } else if (i == 3) { + datas[3] = datas[1]; + } + + QList<QVariant> v; + for (i = 0; i < 4; i++) { + v += QVariant::fromValue<LengthData>(datas[i]); + m[i] = lengthValueFromData(datas[i], f); + } + decl.d->parsed = v; +} + +bool ValueExtractor::extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Width: *w = lengthValue(decl); break; + case Height: *h = lengthValue(decl); break; + case MinimumWidth: *minw = lengthValue(decl); break; + case MinimumHeight: *minh = lengthValue(decl); break; + case MaximumWidth: *maxw = lengthValue(decl); break; + case MaximumHeight: *maxh = lengthValue(decl); break; + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractPosition(int *left, int *top, int *right, int *bottom, QCss::Origin *origin, + Qt::Alignment *position, QCss::PositionMode *mode, Qt::Alignment *textAlignment) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Left: *left = lengthValue(decl); break; + case Top: *top = lengthValue(decl); break; + case Right: *right = lengthValue(decl); break; + case Bottom: *bottom = lengthValue(decl); break; + case QtOrigin: *origin = decl.originValue(); break; + case QtPosition: *position = decl.alignmentValue(); break; + case TextAlignment: *textAlignment = decl.alignmentValue(); break; + case Position: *mode = decl.positionValue(); break; + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractBox(int *margins, int *paddings, int *spacing) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case PaddingLeft: paddings[LeftEdge] = lengthValue(decl); break; + case PaddingRight: paddings[RightEdge] = lengthValue(decl); break; + case PaddingTop: paddings[TopEdge] = lengthValue(decl); break; + case PaddingBottom: paddings[BottomEdge] = lengthValue(decl); break; + case Padding: lengthValues(decl, paddings); break; + + case MarginLeft: margins[LeftEdge] = lengthValue(decl); break; + case MarginRight: margins[RightEdge] = lengthValue(decl); break; + case MarginTop: margins[TopEdge] = lengthValue(decl); break; + case MarginBottom: margins[BottomEdge] = lengthValue(decl); break; + case Margin: lengthValues(decl, margins); break; + case QtSpacing: if (spacing) *spacing = lengthValue(decl); break; + + default: continue; + } + hit = true; + } + + return hit; +} + +int ValueExtractor::extractStyleFeatures() +{ + int features = StyleFeature_None; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + if (decl.d->propertyId == QtStyleFeatures) + features = decl.styleFeaturesValue(); + } + return features; +} + +QSize ValueExtractor::sizeValue(const Declaration &decl) +{ + if (decl.d->parsed.isValid()) { + QList<QVariant> v = decl.d->parsed.toList(); + return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f), + lengthValueFromData(qvariant_cast<LengthData>(v.at(1)), f)); + } + + LengthData x[2] = { {0, LengthData::None }, {0, LengthData::None} }; + if (decl.d->values.count() > 0) + x[0] = lengthValue(decl.d->values.at(0)); + if (decl.d->values.count() > 1) + x[1] = lengthValue(decl.d->values.at(1)); + else + x[1] = x[0]; + QList<QVariant> v; + v << QVariant::fromValue<LengthData>(x[0]) << qVariantFromValue<LengthData>(x[1]); + decl.d->parsed = v; + return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f)); +} + +void ValueExtractor::sizeValues(const Declaration &decl, QSize *radii) +{ + radii[0] = sizeValue(decl); + for (int i = 1; i < 4; i++) + radii[i] = radii[0]; +} + +bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *styles, + QSize *radii) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case BorderLeftWidth: borders[LeftEdge] = lengthValue(decl); break; + case BorderRightWidth: borders[RightEdge] = lengthValue(decl); break; + case BorderTopWidth: borders[TopEdge] = lengthValue(decl); break; + case BorderBottomWidth: borders[BottomEdge] = lengthValue(decl); break; + case BorderWidth: lengthValues(decl, borders); break; + + case BorderLeftColor: colors[LeftEdge] = decl.brushValue(pal); break; + case BorderRightColor: colors[RightEdge] = decl.brushValue(pal); break; + case BorderTopColor: colors[TopEdge] = decl.brushValue(pal); break; + case BorderBottomColor: colors[BottomEdge] = decl.brushValue(pal); break; + case BorderColor: decl.brushValues(colors, pal); break; + + case BorderTopStyle: styles[TopEdge] = decl.styleValue(); break; + case BorderBottomStyle: styles[BottomEdge] = decl.styleValue(); break; + case BorderLeftStyle: styles[LeftEdge] = decl.styleValue(); break; + case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break; + case BorderStyles: decl.styleValues(styles); break; + + case BorderTopLeftRadius: radii[0] = sizeValue(decl); break; + case BorderTopRightRadius: radii[1] = sizeValue(decl); break; + case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break; + case BorderBottomRightRadius: radii[3] = sizeValue(decl); break; + case BorderRadius: sizeValues(decl, radii); break; + + case BorderLeft: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + break; + case BorderTop: + borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]); + break; + case BorderRight: + borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]); + break; + case BorderBottom: + borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]); + break; + case Border: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; + styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; + colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; + break; + + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractOutline(int *borders, QBrush *colors, BorderStyle *styles, + QSize *radii, int *offsets) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case OutlineWidth: lengthValues(decl, borders); break; + case OutlineColor: decl.brushValues(colors, pal); break; + case OutlineStyle: decl.styleValues(styles); break; + + case OutlineTopLeftRadius: radii[0] = sizeValue(decl); break; + case OutlineTopRightRadius: radii[1] = sizeValue(decl); break; + case OutlineBottomLeftRadius: radii[2] = sizeValue(decl); break; + case OutlineBottomRightRadius: radii[3] = sizeValue(decl); break; + case OutlineRadius: sizeValues(decl, radii); break; + case OutlineOffset: lengthValues(decl, offsets); break; + + case Outline: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; + styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; + colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; + break; + + default: continue; + } + hit = true; + } + + return hit; +} + +static Qt::Alignment parseAlignment(const QCss::Value *values, int count) +{ + Qt::Alignment a[2] = { 0, 0 }; + for (int i = 0; i < qMin(2, count); i++) { + if (values[i].type != Value::KnownIdentifier) + break; + switch (values[i].variant.toInt()) { + case Value_Left: a[i] = Qt::AlignLeft; break; + case Value_Right: a[i] = Qt::AlignRight; break; + case Value_Top: a[i] = Qt::AlignTop; break; + case Value_Bottom: a[i] = Qt::AlignBottom; break; + case Value_Center: a[i] = Qt::AlignCenter; break; + default: break; + } + } + + if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter) + a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; + if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter) + a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; + return a[0] | a[1]; +} + +static ColorData parseColorValue(QCss::Value v) +{ + if (v.type == Value::Identifier || v.type == Value::String) { + v.variant.convert(QVariant::Color); + v.type = Value::Color; + } + + if (v.type == Value::Color) + return qvariant_cast<QColor>(v.variant); + + if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) + return QColor(Qt::transparent); + + if (v.type != Value::Function) + return ColorData(); + + QStringList lst = v.variant.toStringList(); + if (lst.count() != 2) + return ColorData(); + + if ((lst.at(0).compare(QLatin1String("palette"), Qt::CaseInsensitive)) == 0) { + int role = findKnownValue(lst.at(1).trimmed(), values, NumKnownValues); + if (role >= Value_FirstColorRole && role <= Value_LastColorRole) + return (QPalette::ColorRole)(role-Value_FirstColorRole); + + return ColorData(); + } + + bool rgb = lst.at(0).startsWith(QLatin1String("rgb")); + + Parser p(lst.at(1)); + if (!p.testExpr()) + return ColorData(); + + QVector<QCss::Value> colorDigits; + if (!p.parseExpr(&colorDigits)) + return ColorData(); + + for (int i = 0; i < qMin(colorDigits.count(), 7); i += 2) { + if (colorDigits.at(i).type == Value::Percentage) { + colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (255. / 100.); + colorDigits[i].type = Value::Number; + } else if (colorDigits.at(i).type != Value::Number) { + return ColorData(); + } + } + + int v1 = colorDigits.at(0).variant.toInt(); + int v2 = colorDigits.at(2).variant.toInt(); + int v3 = colorDigits.at(4).variant.toInt(); + int alpha = colorDigits.count() >= 7 ? colorDigits.at(6).variant.toInt() : 255; + + return rgb ? QColor::fromRgb(v1, v2, v3, alpha) + : QColor::fromHsv(v1, v2, v3, alpha); +} + +static QColor colorFromData(const ColorData& c, const QPalette &pal) +{ + if (c.type == ColorData::Color) { + return c.color; + } else if (c.type == ColorData::Role) { + return pal.color(c.role); + } + return QColor(); +} + +static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) +{ + ColorData c = parseColorValue(v); + if (c.type == ColorData::Color) { + return QBrush(c.color); + } else if (c.type == ColorData::Role) { + return c.role; + } + + if (v.type != Value::Function) + return BrushData(); + + QStringList lst = v.variant.toStringList(); + if (lst.count() != 2) + return BrushData(); + + QStringList gradFuncs; + gradFuncs << QLatin1String("qlineargradient") << QLatin1String("qradialgradient") << QLatin1String("qconicalgradient") << QLatin1String("qgradient"); + int gradType = -1; + + if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1) + return BrushData(); + + QHash<QString, qreal> vars; + QVector<QGradientStop> stops; + + int spread = -1; + QStringList spreads; + spreads << QLatin1String("pad") << QLatin1String("reflect") << QLatin1String("repeat"); + + bool dependsOnThePalette = false; + Parser parser(lst.at(1)); + while (parser.hasNext()) { + parser.skipSpace(); + if (!parser.test(IDENT)) + return BrushData(); + QString attr = parser.lexem(); + parser.skipSpace(); + if (!parser.test(COLON)) + return BrushData(); + parser.skipSpace(); + if (attr.compare(QLatin1String("stop"), Qt::CaseInsensitive) == 0) { + QCss::Value stop, color; + parser.next(); + if (!parser.parseTerm(&stop)) return BrushData(); + parser.skipSpace(); + parser.next(); + if (!parser.parseTerm(&color)) return BrushData(); + ColorData cd = parseColorValue(color); + if(cd.type == ColorData::Role) + dependsOnThePalette = true; + stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal))); + } else { + parser.next(); + QCss::Value value; + (void)parser.parseTerm(&value); + if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0) { + spread = spreads.indexOf(value.variant.toString()); + } else { + vars[attr] = value.variant.toReal(); + } + } + parser.skipSpace(); + (void)parser.test(COMMA); + } + + if (gradType == 0) { + QLinearGradient lg(vars.value(QLatin1String("x1")), vars.value(QLatin1String("y1")), + vars.value(QLatin1String("x2")), vars.value(QLatin1String("y2"))); + lg.setCoordinateMode(QGradient::ObjectBoundingMode); + lg.setStops(stops); + if (spread != -1) + lg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(lg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + if (gradType == 1) { + QRadialGradient rg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), + vars.value(QLatin1String("radius")), vars.value(QLatin1String("fx")), + vars.value(QLatin1String("fy"))); + rg.setCoordinateMode(QGradient::ObjectBoundingMode); + rg.setStops(stops); + if (spread != -1) + rg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(rg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + if (gradType == 2) { + QConicalGradient cg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), + vars.value(QLatin1String("angle"))); + cg.setCoordinateMode(QGradient::ObjectBoundingMode); + cg.setStops(stops); + if (spread != -1) + cg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(cg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + return BrushData(); +} + +static QBrush brushFromData(const BrushData& c, const QPalette &pal) +{ + if (c.type == BrushData::Role) { + return pal.color(c.role); + } else { + return c.brush; + } +} + +static BorderStyle parseStyleValue(QCss::Value v) +{ + if (v.type == Value::KnownIdentifier) { + switch (v.variant.toInt()) { + case Value_None: + return BorderStyle_None; + case Value_Dotted: + return BorderStyle_Dotted; + case Value_Dashed: + return BorderStyle_Dashed; + case Value_Solid: + return BorderStyle_Solid; + case Value_Double: + return BorderStyle_Double; + case Value_DotDash: + return BorderStyle_DotDash; + case Value_DotDotDash: + return BorderStyle_DotDotDash; + case Value_Groove: + return BorderStyle_Groove; + case Value_Ridge: + return BorderStyle_Ridge; + case Value_Inset: + return BorderStyle_Inset; + case Value_Outset: + return BorderStyle_Outset; + case Value_Native: + return BorderStyle_Native; + default: + break; + } + } + + return BorderStyle_Unknown; +} + +void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color) +{ + if (decl.d->parsed.isValid()) { + BorderData data = qvariant_cast<BorderData>(decl.d->parsed); + *width = lengthValueFromData(data.width, f); + *style = data.style; + *color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor()); + return; + } + + *width = 0; + *style = BorderStyle_None; + *color = QColor(); + + if (decl.d->values.isEmpty()) + return; + + BorderData data; + data.width.number = 0; + data.width.unit = LengthData::None; + data.style = BorderStyle_None; + + int i = 0; + if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number) { + data.width = lengthValue(decl.d->values.at(i)); + *width = lengthValueFromData(data.width, f); + if (++i >= decl.d->values.count()) { + decl.d->parsed = QVariant::fromValue<BorderData>(data); + return; + } + } + + data.style = parseStyleValue(decl.d->values.at(i)); + if (data.style != BorderStyle_Unknown) { + *style = data.style; + if (++i >= decl.d->values.count()) { + decl.d->parsed = QVariant::fromValue<BorderData>(data); + return; + } + } else { + data.style = BorderStyle_None; + } + + data.color = parseBrushValue(decl.d->values.at(i), pal); + *color = brushFromData(data.color, pal); + if (data.color.type != BrushData::DependsOnThePalette) + decl.d->parsed = QVariant::fromValue<BorderData>(data); +} + +static void parseShorthandBackgroundProperty(const QVector<QCss::Value> &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal) +{ + *brush = BrushData(); + *image = QString(); + *repeat = Repeat_XY; + *alignment = Qt::AlignTop | Qt::AlignLeft; + + for (int i = 0; i < values.count(); ++i) { + const QCss::Value &v = values.at(i); + if (v.type == Value::Uri) { + *image = v.variant.toString(); + continue; + } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None) { + *image = QString(); + continue; + } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) { + *brush = QBrush(Qt::transparent); + } + + Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(v.variant.toString(), + repeats, NumKnownRepeats)); + if (repeatAttempt != Repeat_Unknown) { + *repeat = repeatAttempt; + continue; + } + + if (v.type == Value::KnownIdentifier) { + const int start = i; + int count = 1; + if (i < values.count() - 1 + && values.at(i + 1).type == Value::KnownIdentifier) { + ++i; + ++count; + } + Qt::Alignment a = parseAlignment(values.constData() + start, count); + if (int(a) != 0) { + *alignment = a; + continue; + } + i -= count - 1; + } + + *brush = parseBrushValue(v, pal); + } +} + +bool ValueExtractor::extractBackground(QBrush *brush, QString *image, Repeat *repeat, + Qt::Alignment *alignment, Origin *origin, Attachment *attachment, + Origin *clip) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + if (decl.d->values.isEmpty()) + continue; + const QCss::Value &val = decl.d->values.at(0); + switch (decl.d->propertyId) { + case BackgroundColor: + *brush = decl.brushValue(); + break; + case BackgroundImage: + if (val.type == Value::Uri) + *image = val.variant.toString(); + break; + case BackgroundRepeat: + if (decl.d->parsed.isValid()) { + *repeat = static_cast<Repeat>(decl.d->parsed.toInt()); + } else { + *repeat = static_cast<Repeat>(findKnownValue(val.variant.toString(), + repeats, NumKnownRepeats)); + decl.d->parsed = *repeat; + } + break; + case BackgroundPosition: + *alignment = decl.alignmentValue(); + break; + case BackgroundOrigin: + *origin = decl.originValue(); + break; + case BackgroundClip: + *clip = decl.originValue(); + break; + case Background: + if (decl.d->parsed.isValid()) { + BackgroundData data = qvariant_cast<BackgroundData>(decl.d->parsed); + *brush = brushFromData(data.brush, pal); + *image = data.image; + *repeat = data.repeat; + *alignment = data.alignment; + } else { + BrushData brushData; + parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal); + *brush = brushFromData(brushData, pal); + if (brushData.type != BrushData::DependsOnThePalette) { + BackgroundData data = { brushData, *image, *repeat, *alignment }; + decl.d->parsed = QVariant::fromValue<BackgroundData>(data); + } + } + break; + case BackgroundAttachment: + *attachment = decl.attachmentValue(); + break; + default: continue; + } + hit = true; + } + return hit; +} + +static bool setFontSizeFromValue(QCss::Value value, QFont *font, int *fontSizeAdjustment) +{ + if (value.type == Value::KnownIdentifier) { + bool valid = true; + switch (value.variant.toInt()) { + case Value_Small: *fontSizeAdjustment = -1; break; + case Value_Medium: *fontSizeAdjustment = 0; break; + case Value_Large: *fontSizeAdjustment = 1; break; + case Value_XLarge: *fontSizeAdjustment = 2; break; + case Value_XXLarge: *fontSizeAdjustment = 3; break; + default: valid = false; break; + } + return valid; + } + if (value.type != Value::Length) + return false; + + bool valid = false; + QString s = value.variant.toString(); + if (s.endsWith(QLatin1String("pt"), Qt::CaseInsensitive)) { + s.chop(2); + value.variant = s; + if (value.variant.convert((QVariant::Type)qMetaTypeId<qreal>())) { + font->setPointSizeF(value.variant.toReal()); + valid = true; + } + } else if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) { + s.chop(2); + value.variant = s; + if (value.variant.convert(QVariant::Int)) { + font->setPixelSize(value.variant.toInt()); + valid = true; + } + } + return valid; +} + +static bool setFontStyleFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type != Value::KnownIdentifier) + return false ; + switch (value.variant.toInt()) { + case Value_Normal: font->setStyle(QFont::StyleNormal); return true; + case Value_Italic: font->setStyle(QFont::StyleItalic); return true; + case Value_Oblique: font->setStyle(QFont::StyleOblique); return true; + default: break; + } + return false; +} + +static bool setFontWeightFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_Normal: font->setWeight(QFont::Normal); return true; + case Value_Bold: font->setWeight(QFont::Bold); return true; + default: break; + } + return false; + } + if (value.type != Value::Number) + return false; + font->setWeight(qMin(value.variant.toInt() / 8, 99)); + return true; +} + +/** \internal + * parse the font family from the values (starting from index \a start) + * and set it the \a font + * \returns true if a family was extracted. + */ +static bool setFontFamilyFromValues(const QVector<QCss::Value> &values, QFont *font, int start = 0) +{ + QString family; + bool shouldAddSpace = false; + for (int i = start; i < values.count(); ++i) { + const QCss::Value &v = values.at(i); + if (v.type == Value::TermOperatorComma) { + family += QLatin1Char(','); + shouldAddSpace = false; + continue; + } + const QString str = v.variant.toString(); + if (str.isEmpty()) + break; + if (shouldAddSpace) + family += QLatin1Char(' '); + family += str; + shouldAddSpace = true; + } + if (family.isEmpty()) + return false; + font->setFamily(family); + return true; +} + +static void setTextDecorationFromValues(const QVector<QCss::Value> &values, QFont *font) +{ + for (int i = 0; i < values.count(); ++i) { + if (values.at(i).type != Value::KnownIdentifier) + continue; + switch (values.at(i).variant.toInt()) { + case Value_Underline: font->setUnderline(true); break; + case Value_Overline: font->setOverline(true); break; + case Value_LineThrough: font->setStrikeOut(true); break; + case Value_None: + font->setUnderline(false); + font->setOverline(false); + font->setStrikeOut(false); + break; + default: break; + } + } +} + +static void parseShorthandFontProperty(const QVector<QCss::Value> &values, QFont *font, int *fontSizeAdjustment) +{ + font->setStyle(QFont::StyleNormal); + font->setWeight(QFont::Normal); + *fontSizeAdjustment = -255; + + int i = 0; + while (i < values.count()) { + if (setFontStyleFromValue(values.at(i), font) + || setFontWeightFromValue(values.at(i), font)) + ++i; + else + break; + } + + if (i < values.count()) { + setFontSizeFromValue(values.at(i), font, fontSizeAdjustment); + ++i; + } + + if (i < values.count()) { + setFontFamilyFromValues(values, font, i); + } +} + +static void setFontVariantFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_Normal: font->setCapitalization(QFont::MixedCase); break; + case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break; + default: break; + } + } +} + +static void setTextTransformFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_None: font->setCapitalization(QFont::MixedCase); break; + case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break; + case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break; + default: break; + } + } +} + +bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment) +{ + if (fontExtracted) { + *font = f; + *fontSizeAdjustment = adjustment; + return fontExtracted == 1; + } + + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + if (decl.d->values.isEmpty()) + continue; + const QCss::Value &val = decl.d->values.at(0); + switch (decl.d->propertyId) { + case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break; + case FontStyle: setFontStyleFromValue(val, font); break; + case FontWeight: setFontWeightFromValue(val, font); break; + case FontFamily: setFontFamilyFromValues(decl.d->values, font); break; + case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break; + case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break; + case FontVariant: setFontVariantFromValue(val, font); break; + case TextTransform: setTextTransformFromValue(val, font); break; + default: continue; + } + hit = true; + } + + f = *font; + adjustment = *fontSizeAdjustment; + fontExtracted = hit ? 1 : 2; + return hit; +} + +bool ValueExtractor::extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Color: *fg = decl.brushValue(pal); break; + case QtSelectionForeground: *sfg = decl.brushValue(pal); break; + case QtSelectionBackground: *sbg = decl.brushValue(pal); break; + case QtAlternateBackground: *abg = decl.brushValue(pal); break; + default: continue; + } + hit = true; + } + return hit; +} + +void ValueExtractor::extractFont() +{ + if (fontExtracted) + return; + int dummy = -255; + extractFont(&f, &dummy); +} + +bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case QtImage: + *icon = decl.iconValue(); + if (decl.d->values.count() > 0 && decl.d->values.at(0).type == Value::Uri) { + // try to pull just the size from the image... + QImageReader imageReader(decl.d->values.at(0).variant.toString()); + if ((*size = imageReader.size()).isNull()) { + // but we'll have to load the whole image if the + // format doesn't support just reading the size + *size = imageReader.read().size(); + } + } + break; + case QtImageAlignment: *a = decl.alignmentValue(); break; + default: continue; + } + hit = true; + } + return hit; +} + +/////////////////////////////////////////////////////////////////////////////// +// Declaration +QColor Declaration::colorValue(const QPalette &pal) const +{ + if (d->values.count() != 1) + return QColor(); + + if (d->parsed.isValid()) { + if (d->parsed.type() == QVariant::Color) + return qvariant_cast<QColor>(d->parsed); + if (d->parsed.type() == QVariant::Int) + return pal.color((QPalette::ColorRole)(d->parsed.toInt())); + } + + ColorData color = parseColorValue(d->values.at(0)); + if(color.type == ColorData::Role) { + d->parsed = QVariant::fromValue<int>(color.role); + return pal.color((QPalette::ColorRole)(color.role)); + } else { + d->parsed = QVariant::fromValue<QColor>(color.color); + return color.color; + } +} + +QBrush Declaration::brushValue(const QPalette &pal) const +{ + if (d->values.count() != 1) + return QBrush(); + + if (d->parsed.isValid()) { + if (d->parsed.type() == QVariant::Brush) + return qvariant_cast<QBrush>(d->parsed); + if (d->parsed.type() == QVariant::Int) + return pal.color((QPalette::ColorRole)(d->parsed.toInt())); + } + + BrushData data = parseBrushValue(d->values.at(0), pal); + + if(data.type == BrushData::Role) { + d->parsed = QVariant::fromValue<int>(data.role); + return pal.color((QPalette::ColorRole)(data.role)); + } else { + if (data.type != BrushData::DependsOnThePalette) + d->parsed = QVariant::fromValue<QBrush>(data.brush); + return data.brush; + } +} + +void Declaration::brushValues(QBrush *c, const QPalette &pal) const +{ + int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value. + // the bit 4 say we need to update d->parsed + int i = 0; + if (d->parsed.isValid()) { + needParse = 0; + QList<QVariant> v = d->parsed.toList(); + for (i = 0; i < qMin(v.count(), 4); i++) { + if (v.at(i).type() == QVariant::Brush) { + c[i] = qvariant_cast<QBrush>(v.at(i)); + } else if (v.at(i).type() == QVariant::Int) { + c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); + } else { + needParse |= (1<<i); + } + } + } + if (needParse != 0) { + QList<QVariant> v; + for (i = 0; i < qMin(d->values.count(), 4); i++) { + if (!(needParse & (1<<i))) + continue; + BrushData data = parseBrushValue(d->values.at(i), pal); + if(data.type == BrushData::Role) { + v += QVariant::fromValue<int>(data.role); + c[i] = pal.color((QPalette::ColorRole)(data.role)); + } else { + if (data.type != BrushData::DependsOnThePalette) { + v += QVariant::fromValue<QBrush>(data.brush); + } else { + v += QVariant(); + } + c[i] = data.brush; + } + } + if (needParse & 0x10) + d->parsed = v; + } + if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush(); + else if (i == 1) c[3] = c[2] = c[1] = c[0]; + else if (i == 2) c[2] = c[0], c[3] = c[1]; + else if (i == 3) c[3] = c[1]; +} + +bool Declaration::realValue(qreal *real, const char *unit) const +{ + if (d->values.count() != 1) + return false; + const Value &v = d->values.at(0); + if (unit && v.type != Value::Length) + return false; + QString s = v.variant.toString(); + if (unit) { + if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) + return false; + s.chop(qstrlen(unit)); + } + bool ok = false; + qreal val = s.toDouble(&ok); + if (ok) + *real = val; + return ok; +} + +static bool intValueHelper(const QCss::Value &v, int *i, const char *unit) +{ + if (unit && v.type != Value::Length) + return false; + QString s = v.variant.toString(); + if (unit) { + if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) + return false; + s.chop(qstrlen(unit)); + } + bool ok = false; + int val = s.toInt(&ok); + if (ok) + *i = val; + return ok; +} + +bool Declaration::intValue(int *i, const char *unit) const +{ + if (d->values.count() != 1) + return false; + return intValueHelper(d->values.at(0), i, unit); +} + +QSize Declaration::sizeValue() const +{ + if (d->parsed.isValid()) + return qvariant_cast<QSize>(d->parsed); + + int x[2] = { 0, 0 }; + if (d->values.count() > 0) + intValueHelper(d->values.at(0), &x[0], "px"); + if (d->values.count() > 1) + intValueHelper(d->values.at(1), &x[1], "px"); + else + x[1] = x[0]; + QSize size(x[0], x[1]); + d->parsed = QVariant::fromValue<QSize>(size); + return size; +} + +QRect Declaration::rectValue() const +{ + if (d->values.count() != 1) + return QRect(); + + if (d->parsed.isValid()) + return qvariant_cast<QRect>(d->parsed); + + const QCss::Value &v = d->values.at(0); + if (v.type != Value::Function) + return QRect(); + QStringList func = v.variant.toStringList(); + if (func.count() != 2 || func.at(0).compare(QLatin1String("rect")) != 0) + return QRect(); + QStringList args = func[1].split(QLatin1Char(' '), QString::SkipEmptyParts); + if (args.count() != 4) + return QRect(); + QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt()); + d->parsed = QVariant::fromValue<QRect>(rect); + return rect; +} + +void Declaration::colorValues(QColor *c, const QPalette &pal) const +{ + int i; + if (d->parsed.isValid()) { + QList<QVariant> v = d->parsed.toList(); + for (i = 0; i < qMin(d->values.count(), 4); i++) { + if (v.at(i).type() == QVariant::Color) { + c[i] = qvariant_cast<QColor>(v.at(i)); + } else { + c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); + } + } + } else { + QList<QVariant> v; + for (i = 0; i < qMin(d->values.count(), 4); i++) { + ColorData color = parseColorValue(d->values.at(i)); + if(color.type == ColorData::Role) { + v += QVariant::fromValue<int>(color.role); + c[i] = pal.color((QPalette::ColorRole)(color.role)); + } else { + v += QVariant::fromValue<QColor>(color.color); + c[i] = color.color; + } + } + d->parsed = v; + } + + if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor(); + else if (i == 1) c[3] = c[2] = c[1] = c[0]; + else if (i == 2) c[2] = c[0], c[3] = c[1]; + else if (i == 3) c[3] = c[1]; +} + +BorderStyle Declaration::styleValue() const +{ + if (d->values.count() != 1) + return BorderStyle_None; + return parseStyleValue(d->values.at(0)); +} + +void Declaration::styleValues(BorderStyle *s) const +{ + int i; + for (i = 0; i < qMin(d->values.count(), 4); i++) + s[i] = parseStyleValue(d->values.at(i)); + if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None; + else if (i == 1) s[3] = s[2] = s[1] = s[0]; + else if (i == 2) s[2] = s[0], s[3] = s[1]; + else if (i == 3) s[3] = s[1]; +} + +Repeat Declaration::repeatValue() const +{ + if (d->parsed.isValid()) + return static_cast<Repeat>(d->parsed.toInt()); + if (d->values.count() != 1) + return Repeat_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + repeats, NumKnownRepeats); + d->parsed = v; + return static_cast<Repeat>(v); +} + +Origin Declaration::originValue() const +{ + if (d->parsed.isValid()) + return static_cast<Origin>(d->parsed.toInt()); + if (d->values.count() != 1) + return Origin_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + origins, NumKnownOrigins); + d->parsed = v; + return static_cast<Origin>(v); +} + +PositionMode Declaration::positionValue() const +{ + if (d->parsed.isValid()) + return static_cast<PositionMode>(d->parsed.toInt()); + if (d->values.count() != 1) + return PositionMode_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + positions, NumKnownPositionModes); + d->parsed = v; + return static_cast<PositionMode>(v); +} + +Attachment Declaration::attachmentValue() const +{ + if (d->parsed.isValid()) + return static_cast<Attachment>(d->parsed.toInt()); + if (d->values.count() != 1) + return Attachment_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + attachments, NumKnownAttachments); + d->parsed = v; + return static_cast<Attachment>(v); +} + +int Declaration::styleFeaturesValue() const +{ + Q_ASSERT(d->propertyId == QtStyleFeatures); + if (d->parsed.isValid()) + return d->parsed.toInt(); + int features = StyleFeature_None; + for (int i = 0; i < d->values.count(); i++) { + features |= static_cast<int>(findKnownValue(d->values.value(i).variant.toString(), + styleFeatures, NumKnownStyleFeatures)); + } + d->parsed = features; + return features; +} + +QString Declaration::uriValue() const +{ + if (d->values.isEmpty() || d->values.at(0).type != Value::Uri) + return QString(); + return d->values.at(0).variant.toString(); +} + +Qt::Alignment Declaration::alignmentValue() const +{ + if (d->parsed.isValid()) + return Qt::Alignment(d->parsed.toInt()); + if (d->values.isEmpty() || d->values.count() > 2) + return Qt::AlignLeft | Qt::AlignTop; + + Qt::Alignment v = parseAlignment(d->values.constData(), d->values.count()); + d->parsed = int(v); + return v; +} + +void Declaration::borderImageValue(QString *image, int *cuts, + TileMode *h, TileMode *v) const +{ + *image = uriValue(); + for (int i = 0; i < 4; i++) + cuts[i] = -1; + *h = *v = TileMode_Stretch; + + if (d->values.count() < 2) + return; + + if (d->values.at(1).type == Value::Number) { // cuts! + int i; + for (i = 0; i < qMin(d->values.count()-1, 4); i++) { + const Value& v = d->values.at(i+1); + if (v.type != Value::Number) + break; + cuts[i] = v.variant.toString().toInt(); + } + if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0; + else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0]; + else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1]; + else if (i == 3) cuts[3] = cuts[1]; + } + + if (d->values.last().type == Value::Identifier) { + *v = static_cast<TileMode>(findKnownValue(d->values.last().variant.toString(), + tileModes, NumKnownTileModes)); + } + if (d->values[d->values.count() - 2].type == Value::Identifier) { + *h = static_cast<TileMode> + (findKnownValue(d->values[d->values.count()-2].variant.toString(), + tileModes, NumKnownTileModes)); + } else + *h = *v; +} + +QIcon Declaration::iconValue() const +{ + if (d->parsed.isValid()) + return qvariant_cast<QIcon>(d->parsed); + + QIcon icon; + for (int i = 0; i < d->values.count();) { + const Value &value = d->values.at(i++); + if (value.type != Value::Uri) + break; + QString uri = value.variant.toString(); + QIcon::Mode mode = QIcon::Normal; + QIcon::State state = QIcon::Off; + for (int j = 0; j < 2; j++) { + if (i != d->values.count() && d->values.at(i).type == Value::KnownIdentifier) { + switch (d->values.at(i).variant.toInt()) { + case Value_Disabled: mode = QIcon::Disabled; break; + case Value_Active: mode = QIcon::Active; break; + case Value_Selected: mode = QIcon::Selected; break; + case Value_Normal: mode = QIcon::Normal; break; + case Value_On: state = QIcon::On; break; + case Value_Off: state = QIcon::Off; break; + default: break; + } + ++i; + } else { + break; + } + } + + // QIcon is soo broken + if (icon.isNull()) + icon = QIcon(uri); + else + icon.addPixmap(uri, mode, state); + + if (i == d->values.count()) + break; + + if (d->values.at(i).type == Value::TermOperatorComma) + i++; + } + + d->parsed = QVariant::fromValue<QIcon>(icon); + return icon; +} + +/////////////////////////////////////////////////////////////////////////////// +// Selector +int Selector::specificity() const +{ + int val = 0; + for (int i = 0; i < basicSelectors.count(); ++i) { + const BasicSelector &sel = basicSelectors.at(i); + if (!sel.elementName.isEmpty()) + val += 1; + + val += (sel.pseudos.count() + sel.attributeSelectors.count()) * 0x10; + val += sel.ids.count() * 0x100; + } + return val; +} + +QString Selector::pseudoElement() const +{ + const BasicSelector& bs = basicSelectors.last(); + if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown) + return bs.pseudos.at(0).name; + return QString(); +} + +quint64 Selector::pseudoClass(quint64 *negated) const +{ + const BasicSelector& bs = basicSelectors.last(); + if (bs.pseudos.isEmpty()) + return PseudoClass_Unspecified; + quint64 pc = PseudoClass_Unknown; + for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.count(); i++) { + const Pseudo &pseudo = bs.pseudos.at(i); + if (pseudo.type == PseudoClass_Unknown) + return PseudoClass_Unknown; + if (!pseudo.negated) + pc |= pseudo.type; + else if (negated) + *negated |= pseudo.type; + } + return pc; +} + +/////////////////////////////////////////////////////////////////////////////// +// StyleSheet +void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity) +{ + QVector<StyleRule> universals; + for (int i = 0; i < styleRules.count(); ++i) { + const StyleRule &rule = styleRules.at(i); + QVector<Selector> universalsSelectors; + for (int j = 0; j < rule.selectors.count(); ++j) { + const Selector& selector = rule.selectors.at(j); + + if (selector.basicSelectors.isEmpty()) + continue; + + if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { + if (selector.basicSelectors.count() != 1) + continue; + } else if (selector.basicSelectors.count() <= 1) { + continue; + } + + const BasicSelector &sel = selector.basicSelectors.at(selector.basicSelectors.count() - 1); + + if (!sel.ids.isEmpty()) { + StyleRule nr; + nr.selectors += selector; + nr.declarations = rule.declarations; + nr.order = i; + idIndex.insert(sel.ids.at(0), nr); + } else if (!sel.elementName.isEmpty()) { + StyleRule nr; + nr.selectors += selector; + nr.declarations = rule.declarations; + nr.order = i; + QString name = sel.elementName; + if (nameCaseSensitivity == Qt::CaseInsensitive) + name=name.toLower(); + nameIndex.insert(name, nr); + } else { + universalsSelectors += selector; + } + } + if (!universalsSelectors.isEmpty()) { + StyleRule nr; + nr.selectors = universalsSelectors; + nr.declarations = rule.declarations; + nr.order = i; + universals << nr; + } + } + styleRules = universals; +} + +/////////////////////////////////////////////////////////////////////////////// +// StyleSelector +StyleSelector::~StyleSelector() +{ +} + +bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const +{ + return nodeNames(node).contains(nodeName, nameCaseSensitivity); +} + +QStringList StyleSelector::nodeIds(NodePtr node) const +{ + return QStringList(attribute(node, QLatin1String("id"))); +} + +bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node) +{ + if (selector.basicSelectors.isEmpty()) + return false; + + if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { + if (selector.basicSelectors.count() != 1) + return false; + return basicSelectorMatches(selector.basicSelectors.at(0), node); + } + if (selector.basicSelectors.count() <= 1) + return false; + + int i = selector.basicSelectors.count() - 1; + node = duplicateNode(node); + bool match = true; + + BasicSelector sel = selector.basicSelectors.at(i); + do { + match = basicSelectorMatches(sel, node); + if (!match) { + if (sel.relationToNext == BasicSelector::MatchNextSelectorIfParent + || i == selector.basicSelectors.count() - 1) // first element must always match! + break; + } + + if (match || sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor) + --i; + + if (i < 0) + break; + + sel = selector.basicSelectors.at(i); + if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor + || sel.relationToNext == BasicSelector::MatchNextSelectorIfParent) { + + NodePtr nextParent = parentNode(node); + freeNode(node); + node = nextParent; + } else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfPreceeds) { + NodePtr previousSibling = previousSiblingNode(node); + freeNode(node); + node = previousSibling; + } + if (isNullNode(node)) { + match = false; + break; + } + } while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor)); + + freeNode(node); + + return match; +} + +bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node) +{ + if (!sel.attributeSelectors.isEmpty()) { + if (!hasAttributes(node)) + return false; + + for (int i = 0; i < sel.attributeSelectors.count(); ++i) { + const QCss::AttributeSelector &a = sel.attributeSelectors.at(i); + + const QString attrValue = attribute(node, a.name); + if (attrValue.isNull()) + return false; + + if (a.valueMatchCriterium == QCss::AttributeSelector::MatchContains) { + + QStringList lst = attrValue.split(QLatin1Char(' ')); + if (!lst.contains(a.value)) + return false; + } else if ( + (a.valueMatchCriterium == QCss::AttributeSelector::MatchEqual + && attrValue != a.value) + || + (a.valueMatchCriterium == QCss::AttributeSelector::MatchBeginsWith + && !attrValue.startsWith(a.value)) + ) + return false; + } + } + + if (!sel.elementName.isEmpty() + && !nodeNameEquals(node, sel.elementName)) + return false; + + if (!sel.ids.isEmpty() + && sel.ids != nodeIds(node)) + return false; + + return true; +} + +void StyleSelector::matchRule(NodePtr node, const StyleRule &rule, StyleSheetOrigin origin, + int depth, QMap<uint, StyleRule> *weightedRules) +{ + for (int j = 0; j < rule.selectors.count(); ++j) { + const Selector& selector = rule.selectors.at(j); + if (selectorMatches(selector, node)) { + uint weight = rule.order + + selector.specificity() *0x100 + + (uint(origin) + depth)*0x100000; + StyleRule newRule = rule; + if(rule.selectors.count() > 1) { + newRule.selectors.resize(1); + newRule.selectors[0] = selector; + } + //We might have rules with the same weight if they came from a rule with several selectors + weightedRules->insertMulti(weight, newRule); + } + } +} + +// Returns style rules that are in ascending order of specificity +// Each of the StyleRule returned will contain exactly one Selector +QVector<StyleRule> StyleSelector::styleRulesForNode(NodePtr node) +{ + QVector<StyleRule> rules; + if (styleSheets.isEmpty()) + return rules; + + QMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below + + //prune using indexed stylesheet + for (int sheetIdx = 0; sheetIdx < styleSheets.count(); ++sheetIdx) { + const StyleSheet &styleSheet = styleSheets.at(sheetIdx); + for (int i = 0; i < styleSheet.styleRules.count(); ++i) { + matchRule(node, styleSheet.styleRules.at(i), styleSheet.origin, styleSheet.depth, &weightedRules); + } + + if (!styleSheet.idIndex.isEmpty()) { + QStringList ids = nodeIds(node); + for (int i = 0; i < ids.count(); i++) { + const QString &key = ids.at(i); + QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.idIndex.constFind(key); + while (it != styleSheet.idIndex.constEnd() && it.key() == key) { + matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules); + ++it; + } + } + } + if (!styleSheet.nameIndex.isEmpty()) { + QStringList names = nodeNames(node); + for (int i = 0; i < names.count(); i++) { + QString name = names.at(i); + if (nameCaseSensitivity == Qt::CaseInsensitive) + name = name.toLower(); + QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.nameIndex.constFind(name); + while (it != styleSheet.nameIndex.constEnd() && it.key() == name) { + matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules); + ++it; + } + } + } + if (!medium.isEmpty()) { + for (int i = 0; i < styleSheet.mediaRules.count(); ++i) { + if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive)) { + for (int j = 0; j < styleSheet.mediaRules.at(i).styleRules.count(); ++j) { + matchRule(node, styleSheet.mediaRules.at(i).styleRules.at(j), styleSheet.origin, + styleSheet.depth, &weightedRules); + } + } + } + } + } + + rules.reserve(weightedRules.count()); + QMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin(); + for ( ; it != weightedRules.constEnd() ; ++it) + rules += *it; + + return rules; +} + +// for qtexthtmlparser which requires just the declarations with Enabled state +// and without pseudo elements +QVector<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo) +{ + QVector<Declaration> decls; + QVector<StyleRule> rules = styleRulesForNode(node); + for (int i = 0; i < rules.count(); i++) { + const Selector& selector = rules.at(i).selectors.at(0); + const QString pseudoElement = selector.pseudoElement(); + + if (extraPseudo && pseudoElement == QLatin1String(extraPseudo)) { + decls += rules.at(i).declarations; + continue; + } + + if (!pseudoElement.isEmpty()) // skip rules with pseudo elements + continue; + quint64 pseudoClass = selector.pseudoClass(); + if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified) + decls += rules.at(i).declarations; + } + return decls; +} + +static inline bool isHexDigit(const char c) +{ + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F') + ; +} + +QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences) +{ + QString output = input; + + if (hasEscapeSequences) + *hasEscapeSequences = false; + + int i = 0; + while (i < output.size()) { + if (output.at(i) == QLatin1Char('\\')) { + + ++i; + // test for unicode hex escape + int hexCount = 0; + const int hexStart = i; + while (i < output.size() + && isHexDigit(output.at(i).toLatin1()) + && hexCount < 7) { + ++hexCount; + ++i; + } + if (hexCount == 0) { + if (hasEscapeSequences) + *hasEscapeSequences = true; + continue; + } + + hexCount = qMin(hexCount, 6); + bool ok = false; + ushort code = output.mid(hexStart, hexCount).toUShort(&ok, 16); + if (ok) { + output.replace(hexStart - 1, hexCount + 1, QChar(code)); + i = hexStart; + } else { + i = hexStart; + } + } else { + ++i; + } + } + return output; +} + +int QCssScanner_Generated::handleCommentStart() +{ + while (pos < input.size() - 1) { + if (input.at(pos) == QLatin1Char('*') + && input.at(pos + 1) == QLatin1Char('/')) { + pos += 2; + break; + } + ++pos; + } + return S; +} + +void Scanner::scan(const QString &preprocessedInput, QVector<Symbol> *symbols) +{ + QCssScanner_Generated scanner(preprocessedInput); + Symbol sym; + int tok = scanner.lex(); + while (tok != -1) { + sym.token = static_cast<QCss::TokenType>(tok); + sym.text = scanner.input; + sym.start = scanner.lexemStart; + sym.len = scanner.lexemLength; + symbols->append(sym); + tok = scanner.lex(); + } +} + +QString Symbol::lexem() const +{ + QString result; + if (len > 0) + result.reserve(len); + for (int i = 0; i < len; ++i) { + if (text.at(start + i) == QLatin1Char('\\') && i < len - 1) + ++i; + result += text.at(start + i); + } + return result; +} + +Parser::Parser(const QString &css, bool isFile) +{ + init(css, isFile); +} + +Parser::Parser() +{ + index = 0; + errorIndex = -1; + hasEscapeSequences = false; +} + +void Parser::init(const QString &css, bool isFile) +{ + QString styleSheet = css; + if (isFile) { + QFile file(css); + if (file.open(QFile::ReadOnly)) { + sourcePath = QFileInfo(styleSheet).absolutePath() + QLatin1Char('/'); + QTextStream stream(&file); + styleSheet = stream.readAll(); + } else { + qWarning() << "QCss::Parser - Failed to load file " << css; + styleSheet.clear(); + } + } else { + sourcePath.clear(); + } + + hasEscapeSequences = false; + symbols.resize(0); + symbols.reserve(8); + Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols); + index = 0; + errorIndex = -1; +} + +bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity) +{ + if (testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("charset"))) { + if (!next(STRING)) return false; + if (!next(SEMICOLON)) return false; + } + + while (test(S) || test(CDO) || test(CDC)) {} + + while (testImport()) { + ImportRule rule; + if (!parseImport(&rule)) return false; + styleSheet->importRules.append(rule); + while (test(S) || test(CDO) || test(CDC)) {} + } + + do { + if (testMedia()) { + MediaRule rule; + if (!parseMedia(&rule)) return false; + styleSheet->mediaRules.append(rule); + } else if (testPage()) { + PageRule rule; + if (!parsePage(&rule)) return false; + styleSheet->pageRules.append(rule); + } else if (testRuleset()) { + StyleRule rule; + if (!parseRuleset(&rule)) return false; + styleSheet->styleRules.append(rule); + } else if (test(ATKEYWORD_SYM)) { + if (!until(RBRACE)) return false; + } else if (hasNext()) { + return false; + } + while (test(S) || test(CDO) || test(CDC)) {} + } while (hasNext()); + styleSheet->buildIndexes(nameCaseSensitivity); + return true; +} + +Symbol Parser::errorSymbol() +{ + if (errorIndex == -1) return Symbol(); + return symbols.at(errorIndex); +} + +static inline void removeOptionalQuotes(QString *str) +{ + if (!str->startsWith(QLatin1Char('\'')) + && !str->startsWith(QLatin1Char('\"'))) + return; + str->remove(0, 1); + str->chop(1); +} + +bool Parser::parseImport(ImportRule *importRule) +{ + skipSpace(); + + if (test(STRING)) { + importRule->href = lexem(); + } else { + if (!testAndParseUri(&importRule->href)) return false; + } + removeOptionalQuotes(&importRule->href); + + skipSpace(); + + if (testMedium()) { + if (!parseMedium(&importRule->media)) return false; + + while (test(COMMA)) { + skipSpace(); + if (!parseNextMedium(&importRule->media)) return false; + } + } + + if (!next(SEMICOLON)) return false; + + skipSpace(); + return true; +} + +bool Parser::parseMedia(MediaRule *mediaRule) +{ + do { + skipSpace(); + if (!parseNextMedium(&mediaRule->media)) return false; + } while (test(COMMA)); + + if (!next(LBRACE)) return false; + skipSpace(); + + while (testRuleset()) { + StyleRule rule; + if (!parseRuleset(&rule)) return false; + mediaRule->styleRules.append(rule); + } + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parseMedium(QStringList *media) +{ + media->append(lexem()); + skipSpace(); + return true; +} + +bool Parser::parsePage(PageRule *pageRule) +{ + skipSpace(); + + if (testPseudoPage()) + if (!parsePseudoPage(&pageRule->selector)) return false; + + skipSpace(); + if (!next(LBRACE)) return false; + + do { + skipSpace(); + Declaration decl; + if (!parseNextDeclaration(&decl)) return false; + if (!decl.isEmpty()) + pageRule->declarations.append(decl); + } while (test(SEMICOLON)); + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parsePseudoPage(QString *selector) +{ + if (!next(IDENT)) return false; + *selector = lexem(); + return true; +} + +bool Parser::parseNextOperator(Value *value) +{ + if (!hasNext()) return true; + switch (next()) { + case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break; + case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break; + default: prev(); break; + } + return true; +} + +bool Parser::parseCombinator(BasicSelector::Relation *relation) +{ + *relation = BasicSelector::NoRelation; + if (lookup() == S) { + *relation = BasicSelector::MatchNextSelectorIfAncestor; + skipSpace(); + } else { + prev(); + } + if (test(PLUS)) { + *relation = BasicSelector::MatchNextSelectorIfPreceeds; + } else if (test(GREATER)) { + *relation = BasicSelector::MatchNextSelectorIfParent; + } + skipSpace(); + return true; +} + +bool Parser::parseProperty(Declaration *decl) +{ + decl->d->property = lexem(); + decl->d->propertyId = static_cast<Property>(findKnownValue(decl->d->property, properties, NumProperties)); + skipSpace(); + return true; +} + +bool Parser::parseRuleset(StyleRule *styleRule) +{ + Selector sel; + if (!parseSelector(&sel)) return false; + styleRule->selectors.append(sel); + + while (test(COMMA)) { + skipSpace(); + Selector sel; + if (!parseNextSelector(&sel)) return false; + styleRule->selectors.append(sel); + } + + skipSpace(); + if (!next(LBRACE)) return false; + const int declarationStart = index; + + do { + skipSpace(); + Declaration decl; + const int rewind = index; + if (!parseNextDeclaration(&decl)) { + index = rewind; + const bool foundSemicolon = until(SEMICOLON); + const int semicolonIndex = index; + + index = declarationStart; + const bool foundRBrace = until(RBRACE); + + if (foundSemicolon && semicolonIndex < index) { + decl = Declaration(); + index = semicolonIndex - 1; + } else { + skipSpace(); + return foundRBrace; + } + } + if (!decl.isEmpty()) + styleRule->declarations.append(decl); + } while (test(SEMICOLON)); + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parseSelector(Selector *sel) +{ + BasicSelector basicSel; + if (!parseSimpleSelector(&basicSel)) return false; + while (testCombinator()) { + if (!parseCombinator(&basicSel.relationToNext)) return false; + + if (!testSimpleSelector()) break; + sel->basicSelectors.append(basicSel); + + basicSel = BasicSelector(); + if (!parseSimpleSelector(&basicSel)) return false; + } + sel->basicSelectors.append(basicSel); + return true; +} + +bool Parser::parseSimpleSelector(BasicSelector *basicSel) +{ + int minCount = 0; + if (lookupElementName()) { + if (!parseElementName(&basicSel->elementName)) return false; + } else { + prev(); + minCount = 1; + } + bool onceMore; + int count = 0; + do { + onceMore = false; + if (test(HASH)) { + QString theid = lexem(); + // chop off leading # + theid.remove(0, 1); + basicSel->ids.append(theid); + onceMore = true; + } else if (testClass()) { + onceMore = true; + AttributeSelector a; + a.name = QLatin1String("class"); + a.valueMatchCriterium = AttributeSelector::MatchContains; + if (!parseClass(&a.value)) return false; + basicSel->attributeSelectors.append(a); + } else if (testAttrib()) { + onceMore = true; + AttributeSelector a; + if (!parseAttrib(&a)) return false; + basicSel->attributeSelectors.append(a); + } else if (testPseudo()) { + onceMore = true; + Pseudo ps; + if (!parsePseudo(&ps)) return false; + basicSel->pseudos.append(ps); + } + if (onceMore) ++count; + } while (onceMore); + return count >= minCount; +} + +bool Parser::parseClass(QString *name) +{ + if (!next(IDENT)) return false; + *name = lexem(); + return true; +} + +bool Parser::parseElementName(QString *name) +{ + switch (lookup()) { + case STAR: name->clear(); break; + case IDENT: *name = lexem(); break; + default: return false; + } + return true; +} + +bool Parser::parseAttrib(AttributeSelector *attr) +{ + skipSpace(); + if (!next(IDENT)) return false; + attr->name = lexem(); + skipSpace(); + + if (test(EQUAL)) { + attr->valueMatchCriterium = AttributeSelector::MatchEqual; + } else if (test(INCLUDES)) { + attr->valueMatchCriterium = AttributeSelector::MatchContains; + } else if (test(DASHMATCH)) { + attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith; + } else { + return next(RBRACKET); + } + + skipSpace(); + + if (!test(IDENT) && !test(STRING)) return false; + attr->value = unquotedLexem(); + + skipSpace(); + return next(RBRACKET); +} + +bool Parser::parsePseudo(Pseudo *pseudo) +{ + (void)test(COLON); + pseudo->negated = test(EXCLAMATION_SYM); + if (test(IDENT)) { + pseudo->name = lexem(); + pseudo->type = static_cast<quint64>(findKnownValue(pseudo->name, pseudos, NumPseudos)); + return true; + } + if (!next(FUNCTION)) return false; + pseudo->function = lexem(); + // chop off trailing parenthesis + pseudo->function.chop(1); + skipSpace(); + if (!test(IDENT)) return false; + pseudo->name = lexem(); + skipSpace(); + return next(RPAREN); +} + +bool Parser::parseNextDeclaration(Declaration *decl) +{ + if (!testProperty()) + return true; // not an error! + if (!parseProperty(decl)) return false; + if (!next(COLON)) return false; + skipSpace(); + if (!parseNextExpr(&decl->d->values)) return false; + if (testPrio()) + if (!parsePrio(decl)) return false; + return true; +} + +bool Parser::testPrio() +{ + const int rewind = index; + if (!test(EXCLAMATION_SYM)) return false; + skipSpace(); + if (!test(IDENT)) { + index = rewind; + return false; + } + if (lexem().compare(QLatin1String("important"), Qt::CaseInsensitive) != 0) { + index = rewind; + return false; + } + return true; +} + +bool Parser::parsePrio(Declaration *declaration) +{ + declaration->d->important = true; + skipSpace(); + return true; +} + +bool Parser::parseExpr(QVector<Value> *values) +{ + Value val; + if (!parseTerm(&val)) return false; + values->append(val); + bool onceMore; + do { + onceMore = false; + val = Value(); + if (!parseNextOperator(&val)) return false; + if (val.type != QCss::Value::Unknown) + values->append(val); + if (testTerm()) { + onceMore = true; + val = Value(); + if (!parseTerm(&val)) return false; + values->append(val); + } + } while (onceMore); + return true; +} + +bool Parser::testTerm() +{ + return test(PLUS) || test(MINUS) + || test(NUMBER) + || test(PERCENTAGE) + || test(LENGTH) + || test(STRING) + || test(IDENT) + || testHexColor() + || testFunction(); +} + +bool Parser::parseTerm(Value *value) +{ + QString str = lexem(); + bool haveUnary = false; + if (lookup() == PLUS || lookup() == MINUS) { + haveUnary = true; + if (!hasNext()) return false; + next(); + str += lexem(); + } + + value->variant = str; + value->type = QCss::Value::String; + switch (lookup()) { + case NUMBER: + value->type = Value::Number; + value->variant.convert(QVariant::Double); + break; + case PERCENTAGE: + value->type = Value::Percentage; + str.chop(1); // strip off % + value->variant = str; + break; + case LENGTH: + value->type = Value::Length; + break; + + case STRING: + if (haveUnary) return false; + value->type = Value::String; + str.chop(1); + str.remove(0, 1); + value->variant = str; + break; + case IDENT: { + if (haveUnary) return false; + value->type = Value::Identifier; + const int theid = findKnownValue(str, values, NumKnownValues); + if (theid != 0) { + value->type = Value::KnownIdentifier; + value->variant = theid; + } + break; + } + default: { + if (haveUnary) return false; + prev(); + if (testHexColor()) { + QColor col; + if (!parseHexColor(&col)) return false; + value->type = Value::Color; + value->variant = col; + } else if (testFunction()) { + QString name, args; + if (!parseFunction(&name, &args)) return false; + if (name == QLatin1String("url")) { + value->type = Value::Uri; + removeOptionalQuotes(&args); + if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) { + args.prepend(sourcePath); + } + value->variant = args; + } else { + value->type = Value::Function; + value->variant = QStringList() << name << args; + } + } else { + return recordError(); + } + return true; + } + } + skipSpace(); + return true; +} + +bool Parser::parseFunction(QString *name, QString *args) +{ + *name = lexem(); + name->chop(1); + skipSpace(); + const int start = index; + if (!until(RPAREN)) return false; + for (int i = start; i < index - 1; ++i) + args->append(symbols.at(i).lexem()); + /* + if (!nextExpr(&arguments)) return false; + if (!next(RPAREN)) return false; + */ + skipSpace(); + return true; +} + +bool Parser::parseHexColor(QColor *col) +{ + col->setNamedColor(lexem()); + if (!col->isValid()) { + qWarning("QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData()); + return false; + } + skipSpace(); + return true; +} + +bool Parser::testAndParseUri(QString *uri) +{ + const int rewind = index; + if (!testFunction()) return false; + + QString name, args; + if (!parseFunction(&name, &args)) { + index = rewind; + return false; + } + if (name.toLower() != QLatin1String("url")) { + index = rewind; + return false; + } + *uri = args; + removeOptionalQuotes(uri); + return true; +} + +bool Parser::testSimpleSelector() +{ + return testElementName() + || (test(HASH)) + || testClass() + || testAttrib() + || testPseudo(); +} + +bool Parser::next(QCss::TokenType t) +{ + if (hasNext() && next() == t) + return true; + return recordError(); +} + +bool Parser::test(QCss::TokenType t) +{ + if (index >= symbols.count()) + return false; + if (symbols.at(index).token == t) { + ++index; + return true; + } + return false; +} + +QString Parser::unquotedLexem() const +{ + QString s = lexem(); + if (lookup() == STRING) { + s.chop(1); + s.remove(0, 1); + } + return s; +} + +QString Parser::lexemUntil(QCss::TokenType t) +{ + QString lexem; + while (hasNext() && next() != t) + lexem += symbol().lexem(); + return lexem; +} + +bool Parser::until(QCss::TokenType target, QCss::TokenType target2) +{ + int braceCount = 0; + int brackCount = 0; + int parenCount = 0; + if (index) { + switch(symbols.at(index-1).token) { + case LBRACE: ++braceCount; break; + case LBRACKET: ++brackCount; break; + case FUNCTION: + case LPAREN: ++parenCount; break; + default: ; + } + } + while (index < symbols.size()) { + QCss::TokenType t = symbols.at(index++).token; + switch (t) { + case LBRACE: ++braceCount; break; + case RBRACE: --braceCount; break; + case LBRACKET: ++brackCount; break; + case RBRACKET: --brackCount; break; + case FUNCTION: + case LPAREN: ++parenCount; break; + case RPAREN: --parenCount; break; + default: break; + } + if ((t == target || (target2 != NONE && t == target2)) + && braceCount <= 0 + && brackCount <= 0 + && parenCount <= 0) + return true; + + if (braceCount < 0 || brackCount < 0 || parenCount < 0) { + --index; + break; + } + } + return false; +} + +bool Parser::testTokenAndEndsWith(QCss::TokenType t, const QLatin1String &str) +{ + if (!test(t)) return false; + if (!lexem().endsWith(str, Qt::CaseInsensitive)) { + prev(); + return false; + } + return true; +} + +QT_END_NAMESPACE +#endif // QT_NO_CSSPARSER diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h new file mode 100644 index 0000000000..86bafc9215 --- /dev/null +++ b/src/gui/text/qcssparser_p.h @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCSSPARSER_P_H +#define QCSSPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QStringList> +#include <QtCore/QVector> +#include <QtCore/QVariant> +#include <QtCore/QPair> +#include <QtCore/QSize> +#include <QtCore/QMultiHash> +#include <QtGui/QFont> +#include <QtGui/QPalette> +#include <QtGui/QIcon> +#include <QtCore/QSharedData> + + +#ifndef QT_NO_CSSPARSER + +// VxWorks defines NONE as (-1) "for times when NULL won't do" +#if defined(Q_OS_VXWORKS) && defined(NONE) +# undef NONE +#endif +#if defined(Q_OS_INTEGRITY) +# undef Value +#endif + +QT_BEGIN_NAMESPACE + +namespace QCss +{ + +enum Property { + UnknownProperty, + BackgroundColor, + Color, + Float, + Font, + FontFamily, + FontSize, + FontStyle, + FontWeight, + Margin, + MarginBottom, + MarginLeft, + MarginRight, + MarginTop, + QtBlockIndent, + QtListIndent, + QtParagraphType, + QtTableType, + QtUserState, + TextDecoration, + TextIndent, + TextUnderlineStyle, + VerticalAlignment, + Whitespace, + QtSelectionForeground, + QtSelectionBackground, + Border, + BorderLeft, + BorderRight, + BorderTop, + BorderBottom, + Padding, + PaddingLeft, + PaddingRight, + PaddingTop, + PaddingBottom, + PageBreakBefore, + PageBreakAfter, + QtAlternateBackground, + BorderLeftStyle, + BorderRightStyle, + BorderTopStyle, + BorderBottomStyle, + BorderStyles, + BorderLeftColor, + BorderRightColor, + BorderTopColor, + BorderBottomColor, + BorderColor, + BorderLeftWidth, + BorderRightWidth, + BorderTopWidth, + BorderBottomWidth, + BorderWidth, + BorderTopLeftRadius, + BorderTopRightRadius, + BorderBottomLeftRadius, + BorderBottomRightRadius, + BorderRadius, + Background, + BackgroundOrigin, + BackgroundClip, + BackgroundRepeat, + BackgroundPosition, + BackgroundAttachment, + BackgroundImage, + BorderImage, + QtSpacing, + Width, + Height, + MinimumWidth, + MinimumHeight, + MaximumWidth, + MaximumHeight, + QtImage, + Left, + Right, + Top, + Bottom, + QtOrigin, + QtPosition, + Position, + QtStyleFeatures, + QtBackgroundRole, + ListStyleType, + ListStyle, + QtImageAlignment, + TextAlignment, + Outline, + OutlineOffset, + OutlineWidth, + OutlineColor, + OutlineStyle, + OutlineRadius, + OutlineTopLeftRadius, + OutlineTopRightRadius, + OutlineBottomLeftRadius, + OutlineBottomRightRadius, + FontVariant, + TextTransform, + QtListNumberPrefix, + QtListNumberSuffix, + LineHeight, + NumProperties +}; + +enum KnownValue { + UnknownValue, + Value_Normal, + Value_Pre, + Value_NoWrap, + Value_PreWrap, + Value_Small, + Value_Medium, + Value_Large, + Value_XLarge, + Value_XXLarge, + Value_Italic, + Value_Oblique, + Value_Bold, + Value_Underline, + Value_Overline, + Value_LineThrough, + Value_Sub, + Value_Super, + Value_Left, + Value_Right, + Value_Top, + Value_Bottom, + Value_Center, + Value_Native, + Value_Solid, + Value_Dotted, + Value_Dashed, + Value_DotDash, + Value_DotDotDash, + Value_Double, + Value_Groove, + Value_Ridge, + Value_Inset, + Value_Outset, + Value_Wave, + Value_Middle, + Value_Auto, + Value_Always, + Value_None, + Value_Transparent, + Value_Disc, + Value_Circle, + Value_Square, + Value_Decimal, + Value_LowerAlpha, + Value_UpperAlpha, + Value_LowerRoman, + Value_UpperRoman, + Value_SmallCaps, + Value_Uppercase, + Value_Lowercase, + + /* keep these in same order as QPalette::ColorRole */ + Value_FirstColorRole, + Value_WindowText = Value_FirstColorRole, + Value_Button, + Value_Light, + Value_Midlight, + Value_Dark, + Value_Mid, + Value_Text, + Value_BrightText, + Value_ButtonText, + Value_Base, + Value_Window, + Value_Shadow, + Value_Highlight, + Value_HighlightedText, + Value_Link, + Value_LinkVisited, + Value_AlternateBase, + Value_LastColorRole = Value_AlternateBase, + + Value_Disabled, + Value_Active, + Value_Selected, + Value_On, + Value_Off, + + NumKnownValues +}; + +enum BorderStyle { + BorderStyle_Unknown, + BorderStyle_None, + BorderStyle_Dotted, + BorderStyle_Dashed, + BorderStyle_Solid, + BorderStyle_Double, + BorderStyle_DotDash, + BorderStyle_DotDotDash, + BorderStyle_Groove, + BorderStyle_Ridge, + BorderStyle_Inset, + BorderStyle_Outset, + BorderStyle_Native, + NumKnownBorderStyles +}; + +enum Edge { + TopEdge, + RightEdge, + BottomEdge, + LeftEdge, + NumEdges +}; + +enum Corner { + TopLeftCorner, + TopRightCorner, + BottomLeftCorner, + BottomRightCorner +}; + +enum TileMode { + TileMode_Unknown, + TileMode_Round, + TileMode_Stretch, + TileMode_Repeat, + NumKnownTileModes +}; + +enum Repeat { + Repeat_Unknown, + Repeat_None, + Repeat_X, + Repeat_Y, + Repeat_XY, + NumKnownRepeats +}; + +enum Origin { + Origin_Unknown, + Origin_Padding, + Origin_Border, + Origin_Content, + Origin_Margin, + NumKnownOrigins +}; + +enum PositionMode { + PositionMode_Unknown, + PositionMode_Static, + PositionMode_Relative, + PositionMode_Absolute, + PositionMode_Fixed, + NumKnownPositionModes +}; + +enum Attachment { + Attachment_Unknown, + Attachment_Fixed, + Attachment_Scroll, + NumKnownAttachments +}; + +enum StyleFeature { + StyleFeature_None = 0, + StyleFeature_BackgroundColor = 1, + StyleFeature_BackgroundGradient = 2, + NumKnownStyleFeatures = 4 +}; + +struct Q_GUI_EXPORT Value +{ + enum Type { + Unknown, + Number, + Percentage, + Length, + String, + Identifier, + KnownIdentifier, + Uri, + Color, + Function, + TermOperatorSlash, + TermOperatorComma + }; + inline Value() : type(Unknown) { } + Type type; + QVariant variant; + QString toString() const; +}; + +struct ColorData { + ColorData() : role(QPalette::NoRole), type(Invalid) {} + ColorData(const QColor &col) : color(col), role(QPalette::NoRole), type(Color) {} + ColorData(QPalette::ColorRole r) : role(r), type(Role) {} + QColor color; + QPalette::ColorRole role; + enum { Invalid, Color, Role} type; +}; + +struct BrushData { + BrushData() : role(QPalette::NoRole), type(Invalid) {} + BrushData(const QBrush &br) : brush(br), role(QPalette::NoRole), type(Brush) {} + BrushData(QPalette::ColorRole r) : role(r), type(Role) {} + QBrush brush; + QPalette::ColorRole role; + enum { Invalid, Brush, Role, DependsOnThePalette } type; +}; + +struct BackgroundData { + BrushData brush; + QString image; + Repeat repeat; + Qt::Alignment alignment; +}; + +struct LengthData { + qreal number; + enum { None, Px, Ex, Em } unit; +}; + +struct BorderData { + LengthData width; + BorderStyle style; + BrushData color; +}; + + +// 1. StyleRule - x:hover, y:clicked > z:checked { prop1: value1; prop2: value2; } +// 2. QVector<Selector> - x:hover, y:clicked z:checked +// 3. QVector<BasicSelector> - y:clicked z:checked +// 4. QVector<Declaration> - { prop1: value1; prop2: value2; } +// 5. Declaration - prop1: value1; + +struct Q_AUTOTEST_EXPORT Declaration +{ + struct DeclarationData : public QSharedData + { + inline DeclarationData() : propertyId(UnknownProperty), important(false) {} + QString property; + Property propertyId; + QVector<Value> values; + QVariant parsed; + bool important; + }; + QExplicitlySharedDataPointer<DeclarationData> d; + inline Declaration() : d(new DeclarationData()) {} + inline bool isEmpty() const { return d->property.isEmpty() && d->propertyId == UnknownProperty; } + + // helper functions + QColor colorValue(const QPalette & = QPalette()) const; + void colorValues(QColor *c, const QPalette & = QPalette()) const; + QBrush brushValue(const QPalette & = QPalette()) const; + void brushValues(QBrush *c, const QPalette & = QPalette()) const; + + BorderStyle styleValue() const; + void styleValues(BorderStyle *s) const; + + Origin originValue() const; + Repeat repeatValue() const; + Qt::Alignment alignmentValue() const; + PositionMode positionValue() const; + Attachment attachmentValue() const; + int styleFeaturesValue() const; + + bool intValue(int *i, const char *unit = 0) const; + bool realValue(qreal *r, const char *unit = 0) const; + + QSize sizeValue() const; + QRect rectValue() const; + QString uriValue() const; + QIcon iconValue() const; + + void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const; +}; + +const quint64 PseudoClass_Unknown = Q_UINT64_C(0x0000000000000000); +const quint64 PseudoClass_Enabled = Q_UINT64_C(0x0000000000000001); +const quint64 PseudoClass_Disabled = Q_UINT64_C(0x0000000000000002); +const quint64 PseudoClass_Pressed = Q_UINT64_C(0x0000000000000004); +const quint64 PseudoClass_Focus = Q_UINT64_C(0x0000000000000008); +const quint64 PseudoClass_Hover = Q_UINT64_C(0x0000000000000010); +const quint64 PseudoClass_Checked = Q_UINT64_C(0x0000000000000020); +const quint64 PseudoClass_Unchecked = Q_UINT64_C(0x0000000000000040); +const quint64 PseudoClass_Indeterminate = Q_UINT64_C(0x0000000000000080); +const quint64 PseudoClass_Unspecified = Q_UINT64_C(0x0000000000000100); +const quint64 PseudoClass_Selected = Q_UINT64_C(0x0000000000000200); +const quint64 PseudoClass_Horizontal = Q_UINT64_C(0x0000000000000400); +const quint64 PseudoClass_Vertical = Q_UINT64_C(0x0000000000000800); +const quint64 PseudoClass_Window = Q_UINT64_C(0x0000000000001000); +const quint64 PseudoClass_Children = Q_UINT64_C(0x0000000000002000); +const quint64 PseudoClass_Sibling = Q_UINT64_C(0x0000000000004000); +const quint64 PseudoClass_Default = Q_UINT64_C(0x0000000000008000); +const quint64 PseudoClass_First = Q_UINT64_C(0x0000000000010000); +const quint64 PseudoClass_Last = Q_UINT64_C(0x0000000000020000); +const quint64 PseudoClass_Middle = Q_UINT64_C(0x0000000000040000); +const quint64 PseudoClass_OnlyOne = Q_UINT64_C(0x0000000000080000); +const quint64 PseudoClass_PreviousSelected = Q_UINT64_C(0x0000000000100000); +const quint64 PseudoClass_NextSelected = Q_UINT64_C(0x0000000000200000); +const quint64 PseudoClass_Flat = Q_UINT64_C(0x0000000000400000); +const quint64 PseudoClass_Left = Q_UINT64_C(0x0000000000800000); +const quint64 PseudoClass_Right = Q_UINT64_C(0x0000000001000000); +const quint64 PseudoClass_Top = Q_UINT64_C(0x0000000002000000); +const quint64 PseudoClass_Bottom = Q_UINT64_C(0x0000000004000000); +const quint64 PseudoClass_Exclusive = Q_UINT64_C(0x0000000008000000); +const quint64 PseudoClass_NonExclusive = Q_UINT64_C(0x0000000010000000); +const quint64 PseudoClass_Frameless = Q_UINT64_C(0x0000000020000000); +const quint64 PseudoClass_ReadOnly = Q_UINT64_C(0x0000000040000000); +const quint64 PseudoClass_Active = Q_UINT64_C(0x0000000080000000); +const quint64 PseudoClass_Closable = Q_UINT64_C(0x0000000100000000); +const quint64 PseudoClass_Movable = Q_UINT64_C(0x0000000200000000); +const quint64 PseudoClass_Floatable = Q_UINT64_C(0x0000000400000000); +const quint64 PseudoClass_Minimized = Q_UINT64_C(0x0000000800000000); +const quint64 PseudoClass_Maximized = Q_UINT64_C(0x0000001000000000); +const quint64 PseudoClass_On = Q_UINT64_C(0x0000002000000000); +const quint64 PseudoClass_Off = Q_UINT64_C(0x0000004000000000); +const quint64 PseudoClass_Editable = Q_UINT64_C(0x0000008000000000); +const quint64 PseudoClass_Item = Q_UINT64_C(0x0000010000000000); +const quint64 PseudoClass_Closed = Q_UINT64_C(0x0000020000000000); +const quint64 PseudoClass_Open = Q_UINT64_C(0x0000040000000000); +const quint64 PseudoClass_EditFocus = Q_UINT64_C(0x0000080000000000); +const quint64 PseudoClass_Alternate = Q_UINT64_C(0x0000100000000000); +// The Any specifier is never generated, but can be used as a wildcard in searches. +const quint64 PseudoClass_Any = Q_UINT64_C(0x0000ffffffffffff); +const int NumPseudos = 46; + +struct Pseudo +{ + Pseudo() : type(0), negated(false) { } + quint64 type; + QString name; + QString function; + bool negated; +}; + +struct AttributeSelector +{ + enum ValueMatchType { + NoMatch, + MatchEqual, + MatchContains, + MatchBeginsWith + }; + inline AttributeSelector() : valueMatchCriterium(NoMatch) {} + + QString name; + QString value; + ValueMatchType valueMatchCriterium; +}; + +struct BasicSelector +{ + inline BasicSelector() : relationToNext(NoRelation) {} + + enum Relation { + NoRelation, + MatchNextSelectorIfAncestor, + MatchNextSelectorIfParent, + MatchNextSelectorIfPreceeds + }; + + QString elementName; + + QStringList ids; + QVector<Pseudo> pseudos; + QVector<AttributeSelector> attributeSelectors; + + Relation relationToNext; +}; + +struct Q_AUTOTEST_EXPORT Selector +{ + QVector<BasicSelector> basicSelectors; + int specificity() const; + quint64 pseudoClass(quint64 *negated = 0) const; + QString pseudoElement() const; +}; + +struct StyleRule; +struct MediaRule; +struct PageRule; +struct ImportRule; + +struct Q_AUTOTEST_EXPORT ValueExtractor +{ + ValueExtractor(const QVector<Declaration> &declarations, const QPalette & = QPalette()); + + bool extractFont(QFont *font, int *fontSizeAdjustment); + bool extractBackground(QBrush *, QString *, Repeat *, Qt::Alignment *, QCss::Origin *, QCss::Attachment *, + QCss::Origin *); + bool extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh); + bool extractPosition(int *l, int *t, int *r, int *b, QCss::Origin *, Qt::Alignment *, + QCss::PositionMode *, Qt::Alignment *); + bool extractBox(int *margins, int *paddings, int *spacing = 0); + bool extractBorder(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii); + bool extractOutline(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii, int *offsets); + bool extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg); + int extractStyleFeatures(); + bool extractImage(QIcon *icon, Qt::Alignment *a, QSize *size); + + int lengthValue(const Declaration &decl); + +private: + void extractFont(); + void borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color); + LengthData lengthValue(const Value& v); + void lengthValues(const Declaration &decl, int *m); + QSize sizeValue(const Declaration &decl); + void sizeValues(const Declaration &decl, QSize *radii); + + QVector<Declaration> declarations; + QFont f; + int adjustment; + int fontExtracted; + QPalette pal; +}; + +struct StyleRule +{ + StyleRule() : order(0) { } + QVector<Selector> selectors; + QVector<Declaration> declarations; + int order; +}; + +struct MediaRule +{ + QStringList media; + QVector<StyleRule> styleRules; +}; + +struct PageRule +{ + QString selector; + QVector<Declaration> declarations; +}; + +struct ImportRule +{ + QString href; + QStringList media; +}; + +enum StyleSheetOrigin { + StyleSheetOrigin_Unspecified, + StyleSheetOrigin_UserAgent, + StyleSheetOrigin_User, + StyleSheetOrigin_Author, + StyleSheetOrigin_Inline +}; + +struct StyleSheet +{ + StyleSheet() : origin(StyleSheetOrigin_Unspecified), depth(0) { } + QVector<StyleRule> styleRules; //only contains rules that are not indexed + QVector<MediaRule> mediaRules; + QVector<PageRule> pageRules; + QVector<ImportRule> importRules; + StyleSheetOrigin origin; + int depth; // applicable only for inline style sheets + QMultiHash<QString, StyleRule> nameIndex; + QMultiHash<QString, StyleRule> idIndex; + void buildIndexes(Qt::CaseSensitivity nameCaseSensitivity = Qt::CaseSensitive); +}; + +class Q_GUI_EXPORT StyleSelector +{ +public: + StyleSelector() : nameCaseSensitivity(Qt::CaseSensitive) {} + virtual ~StyleSelector(); + + union NodePtr { + void *ptr; + int id; + }; + + QVector<StyleRule> styleRulesForNode(NodePtr node); + QVector<Declaration> declarationsForNode(NodePtr node, const char *extraPseudo = 0); + + virtual bool nodeNameEquals(NodePtr node, const QString& nodeName) const; + virtual QString attribute(NodePtr node, const QString &name) const = 0; + virtual bool hasAttributes(NodePtr node) const = 0; + virtual QStringList nodeIds(NodePtr node) const; + virtual QStringList nodeNames(NodePtr node) const = 0; + virtual bool isNullNode(NodePtr node) const = 0; + virtual NodePtr parentNode(NodePtr node) const = 0; + virtual NodePtr previousSiblingNode(NodePtr node) const = 0; + virtual NodePtr duplicateNode(NodePtr node) const = 0; + virtual void freeNode(NodePtr node) const = 0; + + QVector<StyleSheet> styleSheets; + QString medium; + Qt::CaseSensitivity nameCaseSensitivity; +private: + void matchRule(NodePtr node, const StyleRule &rules, StyleSheetOrigin origin, + int depth, QMap<uint, StyleRule> *weightedRules); + bool selectorMatches(const Selector &rule, NodePtr node); + bool basicSelectorMatches(const BasicSelector &rule, NodePtr node); +}; + +enum TokenType { + NONE, + + S, + + CDO, + CDC, + INCLUDES, + DASHMATCH, + + LBRACE, + PLUS, + GREATER, + COMMA, + + STRING, + INVALID, + + IDENT, + + HASH, + + ATKEYWORD_SYM, + + EXCLAMATION_SYM, + + LENGTH, + + PERCENTAGE, + NUMBER, + + FUNCTION, + + COLON, + SEMICOLON, + RBRACE, + SLASH, + MINUS, + DOT, + STAR, + LBRACKET, + RBRACKET, + EQUAL, + LPAREN, + RPAREN, + OR +}; + +struct Q_GUI_EXPORT Symbol +{ + inline Symbol() : token(NONE), start(0), len(-1) {} + TokenType token; + QString text; + int start, len; + QString lexem() const; +}; + +class Q_AUTOTEST_EXPORT Scanner +{ +public: + static QString preprocess(const QString &input, bool *hasEscapeSequences = 0); + static void scan(const QString &preprocessedInput, QVector<Symbol> *symbols); +}; + +class Q_GUI_EXPORT Parser +{ +public: + Parser(); + Parser(const QString &css, bool file = false); + + void init(const QString &css, bool file = false); + bool parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity = Qt::CaseSensitive); + Symbol errorSymbol(); + + bool parseImport(ImportRule *importRule); + bool parseMedia(MediaRule *mediaRule); + bool parseMedium(QStringList *media); + bool parsePage(PageRule *pageRule); + bool parsePseudoPage(QString *selector); + bool parseNextOperator(Value *value); + bool parseCombinator(BasicSelector::Relation *relation); + bool parseProperty(Declaration *decl); + bool parseRuleset(StyleRule *styleRule); + bool parseSelector(Selector *sel); + bool parseSimpleSelector(BasicSelector *basicSel); + bool parseClass(QString *name); + bool parseElementName(QString *name); + bool parseAttrib(AttributeSelector *attr); + bool parsePseudo(Pseudo *pseudo); + bool parseNextDeclaration(Declaration *declaration); + bool parsePrio(Declaration *declaration); + bool parseExpr(QVector<Value> *values); + bool parseTerm(Value *value); + bool parseFunction(QString *name, QString *args); + bool parseHexColor(QColor *col); + bool testAndParseUri(QString *uri); + + inline bool testRuleset() { return testSelector(); } + inline bool testSelector() { return testSimpleSelector(); } + inline bool parseNextSelector(Selector *sel) { if (!testSelector()) return recordError(); return parseSelector(sel); } + bool testSimpleSelector(); + inline bool parseNextSimpleSelector(BasicSelector *basicSel) { if (!testSimpleSelector()) return recordError(); return parseSimpleSelector(basicSel); } + inline bool testElementName() { return test(IDENT) || test(STAR); } + inline bool testClass() { return test(DOT); } + inline bool testAttrib() { return test(LBRACKET); } + inline bool testPseudo() { return test(COLON); } + inline bool testMedium() { return test(IDENT); } + inline bool parseNextMedium(QStringList *media) { if (!testMedium()) return recordError(); return parseMedium(media); } + inline bool testPseudoPage() { return test(COLON); } + inline bool testImport() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("import")); } + inline bool testMedia() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("media")); } + inline bool testPage() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("page")); } + inline bool testCombinator() { return test(PLUS) || test(GREATER) || test(S); } + inline bool testProperty() { return test(IDENT); } + bool testTerm(); + inline bool testExpr() { return testTerm(); } + inline bool parseNextExpr(QVector<Value> *values) { if (!testExpr()) return recordError(); return parseExpr(values); } + bool testPrio(); + inline bool testHexColor() { return test(HASH); } + inline bool testFunction() { return test(FUNCTION); } + inline bool parseNextFunction(QString *name, QString *args) { if (!testFunction()) return recordError(); return parseFunction(name, args); } + + inline bool lookupElementName() const { return lookup() == IDENT || lookup() == STAR; } + + inline void skipSpace() { while (test(S)) {}; } + + inline bool hasNext() const { return index < symbols.count(); } + inline TokenType next() { return symbols.at(index++).token; } + bool next(TokenType t); + bool test(TokenType t); + inline void prev() { index--; } + inline const Symbol &symbol() const { return symbols.at(index - 1); } + inline QString lexem() const { return symbol().lexem(); } + QString unquotedLexem() const; + QString lexemUntil(TokenType t); + bool until(TokenType target, TokenType target2 = NONE); + inline TokenType lookup() const { + return (index - 1) < symbols.count() ? symbols.at(index - 1).token : NONE; + } + + bool testTokenAndEndsWith(TokenType t, const QLatin1String &str); + + inline bool recordError() { errorIndex = index; return false; } + + QVector<Symbol> symbols; + int index; + int errorIndex; + bool hasEscapeSequences; + QString sourcePath; +}; + +} // namespace QCss + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE( QCss::BackgroundData ) +Q_DECLARE_METATYPE( QCss::LengthData ) +Q_DECLARE_METATYPE( QCss::BorderData ) + + +#endif // QT_NO_CSSPARSER + +#endif diff --git a/src/gui/text/qcssscanner.cpp b/src/gui/text/qcssscanner.cpp new file mode 100644 index 0000000000..409b86969c --- /dev/null +++ b/src/gui/text/qcssscanner.cpp @@ -0,0 +1,1146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// auto generated. DO NOT EDIT. +class QCssScanner_Generated +{ +public: + QCssScanner_Generated(const QString &inp); + + inline QChar next() { + return (pos < input.length()) ? input.at(pos++) : QChar(); + } + int handleCommentStart(); + int lex(); + + QString input; + int pos; + int lexemStart; + int lexemLength; +}; + +QCssScanner_Generated::QCssScanner_Generated(const QString &inp) +{ + input = inp; + pos = 0; + lexemStart = 0; + lexemLength = 0; +} + + +int QCssScanner_Generated::lex() +{ + lexemStart = pos; + lexemLength = 0; + int lastAcceptingPos = -1; + int token = -1; + QChar ch; + + // initial state + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_1; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_1; + if (ch.unicode() == 32) + goto state_1; + if (ch.unicode() == 33) { + token = QCss::EXCLAMATION_SYM; + goto found; + } + if (ch.unicode() == 34) + goto state_3; + if (ch.unicode() == 35) + goto state_4; + if (ch.unicode() == 39) + goto state_5; + if (ch.unicode() == 40) { + token = QCss::LPAREN; + goto found; + } + if (ch.unicode() == 41) { + token = QCss::RPAREN; + goto found; + } + if (ch.unicode() == 42) { + token = QCss::STAR; + goto found; + } + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 45) + goto state_11; + if (ch.unicode() == 46) + goto state_12; + if (ch.unicode() == 47) + goto state_13; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_14; + if (ch.unicode() == 58) { + token = QCss::COLON; + goto found; + } + if (ch.unicode() == 59) { + token = QCss::SEMICOLON; + goto found; + } + if (ch.unicode() == 60) + goto state_17; + if (ch.unicode() == 61) { + token = QCss::EQUAL; + goto found; + } + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 64) + goto state_20; + if (ch.unicode() == 91) { + token = QCss::LBRACKET; + goto found; + } + if (ch.unicode() == 92) + goto state_22; + if (ch.unicode() == 93) { + token = QCss::RBRACKET; + goto found; + } + if (ch.unicode() == 95) + goto state_24; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_24; + if (ch.unicode() == 123) + goto state_25; + if (ch.unicode() == 124) + goto state_26; + if (ch.unicode() == 125) { + token = QCss::RBRACE; + goto found; + } + if (ch.unicode() == 126) + goto state_28; + goto out; + state_1: + lastAcceptingPos = pos; + token = QCss::S; + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_29; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_29; + if (ch.unicode() == 32) + goto state_29; + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 123) + goto state_25; + goto out; + state_3: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_4: + ch = next(); + if (ch.unicode() == 45) + goto state_33; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_33; + if (ch.unicode() == 92) + goto state_34; + if (ch.unicode() == 95) + goto state_33; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_33; + goto out; + state_5: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_9: + lastAcceptingPos = pos; + token = QCss::PLUS; + goto out; + state_10: + lastAcceptingPos = pos; + token = QCss::COMMA; + goto out; + state_11: + lastAcceptingPos = pos; + token = QCss::MINUS; + ch = next(); + if (ch.unicode() == 45) + goto state_38; + if (ch.unicode() == 92) + goto state_22; + if (ch.unicode() == 95) + goto state_24; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_24; + goto out; + state_12: + lastAcceptingPos = pos; + token = QCss::DOT; + ch = next(); + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_39; + goto out; + state_13: + lastAcceptingPos = pos; + token = QCss::SLASH; + ch = next(); + if (ch.unicode() == 42) { + token = handleCommentStart(); + goto found; + } + goto out; + state_14: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() == 46) + goto state_43; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_44; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_17: + ch = next(); + if (ch.unicode() == 33) + goto state_47; + goto out; + state_19: + lastAcceptingPos = pos; + token = QCss::GREATER; + goto out; + state_20: + ch = next(); + if (ch.unicode() == 45) + goto state_48; + if (ch.unicode() == 92) + goto state_49; + if (ch.unicode() == 95) + goto state_50; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_50; + goto out; + state_22: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_51; + if (ch.unicode() == 11) + goto state_51; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_51; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_51; + if (ch.unicode() >= 103) + goto state_51; + goto out; + state_24: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_25: + lastAcceptingPos = pos; + token = QCss::LBRACE; + goto out; + state_26: + lastAcceptingPos = pos; + token = QCss::OR; + ch = next(); + if (ch.unicode() == 61) { + token = QCss::DASHMATCH; + goto found; + } + goto out; + state_28: + ch = next(); + if (ch.unicode() == 61) { + token = QCss::INCLUDES; + goto found; + } + goto out; + state_29: + lastAcceptingPos = pos; + token = QCss::S; + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_29; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_29; + if (ch.unicode() == 32) + goto state_29; + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 123) + goto state_25; + goto out; + state_30: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_31: + lastAcceptingPos = pos; + token = QCss::STRING; + goto out; + state_32: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_57; + if (ch.unicode() == 10) + goto state_58; + if (ch.unicode() == 11) + goto state_57; + if (ch.unicode() == 12) + goto state_59; + if (ch.unicode() == 13) + goto state_60; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_57; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_57; + if (ch.unicode() >= 103) + goto state_57; + goto out; + state_33: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_34: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_63; + if (ch.unicode() == 11) + goto state_63; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_63; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_63; + if (ch.unicode() >= 103) + goto state_63; + goto out; + state_35: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_36: + lastAcceptingPos = pos; + token = QCss::STRING; + goto out; + state_37: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_64; + if (ch.unicode() == 10) + goto state_65; + if (ch.unicode() == 11) + goto state_64; + if (ch.unicode() == 12) + goto state_66; + if (ch.unicode() == 13) + goto state_67; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_64; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_64; + if (ch.unicode() >= 103) + goto state_64; + goto out; + state_38: + ch = next(); + if (ch.unicode() == 62) { + token = QCss::CDC; + goto found; + } + goto out; + state_39: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_69; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_41: + lastAcceptingPos = pos; + token = QCss::PERCENTAGE; + goto out; + state_42: + ch = next(); + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_43: + ch = next(); + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_39; + goto out; + state_44: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() == 46) + goto state_43; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_44; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_45: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_70; + if (ch.unicode() == 11) + goto state_70; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_70; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_70; + if (ch.unicode() >= 103) + goto state_70; + goto out; + state_46: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_47: + ch = next(); + if (ch.unicode() == 45) + goto state_73; + goto out; + state_48: + ch = next(); + if (ch.unicode() == 92) + goto state_49; + if (ch.unicode() == 95) + goto state_50; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_50; + goto out; + state_49: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_74; + if (ch.unicode() == 11) + goto state_74; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_74; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_74; + if (ch.unicode() >= 103) + goto state_74; + goto out; + state_50: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_51: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_52: + lastAcceptingPos = pos; + token = QCss::FUNCTION; + goto out; + state_53: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_54: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_77; + if (ch.unicode() == 11) + goto state_77; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_77; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_77; + if (ch.unicode() >= 103) + goto state_77; + goto out; + state_57: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_58: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_59: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_60: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 10) + goto state_78; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_61: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_62: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_79; + if (ch.unicode() == 11) + goto state_79; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_79; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_79; + if (ch.unicode() >= 103) + goto state_79; + goto out; + state_63: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_64: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_65: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_66: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_67: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 10) + goto state_80; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_69: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_69; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_70: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_71: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_72: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_81; + if (ch.unicode() == 11) + goto state_81; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_81; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_81; + if (ch.unicode() >= 103) + goto state_81; + goto out; + state_73: + ch = next(); + if (ch.unicode() == 45) { + token = QCss::CDO; + goto found; + } + goto out; + state_74: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_75: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_76: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_83; + if (ch.unicode() == 11) + goto state_83; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_83; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_83; + if (ch.unicode() >= 103) + goto state_83; + goto out; + state_77: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_78: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_79: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_80: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_81: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_83: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + found: + lastAcceptingPos = pos; + + out: + if (lastAcceptingPos != -1) { + lexemLength = lastAcceptingPos - lexemStart; + pos = lastAcceptingPos; + } + return token; +} + diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp new file mode 100644 index 0000000000..b8cfb1f902 --- /dev/null +++ b/src/gui/text/qfont.cpp @@ -0,0 +1,3188 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qdebug.h" +#include "qpaintdevice.h" +#include "qfontdatabase.h" +#include "qfontmetrics.h" +#include "qfontinfo.h" +#include "qpainter.h" +#include "qhash.h" +#include "qdatastream.h" +#include "qapplication.h" +#include "qstringlist.h" + +#include "qthread.h" +#include "qthreadstorage.h" + +#include <private/qunicodetables_p.h> +#include "qfont_p.h" +#include <private/qfontengine_p.h> +#include <private/qpainter_p.h> +#include <private/qtextengine_p.h> +#include <limits.h> + +#ifdef Q_WS_X11 +#include "qx11info_x11.h" +#include <private/qt_x11_p.h> +#endif +#ifdef Q_WS_QWS +#include "qscreen_qws.h" +#if !defined(QT_NO_QWS_QPF2) +#include <qfile.h> +#include "qfontengine_qpf_p.h" +#endif +#endif +#ifdef Q_OS_SYMBIAN +#include <private/qt_s60_p.h> +#endif +#ifdef Q_WS_QPA +#include <QtGui/qplatformscreen_qpa.h> +#include <QtGui/private/qapplication_p.h> +#endif + +#include <QMutexLocker> + +// #define QFONTCACHE_DEBUG +#ifdef QFONTCACHE_DEBUG +# define FC_DEBUG qDebug +#else +# define FC_DEBUG if (false) qDebug +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_WIN +extern HDC shared_dc(); +#endif + +#ifdef Q_WS_X11 +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); +#endif + +bool QFontDef::exactMatch(const QFontDef &other) const +{ + /* + QFontDef comparison is more complicated than just simple + per-member comparisons. + + When comparing point/pixel sizes, either point or pixelsize + could be -1. in This case we have to compare the non negative + size value. + + This test will fail if the point-sizes differ by 1/2 point or + more or they do not round to the same value. We have to do this + since our API still uses 'int' point-sizes in the API, but store + deci-point-sizes internally. + + To compare the family members, we need to parse the font names + and compare the family/foundry strings separately. This allows + us to compare e.g. "Helvetica" and "Helvetica [Adobe]" with + positive results. + */ + if (pixelSize != -1 && other.pixelSize != -1) { + if (pixelSize != other.pixelSize) + return false; + } else if (pointSize != -1 && other.pointSize != -1) { + if (pointSize != other.pointSize) + return false; + } else { + return false; + } + + if (!ignorePitch && !other.ignorePitch && fixedPitch != other.fixedPitch) + return false; + + if (stretch != 0 && other.stretch != 0 && stretch != other.stretch) + return false; + + QString this_family, this_foundry, other_family, other_foundry; + QFontDatabase::parseFontName(family, this_foundry, this_family); + QFontDatabase::parseFontName(other.family, other_foundry, other_family); + + this_family = QFontDatabase::resolveFontFamilyAlias(this_family); + other_family = QFontDatabase::resolveFontFamilyAlias(other_family); + + return (styleHint == other.styleHint + && styleStrategy == other.styleStrategy + && weight == other.weight + && style == other.style + && this_family == other_family + && (this_foundry.isEmpty() + || other_foundry.isEmpty() + || this_foundry == other_foundry) +#ifdef Q_WS_X11 + && addStyle == other.addStyle +#endif // Q_WS_X11 + ); +} + +extern bool qt_is_gui_used; + +Q_GUI_EXPORT int qt_defaultDpiX() +{ + if (!qt_is_gui_used) + return 75; + + int dpi; +#ifdef Q_WS_X11 + dpi = QX11Info::appDpiX(); +#elif defined(Q_WS_WIN) + dpi = GetDeviceCaps(shared_dc(),LOGPIXELSX); +#elif defined(Q_WS_MAC) + extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + dpi = qt_mac_defaultDpi_x(); +#elif defined(Q_WS_QWS) + if (!qt_screen) + return 72; + QScreen *screen = qt_screen; + const QList<QScreen*> subScreens = qt_screen->subScreens(); + if (!subScreens.isEmpty()) + screen = subScreens.at(0); + dpi = qRound(screen->width() / (screen->physicalWidth() / qreal(25.4))); +#elif defined(Q_WS_QPA) + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + if (pi) { + QPlatformScreen *screen = QApplicationPrivate::platformIntegration()->screens().at(0); + const QSize screenSize = screen->geometry().size(); + const QSize physicalSize = screen->physicalSize(); + dpi = qRound(screenSize.width() / (physicalSize.width() / qreal(25.4))); + } else { + //PI has not been initialised, or it is being initialised. Give a default dpi + dpi = 100; + } +#elif defined(Q_OS_SYMBIAN) + dpi = S60->defaultDpiX; +#endif // Q_WS_X11 + + return dpi; +} + +Q_GUI_EXPORT int qt_defaultDpiY() +{ + if (!qt_is_gui_used) + return 75; + + int dpi; +#ifdef Q_WS_X11 + dpi = QX11Info::appDpiY(); +#elif defined(Q_WS_WIN) + dpi = GetDeviceCaps(shared_dc(),LOGPIXELSY); +#elif defined(Q_WS_MAC) + extern float qt_mac_defaultDpi_y(); //qpaintdevice_mac.cpp + dpi = qt_mac_defaultDpi_y(); +#elif defined(Q_WS_QWS) + if (!qt_screen) + return 72; + QScreen *screen = qt_screen; + const QList<QScreen*> subScreens = qt_screen->subScreens(); + if (!subScreens.isEmpty()) + screen = subScreens.at(0); + dpi = qRound(screen->height() / (screen->physicalHeight() / qreal(25.4))); +#elif defined(Q_WS_QPA) + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + if (pi) { + QPlatformScreen *screen = QApplicationPrivate::platformIntegration()->screens().at(0); + const QSize screenSize = screen->geometry().size(); + const QSize physicalSize = screen->physicalSize(); + dpi = qRound(screenSize.height() / (physicalSize.height() / qreal(25.4))); + } else { + //PI has not been initialised, or it is being initialised. Give a default dpi + dpi = 100; + } +#elif defined(Q_OS_SYMBIAN) + dpi = S60->defaultDpiY; +#endif // Q_WS_X11 + + return dpi; +} + +Q_GUI_EXPORT int qt_defaultDpi() +{ + return qt_defaultDpiY(); +} + +QFontPrivate::QFontPrivate() + : engineData(0), dpi(qt_defaultDpi()), screen(0), + rawMode(false), underline(false), overline(false), strikeOut(false), kerning(true), + capital(0), letterSpacingIsAbsolute(false), scFont(0) +{ +#ifdef Q_WS_X11 + if (QX11Info::display()) + screen = QX11Info::appScreen(); + else + screen = 0; +#endif +#ifdef Q_WS_WIN + hdc = 0; +#endif +} + +QFontPrivate::QFontPrivate(const QFontPrivate &other) + : request(other.request), engineData(0), dpi(other.dpi), screen(other.screen), + rawMode(other.rawMode), underline(other.underline), overline(other.overline), + strikeOut(other.strikeOut), kerning(other.kerning), + capital(other.capital), letterSpacingIsAbsolute(other.letterSpacingIsAbsolute), + letterSpacing(other.letterSpacing), wordSpacing(other.wordSpacing), + scFont(other.scFont) +{ +#ifdef Q_WS_WIN + hdc = other.hdc; +#endif + if (scFont && scFont != this) + scFont->ref.ref(); +} + +QFontPrivate::~QFontPrivate() +{ + if (engineData) + engineData->ref.deref(); + engineData = 0; + if (scFont && scFont != this) + scFont->ref.deref(); + scFont = 0; +} + +extern QMutex *qt_fontdatabase_mutex(); + +#if !defined(Q_WS_MAC) +#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engines[script] +#else +#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engine +#endif + +QFontEngine *QFontPrivate::engineForScript(int script) const +{ + QMutexLocker locker(qt_fontdatabase_mutex()); + if (script >= QUnicodeTables::Inherited) + script = QUnicodeTables::Common; + if (engineData && engineData->fontCache != QFontCache::instance()) { + // throw out engineData that came from a different thread + engineData->ref.deref(); + engineData = 0; + } + if (!engineData || !QT_FONT_ENGINE_FROM_DATA(engineData, script)) + QFontDatabase::load(this, script); + return QT_FONT_ENGINE_FROM_DATA(engineData, script); +} + +void QFontPrivate::alterCharForCapitalization(QChar &c) const { + switch (capital) { + case QFont::AllUppercase: + case QFont::SmallCaps: + c = c.toUpper(); + break; + case QFont::AllLowercase: + c = c.toLower(); + break; + case QFont::MixedCase: + break; + } +} + +QFontPrivate *QFontPrivate::smallCapsFontPrivate() const +{ + if (scFont) + return scFont; + QFont font(const_cast<QFontPrivate *>(this)); + qreal pointSize = font.pointSizeF(); + if (pointSize > 0) + font.setPointSizeF(pointSize * .7); + else + font.setPixelSize((font.pixelSize() * 7 + 5) / 10); + scFont = font.d.data(); + if (scFont != this) + scFont->ref.ref(); + return scFont; +} + + +void QFontPrivate::resolve(uint mask, const QFontPrivate *other) +{ + Q_ASSERT(other != 0); + + dpi = other->dpi; + + if ((mask & QFont::AllPropertiesResolved) == QFont::AllPropertiesResolved) return; + + // assign the unset-bits with the set-bits of the other font def + if (! (mask & QFont::FamilyResolved)) + request.family = other->request.family; + + if (! (mask & QFont::SizeResolved)) { + request.pointSize = other->request.pointSize; + request.pixelSize = other->request.pixelSize; + } + + if (! (mask & QFont::StyleHintResolved)) + request.styleHint = other->request.styleHint; + + if (! (mask & QFont::StyleStrategyResolved)) + request.styleStrategy = other->request.styleStrategy; + + if (! (mask & QFont::WeightResolved)) + request.weight = other->request.weight; + + if (! (mask & QFont::StyleResolved)) + request.style = other->request.style; + + if (! (mask & QFont::FixedPitchResolved)) + request.fixedPitch = other->request.fixedPitch; + + if (! (mask & QFont::StretchResolved)) + request.stretch = other->request.stretch; + + if (! (mask & QFont::HintingPreferenceResolved)) + request.hintingPreference = other->request.hintingPreference; + + if (! (mask & QFont::UnderlineResolved)) + underline = other->underline; + + if (! (mask & QFont::OverlineResolved)) + overline = other->overline; + + if (! (mask & QFont::StrikeOutResolved)) + strikeOut = other->strikeOut; + + if (! (mask & QFont::KerningResolved)) + kerning = other->kerning; + + if (! (mask & QFont::LetterSpacingResolved)) { + letterSpacing = other->letterSpacing; + letterSpacingIsAbsolute = other->letterSpacingIsAbsolute; + } + if (! (mask & QFont::WordSpacingResolved)) + wordSpacing = other->wordSpacing; + if (! (mask & QFont::CapitalizationResolved)) + capital = other->capital; +} + + + + +QFontEngineData::QFontEngineData() + : ref(1), fontCache(QFontCache::instance()) +{ +#if !defined(Q_WS_MAC) + memset(engines, 0, QUnicodeTables::ScriptCount * sizeof(QFontEngine *)); +#else + engine = 0; +#endif +} + +QFontEngineData::~QFontEngineData() +{ +#if !defined(Q_WS_MAC) + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (engines[i]) + engines[i]->ref.deref(); + engines[i] = 0; + } +#else + if (engine) + engine->ref.deref(); + engine = 0; +#endif // Q_WS_X11 || Q_WS_WIN || Q_WS_MAC +} + + + + +/*! + \class QFont + \reentrant + + \brief The QFont class specifies a font used for drawing text. + + \ingroup painting + \ingroup appearance + \ingroup shared + \ingroup richtext-processing + + + When you create a QFont object you specify various attributes that + you want the font to have. Qt will use the font with the specified + attributes, or if no matching font exists, Qt will use the closest + matching installed font. The attributes of the font that is + actually used are retrievable from a QFontInfo object. If the + window system provides an exact match exactMatch() returns true. + Use QFontMetrics to get measurements, e.g. the pixel length of a + string using QFontMetrics::width(). + + Note that a QApplication instance must exist before a QFont can be + used. You can set the application's default font with + QApplication::setFont(). + + If a chosen font does not include all the characters that + need to be displayed, QFont will try to find the characters in the + nearest equivalent fonts. When a QPainter draws a character from a + font the QFont will report whether or not it has the character; if + it does not, QPainter will draw an unfilled square. + + Create QFonts like this: + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 0 + + The attributes set in the constructor can also be set later, e.g. + setFamily(), setPointSize(), setPointSizeFloat(), setWeight() and + setItalic(). The remaining attributes must be set after + contstruction, e.g. setBold(), setUnderline(), setOverline(), + setStrikeOut() and setFixedPitch(). QFontInfo objects should be + created \e after the font's attributes have been set. A QFontInfo + object will not change, even if you change the font's + attributes. The corresponding "get" functions, e.g. family(), + pointSize(), etc., return the values that were set, even though + the values used may differ. The actual values are available from a + QFontInfo object. + + If the requested font family is unavailable you can influence the + \link #fontmatching font matching algorithm\endlink by choosing a + particular \l{QFont::StyleHint} and \l{QFont::StyleStrategy} with + setStyleHint(). The default family (corresponding to the current + style hint) is returned by defaultFamily(). + + The font-matching algorithm has a lastResortFamily() and + lastResortFont() in cases where a suitable match cannot be found. + You can provide substitutions for font family names using + insertSubstitution() and insertSubstitutions(). Substitutions can + be removed with removeSubstitution(). Use substitute() to retrieve + a family's first substitute, or the family name itself if it has + no substitutes. Use substitutes() to retrieve a list of a family's + substitutes (which may be empty). + + Every QFont has a key() which you can use, for example, as the key + in a cache or dictionary. If you want to store a user's font + preferences you could use QSettings, writing the font information + with toString() and reading it back with fromString(). The + operator<<() and operator>>() functions are also available, but + they work on a data stream. + + It is possible to set the height of characters shown on the screen + to a specified number of pixels with setPixelSize(); however using + setPointSize() has a similar effect and provides device + independence. + + In X11 you can set a font using its system + specific name with setRawName(). + + Loading fonts can be expensive, especially on X11. QFont contains + extensive optimizations to make the copying of QFont objects fast, + and to cache the results of the slow window system functions it + depends upon. + + \target fontmatching + The font matching algorithm works as follows: + \list 1 + \o The specified font family is searched for. + \o If not found, the styleHint() is used to select a replacement + family. + \o Each replacement font family is searched for. + \o If none of these are found or there was no styleHint(), "helvetica" + will be searched for. + \o If "helvetica" isn't found Qt will try the lastResortFamily(). + \o If the lastResortFamily() isn't found Qt will try the + lastResortFont() which will always return a name of some kind. + \endlist + + Note that the actual font matching algorithm varies from platform to platform. + + In Windows a request for the "Courier" font is automatically changed to + "Courier New", an improved version of Courier that allows for smooth scaling. + The older "Courier" bitmap font can be selected by setting the PreferBitmap + style strategy (see setStyleStrategy()). + + Once a font is found, the remaining attributes are matched in order of + priority: + \list 1 + \o fixedPitch() + \o pointSize() (see below) + \o weight() + \o style() + \endlist + + If you have a font which matches on family, even if none of the + other attributes match, this font will be chosen in preference to + a font which doesn't match on family but which does match on the + other attributes. This is because font family is the dominant + search criteria. + + The point size is defined to match if it is within 20% of the + requested point size. When several fonts match and are only + distinguished by point size, the font with the closest point size + to the one requested will be chosen. + + The actual family, font size, weight and other font attributes + used for drawing text will depend on what's available for the + chosen family under the window system. A QFontInfo object can be + used to determine the actual values used for drawing the text. + + Examples: + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 1 + If you had both an Adobe and a Cronyx Helvetica, you might get + either. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 2 + + You can specify the foundry you want in the family name. The font f + in the above example will be set to "Helvetica + [Cronyx]". + + To determine the attributes of the font actually used in the window + system, use a QFontInfo object, e.g. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 3 + + To find out font metrics use a QFontMetrics object, e.g. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 4 + + For more general information on fonts, see the + \link http://nwalsh.com/comp.fonts/FAQ/ comp.fonts FAQ.\endlink + Information on encodings can be found from + \link http://czyborra.com/ Roman Czyborra's\endlink page. + + \sa QFontComboBox, QFontMetrics, QFontInfo, QFontDatabase, {Character Map Example} +*/ + +/*! + \internal + \enum QFont::ResolveProperties + + This enum describes the properties of a QFont that can be set on a font + individually and then considered resolved. + + \value FamilyResolved + \value SizeResolved + \value StyleHintResolved + \value StyleStrategyResolved + \value WeightResolved + \value StyleResolved + \value UnderlineResolved + \value OverlineResolved + \value StrikeOutResolved + \value FixedPitchResolved + \value StretchResolved + \value KerningResolved + \value CapitalizationResolved + \value LetterSpacingResolved + \value WordSpacingResolved + \value CompletelyResolved +*/ + +/*! + \enum QFont::Style + + This enum describes the different styles of glyphs that are used to + display text. + + \value StyleNormal Normal glyphs used in unstyled text. + \value StyleItalic Italic glyphs that are specifically designed for + the purpose of representing italicized text. + \value StyleOblique Glyphs with an italic appearance that are typically + based on the unstyled glyphs, but are not fine-tuned + for the purpose of representing italicized text. + + \sa Weight +*/ + +/*! + \fn Qt::HANDLE QFont::handle() const + + Returns the window system handle to the font, for low-level + access. Using this function is \e not portable. +*/ + +/*! + \fn FT_Face QFont::freetypeFace() const + + Returns the handle to the primary FreeType face of the font. If font merging is not disabled a + QFont can contain several physical fonts. + + Returns 0 if the font does not contain a FreeType face. + + \note This function is only available on platforms that provide the FreeType library; + i.e., X11 and some Embedded Linux platforms. +*/ + +/*! + \fn QString QFont::rawName() const + + Returns the name of the font within the underlying window system. + + On X11, this function will return an empty string if Qt is built with + FontConfig support; otherwise the XLFD (X Logical Font Description) is + returned. + + Using the return value of this function is usually \e not \e + portable. + + \sa setRawName() +*/ + +/*! + \fn void QFont::setRawName(const QString &name) + + Sets a font by its system specific name. The function is + particularly useful under X, where system font settings (for + example X resources) are usually available in XLFD (X Logical Font + Description) form only. You can pass an XLFD as \a name to this + function. + + A font set with setRawName() is still a full-featured QFont. It can + be queried (for example with italic()) or modified (for example with + setItalic()) and is therefore also suitable for rendering rich text. + + If Qt's internal font database cannot resolve the raw name, the + font becomes a raw font with \a name as its family. + + Note that the present implementation does not handle wildcards in + XLFDs well, and that font aliases (file \c fonts.alias in the font + directory on X11) are not supported. + + \sa rawName(), setRawMode(), setFamily() +*/ + +/*! + \fn QString QFont::lastResortFamily() const + + Returns the "last resort" font family name. + + The current implementation tries a wide variety of common fonts, + returning the first one it finds. Is is possible that no family is + found in which case an empty string is returned. + + \sa lastResortFont() +*/ + +/*! + \fn QString QFont::defaultFamily() const + + Returns the family name that corresponds to the current style + hint. + + \sa StyleHint styleHint() setStyleHint() +*/ + +/*! + \fn QString QFont::lastResortFont() const + + Returns a "last resort" font name for the font matching algorithm. + This is used if the last resort family is not available. It will + always return a name, if necessary returning something like + "fixed" or "system". + + The current implementation tries a wide variety of common fonts, + returning the first one it finds. The implementation may change + at any time, but this function will always return a string + containing something. + + It is theoretically possible that there really isn't a + lastResortFont() in which case Qt will abort with an error + message. We have not been able to identify a case where this + happens. Please \link bughowto.html report it as a bug\endlink if + it does, preferably with a list of the fonts you have installed. + + \sa lastResortFamily() rawName() +*/ + +/*! + Constructs a font from \a font for use on the paint device \a pd. +*/ +QFont::QFont(const QFont &font, QPaintDevice *pd) + : resolve_mask(font.resolve_mask) +{ + Q_ASSERT(pd != 0); + int dpi = pd->logicalDpiY(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(pd); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } +#ifdef Q_WS_WIN + if (pd->devType() == QInternal::Printer && pd->getDC()) + d->hdc = pd->getDC(); +#endif +} + +/*! + \internal +*/ +QFont::QFont(QFontPrivate *data) + : d(data), resolve_mask(QFont::AllPropertiesResolved) +{ +} + +/*! \internal + Detaches the font object from common font data. +*/ +void QFont::detach() +{ + if (d->ref == 1) { + if (d->engineData) + d->engineData->ref.deref(); + d->engineData = 0; + if (d->scFont && d->scFont != d.data()) + d->scFont->ref.deref(); + d->scFont = 0; + return; + } + + d.detach(); +} + +/*! + Constructs a font object that uses the application's default font. + + \sa QApplication::setFont(), QApplication::font() +*/ +QFont::QFont() + : d(QApplication::font().d.data()), resolve_mask(0) +{ +} + +/*! + Constructs a font object with the specified \a family, \a + pointSize, \a weight and \a italic settings. + + If \a pointSize is zero or negative, the point size of the font + is set to a system-dependent default value. Generally, this is + 12 points, except on Symbian where it is 7 points. + + The \a family name may optionally also include a foundry name, + e.g. "Helvetica [Cronyx]". If the \a family is + available from more than one foundry and the foundry isn't + specified, an arbitrary foundry is chosen. If the family isn't + available a family will be set using the \l{QFont}{font matching} + algorithm. + + \sa Weight, setFamily(), setPointSize(), setWeight(), setItalic(), + setStyleHint() QApplication::font() +*/ +QFont::QFont(const QString &family, int pointSize, int weight, bool italic) + : d(new QFontPrivate()), resolve_mask(QFont::FamilyResolved) +{ + if (pointSize <= 0) { +#ifdef Q_OS_SYMBIAN + pointSize = 7; +#else + pointSize = 12; +#endif + } else { + resolve_mask |= QFont::SizeResolved; + } + + if (weight < 0) { + weight = Normal; + } else { + resolve_mask |= QFont::WeightResolved | QFont::StyleResolved; + } + + if (italic) + resolve_mask |= QFont::StyleResolved; + + d->request.family = family; + d->request.pointSize = qreal(pointSize); + d->request.pixelSize = -1; + d->request.weight = weight; + d->request.style = italic ? QFont::StyleItalic : QFont::StyleNormal; +} + +/*! + Constructs a font that is a copy of \a font. +*/ +QFont::QFont(const QFont &font) + : d(font.d.data()), resolve_mask(font.resolve_mask) +{ +} + +/*! + Destroys the font object and frees all allocated resources. +*/ +QFont::~QFont() +{ +} + +/*! + Assigns \a font to this font and returns a reference to it. +*/ +QFont &QFont::operator=(const QFont &font) +{ + d = font.d.data(); + resolve_mask = font.resolve_mask; + return *this; +} + +/*! + Returns the requested font family name, i.e. the name set in the + constructor or the last setFont() call. + + \sa setFamily() substitutes() substitute() +*/ +QString QFont::family() const +{ + return d->request.family; +} + +/*! + Sets the family name of the font. The name is case insensitive and + may include a foundry name. + + The \a family name may optionally also include a foundry name, + e.g. "Helvetica [Cronyx]". If the \a family is + available from more than one foundry and the foundry isn't + specified, an arbitrary foundry is chosen. If the family isn't + available a family will be set using the \l{QFont}{font matching} + algorithm. + + \sa family(), setStyleHint(), QFontInfo +*/ +void QFont::setFamily(const QString &family) +{ + detach(); + + d->request.family = family; +#if defined(Q_WS_X11) + d->request.addStyle.clear(); +#endif // Q_WS_X11 + + resolve_mask |= QFont::FamilyResolved; +} + +/*! + Returns the point size of the font. Returns -1 if the font size + was specified in pixels. + + \sa setPointSize() pointSizeF() +*/ +int QFont::pointSize() const +{ + return qRound(d->request.pointSize); +} + +/*! + \since 4.8 + + \enum QFont::HintingPreference + + This enum describes the different levels of hinting that can be applied + to glyphs to improve legibility on displays where it might be warranted + by the density of pixels. + + \value PreferDefaultHinting Use the default hinting level for the target platform. + \value PreferNoHinting If possible, render text without hinting the outlines + of the glyphs. The text layout will be typographically accurate and + scalable, using the same metrics as are used e.g. when printing. + \value PreferVerticalHinting If possible, render text with no horizontal hinting, + but align glyphs to the pixel grid in the vertical direction. The text will appear + crisper on displays where the density is too low to give an accurate rendering + of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's + layout will be scalable to higher density devices (such as printers) without impacting + details such as line breaks. + \value PreferFullHinting If possible, render text with hinting in both horizontal and + vertical directions. The text will be altered to optimize legibility on the target + device, but since the metrics will depend on the target size of the text, the positions + of glyphs, line breaks, and other typographical detail will not scale, meaning that a + text layout may look different on devices with different pixel densities. + + Please note that this enum only describes a preference, as the full range of hinting levels + are not supported on all of Qt's supported platforms. The following table details the effect + of a given hinting preference on a selected set of target platforms. + + \table + \header + \o + \o PreferDefaultHinting + \o PreferNoHinting + \o PreferVerticalHinting + \o PreferFullHinting + \row + \o Windows Vista (w/o Platform Update) and earlier + \o Full hinting + \o Full hinting + \o Full hinting + \o Full hinting + \row + \o Windows 7 and Windows Vista (w/Platform Update) and DirectWrite enabled in Qt + \o Full hinting + \o Vertical hinting + \o Vertical hinting + \o Full hinting + \row + \o FreeType + \o Operating System setting + \o No hinting + \o Vertical hinting (light) + \o Full hinting + \row + \o Cocoa on Mac OS X + \o No hinting + \o No hinting + \o No hinting + \o No hinting + \endtable + + \note Please be aware that altering the hinting preference on Windows is available through + the DirectWrite font engine. This is available on Windows Vista after installing the platform + update, and on Windows 7. In order to use this extension, configure Qt using -directwrite. + The target application will then depend on the availability of DirectWrite on the target + system. + +*/ + +/*! + \since 4.8 + + Set the preference for the hinting level of the glyphs to \a hintingPreference. This is a hint + to the underlying font rendering system to use a certain level of hinting, and has varying + support across platforms. See the table in the documentation for QFont::HintingPreference for + more details. + + The default hinting preference is QFont::PreferDefaultHinting. +*/ +void QFont::setHintingPreference(HintingPreference hintingPreference) +{ + detach(); + + d->request.hintingPreference = hintingPreference; + + resolve_mask |= QFont::HintingPreferenceResolved; +} + +/*! + \since 4.8 + + Returns the currently preferred hinting level for glyphs rendered with this font. +*/ +QFont::HintingPreference QFont::hintingPreference() const +{ + return QFont::HintingPreference(d->request.hintingPreference); +} + +/*! + Sets the point size to \a pointSize. The point size must be + greater than zero. + + \sa pointSize() setPointSizeF() +*/ +void QFont::setPointSize(int pointSize) +{ + if (pointSize <= 0) { + qWarning("QFont::setPointSize: Point size <= 0 (%d), must be greater than 0", pointSize); + return; + } + + detach(); + + d->request.pointSize = qreal(pointSize); + d->request.pixelSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Sets the point size to \a pointSize. The point size must be + greater than zero. The requested precision may not be achieved on + all platforms. + + \sa pointSizeF() setPointSize() setPixelSize() +*/ +void QFont::setPointSizeF(qreal pointSize) +{ + if (pointSize <= 0) { + qWarning("QFont::setPointSizeF: Point size <= 0 (%f), must be greater than 0", pointSize); + return; + } + + detach(); + + d->request.pointSize = pointSize; + d->request.pixelSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Returns the point size of the font. Returns -1 if the font size was + specified in pixels. + + \sa pointSize() setPointSizeF() pixelSize() QFontInfo::pointSize() QFontInfo::pixelSize() +*/ +qreal QFont::pointSizeF() const +{ + return d->request.pointSize; +} + +/*! + Sets the font size to \a pixelSize pixels. + + Using this function makes the font device dependent. Use + setPointSize() or setPointSizeF() to set the size of the font + in a device independent manner. + + \sa pixelSize() +*/ +void QFont::setPixelSize(int pixelSize) +{ + if (pixelSize <= 0) { + qWarning("QFont::setPixelSize: Pixel size <= 0 (%d)", pixelSize); + return; + } + + detach(); + + d->request.pixelSize = pixelSize; + d->request.pointSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Returns the pixel size of the font if it was set with + setPixelSize(). Returns -1 if the size was set with setPointSize() + or setPointSizeF(). + + \sa setPixelSize() pointSize() QFontInfo::pointSize() QFontInfo::pixelSize() +*/ +int QFont::pixelSize() const +{ + return d->request.pixelSize; +} + +#ifdef QT3_SUPPORT +/*! \obsolete + + Sets the logical pixel height of font characters when shown on + the screen to \a pixelSize. +*/ +void QFont::setPixelSizeFloat(qreal pixelSize) +{ + setPixelSize((int)pixelSize); +} +#endif + +/*! + \fn bool QFont::italic() const + + Returns true if the style() of the font is not QFont::StyleNormal + + \sa setItalic() style() +*/ + +/*! + \fn void QFont::setItalic(bool enable) + + Sets the style() of the font to QFont::StyleItalic if \a enable is true; + otherwise the style is set to QFont::StyleNormal. + + \sa italic() QFontInfo +*/ + +/*! + Returns the style of the font. + + \sa setStyle() +*/ +QFont::Style QFont::style() const +{ + return (QFont::Style)d->request.style; +} + + +/*! + Sets the style of the font to \a style. + + \sa italic(), QFontInfo +*/ +void QFont::setStyle(Style style) +{ + detach(); + + d->request.style = style; + resolve_mask |= QFont::StyleResolved; +} + +/*! + Returns the weight of the font which is one of the enumerated + values from \l{QFont::Weight}. + + \sa setWeight(), Weight, QFontInfo +*/ +int QFont::weight() const +{ + return d->request.weight; +} + +/*! + \enum QFont::Weight + + Qt uses a weighting scale from 0 to 99 similar to, but not the + same as, the scales used in Windows or CSS. A weight of 0 is + ultralight, whilst 99 will be an extremely black. + + This enum contains the predefined font weights: + + \value Light 25 + \value Normal 50 + \value DemiBold 63 + \value Bold 75 + \value Black 87 +*/ + +/*! + Sets the weight the font to \a weight, which should be a value + from the \l QFont::Weight enumeration. + + \sa weight(), QFontInfo +*/ +void QFont::setWeight(int weight) +{ + Q_ASSERT_X(weight >= 0 && weight <= 99, "QFont::setWeight", "Weight must be between 0 and 99"); + + detach(); + + d->request.weight = weight; + resolve_mask |= QFont::WeightResolved; +} + +/*! + \fn bool QFont::bold() const + + Returns true if weight() is a value greater than \link Weight + QFont::Normal \endlink; otherwise returns false. + + \sa weight(), setBold(), QFontInfo::bold() +*/ + +/*! + \fn void QFont::setBold(bool enable) + + If \a enable is true sets the font's weight to \link Weight + QFont::Bold \endlink; otherwise sets the weight to \link Weight + QFont::Normal\endlink. + + For finer boldness control use setWeight(). + + \sa bold(), setWeight() +*/ + +/*! + Returns true if underline has been set; otherwise returns false. + + \sa setUnderline() +*/ +bool QFont::underline() const +{ + return d->underline; +} + +/*! + If \a enable is true, sets underline on; otherwise sets underline + off. + + \sa underline(), QFontInfo +*/ +void QFont::setUnderline(bool enable) +{ + detach(); + + d->underline = enable; + resolve_mask |= QFont::UnderlineResolved; +} + +/*! + Returns true if overline has been set; otherwise returns false. + + \sa setOverline() +*/ +bool QFont::overline() const +{ + return d->overline; +} + +/*! + If \a enable is true, sets overline on; otherwise sets overline off. + + \sa overline(), QFontInfo +*/ +void QFont::setOverline(bool enable) +{ + detach(); + + d->overline = enable; + resolve_mask |= QFont::OverlineResolved; +} + +/*! + Returns true if strikeout has been set; otherwise returns false. + + \sa setStrikeOut() +*/ +bool QFont::strikeOut() const +{ + return d->strikeOut; +} + +/*! + If \a enable is true, sets strikeout on; otherwise sets strikeout + off. + + \sa strikeOut(), QFontInfo +*/ +void QFont::setStrikeOut(bool enable) +{ + detach(); + + d->strikeOut = enable; + resolve_mask |= QFont::StrikeOutResolved; +} + +/*! + Returns true if fixed pitch has been set; otherwise returns false. + + \sa setFixedPitch(), QFontInfo::fixedPitch() +*/ +bool QFont::fixedPitch() const +{ + return d->request.fixedPitch; +} + +/*! + If \a enable is true, sets fixed pitch on; otherwise sets fixed + pitch off. + + \sa fixedPitch(), QFontInfo +*/ +void QFont::setFixedPitch(bool enable) +{ + detach(); + + d->request.fixedPitch = enable; + d->request.ignorePitch = false; + resolve_mask |= QFont::FixedPitchResolved; +} + +/*! + Returns true if kerning should be used when drawing text with this font. + + \sa setKerning() +*/ +bool QFont::kerning() const +{ + return d->kerning; +} + +/*! + Enables kerning for this font if \a enable is true; otherwise + disables it. By default, kerning is enabled. + + When kerning is enabled, glyph metrics do not add up anymore, + even for Latin text. In other words, the assumption that + width('a') + width('b') is equal to width("ab") is not + neccesairly true. + + \sa kerning(), QFontMetrics +*/ +void QFont::setKerning(bool enable) +{ + detach(); + d->kerning = enable; + resolve_mask |= QFont::KerningResolved; +} + +/*! + Returns the StyleStrategy. + + The style strategy affects the \l{QFont}{font matching} algorithm. + See \l QFont::StyleStrategy for the list of available strategies. + + \sa setStyleHint() QFont::StyleHint +*/ +QFont::StyleStrategy QFont::styleStrategy() const +{ + return (StyleStrategy) d->request.styleStrategy; +} + +/*! + Returns the StyleHint. + + The style hint affects the \l{QFont}{font matching} algorithm. + See \l QFont::StyleHint for the list of available hints. + + \sa setStyleHint(), QFont::StyleStrategy QFontInfo::styleHint() +*/ +QFont::StyleHint QFont::styleHint() const +{ + return (StyleHint) d->request.styleHint; +} + +/*! + \enum QFont::StyleHint + + Style hints are used by the \l{QFont}{font matching} algorithm to + find an appropriate default family if a selected font family is + not available. + + \value AnyStyle leaves the font matching algorithm to choose the + family. This is the default. + + \value SansSerif the font matcher prefer sans serif fonts. + \value Helvetica is a synonym for \c SansSerif. + + \value Serif the font matcher prefers serif fonts. + \value Times is a synonym for \c Serif. + + \value TypeWriter the font matcher prefers fixed pitch fonts. + \value Courier a synonym for \c TypeWriter. + + \value OldEnglish the font matcher prefers decorative fonts. + \value Decorative is a synonym for \c OldEnglish. + + \value Monospace the font matcher prefers fonts that map to the + CSS generic font-family 'monospace'. + + \value Fantasy the font matcher prefers fonts that map to the + CSS generic font-family 'fantasy'. + + \value Cursive the font matcher prefers fonts that map to the + CSS generic font-family 'cursive'. + + \value System the font matcher prefers system fonts. +*/ + +/*! + \enum QFont::StyleStrategy + + The style strategy tells the \l{QFont}{font matching} algorithm + what type of fonts should be used to find an appropriate default + family. + + The following strategies are available: + + \value PreferDefault the default style strategy. It does not prefer + any type of font. + \value PreferBitmap prefers bitmap fonts (as opposed to outline + fonts). + \value PreferDevice prefers device fonts. + \value PreferOutline prefers outline fonts (as opposed to bitmap fonts). + \value ForceOutline forces the use of outline fonts. + \value NoAntialias don't antialias the fonts. + \value PreferAntialias antialias if possible. + \value OpenGLCompatible forces the use of OpenGL compatible + fonts. + \value NoFontMerging If the font selected for a certain writing system + does not contain a character requested to draw, then Qt automatically chooses a similar + looking font that contains the character. The NoFontMerging flag disables this feature. + Please note that enabling this flag will not prevent Qt from automatically picking a + suitable font when the selected font does not support the writing system of the text. + + Any of these may be OR-ed with one of these flags: + + \value PreferMatch prefer an exact match. The font matcher will try to + use the exact font size that has been specified. + \value PreferQuality prefer the best quality font. The font matcher + will use the nearest standard point size that the font + supports. + \value ForceIntegerMetrics forces the use of integer values in font engines that support fractional + font metrics. +*/ + +/*! + Sets the style hint and strategy to \a hint and \a strategy, + respectively. + + If these aren't set explicitly the style hint will default to + \c AnyStyle and the style strategy to \c PreferDefault. + + Qt does not support style hints on X11 since this information + is not provided by the window system. + + \sa StyleHint, styleHint(), StyleStrategy, styleStrategy(), QFontInfo +*/ +void QFont::setStyleHint(StyleHint hint, StyleStrategy strategy) +{ + detach(); + + if ((resolve_mask & (QFont::StyleHintResolved | QFont::StyleStrategyResolved)) && + (StyleHint) d->request.styleHint == hint && + (StyleStrategy) d->request.styleStrategy == strategy) + return; + + d->request.styleHint = hint; + d->request.styleStrategy = strategy; + resolve_mask |= QFont::StyleHintResolved; + resolve_mask |= QFont::StyleStrategyResolved; + +#if defined(Q_WS_X11) + d->request.addStyle.clear(); +#endif // Q_WS_X11 +} + +/*! + Sets the style strategy for the font to \a s. + + \sa QFont::StyleStrategy +*/ +void QFont::setStyleStrategy(StyleStrategy s) +{ + detach(); + + if ((resolve_mask & QFont::StyleStrategyResolved) && + s == (StyleStrategy)d->request.styleStrategy) + return; + + d->request.styleStrategy = s; + resolve_mask |= QFont::StyleStrategyResolved; +} + + +/*! + \enum QFont::Stretch + + Predefined stretch values that follow the CSS naming convention. The higher + the value, the more stretched the text is. + + \value UltraCondensed 50 + \value ExtraCondensed 62 + \value Condensed 75 + \value SemiCondensed 87 + \value Unstretched 100 + \value SemiExpanded 112 + \value Expanded 125 + \value ExtraExpanded 150 + \value UltraExpanded 200 + + \sa setStretch() stretch() +*/ + +/*! + Returns the stretch factor for the font. + + \sa setStretch() + */ +int QFont::stretch() const +{ + return d->request.stretch; +} + +/*! + Sets the stretch factor for the font. + + The stretch factor changes the width of all characters in the font + by \a factor percent. For example, setting \a factor to 150 + results in all characters in the font being 1.5 times (ie. 150%) + wider. The default stretch factor is 100. The minimum stretch + factor is 1, and the maximum stretch factor is 4000. + + The stretch factor is only applied to outline fonts. The stretch + factor is ignored for bitmap fonts. + + NOTE: QFont cannot stretch XLFD fonts. When loading XLFD fonts on + X11, the stretch factor is matched against a predefined set of + values for the SETWIDTH_NAME field of the XLFD. + + \sa stretch() QFont::Stretch +*/ +void QFont::setStretch(int factor) +{ + if (factor < 1 || factor > 4000) { + qWarning("QFont::setStretch: Parameter '%d' out of range", factor); + return; + } + + if ((resolve_mask & QFont::StretchResolved) && + d->request.stretch == (uint)factor) + return; + + detach(); + + d->request.stretch = (uint)factor; + resolve_mask |= QFont::StretchResolved; +} + +/*! + \enum QFont::SpacingType + \since 4.4 + + \value PercentageSpacing A value of 100 will keep the spacing unchanged; a value of 200 will enlarge the + spacing after a character by the width of the character itself. + \value AbsoluteSpacing A positive value increases the letter spacing by the corresponding pixels; a negative + value decreases the spacing. +*/ + +/*! + \since 4.4 + Returns the letter spacing for the font. + + \sa setLetterSpacing(), letterSpacingType(), setWordSpacing() + */ +qreal QFont::letterSpacing() const +{ + return d->letterSpacing.toReal(); +} + +/*! + \since 4.4 + Sets the letter spacing for the font to \a spacing and the type + of spacing to \a type. + + Letter spacing changes the default spacing between individual + letters in the font. The spacing between the letters can be + made smaller as well as larger. + + \sa letterSpacing(), letterSpacingType(), setWordSpacing() +*/ +void QFont::setLetterSpacing(SpacingType type, qreal spacing) +{ + const QFixed newSpacing = QFixed::fromReal(spacing); + const bool absoluteSpacing = type == AbsoluteSpacing; + if ((resolve_mask & QFont::LetterSpacingResolved) && + d->letterSpacingIsAbsolute == absoluteSpacing && + d->letterSpacing == newSpacing) + return; + + detach(); + + d->letterSpacing = newSpacing; + d->letterSpacingIsAbsolute = absoluteSpacing; + resolve_mask |= QFont::LetterSpacingResolved; +} + +/*! + \since 4.4 + Returns the spacing type used for letter spacing. + + \sa letterSpacing(), setLetterSpacing(), setWordSpacing() +*/ +QFont::SpacingType QFont::letterSpacingType() const +{ + return d->letterSpacingIsAbsolute ? AbsoluteSpacing : PercentageSpacing; +} + +/*! + \since 4.4 + Returns the word spacing for the font. + + \sa setWordSpacing(), setLetterSpacing() + */ +qreal QFont::wordSpacing() const +{ + return d->wordSpacing.toReal(); +} + +/*! + \since 4.4 + Sets the word spacing for the font to \a spacing. + + Word spacing changes the default spacing between individual + words. A positive value increases the word spacing + by a corresponding amount of pixels, while a negative value + decreases the inter-word spacing accordingly. + + Word spacing will not apply to writing systems, where indiviaul + words are not separated by white space. + + \sa wordSpacing(), setLetterSpacing() +*/ +void QFont::setWordSpacing(qreal spacing) +{ + const QFixed newSpacing = QFixed::fromReal(spacing); + if ((resolve_mask & QFont::WordSpacingResolved) && + d->wordSpacing == newSpacing) + return; + + detach(); + + d->wordSpacing = newSpacing; + resolve_mask |= QFont::WordSpacingResolved; +} + +/*! + \enum QFont::Capitalization + \since 4.4 + + Rendering option for text this font applies to. + + + \value MixedCase This is the normal text rendering option where no capitalization change is applied. + \value AllUppercase This alters the text to be rendered in all uppercase type. + \value AllLowercase This alters the text to be rendered in all lowercase type. + \value SmallCaps This alters the text to be rendered in small-caps type. + \value Capitalize This alters the text to be rendered with the first character of each word as an uppercase character. +*/ + +/*! + \since 4.4 + Sets the capitalization of the text in this font to \a caps. + + A font's capitalization makes the text appear in the selected capitalization mode. + + \sa capitalization() +*/ +void QFont::setCapitalization(Capitalization caps) +{ + if ((resolve_mask & QFont::CapitalizationResolved) && + capitalization() == caps) + return; + + detach(); + + d->capital = caps; + resolve_mask |= QFont::CapitalizationResolved; +} + +/*! + \since 4.4 + Returns the current capitalization type of the font. + + \sa setCapitalization() +*/ +QFont::Capitalization QFont::capitalization() const +{ + return static_cast<QFont::Capitalization> (d->capital); +} + + +/*! + If \a enable is true, turns raw mode on; otherwise turns raw mode + off. This function only has an effect under X11. + + If raw mode is enabled, Qt will search for an X font with a + complete font name matching the family name, ignoring all other + values set for the QFont. If the font name matches several fonts, + Qt will use the first font returned by X. QFontInfo \e cannot be + used to fetch information about a QFont using raw mode (it will + return the values set in the QFont for all parameters, including + the family name). + + \warning Do not use raw mode unless you really, really need it! In + most (if not all) cases, setRawName() is a much better choice. + + \sa rawMode(), setRawName() +*/ +void QFont::setRawMode(bool enable) +{ + detach(); + + if ((bool) d->rawMode == enable) return; + + d->rawMode = enable; +} + +/*! + Returns true if a window system font exactly matching the settings + of this font is available. + + \sa QFontInfo +*/ +bool QFont::exactMatch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (d->rawMode + ? engine->type() != QFontEngine::Box + : d->request.exactMatch(engine->fontDef)); +} + +/*! + Returns true if this font is equal to \a f; otherwise returns + false. + + Two QFonts are considered equal if their font attributes are + equal. If rawMode() is enabled for both fonts, only the family + fields are compared. + + \sa operator!=() isCopyOf() +*/ +bool QFont::operator==(const QFont &f) const +{ + return (f.d == d + || (f.d->request == d->request + && f.d->request.pointSize == d->request.pointSize + && f.d->underline == d->underline + && f.d->overline == d->overline + && f.d->strikeOut == d->strikeOut + && f.d->kerning == d->kerning + && f.d->capital == d->capital + && f.d->letterSpacingIsAbsolute == d->letterSpacingIsAbsolute + && f.d->letterSpacing == d->letterSpacing + && f.d->wordSpacing == d->wordSpacing + )); +} + + +/*! + Provides an arbitrary comparison of this font and font \a f. + All that is guaranteed is that the operator returns false if both + fonts are equal and that (f1 \< f2) == !(f2 \< f1) if the fonts + are not equal. + + This function is useful in some circumstances, for example if you + want to use QFont objects as keys in a QMap. + + \sa operator==() operator!=() isCopyOf() +*/ +bool QFont::operator<(const QFont &f) const +{ + if (f.d == d) return false; + // the < operator for fontdefs ignores point sizes. + QFontDef &r1 = f.d->request; + QFontDef &r2 = d->request; + if (r1.pointSize != r2.pointSize) return r1.pointSize < r2.pointSize; + if (r1.pixelSize != r2.pixelSize) return r1.pixelSize < r2.pixelSize; + if (r1.weight != r2.weight) return r1.weight < r2.weight; + if (r1.style != r2.style) return r1.style < r2.style; + if (r1.stretch != r2.stretch) return r1.stretch < r2.stretch; + if (r1.styleHint != r2.styleHint) return r1.styleHint < r2.styleHint; + if (r1.styleStrategy != r2.styleStrategy) return r1.styleStrategy < r2.styleStrategy; + if (r1.family != r2.family) return r1.family < r2.family; +#ifdef Q_WS_X11 + if (r1.addStyle != r2.addStyle) return r1.addStyle < r2.addStyle; +#endif // Q_WS_X11 + if (f.d->capital != d->capital) return f.d->capital < d->capital; + + if (f.d->letterSpacingIsAbsolute != d->letterSpacingIsAbsolute) return f.d->letterSpacingIsAbsolute < d->letterSpacingIsAbsolute; + if (f.d->letterSpacing != d->letterSpacing) return f.d->letterSpacing < d->letterSpacing; + if (f.d->wordSpacing != d->wordSpacing) return f.d->wordSpacing < d->wordSpacing; + + int f1attrs = (f.d->underline << 3) + (f.d->overline << 2) + (f.d->strikeOut<<1) + f.d->kerning; + int f2attrs = (d->underline << 3) + (d->overline << 2) + (d->strikeOut<<1) + d->kerning; + return f1attrs < f2attrs; +} + + +/*! + Returns true if this font is different from \a f; otherwise + returns false. + + Two QFonts are considered to be different if their font attributes + are different. If rawMode() is enabled for both fonts, only the + family fields are compared. + + \sa operator==() +*/ +bool QFont::operator!=(const QFont &f) const +{ + return !(operator==(f)); +} + +/*! + Returns the font as a QVariant +*/ +QFont::operator QVariant() const +{ + return QVariant(QVariant::Font, this); +} + +/*! + Returns true if this font and \a f are copies of each other, i.e. + one of them was created as a copy of the other and neither has + been modified since. This is much stricter than equality. + + \sa operator=() operator==() +*/ +bool QFont::isCopyOf(const QFont & f) const +{ + return d == f.d; +} + +/*! + Returns true if raw mode is used for font name matching; otherwise + returns false. + + \sa setRawMode() rawName() +*/ +bool QFont::rawMode() const +{ + return d->rawMode; +} + +/*! + Returns a new QFont that has attributes copied from \a other that + have not been previously set on this font. +*/ +QFont QFont::resolve(const QFont &other) const +{ + if (*this == other + && (resolve_mask == other.resolve_mask || resolve_mask == 0) + && d->dpi == other.d->dpi) { + QFont o = other; + o.resolve_mask = resolve_mask; + return o; + } + + QFont font(*this); + font.detach(); + font.d->resolve(resolve_mask, other.d.data()); + + return font; +} + +/*! + \fn uint QFont::resolve() const + \internal +*/ + +/*! + \fn void QFont::resolve(uint mask) + \internal +*/ + +#ifdef QT3_SUPPORT + +/*! \obsolete + + Please use QApplication::font() instead. +*/ +QFont QFont::defaultFont() +{ + return QApplication::font(); +} + +/*! \obsolete + + Please use QApplication::setFont() instead. +*/ +void QFont::setDefaultFont(const QFont &f) +{ + QApplication::setFont(f); +} + +/*! + \fn qreal QFont::pointSizeFloat() const + \compat + + Use pointSizeF() instead. +*/ + +/*! + \fn void QFont::setPointSizeFloat(qreal size) + \compat + + Use setPointSizeF() instead. +*/ +#endif + + + + +/***************************************************************************** + QFont substitution management + *****************************************************************************/ + +typedef QHash<QString, QStringList> QFontSubst; +Q_GLOBAL_STATIC(QFontSubst, globalFontSubst) + +// create substitution dict +static void initFontSubst() +{ + // default substitutions + static const char * const initTbl[] = { + +#if defined(Q_WS_X11) + "arial", "helvetica", + "times new roman", "times", + "courier new", "courier", + "sans serif", "helvetica", +#elif defined(Q_WS_WIN) + "times", "times new roman", + "courier", "courier new", + "helvetica", "arial", + "sans serif", "arial", +#endif + + 0, 0 + }; + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + if (!fontSubst->isEmpty()) + return; +#if defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG) + if (X11->has_fontconfig) + return; +#endif + + for (int i=0; initTbl[i] != 0; i += 2) { + QStringList &list = (*fontSubst)[QString::fromLatin1(initTbl[i])]; + list.append(QString::fromLatin1(initTbl[i+1])); + } +} + +/*! + Returns the first family name to be used whenever \a familyName is + specified. The lookup is case insensitive. + + If there is no substitution for \a familyName, \a familyName is + returned. + + To obtain a list of substitutions use substitutes(). + + \sa setFamily() insertSubstitutions() insertSubstitution() removeSubstitution() +*/ +QString QFont::substitute(const QString &familyName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QFontSubst::ConstIterator it = fontSubst->constFind(familyName.toLower()); + if (it != fontSubst->constEnd() && !(*it).isEmpty()) + return (*it).first(); + + return familyName; +} + + +/*! + Returns a list of family names to be used whenever \a familyName + is specified. The lookup is case insensitive. + + If there is no substitution for \a familyName, an empty list is + returned. + + \sa substitute() insertSubstitutions() insertSubstitution() removeSubstitution() + */ +QStringList QFont::substitutes(const QString &familyName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + return fontSubst->value(familyName.toLower(), QStringList()); +} + + +/*! + Inserts \a substituteName into the substitution + table for the family \a familyName. + + \sa insertSubstitutions() removeSubstitution() substitutions() substitute() substitutes() +*/ +void QFont::insertSubstitution(const QString &familyName, + const QString &substituteName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList &list = (*fontSubst)[familyName.toLower()]; + QString s = substituteName.toLower(); + if (!list.contains(s)) + list.append(s); +} + + +/*! + Inserts the list of families \a substituteNames into the + substitution list for \a familyName. + + \sa insertSubstitution(), removeSubstitution(), substitutions(), substitute() +*/ +void QFont::insertSubstitutions(const QString &familyName, + const QStringList &substituteNames) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList &list = (*fontSubst)[familyName.toLower()]; + QStringList::ConstIterator it = substituteNames.constBegin(); + while (it != substituteNames.constEnd()) { + QString s = (*it).toLower(); + if (!list.contains(s)) + list.append(s); + it++; + } +} + +/*! \fn void QFont::initialize() + \internal + + Internal function that initializes the font system. The font cache + and font dict do not alloc the keys. The key is a QString which is + shared between QFontPrivate and QXFontName. +*/ + +/*! \fn void QFont::cleanup() + \internal + + Internal function that cleans up the font system. +*/ + +// ### mark: should be called removeSubstitutions() +/*! + Removes all the substitutions for \a familyName. + + \sa insertSubstitutions(), insertSubstitution(), substitutions(), substitute() +*/ +void QFont::removeSubstitution(const QString &familyName) +{ // ### function name should be removeSubstitutions() or + // ### removeSubstitutionList() + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + fontSubst->remove(familyName.toLower()); +} + + +/*! + Returns a sorted list of substituted family names. + + \sa insertSubstitution(), removeSubstitution(), substitute() +*/ +QStringList QFont::substitutions() +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList ret; + QFontSubst::ConstIterator it = fontSubst->constBegin(); + + while (it != fontSubst->constEnd()) { + ret.append(it.key()); + ++it; + } + + ret.sort(); + return ret; +} + + +/* \internal + Internal function. Converts boolean font settings to an unsigned + 8-bit number. Used for serialization etc. +*/ +static quint8 get_font_bits(int version, const QFontPrivate *f) +{ + Q_ASSERT(f != 0); + quint8 bits = 0; + if (f->request.style) + bits |= 0x01; + if (f->underline) + bits |= 0x02; + if (f->overline) + bits |= 0x40; + if (f->strikeOut) + bits |= 0x04; + if (f->request.fixedPitch) + bits |= 0x08; + // if (f.hintSetByUser) + // bits |= 0x10; + if (f->rawMode) + bits |= 0x20; + if (version >= QDataStream::Qt_4_0) { + if (f->kerning) + bits |= 0x10; + } + if (f->request.style == QFont::StyleOblique) + bits |= 0x80; + return bits; +} + +static quint8 get_extended_font_bits(const QFontPrivate *f) +{ + Q_ASSERT(f != 0); + quint8 bits = 0; + if (f->request.ignorePitch) + bits |= 0x01; + if (f->letterSpacingIsAbsolute) + bits |= 0x02; + return bits; +} + +#ifndef QT_NO_DATASTREAM + +/* \internal + Internal function. Sets boolean font settings from an unsigned + 8-bit number. Used for serialization etc. +*/ +static void set_font_bits(int version, quint8 bits, QFontPrivate *f) +{ + Q_ASSERT(f != 0); + f->request.style = (bits & 0x01) != 0 ? QFont::StyleItalic : QFont::StyleNormal; + f->underline = (bits & 0x02) != 0; + f->overline = (bits & 0x40) != 0; + f->strikeOut = (bits & 0x04) != 0; + f->request.fixedPitch = (bits & 0x08) != 0; + // f->hintSetByUser = (bits & 0x10) != 0; + f->rawMode = (bits & 0x20) != 0; + if (version >= QDataStream::Qt_4_0) + f->kerning = (bits & 0x10) != 0; + if ((bits & 0x80) != 0) + f->request.style = QFont::StyleOblique; +} + +static void set_extended_font_bits(quint8 bits, QFontPrivate *f) +{ + Q_ASSERT(f != 0); + f->request.ignorePitch = (bits & 0x01) != 0; + f->letterSpacingIsAbsolute = (bits & 0x02) != 0; +} +#endif + + +/*! + Returns the font's key, a textual representation of a font. It is + typically used as the key for a cache or dictionary of fonts. + + \sa QMap +*/ +QString QFont::key() const +{ + return toString(); +} + +/*! + Returns a description of the font. The description is a + comma-separated list of the attributes, perfectly suited for use + in QSettings. + + \sa fromString() + */ +QString QFont::toString() const +{ + const QChar comma(QLatin1Char(',')); + return family() + comma + + QString::number( pointSizeF()) + comma + + QString::number( pixelSize()) + comma + + QString::number((int) styleHint()) + comma + + QString::number( weight()) + comma + + QString::number((int) style()) + comma + + QString::number((int) underline()) + comma + + QString::number((int) strikeOut()) + comma + + QString::number((int)fixedPitch()) + comma + + QString::number((int) rawMode()); +} + + +/*! + Sets this font to match the description \a descrip. The description + is a comma-separated list of the font attributes, as returned by + toString(). + + \sa toString() + */ +bool QFont::fromString(const QString &descrip) +{ + QStringList l(descrip.split(QLatin1Char(','))); + + int count = l.count(); + if (!count || (count > 2 && count < 9) || count > 11) { + qWarning("QFont::fromString: Invalid description '%s'", + descrip.isEmpty() ? "(empty)" : descrip.toLatin1().data()); + return false; + } + + setFamily(l[0]); + if (count > 1 && l[1].toDouble() > 0.0) + setPointSizeF(l[1].toDouble()); + if (count == 9) { + setStyleHint((StyleHint) l[2].toInt()); + setWeight(qMax(qMin(99, l[3].toInt()), 0)); + setItalic(l[4].toInt()); + setUnderline(l[5].toInt()); + setStrikeOut(l[6].toInt()); + setFixedPitch(l[7].toInt()); + setRawMode(l[8].toInt()); + } else if (count == 10) { + if (l[2].toInt() > 0) + setPixelSize(l[2].toInt()); + setStyleHint((StyleHint) l[3].toInt()); + setWeight(qMax(qMin(99, l[4].toInt()), 0)); + setStyle((QFont::Style)l[5].toInt()); + setUnderline(l[6].toInt()); + setStrikeOut(l[7].toInt()); + setFixedPitch(l[8].toInt()); + setRawMode(l[9].toInt()); + } + if (count >= 9 && !d->request.fixedPitch) // assume 'false' fixedPitch equals default + d->request.ignorePitch = true; + + return true; +} + +#if !defined(Q_WS_QWS) +/*! \internal + + Internal function that dumps font cache statistics. +*/ +void QFont::cacheStatistics() +{ + + +} +#endif // !Q_WS_QWS + + + +/***************************************************************************** + QFont stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM + +/*! + \relates QFont + + Writes the font \a font to the data stream \a s. (toString() + writes to a text stream.) + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator<<(QDataStream &s, const QFont &font) +{ + if (s.version() == 1) { + s << font.d->request.family.toLatin1(); + } else { + s << font.d->request.family; + } + + if (s.version() >= QDataStream::Qt_4_0) { + // 4.0 + double pointSize = font.d->request.pointSize; + qint32 pixelSize = font.d->request.pixelSize; + s << pointSize; + s << pixelSize; + } else if (s.version() <= 3) { + qint16 pointSize = (qint16) (font.d->request.pointSize * 10); + if (pointSize < 0) { +#ifdef Q_WS_X11 + pointSize = (qint16)(font.d->request.pixelSize*720/QX11Info::appDpiY()); +#else + pointSize = (qint16)QFontInfo(font).pointSize() * 10; +#endif + } + s << pointSize; + } else { + s << (qint16) (font.d->request.pointSize * 10); + s << (qint16) font.d->request.pixelSize; + } + + s << (quint8) font.d->request.styleHint; + if (s.version() >= QDataStream::Qt_3_1) + s << (quint8) font.d->request.styleStrategy; + s << (quint8) 0 + << (quint8) font.d->request.weight + << get_font_bits(s.version(), font.d.data()); + if (s.version() >= QDataStream::Qt_4_3) + s << (quint16)font.d->request.stretch; + if (s.version() >= QDataStream::Qt_4_4) + s << get_extended_font_bits(font.d.data()); + if (s.version() >= QDataStream::Qt_4_5) { + s << font.d->letterSpacing.value(); + s << font.d->wordSpacing.value(); + } + return s; +} + + +/*! + \relates QFont + + Reads the font \a font from the data stream \a s. (fromString() + reads from a text stream.) + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator>>(QDataStream &s, QFont &font) +{ + font.d = new QFontPrivate; + font.resolve_mask = QFont::AllPropertiesResolved; + + quint8 styleHint, styleStrategy = QFont::PreferDefault, charSet, weight, bits; + + if (s.version() == 1) { + QByteArray fam; + s >> fam; + font.d->request.family = QString::fromLatin1(fam); + } else { + s >> font.d->request.family; + } + + if (s.version() >= QDataStream::Qt_4_0) { + // 4.0 + double pointSize; + qint32 pixelSize; + s >> pointSize; + s >> pixelSize; + font.d->request.pointSize = qreal(pointSize); + font.d->request.pixelSize = pixelSize; + } else { + qint16 pointSize, pixelSize = -1; + s >> pointSize; + if (s.version() >= 4) + s >> pixelSize; + font.d->request.pointSize = qreal(pointSize / 10.); + font.d->request.pixelSize = pixelSize; + } + s >> styleHint; + if (s.version() >= QDataStream::Qt_3_1) + s >> styleStrategy; + + s >> charSet; + s >> weight; + s >> bits; + + font.d->request.styleHint = styleHint; + font.d->request.styleStrategy = styleStrategy; + font.d->request.weight = weight; + + set_font_bits(s.version(), bits, font.d.data()); + + if (s.version() >= QDataStream::Qt_4_3) { + quint16 stretch; + s >> stretch; + font.d->request.stretch = stretch; + } + + if (s.version() >= QDataStream::Qt_4_4) { + quint8 extendedBits; + s >> extendedBits; + set_extended_font_bits(extendedBits, font.d.data()); + } + if (s.version() >= QDataStream::Qt_4_5) { + int value; + s >> value; + font.d->letterSpacing.setValue(value); + s >> value; + font.d->wordSpacing.setValue(value); + } + + return s; +} + +#endif // QT_NO_DATASTREAM + + +/***************************************************************************** + QFontInfo member functions + *****************************************************************************/ + +/*! + \class QFontInfo + \reentrant + + \brief The QFontInfo class provides general information about fonts. + + \ingroup appearance + \ingroup shared + + The QFontInfo class provides the same access functions as QFont, + e.g. family(), pointSize(), italic(), weight(), fixedPitch(), + styleHint() etc. But whilst the QFont access functions return the + values that were set, a QFontInfo object returns the values that + apply to the font that will actually be used to draw the text. + + For example, when the program asks for a 25pt Courier font on a + machine that has a non-scalable 24pt Courier font, QFont will + (normally) use the 24pt Courier for rendering. In this case, + QFont::pointSize() returns 25 and QFontInfo::pointSize() returns + 24. + + There are three ways to create a QFontInfo object. + \list 1 + \o Calling the QFontInfo constructor with a QFont creates a font + info object for a screen-compatible font, i.e. the font cannot be + a printer font. If the font is changed later, the font + info object is \e not updated. + + (Note: If you use a printer font the values returned may be + inaccurate. Printer fonts are not always accessible so the nearest + screen font is used if a printer font is supplied.) + + \o QWidget::fontInfo() returns the font info for a widget's font. + This is equivalent to calling QFontInfo(widget->font()). If the + widget's font is changed later, the font info object is \e not + updated. + + \o QPainter::fontInfo() returns the font info for a painter's + current font. If the painter's font is changed later, the font + info object is \e not updated. + \endlist + + \sa QFont QFontMetrics QFontDatabase +*/ + +/*! + Constructs a font info object for \a font. + + The font must be screen-compatible, i.e. a font you use when + drawing text in \link QWidget widgets\endlink or \link QPixmap + pixmaps\endlink, not QPicture or QPrinter. + + The font info object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QPainter::fontInfo() to get the font info when painting. + This will give correct results also when painting on paint device + that is not screen-compatible. +*/ +QFontInfo::QFontInfo(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a copy of \a fi. +*/ +QFontInfo::QFontInfo(const QFontInfo &fi) + : d(fi.d.data()) +{ +} + +/*! + Destroys the font info object. +*/ +QFontInfo::~QFontInfo() +{ +} + +/*! + Assigns the font info in \a fi. +*/ +QFontInfo &QFontInfo::operator=(const QFontInfo &fi) +{ + d = fi.d.data(); + return *this; +} + +/*! + Returns the family name of the matched window system font. + + \sa QFont::family() +*/ +QString QFontInfo::family() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.family; +} + +/*! + Returns the point size of the matched window system font. + + \sa pointSizeF() QFont::pointSize() +*/ +int QFontInfo::pointSize() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->fontDef.pointSize); +} + +/*! + Returns the point size of the matched window system font. + + \sa QFont::pointSizeF() +*/ +qreal QFontInfo::pointSizeF() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.pointSize; +} + +/*! + Returns the pixel size of the matched window system font. + + \sa QFont::pointSize() +*/ +int QFontInfo::pixelSize() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.pixelSize; +} + +/*! + Returns the italic value of the matched window system font. + + \sa QFont::italic() +*/ +bool QFontInfo::italic() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.style != QFont::StyleNormal; +} + +/*! + Returns the style value of the matched window system font. + + \sa QFont::style() +*/ +QFont::Style QFontInfo::style() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (QFont::Style)engine->fontDef.style; +} + +/*! + Returns the weight of the matched window system font. + + \sa QFont::weight(), bold() +*/ +int QFontInfo::weight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.weight; + +} + +/*! + \fn bool QFontInfo::bold() const + + Returns true if weight() would return a value greater than + QFont::Normal; otherwise returns false. + + \sa weight(), QFont::bold() +*/ + +/*! + Returns the underline value of the matched window system font. + + \sa QFont::underline() + + \internal + + Here we read the underline flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::underline() const +{ + return d->underline; +} + +/*! + Returns the overline value of the matched window system font. + + \sa QFont::overline() + + \internal + + Here we read the overline flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::overline() const +{ + return d->overline; +} + +/*! + Returns the strikeout value of the matched window system font. + + \sa QFont::strikeOut() + + \internal Here we read the strikeOut flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::strikeOut() const +{ + return d->strikeOut; +} + +/*! + Returns the fixed pitch value of the matched window system font. + + \sa QFont::fixedPitch() +*/ +bool QFontInfo::fixedPitch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); +#ifdef Q_OS_MAC + if (!engine->fontDef.fixedPitchComputed) { + QChar ch[2] = { QLatin1Char('i'), QLatin1Char('m') }; + QGlyphLayoutArray<2> g; + int l = 2; + engine->stringToCMap(ch, 2, &g, &l, 0); + engine->fontDef.fixedPitch = g.advances_x[0] == g.advances_x[1]; + engine->fontDef.fixedPitchComputed = true; + } +#endif + return engine->fontDef.fixedPitch; +} + +/*! + Returns the style of the matched window system font. + + Currently only returns the style hint set in QFont. + + \sa QFont::styleHint() QFont::StyleHint +*/ +QFont::StyleHint QFontInfo::styleHint() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (QFont::StyleHint) engine->fontDef.styleHint; +} + +/*! + Returns true if the font is a raw mode font; otherwise returns + false. + + If it is a raw mode font, all other functions in QFontInfo will + return the same values set in the QFont, regardless of the font + actually used. + + \sa QFont::rawMode() +*/ +bool QFontInfo::rawMode() const +{ + return d->rawMode; +} + +/*! + Returns true if the matched window system font is exactly the same + as the one specified by the font; otherwise returns false. + + \sa QFont::exactMatch() +*/ +bool QFontInfo::exactMatch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (d->rawMode + ? engine->type() != QFontEngine::Box + : d->request.exactMatch(engine->fontDef)); +} + + + + +// ********************************************************************** +// QFontCache +// ********************************************************************** + +#ifdef QFONTCACHE_DEBUG +// fast timeouts for debugging +static const int fast_timeout = 1000; // 1s +static const int slow_timeout = 5000; // 5s +#else +static const int fast_timeout = 10000; // 10s +static const int slow_timeout = 300000; // 5m +#endif // QFONTCACHE_DEBUG + +const uint QFontCache::min_cost = 4*1024; // 4mb + +#ifdef QT_NO_THREAD +Q_GLOBAL_STATIC(QFontCache, theFontCache) + +QFontCache *QFontCache::instance() +{ + return theFontCache(); +} + +void QFontCache::cleanup() +{ +} +#else +Q_GLOBAL_STATIC(QThreadStorage<QFontCache *>, theFontCache) + +QFontCache *QFontCache::instance() +{ + QFontCache *&fontCache = theFontCache()->localData(); + if (!fontCache) + fontCache = new QFontCache; + return fontCache; +} + +void QFontCache::cleanup() +{ + QThreadStorage<QFontCache *> *cache = 0; + QT_TRY { + cache = theFontCache(); + } QT_CATCH (const std::bad_alloc &) { + // no cache - just ignore + } + if (cache && cache->hasLocalData()) + cache->setLocalData(0); +} +#endif // QT_NO_THREAD + +QFontCache::QFontCache() + : QObject(), total_cost(0), max_cost(min_cost), + current_timestamp(0), fast(false), timer_id(-1) +{ +} + +QFontCache::~QFontCache() +{ + clear(); + { + EngineDataCache::ConstIterator it = engineDataCache.constBegin(), + end = engineDataCache.constEnd(); + while (it != end) { + if (it.value()->ref == 0) + delete it.value(); + else + FC_DEBUG("QFontCache::~QFontCache: engineData %p still has refcount %d", + it.value(), int(it.value()->ref)); + ++it; + } + } + EngineCache::ConstIterator it = engineCache.constBegin(), + end = engineCache.constEnd(); + while (it != end) { + if (--it.value().data->cache_count == 0) { + if (it.value().data->ref == 0) { + FC_DEBUG("QFontCache::~QFontCache: deleting engine %p key=(%d / %g %g %d %d %d)", + it.value().data, it.key().script, it.key().def.pointSize, + it.key().def.pixelSize, it.key().def.weight, it.key().def.style, + it.key().def.fixedPitch); + + delete it.value().data; + } else { + FC_DEBUG("QFontCache::~QFontCache: engine = %p still has refcount %d", + it.value().data, int(it.value().data->ref)); + } + } + ++it; + } +} + +void QFontCache::clear() +{ + { + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + QFontEngineData *data = it.value(); +#if !defined(Q_WS_MAC) + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (data->engines[i]) { + data->engines[i]->ref.deref(); + data->engines[i] = 0; + } + } +#else + if (data->engine) { + data->engine->ref.deref(); + data->engine = 0; + } +#endif + ++it; + } + } + + for (EngineCache::Iterator it = engineCache.begin(), end = engineCache.end(); + it != end; ++it) { + if (it->data->ref == 0) { + delete it->data; + it->data = 0; + } + } + + for (EngineCache::Iterator it = engineCache.begin(), end = engineCache.end(); + it != end; ++it) { + if (it->data && it->data->ref == 0) { + delete it->data; + it->data = 0; + } + } + + engineCache.clear(); +} + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) +void QFontCache::removeEngineForFont(const QByteArray &_fontName) +{ + + /* This could be optimized but the code becomes much more complex if we want to handle multi + * font engines and it is probably not worth it. Therefore we just clear the entire font cache. + */ + Q_UNUSED(_fontName); + clear(); +} +#endif + +QFontEngineData *QFontCache::findEngineData(const Key &key) const +{ + EngineDataCache::ConstIterator it = engineDataCache.find(key), + end = engineDataCache.end(); + if (it == end) return 0; + + // found + return it.value(); +} + +void QFontCache::insertEngineData(const Key &key, QFontEngineData *engineData) +{ + FC_DEBUG("QFontCache: inserting new engine data %p", engineData); + + engineDataCache.insert(key, engineData); + increaseCost(sizeof(QFontEngineData)); +} + +QFontEngine *QFontCache::findEngine(const Key &key) +{ + EngineCache::Iterator it = engineCache.find(key), + end = engineCache.end(); + if (it == end) return 0; + + // found... update the hitcount and timestamp + it.value().hits++; + it.value().timestamp = ++current_timestamp; + + FC_DEBUG("QFontCache: found font engine\n" + " %p: timestamp %4u hits %3u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + return it.value().data; +} + +void QFontCache::insertEngine(const Key &key, QFontEngine *engine) +{ + FC_DEBUG("QFontCache: inserting new engine %p", engine); + + Engine data(engine); + data.timestamp = ++current_timestamp; + + engineCache.insert(key, data); + + // only increase the cost if this is the first time we insert the engine + if (engine->cache_count == 0) + increaseCost(engine->cache_cost); + + ++engine->cache_count; +} + +void QFontCache::increaseCost(uint cost) +{ + cost = (cost + 512) / 1024; // store cost in kb + cost = cost > 0 ? cost : 1; + total_cost += cost; + + FC_DEBUG(" COST: increased %u kb, total_cost %u kb, max_cost %u kb", + cost, total_cost, max_cost); + + if (total_cost > max_cost) { + max_cost = total_cost; + + if (timer_id == -1 || ! fast) { + FC_DEBUG(" TIMER: starting fast timer (%d ms)", fast_timeout); + + if (timer_id != -1) killTimer(timer_id); + timer_id = startTimer(fast_timeout); + fast = true; + } + } +} + +void QFontCache::decreaseCost(uint cost) +{ + cost = (cost + 512) / 1024; // cost is stored in kb + cost = cost > 0 ? cost : 1; + Q_ASSERT(cost <= total_cost); + total_cost -= cost; + + FC_DEBUG(" COST: decreased %u kb, total_cost %u kb, max_cost %u kb", + cost, total_cost, max_cost); +} + +#if defined(Q_WS_WIN) || defined (Q_WS_QWS) +void QFontCache::cleanupPrinterFonts() +{ + FC_DEBUG("QFontCache::cleanupPrinterFonts"); + + { + FC_DEBUG(" CLEAN engine data:"); + + // clean out all unused engine data + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + if (it.key().screen == 0) { + ++it; + continue; + } + + if(it.value()->ref != 0) { + for(int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if(it.value()->engines[i]) { + it.value()->engines[i]->ref.deref(); + it.value()->engines[i] = 0; + } + } + ++it; + } else { + + EngineDataCache::Iterator rem = it++; + + decreaseCost(sizeof(QFontEngineData)); + + FC_DEBUG(" %p", rem.value()); + + delete rem.value(); + engineDataCache.erase(rem); + } + } + } + + EngineCache::Iterator it = engineCache.begin(), + end = engineCache.end(); + while(it != end) { + if (it.value().data->ref != 0 || it.key().screen == 0) { + ++it; + continue; + } + + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + if (--it.value().data->cache_count == 0) { + FC_DEBUG(" DELETE: last occurrence in cache"); + + decreaseCost(it.value().data->cache_cost); + delete it.value().data; + } + + engineCache.erase(it++); + } +} +#endif + +void QFontCache::timerEvent(QTimerEvent *) +{ + FC_DEBUG("QFontCache::timerEvent: performing cache maintenance (timestamp %u)", + current_timestamp); + + if (total_cost <= max_cost && max_cost <= min_cost) { + FC_DEBUG(" cache redused sufficiently, stopping timer"); + + killTimer(timer_id); + timer_id = -1; + fast = false; + + return; + } + + // go through the cache and count up everything in use + uint in_use_cost = 0; + + { + FC_DEBUG(" SWEEP engine data:"); + + // make sure the cost of each engine data is at least 1kb + const uint engine_data_cost = + sizeof(QFontEngineData) > 1024 ? sizeof(QFontEngineData) : 1024; + + EngineDataCache::ConstIterator it = engineDataCache.constBegin(), + end = engineDataCache.constEnd(); + for (; it != end; ++it) { +#ifdef QFONTCACHE_DEBUG + FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref)); + +# if defined(Q_WS_X11) || defined(Q_WS_WIN) + // print out all engines + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (! it.value()->engines[i]) + continue; + FC_DEBUG(" contains %p", it.value()->engines[i]); + } +# endif // Q_WS_X11 || Q_WS_WIN +#endif // QFONTCACHE_DEBUG + + if (it.value()->ref != 0) + in_use_cost += engine_data_cost; + } + } + + { + FC_DEBUG(" SWEEP engine:"); + + EngineCache::ConstIterator it = engineCache.constBegin(), + end = engineCache.constEnd(); + for (; it != end; ++it) { + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, cost %u bytes", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->cache_cost); + + if (it.value().data->ref != 0) + in_use_cost += it.value().data->cache_cost / it.value().data->cache_count; + } + + // attempt to make up for rounding errors + in_use_cost += engineCache.size(); + } + + in_use_cost = (in_use_cost + 512) / 1024; // cost is stored in kb + + /* + calculate the new maximum cost for the cache + + NOTE: in_use_cost is *not* correct due to rounding errors in the + above algorithm. instead of worrying about getting the + calculation correct, we are more interested in speed, and use + in_use_cost as a floor for new_max_cost + */ + uint new_max_cost = qMax(qMax(max_cost / 2, in_use_cost), min_cost); + + FC_DEBUG(" after sweep, in use %u kb, total %u kb, max %u kb, new max %u kb", + in_use_cost, total_cost, max_cost, new_max_cost); + + if (new_max_cost == max_cost) { + if (fast) { + FC_DEBUG(" cannot shrink cache, slowing timer"); + + killTimer(timer_id); + timer_id = startTimer(slow_timeout); + fast = false; + } + + return; + } else if (! fast) { + FC_DEBUG(" dropping into passing gear"); + + killTimer(timer_id); + timer_id = startTimer(fast_timeout); + fast = true; + } + + max_cost = new_max_cost; + + { + FC_DEBUG(" CLEAN engine data:"); + + // clean out all unused engine data + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + if (it.value()->ref != 0) { + ++it; + continue; + } + + EngineDataCache::Iterator rem = it++; + + decreaseCost(sizeof(QFontEngineData)); + + FC_DEBUG(" %p", rem.value()); + + delete rem.value(); + engineDataCache.erase(rem); + } + } + + // clean out the engine cache just enough to get below our new max cost + uint current_cost; + do { + current_cost = total_cost; + + EngineCache::Iterator it = engineCache.begin(), + end = engineCache.end(); + // determine the oldest and least popular of the unused engines + uint oldest = ~0u; + uint least_popular = ~0u; + + for (; it != end; ++it) { + if (it.value().data->ref != 0) + continue; + + if (it.value().timestamp < oldest && + it.value().hits <= least_popular) { + oldest = it.value().timestamp; + least_popular = it.value().hits; + } + } + + FC_DEBUG(" oldest %u least popular %u", oldest, least_popular); + + for (it = engineCache.begin(); it != end; ++it) { + if (it.value().data->ref == 0 && + it.value().timestamp == oldest && + it.value().hits == least_popular) + break; + } + + if (it != end) { + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + if (--it.value().data->cache_count == 0) { + FC_DEBUG(" DELETE: last occurrence in cache"); + + decreaseCost(it.value().data->cache_cost); + delete it.value().data; + } else { + /* + this particular font engine is in the cache multiple + times... set current_cost to zero, so that we can + keep looping to get rid of all occurrences + */ + current_cost = 0; + } + + engineCache.erase(it); + } + } while (current_cost != total_cost && total_cost > max_cost); +} + + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug stream, const QFont &font) +{ + return stream << "QFont(" << font.toString() << ')'; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h new file mode 100644 index 0000000000..8dbc746813 --- /dev/null +++ b/src/gui/text/qfont.h @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONT_H +#define QFONT_H + +#include <QtGui/qwindowdefs.h> +#include <QtCore/qstring.h> +#include <QtCore/qsharedpointer.h> + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) +typedef struct FT_FaceRec_* FT_Face; +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontPrivate; /* don't touch */ +class QStringList; +class QVariant; +class Q3TextFormatCollection; + +class Q_GUI_EXPORT QFont +{ + Q_GADGET + Q_ENUMS(StyleStrategy) +public: + enum StyleHint { + Helvetica, SansSerif = Helvetica, + Times, Serif = Times, + Courier, TypeWriter = Courier, + OldEnglish, Decorative = OldEnglish, + System, + AnyStyle, + Cursive, + Monospace, + Fantasy + }; + + enum StyleStrategy { + PreferDefault = 0x0001, + PreferBitmap = 0x0002, + PreferDevice = 0x0004, + PreferOutline = 0x0008, + ForceOutline = 0x0010, + PreferMatch = 0x0020, + PreferQuality = 0x0040, + PreferAntialias = 0x0080, + NoAntialias = 0x0100, + OpenGLCompatible = 0x0200, + ForceIntegerMetrics = 0x0400, + NoFontMerging = 0x8000 + }; + + enum HintingPreference { + PreferDefaultHinting = 0, + PreferNoHinting = 1, + PreferVerticalHinting = 2, + PreferFullHinting = 3 + }; + + enum Weight { + Light = 25, + Normal = 50, + DemiBold = 63, + Bold = 75, + Black = 87 + }; + + enum Style { + StyleNormal, + StyleItalic, + StyleOblique + }; + + enum Stretch { + UltraCondensed = 50, + ExtraCondensed = 62, + Condensed = 75, + SemiCondensed = 87, + Unstretched = 100, + SemiExpanded = 112, + Expanded = 125, + ExtraExpanded = 150, + UltraExpanded = 200 + }; + + enum Capitalization { + MixedCase, + AllUppercase, + AllLowercase, + SmallCaps, + Capitalize + }; + + enum SpacingType { + PercentageSpacing, + AbsoluteSpacing + }; + + enum ResolveProperties { + FamilyResolved = 0x0001, + SizeResolved = 0x0002, + StyleHintResolved = 0x0004, + StyleStrategyResolved = 0x0008, + WeightResolved = 0x0010, + StyleResolved = 0x0020, + UnderlineResolved = 0x0040, + OverlineResolved = 0x0080, + StrikeOutResolved = 0x0100, + FixedPitchResolved = 0x0200, + StretchResolved = 0x0400, + KerningResolved = 0x0800, + CapitalizationResolved = 0x1000, + LetterSpacingResolved = 0x2000, + WordSpacingResolved = 0x4000, + HintingPreferenceResolved = 0x8000, + AllPropertiesResolved = 0xffff + }; + + QFont(); + QFont(const QString &family, int pointSize = -1, int weight = -1, bool italic = false); + QFont(const QFont &, QPaintDevice *pd); + QFont(const QFont &); + ~QFont(); + + QString family() const; + void setFamily(const QString &); + + int pointSize() const; + void setPointSize(int); + qreal pointSizeF() const; + void setPointSizeF(qreal); + + int pixelSize() const; + void setPixelSize(int); + + int weight() const; + void setWeight(int); + + inline bool bold() const; + inline void setBold(bool); + + void setStyle(Style style); + Style style() const; + + inline bool italic() const; + inline void setItalic(bool b); + + bool underline() const; + void setUnderline(bool); + + bool overline() const; + void setOverline(bool); + + bool strikeOut() const; + void setStrikeOut(bool); + + bool fixedPitch() const; + void setFixedPitch(bool); + + bool kerning() const; + void setKerning(bool); + + StyleHint styleHint() const; + StyleStrategy styleStrategy() const; + void setStyleHint(StyleHint, StyleStrategy = PreferDefault); + void setStyleStrategy(StyleStrategy s); + + int stretch() const; + void setStretch(int); + + qreal letterSpacing() const; + SpacingType letterSpacingType() const; + void setLetterSpacing(SpacingType type, qreal spacing); + + qreal wordSpacing() const; + void setWordSpacing(qreal spacing); + + void setCapitalization(Capitalization); + Capitalization capitalization() const; + + void setHintingPreference(HintingPreference hintingPreference); + HintingPreference hintingPreference() const; + + // is raw mode still needed? + bool rawMode() const; + void setRawMode(bool); + + // dupicated from QFontInfo + bool exactMatch() const; + + QFont &operator=(const QFont &); + bool operator==(const QFont &) const; + bool operator!=(const QFont &) const; + bool operator<(const QFont &) const; + operator QVariant() const; + bool isCopyOf(const QFont &) const; +#ifdef Q_COMPILER_RVALUE_REFS + inline QFont &operator=(QFont &&other) + { qSwap(d, other.d); return *this; } +#endif + +#ifdef Q_WS_WIN + HFONT handle() const; +#else // !Q_WS_WIN + Qt::HANDLE handle() const; +#endif // Q_WS_WIN +#ifdef Q_WS_MAC + quint32 macFontID() const; +#endif +#if defined(Q_WS_X11) || defined(Q_WS_QWS) + FT_Face freetypeFace() const; +#endif + + // needed for X11 + void setRawName(const QString &); + QString rawName() const; + + QString key() const; + + QString toString() const; + bool fromString(const QString &); + + static QString substitute(const QString &); + static QStringList substitutes(const QString &); + static QStringList substitutions(); + static void insertSubstitution(const QString&, const QString &); + static void insertSubstitutions(const QString&, const QStringList &); + static void removeSubstitution(const QString &); + static void initialize(); + static void cleanup(); +#ifndef Q_WS_QWS + static void cacheStatistics(); +#endif + + QString defaultFamily() const; + QString lastResortFamily() const; + QString lastResortFont() const; + + QFont resolve(const QFont &) const; + inline uint resolve() const { return resolve_mask; } + inline void resolve(uint mask) { resolve_mask = mask; } + +#ifdef QT3_SUPPORT + static QT3_SUPPORT QFont defaultFont(); + static QT3_SUPPORT void setDefaultFont(const QFont &); + QT3_SUPPORT void setPixelSizeFloat(qreal); + QT3_SUPPORT qreal pointSizeFloat() const { return pointSizeF(); } + QT3_SUPPORT void setPointSizeFloat(qreal size) { setPointSizeF(size); } +#endif + +private: + QFont(QFontPrivate *); + + void detach(); + +#if defined(Q_WS_MAC) + void macSetFont(QPaintDevice *); +#elif defined(Q_WS_X11) + void x11SetScreen(int screen = -1); + int x11Screen() const; +#endif + + friend class QFontPrivate; + friend class QFontDialogPrivate; + friend class QFontMetrics; + friend class QFontMetricsF; + friend class QFontInfo; + friend class QPainter; + friend class QPainterPrivate; + friend class QPSPrintEngineFont; + friend class QApplication; + friend class QWidget; + friend class QWidgetPrivate; + friend class Q3TextFormatCollection; + friend class QTextLayout; + friend class QTextEngine; + friend class QStackTextEngine; + friend class QTextLine; + friend struct QScriptLine; + friend class QGLContext; + friend class QWin32PaintEngine; + friend class QAlphaPaintEngine; + friend class QPainterPath; + friend class QTextItemInt; + friend class QPicturePaintEngine; + friend class QPainterReplayer; + friend class QPaintBufferEngine; + friend class QCommandLinkButtonPrivate; + friend class QFontEngine; + +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QFont &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &); +#endif + + QExplicitlySharedDataPointer<QFontPrivate> d; + uint resolve_mask; +}; + + +inline bool QFont::bold() const +{ return weight() > Normal; } + + +inline void QFont::setBold(bool enable) +{ setWeight(enable ? Bold : Normal); } + +inline bool QFont::italic() const +{ + return (style() != StyleNormal); +} + +inline void QFont::setItalic(bool b) { + setStyle(b ? StyleItalic : StyleNormal); +} + + +/***************************************************************************** + QFont stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QFont &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QFont &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONT_H diff --git a/src/gui/text/qfont_mac.cpp b/src/gui/text/qfont_mac.cpp new file mode 100644 index 0000000000..daf68c03ea --- /dev/null +++ b/src/gui/text/qfont_mac.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qfontengine_mac_p.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpaintdevice.h" +#include "qstring.h" +#include <private/qt_mac_p.h> +#include <private/qtextengine_p.h> +#include <private/qunicodetables_p.h> +#include <qapplication.h> +#include "qfontdatabase.h" +#include <qpainter.h> +#include "qtextengine_p.h" +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + +int qt_mac_pixelsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pixelSize == -1) + ret = def.pointSize * dpi / qt_mac_defaultDpi_x(); + else + ret = def.pixelSize; + return qRound(ret); +} +int qt_mac_pointsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pointSize < 0) + ret = def.pixelSize * qt_mac_defaultDpi_x() / float(dpi); + else + ret = def.pointSize; + return qRound(ret); +} + +QString QFont::rawName() const +{ + return family(); +} + +void QFont::setRawName(const QString &name) +{ + setFamily(name); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +/*! + Returns an ATSUFontID +*/ +quint32 QFont::macFontID() const // ### need 64-bit version +{ +#ifdef QT_MAC_USE_COCOA + return 0; +#elif 1 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Multi) + return static_cast<QFontEngineMacMulti*>(fe)->macFontID(); +#else + Str255 name; + if(FMGetFontFamilyName((FMFontFamily)((UInt32)handle()), name) == noErr) { + short fnum; + GetFNum(name, &fnum); + return fnum; + } +#endif + return 0; +} + +// Returns an ATSUFonFamilyRef +Qt::HANDLE QFont::handle() const +{ +#if 0 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Mac) + return (Qt::HANDLE)static_cast<QFontEngineMacMulti*>(fe)->fontFamilyRef(); +#endif + return 0; +} + +void QFont::initialize() +{ } + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times New Roman"); + case QFont::Courier: + return QString::fromLatin1("Courier New"); + case QFont::Monospace: + return QString::fromLatin1("Courier"); + case QFont::Decorative: + return QString::fromLatin1("Bookman Old Style"); + case QFont::Cursive: + return QString::fromLatin1("Apple Chancery"); + case QFont::Fantasy: + return QString::fromLatin1("Papyrus"); + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("Helvetica"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("Helvetica"); +} + +QString QFont::lastResortFont() const +{ + return QString::fromLatin1("Geneva"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h new file mode 100644 index 0000000000..43dca81e55 --- /dev/null +++ b/src/gui/text/qfont_p.h @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONT_P_H +#define QFONT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qfont.h" +#include "QtCore/qmap.h" +#include "QtCore/qobject.h" +#include <private/qunicodetables_p.h> +#include <QtGui/qfontdatabase.h> +#include "private/qfixed_p.h" + +QT_BEGIN_NAMESPACE + +// forwards +class QFontCache; +class QFontEngine; + +struct QFontDef +{ + inline QFontDef() + : pointSize(-1.0), pixelSize(-1), + styleStrategy(QFont::PreferDefault), styleHint(QFont::AnyStyle), + weight(50), fixedPitch(false), style(QFont::StyleNormal), stretch(100), + ignorePitch(true), hintingPreference(QFont::PreferDefaultHinting) +#ifdef Q_WS_MAC + ,fixedPitchComputed(false) +#endif + { + } + + QString family; + +#ifdef Q_WS_X11 + QString addStyle; +#endif // Q_WS_X11 + + qreal pointSize; + qreal pixelSize; + + uint styleStrategy : 16; + uint styleHint : 8; + + uint weight : 7; // 0-99 + uint fixedPitch : 1; + uint style : 2; + uint stretch : 12; // 0-400 + + uint ignorePitch : 1; + uint hintingPreference : 2; + uint fixedPitchComputed : 1; // for Mac OS X only + int reserved : 14; // for future extensions + + bool exactMatch(const QFontDef &other) const; + bool operator==(const QFontDef &other) const + { + return pixelSize == other.pixelSize + && weight == other.weight + && style == other.style + && stretch == other.stretch + && styleHint == other.styleHint + && styleStrategy == other.styleStrategy + && ignorePitch == other.ignorePitch && fixedPitch == other.fixedPitch + && family == other.family + && hintingPreference == other.hintingPreference +#ifdef Q_WS_X11 + && addStyle == other.addStyle +#endif + ; + } + inline bool operator<(const QFontDef &other) const + { + if (pixelSize != other.pixelSize) return pixelSize < other.pixelSize; + if (weight != other.weight) return weight < other.weight; + if (style != other.style) return style < other.style; + if (stretch != other.stretch) return stretch < other.stretch; + if (styleHint != other.styleHint) return styleHint < other.styleHint; + if (styleStrategy != other.styleStrategy) return styleStrategy < other.styleStrategy; + if (family != other.family) return family < other.family; + if (hintingPreference != other.hintingPreference) return hintingPreference < other.hintingPreference; + +#ifdef Q_WS_X11 + if (addStyle != other.addStyle) return addStyle < other.addStyle; +#endif // Q_WS_X11 + + if (ignorePitch != other.ignorePitch) return ignorePitch < other.ignorePitch; + if (fixedPitch != other.fixedPitch) return fixedPitch < other.fixedPitch; + return false; + } +}; + +class QFontEngineData +{ +public: + QFontEngineData(); + ~QFontEngineData(); + + QAtomicInt ref; + QFontCache *fontCache; + +#if !defined(Q_WS_MAC) + QFontEngine *engines[QUnicodeTables::ScriptCount]; +#else + QFontEngine *engine; +#endif +}; + + +class Q_GUI_EXPORT QFontPrivate +{ +public: +#ifdef Q_WS_X11 + static int defaultEncodingID; +#endif // Q_WS_X11 + + QFontPrivate(); + QFontPrivate(const QFontPrivate &other); + ~QFontPrivate(); + + QFontEngine *engineForScript(int script) const; + void alterCharForCapitalization(QChar &c) const; + + QAtomicInt ref; + QFontDef request; + mutable QFontEngineData *engineData; + int dpi; + int screen; + +#ifdef Q_WS_WIN + HDC hdc; +#endif + + uint rawMode : 1; + uint underline : 1; + uint overline : 1; + uint strikeOut : 1; + uint kerning : 1; + uint capital : 3; + bool letterSpacingIsAbsolute : 1; + + QFixed letterSpacing; + QFixed wordSpacing; + + mutable QFontPrivate *scFont; + QFont smallCapsFont() const { return QFont(smallCapsFontPrivate()); } + QFontPrivate *smallCapsFontPrivate() const; + + static QFontPrivate *get(const QFont &font) + { + return font.d.data(); + } + + void resolve(uint mask, const QFontPrivate *other); +private: + QFontPrivate &operator=(const QFontPrivate &) { return *this; } +}; + + +class QFontCache : public QObject +{ + Q_OBJECT +public: + // note: these static functions work on a per-thread basis + static QFontCache *instance(); + static void cleanup(); + + QFontCache(); + ~QFontCache(); + + void clear(); +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) + void removeEngineForFont(const QByteArray &fontName); +#endif + // universal key structure. QFontEngineDatas and QFontEngines are cached using + // the same keys + struct Key { + Key() : script(0), screen(0) { } + Key(const QFontDef &d, int c, int s = 0) + : def(d), script(c), screen(s) { } + + QFontDef def; + int script; + int screen; + + inline bool operator<(const Key &other) const + { + if (script != other.script) return script < other.script; + if (screen != other.screen) return screen < other.screen; + return def < other.def; + } + inline bool operator==(const Key &other) const + { return def == other.def && script == other.script && screen == other.screen; } + }; + + // QFontEngineData cache + typedef QMap<Key,QFontEngineData*> EngineDataCache; + EngineDataCache engineDataCache; + + QFontEngineData *findEngineData(const Key &key) const; + void insertEngineData(const Key &key, QFontEngineData *engineData); + + // QFontEngine cache + struct Engine { + Engine() : data(0), timestamp(0), hits(0) { } + Engine(QFontEngine *d) : data(d), timestamp(0), hits(0) { } + + QFontEngine *data; + uint timestamp; + uint hits; + }; + + typedef QMap<Key,Engine> EngineCache; + EngineCache engineCache; + + QFontEngine *findEngine(const Key &key); + void insertEngine(const Key &key, QFontEngine *engine); + +#if defined(Q_WS_WIN) || defined(Q_WS_QWS) + void cleanupPrinterFonts(); +#endif + + private: + void increaseCost(uint cost); + void decreaseCost(uint cost); + void timerEvent(QTimerEvent *event); + + static const uint min_cost; + uint total_cost, max_cost; + uint current_timestamp; + bool fast; + int timer_id; +}; + +Q_GUI_EXPORT int qt_defaultDpiX(); +Q_GUI_EXPORT int qt_defaultDpiY(); +Q_GUI_EXPORT int qt_defaultDpi(); + +QT_END_NAMESPACE + +#endif // QFONT_P_H diff --git a/src/gui/text/qfont_qpa.cpp b/src/gui/text/qfont_qpa.cpp new file mode 100644 index 0000000000..ff12da8d97 --- /dev/null +++ b/src/gui/text/qfont_qpa.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/private/qapplication_p.h> +#include <QtGui/QPlatformFontDatabase> + +QT_BEGIN_NAMESPACE + +void QFont::initialize() +{ + QApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase(); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +Qt::HANDLE QFont::handle() const +{ + return 0; +} + +QString QFont::rawName() const +{ + return QLatin1String("unknown"); +} + +void QFont::setRawName(const QString &) +{ +} + +QString QFont::defaultFamily() const +{ + QString familyName; + switch(d->request.styleHint) { + case QFont::Times: + familyName = QString::fromLatin1("times"); + case QFont::Courier: + case QFont::Monospace: + familyName = QString::fromLatin1("monospace"); + case QFont::Decorative: + familyName = QString::fromLatin1("old english"); + case QFont::Helvetica: + case QFont::System: + default: + familyName = QString::fromLatin1("helvetica"); + } + + QStringList list = QApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(familyName,QFont::StyleNormal,QFont::StyleHint(d->request.styleHint),QUnicodeTables::Common); + if (list.size()) { + familyName = list.at(0); + } + return familyName; +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + qFatal("QFont::lastResortFont: Cannot find any reasonable font"); + // Shut compiler up + return QString(); +} + + +QT_END_NAMESPACE + diff --git a/src/gui/text/qfont_qws.cpp b/src/gui/text/qfont_qws.cpp new file mode 100644 index 0000000000..ea2a944432 --- /dev/null +++ b/src/gui/text/qfont_qws.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidget.h" +#include "qpainter.h" +#include "qfont_p.h" +#include <private/qunicodetables_p.h> +#include "qfontdatabase.h" +#include "qtextcodec.h" +#include "qapplication.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qmap.h" +//#include "qmemorymanager_qws.h" +#include "qtextengine_p.h" +#include "qfontengine_p.h" +#if !defined(QT_NO_FREETYPE) +#include "qfontengine_ft_p.h" +#endif + +QT_BEGIN_NAMESPACE + +void QFont::initialize() +{ } + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +Qt::HANDLE QFont::handle() const +{ +#ifndef QT_NO_FREETYPE + return freetypeFace(); +#endif + return 0; +} + +FT_Face QFont::freetypeFace() const +{ +#ifndef QT_NO_FREETYPE + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (engine->type() == QFontEngine::Multi) + engine = static_cast<QFontEngineMulti *>(engine)->engine(0); + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine); + return ft->non_locked_face(); + } +#endif + return 0; +} + +QString QFont::rawName() const +{ + return QLatin1String("unknown"); +} + +void QFont::setRawName(const QString &) +{ +} + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("times"); + case QFont::Courier: + case QFont::Monospace: + return QString::fromLatin1("courier"); + case QFont::Decorative: + return QString::fromLatin1("old english"); + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("helvetica"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + qFatal("QFont::lastResortFont: Cannot find any reasonable font"); + // Shut compiler up + return QString(); +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_s60.cpp b/src/gui/text/qfont_s60.cpp new file mode 100644 index 0000000000..114191d765 --- /dev/null +++ b/src/gui/text/qfont_s60.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include <private/qt_s60_p.h> +#include <private/qpixmap_s60_p.h> +#include "qmutex.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_NO_FREETYPE +Q_GLOBAL_STATIC(QMutex, lastResortFamilyMutex); +#endif // QT_NO_FREETYPE + +extern QStringList qt_symbian_fontFamiliesOnFontServer(); // qfontdatabase_s60.cpp +Q_GLOBAL_STATIC_WITH_INITIALIZER(QStringList, fontFamiliesOnFontServer, { + // We are only interested in the initial font families. No Application fonts. + // Therefore, we are allowed to cache the list. + x->append(qt_symbian_fontFamiliesOnFontServer()); +}); + +QString QFont::lastResortFont() const +{ + // Symbian's font Api does not distinguish between font and family. + // Therefore we try to get a "Family" first, then fall back to "Sans". + static QString font = lastResortFamily(); + if (font.isEmpty()) + font = QLatin1String("Sans"); + return font; +} + +QString QFont::lastResortFamily() const +{ +#ifdef QT_NO_FREETYPE + QMutexLocker locker(lastResortFamilyMutex()); + static QString family; + if (family.isEmpty()) { + + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + CFont *font; + const TInt err = S60->screenDevice()->GetNearestFontInTwips(font, TFontSpec()); + Q_ASSERT(err == KErrNone); + const TFontSpec spec = font->FontSpecInTwips(); + family = QString((const QChar *)spec.iTypeface.iName.Ptr(), spec.iTypeface.iName.Length()); + S60->screenDevice()->ReleaseFont(font); + + lock.relock(); + } + return family; +#else // QT_NO_FREETYPE + // For the FreeType case we just hard code the face name, since otherwise on + // East Asian systems we may get a name for a stroke based (non-ttf) font. + + // TODO: Get the type face name in a proper way + + const bool isJapaneseOrChineseSystem = + User::Language() == ELangJapanese || User::Language() == ELangPrcChinese; + + static QString family; + if (family.isEmpty()) { + QStringList families = qt_symbian_fontFamiliesOnFontServer(); + const char* const preferredFamilies[] = {"Nokia Sans S60", "Series 60 Sans"}; + for (int i = 0; i < sizeof preferredFamilies / sizeof preferredFamilies[0]; ++i) { + const QString preferredFamily = QLatin1String(preferredFamilies[i]); + if (families.contains(preferredFamily)) { + family = preferredFamily; + break; + } + } + } + + return QLatin1String(isJapaneseOrChineseSystem?"Heisei Kaku Gothic S60":family.toLatin1()); +#endif // QT_NO_FREETYPE +} + +QString QFont::defaultFamily() const +{ +#ifdef QT_NO_FREETYPE + switch(d->request.styleHint) { + case QFont::SansSerif: { + static const char* const preferredSansSerif[] = {"Nokia Sans S60", "Series 60 Sans"}; + for (int i = 0; i < sizeof preferredSansSerif / sizeof preferredSansSerif[0]; ++i) { + const QString sansSerif = QLatin1String(preferredSansSerif[i]); + if (fontFamiliesOnFontServer()->contains(sansSerif)) + return sansSerif; + } + } + // No break. Intentional fall through. + default: + return lastResortFamily(); + } +#endif // QT_NO_FREETYPE + return lastResortFamily(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_win.cpp b/src/gui/text/qfont_win.cpp new file mode 100644 index 0000000000..3ef761bfa5 --- /dev/null +++ b/src/gui/text/qfont_win.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qtextengine_p.h" +#include "qfontmetrics.h" +#include "qfontinfo.h" + +#include "qwidget.h" +#include "qpainter.h" +#include <limits.h> +#include "qt_windows.h" +#include <private/qapplication_p.h> +#include "qapplication.h" +#include <private/qunicodetables_p.h> +#include <qfontdatabase.h> + +QT_BEGIN_NAMESPACE + +extern HDC shared_dc(); // common dc for all fonts +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +// ### maybe move to qapplication_win +QFont qt_LOGFONTtoQFont(LOGFONT& lf, bool /*scale*/) +{ + QString family = QString::fromWCharArray(lf.lfFaceName); + QFont qf(family); + qf.setItalic(lf.lfItalic); + if (lf.lfWeight != FW_DONTCARE) + qf.setWeight(weightFromInteger(lf.lfWeight)); + int lfh = qAbs(lf.lfHeight); + qf.setPointSizeF(lfh * 72.0 / GetDeviceCaps(shared_dc(),LOGPIXELSY)); + qf.setUnderline(false); + qf.setOverline(false); + qf.setStrikeOut(false); + return qf; +} + + +static inline float pixelSize(const QFontDef &request, int dpi) +{ + float pSize; + if (request.pointSize != -1) + pSize = request.pointSize * dpi/ 72.; + else + pSize = request.pixelSize; + return pSize; +} + +static inline float pointSize(const QFontDef &fd, int dpi) +{ + float pSize; + if (fd.pointSize < 0) + pSize = fd.pixelSize * 72. / ((float)dpi); + else + pSize = fd.pointSize; + return pSize; +} + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +void QFont::initialize() +{ +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +HFONT QFont::handle() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast<QFontEngineMulti *>(engine)->engine(0); + if (engine->type() == QFontEngine::Win) + return static_cast<QFontEngineWin *>(engine)->hfont; + return 0; +} + +QString QFont::rawName() const +{ + return family(); +} + +void QFont::setRawName(const QString &name) +{ + setFamily(name); +} + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times New Roman"); + case QFont::Courier: + case QFont::Monospace: + return QString::fromLatin1("Courier New"); + case QFont::Decorative: + return QString::fromLatin1("Bookman Old Style"); + case QFont::Cursive: + return QString::fromLatin1("Comic Sans MS"); + case QFont::Fantasy: + return QString::fromLatin1("Impact"); + case QFont::Helvetica: + return QString::fromLatin1("Arial"); + case QFont::System: + default: + return QString::fromLatin1("MS Sans Serif"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + return QString::fromLatin1("arial"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_x11.cpp b/src/gui/text/qfont_x11.cpp new file mode 100644 index 0000000000..c72a5fade5 --- /dev/null +++ b/src/gui/text/qfont_x11.cpp @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define QT_FATAL_ASSERT + +#include "qplatformdefs.h" + +#include "qfont.h" +#include "qapplication.h" +#include "qfontinfo.h" +#include "qfontdatabase.h" +#include "qfontmetrics.h" +#include "qpaintdevice.h" +#include "qtextcodec.h" +#include "qiodevice.h" +#include "qhash.h" + +#include <private/qunicodetables_p.h> +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qfontengine_x11_p.h" +#include "qtextengine_p.h" + +#include <private/qt_x11_p.h> +#include "qx11info_x11.h" + +#include <time.h> +#include <stdlib.h> +#include <ctype.h> + +#define QFONTLOADER_DEBUG +#define QFONTLOADER_DEBUG_VERBOSE + +QT_BEGIN_NAMESPACE + +double qt_pixelSize(double pointSize, int dpi) +{ + if (pointSize < 0) + return -1.; + if (dpi == 75) // the stupid 75 dpi setting on X11 + dpi = 72; + return (pointSize * dpi) /72.; +} + +double qt_pointSize(double pixelSize, int dpi) +{ + if (pixelSize < 0) + return -1.; + if (dpi == 75) // the stupid 75 dpi setting on X11 + dpi = 72; + return pixelSize * 72. / ((double) dpi); +} + +/* + Removes wildcards from an XLFD. + + Returns \a xlfd with all wildcards removed if a match for \a xlfd is + found, otherwise it returns \a xlfd. +*/ +static QByteArray qt_fixXLFD(const QByteArray &xlfd) +{ + QByteArray ret = xlfd; + int count = 0; + char **fontNames = + XListFonts(QX11Info::display(), xlfd, 32768, &count); + if (count > 0) + ret = fontNames[0]; + XFreeFontNames(fontNames); + return ret ; +} + +typedef QHash<int, QString> FallBackHash; +Q_GLOBAL_STATIC(FallBackHash, fallBackHash) + +// Returns the user-configured fallback family for the specified script. +QString qt_fallback_font_family(int script) +{ + FallBackHash *hash = fallBackHash(); + return hash->value(script); +} + +// Sets the fallback family for the specified script. +Q_GUI_EXPORT void qt_x11_set_fallback_font_family(int script, const QString &family) +{ + FallBackHash *hash = fallBackHash(); + if (!family.isEmpty()) + hash->insert(script, family); + else + hash->remove(script); +} + +int QFontPrivate::defaultEncodingID = -1; + +void QFont::initialize() +{ + extern int qt_encoding_id_for_mib(int mib); // from qfontdatabase_x11.cpp + QTextCodec *codec = QTextCodec::codecForLocale(); + // determine the default encoding id using the locale, otherwise + // fallback to latin1 (mib == 4) + int mib = codec ? codec->mibEnum() : 4; + + // for asian locales, use the mib for the font codec instead of the locale codec + switch (mib) { + case 38: // eucKR + mib = 36; + break; + + case 2025: // GB2312 + mib = 57; + break; + + case 113: // GBK + mib = -113; + break; + + case 114: // GB18030 + mib = -114; + break; + + case 2026: // Big5 + mib = -2026; + break; + + case 2101: // Big5-HKSCS + mib = -2101; + break; + + case 16: // JIS7 + mib = 15; + break; + + case 17: // SJIS + case 18: // eucJP + mib = 63; + break; + } + + // get the default encoding id for the locale encoding... + QFontPrivate::defaultEncodingID = qt_encoding_id_for_mib(mib); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +/*! + \internal + X11 Only: Returns the screen with which this font is associated. +*/ +int QFont::x11Screen() const +{ + return d->screen; +} + +/*! \internal + X11 Only: Associate the font with the specified \a screen. +*/ +void QFont::x11SetScreen(int screen) +{ + if (screen < 0) // assume default + screen = QX11Info::appScreen(); + + if (screen == d->screen) + return; // nothing to do + + detach(); + d->screen = screen; +} + +Qt::HANDLE QFont::handle() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast<QFontEngineMulti *>(engine)->engine(0); + if (engine->type() == QFontEngine::XLFD) + return static_cast<QFontEngineXLFD *>(engine)->fontStruct()->fid; + return 0; +} + + +FT_Face QFont::freetypeFace() const +{ +#ifndef QT_NO_FREETYPE + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (engine->type() == QFontEngine::Multi) + engine = static_cast<QFontEngineMulti *>(engine)->engine(0); +#ifndef QT_NO_FONTCONFIG + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine); + return ft->non_locked_face(); + } else +#endif + if (engine->type() == QFontEngine::XLFD) { + const QFontEngineXLFD *xlfd = static_cast<const QFontEngineXLFD *>(engine); + return xlfd->non_locked_face(); + } +#endif + return 0; +} + +QString QFont::rawName() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast<QFontEngineMulti *>(engine)->engine(0); + if (engine->type() == QFontEngine::XLFD) + return QString::fromLatin1(engine->name()); + return QString(); +} +struct QtFontDesc; + +void QFont::setRawName(const QString &name) +{ + detach(); + + // from qfontdatabase_x11.cpp + extern bool qt_fillFontDef(const QByteArray &xlfd, QFontDef *fd, int dpi, QtFontDesc *desc); + + if (!qt_fillFontDef(qt_fixXLFD(name.toLatin1()), &d->request, d->dpi, 0)) { + qWarning("QFont::setRawName: Invalid XLFD: \"%s\"", name.toLatin1().constData()); + + setFamily(name); + setRawMode(true); + } else { + resolve_mask = QFont::AllPropertiesResolved; + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("Helvetica"); +} + +QString QFont::defaultFamily() const +{ + switch (d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times"); + + case QFont::Courier: + return QString::fromLatin1("Courier"); + + case QFont::Monospace: + return QString::fromLatin1("Courier New"); + + case QFont::Cursive: + return QString::fromLatin1("Comic Sans MS"); + + case QFont::Fantasy: + return QString::fromLatin1("Impact"); + + case QFont::Decorative: + return QString::fromLatin1("Old English"); + + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("Helvetica"); + } +} + +/* + Returns a last resort raw font name for the font matching algorithm. + This is used if even the last resort family is not available. It + returns \e something, almost no matter what. The current + implementation tries a wide variety of common fonts, returning the + first one it finds. The implementation may change at any time. +*/ +static const char * const tryFonts[] = { + "-*-helvetica-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-courier-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-times-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-lucida-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-helvetica-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-courier-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-times-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-lucida-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-helvetica-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-courier-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-times-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-lucida-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-fixed-*-*-*-*-*-*-*-*-*-*-*-*", + "6x13", + "7x13", + "8x13", + "9x15", + "fixed", + 0 +}; + +// Returns true if the font exists, false otherwise +static bool fontExists(const QString &fontName) +{ + int count; + char **fontNames = XListFonts(QX11Info::display(), (char*)fontName.toLatin1().constData(), 32768, &count); + if (fontNames) XFreeFontNames(fontNames); + + return count != 0; +} + +QString QFont::lastResortFont() const +{ + static QString last; + + // already found + if (! last.isNull()) + return last; + + int i = 0; + const char* f; + + while ((f = tryFonts[i])) { + last = QString::fromLatin1(f); + + if (fontExists(last)) + return last; + + i++; + } + +#if defined(CHECK_NULL) + qFatal("QFontPrivate::lastResortFont: Cannot find any reasonable font"); +#endif + return last; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp new file mode 100644 index 0000000000..36b0ea9b68 --- /dev/null +++ b/src/gui/text/qfontdatabase.cpp @@ -0,0 +1,2715 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdir.h> +#include "qfontdatabase.h" +#include "qdebug.h" +#include "qalgorithms.h" +#include "qapplication.h" +#include "qvarlengtharray.h" // here or earlier - workaround for VC++6 +#include "qthread.h" +#include "qmutex.h" +#include "private/qunicodetables_p.h" +#include "qfontengine_p.h" + +#ifdef Q_WS_QPA +#include <QtGui/private/qapplication_p.h> +#include <QtGui/qplatformfontdatabase_qpa.h> +#include "qabstractfileengine.h" +#endif + +#ifdef Q_WS_X11 +#include <locale.h> +#endif +#include <stdlib.h> +#include <limits.h> + +#if (defined(Q_WS_QWS)|| defined(Q_OS_SYMBIAN)) && !defined(QT_NO_FREETYPE) +# include <ft2build.h> +# include FT_TRUETYPE_TABLES_H +#endif + +// #define QFONTDATABASE_DEBUG +#ifdef QFONTDATABASE_DEBUG +# define FD_DEBUG qDebug +#else +# define FD_DEBUG if (false) qDebug +#endif + +// #define FONT_MATCH_DEBUG +#ifdef FONT_MATCH_DEBUG +# define FM_DEBUG qDebug +#else +# define FM_DEBUG if (false) qDebug +#endif + +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) +# include <dwrite.h> +#endif + +QT_BEGIN_NAMESPACE + +#define SMOOTH_SCALABLE 0xffff + +bool qt_enable_test_font = false; + +Q_AUTOTEST_EXPORT void qt_setQtEnableTestFont(bool value) +{ + qt_enable_test_font = value; +} + +static int getFontWeight(const QString &weightString) +{ + QString s = weightString.toLower(); + + // Test in decreasing order of commonness + if (s == QLatin1String("medium") || + s == QLatin1String("normal") + || s.compare(QApplication::translate("QFontDatabase", "Normal"), Qt::CaseInsensitive) == 0) + return QFont::Normal; + if (s == QLatin1String("bold") + || s.compare(QApplication::translate("QFontDatabase", "Bold"), Qt::CaseInsensitive) == 0) + return QFont::Bold; + if (s == QLatin1String("demibold") || s == QLatin1String("demi bold") + || s.compare(QApplication::translate("QFontDatabase", "Demi Bold"), Qt::CaseInsensitive) == 0) + return QFont::DemiBold; + if (s == QLatin1String("black") + || s.compare(QApplication::translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0) + return QFont::Black; + if (s == QLatin1String("light")) + return QFont::Light; + + if (s.contains(QLatin1String("bold")) + || s.contains(QApplication::translate("QFontDatabase", "Bold"), Qt::CaseInsensitive)) { + if (s.contains(QLatin1String("demi")) + || s.compare(QApplication::translate("QFontDatabase", "Demi"), Qt::CaseInsensitive) == 0) + return (int) QFont::DemiBold; + return (int) QFont::Bold; + } + + if (s.contains(QLatin1String("light")) + || s.compare(QApplication::translate("QFontDatabase", "Light"), Qt::CaseInsensitive) == 0) + return (int) QFont::Light; + + if (s.contains(QLatin1String("black")) + || s.compare(QApplication::translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0) + return (int) QFont::Black; + + return (int) QFont::Normal; +} + +// convert 0 ~ 1000 integer to QFont::Weight +QFont::Weight weightFromInteger(int weight) +{ + if (weight < 400) + return QFont::Light; + else if (weight < 600) + return QFont::Normal; + else if (weight < 700) + return QFont::DemiBold; + else if (weight < 800) + return QFont::Bold; + else + return QFont::Black; +} + +struct QtFontEncoding +{ + signed int encoding : 16; + + uint xpoint : 16; + uint xres : 8; + uint yres : 8; + uint avgwidth : 16; + uchar pitch : 8; +}; + +struct QtFontSize +{ +#ifdef Q_WS_X11 + QtFontEncoding *encodings; + QtFontEncoding *encodingID(int id, uint xpoint = 0, uint xres = 0, + uint yres = 0, uint avgwidth = 0, bool add = false); + unsigned short count : 16; +#endif // Q_WS_X11 + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + QByteArray fileName; + int fileIndex; +#endif // defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) +#if defined(Q_WS_QPA) + void *handle; +#endif + + unsigned short pixelSize : 16; +}; + + +#ifdef Q_WS_X11 +QtFontEncoding *QtFontSize::encodingID(int id, uint xpoint, uint xres, + uint yres, uint avgwidth, bool add) +{ + // we don't match using the xpoint, xres and yres parameters, only the id + for (int i = 0; i < count; ++i) { + if (encodings[i].encoding == id) + return encodings + i; + } + + if (!add) return 0; + + if (!(count % 4)) { + QtFontEncoding *newEncodings = (QtFontEncoding *) + realloc(encodings, + (((count+4) >> 2) << 2) * sizeof(QtFontEncoding)); + Q_CHECK_PTR(newEncodings); + encodings = newEncodings; + } + encodings[count].encoding = id; + encodings[count].xpoint = xpoint; + encodings[count].xres = xres; + encodings[count].yres = yres; + encodings[count].avgwidth = avgwidth; + encodings[count].pitch = '*'; + return encodings + count++; +} +#endif // Q_WS_X11 + +struct QtFontStyle +{ + struct Key { + Key(const QString &styleString); + Key() : style(QFont::StyleNormal), + weight(QFont::Normal), stretch(0) { } + Key(const Key &o) : style(o.style), + weight(o.weight), stretch(o.stretch) { } + uint style : 2; + signed int weight : 8; + signed int stretch : 12; + + bool operator==(const Key & other) { + return (style == other.style && + weight == other.weight && + (stretch == 0 || other.stretch == 0 || stretch == other.stretch)); + } + bool operator!=(const Key &other) { + return !operator==(other); + } + bool operator <(const Key &o) { + int x = (style << 12) + (weight << 14) + stretch; + int y = (o.style << 12) + (o.weight << 14) + o.stretch; + return (x < y); + } + }; + + QtFontStyle(const Key &k) + : key(k), bitmapScalable(false), smoothScalable(false), + count(0), pixelSizes(0) + { +#if defined(Q_WS_X11) + weightName = setwidthName = 0; +#endif // Q_WS_X11 + } + + ~QtFontStyle() { +#ifdef Q_WS_X11 + delete [] weightName; + delete [] setwidthName; +#endif +#if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + while (count) { + // bitfield count-- in while condition does not work correctly in mwccsym2 + count--; +#ifdef Q_WS_X11 + free(pixelSizes[count].encodings); +#endif +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + pixelSizes[count].fileName.~QByteArray(); +#endif +#if defined (Q_WS_QPA) + QPlatformIntegration *integration = QApplicationPrivate::platformIntegration(); + if (integration) { //on shut down there will be some that we don't release. + integration->fontDatabase()->releaseHandle(pixelSizes[count].handle); + } +#endif + } +#endif + free(pixelSizes); + } + + Key key; + bool bitmapScalable : 1; + bool smoothScalable : 1; + signed int count : 30; + QtFontSize *pixelSizes; + +#ifdef Q_WS_X11 + const char *weightName; + const char *setwidthName; +#endif // Q_WS_X11 +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + bool antialiased; +#endif + + QtFontSize *pixelSize(unsigned short size, bool = false); +}; + +QtFontStyle::Key::Key(const QString &styleString) + : style(QFont::StyleNormal), weight(QFont::Normal), stretch(0) +{ + weight = getFontWeight(styleString); + + if (styleString.contains(QLatin1String("Italic")) + || styleString.contains(QApplication::translate("QFontDatabase", "Italic"))) + style = QFont::StyleItalic; + else if (styleString.contains(QLatin1String("Oblique")) + || styleString.contains(QApplication::translate("QFontDatabase", "Oblique"))) + style = QFont::StyleOblique; +} + +QtFontSize *QtFontStyle::pixelSize(unsigned short size, bool add) +{ + for (int i = 0; i < count; i++) { + if (pixelSizes[i].pixelSize == size) + return pixelSizes + i; + } + if (!add) + return 0; + + if (!pixelSizes) { + // Most style have only one font size, we avoid waisting memory + QtFontSize *newPixelSizes = (QtFontSize *)malloc(sizeof(QtFontSize)); + Q_CHECK_PTR(newPixelSizes); + pixelSizes = newPixelSizes; + } else if (!(count % 8) || count == 1) { + QtFontSize *newPixelSizes = (QtFontSize *) + realloc(pixelSizes, + (((count+8) >> 3) << 3) * sizeof(QtFontSize)); + Q_CHECK_PTR(newPixelSizes); + pixelSizes = newPixelSizes; + } + pixelSizes[count].pixelSize = size; +#ifdef Q_WS_X11 + pixelSizes[count].count = 0; + pixelSizes[count].encodings = 0; +#endif +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + new (&pixelSizes[count].fileName) QByteArray; + pixelSizes[count].fileIndex = 0; +#endif +#if defined(Q_WS_QPA) + pixelSizes[count].handle = 0; +#endif + return pixelSizes + (count++); +} + +struct QtFontFoundry +{ + QtFontFoundry(const QString &n) : name(n), count(0), styles(0) {} + ~QtFontFoundry() { + while (count--) + delete styles[count]; + free(styles); + } + + QString name; + + int count; + QtFontStyle **styles; + QtFontStyle *style(const QtFontStyle::Key &, bool = false); +}; + +QtFontStyle *QtFontFoundry::style(const QtFontStyle::Key &key, bool create) +{ + int pos = 0; + if (count) { + int low = 0; + int high = count; + pos = count / 2; + while (high > low) { + if (styles[pos]->key == key) + return styles[pos]; + if (styles[pos]->key < key) + low = pos + 1; + else + high = pos; + pos = (high + low) / 2; + } + pos = low; + } + if (!create) + return 0; + +// qDebug("adding key (weight=%d, style=%d, oblique=%d stretch=%d) at %d", key.weight, key.style, key.oblique, key.stretch, pos); + if (!(count % 8)) { + QtFontStyle **newStyles = (QtFontStyle **) + realloc(styles, (((count+8) >> 3) << 3) * sizeof(QtFontStyle *)); + Q_CHECK_PTR(newStyles); + styles = newStyles; + } + + QtFontStyle *style = new QtFontStyle(key); + memmove(styles + pos + 1, styles + pos, (count-pos)*sizeof(QtFontStyle *)); + styles[pos] = style; + count++; + return styles[pos]; +} + + +struct QtFontFamily +{ + enum WritingSystemStatus { + Unknown = 0, + Supported = 1, + UnsupportedFT = 2, + UnsupportedXLFD = 4, + Unsupported = UnsupportedFT | UnsupportedXLFD + }; + + QtFontFamily(const QString &n) + : +#ifdef Q_WS_X11 + fixedPitch(true), ftWritingSystemCheck(false), + xlfdLoaded(false), synthetic(false), symbol_checked(false), +#else + fixedPitch(false), +#endif +#ifdef Q_WS_WIN + writingSystemCheck(false), + loaded(false), +#endif +#if !defined(QWS) && defined(Q_OS_MAC) + fixedPitchComputed(false), +#endif + name(n), count(0), foundries(0) +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + , bogusWritingSystems(false) +#endif +#if defined(Q_WS_QPA) + , askedForFallback(false) +#endif + { + memset(writingSystems, 0, sizeof(writingSystems)); + } + ~QtFontFamily() { + while (count--) + delete foundries[count]; + free(foundries); + } + + bool fixedPitch : 1; +#ifdef Q_WS_X11 + bool ftWritingSystemCheck : 1; + bool xlfdLoaded : 1; + bool synthetic : 1; +#endif +#ifdef Q_WS_WIN + bool writingSystemCheck : 1; + bool loaded : 1; +#endif +#if !defined(QWS) && defined(Q_OS_MAC) + bool fixedPitchComputed : 1; +#endif +#ifdef Q_WS_X11 + bool symbol_checked : 1; +#endif + + QString name; +#if defined(Q_WS_X11) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + QByteArray fontFilename; + int fontFileIndex; +#endif +#ifdef Q_WS_WIN + QString english_name; +#endif + int count; + QtFontFoundry **foundries; + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + bool bogusWritingSystems; + QStringList fallbackFamilies; +#endif +#if defined (Q_WS_QPA) + bool askedForFallback; +#endif + unsigned char writingSystems[QFontDatabase::WritingSystemsCount]; + + QtFontFoundry *foundry(const QString &f, bool = false); +}; + +#if !defined(QWS) && defined(Q_OS_MAC) +inline static void qt_mac_get_fixed_pitch(QtFontFamily *f) +{ + if(f && !f->fixedPitchComputed) { + QFontMetrics fm(f->name); + f->fixedPitch = fm.width(QLatin1Char('i')) == fm.width(QLatin1Char('m')); + f->fixedPitchComputed = true; + } +} +#endif + + +QtFontFoundry *QtFontFamily::foundry(const QString &f, bool create) +{ + if (f.isNull() && count == 1) + return foundries[0]; + + for (int i = 0; i < count; i++) { + if (foundries[i]->name.compare(f, Qt::CaseInsensitive) == 0) + return foundries[i]; + } + if (!create) + return 0; + + if (!(count % 8)) { + QtFontFoundry **newFoundries = (QtFontFoundry **) + realloc(foundries, + (((count+8) >> 3) << 3) * sizeof(QtFontFoundry *)); + Q_CHECK_PTR(newFoundries); + foundries = newFoundries; + } + + foundries[count] = new QtFontFoundry(f); + return foundries[count++]; +} + +// ### copied to tools/makeqpf/qpf2.cpp + +// see the Unicode subset bitfields in the MSDN docs +static int requiredUnicodeBits[QFontDatabase::WritingSystemsCount][2] = { + // Any, + { 127, 127 }, + // Latin, + { 0, 127 }, + // Greek, + { 7, 127 }, + // Cyrillic, + { 9, 127 }, + // Armenian, + { 10, 127 }, + // Hebrew, + { 11, 127 }, + // Arabic, + { 13, 127 }, + // Syriac, + { 71, 127 }, + //Thaana, + { 72, 127 }, + //Devanagari, + { 15, 127 }, + //Bengali, + { 16, 127 }, + //Gurmukhi, + { 17, 127 }, + //Gujarati, + { 18, 127 }, + //Oriya, + { 19, 127 }, + //Tamil, + { 20, 127 }, + //Telugu, + { 21, 127 }, + //Kannada, + { 22, 127 }, + //Malayalam, + { 23, 127 }, + //Sinhala, + { 73, 127 }, + //Thai, + { 24, 127 }, + //Lao, + { 25, 127 }, + //Tibetan, + { 70, 127 }, + //Myanmar, + { 74, 127 }, + // Georgian, + { 26, 127 }, + // Khmer, + { 80, 127 }, + // SimplifiedChinese, + { 126, 127 }, + // TraditionalChinese, + { 126, 127 }, + // Japanese, + { 126, 127 }, + // Korean, + { 56, 127 }, + // Vietnamese, + { 0, 127 }, // same as latin1 + // Other, + { 126, 127 }, + // Ogham, + { 78, 127 }, + // Runic, + { 79, 127 }, + // Nko, + { 14, 127 }, +}; + +#define SimplifiedChineseCsbBit 18 +#define TraditionalChineseCsbBit 20 +#define JapaneseCsbBit 17 +#define KoreanCsbBit 21 + +QList<QFontDatabase::WritingSystem> qt_determine_writing_systems_from_truetype_bits(quint32 unicodeRange[4], quint32 codePageRange[2]) +{ + QList<QFontDatabase::WritingSystem> writingSystems; + bool hasScript = false; + + int i; + for(i = 0; i < QFontDatabase::WritingSystemsCount; i++) { + int bit = requiredUnicodeBits[i][0]; + int index = bit/32; + int flag = 1 << (bit&31); + if (bit != 126 && unicodeRange[index] & flag) { + bit = requiredUnicodeBits[i][1]; + index = bit/32; + + flag = 1 << (bit&31); + if (bit == 127 || unicodeRange[index] & flag) { + writingSystems.append(QFontDatabase::WritingSystem(i)); + hasScript = true; + // qDebug("font %s: index=%d, flag=%8x supports script %d", familyName.latin1(), index, flag, i); + } + } + } + if(codePageRange[0] & (1 << SimplifiedChineseCsbBit)) { + writingSystems.append(QFontDatabase::SimplifiedChinese); + hasScript = true; + //qDebug("font %s supports Simplified Chinese", familyName.latin1()); + } + if(codePageRange[0] & (1 << TraditionalChineseCsbBit)) { + writingSystems.append(QFontDatabase::TraditionalChinese); + hasScript = true; + //qDebug("font %s supports Traditional Chinese", familyName.latin1()); + } + if(codePageRange[0] & (1 << JapaneseCsbBit)) { + writingSystems.append(QFontDatabase::Japanese); + hasScript = true; + //qDebug("font %s supports Japanese", familyName.latin1()); + } + if(codePageRange[0] & (1 << KoreanCsbBit)) { + writingSystems.append(QFontDatabase::Korean); + hasScript = true; + //qDebug("font %s supports Korean", familyName.latin1()); + } + if (!hasScript) + writingSystems.append(QFontDatabase::Symbol); + + return writingSystems; +} + +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) +// class with virtual destructor, derived in qfontdatabase_s60.cpp +class QSymbianFontDatabaseExtras +{ +public: + virtual ~QSymbianFontDatabaseExtras() {} +}; +#endif + +class QFontDatabasePrivate +{ +public: + QFontDatabasePrivate() + : count(0), families(0), reregisterAppFonts(false) +#if defined(Q_WS_QWS) + , stream(0) +#endif +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + , symbianExtras(0) +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + , directWriteFactory(0) + , directWriteGdiInterop(0) +#endif + { } + + ~QFontDatabasePrivate() { + free(); +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + if (symbianExtras) + delete symbianExtras; +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + if (directWriteGdiInterop) + directWriteGdiInterop->Release(); + if (directWriteFactory != 0) + directWriteFactory->Release(); +#endif + } + QtFontFamily *family(const QString &f, bool = false); + void free() { + while (count--) + delete families[count]; + ::free(families); + families = 0; + count = 0; + // don't clear the memory fonts! + } + + int count; +#if defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG) + QString systemLang; +#endif + QtFontFamily **families; + +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + IDWriteFactory *directWriteFactory; + IDWriteGdiInterop *directWriteGdiInterop; +#endif + + + struct ApplicationFont { + QString fileName; + QByteArray data; +#if defined(Q_OS_WIN) + HANDLE handle; + bool memoryFont; + QVector<FONTSIGNATURE> signatures; +#elif defined(Q_WS_MAC) + ATSFontContainerRef handle; +#elif defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + QString temporaryFileName; + TInt screenDeviceFontFileId; + TUid fontStoreFontFileUid; +#endif + QStringList families; + }; + QVector<ApplicationFont> applicationFonts; + int addAppFont(const QByteArray &fontData, const QString &fileName); + bool reregisterAppFonts; + bool isApplicationFont(const QString &fileName); + + void invalidate(); + +#if defined(Q_WS_QWS) + bool loadFromCache(const QString &fontPath); + void addQPF2File(const QByteArray &file); +#endif // Q_WS_QWS +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + void addFont(const QString &familyname, const char *foundryname, int weight, + bool italic, int pixelSize, const QByteArray &file, int fileIndex, + bool antialiased, + const QList<QFontDatabase::WritingSystem> &writingSystems = QList<QFontDatabase::WritingSystem>()); +#ifndef QT_NO_FREETYPE + QStringList addTTFile(const QByteArray &file, const QByteArray &fontData = QByteArray()); +#endif // QT_NO_FREETYPE +#endif +#if defined(Q_WS_QWS) + QDataStream *stream; +#elif defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + QSymbianFontDatabaseExtras *symbianExtras; +#endif +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + QStringList fallbackFamilies; +#endif +}; + +void QFontDatabasePrivate::invalidate() +{ + QFontCache::instance()->clear(); + free(); + emit static_cast<QApplication *>(QApplication::instance())->fontDatabaseChanged(); +} + +QtFontFamily *QFontDatabasePrivate::family(const QString &f, bool create) +{ + int low = 0; + int high = count; + int pos = count / 2; + int res = 1; + if (count) { + while ((res = families[pos]->name.compare(f, Qt::CaseInsensitive)) && pos != low) { + if (res > 0) + high = pos; + else + low = pos; + pos = (high + low) / 2; + } + if (!res) + return families[pos]; + } + if (!create) + return 0; + + if (res < 0) + pos++; + + // qDebug("adding family %s at %d total=%d", f.latin1(), pos, count); + if (!(count % 8)) { + QtFontFamily **newFamilies = (QtFontFamily **) + realloc(families, + (((count+8) >> 3) << 3) * sizeof(QtFontFamily *)); + Q_CHECK_PTR(newFamilies); + families = newFamilies; + } + + QtFontFamily *family = new QtFontFamily(f); + memmove(families + pos + 1, families + pos, (count-pos)*sizeof(QtFontFamily *)); + families[pos] = family; + count++; + return families[pos]; +} + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) +void QFontDatabasePrivate::addFont(const QString &familyname, const char *foundryname, int weight, bool italic, int pixelSize, + const QByteArray &file, int fileIndex, bool antialiased, + const QList<QFontDatabase::WritingSystem> &writingSystems) +{ +// qDebug() << "Adding font" << familyname << weight << italic << pixelSize << file << fileIndex << antialiased; + QtFontStyle::Key styleKey; + styleKey.style = italic ? QFont::StyleItalic : QFont::StyleNormal; + styleKey.weight = weight; + styleKey.stretch = 100; + QtFontFamily *f = family(familyname, true); + + if (writingSystems.isEmpty()) { + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + f->writingSystems[ws] = QtFontFamily::Supported; + } + f->bogusWritingSystems = true; + } else { + for (int i = 0; i < writingSystems.count(); ++i) { + f->writingSystems[writingSystems.at(i)] = QtFontFamily::Supported; + } + } + + QtFontFoundry *foundry = f->foundry(QString::fromLatin1(foundryname), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = (pixelSize == 0); + style->antialiased = antialiased; + QtFontSize *size = style->pixelSize(pixelSize?pixelSize:SMOOTH_SCALABLE, true); + size->fileName = file; + size->fileIndex = fileIndex; + +#if defined(Q_WS_QWS) + if (stream) { + *stream << familyname << foundry->name << weight << quint8(italic) << pixelSize + << file << fileIndex << quint8(antialiased); + *stream << quint8(writingSystems.count()); + for (int i = 0; i < writingSystems.count(); ++i) + *stream << quint8(writingSystems.at(i)); + } +#else // ..in case of defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + f->fontFilename = file; + f->fontFileIndex = fileIndex; +#endif +} +#endif + +#if (defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) && !defined(QT_NO_FREETYPE) +QStringList QFontDatabasePrivate::addTTFile(const QByteArray &file, const QByteArray &fontData) +{ + QStringList families; + extern FT_Library qt_getFreetype(); + FT_Library library = qt_getFreetype(); + + int index = 0; + int numFaces = 0; + do { + FT_Face face; + FT_Error error; + if (!fontData.isEmpty()) { + error = FT_New_Memory_Face(library, (const FT_Byte *)fontData.constData(), fontData.size(), index, &face); + } else { + error = FT_New_Face(library, file, index, &face); + } + if (error != FT_Err_Ok) { + qDebug() << "FT_New_Face failed with index" << index << ":" << hex << error; + break; + } + numFaces = face->num_faces; + + int weight = QFont::Normal; + bool italic = face->style_flags & FT_STYLE_FLAG_ITALIC; + + if (face->style_flags & FT_STYLE_FLAG_BOLD) + weight = QFont::Bold; + + QList<QFontDatabase::WritingSystem> writingSystems; + // detect symbol fonts + for (int i = 0; i < face->num_charmaps; ++i) { + FT_CharMap cm = face->charmaps[i]; + if (cm->encoding == ft_encoding_adobe_custom + || cm->encoding == ft_encoding_symbol) { + writingSystems.append(QFontDatabase::Symbol); + break; + } + } + if (writingSystems.isEmpty()) { + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) { + quint32 unicodeRange[4] = { + os2->ulUnicodeRange1, os2->ulUnicodeRange2, os2->ulUnicodeRange3, os2->ulUnicodeRange4 + }; + quint32 codePageRange[2] = { + os2->ulCodePageRange1, os2->ulCodePageRange2 + }; + + writingSystems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + //for (int i = 0; i < writingSystems.count(); ++i) + // qDebug() << QFontDatabase::writingSystemName(writingSystems.at(i)); + } + } + + QString family = QString::fromAscii(face->family_name); + families.append(family); + addFont(family, /*foundry*/ "", weight, italic, + /*pixelsize*/ 0, file, index, /*antialias*/ true, writingSystems); + + FT_Done_Face(face); + ++index; + } while (index < numFaces); + return families; +} +#endif + +static const int scriptForWritingSystem[] = { + QUnicodeTables::Common, // Any + QUnicodeTables::Latin, // Latin + QUnicodeTables::Greek, // Greek + QUnicodeTables::Cyrillic, // Cyrillic + QUnicodeTables::Armenian, // Armenian + QUnicodeTables::Hebrew, // Hebrew + QUnicodeTables::Arabic, // Arabic + QUnicodeTables::Syriac, // Syriac + QUnicodeTables::Thaana, // Thaana + QUnicodeTables::Devanagari, // Devanagari + QUnicodeTables::Bengali, // Bengali + QUnicodeTables::Gurmukhi, // Gurmukhi + QUnicodeTables::Gujarati, // Gujarati + QUnicodeTables::Oriya, // Oriya + QUnicodeTables::Tamil, // Tamil + QUnicodeTables::Telugu, // Telugu + QUnicodeTables::Kannada, // Kannada + QUnicodeTables::Malayalam, // Malayalam + QUnicodeTables::Sinhala, // Sinhala + QUnicodeTables::Thai, // Thai + QUnicodeTables::Lao, // Lao + QUnicodeTables::Tibetan, // Tibetan + QUnicodeTables::Myanmar, // Myanmar + QUnicodeTables::Georgian, // Georgian + QUnicodeTables::Khmer, // Khmer + QUnicodeTables::Common, // SimplifiedChinese + QUnicodeTables::Common, // TraditionalChinese + QUnicodeTables::Common, // Japanese + QUnicodeTables::Hangul, // Korean + QUnicodeTables::Common, // Vietnamese + QUnicodeTables::Common, // Yi + QUnicodeTables::Common, // Tagalog + QUnicodeTables::Common, // Hanunoo + QUnicodeTables::Common, // Buhid + QUnicodeTables::Common, // Tagbanwa + QUnicodeTables::Common, // Limbu + QUnicodeTables::Common, // TaiLe + QUnicodeTables::Common, // Braille + QUnicodeTables::Common, // Symbol + QUnicodeTables::Ogham, // Ogham + QUnicodeTables::Runic, // Runic + QUnicodeTables::Nko // Nko +}; + +int qt_script_for_writing_system(QFontDatabase::WritingSystem writingSystem) +{ + return scriptForWritingSystem[writingSystem]; +} + + +#if defined Q_WS_QWS || (defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG)) || defined(Q_WS_WIN) +static inline bool requiresOpenType(int writingSystem) +{ + return ((writingSystem >= QFontDatabase::Syriac && writingSystem <= QFontDatabase::Sinhala) + || writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko); +} +static inline bool scriptRequiresOpenType(int script) +{ + return ((script >= QUnicodeTables::Syriac && script <= QUnicodeTables::Sinhala) + || script == QUnicodeTables::Khmer || script == QUnicodeTables::Nko); +} +#endif + + +/*! + \internal + + This makes sense of the font family name: + + if the family name contains a '[' and a ']', then we take the text + between the square brackets as the foundry, and the text before the + square brackets as the family (ie. "Arial [Monotype]") +*/ +static void parseFontName(const QString &name, QString &foundry, QString &family) +{ + int i = name.indexOf(QLatin1Char('[')); + int li = name.lastIndexOf(QLatin1Char(']')); + if (i >= 0 && li >= 0 && i < li) { + foundry = name.mid(i + 1, li - i - 1); + if (i > 0 && name[i - 1] == QLatin1Char(' ')) + i--; + family = name.left(i); + } else { + foundry.clear(); + family = name; + } + + // capitalize the family/foundry names + bool space = true; + QChar *s = family.data(); + int len = family.length(); + while(len--) { + if (space) *s = s->toUpper(); + space = s->isSpace(); + ++s; + } + + space = true; + s = foundry.data(); + len = foundry.length(); + while(len--) { + if (space) *s = s->toUpper(); + space = s->isSpace(); + ++s; + } +} + + +struct QtFontDesc +{ + inline QtFontDesc() : family(0), foundry(0), style(0), size(0), encoding(0), familyIndex(-1) {} + QtFontFamily *family; + QtFontFoundry *foundry; + QtFontStyle *style; + QtFontSize *size; + QtFontEncoding *encoding; + int familyIndex; +}; + +#if !defined(Q_WS_MAC) +static void match(int script, const QFontDef &request, + const QString &family_name, const QString &foundry_name, int force_encoding_id, + QtFontDesc *desc, const QList<int> &blacklistedFamilies = QList<int>(), bool forceXLFD=false); + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) +static void initFontDef(const QtFontDesc &desc, const QFontDef &request, QFontDef *fontDef) +{ + fontDef->family = desc.family->name; + if (! desc.foundry->name.isEmpty() && desc.family->count > 1) { + fontDef->family += QString::fromLatin1(" ["); + fontDef->family += desc.foundry->name; + fontDef->family += QLatin1Char(']'); + } + + if (desc.style->smoothScalable) + fontDef->pixelSize = request.pixelSize; + else if ((desc.style->bitmapScalable && (request.styleStrategy & QFont::PreferMatch))) + fontDef->pixelSize = request.pixelSize; + else + fontDef->pixelSize = desc.size->pixelSize; + + fontDef->styleHint = request.styleHint; + fontDef->styleStrategy = request.styleStrategy; + + fontDef->weight = desc.style->key.weight; + fontDef->style = desc.style->key.style; + fontDef->fixedPitch = desc.family->fixedPitch; + fontDef->stretch = desc.style->key.stretch; + fontDef->ignorePitch = false; +} +#endif +#endif + +#if defined(Q_WS_X11) || defined(Q_WS_WIN) || defined(Q_OS_SYMBIAN) || defined(Q_WS_QPA) +static void getEngineData(const QFontPrivate *d, const QFontCache::Key &key) +{ + // look for the requested font in the engine data cache + d->engineData = QFontCache::instance()->findEngineData(key); + if (!d->engineData) { + // create a new one + d->engineData = new QFontEngineData; + QFontCache::instance()->insertEngineData(key, d->engineData); + } else { + d->engineData->ref.ref(); + } +} +#endif + +static QStringList familyList(const QFontDef &req) +{ + // list of families to try + QStringList family_list; + if (req.family.isEmpty()) + return family_list; + + QStringList list = req.family.split(QLatin1Char(',')); + for (int i = 0; i < list.size(); ++i) { + QString str = list.at(i).trimmed(); + if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) + || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) + str = str.mid(1, str.length() - 2); + family_list << str; + } + + // append the substitute list for each family in family_list + QStringList subs_list; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; it != end; ++it) + subs_list += QFont::substitutes(*it); +// qDebug() << "adding substs: " << subs_list; + + family_list += subs_list; + + return family_list; +} + +Q_GLOBAL_STATIC(QFontDatabasePrivate, privateDb) +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, fontDatabaseMutex, (QMutex::Recursive)) + +// used in qfontengine_x11.cpp +QMutex *qt_fontdatabase_mutex() +{ + return fontDatabaseMutex(); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#if defined(Q_WS_X11) +# include "qfontdatabase_x11.cpp" +#elif defined(Q_WS_MAC) +# include "qfontdatabase_mac.cpp" +#elif defined(Q_WS_WIN) +# include "qfontdatabase_win.cpp" +#elif defined(Q_WS_QWS) +# include "qfontdatabase_qws.cpp" +#elif defined(Q_WS_QPA) +# include "qfontdatabase_qpa.cpp" +#elif defined(Q_OS_SYMBIAN) +# include "qfontdatabase_s60.cpp" +#endif +#if !defined(Q_WS_X11) +QString QFontDatabase::resolveFontFamilyAlias(const QString &family) +{ + return family; +} +#endif +QT_END_INCLUDE_NAMESPACE + +static QtFontStyle *bestStyle(QtFontFoundry *foundry, const QtFontStyle::Key &styleKey) +{ + int best = 0; + int dist = 0xffff; + + for ( int i = 0; i < foundry->count; i++ ) { + QtFontStyle *style = foundry->styles[i]; + + int d = qAbs( styleKey.weight - style->key.weight ); + + if ( styleKey.stretch != 0 && style->key.stretch != 0 ) { + d += qAbs( styleKey.stretch - style->key.stretch ); + } + + if (styleKey.style != style->key.style) { + if (styleKey.style != QFont::StyleNormal && style->key.style != QFont::StyleNormal) + // one is italic, the other oblique + d += 0x0001; + else + d += 0x1000; + } + + if ( d < dist ) { + best = i; + dist = d; + } + } + + FM_DEBUG( " best style has distance 0x%x", dist ); + return foundry->styles[best]; +} + +#if defined(Q_WS_X11) +static QtFontEncoding *findEncoding(int script, int styleStrategy, + QtFontSize *size, int force_encoding_id) +{ + QtFontEncoding *encoding = 0; + + if (force_encoding_id >= 0) { + encoding = size->encodingID(force_encoding_id); + if (!encoding) + FM_DEBUG(" required encoding_id not available"); + return encoding; + } + + if (styleStrategy & (QFont::OpenGLCompatible | QFont::PreferBitmap)) { + FM_DEBUG(" PreferBitmap and/or OpenGL set, skipping Freetype"); + } else { + encoding = size->encodingID(-1); // -1 == prefer Freetype + if (encoding) + return encoding; + } + + // FT not available, find an XLFD font, trying the default encoding first + encoding = size->encodingID(QFontPrivate::defaultEncodingID); + if (encoding) { + // does it support the requested script? + bool supportsScript = false; + for (int ws = 1; !supportsScript && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + supportsScript = writingSystems_for_xlfd_encoding[encoding->encoding][ws]; + } + if (!supportsScript) + encoding = 0; + } + // find the first encoding that supports the requested script + for (int ws = 1; !encoding && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int x = 0; !encoding && x < size->count; ++x) { + const int enc = size->encodings[x].encoding; + if (writingSystems_for_xlfd_encoding[enc][ws]) + encoding = size->encodings + x; + } + } + + return encoding; +} +#endif // Q_WS_X11 + +#if !defined(Q_WS_MAC) +static +unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, + const QtFontFamily *family, const QString &foundry_name, + QtFontStyle::Key styleKey, int pixelSize, char pitch, + QtFontDesc *desc, int force_encoding_id) +{ + Q_UNUSED(force_encoding_id); + Q_UNUSED(script); + Q_UNUSED(pitch); + + desc->foundry = 0; + desc->style = 0; + desc->size = 0; + desc->encoding = 0; + + + FM_DEBUG(" REMARK: looking for best foundry for family '%s' [%d]", family->name.toLatin1().constData(), family->count); + + for (int x = 0; x < family->count; ++x) { + QtFontFoundry *foundry = family->foundries[x]; + if (!foundry_name.isEmpty() && foundry->name.compare(foundry_name, Qt::CaseInsensitive) != 0) + continue; + + FM_DEBUG(" looking for matching style in foundry '%s' %d", + foundry->name.isEmpty() ? "-- none --" : foundry->name.toLatin1().constData(), foundry->count); + + QtFontStyle *style = bestStyle(foundry, styleKey); + + if (!style->smoothScalable && (styleStrategy & QFont::ForceOutline)) { + FM_DEBUG(" ForceOutline set, but not smoothly scalable"); + continue; + } + + int px = -1; + QtFontSize *size = 0; + + // 1. see if we have an exact matching size + if (!(styleStrategy & QFont::ForceOutline)) { + size = style->pixelSize(pixelSize); + if (size) { + FM_DEBUG(" found exact size match (%d pixels)", size->pixelSize); + px = size->pixelSize; + } + } + + // 2. see if we have a smoothly scalable font + if (!size && style->smoothScalable && ! (styleStrategy & QFont::PreferBitmap)) { + size = style->pixelSize(SMOOTH_SCALABLE); + if (size) { + FM_DEBUG(" found smoothly scalable font (%d pixels)", pixelSize); + px = pixelSize; + } + } + + // 3. see if we have a bitmap scalable font + if (!size && style->bitmapScalable && (styleStrategy & QFont::PreferMatch)) { + size = style->pixelSize(0); + if (size) { + FM_DEBUG(" found bitmap scalable font (%d pixels)", pixelSize); + px = pixelSize; + } + } + +#ifdef Q_WS_X11 + QtFontEncoding *encoding = 0; +#endif + + // 4. find closest size match + if (! size) { + unsigned int distance = ~0u; + for (int x = 0; x < style->count; ++x) { +#ifdef Q_WS_X11 + encoding = + findEncoding(script, styleStrategy, style->pixelSizes + x, force_encoding_id); + if (!encoding) { + FM_DEBUG(" size %3d does not support the script we want", + style->pixelSizes[x].pixelSize); + continue; + } +#endif + + unsigned int d; + if (style->pixelSizes[x].pixelSize < pixelSize) { + // penalize sizes that are smaller than the + // requested size, due to truncation from floating + // point to integer conversions + d = pixelSize - style->pixelSizes[x].pixelSize + 1; + } else { + d = style->pixelSizes[x].pixelSize - pixelSize; + } + + if (d < distance) { + distance = d; + size = style->pixelSizes + x; + FM_DEBUG(" best size so far: %3d (%d)", size->pixelSize, pixelSize); + } + } + + if (!size) { + FM_DEBUG(" no size supports the script we want"); + continue; + } + + if (style->bitmapScalable && ! (styleStrategy & QFont::PreferQuality) && + (distance * 10 / pixelSize) >= 2) { + // the closest size is not close enough, go ahead and + // use a bitmap scaled font + size = style->pixelSize(0); + px = pixelSize; + } else { + px = size->pixelSize; + } + } + +#ifdef Q_WS_X11 + if (size) { + encoding = findEncoding(script, styleStrategy, size, force_encoding_id); + if (!encoding) size = 0; + } + if (! encoding) { + FM_DEBUG(" foundry doesn't support the script we want"); + continue; + } +#endif // Q_WS_X11 + + unsigned int this_score = 0x0000; + enum { + PitchMismatch = 0x4000, + StyleMismatch = 0x2000, + BitmapScaledPenalty = 0x1000, + EncodingMismatch = 0x0002, + XLFDPenalty = 0x0001 + }; +#ifdef Q_WS_X11 + if (encoding->encoding != -1) { + this_score += XLFDPenalty; + if (encoding->encoding != QFontPrivate::defaultEncodingID) + this_score += EncodingMismatch; + } + if (pitch != '*') { + if (!(pitch == 'm' && encoding->pitch == 'c') && pitch != encoding->pitch) + this_score += PitchMismatch; + } +#else + if (pitch != '*') { +#if !defined(QWS) && defined(Q_OS_MAC) + qt_mac_get_fixed_pitch(const_cast<QtFontFamily*>(family)); +#endif + if ((pitch == 'm' && !family->fixedPitch) + || (pitch == 'p' && family->fixedPitch)) + this_score += PitchMismatch; + } +#endif + if (styleKey != style->key) + this_score += StyleMismatch; + if (!style->smoothScalable && px != size->pixelSize) // bitmap scaled + this_score += BitmapScaledPenalty; + if (px != pixelSize) // close, but not exact, size match + this_score += qAbs(px - pixelSize); + + if (this_score < score) { + FM_DEBUG(" found a match: score %x best score so far %x", + this_score, score); + + score = this_score; + desc->foundry = foundry; + desc->style = style; + desc->size = size; +#ifdef Q_WS_X11 + desc->encoding = encoding; +#endif // Q_WS_X11 + } else { + FM_DEBUG(" score %x no better than best %x", this_score, score); + } + } + + return score; +} +#endif + +#if !defined(Q_WS_MAC) +/*! + \internal + + Tries to find the best match for a given request and family/foundry +*/ +static void match(int script, const QFontDef &request, + const QString &family_name, const QString &foundry_name, int force_encoding_id, + QtFontDesc *desc, const QList<int> &blacklistedFamilies, bool forceXLFD) +{ + Q_UNUSED(force_encoding_id); + + QtFontStyle::Key styleKey; + styleKey.style = request.style; + styleKey.weight = request.weight; + styleKey.stretch = request.stretch; + char pitch = request.ignorePitch ? '*' : request.fixedPitch ? 'm' : 'p'; + + + FM_DEBUG("QFontDatabase::match\n" + " request:\n" + " family: %s [%s], script: %d\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %g\n" + " pitch: %c", + family_name.isEmpty() ? "-- first in script --" : family_name.toLatin1().constData(), + foundry_name.isEmpty() ? "-- any --" : foundry_name.toLatin1().constData(), + script, request.weight, request.style, request.stretch, request.pixelSize, pitch); +#if defined(FONT_MATCH_DEBUG) && defined(Q_WS_X11) + if (force_encoding_id >= 0) { + FM_DEBUG(" required encoding: %d", force_encoding_id); + } +#endif + + desc->family = 0; + desc->foundry = 0; + desc->style = 0; + desc->size = 0; + desc->encoding = 0; + desc->familyIndex = -1; + + unsigned int score = ~0u; + +#ifdef Q_WS_X11 + load(family_name, script, forceXLFD); +#else + Q_UNUSED(forceXLFD); + load(family_name, script); +#endif + + QFontDatabasePrivate *db = privateDb(); + for (int x = 0; x < db->count; ++x) { + if (blacklistedFamilies.contains(x)) + continue; + QtFontDesc test; + test.family = db->families[x]; + test.familyIndex = x; + + if (!family_name.isEmpty() + && test.family->name.compare(family_name, Qt::CaseInsensitive) != 0 +#ifdef Q_WS_WIN + && test.family->english_name.compare(family_name, Qt::CaseInsensitive) != 0 +#endif + ) + continue; + + if (family_name.isEmpty()) + load(test.family->name, script); + + uint score_adjust = 0; + + bool supported = (script == QUnicodeTables::Common); + for (int ws = 1; !supported && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + if (test.family->writingSystems[ws] & QtFontFamily::Supported) + supported = true; + } + if (!supported) { + // family not supported in the script we want + continue; + } + + // as we know the script is supported, we can be sure + // to find a matching font here. + unsigned int newscore = + bestFoundry(script, score, request.styleStrategy, + test.family, foundry_name, styleKey, request.pixelSize, pitch, + &test, force_encoding_id); + if (test.foundry == 0) { + // the specific foundry was not found, so look for + // any foundry matching our requirements + newscore = bestFoundry(script, score, request.styleStrategy, test.family, + QString(), styleKey, request.pixelSize, + pitch, &test, force_encoding_id); + } + newscore += score_adjust; + + if (newscore < score) { + score = newscore; + *desc = test; + } + if (newscore < 10) // xlfd instead of FT... just accept it + break; + } +} +#endif + +static QString styleStringHelper(int weight, QFont::Style style) +{ + QString result; + if (weight >= QFont::Black) + result = QApplication::translate("QFontDatabase", "Black"); + else if (weight >= QFont::Bold) + result = QApplication::translate("QFontDatabase", "Bold"); + else if (weight >= QFont::DemiBold) + result = QApplication::translate("QFontDatabase", "Demi Bold"); + else if (weight < QFont::Normal) + result = QApplication::translate("QFontDatabase", "Light"); + + if (style == QFont::StyleItalic) + result += QLatin1Char(' ') + QApplication::translate("QFontDatabase", "Italic"); + else if (style == QFont::StyleOblique) + result += QLatin1Char(' ') + QApplication::translate("QFontDatabase", "Oblique"); + + if (result.isEmpty()) + result = QApplication::translate("QFontDatabase", "Normal"); + + return result.simplified(); +} + +/*! + Returns a string that describes the style of the \a font. For + example, "Bold Italic", "Bold", "Italic" or "Normal". An empty + string may be returned. +*/ +QString QFontDatabase::styleString(const QFont &font) +{ + return styleStringHelper(font.weight(), font.style()); +} + +/*! + Returns a string that describes the style of the \a fontInfo. For + example, "Bold Italic", "Bold", "Italic" or "Normal". An empty + string may be returned. +*/ +QString QFontDatabase::styleString(const QFontInfo &fontInfo) +{ + return styleStringHelper(fontInfo.weight(), fontInfo.style()); +} + + +/*! + \class QFontDatabase + \threadsafe + + \brief The QFontDatabase class provides information about the fonts available in the underlying window system. + + \ingroup appearance + + The most common uses of this class are to query the database for + the list of font families() and for the pointSizes() and styles() + that are available for each family. An alternative to pointSizes() + is smoothSizes() which returns the sizes at which a given family + and style will look attractive. + + If the font family is available from two or more foundries the + foundry name is included in the family name; for example: + "Helvetica [Adobe]" and "Helvetica [Cronyx]". When you specify a + family, you can either use the old hyphenated "foundry-family" + format or the bracketed "family [foundry]" format; for example: + "Cronyx-Helvetica" or "Helvetica [Cronyx]". If the family has a + foundry it is always returned using the bracketed format, as is + the case with the value returned by families(). + + The font() function returns a QFont given a family, style and + point size. + + A family and style combination can be checked to see if it is + italic() or bold(), and to retrieve its weight(). Similarly we can + call isBitmapScalable(), isSmoothlyScalable(), isScalable() and + isFixedPitch(). + + Use the styleString() to obtain a text version of a style. + + The QFontDatabase class also supports some static functions, for + example, standardSizes(). You can retrieve the description of a + writing system using writingSystemName(), and a sample of + characters in a writing system with writingSystemSample(). + + Example: + + \snippet doc/src/snippets/qfontdatabase/main.cpp 0 + \snippet doc/src/snippets/qfontdatabase/main.cpp 1 + + This example gets the list of font families, the list of + styles for each family, and the point sizes that are available for + each combination of family and style, displaying this information + in a tree view. + + \sa QFont, QFontInfo, QFontMetrics, QFontComboBox, {Character Map Example} +*/ + +/*! + Creates a font database object. +*/ +QFontDatabase::QFontDatabase() +{ + QMutexLocker locker(fontDatabaseMutex()); + createDatabase(); + d = privateDb(); +} + +/*! + \enum QFontDatabase::WritingSystem + + \value Any + \value Latin + \value Greek + \value Cyrillic + \value Armenian + \value Hebrew + \value Arabic + \value Syriac + \value Thaana + \value Devanagari + \value Bengali + \value Gurmukhi + \value Gujarati + \value Oriya + \value Tamil + \value Telugu + \value Kannada + \value Malayalam + \value Sinhala + \value Thai + \value Lao + \value Tibetan + \value Myanmar + \value Georgian + \value Khmer + \value SimplifiedChinese + \value TraditionalChinese + \value Japanese + \value Korean + \value Vietnamese + \value Symbol + \value Other (the same as Symbol) + \value Ogham + \value Runic + \value Nko + + \omitvalue WritingSystemsCount +*/ + +/*! + Returns a sorted list of the available writing systems. This is + list generated from information about all installed fonts on the + system. + + \sa families() +*/ +QList<QFontDatabase::WritingSystem> QFontDatabase::writingSystems() const +{ + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + checkSymbolFonts(); +#endif + + QList<WritingSystem> list; + for (int i = 0; i < d->count; ++i) { + QtFontFamily *family = d->families[i]; + if (family->count == 0) + continue; + for (int x = Latin; x < WritingSystemsCount; ++x) { + const WritingSystem writingSystem = WritingSystem(x); + if (!(family->writingSystems[writingSystem] & QtFontFamily::Supported)) + continue; + if (!list.contains(writingSystem)) + list.append(writingSystem); + } + } + qSort(list); + return list; +} + + +/*! + Returns a sorted list of the writing systems supported by a given + font \a family. + + \sa families() +*/ +QList<QFontDatabase::WritingSystem> QFontDatabase::writingSystems(const QString &family) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + checkSymbolFonts(familyName); +#endif + + QList<WritingSystem> list; + QtFontFamily *f = d->family(familyName); + if (!f || f->count == 0) + return list; + + for (int x = Latin; x < WritingSystemsCount; ++x) { + const WritingSystem writingSystem = WritingSystem(x); + if (f->writingSystems[writingSystem] & QtFontFamily::Supported) + list.append(writingSystem); + } + return list; +} + + +/*! + Returns a sorted list of the available font families which support + the \a writingSystem. + + If a family exists in several foundries, the returned name for + that font is in the form "family [foundry]". Examples: "Times + [Adobe]", "Times [Cronyx]", "Palatino". + + \sa writingSystems() +*/ +QStringList QFontDatabase::families(WritingSystem writingSystem) const +{ + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + if (writingSystem != Any) + checkSymbolFonts(); +#endif + + QStringList flist; + for (int i = 0; i < d->count; i++) { + QtFontFamily *f = d->families[i]; + if (f->count == 0) + continue; + if (writingSystem != Any && (f->writingSystems[writingSystem] != QtFontFamily::Supported)) + continue; + if (f->count == 1) { + flist.append(f->name); + } else { + for (int j = 0; j < f->count; j++) { + QString str = f->name; + QString foundry = f->foundries[j]->name; + if (!foundry.isEmpty()) { + str += QLatin1String(" ["); + str += foundry; + str += QLatin1Char(']'); + } + flist.append(str); + } + } + } + return flist; +} + +/*! + Returns a list of the styles available for the font family \a + family. Some example styles: "Light", "Light Italic", "Bold", + "Oblique", "Demi". The list may be empty. + + \sa families() +*/ +QStringList QFontDatabase::styles(const QString &family) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QStringList l; + QtFontFamily *f = d->family(familyName); + if (!f) + return l; + + QtFontFoundry allStyles(foundryName); + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) { + QtFontStyle::Key ke(foundry->styles[k]->key); + ke.stretch = 0; + allStyles.style(ke, true); + } + } + } + + for (int i = 0; i < allStyles.count; i++) + l.append(styleStringHelper(allStyles.styles[i]->key.weight, (QFont::Style)allStyles.styles[i]->key.style)); + return l; +} + +/*! + Returns true if the font that has family \a family and style \a + style is fixed pitch; otherwise returns false. +*/ + +bool QFontDatabase::isFixedPitch(const QString &family, + const QString &style) const +{ + Q_UNUSED(style); + + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFamily *f = d->family(familyName); +#if !defined(QWS) && defined(Q_OS_MAC) + qt_mac_get_fixed_pitch(f); +#endif + return (f && f->fixedPitch); +} + +/*! + Returns true if the font that has family \a family and style \a + style is a scalable bitmap font; otherwise returns false. Scaling + a bitmap font usually produces an unattractive hardly readable + result, because the pixels of the font are scaled. If you need to + scale a bitmap font it is better to scale it to one of the fixed + sizes returned by smoothSizes(). + + \sa isScalable(), isSmoothlyScalable() +*/ +bool QFontDatabase::isBitmapScalable(const QString &family, + const QString &style) const +{ + bool bitmapScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QtFontFamily *f = d->family(familyName); + if (!f) return bitmapScalable; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + if ((style.isEmpty() || foundry->styles[k]->key == styleKey) + && foundry->styles[k]->bitmapScalable && !foundry->styles[k]->smoothScalable) { + bitmapScalable = true; + goto end; + } + } + } + end: + return bitmapScalable; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is smoothly scalable; otherwise returns false. If this + function returns true, it's safe to scale this font to any size, + and the result will always look attractive. + + \sa isScalable(), isBitmapScalable() +*/ +bool QFontDatabase::isSmoothlyScalable(const QString &family, const QString &style) const +{ + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QtFontFamily *f = d->family(familyName); + if (!f) return smoothScalable; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + if ((style.isEmpty() || foundry->styles[k]->key == styleKey) && foundry->styles[k]->smoothScalable) { + smoothScalable = true; + goto end; + } + } + } + end: + return smoothScalable; +} + +/*! + Returns true if the font that has family \a family and style \a + style is scalable; otherwise returns false. + + \sa isBitmapScalable(), isSmoothlyScalable() +*/ +bool QFontDatabase::isScalable(const QString &family, + const QString &style) const +{ + QMutexLocker locker(fontDatabaseMutex()); + if (isSmoothlyScalable(family, style)) + return true; + return isBitmapScalable(family, style); +} + + +/*! + Returns a list of the point sizes available for the font that has + family \a family and style \a style. The list may be empty. + + \sa smoothSizes(), standardSizes() +*/ +QList<int> QFontDatabase::pointSizes(const QString &family, + const QString &style) +{ +#if defined(Q_WS_WIN) + // windows and macosx are always smoothly scalable + Q_UNUSED(family); + Q_UNUSED(style); + return standardSizes(); +#else + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QList<int> sizes; + + QtFontFamily *fam = d->family(familyName); + if (!fam) return sizes; + + +#ifdef Q_WS_X11 + int dpi = QX11Info::appDpiY(); +#else + const int dpi = qt_defaultDpiY(); // embedded +#endif + + for (int j = 0; j < fam->count; j++) { + QtFontFoundry *foundry = fam->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + QtFontStyle *style = foundry->style(styleKey); + if (!style) continue; + + if (style->smoothScalable) { + smoothScalable = true; + goto end; + } + for (int l = 0; l < style->count; l++) { + const QtFontSize *size = style->pixelSizes + l; + + if (size->pixelSize != 0 && size->pixelSize != USHRT_MAX) { + const uint pointSize = qRound(size->pixelSize * 72.0 / dpi); + if (! sizes.contains(pointSize)) + sizes.append(pointSize); + } + } + } + } + end: + if (smoothScalable) + return standardSizes(); + + qSort(sizes); + return sizes; +#endif +} + +/*! + Returns a QFont object that has family \a family, style \a style + and point size \a pointSize. If no matching font could be created, + a QFont object that uses the application's default font is + returned. +*/ +QFont QFontDatabase::font(const QString &family, const QString &style, + int pointSize) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return QApplication::font(); + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = bestStyle(&allStyles, styleKey); + + if (!s) // no styles found? + return QApplication::font(); + QFont fnt(family, pointSize, s->key.weight); + fnt.setStyle((QFont::Style)s->key.style); + return fnt; +} + + +/*! + Returns the point sizes of a font that has family \a family and + style \a style that will look attractive. The list may be empty. + For non-scalable fonts and bitmap scalable fonts, this function + is equivalent to pointSizes(). + + \sa pointSizes(), standardSizes() +*/ +QList<int> QFontDatabase::smoothSizes(const QString &family, + const QString &style) +{ +#ifdef Q_WS_WIN + Q_UNUSED(family); + Q_UNUSED(style); + return QFontDatabase::standardSizes(); +#else + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QList<int> sizes; + + QtFontFamily *fam = d->family(familyName); + if (!fam) + return sizes; + +#ifdef Q_WS_X11 + int dpi = QX11Info::appDpiY(); +#else + const int dpi = qt_defaultDpiY(); // embedded +#endif + + for (int j = 0; j < fam->count; j++) { + QtFontFoundry *foundry = fam->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + QtFontStyle *style = foundry->style(styleKey); + if (!style) continue; + + if (style->smoothScalable) { + smoothScalable = true; + goto end; + } + for (int l = 0; l < style->count; l++) { + const QtFontSize *size = style->pixelSizes + l; + + if (size->pixelSize != 0 && size->pixelSize != USHRT_MAX) { + const uint pointSize = qRound(size->pixelSize * 72.0 / dpi); + if (! sizes.contains(pointSize)) + sizes.append(pointSize); + } + } + } + } + end: + if (smoothScalable) + return QFontDatabase::standardSizes(); + + qSort(sizes); + return sizes; +#endif +} + + +/*! + Returns a list of standard font sizes. + + \sa smoothSizes(), pointSizes() +*/ +QList<int> QFontDatabase::standardSizes() +{ + QList<int> ret; + static const unsigned short standard[] = + { 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72, 0 }; + const unsigned short *sizes = standard; + while (*sizes) ret << *sizes++; + return ret; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is italic; otherwise returns false. + + \sa weight(), bold() +*/ +bool QFontDatabase::italic(const QString &family, const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return false; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s && s->key.style == QFont::StyleItalic; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is bold; otherwise returns false. + + \sa italic(), weight() +*/ +bool QFontDatabase::bold(const QString &family, + const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return false; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || + foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s && s->key.weight >= QFont::Bold; +} + + +/*! + Returns the weight of the font that has family \a family and style + \a style. If there is no such family and style combination, + returns -1. + + \sa italic(), bold() +*/ +int QFontDatabase::weight(const QString &family, + const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return -1; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || + foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s ? s->key.weight : -1; +} + + +/*! + Returns the names the \a writingSystem (e.g. for displaying to the + user in a dialog). +*/ +QString QFontDatabase::writingSystemName(WritingSystem writingSystem) +{ + const char *name = 0; + switch (writingSystem) { + case Any: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Any"); + break; + case Latin: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Latin"); + break; + case Greek: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Greek"); + break; + case Cyrillic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Cyrillic"); + break; + case Armenian: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Armenian"); + break; + case Hebrew: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Hebrew"); + break; + case Arabic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Arabic"); + break; + case Syriac: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Syriac"); + break; + case Thaana: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Thaana"); + break; + case Devanagari: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Devanagari"); + break; + case Bengali: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Bengali"); + break; + case Gurmukhi: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Gurmukhi"); + break; + case Gujarati: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Gujarati"); + break; + case Oriya: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Oriya"); + break; + case Tamil: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Tamil"); + break; + case Telugu: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Telugu"); + break; + case Kannada: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Kannada"); + break; + case Malayalam: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Malayalam"); + break; + case Sinhala: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Sinhala"); + break; + case Thai: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Thai"); + break; + case Lao: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Lao"); + break; + case Tibetan: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Tibetan"); + break; + case Myanmar: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Myanmar"); + break; + case Georgian: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Georgian"); + break; + case Khmer: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Khmer"); + break; + case SimplifiedChinese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Simplified Chinese"); + break; + case TraditionalChinese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Traditional Chinese"); + break; + case Japanese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Japanese"); + break; + case Korean: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Korean"); + break; + case Vietnamese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Vietnamese"); + break; + case Symbol: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Symbol"); + break; + case Ogham: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Ogham"); + break; + case Runic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Runic"); + break; + case Nko: + name = QT_TRANSLATE_NOOP("QFontDatabase", "N'Ko"); + break; + default: + Q_ASSERT_X(false, "QFontDatabase::writingSystemName", "invalid 'writingSystem' parameter"); + break; + } + return QApplication::translate("QFontDatabase", name); +} + + +/*! + Returns a string with sample characters from \a writingSystem. +*/ +QString QFontDatabase::writingSystemSample(WritingSystem writingSystem) +{ + QString sample; + switch (writingSystem) { + case Any: + case Symbol: + // show only ascii characters + sample += QLatin1String("AaBbzZ"); + break; + case Latin: + // This is cheating... we only show latin-1 characters so that we don't + // end up loading lots of fonts - at least on X11... + sample = QLatin1String("Aa"); + sample += QChar(0x00C3); + sample += QChar(0x00E1); + sample += QLatin1String("Zz"); + break; + case Greek: + sample += QChar(0x0393); + sample += QChar(0x03B1); + sample += QChar(0x03A9); + sample += QChar(0x03C9); + break; + case Cyrillic: + sample += QChar(0x0414); + sample += QChar(0x0434); + sample += QChar(0x0436); + sample += QChar(0x044f); + break; + case Armenian: + sample += QChar(0x053f); + sample += QChar(0x054f); + sample += QChar(0x056f); + sample += QChar(0x057f); + break; + case Hebrew: + sample += QChar(0x05D0); + sample += QChar(0x05D1); + sample += QChar(0x05D2); + sample += QChar(0x05D3); + break; + case Arabic: + sample += QChar(0x0628); + sample += QChar(0x0629); + sample += QChar(0x062A); + sample += QChar(0x063A); + break; + case Syriac: + sample += QChar(0x0715); + sample += QChar(0x0725); + sample += QChar(0x0716); + sample += QChar(0x0726); + break; + case Thaana: + sample += QChar(0x0784); + sample += QChar(0x0794); + sample += QChar(0x078c); + sample += QChar(0x078d); + break; + case Devanagari: + sample += QChar(0x0905); + sample += QChar(0x0915); + sample += QChar(0x0925); + sample += QChar(0x0935); + break; + case Bengali: + sample += QChar(0x0986); + sample += QChar(0x0996); + sample += QChar(0x09a6); + sample += QChar(0x09b6); + break; + case Gurmukhi: + sample += QChar(0x0a05); + sample += QChar(0x0a15); + sample += QChar(0x0a25); + sample += QChar(0x0a35); + break; + case Gujarati: + sample += QChar(0x0a85); + sample += QChar(0x0a95); + sample += QChar(0x0aa5); + sample += QChar(0x0ab5); + break; + case Oriya: + sample += QChar(0x0b06); + sample += QChar(0x0b16); + sample += QChar(0x0b2b); + sample += QChar(0x0b36); + break; + case Tamil: + sample += QChar(0x0b89); + sample += QChar(0x0b99); + sample += QChar(0x0ba9); + sample += QChar(0x0bb9); + break; + case Telugu: + sample += QChar(0x0c05); + sample += QChar(0x0c15); + sample += QChar(0x0c25); + sample += QChar(0x0c35); + break; + case Kannada: + sample += QChar(0x0c85); + sample += QChar(0x0c95); + sample += QChar(0x0ca5); + sample += QChar(0x0cb5); + break; + case Malayalam: + sample += QChar(0x0d05); + sample += QChar(0x0d15); + sample += QChar(0x0d25); + sample += QChar(0x0d35); + break; + case Sinhala: + sample += QChar(0x0d90); + sample += QChar(0x0da0); + sample += QChar(0x0db0); + sample += QChar(0x0dc0); + break; + case Thai: + sample += QChar(0x0e02); + sample += QChar(0x0e12); + sample += QChar(0x0e22); + sample += QChar(0x0e32); + break; + case Lao: + sample += QChar(0x0e8d); + sample += QChar(0x0e9d); + sample += QChar(0x0ead); + sample += QChar(0x0ebd); + break; + case Tibetan: + sample += QChar(0x0f00); + sample += QChar(0x0f01); + sample += QChar(0x0f02); + sample += QChar(0x0f03); + break; + case Myanmar: + sample += QChar(0x1000); + sample += QChar(0x1001); + sample += QChar(0x1002); + sample += QChar(0x1003); + break; + case Georgian: + sample += QChar(0x10a0); + sample += QChar(0x10b0); + sample += QChar(0x10c0); + sample += QChar(0x10d0); + break; + case Khmer: + sample += QChar(0x1780); + sample += QChar(0x1790); + sample += QChar(0x17b0); + sample += QChar(0x17c0); + break; + case SimplifiedChinese: + sample += QChar(0x4e2d); + sample += QChar(0x6587); + sample += QChar(0x8303); + sample += QChar(0x4f8b); + break; + case TraditionalChinese: + sample += QChar(0x4e2d); + sample += QChar(0x6587); + sample += QChar(0x7bc4); + sample += QChar(0x4f8b); + break; + case Japanese: + sample += QChar(0x30b5); + sample += QChar(0x30f3); + sample += QChar(0x30d7); + sample += QChar(0x30eb); + sample += QChar(0x3067); + sample += QChar(0x3059); + break; + case Korean: + sample += QChar(0xac00); + sample += QChar(0xac11); + sample += QChar(0xac1a); + sample += QChar(0xac2f); + break; + case Vietnamese: + { + static const char vietnameseUtf8[] = { + char(0xef), char(0xbb), char(0xbf), char(0xe1), char(0xbb), char(0x97), + char(0xe1), char(0xbb), char(0x99), + char(0xe1), char(0xbb), char(0x91), + char(0xe1), char(0xbb), char(0x93), + }; + sample += QString::fromUtf8(vietnameseUtf8, sizeof(vietnameseUtf8)); + break; + } + case Ogham: + sample += QChar(0x1681); + sample += QChar(0x1682); + sample += QChar(0x1683); + sample += QChar(0x1684); + break; + case Runic: + sample += QChar(0x16a0); + sample += QChar(0x16a1); + sample += QChar(0x16a2); + sample += QChar(0x16a3); + break; + case Nko: + sample += QChar(0x7ca); + sample += QChar(0x7cb); + sample += QChar(0x7cc); + sample += QChar(0x7cd); + break; + default: + break; + } + return sample; +} + + +void QFontDatabase::parseFontName(const QString &name, QString &foundry, QString &family) +{ + QT_PREPEND_NAMESPACE(parseFontName)(name, foundry, family); +} + +void QFontDatabase::createDatabase() +{ initializeDb(); } + +// used from qfontengine_ft.cpp +Q_GUI_EXPORT QByteArray qt_fontdata_from_index(int index) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->applicationFonts.value(index).data; +} + +int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &fileName) +{ + QFontDatabasePrivate::ApplicationFont font; + font.data = fontData; + font.fileName = fileName; + + int i; + for (i = 0; i < applicationFonts.count(); ++i) + if (applicationFonts.at(i).families.isEmpty()) + break; + if (i >= applicationFonts.count()) { + applicationFonts.append(ApplicationFont()); + i = applicationFonts.count() - 1; + } + + if (font.fileName.isEmpty() && !fontData.isEmpty()) + font.fileName = QString::fromLatin1(":qmemoryfonts/") + QString::number(i); + + registerFont(&font); + if (font.families.isEmpty()) + return -1; + + applicationFonts[i] = font; + + invalidate(); + return i; +} + +bool QFontDatabasePrivate::isApplicationFont(const QString &fileName) +{ + for (int i = 0; i < applicationFonts.count(); ++i) + if (applicationFonts.at(i).fileName == fileName) + return true; + return false; +} + +/*! + \since 4.2 + + Loads the font from the file specified by \a fileName and makes it available to + the application. An ID is returned that can be used to remove the font again + with removeApplicationFont() or to retrieve the list of family names contained + in the font. + + The function returns -1 if the font could not be loaded. + + Currently only TrueType fonts, TrueType font collections, and OpenType fonts are + supported. + + \note Adding application fonts on Unix/X11 platforms without fontconfig is + currently not supported. + + \note On Symbian, the font family names get truncated to a length of 20 characters. + + \sa addApplicationFontFromData(), applicationFontFamilies(), removeApplicationFont() +*/ +int QFontDatabase::addApplicationFont(const QString &fileName) +{ + QByteArray data; + QFile f(fileName); + if (!(f.fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag)) { + if (!f.open(QIODevice::ReadOnly)) + return -1; + data = f.readAll(); + } + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->addAppFont(data, fileName); +} + +/*! + \since 4.2 + + Loads the font from binary data specified by \a fontData and makes it available to + the application. An ID is returned that can be used to remove the font again + with removeApplicationFont() or to retrieve the list of family names contained + in the font. + + The function returns -1 if the font could not be loaded. + + Currently only TrueType fonts and TrueType font collections are supported. + + \bold{Note:} Adding application fonts on Unix/X11 platforms without fontconfig is + currently not supported. + + \note On Symbian, the font family names get truncated to a length of 20 characters. + + \sa addApplicationFont(), applicationFontFamilies(), removeApplicationFont() +*/ +int QFontDatabase::addApplicationFontFromData(const QByteArray &fontData) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->addAppFont(fontData, QString() /* fileName */); +} + +/*! + \since 4.2 + + Returns a list of font families for the given application font identified by + \a id. + + \sa addApplicationFont(), addApplicationFontFromData() +*/ +QStringList QFontDatabase::applicationFontFamilies(int id) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->applicationFonts.value(id).families; +} + +/*! + \fn bool QFontDatabase::removeApplicationFont(int id) + \since 4.2 + + Removes the previously loaded application font identified by \a + id. Returns true if unloading of the font succeeded; otherwise + returns false. + + \sa removeAllApplicationFonts(), addApplicationFont(), + addApplicationFontFromData() +*/ + +/*! + \fn bool QFontDatabase::removeAllApplicationFonts() + \since 4.2 + + Removes all application-local fonts previously added using addApplicationFont() + and addApplicationFontFromData(). + + Returns true if unloading of the fonts succeeded; otherwise + returns false. + + \sa removeApplicationFont(), addApplicationFont(), addApplicationFontFromData() +*/ + +/*! + \fn bool QFontDatabase::supportsThreadedFontRendering() + \since 4.4 + + Returns true if font rendering is supported outside the GUI + thread, false otherwise. In other words, a return value of false + means that all QPainter::drawText() calls outside the GUI thread + will not produce readable output. + + \sa {Thread-Support in Qt Modules#Painting In Threads}{Painting In Threads} +*/ + + +QT_END_NAMESPACE + diff --git a/src/gui/text/qfontdatabase.h b/src/gui/text/qfontdatabase.h new file mode 100644 index 0000000000..ae1130eadc --- /dev/null +++ b/src/gui/text/qfontdatabase.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDATABASE_H +#define QFONTDATABASE_H + +#include <QtGui/qwindowdefs.h> +#include <QtCore/qstring.h> +#include <QtGui/qfont.h> +#ifdef QT3_SUPPORT +#include <QtCore/qstringlist.h> +#include <QtCore/qlist.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStringList; +template <class T> class QList; +struct QFontDef; +class QFontEngine; + +class QFontDatabasePrivate; + +class Q_GUI_EXPORT QFontDatabase +{ + Q_GADGET + Q_ENUMS(WritingSystem) +public: + // do not re-order or delete entries from this enum without updating the + // QPF2 format and makeqpf!! + enum WritingSystem { + Any, + + Latin, + Greek, + Cyrillic, + Armenian, + Hebrew, + Arabic, + Syriac, + Thaana, + Devanagari, + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam, + Sinhala, + Thai, + Lao, + Tibetan, + Myanmar, + Georgian, + Khmer, + SimplifiedChinese, + TraditionalChinese, + Japanese, + Korean, + Vietnamese, + + Symbol, + Other = Symbol, + + Ogham, + Runic, + Nko, + + WritingSystemsCount + }; + + static QList<int> standardSizes(); + + QFontDatabase(); + + QList<WritingSystem> writingSystems() const; + QList<WritingSystem> writingSystems(const QString &family) const; + + QStringList families(WritingSystem writingSystem = Any) const; + QStringList styles(const QString &family) const; + QList<int> pointSizes(const QString &family, const QString &style = QString()); + QList<int> smoothSizes(const QString &family, const QString &style); + QString styleString(const QFont &font); + QString styleString(const QFontInfo &fontInfo); + + QFont font(const QString &family, const QString &style, int pointSize) const; + + bool isBitmapScalable(const QString &family, const QString &style = QString()) const; + bool isSmoothlyScalable(const QString &family, const QString &style = QString()) const; + bool isScalable(const QString &family, const QString &style = QString()) const; + bool isFixedPitch(const QString &family, const QString &style = QString()) const; + + bool italic(const QString &family, const QString &style) const; + bool bold(const QString &family, const QString &style) const; + int weight(const QString &family, const QString &style) const; + + static QString writingSystemName(WritingSystem writingSystem); + static QString writingSystemSample(WritingSystem writingSystem); + + static int addApplicationFont(const QString &fileName); + static int addApplicationFontFromData(const QByteArray &fontData); + static QStringList applicationFontFamilies(int id); + static bool removeApplicationFont(int id); + static bool removeAllApplicationFonts(); + + static bool supportsThreadedFontRendering(); + +private: + static void createDatabase(); + static void parseFontName(const QString &name, QString &foundry, QString &family); + static QString resolveFontFamilyAlias(const QString &family); +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + static QFontEngine *findFont(int script, const QFontPrivate *fp, const QFontDef &request); +#endif + static void load(const QFontPrivate *d, int script); +#ifdef Q_WS_X11 + static QFontEngine *loadXlfd(int screen, int script, const QFontDef &request, int force_encoding_id = -1); +#endif + + friend struct QFontDef; + friend class QFontPrivate; + friend class QFontDialog; + friend class QFontDialogPrivate; + friend class QFontEngineMultiXLFD; + friend class QFontEngineMultiQWS; + friend class QFontEngineMultiS60; + friend class QFontEngineMultiQPA; + + QFontDatabasePrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTDATABASE_H diff --git a/src/gui/text/qfontdatabase_mac.cpp b/src/gui/text/qfontdatabase_mac.cpp new file mode 100644 index 0000000000..5ba236b5f7 --- /dev/null +++ b/src/gui/text/qfontdatabase_mac.cpp @@ -0,0 +1,466 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qt_mac_p.h> +#include "qfontengine_p.h" +#include <qfile.h> +#include <qabstractfileengine.h> +#include <stdlib.h> +#include <qendian.h> +#include <private/qfontengine_coretext_p.h> +#include <private/qfontengine_mac_p.h> + +QT_BEGIN_NAMESPACE + +int qt_mac_pixelsize(const QFontDef &def, int dpi); //qfont_mac.cpp +int qt_mac_pointsize(const QFontDef &def, int dpi); //qfont_mac.cpp + +#ifndef QT_MAC_USE_COCOA +static void initWritingSystems(QtFontFamily *family, ATSFontRef atsFont) +{ + ByteCount length = 0; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, 0, 0, &length) != noErr) + return; + QVarLengthArray<uchar> os2Table(length); + if (length < 86 + || ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, length, os2Table.data(), &length) != noErr) + return; + + // See also qfontdatabase_win.cpp, offsets taken from OS/2 table in the TrueType spec + quint32 unicodeRange[4] = { + qFromBigEndian<quint32>(os2Table.data() + 42), + qFromBigEndian<quint32>(os2Table.data() + 46), + qFromBigEndian<quint32>(os2Table.data() + 50), + qFromBigEndian<quint32>(os2Table.data() + 54) + }; + quint32 codePageRange[2] = { qFromBigEndian<quint32>(os2Table.data() + 78), qFromBigEndian<quint32>(os2Table.data() + 82) }; + QList<QFontDatabase::WritingSystem> systems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); +#if 0 + QCFString name; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &name); + qDebug() << systems.count() << "writing systems for" << QString(name); +qDebug() << "first char" << hex << unicodeRange[0]; + for (int i = 0; i < systems.count(); ++i) + qDebug() << QFontDatabase::writingSystemName(systems.at(i)); +#endif + for (int i = 0; i < systems.count(); ++i) + family->writingSystems[systems.at(i)] = QtFontFamily::Supported; +} +#endif + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if(!db || db->count) + return; + +#if defined(QT_MAC_USE_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + QCFType<CTFontCollectionRef> collection = CTFontCollectionCreateFromAvailableFonts(0); + if(!collection) + return; + QCFType<CFArrayRef> fonts = CTFontCollectionCreateMatchingFontDescriptors(collection); + if(!fonts) + return; + QString foundry_name = "CoreText"; + const int numFonts = CFArrayGetCount(fonts); + for(int i = 0; i < numFonts; ++i) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i); + + QCFString family_name = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + QtFontFamily *family = db->family(family_name, true); + for(int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) + family->writingSystems[ws] = QtFontFamily::Supported; + QtFontFoundry *foundry = family->foundry(foundry_name, true); + + QtFontStyle::Key styleKey; + if(QCFType<CFDictionaryRef> styles = (CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)) { + if(CFNumberRef weight = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontWeightTrait)) { + Q_ASSERT(CFNumberIsFloatType(weight)); + double d; + if(CFNumberGetValue(weight, kCFNumberDoubleType, &d)) { + //qDebug() << "BOLD" << (QString)family_name << d; + styleKey.weight = (d > 0.0) ? QFont::Bold : QFont::Normal; + } + } + if(CFNumberRef italic = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontSlantTrait)) { + Q_ASSERT(CFNumberIsFloatType(italic)); + double d; + if(CFNumberGetValue(italic, kCFNumberDoubleType, &d)) { + //qDebug() << "ITALIC" << (QString)family_name << d; + if (d > 0.0) + styleKey.style = QFont::StyleItalic; + } + } + } + + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = true; + if(QCFType<CFNumberRef> size = (CFNumberRef)CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) { + //qDebug() << "WHEE"; + int pixel_size=0; + if(CFNumberIsFloatType(size)) { + double d; + CFNumberGetValue(size, kCFNumberDoubleType, &d); + pixel_size = d; + } else { + CFNumberGetValue(size, kCFNumberIntType, &pixel_size); + } + //qDebug() << "SIZE" << (QString)family_name << pixel_size; + if(pixel_size) + style->pixelSize(pixel_size, true); + } else { + //qDebug() << "WTF?"; + } + } +} else +#endif + { +#ifndef QT_MAC_USE_COCOA + FMFontIterator it; + if (!FMCreateFontIterator(0, 0, kFMUseGlobalScopeOption, &it)) { + while (true) { + FMFont fmFont; + if (FMGetNextFont(&it, &fmFont) != noErr) + break; + + FMFontFamily fmFamily; + FMFontStyle fmStyle; + QString familyName; + + QtFontStyle::Key styleKey; + + ATSFontRef atsFont = FMGetATSFontRefFromFont(fmFont); + + if (!FMGetFontFamilyInstanceFromFont(fmFont, &fmFamily, &fmStyle)) { + { //sanity check the font, and see if we can use it at all! --Sam + ATSUFontID fontID; + if(ATSUFONDtoFontID(fmFamily, 0, &fontID) != noErr) + continue; + } + + if (fmStyle & ::italic) + styleKey.style = QFont::StyleItalic; + if (fmStyle & ::bold) + styleKey.weight = QFont::Bold; + + ATSFontFamilyRef familyRef = FMGetATSFontFamilyRefFromFontFamily(fmFamily); + QCFString cfFamilyName;; + ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &cfFamilyName); + familyName = cfFamilyName; + } else { + QCFString cfFontName; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &cfFontName); + familyName = cfFontName; + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian<quint16>(data); + } + if (macStyle & 1) + styleKey.weight = QFont::Bold; + if (macStyle & 2) + styleKey.style = QFont::StyleItalic; + } + + QtFontFamily *family = db->family(familyName, true); + QtFontFoundry *foundry = family->foundry(QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->pixelSize(0, true); + style->smoothScalable = true; + + initWritingSystems(family, atsFont); + } + FMDisposeFontIterator(&it); + } +#endif + } + +} + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +static const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "Arial"; + break; + case QFont::Serif: + stylehint = "Times New Roman"; + break; + case QFont::TypeWriter: + stylehint = "Courier New"; + break; + default: + if (request.fixedPitch) + stylehint = "Courier New"; + break; + } + return stylehint; +} + +static inline float weightToFloat(unsigned int weight) +{ + return (weight - 50) / 100.0; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + // sanity checks + if(!qApp) + qWarning("QFont: Must construct a QApplication before a QFont"); + + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + Q_UNUSED(script); + + QFontDef req = d->request; + req.pixelSize = qt_mac_pixelsize(req, d->dpi); + + // set the point size to 0 to get better caching + req.pointSize = 0; + QFontCache::Key key = QFontCache::Key(req, QUnicodeTables::Common, d->screen); + + if(!(d->engineData = QFontCache::instance()->findEngineData(key))) { + d->engineData = new QFontEngineData; + QFontCache::instance()->insertEngineData(key, d->engineData); + } else { + d->engineData->ref.ref(); + } + if(d->engineData->engine) // already loaded + return; + + // set it to the actual pointsize, so QFontInfo will do the right thing + req.pointSize = qRound(qt_mac_pointsize(d->request, d->dpi)); + + QFontEngine *e = QFontCache::instance()->findEngine(key); + if(!e && qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + e = new QTestFontEngine(req.pixelSize); + e->fontDef = req; + } + + if(e) { + e->ref.ref(); + d->engineData->engine = e; + return; // the font info and fontdef should already be filled + } + + //find the font + QStringList family_list = familyList(req); + + const char *stylehint = styleHint(req); + if (stylehint) + family_list << QLatin1String(stylehint); + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + +#if defined(QT_MAC_USE_COCOA) + QCFString fontName = NULL, familyName = NULL; +#else + ATSFontFamilyRef familyRef = 0; + ATSFontRef fontRef = 0; +#endif + + QMutexLocker locker(fontDatabaseMutex()); + QFontDatabasePrivate *db = privateDb(); + if (!db->count) + initializeDb(); + for(int i = 0; i < family_list.size(); ++i) { + for (int k = 0; k < db->count; ++k) { + if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) { + QByteArray family_name = db->families[k]->name.toUtf8(); +#if defined(QT_MAC_USE_COCOA) + QCFType<CTFontRef> ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL); + if (ctFont) { + fontName = CTFontCopyFullName(ctFont); + familyName = CTFontCopyFamilyName(ctFont); + goto FamilyFound; + } +#else + familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + if (familyRef) { + fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + goto FamilyFound; + } +#endif + } + } + } +FamilyFound: + //fill in the engine's font definition + QFontDef fontDef = d->request; //copy.. + if(fontDef.pointSize < 0) + fontDef.pointSize = qt_mac_pointsize(fontDef, d->dpi); + else + fontDef.pixelSize = qt_mac_pixelsize(fontDef, d->dpi); + +#ifdef QT_MAC_USE_COCOA + fontDef.family = familyName; + QFontEngine *engine = new QCoreTextFontEngineMulti(fontName, fontDef, d->kerning); +#else + QCFString actualName; + if (ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr) + fontDef.family = actualName; + QFontEngine *engine = new QFontEngineMacMulti(familyRef, fontRef, fontDef, d->kerning); +#endif + d->engineData->engine = engine; + engine->ref.ref(); //a ref for the engineData->engine + QFontCache::instance()->insertEngine(key, engine); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + ATSFontContainerRef handle; + OSStatus e = noErr; + + if(fnt->data.isEmpty()) { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp + FSRef ref; + if(qt_mac_create_fsref(fnt->fileName, &ref) != noErr) + return; + + ATSFontActivateFromFileReference(&ref, kATSFontContextLocal, kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + } else +#endif + { +#ifndef Q_WS_MAC64 + extern Q_CORE_EXPORT OSErr qt_mac_create_fsspec(const QString &, FSSpec *); // global.cpp + FSSpec spec; + if(qt_mac_create_fsspec(fnt->fileName, &spec) != noErr) + return; + + e = ATSFontActivateFromFileSpecification(&spec, kATSFontContextLocal, kATSFontFormatUnspecified, + 0, kATSOptionFlagsDefault, &handle); +#endif + } + } else { + e = ATSFontActivateFromMemory((void *)fnt->data.constData(), fnt->data.size(), kATSFontContextLocal, + kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + + fnt->data = QByteArray(); + } + + if(e != noErr) + return; + + ItemCount fontCount = 0; + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, 0, 0, &fontCount); + if(e != noErr) + return; + + QVarLengthArray<ATSFontRef> containedFonts(fontCount); + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, fontCount, containedFonts.data(), &fontCount); + if(e != noErr) + return; + + fnt->families.clear(); +#if defined(QT_MAC_USE_COCOA) + // Make sure that the family name set on the font matches what + // kCTFontFamilyNameAttribute returns in initializeDb(). + // So far the best solution seems find the installed font + // using CoreText and get the family name from it. + // (ATSFontFamilyGetName appears to be the correct API, but also + // returns the font display name.) + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString fontPostScriptName; + ATSFontGetPostScriptName(containedFonts[i], kATSOptionFlagsDefault, &fontPostScriptName); + QCFType<CTFontDescriptorRef> font = CTFontDescriptorCreateWithNameAndSize(fontPostScriptName, 14); + QCFString familyName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + fnt->families.append(familyName); + } +#else + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString family; + ATSFontGetName(containedFonts[i], kATSOptionFlagsDefault, &family); + fnt->families.append(family); + } +#endif + + fnt->handle = handle; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if(handle < 0 || handle >= db->applicationFonts.count()) + return false; + + OSStatus e = ATSFontDeactivate(db->applicationFonts.at(handle).handle, + /*iRefCon=*/0, kATSOptionFlagsDefault); + if(e != noErr) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + for(int i = 0; i < db->applicationFonts.count(); ++i) { + if(!removeApplicationFont(i)) + return false; + } + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_qpa.cpp b/src/gui/text/qfontdatabase_qpa.cpp new file mode 100644 index 0000000000..7bcce56ac8 --- /dev/null +++ b/src/gui/text/qfontdatabase_qpa.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlibraryinfo.h" +#include <QtCore/qsettings.h> + +#include "qfontengine_qpa_p.h" +#include "qplatformdefs.h" + +#include <QtGui/private/qapplication_p.h> +#include <QtGui/qplatformfontdatabase_qpa.h> + +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +Q_GUI_EXPORT void qt_registerFont(const QString &familyName, const QString &foundryname, int weight, + QFont::Style style, int stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *handle) +{ + QFontDatabasePrivate *d = privateDb(); + // qDebug() << "Adding font" << familyname << weight << italic << pixelSize << file << fileIndex << antialiased; + QtFontStyle::Key styleKey; + styleKey.style = style; + styleKey.weight = weight; + styleKey.stretch = stretch; + QtFontFamily *f = d->family(familyName, true); + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + if (writingSystems.supported(QFontDatabase::WritingSystem(i))) { + f->writingSystems[i] = QtFontFamily::Supported; + } else { + f->writingSystems[i] = QtFontFamily::Unsupported; + } + } + + QtFontFoundry *foundry = f->foundry(foundryname, true); + QtFontStyle *fontStyle = foundry->style(styleKey, true); + fontStyle->smoothScalable = scalable; + fontStyle->antialiased = antialiased; + QtFontSize *size = fontStyle->pixelSize(pixelSize?pixelSize:SMOOTH_SCALABLE, true); + size->handle = handle; +} + +static QStringList fallbackFamilies(const QString &family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) +{ + QStringList retList = QApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script); + QFontDatabasePrivate *db = privateDb(); + + QStringList::iterator i; + for (i = retList.begin(); i != retList.end(); ++i) { + bool contains = false; + for (int j = 0; j < db->count; j++) { + QtFontFamily *qtFamily = db->families[j]; + if (!(i->compare(qtFamily->name,Qt::CaseInsensitive))) { + contains = true; + break; + } + } + if (!contains) { + i = retList.erase(i); + i--; + } + } + return retList; +} + +static void initializeDb() +{ + static int initialized = false; + + if (!initialized) { + //init by asking for the platformfontdb for the first time :) + QApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase(); + initialized = true; + } +} + +#ifndef QT_NO_SETTINGS +// called from qapplication_qws.cpp +void qt_applyFontDatabaseSettings(const QSettings &settings) +{ + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + if (settings.contains(family->name)) + family->fallbackFamilies = settings.value(family->name).toStringList(); + } + + if (settings.contains(QLatin1String("Global Fallbacks"))) + db->fallbackFamilies = settings.value(QLatin1String("Global Fallbacks")).toStringList(); +} +#endif // QT_NO_SETTINGS + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +static +QFontEngine *loadSingleEngine(int script, + const QFontDef &request, + QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + Q_UNUSED(foundry); + + Q_ASSERT(size); + int pixelSize = size->pixelSize; + if (!pixelSize || (style->smoothScalable && pixelSize == SMOOTH_SCALABLE)) + pixelSize = request.pixelSize; + + QFontDef def = request; + def.pixelSize = pixelSize; + + QFontCache::Key key(def,script); + QFontEngine *engine = QFontCache::instance()->findEngine(key); + if (!engine) { + QPlatformFontDatabase *pfdb = QApplicationPrivate::platformIntegration()->fontDatabase(); + engine = pfdb->fontEngine(def,QUnicodeTables::Script(script),size->handle); + if (engine) { + QFontCache::Key key(def,script); + QFontCache::instance()->instance()->insertEngine(key,engine); + } + } + return engine; +} + +static +QFontEngine *loadEngine(int script, const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + + QFontEngine *engine = loadSingleEngine(script, request, foundry, style, size); + //make sure that the db has all fallback families + if (engine + && !(request.styleStrategy & QFont::NoFontMerging) && !engine->symbol ) { + + if (family && !family->askedForFallback) { + QFont::Style fontStyle = QFont::Style(style->key.style); + QFont::StyleHint styleHint = QFont::StyleHint(request.styleHint); + if (styleHint == QFont::AnyStyle && request.fixedPitch) + styleHint = QFont::TypeWriter; + family->fallbackFamilies = fallbackFamilies(family->name,fontStyle,styleHint,QUnicodeTables::Script(script)); + + family->askedForFallback = true; + } + + QStringList fallbacks = privateDb()->fallbackFamilies; + if (family && !family->fallbackFamilies.isEmpty()) + fallbacks = family->fallbackFamilies; + + engine = new QFontEngineMultiQPA(engine, script, fallbacks); + } + + return engine; +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + QFontDatabasePrivate *db = privateDb(); + + fnt->families = QApplicationPrivate::platformIntegration()->fontDatabase()->addApplicationFont(fnt->data,fnt->fileName); + + db->reregisterAppFonts = true; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + db->applicationFonts.clear(); + db->invalidate(); + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +/*! + \internal +*/ +QFontEngine * +QFontDatabase::findFont(int script, const QFontPrivate *fp, + const QFontDef &request) +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int force_encoding_id = -1; + + if (!privateDb()->count) + initializeDb(); + + QFontEngine *engine; + QFontCache::Key key(request, script); + engine = QFontCache::instance()->findEngine(key); + if (engine) { + qDebug() << "Cache hit level 1"; + return engine; + } + + QString family_name, foundry_name; + + parseFontName(request.family, foundry_name, family_name); + + if (qt_enable_test_font && request.family == QLatin1String("__Qt__Box__Engine__")) { + engine =new QTestFontEngine(request.pixelSize); + engine->fontDef = request; + } + + QtFontDesc desc; + match(script, request, family_name, foundry_name, force_encoding_id, &desc); + if (desc.family != 0 && desc.foundry != 0 && desc.style != 0) { + engine = loadEngine(script, request, desc.family, desc.foundry, desc.style, desc.size); + } else { + FM_DEBUG(" NO MATCH FOUND\n"); + } + + if (engine) { + initFontDef(desc, request, &engine->fontDef); + + if (fp) { + QFontDef def = request; + if (def.family.isEmpty()) { + def.family = fp->request.family; + def.family = def.family.left(def.family.indexOf(QLatin1Char(','))); + } + } + } + + if (!engine) { + if (!request.family.isEmpty()) { + QStringList fallbacks = fallbackFamilies(request.family,QFont::Style(request.style),QFont::StyleHint(request.styleHint),QUnicodeTables::Script(script)); + for (int i = 0; i < fallbacks.size(); i++) { + QFontDef def = request; + def.family = fallbacks.at(i); + QFontCache::Key key(def,script); + engine = QFontCache::instance()->findEngine(key); + if (!engine) { + QtFontDesc desc; + match(script, def, def.family, QLatin1String(""), 0, &desc); + if (desc.family == 0 && desc.foundry == 0 && desc.style == 0) { + continue; + } + engine = loadEngine(script, def, desc.family, desc.foundry, desc.style, desc.size); + if (engine) { + initFontDef(desc, def, &engine->fontDef); + break; + } + } + } + } + + if (!engine) + engine = new QFontEngineBox(request.pixelSize); + + FM_DEBUG("returning box engine"); + } + + if (fp && fp->dpi > 0) { + engine->fontDef.pointSize = qreal(double((engine->fontDef.pixelSize * 72) / fp->dpi)); + } else { + engine->fontDef.pointSize = request.pointSize; + } + + return engine; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontDef req = d->request; + + if (req.pixelSize == -1) { + req.pixelSize = floor(((req.pointSize * d->dpi) / 72) * 100 + 0.5) / 100; + req.pixelSize = qRound(req.pixelSize); + } + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72.0/d->dpi; + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, script); + + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + // list of families to try + QStringList family_list; + + if (!req.family.isEmpty()) { + family_list = familyList(req); + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + } + + // null family means find the first font matching the specified script + family_list << QString(); + + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; !fe && it != end; ++it) { + req.family = *it; + + fe = QFontDatabase::findFont(script, d, req); + if (fe && (fe->type()==QFontEngine::Box) && !req.family.isEmpty()) + fe = 0; + } + + if (fe->symbol || (d->request.styleStrategy & QFont::NoFontMerging)) { + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (!d->engineData->engines[i]) { + d->engineData->engines[i] = fe; + fe->ref.ref(); + } + } + } else { + d->engineData->engines[script] = fe; + fe->ref.ref(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_qws.cpp b/src/gui/text/qfontdatabase_qws.cpp new file mode 100644 index 0000000000..d076de05d3 --- /dev/null +++ b/src/gui/text/qfontdatabase_qws.cpp @@ -0,0 +1,975 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdir.h" +#if defined(Q_WS_QWS) +#include "qscreen_qws.h" //so we can check for rotation +#include "qwindowsystem_qws.h" +#endif +#include "qlibraryinfo.h" +#include "qabstractfileengine.h" +#include <QtCore/qsettings.h> +#if !defined(QT_NO_FREETYPE) +#include "qfontengine_ft_p.h" + +#include <ft2build.h> +#include FT_FREETYPE_H + +#endif +#include "qfontengine_qpf_p.h" +#include "private/qfactoryloader_p.h" +#include "private/qcore_unix_p.h" // overrides QT_OPEN +#include "qabstractfontengine_qws.h" +#include "qabstractfontengine_p.h" +#include <qdatetime.h> +#include "qplatformdefs.h" + +// for mmap +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef QT_FONTS_ARE_RESOURCES +#include <qresource.h> +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QFontEngineFactoryInterface_iid, QLatin1String("/fontengines"), Qt::CaseInsensitive)) +#endif + +const quint8 DatabaseVersion = 4; + +// QFontDatabasePrivate::addFont() went into qfontdatabase.cpp + +#ifndef QT_NO_QWS_QPF2 +void QFontDatabasePrivate::addQPF2File(const QByteArray &file) +{ +#ifndef QT_FONTS_ARE_RESOURCES + struct stat st; + if (stat(file.constData(), &st)) + return; + int f = QT_OPEN(file, O_RDONLY, 0); + if (f < 0) + return; + const uchar *data = (const uchar *)mmap(0, st.st_size, PROT_READ, MAP_SHARED, f, 0); + const int dataSize = st.st_size; +#else + QResource res(QLatin1String(file.constData())); + const uchar *data = res.data(); + const int dataSize = res.size(); + //qDebug() << "addQPF2File" << file << data; +#endif + if (data && data != (const uchar *)MAP_FAILED) { + if (QFontEngineQPF::verifyHeader(data, dataSize)) { + QString fontName = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_FontName).toString(); + int pixelSize = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_PixelSize).toInt(); + QVariant weight = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_Weight); + QVariant style = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_Style); + QByteArray writingSystemBits = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_WritingSystems).toByteArray(); + + if (!fontName.isEmpty() && pixelSize) { + int fontWeight = 50; + if (weight.type() == QVariant::Int || weight.type() == QVariant::UInt) + fontWeight = weight.toInt(); + + bool italic = static_cast<QFont::Style>(style.toInt()) & QFont::StyleItalic; + + QList<QFontDatabase::WritingSystem> writingSystems; + for (int i = 0; i < writingSystemBits.count(); ++i) { + uchar currentByte = writingSystemBits.at(i); + for (int j = 0; j < 8; ++j) { + if (currentByte & 1) + writingSystems << QFontDatabase::WritingSystem(i * 8 + j); + currentByte >>= 1; + } + } + + addFont(fontName, /*foundry*/ "prerendered", fontWeight, italic, + pixelSize, file, /*fileIndex*/ 0, + /*antialiased*/ true, writingSystems); + } + } else { + qDebug() << "header verification of QPF2 font" << file << "failed. maybe it is corrupt?"; + } +#ifndef QT_FONTS_ARE_RESOURCES + munmap((void *)data, st.st_size); +#endif + } +#ifndef QT_FONTS_ARE_RESOURCES + QT_CLOSE(f); +#endif +} +#endif // QT_NO_QWS_QPF2 + +// QFontDatabasePrivate::addTTFile() went into qfontdatabase.cpp + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt); + +extern QString qws_fontCacheDir(); + +#ifndef QT_FONTS_ARE_RESOURCES +bool QFontDatabasePrivate::loadFromCache(const QString &fontPath) +{ +#ifdef Q_WS_QWS + const bool weAreTheServer = QWSServer::instance(); +#else + const bool weAreTheServer = true; // assume single-process +#endif + + QString fontDirFile = fontPath + QLatin1String("/fontdir"); + + QFile binaryDb(qws_fontCacheDir() + QLatin1String("/fontdb")); + + if (weAreTheServer) { + QDateTime dbTimeStamp = QFileInfo(binaryDb.fileName()).lastModified(); + + QDateTime fontPathTimeStamp = QFileInfo(fontPath).lastModified(); + if (dbTimeStamp < fontPathTimeStamp) + return false; // let the caller create the cache + + if (QFile::exists(fontDirFile)) { + QDateTime fontDirTimeStamp = QFileInfo(fontDirFile).lastModified(); + if (dbTimeStamp < fontDirTimeStamp) + return false; + } + } + + if (!binaryDb.open(QIODevice::ReadOnly)) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Could not open font database cache!"); + } + + QDataStream stream(&binaryDb); + quint8 version = 0; + quint8 dataStreamVersion = 0; + stream >> version >> dataStreamVersion; + if (version != DatabaseVersion || dataStreamVersion != stream.version()) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Wrong version of the font database cache detected. Found %d/%d expected %d/%d", + version, dataStreamVersion, DatabaseVersion, stream.version()); + } + + QString originalFontPath; + stream >> originalFontPath; + if (originalFontPath != fontPath) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Font path doesn't match. Found %s in database, expected %s", qPrintable(originalFontPath), qPrintable(fontPath)); + } + + QString familyname; + stream >> familyname; + //qDebug() << "populating database from" << binaryDb.fileName(); + while (!familyname.isEmpty() && !stream.atEnd()) { + QString foundryname; + int weight; + quint8 italic; + int pixelSize; + QByteArray file; + int fileIndex; + quint8 antialiased; + quint8 writingSystemCount; + + QList<QFontDatabase::WritingSystem> writingSystems; + + stream >> foundryname >> weight >> italic >> pixelSize + >> file >> fileIndex >> antialiased >> writingSystemCount; + + for (quint8 i = 0; i < writingSystemCount; ++i) { + quint8 ws; + stream >> ws; + writingSystems.append(QFontDatabase::WritingSystem(ws)); + } + + addFont(familyname, foundryname.toLatin1().constData(), weight, italic, pixelSize, file, fileIndex, antialiased, + writingSystems); + + stream >> familyname; + } + + stream >> fallbackFamilies; + //qDebug() << "fallback families from cache:" << fallbackFamilies; + return true; +} +#endif // QT_FONTS_ARE_RESOURCES + +/*! + \internal +*/ + +static QString qwsFontPath() +{ + QString fontpath = QString::fromLocal8Bit(qgetenv("QT_QWS_FONTDIR")); + if (fontpath.isEmpty()) { +#ifdef QT_FONTS_ARE_RESOURCES + fontpath = QLatin1String(":/qt/fonts"); +#else +#ifndef QT_NO_SETTINGS + fontpath = QLibraryInfo::location(QLibraryInfo::LibrariesPath); + fontpath += QLatin1String("/fonts"); +#else + fontpath = QLatin1String("/lib/fonts"); +#endif +#endif //QT_FONTS_ARE_RESOURCES + } + + return fontpath; +} + +#if defined(QFONTDATABASE_DEBUG) && defined(QT_FONTS_ARE_RESOURCES) +class FriendlyResource : public QResource +{ +public: + bool isDir () const { return QResource::isDir(); } + bool isFile () const { return QResource::isFile(); } + QStringList children () const { return QResource::children(); } +}; +#endif +/*! + \internal +*/ +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + QString fontpath = qwsFontPath(); +#ifndef QT_FONTS_ARE_RESOURCES + QString fontDirFile = fontpath + QLatin1String("/fontdir"); + + if(!QFile::exists(fontpath)) { + qFatal("QFontDatabase: Cannot find font directory %s - is Qt installed correctly?", + fontpath.toLocal8Bit().constData()); + } + + const bool loaded = db->loadFromCache(fontpath); + + if (db->reregisterAppFonts) { + db->reregisterAppFonts = false; + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!db->applicationFonts.at(i).families.isEmpty()) { + registerFont(&db->applicationFonts[i]); + } + } + + if (loaded) + return; + + QString dbFileName = qws_fontCacheDir() + QLatin1String("/fontdb"); + + QFile binaryDb(dbFileName + QLatin1String(".tmp")); + binaryDb.open(QIODevice::WriteOnly | QIODevice::Truncate); + db->stream = new QDataStream(&binaryDb); + *db->stream << DatabaseVersion << quint8(db->stream->version()) << fontpath; +// qDebug() << "creating binary database at" << binaryDb.fileName(); + + // Load in font definition file + FILE* fontdef=fopen(fontDirFile.toLocal8Bit().constData(),"r"); + if (fontdef) { + char buf[200]=""; + char name[200]=""; + char render[200]=""; + char file[200]=""; + char isitalic[10]=""; + char flags[10]=""; + do { + fgets(buf,200,fontdef); + if (buf[0] != '#') { + int weight=50; + int size=0; + sscanf(buf,"%s %s %s %s %d %d %s",name,file,render,isitalic,&weight,&size,flags); + QString filename; + if (file[0] != '/') + filename.append(fontpath).append(QLatin1Char('/')); + filename += QLatin1String(file); + bool italic = isitalic[0] == 'y'; + bool smooth = QByteArray(flags).contains('s'); + if (file[0] && QFile::exists(filename)) + db->addFont(QString::fromUtf8(name), /*foundry*/"", weight, italic, size/10, QFile::encodeName(filename), /*fileIndex*/ 0, smooth); + } + } while (!feof(fontdef)); + fclose(fontdef); + } + + + QDir dir(fontpath, QLatin1String("*.qpf")); + for (int i=0; i<int(dir.count()); i++) { + int u0 = dir[i].indexOf(QLatin1Char('_')); + int u1 = dir[i].indexOf(QLatin1Char('_'), u0+1); + int u2 = dir[i].indexOf(QLatin1Char('_'), u1+1); + int u3 = dir[i].indexOf(QLatin1Char('.'), u1+1); + if (u2 < 0) u2 = u3; + + QString familyname = dir[i].left(u0); + int pixelSize = dir[i].mid(u0+1,u1-u0-1).toInt()/10; + bool italic = dir[i].mid(u2-1,1) == QLatin1String("i"); + int weight = dir[i].mid(u1+1,u2-u1-1-(italic?1:0)).toInt(); + + db->addFont(familyname, /*foundry*/ "qt", weight, italic, pixelSize, QFile::encodeName(dir.absoluteFilePath(dir[i])), + /*fileIndex*/ 0, /*antialiased*/ true); + } + +#ifndef QT_NO_FREETYPE + dir.setNameFilters(QStringList() << QLatin1String("*.ttf") + << QLatin1String("*.ttc") << QLatin1String("*.pfa") + << QLatin1String("*.pfb")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); +// qDebug() << "looking at" << file; + db->addTTFile(file); + } +#endif + +#ifndef QT_NO_QWS_QPF2 + dir.setNameFilters(QStringList() << QLatin1String("*.qpf2")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); +// qDebug() << "looking at" << file; + db->addQPF2File(file); + } +#endif + +#else //QT_FONTS_ARE_RESOURCES +#ifdef QFONTDATABASE_DEBUG + { + QResource fontdir(fontpath); + FriendlyResource *fr = static_cast<FriendlyResource*>(&fontdir); + qDebug() << "fontdir" << fr->isValid() << fr->isDir() << fr->children(); + + } +#endif +#ifndef QT_NO_QWS_QPF2 + QDir dir(fontpath, QLatin1String("*.qpf2")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); + //qDebug() << "looking at" << file; + db->addQPF2File(file); + } +#endif +#endif //QT_FONTS_ARE_RESOURCES + + +#ifdef QFONTDATABASE_DEBUG + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + FD_DEBUG("'%s' %s", qPrintable(family->name), (family->fixedPitch ? "fixed" : "")); +#if 0 + for (int i = 0; i < QFont::LastPrivateScript; ++i) { + FD_DEBUG("\t%s: %s", qPrintable(QFontDatabase::scriptName((QFont::Script) i)), + ((family->scripts[i] & QtFontFamily::Supported) ? "Supported" : + (family->scripts[i] & QtFontFamily::UnSupported) == QtFontFamily::UnSupported ? + "UnSupported" : "Unknown")); + } +#endif + + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + FD_DEBUG("\t\t'%s'", qPrintable(foundry->name)); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + FD_DEBUG("\t\t\tstyle: style=%d weight=%d\n" + "\t\t\tstretch=%d", + style->key.style, style->key.weight, + style->key.stretch); + if (style->smoothScalable) + FD_DEBUG("\t\t\t\tsmooth scalable"); + else if (style->bitmapScalable) + FD_DEBUG("\t\t\t\tbitmap scalable"); + if (style->pixelSizes) { + FD_DEBUG("\t\t\t\t%d pixel sizes", style->count); + for (int z = 0; z < style->count; ++z) { + QtFontSize *size = style->pixelSizes + z; + FD_DEBUG("\t\t\t\t size %5d", + size->pixelSize); + } + } + } + } + } +#endif // QFONTDATABASE_DEBUG + +#ifndef QT_NO_LIBRARY + QStringList pluginFoundries = loader()->keys(); +// qDebug() << "plugin foundries:" << pluginFoundries; + for (int i = 0; i < pluginFoundries.count(); ++i) { + const QString foundry(pluginFoundries.at(i)); + + QFontEngineFactoryInterface *factory = qobject_cast<QFontEngineFactoryInterface *>(loader()->instance(foundry)); + if (!factory) { + qDebug() << "Could not load plugin for foundry" << foundry; + continue; + } + + QList<QFontEngineInfo> fonts = factory->availableFontEngines(); + for (int i = 0; i < fonts.count(); ++i) { + QFontEngineInfo info = fonts.at(i); + + int weight = info.weight(); + if (weight <= 0) + weight = QFont::Normal; + + db->addFont(info.family(), foundry.toLatin1().constData(), + weight, info.style() != QFont::StyleNormal, + qRound(info.pixelSize()), /*file*/QByteArray(), + /*fileIndex*/0, /*antiAliased*/true, + info.writingSystems()); + } + } +#endif + +#ifndef QT_FONTS_ARE_RESOURCES + // the empty string/familyname signifies the end of the font list. + *db->stream << QString(); +#endif + { + bool coveredWritingSystems[QFontDatabase::WritingSystemsCount] = { 0 }; + + db->fallbackFamilies.clear(); + + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + bool add = false; + if (family->count == 0) + continue; + if (family->bogusWritingSystems) + continue; + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (coveredWritingSystems[ws]) + continue; + if (family->writingSystems[ws] & QtFontFamily::Supported) { + coveredWritingSystems[ws] = true; + add = true; + } + } + if (add) + db->fallbackFamilies << family->name; + } + //qDebug() << "fallbacks on the server:" << db->fallbackFamilies; +#ifndef QT_FONTS_ARE_RESOURCES + *db->stream << db->fallbackFamilies; +#endif + } +#ifndef QT_FONTS_ARE_RESOURCES + delete db->stream; + db->stream = 0; + QFile::remove(dbFileName); + binaryDb.rename(dbFileName); +#endif +} + +// called from qwindowsystem_qws.cpp +void qt_qws_init_fontdb() +{ + initializeDb(); +} + +#ifndef QT_NO_SETTINGS +// called from qapplication_qws.cpp +void qt_applyFontDatabaseSettings(const QSettings &settings) +{ + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + if (settings.contains(family->name)) + family->fallbackFamilies = settings.value(family->name).toStringList(); + } + + if (settings.contains(QLatin1String("Global Fallbacks"))) + db->fallbackFamilies = settings.value(QLatin1String("Global Fallbacks")).toStringList(); +} +#endif // QT_NO_SETTINGS + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +#ifndef QT_NO_FREETYPE + +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20105 +#define X_SIZE(face,i) ((face)->available_sizes[i].x_ppem) +#define Y_SIZE(face,i) ((face)->available_sizes[i].y_ppem) +#else +#define X_SIZE(face,i) ((face)->available_sizes[i].width << 6) +#define Y_SIZE(face,i) ((face)->available_sizes[i].height << 6) +#endif + +#endif // QT_NO_FREETYPE + +static +QFontEngine *loadSingleEngine(int script, const QFontPrivate *fp, + const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + Q_UNUSED(script); + Q_UNUSED(fp); +#ifdef QT_NO_FREETYPE + Q_UNUSED(foundry); +#endif +#ifdef QT_NO_QWS_QPF + Q_UNUSED(family); +#endif + Q_ASSERT(size); + + int pixelSize = size->pixelSize; + if (!pixelSize || (style->smoothScalable && pixelSize == SMOOTH_SCALABLE)) + pixelSize = request.pixelSize; + +#ifndef QT_NO_QWS_QPF2 + if (foundry->name == QLatin1String("prerendered")) { +#ifdef QT_FONTS_ARE_RESOURCES + QResource res(QLatin1String(size->fileName.constData())); + if (res.isValid()) { + QFontEngineQPF *fe = new QFontEngineQPF(request, res.data(), res.size()); + if (fe->isValid()) + return fe; + delete fe; + qDebug() << "fontengine is not valid! " << size->fileName; + } else { + qDebug() << "Resource not valid" << size->fileName; + } +#else + int f = ::open(size->fileName, O_RDONLY, 0); + if (f >= 0) { + QFontEngineQPF *fe = new QFontEngineQPF(request, f); + if (fe->isValid()) + return fe; + delete fe; // will close f + qDebug() << "fontengine is not valid!"; + } +#endif + } else +#endif + if ( foundry->name != QLatin1String("qt") ) { ///#### is this the best way???? + QString file = QFile::decodeName(size->fileName); + + QFontDef def = request; + def.pixelSize = pixelSize; + +#ifdef QT_NO_QWS_SHARE_FONTS + bool shareFonts = false; +#else + static bool dontShareFonts = !qgetenv("QWS_NO_SHARE_FONTS").isEmpty(); + bool shareFonts = !dontShareFonts; +#endif + + QScopedPointer<QFontEngine> engine; + +#ifndef QT_NO_LIBRARY + QFontEngineFactoryInterface *factory = qobject_cast<QFontEngineFactoryInterface *>(loader()->instance(foundry->name)); + if (factory) { + QFontEngineInfo info; + info.setFamily(request.family); + info.setPixelSize(request.pixelSize); + info.setStyle(QFont::Style(request.style)); + info.setWeight(request.weight); + // #### antialiased + + QAbstractFontEngine *customEngine = factory->create(info); + if (customEngine) { + engine.reset(new QProxyFontEngine(customEngine, def)); + + if (shareFonts) { + QVariant hint = customEngine->fontProperty(QAbstractFontEngine::CacheGlyphsHint); + if (hint.isValid()) + shareFonts = hint.toBool(); + else + shareFonts = (pixelSize < 64); + } + } + } +#endif // QT_NO_LIBRARY + if ((engine.isNull() && !file.isEmpty() && QFile::exists(file)) || privateDb()->isApplicationFont(file)) { + QFontEngine::FaceId faceId; + faceId.filename = file.toLocal8Bit(); + faceId.index = size->fileIndex; + +#ifndef QT_NO_FREETYPE + + QScopedPointer<QFontEngineFT> fte(new QFontEngineFT(def)); + bool antialias = style->antialiased && !(request.styleStrategy & QFont::NoAntialias); + if (fte->init(faceId, antialias, + antialias ? QFontEngineFT::Format_A8 : QFontEngineFT::Format_Mono)) { +#ifdef QT_NO_QWS_QPF2 + return fte.take(); +#else + // try to distinguish between bdf and ttf fonts we can pre-render + // and don't try to share outline fonts + shareFonts = shareFonts + && !fte->defaultGlyphs()->outline_drawing + && !fte->getSfntTable(MAKE_TAG('h', 'e', 'a', 'd')).isEmpty(); + engine.reset(fte.take()); +#endif + } +#endif // QT_NO_FREETYPE + } + if (!engine.isNull()) { +#if !defined(QT_NO_QWS_QPF2) && !defined(QT_FONTS_ARE_RESOURCES) + if (shareFonts) { + QScopedPointer<QFontEngineQPF> fe(new QFontEngineQPF(def, -1, engine.data())); + engine.take(); + if (fe->isValid()) + return fe.take(); + qWarning("Initializing QFontEngineQPF failed for %s", qPrintable(file)); + engine.reset(fe->takeRenderingEngine()); + } +#endif + return engine.take(); + } + } else + { +#ifndef QT_NO_QWS_QPF + QString fn = qwsFontPath(); + fn += QLatin1Char('/'); + fn += family->name.toLower() + + QLatin1Char('_') + QString::number(pixelSize*10) + + QLatin1Char('_') + QString::number(style->key.weight) + + (style->key.style == QFont::StyleItalic ? + QLatin1String("i.qpf") : QLatin1String(".qpf")); + //###rotation ### + + QFontEngine *fe = new QFontEngineQPF1(request, fn); + return fe; +#endif // QT_NO_QWS_QPF + } + return new QFontEngineBox(pixelSize); +} + +static +QFontEngine *loadEngine(int script, const QFontPrivate *fp, + const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + QScopedPointer<QFontEngine> engine(loadSingleEngine(script, fp, request, family, foundry, + style, size)); +#ifndef QT_NO_QWS_QPF + if (!engine.isNull() + && script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) && !engine->symbol) { + + QStringList fallbacks = privateDb()->fallbackFamilies; + + if (family && !family->fallbackFamilies.isEmpty()) + fallbacks = family->fallbackFamilies; + + QFontEngine *fe = new QFontEngineMultiQWS(engine.data(), script, fallbacks); + engine.take(); + engine.reset(fe); + } +#endif + return engine.take(); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + QFontDatabasePrivate *db = privateDb(); +#ifdef QT_NO_FREETYPE + Q_UNUSED(fnt); +#else + fnt->families = db->addTTFile(QFile::encodeName(fnt->fileName), fnt->data); + db->fallbackFamilies += fnt->families; +#endif + db->reregisterAppFonts = true; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + db->applicationFonts.clear(); + db->invalidate(); + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +/*! + \internal +*/ +QFontEngine * +QFontDatabase::findFont(int script, const QFontPrivate *fp, + const QFontDef &request) +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int force_encoding_id = -1; + + if (!privateDb()->count) + initializeDb(); + + QScopedPointer<QFontEngine> fe; + if (fp) { + if (fp->rawMode) { + fe.reset(loadEngine(script, fp, request, 0, 0, 0, 0)); + + // if we fail to load the rawmode font, use a 12pixel box engine instead + if (fe.isNull()) + fe.reset(new QFontEngineBox(12)); + return fe.take(); + } + + QFontCache::Key key(request, script); + fe.reset(QFontCache::instance()->findEngine(key)); + if (! fe.isNull()) + return fe.take(); + } + + QString family_name, foundry_name; + QtFontStyle::Key styleKey; + styleKey.style = request.style; + styleKey.weight = request.weight; + styleKey.stretch = request.stretch; + char pitch = request.ignorePitch ? '*' : request.fixedPitch ? 'm' : 'p'; + + parseFontName(request.family, foundry_name, family_name); + + FM_DEBUG("QFontDatabase::findFont\n" + " request:\n" + " family: %s [%s], script: %d\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %g\n" + " pitch: %c", + family_name.isEmpty() ? "-- first in script --" : family_name.toLatin1().constData(), + foundry_name.isEmpty() ? "-- any --" : foundry_name.toLatin1().constData(), + script, request.weight, request.style, request.stretch, request.pixelSize, pitch); + + if (qt_enable_test_font && request.family == QLatin1String("__Qt__Box__Engine__")) { + fe.reset(new QTestFontEngine(request.pixelSize)); + fe->fontDef = request; + } + + if (fe.isNull()) + { + QtFontDesc desc; + match(script, request, family_name, foundry_name, force_encoding_id, &desc); + + if (desc.family != 0 && desc.foundry != 0 && desc.style != 0 + ) { + FM_DEBUG(" BEST:\n" + " family: %s [%s]\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %d\n" + " pitch: %c\n" + " encoding: %d\n", + desc.family->name.toLatin1().constData(), + desc.foundry->name.isEmpty() ? "-- none --" : desc.foundry->name.toLatin1().constData(), + desc.style->key.weight, desc.style->key.style, + desc.style->key.stretch, desc.size ? desc.size->pixelSize : 0xffff, + 'p', 0 + ); + + fe.reset(loadEngine(script, fp, request, desc.family, desc.foundry, desc.style, desc.size + )); + } else { + FM_DEBUG(" NO MATCH FOUND\n"); + } + if (! fe.isNull()) + initFontDef(desc, request, &fe->fontDef); + } + +#ifndef QT_NO_FREETYPE + if (! fe.isNull()) { + if (scriptRequiresOpenType(script) && fe->type() == QFontEngine::Freetype) { + HB_Face hbFace = static_cast<QFontEngineFT *>(fe.data())->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + fe.reset(0); + } + } + } +#endif + + if (! fe.isNull()) { + if (fp) { + QFontDef def = request; + if (def.family.isEmpty()) { + def.family = fp->request.family; + def.family = def.family.left(def.family.indexOf(QLatin1Char(','))); + } + QFontCache::Key key(def, script); + QFontCache::instance()->insertEngine(key, fe.data()); + } + } + + if (fe.isNull()) { + if (!request.family.isEmpty()) + return 0; + + FM_DEBUG("returning box engine"); + + fe.reset(new QFontEngineBox(request.pixelSize)); + + if (fp) { + QFontCache::Key key(request, script); + QFontCache::instance()->insertEngine(key, fe.data()); + } + } + + if (fp && fp->dpi > 0) { + fe->fontDef.pointSize = qreal(double((fe->fontDef.pixelSize * 72) / fp->dpi)); + } else { + fe->fontDef.pointSize = request.pointSize; + } + + return fe.take(); +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontDef req = d->request; + + if (req.pixelSize == -1) + req.pixelSize = qRound(req.pointSize*d->dpi/72); + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72.0/d->dpi; + + if (!d->engineData) { + QFontCache::Key key(req, script); + + // look for the requested font in the engine data cache + d->engineData = QFontCache::instance()->findEngineData(key); + + if (!d->engineData) { + // create a new one + d->engineData = new QFontEngineData; + QT_TRY { + QFontCache::instance()->insertEngineData(key, d->engineData); + } QT_CATCH(...) { + delete d->engineData; + d->engineData = 0; + QT_RETHROW; + } + } else { + d->engineData->ref.ref(); + } + } + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) return; + + // double scale = 1.0; // ### TODO: fix the scale calculations + + // list of families to try + QStringList family_list; + + if (!req.family.isEmpty()) { + family_list = req.family.split(QLatin1Char(',')); + + // append the substitute list for each family in family_list + QStringList subs_list; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; it != end; ++it) + subs_list += QFont::substitutes(*it); + family_list += subs_list; + + // append the default fallback font for the specified script + // family_list << ... ; ########### + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + } + + // null family means find the first font matching the specified script + family_list << QString(); + + // load the font + QFontEngine *engine = 0; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; !engine && it != end; ++it) { + req.family = *it; + + engine = QFontDatabase::findFont(script, d, req); + if (engine && (engine->type()==QFontEngine::Box) && !req.family.isEmpty()) + engine = 0; + } + + engine->ref.ref(); + d->engineData->engines[script] = engine; +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_s60.cpp b/src/gui/text/qfontdatabase_s60.cpp new file mode 100644 index 0000000000..1db4a7d359 --- /dev/null +++ b/src/gui/text/qfontdatabase_s60.cpp @@ -0,0 +1,1091 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qapplication_p.h> +#include "qdir.h" +#include "qfont_p.h" +#include "qfontengine_s60_p.h" +#include "qabstractfileengine.h" +#include "qdesktopservices.h" +#include "qtemporaryfile.h" +#include "qtextcodec.h" +#include <private/qpixmap_s60_p.h> +#include <private/qt_s60_p.h> +#include "qendian.h" +#include <private/qcore_symbian_p.h> +#ifdef QT_NO_FREETYPE +#include <openfont.h> +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include <graphics/openfontrasterizer.h> // COpenFontRasterizer has moved to a new header file +#endif // SYMBIAN_ENABLE_SPLIT_HEADERS +#endif // QT_NO_FREETYPE + +QT_BEGIN_NAMESPACE + +QStringList qt_symbian_fontFamiliesOnFontServer() // Also used in qfont_s60.cpp +{ + QStringList result; + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + const int numTypeFaces = S60->screenDevice()->NumTypefaces(); + for (int i = 0; i < numTypeFaces; i++) { + TTypefaceSupport typefaceSupport; + S60->screenDevice()->TypefaceSupport(typefaceSupport, i); + const QString familyName((const QChar *)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length()); + result.append(familyName); + } + lock.relock(); + return result; +} + +QFileInfoList alternativeFilePaths(const QString &path, const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort, + bool uniqueFileNames = true) +{ + QFileInfoList result; + + // Prepare a 'soft to hard' drive list: W:, X: ... A:, Z: + QStringList driveStrings; + foreach (const QFileInfo &drive, QDir::drives()) + driveStrings.append(drive.absolutePath()); + driveStrings.sort(); + const QString zDriveString(QLatin1String("Z:/")); + driveStrings.removeAll(zDriveString); + driveStrings.prepend(zDriveString); + + QStringList uniqueFileNameList; + for (int i = driveStrings.count() - 1; i >= 0; --i) { + const QDir dirOnDrive(driveStrings.at(i) + path); + const QFileInfoList entriesOnDrive = dirOnDrive.entryInfoList(nameFilters, filters, sort); + if (uniqueFileNames) { + foreach(const QFileInfo &entry, entriesOnDrive) { + if (!uniqueFileNameList.contains(entry.fileName())) { + uniqueFileNameList.append(entry.fileName()); + result.append(entry); + } + } + } else { + result.append(entriesOnDrive); + } + } + return result; +} + +#ifdef QT_NO_FREETYPE +class QSymbianFontDatabaseExtrasImplementation : public QSymbianFontDatabaseExtras +{ +public: + QSymbianFontDatabaseExtrasImplementation(); + ~QSymbianFontDatabaseExtrasImplementation(); + + const QSymbianTypeFaceExtras *extras(const QString &typeface, bool bold, bool italic) const; + void removeAppFontData(QFontDatabasePrivate::ApplicationFont *fnt); + static inline bool appFontLimitReached(); + TUid addFontFileToFontStore(const QFileInfo &fontFileInfo); + static void clear(); + + static inline QString tempAppFontFolder(); + static const QString appFontMarkerPrefix; + static QString appFontMarker(); // 'qaf<shortUid[+shortPid]>' + + struct CFontFromFontStoreReleaser { + static inline void cleanup(CFont *font) + { + if (!font) + return; + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<const QSymbianFontDatabaseExtrasImplementation*>(privateDb()->symbianExtras); + dbExtras->m_store->ReleaseFont(font); + } + }; + + struct CFontFromScreenDeviceReleaser { + static inline void cleanup(CFont *font) + { + if (!font) + return; + S60->screenDevice()->ReleaseFont(font); + } + }; + +// m_heap, m_store, m_rasterizer and m_extras are used if Symbian +// does not provide the Font Table API + RHeap* m_heap; + CFontStore *m_store; + COpenFontRasterizer *m_rasterizer; + mutable QList<const QSymbianTypeFaceExtras *> m_extras; + + mutable QHash<QString, const QSymbianTypeFaceExtras *> m_extrasHash; + mutable QSet<QString> m_applicationFontFamilies; +}; + +const QString QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix = + QLatin1String("Q"); + +inline QString QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() +{ + return QDir::toNativeSeparators(QDir::tempPath()) + QLatin1Char('\\'); +} + +QString QSymbianFontDatabaseExtrasImplementation::appFontMarker() +{ + static QString result; + if (result.isEmpty()) { + quint16 id = 0; + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + // We are allowed to load app fonts even from previous, crashed runs + // of this application, since we can access the font tables. + const quint32 uid = RProcess().Type().MostDerived().iUid; + id = static_cast<quint16>(uid + (uid >> 16)); + } else { + // If no font table Api is available, we must not even load a font + // from a previous (crashed) run of this application. Reason: we + // won't get the font tables, they are not in the CFontStore. + // So, we use the pid, for more uniqueness. + id = static_cast<quint16>(RProcess().Id().Id()); + } + result = appFontMarkerPrefix + QString::fromLatin1("%1").arg(id & 0x7fff, 3, 32, QLatin1Char('0')); + Q_ASSERT(appFontMarkerPrefix.length() == 1 && result.length() == 4); + } + return result; +} + +static inline bool qt_symbian_fontNameHasAppFontMarker(const QString &fontName) +{ + const int idLength = 3; // Keep in sync with id length in appFontMarker(). + const QString &prefix = QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix; + if (fontName.length() < prefix.length() + idLength + || fontName.mid(fontName.length() - idLength - prefix.length(), prefix.length()) != prefix) + return false; + // Testing if the the id is base32 data + for (int i = fontName.length() - idLength; i < fontName.length(); ++i) { + const QChar &c = fontName.at(i); + if (!(c >= QLatin1Char('0') && c <= QLatin1Char('9') + || c >= QLatin1Char('a') && c <= QLatin1Char('v'))) + return false; + } + return true; +} + +// If fontName is an application font of this app, prepend the app font marker +QString qt_symbian_fontNameWithAppFontMarker(const QString &fontName) +{ + QFontDatabasePrivate *db = privateDb(); + Q_ASSERT(db); + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + return dbExtras->m_applicationFontFamilies.contains(fontName) ? + fontName + QSymbianFontDatabaseExtrasImplementation::appFontMarker() + : fontName; +} + +static inline QString qt_symbian_appFontNameWithoutMarker(const QString &markedFontName) +{ + return markedFontName.left(markedFontName.length() + - QSymbianFontDatabaseExtrasImplementation::appFontMarker().length()); +} + +QSymbianFontDatabaseExtrasImplementation::QSymbianFontDatabaseExtrasImplementation() +{ + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + QStringList filters; + filters.append(QLatin1String("*.ttf")); + filters.append(QLatin1String("*.ccc")); + filters.append(QLatin1String("*.ltt")); + const QFileInfoList fontFiles = alternativeFilePaths(QLatin1String("resource\\Fonts"), filters); + + const TInt heapMinLength = 0x1000; + const TInt heapMaxLength = qMax(0x20000 * fontFiles.count(), heapMinLength); + m_heap = User::ChunkHeap(NULL, heapMinLength, heapMaxLength); + QT_TRAP_THROWING( + m_store = CFontStore::NewL(m_heap); + m_rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E)); + CleanupStack::PushL(m_rasterizer); + m_store->InstallRasterizerL(m_rasterizer); + CleanupStack::Pop(m_rasterizer);); + + foreach (const QFileInfo &fontFileInfo, fontFiles) + addFontFileToFontStore(fontFileInfo); + } +} + +void QSymbianFontDatabaseExtrasImplementation::clear() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + if (!dbExtras) + return; // initializeDb() has never been called + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + qDeleteAll(dbExtras->m_extrasHash); + } else { + typedef QList<const QSymbianTypeFaceExtras *>::iterator iterator; + for (iterator p = dbExtras->m_extras.begin(); p != dbExtras->m_extras.end(); ++p) { + dbExtras->m_store->ReleaseFont((*p)->fontOwner()); + delete *p; + } + dbExtras->m_extras.clear(); + } + dbExtras->m_extrasHash.clear(); +} + +void qt_cleanup_symbianFontDatabase() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + + QSymbianFontDatabaseExtrasImplementation::clear(); + + if (!db->applicationFonts.isEmpty()) { + QFontDatabase::removeAllApplicationFonts(); + // We remove the left over temporary font files of Qt application. + // Active fonts are undeletable since the font server holds a handle + // on them, so we do not need to worry to delete other running + // applications' fonts. + const QDir dir(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder()); + const QStringList filter( + QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix + QLatin1String("*.ttf")); + foreach (const QFileInfo &ttfFile, dir.entryInfoList(filter)) + QFile(ttfFile.absoluteFilePath()).remove(); + db->applicationFonts.clear(); + } +} + +QSymbianFontDatabaseExtrasImplementation::~QSymbianFontDatabaseExtrasImplementation() +{ + qt_cleanup_symbianFontDatabase(); + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + delete m_store; + m_heap->Close(); + } +} + +#ifndef FNTSTORE_H_INLINES_SUPPORT_FMM +/* + Workaround: fntstore.h has an inlined function 'COpenFont* CBitmapFont::OpenFont()' + that returns a private data member. The header will change between SDKs. But Qt has + to build on any SDK version and run on other versions of Symbian OS. + This function performs the needed pointer arithmetic to get the right COpenFont* +*/ +COpenFont* OpenFontFromBitmapFont(const CBitmapFont* aBitmapFont) +{ + const TInt offsetIOpenFont = 92; // '_FOFF(CBitmapFont, iOpenFont)' ..if iOpenFont weren't private + const TUint valueIOpenFont = *(TUint*)PtrAdd(aBitmapFont, offsetIOpenFont); + return (valueIOpenFont & 1) ? + (COpenFont*)PtrAdd(aBitmapFont, valueIOpenFont & ~1) : // New behavior: iOpenFont is offset + (COpenFont*)valueIOpenFont; // Old behavior: iOpenFont is pointer +} +#endif // FNTSTORE_H_INLINES_SUPPORT_FMM + +const QSymbianTypeFaceExtras *QSymbianFontDatabaseExtrasImplementation::extras(const QString &aTypeface, + bool bold, bool italic) const +{ + const QString typeface = qt_symbian_fontNameWithAppFontMarker(aTypeface); + const QString searchKey = typeface + QString::number(int(bold)) + QString::number(int(italic)); + if (!m_extrasHash.contains(searchKey)) { + TFontSpec searchSpec(qt_QString2TPtrC(typeface), 1); + if (bold) + searchSpec.iFontStyle.SetStrokeWeight(EStrokeWeightBold); + if (italic) + searchSpec.iFontStyle.SetPosture(EPostureItalic); + + CFont* font = NULL; + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + const TInt err = S60->screenDevice()->GetNearestFontToDesignHeightInPixels(font, searchSpec); + Q_ASSERT(err == KErrNone && font); + QScopedPointer<CFont, CFontFromScreenDeviceReleaser> sFont(font); + QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font); + sFont.take(); + m_extrasHash.insert(searchKey, extras); + } else { + const TInt err = m_store->GetNearestFontToDesignHeightInPixels(font, searchSpec); + Q_ASSERT(err == KErrNone && font); + const CBitmapFont *bitmapFont = static_cast<CBitmapFont*>(font); + COpenFont *openFont = +#ifdef FNTSTORE_H_INLINES_SUPPORT_FMM + bitmapFont->OpenFont(); +#else // FNTSTORE_H_INLINES_SUPPORT_FMM + OpenFontFromBitmapFont(bitmapFont); +#endif // FNTSTORE_H_INLINES_SUPPORT_FMM + const TOpenFontFaceAttrib* const attrib = openFont->FaceAttrib(); + const QString foundKey = + QString((const QChar*)attrib->FullName().Ptr(), attrib->FullName().Length()); + if (!m_extrasHash.contains(foundKey)) { + QScopedPointer<CFont, CFontFromFontStoreReleaser> sFont(font); + QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font, openFont); + sFont.take(); + m_extras.append(extras); + m_extrasHash.insert(searchKey, extras); + m_extrasHash.insert(foundKey, extras); + } else { + m_store->ReleaseFont(font); + m_extrasHash.insert(searchKey, m_extrasHash.value(foundKey)); + } + } + } + return m_extrasHash.value(searchKey); +} + +void QSymbianFontDatabaseExtrasImplementation::removeAppFontData( + QFontDatabasePrivate::ApplicationFont *fnt) +{ + clear(); + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable() + && fnt->fontStoreFontFileUid.iUid != 0) + m_store->RemoveFile(fnt->fontStoreFontFileUid); + if (!fnt->families.isEmpty()) + m_applicationFontFamilies.remove(fnt->families.first()); + if (fnt->screenDeviceFontFileId != 0) + S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId); + QFile::remove(fnt->temporaryFileName); + *fnt = QFontDatabasePrivate::ApplicationFont(); +} + +bool QSymbianFontDatabaseExtrasImplementation::appFontLimitReached() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return false; + const int maxAppFonts = 5; + int registeredAppFonts = 0; + foreach (const QFontDatabasePrivate::ApplicationFont &appFont, db->applicationFonts) + if (!appFont.families.isEmpty() && ++registeredAppFonts == maxAppFonts) + return true; + return false; +} + +TUid QSymbianFontDatabaseExtrasImplementation::addFontFileToFontStore(const QFileInfo &fontFileInfo) +{ + Q_ASSERT(!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()); + const QString fontFile = QDir::toNativeSeparators(fontFileInfo.absoluteFilePath()); + const TPtrC fontFilePtr(qt_QString2TPtrC(fontFile)); + TUid fontUid = {0}; + TRAP_IGNORE(fontUid = m_store->AddFileL(fontFilePtr)); + return fontUid; +} + +#else // QT_NO_FREETYPE +class QFontEngineFTS60 : public QFontEngineFT +{ +public: + QFontEngineFTS60(const QFontDef &fd); +}; + +QFontEngineFTS60::QFontEngineFTS60(const QFontDef &fd) + : QFontEngineFT(fd) +{ + default_hint_style = HintFull; +} +#endif // QT_NO_FREETYPE + +/* + QFontEngineS60::pixelsToPoints, QFontEngineS60::pointsToPixels, QFontEngineMultiS60::QFontEngineMultiS60 + and QFontEngineMultiS60::QFontEngineMultiS60 should be in qfontengine_s60.cpp. But since also the + Freetype based font rendering need them, they are here. +*/ +qreal QFontEngineS60::pixelsToPoints(qreal pixels, Qt::Orientation orientation) +{ + CWsScreenDevice* device = S60->screenDevice(); + return (orientation == Qt::Horizontal? + device->HorizontalPixelsToTwips(pixels) + :device->VerticalPixelsToTwips(pixels)) / KTwipsPerPoint; +} + +qreal QFontEngineS60::pointsToPixels(qreal points, Qt::Orientation orientation) +{ + CWsScreenDevice* device = S60->screenDevice(); + const int twips = points * KTwipsPerPoint; + return orientation == Qt::Horizontal? + device->HorizontalTwipsToPixels(twips) + :device->VerticalTwipsToPixels(twips); +} + +QFontEngineMultiS60::QFontEngineMultiS60(QFontEngine *first, int script, const QStringList &fallbackFamilies) + : QFontEngineMulti(fallbackFamilies.size() + 1) + , m_script(script) + , m_fallbackFamilies(fallbackFamilies) +{ + engines[0] = first; + first->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiS60::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = m_fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(m_script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); +} + +#ifdef QT_NO_FREETYPE +static bool registerScreenDeviceFont(int screenDeviceFontIndex, + const QSymbianFontDatabaseExtrasImplementation *dbExtras) +{ + TTypefaceSupport typefaceSupport; + S60->screenDevice()->TypefaceSupport(typefaceSupport, screenDeviceFontIndex); + + QString familyName((const QChar*)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length()); + if (qt_symbian_fontNameHasAppFontMarker(familyName)) { + const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker(); + if (familyName.endsWith(marker)) { + familyName = qt_symbian_appFontNameWithoutMarker(familyName); + dbExtras->m_applicationFontFamilies.insert(familyName); + } else { + return false; // This was somebody else's application font. Skip it. + } + } + + CFont *font; // We have to get a font instance in order to know all the details + TFontSpec fontSpec(typefaceSupport.iTypeface.iName, 11); + if (S60->screenDevice()->GetNearestFontInPixels(font, fontSpec) != KErrNone) + return false; + QScopedPointer<CFont, QSymbianFontDatabaseExtrasImplementation::CFontFromScreenDeviceReleaser> sFont(font); + if (font->TypeUid() != KCFbsFontUid) + return false; + TOpenFontFaceAttrib faceAttrib; + const CFbsFont *cfbsFont = static_cast<const CFbsFont *>(font); + cfbsFont->GetFaceAttrib(faceAttrib); + + QtFontStyle::Key styleKey; + styleKey.style = faceAttrib.IsItalic()?QFont::StyleItalic:QFont::StyleNormal; + styleKey.weight = faceAttrib.IsBold()?QFont::Bold:QFont::Normal; + + QtFontFamily *family = privateDb()->family(familyName, true); + family->fixedPitch = faceAttrib.IsMonoWidth(); + QtFontFoundry *foundry = family->foundry(QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = typefaceSupport.iIsScalable; + style->pixelSize(0, true); + + const QSymbianTypeFaceExtras *typeFaceExtras = + dbExtras->extras(familyName, faceAttrib.IsBold(), faceAttrib.IsItalic()); + const QByteArray os2Table = typeFaceExtras->getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + const unsigned char* data = reinterpret_cast<const unsigned char*>(os2Table.constData()); + const unsigned char* ulUnicodeRange = data + 42; + quint32 unicodeRange[4] = { + qFromBigEndian<quint32>(ulUnicodeRange), + qFromBigEndian<quint32>(ulUnicodeRange + 4), + qFromBigEndian<quint32>(ulUnicodeRange + 8), + qFromBigEndian<quint32>(ulUnicodeRange + 12) + }; + const unsigned char* ulCodePageRange = data + 78; + quint32 codePageRange[2] = { + qFromBigEndian<quint32>(ulCodePageRange), + qFromBigEndian<quint32>(ulCodePageRange + 4) + }; + const QList<QFontDatabase::WritingSystem> writingSystems = + qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + foreach (const QFontDatabase::WritingSystem system, writingSystems) + family->writingSystems[system] = QtFontFamily::Supported; + return true; +} +#endif + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if(!db || db->count) + return; + +#ifdef QT_NO_FREETYPE + if (!db->symbianExtras) + db->symbianExtras = new QSymbianFontDatabaseExtrasImplementation; + + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + const int numTypeFaces = S60->screenDevice()->NumTypefaces(); + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + for (int i = 0; i < numTypeFaces; i++) + registerScreenDeviceFont(i, dbExtras); + + // We have to clear/release all CFonts, here, in case one of the fonts is + // an application font of another running Qt app. Otherwise the other Qt app + // cannot remove it's application font, anymore -> "Zombie Font". + QSymbianFontDatabaseExtrasImplementation::clear(); + + lock.relock(); + +#else // QT_NO_FREETYPE + QDir dir(QDesktopServices::storageLocation(QDesktopServices::FontsLocation)); + dir.setNameFilters(QStringList() << QLatin1String("*.ttf") + << QLatin1String("*.ttc") << QLatin1String("*.pfa") + << QLatin1String("*.pfb")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); + db->addTTFile(file); + } +#endif // QT_NO_FREETYPE +} + +static inline void load(const QString &family = QString(), int script = -1) +{ + Q_UNUSED(family) + Q_UNUSED(script) + initializeDb(); +} + +struct OffsetTable { + quint32 sfntVersion; + quint16 numTables, searchRange, entrySelector, rangeShift; +}; + +struct TableRecord { + quint32 tag, checkSum, offset, length; +}; + +struct NameTableHead { + quint16 format, count, stringOffset; +}; + +struct NameRecord { + quint16 platformID, encodingID, languageID, nameID, length, offset; +}; + +static quint32 ttfCalcChecksum(const char *data, quint32 bytesCount) +{ + quint32 result = 0; + const quint32 *ptr = reinterpret_cast<const quint32*>(data); + const quint32 *endPtr = + ptr + (bytesCount + sizeof(quint32) - 1) / sizeof(quint32); + while (ptr < endPtr) { + const quint32 unit32Value = *ptr++; + result += qFromBigEndian(unit32Value); + } + return result; +} + +static inline quint32 toDWordBoundary(quint32 value) +{ + return (value + 3) & ~3; +} + +static inline quint32 dWordPadding(quint32 value) +{ + return (4 - (value & 3)) & 3; +} + +static inline bool ttfMarkNameTable(QByteArray &table, const QString &marker) +{ + const quint32 tableLength = static_cast<quint32>(table.size()); + + if (tableLength > 50000 // hard limit + || tableLength < sizeof(NameTableHead)) // corrupt name table + return false; + + const NameTableHead *head = reinterpret_cast<const NameTableHead*>(table.constData()); + const quint16 count = qFromBigEndian(head->count); + const quint16 stringOffset = qFromBigEndian(head->stringOffset); + if (count > 200 // hard limit + || stringOffset >= tableLength // corrupt name table + || sizeof(NameTableHead) + count * sizeof(NameRecord) >= tableLength) // corrupt name table + return false; + + QTextEncoder encoder(QTextCodec::codecForName("UTF-16BE"), QTextCodec::IgnoreHeader); + const QByteArray markerUtf16BE = encoder.fromUnicode(marker); + const QByteArray markerAscii = marker.toAscii(); + + QByteArray markedTable; + markedTable.reserve(tableLength + marker.length() * 20); // Original size plus some extra + markedTable.append(table, stringOffset); + QByteArray markedStrings; + quint32 stringDataCount = stringOffset; + for (quint16 i = 0; i < count; ++i) { + const quint32 nameRecordOffset = sizeof(NameTableHead) + sizeof(NameRecord) * i; + NameRecord *nameRecord = + reinterpret_cast<NameRecord*>(markedTable.data() + nameRecordOffset); + const quint16 nameID = qFromBigEndian(nameRecord->nameID); + const quint16 platformID = qFromBigEndian(nameRecord->platformID); + const quint16 encodingID = qFromBigEndian(nameRecord->encodingID); + const quint16 offset = qFromBigEndian(nameRecord->offset); + const quint16 length = qFromBigEndian(nameRecord->length); + stringDataCount += length; + if (stringDataCount > 80000 // hard limit. String data may be > name table size. Multiple records can reference the same string. + || static_cast<quint32>(stringOffset + offset + length) > tableLength) // String outside bounds + return false; + const bool needsMarker = + nameID == 1 || nameID == 3 || nameID == 4 || nameID == 16 || nameID == 21; + const bool isUnicode = + platformID == 0 || platformID == 3 && encodingID == 1; + const QByteArray originalString = + QByteArray::fromRawData(table.constData() + stringOffset + offset, length); + QByteArray markedString; + if (needsMarker) { + const int maxBytesLength = (KMaxTypefaceNameLength - marker.length()) * (isUnicode ? 2 : 1); + markedString = originalString.left(maxBytesLength) + (isUnicode ? markerUtf16BE : markerAscii); + } else { + markedString = originalString; + } + nameRecord->offset = qToBigEndian(static_cast<quint16>(markedStrings.length())); + nameRecord->length = qToBigEndian(static_cast<quint16>(markedString.length())); + markedStrings.append(markedString); + } + markedTable.append(markedStrings); + table = markedTable; + return true; +} + +const quint32 ttfMaxFileSize = 3500000; + +static inline bool ttfMarkAppFont(QByteArray &ttf, const QString &marker) +{ + const quint32 ttfChecksumNumber = 0xb1b0afba; + const quint32 alignment = 4; + const quint32 ttfLength = static_cast<quint32>(ttf.size()); + if (ttfLength > ttfMaxFileSize // hard limit + || ttfLength % alignment != 0 // ttf sizes are always factors of 4 + || ttfLength <= sizeof(OffsetTable) // ttf too short + || ttfCalcChecksum(ttf.constData(), ttf.size()) != ttfChecksumNumber) // ttf checksum is invalid + return false; + + const OffsetTable *offsetTable = reinterpret_cast<const OffsetTable*>(ttf.constData()); + const quint16 numTables = qFromBigEndian(offsetTable->numTables); + const quint32 recordsLength = + toDWordBoundary(sizeof(OffsetTable) + numTables * sizeof(TableRecord)); + if (numTables > 30 // hard limit + || recordsLength + numTables * alignment > ttfLength) // Corrupt ttf. Tables would not fit, even if empty. + return false; + + QByteArray markedTtf; + markedTtf.reserve(ttfLength + marker.length() * 20); // Original size plus some extra + markedTtf.append(ttf.constData(), recordsLength); + + const quint32 ttfCheckSumAdjustmentOffset = 8; // Offset from the start of 'head' + int indexOfHeadTable = -1; + quint32 ttfDataSize = recordsLength; + typedef QPair<quint32, quint32> Range; + QList<Range> memoryRanges; + memoryRanges.reserve(numTables); + for (int i = 0; i < numTables; ++i) { + TableRecord *tableRecord = + reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord)); + const quint32 offset = qFromBigEndian(tableRecord->offset); + const quint32 length = qFromBigEndian(tableRecord->length); + const quint32 lengthAligned = toDWordBoundary(length); + ttfDataSize += lengthAligned; + if (offset < recordsLength // must not intersect ttf header/records + || offset % alignment != 0 // must be aligned + || offset > ttfLength - alignment // table out of bounds + || offset + lengthAligned > ttfLength // table out of bounds + || ttfDataSize > ttfLength) // tables would not fit into the ttf + return false; + + foreach (const Range &range, memoryRanges) + if (offset < range.first + range.second && offset + lengthAligned > range.first) + return false; // Overlaps with another table + memoryRanges.append(Range(offset, lengthAligned)); + + quint32 checkSum = qFromBigEndian(tableRecord->checkSum); + if (tableRecord->tag == qToBigEndian(static_cast<quint32>('head'))) { + if (length < ttfCheckSumAdjustmentOffset + sizeof(quint32)) + return false; // Invalid 'head' table + const quint32 *checkSumAdjustmentTag = + reinterpret_cast<const quint32*>(ttf.constData() + offset + ttfCheckSumAdjustmentOffset); + const quint32 checkSumAdjustment = qFromBigEndian(*checkSumAdjustmentTag); + checkSum += checkSumAdjustment; + indexOfHeadTable = i; // For the ttf checksum re-calculation, later + } + if (checkSum != ttfCalcChecksum(ttf.constData() + offset, length)) + return false; // Table checksum is invalid + + bool updateTableChecksum = false; + QByteArray table; + if (tableRecord->tag == qToBigEndian(static_cast<quint32>('name'))) { + table = QByteArray(ttf.constData() + offset, length); + if (!ttfMarkNameTable(table, marker)) + return false; // Name table was not markable. + updateTableChecksum = true; + } else { + table = QByteArray::fromRawData(ttf.constData() + offset, length); + } + + tableRecord->offset = qToBigEndian(markedTtf.size()); + tableRecord->length = qToBigEndian(table.size()); + markedTtf.append(table); + markedTtf.append(QByteArray(dWordPadding(table.size()), 0)); // 0-padding + if (updateTableChecksum) { + TableRecord *tableRecord = // Need to recalculate, since markedTtf changed + reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord)); + const quint32 offset = qFromBigEndian(tableRecord->offset); + const quint32 length = qFromBigEndian(tableRecord->length); + tableRecord->checkSum = qToBigEndian(ttfCalcChecksum(markedTtf.constData() + offset, length)); + } + } + if (indexOfHeadTable == -1 // 'head' table is mandatory + || ttfDataSize != ttfLength) // We do not allow ttf data "holes". Neither does Symbian. + return false; + TableRecord *headRecord = + reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + indexOfHeadTable * sizeof(TableRecord)); + quint32 *checkSumAdjustmentTag = + reinterpret_cast<quint32*>(markedTtf.data() + qFromBigEndian(headRecord->offset) + ttfCheckSumAdjustmentOffset); + *checkSumAdjustmentTag = 0; + const quint32 ttfChecksum = ttfCalcChecksum(markedTtf.constData(), markedTtf.count()); + *checkSumAdjustmentTag = qToBigEndian(ttfChecksumNumber - ttfChecksum); + ttf = markedTtf; + return true; +} + +static inline bool ttfCanSymbianLoadFont(const QByteArray &data, const QString &fileName) +{ + bool result = false; + QString ttfFileName; + QFile tempFileGuard; + QFileInfo info(fileName); + if (!data.isEmpty()) { + QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() + + QSymbianFontDatabaseExtrasImplementation::appFontMarker() + + QLatin1String("XXXXXX.ttf")); + if (!tempfile.open() || tempfile.write(data) == -1) + return false; + ttfFileName = QDir::toNativeSeparators(QFileInfo(tempfile).canonicalFilePath()); + tempfile.setAutoRemove(false); + tempfile.close(); + tempFileGuard.setFileName(ttfFileName); + if (!tempFileGuard.open(QIODevice::ReadOnly)) + return false; + } else if (info.isFile()) { + ttfFileName = QDir::toNativeSeparators(info.canonicalFilePath()); + } else { + return false; + } + + CFontStore *store = 0; + RHeap* heap = User::ChunkHeap(NULL, 0x1000, 0x20000); + if (heap) { + QT_TRAP_THROWING( + CleanupClosePushL(*heap); + store = CFontStore::NewL(heap); + CleanupStack::PushL(store); + COpenFontRasterizer *rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E)); + CleanupStack::PushL(rasterizer); + store->InstallRasterizerL(rasterizer); + CleanupStack::Pop(rasterizer); + TUid fontUid = {-1}; + TRAP_IGNORE(fontUid = store->AddFileL(qt_QString2TPtrC(ttfFileName))); + if (fontUid.iUid != -1) + result = true; + CleanupStack::PopAndDestroy(2, heap); // heap, store + ); + } + + if (tempFileGuard.isOpen()) + tempFileGuard.remove(); + + return result; +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + if (QSymbianFontDatabaseExtrasImplementation::appFontLimitReached() + || fnt->data.size() > ttfMaxFileSize // hard limit + || fnt->data.isEmpty() && (!fnt->fileName.endsWith(QLatin1String(".ttf"), Qt::CaseInsensitive) // Only buffer or .ttf + || QFileInfo(fnt->fileName).size() > ttfMaxFileSize)) // hard limit + return; + +// Using ttfCanSymbianLoadFont() causes crashes on app destruction (Symbian^3|PR1 and lower). +// Therefore, not using it for now, but eventually in a later version. +// if (!ttfCanSymbianLoadFont(fnt->data, fnt->fileName)) +// return; + + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + + if (!db->count) + initializeDb(); + + QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + if (!dbExtras) + return; + + const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker(); + + // The QTemporaryFile object being used in the following section must be + // destructed before letting Symbian load the TTF file. Symbian would not + // load it otherwise, because QTemporaryFile will still keep some handle + // on it. The scope is used to reduce the life time of the QTemporaryFile. + // In order to prevent other processes from modifying the file between the + // moment where the QTemporaryFile is destructed and the file is loaded by + // Symbian, we have a QFile "tempFileGuard" outside the scope which opens + // the file in ReadOnly mode while the QTemporaryFile is still alive. + QFile tempFileGuard; + { + QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() + + marker + QLatin1String("XXXXXX.ttf")); + if (!tempfile.open()) + return; + const QString tempFileName = QFileInfo(tempfile).canonicalFilePath(); + if (fnt->data.isEmpty()) { + QFile sourceFile(fnt->fileName); + if (!sourceFile.open(QIODevice::ReadOnly)) + return; + fnt->data = sourceFile.readAll(); + } + if (!ttfMarkAppFont(fnt->data, marker) || tempfile.write(fnt->data) == -1) + return; + tempfile.setAutoRemove(false); + tempfile.close(); // Tempfile still keeps a file handle, forbidding write access + fnt->data.clear(); // The TTF data was marked and saved. Not needed in memory, anymore. + tempFileGuard.setFileName(tempFileName); + if (!tempFileGuard.open(QIODevice::ReadOnly)) + return; + fnt->temporaryFileName = tempFileName; + } + + const QString fullFileName = QDir::toNativeSeparators(fnt->temporaryFileName); + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + const QStringList fontsOnServerBefore = qt_symbian_fontFamiliesOnFontServer(); + const TInt err = + S60->screenDevice()->AddFile(qt_QString2TPtrC(fullFileName), fnt->screenDeviceFontFileId); + tempFileGuard.close(); // Did its job + const QStringList fontsOnServerAfter = qt_symbian_fontFamiliesOnFontServer(); + if (err == KErrNone && fontsOnServerBefore.count() < fontsOnServerAfter.count()) { // Added to screen device? + int fontOnServerIndex = fontsOnServerAfter.count() - 1; + for (int i = 0; i < fontsOnServerBefore.count(); i++) { + if (fontsOnServerBefore.at(i) != fontsOnServerAfter.at(i)) { + fontOnServerIndex = i; + break; + } + } + + // Must remove all font engines with their CFonts, first. + QFontCache::instance()->clear(); + db->free(); + QSymbianFontDatabaseExtrasImplementation::clear(); + + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) + fnt->fontStoreFontFileUid = dbExtras->addFontFileToFontStore(QFileInfo(fullFileName)); + + const QString &appFontName = fontsOnServerAfter.at(fontOnServerIndex); + fnt->families.append(qt_symbian_appFontNameWithoutMarker(appFontName)); + if (!qt_symbian_fontNameHasAppFontMarker(appFontName) + || !registerScreenDeviceFont(fontOnServerIndex, dbExtras)) + dbExtras->removeAppFontData(fnt); + } else { + if (fnt->screenDeviceFontFileId > 0) + S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId); // May still have the file open! + QFile::remove(fnt->temporaryFileName); + *fnt = QFontDatabasePrivate::ApplicationFont(); + } + lock.relock(); +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (!db || handle < 0 || handle >= db->applicationFonts.count()) + return false; + QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + if (!dbExtras) + return false; + + QFontDatabasePrivate::ApplicationFont *fnt = &db->applicationFonts[handle]; + if (fnt->families.isEmpty()) + return true; // Nothing to remove. Return peacefully. + + // Must remove all font engines with their CFonts, first + QFontCache::instance()->clear(); + db->free(); + dbExtras->removeAppFontData(fnt); + + db->invalidate(); // This will just emit 'fontDatabaseChanged()' + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int applicationFontsCount = privateDb()->applicationFonts.count(); + for (int i = 0; i < applicationFontsCount; ++i) + if (!removeApplicationFont(i)) + return false; + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return false; +} + +static +QFontDef cleanedFontDef(const QFontDef &req) +{ + QFontDef result = req; + if (result.pixelSize <= 0) { + result.pixelSize = QFontEngineS60::pointsToPixels(qMax(qreal(1.0), result.pointSize)); + result.pointSize = 0; + } + return result; +} + +QFontEngine *QFontDatabase::findFont(int script, const QFontPrivate *d, const QFontDef &req) +{ + const QFontCache::Key key(cleanedFontDef(req), script); + + if (!privateDb()->count) + initializeDb(); + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + if (!fe) { + // Making sure that fe->fontDef.family will be an existing font. + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + QtFontDesc desc; + QList<int> blacklistedFamilies; + match(script, key.def, key.def.family, QString(), -1, &desc, blacklistedFamilies); + if (!desc.family) // falling back to application font + desc.family = db->family(QApplication::font().defaultFamily()); + Q_ASSERT(desc.family); + + // Making sure that desc.family supports the requested script + QtFontDesc mappedDesc; + bool supportsScript = false; + do { + match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies); + if (mappedDesc.family == desc.family) { + supportsScript = true; + break; + } + blacklistedFamilies.append(mappedDesc.familyIndex); + } while (mappedDesc.family); + if (!supportsScript) { + blacklistedFamilies.clear(); + match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies); + if (mappedDesc.family) + desc = mappedDesc; + } + + const QString fontFamily = desc.family->name; + QFontDef request = req; + request.family = fontFamily; +#ifdef QT_NO_FREETYPE + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras); + const QSymbianTypeFaceExtras *typeFaceExtras = + dbExtras->extras(fontFamily, request.weight > QFont::Normal, request.style != QFont::StyleNormal); + + // We need a valid pixelSize, e.g. for lineThickness() + if (request.pixelSize < 0) + request.pixelSize = request.pointSize * d->dpi / 72; + + fe = new QFontEngineS60(request, typeFaceExtras); +#else // QT_NO_FREETYPE + Q_UNUSED(d) + QFontEngine::FaceId faceId; + const QtFontFamily * const reqQtFontFamily = db->family(fontFamily); + faceId.filename = reqQtFontFamily->fontFilename; + faceId.index = reqQtFontFamily->fontFileIndex; + + QFontEngineFTS60 *fte = new QFontEngineFTS60(cleanedFontDef(request)); + if (fte->init(faceId, true, QFontEngineFT::Format_A8)) + fe = fte; + else + delete fte; +#endif // QT_NO_FREETYPE + + Q_ASSERT(fe); + if (script == QUnicodeTables::Common + && !(req.styleStrategy & QFont::NoFontMerging) + && !fe->symbol) { + + QStringList commonFonts; + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int i = 0; i < db->count; ++i) { + if (db->families[i]->writingSystems[ws] & QtFontFamily::Supported) + commonFonts.append(db->families[i]->name); + } + } + + // Hack: Prioritize .ccc fonts + const QString niceEastAsianFont(QLatin1String("Sans MT 936_S60")); + if (commonFonts.removeAll(niceEastAsianFont) > 0) + commonFonts.prepend(niceEastAsianFont); + + fe = new QFontEngineMultiS60(fe, script, commonFonts); + } + } + fe->ref.ref(); + QFontCache::instance()->insertEngine(key, fe); + return fe; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontEngine *fe = 0; + QFontDef req = d->request; + + if (!d->engineData) { + const QFontCache::Key key(cleanedFontDef(req), script); + getEngineData(d, key); + } + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + fe = d->engineData->engines[script]; + + if (!fe) { + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else { + fe = findFont(script, d, req); + } + d->engineData->engines[script] = fe; + } +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_win.cpp b/src/gui/text/qfontdatabase_win.cpp new file mode 100644 index 0000000000..05b7509bf6 --- /dev/null +++ b/src/gui/text/qfontdatabase_win.cpp @@ -0,0 +1,1348 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qt_windows.h" +#include <qmath.h> +#include <private/qapplication_p.h> +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qpaintdevice.h" +#include <private/qsystemlibrary_p.h> +#include "qabstractfileengine.h" +#include "qendian.h" + +#if !defined(QT_NO_DIRECTWRITE) +# include "qsettings.h" +# include "qfontenginedirectwrite_p.h" +#endif + +#ifdef Q_OS_WINCE +# include <QTemporaryFile> +#endif + +QT_BEGIN_NAMESPACE + +extern HDC shared_dc(); // common dc for all fonts + +#ifdef MAKE_TAG +#undef MAKE_TAG +#endif +// GetFontData expects the tags in little endian ;( +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch4)) << 24) | \ + (((quint32)(ch3)) << 16) | \ + (((quint32)(ch2)) << 8) | \ + ((quint32)(ch1)) \ + ) + +static HFONT stock_sysfont = 0; + +static bool localizedName(const QString &name) +{ + const QChar *c = name.unicode(); + for(int i = 0; i < name.length(); ++i) { + if(c[i].unicode() >= 0x100) + return true; + } + return false; +} + +static inline quint16 getUShort(const unsigned char *p) +{ + quint16 val; + val = *p++ << 8; + val |= *p; + + return val; +} + +static QString getEnglishName(const uchar *table, quint32 bytes) +{ + QString i18n_name; + enum { + NameRecordSize = 12, + FamilyId = 1, + MS_LangIdEnglish = 0x009 + }; + + // get the name table + quint16 count; + quint16 string_offset; + const unsigned char *names; + + int microsoft_id = -1; + int apple_id = -1; + int unicode_id = -1; + + if(getUShort(table) != 0) + goto error; + + count = getUShort(table+2); + string_offset = getUShort(table+4); + names = table + 6; + + if(string_offset >= bytes || 6 + count*NameRecordSize > string_offset) + goto error; + + for(int i = 0; i < count; ++i) { + // search for the correct name entry + + quint16 platform_id = getUShort(names + i*NameRecordSize); + quint16 encoding_id = getUShort(names + 2 + i*NameRecordSize); + quint16 language_id = getUShort(names + 4 + i*NameRecordSize); + quint16 name_id = getUShort(names + 6 + i*NameRecordSize); + + if(name_id != FamilyId) + continue; + + enum { + PlatformId_Unicode = 0, + PlatformId_Apple = 1, + PlatformId_Microsoft = 3 + }; + + quint16 length = getUShort(names + 8 + i*NameRecordSize); + quint16 offset = getUShort(names + 10 + i*NameRecordSize); + if(DWORD(string_offset + offset + length) >= bytes) + continue; + + if ((platform_id == PlatformId_Microsoft + && (encoding_id == 0 || encoding_id == 1)) + && (language_id & 0x3ff) == MS_LangIdEnglish + && microsoft_id == -1) + microsoft_id = i; + // not sure if encoding id 4 for Unicode is utf16 or ucs4... + else if(platform_id == PlatformId_Unicode && encoding_id < 4 && unicode_id == -1) + unicode_id = i; + else if(platform_id == PlatformId_Apple && encoding_id == 0 && language_id == 0) + apple_id = i; + } + { + bool unicode = false; + int id = -1; + if(microsoft_id != -1) { + id = microsoft_id; + unicode = true; + } else if(apple_id != -1) { + id = apple_id; + unicode = false; + } else if (unicode_id != -1) { + id = unicode_id; + unicode = true; + } + if(id != -1) { + quint16 length = getUShort(names + 8 + id*NameRecordSize); + quint16 offset = getUShort(names + 10 + id*NameRecordSize); + if(unicode) { + // utf16 + + length /= 2; + i18n_name.resize(length); + QChar *uc = (QChar *) i18n_name.unicode(); + const unsigned char *string = table + string_offset + offset; + for(int i = 0; i < length; ++i) + uc[i] = getUShort(string + 2*i); + } else { + // Apple Roman + + i18n_name.resize(length); + QChar *uc = (QChar *) i18n_name.unicode(); + const unsigned char *string = table + string_offset + offset; + for(int i = 0; i < length; ++i) + uc[i] = QLatin1Char(string[i]); + } + } + } + error: + //qDebug("got i18n name of '%s' for font '%s'", i18n_name.latin1(), familyName.toLocal8Bit().data()); + return i18n_name; +} + +static QString getEnglishName(const QString &familyName) +{ + QString i18n_name; + + HDC hdc = GetDC( 0 ); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + memcpy(lf.lfFaceName, familyName.utf16(), qMin(LF_FACESIZE, familyName.length()) * sizeof(wchar_t)); + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hfont = CreateFontIndirect(&lf); + + if(!hfont) { + ReleaseDC(0, hdc); + return QString(); + } + + HGDIOBJ oldobj = SelectObject( hdc, hfont ); + + const DWORD name_tag = MAKE_TAG( 'n', 'a', 'm', 'e' ); + + // get the name table + unsigned char *table = 0; + + DWORD bytes = GetFontData( hdc, name_tag, 0, 0, 0 ); + if ( bytes == GDI_ERROR ) { + // ### Unused variable + /* int err = GetLastError(); */ + goto error; + } + + table = new unsigned char[bytes]; + GetFontData(hdc, name_tag, 0, table, bytes); + if ( bytes == GDI_ERROR ) + goto error; + + i18n_name = getEnglishName(table, bytes); +error: + delete [] table; + SelectObject( hdc, oldobj ); + DeleteObject( hfont ); + ReleaseDC( 0, hdc ); + + //qDebug("got i18n name of '%s' for font '%s'", i18n_name.latin1(), familyName.toLocal8Bit().data()); + return i18n_name; +} + +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +static +void addFontToDatabase(QString familyName, const QString &scriptName, + TEXTMETRIC *textmetric, + const FONTSIGNATURE *signature, + int type) +{ + const int script = -1; + const QString foundryName; + Q_UNUSED(script); + + bool italic = false; + int weight; + bool fixed; + bool ttf; + bool scalable; + int size; + +// QString escript = QString::fromWCharArray(f->elfScript); +// qDebug("script=%s", escript.latin1()); + + NEWTEXTMETRIC *tm = (NEWTEXTMETRIC *)textmetric; + fixed = !(tm->tmPitchAndFamily & TMPF_FIXED_PITCH); + ttf = (tm->tmPitchAndFamily & TMPF_TRUETYPE); + scalable = tm->tmPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE); + size = scalable ? SMOOTH_SCALABLE : tm->tmHeight; + italic = tm->tmItalic; + weight = tm->tmWeight; + + // the "@family" fonts are just the same as "family". Ignore them. + if (familyName[0] != QLatin1Char('@') && !familyName.startsWith(QLatin1String("WST_"))) { + QtFontStyle::Key styleKey; + styleKey.style = italic ? QFont::StyleItalic : QFont::StyleNormal; + styleKey.weight = weightFromInteger(weight); + + QtFontFamily *family = privateDb()->family(familyName, true); + + if(ttf && localizedName(familyName) && family->english_name.isEmpty()) + family->english_name = getEnglishName(familyName); + + QtFontFoundry *foundry = family->foundry(foundryName, true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + + // add fonts windows can generate for us: + if (styleKey.weight <= QFont::DemiBold) { + QtFontStyle::Key key(styleKey); + key.weight = QFont::Bold; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + if (styleKey.style != QFont::StyleItalic) { + QtFontStyle::Key key(styleKey); + key.style = QFont::StyleItalic; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + if (styleKey.weight <= QFont::DemiBold && styleKey.style != QFont::StyleItalic) { + QtFontStyle::Key key(styleKey); + key.weight = QFont::Bold; + key.style = QFont::StyleItalic; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + + family->fixedPitch = fixed; + + if (!family->writingSystemCheck && type & TRUETYPE_FONTTYPE) { + quint32 unicodeRange[4] = { + signature->fsUsb[0], signature->fsUsb[1], + signature->fsUsb[2], signature->fsUsb[3] + }; +#ifdef Q_WS_WINCE + if (signature->fsUsb[0] == 0) { + // If the unicode ranges bit mask is zero then + // EnumFontFamiliesEx failed to determine it properly. + // In this case we just pretend that the font supports all languages. + unicodeRange[0] = 0xbfffffff; // second most significant bit must be zero + unicodeRange[1] = 0xffffffff; + unicodeRange[2] = 0xffffffff; + unicodeRange[3] = 0xffffffff; + } +#endif + quint32 codePageRange[2] = { + signature->fsCsb[0], signature->fsCsb[1] + }; + QList<QFontDatabase::WritingSystem> systems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + + for (int i = 0; i < systems.count(); ++i) { + QFontDatabase::WritingSystem writingSystem = systems.at(i); + + // ### Hack to work around problem with Thai text on Windows 7. Segoe UI contains + // the symbol for Baht, and Windows thus reports that it supports the Thai script. + // Since it's the default UI font on this platform, most widgets will be unable to + // display Thai text by default. As a temporary work around, we special case Segoe UI + // and remove the Thai script from its list of supported writing systems. + if (writingSystem != QFontDatabase::Thai || familyName != QLatin1String("Segoe UI")) + family->writingSystems[writingSystem] = QtFontFamily::Supported; + } + } else if (!family->writingSystemCheck) { + //qDebug("family='%s' script=%s", family->name.latin1(), script.latin1()); + if (scriptName == QLatin1String("Western") + || scriptName == QLatin1String("Baltic") + || scriptName == QLatin1String("Central European") + || scriptName == QLatin1String("Turkish") + || scriptName == QLatin1String("Vietnamese")) + family->writingSystems[QFontDatabase::Latin] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Thai")) + family->writingSystems[QFontDatabase::Thai] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Symbol") + || scriptName == QLatin1String("Other")) + family->writingSystems[QFontDatabase::Symbol] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("OEM/Dos")) + family->writingSystems[QFontDatabase::Latin] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("CHINESE_GB2312")) + family->writingSystems[QFontDatabase::SimplifiedChinese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("CHINESE_BIG5")) + family->writingSystems[QFontDatabase::TraditionalChinese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Cyrillic")) + family->writingSystems[QFontDatabase::Cyrillic] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Hangul")) + family->writingSystems[QFontDatabase::Korean] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Hebrew")) + family->writingSystems[QFontDatabase::Hebrew] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Greek")) + family->writingSystems[QFontDatabase::Greek] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Japanese")) + family->writingSystems[QFontDatabase::Japanese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Arabic")) + family->writingSystems[QFontDatabase::Arabic] = QtFontFamily::Supported; + } + } +} + +static +int CALLBACK +storeFont(ENUMLOGFONTEX* f, NEWTEXTMETRICEX *textmetric, int type, LPARAM /*p*/) +{ + QString familyName = QString::fromWCharArray(f->elfLogFont.lfFaceName); + QString script = QString::fromWCharArray(f->elfScript); + + FONTSIGNATURE signature = textmetric->ntmFontSig; + + // NEWTEXTMETRICEX is a NEWTEXTMETRIC, which according to the documentation is + // identical to a TEXTMETRIC except for the last four members, which we don't use + // anyway + addFontToDatabase(familyName, script, (TEXTMETRIC *)textmetric, &signature, type); + // keep on enumerating + return 1; +} + +static +void populate_database(const QString& fam) +{ + QFontDatabasePrivate *d = privateDb(); + if (!d) + return; + + QtFontFamily *family = 0; + if(!fam.isEmpty()) { + family = d->family(fam); + if(family && family->loaded) + return; + } else if (d->count) { + return; + } + + HDC dummy = GetDC(0); + + LOGFONT lf; + lf.lfCharSet = DEFAULT_CHARSET; + if (fam.isNull()) { + lf.lfFaceName[0] = 0; + } else { + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + } + lf.lfPitchAndFamily = 0; + + EnumFontFamiliesEx(dummy, &lf, + (FONTENUMPROC)storeFont, (LPARAM)privateDb(), 0); + + ReleaseDC(0, dummy); + + for (int i = 0; i < d->applicationFonts.count(); ++i) { + QFontDatabasePrivate::ApplicationFont fnt = d->applicationFonts.at(i); + if (!fnt.memoryFont) + continue; + for (int j = 0; j < fnt.families.count(); ++j) { + const QString familyName = fnt.families.at(j); + HDC hdc = GetDC(0); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + memcpy(lf.lfFaceName, familyName.utf16(), sizeof(wchar_t) * qMin(LF_FACESIZE, familyName.size())); + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hfont = CreateFontIndirect(&lf); + HGDIOBJ oldobj = SelectObject(hdc, hfont); + + TEXTMETRIC textMetrics; + GetTextMetrics(hdc, &textMetrics); + + addFontToDatabase(familyName, QString(), + &textMetrics, + &fnt.signatures.at(j), + TRUETYPE_FONTTYPE); + + SelectObject(hdc, oldobj); + DeleteObject(hfont); + ReleaseDC(0, hdc); + } + } + + if(!fam.isEmpty()) { + family = d->family(fam); + if(family) { + if(!family->writingSystemCheck) { + } + family->loaded = true; + } + } +} + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + populate_database(QString()); + +#ifdef QFONTDATABASE_DEBUG + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + qDebug(" %s: %p", qPrintable(family->name), family); + populate_database(family->name); + +#if 0 + qDebug(" scripts supported:"); + for (int i = 0; i < QUnicodeTables::ScriptCount; i++) + if(family->writingSystems[i] & QtFontFamily::Supported) + qDebug(" %d", i); + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + qDebug(" %s", foundry->name.latin1()); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + qDebug(" style: style=%d weight=%d smooth=%d", style->key.style, + style->key.weight, style->smoothScalable ); + if(!style->smoothScalable) { + for(int i = 0; i < style->count; ++i) { + qDebug(" %d", style->pixelSizes[i].pixelSize); + } + } + } + } +#endif + } +#endif // QFONTDATABASE_DEBUG + +} + +static inline void load(const QString &family = QString(), int = -1) +{ + populate_database(family); +} + + + + + +// -------------------------------------------------------------------------------------- +// font loader +// -------------------------------------------------------------------------------------- + + + +static void initFontInfo(QFontEngineWin *fe, const QFontDef &request, HDC fontHdc, int dpi) +{ + fe->fontDef = request; // most settings are equal + + HDC dc = ((request.styleStrategy & QFont::PreferDevice) && fontHdc) ? fontHdc : shared_dc(); + SelectObject(dc, fe->hfont); + wchar_t n[64]; + GetTextFace(dc, 64, n); + fe->fontDef.family = QString::fromWCharArray(n); + fe->fontDef.fixedPitch = !(fe->tm.tmPitchAndFamily & TMPF_FIXED_PITCH); + if (fe->fontDef.pointSize < 0) { + fe->fontDef.pointSize = fe->fontDef.pixelSize * 72. / dpi; + } else if (fe->fontDef.pixelSize == -1) { + fe->fontDef.pixelSize = qRound(fe->fontDef.pointSize * dpi / 72.); + } +} + +#if !defined(QT_NO_DIRECTWRITE) +static void initFontInfo(QFontEngineDirectWrite *fe, const QFontDef &request, + int dpi, IDWriteFont *font) +{ + fe->fontDef = request; + + IDWriteFontFamily *fontFamily = NULL; + HRESULT hr = font->GetFontFamily(&fontFamily); + + IDWriteLocalizedStrings *familyNames = NULL; + if (SUCCEEDED(hr)) + hr = fontFamily->GetFamilyNames(&familyNames); + + UINT32 index = 0; + BOOL exists = false; + + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + + if (SUCCEEDED(hr)) { + int defaultLocaleSuccess = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH); + + if (defaultLocaleSuccess) + hr = familyNames->FindLocaleName(localeName, &index, &exists); + + if (SUCCEEDED(hr) && !exists) + hr = familyNames->FindLocaleName(L"en-us", &index, &exists); + } + + if (!exists) + index = 0; + + UINT32 length = 0; + if (SUCCEEDED(hr)) + hr = familyNames->GetStringLength(index, &length); + + wchar_t *name = new (std::nothrow) wchar_t[length+1]; + if (name == NULL) + hr = E_OUTOFMEMORY; + + // Get the family name. + if (SUCCEEDED(hr)) + hr = familyNames->GetString(index, name, length + 1); + + if (SUCCEEDED(hr)) + fe->fontDef.family = QString::fromWCharArray(name); + + delete[] name; + if (familyNames != NULL) + familyNames->Release(); + + if (FAILED(hr)) + qErrnoWarning(hr, "initFontInfo: Failed to get family name"); + + if (fe->fontDef.pointSize < 0) + fe->fontDef.pointSize = fe->fontDef.pixelSize * 72. / dpi; + else if (fe->fontDef.pixelSize == -1) + fe->fontDef.pixelSize = qRound(fe->fontDef.pointSize * dpi / 72.); +} +#endif + +static const char *other_tryFonts[] = { + "Arial", + "MS UI Gothic", + "Gulim", + "SimSun", + "PMingLiU", + "Arial Unicode MS", + 0 +}; + +static const char *jp_tryFonts [] = { + "MS UI Gothic", + "Arial", + "Gulim", + "SimSun", + "PMingLiU", + "Arial Unicode MS", + 0 +}; + +static const char *ch_CN_tryFonts [] = { + "SimSun", + "Arial", + "PMingLiU", + "Gulim", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char *ch_TW_tryFonts [] = { + "PMingLiU", + "Arial", + "SimSun", + "Gulim", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char *kr_tryFonts[] = { + "Gulim", + "Arial", + "PMingLiU", + "SimSun", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char **tryFonts = 0; + +#if !defined(QT_NO_DIRECTWRITE) +static QString fontNameSubstitute(const QString &familyName) +{ + QLatin1String key("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\" + "FontSubstitutes"); + return QSettings(key, QSettings::NativeFormat).value(familyName, familyName).toString(); +} +#endif + +static inline HFONT systemFont() +{ + if (stock_sysfont == 0) + stock_sysfont = (HFONT)GetStockObject(SYSTEM_FONT); + return stock_sysfont; +} + +#if !defined(DEFAULT_GUI_FONT) +#define DEFAULT_GUI_FONT 17 +#endif + +static QFontEngine *loadEngine(int script, const QFontDef &request, + HDC fontHdc, int dpi, bool rawMode, + const QtFontDesc *desc, + const QStringList &family_list) +{ + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + + bool useDevice = (request.styleStrategy & QFont::PreferDevice) && fontHdc; + + HDC hdc = shared_dc(); + QString font_name = desc != 0 ? desc->family->name : request.family; + + if (useDevice) { + hdc = fontHdc; + font_name = request.family; + } + + bool stockFont = false; + bool preferClearTypeAA = false; + + HFONT hfont = 0; + + +#if !defined(QT_NO_DIRECTWRITE) + bool useDirectWrite = (request.hintingPreference == QFont::PreferNoHinting) + || (request.hintingPreference == QFont::PreferVerticalHinting); + IDWriteFont *directWriteFont = 0; +#else + bool useDirectWrite = false; +#endif + + if (rawMode) { // will choose a stock font + int f, deffnt = SYSTEM_FONT; + QString fam = desc != 0 ? desc->family->name.toLower() : request.family.toLower(); + if (fam == QLatin1String("default")) + f = deffnt; + else if (fam == QLatin1String("system")) + f = SYSTEM_FONT; +#ifndef Q_WS_WINCE + else if (fam == QLatin1String("system_fixed")) + f = SYSTEM_FIXED_FONT; + else if (fam == QLatin1String("ansi_fixed")) + f = ANSI_FIXED_FONT; + else if (fam == QLatin1String("ansi_var")) + f = ANSI_VAR_FONT; + else if (fam == QLatin1String("device_default")) + f = DEVICE_DEFAULT_FONT; + else if (fam == QLatin1String("oem_fixed")) + f = OEM_FIXED_FONT; +#endif + else if (fam[0] == QLatin1Char('#')) + f = fam.right(fam.length()-1).toInt(); + else + f = deffnt; + hfont = (HFONT)GetStockObject(f); + if (!hfont) { + qErrnoWarning("QFontEngine::loadEngine: GetStockObject failed"); + hfont = systemFont(); + } + stockFont = true; + } else { + + int hint = FF_DONTCARE; + switch (request.styleHint) { + case QFont::Helvetica: + hint = FF_SWISS; + break; + case QFont::Times: + hint = FF_ROMAN; + break; + case QFont::Courier: + hint = FF_MODERN; + break; + case QFont::OldEnglish: + hint = FF_DECORATIVE; + break; + case QFont::System: + hint = FF_MODERN; + break; + default: + break; + } + + lf.lfHeight = -qRound(request.pixelSize); + lf.lfWidth = 0; + lf.lfEscapement = 0; + lf.lfOrientation = 0; + if (desc == 0 || desc->style->key.weight == 50) + lf.lfWeight = FW_DONTCARE; + else + lf.lfWeight = (desc->style->key.weight*900)/99; + lf.lfItalic = (desc != 0 && desc->style->key.style != QFont::StyleNormal); + lf.lfCharSet = DEFAULT_CHARSET; + + int strat = OUT_DEFAULT_PRECIS; + if (request.styleStrategy & QFont::PreferBitmap) { + strat = OUT_RASTER_PRECIS; +#ifndef Q_WS_WINCE + } else if (request.styleStrategy & QFont::PreferDevice) { + strat = OUT_DEVICE_PRECIS; + } else if (request.styleStrategy & QFont::PreferOutline) { + strat = OUT_OUTLINE_PRECIS; + } else if (request.styleStrategy & QFont::ForceOutline) { + strat = OUT_TT_ONLY_PRECIS; +#endif + } + + lf.lfOutPrecision = strat; + + int qual = DEFAULT_QUALITY; + + if (request.styleStrategy & QFont::PreferMatch) + qual = DRAFT_QUALITY; +#ifndef Q_WS_WINCE + else if (request.styleStrategy & QFont::PreferQuality) + qual = PROOF_QUALITY; +#endif + + if (request.styleStrategy & QFont::PreferAntialias) { + if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP) { + qual = CLEARTYPE_QUALITY; + preferClearTypeAA = true; + } else { + qual = ANTIALIASED_QUALITY; + } + } else if (request.styleStrategy & QFont::NoAntialias) { + qual = NONANTIALIASED_QUALITY; + } + + lf.lfQuality = qual; + + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfPitchAndFamily = DEFAULT_PITCH | hint; + + QString fam = font_name; + + if(fam.isEmpty()) + fam = QLatin1String("MS Sans Serif"); + + if ((fam == QLatin1String("MS Sans Serif")) + && (request.style == QFont::StyleItalic || (-lf.lfHeight > 18 && -lf.lfHeight != 24))) { + fam = QLatin1String("Arial"); // MS Sans Serif has bearing problems in italic, and does not scale + } + if (fam == QLatin1String("Courier") && !(request.styleStrategy & QFont::PreferBitmap)) + fam = QLatin1String("Courier New"); + + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + + hfont = CreateFontIndirect(&lf); + if (!hfont) + qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect failed"); + + stockFont = (hfont == 0); + bool ttf = false; + int avWidth = 0; + BOOL res; + HGDIOBJ oldObj = SelectObject(hdc, hfont); + + TEXTMETRIC tm; + res = GetTextMetrics(hdc, &tm); + avWidth = tm.tmAveCharWidth; + ttf = tm.tmPitchAndFamily & TMPF_TRUETYPE; + SelectObject(hdc, oldObj); + + if (!ttf || !useDirectWrite) { + useDirectWrite = false; + + if (hfont && (!ttf || request.stretch != 100)) { + DeleteObject(hfont); + if (!res) + qErrnoWarning("QFontEngine::loadEngine: GetTextMetrics failed"); + lf.lfWidth = avWidth * request.stretch/100; + hfont = CreateFontIndirect(&lf); + if (!hfont) + qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect with stretch failed"); + } + +#ifndef Q_WS_WINCE + if (hfont == 0) { + hfont = (HFONT)GetStockObject(ANSI_VAR_FONT); + stockFont = true; + } +#else + if (hfont == 0) { + hfont = (HFONT)GetStockObject(SYSTEM_FONT); + stockFont = true; + } +#endif + + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + // Default to false for DirectWrite (and re-enable once/if everything + // turns out okay) + useDirectWrite = false; + + QFontDatabasePrivate *db = privateDb(); + if (db->directWriteFactory == 0) { + HRESULT hr = DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown **>(&db->directWriteFactory) + ); + if (FAILED(hr)) { + qErrnoWarning("QFontEngine::loadEngine: DWriteCreateFactory failed"); + } else { + hr = db->directWriteFactory->GetGdiInterop(&db->directWriteGdiInterop); + if (FAILED(hr)) + qErrnoWarning("QFontEngine::loadEngine: GetGdiInterop failed"); + } + } + + if (db->directWriteGdiInterop != 0) { + QString nameSubstitute = fontNameSubstitute(QString::fromWCharArray(lf.lfFaceName)); + memcpy(lf.lfFaceName, nameSubstitute.utf16(), + sizeof(wchar_t) * qMin(nameSubstitute.length() + 1, LF_FACESIZE)); + + HRESULT hr = db->directWriteGdiInterop->CreateFontFromLOGFONT( + &lf, + &directWriteFont); + if (FAILED(hr)) { +#ifndef QT_NO_DEBUG + qErrnoWarning("QFontEngine::loadEngine: CreateFontFromLOGFONT failed " + "for %ls (0x%lx)", + lf.lfFaceName, hr); +#endif + } else { + DeleteObject(hfont); + useDirectWrite = true; + } + } + } +#endif + + } + + QFontEngine *fe = 0; + if (!useDirectWrite) { + QFontEngineWin *few = new QFontEngineWin(font_name, hfont, stockFont, lf); + if (preferClearTypeAA) + few->glyphFormat = QFontEngineGlyphCache::Raster_RGBMask; + + // Also check for OpenType tables when using complex scripts + // ### TODO: This only works for scripts that require OpenType. More generally + // for scripts that do not require OpenType we should just look at the list of + // supported writing systems in the font's OS/2 table. + if (scriptRequiresOpenType(script)) { + HB_Face hbFace = few->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + delete few; + return 0; + } + } + + initFontInfo(few, request, fontHdc, dpi); + fe = few; + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + QFontDatabasePrivate *db = privateDb(); + + IDWriteFontFace *directWriteFontFace = NULL; + HRESULT hr = directWriteFont->CreateFontFace(&directWriteFontFace); + if (SUCCEEDED(hr)) { + QFontEngineDirectWrite *fedw = new QFontEngineDirectWrite(db->directWriteFactory, + directWriteFontFace, + request.pixelSize); + + initFontInfo(fedw, request, dpi, directWriteFont); + + fe = fedw; + } else { + qErrnoWarning(hr, "QFontEngine::loadEngine: CreateFontFace failed"); + } + } + + if (directWriteFont != 0) + directWriteFont->Release(); +#endif + + if(script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) + && desc != 0 + && !(desc->family->writingSystems[QFontDatabase::Symbol] & QtFontFamily::Supported)) { + if(!tryFonts) { + LANGID lid = GetUserDefaultLangID(); + switch( lid&0xff ) { + case LANG_CHINESE: // Chinese (Taiwan) + if ( lid == 0x0804 ) // Taiwan + tryFonts = ch_TW_tryFonts; + else + tryFonts = ch_CN_tryFonts; + break; + case LANG_JAPANESE: + tryFonts = jp_tryFonts; + break; + case LANG_KOREAN: + tryFonts = kr_tryFonts; + break; + default: + tryFonts = other_tryFonts; + break; + } + } + QStringList fm = QFontDatabase().families(); + QStringList list = family_list; + const char **tf = tryFonts; + while(tf && *tf) { + if(fm.contains(QLatin1String(*tf))) + list << QLatin1String(*tf); + ++tf; + } + QFontEngine *mfe = new QFontEngineMultiWin(fe, list); + mfe->fontDef = fe->fontDef; + fe = mfe; + } + return fe; +} + +QFontEngine *qt_load_font_engine_win(const QFontDef &request) +{ + // From qfont.cpp + extern int qt_defaultDpi(); + + QFontCache::Key key(request, QUnicodeTables::Common); + QFontEngine *fe = QFontCache::instance()->findEngine(key); + if (fe != 0) + return fe; + else + return loadEngine(QUnicodeTables::Common, request, 0, qt_defaultDpi(), false, 0, + QStringList()); +} + +const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "Arial"; + break; + case QFont::Serif: + stylehint = "Times New Roman"; + break; + case QFont::TypeWriter: + stylehint = "Courier New"; + break; + default: + if (request.fixedPitch) + stylehint = "Courier New"; + break; + } + return stylehint; +} + +static QFontEngine *loadWin(const QFontPrivate *d, int script, const QFontDef &req) +{ + // list of families to try + QStringList family_list = familyList(req); + + const char *stylehint = styleHint(d->request); + if (stylehint) + family_list << QLatin1String(stylehint); + + // append the default fallback font for the specified script + // family_list << ... ; ########### + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + + // null family means find the first font matching the specified script + family_list << QString(); + + QtFontDesc desc; + QFontEngine *fe = 0; + QList<int> blacklistedFamilies; + + while (!fe) { + for (int i = 0; i < family_list.size(); ++i) { + QString family, foundry; + parseFontName(family_list.at(i), foundry, family); + FM_DEBUG("loadWin: >>>>>>>>>>>>>>trying to match '%s'", family.toLatin1().data()); + QT_PREPEND_NAMESPACE(match)(script, req, family, foundry, -1, &desc, blacklistedFamilies); + if (desc.family) + break; + } + if (!desc.family) + break; + fe = loadEngine(script, req, d->hdc, d->dpi, d->rawMode, &desc, family_list); + if (!fe) + blacklistedFamilies.append(desc.familyIndex); + } + return fe; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + // sanity checks + if (!qApp) + qWarning("QFontDatabase::load: Must construct QApplication first"); + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + + // normalize the request to get better caching + QFontDef req = d->request; + if (req.pixelSize <= 0) + req.pixelSize = floor((100.0 * req.pointSize * d->dpi) / 72. + 0.5) / 100; + if (req.pixelSize < 1) + req.pixelSize = 1; + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, d->rawMode ? QUnicodeTables::Common : script, d->screen); + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + // set it to the actual pointsize, so QFontInfo will do the right thing + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72./d->dpi; + + if (!fe) { + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else { + QMutexLocker locker(fontDatabaseMutex()); + if (!privateDb()->count) + initializeDb(); + fe = loadWin(d, script, req); + } + if (!fe) { + fe = new QFontEngineBox(req.pixelSize); + fe->fontDef = QFontDef(); + } + } + d->engineData->engines[script] = fe; + fe->ref.ref(); + QFontCache::instance()->insertEngine(key, fe); +} + +#if !defined(FR_PRIVATE) +#define FR_PRIVATE 0x10 +#endif + +typedef int (WINAPI *PtrAddFontResourceExW)(LPCWSTR, DWORD, PVOID); +typedef HANDLE (WINAPI *PtrAddFontMemResourceEx)(PVOID, DWORD, PVOID, DWORD *); +typedef BOOL (WINAPI *PtrRemoveFontResourceExW)(LPCWSTR, DWORD, PVOID); +typedef BOOL (WINAPI *PtrRemoveFontMemResourceEx)(HANDLE); + +static QList<quint32> getTrueTypeFontOffsets(const uchar *fontData) +{ + QList<quint32> offsets; + const quint32 headerTag = *reinterpret_cast<const quint32 *>(fontData); + if (headerTag != MAKE_TAG('t', 't', 'c', 'f')) { + if (headerTag != MAKE_TAG(0, 1, 0, 0) + && headerTag != MAKE_TAG('O', 'T', 'T', 'O') + && headerTag != MAKE_TAG('t', 'r', 'u', 'e') + && headerTag != MAKE_TAG('t', 'y', 'p', '1')) + return offsets; + offsets << 0; + return offsets; + } + const quint32 numFonts = qFromBigEndian<quint32>(fontData + 8); + for (uint i = 0; i < numFonts; ++i) { + offsets << qFromBigEndian<quint32>(fontData + 12 + i * 4); + } + return offsets; +} + +static void getFontTable(const uchar *fileBegin, const uchar *data, quint32 tag, const uchar **table, quint32 *length) +{ + const quint16 numTables = qFromBigEndian<quint16>(data + 4); + for (uint i = 0; i < numTables; ++i) { + const quint32 offset = 12 + 16 * i; + if (*reinterpret_cast<const quint32 *>(data + offset) == tag) { + *table = fileBegin + qFromBigEndian<quint32>(data + offset + 8); + *length = qFromBigEndian<quint32>(data + offset + 12); + return; + } + } + *table = 0; + *length = 0; + return; +} + +static void getFamiliesAndSignatures(const QByteArray &fontData, QFontDatabasePrivate::ApplicationFont *appFont) +{ + const uchar *data = reinterpret_cast<const uchar *>(fontData.constData()); + + QList<quint32> offsets = getTrueTypeFontOffsets(data); + if (offsets.isEmpty()) + return; + + for (int i = 0; i < offsets.count(); ++i) { + const uchar *font = data + offsets.at(i); + const uchar *table; + quint32 length; + getFontTable(data, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length); + if (!table) + continue; + QString name = getEnglishName(table, length); + if (name.isEmpty()) + continue; + + appFont->families << name; + FONTSIGNATURE signature; + getFontTable(data, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length); + if (table && length >= 86) { + // See also qfontdatabase_mac.cpp, offsets taken from OS/2 table in the TrueType spec + signature.fsUsb[0] = qFromBigEndian<quint32>(table + 42); + signature.fsUsb[1] = qFromBigEndian<quint32>(table + 46); + signature.fsUsb[2] = qFromBigEndian<quint32>(table + 50); + signature.fsUsb[3] = qFromBigEndian<quint32>(table + 54); + + signature.fsCsb[0] = qFromBigEndian<quint32>(table + 78); + signature.fsCsb[1] = qFromBigEndian<quint32>(table + 82); + } else { + memset(&signature, 0, sizeof(signature)); + } + appFont->signatures << signature; + } +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + if(!fnt->data.isEmpty()) { +#ifndef Q_OS_WINCE + PtrAddFontMemResourceEx ptrAddFontMemResourceEx = (PtrAddFontMemResourceEx)QSystemLibrary::resolve(QLatin1String("gdi32"), + "AddFontMemResourceEx"); + if (!ptrAddFontMemResourceEx) + return; +#endif + getFamiliesAndSignatures(fnt->data, fnt); + if (fnt->families.isEmpty()) + return; + +#ifdef Q_OS_WINCE + HANDLE handle = 0; + + { +#ifdef QT_NO_TEMPORARYFILE + wchar_t lpBuffer[MAX_PATH]; + GetTempPath(MAX_PATH, lpBuffer); + QString s = QString::fromWCharArray(lpBuffer); + QFile tempfile(s + QLatin1String("/font") + QString::number(GetTickCount()) + QLatin1String(".ttf")); + if (!tempfile.open(QIODevice::ReadWrite)) +#else + QTemporaryFile tempfile(QLatin1String("XXXXXXXX.ttf")); + if (!tempfile.open()) +#endif // QT_NO_TEMPORARYFILE + return; + if (tempfile.write(fnt->data) == -1) + return; + +#ifndef QT_NO_TEMPORARYFILE + tempfile.setAutoRemove(false); +#endif + fnt->fileName = QFileInfo(tempfile.fileName()).absoluteFilePath(); + } + + if (AddFontResource((LPCWSTR)fnt->fileName.utf16()) == 0) { + QFile(fnt->fileName).remove(); + return; + } +#else + DWORD dummy = 0; + HANDLE handle = ptrAddFontMemResourceEx((void *)fnt->data.constData(), fnt->data.size(), 0, + &dummy); + if (handle == 0) + return; +#endif // Q_OS_WINCE + + fnt->handle = handle; + fnt->data = QByteArray(); + fnt->memoryFont = true; + } else { + QFile f(fnt->fileName); + if (!f.open(QIODevice::ReadOnly)) + return; + QByteArray data = f.readAll(); + f.close(); + getFamiliesAndSignatures(data, fnt); + +#ifdef Q_OS_WINCE + QFileInfo fileinfo(fnt->fileName); + fnt->fileName = fileinfo.absoluteFilePath(); + if (AddFontResource((LPCWSTR)fnt->fileName.utf16()) == 0) + return; +#else + PtrAddFontResourceExW ptrAddFontResourceExW = (PtrAddFontResourceExW)QSystemLibrary::resolve(QLatin1String("gdi32"), + "AddFontResourceExW"); + if (!ptrAddFontResourceExW + || ptrAddFontResourceExW((wchar_t*)fnt->fileName.utf16(), FR_PRIVATE, 0) == 0) + return; +#endif // Q_OS_WINCE + + fnt->memoryFont = false; + } +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + const QFontDatabasePrivate::ApplicationFont font = db->applicationFonts.at(handle); + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + if (font.memoryFont) { +#ifdef Q_OS_WINCE + bool removeSucceeded = RemoveFontResource((LPCWSTR)font.fileName.utf16()); + QFile tempfile(font.fileName); + tempfile.remove(); + if (!removeSucceeded) + return false; +#else + PtrRemoveFontMemResourceEx ptrRemoveFontMemResourceEx = (PtrRemoveFontMemResourceEx)QSystemLibrary::resolve(QLatin1String("gdi32"), + "RemoveFontMemResourceEx"); + if (!ptrRemoveFontMemResourceEx + || !ptrRemoveFontMemResourceEx(font.handle)) + return false; +#endif // Q_OS_WINCE + } else { +#ifdef Q_OS_WINCE + if (!RemoveFontResource((LPCWSTR)font.fileName.utf16())) + return false; +#else + PtrRemoveFontResourceExW ptrRemoveFontResourceExW = (PtrRemoveFontResourceExW)QSystemLibrary::resolve(QLatin1String("gdi32"), + "RemoveFontResourceExW"); + if (!ptrRemoveFontResourceExW + || !ptrRemoveFontResourceExW((LPCWSTR)font.fileName.utf16(), FR_PRIVATE, 0)) + return false; +#endif // Q_OS_WINCE + } + + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!removeApplicationFont(i)) + return false; + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_x11.cpp b/src/gui/text/qfontdatabase_x11.cpp new file mode 100644 index 0000000000..0c0c4c8343 --- /dev/null +++ b/src/gui/text/qfontdatabase_x11.cpp @@ -0,0 +1,2146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qplatformdefs.h> + +#include <qdebug.h> +#include <qpaintdevice.h> +#include <qelapsedtimer.h> + +#include <private/qt_x11_p.h> +#include "qx11info_x11.h" +#include <qdebug.h> +#include <qfile.h> +#include <qtemporaryfile.h> +#include <qabstractfileengine.h> +#include <qmath.h> + +#include <ctype.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include <private/qfontengine_x11_p.h> + +#ifndef QT_NO_FONTCONFIG +#include <ft2build.h> +#include FT_FREETYPE_H + +#if FC_VERSION >= 20402 +#include <fontconfig/fcfreetype.h> +#endif +#endif + +QT_BEGIN_NAMESPACE + +// from qfont_x11.cpp +extern double qt_pointSize(double pixelSize, int dpi); +extern double qt_pixelSize(double pointSize, int dpi); + +// from qapplication.cpp +extern bool qt_is_gui_used; + +static inline void capitalize (char *s) +{ + bool space = true; + while(*s) { + if (space) + *s = toupper(*s); + space = (*s == ' '); + ++s; + } +} + + +/* + To regenerate the writingSystems_for_xlfd_encoding table, run + 'util/unicode/x11/makeencodings' and paste the generated + 'encodings.c' here. +*/ +// ----- begin of generated code ----- + +#define make_tag( c1, c2, c3, c4 ) \ + ((((unsigned int)c1)<<24) | (((unsigned int)c2)<<16) | \ + (((unsigned int)c3)<<8) | ((unsigned int)c4)) + +struct XlfdEncoding { + const char *name; + int id; + int mib; + unsigned int hash1; + unsigned int hash2; +}; + +static const XlfdEncoding xlfd_encoding[] = { + { "iso8859-1", 0, 4, make_tag('i','s','o','8'), make_tag('5','9','-','1') }, + { "iso8859-2", 1, 5, make_tag('i','s','o','8'), make_tag('5','9','-','2') }, + { "iso8859-3", 2, 6, make_tag('i','s','o','8'), make_tag('5','9','-','3') }, + { "iso8859-4", 3, 7, make_tag('i','s','o','8'), make_tag('5','9','-','4') }, + { "iso8859-9", 4, 12, make_tag('i','s','o','8'), make_tag('5','9','-','9') }, + { "iso8859-10", 5, 13, make_tag('i','s','o','8'), make_tag('9','-','1','0') }, + { "iso8859-13", 6, 109, make_tag('i','s','o','8'), make_tag('9','-','1','3') }, + { "iso8859-14", 7, 110, make_tag('i','s','o','8'), make_tag('9','-','1','4') }, + { "iso8859-15", 8, 111, make_tag('i','s','o','8'), make_tag('9','-','1','5') }, + { "hp-roman8", 9, 2004, make_tag('h','p','-','r'), make_tag('m','a','n','8') }, + { "iso8859-5", 10, 8, make_tag('i','s','o','8'), make_tag('5','9','-','5') }, + { "*-cp1251", 11, 2251, 0, make_tag('1','2','5','1') }, + { "koi8-ru", 12, 2084, make_tag('k','o','i','8'), make_tag('8','-','r','u') }, + { "koi8-u", 13, 2088, make_tag('k','o','i','8'), make_tag('i','8','-','u') }, + { "koi8-r", 14, 2084, make_tag('k','o','i','8'), make_tag('i','8','-','r') }, + { "iso8859-7", 15, 10, make_tag('i','s','o','8'), make_tag('5','9','-','7') }, + { "iso8859-8", 16, 85, make_tag('i','s','o','8'), make_tag('5','9','-','8') }, + { "gb18030-0", 17, -114, make_tag('g','b','1','8'), make_tag('3','0','-','0') }, + { "gb18030.2000-0", 18, -113, make_tag('g','b','1','8'), make_tag('0','0','-','0') }, + { "gbk-0", 19, -113, make_tag('g','b','k','-'), make_tag('b','k','-','0') }, + { "gb2312.*-0", 20, 57, make_tag('g','b','2','3'), 0 }, + { "jisx0201*-0", 21, 15, make_tag('j','i','s','x'), 0 }, + { "jisx0208*-0", 22, 63, make_tag('j','i','s','x'), 0 }, + { "ksc5601*-*", 23, 36, make_tag('k','s','c','5'), 0 }, + { "big5hkscs-0", 24, -2101, make_tag('b','i','g','5'), make_tag('c','s','-','0') }, + { "hkscs-1", 25, -2101, make_tag('h','k','s','c'), make_tag('c','s','-','1') }, + { "big5*-*", 26, -2026, make_tag('b','i','g','5'), 0 }, + { "tscii-*", 27, 2028, make_tag('t','s','c','i'), 0 }, + { "tis620*-*", 28, 2259, make_tag('t','i','s','6'), 0 }, + { "iso8859-11", 29, 2259, make_tag('i','s','o','8'), make_tag('9','-','1','1') }, + { "mulelao-1", 30, -4242, make_tag('m','u','l','e'), make_tag('a','o','-','1') }, + { "ethiopic-unicode", 31, 0, make_tag('e','t','h','i'), make_tag('c','o','d','e') }, + { "iso10646-1", 32, 0, make_tag('i','s','o','1'), make_tag('4','6','-','1') }, + { "unicode-*", 33, 0, make_tag('u','n','i','c'), 0 }, + { "*-symbol", 34, 0, 0, make_tag('m','b','o','l') }, + { "*-fontspecific", 35, 0, 0, make_tag('i','f','i','c') }, + { "fontspecific-*", 36, 0, make_tag('f','o','n','t'), 0 }, + { 0, 0, 0, 0, 0 } +}; + +static const char writingSystems_for_xlfd_encoding[sizeof(xlfd_encoding)][QFontDatabase::WritingSystemsCount] = { + // iso8859-1 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-2 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-3 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-4 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-9 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-10 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-13 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-14 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-15 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // hp-roman8 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-5 + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // *-cp1251 + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-ru + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-u + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-r + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-7 + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-8 + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // gb18030-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gb18030.2000-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gbk-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gb2312.*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // jisx0201*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0 }, + // jisx0208*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0 }, + // ksc5601*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0 }, + // big5hkscs-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // hkscs-1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // big5*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // tscii-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // tis620*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-11 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // mulelao-1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // ethiopic-unicode + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso10646-1 + { 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 0, 0 }, + // unicode-* + { 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 0, 0 }, + // *-symbol + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 }, + // *-fontspecific + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 }, + // fontspecific-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 } + +}; + +// ----- end of generated code ----- + + +const int numEncodings = sizeof(xlfd_encoding) / sizeof(XlfdEncoding) - 1; + +int qt_xlfd_encoding_id(const char *encoding) +{ + // qDebug("looking for encoding id for '%s'", encoding); + int len = strlen(encoding); + if (len < 4) + return -1; + unsigned int hash1 = make_tag(encoding[0], encoding[1], encoding[2], encoding[3]); + const char *ch = encoding + len - 4; + unsigned int hash2 = make_tag(ch[0], ch[1], ch[2], ch[3]); + + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if ((enc->hash1 && enc->hash1 != hash1) || + (enc->hash2 && enc->hash2 != hash2)) + continue; + // hashes match, do a compare if strings match + // the enc->name can contain '*'s we have to interpret correctly + const char *n = enc->name; + const char *e = encoding; + while (1) { + // qDebug("bol: *e='%c', *n='%c'", *e, *n); + if (*e == '\0') { + if (*n) + break; + // qDebug("found encoding id %d", enc->id); + return enc->id; + } + if (*e == *n) { + ++e; + ++n; + continue; + } + if (*n != '*') + break; + ++n; + // qDebug("skip: *e='%c', *n='%c'", *e, *n); + while (*e && *e != *n) + ++e; + } + } + // qDebug("couldn't find encoding %s", encoding); + return -1; +} + +int qt_mib_for_xlfd_encoding(const char *encoding) +{ + int id = qt_xlfd_encoding_id(encoding); + if (id != -1) return xlfd_encoding[id].mib; + return 0; +} + +int qt_encoding_id_for_mib(int mib) +{ + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if (enc->mib == mib) + return enc->id; + } + return -1; +} + +static const char * xlfd_for_id(int id) +{ + // special case: -1 returns the "*-*" encoding, allowing us to do full + // database population in a single X server round trip. + if (id < 0 || id > numEncodings) + return "*-*"; + return xlfd_encoding[id].name; +} + +enum XLFDFieldNames { + Foundry, + Family, + Weight, + Slant, + Width, + AddStyle, + PixelSize, + PointSize, + ResolutionX, + ResolutionY, + Spacing, + AverageWidth, + CharsetRegistry, + CharsetEncoding, + NFontFields +}; + +// Splits an X font name into fields separated by '-' +static bool parseXFontName(char *fontName, char **tokens) +{ + if (! fontName || fontName[0] == '0' || fontName[0] != '-') { + tokens[0] = 0; + return false; + } + + int i; + ++fontName; + for (i = 0; i < NFontFields && fontName && fontName[0]; ++i) { + tokens[i] = fontName; + for (;; ++fontName) { + if (*fontName == '-') + break; + if (! *fontName) { + fontName = 0; + break; + } + } + + if (fontName) *fontName++ = '\0'; + } + + if (i < NFontFields) { + for (int j = i ; j < NFontFields; ++j) + tokens[j] = 0; + return false; + } + + return true; +} + +static inline bool isZero(char *x) +{ + return (x[0] == '0' && x[1] == 0); +} + +static inline bool isScalable(char **tokens) +{ + return (isZero(tokens[PixelSize]) && + isZero(tokens[PointSize]) && + isZero(tokens[AverageWidth])); +} + +static inline bool isSmoothlyScalable(char **tokens) +{ + return (isZero(tokens[ResolutionX]) && + isZero(tokens[ResolutionY])); +} + +static inline bool isFixedPitch(char **tokens) +{ + return (tokens[Spacing][0] == 'm' || + tokens[Spacing][0] == 'c' || + tokens[Spacing][0] == 'M' || + tokens[Spacing][0] == 'C'); +} + +/* + Fills in a font definition (QFontDef) from an XLFD (X Logical Font + Description). + + Returns true if the given xlfd is valid. +*/ +bool qt_fillFontDef(const QByteArray &xlfd, QFontDef *fd, int dpi, QtFontDesc *desc) +{ + char *tokens[NFontFields]; + QByteArray buffer = xlfd; + if (! parseXFontName(buffer.data(), tokens)) + return false; + + capitalize(tokens[Family]); + capitalize(tokens[Foundry]); + + fd->styleStrategy |= QFont::NoAntialias; + fd->family = QString::fromLatin1(tokens[Family]); + QString foundry = QString::fromLatin1(tokens[Foundry]); + if (! foundry.isEmpty() && foundry != QLatin1String("*") && (!desc || desc->family->count > 1)) + fd->family += + QLatin1String(" [") + foundry + QLatin1Char(']'); + + if (qstrlen(tokens[AddStyle]) > 0) + fd->addStyle = QString::fromLatin1(tokens[AddStyle]); + else + fd->addStyle.clear(); + + fd->pointSize = atoi(tokens[PointSize])/10.; + fd->styleHint = QFont::AnyStyle; // ### any until we match families + + char slant = tolower((uchar) tokens[Slant][0]); + fd->style = (slant == 'o' ? QFont::StyleOblique : (slant == 'i' ? QFont::StyleItalic : QFont::StyleNormal)); + char fixed = tolower((uchar) tokens[Spacing][0]); + fd->fixedPitch = (fixed == 'm' || fixed == 'c'); + fd->weight = getFontWeight(QLatin1String(tokens[Weight])); + + int r = atoi(tokens[ResolutionY]); + fd->pixelSize = atoi(tokens[PixelSize]); + // not "0" or "*", or required DPI + if (r && fd->pixelSize && r != dpi) { + // calculate actual pointsize for display DPI + fd->pointSize = qt_pointSize(fd->pixelSize, dpi); + } else if (fd->pixelSize == 0 && fd->pointSize) { + // calculate pixel size from pointsize/dpi + fd->pixelSize = qRound(qt_pixelSize(fd->pointSize, dpi)); + } + + return true; +} + +/* + Fills in a font definition (QFontDef) from the font properties in an + XFontStruct. + + Returns true if the QFontDef could be filled with properties from + the XFontStruct. +*/ +static bool qt_fillFontDef(XFontStruct *fs, QFontDef *fd, int dpi, QtFontDesc *desc) +{ + unsigned long value; + if (!fs || !XGetFontProperty(fs, XA_FONT, &value)) + return false; + + char *n = XGetAtomName(QX11Info::display(), value); + QByteArray xlfd(n); + if (n) + XFree(n); + return qt_fillFontDef(xlfd.toLower(), fd, dpi, desc); +} + + +static QtFontStyle::Key getStyle(char ** tokens) +{ + QtFontStyle::Key key; + + char slant0 = tolower((uchar) tokens[Slant][0]); + + if (slant0 == 'r') { + if (tokens[Slant][1]) { + char slant1 = tolower((uchar) tokens[Slant][1]); + + if (slant1 == 'o') + key.style = QFont::StyleOblique; + else if (slant1 == 'i') + key.style = QFont::StyleItalic; + } + } else if (slant0 == 'o') + key.style = QFont::StyleOblique; + else if (slant0 == 'i') + key.style = QFont::StyleItalic; + + key.weight = getFontWeight(QLatin1String(tokens[Weight])); + + if (qstrcmp(tokens[Width], "normal") == 0) { + key.stretch = 100; + } else if (qstrcmp(tokens[Width], "semi condensed") == 0 || + qstrcmp(tokens[Width], "semicondensed") == 0) { + key.stretch = 90; + } else if (qstrcmp(tokens[Width], "condensed") == 0) { + key.stretch = 80; + } else if (qstrcmp(tokens[Width], "narrow") == 0) { + key.stretch = 60; + } + + return key; +} + + +static bool xlfdsFullyLoaded = false; +static unsigned char encodingLoaded[numEncodings]; + +static void loadXlfds(const char *reqFamily, int encoding_id) +{ + QFontDatabasePrivate *db = privateDb(); + QtFontFamily *fontFamily = reqFamily ? db->family(QLatin1String(reqFamily)) : 0; + + // make sure we don't load twice + if ((encoding_id == -1 && xlfdsFullyLoaded) + || (encoding_id != -1 && encodingLoaded[encoding_id])) + return; + if (fontFamily && fontFamily->xlfdLoaded) + return; + + int fontCount; + // force the X server to give us XLFDs + QByteArray xlfd_pattern("-*-"); + xlfd_pattern += (reqFamily && reqFamily[0] != '\0') ? reqFamily : "*"; + xlfd_pattern += "-*-*-*-*-*-*-*-*-*-*-"; + xlfd_pattern += xlfd_for_id(encoding_id); + + char **fontList = XListFonts(QX11Info::display(), + xlfd_pattern, + 0xffff, &fontCount); + // qDebug("requesting xlfd='%s', got %d fonts", xlfd_pattern.data(), fontCount); + + + char *tokens[NFontFields]; + + for(int i = 0 ; i < fontCount ; i++) { + if (! parseXFontName(fontList[i], tokens)) + continue; + + // get the encoding_id for this xlfd. we need to do this + // here, since we can pass -1 to this function to do full + // database population + *(tokens[CharsetEncoding] - 1) = '-'; + int encoding_id = qt_xlfd_encoding_id(tokens[CharsetRegistry]); + if (encoding_id == -1) + continue; + + char *familyName = tokens[Family]; + capitalize(familyName); + char *foundryName = tokens[Foundry]; + capitalize(foundryName); + QtFontStyle::Key styleKey = getStyle(tokens); + + bool smooth_scalable = false; + bool bitmap_scalable = false; + if (isScalable(tokens)) { + if (isSmoothlyScalable(tokens)) + smooth_scalable = true; + else + bitmap_scalable = true; + } + uint pixelSize = atoi(tokens[PixelSize]); + uint xpointSize = atoi(tokens[PointSize]); + uint xres = atoi(tokens[ResolutionX]); + uint yres = atoi(tokens[ResolutionY]); + uint avgwidth = atoi(tokens[AverageWidth]); + bool fixedPitch = isFixedPitch(tokens); + + if (avgwidth == 0 && pixelSize != 0) { + /* + Ignore bitmap scalable fonts that are automatically + generated by some X servers. We know they are bitmap + scalable because even though they have a specified pixel + size, the average width is zero. + */ + continue; + } + + QtFontFamily *family = fontFamily ? fontFamily : db->family(QLatin1String(familyName), true); + family->fontFileIndex = -1; + family->symbol_checked = true; + QtFontFoundry *foundry = family->foundry(QLatin1String(foundryName), true); + QtFontStyle *style = foundry->style(styleKey, true); + + delete [] style->weightName; + style->weightName = qstrdup(tokens[Weight]); + delete [] style->setwidthName; + style->setwidthName = qstrdup(tokens[Width]); + + if (smooth_scalable) { + style->smoothScalable = true; + style->bitmapScalable = false; + pixelSize = SMOOTH_SCALABLE; + } + if (!style->smoothScalable && bitmap_scalable) + style->bitmapScalable = true; + if (!fixedPitch) + family->fixedPitch = false; + + QtFontSize *size = style->pixelSize(pixelSize, true); + QtFontEncoding *enc = + size->encodingID(encoding_id, xpointSize, xres, yres, avgwidth, true); + enc->pitch = *tokens[Spacing]; + if (!enc->pitch) enc->pitch = '*'; + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + if (writingSystems_for_xlfd_encoding[encoding_id][i]) + family->writingSystems[i] = QtFontFamily::Supported; + } + } + if (!reqFamily) { + // mark encoding as loaded + if (encoding_id == -1) + xlfdsFullyLoaded = true; + else + encodingLoaded[encoding_id] = true; + } + + XFreeFontNames(fontList); +} + + +#ifndef QT_NO_FONTCONFIG + +#ifndef FC_WIDTH +#define FC_WIDTH "width" +#endif + +static int getFCWeight(int fc_weight) +{ + int qtweight = QFont::Black; + if (fc_weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_MEDIUM) / 2) + qtweight = QFont::Light; + else if (fc_weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) + qtweight = QFont::Normal; + else if (fc_weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) + qtweight = QFont::DemiBold; + else if (fc_weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_BLACK) / 2) + qtweight = QFont::Bold; + + return qtweight; +} + +QFontDef qt_FcPatternToQFontDef(FcPattern *pattern, const QFontDef &request) +{ + QFontDef fontDef; + fontDef.styleStrategy = request.styleStrategy; + + fontDef.hintingPreference = request.hintingPreference; + FcChar8 *value = 0; + if (FcPatternGetString(pattern, FC_FAMILY, 0, &value) == FcResultMatch) { + fontDef.family = QString::fromUtf8(reinterpret_cast<const char *>(value)); + } + + double dpi; + if (FcPatternGetDouble(pattern, FC_DPI, 0, &dpi) != FcResultMatch) { + if (X11->display) + dpi = QX11Info::appDpiY(); + else + dpi = qt_defaultDpiY(); + } + + double size; + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) == FcResultMatch) + fontDef.pixelSize = size; + else + fontDef.pixelSize = 12; + + fontDef.pointSize = qt_pointSize(fontDef.pixelSize, qRound(dpi)); + + /* ### + fontDef.styleHint + */ + + int weight; + if (FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight) != FcResultMatch) + weight = FC_WEIGHT_MEDIUM; + fontDef.weight = getFCWeight(weight); + + int slant; + if (FcPatternGetInteger(pattern, FC_SLANT, 0, &slant) != FcResultMatch) + slant = FC_SLANT_ROMAN; + fontDef.style = (slant == FC_SLANT_ITALIC) + ? QFont::StyleItalic + : ((slant == FC_SLANT_OBLIQUE) + ? QFont::StyleOblique + : QFont::StyleNormal); + + + FcBool scalable; + if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = false; + if (scalable) { + fontDef.stretch = request.stretch; + fontDef.style = request.style; + } else { + int width; + if (FcPatternGetInteger(pattern, FC_WIDTH, 0, &width) == FcResultMatch) + fontDef.stretch = width; + else + fontDef.stretch = 100; + } + + int spacing; + if (FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing) == FcResultMatch) { + fontDef.fixedPitch = (spacing >= FC_MONO); + fontDef.ignorePitch = false; + } else { + fontDef.ignorePitch = true; + } + + return fontDef; +} + +static const char *specialLanguages[] = { + "en", // Common + "el", // Greek + "ru", // Cyrillic + "hy", // Armenian + "he", // Hebrew + "ar", // Arabic + "syr", // Syriac + "div", // Thaana + "hi", // Devanagari + "bn", // Bengali + "pa", // Gurmukhi + "gu", // Gujarati + "or", // Oriya + "ta", // Tamil + "te", // Telugu + "kn", // Kannada + "ml", // Malayalam + "si", // Sinhala + "th", // Thai + "lo", // Lao + "bo", // Tibetan + "my", // Myanmar + "ka", // Georgian + "ko", // Hangul + "", // Ogham + "", // Runic + "km", // Khmer + "" // N'Ko +}; +enum { SpecialLanguageCount = sizeof(specialLanguages) / sizeof(const char *) }; + +static const ushort specialChars[] = { + 0, // English + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + 0, // Syriac + 0, // Thaana + 0, // Devanagari + 0, // Bengali + 0, // Gurmukhi + 0, // Gujarati + 0, // Oriya + 0, // Tamil + 0xc15, // Telugu + 0xc95, // Kannada + 0xd15, // Malayalam + 0xd9a, // Sinhala + 0, // Thai + 0, // Lao + 0, // Tibetan + 0x1000, // Myanmar + 0, // Georgian + 0, // Hangul + 0x1681, // Ogham + 0x16a0, // Runic + 0, // Khmer + 0x7ca // N'Ko +}; +enum { SpecialCharCount = sizeof(specialChars) / sizeof(ushort) }; + +// this could become a list of all languages used for each writing +// system, instead of using the single most common language. +static const char *languageForWritingSystem[] = { + 0, // Any + "en", // Latin + "el", // Greek + "ru", // Cyrillic + "hy", // Armenian + "he", // Hebrew + "ar", // Arabic + "syr", // Syriac + "div", // Thaana + "hi", // Devanagari + "bn", // Bengali + "pa", // Gurmukhi + "gu", // Gujarati + "or", // Oriya + "ta", // Tamil + "te", // Telugu + "kn", // Kannada + "ml", // Malayalam + "si", // Sinhala + "th", // Thai + "lo", // Lao + "bo", // Tibetan + "my", // Myanmar + "ka", // Georgian + "km", // Khmer + "zh-cn", // SimplifiedChinese + "zh-tw", // TraditionalChinese + "ja", // Japanese + "ko", // Korean + "vi", // Vietnamese + 0, // Symbol + 0, // Ogham + 0, // Runic + 0 // N'Ko +}; +enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) }; + +// Unfortunately FontConfig doesn't know about some languages. We have to test these through the +// charset. The lists below contain the systems where we need to do this. +static const ushort sampleCharForWritingSystem[] = { + 0, // Any + 0, // Latin + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + 0, // Syriac + 0, // Thaana + 0, // Devanagari + 0, // Bengali + 0, // Gurmukhi + 0, // Gujarati + 0, // Oriya + 0, // Tamil + 0xc15, // Telugu + 0xc95, // Kannada + 0xd15, // Malayalam + 0xd9a, // Sinhala + 0, // Thai + 0, // Lao + 0, // Tibetan + 0x1000, // Myanmar + 0, // Georgian + 0, // Khmer + 0, // SimplifiedChinese + 0, // TraditionalChinese + 0, // Japanese + 0, // Korean + 0, // Vietnamese + 0, // Symbol + 0x1681, // Ogham + 0x16a0, // Runic + 0x7ca // N'Ko +}; +enum { SampleCharCount = sizeof(sampleCharForWritingSystem) / sizeof(ushort) }; + +// Newer FontConfig let's us sort out fonts that contain certain glyphs, but no +// open type tables for is directly. Do this so we don't pick some strange +// pseudo unicode font +static const char *openType[] = { + 0, // Any + 0, // Latin + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + "syrc", // Syriac + "thaa", // Thaana + "deva", // Devanagari + "beng", // Bengali + "guru", // Gurmukhi + "gurj", // Gujarati + "orya", // Oriya + "taml", // Tamil + "telu", // Telugu + "knda", // Kannada + "mlym", // Malayalam + "sinh", // Sinhala + 0, // Thai + 0, // Lao + "tibt", // Tibetan + "mymr", // Myanmar + 0, // Georgian + "khmr", // Khmer + 0, // SimplifiedChinese + 0, // TraditionalChinese + 0, // Japanese + 0, // Korean + 0, // Vietnamese + 0, // Symbol + 0, // Ogham + 0, // Runic + "nko " // N'Ko +}; +enum { OpenTypeCount = sizeof(openType) / sizeof(const char *) }; + + +static void loadFontConfig() +{ + Q_ASSERT_X(X11, "QFontDatabase", + "A QApplication object needs to be constructed before FontConfig is used."); + if (!X11->has_fontconfig) + return; + + Q_ASSERT_X(int(QUnicodeTables::ScriptCount) == SpecialLanguageCount, + "QFontDatabase", "New scripts have been added."); + Q_ASSERT_X(int(QUnicodeTables::ScriptCount) == SpecialCharCount, + "QFontDatabase", "New scripts have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == LanguageCount, + "QFontDatabase", "New writing systems have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == SampleCharCount, + "QFontDatabase", "New writing systems have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == OpenTypeCount, + "QFontDatabase", "New writing systems have been added."); + + QFontDatabasePrivate *db = privateDb(); + FcFontSet *fonts; + + FcPattern *pattern = FcPatternCreate(); + FcDefaultSubstitute(pattern); + FcChar8 *lang = 0; + if (FcPatternGetString(pattern, FC_LANG, 0, &lang) == FcResultMatch) + db->systemLang = QString::fromUtf8((const char *) lang); + FcPatternDestroy(pattern); + + QString familyName; + FcChar8 *value = 0; + int weight_value; + int slant_value; + int spacing_value; + FcChar8 *file_value; + int index_value; + FcChar8 *foundry_value; + FcBool scalable; + + { + FcObjectSet *os = FcObjectSetCreate(); + FcPattern *pattern = FcPatternCreate(); + const char *properties [] = { + FC_FAMILY, FC_WEIGHT, FC_SLANT, + FC_SPACING, FC_FILE, FC_INDEX, + FC_LANG, FC_CHARSET, FC_FOUNDRY, FC_SCALABLE, FC_PIXEL_SIZE, FC_WEIGHT, + FC_WIDTH, +#if FC_VERSION >= 20297 + FC_CAPABILITY, +#endif + (const char *)0 + }; + const char **p = properties; + while (*p) { + FcObjectSetAdd(os, *p); + ++p; + } + fonts = FcFontList(0, pattern, os); + FcObjectSetDestroy(os); + FcPatternDestroy(pattern); + } + + for (int i = 0; i < fonts->nfont; i++) { + if (FcPatternGetString(fonts->fonts[i], FC_FAMILY, 0, &value) != FcResultMatch) + continue; + // capitalize(value); + familyName = QString::fromUtf8((const char *)value); + slant_value = FC_SLANT_ROMAN; + weight_value = FC_WEIGHT_MEDIUM; + spacing_value = FC_PROPORTIONAL; + file_value = 0; + index_value = 0; + scalable = FcTrue; + + if (FcPatternGetInteger (fonts->fonts[i], FC_SLANT, 0, &slant_value) != FcResultMatch) + slant_value = FC_SLANT_ROMAN; + if (FcPatternGetInteger (fonts->fonts[i], FC_WEIGHT, 0, &weight_value) != FcResultMatch) + weight_value = FC_WEIGHT_MEDIUM; + if (FcPatternGetInteger (fonts->fonts[i], FC_SPACING, 0, &spacing_value) != FcResultMatch) + spacing_value = FC_PROPORTIONAL; + if (FcPatternGetString (fonts->fonts[i], FC_FILE, 0, &file_value) != FcResultMatch) + file_value = 0; + if (FcPatternGetInteger (fonts->fonts[i], FC_INDEX, 0, &index_value) != FcResultMatch) + index_value = 0; + if (FcPatternGetBool(fonts->fonts[i], FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = FcTrue; + if (FcPatternGetString(fonts->fonts[i], FC_FOUNDRY, 0, &foundry_value) != FcResultMatch) + foundry_value = 0; + QtFontFamily *family = db->family(familyName, true); + + FcLangSet *langset = 0; + FcResult res = FcPatternGetLangSet(fonts->fonts[i], FC_LANG, 0, &langset); + if (res == FcResultMatch) { + for (int i = 1; i < LanguageCount; ++i) { + const FcChar8 *lang = (const FcChar8*) languageForWritingSystem[i]; + if (!lang) { + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + } else { + FcLangResult langRes = FcLangSetHasLang(langset, lang); + if (langRes != FcLangDifferentLang) + family->writingSystems[i] = QtFontFamily::Supported; + else + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + } + } + family->writingSystems[QFontDatabase::Other] = QtFontFamily::UnsupportedFT; + family->ftWritingSystemCheck = true; + } else { + // we set Other to supported for symbol fonts. It makes no + // sense to merge these with other ones, as they are + // special in a way. + for (int i = 1; i < LanguageCount; ++i) + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + family->writingSystems[QFontDatabase::Other] = QtFontFamily::Supported; + } + + FcCharSet *cs = 0; + res = FcPatternGetCharSet(fonts->fonts[i], FC_CHARSET, 0, &cs); + if (res == FcResultMatch) { + // some languages are not supported by FontConfig, we rather check the + // charset to detect these + for (int i = 1; i < SampleCharCount; ++i) { + if (!sampleCharForWritingSystem[i]) + continue; + if (FcCharSetHasChar(cs, sampleCharForWritingSystem[i])) + family->writingSystems[i] = QtFontFamily::Supported; + } + } + +#if FC_VERSION >= 20297 + for (int j = 1; j < LanguageCount; ++j) { + if (family->writingSystems[j] == QtFontFamily::Supported && requiresOpenType(j) && openType[j]) { + FcChar8 *cap; + res = FcPatternGetString (fonts->fonts[i], FC_CAPABILITY, 0, &cap); + if (res != FcResultMatch || !strstr((const char *)cap, openType[j])) + family->writingSystems[j] = QtFontFamily::UnsupportedFT; + } + } +#endif + + QByteArray file((const char *)file_value); + family->fontFilename = file; + family->fontFileIndex = index_value; + + QtFontStyle::Key styleKey; + styleKey.style = (slant_value == FC_SLANT_ITALIC) + ? QFont::StyleItalic + : ((slant_value == FC_SLANT_OBLIQUE) + ? QFont::StyleOblique + : QFont::StyleNormal); + styleKey.weight = getFCWeight(weight_value); + if (!scalable) { + int width = 100; + FcPatternGetInteger (fonts->fonts[i], FC_WIDTH, 0, &width); + styleKey.stretch = width; + } + + QtFontFoundry *foundry + = family->foundry(foundry_value ? QString::fromUtf8((const char *)foundry_value) : QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + + if (spacing_value < FC_MONO) + family->fixedPitch = false; + + QtFontSize *size; + if (scalable) { + style->smoothScalable = true; + size = style->pixelSize(SMOOTH_SCALABLE, true); + } else { + double pixel_size = 0; + FcPatternGetDouble (fonts->fonts[i], FC_PIXEL_SIZE, 0, &pixel_size); + size = style->pixelSize((int)pixel_size, true); + } + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + enc->pitch = (spacing_value >= FC_CHARCELL ? 'c' : + (spacing_value >= FC_MONO ? 'm' : 'p')); + } + + FcFontSetDestroy (fonts); + + struct FcDefaultFont { + const char *qtname; + const char *rawname; + bool fixed; + }; + const FcDefaultFont defaults[] = { + { "Serif", "serif", false }, + { "Sans Serif", "sans-serif", false }, + { "Monospace", "monospace", true }, + { 0, 0, false } + }; + const FcDefaultFont *f = defaults; + while (f->qtname) { + QtFontFamily *family = db->family(QLatin1String(f->qtname), true); + family->fixedPitch = f->fixed; + family->synthetic = true; + QtFontFoundry *foundry = family->foundry(QString(), true); + + // aliases only make sense for 'common', not for any of the specials + for (int i = 1; i < LanguageCount; ++i) { + if (requiresOpenType(i)) + family->writingSystems[i] = QtFontFamily::UnsupportedFT; + else + family->writingSystems[i] = QtFontFamily::Supported; + } + family->writingSystems[QFontDatabase::Other] = QtFontFamily::UnsupportedFT; + + QtFontStyle::Key styleKey; + for (int i = 0; i < 4; ++i) { + styleKey.style = (i%2) ? QFont::StyleNormal : QFont::StyleItalic; + styleKey.weight = (i > 1) ? QFont::Bold : QFont::Normal; + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = true; + QtFontSize *size = style->pixelSize(SMOOTH_SCALABLE, true); + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + enc->pitch = (f->fixed ? 'm' : 'p'); + } + ++f; + } +} +#endif // QT_NO_FONTCONFIG + +static void initializeDb(); + +static void load(const QString &family = QString(), int script = -1, bool forceXLFD = false) +{ + if (X11->has_fontconfig && !forceXLFD) { + initializeDb(); + return; + } + +#ifdef QFONTDATABASE_DEBUG + QElapsedTimer t; + t.start(); +#endif + + if (family.isNull() && script == -1) { + loadXlfds(0, -1); + } else { + if (family.isNull()) { + // load all families in all writing systems that match \a script + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int i = 0; i < numEncodings; ++i) { + if (writingSystems_for_xlfd_encoding[i][ws]) + loadXlfds(0, i); + } + } + } else { + QtFontFamily *f = privateDb()->family(family); + // could reduce this further with some more magic: + // would need to remember the encodings loaded for the family. + if (!f || !f->xlfdLoaded) + loadXlfds(family.toLatin1(), -1); + } + } + +#ifdef QFONTDATABASE_DEBUG + FD_DEBUG("QFontDatabase: load(%s, %d) took %d ms", + family.toLatin1().constData(), script, t.elapsed()); +#endif +} + +static void checkSymbolFont(QtFontFamily *family) +{ + if (!family || family->symbol_checked || family->fontFilename.isEmpty()) + return; +// qDebug() << "checking " << family->rawName; + family->symbol_checked = true; + + QFontEngine::FaceId id; + id.filename = family->fontFilename; + id.index = family->fontFileIndex; + QFreetypeFace *f = QFreetypeFace::getFace(id); + if (!f) { + qWarning("checkSymbolFonts: Couldn't open face %s (%s/%d)", + qPrintable(family->name), family->fontFilename.data(), family->fontFileIndex); + return; + } + for (int i = 0; i < f->face->num_charmaps; ++i) { + FT_CharMap cm = f->face->charmaps[i]; + if (cm->encoding == FT_ENCODING_ADOBE_CUSTOM + || cm->encoding == FT_ENCODING_MS_SYMBOL) { + for (int x = QFontDatabase::Latin; x < QFontDatabase::Other; ++x) + family->writingSystems[x] = QtFontFamily::Unsupported; + family->writingSystems[QFontDatabase::Other] = QtFontFamily::Supported; + break; + } + } + f->release(id); +} + +static void checkSymbolFonts(const QString &family = QString()) +{ +#ifndef QT_NO_FONTCONFIG + QFontDatabasePrivate *d = privateDb(); + + if (family.isEmpty()) { + for (int i = 0; i < d->count; ++i) + checkSymbolFont(d->families[i]); + } else { + checkSymbolFont(d->family(family)); + } +#endif +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt); + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + QElapsedTimer t; + t.start(); + +#ifndef QT_NO_FONTCONFIG + if (db->reregisterAppFonts) { + db->reregisterAppFonts = false; + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!db->applicationFonts.at(i).families.isEmpty()) { + registerFont(&db->applicationFonts[i]); + } + } + + loadFontConfig(); + FD_DEBUG("QFontDatabase: loaded FontConfig: %d ms", int(t.elapsed())); +#endif + + t.start(); + +#ifndef QT_NO_FONTCONFIG + for (int i = 0; i < db->count; i++) { + for (int j = 0; j < db->families[i]->count; ++j) { // each foundry + QtFontFoundry *foundry = db->families[i]->foundries[j]; + for (int k = 0; k < foundry->count; ++k) { + QtFontStyle *style = foundry->styles[k]; + if (style->key.style != QFont::StyleNormal) continue; + + QtFontSize *size = style->pixelSize(SMOOTH_SCALABLE); + if (! size) continue; // should not happen + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + if (! enc) continue; // should not happen either + + QtFontStyle::Key key = style->key; + + // does this style have an italic equivalent? + key.style = QFont::StyleItalic; + QtFontStyle *equiv = foundry->style(key); + if (equiv) continue; + + // does this style have an oblique equivalent? + key.style = QFont::StyleOblique; + equiv = foundry->style(key); + if (equiv) continue; + + // let's fake one... + equiv = foundry->style(key, true); + equiv->smoothScalable = true; + + QtFontSize *equiv_size = equiv->pixelSize(SMOOTH_SCALABLE, true); + QtFontEncoding *equiv_enc = equiv_size->encodingID(-1, 0, 0, 0, 0, true); + + // keep the same pitch + equiv_enc->pitch = enc->pitch; + } + } + } +#endif + + +#ifdef QFONTDATABASE_DEBUG +#ifndef QT_NO_FONTCONFIG + if (!X11->has_fontconfig) +#endif + // load everything at startup in debug mode. + loadXlfds(0, -1); + + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + FD_DEBUG("'%s' %s fixed=%s", family->name.latin1(), (family->fixedPitch ? "fixed" : ""), + (family->fixedPitch ? "yes" : "no")); + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + QFontDatabase::WritingSystem ws = QFontDatabase::WritingSystem(i); + FD_DEBUG("\t%s: %s", QFontDatabase::writingSystemName(ws).toLatin1().constData(), + ((family->writingSystems[i] & QtFontFamily::Supported) ? "Supported" : + (family->writingSystems[i] & QtFontFamily::Unsupported) == QtFontFamily::Unsupported ? + "Unsupported" : "Unknown")); + } + + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + FD_DEBUG("\t\t'%s'", foundry->name.latin1()); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + FD_DEBUG("\t\t\tstyle: style=%d weight=%d (%s)\n" + "\t\t\tstretch=%d (%s)", + style->key.style, style->key.weight, + style->weightName, style->key.stretch, + style->setwidthName ? style->setwidthName : "nil"); + if (style->smoothScalable) + FD_DEBUG("\t\t\t\tsmooth scalable"); + else if (style->bitmapScalable) + FD_DEBUG("\t\t\t\tbitmap scalable"); + if (style->pixelSizes) { + qDebug("\t\t\t\t%d pixel sizes", style->count); + for (int z = 0; z < style->count; ++z) { + QtFontSize *size = style->pixelSizes + z; + for (int e = 0; e < size->count; ++e) { + FD_DEBUG("\t\t\t\t size %5d pitch %c encoding %s", + size->pixelSize, + size->encodings[e].pitch, + xlfd_for_id(size->encodings[e].encoding)); + } + } + } + } + } + } +#endif // QFONTDATABASE_DEBUG +} + + +// -------------------------------------------------------------------------------------- +// font loader +// -------------------------------------------------------------------------------------- + +static const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "sans-serif"; + break; + case QFont::Serif: + stylehint = "serif"; + break; + case QFont::TypeWriter: + stylehint = "monospace"; + break; + default: + if (request.fixedPitch) + stylehint = "monospace"; + break; + } + return stylehint; +} + +#ifndef QT_NO_FONTCONFIG + +void qt_addPatternProps(FcPattern *pattern, int screen, int script, const QFontDef &request) +{ + int weight_value = FC_WEIGHT_BLACK; + if (request.weight == 0) + weight_value = FC_WEIGHT_MEDIUM; + else if (request.weight < (QFont::Light + QFont::Normal) / 2) + weight_value = FC_WEIGHT_LIGHT; + else if (request.weight < (QFont::Normal + QFont::DemiBold) / 2) + weight_value = FC_WEIGHT_MEDIUM; + else if (request.weight < (QFont::DemiBold + QFont::Bold) / 2) + weight_value = FC_WEIGHT_DEMIBOLD; + else if (request.weight < (QFont::Bold + QFont::Black) / 2) + weight_value = FC_WEIGHT_BOLD; + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, weight_value); + + int slant_value = FC_SLANT_ROMAN; + if (request.style == QFont::StyleItalic) + slant_value = FC_SLANT_ITALIC; + else if (request.style == QFont::StyleOblique) + slant_value = FC_SLANT_OBLIQUE; + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, slant_value); + + double size_value = qMax(qreal(1.), request.pixelSize); + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size_value); + + int stretch = request.stretch; + if (!stretch) + stretch = 100; + FcPatternDel(pattern, FC_WIDTH); + FcPatternAddInteger(pattern, FC_WIDTH, stretch); + + if (X11->display && QX11Info::appDepth(screen) <= 8) { + FcPatternDel(pattern, FC_ANTIALIAS); + // can't do antialiasing on 8bpp + FcPatternAddBool(pattern, FC_ANTIALIAS, false); + } else if (request.styleStrategy & (QFont::PreferAntialias|QFont::NoAntialias)) { + FcPatternDel(pattern, FC_ANTIALIAS); + FcPatternAddBool(pattern, FC_ANTIALIAS, + !(request.styleStrategy & QFont::NoAntialias)); + } + + if (script != QUnicodeTables::Common && *specialLanguages[script] != '\0') { + Q_ASSERT(script < QUnicodeTables::ScriptCount); + FcLangSet *ls = FcLangSetCreate(); + FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]); + FcPatternDel(pattern, FC_LANG); + FcPatternAddLangSet(pattern, FC_LANG, ls); + FcLangSetDestroy(ls); + } +} + +static bool preferScalable(const QFontDef &request) +{ + return request.styleStrategy & (QFont::PreferOutline|QFont::ForceOutline|QFont::PreferQuality|QFont::PreferAntialias); +} + + +static FcPattern *getFcPattern(const QFontPrivate *fp, int script, const QFontDef &request) +{ + if (!X11->has_fontconfig) + return 0; + + FcPattern *pattern = FcPatternCreate(); + if (!pattern) + return 0; + + FcValue value; + value.type = FcTypeString; + + QtFontDesc desc; + QStringList families_and_foundries = familyList(request); + for (int i = 0; i < families_and_foundries.size(); ++i) { + QString family, foundry; + parseFontName(families_and_foundries.at(i), foundry, family); + if (!family.isEmpty()) { + QByteArray cs = family.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAdd(pattern, FC_FAMILY, value, FcTrue); + } + if (i == 0) { + QT_PREPEND_NAMESPACE(match)(script, request, family, foundry, -1, &desc); + if (!foundry.isEmpty()) { + QByteArray cs = foundry.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FOUNDRY, value, FcTrue); + } + } + } + + const char *stylehint = styleHint(request); + if (stylehint) { + value.u.s = (const FcChar8 *)stylehint; + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + } + + if (!request.ignorePitch) { + char pitch_value = FC_PROPORTIONAL; + if (request.fixedPitch || (desc.family && desc.family->fixedPitch)) + pitch_value = FC_MONO; + FcPatternAddInteger(pattern, FC_SPACING, pitch_value); + } + FcPatternAddBool(pattern, FC_OUTLINE, !(request.styleStrategy & QFont::PreferBitmap)); + if (preferScalable(request) || (desc.style && desc.style->smoothScalable)) + FcPatternAddBool(pattern, FC_SCALABLE, true); + + qt_addPatternProps(pattern, fp->screen, script, request); + + FcDefaultSubstitute(pattern); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcConfigSubstitute(0, pattern, FcMatchFont); + + // these should only get added to the pattern _after_ substitution + // append the default fallback font for the specified script + extern QString qt_fallback_font_family(int); + QString fallback = qt_fallback_font_family(script); + if (!fallback.isEmpty()) { + QByteArray cs = fallback.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + } + + // add the default family + QString defaultFamily = QApplication::font().family(); + QByteArray cs = defaultFamily.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + defaultFamily = QApplication::font().defaultFamily(); + cs = defaultFamily.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + + return pattern; +} + + +static void FcFontSetRemove(FcFontSet *fs, int at) +{ + Q_ASSERT(at < fs->nfont); + FcPatternDestroy(fs->fonts[at]); + int len = (--fs->nfont - at) * sizeof(FcPattern *);; + if (len > 0) + memmove(fs->fonts + at, fs->fonts + at + 1, len); +} + +static QFontEngine *tryPatternLoad(FcPattern *p, int screen, + const QFontDef &request, int script, FcPattern **matchedPattern = 0) +{ +#ifdef FONT_MATCH_DEBUG + FcChar8 *fam; + FcPatternGetString(p, FC_FAMILY, 0, &fam); + FM_DEBUG("==== trying %s\n", fam); +#endif + FM_DEBUG("passes charset test\n"); + FcPattern *pattern = FcPatternDuplicate(p); + // add properties back in as the font selected from the + // list doesn't contain them. + qt_addPatternProps(pattern, screen, script, request); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult res; + FcPattern *match = FcFontMatch(0, pattern, &res); + + if (matchedPattern) + *matchedPattern = 0; + + QFontEngineX11FT *engine = 0; + if (!match) // probably no fonts available. + goto done; + + if (matchedPattern) + *matchedPattern = FcPatternDuplicate(match); + + if (script != QUnicodeTables::Common) { + // skip font if it doesn't support the language we want + if (specialChars[script]) { + // need to check the charset, as the langset doesn't work for these scripts + FcCharSet *cs; + if (FcPatternGetCharSet(match, FC_CHARSET, 0, &cs) != FcResultMatch) + goto done; + if (!FcCharSetHasChar(cs, specialChars[script])) + goto done; + } else if (*specialLanguages[script] != '\0'){ + FcLangSet *langSet = 0; + if (FcPatternGetLangSet(match, FC_LANG, 0, &langSet) != FcResultMatch) + goto done; + if (FcLangSetHasLang(langSet, (const FcChar8*)specialLanguages[script]) != FcLangEqual) + goto done; + } + } + + // enforce non-antialiasing if requested. the ft font engine looks at this property. + if (request.styleStrategy & QFont::NoAntialias) { + FcPatternDel(match, FC_ANTIALIAS); + FcPatternAddBool(match, FC_ANTIALIAS, false); + } + + engine = new QFontEngineX11FT(match, qt_FcPatternToQFontDef(match, request), screen); + if (engine->invalid()) { + FM_DEBUG(" --> invalid!\n"); + delete engine; + engine = 0; + } else if (scriptRequiresOpenType(script)) { + HB_Face hbFace = engine->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + delete engine; + engine = 0; + } + } +done: + FcPatternDestroy(pattern); + if (!engine && matchedPattern && *matchedPattern) { + FcPatternDestroy(*matchedPattern); + *matchedPattern = 0; + } + return engine; +} + +FcFontSet *qt_fontSetForPattern(FcPattern *pattern, const QFontDef &request) +{ + FcResult result; + FcFontSet *fs = FcFontSort(0, pattern, FcTrue, 0, &result); +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("first font in fontset:\n"); + FcPatternPrint(fs->fonts[0]); +#endif + + FcBool forceScalable = request.styleStrategy & QFont::ForceOutline; + + // remove fonts if they are not scalable (and should be) + if (forceScalable && fs) { + for (int i = 0; i < fs->nfont; ++i) { + FcPattern *font = fs->fonts[i]; + FcResult res; + FcBool scalable; + res = FcPatternGetBool(font, FC_SCALABLE, 0, &scalable); + if (res != FcResultMatch || !scalable) { + FcFontSetRemove(fs, i); +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("removing pattern:"); + FcPatternPrint(font); +#endif + --i; // go back one + } + } + } + + FM_DEBUG("final pattern contains %d fonts\n", fs->nfont); + + return fs; +} + +static QFontEngine *loadFc(const QFontPrivate *fp, int script, const QFontDef &request) +{ + FM_DEBUG("===================== loadFc: script=%d family='%s'\n", script, request.family.toLatin1().data()); + FcPattern *pattern = getFcPattern(fp, script, request); + +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("\n\nfinal FcPattern contains:\n"); + FcPatternPrint(pattern); +#endif + + QFontEngine *fe = 0; + FcPattern *matchedPattern = 0; + fe = tryPatternLoad(pattern, fp->screen, request, script, &matchedPattern); + if (!fe) { + FcFontSet *fs = qt_fontSetForPattern(pattern, request); + + if (fs) { + for (int i = 0; !fe && i < fs->nfont; ++i) + fe = tryPatternLoad(fs->fonts[i], fp->screen, request, script, &matchedPattern); + FcFontSetDestroy(fs); + } + FM_DEBUG("engine for script %d is %s\n", script, fe ? fe->fontDef.family.toLatin1().data(): "(null)"); + } + if (fe + && script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) && !fe->symbol) { + fe = new QFontEngineMultiFT(fe, matchedPattern, pattern, fp->screen, request); + } else { + FcPatternDestroy(pattern); + if (matchedPattern) + FcPatternDestroy(matchedPattern); + } + return fe; +} + +static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count) +{ +#if FC_VERSION < 20402 + Q_UNUSED(data) + return FcFreeTypeQuery(file, id, blanks, count); +#else + if (data.isEmpty()) + return FcFreeTypeQuery(file, id, blanks, count); + + extern FT_Library qt_getFreetype(); + FT_Library lib = qt_getFreetype(); + + FcPattern *pattern = 0; + + FT_Face face; + if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, &face)) { + *count = face->num_faces; + + pattern = FcFreeTypeQueryFace(face, file, id, blanks); + + FT_Done_Face(face); + } + + return pattern; +#endif +} +#endif // QT_NO_FONTCONFIG + +static QFontEngine *loadRaw(const QFontPrivate *fp, const QFontDef &request) +{ + Q_ASSERT(fp && fp->rawMode); + + QByteArray xlfd = request.family.toLatin1(); + FM_DEBUG("Loading XLFD (rawmode) '%s'", xlfd.data()); + + QFontEngine *fe; + XFontStruct *xfs; + if (!(xfs = XLoadQueryFont(QX11Info::display(), xlfd.data()))) + if (!(xfs = XLoadQueryFont(QX11Info::display(), "fixed"))) + return 0; + + fe = new QFontEngineXLFD(xfs, xlfd, 0); + if (! qt_fillFontDef(xfs, &fe->fontDef, fp->dpi, 0) && + ! qt_fillFontDef(xlfd, &fe->fontDef, fp->dpi, 0)) + fe->fontDef = QFontDef(); + return fe; +} + +QFontEngine *QFontDatabase::loadXlfd(int screen, int script, const QFontDef &request, int force_encoding_id) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QtFontDesc desc; + FM_DEBUG() << "---> loadXlfd: request is" << request.family; + QStringList families_and_foundries = familyList(request); + const char *stylehint = styleHint(request); + if (stylehint) + families_and_foundries << QString::fromLatin1(stylehint); + families_and_foundries << QString(); + FM_DEBUG() << "loadXlfd: list is" << families_and_foundries; + for (int i = 0; i < families_and_foundries.size(); ++i) { + QString family, foundry; + QT_PREPEND_NAMESPACE(parseFontName)(families_and_foundries.at(i), foundry, family); + FM_DEBUG("loadXlfd: >>>>>>>>>>>>>>trying to match '%s' encoding=%d", family.toLatin1().data(), force_encoding_id); + QT_PREPEND_NAMESPACE(match)(script, request, family, foundry, force_encoding_id, &desc, QList<int>(), true); + if (desc.family) + break; + } + + QFontEngine *fe = 0; + if (force_encoding_id != -1 + || (request.styleStrategy & QFont::NoFontMerging) + || (desc.family && desc.family->writingSystems[QFontDatabase::Symbol] & QtFontFamily::Supported)) { + if (desc.family) { + int px = desc.size->pixelSize; + if (desc.style->smoothScalable && px == SMOOTH_SCALABLE) + px = request.pixelSize; + else if (desc.style->bitmapScalable && px == 0) + px = request.pixelSize; + + QByteArray xlfd("-"); + xlfd += desc.foundry->name.isEmpty() ? QByteArray("*") : desc.foundry->name.toLatin1(); + xlfd += '-'; + xlfd += desc.family->name.isEmpty() ? QByteArray("*") : desc.family->name.toLatin1(); + xlfd += '-'; + xlfd += desc.style->weightName ? desc.style->weightName : "*"; + xlfd += '-'; + xlfd += (desc.style->key.style == QFont::StyleItalic + ? 'i' + : (desc.style->key.style == QFont::StyleOblique ? 'o' : 'r')); + xlfd += '-'; + xlfd += desc.style->setwidthName ? desc.style->setwidthName : "*"; + // ### handle add-style + xlfd += "-*-"; + xlfd += QByteArray::number(px); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->xpoint); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->xres); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->yres); + xlfd += '-'; + xlfd += desc.encoding->pitch; + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->avgwidth); + xlfd += '-'; + xlfd += xlfd_for_id(desc.encoding->encoding); + + FM_DEBUG(" using XLFD: %s\n", xlfd.data()); + + const int mib = xlfd_encoding[desc.encoding->encoding].mib; + XFontStruct *xfs; + if ((xfs = XLoadQueryFont(QX11Info::display(), xlfd))) { + fe = new QFontEngineXLFD(xfs, xlfd, mib); + const int dpi = QX11Info::appDpiY(); + if (!qt_fillFontDef(xfs, &fe->fontDef, dpi, &desc) + && !qt_fillFontDef(xlfd, &fe->fontDef, dpi, &desc)) { + initFontDef(desc, request, &fe->fontDef); + } + } + } + if (!fe) { + fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = QFontDef(); + } + } else { + QList<int> encodings; + if (desc.encoding) { + if (desc.encoding->encoding >= 0) + encodings.append(int(desc.encoding->encoding)); + } + + if (desc.size) { + // append all other encodings for the matched font + for (int i = 0; i < desc.size->count; ++i) { + QtFontEncoding *e = desc.size->encodings + i; + if (e == desc.encoding || e->encoding < 0) + continue; + encodings.append(int(e->encoding)); + } + } + // fill in the missing encodings + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if (!encodings.contains(enc->id) && enc->id >= 0) { + encodings.append(enc->id); + } + } + +#if defined(FONT_MATCH_DEBUG) + FM_DEBUG(" using MultiXLFD, encodings:"); + for (int i = 0; i < encodings.size(); ++i) { + const int id = encodings.at(i); + FM_DEBUG(" %2d: %s", xlfd_encoding[id].id, xlfd_encoding[id].name); + } +#endif + + fe = new QFontEngineMultiXLFD(request, encodings, screen); + } + return fe; +} + +#if (defined(QT_ARCH_ARM) || defined(QT_ARCH_ARMV6)) && defined(Q_CC_GNU) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) +#define NEEDS_GCC_BUG_WORKAROUND +#endif + +#ifdef NEEDS_GCC_BUG_WORKAROUND +static inline void gccBugWorkaround(const QFontDef &req) +{ + char buffer[8]; + snprintf(buffer, 8, "%f", req.pixelSize); +} +#endif + +/*! \internal + Loads a QFontEngine for the specified \a script that matches the + QFontDef \e request member variable. +*/ +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + + // normalize the request to get better caching + QFontDef req = d->request; + if (req.pixelSize <= 0) + req.pixelSize = qFloor(qt_pixelSize(req.pointSize, d->dpi) * 100.0 + 0.5) * 0.01; + if (req.pixelSize < 1) + req.pixelSize = 1; + +#ifdef NEEDS_GCC_BUG_WORKAROUND + // req.pixelSize ends up with a bogus value unless this workaround is called + gccBugWorkaround(req); +#endif + + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, d->rawMode ? QUnicodeTables::Common : script, d->screen); + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + // set it to the actual pointsize, so QFontInfo will do the right thing + if (req.pointSize < 0) + req.pointSize = qt_pointSize(req.pixelSize, d->dpi); + + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + if (!fe) { + QMutexLocker locker(fontDatabaseMutex()); + if (!privateDb()->count) + initializeDb(); + + const bool mainThread = (qApp->thread() == QThread::currentThread()); + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else if (d->rawMode) { + if (mainThread) + fe = loadRaw(d, req); +#ifndef QT_NO_FONTCONFIG + } else if (X11->has_fontconfig) { + fe = loadFc(d, script, req); +#endif + } else if (mainThread && qt_is_gui_used) { + fe = loadXlfd(d->screen, script, req); + } + if (!fe) { + fe = new QFontEngineBox(req.pixelSize); + fe->fontDef = QFontDef(); + } + } + if (fe->symbol || (d->request.styleStrategy & QFont::NoFontMerging)) { + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (!d->engineData->engines[i]) { + d->engineData->engines[i] = fe; + fe->ref.ref(); + } + } + } else { + d->engineData->engines[script] = fe; + fe->ref.ref(); + } + QFontCache::instance()->insertEngine(key, fe); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ +#if defined(QT_NO_FONTCONFIG) + return; +#else + if (!X11->has_fontconfig) + return; + + FcConfig *config = FcConfigGetCurrent(); + if (!config) + return; + + FcFontSet *set = FcConfigGetFonts(config, FcSetApplication); + if (!set) { + FcConfigAppFontAddFile(config, (const FcChar8 *)":/non-existent"); + set = FcConfigGetFonts(config, FcSetApplication); // try again + if (!set) + return; + } + + QString fileNameForQuery = fnt->fileName; +#if FC_VERSION < 20402 + QTemporaryFile tmp; + + if (!fnt->data.isEmpty()) { + if (!tmp.open()) + return; + tmp.write(fnt->data); + tmp.flush(); + fileNameForQuery = tmp.fileName(); + } +#endif + + int id = 0; + FcBlanks *blanks = FcConfigGetBlanks(0); + int count = 0; + + QStringList families; + QFontDatabasePrivate *db = privateDb(); + + FcPattern *pattern = 0; + do { + pattern = queryFont((const FcChar8 *)QFile::encodeName(fileNameForQuery).constData(), + fnt->data, id, blanks, &count); + if (!pattern) + return; + + FcPatternDel(pattern, FC_FILE); + FcPatternAddString(pattern, FC_FILE, (const FcChar8 *)fnt->fileName.toUtf8().constData()); + + FcChar8 *fam = 0, *familylang = 0; + int i, n = 0; + for (i = 0; ; i++) { + if (FcPatternGetString(pattern, FC_FAMILYLANG, i, &familylang) != FcResultMatch) + break; + QString familyLang = QString::fromUtf8((const char *) familylang); + if (familyLang.compare(db->systemLang, Qt::CaseInsensitive) == 0) { + n = i; + break; + } + } + + if (FcPatternGetString(pattern, FC_FAMILY, n, &fam) == FcResultMatch) { + QString family = QString::fromUtf8(reinterpret_cast<const char *>(fam)); + families << family; + } + + if (!FcFontSetAdd(set, pattern)) + return; + + ++id; + } while (pattern && id < count); + + fnt->families = families; +#endif +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + FcConfigAppFontClear(0); + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +#endif +} + +bool QFontDatabase::removeAllApplicationFonts() +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + FcConfigAppFontClear(0); + db->applicationFonts.clear(); + db->invalidate(); + return true; +#endif +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + return X11->has_fontconfig; +#endif +} + +QString QFontDatabase::resolveFontFamilyAlias(const QString &family) +{ +#if defined(QT_NO_FONTCONFIG) + return family; +#else + FcPattern *pattern = FcPatternCreate(); + if (!pattern) + return family; + + FcPatternAddString(pattern, FC_FAMILY, (const FcChar8 *) family.toUtf8().data()); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcChar8 *familyAfterSubstitution; + FcPatternGetString(pattern, FC_FAMILY, 0, &familyAfterSubstitution); + QString resolved = QString::fromUtf8((const char *) familyAfterSubstitution); + FcPatternDestroy(pattern); + + return resolved; +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp new file mode 100644 index 0000000000..2f76cc615b --- /dev/null +++ b/src/gui/text/qfontengine.cpp @@ -0,0 +1,1684 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qdebug.h> +#include <private/qfontengine_p.h> + +#include "qbitmap.h" +#include "qpainter.h" +#include "qpainterpath.h" +#include "qvarlengtharray.h" +#include <qmath.h> +#include <qendian.h> +#include <private/qharfbuzz_p.h> + +QT_BEGIN_NAMESPACE + +static inline bool qtransform_equals_no_translate(const QTransform &a, const QTransform &b) +{ + if (a.type() <= QTransform::TxTranslate && b.type() <= QTransform::TxTranslate) { + return true; + } else { + // We always use paths for perspective text anyway, so no + // point in checking the full matrix... + Q_ASSERT(a.type() < QTransform::TxProject); + Q_ASSERT(b.type() < QTransform::TxProject); + + return a.m11() == b.m11() + && a.m12() == b.m12() + && a.m21() == b.m21() + && a.m22() == b.m22(); + } +} + +// Harfbuzz helper functions + +static HB_Bool hb_stringToGlyphs(HB_Font font, const HB_UChar16 *string, hb_uint32 length, HB_Glyph *glyphs, hb_uint32 *numGlyphs, HB_Bool rightToLeft) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(*numGlyphs); + + QTextEngine::ShaperFlags shaperFlags(QTextEngine::GlyphIndicesOnly); + if (rightToLeft) + shaperFlags |= QTextEngine::RightToLeft; + + int nGlyphs = *numGlyphs; + bool result = fe->stringToCMap(reinterpret_cast<const QChar *>(string), length, &qglyphs, &nGlyphs, shaperFlags); + *numGlyphs = nGlyphs; + if (!result) + return false; + + for (hb_uint32 i = 0; i < *numGlyphs; ++i) + glyphs[i] = qglyphs.glyphs[i]; + + return true; +} + +static void hb_getAdvances(HB_Font font, const HB_Glyph *glyphs, hb_uint32 numGlyphs, HB_Fixed *advances, int flags) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(numGlyphs); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + qglyphs.glyphs[i] = glyphs[i]; + + fe->recalcAdvances(&qglyphs, flags & HB_ShaperFlag_UseDesignMetrics ? QFlags<QTextEngine::ShaperFlag>(QTextEngine::DesignMetrics) : QFlags<QTextEngine::ShaperFlag>(0)); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + advances[i] = qglyphs.advances_x[i].value(); +} + +static HB_Bool hb_canRender(HB_Font font, const HB_UChar16 *string, hb_uint32 length) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->canRender(reinterpret_cast<const QChar *>(string), length); +} + +static void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + glyph_metrics_t m = fe->boundingBox(glyph); + metrics->x = m.x.value(); + metrics->y = m.y.value(); + metrics->width = m.width.value(); + metrics->height = m.height.value(); + metrics->xOffset = m.xoff.value(); + metrics->yOffset = m.yoff.value(); +} + +static HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric) +{ + if (metric == HB_FontAscent) { + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->ascent().value(); + } + return 0; +} + +HB_Error QFontEngine::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + Q_UNUSED(glyph) + Q_UNUSED(flags) + Q_UNUSED(point) + Q_UNUSED(xpos) + Q_UNUSED(ypos) + Q_UNUSED(nPoints) + return HB_Err_Not_Covered; +} + +static HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->getPointInOutline(glyph, flags, point, xpos, ypos, nPoints); +} + +static const HB_FontClass hb_fontClass = { + hb_stringToGlyphs, hb_getAdvances, hb_canRender, hb_getPointInOutline, + hb_getGlyphMetrics, hb_getFontMetric +}; + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ + QFontEngine *fe = (QFontEngine *)font; + if (!fe->getSfntTableData(tableTag, buffer, length)) + return HB_Err_Invalid_Argument; + return HB_Err_Ok; +} + +// QFontEngine + +QFontEngine::QFontEngine() + : QObject() +{ + ref = 0; + cache_count = 0; + fsType = 0; + symbol = false; + memset(&hbFont, 0, sizeof(hbFont)); + hbFont.klass = &hb_fontClass; + hbFont.userData = this; + + hbFace = 0; + glyphFormat = -1; +} + +QFontEngine::~QFontEngine() +{ + m_glyphCaches.clear(); + qHBFreeFace(hbFace); +} + +QFixed QFontEngine::lineThickness() const +{ + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + int lw = score / 700; + + // looks better with thicker line for small pointsizes + if (lw < 2 && score >= 1050) lw = 2; + if (lw == 0) lw = 1; + + return lw; +} + +QFixed QFontEngine::underlinePosition() const +{ + return ((lineThickness() * 2) + 3) / 6; +} + +HB_Font QFontEngine::harfbuzzFont() const +{ + if (!hbFont.x_ppem) { + QFixed emSquare = emSquareSize(); + hbFont.x_ppem = fontDef.pixelSize; + hbFont.y_ppem = fontDef.pixelSize * fontDef.stretch / 100; + hbFont.x_scale = (QFixed(hbFont.x_ppem * (1 << 16)) / emSquare).value(); + hbFont.y_scale = (QFixed(hbFont.y_ppem * (1 << 16)) / emSquare).value(); + } + return &hbFont; +} + +HB_Face QFontEngine::harfbuzzFace() const +{ + if (!hbFace) { + hbFace = qHBNewFace(const_cast<QFontEngine *>(this), hb_getSFntTable); + Q_CHECK_PTR(hbFace); + } + return hbFace; +} + +glyph_metrics_t QFontEngine::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + glyph_metrics_t metrics = boundingBox(glyph); + + if (matrix.type() > QTransform::TxTranslate) { + return metrics.transformed(matrix); + } + return metrics; +} + +QFixed QFontEngine::xHeight() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast<QFontEngine *>(this)->boundingBox(glyphs.glyphs[0]); + return bb.height; +} + +QFixed QFontEngine::averageCharWidth() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast<QFontEngine *>(this)->boundingBox(glyphs.glyphs[0]); + return bb.xoff; +} + + +void QFontEngine::getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray<glyph_t> &glyphs_out, QVarLengthArray<QFixedPoint> &positions) +{ + QFixed xpos; + QFixed ypos; + + const bool transform = matrix.m11() != 1. + || matrix.m12() != 0. + || matrix.m21() != 0. + || matrix.m22() != 1.; + if (!transform) { + xpos = QFixed::fromReal(matrix.dx()); + ypos = QFixed::fromReal(matrix.dy()); + } + + int current = 0; + if (flags & QTextItem::RightToLeft) { + int i = glyphs.numGlyphs; + int totalKashidas = 0; + while(i--) { + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + totalKashidas += glyphs.justifications[i].nKashidas; + } + positions.resize(glyphs.numGlyphs+totalKashidas); + glyphs_out.resize(glyphs.numGlyphs+totalKashidas); + + i = 0; + while(i < glyphs.numGlyphs) { + if (glyphs.attributes[i].dontPrint) { + ++i; + continue; + } + xpos -= glyphs.advances_x[i]; + ypos -= glyphs.advances_y[i]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = glyphs.glyphs[i]; + ++current; + if (glyphs.justifications[i].nKashidas) { + QChar ch(0x640); // Kashida character + QGlyphLayoutArray<8> g; + int nglyphs = 7; + stringToCMap(&ch, 1, &g, &nglyphs, 0); + for (uint k = 0; k < glyphs.justifications[i].nKashidas; ++k) { + xpos -= g.advances_x[0]; + ypos -= g.advances_y[0]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = g.glyphs[0]; + ++current; + } + } else { + xpos -= QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } + ++i; + } + } else { + positions.resize(glyphs.numGlyphs); + glyphs_out.resize(glyphs.numGlyphs); + int i = 0; + if (!transform) { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + positions[current].x = xpos + glyphs.offsets[i].x; + positions[current].y = ypos + glyphs.offsets[i].y; + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } else { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + positions[current].x = QFixed::fromReal(gpos.x()); + positions[current].y = QFixed::fromReal(gpos.y()); + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } + } + positions.resize(current); + glyphs_out.resize(current); + Q_ASSERT(positions.size() == glyphs_out.size()); +} + +void QFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + glyph_metrics_t gi = boundingBox(glyph); + bool isValid = gi.isValid(); + if (leftBearing != 0) + *leftBearing = isValid ? gi.x.toReal() : 0.0; + if (rightBearing != 0) + *rightBearing = isValid ? (gi.xoff - gi.x - gi.width).toReal() : 0.0; +} + +glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + glyph_metrics_t bb = boundingBox(glyphs.glyphs[i]); + QFixed x = overall.xoff + glyphs.offsets[i].x + bb.x; + QFixed y = overall.yoff + glyphs.offsets[i].y + bb.y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + bb.width); + ymax = qMax(ymax, y + bb.height); + overall.xoff += bb.xoff; + overall.yoff += bb.yoff; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + + +void QFontEngine::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + addGlyphsToPath(positioned_glyphs.data(), positions.data(), positioned_glyphs.size(), path, flags); +} + +#define GRID(x, y) grid[(y)*(w+1) + (x)] +#define SET(x, y) (*(image_data + (y)*bpl + ((x) >> 3)) & (0x80 >> ((x) & 7))) + +enum { EdgeRight = 0x1, + EdgeDown = 0x2, + EdgeLeft = 0x4, + EdgeUp = 0x8 +}; + +static void collectSingleContour(qreal x0, qreal y0, uint *grid, int x, int y, int w, int h, QPainterPath *path) +{ + Q_UNUSED(h); + + path->moveTo(x + x0, y + y0); + while (GRID(x, y)) { + if (GRID(x, y) & EdgeRight) { + while (GRID(x, y) & EdgeRight) { + GRID(x, y) &= ~EdgeRight; + ++x; + } + Q_ASSERT(x <= w); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeDown) { + while (GRID(x, y) & EdgeDown) { + GRID(x, y) &= ~EdgeDown; + ++y; + } + Q_ASSERT(y <= h); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeLeft) { + while (GRID(x, y) & EdgeLeft) { + GRID(x, y) &= ~EdgeLeft; + --x; + } + Q_ASSERT(x >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeUp) { + while (GRID(x, y) & EdgeUp) { + GRID(x, y) &= ~EdgeUp; + --y; + } + Q_ASSERT(y >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + } + path->closeSubpath(); +} + +Q_GUI_EXPORT void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path) +{ + uint *grid = new uint[(w+1)*(h+1)]; + // set up edges + for (int y = 0; y <= h; ++y) { + for (int x = 0; x <= w; ++x) { + bool topLeft = (x == 0)|(y == 0) ? false : SET(x - 1, y - 1); + bool topRight = (x == w)|(y == 0) ? false : SET(x, y - 1); + bool bottomLeft = (x == 0)|(y == h) ? false : SET(x - 1, y); + bool bottomRight = (x == w)|(y == h) ? false : SET(x, y); + + GRID(x, y) = 0; + if ((!topRight) & bottomRight) + GRID(x, y) |= EdgeRight; + if ((!bottomRight) & bottomLeft) + GRID(x, y) |= EdgeDown; + if ((!bottomLeft) & topLeft) + GRID(x, y) |= EdgeLeft; + if ((!topLeft) & topRight) + GRID(x, y) |= EdgeUp; + } + } + + // collect edges + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (!GRID(x, y)) + continue; + // found start of a contour, follow it + collectSingleContour(x0, y0, grid, x, y, w, h, path); + } + } + delete [] grid; +} + +#undef GRID +#undef SET + + +void QFontEngine::addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +// TODO what to do with 'flags' ?? + Q_UNUSED(flags); + QFixed advanceX = QFixed::fromReal(x); + QFixed advanceY = QFixed::fromReal(y); + for (int i=0; i < glyphs.numGlyphs; ++i) { + glyph_metrics_t metrics = boundingBox(glyphs.glyphs[i]); + if (metrics.width.value() == 0 || metrics.height.value() == 0) { + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + continue; + } + const QImage alphaMask = alphaMapForGlyph(glyphs.glyphs[i]); + + const int w = alphaMask.width(); + const int h = alphaMask.height(); + const int srcBpl = alphaMask.bytesPerLine(); + QImage bitmap; + if (alphaMask.depth() == 1) { + bitmap = alphaMask; + } else { + bitmap = QImage(w, h, QImage::Format_Mono); + const uchar *imageData = alphaMask.bits(); + const int destBpl = bitmap.bytesPerLine(); + uchar *bitmapData = bitmap.bits(); + + for (int yi = 0; yi < h; ++yi) { + const uchar *src = imageData + yi*srcBpl; + uchar *dst = bitmapData + yi*destBpl; + for (int xi = 0; xi < w; ++xi) { + const int byte = xi / 8; + const int bit = xi % 8; + if (bit == 0) + dst[byte] = 0; + if (src[xi]) + dst[byte] |= 128 >> bit; + } + } + } + const uchar *bitmap_data = bitmap.bits(); + QFixedPoint offset = glyphs.offsets[i]; + advanceX += offset.x; + advanceY += offset.y; + qt_addBitmapToPath((advanceX + metrics.x).toReal(), (advanceY + metrics.y).toReal(), bitmap_data, bitmap.bytesPerLine(), w, h, path); + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + } +} + +void QFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + qreal x = positions[0].x.toReal(); + qreal y = positions[0].y.toReal(); + QVarLengthGlyphLayoutArray g(nGlyphs); + + for (int i = 0; i < nGlyphs; ++i) { + g.glyphs[i] = glyphs[i]; + if (i < nGlyphs - 1) { + g.advances_x[i] = positions[i+1].x - positions[i].x; + g.advances_y[i] = positions[i+1].y - positions[i].y; + } else { + g.advances_x[i] = QFixed::fromReal(maxCharWidth()); + g.advances_y[i] = 0; + } + } + + addBitmapFontToPath(x, y, g, path, flags); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/) +{ + // For font engines don't support subpixel positioning + return alphaMapForGlyph(glyph); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, const QTransform &t) +{ + QImage i = alphaMapForGlyph(glyph); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Indexed8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) +{ + if (! supportsSubPixelPositions()) + return alphaMapForGlyph(glyph, t); + + QImage i = alphaMapForGlyph(glyph, subPixelPosition); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Indexed8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/, int /* margin */, const QTransform &t) +{ + QImage alphaMask = alphaMapForGlyph(glyph, t); + QImage rgbMask(alphaMask.width(), alphaMask.height(), QImage::Format_RGB32); + + QVector<QRgb> colorTable = alphaMask.colorTable(); + for (int y=0; y<alphaMask.height(); ++y) { + uint *dst = (uint *) rgbMask.scanLine(y); + uchar *src = (uchar *) alphaMask.scanLine(y); + for (int x=0; x<alphaMask.width(); ++x) { + int val = qAlpha(colorTable.at(src[x])); + dst[x] = qRgb(val, val, val); + } + } + + return rgbMask; +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph) +{ + glyph_metrics_t gm = boundingBox(glyph); + int glyph_x = qFloor(gm.x.toReal()); + int glyph_y = qFloor(gm.y.toReal()); + int glyph_width = qCeil((gm.x + gm.width).toReal()) - glyph_x; + int glyph_height = qCeil((gm.y + gm.height).toReal()) - glyph_y; + + if (glyph_width <= 0 || glyph_height <= 0) + return QImage(); + QFixedPoint pt; + pt.x = -glyph_x; + pt.y = -glyph_y; // the baseline + QPainterPath path; + QImage im(glyph_width + 4, glyph_height, QImage::Format_ARGB32_Premultiplied); + im.fill(Qt::transparent); + QPainter p(&im); + p.setRenderHint(QPainter::Antialiasing); + addGlyphsToPath(&glyph, &pt, 1, &path, 0); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawPath(path); + p.end(); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uchar *dst = (uchar *) indexed.scanLine(y); + uint *src = (uint *) im.scanLine(y); + for (int x=0; x<im.width(); ++x) + dst[x] = qAlpha(src[x]); + } + + return indexed; +} + +void QFontEngine::removeGlyphFromCache(glyph_t) +{ +} + +QFontEngine::Properties QFontEngine::properties() const +{ + Properties p; + QByteArray psname = QFontEngine::convertToPostscriptFontFamilyName(fontDef.family.toUtf8()); + psname += '-'; + psname += QByteArray::number(fontDef.style); + psname += '-'; + psname += QByteArray::number(fontDef.weight); + + p.postscriptName = psname; + p.ascent = ascent(); + p.descent = descent(); + p.leading = leading(); + p.emSquare = p.ascent; + p.boundingBox = QRectF(0, -p.ascent.toReal(), maxCharWidth(), (p.ascent + p.descent).toReal()); + p.italicAngle = 0; + p.capHeight = p.ascent; + p.lineWidth = lineThickness(); + return p; +} + +void QFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + *metrics = boundingBox(glyph); + QFixedPoint p; + p.x = 0; + p.y = 0; + addGlyphsToPath(&glyph, &p, 1, path, QFlag(0)); +} + +QByteArray QFontEngine::getSfntTable(uint tag) const +{ + QByteArray table; + uint len = 0; + if (!getSfntTableData(tag, 0, &len)) + return table; + if (!len) + return table; + table.resize(len); + if (!getSfntTableData(tag, reinterpret_cast<uchar *>(table.data()), &len)) + return QByteArray(); + return table; +} + +void QFontEngine::setGlyphCache(void *key, QFontEngineGlyphCache *data) +{ + Q_ASSERT(data); + + GlyphCacheEntry entry; + entry.context = key; + entry.cache = data; + if (m_glyphCaches.contains(entry)) + return; + + // Limit the glyph caches to 4. This covers all 90 degree rotations and limits + // memory use when there is continuous or random rotation + if (m_glyphCaches.size() == 4) + m_glyphCaches.removeLast(); + + m_glyphCaches.push_front(entry); + +} + +QFontEngineGlyphCache *QFontEngine::glyphCache(void *key, QFontEngineGlyphCache::Type type, const QTransform &transform) const +{ + for (QLinkedList<GlyphCacheEntry>::const_iterator it = m_glyphCaches.constBegin(), end = m_glyphCaches.constEnd(); it != end; ++it) { + QFontEngineGlyphCache *c = it->cache.data(); + if (key == it->context + && type == c->cacheType() + && qtransform_equals_no_translate(c->m_transform, transform)) { + return c; + } + } + return 0; +} + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) +static inline QFixed kerning(int left, int right, const QFontEngine::KernPair *pairs, int numPairs) +{ + uint left_right = (left << 16) + right; + + left = 0, right = numPairs - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + if(pairs[middle].left_right == left_right) + return pairs[middle].adjust; + + if (pairs[middle].left_right < left_right) + left = middle + 1; + else + right = middle - 1; + } + return 0; +} + +void QFontEngine::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int numPairs = kerning_pairs.size(); + if(!numPairs) + return; + + const KernPair *pairs = kerning_pairs.constData(); + + if(flags & QTextEngine::DesignMetrics) { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs); + } else { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += qRound(kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs)); + } +} + +void QFontEngine::loadKerningPairs(QFixed scalingFactor) +{ + kerning_pairs.clear(); + + QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n')); + if (tab.isEmpty()) + return; + + const uchar *table = reinterpret_cast<const uchar *>(tab.constData()); + + unsigned short version = qFromBigEndian<quint16>(table); + if (version != 0) { +// qDebug("wrong version"); + return; + } + + unsigned short numTables = qFromBigEndian<quint16>(table + 2); + { + int offset = 4; + for(int i = 0; i < numTables; ++i) { + if (offset + 6 > tab.size()) { +// qDebug("offset out of bounds"); + goto end; + } + const uchar *header = table + offset; + + ushort version = qFromBigEndian<quint16>(header); + ushort length = qFromBigEndian<quint16>(header+2); + ushort coverage = qFromBigEndian<quint16>(header+4); +// qDebug("subtable: version=%d, coverage=%x",version, coverage); + if(version == 0 && coverage == 0x0001) { + if (offset + length > tab.size()) { +// qDebug("length ouf ot bounds"); + goto end; + } + const uchar *data = table + offset + 6; + + ushort nPairs = qFromBigEndian<quint16>(data); + if(nPairs * 6 + 8 > length - 6) { +// qDebug("corrupt table!"); + // corrupt table + goto end; + } + + int off = 8; + for(int i = 0; i < nPairs; ++i) { + QFontEngine::KernPair p; + p.left_right = (((uint)qFromBigEndian<quint16>(data+off)) << 16) + qFromBigEndian<quint16>(data+off+2); + p.adjust = QFixed(((int)(short)qFromBigEndian<quint16>(data+off+4))) / scalingFactor; + kerning_pairs.append(p); + off += 6; + } + } + offset += length; + } + } +end: + qSort(kerning_pairs); +// for (int i = 0; i < kerning_pairs.count(); ++i) +// qDebug() << 'i' << i << "left_right" << hex << kerning_pairs.at(i).left_right; +} + +#else +void QFontEngine::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ +} +#endif + +int QFontEngine::glyphCount() const +{ + QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p')); + if (maxpTable.size() < 6) + return 0; + return qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(maxpTable.constData() + 4)); +} + +const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize) +{ + const uchar *header = table; + if (tableSize < 4) + return 0; + + const uchar *endPtr = table + tableSize; + + // version check + if (qFromBigEndian<quint16>(header) != 0) + return 0; + + unsigned short numTables = qFromBigEndian<quint16>(header + 2); + const uchar *maps = table + 4; + if (maps + 8 * numTables > endPtr) + return 0; + + enum { + Invalid, + AppleRoman, + Symbol, + Unicode11, + Unicode, + MicrosoftUnicode, + MicrosoftUnicodeExtended + }; + + int symbolTable = -1; + int tableToUse = -1; + int score = Invalid; + for (int n = 0; n < numTables; ++n) { + const quint16 platformId = qFromBigEndian<quint16>(maps + 8 * n); + const quint16 platformSpecificId = qFromBigEndian<quint16>(maps + 8 * n + 2); + switch (platformId) { + case 0: // Unicode + if (score < Unicode && + (platformSpecificId == 0 || + platformSpecificId == 2 || + platformSpecificId == 3)) { + tableToUse = n; + score = Unicode; + } else if (score < Unicode11 && platformSpecificId == 1) { + tableToUse = n; + score = Unicode11; + } + break; + case 1: // Apple + if (score < AppleRoman && platformSpecificId == 0) { // Apple Roman + tableToUse = n; + score = AppleRoman; + } + break; + case 3: // Microsoft + switch (platformSpecificId) { + case 0: + symbolTable = n; + if (score < Symbol) { + tableToUse = n; + score = Symbol; + } + break; + case 1: + if (score < MicrosoftUnicode) { + tableToUse = n; + score = MicrosoftUnicode; + } + break; + case 0xa: + if (score < MicrosoftUnicodeExtended) { + tableToUse = n; + score = MicrosoftUnicodeExtended; + } + break; + default: + break; + } + default: + break; + } + } + if(tableToUse < 0) + return 0; + +resolveTable: + *isSymbolFont = (symbolTable > -1); + + unsigned int unicode_table = qFromBigEndian<quint32>(maps + 8*tableToUse + 4); + + if (!unicode_table || unicode_table + 8 > tableSize) + return 0; + + // get the header of the unicode table + header = table + unicode_table; + + unsigned short format = qFromBigEndian<quint16>(header); + unsigned int length; + if(format < 8) + length = qFromBigEndian<quint16>(header + 2); + else + length = qFromBigEndian<quint32>(header + 4); + + if (table + unicode_table + length > endPtr) + return 0; + *cmapSize = length; + + // To support symbol fonts that contain a unicode table for the symbol area + // we check the cmap tables and fall back to symbol font unless that would + // involve losing information from the unicode table + if (symbolTable > -1 && ((score == Unicode) || (score == Unicode11))) { + const uchar *selectedTable = table + unicode_table; + + // Check that none of the latin1 range are in the unicode table + bool unicodeTableHasLatin1 = false; + for (int uc=0x00; uc<0x100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasLatin1 = true; + break; + } + } + + // Check that at least one symbol char is in the unicode table + bool unicodeTableHasSymbols = false; + if (!unicodeTableHasLatin1) { + for (int uc=0xf000; uc<0xf100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasSymbols = true; + break; + } + } + } + + // Fall back to symbol table + if (!unicodeTableHasLatin1 && unicodeTableHasSymbols) { + tableToUse = symbolTable; + score = Symbol; + goto resolveTable; + } + } + + return table + unicode_table; +} + +quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, uint unicode) +{ + unsigned short format = qFromBigEndian<quint16>(cmap); + if (format == 0) { + if (unicode < 256) + return (int) *(cmap+6+unicode); + } else if (format == 4) { + /* some fonts come with invalid cmap tables, where the last segment + specified end = start = rangeoffset = 0xffff, delta = 0x0001 + Since 0xffff is never a valid Unicode char anyway, we just get rid of the issue + by returning 0 for 0xffff + */ + if(unicode >= 0xffff) + return 0; + quint16 segCountX2 = qFromBigEndian<quint16>(cmap + 6); + const unsigned char *ends = cmap + 14; + int i = 0; + for (; i < segCountX2/2 && qFromBigEndian<quint16>(ends + 2*i) < unicode; i++) {} + + const unsigned char *idx = ends + segCountX2 + 2 + 2*i; + quint16 startIndex = qFromBigEndian<quint16>(idx); + + if (startIndex > unicode) + return 0; + + idx += segCountX2; + qint16 idDelta = (qint16)qFromBigEndian<quint16>(idx); + idx += segCountX2; + quint16 idRangeoffset_t = (quint16)qFromBigEndian<quint16>(idx); + + quint16 glyphIndex; + if (idRangeoffset_t) { + quint16 id = qFromBigEndian<quint16>(idRangeoffset_t + 2*(unicode - startIndex) + idx); + if (id) + glyphIndex = (idDelta + id) % 0x10000; + else + glyphIndex = 0; + } else { + glyphIndex = (idDelta + unicode) % 0x10000; + } + return glyphIndex; + } else if (format == 6) { + quint16 tableSize = qFromBigEndian<quint16>(cmap + 2); + + quint16 firstCode6 = qFromBigEndian<quint16>(cmap + 6); + if (unicode < firstCode6) + return 0; + + quint16 entryCount6 = qFromBigEndian<quint16>(cmap + 8); + if (entryCount6 * 2 + 10 > tableSize) + return 0; + + quint16 sentinel6 = firstCode6 + entryCount6; + if (unicode >= sentinel6) + return 0; + + quint16 entryIndex6 = unicode - firstCode6; + return qFromBigEndian<quint16>(cmap + 10 + (entryIndex6 * 2)); + } else if (format == 12) { + quint32 nGroups = qFromBigEndian<quint32>(cmap + 12); + + cmap += 16; // move to start of groups + + int left = 0, right = nGroups - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + quint32 startCharCode = qFromBigEndian<quint32>(cmap + 12*middle); + if(unicode < startCharCode) + right = middle - 1; + else { + quint32 endCharCode = qFromBigEndian<quint32>(cmap + 12*middle + 4); + if(unicode <= endCharCode) + return qFromBigEndian<quint32>(cmap + 12*middle + 8) + unicode - startCharCode; + left = middle + 1; + } + } + } else { + qDebug("cmap table of format %d not implemented", format); + } + + return 0; +} + +QByteArray QFontEngine::convertToPostscriptFontFamilyName(const QByteArray &family) +{ + QByteArray f = family; + f.replace(' ', ""); + f.replace('(', ""); + f.replace(')', ""); + f.replace('<', ""); + f.replace('>', ""); + f.replace('[', ""); + f.replace(']', ""); + f.replace('{', ""); + f.replace('}', ""); + f.replace('/', ""); + f.replace('%', ""); + return f; +} + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QVector<QRgb>, qt_grayPalette, { + x->resize(256); + QRgb *it = x->data(); + for (int i = 0; i < x->size(); ++i, ++it) + *it = 0xff000000 | i | (i<<8) | (i<<16); +}) + +const QVector<QRgb> &QFontEngine::grayPalette() +{ + return *qt_grayPalette(); +} + +QFixed QFontEngine::lastRightBearing(const QGlyphLayout &glyphs, bool round) +{ + if (glyphs.numGlyphs >= 1) { + glyph_t glyph = glyphs.glyphs[glyphs.numGlyphs - 1]; + glyph_metrics_t gi = boundingBox(glyph); + if (gi.isValid()) + return round ? QFixed(qRound(gi.xoff - gi.x - gi.width)) + : QFixed(gi.xoff - gi.x - gi.width); + } + return 0; +} + +// ------------------------------------------------------------------ +// The box font engine +// ------------------------------------------------------------------ + +QFontEngineBox::QFontEngineBox(int size) + : _size(size) +{ + cache_cost = sizeof(QFontEngineBox); +} + +QFontEngineBox::~QFontEngineBox() +{ +} + +bool QFontEngineBox::stringToCMap(const QChar *, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + for (int i = 0; i < len; i++) { + glyphs->glyphs[i] = 0; + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } + + *nglyphs = len; + glyphs->numGlyphs = len; + return true; +} + +void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; i++) { + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } +} + +void QFontEngineBox::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + QSize s(_size - 3, _size - 3); + for (int k = 0; k < positions.size(); k++) + path->addRect(QRectF(positions[k].toPointF(), s)); +} + +glyph_metrics_t QFontEngineBox::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + overall.width = _size*glyphs.numGlyphs; + overall.height = _size; + overall.xoff = overall.width; + return overall; +} + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) +void QFontEngineBox::draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &ti) +{ + if (!ti.glyphs.numGlyphs) + return; + + // any fixes here should probably also be done in QPaintEnginePrivate::drawBoxTextItem + QSize s(_size - 3, _size - 3); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + + QPainter *painter = p->painter(); + painter->save(); + painter->setBrush(Qt::NoBrush); + QPen pen = painter->pen(); + pen.setWidthF(lineThickness().toReal()); + painter->setPen(pen); + for (int k = 0; k < positions.size(); k++) + painter->drawRect(QRectF(positions[k].toPointF(), s)); + painter->restore(); +} +#endif + +glyph_metrics_t QFontEngineBox::boundingBox(glyph_t) +{ + return glyph_metrics_t(0, -_size, _size, _size, _size, 0); +} + + + +QFixed QFontEngineBox::ascent() const +{ + return _size; +} + +QFixed QFontEngineBox::descent() const +{ + return 0; +} + +QFixed QFontEngineBox::leading() const +{ + QFixed l = _size * QFixed::fromReal(qreal(0.15)); + return l.ceil(); +} + +qreal QFontEngineBox::maxCharWidth() const +{ + return _size; +} + +#ifdef Q_WS_X11 +int QFontEngineBox::cmap() const +{ + return -1; +} +#endif + +const char *QFontEngineBox::name() const +{ + return "null"; +} + +bool QFontEngineBox::canRender(const QChar *, int) +{ + return true; +} + +QFontEngine::Type QFontEngineBox::type() const +{ + return Box; +} + +QImage QFontEngineBox::alphaMapForGlyph(glyph_t) +{ + QImage image(_size, _size, QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + image.setColorTable(colors); + image.fill(0); + + // can't use qpainter for index8; so use setPixel to draw our rectangle. + for (int i=2; i <= _size-3; ++i) { + image.setPixel(i, 2, 255); + image.setPixel(i, _size-3, 255); + image.setPixel(2, i, 255); + image.setPixel(_size-3, i, 255); + } + return image; +} + +// ------------------------------------------------------------------ +// Multi engine +// ------------------------------------------------------------------ + +static inline uchar highByte(glyph_t glyph) +{ return glyph >> 24; } + +// strip high byte from glyph +static inline glyph_t stripped(glyph_t glyph) +{ return glyph & 0x00ffffff; } + +QFontEngineMulti::QFontEngineMulti(int engineCount) +{ + engines.fill(0, engineCount); + cache_cost = 0; +} + +QFontEngineMulti::~QFontEngineMulti() +{ + for (int i = 0; i < engines.size(); ++i) { + QFontEngine *fontEngine = engines.at(i); + if (fontEngine) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + } + } +} + +bool QFontEngineMulti::stringToCMap(const QChar *str, int len, + QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const +{ + int ng = *nglyphs; + if (!engine(0)->stringToCMap(str, len, glyphs, &ng, flags)) + return false; + + int glyph_pos = 0; + for (int i = 0; i < len; ++i) { + bool surrogate = (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 && i < len-1 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + + if (glyphs->glyphs[glyph_pos] == 0 && str[i].category() != QChar::Separator_Line) { + QGlyphLayoutInstance tmp = glyphs->instance(glyph_pos); + for (int x = 1; x < engines.size(); ++x) { + QFontEngine *engine = engines.at(x); + if (!engine) { + const_cast<QFontEngineMulti *>(this)->loadEngine(x); + engine = engines.at(x); + } + Q_ASSERT(engine != 0); + if (engine->type() == Box) + continue; + glyphs->advances_x[glyph_pos] = glyphs->advances_y[glyph_pos] = 0; + glyphs->offsets[glyph_pos] = QFixedPoint(); + int num = 2; + QGlyphLayout offs = glyphs->mid(glyph_pos, num); + engine->stringToCMap(str + i, surrogate ? 2 : 1, &offs, &num, flags); + Q_ASSERT(num == 1); // surrogates only give 1 glyph + if (glyphs->glyphs[glyph_pos]) { + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos] |= (x << 24); + break; + } + } + // ensure we use metrics from the 1st font when we use the fallback image. + if (!glyphs->glyphs[glyph_pos]) { + glyphs->setInstance(glyph_pos, tmp); + } + } + if (surrogate) + ++i; + ++glyph_pos; + } + + *nglyphs = ng; + glyphs->numGlyphs = ng; + return true; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs <= 0) + return glyph_metrics_t(); + + glyph_metrics_t overall; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + return overall; +} + +void QFontEngineMulti::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + int which = highByte(glyph); + engine(which)->getGlyphBearings(stripped(glyph), leftBearing, rightBearing); +} + +void QFontEngineMulti::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (glyphs.numGlyphs <= 0) + return; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + if (flags & QTextItem::RightToLeft) { + for (int gl = 0; gl < glyphs.numGlyphs; gl++) { + x += glyphs.advances_x[gl].toReal(); + y += glyphs.advances_y[gl].toReal(); + } + } + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + if (!(flags & QTextItem::RightToLeft)) { + for (i = start; i < end; ++i) { + x += glyphs.advances_x[i].toReal(); + y += glyphs.advances_y[i].toReal(); + } + } + + // change engine + start = end; + which = e; + } + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; +} + +void QFontEngineMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +void QFontEngineMulti::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(glyph_t glyph) +{ + const int which = highByte(glyph); + Q_ASSERT(which < engines.size()); + return engine(which)->boundingBox(stripped(glyph)); +} + +QFixed QFontEngineMulti::ascent() const +{ return engine(0)->ascent(); } + +QFixed QFontEngineMulti::descent() const +{ return engine(0)->descent(); } + +QFixed QFontEngineMulti::leading() const +{ + return engine(0)->leading(); +} + +QFixed QFontEngineMulti::xHeight() const +{ + return engine(0)->xHeight(); +} + +QFixed QFontEngineMulti::averageCharWidth() const +{ + return engine(0)->averageCharWidth(); +} + +QFixed QFontEngineMulti::lineThickness() const +{ + return engine(0)->lineThickness(); +} + +QFixed QFontEngineMulti::underlinePosition() const +{ + return engine(0)->underlinePosition(); +} + +qreal QFontEngineMulti::maxCharWidth() const +{ + return engine(0)->maxCharWidth(); +} + +qreal QFontEngineMulti::minLeftBearing() const +{ + return engine(0)->minLeftBearing(); +} + +qreal QFontEngineMulti::minRightBearing() const +{ + return engine(0)->minRightBearing(); +} + +bool QFontEngineMulti::canRender(const QChar *string, int len) +{ + if (engine(0)->canRender(string, len)) + return true; + + QVarLengthGlyphLayoutArray glyphs(len); + int nglyphs = len; + if (stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly) == false) { + glyphs.resize(nglyphs); + stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + } + + bool allExist = true; + for (int i = 0; i < nglyphs; i++) { + if (!glyphs.glyphs[i]) { + allExist = false; + break; + } + } + + return allExist; +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t) +{ + Q_ASSERT(false); + return QImage(); +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_coretext.mm b/src/gui/text/qfontengine_coretext.mm new file mode 100644 index 0000000000..20b37300fa --- /dev/null +++ b/src/gui/text/qfontengine_coretext.mm @@ -0,0 +1,843 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_coretext_p.h" + +#include <QtCore/qendian.h> +#include <QtCore/qsettings.h> + +#include <private/qimage_p.h> + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +QT_BEGIN_NAMESPACE + +static float SYNTHETIC_ITALIC_SKEW = tanf(14 * acosf(0) / 90); + +static void loadAdvancesForGlyphs(CTFontRef ctfont, + QVarLengthArray<CGGlyph> &cgGlyphs, + QGlyphLayout *glyphs, int len, + QTextEngine::ShaperFlags flags, + const QFontDef &fontDef) +{ + Q_UNUSED(flags); + QVarLengthArray<CGSize> advances(len); + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, cgGlyphs.data(), advances.data(), len); + + for (int i = 0; i < len; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + continue; + glyphs->advances_x[i] = QFixed::fromReal(advances[i].width); + glyphs->advances_y[i] = QFixed::fromReal(advances[i].height); + } + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + for (int i = 0; i < len; ++i) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + CTFontSymbolicTraits symbolicTraits = 0; + if (fontDef.weight >= QFont::Bold) + symbolicTraits |= kCTFontBoldTrait; + switch (fontDef.style) { + case QFont::StyleNormal: + break; + case QFont::StyleItalic: + case QFont::StyleOblique: + symbolicTraits |= kCTFontItalicTrait; + break; + } + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + QCFType<CTFontDescriptorRef> descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize); + QCFType<CTFontRef> baseFont = CTFontCreateWithFontDescriptor(descriptor, fontDef.pixelSize, &transform); + ctfont = CTFontCreateCopyWithSymbolicTraits(baseFont, fontDef.pixelSize, &transform, symbolicTraits, symbolicTraits); + + // CTFontCreateCopyWithSymbolicTraits returns NULL if we ask for a trait that does + // not exist for the given font. (for example italic) + if (ctfont == 0) { + ctfont = baseFont; + CFRetain(ctfont); + } + init(kerning); +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + ctfont = CTFontCreateWithGraphicsFont(cgFontRef, fontDef.pixelSize, &transform, NULL); + init(kerning); +} + +QCoreTextFontEngineMulti::~QCoreTextFontEngineMulti() +{ + CFRelease(ctfont); +} + +void QCoreTextFontEngineMulti::init(bool kerning) +{ + Q_ASSERT(ctfont != NULL); + attributeDict = CFDictionaryCreateMutable(0, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attributeDict, NSFontAttributeName, ctfont); + if (!kerning) { + float zero = 0.0; + QCFType<CFNumberRef> noKern = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &zero); + CFDictionaryAddValue(attributeDict, kCTKernAttributeName, noKern); + } + + QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef); + fe->ref.ref(); + engines.append(fe); +} + +uint QCoreTextFontEngineMulti::fontIndexForFont(CTFontRef font) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (CFEqual(engineAt(i)->ctfont, font)) + return i; + } + + QCoreTextFontEngineMulti *that = const_cast<QCoreTextFontEngineMulti *>(this); + QCoreTextFontEngine *fe = new QCoreTextFontEngine(font, fontDef); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *) const +{ + QCFType<CFStringRef> cfstring = CFStringCreateWithCharactersNoCopy(0, + reinterpret_cast<const UniChar *>(str), + len, kCFAllocatorNull); + QCFType<CFAttributedStringRef> attributedString = CFAttributedStringCreate(0, cfstring, attributeDict); + QCFType<CTTypesetterRef> typeSetter; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (flags & QTextEngine::RightToLeft) { + const void *optionKeys[] = { kCTTypesetterOptionForcedEmbeddingLevel }; + const short rtlForcedEmbeddingLevelValue = 1; + const void *rtlOptionValues[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &rtlForcedEmbeddingLevelValue) }; + QCFType<CFDictionaryRef> options = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, 1, + &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + typeSetter = CTTypesetterCreateWithAttributedStringAndOptions(attributedString, options); + } else +#endif + typeSetter = CTTypesetterCreateWithAttributedString(attributedString); + + CFRange range = {0, 0}; + QCFType<CTLineRef> line = CTTypesetterCreateLine(typeSetter, range); + CFArrayRef array = CTLineGetGlyphRuns(line); + uint arraySize = CFArrayGetCount(array); + glyph_t *outGlyphs = glyphs->glyphs; + HB_GlyphAttributes *outAttributes = glyphs->attributes; + QFixed *outAdvances_x = glyphs->advances_x; + QFixed *outAdvances_y = glyphs->advances_y; + glyph_t *initialGlyph = outGlyphs; + + if (arraySize == 0) { + // CoreText failed to shape the text we gave it, so we assume one glyph + // per character and build a list of invalid glyphs with zero advance + *nglyphs = len; + for (int i = 0; i < len; ++i) { + outGlyphs[i] = 0; + if (logClusters) + logClusters[i] = i; + outAdvances_x[i] = QFixed(); + outAdvances_y[i] = QFixed(); + outAttributes[i].clusterStart = true; + } + return true; + } + + const bool rtl = (CTRunGetStatus(static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft); + + bool outOBounds = false; + for (uint i = 0; i < arraySize; ++i) { + CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, rtl ? (arraySize - 1 - i) : i)); + CFIndex glyphCount = CTRunGetGlyphCount(run); + if (glyphCount == 0) + continue; + + Q_ASSERT((CTRunGetStatus(run) & kCTRunStatusRightToLeft) == rtl); + CFRange stringRange = CTRunGetStringRange(run); + UniChar endGlyph = CFStringGetCharacterAtIndex(cfstring, stringRange.location + stringRange.length - 1); + bool endWithPDF = QChar::direction(endGlyph) == QChar::DirPDF; + if (endWithPDF) + glyphCount++; + + if (!outOBounds && outGlyphs + glyphCount - initialGlyph > *nglyphs) { + outOBounds = true; + } + if (!outOBounds) { + CFDictionaryRef runAttribs = CTRunGetAttributes(run); + //NSLog(@"Dictionary %@", runAttribs); + if (!runAttribs) + runAttribs = attributeDict; + CTFontRef runFont = static_cast<CTFontRef>(CFDictionaryGetValue(runAttribs, NSFontAttributeName)); + const uint fontIndex = (fontIndexForFont(runFont) << 24); + //NSLog(@"Run Font Name = %@", CTFontCopyFamilyName(runFont)); + if (endWithPDF) + glyphCount--; + + QVarLengthArray<CGGlyph, 512> cgglyphs(0); + const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run); + if (!tmpGlyphs) { + cgglyphs.resize(glyphCount); + CTRunGetGlyphs(run, range, cgglyphs.data()); + tmpGlyphs = cgglyphs.constData(); + } + QVarLengthArray<CGPoint, 512> cgpoints(0); + const CGPoint *tmpPoints = CTRunGetPositionsPtr(run); + if (!tmpPoints) { + cgpoints.resize(glyphCount); + CTRunGetPositions(run, range, cgpoints.data()); + tmpPoints = cgpoints.constData(); + } + + const int rtlOffset = rtl ? (glyphCount - 1) : 0; + const int rtlSign = rtl ? -1 : 1; + + if (logClusters) { + CFRange stringRange = CTRunGetStringRange(run); + QVarLengthArray<CFIndex, 512> stringIndices(0); + const CFIndex *tmpIndices = CTRunGetStringIndicesPtr(run); + if (!tmpIndices) { + stringIndices.resize(glyphCount); + CTRunGetStringIndices(run, range, stringIndices.data()); + tmpIndices = stringIndices.constData(); + } + + const int firstGlyphIndex = outGlyphs - initialGlyph; + outAttributes[0].clusterStart = true; + + CFIndex k = 0; + CFIndex i = 0; + for (i = stringRange.location; + (i < stringRange.location + stringRange.length) && (k < glyphCount); ++i) { + if (tmpIndices[k * rtlSign + rtlOffset] == i || i == stringRange.location) { + logClusters[i] = k + firstGlyphIndex; + outAttributes[k].clusterStart = true; + ++k; + } else { + logClusters[i] = k + firstGlyphIndex - 1; + } + } + // in case of a ligature at the end, fill the remaining logcluster entries + for (;i < stringRange.location + stringRange.length; i++) { + logClusters[i] = k + firstGlyphIndex - 1; + } + } + for (CFIndex i = 0; i < glyphCount - 1; ++i) { + int idx = rtlOffset + rtlSign * i; + outGlyphs[idx] = tmpGlyphs[i] | fontIndex; + outAdvances_x[idx] = QFixed::fromReal(tmpPoints[i + 1].x - tmpPoints[i].x); + // Use negative y advance for flipped coordinate system + outAdvances_y[idx] = QFixed::fromReal(tmpPoints[i].y - tmpPoints[i + 1].y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + outAdvances_x[idx] = outAdvances_x[idx].round(); + outAdvances_y[idx] = outAdvances_y[idx].round(); + } + } + CGSize lastGlyphAdvance; + CTFontGetAdvancesForGlyphs(runFont, kCTFontHorizontalOrientation, tmpGlyphs + glyphCount - 1, &lastGlyphAdvance, 1); + + outGlyphs[rtl ? 0 : (glyphCount - 1)] = tmpGlyphs[glyphCount - 1] | fontIndex; + outAdvances_x[rtl ? 0 : (glyphCount - 1)] = + (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(lastGlyphAdvance.width).round() + : QFixed::fromReal(lastGlyphAdvance.width); + + if (endWithPDF) { + logClusters[stringRange.location + stringRange.length - 1] = glyphCount; + outGlyphs[glyphCount] = 0xFFFF; + outAdvances_x[glyphCount] = 0; + outAdvances_y[glyphCount] = 0; + outAttributes[glyphCount].clusterStart = true; + outAttributes[glyphCount].dontPrint = true; + glyphCount++; + } + } + outGlyphs += glyphCount; + outAttributes += glyphCount; + outAdvances_x += glyphCount; + outAdvances_y += glyphCount; + } + *nglyphs = (outGlyphs - initialGlyph); + return !outOBounds; +} + +bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + *nglyphs = len; + QCFType<CFStringRef> cfstring; + + QVarLengthArray<CGGlyph> cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) { + if (cgGlyphs[i]) { + glyphs->glyphs[i] = cgGlyphs[i]; + } else { + if (!cfstring) + cfstring = CFStringCreateWithCharactersNoCopy(0, reinterpret_cast<const UniChar *>(str), len, kCFAllocatorNull); + QCFType<CTFontRef> substituteFont = CTFontCreateForString(ctfont, cfstring, CFRangeMake(i, 1)); + CGGlyph substituteGlyph = 0; + CTFontGetGlyphsForCharacters(substituteFont, (const UniChar*)str + i, &substituteGlyph, 1); + if (substituteGlyph) { + const uint fontIndex = (fontIndexForFont(substituteFont) << 24); + glyphs->glyphs[i] = substituteGlyph | fontIndex; + if (!(flags & QTextEngine::GlyphIndicesOnly)) { + CGSize advance; + CTFontGetAdvancesForGlyphs(substituteFont, kCTFontHorizontalOrientation, &substituteGlyph, &advance, 1); + glyphs->advances_x[i] = QFixed::fromReal(advance.width); + glyphs->advances_y[i] = QFixed::fromReal(advance.height); + } + } + } + } + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +void QCoreTextFontEngineMulti::loadEngine(int) +{ + // Do nothing + Q_ASSERT(false); +} + +extern int qt_antialiasing_threshold; // from qapplication.cpp + +static inline CGAffineTransform transformFromFontDef(const QFontDef &fontDef) +{ + CGAffineTransform transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + return transform; +} + +QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + ctfont = font; + CFRetain(ctfont); + cgFont = CTFontCopyGraphicsFont(font, NULL); + init(); +} + +QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + cgFont = font; + // Keep reference count balanced + CFRetain(cgFont); + ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, NULL); + init(); +} + +QCoreTextFontEngine::~QCoreTextFontEngine() +{ + CFRelease(cgFont); + CFRelease(ctfont); +} + +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +int getTraitValue(CFDictionaryRef allTraits, CFStringRef trait) +{ + if (CFDictionaryContainsKey(allTraits, trait)) { + CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait); + float v = 0; + CFNumberGetValue(traitNum, kCFNumberFloatType, &v); + // the value we get from CFNumberRef is from -1.0 to 1.0 + int value = v * 500 + 500; + return value; + } + + return 0; +} + +void QCoreTextFontEngine::init() +{ + Q_ASSERT(ctfont != NULL); + Q_ASSERT(cgFont != NULL); + + QCFString family = CTFontCopyFamilyName(ctfont); + fontDef.family = family; + + synthesisFlags = 0; + CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont); + if (traits & kCTFontItalicTrait) + fontDef.style = QFont::StyleItalic; + + CFDictionaryRef allTraits = CTFontCopyTraits(ctfont); + fontDef.weight = weightFromInteger(getTraitValue(allTraits, kCTFontWeightTrait)); + int slant = getTraitValue(allTraits, kCTFontSlantTrait); + if (slant > 500 && !(traits & kCTFontItalicTrait)) + fontDef.style = QFont::StyleOblique; + CFRelease(allTraits); + + if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait)) + synthesisFlags |= SynthesizedBold; + // XXX: we probably don't need to synthesis italic for oblique font + if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait)) + synthesisFlags |= SynthesizedItalic; + + avgCharWidth = 0; + QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + unsigned emSize = CTFontGetUnitsPerEm(ctfont); + if (os2Table.size() >= 10) { + fsType = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(os2Table.constData() + 8)); + // qAbs is a workaround for weird fonts like Lucida Grande + qint16 width = qAbs(qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(os2Table.constData() + 2))); + avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize); + } else + avgCharWidth = QFontEngine::averageCharWidth(); + + ctMaxCharWidth = ctMinLeftBearing = ctMinRightBearing = 0; + QByteArray hheaTable = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a')); + if (hheaTable.size() >= 16) { + quint16 width = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 10)); + ctMaxCharWidth = width * fontDef.pixelSize / emSize; + qint16 bearing = qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 12)); + ctMinLeftBearing = bearing * fontDef.pixelSize / emSize; + bearing = qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 14)); + ctMinRightBearing = bearing * fontDef.pixelSize / emSize; + } +} + +bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + *nglyphs = len; + QCFType<CFStringRef> cfstring; + + QVarLengthArray<CGGlyph> cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) + if (cgGlyphs[i]) + glyphs->glyphs[i] = cgGlyphs[i]; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph) +{ + glyph_metrics_t ret; + CGGlyph g = glyph; + CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, 0, 1); + if (synthesisFlags & QFontEngine::SynthesizedItalic) { + rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW; + } + ret.width = QFixed::fromReal(rect.size.width); + ret.height = QFixed::fromReal(rect.size.height); + ret.x = QFixed::fromReal(rect.origin.x); + ret.y = -QFixed::fromReal(rect.origin.y) - ret.height; + CGSize advances[1]; + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, advances, 1); + ret.xoff = QFixed::fromReal(advances[0].width); + ret.yoff = QFixed::fromReal(advances[0].height); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + ret.xoff = ret.xoff.round(); + ret.yoff = ret.yoff.round(); + } + + return ret; +} + +QFixed QCoreTextFontEngine::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetAscent(ctfont)).round() + : QFixed::fromReal(CTFontGetAscent(ctfont)); +} +QFixed QCoreTextFontEngine::descent() const +{ + QFixed d = QFixed::fromReal(CTFontGetDescent(ctfont)); + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + d = d.round(); + + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return d - 1; +} +QFixed QCoreTextFontEngine::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetLeading(ctfont)).round() + : QFixed::fromReal(CTFontGetLeading(ctfont)); +} +QFixed QCoreTextFontEngine::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetXHeight(ctfont)).round() + : QFixed::fromReal(CTFontGetXHeight(ctfont)); +} + +QFixed QCoreTextFontEngine::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? avgCharWidth.round() : avgCharWidth; +} + +qreal QCoreTextFontEngine::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMaxCharWidth) : ctMaxCharWidth; +} + +qreal QCoreTextFontEngine::minLeftBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinLeftBearing) : ctMinLeftBearing; +} + +qreal QCoreTextFontEngine::minRightBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinRightBearing) : ctMinLeftBearing; +} + +void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray<CGSize> advances(glyphs.size()); + QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont)); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +struct ConvertPathInfo +{ + ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos) : path(newPath), pos(newPos) {} + QPainterPath *path; + QPointF pos; +}; + +static void convertCGPathToQPainterPath(void *info, const CGPathElement *element) +{ + ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info); + switch(element->type) { + case kCGPathElementMoveToPoint: + myInfo->path->moveTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddLineToPoint: + myInfo->path->lineTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddQuadCurveToPoint: + myInfo->path->quadTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y()); + break; + case kCGPathElementAddCurveToPoint: + myInfo->path->cubicTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y(), + element->points[2].x + myInfo->pos.x(), + element->points[2].y + myInfo->pos.y()); + break; + case kCGPathElementCloseSubpath: + myInfo->path->closeSubpath(); + break; + default: + qDebug() << "Unhandled path transform type: " << element->type; + } + +} + +void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + CGAffineTransform cgMatrix = CGAffineTransformIdentity; + cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + for (int i = 0; i < nGlyphs; ++i) { + QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix); + ConvertPathInfo info(path, positions[i].toPointF()); + CGPathApply(cgpath, &info, convertCGPathToQPainterPath); + } +} + +QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, QFixed subPixelPosition, int /*margin*/, bool aa) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+2, QImage::Format_RGB32); + im.fill(0); + + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, aa || + (fontDef.pointSize > qt_antialiasing_threshold + && !(fontDef.styleStrategy & QFont::NoAntialias))); + CGContextSetShouldSmoothFonts(ctx, aa); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + subPixelPosition.toReal(); + qreal pos_y = im.height() + br.y.toReal() - 1; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + QImage im = imageForGlyph(glyph, subPixelPosition, 0, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uint *src = (uint*) im.scanLine(y); + uchar *dst = indexed.scanLine(y); + for (int x=0; x<im.width(); ++x) { + *dst = qGray(*src); + ++dst; + ++src; + } + } + + return indexed; +} + +QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed subPixelPosition, int margin, const QTransform &x) +{ + if (x.type() >= QTransform::TxScale) + return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, margin, x); + + QImage im = imageForGlyph(glyph, subPixelPosition, margin, true); + qGamma_correct_back_to_linear_cs(&im); + return im; +} + +void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int i, numGlyphs = glyphs->numGlyphs; + QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs); + + for (i = 0; i < numGlyphs; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + cgGlyphs[i] = 0; + else + cgGlyphs[i] = glyphs->glyphs[i]; + } + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, numGlyphs, flags, fontDef); +} + +QFontEngine::FaceId QCoreTextFontEngine::faceId() const +{ + return QFontEngine::FaceId(); +} + +bool QCoreTextFontEngine::canRender(const QChar *string, int len) +{ + QVarLengthArray<CGGlyph> cgGlyphs(len); + return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len); +} + +bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0); + if (!table || !length) + return false; + CFIndex tableLength = CFDataGetLength(table); + int availableLength = *length; + *length = tableLength; + if (buffer) { + if (tableLength > availableLength) + return false; + CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer); + } + return true; +} + +void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metrics_t *) +{ + // ### +} + +QFixed QCoreTextFontEngine::emSquareSize() const +{ + return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont))); +} + +QT_END_NAMESPACE + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + diff --git a/src/gui/text/qfontengine_coretext_p.h b/src/gui/text/qfontengine_coretext_p.h new file mode 100644 index 0000000000..1503c3f73f --- /dev/null +++ b/src/gui/text/qfontengine_coretext_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_CORETEXT_P_H +#define QFONTENGINE_CORETEXT_P_H + +#include <private/qfontengine_p.h> + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +class QRawFontPrivate; +class QCoreTextFontEngineMulti; +class QCoreTextFontEngine : public QFontEngine +{ +public: + QCoreTextFontEngine(CTFontRef font, const QFontDef &def); + QCoreTextFontEngine(CGFontRef font, const QFontDef &def); + ~QCoreTextFontEngine(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QCoreTextFontEngine"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + virtual bool supportsSubPixelPositions() const { return true; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + virtual qreal minRightBearing() const; + virtual qreal minLeftBearing() const; + virtual QFixed emSquareSize() const; + +private: + friend class QRawFontPrivate; + + void init(); + QImage imageForGlyph(glyph_t glyph, QFixed subPixelPosition, int margin, bool colorful); + CTFontRef ctfont; + CGFontRef cgFont; + int synthesisFlags; + CGAffineTransform transform; + QFixed avgCharWidth; + qreal ctMaxCharWidth; + qreal ctMinLeftBearing; + qreal ctMinRightBearing; + friend class QCoreTextFontEngineMulti; +}; + +class QCoreTextFontEngineMulti : public QFontEngineMulti +{ +public: + QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning); + QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning); + ~QCoreTextFontEngineMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const; + + virtual const char *name() const { return "CoreText"; } +protected: + virtual void loadEngine(int at); + +private: + void init(bool kerning); + inline const QCoreTextFontEngine *engineAt(int i) const + { return static_cast<const QCoreTextFontEngine *>(engines.at(i)); } + + uint fontIndexForFont(CTFontRef font) const; + CTFontRef ctfont; + mutable QCFType<CFMutableDictionaryRef> attributeDict; + CGAffineTransform transform; + friend class QFontDialogPrivate; +}; + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +#endif // QFONTENGINE_CORETEXT_P_H diff --git a/src/gui/text/qfontengine_ft.cpp b/src/gui/text/qfontengine_ft.cpp new file mode 100644 index 0000000000..8f2da9b713 --- /dev/null +++ b/src/gui/text/qfontengine_ft.cpp @@ -0,0 +1,2074 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdir.h" +#include "qmetatype.h" +#include "qtextstream.h" +#include "qvariant.h" +#include "qfontengine_ft_p.h" + +#ifndef QT_NO_FREETYPE + +#include "qfile.h" +#include "qabstractfileengine.h" +#include "qthreadstorage.h" +#include <qmath.h> +#include <private/qharfbuzz_p.h> + +#include "qfontengine_ft_p.h" +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_SYNTHESIS_H +#include FT_TRUETYPE_TABLES_H +#include FT_TYPE1_TABLES_H +#include FT_GLYPH_H + +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FT_CONFIG_OPTIONS_H) +#include FT_CONFIG_OPTIONS_H +#endif + +#if defined(FT_LCD_FILTER_H) && defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) +#define QT_USE_FREETYPE_LCDFILTER +#endif + +#ifdef QT_LINUXBASE +#include FT_ERRORS_H +#endif + +QT_BEGIN_NAMESPACE + +/* + * Freetype 2.1.7 and earlier used width/height + * for matching sizes in the BDF and PCF loaders. + * This has been fixed for 2.1.8. + */ +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20105 +#define X_SIZE(face,i) ((face)->available_sizes[i].x_ppem) +#define Y_SIZE(face,i) ((face)->available_sizes[i].y_ppem) +#else +#define X_SIZE(face,i) ((face)->available_sizes[i].width << 6) +#define Y_SIZE(face,i) ((face)->available_sizes[i].height << 6) +#endif + +/* FreeType 2.1.10 starts to provide FT_GlyphSlot_Embolden */ +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20110 +#define Q_FT_GLYPHSLOT_EMBOLDEN(slot) FT_GlyphSlot_Embolden(slot) +#else +#define Q_FT_GLYPHSLOT_EMBOLDEN(slot) +#endif + +#define FLOOR(x) ((x) & -64) +#define CEIL(x) (((x)+63) & -64) +#define TRUNC(x) ((x) >> 6) +#define ROUND(x) (((x)+32) & -64) + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) > 20103 + FT_Face face = (FT_Face)font; + FT_ULong ftlen = *length; + FT_Error error = 0; + + if ( !FT_IS_SFNT(face) ) + return HB_Err_Invalid_Argument; + + error = FT_Load_Sfnt_Table(face, tableTag, 0, buffer, &ftlen); + *length = ftlen; + return (HB_Error)error; +#else + return HB_Err_Invalid_Argument; +#endif +} + +// -------------------------- Freetype support ------------------------------ + +class QtFreetypeData +{ +public: + QtFreetypeData() + : library(0) + { } + + FT_Library library; + QHash<QFontEngine::FaceId, QFreetypeFace *> faces; +}; + +#ifdef QT_NO_THREAD +Q_GLOBAL_STATIC(QtFreetypeData, theFreetypeData) + +QtFreetypeData *qt_getFreetypeData() +{ + return theFreetypeData(); +} +#else +Q_GLOBAL_STATIC(QThreadStorage<QtFreetypeData *>, theFreetypeData) + +QtFreetypeData *qt_getFreetypeData() +{ + QtFreetypeData *&freetypeData = theFreetypeData()->localData(); + if (!freetypeData) + freetypeData = new QtFreetypeData; + return freetypeData; +} +#endif + +FT_Library qt_getFreetype() +{ + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!freetypeData->library) + FT_Init_FreeType(&freetypeData->library); + return freetypeData->library; +} + +int QFreetypeFace::fsType() const +{ + int fsType = 0; + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) + fsType = os2->fsType; + return fsType; +} + +HB_Error QFreetypeFace::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + if (HB_Error error = (HB_Error)FT_Load_Glyph(face, glyph, flags)) + return error; + + if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) + return HB_Err_Invalid_SubTable; + + *nPoints = face->glyph->outline.n_points; + if (!(*nPoints)) + return HB_Err_Ok; + + if (point > *nPoints) + return HB_Err_Invalid_SubTable; + + *xpos = face->glyph->outline.points[point].x; + *ypos = face->glyph->outline.points[point].y; + + return HB_Err_Ok; +} + +/* + * One font file can contain more than one font (bold/italic for example) + * find the right one and return it. + * + * Returns the freetype face or 0 in case of an empty file or any other problems + * (like not being able to open the file) + */ +QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, + const QByteArray &fontData) +{ + if (face_id.filename.isEmpty() && fontData.isEmpty()) + return 0; + + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!freetypeData->library) + FT_Init_FreeType(&freetypeData->library); + + QFreetypeFace *freetype = freetypeData->faces.value(face_id, 0); + if (freetype) { + freetype->ref.ref(); + } else { + QScopedPointer<QFreetypeFace> newFreetype(new QFreetypeFace); + FT_Face face; + if (!face_id.filename.isEmpty()) { + QFile file(QString::fromUtf8(face_id.filename)); + if (face_id.filename.startsWith(":qmemoryfonts/")) { + // from qfontdatabase.cpp + extern QByteArray qt_fontdata_from_index(int); + QByteArray idx = face_id.filename; + idx.remove(0, 14); // remove ':qmemoryfonts/' + bool ok = false; + newFreetype->fontData = qt_fontdata_from_index(idx.toInt(&ok)); + if (!ok) + newFreetype->fontData = QByteArray(); + } else if (!(file.fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag)) { + if (!file.open(QIODevice::ReadOnly)) { + return 0; + } + newFreetype->fontData = file.readAll(); + } + } else { + newFreetype->fontData = fontData; + } + if (!newFreetype->fontData.isEmpty()) { + if (FT_New_Memory_Face(freetypeData->library, (const FT_Byte *)newFreetype->fontData.constData(), newFreetype->fontData.size(), face_id.index, &face)) { + return 0; + } + } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) { + return 0; + } + newFreetype->face = face; + + newFreetype->hbFace = qHBNewFace(face, hb_getSFntTable); + Q_CHECK_PTR(newFreetype->hbFace); + newFreetype->ref = 1; + newFreetype->xsize = 0; + newFreetype->ysize = 0; + newFreetype->matrix.xx = 0x10000; + newFreetype->matrix.yy = 0x10000; + newFreetype->matrix.xy = 0; + newFreetype->matrix.yx = 0; + newFreetype->unicode_map = 0; + newFreetype->symbol_map = 0; +#ifndef QT_NO_FONTCONFIG + newFreetype->charset = 0; +#endif + + memset(newFreetype->cmapCache, 0, sizeof(newFreetype->cmapCache)); + + for (int i = 0; i < newFreetype->face->num_charmaps; ++i) { + FT_CharMap cm = newFreetype->face->charmaps[i]; + switch(cm->encoding) { + case FT_ENCODING_UNICODE: + newFreetype->unicode_map = cm; + break; + case FT_ENCODING_APPLE_ROMAN: + case FT_ENCODING_ADOBE_LATIN_1: + if (!newFreetype->unicode_map || newFreetype->unicode_map->encoding != FT_ENCODING_UNICODE) + newFreetype->unicode_map = cm; + break; + case FT_ENCODING_ADOBE_CUSTOM: + case FT_ENCODING_MS_SYMBOL: + if (!newFreetype->symbol_map) + newFreetype->symbol_map = cm; + break; + default: + break; + } + } + + if (!FT_IS_SCALABLE(newFreetype->face) && newFreetype->face->num_fixed_sizes == 1) + FT_Set_Char_Size (face, X_SIZE(newFreetype->face, 0), Y_SIZE(newFreetype->face, 0), 0, 0); +# if 0 + FcChar8 *name; + FcPatternGetString(pattern, FC_FAMILY, 0, &name); + qDebug("%s: using maps: default: %x unicode: %x, symbol: %x", name, + newFreetype->face->charmap ? newFreetype->face->charmap->encoding : 0, + newFreetype->unicode_map ? newFreetype->unicode_map->encoding : 0, + newFreetype->symbol_map ? newFreetype->symbol_map->encoding : 0); + + for (int i = 0; i < 256; i += 8) + qDebug(" %x: %d %d %d %d %d %d %d %d", i, + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i)); +#endif + + FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map); + QT_TRY { + freetypeData->faces.insert(face_id, newFreetype.data()); + } QT_CATCH(...) { + newFreetype.take()->release(face_id); + // we could return null in principle instead of throwing + QT_RETHROW; + } + freetype = newFreetype.take(); + } + return freetype; +} + +void QFreetypeFace::release(const QFontEngine::FaceId &face_id) +{ + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!ref.deref()) { + qHBFreeFace(hbFace); + FT_Done_Face(face); +#ifndef QT_NO_FONTCONFIG + if (charset) + FcCharSetDestroy(charset); +#endif + if(freetypeData->faces.contains(face_id)) + freetypeData->faces.take(face_id); + delete this; + } + if (freetypeData->faces.isEmpty()) { + FT_Done_FreeType(freetypeData->library); + freetypeData->library = 0; + } +} + + +void QFreetypeFace::computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing) +{ + *ysize = qRound(fontDef.pixelSize * 64); + *xsize = *ysize * fontDef.stretch / 100; + *outline_drawing = false; + + /* + * Bitmap only faces must match exactly, so find the closest + * one (height dominant search) + */ + if (!(face->face_flags & FT_FACE_FLAG_SCALABLE)) { + int best = 0; + for (int i = 1; i < face->num_fixed_sizes; i++) { + if (qAbs(*ysize - Y_SIZE(face,i)) < + qAbs (*ysize - Y_SIZE(face, best)) || + (qAbs (*ysize - Y_SIZE(face, i)) == + qAbs (*ysize - Y_SIZE(face, best)) && + qAbs (*xsize - X_SIZE(face, i)) < + qAbs (*xsize - X_SIZE(face, best)))) { + best = i; + } + } + if (FT_Set_Char_Size (face, X_SIZE(face, best), Y_SIZE(face, best), 0, 0) == 0) { + *xsize = X_SIZE(face, best); + *ysize = Y_SIZE(face, best); + } else { + int err = 1; + if (!(face->face_flags & FT_FACE_FLAG_SCALABLE) && ysize == 0 && face->num_fixed_sizes >= 1) { + // work around FT 2.1.10 problem with BDF without PIXEL_SIZE property + err = FT_Set_Pixel_Sizes(face, face->available_sizes[0].width, face->available_sizes[0].height); + if (err && face->num_fixed_sizes == 1) + err = 0; //even more of a workaround... + } + + if (err) + *xsize = *ysize = 0; + } + } else { + *outline_drawing = (*xsize > (64<<6) || *ysize > (64<<6)); + } +} + +QFontEngine::Properties QFreetypeFace::properties() const +{ + QFontEngine::Properties p; + p.postscriptName = FT_Get_Postscript_Name(face); + PS_FontInfoRec font_info; + if (FT_Get_PS_Font_Info(face, &font_info) == 0) + p.copyright = font_info.notice; + if (FT_IS_SCALABLE(face)) { + p.ascent = face->ascender; + p.descent = -face->descender; + p.leading = face->height - face->ascender + face->descender; + p.emSquare = face->units_per_EM; + p.boundingBox = QRectF(face->bbox.xMin, -face->bbox.yMax, + face->bbox.xMax - face->bbox.xMin, + face->bbox.yMax - face->bbox.yMin); + } else { + p.ascent = QFixed::fromFixed(face->size->metrics.ascender); + p.descent = QFixed::fromFixed(-face->size->metrics.descender); + p.leading = QFixed::fromFixed(face->size->metrics.height - face->size->metrics.ascender + face->size->metrics.descender); + p.emSquare = face->size->metrics.y_ppem; +// p.boundingBox = QRectF(-p.ascent.toReal(), 0, (p.ascent + p.descent).toReal(), face->size->metrics.max_advance/64.); + p.boundingBox = QRectF(0, -p.ascent.toReal(), + face->size->metrics.max_advance/64, (p.ascent + p.descent).toReal() ); + } + p.italicAngle = 0; + p.capHeight = p.ascent; + p.lineWidth = face->underline_thickness; + return p; +} + +bool QFreetypeFace::getSfntTable(uint tag, uchar *buffer, uint *length) const +{ + bool result = false; +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) > 20103 + if (FT_IS_SFNT(face)) { + FT_ULong len = *length; + result = FT_Load_Sfnt_Table(face, tag, 0, buffer, &len) == FT_Err_Ok; + *length = len; + } +#endif + return result; +} + +/* Some fonts (such as MingLiu rely on hinting to scale different + components to their correct sizes. While this is really broken (it + should be done in the component glyph itself, not the hinter) we + will have to live with it. + + This means we can not use FT_LOAD_NO_HINTING to get the glyph + outline. All we can do is to load the unscaled glyph and scale it + down manually when required. +*/ +static void scaleOutline(FT_Face face, FT_GlyphSlot g, FT_Fixed x_scale, FT_Fixed y_scale) +{ + x_scale = FT_MulDiv(x_scale, 1 << 10, face->units_per_EM); + y_scale = FT_MulDiv(y_scale, 1 << 10, face->units_per_EM); + FT_Vector *p = g->outline.points; + const FT_Vector *e = p + g->outline.n_points; + while (p < e) { + p->x = FT_MulFix(p->x, x_scale); + p->y = FT_MulFix(p->y, y_scale); + ++p; + } +} + +void QFreetypeFace::addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale) +{ + const qreal factor = 1/64.; + scaleOutline(face, g, x_scale, y_scale); + + QPointF cp = point.toPointF(); + + // convert the outline to a painter path + int i = 0; + for (int j = 0; j < g->outline.n_contours; ++j) { + int last_point = g->outline.contours[j]; + QPointF start = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor); + if(!(g->outline.tags[i] & 1)) { + start += cp + QPointF(g->outline.points[last_point].x*factor, -g->outline.points[last_point].y*factor); + start /= 2; + } +// qDebug("contour: %d -- %d", i, g->outline.contours[j]); +// qDebug("first point at %f %f", start.x(), start.y()); + path->moveTo(start); + + QPointF c[4]; + c[0] = start; + int n = 1; + while (i < last_point) { + ++i; + c[n] = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor); +// qDebug() << " i=" << i << " flag=" << (int)g->outline.tags[i] << "point=" << c[n]; + ++n; + switch (g->outline.tags[i] & 3) { + case 2: + // cubic bezier element + if (n < 4) + continue; + c[3] = (c[3] + c[2])/2; + --i; + break; + case 0: + // quadratic bezier element + if (n < 3) + continue; + c[3] = (c[1] + c[2])/2; + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + --i; + break; + case 1: + case 3: + if (n == 2) { +// qDebug() << "lineTo" << c[1]; + path->lineTo(c[1]); + c[0] = c[1]; + n = 1; + continue; + } else if (n == 3) { + c[3] = c[2]; + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + } + break; + } +// qDebug() << "cubicTo" << c[1] << c[2] << c[3]; + path->cubicTo(c[1], c[2], c[3]); + c[0] = c[3]; + n = 1; + } + if (n == 1) { +// qDebug() << "closeSubpath"; + path->closeSubpath(); + } else { + c[3] = start; + if (n == 2) { + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + } +// qDebug() << "cubicTo" << c[1] << c[2] << c[3]; + path->cubicTo(c[1], c[2], c[3]); + } + ++i; + } +} + +extern void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path); + +void QFreetypeFace::addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path, bool) +{ + if (slot->format != FT_GLYPH_FORMAT_BITMAP + || slot->bitmap.pixel_mode != FT_PIXEL_MODE_MONO) + return; + + QPointF cp = point.toPointF(); + qt_addBitmapToPath(cp.x() + TRUNC(slot->metrics.horiBearingX), cp.y() - TRUNC(slot->metrics.horiBearingY), + slot->bitmap.buffer, slot->bitmap.pitch, slot->bitmap.width, slot->bitmap.rows, path); +} + +QFontEngineFT::Glyph::~Glyph() +{ + delete [] data; +} + +static const uint subpixel_filter[3][3] = { + { 180, 60, 16 }, + { 38, 180, 38 }, + { 16, 60, 180 } +}; + +static inline uint filterPixel(uint red, uint green, uint blue, bool legacyFilter) +{ + uint res; + if (legacyFilter) { + uint high = (red*subpixel_filter[0][0] + green*subpixel_filter[0][1] + blue*subpixel_filter[0][2]) >> 8; + uint mid = (red*subpixel_filter[1][0] + green*subpixel_filter[1][1] + blue*subpixel_filter[1][2]) >> 8; + uint low = (red*subpixel_filter[2][0] + green*subpixel_filter[2][1] + blue*subpixel_filter[2][2]) >> 8; + res = (mid << 24) + (high << 16) + (mid << 8) + low; + } else { + uint alpha = green; + res = (alpha << 24) + (red << 16) + (green << 8) + blue; + } + return res; +} + +static void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter) +{ + int h = height; + const int offs = bgr ? -1 : 1; + const int w = width * 3; + while (h--) { + uint *dd = dst; + for (int x = 0; x < w; x += 3) { + uint red = src[x+1-offs]; + uint green = src[x+1]; + uint blue = src[x+1+offs]; + *dd = filterPixel(red, green, blue, legacyFilter); + ++dd; + } + dst += width; + src += src_pitch; + } +} + +static void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter) +{ + int h = height; + const int offs = bgr ? -src_pitch : src_pitch; + while (h--) { + for (int x = 0; x < width; x++) { + uint red = src[x+src_pitch-offs]; + uint green = src[x+src_pitch]; + uint blue = src[x+src_pitch+offs]; + dst[x] = filterPixel(red, green, blue, legacyFilter); + } + dst += width; + src += 3*src_pitch; + } +} + +static void convoluteBitmap(const uchar *src, uchar *dst, int width, int height, int pitch) +{ + // convolute the bitmap with a triangle filter to get rid of color fringes + // If we take account for a gamma value of 2, we end up with + // weights of 1, 4, 9, 4, 1. We use an approximation of 1, 3, 8, 3, 1 here, + // as this nicely sums up to 16 :) + int h = height; + while (h--) { + dst[0] = dst[1] = 0; + // + for (int x = 2; x < width - 2; ++x) { + uint sum = src[x-2] + 3*src[x-1] + 8*src[x] + 3*src[x+1] + src[x+2]; + dst[x] = (uchar) (sum >> 4); + } + dst[width - 2] = dst[width - 1] = 0; + src += pitch; + dst += pitch; + } +} + +QFontEngineFT::QFontEngineFT(const QFontDef &fd) +{ + fontDef = fd; + matrix.xx = 0x10000; + matrix.yy = 0x10000; + matrix.xy = 0; + matrix.yx = 0; + cache_cost = 100; + kerning_pairs_loaded = false; + transform = false; + embolden = false; + antialias = true; + freetype = 0; + default_load_flags = FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + default_hint_style = HintNone; + subpixelType = Subpixel_None; + lcdFilterType = 0; +#if defined(FT_LCD_FILTER_H) + lcdFilterType = (int)((quintptr) FT_LCD_FILTER_DEFAULT); +#endif + defaultFormat = Format_None; + canUploadGlyphsToServer = false; + embeddedbitmap = false; +} + +QFontEngineFT::~QFontEngineFT() +{ + if (freetype) + freetype->release(face_id); + hbFace = 0; // we share the face in QFreeTypeFace, don't let ~QFontEngine delete it +} + +void QFontEngineFT::freeGlyphSets() +{ + freeServerGlyphSet(defaultGlyphSet.id); + for (int i = 0; i < transformedGlyphSets.count(); ++i) + freeServerGlyphSet(transformedGlyphSets.at(i).id); +} + +bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, + const QByteArray &fontData) +{ + return init(faceId, antialias, format, QFreetypeFace::getFace(faceId, fontData)); +} + +bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, + QFreetypeFace *freetypeFace) +{ + freetype = freetypeFace; + if (!freetype) { + xsize = 0; + ysize = 0; + return false; + } + defaultFormat = format; + this->antialias = antialias; + + if (!antialias) + glyphFormat = QFontEngineGlyphCache::Raster_Mono; + else if (format == Format_A8) + glyphFormat = QFontEngineGlyphCache::Raster_A8; + else if (format == Format_A32) + glyphFormat = QFontEngineGlyphCache::Raster_RGBMask; + + face_id = faceId; + + symbol = freetype->symbol_map != 0; + PS_FontInfoRec psrec; + // don't assume that type1 fonts are symbol fonts by default + if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) { + symbol = bool(fontDef.family.contains(QLatin1String("symbol"), Qt::CaseInsensitive)); + } + // ##### + freetype->hbFace->isSymbolFont = symbol; + + lbearing = rbearing = SHRT_MIN; + freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing); + + FT_Face face = lockFace(); + + if (FT_IS_SCALABLE(face)) { + bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !(face->style_flags & FT_STYLE_FLAG_ITALIC); + if (fake_oblique) + matrix.xy = 0x10000*3/10; + FT_Set_Transform(face, &matrix, 0); + freetype->matrix = matrix; + if (fake_oblique) + transform = true; + // fake bold + if ((fontDef.weight == QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face)) + embolden = true; + // underline metrics + line_thickness = QFixed::fromFixed(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale)); + underline_position = QFixed::fromFixed(-FT_MulFix(face->underline_position, face->size->metrics.y_scale)); + } else { + // copied from QFontEngineQPF + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + line_thickness = score / 700; + // looks better with thicker line for small pointsizes + if (line_thickness < 2 && score >= 1050) + line_thickness = 2; + underline_position = ((line_thickness * 2) + 3) / 6; + } + if (line_thickness < 1) + line_thickness = 1; + + hbFont.x_ppem = face->size->metrics.x_ppem; + hbFont.y_ppem = face->size->metrics.y_ppem; + hbFont.x_scale = face->size->metrics.x_scale; + hbFont.y_scale = face->size->metrics.y_scale; + + hbFace = freetype->hbFace; + + metrics = face->size->metrics; + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + /* + TrueType fonts with embedded bitmaps may have a bitmap font specific + ascent/descent in the EBLC table. There is no direct public API + to extract those values. The only way we've found is to trick freetype + into thinking that it's not a scalable font in FT_SelectSize so that + the metrics are retrieved from the bitmap strikes. + */ + if (FT_IS_SCALABLE(face)) { + for (int i = 0; i < face->num_fixed_sizes; ++i) { + if (xsize == X_SIZE(face, i) && ysize == Y_SIZE(face, i)) { + face->face_flags &= ~FT_FACE_FLAG_SCALABLE; + + FT_Select_Size(face, i); + metrics.ascender = face->size->metrics.ascender; + metrics.descender = face->size->metrics.descender; + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + + face->face_flags |= FT_FACE_FLAG_SCALABLE; + break; + } + } + } +#endif + + unlockFace(); + + fsType = freetype->fsType(); + defaultGlyphSet.id = allocateServerGlyphSet(); + return true; +} + +void QFontEngineFT::setDefaultHintStyle(HintStyle style) +{ + default_hint_style = style; +} + +int QFontEngineFT::loadFlags(QGlyphSet *set, GlyphFormat format, int flags, + bool &hsubpixel, int &vfactor) const +{ + int load_flags = FT_LOAD_DEFAULT | default_load_flags; + int load_target = default_hint_style == HintLight + ? FT_LOAD_TARGET_LIGHT + : FT_LOAD_TARGET_NORMAL; + + if (format == Format_Mono) { + load_target = FT_LOAD_TARGET_MONO; + } else if (format == Format_A32) { + if (subpixelType == QFontEngineFT::Subpixel_RGB || subpixelType == QFontEngineFT::Subpixel_BGR) { + if (default_hint_style == HintFull) + load_target = FT_LOAD_TARGET_LCD; + hsubpixel = true; + } else if (subpixelType == QFontEngineFT::Subpixel_VRGB || subpixelType == QFontEngineFT::Subpixel_VBGR) { + if (default_hint_style == HintFull) + load_target = FT_LOAD_TARGET_LCD_V; + vfactor = 3; + } + } + + if (set && set->outline_drawing) + load_flags = FT_LOAD_NO_BITMAP; + + if (default_hint_style == HintNone || (flags & HB_ShaperFlag_UseDesignMetrics)) + load_flags |= FT_LOAD_NO_HINTING; + else + load_flags |= load_target; + + return load_flags; +} + +QFontEngineFT::Glyph *QFontEngineFT::loadGlyphMetrics(QGlyphSet *set, uint glyph, GlyphFormat format) const +{ + Glyph *g = set->getGlyph(glyph); + if (g && g->format == format) + return g; + + bool hsubpixel = false; + int vfactor = 1; + int load_flags = loadFlags(set, format, 0, hsubpixel, vfactor); + + // apply our matrix to this, but note that the metrics will not be affected by this. + FT_Face face = lockFace(); + FT_Matrix matrix = this->matrix; + FT_Matrix_Multiply(&set->transformationMatrix, &matrix); + FT_Set_Transform(face, &matrix, 0); + freetype->matrix = matrix; + + bool transform = matrix.xx != 0x10000 || matrix.yy != 0x10000 || matrix.xy != 0 || matrix.yx != 0; + if (transform) + load_flags |= FT_LOAD_NO_BITMAP; + + FT_Error err = FT_Load_Glyph(face, glyph, load_flags); + if (err && (load_flags & FT_LOAD_NO_BITMAP)) { + load_flags &= ~FT_LOAD_NO_BITMAP; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err == FT_Err_Too_Few_Arguments) { + // this is an error in the bytecode interpreter, just try to run without it + load_flags |= FT_LOAD_FORCE_AUTOHINT; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err != FT_Err_Ok) + qWarning("load glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + unlockFace(); + if (set->outline_drawing) + return 0; + + if (!g) { + g = new Glyph; + g->uploadedToServer = false; + g->data = 0; + } + + FT_GlyphSlot slot = face->glyph; + if (embolden) Q_FT_GLYPHSLOT_EMBOLDEN(slot); + int left = slot->metrics.horiBearingX; + int right = slot->metrics.horiBearingX + slot->metrics.width; + int top = slot->metrics.horiBearingY; + int bottom = slot->metrics.horiBearingY - slot->metrics.height; + if (transform && slot->format != FT_GLYPH_FORMAT_BITMAP) { // freetype doesn't apply the transformation on the metrics + int l, r, t, b; + FT_Vector vector; + vector.x = left; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + l = r = vector.x; + t = b = vector.y; + vector.x = right; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = right; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = left; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + left = l; + right = r; + top = t; + bottom = b; + } + left = FLOOR(left); + right = CEIL(right); + bottom = FLOOR(bottom); + top = CEIL(top); + + g->linearAdvance = face->glyph->linearHoriAdvance >> 10; + g->width = TRUNC(right-left); + g->height = TRUNC(top-bottom); + g->x = TRUNC(left); + g->y = TRUNC(top); + g->advance = TRUNC(ROUND(face->glyph->advance.x)); + g->format = Format_None; + + return g; +} + +QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, + QFixed subPixelPosition, + GlyphFormat format, + bool fetchMetricsOnly) const +{ +// Q_ASSERT(freetype->lock == 1); + + bool uploadToServer = false; + if (format == Format_None) { + if (defaultFormat != Format_None) { + format = defaultFormat; + if (canUploadGlyphsToServer) + uploadToServer = true; + } else { + format = Format_Mono; + } + } + + Glyph *g = set->getGlyph(glyph, subPixelPosition); + if (g && g->format == format) { + if (uploadToServer && !g->uploadedToServer) { + set->setGlyph(glyph, subPixelPosition, 0); + delete g; + g = 0; + } else { + return g; + } + } + + QFontEngineFT::GlyphInfo info; + + Q_ASSERT(format != Format_None); + bool hsubpixel = false; + int vfactor = 1; + int load_flags = loadFlags(set, format, 0, hsubpixel, vfactor); + +#ifndef Q_WS_QWS + if (format != Format_Mono && !embeddedbitmap) + load_flags |= FT_LOAD_NO_BITMAP; +#endif + + FT_Matrix matrix = freetype->matrix; + bool transform = matrix.xx != 0x10000 + || matrix.yy != 0x10000 + || matrix.xy != 0 + || matrix.yx != 0; + + if (transform) + load_flags |= FT_LOAD_NO_BITMAP; + + FT_Face face = freetype->face; + + FT_Vector v; + v.x = format == Format_Mono ? 0 : FT_Pos(subPixelPosition.toReal() * 64); + v.y = 0; + FT_Set_Transform(face, &freetype->matrix, &v); + + FT_Error err = FT_Load_Glyph(face, glyph, load_flags); + if (err && (load_flags & FT_LOAD_NO_BITMAP)) { + load_flags &= ~FT_LOAD_NO_BITMAP; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err == FT_Err_Too_Few_Arguments) { + // this is an error in the bytecode interpreter, just try to run without it + load_flags |= FT_LOAD_FORCE_AUTOHINT; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err != FT_Err_Ok) + qWarning("load glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + if (set->outline_drawing && fetchMetricsOnly) + return 0; + + FT_GlyphSlot slot = face->glyph; + if (embolden) Q_FT_GLYPHSLOT_EMBOLDEN(slot); + FT_Library library = qt_getFreetype(); + + info.xOff = TRUNC(ROUND(slot->advance.x)); + info.yOff = 0; + + uchar *glyph_buffer = 0; + int glyph_buffer_size = 0; +#if defined(QT_USE_FREETYPE_LCDFILTER) + bool useFreetypeRenderGlyph = false; + if (slot->format == FT_GLYPH_FORMAT_OUTLINE && (hsubpixel || vfactor != 1)) { + err = FT_Library_SetLcdFilter(library, (FT_LcdFilter)lcdFilterType); + if (err == FT_Err_Ok) + useFreetypeRenderGlyph = true; + } + + if (useFreetypeRenderGlyph) { + err = FT_Render_Glyph(slot, hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V); + + if (err != FT_Err_Ok) + qWarning("render glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + FT_Library_SetLcdFilter(library, FT_LCD_FILTER_NONE); + + info.height = slot->bitmap.rows / vfactor; + info.width = hsubpixel ? slot->bitmap.width / 3 : slot->bitmap.width; + info.x = -slot->bitmap_left; + info.y = slot->bitmap_top; + + glyph_buffer_size = info.width * info.height * 4; + glyph_buffer = new uchar[glyph_buffer_size]; + + if (hsubpixel) + convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, slot->bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_RGB, false); + else if (vfactor != 1) + convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, slot->bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_VRGB, false); + } else +#endif + { + int left = slot->metrics.horiBearingX; + int right = slot->metrics.horiBearingX + slot->metrics.width; + int top = slot->metrics.horiBearingY; + int bottom = slot->metrics.horiBearingY - slot->metrics.height; + if(transform && slot->format != FT_GLYPH_FORMAT_BITMAP) { + int l, r, t, b; + FT_Vector vector; + vector.x = left; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + l = r = vector.x; + t = b = vector.y; + vector.x = right; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = right; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = left; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + left = l; + right = r; + top = t; + bottom = b; + } + left = FLOOR(left); + right = CEIL(right); + bottom = FLOOR(bottom); + top = CEIL(top); + + int hpixels = TRUNC(right - left); + // subpixel position requires one more pixel + if (subPixelPosition > 0 && format != Format_Mono) + hpixels++; + + if (hsubpixel) + hpixels = hpixels*3 + 8; + info.width = hpixels; + info.height = TRUNC(top - bottom); + info.x = -TRUNC(left); + info.y = TRUNC(top); + if (hsubpixel) { + info.width /= 3; + info.x += 1; + } + + bool large_glyph = (((short)(slot->linearHoriAdvance>>10) != slot->linearHoriAdvance>>10) + || ((uchar)(info.width) != info.width) + || ((uchar)(info.height) != info.height) + || ((signed char)(info.x) != info.x) + || ((signed char)(info.y) != info.y) + || ((signed char)(info.xOff) != info.xOff)); + + if (large_glyph) { + delete [] glyph_buffer; + return 0; + } + + int pitch = (format == Format_Mono ? ((info.width + 31) & ~31) >> 3 : + (format == Format_A8 ? (info.width + 3) & ~3 : info.width * 4)); + glyph_buffer_size = pitch * info.height; + glyph_buffer = new uchar[glyph_buffer_size]; + + if (slot->format == FT_GLYPH_FORMAT_OUTLINE) { + FT_Bitmap bitmap; + bitmap.rows = info.height*vfactor; + bitmap.width = hpixels; + bitmap.pitch = format == Format_Mono ? (((info.width + 31) & ~31) >> 3) : ((bitmap.width + 3) & ~3); + if (!hsubpixel && vfactor == 1) + bitmap.buffer = glyph_buffer; + else + bitmap.buffer = new uchar[bitmap.rows*bitmap.pitch]; + memset(bitmap.buffer, 0, bitmap.rows*bitmap.pitch); + bitmap.pixel_mode = format == Format_Mono ? FT_PIXEL_MODE_MONO : FT_PIXEL_MODE_GRAY; + FT_Matrix matrix; + matrix.xx = (hsubpixel ? 3 : 1) << 16; + matrix.yy = vfactor << 16; + matrix.yx = matrix.xy = 0; + + FT_Outline_Transform(&slot->outline, &matrix); + FT_Outline_Translate (&slot->outline, (hsubpixel ? -3*left +(4<<6) : -left), -bottom*vfactor); + FT_Outline_Get_Bitmap(library, &slot->outline, &bitmap); + if (hsubpixel) { + Q_ASSERT (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY); + Q_ASSERT(antialias); + uchar *convoluted = new uchar[bitmap.rows*bitmap.pitch]; + bool useLegacyLcdFilter = false; +#if defined(FC_LCD_FILTER) && defined(FT_LCD_FILTER_H) + useLegacyLcdFilter = (lcdFilterType == FT_LCD_FILTER_LEGACY); +#endif + uchar *buffer = bitmap.buffer; + if (!useLegacyLcdFilter) { + convoluteBitmap(bitmap.buffer, convoluted, bitmap.width, info.height, bitmap.pitch); + buffer = convoluted; + } + convertRGBToARGB(buffer + 1, (uint *)glyph_buffer, info.width, info.height, bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_RGB, useLegacyLcdFilter); + delete [] convoluted; + } else if (vfactor != 1) { + convertRGBToARGB_V(bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_VRGB, true); + } + + if (bitmap.buffer != glyph_buffer) + delete [] bitmap.buffer; + } else if (slot->format == FT_GLYPH_FORMAT_BITMAP) { + Q_ASSERT(slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO); + uchar *src = slot->bitmap.buffer; + uchar *dst = glyph_buffer; + int h = slot->bitmap.rows; + if (format == Format_Mono) { + int bytes = ((info.width + 7) & ~7) >> 3; + while (h--) { + memcpy (dst, src, bytes); + dst += pitch; + src += slot->bitmap.pitch; + } + } else { + if (hsubpixel) { + while (h--) { + uint *dd = (uint *)dst; + *dd++ = 0; + for (int x = 0; x < slot->bitmap.width; x++) { + uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000); + *dd++ = a; + } + *dd++ = 0; + dst += pitch; + src += slot->bitmap.pitch; + } + } else if (vfactor != 1) { + while (h--) { + uint *dd = (uint *)dst; + for (int x = 0; x < slot->bitmap.width; x++) { + uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000); + *dd++ = a; + } + dst += pitch; + src += slot->bitmap.pitch; + } + } else { + while (h--) { + for (int x = 0; x < slot->bitmap.width; x++) { + unsigned char a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xff : 0x00); + dst[x] = a; + } + dst += pitch; + src += slot->bitmap.pitch; + } + } + } + } else { + qWarning("QFontEngine: Glyph neither outline nor bitmap format=%d", slot->format); + delete [] glyph_buffer; + return 0; + } + } + + + if (!g) { + g = new Glyph; + g->uploadedToServer = false; + g->data = 0; + } + + g->linearAdvance = slot->linearHoriAdvance >> 10; + g->width = info.width; + g->height = info.height; + g->x = -info.x; + g->y = info.y; + g->advance = info.xOff; + g->format = format; + delete [] g->data; + g->data = glyph_buffer; + + if (uploadToServer) { + uploadGlyphToServer(set, glyph, g, &info, glyph_buffer_size); + } + + set->setGlyph(glyph, subPixelPosition, g); + + return g; +} + +bool QFontEngineFT::uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const +{ + Q_UNUSED(set); + Q_UNUSED(glyphid); + Q_UNUSED(g); + Q_UNUSED(info); + Q_UNUSED(glyphDataSize); + return false; +} + +QFontEngine::FaceId QFontEngineFT::faceId() const +{ + return face_id; +} + +QFontEngine::Properties QFontEngineFT::properties() const +{ + Properties p = freetype->properties(); + if (p.postscriptName.isEmpty()) { + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.family.toUtf8()); + } + + return freetype->properties(); +} + +QFixed QFontEngineFT::emSquareSize() const +{ + if (FT_IS_SCALABLE(freetype->face)) + return freetype->face->units_per_EM; + else + return freetype->face->size->metrics.y_ppem; +} + +bool QFontEngineFT::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + return freetype->getSfntTable(tag, buffer, length); +} + +int QFontEngineFT::synthesized() const +{ + int s = 0; + if ((fontDef.style != QFont::StyleNormal) && !(freetype->face->style_flags & FT_STYLE_FLAG_ITALIC)) + s = SynthesizedItalic; + if ((fontDef.weight == QFont::Bold) && !(freetype->face->style_flags & FT_STYLE_FLAG_BOLD)) + s |= SynthesizedBold; + if (fontDef.stretch != 100 && FT_IS_SCALABLE(freetype->face)) + s |= SynthesizedStretch; + return s; +} + +QFixed QFontEngineFT::ascent() const +{ + return QFixed::fromFixed(metrics.ascender); +} + +QFixed QFontEngineFT::descent() const +{ + // subtract a pixel to work around QFontMetrics's built-in + 1 + return QFixed::fromFixed(-metrics.descender - 64); +} + +QFixed QFontEngineFT::leading() const +{ + return QFixed::fromFixed(metrics.height - metrics.ascender + metrics.descender); +} + +QFixed QFontEngineFT::xHeight() const +{ + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2); + if (os2 && os2->sxHeight) { + lockFace(); + QFixed answer = QFixed(os2->sxHeight*freetype->face->size->metrics.y_ppem)/freetype->face->units_per_EM; + unlockFace(); + return answer; + } + return QFontEngine::xHeight(); +} + +QFixed QFontEngineFT::averageCharWidth() const +{ + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2); + if (os2 && os2->xAvgCharWidth) { + lockFace(); + QFixed answer = QFixed(os2->xAvgCharWidth*freetype->face->size->metrics.x_ppem)/freetype->face->units_per_EM; + unlockFace(); + return answer; + } + return QFontEngine::averageCharWidth(); +} + +qreal QFontEngineFT::maxCharWidth() const +{ + return metrics.max_advance >> 6; +} + +static const ushort char_table[] = { + 40, + 67, + 70, + 75, + 86, + 88, + 89, + 91, + 102, + 114, + 124, + 127, + 205, + 645, + 884, + 922, + 1070, + 12386 +}; + +static const int char_table_entries = sizeof(char_table)/sizeof(ushort); + + +qreal QFontEngineFT::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) + (void) minRightBearing(); // calculates both + return lbearing.toReal(); +} + +qreal QFontEngineFT::minRightBearing() const +{ + if (rbearing == SHRT_MIN) { + lbearing = rbearing = 0; + const QChar *ch = (const QChar *)(const void*)char_table; + QGlyphLayoutArray<char_table_entries> glyphs; + int ng = char_table_entries; + stringToCMap(ch, char_table_entries, &glyphs, &ng, QTextEngine::GlyphIndicesOnly); + while (--ng) { + if (glyphs.glyphs[ng]) { + glyph_metrics_t gi = const_cast<QFontEngineFT *>(this)->boundingBox(glyphs.glyphs[ng]); + lbearing = qMin(lbearing, gi.x); + rbearing = qMin(rbearing, (gi.xoff - gi.x - gi.width)); + } + } + } + return rbearing.toReal(); +} + +QFixed QFontEngineFT::lineThickness() const +{ + return line_thickness; +} + +QFixed QFontEngineFT::underlinePosition() const +{ + return underline_position; +} + +void QFontEngineFT::doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const +{ + if (!kerning_pairs_loaded) { + kerning_pairs_loaded = true; + lockFace(); + if (freetype->face->size->metrics.x_ppem != 0) { + QFixed scalingFactor(freetype->face->units_per_EM/freetype->face->size->metrics.x_ppem); + unlockFace(); + const_cast<QFontEngineFT *>(this)->loadKerningPairs(scalingFactor); + } else { + unlockFace(); + } + } + QFontEngine::doKerning(g, flags); +} + +QFontEngineFT::QGlyphSet *QFontEngineFT::loadTransformedGlyphSet(const QTransform &matrix) +{ + if (matrix.type() > QTransform::TxShear) + return 0; + + // FT_Set_Transform only supports scalable fonts + if (!FT_IS_SCALABLE(freetype->face)) + return 0; + + FT_Matrix m; + m.xx = FT_Fixed(matrix.m11() * 65536); + m.xy = FT_Fixed(-matrix.m21() * 65536); + m.yx = FT_Fixed(-matrix.m12() * 65536); + m.yy = FT_Fixed(matrix.m22() * 65536); + + QGlyphSet *gs = 0; + + for (int i = 0; i < transformedGlyphSets.count(); ++i) { + const QGlyphSet &g = transformedGlyphSets.at(i); + if (g.transformationMatrix.xx == m.xx + && g.transformationMatrix.xy == m.xy + && g.transformationMatrix.yx == m.yx + && g.transformationMatrix.yy == m.yy) { + + // found a match, move it to the front + transformedGlyphSets.move(i, 0); + gs = &transformedGlyphSets[0]; + break; + } + } + + if (!gs) { + // don't try to load huge fonts + bool draw_as_outline = fontDef.pixelSize * qSqrt(qAbs(matrix.det())) >= 64; + if (draw_as_outline) + return 0; + + // don't cache more than 10 transformations + if (transformedGlyphSets.count() >= 10) { + transformedGlyphSets.move(transformedGlyphSets.size() - 1, 0); + freeServerGlyphSet(transformedGlyphSets.at(0).id); + } else { + transformedGlyphSets.prepend(QGlyphSet()); + } + gs = &transformedGlyphSets[0]; + + gs->clear(); + + gs->id = allocateServerGlyphSet(); + + gs->transformationMatrix = m; + gs->outline_drawing = draw_as_outline; + } + + return gs; +} + +QFixed QFontEngineFT::subPixelPositionForX(QFixed x) +{ + int m_subPixelPositionCount = 4; + if (!supportsSubPixelPositions()) + return 0; + + QFixed subPixelPosition; + if (x != 0) { + subPixelPosition = x - x.floor(); + QFixed fraction = (subPixelPosition / QFixed::fromReal(1.0 / m_subPixelPositionCount)).floor(); + subPixelPosition = fraction / QFixed(m_subPixelPositionCount); + } + return subPixelPosition; +} + +bool QFontEngineFT::loadGlyphs(QGlyphSet *gs, const glyph_t *glyphs, int num_glyphs, + const QFixedPoint *positions, + GlyphFormat format) +{ + FT_Face face = 0; + + for (int i = 0; i < num_glyphs; ++i) { + QFixed spp = subPixelPositionForX(positions[i].x); + Glyph *glyph = gs->getGlyph(glyphs[i], spp); + if (glyph == 0 || glyph->format != format) { + if (!face) { + face = lockFace(); + FT_Matrix m = matrix; + FT_Matrix_Multiply(&gs->transformationMatrix, &m); + FT_Set_Transform(face, &m, 0); + freetype->matrix = m; + } + if (!loadGlyph(gs, glyphs[i], spp, format)) { + unlockFace(); + return false; + } + } + } + + if (face) + unlockFace(); + + return true; +} + +void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + FT_Face face = lockFace(Unscaled); + FT_Set_Transform(face, 0, 0); + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + int left = face->glyph->metrics.horiBearingX; + int right = face->glyph->metrics.horiBearingX + face->glyph->metrics.width; + int top = face->glyph->metrics.horiBearingY; + int bottom = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; + + QFixedPoint p; + p.x = 0; + p.y = 0; + + metrics->width = QFixed::fromFixed(right-left); + metrics->height = QFixed::fromFixed(top-bottom); + metrics->x = QFixed::fromFixed(left); + metrics->y = QFixed::fromFixed(-top); + metrics->xoff = QFixed::fromFixed(face->glyph->advance.x); + + if (!FT_IS_SCALABLE(freetype->face)) + QFreetypeFace::addBitmapToPath(face->glyph, p, path); + else + QFreetypeFace::addGlyphToPath(face, face->glyph, p, path, face->units_per_EM << 6, face->units_per_EM << 6); + + FT_Set_Transform(face, &freetype->matrix, 0); + unlockFace(); +} + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +bool QFontEngineFT::canRender(const QChar *string, int len) +{ + FT_Face face = freetype->face; +#if 0 + if (_cmap != -1) { + lockFace(); + for ( int i = 0; i < len; i++ ) { + unsigned int uc = getChar(string, i, len); + if (!FcCharSetHasChar (_font->charset, uc) && getAdobeCharIndex(face, _cmap, uc) == 0) { + allExist = false; + break; + } + } + unlockFace(); + } else +#endif + { + for ( int i = 0; i < len; i++ ) { + unsigned int uc = getChar(string, i, len); + if (!FT_Get_Char_Index(face, uc)) + return false; + } + } + return true; +} + +void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + if (FT_IS_SCALABLE(freetype->face)) { + QFontEngine::addOutlineToPath(x, y, glyphs, path, flags); + } else { + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> positioned_glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + FT_Face face = lockFace(Unscaled); + for (int gl = 0; gl < glyphs.numGlyphs; gl++) { + FT_UInt glyph = positioned_glyphs[gl]; + FT_Load_Glyph(face, glyph, FT_LOAD_TARGET_MONO); + freetype->addBitmapToPath(face->glyph, positions[gl], path); + } + unlockFace(); + } +} + +void QFontEngineFT::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + FT_Face face = lockFace(Unscaled); + + for (int gl = 0; gl < numGlyphs; gl++) { + FT_UInt glyph = glyphs[gl]; + + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + FT_GlyphSlot g = face->glyph; + if (g->format != FT_GLYPH_FORMAT_OUTLINE) + continue; + QFreetypeFace::addGlyphToPath(face, g, positions[gl], path, xsize, ysize); + } + unlockFace(); +} + +bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if !defined(QT_NO_FONTCONFIG) + extern QMutex *qt_fontdatabase_mutex(); + QMutex *mtx = 0; +#endif + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (freetype->symbol_map) { + FT_Face face = freetype->face; + for ( int i = 0; i < len; ++i ) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0; + if ( !glyphs->glyphs[glyph_pos] ) { + glyph_t glyph; +#if !defined(QT_NO_FONTCONFIG) + if (!mtx) { + mtx = qt_fontdatabase_mutex(); + mtx->lock(); + } + + if (freetype->charset != 0 && FcCharSetHasChar(freetype->charset, uc)) { +#else + if (false) { +#endif + redo0: + glyph = FT_Get_Char_Index(face, uc); + if (!glyph && (uc == 0xa0 || uc == 0x9)) { + uc = 0x20; + goto redo0; + } + } else { + FT_Set_Charmap(face, freetype->symbol_map); + glyph = FT_Get_Char_Index(face, uc); + FT_Set_Charmap(face, freetype->unicode_map); + } + glyphs->glyphs[glyph_pos] = glyph; + if (uc < QFreetypeFace::cmapCacheSize) + freetype->cmapCache[uc] = glyph; + } + ++glyph_pos; + } + } else { + FT_Face face = freetype->face; + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0; + if (!glyphs->glyphs[glyph_pos]) { +#if !defined(QT_NO_FONTCONFIG) + if (!mtx) { + mtx = qt_fontdatabase_mutex(); + mtx->lock(); + } + + if (freetype->charset == 0 || FcCharSetHasChar(freetype->charset, uc)) +#endif + { + redo: + glyph_t glyph = FT_Get_Char_Index(face, uc); + if (!glyph && (uc == 0xa0 || uc == 0x9)) { + uc = 0x20; + goto redo; + } + glyphs->glyphs[glyph_pos] = glyph; + if (uc < QFreetypeFace::cmapCacheSize) + freetype->cmapCache[uc] = glyph; + } + } + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + +#if !defined(QT_NO_FONTCONFIG) + if (mtx) + mtx->unlock(); +#endif + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineFT::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + FT_Face face = 0; + bool design = (default_hint_style == HintNone || + default_hint_style == HintLight || + (flags & HB_ShaperFlag_UseDesignMetrics)); + for (int i = 0; i < glyphs->numGlyphs; i++) { + Glyph *g = defaultGlyphSet.getGlyph(glyphs->glyphs[i]); + if (g) { + glyphs->advances_x[i] = design ? QFixed::fromFixed(g->linearAdvance) : QFixed(g->advance); + } else { + if (!face) + face = lockFace(); + g = loadGlyph(glyphs->glyphs[i], 0, Format_None, true); + glyphs->advances_x[i] = design ? QFixed::fromFixed(face->glyph->linearHoriAdvance >> 10) + : QFixed::fromFixed(face->glyph->metrics.horiAdvance).round(); + } + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = 0; + } + if (face) + unlockFace(); +} + +glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs) +{ + + FT_Face face = 0; + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + Glyph *g = defaultGlyphSet.getGlyph(glyphs.glyphs[i]); + if (!g) { + if (!face) + face = lockFace(); + g = loadGlyph(glyphs.glyphs[i], 0, Format_None, true); + } + if (g) { + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y - g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += qRound(g->advance); + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + QFixed x = overall.xoff + glyphs.offsets[i].x - (-TRUNC(left)); + QFixed y = overall.yoff + glyphs.offsets[i].y - TRUNC(top); + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + TRUNC(right - left)); + ymax = qMax(ymax, y + TRUNC(top - bottom)); + overall.xoff += qRound(TRUNC(ROUND(face->glyph->advance.x))); + } + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + if (face) + unlockFace(); + + return overall; +} + +glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph) +{ + FT_Face face = 0; + glyph_metrics_t overall; + Glyph *g = defaultGlyphSet.getGlyph(glyph); + if (!g) { + face = lockFace(); + g = loadGlyph(glyph, 0, Format_None, true); + } + if (g) { + overall.x = g->x; + overall.y = -g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + overall.xoff = overall.xoff.round(); + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + overall.width = TRUNC(right-left); + overall.height = TRUNC(top-bottom); + overall.x = TRUNC(left); + overall.y = -TRUNC(top); + overall.xoff = TRUNC(ROUND(face->glyph->advance.x)); + } + if (face) + unlockFace(); + return overall; +} + +glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + return alphaMapBoundingBox(glyph, 0, matrix, QFontEngine::Format_None); +} + +glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, QFixed subPixelPosition, const QTransform &matrix, QFontEngine::GlyphFormat format) +{ + FT_Face face = 0; + glyph_metrics_t overall; + QGlyphSet *glyphSet = 0; + if (matrix.type() > QTransform::TxTranslate && FT_IS_SCALABLE(freetype->face)) { + // TODO move everything here to a method of its own to access glyphSets + // to be shared with a new method that will replace loadTransformedGlyphSet() + FT_Matrix m; + m.xx = FT_Fixed(matrix.m11() * 65536); + m.xy = FT_Fixed(-matrix.m21() * 65536); + m.yx = FT_Fixed(-matrix.m12() * 65536); + m.yy = FT_Fixed(matrix.m22() * 65536); + for (int i = 0; i < transformedGlyphSets.count(); ++i) { + const QGlyphSet &g = transformedGlyphSets.at(i); + if (g.transformationMatrix.xx == m.xx + && g.transformationMatrix.xy == m.xy + && g.transformationMatrix.yx == m.yx + && g.transformationMatrix.yy == m.yy) { + + // found a match, move it to the front + transformedGlyphSets.move(i, 0); + glyphSet = &transformedGlyphSets[0]; + break; + } + } + + if (!glyphSet) { + // don't cache more than 10 transformations + if (transformedGlyphSets.count() >= 10) { + transformedGlyphSets.move(transformedGlyphSets.size() - 1, 0); + freeServerGlyphSet(transformedGlyphSets.at(0).id); + } else { + transformedGlyphSets.prepend(QGlyphSet()); + } + glyphSet = &transformedGlyphSets[0]; + glyphSet->clear(); + glyphSet->id = allocateServerGlyphSet(); + glyphSet->transformationMatrix = m; + } + Q_ASSERT(glyphSet); + } else { + glyphSet = &defaultGlyphSet; + } + Glyph * g = glyphSet->getGlyph(glyph); + if (!g || g->format != format) { + face = lockFace(); + FT_Matrix m = this->matrix; + FT_Matrix_Multiply(&glyphSet->transformationMatrix, &m); + freetype->matrix = m; + g = loadGlyph(glyphSet, glyph, subPixelPosition, format); + } + + if (g) { + overall.x = g->x; + overall.y = -g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + overall.width = TRUNC(right-left); + overall.height = TRUNC(top-bottom); + overall.x = TRUNC(left); + overall.y = -TRUNC(top); + overall.xoff = TRUNC(ROUND(face->glyph->advance.x)); + } + if (face) + unlockFace(); + return overall; +} + +QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition) +{ + lockFace(); + + GlyphFormat glyph_format = antialias ? Format_A8 : Format_Mono; + + Glyph *glyph = defaultGlyphSet.outline_drawing ? 0 : loadGlyph(g, subPixelPosition, glyph_format); + if (!glyph) { + unlockFace(); + return QFontEngine::alphaMapForGlyph(g); + } + + const int pitch = antialias ? (glyph->width + 3) & ~3 : ((glyph->width + 31)/32) * 4; + + QImage img(glyph->width, glyph->height, antialias ? QImage::Format_Indexed8 : QImage::Format_Mono); + if (antialias) { + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + img.setColorTable(colors); + } else { + QVector<QRgb> colors(2); + colors[0] = qRgba(0, 0, 0, 0); + colors[1] = qRgba(0, 0, 0, 255); + img.setColorTable(colors); + } + Q_ASSERT(img.bytesPerLine() == pitch); + if (glyph->width) { + for (int y = 0; y < glyph->height; ++y) + memcpy(img.scanLine(y), &glyph->data[y * pitch], pitch); + } + unlockFace(); + + return img; +} + +QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, QFixed subPixelPosition, int margin, const QTransform &t) +{ + if (t.type() > QTransform::TxTranslate) + return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, margin, t); + + lockFace(); + + GlyphFormat glyph_format = Format_A32; + + Glyph *glyph = defaultGlyphSet.outline_drawing ? 0 : loadGlyph(g, subPixelPosition, glyph_format); + if (!glyph) { + unlockFace(); + return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, margin, t); + } + + QImage img(glyph->width, glyph->height, QImage::Format_RGB32); + memcpy(img.bits(), glyph->data, 4 * glyph->width * glyph->height); + unlockFace(); + + return img; +} + +void QFontEngineFT::removeGlyphFromCache(glyph_t glyph) +{ + defaultGlyphSet.removeGlyphFromCache(glyph, 0); +} + +int QFontEngineFT::glyphCount() const +{ + int count = 0; + FT_Face face = lockFace(); + if (face) { + count = face->num_glyphs; + unlockFace(); + } + return count; +} + +FT_Face QFontEngineFT::lockFace(Scaling scale) const +{ + freetype->lock(); + FT_Face face = freetype->face; + if (scale == Unscaled) { + FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0); + freetype->xsize = face->units_per_EM << 6; + freetype->ysize = face->units_per_EM << 6; + } else if (freetype->xsize != xsize || freetype->ysize != ysize) { + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + freetype->xsize = xsize; + freetype->ysize = ysize; + } + if (freetype->matrix.xx != matrix.xx || + freetype->matrix.yy != matrix.yy || + freetype->matrix.xy != matrix.xy || + freetype->matrix.yx != matrix.yx) { + freetype->matrix = matrix; + FT_Set_Transform(face, &freetype->matrix, 0); + } + + return face; +} + +void QFontEngineFT::unlockFace() const +{ + freetype->unlock(); +} + +FT_Face QFontEngineFT::non_locked_face() const +{ + return freetype->face; +} + + +QFontEngineFT::QGlyphSet::QGlyphSet() + : id(0), outline_drawing(false) +{ + transformationMatrix.xx = 0x10000; + transformationMatrix.yy = 0x10000; + transformationMatrix.xy = 0; + transformationMatrix.yx = 0; + memset(fast_glyph_data, 0, sizeof(fast_glyph_data)); + fast_glyph_count = 0; +} + +QFontEngineFT::QGlyphSet::~QGlyphSet() +{ + clear(); +} + +void QFontEngineFT::QGlyphSet::clear() +{ + if (fast_glyph_count > 0) { + for (int i = 0; i < 256; ++i) { + if (fast_glyph_data[i]) { + delete fast_glyph_data[i]; + fast_glyph_data[i] = 0; + } + } + fast_glyph_count = 0; + } + qDeleteAll(glyph_data); + glyph_data.clear(); +} + +void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index, QFixed subPixelPosition) +{ + if (useFastGlyphData(index, subPixelPosition)) { + if (fast_glyph_data[index]) { + delete fast_glyph_data[index]; + fast_glyph_data[index] = 0; + if (fast_glyph_count > 0) + --fast_glyph_count; + } + } else { + delete glyph_data.take(GlyphAndSubPixelPosition(index, subPixelPosition)); + } +} + +void QFontEngineFT::QGlyphSet::setGlyph(glyph_t index, QFixed subPixelPosition, Glyph *glyph) +{ + if (useFastGlyphData(index, subPixelPosition)) { + if (!fast_glyph_data[index]) + ++fast_glyph_count; + fast_glyph_data[index] = glyph; + } else { + glyph_data.insert(GlyphAndSubPixelPosition(index, subPixelPosition), glyph); + } +} + +unsigned long QFontEngineFT::allocateServerGlyphSet() +{ + return 0; +} + +void QFontEngineFT::freeServerGlyphSet(unsigned long id) +{ + Q_UNUSED(id); +} + +HB_Error QFontEngineFT::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + lockFace(); + bool hsubpixel = true; + int vfactor = 1; + int load_flags = loadFlags(0, Format_A8, flags, hsubpixel, vfactor); + HB_Error result = freetype->getPointInOutline(glyph, load_flags, point, xpos, ypos, nPoints); + unlockFace(); + return result; +} + +QT_END_NAMESPACE + +#endif // QT_NO_FREETYPE diff --git a/src/gui/text/qfontengine_ft_p.h b/src/gui/text/qfontengine_ft_p.h new file mode 100644 index 0000000000..887efed843 --- /dev/null +++ b/src/gui/text/qfontengine_ft_p.h @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QFONTENGINE_FT_P_H +#define QFONTENGINE_FT_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 "qfontengine_p.h" + +#ifndef QT_NO_FREETYPE + +#include <ft2build.h> +#include FT_FREETYPE_H + +#if defined(Q_WS_X11) +#include <private/qt_x11_p.h> +#endif + +#include <unistd.h> + +#ifndef QT_NO_FONTCONFIG +#include <fontconfig/fontconfig.h> +#endif + +#include <qmutex.h> + +#include <harfbuzz-shaper.h> + +QT_BEGIN_NAMESPACE + +class QFontEngineFTRawFont; + +/* + * This struct represents one font file on disk (like Arial.ttf) and is shared between all the font engines + * that show this font file (at different pixel sizes). + */ +struct QFreetypeFace +{ + void computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing); + QFontEngine::Properties properties() const; + bool getSfntTable(uint tag, uchar *buffer, uint *length) const; + + static QFreetypeFace *getFace(const QFontEngine::FaceId &face_id, + const QByteArray &fontData = QByteArray()); + void release(const QFontEngine::FaceId &face_id); + + // locks the struct for usage. Any read/write operations require locking. + void lock() + { + _lock.lock(); + } + void unlock() + { + _lock.unlock(); + } + + FT_Face face; + HB_Face hbFace; +#ifndef QT_NO_FONTCONFIG + FcCharSet *charset; +#endif + int xsize; // 26.6 + int ysize; // 26.6 + FT_Matrix matrix; + FT_CharMap unicode_map; + FT_CharMap symbol_map; + + enum { cmapCacheSize = 0x200 }; + glyph_t cmapCache[cmapCacheSize]; + + int fsType() const; + + HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale); + static void addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path, bool = false); + +private: + friend class QFontEngineFTRawFont; + friend class QScopedPointerDeleter<QFreetypeFace>; + QFreetypeFace() : _lock(QMutex::Recursive) {} + ~QFreetypeFace() {} + QAtomicInt ref; + QMutex _lock; + QByteArray fontData; +}; + +class Q_GUI_EXPORT QFontEngineFT : public QFontEngine +{ +public: + + /* we don't cache glyphs that are too large anyway, so we can make this struct rather small */ + struct Glyph { + ~Glyph(); + short linearAdvance; + unsigned char width; + unsigned char height; + signed char x; + signed char y; + signed char advance; + signed char format; + uchar *data; + unsigned int uploadedToServer : 1; + }; + + enum SubpixelAntialiasingType { + Subpixel_None, + Subpixel_RGB, + Subpixel_BGR, + Subpixel_VRGB, + Subpixel_VBGR + }; + +#if defined(Q_WS_X11) && !defined(QT_NO_XRENDER) + typedef XGlyphInfo GlyphInfo; +#else + struct GlyphInfo { + unsigned short width; + unsigned short height; + short x; + short y; + short xOff; + short yOff; + }; +#endif + + struct GlyphAndSubPixelPosition + { + GlyphAndSubPixelPosition(glyph_t g, QFixed spp) : glyph(g), subPixelPosition(spp) {} + + bool operator==(const GlyphAndSubPixelPosition &other) const + { + return glyph == other.glyph && subPixelPosition == other.subPixelPosition; + } + + glyph_t glyph; + QFixed subPixelPosition; + }; + + struct QGlyphSet + { + QGlyphSet(); + ~QGlyphSet(); + FT_Matrix transformationMatrix; + unsigned long id; // server sided id, GlyphSet for X11 + bool outline_drawing; + + void removeGlyphFromCache(glyph_t index, QFixed subPixelPosition); + void clear(); + inline bool useFastGlyphData(glyph_t index, QFixed subPixelPosition) const { + return (index < 256 && subPixelPosition == 0); + } + inline Glyph *getGlyph(glyph_t index, QFixed subPixelPosition = 0) const + { + if (useFastGlyphData(index, subPixelPosition)) + return fast_glyph_data[index]; + return glyph_data.value(GlyphAndSubPixelPosition(index, subPixelPosition)); + } + void setGlyph(glyph_t index, QFixed spp, Glyph *glyph); + +private: + mutable QHash<GlyphAndSubPixelPosition, Glyph *> glyph_data; // maps from glyph index to glyph data + mutable Glyph *fast_glyph_data[256]; // for fast lookup of glyphs < 256 + mutable int fast_glyph_count; + }; + + virtual QFontEngine::FaceId faceId() const; + virtual QFontEngine::Properties properties() const; + virtual QFixed emSquareSize() const; + virtual bool supportsSubPixelPositions() const + { + return default_hint_style == HintLight || + default_hint_style == HintNone; + } + + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + + void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + inline virtual Type type() const + { return QFontEngine::Freetype; } + inline virtual const char *name() const + { return "freetype"; } + + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + + virtual bool canRender(const QChar *string, int len); + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + virtual glyph_metrics_t boundingBox(glyph_t glyph, const QTransform &matrix); + + virtual void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const; + virtual QImage alphaMapForGlyph(glyph_t g) { return alphaMapForGlyph(g, 0); } + virtual QImage alphaMapForGlyph(glyph_t, QFixed); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, + QFixed subPixelPosition, + const QTransform &matrix, + QFontEngine::GlyphFormat format); + virtual void removeGlyphFromCache(glyph_t glyph); + + virtual int glyphCount() const; + + enum Scaling { + Scaled, + Unscaled + }; + FT_Face lockFace(Scaling scale = Scaled) const; + void unlockFace() const; + + FT_Face non_locked_face() const; + + inline bool drawAntialiased() const { return antialias; } + inline bool invalid() const { return xsize == 0 && ysize == 0; } + inline bool isBitmapFont() const { return defaultFormat == Format_Mono; } + + inline Glyph *loadGlyph(uint glyph, QFixed subPixelPosition, GlyphFormat format = Format_None, bool fetchMetricsOnly = false) const + { return loadGlyph(&defaultGlyphSet, glyph, subPixelPosition, format, fetchMetricsOnly); } + Glyph *loadGlyph(QGlyphSet *set, uint glyph, QFixed subPixelPosition, GlyphFormat = Format_None, bool fetchMetricsOnly = false) const; + + QGlyphSet *defaultGlyphs() { return &defaultGlyphSet; } + GlyphFormat defaultGlyphFormat() const { return defaultFormat; } + + inline Glyph *cachedGlyph(glyph_t g) const { return defaultGlyphSet.getGlyph(g, 0); } + + QGlyphSet *loadTransformedGlyphSet(const QTransform &matrix); + QFixed subPixelPositionForX(QFixed x); + bool loadGlyphs(QGlyphSet *gs, const glyph_t *glyphs, int num_glyphs, + const QFixedPoint *positions, + GlyphFormat format = Format_Render); + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + virtual void draw(QPaintEngine * /*p*/, qreal /*x*/, qreal /*y*/, const QTextItemInt & /*si*/) {} +#endif + + QFontEngineFT(const QFontDef &fd); + virtual ~QFontEngineFT(); + + bool init(FaceId faceId, bool antiaalias, GlyphFormat defaultFormat = Format_None, + const QByteArray &fontData = QByteArray()); + bool init(FaceId faceId, bool antialias, GlyphFormat format, + QFreetypeFace *freetypeFace); + + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + enum HintStyle { + HintNone, + HintLight, + HintMedium, + HintFull + }; + + void setDefaultHintStyle(HintStyle style); + HintStyle defaultHintStyle() const { return default_hint_style; } +protected: + + void freeGlyphSets(); + + virtual bool uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const; + virtual unsigned long allocateServerGlyphSet(); + virtual void freeServerGlyphSet(unsigned long id); + + QFreetypeFace *freetype; + int default_load_flags; + + + HintStyle default_hint_style; + + bool antialias; + bool transform; + bool embolden; + SubpixelAntialiasingType subpixelType; + int lcdFilterType; + bool canUploadGlyphsToServer; + bool embeddedbitmap; + +private: + friend class QFontEngineFTRawFont; + + QFontEngineFT::Glyph *loadGlyphMetrics(QGlyphSet *set, uint glyph, GlyphFormat format) const; + int loadFlags(QGlyphSet *set, GlyphFormat format, int flags, bool &hsubpixel, int &vfactor) const; + + GlyphFormat defaultFormat; + FT_Matrix matrix; + + QList<QGlyphSet> transformedGlyphSets; + mutable QGlyphSet defaultGlyphSet; + + QFontEngine::FaceId face_id; + + int xsize; + int ysize; + + mutable QFixed lbearing; + mutable QFixed rbearing; + QFixed line_thickness; + QFixed underline_position; + + FT_Size_Metrics metrics; + mutable bool kerning_pairs_loaded; +}; + +inline uint qHash(const QFontEngineFT::GlyphAndSubPixelPosition &g) +{ + return (g.glyph << 8) | (g.subPixelPosition * 10).round().toInt(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_FREETYPE + +#endif // QFONTENGINE_FT_P_H diff --git a/src/gui/text/qfontengine_mac.mm b/src/gui/text/qfontengine_mac.mm new file mode 100644 index 0000000000..673a7c86fa --- /dev/null +++ b/src/gui/text/qfontengine_mac.mm @@ -0,0 +1,1236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_mac_p.h" + +#include <private/qapplication_p.h> +#include <private/qfontengine_p.h> +#include <private/qpainter_p.h> +#include <private/qtextengine_p.h> +#include <qbitmap.h> +#include <private/qpaintengine_mac_p.h> +#include <private/qprintengine_mac_p.h> +#include <qglobal.h> +#include <qpixmap.h> +#include <qpixmapcache.h> +#include <qvarlengtharray.h> +#include <qdebug.h> +#include <qendian.h> +#include <qmath.h> +#include <private/qimage_p.h> + +#include <ApplicationServices/ApplicationServices.h> +#include <AppKit/AppKit.h> + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + QFontEngine debug facilities + *****************************************************************************/ +//#define DEBUG_ADVANCES + +extern int qt_antialiasing_threshold; // QApplication.cpp + +#ifndef FixedToQFixed +#define FixedToQFixed(a) QFixed::fromFixed((a) >> 10) +#define QFixedToFixed(x) ((x).value() << 10) +#endif + +class QMacFontPath +{ + float x, y; + QPainterPath *path; +public: + inline QMacFontPath(float _x, float _y, QPainterPath *_path) : x(_x), y(_y), path(_path) { } + inline void setPosition(float _x, float _y) { x = _x; y = _y; } + inline void advance(float _x) { x += _x; } + static OSStatus lineTo(const Float32Point *, void *); + static OSStatus cubicTo(const Float32Point *, const Float32Point *, + const Float32Point *, void *); + static OSStatus moveTo(const Float32Point *, void *); + static OSStatus closePath(void *); +}; + +OSStatus QMacFontPath::lineTo(const Float32Point *pt, void *data) + +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->lineTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::cubicTo(const Float32Point *cp1, const Float32Point *cp2, + const Float32Point *ep, void *data) + +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->cubicTo(p->x + cp1->x, p->y + cp1->y, + p->x + cp2->x, p->y + cp2->y, + p->x + ep->x, p->y + ep->y); + return noErr; +} + +OSStatus QMacFontPath::moveTo(const Float32Point *pt, void *data) +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->moveTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::closePath(void *data) +{ + static_cast<QMacFontPath*>(data)->path->closeSubpath(); + return noErr; +} + + +#ifndef QT_MAC_USE_COCOA +QFontEngineMacMulti::QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + this->kerning = kerning; + + // hopefully (CTFontCreateWithName or CTFontCreateWithFontDescriptor) + CTFontCreateCopyWithSymbolicTraits + // (or CTFontCreateWithQuickdrawInstance) + FMFontFamily fmFamily; + FMFontStyle fntStyle = 0; + fmFamily = FMGetFontFamilyFromATSFontFamilyRef(atsFamily); + if (fmFamily == kInvalidFontFamily) { + // Use the ATSFont then... + fontID = FMGetFontFromATSFontRef(atsFontRef); + } else { + if (fontDef.weight >= QFont::Bold) + fntStyle |= ::bold; + if (fontDef.style != QFont::StyleNormal) + fntStyle |= ::italic; + + FMFontStyle intrinsicStyle; + FMFont fnt = 0; + if (FMGetFontFromFontFamilyInstance(fmFamily, fntStyle, &fnt, &intrinsicStyle) == noErr) + fontID = FMGetATSFontRefFromFont(fnt); + } + + // CFDictionaryRef, <CTStringAttributes.h> + OSStatus status; + + status = ATSUCreateTextLayout(&textLayout); + Q_ASSERT(status == noErr); + + const int maxAttributeCount = 5; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(fontDef.pixelSize, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + tags[attributeCount] = kATSUFontTag; + sizes[attributeCount] = sizeof(fontID); + values[attributeCount] = &this->fontID; + ++attributeCount; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + tags[attributeCount] = kATSUFontMatrixTag; + sizes[attributeCount] = sizeof(transform); + values[attributeCount] = &transform; + ++attributeCount; + } + + status = ATSUCreateStyle(&style); + Q_ASSERT(status == noErr); + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + status = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(status == noErr); + + QFontEngineMac *fe = new QFontEngineMac(style, fontID, fontDef, this); + fe->ref.ref(); + engines.append(fe); +} + +QFontEngineMacMulti::~QFontEngineMacMulti() +{ + ATSUDisposeTextLayout(textLayout); + ATSUDisposeStyle(style); + + for (int i = 0; i < engines.count(); ++i) { + QFontEngineMac *fe = const_cast<QFontEngineMac *>(static_cast<const QFontEngineMac *>(engines.at(i))); + fe->multiEngine = 0; + if (!fe->ref.deref()) + delete fe; + } + engines.clear(); +} + +struct QGlyphLayoutInfo +{ + QGlyphLayout *glyphs; + int *numGlyphs; + bool callbackCalled; + int *mappedFonts; + QTextEngine::ShaperFlags flags; + QFontEngineMacMulti::ShaperItem *shaperItem; + unsigned int styleStrategy; +}; + +static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon, + void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus) +{ + Q_UNUSED(selector); + Q_UNUSED(operationExtraParameter); + + QGlyphLayoutInfo *nfo = reinterpret_cast<QGlyphLayoutInfo *>(refCon); + nfo->callbackCalled = true; + + ATSLayoutRecord *layoutData = 0; + ItemCount itemCount = 0; + + OSStatus e = noErr; + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + /*iCreate =*/ false, + (void **) &layoutData, + &itemCount); + if (e != noErr) + return e; + + *nfo->numGlyphs = itemCount - 1; + + Fixed *baselineDeltas = 0; + + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + /*iCreate =*/ true, + (void **) &baselineDeltas, + &itemCount); + if (e != noErr) + return e; + + int nextCharStop = -1; + int currentClusterGlyph = -1; // first glyph in log cluster + QFontEngineMacMulti::ShaperItem *item = nfo->shaperItem; + if (item->charAttributes) { + item = nfo->shaperItem; +#if !defined(QT_NO_DEBUG) + int surrogates = 0; + const QChar *str = item->string; + for (int i = item->from; i < item->from + item->length - 1; ++i) { + surrogates += (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + } +#endif + for (nextCharStop = item->from; nextCharStop < item->from + item->length; ++nextCharStop) + if (item->charAttributes[nextCharStop].charStop) + break; + nextCharStop -= item->from; + } + + nfo->glyphs->attributes[0].clusterStart = true; + int glyphIdx = 0; + int glyphIncrement = 1; + if (nfo->flags & QTextEngine::RightToLeft) { + glyphIdx = itemCount - 2; + glyphIncrement = -1; + } + for (int i = 0; i < *nfo->numGlyphs; ++i, glyphIdx += glyphIncrement) { + + int charOffset = layoutData[glyphIdx].originalOffset / sizeof(UniChar); + const int fontIdx = nfo->mappedFonts[charOffset]; + + ATSGlyphRef glyphId = layoutData[glyphIdx].glyphID; + + QFixed yAdvance = FixedToQFixed(baselineDeltas[glyphIdx]); + QFixed xAdvance = FixedToQFixed(layoutData[glyphIdx + 1].realPos - layoutData[glyphIdx].realPos); + + if (nfo->styleStrategy & QFont::ForceIntegerMetrics) { + yAdvance = yAdvance.round(); + xAdvance = xAdvance.round(); + } + + if (glyphId != 0xffff || i == 0) { + if (i < nfo->glyphs->numGlyphs) + { + nfo->glyphs->glyphs[i] = (glyphId & 0x00ffffff) | (fontIdx << 24); + + nfo->glyphs->advances_y[i] = yAdvance; + nfo->glyphs->advances_x[i] = xAdvance; + } + } else { + // ATSUI gives us 0xffff as glyph id at the index in the glyph array for + // a character position that maps to a ligtature. Such a glyph id does not + // result in any visual glyph, but it may have an advance, which is why we + // sum up the glyph advances. + --i; + nfo->glyphs->advances_y[i] += yAdvance; + nfo->glyphs->advances_x[i] += xAdvance; + *nfo->numGlyphs -= 1; + } + + if (item->log_clusters) { + if (charOffset >= nextCharStop) { + nfo->glyphs->attributes[i].clusterStart = true; + currentClusterGlyph = i; + + ++nextCharStop; + for (; nextCharStop < item->length; ++nextCharStop) + if (item->charAttributes[item->from + nextCharStop].charStop) + break; + } else { + if (currentClusterGlyph == -1) + currentClusterGlyph = i; + } + item->log_clusters[charOffset] = currentClusterGlyph; + + // surrogate handling + if (charOffset < item->length - 1) { + QChar current = item->string[item->from + charOffset]; + QChar next = item->string[item->from + charOffset + 1]; + if (current.unicode() >= 0xd800 && current.unicode() < 0xdc00 + && next.unicode() >= 0xdc00 && next.unicode() < 0xe000) { + item->log_clusters[charOffset + 1] = currentClusterGlyph; + } + } + } + } + + /* + if (item) { + qDebug() << "resulting logclusters:"; + for (int i = 0; i < item->length; ++i) + qDebug() << "logClusters[" << i << "] =" << item->log_clusters[i]; + qDebug() << "clusterstarts:"; + for (int i = 0; i < *nfo->numGlyphs; ++i) + qDebug() << "clusterStart[" << i << "] =" << nfo->glyphs[i].attributes.clusterStart; + } + */ + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + (void **) &baselineDeltas); + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + (void **) &layoutData); + + *callbackStatus = kATSULayoutOperationCallbackStatusHandled; + return noErr; +} + +int QFontEngineMacMulti::fontIndexForFontID(ATSUFontID id) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (engineAt(i)->fontID == id) + return i; + } + + QFontEngineMacMulti *that = const_cast<QFontEngineMacMulti *>(this); + QFontEngineMac *fe = new QFontEngineMac(style, id, fontDef, that); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + return stringToCMap(str, len, glyphs, nglyphs, flags, /*logClusters=*/0, /*charAttributes=*/0); +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + ShaperItem shaperItem; + shaperItem.string = str; + shaperItem.from = 0; + shaperItem.length = len; + shaperItem.glyphs = *glyphs; + shaperItem.glyphs.numGlyphs = *nglyphs; + shaperItem.flags = flags; + shaperItem.log_clusters = logClusters; + shaperItem.charAttributes = charAttributes; + + const int maxChars = qMax(1, + int(SHRT_MAX / maxCharWidth()) + - 10 // subtract a few to be on the safe side + ); + if (len < maxChars || !charAttributes) + return stringToCMapInternal(str, len, glyphs, nglyphs, flags, &shaperItem); + + int charIdx = 0; + int glyphIdx = 0; + ShaperItem tmpItem = shaperItem; + + do { + tmpItem.from = shaperItem.from + charIdx; + + int charCount = qMin(maxChars, len - charIdx); + + int lastWhitespace = tmpItem.from + charCount - 1; + int lastSoftBreak = lastWhitespace; + int lastCharStop = lastSoftBreak; + for (int i = lastCharStop; i >= tmpItem.from; --i) { + if (tmpItem.charAttributes[i].whiteSpace) { + lastWhitespace = i; + break; + } if (tmpItem.charAttributes[i].lineBreakType != HB_NoBreak) { + lastSoftBreak = i; + } if (tmpItem.charAttributes[i].charStop) { + lastCharStop = i; + } + } + charCount = qMin(lastWhitespace, qMin(lastSoftBreak, lastCharStop)) - tmpItem.from + 1; + + int glyphCount = shaperItem.glyphs.numGlyphs - glyphIdx; + if (glyphCount <= 0) + return false; + tmpItem.length = charCount; + tmpItem.glyphs = shaperItem.glyphs.mid(glyphIdx, glyphCount); + tmpItem.log_clusters = shaperItem.log_clusters + charIdx; + if (!stringToCMapInternal(tmpItem.string + tmpItem.from, tmpItem.length, + &tmpItem.glyphs, &glyphCount, flags, + &tmpItem)) { + *nglyphs = glyphIdx + glyphCount; + return false; + } + for (int i = 0; i < charCount; ++i) + tmpItem.log_clusters[i] += glyphIdx; + glyphIdx += glyphCount; + charIdx += charCount; + } while (charIdx < len); + *nglyphs = glyphIdx; + glyphs->numGlyphs = glyphIdx; + + return true; +} + +bool QFontEngineMacMulti::stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags,ShaperItem *shaperItem) const +{ + //qDebug() << "stringToCMap" << QString(str, len); + + OSStatus e = noErr; + + e = ATSUSetTextPointerLocation(textLayout, (UniChar *)(str), 0, len, len); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetTextPointerLocation %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + QGlyphLayoutInfo nfo; + nfo.glyphs = glyphs; + nfo.numGlyphs = nglyphs; + nfo.callbackCalled = false; + nfo.flags = flags; + nfo.shaperItem = shaperItem; + nfo.styleStrategy = fontDef.styleStrategy; + + int prevNumGlyphs = *nglyphs; + + QVarLengthArray<int> mappedFonts(len); + for (int i = 0; i < len; ++i) + mappedFonts[i] = 0; + nfo.mappedFonts = mappedFonts.data(); + + Q_ASSERT(sizeof(void *) <= sizeof(URefCon)); + e = ATSUSetTextLayoutRefCon(textLayout, (URefCon)&nfo); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetTextLayoutRefCon %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + { + const int maxAttributeCount = 3; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + tags[attributeCount] = kATSULineLayoutOptionsTag; + ATSLineLayoutOptions layopts = kATSLineHasNoOpticalAlignment + | kATSLineIgnoreFontLeading + | kATSLineNoSpecialJustification // we do kashidas ourselves + | kATSLineDisableAllJustification + ; + + if (fontDef.styleStrategy & QFont::NoAntialias) + layopts |= kATSLineNoAntiAliasing; + + if (!kerning) + layopts |= kATSLineDisableAllKerningAdjustments; + + values[attributeCount] = &layopts; + sizes[attributeCount] = sizeof(layopts); + ++attributeCount; + + tags[attributeCount] = kATSULayoutOperationOverrideTag; + ATSULayoutOperationOverrideSpecifier spec; + spec.operationSelector = kATSULayoutOperationPostLayoutAdjustment; + spec.overrideUPP = atsuPostLayoutCallback; + values[attributeCount] = &spec; + sizes[attributeCount] = sizeof(spec); + ++attributeCount; + + // CTWritingDirection + Boolean direction; + if (flags & QTextEngine::RightToLeft) + direction = kATSURightToLeftBaseDirection; + else + direction = kATSULeftToRightBaseDirection; + tags[attributeCount] = kATSULineDirectionTag; + values[attributeCount] = &direction; + sizes[attributeCount] = sizeof(direction); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + e = ATSUSetLayoutControls(textLayout, attributeCount, tags, sizes, values); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetLayoutControls %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + } + + e = ATSUSetRunStyle(textLayout, style, 0, len); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetRunStyle %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + if (!(fontDef.styleStrategy & QFont::NoFontMerging)) { + int pos = 0; + do { + ATSUFontID substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + int fontIdx = fontIndexForFontID(substFont); + for (uint i = 0; i < changeCount; ++i) + mappedFonts[changedOffset + i] = fontIdx; + pos = changedOffset + changeCount; + ATSUSetRunStyle(textLayout, engineAt(fontIdx)->style, changedOffset, changeCount); + } else if (e == kATSUFontsNotMatched) { + pos = changedOffset + changeCount; + } + } while (pos < len && e != noErr); + } + { // trigger the a layout + // CFAttributedStringCreate, CTFramesetterCreateWithAttributedString (or perhaps Typesetter) + Rect rect; + e = ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning, kATSUToTextEnd, + /*iLocationX =*/ 0, /*iLocationY =*/ 0, + &rect); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage %s: %d", long(e), __FILE__, __LINE__); + return false; + } + } + + if (!nfo.callbackCalled) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage did not trigger callback %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + ATSUClearLayoutCache(textLayout, kATSUFromTextBeginning); + if (prevNumGlyphs < *nfo.numGlyphs) + return false; + return true; +} + +void QFontEngineMacMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_ASSERT(false); + Q_UNUSED(glyphs); + Q_UNUSED(flags); +} + +void QFontEngineMacMulti::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ + //Q_ASSERT(false); +} + +void QFontEngineMacMulti::loadEngine(int /*at*/) +{ + // should never be called! + Q_ASSERT(false); +} + +bool QFontEngineMacMulti::canRender(const QChar *string, int len) +{ + ATSUSetTextPointerLocation(textLayout, reinterpret_cast<const UniChar *>(string), 0, len, len); + ATSUSetRunStyle(textLayout, style, 0, len); + + OSStatus e = noErr; + int pos = 0; + do { + FMFont substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + // CTFontCreateForString + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + pos = changedOffset + changeCount; + } else if (e == kATSUFontsNotMatched) { + break; + } + } while (pos < len && e != noErr); + + return e == noErr || e == kATSUFontsMatched; +} + +QFontEngineMac::QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine) + : fontID(fontID), multiEngine(multiEngine), cmap(0), symbolCMap(false) +{ + fontDef = def; + ATSUCreateAndCopyStyle(baseStyle, &style); + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + cgFont = CGFontCreateWithPlatformFont(&atsFont); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + synthesisFlags = 0; + + // synthesizing using CG is not recommended + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian<quint16>(data); + } + + Boolean atsuBold = false; + Boolean atsuItalic = false; + if (fontDef.weight >= QFont::Bold) { + if (!(macStyle & 1)) { + synthesisFlags |= SynthesizedBold; + atsuBold = true; + tags[attributeCount] = kATSUQDBoldfaceTag; + sizes[attributeCount] = sizeof(atsuBold); + values[attributeCount] = &atsuBold; + ++attributeCount; + } + } + if (fontDef.style != QFont::StyleNormal) { + if (!(macStyle & 2)) { + synthesisFlags |= SynthesizedItalic; + atsuItalic = true; + tags[attributeCount] = kATSUQDItalicTag; + sizes[attributeCount] = sizeof(atsuItalic); + values[attributeCount] = &atsuItalic; + ++attributeCount; + } + } + + tags[attributeCount] = kATSUFontTag; + values[attributeCount] = &fontID; + sizes[attributeCount] = sizeof(fontID); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // CTFontCopyTable + quint16 tmpFsType; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 8, 2, &tmpFsType, 0) == noErr) + fsType = qFromBigEndian<quint16>(tmpFsType); + else + fsType = 0; + + if (multiEngine) + transform = multiEngine->transform; + else + transform = CGAffineTransformIdentity; + + ATSUTextMeasurement metric; + + ATSUGetAttribute(style, kATSUAscentTag, sizeof(metric), &metric, 0); + m_ascent = FixRound(metric); + + ATSUGetAttribute(style, kATSUDescentTag, sizeof(metric), &metric, 0); + m_descent = FixRound(metric); + + ATSUGetAttribute(style, kATSULeadingTag, sizeof(metric), &metric, 0); + m_leading = FixRound(metric); + + ATSFontMetrics metrics; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_maxCharWidth = metrics.maxAdvanceWidth * fontDef.pointSize; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_xHeight = QFixed::fromReal(metrics.xHeight * fontDef.pointSize); + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_averageCharWidth = QFixed::fromReal(metrics.avgAdvanceWidth * fontDef.pointSize); + + // Use width of 'X' if ATSFontGetHorizontalMetrics returns 0 for avgAdvanceWidth. + if (m_averageCharWidth == QFixed(0)) { + QChar c('X'); + QGlyphLayoutArray<1> glyphs; + int nglyphs = 1; + stringToCMap(&c, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t metrics = boundingBox(glyphs); + m_averageCharWidth = metrics.width; + } +} + +QFontEngineMac::~QFontEngineMac() +{ + ATSUDisposeStyle(style); +} + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +// Not used directly for shaping, only used to calculate m_averageCharWidth +bool QFontEngineMac::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (!cmap) { + cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + int size = 0; + cmap = getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()), cmapTable.size(), &symbolCMap, &size); + if (!cmap) + return false; + } + if (symbolCMap) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[i] && uc < 0x100) + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + } + } + + *nglyphs = len; + glyphs->numGlyphs = *nglyphs; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineMac::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_UNUSED(flags) + + QVarLengthArray<GlyphID> atsuGlyphs(glyphs->numGlyphs); + for (int i = 0; i < glyphs->numGlyphs; ++i) + atsuGlyphs[i] = glyphs->glyphs[i]; + + QVarLengthArray<ATSGlyphScreenMetrics> metrics(glyphs->numGlyphs); + + ATSUGlyphGetScreenMetrics(style, glyphs->numGlyphs, atsuGlyphs.data(), sizeof(GlyphID), + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + metrics.data()); + + for (int i = 0; i < glyphs->numGlyphs; ++i) { + glyphs->advances_x[i] = QFixed::fromReal(metrics[i].deviceAdvance.x); + glyphs->advances_y[i] = QFixed::fromReal(metrics[i].deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QFontEngineMac::boundingBox(glyph_t glyph) +{ + GlyphID atsuGlyph = glyph; + + ATSGlyphScreenMetrics metrics; + + ATSUGlyphGetScreenMetrics(style, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &metrics); + + // ### check again + + glyph_metrics_t gm; + gm.width = int(metrics.width); + gm.height = int(metrics.height); + gm.x = QFixed::fromReal(metrics.topLeft.x); + gm.y = -QFixed::fromReal(metrics.topLeft.y); + gm.xoff = QFixed::fromReal(metrics.deviceAdvance.x); + gm.yoff = QFixed::fromReal(metrics.deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + gm.x = gm.x.floor(); + gm.y = gm.y.floor(); + gm.xoff = gm.xoff.round(); + gm.yoff = gm.yoff.round(); + } + + return gm; +} + +QFixed QFontEngineMac::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_ascent.round() + : m_ascent; +} + +QFixed QFontEngineMac::descent() const +{ + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_descent.round() - 1 + : m_descent; +} + +QFixed QFontEngineMac::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_leading.round() + : m_leading; +} + +qreal QFontEngineMac::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(m_maxCharWidth) + : m_maxCharWidth; +} + +QFixed QFontEngineMac::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_xHeight.round() + : m_xHeight; +} + +QFixed QFontEngineMac::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_averageCharWidth.round() + : m_averageCharWidth; +} + +static void addGlyphsToPathHelper(ATSUStyle style, glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path) +{ + if (!numGlyphs) + return; + + OSStatus e; + + QMacFontPath fontpath(0, 0, path); + ATSCubicMoveToUPP moveTo = NewATSCubicMoveToUPP(QMacFontPath::moveTo); + ATSCubicLineToUPP lineTo = NewATSCubicLineToUPP(QMacFontPath::lineTo); + ATSCubicCurveToUPP cubicTo = NewATSCubicCurveToUPP(QMacFontPath::cubicTo); + ATSCubicClosePathUPP closePath = NewATSCubicClosePathUPP(QMacFontPath::closePath); + + // CTFontCreatePathForGlyph + for (int i = 0; i < numGlyphs; ++i) { + GlyphID glyph = glyphs[i]; + + fontpath.setPosition(positions[i].x.toReal(), positions[i].y.toReal()); + ATSUGlyphGetCubicPaths(style, glyph, moveTo, lineTo, + cubicTo, closePath, &fontpath, &e); + } + + DisposeATSCubicMoveToUPP(moveTo); + DisposeATSCubicLineToUPP(lineTo); + DisposeATSCubicCurveToUPP(cubicTo); + DisposeATSCubicClosePathUPP(closePath); +} + +void QFontEngineMac::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path, + QTextItem::RenderFlags) +{ + addGlyphsToPathHelper(style, glyphs, positions, numGlyphs, path); +} + + +/*! + Helper function for alphaMapForGlyph and alphaRGBMapForGlyph. The two are identical, except for + the subpixel antialiasing... +*/ +QImage QFontEngineMac::imageForGlyph(glyph_t glyph, int margin, bool colorful) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+4, QImage::Format_RGB32); + im.fill(0xff000000); + + CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, fontDef.pointSize > qt_antialiasing_threshold && !(fontDef.styleStrategy & QFont::NoAntialias)); + // turn off sub-pixel hinting - no support for that in OpenGL + CGContextSetShouldSmoothFonts(ctx, colorful); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + 1; + qreal pos_y = im.height() + br.y.toReal() - 2; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QFontEngineMac::alphaMapForGlyph(glyph_t glyph) +{ + QImage im = imageForGlyph(glyph, 2, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uint *src = (uint*) im.scanLine(y); + uchar *dst = indexed.scanLine(y); + for (int x=0; x<im.width(); ++x) { + *dst = qGray(*src); + ++dst; + ++src; + } + } + + return indexed; +} + +QImage QFontEngineMac::alphaRGBMapForGlyph(glyph_t glyph, QFixed, int margin, const QTransform &t) +{ + QImage im = imageForGlyph(glyph, margin, true); + + if (t.type() >= QTransform::TxScale) { + im = im.transformed(t); + } + + qGamma_correct_back_to_linear_cs(&im); + + return im; +} + + +bool QFontEngineMac::canRender(const QChar *string, int len) +{ + Q_ASSERT(false); + Q_UNUSED(string); + Q_UNUSED(len); + return false; +} + +void QFontEngineMac::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray<CGSize> advances(glyphs.size()); + QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +QFontEngine::FaceId QFontEngineMac::faceId() const +{ + FaceId ret; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + // CTFontGetPlatformFont + FSRef ref; + if (ATSFontGetFileReference(FMGetATSFontRefFromFont(fontID), &ref) != noErr) + return ret; + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +}else +#endif +{ + FSSpec spec; + if (ATSFontGetFileSpecification(FMGetATSFontRefFromFont(fontID), &spec) != noErr) + return ret; + + FSRef ref; + FSpMakeFSRef(&spec, &ref); + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +} + return ret; +} + +QByteArray QFontEngineMac::getSfntTable(uint tag) const +{ + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + + ByteCount length; + OSStatus status = ATSFontGetTable(atsFont, tag, 0, 0, 0, &length); + if (status != noErr) + return QByteArray(); + QByteArray table(length, 0); + // CTFontCopyTable + status = ATSFontGetTable(atsFont, tag, 0, table.length(), table.data(), &length); + if (status != noErr) + return QByteArray(); + return table; +} + +QFontEngine::Properties QFontEngineMac::properties() const +{ + QFontEngine::Properties props; + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + quint16 tmp; + // CTFontGetUnitsPerEm + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 18, 2, &tmp, 0) == noErr) + props.emSquare = qFromBigEndian<quint16>(tmp); + struct { + qint16 xMin; + qint16 yMin; + qint16 xMax; + qint16 yMax; + } bbox; + bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; + // CTFontGetBoundingBox + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 36, 8, &bbox, 0) == noErr) { + bbox.xMin = qFromBigEndian<quint16>(bbox.xMin); + bbox.yMin = qFromBigEndian<quint16>(bbox.yMin); + bbox.xMax = qFromBigEndian<quint16>(bbox.xMax); + bbox.yMax = qFromBigEndian<quint16>(bbox.yMax); + } + struct { + qint16 ascender; + qint16 descender; + qint16 linegap; + } metrics; + metrics.ascender = metrics.descender = metrics.linegap = 0; + // CTFontGetAscent, etc. + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'h', 'e', 'a'), 4, 6, &metrics, 0) == noErr) { + metrics.ascender = qFromBigEndian<quint16>(metrics.ascender); + metrics.descender = qFromBigEndian<quint16>(metrics.descender); + metrics.linegap = qFromBigEndian<quint16>(metrics.linegap); + } + props.ascent = metrics.ascender; + props.descent = -metrics.descender; + props.leading = metrics.linegap; + props.boundingBox = QRectF(bbox.xMin, -bbox.yMax, + bbox.xMax - bbox.xMin, + bbox.yMax - bbox.yMin); + props.italicAngle = 0; + props.capHeight = props.ascent; + + qint16 lw = 0; + // fonts lie + if (ATSFontGetTable(atsFont, MAKE_TAG('p', 'o', 's', 't'), 10, 2, &lw, 0) == noErr) + lw = qFromBigEndian<quint16>(lw); + props.lineWidth = lw; + + // CTFontCopyPostScriptName + QCFString psName; + if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr) + props.postscriptName = QString(psName).toUtf8(); + props.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(props.postscriptName); + return props; +} + +void QFontEngineMac::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + ATSUStyle unscaledStyle; + ATSUCreateAndCopyStyle(style, &unscaledStyle); + + int emSquare = properties().emSquare.toInt(); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(emSquare, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(unscaledStyle, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // various CTFont metrics functions: CTFontGetBoundingRectsForGlyphs, CTFontGetAdvancesForGlyphs + GlyphID atsuGlyph = glyph; + ATSGlyphScreenMetrics atsuMetrics; + ATSUGlyphGetScreenMetrics(unscaledStyle, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &atsuMetrics); + + metrics->width = int(atsuMetrics.width); + metrics->height = int(atsuMetrics.height); + metrics->x = QFixed::fromReal(atsuMetrics.topLeft.x); + metrics->y = -QFixed::fromReal(atsuMetrics.topLeft.y); + metrics->xoff = QFixed::fromReal(atsuMetrics.deviceAdvance.x); + metrics->yoff = QFixed::fromReal(atsuMetrics.deviceAdvance.y); + + QFixedPoint p; + addGlyphsToPathHelper(unscaledStyle, &glyph, &p, 1, path); + + ATSUDisposeStyle(unscaledStyle); +} +#endif // !QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_mac_p.h b/src/gui/text/qfontengine_mac_p.h new file mode 100644 index 0000000000..385fa83fe9 --- /dev/null +++ b/src/gui/text/qfontengine_mac_p.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_MAC_P_H +#define QFONTENGINE_MAC_P_H + +#include <private/qfontengine_p.h> + +#ifndef QT_MAC_USE_COCOA +class QFontEngineMacMulti; +class QFontEngineMac : public QFontEngine +{ + friend class QFontEngineMacMulti; +public: + QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine = 0); + virtual ~QFontEngineMac(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *numGlyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QFontEngineMac"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual QByteArray getSfntTable(uint tag) const; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + +private: + QImage imageForGlyph(glyph_t glyph, int margin, bool colorful); + + ATSUFontID fontID; + QCFType<CGFontRef> cgFont; + ATSUStyle style; + int synthesisFlags; + mutable QGlyphLayout kashidaGlyph; + QFontEngineMacMulti *multiEngine; + mutable const unsigned char *cmap; + mutable bool symbolCMap; + mutable QByteArray cmapTable; + CGAffineTransform transform; + QFixed m_ascent; + QFixed m_descent; + QFixed m_leading; + qreal m_maxCharWidth; + QFixed m_xHeight; + QFixed m_averageCharWidth; +}; + +class QFontEngineMacMulti : public QFontEngineMulti +{ + friend class QFontEngineMac; +public: + // internal + struct ShaperItem + { + inline ShaperItem() : string(0), from(0), length(0), + log_clusters(0), charAttributes(0) {} + + const QChar *string; + int from; + int length; + QGlyphLayout glyphs; + unsigned short *log_clusters; + const HB_CharAttributes *charAttributes; + QTextEngine::ShaperFlags flags; + }; + + QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning); + virtual ~QFontEngineMacMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const; + + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual const char *name() const { return "ATSUI"; } + + virtual bool canRender(const QChar *string, int len); + + inline ATSUFontID macFontID() const { return fontID; } + +protected: + virtual void loadEngine(int at); + +private: + inline const QFontEngineMac *engineAt(int i) const + { return static_cast<const QFontEngineMac *>(engines.at(i)); } + + bool stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, ShaperItem *item) const; + + int fontIndexForFontID(ATSUFontID id) const; + + ATSUFontID fontID; + uint kerning : 1; + + mutable ATSUTextLayout textLayout; + mutable ATSUStyle style; + CGAffineTransform transform; +}; +#endif //!QT_MAC_USE_COCOA + +#endif // QFONTENGINE_MAC_P_H diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h new file mode 100644 index 0000000000..5b39fd39ad --- /dev/null +++ b/src/gui/text/qfontengine_p.h @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_P_H +#define QFONTENGINE_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 "QtCore/qglobal.h" +#include "QtCore/qatomic.h" +#include <QtCore/qvarlengtharray.h> +#include <QtCore/QLinkedList> +#include "private/qtextengine_p.h" +#include "private/qfont_p.h" + +#ifdef Q_WS_WIN +# include "QtCore/qt_windows.h" +#endif + +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "QtCore/qmap.h" +# include "QtCore/qcache.h" +# include "private/qcore_mac_p.h" +#endif + +#include <private/qfontengineglyphcache_p.h> + +struct glyph_metrics_t; +typedef unsigned int glyph_t; + +QT_BEGIN_NAMESPACE + +class QChar; +class QPainterPath; + +class QTextEngine; +struct QGlyphLayout; + +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch1)) << 24) | \ + (((quint32)(ch2)) << 16) | \ + (((quint32)(ch3)) << 8) | \ + ((quint32)(ch4)) \ + ) + + +class Q_GUI_EXPORT QFontEngine : public QObject +{ +public: + enum Type { + Box, + Multi, + + // X11 types + XLFD, + + // MS Windows types + Win, + + // Apple Mac OS types + Mac, + + // QWS types + Freetype, + QPF1, + QPF2, + Proxy, + + // S60 types + S60FontEngine, // Cannot be simply called "S60". Reason is qt_s60Data.h + + DirectWrite, + + TestFontEngine = 0x1000 + }; + + enum GlyphFormat { + Format_None, + Format_Render = Format_None, + Format_Mono, + Format_A8, + Format_A32 + }; + + QFontEngine(); + virtual ~QFontEngine(); + + // all of these are in unscaled metrics if the engine supports uncsaled metrics, + // otherwise in design metrics + struct Properties { + QByteArray postscriptName; + QByteArray copyright; + QRectF boundingBox; + QFixed emSquare; + QFixed ascent; + QFixed descent; + QFixed leading; + QFixed italicAngle; + QFixed capHeight; + QFixed lineWidth; + }; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + QByteArray getSfntTable(uint /*tag*/) const; + virtual bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const { return false; } + + struct FaceId { + FaceId() : index(0), encoding(0) {} + QByteArray filename; + int index; + int encoding; + }; + virtual FaceId faceId() const { return FaceId(); } + enum SynthesizedFlags { + SynthesizedItalic = 0x1, + SynthesizedBold = 0x2, + SynthesizedStretch = 0x4 + }; + virtual int synthesized() const { return 0; } + virtual bool supportsSubPixelPositions() const { return false; } + + virtual QFixed emSquareSize() const { return ascent(); } + + /* returns 0 as glyph index for non existent glyphs */ + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const = 0; + + /** + * This is a callback from harfbuzz. The font engine uses the font-system in use to find out the + * advances of each glyph and set it on the layout. + */ + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const {} + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) && !defined(Q_WS_QPA) + virtual void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si) = 0; +#endif + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + void getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray<glyph_t> &glyphs_out, QVarLengthArray<QFixedPoint> &positions); + + virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags flags); + void addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags); + /** + * Create a qimage with the alpha values for the glyph. + * Returns an image indexed_8 with index values ranging from 0=fully transparent to 255=opaque + */ + virtual QImage alphaMapForGlyph(glyph_t); + virtual QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition); + virtual QImage alphaMapForGlyph(glyph_t, const QTransform &t); + virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + + virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, QFixed /*subPixelPosition*/, const QTransform &matrix, GlyphFormat /*format*/) + { + return boundingBox(glyph, matrix); + } + + virtual void removeGlyphFromCache(glyph_t); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) = 0; + virtual glyph_metrics_t boundingBox(glyph_t glyph) = 0; + virtual glyph_metrics_t boundingBox(glyph_t glyph, const QTransform &matrix); + glyph_metrics_t tightBoundingBox(const QGlyphLayout &glyphs); + + virtual QFixed ascent() const = 0; + virtual QFixed descent() const = 0; + virtual QFixed leading() const = 0; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + + virtual qreal maxCharWidth() const = 0; + virtual qreal minLeftBearing() const { return qreal(); } + virtual qreal minRightBearing() const { return qreal(); } + + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); + + virtual const char *name() const = 0; + + virtual bool canRender(const QChar *string, int len) = 0; + + virtual Type type() const = 0; + + virtual int glyphCount() const; + + HB_Font harfbuzzFont() const; + HB_Face harfbuzzFace() const; + + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + void setGlyphCache(void *key, QFontEngineGlyphCache *data); + QFontEngineGlyphCache *glyphCache(void *key, QFontEngineGlyphCache::Type type, const QTransform &transform) const; + + static const uchar *getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize); + static quint32 getTrueTypeGlyphIndex(const uchar *cmap, uint unicode); + + static QByteArray convertToPostscriptFontFamilyName(const QByteArray &fontFamily); + + QAtomicInt ref; + QFontDef fontDef; + uint cache_cost; // amount of mem used in kb by the font + int cache_count; + uint fsType : 16; + bool symbol; + mutable HB_FontRec hbFont; + mutable HB_Face hbFace; +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + struct KernPair { + uint left_right; + QFixed adjust; + + inline bool operator<(const KernPair &other) const + { + return left_right < other.left_right; + } + }; + QVector<KernPair> kerning_pairs; + void loadKerningPairs(QFixed scalingFactor); +#endif + + int glyphFormat; + +protected: + static const QVector<QRgb> &grayPalette(); + QFixed lastRightBearing(const QGlyphLayout &glyphs, bool round = false); + +private: + struct GlyphCacheEntry { + void *context; + QExplicitlySharedDataPointer<QFontEngineGlyphCache> cache; + bool operator==(const GlyphCacheEntry &other) { return context == other.context && cache == other.cache; } + }; + + mutable QLinkedList<GlyphCacheEntry> m_glyphCaches; +}; + +inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId &f2) +{ + return (f1.index == f2.index) && (f1.encoding == f2.encoding) && (f1.filename == f2.filename); +} + +inline uint qHash(const QFontEngine::FaceId &f) +{ + return qHash((f.index << 16) + f.encoding) + qHash(f.filename); +} + + +class QGlyph; + +#if defined(Q_WS_QWS) + +#ifndef QT_NO_QWS_QPF + +class QFontEngineQPF1Data; + +class QFontEngineQPF1 : public QFontEngine +{ +public: + QFontEngineQPF1(const QFontDef&, const QString &fn); + ~QFontEngineQPF1(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QFixed underlinePosition() const; + virtual QFixed lineThickness() const; + + virtual Type type() const; + + virtual bool canRender(const QChar *string, int len); + inline const char *name() const { return 0; } + virtual QImage alphaMapForGlyph(glyph_t); + + + QFontEngineQPF1Data *d; +}; +#endif // QT_NO_QWS_QPF + +#endif // QWS + + +class QFontEngineBox : public QFontEngine +{ +public: + QFontEngineBox(int size); + ~QFontEngineBox(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); +#endif + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const { return 0; } + virtual qreal minRightBearing() const { return 0; } + virtual QImage alphaMapForGlyph(glyph_t); + +#ifdef Q_WS_X11 + int cmap() const; +#endif + virtual const char *name() const; + + virtual bool canRender(const QChar *string, int len); + + virtual Type type() const; + inline int size() const { return _size; } + +private: + friend class QFontPrivate; + int _size; +}; + +class QFontEngineMulti : public QFontEngine +{ +public: + explicit QFontEngineMulti(int engineCount); + ~QFontEngineMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags flags); + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual QImage alphaMapForGlyph(glyph_t); + + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + + virtual inline Type type() const + { return QFontEngine::Multi; } + + virtual bool canRender(const QChar *string, int len); + inline virtual const char *name() const + { return "Multi"; } + + QFontEngine *engine(int at) const + {Q_ASSERT(at < engines.size()); return engines.at(at); } + + +protected: + friend class QPSPrintEnginePrivate; + friend class QPSPrintEngineFontMulti; + friend class QRawFont; + virtual void loadEngine(int at) = 0; + QVector<QFontEngine *> engines; +}; + +class QTestFontEngine : public QFontEngineBox +{ +public: + QTestFontEngine(int size) : QFontEngineBox(size) {} + virtual Type type() const { return TestFontEngine; } +}; + +QT_END_NAMESPACE + +#ifdef Q_WS_WIN +# include "private/qfontengine_win_p.h" +#endif + +#if defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) +# include "private/qfontengine_ft_p.h" +#endif + +#endif // QFONTENGINE_P_H diff --git a/src/gui/text/qfontengine_qpa.cpp b/src/gui/text/qfontengine_qpa.cpp new file mode 100644 index 0000000000..851bb5964c --- /dev/null +++ b/src/gui/text/qfontengine_qpa.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_qpa_p.h" + +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QDir> +#include <QtCore/QBuffer> + +#include <QtGui/private/qapplication_p.h> +#include <QtGui/QPlatformFontDatabase> +#include <QtGui/private/qpaintengine_raster_p.h> + +QT_BEGIN_NAMESPACE + +//#define DEBUG_HEADER +//#define DEBUG_FONTENGINE + +static QFontEngineQPA::TagType tagTypes[QFontEngineQPA::NumTags] = { + QFontEngineQPA::StringType, // FontName + QFontEngineQPA::StringType, // FileName + QFontEngineQPA::UInt32Type, // FileIndex + QFontEngineQPA::UInt32Type, // FontRevision + QFontEngineQPA::StringType, // FreeText + QFontEngineQPA::FixedType, // Ascent + QFontEngineQPA::FixedType, // Descent + QFontEngineQPA::FixedType, // Leading + QFontEngineQPA::FixedType, // XHeight + QFontEngineQPA::FixedType, // AverageCharWidth + QFontEngineQPA::FixedType, // MaxCharWidth + QFontEngineQPA::FixedType, // LineThickness + QFontEngineQPA::FixedType, // MinLeftBearing + QFontEngineQPA::FixedType, // MinRightBearing + QFontEngineQPA::FixedType, // UnderlinePosition + QFontEngineQPA::UInt8Type, // GlyphFormat + QFontEngineQPA::UInt8Type, // PixelSize + QFontEngineQPA::UInt8Type, // Weight + QFontEngineQPA::UInt8Type, // Style + QFontEngineQPA::StringType, // EndOfHeader + QFontEngineQPA::BitFieldType// WritingSystems +}; + + +#if defined(DEBUG_HEADER) +# define DEBUG_VERIFY qDebug +#else +# define DEBUG_VERIFY if (0) qDebug +#endif + +#define READ_VERIFY(type, variable) \ + if (tagPtr + sizeof(type) > endPtr) { \ + DEBUG_VERIFY() << "read verify failed in line" << __LINE__; \ + return 0; \ + } \ + variable = qFromBigEndian<type>(tagPtr); \ + DEBUG_VERIFY() << "read value" << variable << "of type " #type; \ + tagPtr += sizeof(type) + +template <typename T> +T readValue(const uchar *&data) +{ + T value = qFromBigEndian<T>(data); + data += sizeof(T); + return value; +} + +#define VERIFY(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "condition " #condition " failed in line" << __LINE__; \ + return 0; \ + } + +#define VERIFY_TAG(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "verifying tag condition " #condition " failed in line" << __LINE__ << "with tag" << tag; \ + return 0; \ + } + +static inline const uchar *verifyTag(const uchar *tagPtr, const uchar *endPtr) +{ + quint16 tag, length; + READ_VERIFY(quint16, tag); + READ_VERIFY(quint16, length); + if (tag == QFontEngineQPA::Tag_EndOfHeader) + return endPtr; + if (tag < QFontEngineQPA::NumTags) { + switch (tagTypes[tag]) { + case QFontEngineQPA::BitFieldType: + case QFontEngineQPA::StringType: + // can't do anything... + break; + case QFontEngineQPA::UInt32Type: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPA::FixedType: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPA::UInt8Type: + VERIFY_TAG(length == sizeof(quint8)); + break; + } +#if defined(DEBUG_HEADER) + if (length == 1) + qDebug() << "tag data" << hex << *tagPtr; + else if (length == 4) + qDebug() << "tag data" << hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; +#endif + } + return tagPtr + length; +} + +const QFontEngineQPA::Glyph *QFontEngineQPA::findGlyph(glyph_t g) const +{ + if (!g || g >= glyphMapEntries) + return 0; + const quint32 *gmapPtr = reinterpret_cast<const quint32 *>(fontData + glyphMapOffset); + quint32 glyphPos = qFromBigEndian<quint32>(gmapPtr[g]); + if (glyphPos > glyphDataSize) { + if (glyphPos == 0xffffffff) + return 0; +#if defined(DEBUG_FONTENGINE) + qDebug() << "glyph" << g << "outside of glyphData, remapping font file"; +#endif + if (glyphPos > glyphDataSize) + return 0; + } + return reinterpret_cast<const Glyph *>(fontData + glyphDataOffset + glyphPos); +} + +bool QFontEngineQPA::verifyHeader(const uchar *data, int size) +{ + VERIFY(size >= int(sizeof(Header))); + const Header *header = reinterpret_cast<const Header *>(data); + if (header->magic[0] != 'Q' + || header->magic[1] != 'P' + || header->magic[2] != 'F' + || header->magic[3] != '2') + return false; + + VERIFY(header->majorVersion <= CurrentMajorVersion); + const quint16 dataSize = qFromBigEndian<quint16>(header->dataSize); + VERIFY(size >= int(sizeof(Header)) + dataSize); + + const uchar *tagPtr = data + sizeof(Header); + const uchar *tagEndPtr = tagPtr + dataSize; + while (tagPtr < tagEndPtr - 3) { + tagPtr = verifyTag(tagPtr, tagEndPtr); + VERIFY(tagPtr); + } + + VERIFY(tagPtr <= tagEndPtr); + return true; +} + +QVariant QFontEngineQPA::extractHeaderField(const uchar *data, HeaderTag requestedTag) +{ + const Header *header = reinterpret_cast<const Header *>(data); + const uchar *tagPtr = data + sizeof(Header); + const uchar *endPtr = tagPtr + qFromBigEndian<quint16>(header->dataSize); + while (tagPtr < endPtr - 3) { + quint16 tag = readValue<quint16>(tagPtr); + quint16 length = readValue<quint16>(tagPtr); + if (tag == requestedTag) { + switch (tagTypes[requestedTag]) { + case StringType: + return QVariant(QString::fromUtf8(reinterpret_cast<const char *>(tagPtr), length)); + case UInt32Type: + return QVariant(readValue<quint32>(tagPtr)); + case UInt8Type: + return QVariant(uint(*tagPtr)); + case FixedType: + return QVariant(QFixed::fromFixed(readValue<quint32>(tagPtr)).toReal()); + case BitFieldType: + return QVariant(QByteArray(reinterpret_cast<const char *>(tagPtr), length)); + } + return QVariant(); + } else if (tag == Tag_EndOfHeader) { + break; + } + tagPtr += length; + } + + return QVariant(); +} + + + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +QFontEngineQPA::QFontEngineQPA(const QFontDef &def, const QByteArray &data) + : fontData(reinterpret_cast<const uchar *>(data.constData())), dataSize(data.size()) +{ + fontDef = def; + cache_cost = 100; + externalCMap = 0; + cmapOffset = 0; + cmapSize = 0; + glyphMapOffset = 0; + glyphMapEntries = 0; + glyphDataOffset = 0; + glyphDataSize = 0; + kerning_pairs_loaded = false; + readOnly = true; + +#if defined(DEBUG_FONTENGINE) + qDebug() << "QFontEngineQPA::QFontEngineQPA( fd =" << fd << ", renderingFontEngine =" << renderingFontEngine << ')'; +#endif + + if (!verifyHeader(fontData, dataSize)) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "verifyHeader failed!"; +#endif + return; + } + + const Header *header = reinterpret_cast<const Header *>(fontData); + + readOnly = (header->lock == 0xffffffff); + + const uchar *imgData = fontData + sizeof(Header) + qFromBigEndian<quint16>(header->dataSize); + const uchar *endPtr = fontData + dataSize; + while (imgData <= endPtr - 8) { + quint16 blockTag = readValue<quint16>(imgData); + imgData += 2; // skip padding + quint32 blockSize = readValue<quint32>(imgData); + + if (blockTag == CMapBlock) { + cmapOffset = imgData - fontData; + cmapSize = blockSize; + } else if (blockTag == GMapBlock) { + glyphMapOffset = imgData - fontData; + glyphMapEntries = blockSize / 4; + } else if (blockTag == GlyphBlock) { + glyphDataOffset = imgData - fontData; + glyphDataSize = blockSize; + } + + imgData += blockSize; + } + + face_id.filename = QFile::encodeName(extractHeaderField(fontData, Tag_FileName).toString()); + face_id.index = extractHeaderField(fontData, Tag_FileIndex).toInt(); + + // get the real cmap + if (cmapOffset) { + int tableSize = cmapSize; + const uchar *cmapPtr = getCMap(fontData + cmapOffset, tableSize, &symbol, &cmapSize); + if (cmapPtr) + cmapOffset = cmapPtr - fontData; + else + cmapOffset = 0; + } else if (externalCMap) { + int tableSize = cmapSize; + externalCMap = getCMap(externalCMap, tableSize, &symbol, &cmapSize); + } + + // verify all the positions in the glyphMap + if (glyphMapOffset) { + const quint32 *gmapPtr = reinterpret_cast<const quint32 *>(fontData + glyphMapOffset); + for (uint i = 0; i < glyphMapEntries; ++i) { + quint32 glyphDataPos = qFromBigEndian<quint32>(gmapPtr[i]); + if (glyphDataPos == 0xffffffff) + continue; + if (glyphDataPos >= glyphDataSize) { + // error + glyphMapOffset = 0; + glyphMapEntries = 0; + break; + } + } + } + +#if defined(DEBUG_FONTENGINE) + if (!isValid()) + qDebug() << "fontData" << fontData << "dataSize" << dataSize + << "externalCMap" << externalCMap << "cmapOffset" << cmapOffset + << "glyphMapOffset" << glyphMapOffset << "glyphDataOffset" << glyphDataOffset + << "fd" << fd << "glyphDataSize" << glyphDataSize; +#endif +} + +QFontEngineQPA::~QFontEngineQPA() +{ +} + +bool QFontEngineQPA::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + Q_UNUSED(tag); + Q_UNUSED(buffer); + *length = 0; + return false; +} + +bool QFontEngineQPA::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if defined(DEBUG_FONTENGINE) + QSet<QChar> seenGlyphs; +#endif + + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + ++glyph_pos; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); +#if 0 && defined(DEBUG_FONTENGINE) + QChar c(uc); + if (!findGlyph(glyphs[glyph_pos].glyph) && !seenGlyphs.contains(c)) + qDebug() << "glyph for character" << c << '/' << hex << uc << "is" << dec << glyphs[glyph_pos].glyph; + + seenGlyphs.insert(c); +#endif + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineQPA::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; ++i) { + const Glyph *g = findGlyph(glyphs->glyphs[i]); + if (!g) { + glyphs->glyphs[i] = 0; + continue; + } + glyphs->advances_x[i] = g->advance; + glyphs->advances_y[i] = 0; + } +} + +QImage QFontEngineQPA::alphaMapForGlyph(glyph_t g) +{ + const Glyph *glyph = findGlyph(g); + if (!glyph) + return QImage(); + + const uchar *bits = ((const uchar *) glyph) + sizeof(Glyph); + + QImage image(bits,glyph->width, glyph->height, glyph->bytesPerLine, QImage::Format_Indexed8); + + return image; +} + +void QFontEngineQPA::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPA::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (!g) + continue; + + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y + g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += g->advance; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineQPA::boundingBox(glyph_t glyph) +{ + glyph_metrics_t overall; + const Glyph *g = findGlyph(glyph); + if (!g) + return overall; + overall.x = g->x; + overall.y = g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + return overall; +} + +QFixed QFontEngineQPA::ascent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Ascent).value<qreal>()); +} + +QFixed QFontEngineQPA::descent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Descent).value<qreal>()); +} + +QFixed QFontEngineQPA::leading() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Leading).value<qreal>()); +} + +qreal QFontEngineQPA::maxCharWidth() const +{ + return extractHeaderField(fontData, Tag_MaxCharWidth).value<qreal>(); +} + +qreal QFontEngineQPA::minLeftBearing() const +{ + return extractHeaderField(fontData, Tag_MinLeftBearing).value<qreal>(); +} + +qreal QFontEngineQPA::minRightBearing() const +{ + return extractHeaderField(fontData, Tag_MinRightBearing).value<qreal>(); +} + +QFixed QFontEngineQPA::underlinePosition() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_UnderlinePosition).value<qreal>()); +} + +QFixed QFontEngineQPA::lineThickness() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_LineThickness).value<qreal>()); +} + +QFontEngine::Type QFontEngineQPA::type() const +{ + return QFontEngine::QPF2; +} + +bool QFontEngineQPA::canRender(const QChar *string, int len) +{ + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + glyph_t g = getTrueTypeGlyphIndex(cmap, uc); + if(!g && uc < 0x100) + g = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + if (!g) + return false; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (!getTrueTypeGlyphIndex(cmap, uc)) + return false; + } + } + return true; +} + +bool QFontEngineQPA::isValid() const +{ + return fontData && dataSize && (cmapOffset || externalCMap) + && glyphMapOffset && glyphDataOffset && glyphDataSize > 0; +} + +void QPAGenerator::generate() +{ + writeHeader(); + writeGMap(); + writeBlock(QFontEngineQPA::GlyphBlock, QByteArray()); + + dev->seek(4); // position of header.lock + writeUInt32(0); +} + +void QPAGenerator::writeHeader() +{ + QFontEngineQPA::Header header; + + header.magic[0] = 'Q'; + header.magic[1] = 'P'; + header.magic[2] = 'F'; + header.magic[3] = '2'; + header.lock = 1; + header.majorVersion = QFontEngineQPA::CurrentMajorVersion; + header.minorVersion = QFontEngineQPA::CurrentMinorVersion; + header.dataSize = 0; + dev->write((const char *)&header, sizeof(header)); + + writeTaggedString(QFontEngineQPA::Tag_FontName, fe->fontDef.family.toUtf8()); + + QFontEngine::FaceId face = fe->faceId(); + writeTaggedString(QFontEngineQPA::Tag_FileName, face.filename); + writeTaggedUInt32(QFontEngineQPA::Tag_FileIndex, face.index); + + { + uchar data[4]; + uint len = 4; + bool ok = fe->getSfntTableData(MAKE_TAG('h', 'e', 'a', 'd'), data, &len); + if (ok) { + const quint32 revision = qFromBigEndian<quint32>(data); + writeTaggedUInt32(QFontEngineQPA::Tag_FontRevision, revision); + } + } + + writeTaggedQFixed(QFontEngineQPA::Tag_Ascent, fe->ascent()); + writeTaggedQFixed(QFontEngineQPA::Tag_Descent, fe->descent()); + writeTaggedQFixed(QFontEngineQPA::Tag_Leading, fe->leading()); + writeTaggedQFixed(QFontEngineQPA::Tag_XHeight, fe->xHeight()); + writeTaggedQFixed(QFontEngineQPA::Tag_AverageCharWidth, fe->averageCharWidth()); + writeTaggedQFixed(QFontEngineQPA::Tag_MaxCharWidth, QFixed::fromReal(fe->maxCharWidth())); + writeTaggedQFixed(QFontEngineQPA::Tag_LineThickness, fe->lineThickness()); + writeTaggedQFixed(QFontEngineQPA::Tag_MinLeftBearing, QFixed::fromReal(fe->minLeftBearing())); + writeTaggedQFixed(QFontEngineQPA::Tag_MinRightBearing, QFixed::fromReal(fe->minRightBearing())); + writeTaggedQFixed(QFontEngineQPA::Tag_UnderlinePosition, fe->underlinePosition()); + writeTaggedUInt8(QFontEngineQPA::Tag_PixelSize, fe->fontDef.pixelSize); + writeTaggedUInt8(QFontEngineQPA::Tag_Weight, fe->fontDef.weight); + writeTaggedUInt8(QFontEngineQPA::Tag_Style, fe->fontDef.style); + + writeTaggedUInt8(QFontEngineQPA::Tag_GlyphFormat, QFontEngineQPA::AlphamapGlyphs); + + writeTaggedString(QFontEngineQPA::Tag_EndOfHeader, QByteArray()); + align4(); + + const quint64 size = dev->pos(); + header.dataSize = qToBigEndian<quint16>(size - sizeof(header)); + dev->seek(0); + dev->write((const char *)&header, sizeof(header)); + dev->seek(size); +} + +void QPAGenerator::writeGMap() +{ + const quint16 glyphCount = fe->glyphCount(); + + writeUInt16(QFontEngineQPA::GMapBlock); + writeUInt16(0); // padding + writeUInt32(glyphCount * 4); + + QByteArray &buffer = dev->buffer(); + const int numBytes = glyphCount * sizeof(quint32); + qint64 pos = buffer.size(); + buffer.resize(pos + numBytes); + qMemSet(buffer.data() + pos, 0xff, numBytes); + dev->seek(pos + numBytes); +} + +void QPAGenerator::writeBlock(QFontEngineQPA::BlockTag tag, const QByteArray &data) +{ + writeUInt16(tag); + writeUInt16(0); // padding + const int padSize = ((data.size() + 3) / 4) * 4 - data.size(); + writeUInt32(data.size() + padSize); + dev->write(data); + for (int i = 0; i < padSize; ++i) + writeUInt8(0); +} + +void QPAGenerator::writeTaggedString(QFontEngineQPA::HeaderTag tag, const QByteArray &string) +{ + writeUInt16(tag); + writeUInt16(string.length()); + dev->write(string); +} + +void QPAGenerator::writeTaggedUInt32(QFontEngineQPA::HeaderTag tag, quint32 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt32(value); +} + +void QPAGenerator::writeTaggedUInt8(QFontEngineQPA::HeaderTag tag, quint8 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt8(value); +} + +void QPAGenerator::writeTaggedQFixed(QFontEngineQPA::HeaderTag tag, QFixed value) +{ + writeUInt16(tag); + writeUInt16(sizeof(quint32)); + writeUInt32(value.value()); +} + + +/* + Creates a new multi QPA engine. + + This function takes ownership of the QFontEngine, increasing it's refcount. +*/ +QFontEngineMultiQPA::QFontEngineMultiQPA(QFontEngine *fe, int _script, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size() + 1), + fallbackFamilies(fallbacks), script(_script) +{ + engines[0] = fe; + fe->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiQPA::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); + engines[at]->ref.ref(); + engines[at]->fontDef = request; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_qpa_p.h b/src/gui/text/qfontengine_qpa_p.h new file mode 100644 index 0000000000..e15beae36f --- /dev/null +++ b/src/gui/text/qfontengine_qpa_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_QPA_P_H +#define QFONTENGINE_QPA_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 <QtCore/qconfig.h> +#include <QtCore/qglobal.h> +#include <QtCore/qendian.h> +#include <QtCore/QBuffer> + +#include "qfontengine_p.h" + +#include <QtCore/QFile> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QFontEngine; +class QFreetypeFace; +class QBuffer; + +class Q_GUI_EXPORT QFontEngineQPA : public QFontEngine +{ +public: + // if you add new tags please make sure to update the tables in + // qpfutil.cpp and tools/makeqpf/qpf2.cpp + enum HeaderTag { + Tag_FontName, // 0 string + Tag_FileName, // 1 string + Tag_FileIndex, // 2 quint32 + Tag_FontRevision, // 3 quint32 + Tag_FreeText, // 4 string + Tag_Ascent, // 5 QFixed + Tag_Descent, // 6 QFixed + Tag_Leading, // 7 QFixed + Tag_XHeight, // 8 QFixed + Tag_AverageCharWidth, // 9 QFixed + Tag_MaxCharWidth, // 10 QFixed + Tag_LineThickness, // 11 QFixed + Tag_MinLeftBearing, // 12 QFixed + Tag_MinRightBearing, // 13 QFixed + Tag_UnderlinePosition, // 14 QFixed + Tag_GlyphFormat, // 15 quint8 + Tag_PixelSize, // 16 quint8 + Tag_Weight, // 17 quint8 + Tag_Style, // 18 quint8 + Tag_EndOfHeader, // 19 string + Tag_WritingSystems, // 20 bitfield + + NumTags + }; + + enum TagType { + StringType, + FixedType, + UInt8Type, + UInt32Type, + BitFieldType + }; + + struct Tag + { + quint16 tag; + quint16 size; + }; + + enum GlyphFormat { + BitmapGlyphs = 1, + AlphamapGlyphs = 8 + }; + + enum { + CurrentMajorVersion = 2, + CurrentMinorVersion = 0 + }; + + // The CMap is identical to the TrueType CMap table format + // The GMap table is a normal array with the total number of + // covered glyphs in the TrueType font + enum BlockTag { + CMapBlock, + GMapBlock, + GlyphBlock + }; + + struct Q_PACKED Header + { + char magic[4]; // 'QPF2' + quint32 lock; // values: 0 = unlocked, 0xffffffff = read-only, otherwise qws client id of locking process + quint8 majorVersion; + quint8 minorVersion; + quint16 dataSize; + }; + + struct Q_PACKED Block + { + quint16 tag; + quint16 pad; + quint32 dataSize; + }; + + struct Q_PACKED Glyph + { + quint8 width; + quint8 height; + quint8 bytesPerLine; + qint8 x; + qint8 y; + qint8 advance; + }; + + QFontEngineQPA(const QFontDef &def, const QByteArray &data); + ~QFontEngineQPA(); + + FaceId faceId() const { return face_id; } + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + QImage alphaMapForGlyph(glyph_t t); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + QFixed underlinePosition() const; + QFixed lineThickness() const; + + Type type() const; + + bool canRender(const QChar *string, int len); + inline const char *name() const { return "QPF2"; } + + virtual int glyphCount() const { return glyphMapEntries; } + + bool isValid() const; + + const Glyph *findGlyph(glyph_t g) const; + + static bool verifyHeader(const uchar *data, int size); + static QVariant extractHeaderField(const uchar *data, HeaderTag tag); + +private: + + const uchar *fontData; + int dataSize; + const uchar *externalCMap; + quint32 cmapOffset; + int cmapSize; + quint32 glyphMapOffset; + quint32 glyphMapEntries; + quint32 glyphDataOffset; + quint32 glyphDataSize; + QString internalFileName; + QString encodedFileName; + bool readOnly; + + FaceId face_id; + QByteArray freetypeCMapTable; + mutable bool kerning_pairs_loaded; +}; + +struct QPAGenerator +{ + QPAGenerator(QBuffer *device, QFontEngine *engine) + : dev(device), fe(engine) {} + + void generate(); + void writeHeader(); + void writeGMap(); + void writeBlock(QFontEngineQPA::BlockTag tag, const QByteArray &data); + + void writeTaggedString(QFontEngineQPA::HeaderTag tag, const QByteArray &string); + void writeTaggedUInt32(QFontEngineQPA::HeaderTag tag, quint32 value); + void writeTaggedUInt8(QFontEngineQPA::HeaderTag tag, quint8 value); + void writeTaggedQFixed(QFontEngineQPA::HeaderTag tag, QFixed value); + + void writeUInt16(quint16 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt32(quint32 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt8(quint8 value) { dev->write((const char *)&value, sizeof(value)); } + void writeInt8(qint8 value) { dev->write((const char *)&value, sizeof(value)); } + + void align4() { while (dev->pos() & 3) { dev->putChar('\0'); } } + + QBuffer *dev; + QFontEngine *fe; +}; + +class QFontEngineMultiQPA : public QFontEngineMulti +{ +public: + QFontEngineMultiQPA(QFontEngine *fe, int script, const QStringList &fallbacks); + + void loadEngine(int at); + +private: + QStringList fallbackFamilies; + int script; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTENGINE_QPA_P_H diff --git a/src/gui/text/qfontengine_qpf.cpp b/src/gui/text/qfontengine_qpf.cpp new file mode 100644 index 0000000000..d35bbe506c --- /dev/null +++ b/src/gui/text/qfontengine_qpf.cpp @@ -0,0 +1,1220 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_qpf_p.h" + +#include "private/qpaintengine_raster_p.h" +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qfileinfo.h> + +#include <QtCore/qfile.h> +#include <QtCore/qdir.h> +#include <QtCore/qbuffer.h> +#if !defined(QT_NO_FREETYPE) +#include "private/qfontengine_ft_p.h" +#endif +#include "private/qcore_unix_p.h" // overrides QT_OPEN + +// for mmap +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <errno.h> + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF2 + +#include "qpfutil.cpp" + +QT_BEGIN_INCLUDE_NAMESPACE + +#if defined(Q_WS_QWS) +# include "private/qwscommand_qws_p.h" +# include "qwsdisplay_qws.h" +# include "qabstractfontengine_p.h" +#endif +#include "qplatformdefs.h" +QT_END_INCLUDE_NAMESPACE + +//#define DEBUG_HEADER +//#define DEBUG_FONTENGINE + +#if defined(DEBUG_HEADER) +# define DEBUG_VERIFY qDebug +#else +# define DEBUG_VERIFY if (0) qDebug +#endif + +#define READ_VERIFY(type, variable) \ + if (tagPtr + sizeof(type) > endPtr) { \ + DEBUG_VERIFY() << "read verify failed in line" << __LINE__; \ + return 0; \ + } \ + variable = qFromBigEndian<type>(tagPtr); \ + DEBUG_VERIFY() << "read value" << variable << "of type " #type; \ + tagPtr += sizeof(type) + +template <typename T> +T readValue(const uchar *&data) +{ + T value = qFromBigEndian<T>(data); + data += sizeof(T); + return value; +} + +#define VERIFY(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "condition " #condition " failed in line" << __LINE__; \ + return 0; \ + } + +#define VERIFY_TAG(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "verifying tag condition " #condition " failed in line" << __LINE__ << "with tag" << tag; \ + return 0; \ + } + +static inline const uchar *verifyTag(const uchar *tagPtr, const uchar *endPtr) +{ + quint16 tag, length; + READ_VERIFY(quint16, tag); + READ_VERIFY(quint16, length); + if (tag == QFontEngineQPF::Tag_EndOfHeader) + return endPtr; + if (tag < QFontEngineQPF::NumTags) { + switch (tagTypes[tag]) { + case QFontEngineQPF::BitFieldType: + case QFontEngineQPF::StringType: + // can't do anything... + break; + case QFontEngineQPF::UInt32Type: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPF::FixedType: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPF::UInt8Type: + VERIFY_TAG(length == sizeof(quint8)); + break; + } +#if defined(DEBUG_HEADER) + if (length == 1) + qDebug() << "tag data" << hex << *tagPtr; + else if (length == 4) + qDebug() << "tag data" << hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; +#endif + } + return tagPtr + length; +} + +const QFontEngineQPF::Glyph *QFontEngineQPF::findGlyph(glyph_t g) const +{ + if (!g || g >= glyphMapEntries) + return 0; + const quint32 *gmapPtr = reinterpret_cast<const quint32 *>(fontData + glyphMapOffset); + quint32 glyphPos = qFromBigEndian<quint32>(gmapPtr[g]); + if (glyphPos > glyphDataSize) { + if (glyphPos == 0xffffffff) + return 0; +#if defined(DEBUG_FONTENGINE) + qDebug() << "glyph" << g << "outside of glyphData, remapping font file"; +#endif +#if !defined(QT_NO_FREETYPE) && !defined(QT_FONTS_ARE_RESOURCES) + const_cast<QFontEngineQPF *>(this)->remapFontData(); +#endif + if (glyphPos > glyphDataSize) + return 0; + } + return reinterpret_cast<const Glyph *>(fontData + glyphDataOffset + glyphPos); +} + +bool QFontEngineQPF::verifyHeader(const uchar *data, int size) +{ + VERIFY(size >= int(sizeof(Header))); + const Header *header = reinterpret_cast<const Header *>(data); + if (header->magic[0] != 'Q' + || header->magic[1] != 'P' + || header->magic[2] != 'F' + || header->magic[3] != '2') + return false; + + VERIFY(header->majorVersion <= CurrentMajorVersion); + const quint16 dataSize = qFromBigEndian<quint16>(header->dataSize); + VERIFY(size >= int(sizeof(Header)) + dataSize); + + const uchar *tagPtr = data + sizeof(Header); + const uchar *tagEndPtr = tagPtr + dataSize; + while (tagPtr < tagEndPtr - 3) { + tagPtr = verifyTag(tagPtr, tagEndPtr); + VERIFY(tagPtr); + } + + VERIFY(tagPtr <= tagEndPtr); + return true; +} + +QVariant QFontEngineQPF::extractHeaderField(const uchar *data, HeaderTag requestedTag) +{ + const Header *header = reinterpret_cast<const Header *>(data); + const uchar *tagPtr = data + sizeof(Header); + const uchar *endPtr = tagPtr + qFromBigEndian<quint16>(header->dataSize); + while (tagPtr < endPtr - 3) { + quint16 tag = readValue<quint16>(tagPtr); + quint16 length = readValue<quint16>(tagPtr); + if (tag == requestedTag) { + switch (tagTypes[requestedTag]) { + case StringType: + return QVariant(QString::fromUtf8(reinterpret_cast<const char *>(tagPtr), length)); + case UInt32Type: + return QVariant(readValue<quint32>(tagPtr)); + case UInt8Type: + return QVariant(uint(*tagPtr)); + case FixedType: + return QVariant(QFixed::fromFixed(readValue<quint32>(tagPtr)).toReal()); + case BitFieldType: + return QVariant(QByteArray(reinterpret_cast<const char *>(tagPtr), length)); + } + return QVariant(); + } else if (tag == Tag_EndOfHeader) { + break; + } + tagPtr += length; + } + + return QVariant(); +} + +#endif // QT_NO_QWS_QPF2 + +QString qws_fontCacheDir() +{ + QString dir; +#if defined(Q_WS_QWS) + extern QString qws_dataDir(); + dir = qws_dataDir(); +#else + dir = QDir::tempPath(); +#endif + dir.append(QLatin1String("/fonts/")); + QDir qd(dir); + if (!qd.exists() && !qd.mkpath(dir)) + dir = QDir::tempPath(); + return dir; +} + +#ifndef QT_NO_QWS_QPF2 + +#ifndef QT_FONTS_ARE_RESOURCES +QList<QByteArray> QFontEngineQPF::cleanUpAfterClientCrash(const QList<int> &crashedClientIds) +{ + QList<QByteArray> removedFonts; + QDir dir(qws_fontCacheDir(), QLatin1String("*.qsf")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray fileName = QFile::encodeName(dir.absoluteFilePath(dir[i])); + + int fd = QT_OPEN(fileName.constData(), O_RDONLY, 0); + if (fd >= 0) { + void *header = ::mmap(0, sizeof(QFontEngineQPF::Header), PROT_READ, MAP_SHARED, fd, 0); + if (header && header != MAP_FAILED) { + quint32 lockValue = reinterpret_cast<QFontEngineQPF::Header *>(header)->lock; + + if (lockValue && crashedClientIds.contains(lockValue)) { + removedFonts.append(fileName); + QFile::remove(QFile::decodeName(fileName)); + } + + ::munmap(header, sizeof(QFontEngineQPF::Header)); + } + QT_CLOSE(fd); + } + } + if (!removedFonts.isEmpty()) + qDebug() << "list of corrupted and removed fonts:" << removedFonts; + return removedFonts; +} +#endif + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} +#ifdef QT_FONTS_ARE_RESOURCES +QFontEngineQPF::QFontEngineQPF(const QFontDef &def, const uchar *bytes, int size) + : fd(-1), fontData(bytes), dataSize(size), renderingFontEngine(0) +#else +QFontEngineQPF::QFontEngineQPF(const QFontDef &def, int fileDescriptor, QFontEngine *fontEngine) + : fd(fileDescriptor), fontData(0), dataSize(0), renderingFontEngine(fontEngine) +#endif +{ + fontDef = def; + cache_cost = 100; + freetype = 0; + externalCMap = 0; + cmapOffset = 0; + cmapSize = 0; + glyphMapOffset = 0; + glyphMapEntries = 0; + glyphDataOffset = 0; + glyphDataSize = 0; + if (renderingFontEngine) + glyphFormat = renderingFontEngine->glyphFormat; + kerning_pairs_loaded = false; + readOnly = true; + +#if defined(DEBUG_FONTENGINE) + qDebug() << "QFontEngineQPF::QFontEngineQPF( fd =" << fd << ", renderingFontEngine =" << renderingFontEngine << ')'; +#endif + +#ifndef QT_FONTS_ARE_RESOURCES + if (fd < 0) { + if (!renderingFontEngine) + return; + + fileName = fontDef.family.toLower() + QLatin1Char('_') + + QString::number(fontDef.pixelSize) + + QLatin1Char('_') + QString::number(fontDef.weight) + + (fontDef.style != QFont::StyleNormal ? + QLatin1String("_italic") : QLatin1String("")) + + QLatin1String(".qsf"); + fileName.replace(QLatin1Char(' '), QLatin1Char('_')); + fileName.prepend(qws_fontCacheDir()); + + encodedFileName = QFile::encodeName(fileName); + if (::access(encodedFileName, F_OK) == 0) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "found existing qpf:" << fileName; +#endif + if (::access(encodedFileName, W_OK | R_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDWR); + } + // read-write access failed - try read-only access + if (fd == -1 && ::access(encodedFileName, R_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDONLY); + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning("QFontEngineQPF: unable to open %s", encodedName.constData()); +#endif + return; + } + } + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qWarning("QFontEngineQPF: insufficient access rights to %s", encodedName.constData()); +#endif + return; + } + } else { +#if defined(DEBUG_FONTENGINE) + qDebug() << "creating qpf on the fly:" << fileName; +#endif + if (::access(QFile::encodeName(qws_fontCacheDir()), W_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDWR | O_EXCL | O_CREAT, 0644); + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: open() failed for %s", encodedName.constData()); +#endif + return; + } + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QPFGenerator generator(&buffer, renderingFontEngine); + generator.generate(); + buffer.close(); + const QByteArray &data = buffer.data(); + if (QT_WRITE(fd, data.constData(), data.size()) == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: write() failed for %s", encodedName.constData()); +#endif + return; + } + } else { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: access() failed for %s", qPrintable(qws_fontCacheDir())); +#endif + return; + } + } + } + + QT_STATBUF st; + if (QT_FSTAT(fd, &st)) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: fstat failed!"); +#endif + return; + } + dataSize = st.st_size; + + + fontData = (const uchar *)::mmap(0, st.st_size, PROT_READ | (renderingFontEngine ? PROT_WRITE : 0), MAP_SHARED, fd, 0); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +#if defined(DEBUG_FONTENGINE) + perror("mmap failed"); +#endif + fontData = 0; + return; + } +#endif //QT_FONTS_ARE_RESOURCES + + if (!verifyHeader(fontData, dataSize)) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "verifyHeader failed!"; +#endif + return; + } + + const Header *header = reinterpret_cast<const Header *>(fontData); + + readOnly = (header->lock == 0xffffffff); + + const uchar *data = fontData + sizeof(Header) + qFromBigEndian<quint16>(header->dataSize); + const uchar *endPtr = fontData + dataSize; + while (data <= endPtr - 8) { + quint16 blockTag = readValue<quint16>(data); + data += 2; // skip padding + quint32 blockSize = readValue<quint32>(data); + + if (blockTag == CMapBlock) { + cmapOffset = data - fontData; + cmapSize = blockSize; + } else if (blockTag == GMapBlock) { + glyphMapOffset = data - fontData; + glyphMapEntries = blockSize / 4; + } else if (blockTag == GlyphBlock) { + glyphDataOffset = data - fontData; + glyphDataSize = blockSize; + } + + data += blockSize; + } + + face_id.filename = QFile::encodeName(extractHeaderField(fontData, Tag_FileName).toString()); + face_id.index = extractHeaderField(fontData, Tag_FileIndex).toInt(); +#if !defined(QT_NO_FREETYPE) && !defined(QT_FONTS_ARE_RESOURCES) + freetype = QFreetypeFace::getFace(face_id); + if (!freetype) { + QString newPath = +#ifndef QT_NO_SETTINGS + QLibraryInfo::location(QLibraryInfo::LibrariesPath) + +#endif + QLatin1String("/fonts/") + + QFileInfo(QFile::decodeName(face_id.filename)).fileName(); + face_id.filename = QFile::encodeName(newPath); + freetype = QFreetypeFace::getFace(face_id); + } + if (freetype) { + const quint32 qpfTtfRevision = extractHeaderField(fontData, Tag_FontRevision).toUInt(); + uchar data[4]; + uint length = 4; + bool ok = freetype->getSfntTable(MAKE_TAG('h', 'e', 'a', 'd'), data, &length); + if (!ok || length != 4 + || qFromBigEndian<quint32>(data) != qpfTtfRevision) { + freetype->release(face_id); + freetype = 0; + } + } + if (!cmapOffset && freetype) { + freetypeCMapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + externalCMap = reinterpret_cast<const uchar *>(freetypeCMapTable.constData()); + cmapSize = freetypeCMapTable.size(); + } +#endif + + // get the real cmap + if (cmapOffset) { + int tableSize = cmapSize; + const uchar *cmapPtr = getCMap(fontData + cmapOffset, tableSize, &symbol, &cmapSize); + if (cmapPtr) + cmapOffset = cmapPtr - fontData; + else + cmapOffset = 0; + } else if (externalCMap) { + int tableSize = cmapSize; + externalCMap = getCMap(externalCMap, tableSize, &symbol, &cmapSize); + } + + // verify all the positions in the glyphMap + if (glyphMapOffset) { + const quint32 *gmapPtr = reinterpret_cast<const quint32 *>(fontData + glyphMapOffset); + for (uint i = 0; i < glyphMapEntries; ++i) { + quint32 glyphDataPos = qFromBigEndian<quint32>(gmapPtr[i]); + if (glyphDataPos == 0xffffffff) + continue; + if (glyphDataPos >= glyphDataSize) { + // error + glyphMapOffset = 0; + glyphMapEntries = 0; + break; + } + } + } + +#if defined(DEBUG_FONTENGINE) + if (!isValid()) + qDebug() << "fontData" << fontData << "dataSize" << dataSize + << "externalCMap" << externalCMap << "cmapOffset" << cmapOffset + << "glyphMapOffset" << glyphMapOffset << "glyphDataOffset" << glyphDataOffset + << "fd" << fd << "glyphDataSize" << glyphDataSize; +#endif +#if defined(Q_WS_QWS) + if (isValid() && renderingFontEngine) + qt_fbdpy->sendFontCommand(QWSFontCommand::StartedUsingFont, encodedFileName); +#endif +} + +QFontEngineQPF::~QFontEngineQPF() +{ +#if defined(Q_WS_QWS) + if (isValid() && renderingFontEngine) { + QT_TRY { + qt_fbdpy->sendFontCommand(QWSFontCommand::StoppedUsingFont, encodedFileName); + } QT_CATCH(...) { + qDebug("QFontEngineQPF::~QFontEngineQPF: Out of memory"); + // ignore. + } + } +#endif + delete renderingFontEngine; + if (fontData) { + if (munmap((void *)fontData, dataSize) == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "~QFontEngineQPF: Unable to munmap"); +#endif + } + } + if (fd != -1) + ::close(fd); +#if !defined(QT_NO_FREETYPE) + if (freetype) + freetype->release(face_id); +#endif +} + +bool QFontEngineQPF::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ +#if !defined(QT_NO_FREETYPE) + if (freetype) + return freetype->getSfntTable(tag, buffer, length); +#endif + Q_UNUSED(tag); + Q_UNUSED(buffer); + *length = 0; + return false; +} + +bool QFontEngineQPF::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (!externalCMap && !cmapOffset && renderingFontEngine) { + if (!renderingFontEngine->stringToCMap(str, len, glyphs, nglyphs, flags)) + return false; +#ifndef QT_NO_FREETYPE + const_cast<QFontEngineQPF *>(this)->ensureGlyphsLoaded(*glyphs); +#endif + return true; + } + + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if defined(DEBUG_FONTENGINE) + QSet<QChar> seenGlyphs; +#endif + + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + ++glyph_pos; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); +#if 0 && defined(DEBUG_FONTENGINE) + QChar c(uc); + if (!findGlyph(glyphs[glyph_pos].glyph) && !seenGlyphs.contains(c)) + qDebug() << "glyph for character" << c << '/' << hex << uc << "is" << dec << glyphs[glyph_pos].glyph; + + seenGlyphs.insert(c); +#endif + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineQPF::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ +#ifndef QT_NO_FREETYPE + const_cast<QFontEngineQPF *>(this)->ensureGlyphsLoaded(*glyphs); +#endif + for (int i = 0; i < glyphs->numGlyphs; ++i) { + const Glyph *g = findGlyph(glyphs->glyphs[i]); + if (!g) { + glyphs->glyphs[i] = 0; + continue; + } + glyphs->advances_x[i] = g->advance; + glyphs->advances_y[i] = 0; + } +} + +QImage QFontEngineQPF::alphaMapForGlyph(glyph_t g) +{ + const Glyph *glyph = findGlyph(g); + if (!glyph) + return QImage(); + + const uchar *bits = ((const uchar *) glyph) + sizeof(Glyph); + + QImage image(glyph->width, glyph->height, QImage::Format_Indexed8); + for (int j=0; j<256; ++j) + image.setColor(j, qRgba(0, 0, 0, j)); + + for (int i=0; i<glyph->height; ++i) { + memcpy(image.scanLine(i), bits, glyph->bytesPerLine); + bits += glyph->bytesPerLine; + } + return image; +} + +void QFontEngineQPF::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast<QRasterPaintEngine*>(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + for(int i = 0; i < glyphs.size(); i++) { + const Glyph *glyph = findGlyph(glyphs[i]); + if (!glyph) + continue; + + const int depth = 8; //### + + paintEngine->alphaPenBlt(reinterpret_cast<const uchar *>(glyph) + sizeof(Glyph), glyph->bytesPerLine, depth, + qRound(positions[i].x) + glyph->x, + qRound(positions[i].y) + glyph->y, + glyph->width, glyph->height); + } +} + +void QFontEngineQPF::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (renderingFontEngine && + (renderingFontEngine->type() != QFontEngine::Proxy + || static_cast<QProxyFontEngine *>(renderingFontEngine)->capabilities() & QAbstractFontEngine::CanOutlineGlyphs)) { + renderingFontEngine->addOutlineToPath(x, y, glyphs, path, flags); + return; + } + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPF::boundingBox(const QGlyphLayout &glyphs) +{ +#ifndef QT_NO_FREETYPE + const_cast<QFontEngineQPF *>(this)->ensureGlyphsLoaded(glyphs); +#endif + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (!g) + continue; + + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y + g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += g->advance; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineQPF::boundingBox(glyph_t glyph) +{ +#ifndef QT_NO_FREETYPE + { + QGlyphLayoutArray<1> tmp; + tmp.glyphs[0] = glyph; + const_cast<QFontEngineQPF *>(this)->ensureGlyphsLoaded(tmp); + } +#endif + glyph_metrics_t overall; + const Glyph *g = findGlyph(glyph); + if (!g) + return overall; + overall.x = g->x; + overall.y = g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + return overall; +} + +QFixed QFontEngineQPF::ascent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Ascent).value<qreal>()); +} + +QFixed QFontEngineQPF::descent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Descent).value<qreal>()); +} + +QFixed QFontEngineQPF::leading() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Leading).value<qreal>()); +} + +qreal QFontEngineQPF::maxCharWidth() const +{ + return extractHeaderField(fontData, Tag_MaxCharWidth).value<qreal>(); +} + +qreal QFontEngineQPF::minLeftBearing() const +{ + return extractHeaderField(fontData, Tag_MinLeftBearing).value<qreal>(); +} + +qreal QFontEngineQPF::minRightBearing() const +{ + return extractHeaderField(fontData, Tag_MinRightBearing).value<qreal>(); +} + +QFixed QFontEngineQPF::underlinePosition() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_UnderlinePosition).value<qreal>()); +} + +QFixed QFontEngineQPF::lineThickness() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_LineThickness).value<qreal>()); +} + +QFontEngine::Type QFontEngineQPF::type() const +{ + return QFontEngine::QPF2; +} + +bool QFontEngineQPF::canRender(const QChar *string, int len) +{ + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + glyph_t g = getTrueTypeGlyphIndex(cmap, uc); + if(!g && uc < 0x100) + g = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + if (!g) + return false; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (!getTrueTypeGlyphIndex(cmap, uc)) + return false; + } + } + return true; +} + +bool QFontEngineQPF::isValid() const +{ + return fontData && dataSize && (cmapOffset || externalCMap || renderingFontEngine) + && glyphMapOffset && glyphDataOffset && (fd >= 0 || glyphDataSize > 0); +} + +#if !defined(QT_NO_FREETYPE) +FT_Face QFontEngineQPF::lockFace() const +{ + Q_ASSERT(freetype); + freetype->lock(); + FT_Face face = freetype->face; + + // ### not perfect + const int ysize = qRound(fontDef.pixelSize * qreal(64)); + const int xsize = ysize; + + if (freetype->xsize != xsize || freetype->ysize != ysize) { + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + freetype->xsize = xsize; + freetype->ysize = ysize; + } + FT_Matrix identityMatrix; + identityMatrix.xx = 0x10000; + identityMatrix.yy = 0x10000; + identityMatrix.xy = 0; + identityMatrix.yx = 0; + if (freetype->matrix.xx != identityMatrix.xx || + freetype->matrix.yy != identityMatrix.yy || + freetype->matrix.xy != identityMatrix.xy || + freetype->matrix.yx != identityMatrix.yx) { + freetype->matrix = identityMatrix; + FT_Set_Transform(face, &freetype->matrix, 0); + } + return face; +} + +void QFontEngineQPF::unlockFace() const +{ + freetype->unlock(); +} + +void QFontEngineQPF::doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const +{ + if (!kerning_pairs_loaded) { + kerning_pairs_loaded = true; + if (freetype) { + lockFace(); + if (freetype->face->size->metrics.x_ppem != 0) { + QFixed scalingFactor(freetype->face->units_per_EM/freetype->face->size->metrics.x_ppem); + unlockFace(); + const_cast<QFontEngineQPF *>(this)->loadKerningPairs(scalingFactor); + } else { + unlockFace(); + } + } + } + QFontEngine::doKerning(g, flags); +} + +HB_Error QFontEngineQPF::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + if (!freetype) + return HB_Err_Not_Covered; + lockFace(); + HB_Error result = freetype->getPointInOutline(glyph, flags, point, xpos, ypos, nPoints); + unlockFace(); + return result; +} + +QFixed QFontEngineQPF::emSquareSize() const +{ + if (!freetype) + return QFontEngine::emSquareSize(); + if (FT_IS_SCALABLE(freetype->face)) + return freetype->face->units_per_EM; + else + return freetype->face->size->metrics.y_ppem; +} + +void QFontEngineQPF::ensureGlyphsLoaded(const QGlyphLayout &glyphs) +{ + if (readOnly) + return; + bool locked = false; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + if (!glyphs.glyphs[i]) + continue; + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (g) + continue; + if (!locked) { + if (!lockFile()) + return; + locked = true; + g = findGlyph(glyphs.glyphs[i]); + if (g) + continue; + } + loadGlyph(glyphs.glyphs[i]); + } + if (locked) { + unlockFile(); +#if defined(DEBUG_FONTENGINE) + qDebug() << "Finished rendering glyphs\n"; +#endif + } +} + +void QFontEngineQPF::loadGlyph(glyph_t glyph) +{ + quint32 glyphPos = ~0; + + if (!renderingFontEngine) + return; + QImage img = renderingFontEngine->alphaMapForGlyph(glyph); + if (img.format() != QImage::Format_Indexed8) { + bool mono = img.depth() == 1; + img = img.convertToFormat(QImage::Format_Indexed8); + if (mono) { + //### we know that 1 is opaque and 0 is transparent + uchar *byte = img.bits(); + int count = img.byteCount(); + while (count--) + *byte++ *= 0xff; + } + } + glyph_metrics_t metrics = renderingFontEngine->boundingBox(glyph); + renderingFontEngine->removeGlyphFromCache(glyph); + + off_t oldSize = ::lseek(fd, 0, SEEK_END); + if (oldSize == (off_t)-1) + return; + + Glyph g; + g.width = img.width(); + g.height = img.height(); + g.bytesPerLine = img.bytesPerLine(); + g.x = qRound(metrics.x); + g.y = qRound(metrics.y); + g.advance = qRound(metrics.xoff); + + QT_WRITE(fd, &g, sizeof(g)); + QT_WRITE(fd, img.bits(), img.byteCount()); + + glyphPos = oldSize - glyphDataOffset; +#if 0 && defined(DEBUG_FONTENGINE) + qDebug() << "glyphPos for new glyph" << glyph << "is" << glyphPos << "oldSize" << oldSize << "glyphDataOffset" << glyphDataOffset; +#endif + + quint32 *gmap = (quint32 *)(fontData + glyphMapOffset); + gmap[glyph] = qToBigEndian(glyphPos); + + glyphDataSize = glyphPos + sizeof(g) + img.byteCount(); + quint32 *blockSizePtr = (quint32 *)(fontData + glyphDataOffset - 4); + *blockSizePtr = qToBigEndian(glyphDataSize); +} + +bool QFontEngineQPF::lockFile() +{ + // #### this does not handle the case when the process holding the + // lock hangs for some reason + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // lock the whole file + while (fcntl(fd, F_SETLKW, &lock) != 0) { + if (errno == EINTR) + continue; + perror("locking qpf"); + return false; + } + Header *header = (Header *)fontData; + if (header->lock) { + lock.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &lock) != 0) + perror("unlocking possibly corrupt qpf"); + return false; + } +#if defined(Q_WS_QWS) + extern int qws_client_id; + // qws_client_id == 0 means we're the server. in this case we just + // set the id to 1 + header->lock = qws_client_id ? qws_client_id : 1; +#else + header->lock = 1; +#endif + return true; +} + +void QFontEngineQPF::unlockFile() +{ + ((Header *)fontData)->lock = 0; + + struct flock lock; + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // lock the whole file + if (fcntl(fd, F_SETLK, &lock) != 0) { + perror("unlocking qpf"); + } + + remapFontData(); +} + +void QFontEngineQPF::remapFontData() +{ + off_t newFileSize = ::lseek(fd, 0, SEEK_END); + if (newFileSize == (off_t)-1) { +#ifdef DEBUG_FONTENGINE + perror("QFontEngineQPF::remapFontData: lseek failed"); +#endif + fontData = 0; + return; + } + +#ifndef QT_NO_MREMAP + fontData = static_cast<uchar *>(::mremap(const_cast<uchar *>(fontData), dataSize, newFileSize, MREMAP_MAYMOVE)); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +# if defined(DEBUG_FONTENGINE) + perror("QFontEngineQPF::remapFontData(): mremap failed"); +# endif + fontData = 0; + } + + if (!fontData) +#endif // QT_NO_MREMAP + { + int status = ::munmap((void *)fontData, dataSize); + if (status != 0) + qErrnoWarning(status, "QFontEngineQPF::remapFomrData: munmap failed!"); + + fontData = (const uchar *)::mmap(0, newFileSize, PROT_READ | (renderingFontEngine ? PROT_WRITE : 0), + MAP_SHARED, fd, 0); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +# if defined(DEBUG_FONTENGINE) + perror("mmap failed"); +# endif + fontData = 0; + return; + } + } + + dataSize = newFileSize; + glyphDataSize = newFileSize - glyphDataOffset; +#if defined(DEBUG_FONTENGINE) + qDebug() << "remapped the font file to" << newFileSize << "bytes"; +#endif +} + +#endif // QT_NO_FREETYPE + +void QPFGenerator::generate() +{ + writeHeader(); + writeGMap(); + writeBlock(QFontEngineQPF::GlyphBlock, QByteArray()); + + dev->seek(4); // position of header.lock + writeUInt32(0); +} + +void QPFGenerator::writeHeader() +{ + QFontEngineQPF::Header header; + + header.magic[0] = 'Q'; + header.magic[1] = 'P'; + header.magic[2] = 'F'; + header.magic[3] = '2'; + header.lock = 1; + header.majorVersion = QFontEngineQPF::CurrentMajorVersion; + header.minorVersion = QFontEngineQPF::CurrentMinorVersion; + header.dataSize = 0; + dev->write((const char *)&header, sizeof(header)); + + writeTaggedString(QFontEngineQPF::Tag_FontName, fe->fontDef.family.toUtf8()); + + QFontEngine::FaceId face = fe->faceId(); + writeTaggedString(QFontEngineQPF::Tag_FileName, face.filename); + writeTaggedUInt32(QFontEngineQPF::Tag_FileIndex, face.index); + + { + uchar data[4]; + uint len = 4; + bool ok = fe->getSfntTableData(MAKE_TAG('h', 'e', 'a', 'd'), data, &len); + if (ok) { + const quint32 revision = qFromBigEndian<quint32>(data); + writeTaggedUInt32(QFontEngineQPF::Tag_FontRevision, revision); + } + } + + writeTaggedQFixed(QFontEngineQPF::Tag_Ascent, fe->ascent()); + writeTaggedQFixed(QFontEngineQPF::Tag_Descent, fe->descent()); + writeTaggedQFixed(QFontEngineQPF::Tag_Leading, fe->leading()); + writeTaggedQFixed(QFontEngineQPF::Tag_XHeight, fe->xHeight()); + writeTaggedQFixed(QFontEngineQPF::Tag_AverageCharWidth, fe->averageCharWidth()); + writeTaggedQFixed(QFontEngineQPF::Tag_MaxCharWidth, QFixed::fromReal(fe->maxCharWidth())); + writeTaggedQFixed(QFontEngineQPF::Tag_LineThickness, fe->lineThickness()); + writeTaggedQFixed(QFontEngineQPF::Tag_MinLeftBearing, QFixed::fromReal(fe->minLeftBearing())); + writeTaggedQFixed(QFontEngineQPF::Tag_MinRightBearing, QFixed::fromReal(fe->minRightBearing())); + writeTaggedQFixed(QFontEngineQPF::Tag_UnderlinePosition, fe->underlinePosition()); + writeTaggedUInt8(QFontEngineQPF::Tag_PixelSize, fe->fontDef.pixelSize); + writeTaggedUInt8(QFontEngineQPF::Tag_Weight, fe->fontDef.weight); + writeTaggedUInt8(QFontEngineQPF::Tag_Style, fe->fontDef.style); + + writeTaggedUInt8(QFontEngineQPF::Tag_GlyphFormat, QFontEngineQPF::AlphamapGlyphs); + + writeTaggedString(QFontEngineQPF::Tag_EndOfHeader, QByteArray()); + align4(); + + const quint64 size = dev->pos(); + header.dataSize = qToBigEndian<quint16>(size - sizeof(header)); + dev->seek(0); + dev->write((const char *)&header, sizeof(header)); + dev->seek(size); +} + +void QPFGenerator::writeGMap() +{ + const quint16 glyphCount = fe->glyphCount(); + + writeUInt16(QFontEngineQPF::GMapBlock); + writeUInt16(0); // padding + writeUInt32(glyphCount * 4); + + QByteArray &buffer = dev->buffer(); + const int numBytes = glyphCount * sizeof(quint32); + qint64 pos = buffer.size(); + buffer.resize(pos + numBytes); + qMemSet(buffer.data() + pos, 0xff, numBytes); + dev->seek(pos + numBytes); +} + +void QPFGenerator::writeBlock(QFontEngineQPF::BlockTag tag, const QByteArray &data) +{ + writeUInt16(tag); + writeUInt16(0); // padding + const int padSize = ((data.size() + 3) / 4) * 4 - data.size(); + writeUInt32(data.size() + padSize); + dev->write(data); + for (int i = 0; i < padSize; ++i) + writeUInt8(0); +} + +void QPFGenerator::writeTaggedString(QFontEngineQPF::HeaderTag tag, const QByteArray &string) +{ + writeUInt16(tag); + writeUInt16(string.length()); + dev->write(string); +} + +void QPFGenerator::writeTaggedUInt32(QFontEngineQPF::HeaderTag tag, quint32 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt32(value); +} + +void QPFGenerator::writeTaggedUInt8(QFontEngineQPF::HeaderTag tag, quint8 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt8(value); +} + +void QPFGenerator::writeTaggedQFixed(QFontEngineQPF::HeaderTag tag, QFixed value) +{ + writeUInt16(tag); + writeUInt16(sizeof(quint32)); + writeUInt32(value.value()); +} + +#endif // QT_NO_QWS_QPF2 + +/* + Creates a new multi qws engine. + + This function takes ownership of the QFontEngine, increasing it's refcount. +*/ +QFontEngineMultiQWS::QFontEngineMultiQWS(QFontEngine *fe, int _script, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size() + 1), + fallbackFamilies(fallbacks), script(_script) +{ + engines[0] = fe; + fe->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiQWS::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); + engines[at]->ref.ref(); + engines[at]->fontDef = request; +} + +void QFontEngineMultiQWS::draw(QPaintEngine */*p*/, qreal /*x*/, qreal /*y*/, const QTextItemInt &/*si*/) +{ + qFatal("QFontEngineMultiQWS::draw should never be called!"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_qpf_p.h b/src/gui/text/qfontengine_qpf_p.h new file mode 100644 index 0000000000..571ce1085e --- /dev/null +++ b/src/gui/text/qfontengine_qpf_p.h @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_QPF_P_H +#define QFONTENGINE_QPF_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 "qfontengine_p.h" +#include <qendian.h> +#include <qbuffer.h> + +#ifndef QT_NO_QWS_QPF2 +#if !defined(QT_NO_FREETYPE) +# include "qfontengine_ft_p.h" +#endif +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF2 + +class QFontEngine; +class QFreetypeFace; + +class Q_GUI_EXPORT QFontEngineQPF : public QFontEngine +{ +public: + // if you add new tags please make sure to update the tables in + // qpfutil.cpp and tools/makeqpf/qpf2.cpp + enum HeaderTag { + Tag_FontName, // 0 string + Tag_FileName, // 1 string + Tag_FileIndex, // 2 quint32 + Tag_FontRevision, // 3 quint32 + Tag_FreeText, // 4 string + Tag_Ascent, // 5 QFixed + Tag_Descent, // 6 QFixed + Tag_Leading, // 7 QFixed + Tag_XHeight, // 8 QFixed + Tag_AverageCharWidth, // 9 QFixed + Tag_MaxCharWidth, // 10 QFixed + Tag_LineThickness, // 11 QFixed + Tag_MinLeftBearing, // 12 QFixed + Tag_MinRightBearing, // 13 QFixed + Tag_UnderlinePosition, // 14 QFixed + Tag_GlyphFormat, // 15 quint8 + Tag_PixelSize, // 16 quint8 + Tag_Weight, // 17 quint8 + Tag_Style, // 18 quint8 + Tag_EndOfHeader, // 19 string + Tag_WritingSystems, // 20 bitfield + + NumTags + }; + + enum TagType { + StringType, + FixedType, + UInt8Type, + UInt32Type, + BitFieldType + }; + + struct Tag + { + quint16 tag; + quint16 size; + }; + + enum GlyphFormat { + BitmapGlyphs = 1, + AlphamapGlyphs = 8 + }; + + enum { + CurrentMajorVersion = 2, + CurrentMinorVersion = 0 + }; + + // The CMap is identical to the TrueType CMap table format + // The GMap table is a normal array with the total number of + // covered glyphs in the TrueType font + enum BlockTag { + CMapBlock, + GMapBlock, + GlyphBlock + }; + + struct Q_PACKED Header + { + char magic[4]; // 'QPF2' + quint32 lock; // values: 0 = unlocked, 0xffffffff = read-only, otherwise qws client id of locking process + quint8 majorVersion; + quint8 minorVersion; + quint16 dataSize; + }; + + struct Q_PACKED Block + { + quint16 tag; + quint16 pad; + quint32 dataSize; + }; + + struct Q_PACKED Glyph + { + quint8 width; + quint8 height; + quint8 bytesPerLine; + qint8 x; + qint8 y; + qint8 advance; + }; + +#ifdef QT_FONTS_ARE_RESOURCES + QFontEngineQPF(const QFontDef &def, const uchar *bytes, int size); +#else + QFontEngineQPF(const QFontDef &def, int fd, QFontEngine *renderingFontEngine = 0); +#endif + ~QFontEngineQPF(); + + FaceId faceId() const { return face_id; } + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + QImage alphaMapForGlyph(glyph_t t); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + QFixed underlinePosition() const; + QFixed lineThickness() const; + + Type type() const; + + bool canRender(const QChar *string, int len); + inline const char *name() const { return "QPF2"; } + + virtual int glyphCount() const { return glyphMapEntries; } + + bool isValid() const; + + const Glyph *findGlyph(glyph_t g) const; + + static bool verifyHeader(const uchar *data, int size); + static QVariant extractHeaderField(const uchar *data, HeaderTag tag); + static QList<QByteArray> cleanUpAfterClientCrash(const QList<int> &crashedClientIds); + +#if !defined(QT_NO_FREETYPE) + FT_Face lockFace() const; + void unlockFace() const; + void doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const; + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + virtual QFixed emSquareSize() const; +#endif + + inline QString fontFile() const { return fileName; } + + QFontEngine *renderingEngine() const { return renderingFontEngine; } + + QFontEngine *takeRenderingEngine() + { + QFontEngine *engine = renderingFontEngine; + renderingFontEngine = 0; + return engine; + } + +private: +#if !defined(QT_NO_FREETYPE) + void ensureGlyphsLoaded(const QGlyphLayout &glyphs); + void loadGlyph(glyph_t glyph); + bool lockFile(); + void unlockFile(); + void remapFontData(); +#endif + + int fd; + const uchar *fontData; + int dataSize; + const uchar *externalCMap; + quint32 cmapOffset; + int cmapSize; + quint32 glyphMapOffset; + quint32 glyphMapEntries; + quint32 glyphDataOffset; + quint32 glyphDataSize; + QString fileName; + QByteArray encodedFileName; + bool readOnly; + + QFreetypeFace *freetype; + FaceId face_id; + QByteArray freetypeCMapTable; + mutable bool kerning_pairs_loaded; + QFontEngine *renderingFontEngine; +}; + +struct QPFGenerator +{ + QPFGenerator(QBuffer *device, QFontEngine *engine) + : dev(device), fe(engine) {} + + void generate(); + void writeHeader(); + void writeGMap(); + void writeBlock(QFontEngineQPF::BlockTag tag, const QByteArray &data); + + void writeTaggedString(QFontEngineQPF::HeaderTag tag, const QByteArray &string); + void writeTaggedUInt32(QFontEngineQPF::HeaderTag tag, quint32 value); + void writeTaggedUInt8(QFontEngineQPF::HeaderTag tag, quint8 value); + void writeTaggedQFixed(QFontEngineQPF::HeaderTag tag, QFixed value); + + void writeUInt16(quint16 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt32(quint32 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt8(quint8 value) { dev->write((const char *)&value, sizeof(value)); } + void writeInt8(qint8 value) { dev->write((const char *)&value, sizeof(value)); } + + void align4() { while (dev->pos() & 3) { dev->putChar('\0'); } } + + QBuffer *dev; + QFontEngine *fe; +}; + +#endif // QT_NO_QWS_QPF2 + +class QFontEngineMultiQWS : public QFontEngineMulti +{ +public: + QFontEngineMultiQWS(QFontEngine *fe, int script, const QStringList &fallbacks); + + void loadEngine(int at); + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + +private: + QStringList fallbackFamilies; + int script; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_QPF_P_H diff --git a/src/gui/text/qfontengine_qws.cpp b/src/gui/text/qfontengine_qws.cpp new file mode 100644 index 0000000000..b71c4a7aba --- /dev/null +++ b/src/gui/text/qfontengine_qws.cpp @@ -0,0 +1,665 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_p.h" +#include <private/qunicodetables_p.h> +#include <qwsdisplay_qws.h> +#include <qvarlengtharray.h> +#include <private/qpainter_p.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qpdf_p.h> +#include "qtextengine_p.h" +#include "private/qcore_unix_p.h" // overrides QT_OPEN + +#include <qdebug.h> + + +#ifndef QT_NO_QWS_QPF + +#include "qfile.h" +#include "qdir.h" + +#define QT_USE_MMAP +#include <stdlib.h> + +#ifdef QT_USE_MMAP +// for mmap +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <errno.h> + +# if defined(QT_LINUXBASE) && !defined(MAP_FILE) + // LSB 3.2 does not define MAP_FILE +# define MAP_FILE 0 +# endif + +#endif + +#endif // QT_NO_QWS_QPF + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF +QT_BEGIN_INCLUDE_NAMESPACE +#include "qplatformdefs.h" +QT_END_INCLUDE_NAMESPACE + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +#define FM_SMOOTH 1 + + +class Q_PACKED QPFGlyphMetrics { + +public: + quint8 linestep; + quint8 width; + quint8 height; + quint8 flags; + + qint8 bearingx; // Difference from pen position to glyph's left bbox + quint8 advance; // Difference between pen positions + qint8 bearingy; // Used for putting characters on baseline + + qint8 reserved; // Do not use + + // Flags: + // RendererOwnsData - the renderer is responsible for glyph data + // memory deletion otherwise QPFGlyphTree must + // delete [] the data when the glyph is deleted. + enum Flags { RendererOwnsData=0x01 }; +}; + +class QPFGlyph { +public: + QPFGlyph() { metrics=0; data=0; } + QPFGlyph(QPFGlyphMetrics* m, uchar* d) : + metrics(m), data(d) { } + ~QPFGlyph() {} + + QPFGlyphMetrics* metrics; + uchar* data; +}; + +struct Q_PACKED QPFFontMetrics{ + qint8 ascent,descent; + qint8 leftbearing,rightbearing; + quint8 maxwidth; + qint8 leading; + quint8 flags; + quint8 underlinepos; + quint8 underlinewidth; + quint8 reserved3; +}; + + +class QPFGlyphTree { +public: + /* reads in a tree like this: + + A-Z + / \ + 0-9 a-z + + etc. + + */ + glyph_t min,max; + QPFGlyphTree* less; + QPFGlyphTree* more; + QPFGlyph* glyph; +public: +#ifdef QT_USE_MMAP + QPFGlyphTree(uchar*& data) + { + read(data); + } +#else + QPFGlyphTree(QIODevice& f) + { + read(f); + } +#endif + + ~QPFGlyphTree() + { + // NOTE: does not delete glyph[*].metrics or .data. + // the caller does this (only they know who owns + // the data). See clear(). + delete less; + delete more; + delete [] glyph; + } + + bool inFont(glyph_t g) const + { + if ( g < min ) { + if ( !less ) + return false; + return less->inFont(g); + } else if ( g > max ) { + if ( !more ) + return false; + return more->inFont(g); + } + return true; + } + + QPFGlyph* get(glyph_t g) + { + if ( g < min ) { + if ( !less ) + return 0; + return less->get(g); + } else if ( g > max ) { + if ( !more ) + return 0; + return more->get(g); + } + return &glyph[g - min]; + } + int totalChars() const + { + if ( !this ) return 0; + return max-min+1 + less->totalChars() + more->totalChars(); + } + int weight() const + { + if ( !this ) return 0; + return 1 + less->weight() + more->weight(); + } + + void dump(int indent=0) + { + for (int i=0; i<indent; i++) printf(" "); + printf("%d..%d",min,max); + //if ( indent == 0 ) + printf(" (total %d)",totalChars()); + printf("\n"); + if ( less ) less->dump(indent+1); + if ( more ) more->dump(indent+1); + } + +private: + QPFGlyphTree() + { + } + +#ifdef QT_USE_MMAP + void read(uchar*& data) + { + // All node data first + readNode(data); + // Then all non-video data + readMetrics(data); + // Then all video data + readData(data); + } +#else + void read(QIODevice& f) + { + // All node data first + readNode(f); + // Then all non-video data + readMetrics(f); + // Then all video data + readData(f); + } +#endif + +#ifdef QT_USE_MMAP + void readNode(uchar*& data) + { + uchar rw = *data++; + uchar cl = *data++; + min = (rw << 8) | cl; + rw = *data++; + cl = *data++; + max = (rw << 8) | cl; + int flags = *data++; + if ( flags & 1 ) + less = new QPFGlyphTree; + else + less = 0; + if ( flags & 2 ) + more = new QPFGlyphTree; + else + more = 0; + int n = max-min+1; + glyph = new QPFGlyph[n]; + + if ( less ) + less->readNode(data); + if ( more ) + more->readNode(data); + } +#else + void readNode(QIODevice& f) + { + char rw; + char cl; + f.getChar(&rw); + f.getChar(&cl); + min = (rw << 8) | cl; + f.getChar(&rw); + f.getChar(&cl); + max = (rw << 8) | cl; + char flags; + f.getChar(&flags); + if ( flags & 1 ) + less = new QPFGlyphTree; + else + less = 0; + if ( flags & 2 ) + more = new QPFGlyphTree; + else + more = 0; + int n = max-min+1; + glyph = new QPFGlyph[n]; + + if ( less ) + less->readNode(f); + if ( more ) + more->readNode(f); + } +#endif + +#ifdef QT_USE_MMAP + void readMetrics(uchar*& data) + { + int n = max-min+1; + for (int i=0; i<n; i++) { + glyph[i].metrics = (QPFGlyphMetrics*)data; + data += sizeof(QPFGlyphMetrics); + } + if ( less ) + less->readMetrics(data); + if ( more ) + more->readMetrics(data); + } +#else + void readMetrics(QIODevice& f) + { + int n = max-min+1; + for (int i=0; i<n; i++) { + glyph[i].metrics = new QPFGlyphMetrics; + f.read((char*)glyph[i].metrics, sizeof(QPFGlyphMetrics)); + } + if ( less ) + less->readMetrics(f); + if ( more ) + more->readMetrics(f); + } +#endif + +#ifdef QT_USE_MMAP + void readData(uchar*& data) + { + int n = max-min+1; + for (int i=0; i<n; i++) { + QSize s( glyph[i].metrics->width, glyph[i].metrics->height ); + //######### s = qt_screen->mapToDevice( s ); + uint datasize = glyph[i].metrics->linestep * s.height(); + glyph[i].data = data; data += datasize; + } + if ( less ) + less->readData(data); + if ( more ) + more->readData(data); + } +#else + void readData(QIODevice& f) + { + int n = max-min+1; + for (int i=0; i<n; i++) { + QSize s( glyph[i].metrics->width, glyph[i].metrics->height ); + //############### s = qt_screen->mapToDevice( s ); + uint datasize = glyph[i].metrics->linestep * s.height(); + glyph[i].data = new uchar[datasize]; // ### deleted? + f.read((char*)glyph[i].data, datasize); + } + if ( less ) + less->readData(f); + if ( more ) + more->readData(f); + } +#endif + +}; + +class QFontEngineQPF1Data +{ +public: + QPFFontMetrics fm; + QPFGlyphTree *tree; + void *mmapStart; + size_t mmapLength; +}; + +#if defined(Q_OS_INTEGRITY) +static void *qt_mmap(void *start, size_t length, int /*prot*/, int /*flags*/, int fd, off_t offset) +{ + // INTEGRITY cannot mmap local files - load it into a local buffer + if (::lseek(fd, offset, SEEK_SET) == -1) { +# if defined(DEBUG_FONTENGINE) + perror("lseek failed"); +# endif + } + void *buf = malloc(length); + if (::read(fd, buf, length) != (ssize_t)length) { +# if defined(DEBUG_FONTENGINE) + perror("read failed"); +# endif + } + + return buf; +} +#else +static inline void *qt_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) +{ + return mmap(start, length, prot, flags, fd, offset); +} +#endif + + + +QFontEngineQPF1::QFontEngineQPF1(const QFontDef&, const QString &fn) +{ + cache_cost = 1; + + int f = QT_OPEN( QFile::encodeName(fn), O_RDONLY, 0); + Q_ASSERT(f>=0); + QT_STATBUF st; + if ( QT_FSTAT( f, &st ) ) + qFatal("Failed to stat %s",QFile::encodeName(fn).data()); + uchar* data = (uchar*)qt_mmap( 0, // any address + st.st_size, // whole file + PROT_READ, // read-only memory +#if defined(Q_OS_INTEGRITY) + 0, +#elif !defined(Q_OS_SOLARIS) && !defined(Q_OS_QNX4) && !defined(Q_OS_VXWORKS) + MAP_FILE | MAP_PRIVATE, // swap-backed map from file +#else + MAP_PRIVATE, +#endif + f, 0 ); // from offset 0 of f +#if !defined(MAP_FAILED) && (defined(Q_OS_QNX4) || defined(Q_OS_INTEGRITY)) +#define MAP_FAILED ((void *)-1) +#endif + if ( !data || data == (uchar*)MAP_FAILED ) + qFatal("Failed to mmap %s",QFile::encodeName(fn).data()); + QT_CLOSE(f); + + d = new QFontEngineQPF1Data; + d->mmapStart = data; + d->mmapLength = st.st_size; + memcpy(reinterpret_cast<char*>(&d->fm),data,sizeof(d->fm)); + + data += sizeof(d->fm); + d->tree = new QPFGlyphTree(data); + glyphFormat = (d->fm.flags & FM_SMOOTH) ? QFontEngineGlyphCache::Raster_A8 + : QFontEngineGlyphCache::Raster_Mono; +#if 0 + qDebug() << "font file" << fn + << "ascent" << d->fm.ascent << "descent" << d->fm.descent + << "leftbearing" << d->fm.leftbearing + << "rightbearing" << d->fm.rightbearing + << "maxwidth" << d->fm.maxwidth + << "leading" << d->fm.leading + << "flags" << d->fm.flags + << "underlinepos" << d->fm.underlinepos + << "underlinewidth" << d->fm.underlinewidth; +#endif +} + +QFontEngineQPF1::~QFontEngineQPF1() +{ + if (d->mmapStart) + munmap(d->mmapStart, d->mmapLength); + delete d->tree; + delete d; +} + + +bool QFontEngineQPF1::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if(*nglyphs < len) { + *nglyphs = len; + return false; + } + *nglyphs = 0; + + bool mirrored = flags & QTextEngine::RightToLeft; + for(int i = 0; i < len; i++) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[*nglyphs] = uc < 0x10000 ? uc : 0; + ++*nglyphs; + } + + glyphs->numGlyphs = *nglyphs; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineQPF1::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for(int i = 0; i < glyphs->numGlyphs; i++) { + QPFGlyph *glyph = d->tree->get(glyphs->glyphs[i]); + + glyphs->advances_x[i] = glyph ? glyph->metrics->advance : 0; + glyphs->advances_y[i] = 0; + + if (!glyph) + glyphs->glyphs[i] = 0; + } +} + +void QFontEngineQPF1::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast<QRasterPaintEngine*>(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + int depth = (d->fm.flags & FM_SMOOTH) ? 8 : 1; + for(int i = 0; i < glyphs.size(); i++) { + const QPFGlyph *glyph = d->tree->get(glyphs[i]); + if (!glyph) + continue; + + int bpl = glyph->metrics->linestep; + + if(glyph->data) + paintEngine->alphaPenBlt(glyph->data, bpl, depth, + qRound(positions[i].x) + glyph->metrics->bearingx, + qRound(positions[i].y) - glyph->metrics->bearingy, + glyph->metrics->width,glyph->metrics->height); + } +} + + +QImage QFontEngineQPF1::alphaMapForGlyph(glyph_t g) +{ + const QPFGlyph *glyph = d->tree->get(g); + if (!glyph) + return QImage(); + + int mono = !(d->fm.flags & FM_SMOOTH); + + const uchar *bits = glyph->data;//((const uchar *) glyph); + + QImage image; + if (mono) { + image = QImage((glyph->metrics->width+7)&~7, glyph->metrics->height, QImage::Format_Mono); + image.setColor(0, qRgba(0, 0, 0, 0)); + image.setColor(1, qRgba(0, 0, 0, 255)); + } else { + image = QImage(glyph->metrics->width, glyph->metrics->height, QImage::Format_Indexed8); + for (int j=0; j<256; ++j) + image.setColor(j, qRgba(0, 0, 0, j)); + } + for (int i=0; i<glyph->metrics->height; ++i) { + memcpy(image.scanLine(i), bits, glyph->metrics->linestep); + bits += glyph->metrics->linestep; + } + return image; +} + + + +void QFontEngineQPF1::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPF1::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), ascent()+descent()+1, w, 0); +} + +glyph_metrics_t QFontEngineQPF1::boundingBox(glyph_t glyph) +{ + const QPFGlyph *g = d->tree->get(glyph); + if (!g) + return glyph_metrics_t(); + Q_ASSERT(g); + return glyph_metrics_t(g->metrics->bearingx, -g->metrics->bearingy, + g->metrics->width, g->metrics->height, + g->metrics->advance, 0); +} + +QFixed QFontEngineQPF1::ascent() const +{ + return d->fm.ascent; +} + +QFixed QFontEngineQPF1::descent() const +{ + return d->fm.descent; +} + +QFixed QFontEngineQPF1::leading() const +{ + return d->fm.leading; +} + +qreal QFontEngineQPF1::maxCharWidth() const +{ + return d->fm.maxwidth; +} +/* +const char *QFontEngineQPF1::name() const +{ + return "qt"; +} +*/ +bool QFontEngineQPF1::canRender(const QChar *str, int len) +{ + for(int i = 0; i < len; i++) + if (!d->tree->inFont(str[i].unicode())) + return false; + return true; +} + +QFontEngine::Type QFontEngineQPF1::type() const +{ + return QPF1; +} + +qreal QFontEngineQPF1::minLeftBearing() const +{ + return d->fm.leftbearing; +} + +qreal QFontEngineQPF1::minRightBearing() const +{ + return d->fm.rightbearing; +} + +QFixed QFontEngineQPF1::underlinePosition() const +{ + return d->fm.underlinepos; +} + +QFixed QFontEngineQPF1::lineThickness() const +{ + return d->fm.underlinewidth; +} + +#endif //QT_NO_QWS_QPF + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_s60.cpp b/src/gui/text/qfontengine_s60.cpp new file mode 100644 index 0000000000..e9b54e350f --- /dev/null +++ b/src/gui/text/qfontengine_s60.cpp @@ -0,0 +1,569 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_s60_p.h" +#include "qtextengine_p.h" +#include "qendian.h" +#include "qglobal.h" +#include <private/qapplication_p.h> +#include "qimage.h" +#include <private/qt_s60_p.h> +#include <private/qpixmap_s60_p.h> + +#include <e32base.h> +#include <e32std.h> +#include <eikenv.h> +#include <gdi.h> +#if defined(Q_SYMBIAN_HAS_GLYPHOUTLINE_API) +#include <graphics/gdi/gdiplatapi.h> +#endif // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +// Replication of TGetFontTableParam & friends. +// There is unfortunately no compile time flag like SYMBIAN_FONT_TABLE_API +// that would help us to only replicate these things for Symbian versions +// that do not yet have the font table Api. Symbian's public SDK does +// generally not define any usable macros. +class QSymbianTGetFontTableParam +{ +public: + TUint32 iTag; + TAny *iContent; + TInt iLength; +}; +const TUid QSymbianKFontGetFontTable = {0x102872C1}; +const TUid QSymbianKFontReleaseFontTable = {0x2002AC24}; + +QT_BEGIN_NAMESPACE + +QSymbianTypeFaceExtras::QSymbianTypeFaceExtras(CFont* cFont, COpenFont *openFont) + : m_cFont(cFont) + , m_symbolCMap(false) + , m_openFont(openFont) +{ + if (!symbianFontTableApiAvailable()) { + TAny *trueTypeExtension = NULL; + m_openFont->ExtendedInterface(KUidOpenFontTrueTypeExtension, trueTypeExtension); + m_trueTypeExtension = static_cast<MOpenFontTrueTypeExtension*>(trueTypeExtension); + Q_ASSERT(m_trueTypeExtension); + } +} + +QSymbianTypeFaceExtras::~QSymbianTypeFaceExtras() +{ + if (symbianFontTableApiAvailable()) + S60->screenDevice()->ReleaseFont(m_cFont); +} + +QByteArray QSymbianTypeFaceExtras::getSfntTable(uint tag) const +{ + if (symbianFontTableApiAvailable()) { + QSymbianTGetFontTableParam fontTableParams = { tag, 0, 0 }; + if (m_cFont->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + const char* const fontTableContent = + static_cast<const char *>(fontTableParams.iContent); + const QByteArray fontTable(fontTableContent, fontTableParams.iLength); + m_cFont->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + return fontTable; + } + return QByteArray(); + } else { + Q_ASSERT(m_trueTypeExtension->HasTrueTypeTable(tag)); + TInt error = KErrNone; + TInt tableByteLength = 0; + TAny *table = m_trueTypeExtension->GetTrueTypeTable(error, tag, &tableByteLength); + Q_CHECK_PTR(table); + const QByteArray result(static_cast<const char*>(table), tableByteLength); + m_trueTypeExtension->ReleaseTrueTypeTable(table); + return result; + } +} + +bool QSymbianTypeFaceExtras::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + bool result = true; + if (symbianFontTableApiAvailable()) { + QSymbianTGetFontTableParam fontTableParams = { tag, 0, 0 }; + if (m_cFont->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + if (*length > 0 && *length < fontTableParams.iLength) { + result = false; // Caller did not allocate enough memory + } else { + *length = fontTableParams.iLength; + if (buffer) + memcpy(buffer, fontTableParams.iContent, fontTableParams.iLength); + } + m_cFont->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + } else { + result = false; + } + } else { + if (!m_trueTypeExtension->HasTrueTypeTable(tag)) + return false; + + TInt error = KErrNone; + TInt tableByteLength; + TAny *table = m_trueTypeExtension->GetTrueTypeTable(error, tag, &tableByteLength); + Q_CHECK_PTR(table); + + if (error != KErrNone) { + return false; + } else if (*length > 0 && *length < tableByteLength) { + result = false; // Caller did not allocate enough memory + } else { + *length = tableByteLength; + if (buffer) + memcpy(buffer, table, tableByteLength); + } + + m_trueTypeExtension->ReleaseTrueTypeTable(table); + } + return result; +} + +const uchar *QSymbianTypeFaceExtras::cmap() const +{ + if (m_cmapTable.isNull()) { + const QByteArray cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + int size = 0; + const uchar *cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *> + (cmapTable.constData()), cmapTable.size(), &m_symbolCMap, &size); + m_cmapTable = QByteArray(reinterpret_cast<const char *>(cmap), size); + } + return reinterpret_cast<const uchar *>(m_cmapTable.constData()); +} + +bool QSymbianTypeFaceExtras::isSymbolCMap() const +{ + return m_symbolCMap; +} + +CFont *QSymbianTypeFaceExtras::fontOwner() const +{ + return m_cFont; +} + +QFixed QSymbianTypeFaceExtras::unitsPerEm() const +{ + if (m_unitsPerEm.value() != 0) + return m_unitsPerEm; + const QByteArray head = getSfntTable(MAKE_TAG('h', 'e', 'a', 'd')); + const int unitsPerEmOffset = 18; + if (head.size() > unitsPerEmOffset + sizeof(quint16)) { + const uchar* tableData = reinterpret_cast<const uchar*>(head.constData()); + const uchar* unitsPerEm = tableData + unitsPerEmOffset; + m_unitsPerEm = qFromBigEndian<quint16>(unitsPerEm); + } else { + // Bitmap font? Corrupt font? + // We return -1 and let the QFontEngineS60 return the pixel size. + m_unitsPerEm = -1; + } + return m_unitsPerEm; +} + +bool QSymbianTypeFaceExtras::symbianFontTableApiAvailable() +{ + enum FontTableApiAvailability { + Unknown, + Available, + Unavailable + }; + static FontTableApiAvailability availability = + QSysInfo::symbianVersion() < QSysInfo::SV_SF_3 ? + Unavailable : Unknown; + if (availability == Unknown) { + // Actually, we should ask CFeatureDiscovery::IsFeatureSupportedL() + // with FfFontTable here. But since at the time of writing, the + // FfFontTable flag check either gave false positives or false + // negatives. Here comes an implicit check via CFont::ExtendedFunction. + QSymbianTGetFontTableParam fontTableParams = { + MAKE_TAG('O', 'S', '/', '2'), 0, 0 }; + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + CFont *font; + const TInt getFontErr = S60->screenDevice()->GetNearestFontInTwips(font, TFontSpec()); + Q_ASSERT(getFontErr == KErrNone); + if (font->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + font->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + availability = Available; + } else { + availability = Unavailable; + } + S60->screenDevice()->ReleaseFont(font); + lock.relock(); + } + return availability == Available; +} + +// duplicated from qfontengine_xyz.cpp +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +extern QString qt_symbian_fontNameWithAppFontMarker(const QString &fontName); // qfontdatabase_s60.cpp + +CFont *QFontEngineS60::fontWithSize(qreal size) const +{ + CFont *result = 0; + const QString family = qt_symbian_fontNameWithAppFontMarker(QFontEngine::fontDef.family); + TFontSpec fontSpec(qt_QString2TPtrC(family), TInt(size)); + fontSpec.iFontStyle.SetBitmapType(EAntiAliasedGlyphBitmap); + fontSpec.iFontStyle.SetPosture(QFontEngine::fontDef.style == QFont::StyleNormal?EPostureUpright:EPostureItalic); + fontSpec.iFontStyle.SetStrokeWeight(QFontEngine::fontDef.weight > QFont::Normal?EStrokeWeightBold:EStrokeWeightNormal); + const TInt errorCode = S60->screenDevice()->GetNearestFontToDesignHeightInPixels(result, fontSpec); + Q_ASSERT(result && (errorCode == 0)); + return result; +} + +void QFontEngineS60::setFontScale(qreal scale) +{ + if (qFuzzyCompare(scale, qreal(1))) { + if (!m_originalFont) + m_originalFont = fontWithSize(m_originalFontSizeInPixels); + m_activeFont = m_originalFont; + } else { + const qreal scaledFontSizeInPixels = m_originalFontSizeInPixels * scale; + if (!m_scaledFont || + (TInt(scaledFontSizeInPixels) != TInt(m_scaledFontSizeInPixels))) { + releaseFont(m_scaledFont); + m_scaledFontSizeInPixels = scaledFontSizeInPixels; + m_scaledFont = fontWithSize(m_scaledFontSizeInPixels); + } + m_activeFont = m_scaledFont; + } +} + +void QFontEngineS60::releaseFont(CFont *&font) +{ + if (font) { + S60->screenDevice()->ReleaseFont(font); + font = 0; + } +} + +QFontEngineS60::QFontEngineS60(const QFontDef &request, const QSymbianTypeFaceExtras *extras) + : m_extras(extras) + , m_originalFont(0) + , m_originalFontSizeInPixels((request.pixelSize >= 0)? + request.pixelSize:pointsToPixels(request.pointSize)) + , m_scaledFont(0) + , m_scaledFontSizeInPixels(0) + , m_activeFont(0) +{ + QFontEngine::fontDef = request; + setFontScale(1.0); + cache_cost = sizeof(QFontEngineS60); +} + +QFontEngineS60::~QFontEngineS60() +{ + releaseFont(m_originalFont); + releaseFont(m_scaledFont); +} + +QFixed QFontEngineS60::emSquareSize() const +{ + const QFixed unitsPerEm = m_extras->unitsPerEm(); + return unitsPerEm.toInt() == -1 ? + QFixed::fromReal(m_originalFontSizeInPixels) : unitsPerEm; +} + +bool QFontEngineS60::stringToCMap(const QChar *characters, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + HB_Glyph *g = glyphs->glyphs; + const unsigned char* cmap = m_extras->cmap(); + const bool isRtl = (flags & QTextEngine::RightToLeft); + for (int i = 0; i < len; ++i) { + const unsigned int uc = getChar(characters, i, len); + *g++ = QFontEngine::getTrueTypeGlyphIndex(cmap, + (isRtl && !m_extras->isSymbolCMap()) ? QChar::mirroredChar(uc) : uc); + } + + glyphs->numGlyphs = g - glyphs->glyphs; + *nglyphs = glyphs->numGlyphs; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineS60::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_UNUSED(flags); + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + for (int i = 0; i < glyphs->numGlyphs; i++) { + getCharacterData(glyphs->glyphs[i], metrics, glyphBitmapBytes, glyphBitmapSize); + glyphs->advances_x[i] = metrics.HorizAdvance(); + glyphs->advances_y[i] = 0; + } +} + +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API +static bool parseGlyphPathData(const char *dataStr, const char *dataEnd, QPainterPath &path, + qreal fontPixelSize, const QPointF &offset, bool hinted); +#endif //Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +void QFontEngineS60::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, + int nglyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API + Q_UNUSED(flags) + RGlyphOutlineIterator iterator; + const TInt error = iterator.Open(*m_activeFont, glyphs, nglyphs); + if (KErrNone != error) + return; + const qreal fontSizeInPixels = qreal(m_activeFont->HeightInPixels()); + int count = 0; + do { + const TUint8* outlineUint8 = iterator.Outline(); + const char* const outlineChar = reinterpret_cast<const char*>(outlineUint8); + const char* const outlineEnd = outlineChar + iterator.OutlineLength(); + parseGlyphPathData(outlineChar, outlineEnd, *path, fontSizeInPixels, + positions[count++].toPointF(), false); + } while(KErrNone == iterator.Next() && count <= nglyphs); + iterator.Close(); +#else // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + QFontEngine::addGlyphsToPath(glyphs, positions, nglyphs, path, flags); +#endif //Q_SYMBIAN_HAS_GLYPHOUTLINE_API +} + +QImage QFontEngineS60::alphaMapForGlyph(glyph_t glyph) +{ + // Note: On some Symbian versions (apparently <= Symbian^1), this + // function will return gray values 0x00, 0x10 ... 0xe0, 0xf0 due + // to a bug. The glyphs are nowhere perfectly opaque. + // This has been fixed for Symbian^3. + + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + getCharacterData(glyph, metrics, glyphBitmapBytes, glyphBitmapSize); + QImage result(glyphBitmapBytes, glyphBitmapSize.iWidth, glyphBitmapSize.iHeight, glyphBitmapSize.iWidth, QImage::Format_Indexed8); + result.setColorTable(grayPalette()); + return result; +} + +glyph_metrics_t QFontEngineS60::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), ascent()+descent()+1, w, 0); +} + +glyph_metrics_t QFontEngineS60::boundingBox_const(glyph_t glyph) const +{ + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + getCharacterData(glyph, metrics, glyphBitmapBytes, glyphBitmapSize); + const glyph_metrics_t result( + metrics.HorizBearingX(), + -metrics.HorizBearingY(), + metrics.Width(), + metrics.Height(), + metrics.HorizAdvance(), + 0 + ); + return result; +} + +glyph_metrics_t QFontEngineS60::boundingBox(glyph_t glyph) +{ + return boundingBox_const(glyph); +} + +QFixed QFontEngineS60::ascent() const +{ + // Workaround for QTBUG-8013 + // Stroke based fonts may return an incorrect FontMaxAscent of 0. + const QFixed ascent = m_originalFont->FontMaxAscent(); + return (ascent > 0) ? ascent : QFixed::fromReal(m_originalFontSizeInPixels) - descent(); +} + +QFixed QFontEngineS60::descent() const +{ + return m_originalFont->FontMaxDescent(); +} + +QFixed QFontEngineS60::leading() const +{ + return 0; +} + +qreal QFontEngineS60::maxCharWidth() const +{ + return m_originalFont->MaxCharWidthInPixels(); +} + +const char *QFontEngineS60::name() const +{ + return "QFontEngineS60"; +} + +bool QFontEngineS60::canRender(const QChar *string, int len) +{ + const unsigned char *cmap = m_extras->cmap(); + for (int i = 0; i < len; ++i) { + const unsigned int uc = getChar(string, i, len); + if (QFontEngine::getTrueTypeGlyphIndex(cmap, uc) == 0) + return false; + } + return true; +} + +QByteArray QFontEngineS60::getSfntTable(uint tag) const +{ + return m_extras->getSfntTable(tag); +} + +bool QFontEngineS60::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + return m_extras->getSfntTableData(tag, buffer, length); +} + +QFontEngine::Type QFontEngineS60::type() const +{ + return QFontEngine::S60FontEngine; +} + +void QFontEngineS60::getCharacterData(glyph_t glyph, TOpenFontCharMetrics& metrics, const TUint8*& bitmap, TSize& bitmapSize) const +{ + // Setting the most significant bit tells GetCharacterData + // that 'code' is a Glyph ID, rather than a UTF-16 value + const TUint specialCode = (TUint)glyph | 0x80000000; + + const CFont::TCharacterDataAvailability availability = + m_activeFont->GetCharacterData(specialCode, metrics, bitmap, bitmapSize); + const glyph_t fallbackGlyph = '?'; + if (availability != CFont::EAllCharacterData) { + const CFont::TCharacterDataAvailability fallbackAvailability = + m_activeFont->GetCharacterData(fallbackGlyph, metrics, bitmap, bitmapSize); + Q_ASSERT(fallbackAvailability == CFont::EAllCharacterData); + } +} + +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API +static inline void skipSpacesAndComma(const char* &str, const char* const strEnd) +{ + while (str <= strEnd && (*str == ' ' || *str == ',')) + ++str; +} + +static bool parseGlyphPathData(const char *svgPath, const char *svgPathEnd, QPainterPath &path, + qreal fontPixelSize, const QPointF &offset, bool hinted) +{ + Q_UNUSED(hinted) + QPointF p1, p2, firstSubPathPoint; + qreal *elementValues[] = + {&p1.rx(), &p1.ry(), &p2.rx(), &p2.ry()}; + const int unitsPerEm = 2048; // See: http://en.wikipedia.org/wiki/Em_%28typography%29 + const qreal resizeFactor = fontPixelSize / unitsPerEm; + + while (svgPath < svgPathEnd) { + skipSpacesAndComma(svgPath, svgPathEnd); + const char pathElem = *svgPath++; + skipSpacesAndComma(svgPath, svgPathEnd); + + if (pathElem != 'Z') { + char *endStr = 0; + int elementValuesCount = 0; + for (int i = 0; i < 4; ++i) { // 4 = size of elementValues[] + qreal coordinateValue = strtod(svgPath, &endStr); + if (svgPath == endStr) + break; + if (i % 2) // Flip vertically + coordinateValue = -coordinateValue; + *elementValues[i] = coordinateValue * resizeFactor; + elementValuesCount++; + svgPath = endStr; + skipSpacesAndComma(svgPath, svgPathEnd); + } + p1 += offset; + if (elementValuesCount == 2) + p2 = firstSubPathPoint; + else + p2 += offset; + } + + switch (pathElem) { + case 'M': + firstSubPathPoint = p1; + path.moveTo(p1); + break; + case 'Z': + path.closeSubpath(); + break; + case 'L': + path.lineTo(p1); + break; + case 'Q': + path.quadTo(p1, p2); + break; + default: + return false; + } + } + return true; +} +#endif // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_s60_p.h b/src/gui/text/qfontengine_s60_p.h new file mode 100644 index 0000000000..c0eeef5264 --- /dev/null +++ b/src/gui/text/qfontengine_s60_p.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_S60_P_H +#define QFONTENGINE_S60_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 "qconfig.h" +#include <private/qfontengine_p.h> +#include "qsize.h" +#include <openfont.h> + +// The glyph outline code is intentionally disabled. It will be reactivated as +// soon as the glyph outline API is backported from Symbian(^4) to Symbian(^3). +#if 0 +#define Q_SYMBIAN_HAS_GLYPHOUTLINE_API +#endif + +class CFont; + +QT_BEGIN_NAMESPACE + +// ..gives us access to truetype tables +class QSymbianTypeFaceExtras +{ +public: + QSymbianTypeFaceExtras(CFont* cFont, COpenFont *openFont = 0); + ~QSymbianTypeFaceExtras(); + + QByteArray getSfntTable(uint tag) const; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + const uchar *cmap() const; + CFont *fontOwner() const; + bool isSymbolCMap() const; + QFixed unitsPerEm() const; + static bool symbianFontTableApiAvailable(); + +private: + CFont* m_cFont; + mutable bool m_symbolCMap; + mutable QByteArray m_cmapTable; + mutable QFixed m_unitsPerEm; + + // m_openFont and m_openFont are used if Symbian does not provide + // the Font Table API + COpenFont *m_openFont; + mutable MOpenFontTrueTypeExtension *m_trueTypeExtension; +}; + +class QFontEngineS60 : public QFontEngine +{ +public: + QFontEngineS60(const QFontDef &fontDef, const QSymbianTypeFaceExtras *extras); + ~QFontEngineS60(); + + QFixed emSquareSize() const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const; + + void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + QImage alphaMapForGlyph(glyph_t glyph); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox_const(glyph_t glyph) const; // Const correctnes quirk. + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const { return 0; } + qreal minRightBearing() const { return 0; } + + QByteArray getSfntTable(uint tag) const; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + static qreal pixelsToPoints(qreal pixels, Qt::Orientation orientation = Qt::Horizontal); + static qreal pointsToPixels(qreal points, Qt::Orientation orientation = Qt::Horizontal); + + const char *name() const; + + bool canRender(const QChar *string, int len); + + Type type() const; + + void getCharacterData(glyph_t glyph, TOpenFontCharMetrics& metrics, const TUint8*& bitmap, TSize& bitmapSize) const; + void setFontScale(qreal scale); + +private: + friend class QFontPrivate; + friend class QSymbianVGFontGlyphCache; + + QFixed glyphAdvance(HB_Glyph glyph) const; + CFont *fontWithSize(qreal size) const; + static void releaseFont(CFont *&font); + + const QSymbianTypeFaceExtras *m_extras; + CFont* m_originalFont; + const qreal m_originalFontSizeInPixels; + CFont* m_scaledFont; + qreal m_scaledFontSizeInPixels; + CFont* m_activeFont; +}; + +class QFontEngineMultiS60 : public QFontEngineMulti +{ +public: + QFontEngineMultiS60(QFontEngine *first, int script, const QStringList &fallbackFamilies); + void loadEngine(int at); + + int m_script; + QStringList m_fallbackFamilies; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_S60_P_H diff --git a/src/gui/text/qfontengine_win.cpp b/src/gui/text/qfontengine_win.cpp new file mode 100644 index 0000000000..82d9da0be9 --- /dev/null +++ b/src/gui/text/qfontengine_win.cpp @@ -0,0 +1,1322 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#if _WIN32_WINNT < 0x0500 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#include "qfontengine_p.h" +#include "qtextengine_p.h" +#include <qglobal.h> +#include "qt_windows.h" +#include <private/qapplication_p.h> + +#include <private/qsystemlibrary_p.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <limits.h> + +#include <qendian.h> +#include <qmath.h> +#include <qthreadstorage.h> + +#include <private/qunicodetables_p.h> +#include <qbitmap.h> + +#include <private/qpainter_p.h> +#include "qpaintengine.h" +#include "qvarlengtharray.h" +#include <private/qpaintengine_raster_p.h> +#include <private/qnativeimage_p.h> + +#if defined(Q_WS_WINCE) +#include "qguifunctions_wince.h" +#endif + +//### mingw needed define +#ifndef TT_PRIM_CSPLINE +#define TT_PRIM_CSPLINE 3 +#endif + +#ifdef MAKE_TAG +#undef MAKE_TAG +#endif +// GetFontData expects the tags in little endian ;( +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch4)) << 24) | \ + (((quint32)(ch3)) << 16) | \ + (((quint32)(ch2)) << 8) | \ + ((quint32)(ch1)) \ + ) + +// common DC for all fonts + +QT_BEGIN_NAMESPACE + +class QtHDC +{ + HDC _hdc; +public: + QtHDC() + { + HDC displayDC = GetDC(0); + _hdc = CreateCompatibleDC(displayDC); + ReleaseDC(0, displayDC); + } + ~QtHDC() + { + if (_hdc) + DeleteDC(_hdc); + } + HDC hdc() const + { + return _hdc; + } +}; + +#ifndef QT_NO_THREAD +Q_GLOBAL_STATIC(QThreadStorage<QtHDC *>, local_shared_dc) +HDC shared_dc() +{ + QtHDC *&hdc = local_shared_dc()->localData(); + if (!hdc) + hdc = new QtHDC; + return hdc->hdc(); +} +#else +HDC shared_dc() +{ + return 0; +} +#endif + +#ifndef Q_WS_WINCE +typedef BOOL (WINAPI *PtrGetCharWidthI)(HDC, UINT, UINT, LPWORD, LPINT); +static PtrGetCharWidthI ptrGetCharWidthI = 0; +static bool resolvedGetCharWidthI = false; + +static void resolveGetCharWidthI() +{ + if (resolvedGetCharWidthI) + return; + resolvedGetCharWidthI = true; + ptrGetCharWidthI = (PtrGetCharWidthI)QSystemLibrary::resolve(QLatin1String("gdi32"), "GetCharWidthI"); +} +#endif // !defined(Q_WS_WINCE) + +// defined in qtextengine_win.cpp +typedef void *SCRIPT_CACHE; +typedef HRESULT (WINAPI *fScriptFreeCache)(SCRIPT_CACHE *); +extern fScriptFreeCache ScriptFreeCache; + +static inline quint32 getUInt(unsigned char *p) +{ + quint32 val; + val = *p++ << 24; + val |= *p++ << 16; + val |= *p++ << 8; + val |= *p; + + return val; +} + +static inline quint16 getUShort(unsigned char *p) +{ + quint16 val; + val = *p++ << 8; + val |= *p; + + return val; +} + +// general font engine + +QFixed QFontEngineWin::lineThickness() const +{ + if(lineWidth > 0) + return lineWidth; + + return QFontEngine::lineThickness(); +} + +static OUTLINETEXTMETRIC *getOutlineTextMetric(HDC hdc) +{ + int size; + size = GetOutlineTextMetrics(hdc, 0, 0); + OUTLINETEXTMETRIC *otm = (OUTLINETEXTMETRIC *)malloc(size); + GetOutlineTextMetrics(hdc, size, otm); + return otm; +} + +void QFontEngineWin::getCMap() +{ + ttf = (bool)(tm.tmPitchAndFamily & TMPF_TRUETYPE); + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + bool symb = false; + if (ttf) { + cmapTable = getSfntTable(qbswap<quint32>(MAKE_TAG('c', 'm', 'a', 'p'))); + int size = 0; + cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()), + cmapTable.size(), &symb, &size); + } + if (!cmap) { + ttf = false; + symb = false; + } + symbol = symb; + designToDevice = 1; + _faceId.index = 0; + if(cmap) { + OUTLINETEXTMETRIC *otm = getOutlineTextMetric(hdc); + designToDevice = QFixed((int)otm->otmEMSquare)/int(otm->otmTextMetrics.tmHeight); + unitsPerEm = otm->otmEMSquare; + x_height = (int)otm->otmsXHeight; + loadKerningPairs(designToDevice); + _faceId.filename = QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpFullName)).toLatin1(); + lineWidth = otm->otmsUnderscoreSize; + fsType = otm->otmfsType; + free(otm); + } else { + unitsPerEm = tm.tmHeight; + } +} + + +inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +int QFontEngineWin::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout *glyphs, bool mirrored) const +{ + int i = 0; + int glyph_pos = 0; + if (mirrored) { +#if defined(Q_WS_WINCE) + { +#else + if (symbol) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if (!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else if (ttf) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, QChar::mirroredChar(uc)); + } + } else { +#endif + wchar_t first = tm.tmFirstChar; + wchar_t last = tm.tmLastChar; + + for (; i < numChars; ++i, ++glyph_pos) { + uint ucs = QChar::mirroredChar(getChar(str, i, numChars)); + if ( +#ifdef Q_WS_WINCE + tm.tmFirstChar > 60000 || // see line 375 +#endif + ucs >= first && ucs <= last) + glyphs->glyphs[glyph_pos] = ucs; + else + glyphs->glyphs[glyph_pos] = 0; + } + } + } else { +#if defined(Q_WS_WINCE) + { +#else + if (symbol) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else if (ttf) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + } + } else { +#endif + wchar_t first = tm.tmFirstChar; + wchar_t last = tm.tmLastChar; + + for (; i < numChars; ++i, ++glyph_pos) { + uint uc = getChar(str, i, numChars); + if ( +#ifdef Q_WS_WINCE + tm.tmFirstChar > 60000 || // see comment in QFontEngineWin +#endif + uc >= first && uc <= last) + glyphs->glyphs[glyph_pos] = uc; + else + glyphs->glyphs[glyph_pos] = 0; + } + } + } + glyphs->numGlyphs = glyph_pos; + return glyph_pos; +} + + +QFontEngineWin::QFontEngineWin(const QString &name, HFONT _hfont, bool stockFont, LOGFONT lf) +{ + //qDebug("regular windows font engine created: font='%s', size=%d", name, lf.lfHeight); + + _name = name; + + cmap = 0; + hfont = _hfont; + logfont = lf; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + this->stockFont = stockFont; + fontDef.pixelSize = -lf.lfHeight; + + lbearing = SHRT_MIN; + rbearing = SHRT_MIN; + synthesized_flags = -1; + lineWidth = -1; + x_height = -1; + + BOOL res = GetTextMetrics(hdc, &tm); + fontDef.fixedPitch = !(tm.tmPitchAndFamily & TMPF_FIXED_PITCH); + if (!res) { + qErrnoWarning("QFontEngineWin: GetTextMetrics failed"); + ZeroMemory(&tm, sizeof(TEXTMETRIC)); + } + + cache_cost = tm.tmHeight * tm.tmAveCharWidth * 2000; + getCMap(); + + widthCache = 0; + widthCacheSize = 0; + designAdvances = 0; + designAdvancesSize = 0; + +#ifndef Q_WS_WINCE + if (!resolvedGetCharWidthI) + resolveGetCharWidthI(); +#endif +} + +QFontEngineWin::~QFontEngineWin() +{ + if (designAdvances) + free(designAdvances); + + if (widthCache) + free(widthCache); + + // make sure we aren't by accident still selected + SelectObject(shared_dc(), (HFONT)GetStockObject(SYSTEM_FONT)); + + if (!stockFont) { + if (!DeleteObject(hfont)) + qErrnoWarning("QFontEngineWin: failed to delete non-stock font..."); + } +} + +HGDIOBJ QFontEngineWin::selectDesignFont() const +{ + LOGFONT f = logfont; + f.lfHeight = unitsPerEm; + HFONT designFont = CreateFontIndirect(&f); + return SelectObject(shared_dc(), designFont); +} + +bool QFontEngineWin::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + *nglyphs = getGlyphIndexes(str, len, glyphs, flags & QTextEngine::RightToLeft); + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + return true; +} + +inline void calculateTTFGlyphWidth(HDC hdc, UINT glyph, int &width) +{ +#if defined(Q_WS_WINCE) + GetCharWidth32(hdc, glyph, glyph, &width); +#else + if (ptrGetCharWidthI) + ptrGetCharWidthI(hdc, glyph, 1, 0, &width); +#endif +} + +void QFontEngineWin::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + HGDIOBJ oldFont = 0; + HDC hdc = shared_dc(); + if (ttf && (flags & QTextEngine::DesignMetrics)) { + for(int i = 0; i < glyphs->numGlyphs; i++) { + unsigned int glyph = glyphs->glyphs[i]; + if(int(glyph) >= designAdvancesSize) { + int newSize = (glyph + 256) >> 8 << 8; + designAdvances = q_check_ptr((QFixed *)realloc(designAdvances, + newSize*sizeof(QFixed))); + for(int i = designAdvancesSize; i < newSize; ++i) + designAdvances[i] = -1000000; + designAdvancesSize = newSize; + } + if (designAdvances[glyph] < -999999) { + if (!oldFont) + oldFont = selectDesignFont(); + + int width = 0; + calculateTTFGlyphWidth(hdc, glyph, width); + designAdvances[glyph] = QFixed(width) / designToDevice; + } + glyphs->advances_x[i] = designAdvances[glyph]; + glyphs->advances_y[i] = 0; + } + if(oldFont) + DeleteObject(SelectObject(hdc, oldFont)); + } else { + for(int i = 0; i < glyphs->numGlyphs; i++) { + unsigned int glyph = glyphs->glyphs[i]; + + glyphs->advances_y[i] = 0; + + if (glyph >= widthCacheSize) { + int newSize = (glyph + 256) >> 8 << 8; + widthCache = q_check_ptr((unsigned char *)realloc(widthCache, + newSize*sizeof(QFixed))); + memset(widthCache + widthCacheSize, 0, newSize - widthCacheSize); + widthCacheSize = newSize; + } + glyphs->advances_x[i] = widthCache[glyph]; + // font-width cache failed + if (glyphs->advances_x[i] == 0) { + int width = 0; + if (!oldFont) + oldFont = SelectObject(hdc, hfont); + + if (!ttf) { + QChar ch[2] = { ushort(glyph), 0 }; + int chrLen = 1; + if (glyph > 0xffff) { + ch[0] = QChar::highSurrogate(glyph); + ch[1] = QChar::lowSurrogate(glyph); + ++chrLen; + } + SIZE size = {0, 0}; + GetTextExtentPoint32(hdc, (wchar_t *)ch, chrLen, &size); + width = size.cx; + } else { + calculateTTFGlyphWidth(hdc, glyph, width); + } + glyphs->advances_x[i] = width; + // if glyph's within cache range, store it for later + if (width > 0 && width < 0x100) + widthCache[glyph] = width; + } + } + + if (oldFont) + SelectObject(hdc, oldFont); + } +} + +glyph_metrics_t QFontEngineWin::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -tm.tmAscent, w - lastRightBearing(glyphs), tm.tmHeight, w, 0); +} + +#ifndef Q_WS_WINCE +bool QFontEngineWin::getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const +{ + Q_ASSERT(metrics != 0); + + HDC hdc = shared_dc(); + + GLYPHMETRICS gm; + DWORD res = 0; + MAT2 mat; + mat.eM11.value = mat.eM22.value = 1; + mat.eM11.fract = mat.eM22.fract = 0; + mat.eM21.value = mat.eM12.value = 0; + mat.eM21.fract = mat.eM12.fract = 0; + + if (t.type() > QTransform::TxTranslate) { + // We need to set the transform using the HDC's world + // matrix rather than using the MAT2 above, because the + // results provided when transforming via MAT2 does not + // match the glyphs that are drawn using a WorldTransform + XFORM xform; + xform.eM11 = t.m11(); + xform.eM12 = t.m12(); + xform.eM21 = t.m21(); + xform.eM22 = t.m22(); + xform.eDx = 0; + xform.eDy = 0; + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + } + + uint format = GGO_METRICS; + if (ttf) + format |= GGO_GLYPH_INDEX; + res = GetGlyphOutline(hdc, glyph, format, &gm, 0, 0, &mat); + + if (t.type() > QTransform::TxTranslate) { + XFORM xform; + xform.eM11 = xform.eM22 = 1; + xform.eM12 = xform.eM21 = xform.eDx = xform.eDy = 0; + SetWorldTransform(hdc, &xform); + SetGraphicsMode(hdc, GM_COMPATIBLE); + } + + if (res != GDI_ERROR) { + *metrics = glyph_metrics_t(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y, + (int)gm.gmBlackBoxX, (int)gm.gmBlackBoxY, gm.gmCellIncX, gm.gmCellIncY); + return true; + } else { + return false; + } +} +#endif + +glyph_metrics_t QFontEngineWin::boundingBox(glyph_t glyph, const QTransform &t) +{ +#ifndef Q_WS_WINCE + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + + glyph_metrics_t glyphMetrics; + bool success = getOutlineMetrics(glyph, t, &glyphMetrics); + + if (!ttf && !success) { + // Bitmap fonts + wchar_t ch = glyph; + ABCFLOAT abc; + GetCharABCWidthsFloat(hdc, ch, ch, &abc); + int width = qRound(abc.abcfB); + + return glyph_metrics_t(QFixed::fromReal(abc.abcfA), -tm.tmAscent, width, tm.tmHeight, width, 0).transformed(t); + } + + return glyphMetrics; +#else + HDC hdc = shared_dc(); + HGDIOBJ oldFont = SelectObject(hdc, hfont); + + ABC abc; + int width; + int advance; +#ifdef GWES_MGTT // true type fonts + if (GetCharABCWidths(hdc, glyph, glyph, &abc)) { + width = qAbs(abc.abcA) + abc.abcB + qAbs(abc.abcC); + advance = abc.abcA + abc.abcB + abc.abcC; + } + else +#endif +#if defined(GWES_MGRAST) || defined(GWES_MGRAST2) // raster fonts + if (GetCharWidth32(hdc, glyph, glyph, &width)) { + advance = width; + } + else +#endif + { // fallback + width = tm.tmMaxCharWidth; + advance = width; + } + + SelectObject(hdc, oldFont); + return glyph_metrics_t(0, -tm.tmAscent, width, tm.tmHeight, advance, 0).transformed(t); +#endif +} + +QFixed QFontEngineWin::ascent() const +{ + return tm.tmAscent; +} + +QFixed QFontEngineWin::descent() const +{ + // ### we substract 1 to even out the historical +1 in QFontMetrics's + // ### height=asc+desc+1 equation. Fix in Qt5. + return tm.tmDescent - 1; +} + +QFixed QFontEngineWin::leading() const +{ + return tm.tmExternalLeading; +} + + +QFixed QFontEngineWin::xHeight() const +{ + if(x_height >= 0) + return x_height; + return QFontEngine::xHeight(); +} + +QFixed QFontEngineWin::averageCharWidth() const +{ + return tm.tmAveCharWidth; +} + +qreal QFontEngineWin::maxCharWidth() const +{ + return tm.tmMaxCharWidth; +} + +enum { max_font_count = 256 }; +static const ushort char_table[] = { + 40, + 67, + 70, + 75, + 86, + 88, + 89, + 91, + 102, + 114, + 124, + 127, + 205, + 645, + 884, + 922, + 1070, + 12386, + 0 +}; + +static const int char_table_entries = sizeof(char_table)/sizeof(ushort); + +#ifndef Q_CC_MINGW +void QFontEngineWin::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + +#ifndef Q_WS_WINCE + if (ttf) +#endif + + { + ABC abcWidths; + GetCharABCWidthsI(hdc, glyph, 1, 0, &abcWidths); + if (leftBearing) + *leftBearing = abcWidths.abcA; + if (rightBearing) + *rightBearing = abcWidths.abcC; + } + +#ifndef Q_WS_WINCE + else { + QFontEngine::getGlyphBearings(glyph, leftBearing, rightBearing); + } +#endif +} +#endif // Q_CC_MINGW + +qreal QFontEngineWin::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) + minRightBearing(); // calculates both + + return lbearing; +} + +qreal QFontEngineWin::minRightBearing() const +{ +#ifdef Q_WS_WINCE + if (rbearing == SHRT_MIN) { + int ml = 0; + int mr = 0; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + if (ttf) { + ABC *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar; + if (n <= max_font_count) { + abc = new ABC[n+1]; + GetCharABCWidths(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABC[char_table_entries+1]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidths(hdc, char_table[i], char_table[i], abc+i); + n = char_table_entries; + } + ml = abc[0].abcA; + mr = abc[0].abcC; + for (int i = 1; i < n; i++) { + if (abc[i].abcA + abc[i].abcB + abc[i].abcC != 0) { + ml = qMin(ml,abc[i].abcA); + mr = qMin(mr,abc[i].abcC); + } + } + delete [] abc; + } + lbearing = ml; + rbearing = mr; + } + + return rbearing; +#else + if (rbearing == SHRT_MIN) { + int ml = 0; + int mr = 0; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + if (ttf) { + ABC *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar; + if (n <= max_font_count) { + abc = new ABC[n+1]; + GetCharABCWidths(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABC[char_table_entries+1]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidths(hdc, char_table[i], char_table[i], abc + i); + n = char_table_entries; + } + ml = abc[0].abcA; + mr = abc[0].abcC; + for (int i = 1; i < n; i++) { + if (abc[i].abcA + abc[i].abcB + abc[i].abcC != 0) { + ml = qMin(ml,abc[i].abcA); + mr = qMin(mr,abc[i].abcC); + } + } + delete [] abc; + } else { + ABCFLOAT *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar+1; + if (n <= max_font_count) { + abc = new ABCFLOAT[n]; + GetCharABCWidthsFloat(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABCFLOAT[char_table_entries]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidthsFloat(hdc, char_table[i], char_table[i], abc+i); + n = char_table_entries; + } + float fml = abc[0].abcfA; + float fmr = abc[0].abcfC; + for (int i=1; i<n; i++) { + if (abc[i].abcfA + abc[i].abcfB + abc[i].abcfC != 0) { + fml = qMin(fml,abc[i].abcfA); + fmr = qMin(fmr,abc[i].abcfC); + } + } + ml = int(fml - 0.9999); + mr = int(fmr - 0.9999); + delete [] abc; + } + lbearing = ml; + rbearing = mr; + } + + return rbearing; +#endif +} + + +const char *QFontEngineWin::name() const +{ + return 0; +} + +bool QFontEngineWin::canRender(const QChar *string, int len) +{ + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (getTrueTypeGlyphIndex(cmap, uc) == 0) { + if (uc < 0x100) { + if (getTrueTypeGlyphIndex(cmap, uc + 0xf000) == 0) + return false; + } else { + return false; + } + } + } + } else if (ttf) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (getTrueTypeGlyphIndex(cmap, uc) == 0) + return false; + } + } else { + while(len--) { + if (tm.tmFirstChar > string->unicode() || tm.tmLastChar < string->unicode()) + return false; + } + } + return true; +} + +QFontEngine::Type QFontEngineWin::type() const +{ + return QFontEngine::Win; +} + +static inline double qt_fixed_to_double(const FIXED &p) { + return ((p.value << 16) + p.fract) / 65536.0; +} + +static inline QPointF qt_to_qpointf(const POINTFX &pt, qreal scale) { + return QPointF(qt_fixed_to_double(pt.x) * scale, -qt_fixed_to_double(pt.y) * scale); +} + +#ifndef GGO_UNHINTED +#define GGO_UNHINTED 0x0100 +#endif + +static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc, + QPainterPath *path, bool ttf, glyph_metrics_t *metric = 0, qreal scale = 1) +{ +#if defined(Q_WS_WINCE) + Q_UNUSED(glyph); + Q_UNUSED(hdc); +#endif + MAT2 mat; + mat.eM11.value = mat.eM22.value = 1; + mat.eM11.fract = mat.eM22.fract = 0; + mat.eM21.value = mat.eM12.value = 0; + mat.eM21.fract = mat.eM12.fract = 0; + uint glyphFormat = GGO_NATIVE; + + if (ttf) + glyphFormat |= GGO_GLYPH_INDEX; + + GLYPHMETRICS gMetric; + memset(&gMetric, 0, sizeof(GLYPHMETRICS)); + int bufferSize = GDI_ERROR; +#if !defined(Q_WS_WINCE) + bufferSize = GetGlyphOutline(hdc, glyph, glyphFormat, &gMetric, 0, 0, &mat); +#endif + if ((DWORD)bufferSize == GDI_ERROR) { + return false; + } + + void *dataBuffer = new char[bufferSize]; + DWORD ret = GDI_ERROR; +#if !defined(Q_WS_WINCE) + ret = GetGlyphOutline(hdc, glyph, glyphFormat, &gMetric, bufferSize, dataBuffer, &mat); +#endif + if (ret == GDI_ERROR) { + delete [](char *)dataBuffer; + return false; + } + + if(metric) { + // #### obey scale + *metric = glyph_metrics_t(gMetric.gmptGlyphOrigin.x, -gMetric.gmptGlyphOrigin.y, + (int)gMetric.gmBlackBoxX, (int)gMetric.gmBlackBoxY, + gMetric.gmCellIncX, gMetric.gmCellIncY); + } + + int offset = 0; + int headerOffset = 0; + TTPOLYGONHEADER *ttph = 0; + + QPointF oset = position.toPointF(); + while (headerOffset < bufferSize) { + ttph = (TTPOLYGONHEADER*)((char *)dataBuffer + headerOffset); + + QPointF lastPoint(qt_to_qpointf(ttph->pfxStart, scale)); + path->moveTo(lastPoint + oset); + offset += sizeof(TTPOLYGONHEADER); + TTPOLYCURVE *curve; + while (offset<int(headerOffset + ttph->cb)) { + curve = (TTPOLYCURVE*)((char*)(dataBuffer) + offset); + switch (curve->wType) { + case TT_PRIM_LINE: { + for (int i=0; i<curve->cpfx; ++i) { + QPointF p = qt_to_qpointf(curve->apfx[i], scale) + oset; + path->lineTo(p); + } + break; + } + case TT_PRIM_QSPLINE: { + const QPainterPath::Element &elm = path->elementAt(path->elementCount()-1); + QPointF prev(elm.x, elm.y); + QPointF endPoint; + for (int i=0; i<curve->cpfx - 1; ++i) { + QPointF p1 = qt_to_qpointf(curve->apfx[i], scale) + oset; + QPointF p2 = qt_to_qpointf(curve->apfx[i+1], scale) + oset; + if (i < curve->cpfx - 2) { + endPoint = QPointF((p1.x() + p2.x()) / 2, (p1.y() + p2.y()) / 2); + } else { + endPoint = p2; + } + + path->quadTo(p1, endPoint); + prev = endPoint; + } + + break; + } + case TT_PRIM_CSPLINE: { + for (int i=0; i<curve->cpfx; ) { + QPointF p2 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + QPointF p3 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + QPointF p4 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + path->cubicTo(p2, p3, p4); + } + break; + } + default: + qWarning("QFontEngineWin::addOutlineToPath, unhandled switch case"); + } + offset += sizeof(TTPOLYCURVE) + (curve->cpfx-1) * sizeof(POINTFX); + } + path->closeSubpath(); + headerOffset += ttph->cb; + } + delete [] (char*)dataBuffer; + + return true; +} + +void QFontEngineWin::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + LOGFONT lf = logfont; + // The sign must be negative here to make sure we match against character height instead of + // hinted cell height. This ensures that we get linear matching, and we need this for + // paths since we later on apply a scaling transform to the glyph outline to get the + // font at the correct pixel size. + lf.lfHeight = -unitsPerEm; + lf.lfWidth = 0; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + + for(int i = 0; i < nglyphs; ++i) { + if (!addGlyphToPath(glyphs[i], positions[i], hdc, path, ttf, /*metric*/0, + qreal(fontDef.pixelSize) / unitsPerEm)) { + // Some windows fonts, like "Modern", are vector stroke + // fonts, which are reported as TMPF_VECTOR but do not + // support GetGlyphOutline, and thus we set this bit so + // that addOutLineToPath can check it and return safely... + hasOutline = false; + break; + } + } + DeleteObject(SelectObject(hdc, oldfont)); +} + +void QFontEngineWin::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +#if !defined(Q_WS_WINCE) + if(tm.tmPitchAndFamily & (TMPF_TRUETYPE | TMPF_VECTOR)) { + hasOutline = true; + QFontEngine::addOutlineToPath(x, y, glyphs, path, flags); + if (hasOutline) { + // has_outline is set to false if addGlyphToPath gets + // false from GetGlyphOutline, meaning its not an outline + // font. + return; + } + } +#endif + QFontEngine::addBitmapFontToPath(x, y, glyphs, path, flags); +} + +QFontEngine::FaceId QFontEngineWin::faceId() const +{ + return _faceId; +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include <qdebug.h> +QT_END_INCLUDE_NAMESPACE + +int QFontEngineWin::synthesized() const +{ + if(synthesized_flags == -1) { + synthesized_flags = 0; + if(ttf) { + const DWORD HEAD = MAKE_TAG('h', 'e', 'a', 'd'); + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + uchar data[4]; + GetFontData(hdc, HEAD, 44, &data, 4); + USHORT macStyle = getUShort(data); + if (tm.tmItalic && !(macStyle & 2)) + synthesized_flags = SynthesizedItalic; + if (fontDef.stretch != 100 && ttf) + synthesized_flags |= SynthesizedStretch; + if (tm.tmWeight >= 500 && !(macStyle & 1)) + synthesized_flags |= SynthesizedBold; + //qDebug() << "font is" << _name << + // "it=" << (macStyle & 2) << fontDef.style << "flags=" << synthesized_flags; + } + } + return synthesized_flags; +} + +QFixed QFontEngineWin::emSquareSize() const +{ + return unitsPerEm; +} + +QFontEngine::Properties QFontEngineWin::properties() const +{ + LOGFONT lf = logfont; + lf.lfHeight = unitsPerEm; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + OUTLINETEXTMETRIC *otm = getOutlineTextMetric(hdc); + Properties p; + p.emSquare = unitsPerEm; + p.italicAngle = otm->otmItalicAngle; + p.postscriptName = QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpFamilyName)).toLatin1(); + p.postscriptName += QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpStyleName)).toLatin1(); + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(p.postscriptName); + p.boundingBox = QRectF(otm->otmrcFontBox.left, -otm->otmrcFontBox.top, + otm->otmrcFontBox.right - otm->otmrcFontBox.left, + otm->otmrcFontBox.top - otm->otmrcFontBox.bottom); + p.ascent = otm->otmAscent; + p.descent = -otm->otmDescent; + p.leading = (int)otm->otmLineGap; + p.capHeight = 0; + p.lineWidth = otm->otmsUnderscoreSize; + free(otm); + DeleteObject(SelectObject(hdc, oldfont)); + return p; +} + +void QFontEngineWin::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + LOGFONT lf = logfont; + lf.lfHeight = unitsPerEm; + int flags = synthesized(); + if(flags & SynthesizedItalic) + lf.lfItalic = false; + lf.lfWidth = 0; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + QFixedPoint p; + p.x = 0; + p.y = 0; + addGlyphToPath(glyph, p, hdc, path, ttf, metrics); + DeleteObject(SelectObject(hdc, oldfont)); +} + +bool QFontEngineWin::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + if (!ttf) + return false; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + DWORD t = qbswap<quint32>(tag); + *length = GetFontData(hdc, t, 0, buffer, *length); + return *length != GDI_ERROR; +} + +#if !defined(CLEARTYPE_QUALITY) +# define CLEARTYPE_QUALITY 5 +#endif + +extern bool qt_cleartype_enabled; + +QNativeImage *QFontEngineWin::drawGDIGlyph(HFONT font, glyph_t glyph, int margin, + const QTransform &t, QImage::Format mask_format) +{ + Q_UNUSED(mask_format) + glyph_metrics_t gm = boundingBox(glyph); + +// printf(" -> for glyph %4x\n", glyph); + + int gx = gm.x.toInt(); + int gy = gm.y.toInt(); + int iw = gm.width.toInt(); + int ih = gm.height.toInt(); + + if (iw <= 0 || iw <= 0) + return 0; + + bool has_transformation = t.type() > QTransform::TxTranslate; + +#ifndef Q_WS_WINCE + unsigned int options = ttf ? ETO_GLYPH_INDEX : 0; + XFORM xform; + + if (has_transformation) { + xform.eM11 = t.m11(); + xform.eM12 = t.m12(); + xform.eM21 = t.m21(); + xform.eM22 = t.m22(); + xform.eDx = margin; + xform.eDy = margin; + + QtHDC qthdc; + HDC hdc = qthdc.hdc(); + + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + HGDIOBJ old_font = SelectObject(hdc, font); + + int ggo_options = GGO_METRICS | (ttf ? GGO_GLYPH_INDEX : 0); + GLYPHMETRICS tgm; + MAT2 mat; + memset(&mat, 0, sizeof(mat)); + mat.eM11.value = mat.eM22.value = 1; + + if (GetGlyphOutline(hdc, glyph, ggo_options, &tgm, 0, 0, &mat) == GDI_ERROR) { + qWarning("QWinFontEngine: unable to query transformed glyph metrics..."); + return 0; + } + + iw = tgm.gmBlackBoxX; + ih = tgm.gmBlackBoxY; + + xform.eDx -= tgm.gmptGlyphOrigin.x; + xform.eDy += tgm.gmptGlyphOrigin.y; + + SetGraphicsMode(hdc, GM_COMPATIBLE); + SelectObject(hdc, old_font); + } +#else // else winc + unsigned int options = 0; +#ifdef DEBUG + Q_ASSERT(!has_transformation); +#else + Q_UNUSED(has_transformation); +#endif +#endif + + QNativeImage *ni = new QNativeImage(iw + 2 * margin + 4, + ih + 2 * margin + 4, + QNativeImage::systemFormat(), !qt_cleartype_enabled); + + /*If cleartype is enabled we use the standard system format even on Windows CE + and not the special textbuffer format we have to use if cleartype is disabled*/ + + ni->image.fill(0xffffffff); + + HDC hdc = ni->hdc; + + SelectObject(hdc, GetStockObject(NULL_BRUSH)); + SelectObject(hdc, GetStockObject(BLACK_PEN)); + SetTextColor(hdc, RGB(0,0,0)); + SetBkMode(hdc, TRANSPARENT); + SetTextAlign(hdc, TA_BASELINE); + + HGDIOBJ old_font = SelectObject(hdc, font); + +#ifndef Q_OS_WINCE + if (has_transformation) { + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + ExtTextOut(hdc, 0, 0, options, 0, (LPCWSTR) &glyph, 1, 0); + } else +#endif + { + ExtTextOut(hdc, -gx + margin, -gy + margin, options, 0, (LPCWSTR) &glyph, 1, 0); + } + + SelectObject(hdc, old_font); + return ni; +} + + +extern uint qt_pow_gamma[256]; + +QImage QFontEngineWin::alphaMapForGlyph(glyph_t glyph, const QTransform &xform) +{ + HFONT font = hfont; + if (qt_cleartype_enabled) { + LOGFONT lf = logfont; + lf.lfQuality = ANTIALIASED_QUALITY; + font = CreateFontIndirect(&lf); + } + QImage::Format mask_format = QNativeImage::systemFormat(); +#ifndef Q_OS_WINCE + mask_format = QImage::Format_RGB32; +#endif + + QNativeImage *mask = drawGDIGlyph(font, glyph, 0, xform, mask_format); + if (mask == 0) + return QImage(); + + QImage indexed(mask->width(), mask->height(), QImage::Format_Indexed8); + + // ### This part is kinda pointless, but we'll crash later if we don't because some + // code paths expects there to be colortables for index8-bit... + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + // Copy data... Cannot use QPainter here as GDI has messed up the + // Alpha channel of the ni.image pixels... + for (int y=0; y<mask->height(); ++y) { + uchar *dest = indexed.scanLine(y); + if (mask->image.format() == QImage::Format_RGB16) { + const qint16 *src = (qint16 *) ((const QImage &) mask->image).scanLine(y); + for (int x=0; x<mask->width(); ++x) + dest[x] = 255 - qGray(src[x]); + } else { + const uint *src = (uint *) ((const QImage &) mask->image).scanLine(y); + for (int x=0; x<mask->width(); ++x) { +#ifdef Q_OS_WINCE + dest[x] = 255 - qGray(src[x]); +#else + if (QNativeImage::systemFormat() == QImage::Format_RGB16) + dest[x] = 255 - qGray(src[x]); + else + dest[x] = 255 - (qt_pow_gamma[qGray(src[x])] * 255. / 2047.); +#endif + } + } + } + + // Cleanup... + delete mask; + if (qt_cleartype_enabled) { + DeleteObject(font); + } + + return indexed; +} + +#define SPI_GETFONTSMOOTHINGCONTRAST 0x200C +#define SPI_SETFONTSMOOTHINGCONTRAST 0x200D + +QImage QFontEngineWin::alphaRGBMapForGlyph(glyph_t glyph, QFixed, int margin, const QTransform &t) +{ + HFONT font = hfont; + + int contrast; + SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &contrast, 0); + SystemParametersInfo(SPI_SETFONTSMOOTHINGCONTRAST, 0, (void *) 1000, 0); + + QNativeImage *mask = drawGDIGlyph(font, glyph, margin, t, QImage::Format_RGB32); + SystemParametersInfo(SPI_SETFONTSMOOTHINGCONTRAST, 0, (void *) contrast, 0); + + if (mask == 0) + return QImage(); + + // Gracefully handle the odd case when the display is 16-bit + const QImage source = mask->image.depth() == 32 + ? mask->image + : mask->image.convertToFormat(QImage::Format_RGB32); + + QImage rgbMask(mask->width(), mask->height(), QImage::Format_RGB32); + for (int y=0; y<mask->height(); ++y) { + uint *dest = (uint *) rgbMask.scanLine(y); + const uint *src = (uint *) source.scanLine(y); + for (int x=0; x<mask->width(); ++x) { + dest[x] = 0xffffffff - (0x00ffffff & src[x]); + } + } + + delete mask; + + return rgbMask; +} + +// -------------------------------------- Multi font engine + +QFontEngineMultiWin::QFontEngineMultiWin(QFontEngine *first, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size()+1), + fallbacks(fallbacks) +{ + engines[0] = first; + first->ref.ref(); + fontDef = engines[0]->fontDef; + cache_cost = first->cache_cost; +} + +void QFontEngineMultiWin::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QString fam = fallbacks.at(at-1); + + LOGFONT lf = static_cast<QFontEngineWin *>(engines.at(0))->logfont; + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + HFONT hfont = CreateFontIndirect(&lf); + + bool stockFont = false; + if (hfont == 0) { + hfont = (HFONT)GetStockObject(ANSI_VAR_FONT); + stockFont = true; + } + engines[at] = new QFontEngineWin(fam, hfont, stockFont, lf); + engines[at]->ref.ref(); + engines[at]->fontDef = fontDef; + + // TODO: increase cost in QFontCache for the font engine loaded here +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_win_p.h b/src/gui/text/qfontengine_win_p.h new file mode 100644 index 0000000000..28d80008e4 --- /dev/null +++ b/src/gui/text/qfontengine_win_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_WIN_P_H +#define QFONTENGINE_WIN_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 <QtCore/qconfig.h> + +QT_BEGIN_NAMESPACE + +class QNativeImage; + +class QFontEngineWin : public QFontEngine +{ +public: + QFontEngineWin(const QString &name, HFONT, bool, LOGFONT); + ~QFontEngineWin(); + + virtual QFixed lineThickness() const; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual FaceId faceId() const; + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + virtual QFixed emSquareSize() const; + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const; + + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + HGDIOBJ selectDesignFont() const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t g) { return boundingBox(g, QTransform()); } + virtual glyph_metrics_t boundingBox(glyph_t g, const QTransform &t); + + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + + virtual const char *name() const; + + bool canRender(const QChar *string, int len); + + Type type() const; + + virtual QImage alphaMapForGlyph(glyph_t t) { return alphaMapForGlyph(t, QTransform()); } + virtual QImage alphaMapForGlyph(glyph_t, const QTransform &xform); + virtual QImage alphaRGBMapForGlyph(glyph_t t, QFixed subPixelPosition, int margin, const QTransform &xform); + +#ifndef Q_CC_MINGW + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); +#endif + + int getGlyphIndexes(const QChar *ch, int numChars, QGlyphLayout *glyphs, bool mirrored) const; + void getCMap(); + +#ifndef Q_WS_WINCE + bool getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const; +#endif + + QString _name; + HFONT hfont; + LOGFONT logfont; + uint stockFont : 1; + uint ttf : 1; + uint hasOutline : 1; + TEXTMETRIC tm; + int lw; + const unsigned char *cmap; + QByteArray cmapTable; + mutable qreal lbearing; + mutable qreal rbearing; + QFixed designToDevice; + int unitsPerEm; + QFixed x_height; + FaceId _faceId; + + mutable int synthesized_flags; + mutable QFixed lineWidth; + mutable unsigned char *widthCache; + mutable uint widthCacheSize; + mutable QFixed *designAdvances; + mutable int designAdvancesSize; + +private: + QNativeImage *drawGDIGlyph(HFONT font, glyph_t, int margin, const QTransform &xform, + QImage::Format mask_format); + +}; + +class QFontEngineMultiWin : public QFontEngineMulti +{ +public: + QFontEngineMultiWin(QFontEngine *first, const QStringList &fallbacks); + void loadEngine(int at); + + QStringList fallbacks; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_WIN_P_H diff --git a/src/gui/text/qfontengine_x11.cpp b/src/gui/text/qfontengine_x11.cpp new file mode 100644 index 0000000000..9f3f8d3d9d --- /dev/null +++ b/src/gui/text/qfontengine_x11.cpp @@ -0,0 +1,1201 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbitmap.h" + +// #define FONTENGINE_DEBUG + +#include <qapplication.h> +#include <qbytearray.h> +#include <qdebug.h> +#include <qtextcodec.h> +#include <qthread.h> + +#include "qfontdatabase.h" +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qvarlengtharray.h" +#include "qwidget.h" +#include "qsettings.h" +#include "qfile.h" + +#include <private/qpaintengine_x11_p.h> +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include <qhash.h> + +#include <private/qpainter_p.h> +#include <private/qunicodetables_p.h> + +#include <private/qt_x11_p.h> +#include <private/qpixmap_x11_p.h> +#include "qx11info_x11.h" +#include "qfontengine_x11_p.h" + +#include <limits.h> + +#include <ft2build.h> +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FC_LCD_FILTER) + +#ifndef FC_LCD_FILTER_NONE +#define FC_LCD_FILTER_NONE FC_LCD_NONE +#endif + +#ifndef FC_LCD_FILTER_DEFAULT +#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT +#endif + +#ifndef FC_LCD_FILTER_LIGHT +#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT +#endif + +#ifndef FC_LCD_FILTER_LEGACY +#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY +#endif + +#endif + +QT_BEGIN_NAMESPACE + + +// ------------------------------------------------------------------ +// Multi XLFD engine +// ------------------------------------------------------------------ + +QFontEngineMultiXLFD::QFontEngineMultiXLFD(const QFontDef &r, const QList<int> &l, int s) + : QFontEngineMulti(l.size()), encodings(l), screen(s), request(r) +{ + loadEngine(0); + fontDef = engines[0]->fontDef; +} + +QFontEngineMultiXLFD::~QFontEngineMultiXLFD() +{ } + +void QFontEngineMultiXLFD::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + const int encoding = encodings.at(at); + QFontEngine *fontEngine = QFontDatabase::loadXlfd(0, QUnicodeTables::Common, request, encoding); + Q_ASSERT(fontEngine != 0); + fontEngine->ref.ref(); + engines[at] = fontEngine; +} + +// ------------------------------------------------------------------ +// Xlfd font engine +// ------------------------------------------------------------------ + +#ifndef QT_NO_FREETYPE + +static QStringList *qt_fontpath = 0; + +static QStringList fontPath() +{ + if (qt_fontpath) + return *qt_fontpath; + + // append qsettings fontpath + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + QStringList fontpath; + + int npaths; + char** font_path; + font_path = XGetFontPath(X11->display, &npaths); + bool xfsconfig_read = false; + for (int i=0; i<npaths; i++) { + // If we're using xfs, append font paths from /etc/X11/fs/config + // can't hurt, and chances are we'll get all fonts that way. + if (((font_path[i])[0] != '/') && !xfsconfig_read) { + // We're using xfs -> read its config + bool finished = false; + QFile f(QLatin1String("/etc/X11/fs/config")); + if (!f.exists()) + f.setFileName(QLatin1String("/usr/X11R6/lib/X11/fs/config")); + if (!f.exists()) + f.setFileName(QLatin1String("/usr/X11/lib/X11/fs/config")); + if (f.exists()) { + f.open(QIODevice::ReadOnly); + while (f.error()==QFile::NoError && !finished) { + QString fs = QString::fromLocal8Bit(f.readLine(1024)); + fs=fs.trimmed(); + if (fs.left(9)==QLatin1String("catalogue") && fs.contains(QLatin1Char('='))) { + fs = fs.mid(fs.indexOf(QLatin1Char('=')) + 1).trimmed(); + bool end = false; + while (f.error()==QFile::NoError && !end) { + if (fs[int(fs.length())-1] == QLatin1Char(',')) + fs = fs.left(fs.length()-1); + else + end = true; + + fs = fs.left(fs.indexOf(QLatin1String(":unscaled"))); + if (fs[0] != QLatin1Char('#')) + fontpath += fs; + fs = QLatin1String(f.readLine(1024)); + fs = fs.trimmed(); + if (fs.isEmpty()) + end = true; + } + finished = true; + } + } + f.close(); + } + xfsconfig_read = true; + } else { + QString fs = QString::fromLocal8Bit(font_path[i]); + fontpath += fs.left(fs.indexOf(QLatin1String(":unscaled"))); + } + } + XFreeFontPath(font_path); + + // append qsettings fontpath + QStringList fp = settings.value(QLatin1String("fontPath")).toStringList(); + if (!fp.isEmpty()) + fontpath += fp; + + qt_fontpath = new QStringList(fontpath); + return fontpath; +} + +static QFontEngine::FaceId fontFile(const QByteArray &_xname, QFreetypeFace **freetype, int *synth) +{ + *freetype = 0; + *synth = 0; + + QByteArray xname = _xname.toLower(); + + int pos = 0; + int minus = 0; + while (minus < 5 && (pos = xname.indexOf('-', pos + 1))) + ++minus; + QByteArray searchname = xname.left(pos); + while (minus < 12 && (pos = xname.indexOf('-', pos + 1))) + ++minus; + QByteArray encoding = xname.mid(pos + 1); + //qDebug("xname='%s', searchname='%s', encoding='%s'", xname.data(), searchname.data(), encoding.data()); + QStringList fontpath = fontPath(); + QFontEngine::FaceId face_id; + face_id.index = 0; + + QByteArray best_mapping; + + for (QStringList::ConstIterator it = fontpath.constBegin(); it != fontpath.constEnd(); ++it) { + if (!(*it).startsWith(QLatin1Char('/'))) + continue; // not a path name, a font server + QString fontmapname; + int num = 0; + // search font.dir and font.scale for the right file + while (num < 2) { + if (num == 0) + fontmapname = (*it) + QLatin1String("/fonts.scale"); + else + fontmapname = (*it) + QLatin1String("/fonts.dir"); + ++num; + //qWarning(fontmapname); + QFile fontmap(fontmapname); + if (!fontmap.open(QIODevice::ReadOnly)) + continue; + while (!fontmap.atEnd()) { + QByteArray mapping = fontmap.readLine(); + QByteArray lmapping = mapping.toLower(); + + //qWarning(xfontname); + //qWarning(mapping); + if (!lmapping.contains(searchname)) + continue; + int index = mapping.indexOf(' '); + QByteArray ffn = mapping.mid(0,index); + // remove bitmap formats freetype can't handle + if (ffn.contains(".spd") || ffn.contains(".phont")) + continue; + bool best_match = false; + if (!best_mapping.isEmpty()) { + if (lmapping.contains("-0-0-0-0-")) { // scalable font + best_match = true; + goto found; + } + if (lmapping.contains(encoding) && !best_mapping.toLower().contains(encoding)) + goto found; + continue; + } + + found: + int colon = ffn.lastIndexOf(':'); + if (colon != -1) { + QByteArray s = ffn.left(colon); + ffn = ffn.mid(colon + 1); + if (s.contains("ds=")) + *synth |= QFontEngine::SynthesizedBold; + if (s.contains("ai=")) + *synth |= QFontEngine::SynthesizedItalic; + } + face_id.filename = (*it).toLocal8Bit() + '/' + ffn; + best_mapping = mapping; + if (best_match) + goto end; + } + } + } +end: +// qDebug("fontfile for %s is from '%s'\n got %s synth=%d", xname.data(), +// best_mapping.data(), face_id.filename.data(), *synth); + *freetype = QFreetypeFace::getFace(face_id); + if (!*freetype) { + face_id.index = 0; + face_id.filename = QByteArray(); + } + return face_id; +} + +#endif // QT_NO_FREETYPE + +// defined in qfontdatabase_x11.cpp +extern int qt_mib_for_xlfd_encoding(const char *encoding); +extern int qt_xlfd_encoding_id(const char *encoding); + +static inline XCharStruct *charStruct(XFontStruct *xfs, uint ch) +{ + XCharStruct *xcs = 0; + unsigned char r = ch>>8; + unsigned char c = ch&0xff; + if (xfs->per_char && + r >= xfs->min_byte1 && + r <= xfs->max_byte1 && + c >= xfs->min_char_or_byte2 && + c <= xfs->max_char_or_byte2) { + xcs = xfs->per_char + ((r - xfs->min_byte1) * + (xfs->max_char_or_byte2 - + xfs->min_char_or_byte2 + 1)) + + (c - xfs->min_char_or_byte2); + if (xcs->width == 0 && xcs->ascent == 0 && xcs->descent == 0) + xcs = 0; + } + return xcs; +} + +QFontEngineXLFD::QFontEngineXLFD(XFontStruct *fs, const QByteArray &name, int mib) + : _fs(fs), _name(name), _codec(0), _cmap(mib) +{ + if (_cmap) _codec = QTextCodec::codecForMib(_cmap); + + cache_cost = (((fs->max_byte1 - fs->min_byte1) * + (fs->max_char_or_byte2 - fs->min_char_or_byte2 + 1)) + + fs->max_char_or_byte2 - fs->min_char_or_byte2); + cache_cost = ((fs->max_bounds.ascent + fs->max_bounds.descent) * + (fs->max_bounds.width * cache_cost / 8)); + lbearing = SHRT_MIN; + rbearing = SHRT_MIN; + face_id.index = -1; + freetype = 0; + synth = 0; +} + +QFontEngineXLFD::~QFontEngineXLFD() +{ + XFreeFont(QX11Info::display(), _fs); + _fs = 0; +#ifndef QT_NO_FREETYPE + if (freetype) + freetype->release(face_id); +#endif +} + +bool QFontEngineXLFD::stringToCMap(const QChar *s, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + // filter out surrogates, we can't handle them anyway with XLFD fonts + QVarLengthArray<ushort> _s(len); + QChar *str = (QChar *)_s.data(); + for (int i = 0; i < len; ++i) { + if (i < len - 1 + && s[i].unicode() >= 0xd800 && s[i].unicode() < 0xdc00 + && s[i+1].unicode() >= 0xdc00 && s[i].unicode() < 0xe000) { + *str = QChar(); + ++i; + } else { + *str = s[i]; + } + ++str; + } + + len = str - (QChar *)_s.data(); + str = (QChar *)_s.data(); + + bool mirrored = flags & QTextEngine::RightToLeft; + if (_codec) { + bool haveNbsp = false; + for (int i = 0; i < len; i++) + if (str[i].unicode() == 0xa0) { + haveNbsp = true; + break; + } + + QVarLengthArray<unsigned short> ch(len); + QChar *chars = (QChar *)ch.data(); + if (haveNbsp || mirrored) { + for (int i = 0; i < len; i++) + chars[i] = (str[i].unicode() == 0xa0 ? 0x20 : + (mirrored ? QChar::mirroredChar(str[i].unicode()) : str[i].unicode())); + } else { + for (int i = 0; i < len; i++) + chars[i] = str[i].unicode(); + } + QTextCodec::ConverterState state; + state.flags = QTextCodec::ConvertInvalidToNull; + QByteArray ba = _codec->fromUnicode(chars, len, &state); + if (ba.length() == 2*len) { + // double byte encoding + const uchar *data = (const uchar *)ba.constData(); + for (int i = 0; i < len; i++) { + glyphs->glyphs[i] = ((ushort)data[0] << 8) + data[1]; + data += 2; + } + } else { + const uchar *data = (const uchar *)ba.constData(); + for (int i = 0; i < len; i++) + glyphs->glyphs[i] = (ushort)data[i]; + } + } else { + int i = len; + const QChar *c = str + len; + if (mirrored) { + while (c != str) + glyphs->glyphs[--i] = (--c)->unicode() == 0xa0 ? 0x20 : QChar::mirroredChar(c->unicode()); + } else { + while (c != str) + glyphs->glyphs[--i] = (--c)->unicode() == 0xa0 ? 0x20 : c->unicode(); + } + } + *nglyphs = len; + glyphs->numGlyphs = len; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineXLFD::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags /*flags*/) const +{ + int i = glyphs->numGlyphs; + XCharStruct *xcs; + // inlined for better performance + if (!_fs->per_char) { + xcs = &_fs->min_bounds; + while (i != 0) { + --i; + const unsigned char r = glyphs->glyphs[i] >> 8; + const unsigned char c = glyphs->glyphs[i] & 0xff; + if (r >= _fs->min_byte1 && + r <= _fs->max_byte1 && + c >= _fs->min_char_or_byte2 && + c <= _fs->max_char_or_byte2) { + glyphs->advances_x[i] = xcs->width; + } else { + glyphs->glyphs[i] = 0; + } + } + } + else if (!_fs->max_byte1) { + XCharStruct *base = _fs->per_char - _fs->min_char_or_byte2; + while (i != 0) { + unsigned int gl = glyphs->glyphs[--i]; + xcs = (gl >= _fs->min_char_or_byte2 && gl <= _fs->max_char_or_byte2) ? + base + gl : 0; + if (!xcs || (!xcs->width && !xcs->ascent && !xcs->descent)) { + glyphs->glyphs[i] = 0; + } else { + glyphs->advances_x[i] = xcs->width; + } + } + } + else { + while (i != 0) { + xcs = charStruct(_fs, glyphs->glyphs[--i]); + if (!xcs) { + glyphs->glyphs[i] = 0; + } else { + glyphs->advances_x[i] = xcs->width; + } + } + } +} + +glyph_metrics_t QFontEngineXLFD::boundingBox(const QGlyphLayout &glyphs) +{ + int i; + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + QFixed ymax; + QFixed xmax; + for (i = 0; i < glyphs.numGlyphs; i++) { + XCharStruct *xcs = charStruct(_fs, glyphs.glyphs[i]); + if (xcs) { + QFixed x = overall.xoff + glyphs.offsets[i].x + xcs->lbearing; + QFixed y = overall.yoff + glyphs.offsets[i].y - xcs->ascent; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + // XCharStruct::rbearing is defined as distance from left edge to rightmost pixel + xmax = qMax(xmax, overall.xoff + glyphs.offsets[i].x + xcs->rbearing); + ymax = qMax(ymax, y + xcs->ascent + xcs->descent); + overall.xoff += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } else { + QFixed size = _fs->ascent; + overall.x = qMin(overall.x, overall.xoff); + overall.y = qMin(overall.y, overall.yoff - size); + ymax = qMax(ymax, overall.yoff); + overall.xoff += size; + xmax = qMax(xmax, overall.xoff); + } + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineXLFD::boundingBox(glyph_t glyph) +{ + glyph_metrics_t gm; + XCharStruct *xcs = charStruct(_fs, glyph); + if (xcs) { + // XCharStruct::rbearing is defined as distance from left edge to rightmost pixel + // XCharStruct::width is defined as the advance + gm = glyph_metrics_t(xcs->lbearing, -xcs->ascent, xcs->rbearing- xcs->lbearing, xcs->ascent + xcs->descent, + xcs->width, 0); + } else { + QFixed size = ascent(); + gm = glyph_metrics_t(0, size, size, size, size, 0); + } + return gm; +} + +QFixed QFontEngineXLFD::ascent() const +{ + return _fs->ascent; +} + +QFixed QFontEngineXLFD::descent() const +{ + return (_fs->descent-1); +} + +QFixed QFontEngineXLFD::leading() const +{ + QFixed l = QFixed(qMin<int>(_fs->ascent, _fs->max_bounds.ascent) + + qMin<int>(_fs->descent, _fs->max_bounds.descent)) * QFixed::fromReal(0.15); + return l.ceil(); +} + +qreal QFontEngineXLFD::maxCharWidth() const +{ + return _fs->max_bounds.width; +} + + +// Loads the font for the specified script +static inline int maxIndex(XFontStruct *f) { + return (((f->max_byte1 - f->min_byte1) * + (f->max_char_or_byte2 - f->min_char_or_byte2 + 1)) + + f->max_char_or_byte2 - f->min_char_or_byte2); +} + +qreal QFontEngineXLFD::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) { + if (_fs->per_char) { + XCharStruct *cs = _fs->per_char; + int nc = maxIndex(_fs) + 1; + int mx = cs->lbearing; + + for (int c = 1; c < nc; c++) { + // ignore the bearings for characters whose ink is + // completely outside the normal bounding box + if ((cs[c].lbearing <= 0 && cs[c].rbearing <= 0) || + (cs[c].lbearing >= cs[c].width && cs[c].rbearing >= cs[c].width)) + continue; + + int nmx = cs[c].lbearing; + + if (nmx < mx) + mx = nmx; + } + + ((QFontEngineXLFD *)this)->lbearing = mx; + } else + ((QFontEngineXLFD *)this)->lbearing = _fs->min_bounds.lbearing; + } + return lbearing; +} + +qreal QFontEngineXLFD::minRightBearing() const +{ + if (rbearing == SHRT_MIN) { + if (_fs->per_char) { + XCharStruct *cs = _fs->per_char; + int nc = maxIndex(_fs) + 1; + int mx = cs->rbearing; + + for (int c = 1; c < nc; c++) { + // ignore the bearings for characters whose ink is + // completely outside the normal bounding box + if ((cs[c].lbearing <= 0 && cs[c].rbearing <= 0) || + (cs[c].lbearing >= cs[c].width && cs[c].rbearing >= cs[c].width)) + continue; + + int nmx = cs[c].rbearing; + + if (nmx < mx) + mx = nmx; + } + + ((QFontEngineXLFD *)this)->rbearing = mx; + } else + ((QFontEngineXLFD *)this)->rbearing = _fs->min_bounds.rbearing; + } + return rbearing; +} + +const char *QFontEngineXLFD::name() const +{ + return _name; +} + +bool QFontEngineXLFD::canRender(const QChar *string, int len) +{ + QVarLengthGlyphLayoutArray glyphs(len); + int nglyphs = len; + if (stringToCMap(string, len, &glyphs, &nglyphs, 0) == false) { + glyphs.resize(nglyphs); + stringToCMap(string, len, &glyphs, &nglyphs, 0); + } + + bool allExist = true; + for (int i = 0; i < nglyphs; i++) { + if (!glyphs.glyphs[i] || !charStruct(_fs, glyphs.glyphs[i])) { + allExist = false; + break; + } + } + + return allExist; +} + +QBitmap QFontEngineXLFD::bitmapForGlyphs(const QGlyphLayout &glyphs, const glyph_metrics_t &metrics, QTextItem::RenderFlags flags) +{ + int w = metrics.width.toInt(); + int h = metrics.height.toInt(); + if (w <= 0 || h <= 0) + return QBitmap(); + + QPixmapData *data = new QX11PixmapData(QPixmapData::BitmapType); + data->resize(w, h); + QPixmap bm(data); + QPainter p(&bm); + p.fillRect(0, 0, w, h, Qt::color0); + p.setPen(Qt::color1); + + QTextItemInt item; + item.flags = flags; + item.ascent = -metrics.y; + item.descent = metrics.height - item.ascent; + item.width = metrics.width; + item.chars = 0; + item.num_chars = 0; + item.logClusters = 0; + item.glyphs = glyphs; + item.fontEngine = this; + item.f = 0; + + p.drawTextItem(QPointF(-metrics.x.toReal(), item.ascent.toReal()), item); + p.end(); + + return QBitmap(bm); +} + +void QFontEngineXLFD::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + // cannot use QFontEngine::addBitmapFontToPath(), since we don't + // have direct access to the glyph bitmaps, so we have to draw + // onto a QBitmap, then convert to QImage, then to path + glyph_metrics_t metrics = boundingBox(glyphs); + + QImage image = bitmapForGlyphs(glyphs, metrics, flags).toImage(); + if (image.isNull()) + return; + + image = image.convertToFormat(QImage::Format_Mono); + const uchar *image_data = image.bits(); + uint bpl = image.bytesPerLine(); + // from qfontengine.cpp + extern void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, + int bpl, int w, int h, QPainterPath *path); + qt_addBitmapToPath(x, y + metrics.y.toReal(), image_data, bpl, image.width(), image.height(), path); +} + +QFontEngine::FaceId QFontEngineXLFD::faceId() const +{ +#ifndef QT_NO_FREETYPE + if (face_id.index == -1) { + face_id = fontFile(_name, &freetype, &synth); + if (_codec) + face_id.encoding = _codec->mibEnum(); + if (freetype) { + const_cast<QFontEngineXLFD *>(this)->fsType = freetype->fsType(); + } else { + face_id.index = 0; + face_id.filename = '-' + QFontEngine::properties().postscriptName; + } + } +#endif + + return face_id; +} + +QFontEngine::Properties QFontEngineXLFD::properties() const +{ + if (face_id.index == -1) + (void)faceId(); + +#ifndef QT_NO_FREETYPE + if (freetype) + return freetype->properties(); +#endif + return QFontEngine::properties(); +} + +void QFontEngineXLFD::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + if (face_id.index == -1) + (void)faceId(); +#ifndef QT_NO_FREETYPE + if (!freetype) +#endif + { + QFontEngine::getUnscaledGlyph(glyph, path, metrics); + return; + } + +#ifndef QT_NO_FREETYPE + freetype->lock(); + + FT_Face face = freetype->face; + FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0); + freetype->xsize = face->units_per_EM << 6; + freetype->ysize = face->units_per_EM << 6; + FT_Set_Transform(face, 0, 0); + glyph = glyphIndexToFreetypeGlyphIndex(glyph); + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + int left = face->glyph->metrics.horiBearingX; + int right = face->glyph->metrics.horiBearingX + face->glyph->metrics.width; + int top = face->glyph->metrics.horiBearingY; + int bottom = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; + + QFixedPoint p; + p.x = 0; + p.y = 0; + metrics->width = QFixed::fromFixed(right-left); + metrics->height = QFixed::fromFixed(top-bottom); + metrics->x = QFixed::fromFixed(left); + metrics->y = QFixed::fromFixed(-top); + metrics->xoff = QFixed::fromFixed(face->glyph->advance.x); + + if (!FT_IS_SCALABLE(freetype->face)) + QFreetypeFace::addBitmapToPath(face->glyph, p, path); + else + QFreetypeFace::addGlyphToPath(face, face->glyph, p, path, face->units_per_EM << 6, face->units_per_EM << 6); + + FT_Set_Transform(face, &freetype->matrix, 0); + freetype->unlock(); +#endif // QT_NO_FREETYPE +} + + +bool QFontEngineXLFD::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ +#ifndef QT_NO_FREETYPE + if (face_id.index == -1) + (void)faceId(); + if (!freetype) + return false; + return freetype->getSfntTable(tag, buffer, length); +#else + Q_UNUSED(tag); + Q_UNUSED(buffer); + Q_UNUSED(length); + return false; +#endif +} + +int QFontEngineXLFD::synthesized() const +{ + return synth; +} + +QImage QFontEngineXLFD::alphaMapForGlyph(glyph_t glyph) +{ + glyph_metrics_t metrics = boundingBox(glyph); + +/* + printf("a) w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n", + metrics.width.toReal(), + metrics.height.toReal(), + metrics.xoff.toReal(), + metrics.yoff.toReal(), + metrics.x.toReal(), + metrics.y.toReal()); +*/ + + QGlyphLayoutArray<1> glyphs; + glyphs.glyphs[0] = glyph; + + QImage image = bitmapForGlyphs(glyphs, metrics).toImage(); +//image.save(QString::fromLatin1("x11cache-%1.png").arg((int)glyph)); + + image = image.convertToFormat(QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i = 0; i < 256; ++i) + colors[i] = qRgba(0, 0, 0, i); + image.setColorTable(colors); + + int width = image.width(); + int height = image.height(); + for (int y = 0; y < height; ++y) { + uchar *bits = image.scanLine(y); + for (int x = 0; x < width; ++x) + bits[x] = ~(bits[x]-1); + } + + return image; +} + +#ifndef QT_NO_FREETYPE + +FT_Face QFontEngineXLFD::non_locked_face() const +{ + return freetype ? freetype->face : 0; +} + +uint QFontEngineXLFD::toUnicode(glyph_t g) const +{ + if (_codec) { + QTextCodec::ConverterState state; + state.flags = QTextCodec::ConvertInvalidToNull; + uchar data[2]; + int l = 1; + if (g > 255) { + data[0] = (g >> 8); + data[1] = (g & 255); + l = 2; + } else { + data[0] = g; + } + QString s = _codec->toUnicode((char *)data, l, &state); + Q_ASSERT(s.length() == 1); + g = s.at(0).unicode(); + } + return g; +} + +glyph_t QFontEngineXLFD::glyphIndexToFreetypeGlyphIndex(glyph_t g) const +{ + return FT_Get_Char_Index(freetype->face, toUnicode(g)); +} +#endif + +#ifndef QT_NO_FONTCONFIG + +// ------------------------------------------------------------------ +// Multi FT engine +// ------------------------------------------------------------------ + +static QFontEngine *engineForPattern(FcPattern *pattern, const QFontDef &request, + int screen) +{ + FcResult res; + FcPattern *match = FcFontMatch(0, pattern, &res); + QFontEngineX11FT *engine = new QFontEngineX11FT(match, request, screen); + if (!engine->invalid()) + return engine; + + delete engine; + QFontEngine *fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = request; + return fe; +} + +QFontEngineMultiFT::QFontEngineMultiFT(QFontEngine *fe, FcPattern *matchedPattern, FcPattern *p, int s, const QFontDef &req) + : QFontEngineMulti(2), request(req), pattern(p), firstEnginePattern(matchedPattern), fontSet(0), screen(s) +{ + + engines[0] = fe; + engines.at(0)->ref.ref(); + fontDef = engines[0]->fontDef; + cache_cost = 100; + firstFontIndex = 1; +} + +QFontEngineMultiFT::~QFontEngineMultiFT() +{ + extern QMutex *qt_fontdatabase_mutex(); + QMutexLocker locker(qt_fontdatabase_mutex()); + + FcPatternDestroy(pattern); + if (firstEnginePattern) + FcPatternDestroy(firstEnginePattern); + if (fontSet) + FcFontSetDestroy(fontSet); +} + + +void QFontEngineMultiFT::loadEngine(int at) +{ + extern QMutex *qt_fontdatabase_mutex(); + QMutexLocker locker(qt_fontdatabase_mutex()); + + extern void qt_addPatternProps(FcPattern *pattern, int screen, int script, + const QFontDef &request); + extern QFontDef qt_FcPatternToQFontDef(FcPattern *pattern, const QFontDef &); + extern FcFontSet *qt_fontSetForPattern(FcPattern *pattern, const QFontDef &request); + + Q_ASSERT(at > 0); + if (!fontSet) { + fontSet = qt_fontSetForPattern(pattern, request); + + // it may happen that the fontset of fallbacks consists of only one font. In this case we + // have to fall back to the box fontengine as we cannot render the glyph. + if (fontSet->nfont == 1 && at == 1 && engines.size() == 2) { + Q_ASSERT(engines.at(at) == 0); + QFontEngine *fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = request; + engines[at] = fe; + return; + } + + if (firstEnginePattern) { + + if (!FcPatternEqual(firstEnginePattern, fontSet->fonts[0])) + firstFontIndex = 0; + + FcPatternDestroy(firstEnginePattern); + firstEnginePattern = 0; + } + + engines.resize(fontSet->nfont + 1 - firstFontIndex); + } + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + FcPattern *pattern = FcPatternDuplicate(fontSet->fonts[at + firstFontIndex - 1]); + qt_addPatternProps(pattern, screen, QUnicodeTables::Common, request); + + QFontDef fontDef = qt_FcPatternToQFontDef(pattern, this->request); + + // note: we use -1 for the script to make sure that we keep real + // FT engines separate from Multi engines in the font cache + QFontCache::Key key(fontDef, -1, screen); + QFontEngine *fontEngine = QFontCache::instance()->findEngine(key); + if (!fontEngine) { + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + fontEngine = engineForPattern(pattern, request, screen); + QFontCache::instance()->insertEngine(key, fontEngine); + } + FcPatternDestroy(pattern); + fontEngine->ref.ref(); + engines[at] = fontEngine; +} + +// ------------------------------------------------------------------ +// X11 FT engine +// ------------------------------------------------------------------ + + + +Q_GUI_EXPORT void qt_x11ft_convert_pattern(FcPattern *pattern, QByteArray *file_name, int *index, bool *antialias) +{ + FcChar8 *fileName; + FcPatternGetString(pattern, FC_FILE, 0, &fileName); + *file_name = (const char *)fileName; + if (!FcPatternGetInteger(pattern, FC_INDEX, 0, index)) + index = 0; + FcBool b; + if (FcPatternGetBool(pattern, FC_ANTIALIAS, 0, &b) == FcResultMatch) + *antialias = b; +} + + +QFontEngineX11FT::QFontEngineX11FT(FcPattern *pattern, const QFontDef &fd, int screen) + : QFontEngineFT(fd) +{ +// FcPatternPrint(pattern); + + bool antialias = X11->fc_antialias; + QByteArray file_name; + int face_index; + qt_x11ft_convert_pattern(pattern, &file_name, &face_index, &antialias); + QFontEngine::FaceId face_id; + face_id.filename = file_name; + face_id.index = face_index; + + canUploadGlyphsToServer = QApplication::testAttribute(Qt::AA_X11InitThreads) || (qApp->thread() == QThread::currentThread()); + + subpixelType = Subpixel_None; + if (antialias) { + int subpixel = X11->display ? X11->screens[screen].subpixel : FC_RGBA_UNKNOWN; + if (subpixel == FC_RGBA_UNKNOWN) + (void) FcPatternGetInteger(pattern, FC_RGBA, 0, &subpixel); + if (!antialias || subpixel == FC_RGBA_UNKNOWN) + subpixel = FC_RGBA_NONE; + + switch (subpixel) { + case FC_RGBA_NONE: subpixelType = Subpixel_None; break; + case FC_RGBA_RGB: subpixelType = Subpixel_RGB; break; + case FC_RGBA_BGR: subpixelType = Subpixel_BGR; break; + case FC_RGBA_VRGB: subpixelType = Subpixel_VRGB; break; + case FC_RGBA_VBGR: subpixelType = Subpixel_VBGR; break; + default: break; + } + } + + if (fd.hintingPreference != QFont::PreferDefaultHinting) { + switch (fd.hintingPreference) { + case QFont::PreferNoHinting: + default_hint_style = HintNone; + break; + case QFont::PreferVerticalHinting: + default_hint_style = HintLight; + break; + case QFont::PreferFullHinting: + default: + default_hint_style = HintFull; + break; + } + } +#ifdef FC_HINT_STYLE + else { + int hint_style = 0; + // Try to use Xft.hintstyle from XDefaults first if running in GNOME, to match + // the behavior of cairo + if (X11->fc_hint_style > -1 && X11->desktopEnvironment == DE_GNOME) + hint_style = X11->fc_hint_style; + else if (FcPatternGetInteger (pattern, FC_HINT_STYLE, 0, &hint_style) == FcResultNoMatch + && X11->fc_hint_style > -1) + hint_style = X11->fc_hint_style; + + switch (hint_style) { + case FC_HINT_NONE: + default_hint_style = HintNone; + break; + case FC_HINT_SLIGHT: + default_hint_style = HintLight; + break; + case FC_HINT_MEDIUM: + default_hint_style = HintMedium; + break; + default: + default_hint_style = HintFull; + break; + } + } +#endif + +#if defined(FC_AUTOHINT) && defined(FT_LOAD_FORCE_AUTOHINT) + { + bool autohint = false; + + FcBool b; + if (FcPatternGetBool(pattern, FC_AUTOHINT, 0, &b) == FcResultMatch) + autohint = b; + + if (autohint) + default_load_flags |= FT_LOAD_FORCE_AUTOHINT; + } +#endif + +#if defined(FC_LCD_FILTER) && defined(FT_LCD_FILTER_H) + { + int filter = FC_LCD_FILTER_NONE; + if (FcPatternGetInteger(pattern, FC_LCD_FILTER, 0, &filter) == FcResultMatch) { + switch (filter) { + case FC_LCD_FILTER_NONE: + lcdFilterType = FT_LCD_FILTER_NONE; + break; + case FC_LCD_FILTER_DEFAULT: + lcdFilterType = FT_LCD_FILTER_DEFAULT; + break; + case FC_LCD_FILTER_LIGHT: + lcdFilterType = FT_LCD_FILTER_LIGHT; + break; + case FC_LCD_FILTER_LEGACY: + lcdFilterType = FT_LCD_FILTER_LEGACY; + break; + default: + // new unknown lcd filter type?! + break; + } + } + } +#endif + +#ifdef FC_EMBEDDED_BITMAP + { + FcBool b; + if (FcPatternGetBool(pattern, FC_EMBEDDED_BITMAP, 0, &b) == FcResultMatch) + embeddedbitmap = b; + } +#endif + + GlyphFormat defaultFormat = Format_None; + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + int format = PictStandardA8; + if (!antialias) + format = PictStandardA1; + else if (subpixelType == Subpixel_RGB + || subpixelType == Subpixel_BGR + || subpixelType == Subpixel_VRGB + || subpixelType == Subpixel_VBGR) + format = PictStandardARGB32; + xglyph_format = format; + + if (subpixelType != QFontEngineFT::Subpixel_None) + defaultFormat = Format_A32; + else if (antialias) + defaultFormat = Format_A8; + else + defaultFormat = Format_Mono; + } +#endif + + if (!init(face_id, antialias, defaultFormat)) { + FcPatternDestroy(pattern); + return; + } + + if (!freetype->charset) { + FcCharSet *cs; + FcPatternGetCharSet (pattern, FC_CHARSET, 0, &cs); + freetype->charset = FcCharSetCopy(cs); + } + FcPatternDestroy(pattern); +} + +QFontEngineX11FT::~QFontEngineX11FT() +{ + freeGlyphSets(); +} + +unsigned long QFontEngineX11FT::allocateServerGlyphSet() +{ +#ifndef QT_NO_XRENDER + if (!canUploadGlyphsToServer || !X11->use_xrender) + return 0; + return XRenderCreateGlyphSet(X11->display, XRenderFindStandardFormat(X11->display, xglyph_format)); +#else + return 0; +#endif +} + +void QFontEngineX11FT::freeServerGlyphSet(unsigned long id) +{ +#ifndef QT_NO_XRENDER + if (!id) + return; + XRenderFreeGlyphSet(X11->display, id); +#endif +} + +bool QFontEngineX11FT::uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const +{ +#ifndef QT_NO_XRENDER + if (!canUploadGlyphsToServer) + return false; + if (g->format == Format_Mono) { + /* + * swap bit order around; FreeType is always MSBFirst + */ + if (BitmapBitOrder(X11->display) != MSBFirst) { + unsigned char *line = g->data; + int i = glyphDataSize; + while (i--) { + unsigned char c; + c = *line; + c = ((c << 1) & 0xaa) | ((c >> 1) & 0x55); + c = ((c << 2) & 0xcc) | ((c >> 2) & 0x33); + c = ((c << 4) & 0xf0) | ((c >> 4) & 0x0f); + *line++ = c; + } + } + } + + ::Glyph xglyph = glyphid; + XRenderAddGlyphs (X11->display, set->id, &xglyph, info, 1, (const char *)g->data, glyphDataSize); + delete [] g->data; + g->data = 0; + g->format = Format_None; + g->uploadedToServer = true; + return true; +#else + return false; +#endif +} + +#endif // QT_NO_FONTCONFIG + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_x11_p.h b/src/gui/text/qfontengine_x11_p.h new file mode 100644 index 0000000000..ad68fac167 --- /dev/null +++ b/src/gui/text/qfontengine_x11_p.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_X11_P_H +#define QFONTENGINE_X11_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/qt_x11_p.h> + +#include <private/qfontengine_ft_p.h> + +QT_BEGIN_NAMESPACE + +class QFreetypeFace; + +// -------------------------------------------------------------------------- + +class QFontEngineMultiXLFD : public QFontEngineMulti +{ +public: + QFontEngineMultiXLFD(const QFontDef &r, const QList<int> &l, int s); + ~QFontEngineMultiXLFD(); + + void loadEngine(int at); + +private: + QList<int> encodings; + int screen; + QFontDef request; +}; + +/** + * \internal The font engine for X Logical Font Description (XLFD) fonts, which is for X11 systems without freetype. + */ +class QFontEngineXLFD : public QFontEngine +{ +public: + QFontEngineXLFD(XFontStruct *f, const QByteArray &name, int mib); + ~QFontEngineXLFD(); + + virtual QFontEngine::FaceId faceId() const; + QFontEngine::Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags); + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QImage alphaMapForGlyph(glyph_t); + + virtual inline Type type() const + { return QFontEngine::XLFD; } + + virtual bool canRender(const QChar *string, int len); + virtual const char *name() const; + + inline XFontStruct *fontStruct() const + { return _fs; } + +#ifndef QT_NO_FREETYPE + FT_Face non_locked_face() const; + glyph_t glyphIndexToFreetypeGlyphIndex(glyph_t g) const; +#endif + uint toUnicode(glyph_t g) const; + +private: + QBitmap bitmapForGlyphs(const QGlyphLayout &glyphs, const glyph_metrics_t &metrics, QTextItem::RenderFlags flags = 0); + + XFontStruct *_fs; + QByteArray _name; + QTextCodec *_codec; + int _cmap; + int lbearing, rbearing; + mutable QFontEngine::FaceId face_id; + mutable QFreetypeFace *freetype; + mutable int synth; +}; + +#ifndef QT_NO_FONTCONFIG + +class Q_GUI_EXPORT QFontEngineMultiFT : public QFontEngineMulti +{ +public: + QFontEngineMultiFT(QFontEngine *fe, FcPattern *firstEnginePattern, FcPattern *p, int s, const QFontDef &request); + ~QFontEngineMultiFT(); + + void loadEngine(int at); + +private: + QFontDef request; + FcPattern *pattern; + FcPattern *firstEnginePattern; + FcFontSet *fontSet; + int screen; + int firstFontIndex; // first font in fontset +}; + +class Q_GUI_EXPORT QFontEngineX11FT : public QFontEngineFT +{ +public: + explicit QFontEngineX11FT(const QFontDef &fontDef) : QFontEngineFT(fontDef) {} + explicit QFontEngineX11FT(FcPattern *pattern, const QFontDef &fd, int screen); + ~QFontEngineX11FT(); + +#ifndef QT_NO_XRENDER + int xglyph_format; +#endif + +protected: + virtual bool uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const; + virtual unsigned long allocateServerGlyphSet(); + virtual void freeServerGlyphSet(unsigned long id); +}; + +#endif // QT_NO_FONTCONFIG + +QT_END_NAMESPACE + +#endif // QFONTENGINE_X11_P_H diff --git a/src/gui/text/qfontenginedirectwrite.cpp b/src/gui/text/qfontenginedirectwrite.cpp new file mode 100644 index 0000000000..f0a3644865 --- /dev/null +++ b/src/gui/text/qfontenginedirectwrite.cpp @@ -0,0 +1,635 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_DIRECTWRITE + +#include "qfontenginedirectwrite_p.h" + +#include <qendian.h> +#include <dwrite.h> +#include <private/qnativeimage_p.h> + +#include <d2d1.h> + +QT_BEGIN_NAMESPACE + +// Convert from design units to logical pixels +#define DESIGN_TO_LOGICAL(DESIGN_UNIT_VALUE) \ + QFixed::fromReal((qreal(DESIGN_UNIT_VALUE) / qreal(m_unitsPerEm)) * fontDef.pixelSize) + +namespace { + + class GeometrySink: public IDWriteGeometrySink + { + public: + GeometrySink(QPainterPath *path) : m_path(path), m_refCount(0) + { + Q_ASSERT(m_path != 0); + } + + IFACEMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *beziers, UINT bezierCount); + IFACEMETHOD_(void, AddLines)(const D2D1_POINT_2F *points, UINT pointCount); + IFACEMETHOD_(void, BeginFigure)(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin); + IFACEMETHOD(Close)(); + IFACEMETHOD_(void, EndFigure)(D2D1_FIGURE_END figureEnd); + IFACEMETHOD_(void, SetFillMode)(D2D1_FILL_MODE fillMode); + IFACEMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT vertexFlags); + + IFACEMETHOD_(unsigned long, AddRef)(); + IFACEMETHOD_(unsigned long, Release)(); + IFACEMETHOD(QueryInterface)(IID const &riid, void **ppvObject); + + private: + inline static QPointF fromD2D1_POINT_2F(const D2D1_POINT_2F &inp) + { + return QPointF(inp.x, inp.y); + } + + unsigned long m_refCount; + QPointF m_startPoint; + QPainterPath *m_path; + }; + + void GeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers, + UINT bezierCount) + { + for (uint i=0; i<bezierCount; ++i) { + QPointF c1 = fromD2D1_POINT_2F(beziers[i].point1); + QPointF c2 = fromD2D1_POINT_2F(beziers[i].point2); + QPointF p2 = fromD2D1_POINT_2F(beziers[i].point3); + + m_path->cubicTo(c1, c2, p2); + } + } + + void GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT pointsCount) + { + for (uint i=0; i<pointsCount; ++i) + m_path->lineTo(fromD2D1_POINT_2F(points[i])); + } + + void GeometrySink::BeginFigure(D2D1_POINT_2F startPoint, + D2D1_FIGURE_BEGIN /*figureBegin*/) + { + m_startPoint = fromD2D1_POINT_2F(startPoint); + m_path->moveTo(m_startPoint); + } + + IFACEMETHODIMP GeometrySink::Close() + { + return E_NOTIMPL; + } + + void GeometrySink::EndFigure(D2D1_FIGURE_END figureEnd) + { + if (figureEnd == D2D1_FIGURE_END_CLOSED) + m_path->closeSubpath(); + } + + void GeometrySink::SetFillMode(D2D1_FILL_MODE fillMode) + { + m_path->setFillRule(fillMode == D2D1_FILL_MODE_ALTERNATE + ? Qt::OddEvenFill + : Qt::WindingFill); + } + + void GeometrySink::SetSegmentFlags(D2D1_PATH_SEGMENT /*vertexFlags*/) + { + /* Not implemented */ + } + + IFACEMETHODIMP_(unsigned long) GeometrySink::AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + IFACEMETHODIMP_(unsigned long) GeometrySink::Release() + { + unsigned long newCount = InterlockedDecrement(&m_refCount); + if (newCount == 0) + { + delete this; + return 0; + } + + return newCount; + } + + IFACEMETHODIMP GeometrySink::QueryInterface(IID const &riid, void **ppvObject) + { + if (__uuidof(IDWriteGeometrySink) == riid) { + *ppvObject = this; + } else if (__uuidof(IUnknown) == riid) { + *ppvObject = this; + } else { + *ppvObject = NULL; + return E_FAIL; + } + + AddRef(); + return S_OK; + } + +} + +QFontEngineDirectWrite::QFontEngineDirectWrite(IDWriteFactory *directWriteFactory, + IDWriteFontFace *directWriteFontFace, + qreal pixelSize) + : m_directWriteFontFace(directWriteFontFace) + , m_directWriteFactory(directWriteFactory) + , m_directWriteBitmapRenderTarget(0) + , m_lineThickness(-1) + , m_unitsPerEm(-1) + , m_ascent(-1) + , m_descent(-1) + , m_xHeight(-1) + , m_lineGap(-1) +{ + m_directWriteFactory->AddRef(); + m_directWriteFontFace->AddRef(); + + fontDef.pixelSize = pixelSize; + collectMetrics(); +} + +QFontEngineDirectWrite::~QFontEngineDirectWrite() +{ + m_directWriteFactory->Release(); + m_directWriteFontFace->Release(); + + if (m_directWriteBitmapRenderTarget != 0) + m_directWriteBitmapRenderTarget->Release(); +} + +void QFontEngineDirectWrite::collectMetrics() +{ + if (m_directWriteFontFace != 0) { + DWRITE_FONT_METRICS metrics; + + m_directWriteFontFace->GetMetrics(&metrics); + m_unitsPerEm = metrics.designUnitsPerEm; + + m_lineThickness = DESIGN_TO_LOGICAL(metrics.underlineThickness); + m_ascent = DESIGN_TO_LOGICAL(metrics.ascent); + m_descent = DESIGN_TO_LOGICAL(metrics.descent); + m_xHeight = DESIGN_TO_LOGICAL(metrics.xHeight); + m_lineGap = DESIGN_TO_LOGICAL(metrics.lineGap); + } +} + +QFixed QFontEngineDirectWrite::lineThickness() const +{ + if (m_lineThickness > 0) + return m_lineThickness; + else + return QFontEngine::lineThickness(); +} + +bool QFontEngineDirectWrite::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + if (m_directWriteFontFace) { + DWORD t = qbswap<quint32>(tag); + + const void *tableData = 0; + void *tableContext = 0; + UINT32 tableSize; + BOOL exists; + HRESULT hr = m_directWriteFontFace->TryGetFontTable( + t, &tableData, &tableSize, &tableContext, &exists + ); + + if (SUCCEEDED(hr)) { + if (!exists) + return false; + + if (buffer == 0) { + *length = tableSize; + return true; + } else if (*length < tableSize) { + return false; + } + + qMemCopy(buffer, tableData, tableSize); + m_directWriteFontFace->ReleaseFontTable(tableContext); + + return true; + } else { + qErrnoWarning("QFontEngineDirectWrite::getSfntTableData: TryGetFontTable failed"); + } + } + + return false; +} + +QFixed QFontEngineDirectWrite::emSquareSize() const +{ + if (m_unitsPerEm > 0) + return m_unitsPerEm; + else + return QFontEngine::emSquareSize(); +} + +inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +bool QFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (m_directWriteFontFace != 0) { + QVarLengthArray<UINT32> codePoints(len); + for (int i=0; i<len; ++i) { + codePoints[i] = getChar(str, i, len); + if (flags & QTextEngine::RightToLeft) + codePoints[i] = QChar::mirroredChar(codePoints[i]); + } + + QVarLengthArray<UINT16> glyphIndices(len); + HRESULT hr = m_directWriteFontFace->GetGlyphIndicesW(codePoints.data(), + len, + glyphIndices.data()); + + if (SUCCEEDED(hr)) { + for (int i=0; i<len; ++i) + glyphs->glyphs[i] = glyphIndices[i]; + + *nglyphs = len; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, 0); + + return true; + } else { + qErrnoWarning("QFontEngineDirectWrite::stringToCMap: GetGlyphIndicesW failed"); + } + } + + return false; +} + +void QFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + if (m_directWriteFontFace == 0) + return; + + QVarLengthArray<UINT16> glyphIndices(glyphs->numGlyphs); + + // ### Caching? + for(int i=0; i<glyphs->numGlyphs; i++) + glyphIndices[i] = UINT16(glyphs->glyphs[i]); + + QVarLengthArray<DWRITE_GLYPH_METRICS> glyphMetrics(glyphIndices.size()); + HRESULT hr = m_directWriteFontFace->GetDesignGlyphMetrics(glyphIndices.data(), + glyphIndices.size(), + glyphMetrics.data()); + if (SUCCEEDED(hr)) { + for (int i=0; i<glyphs->numGlyphs; ++i) { + glyphs->advances_x[i] = DESIGN_TO_LOGICAL(glyphMetrics[i].advanceWidth); + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = 0; + } + } else { + qErrnoWarning("QFontEngineDirectWrite::recalcAdvances: GetDesignGlyphMetrics failed"); + } +} + +void QFontEngineDirectWrite::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (m_directWriteFontFace == 0) + return; + + QVarLengthArray<UINT16> glyphIndices(nglyphs); + QVarLengthArray<DWRITE_GLYPH_OFFSET> glyphOffsets(nglyphs); + QVarLengthArray<FLOAT> glyphAdvances(nglyphs); + + for (int i=0; i<nglyphs; ++i) { + glyphIndices[i] = glyphs[i]; + glyphOffsets[i].advanceOffset = positions[i].x.toReal(); + glyphOffsets[i].ascenderOffset = -positions[i].y.toReal(); + glyphAdvances[i] = 0.0; + } + + GeometrySink geometrySink(path); + HRESULT hr = m_directWriteFontFace->GetGlyphRunOutline( + fontDef.pixelSize, + glyphIndices.data(), + glyphAdvances.data(), + glyphOffsets.data(), + nglyphs, + false, + flags & QTextItem::RightToLeft, + &geometrySink + ); + + if (FAILED(hr)) + qErrnoWarning("QFontEngineDirectWrite::addGlyphsToPath: GetGlyphRunOutline failed"); +} + +glyph_metrics_t QFontEngineDirectWrite::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() : glyphs.effectiveAdvance(i); + + } + + return glyph_metrics_t(0, -m_ascent, w - lastRightBearing(glyphs), m_ascent + m_descent, w, 0); +} + +glyph_metrics_t QFontEngineDirectWrite::boundingBox(glyph_t g) +{ + if (m_directWriteFontFace == 0) + return glyph_metrics_t(); + + UINT16 glyphIndex = g; + + DWRITE_GLYPH_METRICS glyphMetrics; + HRESULT hr = m_directWriteFontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics); + if (SUCCEEDED(hr)) { + QFixed advanceWidth = DESIGN_TO_LOGICAL(glyphMetrics.advanceWidth); + QFixed leftSideBearing = DESIGN_TO_LOGICAL(glyphMetrics.leftSideBearing); + QFixed rightSideBearing = DESIGN_TO_LOGICAL(glyphMetrics.rightSideBearing); + QFixed advanceHeight = DESIGN_TO_LOGICAL(glyphMetrics.advanceHeight); + QFixed verticalOriginY = DESIGN_TO_LOGICAL(glyphMetrics.verticalOriginY); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + advanceWidth = advanceWidth.round(); + advanceHeight = advanceHeight.round(); + } + + QFixed width = advanceWidth - leftSideBearing - rightSideBearing; + + return glyph_metrics_t(-leftSideBearing, -verticalOriginY, + width, m_ascent + m_descent, + advanceWidth, advanceHeight); + } else { + qErrnoWarning("QFontEngineDirectWrite::boundingBox: GetDesignGlyphMetrics failed"); + } + + return glyph_metrics_t(); +} + +QFixed QFontEngineDirectWrite::ascent() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_ascent.round() + : m_ascent; +} + +QFixed QFontEngineDirectWrite::descent() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? (m_descent - 1).round() + : (m_descent - 1); +} + +QFixed QFontEngineDirectWrite::leading() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_lineGap.round() + : m_lineGap; +} + +QFixed QFontEngineDirectWrite::xHeight() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_xHeight.round() + : m_xHeight; +} + +qreal QFontEngineDirectWrite::maxCharWidth() const +{ + // ### + return 0; +} + +extern uint qt_pow_gamma[256]; + +QImage QFontEngineDirectWrite::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + QImage im = imageForGlyph(glyph, subPixelPosition, 0, QTransform()); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uint *src = (uint*) im.scanLine(y); + uchar *dst = indexed.scanLine(y); + for (int x=0; x<im.width(); ++x) { + *dst = 255 - (qt_pow_gamma[qGray(0xffffffff - *src)] * 255. / 2047.); + ++dst; + ++src; + } + } + + return indexed; +} + +bool QFontEngineDirectWrite::supportsSubPixelPositions() const +{ + return true; +} + +QImage QFontEngineDirectWrite::imageForGlyph(glyph_t t, + QFixed subPixelPosition, + int margin, + const QTransform &xform) +{ + glyph_metrics_t metrics = QFontEngine::boundingBox(t, xform); + int width = (metrics.width + margin * 2 + 4).ceil().toInt() ; + int height = (metrics.height + margin * 2 + 4).ceil().toInt(); + + UINT16 glyphIndex = t; + FLOAT glyphAdvance = metrics.xoff.toReal(); + + DWRITE_GLYPH_OFFSET glyphOffset; + glyphOffset.advanceOffset = 0; + glyphOffset.ascenderOffset = 0; + + DWRITE_GLYPH_RUN glyphRun; + glyphRun.fontFace = m_directWriteFontFace; + glyphRun.fontEmSize = fontDef.pixelSize; + glyphRun.glyphCount = 1; + glyphRun.glyphIndices = &glyphIndex; + glyphRun.glyphAdvances = &glyphAdvance; + glyphRun.isSideways = false; + glyphRun.bidiLevel = 0; + glyphRun.glyphOffsets = &glyphOffset; + + QFixed x = margin - metrics.x.round() + subPixelPosition; + QFixed y = margin - metrics.y.floor(); + + DWRITE_MATRIX transform; + transform.dx = x.toReal(); + transform.dy = y.toReal(); + transform.m11 = xform.m11(); + transform.m12 = xform.m12(); + transform.m21 = xform.m21(); + transform.m22 = xform.m22(); + + IDWriteGlyphRunAnalysis *glyphAnalysis = NULL; + HRESULT hr = m_directWriteFactory->CreateGlyphRunAnalysis( + &glyphRun, + 1.0f, + &transform, + DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC, + DWRITE_MEASURING_MODE_NATURAL, + 0.0, 0.0, + &glyphAnalysis + ); + + if (SUCCEEDED(hr)) { + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + + int size = width * height * 3; + BYTE *alphaValues = new BYTE[size]; + qMemSet(alphaValues, size, 0); + + hr = glyphAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1, + &rect, + alphaValues, + size); + + if (SUCCEEDED(hr)) { + QImage img(width, height, QImage::Format_RGB32); + img.fill(0xffffffff); + + for (int y=0; y<height; ++y) { + uint *dest = reinterpret_cast<uint *>(img.scanLine(y)); + BYTE *src = alphaValues + width * 3 * y; + + for (int x=0; x<width; ++x) { + dest[x] = *(src) << 16 + | *(src + 1) << 8 + | *(src + 2); + + src += 3; + } + } + + delete[] alphaValues; + glyphAnalysis->Release(); + + return img; + } else { + delete[] alphaValues; + glyphAnalysis->Release(); + + qErrnoWarning("QFontEngineDirectWrite::imageForGlyph: CreateAlphaTexture failed"); + } + + } else { + qErrnoWarning("QFontEngineDirectWrite::imageForGlyph: CreateGlyphRunAnalysis failed"); + } + + return QImage(); +} + +QImage QFontEngineDirectWrite::alphaRGBMapForGlyph(glyph_t t, + QFixed subPixelPosition, + int margin, + const QTransform &xform) +{ + QImage mask = imageForGlyph(t, subPixelPosition, margin, xform); + return mask.depth() == 32 + ? mask + : mask.convertToFormat(QImage::Format_RGB32); +} + +const char *QFontEngineDirectWrite::name() const +{ + return 0; +} + +bool QFontEngineDirectWrite::canRender(const QChar *string, int len) +{ + QVarLengthArray<UINT32> codePoints(len); + int actualLength = 0; + for (int i=0; i<len; ++i, actualLength++) + codePoints[actualLength] = getChar(string, i, len); + + QVarLengthArray<UINT16> glyphIndices(actualLength); + HRESULT hr = m_directWriteFontFace->GetGlyphIndices(codePoints.data(), actualLength, + glyphIndices.data()); + if (FAILED(hr)) { + qErrnoWarning(hr, "QFontEngineDirectWrite::canRender: GetGlyphIndices failed"); + return false; + } else { + for (int i=0; i<glyphIndices.size(); ++i) { + if (glyphIndices.at(i) == 0) + return false; + } + + return true; + } +} + +QFontEngine::Type QFontEngineDirectWrite::type() const +{ + return QFontEngine::DirectWrite; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DIRECTWRITE diff --git a/src/gui/text/qfontenginedirectwrite_p.h b/src/gui/text/qfontenginedirectwrite_p.h new file mode 100644 index 0000000000..c440a6ca65 --- /dev/null +++ b/src/gui/text/qfontenginedirectwrite_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINEDIRECTWRITE_H +#define QFONTENGINEDIRECTWRITE_H + +#ifndef QT_NO_DIRECTWRITE + +// +// 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/qfontengine_p.h" + +struct IDWriteFont ; +struct IDWriteFontFace ; +struct IDWriteFactory ; +struct IDWriteBitmapRenderTarget ; +struct IDWriteGdiInterop ; + +QT_BEGIN_NAMESPACE + +class QFontEngineDirectWrite : public QFontEngine +{ + Q_OBJECT +public: + explicit QFontEngineDirectWrite(IDWriteFactory *directWriteFactory, + IDWriteFontFace *directWriteFontFace, + qreal pixelSize); + ~QFontEngineDirectWrite(); + + QFixed lineThickness() const; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + QFixed emSquareSize() const; + + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const; + + void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox(glyph_t g); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + QFixed xHeight() const; + qreal maxCharWidth() const; + + const char *name() const; + + bool supportsSubPixelPositions() const; + + QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition); + QImage alphaRGBMapForGlyph(glyph_t t, QFixed subPixelPosition, int margin, + const QTransform &xform); + + bool canRender(const QChar *string, int len); + Type type() const; + +private: + friend class QRawFontPrivate; + + QImage imageForGlyph(glyph_t t, QFixed subPixelPosition, int margin, const QTransform &xform); + void collectMetrics(); + + IDWriteFontFace *m_directWriteFontFace; + IDWriteFactory *m_directWriteFactory; + IDWriteBitmapRenderTarget *m_directWriteBitmapRenderTarget; + + QFixed m_lineThickness; + int m_unitsPerEm; + QFixed m_ascent; + QFixed m_descent; + QFixed m_xHeight; + QFixed m_lineGap; + FaceId m_faceId; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_DIRECTWRITE + +#endif // QFONTENGINEDIRECTWRITE_H diff --git a/src/gui/text/qfontengineglyphcache_p.h b/src/gui/text/qfontengineglyphcache_p.h new file mode 100644 index 0000000000..8f8d93daf9 --- /dev/null +++ b/src/gui/text/qfontengineglyphcache_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINEGLYPHCACHE_P_H +#define QFONTENGINEGLYPHCACHE_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 "QtCore/qglobal.h" +#include "QtCore/qatomic.h" +#include <QtCore/qvarlengtharray.h> +#include "private/qfont_p.h" + +#ifdef Q_WS_WIN +# include "QtCore/qt_windows.h" +#endif + +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "QtCore/qmap.h" +# include "QtCore/qcache.h" +# include "private/qcore_mac_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QFontEngineGlyphCache: public QSharedData +{ +public: + enum Type { + Raster_RGBMask, + Raster_A8, + Raster_Mono + }; + + QFontEngineGlyphCache(const QTransform &matrix, Type type) : m_transform(matrix), m_type(type) { } + + virtual ~QFontEngineGlyphCache() { } + + Type cacheType() const { return m_type; } + + QTransform m_transform; + QFontEngineGlyphCache::Type m_type; +}; +typedef QHash<void *, QList<QFontEngineGlyphCache *> > GlyphPointerHash; +typedef QHash<int, QList<QFontEngineGlyphCache *> > GlyphIntHash; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/text/qfontinfo.h b/src/gui/text/qfontinfo.h new file mode 100644 index 0000000000..cc7db4acf4 --- /dev/null +++ b/src/gui/text/qfontinfo.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTINFO_H +#define QFONTINFO_H + +#include <QtGui/qfont.h> +#include <QtCore/qsharedpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QFontInfo +{ +public: + QFontInfo(const QFont &); + QFontInfo(const QFontInfo &); + ~QFontInfo(); + + QFontInfo &operator=(const QFontInfo &); + + QString family() const; + int pixelSize() const; + int pointSize() const; + qreal pointSizeF() const; + bool italic() const; + QFont::Style style() const; + int weight() const; + inline bool bold() const { return weight() > QFont::Normal; } + bool underline() const; + bool overline() const; + bool strikeOut() const; + bool fixedPitch() const; + QFont::StyleHint styleHint() const; + bool rawMode() const; + + bool exactMatch() const; + +private: + QExplicitlySharedDataPointer<QFontPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTINFO_H diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp new file mode 100644 index 0000000000..2ec245e5da --- /dev/null +++ b/src/gui/text/qfontmetrics.cpp @@ -0,0 +1,1819 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qpaintdevice.h" +#include "qfontmetrics.h" + +#include "qfont_p.h" +#include "qfontengine_p.h" +#include <private/qunicodetables_p.h> + +#include <math.h> + +#ifdef Q_WS_X11 +#include "qx11info_x11.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_X11 +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); +#endif + +extern void qt_format_text(const QFont& font, const QRectF &_r, + int tf, const QString &text, QRectF *brect, + int tabStops, int *tabArray, int tabArrayLen, + QPainter *painter); + +/***************************************************************************** + QFontMetrics member functions + *****************************************************************************/ + +/*! + \class QFontMetrics + \reentrant + + \brief The QFontMetrics class provides font metrics information. + + \ingroup painting + \ingroup shared + + QFontMetrics functions calculate the size of characters and + strings for a given font. There are three ways you can create a + QFontMetrics object: + + \list 1 + \o Calling the QFontMetrics constructor with a QFont creates a + font metrics object for a screen-compatible font, i.e. the font + cannot be a printer font. If the font is changed + later, the font metrics object is \e not updated. + + (Note: If you use a printer font the values returned may be + inaccurate. Printer fonts are not always accessible so the nearest + screen font is used if a printer font is supplied.) + + \o QWidget::fontMetrics() returns the font metrics for a widget's + font. This is equivalent to QFontMetrics(widget->font()). If the + widget's font is changed later, the font metrics object is \e not + updated. + + \o QPainter::fontMetrics() returns the font metrics for a + painter's current font. If the painter's font is changed later, the + font metrics object is \e not updated. + \endlist + + Once created, the object provides functions to access the + individual metrics of the font, its characters, and for strings + rendered in the font. + + There are several functions that operate on the font: ascent(), + descent(), height(), leading() and lineSpacing() return the basic + size properties of the font. The underlinePos(), overlinePos(), + strikeOutPos() and lineWidth() functions, return the properties of + the line that underlines, overlines or strikes out the + characters. These functions are all fast. + + There are also some functions that operate on the set of glyphs in + the font: minLeftBearing(), minRightBearing() and maxWidth(). + These are by necessity slow, and we recommend avoiding them if + possible. + + For each character, you can get its width(), leftBearing() and + rightBearing() and find out whether it is in the font using + inFont(). You can also treat the character as a string, and use + the string functions on it. + + The string functions include width(), to return the width of a + string in pixels (or points, for a printer), boundingRect(), to + return a rectangle large enough to contain the rendered string, + and size(), to return the size of that rectangle. + + Example: + \snippet doc/src/snippets/code/src_gui_text_qfontmetrics.cpp 0 + + \sa QFont, QFontInfo, QFontDatabase, QFontComboBox, {Character Map Example} +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(int x, int y, int width, int height, + int flags, const QString &text, int tabStops, int *tabArray) const + \overload + + Returns the bounding rectangle for the given \a text within the + rectangle specified by the \a x and \a y coordinates, \a width, and + \a height. + + If Qt::TextExpandTabs is set in \a flags and \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise, if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). +*/ + +/*! + Constructs a font metrics object for \a font. + + The font metrics will be compatible with the paintdevice used to + create \a font. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QFontMetrics(const QFont &, QPaintDevice *) to get the font + metrics that are compatible with a certain paint device. +*/ +QFontMetrics::QFontMetrics(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a font metrics object for \a font and \a paintdevice. + + The font metrics will be compatible with the paintdevice passed. + If the \a paintdevice is 0, the metrics will be screen-compatible, + ie. the metrics you get if you use the font for drawing text on a + \link QWidget widgets\endlink or \link QPixmap pixmaps\endlink, + not on a QPicture or QPrinter. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. +*/ +QFontMetrics::QFontMetrics(const QFont &font, QPaintDevice *paintdevice) +{ + int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(paintdevice); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } + +} + +/*! + Constructs a copy of \a fm. +*/ +QFontMetrics::QFontMetrics(const QFontMetrics &fm) + : d(fm.d.data()) +{ +} + +/*! + Destroys the font metrics object and frees all allocated + resources. +*/ +QFontMetrics::~QFontMetrics() +{ +} + +/*! + Assigns the font metrics \a fm. +*/ +QFontMetrics &QFontMetrics::operator=(const QFontMetrics &fm) +{ + d = fm.d.data(); + return *this; +} + +/*! + \overload + Returns true if \a other is equal to this object; otherwise + returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator!=() +*/ +bool QFontMetrics::operator ==(const QFontMetrics &other) const +{ + return d == other.d; +} + +/*! + Returns true if \a other is equal to this object; otherwise + returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator!=() +*/ +bool QFontMetrics::operator ==(const QFontMetrics &other) +{ + return d == other.d; +} + +/*! + \fn bool QFontMetrics::operator!=(const QFontMetrics &other) + + Returns true if \a other is not equal to this object; otherwise returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator==() +*/ + +/*! + \fn bool QFontMetrics::operator !=(const QFontMetrics &other) const + + Returns true if \a other is not equal to this object; otherwise returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator==() +*/ + +/*! + Returns the ascent of the font. + + The ascent of a font is the distance from the baseline to the + highest position characters extend to. In practice, some font + designers break this rule, e.g. when they put more than one accent + on top of a character, or to accommodate an unusual character in + an exotic language, so it is possible (though rare) that this + value will be too small. + + \sa descent() +*/ +int QFontMetrics::ascent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->ascent()); +} + + +/*! + Returns the descent of the font. + + The descent is the distance from the base line to the lowest point + characters extend to. In practice, some font designers break this rule, + e.g. to accommodate an unusual character in an exotic language, so + it is possible (though rare) that this value will be too small. + + \sa ascent() +*/ +int QFontMetrics::descent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->descent()); +} + +/*! + Returns the height of the font. + + This is always equal to ascent()+descent()+1 (the 1 is for the + base line). + + \sa leading(), lineSpacing() +*/ +int QFontMetrics::height() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->ascent()) + qRound(engine->descent()) + 1; +} + +/*! + Returns the leading of the font. + + This is the natural inter-line spacing. + + \sa height(), lineSpacing() +*/ +int QFontMetrics::leading() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->leading()); +} + +/*! + Returns the distance from one base line to the next. + + This value is always equal to leading()+height(). + + \sa height(), leading() +*/ +int QFontMetrics::lineSpacing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->leading()) + qRound(engine->ascent()) + qRound(engine->descent()) + 1; +} + +/*! + Returns the minimum left bearing of the font. + + This is the smallest leftBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minRightBearing(), leftBearing() +*/ +int QFontMetrics::minLeftBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->minLeftBearing()); +} + +/*! + Returns the minimum right bearing of the font. + + This is the smallest rightBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minLeftBearing(), rightBearing() +*/ +int QFontMetrics::minRightBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->minRightBearing()); +} + +/*! + Returns the width of the widest character in the font. +*/ +int QFontMetrics::maxWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->maxCharWidth()); +} + +/*! + Returns the 'x' height of the font. This is often but not always + the same as the height of the character 'x'. +*/ +int QFontMetrics::xHeight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (d->capital == QFont::SmallCaps) + return qRound(d->smallCapsFontPrivate()->engineForScript(QUnicodeTables::Common)->ascent()); + return qRound(engine->xHeight()); +} + +/*! + \since 4.2 + + Returns the average width of glyphs in the font. +*/ +int QFontMetrics::averageCharWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->averageCharWidth()); +} + +/*! + Returns true if character \a ch is a valid character in the font; + otherwise returns false. +*/ +bool QFontMetrics::inFont(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + return engine->canRender(&ch, 1); +} + +/*! + Returns true if the character encoded in UCS-4/UTF-32 is a valid + character in the font; otherwise returns false. +*/ +bool QFontMetrics::inFontUcs4(uint ucs4) const +{ + const int script = QUnicodeTables::script(ucs4); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + QString utf16 = QString::fromUcs4(&ucs4, 1); + return engine->canRender(utf16.data(), utf16.length()); +} + +/*! + Returns the left bearing of character \a ch in the font. + + The left bearing is the right-ward distance of the left-most pixel + of the character from the logical origin of the character. This + value is negative if the pixels of the character extend to the + left of the logical origin. + + See width(QChar) for a graphical description of this metric. + + \sa rightBearing(), minLeftBearing(), width() +*/ +int QFontMetrics::leftBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal lb; + engine->getGlyphBearings(glyphs.glyphs[0], &lb); + return qRound(lb); +} + +/*! + Returns the right bearing of character \a ch in the font. + + The right bearing is the left-ward distance of the right-most + pixel of the character from the logical origin of a subsequent + character. This value is negative if the pixels of the character + extend to the right of the width() of the character. + + See width() for a graphical description of this metric. + + \sa leftBearing(), minRightBearing(), width() +*/ +int QFontMetrics::rightBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal rb; + engine->getGlyphBearings(glyphs.glyphs[0], 0, &rb); + return qRound(rb); +} + +/*! + Returns the width in pixels of the first \a len characters of \a + text. If \a len is negative (the default), the entire string is + used. + + Note that this value is \e not equal to boundingRect().width(); + boundingRect() returns a rectangle describing the pixels this + string will cover whereas width() returns the distance to where + the next string should be drawn. + + \sa boundingRect() +*/ +int QFontMetrics::width(const QString &text, int len) const +{ + return width(text, len, 0); +} + +/*! + \internal +*/ +int QFontMetrics::width(const QString &text, int len, int flags) const +{ + int pos = text.indexOf(QLatin1Char('\x9c')); + if (pos != -1) { + len = (len < 0) ? pos : qMin(pos, len); + } else if (len < 0) { + len = text.length(); + } + if (len == 0) + return 0; + + if (flags & Qt::TextBypassShaping) { + // Skip harfbuzz complex shaping, only use advances + int numGlyphs = len; + QVarLengthGlyphLayoutArray glyphs(numGlyphs); + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (!engine->stringToCMap(text.data(), len, &glyphs, &numGlyphs, 0)) { + glyphs.resize(numGlyphs); + if (!engine->stringToCMap(text.data(), len, &glyphs, &numGlyphs, 0)) + Q_ASSERT_X(false, Q_FUNC_INFO, "stringToCMap shouldn't fail twice"); + } + + QFixed width; + for (int i = 0; i < numGlyphs; ++i) + width += glyphs.advances_x[i]; + return qRound(width); + } + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + return qRound(layout.width(0, len)); +} + +/*! + \overload + + \img bearings.png Bearings + + Returns the logical width of character \a ch in pixels. This is a + distance appropriate for drawing a subsequent character after \a + ch. + + Some of the metrics are described in the image to the right. The + central dark rectangles cover the logical width() of each + character. The outer pale rectangles cover the leftBearing() and + rightBearing() of each character. Notice that the bearings of "f" + in this particular font are both negative, while the bearings of + "o" are both positive. + + \warning This function will produce incorrect results for Arabic + characters or non-spacing marks in the middle of a string, as the + glyph shaping and positioning of marks that happens when + processing strings cannot be taken into account. When implementing + an interactive text control, use QTextLayout instead. + + \sa boundingRect() +*/ +int QFontMetrics::width(QChar ch) const +{ + if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) + return 0; + + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + return qRound(glyphs.advances_x[0]); +} + +/*! \obsolete + + Returns the width of the character at position \a pos in the + string \a text. + + The whole string is needed, as the glyph drawn may change + depending on the context (the letter before and after the current + one) for some languages (e.g. Arabic). + + This function also takes non spacing marks and ligatures into + account. +*/ +int QFontMetrics::charWidth(const QString &text, int pos) const +{ + if (pos < 0 || pos > (int)text.length()) + return 0; + + QChar ch = text.unicode()[pos]; + const int script = QUnicodeTables::script(ch); + int width; + + if (script != QUnicodeTables::Common) { + // complex script shaping. Have to do some hard work + int from = qMax(0, pos - 8); + int to = qMin(text.length(), pos + 8); + QString cstr = QString::fromRawData(text.unicode() + from, to - from); + QStackTextEngine layout(cstr, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + width = qRound(layout.width(pos-from, 1)); + } else if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) { + width = 0; + } else { + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + width = qRound(glyphs.advances_x[0]); + } + return width; +} + +/*! + Returns the bounding rectangle of the characters in the string + specified by \a text. The bounding rectangle always covers at least + the set of pixels the text would cover if drawn at (0, 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + The height of the bounding rectangle is at least as large as the + value returned by height(). + + \sa width(), height(), QPainter::boundingRect(), tightBoundingRect() +*/ +QRect QFontMetrics::boundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.boundingBox(0, text.length()); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + +/*! + Returns the rectangle that is covered by ink if character \a ch + were to be drawn at the origin of the coordinate system. + + Note that the bounding rectangle may extend to the left of (0, 0) + (e.g., for italicized fonts), and that the text output may cover \e + all pixels in the bounding rectangle. For a space character the rectangle + will usually be empty. + + Note that the rectangle usually extends both above and below the + base line. + + \warning The width of the returned rectangle is not the advance width + of the character. Use boundingRect(const QString &) or width() instead. + + \sa width() +*/ +QRect QFontMetrics::boundingRect(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t gm = engine->boundingBox(glyphs.glyphs[0]); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + +/*! + \overload + + Returns the bounding rectangle of the characters in the string + specified by \a text, which is the set of pixels the text would + cover if drawn at (0, 0). The drawing, and hence the bounding + rectangle, is constrained to the rectangle \a rect. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::AlignLeft aligns to the left border, except for + Arabic and Hebrew where it aligns to the right. + \o Qt::AlignRight aligns to the right border, except for + Arabic and Hebrew where it aligns to the left. + \o Qt::AlignJustify produces justified text. + \o Qt::AlignHCenter aligns horizontally centered. + \o Qt::AlignTop aligns to the top border. + \o Qt::AlignBottom aligns to the bottom border. + \o Qt::AlignVCenter aligns vertically centered + \o Qt::AlignCenter (== \c{Qt::AlignHCenter | Qt::AlignVCenter}) + \o Qt::TextSingleLine ignores newline characters in the text. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordWrap breaks the text to fit the rectangle. + \endlist + + Qt::Horizontal alignment defaults to Qt::AlignLeft and vertical + alignment defaults to Qt::AlignTop. + + If several of the horizontal or several of the vertical alignment + flags are set, the resulting alignment is undefined. + + If Qt::TextExpandTabs is set in \a flags, then: if \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the text output may cover \e + all pixels in the bounding rectangle. + + Newline characters are processed as linebreaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + The bounding rectangle returned by this function is somewhat larger + than that calculated by the simpler boundingRect() function. This + function uses the \link minLeftBearing() maximum left \endlink and + \link minRightBearing() right \endlink font bearings as is + necessary for multi-line text to align correctly. Also, + fontHeight() and lineSpacing() are used to calculate the height, + rather than individual character heights. + + \sa width(), QPainter::boundingRect(), Qt::Alignment +*/ +QRect QFontMetrics::boundingRect(const QRect &rect, int flags, const QString &text, int tabStops, + int *tabArray) const +{ + int tabArrayLen = 0; + if (tabArray) + while (tabArray[tabArrayLen]) + tabArrayLen++; + + QRectF rb; + QRectF rr(rect); + qt_format_text(QFont(d.data()), rr, flags | Qt::TextDontPrint, text, &rb, tabStops, tabArray, + tabArrayLen, 0); + + return rb.toAlignedRect(); +} + +/*! + Returns the size in pixels of \a text. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::TextSingleLine ignores newline characters. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordBreak breaks the text to fit the rectangle. + \endlist + + If Qt::TextExpandTabs is set in \a flags, then: if \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). + + Newline characters are processed as linebreaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + \sa boundingRect() +*/ +QSize QFontMetrics::size(int flags, const QString &text, int tabStops, int *tabArray) const +{ + return boundingRect(QRect(0,0,0,0), flags | Qt::TextLongestVariant, text, tabStops, tabArray).size(); +} + +/*! + \since 4.3 + + Returns a tight bounding rectangle around the characters in the + string specified by \a text. The bounding rectangle always covers + at least the set of pixels the text would cover if drawn at (0, + 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + \warning Calling this method is very slow on Windows. + + \sa width(), height(), boundingRect() +*/ +QRect QFontMetrics::tightBoundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.tightBoundingBox(0, text.length()); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + + +/*! + \since 4.2 + + If the string \a text is wider than \a width, returns an elided + version of the string (i.e., a string with "..." in it). + Otherwise, returns the original string. + + The \a mode parameter specifies whether the text is elided on the + left (e.g., "...tech"), in the middle (e.g., "Tr...ch"), or on + the right (e.g., "Trol..."). + + The \a width is specified in pixels, not characters. + + The \a flags argument is optional and currently only supports + Qt::TextShowMnemonic as value. + + The elide mark will follow the \l{Qt::LayoutDirection}{layout + direction}; it will be on the right side of the text for + right-to-left layouts, and on the left side for right-to-left + layouts. Note that this behavior is independent of the text + language. +*/ +QString QFontMetrics::elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const +{ + QString _text = text; + if (!(flags & Qt::TextLongestVariant)) { + int posA = 0; + int posB = _text.indexOf(QLatin1Char('\x9c')); + while (posB >= 0) { + QString portion = _text.mid(posA, posB - posA); + if (size(flags, portion).width() <= width) + return portion; + posA = posB + 1; + posB = _text.indexOf(QLatin1Char('\x9c'), posA); + } + _text = _text.mid(posA); + } + QStackTextEngine engine(_text, QFont(d.data())); + return engine.elidedText(mode, width, flags); +} + +/*! + Returns the distance from the base line to where an underscore + should be drawn. + + \sa overlinePos(), strikeOutPos(), lineWidth() +*/ +int QFontMetrics::underlinePos() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->underlinePosition()); +} + +/*! + Returns the distance from the base line to where an overline + should be drawn. + + \sa underlinePos(), strikeOutPos(), lineWidth() +*/ +int QFontMetrics::overlinePos() const +{ + return ascent() + 1; +} + +/*! + Returns the distance from the base line to where the strikeout + line should be drawn. + + \sa underlinePos(), overlinePos(), lineWidth() +*/ +int QFontMetrics::strikeOutPos() const +{ + int pos = ascent() / 3; + return pos > 0 ? pos : 1; +} + +/*! + Returns the width of the underline and strikeout lines, adjusted + for the point size of the font. + + \sa underlinePos(), overlinePos(), strikeOutPos() +*/ +int QFontMetrics::lineWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->lineThickness()); +} + + + + +/***************************************************************************** + QFontMetricsF member functions + *****************************************************************************/ + +/*! + \class QFontMetricsF + \reentrant + + \brief The QFontMetricsF class provides font metrics information. + + \ingroup painting + \ingroup shared + + QFontMetricsF functions calculate the size of characters and + strings for a given font. You can construct a QFontMetricsF object + with an existing QFont to obtain metrics for that font. If the + font is changed later, the font metrics object is \e not updated. + + Once created, the object provides functions to access the + individual metrics of the font, its characters, and for strings + rendered in the font. + + There are several functions that operate on the font: ascent(), + descent(), height(), leading() and lineSpacing() return the basic + size properties of the font. The underlinePos(), overlinePos(), + strikeOutPos() and lineWidth() functions, return the properties of + the line that underlines, overlines or strikes out the + characters. These functions are all fast. + + There are also some functions that operate on the set of glyphs in + the font: minLeftBearing(), minRightBearing() and maxWidth(). + These are by necessity slow, and we recommend avoiding them if + possible. + + For each character, you can get its width(), leftBearing() and + rightBearing() and find out whether it is in the font using + inFont(). You can also treat the character as a string, and use + the string functions on it. + + The string functions include width(), to return the width of a + string in pixels (or points, for a printer), boundingRect(), to + return a rectangle large enough to contain the rendered string, + and size(), to return the size of that rectangle. + + Example: + \snippet doc/src/snippets/code/src_gui_text_qfontmetrics.cpp 1 + + \sa QFont QFontInfo QFontDatabase +*/ + +/*! + \since 4.2 + + Constructs a font metrics object with floating point precision + from the given \a fontMetrics object. +*/ +QFontMetricsF::QFontMetricsF(const QFontMetrics &fontMetrics) + : d(fontMetrics.d.data()) +{ +} + +/*! + \since 4.2 + + Assigns \a other to this object. +*/ +QFontMetricsF &QFontMetricsF::operator=(const QFontMetrics &other) +{ + d = other.d.data(); + return *this; +} + +/*! + Constructs a font metrics object for \a font. + + The font metrics will be compatible with the paintdevice used to + create \a font. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QFontMetricsF(const QFont &, QPaintDevice *) to get the font + metrics that are compatible with a certain paint device. +*/ +QFontMetricsF::QFontMetricsF(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a font metrics object for \a font and \a paintdevice. + + The font metrics will be compatible with the paintdevice passed. + If the \a paintdevice is 0, the metrics will be screen-compatible, + ie. the metrics you get if you use the font for drawing text on a + \link QWidget widgets\endlink or \link QPixmap pixmaps\endlink, + not on a QPicture or QPrinter. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. +*/ +QFontMetricsF::QFontMetricsF(const QFont &font, QPaintDevice *paintdevice) +{ + int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(paintdevice); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } + +} + +/*! + Constructs a copy of \a fm. +*/ +QFontMetricsF::QFontMetricsF(const QFontMetricsF &fm) + : d(fm.d.data()) +{ +} + +/*! + Destroys the font metrics object and frees all allocated + resources. +*/ +QFontMetricsF::~QFontMetricsF() +{ +} + +/*! + Assigns the font metrics \a fm to this font metrics object. +*/ +QFontMetricsF &QFontMetricsF::operator=(const QFontMetricsF &fm) +{ + d = fm.d.data(); + return *this; +} + +/*! + \overload + Returns true if the font metrics are equal to the \a other font + metrics; otherwise returns false. + + Two font metrics are considered equal if they were constructed from the + same QFont and the paint devices they were constructed for are + considered to be compatible. +*/ +bool QFontMetricsF::operator ==(const QFontMetricsF &other) const +{ + return d == other.d; +} + +/*! + Returns true if the font metrics are equal to the \a other font + metrics; otherwise returns false. + + Two font metrics are considered equal if they were constructed from the + same QFont and the paint devices they were constructed for are + considered to be compatible. +*/ +bool QFontMetricsF::operator ==(const QFontMetricsF &other) +{ + return d == other.d; +} + +/*! + \fn bool QFontMetricsF::operator!=(const QFontMetricsF &other) + + Returns true if the font metrics are not equal to the \a other font + metrics; otherwise returns false. + + \sa operator==() +*/ + +/*! + \fn bool QFontMetricsF::operator !=(const QFontMetricsF &other) const + \overload + + Returns true if the font metrics are not equal to the \a other font + metrics; otherwise returns false. + + \sa operator==() +*/ + +/*! + Returns the ascent of the font. + + The ascent of a font is the distance from the baseline to the + highest position characters extend to. In practice, some font + designers break this rule, e.g. when they put more than one accent + on top of a character, or to accommodate an unusual character in + an exotic language, so it is possible (though rare) that this + value will be too small. + + \sa descent() +*/ +qreal QFontMetricsF::ascent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->ascent().toReal(); +} + + +/*! + Returns the descent of the font. + + The descent is the distance from the base line to the lowest point + characters extend to. (Note that this is different from X, which + adds 1 pixel.) In practice, some font designers break this rule, + e.g. to accommodate an unusual character in an exotic language, so + it is possible (though rare) that this value will be too small. + + \sa ascent() +*/ +qreal QFontMetricsF::descent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->descent().toReal(); +} + +/*! + Returns the height of the font. + + This is always equal to ascent()+descent()+1 (the 1 is for the + base line). + + \sa leading(), lineSpacing() +*/ +qreal QFontMetricsF::height() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + + return (engine->ascent() + engine->descent() + 1).toReal(); +} + +/*! + Returns the leading of the font. + + This is the natural inter-line spacing. + + \sa height(), lineSpacing() +*/ +qreal QFontMetricsF::leading() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->leading().toReal(); +} + +/*! + Returns the distance from one base line to the next. + + This value is always equal to leading()+height(). + + \sa height(), leading() +*/ +qreal QFontMetricsF::lineSpacing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (engine->leading() + engine->ascent() + engine->descent() + 1).toReal(); +} + +/*! + Returns the minimum left bearing of the font. + + This is the smallest leftBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minRightBearing(), leftBearing() +*/ +qreal QFontMetricsF::minLeftBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->minLeftBearing(); +} + +/*! + Returns the minimum right bearing of the font. + + This is the smallest rightBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minLeftBearing(), rightBearing() +*/ +qreal QFontMetricsF::minRightBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->minRightBearing(); +} + +/*! + Returns the width of the widest character in the font. +*/ +qreal QFontMetricsF::maxWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->maxCharWidth(); +} + +/*! + Returns the 'x' height of the font. This is often but not always + the same as the height of the character 'x'. +*/ +qreal QFontMetricsF::xHeight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (d->capital == QFont::SmallCaps) + return d->smallCapsFontPrivate()->engineForScript(QUnicodeTables::Common)->ascent().toReal(); + return engine->xHeight().toReal(); +} + +/*! + \since 4.2 + + Returns the average width of glyphs in the font. +*/ +qreal QFontMetricsF::averageCharWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->averageCharWidth().toReal(); +} + +/*! + Returns true if character \a ch is a valid character in the font; + otherwise returns false. +*/ +bool QFontMetricsF::inFont(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + return engine->canRender(&ch, 1); +} + +/*! + Returns true if the character encoded in UCS-4/UTF-32 is a valid + character in the font; otherwise returns false. +*/ +bool QFontMetricsF::inFontUcs4(uint ucs4) const +{ + const int script = QUnicodeTables::script(ucs4); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + QString utf16 = QString::fromUcs4(&ucs4, 1); + return engine->canRender(utf16.data(), utf16.length()); +} + +/*! + Returns the left bearing of character \a ch in the font. + + The left bearing is the right-ward distance of the left-most pixel + of the character from the logical origin of the character. This + value is negative if the pixels of the character extend to the + left of the logical origin. + + See width(QChar) for a graphical description of this metric. + + \sa rightBearing(), minLeftBearing(), width() +*/ +qreal QFontMetricsF::leftBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal lb; + engine->getGlyphBearings(glyphs.glyphs[0], &lb); + return lb; +} + +/*! + Returns the right bearing of character \a ch in the font. + + The right bearing is the left-ward distance of the right-most + pixel of the character from the logical origin of a subsequent + character. This value is negative if the pixels of the character + extend to the right of the width() of the character. + + See width() for a graphical description of this metric. + + \sa leftBearing(), minRightBearing(), width() +*/ +qreal QFontMetricsF::rightBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal rb; + engine->getGlyphBearings(glyphs.glyphs[0], 0, &rb); + return rb; + +} + +/*! + Returns the width in pixels of the characters in the given \a text. + + Note that this value is \e not equal to the width returned by + boundingRect().width() because boundingRect() returns a rectangle + describing the pixels this string will cover whereas width() + returns the distance to where the next string should be drawn. + + \sa boundingRect() +*/ +qreal QFontMetricsF::width(const QString &text) const +{ + int pos = text.indexOf(QLatin1Char('\x9c')); + int len = (pos != -1) ? pos : text.length(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + return layout.width(0, len).toReal(); +} + +/*! + \overload + + \img bearings.png Bearings + + Returns the logical width of character \a ch in pixels. This is a + distance appropriate for drawing a subsequent character after \a + ch. + + Some of the metrics are described in the image to the right. The + central dark rectangles cover the logical width() of each + character. The outer pale rectangles cover the leftBearing() and + rightBearing() of each character. Notice that the bearings of "f" + in this particular font are both negative, while the bearings of + "o" are both positive. + + \warning This function will produce incorrect results for Arabic + characters or non-spacing marks in the middle of a string, as the + glyph shaping and positioning of marks that happens when + processing strings cannot be taken into account. When implementing + an interactive text control, use QTextLayout instead. + + \sa boundingRect() +*/ +qreal QFontMetricsF::width(QChar ch) const +{ + if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) + return 0.; + + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + return glyphs.advances_x[0].toReal(); +} + +/*! + Returns the bounding rectangle of the characters in the string + specified by \a text. The bounding rectangle always covers at least + the set of pixels the text would cover if drawn at (0, 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + The height of the bounding rectangle is at least as large as the + value returned height(). + + \sa width(), height(), QPainter::boundingRect() +*/ +QRectF QFontMetricsF::boundingRect(const QString &text) const +{ + int len = text.length(); + if (len == 0) + return QRectF(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.boundingBox(0, len); + return QRectF(gm.x.toReal(), gm.y.toReal(), + gm.width.toReal(), gm.height.toReal()); +} + +/*! + Returns the bounding rectangle of the character \a ch relative to + the left-most point on the base line. + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the text output may cover \e + all pixels in the bounding rectangle. + + Note that the rectangle usually extends both above and below the + base line. + + \sa width() +*/ +QRectF QFontMetricsF::boundingRect(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t gm = engine->boundingBox(glyphs.glyphs[0]); + return QRectF(gm.x.toReal(), gm.y.toReal(), gm.width.toReal(), gm.height.toReal()); +} + +/*! + \overload + + Returns the bounding rectangle of the characters in the given \a text. + This is the set of pixels the text would cover if drawn when constrained + to the bounding rectangle specified by \a rect. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::AlignLeft aligns to the left border, except for + Arabic and Hebrew where it aligns to the right. + \o Qt::AlignRight aligns to the right border, except for + Arabic and Hebrew where it aligns to the left. + \o Qt::AlignJustify produces justified text. + \o Qt::AlignHCenter aligns horizontally centered. + \o Qt::AlignTop aligns to the top border. + \o Qt::AlignBottom aligns to the bottom border. + \o Qt::AlignVCenter aligns vertically centered + \o Qt::AlignCenter (== \c{Qt::AlignHCenter | Qt::AlignVCenter}) + \o Qt::TextSingleLine ignores newline characters in the text. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordWrap breaks the text to fit the rectangle. + \endlist + + Qt::Horizontal alignment defaults to Qt::AlignLeft and vertical + alignment defaults to Qt::AlignTop. + + If several of the horizontal or several of the vertical alignment + flags are set, the resulting alignment is undefined. + + These flags are defined in \l{Qt::AlignmentFlag}. + + If Qt::TextExpandTabs is set in \a flags, the following behavior is + used to interpret tab characters in the text: + \list + \o If \a tabArray is non-null, it specifies a 0-terminated sequence of + pixel-positions for tabs in the text. + \o If \a tabStops is non-zero, it is used as the tab spacing (in pixels). + \endlist + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts. + + Newline characters are processed as line breaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + The bounding rectangle returned by this function is somewhat larger + than that calculated by the simpler boundingRect() function. This + function uses the \link minLeftBearing() maximum left \endlink and + \link minRightBearing() right \endlink font bearings as is + necessary for multi-line text to align correctly. Also, + fontHeight() and lineSpacing() are used to calculate the height, + rather than individual character heights. + + \sa width(), QPainter::boundingRect(), Qt::Alignment +*/ +QRectF QFontMetricsF::boundingRect(const QRectF &rect, int flags, const QString& text, + int tabStops, int *tabArray) const +{ + int tabArrayLen = 0; + if (tabArray) + while (tabArray[tabArrayLen]) + tabArrayLen++; + + QRectF rb; + qt_format_text(QFont(d.data()), rect, flags | Qt::TextDontPrint, text, &rb, tabStops, tabArray, + tabArrayLen, 0); + return rb; +} + +/*! + Returns the size in pixels of the characters in the given \a text. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::TextSingleLine ignores newline characters. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordBreak breaks the text to fit the rectangle. + \endlist + + These flags are defined in \l{Qt::TextFlags}. + + If Qt::TextExpandTabs is set in \a flags, the following behavior is + used to interpret tab characters in the text: + \list + \o If \a tabArray is non-null, it specifies a 0-terminated sequence of + pixel-positions for tabs in the text. + \o If \a tabStops is non-zero, it is used as the tab spacing (in pixels). + \endlist + + Newline characters are processed as line breaks. + + Note: Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + \sa boundingRect() +*/ +QSizeF QFontMetricsF::size(int flags, const QString &text, int tabStops, int *tabArray) const +{ + return boundingRect(QRectF(), flags | Qt::TextLongestVariant, text, tabStops, tabArray).size(); +} + +/*! + \since 4.3 + + Returns a tight bounding rectangle around the characters in the + string specified by \a text. The bounding rectangle always covers + at least the set of pixels the text would cover if drawn at (0, + 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + \warning Calling this method is very slow on Windows. + + \sa width(), height(), boundingRect() +*/ +QRectF QFontMetricsF::tightBoundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.tightBoundingBox(0, text.length()); + return QRectF(gm.x.toReal(), gm.y.toReal(), gm.width.toReal(), gm.height.toReal()); +} + +/*! + \since 4.2 + + If the string \a text is wider than \a width, returns an elided + version of the string (i.e., a string with "..." in it). + Otherwise, returns the original string. + + The \a mode parameter specifies whether the text is elided on the + left (e.g., "...tech"), in the middle (e.g., "Tr...ch"), or on + the right (e.g., "Trol..."). + + The \a width is specified in pixels, not characters. + + The \a flags argument is optional and currently only supports + Qt::TextShowMnemonic as value. +*/ +QString QFontMetricsF::elidedText(const QString &text, Qt::TextElideMode mode, qreal width, int flags) const +{ + QString _text = text; + if (!(flags & Qt::TextLongestVariant)) { + int posA = 0; + int posB = _text.indexOf(QLatin1Char('\x9c')); + while (posB >= 0) { + QString portion = _text.mid(posA, posB - posA); + if (size(flags, portion).width() <= width) + return portion; + posA = posB + 1; + posB = _text.indexOf(QLatin1Char('\x9c'), posA); + } + _text = _text.mid(posA); + } + QStackTextEngine engine(_text, QFont(d.data())); + return engine.elidedText(mode, QFixed::fromReal(width), flags); +} + +/*! + Returns the distance from the base line to where an underscore + should be drawn. + + \sa overlinePos(), strikeOutPos(), lineWidth() +*/ +qreal QFontMetricsF::underlinePos() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->underlinePosition().toReal(); +} + +/*! + Returns the distance from the base line to where an overline + should be drawn. + + \sa underlinePos(), strikeOutPos(), lineWidth() +*/ +qreal QFontMetricsF::overlinePos() const +{ + return ascent() + 1; +} + +/*! + Returns the distance from the base line to where the strikeout + line should be drawn. + + \sa underlinePos(), overlinePos(), lineWidth() +*/ +qreal QFontMetricsF::strikeOutPos() const +{ + return ascent() / 3.; +} + +/*! + Returns the width of the underline and strikeout lines, adjusted + for the point size of the font. + + \sa underlinePos(), overlinePos(), strikeOutPos() +*/ +qreal QFontMetricsF::lineWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->lineThickness().toReal(); +} + +/*! + \fn QSize QFontMetrics::size(int flags, const QString &text, int len, + int tabStops, int *tabArray) const + \compat + + Use the size() function in combination with QString::left() + instead. + + \oldcode + QSize size = size(flags, str, len, tabstops, tabarray); + \newcode + QSize size = size(flags, str.left(len), tabstops, tabarray); + \endcode +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(int x, int y, int w, int h, int flags, + const QString& text, int len, int tabStops, int *tabArray) const + \compat + + Use the boundingRect() function in combination with + QString::left() and a QRect constructor instead. + + \oldcode + QRect rect = boundingRect(x, y, w, h , flags, text, len, + tabStops, tabArray); + \newcode + QRect rect = boundingRect(QRect(x, y, w, h), flags, text.left(len), + tabstops, tabarray); + \endcode + +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(const QString &text, int len) const + \compat + + Use the boundingRect() function in combination with + QString::left() instead. + + \oldcode + QRect rect = boundingRect(text, len); + \newcode + QRect rect = boundingRect(text.left(len)); + \endcode +*/ + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h new file mode 100644 index 0000000000..5fe8676993 --- /dev/null +++ b/src/gui/text/qfontmetrics.h @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTMETRICS_H +#define QFONTMETRICS_H + +#include <QtGui/qfont.h> +#include <QtCore/qsharedpointer.h> +#ifndef QT_INCLUDE_COMPAT +#include <QtCore/qrect.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifdef Q_WS_QWS +class QFontEngine; +#endif + +class QTextCodec; +class QRect; + + +class Q_GUI_EXPORT QFontMetrics +{ +public: + QFontMetrics(const QFont &); + QFontMetrics(const QFont &, QPaintDevice *pd); + QFontMetrics(const QFontMetrics &); + ~QFontMetrics(); + + QFontMetrics &operator=(const QFontMetrics &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QFontMetrics &operator=(QFontMetrics &&other) + { qSwap(d, other.d); return *this; } +#endif + + int ascent() const; + int descent() const; + int height() const; + int leading() const; + int lineSpacing() const; + int minLeftBearing() const; + int minRightBearing() const; + int maxWidth() const; + + int xHeight() const; + int averageCharWidth() const; + + bool inFont(QChar) const; + bool inFontUcs4(uint ucs4) const; + + int leftBearing(QChar) const; + int rightBearing(QChar) const; + int width(const QString &, int len = -1) const; + int width(const QString &, int len, int flags) const; + + int width(QChar) const; + int charWidth(const QString &str, int pos) const; + + QRect boundingRect(QChar) const; + + QRect boundingRect(const QString &text) const; + QRect boundingRect(const QRect &r, int flags, const QString &text, int tabstops=0, int *tabarray=0) const; + inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text, + int tabstops=0, int *tabarray=0) const + { return boundingRect(QRect(x, y, w, h), flags, text, tabstops, tabarray); } + QSize size(int flags, const QString& str, int tabstops=0, int *tabarray=0) const; + + QRect tightBoundingRect(const QString &text) const; + + QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags = 0) const; + + int underlinePos() const; + int overlinePos() const; + int strikeOutPos() const; + int lineWidth() const; + + bool operator==(const QFontMetrics &other); // 5.0 - remove me + bool operator==(const QFontMetrics &other) const; + inline bool operator !=(const QFontMetrics &other) { return !operator==(other); } // 5.0 - remove me + inline bool operator !=(const QFontMetrics &other) const { return !operator==(other); } + +#ifdef QT3_SUPPORT + inline QRect boundingRect(const QString &text, int len) const + { return boundingRect(text.left(len)); } + inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString& str, int len, + int tabstops=0, int *tabarray=0) const + { return boundingRect(QRect(x, y, w, h), flags, str.left(len), tabstops, tabarray); } + inline QSize size(int flags, const QString& str, int len, int tabstops=0, int *tabarray=0) const + { return size(flags, str.left(len), tabstops, tabarray); } +#endif +private: +#if defined(Q_WS_MAC) + friend class QFontPrivate; +#endif + friend class QFontMetricsF; + friend class QStackTextEngine; + + QExplicitlySharedDataPointer<QFontPrivate> d; +}; + + +class Q_GUI_EXPORT QFontMetricsF +{ +public: + QFontMetricsF(const QFont &); + QFontMetricsF(const QFont &, QPaintDevice *pd); + QFontMetricsF(const QFontMetrics &); + QFontMetricsF(const QFontMetricsF &); + ~QFontMetricsF(); + + QFontMetricsF &operator=(const QFontMetricsF &); + QFontMetricsF &operator=(const QFontMetrics &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QFontMetricsF &operator=(QFontMetricsF &&other) + { qSwap(d, other.d); return *this; } +#endif + qreal ascent() const; + qreal descent() const; + qreal height() const; + qreal leading() const; + qreal lineSpacing() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + qreal maxWidth() const; + + qreal xHeight() const; + qreal averageCharWidth() const; + + bool inFont(QChar) const; + bool inFontUcs4(uint ucs4) const; + + qreal leftBearing(QChar) const; + qreal rightBearing(QChar) const; + qreal width(const QString &string) const; + + qreal width(QChar) const; + + QRectF boundingRect(const QString &string) const; + QRectF boundingRect(QChar) const; + QRectF boundingRect(const QRectF &r, int flags, const QString& string, int tabstops=0, int *tabarray=0) const; + QSizeF size(int flags, const QString& str, int tabstops=0, int *tabarray=0) const; + + QRectF tightBoundingRect(const QString &text) const; + + QString elidedText(const QString &text, Qt::TextElideMode mode, qreal width, int flags = 0) const; + + qreal underlinePos() const; + qreal overlinePos() const; + qreal strikeOutPos() const; + qreal lineWidth() const; + + bool operator==(const QFontMetricsF &other); // 5.0 - remove me + bool operator==(const QFontMetricsF &other) const; + inline bool operator !=(const QFontMetricsF &other) { return !operator==(other); } // 5.0 - remove me + inline bool operator !=(const QFontMetricsF &other) const { return !operator==(other); } + +private: + QExplicitlySharedDataPointer<QFontPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTMETRICS_H diff --git a/src/gui/text/qfontsubset.cpp b/src/gui/text/qfontsubset.cpp new file mode 100644 index 0000000000..9dc28c35f0 --- /dev/null +++ b/src/gui/text/qfontsubset.cpp @@ -0,0 +1,1743 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qdebug.h> +#include "qfontsubset_p.h" +#include <qendian.h> +#include <qpainterpath.h> +#include "private/qpdf_p.h" +#include "private/qfunctions_p.h" + +#ifdef Q_WS_X11 +#include "private/qfontengine_x11_p.h" +#endif + +#ifndef QT_NO_FREETYPE +#if defined(Q_WS_X11) || defined(Q_WS_QWS) +# include "private/qfontengine_ft_p.h" +#endif +#include <ft2build.h> +#include FT_FREETYPE_H +#endif + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +static const char * const agl = +".notdef\0space\0exclam\0quotedbl\0numbersign\0dollar\0percent\0ampersand\0" +"quotesingle\0parenleft\0parenright\0asterisk\0plus\0comma\0hyphen\0period\0" +"slash\0zero\0one\0two\0three\0four\0five\0six\0seven\0eight\0nine\0colon\0" +"semicolon\0less\0equal\0greater\0question\0at\0A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0" +"K\0L\0M\0N\0O\0P\0Q\0R\0S\0T\0U\0V\0W\0X\0Y\0Z\0bracketleft\0backslash\0" +"bracketright\0asciicircum\0underscore\0grave\0a\0b\0c\0d\0e\0f\0g\0h\0i\0j\0" +"k\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z\0braceleft\0bar\0braceright\0" +"asciitilde\0space\0exclamdown\0cent\0sterling\0currency\0yen\0brokenbar\0" +"section\0dieresis\0copyright\0ordfeminine\0guillemotleft\0logicalnot\0" +"hyphen\0registered\0macron\0degree\0plusminus\0twosuperior\0threesuperior\0" +"acute\0mu\0paragraph\0periodcentered\0cedilla\0onesuperior\0ordmasculine\0" +"guillemotright\0onequarter\0onehalf\0threequarters\0questiondown\0Agrave\0" +"Aacute\0Acircumflex\0Atilde\0Adieresis\0Aring\0AE\0Ccedilla\0Egrave\0Eacute\0" +"Ecircumflex\0Edieresis\0Igrave\0Iacute\0Icircumflex\0Idieresis\0Eth\0Ntilde\0" +"Ograve\0Oacute\0Ocircumflex\0Otilde\0Odieresis\0multiply\0Oslash\0Ugrave\0" +"Uacute\0Ucircumflex\0Udieresis\0Yacute\0Thorn\0germandbls\0agrave\0aacute\0" +"acircumflex\0atilde\0adieresis\0aring\0ae\0ccedilla\0egrave\0eacute\0" +"ecircumflex\0edieresis\0igrave\0iacute\0icircumflex\0idieresis\0eth\0ntilde\0" +"ograve\0oacute\0ocircumflex\0otilde\0odieresis\0divide\0oslash\0ugrave\0" +"uacute\0ucircumflex\0udieresis\0yacute\0thorn\0ydieresis\0Amacron\0amacron\0" +"Abreve\0abreve\0Aogonek\0aogonek\0Cacute\0cacute\0Ccircumflex\0ccircumflex\0" +"Cdotaccent\0cdotaccent\0Ccaron\0ccaron\0Dcaron\0dcaron\0Dcroat\0dcroat\0" +"Emacron\0emacron\0Ebreve\0ebreve\0Edotaccent\0edotaccent\0Eogonek\0eogonek\0" +"Ecaron\0ecaron\0Gcircumflex\0gcircumflex\0Gbreve\0gbreve\0Gdotaccent\0" +"gdotaccent\0Gcommaaccent\0gcommaaccent\0Hcircumflex\0hcircumflex\0Hbar\0" +"hbar\0Itilde\0itilde\0Imacron\0imacron\0Ibreve\0ibreve\0Iogonek\0iogonek\0" +"Idotaccent\0dotlessi\0IJ\0ij\0Jcircumflex\0jcircumflex\0Kcommaaccent\0" +"kcommaaccent\0kgreenlandic\0Lacute\0lacute\0Lcommaaccent\0lcommaaccent\0" +"Lcaron\0lcaron\0Ldot\0ldot\0Lslash\0lslash\0Nacute\0nacute\0Ncommaaccent\0" +"ncommaaccent\0Ncaron\0ncaron\0napostrophe\0Eng\0eng\0Omacron\0omacron\0" +"Obreve\0obreve\0Ohungarumlaut\0ohungarumlaut\0OE\0oe\0Racute\0racute\0" +"Rcommaaccent\0rcommaaccent\0Rcaron\0rcaron\0Sacute\0sacute\0Scircumflex\0" +"scircumflex\0Scedilla\0scedilla\0Scaron\0scaron\0Tcaron\0tcaron\0Tbar\0tbar\0" +"Utilde\0utilde\0Umacron\0umacron\0Ubreve\0ubreve\0Uring\0uring\0" +"Uhungarumlaut\0uhungarumlaut\0Uogonek\0uogonek\0Wcircumflex\0wcircumflex\0" +"Ycircumflex\0ycircumflex\0Ydieresis\0Zacute\0zacute\0Zdotaccent\0zdotaccent\0" +"Zcaron\0zcaron\0longs\0florin\0Ohorn\0ohorn\0Uhorn\0uhorn\0Gcaron\0gcaron\0" +"Aringacute\0aringacute\0AEacute\0aeacute\0Oslashacute\0oslashacute\0" +"Scommaaccent\0scommaaccent\0Tcommaaccent\0tcommaaccent\0afii57929\0" +"afii64937\0circumflex\0caron\0breve\0dotaccent\0ring\0ogonek\0tilde\0" +"hungarumlaut\0gravecomb\0acutecomb\0tildecomb\0hookabovecomb\0dotbelowcomb\0" +"tonos\0dieresistonos\0Alphatonos\0anoteleia\0Epsilontonos\0Etatonos\0" +"Iotatonos\0Omicrontonos\0Upsilontonos\0Omegatonos\0iotadieresistonos\0Alpha\0" +"Beta\0Gamma\0Delta\0Epsilon\0Zeta\0Eta\0Theta\0Iota\0Kappa\0Lambda\0Mu\0Nu\0" +"Xi\0Omicron\0Pi\0Rho\0Sigma\0Tau\0Upsilon\0Phi\0Chi\0Psi\0Omega\0" +"Iotadieresis\0Upsilondieresis\0alphatonos\0epsilontonos\0etatonos\0" +"iotatonos\0upsilondieresistonos\0alpha\0beta\0gamma\0delta\0epsilon\0zeta\0" +"eta\0theta\0iota\0kappa\0lambda\0mu\0nu\0xi\0omicron\0pi\0rho\0sigma1\0" +"sigma\0tau\0upsilon\0phi\0chi\0psi\0omega\0iotadieresis\0upsilondieresis\0" +; + +static const struct { quint16 u; quint16 index; } unicode_to_aglindex[] = { + {0x0000, 0}, {0x0020, 8}, {0x0021, 14}, {0x0022, 21}, + {0x0023, 30}, {0x0024, 41}, {0x0025, 48}, {0x0026, 56}, + {0x0027, 66}, {0x0028, 78}, {0x0029, 88}, {0x002A, 99}, + {0x002B, 108}, {0x002C, 113}, {0x002D, 119}, {0x002E, 126}, + {0x002F, 133}, {0x0030, 139}, {0x0031, 144}, {0x0032, 148}, + {0x0033, 152}, {0x0034, 158}, {0x0035, 163}, {0x0036, 168}, + {0x0037, 172}, {0x0038, 178}, {0x0039, 184}, {0x003A, 189}, + {0x003B, 195}, {0x003C, 205}, {0x003D, 210}, {0x003E, 216}, + {0x003F, 224}, {0x0040, 233}, {0x0041, 236}, {0x0042, 238}, + {0x0043, 240}, {0x0044, 242}, {0x0045, 244}, {0x0046, 246}, + {0x0047, 248}, {0x0048, 250}, {0x0049, 252}, {0x004A, 254}, + {0x004B, 256}, {0x004C, 258}, {0x004D, 260}, {0x004E, 262}, + {0x004F, 264}, {0x0050, 266}, {0x0051, 268}, {0x0052, 270}, + {0x0053, 272}, {0x0054, 274}, {0x0055, 276}, {0x0056, 278}, + {0x0057, 280}, {0x0058, 282}, {0x0059, 284}, {0x005A, 286}, + {0x005B, 288}, {0x005C, 300}, {0x005D, 310}, {0x005E, 323}, + {0x005F, 335}, {0x0060, 346}, {0x0061, 352}, {0x0062, 354}, + {0x0063, 356}, {0x0064, 358}, {0x0065, 360}, {0x0066, 362}, + {0x0067, 364}, {0x0068, 366}, {0x0069, 368}, {0x006A, 370}, + {0x006B, 372}, {0x006C, 374}, {0x006D, 376}, {0x006E, 378}, + {0x006F, 380}, {0x0070, 382}, {0x0071, 384}, {0x0072, 386}, + {0x0073, 388}, {0x0074, 390}, {0x0075, 392}, {0x0076, 394}, + {0x0077, 396}, {0x0078, 398}, {0x0079, 400}, {0x007A, 402}, + {0x007B, 404}, {0x007C, 414}, {0x007D, 418}, {0x007E, 429}, + {0x00A0, 440}, {0x00A1, 446}, {0x00A2, 457}, {0x00A3, 462}, + {0x00A4, 471}, {0x00A5, 480}, {0x00A6, 484}, {0x00A7, 494}, + {0x00A8, 502}, {0x00A9, 511}, {0x00AA, 521}, {0x00AB, 533}, + {0x00AC, 547}, {0x00AD, 558}, {0x00AE, 565}, {0x00AF, 576}, + {0x00B0, 583}, {0x00B1, 590}, {0x00B2, 600}, {0x00B3, 612}, + {0x00B4, 626}, {0x00B5, 632}, {0x00B6, 635}, {0x00B7, 645}, + {0x00B8, 660}, {0x00B9, 668}, {0x00BA, 680}, {0x00BB, 693}, + {0x00BC, 708}, {0x00BD, 719}, {0x00BE, 727}, {0x00BF, 741}, + {0x00C0, 754}, {0x00C1, 761}, {0x00C2, 768}, {0x00C3, 780}, + {0x00C4, 787}, {0x00C5, 797}, {0x00C6, 803}, {0x00C7, 806}, + {0x00C8, 815}, {0x00C9, 822}, {0x00CA, 829}, {0x00CB, 841}, + {0x00CC, 851}, {0x00CD, 858}, {0x00CE, 865}, {0x00CF, 877}, + {0x00D0, 887}, {0x00D1, 891}, {0x00D2, 898}, {0x00D3, 905}, + {0x00D4, 912}, {0x00D5, 924}, {0x00D6, 931}, {0x00D7, 941}, + {0x00D8, 950}, {0x00D9, 957}, {0x00DA, 964}, {0x00DB, 971}, + {0x00DC, 983}, {0x00DD, 993}, {0x00DE, 1000}, {0x00DF, 1006}, + {0x00E0, 1017}, {0x00E1, 1024}, {0x00E2, 1031}, {0x00E3, 1043}, + {0x00E4, 1050}, {0x00E5, 1060}, {0x00E6, 1066}, {0x00E7, 1069}, + {0x00E8, 1078}, {0x00E9, 1085}, {0x00EA, 1092}, {0x00EB, 1104}, + {0x00EC, 1114}, {0x00ED, 1121}, {0x00EE, 1128}, {0x00EF, 1140}, + {0x00F0, 1150}, {0x00F1, 1154}, {0x00F2, 1161}, {0x00F3, 1168}, + {0x00F4, 1175}, {0x00F5, 1187}, {0x00F6, 1194}, {0x00F7, 1204}, + {0x00F8, 1211}, {0x00F9, 1218}, {0x00FA, 1225}, {0x00FB, 1232}, + {0x00FC, 1244}, {0x00FD, 1254}, {0x00FE, 1261}, {0x00FF, 1267}, + {0x0100, 1277}, {0x0101, 1285}, {0x0102, 1293}, {0x0103, 1300}, + {0x0104, 1307}, {0x0105, 1315}, {0x0106, 1323}, {0x0107, 1330}, + {0x0108, 1337}, {0x0109, 1349}, {0x010A, 1361}, {0x010B, 1372}, + {0x010C, 1383}, {0x010D, 1390}, {0x010E, 1397}, {0x010F, 1404}, + {0x0110, 1411}, {0x0111, 1418}, {0x0112, 1425}, {0x0113, 1433}, + {0x0114, 1441}, {0x0115, 1448}, {0x0116, 1455}, {0x0117, 1466}, + {0x0118, 1477}, {0x0119, 1485}, {0x011A, 1493}, {0x011B, 1500}, + {0x011C, 1507}, {0x011D, 1519}, {0x011E, 1531}, {0x011F, 1538}, + {0x0120, 1545}, {0x0121, 1556}, {0x0122, 1567}, {0x0123, 1580}, + {0x0124, 1593}, {0x0125, 1605}, {0x0126, 1617}, {0x0127, 1622}, + {0x0128, 1627}, {0x0129, 1634}, {0x012A, 1641}, {0x012B, 1649}, + {0x012C, 1657}, {0x012D, 1664}, {0x012E, 1671}, {0x012F, 1679}, + {0x0130, 1687}, {0x0131, 1698}, {0x0132, 1707}, {0x0133, 1710}, + {0x0134, 1713}, {0x0135, 1725}, {0x0136, 1737}, {0x0137, 1750}, + {0x0138, 1763}, {0x0139, 1776}, {0x013A, 1783}, {0x013B, 1790}, + {0x013C, 1803}, {0x013D, 1816}, {0x013E, 1823}, {0x013F, 1830}, + {0x0140, 1835}, {0x0141, 1840}, {0x0142, 1847}, {0x0143, 1854}, + {0x0144, 1861}, {0x0145, 1868}, {0x0146, 1881}, {0x0147, 1894}, + {0x0148, 1901}, {0x0149, 1908}, {0x014A, 1920}, {0x014B, 1924}, + {0x014C, 1928}, {0x014D, 1936}, {0x014E, 1944}, {0x014F, 1951}, + {0x0150, 1958}, {0x0151, 1972}, {0x0152, 1986}, {0x0153, 1989}, + {0x0154, 1992}, {0x0155, 1999}, {0x0156, 2006}, {0x0157, 2019}, + {0x0158, 2032}, {0x0159, 2039}, {0x015A, 2046}, {0x015B, 2053}, + {0x015C, 2060}, {0x015D, 2072}, {0x015E, 2084}, {0x015F, 2093}, + {0x0160, 2102}, {0x0161, 2109}, {0x0164, 2116}, {0x0165, 2123}, + {0x0166, 2130}, {0x0167, 2135}, {0x0168, 2140}, {0x0169, 2147}, + {0x016A, 2154}, {0x016B, 2162}, {0x016C, 2170}, {0x016D, 2177}, + {0x016E, 2184}, {0x016F, 2190}, {0x0170, 2196}, {0x0171, 2210}, + {0x0172, 2224}, {0x0173, 2232}, {0x0174, 2240}, {0x0175, 2252}, + {0x0176, 2264}, {0x0177, 2276}, {0x0178, 2288}, {0x0179, 2298}, + {0x017A, 2305}, {0x017B, 2312}, {0x017C, 2323}, {0x017D, 2334}, + {0x017E, 2341}, {0x017F, 2348}, {0x0192, 2354}, {0x01A0, 2361}, + {0x01A1, 2367}, {0x01AF, 2373}, {0x01B0, 2379}, {0x01E6, 2385}, + {0x01E7, 2392}, {0x01FA, 2399}, {0x01FB, 2410}, {0x01FC, 2421}, + {0x01FD, 2429}, {0x01FE, 2437}, {0x01FF, 2449}, {0x0218, 2461}, + {0x0219, 2474}, {0x021A, 2487}, {0x021B, 2500}, {0x02BC, 2513}, + {0x02BD, 2523}, {0x02C6, 2533}, {0x02C7, 2544}, {0x02D8, 2550}, + {0x02D9, 2556}, {0x02DA, 2566}, {0x02DB, 2571}, {0x02DC, 2578}, + {0x02DD, 2584}, {0x0300, 2597}, {0x0301, 2607}, {0x0303, 2617}, + {0x0309, 2627}, {0x0323, 2641}, {0x0384, 2654}, {0x0385, 2660}, + {0x0386, 2674}, {0x0387, 2685}, {0x0388, 2695}, {0x0389, 2708}, + {0x038A, 2717}, {0x038C, 2727}, {0x038E, 2740}, {0x038F, 2753}, + {0x0390, 2764}, {0x0391, 2782}, {0x0392, 2788}, {0x0393, 2793}, + {0x0394, 2799}, {0x0395, 2805}, {0x0396, 2813}, {0x0397, 2818}, + {0x0398, 2822}, {0x0399, 2828}, {0x039A, 2833}, {0x039B, 2839}, + {0x039C, 2846}, {0x039D, 2849}, {0x039E, 2852}, {0x039F, 2855}, + {0x03A0, 2863}, {0x03A1, 2866}, {0x03A3, 2870}, {0x03A4, 2876}, + {0x03A5, 2880}, {0x03A6, 2888}, {0x03A7, 2892}, {0x03A8, 2896}, + {0x03A9, 2900}, {0x03AA, 2906}, {0x03AB, 2919}, {0x03AC, 2935}, + {0x03AD, 2946}, {0x03AE, 2959}, {0x03AF, 2968}, {0x03B0, 2978}, + {0x03B1, 2999}, {0x03B2, 3005}, {0x03B3, 3010}, {0x03B4, 3016}, + {0x03B5, 3022}, {0x03B6, 3030}, {0x03B7, 3035}, {0x03B8, 3039}, + {0x03B9, 3045}, {0x03BA, 3050}, {0x03BB, 3056}, {0x03BC, 3063}, + {0x03BD, 3066}, {0x03BE, 3069}, {0x03BF, 3072}, {0x03C0, 3080}, + {0x03C1, 3083}, {0x03C2, 3087}, {0x03C3, 3094}, {0x03C4, 3100}, + {0x03C5, 3104}, {0x03C6, 3112}, {0x03C7, 3116}, {0x03C8, 3120}, + {0x03C9, 3124}, {0x03CA, 3130}, {0x03CB, 3143}, {0x03CC, 3159}, + {0x03CD, 3172}, {0x03CE, 3185}, {0x03D1, 3196}, {0x03D2, 3203}, + {0x03D5, 3212}, {0x03D6, 3217}, {0xFFFF, 3224} +}; + +// This map is used for symbol fonts to get the correct glyph names for the latin range +static const unsigned short symbol_map[0x100] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x2200, 0x0023, 0x2203, 0x0025, 0x0026, 0x220b, + 0x0028, 0x0029, 0x2217, 0x002b, 0x002c, 0x2212, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + + 0x2245, 0x0391, 0x0392, 0x03a7, 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, 0x039b, 0x039c, 0x039d, 0x039f, + 0x03a0, 0x0398, 0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0x005b, 0x2234, 0x005d, 0x22a5, 0x005f, + 0xf8e5, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03d5, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03b8, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0x007b, 0x007c, 0x007d, 0x223c, 0x007f, + + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x20ac, 0x03d2, 0x2023, 0x2264, 0x2044, 0x221e, 0x0192, 0x2263, + 0x2666, 0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, + 0x00b0, 0x00b1, 0x2033, 0x2265, 0x00d7, 0x221d, 0x2202, 0x2022, + 0x00f7, 0x2260, 0x2261, 0x2248, 0x2026, 0xf8e6, 0xf8e7, 0x21b5, + + 0x2135, 0x2111, 0x211c, 0x2118, 0x2297, 0x2295, 0x2205, 0x2229, + 0x222a, 0x2283, 0x2287, 0x2284, 0x2282, 0x2286, 0x2208, 0x2209, + 0x2220, 0x2207, 0xf6da, 0xf6d9, 0xf6db, 0x220f, 0x221a, 0x22c5, + 0x00ac, 0x2227, 0x2228, 0x21d4, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x25ca, 0x2329, 0xf8e8, 0xf8e9, 0xf8ea, 0x2211, 0xf8eb, 0xf8ec, + 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0, 0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, + 0x0000, 0x232a, 0x222b, 0x2320, 0xf8f5, 0x2321, 0xf8f6, 0xf8f7, + 0xf8f8, 0xf8f9, 0xf8fa, 0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0x0000, +}; + +// ---------------------------- PS/PDF helper methods ----------------------------------- + +QByteArray QFontSubset::glyphName(unsigned short unicode, bool symbol) +{ + if (symbol && unicode < 0x100) + // map from latin1 to symbol + unicode = symbol_map[unicode]; + + int l = 0; + while(unicode_to_aglindex[l].u < unicode) + l++; + if (unicode_to_aglindex[l].u == unicode) + return agl + unicode_to_aglindex[l].index; + + char buffer[8]; + buffer[0] = 'u'; + buffer[1] = 'n'; + buffer[2] = 'i'; + QPdf::toHex(unicode, buffer+3); + return buffer; +} + +#ifndef QT_NO_FREETYPE +static FT_Face ft_face(const QFontEngine *engine) +{ +#ifdef Q_WS_X11 +#ifndef QT_NO_FONTCONFIG + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine); + return ft->non_locked_face(); + } else +#endif + if (engine->type() == QFontEngine::XLFD) { + const QFontEngineXLFD *xlfd = static_cast<const QFontEngineXLFD *>(engine); + return xlfd->non_locked_face(); + } +#endif +#ifdef Q_WS_QWS + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine); + return ft->non_locked_face(); + } +#endif + return 0; +} +#endif + +QByteArray QFontSubset::glyphName(unsigned int glyph, const QVector<int> reverseMap) const +{ + uint glyphIndex = glyph_indices[glyph]; + + if (glyphIndex == 0) + return "/.notdef"; + + QByteArray ba; + QPdf::ByteStream s(&ba); +#ifndef QT_NO_FREETYPE + FT_Face face = ft_face(fontEngine); + + char name[32]; + name[0] = 0; + if (face && FT_HAS_GLYPH_NAMES(face)) { +#if defined(Q_WS_X11) + if (fontEngine->type() == QFontEngine::XLFD) + glyphIndex = static_cast<QFontEngineXLFD *>(fontEngine)->glyphIndexToFreetypeGlyphIndex(glyphIndex); +#endif + FT_Get_Glyph_Name(face, glyphIndex, &name, 32); + if (name[0] == '.') // fix broken PS fonts returning .notdef for many glyphs + name[0] = 0; + } + if (name[0]) { + s << '/' << name; + } else +#endif +#if defined(Q_WS_X11) + if (fontEngine->type() == QFontEngine::XLFD) { + uint uc = static_cast<QFontEngineXLFD *>(fontEngine)->toUnicode(glyphIndex); + s << '/' << glyphName(uc, false /* ### */); + } else +#endif + if (reverseMap[glyphIndex] && reverseMap[glyphIndex] < 0x10000) { + s << '/' << glyphName(reverseMap[glyphIndex], false); + } else { + s << "/gl" << (int)glyphIndex; + } + return ba; +} + + +QByteArray QFontSubset::widthArray() const +{ + Q_ASSERT(!widths.isEmpty()); + + QFontEngine::Properties properties = fontEngine->properties(); + + QByteArray width; + QPdf::ByteStream s(&width); + QFixed scale = QFixed(1000)/emSquare; + + QFixed defWidth = widths[0]; + //qDebug("defWidth=%d, scale=%f", defWidth.toInt(), scale.toReal()); + for (int i = 0; i < nGlyphs(); ++i) { + if (defWidth != widths[i]) + defWidth = 0; + } + if (defWidth > 0) { + s << "/DW " << (defWidth*scale).toInt(); + } else { + s << "/W ["; + for (int g = 0; g < nGlyphs();) { + QFixed w = widths[g]; + int start = g; + int startLinear = 0; + ++g; + while (g < nGlyphs()) { + QFixed nw = widths[g]; + if (nw == w) { + if (!startLinear) + startLinear = g - 1; + } else { + if (startLinear > 0 && g - startLinear >= 10) + break; + startLinear = 0; + } + w = nw; + ++g; + } + // qDebug("start=%x startLinear=%x g-1=%x",start,startLinear,g-1); + if (g - startLinear < 10) + startLinear = 0; + int endnonlinear = startLinear ? startLinear : g; + // qDebug(" startLinear=%x endnonlinear=%x", startLinear,endnonlinear); + if (endnonlinear > start) { + s << start << '['; + for (int i = start; i < endnonlinear; ++i) + s << (widths[i]*scale).toInt(); + s << "]\n"; + } + if (startLinear) + s << startLinear << g - 1 << (widths[startLinear]*scale).toInt() << '\n'; + } + s << "]\n"; + } + return width; +} + +static void checkRanges(QPdf::ByteStream &ts, QByteArray &ranges, int &nranges) +{ + if (++nranges > 100) { + ts << nranges << "beginbfrange\n" + << ranges << "endbfrange\n"; + ranges = QByteArray(); + nranges = 0; + } +} + +QVector<int> QFontSubset::getReverseMap() const +{ + QVector<int> reverseMap; + reverseMap.resize(0x10000); + for (uint i = 0; i < 0x10000; ++i) + reverseMap[i] = 0; + QGlyphLayoutArray<10> glyphs; + for (uint uc = 0; uc < 0x10000; ++uc) { + QChar ch(uc); + int nglyphs = 10; + fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + int idx = glyph_indices.indexOf(glyphs.glyphs[0]); + if (idx >= 0 && !reverseMap.at(idx)) + reverseMap[idx] = uc; + } + return reverseMap; +} + +QByteArray QFontSubset::createToUnicodeMap() const +{ + QVector<int> reverseMap = getReverseMap(); + + QByteArray touc; + QPdf::ByteStream ts(&touc); + ts << "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n" + "/CMapName /Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<0000> <FFFF>\n" + "endcodespacerange\n"; + + int nranges = 1; + QByteArray ranges = "<0000> <0000> <0000>\n"; + QPdf::ByteStream s(&ranges); + + char buf[5]; + for (int g = 1; g < nGlyphs(); ) { + int uc0 = reverseMap.at(g); + if (!uc0) { + ++g; + continue; + } + int start = g; + int startLinear = 0; + ++g; + while (g < nGlyphs()) { + int uc = reverseMap[g]; + // cmaps can't have the high byte changing within one range, so we need to break on that as well + if (!uc || (g>>8) != (start >> 8)) + break; + if (uc == uc0 + 1) { + if (!startLinear) + startLinear = g - 1; + } else { + if (startLinear > 0 && g - startLinear >= 10) + break; + startLinear = 0; + } + uc0 = uc; + ++g; + } + // qDebug("start=%x startLinear=%x g-1=%x",start,startLinear,g-1); + if (g - startLinear < 10) + startLinear = 0; + int endnonlinear = startLinear ? startLinear : g; + // qDebug(" startLinear=%x endnonlinear=%x", startLinear,endnonlinear); + if (endnonlinear > start) { + s << '<' << QPdf::toHex((ushort)start, buf) << "> <"; + s << QPdf::toHex((ushort)(endnonlinear - 1), buf) << "> "; + if (endnonlinear == start + 1) { + s << '<' << QPdf::toHex((ushort)reverseMap[start], buf) << ">\n"; + } else { + s << '['; + for (int i = start; i < endnonlinear; ++i) { + s << '<' << QPdf::toHex((ushort)reverseMap[i], buf) << "> "; + } + s << "]\n"; + } + checkRanges(ts, ranges, nranges); + } + if (startLinear) { + while (startLinear < g) { + int len = g - startLinear; + int uc_start = reverseMap[startLinear]; + int uc_end = uc_start + len - 1; + if ((uc_end >> 8) != (uc_start >> 8)) + len = 256 - (uc_start & 0xff); + s << '<' << QPdf::toHex((ushort)startLinear, buf) << "> <"; + s << QPdf::toHex((ushort)(startLinear + len - 1), buf) << "> "; + s << '<' << QPdf::toHex((ushort)reverseMap[startLinear], buf) << ">\n"; + checkRanges(ts, ranges, nranges); + startLinear += len; + } + } + } + if (nranges) { + ts << nranges << "beginbfrange\n" + << ranges << "endbfrange\n"; + } + ts << "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"; + + return touc; +} + +int QFontSubset::addGlyph(int index) +{ + int idx = glyph_indices.indexOf(index); + if (idx < 0) { + idx = glyph_indices.size(); + glyph_indices.append(index); + } + return idx; +} + + +// ------------------------------ Truetype generation ---------------------------------------------- + +typedef qint16 F2DOT14; +typedef quint32 Tag; +typedef quint16 GlyphID; +typedef quint16 Offset; + + +class QTtfStream { +public: + QTtfStream(QByteArray &ba) : data((uchar *)ba.data()) { start = data; } + QTtfStream &operator <<(quint8 v) { *data = v; ++data; return *this; } + QTtfStream &operator <<(quint16 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(quint32 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint8 v) { *data = quint8(v); ++data; return *this; } + QTtfStream &operator <<(qint16 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint32 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint64 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + + int offset() const { return data - start; } + void setOffset(int o) { data = start + o; } + void align4() { while (offset() & 3) { *data = '\0'; ++data; } } +private: + uchar *data; + uchar *start; +}; + +struct QTtfTable { + Tag tag; + QByteArray data; +}; +Q_DECLARE_TYPEINFO(QTtfTable, Q_MOVABLE_TYPE); + + +struct qttf_head_table { + qint32 font_revision; + quint16 flags; + qint64 created; + qint64 modified; + qint16 xMin; + qint16 yMin; + qint16 xMax; + qint16 yMax; + quint16 macStyle; + qint16 indexToLocFormat; +}; + + +struct qttf_hhea_table { + qint16 ascender; + qint16 descender; + qint16 lineGap; + quint16 maxAdvanceWidth; + qint16 minLeftSideBearing; + qint16 minRightSideBearing; + qint16 xMaxExtent; + quint16 numberOfHMetrics; +}; + + +struct qttf_maxp_table { + quint16 numGlyphs; + quint16 maxPoints; + quint16 maxContours; + quint16 maxCompositePoints; + quint16 maxCompositeContours; + quint16 maxComponentElements; + quint16 maxComponentDepth; +}; + +struct qttf_name_table { + QString copyright; + QString family; + QString subfamily; + QString postscript_name; +}; + + +static QTtfTable generateHead(const qttf_head_table &head); +static QTtfTable generateHhea(const qttf_hhea_table &hhea); +static QTtfTable generateMaxp(const qttf_maxp_table &maxp); +static QTtfTable generateName(const qttf_name_table &name); + +struct qttf_font_tables +{ + qttf_head_table head; + qttf_hhea_table hhea; + qttf_maxp_table maxp; +}; + + +struct QTtfGlyph { + quint16 index; + qint16 xMin; + qint16 xMax; + qint16 yMin; + qint16 yMax; + quint16 advanceWidth; + qint16 lsb; + quint16 numContours; + quint16 numPoints; + QByteArray data; +}; +Q_DECLARE_TYPEINFO(QTtfGlyph, Q_MOVABLE_TYPE); + +static QTtfGlyph generateGlyph(int index, const QPainterPath &path, qreal advance, qreal lsb, qreal ppem); +// generates glyf, loca and hmtx +static QList<QTtfTable> generateGlyphTables(qttf_font_tables &tables, const QList<QTtfGlyph> &_glyphs); + +static QByteArray bindFont(const QList<QTtfTable>& _tables); + + +static quint32 checksum(const QByteArray &table) +{ + quint32 sum = 0; + int offset = 0; + const uchar *d = (uchar *)table.constData(); + while (offset <= table.size()-3) { + sum += qFromBigEndian<quint32>(d + offset); + offset += 4; + } + int shift = 24; + quint32 x = 0; + while (offset < table.size()) { + x |= ((quint32)d[offset]) << shift; + ++offset; + shift -= 8; + } + sum += x; + + return sum; +} + +static QTtfTable generateHead(const qttf_head_table &head) +{ + const int head_size = 54; + QTtfTable t; + t.tag = MAKE_TAG('h', 'e', 'a', 'd'); + t.data.resize(head_size); + + QTtfStream s(t.data); + +// qint32 Table version number 0x00010000 for version 1.0. +// qint32 fontRevision Set by font manufacturer. + s << qint32(0x00010000) + << head.font_revision +// quint32 checkSumAdjustment To compute: set it to 0, sum the entire font as quint32, then store 0xB1B0AFBA - sum. + << quint32(0) +// quint32 magicNumber Set to 0x5F0F3CF5. + << quint32(0x5F0F3CF5) +// quint16 flags Bit 0: Baseline for font at y=0; +// Bit 1: Left sidebearing point at x=0; +// Bit 2: Instructions may depend on point size; +// Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear; +// Bit 4: Instructions may alter advance width (the advance widths might not scale linearly); +// Bits 5-10: These should be set according to Apple's specification . However, they are not implemented in OpenType. +// Bit 11: Font data is 'lossless,' as a result of having been compressed and decompressed with the Agfa MicroType Express engine. +// Bit 12: Font converted (produce compatible metrics) +// Bit 13: Font optimized for ClearType +// Bit 14: Reserved, set to 0 +// Bit 15: Reserved, set to 0 + << quint16(0) + +// quint16 unitsPerEm Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines. + << quint16(2048) +// qint64 created Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer + << head.created +// qint64 modified Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer + << head.modified +// qint16 xMin For all glyph bounding boxes. +// qint16 yMin For all glyph bounding boxes. +// qint16 xMax For all glyph bounding boxes. +// qint16 yMax For all glyph bounding boxes. + << head.xMin + << head.yMin + << head.xMax + << head.yMax +// quint16 macStyle Bit 0: Bold (if set to 1); +// Bit 1: Italic (if set to 1) +// Bit 2: Underline (if set to 1) +// Bit 3: Outline (if set to 1) +// Bit 4: Shadow (if set to 1) +// Bit 5: Condensed (if set to 1) +// Bit 6: Extended (if set to 1) +// Bits 7-15: Reserved (set to 0). + << head.macStyle +// quint16 lowestRecPPEM Smallest readable size in pixels. + << quint16(6) // just a wild guess +// qint16 fontDirectionHint 0: Fully mixed directional glyphs; + << qint16(0) +// 1: Only strongly left to right; +// 2: Like 1 but also contains neutrals; +// -1: Only strongly right to left; +// -2: Like -1 but also contains neutrals. 1 +// qint16 indexToLocFormat 0 for short offsets, 1 for long. + << head.indexToLocFormat +// qint16 glyphDataFormat 0 for current format. + << qint16(0); + + Q_ASSERT(s.offset() == head_size); + return t; +} + + +static QTtfTable generateHhea(const qttf_hhea_table &hhea) +{ + const int hhea_size = 36; + QTtfTable t; + t.tag = MAKE_TAG('h', 'h', 'e', 'a'); + t.data.resize(hhea_size); + + QTtfStream s(t.data); +// qint32 Table version number 0x00010000 for version 1.0. + s << qint32(0x00010000) +// qint16 Ascender Typographic ascent. (Distance from baseline of highest ascender) + << hhea.ascender +// qint16 Descender Typographic descent. (Distance from baseline of lowest descender) + << hhea.descender +// qint16 LineGap Typographic line gap. +// Negative LineGap values are treated as zero +// in Windows 3.1, System 6, and +// System 7. + << hhea.lineGap +// quint16 advanceWidthMax Maximum advance width value in 'hmtx' table. + << hhea.maxAdvanceWidth +// qint16 minLeftSideBearing Minimum left sidebearing value in 'hmtx' table. + << hhea.minLeftSideBearing +// qint16 minRightSideBearing Minimum right sidebearing value; calculated as Min(aw - lsb - (xMax - xMin)). + << hhea.minRightSideBearing +// qint16 xMaxExtent Max(lsb + (xMax - xMin)). + << hhea.xMaxExtent +// qint16 caretSlopeRise Used to calculate the slope of the cursor (rise/run); 1 for vertical. + << qint16(1) +// qint16 caretSlopeRun 0 for vertical. + << qint16(0) +// qint16 caretOffset The amount by which a slanted highlight on a glyph needs to be shifted to produce the best appearance. Set to 0 for non-slanted fonts + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 metricDataFormat 0 for current format. + << qint16(0) +// quint16 numberOfHMetrics Number of hMetric entries in 'hmtx' table + << hhea.numberOfHMetrics; + + Q_ASSERT(s.offset() == hhea_size); + return t; +} + + +static QTtfTable generateMaxp(const qttf_maxp_table &maxp) +{ + const int maxp_size = 32; + QTtfTable t; + t.tag = MAKE_TAG('m', 'a', 'x', 'p'); + t.data.resize(maxp_size); + + QTtfStream s(t.data); + +// qint32 Table version number 0x00010000 for version 1.0. + s << qint32(0x00010000) +// quint16 numGlyphs The number of glyphs in the font. + << maxp.numGlyphs +// quint16 maxPoints Maximum points in a non-composite glyph. + << maxp.maxPoints +// quint16 maxContours Maximum contours in a non-composite glyph. + << maxp.maxContours +// quint16 maxCompositePoints Maximum points in a composite glyph. + << maxp.maxCompositePoints +// quint16 maxCompositeContours Maximum contours in a composite glyph. + << maxp.maxCompositeContours +// quint16 maxZones 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases. + << quint16(1) // we do not embed instructions +// quint16 maxTwilightPoints Maximum points used in Z0. + << quint16(0) +// quint16 maxStorage Number of Storage Area locations. + << quint16(0) +// quint16 maxFunctionDefs Number of FDEFs. + << quint16(0) +// quint16 maxInstructionDefs Number of IDEFs. + << quint16(0) +// quint16 maxStackElements Maximum stack depth2. + << quint16(0) +// quint16 maxSizeOfInstructions Maximum byte count for glyph instructions. + << quint16(0) +// quint16 maxComponentElements Maximum number of components referenced at "top level" for any composite glyph. + << maxp.maxComponentElements +// quint16 maxComponentDepth Maximum levels of recursion; 1 for simple components. + << maxp.maxComponentDepth; + + Q_ASSERT(s.offset() == maxp_size); + return t; +} + +struct QTtfNameRecord { + quint16 nameId; + QString value; +}; + +static QTtfTable generateName(const QList<QTtfNameRecord> &name); + +static QTtfTable generateName(const qttf_name_table &name) +{ + QList<QTtfNameRecord> list; + QTtfNameRecord rec; + rec.nameId = 0; + rec.value = name.copyright; + list.append(rec); + rec.nameId = 1; + rec.value = name.family; + list.append(rec); + rec.nameId = 2; + rec.value = name.subfamily; + list.append(rec); + rec.nameId = 4; + rec.value = name.family; + if (name.subfamily != QLatin1String("Regular")) + rec.value += QLatin1Char(' ') + name.subfamily; + list.append(rec); + rec.nameId = 6; + rec.value = name.postscript_name; + list.append(rec); + + return generateName(list); +} + +// ####### should probably generate Macintosh/Roman name entries as well +static QTtfTable generateName(const QList<QTtfNameRecord> &name) +{ + const int char_size = 2; + + QTtfTable t; + t.tag = MAKE_TAG('n', 'a', 'm', 'e'); + + const int name_size = 6 + 12*name.size(); + int string_size = 0; + for (int i = 0; i < name.size(); ++i) { + string_size += name.at(i).value.length()*char_size; + } + t.data.resize(name_size + string_size); + + QTtfStream s(t.data); +// quint16 format Format selector (=0). + s << quint16(0) +// quint16 count Number of name records. + << quint16(name.size()) +// quint16 stringOffset Offset to start of string storage (from start of table). + << quint16(name_size); +// NameRecord nameRecord[count] The name records where count is the number of records. +// (Variable) + + int off = 0; + for (int i = 0; i < name.size(); ++i) { + int len = name.at(i).value.length()*char_size; +// quint16 platformID Platform ID. +// quint16 encodingID Platform-specific encoding ID. +// quint16 languageID Language ID. + s << quint16(3) + << quint16(1) + << quint16(0x0409) // en_US +// quint16 nameId Name ID. + << name.at(i).nameId +// quint16 length String length (in bytes). + << quint16(len) +// quint16 offset String offset from start of storage area (in bytes). + << quint16(off); + off += len; + } + for (int i = 0; i < name.size(); ++i) { + const QString &n = name.at(i).value; + const ushort *uc = n.utf16(); + for (int i = 0; i < n.length(); ++i) { + s << quint16(*uc); + ++uc; + } + } + return t; +} + + +enum Flags { + OffCurve = 0, + OnCurve = (1 << 0), + XShortVector = (1 << 1), + YShortVector = (1 << 2), + Repeat = (1 << 3), + XSame = (1 << 4), + XShortPositive = (1 << 4), + YSame = (1 << 5), + YShortPositive = (1 << 5) +}; +struct TTF_POINT { + qint16 x; + qint16 y; + quint8 flags; +}; +Q_DECLARE_TYPEINFO(TTF_POINT, Q_PRIMITIVE_TYPE); + +static void convertPath(const QPainterPath &path, QList<TTF_POINT> *points, QList<int> *endPoints, qreal ppem) +{ + int numElements = path.elementCount(); + for (int i = 0; i < numElements - 1; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + TTF_POINT p; + p.x = qRound(e.x * 2048. / ppem); + p.y = qRound(-e.y * 2048. / ppem); + p.flags = 0; + + switch(e.type) { + case QPainterPath::MoveToElement: + if (i != 0) { + // see if start and end points of the last contour agree + int start = endPoints->size() ? endPoints->at(endPoints->size()-1) - 1 : 0; + int end = points->size() - 1; + if (points->at(end).x == points->at(start).x + && points->at(end).y == points->at(start).y) + points->takeLast(); + endPoints->append(points->size() - 1); + } + // fall through + case QPainterPath::LineToElement: + p.flags = OnCurve; + break; + case QPainterPath::CurveToElement: { + // cubic bezier curve, we need to reduce to a list of quadratic curves + TTF_POINT list[3*16 + 4]; // we need max 16 subdivisions + list[3] = points->at(points->size() - 1); + list[2] = p; + const QPainterPath::Element &e2 = path.elementAt(++i); + list[1].x = qRound(e2.x * 2048. / ppem); + list[1].y = qRound(-e2.y * 2048. / ppem); + const QPainterPath::Element &e3 = path.elementAt(++i); + list[0].x = qRound(e3.x * 2048. / ppem); + list[0].y = qRound(-e3.y * 2048. / ppem); + + TTF_POINT *base = list; + + bool try_reduce = points->size() > 1 + && points->at(points->size() - 1).flags == OnCurve + && points->at(points->size() - 2).flags == OffCurve; +// qDebug("generating beziers:"); + while (base >= list) { + const int split_limit = 3; +// { +// qDebug("iteration:"); +// TTF_POINT *x = list; +// while (x <= base + 3) { +// qDebug() << " " << QPoint(x->x, x->y); +// ++x; +// } +// } + Q_ASSERT(base - list < 3*16 + 1); + // first see if we can easily reduce the cubic to a quadratic bezier curve + int i1_x = base[1].x + ((base[1].x - base[0].x) >> 1); + int i1_y = base[1].y + ((base[1].y - base[0].y) >> 1); + int i2_x = base[2].x + ((base[2].x - base[3].x) >> 1); + int i2_y = base[2].y + ((base[2].y - base[3].y) >> 1); +// qDebug() << "checking: i1=" << QPoint(i1_x, i1_y) << " i2=" << QPoint(i2_x, i2_y); + if (qAbs(i1_x - i2_x) <= split_limit && qAbs(i1_y - i2_y) <= split_limit) { + // got a quadratic bezier curve + TTF_POINT np; + np.x = (i1_x + i2_x) >> 1; + np.y = (i1_y + i2_y) >> 1; + if (try_reduce) { + // see if we can optimize out the last onCurve point + int mx = (points->at(points->size() - 2).x + base[2].x) >> 1; + int my = (points->at(points->size() - 2).y + base[2].y) >> 1; + if (qAbs(mx - base[3].x) <= split_limit && qAbs(my = base[3].y) <= split_limit) + points->takeLast(); + try_reduce = false; + } + np.flags = OffCurve; + points->append(np); +// qDebug() << " appending offcurve point " << QPoint(np.x, np.y); + base -= 3; + } else { + // need to split +// qDebug() << " -> splitting"; + qint16 a, b, c, d; + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) >> 1; + base[5].x = b = ( base[3].x + d ) >> 1; + c = ( c + d ) >> 1; + base[2].x = a = ( a + c ) >> 1; + base[4].x = b = ( b + c ) >> 1; + base[3].x = ( a + b ) >> 1; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) >> 1; + base[5].y = b = ( base[3].y + d ) >> 1; + c = ( c + d ) >> 1; + base[2].y = a = ( a + c ) >> 1; + base[4].y = b = ( b + c ) >> 1; + base[3].y = ( a + b ) >> 1; + base += 3; + } + } + p = list[0]; + p.flags = OnCurve; + break; + } + case QPainterPath::CurveToDataElement: + Q_ASSERT(false); + break; + } +// qDebug() << " appending oncurve point " << QPoint(p.x, p.y); + points->append(p); + } + int start = endPoints->size() ? endPoints->at(endPoints->size()-1) + 1 : 0; + int end = points->size() - 1; + if (points->at(end).x == points->at(start).x + && points->at(end).y == points->at(start).y) + points->takeLast(); + endPoints->append(points->size() - 1); +} + +static void getBounds(const QList<TTF_POINT> &points, qint16 *xmin, qint16 *xmax, qint16 *ymin, qint16 *ymax) +{ + *xmin = points.at(0).x; + *xmax = *xmin; + *ymin = points.at(0).y; + *ymax = *ymin; + + for (int i = 1; i < points.size(); ++i) { + *xmin = qMin(*xmin, points.at(i).x); + *xmax = qMax(*xmax, points.at(i).x); + *ymin = qMin(*ymin, points.at(i).y); + *ymax = qMax(*ymax, points.at(i).y); + } +} + +static int convertToRelative(QList<TTF_POINT> *points) +{ + // convert points to relative and setup flags +// qDebug() << "relative points:"; + qint16 prev_x = 0; + qint16 prev_y = 0; + int point_array_size = 0; + for (int i = 0; i < points->size(); ++i) { + const int x = points->at(i).x; + const int y = points->at(i).y; + TTF_POINT rel; + rel.x = x - prev_x; + rel.y = y - prev_y; + rel.flags = points->at(i).flags; + Q_ASSERT(rel.flags < 2); + if (!rel.x) { + rel.flags |= XSame; + } else if (rel.x > 0 && rel.x < 256) { + rel.flags |= XShortVector|XShortPositive; + point_array_size++; + } else if (rel.x < 0 && rel.x > -256) { + rel.flags |= XShortVector; + rel.x = -rel.x; + point_array_size++; + } else { + point_array_size += 2; + } + if (!rel.y) { + rel.flags |= YSame; + } else if (rel.y > 0 && rel.y < 256) { + rel.flags |= YShortVector|YShortPositive; + point_array_size++; + } else if (rel.y < 0 && rel.y > -256) { + rel.flags |= YShortVector; + rel.y = -rel.y; + point_array_size++; + } else { + point_array_size += 2; + } + (*points)[i] = rel; +// #define toString(x) ((rel.flags & x) ? #x : "") +// qDebug() << " " << QPoint(rel.x, rel.y) << "flags=" +// << toString(OnCurve) << toString(XShortVector) +// << (rel.flags & XShortVector ? toString(XShortPositive) : toString(XSame)) +// << toString(YShortVector) +// << (rel.flags & YShortVector ? toString(YShortPositive) : toString(YSame)); + + prev_x = x; + prev_y = y; + } + return point_array_size; +} + +static void getGlyphData(QTtfGlyph *glyph, const QList<TTF_POINT> &points, const QList<int> &endPoints, int point_array_size) +{ + const int max_size = 5*sizeof(qint16) // header + + endPoints.size()*sizeof(quint16) // end points of contours + + sizeof(quint16) // instruction length == 0 + + points.size()*(1) // flags + + point_array_size; // coordinates + + glyph->data.resize(max_size); + + QTtfStream s(glyph->data); + s << qint16(endPoints.size()) + << glyph->xMin << glyph->yMin << glyph->xMax << glyph->yMax; + + for (int i = 0; i < endPoints.size(); ++i) + s << quint16(endPoints.at(i)); + s << quint16(0); // instruction length + + // emit flags + for (int i = 0; i < points.size(); ++i) + s << quint8(points.at(i).flags); + // emit points + for (int i = 0; i < points.size(); ++i) { + quint8 flags = points.at(i).flags; + qint16 x = points.at(i).x; + + if (flags & XShortVector) + s << quint8(x); + else if (!(flags & XSame)) + s << qint16(x); + } + for (int i = 0; i < points.size(); ++i) { + quint8 flags = points.at(i).flags; + qint16 y = points.at(i).y; + + if (flags & YShortVector) + s << quint8(y); + else if (!(flags & YSame)) + s << qint16(y); + } + +// qDebug() << "offset=" << s.offset() << "max_size=" << max_size << "point_array_size=" << point_array_size; + Q_ASSERT(s.offset() == max_size); + + glyph->numContours = endPoints.size(); + glyph->numPoints = points.size(); +} + +static QTtfGlyph generateGlyph(int index, const QPainterPath &path, qreal advance, qreal lsb, qreal ppem) +{ + QList<TTF_POINT> points; + QList<int> endPoints; + QTtfGlyph glyph; + glyph.index = index; + glyph.advanceWidth = qRound(advance * 2048. / ppem); + glyph.lsb = qRound(lsb * 2048. / ppem); + + if (!path.elementCount()) { + //qDebug("glyph %d is empty", index); + lsb = 0; + glyph.xMin = glyph.xMax = glyph.yMin = glyph.yMax = 0; + glyph.numContours = 0; + glyph.numPoints = 0; + return glyph; + } + + convertPath(path, &points, &endPoints, ppem); + +// qDebug() << "number of contours=" << endPoints.size(); +// for (int i = 0; i < points.size(); ++i) +// qDebug() << " point[" << i << "] = " << QPoint(points.at(i).x, points.at(i).y) << " flags=" << points.at(i).flags; +// qDebug() << "endPoints:"; +// for (int i = 0; i < endPoints.size(); ++i) +// qDebug() << endPoints.at(i); + + getBounds(points, &glyph.xMin, &glyph.xMax, &glyph.yMin, &glyph.yMax); + int point_array_size = convertToRelative(&points); + getGlyphData(&glyph, points, endPoints, point_array_size); + return glyph; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator <(const QTtfGlyph &g1, const QTtfGlyph &g2) +{ + return g1.index < g2.index; +} + +static QList<QTtfTable> generateGlyphTables(qttf_font_tables &tables, const QList<QTtfGlyph> &_glyphs) +{ + const int max_size_small = 65536*2; + QList<QTtfGlyph> glyphs = _glyphs; + qSort(glyphs); + + Q_ASSERT(tables.maxp.numGlyphs == glyphs.at(glyphs.size()-1).index + 1); + int nGlyphs = tables.maxp.numGlyphs; + + int glyf_size = 0; + for (int i = 0; i < glyphs.size(); ++i) + glyf_size += (glyphs.at(i).data.size() + 3) & ~3; + + tables.head.indexToLocFormat = glyf_size < max_size_small ? 0 : 1; + tables.hhea.numberOfHMetrics = nGlyphs; + + QTtfTable glyf; + glyf.tag = MAKE_TAG('g', 'l', 'y', 'f'); + + QTtfTable loca; + loca.tag = MAKE_TAG('l', 'o', 'c', 'a'); + loca.data.resize(glyf_size < max_size_small ? (nGlyphs+1)*sizeof(quint16) : (nGlyphs+1)*sizeof(quint32)); + QTtfStream ls(loca.data); + + QTtfTable hmtx; + hmtx.tag = MAKE_TAG('h', 'm', 't', 'x'); + hmtx.data.resize(nGlyphs*4); + QTtfStream hs(hmtx.data); + + int pos = 0; + for (int i = 0; i < nGlyphs; ++i) { + int gpos = glyf.data.size(); + quint16 advance = 0; + qint16 lsb = 0; + + if (glyphs[pos].index == i) { + // emit glyph +// qDebug("emitting glyph %d: size=%d", i, glyphs.at(i).data.size()); + glyf.data += glyphs.at(pos).data; + while (glyf.data.size() & 1) + glyf.data.append('\0'); + advance = glyphs.at(pos).advanceWidth; + lsb = glyphs.at(pos).lsb; + ++pos; + } + if (glyf_size < max_size_small) { + // use short loca format + ls << quint16(gpos>>1); + } else { + // use long loca format + ls << quint32(gpos); + } + hs << advance + << lsb; + } + if (glyf_size < max_size_small) { + // use short loca format + ls << quint16(glyf.data.size()>>1); + } else { + // use long loca format + ls << quint32(glyf.data.size()); + } + + Q_ASSERT(loca.data.size() == ls.offset()); + Q_ASSERT(hmtx.data.size() == hs.offset()); + + QList<QTtfTable> list; + list.append(glyf); + list.append(loca); + list.append(hmtx); + return list; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator <(const QTtfTable &t1, const QTtfTable &t2) +{ + return t1.tag < t2.tag; +} + +static QByteArray bindFont(const QList<QTtfTable>& _tables) +{ + QList<QTtfTable> tables = _tables; + + qSort(tables); + + QByteArray font; + const int header_size = sizeof(qint32) + 4*sizeof(quint16); + const int directory_size = 4*sizeof(quint32)*tables.size(); + font.resize(header_size + directory_size); + + int log2 = 0; + int pow = 1; + int n = tables.size() >> 1; + while (n) { + ++log2; + pow <<= 1; + n >>= 1; + } + + quint32 head_offset = 0; + { + QTtfStream f(font); +// Offset Table +// Type Name Description +// qint32 sfnt version 0x00010000 for version 1.0. +// quint16 numTables Number of tables. +// quint16 searchRange (Maximum power of 2 <= numTables) x 16. +// quint16 entrySelector Log2(maximum power of 2 <= numTables). +// quint16 rangeShift NumTables x 16-searchRange. + f << qint32(0x00010000) + << quint16(tables.size()) + << quint16(16*pow) + << quint16(log2) + << quint16(16*(tables.size() - pow)); + +// Table Directory +// Type Name Description +// quint32 tag 4 -byte identifier. +// quint32 checkSum CheckSum for this table. +// quint32 offset Offset from beginning of TrueType font file. +// quint32 length Length of this table. + quint32 table_offset = header_size + directory_size; + for (int i = 0; i < tables.size(); ++i) { + const QTtfTable &t = tables.at(i); + const quint32 size = (t.data.size() + 3) & ~3; + if (t.tag == MAKE_TAG('h', 'e', 'a', 'd')) + head_offset = table_offset; + f << t.tag + << checksum(t.data) + << table_offset + << t.data.size(); + table_offset += size; +#define TAG(x) char(t.tag >> 24) << char((t.tag >> 16) & 0xff) << char((t.tag >> 8) & 0xff) << char(t.tag & 0xff) + //qDebug() << "table " << TAG(t.tag) << "has size " << t.data.size() << "stream at " << f.offset(); + } + } + for (int i = 0; i < tables.size(); ++i) { + const QByteArray &t = tables.at(i).data; + font += t; + int s = t.size(); + while (s & 3) { font += '\0'; ++s; } + } + + if (!head_offset) { + qWarning("QFontSubset: Font misses 'head' table"); + return QByteArray(); + } + + // calculate the fonts checksum and qToBigEndian into 'head's checksum_adjust + quint32 checksum_adjust = 0xB1B0AFBA - checksum(font); + qToBigEndian(checksum_adjust, (uchar *)font.data() + head_offset + 8); + + return font; +} + + +/* + PDF requires the following tables: + + head, hhea, loca, maxp, cvt , prep, glyf, hmtx, fpgm + + This means we don't have to add a os/2, post or name table. cvt , prep and fpgm could be empty + if really required. +*/ + +QByteArray QFontSubset::toTruetype() const +{ + qttf_font_tables font; + memset(&font, 0, sizeof(qttf_font_tables)); + + qreal ppem = fontEngine->fontDef.pixelSize; +#define TO_TTF(x) qRound(x * 2048. / ppem) + QList<QTtfGlyph> glyphs; + + QFontEngine::Properties properties = fontEngine->properties(); + // initialize some stuff needed in createWidthArray + emSquare = 2048; + widths.resize(nGlyphs()); + + // head table + font.head.font_revision = 0x00010000; + font.head.flags = (1 << 2) | (1 << 4); + font.head.created = 0; // ### + font.head.modified = 0; // ### + font.head.xMin = SHRT_MAX; + font.head.xMax = SHRT_MIN; + font.head.yMin = SHRT_MAX; + font.head.yMax = SHRT_MIN; + font.head.macStyle = (fontEngine->fontDef.weight > QFont::Normal) ? 1 : 0; + font.head.macStyle |= (fontEngine->fontDef.styleHint != QFont::StyleNormal) ? 1 : 0; + + // hhea table + font.hhea.ascender = qRound(properties.ascent); + font.hhea.descender = -qRound(properties.descent); + font.hhea.lineGap = qRound(properties.leading); + font.hhea.maxAdvanceWidth = TO_TTF(fontEngine->maxCharWidth()); + font.hhea.minLeftSideBearing = TO_TTF(fontEngine->minLeftBearing()); + font.hhea.minRightSideBearing = TO_TTF(fontEngine->minRightBearing()); + font.hhea.xMaxExtent = SHRT_MIN; + + font.maxp.numGlyphs = 0; + font.maxp.maxPoints = 0; + font.maxp.maxContours = 0; + font.maxp.maxCompositePoints = 0; + font.maxp.maxCompositeContours = 0; + font.maxp.maxComponentElements = 0; + font.maxp.maxComponentDepth = 0; + font.maxp.numGlyphs = nGlyphs(); + + + + uint sumAdvances = 0; + for (int i = 0; i < nGlyphs(); ++i) { + glyph_t g = glyph_indices.at(i); + QPainterPath path; + glyph_metrics_t metric; + fontEngine->getUnscaledGlyph(g, &path, &metric); + if (noEmbed) { + path = QPainterPath(); + if (g == 0) + path.addRect(QRectF(0, 0, 1000, 1000)); + } + QTtfGlyph glyph = generateGlyph(i, path, metric.xoff.toReal(), metric.x.toReal(), properties.emSquare.toReal()); + + font.head.xMin = qMin(font.head.xMin, glyph.xMin); + font.head.xMax = qMax(font.head.xMax, glyph.xMax); + font.head.yMin = qMin(font.head.yMin, glyph.yMin); + font.head.yMax = qMax(font.head.yMax, glyph.yMax); + + font.hhea.xMaxExtent = qMax(font.hhea.xMaxExtent, (qint16)(glyph.lsb + glyph.xMax - glyph.xMin)); + + font.maxp.maxPoints = qMax(font.maxp.maxPoints, glyph.numPoints); + font.maxp.maxContours = qMax(font.maxp.maxContours, glyph.numContours); + + if (glyph.xMax > glyph.xMin) + sumAdvances += glyph.xMax - glyph.xMin; + +// qDebug("adding glyph %d size=%d", glyph.index, glyph.data.size()); + glyphs.append(glyph); + widths[i] = glyph.advanceWidth; + } + + + QList<QTtfTable> tables = generateGlyphTables(font, glyphs); + tables.append(generateHead(font.head)); + tables.append(generateHhea(font.hhea)); + tables.append(generateMaxp(font.maxp)); + // name + QTtfTable name_table; + name_table.tag = MAKE_TAG('n', 'a', 'm', 'e'); + if (!noEmbed) + name_table.data = fontEngine->getSfntTable(name_table.tag); + if (name_table.data.isEmpty()) { + qttf_name_table name; + if (noEmbed) + name.copyright = QLatin1String("Fake font"); + else + name.copyright = QLatin1String(properties.copyright); + name.family = fontEngine->fontDef.family; + name.subfamily = QLatin1String("Regular"); // ###### + name.postscript_name = QLatin1String(properties.postscriptName); + name_table = generateName(name); + } + tables.append(name_table); + + if (!noEmbed) { + QTtfTable os2; + os2.tag = MAKE_TAG('O', 'S', '/', '2'); + os2.data = fontEngine->getSfntTable(os2.tag); + if (!os2.data.isEmpty()) + tables.append(os2); + } + + return bindFont(tables); +} + +// ------------------ Type 1 generation --------------------------- + +// needs at least 6 bytes of space in tmp +static const char *encodeNumber(int num, char *tmp) +{ + const char *ret = tmp; + if(num >= -107 && num <= 107) { + QPdf::toHex((uchar)(num + 139), tmp); + tmp += 2; + } else if (num > 107 && num <= 1131) { + num -= 108; + QPdf::toHex((uchar)((num >> 8) + 247), tmp); + tmp += 2; + QPdf::toHex((uchar)(num & 0xff), tmp); + tmp += 2; + } else if(num < - 107 && num >= -1131) { + num += 108; + num = -num; + QPdf::toHex((uchar)((num >> 8) + 251), tmp); + tmp += 2; + QPdf::toHex((uchar)(num & 0xff), tmp); + tmp += 2; + } else { + *tmp++ = 'f'; + *tmp++ = 'f'; + QPdf::toHex((uchar)(num >> 24), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 16), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 8), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 0), tmp); + tmp += 2; + } + *tmp = 0; +// qDebug("encodeNumber: %d -> '%s'", num, ret); + return ret; +} + +static QByteArray charString(const QPainterPath &path, qreal advance, qreal lsb, qreal ppem) +{ + // the charstring commands we need + const char *hsbw = "0D"; + const char *closepath = "09"; + const char *moveto[3] = { "16", "04", "15" }; + const char *lineto[3] = { "06", "07", "05" }; + const char *rcurveto = "08"; + const char *endchar = "0E"; + + enum { horizontal = 1, vertical = 2 }; + + char tmp[16]; + + qreal factor = 1000./ppem; + + int lsb_i = qRound(lsb*factor); + int advance_i = qRound(advance*factor); +// qDebug("--- charstring"); + + // first of all add lsb and width to the charstring using the hsbw command + QByteArray charstring; + charstring += encodeNumber(lsb_i, tmp); + charstring += encodeNumber(advance_i, tmp); + charstring += hsbw; + + // add the path + int xl = lsb_i; + int yl = 0; + bool openpath = false; + for (int i = 0; i < path.elementCount(); ++i) { + const QPainterPath::Element &elm = path.elementAt(i); + int x = qRound(elm.x*factor); + int y = -qRound(elm.y*factor); + int dx = x - xl; + int dy = y - yl; + if (elm.type == QPainterPath::MoveToElement && openpath) { +// qDebug("closepath %s", closepath); + charstring += closepath; + } + if (elm.type == QPainterPath::MoveToElement || + elm.type == QPainterPath::LineToElement) { + int type = -1; + if (dx || !dy) { + charstring += encodeNumber(dx, tmp); + type += horizontal; +// qDebug("horizontal"); + } + if (dy) { + charstring += encodeNumber(dy, tmp); + type += vertical; +// qDebug("vertical"); + } +// qDebug("moveto/lineto %s", (elm.type == QPainterPath::MoveToElement ? moveto[type] : lineto[type])); + charstring += (elm.type == QPainterPath::MoveToElement ? moveto[type] : lineto[type]); + openpath = true; + xl = x; + yl = y; + } else { + Q_ASSERT(elm.type == QPainterPath::CurveToElement); + const QPainterPath::Element &elm2 = path.elementAt(++i); + const QPainterPath::Element &elm3 = path.elementAt(++i); + int x2 = qRound(elm2.x*factor); + int y2 = -qRound(elm2.y*factor); + int x3 = qRound(elm3.x*factor); + int y3 = -qRound(elm3.y*factor); + charstring += encodeNumber(dx, tmp); + charstring += encodeNumber(dy, tmp); + charstring += encodeNumber(x2 - x, tmp); + charstring += encodeNumber(y2 - y, tmp); + charstring += encodeNumber(x3 - x2, tmp); + charstring += encodeNumber(y3 - y2, tmp); + charstring += rcurveto; + openpath = true; + xl = x3; + yl = y3; +// qDebug("rcurveto"); + } + } + if (openpath) + charstring += closepath; + charstring += endchar; + if (charstring.length() > 240) { + int pos = 240; + while (pos < charstring.length()) { + charstring.insert(pos, '\n'); + pos += 241; + } + } + return charstring; +} + +#ifndef QT_NO_FREETYPE +static const char *helvetica_styles[4] = { + "Helvetica", + "Helvetica-Bold", + "Helvetica-Oblique", + "Helvetica-BoldOblique" +}; +static const char *times_styles[4] = { + "Times-Regular", + "Times-Bold", + "Times-Italic", + "Times-BoldItalic" +}; +static const char *courier_styles[4] = { + "Courier", + "Courier-Bold", + "Courier-Oblique", + "Courier-BoldOblique" +}; +#endif + +QByteArray QFontSubset::toType1() const +{ + QFontEngine::Properties properties = fontEngine->properties(); + QVector<int> reverseMap = getReverseMap(); + + QByteArray font; + QPdf::ByteStream s(&font); + + QByteArray id = QByteArray::number(object_id); + QByteArray psname = properties.postscriptName; + psname.replace(' ', ""); + + standard_font = false; + +#ifndef QT_NO_FREETYPE + FT_Face face = ft_face(fontEngine); + if (face && !FT_IS_SCALABLE(face)) { + int style = 0; + if (fontEngine->fontDef.style) + style += 2; + if (fontEngine->fontDef.weight >= QFont::Bold) + style++; + if (fontEngine->fontDef.family.contains(QLatin1String("Helvetica"))) { + psname = helvetica_styles[style]; + standard_font = true; + } else if (fontEngine->fontDef.family.contains(QLatin1String("Times"))) { + psname = times_styles[style]; + standard_font = true; + } else if (fontEngine->fontDef.family.contains(QLatin1String("Courier"))) { + psname = courier_styles[style]; + standard_font = true; + } + } +#endif + s << "/F" << id << "-Base\n"; + if (standard_font) { + s << '/' << psname << " findfont\n" + "0 dict copy dup /NumGlyphs 0 put dup /CMap 256 array put def\n"; + } else { + s << "<<\n"; + if(!psname.isEmpty()) + s << "/FontName /" << psname << '\n'; + s << "/FontInfo <</FsType " << (int)fontEngine->fsType << ">>\n" + "/FontType 1\n" + "/PaintType 0\n" + "/FontMatrix [.001 0 0 .001 0 0]\n" + "/FontBBox { 0 0 0 0 }\n" + "/Private <<\n" + "/password 5839\n" + "/MinFeature {16 16}\n" + "/BlueValues []\n" + "/lenIV -1\n" + ">>\n" + "/CharStrings << >>\n" + "/NumGlyphs 0\n" + "/CMap 256 array\n" + ">> def\n"; + } + s << type1AddedGlyphs(); + downloaded_glyphs = glyph_indices.size(); + + return font; +} + +QByteArray QFontSubset::type1AddedGlyphs() const +{ + if (downloaded_glyphs == glyph_indices.size()) + return QByteArray(); + + QFontEngine::Properties properties = fontEngine->properties(); + QVector<int> reverseMap = getReverseMap(); + QByteArray glyphs; + QPdf::ByteStream s(&glyphs); + + int nGlyphs = glyph_indices.size(); + QByteArray id = QByteArray::number(object_id); + + s << 'F' << id << "-Base [\n"; + for (int i = downloaded_glyphs; i < nGlyphs; ++i) { + glyph_t g = glyph_indices.at(i); + QPainterPath path; + glyph_metrics_t metric; + fontEngine->getUnscaledGlyph(g, &path, &metric); + QByteArray charstring = charString(path, metric.xoff.toReal(), metric.x.toReal(), + properties.emSquare.toReal()); + s << glyphName(i, reverseMap); + if (!standard_font) + s << "\n<" << charstring << ">\n"; + } + s << (standard_font ? "] T1AddMapping\n" : "] T1AddGlyphs\n"); + return glyphs; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/text/qfontsubset_p.h b/src/gui/text/qfontsubset_p.h new file mode 100644 index 0000000000..a09aced355 --- /dev/null +++ b/src/gui/text/qfontsubset_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTSUBSET_P_H +#define QFONTSUBSET_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/qfontengine_p.h" + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +class QFontSubset +{ +public: + QFontSubset(QFontEngine *fe, int obj_id = 0) + : object_id(obj_id), noEmbed(false), fontEngine(fe), downloaded_glyphs(0), standard_font(false) + { fontEngine->ref.ref(); addGlyph(0); } + ~QFontSubset() { + if (!fontEngine->ref.deref()) + delete fontEngine; + } + + QByteArray toTruetype() const; + QByteArray toType1() const; + QByteArray type1AddedGlyphs() const; + QByteArray widthArray() const; + QByteArray createToUnicodeMap() const; + QVector<int> getReverseMap() const; + QByteArray glyphName(unsigned int glyph, const QVector<int> reverseMap) const; + + static QByteArray glyphName(unsigned short unicode, bool symbol); + + int addGlyph(int index); + const int object_id; + bool noEmbed; + QFontEngine *fontEngine; + QList<int> glyph_indices; + mutable int downloaded_glyphs; + mutable bool standard_font; + int nGlyphs() const { return glyph_indices.size(); } + mutable QFixed emSquare; + mutable QVector<QFixed> widths; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QFONTSUBSET_P_H diff --git a/src/gui/text/qfragmentmap.cpp b/src/gui/text/qfragmentmap.cpp new file mode 100644 index 0000000000..8ada5bbab7 --- /dev/null +++ b/src/gui/text/qfragmentmap.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qtools_p.h> + +#include "qfragmentmap_p.h" + + diff --git a/src/gui/text/qfragmentmap_p.h b/src/gui/text/qfragmentmap_p.h new file mode 100644 index 0000000000..4057142b34 --- /dev/null +++ b/src/gui/text/qfragmentmap_p.h @@ -0,0 +1,888 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFRAGMENTMAP_P_H +#define QFRAGMENTMAP_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 "QtCore/qglobal.h" +#include <stdlib.h> +#include <private/qtools_p.h> + +QT_BEGIN_NAMESPACE + + +template <int N = 1> +class QFragment +{ +public: + quint32 parent; + quint32 left; + quint32 right; + quint32 color; + quint32 size_left_array[N]; + quint32 size_array[N]; + enum {size_array_max = N }; +}; + +template <class Fragment> +class QFragmentMapData +{ + enum Color { Red, Black }; +public: + QFragmentMapData(); + ~QFragmentMapData(); + + void init(); + + class Header + { + public: + quint32 root; // this relies on being at the same position as parent in the fragment struct + quint32 tag; + quint32 freelist; + quint32 node_count; + quint32 allocated; + }; + + + enum {fragmentSize = sizeof(Fragment) }; + + + int length(uint field = 0) const; + + + inline Fragment *fragment(uint index) { + return (fragments + index); + } + inline const Fragment *fragment(uint index) const { + return (fragments + index); + } + + + inline Fragment &F(uint index) { return fragments[index] ; } + inline const Fragment &F(uint index) const { return fragments[index] ; } + + inline bool isRoot(uint index) const { + return !fragment(index)->parent; + } + + inline uint position(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + const Fragment *f = fragment(node); + uint offset = f->size_left_array[field]; + while (f->parent) { + uint p = f->parent; + f = fragment(p); + if (f->right == node) + offset += f->size_left_array[field] + f->size_array[field]; + node = p; + } + return offset; + } + inline uint sizeRight(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + uint sr = 0; + const Fragment *f = fragment(node); + node = f->right; + while (node) { + f = fragment(node); + sr += f->size_left_array[field] + f->size_array[field]; + node = f->right; + } + return sr; + } + inline uint sizeLeft(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + return fragment(node)->size_left_array[field]; + } + + + inline uint size(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + return fragment(node)->size_array[field]; + } + + inline void setSize(uint node, int new_size, uint field = 0) { + Q_ASSERT(field < Fragment::size_array_max); + Fragment *f = fragment(node); + int diff = new_size - f->size_array[field]; + f->size_array[field] = new_size; + while (f->parent) { + uint p = f->parent; + f = fragment(p); + if (f->left == node) + f->size_left_array[field] += diff; + node = p; + } + } + + + uint findNode(int k, uint field = 0) const; + + uint insert_single(int key, uint length); + uint erase_single(uint f); + + uint minimum(uint n) const { + while (n && fragment(n)->left) + n = fragment(n)->left; + return n; + } + + uint maximum(uint n) const { + while (n && fragment(n)->right) + n = fragment(n)->right; + return n; + } + + uint next(uint n) const; + uint previous(uint n) const; + + inline uint root() const { + Q_ASSERT(!head->root || !fragment(head->root)->parent); + return head->root; + } + inline void setRoot(uint new_root) { + Q_ASSERT(!head->root || !fragment(new_root)->parent); + head->root = new_root; + } + + inline bool isValid(uint n) const { + return n > 0 && n != head->freelist; + } + + union { + Header *head; + Fragment *fragments; + }; + +private: + + void rotateLeft(uint x); + void rotateRight(uint x); + void rebalance(uint x); + void removeAndRebalance(uint z); + + uint createFragment(); + void freeFragment(uint f); + +}; + +template <class Fragment> +QFragmentMapData<Fragment>::QFragmentMapData() + : fragments(0) +{ + init(); +} + +template <class Fragment> +void QFragmentMapData<Fragment>::init() +{ + // the following code will realloc an existing fragment or create a new one. + // it will also ignore errors when shrinking an existing fragment. + Fragment *newFragments = (Fragment *)realloc(fragments, 64*fragmentSize); + if (newFragments) { + fragments = newFragments; + head->allocated = 64; + } + Q_CHECK_PTR(fragments); + + head->tag = (((quint32)'p') << 24) | (((quint32)'m') << 16) | (((quint32)'a') << 8) | 'p'; //TAG('p', 'm', 'a', 'p'); + head->root = 0; + head->freelist = 1; + head->node_count = 0; + // mark all items to the right as unused + F(head->freelist).right = 0; +} + +template <class Fragment> +QFragmentMapData<Fragment>::~QFragmentMapData() +{ + free(fragments); +} + +template <class Fragment> +uint QFragmentMapData<Fragment>::createFragment() +{ + Q_ASSERT(head->freelist <= head->allocated); + + uint freePos = head->freelist; + if (freePos == head->allocated) { + // need to create some free space + uint needed = qAllocMore((freePos+1)*fragmentSize, 0); + Q_ASSERT(needed/fragmentSize > head->allocated); + Fragment *newFragments = (Fragment *)realloc(fragments, needed); + Q_CHECK_PTR(newFragments); + fragments = newFragments; + head->allocated = needed/fragmentSize; + F(freePos).right = 0; + } + + uint nextPos = F(freePos).right; + if (!nextPos) { + nextPos = freePos+1; + if (nextPos < head->allocated) + F(nextPos).right = 0; + } + + head->freelist = nextPos; + + ++head->node_count; + + return freePos; +} + +template <class Fragment> +void QFragmentMapData<Fragment>::freeFragment(uint i) +{ + F(i).right = head->freelist; + head->freelist = i; + + --head->node_count; +} + + +template <class Fragment> +uint QFragmentMapData<Fragment>::next(uint n) const { + Q_ASSERT(n); + if (F(n).right) { + n = F(n).right; + while (F(n).left) + n = F(n).left; + } else { + uint y = F(n).parent; + while (F(n).parent && n == F(y).right) { + n = y; + y = F(y).parent; + } + n = y; + } + return n; +} + +template <class Fragment> +uint QFragmentMapData<Fragment>::previous(uint n) const { + if (!n) + return maximum(root()); + + if (F(n).left) { + n = F(n).left; + while (F(n).right) + n = F(n).right; + } else { + uint y = F(n).parent; + while (F(n).parent && n == F(y).left) { + n = y; + y = F(y).parent; + } + n = y; + } + return n; +} + + +/* + x y + \ / \ + y --> x b + / \ \ + a b a +*/ +template <class Fragment> +void QFragmentMapData<Fragment>::rotateLeft(uint x) +{ + uint p = F(x).parent; + uint y = F(x).right; + + + if (y) { + F(x).right = F(y).left; + if (F(y).left) + F(F(y).left).parent = x; + F(y).left = x; + F(y).parent = p; + } else { + F(x).right = 0; + } + if (!p) { + Q_ASSERT(head->root == x); + head->root = y; + } + else if (x == F(p).left) + F(p).left = y; + else + F(p).right = y; + F(x).parent = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] += F(x).size_left_array[field] + F(x).size_array[field]; +} + + +/* + x y + / / \ + y --> a x + / \ / + a b b +*/ +template <class Fragment> +void QFragmentMapData<Fragment>::rotateRight(uint x) +{ + uint y = F(x).left; + uint p = F(x).parent; + + if (y) { + F(x).left = F(y).right; + if (F(y).right) + F(F(y).right).parent = x; + F(y).right = x; + F(y).parent = p; + } else { + F(x).left = 0; + } + if (!p) { + Q_ASSERT(head->root == x); + head->root = y; + } + else if (x == F(p).right) + F(p).right = y; + else + F(p).left = y; + F(x).parent = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(x).size_left_array[field] -= F(y).size_left_array[field] + F(y).size_array[field]; +} + + +template <class Fragment> +void QFragmentMapData<Fragment>::rebalance(uint x) +{ + F(x).color = Red; + + while (F(x).parent && F(F(x).parent).color == Red) { + uint p = F(x).parent; + uint pp = F(p).parent; + Q_ASSERT(pp); + if (p == F(pp).left) { + uint y = F(pp).right; + if (y && F(y).color == Red) { + F(p).color = Black; + F(y).color = Black; + F(pp).color = Red; + x = pp; + } else { + if (x == F(p).right) { + x = p; + rotateLeft(x); + p = F(x).parent; + pp = F(p).parent; + } + F(p).color = Black; + if (pp) { + F(pp).color = Red; + rotateRight(pp); + } + } + } else { + uint y = F(pp).left; + if (y && F(y).color == Red) { + F(p).color = Black; + F(y).color = Black; + F(pp).color = Red; + x = pp; + } else { + if (x == F(p).left) { + x = p; + rotateRight(x); + p = F(x).parent; + pp = F(p).parent; + } + F(p).color = Black; + if (pp) { + F(pp).color = Red; + rotateLeft(pp); + } + } + } + } + F(root()).color = Black; +} + + +template <class Fragment> +uint QFragmentMapData<Fragment>::erase_single(uint z) +{ + uint w = previous(z); + uint y = z; + uint x; + uint p; + + if (!F(y).left) { + x = F(y).right; + } else if (!F(y).right) { + x = F(y).left; + } else { + y = F(y).right; + while (F(y).left) + y = F(y).left; + x = F(y).right; + } + + if (y != z) { + F(F(z).left).parent = y; + F(y).left = F(z).left; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] = F(z).size_left_array[field]; + if (y != F(z).right) { + /* + z y + / \ / \ + a b a b + / / + ... --> ... + / / + y x + / \ + 0 x + */ + p = F(y).parent; + if (x) + F(x).parent = p; + F(p).left = x; + F(y).right = F(z).right; + F(F(z).right).parent = y; + uint n = p; + while (n != y) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(n).size_left_array[field] -= F(y).size_array[field]; + n = F(n).parent; + } + } else { + /* + z y + / \ / \ + a y --> a x + / \ + 0 x + */ + p = y; + } + uint zp = F(z).parent; + if (!zp) { + Q_ASSERT(head->root == z); + head->root = y; + } else if (F(zp).left == z) { + F(zp).left = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(zp).size_left_array[field] -= F(z).size_array[field]; + } else { + F(zp).right = y; + } + F(y).parent = zp; + // Swap the colors + uint c = F(y).color; + F(y).color = F(z).color; + F(z).color = c; + y = z; + } else { + /* + p p p p + / / \ \ + z --> x z --> x + | | + x x + */ + p = F(z).parent; + if (x) + F(x).parent = p; + if (!p) { + Q_ASSERT(head->root == z); + head->root = x; + } else if (F(p).left == z) { + F(p).left = x; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] -= F(z).size_array[field]; + } else { + F(p).right = x; + } + } + uint n = z; + while (F(n).parent) { + uint p = F(n).parent; + if (F(p).left == n) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] -= F(z).size_array[field]; + } + n = p; + } + + freeFragment(z); + + + if (F(y).color != Red) { + while (F(x).parent && (x == 0 || F(x).color == Black)) { + if (x == F(p).left) { + uint w = F(p).right; + if (F(w).color == Red) { + F(w).color = Black; + F(p).color = Red; + rotateLeft(p); + w = F(p).right; + } + if ((F(w).left == 0 || F(F(w).left).color == Black) && + (F(w).right == 0 || F(F(w).right).color == Black)) { + F(w).color = Red; + x = p; + p = F(x).parent; + } else { + if (F(w).right == 0 || F(F(w).right).color == Black) { + if (F(w).left) + F(F(w).left).color = Black; + F(w).color = Red; + rotateRight(F(p).right); + w = F(p).right; + } + F(w).color = F(p).color; + F(p).color = Black; + if (F(w).right) + F(F(w).right).color = Black; + rotateLeft(p); + break; + } + } else { + uint w = F(p).left; + if (F(w).color == Red) { + F(w).color = Black; + F(p).color = Red; + rotateRight(p); + w = F(p).left; + } + if ((F(w).right == 0 || F(F(w).right).color == Black) && + (F(w).left == 0 || F(F(w).left).color == Black)) { + F(w).color = Red; + x = p; + p = F(x).parent; + } else { + if (F(w).left == 0 || F(F(w).left).color == Black) { + if (F(w).right) + F(F(w).right).color = Black; + F(w).color = Red; + rotateLeft(F(p).left); + w = F(p).left; + } + F(w).color = F(p).color; + F(p).color = Black; + if (F(w).left) + F(F(w).left).color = Black; + rotateRight(p); + break; + } + } + } + if (x) + F(x).color = Black; + } + + return w; +} + +template <class Fragment> +uint QFragmentMapData<Fragment>::findNode(int k, uint field) const +{ + Q_ASSERT(field < Fragment::size_array_max); + uint x = root(); + + uint s = k; + while (x) { + if (sizeLeft(x, field) <= s) { + if (s < sizeLeft(x, field) + size(x, field)) + return x; + s -= sizeLeft(x, field) + size(x, field); + x = F(x).right; + } else { + x = F(x).left; + } + } + return 0; +} + +template <class Fragment> +uint QFragmentMapData<Fragment>::insert_single(int key, uint length) +{ + Q_ASSERT(!findNode(key) || (int)this->position(findNode(key)) == key); + + uint z = createFragment(); + + F(z).left = 0; + F(z).right = 0; + F(z).size_array[0] = length; + for (uint field = 1; field < Fragment::size_array_max; ++field) + F(z).size_array[field] = 1; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(z).size_left_array[field] = 0; + + uint y = 0; + uint x = root(); + + Q_ASSERT(!x || F(x).parent == 0); + + uint s = key; + bool right = false; + while (x) { + y = x; + if (s <= F(x).size_left_array[0]) { + x = F(x).left; + right = false; + } else { + s -= F(x).size_left_array[0] + F(x).size_array[0]; + x = F(x).right; + right = true; + } + } + + F(z).parent = y; + if (!y) { + head->root = z; + } else if (!right) { + F(y).left = z; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] = F(z).size_array[field]; + } else { + F(y).right = z; + } + while (y && F(y).parent) { + uint p = F(y).parent; + if (F(p).left == y) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] += F(z).size_array[field]; + } + y = p; + } + rebalance(z); + + return z; +} + + +template <class Fragment> +int QFragmentMapData<Fragment>::length(uint field) const { + uint root = this->root(); + return root ? sizeLeft(root, field) + size(root, field) + sizeRight(root, field) : 0; +} + + +template <class Fragment> // NOTE: must inherit QFragment +class QFragmentMap +{ +public: + class Iterator + { + public: + QFragmentMap *pt; + quint32 n; + + Iterator() : pt(0), n(0) {} + Iterator(QFragmentMap *p, int node) : pt(p), n(node) {} + Iterator(const Iterator& it) : pt(it.pt), n(it.n) {} + + inline bool atEnd() const { return !n; } + + bool operator==(const Iterator& it) const { return pt == it.pt && n == it.n; } + bool operator!=(const Iterator& it) const { return pt != it.pt || n != it.n; } + bool operator<(const Iterator &it) const { return position() < it.position(); } + + Fragment *operator*() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator*() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + Fragment *operator->() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator->() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + int position() const { Q_ASSERT(!atEnd()); return pt->data.position(n); } + const Fragment *value() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + Fragment *value() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + Iterator& operator++() { + n = pt->data.next(n); + return *this; + } + Iterator& operator--() { + n = pt->data.previous(n); + return *this; + } + + }; + + + class ConstIterator + { + public: + const QFragmentMap *pt; + quint32 n; + + /** + * Functions + */ + ConstIterator() : pt(0), n(0) {} + ConstIterator(const QFragmentMap *p, int node) : pt(p), n(node) {} + ConstIterator(const ConstIterator& it) : pt(it.pt), n(it.n) {} + ConstIterator(const Iterator& it) : pt(it.pt), n(it.n) {} + + inline bool atEnd() const { return !n; } + + bool operator==(const ConstIterator& it) const { return pt == it.pt && n == it.n; } + bool operator!=(const ConstIterator& it) const { return pt != it.pt || n != it.n; } + bool operator<(const ConstIterator &it) const { return position() < it.position(); } + + const Fragment *operator*() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator->() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + int position() const { Q_ASSERT(!atEnd()); return pt->data.position(n); } + int size() const { Q_ASSERT(!atEnd()); return pt->data.size(n); } + const Fragment *value() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + ConstIterator& operator++() { + n = pt->data.next(n); + return *this; + } + ConstIterator& operator--() { + n = pt->data.previous(n); + return *this; + } + }; + + + QFragmentMap() {} + ~QFragmentMap() + { + if (!data.fragments) + return; // in case of out-of-memory, we won't have fragments + for (Iterator it = begin(); !it.atEnd(); ++it) + it.value()->free(); + } + + inline void clear() { + for (Iterator it = begin(); !it.atEnd(); ++it) + it.value()->free(); + data.init(); + } + + inline Iterator begin() { return Iterator(this, data.minimum(data.root())); } + inline Iterator end() { return Iterator(this, 0); } + inline ConstIterator begin() const { return ConstIterator(this, data.minimum(data.root())); } + inline ConstIterator end() const { return ConstIterator(this, 0); } + + inline ConstIterator last() const { return ConstIterator(this, data.maximum(data.root())); } + + inline bool isEmpty() const { return data.head->node_count == 0; } + inline int numNodes() const { return data.head->node_count; } + int length(uint field = 0) const { return data.length(field); } + + Iterator find(int k, uint field = 0) { return Iterator(this, data.findNode(k, field)); } + ConstIterator find(int k, uint field = 0) const { return ConstIterator(this, data.findNode(k, field)); } + + uint findNode(int k, uint field = 0) const { return data.findNode(k, field); } + + uint insert_single(int key, uint length) + { + uint f = data.insert_single(key, length); + if (f != 0) { + Fragment *frag = fragment(f); + Q_ASSERT(frag); + frag->initialize(); + } + return f; + } + uint erase_single(uint f) + { + if (f != 0) { + Fragment *frag = fragment(f); + Q_ASSERT(frag); + frag->free(); + } + return data.erase_single(f); + } + + inline Fragment *fragment(uint index) { + Q_ASSERT(index != 0); + return data.fragment(index); + } + inline const Fragment *fragment(uint index) const { + Q_ASSERT(index != 0); + return data.fragment(index); + } + inline uint position(uint node, uint field = 0) const { return data.position(node, field); } + inline bool isValid(uint n) const { return data.isValid(n); } + inline uint next(uint n) const { return data.next(n); } + inline uint previous(uint n) const { return data.previous(n); } + inline uint size(uint node, uint field = 0) const { return data.size(node, field); } + inline void setSize(uint node, int new_size, uint field = 0) + { data.setSize(node, new_size, field); + if (node != 0 && field == 0) { + Fragment *frag = fragment(node); + Q_ASSERT(frag); + frag->invalidate(); + } + } + + inline int firstNode() const { return data.minimum(data.root()); } + +private: + friend class Iterator; + friend class ConstIterator; + + QFragmentMapData<Fragment> data; + + QFragmentMap(const QFragmentMap& m); + QFragmentMap& operator= (const QFragmentMap& m); +}; + +QT_END_NAMESPACE + +#endif // QFRAGMENTMAP_P_H diff --git a/src/gui/text/qglyphs.cpp b/src/gui/text/qglyphs.cpp new file mode 100644 index 0000000000..b8a418de44 --- /dev/null +++ b/src/gui/text/qglyphs.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#if !defined(QT_NO_RAWFONT) + +#include "qglyphs.h" +#include "qglyphs_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGlyphs + \brief the QGlyphs class provides direct access to the internal glyphs in a font + \since 4.8 + + \ingroup text + \mainclass + + When Qt displays a string of text encoded in Unicode, it will first convert the Unicode points + into a list of glyph indexes and a list of positions based on one or more fonts. The Unicode + representation of the text and the QFont object will in this case serve as a convenient + abstraction that hides the details of what actually takes place when displaying the text + on-screen. For instance, by the time the text actually reaches the screen, it may be represented + by a set of fonts in addition to the one specified by the user, e.g. in case the originally + selected font did not support all the writing systems contained in the text. + + Under certain circumstances, it can be useful as an application developer to have more low-level + control over which glyphs in a specific font are drawn to the screen. This could for instance + be the case in applications that use an external font engine and text shaper together with Qt. + QGlyphs provides an interface to the raw data needed to get text on the screen. It + contains a list of glyph indexes, a position for each glyph and a font. + + It is the user's responsibility to ensure that the selected font actually contains the + provided glyph indexes. + + QTextLayout::glyphs() or QTextFragment::glyphs() can be used to convert unicode encoded text + into a list of QGlyphs objects, and QPainter::drawGlyphs() can be used to draw the glyphs. + + \note Please note that QRawFont is considered local to the thread in which it is constructed, + which in turn means that a new QRawFont will have to be created and set on the QGlyphs if it is + moved to a different thread. If the QGlyphs contains a reference to a QRawFont from a different + thread than the current, it will not be possible to draw the glyphs using a QPainter, as the + QRawFont is considered invalid and inaccessible in this case. +*/ + + +/*! + Constructs an empty QGlyphs object. +*/ +QGlyphs::QGlyphs() : d(new QGlyphsPrivate) +{ +} + +/*! + Constructs a QGlyphs object which is a copy of \a other. +*/ +QGlyphs::QGlyphs(const QGlyphs &other) +{ + d = other.d; +} + +/*! + Destroys the QGlyphs. +*/ +QGlyphs::~QGlyphs() +{ + // Required for QExplicitlySharedDataPointer +} + +/*! + \internal +*/ +void QGlyphs::detach() +{ + if (d->ref != 1) + d.detach(); +} + +/*! + Assigns \a other to this QGlyphs object. +*/ +QGlyphs &QGlyphs::operator=(const QGlyphs &other) +{ + d = other.d; + return *this; +} + +/*! + Compares \a other to this QGlyphs object. Returns true if the list of glyph indexes, + the list of positions and the font are all equal, otherwise returns false. +*/ +bool QGlyphs::operator==(const QGlyphs &other) const +{ + return ((d == other.d) + || (d->glyphIndexes == other.d->glyphIndexes + && d->glyphPositions == other.d->glyphPositions + && d->overline == other.d->overline + && d->underline == other.d->underline + && d->strikeOut == other.d->strikeOut + && d->font == other.d->font)); +} + +/*! + Compares \a other to this QGlyphs object. Returns true if any of the list of glyph + indexes, the list of positions or the font are different, otherwise returns false. +*/ +bool QGlyphs::operator!=(const QGlyphs &other) const +{ + return !(*this == other); +} + +/*! + \internal + + Adds together the lists of glyph indexes and positions in \a other and this QGlyphs + object and returns the result. The font in the returned QGlyphs will be the same as in + this QGlyphs object. +*/ +QGlyphs QGlyphs::operator+(const QGlyphs &other) const +{ + QGlyphs ret(*this); + ret += other; + return ret; +} + +/*! + \internal + + Appends the glyph indexes and positions in \a other to this QGlyphs object and returns + a reference to the current object. +*/ +QGlyphs &QGlyphs::operator+=(const QGlyphs &other) +{ + detach(); + + d->glyphIndexes += other.d->glyphIndexes; + d->glyphPositions += other.d->glyphPositions; + + return *this; +} + +/*! + Returns the font selected for this QGlyphs object. + + \sa setFont() +*/ +QRawFont QGlyphs::font() const +{ + return d->font; +} + +/*! + Sets the font in which to look up the glyph indexes to \a font. + + \sa font(), setGlyphIndexes() +*/ +void QGlyphs::setFont(const QRawFont &font) +{ + detach(); + d->font = font; +} + +/*! + Returns the glyph indexes for this QGlyphs object. + + \sa setGlyphIndexes(), setPositions() +*/ +QVector<quint32> QGlyphs::glyphIndexes() const +{ + return d->glyphIndexes; +} + +/*! + Set the glyph indexes for this QGlyphs object to \a glyphIndexes. The glyph indexes must + be valid for the selected font. +*/ +void QGlyphs::setGlyphIndexes(const QVector<quint32> &glyphIndexes) +{ + detach(); + d->glyphIndexes = glyphIndexes; +} + +/*! + Returns the position of the edge of the baseline for each glyph in this set of glyph indexes. +*/ +QVector<QPointF> QGlyphs::positions() const +{ + return d->glyphPositions; +} + +/*! + Sets the positions of the edge of the baseline for each glyph in this set of glyph indexes to + \a positions. +*/ +void QGlyphs::setPositions(const QVector<QPointF> &positions) +{ + detach(); + d->glyphPositions = positions; +} + +/*! + Clears all data in the QGlyphs object. +*/ +void QGlyphs::clear() +{ + detach(); + d->glyphPositions = QVector<QPointF>(); + d->glyphIndexes = QVector<quint32>(); + d->font = QRawFont(); + d->strikeOut = false; + d->overline = false; + d->underline = false; +} + +/*! + Returns true if this QGlyphs should be painted with an overline decoration. + + \sa setOverline() +*/ +bool QGlyphs::overline() const +{ + return d->overline; +} + +/*! + Indicates that this QGlyphs should be painted with an overline decoration if \a overline is true. + Otherwise the QGlyphs should be painted with no overline decoration. + + \sa overline() +*/ +void QGlyphs::setOverline(bool overline) +{ + detach(); + d->overline = overline; +} + +/*! + Returns true if this QGlyphs should be painted with an underline decoration. + + \sa setUnderline() +*/ +bool QGlyphs::underline() const +{ + return d->underline; +} + +/*! + Indicates that this QGlyphs should be painted with an underline decoration if \a underline is + true. Otherwise the QGlyphs should be painted with no underline decoration. + + \sa underline() +*/ +void QGlyphs::setUnderline(bool underline) +{ + detach(); + d->underline = underline; +} + +/*! + Returns true if this QGlyphs should be painted with a strike out decoration. + + \sa setStrikeOut() +*/ +bool QGlyphs::strikeOut() const +{ + return d->strikeOut; +} + +/*! + Indicates that this QGlyphs should be painted with an strike out decoration if \a strikeOut is + true. Otherwise the QGlyphs should be painted with no strike out decoration. + + \sa strikeOut() +*/ +void QGlyphs::setStrikeOut(bool strikeOut) +{ + detach(); + d->strikeOut = strikeOut; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qglyphs.h b/src/gui/text/qglyphs.h new file mode 100644 index 0000000000..4d7dcaf554 --- /dev/null +++ b/src/gui/text/qglyphs.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLYPHS_H +#define QGLYPHS_H + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qvector.h> +#include <QtCore/qpoint.h> +#include <QtGui/qrawfont.h> + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGlyphsPrivate; +class Q_GUI_EXPORT QGlyphs +{ +public: + QGlyphs(); + QGlyphs(const QGlyphs &other); + ~QGlyphs(); + + QRawFont font() const; + void setFont(const QRawFont &font); + + QVector<quint32> glyphIndexes() const; + void setGlyphIndexes(const QVector<quint32> &glyphIndexes); + + QVector<QPointF> positions() const; + void setPositions(const QVector<QPointF> &positions); + + void clear(); + + QGlyphs &operator=(const QGlyphs &other); + bool operator==(const QGlyphs &other) const; + bool operator!=(const QGlyphs &other) const; + + void setOverline(bool overline); + bool overline() const; + + void setUnderline(bool underline); + bool underline() const; + + void setStrikeOut(bool strikeOut); + bool strikeOut() const; + +private: + friend class QGlyphsPrivate; + friend class QTextLine; + + QGlyphs operator+(const QGlyphs &other) const; + QGlyphs &operator+=(const QGlyphs &other); + + void detach(); + QExplicitlySharedDataPointer<QGlyphsPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_RAWFONT + +#endif // QGLYPHS_H diff --git a/src/gui/text/qglyphs_p.h b/src/gui/text/qglyphs_p.h new file mode 100644 index 0000000000..944f777d4a --- /dev/null +++ b/src/gui/text/qglyphs_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLYPHS_P_H +#define QGLYPHS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "qglyphs.h" +#include "qrawfont.h" + +#include <qfont.h> + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QGlyphsPrivate: public QSharedData +{ +public: + QGlyphsPrivate() + : overline(false) + , underline(false) + , strikeOut(false) + { + } + + QGlyphsPrivate(const QGlyphsPrivate &other) + : QSharedData(other) + , glyphIndexes(other.glyphIndexes) + , glyphPositions(other.glyphPositions) + , font(other.font) + , overline(other.overline) + , underline(other.underline) + , strikeOut(other.strikeOut) + { + } + + QVector<quint32> glyphIndexes; + QVector<QPointF> glyphPositions; + QRawFont font; + + uint overline : 1; + uint underline : 1; + uint strikeOut : 1; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGLYPHS_P_H + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qpfutil.cpp b/src/gui/text/qpfutil.cpp new file mode 100644 index 0000000000..734d4c3592 --- /dev/null +++ b/src/gui/text/qpfutil.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +static const QFontEngineQPF::TagType tagTypes[QFontEngineQPF::NumTags] = { + QFontEngineQPF::StringType, // FontName + QFontEngineQPF::StringType, // FileName + QFontEngineQPF::UInt32Type, // FileIndex + QFontEngineQPF::UInt32Type, // FontRevision + QFontEngineQPF::StringType, // FreeText + QFontEngineQPF::FixedType, // Ascent + QFontEngineQPF::FixedType, // Descent + QFontEngineQPF::FixedType, // Leading + QFontEngineQPF::FixedType, // XHeight + QFontEngineQPF::FixedType, // AverageCharWidth + QFontEngineQPF::FixedType, // MaxCharWidth + QFontEngineQPF::FixedType, // LineThickness + QFontEngineQPF::FixedType, // MinLeftBearing + QFontEngineQPF::FixedType, // MinRightBearing + QFontEngineQPF::FixedType, // UnderlinePosition + QFontEngineQPF::UInt8Type, // GlyphFormat + QFontEngineQPF::UInt8Type, // PixelSize + QFontEngineQPF::UInt8Type, // Weight + QFontEngineQPF::UInt8Type, // Style + QFontEngineQPF::StringType, // EndOfHeader + QFontEngineQPF::BitFieldType// WritingSystems +}; + + diff --git a/src/gui/text/qplatformfontdatabase_qpa.cpp b/src/gui/text/qplatformfontdatabase_qpa.cpp new file mode 100644 index 0000000000..6fa25e7ea2 --- /dev/null +++ b/src/gui/text/qplatformfontdatabase_qpa.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformfontdatabase_qpa.h" +#include <QtGui/private/qfontengine_p.h> +#include <QtGui/private/qfontengine_qpa_p.h> +#include <QtCore/QLibraryInfo> +#include <QtCore/QDir> + +QT_BEGIN_NAMESPACE + +extern void qt_registerFont(const QString &familyname, const QString &foundryname, int weight, + QFont::Style style, int stretch, bool antialiased,bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *hanlde); + +void QPlatformFontDatabase::registerQPF2Font(const QByteArray &dataArray, void *handle) +{ + if (dataArray.size() == 0) + return; + + const uchar *data = reinterpret_cast<const uchar *>(dataArray.constData()); + if (QFontEngineQPA::verifyHeader(data, dataArray.size())) { + QString fontName = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_FontName).toString(); + int pixelSize = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_PixelSize).toInt(); + QVariant weight = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_Weight); + QVariant style = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_Style); + QByteArray writingSystemBits = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_WritingSystems).toByteArray(); + + if (!fontName.isEmpty() && pixelSize) { + QFont::Weight fontWeight = QFont::Normal; + if (weight.type() == QVariant::Int || weight.type() == QVariant::UInt) + fontWeight = QFont::Weight(weight.toInt()); + + QFont::Style fontStyle = static_cast<QFont::Style>(style.toInt()); + + QSupportedWritingSystems writingSystems; + for (int i = 0; i < writingSystemBits.count(); ++i) { + uchar currentByte = writingSystemBits.at(i); + for (int j = 0; j < 8; ++j) { + if (currentByte & 1) + writingSystems.setSupported(QFontDatabase::WritingSystem(i * 8 + j)); + currentByte >>= 1; + } + } + QFont::Stretch stretch = QFont::Unstretched; + registerFont(fontName,QString(),fontWeight,fontStyle,stretch,true,false,pixelSize,writingSystems,handle); + } + } else { + qDebug() << "header verification of QPF2 font failed. maybe it is corrupt?"; + } +} + +void QPlatformFontDatabase::registerFont(const QString &familyname, const QString &foundryname, QFont::Weight weight, + QFont::Style style, QFont::Stretch stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *usrPtr) +{ + if (scalable) + pixelSize = 0; + qt_registerFont(familyname,foundryname,weight,style,stretch,antialiased,scalable,pixelSize,writingSystems,usrPtr); +} + +class QWritingSystemsPrivate +{ +public: + QWritingSystemsPrivate() + : ref(1) + , vector(QFontDatabase::WritingSystemsCount,false) + { + } + + QWritingSystemsPrivate(const QWritingSystemsPrivate *other) + : ref(1) + , vector(other->vector) + { + } + + QAtomicInt ref; + QVector<bool> vector; +}; + +QSupportedWritingSystems::QSupportedWritingSystems() +{ + d = new QWritingSystemsPrivate; +} + +QSupportedWritingSystems::QSupportedWritingSystems(const QSupportedWritingSystems &other) +{ + d = other.d; + d->ref.ref(); +} + +QSupportedWritingSystems &QSupportedWritingSystems::operator=(const QSupportedWritingSystems &other) +{ + if (d != other.d) { + other.d->ref.ref(); + if (!d->ref.deref()) + delete d; + d = other.d; + } + return *this; +} + +QSupportedWritingSystems::~QSupportedWritingSystems() +{ + if (!d->ref.deref()) + delete d; +} + +void QSupportedWritingSystems::detach() +{ + if (d->ref != 1) { + QWritingSystemsPrivate *newd = new QWritingSystemsPrivate(d); + if (!d->ref.deref()) + delete d; + d = newd; + } +} + +void QSupportedWritingSystems::setSupported(QFontDatabase::WritingSystem writingSystem, bool support) +{ + detach(); + d->vector[writingSystem] = support; +} + +bool QSupportedWritingSystems::supported(QFontDatabase::WritingSystem writingSystem) const +{ + return d->vector.at(writingSystem); +} + +/*! + \class QSupportedWritingSystems + \brief The QSupportedWritingSystems class is used when registering fonts with the internal Qt + fontdatabase + \ingroup painting + + Its to provide an easy to use interface for indicating what writing systems a specific font + supports. + +*/ + +/*! + This function is called once at startup by Qts internal fontdatabase. Reimplement this function + in a subclass for a convenient place to initialise the internal fontdatabase. + + The default implementation looks in the fontDir() location and registers all qpf2 fonts. +*/ +void QPlatformFontDatabase::populateFontDatabase() +{ + QString fontpath = fontDir(); + + if(!QFile::exists(fontpath)) { + qFatal("QFontDatabase: Cannot find font directory %s - is Qt installed correctly?", + qPrintable(fontpath)); + } + + QDir dir(fontpath); + dir.setNameFilters(QStringList() << QLatin1String("*.qpf2")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray fileName = QFile::encodeName(dir.absoluteFilePath(dir[i])); + QFile file(QString::fromLocal8Bit(fileName)); + if (file.open(QFile::ReadOnly)) { + const QByteArray fileData = file.readAll(); + QByteArray *fileDataPtr = new QByteArray(fileData); + registerQPF2Font(fileData, fileDataPtr); + } + } +} + +/*! + +*/ +QFontEngine *QPlatformFontDatabase::fontEngine(const QFontDef &fontDef, QUnicodeTables::Script script, void *handle) +{ + Q_UNUSED(script); + Q_UNUSED(handle); + QByteArray *fileDataPtr = static_cast<QByteArray *>(handle); + QFontEngineQPA *engine = new QFontEngineQPA(fontDef,*fileDataPtr); + //qDebug() << fontDef.pixelSize << fontDef.weight << fontDef.style << fontDef.stretch << fontDef.styleHint << fontDef.styleStrategy << fontDef.family << script; + return engine; +} + +/*! + +*/ +QStringList QPlatformFontDatabase::fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const +{ + Q_UNUSED(family); + Q_UNUSED(style); + Q_UNUSED(styleHint); + Q_UNUSED(script); + return QStringList(); +} + +/*! + Adds an application font. Returns a list of family names, or an empty list if the font could + not be added +*/ +QStringList QPlatformFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName) +{ + Q_UNUSED(fontData); + Q_UNUSED(fileName); + + qWarning("This plugin does not support application fonts"); + return QStringList(); +} + +/*! + +*/ +void QPlatformFontDatabase::releaseHandle(void *handle) +{ + QByteArray *fileDataPtr = static_cast<QByteArray *>(handle); + delete fileDataPtr; +} + +/*! + +*/ +QString QPlatformFontDatabase::fontDir() const +{ + QString fontpath = QString::fromLocal8Bit(qgetenv("QT_QPA_FONTDIR")); + if (fontpath.isEmpty()) { +#ifndef QT_NO_SETTINGS + fontpath = QLibraryInfo::location(QLibraryInfo::LibrariesPath); + fontpath += QLatin1String("/fonts"); +#endif + } + + return fontpath; +} + +/*! + \class QPlatformFontDatabase + \brief The QPlatformFontDatabase makes it possible to customize how fonts are picked up, and + and how they are rendered + + \ingroup painting + + QPlatformFontDatabase is the superclass which is intended to let platform implementations use + native font handling. + + Qt has its internal fontdatabase which it uses to pick up available fonts. To be able + to populate this database subclass this class, and reimplement populateFontDatabase(). + + Use the function registerFont to populate the internal fontdatabase. + + Sometimes a specified font does not have the required glyphs, then the fallbackForFamily + function is called. + + \sa QSupportedWritingSystems +*/ +QT_END_NAMESPACE diff --git a/src/gui/text/qplatformfontdatabase_qpa.h b/src/gui/text/qplatformfontdatabase_qpa.h new file mode 100644 index 0000000000..e0e4f04d89 --- /dev/null +++ b/src/gui/text/qplatformfontdatabase_qpa.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMFONTDATABASE_QPA_H +#define QPLATFORMFONTDATABASE_QPA_H + +#include <QtCore/qconfig.h> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QList> +#include <QtGui/QFontDatabase> +#include <QtGui/private/qfont_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWritingSystemsPrivate; + +class Q_GUI_EXPORT QSupportedWritingSystems +{ +public: + + QSupportedWritingSystems(); + QSupportedWritingSystems(const QSupportedWritingSystems &other); + QSupportedWritingSystems &operator=(const QSupportedWritingSystems &other); + ~QSupportedWritingSystems(); + + void setSupported(QFontDatabase::WritingSystem, bool supported = true); + bool supported(QFontDatabase::WritingSystem) const; + +private: + void detach(); + + QWritingSystemsPrivate *d; + + friend Q_GUI_EXPORT bool operator==(const QSupportedWritingSystems &, const QSupportedWritingSystems &); + friend Q_GUI_EXPORT bool operator!=(const QSupportedWritingSystems &, const QSupportedWritingSystems &); +}; + +Q_GUI_EXPORT bool operator==(const QSupportedWritingSystems &, const QSupportedWritingSystems &); +Q_GUI_EXPORT bool operator!=(const QSupportedWritingSystems &, const QSupportedWritingSystems &); + +class QFontRequestPrivate; + +class Q_GUI_EXPORT QPlatformFontDatabase +{ +public: + virtual void populateFontDatabase(); + virtual QFontEngine *fontEngine(const QFontDef &fontDef, QUnicodeTables::Script script, void *handle); + virtual QStringList fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const; + virtual QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName); + virtual void releaseHandle(void *handle); + + virtual QString fontDir() const; + + //callback + static void registerQPF2Font(const QByteArray &dataArray, void *handle); + static void registerFont(const QString &familyname, const QString &foundryname, QFont::Weight weight, + QFont::Style style, QFont::Stretch stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *handle); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMFONTDATABASE_QPA_H diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp new file mode 100644 index 0000000000..4a715c27cc --- /dev/null +++ b/src/gui/text/qrawfont.cpp @@ -0,0 +1,612 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont.h" +#include "qrawfont_p.h" + +#include <QtCore/qthread.h> +#include <QtCore/qendian.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QRawFont + \brief The QRawFont class provides access to a single physical instance of a font. + \since 4.8 + + \ingroup text + \mainclass + + \note QRawFont is a low level class. For most purposes QFont is a more appropriate class. + + Most commonly, when presenting text in a user interface, the exact fonts used + to render the characters is to some extent unknown. This can be the case for several + reasons: For instance, the actual, physical fonts present on the target system could be + unexpected to the developers, or the text could contain user selected styles, sizes or + writing systems that are not supported by font chosen in the code. + + Therefore, Qt's QFont class really represents a query for fonts. When text is interpreted, + Qt will do its best to match the text to the query, but depending on the support, different + fonts can be used behind the scenes. + + For most use cases, this is both expected and necessary, as it minimizes the possibility of + text in the user interface being undisplayable. In some cases, however, more direct control + over the process might be useful. It is for these use cases the QRawFont class exists. + + A QRawFont object represents a single, physical instance of a given font in a given pixel size. + I.e. in the typical case it represents a set of TrueType or OpenType font tables and uses a + user specified pixel size to convert metrics into logical pixel units. In can be used in + combination with the QGlyphs class to draw specific glyph indexes at specific positions, and + also have accessors to some relevant data in the physical font. + + QRawFont only provides support for the main font technologies: GDI and DirectWrite on Windows + platforms, FreeType on Symbian and Linux platforms and CoreText on Mac OS X. For other + font back-ends, the APIs will be disabled. + + QRawFont can be constructed in a number of ways: + \list + \o \l It can be constructed by calling QTextLayout::glyphs() or QTextFragment::glyphs(). The + returned QGlyphs objects will contain QRawFont objects which represent the actual fonts + used to render each portion of the text. + \o \l It can be constructed by passing a QFont object to QRawFont::fromFont(). The function + will return a QRawFont object representing the font that will be selected as response to + the QFont query and the selected writing system. + \o \l It can be constructed by passing a file name or QByteArray directly to the QRawFont + constructor, or by calling loadFromFile() or loadFromData(). In this case, the + font will not be registered in QFontDatabase, and it will not be available as part of + regular font selection. + \endlist + + QRawFont is considered local to the thread in which it is constructed (either using a + constructor, or by calling loadFromData() or loadFromFile()). The QRawFont cannot be moved to a + different thread, but will have to be recreated in the thread in question. + + \note For the requirement of caching glyph indexes and font selections for static text to avoid + reshaping and relayouting in the inner loop of an application, a better choice is the QStaticText + class, since it optimizes the memory cost of the cache and also provides the possibility of paint + engine specific caches for an additional speed-up. +*/ + +/*! + \enum QRawFont::AntialiasingType + + This enum represents the different ways a glyph can be rasterized in the function + alphaMapForGlyph(). + + \value PixelAntialiasing Will rasterize by measuring the coverage of the shape on whole pixels. + The returned image contains the alpha values of each pixel based on the coverage of + the glyph shape. + \value SubPixelAntialiasing Will rasterize by measuring the coverage of each subpixel, + returning a separate alpha value for each of the red, green and blue components of + each pixel. +*/ + +/*! + Constructs an invalid QRawFont. +*/ +QRawFont::QRawFont() + : d(new QRawFontPrivate) +{ +} + +/*! + Constructs a QRawFont representing the font contained in the file referenced by \a fileName, + with \a pixelSize size in pixels, and the selected \a hintingPreference. + + \note The referenced file must contain a TrueType or OpenType font. +*/ +QRawFont::QRawFont(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference) + : d(new QRawFontPrivate) +{ + loadFromFile(fileName, pixelSize, hintingPreference); +} + +/*! + Constructs a QRawFont representing the font contained in \a fontData. + + \note The data must contain a TrueType or OpenType font. +*/ +QRawFont::QRawFont(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) + : d(new QRawFontPrivate) +{ + loadFromData(fontData, pixelSize, hintingPreference); +} + +/*! + Creates a QRawFont which is a copy of \a other. +*/ +QRawFont::QRawFont(const QRawFont &other) +{ + d = other.d; +} + +/*! + Destroys the QRawFont +*/ +QRawFont::~QRawFont() +{ +} + +/*! + Assigns \a other to this QRawFont. +*/ +QRawFont &QRawFont::operator=(const QRawFont &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if the QRawFont is valid and false otherwise. +*/ +bool QRawFont::isValid() const +{ + Q_ASSERT(d->thread == 0 || d->thread == QThread::currentThread()); + return d->fontEngine != 0; +} + +/*! + Replaces the current QRawFont with the contents of the file references by \a fileName. + + The file must reference a TrueType or OpenType font. + + \sa loadFromData() +*/ +void QRawFont::loadFromFile(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) + loadFromData(file.readAll(), pixelSize, hintingPreference); +} + +/*! + Replaces the current QRawFont with the contents of \a fontData. + + The \a fontData must contain a TrueType or OpenType font. + + \sa loadFromFile() +*/ +void QRawFont::loadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + detach(); + d->cleanUp(); + d->hintingPreference = hintingPreference; + d->thread = QThread::currentThread(); + d->platformLoadFromData(fontData, pixelSize, hintingPreference); +} + +/*! + This function returns a rasterized image of the glyph at a given \a glyphIndex in the underlying + font, if the QRawFont is valid, otherwise it will return an invalid QImage. + + If \a antialiasingType is set to QRawFont::SubPixelAntialiasing, then the resulting image will be + in QImage::Format_RGB32 and the RGB values of each pixel will represent the subpixel opacities of + the pixel in the rasterization of the glyph. Otherwise, the image will be in the format of + QImage::Format_A8 and each pixel will contain the opacity of the pixel in the rasterization. + + \sa pathForGlyph(), QPainter::drawGlyphs() +*/ +QImage QRawFont::alphaMapForGlyph(quint32 glyphIndex, AntialiasingType antialiasingType, + const QTransform &transform) const +{ + if (!isValid()) + return QImage(); + + if (antialiasingType == SubPixelAntialiasing) + return d->fontEngine->alphaRGBMapForGlyph(glyphIndex, QFixed(), 0, transform); + else + return d->fontEngine->alphaMapForGlyph(glyphIndex, QFixed(), transform); +} + +/*! + This function returns the shape of the glyph at a given \a glyphIndex in the underlying font + if the QRawFont is valid. Otherwise, it returns an empty QPainterPath. + + The returned glyph will always be unhinted. + + \sa alphaMapForGlyph(), QPainterPath::addText() +*/ +QPainterPath QRawFont::pathForGlyph(quint32 glyphIndex) const +{ + if (!isValid()) + return QPainterPath(); + + QFixedPoint position; + QPainterPath path; + d->fontEngine->addGlyphsToPath(&glyphIndex, &position, 1, &path, 0); + return path; +} + +/*! + Returns true if this QRawFont is equal to \a other. Otherwise, returns false. +*/ +bool QRawFont::operator==(const QRawFont &other) const +{ + return d->fontEngine == other.d->fontEngine; +} + +/*! + Returns the ascent of this QRawFont in pixel units. + + \sa QFontMetricsF::ascent() +*/ +qreal QRawFont::ascent() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->ascent().toReal(); +} + +/*! + Returns the descent of this QRawFont in pixel units. + + \sa QFontMetricsF::descent() +*/ +qreal QRawFont::descent() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->descent().toReal(); +} + +/*! + Returns the pixel size set for this QRawFont. The pixel size affects how glyphs are + rasterized, the size of glyphs returned by pathForGlyph(), and is used to convert + internal metrics from design units to logical pixel units. + + \sa setPixelSize() +*/ +int QRawFont::pixelSize() const +{ + if (!isValid()) + return -1; + + return d->fontEngine->fontDef.pixelSize; +} + +/*! + Returns the number of design units define the width and height of the em square + for this QRawFont. This value is used together with the pixel size when converting design metrics + to pixel units, as the internal metrics are specified in design units and the pixel size gives + the size of 1 em in pixels. + + \sa pixelSize(), setPixelSize() +*/ +qreal QRawFont::unitsPerEm() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->emSquareSize().toReal(); +} + +/*! + Returns the family name of this QRawFont. +*/ +QString QRawFont::familyName() const +{ + if (!isValid()) + return QString(); + + return d->fontEngine->fontDef.family; +} + +/*! + Returns the style of this QRawFont. + + \sa QFont::style() +*/ +QFont::Style QRawFont::style() const +{ + if (!isValid()) + return QFont::StyleNormal; + + return QFont::Style(d->fontEngine->fontDef.style); +} + +/*! + Returns the weight of this QRawFont. + + \sa QFont::weight() +*/ +int QRawFont::weight() const +{ + if (!isValid()) + return -1; + + return int(d->fontEngine->fontDef.weight); +} + +/*! + Converts a string of unicode points to glyph indexes using the CMAP table in the + underlying font. Note that in cases where there are other tables in the font that affect the + shaping of the text, the returned glyph indexes will not correctly represent the rendering + of the text. To get the correctly shaped text, you can use QTextLayout to lay out and shape the + text, and then call QTextLayout::glyphs() to get the set of glyph index list and QRawFont pairs. + + \sa advancesForGlyphIndexes(), QGlyphs, QTextLayout::glyphs(), QTextFragment::glyphs() +*/ +QVector<quint32> QRawFont::glyphIndexesForString(const QString &text) const +{ + if (!isValid()) + return QVector<quint32>(); + + int nglyphs = text.size(); + QVarLengthGlyphLayoutArray glyphs(nglyphs); + if (!d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &nglyphs, + QTextEngine::GlyphIndicesOnly)) { + glyphs.resize(nglyphs); + if (!d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &nglyphs, + QTextEngine::GlyphIndicesOnly)) { + Q_ASSERT_X(false, Q_FUNC_INFO, "stringToCMap shouldn't fail twice"); + return QVector<quint32>(); + } + } + + QVector<quint32> glyphIndexes; + for (int i=0; i<nglyphs; ++i) + glyphIndexes.append(glyphs.glyphs[i]); + + return glyphIndexes; +} + +/*! + Returns the QRawFont's advances for each of the \a glyphIndexes in pixel units. The advances + give the distance from the position of a given glyph to where the next glyph should be drawn + to make it appear as if the two glyphs are unspaced. + + \sa QTextLine::horizontalAdvance(), QFontMetricsF::width() +*/ +QVector<QPointF> QRawFont::advancesForGlyphIndexes(const QVector<quint32> &glyphIndexes) const +{ + if (!isValid()) + return QVector<QPointF>(); + + int numGlyphs = glyphIndexes.size(); + QVarLengthGlyphLayoutArray glyphs(numGlyphs); + qMemCopy(glyphs.glyphs, glyphIndexes.data(), numGlyphs * sizeof(quint32)); + + d->fontEngine->recalcAdvances(&glyphs, 0); + + QVector<QPointF> advances; + for (int i=0; i<numGlyphs; ++i) + advances.append(QPointF(glyphs.advances_x[i].toReal(), glyphs.advances_y[i].toReal())); + + return advances; +} + +/*! + Returns the hinting preference used to construct this QRawFont. + + \sa QFont::hintingPreference() +*/ +QFont::HintingPreference QRawFont::hintingPreference() const +{ + if (!isValid()) + return QFont::PreferDefaultHinting; + + return d->hintingPreference; +} + +/*! + Retrieves the sfnt table named \a tagName from the underlying physical font, or an empty + byte array if no such table was found. The returned font table's byte order is Big Endian, like + the sfnt format specifies. The \a tagName must be four characters long and should be formatted + in the default endianness of the current platform. +*/ +QByteArray QRawFont::fontTable(const char *tagName) const +{ + if (!isValid()) + return QByteArray(); + + const quint32 *tagId = reinterpret_cast<const quint32 *>(tagName); + return d->fontEngine->getSfntTable(qToBigEndian(*tagId)); +} + +// From qfontdatabase.cpp +extern QList<QFontDatabase::WritingSystem> qt_determine_writing_systems_from_truetype_bits(quint32 unicodeRange[4], quint32 codePageRange[2]); + +/*! + Returns a list of writing systems supported by the font according to designer supplied + information in the font file. Please note that this does not guarantee support for a + specific unicode point in the font. You can use the supportsCharacter() to check support + for a single, specific character. + + \note The list is determined based on the unicode ranges and codepage ranges set in the font's + OS/2 table and requires such a table to be present in the underlying font file. + + \sa supportsCharacter() +*/ +QList<QFontDatabase::WritingSystem> QRawFont::supportedWritingSystems() const +{ + if (isValid()) { + QByteArray os2Table = fontTable("OS/2"); + if (!os2Table.isEmpty() && os2Table.size() > 86) { + char *data = os2Table.data(); + quint32 *bigEndianUnicodeRanges = reinterpret_cast<quint32 *>(data + 42); + quint32 *bigEndianCodepageRanges = reinterpret_cast<quint32 *>(data + 78); + + quint32 unicodeRanges[4]; + quint32 codepageRanges[2]; + + for (int i=0; i<4; ++i) { + if (i < 2) + codepageRanges[i] = qFromBigEndian(bigEndianCodepageRanges[i]); + unicodeRanges[i] = qFromBigEndian(bigEndianUnicodeRanges[i]); + } + + return qt_determine_writing_systems_from_truetype_bits(unicodeRanges, codepageRanges); + } + } + + return QList<QFontDatabase::WritingSystem>(); +} + +/*! + Returns true if the font has a glyph that corresponds to the given \a character. + + \sa supportedWritingSystems() +*/ +bool QRawFont::supportsCharacter(const QChar &character) const +{ + if (!isValid()) + return false; + + return d->fontEngine->canRender(&character, 1); +} + +/*! + Returns true if the font has a glyph that corresponds to the UCS-4 encoded character \a ucs4. + + \sa supportedWritingSystems() +*/ +bool QRawFont::supportsCharacter(quint32 ucs4) const +{ + if (!isValid()) + return false; + + QString str = QString::fromUcs4(&ucs4, 1); + return d->fontEngine->canRender(str.constData(), str.size()); +} + +// qfontdatabase.cpp +extern int qt_script_for_writing_system(QFontDatabase::WritingSystem writingSystem); + +/*! + Fetches the physical representation based on a \a font query. The physical font returned is + the font that will be preferred by Qt in order to display text in the selected \a writingSystem. +*/ +QRawFont QRawFont::fromFont(const QFont &font, QFontDatabase::WritingSystem writingSystem) +{ +#if defined(Q_WS_MAC) + QTextLayout layout(QFontDatabase::writingSystemSample(writingSystem), font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QList<QGlyphs> list = layout.glyphs(); + if (list.size()) { + // Pick the one matches the family name we originally requested, + // if none of them match, just pick the first one + for (int i = 0; i < list.size(); i++) { + QGlyphs glyphs = list.at(i); + QRawFont rawfont = glyphs.font(); + if (rawfont.familyName() == font.family()) + return rawfont; + } + return list.at(0).font(); + } + return QRawFont(); +#else + QFontPrivate *font_d = QFontPrivate::get(font); + int script = qt_script_for_writing_system(writingSystem); + QFontEngine *fe = font_d->engineForScript(script); + + if (fe != 0 && fe->type() == QFontEngine::Multi) { + QFontEngineMulti *multiEngine = static_cast<QFontEngineMulti *>(fe); + fe = multiEngine->engine(0); + if (fe == 0) { + multiEngine->loadEngine(0); + fe = multiEngine->engine(0); + } + } + + if (fe != 0) { + QRawFont rawFont; + rawFont.d.data()->fontEngine = fe; + rawFont.d.data()->fontEngine->ref.ref(); + rawFont.d.data()->hintingPreference = font.hintingPreference(); + return rawFont; + } else { + return QRawFont(); + } +#endif +} + +/*! + Sets the pixel size with which this font should be rendered to \a pixelSize. +*/ +void QRawFont::setPixelSize(int pixelSize) +{ + detach(); + d->platformSetPixelSize(pixelSize); +} + +/*! + \internal +*/ +void QRawFont::detach() +{ + if (d->ref != 1) + d.detach(); +} + +/*! + \internal +*/ +void QRawFontPrivate::cleanUp() +{ + platformCleanUp(); + if (fontEngine != 0) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + fontEngine = 0; + } + hintingPreference = QFont::PreferDefaultHinting; +} + +#endif // QT_NO_RAWFONT + +QT_END_NAMESPACE diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h new file mode 100644 index 0000000000..96dc838ede --- /dev/null +++ b/src/gui/text/qrawfont.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRAWFONT_H +#define QRAWFONT_H + +#include <QtCore/qstring.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qglobal.h> +#include <QtCore/qobject.h> +#include <QtCore/qpoint.h> +#include <QtGui/qfont.h> +#include <QtGui/qtransform.h> +#include <QtGui/qfontdatabase.h> + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QRawFontPrivate; +class Q_GUI_EXPORT QRawFont +{ +public: + enum AntialiasingType { + PixelAntialiasing, + SubPixelAntialiasing + }; + + QRawFont(); + QRawFont(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference = QFont::PreferDefaultHinting); + QRawFont(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference = QFont::PreferDefaultHinting); + QRawFont(const QRawFont &other); + ~QRawFont(); + + bool isValid() const; + + QRawFont &operator=(const QRawFont &other); + bool operator==(const QRawFont &other) const; + + QString familyName() const; + + QFont::Style style() const; + int weight() const; + + QVector<quint32> glyphIndexesForString(const QString &text) const; + QVector<QPointF> advancesForGlyphIndexes(const QVector<quint32> &glyphIndexes) const; + + QImage alphaMapForGlyph(quint32 glyphIndex, + AntialiasingType antialiasingType = SubPixelAntialiasing, + const QTransform &transform = QTransform()) const; + QPainterPath pathForGlyph(quint32 glyphIndex) const; + + void setPixelSize(int pixelSize); + int pixelSize() const; + + QFont::HintingPreference hintingPreference() const; + + qreal ascent() const; + qreal descent() const; + + qreal unitsPerEm() const; + + void loadFromFile(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference); + + void loadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference); + + bool supportsCharacter(quint32 ucs4) const; + bool supportsCharacter(const QChar &character) const; + QList<QFontDatabase::WritingSystem> supportedWritingSystems() const; + + QByteArray fontTable(const char *tagName) const; + + static QRawFont fromFont(const QFont &font, + QFontDatabase::WritingSystem writingSystem = QFontDatabase::Any); + +private: + friend class QRawFontPrivate; + + void detach(); + + QExplicitlySharedDataPointer<QRawFontPrivate> d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_RAWFONT + +#endif // QRAWFONT_H diff --git a/src/gui/text/qrawfont_ft.cpp b/src/gui/text/qrawfont_ft.cpp new file mode 100644 index 0000000000..eefbd92118 --- /dev/null +++ b/src/gui/text/qrawfont_ft.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont_p.h" +#include "qfontengine_ft_p.h" + +#if defined(Q_WS_X11) +# include "qfontengine_x11_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QFontEngineFTRawFont + +#if defined(Q_WS_X11) + : public QFontEngineX11FT +#else + : public QFontEngineFT +#endif + +{ +public: + QFontEngineFTRawFont(const QFontDef &fontDef) +#if defined(Q_WS_X11) + : QFontEngineX11FT(fontDef) +#else + : QFontEngineFT(fontDef) +#endif + { + } + + void updateFamilyNameAndStyle() + { + fontDef.family = QString::fromAscii(freetype->face->family_name); + + if (freetype->face->style_flags & FT_STYLE_FLAG_ITALIC) + fontDef.style = QFont::StyleItalic; + + if (freetype->face->style_flags & FT_STYLE_FLAG_BOLD) + fontDef.weight = QFont::Bold; + } + + bool initFromData(const QByteArray &fontData) + { + FaceId faceId; + faceId.filename = ""; + faceId.index = 0; + + return init(faceId, true, Format_None, fontData); + } + + bool initFromFontEngine(QFontEngine *oldFontEngine) + { + QFontEngineFT *fe = static_cast<QFontEngineFT *>(oldFontEngine); + + // Increase the reference of this QFreetypeFace since one more QFontEngineFT + // will be using it + fe->freetype->ref.ref(); + if (!init(fe->faceId(), fe->antialias, fe->defaultFormat, fe->freetype)) + return false; + + default_load_flags = fe->default_load_flags; + default_hint_style = fe->default_hint_style; + antialias = fe->antialias; + transform = fe->transform; + embolden = fe->embolden; + subpixelType = fe->subpixelType; + lcdFilterType = fe->lcdFilterType; + canUploadGlyphsToServer = fe->canUploadGlyphsToServer; + embeddedbitmap = fe->embeddedbitmap; + +#if defined(Q_WS_X11) + xglyph_format = static_cast<QFontEngineX11FT *>(fe)->xglyph_format; +#endif + return true; + } +}; + + +void QRawFontPrivate::platformCleanUp() +{ + // Font engine handles all resources +} + +void QRawFontPrivate::platformLoadFromData(const QByteArray &fontData, int pixelSize, + QFont::HintingPreference hintingPreference) +{ + Q_ASSERT(fontEngine == 0); + + QFontDef fontDef; + fontDef.pixelSize = pixelSize; + + QFontEngineFTRawFont *fe = new QFontEngineFTRawFont(fontDef); + if (!fe->initFromData(fontData)) { + delete fe; + return; + } + + fe->updateFamilyNameAndStyle(); + + switch (hintingPreference) { + case QFont::PreferNoHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintNone); + break; + case QFont::PreferFullHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintFull); + break; + case QFont::PreferVerticalHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintLight); + break; + default: + // Leave it as it is + break; + } + + fontEngine = fe; + fontEngine->ref.ref(); +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + + QFontDef fontDef; + fontDef.pixelSize = pixelSize; + QFontEngineFTRawFont *fe = new QFontEngineFTRawFont(fontDef); + if (!fe->initFromFontEngine(oldFontEngine)) { + delete fe; + return; + } + + fontEngine = fe; + fontEngine->fontDef = oldFontEngine->fontDef; + fontEngine->fontDef.pixelSize = pixelSize; + fontEngine->ref.ref(); + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qrawfont_mac.cpp b/src/gui/text/qrawfont_mac.cpp new file mode 100644 index 0000000000..56005c61f5 --- /dev/null +++ b/src/gui/text/qrawfont_mac.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont_p.h" +#include "qfontengine_coretext_p.h" + +QT_BEGIN_NAMESPACE + +void QRawFontPrivate::platformCleanUp() +{ +} + +extern int qt_defaultDpi(); + +void QRawFontPrivate::platformLoadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + // Mac OS X ignores it + Q_UNUSED(hintingPreference); + + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(NULL, + fontData.constData(), fontData.size(), NULL); + + CGFontRef cgFont = CGFontCreateWithDataProvider(dataProvider); + + if (cgFont == NULL) { + qWarning("QRawFont::platformLoadFromData: CGFontCreateWithDataProvider failed"); + } else { + QFontDef def; + def.pixelSize = pixelSize; + def.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + fontEngine = new QCoreTextFontEngine(cgFont, def); + CFRelease(cgFont); + fontEngine->ref.ref(); + } +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + + QFontDef fontDef = oldFontEngine->fontDef; + fontDef.pixelSize = pixelSize; + fontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + + QCoreTextFontEngine *ctFontEngine = static_cast<QCoreTextFontEngine *>(oldFontEngine); + Q_ASSERT(ctFontEngine->cgFont); + + fontEngine = new QCoreTextFontEngine(ctFontEngine->cgFont, fontDef); + fontEngine->ref.ref(); + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qrawfont_p.h b/src/gui/text/qrawfont_p.h new file mode 100644 index 0000000000..f9a9ab55cd --- /dev/null +++ b/src/gui/text/qrawfont_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRAWFONTPRIVATE_P_H +#define QRAWFONTPRIVATE_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 "qrawfont.h" +#include "qfontengine_p.h" +#include <QtCore/qthreadstorage.h> + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_NAMESPACE + +namespace { class CustomFontFileLoader; } +class Q_AUTOTEST_EXPORT QRawFontPrivate +{ +public: + QRawFontPrivate() + : fontEngine(0) + , hintingPreference(QFont::PreferDefaultHinting) + , thread(0) +#if defined(Q_WS_WIN) + , fontHandle(NULL) + , ptrAddFontMemResourceEx(NULL) + , ptrRemoveFontMemResourceEx(NULL) +#endif + {} + + QRawFontPrivate(const QRawFontPrivate &other) + : hintingPreference(other.hintingPreference) + , thread(other.thread) +#if defined(Q_WS_WIN) + , fontHandle(NULL) + , ptrAddFontMemResourceEx(other.ptrAddFontMemResourceEx) + , ptrRemoveFontMemResourceEx(other.ptrRemoveFontMemResourceEx) + , uniqueFamilyName(other.uniqueFamilyName) +#endif + { + fontEngine = other.fontEngine; + if (fontEngine != 0) + fontEngine->ref.ref(); + } + + ~QRawFontPrivate() + { + Q_ASSERT(ref == 0); + cleanUp(); + } + + void cleanUp(); + void platformCleanUp(); + void platformLoadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference); + void platformSetPixelSize(int pixelSize); + + static QRawFontPrivate *get(const QRawFont &font) { return font.d.data(); } + + QFontEngine *fontEngine; + QFont::HintingPreference hintingPreference; + QThread *thread; + QAtomicInt ref; + +#if defined(Q_WS_WIN) + HANDLE fontHandle; + + typedef HANDLE (WINAPI *PtrAddFontMemResourceEx)(PVOID, DWORD, PVOID, DWORD *); + typedef BOOL (WINAPI *PtrRemoveFontMemResourceEx)(HANDLE); + + PtrAddFontMemResourceEx ptrAddFontMemResourceEx; + PtrRemoveFontMemResourceEx ptrRemoveFontMemResourceEx; + + QString uniqueFamilyName; + +#endif // Q_WS_WIN +}; + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT + +#endif // QRAWFONTPRIVATE_P_H diff --git a/src/gui/text/qrawfont_win.cpp b/src/gui/text/qrawfont_win.cpp new file mode 100644 index 0000000000..fb5c6f46b6 --- /dev/null +++ b/src/gui/text/qrawfont_win.cpp @@ -0,0 +1,750 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qrawfont_p.h" +#include <private/qsystemlibrary_p.h> + +#if !defined(QT_NO_DIRECTWRITE) +# include "qfontenginedirectwrite_p.h" +# include <dwrite.h> +#endif + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_NAMESPACE + +namespace { + + template<typename T> + struct BigEndian + { + quint8 data[sizeof(T)]; + + operator T() const + { + T littleEndian = 0; + for (int i=0; i<sizeof(T); ++i) { + littleEndian |= data[i] << ((sizeof(T) - i - 1) * 8); + } + + return littleEndian; + } + + BigEndian<T> &operator=(const T &t) + { + for (int i=0; i<sizeof(T); ++i) { + data[i] = ((t >> (sizeof(T) - i - 1) * 8) & 0xff); + } + + return *this; + } + }; + +# pragma pack(1) + + // Common structure for all formats of the "name" table + struct NameTable + { + BigEndian<quint16> format; + BigEndian<quint16> count; + BigEndian<quint16> stringOffset; + }; + + struct NameRecord + { + BigEndian<quint16> platformID; + BigEndian<quint16> encodingID; + BigEndian<quint16> languageID; + BigEndian<quint16> nameID; + BigEndian<quint16> length; + BigEndian<quint16> offset; + }; + + struct OffsetSubTable + { + BigEndian<quint32> scalerType; + BigEndian<quint16> numTables; + BigEndian<quint16> searchRange; + BigEndian<quint16> entrySelector; + BigEndian<quint16> rangeShift; + }; + + struct TableDirectory + { + BigEndian<quint32> identifier; + BigEndian<quint32> checkSum; + BigEndian<quint32> offset; + BigEndian<quint32> length; + }; + + struct OS2Table + { + BigEndian<quint16> version; + BigEndian<qint16> avgCharWidth; + BigEndian<quint16> weightClass; + BigEndian<quint16> widthClass; + BigEndian<quint16> type; + BigEndian<qint16> subscriptXSize; + BigEndian<qint16> subscriptYSize; + BigEndian<qint16> subscriptXOffset; + BigEndian<qint16> subscriptYOffset; + BigEndian<qint16> superscriptXSize; + BigEndian<qint16> superscriptYSize; + BigEndian<qint16> superscriptXOffset; + BigEndian<qint16> superscriptYOffset; + BigEndian<qint16> strikeOutSize; + BigEndian<qint16> strikeOutPosition; + BigEndian<qint16> familyClass; + quint8 panose[10]; + BigEndian<quint32> unicodeRanges[4]; + quint8 vendorID[4]; + BigEndian<quint16> selection; + BigEndian<quint16> firstCharIndex; + BigEndian<quint16> lastCharIndex; + BigEndian<qint16> typoAscender; + BigEndian<qint16> typoDescender; + BigEndian<qint16> typoLineGap; + BigEndian<quint16> winAscent; + BigEndian<quint16> winDescent; + BigEndian<quint32> codepageRanges[2]; + BigEndian<qint16> height; + BigEndian<qint16> capHeight; + BigEndian<quint16> defaultChar; + BigEndian<quint16> breakChar; + BigEndian<quint16> maxContext; + }; + +# pragma pack() + + class EmbeddedFont + { + public: + EmbeddedFont(const QByteArray &fontData); + + QString changeFamilyName(const QString &newFamilyName); + QByteArray data() const { return m_fontData; } + TableDirectory *tableDirectoryEntry(const QByteArray &tagName); + QString familyName(TableDirectory *nameTableDirectory = 0); + + private: + QByteArray m_fontData; + }; + + EmbeddedFont::EmbeddedFont(const QByteArray &fontData) : m_fontData(fontData) + { + } + + TableDirectory *EmbeddedFont::tableDirectoryEntry(const QByteArray &tagName) + { + Q_ASSERT(tagName.size() == 4); + + const BigEndian<quint32> *tagIdPtr = + reinterpret_cast<const BigEndian<quint32> *>(tagName.constData()); + quint32 tagId = *tagIdPtr; + + OffsetSubTable *offsetSubTable = reinterpret_cast<OffsetSubTable *>(m_fontData.data()); + TableDirectory *tableDirectory = reinterpret_cast<TableDirectory *>(offsetSubTable + 1); + + TableDirectory *nameTableDirectoryEntry = 0; + for (int i=0; i<offsetSubTable->numTables; ++i, ++tableDirectory) { + if (tableDirectory->identifier == tagId) { + nameTableDirectoryEntry = tableDirectory; + break; + } + } + + return nameTableDirectoryEntry; + } + + QString EmbeddedFont::familyName(TableDirectory *nameTableDirectoryEntry) + { + QString name; + + if (nameTableDirectoryEntry == 0) + nameTableDirectoryEntry = tableDirectoryEntry("name"); + + if (nameTableDirectoryEntry != 0) { + NameTable *nameTable = reinterpret_cast<NameTable *>(m_fontData.data() + + nameTableDirectoryEntry->offset); + NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1); + for (int i=0; i<nameTable->count; ++i, ++nameRecord) { + if (nameRecord->nameID == 1 + && nameRecord->platformID == 3 // Windows + && nameRecord->languageID == 0x0409) { // US English + const void *ptr = reinterpret_cast<const quint8 *>(nameTable) + + nameTable->stringOffset + + nameRecord->offset; + + const BigEndian<quint16> *s = reinterpret_cast<const BigEndian<quint16> *>(ptr); + for (int j=0; j<nameRecord->length / sizeof(quint16); ++j) + name += QChar(s[j]); + + break; + } + } + } + + return name; + } + + QString EmbeddedFont::changeFamilyName(const QString &newFamilyName) + { + TableDirectory *nameTableDirectoryEntry = tableDirectoryEntry("name"); + if (nameTableDirectoryEntry == 0) + return QString(); + + QString oldFamilyName = familyName(nameTableDirectoryEntry); + + // Reserve size for name table header, five required name records and string + const int requiredRecordCount = 5; + quint16 nameIds[requiredRecordCount] = { 1, 2, 3, 4, 6 }; + + int sizeOfHeader = sizeof(NameTable) + sizeof(NameRecord) * requiredRecordCount; + int newFamilyNameSize = newFamilyName.size() * sizeof(quint16); + + const QString regularString = QString::fromLatin1("Regular"); + int regularStringSize = regularString.size() * sizeof(quint16); + + // Align table size of table to 32 bits (pad with 0) + int fullSize = ((sizeOfHeader + newFamilyNameSize + regularStringSize) & ~3) + 4; + + QByteArray newNameTable(fullSize, char(0)); + + { + NameTable *nameTable = reinterpret_cast<NameTable *>(newNameTable.data()); + nameTable->count = requiredRecordCount; + nameTable->stringOffset = sizeOfHeader; + + NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1); + for (int i=0; i<requiredRecordCount; ++i, nameRecord++) { + nameRecord->nameID = nameIds[i]; + nameRecord->encodingID = 1; + nameRecord->languageID = 0x0409; + nameRecord->platformID = 3; + nameRecord->length = newFamilyNameSize; + + // Special case for sub-family + if (nameIds[i] == 4) { + nameRecord->offset = newFamilyNameSize; + nameRecord->length = regularStringSize; + } + } + + // nameRecord now points to string data + BigEndian<quint16> *stringStorage = reinterpret_cast<BigEndian<quint16> *>(nameRecord); + const quint16 *sourceString = newFamilyName.utf16(); + for (int i=0; i<newFamilyName.size(); ++i) + stringStorage[i] = sourceString[i]; + stringStorage += newFamilyName.size(); + + sourceString = regularString.utf16(); + for (int i=0; i<regularString.size(); ++i) + stringStorage[i] = sourceString[i]; + } + + quint32 *p = reinterpret_cast<quint32 *>(newNameTable.data()); + quint32 *tableEnd = reinterpret_cast<quint32 *>(newNameTable.data() + fullSize); + + quint32 checkSum = 0; + while (p < tableEnd) + checkSum += *(p++); + + nameTableDirectoryEntry->checkSum = checkSum; + nameTableDirectoryEntry->offset = m_fontData.size(); + nameTableDirectoryEntry->length = fullSize; + + m_fontData.append(newNameTable); + + return oldFamilyName; + } + +#if !defined(QT_NO_DIRECTWRITE) + + class DirectWriteFontFileStream: public IDWriteFontFileStream + { + public: + DirectWriteFontFileStream(const QByteArray &fontData) + : m_fontData(fontData) + , m_referenceCount(0) + { + } + + ~DirectWriteFontFileStream() + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE ReadFileFragment(const void **fragmentStart, UINT64 fileOffset, + UINT64 fragmentSize, OUT void **fragmentContext); + void STDMETHODCALLTYPE ReleaseFileFragment(void *fragmentContext); + HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64 *fileSize); + HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64 *lastWriteTime); + + private: + QByteArray m_fontData; + ULONG m_referenceCount; + }; + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::QueryInterface(REFIID iid, void **object) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) { + *object = this; + AddRef(); + return S_OK; + } else { + *object = NULL; + return E_NOINTERFACE; + } + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::AddRef() + { + return InterlockedIncrement(&m_referenceCount); + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::Release() + { + ULONG newCount = InterlockedDecrement(&m_referenceCount); + if (newCount == 0) + delete this; + return newCount; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::ReadFileFragment( + const void **fragmentStart, + UINT64 fileOffset, + UINT64 fragmentSize, + OUT void **fragmentContext) + { + *fragmentContext = NULL; + if (fragmentSize + fileOffset <= m_fontData.size()) { + *fragmentStart = m_fontData.data() + fileOffset; + return S_OK; + } else { + *fragmentStart = NULL; + return E_FAIL; + } + } + + void STDMETHODCALLTYPE DirectWriteFontFileStream::ReleaseFileFragment(void *) + { + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetFileSize(UINT64 *fileSize) + { + *fileSize = m_fontData.size(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime) + { + *lastWriteTime = 0; + return E_NOTIMPL; + } + + class DirectWriteFontFileLoader: public IDWriteFontFileLoader + { + public: + DirectWriteFontFileLoader() : m_referenceCount(0) {} + + ~DirectWriteFontFileLoader() + { + } + + inline void addKey(const void *key, const QByteArray &fontData) + { + Q_ASSERT(!m_fontDatas.contains(key)); + m_fontDatas.insert(key, fontData); + } + + inline void removeKey(const void *key) + { + m_fontDatas.remove(key); + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE CreateStreamFromKey(void const *fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream **fontFileStream); + + private: + ULONG m_referenceCount; + QHash<const void *, QByteArray> m_fontDatas; + }; + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::QueryInterface(const IID &iid, + void **object) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) { + *object = this; + AddRef(); + return S_OK; + } else { + *object = NULL; + return E_NOINTERFACE; + } + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::AddRef() + { + return InterlockedIncrement(&m_referenceCount); + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::Release() + { + ULONG newCount = InterlockedDecrement(&m_referenceCount); + if (newCount == 0) + delete this; + return newCount; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::CreateStreamFromKey( + void const *fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream **fontFileStream) + { + Q_UNUSED(fontFileReferenceKeySize); + + if (fontFileReferenceKeySize != sizeof(const void *)) { + qWarning("DirectWriteFontFileLoader::CreateStreamFromKey: Wrong key size"); + return E_FAIL; + } + + const void *key = *reinterpret_cast<void * const *>(fontFileReferenceKey); + *fontFileStream = NULL; + if (!m_fontDatas.contains(key)) + return E_FAIL; + + QByteArray fontData = m_fontDatas.value(key); + DirectWriteFontFileStream *stream = new DirectWriteFontFileStream(fontData); + stream->AddRef(); + *fontFileStream = stream; + + return S_OK; + } + + class CustomFontFileLoader + { + public: + CustomFontFileLoader() : m_directWriteFactory(0), m_directWriteFontFileLoader(0) + { + HRESULT hres = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown **>(&m_directWriteFactory)); + if (FAILED(hres)) { + qErrnoWarning(hres, "CustomFontFileLoader::CustomFontFileLoader: " + "DWriteCreateFactory failed."); + } else { + m_directWriteFontFileLoader = new DirectWriteFontFileLoader(); + m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader); + } + } + + ~CustomFontFileLoader() + { + if (m_directWriteFactory != 0 && m_directWriteFontFileLoader != 0) + m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader); + + if (m_directWriteFactory != 0) + m_directWriteFactory->Release(); + } + + void addKey(const void *key, const QByteArray &fontData) + { + if (m_directWriteFontFileLoader != 0) + m_directWriteFontFileLoader->addKey(key, fontData); + } + + void removeKey(const void *key) + { + if (m_directWriteFontFileLoader != 0) + m_directWriteFontFileLoader->removeKey(key); + } + + IDWriteFontFileLoader *loader() const + { + return m_directWriteFontFileLoader; + } + + private: + IDWriteFactory *m_directWriteFactory; + DirectWriteFontFileLoader *m_directWriteFontFileLoader; + }; + +#endif + +} // Anonymous namespace + + +// From qfontdatabase_win.cpp +extern QFontEngine *qt_load_font_engine_win(const QFontDef &request); +// From qfontdatabase.cpp +extern QFont::Weight weightFromInteger(int weight); + +void QRawFontPrivate::platformCleanUp() +{ + if (fontHandle != NULL) { + if (ptrRemoveFontMemResourceEx == NULL) { + void *func = QSystemLibrary::resolve(QLatin1String("gdi32"), "RemoveFontMemResourceEx"); + ptrRemoveFontMemResourceEx = + reinterpret_cast<QRawFontPrivate::PtrRemoveFontMemResourceEx>(func); + } + + if (ptrRemoveFontMemResourceEx == NULL) { + qWarning("QRawFont::platformCleanUp: Can't find RemoveFontMemResourceEx in gdi32"); + fontHandle = NULL; + } else { + ptrRemoveFontMemResourceEx(fontHandle); + fontHandle = NULL; + } + } +} + +void QRawFontPrivate::platformLoadFromData(const QByteArray &_fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + QByteArray fontData(_fontData); + EmbeddedFont font(fontData); + +#if !defined(QT_NO_DIRECTWRITE) + if (hintingPreference == QFont::PreferDefaultHinting + || hintingPreference == QFont::PreferFullHinting) +#endif + { + GUID guid; + CoCreateGuid(&guid); + + uniqueFamilyName = QString::fromLatin1("f") + + QString::number(guid.Data1, 36) + QLatin1Char('-') + + QString::number(guid.Data2, 36) + QLatin1Char('-') + + QString::number(guid.Data3, 36) + QLatin1Char('-') + + QString::number(*reinterpret_cast<quint64 *>(guid.Data4), 36); + + QString actualFontName = font.changeFamilyName(uniqueFamilyName); + if (actualFontName.isEmpty()) { + qWarning("QRawFont::platformLoadFromData: Can't change family name of font"); + return; + } + + if (ptrAddFontMemResourceEx == NULL || ptrRemoveFontMemResourceEx == NULL) { + void *func = QSystemLibrary::resolve(QLatin1String("gdi32"), "RemoveFontMemResourceEx"); + ptrRemoveFontMemResourceEx = + reinterpret_cast<QRawFontPrivate::PtrRemoveFontMemResourceEx>(func); + + func = QSystemLibrary::resolve(QLatin1String("gdi32"), "AddFontMemResourceEx"); + ptrAddFontMemResourceEx = + reinterpret_cast<QRawFontPrivate::PtrAddFontMemResourceEx>(func); + } + + Q_ASSERT(fontHandle == NULL); + if (ptrAddFontMemResourceEx != NULL && ptrRemoveFontMemResourceEx != NULL) { + DWORD count = 0; + fontData = font.data(); + fontHandle = ptrAddFontMemResourceEx(fontData.data(), fontData.size(), 0, &count); + + if (count == 0 && fontHandle != NULL) { + ptrRemoveFontMemResourceEx(fontHandle); + fontHandle = NULL; + } + } + + if (fontHandle == NULL) { + qWarning("QRawFont::platformLoadFromData: AddFontMemResourceEx failed"); + } else { + QFontDef request; + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + request.styleStrategy = QFont::NoFontMerging | QFont::PreferMatch; + request.hintingPreference = hintingPreference; + + fontEngine = qt_load_font_engine_win(request); + if (request.family != fontEngine->fontDef.family) { + qWarning("QRawFont::platformLoadFromData: Failed to load font. " + "Got fallback instead: %s", qPrintable(fontEngine->fontDef.family)); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + fontEngine = 0; + } else { + Q_ASSERT(fontEngine->cache_count == 0 && fontEngine->ref == 0); + + // Override the generated font name + fontEngine->fontDef.family = actualFontName; + fontEngine->ref.ref(); + } + } + } +#if !defined(QT_NO_DIRECTWRITE) + else { + CustomFontFileLoader fontFileLoader; + fontFileLoader.addKey(this, fontData); + + IDWriteFactory *factory = NULL; + HRESULT hres = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown **>(&factory)); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: DWriteCreateFactory failed"); + return; + } + + IDWriteFontFile *fontFile = NULL; + void *key = this; + + hres = factory->CreateCustomFontFileReference(&key, sizeof(void *), + fontFileLoader.loader(), &fontFile); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: " + "CreateCustomFontFileReference failed"); + factory->Release(); + return; + } + + BOOL isSupportedFontType; + DWRITE_FONT_FILE_TYPE fontFileType; + DWRITE_FONT_FACE_TYPE fontFaceType; + UINT32 numberOfFaces; + fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces); + if (!isSupportedFontType) { + fontFile->Release(); + factory->Release(); + return; + } + + IDWriteFontFace *directWriteFontFace = NULL; + hres = factory->CreateFontFace(fontFaceType, 1, &fontFile, 0, DWRITE_FONT_SIMULATIONS_NONE, + &directWriteFontFace); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: CreateFontFace failed"); + fontFile->Release(); + factory->Release(); + return; + } + + fontFile->Release(); + + fontEngine = new QFontEngineDirectWrite(factory, directWriteFontFace, pixelSize); + + // Get font family from font data + fontEngine->fontDef.family = font.familyName(); + fontEngine->ref.ref(); + + directWriteFontFace->Release(); + factory->Release(); + } +#endif + + // Get style and weight info + if (fontEngine != 0) { + TableDirectory *os2TableEntry = font.tableDirectoryEntry("OS/2"); + if (os2TableEntry != 0) { + const OS2Table *os2Table = + reinterpret_cast<const OS2Table *>(fontData.constData() + + os2TableEntry->offset); + + bool italic = os2Table->selection & 1; + bool oblique = os2Table->selection & 128; + + if (italic) + fontEngine->fontDef.style = QFont::StyleItalic; + else if (oblique) + fontEngine->fontDef.style = QFont::StyleOblique; + else + fontEngine->fontDef.style = QFont::StyleNormal; + + fontEngine->fontDef.weight = weightFromInteger(os2Table->weightClass); + } + } +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + +#if !defined(QT_NO_DIRECTWRITE) + if (fontEngine->type() == QFontEngine::Win) +#endif + + { + QFontDef request = fontEngine->fontDef; + QString actualFontName = request.family; + if (!uniqueFamilyName.isEmpty()) + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + + fontEngine = qt_load_font_engine_win(request); + if (fontEngine != NULL) { + fontEngine->fontDef.family = actualFontName; + fontEngine->ref.ref(); + } + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + QFontEngineDirectWrite *dWriteFE = static_cast<QFontEngineDirectWrite *>(fontEngine); + fontEngine = new QFontEngineDirectWrite(dWriteFE->m_directWriteFactory, + dWriteFE->m_directWriteFontFace, + pixelSize); + + fontEngine->fontDef = dWriteFE->fontDef; + fontEngine->fontDef.pixelSize = pixelSize; + fontEngine->ref.ref(); + } +#endif + + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qstatictext.cpp b/src/gui/text/qstatictext.cpp new file mode 100644 index 0000000000..1cfb4b61f9 --- /dev/null +++ b/src/gui/text/qstatictext.cpp @@ -0,0 +1,733 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstatictext.h" +#include "qstatictext_p.h" +#include <private/qtextengine_p.h> +#include <private/qfontengine_p.h> +#include <qabstracttextdocumentlayout.h> + +#include <QtGui/qapplication.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QStaticText + \brief The QStaticText class enables optimized drawing of text when the text and its layout + is updated rarely. + \since 4.7 + + \ingroup multimedia + \ingroup text + \mainclass + + QStaticText provides a way to cache layout data for a block of text so that it can be drawn + more efficiently than by using QPainter::drawText() in which the layout information is + recalculated with every call. + + The class primarily provides an optimization for cases where the text, its font and the + transformations on the painter are static over several paint events. If the text or its layout + is changed for every iteration, QPainter::drawText() is the more efficient alternative, since + the static text's layout would have to be recalculated to take the new state into consideration. + + Translating the painter will not cause the layout of the text to be recalculated, but will cause + a very small performance impact on drawStaticText(). Altering any other parts of the painter's + transformation or the painter's font will cause the layout of the static text to be + recalculated. This should be avoided as often as possible to maximize the performance + benefit of using QStaticText. + + In addition, only affine transformations are supported by drawStaticText(). Calling + drawStaticText() on a projected painter will perform slightly worse than using the regular + drawText() call, so this should be avoided. + + \code + class MyWidget: public QWidget + { + public: + MyWidget(QWidget *parent = 0) : QWidget(parent), m_staticText("This is static text") + + protected: + void paintEvent(QPaintEvent *) + { + QPainter painter(this); + painter.drawStaticText(0, 0, m_staticText); + } + + private: + QStaticText m_staticText; + }; + \endcode + + The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific + point with no boundaries, and also when QPainter::drawText() is called with a bounding + rectangle. + + If a bounding rectangle is not required, create a QStaticText object without setting a preferred + text width. The text will then occupy a single line. + + If you set a text width on the QStaticText object, this will bound the text. The text will + be formatted so that no line exceeds the given width. The text width set for QStaticText will + not automatically be used for clipping. To achieve clipping in addition to line breaks, use + QPainter::setClipRect(). The position of the text is decided by the argument passed to + QPainter::drawStaticText() and can change from call to call with a minimal impact on + performance. + + For extra convenience, it is possible to apply formatting to the text using the HTML subset + supported by QTextDocument. QStaticText will attempt to guess the format of the input text using + Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force + QStaticText to display its contents as either plain text or rich text, use the function + QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText. + + QStaticText can only represent text, so only HTML tags which alter the layout or appearance of + the text will be respected. Adding an image to the input HTML, for instance, will cause the + image to be included as part of the layout, affecting the positions of the text glyphs, but it + will not be displayed. The result will be an empty area the size of the image in the output. + Similarly, using tables will cause the text to be laid out in table format, but the borders + will not be drawn. + + If it's the first time the static text is drawn, or if the static text, or the painter's font + has been altered since the last time it was drawn, the text's layout has to be + recalculated. On some paint engines, changing the matrix of the painter will also cause the + layout to be recalculated. In particular, this will happen for any engine except for the + OpenGL2 paint engine. Recalculating the layout will impose an overhead on the + QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you + can call prepare() ahead of time to ensure that the layout is calculated. + + \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument +*/ + +/*! + \enum QStaticText::PerformanceHint + + This enum the different performance hints that can be set on the QStaticText. These hints + can be used to indicate that the QStaticText should use additional caches, if possible, + to improve performance at the expense of memory. In particular, setting the performance hint + AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics + system or when drawing to a QGLWidget. + + \value ModerateCaching Do basic caching for high performance at a low memory cost. + \value AggressiveCaching Use additional caching when available. This may improve performance + at a higher memory cost. +*/ + +/*! + Constructs an empty QStaticText +*/ +QStaticText::QStaticText() + : data(new QStaticTextPrivate) +{ +} + +/*! + Constructs a QStaticText object with the given \a text. +*/ +QStaticText::QStaticText(const QString &text) + : data(new QStaticTextPrivate) +{ + data->text = text; + data->invalidate(); +} + +/*! + Constructs a QStaticText object which is a copy of \a other. +*/ +QStaticText::QStaticText(const QStaticText &other) +{ + data = other.data; +} + +/*! + Destroys the QStaticText. +*/ +QStaticText::~QStaticText() +{ + Q_ASSERT(!data || data->ref >= 1); +} + +/*! + \internal +*/ +void QStaticText::detach() +{ + if (data->ref != 1) + data.detach(); +} + +/*! + Prepares the QStaticText object for being painted with the given \a matrix and the given \a font + to avoid overhead when the actual drawStaticText() call is made. + + When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part + of the QStaticText object has changed since the last time it was drawn. It will also be + recalculated if the painter's font is not the same as when the QStaticText was last drawn, or, + on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered + since the static text was last drawn. + + To avoid the overhead of creating the layout the first time you draw the QStaticText after + making changes, you can use the prepare() function and pass in the \a matrix and \a font you + expect to use when drawing the text. + + \sa QPainter::setFont(), QPainter::setMatrix() +*/ +void QStaticText::prepare(const QTransform &matrix, const QFont &font) +{ + data->matrix = matrix; + data->font = font; + data->init(); +} + + +/*! + Assigns \a other to this QStaticText. +*/ +QStaticText &QStaticText::operator=(const QStaticText &other) +{ + data = other.data; + return *this; +} + +/*! + Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths + are equal. +*/ +bool QStaticText::operator==(const QStaticText &other) const +{ + return (data == other.data + || (data->text == other.data->text + && data->font == other.data->font + && data->textWidth == other.data->textWidth)); +} + +/*! + Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes + are different. +*/ +bool QStaticText::operator!=(const QStaticText &other) const +{ + return !(*this == other); +} + +/*! + Sets the text of the QStaticText to \a text. + + \note This function will cause the layout of the text to require recalculation. + + \sa text() +*/ +void QStaticText::setText(const QString &text) +{ + detach(); + data->text = text; + data->invalidate(); +} + +/*! + Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to + Qt::AutoText (the default), the format of the text will try to be determined using the + function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be + displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags + that alter the font of the text, its color, or its layout are supported by QStaticText. + + \note This function will cause the layout of the text to require recalculation. + + \sa textFormat(), setText(), text() +*/ +void QStaticText::setTextFormat(Qt::TextFormat textFormat) +{ + detach(); + data->textFormat = textFormat; + data->invalidate(); +} + +/*! + Returns the text format of the QStaticText. + + \sa setTextFormat(), setText(), text() +*/ +Qt::TextFormat QStaticText::textFormat() const +{ + return Qt::TextFormat(data->textFormat); +} + +/*! + Returns the text of the QStaticText. + + \sa setText() +*/ +QString QStaticText::text() const +{ + return data->text; +} + +/*! + Sets the performance hint of the QStaticText according to the \a + performanceHint provided. The \a performanceHint is used to + customize how much caching is done internally to improve + performance. + + The default is QStaticText::ModerateCaching. + + \note This function will cause the layout of the text to require recalculation. + + \sa performanceHint() +*/ +void QStaticText::setPerformanceHint(PerformanceHint performanceHint) +{ + if ((performanceHint == ModerateCaching && !data->useBackendOptimizations) + || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) { + return; + } + detach(); + data->useBackendOptimizations = (performanceHint == AggressiveCaching); + data->invalidate(); +} + +/*! + Returns which performance hint is set for the QStaticText. + + \sa setPerformanceHint() +*/ +QStaticText::PerformanceHint QStaticText::performanceHint() const +{ + return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching; +} + +/*! + Sets the text option structure that controls the layout process to the given \a textOption. + + \sa textOption() +*/ +void QStaticText::setTextOption(const QTextOption &textOption) +{ + detach(); + data->textOption = textOption; + data->invalidate(); +} + +/*! + Returns the current text option used to control the layout process. +*/ +QTextOption QStaticText::textOption() const +{ + return data->textOption; +} + +/*! + Sets the preferred width for this QStaticText. If the text is wider than the specified width, + it will be broken into multiple lines and grow vertically. If the text cannot be split into + multiple lines, it will be larger than the specified \a textWidth. + + Setting the preferred text width to a negative number will cause the text to be unbounded. + + Use size() to get the actual size of the text. + + \note This function will cause the layout of the text to require recalculation. + + \sa textWidth(), size() +*/ +void QStaticText::setTextWidth(qreal textWidth) +{ + detach(); + data->textWidth = textWidth; + data->invalidate(); +} + +/*! + Returns the preferred width for this QStaticText. + + \sa setTextWidth() +*/ +qreal QStaticText::textWidth() const +{ + return data->textWidth; +} + +/*! + Returns the size of the bounding rect for this QStaticText. + + \sa textWidth() +*/ +QSizeF QStaticText::size() const +{ + if (data->needsRelayout) + data->init(); + return data->actualSize; +} + +QStaticTextPrivate::QStaticTextPrivate() + : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), + needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText), + untransformedCoordinates(false) +{ +} + +QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other) + : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix), + items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption), + needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations), + textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates) +{ +} + +QStaticTextPrivate::~QStaticTextPrivate() +{ + delete[] items; + delete[] glyphPool; + delete[] positionPool; + delete[] charPool; +} + +QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q) +{ + return q->data.data(); +} + +namespace { + + class DrawTextItemRecorder: public QPaintEngine + { + public: + DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations) + : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations), + m_untransformedCoordinates(untransformedCoordinates) + { + } + + virtual void updateState(const QPaintEngineState &newState) + { + if (newState.state() & QPaintEngine::DirtyPen) + m_dirtyPen = true; + } + + virtual void drawTextItem(const QPointF &position, const QTextItem &textItem) + { + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + + QStaticTextItem currentItem; + currentItem.setFontEngine(ti.fontEngine); + currentItem.font = ti.font(); + currentItem.charOffset = m_chars.size(); + currentItem.numChars = ti.num_chars; + currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool + currentItem.positionOffset = m_glyphs.size(); // Offset into position pool + currentItem.useBackendOptimizations = m_useBackendOptimizations; + if (m_dirtyPen) + currentItem.color = state->pen().color(); + + QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform(); + matrix.translate(position.x(), position.y()); + + QVarLengthArray<glyph_t> glyphs; + QVarLengthArray<QFixedPoint> positions; + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + int size = glyphs.size(); + Q_ASSERT(size == positions.size()); + currentItem.numGlyphs = size; + + m_glyphs.resize(m_glyphs.size() + size); + m_positions.resize(m_glyphs.size()); + m_chars.resize(m_chars.size() + ti.num_chars); + + glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset; + memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs); + + QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset; + memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs); + + QChar *charsDestination = m_chars.data() + currentItem.charOffset; + memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars); + + m_items.append(currentItem); + } + + virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ) + { + /* intentionally empty */ + } + + virtual bool begin(QPaintDevice *) { return true; } + virtual bool end() { return true; } + virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {} + virtual Type type() const + { + return User; + } + + QVector<QStaticTextItem> items() const + { + return m_items; + } + + QVector<QFixedPoint> positions() const + { + return m_positions; + } + + QVector<glyph_t> glyphs() const + { + return m_glyphs; + } + + QVector<QChar> chars() const + { + return m_chars; + } + + private: + QVector<QStaticTextItem> m_items; + QVector<QFixedPoint> m_positions; + QVector<glyph_t> m_glyphs; + QVector<QChar> m_chars; + + bool m_dirtyPen; + bool m_useBackendOptimizations; + bool m_untransformedCoordinates; + }; + + class DrawTextItemDevice: public QPaintDevice + { + public: + DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations) + { + m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates, + useBackendOptimizations); + } + + ~DrawTextItemDevice() + { + delete m_paintEngine; + } + + int metric(PaintDeviceMetric m) const + { + int val; + switch (m) { + case PdmWidth: + case PdmHeight: + case PdmWidthMM: + case PdmHeightMM: + val = 0; + break; + case PdmDpiX: + case PdmPhysicalDpiX: + val = qt_defaultDpiX(); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + val = qt_defaultDpiY(); + break; + case PdmNumColors: + val = 16777216; + break; + case PdmDepth: + val = 24; + break; + default: + val = 0; + qWarning("DrawTextItemDevice::metric: Invalid metric command"); + } + return val; + } + + virtual QPaintEngine *paintEngine() const + { + return m_paintEngine; + } + + QVector<glyph_t> glyphs() const + { + return m_paintEngine->glyphs(); + } + + QVector<QFixedPoint> positions() const + { + return m_paintEngine->positions(); + } + + QVector<QStaticTextItem> items() const + { + return m_paintEngine->items(); + } + + QVector<QChar> chars() const + { + return m_paintEngine->chars(); + } + + private: + DrawTextItemRecorder *m_paintEngine; + }; +} + +void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p) +{ + bool preferRichText = textFormat == Qt::RichText + || (textFormat == Qt::AutoText && Qt::mightBeRichText(text)); + + if (!preferRichText) { + QTextLayout textLayout; + textLayout.setText(text); + textLayout.setFont(font); + textLayout.setTextOption(textOption); + + qreal leading = QFontMetricsF(font).leading(); + qreal height = -leading; + + textLayout.beginLayout(); + while (1) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + + if (textWidth >= 0.0) + line.setLineWidth(textWidth); + height += leading; + line.setPosition(QPointF(0.0, height)); + height += line.height(); + } + textLayout.endLayout(); + + actualSize = textLayout.boundingRect().size(); + textLayout.draw(p, topLeftPosition); + } else { + QTextDocument document; +#ifndef QT_NO_CSSPARSER + QColor color = p->pen().color(); + document.setDefaultStyleSheet(QString::fromLatin1("body { color: #%1%2%3 }") + .arg(QString::number(color.red(), 16), 2, QLatin1Char('0')) + .arg(QString::number(color.green(), 16), 2, QLatin1Char('0')) + .arg(QString::number(color.blue(), 16), 2, QLatin1Char('0'))); +#endif + document.setDefaultFont(font); + document.setDocumentMargin(0.0); +#ifndef QT_NO_TEXTHTMLPARSER + document.setHtml(text); +#else + document.setPlainText(text); +#endif + if (textWidth >= 0.0) + document.setTextWidth(textWidth); + else + document.adjustSize(); + document.setDefaultTextOption(textOption); + + p->save(); + p->translate(topLeftPosition); + QAbstractTextDocumentLayout::PaintContext ctx; + ctx.palette.setColor(QPalette::Text, p->pen().color()); + document.documentLayout()->draw(p, ctx); + p->restore(); + + if (textWidth >= 0.0) + document.adjustSize(); // Find optimal size + + actualSize = document.size(); + } +} + +void QStaticTextPrivate::init() +{ + delete[] items; + delete[] glyphPool; + delete[] positionPool; + delete[] charPool; + + position = QPointF(0, 0); + + DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations); + { + QPainter painter(&device); + painter.setFont(font); + painter.setTransform(matrix); + + paintText(QPointF(0, 0), &painter); + } + + QVector<QStaticTextItem> deviceItems = device.items(); + QVector<QFixedPoint> positions = device.positions(); + QVector<glyph_t> glyphs = device.glyphs(); + QVector<QChar> chars = device.chars(); + + itemCount = deviceItems.size(); + items = new QStaticTextItem[itemCount]; + + glyphPool = new glyph_t[glyphs.size()]; + memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t)); + + positionPool = new QFixedPoint[positions.size()]; + memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint)); + + charPool = new QChar[chars.size()]; + memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar)); + + for (int i=0; i<itemCount; ++i) { + items[i] = deviceItems.at(i); + + items[i].glyphs = glyphPool + items[i].glyphOffset; + items[i].glyphPositions = positionPool + items[i].positionOffset; + items[i].chars = charPool + items[i].charOffset; + } + + needsRelayout = false; +} + +QStaticTextItem::~QStaticTextItem() +{ + if (m_userData != 0 && !m_userData->ref.deref()) + delete m_userData; + if (!m_fontEngine->ref.deref()) + delete m_fontEngine; +} + +void QStaticTextItem::setFontEngine(QFontEngine *fe) +{ + if (m_fontEngine != 0) { + if (!m_fontEngine->ref.deref()) + delete m_fontEngine; + } + + m_fontEngine = fe; + if (m_fontEngine != 0) + m_fontEngine->ref.ref(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qstatictext.h b/src/gui/text/qstatictext.h new file mode 100644 index 0000000000..dc3e913ae9 --- /dev/null +++ b/src/gui/text/qstatictext.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATICTEXT_H +#define QSTATICTEXT_H + +#include <QtCore/qsize.h> +#include <QtCore/qstring.h> +#include <QtCore/qmetatype.h> + +#include <QtGui/qtransform.h> +#include <QtGui/qfont.h> +#include <QtGui/qtextoption.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStaticTextPrivate; +class Q_GUI_EXPORT QStaticText +{ +public: + enum PerformanceHint { + ModerateCaching, + AggressiveCaching + }; + + QStaticText(); + QStaticText(const QString &text); + QStaticText(const QStaticText &other); + ~QStaticText(); + + void setText(const QString &text); + QString text() const; + + void setTextFormat(Qt::TextFormat textFormat); + Qt::TextFormat textFormat() const; + + void setTextWidth(qreal textWidth); + qreal textWidth() const; + + void setTextOption(const QTextOption &textOption); + QTextOption textOption() const; + + QSizeF size() const; + + void prepare(const QTransform &matrix = QTransform(), const QFont &font = QFont()); + + void setPerformanceHint(PerformanceHint performanceHint); + PerformanceHint performanceHint() const; + + QStaticText &operator=(const QStaticText &); + bool operator==(const QStaticText &) const; + bool operator!=(const QStaticText &) const; + +private: + void detach(); + + QExplicitlySharedDataPointer<QStaticTextPrivate> data; + friend class QStaticTextPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QStaticText) + +QT_END_HEADER + +#endif // QSTATICTEXT_H diff --git a/src/gui/text/qstatictext_p.h b/src/gui/text/qstatictext_p.h new file mode 100644 index 0000000000..af11a97c1b --- /dev/null +++ b/src/gui/text/qstatictext_p.h @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATICTEXT_P_H +#define QSTATICTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "qstatictext.h" + +#include <private/qtextureglyphcache_p.h> +#include <QtGui/qcolor.h> + +QT_BEGIN_NAMESPACE + +class QStaticTextUserData +{ +public: + enum Type { + NoUserData, + OpenGLUserData + }; + + QStaticTextUserData(Type t) : type(t) { ref = 0; } + virtual ~QStaticTextUserData() {} + + QAtomicInt ref; + Type type; +}; + +class Q_GUI_EXPORT QStaticTextItem +{ +public: + QStaticTextItem() : chars(0), numChars(0), useBackendOptimizations(false), + userDataNeedsUpdate(0), m_fontEngine(0), m_userData(0) {} + + QStaticTextItem(const QStaticTextItem &other) + { + operator=(other); + } + + void operator=(const QStaticTextItem &other) + { + glyphPositions = other.glyphPositions; + glyphs = other.glyphs; + chars = other.chars; + numGlyphs = other.numGlyphs; + numChars = other.numChars; + font = other.font; + color = other.color; + useBackendOptimizations = other.useBackendOptimizations; + userDataNeedsUpdate = other.userDataNeedsUpdate; + + m_fontEngine = 0; + m_userData = 0; + setUserData(other.userData()); + setFontEngine(other.fontEngine()); + } + + ~QStaticTextItem(); + + void setUserData(QStaticTextUserData *newUserData) + { + if (m_userData == newUserData) + return; + + if (m_userData != 0 && !m_userData->ref.deref()) + delete m_userData; + + m_userData = newUserData; + if (m_userData != 0) + m_userData->ref.ref(); + } + QStaticTextUserData *userData() const { return m_userData; } + + void setFontEngine(QFontEngine *fe); + QFontEngine *fontEngine() const { return m_fontEngine; } + + union { + QFixedPoint *glyphPositions; // 8 bytes per glyph + int positionOffset; + }; + union { + glyph_t *glyphs; // 4 bytes per glyph + int glyphOffset; + }; + union { + QChar *chars; // 2 bytes per glyph + int charOffset; + }; + // ================= + // 14 bytes per glyph + + // 12 bytes for pointers + int numGlyphs; // 4 bytes per item + int numChars; // 4 bytes per item + QFont font; // 8 bytes per item + QColor color; // 10 bytes per item + char useBackendOptimizations : 1; // 1 byte per item + char userDataNeedsUpdate : 1; // + // ================ + // 51 bytes per item + +private: // Needs special handling in setters, so private to avoid abuse + QFontEngine *m_fontEngine; // 4 bytes per item + QStaticTextUserData *m_userData; // 8 bytes per item + +}; + +class QStaticText; +class Q_AUTOTEST_EXPORT QStaticTextPrivate +{ +public: + QStaticTextPrivate(); + QStaticTextPrivate(const QStaticTextPrivate &other); + ~QStaticTextPrivate(); + + void init(); + void paintText(const QPointF &pos, QPainter *p); + + void invalidate() + { + needsRelayout = true; + } + + QAtomicInt ref; // 4 bytes per text + + QString text; // 4 bytes per text + QFont font; // 8 bytes per text + qreal textWidth; // 8 bytes per text + QSizeF actualSize; // 16 bytes per text + QPointF position; // 16 bytes per text + + QTransform matrix; // 80 bytes per text + QStaticTextItem *items; // 4 bytes per text + int itemCount; // 4 bytes per text + + glyph_t *glyphPool; // 4 bytes per text + QFixedPoint *positionPool; // 4 bytes per text + QChar *charPool; // 4 bytes per text + + QTextOption textOption; // 28 bytes per text + + unsigned char needsRelayout : 1; // 1 byte per text + unsigned char useBackendOptimizations : 1; + unsigned char textFormat : 2; + unsigned char untransformedCoordinates : 1; + // ================ + // 195 bytes per text + + static QStaticTextPrivate *get(const QStaticText *q); +}; + +QT_END_NAMESPACE + +#endif // QSTATICTEXT_P_H diff --git a/src/gui/text/qsyntaxhighlighter.cpp b/src/gui/text/qsyntaxhighlighter.cpp new file mode 100644 index 0000000000..6a3355b90d --- /dev/null +++ b/src/gui/text/qsyntaxhighlighter.cpp @@ -0,0 +1,665 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsyntaxhighlighter.h" + +#ifndef QT_NO_SYNTAXHIGHLIGHTER +#include <private/qobject_p.h> +#include <qtextdocument.h> +#include <private/qtextdocument_p.h> +#include <qtextlayout.h> +#include <qpointer.h> +#include <qtextobject.h> +#include <qtextcursor.h> +#include <qdebug.h> +#include <qtextedit.h> +#include <qtimer.h> + +QT_BEGIN_NAMESPACE + +class QSyntaxHighlighterPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSyntaxHighlighter) +public: + inline QSyntaxHighlighterPrivate() + : rehighlightPending(false), inReformatBlocks(false) + {} + + QPointer<QTextDocument> doc; + + void _q_reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlock(const QTextBlock &block); + + inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) { + inReformatBlocks = true; + cursor.beginEditBlock(); + int from = cursor.position(); + cursor.movePosition(operation); + reformatBlocks(from, 0, cursor.position() - from); + cursor.endEditBlock(); + inReformatBlocks = false; + } + + inline void _q_delayedRehighlight() { + if (!rehighlightPending) + return; + rehighlightPending = false; + q_func()->rehighlight(); + } + + void applyFormatChanges(); + QVector<QTextCharFormat> formatChanges; + QTextBlock currentBlock; + bool rehighlightPending; + bool inReformatBlocks; +}; + +void QSyntaxHighlighterPrivate::applyFormatChanges() +{ + bool formatsChanged = false; + + QTextLayout *layout = currentBlock.layout(); + + QList<QTextLayout::FormatRange> ranges = layout->additionalFormats(); + + const int preeditAreaStart = layout->preeditAreaPosition(); + const int preeditAreaLength = layout->preeditAreaText().length(); + + if (preeditAreaLength != 0) { + QList<QTextLayout::FormatRange>::Iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->start >= preeditAreaStart + && it->start + it->length <= preeditAreaStart + preeditAreaLength) { + ++it; + } else { + it = ranges.erase(it); + formatsChanged = true; + } + } + } else if (!ranges.isEmpty()) { + ranges.clear(); + formatsChanged = true; + } + + QTextCharFormat emptyFormat; + + QTextLayout::FormatRange r; + r.start = -1; + + int i = 0; + while (i < formatChanges.count()) { + + while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat) + ++i; + + if (i >= formatChanges.count()) + break; + + r.start = i; + r.format = formatChanges.at(i); + + while (i < formatChanges.count() && formatChanges.at(i) == r.format) + ++i; + + if (i >= formatChanges.count()) + break; + + r.length = i - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + r.start = -1; + } + + if (r.start != -1) { + r.length = formatChanges.count() - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + } + + if (formatsChanged) { + layout->setAdditionalFormats(ranges); + doc->markContentsDirty(currentBlock.position(), currentBlock.length()); + } +} + +void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + if (!inReformatBlocks) + reformatBlocks(from, charsRemoved, charsAdded); +} + +void QSyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + rehighlightPending = false; + + QTextBlock block = doc->findBlock(from); + if (!block.isValid()) + return; + + int endPosition; + QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0)); + if (lastBlock.isValid()) + endPosition = lastBlock.position() + lastBlock.length(); + else + endPosition = doc->docHandle()->length(); + + bool forceHighlightOfNextBlock = false; + + while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) { + const int stateBeforeHighlight = block.userState(); + + reformatBlock(block); + + forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); + + block = block.next(); + } + + formatChanges.clear(); +} + +void QSyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block) +{ + Q_Q(QSyntaxHighlighter); + + Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively"); + + currentBlock = block; + + formatChanges.fill(QTextCharFormat(), block.length() - 1); + q->highlightBlock(block.text()); + applyFormatChanges(); + + currentBlock = QTextBlock(); +} + +/*! + \class QSyntaxHighlighter + \reentrant + + \brief The QSyntaxHighlighter class allows you to define syntax + highlighting rules, and in addition you can use the class to query + a document's current formatting or user data. + + \since 4.1 + + \ingroup richtext-processing + + The QSyntaxHighlighter class is a base class for implementing + QTextEdit syntax highlighters. A syntax highligher automatically + highlights parts of the text in a QTextEdit, or more generally in + a QTextDocument. Syntax highlighters are often used when the user + is entering text in a specific format (for example source code) + and help the user to read the text and identify syntax errors. + + To provide your own syntax highlighting, you must subclass + QSyntaxHighlighter and reimplement highlightBlock(). + + When you create an instance of your QSyntaxHighlighter subclass, + pass it the QTextEdit or QTextDocument that you want the syntax + highlighting to be applied to. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 0 + + After this your highlightBlock() function will be called + automatically whenever necessary. Use your highlightBlock() + function to apply formatting (e.g. setting the font and color) to + the text that is passed to it. QSyntaxHighlighter provides the + setFormat() function which applies a given QTextCharFormat on + the current text block. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 1 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text is deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment": + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 2 + + In the example above, we first set the current block state to + 0. Then, if the previous block ended within a comment, we higlight + from the beginning of the current block (\c {startIndex = + 0}). Otherwise, we search for the given start expression. If the + specified end expression cannot be found in the text block, we + change the current block state by calling setCurrentBlockState(), + and make sure that the rest of the block is higlighted. + + In addition you can query the current formatting and user data + using the format() and currentBlockUserData() functions + respectively. You can also attach user data to the current text + block using the setCurrentBlockUserData() function. + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. For an example, see the + setCurrentBlockUserData() documentation. + + \sa QTextEdit, {Syntax Highlighter Example} +*/ + +/*! + Constructs a QSyntaxHighlighter with the given \a parent. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ +} + +/*! + Constructs a QSyntaxHighlighter and installs it on \a parent. + The specified QTextDocument also becomes the owner of the + QSyntaxHighlighter. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ + setDocument(parent); +} + +/*! + Constructs a QSyntaxHighlighter and installs it on \a parent 's + QTextDocument. The specified QTextEdit also becomes the owner of + the QSyntaxHighlighter. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QTextEdit *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ + setDocument(parent->document()); +} + +/*! + Destructor. Uninstalls this syntax highlighter from the text document. +*/ +QSyntaxHighlighter::~QSyntaxHighlighter() +{ + setDocument(0); +} + +/*! + Installs the syntax highlighter on the given QTextDocument \a doc. + A QSyntaxHighlighter can only be used with one document at a time. +*/ +void QSyntaxHighlighter::setDocument(QTextDocument *doc) +{ + Q_D(QSyntaxHighlighter); + if (d->doc) { + disconnect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + + QTextCursor cursor(d->doc); + cursor.beginEditBlock(); + for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next()) + blk.layout()->clearAdditionalFormats(); + cursor.endEditBlock(); + } + d->doc = doc; + if (d->doc) { + connect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + d->rehighlightPending = true; + QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight())); + } +} + +/*! + Returns the QTextDocument on which this syntax highlighter is + installed. +*/ +QTextDocument *QSyntaxHighlighter::document() const +{ + Q_D(const QSyntaxHighlighter); + return d->doc; +} + +/*! + \since 4.2 + + Reapplies the highlighting to the whole document. + + \sa rehighlightBlock() +*/ +void QSyntaxHighlighter::rehighlight() +{ + Q_D(QSyntaxHighlighter); + if (!d->doc) + return; + + QTextCursor cursor(d->doc); + d->rehighlight(cursor, QTextCursor::End); +} + +/*! + \since 4.6 + + Reapplies the highlighting to the given QTextBlock \a block. + + \sa rehighlight() +*/ +void QSyntaxHighlighter::rehighlightBlock(const QTextBlock &block) +{ + Q_D(QSyntaxHighlighter); + if (!d->doc || !block.isValid() || block.document() != d->doc) + return; + + const bool rehighlightPending = d->rehighlightPending; + + QTextCursor cursor(block); + d->rehighlight(cursor, QTextCursor::EndOfBlock); + + if (rehighlightPending) + d->rehighlightPending = rehighlightPending; +} + +/*! + \fn void QSyntaxHighlighter::highlightBlock(const QString &text) + + Highlights the given text block. This function is called when + necessary by the rich text engine, i.e. on text blocks which have + changed. + + To provide your own syntax highlighting, you must subclass + QSyntaxHighlighter and reimplement highlightBlock(). In your + reimplementation you should parse the block's \a text and call + setFormat() as often as necessary to apply any font and color + changes that you require. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 3 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text gets deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment". For a text block + that ended in the middle of a comment you'd set 1 using + setCurrentBlockState, and for other paragraphs you'd set 0. + In your parsing code if the return value of previousBlockState() + is 1, you would highlight the text as a C++ comment until you + reached the closing \c{*}\c{/}. + + \sa previousBlockState(), setFormat(), setCurrentBlockState() +*/ + +/*! + This function is applied to the syntax highlighter's current text + block (i.e. the text that is passed to the highlightBlock() + function). + + The specified \a format is applied to the text from the \a start + position for a length of \a count characters (if \a count is 0, + nothing is done). The formatting properties set in \a format are + merged at display time with the formatting information stored + directly in the document, for example as previously set with + QTextCursor's functions. Note that the document itself remains + unmodified by the format set through this function. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format) +{ + Q_D(QSyntaxHighlighter); + if (start < 0 || start >= d->formatChanges.count()) + return; + + const int end = qMin(start + count, d->formatChanges.count()); + for (int i = start; i < end; ++i) + d->formatChanges[i] = format; +} + +/*! + \overload + + The specified \a color is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QColor &color) +{ + QTextCharFormat format; + format.setForeground(color); + setFormat(start, count, format); +} + +/*! + \overload + + The specified \a font is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QFont &font) +{ + QTextCharFormat format; + format.setFont(font); + setFormat(start, count, format); +} + +/*! + \fn QTextCharFormat QSyntaxHighlighter::format(int position) const + + Returns the format at \a position inside the syntax highlighter's + current text block. +*/ +QTextCharFormat QSyntaxHighlighter::format(int pos) const +{ + Q_D(const QSyntaxHighlighter); + if (pos < 0 || pos >= d->formatChanges.count()) + return QTextCharFormat(); + return d->formatChanges.at(pos); +} + +/*! + Returns the end state of the text block previous to the + syntax highlighter's current block. If no value was + previously set, the returned value is -1. + + \sa highlightBlock(), setCurrentBlockState() +*/ +int QSyntaxHighlighter::previousBlockState() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + const QTextBlock previous = d->currentBlock.previous(); + if (!previous.isValid()) + return -1; + + return previous.userState(); +} + +/*! + Returns the state of the current text block. If no value is set, + the returned value is -1. +*/ +int QSyntaxHighlighter::currentBlockState() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + return d->currentBlock.userState(); +} + +/*! + Sets the state of the current text block to \a newState. + + \sa highlightBlock() +*/ +void QSyntaxHighlighter::setCurrentBlockState(int newState) +{ + Q_D(QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserState(newState); +} + +/*! + Attaches the given \a data to the current text block. The + ownership is passed to the underlying text document, i.e. the + provided QTextBlockUserData object will be deleted if the + corresponding text block gets deleted. + + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. + + For example while parsing the text, you can keep track of + parenthesis characters that you encounter ('{[(' and the like), + and store their relative position and the actual QChar in a simple + class derived from QTextBlockUserData: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 4 + + During cursor navigation in the associated editor, you can ask the + current QTextBlock (retrieved using the QTextCursor::block() + function) if it has a user data object set and cast it to your \c + BlockData object. Then you can check if the current cursor + position matches with a previously recorded parenthesis position, + and, depending on the type of parenthesis (opening or closing), + find the next opening or closing parenthesis on the same level. + + In this way you can do a visual parenthesis matching and highlight + from the current cursor position to the matching parenthesis. That + makes it easier to spot a missing parenthesis in your code and to + find where a corresponding opening/closing parenthesis is when + editing parenthesis intensive code. + + \sa QTextBlock::setUserData() +*/ +void QSyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data) +{ + Q_D(QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserData(data); +} + +/*! + Returns the QTextBlockUserData object previously attached to the + current text block. + + \sa QTextBlock::userData(), setCurrentBlockUserData() +*/ +QTextBlockUserData *QSyntaxHighlighter::currentBlockUserData() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return 0; + + return d->currentBlock.userData(); +} + +/*! + \since 4.4 + + Returns the current text block. +*/ +QTextBlock QSyntaxHighlighter::currentBlock() const +{ + Q_D(const QSyntaxHighlighter); + return d->currentBlock; +} + +QT_END_NAMESPACE + +#include "moc_qsyntaxhighlighter.cpp" + +#endif // QT_NO_SYNTAXHIGHLIGHTER diff --git a/src/gui/text/qsyntaxhighlighter.h b/src/gui/text/qsyntaxhighlighter.h new file mode 100644 index 0000000000..9cd5778074 --- /dev/null +++ b/src/gui/text/qsyntaxhighlighter.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSYNTAXHIGHLIGHTER_H +#define QSYNTAXHIGHLIGHTER_H + +#include <QtCore/qglobal.h> + +#ifndef QT_NO_SYNTAXHIGHLIGHTER + +#include <QtCore/qobject.h> +#include <QtGui/qtextobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocument; +class QSyntaxHighlighterPrivate; +class QTextCharFormat; +class QFont; +class QColor; +class QTextBlockUserData; +class QTextEdit; + +class Q_GUI_EXPORT QSyntaxHighlighter : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSyntaxHighlighter) +public: + QSyntaxHighlighter(QObject *parent); + QSyntaxHighlighter(QTextDocument *parent); + QSyntaxHighlighter(QTextEdit *parent); + virtual ~QSyntaxHighlighter(); + + void setDocument(QTextDocument *doc); + QTextDocument *document() const; + +public Q_SLOTS: + void rehighlight(); + void rehighlightBlock(const QTextBlock &block); + +protected: + virtual void highlightBlock(const QString &text) = 0; + + void setFormat(int start, int count, const QTextCharFormat &format); + void setFormat(int start, int count, const QColor &color); + void setFormat(int start, int count, const QFont &font); + QTextCharFormat format(int pos) const; + + int previousBlockState() const; + int currentBlockState() const; + void setCurrentBlockState(int newState); + + void setCurrentBlockUserData(QTextBlockUserData *data); + QTextBlockUserData *currentBlockUserData() const; + + QTextBlock currentBlock() const; + +private: + Q_DISABLE_COPY(QSyntaxHighlighter) + Q_PRIVATE_SLOT(d_func(), void _q_reformatBlocks(int from, int charsRemoved, int charsAdded)) + Q_PRIVATE_SLOT(d_func(), void _q_delayedRehighlight()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SYNTAXHIGHLIGHTER + +#endif // QSYNTAXHIGHLIGHTER_H diff --git a/src/gui/text/qtextcontrol.cpp b/src/gui/text/qtextcontrol.cpp new file mode 100644 index 0000000000..43967307bc --- /dev/null +++ b/src/gui/text/qtextcontrol.cpp @@ -0,0 +1,3148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextcontrol_p.h" +#include "qtextcontrol_p_p.h" + +#ifndef QT_NO_TEXTCONTROL + +#include <qfont.h> +#include <qpainter.h> +#include <qevent.h> +#include <qdebug.h> +#include <qmime.h> +#include <qdrag.h> +#include <qclipboard.h> +#include <qmenu.h> +#include <qstyle.h> +#include <qtimer.h> +#include "private/qtextdocumentlayout_p.h" +#include "private/qabstracttextdocumentlayout_p.h" +#include "private/qtextedit_p.h" +#include "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qtextcontrol_p.h" +#include "qgraphicssceneevent.h" +#include "qprinter.h" +#include "qtextdocumentwriter.h" +#include "private/qtextcursor_p.h" + +#include <qtextformat.h> +#include <qdatetime.h> +#include <qbuffer.h> +#include <qapplication.h> +#include <limits.h> +#include <qtexttable.h> +#include <qvariant.h> +#include <qurl.h> +#include <qdesktopservices.h> +#include <qinputcontext.h> +#include <qtooltip.h> +#include <qstyleoption.h> +#include <QtGui/qlineedit.h> + +#ifndef QT_NO_SHORTCUT +#include "private/qapplication_p.h" +#include "private/qshortcutmap_p.h" +#include <qkeysequence.h> +#define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? QLatin1Char('\t') + QString(QKeySequence(k)) : QString()) +#else +#define ACCEL_KEY(k) QString() +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_CONTEXTMENU +#if defined(Q_WS_WIN) || defined(Q_WS_X11) +extern bool qt_use_rtl_extensions; +#endif +#endif + +// could go into QTextCursor... +static QTextLine currentTextLine(const QTextCursor &cursor) +{ + const QTextBlock block = cursor.block(); + if (!block.isValid()) + return QTextLine(); + + const QTextLayout *layout = block.layout(); + if (!layout) + return QTextLine(); + + const int relativePos = cursor.position() - block.position(); + return layout->lineForTextPosition(relativePos); +} + +QTextControlPrivate::QTextControlPrivate() + : doc(0), cursorOn(false), cursorIsFocusIndicator(false), + interactionFlags(Qt::TextEditorInteraction), + dragEnabled(true), +#ifndef QT_NO_DRAGANDDROP + mousePressed(false), mightStartDrag(false), +#endif + lastSelectionState(false), ignoreAutomaticScrollbarAdjustement(false), + overwriteMode(false), + acceptRichText(true), + preeditCursor(0), hideCursor(false), + hasFocus(false), +#ifdef QT_KEYPAD_NAVIGATION + hasEditFocus(false), +#endif + isEnabled(true), + hadSelectionOnMousePress(false), + ignoreUnusedNavigationEvents(false), + openExternalLinks(false), + wordSelectionEnabled(false) +{} + +bool QTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e) +{ +#ifdef QT_NO_SHORTCUT + Q_UNUSED(e); +#endif + + Q_Q(QTextControl); + if (cursor.isNull()) + return false; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + QTextCursor::MoveOperation op = QTextCursor::NoMove; + + if (false) { + } +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToNextChar) { + op = QTextCursor::Right; + } + else if (e == QKeySequence::MoveToPreviousChar) { + op = QTextCursor::Left; + } + else if (e == QKeySequence::SelectNextChar) { + op = QTextCursor::Right; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousChar) { + op = QTextCursor::Left; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextWord) { + op = QTextCursor::WordRight; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousWord) { + op = QTextCursor::WordLeft; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfLine) { + op = QTextCursor::StartOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfLine) { + op = QTextCursor::EndOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfBlock) { + op = QTextCursor::StartOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfBlock) { + op = QTextCursor::EndOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfDocument) { + op = QTextCursor::Start; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfDocument) { + op = QTextCursor::End; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousLine) { + op = QTextCursor::Up; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextLine) { + op = QTextCursor::Down; + mode = QTextCursor::KeepAnchor; + { + QTextBlock block = cursor.block(); + QTextLine line = currentTextLine(cursor); + if (!block.next().isValid() + && line.isValid() + && line.lineNumber() == block.layout()->lineCount() - 1) + op = QTextCursor::End; + } + } + else if (e == QKeySequence::MoveToNextWord) { + op = QTextCursor::WordRight; + } + else if (e == QKeySequence::MoveToPreviousWord) { + op = QTextCursor::WordLeft; + } + else if (e == QKeySequence::MoveToEndOfBlock) { + op = QTextCursor::EndOfBlock; + } + else if (e == QKeySequence::MoveToStartOfBlock) { + op = QTextCursor::StartOfBlock; + } + else if (e == QKeySequence::MoveToNextLine) { + op = QTextCursor::Down; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToStartOfLine) { + op = QTextCursor::StartOfLine; + } + else if (e == QKeySequence::MoveToEndOfLine) { + op = QTextCursor::EndOfLine; + } + else if (e == QKeySequence::MoveToStartOfDocument) { + op = QTextCursor::Start; + } + else if (e == QKeySequence::MoveToEndOfDocument) { + op = QTextCursor::End; + } +#endif // QT_NO_SHORTCUT + else { + return false; + } + +// Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but +// here's the breakdown: +// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command), +// Alt (Option), or Meta (Control). +// Command/Control + Left/Right -- Move to left or right of the line +// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor) +// Option + Left/Right -- Move one word Left/right. +// + Up/Down -- Begin/End of Paragraph. +// Home/End Top/Bottom of file. (usually don't move the cursor, but will select) + + bool visualNavigation = cursor.visualNavigation(); + cursor.setVisualNavigation(true); + const bool moved = cursor.movePosition(op, mode); + cursor.setVisualNavigation(visualNavigation); + q->ensureCursorVisible(); + + bool ignoreNavigationEvents = ignoreUnusedNavigationEvents; + bool isNavigationEvent = e->key() == Qt::Key_Up || e->key() == Qt::Key_Down; + +#ifdef QT_KEYPAD_NAVIGATION + ignoreNavigationEvents = ignoreNavigationEvents || QApplication::keypadNavigationEnabled(); + isNavigationEvent = isNavigationEvent || + (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional + && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)); +#else + isNavigationEvent = isNavigationEvent || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right; +#endif + + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } else if (ignoreNavigationEvents && isNavigationEvent && oldSelection.anchor() == cursor.anchor()) { + return false; + } + + selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor)); + + repaintOldAndNewSelection(oldSelection); + + return true; +} + +void QTextControlPrivate::updateCurrentCharFormat() +{ + Q_Q(QTextControl); + + QTextCharFormat fmt = cursor.charFormat(); + if (fmt == lastCharFormat) + return; + lastCharFormat = fmt; + + emit q->currentCharFormatChanged(fmt); + emit q->microFocusChanged(); +} + +void QTextControlPrivate::indent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() + 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat format = list->format(); + format.setIndent(format.indent() + 1); + + if (list->itemNumber(cursor.block()) == 1) + list->setFormat(format); + else + cursor.createList(format); + } +} + +void QTextControlPrivate::outdent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() - 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat listFmt = list->format(); + listFmt.setIndent(listFmt.indent() - 1); + list->setFormat(listFmt); + } +} + +void QTextControlPrivate::gotoNextTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() + cell.columnSpan(); + int newRow = cell.row(); + + if (newColumn >= table->columns()) { + newColumn = 0; + ++newRow; + if (newRow >= table->rows()) + table->insertRows(table->rows(), 1); + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QTextControlPrivate::gotoPreviousTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() - 1; + int newRow = cell.row(); + + if (newColumn < 0) { + newColumn = table->columns() - 1; + --newRow; + if (newRow < 0) + return; + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QTextControlPrivate::createAutoBulletList() +{ + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextListFormat listFmt; + listFmt.setStyle(QTextListFormat::ListDisc); + listFmt.setIndent(blockFmt.indent() + 1); + + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + + cursor.createList(listFmt); + + cursor.endEditBlock(); +} + +void QTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QTextControl); + setContent(format, text, document); + + doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable); + q->setCursorWidth(-1); +} + +void QTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QTextControl); + + // for use when called from setPlainText. we may want to re-use the currently + // set char format then. + const QTextCharFormat charFormatForInsertion = cursor.charFormat(); + + bool clearDocument = true; + if (!doc) { + if (document) { + doc = document; + clearDocument = false; + } else { + palette = QApplication::palette("QTextControl"); + doc = new QTextDocument(q); + } + _q_documentLayoutChanged(); + cursor = QTextCursor(doc); + +// #### doc->documentLayout()->setPaintDevice(viewport); + + QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection())); + QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor))); + QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged())); + + // convenience signal forwards + QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); + QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); + } + + bool previousUndoRedoState = doc->isUndoRedoEnabled(); + if (!document) + doc->setUndoRedoEnabled(false); + + //Saving the index save some time. + static int contentsChangedIndex = QTextDocument::staticMetaObject.indexOfSignal("contentsChanged()"); + static int textChangedIndex = QTextControl::staticMetaObject.indexOfSignal("textChanged()"); + // avoid multiple textChanged() signals being emitted + QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex); + + if (!text.isEmpty()) { + // clear 'our' cursor for insertion to prevent + // the emission of the cursorPositionChanged() signal. + // instead we emit it only once at the end instead of + // at the end of the document after loading and when + // positioning the cursor again to the start of the + // document. + cursor = QTextCursor(); + if (format == Qt::PlainText) { + QTextCursor formatCursor(doc); + // put the setPlainText and the setCharFormat into one edit block, + // so that the syntax highlight triggers only /once/ for the entire + // document, not twice. + formatCursor.beginEditBlock(); + doc->setPlainText(text); + doc->setUndoRedoEnabled(false); + formatCursor.select(QTextCursor::Document); + formatCursor.setCharFormat(charFormatForInsertion); + formatCursor.endEditBlock(); + } else { +#ifndef QT_NO_TEXTHTMLPARSER + doc->setHtml(text); +#else + doc->setPlainText(text); +#endif + doc->setUndoRedoEnabled(false); + } + cursor = QTextCursor(doc); + } else if (clearDocument) { + doc->clear(); + } + cursor.setCharFormat(charFormatForInsertion); + + QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex); + emit q->textChanged(); + if (!document) + doc->setUndoRedoEnabled(previousUndoRedoState); + _q_updateCurrentCharFormatAndSelection(); + if (!document) + doc->setModified(false); + + q->ensureCursorVisible(); + emit q->cursorPositionChanged(); +} + +void QTextControlPrivate::startDrag() +{ +#ifndef QT_NO_DRAGANDDROP + Q_Q(QTextControl); + mousePressed = false; + if (!contextWidget) + return; + QMimeData *data = q->createMimeDataFromSelection(); + + QDrag *drag = new QDrag(contextWidget); + drag->setMimeData(data); + + Qt::DropActions actions = Qt::CopyAction; + Qt::DropAction action; + if (interactionFlags & Qt::TextEditable) { + actions |= Qt::MoveAction; + action = drag->exec(actions, Qt::MoveAction); + } else { + action = drag->exec(actions, Qt::CopyAction); + } + + if (action == Qt::MoveAction && drag->target() != contextWidget) + cursor.removeSelectedText(); +#endif +} + +void QTextControlPrivate::setCursorPosition(const QPointF &pos) +{ + Q_Q(QTextControl); + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + return; + cursor.setPosition(cursorPos); +} + +void QTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode) +{ + cursor.setPosition(pos, mode); + + if (mode != QTextCursor::KeepAnchor) { + selectedWordOnDoubleClick = QTextCursor(); + selectedBlockOnTrippleClick = QTextCursor(); + } +} + +void QTextControlPrivate::repaintCursor() +{ + Q_Q(QTextControl); + emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor)); +} + +void QTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection) +{ + Q_Q(QTextControl); + if (cursor.hasSelection() + && oldSelection.hasSelection() + && cursor.currentFrame() == oldSelection.currentFrame() + && !cursor.hasComplexSelection() + && !oldSelection.hasComplexSelection() + && cursor.anchor() == oldSelection.anchor() + ) { + QTextCursor differenceSelection(doc); + differenceSelection.setPosition(oldSelection.position()); + differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor); + emit q->updateRequest(q->selectionRect(differenceSelection)); + } else { + if (!oldSelection.isNull()) + emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection)); + emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor)); + } +} + +void QTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/) +{ + Q_Q(QTextControl); + if (forceEmitSelectionChanged) + emit q->selectionChanged(); + + bool current = cursor.hasSelection(); + if (current == lastSelectionState) + return; + + lastSelectionState = current; + emit q->copyAvailable(current); + if (!forceEmitSelectionChanged) + emit q->selectionChanged(); + emit q->microFocusChanged(); +} + +void QTextControlPrivate::_q_updateCurrentCharFormatAndSelection() +{ + updateCurrentCharFormat(); + selectionChanged(); +} + +#ifndef QT_NO_CLIPBOARD +void QTextControlPrivate::setClipboardSelection() +{ + QClipboard *clipboard = QApplication::clipboard(); + if (!cursor.hasSelection() || !clipboard->supportsSelection()) + return; + Q_Q(QTextControl); + QMimeData *data = q->createMimeDataFromSelection(); + clipboard->setMimeData(data, QClipboard::Selection); +} +#endif + +void QTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor) +{ + Q_Q(QTextControl); + if (someCursor.isCopyOf(cursor)) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } +} + +void QTextControlPrivate::_q_documentLayoutChanged() +{ + Q_Q(QTextControl); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QObject::connect(layout, SIGNAL(update(QRectF)), q, SIGNAL(updateRequest(QRectF))); + QObject::connect(layout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(_q_updateBlock(QTextBlock))); + QObject::connect(layout, SIGNAL(documentSizeChanged(QSizeF)), q, SIGNAL(documentSizeChanged(QSizeF))); + +} + +void QTextControlPrivate::setBlinkingCursorEnabled(bool enable) +{ + Q_Q(QTextControl); + + if (enable && QApplication::cursorFlashTime() > 0) + cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); + else + cursorBlinkTimer.stop(); + + cursorOn = enable; + + repaintCursor(); +} + +void QTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition) +{ + Q_Q(QTextControl); + + // if inside the initial selected word keep that + if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart() + && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) { + q->setTextCursor(selectedWordOnDoubleClick); + return; + } + + QTextCursor curs = selectedWordOnDoubleClick; + curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + + if (!curs.movePosition(QTextCursor::StartOfWord)) + return; + const int wordStartPos = curs.position(); + + const int blockPos = curs.block().position(); + const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft(); + + QTextLine line = currentTextLine(curs); + if (!line.isValid()) + return; + + const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (!curs.movePosition(QTextCursor::EndOfWord)) + return; + const int wordEndPos = curs.position(); + + const QTextLine otherLine = currentTextLine(curs); + if (otherLine.textStart() != line.textStart() + || wordEndPos == wordStartPos) + return; + + const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (mouseXPosition < wordStartX || mouseXPosition > wordEndX) + return; + + // keep the already selected word even when moving to the left + // (#39164) + if (suggestedNewPosition < selectedWordOnDoubleClick.position()) + cursor.setPosition(selectedWordOnDoubleClick.selectionEnd()); + else + cursor.setPosition(selectedWordOnDoubleClick.selectionStart()); + + const qreal differenceToStart = mouseXPosition - wordStartX; + const qreal differenceToEnd = wordEndX - mouseXPosition; + + if (differenceToStart < differenceToEnd) + setCursorPosition(wordStartPos, QTextCursor::KeepAnchor); + else + setCursorPosition(wordEndPos, QTextCursor::KeepAnchor); + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition) +{ + Q_Q(QTextControl); + + // if inside the initial selected line keep that + if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart() + && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) { + q->setTextCursor(selectedBlockOnTrippleClick); + return; + } + + if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) { + cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(selectedBlockOnTrippleClick.selectionStart()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + } + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QTextControlPrivate::_q_deleteSelected() +{ + if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection()) + return; + cursor.removeSelectedText(); +} + +void QTextControl::undo() +{ + Q_D(QTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->undo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +void QTextControl::redo() +{ + Q_D(QTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->redo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +QTextControl::QTextControl(QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(); +} + +QTextControl::QTextControl(const QString &text, QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(Qt::RichText, text); +} + +QTextControl::QTextControl(QTextDocument *doc, QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(Qt::RichText, QString(), doc); +} + +QTextControl::~QTextControl() +{ +} + +void QTextControl::setDocument(QTextDocument *document) +{ + Q_D(QTextControl); + if (d->doc == document) + return; + + d->doc->disconnect(this); + d->doc->documentLayout()->disconnect(this); + d->doc->documentLayout()->setPaintDevice(0); + + if (d->doc->parent() == this) + delete d->doc; + + d->doc = 0; + d->setContent(Qt::RichText, QString(), document); +} + +QTextDocument *QTextControl::document() const +{ + Q_D(const QTextControl); + return d->doc; +} + +void QTextControl::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QTextControl); + d->cursorIsFocusIndicator = false; + const bool posChanged = cursor.position() != d->cursor.position(); + const QTextCursor oldSelection = d->cursor; + d->cursor = cursor; + d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (posChanged) + emit cursorPositionChanged(); +} + +QTextCursor QTextControl::textCursor() const +{ + Q_D(const QTextControl); + return d->cursor; +} + +#ifndef QT_NO_CLIPBOARD + +void QTextControl::cut() +{ + Q_D(QTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection()) + return; + copy(); + d->cursor.removeSelectedText(); +} + +void QTextControl::copy() +{ + Q_D(QTextControl); + if (!d->cursor.hasSelection()) + return; + QMimeData *data = createMimeDataFromSelection(); + QApplication::clipboard()->setMimeData(data); +} + +void QTextControl::paste(QClipboard::Mode mode) +{ + const QMimeData *md = QApplication::clipboard()->mimeData(mode); + if (md) + insertFromMimeData(md); +} +#endif + +void QTextControl::clear() +{ + Q_D(QTextControl); + // clears and sets empty content + d->extraSelections.clear(); + d->setContent(); +} + + +void QTextControl::selectAll() +{ + Q_D(QTextControl); + const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor()); + d->cursor.select(QTextCursor::Document); + d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor())); + d->cursorIsFocusIndicator = false; + emit updateRequest(); +} + +void QTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset, QWidget *contextWidget) +{ + QMatrix m; + m.translate(coordinateOffset.x(), coordinateOffset.y()); + processEvent(e, m, contextWidget); +} + +void QTextControl::processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget) +{ + Q_D(QTextControl); + if (d->interactionFlags == Qt::NoTextInteraction) { + e->ignore(); + return; + } + + d->contextWidget = contextWidget; + + if (!d->contextWidget) { + switch (e->type()) { +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneContextMenu: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHelp: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneEvent *ev = static_cast<QGraphicsSceneEvent *>(e); + d->contextWidget = ev->widget(); + break; + } +#endif // QT_NO_GRAPHICSVIEW + default: break; + }; + } + + switch (e->type()) { + case QEvent::KeyPress: + d->keyPressEvent(static_cast<QKeyEvent *>(e)); + break; + case QEvent::MouseButtonPress: { + QMouseEvent *ev = static_cast<QMouseEvent *>(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseMove: { + QMouseEvent *ev = static_cast<QMouseEvent *>(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonRelease: { + QMouseEvent *ev = static_cast<QMouseEvent *>(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonDblClick: { + QMouseEvent *ev = static_cast<QMouseEvent *>(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::InputMethod: + d->inputMethodEvent(static_cast<QInputMethodEvent *>(e)); + break; +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: { + QContextMenuEvent *ev = static_cast<QContextMenuEvent *>(e); + d->contextMenuEvent(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; } +#endif // QT_NO_CONTEXTMENU + case QEvent::FocusIn: + case QEvent::FocusOut: + d->focusEvent(static_cast<QFocusEvent *>(e)); + break; + + case QEvent::EnabledChange: + d->isEnabled = e->isAccepted(); + break; + +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *ev = static_cast<QHelpEvent *>(e); + d->showToolTip(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; + } +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_DRAGANDDROP + case QEvent::DragEnter: { + QDragEnterEvent *ev = static_cast<QDragEnterEvent *>(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; + } + case QEvent::DragLeave: + d->dragLeaveEvent(); + break; + case QEvent::DragMove: { + QDragMoveEvent *ev = static_cast<QDragMoveEvent *>(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; + } + case QEvent::Drop: { + QDropEvent *ev = static_cast<QDropEvent *>(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->acceptProposedAction(); + break; + } +#endif + +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMousePress: { + QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseMove: { + QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseRelease: { + QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *ev = static_cast<QGraphicsSceneContextMenuEvent *>(e); + d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget); + break; } + + case QEvent::GraphicsSceneHoverMove: { + QGraphicsSceneHoverEvent *ev = static_cast<QGraphicsSceneHoverEvent *>(e); + d->mouseMoveEvent(ev, Qt::NoButton, matrix.map(ev->pos()), ev->modifiers(),Qt::NoButton, + ev->screenPos()); + break; } + + case QEvent::GraphicsSceneDragEnter: { + QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDragLeave: + d->dragLeaveEvent(); + break; + case QEvent::GraphicsSceneDragMove: { + QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->accept(); + break; } +#endif // QT_NO_GRAPHICSVIEW +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + case QEvent::LeaveEditFocus: + if (QApplication::keypadNavigationEnabled()) + d->editFocusEvent(e); + break; +#endif + case QEvent::ShortcutOverride: + if (d->interactionFlags & Qt::TextEditable) { + QKeyEvent* ke = static_cast<QKeyEvent *>(e); + if (ke->modifiers() == Qt::NoModifier + || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Tab: + ke->accept(); + default: + break; + } + } +#ifndef QT_NO_SHORTCUT + } else if (ke == QKeySequence::Copy + || ke == QKeySequence::Paste + || ke == QKeySequence::Cut + || ke == QKeySequence::Redo + || ke == QKeySequence::Undo + || ke == QKeySequence::MoveToNextWord + || ke == QKeySequence::MoveToPreviousWord + || ke == QKeySequence::MoveToStartOfDocument + || ke == QKeySequence::MoveToEndOfDocument + || ke == QKeySequence::SelectNextWord + || ke == QKeySequence::SelectPreviousWord + || ke == QKeySequence::SelectStartOfLine + || ke == QKeySequence::SelectEndOfLine + || ke == QKeySequence::SelectStartOfBlock + || ke == QKeySequence::SelectEndOfBlock + || ke == QKeySequence::SelectStartOfDocument + || ke == QKeySequence::SelectEndOfDocument + || ke == QKeySequence::SelectAll + ) { + ke->accept(); +#endif + } + } + break; + default: + break; + } +} + +bool QTextControl::event(QEvent *e) +{ + return QObject::event(e); +} + +void QTextControl::timerEvent(QTimerEvent *e) +{ + Q_D(QTextControl); + if (e->timerId() == d->cursorBlinkTimer.timerId()) { + d->cursorOn = !d->cursorOn; + + if (d->cursor.hasSelection()) + d->cursorOn &= (QApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected) + != 0); + + d->repaintCursor(); + } else if (e->timerId() == d->trippleClickTimer.timerId()) { + d->trippleClickTimer.stop(); + } +} + +void QTextControl::setPlainText(const QString &text) +{ + Q_D(QTextControl); + d->setContent(Qt::PlainText, text); +} + +void QTextControl::setHtml(const QString &text) +{ + Q_D(QTextControl); + d->setContent(Qt::RichText, text); +} + +void QTextControlPrivate::keyPressEvent(QKeyEvent *e) +{ + Q_Q(QTextControl); +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::SelectAll) { + e->accept(); + q->selectAll(); + return; + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Copy) { + e->accept(); + q->copy(); + return; + } +#endif +#endif // QT_NO_SHORTCUT + + if (interactionFlags & Qt::TextSelectableByKeyboard + && cursorMoveKeyEvent(e)) + goto accept; + + if (interactionFlags & Qt::LinksAccessibleByKeyboard) { + if ((e->key() == Qt::Key_Return + || e->key() == Qt::Key_Enter +#ifdef QT_KEYPAD_NAVIGATION + || e->key() == Qt::Key_Select +#endif + ) + && cursor.hasSelection()) { + + e->accept(); + activateLinkUnderCursor(); + return; + } + } + + if (!(interactionFlags & Qt::TextEditable)) { + e->ignore(); + return; + } + + if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) { + QTextBlockFormat fmt; + fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft); + cursor.mergeBlockFormat(fmt); + goto accept; + } + + // schedule a repaint of the region of the cursor, as when we move it we + // want to make sure the old cursor disappears (not noticeable when moving + // only a few pixels but noticeable when jumping between cells in tables for + // example) + repaintSelection(); + + if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) { + QTextBlockFormat blockFmt = cursor.blockFormat(); + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart() && !cursor.hasSelection()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + QTextCursor localCursor = cursor; + localCursor.deletePreviousChar(); + } + goto accept; + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::InsertParagraphSeparator) { + cursor.insertBlock(); + e->accept(); + goto accept; + } else if (e == QKeySequence::InsertLineSeparator) { + cursor.insertText(QString(QChar::LineSeparator)); + e->accept(); + goto accept; + } +#endif + if (false) { + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::Undo) { + q->undo(); + } + else if (e == QKeySequence::Redo) { + q->redo(); + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Cut) { + q->cut(); + } + else if (e == QKeySequence::Paste) { + QClipboard::Mode mode = QClipboard::Clipboard; +#ifdef Q_WS_X11 + if (e->modifiers() == (Qt::CTRL | Qt::SHIFT) && e->key() == Qt::Key_Insert) + mode = QClipboard::Selection; +#endif + q->paste(mode); + } +#endif + else if (e == QKeySequence::Delete) { + QTextCursor localCursor = cursor; + localCursor.deleteChar(); + } + else if (e == QKeySequence::DeleteEndOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteStartOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteEndOfLine) { + QTextBlock block = cursor.block(); + if (cursor.position() == block.position() + block.length() - 2) + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + else + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } +#endif // QT_NO_SHORTCUT + else { + goto process; + } + goto accept; + +process: + { + QString text = e->text(); + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { + if (overwriteMode + // no need to call deleteChar() if we have a selection, insertText + // does it already + && !cursor.hasSelection() + && !cursor.atBlockEnd()) + cursor.deleteChar(); + + cursor.insertText(text); + selectionChanged(); + } else { + e->ignore(); + return; + } + } + + accept: + + e->accept(); + cursorOn = true; + + q->ensureCursorVisible(); + + updateCurrentCharFormat(); +} + +QVariant QTextControl::loadResource(int type, const QUrl &name) +{ +#ifdef QT_NO_TEXTEDIT + Q_UNUSED(type); + Q_UNUSED(name); +#else + if (QTextEdit *textEdit = qobject_cast<QTextEdit *>(parent())) { + QUrl resolvedName = textEdit->d_func()->resolveUrl(name); + return textEdit->loadResource(type, resolvedName); + } +#endif + return QVariant(); +} + +void QTextControlPrivate::_q_updateBlock(const QTextBlock &block) +{ + Q_Q(QTextControl); + QRectF br = q->blockBoundingRect(block); + br.setRight(qreal(INT_MAX)); // the block might have shrunk + emit q->updateRequest(br); +} + +QRectF QTextControlPrivate::rectForPosition(int position) const +{ + Q_Q(const QTextControl); + const QTextBlock block = doc->findBlock(position); + if (!block.isValid()) + return QRectF(); + const QAbstractTextDocumentLayout *docLayout = doc->documentLayout(); + const QTextLayout *layout = block.layout(); + const QPointF layoutPos = q->blockBoundingRect(block).topLeft(); + int relativePos = position - block.position(); + if (preeditCursor != 0) { + int preeditPos = layout->preeditAreaPosition(); + if (relativePos == preeditPos) + relativePos += preeditCursor; + else if (relativePos > preeditPos) + relativePos += layout->preeditAreaText().length(); + } + QTextLine line = layout->lineForTextPosition(relativePos); + + int cursorWidth; + { + bool ok = false; +#ifndef QT_NO_PROPERTIES + cursorWidth = docLayout->property("cursorWidth").toInt(&ok); +#endif + if (!ok) + cursorWidth = 1; + } + + QRectF r; + + if (line.isValid()) { + qreal x = line.cursorToX(relativePos); + qreal w = 0; + if (overwriteMode) { + if (relativePos < line.textLength() - line.textStart()) + w = line.cursorToX(relativePos + 1) - x; + else + w = QFontMetrics(block.layout()->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw() + } + r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), + cursorWidth + w, line.height()); + } else { + r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height + } + + return r; +} + +static inline bool firstFramePosLessThanCursorPos(QTextFrame *frame, int position) +{ + return frame->firstPosition() < position; +} + +static inline bool cursorPosLessThanLastFramePos(int position, QTextFrame *frame) +{ + return position < frame->lastPosition(); +} + +static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor) +{ + QRectF r; + QTextFrame *frame = cursor.currentFrame(); + const QList<QTextFrame *> children = frame->childFrames(); + + const QList<QTextFrame *>::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(), + cursor.selectionStart(), firstFramePosLessThanCursorPos); + const QList<QTextFrame *>::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(), + cursor.selectionEnd(), cursorPosLessThanLastFramePos); + for (QList<QTextFrame *>::ConstIterator it = firstFrame; it != lastFrame; ++it) { + if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow) + r |= frame->document()->documentLayout()->frameBoundingRect(*it); + } + return r; +} + +QRectF QTextControl::selectionRect(const QTextCursor &cursor) const +{ + Q_D(const QTextControl); + + QRectF r = d->rectForPosition(cursor.selectionStart()); + + if (cursor.hasComplexSelection() && cursor.currentTable()) { + QTextTable *table = cursor.currentTable(); + + r = d->doc->documentLayout()->frameBoundingRect(table); + /* + int firstRow, numRows, firstColumn, numColumns; + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + + const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn); + const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1); + + const QAbstractTextDocumentLayout * const layout = doc->documentLayout(); + + QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block()); + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow, col); + const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top(); + + tableSelRect.setTop(qMin(tableSelRect.top(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn); + const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left(); + + tableSelRect.setLeft(qMin(tableSelRect.left(), x)); + } + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col); + const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom(); + + tableSelRect.setBottom(qMax(tableSelRect.bottom(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1); + const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right(); + + tableSelRect.setRight(qMax(tableSelRect.right(), x)); + } + + r = tableSelRect.toRect(); + */ + } else if (cursor.hasSelection()) { + const int position = cursor.selectionStart(); + const int anchor = cursor.selectionEnd(); + const QTextBlock posBlock = d->doc->findBlock(position); + const QTextBlock anchorBlock = d->doc->findBlock(anchor); + if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) { + const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position()); + const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position()); + + const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber()); + const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber()); + const QTextLayout *layout = posBlock.layout(); + r = QRectF(); + for (int i = firstLine; i <= lastLine; ++i) { + r |= layout->lineAt(i).rect(); + r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled + } + r.translate(blockBoundingRect(posBlock).topLeft()); + } else { + QRectF anchorRect = d->rectForPosition(cursor.selectionEnd()); + r |= anchorRect; + r |= boundingRectOfFloatsInSelection(cursor); + QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame())); + r.setLeft(frameRect.left()); + r.setRight(frameRect.right()); + } + if (r.isValid()) + r.adjust(-1, -1, 1, 1); + } + + return r; +} + +QRectF QTextControl::selectionRect() const +{ + Q_D(const QTextControl); + return selectionRect(d->cursor); +} + +void QTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonPress, button, pos, modifiers, buttons, globalPos)) { + return; + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + anchorOnMousePress = q->anchorAt(pos); + + if (cursorIsFocusIndicator) { + cursorIsFocusIndicator = false; + repaintSelection(); + cursor.clearSelection(); + } + } + if (!(button & Qt::LeftButton) || + !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) { + e->ignore(); + return; + } + + cursorIsFocusIndicator = false; + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + mousePressed = (interactionFlags & Qt::TextSelectableByMouse); +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + + if (trippleClickTimer.isActive() + && ((pos - trippleClickPoint).toPoint().manhattanLength() < QApplication::startDragDistance())) { + + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selectedBlockOnTrippleClick = cursor; + + anchorOnMousePress = QString(); + + trippleClickTimer.stop(); + } else { + int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) { + e->ignore(); + return; + } + + if (modifiers == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) { + if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(cursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(cursorPos, pos.x()); + else if (!wordSelectionEnabled) + setCursorPosition(cursorPos, QTextCursor::KeepAnchor); + } else { + + if (dragEnabled + && cursor.hasSelection() + && !cursorIsFocusIndicator + && cursorPos >= cursor.selectionStart() + && cursorPos <= cursor.selectionEnd() + && q->hitTest(pos, Qt::ExactHit) != -1) { +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = true; + dragStartPos = pos.toPoint(); +#endif + return; + } + + setCursorPosition(cursorPos); + } + } + + if (interactionFlags & Qt::TextEditable) { + q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); + } else { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + selectionChanged(); + } + repaintOldAndNewSelection(oldSelection); + hadSelectionOnMousePress = cursor.hasSelection(); +} + +void QTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &mousePos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos)) { + return; + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + QString anchor = q->anchorAt(mousePos); + if (anchor != highlightedAnchor) { + highlightedAnchor = anchor; + emit q->linkHovered(anchor); + } + } + + if (!(buttons & Qt::LeftButton)) + return; + + const bool editable = interactionFlags & Qt::TextEditable; + + if (!(mousePressed + || editable + || mightStartDrag + || selectedWordOnDoubleClick.hasSelection() + || selectedBlockOnTrippleClick.hasSelection())) + return; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + if (mightStartDrag) { + if ((mousePos.toPoint() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) + startDrag(); + return; + } + + if (!mousePressed) + return; + + const qreal mouseX = qreal(mousePos.x()); + + int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); + if (newCursorPos == -1) + return; + + if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(newCursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(newCursorPos, mouseX); + else + setCursorPosition(newCursorPos, QTextCursor::KeepAnchor); + + if (interactionFlags & Qt::TextEditable) { + // don't call ensureVisible for the visible cursor to avoid jumping + // scrollbars. the autoscrolling ensures smooth scrolling if necessary. + //q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); +#ifndef QT_NO_IM + if (contextWidget) { + if (QInputContext *ic = inputContext()) { + ic->update(); + } + } +#endif //QT_NO_IM + } else { + //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1))); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + } + selectionChanged(true); + repaintOldAndNewSelection(oldSelection); +} + +void QTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonRelease, button, pos, modifiers, buttons, globalPos)) { + return; + } + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + +#ifndef QT_NO_DRAGANDDROP + if (mightStartDrag && (button & Qt::LeftButton)) { + mousePressed = false; + setCursorPosition(pos); + cursor.clearSelection(); + selectionChanged(); + } +#endif + if (mousePressed) { + mousePressed = false; +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); + selectionChanged(true); + } else if (button == Qt::MidButton + && (interactionFlags & Qt::TextEditable) + && QApplication::clipboard()->supportsSelection()) { + setCursorPosition(pos); + const QMimeData *md = QApplication::clipboard()->mimeData(QClipboard::Selection); + if (md) + q->insertFromMimeData(md); +#endif + } + + repaintOldAndNewSelection(oldSelection); + + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + if (!(button & Qt::LeftButton)) + return; + + const QString anchor = q->anchorAt(pos); + + if (anchor.isEmpty()) + return; + + if (!cursor.hasSelection() + || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) { + + const int anchorPos = q->hitTest(pos, Qt::ExactHit); + if (anchorPos != -1) { + cursor.setPosition(anchorPos); + + QString anchor = anchorOnMousePress; + anchorOnMousePress = QString(); + activateLinkUnderCursor(anchor); + } + } + } +} + +void QTextControlPrivate::mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonDblClick, button, pos, modifiers, buttons, globalPos)) { + return; + } + + if (button != Qt::LeftButton + || !(interactionFlags & Qt::TextSelectableByMouse)) { + e->ignore(); + return; + } + +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + const QTextCursor oldSelection = cursor; + setCursorPosition(pos); + QTextLine line = currentTextLine(cursor); + bool doEmit = false; + if (line.isValid() && line.textLength()) { + cursor.select(QTextCursor::WordUnderCursor); + doEmit = true; + } + repaintOldAndNewSelection(oldSelection); + + cursorIsFocusIndicator = false; + selectedWordOnDoubleClick = cursor; + + trippleClickPoint = pos; + trippleClickTimer.start(QApplication::doubleClickInterval(), q); + if (doEmit) { + selectionChanged(); +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + emit q->cursorPositionChanged(); + } +} + +bool QTextControlPrivate::sendMouseEventToInputContext( + QEvent *e, QEvent::Type eventType, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint &globalPos) +{ +#if !defined(QT_NO_IM) + Q_Q(QTextControl); + + QTextLayout *layout = cursor.block().layout(); + if (contextWidget && layout && !layout->preeditAreaText().isEmpty()) { + QInputContext *ctx = inputContext(); + int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position(); + + if (cursorPos < 0 || cursorPos > layout->preeditAreaText().length()) { + cursorPos = -1; + // don't send move events outside the preedit area + if (eventType == QEvent::MouseMove) + return true; + } + if (ctx) { + QMouseEvent ev(eventType, contextWidget->mapFromGlobal(globalPos), globalPos, + button, buttons, modifiers); + ctx->mouseHandler(cursorPos, &ev); + e->setAccepted(ev.isAccepted()); + } + if (!layout->preeditAreaText().isEmpty()) + return true; + } +#else + Q_UNUSED(e); + Q_UNUSED(eventType); + Q_UNUSED(button); + Q_UNUSED(pos); + Q_UNUSED(modifiers); + Q_UNUSED(buttons); + Q_UNUSED(globalPos); +#endif + return false; +} + +void QTextControlPrivate::contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget) +{ +#ifdef QT_NO_CONTEXTMENU + Q_UNUSED(screenPos); + Q_UNUSED(docPos); + Q_UNUSED(contextWidget); +#else + Q_Q(QTextControl); + if (!hasFocus) + return; + QMenu *menu = q->createStandardContextMenu(docPos, contextWidget); + if (!menu) + return; + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(screenPos); +#endif +} + +bool QTextControlPrivate::dragEnterEvent(QEvent *e, const QMimeData *mimeData) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + dndFeedbackCursor = QTextCursor(); + + return true; // accept proposed action +} + +void QTextControlPrivate::dragLeaveEvent() +{ + Q_Q(QTextControl); + + const QRectF crect = q->cursorRect(dndFeedbackCursor); + dndFeedbackCursor = QTextCursor(); + + if (crect.isValid()) + emit q->updateRequest(crect); +} + +bool QTextControlPrivate::dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos != -1) { + QRectF crect = q->cursorRect(dndFeedbackCursor); + if (crect.isValid()) + emit q->updateRequest(crect); + + dndFeedbackCursor = cursor; + dndFeedbackCursor.setPosition(cursorPos); + + crect = q->cursorRect(dndFeedbackCursor); + emit q->updateRequest(crect); + } + + return true; // accept proposed action +} + +bool QTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QWidget *source) +{ + Q_Q(QTextControl); + dndFeedbackCursor = QTextCursor(); + + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) + return false; + + repaintSelection(); + + QTextCursor insertionCursor = q->cursorForPosition(pos); + insertionCursor.beginEditBlock(); + + if (dropAction == Qt::MoveAction && source == contextWidget) + cursor.removeSelectedText(); + + cursor = insertionCursor; + q->insertFromMimeData(mimeData); + insertionCursor.endEditBlock(); + q->ensureCursorVisible(); + return true; // accept proposed action +} + +void QTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) { + e->ignore(); + return; + } + bool isGettingInput = !e->commitString().isEmpty() + || e->preeditString() != cursor.block().layout()->preeditAreaText() + || e->replacementLength() > 0; + + cursor.beginEditBlock(); + if (isGettingInput) { + cursor.removeSelectedText(); + } + + // insert commit string + if (!e->commitString().isEmpty() || e->replacementLength()) { + QTextCursor c = cursor; + c.setPosition(c.position() + e->replacementStart()); + c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor); + c.insertText(e->commitString()); + } + + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Selection) { + QTextCursor oldCursor = cursor; + int blockStart = a.start + cursor.block().position(); + cursor.setPosition(blockStart, QTextCursor::MoveAnchor); + cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor); + q->ensureCursorVisible(); + repaintOldAndNewSelection(oldCursor); + } + } + + QTextBlock block = cursor.block(); + QTextLayout *layout = block.layout(); + if (isGettingInput) + layout->setPreeditArea(cursor.position() - block.position(), e->preeditString()); + QList<QTextLayout::FormatRange> overrides; + const int oldPreeditCursor = preeditCursor; + preeditCursor = e->preeditString().length(); + hideCursor = false; + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Cursor) { + preeditCursor = a.start; + hideCursor = !a.length; + } else if (a.type == QInputMethodEvent::TextFormat) { + QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat(); + if (f.isValid()) { + QTextLayout::FormatRange o; + o.start = a.start + cursor.position() - block.position(); + o.length = a.length; + o.format = f; + overrides.append(o); + } + } + } + layout->setAdditionalFormats(overrides); + cursor.endEditBlock(); + if (cursor.d) + cursor.d->setX(); + if (oldPreeditCursor != preeditCursor) + emit q->microFocusChanged(); +} + +QVariant QTextControl::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QTextControl); + QTextBlock block = d->cursor.block(); + switch(property) { + case Qt::ImMicroFocus: + return cursorRect(); + case Qt::ImFont: + return QVariant(d->cursor.charFormat().font()); + case Qt::ImCursorPosition: + return QVariant(d->cursor.position() - block.position()); + case Qt::ImSurroundingText: + return QVariant(block.text()); + case Qt::ImCurrentSelection: + return QVariant(d->cursor.selectedText()); + case Qt::ImMaximumTextLength: + return QVariant(); // No limit. + case Qt::ImAnchorPosition: + return QVariant(qBound(0, d->cursor.anchor() - block.position(), block.length())); + default: + return QVariant(); + } +} + +void QTextControl::setFocus(bool focus, Qt::FocusReason reason) +{ + QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut, + reason); + processEvent(&ev); +} + +void QTextControlPrivate::focusEvent(QFocusEvent *e) +{ + Q_Q(QTextControl); + emit q->updateRequest(q->selectionRect()); + if (e->gotFocus()) { +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || (hasEditFocus && (e->reason() == Qt::PopupFocusReason +#ifdef Q_OS_SYMBIAN + || e->reason() == Qt::ActiveWindowFocusReason +#endif + ))) { +#endif + cursorOn = (interactionFlags & Qt::TextSelectableByKeyboard); + if (interactionFlags & Qt::TextEditable) { + setBlinkingCursorEnabled(true); + } +#ifdef QT_KEYPAD_NAVIGATION + } +#endif + } else { + setBlinkingCursorEnabled(false); + + if (cursorIsFocusIndicator + && e->reason() != Qt::ActiveWindowFocusReason + && e->reason() != Qt::PopupFocusReason + && cursor.hasSelection()) { + cursor.clearSelection(); + } + } + hasFocus = e->gotFocus(); +} + +QString QTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const +{ + if (anchorCursor.hasSelection()) { + QTextCursor cursor = anchorCursor; + if (cursor.selectionStart() != cursor.position()) + cursor.setPosition(cursor.selectionStart()); + cursor.movePosition(QTextCursor::NextCharacter); + QTextCharFormat fmt = cursor.charFormat(); + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) + return fmt.stringProperty(QTextFormat::AnchorHref); + } + return QString(); +} + +#ifdef QT_KEYPAD_NAVIGATION +void QTextControlPrivate::editFocusEvent(QEvent *e) +{ + Q_Q(QTextControl); + + if (QApplication::keypadNavigationEnabled()) { + if (e->type() == QEvent::EnterEditFocus && interactionFlags & Qt::TextEditable) { + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + const bool moved = cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + q->ensureCursorVisible(); + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + selectionChanged(); + repaintOldAndNewSelection(oldSelection); + + setBlinkingCursorEnabled(true); + } else + setBlinkingCursorEnabled(false); + } + + hasEditFocus = e->type() == QEvent::EnterEditFocus ? true : false; +} +#endif + +#ifndef QT_NO_CONTEXTMENU +QMenu *QTextControl::createStandardContextMenu(const QPointF &pos, QWidget *parent) +{ + Q_D(QTextControl); + + const bool showTextSelectionActions = d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + d->linkToCopy = QString(); + if (!pos.isNull()) + d->linkToCopy = anchorAt(pos); + + if (d->linkToCopy.isEmpty() && !showTextSelectionActions) + return 0; + + QMenu *menu = new QMenu(parent); + QAction *a; + + if (d->interactionFlags & Qt::TextEditable) { + a = menu->addAction(tr("&Undo") + ACCEL_KEY(QKeySequence::Undo), this, SLOT(undo())); + a->setEnabled(d->doc->isUndoAvailable()); + a = menu->addAction(tr("&Redo") + ACCEL_KEY(QKeySequence::Redo), this, SLOT(redo())); + a->setEnabled(d->doc->isRedoAvailable()); + menu->addSeparator(); + + a = menu->addAction(tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut), this, SLOT(cut())); + a->setEnabled(d->cursor.hasSelection()); + } + + if (showTextSelectionActions) { + a = menu->addAction(tr("&Copy") + ACCEL_KEY(QKeySequence::Copy), this, SLOT(copy())); + a->setEnabled(d->cursor.hasSelection()); + } + + if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard) + || (d->interactionFlags & Qt::LinksAccessibleByMouse)) { + + a = menu->addAction(tr("Copy &Link Location"), this, SLOT(_q_copyLink())); + a->setEnabled(!d->linkToCopy.isEmpty()); + } + + if (d->interactionFlags & Qt::TextEditable) { +#if !defined(QT_NO_CLIPBOARD) + a = menu->addAction(tr("&Paste") + ACCEL_KEY(QKeySequence::Paste), this, SLOT(paste())); + a->setEnabled(canPaste()); +#endif + a = menu->addAction(tr("Delete"), this, SLOT(_q_deleteSelected())); + a->setEnabled(d->cursor.hasSelection()); + } + + + if (showTextSelectionActions) { + menu->addSeparator(); + a = menu->addAction(tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll), this, SLOT(selectAll())); + a->setEnabled(!d->doc->isEmpty()); + } + +#if !defined(QT_NO_IM) + if (d->contextWidget) { + QInputContext *qic = d->inputContext(); + if (qic) { + QList<QAction *> imActions = qic->actions(); + for (int i = 0; i < imActions.size(); ++i) + menu->addAction(imActions.at(i)); + } + } +#endif + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) + if ((d->interactionFlags & Qt::TextEditable) && qt_use_rtl_extensions) { +#else + if (d->interactionFlags & Qt::TextEditable) { +#endif + menu->addSeparator(); + QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, menu); + menu->addMenu(ctrlCharacterMenu); + } + + return menu; +} +#endif // QT_NO_CONTEXTMENU + +QTextCursor QTextControl::cursorForPosition(const QPointF &pos) const +{ + Q_D(const QTextControl); + int cursorPos = hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + cursorPos = 0; + QTextCursor c(d->doc); + c.setPosition(cursorPos); + return c; +} + +QRectF QTextControl::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QTextControl); + if (cursor.isNull()) + return QRectF(); + + return d->rectForPosition(cursor.position()); +} + +QRectF QTextControl::cursorRect() const +{ + Q_D(const QTextControl); + return cursorRect(d->cursor); +} + +QRectF QTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const +{ + if (cursor.isNull()) + return QRectF(); + + return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0); +} + +QString QTextControl::anchorAt(const QPointF &pos) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->anchorAt(pos); +} + +QString QTextControl::anchorAtCursor() const +{ + Q_D(const QTextControl); + + return d->anchorForCursor(d->cursor); +} + +bool QTextControl::overwriteMode() const +{ + Q_D(const QTextControl); + return d->overwriteMode; +} + +void QTextControl::setOverwriteMode(bool overwrite) +{ + Q_D(QTextControl); + d->overwriteMode = overwrite; +} + +int QTextControl::cursorWidth() const +{ +#ifndef QT_NO_PROPERTIES + Q_D(const QTextControl); + return d->doc->documentLayout()->property("cursorWidth").toInt(); +#else + return 1; +#endif +} + +void QTextControl::setCursorWidth(int width) +{ + Q_D(QTextControl); +#ifdef QT_NO_PROPERTIES + Q_UNUSED(width); +#else + if (width == -1) + width = QApplication::style()->pixelMetric(QStyle::PM_TextCursorWidth); + d->doc->documentLayout()->setProperty("cursorWidth", width); +#endif + d->repaintCursor(); +} + +bool QTextControl::acceptRichText() const +{ + Q_D(const QTextControl); + return d->acceptRichText; +} + +void QTextControl::setAcceptRichText(bool accept) +{ + Q_D(QTextControl); + d->acceptRichText = accept; +} + +#ifndef QT_NO_TEXTEDIT + +void QTextControl::setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections) +{ + Q_D(QTextControl); + + QHash<int, int> hash; + for (int i = 0; i < d->extraSelections.count(); ++i) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(i); + hash.insertMulti(esel.cursor.anchor(), i); + } + + for (int i = 0; i < selections.count(); ++i) { + const QTextEdit::ExtraSelection &sel = selections.at(i); + QHash<int, int>::iterator it = hash.find(sel.cursor.anchor()); + if (it != hash.end()) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + if (esel.cursor.position() == sel.cursor.position() + && esel.format == sel.format) { + hash.erase(it); + continue; + } + } + QRectF r = selectionRect(sel.cursor); + if (sel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + for (QHash<int, int>::iterator it = hash.begin(); it != hash.end(); ++it) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + QRectF r = selectionRect(esel.cursor); + if (esel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + d->extraSelections.resize(selections.count()); + for (int i = 0; i < selections.count(); ++i) { + d->extraSelections[i].cursor = selections.at(i).cursor; + d->extraSelections[i].format = selections.at(i).format; + } +} + +QList<QTextEdit::ExtraSelection> QTextControl::extraSelections() const +{ + Q_D(const QTextControl); + QList<QTextEdit::ExtraSelection> selections; + for (int i = 0; i < d->extraSelections.count(); ++i) { + QTextEdit::ExtraSelection sel; + sel.cursor = d->extraSelections.at(i).cursor; + sel.format = d->extraSelections.at(i).format; + selections.append(sel); + } + return selections; +} + +#endif // QT_NO_TEXTEDIT + +void QTextControl::setTextWidth(qreal width) +{ + Q_D(QTextControl); + d->doc->setTextWidth(width); +} + +qreal QTextControl::textWidth() const +{ + Q_D(const QTextControl); + return d->doc->textWidth(); +} + +QSizeF QTextControl::size() const +{ + Q_D(const QTextControl); + return d->doc->size(); +} + +void QTextControl::setOpenExternalLinks(bool open) +{ + Q_D(QTextControl); + d->openExternalLinks = open; +} + +bool QTextControl::openExternalLinks() const +{ + Q_D(const QTextControl); + return d->openExternalLinks; +} + +bool QTextControl::ignoreUnusedNavigationEvents() const +{ + Q_D(const QTextControl); + return d->ignoreUnusedNavigationEvents; +} + +void QTextControl::setIgnoreUnusedNavigationEvents(bool ignore) +{ + Q_D(QTextControl); + d->ignoreUnusedNavigationEvents = ignore; +} + +void QTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) +{ + Q_D(QTextControl); + const QTextCursor oldSelection = d->cursor; + const bool moved = d->cursor.movePosition(op, mode); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (moved) + emit cursorPositionChanged(); +} + +bool QTextControl::canPaste() const +{ +#ifndef QT_NO_CLIPBOARD + Q_D(const QTextControl); + if (d->interactionFlags & Qt::TextEditable) { + const QMimeData *md = QApplication::clipboard()->mimeData(); + return md && canInsertFromMimeData(md); + } +#endif + return false; +} + +void QTextControl::setCursorIsFocusIndicator(bool b) +{ + Q_D(QTextControl); + d->cursorIsFocusIndicator = b; + d->repaintCursor(); +} + +bool QTextControl::cursorIsFocusIndicator() const +{ + Q_D(const QTextControl); + return d->cursorIsFocusIndicator; +} + + +void QTextControl::setDragEnabled(bool enabled) +{ + Q_D(QTextControl); + d->dragEnabled = enabled; +} + +bool QTextControl::isDragEnabled() const +{ + Q_D(const QTextControl); + return d->dragEnabled; +} + +void QTextControl::setWordSelectionEnabled(bool enabled) +{ + Q_D(QTextControl); + d->wordSelectionEnabled = enabled; +} + +bool QTextControl::isWordSelectionEnabled() const +{ + Q_D(const QTextControl); + return d->wordSelectionEnabled; +} + +#ifndef QT_NO_PRINTER +void QTextControl::print(QPrinter *printer) const +{ +#ifndef QT_NO_PRINTER + Q_D(const QTextControl); + if (!printer || !printer->isValid()) + return; + QTextDocument *tempDoc = 0; + const QTextDocument *doc = d->doc; + if (printer->printRange() == QPrinter::Selection) { + if (!d->cursor.hasSelection()) + return; + tempDoc = new QTextDocument(const_cast<QTextDocument *>(doc)); + tempDoc->setMetaInformation(QTextDocument::DocumentTitle, doc->metaInformation(QTextDocument::DocumentTitle)); + tempDoc->setPageSize(doc->pageSize()); + tempDoc->setDefaultFont(doc->defaultFont()); + tempDoc->setUseDesignMetrics(doc->useDesignMetrics()); + QTextCursor(tempDoc).insertFragment(d->cursor.selection()); + doc = tempDoc; + + // copy the custom object handlers + doc->documentLayout()->d_func()->handlers = d->doc->documentLayout()->d_func()->handlers; + } + doc->print(printer); + delete tempDoc; +#endif +} +#endif // QT_NO_PRINTER + +QMimeData *QTextControl::createMimeDataFromSelection() const +{ + Q_D(const QTextControl); + const QTextDocumentFragment fragment(d->cursor); + return new QTextEditMimeData(fragment); +} + +bool QTextControl::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QTextControl); + if (d->acceptRichText) + return (source->hasText() && !source->text().isEmpty()) + || source->hasHtml() + || source->hasFormat(QLatin1String("application/x-qrichtext")) + || source->hasFormat(QLatin1String("application/x-qt-richtext")); + else + return source->hasText() && !source->text().isEmpty(); +} + +void QTextControl::insertFromMimeData(const QMimeData *source) +{ + Q_D(QTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !source) + return; + + bool hasData = false; + QTextDocumentFragment fragment; +#ifndef QT_NO_TEXTHTMLPARSER + if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) { + // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore). + QString richtext = QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext"))); + richtext.prepend(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />")); + fragment = QTextDocumentFragment::fromHtml(richtext, d->doc); + hasData = true; + } else if (source->hasHtml() && d->acceptRichText) { + fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc); + hasData = true; + } else { + QString text = source->text(); + if (!text.isNull()) { + fragment = QTextDocumentFragment::fromPlainText(text); + hasData = true; + } + } +#else + fragment = QTextDocumentFragment::fromPlainText(source->text()); +#endif // QT_NO_TEXTHTMLPARSER + + if (hasData) + d->cursor.insertFragment(fragment); + ensureCursorVisible(); +} + +bool QTextControl::findNextPrevAnchor(const QTextCursor &startCursor, bool next, QTextCursor &newAnchor) +{ + Q_D(QTextControl); + + int anchorStart = -1; + QString anchorHref; + int anchorEnd = -1; + + if (next) { + const int startPos = startCursor.selectionEnd(); + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator it = block.begin(); + + while (!it.atEnd() && it.fragment().position() < startPos) + ++it; + + while (block.isValid()) { + anchorStart = -1; + + // find next anchor + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position(); + anchorHref = fmt.anchorHref(); + break; + } + } + + if (anchorStart != -1) { + anchorEnd = -1; + + // find next non-anchor fragment + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position(); + break; + } + } + + if (anchorEnd == -1) + anchorEnd = block.position() + block.length() - 1; + + // make found selection + break; + } + + block = block.next(); + it = block.begin(); + } + } else { + int startPos = startCursor.selectionStart(); + if (startPos > 0) + --startPos; + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator blockStart = block.begin(); + QTextBlock::Iterator it = block.end(); + + if (startPos == block.position()) { + it = block.begin(); + } else { + do { + if (it == blockStart) { + it = QTextBlock::Iterator(); + block = QTextBlock(); + } else { + --it; + } + } while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos); + } + + while (block.isValid()) { + anchorStart = -1; + + if (!it.atEnd()) { + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position() + fragment.length(); + anchorHref = fmt.anchorHref(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + } + + if (anchorStart != -1 && !it.atEnd()) { + anchorEnd = -1; + + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position() + fragment.length(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + + if (anchorEnd == -1) + anchorEnd = qMax(0, block.position()); + + break; + } + + block = block.previous(); + it = block.end(); + if (it != block.begin()) + --it; + blockStart = block.begin(); + } + + } + + if (anchorStart != -1 && anchorEnd != -1) { + newAnchor = d->cursor; + newAnchor.setPosition(anchorStart); + newAnchor.setPosition(anchorEnd, QTextCursor::KeepAnchor); + return true; + } + + return false; +} + +void QTextControlPrivate::activateLinkUnderCursor(QString href) +{ + QTextCursor oldCursor = cursor; + + if (href.isEmpty()) { + QTextCursor tmp = cursor; + if (tmp.selectionStart() != tmp.position()) + tmp.setPosition(tmp.selectionStart()); + tmp.movePosition(QTextCursor::NextCharacter); + href = tmp.charFormat().anchorHref(); + } + if (href.isEmpty()) + return; + + if (!cursor.hasSelection()) { + QTextBlock block = cursor.block(); + const int cursorPos = cursor.position(); + + QTextBlock::Iterator it = block.begin(); + QTextBlock::Iterator linkFragment; + + for (; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + const int fragmentPos = fragment.position(); + if (fragmentPos <= cursorPos && + fragmentPos + fragment.length() > cursorPos) { + linkFragment = it; + break; + } + } + + if (!linkFragment.atEnd()) { + it = linkFragment; + cursor.setPosition(it.fragment().position()); + if (it != block.begin()) { + do { + --it; + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position()); + } while (it != block.begin()); + } + + for (it = linkFragment; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); + } + } + } + + if (hasFocus) { + cursorIsFocusIndicator = true; + } else { + cursorIsFocusIndicator = false; + cursor.clearSelection(); + } + repaintOldAndNewSelection(oldCursor); + +#ifndef QT_NO_DESKTOPSERVICES + if (openExternalLinks) + QDesktopServices::openUrl(href); + else +#endif + emit q_func()->linkActivated(href); +} + +#ifndef QT_NO_TOOLTIP +void QTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget) +{ + const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip(); + if (toolTip.isEmpty()) + return; + QToolTip::showText(globalPos, toolTip, contextWidget); +} +#endif // QT_NO_TOOLTIP + +bool QTextControl::setFocusToNextOrPreviousAnchor(bool next) +{ + Q_D(QTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + QRectF crect = selectionRect(); + emit updateRequest(crect); + + // If we don't have a current anchor, we start from the start/end + if (!d->cursor.hasSelection()) { + d->cursor = QTextCursor(d->doc); + if (next) + d->cursor.movePosition(QTextCursor::Start); + else + d->cursor.movePosition(QTextCursor::End); + } + + QTextCursor newAnchor; + if (findNextPrevAnchor(d->cursor, next, newAnchor)) { + d->cursor = newAnchor; + d->cursorIsFocusIndicator = true; + } else { + d->cursor.clearSelection(); + } + + if (d->cursor.hasSelection()) { + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; + } else { + return false; + } +} + +bool QTextControl::setFocusToAnchor(const QTextCursor &newCursor) +{ + Q_D(QTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + // Verify that this is an anchor. + const QString anchorHref = d->anchorForCursor(newCursor); + if (anchorHref.isEmpty()) + return false; + + // and process it + QRectF crect = selectionRect(); + emit updateRequest(crect); + + d->cursor.setPosition(newCursor.selectionStart()); + d->cursor.setPosition(newCursor.selectionEnd(), QTextCursor::KeepAnchor); + d->cursorIsFocusIndicator = true; + + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; +} + +void QTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QTextControl); + if (flags == d->interactionFlags) + return; + d->interactionFlags = flags; + + if (d->hasFocus) + d->setBlinkingCursorEnabled(flags & Qt::TextEditable); +} + +Qt::TextInteractionFlags QTextControl::textInteractionFlags() const +{ + Q_D(const QTextControl); + return d->interactionFlags; +} + +void QTextControl::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QTextControl); + d->cursor.mergeCharFormat(modifier); + d->updateCurrentCharFormat(); +} + +void QTextControl::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QTextControl); + d->cursor.setCharFormat(format); + d->updateCurrentCharFormat(); +} + +QTextCharFormat QTextControl::currentCharFormat() const +{ + Q_D(const QTextControl); + return d->cursor.charFormat(); +} + +void QTextControl::insertPlainText(const QString &text) +{ + Q_D(QTextControl); + d->cursor.insertText(text); +} + +#ifndef QT_NO_TEXTHTMLPARSER +void QTextControl::insertHtml(const QString &text) +{ + Q_D(QTextControl); + d->cursor.insertHtml(text); +} +#endif // QT_NO_TEXTHTMLPARSER + +QPointF QTextControl::anchorPosition(const QString &name) const +{ + Q_D(const QTextControl); + if (name.isEmpty()) + return QPointF(); + + QRectF r; + for (QTextBlock block = d->doc->begin(); block.isValid(); block = block.next()) { + QTextCharFormat format = block.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(block.position()); + break; + } + + for (QTextBlock::Iterator it = block.begin(); !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + format = fragment.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(fragment.position()); + block = QTextBlock(); + break; + } + } + } + if (!r.isValid()) + return QPointF(); + return QPointF(0, r.top()); +} + +void QTextControl::adjustSize() +{ + Q_D(QTextControl); + d->doc->adjustSize(); +} + +bool QTextControl::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QTextControl); + QTextCursor search = d->doc->find(exp, d->cursor, options); + if (search.isNull()) + return false; + + setTextCursor(search); + return true; +} + + + +void QTextControlPrivate::append(const QString &text, Qt::TextFormat format) +{ + QTextCursor tmp(doc); + tmp.beginEditBlock(); + tmp.movePosition(QTextCursor::End); + + if (!doc->isEmpty()) + tmp.insertBlock(cursor.blockFormat(), cursor.charFormat()); + else + tmp.setCharFormat(cursor.charFormat()); + + // preserve the char format + QTextCharFormat oldCharFormat = cursor.charFormat(); + +#ifndef QT_NO_TEXTHTMLPARSER + if (format == Qt::RichText || (format == Qt::AutoText && Qt::mightBeRichText(text))) { + tmp.insertHtml(text); + } else { + tmp.insertText(text); + } +#else + tmp.insertText(text); +#endif // QT_NO_TEXTHTMLPARSER + if (!cursor.hasSelection()) + cursor.setCharFormat(oldCharFormat); + + tmp.endEditBlock(); +} + +void QTextControl::append(const QString &text) +{ + Q_D(QTextControl); + d->append(text, Qt::AutoText); +} + +void QTextControl::appendHtml(const QString &html) +{ + Q_D(QTextControl); + d->append(html, Qt::RichText); +} + +void QTextControl::appendPlainText(const QString &text) +{ + Q_D(QTextControl); + d->append(text, Qt::PlainText); +} + + +void QTextControl::ensureCursorVisible() +{ + Q_D(QTextControl); + QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0); + emit visibilityRequest(crect); + emit microFocusChanged(); +} + +QPalette QTextControl::palette() const +{ + Q_D(const QTextControl); + return d->palette; +} + +void QTextControl::setPalette(const QPalette &pal) +{ + Q_D(QTextControl); + d->palette = pal; +} + +QAbstractTextDocumentLayout::PaintContext QTextControl::getPaintContext(QWidget *widget) const +{ + Q_D(const QTextControl); + + QAbstractTextDocumentLayout::PaintContext ctx; + + ctx.selections = d->extraSelections; + ctx.palette = d->palette; + if (d->cursorOn && d->isEnabled) { + if (d->hideCursor) + ctx.cursorPosition = -1; + else if (d->preeditCursor != 0) + ctx.cursorPosition = - (d->preeditCursor + 2); + else + ctx.cursorPosition = d->cursor.position(); + } + + if (!d->dndFeedbackCursor.isNull()) + ctx.cursorPosition = d->dndFeedbackCursor.position(); +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || d->hasEditFocus) +#endif + if (d->cursor.hasSelection()) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = d->cursor; + if (d->cursorIsFocusIndicator) { + QStyleOption opt; + opt.palette = ctx.palette; + QStyleHintReturnVariant ret; + QStyle *style = QApplication::style(); + if (widget) + style = widget->style(); + style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret); + selection.format = qvariant_cast<QTextFormat>(ret.variant).toCharFormat(); + } else { + QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive; + selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight)); + selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText)); + QStyleOption opt; + QStyle *style = QApplication::style(); + if (widget) { + opt.initFrom(widget); + style = widget->style(); + } + if (style->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, widget)) + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + } + ctx.selections.append(selection); + } + + return ctx; +} + +void QTextControl::drawContents(QPainter *p, const QRectF &rect, QWidget *widget) +{ + Q_D(QTextControl); + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext(widget); + if (rect.isValid()) + p->setClipRect(rect, Qt::IntersectClip); + ctx.clip = rect; + + d->doc->documentLayout()->draw(p, ctx); + p->restore(); +} + +void QTextControlPrivate::_q_copyLink() +{ +#ifndef QT_NO_CLIPBOARD + QMimeData *md = new QMimeData; + md->setText(linkToCopy); + QApplication::clipboard()->setMimeData(md); +#endif +} + +QInputContext *QTextControlPrivate::inputContext() +{ + QInputContext *ctx = contextWidget->inputContext(); + if (!ctx && contextWidget->parentWidget()) + ctx = contextWidget->parentWidget()->inputContext(); + return ctx; +} + +int QTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->hitTest(point, accuracy); +} + +QRectF QTextControl::blockBoundingRect(const QTextBlock &block) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->blockBoundingRect(block); +} + +#ifndef QT_NO_CONTEXTMENU +#define NUM_CONTROL_CHARACTERS 10 +const struct QUnicodeControlCharacter { + const char *text; + ushort character; +} qt_controlCharacters[NUM_CONTROL_CHARACTERS] = { + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWSP Zero width space"), 0x200b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRE Start of left-to-right embedding"), 0x202a }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLE Start of right-to-left embedding"), 0x202b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRO Start of left-to-right override"), 0x202d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLO Start of right-to-left override"), 0x202e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDF Pop directional formatting"), 0x202c }, +}; + +QUnicodeControlCharacterMenu::QUnicodeControlCharacterMenu(QObject *_editWidget, QWidget *parent) + : QMenu(parent), editWidget(_editWidget) +{ + setTitle(tr("Insert Unicode control character")); + for (int i = 0; i < NUM_CONTROL_CHARACTERS; ++i) { + addAction(tr(qt_controlCharacters[i].text), this, SLOT(menuActionTriggered())); + } +} + +void QUnicodeControlCharacterMenu::menuActionTriggered() +{ + QAction *a = qobject_cast<QAction *>(sender()); + int idx = actions().indexOf(a); + if (idx < 0 || idx >= NUM_CONTROL_CHARACTERS) + return; + QChar c(qt_controlCharacters[idx].character); + QString str(c); + +#ifndef QT_NO_TEXTEDIT + if (QTextEdit *edit = qobject_cast<QTextEdit *>(editWidget)) { + edit->insertPlainText(str); + return; + } +#endif + if (QTextControl *control = qobject_cast<QTextControl *>(editWidget)) { + control->insertPlainText(str); + } +#ifndef QT_NO_LINEEDIT + if (QLineEdit *edit = qobject_cast<QLineEdit *>(editWidget)) { + edit->insert(str); + return; + } +#endif +} +#endif // QT_NO_CONTEXTMENU + +QStringList QTextEditMimeData::formats() const +{ + if (!fragment.isEmpty()) + return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html") +#ifndef QT_NO_TEXTODFWRITER + << QString::fromLatin1("application/vnd.oasis.opendocument.text") +#endif + ; + else + return QMimeData::formats(); +} + +QVariant QTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const +{ + if (!fragment.isEmpty()) + setup(); + return QMimeData::retrieveData(mimeType, type); +} + +void QTextEditMimeData::setup() const +{ + QTextEditMimeData *that = const_cast<QTextEditMimeData *>(this); +#ifndef QT_NO_TEXTHTMLPARSER + that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8()); +#endif +#ifndef QT_NO_TEXTODFWRITER + { + QBuffer buffer; + QTextDocumentWriter writer(&buffer, "ODF"); + writer.write(fragment); + buffer.close(); + that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data()); + } +#endif + that->setText(fragment.toPlainText()); + fragment = QTextDocumentFragment(); +} + +QT_END_NAMESPACE + +#include "moc_qtextcontrol_p.cpp" + +#endif // QT_NO_TEXTCONTROL diff --git a/src/gui/text/qtextcontrol_p.h b/src/gui/text/qtextcontrol_p.h new file mode 100644 index 0000000000..31fa843a3a --- /dev/null +++ b/src/gui/text/qtextcontrol_p.h @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCONTROL_P_H +#define QTEXTCONTROL_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 <QtGui/qtextdocument.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qtextformat.h> +#include <QtGui/qtextedit.h> +#include <QtGui/qmenu.h> +#include <QtCore/qrect.h> +#include <QtGui/qabstracttextdocumentlayout.h> +#include <QtGui/qtextdocumentfragment.h> +#include <QtGui/qclipboard.h> + +#ifdef QT3_SUPPORT +#include <QtGui/qtextobject.h> +#include <QtGui/qtextlayout.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyleSheet; +class QTextDocument; +class QMenu; +class QTextControlPrivate; +class QMimeData; +class QAbstractScrollArea; +class QEvent; +class QTimerEvent; + +class Q_GUI_EXPORT QTextControl : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QTextControl) +#ifndef QT_NO_TEXTHTMLPARSER + Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true) +#endif + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + Q_PROPERTY(bool acceptRichText READ acceptRichText WRITE setAcceptRichText) + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) + Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + Q_PROPERTY(bool ignoreUnusedNavigationEvents READ ignoreUnusedNavigationEvents WRITE setIgnoreUnusedNavigationEvents) +public: + explicit QTextControl(QObject *parent = 0); + explicit QTextControl(const QString &text, QObject *parent = 0); + explicit QTextControl(QTextDocument *doc, QObject *parent = 0); + virtual ~QTextControl(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + void mergeCurrentCharFormat(const QTextCharFormat &modifier); + + void setCurrentCharFormat(const QTextCharFormat &format); + QTextCharFormat currentCharFormat() const; + + bool find(const QString &exp, QTextDocument::FindFlags options = 0); + + inline QString toPlainText() const + { return document()->toPlainText(); } +#ifndef QT_NO_TEXTHTMLPARSER + inline QString toHtml() const + { return document()->toHtml(); } +#endif + + virtual void ensureCursorVisible(); + + virtual QVariant loadResource(int type, const QUrl &name); +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(const QPointF &pos, QWidget *parent); +#endif + + QTextCursor cursorForPosition(const QPointF &pos) const; + QRectF cursorRect(const QTextCursor &cursor) const; + QRectF cursorRect() const; + QRectF selectionRect(const QTextCursor &cursor) const; + QRectF selectionRect() const; + + QString anchorAt(const QPointF &pos) const; + QPointF anchorPosition(const QString &name) const; + + QString anchorAtCursor() const; + + bool overwriteMode() const; + void setOverwriteMode(bool overwrite); + + int cursorWidth() const; + void setCursorWidth(int width); + + bool acceptRichText() const; + void setAcceptRichText(bool accept); + +#ifndef QT_NO_TEXTEDIT + void setExtraSelections(const QList<QTextEdit::ExtraSelection> &selections); + QList<QTextEdit::ExtraSelection> extraSelections() const; +#endif + + void setTextWidth(qreal width); + qreal textWidth() const; + QSizeF size() const; + + void setOpenExternalLinks(bool open); + bool openExternalLinks() const; + + void setIgnoreUnusedNavigationEvents(bool ignore); + bool ignoreUnusedNavigationEvents() const; + + void moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + bool canPaste() const; + + void setCursorIsFocusIndicator(bool b); + bool cursorIsFocusIndicator() const; + + void setDragEnabled(bool enabled); + bool isDragEnabled() const; + + bool isWordSelectionEnabled() const; + void setWordSelectionEnabled(bool enabled); + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + + virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const; + virtual QRectF blockBoundingRect(const QTextBlock &block) const; + QAbstractTextDocumentLayout::PaintContext getPaintContext(QWidget *widget) const; + +public Q_SLOTS: + void setPlainText(const QString &text); + void setHtml(const QString &text); + +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(QClipboard::Mode mode = QClipboard::Clipboard); +#endif + + void undo(); + void redo(); + + void clear(); + void selectAll(); + + void insertPlainText(const QString &text); +#ifndef QT_NO_TEXTHTMLPARSER + void insertHtml(const QString &text); +#endif + + void append(const QString &text); + void appendHtml(const QString &html); + void appendPlainText(const QString &text); + + void adjustSize(); + +Q_SIGNALS: + void textChanged(); + void undoAvailable(bool b); + void redoAvailable(bool b); + void currentCharFormatChanged(const QTextCharFormat &format); + void copyAvailable(bool b); + void selectionChanged(); + void cursorPositionChanged(); + + // control signals + void updateRequest(const QRectF &rect = QRectF()); + void documentSizeChanged(const QSizeF &); + void blockCountChanged(int newBlockCount); + void visibilityRequest(const QRectF &rect); + void microFocusChanged(); + void linkActivated(const QString &link); + void linkHovered(const QString &); + void modificationChanged(bool m); + +public: + // control properties + QPalette palette() const; + void setPalette(const QPalette &pal); + + virtual void processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget = 0); + void processEvent(QEvent *e, const QPointF &coordinateOffset = QPointF(), QWidget *contextWidget = 0); + + // control methods + void drawContents(QPainter *painter, const QRectF &rect = QRectF(), QWidget *widget = 0); + + void setFocus(bool focus, Qt::FocusReason = Qt::OtherFocusReason); + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + virtual QMimeData *createMimeDataFromSelection() const; + virtual bool canInsertFromMimeData(const QMimeData *source) const; + virtual void insertFromMimeData(const QMimeData *source); + + bool setFocusToAnchor(const QTextCursor &newCursor); + bool setFocusToNextOrPreviousAnchor(bool next); + bool findNextPrevAnchor(const QTextCursor& from, bool next, QTextCursor& newAnchor); + +protected: + virtual void timerEvent(QTimerEvent *e); + + virtual bool event(QEvent *e); + +private: + Q_DISABLE_COPY(QTextControl) + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrentCharFormatAndSelection()) + Q_PRIVATE_SLOT(d_func(), void _q_emitCursorPosChanged(const QTextCursor &)) + Q_PRIVATE_SLOT(d_func(), void _q_deleteSelected()) + Q_PRIVATE_SLOT(d_func(), void _q_copyLink()) + Q_PRIVATE_SLOT(d_func(), void _q_updateBlock(const QTextBlock &)) + Q_PRIVATE_SLOT(d_func(), void _q_documentLayoutChanged()) +}; + + +#ifndef QT_NO_CONTEXTMENU +class QUnicodeControlCharacterMenu : public QMenu +{ + Q_OBJECT +public: + QUnicodeControlCharacterMenu(QObject *editWidget, QWidget *parent); + +private Q_SLOTS: + void menuActionTriggered(); + +private: + QObject *editWidget; +}; +#endif // QT_NO_CONTEXTMENU + + +// also used by QLabel +class QTextEditMimeData : public QMimeData +{ +public: + inline QTextEditMimeData(const QTextDocumentFragment &aFragment) : fragment(aFragment) {} + + virtual QStringList formats() const; +protected: + virtual QVariant retrieveData(const QString &mimeType, QVariant::Type type) const; +private: + void setup() const; + + mutable QTextDocumentFragment fragment; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTCONTROL_H diff --git a/src/gui/text/qtextcontrol_p_p.h b/src/gui/text/qtextcontrol_p_p.h new file mode 100644 index 0000000000..94670e225e --- /dev/null +++ b/src/gui/text/qtextcontrol_p_p.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCONTROL_P_P_H +#define QTEXTCONTROL_P_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 "QtGui/qtextdocumentfragment.h" +#include "QtGui/qscrollbar.h" +#include "QtGui/qtextcursor.h" +#include "QtGui/qtextformat.h" +#include "QtGui/qmenu.h" +#include "QtGui/qabstracttextdocumentlayout.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qpointer.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QMimeData; +class QAbstractScrollArea; +class QInputContext; + +class QTextControlPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QTextControl) +public: + QTextControlPrivate(); + + bool cursorMoveKeyEvent(QKeyEvent *e); + + void updateCurrentCharFormat(); + + void indent(); + void outdent(); + + void gotoNextTableCell(); + void gotoPreviousTableCell(); + + void createAutoBulletList(); + + void init(Qt::TextFormat format = Qt::RichText, const QString &text = QString(), + QTextDocument *document = 0); + void setContent(Qt::TextFormat format = Qt::RichText, const QString &text = QString(), + QTextDocument *document = 0); + void startDrag(); + + void paste(const QMimeData *source); + + void setCursorPosition(const QPointF &pos); + void setCursorPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + void repaintCursor(); + inline void repaintSelection() + { repaintOldAndNewSelection(QTextCursor()); } + void repaintOldAndNewSelection(const QTextCursor &oldSelection); + + void selectionChanged(bool forceEmitSelectionChanged = false); + + void _q_updateCurrentCharFormatAndSelection(); + +#ifndef QT_NO_CLIPBOARD + void setClipboardSelection(); +#endif + + void _q_emitCursorPosChanged(const QTextCursor &someCursor); + + void setBlinkingCursorEnabled(bool enable); + + void extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition); + void extendBlockwiseSelection(int suggestedNewPosition); + + void _q_deleteSelected(); + + void _q_setCursorAfterUndoRedo(int undoPosition, int charsAdded, int charsRemoved); + + QRectF cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const; + QRectF rectForPosition(int position) const; + QRectF selectionRect(const QTextCursor &cursor) const; + inline QRectF selectionRect() const + { return selectionRect(this->cursor); } + + QString anchorForCursor(const QTextCursor &anchor) const; + + void keyPressEvent(QKeyEvent *e); + void mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + bool sendMouseEventToInputContext(QEvent *e, QEvent::Type eventType, Qt::MouseButton button, + const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget); + void focusEvent(QFocusEvent *e); +#ifdef QT_KEYPAD_NAVIGATION + void editFocusEvent(QEvent *e); +#endif + bool dragEnterEvent(QEvent *e, const QMimeData *mimeData); + void dragLeaveEvent(); + bool dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos); + bool dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QWidget *source); + + void inputMethodEvent(QInputMethodEvent *); + + void activateLinkUnderCursor(QString href = QString()); + +#ifndef QT_NO_TOOLTIP + void showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget); +#endif + + void append(const QString &text, Qt::TextFormat format = Qt::AutoText); + + QInputContext *inputContext(); + + QTextDocument *doc; + bool cursorOn; + QTextCursor cursor; + bool cursorIsFocusIndicator; + QTextCharFormat lastCharFormat; + + QTextCursor dndFeedbackCursor; + + Qt::TextInteractionFlags interactionFlags; + + QBasicTimer cursorBlinkTimer; + QBasicTimer trippleClickTimer; + QPointF trippleClickPoint; + + bool dragEnabled; + + bool mousePressed; + + bool mightStartDrag; + QPoint dragStartPos; + QPointer<QWidget> contextWidget; + + bool lastSelectionState; + + bool ignoreAutomaticScrollbarAdjustement; + + QTextCursor selectedWordOnDoubleClick; + QTextCursor selectedBlockOnTrippleClick; + + bool overwriteMode; + bool acceptRichText; + + int preeditCursor; + bool hideCursor; // used to hide the cursor in the preedit area + + QVector<QAbstractTextDocumentLayout::Selection> extraSelections; + + QPalette palette; + bool hasFocus; +#ifdef QT_KEYPAD_NAVIGATION + bool hasEditFocus; +#endif + bool isEnabled; + + QString highlightedAnchor; // Anchor below cursor + QString anchorOnMousePress; + bool hadSelectionOnMousePress; + + bool ignoreUnusedNavigationEvents; + bool openExternalLinks; + + bool wordSelectionEnabled; + + QString linkToCopy; + void _q_copyLink(); + void _q_updateBlock(const QTextBlock &); + void _q_documentLayoutChanged(); +}; + +QT_END_NAMESPACE + +#endif // QTEXTCONTROL_P_H diff --git a/src/gui/text/qtextcursor.cpp b/src/gui/text/qtextcursor.cpp new file mode 100644 index 0000000000..6ddfdb00e1 --- /dev/null +++ b/src/gui/text/qtextcursor.cpp @@ -0,0 +1,2561 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextcursor.h" +#include "qtextcursor_p.h" +#include "qglobal.h" +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtextlist.h" +#include "qtexttable.h" +#include "qtexttable_p.h" +#include "qtextengine_p.h" +#include "qabstracttextdocumentlayout.h" + +#include <qtextlayout.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +enum { + AdjustPrev = 0x1, + AdjustUp = 0x3, + AdjustNext = 0x4, + AdjustDown = 0x12 +}; + +QTextCursorPrivate::QTextCursorPrivate(QTextDocumentPrivate *p) + : priv(p), x(0), position(0), anchor(0), adjusted_anchor(0), + currentCharFormat(-1), visualNavigation(false), keepPositionOnInsert(false), + changed(false) +{ + priv->addCursor(this); +} + +QTextCursorPrivate::QTextCursorPrivate(const QTextCursorPrivate &rhs) + : QSharedData(rhs) +{ + position = rhs.position; + anchor = rhs.anchor; + adjusted_anchor = rhs.adjusted_anchor; + priv = rhs.priv; + x = rhs.x; + currentCharFormat = rhs.currentCharFormat; + visualNavigation = rhs.visualNavigation; + keepPositionOnInsert = rhs.keepPositionOnInsert; + changed = rhs.changed; + priv->addCursor(this); +} + +QTextCursorPrivate::~QTextCursorPrivate() +{ + if (priv) + priv->removeCursor(this); +} + +QTextCursorPrivate::AdjustResult QTextCursorPrivate::adjustPosition(int positionOfChange, int charsAddedOrRemoved, QTextUndoCommand::Operation op) +{ + QTextCursorPrivate::AdjustResult result = QTextCursorPrivate::CursorMoved; + // not(!) <= , so that inserting text adjusts the cursor correctly + if (position < positionOfChange + || (position == positionOfChange + && (op == QTextUndoCommand::KeepCursor + || keepPositionOnInsert) + ) + ) { + result = CursorUnchanged; + } else { + if (charsAddedOrRemoved < 0 && position < positionOfChange - charsAddedOrRemoved) + position = positionOfChange; + else + position += charsAddedOrRemoved; + + currentCharFormat = -1; + } + + if (anchor >= positionOfChange + && (anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) { + if (charsAddedOrRemoved < 0 && anchor < positionOfChange - charsAddedOrRemoved) + anchor = positionOfChange; + else + anchor += charsAddedOrRemoved; + } + + if (adjusted_anchor >= positionOfChange + && (adjusted_anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) { + if (charsAddedOrRemoved < 0 && adjusted_anchor < positionOfChange - charsAddedOrRemoved) + adjusted_anchor = positionOfChange; + else + adjusted_anchor += charsAddedOrRemoved; + } + + return result; +} + +void QTextCursorPrivate::setX() +{ + if (priv->isInEditBlock()) { + x = -1; // mark dirty + return; + } + + QTextBlock block = this->block(); + const QTextLayout *layout = blockLayout(block); + int pos = position - block.position(); + + QTextLine line = layout->lineForTextPosition(pos); + if (line.isValid()) + x = line.cursorToX(pos); + else + x = -1; // delayed init. Makes movePosition() call setX later on again. +} + +void QTextCursorPrivate::remove() +{ + if (anchor == position) + return; + currentCharFormat = -1; + int pos1 = position; + int pos2 = adjusted_anchor; + QTextUndoCommand::Operation op = QTextUndoCommand::KeepCursor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + op = QTextUndoCommand::MoveCursor; + } + + // deleting inside table? -> delete only content + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int startRow, startCol, numRows, numCols; + selectedTableCells(&startRow, &numRows, &startCol, &numCols); + clearCells(table, startRow, startCol, numRows, numCols, op); + adjusted_anchor = anchor = position; + priv->endEditBlock(); + } else { + priv->remove(pos1, pos2-pos1, op); + adjusted_anchor = anchor = position; + priv->finishEdit(); + } + +} + +void QTextCursorPrivate::clearCells(QTextTable *table, int startRow, int startCol, int numRows, int numCols, QTextUndoCommand::Operation op) +{ + priv->beginEditBlock(); + + for (int row = startRow; row < startRow + numRows; ++row) + for (int col = startCol; col < startCol + numCols; ++col) { + QTextTableCell cell = table->cellAt(row, col); + const int startPos = cell.firstPosition(); + const int endPos = cell.lastPosition(); + Q_ASSERT(startPos <= endPos); + priv->remove(startPos, endPos - startPos, op); + } + + priv->endEditBlock(); +} + +bool QTextCursorPrivate::canDelete(int pos) const +{ + QTextDocumentPrivate::FragmentIterator fit = priv->find(pos); + QTextCharFormat fmt = priv->formatCollection()->charFormat((*fit)->format); + return (fmt.objectIndex() == -1 || fmt.objectType() == QTextFormat::ImageObject); +} + +void QTextCursorPrivate::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat) +{ + QTextFormatCollection *formats = priv->formatCollection(); + int idx = formats->indexForFormat(format); + Q_ASSERT(formats->format(idx).isBlockFormat()); + + priv->insertBlock(position, idx, formats->indexForFormat(charFormat)); + currentCharFormat = -1; +} + +void QTextCursorPrivate::adjustCursor(QTextCursor::MoveOperation m) +{ + adjusted_anchor = anchor; + if (position == anchor) + return; + + QTextFrame *f_position = priv->frameAt(position); + QTextFrame *f_anchor = priv->frameAt(adjusted_anchor); + + if (f_position != f_anchor) { + // find common parent frame + QList<QTextFrame *> positionChain; + QList<QTextFrame *> anchorChain; + QTextFrame *f = f_position; + while (f) { + positionChain.prepend(f); + f = f->parentFrame(); + } + f = f_anchor; + while (f) { + anchorChain.prepend(f); + f = f->parentFrame(); + } + Q_ASSERT(positionChain.at(0) == anchorChain.at(0)); + int i = 1; + int l = qMin(positionChain.size(), anchorChain.size()); + for (; i < l; ++i) { + if (positionChain.at(i) != anchorChain.at(i)) + break; + } + + if (m <= QTextCursor::WordLeft) { + if (i < positionChain.size()) + position = positionChain.at(i)->firstPosition() - 1; + } else { + if (i < positionChain.size()) + position = positionChain.at(i)->lastPosition() + 1; + } + if (position < adjusted_anchor) { + if (i < anchorChain.size()) + adjusted_anchor = anchorChain.at(i)->lastPosition() + 1; + } else { + if (i < anchorChain.size()) + adjusted_anchor = anchorChain.at(i)->firstPosition() - 1; + } + + f_position = positionChain.at(i-1); + } + + // same frame, either need to adjust to cell boundaries or return + QTextTable *table = qobject_cast<QTextTable *>(f_position); + if (!table) + return; + + QTextTableCell c_position = table->cellAt(position); + QTextTableCell c_anchor = table->cellAt(adjusted_anchor); + if (c_position != c_anchor) { + bool before; + int col_position = c_position.column(); + int col_anchor = c_anchor.column(); + if (col_position == col_anchor) { + before = c_position.row() < c_anchor.row(); + } else { + before = col_position < col_anchor; + } + + // adjust to cell boundaries + if (m <= QTextCursor::WordLeft) { + position = c_position.firstPosition(); + if (!before) + --position; + } else { + position = c_position.lastPosition(); + if (before) + ++position; + } + if (position < adjusted_anchor) + adjusted_anchor = c_anchor.lastPosition(); + else + adjusted_anchor = c_anchor.firstPosition(); + } + currentCharFormat = -1; +} + +void QTextCursorPrivate::aboutToRemoveCell(int from, int to) +{ + Q_ASSERT(from <= to); + if (position == anchor) + return; + + QTextTable *t = qobject_cast<QTextTable *>(priv->frameAt(position)); + if (!t) + return; + QTextTableCell removedCellFrom = t->cellAt(from); + QTextTableCell removedCellEnd = t->cellAt(to); + if (! removedCellFrom.isValid() || !removedCellEnd.isValid()) + return; + + int curFrom = position; + int curTo = adjusted_anchor; + if (curTo < curFrom) + qSwap(curFrom, curTo); + + QTextTableCell cellStart = t->cellAt(curFrom); + QTextTableCell cellEnd = t->cellAt(curTo); + + if (cellStart.row() >= removedCellFrom.row() && cellEnd.row() <= removedCellEnd.row() + && cellStart.column() >= removedCellFrom.column() + && cellEnd.column() <= removedCellEnd.column()) { // selection is completely removed + // find a new position, as close as possible to where we were. + QTextTableCell cell; + if (removedCellFrom.row() == 0 && removedCellEnd.row() == t->rows()-1) // removed n columns + cell = t->cellAt(cellStart.row(), removedCellEnd.column()+1); + else if (removedCellFrom.column() == 0 && removedCellEnd.column() == t->columns()-1) // removed n rows + cell = t->cellAt(removedCellEnd.row() + 1, cellStart.column()); + + int newPosition; + if (cell.isValid()) + newPosition = cell.firstPosition(); + else + newPosition = t->lastPosition()+1; + + setPosition(newPosition); + anchor = newPosition; + adjusted_anchor = newPosition; + x = 0; + } + else if (cellStart.row() >= removedCellFrom.row() && cellStart.row() <= removedCellEnd.row() + && cellEnd.row() > removedCellEnd.row()) { + int newPosition = t->cellAt(removedCellEnd.row() + 1, cellStart.column()).firstPosition(); + if (position < anchor) + position = newPosition; + else + anchor = adjusted_anchor = newPosition; + } + else if (cellStart.column() >= removedCellFrom.column() && cellStart.column() <= removedCellEnd.column() + && cellEnd.column() > removedCellEnd.column()) { + int newPosition = t->cellAt(cellStart.row(), removedCellEnd.column()+1).firstPosition(); + if (position < anchor) + position = newPosition; + else + anchor = adjusted_anchor = newPosition; + } +} + +bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) +{ + currentCharFormat = -1; + bool adjustX = true; + QTextBlock blockIt = block(); + + if (!blockIt.isValid()) + return false; + + if (op >= QTextCursor::Left && op <= QTextCursor::WordRight + && blockIt.textDirection() == Qt::RightToLeft) { + if (op == QTextCursor::Left) + op = QTextCursor::NextCharacter; + else if (op == QTextCursor::Right) + op = QTextCursor::PreviousCharacter; + else if (op == QTextCursor::WordLeft) + op = QTextCursor::NextWord; + else if (op == QTextCursor::WordRight) + op = QTextCursor::PreviousWord; + } + + const QTextLayout *layout = blockLayout(blockIt); + int relativePos = position - blockIt.position(); + QTextLine line; + if (!priv->isInEditBlock()) + line = layout->lineForTextPosition(relativePos); + + Q_ASSERT(priv->frameAt(position) == priv->frameAt(adjusted_anchor)); + + int newPosition = position; + + if (x == -1 && !priv->isInEditBlock() && (op == QTextCursor::Up || op == QTextCursor::Down)) + setX(); + + switch(op) { + case QTextCursor::NoMove: + return true; + + case QTextCursor::Start: + newPosition = 0; + break; + case QTextCursor::StartOfLine: { + newPosition = blockIt.position(); + if (line.isValid()) + newPosition += line.textStart(); + + break; + } + case QTextCursor::StartOfBlock: { + newPosition = blockIt.position(); + break; + } + case QTextCursor::PreviousBlock: { + if (blockIt == priv->blocksBegin()) + return false; + blockIt = blockIt.previous(); + + newPosition = blockIt.position(); + break; + } + case QTextCursor::PreviousCharacter: + case QTextCursor::Left: + newPosition = priv->previousCursorPosition(position, QTextLayout::SkipCharacters); + break; + case QTextCursor::StartOfWord: { + if (relativePos == 0) + break; + + // skip if already at word start + QTextEngine *engine = layout->engine(); + engine->attributes(); + if ((relativePos == blockIt.length() - 1) + && (engine->atSpace(relativePos - 1) || engine->atWordSeparator(relativePos - 1))) + return false; + + if (relativePos < blockIt.length()-1) + ++position; + + // FALL THROUGH! + } + case QTextCursor::PreviousWord: + case QTextCursor::WordLeft: + newPosition = priv->previousCursorPosition(position, QTextLayout::SkipWords); + break; + case QTextCursor::Up: { + int i = line.lineNumber() - 1; + if (i == -1) { + if (blockIt == priv->blocksBegin()) + return false; + int blockPosition = blockIt.position(); + QTextTable *table = qobject_cast<QTextTable *>(priv->frameAt(blockPosition)); + if (table) { + QTextTableCell cell = table->cellAt(blockPosition); + if (cell.firstPosition() == blockPosition) { + int row = cell.row() - 1; + if (row >= 0) { + blockPosition = table->cellAt(row, cell.column()).lastPosition(); + } else { + // move to line above the table + blockPosition = table->firstPosition() - 1; + } + blockIt = priv->blocksFind(blockPosition); + } else { + blockIt = blockIt.previous(); + } + } else { + blockIt = blockIt.previous(); + } + layout = blockLayout(blockIt); + i = layout->lineCount()-1; + } + if (layout->lineCount()) { + QTextLine line = layout->lineAt(i); + newPosition = line.xToCursor(x) + blockIt.position(); + } else { + newPosition = blockIt.position(); + } + adjustX = false; + break; + } + + case QTextCursor::End: + newPosition = priv->length() - 1; + break; + case QTextCursor::EndOfLine: { + if (!line.isValid() || line.textLength() == 0) { + if (blockIt.length() >= 1) + // position right before the block separator + newPosition = blockIt.position() + blockIt.length() - 1; + break; + } + newPosition = blockIt.position() + line.textStart() + line.textLength(); + if (line.lineNumber() < layout->lineCount() - 1) { + const QString text = blockIt.text(); + // ###### this relies on spaces being the cause for linebreaks. + // this doesn't work with japanese + if (text.at(line.textStart() + line.textLength() - 1).isSpace()) + --newPosition; + } + break; + } + case QTextCursor::EndOfWord: { + QTextEngine *engine = layout->engine(); + engine->attributes(); + const int len = blockIt.length() - 1; + if (relativePos >= len) + return false; + if (engine->atWordSeparator(relativePos)) { + ++relativePos; + while (relativePos < len && engine->atWordSeparator(relativePos)) + ++relativePos; + } else { + while (relativePos < len && !engine->atSpace(relativePos) && !engine->atWordSeparator(relativePos)) + ++relativePos; + } + newPosition = blockIt.position() + relativePos; + break; + } + case QTextCursor::EndOfBlock: + if (blockIt.length() >= 1) + // position right before the block separator + newPosition = blockIt.position() + blockIt.length() - 1; + break; + case QTextCursor::NextBlock: { + blockIt = blockIt.next(); + if (!blockIt.isValid()) + return false; + + newPosition = blockIt.position(); + break; + } + case QTextCursor::NextCharacter: + case QTextCursor::Right: + newPosition = priv->nextCursorPosition(position, QTextLayout::SkipCharacters); + break; + case QTextCursor::NextWord: + case QTextCursor::WordRight: + newPosition = priv->nextCursorPosition(position, QTextLayout::SkipWords); + break; + + case QTextCursor::Down: { + int i = line.lineNumber() + 1; + + if (i >= layout->lineCount()) { + int blockPosition = blockIt.position() + blockIt.length() - 1; + QTextTable *table = qobject_cast<QTextTable *>(priv->frameAt(blockPosition)); + if (table) { + QTextTableCell cell = table->cellAt(blockPosition); + if (cell.lastPosition() == blockPosition) { + int row = cell.row() + cell.rowSpan(); + if (row < table->rows()) { + blockPosition = table->cellAt(row, cell.column()).firstPosition(); + } else { + // move to line below the table + blockPosition = table->lastPosition() + 1; + } + blockIt = priv->blocksFind(blockPosition); + } else { + blockIt = blockIt.next(); + } + } else { + blockIt = blockIt.next(); + } + + if (blockIt == priv->blocksEnd()) + return false; + layout = blockLayout(blockIt); + i = 0; + } + if (layout->lineCount()) { + QTextLine line = layout->lineAt(i); + newPosition = line.xToCursor(x) + blockIt.position(); + } else { + newPosition = blockIt.position(); + } + adjustX = false; + break; + } + case QTextCursor::NextCell: // fall through + case QTextCursor::PreviousCell: // fall through + case QTextCursor::NextRow: // fall through + case QTextCursor::PreviousRow: { + QTextTable *table = qobject_cast<QTextTable *>(priv->frameAt(position)); + if (!table) + return false; + + QTextTableCell cell = table->cellAt(position); + Q_ASSERT(cell.isValid()); + int column = cell.column(); + int row = cell.row(); + const int currentRow = row; + if (op == QTextCursor::NextCell || op == QTextCursor::NextRow) { + do { + column += cell.columnSpan(); + if (column >= table->columns()) { + column = 0; + ++row; + } + cell = table->cellAt(row, column); + // note we also continue while we have not reached a cell thats not merged with one above us + } while (cell.isValid() + && ((op == QTextCursor::NextRow && currentRow == cell.row()) + || cell.row() < row)); + } + else if (op == QTextCursor::PreviousCell || op == QTextCursor::PreviousRow) { + do { + --column; + if (column < 0) { + column = table->columns()-1; + --row; + } + cell = table->cellAt(row, column); + // note we also continue while we have not reached a cell thats not merged with one above us + } while (cell.isValid() + && ((op == QTextCursor::PreviousRow && currentRow == cell.row()) + || cell.row() < row)); + } + if (cell.isValid()) + newPosition = cell.firstPosition(); + break; + } + } + + if (mode == QTextCursor::KeepAnchor) { + QTextTable *table = qobject_cast<QTextTable *>(priv->frameAt(position)); + if (table && ((op >= QTextCursor::PreviousBlock && op <= QTextCursor::WordLeft) + || (op >= QTextCursor::NextBlock && op <= QTextCursor::WordRight))) { + int oldColumn = table->cellAt(position).column(); + + const QTextTableCell otherCell = table->cellAt(newPosition); + if (!otherCell.isValid()) + return false; + + int newColumn = otherCell.column(); + if ((oldColumn > newColumn && op >= QTextCursor::End) + || (oldColumn < newColumn && op <= QTextCursor::WordLeft)) + return false; + } + } + + const bool moved = setPosition(newPosition); + + if (mode == QTextCursor::MoveAnchor) { + anchor = position; + adjusted_anchor = position; + } else { + adjustCursor(op); + } + + if (adjustX) + setX(); + + return moved; +} + +QTextTable *QTextCursorPrivate::complexSelectionTable() const +{ + if (position == anchor) + return 0; + + QTextTable *t = qobject_cast<QTextTable *>(priv->frameAt(position)); + if (t) { + QTextTableCell cell_pos = t->cellAt(position); + QTextTableCell cell_anchor = t->cellAt(adjusted_anchor); + + Q_ASSERT(cell_anchor.isValid()); + + if (cell_pos == cell_anchor) + t = 0; + } + return t; +} + +void QTextCursorPrivate::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const +{ + *firstRow = -1; + *firstColumn = -1; + *numRows = -1; + *numColumns = -1; + + if (position == anchor) + return; + + QTextTable *t = qobject_cast<QTextTable *>(priv->frameAt(position)); + if (!t) + return; + + QTextTableCell cell_pos = t->cellAt(position); + QTextTableCell cell_anchor = t->cellAt(adjusted_anchor); + + Q_ASSERT(cell_anchor.isValid()); + + if (cell_pos == cell_anchor) + return; + + *firstRow = qMin(cell_pos.row(), cell_anchor.row()); + *firstColumn = qMin(cell_pos.column(), cell_anchor.column()); + *numRows = qMax(cell_pos.row() + cell_pos.rowSpan(), cell_anchor.row() + cell_anchor.rowSpan()) - *firstRow; + *numColumns = qMax(cell_pos.column() + cell_pos.columnSpan(), cell_anchor.column() + cell_anchor.columnSpan()) - *firstColumn; +} + +static void setBlockCharFormatHelper(QTextDocumentPrivate *priv, int pos1, int pos2, + const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + QTextBlock it = priv->blocksFind(pos1); + QTextBlock end = priv->blocksFind(pos2); + if (end.isValid()) + end = end.next(); + + for (; it != end; it = it.next()) { + priv->setCharFormat(it.position() - 1, 1, format, changeMode); + } +} + +void QTextCursorPrivate::setBlockCharFormat(const QTextCharFormat &_format, + QTextDocumentPrivate::FormatChangeMode changeMode) +{ + priv->beginEditBlock(); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + QTextTable *table = complexSelectionTable(); + if (table) { + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode); + } + } + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode); + } + priv->endEditBlock(); +} + + +void QTextCursorPrivate::setBlockFormat(const QTextBlockFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + priv->setBlockFormat(priv->blocksFind(pos1), priv->blocksFind(pos2), format, changeMode); + } + } + priv->endEditBlock(); + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + priv->setBlockFormat(priv->blocksFind(pos1), priv->blocksFind(pos2), format, changeMode); + } +} + +void QTextCursorPrivate::setCharFormat(const QTextCharFormat &_format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + Q_ASSERT(position != anchor); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + priv->setCharFormat(pos1, pos2-pos1, format, changeMode); + } + } + priv->endEditBlock(); + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + priv->setCharFormat(pos1, pos2-pos1, format, changeMode); + } +} + + +QTextLayout *QTextCursorPrivate::blockLayout(QTextBlock &block) const{ + QTextLayout *tl = block.layout(); + if (!tl->lineCount() && priv->layout()) + priv->layout()->blockBoundingRect(block); + return tl; +} + +/*! + \class QTextCursor + \reentrant + + \brief The QTextCursor class offers an API to access and modify QTextDocuments. + + \ingroup richtext-processing + \ingroup shared + + Text cursors are objects that are used to access and modify the + contents and underlying structure of text documents via a + programming interface that mimics the behavior of a cursor in a + text editor. QTextCursor contains information about both the + cursor's position within a QTextDocument and any selection that it + has made. + + QTextCursor is modeled on the way a text cursor behaves in a text + editor, providing a programmatic means of performing standard + actions through the user interface. A document can be thought of + as a single string of characters. The cursor's current position() + then is always either \e between two consecutive characters in the + string, or else \e before the very first character or \e after the + very last character in the string. Documents can also contain + tables, lists, images, and other objects in addition to text but, + from the developer's point of view, the document can be treated as + one long string. Some portions of that string can be considered + to lie within particular blocks (e.g. paragraphs), or within a + table's cell, or a list's item, or other structural elements. When + we refer to "current character" we mean the character immediately + \e before the cursor position() in the document. Similarly, the + "current block" is the block that contains the cursor position(). + + A QTextCursor also has an anchor() position. The text that is + between the anchor() and the position() is the selection. If + anchor() == position() there is no selection. + + The cursor position can be changed programmatically using + setPosition() and movePosition(); the latter can also be used to + select text. For selections see selectionStart(), selectionEnd(), + hasSelection(), clearSelection(), and removeSelectedText(). + + If the position() is at the start of a block atBlockStart() + returns true; and if it is at the end of a block atBlockEnd() returns + true. The format of the current character is returned by + charFormat(), and the format of the current block is returned by + blockFormat(). + + Formatting can be applied to the current text document using the + setCharFormat(), mergeCharFormat(), setBlockFormat() and + mergeBlockFormat() functions. The 'set' functions will replace the + cursor's current character or block format, while the 'merge' + functions add the given format properties to the cursor's current + format. If the cursor has a selection the given format is applied + to the current selection. Note that when only parts of a block is + selected the block format is applied to the entire block. The text + at the current character position can be turned into a list using + createList(). + + Deletions can be achieved using deleteChar(), + deletePreviousChar(), and removeSelectedText(). + + Text strings can be inserted into the document with the insertText() + function, blocks (representing new paragraphs) can be inserted with + insertBlock(). + + Existing fragments of text can be inserted with insertFragment() but, + if you want to insert pieces of text in various formats, it is usually + still easier to use insertText() and supply a character format. + + Various types of higher-level structure can also be inserted into the + document with the cursor: + + \list + \i Lists are ordered sequences of block elements that are decorated with + bullet points or symbols. These are inserted in a specified format + with insertList(). + \i Tables are inserted with the insertTable() function, and can be + given an optional format. These contain an array of cells that can + be traversed using the cursor. + \i Inline images are inserted with insertImage(). The image to be + used can be specified in an image format, or by name. + \i Frames are inserted by calling insertFrame() with a specified format. + \endlist + + Actions can be grouped (i.e. treated as a single action for + undo/redo) using beginEditBlock() and endEditBlock(). + + Cursor movements are limited to valid cursor positions. In Latin + writing this is between any two consecutive characters in the + text, before the first character, or after the last character. In + some other writing systems cursor movements are limited to + "clusters" (e.g. a syllable in Devanagari, or a base letter plus + diacritics). Functions such as movePosition() and deleteChar() + limit cursor movement to these valid positions. + + \sa \link richtext.html Rich Text Processing\endlink + +*/ + +/*! + \enum QTextCursor::MoveOperation + + \value NoMove Keep the cursor where it is + + \value Start Move to the start of the document. + \value StartOfLine Move to the start of the current line. + \value StartOfBlock Move to the start of the current block. + \value StartOfWord Move to the start of the current word. + \value PreviousBlock Move to the start of the previous block. + \value PreviousCharacter Move to the previous character. + \value PreviousWord Move to the beginning of the previous word. + \value Up Move up one line. + \value Left Move left one character. + \value WordLeft Move left one word. + + \value End Move to the end of the document. + \value EndOfLine Move to the end of the current line. + \value EndOfWord Move to the end of the current word. + \value EndOfBlock Move to the end of the current block. + \value NextBlock Move to the beginning of the next block. + \value NextCharacter Move to the next character. + \value NextWord Move to the next word. + \value Down Move down one line. + \value Right Move right one character. + \value WordRight Move right one word. + + \value NextCell Move to the beginning of the next table cell inside the + current table. If the current cell is the last cell in the row, the + cursor will move to the first cell in the next row. + \value PreviousCell Move to the beginning of the previous table cell + inside the current table. If the current cell is the first cell in + the row, the cursor will move to the last cell in the previous row. + \value NextRow Move to the first new cell of the next row in the current + table. + \value PreviousRow Move to the last cell of the previous row in the + current table. + + \sa movePosition() +*/ + +/*! + \enum QTextCursor::MoveMode + + \value MoveAnchor Moves the anchor to the same position as the cursor itself. + \value KeepAnchor Keeps the anchor where it is. + + If the anchor() is kept where it is and the position() is moved, + the text in between will be selected. +*/ + +/*! + \enum QTextCursor::SelectionType + + This enum describes the types of selection that can be applied with the + select() function. + + \value Document Selects the entire document. + \value BlockUnderCursor Selects the block of text under the cursor. + \value LineUnderCursor Selects the line of text under the cursor. + \value WordUnderCursor Selects the word under the cursor. If the cursor + is not positioned within a string of selectable characters, no + text is selected. +*/ + +/*! + Constructs a null cursor. + */ +QTextCursor::QTextCursor() + : d(0) +{ +} + +/*! + Constructs a cursor pointing to the beginning of the \a document. + */ +QTextCursor::QTextCursor(QTextDocument *document) + : d(new QTextCursorPrivate(document->docHandle())) +{ +} + +/*! + Constructs a cursor pointing to the beginning of the \a frame. +*/ +QTextCursor::QTextCursor(QTextFrame *frame) + : d(new QTextCursorPrivate(frame->document()->docHandle())) +{ + d->adjusted_anchor = d->anchor = d->position = frame->firstPosition(); +} + + +/*! + Constructs a cursor pointing to the beginning of the \a block. +*/ +QTextCursor::QTextCursor(const QTextBlock &block) + : d(new QTextCursorPrivate(block.docHandle())) +{ + d->adjusted_anchor = d->anchor = d->position = block.position(); +} + + +/*! + \internal + */ +QTextCursor::QTextCursor(QTextDocumentPrivate *p, int pos) + : d(new QTextCursorPrivate(p)) +{ + d->adjusted_anchor = d->anchor = d->position = pos; + + d->setX(); +} + +/*! + \internal +*/ +QTextCursor::QTextCursor(QTextCursorPrivate *d) +{ + Q_ASSERT(d); + this->d = d; +} + +/*! + Constructs a new cursor that is a copy of \a cursor. + */ +QTextCursor::QTextCursor(const QTextCursor &cursor) +{ + d = cursor.d; +} + +/*! + Makes a copy of \a cursor and assigns it to this QTextCursor. Note + that QTextCursor is an \l{Implicitly Shared Classes}{implicitly + shared} class. + + */ +QTextCursor &QTextCursor::operator=(const QTextCursor &cursor) +{ + d = cursor.d; + return *this; +} + +/*! + Destroys the QTextCursor. + */ +QTextCursor::~QTextCursor() +{ +} + +/*! + Returns true if the cursor is null; otherwise returns false. A null + cursor is created by the default constructor. + */ +bool QTextCursor::isNull() const +{ + return !d || !d->priv; +} + +/*! + Moves the cursor to the absolute position in the document specified by + \a pos using a \c MoveMode specified by \a m. The cursor is positioned + between characters. + + \sa position() movePosition() anchor() +*/ +void QTextCursor::setPosition(int pos, MoveMode m) +{ + if (!d || !d->priv) + return; + + if (pos < 0 || pos >= d->priv->length()) { + qWarning("QTextCursor::setPosition: Position '%d' out of range", pos); + return; + } + + d->setPosition(pos); + if (m == MoveAnchor) { + d->anchor = pos; + d->adjusted_anchor = pos; + } else { // keep anchor + QTextCursor::MoveOperation op; + if (pos < d->anchor) + op = QTextCursor::Left; + else + op = QTextCursor::Right; + d->adjustCursor(op); + } + d->setX(); +} + +/*! + Returns the absolute position of the cursor within the document. + The cursor is positioned between characters. + + \sa setPosition() movePosition() anchor() positionInBlock() +*/ +int QTextCursor::position() const +{ + if (!d || !d->priv) + return -1; + return d->position; +} + +/*! + \since 4.7 + Returns the relative position of the cursor within the block. + The cursor is positioned between characters. + + This is equivalent to \c{ position() - block().position()}. + + \sa position() +*/ +int QTextCursor::positionInBlock() const +{ + if (!d || !d->priv) + return 0; + return d->position - d->block().position(); +} + +/*! + Returns the anchor position; this is the same as position() unless + there is a selection in which case position() marks one end of the + selection and anchor() marks the other end. Just like the cursor + position, the anchor position is between characters. + + \sa position() setPosition() movePosition() selectionStart() selectionEnd() +*/ +int QTextCursor::anchor() const +{ + if (!d || !d->priv) + return -1; + return d->anchor; +} + +/*! + \fn bool QTextCursor::movePosition(MoveOperation operation, MoveMode mode, int n) + + Moves the cursor by performing the given \a operation \a n times, using the specified + \a mode, and returns true if all operations were completed successfully; otherwise + returns false. + + For example, if this function is repeatedly used to seek to the end of the next + word, it will eventually fail when the end of the document is reached. + + By default, the move operation is performed once (\a n = 1). + + If \a mode is \c KeepAnchor, the cursor selects the text it moves + over. This is the same effect that the user achieves when they + hold down the Shift key and move the cursor with the cursor keys. + + \sa setVisualNavigation() +*/ +bool QTextCursor::movePosition(MoveOperation op, MoveMode mode, int n) +{ + if (!d || !d->priv) + return false; + switch (op) { + case Start: + case StartOfLine: + case End: + case EndOfLine: + n = 1; + break; + default: break; + } + + int previousPosition = d->position; + for (; n > 0; --n) { + if (!d->movePosition(op, mode)) + return false; + } + + if (d->visualNavigation && !d->block().isVisible()) { + QTextBlock b = d->block(); + if (previousPosition < d->position) { + while (!b.next().isVisible()) + b = b.next(); + d->setPosition(b.position() + b.length() - 1); + } else { + while (!b.previous().isVisible()) + b = b.previous(); + d->setPosition(b.position()); + } + if (mode == QTextCursor::MoveAnchor) + d->anchor = d->position; + while (d->movePosition(op, mode) + && !d->block().isVisible()) + ; + + } + return true; +} + +/*! + \since 4.4 + + Returns true if the cursor does visual navigation; otherwise + returns false. + + Visual navigation means skipping over hidden text pragraphs. The + default is false. + + \sa setVisualNavigation(), movePosition() + */ +bool QTextCursor::visualNavigation() const +{ + return d ? d->visualNavigation : false; +} + +/*! + \since 4.4 + + Sets visual navigation to \a b. + + Visual navigation means skipping over hidden text pragraphs. The + default is false. + + \sa visualNavigation(), movePosition() + */ +void QTextCursor::setVisualNavigation(bool b) +{ + if (d) + d->visualNavigation = b; +} + + +/*! + \since 4.7 + + Sets the visual x position for vertical cursor movements to \a x. + + The vertical movement x position is cleared automatically when the cursor moves horizontally, and kept + unchanged when the cursor moves vertically. The mechanism allows the cursor to move up and down on a + visually straight line with proportional fonts, and to gently "jump" over short lines. + + A value of -1 indicates no predefined x position. It will then be set automatically the next time the + cursor moves up or down. + + \sa verticalMovementX() + */ +void QTextCursor::setVerticalMovementX(int x) +{ + if (d) + d->x = x; +} + +/*! \since 4.7 + + Returns the visual x position for vertical cursor movements. + + A value of -1 indicates no predefined x position. It will then be set automatically the next time the + cursor moves up or down. + + \sa setVerticalMovementX() + */ +int QTextCursor::verticalMovementX() const +{ + return d ? d->x : -1; +} + +/*! + \since 4.7 + + Returns whether the cursor should keep its current position when text gets inserted at the position of the + cursor. + + The default is false; + + \sa setKeepPositionOnInsert() + */ +bool QTextCursor::keepPositionOnInsert() const +{ + return d ? d->keepPositionOnInsert : false; +} + +/*! + \since 4.7 + + Defines whether the cursor should keep its current position when text gets inserted at the current position of the + cursor. + + If \a b is true, the cursor keeps its current position when text gets inserted at the positing of the cursor. + If \a b is false, the cursor moves along with the inserted text. + + The default is false. + + Note that a cursor always moves when text is inserted before the current position of the cursor, and it + always keeps its position when text is inserted after the current position of the cursor. + + \sa keepPositionOnInsert() + */ +void QTextCursor::setKeepPositionOnInsert(bool b) +{ + if (d) + d->keepPositionOnInsert = b; +} + + + +/*! + Inserts \a text at the current position, using the current + character format. + + If there is a selection, the selection is deleted and replaced by + \a text, for example: + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 0 + This clears any existing selection, selects the word at the cursor + (i.e. from position() forward), and replaces the selection with + the phrase "Hello World". + + Any ASCII linefeed characters (\\n) in the inserted text are transformed + into unicode block separators, corresponding to insertBlock() calls. + + \sa charFormat() hasSelection() +*/ +void QTextCursor::insertText(const QString &text) +{ + QTextCharFormat fmt = charFormat(); + fmt.clearProperty(QTextFormat::ObjectType); + insertText(text, fmt); +} + +/*! + \fn void QTextCursor::insertText(const QString &text, const QTextCharFormat &format) + \overload + + Inserts \a text at the current position with the given \a format. +*/ +void QTextCursor::insertText(const QString &text, const QTextCharFormat &_format) +{ + if (!d || !d->priv) + return; + + Q_ASSERT(_format.isValid()); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + bool hasEditBlock = false; + + if (d->anchor != d->position) { + hasEditBlock = true; + d->priv->beginEditBlock(); + d->remove(); + } + + if (!text.isEmpty()) { + QTextFormatCollection *formats = d->priv->formatCollection(); + int formatIdx = formats->indexForFormat(format); + Q_ASSERT(formats->format(formatIdx).isCharFormat()); + + QTextBlockFormat blockFmt = blockFormat(); + + + int textStart = d->priv->text.length(); + int blockStart = 0; + d->priv->text += text; + int textEnd = d->priv->text.length(); + + for (int i = 0; i < text.length(); ++i) { + QChar ch = text.at(i); + + const int blockEnd = i; + + if (ch == QLatin1Char('\r') + && (i + 1) < text.length() + && text.at(i + 1) == QLatin1Char('\n')) { + ++i; + ch = text.at(i); + } + + if (ch == QLatin1Char('\n') + || ch == QChar::ParagraphSeparator + || ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame + || ch == QLatin1Char('\r')) { + + if (!hasEditBlock) { + hasEditBlock = true; + d->priv->beginEditBlock(); + } + + if (blockEnd > blockStart) + d->priv->insert(d->position, textStart + blockStart, blockEnd - blockStart, formatIdx); + + d->insertBlock(blockFmt, format); + blockStart = i + 1; + } + } + if (textStart + blockStart < textEnd) + d->priv->insert(d->position, textStart + blockStart, textEnd - textStart - blockStart, formatIdx); + } + if (hasEditBlock) + d->priv->endEditBlock(); + d->setX(); +} + +/*! + If there is no selected text, deletes the character \e at the + current cursor position; otherwise deletes the selected text. + + \sa deletePreviousChar() hasSelection() clearSelection() +*/ +void QTextCursor::deleteChar() +{ + if (!d || !d->priv) + return; + + if (d->position != d->anchor) { + removeSelectedText(); + return; + } + + if (!d->canDelete(d->position)) + return; + d->adjusted_anchor = d->anchor = + d->priv->nextCursorPosition(d->anchor, QTextLayout::SkipCharacters); + d->remove(); + d->setX(); +} + +/*! + If there is no selected text, deletes the character \e before the + current cursor position; otherwise deletes the selected text. + + \sa deleteChar() hasSelection() clearSelection() +*/ +void QTextCursor::deletePreviousChar() +{ + if (!d || !d->priv) + return; + + if (d->position != d->anchor) { + removeSelectedText(); + return; + } + + if (d->anchor < 1 || !d->canDelete(d->anchor-1)) + return; + d->anchor--; + + QTextDocumentPrivate::FragmentIterator fragIt = d->priv->find(d->anchor); + const QTextFragmentData * const frag = fragIt.value(); + int fpos = fragIt.position(); + QChar uc = d->priv->buffer().at(d->anchor - fpos + frag->stringPosition); + if (d->anchor > fpos && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) { + // second half of a surrogate, check if we have the first half as well, + // if yes delete both at once + uc = d->priv->buffer().at(d->anchor - 1 - fpos + frag->stringPosition); + if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) + --d->anchor; + } + + d->adjusted_anchor = d->anchor; + d->remove(); + d->setX(); +} + +/*! + Selects text in the document according to the given \a selection. +*/ +void QTextCursor::select(SelectionType selection) +{ + if (!d || !d->priv) + return; + + clearSelection(); + + const QTextBlock block = d->block(); + + switch (selection) { + case LineUnderCursor: + movePosition(StartOfLine); + movePosition(EndOfLine, KeepAnchor); + break; + case WordUnderCursor: + movePosition(StartOfWord); + movePosition(EndOfWord, KeepAnchor); + break; + case BlockUnderCursor: + if (block.length() == 1) // no content + break; + movePosition(StartOfBlock); + // also select the paragraph separator + if (movePosition(PreviousBlock)) { + movePosition(EndOfBlock); + movePosition(NextBlock, KeepAnchor); + } + movePosition(EndOfBlock, KeepAnchor); + break; + case Document: + movePosition(Start); + movePosition(End, KeepAnchor); + break; + } +} + +/*! + Returns true if the cursor contains a selection; otherwise returns false. +*/ +bool QTextCursor::hasSelection() const +{ + return !!d && d->position != d->anchor; +} + + +/*! + Returns true if the cursor contains a selection that is not simply a + range from selectionStart() to selectionEnd(); otherwise returns false. + + Complex selections are ones that span at least two cells in a table; + their extent is specified by selectedTableCells(). +*/ +bool QTextCursor::hasComplexSelection() const +{ + if (!d) + return false; + + return d->complexSelectionTable() != 0; +} + +/*! + If the selection spans over table cells, \a firstRow is populated + with the number of the first row in the selection, \a firstColumn + with the number of the first column in the selection, and \a + numRows and \a numColumns with the number of rows and columns in + the selection. If the selection does not span any table cells the + results are harmless but undefined. +*/ +void QTextCursor::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const +{ + *firstRow = -1; + *firstColumn = -1; + *numRows = -1; + *numColumns = -1; + + if (!d || d->position == d->anchor) + return; + + d->selectedTableCells(firstRow, numRows, firstColumn, numColumns); +} + + +/*! + Clears the current selection by setting the anchor to the cursor position. + + Note that it does \bold{not} delete the text of the selection. + + \sa removeSelectedText() hasSelection() +*/ +void QTextCursor::clearSelection() +{ + if (!d) + return; + d->adjusted_anchor = d->anchor = d->position; + d->currentCharFormat = -1; +} + +/*! + If there is a selection, its content is deleted; otherwise does + nothing. + + \sa hasSelection() +*/ +void QTextCursor::removeSelectedText() +{ + if (!d || !d->priv || d->position == d->anchor) + return; + + d->priv->beginEditBlock(); + d->remove(); + d->priv->endEditBlock(); + d->setX(); +} + +/*! + Returns the start of the selection or position() if the + cursor doesn't have a selection. + + \sa selectionEnd() position() anchor() +*/ +int QTextCursor::selectionStart() const +{ + if (!d || !d->priv) + return -1; + return qMin(d->position, d->adjusted_anchor); +} + +/*! + Returns the end of the selection or position() if the cursor + doesn't have a selection. + + \sa selectionStart() position() anchor() +*/ +int QTextCursor::selectionEnd() const +{ + if (!d || !d->priv) + return -1; + return qMax(d->position, d->adjusted_anchor); +} + +static void getText(QString &text, QTextDocumentPrivate *priv, const QString &docText, int pos, int end) +{ + while (pos < end) { + QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + + const int offsetInFragment = qMax(0, pos - fragIt.position()); + const int len = qMin(int(frag->size_array[0] - offsetInFragment), end - pos); + + text += QString(docText.constData() + frag->stringPosition + offsetInFragment, len); + pos += len; + } +} + +/*! + Returns the current selection's text (which may be empty). This + only returns the text, with no rich text formatting information. + If you want a document fragment (i.e. formatted rich text) use + selection() instead. + + \note If the selection obtained from an editor spans a line break, + the text will contain a Unicode U+2029 paragraph separator character + instead of a newline \c{\n} character. Use QString::replace() to + replace these characters with newlines. +*/ +QString QTextCursor::selectedText() const +{ + if (!d || !d->priv || d->position == d->anchor) + return QString(); + + const QString docText = d->priv->buffer(); + QString text; + + QTextTable *table = d->complexSelectionTable(); + if (table) { + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + getText(text, d->priv, docText, cell.firstPosition(), cell.lastPosition()); + } + } + } else { + getText(text, d->priv, docText, selectionStart(), selectionEnd()); + } + + return text; +} + +/*! + Returns the current selection (which may be empty) with all its + formatting information. If you just want the selected text (i.e. + plain text) use selectedText() instead. + + \note Unlike QTextDocumentFragment::toPlainText(), + selectedText() may include special unicode characters such as + QChar::ParagraphSeparator. + + \sa QTextDocumentFragment::toPlainText() +*/ +QTextDocumentFragment QTextCursor::selection() const +{ + return QTextDocumentFragment(*this); +} + +/*! + Returns the block that contains the cursor. +*/ +QTextBlock QTextCursor::block() const +{ + if (!d || !d->priv) + return QTextBlock(); + return d->block(); +} + +/*! + Returns the block format of the block the cursor is in. + + \sa setBlockFormat() charFormat() + */ +QTextBlockFormat QTextCursor::blockFormat() const +{ + if (!d || !d->priv) + return QTextBlockFormat(); + + return d->block().blockFormat(); +} + +/*! + Sets the block format of the current block (or all blocks that + are contained in the selection) to \a format. + + \sa blockFormat(), mergeBlockFormat() +*/ +void QTextCursor::setBlockFormat(const QTextBlockFormat &format) +{ + if (!d || !d->priv) + return; + + d->setBlockFormat(format, QTextDocumentPrivate::SetFormat); +} + +/*! + Modifies the block format of the current block (or all blocks that + are contained in the selection) with the block format specified by + \a modifier. + + \sa setBlockFormat(), blockFormat() +*/ +void QTextCursor::mergeBlockFormat(const QTextBlockFormat &modifier) +{ + if (!d || !d->priv) + return; + + d->setBlockFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns the block character format of the block the cursor is in. + + The block char format is the format used when inserting text at the + beginning of an empty block. + + \sa setBlockCharFormat() + */ +QTextCharFormat QTextCursor::blockCharFormat() const +{ + if (!d || !d->priv) + return QTextCharFormat(); + + return d->block().charFormat(); +} + +/*! + Sets the block char format of the current block (or all blocks that + are contained in the selection) to \a format. + + \sa blockCharFormat() +*/ +void QTextCursor::setBlockCharFormat(const QTextCharFormat &format) +{ + if (!d || !d->priv) + return; + + d->setBlockCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices); +} + +/*! + Modifies the block char format of the current block (or all blocks that + are contained in the selection) with the block format specified by + \a modifier. + + \sa setBlockCharFormat() +*/ +void QTextCursor::mergeBlockCharFormat(const QTextCharFormat &modifier) +{ + if (!d || !d->priv) + return; + + d->setBlockCharFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns the format of the character immediately before the cursor + position(). If the cursor is positioned at the beginning of a text + block that is not empty then the format of the character + immediately after the cursor is returned. + + \sa insertText(), blockFormat() + */ +QTextCharFormat QTextCursor::charFormat() const +{ + if (!d || !d->priv) + return QTextCharFormat(); + + int idx = d->currentCharFormat; + if (idx == -1) { + QTextBlock block = d->block(); + + int pos; + if (d->position == block.position() + && block.length() > 1) + pos = d->position; + else + pos = d->position - 1; + + if (pos == -1) { + idx = d->priv->blockCharFormatIndex(d->priv->blockMap().firstNode()); + } else { + Q_ASSERT(pos >= 0 && pos < d->priv->length()); + + QTextDocumentPrivate::FragmentIterator it = d->priv->find(pos); + Q_ASSERT(!it.atEnd()); + idx = it.value()->format; + } + } + + QTextCharFormat cfmt = d->priv->formatCollection()->charFormat(idx); + cfmt.clearProperty(QTextFormat::ObjectIndex); + + Q_ASSERT(cfmt.isValid()); + return cfmt; +} + +/*! + Sets the cursor's current character format to the given \a + format. If the cursor has a selection, the given \a format is + applied to the current selection. + + \sa hasSelection(), mergeCharFormat() +*/ +void QTextCursor::setCharFormat(const QTextCharFormat &format) +{ + if (!d || !d->priv) + return; + if (d->position == d->anchor) { + d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format); + return; + } + d->setCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices); +} + +/*! + Merges the cursor's current character format with the properties + described by format \a modifier. If the cursor has a selection, + this function applies all the properties set in \a modifier to all + the character formats that are part of the selection. + + \sa hasSelection(), setCharFormat() +*/ +void QTextCursor::mergeCharFormat(const QTextCharFormat &modifier) +{ + if (!d || !d->priv) + return; + if (d->position == d->anchor) { + QTextCharFormat format = charFormat(); + format.merge(modifier); + d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format); + return; + } + + d->setCharFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns true if the cursor is at the start of a block; otherwise + returns false. + + \sa atBlockEnd(), atStart() +*/ +bool QTextCursor::atBlockStart() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->block().position(); +} + +/*! + Returns true if the cursor is at the end of a block; otherwise + returns false. + + \sa atBlockStart(), atEnd() +*/ +bool QTextCursor::atBlockEnd() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->block().position() + d->block().length() - 1; +} + +/*! + Returns true if the cursor is at the start of the document; + otherwise returns false. + + \sa atBlockStart(), atEnd() +*/ +bool QTextCursor::atStart() const +{ + if (!d || !d->priv) + return false; + + return d->position == 0; +} + +/*! + \since 4.6 + + Returns true if the cursor is at the end of the document; + otherwise returns false. + + \sa atStart(), atBlockEnd() +*/ +bool QTextCursor::atEnd() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->priv->length() - 1; +} + +/*! + Inserts a new empty block at the cursor position() with the + current blockFormat() and charFormat(). + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock() +{ + insertBlock(blockFormat()); +} + +/*! + \overload + + Inserts a new empty block at the cursor position() with block + format \a format and the current charFormat() as block char format. + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock(const QTextBlockFormat &format) +{ + QTextCharFormat charFmt = charFormat(); + charFmt.clearProperty(QTextFormat::ObjectType); + insertBlock(format, charFmt); +} + +/*! + \fn void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat) + \overload + + Inserts a new empty block at the cursor position() with block + format \a format and \a charFormat as block char format. + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &_charFormat) +{ + if (!d || !d->priv) + return; + + QTextCharFormat charFormat = _charFormat; + charFormat.clearProperty(QTextFormat::ObjectIndex); + + d->priv->beginEditBlock(); + d->remove(); + d->insertBlock(format, charFormat); + d->priv->endEditBlock(); + d->setX(); +} + +/*! + Inserts a new block at the current position and makes it the first + list item of a newly created list with the given \a format. Returns + the created list. + + \sa currentList() createList() insertBlock() + */ +QTextList *QTextCursor::insertList(const QTextListFormat &format) +{ + insertBlock(); + return createList(format); +} + +/*! + \overload + + Inserts a new block at the current position and makes it the first + list item of a newly created list with the given \a style. Returns + the created list. + + \sa currentList(), createList(), insertBlock() + */ +QTextList *QTextCursor::insertList(QTextListFormat::Style style) +{ + insertBlock(); + return createList(style); +} + +/*! + Creates and returns a new list with the given \a format, and makes the + current paragraph the cursor is in the first list item. + + \sa insertList() currentList() + */ +QTextList *QTextCursor::createList(const QTextListFormat &format) +{ + if (!d || !d->priv) + return 0; + + QTextList *list = static_cast<QTextList *>(d->priv->createObject(format)); + QTextBlockFormat modifier; + modifier.setObjectIndex(list->objectIndex()); + mergeBlockFormat(modifier); + return list; +} + +/*! + \overload + + Creates and returns a new list with the given \a style, making the + cursor's current paragraph the first list item. + + The style to be used is defined by the QTextListFormat::Style enum. + + \sa insertList() currentList() + */ +QTextList *QTextCursor::createList(QTextListFormat::Style style) +{ + QTextListFormat fmt; + fmt.setStyle(style); + return createList(fmt); +} + +/*! + Returns the current list if the cursor position() is inside a + block that is part of a list; otherwise returns 0. + + \sa insertList() createList() + */ +QTextList *QTextCursor::currentList() const +{ + if (!d || !d->priv) + return 0; + + QTextBlockFormat b = blockFormat(); + QTextObject *o = d->priv->objectForFormat(b); + return qobject_cast<QTextList *>(o); +} + +/*! + \fn QTextTable *QTextCursor::insertTable(int rows, int columns) + + \overload + + Creates a new table with the given number of \a rows and \a columns, + inserts it at the current cursor position() in the document, and returns + the table object. The cursor is moved to the beginning of the first cell. + + There must be at least one row and one column in the table. + + \sa currentTable() + */ +QTextTable *QTextCursor::insertTable(int rows, int cols) +{ + return insertTable(rows, cols, QTextTableFormat()); +} + +/*! + \fn QTextTable *QTextCursor::insertTable(int rows, int columns, const QTextTableFormat &format) + + Creates a new table with the given number of \a rows and \a columns + in the specified \a format, inserts it at the current cursor position() + in the document, and returns the table object. The cursor is moved to + the beginning of the first cell. + + There must be at least one row and one column in the table. + + \sa currentTable() +*/ +QTextTable *QTextCursor::insertTable(int rows, int cols, const QTextTableFormat &format) +{ + if(!d || !d->priv || rows == 0 || cols == 0) + return 0; + + int pos = d->position; + QTextTable *t = QTextTablePrivate::createTable(d->priv, d->position, rows, cols, format); + d->setPosition(pos+1); + // ##### what should we do if we have a selection? + d->anchor = d->position; + d->adjusted_anchor = d->anchor; + return t; +} + +/*! + Returns a pointer to the current table if the cursor position() + is inside a block that is part of a table; otherwise returns 0. + + \sa insertTable() +*/ +QTextTable *QTextCursor::currentTable() const +{ + if(!d || !d->priv) + return 0; + + QTextFrame *frame = d->priv->frameAt(d->position); + while (frame) { + QTextTable *table = qobject_cast<QTextTable *>(frame); + if (table) + return table; + frame = frame->parentFrame(); + } + return 0; +} + +/*! + Inserts a frame with the given \a format at the current cursor position(), + moves the cursor position() inside the frame, and returns the frame. + + If the cursor holds a selection, the whole selection is moved inside the + frame. + + \sa hasSelection() +*/ +QTextFrame *QTextCursor::insertFrame(const QTextFrameFormat &format) +{ + if (!d || !d->priv) + return 0; + + return d->priv->insertFrame(selectionStart(), selectionEnd(), format); +} + +/*! + Returns a pointer to the current frame. Returns 0 if the cursor is invalid. + + \sa insertFrame() +*/ +QTextFrame *QTextCursor::currentFrame() const +{ + if(!d || !d->priv) + return 0; + + return d->priv->frameAt(d->position); +} + + +/*! + Inserts the text \a fragment at the current position(). +*/ +void QTextCursor::insertFragment(const QTextDocumentFragment &fragment) +{ + if (!d || !d->priv || fragment.isEmpty()) + return; + + d->priv->beginEditBlock(); + d->remove(); + fragment.d->insert(*this); + d->priv->endEditBlock(); + + if (fragment.d && fragment.d->doc) + d->priv->mergeCachedResources(fragment.d->doc->docHandle()); +} + +/*! + \since 4.2 + Inserts the text \a html at the current position(). The text is interpreted as + HTML. + + \note When using this function with a style sheet, the style sheet will + only apply to the current block in the document. In order to apply a style + sheet throughout a document, use QTextDocument::setDefaultStyleSheet() + instead. +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +void QTextCursor::insertHtml(const QString &html) +{ + if (!d || !d->priv) + return; + QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html, d->priv->document()); + insertFragment(fragment); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \overload + \since 4.2 + + Inserts the image defined by the given \a format at the cursor's current position + with the specified \a alignment. + + \sa position() +*/ +void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment) +{ + if (!d || !d->priv) + return; + + QTextFrameFormat ffmt; + ffmt.setPosition(alignment); + QTextObject *obj = d->priv->createObject(ffmt); + + QTextImageFormat fmt = format; + fmt.setObjectIndex(obj->objectIndex()); + + d->priv->beginEditBlock(); + d->remove(); + const int idx = d->priv->formatCollection()->indexForFormat(fmt); + d->priv->insert(d->position, QString(QChar(QChar::ObjectReplacementCharacter)), idx); + d->priv->endEditBlock(); +} + +/*! + Inserts the image defined by \a format at the current position(). +*/ +void QTextCursor::insertImage(const QTextImageFormat &format) +{ + insertText(QString(QChar::ObjectReplacementCharacter), format); +} + +/*! + \overload + + Convenience method for inserting the image with the given \a name at the + current position(). + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 1 +*/ +void QTextCursor::insertImage(const QString &name) +{ + QTextImageFormat format; + format.setName(name); + insertImage(format); +} + +/*! + \since 4.5 + \overload + + Convenience function for inserting the given \a image with an optional + \a name at the current position(). +*/ +void QTextCursor::insertImage(const QImage &image, const QString &name) +{ + if (image.isNull()) { + qWarning("QTextCursor::insertImage: attempt to add an invalid image"); + return; + } + QString imageName = name; + if (name.isEmpty()) + imageName = QString::number(image.serialNumber()); + d->priv->document()->addResource(QTextDocument::ImageResource, QUrl(imageName), image); + QTextImageFormat format; + format.setName(imageName); + insertImage(format); +} + +/*! + \fn bool QTextCursor::operator!=(const QTextCursor &other) const + + Returns true if the \a other cursor is at a different position in + the document as this cursor; otherwise returns false. +*/ +bool QTextCursor::operator!=(const QTextCursor &rhs) const +{ + return !operator==(rhs); +} + +/*! + \fn bool QTextCursor::operator<(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned later in the + document than this cursor; otherwise returns false. +*/ +bool QTextCursor::operator<(const QTextCursor &rhs) const +{ + if (!d) + return !!rhs.d; + + if (!rhs.d) + return false; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<", "cannot compare cursors attached to different documents"); + + return d->position < rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator<=(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned later or at the + same position in the document as this cursor; otherwise returns + false. +*/ +bool QTextCursor::operator<=(const QTextCursor &rhs) const +{ + if (!d) + return true; + + if (!rhs.d) + return false; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<=", "cannot compare cursors attached to different documents"); + + return d->position <= rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator==(const QTextCursor &other) const + + Returns true if the \a other cursor is at the same position in the + document as this cursor; otherwise returns false. +*/ +bool QTextCursor::operator==(const QTextCursor &rhs) const +{ + if (!d) + return !rhs.d; + + if (!rhs.d) + return false; + + return d->position == rhs.d->position && d->priv == rhs.d->priv; +} + +/*! + \fn bool QTextCursor::operator>=(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned earlier or at the + same position in the document as this cursor; otherwise returns + false. +*/ +bool QTextCursor::operator>=(const QTextCursor &rhs) const +{ + if (!d) + return false; + + if (!rhs.d) + return true; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>=", "cannot compare cursors attached to different documents"); + + return d->position >= rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator>(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned earlier in the + document than this cursor; otherwise returns false. +*/ +bool QTextCursor::operator>(const QTextCursor &rhs) const +{ + if (!d) + return false; + + if (!rhs.d) + return true; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>", "cannot compare cursors attached to different documents"); + + return d->position > rhs.d->position; +} + +/*! + Indicates the start of a block of editing operations on the + document that should appear as a single operation from an + undo/redo point of view. + + For example: + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 2 + + The call to undo() will cause both insertions to be undone, + causing both "World" and "Hello" to be removed. + + It is possible to nest calls to beginEditBlock and endEditBlock. The + top-most pair will determine the scope of the undo/redo operation. + + \sa endEditBlock() + */ +void QTextCursor::beginEditBlock() +{ + if (!d || !d->priv) + return; + + if (d->priv->editBlock == 0) // we are the initial edit block, store current cursor position for undo + d->priv->editBlockCursorPosition = d->position; + + d->priv->beginEditBlock(); +} + +/*! + Like beginEditBlock() indicates the start of a block of editing operations + that should appear as a single operation for undo/redo. However unlike + beginEditBlock() it does not start a new block but reverses the previous call to + endEditBlock() and therefore makes following operations part of the previous edit block created. + + For example: + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 3 + + The call to undo() will cause all three insertions to be undone. + + \sa beginEditBlock(), endEditBlock() + */ +void QTextCursor::joinPreviousEditBlock() +{ + if (!d || !d->priv) + return; + + d->priv->joinPreviousEditBlock(); +} + +/*! + Indicates the end of a block of editing operations on the document + that should appear as a single operation from an undo/redo point + of view. + + \sa beginEditBlock() + */ + +void QTextCursor::endEditBlock() +{ + if (!d || !d->priv) + return; + + d->priv->endEditBlock(); +} + +/*! + Returns true if this cursor and \a other are copies of each other, i.e. + one of them was created as a copy of the other and neither has moved since. + This is much stricter than equality. + + \sa operator=() operator==() +*/ +bool QTextCursor::isCopyOf(const QTextCursor &other) const +{ + return d == other.d; +} + +/*! + \since 4.2 + Returns the number of the block the cursor is in, or 0 if the cursor is invalid. + + Note that this function only makes sense in documents without complex objects such + as tables or frames. +*/ +int QTextCursor::blockNumber() const +{ + if (!d || !d->priv) + return 0; + + return d->block().blockNumber(); +} + + +/*! + \since 4.2 + Returns the position of the cursor within its containing line. + + Note that this is the column number relative to a wrapped line, + not relative to the block (i.e. the paragraph). + + You probably want to call positionInBlock() instead. + + \sa positionInBlock() +*/ +int QTextCursor::columnNumber() const +{ + if (!d || !d->priv) + return 0; + + QTextBlock block = d->block(); + if (!block.isValid()) + return 0; + + const QTextLayout *layout = d->blockLayout(block); + + const int relativePos = d->position - block.position(); + + if (layout->lineCount() == 0) + return relativePos; + + QTextLine line = layout->lineForTextPosition(relativePos); + if (!line.isValid()) + return 0; + return relativePos - line.textStart(); +} + +/*! + \since 4.5 + Returns the document this cursor is associated with. +*/ +QTextDocument *QTextCursor::document() const +{ + if (d->priv) + return d->priv->document(); + return 0; // document went away +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextcursor.h b/src/gui/text/qtextcursor.h new file mode 100644 index 0000000000..4eaeb41ee9 --- /dev/null +++ b/src/gui/text/qtextcursor.h @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCURSOR_H +#define QTEXTCURSOR_H + +#include <QtCore/qstring.h> +#include <QtCore/qshareddata.h> +#include <QtGui/qtextformat.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocument; +class QTextCursorPrivate; +class QTextDocumentFragment; +class QTextCharFormat; +class QTextBlockFormat; +class QTextListFormat; +class QTextTableFormat; +class QTextFrameFormat; +class QTextImageFormat; +class QTextDocumentPrivate; +class QTextList; +class QTextTable; +class QTextFrame; +class QTextBlock; + +class Q_GUI_EXPORT QTextCursor +{ +public: + QTextCursor(); + explicit QTextCursor(QTextDocument *document); + QTextCursor(QTextDocumentPrivate *p, int pos); + explicit QTextCursor(QTextFrame *frame); + explicit QTextCursor(const QTextBlock &block); + explicit QTextCursor(QTextCursorPrivate *d); + QTextCursor(const QTextCursor &cursor); + QTextCursor &operator=(const QTextCursor &other); + ~QTextCursor(); + + bool isNull() const; + + enum MoveMode { + MoveAnchor, + KeepAnchor + }; + + void setPosition(int pos, MoveMode mode = MoveAnchor); + int position() const; + int positionInBlock() const; + + int anchor() const; + + void insertText(const QString &text); + void insertText(const QString &text, const QTextCharFormat &format); + + enum MoveOperation { + NoMove, + + Start, + Up, + StartOfLine, + StartOfBlock, + StartOfWord, + PreviousBlock, + PreviousCharacter, + PreviousWord, + Left, + WordLeft, + + End, + Down, + EndOfLine, + EndOfWord, + EndOfBlock, + NextBlock, + NextCharacter, + NextWord, + Right, + WordRight, + + NextCell, + PreviousCell, + NextRow, + PreviousRow + }; + + bool movePosition(MoveOperation op, MoveMode = MoveAnchor, int n = 1); + + bool visualNavigation() const; + void setVisualNavigation(bool b); + + void setVerticalMovementX(int x); + int verticalMovementX() const; + + void setKeepPositionOnInsert(bool b); + bool keepPositionOnInsert() const; + + void deleteChar(); + void deletePreviousChar(); + + enum SelectionType { + WordUnderCursor, + LineUnderCursor, + BlockUnderCursor, + Document + }; + void select(SelectionType selection); + + bool hasSelection() const; + bool hasComplexSelection() const; + void removeSelectedText(); + void clearSelection(); + int selectionStart() const; + int selectionEnd() const; + + QString selectedText() const; + QTextDocumentFragment selection() const; + void selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const; + + QTextBlock block() const; + + QTextCharFormat charFormat() const; + void setCharFormat(const QTextCharFormat &format); + void mergeCharFormat(const QTextCharFormat &modifier); + + QTextBlockFormat blockFormat() const; + void setBlockFormat(const QTextBlockFormat &format); + void mergeBlockFormat(const QTextBlockFormat &modifier); + + QTextCharFormat blockCharFormat() const; + void setBlockCharFormat(const QTextCharFormat &format); + void mergeBlockCharFormat(const QTextCharFormat &modifier); + + bool atBlockStart() const; + bool atBlockEnd() const; + bool atStart() const; + bool atEnd() const; + + void insertBlock(); + void insertBlock(const QTextBlockFormat &format); + void insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat); + + QTextList *insertList(const QTextListFormat &format); + QTextList *insertList(QTextListFormat::Style style); + + QTextList *createList(const QTextListFormat &format); + QTextList *createList(QTextListFormat::Style style); + QTextList *currentList() const; + + QTextTable *insertTable(int rows, int cols, const QTextTableFormat &format); + QTextTable *insertTable(int rows, int cols); + QTextTable *currentTable() const; + + QTextFrame *insertFrame(const QTextFrameFormat &format); + QTextFrame *currentFrame() const; + + void insertFragment(const QTextDocumentFragment &fragment); + +#ifndef QT_NO_TEXTHTMLPARSER + void insertHtml(const QString &html); +#endif // QT_NO_TEXTHTMLPARSER + + void insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment); + void insertImage(const QTextImageFormat &format); + void insertImage(const QString &name); + void insertImage(const QImage &image, const QString &name = QString()); + + void beginEditBlock(); + void joinPreviousEditBlock(); + void endEditBlock(); + + bool operator!=(const QTextCursor &rhs) const; + bool operator<(const QTextCursor &rhs) const; + bool operator<=(const QTextCursor &rhs) const; + bool operator==(const QTextCursor &rhs) const; + bool operator>=(const QTextCursor &rhs) const; + bool operator>(const QTextCursor &rhs) const; + + bool isCopyOf(const QTextCursor &other) const; + + int blockNumber() const; + int columnNumber() const; + + QTextDocument *document() const; + +private: + QSharedDataPointer<QTextCursorPrivate> d; + friend class QTextDocumentFragmentPrivate; + friend class QTextCopyHelper; + friend class QTextControlPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTCURSOR_H diff --git a/src/gui/text/qtextcursor_p.h b/src/gui/text/qtextcursor_p.h new file mode 100644 index 0000000000..566495e1a3 --- /dev/null +++ b/src/gui/text/qtextcursor_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCURSOR_P_H +#define QTEXTCURSOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtextcursor.h" +#include "qtextdocument.h" +#include "qtextdocument_p.h" +#include <private/qtextformat_p.h> +#include "qtextobject.h" + +QT_BEGIN_NAMESPACE + +class QTextCursorPrivate : public QSharedData +{ +public: + QTextCursorPrivate(QTextDocumentPrivate *p); + QTextCursorPrivate(const QTextCursorPrivate &rhs); + ~QTextCursorPrivate(); + + enum AdjustResult { CursorMoved, CursorUnchanged }; + AdjustResult adjustPosition(int positionOfChange, int charsAddedOrRemoved, QTextUndoCommand::Operation op); + + void adjustCursor(QTextCursor::MoveOperation m); + + void remove(); + void clearCells(QTextTable *table, int startRow, int startCol, int numRows, int numCols, QTextUndoCommand::Operation op); + inline bool setPosition(int newPosition) { + Q_ASSERT(newPosition >= 0 && newPosition < priv->length()); + bool moved = position != newPosition; + if (moved) { + position = newPosition; + currentCharFormat = -1; + } + return moved; + } + void setX(); + bool canDelete(int pos) const; + + void insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat); + bool movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + inline QTextBlock block() const + { return QTextBlock(priv, priv->blockMap().findNode(position)); } + inline QTextBlockFormat blockFormat() const + { return block().blockFormat(); } + + QTextLayout *blockLayout(QTextBlock &block) const; + + QTextTable *complexSelectionTable() const; + void selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const; + + void setBlockCharFormat(const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + void setBlockFormat(const QTextBlockFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + void setCharFormat(const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + + void aboutToRemoveCell(int from, int to); + + QTextDocumentPrivate *priv; + qreal x; + int position; + int anchor; + int adjusted_anchor; + int currentCharFormat; + uint visualNavigation : 1; + uint keepPositionOnInsert : 1; + uint changed : 1; +}; + +QT_END_NAMESPACE + +#endif // QTEXTCURSOR_P_H diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp new file mode 100644 index 0000000000..6dbd755d93 --- /dev/null +++ b/src/gui/text/qtextdocument.cpp @@ -0,0 +1,3044 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocument.h" +#include <qtextformat.h> +#include "qtextdocumentlayout_p.h" +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtexttable.h" +#include "qtextlist.h" +#include <qdebug.h> +#include <qregexp.h> +#include <qvarlengtharray.h> +#include <qtextcodec.h> +#include <qthread.h> +#include "qtexthtmlparser_p.h" +#include "qpainter.h" +#include "qprinter.h" +#include "qtextedit.h" +#include <qfile.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qapplication.h> +#include "qtextcontrol_p.h" +#include "qfont_p.h" +#include "private/qtextedit_p.h" +#include "private/qdataurl_p.h" + +#include "qtextdocument_p.h" +#include <private/qprinter_p.h> +#include <private/qabstracttextdocumentlayout_p.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n); + +/*! + Returns true if the string \a text is likely to be rich text; + otherwise returns false. + + This function uses a fast and therefore simple heuristic. It + mainly checks whether there is something that looks like a tag + before the first line break. Although the result may be correct + for common cases, there is no guarantee. + + This function is defined in the \c <QTextDocument> header file. +*/ +bool Qt::mightBeRichText(const QString& text) +{ + if (text.isEmpty()) + return false; + int start = 0; + + while (start < text.length() && text.at(start).isSpace()) + ++start; + + // skip a leading <?xml ... ?> as for example with xhtml + if (text.mid(start, 5) == QLatin1String("<?xml")) { + while (start < text.length()) { + if (text.at(start) == QLatin1Char('?') + && start + 2 < text.length() + && text.at(start + 1) == QLatin1Char('>')) { + start += 2; + break; + } + ++start; + } + + while (start < text.length() && text.at(start).isSpace()) + ++start; + } + + if (text.mid(start, 5).toLower() == QLatin1String("<!doc")) + return true; + int open = start; + while (open < text.length() && text.at(open) != QLatin1Char('<') + && text.at(open) != QLatin1Char('\n')) { + if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;")) + return true; // support desperate attempt of user to see <...> + ++open; + } + if (open < text.length() && text.at(open) == QLatin1Char('<')) { + const int close = text.indexOf(QLatin1Char('>'), open); + if (close > -1) { + QString tag; + for (int i = open+1; i < close; ++i) { + if (text[i].isDigit() || text[i].isLetter()) + tag += text[i]; + else if (!tag.isEmpty() && text[i].isSpace()) + break; + else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close) + break; + else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!'))) + return false; // that's not a tag + } +#ifndef QT_NO_TEXTHTMLPARSER + return QTextHtmlParser::lookupElement(tag.toLower()) != -1; +#else + return false; +#endif // QT_NO_TEXTHTMLPARSER + } + } + return false; +} + +/*! + Converts the plain text string \a plain to a HTML string with + HTML metacharacters \c{<}, \c{>}, \c{&}, and \c{"} replaced by HTML + entities. + + Example: + + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0 + + This function is defined in the \c <QTextDocument> header file. + + \sa convertFromPlainText(), mightBeRichText() +*/ +QString Qt::escape(const QString& plain) +{ + QString rich; + rich.reserve(int(plain.length() * 1.1)); + for (int i = 0; i < plain.length(); ++i) { + if (plain.at(i) == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain.at(i) == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain.at(i) == QLatin1Char('&')) + rich += QLatin1String("&"); + else if (plain.at(i) == QLatin1Char('"')) + rich += QLatin1String("""); + else + rich += plain.at(i); + } + return rich; +} + +/*! + \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode) + + Converts the plain text string \a plain to an HTML-formatted + paragraph while preserving most of its look. + + \a mode defines how whitespace is handled. + + This function is defined in the \c <QTextDocument> header file. + + \sa escape(), mightBeRichText() +*/ +QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) +{ + int col = 0; + QString rich; + rich += QLatin1String("<p>"); + for (int i = 0; i < plain.length(); ++i) { + if (plain[i] == QLatin1Char('\n')){ + int c = 1; + while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) { + i++; + c++; + } + if (c == 1) + rich += QLatin1String("<br>\n"); + else { + rich += QLatin1String("</p>\n"); + while (--c > 1) + rich += QLatin1String("<br>\n"); + rich += QLatin1String("<p>"); + } + col = 0; + } else { + if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){ + rich += QChar(0x00a0U); + ++col; + while (col % 8) { + rich += QChar(0x00a0U); + ++col; + } + } + else if (mode == Qt::WhiteSpacePre && plain[i].isSpace()) + rich += QChar(0x00a0U); + else if (plain[i] == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain[i] == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain[i] == QLatin1Char('&')) + rich += QLatin1String("&"); + else + rich += plain[i]; + ++col; + } + } + if (col != 0) + rich += QLatin1String("</p>"); + return rich; +} + +#ifndef QT_NO_TEXTCODEC +/*! + \internal + + This function is defined in the \c <QTextDocument> header file. +*/ +QTextCodec *Qt::codecForHtml(const QByteArray &ba) +{ + return QTextCodec::codecForHtml(ba); +} +#endif + +/*! + \class QTextDocument + \reentrant + + \brief The QTextDocument class holds formatted text that can be + viewed and edited using a QTextEdit. + + \ingroup richtext-processing + + + QTextDocument is a container for structured rich text documents, providing + support for styled text and various types of document elements, such as + lists, tables, frames, and images. + They can be created for use in a QTextEdit, or used independently. + + Each document element is described by an associated format object. Each + format object is treated as a unique object by QTextDocuments, and can be + passed to objectForFormat() to obtain the document element that it is + applied to. + + A QTextDocument can be edited programmatically using a QTextCursor, and + its contents can be examined by traversing the document structure. The + entire document structure is stored as a hierarchy of document elements + beneath the root frame, found with the rootFrame() function. Alternatively, + if you just want to iterate over the textual contents of the document you + can use begin(), end(), and findBlock() to retrieve text blocks that you + can examine and iterate over. + + The layout of a document is determined by the documentLayout(); + you can create your own QAbstractTextDocumentLayout subclass and + set it using setDocumentLayout() if you want to use your own + layout logic. The document's title and other meta-information can be + obtained by calling the metaInformation() function. For documents that + are exposed to users through the QTextEdit class, the document title + is also available via the QTextEdit::documentTitle() function. + + The toPlainText() and toHtml() convenience functions allow you to retrieve the + contents of the document as plain text and HTML. + The document's text can be searched using the find() functions. + + Undo/redo of operations performed on the document can be controlled using + the setUndoRedoEnabled() function. The undo/redo system can be controlled + by an editor widget through the undo() and redo() slots; the document also + provides contentsChanged(), undoAvailable(), and redoAvailable() signals + that inform connected editor widgets about the state of the undo/redo + system. The following are the undo/redo operations of a QTextDocument: + + \list + \o Insertion or removal of characters. A sequence of insertions or removals + within the same text block are regarded as a single undo/redo operation. + \o Insertion or removal of text blocks. Sequences of insertion or removals + in a single operation (e.g., by selecting and then deleting text) are + regarded as a single undo/redo operation. + \o Text character format changes. + \o Text block format changes. + \o Text block group format changes. + \endlist + + \sa QTextCursor, QTextEdit, \link richtext.html Rich Text Processing\endlink , {Text Object Example} +*/ + +/*! + \property QTextDocument::defaultFont + \brief the default font used to display the document's text +*/ + +/*! + \property QTextDocument::defaultTextOption + \brief the default text option will be set on all \l{QTextLayout}s in the document. + + When \l{QTextBlock}s are created, the defaultTextOption is set on their + QTextLayout. This allows setting global properties for the document such as the + default word wrap mode. + */ + +/*! + Constructs an empty QTextDocument with the given \a parent. +*/ +QTextDocument::QTextDocument(QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Constructs a QTextDocument containing the plain (unformatted) \a text + specified, and with the given \a parent. +*/ +QTextDocument::QTextDocument(const QString &text, QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); + QTextCursor(this).insertText(text); +} + +/*! + \internal +*/ +QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Destroys the document. +*/ +QTextDocument::~QTextDocument() +{ +} + + +/*! + Creates a new QTextDocument that is a copy of this text document. \a + parent is the parent of the returned text document. +*/ +QTextDocument *QTextDocument::clone(QObject *parent) const +{ + Q_D(const QTextDocument); + QTextDocument *doc = new QTextDocument(parent); + QTextCursor(doc).insertFragment(QTextDocumentFragment(this)); + doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat()); + QTextDocumentPrivate *priv = doc->d_func(); + priv->title = d->title; + priv->url = d->url; + priv->pageSize = d->pageSize; + priv->indentWidth = d->indentWidth; + priv->defaultTextOption = d->defaultTextOption; + priv->setDefaultFont(d->defaultFont()); + priv->resources = d->resources; + priv->cachedResources.clear(); +#ifndef QT_NO_CSSPARSER + priv->defaultStyleSheet = d->defaultStyleSheet; + priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet; +#endif + return doc; +} + +/*! + Returns true if the document is empty; otherwise returns false. +*/ +bool QTextDocument::isEmpty() const +{ + Q_D(const QTextDocument); + /* because if we're empty we still have one single paragraph as + * one single fragment */ + return d->length() <= 1; +} + +/*! + Clears the document. +*/ +void QTextDocument::clear() +{ + Q_D(QTextDocument); + d->clear(); + d->resources.clear(); +} + +/*! + \since 4.2 + + Undoes the last editing operation on the document if undo is + available. The provided \a cursor is positioned at the end of the + location where the edition operation was undone. + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undoAvailable(), isUndoRedoEnabled() +*/ +void QTextDocument::undo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(true); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! + \since 4.2 + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. + + The provided \a cursor is positioned at the end of the location where + the edition operation was redone. +*/ +void QTextDocument::redo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(false); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! \enum QTextDocument::Stacks + + \value UndoStack The undo stack. + \value RedoStack The redo stack. + \value UndoAndRedoStacks Both the undo and redo stacks. +*/ + +/*! + \since 4.7 + Clears the stacks specified by \a stacksToClear. + + This method clears any commands on the undo stack, the redo stack, + or both (the default). If commands are cleared, the appropriate + signals are emitted, QTextDocument::undoAvailable() or + QTextDocument::redoAvailable(). + + \sa QTextDocument::undoAvailable() QTextDocument::redoAvailable() +*/ +void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear) +{ + Q_D(QTextDocument); + d->clearUndoRedoStacks(stacksToClear, true); +} + +/*! + \overload + +*/ +void QTextDocument::undo() +{ + Q_D(QTextDocument); + d->undoRedo(true); +} + +/*! + \overload + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. +*/ +void QTextDocument::redo() +{ + Q_D(QTextDocument); + d->undoRedo(false); +} + +/*! + \internal + + Appends a custom undo \a item to the undo stack. +*/ +void QTextDocument::appendUndoItem(QAbstractUndoItem *item) +{ + Q_D(QTextDocument); + d->appendUndoItem(item); +} + +/*! + \property QTextDocument::undoRedoEnabled + \brief whether undo/redo are enabled for this document + + This defaults to true. If disabled, the undo stack is cleared and + no items will be added to it. +*/ +void QTextDocument::setUndoRedoEnabled(bool enable) +{ + Q_D(QTextDocument); + d->enableUndoRedo(enable); +} + +bool QTextDocument::isUndoRedoEnabled() const +{ + Q_D(const QTextDocument); + return d->isUndoRedoEnabled(); +} + +/*! + \property QTextDocument::maximumBlockCount + \since 4.2 + \brief Specifies the limit for blocks in the document. + + Specifies the maximum number of blocks the document may have. If there are + more blocks in the document that specified with this property blocks are removed + from the beginning of the document. + + A negative or zero value specifies that the document may contain an unlimited + amount of blocks. + + The default value is 0. + + Note that setting this property will apply the limit immediately to the document + contents. + + Setting this property also disables the undo redo history. + + This property is undefined in documents with tables or frames. +*/ +int QTextDocument::maximumBlockCount() const +{ + Q_D(const QTextDocument); + return d->maximumBlockCount; +} + +void QTextDocument::setMaximumBlockCount(int maximum) +{ + Q_D(QTextDocument); + d->maximumBlockCount = maximum; + d->ensureMaximumBlockCount(); + setUndoRedoEnabled(false); +} + +/*! + \since 4.3 + + The default text option is used on all QTextLayout objects in the document. + This allows setting global properties for the document such as the default + word wrap mode. +*/ +QTextOption QTextDocument::defaultTextOption() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption; +} + +/*! + \since 4.3 + + Sets the default text option. +*/ +void QTextDocument::setDefaultTextOption(const QTextOption &option) +{ + Q_D(QTextDocument); + d->defaultTextOption = option; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + \fn void QTextDocument::markContentsDirty(int position, int length) + + Marks the contents specified by the given \a position and \a length + as "dirty", informing the document that it needs to be laid out + again. +*/ +void QTextDocument::markContentsDirty(int from, int length) +{ + Q_D(QTextDocument); + d->documentChange(from, length); + if (!d->inContentsChange) { + if (d->lout) { + d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength); + d->docChangeFrom = -1; + } + } +} + +/*! + \property QTextDocument::useDesignMetrics + \since 4.1 + \brief whether the document uses design metrics of fonts to improve the accuracy of text layout + + If this property is set to true, the layout will use design metrics. + Otherwise, the metrics of the paint device as set on + QAbstractTextDocumentLayout::setPaintDevice() will be used. + + Using design metrics makes a layout have a width that is no longer dependent on hinting + and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width + scales much more linearly based on paintdevice metrics than it would otherwise. + + By default, this property is false. +*/ + +void QTextDocument::setUseDesignMetrics(bool b) +{ + Q_D(QTextDocument); + if (b == d->defaultTextOption.useDesignMetrics()) + return; + d->defaultTextOption.setUseDesignMetrics(b); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +bool QTextDocument::useDesignMetrics() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption.useDesignMetrics(); +} + +/*! + \since 4.2 + + Draws the content of the document with painter \a p, clipped to \a rect. + If \a rect is a null rectangle (default) then the document is painted unclipped. +*/ +void QTextDocument::drawContents(QPainter *p, const QRectF &rect) +{ + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx; + if (rect.isValid()) { + p->setClipRect(rect); + ctx.clip = rect; + } + documentLayout()->draw(p, ctx); + p->restore(); +} + +/*! + \property QTextDocument::textWidth + \since 4.2 + + The text width specifies the preferred width for text in the document. If + the text (or content in general) is wider than the specified with it is broken + into multiple lines and grows vertically. If the text cannot be broken into multiple + lines to fit into the specified text width it will be larger and the size() and the + idealWidth() property will reflect that. + + If the text width is set to -1 then the text will not be broken into multiple lines + unless it is enforced through an explicit line break or a new paragraph. + + The default value is -1. + + Setting the text width will also set the page height to -1, causing the document to + grow or shrink vertically in a continuous way. If you want the document layout to break + the text into multiple pages then you have to set the pageSize property instead. + + \sa size(), idealWidth(), pageSize() +*/ +void QTextDocument::setTextWidth(qreal width) +{ + Q_D(QTextDocument); + QSizeF sz = d->pageSize; + sz.setWidth(width); + sz.setHeight(-1); + setPageSize(sz); +} + +qreal QTextDocument::textWidth() const +{ + Q_D(const QTextDocument); + return d->pageSize.width(); +} + +/*! + \since 4.2 + + Returns the ideal width of the text document. The ideal width is the actually used width + of the document without optional alignments taken into account. It is always <= size().width(). + + \sa adjustSize(), textWidth +*/ +qreal QTextDocument::idealWidth() const +{ + if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout())) + return lout->idealWidth(); + return textWidth(); +} + +/*! + \property QTextDocument::documentMargin + \since 4.5 + + The margin around the document. The default is 4. +*/ +qreal QTextDocument::documentMargin() const +{ + Q_D(const QTextDocument); + return d->documentMargin; +} + +void QTextDocument::setDocumentMargin(qreal margin) +{ + Q_D(QTextDocument); + if (d->documentMargin != margin) { + d->documentMargin = margin; + + QTextFrame* root = rootFrame(); + QTextFrameFormat format = root->frameFormat(); + format.setMargin(margin); + root->setFrameFormat(format); + + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + +/*! + \property QTextDocument::indentWidth + \since 4.4 + + Returns the width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40. +*/ +qreal QTextDocument::indentWidth() const +{ + Q_D(const QTextDocument); + return d->indentWidth; +} + + +/*! + \since 4.4 + + Sets the \a width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40 . + + \sa indentWidth() +*/ +void QTextDocument::setIndentWidth(qreal width) +{ + Q_D(QTextDocument); + if (d->indentWidth != width) { + d->indentWidth = width; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + + + +/*! + \since 4.2 + + Adjusts the document to a reasonable size. + + \sa idealWidth(), textWidth, size +*/ +void QTextDocument::adjustSize() +{ + // Pull this private function in from qglobal.cpp + QFont f = defaultFont(); + QFontMetrics fm(f); + int mw = fm.width(QLatin1Char('x')) * 80; + int w = mw; + setTextWidth(w); + QSizeF size = documentLayout()->documentSize(); + if (size.width() != 0) { + w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3)); + setTextWidth(qMin(w, mw)); + + size = documentLayout()->documentSize(); + if (w*3 < 5*size.height()) { + w = qt_int_sqrt((uint)(2 * size.height() * size.width())); + setTextWidth(qMin(w, mw)); + } + } + setTextWidth(idealWidth()); +} + +/*! + \property QTextDocument::size + \since 4.2 + + Returns the actual size of the document. + This is equivalent to documentLayout()->documentSize(); + + The size of the document can be changed either by setting + a text width or setting an entire page size. + + Note that the width is always >= pageSize().width(). + + By default, for a newly-created, empty document, this property contains + a configuration-dependent size. + + \sa setTextWidth(), setPageSize(), idealWidth() +*/ +QSizeF QTextDocument::size() const +{ + return documentLayout()->documentSize(); +} + +/*! + \property QTextDocument::blockCount + \since 4.2 + + Returns the number of text blocks in the document. + + The value of this property is undefined in documents with tables or frames. + + By default, if defined, this property contains a value of 1. + \sa lineCount(), characterCount() +*/ +int QTextDocument::blockCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().numNodes(); +} + + +/*! + \since 4.5 + + Returns the number of lines of this document (if the layout supports + this). Otherwise, this is identical to the number of blocks. + + \sa blockCount(), characterCount() + */ +int QTextDocument::lineCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().length(2); +} + +/*! + \since 4.5 + + Returns the number of characters of this document. + + \sa blockCount(), characterAt() + */ +int QTextDocument::characterCount() const +{ + Q_D(const QTextDocument); + return d->length(); +} + +/*! + \since 4.5 + + Returns the character at position \a pos, or a null character if the + position is out of range. + + \sa characterCount() + */ +QChar QTextDocument::characterAt(int pos) const +{ + Q_D(const QTextDocument); + if (pos < 0 || pos >= d->length()) + return QChar(); + QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + const int offsetInFragment = qMax(0, pos - fragIt.position()); + return d->text.at(frag->stringPosition + offsetInFragment); +} + + +/*! + \property QTextDocument::defaultStyleSheet + \since 4.2 + + The default style sheet is applied to all newly HTML formatted text that is + inserted into the document, for example using setHtml() or QTextCursor::insertHtml(). + + The style sheet needs to be compliant to CSS 2.1 syntax. + + \bold{Note:} Changing the default style sheet does not have any effect to the existing content + of the document. + + \sa {Supported HTML Subset} +*/ + +#ifndef QT_NO_CSSPARSER +void QTextDocument::setDefaultStyleSheet(const QString &sheet) +{ + Q_D(QTextDocument); + d->defaultStyleSheet = sheet; + QCss::Parser parser(sheet); + d->parsedDefaultStyleSheet = QCss::StyleSheet(); + d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent; + parser.parse(&d->parsedDefaultStyleSheet); +} + +QString QTextDocument::defaultStyleSheet() const +{ + Q_D(const QTextDocument); + return d->defaultStyleSheet; +} +#endif // QT_NO_CSSPARSER + +/*! + \fn void QTextDocument::contentsChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + \sa contentsChange() +*/ + +/*! + \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded) + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + Information is provided about the \a position of the character in the + document where the change occurred, the number of characters removed + (\a charsRemoved), and the number of characters added (\a charsAdded). + + The signal is emitted before the document's layout manager is notified + about the change. This hook allows you to implement syntax highlighting + for the document. + + \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged() +*/ + + +/*! + \fn QTextDocument::undoAvailable(bool available); + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undo(), isUndoRedoEnabled() +*/ + +/*! + \fn QTextDocument::redoAvailable(bool available); + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor); + + This signal is emitted whenever the position of a cursor changed + due to an editing operation. The cursor that changed is passed in + \a cursor. If you need a signal when the cursor is moved with the + arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in + QTextEdit. +*/ + +/*! + \fn QTextDocument::blockCountChanged(int newBlockCount); + \since 4.3 + + This signal is emitted when the total number of text blocks in the + document changes. The value passed in \a newBlockCount is the new + total. +*/ + +/*! + \fn QTextDocument::documentLayoutChanged(); + \since 4.4 + + This signal is emitted when a new document layout is set. + + \sa setDocumentLayout() + +*/ + + +/*! + Returns true if undo is available; otherwise returns false. + + \sa isRedoAvailable(), availableUndoSteps() +*/ +bool QTextDocument::isUndoAvailable() const +{ + Q_D(const QTextDocument); + return d->isUndoAvailable(); +} + +/*! + Returns true if redo is available; otherwise returns false. + + \sa isUndoAvailable(), availableRedoSteps() +*/ +bool QTextDocument::isRedoAvailable() const +{ + Q_D(const QTextDocument); + return d->isRedoAvailable(); +} + +/*! \since 4.6 + + Returns the number of available undo steps. + + \sa isUndoAvailable() +*/ +int QTextDocument::availableUndoSteps() const +{ + Q_D(const QTextDocument); + return d->availableUndoSteps(); +} + +/*! \since 4.6 + + Returns the number of available redo steps. + + \sa isRedoAvailable() +*/ +int QTextDocument::availableRedoSteps() const +{ + Q_D(const QTextDocument); + return d->availableRedoSteps(); +} + +/*! \since 4.4 + + Returns the document's revision (if undo is enabled). + + The revision is guaranteed to increase when a document that is not + modified is edited. + + \sa QTextBlock::revision(), isModified() + */ +int QTextDocument::revision() const +{ + Q_D(const QTextDocument); + return d->revision; +} + + + +/*! + Sets the document to use the given \a layout. The previous layout + is deleted. + + \sa documentLayoutChanged() +*/ +void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout) +{ + Q_D(QTextDocument); + d->setLayout(layout); +} + +/*! + Returns the document layout for this document. +*/ +QAbstractTextDocumentLayout *QTextDocument::documentLayout() const +{ + Q_D(const QTextDocument); + if (!d->lout) { + QTextDocument *that = const_cast<QTextDocument *>(this); + that->d_func()->setLayout(new QTextDocumentLayout(that)); + } + return d->lout; +} + + +/*! + Returns meta information about the document of the type specified by + \a info. + + \sa setMetaInformation() +*/ +QString QTextDocument::metaInformation(MetaInformation info) const +{ + Q_D(const QTextDocument); + switch (info) { + case DocumentTitle: + return d->title; + case DocumentUrl: + return d->url; + } + return QString(); +} + +/*! + Sets the document's meta information of the type specified by \a info + to the given \a string. + + \sa metaInformation() +*/ +void QTextDocument::setMetaInformation(MetaInformation info, const QString &string) +{ + Q_D(QTextDocument); + switch (info) { + case DocumentTitle: + d->title = string; + break; + case DocumentUrl: + d->url = string; + break; + } +} + +/*! + Returns the plain text contained in the document. If you want + formatting information use a QTextCursor instead. + + \sa toHtml() +*/ +QString QTextDocument::toPlainText() const +{ + Q_D(const QTextDocument); + QString txt = d->plainText(); + + QChar *uc = txt.data(); + QChar *e = uc + txt.size(); + + for (; uc != e; ++uc) { + switch (uc->unicode()) { + case 0xfdd0: // QTextBeginningOfFrame + case 0xfdd1: // QTextEndOfFrame + case QChar::ParagraphSeparator: + case QChar::LineSeparator: + *uc = QLatin1Char('\n'); + break; + case QChar::Nbsp: + *uc = QLatin1Char(' '); + break; + default: + ; + } + } + return txt; +} + +/*! + Replaces the entire contents of the document with the given plain + \a text. + + \sa setHtml() +*/ +void QTextDocument::setPlainText(const QString &text) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->beginEditBlock(); + d->clear(); + QTextCursor(this).insertText(text); + d->endEditBlock(); + d->enableUndoRedo(previousState); +} + +/*! + Replaces the entire contents of the document with the given + HTML-formatted text in the \a html string. + + The HTML formatting is respected as much as possible; for example, + "<b>bold</b> text" will produce text where the first word has a font + weight that gives it a bold appearance: "\bold{bold} text". + + \note It is the responsibility of the caller to make sure that the + text is correctly decoded when a QString containing HTML is created + and passed to setHtml(). + + \sa setPlainText(), {Supported HTML Subset} +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +void QTextDocument::setHtml(const QString &html) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->beginEditBlock(); + d->clear(); + QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import(); + d->endEditBlock(); + d->enableUndoRedo(previousState); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \enum QTextDocument::FindFlag + + This enum describes the options available to QTextDocument's find function. The options + can be OR-ed together from the following list: + + \value FindBackward Search backwards instead of forwards. + \value FindCaseSensitively By default find works case insensitive. Specifying this option + changes the behaviour to a case sensitive find operation. + \value FindWholeWords Makes find match only complete words. +*/ + +/*! + \enum QTextDocument::MetaInformation + + This enum describes the different types of meta information that can be + added to a document. + + \value DocumentTitle The title of the document. + \value DocumentUrl The url of the document. The loadResource() function uses + this url as the base when loading relative resources. + + \sa metaInformation(), setMetaInformation() +*/ + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const + + \overload + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString + was found; otherwise returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const +{ + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, from, options); +} + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, pos, options); +} + + +static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset, + QTextDocument::FindFlags options, QTextCursor &cursor) +{ + const QRegExp expr(expression); + QString text = block.text(); + text.replace(QChar::Nbsp, QLatin1Char(' ')); + + int idx = -1; + while (offset >=0 && offset <= text.length()) { + idx = (options & QTextDocument::FindBackward) ? + expr.lastIndexIn(text, offset) : expr.indexIn(text, offset); + if (idx == -1) + return false; + + if (options & QTextDocument::FindWholeWords) { + const int start = idx; + const int end = start + expr.matchedLength(); + if ((start != 0 && text.at(start - 1).isLetterOrNumber()) + || (end != text.length() && text.at(end).isLetterOrNumber())) { + //if this is not a whole word, continue the search in the string + offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; + idx = -1; + continue; + } + } + //we have a hit, return the cursor for that. + break; + } + if (idx == -1) + return false; + cursor = QTextCursor(block.docHandle(), block.position() + idx); + cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor); + return true; +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const + + \overload + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const +{ + Q_D(const QTextDocument); + + if (expr.isEmpty()) + return QTextCursor(); + + int pos = from; + //the cursor is positioned between characters, so for a backward search + //do not include the character given in the position. + if (options & FindBackward) { + --pos ; + if(pos < 0) + return QTextCursor(); + } + + QTextCursor cursor; + QTextBlock block = d->blocksFind(pos); + + if (!(options & FindBackward)) { + int blockOffset = qMax(0, pos - block.position()); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + blockOffset = 0; + block = block.next(); + } + } else { + int blockOffset = pos - block.position(); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + block = block.previous(); + blockOffset = block.length() - 1; + } + } + + return QTextCursor(); +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + return find(expr, pos, options); +} + + +/*! + \fn QTextObject *QTextDocument::createObject(const QTextFormat &format) + + Creates and returns a new document object (a QTextObject), based + on the given \a format. + + QTextObjects will always get created through this method, so you + must reimplement it if you use custom text objects inside your document. +*/ +QTextObject *QTextDocument::createObject(const QTextFormat &f) +{ + QTextObject *obj = 0; + if (f.isListFormat()) + obj = new QTextList(this); + else if (f.isTableFormat()) + obj = new QTextTable(this); + else if (f.isFrameFormat()) + obj = new QTextFrame(this); + + return obj; +} + +/*! + \internal + + Returns the frame that contains the text cursor position \a pos. +*/ +QTextFrame *QTextDocument::frameAt(int pos) const +{ + Q_D(const QTextDocument); + return d->frameAt(pos); +} + +/*! + Returns the document's root frame. +*/ +QTextFrame *QTextDocument::rootFrame() const +{ + Q_D(const QTextDocument); + return d->rootFrame(); +} + +/*! + Returns the text object associated with the given \a objectIndex. +*/ +QTextObject *QTextDocument::object(int objectIndex) const +{ + Q_D(const QTextDocument); + return d->objectForIndex(objectIndex); +} + +/*! + Returns the text object associated with the format \a f. +*/ +QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const +{ + Q_D(const QTextDocument); + return d->objectForFormat(f); +} + + +/*! + Returns the text block that contains the \a{pos}-th character. +*/ +QTextBlock QTextDocument::findBlock(int pos) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(pos)); +} + +/*! + \since 4.4 + Returns the text block with the specified \a blockNumber. + + \sa QTextBlock::blockNumber() +*/ +QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1)); +} + +/*! + \since 4.5 + Returns the text block that contains the specified \a lineNumber. + + \sa QTextBlock::firstLineNumber() +*/ +QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2)); +} + +/*! + Returns the document's first text block. + + \sa firstBlock() +*/ +QTextBlock QTextDocument::begin() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + This function returns a block to test for the end of the document + while iterating over it. + + \snippet doc/src/snippets/textdocumentendsnippet.cpp 0 + + The block returned is invalid and represents the block after the + last block in the document. You can use lastBlock() to retrieve the + last valid block of the document. + + \sa lastBlock() +*/ +QTextBlock QTextDocument::end() const +{ + return QTextBlock(docHandle(), 0); +} + +/*! + \since 4.4 + Returns the document's first text block. +*/ +QTextBlock QTextDocument::firstBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + \since 4.4 + Returns the document's last (valid) text block. +*/ +QTextBlock QTextDocument::lastBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().last().n); +} + +/*! + \property QTextDocument::pageSize + \brief the page size that should be used for laying out the document + + By default, for a newly-created, empty document, this property contains + an undefined size. + + \sa modificationChanged() +*/ + +void QTextDocument::setPageSize(const QSizeF &size) +{ + Q_D(QTextDocument); + d->pageSize = size; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +QSizeF QTextDocument::pageSize() const +{ + Q_D(const QTextDocument); + return d->pageSize; +} + +/*! + returns the number of pages in this document. +*/ +int QTextDocument::pageCount() const +{ + return documentLayout()->pageCount(); +} + +/*! + Sets the default \a font to use in the document layout. +*/ +void QTextDocument::setDefaultFont(const QFont &font) +{ + Q_D(QTextDocument); + d->setDefaultFont(font); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + Returns the default font to be used in the document layout. +*/ +QFont QTextDocument::defaultFont() const +{ + Q_D(const QTextDocument); + return d->defaultFont(); +} + +/*! + \fn QTextDocument::modificationChanged(bool changed) + + This signal is emitted whenever the content of the document + changes in a way that affects the modification state. If \a + changed is true, the document has been modified; otherwise it is + false. + + For example, calling setModified(false) on a document and then + inserting text causes the signal to get emitted. If you undo that + operation, causing the document to return to its original + unmodified state, the signal will get emitted again. +*/ + +/*! + \property QTextDocument::modified + \brief whether the document has been modified by the user + + By default, this property is false. + + \sa modificationChanged() +*/ + +bool QTextDocument::isModified() const +{ + return docHandle()->isModified(); +} + +void QTextDocument::setModified(bool m) +{ + docHandle()->setModified(m); +} + +#ifndef QT_NO_PRINTER +static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos) +{ + painter->save(); + painter->translate(body.left(), body.top() - (index - 1) * body.height()); + QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout::PaintContext ctx; + + painter->setClipRect(view); + ctx.clip = view; + + // don't use the system palette text as default text color, on HP/UX + // for example that's white, and white text on white paper doesn't + // look that nice + ctx.palette.setColor(QPalette::Text, Qt::black); + + layout->draw(painter, ctx); + + if (!pageNumberPos.isNull()) { + painter->setClipping(false); + painter->setFont(QFont(doc->defaultFont())); + const QString pageString = QString::number(index); + + painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)), + qRound(pageNumberPos.y() + view.top()), + pageString); + } + + painter->restore(); +} + +/*! + Prints the document to the given \a printer. The QPrinter must be + set up before being used with this function. + + This is only a convenience method to print the whole document to the printer. + + If the document is already paginated through a specified height in the pageSize() + property it is printed as-is. + + If the document is not paginated, like for example a document used in a QTextEdit, + then a temporary copy of the document is created and the copy is broken into + multiple pages according to the size of the QPrinter's paperRect(). By default + a 2 cm margin is set around the document contents. In addition the current page + number is printed at the bottom of each page. + + Note that QPrinter::Selection is not supported as print range with this function since + the selection is a property of QTextCursor. If you have a QTextEdit associated with + your QTextDocument then you can use QTextEdit's print() function because QTextEdit has + access to the user's selection. + + \sa QTextEdit::print() +*/ + +void QTextDocument::print(QPrinter *printer) const +{ + Q_D(const QTextDocument); + + if (!printer || !printer->isValid()) + return; + + if (!d->title.isEmpty()) + printer->setDocName(d->title); + + bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull() + && d->pageSize.height() != INT_MAX; + + if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins) + printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter); + + QPainter p(printer); + + // Check that there is a valid device to print to. + if (!p.isActive()) + return; + + const QTextDocument *doc = this; + QScopedPointer<QTextDocument> clonedDoc; + (void)doc->documentLayout(); // make sure that there is a layout + + QRectF body = QRectF(QPointF(0, 0), d->pageSize); + QPointF pageNumberPos; + + if (documentPaginated) { + qreal sourceDpiX = qt_defaultDpi(); + qreal sourceDpiY = sourceDpiX; + + QPaintDevice *dev = doc->documentLayout()->paintDevice(); + if (dev) { + sourceDpiX = dev->logicalDpiX(); + sourceDpiY = dev->logicalDpiY(); + } + + const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX; + const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY; + + // scale to dpi + p.scale(dpiScaleX, dpiScaleY); + + QSizeF scaledPageSize = d->pageSize; + scaledPageSize.rwidth() *= dpiScaleX; + scaledPageSize.rheight() *= dpiScaleY; + + const QSizeF printerPageSize(printer->pageRect().size()); + + // scale to page + p.scale(printerPageSize.width() / scaledPageSize.width(), + printerPageSize.height() / scaledPageSize.height()); + } else { + doc = clone(const_cast<QTextDocument *>(this)); + clonedDoc.reset(const_cast<QTextDocument *>(doc)); + + for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock(); + srcBlock.isValid() && dstBlock.isValid(); + srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { + dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats()); + } + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + layout->setPaintDevice(p.device()); + + // copy the custom object handlers + layout->d_func()->handlers = documentLayout()->d_func()->handlers; + + int dpiy = p.device()->logicalDpiY(); + int margin = 0; + if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) { + // for compatibility + margin = (int) ((2/2.54)*dpiy); // 2 cm margins + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + } + + QRectF pageRect(printer->pageRect()); + body = QRectF(0, 0, pageRect.width(), pageRect.height()); + pageNumberPos = QPointF(body.width() - margin, + body.height() - margin + + QFontMetrics(doc->defaultFont(), p.device()).ascent() + + 5 * dpiy / 72.0); + clonedDoc->setPageSize(body.size()); + } + + int docCopies; + int pageCopies; + if (printer->collateCopies() == true){ + docCopies = 1; + pageCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); + } else { + docCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); + pageCopies = 1; + } + + int fromPage = printer->fromPage(); + int toPage = printer->toPage(); + bool ascending = true; + + if (fromPage == 0 && toPage == 0) { + fromPage = 1; + toPage = doc->pageCount(); + } + // paranoia check + fromPage = qMax(1, fromPage); + toPage = qMin(doc->pageCount(), toPage); + + if (toPage < fromPage) { + // if the user entered a page range outside the actual number + // of printable pages, just return + return; + } + + if (printer->pageOrder() == QPrinter::LastPageFirst) { + int tmp = fromPage; + fromPage = toPage; + toPage = tmp; + ascending = false; + } + + for (int i = 0; i < docCopies; ++i) { + + int page = fromPage; + while (true) { + for (int j = 0; j < pageCopies; ++j) { + if (printer->printerState() == QPrinter::Aborted + || printer->printerState() == QPrinter::Error) + return; + printPage(page, &p, doc, body, pageNumberPos); + if (j < pageCopies - 1) + printer->newPage(); + } + + if (page == toPage) + break; + + if (ascending) + ++page; + else + --page; + + printer->newPage(); + } + + if ( i < docCopies - 1) + printer->newPage(); + } +} +#endif + +/*! + \enum QTextDocument::ResourceType + + This enum describes the types of resources that can be loaded by + QTextDocument's loadResource() function. + + \value HtmlResource The resource contains HTML. + \value ImageResource The resource contains image data. + Currently supported data types are QVariant::Pixmap and + QVariant::Image. If the corresponding variant is of type + QVariant::ByteArray then Qt attempts to load the image using + QImage::loadFromData. QVariant::Icon is currently not supported. + The icon needs to be converted to one of the supported types first, + for example using QIcon::pixmap. + \value StyleSheetResource The resource contains CSS. + \value UserResource The first available value for user defined + resource types. + + \sa loadResource() +*/ + +/*! + Returns data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + Resources are cached internally in the document. If a resource can + not be found in the cache, loadResource is called to try to load + the resource. loadResource should then use addResource to add the + resource to the cache. + + \sa QTextDocument::ResourceType +*/ +QVariant QTextDocument::resource(int type, const QUrl &name) const +{ + Q_D(const QTextDocument); + QVariant r = d->resources.value(name); + if (!r.isValid()) { + r = d->cachedResources.value(name); + if (!r.isValid()) + r = const_cast<QTextDocument *>(this)->loadResource(type, name); + } + return r; +} + +/*! + Adds the resource \a resource to the resource cache, using \a + type and \a name as identifiers. \a type should be a value from + QTextDocument::ResourceType. + + For example, you can add an image as a resource in order to reference it + from within the document: + + \snippet snippets/textdocument-resources/main.cpp Adding a resource + + The image can be inserted into the document using the QTextCursor API: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor + + Alternatively, you can insert images using the HTML \c img tag: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML +*/ +void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource) +{ + Q_UNUSED(type); + Q_D(QTextDocument); + d->resources.insert(name, resource); +} + +/*! + Loads data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + When called by Qt, \a type is one of the values of + QTextDocument::ResourceType. + + If the QTextDocument is a child object of a QTextEdit, QTextBrowser, + or a QTextDocument itself then the default implementation tries + to retrieve the data from the parent. +*/ +QVariant QTextDocument::loadResource(int type, const QUrl &name) +{ + Q_D(QTextDocument); + QVariant r; + + QTextDocument *doc = qobject_cast<QTextDocument *>(parent()); + if (doc) { + r = doc->loadResource(type, name); + } +#ifndef QT_NO_TEXTEDIT + else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) { + QUrl resolvedName = edit->d_func()->resolveUrl(name); + r = edit->loadResource(type, resolvedName); + } +#endif +#ifndef QT_NO_TEXTCONTROL + else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) { + r = control->loadResource(type, name); + } +#endif + + // handle data: URLs + if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) + r = qDecodeDataUrl(name).second; + + // if resource was not loaded try to load it here + if (!doc && r.isNull() && name.isRelative()) { + QUrl currentURL = d->url; + QUrl resourceUrl = name; + + // For the second case QUrl can merge "#someanchor" with "foo.html" + // correctly to "foo.html#someanchor" + if (!(currentURL.isRelative() + || (currentURL.scheme() == QLatin1String("file") + && !QFileInfo(currentURL.toLocalFile()).isAbsolute())) + || (name.hasFragment() && name.path().isEmpty())) { + resourceUrl = currentURL.resolved(name); + } else { + // this is our last resort when current url and new url are both relative + // we try to resolve against the current working directory in the local + // file system. + QFileInfo fi(currentURL.toLocalFile()); + if (fi.exists()) { + resourceUrl = + QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name); + } else if (currentURL.isEmpty()) { + resourceUrl.setScheme(QLatin1String("file")); + } + } + + QString s = resourceUrl.toLocalFile(); + QFile f(s); + if (!s.isEmpty() && f.open(QFile::ReadOnly)) { + r = f.readAll(); + f.close(); + } + } + + if (!r.isNull()) { + if (type == ImageResource && r.type() == QVariant::ByteArray) { + if (qApp->thread() != QThread::currentThread()) { + // must use images in non-GUI threads + QImage image; + image.loadFromData(r.toByteArray()); + if (!image.isNull()) + r = image; + } else { + QPixmap pm; + pm.loadFromData(r.toByteArray()); + if (!pm.isNull()) + r = pm; + } + } + d->cachedResources.insert(name, r); + } + return r; +} + +static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to) +{ + QTextFormat diff = to; + + const QMap<int, QVariant> props = to.properties(); + for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end(); + it != end; ++it) + if (it.value() == from.property(it.key())) + diff.clearProperty(it.key()); + + return diff; +} + +QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc) + : doc(_doc), fragmentMarkers(false) +{ + const QFont defaultFont = doc->defaultFont(); + defaultCharFormat.setFont(defaultFont); + // don't export those for the default font since we cannot turn them off with CSS + defaultCharFormat.clearProperty(QTextFormat::FontUnderline); + defaultCharFormat.clearProperty(QTextFormat::FontOverline); + defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut); + defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle); +} + +/*! + Returns the document in HTML format. The conversion may not be + perfect, especially for complex documents, due to the limitations + of HTML. +*/ +QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode) +{ + html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " + "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" + "<html><head><meta name=\"qrichtext\" content=\"1\" />"); + html.reserve(doc->docHandle()->length()); + + fragmentMarkers = (mode == ExportFragment); + + if (!encoding.isEmpty()) + html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding)); + + QString title = doc->metaInformation(QTextDocument::DocumentTitle); + if (!title.isEmpty()) + html += QString::fromLatin1("<title>") + title + QString::fromLatin1(""); + html += QLatin1String(""); + html += QLatin1String("rootFrame()->frameFormat(); + emitBackgroundAttribute(fmt); + + } else { + defaultCharFormat = QTextCharFormat(); + } + html += QLatin1Char('>'); + + QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat(); + rootFmt.clearProperty(QTextFormat::BackgroundBrush); + + QTextFrameFormat defaultFmt; + defaultFmt.setMargin(doc->documentMargin()); + + if (rootFmt == defaultFmt) + emitFrame(doc->rootFrame()->begin()); + else + emitTextFrame(doc->rootFrame()); + + html += QLatin1String(""); + return html; +} + +void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value) +{ + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += Qt::escape(value); + html += QLatin1Char('"'); +} + +bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) +{ + bool attributesEmitted = false; + + { + const QString family = format.fontFamily(); + if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) { + emitFontFamily(family); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontPointSize) + && format.fontPointSize() != defaultCharFormat.fontPointSize()) { + html += QLatin1String(" font-size:"); + html += QString::number(format.fontPointSize()); + html += QLatin1String("pt;"); + attributesEmitted = true; + } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) { + static const char * const sizeNames[] = { + "small", "medium", "large", "x-large", "xx-large" + }; + const char *name = 0; + const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1; + if (idx >= 0 && idx <= 4) { + name = sizeNames[idx]; + } + if (name) { + html += QLatin1String(" font-size:"); + html += QLatin1String(name); + html += QLatin1Char(';'); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontWeight) + && format.fontWeight() != defaultCharFormat.fontWeight()) { + html += QLatin1String(" font-weight:"); + html += QString::number(format.fontWeight() * 8); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.hasProperty(QTextFormat::FontItalic) + && format.fontItalic() != defaultCharFormat.fontItalic()) { + html += QLatin1String(" font-style:"); + html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + QLatin1String decorationTag(" text-decoration:"); + html += decorationTag; + bool hasDecoration = false; + bool atLeastOneDecorationSet = false; + + if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle)) + && format.fontUnderline() != defaultCharFormat.fontUnderline()) { + hasDecoration = true; + if (format.fontUnderline()) { + html += QLatin1String(" underline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontOverline) + && format.fontOverline() != defaultCharFormat.fontOverline()) { + hasDecoration = true; + if (format.fontOverline()) { + html += QLatin1String(" overline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontStrikeOut) + && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) { + hasDecoration = true; + if (format.fontStrikeOut()) { + html += QLatin1String(" line-through"); + atLeastOneDecorationSet = true; + } + } + + if (hasDecoration) { + if (!atLeastOneDecorationSet) + html += QLatin1String("none"); + html += QLatin1Char(';'); + attributesEmitted = true; + } else { + html.chop(qstrlen(decorationTag.latin1())); + } + + if (format.foreground() != defaultCharFormat.foreground() + && format.foreground().style() != Qt::NoBrush) { + html += QLatin1String(" color:"); + html += format.foreground().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.background() != defaultCharFormat.background() + && format.background().style() == Qt::SolidPattern) { + html += QLatin1String(" background-color:"); + html += format.background().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.verticalAlignment() != defaultCharFormat.verticalAlignment() + && format.verticalAlignment() != QTextCharFormat::AlignNormal) + { + html += QLatin1String(" vertical-align:"); + + QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); + if (valign == QTextCharFormat::AlignSubScript) + html += QLatin1String("sub"); + else if (valign == QTextCharFormat::AlignSuperScript) + html += QLatin1String("super"); + else if (valign == QTextCharFormat::AlignMiddle) + html += QLatin1String("middle"); + else if (valign == QTextCharFormat::AlignTop) + html += QLatin1String("top"); + else if (valign == QTextCharFormat::AlignBottom) + html += QLatin1String("bottom"); + + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.fontCapitalization() != QFont::MixedCase) { + const QFont::Capitalization caps = format.fontCapitalization(); + if (caps == QFont::AllUppercase) + html += QLatin1String(" text-transform:uppercase;"); + else if (caps == QFont::AllLowercase) + html += QLatin1String(" text-transform:lowercase;"); + else if (caps == QFont::SmallCaps) + html += QLatin1String(" font-variant:small-caps;"); + attributesEmitted = true; + } + + if (format.fontWordSpacing() != 0.0) { + html += QLatin1String(" word-spacing:"); + html += QString::number(format.fontWordSpacing()); + html += QLatin1String("px;"); + attributesEmitted = true; + } + + return attributesEmitted; +} + +void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length) +{ + if (length.type() == QTextLength::VariableLength) // default + return; + + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += QString::number(length.rawValue()); + + if (length.type() == QTextLength::PercentageLength) + html += QLatin1String("%\""); + else + html += QLatin1Char('\"'); +} + +void QTextHtmlExporter::emitAlignment(Qt::Alignment align) +{ + if (align & Qt::AlignLeft) + return; + else if (align & Qt::AlignRight) + html += QLatin1String(" align=\"right\""); + else if (align & Qt::AlignHCenter) + html += QLatin1String(" align=\"center\""); + else if (align & Qt::AlignJustify) + html += QLatin1String(" align=\"justify\""); +} + +void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode) +{ + if (pos == QTextFrameFormat::InFlow) + return; + + if (mode == EmitStyleTag) + html += QLatin1String(" style=\"float:"); + else + html += QLatin1String(" float:"); + + if (pos == QTextFrameFormat::FloatLeft) + html += QLatin1String(" left;"); + else if (pos == QTextFrameFormat::FloatRight) + html += QLatin1String(" right;"); + else + Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type"); + + if (mode == EmitStyleTag) + html += QLatin1Char('\"'); +} + +void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style) +{ + Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset); + + html += QLatin1String(" border-style:"); + + switch (style) { + case QTextFrameFormat::BorderStyle_None: + html += QLatin1String("none"); + break; + case QTextFrameFormat::BorderStyle_Dotted: + html += QLatin1String("dotted"); + break; + case QTextFrameFormat::BorderStyle_Dashed: + html += QLatin1String("dashed"); + break; + case QTextFrameFormat::BorderStyle_Solid: + html += QLatin1String("solid"); + break; + case QTextFrameFormat::BorderStyle_Double: + html += QLatin1String("double"); + break; + case QTextFrameFormat::BorderStyle_DotDash: + html += QLatin1String("dot-dash"); + break; + case QTextFrameFormat::BorderStyle_DotDotDash: + html += QLatin1String("dot-dot-dash"); + break; + case QTextFrameFormat::BorderStyle_Groove: + html += QLatin1String("groove"); + break; + case QTextFrameFormat::BorderStyle_Ridge: + html += QLatin1String("ridge"); + break; + case QTextFrameFormat::BorderStyle_Inset: + html += QLatin1String("inset"); + break; + case QTextFrameFormat::BorderStyle_Outset: + html += QLatin1String("outset"); + break; + default: + Q_ASSERT(false); + break; + }; + + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy) +{ + if (policy & QTextFormat::PageBreak_AlwaysBefore) + html += QLatin1String(" page-break-before:always;"); + + if (policy & QTextFormat::PageBreak_AlwaysAfter) + html += QLatin1String(" page-break-after:always;"); +} + +void QTextHtmlExporter::emitFontFamily(const QString &family) +{ + html += QLatin1String(" font-family:"); + + QLatin1String quote("\'"); + if (family.contains(QLatin1Char('\''))) + quote = QLatin1String("""); + + html += quote; + html += Qt::escape(family); + html += quote; + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right) +{ + html += QLatin1String(" margin-top:"); + html += top; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-bottom:"); + html += bottom; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-left:"); + html += left; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-right:"); + html += right; + html += QLatin1String("px;"); +} + +void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) +{ + const QTextCharFormat format = fragment.charFormat(); + + bool closeAnchor = false; + + if (format.isAnchor()) { + const QString name = format.anchorName(); + if (!name.isEmpty()) { + html += QLatin1String(""); + } + const QString href = format.anchorHref(); + if (!href.isEmpty()) { + html += QLatin1String(""); + closeAnchor = true; + } + } + + QString txt = fragment.text(); + const bool isObject = txt.contains(QChar::ObjectReplacementCharacter); + const bool isImage = isObject && format.isImageFormat(); + + QLatin1String styleTag(""); + else + html.chop(qstrlen(styleTag.latin1())); + + if (isObject) { + for (int i = 0; isImage && i < txt.length(); ++i) { + QTextImageFormat imgFmt = format.toImageFormat(); + + html += QLatin1String("(doc->objectForFormat(imgFmt))) + emitFloatStyle(imageFrame->frameFormat().position()); + + html += QLatin1String(" />"); + } + } else { + Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter)); + + txt = Qt::escape(txt); + + // split for [\n{LineSeparator}] + QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]"); + forcedLineBreakRegExp[3] = QChar::LineSeparator; + + const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp)); + for (int i = 0; i < lines.count(); ++i) { + if (i > 0) + html += QLatin1String("
"); // space on purpose for compatibility with Netscape, Lynx & Co. + html += lines.at(i); + } + } + + if (attributesEmitted) + html += QLatin1String("
"); + + if (closeAnchor) + html += QLatin1String("
"); +} + +static bool isOrderedList(int style) +{ + return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha + || style == QTextListFormat::ListUpperAlpha + || style == QTextListFormat::ListUpperRoman + || style == QTextListFormat::ListLowerRoman + ; +} + +void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) +{ + QTextBlockFormat format = block.blockFormat(); + emitAlignment(format.alignment()); + + // assume default to not bloat the html too much + // html += QLatin1String(" dir='ltr'"); + if (block.textDirection() == Qt::RightToLeft) + html += QLatin1String(" dir='rtl'"); + + QLatin1String style(" style=\""); + html += style; + + const bool emptyBlock = block.begin().atEnd(); + if (emptyBlock) { + html += QLatin1String("-qt-paragraph-type:empty;"); + } + + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + html += QLatin1String(" -qt-block-indent:"); + html += QString::number(format.indent()); + html += QLatin1Char(';'); + + html += QLatin1String(" text-indent:"); + html += QString::number(format.textIndent()); + html += QLatin1String("px;"); + + if (block.userState() != -1) { + html += QLatin1String(" -qt-user-state:"); + html += QString::number(block.userState()); + html += QLatin1Char(';'); + } + + emitPageBreakPolicy(format.pageBreakPolicy()); + + QTextCharFormat diff; + if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag + const QTextCharFormat blockCharFmt = block.charFormat(); + diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat(); + } + + diff.clearProperty(QTextFormat::BackgroundBrush); + if (format.hasProperty(QTextFormat::BackgroundBrush)) { + QBrush bg = format.background(); + if (bg.style() != Qt::NoBrush) + diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush)); + } + + if (!diff.properties().isEmpty()) + emitCharFormatStyle(diff); + + html += QLatin1Char('"'); + +} + +void QTextHtmlExporter::emitBlock(const QTextBlock &block) +{ + if (block.begin().atEnd()) { + // ### HACK, remove once QTextFrame::Iterator is fixed + int p = block.position(); + if (p > 0) + --p; + QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p); + QChar ch = doc->docHandle()->buffer().at(frag->stringPosition); + if (ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame) + return; + } + + html += QLatin1Char('\n'); + + // save and later restore, in case we 'change' the default format by + // emitting block char format information + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextList *list = block.textList(); + if (list) { + if (list->itemNumber(block) == 0) { // first item? emit
    or appropriate + const QTextListFormat format = list->format(); + const int style = format.style(); + switch (style) { + case QTextListFormat::ListDecimal: html += QLatin1String(""); + } + + html += QLatin1String(""); + return; + } + + const bool pre = blockFormat.nonBreakableLines(); + if (pre) { + if (list) + html += QLatin1Char('>'); + html += QLatin1String("'); + + QTextBlock::Iterator it = block.begin(); + if (fragmentMarkers && !it.atEnd() && block == doc->begin()) + html += QLatin1String(""); + + for (; !it.atEnd(); ++it) + emitFragment(it.fragment()); + + if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length()) + html += QLatin1String(""); + + if (pre) + html += QLatin1String(""); + else if (list) + html += QLatin1String(""); + else + html += QLatin1String("

    "); + + if (list) { + if (list->itemNumber(block) == list->count() - 1) { // last item? close list + if (isOrderedList(list->format().style())) + html += QLatin1String(""); + else + html += QLatin1String("
"); + } + } + + defaultCharFormat = oldDefaultCharFormat; +} + +extern bool qHasPixmapTexture(const QBrush& brush); + +QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap) +{ + QString url; + if (!doc) + return url; + + if (QTextDocument *parent = qobject_cast(doc->parent())) + return findUrlForImage(parent, cacheKey, isPixmap); + + if (doc && doc->docHandle()) { + QTextDocumentPrivate *priv = doc->docHandle(); + QMap::const_iterator it = priv->cachedResources.constBegin(); + for (; it != priv->cachedResources.constEnd(); ++it) { + + const QVariant &v = it.value(); + if (v.type() == QVariant::Image && !isPixmap) { + if (qvariant_cast(v).cacheKey() == cacheKey) + break; + } + + if (v.type() == QVariant::Pixmap && isPixmap) { + if (qvariant_cast(v).cacheKey() == cacheKey) + break; + } + } + + if (it != priv->cachedResources.constEnd()) + url = it.key().toString(); + } + + return url; +} + +void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv) +{ + if (!priv) + return; + + cachedResources.unite(priv->cachedResources); +} + +void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format) +{ + if (format.hasProperty(QTextFormat::BackgroundImageUrl)) { + QString url = format.property(QTextFormat::BackgroundImageUrl).toString(); + emitAttribute("background", url); + } else { + const QBrush &brush = format.background(); + if (brush.style() == Qt::SolidPattern) { + emitAttribute("bgcolor", brush.color().name()); + } else if (brush.style() == Qt::TexturePattern) { + const bool isPixmap = qHasPixmapTexture(brush); + const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey(); + + const QString url = findUrlForImage(doc, cacheKey, isPixmap); + + if (!url.isEmpty()) + emitAttribute("background", url); + } + } +} + +void QTextHtmlExporter::emitTable(const QTextTable *table) +{ + QTextTableFormat format = table->format(); + + html += QLatin1String("\n'); + + const int rows = table->rows(); + const int columns = table->columns(); + + QVector columnWidths = format.columnWidthConstraints(); + if (columnWidths.isEmpty()) { + columnWidths.resize(columns); + columnWidths.fill(QTextLength()); + } + Q_ASSERT(columnWidths.count() == columns); + + QVarLengthArray widthEmittedForColumn(columns); + for (int i = 0; i < columns; ++i) + widthEmittedForColumn[i] = false; + + const int headerRowCount = qMin(format.headerRowCount(), rows); + if (headerRowCount > 0) + html += QLatin1String(""); + + for (int row = 0; row < rows; ++row) { + html += QLatin1String("\n"); + + for (int col = 0; col < columns; ++col) { + const QTextTableCell cell = table->cellAt(row, col); + + // for col/rowspans + if (cell.row() != row) + continue; + + if (cell.column() != col) + continue; + + html += QLatin1String("\n 1) + emitAttribute("colspan", QString::number(cell.columnSpan())); + + if (cell.rowSpan() > 1) + emitAttribute("rowspan", QString::number(cell.rowSpan())); + + const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); + emitBackgroundAttribute(cellFormat); + + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment(); + + QString styleString; + if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) { + styleString += QLatin1String(" vertical-align:"); + switch (valign) { + case QTextCharFormat::AlignMiddle: + styleString += QLatin1String("middle"); + break; + case QTextCharFormat::AlignTop: + styleString += QLatin1String("top"); + break; + case QTextCharFormat::AlignBottom: + styleString += QLatin1String("bottom"); + break; + default: + break; + } + styleString += QLatin1Char(';'); + + QTextCharFormat temp; + temp.setVerticalAlignment(valign); + defaultCharFormat.merge(temp); + } + + if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding)) + styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding)) + styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding)) + styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding)) + styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';'); + + if (!styleString.isEmpty()) + html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"'); + + html += QLatin1Char('>'); + + emitFrame(cell.begin()); + + html += QLatin1String(""); + + defaultCharFormat = oldDefaultCharFormat; + } + + html += QLatin1String(""); + if (headerRowCount > 0 && row == headerRowCount - 1) + html += QLatin1String(""); + } + + html += QLatin1String(""); +} + +void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt) +{ + if (!frameIt.atEnd()) { + QTextFrame::Iterator next = frameIt; + ++next; + if (next.atEnd() + && frameIt.currentFrame() == 0 + && frameIt.parentFrame() != doc->rootFrame() + && frameIt.currentBlock().begin().atEnd()) + return; + } + + for (QTextFrame::Iterator it = frameIt; + !it.atEnd(); ++it) { + if (QTextFrame *f = it.currentFrame()) { + if (QTextTable *table = qobject_cast(f)) { + emitTable(table); + } else { + emitTextFrame(f); + } + } else if (it.currentBlock().isValid()) { + emitBlock(it.currentBlock()); + } + } +} + +void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) +{ + FrameType frameType = f->parentFrame() ? TextFrame : RootFrame; + + html += QLatin1String("\nframeFormat(); + + if (format.hasProperty(QTextFormat::FrameBorder)) + emitAttribute("border", QString::number(format.border())); + + emitFrameStyle(format, frameType); + + emitTextLength("width", format.width()); + emitTextLength("height", format.height()); + + // root frame's bcolor goes in the tag + if (frameType != RootFrame) + emitBackgroundAttribute(format); + + html += QLatin1Char('>'); + html += QLatin1String("\n\n"); + emitFrame(f->begin()); + html += QLatin1String(""); +} + +void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType) +{ + QLatin1String styleAttribute(" style=\""); + html += styleAttribute; + const int originalHtmlLength = html.length(); + + if (frameType == TextFrame) + html += QLatin1String("-qt-table-type: frame;"); + else if (frameType == RootFrame) + html += QLatin1String("-qt-table-type: root;"); + + const QTextFrameFormat defaultFormat; + + emitFloatStyle(format.position(), OmitStyleTag); + emitPageBreakPolicy(format.pageBreakPolicy()); + + if (format.borderBrush() != defaultFormat.borderBrush()) { + html += QLatin1String(" border-color:"); + html += format.borderBrush().color().name(); + html += QLatin1Char(';'); + } + + if (format.borderStyle() != defaultFormat.borderStyle()) + emitBorderStyle(format.borderStyle()); + + if (format.hasProperty(QTextFormat::FrameMargin) + || format.hasProperty(QTextFormat::FrameLeftMargin) + || format.hasProperty(QTextFormat::FrameRightMargin) + || format.hasProperty(QTextFormat::FrameTopMargin) + || format.hasProperty(QTextFormat::FrameBottomMargin)) + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + if (html.length() == originalHtmlLength) // nothing emitted? + html.chop(qstrlen(styleAttribute.latin1())); + else + html += QLatin1Char('\"'); +} + +/*! + Returns a string containing an HTML representation of the document. + + The \a encoding parameter specifies the value for the charset attribute + in the html header. For example if 'utf-8' is specified then the + beginning of the generated html will look like this: + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1 + + If no encoding is specified then no such meta information is generated. + + If you later on convert the returned html string into a byte array for + transmission over a network or when saving to disk you should specify + the encoding you're going to use for the conversion to a byte array here. + + \sa {Supported HTML Subset} +*/ +#ifndef QT_NO_TEXTHTMLPARSER +QString QTextDocument::toHtml(const QByteArray &encoding) const +{ + return QTextHtmlExporter(this).toHtml(encoding); +} +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Returns a vector of text formats for all the formats used in the document. +*/ +QVector QTextDocument::allFormats() const +{ + Q_D(const QTextDocument); + return d->formatCollection()->formats; +} + + +/*! + \internal + + So that not all classes have to be friends of each other... +*/ +QTextDocumentPrivate *QTextDocument::docHandle() const +{ + Q_D(const QTextDocument); + return const_cast(d); +} + +/*! + \since 4.4 + \fn QTextDocument::undoCommandAdded() + + This signal is emitted every time a new level of undo is added to the QTextDocument. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h new file mode 100644 index 0000000000..f87ccc91e8 --- /dev/null +++ b/src/gui/text/qtextdocument.h @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENT_H +#define QTEXTDOCUMENT_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextFormatCollection; +class QTextListFormat; +class QRect; +class QPainter; +class QPrinter; +class QAbstractTextDocumentLayout; +class QPoint; +class QTextCursor; +class QTextObject; +class QTextFormat; +class QTextFrame; +class QTextBlock; +class QTextCodec; +class QUrl; +class QVariant; +class QRectF; +class QTextOption; + +template class QVector; + +namespace Qt +{ + enum HitTestAccuracy { ExactHit, FuzzyHit }; + enum WhiteSpaceMode { + WhiteSpaceNormal, + WhiteSpacePre, + WhiteSpaceNoWrap, + WhiteSpaceModeUndefined = -1 + }; + + Q_GUI_EXPORT bool mightBeRichText(const QString&); + Q_GUI_EXPORT QString escape(const QString& plain); + Q_GUI_EXPORT QString convertFromPlainText(const QString &plain, WhiteSpaceMode mode = WhiteSpacePre); + +#ifndef QT_NO_TEXTCODEC + Q_GUI_EXPORT QTextCodec *codecForHtml(const QByteArray &ba); +#endif +} + +class Q_GUI_EXPORT QAbstractUndoItem +{ +public: + virtual ~QAbstractUndoItem() = 0; + virtual void undo() = 0; + virtual void redo() = 0; +}; + +inline QAbstractUndoItem::~QAbstractUndoItem() +{ +} + +class QTextDocumentPrivate; + +class Q_GUI_EXPORT QTextDocument : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled) + Q_PROPERTY(bool modified READ isModified WRITE setModified DESIGNABLE false) + Q_PROPERTY(QSizeF pageSize READ pageSize WRITE setPageSize) + Q_PROPERTY(QFont defaultFont READ defaultFont WRITE setDefaultFont) + Q_PROPERTY(bool useDesignMetrics READ useDesignMetrics WRITE setUseDesignMetrics) + Q_PROPERTY(QSizeF size READ size) + Q_PROPERTY(qreal textWidth READ textWidth WRITE setTextWidth) + Q_PROPERTY(int blockCount READ blockCount) + Q_PROPERTY(qreal indentWidth READ indentWidth WRITE setIndentWidth) +#ifndef QT_NO_CSSPARSER + Q_PROPERTY(QString defaultStyleSheet READ defaultStyleSheet WRITE setDefaultStyleSheet) +#endif + Q_PROPERTY(int maximumBlockCount READ maximumBlockCount WRITE setMaximumBlockCount) + Q_PROPERTY(qreal documentMargin READ documentMargin WRITE setDocumentMargin) + QDOC_PROPERTY(QTextOption defaultTextOption READ defaultTextOption WRITE setDefaultTextOption) + +public: + explicit QTextDocument(QObject *parent = 0); + explicit QTextDocument(const QString &text, QObject *parent = 0); + ~QTextDocument(); + + QTextDocument *clone(QObject *parent = 0) const; + + bool isEmpty() const; + virtual void clear(); + + void setUndoRedoEnabled(bool enable); + bool isUndoRedoEnabled() const; + + bool isUndoAvailable() const; + bool isRedoAvailable() const; + + int availableUndoSteps() const; + int availableRedoSteps() const; + + int revision() const; + + void setDocumentLayout(QAbstractTextDocumentLayout *layout); + QAbstractTextDocumentLayout *documentLayout() const; + + enum MetaInformation { + DocumentTitle, + DocumentUrl + }; + void setMetaInformation(MetaInformation info, const QString &); + QString metaInformation(MetaInformation info) const; + +#ifndef QT_NO_TEXTHTMLPARSER + QString toHtml(const QByteArray &encoding = QByteArray()) const; + void setHtml(const QString &html); +#endif + + QString toPlainText() const; + void setPlainText(const QString &text); + + QChar characterAt(int pos) const; + + enum FindFlag + { + FindBackward = 0x00001, + FindCaseSensitively = 0x00002, + FindWholeWords = 0x00004 + }; + Q_DECLARE_FLAGS(FindFlags, FindFlag) + + QTextCursor find(const QString &subString, int from = 0, FindFlags options = 0) const; + QTextCursor find(const QString &subString, const QTextCursor &from, FindFlags options = 0) const; + + QTextCursor find(const QRegExp &expr, int from = 0, FindFlags options = 0) const; + QTextCursor find(const QRegExp &expr, const QTextCursor &from, FindFlags options = 0) const; + + QTextFrame *frameAt(int pos) const; + QTextFrame *rootFrame() const; + + QTextObject *object(int objectIndex) const; + QTextObject *objectForFormat(const QTextFormat &) const; + + QTextBlock findBlock(int pos) const; + QTextBlock findBlockByNumber(int blockNumber) const; + QTextBlock findBlockByLineNumber(int blockNumber) const; + QTextBlock begin() const; + QTextBlock end() const; + + QTextBlock firstBlock() const; + QTextBlock lastBlock() const; + + void setPageSize(const QSizeF &size); + QSizeF pageSize() const; + + void setDefaultFont(const QFont &font); + QFont defaultFont() const; + + int pageCount() const; + + bool isModified() const; + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + + enum ResourceType { + HtmlResource = 1, + ImageResource = 2, + StyleSheetResource = 3, + + UserResource = 100 + }; + + QVariant resource(int type, const QUrl &name) const; + void addResource(int type, const QUrl &name, const QVariant &resource); + + QVector allFormats() const; + + void markContentsDirty(int from, int length); + + void setUseDesignMetrics(bool b); + bool useDesignMetrics() const; + + void drawContents(QPainter *painter, const QRectF &rect = QRectF()); + + void setTextWidth(qreal width); + qreal textWidth() const; + + qreal idealWidth() const; + + qreal indentWidth() const; + void setIndentWidth(qreal width); + + qreal documentMargin() const; + void setDocumentMargin(qreal margin); + + void adjustSize(); + QSizeF size() const; + + int blockCount() const; + int lineCount() const; + int characterCount() const; + +#ifndef QT_NO_CSSPARSER + void setDefaultStyleSheet(const QString &sheet); + QString defaultStyleSheet() const; +#endif + + void undo(QTextCursor *cursor); + void redo(QTextCursor *cursor); + + enum Stacks { + UndoStack = 0x01, + RedoStack = 0x02, + UndoAndRedoStacks = UndoStack | RedoStack + }; + void clearUndoRedoStacks(Stacks historyToClear = UndoAndRedoStacks); + + int maximumBlockCount() const; + void setMaximumBlockCount(int maximum); + + QTextOption defaultTextOption() const; + void setDefaultTextOption(const QTextOption &option); + +Q_SIGNALS: + void contentsChange(int from, int charsRemoves, int charsAdded); + void contentsChanged(); + void undoAvailable(bool); + void redoAvailable(bool); + void undoCommandAdded(); + void modificationChanged(bool m); + void cursorPositionChanged(const QTextCursor &cursor); + void blockCountChanged(int newBlockCount); + + void documentLayoutChanged(); + +public Q_SLOTS: + void undo(); + void redo(); + void appendUndoItem(QAbstractUndoItem *); + void setModified(bool m = true); + +protected: + virtual QTextObject *createObject(const QTextFormat &f); + virtual QVariant loadResource(int type, const QUrl &name); + + QTextDocument(QTextDocumentPrivate &dd, QObject *parent); +public: + QTextDocumentPrivate *docHandle() const; +private: + Q_DISABLE_COPY(QTextDocument) + Q_DECLARE_PRIVATE(QTextDocument) + friend class QTextObjectPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextDocument::FindFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTDOCUMENT_H diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp new file mode 100644 index 0000000000..a997720c12 --- /dev/null +++ b/src/gui/text/qtextdocument_p.cpp @@ -0,0 +1,1724 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qtextdocument_p.h" +#include "qtextdocument.h" +#include +#include "qtextformat_p.h" +#include "qtextobject_p.h" +#include "qtextcursor.h" +#include "qtextimagehandler_p.h" +#include "qtextcursor_p.h" +#include "qtextdocumentlayout_p.h" +#include "qtexttable.h" +#include "qtextengine_p.h" + +#include + +QT_BEGIN_NAMESPACE + +#define PMDEBUG if(0) qDebug + +// The VxWorks DIAB compiler crashes when initializing the anonymouse union with { a7 } +#if !defined(Q_CC_DIAB) +# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \ + QTextUndoCommand c = { a1, a2, 0, 0, quint8(a3), a4, a5, a6, { a7 }, a8 } +#else +# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \ + QTextUndoCommand c = { a1, a2, 0, 0, a3, a4, a5, a6 }; c.blockFormat = a7; c.revision = a8 +#endif + +/* + Structure of a document: + + DOCUMENT :== FRAME_CONTENTS + FRAME :== START_OF_FRAME FRAME_CONTENTS END_OF_FRAME + FRAME_CONTENTS = LIST_OF_BLOCKS ((FRAME | TABLE) LIST_OF_BLOCKS)* + TABLE :== (START_OF_FRAME TABLE_CELL)+ END_OF_FRAME + TABLE_CELL = FRAME_CONTENTS + LIST_OF_BLOCKS :== (BLOCK END_OF_PARA)* BLOCK + BLOCK :== (FRAGMENT)* + FRAGMENT :== String of characters + + END_OF_PARA :== 0x2029 # Paragraph separator in Unicode + START_OF_FRAME :== 0xfdd0 + END_OF_FRAME := 0xfdd1 + + Note also that LIST_OF_BLOCKS can be empty. Nevertheless, there is + at least one valid cursor position there where you could start + typing. The block format is in this case determined by the last + END_OF_PARA/START_OF_FRAME/END_OF_FRAME (see below). + + Lists are not in here, as they are treated specially. A list is just + a collection of (not necessarily connected) blocks, that share the + same objectIndex() in the format that refers to the list format and + object. + + The above does not clearly note where formats are. Here's + how it looks currently: + + FRAGMENT: one charFormat associated + + END_OF_PARA: one charFormat, and a blockFormat for the _next_ block. + + START_OF_FRAME: one char format, and a blockFormat (for the next + block). The format associated with the objectIndex() of the + charFormat decides whether this is a frame or table and its + properties + + END_OF_FRAME: one charFormat and a blockFormat (for the next + block). The object() of the charFormat is the same as for the + corresponding START_OF_BLOCK. + + + The document is independent of the layout with certain restrictions: + + * Cursor movement (esp. up and down) depend on the layout. + * You cannot have more than one layout, as the layout data of QTextObjects + is stored in the text object itself. + +*/ + +void QTextBlockData::invalidate() const +{ + if (layout) + layout->engine()->invalidate(); +} + +static bool isValidBlockSeparator(const QChar &ch) +{ + return ch == QChar::ParagraphSeparator + || ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame; +} + +#ifndef QT_NO_DEBUG +static bool noBlockInString(const QString &str) +{ + return !str.contains(QChar::ParagraphSeparator) + && !str.contains(QTextBeginningOfFrame) + && !str.contains(QTextEndOfFrame); +} +#endif + +bool QTextUndoCommand::tryMerge(const QTextUndoCommand &other) +{ + if (command != other.command) + return false; + + if (command == Inserted + && (pos + length == other.pos) + && (strPos + length == other.strPos) + && format == other.format) { + + length += other.length; + return true; + } + + // removal to the 'right' using 'Delete' key + if (command == Removed + && pos == other.pos + && (strPos + length == other.strPos) + && format == other.format) { + + length += other.length; + return true; + } + + // removal to the 'left' using 'Backspace' + if (command == Removed + && (other.pos + other.length == pos) + && (other.strPos + other.length == strPos) + && (format == other.format)) { + + int l = length; + (*this) = other; + + length += l; + return true; + } + + return false; +} + +QTextDocumentPrivate::QTextDocumentPrivate() + : wasUndoAvailable(false), + wasRedoAvailable(false), + docChangeOldLength(0), + docChangeLength(0), + framesDirty(true), + rtFrame(0), + initialBlockCharFormatIndex(-1) // set correctly later in init() +{ + editBlock = 0; + editBlockCursorPosition = -1; + docChangeFrom = -1; + + undoState = 0; + revision = -1; // init() inserts a block, bringing it to 0 + + lout = 0; + + modified = false; + modifiedState = 0; + + undoEnabled = true; + inContentsChange = false; + blockCursorAdjustment = false; + + defaultTextOption.setTabStop(80); // same as in qtextengine.cpp + defaultTextOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + indentWidth = 40; + documentMargin = 4; + + maximumBlockCount = 0; + needsEnsureMaximumBlockCount = false; + unreachableCharacterCount = 0; + lastBlockCount = 0; +} + +void QTextDocumentPrivate::init() +{ + framesDirty = false; + + bool undoState = undoEnabled; + undoEnabled = false; + initialBlockCharFormatIndex = formats.indexForFormat(QTextCharFormat()); + insertBlock(0, formats.indexForFormat(QTextBlockFormat()), formats.indexForFormat(QTextCharFormat())); + undoEnabled = undoState; + modified = false; + modifiedState = 0; +} + +void QTextDocumentPrivate::clear() +{ + Q_Q(QTextDocument); + + foreach (QTextCursorPrivate *curs, cursors) { + curs->setPosition(0); + curs->currentCharFormat = -1; + curs->anchor = 0; + curs->adjusted_anchor = 0; + } + + QListoldCursors = cursors; + QT_TRY{ + cursors.clear(); + + QMap::Iterator objectIt = objects.begin(); + while (objectIt != objects.end()) { + if (*objectIt != rtFrame) { + delete *objectIt; + objectIt = objects.erase(objectIt); + } else { + ++objectIt; + } + } + // also clear out the remaining root frame pointer + // (we're going to delete the object further down) + objects.clear(); + + title.clear(); + clearUndoRedoStacks(QTextDocument::UndoAndRedoStacks); + text = QString(); + unreachableCharacterCount = 0; + modifiedState = 0; + modified = false; + formats = QTextFormatCollection(); + int len = fragments.length(); + fragments.clear(); + blocks.clear(); + cachedResources.clear(); + delete rtFrame; + rtFrame = 0; + init(); + cursors = oldCursors; + inContentsChange = true; + q->contentsChange(0, len, 0); + inContentsChange = false; + if (lout) + lout->documentChanged(0, len, 0); + } QT_CATCH(...) { + cursors = oldCursors; // at least recover the cursors + QT_RETHROW; + } +} + +QTextDocumentPrivate::~QTextDocumentPrivate() +{ + foreach (QTextCursorPrivate *curs, cursors) + curs->priv = 0; + cursors.clear(); + undoState = 0; + undoEnabled = true; + clearUndoRedoStacks(QTextDocument::RedoStack); +} + +void QTextDocumentPrivate::setLayout(QAbstractTextDocumentLayout *layout) +{ + Q_Q(QTextDocument); + if (lout == layout) + return; + const bool firstLayout = !lout; + delete lout; + lout = layout; + + if (!firstLayout) + for (BlockMap::Iterator it = blocks.begin(); !it.atEnd(); ++it) + it->free(); + + emit q->documentLayoutChanged(); + inContentsChange = true; + emit q->contentsChange(0, 0, length()); + inContentsChange = false; + if (lout) + lout->documentChanged(0, 0, length()); +} + + +void QTextDocumentPrivate::insert_string(int pos, uint strPos, uint length, int format, QTextUndoCommand::Operation op) +{ + // ##### optimize when only appending to the fragment! + Q_ASSERT(noBlockInString(text.mid(strPos, length))); + + split(pos); + uint x = fragments.insert_single(pos, length); + QTextFragmentData *X = fragments.fragment(x); + X->format = format; + X->stringPosition = strPos; + uint w = fragments.previous(x); + if (w) + unite(w); + + int b = blocks.findNode(pos); + blocks.setSize(b, blocks.size(b)+length); + + Q_ASSERT(blocks.length() == fragments.length()); + + QTextFrame *frame = qobject_cast(objectForFormat(format)); + if (frame) { + frame->d_func()->fragmentAdded(text.at(strPos), x); + framesDirty = true; + } + + adjustDocumentChangesAndCursors(pos, length, op); +} + +int QTextDocumentPrivate::insert_block(int pos, uint strPos, int format, int blockFormat, QTextUndoCommand::Operation op, int command) +{ + split(pos); + uint x = fragments.insert_single(pos, 1); + QTextFragmentData *X = fragments.fragment(x); + X->format = format; + X->stringPosition = strPos; + // no need trying to unite, since paragraph separators are always in a fragment of their own + + Q_ASSERT(isValidBlockSeparator(text.at(strPos))); + Q_ASSERT(blocks.length()+1 == fragments.length()); + + int block_pos = pos; + if (blocks.length() && command == QTextUndoCommand::BlockRemoved) + ++block_pos; + int size = 1; + int n = blocks.findNode(block_pos); + int key = n ? blocks.position(n) : blocks.length(); + + Q_ASSERT(n || (!n && block_pos == blocks.length())); + if (key != block_pos) { + Q_ASSERT(key < block_pos); + int oldSize = blocks.size(n); + blocks.setSize(n, block_pos-key); + size += oldSize - (block_pos-key); + } + int b = blocks.insert_single(block_pos, size); + QTextBlockData *B = blocks.fragment(b); + B->format = blockFormat; + + Q_ASSERT(blocks.length() == fragments.length()); + + QTextBlockGroup *group = qobject_cast(objectForFormat(blockFormat)); + if (group) + group->blockInserted(QTextBlock(this, b)); + + QTextFrame *frame = qobject_cast(objectForFormat(formats.format(format))); + if (frame) { + frame->d_func()->fragmentAdded(text.at(strPos), x); + framesDirty = true; + } + + adjustDocumentChangesAndCursors(pos, 1, op); + return x; +} + +int QTextDocumentPrivate::insertBlock(const QChar &blockSeparator, + int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op) +{ + Q_ASSERT(formats.format(blockFormat).isBlockFormat()); + Q_ASSERT(formats.format(charFormat).isCharFormat()); + Q_ASSERT(pos >= 0 && (pos < fragments.length() || (pos == 0 && fragments.length() == 0))); + Q_ASSERT(isValidBlockSeparator(blockSeparator)); + + beginEditBlock(); + + int strPos = text.length(); + text.append(blockSeparator); + + int ob = blocks.findNode(pos); + bool atBlockEnd = true; + bool atBlockStart = true; + int oldRevision = 0; + if (ob) { + atBlockEnd = (pos - blocks.position(ob) == blocks.size(ob)-1); + atBlockStart = ((int)blocks.position(ob) == pos); + oldRevision = blocks.fragment(ob)->revision; + } + + const int fragment = insert_block(pos, strPos, charFormat, blockFormat, op, QTextUndoCommand::BlockRemoved); + + Q_ASSERT(blocks.length() == fragments.length()); + + int b = blocks.findNode(pos); + QTextBlockData *B = blocks.fragment(b); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockInserted, (editBlock != 0), + op, charFormat, strPos, pos, blockFormat, + B->revision); + + appendUndoItem(c); + Q_ASSERT(undoState == undoStack.size()); + + // update revision numbers of the modified blocks. + B->revision = (atBlockEnd && !atBlockStart)? oldRevision : revision; + b = blocks.next(b); + if (b) { + B = blocks.fragment(b); + B->revision = atBlockStart ? oldRevision : revision; + } + + if (formats.charFormat(charFormat).objectIndex() == -1) + needsEnsureMaximumBlockCount = true; + + endEditBlock(); + return fragment; +} + +int QTextDocumentPrivate::insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op) +{ + return insertBlock(QChar::ParagraphSeparator, pos, blockFormat, charFormat, op); +} + +void QTextDocumentPrivate::insert(int pos, int strPos, int strLength, int format) +{ + if (strLength <= 0) + return; + + Q_ASSERT(pos >= 0 && pos < fragments.length()); + Q_ASSERT(formats.format(format).isCharFormat()); + + insert_string(pos, strPos, strLength, format, QTextUndoCommand::MoveCursor); + if (undoEnabled) { + int b = blocks.findNode(pos); + QTextBlockData *B = blocks.fragment(b); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Inserted, (editBlock != 0), + QTextUndoCommand::MoveCursor, format, strPos, pos, strLength, + B->revision); + appendUndoItem(c); + B->revision = revision; + Q_ASSERT(undoState == undoStack.size()); + } + finishEdit(); +} + +void QTextDocumentPrivate::insert(int pos, const QString &str, int format) +{ + if (str.size() == 0) + return; + + Q_ASSERT(noBlockInString(str)); + + int strPos = text.length(); + text.append(str); + insert(pos, strPos, str.length(), format); +} + +int QTextDocumentPrivate::remove_string(int pos, uint length, QTextUndoCommand::Operation op) +{ + Q_ASSERT(pos >= 0); + Q_ASSERT(blocks.length() == fragments.length()); + Q_ASSERT(blocks.length() >= pos+(int)length); + + int b = blocks.findNode(pos); + uint x = fragments.findNode(pos); + + Q_ASSERT(blocks.size(b) > length); + Q_ASSERT(x && fragments.position(x) == (uint)pos && fragments.size(x) == length); + Q_ASSERT(noBlockInString(text.mid(fragments.fragment(x)->stringPosition, length))); + + blocks.setSize(b, blocks.size(b)-length); + + QTextFrame *frame = qobject_cast(objectForFormat(fragments.fragment(x)->format)); + if (frame) { + frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x); + framesDirty = true; + } + + const int w = fragments.erase_single(x); + + if (!undoEnabled) + unreachableCharacterCount += length; + + adjustDocumentChangesAndCursors(pos, -int(length), op); + + return w; +} + +int QTextDocumentPrivate::remove_block(int pos, int *blockFormat, int command, QTextUndoCommand::Operation op) +{ + Q_ASSERT(pos >= 0); + Q_ASSERT(blocks.length() == fragments.length()); + Q_ASSERT(blocks.length() > pos); + + int b = blocks.findNode(pos); + uint x = fragments.findNode(pos); + + Q_ASSERT(x && (int)fragments.position(x) == pos); + Q_ASSERT(fragments.size(x) == 1); + Q_ASSERT(isValidBlockSeparator(text.at(fragments.fragment(x)->stringPosition))); + Q_ASSERT(b); + + if (blocks.size(b) == 1 && command == QTextUndoCommand::BlockAdded) { + Q_ASSERT((int)blocks.position(b) == pos); +// qDebug("removing empty block"); + // empty block remove the block itself + } else { + // non empty block, merge with next one into this block +// qDebug("merging block with next"); + int n = blocks.next(b); + Q_ASSERT((int)blocks.position(n) == pos + 1); + blocks.setSize(b, blocks.size(b) + blocks.size(n) - 1); + b = n; + } + *blockFormat = blocks.fragment(b)->format; + + QTextBlockGroup *group = qobject_cast(objectForFormat(blocks.fragment(b)->format)); + if (group) + group->blockRemoved(QTextBlock(this, b)); + + QTextFrame *frame = qobject_cast(objectForFormat(fragments.fragment(x)->format)); + if (frame) { + frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x); + framesDirty = true; + } + + blocks.erase_single(b); + const int w = fragments.erase_single(x); + + adjustDocumentChangesAndCursors(pos, -1, op); + + return w; +} + +#if !defined(QT_NO_DEBUG) +static bool isAncestorFrame(QTextFrame *possibleAncestor, QTextFrame *child) +{ + while (child) { + if (child == possibleAncestor) + return true; + child = child->parentFrame(); + } + return false; +} +#endif + +void QTextDocumentPrivate::move(int pos, int to, int length, QTextUndoCommand::Operation op) +{ + Q_ASSERT(to <= fragments.length() && to <= pos); + Q_ASSERT(pos >= 0 && pos+length <= fragments.length()); + Q_ASSERT(blocks.length() == fragments.length()); + + if (pos == to) + return; + + const bool needsInsert = to != -1; + +#if !defined(QT_NO_DEBUG) + const bool startAndEndInSameFrame = (frameAt(pos) == frameAt(pos + length - 1)); + + const bool endIsEndOfChildFrame = (isAncestorFrame(frameAt(pos), frameAt(pos + length - 1)) + && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame); + + const bool startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent + = (text.at(find(pos)->stringPosition) == QTextBeginningOfFrame + && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame + && frameAt(pos)->parentFrame() == frameAt(pos + length - 1)->parentFrame()); + + const bool isFirstTableCell = (qobject_cast(frameAt(pos + length - 1)) + && frameAt(pos + length - 1)->parentFrame() == frameAt(pos)); + + Q_ASSERT(startAndEndInSameFrame || endIsEndOfChildFrame || startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent || isFirstTableCell); +#endif + + split(pos); + split(pos+length); + + uint dst = needsInsert ? fragments.findNode(to) : 0; + uint dstKey = needsInsert ? fragments.position(dst) : 0; + + uint x = fragments.findNode(pos); + uint end = fragments.findNode(pos+length); + + uint w = 0; + while (x != end) { + uint n = fragments.next(x); + + uint key = fragments.position(x); + uint b = blocks.findNode(key+1); + QTextBlockData *B = blocks.fragment(b); + int blockRevision = B->revision; + + QTextFragmentData *X = fragments.fragment(x); + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Removed, (editBlock != 0), + op, X->format, X->stringPosition, key, X->size_array[0], + blockRevision); + QT_INIT_TEXTUNDOCOMMAND(cInsert, QTextUndoCommand::Inserted, (editBlock != 0), + op, X->format, X->stringPosition, dstKey, X->size_array[0], + blockRevision); + + if (key+1 != blocks.position(b)) { +// qDebug("remove_string from %d length %d", key, X->size_array[0]); + Q_ASSERT(noBlockInString(text.mid(X->stringPosition, X->size_array[0]))); + w = remove_string(key, X->size_array[0], op); + + if (needsInsert) { + insert_string(dstKey, X->stringPosition, X->size_array[0], X->format, op); + dstKey += X->size_array[0]; + } + } else { +// qDebug("remove_block at %d", key); + Q_ASSERT(X->size_array[0] == 1 && isValidBlockSeparator(text.at(X->stringPosition))); + b = blocks.previous(b); + B = 0; + c.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockDeleted : QTextUndoCommand::BlockRemoved; + w = remove_block(key, &c.blockFormat, QTextUndoCommand::BlockAdded, op); + + if (needsInsert) { + insert_block(dstKey++, X->stringPosition, X->format, c.blockFormat, op, QTextUndoCommand::BlockRemoved); + cInsert.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockAdded : QTextUndoCommand::BlockInserted; + cInsert.blockFormat = c.blockFormat; + } + } + appendUndoItem(c); + if (B) + B->revision = revision; + x = n; + + if (needsInsert) + appendUndoItem(cInsert); + } + if (w) + unite(w); + + Q_ASSERT(blocks.length() == fragments.length()); + + if (!blockCursorAdjustment) + finishEdit(); +} + +void QTextDocumentPrivate::remove(int pos, int length, QTextUndoCommand::Operation op) +{ + if (length == 0) + return; + blockCursorAdjustment = true; + move(pos, -1, length, op); + blockCursorAdjustment = false; + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->adjustPosition(pos, -length, op) == QTextCursorPrivate::CursorMoved) { + curs->changed = true; + } + } + finishEdit(); +} + +void QTextDocumentPrivate::setCharFormat(int pos, int length, const QTextCharFormat &newFormat, FormatChangeMode mode) +{ + beginEditBlock(); + + Q_ASSERT(newFormat.isValid()); + + int newFormatIdx = -1; + if (mode == SetFormatAndPreserveObjectIndices) { + QTextCharFormat cleanFormat = newFormat; + cleanFormat.clearProperty(QTextFormat::ObjectIndex); + newFormatIdx = formats.indexForFormat(cleanFormat); + } else if (mode == SetFormat) { + newFormatIdx = formats.indexForFormat(newFormat); + } + + if (pos == -1) { + if (mode == MergeFormat) { + QTextFormat format = formats.format(initialBlockCharFormatIndex); + format.merge(newFormat); + initialBlockCharFormatIndex = formats.indexForFormat(format); + } else if (mode == SetFormatAndPreserveObjectIndices + && formats.format(initialBlockCharFormatIndex).objectIndex() != -1) { + QTextCharFormat f = newFormat; + f.setObjectIndex(formats.format(initialBlockCharFormatIndex).objectIndex()); + initialBlockCharFormatIndex = formats.indexForFormat(f); + } else { + initialBlockCharFormatIndex = newFormatIdx; + } + + ++pos; + --length; + } + + const int startPos = pos; + const int endPos = pos + length; + + split(startPos); + split(endPos); + + while (pos < endPos) { + FragmentMap::Iterator it = fragments.find(pos); + Q_ASSERT(!it.atEnd()); + + QTextFragmentData *fragment = it.value(); + + Q_ASSERT(formats.format(fragment->format).type() == QTextFormat::CharFormat); + + int offset = pos - it.position(); + int length = qMin(endPos - pos, int(fragment->size_array[0] - offset)); + int oldFormat = fragment->format; + + if (mode == MergeFormat) { + QTextFormat format = formats.format(fragment->format); + format.merge(newFormat); + fragment->format = formats.indexForFormat(format); + } else if (mode == SetFormatAndPreserveObjectIndices + && formats.format(oldFormat).objectIndex() != -1) { + QTextCharFormat f = newFormat; + f.setObjectIndex(formats.format(oldFormat).objectIndex()); + fragment->format = formats.indexForFormat(f); + } else { + fragment->format = newFormatIdx; + } + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::CharFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat, + 0, pos, length, 0); + appendUndoItem(c); + + pos += length; + Q_ASSERT(pos == (int)(it.position() + fragment->size_array[0]) || pos >= endPos); + } + + int n = fragments.findNode(startPos - 1); + if (n) + unite(n); + + n = fragments.findNode(endPos); + if (n) + unite(n); + + QTextBlock blockIt = blocksFind(startPos); + QTextBlock endIt = blocksFind(endPos); + if (endIt.isValid()) + endIt = endIt.next(); + for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next()) + QTextDocumentPrivate::block(blockIt)->invalidate(); + + documentChange(startPos, length); + + endEditBlock(); +} + +void QTextDocumentPrivate::setBlockFormat(const QTextBlock &from, const QTextBlock &to, + const QTextBlockFormat &newFormat, FormatChangeMode mode) +{ + beginEditBlock(); + + Q_ASSERT(mode != SetFormatAndPreserveObjectIndices); // only implemented for setCharFormat + + Q_ASSERT(newFormat.isValid()); + + int newFormatIdx = -1; + if (mode == SetFormat) + newFormatIdx = formats.indexForFormat(newFormat); + QTextBlockGroup *group = qobject_cast(objectForFormat(newFormat)); + + QTextBlock it = from; + QTextBlock end = to; + if (end.isValid()) + end = end.next(); + + for (; it != end; it = it.next()) { + int oldFormat = block(it)->format; + QTextBlockFormat format = formats.blockFormat(oldFormat); + QTextBlockGroup *oldGroup = qobject_cast(objectForFormat(format)); + if (mode == MergeFormat) { + format.merge(newFormat); + newFormatIdx = formats.indexForFormat(format); + group = qobject_cast(objectForFormat(format)); + } + block(it)->format = newFormatIdx; + + block(it)->invalidate(); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat, + 0, it.position(), 1, 0); + appendUndoItem(c); + + if (group != oldGroup) { + if (oldGroup) + oldGroup->blockRemoved(it); + if (group) + group->blockInserted(it); + } else if (group) { + group->blockFormatChanged(it); + } + } + + documentChange(from.position(), to.position() + to.length() - from.position()); + + endEditBlock(); +} + + +bool QTextDocumentPrivate::split(int pos) +{ + uint x = fragments.findNode(pos); + if (x) { + int k = fragments.position(x); +// qDebug("found fragment with key %d, size_left=%d, size=%d to split at %d", +// k, (*it)->size_left[0], (*it)->size_array[0], pos); + if (k != pos) { + Q_ASSERT(k <= pos); + // need to resize the first fragment and add a new one + QTextFragmentData *X = fragments.fragment(x); + int oldsize = X->size_array[0]; + fragments.setSize(x, pos-k); + uint n = fragments.insert_single(pos, oldsize-(pos-k)); + X = fragments.fragment(x); + QTextFragmentData *N = fragments.fragment(n); + N->stringPosition = X->stringPosition + pos-k; + N->format = X->format; + return true; + } + } + return false; +} + +bool QTextDocumentPrivate::unite(uint f) +{ + uint n = fragments.next(f); + if (!n) + return false; + + QTextFragmentData *ff = fragments.fragment(f); + QTextFragmentData *nf = fragments.fragment(n); + + if (nf->format == ff->format && (ff->stringPosition + (int)ff->size_array[0] == nf->stringPosition)) { + if (isValidBlockSeparator(text.at(ff->stringPosition)) + || isValidBlockSeparator(text.at(nf->stringPosition))) + return false; + + fragments.setSize(f, ff->size_array[0] + nf->size_array[0]); + fragments.erase_single(n); + return true; + } + return false; +} + + +int QTextDocumentPrivate::undoRedo(bool undo) +{ + PMDEBUG("%s, undoState=%d, undoStack size=%d", undo ? "undo:" : "redo:", undoState, undoStack.size()); + if (!undoEnabled || (undo && undoState == 0) || (!undo && undoState == undoStack.size())) + return -1; + + undoEnabled = false; + beginEditBlock(); + int editPos = -1; + int editLength = -1; + while (1) { + if (undo) + --undoState; + QTextUndoCommand &c = undoStack[undoState]; + int resetBlockRevision = c.pos; + + switch(c.command) { + case QTextUndoCommand::Inserted: + remove(c.pos, c.length, (QTextUndoCommand::Operation)c.operation); + PMDEBUG(" erase: from %d, length %d", c.pos, c.length); + c.command = QTextUndoCommand::Removed; + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::Removed: + PMDEBUG(" insert: format %d (from %d, length %d, strpos=%d)", c.format, c.pos, c.length, c.strPos); + insert_string(c.pos, c.strPos, c.length, c.format, (QTextUndoCommand::Operation)c.operation); + c.command = QTextUndoCommand::Inserted; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += c.length; + break; + case QTextUndoCommand::BlockInserted: + case QTextUndoCommand::BlockAdded: + remove_block(c.pos, &c.blockFormat, c.command, (QTextUndoCommand::Operation)c.operation); + PMDEBUG(" blockremove: from %d", c.pos); + if (c.command == QTextUndoCommand::BlockInserted) + c.command = QTextUndoCommand::BlockRemoved; + else + c.command = QTextUndoCommand::BlockDeleted; + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::BlockRemoved: + case QTextUndoCommand::BlockDeleted: + PMDEBUG(" blockinsert: charformat %d blockformat %d (pos %d, strpos=%d)", c.format, c.blockFormat, c.pos, c.strPos); + insert_block(c.pos, c.strPos, c.format, c.blockFormat, (QTextUndoCommand::Operation)c.operation, c.command); + resetBlockRevision += 1; + if (c.command == QTextUndoCommand::BlockRemoved) + c.command = QTextUndoCommand::BlockInserted; + else + c.command = QTextUndoCommand::BlockAdded; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += 1; + break; + case QTextUndoCommand::CharFormatChanged: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" charFormat: format %d (from %d, length %d)", c.format, c.pos, c.length); + FragmentIterator it = find(c.pos); + Q_ASSERT(!it.atEnd()); + + int oldFormat = it.value()->format; + setCharFormat(c.pos, c.length, formats.charFormat(c.format)); + c.format = oldFormat; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += c.length; + break; + } + case QTextUndoCommand::BlockFormatChanged: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" blockformat: format %d pos %d", c.format, c.pos); + QTextBlock it = blocksFind(c.pos); + Q_ASSERT(it.isValid()); + + int oldFormat = block(it)->format; + block(it)->format = c.format; + QTextBlockGroup *oldGroup = qobject_cast(objectForFormat(formats.blockFormat(oldFormat))); + QTextBlockGroup *group = qobject_cast(objectForFormat(formats.blockFormat(c.format))); + c.format = oldFormat; + if (group != oldGroup) { + if (oldGroup) + oldGroup->blockRemoved(it); + if (group) + group->blockInserted(it); + } else if (group) { + group->blockFormatChanged(it); + } + documentChange(it.position(), it.length()); + editPos = -1; + break; + } + case QTextUndoCommand::GroupFormatChange: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" group format change"); + QTextObject *object = objectForIndex(c.objectIndex); + int oldFormat = formats.objectFormatIndex(c.objectIndex); + changeObjectFormat(object, c.format); + c.format = oldFormat; + editPos = -1; + break; + } + case QTextUndoCommand::CursorMoved: + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::Custom: + resetBlockRevision = -1; // ## TODO + if (undo) + c.custom->undo(); + else + c.custom->redo(); + editPos = -1; + break; + default: + Q_ASSERT(false); + } + + if (resetBlockRevision >= 0) { + int b = blocks.findNode(resetBlockRevision); + QTextBlockData *B = blocks.fragment(b); + B->revision = c.revision; + } + + if (!undo) + ++undoState; + + bool inBlock = ( + undoState > 0 + && undoState < undoStack.size() + && undoStack[undoState].block_part + && undoStack[undoState-1].block_part + && !undoStack[undoState-1].block_end + ); + if (!inBlock) + break; + } + undoEnabled = true; + + int newCursorPos = -1; + + if (editPos >=0) + newCursorPos = editPos + editLength; + else if (docChangeFrom >= 0) + newCursorPos= qMin(docChangeFrom + docChangeLength, length() - 1); + + endEditBlock(); + emitUndoAvailable(isUndoAvailable()); + emitRedoAvailable(isRedoAvailable()); + + return newCursorPos; +} + +/*! + Appends a custom undo \a item to the undo stack. +*/ +void QTextDocumentPrivate::appendUndoItem(QAbstractUndoItem *item) +{ + if (!undoEnabled) { + delete item; + return; + } + + QTextUndoCommand c; + c.command = QTextUndoCommand::Custom; + c.block_part = editBlock != 0; + c.block_end = 0; + c.operation = QTextUndoCommand::MoveCursor; + c.format = 0; + c.strPos = 0; + c.pos = 0; + c.blockFormat = 0; + + c.custom = item; + appendUndoItem(c); +} + +void QTextDocumentPrivate::appendUndoItem(const QTextUndoCommand &c) +{ + PMDEBUG("appendUndoItem, command=%d enabled=%d", c.command, undoEnabled); + if (!undoEnabled) + return; + if (undoState < undoStack.size()) + clearUndoRedoStacks(QTextDocument::RedoStack); + + if (editBlock != 0 && editBlockCursorPosition >= 0) { // we had a beginEditBlock() with a cursor position + if (c.pos != (quint32) editBlockCursorPosition) { // and that cursor position is different from the command + // generate a CursorMoved undo item + QT_INIT_TEXTUNDOCOMMAND(cc, QTextUndoCommand::CursorMoved, true, QTextUndoCommand::MoveCursor, + 0, 0, editBlockCursorPosition, 0, 0); + undoStack.append(cc); + undoState++; + editBlockCursorPosition = -1; + } + } + + + if (!undoStack.isEmpty() && modified) { + QTextUndoCommand &last = undoStack[undoState - 1]; + + if ( (last.block_part && c.block_part && !last.block_end) // part of the same block => can merge + || (!c.block_part && !last.block_part)) { // two single undo items => can merge + + if (last.tryMerge(c)) + return; + } + } + if (modifiedState > undoState) + modifiedState = -1; + undoStack.append(c); + undoState++; + emitUndoAvailable(true); + emitRedoAvailable(false); + + if (!c.block_part) + emit document()->undoCommandAdded(); +} + +void QTextDocumentPrivate::clearUndoRedoStacks(QTextDocument::Stacks stacksToClear, + bool emitSignals) +{ + bool undoCommandsAvailable = undoState != 0; + bool redoCommandsAvailable = undoState != undoStack.size(); + if (stacksToClear == QTextDocument::UndoStack && undoCommandsAvailable) { + for (int i = 0; i < undoState; ++i) { + QTextUndoCommand c = undoStack[undoState]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoStack.remove(0, undoState); + undoStack.resize(undoStack.size() - undoState); + undoState = 0; + if (emitSignals) + emitUndoAvailable(false); + } else if (stacksToClear == QTextDocument::RedoStack + && redoCommandsAvailable) { + for (int i = undoState; i < undoStack.size(); ++i) { + QTextUndoCommand c = undoStack[i]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoStack.resize(undoState); + if (emitSignals) + emitRedoAvailable(false); + } else if (stacksToClear == QTextDocument::UndoAndRedoStacks + && !undoStack.isEmpty()) { + for (int i = 0; i < undoStack.size(); ++i) { + QTextUndoCommand c = undoStack[i]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoState = 0; + undoStack.resize(0); + if (emitSignals && undoCommandsAvailable) + emitUndoAvailable(false); + if (emitSignals && redoCommandsAvailable) + emitRedoAvailable(false); + } +} + +void QTextDocumentPrivate::emitUndoAvailable(bool available) +{ + if (available != wasUndoAvailable) { + Q_Q(QTextDocument); + emit q->undoAvailable(available); + wasUndoAvailable = available; + } +} + +void QTextDocumentPrivate::emitRedoAvailable(bool available) +{ + if (available != wasRedoAvailable) { + Q_Q(QTextDocument); + emit q->redoAvailable(available); + wasRedoAvailable = available; + } +} + +void QTextDocumentPrivate::enableUndoRedo(bool enable) +{ + if (enable && maximumBlockCount > 0) + return; + + if (!enable) { + undoState = 0; + clearUndoRedoStacks(QTextDocument::RedoStack); + emitUndoAvailable(false); + emitRedoAvailable(false); + } + modifiedState = modified ? -1 : undoState; + undoEnabled = enable; + if (!undoEnabled) + compressPieceTable(); +} + +void QTextDocumentPrivate::joinPreviousEditBlock() +{ + beginEditBlock(); + + if (undoEnabled && undoState) + undoStack[undoState - 1].block_end = false; +} + +void QTextDocumentPrivate::endEditBlock() +{ + Q_ASSERT(editBlock > 0); + if (--editBlock) + return; + + if (undoEnabled && undoState > 0) { + const bool wasBlocking = !undoStack[undoState - 1].block_end; + if (undoStack[undoState - 1].block_part) { + undoStack[undoState - 1].block_end = true; + if (wasBlocking) + emit document()->undoCommandAdded(); + } + } + + editBlockCursorPosition = -1; + + finishEdit(); +} + +void QTextDocumentPrivate::finishEdit() +{ + Q_Q(QTextDocument); + + if (editBlock) + return; + + if (framesDirty) + scan_frames(docChangeFrom, docChangeOldLength, docChangeLength); + + if (lout && docChangeFrom >= 0) { + if (!inContentsChange) { + inContentsChange = true; + emit q->contentsChange(docChangeFrom, docChangeOldLength, docChangeLength); + inContentsChange = false; + } + lout->documentChanged(docChangeFrom, docChangeOldLength, docChangeLength); + } + + docChangeFrom = -1; + + if (needsEnsureMaximumBlockCount) { + needsEnsureMaximumBlockCount = false; + if (ensureMaximumBlockCount()) { + // if ensureMaximumBlockCount() returns true + // it will have called endEditBlock() and + // compressPieceTable() itself, so we return here + // to prevent getting two contentsChanged emits + return; + } + } + + QList changedCursors; + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->changed) { + curs->changed = false; + changedCursors.append(QTextCursor(curs)); + } + } + foreach (const QTextCursor &cursor, changedCursors) + emit q->cursorPositionChanged(cursor); + + contentsChanged(); + + if (blocks.numNodes() != lastBlockCount) { + lastBlockCount = blocks.numNodes(); + emit q->blockCountChanged(lastBlockCount); + } + + if (!undoEnabled && unreachableCharacterCount) + compressPieceTable(); +} + +void QTextDocumentPrivate::documentChange(int from, int length) +{ +// qDebug("QTextDocumentPrivate::documentChange: from=%d,length=%d", from, length); + if (docChangeFrom < 0) { + docChangeFrom = from; + docChangeOldLength = length; + docChangeLength = length; + return; + } + int start = qMin(from, docChangeFrom); + int end = qMax(from + length, docChangeFrom + docChangeLength); + int diff = qMax(0, end - start - docChangeLength); + docChangeFrom = start; + docChangeOldLength += diff; + docChangeLength += diff; +} + +/* + adjustDocumentChangesAndCursors is called whenever there is an insert or remove of characters. + param from is the cursor position in the document + param addedOrRemoved is the amount of characters added or removed. A negative number means characters are removed. + + The function stores information to be emitted when finishEdit() is called. +*/ +void QTextDocumentPrivate::adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op) +{ + if (!editBlock) + ++revision; + + if (blockCursorAdjustment) { + ; // postpone, will be called again from QTextDocumentPrivate::remove() + } else { + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) { + curs->changed = true; + } + } + } + +// qDebug("QTextDocumentPrivate::adjustDocumentChanges: from=%d,addedOrRemoved=%d", from, addedOrRemoved); + if (docChangeFrom < 0) { + docChangeFrom = from; + if (addedOrRemoved > 0) { + docChangeOldLength = 0; + docChangeLength = addedOrRemoved; + } else { + docChangeOldLength = -addedOrRemoved; + docChangeLength = 0; + } +// qDebug("adjustDocumentChanges:"); +// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength); + return; + } + + // have to merge the new change with the already existing one. + int added = qMax(0, addedOrRemoved); + int removed = qMax(0, -addedOrRemoved); + + int diff = 0; + if(from + removed < docChangeFrom) + diff = docChangeFrom - from - removed; + else if(from > docChangeFrom + docChangeLength) + diff = from - (docChangeFrom + docChangeLength); + + int overlap_start = qMax(from, docChangeFrom); + int overlap_end = qMin(from + removed, docChangeFrom + docChangeLength); + int removedInside = qMax(0, overlap_end - overlap_start); + removed -= removedInside; + +// qDebug("adjustDocumentChanges: from=%d, addedOrRemoved=%d, diff=%d, removedInside=%d", from, addedOrRemoved, diff, removedInside); + docChangeFrom = qMin(docChangeFrom, from); + docChangeOldLength += removed + diff; + docChangeLength += added - removedInside + diff; +// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength); + +} + + +QString QTextDocumentPrivate::plainText() const +{ + QString result; + result.resize(length()); + const QChar *text_unicode = text.unicode(); + QChar *data = result.data(); + for (QTextDocumentPrivate::FragmentIterator it = begin(); it != end(); ++it) { + const QTextFragmentData *f = *it; + ::memcpy(data, text_unicode + f->stringPosition, f->size_array[0] * sizeof(QChar)); + data += f->size_array[0]; + } + // remove trailing block separator + result.chop(1); + return result; +} + +int QTextDocumentPrivate::blockCharFormatIndex(int node) const +{ + int pos = blocks.position(node); + if (pos == 0) + return initialBlockCharFormatIndex; + + return fragments.find(pos - 1)->format; +} + +int QTextDocumentPrivate::nextCursorPosition(int position, QTextLayout::CursorMode mode) const +{ + if (position == length()-1) + return position; + + QTextBlock it = blocksFind(position); + int start = it.position(); + int end = start + it.length() - 1; + if (position == end) + return end + 1; + + return it.layout()->nextCursorPosition(position-start, mode) + start; +} + +int QTextDocumentPrivate::previousCursorPosition(int position, QTextLayout::CursorMode mode) const +{ + if (position == 0) + return position; + + QTextBlock it = blocksFind(position); + int start = it.position(); + if (position == start) + return start - 1; + + return it.layout()->previousCursorPosition(position-start, mode) + start; +} + +void QTextDocumentPrivate::changeObjectFormat(QTextObject *obj, int format) +{ + beginEditBlock(); + int objectIndex = obj->objectIndex(); + int oldFormatIndex = formats.objectFormatIndex(objectIndex); + formats.setObjectFormatIndex(objectIndex, format); + + QTextBlockGroup *b = qobject_cast(obj); + if (b) { + b->d_func()->markBlocksDirty(); + } + QTextFrame *f = qobject_cast(obj); + if (f) + documentChange(f->firstPosition(), f->lastPosition() - f->firstPosition()); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::GroupFormatChange, (editBlock != 0), QTextUndoCommand::MoveCursor, oldFormatIndex, + 0, 0, obj->d_func()->objectIndex, 0); + appendUndoItem(c); + + endEditBlock(); +} + +static QTextFrame *findChildFrame(QTextFrame *f, int pos) +{ + /* Binary search for frame at pos */ + const QList children = f->childFrames(); + int first = 0; + int last = children.size() - 1; + while (first <= last) { + int mid = (first + last) / 2; + QTextFrame *c = children.at(mid); + if (pos > c->lastPosition()) + first = mid + 1; + else if (pos < c->firstPosition()) + last = mid - 1; + else + return c; + } + return 0; +} + +QTextFrame *QTextDocumentPrivate::rootFrame() const +{ + if (!rtFrame) { + QTextFrameFormat defaultRootFrameFormat; + defaultRootFrameFormat.setMargin(documentMargin); + rtFrame = qobject_cast(const_cast(this)->createObject(defaultRootFrameFormat)); + } + return rtFrame; +} + +QTextFrame *QTextDocumentPrivate::frameAt(int pos) const +{ + QTextFrame *f = rootFrame(); + + while (1) { + QTextFrame *c = findChildFrame(f, pos); + if (!c) + return f; + f = c; + } +} + +void QTextDocumentPrivate::clearFrame(QTextFrame *f) +{ + for (int i = 0; i < f->d_func()->childFrames.count(); ++i) + clearFrame(f->d_func()->childFrames.at(i)); + f->d_func()->childFrames.clear(); + f->d_func()->parentFrame = 0; +} + +void QTextDocumentPrivate::scan_frames(int pos, int charsRemoved, int charsAdded) +{ + // ###### optimize + Q_UNUSED(pos); + Q_UNUSED(charsRemoved); + Q_UNUSED(charsAdded); + + QTextFrame *f = rootFrame(); + clearFrame(f); + + for (FragmentIterator it = begin(); it != end(); ++it) { + // QTextFormat fmt = formats.format(it->format); + QTextFrame *frame = qobject_cast(objectForFormat(it->format)); + if (!frame) + continue; + + Q_ASSERT(it.size() == 1); + QChar ch = text.at(it->stringPosition); + + if (ch == QTextBeginningOfFrame) { + if (f != frame) { + // f == frame happens for tables + Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0); + frame->d_func()->parentFrame = f; + f->d_func()->childFrames.append(frame); + f = frame; + } + } else if (ch == QTextEndOfFrame) { + Q_ASSERT(f == frame); + Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0); + f = frame->d_func()->parentFrame; + } else if (ch == QChar::ObjectReplacementCharacter) { + Q_ASSERT(f != frame); + Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0); + Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0); + frame->d_func()->parentFrame = f; + f->d_func()->childFrames.append(frame); + } else { + Q_ASSERT(false); + } + } + Q_ASSERT(f == rtFrame); + framesDirty = false; +} + +void QTextDocumentPrivate::insert_frame(QTextFrame *f) +{ + int start = f->firstPosition(); + int end = f->lastPosition(); + QTextFrame *parent = frameAt(start-1); + Q_ASSERT(parent == frameAt(end+1)); + + if (start != end) { + // iterator over the parent and move all children contained in my frame to myself + for (int i = 0; i < parent->d_func()->childFrames.size(); ++i) { + QTextFrame *c = parent->d_func()->childFrames.at(i); + if (start < c->firstPosition() && end > c->lastPosition()) { + parent->d_func()->childFrames.removeAt(i); + f->d_func()->childFrames.append(c); + c->d_func()->parentFrame = f; + } + } + } + // insert at the correct position + int i = 0; + for (; i < parent->d_func()->childFrames.size(); ++i) { + QTextFrame *c = parent->d_func()->childFrames.at(i); + if (c->firstPosition() > end) + break; + } + parent->d_func()->childFrames.insert(i, f); + f->d_func()->parentFrame = parent; +} + +QTextFrame *QTextDocumentPrivate::insertFrame(int start, int end, const QTextFrameFormat &format) +{ + Q_ASSERT(start >= 0 && start < length()); + Q_ASSERT(end >= 0 && end < length()); + Q_ASSERT(start <= end || end == -1); + + if (start != end && frameAt(start) != frameAt(end)) + return 0; + + beginEditBlock(); + + QTextFrame *frame = qobject_cast(createObject(format)); + Q_ASSERT(frame); + + // #### using the default block and char format below might be wrong + int idx = formats.indexForFormat(QTextBlockFormat()); + QTextCharFormat cfmt; + cfmt.setObjectIndex(frame->objectIndex()); + int charIdx = formats.indexForFormat(cfmt); + + insertBlock(QTextBeginningOfFrame, start, idx, charIdx, QTextUndoCommand::MoveCursor); + insertBlock(QTextEndOfFrame, ++end, idx, charIdx, QTextUndoCommand::KeepCursor); + + frame->d_func()->fragment_start = find(start).n; + frame->d_func()->fragment_end = find(end).n; + + insert_frame(frame); + + endEditBlock(); + + return frame; +} + +void QTextDocumentPrivate::removeFrame(QTextFrame *frame) +{ + QTextFrame *parent = frame->d_func()->parentFrame; + if (!parent) + return; + + int start = frame->firstPosition(); + int end = frame->lastPosition(); + Q_ASSERT(end >= start); + + beginEditBlock(); + + // remove already removes the frames from the tree + remove(end, 1); + remove(start-1, 1); + + endEditBlock(); +} + +QTextObject *QTextDocumentPrivate::objectForIndex(int objectIndex) const +{ + if (objectIndex < 0) + return 0; + + QTextObject *object = objects.value(objectIndex, 0); + if (!object) { + QTextDocumentPrivate *that = const_cast(this); + QTextFormat fmt = formats.objectFormat(objectIndex); + object = that->createObject(fmt, objectIndex); + } + return object; +} + +QTextObject *QTextDocumentPrivate::objectForFormat(int formatIndex) const +{ + int objectIndex = formats.format(formatIndex).objectIndex(); + return objectForIndex(objectIndex); +} + +QTextObject *QTextDocumentPrivate::objectForFormat(const QTextFormat &f) const +{ + return objectForIndex(f.objectIndex()); +} + +QTextObject *QTextDocumentPrivate::createObject(const QTextFormat &f, int objectIndex) +{ + QTextObject *obj = document()->createObject(f); + + if (obj) { + obj->d_func()->objectIndex = objectIndex == -1 ? formats.createObjectIndex(f) : objectIndex; + objects[obj->d_func()->objectIndex] = obj; + } + + return obj; +} + +void QTextDocumentPrivate::deleteObject(QTextObject *object) +{ + const int objIdx = object->d_func()->objectIndex; + objects.remove(objIdx); + delete object; +} + +void QTextDocumentPrivate::contentsChanged() +{ + Q_Q(QTextDocument); + if (editBlock) + return; + + bool m = undoEnabled ? (modifiedState != undoState) : true; + if (modified != m) { + modified = m; + emit q->modificationChanged(modified); + } + + emit q->contentsChanged(); +} + +void QTextDocumentPrivate::compressPieceTable() +{ + if (undoEnabled) + return; + + const uint garbageCollectionThreshold = 96 * 1024; // bytes + + //qDebug() << "unreachable bytes:" << unreachableCharacterCount * sizeof(QChar) << " -- limit" << garbageCollectionThreshold << "text size =" << text.size() << "capacity:" << text.capacity(); + + bool compressTable = unreachableCharacterCount * sizeof(QChar) > garbageCollectionThreshold + && text.size() >= text.capacity() * 0.9; + if (!compressTable) + return; + + QString newText; + newText.resize(text.size()); + QChar *newTextPtr = newText.data(); + int newLen = 0; + + for (FragmentMap::Iterator it = fragments.begin(); !it.atEnd(); ++it) { + memcpy(newTextPtr, text.constData() + it->stringPosition, it->size_array[0] * sizeof(QChar)); + it->stringPosition = newLen; + newTextPtr += it->size_array[0]; + newLen += it->size_array[0]; + } + + newText.resize(newLen); + newText.squeeze(); + //qDebug() << "removed" << text.size() - newText.size() << "characters"; + text = newText; + unreachableCharacterCount = 0; +} + +void QTextDocumentPrivate::setModified(bool m) +{ + Q_Q(QTextDocument); + if (m == modified) + return; + + modified = m; + if (!modified) + modifiedState = undoState; + else + modifiedState = -1; + + emit q->modificationChanged(modified); +} + +bool QTextDocumentPrivate::ensureMaximumBlockCount() +{ + if (maximumBlockCount <= 0) + return false; + if (blocks.numNodes() <= maximumBlockCount) + return false; + + beginEditBlock(); + + const int blocksToRemove = blocks.numNodes() - maximumBlockCount; + QTextCursor cursor(this, 0); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, blocksToRemove); + + unreachableCharacterCount += cursor.selectionEnd() - cursor.selectionStart(); + + // preserve the char format of the paragraph that is to become the new first one + QTextCharFormat charFmt = cursor.blockCharFormat(); + cursor.removeSelectedText(); + cursor.setBlockCharFormat(charFmt); + + endEditBlock(); + + compressPieceTable(); + + return true; +} + +/// This method is called from QTextTable when it is about to remove a table-cell to allow cursors to update their selection. +void QTextDocumentPrivate::aboutToRemoveCell(int from, int to) +{ + Q_ASSERT(from <= to); + foreach (QTextCursorPrivate *curs, cursors) + curs->aboutToRemoveCell(from, to); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocument_p.h b/src/gui/text/qtextdocument_p.h new file mode 100644 index 0000000000..b464f2ee40 --- /dev/null +++ b/src/gui/text/qtextdocument_p.h @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENT_P_H +#define QTEXTDOCUMENT_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 "QtCore/qglobal.h" +#include "QtCore/qstring.h" +#include "QtCore/qvector.h" +#include "QtCore/qlist.h" +#include "private/qobject_p.h" +#include "private/qfragmentmap_p.h" +#include "QtGui/qtextlayout.h" +#include "QtGui/qtextoption.h" +#include "private/qtextformat_p.h" +#include "QtGui/qtextdocument.h" +#include "QtGui/qtextobject.h" +#include "QtCore/qmap.h" +#include "QtCore/qvariant.h" +#include "QtCore/qurl.h" +#include "private/qcssparser_p.h" + +// #define QT_QMAP_DEBUG + +#ifdef QT_QMAP_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +class QTextFormatCollection; +class QTextFormat; +class QTextBlockFormat; +class QTextCursorPrivate; +class QAbstractTextDocumentLayout; +class QTextDocument; +class QTextFrame; + +#define QTextBeginningOfFrame QChar(0xfdd0) +#define QTextEndOfFrame QChar(0xfdd1) + +class QTextFragmentData : public QFragment<> +{ +public: + inline void initialize() {} + inline void invalidate() const {} + inline void free() {} + int stringPosition; + int format; +}; + +class QTextBlockData : public QFragment<3> +{ +public: + inline void initialize() + { layout = 0; userData = 0; userState = -1; revision = 0; hidden = 0; } + void invalidate() const; + inline void free() + { delete layout; layout = 0; delete userData; userData = 0; } + + mutable int format; + // ##### probably store a QTextEngine * here! + mutable QTextLayout *layout; + mutable QTextBlockUserData *userData; + mutable int userState; + mutable int revision : 31; + mutable uint hidden : 1; +}; + + +class QAbstractUndoItem; + +class QTextUndoCommand +{ +public: + enum Command { + Inserted = 0, + Removed = 1, + CharFormatChanged = 2, + BlockFormatChanged = 3, + BlockInserted = 4, + BlockRemoved = 5, + BlockAdded = 6, + BlockDeleted = 7, + GroupFormatChange = 8, + CursorMoved = 9, + Custom = 256 + }; + enum Operation { + KeepCursor = 0, + MoveCursor = 1 + }; + quint16 command; + uint block_part : 1; // all commands that are part of an undo block (including the first and the last one) have this set to 1 + uint block_end : 1; // the last command in an undo block has this set to 1. + uint block_padding : 6; // padding since block used to be a quint8 + quint8 operation; + int format; + quint32 strPos; + quint32 pos; + union { + int blockFormat; + quint32 length; + QAbstractUndoItem *custom; + int objectIndex; + }; + quint32 revision; + + bool tryMerge(const QTextUndoCommand &other); +}; +Q_DECLARE_TYPEINFO(QTextUndoCommand, Q_PRIMITIVE_TYPE); + +class Q_AUTOTEST_EXPORT QTextDocumentPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QTextDocument) +public: + typedef QFragmentMap FragmentMap; + typedef FragmentMap::ConstIterator FragmentIterator; + typedef QFragmentMap BlockMap; + + QTextDocumentPrivate(); + ~QTextDocumentPrivate(); + + void init(); + void clear(); + + void setLayout(QAbstractTextDocumentLayout *layout); + + void insert(int pos, const QString &text, int format); + void insert(int pos, int strPos, int strLength, int format); + int insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + int insertBlock(const QChar &blockSeparator, int pos, int blockFormat, int charFormat, + QTextUndoCommand::Operation op = QTextUndoCommand::MoveCursor); + + void move(int from, int to, int length, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + void remove(int pos, int length, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + + void aboutToRemoveCell(int cursorFrom, int cursorEnd); + + QTextFrame *insertFrame(int start, int end, const QTextFrameFormat &format); + void removeFrame(QTextFrame *frame); + + enum FormatChangeMode { MergeFormat, SetFormat, SetFormatAndPreserveObjectIndices }; + + void setCharFormat(int pos, int length, const QTextCharFormat &newFormat, FormatChangeMode mode = SetFormat); + void setBlockFormat(const QTextBlock &from, const QTextBlock &to, + const QTextBlockFormat &newFormat, FormatChangeMode mode = SetFormat); + + void emitUndoAvailable(bool available); + void emitRedoAvailable(bool available); + + int undoRedo(bool undo); + inline void undo() { undoRedo(true); } + inline void redo() { undoRedo(false); } + void appendUndoItem(QAbstractUndoItem *); + inline void beginEditBlock() { if (0 == editBlock++) ++revision; } + void joinPreviousEditBlock(); + void endEditBlock(); + void finishEdit(); + inline bool isInEditBlock() const { return editBlock; } + void enableUndoRedo(bool enable); + inline bool isUndoRedoEnabled() const { return undoEnabled; } + + inline bool isUndoAvailable() const { return undoEnabled && undoState > 0; } + inline bool isRedoAvailable() const { return undoEnabled && undoState < undoStack.size(); } + + inline int availableUndoSteps() const { return undoEnabled ? undoState : 0; } + inline int availableRedoSteps() const { return undoEnabled ? qMax(undoStack.size() - undoState - 1, 0) : 0; } + + inline QString buffer() const { return text; } + QString plainText() const; + inline int length() const { return fragments.length(); } + + inline QTextFormatCollection *formatCollection() { return &formats; } + inline const QTextFormatCollection *formatCollection() const { return &formats; } + inline QAbstractTextDocumentLayout *layout() const { return lout; } + + inline FragmentIterator find(int pos) const { return fragments.find(pos); } + inline FragmentIterator begin() const { return fragments.begin(); } + inline FragmentIterator end() const { return fragments.end(); } + + inline QTextBlock blocksBegin() const { return QTextBlock(const_cast(this), blocks.firstNode()); } + inline QTextBlock blocksEnd() const { return QTextBlock(const_cast(this), 0); } + inline QTextBlock blocksFind(int pos) const { return QTextBlock(const_cast(this), blocks.findNode(pos)); } + int blockCharFormatIndex(int node) const; + + inline int numBlocks() const { return blocks.numNodes(); } + + const BlockMap &blockMap() const { return blocks; } + const FragmentMap &fragmentMap() const { return fragments; } + BlockMap &blockMap() { return blocks; } + FragmentMap &fragmentMap() { return fragments; } + + static const QTextBlockData *block(const QTextBlock &it) { return it.p->blocks.fragment(it.n); } + + int nextCursorPosition(int position, QTextLayout::CursorMode mode) const; + int previousCursorPosition(int position, QTextLayout::CursorMode mode) const; + + void changeObjectFormat(QTextObject *group, int format); + + void setModified(bool m); + inline bool isModified() const { return modified; } + + inline QFont defaultFont() const { return formats.defaultFont(); } + inline void setDefaultFont(const QFont &f) { formats.setDefaultFont(f); } + + void clearUndoRedoStacks(QTextDocument::Stacks stacksToClear, bool emitSignals = false); + +private: + bool split(int pos); + bool unite(uint f); + + void insert_string(int pos, uint strPos, uint length, int format, QTextUndoCommand::Operation op); + int insert_block(int pos, uint strPos, int format, int blockformat, QTextUndoCommand::Operation op, int command); + int remove_string(int pos, uint length, QTextUndoCommand::Operation op); + int remove_block(int pos, int *blockformat, int command, QTextUndoCommand::Operation op); + + void insert_frame(QTextFrame *f); + void scan_frames(int pos, int charsRemoved, int charsAdded); + static void clearFrame(QTextFrame *f); + + void adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op); + + bool wasUndoAvailable; + bool wasRedoAvailable; + +public: + void documentChange(int from, int length); + + inline void addCursor(QTextCursorPrivate *c) { cursors.append(c); } + inline void removeCursor(QTextCursorPrivate *c) { cursors.removeAll(c); } + + QTextFrame *frameAt(int pos) const; + QTextFrame *rootFrame() const; + + QTextObject *objectForIndex(int objectIndex) const; + QTextObject *objectForFormat(int formatIndex) const; + QTextObject *objectForFormat(const QTextFormat &f) const; + + QTextObject *createObject(const QTextFormat &newFormat, int objectIndex = -1); + void deleteObject(QTextObject *object); + + QTextDocument *document() { return q_func(); } + const QTextDocument *document() const { return q_func(); } + + bool ensureMaximumBlockCount(); + +private: + QTextDocumentPrivate(const QTextDocumentPrivate& m); + QTextDocumentPrivate& operator= (const QTextDocumentPrivate& m); + + void appendUndoItem(const QTextUndoCommand &c); + + void contentsChanged(); + + void compressPieceTable(); + + QString text; + uint unreachableCharacterCount; + + QVector undoStack; + bool undoEnabled; + int undoState; + int revision; + // position in undo stack of the last setModified(false) call + int modifiedState; + bool modified; + + int editBlock; + int editBlockCursorPosition; + int docChangeFrom; + int docChangeOldLength; + int docChangeLength; + bool framesDirty; + + QTextFormatCollection formats; + mutable QTextFrame *rtFrame; + QAbstractTextDocumentLayout *lout; + FragmentMap fragments; + BlockMap blocks; + int initialBlockCharFormatIndex; + + QList cursors; + QMap objects; + QMap resources; + QMap cachedResources; + QString defaultStyleSheet; + + int lastBlockCount; + +public: + QTextOption defaultTextOption; +#ifndef QT_NO_CSSPARSER + QCss::StyleSheet parsedDefaultStyleSheet; +#endif + int maximumBlockCount; + uint needsEnsureMaximumBlockCount : 1; + uint inContentsChange : 1; + uint blockCursorAdjustment : 1; + QSizeF pageSize; + QString title; + QString url; + qreal indentWidth; + qreal documentMargin; + + void mergeCachedResources(const QTextDocumentPrivate *priv); + + friend class QTextHtmlExporter; + friend class QTextCursor; +}; + +class QTextTable; +class QTextHtmlExporter +{ +public: + QTextHtmlExporter(const QTextDocument *_doc); + + enum ExportMode { + ExportEntireDocument, + ExportFragment + }; + + QString toHtml(const QByteArray &encoding, ExportMode mode = ExportEntireDocument); + +private: + enum StyleMode { EmitStyleTag, OmitStyleTag }; + enum FrameType { TextFrame, TableFrame, RootFrame }; + + void emitFrame(QTextFrame::Iterator frameIt); + void emitTextFrame(const QTextFrame *frame); + void emitBlock(const QTextBlock &block); + void emitTable(const QTextTable *table); + void emitFragment(const QTextFragment &fragment); + + void emitBlockAttributes(const QTextBlock &block); + bool emitCharFormatStyle(const QTextCharFormat &format); + void emitTextLength(const char *attribute, const QTextLength &length); + void emitAlignment(Qt::Alignment alignment); + void emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode = EmitStyleTag); + void emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right); + void emitAttribute(const char *attribute, const QString &value); + void emitFrameStyle(const QTextFrameFormat &format, FrameType frameType); + void emitBorderStyle(QTextFrameFormat::BorderStyle style); + void emitPageBreakPolicy(QTextFormat::PageBreakFlags policy); + + void emitFontFamily(const QString &family); + + void emitBackgroundAttribute(const QTextFormat &format); + QString findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap); + + QString html; + QTextCharFormat defaultCharFormat; + const QTextDocument *doc; + bool fragmentMarkers; +}; + +QT_END_NAMESPACE + +#endif // QTEXTDOCUMENT_P_H diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp new file mode 100644 index 0000000000..7890b38067 --- /dev/null +++ b/src/gui/text/qtextdocumentfragment.cpp @@ -0,0 +1,1224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtextcursor_p.h" +#include "qtextlist.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt) +#if defined(Q_CC_DIAB) // compiler bug + : formatCollection(*_destination.d->priv->formatCollection()), originalText((const QString)_source.d->priv->buffer()) +#else + : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer()) +#endif +{ + src = _source.d->priv; + dst = _destination.d->priv; + insertPos = _destination.position(); + this->forceCharFormat = forceCharFormat; + primaryCharFormatIndex = convertFormatIndex(fmt); + cursor = _source; +} + +int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet) +{ + QTextFormat fmt = oldFormat; + if (objectIndexToSet != -1) { + fmt.setObjectIndex(objectIndexToSet); + } else if (fmt.objectIndex() != -1) { + int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1); + if (newObjectIndex == -1) { + QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex()); + Q_ASSERT(objFormat.objectIndex() == -1); + newObjectIndex = formatCollection.createObjectIndex(objFormat); + objectIndexMap.insert(fmt.objectIndex(), newObjectIndex); + } + fmt.setObjectIndex(newObjectIndex); + } + int idx = formatCollection.indexForFormat(fmt); + Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type()); + return idx; +} + +int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex) +{ + QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + + Q_ASSERT(objectIndex == -1 + || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1)); + + int charFormatIndex; + if (forceCharFormat) + charFormatIndex = primaryCharFormatIndex; + else + charFormatIndex = convertFormatIndex(frag->format, objectIndex); + + const int inFragmentOffset = qMax(0, pos - fragIt.position()); + int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos); + + QTextBlock nextBlock = src->blocksFind(pos + 1); + + int blockIdx = -2; + if (nextBlock.position() == pos + 1) { + blockIdx = convertFormatIndex(nextBlock.blockFormat()); + } else if (pos == 0 && insertPos == 0) { + dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat()); + dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat()); + } + + QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy); + if (txtToInsert.length() == 1 + && (txtToInsert.at(0) == QChar::ParagraphSeparator + || txtToInsert.at(0) == QTextBeginningOfFrame + || txtToInsert.at(0) == QTextEndOfFrame + ) + ) { + dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex); + ++insertPos; + } else { + if (nextBlock.textList()) { + QTextBlock dstBlock = dst->blocksFind(insertPos); + if (!dstBlock.textList()) { + // insert a new text block with the block and char format from the + // source block to make sure that the following text fragments + // end up in a list as they should + int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat()); + int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat()); + dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex); + ++insertPos; + } + } + dst->insert(insertPos, txtToInsert, charFormatIndex); + const int userState = nextBlock.userState(); + if (userState != -1) + dst->blocksFind(insertPos).setUserState(userState); + insertPos += txtToInsert.length(); + } + + return charsToCopy; +} + +void QTextCopyHelper::appendFragments(int pos, int endPos) +{ + Q_ASSERT(pos < endPos); + + while (pos < endPos) + pos += appendFragment(pos, endPos); +} + +void QTextCopyHelper::copy() +{ + if (cursor.hasComplexSelection()) { + QTextTable *table = cursor.currentTable(); + int row_start, col_start, num_rows, num_cols; + cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + QTextTableFormat tableFormat = table->format(); + tableFormat.setColumns(num_cols); + tableFormat.clearColumnWidthConstraints(); + const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + const int rspan = cell.rowSpan(); + const int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + // add the QTextBeginningOfFrame + QTextCharFormat cellFormat = cell.format(); + if (r + rspan >= row_start + num_rows) { + cellFormat.setTableCellRowSpan(row_start + num_rows - r); + } + if (c + cspan >= col_start + num_cols) { + cellFormat.setTableCellColumnSpan(col_start + num_cols - c); + } + const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex); + + int blockIdx = -2; + const int cellPos = cell.firstPosition(); + QTextBlock block = src->blocksFind(cellPos); + if (block.position() == cellPos) { + blockIdx = convertFormatIndex(block.blockFormat()); + } + + dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex); + ++insertPos; + + // nothing to add for empty cells + if (cell.lastPosition() > cellPos) { + // add the contents + appendFragments(cellPos, cell.lastPosition()); + } + } + } + + // add end of table + int end = table->lastPosition(); + appendFragment(end, end+1, objectIndex); + } else { + appendFragments(cursor.selectionStart(), cursor.selectionEnd()); + } +} + +QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor) + : ref(1), doc(new QTextDocument), importedFromPlainText(false) +{ + doc->setUndoRedoEnabled(false); + + if (!_cursor.hasSelection()) + return; + + doc->docHandle()->beginEditBlock(); + QTextCursor destCursor(doc); + QTextCopyHelper(_cursor, destCursor).copy(); + doc->docHandle()->endEditBlock(); + + if (_cursor.d) + doc->docHandle()->mergeCachedResources(_cursor.d->priv); +} + +void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const +{ + if (_cursor.isNull()) + return; + + QTextDocumentPrivate *destPieceTable = _cursor.d->priv; + destPieceTable->beginEditBlock(); + + QTextCursor sourceCursor(doc); + sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy(); + + destPieceTable->endEditBlock(); +} + +/*! + \class QTextDocumentFragment + \reentrant + + \brief The QTextDocumentFragment class represents a piece of formatted text + from a QTextDocument. + + \ingroup richtext-processing + \ingroup shared + + A QTextDocumentFragment is a fragment of rich text, that can be inserted into + a QTextDocument. A document fragment can be created from a + QTextDocument, from a QTextCursor's selection, or from another + document fragment. Document fragments can also be created by the + static functions, fromPlainText() and fromHtml(). + + The contents of a document fragment can be obtained as plain text + by using the toPlainText() function, or it can be obtained as HTML + with toHtml(). +*/ + + +/*! + Constructs an empty QTextDocumentFragment. + + \sa isEmpty() +*/ +QTextDocumentFragment::QTextDocumentFragment() + : d(0) +{ +} + +/*! + Converts the given \a document into a QTextDocumentFragment. + Note that the QTextDocumentFragment only stores the document contents, not meta information + like the document's title. +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document) + : d(0) +{ + if (!document) + return; + + QTextCursor cursor(const_cast(document)); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + d = new QTextDocumentFragmentPrivate(cursor); +} + +/*! + Creates a QTextDocumentFragment from the \a{cursor}'s selection. + If the cursor doesn't have a selection, the created fragment is empty. + + \sa isEmpty() QTextCursor::selection() +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor) + : d(0) +{ + if (!cursor.hasSelection()) + return; + + d = new QTextDocumentFragmentPrivate(cursor); +} + +/*! + \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other) + + Copy constructor. Creates a copy of the \a other fragment. +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs) + : d(rhs.d) +{ + if (d) + d->ref.ref(); +} + +/*! + \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other) + + Assigns the \a other fragment to this fragment. +*/ +QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs) +{ + if (rhs.d) + rhs.d->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = rhs.d; + return *this; +} + +/*! + Destroys the document fragment. +*/ +QTextDocumentFragment::~QTextDocumentFragment() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Returns true if the fragment is empty; otherwise returns false. +*/ +bool QTextDocumentFragment::isEmpty() const +{ + return !d || !d->doc || d->doc->docHandle()->length() <= 1; +} + +/*! + Returns the document fragment's text as plain text (i.e. with no + formatting information). + + \sa toHtml() +*/ +QString QTextDocumentFragment::toPlainText() const +{ + if (!d) + return QString(); + + return d->doc->toPlainText(); +} + +// #### Qt 5: merge with other overload +/*! + \overload +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +QString QTextDocumentFragment::toHtml() const +{ + return toHtml(QByteArray()); +} + +/*! + \since 4.2 + + Returns the contents of the document fragment as HTML, + using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1"). + + \sa toPlainText(), QTextDocument::toHtml(), QTextCodec +*/ +QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const +{ + if (!d) + return QString(); + + return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Returns a document fragment that contains the given \a plainText. + + When inserting such a fragment into a QTextDocument the current char format of + the QTextCursor used for insertion is used as format for the text. +*/ +QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText) +{ + QTextDocumentFragment res; + + res.d = new QTextDocumentFragmentPrivate; + res.d->importedFromPlainText = true; + QTextCursor cursor(res.d->doc); + cursor.insertText(plainText); + return res; +} + +static QTextListFormat::Style nextListStyle(QTextListFormat::Style style) +{ + if (style == QTextListFormat::ListDisc) + return QTextListFormat::ListCircle; + else if (style == QTextListFormat::ListCircle) + return QTextListFormat::ListSquare; + return style; +} + +#ifndef QT_NO_TEXTHTMLPARSER + +QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider) + : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode) +{ + cursor = QTextCursor(doc); + wsm = QTextHtmlParserNode::WhiteSpaceNormal; + + QString html = _html; + const int startFragmentPos = html.indexOf(QLatin1String("")); + if (startFragmentPos != -1) { + QString qt3RichTextHeader(QLatin1String("")); + + // Hack for Qt3 + const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader); + + const int endFragmentPos = html.indexOf(QLatin1String("")); + if (startFragmentPos < endFragmentPos) + html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos); + else + html = html.mid(startFragmentPos); + + if (hasQtRichtextMetaTag) + html.prepend(qt3RichTextHeader); + } + + parse(html, resourceProvider ? resourceProvider : doc); +// dumpHtml(); +} + +void QTextHtmlImporter::import() +{ + cursor.beginEditBlock(); + hasBlock = true; + forceBlockMerging = false; + compressNextWhitespace = RemoveWhiteSpace; + blockTagClosed = false; + for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) { + currentNode = &at(currentNodeIdx); + wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm; + + /* + * process each node in three stages: + * 1) check if the hierarchy changed and we therefore passed the + * equivalent of a closing tag -> we may need to finish off + * some structures like tables + * + * 2) check if the current node is a special node like a + * ,
    or tag that requires special processing + * + * 3) if the node should result in a QTextBlock create one and + * finally insert text that may be attached to the node + */ + + /* emit 'closing' table blocks or adjust current indent level + * if we + * 1) are beyond the first node + * 2) the current node not being a child of the previous node + * means there was a tag closing in the input html + */ + if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) { + blockTagClosed = closeTag(); + // visually collapse subsequent block tags, but if the element after the closed block tag + // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting + // hasBlock to false. + if (blockTagClosed + && !currentNode->isBlock() + && currentNode->id != Html_unknown) + { + hasBlock = false; + } else if (hasBlock) { + // when collapsing subsequent block tags we need to clear the block format + QTextBlockFormat blockFormat = currentNode->blockFormat; + blockFormat.setIndent(indent); + + QTextBlockFormat oldFormat = cursor.blockFormat(); + if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) { + QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy(); + if (pageBreak == QTextFormat::PageBreak_AlwaysAfter) + /* We remove an empty paragrah that requested a page break after. + moving that request to the next paragraph means we also need to make + that a pagebreak before to keep the same visual appearance. + */ + pageBreak = QTextFormat::PageBreak_AlwaysBefore; + blockFormat.setPageBreakPolicy(pageBreak); + } + + cursor.setBlockFormat(blockFormat); + } + } + + if (currentNode->displayMode == QTextHtmlElement::DisplayNone) { + if (currentNode->id == Html_title) + doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text); + // ignore explicitly 'invisible' elements + continue; + } + + if (processSpecialNodes() == ContinueWithNextNode) + continue; + + // make sure there's a block for 'Blah' after
    • foo
    Blah + if (blockTagClosed + && !hasBlock + && !currentNode->isBlock() + && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace() + && currentNode->displayMode == QTextHtmlElement::DisplayInline) { + + QTextBlockFormat block = currentNode->blockFormat; + block.setIndent(indent); + + appendBlock(block, currentNode->charFormat); + + hasBlock = true; + } + + if (currentNode->isBlock()) { + if (processBlockNode() == ContinueWithNextNode) + continue; + } + + if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) { + namedAnchors.append(currentNode->charFormat.anchorName()); + } + + if (appendNodeText()) + hasBlock = false; // if we actually appended text then we don't + // have an empty block anymore + } + + cursor.endEditBlock(); +} + +bool QTextHtmlImporter::appendNodeText() +{ + const int initialCursorPosition = cursor.position(); + QTextCharFormat format = currentNode->charFormat; + + if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap) + compressNextWhitespace = PreserveWhiteSpace; + + QString text = currentNode->text; + + QString textToInsert; + textToInsert.reserve(text.size()); + + for (int i = 0; i < text.length(); ++i) { + QChar ch = text.at(i); + + if (ch.isSpace() + && ch != QChar::Nbsp + && ch != QChar::ParagraphSeparator) { + + if (compressNextWhitespace == CollapseWhiteSpace) + compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next. + else if(compressNextWhitespace == RemoveWhiteSpace) + continue; + + if (wsm == QTextHtmlParserNode::WhiteSpacePre + || textEditMode + ) { + if (ch == QLatin1Char('\n')) { + if (textEditMode) + continue; + } else if (ch == QLatin1Char('\r')) { + continue; + } + } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) { + compressNextWhitespace = RemoveWhiteSpace; + if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap) + ch = QChar::Nbsp; + else + ch = QLatin1Char(' '); + } + } else { + compressNextWhitespace = PreserveWhiteSpace; + } + + if (ch == QLatin1Char('\n') + || ch == QChar::ParagraphSeparator) { + + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + textToInsert.clear(); + } + + QTextBlockFormat fmt = cursor.blockFormat(); + + if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) { + QTextBlockFormat tmp = fmt; + tmp.clearProperty(QTextFormat::BlockBottomMargin); + cursor.setBlockFormat(tmp); + } + + fmt.clearProperty(QTextFormat::BlockTopMargin); + appendBlock(fmt, cursor.charFormat()); + } else { + if (!namedAnchors.isEmpty()) { + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + textToInsert.clear(); + } + + format.setAnchor(true); + format.setAnchorNames(namedAnchors); + cursor.insertText(ch, format); + namedAnchors.clear(); + format.clearProperty(QTextFormat::IsAnchor); + format.clearProperty(QTextFormat::AnchorName); + } else { + textToInsert += ch; + } + } + } + + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + } + + return cursor.position() != initialCursorPosition; +} + +QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes() +{ + switch (currentNode->id) { + case Html_body: + if (currentNode->charFormat.background().style() != Qt::NoBrush) { + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setBackground(currentNode->charFormat.background()); + doc->rootFrame()->setFrameFormat(fmt); + const_cast(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush); + } + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_ol: + case Html_ul: { + QTextListFormat::Style style = currentNode->listStyle; + + if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) { + const QTextHtmlParserNode *n = &at(currentNode->parent); + while (n) { + if (n->id == Html_ul) { + style = nextListStyle(currentNode->listStyle); + } + if (n->parent) + n = &at(n->parent); + else + n = 0; + } + } + + QTextListFormat listFmt; + listFmt.setStyle(style); + if (!currentNode->textListNumberPrefix.isNull()) + listFmt.setNumberPrefix(currentNode->textListNumberPrefix); + if (!currentNode->textListNumberSuffix.isNull()) + listFmt.setNumberSuffix(currentNode->textListNumberSuffix); + + ++indent; + if (currentNode->hasCssListIndent) + listFmt.setIndent(currentNode->cssListIndent); + else + listFmt.setIndent(indent); + + List l; + l.format = listFmt; + l.listNode = currentNodeIdx; + lists.append(l); + compressNextWhitespace = RemoveWhiteSpace; + + // broken html:
      Text here
    • Foo + const QString simpl = currentNode->text.simplified(); + if (simpl.isEmpty() || simpl.at(0).isSpace()) + return ContinueWithNextNode; + break; + } + + case Html_table: { + Table t = scanTable(currentNodeIdx); + tables.append(t); + hasBlock = false; + compressNextWhitespace = RemoveWhiteSpace; + return ContinueWithNextNode; + } + + case Html_tr: + return ContinueWithNextNode; + + case Html_img: { + QTextImageFormat fmt; + fmt.setName(currentNode->imageName); + + fmt.merge(currentNode->charFormat); + + if (currentNode->imageWidth != -1) + fmt.setWidth(currentNode->imageWidth); + if (currentNode->imageHeight != -1) + fmt.setHeight(currentNode->imageHeight); + + cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat)); + + cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); + cursor.mergeCharFormat(currentNode->charFormat); + cursor.movePosition(QTextCursor::Right); + compressNextWhitespace = CollapseWhiteSpace; + + hasBlock = false; + return ContinueWithNextNode; + } + + case Html_hr: { + QTextBlockFormat blockFormat = currentNode->blockFormat; + blockFormat.setTopMargin(topMargin(currentNodeIdx)); + blockFormat.setBottomMargin(bottomMargin(currentNodeIdx)); + blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width); + if (hasBlock && importMode == ImportToDocument) + cursor.mergeBlockFormat(blockFormat); + else + appendBlock(blockFormat); + hasBlock = false; + compressNextWhitespace = RemoveWhiteSpace; + return ContinueWithNextNode; + } + + default: break; + } + return ContinueWithCurrentNode; +} + +// returns true if a block tag was closed +bool QTextHtmlImporter::closeTag() +{ + const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1); + const int endDepth = depth(currentNodeIdx) - 1; + int depth = this->depth(currentNodeIdx - 1); + bool blockTagClosed = false; + + while (depth > endDepth) { + Table *t = 0; + if (!tables.isEmpty()) + t = &tables.last(); + + switch (closedNode->id) { + case Html_tr: + if (t && !t->isTextFrame) { + ++t->currentRow; + + // for broken html with rowspans but missing tr tags + while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow) + ++t->currentCell; + } + + blockTagClosed = true; + break; + + case Html_table: + if (!t) + break; + indent = t->lastIndent; + + tables.resize(tables.size() - 1); + t = 0; + + if (tables.isEmpty()) { + cursor = doc->rootFrame()->lastCursorPosition(); + } else { + t = &tables.last(); + if (t->isTextFrame) + cursor = t->frame->lastCursorPosition(); + else if (!t->currentCell.atEnd()) + cursor = t->currentCell.cell().lastCursorPosition(); + } + + // we don't need an extra block after tables, so we don't + // claim to have closed one for the creation of a new one + // in import() + blockTagClosed = false; + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_th: + case Html_td: + if (t && !t->isTextFrame) + ++t->currentCell; + blockTagClosed = true; + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_ol: + case Html_ul: + if (lists.isEmpty()) + break; + lists.resize(lists.size() - 1); + --indent; + blockTagClosed = true; + break; + + case Html_br: + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_div: + if (closedNode->children.isEmpty()) + break; + // fall through + default: + if (closedNode->isBlock()) + blockTagClosed = true; + break; + } + + closedNode = &at(closedNode->parent); + --depth; + } + + return blockTagClosed; +} + +QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx) +{ + Table table; + table.columns = 0; + + QVector columnWidths; + + int tableHeaderRowCount = 0; + QVector rowNodes; + rowNodes.reserve(at(tableNodeIdx).children.count()); + foreach (int row, at(tableNodeIdx).children) + switch (at(row).id) { + case Html_tr: + rowNodes += row; + break; + case Html_thead: + case Html_tbody: + case Html_tfoot: + foreach (int potentialRow, at(row).children) + if (at(potentialRow).id == Html_tr) { + rowNodes += potentialRow; + if (at(row).id == Html_thead) + ++tableHeaderRowCount; + } + break; + default: break; + } + + QVector rowColSpans; + QVector rowColSpanForColumn; + + int effectiveRow = 0; + foreach (int row, rowNodes) { + int colsInRow = 0; + + foreach (int cell, at(row).children) + if (at(cell).isTableCell()) { + // skip all columns with spans from previous rows + while (colsInRow < rowColSpanForColumn.size()) { + const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow]; + + if (spanInfo.row + spanInfo.rowSpan > effectiveRow) { + Q_ASSERT(spanInfo.col == colsInRow); + colsInRow += spanInfo.colSpan; + } else + break; + } + + const QTextHtmlParserNode &c = at(cell); + const int currentColumn = colsInRow; + colsInRow += c.tableCellColSpan; + + RowColSpanInfo spanInfo; + spanInfo.row = effectiveRow; + spanInfo.col = currentColumn; + spanInfo.colSpan = c.tableCellColSpan; + spanInfo.rowSpan = c.tableCellRowSpan; + if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1) + rowColSpans.append(spanInfo); + + columnWidths.resize(qMax(columnWidths.count(), colsInRow)); + rowColSpanForColumn.resize(columnWidths.size()); + for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) { + if (columnWidths.at(i).type() == QTextLength::VariableLength) { + QTextLength w = c.width; + if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength) + w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan); + columnWidths[i] = w; + } + rowColSpanForColumn[i] = spanInfo; + } + } + + table.columns = qMax(table.columns, colsInRow); + + ++effectiveRow; + } + table.rows = effectiveRow; + + table.lastIndent = indent; + indent = 0; + + if (table.rows == 0 || table.columns == 0) + return table; + + QTextFrameFormat fmt; + const QTextHtmlParserNode &node = at(tableNodeIdx); + + if (!node.isTextFrame) { + QTextTableFormat tableFmt; + tableFmt.setCellSpacing(node.tableCellSpacing); + tableFmt.setCellPadding(node.tableCellPadding); + if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment)) + tableFmt.setAlignment(node.blockFormat.alignment()); + tableFmt.setColumns(table.columns); + tableFmt.setColumnWidthConstraints(columnWidths); + tableFmt.setHeaderRowCount(tableHeaderRowCount); + fmt = tableFmt; + } + + fmt.setTopMargin(topMargin(tableNodeIdx)); + fmt.setBottomMargin(bottomMargin(tableNodeIdx)); + fmt.setLeftMargin(leftMargin(tableNodeIdx) + + table.lastIndent * 40 // ##### not a good emulation + ); + fmt.setRightMargin(rightMargin(tableNodeIdx)); + + // compatibility + if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin()) + && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin()) + && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin())) + fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin()); + + fmt.setBorderStyle(node.borderStyle); + fmt.setBorderBrush(node.borderBrush); + fmt.setBorder(node.tableBorder); + fmt.setWidth(node.width); + fmt.setHeight(node.height); + if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy)) + fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy()); + + if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection)) + fmt.setLayoutDirection(node.blockFormat.layoutDirection()); + if (node.charFormat.background().style() != Qt::NoBrush) + fmt.setBackground(node.charFormat.background()); + fmt.setPosition(QTextFrameFormat::Position(node.cssFloat)); + + if (node.isTextFrame) { + if (node.isRootFrame) { + table.frame = cursor.currentFrame(); + table.frame->setFrameFormat(fmt); + } else + table.frame = cursor.insertFrame(fmt); + + table.isTextFrame = true; + } else { + const int oldPos = cursor.position(); + QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat()); + table.frame = textTable; + + for (int i = 0; i < rowColSpans.count(); ++i) { + const RowColSpanInfo &nfo = rowColSpans.at(i); + textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan); + } + + table.currentCell = TableCellIterator(textTable); + cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table + } + return table; +} + +QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode() +{ + QTextBlockFormat block; + QTextCharFormat charFmt; + bool modifiedBlockFormat = true; + bool modifiedCharFormat = true; + + if (currentNode->isTableCell() && !tables.isEmpty()) { + Table &t = tables.last(); + if (!t.isTextFrame && !t.currentCell.atEnd()) { + QTextTableCell cell = t.currentCell.cell(); + if (cell.isValid()) { + QTextTableCellFormat fmt = cell.format().toTableCellFormat(); + if (topPadding(currentNodeIdx) >= 0) + fmt.setTopPadding(topPadding(currentNodeIdx)); + if (bottomPadding(currentNodeIdx) >= 0) + fmt.setBottomPadding(bottomPadding(currentNodeIdx)); + if (leftPadding(currentNodeIdx) >= 0) + fmt.setLeftPadding(leftPadding(currentNodeIdx)); + if (rightPadding(currentNodeIdx) >= 0) + fmt.setRightPadding(rightPadding(currentNodeIdx)); + cell.setFormat(fmt); + + cursor.setPosition(cell.firstPosition()); + } + } + hasBlock = true; + compressNextWhitespace = RemoveWhiteSpace; + + if (currentNode->charFormat.background().style() != Qt::NoBrush) { + charFmt.setBackground(currentNode->charFormat.background()); + cursor.mergeBlockCharFormat(charFmt); + } + } + + if (hasBlock) { + block = cursor.blockFormat(); + charFmt = cursor.blockCharFormat(); + modifiedBlockFormat = false; + modifiedCharFormat = false; + } + + // collapse + { + qreal tm = qreal(topMargin(currentNodeIdx)); + if (tm > block.topMargin()) { + block.setTopMargin(tm); + modifiedBlockFormat = true; + } + } + + int bottomMargin = this->bottomMargin(currentNodeIdx); + + // for list items we may want to collapse with the bottom margin of the + // list. + const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0; + if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd) + && parentNode + && (parentNode->isListStart() || parentNode->id == Html_dl) + && (parentNode->children.last() == currentNodeIdx)) { + bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent)); + } + + if (block.bottomMargin() != bottomMargin) { + block.setBottomMargin(bottomMargin); + modifiedBlockFormat = true; + } + + { + const qreal lm = leftMargin(currentNodeIdx); + const qreal rm = rightMargin(currentNodeIdx); + + if (block.leftMargin() != lm) { + block.setLeftMargin(lm); + modifiedBlockFormat = true; + } + if (block.rightMargin() != rm) { + block.setRightMargin(rm); + modifiedBlockFormat = true; + } + } + + if (currentNode->id != Html_li + && indent != 0 + && (lists.isEmpty() + || !hasBlock + || !lists.last().list + || lists.last().list->itemNumber(cursor.block()) == -1 + ) + ) { + block.setIndent(indent); + modifiedBlockFormat = true; + } + + if (currentNode->blockFormat.propertyCount() > 0) { + modifiedBlockFormat = true; + block.merge(currentNode->blockFormat); + } + + if (currentNode->charFormat.propertyCount() > 0) { + modifiedCharFormat = true; + charFmt.merge(currentNode->charFormat); + } + + // #################### + // block.setFloatPosition(node->cssFloat); + + if (wsm == QTextHtmlParserNode::WhiteSpacePre) { + block.setNonBreakableLines(true); + modifiedBlockFormat = true; + } + + if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) { + block.setBackground(currentNode->charFormat.background()); + modifiedBlockFormat = true; + } + + if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) { + if (modifiedBlockFormat) + cursor.setBlockFormat(block); + if (modifiedCharFormat) + cursor.setBlockCharFormat(charFmt); + } else { + if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) { + cursor.setBlockFormat(block); + cursor.setBlockCharFormat(charFmt); + } else { + appendBlock(block, charFmt); + } + } + + if (currentNode->userState != -1) + cursor.block().setUserState(currentNode->userState); + + if (currentNode->id == Html_li && !lists.isEmpty()) { + List &l = lists.last(); + if (l.list) { + l.list->add(cursor.block()); + } else { + l.list = cursor.createList(l.format); + const qreal listTopMargin = topMargin(l.listNode); + if (listTopMargin > block.topMargin()) { + block.setTopMargin(listTopMargin); + cursor.mergeBlockFormat(block); + } + } + if (hasBlock) { + QTextBlockFormat fmt; + fmt.setIndent(0); + cursor.mergeBlockFormat(fmt); + } + } + + forceBlockMerging = false; + if (currentNode->id == Html_body || currentNode->id == Html_html) + forceBlockMerging = true; + + if (currentNode->isEmptyParagraph) { + hasBlock = false; + return ContinueWithNextNode; + } + + hasBlock = true; + blockTagClosed = false; + return ContinueWithCurrentNode; +} + +void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt) +{ + if (!namedAnchors.isEmpty()) { + charFmt.setAnchor(true); + charFmt.setAnchorNames(namedAnchors); + namedAnchors.clear(); + } + + cursor.insertBlock(format, charFmt); + + if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap) + compressNextWhitespace = RemoveWhiteSpace; +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text) + + Returns a QTextDocumentFragment based on the arbitrary piece of + HTML in the given \a text. The formatting is preserved as much as + possible; for example, "bold" will become a document + fragment with the text "bold" with a bold character format. +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html) +{ + return fromHtml(html, 0); +} + +/*! + \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider) + \since 4.2 + + Returns a QTextDocumentFragment based on the arbitrary piece of + HTML in the given \a text. The formatting is preserved as much as + possible; for example, "bold" will become a document + fragment with the text "bold" with a bold character format. + + If the provided HTML contains references to external resources such as imported style sheets, then + they will be loaded through the \a resourceProvider. +*/ + +QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider) +{ + QTextDocumentFragment res; + res.d = new QTextDocumentFragmentPrivate; + + QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider); + importer.import(); + return res; +} + +QT_END_NAMESPACE +#endif // QT_NO_TEXTHTMLPARSER diff --git a/src/gui/text/qtextdocumentfragment.h b/src/gui/text/qtextdocumentfragment.h new file mode 100644 index 0000000000..976c538d0f --- /dev/null +++ b/src/gui/text/qtextdocumentfragment.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTFRAGMENT_H +#define QTEXTDOCUMENTFRAGMENT_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextStream; +class QTextDocument; +class QTextDocumentFragmentPrivate; +class QTextCursor; + +class Q_GUI_EXPORT QTextDocumentFragment +{ +public: + QTextDocumentFragment(); + explicit QTextDocumentFragment(const QTextDocument *document); + explicit QTextDocumentFragment(const QTextCursor &range); + QTextDocumentFragment(const QTextDocumentFragment &rhs); + QTextDocumentFragment &operator=(const QTextDocumentFragment &rhs); + ~QTextDocumentFragment(); + + bool isEmpty() const; + + QString toPlainText() const; +#ifndef QT_NO_TEXTHTMLPARSER + QString toHtml() const; + QString toHtml(const QByteArray &encoding) const; +#endif // QT_NO_TEXTHTMLPARSER + + static QTextDocumentFragment fromPlainText(const QString &plainText); +#ifndef QT_NO_TEXTHTMLPARSER + static QTextDocumentFragment fromHtml(const QString &html); + static QTextDocumentFragment fromHtml(const QString &html, const QTextDocument *resourceProvider); +#endif // QT_NO_TEXTHTMLPARSER + +private: + QTextDocumentFragmentPrivate *d; + friend class QTextCursor; + friend class QTextDocumentWriter; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTDOCUMENTFRAGMENT_H diff --git a/src/gui/text/qtextdocumentfragment_p.h b/src/gui/text/qtextdocumentfragment_p.h new file mode 100644 index 0000000000..5b04862f54 --- /dev/null +++ b/src/gui/text/qtextdocumentfragment_p.h @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTFRAGMENT_P_H +#define QTEXTDOCUMENTFRAGMENT_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 "QtGui/qtextdocument.h" +#include "private/qtexthtmlparser_p.h" +#include "private/qtextdocument_p.h" +#include "QtGui/qtexttable.h" +#include "QtCore/qatomic.h" +#include "QtCore/qlist.h" +#include "QtCore/qmap.h" +#include "QtCore/qpointer.h" +#include "QtCore/qvarlengtharray.h" +#include "QtCore/qdatastream.h" + +QT_BEGIN_NAMESPACE + +class QTextDocumentFragmentPrivate; + +class QTextCopyHelper +{ +public: + QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat = false, const QTextCharFormat &fmt = QTextCharFormat()); + + void copy(); + +private: + void appendFragments(int pos, int endPos); + int appendFragment(int pos, int endPos, int objectIndex = -1); + int convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet = -1); + inline int convertFormatIndex(int oldFormatIndex, int objectIndexToSet = -1) + { return convertFormatIndex(src->formatCollection()->format(oldFormatIndex), objectIndexToSet); } + inline QTextFormat convertFormat(const QTextFormat &fmt) + { return dst->formatCollection()->format(convertFormatIndex(fmt)); } + + int insertPos; + + bool forceCharFormat; + int primaryCharFormatIndex; + + QTextCursor cursor; + QTextDocumentPrivate *dst; + QTextDocumentPrivate *src; + QTextFormatCollection &formatCollection; + const QString originalText; + QMap objectIndexMap; +}; + +class QTextDocumentFragmentPrivate +{ +public: + QTextDocumentFragmentPrivate(const QTextCursor &cursor = QTextCursor()); + inline ~QTextDocumentFragmentPrivate() { delete doc; } + + void insert(QTextCursor &cursor) const; + + QAtomicInt ref; + QTextDocument *doc; + + uint importedFromPlainText : 1; +private: + Q_DISABLE_COPY(QTextDocumentFragmentPrivate) +}; + +#ifndef QT_NO_TEXTHTMLPARSER + +class QTextHtmlImporter : public QTextHtmlParser +{ + struct Table; +public: + enum ImportMode { + ImportToFragment, + ImportToDocument + }; + + QTextHtmlImporter(QTextDocument *_doc, const QString &html, + ImportMode mode, + const QTextDocument *resourceProvider = 0); + + void import(); + +private: + bool closeTag(); + + Table scanTable(int tableNodeIdx); + + enum ProcessNodeResult { ContinueWithNextNode, ContinueWithCurrentNode }; + + void appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt = QTextCharFormat()); + bool appendNodeText(); + + ProcessNodeResult processBlockNode(); + ProcessNodeResult processSpecialNodes(); + + struct List + { + inline List() : listNode(0) {} + QTextListFormat format; + int listNode; + QPointer list; + }; + QVector lists; + int indent; + + // insert a named anchor the next time we emit a char format, + // either in a block or in regular text + QStringList namedAnchors; + +#ifdef Q_CC_SUN + friend struct QTextHtmlImporter::Table; +#endif + struct TableCellIterator + { + inline TableCellIterator(QTextTable *t = 0) : table(t), row(0), column(0) {} + + inline TableCellIterator &operator++() { + if (atEnd()) + return *this; + do { + const QTextTableCell cell = table->cellAt(row, column); + if (!cell.isValid()) + break; + column += cell.columnSpan(); + if (column >= table->columns()) { + column = 0; + ++row; + } + } while (row < table->rows() && table->cellAt(row, column).row() != row); + + return *this; + } + + inline bool atEnd() const { return table == 0 || row >= table->rows(); } + + QTextTableCell cell() const { return table->cellAt(row, column); } + + QTextTable *table; + int row; + int column; + }; + + friend struct Table; + struct Table + { + Table() : isTextFrame(false), rows(0), columns(0), currentRow(0), lastIndent(0) {} + QPointer frame; + bool isTextFrame; + int rows; + int columns; + int currentRow; // ... for buggy html (see html_skipCell testcase) + TableCellIterator currentCell; + int lastIndent; + }; + QVector
tables; + + struct RowColSpanInfo + { + int row, col; + int rowSpan, colSpan; + }; + + enum WhiteSpace + { + RemoveWhiteSpace, + CollapseWhiteSpace, + PreserveWhiteSpace + }; + + WhiteSpace compressNextWhitespace; + + QTextDocument *doc; + QTextCursor cursor; + QTextHtmlParserNode::WhiteSpaceMode wsm; + ImportMode importMode; + bool hasBlock; + bool forceBlockMerging; + bool blockTagClosed; + int currentNodeIdx; + const QTextHtmlParserNode *currentNode; +}; + +QT_END_NAMESPACE +#endif // QT_NO_TEXTHTMLPARSER + +#endif // QTEXTDOCUMENTFRAGMENT_P_H diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp new file mode 100644 index 0000000000..ce157be254 --- /dev/null +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -0,0 +1,3263 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocumentlayout_p.h" +#include "qtextdocument_p.h" +#include "qtextimagehandler_p.h" +#include "qtexttable.h" +#include "qtextlist.h" +#include "qtextengine_p.h" +#include "private/qcssutil_p.h" + +#include "qabstracttextdocumentlayout_p.h" +#include "qcssparser_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/qfunctions_p.h" + +// #define LAYOUT_DEBUG + +#ifdef LAYOUT_DEBUG +#define LDEBUG qDebug() +#define INC_INDENT debug_indent += " " +#define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2) +#else +#define LDEBUG if(0) qDebug() +#define INC_INDENT do {} while(0) +#define DEC_INDENT do {} while(0) +#endif + +QT_BEGIN_NAMESPACE + +// ################ should probably add frameFormatChange notification! + +struct QTextLayoutStruct; + +class QTextFrameData : public QTextFrameLayoutData +{ +public: + QTextFrameData(); + + // relative to parent frame + QFixedPoint position; + QFixedSize size; + + // contents starts at (margin+border/margin+border) + QFixed topMargin; + QFixed bottomMargin; + QFixed leftMargin; + QFixed rightMargin; + QFixed border; + QFixed padding; + // contents width includes padding (as we need to treat this on a per cell basis for tables) + QFixed contentsWidth; + QFixed contentsHeight; + QFixed oldContentsWidth; + + // accumulated margins + QFixed effectiveTopMargin; + QFixed effectiveBottomMargin; + + QFixed minimumWidth; + QFixed maximumWidth; + + QTextLayoutStruct *currentLayoutStruct; + + bool sizeDirty; + bool layoutDirty; + + QList > floats; +}; + +QTextFrameData::QTextFrameData() + : maximumWidth(QFIXED_MAX), + currentLayoutStruct(0), sizeDirty(true), layoutDirty(true) +{ +} + +struct QTextLayoutStruct { + QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false) + {} + QTextFrame *frame; + QFixed x_left; + QFixed x_right; + QFixed frameY; // absolute y position of the current frame + QFixed y; // always relative to the current frame + QFixed contentsWidth; + QFixed minimumWidth; + QFixed maximumWidth; + bool fullLayout; + QList pendingFloats; + QFixed pageHeight; + QFixed pageBottom; + QFixed pageTopMargin; + QFixed pageBottomMargin; + QRectF updateRect; + QRectF updateRectForFloats; + + inline void addUpdateRectForFloat(const QRectF &rect) { + if (updateRectForFloats.isValid()) + updateRectForFloats |= rect; + else + updateRectForFloats = rect; + } + + inline QFixed absoluteY() const + { return frameY + y; } + + inline int currentPage() const + { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); } + + inline void newPage() + { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; } +}; + +class QTextTableData : public QTextFrameData +{ +public: + QFixed cellSpacing, cellPadding; + qreal deviceScale; + QVector minWidths; + QVector maxWidths; + QVector widths; + QVector heights; + QVector columnPositions; + QVector rowPositions; + + QVector cellVerticalOffsets; + + QFixed headerHeight; + + // maps from cell index (row + col * rowCount) to child frames belonging to + // the specific cell + QMultiHash childFrameMap; + + inline QFixed cellWidth(int column, int colspan) const + { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1) + - columnPositions.at(column); } + + inline void calcRowPosition(int row) + { + if (row > 0) + rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border; + } + + QRectF cellRect(const QTextTableCell &cell) const; + + inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const + { + QVariant v = format.property(property); + if (v.isNull()) { + return cellPadding; + } else { + Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float); + return QFixed::fromReal(v.toReal() * deviceScale); + } + } + + inline QFixed topPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellTopPadding); + } + + inline QFixed bottomPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellBottomPadding); + } + + inline QFixed leftPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellLeftPadding); + } + + inline QFixed rightPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellRightPadding); + } + + inline QFixedPoint cellPosition(const QTextTableCell &cell) const + { + const QTextFormat fmt = cell.format(); + return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt)); + } + + void updateTableSize(); + +private: + inline QFixedPoint cellPosition(int row, int col) const + { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); } +}; + +static QTextFrameData *createData(QTextFrame *f) +{ + QTextFrameData *data; + if (qobject_cast(f)) + data = new QTextTableData; + else + data = new QTextFrameData; + f->setLayoutData(data); + return data; +} + +static inline QTextFrameData *data(QTextFrame *f) +{ + QTextFrameData *data = static_cast(f->layoutData()); + if (!data) + data = createData(f); + return data; +} + +static bool isFrameFromInlineObject(QTextFrame *f) +{ + return f->firstPosition() > f->lastPosition(); +} + +void QTextTableData::updateTableSize() +{ + const QFixed effectiveTopMargin = this->topMargin + border + padding; + const QFixed effectiveBottomMargin = this->bottomMargin + border + padding; + const QFixed effectiveLeftMargin = this->leftMargin + border + padding; + const QFixed effectiveRightMargin = this->rightMargin + border + padding; + size.height = contentsHeight == -1 + ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin + : effectiveTopMargin + contentsHeight + effectiveBottomMargin; + size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin; +} + +QRectF QTextTableData::cellRect(const QTextTableCell &cell) const +{ + const int row = cell.row(); + const int rowSpan = cell.rowSpan(); + const int column = cell.column(); + const int colSpan = cell.columnSpan(); + + return QRectF(columnPositions.at(column).toReal(), + rowPositions.at(row).toReal(), + (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(), + (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal()); +} + +static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt) +{ + return !nextIt.atEnd() + && qobject_cast(nextIt.currentFrame()) + && block.isValid() + && block.length() == 1 + && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth) + && !format.hasProperty(QTextFormat::BackgroundBrush) + && nextIt.currentFrame()->firstPosition() == block.position() + 1 + ; +} + +static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it) +{ + QTextFrame::Iterator next = it; ++next; + if (it.currentFrame()) + return false; + QTextBlock block = it.currentBlock(); + return isEmptyBlockBeforeTable(block, block.blockFormat(), next); +} + +static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame) +{ + return qobject_cast(previousFrame) + && block.isValid() + && block.length() == 1 + && previousFrame->lastPosition() == block.position() - 1 + ; +} + +static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame) +{ + return qobject_cast(previousFrame) + && block.isValid() + && block.length() > 1 + && block.text().at(0) == QChar::LineSeparator + && previousFrame->lastPosition() == block.position() - 1 + ; +} + +/* + +Optimization strategies: + +HTML layout: + +* Distinguish between normal and special flow. For normal flow the condition: + y1 > y2 holds for all blocks with b1.key() > b2.key(). +* Special flow is: floats, table cells + +* Normal flow within table cells. Tables (not cells) are part of the normal flow. + + +* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks. +* If height doesn't change, no need to do anything + +Table cells: + +* If minWidth of cell changes, recalculate table width, relayout if needed. +* What about maxWidth when doing auto layout? + +Floats: +* need fixed or proportional width, otherwise don't float! +* On width/height change relayout surrounding paragraphs. + +Document width change: +* full relayout needed + + +Float handling: + +* Floats are specified by a special format object. +* currently only floating images are implemented. + +*/ + +/* + + On the table layouting: + + +---[ table border ]------------------------- + | [ cell spacing ] + | +------[ cell border ]-----+ +-------- + | | | | + | | + | | + | | + | + + rowPositions[i] and columnPositions[i] point at the cell content + position. So for example the left border is drawn at + x = columnPositions[i] - fd->border and similar for y. + +*/ + +struct QCheckPoint +{ + QFixed y; + QFixed frameY; // absolute y position of the current frame + int positionInFrame; + QFixed minimumWidth; + QFixed maximumWidth; + QFixed contentsWidth; +}; +Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE); + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y) +{ + return checkPoint.y < y; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos) +{ + return checkPoint.positionInFrame < pos; +} + +static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF()) +{ + p->save(); + if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) { + if (!gradientRect.isNull()) { + QTransform m; + m.translate(gradientRect.left(), gradientRect.top()); + m.scale(gradientRect.width(), gradientRect.height()); + brush.setTransform(m); + const_cast(brush.gradient())->setCoordinateMode(QGradient::LogicalMode); + } + } else { + p->setBrushOrigin(origin); + } + p->fillRect(rect, brush); + p->restore(); +} + +class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate +{ + Q_DECLARE_PUBLIC(QTextDocumentLayout) +public: + QTextDocumentLayoutPrivate(); + + QTextOption::WrapMode wordWrapMode; +#ifdef LAYOUT_DEBUG + mutable QString debug_indent; +#endif + + int fixedColumnWidth; + int cursorWidth; + + QSizeF lastReportedSize; + QRectF viewportRect; + QRectF clipRect; + + mutable int currentLazyLayoutPosition; + mutable int lazyLayoutStepSize; + QBasicTimer layoutTimer; + mutable QBasicTimer sizeChangedTimer; + uint showLayoutProgress : 1; + uint insideDocumentChange : 1; + + int lastPageCount; + qreal idealWidth; + bool contentHasAlignment; + + QFixed blockIndent(const QTextBlockFormat &blockFormat) const; + + void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame *f) const; + void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame::Iterator it, const QList &floats, QTextBlock *cursorBlockNeedingRepaint) const; + void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, bool inRootFrame) const; + void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, const QTextCharFormat *selectionFormat) const; + void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context, + QTextTable *table, QTextTableData *td, int r, int c, + QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const; + void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border, + const QBrush &brush, QTextFrameFormat::BorderStyle style) const; + void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const; + + enum HitPoint { + PointBefore, + PointAfter, + PointInside, + PointExact + }; + HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + + QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width, + int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY, + bool withPageBreaks); + void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos); + QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY); + + void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0); + + // calls the next one + QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0); + QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0); + + void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat, + QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat); + void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0); + void pageBreakInsideTable(QTextTable *table, QTextLayoutStruct *layoutStruct); + + + void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const; + QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const; + + QVector checkPoints; + + QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const; + QTextFrame::Iterator frameIteratorForTextPosition(int position) const; + + void ensureLayouted(QFixed y) const; + void ensureLayoutedByPosition(int position) const; + inline void ensureLayoutFinished() const + { ensureLayoutedByPosition(INT_MAX); } + void layoutStep() const; + + QRectF frameBoundingRectInternal(QTextFrame *frame) const; + + qreal scaleToDevice(qreal value) const; + QFixed scaleToDevice(QFixed value) const; +}; + +QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate() + : fixedColumnWidth(-1), + cursorWidth(1), + currentLazyLayoutPosition(-1), + lazyLayoutStepSize(1000), + lastPageCount(-1) +{ + showLayoutProgress = true; + insideDocumentChange = false; + idealWidth = 0; + contentHasAlignment = false; +} + +QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const +{ + QTextFrame *rootFrame = document->rootFrame(); + + if (checkPoints.isEmpty() + || y < 0 || y > data(rootFrame)->size.height) + return rootFrame->begin(); + + QVector::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y); + if (checkPoint == checkPoints.end()) + return rootFrame->begin(); + + if (checkPoint != checkPoints.begin()) + --checkPoint; + + const int position = rootFrame->firstPosition() + checkPoint->positionInFrame; + return frameIteratorForTextPosition(position); +} + +QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const +{ + QTextFrame *rootFrame = docPrivate->rootFrame(); + + const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap(); + const int begin = map.findNode(rootFrame->firstPosition()); + const int end = map.findNode(rootFrame->lastPosition()+1); + + const int block = map.findNode(position); + const int blockPos = map.position(block); + + QTextFrame::iterator it(rootFrame, block, begin, end); + + QTextFrame *containingFrame = docPrivate->frameAt(blockPos); + if (containingFrame != rootFrame) { + while (containingFrame->parentFrame() != rootFrame) { + containingFrame = containingFrame->parentFrame(); + Q_ASSERT(containingFrame); + } + + it.cf = containingFrame; + it.cb = 0; + } + + return it; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + QTextFrameData *fd = data(frame); + // ######### + if (fd->layoutDirty) + return PointAfter; + Q_ASSERT(!fd->layoutDirty); + Q_ASSERT(!fd->sizeDirty); + const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y); + + QTextFrame *rootFrame = docPrivate->rootFrame(); + +// LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point +// << "position" << fd->position << "size" << fd->size; + if (frame != rootFrame) { + if (relativePoint.y < 0 || relativePoint.x < 0) { + *position = frame->firstPosition() - 1; +// LDEBUG << "before pos=" << *position; + return PointBefore; + } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) { + *position = frame->lastPosition() + 1; +// LDEBUG << "after pos=" << *position; + return PointAfter; + } + } + + if (isFrameFromInlineObject(frame)) { + *position = frame->firstPosition() - 1; + return PointExact; + } + + if (QTextTable *table = qobject_cast(frame)) { + const int rows = table->rows(); + const int columns = table->columns(); + QTextTableData *td = static_cast(data(table)); + + if (!td->childFrameMap.isEmpty()) { + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + if (cell.row() != r || cell.column() != c) + continue; + + QRectF cellRect = td->cellRect(cell); + const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft()); + const QFixedPoint pointInCell = relativePoint - cellPos; + + const QList childFrames = td->childFrameMap.values(r + c * rows); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *child = childFrames.at(i); + if (isFrameFromInlineObject(child) + && child->frameFormat().position() != QTextFrameFormat::InFlow + && hitTest(child, pointInCell, position, l, accuracy) == PointExact) + { + return PointExact; + } + } + } + } + } + + return hitTest(table, relativePoint, position, l, accuracy); + } + + const QList childFrames = frame->childFrames(); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *child = childFrames.at(i); + if (isFrameFromInlineObject(child) + && child->frameFormat().position() != QTextFrameFormat::InFlow + && hitTest(child, relativePoint, position, l, accuracy) == PointExact) + { + return PointExact; + } + } + + QTextFrame::Iterator it = frame->begin(); + + if (frame == rootFrame) { + it = frameIteratorForYPosition(relativePoint.y); + + Q_ASSERT(it.parentFrame() == frame); + } + + if (it.currentFrame()) + *position = it.currentFrame()->firstPosition(); + else + *position = it.currentBlock().position(); + + return hitTest(it, PointBefore, relativePoint, position, l, accuracy); +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + INC_INDENT; + + for (; !it.atEnd(); ++it) { + QTextFrame *c = it.currentFrame(); + HitPoint hp; + int pos = -1; + if (c) { + hp = hitTest(c, p, &pos, l, accuracy); + } else { + hp = hitTest(it.currentBlock(), p, &pos, l, accuracy); + } + if (hp >= PointInside) { + if (isEmptyBlockBeforeTable(it)) + continue; + hit = hp; + *position = pos; + break; + } + if (hp == PointBefore && pos < *position) { + *position = pos; + hit = hp; + } else if (hp == PointAfter && pos > *position) { + *position = pos; + hit = hp; + } + } + + DEC_INDENT; +// LDEBUG << "inside=" << hit << " pos=" << *position; + return hit; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + QTextTableData *td = static_cast(data(table)); + + QVector::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y); + if (rowIt == td->rowPositions.constEnd()) { + rowIt = td->rowPositions.constEnd() - 1; + } else if (rowIt != td->rowPositions.constBegin()) { + --rowIt; + } + + QVector::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x); + if (colIt == td->columnPositions.constEnd()) { + colIt = td->columnPositions.constEnd() - 1; + } else if (colIt != td->columnPositions.constBegin()) { + --colIt; + } + + QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(), + colIt - td->columnPositions.constBegin()); + if (!cell.isValid()) + return PointBefore; + + *position = cell.firstPosition(); + + HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy); + + if (hp == PointExact) + return hp; + if (hp == PointAfter) + *position = cell.lastPosition(); + return PointInside; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, + Qt::HitTestAccuracy accuracy) const +{ + QTextLayout *tl = bl.layout(); + QRectF textrect = tl->boundingRect(); + textrect.translate(tl->position()); +// LDEBUG << " checking block" << bl.position() << "point=" << point +// << " tlrect" << textrect; + *position = bl.position(); + if (point.y.toReal() < textrect.top()) { +// LDEBUG << " before pos=" << *position; + return PointBefore; + } else if (point.y.toReal() > textrect.bottom()) { + *position += bl.length(); +// LDEBUG << " after pos=" << *position; + return PointAfter; + } + + QPointF pos = point.toPointF() - tl->position(); + + // ### rtl? + + HitPoint hit = PointInside; + *l = tl; + int off = 0; + for (int i = 0; i < tl->lineCount(); ++i) { + QTextLine line = tl->lineAt(i); + const QRectF lr = line.naturalTextRect(); + if (lr.top() > pos.y()) { + off = qMin(off, line.textStart()); + } else if (lr.bottom() <= pos.y()) { + off = qMax(off, line.textStart() + line.textLength()); + } else { + if (lr.left() <= pos.x() && lr.right() >= pos.x()) + hit = PointExact; + // when trying to hit an anchor we want it to hit not only in the left + // half + if (accuracy == Qt::ExactHit) + off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter); + else + off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters); + break; + } + } + *position += off; + +// LDEBUG << " inside=" << hit << " pos=" << *position; + return hit; +} + +// ### could be moved to QTextBlock +QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const +{ + qreal indent = blockFormat.indent(); + + QTextObject *object = document->objectForFormat(blockFormat); + if (object) + indent += object->format().toListFormat().indent(); + + if (qIsNull(indent)) + return 0; + + qreal scale = 1; + if (paintDevice) { + scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi()); + } + + return QFixed::fromReal(indent * scale * document->indentWidth()); +} + +void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, + qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const +{ + const qreal pageHeight = document->pageSize().height(); + const int topPage = pageHeight > 0 ? static_cast(rect.top() / pageHeight) : 0; + const int bottomPage = pageHeight > 0 ? static_cast((rect.bottom() + border) / pageHeight) : 0; + +#ifndef QT_NO_CSSPARSER + QCss::BorderStyle cssStyle = static_cast(style + 1); +#endif //QT_NO_CSSPARSER + + bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing); + painter->setRenderHint(QPainter::Antialiasing); + + for (int i = topPage; i <= bottomPage; ++i) { + QRectF clipped = rect.toRect(); + + if (topPage != bottomPage) { + clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border)); + clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin)); + + if (clipped.bottom() <= clipped.top()) + continue; + } +#ifndef QT_NO_CSSPARSER + qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush); + qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush); + qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush); + qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush); +#else + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(brush); + painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border)); + painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border)); + painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom())); + painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border)); + painter->restore(); +#endif //QT_NO_CSSPARSER + } + if (turn_off_antialiasing) + painter->setRenderHint(QPainter::Antialiasing, false); +} + +void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const +{ + + const QBrush bg = frame->frameFormat().background(); + if (bg != Qt::NoBrush) { + QRectF bgRect = rect; + bgRect.adjust((fd->leftMargin + fd->border).toReal(), + (fd->topMargin + fd->border).toReal(), + - (fd->rightMargin + fd->border).toReal(), + - (fd->bottomMargin + fd->border).toReal()); + + QRectF gradientRect; // invalid makes it default to bgRect + QPointF origin = bgRect.topLeft(); + if (!frame->parentFrame()) { + bgRect = clip; + gradientRect.setWidth(painter->device()->width()); + gradientRect.setHeight(painter->device()->height()); + } + fillBackground(painter, bgRect, bg, origin, gradientRect); + } + if (fd->border != 0) { + painter->save(); + painter->setBrush(Qt::lightGray); + painter->setPen(Qt::NoPen); + + const qreal leftEdge = rect.left() + fd->leftMargin.toReal(); + const qreal border = fd->border.toReal(); + const qreal topMargin = fd->topMargin.toReal(); + const qreal leftMargin = fd->leftMargin.toReal(); + const qreal bottomMargin = fd->bottomMargin.toReal(); + const qreal rightMargin = fd->rightMargin.toReal(); + const qreal w = rect.width() - 2 * border - leftMargin - rightMargin; + const qreal h = rect.height() - 2 * border - topMargin - bottomMargin; + + drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border), + fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(), + border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle()); + + painter->restore(); + } +} + +static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context, + const QTextTableCell &cell, + int r, int c, + const int *selectedTableCells) +{ + for (int i = 0; i < cell_context.selections.size(); ++i) { + int row_start = selectedTableCells[i * 4]; + int col_start = selectedTableCells[i * 4 + 1]; + int num_rows = selectedTableCells[i * 4 + 2]; + int num_cols = selectedTableCells[i * 4 + 3]; + + if (row_start != -1) { + if (r >= row_start && r < row_start + num_rows + && c >= col_start && c < col_start + num_cols) + { + int firstPosition = cell.firstPosition(); + int lastPosition = cell.lastPosition(); + + // make sure empty cells are still selected + if (firstPosition == lastPosition) + ++lastPosition; + + cell_context.selections[i].cursor.setPosition(firstPosition); + cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor); + } else { + cell_context.selections[i].cursor.clearSelection(); + } + } + + // FullWidthSelection is not useful for tables + cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection); + } +} + +void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame *frame) const +{ + QTextFrameData *fd = data(frame); + // ####### + if (fd->layoutDirty) + return; + Q_ASSERT(!fd->sizeDirty); + Q_ASSERT(!fd->layoutDirty); + + const QPointF off = offset + fd->position.toPointF(); + if (context.clip.isValid() + && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top() + || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left())) + return; + +// LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset; +// INC_INDENT; + + // if the cursor is /on/ a table border we may need to repaint it + // afterwards, as we usually draw the decoration first + QTextBlock cursorBlockNeedingRepaint; + QPointF offsetOfRepaintedCursorBlock = off; + + QTextTable *table = qobject_cast(frame); + const QRectF frameRect(off, fd->size.toSizeF()); + + if (table) { + const int rows = table->rows(); + const int columns = table->columns(); + QTextTableData *td = static_cast(data(table)); + + QVarLengthArray selectedTableCells(context.selections.size() * 4); + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i); + int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1; + + if (s.cursor.currentTable() == table) + s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + selectedTableCells[i * 4] = row_start; + selectedTableCells[i * 4 + 1] = col_start; + selectedTableCells[i * 4 + 2] = num_rows; + selectedTableCells[i * 4 + 3] = num_cols; + } + + QFixed pageHeight = QFixed::fromReal(document->pageSize().height()); + if (pageHeight <= 0) + pageHeight = QFIXED_MAX; + + const int tableStartPage = (td->position.y / pageHeight).truncate(); + const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate(); + + qreal border = td->border.toReal(); + drawFrameDecoration(painter, frame, fd, context.clip, frameRect); + + // draw the table headers + const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1); + int page = tableStartPage + 1; + while (page <= tableEndPage) { + const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border; + const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal(); + for (int r = 0; r < headerRowCount; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + QAbstractTextDocumentLayout::PaintContext cell_context = context; + adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data()); + QRectF cellRect = td->cellRect(cell); + + cellRect.translate(off.x(), headerOffset); + // we need to account for the cell border in the clipping test + int leftAdjust = qMin(qreal(0), 1 - border); + if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip)) + continue; + + drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint, + &offsetOfRepaintedCursorBlock); + } + } + ++page; + } + + int firstRow = 0; + int lastRow = rows; + + if (context.clip.isValid()) { + QVector::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y())); + if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) { + --rowIt; + firstRow = rowIt - td->rowPositions.constBegin(); + } + + rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y())); + if (rowIt != td->rowPositions.constEnd()) { + ++rowIt; + lastRow = rowIt - td->rowPositions.constBegin(); + } + } + + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(firstRow, c); + firstRow = qMin(firstRow, cell.row()); + } + + for (int r = firstRow; r < lastRow; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + QAbstractTextDocumentLayout::PaintContext cell_context = context; + adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data()); + QRectF cellRect = td->cellRect(cell); + + cellRect.translate(off); + // we need to account for the cell border in the clipping test + int leftAdjust = qMin(qreal(0), 1 - border); + if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip)) + continue; + + drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint, + &offsetOfRepaintedCursorBlock); + } + } + + } else { + drawFrameDecoration(painter, frame, fd, context.clip, frameRect); + + QTextFrame::Iterator it = frame->begin(); + + if (frame == docPrivate->rootFrame()) + it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top())); + + QList floats; + for (int i = 0; i < fd->floats.count(); ++i) + floats.append(fd->floats.at(i)); + + drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint); + } + + if (cursorBlockNeedingRepaint.isValid()) { + const QPen oldPen = painter->pen(); + painter->setPen(context.palette.color(QPalette::Text)); + const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position(); + cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock, + cursorPos, cursorWidth); + painter->setPen(oldPen); + } + +// DEC_INDENT; + + return; +} + +void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context, + QTextTable *table, QTextTableData *td, int r, int c, + QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const +{ + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + return; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + return; + } + + QTextFormat fmt = cell.format(); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed topPadding = td->topPadding(fmt); + + if (td->border != 0) { + const QBrush oldBrush = painter->brush(); + const QPen oldPen = painter->pen(); + + const qreal border = td->border.toReal(); + + QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border); + + // invert the border style for cells + QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle(); + switch (cellBorder) { + case QTextFrameFormat::BorderStyle_Inset: + cellBorder = QTextFrameFormat::BorderStyle_Outset; + break; + case QTextFrameFormat::BorderStyle_Outset: + cellBorder = QTextFrameFormat::BorderStyle_Inset; + break; + case QTextFrameFormat::BorderStyle_Groove: + cellBorder = QTextFrameFormat::BorderStyle_Ridge; + break; + case QTextFrameFormat::BorderStyle_Ridge: + cellBorder = QTextFrameFormat::BorderStyle_Groove; + break; + default: + break; + } + + qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal(); + qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal(); + + const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1); + if (r >= headerRowCount) + topMargin += td->headerHeight.toReal(); + + drawBorder(painter, borderRect, topMargin, bottomMargin, + border, table->format().borderBrush(), cellBorder); + + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + + const QBrush bg = cell.format().background(); + const QPointF brushOrigin = painter->brushOrigin(); + if (bg.style() != Qt::NoBrush) { + fillBackground(painter, cellRect, bg, cellRect.topLeft()); + + if (bg.style() > Qt::SolidPattern) + painter->setBrushOrigin(cellRect.topLeft()); + } + + const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns()); + + const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(), + cellRect.top() + (topPadding + verticalOffset).toReal()); + + QTextBlock repaintBlock; + drawFlow(cellPos, painter, cell_context, cell.begin(), + td->childFrameMap.values(r + c * table->rows()), + &repaintBlock); + if (repaintBlock.isValid()) { + *cursorBlockNeedingRepaint = repaintBlock; + *cursorBlockOffset = cellPos; + } + + if (bg.style() > Qt::SolidPattern) + painter->setBrushOrigin(brushOrigin); +} + +void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame::Iterator it, const QList &floats, QTextBlock *cursorBlockNeedingRepaint) const +{ + Q_Q(const QTextDocumentLayout); + const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0); + + QVector::ConstIterator lastVisibleCheckPoint = checkPoints.end(); + if (inRootFrame && context.clip.isValid()) { + lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom())); + } + + QTextBlock previousBlock; + QTextFrame *previousFrame = 0; + + for (; !it.atEnd(); ++it) { + QTextFrame *c = it.currentFrame(); + + if (inRootFrame && !checkPoints.isEmpty()) { + int currentPosInDoc; + if (c) + currentPosInDoc = c->firstPosition(); + else + currentPosInDoc = it.currentBlock().position(); + + // if we're past what is already laid out then we're better off + // not trying to draw things that may not be positioned correctly yet + if (currentPosInDoc >= checkPoints.last().positionInFrame) + break; + + if (lastVisibleCheckPoint != checkPoints.end() + && context.clip.isValid() + && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame + ) + break; + } + + if (c) + drawFrame(offset, painter, context, c); + else { + QAbstractTextDocumentLayout::PaintContext pc = context; + if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame)) + pc.selections.clear(); + drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame); + } + + // when entering a table and the previous block is empty + // then layoutFlow 'hides' the block that just causes a + // new line by positioning it /on/ the table border. as we + // draw that block before the table itself the decoration + // 'overpaints' the cursor and we need to paint it afterwards + // again + if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it) + && previousBlock.contains(context.cursorPosition) + ) { + *cursorBlockNeedingRepaint = previousBlock; + } + + previousBlock = it.currentBlock(); + previousFrame = c; + } + + for (int i = 0; i < floats.count(); ++i) { + QTextFrame *frame = floats.at(i); + if (!isFrameFromInlineObject(frame) + || frame->frameFormat().position() == QTextFrameFormat::InFlow) + continue; + + const int pos = frame->firstPosition() - 1; + QTextCharFormat format = const_cast(q)->format(pos); + QTextObjectInterface *handler = q->handlerForObject(format.objectType()); + if (handler) { + QRectF rect = frameBoundingRectInternal(frame); + handler->drawObject(painter, rect, document, pos, format); + } + } +} + +void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, bool inRootFrame) const +{ + const QTextLayout *tl = bl.layout(); + QRectF r = tl->boundingRect(); + r.translate(offset + tl->position()); + if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom())) + return; +// LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect(); + + QTextBlockFormat blockFormat = bl.blockFormat(); + + QBrush bg = blockFormat.background(); + if (bg != Qt::NoBrush) { + QRectF rect = r; + + // extend the background rectangle if we're in the root frame with NoWrap, + // as the rect of the text block will then be only the width of the text + // instead of the full page width + if (inRootFrame && document->pageSize().width() <= 0) { + const QTextFrameData *fd = data(document->rootFrame()); + rect.setRight((fd->size.width - fd->rightMargin).toReal()); + } + + fillBackground(painter, rect, bg, r.topLeft()); + } + + QVector selections; + int blpos = bl.position(); + int bllen = bl.length(); + const QTextCharFormat *selFormat = 0; + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i); + const int selStart = range.cursor.selectionStart() - blpos; + const int selEnd = range.cursor.selectionEnd() - blpos; + if (selStart < bllen && selEnd > 0 + && selEnd > selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + selections.append(o); + } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection) + && bl.contains(range.cursor.position())) { + // for full width selections we don't require an actual selection, just + // a position to specify the line. that's more convenience in usage. + QTextLayout::FormatRange o; + QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos); + o.start = l.textStart(); + o.length = l.textLength(); + if (o.start + o.length == bllen - 1) + ++o.length; // include newline + o.format = range.format; + selections.append(o); + } + if (selStart < 0 && selEnd >= 1) + selFormat = &range.format; + } + + QTextObject *object = document->objectForFormat(bl.blockFormat()); + if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined) + drawListItem(offset, painter, context, bl, selFormat); + + QPen oldPen = painter->pen(); + painter->setPen(context.palette.color(QPalette::Text)); + + tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect); + + if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) + || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) { + int cpos = context.cursorPosition; + if (cpos < -1) + cpos = tl->preeditAreaPosition() - (cpos + 2); + else + cpos -= blpos; + tl->drawCursor(painter, offset, cpos, cursorWidth); + } + + if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { + const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width()); + painter->setPen(context.palette.color(QPalette::Dark)); + qreal y = r.bottom(); + if (bl.length() == 1) + y = r.top() + r.height() / 2; + + const qreal middleX = r.left() + r.width() / 2; + painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y)); + } + + painter->setPen(oldPen); +} + + +void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, const QTextCharFormat *selectionFormat) const +{ + Q_Q(const QTextDocumentLayout); + const QTextBlockFormat blockFormat = bl.blockFormat(); + const QTextCharFormat charFormat = QTextCursor(bl).charFormat(); + QFont font(charFormat.font()); + if (q->paintDevice()) + font = QFont(font, q->paintDevice()); + + const QFontMetrics fontMetrics(font); + QTextObject * const object = document->objectForFormat(blockFormat); + const QTextListFormat lf = object->format().toListFormat(); + int style = lf.style(); + QString itemText; + QSizeF size; + + if (blockFormat.hasProperty(QTextFormat::ListStyle)) + style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle)); + + QTextLayout *layout = bl.layout(); + if (layout->lineCount() == 0) + return; + QTextLine firstLine = layout->lineAt(0); + Q_ASSERT(firstLine.isValid()); + QPointF pos = (offset + layout->position()).toPoint(); + Qt::LayoutDirection dir = bl.textDirection(); + { + QRectF textRect = firstLine.naturalTextRect(); + pos += textRect.topLeft().toPoint(); + if (dir == Qt::RightToLeft) + pos.rx() += textRect.width(); + } + + switch (style) { + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + itemText = static_cast(object)->itemText(bl); + size.setWidth(fontMetrics.width(itemText)); + size.setHeight(fontMetrics.height()); + break; + + case QTextListFormat::ListSquare: + case QTextListFormat::ListCircle: + case QTextListFormat::ListDisc: + size.setWidth(fontMetrics.lineSpacing() / 3); + size.setHeight(size.width()); + break; + + case QTextListFormat::ListStyleUndefined: + return; + default: return; + } + + QRectF r(pos, size); + + qreal xoff = fontMetrics.width(QLatin1Char(' ')); + if (dir == Qt::LeftToRight) + xoff = -xoff - size.width(); + r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2)); + + painter->save(); + + painter->setRenderHint(QPainter::Antialiasing); + + if (selectionFormat) { + painter->setPen(QPen(selectionFormat->foreground(), 0)); + painter->fillRect(r, selectionFormat->background()); + } else { + QBrush fg = charFormat.foreground(); + if (fg == Qt::NoBrush) + fg = context.palette.text(); + painter->setPen(QPen(fg, 0)); + } + + QBrush brush = context.palette.brush(QPalette::Text); + + switch (style) { + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: { + QTextLayout layout(itemText, font, q->paintDevice()); + layout.setCacheEnabled(true); + QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); + option.setTextDirection(dir); + layout.setTextOption(option); + layout.beginLayout(); + QTextLine line = layout.createLine(); + if (line.isValid()) + line.setLeadingIncluded(true); + layout.endLayout(); + layout.draw(painter, QPointF(r.left(), pos.y())); + break; + } + case QTextListFormat::ListSquare: + painter->fillRect(r, brush); + break; + case QTextListFormat::ListCircle: + painter->setPen(QPen(brush, 0)); + painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering + break; + case QTextListFormat::ListDisc: + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->drawEllipse(r); + break; + case QTextListFormat::ListStyleUndefined: + break; + default: + break; + } + + painter->restore(); +} + +static QFixed flowPosition(const QTextFrame::iterator it) +{ + if (it.atEnd()) + return 0; + + if (it.currentFrame()) { + return data(it.currentFrame())->position.y; + } else { + QTextBlock block = it.currentBlock(); + QTextLayout *layout = block.layout(); + if (layout->lineCount() == 0) + return QFixed::fromReal(layout->position().y()); + else + return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y()); + } +} + +static QFixed firstChildPos(const QTextFrame *f) +{ + return flowPosition(f->begin()); +} + +QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width, + int layoutFrom, int layoutTo, QTextTableData *td, + QFixed absoluteTableY, bool withPageBreaks) +{ + LDEBUG << "layoutCell"; + QTextLayoutStruct layoutStruct; + layoutStruct.frame = t; + layoutStruct.minimumWidth = 0; + layoutStruct.maximumWidth = QFIXED_MAX; + layoutStruct.y = 0; + + const QTextFormat fmt = cell.format(); + const QFixed topPadding = td->topPadding(fmt); + if (withPageBreaks) { + layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding; + } + layoutStruct.x_left = 0; + layoutStruct.x_right = width; + // we get called with different widths all the time (for example for figuring + // out the min/max widths), so we always have to do the full layout ;( + // also when for example in a table layoutFrom/layoutTo affect only one cell, + // making that one cell grow the available width of the other cells may change + // (shrink) and therefore when layoutCell gets called for them they have to + // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence + // this line: + + layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height()); + if (layoutStruct.pageHeight < 0 || !withPageBreaks) + layoutStruct.pageHeight = QFIXED_MAX; + const int currentPage = layoutStruct.currentPage(); + layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding; + layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt); + layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin; + + layoutStruct.fullLayout = true; + + QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY; + layoutStruct.y = qMax(layoutStruct.y, pageTop); + + const QList childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows()); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *frame = childFrames.at(i); + QTextFrameData *cd = data(frame); + cd->sizeDirty = true; + } + + layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width); + + QFixed floatMinWidth; + + // floats that are located inside the text (like inline images) aren't taken into account by + // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we + // do that here. For example with + // when the image happens to be higher than the text + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *frame = childFrames.at(i); + QTextFrameData *cd = data(frame); + + if (frame->frameFormat().position() != QTextFrameFormat::InFlow) + layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height); + + floatMinWidth = qMax(floatMinWidth, cd->minimumWidth); + } + + // constraint the maximumWidth by the minimum width of the fixed size floats, to + // keep them visible + layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth); + + // as floats in cells get added to the table's float list but must not affect + // floats in other cells we must clear the list here. + data(t)->floats.clear(); + +// qDebug() << "layoutCell done"; + + return layoutStruct; +} + +QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY) +{ + LDEBUG << "layoutTable"; + QTextTableData *td = static_cast(data(table)); + Q_ASSERT(td->sizeDirty); + const int rows = table->rows(); + const int columns = table->columns(); + + const QTextTableFormat fmt = table->format(); + + td->childFrameMap.clear(); + { + const QList children = table->childFrames(); + for (int i = 0; i < children.count(); ++i) { + QTextFrame *frame = children.at(i); + QTextTableCell cell = table->cellAt(frame->firstPosition()); + td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame); + } + } + + QVector columnWidthConstraints = fmt.columnWidthConstraints(); + if (columnWidthConstraints.size() != columns) + columnWidthConstraints.resize(columns); + Q_ASSERT(columnWidthConstraints.count() == columns); + + const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing())); + td->deviceScale = scaleToDevice(qreal(1)); + td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding())); + const QFixed leftMargin = td->leftMargin + td->border + td->padding; + const QFixed rightMargin = td->rightMargin + td->border + td->padding; + const QFixed topMargin = td->topMargin + td->border + td->padding; + + const QFixed absoluteTableY = parentY + td->position.y; + + const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode(); + +recalc_minmax_widths: + + QFixed remainingWidth = td->contentsWidth; + // two (vertical) borders per cell per column + remainingWidth -= columns * 2 * td->border; + // inter-cell spacing + remainingWidth -= (columns - 1) * cellSpacing; + // cell spacing at the left and right hand side + remainingWidth -= 2 * cellSpacing; + // remember the width used to distribute to percentaged columns + const QFixed initialTotalWidth = remainingWidth; + + td->widths.resize(columns); + td->widths.fill(0); + + td->minWidths.resize(columns); + // start with a minimum width of 0. totally empty + // cells of default created tables are invisible otherwise + // and therefore hardly editable + td->minWidths.fill(1); + + td->maxWidths.resize(columns); + td->maxWidths.fill(QFIXED_MAX); + + // calculate minimum and maximum sizes of the columns + for (int i = 0; i < columns; ++i) { + for (int row = 0; row < rows; ++row) { + const QTextTableCell cell = table->cellAt(row, i); + const int cspan = cell.columnSpan(); + + if (cspan > 1 && i != cell.column()) + continue; + + const QTextFormat fmt = cell.format(); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed rightPadding = td->rightPadding(fmt); + const QFixed widthPadding = leftPadding + rightPadding; + + // to figure out the min and the max width lay out the cell at + // maximum width. otherwise the maxwidth calculation sometimes + // returns wrong values + QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom, + layoutTo, td, absoluteTableY, + /*withPageBreaks =*/false); + + // distribute the minimum width over all columns the cell spans + QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding; + for (int n = 0; n < cspan; ++n) { + const int col = i + n; + QFixed w = widthToDistribute / (cspan - n); + td->minWidths[col] = qMax(td->minWidths.at(col), w); + widthToDistribute -= td->minWidths.at(col); + if (widthToDistribute <= 0) + break; + } + + QFixed maxW = td->maxWidths.at(i); + if (layoutStruct.maximumWidth != QFIXED_MAX) { + if (maxW == QFIXED_MAX) + maxW = layoutStruct.maximumWidth + widthPadding; + else + maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding); + } + if (maxW == QFIXED_MAX) + continue; + + widthToDistribute = maxW; + for (int n = 0; n < cspan; ++n) { + const int col = i + n; + QFixed w = widthToDistribute / (cspan - n); + td->maxWidths[col] = qMax(td->minWidths.at(col), w); + widthToDistribute -= td->maxWidths.at(col); + if (widthToDistribute <= 0) + break; + } + } + } + + // set fixed values, figure out total percentages used and number of + // variable length cells. Also assign the minimum width for variable columns. + QFixed totalPercentage; + int variableCols = 0; + QFixed totalMinWidth = 0; + for (int i = 0; i < columns; ++i) { + const QTextLength &length = columnWidthConstraints.at(i); + if (length.type() == QTextLength::FixedLength) { + td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i)); + remainingWidth -= td->widths.at(i); + } else if (length.type() == QTextLength::PercentageLength) { + totalPercentage += QFixed::fromReal(length.rawValue()); + } else if (length.type() == QTextLength::VariableLength) { + variableCols++; + + td->widths[i] = td->minWidths.at(i); + remainingWidth -= td->minWidths.at(i); + } + totalMinWidth += td->minWidths.at(i); + } + + // set percentage values + { + const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100; + QFixed remainingMinWidths = totalMinWidth; + for (int i = 0; i < columns; ++i) { + remainingMinWidths -= td->minWidths.at(i); + if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) { + const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue()); + + const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage; + if (percentWidth >= td->minWidths.at(i)) { + td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths); + } else { + td->widths[i] = td->minWidths.at(i); + } + remainingWidth -= td->widths.at(i); + } + } + } + + // for variable columns distribute the remaining space + if (variableCols > 0 && remainingWidth > 0) { + QVarLengthArray columnsWithProperMaxSize; + for (int i = 0; i < columns; ++i) + if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength + && td->maxWidths.at(i) != QFIXED_MAX) + columnsWithProperMaxSize.append(i); + + QFixed lastRemainingWidth = remainingWidth; + while (remainingWidth > 0) { + for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) { + const int col = columnsWithProperMaxSize[k]; + const int colsLeft = columnsWithProperMaxSize.count() - k; + const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft); + td->widths[col] += w; + remainingWidth -= w; + } + if (remainingWidth == lastRemainingWidth) + break; + lastRemainingWidth = remainingWidth; + } + + if (remainingWidth > 0 + // don't unnecessarily grow variable length sized tables + && fmt.width().type() != QTextLength::VariableLength) { + const QFixed widthPerAnySizedCol = remainingWidth / variableCols; + for (int col = 0; col < columns; ++col) { + if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength) + td->widths[col] += widthPerAnySizedCol; + } + } + } + + td->columnPositions.resize(columns); + td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border; + + for (int i = 1; i < columns; ++i) + td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing; + + // - margin to compensate the + margin in columnPositions[0] + const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin; + + // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap + // mode + if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere + && contentsWidth > td->contentsWidth) { + docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere); + // go back to the top of the function + goto recalc_minmax_widths; + } + + td->contentsWidth = contentsWidth; + + docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode); + + td->heights.resize(rows); + td->heights.fill(0); + + td->rowPositions.resize(rows); + td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border; + + bool haveRowSpannedCells = false; + + // need to keep track of cell heights for vertical alignment + QVector cellHeights; + cellHeights.reserve(rows * columns); + + QFixed pageHeight = QFixed::fromReal(document->pageSize().height()); + if (pageHeight <= 0) + pageHeight = QFIXED_MAX; + + QVector heightToDistribute; + heightToDistribute.resize(columns); + + td->headerHeight = 0; + const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1); + const QFixed originalTopMargin = td->effectiveTopMargin; + bool hasDroppedTable = false; + + // now that we have the column widths we can lay out all cells with the right width. + // spanning cells are only allowed to grow the last row spanned by the cell. + // + // ### this could be made faster by iterating over the cells array of QTextTable + for (int r = 0; r < rows; ++r) { + td->calcRowPosition(r); + + const int tableStartPage = (absoluteTableY / pageHeight).truncate(); + const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate(); + const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border; + const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border; + const QFixed nextPageTop = pageTop + pageHeight; + + if (td->rowPositions[r] > pageBottom) + td->rowPositions[r] = nextPageTop; + else if (td->rowPositions[r] < pageTop) + td->rowPositions[r] = pageTop; + + bool dropRowToNextPage = true; + int cellCountBeforeRow = cellHeights.size(); + + // if we drop the row to the next page we need to subtract the drop + // distance from any row spanning cells + QFixed dropDistance = 0; + +relayout: + const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate(); + // if any of the header rows or the first non-header row start on the next page + // then the entire header should be dropped + if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) { + td->rowPositions[0] = nextPageTop; + cellHeights.clear(); + td->effectiveTopMargin = originalTopMargin; + hasDroppedTable = true; + r = -1; + continue; + } + + int rowCellCount = 0; + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + const int rspan = cell.rowSpan(); + const int cspan = cell.columnSpan(); + + if (cspan > 1 && cell.column() != c) + continue; + + if (rspan > 1) { + haveRowSpannedCells = true; + + const int cellRow = cell.row(); + if (cellRow != r) { + // the last row gets all the remaining space + if (cellRow + rspan - 1 == r) + td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance); + continue; + } + } + + const QTextFormat fmt = cell.format(); + + const QFixed topPadding = td->topPadding(fmt); + const QFixed bottomPadding = td->bottomPadding(fmt); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed rightPadding = td->rightPadding(fmt); + const QFixed widthPadding = leftPadding + rightPadding; + + ++rowCellCount; + + const QFixed width = td->cellWidth(c, cspan) - widthPadding; + QTextLayoutStruct layoutStruct = layoutCell(table, cell, width, + layoutFrom, layoutTo, + td, absoluteTableY, + /*withPageBreaks =*/true); + + const QFixed height = layoutStruct.y + bottomPadding + topPadding; + + if (rspan > 1) + heightToDistribute[c] = height + dropDistance; + else + td->heights[r] = qMax(td->heights.at(r), height); + + cellHeights.append(layoutStruct.y); + + QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin()); + if (childPos < pageBottom) + dropRowToNextPage = false; + } + + if (rowCellCount > 0 && dropRowToNextPage) { + dropDistance = nextPageTop - td->rowPositions[r]; + td->rowPositions[r] = nextPageTop; + td->heights[r] = 0; + dropRowToNextPage = false; + cellHeights.resize(cellCountBeforeRow); + if (r > headerRowCount) + td->heights[r-1] = pageBottom - td->rowPositions[r-1]; + goto relayout; + } + + if (haveRowSpannedCells) { + const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border; + for (int c = 0; c < columns; ++c) + heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0)); + } + + if (r == headerRowCount - 1) { + td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border; + td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate(); + td->effectiveTopMargin += td->headerHeight; + } + } + + td->effectiveTopMargin = originalTopMargin; + + // now that all cells have been properly laid out, we can compute the + // vertical offsets for vertical alignment + td->cellVerticalOffsets.resize(rows * columns); + int cellIndex = 0; + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + if (cell.row() != r || cell.column() != c) + continue; + + const int rowSpan = cell.rowSpan(); + const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r); + + const QTextCharFormat cellFormat = cell.format(); + const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat); + + QFixed offset = 0; + switch (cellFormat.verticalAlignment()) { + case QTextCharFormat::AlignMiddle: + offset = (availableHeight - cellHeight) / 2; + break; + case QTextCharFormat::AlignBottom: + offset = availableHeight - cellHeight; + break; + default: + break; + }; + + for (int rd = 0; rd < cell.rowSpan(); ++rd) { + for (int cd = 0; cd < cell.columnSpan(); ++cd) { + const int index = (c + cd) + (r + rd) * columns; + td->cellVerticalOffsets[index] = offset; + } + } + } + } + + td->minimumWidth = td->columnPositions.at(0); + for (int i = 0; i < columns; ++i) { + td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing; + } + td->minimumWidth += rightMargin - td->border; + + td->maximumWidth = td->columnPositions.at(0); + for (int i = 0; i < columns; ++i) + if (td->maxWidths.at(i) != QFIXED_MAX) + td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing; + td->maximumWidth += rightMargin - td->border; + + td->updateTableSize(); + td->sizeDirty = false; + return QRectF(); // invalid rect -> update everything +} + +void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine) +{ + QTextFrameData *fd = data(frame); + + QTextFrame *parent = frame->parentFrame(); + Q_ASSERT(parent); + QTextFrameData *pd = data(parent); + Q_ASSERT(pd && pd->currentLayoutStruct); + + QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct; + + if (!pd->floats.contains(frame)) + pd->floats.append(frame); + fd->layoutDirty = true; + Q_ASSERT(!fd->sizeDirty); + +// qDebug() << "positionFloat:" << frame << "width=" << fd->size.width; + QFixed y = layoutStruct->y; + if (currentLine) { + QFixed left, right; + floatMargins(y, layoutStruct, &left, &right); +// qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width(); + if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) { + layoutStruct->pendingFloats.append(frame); +// qDebug() << " adding to pending list"; + return; + } + } + + bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom); + if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) { + layoutStruct->newPage(); + y = layoutStruct->y; + + frameSpansIntoNextPage = false; + } + + y = findY(y, layoutStruct, fd->size.width); + + QFixed left, right; + floatMargins(y, layoutStruct, &left, &right); + + if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) { + fd->position.x = left; + fd->position.y = y; + } else { + fd->position.x = right - fd->size.width; + fd->position.y = y; + } + + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth); + layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth); + +// qDebug()<< "float positioned at " << fd->position.x << fd->position.y; + fd->layoutDirty = false; + + // If the frame is a table, then positioning it will affect the size if it covers more than + // one page, because of page breaks and repeating the header. + if (qobject_cast(frame) != 0) + fd->sizeDirty = frameSpansIntoNextPage; +} + +QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY) +{ + LDEBUG << "layoutFrame (pre)"; + Q_ASSERT(data(f)->sizeDirty); +// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame()); + + QTextFrameFormat fformat = f->frameFormat(); + + QTextFrame *parent = f->parentFrame(); + const QTextFrameData *pd = parent ? data(parent) : 0; + + const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width()); + QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth)); + if (fformat.width().type() == QTextLength::FixedLength) + width = scaleToDevice(width); + + const QFixed maximumHeight = pd ? pd->contentsHeight : -1; + const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength) + ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal())) + : -1; + + return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY); +} + +QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY) +{ + LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo; + Q_ASSERT(data(f)->sizeDirty); +// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame()); + + QTextFrameData *fd = data(f); + QFixed newContentsWidth; + + { + QTextFrameFormat fformat = f->frameFormat(); + // set sizes of this frame from the format + fd->topMargin = QFixed::fromReal(fformat.topMargin()); + fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin()); + fd->leftMargin = QFixed::fromReal(fformat.leftMargin()); + fd->rightMargin = QFixed::fromReal(fformat.rightMargin()); + fd->border = QFixed::fromReal(fformat.border()); + fd->padding = QFixed::fromReal(fformat.padding()); + + QTextFrame *parent = f->parentFrame(); + const QTextFrameData *pd = parent ? data(parent) : 0; + + // accumulate top and bottom margins + if (parent) { + fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding; + fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding; + + if (qobject_cast(parent)) { + const QTextTableData *td = static_cast(pd); + fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding; + fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding; + } + } else { + fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding; + fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding; + } + + newContentsWidth = frameWidth - 2*(fd->border + fd->padding) + - fd->leftMargin - fd->rightMargin; + + if (frameHeight != -1) { + fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding) + - fd->topMargin - fd->bottomMargin; + } else { + fd->contentsHeight = frameHeight; + } + } + + if (isFrameFromInlineObject(f)) { + // never reached, handled in resizeInlineObject/positionFloat instead + return QRectF(); + } + + if (QTextTable *table = qobject_cast(f)) { + fd->contentsWidth = newContentsWidth; + return layoutTable(table, layoutFrom, layoutTo, parentY); + } + + // set fd->contentsWidth temporarily, so that layoutFrame for the children + // picks the right width. We'll initialize it properly at the end of this + // function. + fd->contentsWidth = newContentsWidth; + + QTextLayoutStruct layoutStruct; + layoutStruct.frame = f; + layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding; + layoutStruct.x_right = layoutStruct.x_left + newContentsWidth; + layoutStruct.y = fd->topMargin + fd->border + fd->padding; + layoutStruct.frameY = parentY + fd->position.y; + layoutStruct.contentsWidth = 0; + layoutStruct.minimumWidth = 0; + layoutStruct.maximumWidth = QFIXED_MAX; + layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth; + layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX))); + LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right + << "fullLayout" << layoutStruct.fullLayout; + fd->oldContentsWidth = newContentsWidth; + + layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height()); + if (layoutStruct.pageHeight < 0) + layoutStruct.pageHeight = QFIXED_MAX; + + const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate(); + layoutStruct.pageTopMargin = fd->effectiveTopMargin; + layoutStruct.pageBottomMargin = fd->effectiveBottomMargin; + layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin; + + if (!f->parentFrame()) + idealWidth = 0; // reset + + QTextFrame::Iterator it = f->begin(); + layoutFlow(it, &layoutStruct, layoutFrom, layoutTo); + + QFixed maxChildFrameWidth = 0; + QList children = f->childFrames(); + for (int i = 0; i < children.size(); ++i) { + QTextFrame *c = children.at(i); + QTextFrameData *cd = data(c); + maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width); + } + + const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin; + if (!f->parentFrame()) { + idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal(); + idealWidth += marginWidth.toReal(); + } + + QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth)); + fd->contentsWidth = actualWidth; + if (newContentsWidth <= 0) { // nowrap layout? + fd->contentsWidth = newContentsWidth; + } + + fd->minimumWidth = layoutStruct.minimumWidth; + fd->maximumWidth = layoutStruct.maximumWidth; + + fd->size.height = fd->contentsHeight == -1 + ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin + : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin; + fd->size.width = actualWidth + marginWidth; + fd->sizeDirty = false; + if (layoutStruct.updateRectForFloats.isValid()) + layoutStruct.updateRect |= layoutStruct.updateRectForFloats; + return layoutStruct.updateRect; +} + +void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, + int layoutFrom, int layoutTo, QFixed width) +{ + LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo; + QTextFrameData *fd = data(layoutStruct->frame); + + fd->currentLayoutStruct = layoutStruct; + + QTextFrame::Iterator previousIt; + + const bool inRootFrame = (it.parentFrame() == document->rootFrame()); + if (inRootFrame) { + bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty(); + + if (!redoCheckPoints) { + QVector::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom); + if (checkPoint != checkPoints.end()) { + if (checkPoint != checkPoints.begin()) + --checkPoint; + + layoutStruct->y = checkPoint->y; + layoutStruct->frameY = checkPoint->frameY; + layoutStruct->minimumWidth = checkPoint->minimumWidth; + layoutStruct->maximumWidth = checkPoint->maximumWidth; + layoutStruct->contentsWidth = checkPoint->contentsWidth; + + if (layoutStruct->pageHeight > 0) { + int page = layoutStruct->currentPage(); + layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin; + } + + it = frameIteratorForTextPosition(checkPoint->positionInFrame); + checkPoints.resize(checkPoint - checkPoints.begin() + 1); + + if (checkPoint != checkPoints.begin()) { + previousIt = it; + --previousIt; + } + } else { + redoCheckPoints = true; + } + } + + if (redoCheckPoints) { + checkPoints.clear(); + QCheckPoint cp; + cp.y = layoutStruct->y; + cp.frameY = layoutStruct->frameY; + cp.positionInFrame = 0; + cp.minimumWidth = layoutStruct->minimumWidth; + cp.maximumWidth = layoutStruct->maximumWidth; + cp.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(cp); + } + } + + QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat(); + + QFixed maximumBlockWidth = 0; + while (!it.atEnd()) { + QTextFrame *c = it.currentFrame(); + + int docPos; + if (it.currentFrame()) + docPos = it.currentFrame()->firstPosition(); + else + docPos = it.currentBlock().position(); + + if (inRootFrame) { + if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) { + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + if (left == layoutStruct->x_left && right == layoutStruct->x_right) { + QCheckPoint p; + p.y = layoutStruct->y; + p.frameY = layoutStruct->frameY; + p.positionInFrame = docPos; + p.minimumWidth = layoutStruct->minimumWidth; + p.maximumWidth = layoutStruct->maximumWidth; + p.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(p); + + if (currentLazyLayoutPosition != -1 + && docPos > currentLazyLayoutPosition + lazyLayoutStepSize) + break; + + } + } + } + + if (c) { + // position child frame + QTextFrameData *cd = data(c); + + QTextFrameFormat fformat = c->frameFormat(); + + if (fformat.position() == QTextFrameFormat::InFlow) { + if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore) + layoutStruct->newPage(); + + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, layoutStruct->x_left); + right = qMin(right, layoutStruct->x_right); + + if (right - left < cd->size.width) { + layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + } + + QFixedPoint pos(left, layoutStruct->y); + + Qt::Alignment align = Qt::AlignLeft; + + QTextTable *table = qobject_cast(c); + + if (table) + align = table->format().alignment() & Qt::AlignHorizontal_Mask; + + // detect whether we have any alignment in the document that disallows optimizations, + // such as not laying out the document again in a textedit with wrapping disabled. + if (inRootFrame && !(align & Qt::AlignLeft)) + contentHasAlignment = true; + + cd->position = pos; + + if (document->pageSize().height() > 0.0f) + cd->sizeDirty = true; + + if (cd->sizeDirty) { + if (width != 0) + layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY); + else + layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY); + + QFixed absoluteChildPos = table ? pos.y + static_cast(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c); + absoluteChildPos += layoutStruct->frameY; + + // drop entire frame to next page if first child of frame is on next page + if (absoluteChildPos > layoutStruct->pageBottom) { + layoutStruct->newPage(); + pos.y = layoutStruct->y; + + cd->position = pos; + cd->sizeDirty = true; + + if (width != 0) + layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY); + else + layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY); + } + } + + // align only if there is space for alignment + if (right - left > cd->size.width) { + if (align & Qt::AlignRight) + pos.x += layoutStruct->x_right - cd->size.width; + else if (align & Qt::AlignHCenter) + pos.x += (layoutStruct->x_right - cd->size.width) / 2; + } + + cd->position = pos; + + layoutStruct->y += cd->size.height; + const int page = layoutStruct->currentPage(); + layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin; + + cd->layoutDirty = false; + + if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter) + layoutStruct->newPage(); + } else { + QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF()); + QRectF updateRect; + + if (cd->sizeDirty) + updateRect = layoutFrame(c, layoutFrom, layoutTo); + + positionFloat(c); + + // If the size was made dirty when the position was set, layout again + if (cd->sizeDirty) + updateRect = layoutFrame(c, layoutFrom, layoutTo); + + QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF()); + + if (frameRect == oldFrameRect && updateRect.isValid()) + updateRect.translate(cd->position.toPointF()); + else + updateRect = frameRect; + + layoutStruct->addUpdateRectForFloat(updateRect); + if (oldFrameRect.isValid()) + layoutStruct->addUpdateRectForFloat(oldFrameRect); + } + + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth); + layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth); + + previousIt = it; + ++it; + } else { + QTextFrame::Iterator lastIt; + if (!previousIt.atEnd()) + lastIt = previousIt; + previousIt = it; + QTextBlock block = it.currentBlock(); + ++it; + + const QTextBlockFormat blockFormat = block.blockFormat(); + + if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore) + layoutStruct->newPage(); + + const QFixed origY = layoutStruct->y; + const QFixed origPageBottom = layoutStruct->pageBottom; + const QFixed origMaximumWidth = layoutStruct->maximumWidth; + layoutStruct->maximumWidth = 0; + + const QTextBlockFormat *previousBlockFormatPtr = 0; + if (lastIt.currentBlock().isValid()) + previousBlockFormatPtr = &previousBlockFormat; + + // layout and position child block + layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr); + + // detect whether we have any alignment in the document that disallows optimizations, + // such as not laying out the document again in a textedit with wrapping disabled. + if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft)) + contentHasAlignment = true; + + // if the block right before a table is empty 'hide' it by + // positioning it into the table border + if (isEmptyBlockBeforeTable(block, blockFormat, it)) { + const QTextBlock lastBlock = lastIt.currentBlock(); + const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f; + layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin())); + layoutStruct->pageBottom = origPageBottom; + } else { + // if the block right after a table is empty then 'hide' it, too + if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) { + QTextTableData *td = static_cast(data(lastIt.currentFrame())); + QTextLayout *layout = block.layout(); + + QPointF pos((td->position.x + td->size.width).toReal(), + (td->position.y + td->size.height).toReal() - layout->boundingRect().height()); + + layout->setPosition(pos); + layoutStruct->y = origY; + layoutStruct->pageBottom = origPageBottom; + } + + // if the block right after a table starts with a line separator, shift it up by one line + if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) { + QTextTableData *td = static_cast(data(lastIt.currentFrame())); + QTextLayout *layout = block.layout(); + + QFixed height = QFixed::fromReal(layout->lineAt(0).height()); + + if (layoutStruct->pageBottom == origPageBottom) { + layoutStruct->y -= height; + layout->setPosition(layout->position() - QPointF(0, height.toReal())); + } else { + // relayout block to correctly handle page breaks + layoutStruct->y = origY - height; + layoutStruct->pageBottom = origPageBottom; + layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr); + } + + QPointF linePos((td->position.x + td->size.width).toReal(), + (td->position.y + td->size.height - height).toReal()); + + layout->lineAt(0).setPosition(linePos - layout->position()); + } + + if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter) + layoutStruct->newPage(); + } + + maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth); + layoutStruct->maximumWidth = origMaximumWidth; + previousBlockFormat = blockFormat; + } + } + if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0) + layoutStruct->maximumWidth = maximumBlockWidth; + else + layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth); + + // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y. + // we don't need to do it for tables though because floats in tables are per table + // and not per cell and layoutCell already takes care of doing the same as we do here + if (!qobject_cast(layoutStruct->frame)) { + QList children = layoutStruct->frame->childFrames(); + for (int i = 0; i < children.count(); ++i) { + QTextFrameData *fd = data(children.at(i)); + if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow) + layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height); + } + } + + if (inRootFrame) { + // we assume that any float is aligned in a way that disallows the optimizations that rely + // on unaligned content. + if (!fd->floats.isEmpty()) + contentHasAlignment = true; + + if (it.atEnd()) { + //qDebug() << "layout done!"; + currentLazyLayoutPosition = -1; + QCheckPoint cp; + cp.y = layoutStruct->y; + cp.positionInFrame = docPrivate->length(); + cp.minimumWidth = layoutStruct->minimumWidth; + cp.maximumWidth = layoutStruct->maximumWidth; + cp.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(cp); + checkPoints.reserve(checkPoints.size()); + } else { + currentLazyLayoutPosition = checkPoints.last().positionInFrame; + // ####### + //checkPoints.last().positionInFrame = q->document()->docHandle()->length(); + } + } + + + fd->currentLayoutStruct = 0; +} + +static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling, + QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight) +{ + *lineHeight = QFixed::fromReal(blockFormat.lineHeight(line.height(), scaling)); + + if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight || blockFormat.lineHeightType() == QTextBlockFormat::MinimumHeight) { + *lineBreakHeight = *lineHeight; + if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight) + *lineAdjustment = QFixed::fromReal(line.ascent() + qMax(line.leading(), qreal(0.0))) - ((*lineHeight * 4) / 5); + else + *lineAdjustment = QFixed::fromReal(line.height()) - *lineHeight; + } + else { + *lineBreakHeight = QFixed::fromReal(line.height()); + *lineAdjustment = 0; + } +} + +void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat, + QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat) +{ + Q_Q(QTextDocumentLayout); + + QTextLayout *tl = bl.layout(); + const int blockLength = bl.length(); + + LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo; + +// qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')'; + + if (previousBlockFormat) { + qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin()); + if (margin > 0 && q->paintDevice()) { + margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()); + } + layoutStruct->y += QFixed::fromReal(margin); + } + + //QTextFrameData *fd = data(layoutStruct->frame); + + Qt::LayoutDirection dir = bl.textDirection(); + + QFixed extraMargin; + if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) { + QFontMetricsF fm(bl.charFormat().font()); + extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5)))); + } + + const QFixed indent = this->blockIndent(blockFormat); + const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent); + const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin); + + const QPointF oldPosition = tl->position(); + tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal())); + + if (layoutStruct->fullLayout + || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo) + // force relayout if we cross a page boundary + || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) { + + LDEBUG << " do layout"; + QTextOption option = docPrivate->defaultTextOption; + option.setTextDirection(dir); + option.setTabs( blockFormat.tabPositions() ); + + Qt::Alignment align = docPrivate->defaultTextOption.alignment(); + if (blockFormat.hasProperty(QTextFormat::BlockAlignment)) + align = blockFormat.alignment(); + option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed; + + if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) { + option.setWrapMode(QTextOption::ManualWrap); + } + + tl->setTextOption(option); + + const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere); + +// qDebug() << " layouting block at" << bl.position(); + const QFixed cy = layoutStruct->y; + const QFixed l = layoutStruct->x_left + totalLeftMargin; + const QFixed r = layoutStruct->x_right - totalRightMargin; + + tl->beginLayout(); + bool firstLine = true; + while (1) { + QTextLine line = tl->createLine(); + if (!line.isValid()) + break; + line.setLeadingIncluded(true); + + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + QFixed text_indent; + if (firstLine) { + text_indent = QFixed::fromReal(blockFormat.textIndent()); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + firstLine = false; + } +// qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <textWidth" << line.textWidth(); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + + if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) { + // float has been added in the meantime, redo + layoutStruct->pendingFloats.clear(); + + line.setLineWidth((right-left).toReal()); + if (QFixed::fromReal(line.naturalTextWidth()) > right-left) { + if (haveWordOrAnyWrapMode) { + option.setWrapMode(QTextOption::WrapAnywhere); + tl->setTextOption(option); + } + + layoutStruct->pendingFloats.clear(); + // lines min width more than what we have + layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth())); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + line.setLineWidth(qMax(line.naturalTextWidth(), (right-left).toReal())); + + if (haveWordOrAnyWrapMode) { + option.setWrapMode(QTextOption::WordWrap); + tl->setTextOption(option); + } + } + + } + + QFixed lineBreakHeight, lineHeight, lineAdjustment; + qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ? + qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1; + getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight); + + if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) { + layoutStruct->newPage(); + + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + } + + line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy - lineAdjustment).toReal())); + layoutStruct->y += lineHeight; + layoutStruct->contentsWidth + = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin); + + // position floats + for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) { + QTextFrame *f = layoutStruct->pendingFloats.at(i); + positionFloat(f); + } + layoutStruct->pendingFloats.clear(); + } + tl->endLayout(); + } else { + const int cnt = tl->lineCount(); + for (int i = 0; i < cnt; ++i) { + LDEBUG << "going to move text line" << i; + QTextLine line = tl->lineAt(i); + layoutStruct->contentsWidth + = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin); + + QFixed lineBreakHeight, lineHeight, lineAdjustment; + qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ? + qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1; + getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight); + + if (layoutStruct->pageHeight != QFIXED_MAX) { + if (layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) + layoutStruct->newPage(); + line.setPosition(QPointF(line.position().x(), (layoutStruct->y - lineAdjustment).toReal() - tl->position().y())); + } + layoutStruct->y += lineHeight; + } + if (layoutStruct->updateRect.isValid() + && blockLength > 1) { + if (layoutFrom >= blockPosition + blockLength) { + // if our height didn't change and the change in the document is + // in one of the later paragraphs, then we don't need to repaint + // this one + layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal())); + } else if (layoutTo < blockPosition) { + if (oldPosition == tl->position()) + // if the change in the document happened earlier in the document + // and our position did /not/ change because none of the earlier paragraphs + // or frames changed their height, then we don't need to repaint + // this one + layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y())); + else + layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset + } + } + } + + // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon) + const QFixed margins = totalLeftMargin + totalRightMargin; + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins); + + const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins; + + if (maxW > 0) { + if (layoutStruct->maximumWidth == QFIXED_MAX) + layoutStruct->maximumWidth = maxW; + else + layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW); + } +} + +void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, + QFixed *left, QFixed *right) const +{ +// qDebug() << "floatMargins y=" << y; + *left = layoutStruct->x_left; + *right = layoutStruct->x_right; + QTextFrameData *lfd = data(layoutStruct->frame); + for (int i = 0; i < lfd->floats.size(); ++i) { + QTextFrameData *fd = data(lfd->floats.at(i)); + if (!fd->layoutDirty) { + if (fd->position.y <= y && fd->position.y + fd->size.height > y) { +// qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width(); + if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft) + *left = qMax(*left, fd->position.x + fd->size.width); + else + *right = qMin(*right, fd->position.x); + } + } + } +// qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<x_right - layoutStruct->x_left); + +// qDebug() << "findY:" << yFrom; + while (1) { + floatMargins(yFrom, layoutStruct, &left, &right); +// qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth; + if (right-left >= requiredWidth) + break; + + // move float down until we find enough space + QFixed newY = QFIXED_MAX; + QTextFrameData *lfd = data(layoutStruct->frame); + for (int i = 0; i < lfd->floats.size(); ++i) { + QTextFrameData *fd = data(lfd->floats.at(i)); + if (!fd->layoutDirty) { + if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom) + newY = qMin(newY, fd->position.y + fd->size.height); + } + } + if (newY == QFIXED_MAX) + break; + yFrom = newY; + } + return yFrom; +} + +QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc) + : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc) +{ + registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this)); +} + + +void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context) +{ + Q_D(QTextDocumentLayout); + QTextFrame *frame = d->document->rootFrame(); + QTextFrameData *fd = data(frame); + + if(fd->sizeDirty) + return; + + if (context.clip.isValid()) { + d->ensureLayouted(QFixed::fromReal(context.clip.bottom())); + } else { + d->ensureLayoutFinished(); + } + + QFixed width = fd->size.width; + if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) { + // we're in NoWrap mode, meaning the frame should expand to the viewport + // so that backgrounds are drawn correctly + fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right())); + } + + // Make sure we conform to the root frames bounds when drawing. + d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0); + d->drawFrame(QPointF(), painter, context, frame); + fd->size.width = width; +} + +void QTextDocumentLayout::setViewport(const QRectF &viewport) +{ + Q_D(QTextDocumentLayout); + d->viewportRect = viewport; +} + +static void markFrames(QTextFrame *current, int from, int oldLength, int length) +{ + int end = qMax(oldLength, length) + from; + + if (current->firstPosition() >= end || current->lastPosition() < from) + return; + + QTextFrameData *fd = data(current); + for (int i = 0; i < fd->floats.size(); ++i) { + QTextFrame *f = fd->floats[i]; + if (!f) { + // float got removed in editing operation + fd->floats.removeAt(i); + --i; + } + } + + fd->layoutDirty = true; + fd->sizeDirty = true; + +// qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition()); + QList children = current->childFrames(); + for (int i = 0; i < children.size(); ++i) + markFrames(children.at(i), from, oldLength, length); +} + +void QTextDocumentLayout::documentChanged(int from, int oldLength, int length) +{ + Q_D(QTextDocumentLayout); + + QTextBlock blockIt = document()->findBlock(from); + QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1)); + if (endIt.isValid()) + endIt = endIt.next(); + for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next()) + blockIt.clearLayout(); + + if (d->docPrivate->pageSize.isNull()) + return; + + QRectF updateRect; + + d->lazyLayoutStepSize = 1000; + d->sizeChangedTimer.stop(); + d->insideDocumentChange = true; + + const int documentLength = d->docPrivate->length(); + const bool fullLayout = (oldLength == 0 && length == documentLength); + const bool smallChange = documentLength > 0 + && (qMax(length, oldLength) * 100 / documentLength) < 5; + + // don't show incremental layout progress (avoid scroll bar flicker) + // if we see only a small change in the document and we're either starting + // a layout run or we're already in progress for that and we haven't seen + // any bigger change previously (showLayoutProgress already false) + if (smallChange + && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false)) + d->showLayoutProgress = false; + else + d->showLayoutProgress = true; + + if (fullLayout) { + d->contentHasAlignment = false; + d->currentLazyLayoutPosition = 0; + d->checkPoints.clear(); + d->layoutStep(); + } else { + d->ensureLayoutedByPosition(from); + updateRect = doLayout(from, oldLength, length); + } + + if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1) + d->layoutTimer.start(10, this); + + d->insideDocumentChange = false; + + if (d->showLayoutProgress) { + const QSizeF newSize = dynamicDocumentSize(); + if (newSize != d->lastReportedSize) { + d->lastReportedSize = newSize; + emit documentSizeChanged(newSize); + } + } + + if (!updateRect.isValid()) { + // don't use the frame size, it might have shrunken + updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX))); + } + + emit update(updateRect); +} + +QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length) +{ + Q_D(QTextDocumentLayout); + +// qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length); + + // mark all frames between f_start and f_end as dirty + markFrames(d->docPrivate->rootFrame(), from, oldLength, length); + + QRectF updateRect; + + QTextFrame *root = d->docPrivate->rootFrame(); + if(data(root)->sizeDirty) + updateRect = d->layoutFrame(root, from, from + length); + data(root)->layoutDirty = false; + + if (d->currentLazyLayoutPosition == -1) + layoutFinished(); + else if (d->showLayoutProgress) + d->sizeChangedTimer.start(0, this); + + return updateRect; +} + +int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayouted(QFixed::fromReal(point.y())); + QTextFrame *f = d->docPrivate->rootFrame(); + int position = 0; + QTextLayout *l = 0; + QFixedPoint pointf; + pointf.x = QFixed::fromReal(point.x()); + pointf.y = QFixed::fromReal(point.y()); + QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy); + if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact) + return -1; + + // ensure we stay within document bounds + int lastPos = f->lastPosition(); + if (l && !l->preeditAreaText().isEmpty()) + lastPos += l->preeditAreaText().length(); + if (position > lastPos) + position = lastPos; + else if (position < 0) + position = 0; + + return position; +} + +void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format); + + QTextFrameFormat::Position pos = QTextFrameFormat::InFlow; + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (frame) { + pos = frame->frameFormat().position(); + QTextFrameData *fd = data(frame); + fd->sizeDirty = false; + fd->size = QFixedSize::fromSizeF(intrinsic); + fd->minimumWidth = fd->maximumWidth = fd->size.width; + } + + QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0)); + item.setWidth(inlineSize.width()); + if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) { + item.setDescent(inlineSize.height() / 2); + item.setAscent(inlineSize.height() / 2 - 1); + } else { + item.setDescent(0); + item.setAscent(inlineSize.height() - 1); + } +} + +void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + Q_UNUSED(posInDocument); + if (item.width() != 0) + // inline + return; + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (!frame) + return; + + QTextBlock b = d->document->findBlock(frame->firstPosition()); + QTextLine line; + if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition()) + line = b.layout()->lineAt(b.layout()->lineCount()-1); +// qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() << +// frame->firstPosition() << frame->lastPosition(); + d->positionFloat(frame, line.isValid() ? &line : 0); +} + +void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow) + return; // don't draw floating frames from inline objects here but in drawFlow instead + +// qDebug() << "drawObject at" << r; + QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format); +} + +int QTextDocumentLayout::dynamicPageCount() const +{ + Q_D(const QTextDocumentLayout); + const QSizeF pgSize = d->document->pageSize(); + if (pgSize.height() < 0) + return 1; + return qCeil(dynamicDocumentSize().height() / pgSize.height()); +} + +QSizeF QTextDocumentLayout::dynamicDocumentSize() const +{ + Q_D(const QTextDocumentLayout); + return data(d->docPrivate->rootFrame())->size.toSizeF(); +} + +int QTextDocumentLayout::pageCount() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return dynamicPageCount(); +} + +QSizeF QTextDocumentLayout::documentSize() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return dynamicDocumentSize(); +} + +void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const +{ + Q_Q(const QTextDocumentLayout); + if (currentLazyLayoutPosition == -1) + return; + const QSizeF oldSize = q->dynamicDocumentSize(); + + if (checkPoints.isEmpty()) + layoutStep(); + + while (currentLazyLayoutPosition != -1 + && checkPoints.last().y < y) + layoutStep(); +} + +void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const +{ + if (currentLazyLayoutPosition == -1) + return; + if (position < currentLazyLayoutPosition) + return; + while (currentLazyLayoutPosition != -1 + && currentLazyLayoutPosition < position) { + const_cast(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition); + } +} + +void QTextDocumentLayoutPrivate::layoutStep() const +{ + ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize); + lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2); +} + +void QTextDocumentLayout::setCursorWidth(int width) +{ + Q_D(QTextDocumentLayout); + d->cursorWidth = width; +} + +int QTextDocumentLayout::cursorWidth() const +{ + Q_D(const QTextDocumentLayout); + return d->cursorWidth; +} + +void QTextDocumentLayout::setFixedColumnWidth(int width) +{ + Q_D(QTextDocumentLayout); + d->fixedColumnWidth = width; +} + +QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const +{ + Q_D(const QTextDocumentLayout); + if (d->docPrivate->pageSize.isNull()) + return QRectF(); + d->ensureLayoutFinished(); + return d->frameBoundingRectInternal(frame); +} + +QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const +{ + QPointF pos; + const int framePos = frame->firstPosition(); + QTextFrame *f = frame; + while (f) { + QTextFrameData *fd = data(f); + pos += fd->position.toPointF(); + + if (QTextTable *table = qobject_cast(f)) { + QTextTableCell cell = table->cellAt(framePos); + if (cell.isValid()) + pos += static_cast(fd)->cellPosition(cell).toPointF(); + } + + f = f->parentFrame(); + } + return QRectF(pos, data(frame)->size.toSizeF()); +} + +QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const +{ + Q_D(const QTextDocumentLayout); + if (d->docPrivate->pageSize.isNull() || !block.isValid()) + return QRectF(); + d->ensureLayoutedByPosition(block.position() + block.length()); + QTextFrame *frame = d->document->frameAt(block.position()); + QPointF offset; + const int blockPos = block.position(); + + while (frame) { + QTextFrameData *fd = data(frame); + offset += fd->position.toPointF(); + + if (QTextTable *table = qobject_cast(frame)) { + QTextTableCell cell = table->cellAt(blockPos); + if (cell.isValid()) + offset += static_cast(fd)->cellPosition(cell).toPointF(); + } + + frame = frame->parentFrame(); + } + + const QTextLayout *layout = block.layout(); + QRectF rect = layout->boundingRect(); + rect.moveTopLeft(layout->position() + offset); + return rect; +} + +int QTextDocumentLayout::layoutStatus() const +{ + Q_D(const QTextDocumentLayout); + int pos = d->currentLazyLayoutPosition; + if (pos == -1) + return 100; + return pos * 100 / d->document->docHandle()->length(); +} + +void QTextDocumentLayout::timerEvent(QTimerEvent *e) +{ + Q_D(QTextDocumentLayout); + if (e->timerId() == d->layoutTimer.timerId()) { + if (d->currentLazyLayoutPosition != -1) + d->layoutStep(); + } else if (e->timerId() == d->sizeChangedTimer.timerId()) { + d->lastReportedSize = dynamicDocumentSize(); + emit documentSizeChanged(d->lastReportedSize); + d->sizeChangedTimer.stop(); + + if (d->currentLazyLayoutPosition == -1) { + const int newCount = dynamicPageCount(); + if (newCount != d->lastPageCount) { + d->lastPageCount = newCount; + emit pageCountChanged(newCount); + } + } + } else { + QAbstractTextDocumentLayout::timerEvent(e); + } +} + +void QTextDocumentLayout::layoutFinished() +{ + Q_D(QTextDocumentLayout); + d->layoutTimer.stop(); + if (!d->insideDocumentChange) + d->sizeChangedTimer.start(0, this); + // reset + d->showLayoutProgress = true; +} + +void QTextDocumentLayout::ensureLayouted(qreal y) +{ + d_func()->ensureLayouted(QFixed::fromReal(y)); +} + +qreal QTextDocumentLayout::idealWidth() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return d->idealWidth; +} + +bool QTextDocumentLayout::contentHasAlignment() const +{ + Q_D(const QTextDocumentLayout); + return d->contentHasAlignment; +} + +qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const +{ + if (!paintDevice) + return value; + return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi()); +} + +QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const +{ + if (!paintDevice) + return value; + return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi()); +} + +QT_END_NAMESPACE + +#include "moc_qtextdocumentlayout_p.cpp" diff --git a/src/gui/text/qtextdocumentlayout_p.h b/src/gui/text/qtextdocumentlayout_p.h new file mode 100644 index 0000000000..3c0383c3a4 --- /dev/null +++ b/src/gui/text/qtextdocumentlayout_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTLAYOUT_P_H +#define QTEXTDOCUMENTLAYOUT_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 "QtGui/qabstracttextdocumentlayout.h" +#include "QtGui/qtextoption.h" +#include "QtGui/qtextobject.h" + +QT_BEGIN_NAMESPACE + +class QTextListFormat; + +class QTextDocumentLayoutPrivate; + +class Q_AUTOTEST_EXPORT QTextDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_DECLARE_PRIVATE(QTextDocumentLayout) + Q_OBJECT + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(qreal idealWidth READ idealWidth) + Q_PROPERTY(bool contentHasAlignment READ contentHasAlignment) +public: + explicit QTextDocumentLayout(QTextDocument *doc); + + // from the abstract layout + void draw(QPainter *painter, const PaintContext &context); + int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const; + + int pageCount() const; + QSizeF documentSize() const; + + void setCursorWidth(int width); + int cursorWidth() const; + + // internal, to support the ugly FixedColumnWidth wordwrap mode in QTextEdit + void setFixedColumnWidth(int width); + + // internal for QTextEdit's NoWrap mode + void setViewport(const QRectF &viewport); + + virtual QRectF frameBoundingRect(QTextFrame *frame) const; + virtual QRectF blockBoundingRect(const QTextBlock &block) const; + + // #### + int layoutStatus() const; + int dynamicPageCount() const; + QSizeF dynamicDocumentSize() const; + void ensureLayouted(qreal); + + qreal idealWidth() const; + + bool contentHasAlignment() const; + +protected: + void documentChanged(int from, int oldLength, int length); + void resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + void positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + void drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format); + virtual void timerEvent(QTimerEvent *e); +private: + QRectF doLayout(int from, int oldLength, int length); + void layoutFinished(); +}; + +QT_END_NAMESPACE + +#endif // QTEXTDOCUMENTLAYOUT_P_H diff --git a/src/gui/text/qtextdocumentwriter.cpp b/src/gui/text/qtextdocumentwriter.cpp new file mode 100644 index 0000000000..fe91a5511c --- /dev/null +++ b/src/gui/text/qtextdocumentwriter.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qtextdocumentwriter.h" + +#include +#include +#include +#include +#include +#include +#include "qtextdocument.h" +#include "qtextdocumentfragment.h" + +#include "qtextdocumentfragment_p.h" +#include "qtextodfwriter_p.h" + +QT_BEGIN_NAMESPACE + +class QTextDocumentWriterPrivate +{ +public: + QTextDocumentWriterPrivate(QTextDocumentWriter* qq); + + // device + QByteArray format; + QIODevice *device; + bool deleteDevice; +#ifndef QT_NO_TEXTCODEC + QTextCodec *codec; +#endif + + QTextDocumentWriter *q; +}; + +/*! + \since 4.5 + \class QTextDocumentWriter + + \brief The QTextDocumentWriter class provides a format-independent interface for writing a QTextDocument to files or other devices. + + \ingroup richtext-processing + \ingroup io + + To write a document, construct a QTextDocumentWriter object with either a + file name or a device object, and specify the document format to be + written. You can construct a writer and set the format using setFormat() + later. + + Call write() to write the document to the device. If the document is + successfully written, this function returns true. However, if an error + occurs when writing the document, it will return false. + + Call supportedDocumentFormats() for a list of formats that + QTextDocumentWriter can write. + + Since the capabilities of the supported output formats vary considerably, + the writer simply outputs the appropriate subset of objects for each format. + This typically includes the formatted text and images contained in a + document. +*/ + +/*! + \internal +*/ +QTextDocumentWriterPrivate::QTextDocumentWriterPrivate(QTextDocumentWriter *qq) + : device(0), + deleteDevice(false), +#ifndef QT_NO_TEXTCODEC + codec(QTextCodec::codecForName("utf-8")), +#endif + q(qq) +{ +} + +/*! + Constructs an empty QTextDocumentWriter object. Before writing, you must + call setFormat() to set a document format, then setDevice() or + setFileName(). +*/ +QTextDocumentWriter::QTextDocumentWriter() + : d(new QTextDocumentWriterPrivate(this)) +{ +} + +/*! + Constructs a QTextDocumentWriter object to write to the given \a device + in the document format specified by \a format. +*/ +QTextDocumentWriter::QTextDocumentWriter(QIODevice *device, const QByteArray &format) + : d(new QTextDocumentWriterPrivate(this)) +{ + d->device = device; + d->format = format; +} + +/*! + Constructs an QTextDocumentWriter object that will write to a file with + the name \a fileName, using the document format specified by \a format. + If \a format is not provided, QTextDocumentWriter will detect the document + format by inspecting the extension of \a fileName. +*/ +QTextDocumentWriter::QTextDocumentWriter(const QString &fileName, const QByteArray &format) + : d(new QTextDocumentWriterPrivate(this)) +{ + QFile *file = new QFile(fileName); + d->device = file; + d->deleteDevice = true; + d->format = format; +} + +/*! + Destroys the QTextDocumentWriter object. +*/ +QTextDocumentWriter::~QTextDocumentWriter() +{ + if (d->deleteDevice) + delete d->device; + delete d; +} + +/*! + Sets the format used to write documents to the \a format specified. + \a format is a case insensitive text string. For example: + + \snippet doc/src/snippets/code/src.gui.text.qtextdocumentwriter.cpp 0 + + You can call supportedDocumentFormats() for the full list of formats + QTextDocumentWriter supports. + + \sa format() +*/ +void QTextDocumentWriter::setFormat (const QByteArray &format) +{ + d->format = format; +} + +/*! + Returns the format used for writing documents. + + \sa setFormat() +*/ +QByteArray QTextDocumentWriter::format () const +{ + return d->format; +} + +/*! + Sets the writer's device to the \a device specified. If a device has + already been set, the old device is removed but otherwise left + unchanged. + + If the device is not already open, QTextDocumentWriter will attempt to + open the device in \l QIODevice::WriteOnly mode by calling open(). + + \note This will not work for certain devices, such as QProcess, + QTcpSocket and QUdpSocket, where some configuration is required before + the device can be opened. + + \sa device(), setFileName() +*/ +void QTextDocumentWriter::setDevice (QIODevice *device) +{ + if (d->device && d->deleteDevice) + delete d->device; + + d->device = device; + d->deleteDevice = false; +} + +/*! + Returns the device currently assigned, or 0 if no device has been + assigned. +*/ +QIODevice *QTextDocumentWriter::device () const +{ + return d->device; +} + +/*! + Sets the name of the file to be written to \a fileName. Internally, + QTextDocumentWriter will create a QFile and open it in \l + QIODevice::WriteOnly mode, and use this file when writing the document. + + \sa fileName(), setDevice() +*/ +void QTextDocumentWriter::setFileName (const QString &fileName) +{ + setDevice(new QFile(fileName)); + d->deleteDevice = true; +} + +/*! + If the currently assigned device is a QFile, or if setFileName() + has been called, this function returns the name of the file + to be written to. In all other cases, it returns an empty string. + + \sa setFileName(), setDevice() +*/ +QString QTextDocumentWriter::fileName () const +{ + QFile *file = qobject_cast(d->device); + return file ? file->fileName() : QString(); +} + +/*! + Writes the given \a document to the assigned device or file and + returns true if successful; otherwise returns false. +*/ +bool QTextDocumentWriter::write(const QTextDocument *document) +{ + QByteArray suffix; + + if (d->device && d->format.isEmpty()) { + // if there's no format, see if device is a file, and if so, find + // the file suffix + if (QFile *file = qobject_cast(d->device)) + suffix = QFileInfo(file->fileName()).suffix().toLower().toLatin1(); + } + + QByteArray format = !d->format.isEmpty() ? d->format.toLower() : suffix; + +#ifndef QT_NO_TEXTODFWRITER + if (format == "odf" || format == "opendocumentformat" || format == "odt") { + QTextOdfWriter writer(*document, d->device); +#ifndef QT_NO_TEXTCODEC + writer.setCodec(d->codec); +#endif + return writer.writeAll(); + } +#endif // QT_NO_TEXTODFWRITER + +#ifndef QT_NO_TEXTHTMLPARSER + if (format == "html" || format == "htm") { + if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) { + qWarning() << "QTextDocumentWriter::write: the device can not be opened for writing"; + return false; + } + QTextStream ts(d->device); +#ifndef QT_NO_TEXTCODEC + ts.setCodec(d->codec); + ts << document->toHtml(d->codec->name()); +#endif + d->device->close(); + return true; + } +#endif + if (format == "txt" || format == "plaintext") { + if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) { + qWarning() << "QTextDocumentWriter::write: the device can not be opened for writing"; + return false; + } + QTextStream ts(d->device); +#ifndef QT_NO_TEXTCODEC + ts.setCodec(d->codec); +#endif + ts << document->toPlainText(); + d->device->close(); + return true; + } + + return false; +} + +/*! + Writes the document fragment specified by \a fragment to the assigned device + or file and returns true if successful; otherwise returns false. +*/ +bool QTextDocumentWriter::write(const QTextDocumentFragment &fragment) +{ + if (fragment.d == 0) + return false; // invalid fragment. + QTextDocument *doc = fragment.d->doc; + if (doc) + return write(doc); + return false; +} + +/*! + Sets the codec for this stream to \a codec. The codec is used for + encoding any data that is written. By default, QTextDocumentWriter + uses UTF-8. +*/ + +#ifndef QT_NO_TEXTCODEC +void QTextDocumentWriter::setCodec(QTextCodec *codec) +{ + if (codec == 0) + codec = QTextCodec::codecForName("UTF-8"); + Q_ASSERT(codec); + d->codec = codec; +} +#endif + +/*! + Returns the codec that is currently assigned to the writer. +*/ +#ifndef QT_NO_TEXTCODEC +QTextCodec *QTextDocumentWriter::codec() const +{ + return d->codec; +} +#endif + +/*! + Returns the list of document formats supported by QTextDocumentWriter. + + By default, Qt can write the following formats: + + \table + \header \o Format \o Description + \row \o plaintext \o Plain text + \row \o HTML \o HyperText Markup Language + \row \o ODF \o OpenDocument Format + \endtable + + \sa setFormat() +*/ +QList QTextDocumentWriter::supportedDocumentFormats() +{ + QList answer; + answer << "plaintext"; + +#ifndef QT_NO_TEXTHTMLPARSER + answer << "HTML"; +#endif +#ifndef QT_NO_TEXTODFWRITER + answer << "ODF"; +#endif // QT_NO_TEXTODFWRITER + + qSort(answer); + return answer; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocumentwriter.h b/src/gui/text/qtextdocumentwriter.h new file mode 100644 index 0000000000..3fb002cb24 --- /dev/null +++ b/src/gui/text/qtextdocumentwriter.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QTEXTDOCUMENTWRITER_H +#define QTEXTDOCUMENTWRITER_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocumentWriterPrivate; +class QIODevice; +class QByteArray; +class QTextDocument; +class QTextDocumentFragment; + +class Q_GUI_EXPORT QTextDocumentWriter +{ +public: + QTextDocumentWriter(); + QTextDocumentWriter(QIODevice *device, const QByteArray &format); + QTextDocumentWriter(const QString &fileName, const QByteArray &format = QByteArray()); + ~QTextDocumentWriter(); + + void setFormat (const QByteArray &format); + QByteArray format () const; + + void setDevice (QIODevice *device); + QIODevice *device () const; + void setFileName (const QString &fileName); + QString fileName () const; + + bool write(const QTextDocument *document); + bool write(const QTextDocumentFragment &fragment); + +#ifndef QT_NO_TEXTCODEC + void setCodec(QTextCodec *codec); + QTextCodec *codec() const; +#endif + + static QList supportedDocumentFormats(); + +private: + Q_DISABLE_COPY(QTextDocumentWriter) + QTextDocumentWriterPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp new file mode 100644 index 0000000000..08d0eca7ac --- /dev/null +++ b/src/gui/text/qtextengine.cpp @@ -0,0 +1,2845 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdebug.h" +#include "qtextformat.h" +#include "qtextformat_p.h" +#include "qtextengine_p.h" +#include "qabstracttextdocumentlayout.h" +#include "qtextlayout.h" +#include "qtextboundaryfinder.h" +#include "qvarlengtharray.h" +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qstring.h" +#include +#include "qtextdocument_p.h" +#include +#include + + +QT_BEGIN_NAMESPACE + +namespace { +// Helper class used in QTextEngine::itemize +// keep it out here to allow us to keep supporting various compilers. +class Itemizer { +public: + Itemizer(const QString &string, const QScriptAnalysis *analysis, QScriptItemArray &items) + : m_string(string), + m_analysis(analysis), + m_items(items), + m_splitter(0) + { + } + ~Itemizer() + { + delete m_splitter; + } + + /// generate the script items + /// The caps parameter is used to choose the algoritm of splitting text and assiging roles to the textitems + void generate(int start, int length, QFont::Capitalization caps) + { + if ((int)caps == (int)QFont::SmallCaps) + generateScriptItemsSmallCaps(reinterpret_cast(m_string.unicode()), start, length); + else if(caps == QFont::Capitalize) + generateScriptItemsCapitalize(start, length); + else if(caps != QFont::MixedCase) { + generateScriptItemsAndChangeCase(start, length, + caps == QFont::AllLowercase ? QScriptAnalysis::Lowercase : QScriptAnalysis::Uppercase); + } + else + generateScriptItems(start, length); + } + +private: + enum { MaxItemLength = 4096 }; + + void generateScriptItemsAndChangeCase(int start, int length, QScriptAnalysis::Flags flags) + { + generateScriptItems(start, length); + if (m_items.isEmpty()) // the next loop won't work in that case + return; + QScriptItemArray::Iterator iter = m_items.end(); + do { + iter--; + if (iter->analysis.flags < QScriptAnalysis::TabOrObject) + iter->analysis.flags = flags; + } while (iter->position > start); + } + + void generateScriptItems(int start, int length) + { + if (!length) + return; + const int end = start + length; + for (int i = start + 1; i < end; ++i) { + if ((m_analysis[i] == m_analysis[start]) + && m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject + && i - start < MaxItemLength) + continue; + m_items.append(QScriptItem(start, m_analysis[start])); + start = i; + } + m_items.append(QScriptItem(start, m_analysis[start])); + } + + void generateScriptItemsCapitalize(int start, int length) + { + if (!length) + return; + + if (!m_splitter) + m_splitter = new QTextBoundaryFinder(QTextBoundaryFinder::Word, + m_string.constData(), m_string.length(), + /*buffer*/0, /*buffer size*/0); + + m_splitter->setPosition(start); + QScriptAnalysis itemAnalysis = m_analysis[start]; + + if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartWord) { + itemAnalysis.flags = QScriptAnalysis::Uppercase; + m_splitter->toNextBoundary(); + } + + const int end = start + length; + for (int i = start + 1; i < end; ++i) { + + bool atWordBoundary = false; + + if (i == m_splitter->position()) { + if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartWord + && m_analysis[i].flags < QScriptAnalysis::TabOrObject) + atWordBoundary = true; + + m_splitter->toNextBoundary(); + } + + if (m_analysis[i] == itemAnalysis + && m_analysis[i].flags < QScriptAnalysis::TabOrObject + && !atWordBoundary + && i - start < MaxItemLength) + continue; + + m_items.append(QScriptItem(start, itemAnalysis)); + start = i; + itemAnalysis = m_analysis[start]; + + if (atWordBoundary) + itemAnalysis.flags = QScriptAnalysis::Uppercase; + } + m_items.append(QScriptItem(start, itemAnalysis)); + } + + void generateScriptItemsSmallCaps(const ushort *uc, int start, int length) + { + if (!length) + return; + bool lower = (QChar::category(uc[start]) == QChar::Letter_Lowercase); + const int end = start + length; + // split text into parts that are already uppercase and parts that are lowercase, and mark the latter to be uppercased later. + for (int i = start + 1; i < end; ++i) { + bool l = (QChar::category(uc[i]) == QChar::Letter_Lowercase); + if ((m_analysis[i] == m_analysis[start]) + && m_analysis[i].flags < QScriptAnalysis::TabOrObject + && l == lower + && i - start < MaxItemLength) + continue; + m_items.append(QScriptItem(start, m_analysis[start])); + if (lower) + m_items.last().analysis.flags = QScriptAnalysis::SmallCaps; + + start = i; + lower = l; + } + m_items.append(QScriptItem(start, m_analysis[start])); + if (lower) + m_items.last().analysis.flags = QScriptAnalysis::SmallCaps; + } + + const QString &m_string; + const QScriptAnalysis * const m_analysis; + QScriptItemArray &m_items; + QTextBoundaryFinder *m_splitter; +}; +} + + +// ---------------------------------------------------------------------------- +// +// The BiDi algorithm +// +// ---------------------------------------------------------------------------- + +#define BIDI_DEBUG 0 +#if (BIDI_DEBUG >= 1) +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE +using namespace std; + +static const char *directions[] = { + "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON", + "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN" +}; + +#endif + +struct QBidiStatus { + QBidiStatus() { + eor = QChar::DirON; + lastStrong = QChar::DirON; + last = QChar:: DirON; + dir = QChar::DirON; + } + QChar::Direction eor; + QChar::Direction lastStrong; + QChar::Direction last; + QChar::Direction dir; +}; + +enum { MaxBidiLevel = 61 }; + +struct QBidiControl { + inline QBidiControl(bool rtl) + : cCtx(0), base(rtl ? 1 : 0), level(rtl ? 1 : 0), override(false) {} + + inline void embed(bool rtl, bool o = false) { + unsigned int toAdd = 1; + if((level%2 != 0) == rtl ) { + ++toAdd; + } + if (level + toAdd <= MaxBidiLevel) { + ctx[cCtx].level = level; + ctx[cCtx].override = override; + cCtx++; + override = o; + level += toAdd; + } + } + inline bool canPop() const { return cCtx != 0; } + inline void pdf() { + Q_ASSERT(cCtx); + --cCtx; + level = ctx[cCtx].level; + override = ctx[cCtx].override; + } + + inline QChar::Direction basicDirection() const { + return (base ? QChar::DirR : QChar:: DirL); + } + inline unsigned int baseLevel() const { + return base; + } + inline QChar::Direction direction() const { + return ((level%2) ? QChar::DirR : QChar:: DirL); + } + + struct { + unsigned int level; + bool override; + } ctx[MaxBidiLevel]; + unsigned int cCtx; + const unsigned int base; + unsigned int level; + bool override; +}; + + +static void appendItems(QScriptAnalysis *analysis, int &start, int &stop, const QBidiControl &control, QChar::Direction dir) +{ + if (start > stop) + return; + + int level = control.level; + + if(dir != QChar::DirON && !control.override) { + // add level of run (cases I1 & I2) + if(level % 2) { + if(dir == QChar::DirL || dir == QChar::DirAN || dir == QChar::DirEN) + level++; + } else { + if(dir == QChar::DirR) + level++; + else if(dir == QChar::DirAN || dir == QChar::DirEN) + level += 2; + } + } + +#if (BIDI_DEBUG >= 1) + qDebug("new run: dir=%s from %d, to %d level = %d override=%d", directions[dir], start, stop, level, control.override); +#endif + QScriptAnalysis *s = analysis + start; + const QScriptAnalysis *e = analysis + stop; + while (s <= e) { + s->bidiLevel = level; + ++s; + } + ++stop; + start = stop; +} + + +// creates the next QScript items. +static bool bidiItemize(QTextEngine *engine, QScriptAnalysis *analysis, QBidiControl &control) +{ + bool rightToLeft = (control.basicDirection() == 1); + bool hasBidi = rightToLeft; +#if BIDI_DEBUG >= 2 + qDebug() << "bidiItemize: rightToLeft=" << rightToLeft << engine->layoutData->string; +#endif + + int sor = 0; + int eor = -1; + + + int length = engine->layoutData->string.length(); + + const ushort *unicode = (const ushort *)engine->layoutData->string.unicode(); + int current = 0; + + QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL; + QBidiStatus status; + + QChar::Direction sdir = QChar::direction(*unicode); + if (sdir != QChar::DirL && sdir != QChar::DirR && sdir != QChar::DirEN && sdir != QChar::DirAN) + sdir = QChar::DirON; + else + dir = QChar::DirON; + status.eor = sdir; + status.lastStrong = rightToLeft ? QChar::DirR : QChar::DirL; + status.last = status.lastStrong; + status.dir = sdir; + + + while (current <= length) { + + QChar::Direction dirCurrent; + if (current == (int)length) + dirCurrent = control.basicDirection(); + else + dirCurrent = QChar::direction(unicode[current]); + +#if (BIDI_DEBUG >= 2) +// qDebug() << "pos=" << current << " dir=" << directions[dir] +// << " current=" << directions[dirCurrent] << " last=" << directions[status.last] +// << " eor=" << eor << '/' << directions[status.eor] +// << " sor=" << sor << " lastStrong=" +// << directions[status.lastStrong] +// << " level=" << (int)control.level << " override=" << (bool)control.override; +#endif + + switch(dirCurrent) { + + // embedding and overrides (X1-X9 in the BiDi specs) + case QChar::DirRLE: + case QChar::DirRLO: + case QChar::DirLRE: + case QChar::DirLRO: + { + bool rtl = (dirCurrent == QChar::DirRLE || dirCurrent == QChar::DirRLO); + hasBidi |= rtl; + bool override = (dirCurrent == QChar::DirLRO || dirCurrent == QChar::DirRLO); + + unsigned int level = control.level+1; + if ((level%2 != 0) == rtl) ++level; + if(level < MaxBidiLevel) { + eor = current-1; + appendItems(analysis, sor, eor, control, dir); + eor = current; + control.embed(rtl, override); + QChar::Direction edir = (rtl ? QChar::DirR : QChar::DirL); + dir = status.eor = edir; + status.lastStrong = edir; + } + break; + } + case QChar::DirPDF: + { + if (control.canPop()) { + if (dir != control.direction()) { + eor = current-1; + appendItems(analysis, sor, eor, control, dir); + dir = control.direction(); + } + eor = current; + appendItems(analysis, sor, eor, control, dir); + control.pdf(); + dir = QChar::DirON; status.eor = QChar::DirON; + status.last = control.direction(); + if (control.override) + dir = control.direction(); + else + dir = QChar::DirON; + status.lastStrong = control.direction(); + } + break; + } + + // strong types + case QChar::DirL: + if(dir == QChar::DirON) + dir = QChar::DirL; + switch(status.last) + { + case QChar::DirL: + eor = current; status.eor = QChar::DirL; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + if (eor >= 0) { + appendItems(analysis, sor, eor, control, dir); + dir = eor < length ? QChar::direction(unicode[eor]) : control.basicDirection(); + status.eor = dir; + } else { + eor = current; status.eor = dir; + } + break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(dir != QChar::DirL) { + //last stuff takes embedding dir + if(control.direction() == QChar::DirR) { + if(status.eor != QChar::DirR) { + // AN or EN + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirR; + } + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = eor < length ? QChar::direction(unicode[eor]) : control.basicDirection(); + status.eor = dir; + } else { + if(status.eor != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirL; + } else { + eor = current; status.eor = QChar::DirL; break; + } + } + } else { + eor = current; status.eor = QChar::DirL; + } + default: + break; + } + status.lastStrong = QChar::DirL; + break; + case QChar::DirAL: + case QChar::DirR: + hasBidi = true; + if(dir == QChar::DirON) dir = QChar::DirR; + switch(status.last) + { + case QChar::DirL: + case QChar::DirEN: + case QChar::DirAN: + if (eor >= 0) + appendItems(analysis, sor, eor, control, dir); + // fall through + case QChar::DirR: + case QChar::DirAL: + dir = QChar::DirR; eor = current; status.eor = QChar::DirR; break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor != QChar::DirR && status.eor != QChar::DirAL) { + //last stuff takes embedding dir + if(control.direction() == QChar::DirR + || status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirR; status.eor = QChar::DirON; + eor = current; + } else { + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirR; status.eor = QChar::DirON; + } + } else { + eor = current; status.eor = QChar::DirR; + } + default: + break; + } + status.lastStrong = dirCurrent; + break; + + // weak types: + + case QChar::DirNSM: + if (eor == current-1) + eor = current; + break; + case QChar::DirEN: + // if last strong was AL change EN to AN + if(status.lastStrong != QChar::DirAL) { + if(dir == QChar::DirON) { + if(status.lastStrong == QChar::DirL) + dir = QChar::DirL; + else + dir = QChar::DirEN; + } + switch(status.last) + { + case QChar::DirET: + if (status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirAN; + } + // fall through + case QChar::DirEN: + case QChar::DirL: + eor = current; + status.eor = dirCurrent; + break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirAN: + if (eor >= 0) + appendItems(analysis, sor, eor, control, dir); + else + eor = current; + status.eor = QChar::DirEN; + dir = QChar::DirAN; break; + case QChar::DirES: + case QChar::DirCS: + if(status.eor == QChar::DirEN || dir == QChar::DirAN) { + eor = current; break; + } + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor == QChar::DirR) { + // neutrals go to R + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirEN; + dir = QChar::DirAN; + } + else if(status.eor == QChar::DirL || + (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { + eor = current; status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirON; + eor = current - 1; + dir = QChar::DirR; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirON; + dir = QChar::DirAN; + } else { + eor = current; status.eor = dirCurrent; + } + } + default: + break; + } + break; + } + case QChar::DirAN: + hasBidi = true; + dirCurrent = QChar::DirAN; + if(dir == QChar::DirON) dir = QChar::DirAN; + switch(status.last) + { + case QChar::DirL: + case QChar::DirAN: + eor = current; status.eor = QChar::DirAN; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + if (eor >= 0){ + appendItems(analysis, sor, eor, control, dir); + } else { + eor = current; + } + dir = QChar::DirAN; status.eor = QChar::DirAN; + break; + case QChar::DirCS: + if(status.eor == QChar::DirAN) { + eor = current; break; + } + case QChar::DirES: + case QChar::DirET: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor == QChar::DirR) { + // neutrals go to R + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirAN; + dir = QChar::DirAN; + } else if(status.eor == QChar::DirL || + (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { + eor = current; status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + eor = current - 1; + dir = QChar::DirR; + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirAN; + dir = QChar::DirAN; + } else { + eor = current; status.eor = dirCurrent; + } + } + default: + break; + } + break; + case QChar::DirES: + case QChar::DirCS: + break; + case QChar::DirET: + if(status.last == QChar::DirEN) { + dirCurrent = QChar::DirEN; + eor = current; status.eor = dirCurrent; + } + break; + + // boundary neutrals should be ignored + case QChar::DirBN: + break; + // neutrals + case QChar::DirB: + // ### what do we do with newline and paragraph separators that come to here? + break; + case QChar::DirS: + // ### implement rule L1 + break; + case QChar::DirWS: + case QChar::DirON: + break; + default: + break; + } + + //qDebug() << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << control.direction(); + + if(current >= (int)length) break; + + // set status.last as needed. + switch(dirCurrent) { + case QChar::DirET: + case QChar::DirES: + case QChar::DirCS: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + switch(status.last) + { + case QChar::DirL: + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + status.last = dirCurrent; + break; + default: + status.last = QChar::DirON; + } + break; + case QChar::DirNSM: + case QChar::DirBN: + // ignore these + break; + case QChar::DirLRO: + case QChar::DirLRE: + status.last = QChar::DirL; + break; + case QChar::DirRLO: + case QChar::DirRLE: + status.last = QChar::DirR; + break; + case QChar::DirEN: + if (status.last == QChar::DirL) { + status.last = QChar::DirL; + break; + } + // fall through + default: + status.last = dirCurrent; + } + + ++current; + } + +#if (BIDI_DEBUG >= 1) + qDebug() << "reached end of line current=" << current << ", eor=" << eor; +#endif + eor = current - 1; // remove dummy char + + if (sor <= eor) + appendItems(analysis, sor, eor, control, dir); + + return hasBidi; +} + +void QTextEngine::bidiReorder(int numItems, const quint8 *levels, int *visualOrder) +{ + + // first find highest and lowest levels + quint8 levelLow = 128; + quint8 levelHigh = 0; + int i = 0; + while (i < numItems) { + //printf("level = %d\n", r->level); + if (levels[i] > levelHigh) + levelHigh = levels[i]; + if (levels[i] < levelLow) + levelLow = levels[i]; + i++; + } + + // implements reordering of the line (L2 according to BiDi spec): + // L2. From the highest level found in the text to the lowest odd level on each line, + // reverse any contiguous sequence of characters that are at that level or higher. + + // reversing is only done up to the lowest odd level + if(!(levelLow%2)) levelLow++; + +#if (BIDI_DEBUG >= 1) +// qDebug() << "reorderLine: lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh; +#endif + + int count = numItems - 1; + for (i = 0; i < numItems; i++) + visualOrder[i] = i; + + while(levelHigh >= levelLow) { + int i = 0; + while (i < count) { + while(i < count && levels[i] < levelHigh) i++; + int start = i; + while(i <= count && levels[i] >= levelHigh) i++; + int end = i-1; + + if(start != end) { + //qDebug() << "reversing from " << start << " to " << end; + for(int j = 0; j < (end-start+1)/2; j++) { + int tmp = visualOrder[start+j]; + visualOrder[start+j] = visualOrder[end-j]; + visualOrder[end-j] = tmp; + } + } + i++; + } + levelHigh--; + } + +#if (BIDI_DEBUG >= 1) +// qDebug() << "visual order is:"; +// for (i = 0; i < numItems; i++) +// qDebug() << visualOrder[i]; +#endif +} + +QT_BEGIN_INCLUDE_NAMESPACE + +#if defined(Q_WS_X11) || defined (Q_WS_QWS) +# include "qfontengine_ft_p.h" +#elif defined(Q_WS_MAC) +# include "qtextengine_mac.cpp" +#endif + +#include + +QT_END_INCLUDE_NAMESPACE + +// ask the font engine to find out which glyphs (as an index in the specific font) to use for the text in one item. +static bool stringToGlyphs(HB_ShaperItem *item, QGlyphLayout *glyphs, QFontEngine *fontEngine) +{ + int nGlyphs = item->num_glyphs; + + QTextEngine::ShaperFlags shaperFlags(QTextEngine::GlyphIndicesOnly); + if (item->item.bidiLevel % 2) + shaperFlags |= QTextEngine::RightToLeft; + + bool result = fontEngine->stringToCMap(reinterpret_cast(item->string + item->item.pos), item->item.length, glyphs, &nGlyphs, shaperFlags); + item->num_glyphs = nGlyphs; + glyphs->numGlyphs = nGlyphs; + return result; +} + +// shape all the items that intersect with the line, taking tab widths into account to find out what text actually fits in the line. +void QTextEngine::shapeLine(const QScriptLine &line) +{ + QFixed x; + bool first = true; + const int end = findItem(line.from + line.length - 1); + int item = findItem(line.from); + if (item == -1) + return; + for (item = findItem(line.from); item <= end; ++item) { + QScriptItem &si = layoutData->items[item]; + if (si.analysis.flags == QScriptAnalysis::Tab) { + ensureSpace(1); + si.width = calculateTabWidth(item, x); + } else { + shape(item); + } + if (first && si.position != line.from) { // that means our x position has to be offset + QGlyphLayout glyphs = shapedGlyphs(&si); + Q_ASSERT(line.from > si.position); + for (int i = line.from - si.position - 1; i >= 0; i--) { + x -= glyphs.effectiveAdvance(i); + } + } + first = false; + + x += si.width; + } +} + +void QTextEngine::shapeText(int item) const +{ + Q_ASSERT(item < layoutData->items.size()); + QScriptItem &si = layoutData->items[item]; + + if (si.num_glyphs) + return; + +#if defined(Q_WS_MAC) + shapeTextMac(item); +#elif defined(Q_WS_WINCE) + shapeTextWithCE(item); +#else + shapeTextWithHarfbuzz(item); +#endif + + si.width = 0; + + if (!si.num_glyphs) + return; + QGlyphLayout glyphs = shapedGlyphs(&si); + + QFont font = this->font(si); + bool letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute; + QFixed letterSpacing = font.d->letterSpacing; + QFixed wordSpacing = font.d->wordSpacing; + + if (letterSpacingIsAbsolute && letterSpacing.value()) + letterSpacing *= font.d->dpi / qt_defaultDpiY(); + + if (letterSpacing != 0) { + for (int i = 1; i < si.num_glyphs; ++i) { + if (glyphs.attributes[i].clusterStart) { + if (letterSpacingIsAbsolute) + glyphs.advances_x[i-1] += letterSpacing; + else { + QFixed &advance = glyphs.advances_x[i-1]; + advance += (letterSpacing - 100) * advance / 100; + } + } + } + if (letterSpacingIsAbsolute) + glyphs.advances_x[si.num_glyphs-1] += letterSpacing; + else { + QFixed &advance = glyphs.advances_x[si.num_glyphs-1]; + advance += (letterSpacing - 100) * advance / 100; + } + } + if (wordSpacing != 0) { + for (int i = 0; i < si.num_glyphs; ++i) { + if (glyphs.attributes[i].justification == HB_Space + || glyphs.attributes[i].justification == HB_Arabic_Space) { + // word spacing only gets added once to a consecutive run of spaces (see CSS spec) + if (i + 1 == si.num_glyphs + ||(glyphs.attributes[i+1].justification != HB_Space + && glyphs.attributes[i+1].justification != HB_Arabic_Space)) + glyphs.advances_x[i] += wordSpacing; + } + } + } + + for (int i = 0; i < si.num_glyphs; ++i) + si.width += glyphs.advances_x[i]; +} + +static inline bool hasCaseChange(const QScriptItem &si) +{ + return si.analysis.flags == QScriptAnalysis::SmallCaps || + si.analysis.flags == QScriptAnalysis::Uppercase || + si.analysis.flags == QScriptAnalysis::Lowercase; +} + +#if defined(Q_WS_WINCE) //TODO +// set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs +// and no reordering. +// also computes logClusters heuristically +static void heuristicSetGlyphAttributes(const QChar *uc, int length, QGlyphLayout *glyphs, unsigned short *logClusters, int num_glyphs) +{ + // ### zeroWidth and justification are missing here!!!!! + + Q_UNUSED(num_glyphs); + Q_ASSERT(num_glyphs <= length); + +// qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs); + + int glyph_pos = 0; + for (int i = 0; i < length; i++) { + if (uc[i].unicode() >= 0xd800 && uc[i].unicode() < 0xdc00 && i < length-1 + && uc[i+1].unicode() >= 0xdc00 && uc[i+1].unicode() < 0xe000) { + logClusters[i] = glyph_pos; + logClusters[++i] = glyph_pos; + } else { + logClusters[i] = glyph_pos; + } + ++glyph_pos; + } + + // first char in a run is never (treated as) a mark + int cStart = 0; + + const bool symbolFont = false; // #### + glyphs->attributes[0].mark = false; + glyphs->attributes[0].clusterStart = true; + glyphs->attributes[0].dontPrint = (!symbolFont && uc[0].unicode() == 0x00ad) || qIsControlChar(uc[0].unicode()); + + int pos = 0; + int lastCat = QChar::category(uc[0].unicode()); + for (int i = 1; i < length; ++i) { + if (logClusters[i] == pos) + // same glyph + continue; + ++pos; + while (pos < logClusters[i]) { + glyphs[pos].attributes = glyphs[pos-1].attributes; + ++pos; + } + // hide soft-hyphens by default + if ((!symbolFont && uc[i].unicode() == 0x00ad) || qIsControlChar(uc[i].unicode())) + glyphs->attributes[pos].dontPrint = true; + const QUnicodeTables::Properties *prop = QUnicodeTables::properties(uc[i].unicode()); + int cat = prop->category; + if (cat != QChar::Mark_NonSpacing) { + glyphs->attributes[pos].mark = false; + glyphs->attributes[pos].clusterStart = true; + glyphs->attributes[pos].combiningClass = 0; + cStart = logClusters[i]; + } else { + int cmb = prop->combiningClass; + + if (cmb == 0) { + // Fix 0 combining classes + if ((uc[pos].unicode() & 0xff00) == 0x0e00) { + // thai or lao + unsigned char col = uc[pos].cell(); + if (col == 0x31 || + col == 0x34 || + col == 0x35 || + col == 0x36 || + col == 0x37 || + col == 0x47 || + col == 0x4c || + col == 0x4d || + col == 0x4e) { + cmb = QChar::Combining_AboveRight; + } else if (col == 0xb1 || + col == 0xb4 || + col == 0xb5 || + col == 0xb6 || + col == 0xb7 || + col == 0xbb || + col == 0xcc || + col == 0xcd) { + cmb = QChar::Combining_Above; + } else if (col == 0xbc) { + cmb = QChar::Combining_Below; + } + } + } + + glyphs->attributes[pos].mark = true; + glyphs->attributes[pos].clusterStart = false; + glyphs->attributes[pos].combiningClass = cmb; + logClusters[i] = cStart; + glyphs->advances_x[pos] = 0; + glyphs->advances_y[pos] = 0; + } + + // one gets an inter character justification point if the current char is not a non spacing mark. + // as then the current char belongs to the last one and one gets a space justification point + // after the space char. + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos-1].justification = HB_Space; + else if (cat != QChar::Mark_NonSpacing) + glyphs->attributes[pos-1].justification = HB_Character; + else + glyphs->attributes[pos-1].justification = HB_NoJustification; + + lastCat = cat; + } + pos = logClusters[length-1]; + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos].justification = HB_Space; + else + glyphs->attributes[pos].justification = HB_Character; +} + +void QTextEngine::shapeTextWithCE(int item) const +{ + QScriptItem &si = layoutData->items[item]; + si.glyph_data_offset = layoutData->used; + + QFontEngine *fe = fontEngine(si, &si.ascent, &si.descent, &si.leading); + + QTextEngine::ShaperFlags flags; + if (si.analysis.bidiLevel % 2) + flags |= RightToLeft; + if (option.useDesignMetrics()) + flags |= DesignMetrics; + + // pre-initialize char attributes + if (! attributes()) + return; + + const int len = length(item); + int num_glyphs = length(item); + const QChar *str = layoutData->string.unicode() + si.position; + ushort upperCased[256]; + if (hasCaseChange(si)) { + ushort *uc = upperCased; + if (len > 256) + uc = new ushort[len]; + for (int i = 0; i < len; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = str[i].toLower().unicode(); + else + uc[i] = str[i].toUpper().unicode(); + } + str = reinterpret_cast(uc); + } + + while (true) { + if (! ensureSpace(num_glyphs)) { + // If str is converted to uppercase/lowercase form with a new buffer, + // we need to delete that buffer before return for error + const ushort *uc = reinterpret_cast(str); + if (hasCaseChange(si) && uc != upperCased) + delete [] uc; + return; + } + num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used; + + QGlyphLayout g = availableGlyphs(&si); + unsigned short *log_clusters = logClusters(&si); + + if (fe->stringToCMap(str, + len, + &g, + &num_glyphs, + flags)) { + heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs); + break; + } + } + + si.num_glyphs = num_glyphs; + + layoutData->used += si.num_glyphs; + + const ushort *uc = reinterpret_cast(str); + if (hasCaseChange(si) && uc != upperCased) + delete [] uc; +} +#endif + +static inline void moveGlyphData(const QGlyphLayout &destination, const QGlyphLayout &source, int num) +{ + if (num > 0 && destination.glyphs != source.glyphs) { + memmove(destination.glyphs, source.glyphs, num * sizeof(HB_Glyph)); + memmove(destination.attributes, source.attributes, num * sizeof(HB_GlyphAttributes)); + memmove(destination.advances_x, source.advances_x, num * sizeof(HB_Fixed)); + memmove(destination.offsets, source.offsets, num * sizeof(HB_FixedPoint)); + } +} + +/// take the item from layoutData->items and +void QTextEngine::shapeTextWithHarfbuzz(int item) const +{ + Q_ASSERT(sizeof(HB_Fixed) == sizeof(QFixed)); + Q_ASSERT(sizeof(HB_FixedPoint) == sizeof(QFixedPoint)); + + QScriptItem &si = layoutData->items[item]; + + si.glyph_data_offset = layoutData->used; + + QFontEngine *font = fontEngine(si, &si.ascent, &si.descent, &si.leading); + + bool kerningEnabled = this->font(si).d->kerning; + + HB_ShaperItem entire_shaper_item; + qMemSet(&entire_shaper_item, 0, sizeof(entire_shaper_item)); + entire_shaper_item.string = reinterpret_cast(layoutData->string.constData()); + entire_shaper_item.stringLength = layoutData->string.length(); + entire_shaper_item.item.script = (HB_Script)si.analysis.script; + entire_shaper_item.item.pos = si.position; + entire_shaper_item.item.length = length(item); + entire_shaper_item.item.bidiLevel = si.analysis.bidiLevel; + + HB_UChar16 upperCased[256]; // XXX what about making this 4096, so we don't have to extend it ever. + if (hasCaseChange(si)) { + HB_UChar16 *uc = upperCased; + if (entire_shaper_item.item.length > 256) + uc = new HB_UChar16[entire_shaper_item.item.length]; + for (uint i = 0; i < entire_shaper_item.item.length; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = QChar::toLower(entire_shaper_item.string[si.position + i]); + else + uc[i] = QChar::toUpper(entire_shaper_item.string[si.position + i]); + } + entire_shaper_item.item.pos = 0; + entire_shaper_item.string = uc; + entire_shaper_item.stringLength = entire_shaper_item.item.length; + } + + entire_shaper_item.shaperFlags = 0; + if (!kerningEnabled) + entire_shaper_item.shaperFlags |= HB_ShaperFlag_NoKerning; + if (option.useDesignMetrics()) + entire_shaper_item.shaperFlags |= HB_ShaperFlag_UseDesignMetrics; + + entire_shaper_item.num_glyphs = qMax(layoutData->glyphLayout.numGlyphs - layoutData->used, int(entire_shaper_item.item.length)); + if (! ensureSpace(entire_shaper_item.num_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + QGlyphLayout initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs); + + if (!stringToGlyphs(&entire_shaper_item, &initialGlyphs, font)) { + if (! ensureSpace(entire_shaper_item.num_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs); + + if (!stringToGlyphs(&entire_shaper_item, &initialGlyphs, font)) { + // ############ if this happens there's a bug in the fontengine + if (hasCaseChange(si) && entire_shaper_item.string != upperCased) + delete [] const_cast(entire_shaper_item.string); + return; + } + } + + // split up the item into parts that come from different font engines. + QVarLengthArray itemBoundaries(2); + // k * 2 entries, array[k] == index in string, array[k + 1] == index in glyphs + itemBoundaries[0] = entire_shaper_item.item.pos; + itemBoundaries[1] = 0; + + if (font->type() == QFontEngine::Multi) { + uint lastEngine = 0; + int charIdx = entire_shaper_item.item.pos; + const int stringEnd = charIdx + entire_shaper_item.item.length; + for (quint32 i = 0; i < entire_shaper_item.num_glyphs; ++i, ++charIdx) { + uint engineIdx = initialGlyphs.glyphs[i] >> 24; + if (engineIdx != lastEngine && i > 0) { + itemBoundaries.append(charIdx); + itemBoundaries.append(i); + } + lastEngine = engineIdx; + if (HB_IsHighSurrogate(entire_shaper_item.string[charIdx]) + && charIdx < stringEnd - 1 + && HB_IsLowSurrogate(entire_shaper_item.string[charIdx + 1])) + ++charIdx; + } + } + + + + int remaining_glyphs = entire_shaper_item.num_glyphs; + int glyph_pos = 0; + // for each item shape using harfbuzz and store the results in our layoutData's glyphs array. + for (int k = 0; k < itemBoundaries.size(); k += 2) { // for the +2, see the comment at the definition of itemBoundaries + + HB_ShaperItem shaper_item = entire_shaper_item; + + shaper_item.item.pos = itemBoundaries[k]; + if (k < itemBoundaries.size() - 3) { + shaper_item.item.length = itemBoundaries[k + 2] - shaper_item.item.pos; + shaper_item.num_glyphs = itemBoundaries[k + 3] - itemBoundaries[k + 1]; + } else { // last combo in the list, avoid out of bounds access. + shaper_item.item.length -= shaper_item.item.pos - entire_shaper_item.item.pos; + shaper_item.num_glyphs -= itemBoundaries[k + 1]; + } + shaper_item.initialGlyphCount = shaper_item.num_glyphs; + if (shaper_item.num_glyphs < shaper_item.item.length) + shaper_item.num_glyphs = shaper_item.item.length; + + QFontEngine *actualFontEngine = font; + uint engineIdx = 0; + if (font->type() == QFontEngine::Multi) { + engineIdx = uint(availableGlyphs(&si).glyphs[glyph_pos] >> 24); + + actualFontEngine = static_cast(font)->engine(engineIdx); + } + + shaper_item.font = actualFontEngine->harfbuzzFont(); + shaper_item.face = actualFontEngine->harfbuzzFace(); + + shaper_item.glyphIndicesPresent = true; + + remaining_glyphs -= shaper_item.initialGlyphCount; + + do { + if (! ensureSpace(glyph_pos + shaper_item.num_glyphs + remaining_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + + const QGlyphLayout g = availableGlyphs(&si).mid(glyph_pos); + if (shaper_item.num_glyphs > shaper_item.item.length) + moveGlyphData(g.mid(shaper_item.num_glyphs), g.mid(shaper_item.initialGlyphCount), remaining_glyphs); + + shaper_item.glyphs = g.glyphs; + shaper_item.attributes = g.attributes; + shaper_item.advances = reinterpret_cast(g.advances_x); + shaper_item.offsets = reinterpret_cast(g.offsets); + + if (shaper_item.glyphIndicesPresent) { + for (hb_uint32 i = 0; i < shaper_item.initialGlyphCount; ++i) + shaper_item.glyphs[i] &= 0x00ffffff; + } + + shaper_item.log_clusters = logClusters(&si) + shaper_item.item.pos - entire_shaper_item.item.pos; + +// qDebug(" .. num_glyphs=%d, used=%d, item.num_glyphs=%d", num_glyphs, used, shaper_item.num_glyphs); + } while (!qShapeItem(&shaper_item)); // this does the actual shaping via harfbuzz. + + QGlyphLayout g = availableGlyphs(&si).mid(glyph_pos, shaper_item.num_glyphs); + moveGlyphData(g.mid(shaper_item.num_glyphs), g.mid(shaper_item.initialGlyphCount), remaining_glyphs); + + for (hb_uint32 i = 0; i < shaper_item.num_glyphs; ++i) + g.glyphs[i] = g.glyphs[i] | (engineIdx << 24); + + for (hb_uint32 i = 0; i < shaper_item.item.length; ++i) + shaper_item.log_clusters[i] += glyph_pos; + + if (kerningEnabled && !shaper_item.kerning_applied) + font->doKerning(&g, option.useDesignMetrics() ? QFlag(QTextEngine::DesignMetrics) : QFlag(0)); + + glyph_pos += shaper_item.num_glyphs; + } + +// qDebug(" -> item: script=%d num_glyphs=%d", shaper_item.script, shaper_item.num_glyphs); + si.num_glyphs = glyph_pos; + + layoutData->used += si.num_glyphs; + + if (hasCaseChange(si) && entire_shaper_item.string != upperCased) + delete [] const_cast(entire_shaper_item.string); +} + +static void init(QTextEngine *e) +{ + e->ignoreBidi = false; + e->cacheGlyphs = false; + e->forceJustification = false; + + e->layoutData = 0; + + e->minWidth = 0; + e->maxWidth = 0; + + e->underlinePositions = 0; + e->specialData = 0; + e->stackEngine = false; +} + +QTextEngine::QTextEngine() +{ + init(this); +} + +QTextEngine::QTextEngine(const QString &str, const QFont &f) + : text(str), + fnt(f) +{ + init(this); +} + +QTextEngine::~QTextEngine() +{ + if (!stackEngine) + delete layoutData; + delete specialData; +} + +const HB_CharAttributes *QTextEngine::attributes() const +{ + if (layoutData && layoutData->haveCharAttributes) + return (HB_CharAttributes *) layoutData->memory; + + itemize(); + if (! ensureSpace(layoutData->string.length())) + return NULL; + + QVarLengthArray hbScriptItems(layoutData->items.size()); + + for (int i = 0; i < layoutData->items.size(); ++i) { + const QScriptItem &si = layoutData->items[i]; + hbScriptItems[i].pos = si.position; + hbScriptItems[i].length = length(i); + hbScriptItems[i].bidiLevel = si.analysis.bidiLevel; + hbScriptItems[i].script = (HB_Script)si.analysis.script; + } + + qGetCharAttributes(reinterpret_cast(layoutData->string.constData()), + layoutData->string.length(), + hbScriptItems.data(), hbScriptItems.size(), + (HB_CharAttributes *)layoutData->memory); + + + layoutData->haveCharAttributes = true; + return (HB_CharAttributes *) layoutData->memory; +} + +void QTextEngine::shape(int item) const +{ + if (layoutData->items[item].analysis.flags == QScriptAnalysis::Object) { + ensureSpace(1); + if (block.docHandle()) { + QTextFormat format = formats()->format(formatIndex(&layoutData->items[item])); + docLayout()->resizeInlineObject(QTextInlineObject(item, const_cast(this)), + layoutData->items[item].position + block.position(), format); + } + } else if (layoutData->items[item].analysis.flags == QScriptAnalysis::Tab) { + // set up at least the ascent/descent/leading of the script item for the tab + fontEngine(layoutData->items[item], + &layoutData->items[item].ascent, + &layoutData->items[item].descent, + &layoutData->items[item].leading); + } else { + shapeText(item); + } +} + +static inline void releaseCachedFontEngine(QFontEngine *fontEngine) +{ + if (fontEngine) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + } +} + +void QTextEngine::invalidate() +{ + freeMemory(); + minWidth = 0; + maxWidth = 0; + if (specialData) + specialData->resolvedFormatIndices.clear(); + + releaseCachedFontEngine(feCache.prevFontEngine); + releaseCachedFontEngine(feCache.prevScaledFontEngine); + feCache.reset(); +} + +void QTextEngine::clearLineData() +{ + lines.clear(); +} + +void QTextEngine::validate() const +{ + if (layoutData) + return; + layoutData = new LayoutData(); + if (block.docHandle()) { + layoutData->string = block.text(); + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) + layoutData->string += QLatin1Char(block.next().isValid() ? 0xb6 : 0x20); + } else { + layoutData->string = text; + } + if (specialData && specialData->preeditPosition != -1) + layoutData->string.insert(specialData->preeditPosition, specialData->preeditText); +} + +void QTextEngine::itemize() const +{ + validate(); + if (layoutData->items.size()) + return; + + int length = layoutData->string.length(); + if (!length) + return; +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + // ATSUI requires RTL flags to correctly identify the character stops. + bool ignore = false; +#else + bool ignore = ignoreBidi; +#endif + + bool rtl = isRightToLeft(); + + if (!ignore && !rtl) { + ignore = true; + const QChar *start = layoutData->string.unicode(); + const QChar * const end = start + length; + while (start < end) { + if (start->unicode() >= 0x590) { + ignore = false; + break; + } + ++start; + } + } + + QVarLengthArray scriptAnalysis(length); + QScriptAnalysis *analysis = scriptAnalysis.data(); + + QBidiControl control(rtl); + + if (ignore) { + memset(analysis, 0, length*sizeof(QScriptAnalysis)); + if (option.textDirection() == Qt::RightToLeft) { + for (int i = 0; i < length; ++i) + analysis[i].bidiLevel = 1; + layoutData->hasBidi = true; + } + } else { + layoutData->hasBidi = bidiItemize(const_cast(this), analysis, control); + } + + const ushort *uc = reinterpret_cast(layoutData->string.unicode()); + const ushort *e = uc + length; + int lastScript = QUnicodeTables::Common; + while (uc < e) { + int script = QUnicodeTables::script(*uc); + if (script == QUnicodeTables::Inherited) + script = lastScript; + analysis->flags = QScriptAnalysis::None; + if (*uc == QChar::ObjectReplacementCharacter) { + if (analysis->bidiLevel % 2) + --analysis->bidiLevel; + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Object; + } else if (*uc == QChar::LineSeparator) { + if (analysis->bidiLevel % 2) + --analysis->bidiLevel; + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::LineOrParagraphSeparator; + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) + *const_cast(uc) = 0x21B5; // visual line separator + } else if (*uc == 9) { + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Tab; + analysis->bidiLevel = control.baseLevel(); + } else if ((*uc == 32 || *uc == QChar::Nbsp) + && (option.flags() & QTextOption::ShowTabsAndSpaces)) { + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Space; + analysis->bidiLevel = control.baseLevel(); + } else { + analysis->script = script; + } + lastScript = analysis->script; + ++uc; + ++analysis; + } + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) { + (analysis-1)->flags = QScriptAnalysis::LineOrParagraphSeparator; // to exclude it from width + } + + Itemizer itemizer(layoutData->string, scriptAnalysis.data(), layoutData->items); + + const QTextDocumentPrivate *p = block.docHandle(); + if (p) { + SpecialData *s = specialData; + + QTextDocumentPrivate::FragmentIterator it = p->find(block.position()); + QTextDocumentPrivate::FragmentIterator end = p->find(block.position() + block.length() - 1); // -1 to omit the block separator char + int format = it.value()->format; + + int prevPosition = 0; + int position = prevPosition; + while (1) { + const QTextFragmentData * const frag = it.value(); + if (it == end || format != frag->format) { + if (s && position >= s->preeditPosition) { + position += s->preeditText.length(); + s = 0; + } + Q_ASSERT(position <= length); + itemizer.generate(prevPosition, position - prevPosition, + formats()->charFormat(format).fontCapitalization()); + if (it == end) { + if (position < length) + itemizer.generate(position, length - position, + formats()->charFormat(format).fontCapitalization()); + break; + } + format = frag->format; + prevPosition = position; + } + position += frag->size_array[0]; + ++it; + } + } else { + itemizer.generate(0, length, static_cast (fnt.d->capital)); + } + + addRequiredBoundaries(); + resolveAdditionalFormats(); +} + +bool QTextEngine::isRightToLeft() const +{ + switch (option.textDirection()) { + case Qt::LeftToRight: + return false; + case Qt::RightToLeft: + return true; + default: + break; + } + // this places the cursor in the right position depending on the keyboard layout + if (layoutData->string.isEmpty()) + return QApplication::keyboardInputDirection() == Qt::RightToLeft; + return layoutData->string.isRightToLeft(); +} + + +int QTextEngine::findItem(int strPos) const +{ + itemize(); + int left = 0; + int right = layoutData->items.size()-1; + while(left <= right) { + int middle = ((right-left)/2)+left; + if (strPos > layoutData->items[middle].position) + left = middle+1; + else if(strPos < layoutData->items[middle].position) + right = middle-1; + else { + return middle; + } + } + return right; +} + +QFixed QTextEngine::width(int from, int len) const +{ + itemize(); + + QFixed w = 0; + +// qDebug("QTextEngine::width(from = %d, len = %d), numItems=%d, strleng=%d", from, len, items.size(), string.length()); + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + int pos = si->position; + int ilen = length(i); +// qDebug("item %d: from %d len %d", i, pos, ilen); + if (pos >= from + len) + break; + if (pos + ilen > from) { + if (!si->num_glyphs) + shape(i); + + if (si->analysis.flags == QScriptAnalysis::Object) { + w += si->width; + continue; + } else if (si->analysis.flags == QScriptAnalysis::Tab) { + w += calculateTabWidth(i, w); + continue; + } + + + QGlyphLayout glyphs = shapedGlyphs(si); + unsigned short *logClusters = this->logClusters(si); + +// fprintf(stderr, " logclusters:"); +// for (int k = 0; k < ilen; k++) +// fprintf(stderr, " %d", logClusters[k]); +// fprintf(stderr, "\n"); + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + +// qDebug("char: start=%d end=%d / glyph: start = %d, end = %d", charFrom, charEnd, glyphStart, glyphEnd); + for (int i = glyphStart; i < glyphEnd; i++) + w += glyphs.advances_x[i] * !glyphs.attributes[i].dontPrint; + } + } + } +// qDebug(" --> w= %d ", w); + return w; +} + +glyph_metrics_t QTextEngine::boundingBox(int from, int len) const +{ + itemize(); + + glyph_metrics_t gm; + + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + + int pos = si->position; + int ilen = length(i); + if (pos > from + len) + break; + if (pos + ilen > from) { + if (!si->num_glyphs) + shape(i); + + if (si->analysis.flags == QScriptAnalysis::Object) { + gm.width += si->width; + continue; + } else if (si->analysis.flags == QScriptAnalysis::Tab) { + gm.width += calculateTabWidth(i, gm.width); + continue; + } + + unsigned short *logClusters = this->logClusters(si); + QGlyphLayout glyphs = shapedGlyphs(si); + + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + QFontEngine *fe = fontEngine(*si); + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + if (glyphStart <= glyphEnd ) { + glyph_metrics_t m = fe->boundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart)); + gm.x = qMin(gm.x, m.x + gm.xoff); + gm.y = qMin(gm.y, m.y + gm.yoff); + gm.width = qMax(gm.width, m.width+gm.xoff); + gm.height = qMax(gm.height, m.height+gm.yoff); + gm.xoff += m.xoff; + gm.yoff += m.yoff; + } + } + } + } + return gm; +} + +glyph_metrics_t QTextEngine::tightBoundingBox(int from, int len) const +{ + itemize(); + + glyph_metrics_t gm; + + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + int pos = si->position; + int ilen = length(i); + if (pos > from + len) + break; + if (pos + len > from) { + if (!si->num_glyphs) + shape(i); + unsigned short *logClusters = this->logClusters(si); + QGlyphLayout glyphs = shapedGlyphs(si); + + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + if (glyphStart <= glyphEnd ) { + QFontEngine *fe = fontEngine(*si); + glyph_metrics_t m = fe->tightBoundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart)); + gm.x = qMin(gm.x, m.x + gm.xoff); + gm.y = qMin(gm.y, m.y + gm.yoff); + gm.width = qMax(gm.width, m.width+gm.xoff); + gm.height = qMax(gm.height, m.height+gm.yoff); + gm.xoff += m.xoff; + gm.yoff += m.yoff; + } + } + } + } + return gm; +} + +QFont QTextEngine::font(const QScriptItem &si) const +{ + QFont font = fnt; + if (hasFormats()) { + QTextCharFormat f = format(&si); + font = f.font(); + + if (block.docHandle() && block.docHandle()->layout()) { + // Make sure we get the right dpi on printers + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + font = QFont(font, pdev); + } else { + font = font.resolve(fnt); + } + QTextCharFormat::VerticalAlignment valign = f.verticalAlignment(); + if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) { + if (font.pointSize() != -1) + font.setPointSize((font.pointSize() * 2) / 3); + else + font.setPixelSize((font.pixelSize() * 2) / 3); + } + } + + if (si.analysis.flags == QScriptAnalysis::SmallCaps) + font = font.d->smallCapsFont(); + + return font; +} + +QTextEngine::FontEngineCache::FontEngineCache() +{ + reset(); +} + +//we cache the previous results of this function, as calling it numerous times with the same effective +//input is common (and hard to cache at a higher level) +QFontEngine *QTextEngine::fontEngine(const QScriptItem &si, QFixed *ascent, QFixed *descent, QFixed *leading) const +{ + QFontEngine *engine = 0; + QFontEngine *scaledEngine = 0; + int script = si.analysis.script; + + QFont font = fnt; + if (hasFormats()) { + if (feCache.prevFontEngine && feCache.prevPosition == si.position && feCache.prevLength == length(&si) && feCache.prevScript == script) { + engine = feCache.prevFontEngine; + scaledEngine = feCache.prevScaledFontEngine; + } else { + QTextCharFormat f = format(&si); + font = f.font(); + + if (block.docHandle() && block.docHandle()->layout()) { + // Make sure we get the right dpi on printers + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + font = QFont(font, pdev); + } else { + font = font.resolve(fnt); + } + engine = font.d->engineForScript(script); + QTextCharFormat::VerticalAlignment valign = f.verticalAlignment(); + if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) { + if (font.pointSize() != -1) + font.setPointSize((font.pointSize() * 2) / 3); + else + font.setPixelSize((font.pixelSize() * 2) / 3); + scaledEngine = font.d->engineForScript(script); + } + feCache.prevFontEngine = engine; + if (engine) + engine->ref.ref(); + feCache.prevScaledFontEngine = scaledEngine; + if (scaledEngine) + scaledEngine->ref.ref(); + feCache.prevScript = script; + feCache.prevPosition = si.position; + feCache.prevLength = length(&si); + } + } else { + if (feCache.prevFontEngine && feCache.prevScript == script && feCache.prevPosition == -1) + engine = feCache.prevFontEngine; + else { + engine = font.d->engineForScript(script); + feCache.prevFontEngine = engine; + if (engine) + engine->ref.ref(); + feCache.prevScript = script; + feCache.prevPosition = -1; + feCache.prevLength = -1; + feCache.prevScaledFontEngine = 0; + } + } + + if (si.analysis.flags == QScriptAnalysis::SmallCaps) { + QFontPrivate *p = font.d->smallCapsFontPrivate(); + scaledEngine = p->engineForScript(script); + } + + if (ascent) { + *ascent = engine->ascent(); + *descent = engine->descent(); + *leading = engine->leading(); + } + + if (scaledEngine) + return scaledEngine; + return engine; +} + +struct QJustificationPoint { + int type; + QFixed kashidaWidth; + QGlyphLayout glyph; + QFontEngine *fontEngine; +}; + +Q_DECLARE_TYPEINFO(QJustificationPoint, Q_PRIMITIVE_TYPE); + +static void set(QJustificationPoint *point, int type, const QGlyphLayout &glyph, QFontEngine *fe) +{ + point->type = type; + point->glyph = glyph; + point->fontEngine = fe; + + if (type >= HB_Arabic_Normal) { + QChar ch(0x640); // Kashida character + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + fe->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + if (glyphs.glyphs[0] && glyphs.advances_x[0] != 0) { + point->kashidaWidth = glyphs.advances_x[0]; + } else { + point->type = HB_NoJustification; + point->kashidaWidth = 0; + } + } +} + + +void QTextEngine::justify(const QScriptLine &line) +{ +// qDebug("justify: line.gridfitted = %d, line.justified=%d", line.gridfitted, line.justified); + if (line.gridfitted && line.justified) + return; + + if (!line.gridfitted) { + // redo layout in device metrics, then adjust + const_cast(line).gridfitted = true; + } + + if ((option.alignment() & Qt::AlignHorizontal_Mask) != Qt::AlignJustify) + return; + + itemize(); + + if (!forceJustification) { + int end = line.from + (int)line.length; + if (end == layoutData->string.length()) + return; // no justification at end of paragraph + if (end && layoutData->items[findItem(end-1)].analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) + return; // no justification at the end of an explicitly separated line + } + + // justify line + int maxJustify = 0; + + // don't include trailing white spaces when doing justification + int line_length = line.length; + const HB_CharAttributes *a = attributes(); + if (! a) + return; + a += line.from; + while (line_length && a[line_length-1].whiteSpace) + --line_length; + // subtract one char more, as we can't justfy after the last character + --line_length; + + if (!line_length) + return; + + int firstItem = findItem(line.from); + int nItems = findItem(line.from + line_length - 1) - firstItem + 1; + + QVarLengthArray justificationPoints; + int nPoints = 0; +// qDebug("justifying from %d len %d, firstItem=%d, nItems=%d (%s)", line.from, line_length, firstItem, nItems, layoutData->string.mid(line.from, line_length).toUtf8().constData()); + QFixed minKashida = 0x100000; + + // we need to do all shaping before we go into the next loop, as we there + // store pointers to the glyph data that could get reallocated by the shaping + // process. + for (int i = 0; i < nItems; ++i) { + QScriptItem &si = layoutData->items[firstItem + i]; + if (!si.num_glyphs) + shape(firstItem + i); + } + + for (int i = 0; i < nItems; ++i) { + QScriptItem &si = layoutData->items[firstItem + i]; + + int kashida_type = HB_Arabic_Normal; + int kashida_pos = -1; + + int start = qMax(line.from - si.position, 0); + int end = qMin(line.from + line_length - (int)si.position, length(firstItem+i)); + + unsigned short *log_clusters = logClusters(&si); + + int gs = log_clusters[start]; + int ge = (end == length(firstItem+i) ? si.num_glyphs : log_clusters[end]); + + const QGlyphLayout g = shapedGlyphs(&si); + + for (int i = gs; i < ge; ++i) { + g.justifications[i].type = QGlyphJustification::JustifyNone; + g.justifications[i].nKashidas = 0; + g.justifications[i].space_18d6 = 0; + + justificationPoints.resize(nPoints+3); + int justification = g.attributes[i].justification; + + switch(justification) { + case HB_NoJustification: + break; + case HB_Space : + // fall through + case HB_Arabic_Space : + if (kashida_pos >= 0) { +// qDebug("kashida position at %d in word", kashida_pos); + set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si)); + if (justificationPoints[nPoints].kashidaWidth > 0) { + minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth); + maxJustify = qMax(maxJustify, justificationPoints[nPoints].type); + ++nPoints; + } + } + kashida_pos = -1; + kashida_type = HB_Arabic_Normal; + // fall through + case HB_Character : + set(&justificationPoints[nPoints++], justification, g.mid(i), fontEngine(si)); + maxJustify = qMax(maxJustify, justification); + break; + case HB_Arabic_Normal : + case HB_Arabic_Waw : + case HB_Arabic_BaRa : + case HB_Arabic_Alef : + case HB_Arabic_HaaDal : + case HB_Arabic_Seen : + case HB_Arabic_Kashida : + if (justification >= kashida_type) { + kashida_pos = i; + kashida_type = justification; + } + } + } + if (kashida_pos >= 0) { + set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si)); + if (justificationPoints[nPoints].kashidaWidth > 0) { + minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth); + maxJustify = qMax(maxJustify, justificationPoints[nPoints].type); + ++nPoints; + } + } + } + + QFixed need = line.width - line.textWidth; + if (need < 0) { + // line overflows already! + const_cast(line).justified = true; + return; + } + +// qDebug("doing justification: textWidth=%x, requested=%x, maxJustify=%d", line.textWidth.value(), line.width.value(), maxJustify); +// qDebug(" minKashida=%f, need=%f", minKashida.toReal(), need.toReal()); + + // distribute in priority order + if (maxJustify >= HB_Arabic_Normal) { + while (need >= minKashida) { + for (int type = maxJustify; need >= minKashida && type >= HB_Arabic_Normal; --type) { + for (int i = 0; need >= minKashida && i < nPoints; ++i) { + if (justificationPoints[i].type == type && justificationPoints[i].kashidaWidth <= need) { + justificationPoints[i].glyph.justifications->nKashidas++; + // ############ + justificationPoints[i].glyph.justifications->space_18d6 += justificationPoints[i].kashidaWidth.value(); + need -= justificationPoints[i].kashidaWidth; +// qDebug("adding kashida type %d with width %x, neednow %x", type, justificationPoints[i].kashidaWidth, need.value()); + } + } + } + } + } + Q_ASSERT(need >= 0); + if (!need) + goto end; + + maxJustify = qMin(maxJustify, (int)HB_Space); + for (int type = maxJustify; need != 0 && type > 0; --type) { + int n = 0; + for (int i = 0; i < nPoints; ++i) { + if (justificationPoints[i].type == type) + ++n; + } +// qDebug("number of points for justification type %d: %d", type, n); + + + if (!n) + continue; + + for (int i = 0; i < nPoints; ++i) { + if (justificationPoints[i].type == type) { + QFixed add = need/n; +// qDebug("adding %x to glyph %x", add.value(), justificationPoints[i].glyph->glyph); + justificationPoints[i].glyph.justifications[0].space_18d6 = add.value(); + need -= add; + --n; + } + } + + Q_ASSERT(!need); + } + end: + const_cast(line).justified = true; +} + +void QScriptLine::setDefaultHeight(QTextEngine *eng) +{ + QFont f; + QFontEngine *e; + + if (eng->block.docHandle() && eng->block.docHandle()->layout()) { + f = eng->block.charFormat().font(); + // Make sure we get the right dpi on printers + QPaintDevice *pdev = eng->block.docHandle()->layout()->paintDevice(); + if (pdev) + f = QFont(f, pdev); + e = f.d->engineForScript(QUnicodeTables::Common); + } else { + e = eng->fnt.d->engineForScript(QUnicodeTables::Common); + } + + QFixed other_ascent = e->ascent(); + QFixed other_descent = e->descent(); + QFixed other_leading = e->leading(); + leading = qMax(leading + ascent, other_leading + other_ascent) - qMax(ascent, other_ascent); + ascent = qMax(ascent, other_ascent); + descent = qMax(descent, other_descent); +} + +QTextEngine::LayoutData::LayoutData() +{ + memory = 0; + allocated = 0; + memory_on_stack = false; + used = 0; + hasBidi = false; + layoutState = LayoutEmpty; + haveCharAttributes = false; + logClustersPtr = 0; + available_glyphs = 0; +} + +QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, int _allocated) + : string(str) +{ + allocated = _allocated; + + int space_charAttributes = sizeof(HB_CharAttributes)*string.length()/sizeof(void*) + 1; + int space_logClusters = sizeof(unsigned short)*string.length()/sizeof(void*) + 1; + available_glyphs = ((int)allocated - space_charAttributes - space_logClusters)*(int)sizeof(void*)/(int)QGlyphLayout::spaceNeededForGlyphLayout(1); + + if (available_glyphs < str.length()) { + // need to allocate on the heap + allocated = 0; + + memory_on_stack = false; + memory = 0; + logClustersPtr = 0; + } else { + memory_on_stack = true; + memory = stack_memory; + logClustersPtr = (unsigned short *)(memory + space_charAttributes); + + void *m = memory + space_charAttributes + space_logClusters; + glyphLayout = QGlyphLayout(reinterpret_cast(m), str.length()); + glyphLayout.clear(); + memset(memory, 0, space_charAttributes*sizeof(void *)); + } + used = 0; + hasBidi = false; + layoutState = LayoutEmpty; + haveCharAttributes = false; +} + +QTextEngine::LayoutData::~LayoutData() +{ + if (!memory_on_stack) + free(memory); + memory = 0; +} + +bool QTextEngine::LayoutData::reallocate(int totalGlyphs) +{ + Q_ASSERT(totalGlyphs >= glyphLayout.numGlyphs); + if (memory_on_stack && available_glyphs >= totalGlyphs) { + glyphLayout.grow(glyphLayout.data(), totalGlyphs); + return true; + } + + int space_charAttributes = sizeof(HB_CharAttributes)*string.length()/sizeof(void*) + 1; + int space_logClusters = sizeof(unsigned short)*string.length()/sizeof(void*) + 1; + int space_glyphs = QGlyphLayout::spaceNeededForGlyphLayout(totalGlyphs)/sizeof(void*) + 2; + + int newAllocated = space_charAttributes + space_glyphs + space_logClusters; + // These values can be negative if the length of string/glyphs causes overflow, + // we can't layout such a long string all at once, so return false here to + // indicate there is a failure + if (space_charAttributes < 0 || space_logClusters < 0 || space_glyphs < 0 || newAllocated < allocated) { + layoutState = LayoutFailed; + return false; + } + + void **newMem = memory; + newMem = (void **)::realloc(memory_on_stack ? 0 : memory, newAllocated*sizeof(void *)); + if (!newMem) { + layoutState = LayoutFailed; + return false; + } + if (memory_on_stack) + memcpy(newMem, memory, allocated*sizeof(void *)); + memory = newMem; + memory_on_stack = false; + + void **m = memory; + m += space_charAttributes; + logClustersPtr = (unsigned short *) m; + m += space_logClusters; + + const int space_preGlyphLayout = space_charAttributes + space_logClusters; + if (allocated < space_preGlyphLayout) + memset(memory + allocated, 0, (space_preGlyphLayout - allocated)*sizeof(void *)); + + glyphLayout.grow(reinterpret_cast(m), totalGlyphs); + + allocated = newAllocated; + return true; +} + +// grow to the new size, copying the existing data to the new layout +void QGlyphLayout::grow(char *address, int totalGlyphs) +{ + QGlyphLayout oldLayout(address, numGlyphs); + QGlyphLayout newLayout(address, totalGlyphs); + + if (numGlyphs) { + // move the existing data + memmove(newLayout.attributes, oldLayout.attributes, numGlyphs * sizeof(HB_GlyphAttributes)); + memmove(newLayout.justifications, oldLayout.justifications, numGlyphs * sizeof(QGlyphJustification)); + memmove(newLayout.advances_y, oldLayout.advances_y, numGlyphs * sizeof(QFixed)); + memmove(newLayout.advances_x, oldLayout.advances_x, numGlyphs * sizeof(QFixed)); + memmove(newLayout.glyphs, oldLayout.glyphs, numGlyphs * sizeof(HB_Glyph)); + } + + // clear the new data + newLayout.clear(numGlyphs); + + *this = newLayout; +} + +void QTextEngine::freeMemory() +{ + if (!stackEngine) { + delete layoutData; + layoutData = 0; + } else { + layoutData->used = 0; + layoutData->hasBidi = false; + layoutData->layoutState = LayoutEmpty; + layoutData->haveCharAttributes = false; + } + for (int i = 0; i < lines.size(); ++i) { + lines[i].justified = 0; + lines[i].gridfitted = 0; + } +} + +int QTextEngine::formatIndex(const QScriptItem *si) const +{ + if (specialData && !specialData->resolvedFormatIndices.isEmpty()) + return specialData->resolvedFormatIndices.at(si - &layoutData->items[0]); + QTextDocumentPrivate *p = block.docHandle(); + if (!p) + return -1; + int pos = si->position; + if (specialData && si->position >= specialData->preeditPosition) { + if (si->position < specialData->preeditPosition + specialData->preeditText.length()) + pos = qMax(specialData->preeditPosition - 1, 0); + else + pos -= specialData->preeditText.length(); + } + QTextDocumentPrivate::FragmentIterator it = p->find(block.position() + pos); + return it.value()->format; +} + + +QTextCharFormat QTextEngine::format(const QScriptItem *si) const +{ + QTextCharFormat format; + const QTextFormatCollection *formats = 0; + if (block.docHandle()) { + formats = this->formats(); + format = formats->charFormat(formatIndex(si)); + } + if (specialData && specialData->resolvedFormatIndices.isEmpty()) { + int end = si->position + length(si); + for (int i = 0; i < specialData->addFormats.size(); ++i) { + const QTextLayout::FormatRange &r = specialData->addFormats.at(i); + if (r.start <= si->position && r.start + r.length >= end) { + if (!specialData->addFormatIndices.isEmpty()) + format.merge(formats->format(specialData->addFormatIndices.at(i))); + else + format.merge(r.format); + } + } + } + return format; +} + +void QTextEngine::addRequiredBoundaries() const +{ + if (specialData) { + for (int i = 0; i < specialData->addFormats.size(); ++i) { + const QTextLayout::FormatRange &r = specialData->addFormats.at(i); + setBoundary(r.start); + setBoundary(r.start + r.length); + //qDebug("adding boundaries %d %d", r.start, r.start+r.length); + } + } +} + +bool QTextEngine::atWordSeparator(int position) const +{ + const QChar c = layoutData->string.at(position); + switch (c.toLatin1()) { + case '.': + case ',': + case '?': + case '!': + case '@': + case '#': + case '$': + case ':': + case ';': + case '-': + case '<': + case '>': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '=': + case '/': + case '+': + case '%': + case '&': + case '^': + case '*': + case '\'': + case '"': + case '`': + case '~': + case '|': + return true; + default: + return false; + } +} + +bool QTextEngine::atSpace(int position) const +{ + const QChar c = layoutData->string.at(position); + + return c == QLatin1Char(' ') + || c == QChar::Nbsp + || c == QChar::LineSeparator + || c == QLatin1Char('\t') + ; +} + + +void QTextEngine::indexAdditionalFormats() +{ + if (!block.docHandle()) + return; + + specialData->addFormatIndices.resize(specialData->addFormats.count()); + QTextFormatCollection * const formats = this->formats(); + + for (int i = 0; i < specialData->addFormats.count(); ++i) { + specialData->addFormatIndices[i] = formats->indexForFormat(specialData->addFormats.at(i).format); + specialData->addFormats[i].format = QTextCharFormat(); + } +} + +/* These two helper functions are used to determine whether we need to insert a ZWJ character + between the text that gets truncated and the ellipsis. This is important to get + correctly shaped results for arabic text. +*/ +static bool nextCharJoins(const QString &string, int pos) +{ + while (pos < string.length() && string.at(pos).category() == QChar::Mark_NonSpacing) + ++pos; + if (pos == string.length()) + return false; + return string.at(pos).joining() != QChar::OtherJoining; +} + +static bool prevCharJoins(const QString &string, int pos) +{ + while (pos > 0 && string.at(pos - 1).category() == QChar::Mark_NonSpacing) + --pos; + if (pos == 0) + return false; + return (string.at(pos - 1).joining() == QChar::Dual || string.at(pos - 1).joining() == QChar::Center); +} + +QString QTextEngine::elidedText(Qt::TextElideMode mode, const QFixed &width, int flags) const +{ +// qDebug() << "elidedText; available width" << width.toReal() << "text width:" << this->width(0, layoutData->string.length()).toReal(); + + if (flags & Qt::TextShowMnemonic) { + itemize(); + HB_CharAttributes *attributes = const_cast(this->attributes()); + if (!attributes) + return QString(); + for (int i = 0; i < layoutData->items.size(); ++i) { + QScriptItem &si = layoutData->items[i]; + if (!si.num_glyphs) + shape(i); + + unsigned short *logClusters = this->logClusters(&si); + QGlyphLayout glyphs = shapedGlyphs(&si); + + const int end = si.position + length(&si); + for (int i = si.position; i < end - 1; ++i) { + if (layoutData->string.at(i) == QLatin1Char('&')) { + const int gp = logClusters[i - si.position]; + glyphs.attributes[gp].dontPrint = true; + attributes[i + 1].charStop = false; + attributes[i + 1].whiteSpace = false; + attributes[i + 1].lineBreakType = HB_NoBreak; + if (layoutData->string.at(i + 1) == QLatin1Char('&')) + ++i; + } + } + } + } + + validate(); + + if (mode == Qt::ElideNone + || this->width(0, layoutData->string.length()) <= width + || layoutData->string.length() <= 1) + return layoutData->string; + + QFixed ellipsisWidth; + QString ellipsisText; + { + QChar ellipsisChar(0x2026); + + QFontEngine *fe = fnt.d->engineForScript(QUnicodeTables::Common); + + QGlyphLayoutArray<1> ellipsisGlyph; + { + QFontEngine *feForEllipsis = (fe->type() == QFontEngine::Multi) + ? static_cast(fe)->engine(0) + : fe; + + if (feForEllipsis->type() == QFontEngine::Mac) + feForEllipsis = fe; + + // the lookup can be really slow when we use XLFD fonts + if (feForEllipsis->type() != QFontEngine::XLFD + && feForEllipsis->canRender(&ellipsisChar, 1)) { + int nGlyphs = 1; + feForEllipsis->stringToCMap(&ellipsisChar, 1, &ellipsisGlyph, &nGlyphs, 0); + } + } + + if (ellipsisGlyph.glyphs[0]) { + ellipsisWidth = ellipsisGlyph.advances_x[0]; + ellipsisText = ellipsisChar; + } else { + QString dotDotDot(QLatin1String("...")); + + QGlyphLayoutArray<3> glyphs; + int nGlyphs = 3; + if (!fe->stringToCMap(dotDotDot.constData(), 3, &glyphs, &nGlyphs, 0)) + // should never happen... + return layoutData->string; + for (int i = 0; i < nGlyphs; ++i) + ellipsisWidth += glyphs.advances_x[i]; + ellipsisText = dotDotDot; + } + } + + const QFixed availableWidth = width - ellipsisWidth; + if (availableWidth < 0) + return QString(); + + const HB_CharAttributes *attributes = this->attributes(); + if (!attributes) + return QString(); + + if (mode == Qt::ElideRight) { + QFixed currentWidth; + int pos; + int nextBreak = 0; + + do { + pos = nextBreak; + + ++nextBreak; + while (nextBreak < layoutData->string.length() && !attributes[nextBreak].charStop) + ++nextBreak; + + currentWidth += this->width(pos, nextBreak - pos); + } while (nextBreak < layoutData->string.length() + && currentWidth < availableWidth); + + if (nextCharJoins(layoutData->string, pos)) + ellipsisText.prepend(QChar(0x200d) /* ZWJ */); + + return layoutData->string.left(pos) + ellipsisText; + } else if (mode == Qt::ElideLeft) { + QFixed currentWidth; + int pos; + int nextBreak = layoutData->string.length(); + + do { + pos = nextBreak; + + --nextBreak; + while (nextBreak > 0 && !attributes[nextBreak].charStop) + --nextBreak; + + currentWidth += this->width(nextBreak, pos - nextBreak); + } while (nextBreak > 0 + && currentWidth < availableWidth); + + if (prevCharJoins(layoutData->string, pos)) + ellipsisText.append(QChar(0x200d) /* ZWJ */); + + return ellipsisText + layoutData->string.mid(pos); + } else if (mode == Qt::ElideMiddle) { + QFixed leftWidth; + QFixed rightWidth; + + int leftPos = 0; + int nextLeftBreak = 0; + + int rightPos = layoutData->string.length(); + int nextRightBreak = layoutData->string.length(); + + do { + leftPos = nextLeftBreak; + rightPos = nextRightBreak; + + ++nextLeftBreak; + while (nextLeftBreak < layoutData->string.length() && !attributes[nextLeftBreak].charStop) + ++nextLeftBreak; + + --nextRightBreak; + while (nextRightBreak > 0 && !attributes[nextRightBreak].charStop) + --nextRightBreak; + + leftWidth += this->width(leftPos, nextLeftBreak - leftPos); + rightWidth += this->width(nextRightBreak, rightPos - nextRightBreak); + } while (nextLeftBreak < layoutData->string.length() + && nextRightBreak > 0 + && leftWidth + rightWidth < availableWidth); + + if (nextCharJoins(layoutData->string, leftPos)) + ellipsisText.prepend(QChar(0x200d) /* ZWJ */); + if (prevCharJoins(layoutData->string, rightPos)) + ellipsisText.append(QChar(0x200d) /* ZWJ */); + + return layoutData->string.left(leftPos) + ellipsisText + layoutData->string.mid(rightPos); + } + + return layoutData->string; +} + +void QTextEngine::setBoundary(int strPos) const +{ + if (strPos <= 0 || strPos >= layoutData->string.length()) + return; + + int itemToSplit = 0; + while (itemToSplit < layoutData->items.size() && layoutData->items.at(itemToSplit).position <= strPos) + itemToSplit++; + itemToSplit--; + if (layoutData->items.at(itemToSplit).position == strPos) { + // already a split at the requested position + return; + } + splitItem(itemToSplit, strPos - layoutData->items.at(itemToSplit).position); +} + +void QTextEngine::splitItem(int item, int pos) const +{ + if (pos <= 0) + return; + + layoutData->items.insert(item + 1, layoutData->items[item]); + QScriptItem &oldItem = layoutData->items[item]; + QScriptItem &newItem = layoutData->items[item+1]; + newItem.position += pos; + + if (oldItem.num_glyphs) { + // already shaped, break glyphs aswell + int breakGlyph = logClusters(&oldItem)[pos]; + + newItem.num_glyphs = oldItem.num_glyphs - breakGlyph; + oldItem.num_glyphs = breakGlyph; + newItem.glyph_data_offset = oldItem.glyph_data_offset + breakGlyph; + + for (int i = 0; i < newItem.num_glyphs; i++) + logClusters(&newItem)[i] -= breakGlyph; + + QFixed w = 0; + const QGlyphLayout g = shapedGlyphs(&oldItem); + for(int j = 0; j < breakGlyph; ++j) + w += g.advances_x[j]; + + newItem.width = oldItem.width - w; + oldItem.width = w; + } + +// qDebug("split at position %d itempos=%d", pos, item); +} + +QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const +{ + const QScriptItem &si = layoutData->items[item]; + + QFixed dpiScale = 1; + if (block.docHandle() && block.docHandle()->layout()) { + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + dpiScale = QFixed::fromReal(pdev->logicalDpiY() / qreal(qt_defaultDpiY())); + } else { + dpiScale = QFixed::fromReal(fnt.d->dpi / qreal(qt_defaultDpiY())); + } + + QList tabArray = option.tabs(); + if (!tabArray.isEmpty()) { + if (isRightToLeft()) { // rebase the tabArray positions. + QList newTabs; + QList::Iterator iter = tabArray.begin(); + while(iter != tabArray.end()) { + QTextOption::Tab tab = *iter; + if (tab.type == QTextOption::LeftTab) + tab.type = QTextOption::RightTab; + else if (tab.type == QTextOption::RightTab) + tab.type = QTextOption::LeftTab; + newTabs << tab; + ++iter; + } + tabArray = newTabs; + } + for (int i = 0; i < tabArray.size(); ++i) { + QFixed tab = QFixed::fromReal(tabArray[i].position) * dpiScale; + if (tab > x) { // this is the tab we need. + QTextOption::Tab tabSpec = tabArray[i]; + int tabSectionEnd = layoutData->string.count(); + if (tabSpec.type == QTextOption::RightTab || tabSpec.type == QTextOption::CenterTab) { + // find next tab to calculate the width required. + tab = QFixed::fromReal(tabSpec.position); + for (int i=item + 1; i < layoutData->items.count(); i++) { + const QScriptItem &item = layoutData->items[i]; + if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it. + tabSectionEnd = item.position; + break; + } + } + } + else if (tabSpec.type == QTextOption::DelimiterTab) + // find delimitor character to calculate the width required + tabSectionEnd = qMax(si.position, layoutData->string.indexOf(tabSpec.delimiter, si.position) + 1); + + if (tabSectionEnd > si.position) { + QFixed length; + // Calculate the length of text between this tab and the tabSectionEnd + for (int i=item; i < layoutData->items.count(); i++) { + QScriptItem &item = layoutData->items[i]; + if (item.position > tabSectionEnd || item.position <= si.position) + continue; + shape(i); // first, lets make sure relevant text is already shaped + QGlyphLayout glyphs = this->shapedGlyphs(&item); + const int end = qMin(item.position + item.num_glyphs, tabSectionEnd) - item.position; + for (int i=0; i < end; i++) + length += glyphs.advances_x[i] * !glyphs.attributes[i].dontPrint; + if (end + item.position == tabSectionEnd && tabSpec.type == QTextOption::DelimiterTab) // remove half of matching char + length -= glyphs.advances_x[end] / 2 * !glyphs.attributes[end].dontPrint; + } + + switch (tabSpec.type) { + case QTextOption::CenterTab: + length /= 2; + // fall through + case QTextOption::DelimiterTab: + // fall through + case QTextOption::RightTab: + tab = QFixed::fromReal(tabSpec.position) * dpiScale - length; + if (tab < 0) // default to tab taking no space + return QFixed(); + break; + case QTextOption::LeftTab: + break; + } + } + return tab - x; + } + } + } + QFixed tab = QFixed::fromReal(option.tabStop()); + if (tab <= 0) + tab = 80; // default + tab *= dpiScale; + QFixed nextTabPos = ((x / tab).truncate() + 1) * tab; + QFixed tabWidth = nextTabPos - x; + + return tabWidth; +} + +void QTextEngine::resolveAdditionalFormats() const +{ + if (!specialData || specialData->addFormats.isEmpty() + || !block.docHandle() + || !specialData->resolvedFormatIndices.isEmpty()) + return; + + QTextFormatCollection *collection = this->formats(); + + specialData->resolvedFormatIndices.clear(); + QVector indices(layoutData->items.count()); + for (int i = 0; i < layoutData->items.count(); ++i) { + QTextCharFormat f = format(&layoutData->items.at(i)); + indices[i] = collection->indexForFormat(f); + } + specialData->resolvedFormatIndices = indices; +} + +QFixed QTextEngine::leadingSpaceWidth(const QScriptLine &line) +{ + if (!line.hasTrailingSpaces + || (option.flags() & QTextOption::IncludeTrailingSpaces) + || !isRightToLeft()) + return QFixed(); + + int pos = line.length; + const HB_CharAttributes *attributes = this->attributes(); + if (!attributes) + return QFixed(); + while (pos > 0 && attributes[line.from + pos - 1].whiteSpace) + --pos; + return width(line.from + pos, line.length - pos); +} + +QStackTextEngine::QStackTextEngine(const QString &string, const QFont &f) + : QTextEngine(string, f), + _layoutData(string, _memory, MemSize) +{ + stackEngine = true; + layoutData = &_layoutData; +} + +QTextItemInt::QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format) + : justified(false), underlineStyle(QTextCharFormat::NoUnderline), charFormat(format), + num_chars(0), chars(0), logClusters(0), f(0), fontEngine(0) +{ + // explicitly initialize flags so that initFontAttributes can be called + // multiple times on the same TextItem + flags = 0; + if (si.analysis.bidiLevel %2) + flags |= QTextItem::RightToLeft; + ascent = si.ascent; + descent = si.descent; + f = font; + fontEngine = f->d->engineForScript(si.analysis.script); + Q_ASSERT(fontEngine); + + if (format.hasProperty(QTextFormat::TextUnderlineStyle)) { + underlineStyle = format.underlineStyle(); + } else if (format.boolProperty(QTextFormat::FontUnderline) + || f->d->underline) { + underlineStyle = QTextCharFormat::SingleUnderline; + } + + // compat + if (underlineStyle == QTextCharFormat::SingleUnderline) + flags |= QTextItem::Underline; + + if (f->d->overline || format.fontOverline()) + flags |= QTextItem::Overline; + if (f->d->strikeOut || format.fontStrikeOut()) + flags |= QTextItem::StrikeOut; +} + +QTextItemInt::QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars_, int numChars, QFontEngine *fe) + : flags(0), justified(false), underlineStyle(QTextCharFormat::NoUnderline), + num_chars(numChars), chars(chars_), logClusters(0), f(font), glyphs(g), fontEngine(fe) +{ +} + +QTextItemInt QTextItemInt::midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const +{ + QTextItemInt ti = *this; + const int end = firstGlyphIndex + numGlyphs; + ti.glyphs = glyphs.mid(firstGlyphIndex, numGlyphs); + ti.fontEngine = fontEngine; + + if (logClusters && chars) { + const int logClusterOffset = logClusters[0]; + while (logClusters[ti.chars - chars] - logClusterOffset < firstGlyphIndex) + ++ti.chars; + + ti.logClusters += (ti.chars - chars); + + ti.num_chars = 0; + int char_start = ti.chars - chars; + while (char_start + ti.num_chars < num_chars && ti.logClusters[ti.num_chars] - logClusterOffset < end) + ++ti.num_chars; + } + return ti; +} + + +QTransform qt_true_matrix(qreal w, qreal h, QTransform x) +{ + QRectF rect = x.mapRect(QRectF(0, 0, w, h)); + return x * QTransform::fromTranslate(-rect.x(), -rect.y()); +} + + +glyph_metrics_t glyph_metrics_t::transformed(const QTransform &matrix) const +{ + if (matrix.type() < QTransform::TxTranslate) + return *this; + + glyph_metrics_t m = *this; + + qreal w = width.toReal(); + qreal h = height.toReal(); + QTransform xform = qt_true_matrix(w, h, matrix); + + QRectF rect(0, 0, w, h); + rect = xform.mapRect(rect); + m.width = QFixed::fromReal(rect.width()); + m.height = QFixed::fromReal(rect.height()); + + QLineF l = xform.map(QLineF(x.toReal(), y.toReal(), xoff.toReal(), yoff.toReal())); + + m.x = QFixed::fromReal(l.x1()); + m.y = QFixed::fromReal(l.y1()); + + // The offset is relative to the baseline which is why we use dx/dy of the line + m.xoff = QFixed::fromReal(l.dx()); + m.yoff = QFixed::fromReal(l.dy()); + + return m; +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextengine_mac.cpp b/src/gui/text/qtextengine_mac.cpp new file mode 100644 index 0000000000..97e8c5b0f8 --- /dev/null +++ b/src/gui/text/qtextengine_mac.cpp @@ -0,0 +1,656 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextengine_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +// set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs +// and no reordering. +// also computes logClusters heuristically +static void heuristicSetGlyphAttributes(const QChar *uc, int length, QGlyphLayout *glyphs, unsigned short *logClusters, int num_glyphs) +{ + // ### zeroWidth and justification are missing here!!!!! + + Q_UNUSED(num_glyphs); + +// qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs); + + const bool symbolFont = false; // #### + glyphs->attributes[0].mark = false; + glyphs->attributes[0].clusterStart = true; + glyphs->attributes[0].dontPrint = (!symbolFont && uc[0].unicode() == 0x00ad) || qIsControlChar(uc[0].unicode()); + + int pos = 0; + int lastCat = QChar::category(uc[0].unicode()); + for (int i = 1; i < length; ++i) { + if (logClusters[i] == pos) + // same glyph + continue; + ++pos; + while (pos < logClusters[i]) { + ++pos; + } + // hide soft-hyphens by default + if ((!symbolFont && uc[i].unicode() == 0x00ad) || qIsControlChar(uc[i].unicode())) + glyphs->attributes[pos].dontPrint = true; + const QUnicodeTables::Properties *prop = QUnicodeTables::properties(uc[i].unicode()); + int cat = prop->category; + + // one gets an inter character justification point if the current char is not a non spacing mark. + // as then the current char belongs to the last one and one gets a space justification point + // after the space char. + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos-1].justification = HB_Space; + else if (cat != QChar::Mark_NonSpacing) + glyphs->attributes[pos-1].justification = HB_Character; + else + glyphs->attributes[pos-1].justification = HB_NoJustification; + + lastCat = cat; + } + pos = logClusters[length-1]; + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos].justification = HB_Space; + else + glyphs->attributes[pos].justification = HB_Character; +} + +struct QArabicProperties { + unsigned char shape; + unsigned char justification; +}; +Q_DECLARE_TYPEINFO(QArabicProperties, Q_PRIMITIVE_TYPE); + +enum QArabicShape { + XIsolated, + XFinal, + XInitial, + XMedial, + // intermediate state + XCausing +}; + + +// these groups correspond to the groups defined in the Unicode standard. +// Some of these groups are equal with regards to both joining and line breaking behaviour, +// and thus have the same enum value +// +// I'm not sure the mapping of syriac to arabic enums is correct with regards to justification, but as +// I couldn't find any better document I'll hope for the best. +enum ArabicGroup { + // NonJoining + ArabicNone, + ArabicSpace, + // Transparent + Transparent, + // Causing + Center, + Kashida, + + // Arabic + // Dual + Beh, + Noon, + Meem = Noon, + Heh = Noon, + KnottedHeh = Noon, + HehGoal = Noon, + SwashKaf = Noon, + Yeh, + Hah, + Seen, + Sad = Seen, + Tah, + Kaf = Tah, + Gaf = Tah, + Lam = Tah, + Ain, + Feh = Ain, + Qaf = Ain, + // Right + Alef, + Waw, + Dal, + TehMarbuta = Dal, + Reh, + HamzaOnHehGoal, + YehWithTail = HamzaOnHehGoal, + YehBarre = HamzaOnHehGoal, + + // Syriac + // Dual + Beth = Beh, + Gamal = Ain, + Heth = Noon, + Teth = Hah, + Yudh = Noon, + Kaph = Noon, + Lamadh = Lam, + Mim = Noon, + Nun = Noon, + Semakh = Noon, + FinalSemakh = Noon, + SyriacE = Ain, + Pe = Ain, + ReversedPe = Hah, + Qaph = Noon, + Shin = Noon, + Fe = Ain, + + // Right + Alaph = Alef, + Dalath = Dal, + He = Dal, + SyriacWaw = Waw, + Zain = Alef, + YudhHe = Waw, + Sadhe = HamzaOnHehGoal, + Taw = Dal, + + // Compiler bug? Otherwise ArabicGroupsEnd would be equal to Dal + 1. + Dummy = HamzaOnHehGoal, + ArabicGroupsEnd +}; + +static const unsigned char arabic_group[0x150] = { + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + ArabicNone, ArabicNone, Alef, Alef, + Waw, Alef, Yeh, Alef, + Beh, TehMarbuta, Beh, Beh, + Hah, Hah, Hah, Dal, + + Dal, Reh, Reh, Seen, + Seen, Sad, Sad, Tah, + Tah, Ain, Ain, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + // 0x640 + Kashida, Feh, Qaf, Kaf, + Lam, Meem, Noon, Heh, + Waw, Yeh, Yeh, Transparent, + Transparent, Transparent, Transparent, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, Beh, Qaf, + + Transparent, Alef, Alef, Alef, + ArabicNone, Alef, Waw, Waw, + Yeh, Beh, Beh, Beh, + Beh, Beh, Beh, Beh, + + // 0x680 + Beh, Hah, Hah, Hah, + Hah, Hah, Hah, Hah, + Dal, Dal, Dal, Dal, + Dal, Dal, Dal, Dal, + + Dal, Reh, Reh, Reh, + Reh, Reh, Reh, Reh, + Reh, Reh, Seen, Seen, + Seen, Sad, Sad, Tah, + + Ain, Feh, Feh, Feh, + Feh, Feh, Feh, Qaf, + Qaf, Gaf, SwashKaf, Gaf, + Kaf, Kaf, Kaf, Gaf, + + Gaf, Gaf, Gaf, Gaf, + Gaf, Lam, Lam, Lam, + Lam, Noon, Noon, Noon, + Noon, Noon, KnottedHeh, Hah, + + // 0x6c0 + TehMarbuta, HehGoal, HamzaOnHehGoal, HamzaOnHehGoal, + Waw, Waw, Waw, Waw, + Waw, Waw, Waw, Waw, + Yeh, YehWithTail, Yeh, Waw, + + Yeh, Yeh, YehBarre, YehBarre, + ArabicNone, TehMarbuta, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, Transparent, + Transparent, ArabicNone, Transparent, Transparent, + Transparent, Transparent, Dal, Reh, + + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, Seen, Sad, + Ain, ArabicNone, ArabicNone, KnottedHeh, + + // 0x700 + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + Alaph, Transparent, Beth, Gamal, + Gamal, Dalath, Dalath, He, + SyriacWaw, Zain, Heth, Teth, + Teth, Yudh, YudhHe, Kaph, + + Lamadh, Mim, Nun, Semakh, + FinalSemakh, SyriacE, Pe, ReversedPe, + Sadhe, Qaph, Dalath, Shin, + Taw, Beth, Gamal, Dalath, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, ArabicNone, + ArabicNone, Zain, Kaph, Fe, +}; + +static inline ArabicGroup arabicGroup(unsigned short uc) +{ + if (uc >= 0x0600 && uc < 0x750) + return (ArabicGroup) arabic_group[uc-0x600]; + else if (uc == 0x200d) + return Center; + else if (QChar::category(uc) == QChar::Separator_Space) + return ArabicSpace; + else + return ArabicNone; +} + + +/* + Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on + arabic). + + Each unicode char has a joining class (right, dual (left&right), center (joincausing) or transparent). + transparent joining is not encoded in QChar::joining(), but applies to all combining marks and format marks. + + Right join-causing: dual + center + Left join-causing: dual + right + center + + Rules are as follows (for a string already in visual order, as we have it here): + + R1 Transparent characters do not affect joining behaviour. + R2 A right joining character, that has a right join-causing char on the right will get form XRight + (R3 A left joining character, that has a left join-causing char on the left will get form XLeft) + Note: the above rule is meaningless, as there are no pure left joining characters defined in Unicode + R4 A dual joining character, that has a left join-causing char on the left and a right join-causing char on + the right will get form XMedial + R5 A dual joining character, that has a right join causing char on the right, and no left join causing char on the left + will get form XRight + R6 A dual joining character, that has a left join causing char on the left, and no right join causing char on the right + will get form XLeft + R7 Otherwise the character will get form XIsolated + + Additionally we have to do the minimal ligature support for lam-alef ligatures: + + L1 Transparent characters do not affect ligature behaviour. + L2 Any sequence of Alef(XRight) + Lam(XMedial) will form the ligature Alef.Lam(XLeft) + L3 Any sequence of Alef(XRight) + Lam(XLeft) will form the ligature Alef.Lam(XIsolated) + + The state table below handles rules R1-R7. +*/ + +enum Joining { + JNone, + JCausing, + JDual, + JRight, + JTransparent +}; + +static const Joining joining_for_group[ArabicGroupsEnd] = { + // NonJoining + JNone, // ArabicNone + JNone, // ArabicSpace + // Transparent + JTransparent, // Transparent + // Causing + JCausing, // Center + JCausing, // Kashida + // Dual + JDual, // Beh + JDual, // Noon + JDual, // Yeh + JDual, // Hah + JDual, // Seen + JDual, // Tah + JDual, // Ain + // Right + JRight, // Alef + JRight, // Waw + JRight, // Dal + JRight, // Reh + JRight // HamzaOnHehGoal +}; + + +struct JoiningPair { + QArabicShape form1; + QArabicShape form2; +}; + +static const JoiningPair joining_table[5][4] = +// None, Causing, Dual, Right +{ + { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XInitial }, { XIsolated, XIsolated } }, // XIsolated + { { XFinal, XIsolated }, { XFinal, XCausing }, { XFinal, XInitial }, { XFinal, XIsolated } }, // XFinal + { { XIsolated, XIsolated }, { XInitial, XCausing }, { XInitial, XMedial }, { XInitial, XFinal } }, // XInitial + { { XFinal, XIsolated }, { XMedial, XCausing }, { XMedial, XMedial }, { XMedial, XFinal } }, // XMedial + { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XMedial }, { XIsolated, XFinal } }, // XCausing +}; + + +/* +According to http://www.microsoft.com/middleeast/Arabicdev/IE6/KBase.asp + +1. Find the priority of the connecting opportunities in each word +2. Add expansion at the highest priority connection opportunity +3. If more than one connection opportunity have the same highest value, + use the opportunity closest to the end of the word. + +Following is a chart that provides the priority for connection +opportunities and where expansion occurs. The character group names +are those in table 6.6 of the UNICODE 2.0 book. + + +PrioritY Glyph Condition Kashida Location + +Arabic_Kashida User inserted Kashida The user entered a Kashida in a position. After the user + (Shift+j or Shift+[E with hat]) Thus, it is the highest priority to insert an inserted kashida + automatic kashida. + +Arabic_Seen Seen, Sad Connecting to the next character. After the character. + (Initial or medial form). + +Arabic_HaaDal Teh Marbutah, Haa, Dal Connecting to previous character. Before the final form + of these characters. + +Arabic_Alef Alef, Tah, Lam, Connecting to previous character. Before the final form + Kaf and Gaf of these characters. + +Arabic_BaRa Reh, Yeh Connected to medial Beh Before preceding medial Baa + +Arabic_Waw Waw, Ain, Qaf, Feh Connecting to previous character. Before the final form of + these characters. + +Arabic_Normal Other connecting Connecting to previous character. Before the final form + characters of these characters. + + + +This seems to imply that we have at most one kashida point per arabic word. + +*/ + +void qt_getArabicProperties(const unsigned short *chars, int len, QArabicProperties *properties) +{ +// qDebug("arabicSyriacOpenTypeShape: properties:"); + int lastPos = 0; + int lastGroup = ArabicNone; + + ArabicGroup group = arabicGroup(chars[0]); + Joining j = joining_for_group[group]; + QArabicShape shape = joining_table[XIsolated][j].form2; + properties[0].justification = HB_NoJustification; + + for (int i = 1; i < len; ++i) { + // #### fix handling for spaces and punktuation + properties[i].justification = HB_NoJustification; + + group = arabicGroup(chars[i]); + j = joining_for_group[group]; + + if (j == JTransparent) { + properties[i].shape = XIsolated; + continue; + } + + properties[lastPos].shape = joining_table[shape][j].form1; + shape = joining_table[shape][j].form2; + + switch(lastGroup) { + case Seen: + if (properties[lastPos].shape == XInitial || properties[lastPos].shape == XMedial) + properties[i-1].justification = HB_Arabic_Seen; + break; + case Hah: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_HaaDal; + break; + case Alef: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Alef; + break; + case Ain: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Waw; + break; + case Noon: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Normal; + break; + case ArabicNone: + break; + + default: + Q_ASSERT(false); + } + + lastGroup = ArabicNone; + + switch(group) { + case ArabicNone: + case Transparent: + // ### Center should probably be treated as transparent when it comes to justification. + case Center: + break; + case ArabicSpace: + properties[i].justification = HB_Arabic_Space; + break; + case Kashida: + properties[i].justification = HB_Arabic_Kashida; + break; + case Seen: + lastGroup = Seen; + break; + + case Hah: + case Dal: + lastGroup = Hah; + break; + + case Alef: + case Tah: + lastGroup = Alef; + break; + + case Yeh: + case Reh: + if (properties[lastPos].shape == XMedial && arabicGroup(chars[lastPos]) == Beh) + properties[lastPos-1].justification = HB_Arabic_BaRa; + break; + + case Ain: + case Waw: + lastGroup = Ain; + break; + + case Noon: + case Beh: + case HamzaOnHehGoal: + lastGroup = Noon; + break; + case ArabicGroupsEnd: + Q_ASSERT(false); + } + + lastPos = i; + } + properties[lastPos].shape = joining_table[shape][JNone].form1; + + +// for (int i = 0; i < len; ++i) +// qDebug("arabic properties(%d): uc=%x shape=%d, justification=%d", i, chars[i], properties[i].shape, properties[i].justification); +} + +void QTextEngine::shapeTextMac(int item) const +{ + QScriptItem &si = layoutData->items[item]; + + si.glyph_data_offset = layoutData->used; + + QFontEngine *font = fontEngine(si, &si.ascent, &si.descent, &si.leading); + if (font->type() != QFontEngine::Multi) { + shapeTextWithHarfbuzz(item); + return; + } + +#ifndef QT_MAC_USE_COCOA + QFontEngineMacMulti *fe = static_cast(font); +#else + QCoreTextFontEngineMulti *fe = static_cast(font); +#endif + QTextEngine::ShaperFlags flags; + if (si.analysis.bidiLevel % 2) + flags |= RightToLeft; + if (option.useDesignMetrics()) + flags |= DesignMetrics; + + attributes(); // pre-initialize char attributes + + const int len = length(item); + int num_glyphs = length(item); + const QChar *str = layoutData->string.unicode() + si.position; + ushort upperCased[256]; + if (si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase + || si.analysis.flags == QScriptAnalysis::Lowercase) { + ushort *uc = upperCased; + if (len > 256) + uc = new ushort[len]; + for (int i = 0; i < len; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = str[i].toLower().unicode(); + else + uc[i] = str[i].toUpper().unicode(); + } + str = reinterpret_cast(uc); + } + + ensureSpace(num_glyphs); + num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used; + + QGlyphLayout g = availableGlyphs(&si); + g.numGlyphs = num_glyphs; + unsigned short *log_clusters = logClusters(&si); + + bool stringToCMapFailed = false; + if (!fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, attributes())) { + ensureSpace(num_glyphs); + g = availableGlyphs(&si); + stringToCMapFailed = !fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, + attributes()); + } + + if (!stringToCMapFailed) { + heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs); + + si.num_glyphs = num_glyphs; + + layoutData->used += si.num_glyphs; + + QGlyphLayout g = shapedGlyphs(&si); + + if (si.analysis.script == QUnicodeTables::Arabic) { + QVarLengthArray props(len + 2); + QArabicProperties *properties = props.data(); + int f = si.position; + int l = len; + if (f > 0) { + --f; + ++l; + ++properties; + } + if (f + l < layoutData->string.length()) { + ++l; + } + qt_getArabicProperties((const unsigned short *)(layoutData->string.unicode()+f), l, props.data()); + + unsigned short *log_clusters = logClusters(&si); + + for (int i = 0; i < len; ++i) { + int gpos = log_clusters[i]; + g.attributes[gpos].justification = properties[i].justification; + } + } + } + + const ushort *uc = reinterpret_cast(str); + + if ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase + || si.analysis.flags == QScriptAnalysis::Lowercase) + && uc != upperCased) + delete [] uc; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h new file mode 100644 index 0000000000..366c5c3207 --- /dev/null +++ b/src/gui/text/qtextengine_p.h @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTENGINE_P_H +#define QTEXTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qglobal.h" +#include "QtCore/qstring.h" +#include "QtCore/qvarlengtharray.h" +#include "QtCore/qnamespace.h" +#include "QtGui/qtextlayout.h" +#include "private/qtextformat_p.h" +#include "private/qfont_p.h" +#include "QtCore/qvector.h" +#include "QtGui/qpaintengine.h" +#include "QtGui/qtextobject.h" +#include "QtGui/qtextoption.h" +#include "QtCore/qset.h" +#include "QtCore/qdebug.h" +#ifndef QT_BUILD_COMPAT_LIB +#include "private/qtextdocument_p.h" +#endif +#include "private/qharfbuzz_p.h" +#include "private/qfixed_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QFontPrivate; +class QFontEngine; + +class QString; +class QPainter; + +class QAbstractTextDocumentLayout; + + +// this uses the same coordinate system as Qt, but a different one to freetype. +// * y is usually negative, and is equal to the ascent. +// * negative yoff means the following stuff is drawn higher up. +// the characters bounding rect is given by QRect(x,y,width,height), its advance by +// xoo and yoff +struct glyph_metrics_t +{ + inline glyph_metrics_t() + : x(100000), y(100000) {} + inline glyph_metrics_t(QFixed _x, QFixed _y, QFixed _width, QFixed _height, QFixed _xoff, QFixed _yoff) + : x(_x), + y(_y), + width(_width), + height(_height), + xoff(_xoff), + yoff(_yoff) + {} + QFixed x; + QFixed y; + QFixed width; + QFixed height; + QFixed xoff; + QFixed yoff; + + glyph_metrics_t transformed(const QTransform &xform) const; + inline bool isValid() const {return x != 100000 && y != 100000;} +}; +Q_DECLARE_TYPEINFO(glyph_metrics_t, Q_PRIMITIVE_TYPE); + +struct Q_AUTOTEST_EXPORT QScriptAnalysis +{ + enum Flags { + None = 0, + Lowercase = 1, + Uppercase = 2, + SmallCaps = 3, + LineOrParagraphSeparator = 4, + Space = 5, + SpaceTabOrObject = Space, + Tab = 6, + TabOrObject = Tab, + Object = 7 + }; + unsigned short script : 7; + unsigned short bidiLevel : 6; // Unicode Bidi algorithm embedding level (0-61) + unsigned short flags : 3; + inline bool operator == (const QScriptAnalysis &other) const { + return script == other.script && bidiLevel == other.bidiLevel && flags == other.flags; + } +}; +Q_DECLARE_TYPEINFO(QScriptAnalysis, Q_PRIMITIVE_TYPE); + +struct QGlyphJustification +{ + inline QGlyphJustification() + : type(0), nKashidas(0), space_18d6(0) + {} + + enum JustificationType { + JustifyNone, + JustifySpace, + JustifyKashida + }; + + uint type :2; + uint nKashidas : 6; // more do not make sense... + uint space_18d6 : 24; +}; +Q_DECLARE_TYPEINFO(QGlyphJustification, Q_PRIMITIVE_TYPE); + +struct QGlyphLayoutInstance +{ + QFixedPoint offset; + QFixedPoint advance; + HB_Glyph glyph; + QGlyphJustification justification; + HB_GlyphAttributes attributes; +}; + +struct QGlyphLayout +{ + // init to 0 not needed, done when shaping + QFixedPoint *offsets; // 8 bytes per element + HB_Glyph *glyphs; // 4 bytes per element + QFixed *advances_x; // 4 bytes per element + QFixed *advances_y; // 4 bytes per element + QGlyphJustification *justifications; // 4 bytes per element + HB_GlyphAttributes *attributes; // 2 bytes per element + + int numGlyphs; + + inline QGlyphLayout() : numGlyphs(0) {} + + inline explicit QGlyphLayout(char *address, int totalGlyphs) + { + offsets = reinterpret_cast(address); + int offset = totalGlyphs * sizeof(HB_FixedPoint); + glyphs = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(HB_Glyph); + advances_x = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QFixed); + advances_y = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QFixed); + justifications = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QGlyphJustification); + attributes = reinterpret_cast(address + offset); + numGlyphs = totalGlyphs; + } + + inline QGlyphLayout mid(int position, int n = -1) const { + QGlyphLayout copy = *this; + copy.glyphs += position; + copy.advances_x += position; + copy.advances_y += position; + copy.offsets += position; + copy.justifications += position; + copy.attributes += position; + if (n == -1) + copy.numGlyphs -= position; + else + copy.numGlyphs = n; + return copy; + } + + static inline int spaceNeededForGlyphLayout(int totalGlyphs) { + return totalGlyphs * (sizeof(HB_Glyph) + sizeof(HB_GlyphAttributes) + + sizeof(QFixed) + sizeof(QFixed) + sizeof(QFixedPoint) + + sizeof(QGlyphJustification)); + } + + inline QFixed effectiveAdvance(int item) const + { return (advances_x[item] + QFixed::fromFixed(justifications[item].space_18d6)) * !attributes[item].dontPrint; } + + inline QGlyphLayoutInstance instance(int position) const { + QGlyphLayoutInstance g; + g.offset.x = offsets[position].x; + g.offset.y = offsets[position].y; + g.glyph = glyphs[position]; + g.advance.x = advances_x[position]; + g.advance.y = advances_y[position]; + g.justification = justifications[position]; + g.attributes = attributes[position]; + return g; + } + + inline void setInstance(int position, const QGlyphLayoutInstance &g) { + offsets[position].x = g.offset.x; + offsets[position].y = g.offset.y; + glyphs[position] = g.glyph; + advances_x[position] = g.advance.x; + advances_y[position] = g.advance.y; + justifications[position] = g.justification; + attributes[position] = g.attributes; + } + + inline void clear(int first = 0, int last = -1) { + if (last == -1) + last = numGlyphs; + if (first == 0 && last == numGlyphs + && reinterpret_cast(offsets + numGlyphs) == reinterpret_cast(glyphs)) { + memset(offsets, 0, spaceNeededForGlyphLayout(numGlyphs)); + } else { + const int num = last - first; + memset(offsets + first, 0, num * sizeof(QFixedPoint)); + memset(glyphs + first, 0, num * sizeof(HB_Glyph)); + memset(advances_x + first, 0, num * sizeof(QFixed)); + memset(advances_y + first, 0, num * sizeof(QFixed)); + memset(justifications + first, 0, num * sizeof(QGlyphJustification)); + memset(attributes + first, 0, num * sizeof(HB_GlyphAttributes)); + } + } + + inline char *data() { + return reinterpret_cast(offsets); + } + + void grow(char *address, int totalGlyphs); +}; + +class QVarLengthGlyphLayoutArray : private QVarLengthArray, public QGlyphLayout +{ +private: + typedef QVarLengthArray Array; +public: + QVarLengthGlyphLayoutArray(int totalGlyphs) + : Array(spaceNeededForGlyphLayout(totalGlyphs) / sizeof(void *) + 1) + , QGlyphLayout(reinterpret_cast(Array::data()), totalGlyphs) + { + memset(Array::data(), 0, Array::size() * sizeof(void *)); + } + + void resize(int totalGlyphs) + { + Array::resize(spaceNeededForGlyphLayout(totalGlyphs) / sizeof(void *) + 1); + + *((QGlyphLayout *)this) = QGlyphLayout(reinterpret_cast(Array::data()), totalGlyphs); + memset(Array::data(), 0, Array::size() * sizeof(void *)); + } +}; + +template struct QGlyphLayoutArray : public QGlyphLayout +{ +public: + QGlyphLayoutArray() + : QGlyphLayout(reinterpret_cast(buffer), N) + { + memset(buffer, 0, sizeof(buffer)); + } + +private: + void *buffer[(N * (sizeof(HB_Glyph) + sizeof(HB_GlyphAttributes) + + sizeof(QFixed) + sizeof(QFixed) + sizeof(QFixedPoint) + + sizeof(QGlyphJustification))) + / sizeof(void *) + 1]; +}; + +struct QScriptItem; +/// Internal QTextItem +class QTextItemInt : public QTextItem +{ +public: + inline QTextItemInt() + : justified(false), underlineStyle(QTextCharFormat::NoUnderline), num_chars(0), chars(0), + logClusters(0), f(0), fontEngine(0) + {} + QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format = QTextCharFormat()); + QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars, int numChars, QFontEngine *fe); + + /// copy the structure items, adjusting the glyphs arrays to the right subarrays. + /// the width of the returned QTextItemInt is not adjusted, for speed reasons + QTextItemInt midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const; + + QFixed descent; + QFixed ascent; + QFixed width; + + RenderFlags flags; + bool justified; + QTextCharFormat::UnderlineStyle underlineStyle; + const QTextCharFormat charFormat; + int num_chars; + const QChar *chars; + const unsigned short *logClusters; + const QFont *f; + + QGlyphLayout glyphs; + QFontEngine *fontEngine; +}; + +inline bool qIsControlChar(ushort uc) +{ + return uc >= 0x200b && uc <= 0x206f + && (uc <= 0x200f /* ZW Space, ZWNJ, ZWJ, LRM and RLM */ + || (uc >= 0x2028 && uc <= 0x202f /* LS, PS, LRE, RLE, PDF, LRO, RLO, NNBSP */) + || uc >= 0x206a /* ISS, ASS, IAFS, AFS, NADS, NODS */); +} + +struct Q_AUTOTEST_EXPORT QScriptItem +{ + inline QScriptItem() + : position(0), + num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), + glyph_data_offset(0) {} + inline QScriptItem(int p, const QScriptAnalysis &a) + : position(p), analysis(a), + num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), + glyph_data_offset(0) {} + + int position; + QScriptAnalysis analysis; + unsigned short num_glyphs; + QFixed descent; + QFixed ascent; + QFixed leading; + QFixed width; + int glyph_data_offset; + QFixed height() const { return ascent + descent + 1; } +}; + + +Q_DECLARE_TYPEINFO(QScriptItem, Q_MOVABLE_TYPE); + +typedef QVector QScriptItemArray; + +struct Q_AUTOTEST_EXPORT QScriptLine +{ + // created and filled in QTextLine::layout_helper + QScriptLine() + : from(0), length(0), + justified(0), gridfitted(0), + hasTrailingSpaces(0), leadingIncluded(0) {} + QFixed descent; + QFixed ascent; + QFixed leading; + QFixed x; + QFixed y; + QFixed width; + QFixed textWidth; + QFixed textAdvance; + int from; + signed int length : 28; + mutable uint justified : 1; + mutable uint gridfitted : 1; + uint hasTrailingSpaces : 1; + uint leadingIncluded : 1; + QFixed height() const { return (ascent + descent).ceil() + 1 + + (leadingIncluded? qMax(QFixed(),leading) : QFixed()); } + QFixed base() const { return ascent + + (leadingIncluded ? qMax(QFixed(),leading) : QFixed()); } + void setDefaultHeight(QTextEngine *eng); + void operator+=(const QScriptLine &other); +}; +Q_DECLARE_TYPEINFO(QScriptLine, Q_PRIMITIVE_TYPE); + + +inline void QScriptLine::operator+=(const QScriptLine &other) +{ + leading= qMax(leading + ascent, other.leading + other.ascent) - qMax(ascent, other.ascent); + descent = qMax(descent, other.descent); + ascent = qMax(ascent, other.ascent); + textWidth += other.textWidth; + length += other.length; +} + +typedef QVector QScriptLineArray; + +class QFontPrivate; +class QTextFormatCollection; + +class Q_GUI_EXPORT QTextEngine { +public: + enum LayoutState { + LayoutEmpty, + InLayout, + LayoutFailed, + }; + struct LayoutData { + LayoutData(const QString &str, void **stack_memory, int mem_size); + LayoutData(); + ~LayoutData(); + mutable QScriptItemArray items; + int allocated; + int available_glyphs; + void **memory; + unsigned short *logClustersPtr; + QGlyphLayout glyphLayout; + mutable int used; + uint hasBidi : 1; + uint layoutState : 2; + uint memory_on_stack : 1; + uint haveCharAttributes : 1; + QString string; + bool reallocate(int totalGlyphs); + }; + + QTextEngine(LayoutData *data); + QTextEngine(); + QTextEngine(const QString &str, const QFont &f); + ~QTextEngine(); + + enum Mode { + WidthOnly = 0x07 + }; + + // keep in sync with QAbstractFontEngine::TextShapingFlag!! + enum ShaperFlag { + RightToLeft = 0x0001, + DesignMetrics = 0x0002, + GlyphIndicesOnly = 0x0004 + }; + Q_DECLARE_FLAGS(ShaperFlags, ShaperFlag) + + void invalidate(); + void clearLineData(); + + void validate() const; + void itemize() const; + + bool isRightToLeft() const; + static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder); + + const HB_CharAttributes *attributes() const; + + void shape(int item) const; + + void justify(const QScriptLine &si); + + QFixed width(int charFrom, int numChars) const; + glyph_metrics_t boundingBox(int from, int len) const; + glyph_metrics_t tightBoundingBox(int from, int len) const; + + int length(int item) const { + const QScriptItem &si = layoutData->items[item]; + int from = si.position; + item++; + return (item < layoutData->items.size() ? layoutData->items[item].position : layoutData->string.length()) - from; + } + int length(const QScriptItem *si) const { + int end; + if (si + 1 < layoutData->items.constData()+ layoutData->items.size()) + end = (si+1)->position; + else + end = layoutData->string.length(); + return end - si->position; + } + + QFontEngine *fontEngine(const QScriptItem &si, QFixed *ascent = 0, QFixed *descent = 0, QFixed *leading = 0) const; + QFont font(const QScriptItem &si) const; + inline QFont font() const { return fnt; } + + /** + * Returns a pointer to an array of log clusters, offset at the script item. + * Each item in the array is a unsigned short. For each character in the original string there is an entry in the table + * so there is a one to one correlation in indexes between the original text and the index in the logcluster. + * The value of each item is the position in the glyphs array. Multiple similar pointers in the logclusters array imply + * that one glyph is used for more than one character. + * \sa glyphs() + */ + inline unsigned short *logClusters(const QScriptItem *si) const + { return layoutData->logClustersPtr+si->position; } + /** + * Returns an array of QGlyphLayout items, offset at the script item. + * Each item in the array matches one glyph in the text, storing the advance, position etc. + * The returned item's length equals to the number of available glyphs. This may be more + * than what was actually shaped. + * \sa logClusters() + */ + inline QGlyphLayout availableGlyphs(const QScriptItem *si) const { + return layoutData->glyphLayout.mid(si->glyph_data_offset); + } + /** + * Returns an array of QGlyphLayout items, offset at the script item. + * Each item in the array matches one glyph in the text, storing the advance, position etc. + * The returned item's length equals to the number of shaped glyphs. + * \sa logClusters() + */ + inline QGlyphLayout shapedGlyphs(const QScriptItem *si) const { + return layoutData->glyphLayout.mid(si->glyph_data_offset, si->num_glyphs); + } + + inline bool ensureSpace(int nGlyphs) const { + if (layoutData->glyphLayout.numGlyphs - layoutData->used < nGlyphs) + return layoutData->reallocate((((layoutData->used + nGlyphs)*3/2 + 15) >> 4) << 4); + return true; + } + + void freeMemory(); + + int findItem(int strPos) const; + inline QTextFormatCollection *formats() const { +#ifdef QT_BUILD_COMPAT_LIB + return 0; // Compat should never reference this symbol +#else + return block.docHandle()->formatCollection(); +#endif + } + QTextCharFormat format(const QScriptItem *si) const; + inline QAbstractTextDocumentLayout *docLayout() const { +#ifdef QT_BUILD_COMPAT_LIB + return 0; // Compat should never reference this symbol +#else + return block.docHandle()->document()->documentLayout(); +#endif + } + int formatIndex(const QScriptItem *si) const; + + /// returns the width of tab at index (in the tabs array) with the tab-start at position x + QFixed calculateTabWidth(int index, QFixed x) const; + + mutable QScriptLineArray lines; + + struct FontEngineCache { + FontEngineCache(); + mutable QFontEngine *prevFontEngine; + mutable QFontEngine *prevScaledFontEngine; + mutable int prevScript; + mutable int prevPosition; + mutable int prevLength; + inline void reset() { + prevFontEngine = 0; + prevScaledFontEngine = 0; + prevScript = -1; + prevPosition = -1; + prevLength = -1; + } + }; + mutable FontEngineCache feCache; + + QString text; + QFont fnt; + QTextBlock block; + + QTextOption option; + + QFixed minWidth; + QFixed maxWidth; + QPointF position; + uint ignoreBidi : 1; + uint cacheGlyphs : 1; + uint stackEngine : 1; + uint forceJustification : 1; + + int *underlinePositions; + + mutable LayoutData *layoutData; + + inline bool hasFormats() const { return (block.docHandle() || specialData); } + + struct SpecialData { + int preeditPosition; + QString preeditText; + QList addFormats; + QVector addFormatIndices; + QVector resolvedFormatIndices; + }; + SpecialData *specialData; + + bool atWordSeparator(int position) const; + bool atSpace(int position) const; + void indexAdditionalFormats(); + + QString elidedText(Qt::TextElideMode mode, const QFixed &width, int flags = 0) const; + + void shapeLine(const QScriptLine &line); + QFixed leadingSpaceWidth(const QScriptLine &line); + +private: + void setBoundary(int strPos) const; + void addRequiredBoundaries() const; + void shapeText(int item) const; + void shapeTextWithHarfbuzz(int item) const; +#if defined(Q_WS_WINCE) + void shapeTextWithCE(int item) const; +#endif +#if defined(Q_WS_MAC) + void shapeTextMac(int item) const; +#endif + void splitItem(int item, int pos) const; + + void resolveAdditionalFormats() const; +}; + +class QStackTextEngine : public QTextEngine { +public: + enum { MemSize = 256*40/sizeof(void *) }; + QStackTextEngine(const QString &string, const QFont &f); + LayoutData _layoutData; + void *_memory[MemSize]; +}; + + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextEngine::ShaperFlags) + +QT_END_NAMESPACE + +#endif // QTEXTENGINE_P_H diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp new file mode 100644 index 0000000000..a02ea49f67 --- /dev/null +++ b/src/gui/text/qtextformat.cpp @@ -0,0 +1,3297 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextformat.h" +#include "qtextformat_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QTextLength + \reentrant + + \brief The QTextLength class encapsulates the different types of length + used in a QTextDocument. + + \ingroup richtext-processing + + When we specify a value for the length of an element in a text document, + we often need to provide some other information so that the length is + used in the way we expect. For example, when we specify a table width, + the value can represent a fixed number of pixels, or it can be a percentage + value. This information changes both the meaning of the value and the way + it is used. + + Generally, this class is used to specify table widths. These can be + specified either as a fixed amount of pixels, as a percentage of the + containing frame's width, or by a variable width that allows it to take + up just the space it requires. + + \sa QTextTable +*/ + +/*! + \fn explicit QTextLength::QTextLength() + + Constructs a new length object which represents a variable size. +*/ + +/*! + \fn QTextLength::QTextLength(Type type, qreal value) + + Constructs a new length object of the given \a type and \a value. +*/ + +/*! + \fn Type QTextLength::type() const + + Returns the type of this length object. + + \sa QTextLength::Type +*/ + +/*! + \fn qreal QTextLength::value(qreal maximumLength) const + + Returns the effective length, constrained by the type of the length object + and the specified \a maximumLength. + + \sa type() +*/ + +/*! + \fn qreal QTextLength::rawValue() const + + Returns the constraint value that is specific for the type of the length. + If the length is QTextLength::PercentageLength then the raw value is in + percent, in the range of 0 to 100. If the length is QTextLength::FixedLength + then that fixed amount is returned. For variable lengths, zero is returned. +*/ + +/*! + \fn bool QTextLength::operator==(const QTextLength &other) const + + Returns true if this text length is the same as the \a other text + length. +*/ + +/*! + \fn bool QTextLength::operator!=(const QTextLength &other) const + + Returns true if this text length is different from the \a other text + length. +*/ + +/*! + \enum QTextLength::Type + + This enum describes the different types a length object can + have. + + \value VariableLength The width of the object is variable + \value FixedLength The width of the object is fixed + \value PercentageLength The width of the object is in + percentage of the maximum width + + \sa type() +*/ + +/*! + Returns the text length as a QVariant +*/ +QTextLength::operator QVariant() const +{ + return QVariant(QVariant::TextLength, this); +} + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &stream, const QTextLength &length) +{ + return stream << qint32(length.lengthType) << double(length.fixedValueOrPercentage); +} + +QDataStream &operator>>(QDataStream &stream, QTextLength &length) +{ + qint32 type; + double fixedValueOrPercentage; + stream >> type >> fixedValueOrPercentage; + length.fixedValueOrPercentage = fixedValueOrPercentage; + length.lengthType = QTextLength::Type(type); + return stream; +} +#endif // QT_NO_DATASTREAM + +class QTextFormatPrivate : public QSharedData +{ +public: + QTextFormatPrivate() : hashDirty(true), fontDirty(true), hashValue(0) {} + + struct Property + { + inline Property(qint32 k, const QVariant &v) : key(k), value(v) {} + inline Property() {} + + qint32 key; + QVariant value; + + inline bool operator==(const Property &other) const + { return key == other.key && value == other.value; } + inline bool operator!=(const Property &other) const + { return key != other.key || value != other.value; } + }; + + inline uint hash() const + { + if (!hashDirty) + return hashValue; + return recalcHash(); + } + + inline bool operator==(const QTextFormatPrivate &rhs) const { + if (hash() != rhs.hash()) + return false; + + return props == rhs.props; + } + + inline void insertProperty(qint32 key, const QVariant &value) + { + hashDirty = true; + if (key >= QTextFormat::FirstFontProperty && key <= QTextFormat::LastFontProperty) + fontDirty = true; + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) { + props[i].value = value; + return; + } + props.append(Property(key, value)); + } + + inline void clearProperty(qint32 key) + { + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) { + hashDirty = true; + if (key >= QTextFormat::FirstFontProperty && key <= QTextFormat::LastFontProperty) + fontDirty = true; + props.remove(i); + return; + } + } + + inline int propertyIndex(qint32 key) const + { + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) + return i; + return -1; + } + + inline QVariant property(qint32 key) const + { + const int idx = propertyIndex(key); + if (idx < 0) + return QVariant(); + return props.at(idx).value; + } + + inline bool hasProperty(qint32 key) const + { return propertyIndex(key) != -1; } + + void resolveFont(const QFont &defaultFont); + + inline const QFont &font() const { + if (fontDirty) + recalcFont(); + return fnt; + } + + QVector props; +private: + + uint recalcHash() const; + void recalcFont() const; + + mutable bool hashDirty; + mutable bool fontDirty; + mutable uint hashValue; + mutable QFont fnt; + + friend QDataStream &operator<<(QDataStream &, const QTextFormat &); + friend QDataStream &operator>>(QDataStream &, QTextFormat &); +}; + +// this is only safe because sizeof(int) == sizeof(float) +static inline uint hash(float d) +{ +#ifdef Q_CC_GNU + // this is a GCC extension and isn't guaranteed to work in other compilers + // the reinterpret_cast below generates a strict-aliasing warning with GCC + union { float f; uint u; } cvt; + cvt.f = d; + return cvt.u; +#else + return reinterpret_cast(d); +#endif +} + +static inline uint hash(const QColor &color) +{ + return (color.isValid()) ? color.rgba() : 0x234109; +} + +static inline uint hash(const QPen &pen) +{ + return hash(pen.color()) + hash(pen.widthF()); +} + +static inline uint hash(const QBrush &brush) +{ + return hash(brush.color()) + (brush.style() << 3); +} + +static inline uint variantHash(const QVariant &variant) +{ + // simple and fast hash functions to differentiate between type and value + switch (variant.userType()) { // sorted by occurrence frequency + case QVariant::String: return qHash(variant.toString()); + case QVariant::Double: return hash(variant.toDouble()); + case QVariant::Int: return 0x811890 + variant.toInt(); + case QVariant::Brush: + return 0x01010101 + hash(qvariant_cast(variant)); + case QVariant::Bool: return 0x371818 + variant.toBool(); + case QVariant::Pen: return 0x02020202 + hash(qvariant_cast(variant)); + case QVariant::List: + return 0x8377 + qvariant_cast(variant).count(); + case QVariant::Color: return hash(qvariant_cast(variant)); + case QVariant::TextLength: + return 0x377 + hash(qvariant_cast(variant).rawValue()); + case QMetaType::Float: return hash(variant.toFloat()); + case QVariant::Invalid: return 0; + default: break; + } + return qHash(variant.typeName()); +} + +static inline int getHash(const QTextFormatPrivate *d, int format) +{ + return (d ? d->hash() : 0) + format; +} + +uint QTextFormatPrivate::recalcHash() const +{ + hashValue = 0; + for (QVector::ConstIterator it = props.constBegin(); it != props.constEnd(); ++it) + hashValue += (it->key << 16) + variantHash(it->value); + + hashDirty = false; + + return hashValue; +} + +void QTextFormatPrivate::resolveFont(const QFont &defaultFont) +{ + recalcFont(); + const uint oldMask = fnt.resolve(); + fnt = fnt.resolve(defaultFont); + + if (hasProperty(QTextFormat::FontSizeAdjustment)) { + const qreal scaleFactors[7] = {qreal(0.7), qreal(0.8), qreal(1.0), qreal(1.2), qreal(1.5), qreal(2), qreal(2.4)}; + + const int htmlFontSize = qBound(0, property(QTextFormat::FontSizeAdjustment).toInt() + 3 - 1, 6); + + + if (defaultFont.pointSize() <= 0) { + qreal pixelSize = scaleFactors[htmlFontSize] * defaultFont.pixelSize(); + fnt.setPixelSize(qRound(pixelSize)); + } else { + qreal pointSize = scaleFactors[htmlFontSize] * defaultFont.pointSizeF(); + fnt.setPointSizeF(pointSize); + } + } + + fnt.resolve(oldMask); +} + +void QTextFormatPrivate::recalcFont() const +{ + // update cached font as well + QFont f; + + for (int i = 0; i < props.count(); ++i) { + switch (props.at(i).key) { + case QTextFormat::FontFamily: + f.setFamily(props.at(i).value.toString()); + break; + case QTextFormat::FontPointSize: + f.setPointSizeF(props.at(i).value.toReal()); + break; + case QTextFormat::FontPixelSize: + f.setPixelSize(props.at(i).value.toInt()); + break; + case QTextFormat::FontWeight: { + int weight = props.at(i).value.toInt(); + if (weight == 0) weight = QFont::Normal; + f.setWeight(weight); + break; } + case QTextFormat::FontItalic: + f.setItalic(props.at(i).value.toBool()); + break; + case QTextFormat::FontUnderline: + if (! hasProperty(QTextFormat::TextUnderlineStyle)) // don't use the old one if the new one is there. + f.setUnderline(props.at(i).value.toBool()); + break; + case QTextFormat::TextUnderlineStyle: + f.setUnderline(static_cast(props.at(i).value.toInt()) == QTextCharFormat::SingleUnderline); + break; + case QTextFormat::FontOverline: + f.setOverline(props.at(i).value.toBool()); + break; + case QTextFormat::FontStrikeOut: + f.setStrikeOut(props.at(i).value.toBool()); + break; + case QTextFormat::FontLetterSpacing: + f.setLetterSpacing(QFont::PercentageSpacing, props.at(i).value.toReal()); + break; + case QTextFormat::FontWordSpacing: + f.setWordSpacing(props.at(i).value.toReal()); + break; + case QTextFormat::FontCapitalization: + f.setCapitalization(static_cast (props.at(i).value.toInt())); + break; + case QTextFormat::FontFixedPitch: { + const bool value = props.at(i).value.toBool(); + if (f.fixedPitch() != value) + f.setFixedPitch(value); + break; } + case QTextFormat::FontStyleHint: + f.setStyleHint(static_cast(props.at(i).value.toInt()), f.styleStrategy()); + break; + case QTextFormat::FontHintingPreference: + f.setHintingPreference(static_cast(props.at(i).value.toInt())); + break; + case QTextFormat::FontStyleStrategy: + f.setStyleStrategy(static_cast(props.at(i).value.toInt())); + break; + case QTextFormat::FontKerning: + f.setKerning(props.at(i).value.toBool()); + break; + default: + break; + } + } + fnt = f; + fontDirty = false; +} + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QTextFormat &fmt) +{ + stream << fmt.format_type << fmt.properties(); + return stream; +} + +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) +{ + QMap properties; + stream >> fmt.format_type >> properties; + + // QTextFormat's default constructor doesn't allocate the private structure, so + // we have to do this, in case fmt is a default constructed value. + if(!fmt.d) + fmt.d = new QTextFormatPrivate(); + + for (QMap::ConstIterator it = properties.constBegin(); + it != properties.constEnd(); ++it) + fmt.d->insertProperty(it.key(), it.value()); + + return stream; +} +#endif // QT_NO_DATASTREAM + +/*! + \class QTextFormat + \reentrant + + \brief The QTextFormat class provides formatting information for a + QTextDocument. + + \ingroup richtext-processing + \ingroup shared + + A QTextFormat is a generic class used for describing the format of + parts of a QTextDocument. The derived classes QTextCharFormat, + QTextBlockFormat, QTextListFormat, and QTextTableFormat are usually + more useful, and describe the formatting that is applied to + specific parts of the document. + + A format has a \c FormatType which specifies the kinds of text item it + can format; e.g. a block of text, a list, a table, etc. A format + also has various properties (some specific to particular format + types), as described by the Property enum. Every property has a + corresponding Property. + + The format type is given by type(), and the format can be tested + with isCharFormat(), isBlockFormat(), isListFormat(), + isTableFormat(), isFrameFormat(), and isImageFormat(). If the + type is determined, it can be retrieved with toCharFormat(), + toBlockFormat(), toListFormat(), toTableFormat(), toFrameFormat(), + and toImageFormat(). + + A format's properties can be set with the setProperty() functions, + and retrieved with boolProperty(), intProperty(), doubleProperty(), + and stringProperty() as appropriate. All the property IDs used in + the format can be retrieved with allPropertyIds(). One format can + be merged into another using merge(). + + A format's object index can be set with setObjectIndex(), and + retrieved with objectIndex(). These methods can be used to + associate the format with a QTextObject. It is used to represent + lists, frames, and tables inside the document. + + \sa {Rich Text Processing} +*/ + +/*! + \enum QTextFormat::FormatType + + This enum describes the text item a QTextFormat object is formatting. + + \value InvalidFormat An invalid format as created by the default + constructor + \value BlockFormat The object formats a text block + \value CharFormat The object formats a single character + \value ListFormat The object formats a list + \value TableFormat The object formats a table + \value FrameFormat The object formats a frame + + \value UserFormat + + \sa QTextCharFormat, QTextBlockFormat, QTextListFormat, + QTextTableFormat, type() +*/ + +/*! + \enum QTextFormat::Property + + This enum describes the different properties a format can have. + + \value ObjectIndex The index of the formatted object. See objectIndex(). + + Paragraph and character properties + + \value CssFloat How a frame is located relative to the surrounding text + \value LayoutDirection The layout direction of the text in the document + (Qt::LayoutDirection). + + \value OutlinePen + \value ForegroundBrush + \value BackgroundBrush + \value BackgroundImageUrl + + Paragraph properties + + \value BlockAlignment + \value BlockTopMargin + \value BlockBottomMargin + \value BlockLeftMargin + \value BlockRightMargin + \value TextIndent + \value TabPositions Specifies the tab positions. The tab positions are structs of QTextOption::Tab which are stored in + a QList (internally, in a QList). + \value BlockIndent + \value LineHeight + \value LineHeightType + \value BlockNonBreakableLines + \value BlockTrailingHorizontalRulerWidth The width of a horizontal ruler element. + + Character properties + + \value FontFamily + \value FontPointSize + \value FontPixelSize + \value FontSizeAdjustment Specifies the change in size given to the fontsize already set using + FontPointSize or FontPixelSize. + \value FontFixedPitch + \omitvalue FontSizeIncrement + \value FontWeight + \value FontItalic + \value FontUnderline \e{This property has been deprecated.} Use QTextFormat::TextUnderlineStyle instead. + \value FontOverline + \value FontStrikeOut + \value FontCapitalization Specifies the capitalization type that is to be applied to the text. + \value FontLetterSpacing Changes the default spacing between individual letters in the font. The value is + specified in percentage, with 100 as the default value. + \value FontWordSpacing Changes the default spacing between individual words. A positive value increases the word spacing + by the corresponding pixels; a negative value decreases the spacing. + \value FontStyleHint Corresponds to the QFont::StyleHint property + \value FontStyleStrategy Corresponds to the QFont::StyleStrategy property + \value FontKerning Specifies whether the font has kerning turned on. + + \omitvalue FirstFontProperty + \omitvalue LastFontProperty + + \value TextUnderlineColor + \value TextVerticalAlignment + \value TextOutline + \value TextUnderlineStyle + \value TextToolTip Specifies the (optional) tool tip to be displayed for a fragment of text. + + \value IsAnchor + \value AnchorHref + \value AnchorName + \value ObjectType + + List properties + + \value ListStyle + \value ListIndent + + Table and frame properties + + \value FrameBorder + \value FrameBorderBrush + \value FrameBorderStyle See the \l{QTextFrameFormat::BorderStyle}{BorderStyle} enum. + \value FrameBottomMargin + \value FrameHeight + \value FrameLeftMargin + \value FrameMargin + \value FramePadding + \value FrameRightMargin + \value FrameTopMargin + \value FrameWidth + \value TableCellSpacing + \value TableCellPadding + \value TableColumns + \value TableColumnWidthConstraints + \value TableHeaderRowCount + + Table cell properties + + \value TableCellRowSpan + \value TableCellColumnSpan + \value TableCellLeftPadding + \value TableCellRightPadding + \value TableCellTopPadding + \value TableCellBottomPadding + + Image properties + + \value ImageName + \value ImageWidth + \value ImageHeight + + Selection properties + + \value FullWidthSelection When set on the characterFormat of a selection, + the whole width of the text will be shown selected. + + Page break properties + + \value PageBreakPolicy Specifies how pages are broken. See the PageBreakFlag enum. + + \value UserProperty + + \sa property(), setProperty() +*/ + +/*! + \enum QTextFormat::ObjectTypes + + This enum describes what kind of QTextObject this format is associated with. + + \value NoObject + \value ImageObject + \value TableObject + \value TableCellObject + \value UserObject The first object that can be used for application-specific purposes. + + \sa QTextObject, QTextTable, QTextObject::format() +*/ + +/*! + \enum QTextFormat::PageBreakFlag + \since 4.2 + + This enum describes how page breaking is performed when printing. It maps to the + corresponding css properties. + + \value PageBreak_Auto The page break is determined automatically depending on the + available space on the current page + \value PageBreak_AlwaysBefore The page is always broken before the paragraph/table + \value PageBreak_AlwaysAfter A new page is always started after the paragraph/table + + \sa QTextBlockFormat::pageBreakPolicy(), QTextFrameFormat::pageBreakPolicy(), + PageBreakPolicy +*/ + +/*! + \fn bool QTextFormat::isValid() const + + Returns true if the format is valid (i.e. is not + InvalidFormat); otherwise returns false. +*/ + +/*! + \fn bool QTextFormat::isCharFormat() const + + Returns true if this text format is a \c CharFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isBlockFormat() const + + Returns true if this text format is a \c BlockFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isListFormat() const + + Returns true if this text format is a \c ListFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isTableFormat() const + + Returns true if this text format is a \c TableFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isFrameFormat() const + + Returns true if this text format is a \c FrameFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isImageFormat() const + + Returns true if this text format is an image format; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isTableCellFormat() const + \since 4.4 + + Returns true if this text format is a \c TableCellFormat; otherwise + returns false. +*/ + + +/*! + Creates a new text format with an \c InvalidFormat. + + \sa FormatType +*/ +QTextFormat::QTextFormat() + : format_type(InvalidFormat) +{ +} + +/*! + Creates a new text format of the given \a type. + + \sa FormatType +*/ +QTextFormat::QTextFormat(int type) + : format_type(type) +{ +} + + +/*! + \fn QTextFormat::QTextFormat(const QTextFormat &other) + + Creates a new text format with the same attributes as the \a other + text format. +*/ +QTextFormat::QTextFormat(const QTextFormat &rhs) + : d(rhs.d), format_type(rhs.format_type) +{ +} + +/*! + \fn QTextFormat &QTextFormat::operator=(const QTextFormat &other) + + Assigns the \a other text format to this text format, and returns a + reference to this text format. +*/ +QTextFormat &QTextFormat::operator=(const QTextFormat &rhs) +{ + d = rhs.d; + format_type = rhs.format_type; + return *this; +} + +/*! + Destroys this text format. +*/ +QTextFormat::~QTextFormat() +{ +} + + +/*! + Returns the text format as a QVariant +*/ +QTextFormat::operator QVariant() const +{ + return QVariant(QVariant::TextFormat, this); +} + +/*! + Merges the \a other format with this format; where there are + conflicts the \a other format takes precedence. +*/ +void QTextFormat::merge(const QTextFormat &other) +{ + if (format_type != other.format_type) + return; + + if (!d) { + d = other.d; + return; + } + + if (!other.d) + return; + + QTextFormatPrivate *d = this->d; + + const QVector &otherProps = other.d->props; + d->props.reserve(d->props.size() + otherProps.size()); + for (int i = 0; i < otherProps.count(); ++i) { + const QTextFormatPrivate::Property &p = otherProps.at(i); + d->insertProperty(p.key, p.value); + } +} + +/*! + Returns the type of this format. + + \sa FormatType +*/ +int QTextFormat::type() const +{ + return format_type; +} + +/*! + Returns this format as a block format. +*/ +QTextBlockFormat QTextFormat::toBlockFormat() const +{ + return QTextBlockFormat(*this); +} + +/*! + Returns this format as a character format. +*/ +QTextCharFormat QTextFormat::toCharFormat() const +{ + return QTextCharFormat(*this); +} + +/*! + Returns this format as a list format. +*/ +QTextListFormat QTextFormat::toListFormat() const +{ + return QTextListFormat(*this); +} + +/*! + Returns this format as a table format. +*/ +QTextTableFormat QTextFormat::toTableFormat() const +{ + return QTextTableFormat(*this); +} + +/*! + Returns this format as a frame format. +*/ +QTextFrameFormat QTextFormat::toFrameFormat() const +{ + return QTextFrameFormat(*this); +} + +/*! + Returns this format as an image format. +*/ +QTextImageFormat QTextFormat::toImageFormat() const +{ + return QTextImageFormat(*this); +} + +/*! + \since 4.4 + + Returns this format as a table cell format. +*/ +QTextTableCellFormat QTextFormat::toTableCellFormat() const +{ + return QTextTableCellFormat(*this); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property isn't of QTextFormat::Bool type, false is returned instead. + + \sa setProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +bool QTextFormat::boolProperty(int propertyId) const +{ + if (!d) + return false; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Bool) + return false; + return prop.toBool(); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property is not of QTextFormat::Integer type, 0 is returned instead. + + \sa setProperty() boolProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +int QTextFormat::intProperty(int propertyId) const +{ + // required, since the default layout direction has to be LayoutDirectionAuto, which is not integer 0 + int def = (propertyId == QTextFormat::LayoutDirection) ? int(Qt::LayoutDirectionAuto) : 0; + + if (!d) + return def; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Int) + return def; + return prop.toInt(); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property isn't of QVariant::Double or QMetaType::Float type, 0 is + returned instead. + + \sa setProperty() boolProperty() intProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +qreal QTextFormat::doubleProperty(int propertyId) const +{ + if (!d) + return 0.; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Double && prop.userType() != QMetaType::Float) + return 0.; + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::String type, an empty string is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +QString QTextFormat::stringProperty(int propertyId) const +{ + if (!d) + return QString(); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::String) + return QString(); + return prop.toString(); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Color type, an invalid color is + returned instead. + + \sa setProperty(), boolProperty(), intProperty(), doubleProperty(), + stringProperty(), lengthProperty(), lengthVectorProperty(), Property +*/ +QColor QTextFormat::colorProperty(int propertyId) const +{ + if (!d) + return QColor(); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Color) + return QColor(); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Pen type, Qt::NoPen is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() lengthProperty() lengthVectorProperty() Property +*/ +QPen QTextFormat::penProperty(int propertyId) const +{ + if (!d) + return QPen(Qt::NoPen); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Pen) + return QPen(Qt::NoPen); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Brush type, Qt::NoBrush is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() lengthProperty() lengthVectorProperty() Property +*/ +QBrush QTextFormat::brushProperty(int propertyId) const +{ + if (!d) + return QBrush(Qt::NoBrush); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Brush) + return QBrush(Qt::NoBrush); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthVectorProperty() Property +*/ +QTextLength QTextFormat::lengthProperty(int propertyId) const +{ + if (!d) + return QTextLength(); + return qvariant_cast(d->property(propertyId)); +} + +/*! + Returns the value of the property given by \a propertyId. If the + property isn't of QTextFormat::LengthVector type, an empty length + vector is returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() Property +*/ +QVector QTextFormat::lengthVectorProperty(int propertyId) const +{ + QVector vector; + if (!d) + return vector; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::List) + return vector; + + QList propertyList = prop.toList(); + for (int i=0; i(var)); + } + + return vector; +} + +/*! + Returns the property specified by the given \a propertyId. + + \sa Property +*/ +QVariant QTextFormat::property(int propertyId) const +{ + return d ? d->property(propertyId) : QVariant(); +} + +/*! + Sets the property specified by the \a propertyId to the given \a value. + + \sa Property +*/ +void QTextFormat::setProperty(int propertyId, const QVariant &value) +{ + if (!d) + d = new QTextFormatPrivate; + if (!value.isValid()) + clearProperty(propertyId); + else + d->insertProperty(propertyId, value); +} + +/*! + Sets the value of the property given by \a propertyId to \a value. + + \sa lengthVectorProperty() Property +*/ +void QTextFormat::setProperty(int propertyId, const QVector &value) +{ + if (!d) + d = new QTextFormatPrivate; + QVariantList list; + for (int i=0; iinsertProperty(propertyId, list); +} + +/*! + Clears the value of the property given by \a propertyId + + \sa Property +*/ +void QTextFormat::clearProperty(int propertyId) +{ + if (!d) + return; + d->clearProperty(propertyId); +} + + +/*! + \fn void QTextFormat::setObjectType(int type) + + Sets the text format's object type to \a type. + + \sa ObjectTypes, objectType() +*/ + + +/*! + \fn int QTextFormat::objectType() const + + Returns the text format's object type. + + \sa ObjectTypes, setObjectType() +*/ + + +/*! + Returns the index of the format object, or -1 if the format object is invalid. + + \sa setObjectIndex() +*/ +int QTextFormat::objectIndex() const +{ + if (!d) + return -1; + const QVariant prop = d->property(ObjectIndex); + if (prop.userType() != QVariant::Int) // #### + return -1; + return prop.toInt(); +} + +/*! + \fn void QTextFormat::setObjectIndex(int index) + + Sets the format object's object \a index. + + \sa objectIndex() +*/ +void QTextFormat::setObjectIndex(int o) +{ + if (o == -1) { + if (d) + d->clearProperty(ObjectIndex); + } else { + if (!d) + d = new QTextFormatPrivate; + // ### type + d->insertProperty(ObjectIndex, o); + } +} + +/*! + Returns true if the text format has a property with the given \a + propertyId; otherwise returns false. + + \sa properties() Property +*/ +bool QTextFormat::hasProperty(int propertyId) const +{ + return d ? d->hasProperty(propertyId) : false; +} + +/* + Returns the property type for the given \a propertyId. + + \sa hasProperty() allPropertyIds() Property +*/ + +/*! + Returns a map with all properties of this text format. +*/ +QMap QTextFormat::properties() const +{ + QMap map; + if (d) { + for (int i = 0; i < d->props.count(); ++i) + map.insert(d->props.at(i).key, d->props.at(i).value); + } + return map; +} + +/*! + \since 4.3 + Returns the number of properties stored in the format. +*/ +int QTextFormat::propertyCount() const +{ + return d ? d->props.count() : 0; +} + +/*! + \fn bool QTextFormat::operator!=(const QTextFormat &other) const + + Returns true if this text format is different from the \a other text + format. +*/ + + +/*! + \fn bool QTextFormat::operator==(const QTextFormat &other) const + + Returns true if this text format is the same as the \a other text + format. +*/ +bool QTextFormat::operator==(const QTextFormat &rhs) const +{ + if (format_type != rhs.format_type) + return false; + + if (d == rhs.d) + return true; + + if (d && d->props.isEmpty() && !rhs.d) + return true; + + if (!d && rhs.d && rhs.d->props.isEmpty()) + return true; + + if (!d || !rhs.d) + return false; + + return *d == *rhs.d; +} + +/*! + \class QTextCharFormat + \reentrant + + \brief The QTextCharFormat class provides formatting information for + characters in a QTextDocument. + + \ingroup richtext-processing + + The character format of text in a document specifies the visual properties + of the text, as well as information about its role in a hypertext document. + + The font used can be set by supplying a font to the setFont() function, and + each aspect of its appearance can be adjusted to give the desired effect. + setFontFamily() and setFontPointSize() define the font's family (e.g. Times) + and printed size; setFontWeight() and setFontItalic() provide control over + the style of the font. setFontUnderline(), setFontOverline(), + setFontStrikeOut(), and setFontFixedPitch() provide additional effects for + text. + + The color is set with setForeground(). If the text is intended to be used + as an anchor (for hyperlinks), this can be enabled with setAnchor(). The + setAnchorHref() and setAnchorNames() functions are used to specify the + information about the hyperlink's destination and the anchor's name. + + \sa QTextFormat QTextBlockFormat QTextTableFormat QTextListFormat +*/ + +/*! + \enum QTextCharFormat::VerticalAlignment + + This enum describes the ways that adjacent characters can be vertically + aligned. + + \value AlignNormal Adjacent characters are positioned in the standard + way for text in the writing system in use. + \value AlignSuperScript Characters are placed above the baseline for + normal text. + \value AlignSubScript Characters are placed below the baseline for + normal text. + \value AlignMiddle The center of the object is vertically aligned with the base line. + Currently, this is only implemented for inline objects. + \value AlignBottom The bottom edge of the object is vertically aligned with + the base line. + \value AlignTop The top edge of the object is vertically aligned with + the base line. +*/ + +/*! + \enum QTextCharFormat::UnderlineStyle + + This enum describes the different ways drawing underlined text. + + \value NoUnderline Text is draw without any underlining decoration. + \value SingleUnderline A line is drawn using Qt::SolidLine. + \value DashUnderline Dashes are drawn using Qt::DashLine. + \value DotLine Dots are drawn using Qt::DotLine; + \value DashDotLine Dashs and dots are drawn using Qt::DashDotLine. + \value DashDotDotLine Underlines draw drawn using Qt::DashDotDotLine. + \value WaveUnderline The text is underlined using a wave shaped line. + \value SpellCheckUnderline The underline is drawn depending on the QStyle::SH_SpellCeckUnderlineStyle + style hint of the QApplication style. By default this is mapped to + WaveUnderline, on Mac OS X it is mapped to DashDotLine. + + \sa Qt::PenStyle +*/ + +/*! + \fn QTextCharFormat::QTextCharFormat() + + Constructs a new character format object. +*/ +QTextCharFormat::QTextCharFormat() : QTextFormat(CharFormat) {} + +/*! + \internal + \fn QTextCharFormat::QTextCharFormat(const QTextFormat &other) + + Creates a new character format with the same attributes as the \a given + text format. +*/ +QTextCharFormat::QTextCharFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn bool QTextCharFormat::isValid() const + + Returns true if this character format is valid; otherwise returns + false. +*/ + + +/*! + \fn void QTextCharFormat::setFontFamily(const QString &family) + + Sets the text format's font \a family. + + \sa setFont() +*/ + + +/*! + \fn QString QTextCharFormat::fontFamily() const + + Returns the text format's font family. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontPointSize(qreal size) + + Sets the text format's font \a size. + + \sa setFont() +*/ + + +/*! + \fn qreal QTextCharFormat::fontPointSize() const + + Returns the font size used to display text in this format. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontWeight(int weight) + + Sets the text format's font weight to \a weight. + + \sa setFont(), QFont::Weight +*/ + + +/*! + \fn int QTextCharFormat::fontWeight() const + + Returns the text format's font weight. + + \sa font(), QFont::Weight +*/ + + +/*! + \fn void QTextCharFormat::setFontItalic(bool italic) + + If \a italic is true, sets the text format's font to be italic; otherwise + the font will be non-italic. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontItalic() const + + Returns true if the text format's font is italic; otherwise + returns false. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontUnderline(bool underline) + + If \a underline is true, sets the text format's font to be underlined; + otherwise it is displayed non-underlined. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontUnderline() const + + Returns true if the text format's font is underlined; otherwise + returns false. + + \sa font() +*/ +bool QTextCharFormat::fontUnderline() const +{ + if (hasProperty(TextUnderlineStyle)) + return underlineStyle() == SingleUnderline; + return boolProperty(FontUnderline); +} + +/*! + \fn UnderlineStyle QTextCharFormat::underlineStyle() const + \since 4.2 + + Returns the style of underlining the text. +*/ + +/*! + \fn void QTextCharFormat::setUnderlineStyle(UnderlineStyle style) + \since 4.2 + + Sets the style of underlining the text to \a style. +*/ +void QTextCharFormat::setUnderlineStyle(UnderlineStyle style) +{ + setProperty(TextUnderlineStyle, style); + // for compatibility + setProperty(FontUnderline, style == SingleUnderline); +} + +/*! + \fn void QTextCharFormat::setFontOverline(bool overline) + + If \a overline is true, sets the text format's font to be overlined; + otherwise the font is displayed non-overlined. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontOverline() const + + Returns true if the text format's font is overlined; otherwise + returns false. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontStrikeOut(bool strikeOut) + + If \a strikeOut is true, sets the text format's font with strike-out + enabled (with a horizontal line through it); otherwise it is displayed + without strikeout. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontStrikeOut() const + + Returns true if the text format's font is struck out (has a horizontal line + drawn through it); otherwise returns false. + + \sa font() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontStyleHint(QFont::StyleHint hint, QFont::StyleStrategy strategy) + + Sets the font style \a hint and \a strategy. + + Qt does not support style hints on X11 since this information is not provided by the window system. + + \sa setFont() + \sa QFont::setStyleHint() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontStyleStrategy(QFont::StyleStrategy strategy) + + Sets the font style \a strategy. + + \sa setFont() + \sa QFont::setStyleStrategy() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontKerning(bool enable) + Enables kerning for this font if \a enable is true; otherwise disables it. + + When kerning is enabled, glyph metrics do not add up anymore, even for + Latin text. In other words, the assumption that width('a') + width('b') + is equal to width("ab") is not neccesairly true. + + \sa setFont() +*/ + + +/*! + \fn QTextCharFormat::StyleHint QTextCharFormat::fontStyleHint() const + \since 4.5 + + Returns the font style hint. + + \sa setFontStyleHint(), font() +*/ + + +/*! + \since 4.5 + \fn QTextCharFormat::StyleStrategy QTextCharFormat::fontStyleStrategy() const + + Returns the current font style strategy. + + \sa setFontStyleStrategy() + \sa font() +*/ + + +/*! + \since 4.5 + \fn bool QTextCharFormat::fontKerning() const + Returns true if the font kerning is enabled. + + \sa setFontKerning() + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontFixedPitch(bool fixedPitch) + + If \a fixedPitch is true, sets the text format's font to be fixed pitch; + otherwise a non-fixed pitch font is used. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontFixedPitch() const + + Returns true if the text format's font is fixed pitch; otherwise + returns false. + + \sa font() +*/ + +/*! + \since 4.8 + + \fn void QTextCharFormat::setFontHintingPreference(QFont::HintingPreference hintingPreference) + + Sets the hinting preference of the text format's font to be \a hintingPreference. + + \sa setFont(), QFont::setHintingPreference() +*/ + +/*! + \since 4.8 + + \fn QFont::HintingPreference QTextCharFormat::fontHintingPreference() const + + Returns the hinting preference set for this text format. + + \sa font(), QFont::hintingPreference() +*/ + +/*! + \fn QPen QTextCharFormat::textOutline() const + + Returns the pen used to draw the outlines of characters in this format. +*/ + + +/*! + \fn void QTextCharFormat::setTextOutline(const QPen &pen) + + Sets the pen used to draw the outlines of characters to the given \a pen. +*/ + +/*! + \fn void QTextCharFormat::setToolTip(const QString &text) + \since 4.3 + + Sets the tool tip for a fragment of text to the given \a text. +*/ + +/*! + \fn QString QTextCharFormat::toolTip() const + \since 4.3 + + Returns the tool tip that is displayed for a fragment of text. +*/ + +/*! + \fn void QTextFormat::setForeground(const QBrush &brush) + + Sets the foreground brush to the specified \a brush. The foreground + brush is mostly used to render text. + + \sa foreground() clearForeground() setBackground() +*/ + + +/*! + \fn QBrush QTextFormat::foreground() const + + Returns the brush used to render foreground details, such as text, + frame outlines, and table borders. + + \sa setForeground() clearForeground() background() +*/ + +/*! + \fn void QTextFormat::clearForeground() + + Clears the brush used to paint the document's foreground. The default + brush will be used. + + \sa foreground() setForeground() clearBackground() +*/ + + +/*! + \fn void QTextCharFormat::setAnchor(bool anchor) + + If \a anchor is true, text with this format represents an anchor, and is + formatted in the appropriate way; otherwise the text is formatted normally. + (Anchors are hyperlinks which are often shown underlined and in a different + color from plain text.) + + The way the text is rendered is independent of whether or not the format + has a valid anchor defined. Use setAnchorHref(), and optionally + setAnchorNames() to create a hypertext link. + + \sa isAnchor() +*/ + + +/*! + \fn bool QTextCharFormat::isAnchor() const + + Returns true if the text is formatted as an anchor; otherwise + returns false. + + \sa setAnchor() setAnchorHref() setAnchorNames() +*/ + + +/*! + \fn void QTextCharFormat::setAnchorHref(const QString &value) + + Sets the hypertext link for the text format to the given \a value. + This is typically a URL like "http://example.com/index.html". + + The anchor will be displayed with the \a value as its display text; + if you want to display different text call setAnchorNames(). + + To format the text as a hypertext link use setAnchor(). +*/ + + +/*! + \fn QString QTextCharFormat::anchorHref() const + + Returns the text format's hypertext link, or an empty string if + none has been set. +*/ + + +/*! + \fn void QTextCharFormat::setAnchorName(const QString &name) + \obsolete + + This function is deprecated. Use setAnchorNames() instead. + + Sets the text format's anchor \a name. For the anchor to work as a + hyperlink, the destination must be set with setAnchorHref() and + the anchor must be enabled with setAnchor(). +*/ + +/*! + \fn void QTextCharFormat::setAnchorNames(const QStringList &names) + \since 4.3 + + Sets the text format's anchor \a names. For the anchor to work as a + hyperlink, the destination must be set with setAnchorHref() and + the anchor must be enabled with setAnchor(). +*/ + +/*! + \fn QString QTextCharFormat::anchorName() const + \obsolete + + This function is deprecated. Use anchorNames() instead. + + Returns the anchor name associated with this text format, or an empty + string if none has been set. If the anchor name is set, text with this + format can be the destination of a hypertext link. +*/ +QString QTextCharFormat::anchorName() const +{ + QVariant prop = property(AnchorName); + if (prop.userType() == QVariant::StringList) + return prop.toStringList().value(0); + else if (prop.userType() != QVariant::String) + return QString(); + return prop.toString(); +} + +/*! + \fn QStringList QTextCharFormat::anchorNames() const + \since 4.3 + + Returns the anchor names associated with this text format, or an empty + string list if none has been set. If the anchor names are set, text with this + format can be the destination of a hypertext link. +*/ +QStringList QTextCharFormat::anchorNames() const +{ + QVariant prop = property(AnchorName); + if (prop.userType() == QVariant::StringList) + return prop.toStringList(); + else if (prop.userType() != QVariant::String) + return QStringList(); + return QStringList(prop.toString()); +} + + +/*! + \fn void QTextCharFormat::setTableCellRowSpan(int tableCellRowSpan) + \internal + + If this character format is applied to characters in a table cell, + the cell will span \a tableCellRowSpan rows. +*/ + + +/*! + \fn int QTextCharFormat::tableCellRowSpan() const + \internal + + If this character format is applied to characters in a table cell, + this function returns the number of rows spanned by the text (this may + be 1); otherwise it returns 1. +*/ + +/*! + \fn void QTextCharFormat::setTableCellColumnSpan(int tableCellColumnSpan) + \internal + + If this character format is applied to characters in a table cell, + the cell will span \a tableCellColumnSpan columns. +*/ + + +/*! + \fn int QTextCharFormat::tableCellColumnSpan() const + \internal + + If this character format is applied to characters in a table cell, + this function returns the number of columns spanned by the text (this + may be 1); otherwise it returns 1. +*/ + +/*! + \fn void QTextCharFormat::setUnderlineColor(const QColor &color) + + Sets the underline color used for the characters with this format to + the \a color specified. + + \sa underlineColor() +*/ + +/*! + \fn QColor QTextCharFormat::underlineColor() const + + Returns the color used to underline the characters with this format. + + \sa setUnderlineColor() +*/ + +/*! + \fn void QTextCharFormat::setVerticalAlignment(VerticalAlignment alignment) + + Sets the vertical alignment used for the characters with this format to + the \a alignment specified. + + \sa verticalAlignment() +*/ + +/*! + \fn VerticalAlignment QTextCharFormat::verticalAlignment() const + + Returns the vertical alignment used for characters with this format. + + \sa setVerticalAlignment() +*/ + +/*! + Sets the text format's \a font. +*/ +void QTextCharFormat::setFont(const QFont &font) +{ + setFontFamily(font.family()); + + const qreal pointSize = font.pointSizeF(); + if (pointSize > 0) { + setFontPointSize(pointSize); + } else { + const int pixelSize = font.pixelSize(); + if (pixelSize > 0) + setProperty(QTextFormat::FontPixelSize, pixelSize); + } + + setFontWeight(font.weight()); + setFontItalic(font.italic()); + setUnderlineStyle(font.underline() ? SingleUnderline : NoUnderline); + setFontOverline(font.overline()); + setFontStrikeOut(font.strikeOut()); + setFontFixedPitch(font.fixedPitch()); + setFontCapitalization(font.capitalization()); + setFontWordSpacing(font.wordSpacing()); + if (font.letterSpacingType() == QFont::PercentageSpacing) + setFontLetterSpacing(font.letterSpacing()); + setFontStyleHint(font.styleHint()); + setFontStyleStrategy(font.styleStrategy()); + setFontKerning(font.kerning()); +} + +/*! + Returns the font for this character format. +*/ +QFont QTextCharFormat::font() const +{ + return d ? d->font() : QFont(); +} + +/*! + \class QTextBlockFormat + \reentrant + + \brief The QTextBlockFormat class provides formatting information for + blocks of text in a QTextDocument. + + \ingroup richtext-processing + + A document is composed of a list of blocks, represented by QTextBlock + objects. Each block can contain an item of some kind, such as a + paragraph of text, a table, a list, or an image. Every block has an + associated QTextBlockFormat that specifies its characteristics. + + To cater for left-to-right and right-to-left languages you can set + a block's direction with setDirection(). Paragraph alignment is + set with setAlignment(). Margins are controlled by setTopMargin(), + setBottomMargin(), setLeftMargin(), setRightMargin(). Overall + indentation is set with setIndent(), the indentation of the first + line with setTextIndent(). + + Line spacing is set with setLineHeight() and retrieved via lineHeight() + and lineHeightType(). The types of line spacing available are in the + LineHeightTypes enum. + + Line breaking can be enabled and disabled with setNonBreakableLines(). + + The brush used to paint the paragraph's background + is set with \l{QTextFormat::setBackground()}{setBackground()}, and other + aspects of the text's appearance can be customized by using the + \l{QTextFormat::setProperty()}{setProperty()} function with the + \c OutlinePen, \c ForegroundBrush, and \c BackgroundBrush + \l{QTextFormat::Property} values. + + If a text block is part of a list, it can also have a list format that + is accessible with the listFormat() function. + + \sa QTextBlock, QTextCharFormat +*/ + +/*! + \since 4.8 + \enum QTextBlockFormat::LineHeightTypes + + This enum describes the various types of line spacing support paragraphs can have. + + \value SingleHeight This is the default line height: single spacing. + \value ProportionalHeight This sets the spacing proportional to the line (in percentage). + For example, set to 200 for double spacing. + \value FixedHeight This sets the line height to a fixed line height (in pixels). + \value MinimumHeight This sets the minimum line height (in pixels). + \value LineDistanceHeight This adds the specified height between lines (in pixels). + + \sa lineHeight(), lineHeightType(), setLineHeight() +*/ + +/*! + \fn QTextBlockFormat::QTextBlockFormat() + + Constructs a new QTextBlockFormat. +*/ +QTextBlockFormat::QTextBlockFormat() : QTextFormat(BlockFormat) {} + +/*! + \internal + \fn QTextBlockFormat::QTextBlockFormat(const QTextFormat &other) + + Creates a new block format with the same attributes as the \a given + text format. +*/ +QTextBlockFormat::QTextBlockFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \since 4.4 + Sets the tab positions for the text block to those specified by + \a tabs. + + \sa tabPositions() +*/ +void QTextBlockFormat::setTabPositions(const QList &tabs) +{ + QList list; + QList::ConstIterator iter = tabs.constBegin(); + while (iter != tabs.constEnd()) { + QVariant v; + v.setValue(*iter); + list.append(v); + ++iter; + } + setProperty(TabPositions, list); +} + +/*! + \since 4.4 + Returns a list of tab positions defined for the text block. + + \sa setTabPositions() +*/ +QList QTextBlockFormat::tabPositions() const +{ + QVariant variant = property(TabPositions); + if(variant.isNull()) + return QList(); + QList answer; + QList variantsList = qvariant_cast >(variant); + QList::Iterator iter = variantsList.begin(); + while(iter != variantsList.end()) { + answer.append( qvariant_cast(*iter)); + ++iter; + } + return answer; +} + +/*! + \fn QTextBlockFormat::isValid() const + + Returns true if this block format is valid; otherwise returns + false. +*/ + +/*! + \fn void QTextFormat::setLayoutDirection(Qt::LayoutDirection direction) + + Sets the document's layout direction to the specified \a direction. + + \sa layoutDirection() +*/ + + +/*! + \fn Qt::LayoutDirection QTextFormat::layoutDirection() const + + Returns the document's layout direction. + + \sa setLayoutDirection() +*/ + + +/*! + \fn void QTextBlockFormat::setAlignment(Qt::Alignment alignment) + + Sets the paragraph's \a alignment. + + \sa alignment() +*/ + + +/*! + \fn Qt::Alignment QTextBlockFormat::alignment() const + + Returns the paragraph's alignment. + + \sa setAlignment() +*/ + + +/*! + \fn void QTextBlockFormat::setTopMargin(qreal margin) + + Sets the paragraph's top \a margin. + + \sa topMargin() setBottomMargin() setLeftMargin() setRightMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::topMargin() const + + Returns the paragraph's top margin. + + \sa setTopMargin() bottomMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setBottomMargin(qreal margin) + + Sets the paragraph's bottom \a margin. + + \sa bottomMargin() setTopMargin() setLeftMargin() setRightMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::bottomMargin() const + + Returns the paragraph's bottom margin. + + \sa setBottomMargin() topMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setLeftMargin(qreal margin) + + Sets the paragraph's left \a margin. Indentation can be applied separately + with setIndent(). + + \sa leftMargin() setRightMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::leftMargin() const + + Returns the paragraph's left margin. + + \sa setLeftMargin() rightMargin() indent() +*/ + + +/*! + \fn void QTextBlockFormat::setRightMargin(qreal margin) + + Sets the paragraph's right \a margin. + + \sa rightMargin() setLeftMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::rightMargin() const + + Returns the paragraph's right margin. + + \sa setRightMargin() leftMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setTextIndent(qreal indent) + + Sets the \a indent for the first line in the block. This allows the first + line of a paragraph to be indented differently to the other lines, + enhancing the readability of the text. + + \sa textIndent() setLeftMargin() setRightMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::textIndent() const + + Returns the paragraph's text indent. + + \sa setTextIndent() +*/ + + +/*! + \fn void QTextBlockFormat::setIndent(int indentation) + + Sets the paragraph's \a indentation. Margins are set independently of + indentation with setLeftMargin() and setTextIndent(). + The \a indentation is an integer that is multiplied with the document-wide + standard indent, resulting in the actual indent of the paragraph. + + \sa indent() QTextDocument::indentWidth() +*/ + + +/*! + \fn int QTextBlockFormat::indent() const + + Returns the paragraph's indent. + + \sa setIndent() +*/ + + +/*! + \fn void QTextBlockFormat::setLineHeight(qreal height, int heightType) + \since 4.8 + + This sets the line height for the paragraph to the value in height + which is dependant on heightType, described by the LineHeightTypes enum. + + \sa LineHeightTypes, lineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeight(qreal scriptLineHeight, qreal scaling) const + \since 4.8 + + This returns what the height of the lines in the paragraph will be depending + on the given height of the script line and the scaling. The value that is returned + is also dependant on the given LineHeightType of the paragraph as well as the LineHeight + setting that has been set for the paragraph. The scaling is needed for the heights + that include a fixed number of pixels, to scale them appropriately for printing. + + \sa LineHeightTypes, setLineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeight() const + \since 4.8 + + This returns the LineHeight property for the paragraph. + + \sa LineHeightTypes, setLineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeightType() const + \since 4.8 + + This returns the LineHeightType property of the paragraph. + + \sa LineHeightTypes, setLineHeight(), lineHeight() +*/ + + +/*! + \fn void QTextBlockFormat::setNonBreakableLines(bool b) + + If \a b is true, the lines in the paragraph are treated as + non-breakable; otherwise they are breakable. + + \sa nonBreakableLines() +*/ + + +/*! + \fn bool QTextBlockFormat::nonBreakableLines() const + + Returns true if the lines in the paragraph are non-breakable; + otherwise returns false. + + \sa setNonBreakableLines() +*/ + +/*! + \fn QTextFormat::PageBreakFlags QTextBlockFormat::pageBreakPolicy() const + \since 4.2 + + Returns the currently set page break policy for the paragraph. The default is + QTextFormat::PageBreak_Auto. + + \sa setPageBreakPolicy() +*/ + +/*! + \fn void QTextBlockFormat::setPageBreakPolicy(PageBreakFlags policy) + \since 4.2 + + Sets the page break policy for the paragraph to \a policy. + + \sa pageBreakPolicy() +*/ + +/*! + \class QTextListFormat + \reentrant + + \brief The QTextListFormat class provides formatting information for + lists in a QTextDocument. + + \ingroup richtext-processing + + A list is composed of one or more items, represented as text blocks. + The list's format specifies the appearance of items in the list. + In particular, it determines the indentation and the style of each item. + + The indentation of the items is an integer value that causes each item to + be offset from the left margin by a certain amount. This value is read with + indent() and set with setIndent(). + + The style used to decorate each item is set with setStyle() and can be read + with the style() function. The style controls the type of bullet points and + numbering scheme used for items in the list. Note that lists that use the + decimal numbering scheme begin counting at 1 rather than 0. + + \sa QTextList +*/ + +/*! + \enum QTextListFormat::Style + + This enum describes the symbols used to decorate list items: + + \value ListDisc a filled circle + \value ListCircle an empty circle + \value ListSquare a filled square + \value ListDecimal decimal values in ascending order + \value ListLowerAlpha lower case Latin characters in alphabetical order + \value ListUpperAlpha upper case Latin characters in alphabetical order + \value ListLowerRoman lower case roman numerals (supports up to 4999 items only) + \value ListUpperRoman upper case roman numerals (supports up to 4999 items only) + \omitvalue ListStyleUndefined +*/ + +/*! + \fn QTextListFormat::QTextListFormat() + + Constructs a new list format object. +*/ +QTextListFormat::QTextListFormat() + : QTextFormat(ListFormat) +{ + setIndent(1); +} + +/*! + \internal + \fn QTextListFormat::QTextListFormat(const QTextFormat &other) + + Creates a new list format with the same attributes as the \a given + text format. +*/ +QTextListFormat::QTextListFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn bool QTextListFormat::isValid() const + + Returns true if this list format is valid; otherwise + returns false. +*/ + +/*! + \fn void QTextListFormat::setStyle(Style style) + + Sets the list format's \a style. + + \sa style() Style +*/ + +/*! + \fn Style QTextListFormat::style() const + + Returns the list format's style. + + \sa setStyle() Style +*/ + + +/*! + \fn void QTextListFormat::setIndent(int indentation) + + Sets the list format's \a indentation. + The indentation is multiplied by the QTextDocument::indentWidth + property to get the effective indent in pixels. + + \sa indent() +*/ + + +/*! + \fn int QTextListFormat::indent() const + + Returns the list format's indentation. + The indentation is multiplied by the QTextDocument::indentWidth + property to get the effective indent in pixels. + + \sa setIndent() +*/ + +/*! + \fn void QTextListFormat::setNumberPrefix(const QString &numberPrefix) + \since 4.8 + + Sets the list format's number prefix. This can be used with all + sorted list types. It does not have any effect on unsorted list types. + + \sa numberPrefix() +*/ + +/*! + \fn int QTextListFormat::numberPrefix() const + \since 4.8 + + Returns the list format's number prefix. + + \sa setNumberPrefix() +*/ + +/*! + \fn void QTextListFormat::setNumberSuffix(const QString &numberSuffix) + \since 4.8 + + Sets the list format's number suffix. This can be used with all + sorted list types. It does not have any effect on unsorted list types. + The default suffix is ".". + + \sa numberSuffix() +*/ + +/*! + \fn int QTextListFormat::numberSuffix() const + \since 4.8 + + Returns the list format's number suffix. + + \sa setNumberSuffix() +*/ + +/*! + \class QTextFrameFormat + \reentrant + + \brief The QTextFrameFormat class provides formatting information for + frames in a QTextDocument. + + \ingroup richtext-processing + + A text frame groups together one or more blocks of text, providing a layer + of structure larger than the paragraph. The format of a frame specifies + how it is rendered and positioned on the screen. It does not directly + specify the behavior of the text formatting within, but provides + constraints on the layout of its children. + + The frame format defines the width() and height() of the frame on the + screen. Each frame can have a border() that surrounds its contents with + a rectangular box. The border is surrounded by a margin() around the frame, + and the contents of the frame are kept separate from the border by the + frame's padding(). This scheme is similar to the box model used by Cascading + Style Sheets for HTML pages. + + \img qtextframe-style.png + + The position() of a frame is set using setPosition() and determines how it + is located relative to the surrounding text. + + The validity of a QTextFrameFormat object can be determined with the + isValid() function. + + \sa QTextFrame QTextBlockFormat +*/ + +/*! + \enum QTextFrameFormat::Position + + This enum describes how a frame is located relative to the surrounding text. + + \value InFlow + \value FloatLeft + \value FloatRight + + \sa position() CssFloat +*/ + +/*! + \enum QTextFrameFormat::BorderStyle + \since 4.3 + + This enum describes different border styles for the text frame. + + \value BorderStyle_None + \value BorderStyle_Dotted + \value BorderStyle_Dashed + \value BorderStyle_Solid + \value BorderStyle_Double + \value BorderStyle_DotDash + \value BorderStyle_DotDotDash + \value BorderStyle_Groove + \value BorderStyle_Ridge + \value BorderStyle_Inset + \value BorderStyle_Outset + + \sa borderStyle() FrameBorderStyle +*/ + +/*! + \fn QTextFrameFormat::QTextFrameFormat() + + Constructs a text frame format object with the default properties. +*/ +QTextFrameFormat::QTextFrameFormat() : QTextFormat(FrameFormat) +{ + setBorderStyle(BorderStyle_Outset); + setBorderBrush(Qt::darkGray); +} + +/*! + \internal + \fn QTextFrameFormat::QTextFrameFormat(const QTextFormat &other) + + Creates a new frame format with the same attributes as the \a given + text format. +*/ +QTextFrameFormat::QTextFrameFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn QTextFrameFormat::isValid() const + + Returns true if the format description is valid; otherwise returns false. +*/ + +/*! + \fn QTextFrameFormat::setPosition(Position policy) + + Sets the \a policy for positioning frames with this frame format. + +*/ + +/*! + \fn Position QTextFrameFormat::position() const + + Returns the positioning policy for frames with this frame format. +*/ + +/*! + \fn QTextFrameFormat::setBorder(qreal width) + + Sets the \a width (in pixels) of the frame's border. +*/ + +/*! + \fn qreal QTextFrameFormat::border() const + + Returns the width of the border in pixels. +*/ + +/*! + \fn QTextFrameFormat::setBorderBrush(const QBrush &brush) + \since 4.3 + + Sets the \a brush used for the frame's border. +*/ + +/*! + \fn QBrush QTextFrameFormat::borderBrush() const + \since 4.3 + + Returns the brush used for the frame's border. +*/ + +/*! + \fn QTextFrameFormat::setBorderStyle(BorderStyle style) + \since 4.3 + + Sets the \a style of the frame's border. +*/ + +/*! + \fn BorderStyle QTextFrameFormat::borderStyle() const + \since 4.3 + + Returns the style of the frame's border. +*/ + +/*! + \fn QTextFrameFormat::setMargin(qreal margin) + + Sets the frame's \a margin in pixels. + This method also sets the left, right, top and bottom margins + of the frame to the same value. The individual margins override + the general margin. +*/ +void QTextFrameFormat::setMargin(qreal amargin) +{ + setProperty(FrameMargin, amargin); + setProperty(FrameTopMargin, amargin); + setProperty(FrameBottomMargin, amargin); + setProperty(FrameLeftMargin, amargin); + setProperty(FrameRightMargin, amargin); +} + + +/*! + \fn qreal QTextFrameFormat::margin() const + + Returns the width of the frame's external margin in pixels. +*/ + +/*! + \fn QTextFrameFormat::setTopMargin(qreal margin) + \since 4.3 + + Sets the frame's top \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::topMargin() const + \since 4.3 + + Returns the width of the frame's top margin in pixels. +*/ +qreal QTextFrameFormat::topMargin() const +{ + if (!hasProperty(FrameTopMargin)) + return margin(); + return doubleProperty(FrameTopMargin); +} + +/*! + \fn QTextFrameFormat::setBottomMargin(qreal margin) + \since 4.3 + + Sets the frame's bottom \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::bottomMargin() const + \since 4.3 + + Returns the width of the frame's bottom margin in pixels. +*/ +qreal QTextFrameFormat::bottomMargin() const +{ + if (!hasProperty(FrameBottomMargin)) + return margin(); + return doubleProperty(FrameBottomMargin); +} + +/*! + \fn QTextFrameFormat::setLeftMargin(qreal margin) + \since 4.3 + + Sets the frame's left \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::leftMargin() const + \since 4.3 + + Returns the width of the frame's left margin in pixels. +*/ +qreal QTextFrameFormat::leftMargin() const +{ + if (!hasProperty(FrameLeftMargin)) + return margin(); + return doubleProperty(FrameLeftMargin); +} + +/*! + \fn QTextFrameFormat::setRightMargin(qreal margin) + \since 4.3 + + Sets the frame's right \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::rightMargin() const + \since 4.3 + + Returns the width of the frame's right margin in pixels. +*/ +qreal QTextFrameFormat::rightMargin() const +{ + if (!hasProperty(FrameRightMargin)) + return margin(); + return doubleProperty(FrameRightMargin); +} + +/*! + \fn QTextFrameFormat::setPadding(qreal width) + + Sets the \a width of the frame's internal padding in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::padding() const + + Returns the width of the frame's internal padding in pixels. +*/ + +/*! + \fn QTextFrameFormat::setWidth(const QTextLength &width) + + Sets the frame's border rectangle's \a width. + + \sa QTextLength +*/ + +/*! + \fn QTextFrameFormat::setWidth(qreal width) + \overload + + Convenience method that sets the width of the frame's border + rectangle's width to the specified fixed \a width. +*/ + +/*! + \fn QTextFormat::PageBreakFlags QTextFrameFormat::pageBreakPolicy() const + \since 4.2 + + Returns the currently set page break policy for the frame/table. The default is + QTextFormat::PageBreak_Auto. + + \sa setPageBreakPolicy() +*/ + +/*! + \fn void QTextFrameFormat::setPageBreakPolicy(PageBreakFlags policy) + \since 4.2 + + Sets the page break policy for the frame/table to \a policy. + + \sa pageBreakPolicy() +*/ + +/*! + \fn QTextLength QTextFrameFormat::width() const + + Returns the width of the frame's border rectangle. + + \sa QTextLength +*/ + +/*! + \fn void QTextFrameFormat::setHeight(const QTextLength &height) + + Sets the frame's \a height. +*/ + +/*! + \fn void QTextFrameFormat::setHeight(qreal height) + \overload + + Sets the frame's \a height. +*/ + +/*! + \fn qreal QTextFrameFormat::height() const + + Returns the height of the frame's border rectangle. +*/ + +/*! + \class QTextTableFormat + \reentrant + + \brief The QTextTableFormat class provides formatting information for + tables in a QTextDocument. + + \ingroup richtext-processing + + A table is a group of cells ordered into rows and columns. Each table + contains at least one row and one column. Each cell contains a block. + Tables in rich text documents are formatted using the properties + defined in this class. + + Tables are horizontally justified within their parent frame according to the + table's alignment. This can be read with the alignment() function and set + with setAlignment(). + + Cells within the table are separated by cell spacing. The number of pixels + between cells is set with setCellSpacing() and read with cellSpacing(). + The contents of each cell is surrounded by cell padding. The number of pixels + between each cell edge and its contents is set with setCellPadding() and read + with cellPadding(). + + \image qtexttableformat-cell.png + + The table's background color can be read with the background() function, + and can be specified with setBackground(). The background color of each + cell can be set independently, and will control the color of the cell within + the padded area. + + The table format also provides a way to constrain the widths of the columns + in the table. Columns can be assigned a fixed width, a variable width, or + a percentage of the available width (see QTextLength). The columns() function + returns the number of columns with constraints, and the + columnWidthConstraints() function returns the constraints defined for the + table. These quantities can also be set by calling setColumnWidthConstraints() + with a vector containing new constraints. If no constraints are + required, clearColumnWidthConstraints() can be used to remove them. + + \sa QTextTable QTextTableCell QTextLength +*/ + +/*! + \fn QTextTableFormat::QTextTableFormat() + + Constructs a new table format object. +*/ +QTextTableFormat::QTextTableFormat() + : QTextFrameFormat() +{ + setObjectType(TableObject); + setCellSpacing(2); + setBorder(1); +} + +/*! + \internal + \fn QTextTableFormat::QTextTableFormat(const QTextFormat &other) + + Creates a new table format with the same attributes as the \a given + text format. +*/ +QTextTableFormat::QTextTableFormat(const QTextFormat &fmt) + : QTextFrameFormat(fmt) +{ +} + +/*! + \fn bool QTextTableFormat::isValid() const + + Returns true if this table format is valid; otherwise + returns false. +*/ + + +/*! + \fn int QTextTableFormat::columns() const + + Returns the number of columns specified by the table format. +*/ + + +/*! + \internal + \fn void QTextTableFormat::setColumns(int columns) + + Sets the number of \a columns required by the table format. + + \sa columns() +*/ + +/*! + \fn void QTextTableFormat::clearColumnWidthConstraints() + + Clears the column width constraints for the table. + + \sa columnWidthConstraints() setColumnWidthConstraints() +*/ + +/*! + \fn void QTextTableFormat::setColumnWidthConstraints(const QVector &constraints) + + Sets the column width \a constraints for the table. + + \sa columnWidthConstraints() clearColumnWidthConstraints() +*/ + +/*! + \fn QVector QTextTableFormat::columnWidthConstraints() const + + Returns a list of constraints used by this table format to control the + appearance of columns in a table. + + \sa setColumnWidthConstraints() +*/ + +/*! + \fn qreal QTextTableFormat::cellSpacing() const + + Returns the table's cell spacing. This describes the distance between + adjacent cells. +*/ + +/*! + \fn void QTextTableFormat::setCellSpacing(qreal spacing) + + Sets the cell \a spacing for the table. This determines the distance + between adjacent cells. +*/ + +/*! + \fn qreal QTextTableFormat::cellPadding() const + + Returns the table's cell padding. This describes the distance between + the border of a cell and its contents. +*/ + +/*! + \fn void QTextTableFormat::setCellPadding(qreal padding) + + Sets the cell \a padding for the table. This determines the distance + between the border of a cell and its contents. +*/ + +/*! + \fn void QTextTableFormat::setAlignment(Qt::Alignment alignment) + + Sets the table's \a alignment. + + \sa alignment() +*/ + +/*! + \fn Qt::Alignment QTextTableFormat::alignment() const + + Returns the table's alignment. + + \sa setAlignment() +*/ + +/*! + \fn void QTextTableFormat::setHeaderRowCount(int count) + \since 4.2 + + Declares the first \a count rows of the table as table header. + The table header rows get repeated when a table is broken + across a page boundary. +*/ + +/*! + \fn int QTextTableFormat::headerRowCount() const + \since 4.2 + + Returns the number of rows in the table that define the header. + + \sa setHeaderRowCount() +*/ + +/*! + \fn void QTextFormat::setBackground(const QBrush &brush) + + Sets the brush use to paint the document's background to the + \a brush specified. + + \sa background() clearBackground() setForeground() +*/ + +/*! + \fn QColor QTextFormat::background() const + + Returns the brush used to paint the document's background. + + \sa setBackground() clearBackground() foreground() +*/ + +/*! + \fn void QTextFormat::clearBackground() + + Clears the brush used to paint the document's background. The default + brush will be used. + + \sa background() setBackground() clearForeground() +*/ + + +/*! + \class QTextImageFormat + \reentrant + + \brief The QTextImageFormat class provides formatting information for + images in a QTextDocument. + + \ingroup richtext-processing + + Inline images are represented by an object replacement character + (0xFFFC in Unicode) which has an associated QTextImageFormat. The + image format specifies a name with setName() that is used to + locate the image. The size of the rectangle that the image will + occupy is specified using setWidth() and setHeight(). + + Images can be supplied in any format for which Qt has an image + reader, so SVG drawings can be included alongside PNG, TIFF and + other bitmap formats. + + \sa QImage, QImageReader +*/ + +/*! + \fn QTextImageFormat::QTextImageFormat() + + Creates a new image format object. +*/ +QTextImageFormat::QTextImageFormat() : QTextCharFormat() { setObjectType(ImageObject); } + +/*! + \internal + \fn QTextImageFormat::QTextImageFormat(const QTextFormat &other) + + Creates a new image format with the same attributes as the \a given + text format. +*/ +QTextImageFormat::QTextImageFormat(const QTextFormat &fmt) + : QTextCharFormat(fmt) +{ +} + +/*! + \fn bool QTextImageFormat::isValid() const + + Returns true if this image format is valid; otherwise returns false. +*/ + + +/*! + \fn void QTextImageFormat::setName(const QString &name) + + Sets the \a name of the image. The \a name is used to locate the image + in the application's resources. + + \sa name() +*/ + + +/*! + \fn QString QTextImageFormat::name() const + + Returns the name of the image. The name refers to an entry in the + application's resources file. + + \sa setName() +*/ + +/*! + \fn void QTextImageFormat::setWidth(qreal width) + + Sets the \a width of the rectangle occupied by the image. + + \sa width() setHeight() +*/ + + +// ### Qt5 qreal replace with a QTextLength +/*! + \fn qreal QTextImageFormat::width() const + + Returns the width of the rectangle occupied by the image. + + \sa height() setWidth() +*/ + + +/*! + \fn void QTextImageFormat::setHeight(qreal height) + + Sets the \a height of the rectangle occupied by the image. + + \sa height() setWidth() +*/ + + +// ### Qt5 qreal replace with a QTextLength +/*! + \fn qreal QTextImageFormat::height() const + + Returns the height of the rectangle occupied by the image. + + \sa width() setHeight() +*/ + +/*! + \fn void QTextCharFormat::setFontCapitalization(QFont::Capitalization capitalization) + \since 4.4 + + Sets the capitalization of the text that apppears in this font to \a capitalization. + + A font's capitalization makes the text appear in the selected capitalization mode. + + \sa fontCapitalization() +*/ + +/*! + \fn Capitalization QTextCharFormat::fontCapitalization() const + \since 4.4 + + Returns the current capitalization type of the font. +*/ + +/*! + \fn void QTextCharFormat::setFontLetterSpacing(qreal spacing) + \since 4.4 + + Sets the letter spacing of this format to the given \a spacing, in percent. + A value of 100 indicates default spacing; a value of 200 doubles the amount + of space a letter takes. + + \sa fontLetterSpacing() +*/ + +/*! + \fn qreal QTextCharFormat::fontLetterSpacing() const + \since 4.4 + + Returns the current letter spacing percentage. +*/ + +/*! + \fn void QTextCharFormat::setFontWordSpacing(qreal spacing) + \since 4.4 + + Sets the word spacing of this format to the given \a spacing, in pixels. + + \sa fontWordSpacing() +*/ + +/*! + \fn qreal QTextCharFormat::fontWordSpacing() const + \since 4.4 + + Returns the current word spacing value. +*/ + +/*! + \fn qreal QTextTableCellFormat::topPadding() const + \since 4.4 + + Gets the top padding of the table cell. + + \sa setTopPadding(), leftPadding(), rightPadding(), bottomPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::bottomPadding() const + \since 4.4 + + Gets the bottom padding of the table cell. + + \sa setBottomPadding(), leftPadding(), rightPadding(), topPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::leftPadding() const + \since 4.4 + + Gets the left padding of the table cell. + + \sa setLeftPadding(), rightPadding(), topPadding(), bottomPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::rightPadding() const + \since 4.4 + + Gets the right padding of the table cell. + + \sa setRightPadding(), leftPadding(), topPadding(), bottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setTopPadding(qreal padding) + \since 4.4 + + Sets the top \a padding of the table cell. + + \sa topPadding(), setLeftPadding(), setRightPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setBottomPadding(qreal padding) + \since 4.4 + + Sets the bottom \a padding of the table cell. + + \sa bottomPadding(), setLeftPadding(), setRightPadding(), setTopPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setLeftPadding(qreal padding) + \since 4.4 + + Sets the left \a padding of the table cell. + + \sa leftPadding(), setRightPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setRightPadding(qreal padding) + \since 4.4 + + Sets the right \a padding of the table cell. + + \sa rightPadding(), setLeftPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setPadding(qreal padding) + \since 4.4 + + Sets the left, right, top, and bottom \a padding of the table cell. + + \sa setLeftPadding(), setRightPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn bool QTextTableCellFormat::isValid() const + \since 4.4 + + Returns true if this table cell format is valid; otherwise returns false. +*/ + +/*! + \fn QTextTableCellFormat::QTextTableCellFormat() + \since 4.4 + + Constructs a new table cell format object. +*/ +QTextTableCellFormat::QTextTableCellFormat() + : QTextCharFormat() +{ + setObjectType(TableCellObject); +} + +/*! + \internal + \fn QTextTableCellFormat::QTextTableCellFormat(const QTextFormat &other) + + Creates a new table cell format with the same attributes as the \a given + text format. +*/ +QTextTableCellFormat::QTextTableCellFormat(const QTextFormat &fmt) + : QTextCharFormat(fmt) +{ +} + +/*! + \class QTextTableCellFormat + \reentrant + \since 4.4 + + \brief The QTextTableCellFormat class provides formatting information for + table cells in a QTextDocument. + + \ingroup richtext-processing + + The table cell format of a table cell in a document specifies the visual + properties of the table cell. + + The padding properties of a table cell are controlled by setLeftPadding(), + setRightPadding(), setTopPadding(), and setBottomPadding(). All the paddings + can be set at once using setPadding(). + + \sa QTextFormat QTextBlockFormat QTextTableFormat QTextCharFormat +*/ + +// ------------------------------------------------------ + + +QTextFormatCollection::QTextFormatCollection(const QTextFormatCollection &rhs) +{ + formats = rhs.formats; + objFormats = rhs.objFormats; +} + +QTextFormatCollection &QTextFormatCollection::operator=(const QTextFormatCollection &rhs) +{ + formats = rhs.formats; + objFormats = rhs.objFormats; + return *this; +} + +QTextFormatCollection::~QTextFormatCollection() +{ +} + +int QTextFormatCollection::indexForFormat(const QTextFormat &format) +{ + uint hash = getHash(format.d, format.format_type); + QMultiHash::const_iterator i = hashes.find(hash); + while (i != hashes.end() && i.key() == hash) { + if (formats.value(i.value()) == format) { + return i.value(); + } + ++i; + } + + int idx = formats.size(); + formats.append(format); + + QT_TRY{ + QTextFormat &f = formats.last(); + if (!f.d) + f.d = new QTextFormatPrivate; + f.d->resolveFont(defaultFnt); + + if (!hashes.contains(hash, idx)) + hashes.insert(hash, idx); + + } QT_CATCH(...) { + formats.pop_back(); + QT_RETHROW; + } + return idx; +} + +bool QTextFormatCollection::hasFormatCached(const QTextFormat &format) const +{ + uint hash = getHash(format.d, format.format_type); + QMultiHash::const_iterator i = hashes.find(hash); + while (i != hashes.end() && i.key() == hash) { + if (formats.value(i.value()) == format) { + return true; + } + ++i; + } + return false; +} + +QTextFormat QTextFormatCollection::objectFormat(int objectIndex) const +{ + if (objectIndex == -1) + return QTextFormat(); + return format(objFormats.at(objectIndex)); +} + +void QTextFormatCollection::setObjectFormat(int objectIndex, const QTextFormat &f) +{ + const int formatIndex = indexForFormat(f); + objFormats[objectIndex] = formatIndex; +} + +int QTextFormatCollection::objectFormatIndex(int objectIndex) const +{ + if (objectIndex == -1) + return -1; + return objFormats.at(objectIndex); +} + +void QTextFormatCollection::setObjectFormatIndex(int objectIndex, int formatIndex) +{ + objFormats[objectIndex] = formatIndex; +} + +int QTextFormatCollection::createObjectIndex(const QTextFormat &f) +{ + const int objectIndex = objFormats.size(); + objFormats.append(indexForFormat(f)); + return objectIndex; +} + +QTextFormat QTextFormatCollection::format(int idx) const +{ + if (idx < 0 || idx >= formats.count()) + return QTextFormat(); + + return formats.at(idx); +} + +void QTextFormatCollection::setDefaultFont(const QFont &f) +{ + defaultFnt = f; + for (int i = 0; i < formats.count(); ++i) + if (formats[i].d) + formats[i].d->resolveFont(defaultFnt); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h new file mode 100644 index 0000000000..ff28eaa1b2 --- /dev/null +++ b/src/gui/text/qtextformat.h @@ -0,0 +1,976 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTFORMAT_H +#define QTEXTFORMAT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QVariant; +class QFont; + +class QTextFormatCollection; +class QTextFormatPrivate; +class QTextBlockFormat; +class QTextCharFormat; +class QTextListFormat; +class QTextTableFormat; +class QTextFrameFormat; +class QTextImageFormat; +class QTextTableCellFormat; +class QTextFormat; +class QTextObject; +class QTextCursor; +class QTextDocument; +class QTextLength; + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextLength &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextLength &); +#endif + +class Q_GUI_EXPORT QTextLength +{ +public: + enum Type { VariableLength = 0, FixedLength, PercentageLength }; + + inline QTextLength() : lengthType(VariableLength), fixedValueOrPercentage(0) {} + + inline explicit QTextLength(Type type, qreal value); + + inline Type type() const { return lengthType; } + inline qreal value(qreal maximumLength) const + { + switch (lengthType) { + case FixedLength: return fixedValueOrPercentage; + case VariableLength: return maximumLength; + case PercentageLength: return fixedValueOrPercentage * maximumLength / qreal(100); + } + return -1; + } + + inline qreal rawValue() const { return fixedValueOrPercentage; } + + inline bool operator==(const QTextLength &other) const + { return lengthType == other.lengthType + && qFuzzyCompare(fixedValueOrPercentage, other.fixedValueOrPercentage); } + inline bool operator!=(const QTextLength &other) const + { return lengthType != other.lengthType + || !qFuzzyCompare(fixedValueOrPercentage, other.fixedValueOrPercentage); } + operator QVariant() const; + +private: + Type lengthType; + qreal fixedValueOrPercentage; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextLength &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextLength &); +}; + +inline QTextLength::QTextLength(Type atype, qreal avalue) + : lengthType(atype), fixedValueOrPercentage(avalue) {} + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextFormat &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextFormat &); +#endif + +class Q_GUI_EXPORT QTextFormat +{ + Q_GADGET + Q_ENUMS(FormatType Property ObjectTypes) +public: + enum FormatType { + InvalidFormat = -1, + BlockFormat = 1, + CharFormat = 2, + ListFormat = 3, + TableFormat = 4, + FrameFormat = 5, + + UserFormat = 100 + }; + + enum Property { + ObjectIndex = 0x0, + + // paragraph and char + CssFloat = 0x0800, + LayoutDirection = 0x0801, + + OutlinePen = 0x810, + BackgroundBrush = 0x820, + ForegroundBrush = 0x821, + // Internal to qtextlayout.cpp: ObjectSelectionBrush = 0x822 + BackgroundImageUrl = 0x823, + + // paragraph + BlockAlignment = 0x1010, + BlockTopMargin = 0x1030, + BlockBottomMargin = 0x1031, + BlockLeftMargin = 0x1032, + BlockRightMargin = 0x1033, + TextIndent = 0x1034, + TabPositions = 0x1035, + BlockIndent = 0x1040, + LineHeight = 0x1048, + LineHeightType = 0x1049, + BlockNonBreakableLines = 0x1050, + BlockTrailingHorizontalRulerWidth = 0x1060, + + // character properties + FirstFontProperty = 0x1FE0, + FontCapitalization = FirstFontProperty, + FontLetterSpacing = 0x1FE1, + FontWordSpacing = 0x1FE2, + FontStyleHint = 0x1FE3, + FontStyleStrategy = 0x1FE4, + FontKerning = 0x1FE5, + FontHintingPreference = 0x1FE6, + FontFamily = 0x2000, + FontPointSize = 0x2001, + FontSizeAdjustment = 0x2002, + FontSizeIncrement = FontSizeAdjustment, // old name, compat + FontWeight = 0x2003, + FontItalic = 0x2004, + FontUnderline = 0x2005, // deprecated, use TextUnderlineStyle instead + FontOverline = 0x2006, + FontStrikeOut = 0x2007, + FontFixedPitch = 0x2008, + FontPixelSize = 0x2009, + LastFontProperty = FontPixelSize, + + TextUnderlineColor = 0x2010, + TextVerticalAlignment = 0x2021, + TextOutline = 0x2022, + TextUnderlineStyle = 0x2023, + TextToolTip = 0x2024, + + IsAnchor = 0x2030, + AnchorHref = 0x2031, + AnchorName = 0x2032, + ObjectType = 0x2f00, + + // list properties + ListStyle = 0x3000, + ListIndent = 0x3001, + ListNumberPrefix = 0x3002, + ListNumberSuffix = 0x3003, + + // table and frame properties + FrameBorder = 0x4000, + FrameMargin = 0x4001, + FramePadding = 0x4002, + FrameWidth = 0x4003, + FrameHeight = 0x4004, + FrameTopMargin = 0x4005, + FrameBottomMargin = 0x4006, + FrameLeftMargin = 0x4007, + FrameRightMargin = 0x4008, + FrameBorderBrush = 0x4009, + FrameBorderStyle = 0x4010, + + TableColumns = 0x4100, + TableColumnWidthConstraints = 0x4101, + TableCellSpacing = 0x4102, + TableCellPadding = 0x4103, + TableHeaderRowCount = 0x4104, + + // table cell properties + TableCellRowSpan = 0x4810, + TableCellColumnSpan = 0x4811, + + TableCellTopPadding = 0x4812, + TableCellBottomPadding = 0x4813, + TableCellLeftPadding = 0x4814, + TableCellRightPadding = 0x4815, + + // image properties + ImageName = 0x5000, + ImageWidth = 0x5010, + ImageHeight = 0x5011, + + // internal + /* + SuppressText = 0x5012, + SuppressBackground = 0x513 + */ + + // selection properties + FullWidthSelection = 0x06000, + + // page break properties + PageBreakPolicy = 0x7000, + + // -- + UserProperty = 0x100000 + }; + + enum ObjectTypes { + NoObject, + ImageObject, + TableObject, + TableCellObject, + + UserObject = 0x1000 + }; + + enum PageBreakFlag { + PageBreak_Auto = 0, + PageBreak_AlwaysBefore = 0x001, + PageBreak_AlwaysAfter = 0x010 + // PageBreak_AlwaysInside = 0x100 + }; + Q_DECLARE_FLAGS(PageBreakFlags, PageBreakFlag) + + QTextFormat(); + + explicit QTextFormat(int type); + + QTextFormat(const QTextFormat &rhs); + QTextFormat &operator=(const QTextFormat &rhs); + ~QTextFormat(); + + void merge(const QTextFormat &other); + + inline bool isValid() const { return type() != InvalidFormat; } + + int type() const; + + int objectIndex() const; + void setObjectIndex(int object); + + QVariant property(int propertyId) const; + void setProperty(int propertyId, const QVariant &value); + void clearProperty(int propertyId); + bool hasProperty(int propertyId) const; + + bool boolProperty(int propertyId) const; + int intProperty(int propertyId) const; + qreal doubleProperty(int propertyId) const; + QString stringProperty(int propertyId) const; + QColor colorProperty(int propertyId) const; + QPen penProperty(int propertyId) const; + QBrush brushProperty(int propertyId) const; + QTextLength lengthProperty(int propertyId) const; + QVector lengthVectorProperty(int propertyId) const; + + void setProperty(int propertyId, const QVector &lengths); + + QMap properties() const; + int propertyCount() const; + + inline void setObjectType(int type); + inline int objectType() const + { return intProperty(ObjectType); } + + inline bool isCharFormat() const { return type() == CharFormat; } + inline bool isBlockFormat() const { return type() == BlockFormat; } + inline bool isListFormat() const { return type() == ListFormat; } + inline bool isFrameFormat() const { return type() == FrameFormat; } + inline bool isImageFormat() const { return type() == CharFormat && objectType() == ImageObject; } + inline bool isTableFormat() const { return type() == FrameFormat && objectType() == TableObject; } + inline bool isTableCellFormat() const { return type() == CharFormat && objectType() == TableCellObject; } + + QTextBlockFormat toBlockFormat() const; + QTextCharFormat toCharFormat() const; + QTextListFormat toListFormat() const; + QTextTableFormat toTableFormat() const; + QTextFrameFormat toFrameFormat() const; + QTextImageFormat toImageFormat() const; + QTextTableCellFormat toTableCellFormat() const; + + bool operator==(const QTextFormat &rhs) const; + inline bool operator!=(const QTextFormat &rhs) const { return !operator==(rhs); } + operator QVariant() const; + + inline void setLayoutDirection(Qt::LayoutDirection direction) + { setProperty(QTextFormat::LayoutDirection, direction); } + inline Qt::LayoutDirection layoutDirection() const + { return Qt::LayoutDirection(intProperty(QTextFormat::LayoutDirection)); } + + inline void setBackground(const QBrush &brush) + { setProperty(BackgroundBrush, brush); } + inline QBrush background() const + { return brushProperty(BackgroundBrush); } + inline void clearBackground() + { clearProperty(BackgroundBrush); } + + inline void setForeground(const QBrush &brush) + { setProperty(ForegroundBrush, brush); } + inline QBrush foreground() const + { return brushProperty(ForegroundBrush); } + inline void clearForeground() + { clearProperty(ForegroundBrush); } + +private: + QSharedDataPointer d; + qint32 format_type; + + friend class QTextFormatCollection; + friend class QTextCharFormat; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextFormat &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextFormat &); +}; + +inline void QTextFormat::setObjectType(int atype) +{ setProperty(ObjectType, atype); } + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextFormat::PageBreakFlags) + +class Q_GUI_EXPORT QTextCharFormat : public QTextFormat +{ +public: + enum VerticalAlignment { + AlignNormal = 0, + AlignSuperScript, + AlignSubScript, + AlignMiddle, + AlignTop, + AlignBottom + }; + enum UnderlineStyle { // keep in sync with Qt::PenStyle! + NoUnderline, + SingleUnderline, + DashUnderline, + DotLine, + DashDotLine, + DashDotDotLine, + WaveUnderline, + SpellCheckUnderline + }; + + QTextCharFormat(); + + bool isValid() const { return isCharFormat(); } + void setFont(const QFont &font); + QFont font() const; + + inline void setFontFamily(const QString &family) + { setProperty(FontFamily, family); } + inline QString fontFamily() const + { return stringProperty(FontFamily); } + + inline void setFontPointSize(qreal size) + { setProperty(FontPointSize, size); } + inline qreal fontPointSize() const + { return doubleProperty(FontPointSize); } + + inline void setFontWeight(int weight) + { if (weight == QFont::Normal) weight = 0; setProperty(FontWeight, weight); } + inline int fontWeight() const + { int weight = intProperty(FontWeight); if (weight == 0) weight = QFont::Normal; return weight; } + inline void setFontItalic(bool italic) + { setProperty(FontItalic, italic); } + inline bool fontItalic() const + { return boolProperty(FontItalic); } + inline void setFontCapitalization(QFont::Capitalization capitalization) + { setProperty(FontCapitalization, capitalization); } + inline QFont::Capitalization fontCapitalization() const + { return static_cast(intProperty(FontCapitalization)); } + inline void setFontLetterSpacing(qreal spacing) + { setProperty(FontLetterSpacing, spacing); } + inline qreal fontLetterSpacing() const + { return doubleProperty(FontLetterSpacing); } + inline void setFontWordSpacing(qreal spacing) + { setProperty(FontWordSpacing, spacing); } + inline qreal fontWordSpacing() const + { return doubleProperty(FontWordSpacing); } + + inline void setFontUnderline(bool underline) + { setProperty(TextUnderlineStyle, underline ? SingleUnderline : NoUnderline); } + bool fontUnderline() const; + + inline void setFontOverline(bool overline) + { setProperty(FontOverline, overline); } + inline bool fontOverline() const + { return boolProperty(FontOverline); } + + inline void setFontStrikeOut(bool strikeOut) + { setProperty(FontStrikeOut, strikeOut); } + inline bool fontStrikeOut() const + { return boolProperty(FontStrikeOut); } + + inline void setUnderlineColor(const QColor &color) + { setProperty(TextUnderlineColor, color); } + inline QColor underlineColor() const + { return colorProperty(TextUnderlineColor); } + + inline void setFontFixedPitch(bool fixedPitch) + { setProperty(FontFixedPitch, fixedPitch); } + inline bool fontFixedPitch() const + { return boolProperty(FontFixedPitch); } + + inline void setFontStyleHint(QFont::StyleHint hint, QFont::StyleStrategy strategy = QFont::PreferDefault) + { setProperty(FontStyleHint, hint); setProperty(FontStyleStrategy, strategy); } + inline void setFontStyleStrategy(QFont::StyleStrategy strategy) + { setProperty(FontStyleStrategy, strategy); } + QFont::StyleHint fontStyleHint() const + { return static_cast(intProperty(FontStyleHint)); } + QFont::StyleStrategy fontStyleStrategy() const + { return static_cast(intProperty(FontStyleStrategy)); } + + inline void setFontHintingPreference(QFont::HintingPreference hintingPreference) + { + setProperty(FontHintingPreference, hintingPreference); + } + + inline QFont::HintingPreference fontHintingPreference() const + { + return static_cast(intProperty(FontHintingPreference)); + } + + inline void setFontKerning(bool enable) + { setProperty(FontKerning, enable); } + inline bool fontKerning() const + { return boolProperty(FontKerning); } + + void setUnderlineStyle(UnderlineStyle style); + inline UnderlineStyle underlineStyle() const + { return static_cast(intProperty(TextUnderlineStyle)); } + + inline void setVerticalAlignment(VerticalAlignment alignment) + { setProperty(TextVerticalAlignment, alignment); } + inline VerticalAlignment verticalAlignment() const + { return static_cast(intProperty(TextVerticalAlignment)); } + + inline void setTextOutline(const QPen &pen) + { setProperty(TextOutline, pen); } + inline QPen textOutline() const + { return penProperty(TextOutline); } + + inline void setToolTip(const QString &tip) + { setProperty(TextToolTip, tip); } + inline QString toolTip() const + { return stringProperty(TextToolTip); } + + inline void setAnchor(bool anchor) + { setProperty(IsAnchor, anchor); } + inline bool isAnchor() const + { return boolProperty(IsAnchor); } + + inline void setAnchorHref(const QString &value) + { setProperty(AnchorHref, value); } + inline QString anchorHref() const + { return stringProperty(AnchorHref); } + + inline void setAnchorName(const QString &name) + { setAnchorNames(QStringList(name)); } + QString anchorName() const; + + inline void setAnchorNames(const QStringList &names) + { setProperty(AnchorName, names); } + QStringList anchorNames() const; + + inline void setTableCellRowSpan(int tableCellRowSpan); + inline int tableCellRowSpan() const + { int s = intProperty(TableCellRowSpan); if (s == 0) s = 1; return s; } + inline void setTableCellColumnSpan(int tableCellColumnSpan); + inline int tableCellColumnSpan() const + { int s = intProperty(TableCellColumnSpan); if (s == 0) s = 1; return s; } + +protected: + explicit QTextCharFormat(const QTextFormat &fmt); + friend class QTextFormat; +}; + +inline void QTextCharFormat::setTableCellRowSpan(int _tableCellRowSpan) +{ + if (_tableCellRowSpan <= 1) + clearProperty(TableCellRowSpan); // the getter will return 1 here. + else + setProperty(TableCellRowSpan, _tableCellRowSpan); +} + +inline void QTextCharFormat::setTableCellColumnSpan(int _tableCellColumnSpan) +{ + if (_tableCellColumnSpan <= 1) + clearProperty(TableCellColumnSpan); // the getter will return 1 here. + else + setProperty(TableCellColumnSpan, _tableCellColumnSpan); +} + +class Q_GUI_EXPORT QTextBlockFormat : public QTextFormat +{ +public: + enum LineHeightTypes { + SingleHeight = 0, + ProportionalHeight = 1, + FixedHeight = 2, + MinimumHeight = 3, + LineDistanceHeight = 4 + }; + + QTextBlockFormat(); + + bool isValid() const { return isBlockFormat(); } + + inline void setAlignment(Qt::Alignment alignment); + inline Qt::Alignment alignment() const + { int a = intProperty(BlockAlignment); if (a == 0) a = Qt::AlignLeft; return QFlag(a); } + + inline void setTopMargin(qreal margin) + { setProperty(BlockTopMargin, margin); } + inline qreal topMargin() const + { return doubleProperty(BlockTopMargin); } + + inline void setBottomMargin(qreal margin) + { setProperty(BlockBottomMargin, margin); } + inline qreal bottomMargin() const + { return doubleProperty(BlockBottomMargin); } + + inline void setLeftMargin(qreal margin) + { setProperty(BlockLeftMargin, margin); } + inline qreal leftMargin() const + { return doubleProperty(BlockLeftMargin); } + + inline void setRightMargin(qreal margin) + { setProperty(BlockRightMargin, margin); } + inline qreal rightMargin() const + { return doubleProperty(BlockRightMargin); } + + inline void setTextIndent(qreal aindent) + { setProperty(TextIndent, aindent); } + inline qreal textIndent() const + { return doubleProperty(TextIndent); } + + inline void setIndent(int indent); + inline int indent() const + { return intProperty(BlockIndent); } + + inline void setLineHeight(qreal height, int heightType) + { setProperty(LineHeight, height); setProperty(LineHeightType, heightType); } + inline qreal lineHeight(qreal scriptLineHeight, qreal scaling) const; + inline qreal lineHeight() const + { return doubleProperty(LineHeight); } + inline int lineHeightType() const + { return intProperty(LineHeightType); } + + inline void setNonBreakableLines(bool b) + { setProperty(BlockNonBreakableLines, b); } + inline bool nonBreakableLines() const + { return boolProperty(BlockNonBreakableLines); } + + inline void setPageBreakPolicy(PageBreakFlags flags) + { setProperty(PageBreakPolicy, int(flags)); } + inline PageBreakFlags pageBreakPolicy() const + { return PageBreakFlags(intProperty(PageBreakPolicy)); } + + void setTabPositions(const QList &tabs); + QList tabPositions() const; + +protected: + explicit QTextBlockFormat(const QTextFormat &fmt); + friend class QTextFormat; +}; + +inline void QTextBlockFormat::setAlignment(Qt::Alignment aalignment) +{ setProperty(BlockAlignment, int(aalignment)); } + +inline void QTextBlockFormat::setIndent(int aindent) +{ setProperty(BlockIndent, aindent); } + +inline qreal QTextBlockFormat::lineHeight(qreal scriptLineHeight, qreal scaling = 1.0) const +{ + switch(intProperty(LineHeightType)) { + case SingleHeight: + return(scriptLineHeight); + case ProportionalHeight: + return(scriptLineHeight * doubleProperty(LineHeight) / 100.0); + case FixedHeight: + return(doubleProperty(LineHeight) * scaling); + case MinimumHeight: + return(qMax(scriptLineHeight, doubleProperty(LineHeight) * scaling)); + case LineDistanceHeight: + return(scriptLineHeight + doubleProperty(LineHeight) * scaling); + } + return(0); +} + +class Q_GUI_EXPORT QTextListFormat : public QTextFormat +{ +public: + QTextListFormat(); + + bool isValid() const { return isListFormat(); } + + enum Style { + ListDisc = -1, + ListCircle = -2, + ListSquare = -3, + ListDecimal = -4, + ListLowerAlpha = -5, + ListUpperAlpha = -6, + ListLowerRoman = -7, + ListUpperRoman = -8, + ListStyleUndefined = 0 + }; + + inline void setStyle(Style style); + inline Style style() const + { return static_cast
blah